Skip to content

Commit 810c290

Browse files
authored
Merge pull request #64 from keichi/pointer
Add pointer parser
2 parents c797760 + 53fb0cb commit 810c290

File tree

5 files changed

+237
-22
lines changed

5 files changed

+237
-22
lines changed

README.md

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,24 @@ data. Binary-parser dynamically generates and compiles the parser code
1010
on-the-fly, which runs as fast as a hand-written parser (which takes much more
1111
time and effort to write). Supported data types are:
1212

13-
- Integers (supports 8, 16, 32 bit signed- and unsigned integers)
14-
- Floating point numbers (supports 32 and 64 bit floating point values)
15-
- Bit fields (supports bit fields with length from 1 to 32 bits)
16-
- Strings (supports various encodings, fixed-length and variable-length, zero
17-
terminated string)
18-
- Arrays (supports user-defined element type, fixed-length and variable-length)
19-
- Choices
13+
- [Integers](#uint8-16-32-64le-bename-options) (supports 8, 16, 32 and 64bit
14+
signed and unsigned integers)
15+
- [Floating point numbers](#float-doublele-bename-options) (supports 32 and 64
16+
bit floating point values)
17+
- [Bit fields](#bit1-32name-options) (supports bit fields with length from 1
18+
to 32 bits)
19+
- [Strings](#stringname-options) (supports various encodings, fixed-length and
20+
variable-length, zero terminated string)
21+
- [Arrays](#arrayname-options) (supports user-defined element type,
22+
fixed-length and variable-length)
23+
- [Choices](#choicename-options)
24+
- [Pointers](#pointername-options)
2025
- User defined types
2126

22-
This library's features are inspired by [BinData](https://github.com/dmendel/bindata)
23-
, its syntax by [binary](https://github.com/substack/node-binary).
27+
Binary-parser is inspired by [BinData](https://github.com/dmendel/bindata)
28+
and [binary](https://github.com/substack/node-binary).
2429

2530
## Installation
26-
Binary-parser can be installed with [npm](https://npmjs.org/):
2731

2832
```shell
2933
$ npm install binary-parser
@@ -78,8 +82,8 @@ nothing.
7882

7983
### parse(buffer)
8084
Parse a `Buffer` object `buffer` with this parser and return the resulting
81-
object. When `parse(buffer)` is called for the first time, parser code is
82-
compiled on-the-fly and internally cached.
85+
object. When `parse(buffer)` is called for the first time, the associated
86+
parser code is compiled on-the-fly and internally cached.
8387

8488
### create(constructorFunction)
8589
Set the constructor function that should be called to create the object
@@ -90,11 +94,11 @@ Parse bytes as an integer and store it in a variable named `name`. `name`
9094
should consist only of alphanumeric characters and start with an alphabet.
9195
Number of bits can be chosen from 8, 16, 32 and 64. Byte-ordering can be either
9296
`l` for little endian or `b` for big endian. With no prefix, it parses as a
93-
signed number, with `u` prefixed as an unsigned number. The runtime type
94-
returned by the 8, 16, 32 bit methods is `number` while the type
95-
returned by the 64 bit is `bigint`.
96-
97-
**NOTE:** [u]int64{be,le} methods only work if your runtime is Nodejs v12.0.0 or
97+
signed number, with `u` prefixed as an unsigned number. The runtime type
98+
returned by the 8, 16, 32 bit methods is `number` while the type
99+
returned by the 64 bit is `bigint`.
100+
101+
**NOTE:** [u]int64{be,le} methods only work if your runtime is node v12.0.0 or
98102
greater. Lower version will throw a runtime error.
99103

100104
```javascript
@@ -277,6 +281,14 @@ current object. `options` is an object which can have the following keys:
277281

278282
- `type` - (Required) A `Parser` object.
279283

284+
### pointer(name [,options])
285+
Jump to `offset`, execute parser for `type` and rewind to previous offset.
286+
287+
- `type` - (Required) A `Parser` object.
288+
- `offset` - (Required) Note that this indicates absolute offset from the
289+
start of the input buffer. Can be a string `[u]int{8, 16, 32, 64}{le, be}`
290+
or an user defined Parser object.
291+
280292
### skip(length)
281293
Skip parsing for `length` bytes.
282294

@@ -398,7 +410,7 @@ Dynamically generates the code for this parser and returns it as a string.
398410
Usually used for debugging.
399411

400412
### Common options
401-
These are common options that can be specified in all parsers.
413+
These options can be used in all parsers.
402414

403415
- `formatter` - Function that transforms the parsed value into a more desired
404416
form.
@@ -437,11 +449,10 @@ These are common options that can be specified in all parsers.
437449
```
438450

439451
## Examples
440-
See `example` for more complex examples.
452+
See `example/` for real-world examples.
441453

442454
## Support
443455
Please report issues to the
444-
[issue tracker](https://github.com/Keichi/binary-parser/issues) if you have
456+
[issue tracker](https://github.com/keichi/binary-parser/issues) if you have
445457
any difficulties using this module, found a bug, or request a new feature.
446-
447-
Pull requests with fixes and improvements are welcomed!
458+
Pull requests are welcomed.

example/elf32.js

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
var Parser = require('../dist/binary_parser').Parser;
2+
3+
var ELF32ProgramHeader = new Parser()
4+
.endianess('little')
5+
.uint32('type')
6+
.uint32('offset')
7+
.uint32('vaddr')
8+
.uint32('paddr')
9+
.uint32('filesz')
10+
.uint32('memsz')
11+
.uint32('flags')
12+
.uint32('align');
13+
14+
var ELF32ProgramHeaderTable = new Parser().array('items', {
15+
type: ELF32ProgramHeader,
16+
length: function(vars) {
17+
return vars.phnum;
18+
},
19+
});
20+
21+
var ELF32SectionHeader = new Parser()
22+
.endianess('little')
23+
.uint32('name')
24+
.uint32('type')
25+
.uint32('flags')
26+
.uint32('address')
27+
.uint32('offset')
28+
.uint32('size')
29+
.uint32('link')
30+
.uint32('info')
31+
.uint32('addralign')
32+
.uint32('entsize');
33+
34+
var ELF32SectionHeaderTable = new Parser().array('items', {
35+
type: ELF32SectionHeader,
36+
length: function(vars) {
37+
return vars.shnum;
38+
},
39+
});
40+
41+
var ELF32SectionHeaderStringTable = new Parser().skip(1).array('items', {
42+
type: new Parser().string('name', { zeroTerminated: true }),
43+
lengthInBytes: function(vars) {
44+
var shstr = vars.section_headers.items[vars.shstrndx];
45+
return shstr.size - 1;
46+
},
47+
});
48+
49+
var ELF32Header = new Parser()
50+
.endianess('little')
51+
.buffer('ident', { length: 16 })
52+
.uint16('type')
53+
.uint16('machine')
54+
.uint32('version')
55+
.uint32('entry')
56+
.uint32('phoff')
57+
.uint32('shoff')
58+
.uint32('flags')
59+
.uint16('ehsize')
60+
.uint16('phentsize')
61+
.uint16('phnum')
62+
.uint16('shentsize')
63+
.uint16('shnum')
64+
.uint16('shstrndx')
65+
.pointer('program_headers', {
66+
type: ELF32ProgramHeaderTable,
67+
offset: 'phoff',
68+
})
69+
.pointer('section_headers', {
70+
type: ELF32SectionHeaderTable,
71+
offset: 'shoff',
72+
})
73+
.pointer('strings', {
74+
type: ELF32SectionHeaderStringTable,
75+
offset: function() {
76+
var shstr = vars.section_headers.items[vars.shstrndx];
77+
return shstr.offset;
78+
},
79+
});
80+
81+
require('fs').readFile('hello', function(err, data) {
82+
var result = ELF32Header.parse(data);
83+
console.log(require('util').inspect(result, { depth: null }));
84+
});

example/hello

7.13 KB
Binary file not shown.

lib/binary_parser.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ interface ParserOptions {
2121
stripNull?: null;
2222
key?: null;
2323
tag?: null;
24+
offset?: null;
2425
}
2526

2627
type Types = PrimitiveTypes | ComplexTypes;
@@ -33,6 +34,7 @@ type ComplexTypes =
3334
| 'choice'
3435
| 'nest'
3536
| 'skip'
37+
| 'pointer'
3638
| '';
3739

3840
type Endianess = 'be' | 'le';
@@ -148,6 +150,7 @@ const CAPITILIZED_TYPE_NAMES: { [key in Types]: string } = {
148150
choice: 'Choice',
149151
nest: 'Nest',
150152
skip: 'Skip',
153+
pointer: 'Pointer',
151154
'': '',
152155
};
153156

@@ -510,6 +513,32 @@ export class Parser {
510513
return this.setNextParser('nest', varName as string, options);
511514
}
512515

516+
pointer(varName: string, options?: ParserOptions) {
517+
if (!options.offset) {
518+
throw new Error('Offset option of pointer is not defined.');
519+
}
520+
521+
if (!options.type) {
522+
throw new Error('Type option of pointer is not defined.');
523+
} else if (typeof options.type === 'string') {
524+
if (
525+
Object.keys(PRIMITIVE_SIZES).indexOf(options.type) < 0 &&
526+
!aliasRegistry[options.type]
527+
) {
528+
throw new Error(
529+
'Specified type "' + options.type + '" is not supported.'
530+
);
531+
}
532+
} else if (options.type instanceof Parser) {
533+
} else {
534+
throw new Error(
535+
'Type option of pointer must be a string or a Parser object.'
536+
);
537+
}
538+
539+
return this.setNextParser('pointer', varName, options);
540+
}
541+
513542
endianess(endianess: 'little' | 'big') {
514543
switch (endianess.toLowerCase()) {
515544
case 'little':
@@ -730,6 +759,9 @@ export class Parser {
730759
case 'choice':
731760
this.generateChoice(ctx);
732761
break;
762+
case 'pointer':
763+
this.generatePointer(ctx);
764+
break;
733765
}
734766
this.generateAssert(ctx);
735767
}
@@ -1040,4 +1072,40 @@ export class Parser {
10401072
ctx.pushCode(`${varName} = (${formatter}).call(this, ${varName});`);
10411073
}
10421074
}
1075+
1076+
private generatePointer(ctx: Context) {
1077+
const type = this.options.type;
1078+
const offset = ctx.generateOption(this.options.offset);
1079+
const tempVar = ctx.generateTmpVariable();
1080+
const nestVar = ctx.generateVariable(this.varName);
1081+
1082+
// Save current offset
1083+
ctx.pushCode(`var ${tempVar} = offset;`);
1084+
1085+
// Move offset
1086+
ctx.pushCode(`offset = ${offset};`);
1087+
1088+
if (this.options.type instanceof Parser) {
1089+
ctx.pushCode(`${nestVar} = {};`);
1090+
ctx.pushPath(this.varName);
1091+
this.options.type.generate(ctx);
1092+
ctx.popPath(this.varName);
1093+
} else if (aliasRegistry[this.options.type]) {
1094+
const tempVar = ctx.generateTmpVariable();
1095+
ctx.pushCode(
1096+
`var ${tempVar} = ${FUNCTION_PREFIX + this.options.type}(offset);`
1097+
);
1098+
ctx.pushCode(
1099+
`${nestVar} = ${tempVar}.result; offset = ${tempVar}.offset;`
1100+
);
1101+
if (this.options.type !== this.alias) ctx.addReference(this.options.type);
1102+
} else if (Object.keys(PRIMITIVE_SIZES).indexOf(this.options.type) >= 0) {
1103+
const typeName = CAPITILIZED_TYPE_NAMES[type as Types];
1104+
ctx.pushCode(`${nestVar} = buffer.read${typeName}(offset);`);
1105+
ctx.pushCode(`offset += ${PRIMITIVE_SIZES[type as PrimitiveTypes]};`);
1106+
}
1107+
1108+
// Restore offset
1109+
ctx.pushCode(`offset = ${tempVar};`);
1110+
}
10431111
}

test/composite_parser.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -943,6 +943,58 @@ describe('Composite parser', function() {
943943
});
944944
});
945945

946+
describe('Pointer parser', function() {
947+
it('should move pointer to specified offset', function() {
948+
var parser = Parser.start().pointer('x', { type: 'uint8', offset: 2 });
949+
var buf = Buffer.from([0x1, 0x2, 0x3, 0x4, 0x5]);
950+
951+
assert.deepEqual(parser.parse(buf), { x: 3 });
952+
});
953+
954+
it('should restore pointer to original position', function() {
955+
var parser = Parser.start()
956+
.pointer('x', { type: 'uint8', offset: 2 })
957+
.uint16be('y');
958+
var buf = Buffer.from([0x1, 0x2, 0x3, 0x4, 0x5]);
959+
960+
assert.deepEqual(parser.parse(buf), { x: 0x3, y: 0x0102 });
961+
});
962+
963+
it('should work with child parser', function() {
964+
var parser = Parser.start()
965+
.uint32le('x')
966+
.pointer('y', {
967+
type: Parser.start().string('s', { zeroTerminated: true }),
968+
offset: 4,
969+
});
970+
var buf = Buffer.from('\1\2\3\4hello\0\6', 'ascii');
971+
972+
assert.deepEqual(parser.parse(buf), {
973+
x: 0x04030201,
974+
y: { s: 'hello' },
975+
});
976+
});
977+
978+
it('should pass variable context to child parser', function() {});
979+
var parser = Parser.start()
980+
.uint16be('len')
981+
.pointer('child', {
982+
offset: 4,
983+
type: Parser.start().array('a', {
984+
type: 'uint8',
985+
length: function(vars) {
986+
return vars.len;
987+
},
988+
}),
989+
});
990+
var buf = Buffer.from('\0\6\0\0\1\2\3\4\5\6');
991+
992+
assert.deepEqual(parser.parse(buf), {
993+
len: 6,
994+
child: { a: [1, 2, 3, 4, 5, 6] },
995+
});
996+
});
997+
946998
describe('Utilities', function() {
947999
it('should count size for fixed size structs', function() {
9481000
var parser = Parser.start()

0 commit comments

Comments
 (0)