Add ext resources.
This commit is contained in:
Submodule 08_Collected_MultiPROG_Scripts/GitHub/CarKeyGuyNL_Multi-Prog-Scripts deleted from b1b20916b0
BIN
Binary file not shown.
@@ -0,0 +1,38 @@
|
||||
# Multi Prog Scrips
|
||||
|
||||
Here you can download the X-Horse Multi-Prog scripts i made.
|
||||
|
||||
## Installation
|
||||
Download the desired script, open Xhorse Multi-PROG, \
|
||||
Go to 'Scripts' -> 'Released features' -> 'Import' and \
|
||||
select the .mjs file you just downloaded.
|
||||
Under 'Released features' Select the script you imported
|
||||
and click on Run. The script functions will now be visible in the menu.
|
||||
|
||||
## Script available
|
||||
### 1. EWS Code Calculator
|
||||
To program remotes for Land Rover / Mini / Rover, you need 3 sets of code which can be calculated by the barcode on the lable, or the sticker on the PCB of the remote (Original remotes. After-market remotes usual don't have a sticker on the PCB).
|
||||
|
||||
#### To calculate from barcode label,
|
||||
Click on 'EWS Barcode', Enter the 35 characters, including the * symbols \
|
||||
Example:
|
||||
```bash
|
||||
Here are the codes for Barcode *NAD644DAD644CFFN**FFFFFFFF529BB2V*
|
||||
-----------------------------------
|
||||
Basic Code----------| AD 64 4D
|
||||
Random Code---------| AD 64 4C
|
||||
Encryption Code-----| FF FF FF FF FF 52 9B B2
|
||||
-----------------------------------
|
||||
```
|
||||
|
||||
#### To calculate from sticker,
|
||||
Click on 'EWS Sticker', Enter the 6 digits of the top row of the sticker.
|
||||
For example: "AD644D".
|
||||
|
||||
|
||||
```bash
|
||||
Here are the codes for Sticker AD 64 4D
|
||||
-----------------------------------
|
||||
Basic Code----------| AD 64 4D
|
||||
Random Code---------| AD 64 4C
|
||||
Encryption Code-----| FF FF FF FF FF 52 9B B2
|
||||
Submodule 08_Collected_MultiPROG_Scripts/GitHub/binary-parser deleted from d7dc356679
@@ -0,0 +1,11 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.{ts,js,json}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
@@ -0,0 +1,22 @@
|
||||
### Node ###
|
||||
lib-cov
|
||||
*.seed
|
||||
*.log
|
||||
*.csv
|
||||
*.dat
|
||||
*.out
|
||||
*.pid
|
||||
*.gz
|
||||
|
||||
pids
|
||||
logs
|
||||
results
|
||||
|
||||
npm-debug.log
|
||||
node_modules
|
||||
|
||||
coverage/
|
||||
.nyc_output/
|
||||
dist/
|
||||
|
||||
.cache/
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"editor.formatOnSave": true
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2013-2021 Keichi Takahashi <keichi.t@me.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -0,0 +1,653 @@
|
||||
# Binary-parser
|
||||
|
||||
[](https://github.com/keichi/binary-parser/actions?query=workflow%3Atest-nodejs)
|
||||
[](https://www.npmjs.com/package/binary-parser)
|
||||
[](https://github.com/keichi/binary-parser/blob/master/LICENSE)
|
||||
|
||||
Binary-parser is a parser builder for JavaScript that enables you to write
|
||||
efficient binary parsers in a simple and declarative manner.
|
||||
|
||||
It supports all common data types required to analyze a structured binary
|
||||
data. Binary-parser dynamically generates and compiles the parser code
|
||||
on-the-fly, which runs as fast as a hand-written parser (which takes much more
|
||||
time and effort to write). Supported data types are:
|
||||
|
||||
- [Integers](#uint8-16-32-64le-bename-options) (8, 16, 32 and 64 bit signed
|
||||
and unsigned integers)
|
||||
- [Floating point numbers](#float-doublele-bename-options) (32 and 64 bit
|
||||
floating point values)
|
||||
- [Bit fields](#bit1-32name-options) (bit fields with length from 1 to 32
|
||||
bits)
|
||||
- [Strings](#stringname-options) (fixed-length, variable-length and zero
|
||||
terminated strings with various encodings)
|
||||
- [Arrays](#arrayname-options) (fixed-length and variable-length arrays of
|
||||
builtin or user-defined element types)
|
||||
- [Choices](#choicename-options) (supports integer keys)
|
||||
- [Pointers](#pointername-options)
|
||||
- User defined types (arbitrary combination of builtin types)
|
||||
|
||||
Binary-parser was inspired by [BinData](https://github.com/dmendel/bindata)
|
||||
and [binary](https://github.com/substack/node-binary).
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. Create an empty `Parser` object with `new Parser()` or `Parser.start()`.
|
||||
2. Chain methods to build your desired parser. (See [API](#api) for detailed
|
||||
documentation of each method)
|
||||
3. Call `Parser.prototype.parse` with a `Buffer`/`Uint8Array` object passed as
|
||||
its only argument.
|
||||
4. The parsed result will be returned as an object.
|
||||
- If parsing failed, an exception will be thrown.
|
||||
|
||||
```javascript
|
||||
// Module import
|
||||
const Parser = require("binary-parser").Parser;
|
||||
|
||||
// Alternative way to import the module
|
||||
// import { Parser } from "binary-parser";
|
||||
|
||||
// Build an IP packet header Parser
|
||||
const ipHeader = new Parser()
|
||||
.endianness("big")
|
||||
.bit4("version")
|
||||
.bit4("headerLength")
|
||||
.uint8("tos")
|
||||
.uint16("packetLength")
|
||||
.uint16("id")
|
||||
.bit3("offset")
|
||||
.bit13("fragOffset")
|
||||
.uint8("ttl")
|
||||
.uint8("protocol")
|
||||
.uint16("checksum")
|
||||
.array("src", {
|
||||
type: "uint8",
|
||||
length: 4
|
||||
})
|
||||
.array("dst", {
|
||||
type: "uint8",
|
||||
length: 4
|
||||
});
|
||||
|
||||
// Prepare buffer to parse.
|
||||
const buf = Buffer.from("450002c5939900002c06ef98adc24f6c850186d1", "hex");
|
||||
|
||||
// Parse buffer and show result
|
||||
console.log(ipHeader.parse(buf));
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
You can install `binary-parser` via npm:
|
||||
|
||||
```bash
|
||||
npm install binary-parser
|
||||
```
|
||||
|
||||
The npm package provides entry points for both CommonJS and ES modules.
|
||||
|
||||
## API
|
||||
|
||||
### new Parser()
|
||||
Create an empty parser object that parses nothing.
|
||||
|
||||
### parse(buffer)
|
||||
Parse a `Buffer`/`Uint8Array` object `buffer` with this parser and return the
|
||||
resulting object. When `parse(buffer)` is called for the first time, the
|
||||
associated parser code is compiled on-the-fly and internally cached.
|
||||
|
||||
### create(constructorFunction)
|
||||
Set the constructor function that should be called to create the object
|
||||
returned from the `parse` method.
|
||||
|
||||
### [u]int{8, 16, 32, 64}{le, be}(name[, options])
|
||||
Parse bytes as an integer and store it in a variable named `name`. `name`
|
||||
should consist only of alphanumeric characters and start with an alphabet.
|
||||
Number of bits can be chosen from 8, 16, 32 and 64. Byte-ordering can be either
|
||||
`le` for little endian or `be` for big endian. With no prefix, it parses as a
|
||||
signed number, with `u` prefix as an unsigned number. The runtime type
|
||||
returned by the 8, 16, 32 bit methods is `number` while the type
|
||||
returned by the 64 bit is `bigint`.
|
||||
|
||||
**Note:** [u]int64{be,le} methods only work if your runtime is node v12.0.0 or
|
||||
greater. Lower versions will throw a runtime error.
|
||||
|
||||
```javascript
|
||||
const parser = new Parser()
|
||||
// Signed 32-bit integer (little endian)
|
||||
.int32le("a")
|
||||
// Unsigned 8-bit integer
|
||||
.uint8("b")
|
||||
// Signed 16-bit integer (big endian)
|
||||
.int16be("c")
|
||||
// signed 64-bit integer (big endian)
|
||||
.int64be("d")
|
||||
```
|
||||
|
||||
### bit\[1-32\](name[, options])
|
||||
Parse bytes as a bit field and store it in variable `name`. There are 32
|
||||
methods from `bit1` to `bit32` each corresponding to 1-bit-length to
|
||||
32-bits-length bit field.
|
||||
|
||||
### {float, double}{le, be}(name[, options])
|
||||
Parse bytes as a floating-point value and stores it to a variable named
|
||||
`name`.
|
||||
|
||||
```javascript
|
||||
const parser = new Parser()
|
||||
// 32-bit floating value (big endian)
|
||||
.floatbe("a")
|
||||
// 64-bit floating value (little endian)
|
||||
.doublele("b");
|
||||
```
|
||||
|
||||
### string(name[, options])
|
||||
Parse bytes as a string. `name` should consist only of alpha numeric
|
||||
characters and start with an alphabet. `options` is an object which can have
|
||||
the following keys:
|
||||
|
||||
- `encoding` - (Optional, defaults to `utf8`) Specify which encoding to use.
|
||||
Supported encodings include `"hex"` and all encodings supported by
|
||||
[`TextDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder/encoding).
|
||||
- `length ` - (Optional) Length of the string. Can be a number, string or a
|
||||
function. Use number for statically sized arrays, string to reference
|
||||
another variable and function to do some calculation.
|
||||
- `zeroTerminated` - (Optional, defaults to `false`) If true, then this parser
|
||||
reads until it reaches zero.
|
||||
- `greedy` - (Optional, defaults to `false`) If true, then this parser reads
|
||||
until it reaches the end of the buffer. Will consume zero-bytes.
|
||||
- `stripNull` - (Optional, must be used with `length`) If true, then strip
|
||||
null characters from end of the string.
|
||||
|
||||
### buffer(name[, options])
|
||||
Parse bytes as a buffer. Its type will be the same as the input to
|
||||
`parse(buffer)`. `name` should consist only of alpha numeric characters and
|
||||
start with an alphabet. `options` is an object which can have the following
|
||||
keys:
|
||||
|
||||
- `clone` - (Optional, defaults to `false`) By default,
|
||||
`buffer(name [,options])` returns a new buffer which references the same
|
||||
memory as the parser input, but offset and cropped by a certain range. If
|
||||
this option is true, input buffer will be cloned and a new buffer
|
||||
referencing a new memory region is returned.
|
||||
- `length ` - (either `length` or `readUntil` is required) Length of the
|
||||
buffer. Can be a number, string or a function. Use number for statically
|
||||
sized buffers, string to reference another variable and function to do some
|
||||
calculation.
|
||||
- `readUntil` - (either `length` or `readUntil` is required) If `"eof"`, then
|
||||
this parser will read till it reaches the end of the `Buffer`/`Uint8Array`
|
||||
object. If it is a function, this parser will read the buffer until the
|
||||
function returns true.
|
||||
|
||||
### array(name, options)
|
||||
Parse bytes as an array. `options` is an object which can have the following
|
||||
keys:
|
||||
|
||||
- `type` - (Required) Type of the array element. Can be a string or a user
|
||||
defined `Parser` object. If it's a string, you have to choose from [u]int{8,
|
||||
16, 32}{le, be}.
|
||||
- `length` - (either `length`, `lengthInBytes`, or `readUntil` is required)
|
||||
Length of the array. Can be a number, string or a function. Use number for
|
||||
statically sized arrays.
|
||||
- `lengthInBytes` - (either `length`, `lengthInBytes`, or `readUntil` is
|
||||
required) Length of the array expressed in bytes. Can be a number, string or
|
||||
a function. Use number for statically sized arrays.
|
||||
- `readUntil` - (either `length`, `lengthInBytes`, or `readUntil` is required)
|
||||
If `"eof"`, then this parser reads until the end of the `Buffer`/`Uint8Array`
|
||||
object. If function it reads until the function returns true.
|
||||
|
||||
```javascript
|
||||
const parser = new Parser()
|
||||
// Statically sized array
|
||||
.array("data", {
|
||||
type: "int32",
|
||||
length: 8
|
||||
})
|
||||
|
||||
// Dynamically sized array (references another variable)
|
||||
.uint8("dataLength")
|
||||
.array("data2", {
|
||||
type: "int32",
|
||||
length: "dataLength"
|
||||
})
|
||||
|
||||
// Dynamically sized array (with some calculation)
|
||||
.array("data3", {
|
||||
type: "int32",
|
||||
length: function() {
|
||||
return this.dataLength - 1;
|
||||
} // other fields are available through `this`
|
||||
})
|
||||
|
||||
// Statically sized array
|
||||
.array("data4", {
|
||||
type: "int32",
|
||||
lengthInBytes: 16
|
||||
})
|
||||
|
||||
// Dynamically sized array (references another variable)
|
||||
.uint8("dataLengthInBytes")
|
||||
.array("data5", {
|
||||
type: "int32",
|
||||
lengthInBytes: "dataLengthInBytes"
|
||||
})
|
||||
|
||||
// Dynamically sized array (with some calculation)
|
||||
.array("data6", {
|
||||
type: "int32",
|
||||
lengthInBytes: function() {
|
||||
return this.dataLengthInBytes - 4;
|
||||
} // other fields are available through `this`
|
||||
})
|
||||
|
||||
// Dynamically sized array (with stop-check on parsed item)
|
||||
.array("data7", {
|
||||
type: "int32",
|
||||
readUntil: function(item, buffer) {
|
||||
return item === 42;
|
||||
} // stop when specific item is parsed. buffer can be used to perform a read-ahead.
|
||||
})
|
||||
|
||||
// Use user defined parser object
|
||||
.array("data8", {
|
||||
type: userDefinedParser,
|
||||
length: "dataLength"
|
||||
});
|
||||
```
|
||||
|
||||
### choice([name,] options)
|
||||
Choose one parser from multiple parsers according to a field value and store
|
||||
its parsed result to key `name`. If `name` is null or omitted, the result of
|
||||
the chosen parser is directly embedded into the current object. `options` is
|
||||
an object which can have the following keys:
|
||||
|
||||
- `tag` - (Required) The value used to determine which parser to use from the
|
||||
`choices`. Can be a string pointing to another field or a function.
|
||||
- `choices` - (Required) An object which key is an integer and value is the
|
||||
parser which is executed when `tag` equals the key value.
|
||||
- `defaultChoice` - (Optional) In case if the tag value doesn't match any of
|
||||
`choices`, this parser is used.
|
||||
|
||||
```javascript
|
||||
const parser1 = ...;
|
||||
const parser2 = ...;
|
||||
const parser3 = ...;
|
||||
|
||||
const parser = new Parser().uint8("tagValue").choice("data", {
|
||||
tag: "tagValue",
|
||||
choices: {
|
||||
1: parser1, // if tagValue == 1, execute parser1
|
||||
4: parser2, // if tagValue == 4, execute parser2
|
||||
5: parser3 // if tagValue == 5, execute parser3
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Combining `choice` with `array` is an idiom to parse
|
||||
[TLV](http://en.wikipedia.org/wiki/Type-length-value)-based binary formats.
|
||||
|
||||
### nest([name,] options)
|
||||
Execute an inner parser and store its result to key `name`. If `name` is null
|
||||
or omitted, the result of the inner parser is directly embedded into the
|
||||
current object. `options` is an object which can have the following keys:
|
||||
|
||||
- `type` - (Required) A `Parser` object.
|
||||
|
||||
### pointer(name [,options])
|
||||
Jump to `offset`, execute parser for `type` and rewind to previous offset.
|
||||
Useful for parsing binary formats such as ELF where the offset of a field is
|
||||
pointed by another field.
|
||||
|
||||
- `type` - (Required) Can be a string `[u]int{8, 16, 32, 64}{le, be}`
|
||||
or a user defined `Parser` object.
|
||||
- `offset` - (Required) Indicates absolute offset from the beginning of the
|
||||
input buffer. Can be a number, string or a function.
|
||||
|
||||
### saveOffset(name [,options])
|
||||
Save the current buffer offset as key `name`. This function is only useful
|
||||
when called after another function which would advance the internal buffer
|
||||
offset.
|
||||
|
||||
```javascript
|
||||
const parser = new Parser()
|
||||
// this call advances the buffer offset by
|
||||
// a variable (i.e. unknown to us) number of bytes
|
||||
.string("name", {
|
||||
zeroTerminated: true
|
||||
})
|
||||
// this variable points to an absolute position
|
||||
// in the buffer
|
||||
.uint32("seekOffset")
|
||||
// now, save the "current" offset in the stream
|
||||
// as the variable "currentOffset"
|
||||
.saveOffset("currentOffset")
|
||||
// finally, use the saved offset to figure out
|
||||
// how many bytes we need to skip
|
||||
.seek(function() {
|
||||
return this.seekOffset - this.currentOffset;
|
||||
})
|
||||
... // the parser would continue here
|
||||
```
|
||||
|
||||
### seek(relOffset)
|
||||
Move the buffer offset for `relOffset` bytes from the current position. Use a
|
||||
negative `relOffset` value to rewind the offset. This method was previously
|
||||
named `skip(length)`.
|
||||
|
||||
### endianness(endianness)
|
||||
Define what endianness to use in this parser. `endianness` can be either
|
||||
`"little"` or `"big"`. The default endianness of `Parser` is set to big-endian.
|
||||
|
||||
```javascript
|
||||
const parser = new Parser()
|
||||
.endianness("little")
|
||||
// You can specify endianness explicitly
|
||||
.uint16be("a")
|
||||
.uint32le("a")
|
||||
// Or you can omit endianness (in this case, little-endian is used)
|
||||
.uint16("b")
|
||||
.int32("c");
|
||||
```
|
||||
|
||||
This setting also affects the bit extraction order of
|
||||
[bit field](#bit1-32name-options) methods (`bit1` to `bit32`). In big-endian
|
||||
mode, bit fields are extracted from MSB to LSB.
|
||||
In little-endian mode, bit fields are extracted from LSB to MSB.
|
||||
If you need big-endian bit ordering while using little-endian
|
||||
byte ordering, you can parse bit fields in a separate `Parser` with
|
||||
`.endianness("big")` and embed it using `.nest()`.
|
||||
|
||||
### namely(alias)
|
||||
Set an alias to this parser, so that it can be referred to by name in methods
|
||||
like `.array`, `.nest` and `.choice`, without the requirement to have an
|
||||
instance of this parser.
|
||||
|
||||
Especially, the parser may reference itself:
|
||||
|
||||
```javascript
|
||||
const stop = new Parser();
|
||||
|
||||
const parser = new Parser()
|
||||
.namely("self") // use 'self' to refer to the parser itself
|
||||
.uint8("type")
|
||||
.choice("data", {
|
||||
tag: "type",
|
||||
choices: {
|
||||
0: stop,
|
||||
1: "self",
|
||||
2: Parser.start()
|
||||
.nest("left", { type: "self" })
|
||||
.nest("right", { type: "self" }),
|
||||
3: Parser.start()
|
||||
.nest("one", { type: "self" })
|
||||
.nest("two", { type: "self" })
|
||||
.nest("three", { type: "self" })
|
||||
}
|
||||
});
|
||||
|
||||
// 2
|
||||
// / \
|
||||
// 3 1
|
||||
// / | \ \
|
||||
// 1 0 2 0
|
||||
// / / \
|
||||
// 0 1 0
|
||||
// /
|
||||
// 0
|
||||
|
||||
const buffer = Buffer.from([
|
||||
2,
|
||||
/* left -> */ 3,
|
||||
/* one -> */ 1, /* -> */ 0,
|
||||
/* two -> */ 0,
|
||||
/* three -> */ 2,
|
||||
/* left -> */ 1, /* -> */ 0,
|
||||
/* right -> */ 0,
|
||||
/* right -> */ 1, /* -> */ 0
|
||||
]);
|
||||
|
||||
parser.parse(buffer);
|
||||
```
|
||||
|
||||
For most of the cases there is almost no difference to the instance-way of
|
||||
referencing, but this method provides the way to parse recursive trees, where
|
||||
each node could reference the node of the same type from the inside.
|
||||
|
||||
Also, when you reference a parser using its instance twice, the generated code
|
||||
will contain two similar parts of the code included, while with the named
|
||||
approach, it will include a function with a name, and will just call this
|
||||
function for every case of usage.
|
||||
|
||||
**Note**: This style could lead to circular references and infinite recursion,
|
||||
to avoid this, ensure that every possible path has its end. Also, this
|
||||
recursion is not tail-optimized, so could lead to memory leaks when it goes
|
||||
too deep.
|
||||
|
||||
An example of referencing other parsers:
|
||||
|
||||
```javascript
|
||||
// the line below registers the name "self", so we will be able to use it in
|
||||
// `twoCells` as a reference
|
||||
const parser = Parser.start().namely("self");
|
||||
|
||||
const stop = Parser.start().namely("stop");
|
||||
|
||||
const twoCells = Parser.start()
|
||||
.namely("twoCells")
|
||||
.nest("left", { type: "self" })
|
||||
.nest("right", { type: "stop" });
|
||||
|
||||
parser.uint8("type").choice("data", {
|
||||
tag: "type",
|
||||
choices: {
|
||||
0: "stop",
|
||||
1: "self",
|
||||
2: "twoCells"
|
||||
}
|
||||
});
|
||||
|
||||
const buffer = Buffer.from([2, /* left */ 1, 1, 0, /* right */ 0]);
|
||||
|
||||
parser.parse(buffer);
|
||||
```
|
||||
|
||||
### wrapped([name,] options)
|
||||
Read data, then wrap it by transforming it by a function for further parsing.
|
||||
It works similarly to a buffer where it reads a block of data. But instead of
|
||||
returning the buffer it will pass the buffer on to a parser for further processing.
|
||||
|
||||
The result will be stored in the key `name`. If `name` is an empty string or
|
||||
`null`, or if it is omitted, the parsed result is directly embedded into the
|
||||
current object.
|
||||
|
||||
- `wrapper` - (Required) A function taking a buffer and returning a buffer
|
||||
(`(x: Buffer | Uint8Array ) => Buffer | Uint8Array`) transforming the buffer
|
||||
into a buffer expected by `type`.
|
||||
- `type` - (Required) A `Parser` object to parse the buffer returned by `wrapper`.
|
||||
- `length ` - (either `length` or `readUntil` is required) Length of the
|
||||
buffer. Can be a number, string or a function. Use a number for statically
|
||||
sized buffers, a string to reference another variable and a function to do some
|
||||
calculation.
|
||||
- `readUntil` - (either `length` or `readUntil` is required) If `"eof"`, then
|
||||
this parser will read till it reaches the end of the `Buffer`/`Uint8Array`
|
||||
object. If it is a function, this parser will read the buffer until the
|
||||
function returns `true`.
|
||||
|
||||
```javascript
|
||||
const zlib = require("zlib");
|
||||
// A parser to run on the data returned by the wrapper
|
||||
const textParser = Parser.start()
|
||||
.string("text", {
|
||||
zeroTerminated: true,
|
||||
});
|
||||
|
||||
const mainParser = Parser.start()
|
||||
// Read length of the data to wrap
|
||||
.uint32le("length")
|
||||
// Read wrapped data
|
||||
.wrapped("wrappedData", {
|
||||
// Indicate how much data to read, like buffer()
|
||||
length: "length",
|
||||
// Define function to pre-process the data buffer
|
||||
wrapper: function (buffer) {
|
||||
// E.g. decompress data and return it for further parsing
|
||||
return zlib.inflateRawSync(buffer);
|
||||
},
|
||||
// The parser to run on the decompressed data
|
||||
type: textParser,
|
||||
});
|
||||
|
||||
mainParser.parse(buffer);
|
||||
```
|
||||
|
||||
### sizeOf()
|
||||
Returns how many bytes this parser consumes. If the size of the parser cannot
|
||||
be statically determined, a `NaN` is returned.
|
||||
|
||||
### compile()
|
||||
Compile this parser on-the-fly and cache its result. Usually, there is no need
|
||||
to call this method directly, since it's called when `parse(buffer)` is
|
||||
executed for the first time.
|
||||
|
||||
### getCode()
|
||||
Dynamically generates the code for this parser and returns it as a string.
|
||||
Useful for debugging the generated code.
|
||||
|
||||
### Common options
|
||||
These options can be used in all parsers.
|
||||
|
||||
- `formatter` - Function that transforms the parsed value into a more desired
|
||||
form.
|
||||
```javascript
|
||||
const parser = new Parser().array("ipv4", {
|
||||
type: uint8,
|
||||
length: "4",
|
||||
formatter: function(arr) {
|
||||
return arr.join(".");
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
- `assert` - Do assertion on the parsed result (useful for checking magic
|
||||
numbers and so on). If `assert` is a `string` or `number`, the actual parsed
|
||||
result will be compared with it with `===` (strict equality check), and an
|
||||
exception is thrown if they mismatch. On the other hand, if `assert` is a
|
||||
function, that function is executed with one argument (the parsed result)
|
||||
and if it returns false, an exception is thrown.
|
||||
|
||||
```javascript
|
||||
// simple maginc number validation
|
||||
const ClassFile = Parser.start()
|
||||
.endianness("big")
|
||||
.uint32("magic", { assert: 0xcafebabe });
|
||||
|
||||
// Doing more complex assertion with a predicate function
|
||||
const parser = new Parser()
|
||||
.int16le("a")
|
||||
.int16le("b")
|
||||
.int16le("c", {
|
||||
assert: function(x) {
|
||||
return this.a + this.b === x;
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Context variables
|
||||
You can use some special fields while parsing to traverse your structure.
|
||||
These context variables will be removed after the parsing process.
|
||||
Note that this feature is turned off by default for performance reasons, and
|
||||
you need to call `.useContextVars()` at the top level `Parser` to enable it.
|
||||
Otherwise, the context variables will not be present.
|
||||
|
||||
- `$parent` - This field references the parent structure. This variable will be
|
||||
`null` while parsing the root structure.
|
||||
|
||||
```javascript
|
||||
var parser = new Parser()
|
||||
.useContextVars()
|
||||
.nest("header", {
|
||||
type: new Parser().uint32("length"),
|
||||
})
|
||||
.array("data", {
|
||||
type: "int32",
|
||||
length: function() {
|
||||
return this.$parent.header.length;
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
- `$root` - This field references the root structure.
|
||||
|
||||
```javascript
|
||||
const parser = new Parser()
|
||||
.useContextVars()
|
||||
.nest("header", {
|
||||
type: new Parser().uint32("length"),
|
||||
})
|
||||
.nest("data", {
|
||||
type: new Parser()
|
||||
.uint32("value")
|
||||
.array("data", {
|
||||
type: "int32",
|
||||
length: function() {
|
||||
return this.$root.header.length;
|
||||
}
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
- `$index` - This field references the actual index in array parsing. This
|
||||
variable will be available only when using the `length` mode for arrays.
|
||||
|
||||
```javascript
|
||||
const parser = new Parser()
|
||||
.useContextVars()
|
||||
.nest("header", {
|
||||
type: new Parser().uint32("length"),
|
||||
})
|
||||
.nest("data", {
|
||||
type: new Parser()
|
||||
.uint32("value")
|
||||
.array("data", {
|
||||
type: new Parser().nest({
|
||||
type: new Parser().uint8("_tmp"),
|
||||
formatter: function(item) {
|
||||
return this.$index % 2 === 0 ? item._tmp : String.fromCharCode(item._tmp);
|
||||
}
|
||||
}),
|
||||
length: "$root.header.length"
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
See `example/` for real-world examples.
|
||||
|
||||
## Benchmarks
|
||||
|
||||
A benchmark script to compare the parsing performance with binparse, structron
|
||||
and destruct.js is available under `benchmark/`.
|
||||
|
||||
## Contributing
|
||||
|
||||
Please report issues to the
|
||||
[issue tracker](https://github.com/keichi/binary-parser/issues) if you have
|
||||
any difficulties using this module, found a bug, or would like to request a
|
||||
new feature. Pull requests are welcome.
|
||||
|
||||
To contribute code, first clone this repo, then install the dependencies:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/keichi/binary-parser.git
|
||||
cd binary-parser
|
||||
npm install
|
||||
```
|
||||
|
||||
If you added a feature or fixed a bug, update the test suite under `test/` and
|
||||
then run it like this:
|
||||
|
||||
```bash
|
||||
npm run test
|
||||
```
|
||||
|
||||
Make sure all the tests pass before submitting a pull request.
|
||||
@@ -0,0 +1,96 @@
|
||||
import Benchmark from "benchmark";
|
||||
import { bp } from "binparse";
|
||||
import { Parser } from "../dist/binary_parser.js";
|
||||
import Destruct from "destruct-js";
|
||||
import Struct from "structron";
|
||||
|
||||
const suite = new Benchmark.Suite();
|
||||
|
||||
// binparse
|
||||
const PointParser = bp.object("Point", {
|
||||
x: bp.lu16,
|
||||
y: bp.lu16,
|
||||
z: bp.lu16,
|
||||
});
|
||||
|
||||
const PointsParser = bp.object("SimpleObject", {
|
||||
length: bp.lu32,
|
||||
points: bp.array("Points", PointParser, "length"),
|
||||
});
|
||||
|
||||
// binary-parser
|
||||
const Points = new Parser().uint32le("len").array("points", {
|
||||
length: "len",
|
||||
type: new Parser().uint16le("x").uint16le("y").uint16le("z"),
|
||||
});
|
||||
|
||||
// destruct-js
|
||||
const spec = new Destruct.Spec({ mode: Destruct.Mode.LE });
|
||||
spec
|
||||
.field("len", Destruct.UInt32)
|
||||
.loop(
|
||||
"points",
|
||||
(r) => r.len,
|
||||
new Destruct.Spec({ mode: Destruct.Mode.LE })
|
||||
.field("x", Destruct.UInt16)
|
||||
.field("y", Destruct.UInt16)
|
||||
.field("z", Destruct.UInt16),
|
||||
);
|
||||
|
||||
// structron
|
||||
const PointsStruct = new Struct()
|
||||
.addMember(Struct.TYPES.UINT_LE, "len")
|
||||
.addArray(
|
||||
new Struct()
|
||||
.addMember(Struct.TYPES.USHORT_LE, "x")
|
||||
.addMember(Struct.TYPES.USHORT_LE, "y")
|
||||
.addMember(Struct.TYPES.USHORT_LE, "z"),
|
||||
"points",
|
||||
0,
|
||||
"len",
|
||||
);
|
||||
|
||||
// Prepare input
|
||||
const n = 1000;
|
||||
const buf = Buffer.alloc(4 + n * 2 * 3);
|
||||
|
||||
buf.writeUInt32LE(n, 0);
|
||||
for (let i = 0; i < n; i++) {
|
||||
buf.writeUInt16LE(123, i * 6 + 0 + 4);
|
||||
buf.writeUInt16LE(456, i * 6 + 2 + 4);
|
||||
buf.writeUInt16LE(789, i * 6 + 4 + 4);
|
||||
}
|
||||
|
||||
// Run benchmarks
|
||||
suite
|
||||
.add("hand-written", function () {
|
||||
const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
||||
const n = view.getUint32(0, true);
|
||||
const points = [];
|
||||
for (let i = 0; i < n; i++) {
|
||||
points.push({
|
||||
x: view.getUint16(i * 6 + 0 + 4, true),
|
||||
y: view.getUint16(i * 6 + 2 + 4, true),
|
||||
z: view.getUint16(i * 6 + 4 + 4, true),
|
||||
});
|
||||
}
|
||||
})
|
||||
.add("binparse", function () {
|
||||
const points = PointsParser.read(buf);
|
||||
})
|
||||
.add("binary-parser", function () {
|
||||
const points = Points.parse(buf);
|
||||
})
|
||||
.add("destruct-js", function () {
|
||||
const points = spec.read(buf);
|
||||
})
|
||||
.add("structron", function () {
|
||||
const points = PointsStruct.read(buf);
|
||||
})
|
||||
.on("cycle", function (event) {
|
||||
console.log(String(event.target));
|
||||
})
|
||||
.on("complete", function () {
|
||||
console.log("Fastest is " + this.filter("fastest").map("name"));
|
||||
})
|
||||
.run({ async: true });
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"name": "benchmark",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"benchmark": "^2.1.4",
|
||||
"binparse": "^2.1.0",
|
||||
"destruct-js": "^0.2.13",
|
||||
"structron": "^0.4.3"
|
||||
}
|
||||
},
|
||||
"node_modules/benchmark": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/benchmark/-/benchmark-2.1.4.tgz",
|
||||
"integrity": "sha1-CfPeMckWQl1JjMLuVloOvzwqVik=",
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.4",
|
||||
"platform": "^1.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/binparse": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/binparse/-/binparse-2.1.0.tgz",
|
||||
"integrity": "sha512-aHbVgEQnWpPc4nlK7ZdPedEXFoufVwRCJkp2oZ4nHnSCuwt1Fx/jVPNLOaq3120uNmk3dip5RH/jxI76OA22dw==",
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/destruct-js": {
|
||||
"version": "0.2.13",
|
||||
"resolved": "https://registry.npmjs.org/destruct-js/-/destruct-js-0.2.13.tgz",
|
||||
"integrity": "sha512-A5BL4uFygS0I12qNIPEzEqIqQfhkkr49o674SDFXsoHbX2Fg7/+vYgZen8l6GgJ0ezNrAyT9+5MZzBOY37ccjg=="
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.23",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
|
||||
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="
|
||||
},
|
||||
"node_modules/platform": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz",
|
||||
"integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg=="
|
||||
},
|
||||
"node_modules/structron": {
|
||||
"version": "0.4.3",
|
||||
"resolved": "https://registry.npmjs.org/structron/-/structron-0.4.3.tgz",
|
||||
"integrity": "sha512-DaDuI2mQy+QjtxQ8/IjTNPI6FLIF0jxqzwhUn9t1iKneV/0ANHkJfqWl/8pPBepNRc6rl46Q+TpQlnKvDm/9rA=="
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"benchmark": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/benchmark/-/benchmark-2.1.4.tgz",
|
||||
"integrity": "sha1-CfPeMckWQl1JjMLuVloOvzwqVik=",
|
||||
"requires": {
|
||||
"lodash": "^4.17.4",
|
||||
"platform": "^1.3.3"
|
||||
}
|
||||
},
|
||||
"binparse": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/binparse/-/binparse-2.1.0.tgz",
|
||||
"integrity": "sha512-aHbVgEQnWpPc4nlK7ZdPedEXFoufVwRCJkp2oZ4nHnSCuwt1Fx/jVPNLOaq3120uNmk3dip5RH/jxI76OA22dw=="
|
||||
},
|
||||
"destruct-js": {
|
||||
"version": "0.2.13",
|
||||
"resolved": "https://registry.npmjs.org/destruct-js/-/destruct-js-0.2.13.tgz",
|
||||
"integrity": "sha512-A5BL4uFygS0I12qNIPEzEqIqQfhkkr49o674SDFXsoHbX2Fg7/+vYgZen8l6GgJ0ezNrAyT9+5MZzBOY37ccjg=="
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.23",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
|
||||
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="
|
||||
},
|
||||
"platform": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz",
|
||||
"integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg=="
|
||||
},
|
||||
"structron": {
|
||||
"version": "0.4.3",
|
||||
"resolved": "https://registry.npmjs.org/structron/-/structron-0.4.3.tgz",
|
||||
"integrity": "sha512-DaDuI2mQy+QjtxQ8/IjTNPI6FLIF0jxqzwhUn9t1iKneV/0ANHkJfqWl/8pPBepNRc6rl46Q+TpQlnKvDm/9rA=="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"benchmark": "^2.1.4",
|
||||
"binparse": "^2.1.0",
|
||||
"destruct-js": "^0.2.13",
|
||||
"structron": "^0.4.3"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,5 @@
|
||||
public class Hello {
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Hello, world!");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { readFile } from "fs";
|
||||
import { join } from "path";
|
||||
|
||||
import { Parser } from "../lib/binary_parser";
|
||||
|
||||
// C structure BITMAPFILEHEADER
|
||||
// typedef struct tagBITMAPFILEHEADER {
|
||||
// WORD bfType;
|
||||
// DWORD bfSize;
|
||||
// WORD bfReserved1;
|
||||
// WORD bfReserved2;
|
||||
// DWORD bfOffBits;
|
||||
// } BITMAPFILEHEADER, *PBITMAPFILEHEADER;
|
||||
const bmpFileHeader = new Parser()
|
||||
.endianness("little")
|
||||
.string("type", {
|
||||
length: 2,
|
||||
assert: "BM",
|
||||
})
|
||||
.uint32("size")
|
||||
.uint16("reserved1")
|
||||
.uint16("reserved2")
|
||||
.uint32("offBits");
|
||||
|
||||
// C structure BITMAPINFOHEADER definition
|
||||
// typedef struct tagBITMAPINFOHEADER {
|
||||
// DWORD biSize;
|
||||
// LONG biWidth;
|
||||
// LONG biHeight;
|
||||
// WORD biPlanes;
|
||||
// WORD biBitCount;
|
||||
// DWORD biCompression;
|
||||
// DWORD biSizeImage;
|
||||
// LONG biXPelsPerMeter;
|
||||
// LONG biYPelsPerMeter;
|
||||
// DWORD biClrUsed;
|
||||
// DWORD biClrImportant;
|
||||
// } BITMAPINFOHEADER;
|
||||
const bmpInfoHeader = new Parser()
|
||||
.endianness("little")
|
||||
.uint32("size")
|
||||
.int32("width")
|
||||
.int32("height")
|
||||
.uint16("planes")
|
||||
.uint16("bitCount")
|
||||
.uint32("compression")
|
||||
.uint32("sizeImage")
|
||||
.int32("xPelsPerMeter")
|
||||
.int32("yPelsPerMeter")
|
||||
.uint32("clrUsed")
|
||||
.uint32("clrImportant");
|
||||
|
||||
const bmpFile = new Parser()
|
||||
.nest("fileHeader", {
|
||||
type: bmpFileHeader,
|
||||
})
|
||||
.nest("infoHeader", {
|
||||
type: bmpInfoHeader,
|
||||
});
|
||||
|
||||
readFile(join(__dirname, "test.bmp"), (_, data) => {
|
||||
console.log(bmpFile.parse(data));
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,88 @@
|
||||
import { readFile } from "fs";
|
||||
import { join } from "path";
|
||||
import { inspect } from "util";
|
||||
|
||||
import { Parser } from "../lib/binary_parser";
|
||||
|
||||
const ELF32ProgramHeader = new Parser()
|
||||
.endianness("little")
|
||||
.uint32("type")
|
||||
.uint32("offset")
|
||||
.uint32("vaddr")
|
||||
.uint32("paddr")
|
||||
.uint32("filesz")
|
||||
.uint32("memsz")
|
||||
.uint32("flags")
|
||||
.uint32("align");
|
||||
|
||||
const ELF32ProgramHeaderTable = new Parser().array("items", {
|
||||
type: ELF32ProgramHeader,
|
||||
length: function (vars) {
|
||||
return vars.phnum;
|
||||
},
|
||||
});
|
||||
|
||||
const ELF32SectionHeader = new Parser()
|
||||
.endianness("little")
|
||||
.uint32("name")
|
||||
.uint32("type")
|
||||
.uint32("flags")
|
||||
.uint32("address")
|
||||
.uint32("offset")
|
||||
.uint32("size")
|
||||
.uint32("link")
|
||||
.uint32("info")
|
||||
.uint32("addralign")
|
||||
.uint32("entsize");
|
||||
|
||||
const ELF32SectionHeaderTable = new Parser().array("items", {
|
||||
type: ELF32SectionHeader,
|
||||
length: function (vars) {
|
||||
return vars.shnum;
|
||||
},
|
||||
});
|
||||
|
||||
const ELF32SectionHeaderStringTable = new Parser().seek(1).array("items", {
|
||||
type: new Parser().string("name", { zeroTerminated: true }),
|
||||
lengthInBytes: function (vars) {
|
||||
const shstr = vars.section_headers.items[vars.shstrndx];
|
||||
return shstr.size - 1;
|
||||
},
|
||||
});
|
||||
|
||||
const ELF32Header = new Parser()
|
||||
.endianness("little")
|
||||
.buffer("ident", { length: 16 })
|
||||
.uint16("type")
|
||||
.uint16("machine")
|
||||
.uint32("version")
|
||||
.uint32("entry")
|
||||
.uint32("phoff")
|
||||
.uint32("shoff")
|
||||
.uint32("flags")
|
||||
.uint16("ehsize")
|
||||
.uint16("phentsize")
|
||||
.uint16("phnum")
|
||||
.uint16("shentsize")
|
||||
.uint16("shnum")
|
||||
.uint16("shstrndx")
|
||||
.pointer("program_headers", {
|
||||
type: ELF32ProgramHeaderTable,
|
||||
offset: "phoff",
|
||||
})
|
||||
.pointer("section_headers", {
|
||||
type: ELF32SectionHeaderTable,
|
||||
offset: "shoff",
|
||||
})
|
||||
.pointer("strings", {
|
||||
type: ELF32SectionHeaderStringTable,
|
||||
offset: function (vars) {
|
||||
const shstr = vars.section_headers.items[vars.shstrndx];
|
||||
return shstr.offset;
|
||||
},
|
||||
});
|
||||
|
||||
readFile(join(__dirname, "hello"), (_, data) => {
|
||||
const result = ELF32Header.parse(data);
|
||||
console.log(inspect(result, { depth: null, colors: true }));
|
||||
});
|
||||
Binary file not shown.
@@ -0,0 +1,26 @@
|
||||
import { Parser } from "../lib/binary_parser";
|
||||
|
||||
const ipHeader = new Parser()
|
||||
.endianness("big")
|
||||
.bit4("version")
|
||||
.bit4("headerLength")
|
||||
.uint8("tos")
|
||||
.uint16("packetLength")
|
||||
.uint16("id")
|
||||
.bit3("offset")
|
||||
.bit13("fragOffset")
|
||||
.uint8("ttl")
|
||||
.uint8("protocol")
|
||||
.uint16("checksum")
|
||||
.array("src", {
|
||||
type: "uint8",
|
||||
length: 4,
|
||||
})
|
||||
.array("dst", {
|
||||
type: "uint8",
|
||||
length: 4,
|
||||
});
|
||||
|
||||
const buf = Buffer.from("450002c5939900002c06ef98adc24f6c850186d1", "hex");
|
||||
|
||||
console.log(ipHeader.parse(buf));
|
||||
@@ -0,0 +1,113 @@
|
||||
import { readFile } from "fs";
|
||||
import { join } from "path";
|
||||
import { inspect } from "util";
|
||||
|
||||
import { Parser } from "../lib/binary_parser";
|
||||
|
||||
const SOI = Parser.start();
|
||||
|
||||
const EOI = Parser.start();
|
||||
|
||||
const APP0 = Parser.start()
|
||||
.endianness("big")
|
||||
.uint16("length")
|
||||
.string("id", {
|
||||
encoding: "ascii",
|
||||
zeroTerminated: true,
|
||||
assert: "JFIF",
|
||||
})
|
||||
.uint16("version")
|
||||
.uint8("unit")
|
||||
.uint16("xDensity")
|
||||
.uint16("yDensity")
|
||||
.uint8("thumbWidth")
|
||||
.uint8("thumbHeight")
|
||||
.array("thumbData", {
|
||||
type: "uint8",
|
||||
length: function (this: any) {
|
||||
return this.Xt * this.Yt * 3;
|
||||
},
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
const COM = Parser.start()
|
||||
.endianness("big")
|
||||
.uint16("length")
|
||||
.string("comment", {
|
||||
encoding: "ascii",
|
||||
length: function (this: any) {
|
||||
return this.length - 2;
|
||||
},
|
||||
});
|
||||
|
||||
const SOS = Parser.start()
|
||||
.endianness("big")
|
||||
.uint16("length")
|
||||
.uint8("componentCount")
|
||||
.array("components", {
|
||||
type: Parser.start().uint8("id").uint8("dht"),
|
||||
length: "componentCount",
|
||||
})
|
||||
.uint8("spectrumStart")
|
||||
.uint8("spectrumEnd")
|
||||
.uint8("spectrumSelect");
|
||||
|
||||
const DQT = Parser.start()
|
||||
.endianness("big")
|
||||
.uint16("length")
|
||||
.array("tables", {
|
||||
type: Parser.start().uint8("precisionAndTableId").array("table", {
|
||||
type: "uint8",
|
||||
length: 64,
|
||||
}),
|
||||
length: function (this: any) {
|
||||
return (this.length - 2) / 65;
|
||||
},
|
||||
});
|
||||
|
||||
const SOF0 = Parser.start()
|
||||
.endianness("big")
|
||||
.uint16("length")
|
||||
.uint8("precision")
|
||||
.uint16("width")
|
||||
.uint16("height")
|
||||
.uint8("componentCount")
|
||||
.array("components", {
|
||||
type: Parser.start()
|
||||
.uint8("id")
|
||||
.uint8("samplingFactor")
|
||||
.uint8("quantizationTableId"),
|
||||
length: "componentCount",
|
||||
});
|
||||
|
||||
const Ignore = Parser.start()
|
||||
.endianness("big")
|
||||
.uint16("length")
|
||||
.seek(function (this: any) {
|
||||
return this.length - 2;
|
||||
});
|
||||
|
||||
const Segment = Parser.start()
|
||||
.endianness("big")
|
||||
.uint16("marker")
|
||||
.choice("segment", {
|
||||
tag: "marker",
|
||||
choices: {
|
||||
0xffd8: SOI,
|
||||
0xffd9: EOI,
|
||||
0xffe0: APP0,
|
||||
0xffda: SOS,
|
||||
0xffdb: DQT,
|
||||
0xffc0: SOF0,
|
||||
},
|
||||
defaultChoice: Ignore,
|
||||
});
|
||||
|
||||
const JPEG = Parser.start().array("segments", {
|
||||
type: Segment,
|
||||
readUntil: "eof",
|
||||
});
|
||||
|
||||
readFile(join(__dirname, "test.jpg"), (_, data) => {
|
||||
console.log(inspect(JPEG.parse(data), { depth: null, colors: true }));
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import { readFile } from "fs";
|
||||
import { join } from "path";
|
||||
import { inspect } from "util";
|
||||
|
||||
import { Parser } from "../lib/binary_parser";
|
||||
|
||||
const chs = new Parser()
|
||||
.uint8("head")
|
||||
.bit2("cylinderHigh")
|
||||
.bit6("sector")
|
||||
.uint8("cylinder");
|
||||
|
||||
const partitionTable = new Parser()
|
||||
.uint8("bootFlag")
|
||||
.nest("startCHS", {
|
||||
type: chs,
|
||||
formatter: function (val) {
|
||||
delete val.cylinderHigh;
|
||||
return val;
|
||||
},
|
||||
})
|
||||
.uint8("type")
|
||||
.nest("endCHS", {
|
||||
type: chs,
|
||||
formatter: function (val) {
|
||||
val.cylinder |= val.cylinderHigh << 8;
|
||||
delete val.cylinderHigh;
|
||||
return val;
|
||||
},
|
||||
})
|
||||
.uint32le("startLBA")
|
||||
.uint32le("endLBA");
|
||||
|
||||
const mbrParser = new Parser()
|
||||
.seek(446)
|
||||
.array("partitionTables", {
|
||||
type: partitionTable,
|
||||
length: 4,
|
||||
})
|
||||
.int16be("signature", {
|
||||
assert: 0x55aa,
|
||||
});
|
||||
|
||||
readFile(join(__dirname, "raspbian.img"), (_, data) => {
|
||||
console.log(inspect(mbrParser.parse(data), { depth: null, colors: true }));
|
||||
});
|
||||
Binary file not shown.
@@ -0,0 +1,44 @@
|
||||
import { join } from "path";
|
||||
import { readFile } from "fs";
|
||||
|
||||
import { Parser } from "../lib/binary_parser";
|
||||
|
||||
function oct2int(s: string): number {
|
||||
return parseInt(s, 8);
|
||||
}
|
||||
|
||||
const tarHeader = new Parser()
|
||||
.string("name", { length: 100, stripNull: true })
|
||||
.string("mode", { length: 8, stripNull: true, formatter: oct2int })
|
||||
.string("uid", { length: 8, stripNull: true, formatter: oct2int })
|
||||
.string("gid", { length: 8, stripNull: true, formatter: oct2int })
|
||||
.string("size", { length: 12, stripNull: true, formatter: oct2int })
|
||||
.string("mtime", { length: 12, stripNull: true, formatter: oct2int })
|
||||
.string("chksum", { length: 8, stripNull: true, formatter: oct2int })
|
||||
.string("typeflag", { length: 1, stripNull: true, formatter: oct2int })
|
||||
.string("linkname", { length: 100, stripNull: true })
|
||||
.string("magic", { length: 6, stripNull: true })
|
||||
.string("version", { length: 2, stripNull: true, formatter: oct2int })
|
||||
.string("uname", { length: 32, stripNull: true })
|
||||
.string("gname", { length: 32, stripNull: true })
|
||||
.string("devmajor", { length: 8, stripNull: true, formatter: oct2int })
|
||||
.string("devminor", { length: 8, stripNull: true, formatter: oct2int })
|
||||
.string("prefix", { length: 155, stripNull: true })
|
||||
.seek(12);
|
||||
|
||||
const tarItem = new Parser()
|
||||
.nest({
|
||||
type: tarHeader,
|
||||
})
|
||||
.seek(function (this: any) {
|
||||
return Math.ceil(this.size / 512) * 512;
|
||||
});
|
||||
|
||||
const tarArchive = new Parser().array("files", {
|
||||
type: tarItem,
|
||||
readUntil: "eof",
|
||||
});
|
||||
|
||||
readFile(join(__dirname, "test.tar"), (_, data) => {
|
||||
console.dir(tarArchive.parse(data), { depth: null, colors: true });
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Parser } from "../lib/binary_parser";
|
||||
|
||||
const tcpHeader = new Parser()
|
||||
.endianness("big")
|
||||
.uint16("srcPort")
|
||||
.uint16("dstPort")
|
||||
.uint32("seq")
|
||||
.uint32("ack")
|
||||
.bit4("dataOffset")
|
||||
.bit6("reserved")
|
||||
.nest("flags", {
|
||||
type: new Parser()
|
||||
.bit1("urg")
|
||||
.bit1("ack")
|
||||
.bit1("psh")
|
||||
.bit1("rst")
|
||||
.bit1("syn")
|
||||
.bit1("fin"),
|
||||
})
|
||||
.uint16("windowSize")
|
||||
.uint16("checksum")
|
||||
.uint16("urgentPointer");
|
||||
|
||||
const buf = Buffer.from(
|
||||
"e8a203e108e177e13d20756b801829d3004100000101080a2ea486ba793310bc",
|
||||
"hex",
|
||||
);
|
||||
|
||||
console.log(tcpHeader.parse(buf));
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
Binary file not shown.
@@ -0,0 +1,55 @@
|
||||
// Karma configuration
|
||||
// Generated on Mon Oct 04 2021 18:19:34 GMT+0900 (Japan Standard Time)
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
// base path that will be used to resolve all patterns (eg. files, exclude)
|
||||
basePath: "",
|
||||
|
||||
// frameworks to use
|
||||
// available frameworks: https://www.npmjs.com/search?q=keywords:karma-adapter
|
||||
frameworks: ["mocha", "karma-typescript"],
|
||||
|
||||
// list of files / patterns to load in the browser
|
||||
files: ["test/*.ts", "lib/*.ts"],
|
||||
|
||||
// list of files / patterns to exclude
|
||||
exclude: [],
|
||||
|
||||
// preprocess matching files before serving them to the browser
|
||||
// available preprocessors: https://www.npmjs.com/search?q=keywords:karma-preprocessor
|
||||
preprocessors: {
|
||||
"**/*.ts": "karma-typescript", // *.tsx for React Jsx
|
||||
},
|
||||
|
||||
// test results reporter to use
|
||||
// possible values: 'dots', 'progress'
|
||||
// available reporters: https://www.npmjs.com/search?q=keywords:karma-reporter
|
||||
reporters: ["progress", "karma-typescript"],
|
||||
|
||||
// web server port
|
||||
port: 9876,
|
||||
|
||||
// enable / disable colors in the output (reporters and logs)
|
||||
colors: true,
|
||||
|
||||
// level of logging
|
||||
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
|
||||
logLevel: config.LOG_INFO,
|
||||
|
||||
// enable / disable watching file and executing tests whenever any file changes
|
||||
autoWatch: true,
|
||||
|
||||
// start these browsers
|
||||
// available browser launchers: https://www.npmjs.com/search?q=keywords:karma-launcher
|
||||
browsers: ["Chrome"],
|
||||
|
||||
// Continuous Integration mode
|
||||
// if true, Karma captures browsers, runs the tests and exits
|
||||
singleRun: false,
|
||||
|
||||
// Concurrency level
|
||||
// how many browser instances should be started simultaneously
|
||||
concurrency: Infinity,
|
||||
});
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"name": "binary-parser",
|
||||
"version": "2.3.0",
|
||||
"description": "Blazing-fast binary parser builder",
|
||||
"main": "dist/binary_parser.js",
|
||||
"module": "dist/esm/binary_parser.mjs",
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^10.0.1",
|
||||
"@types/node": "^20.4.2",
|
||||
"karma": "^6.4.2",
|
||||
"karma-chrome-launcher": "^3.2.0",
|
||||
"karma-mocha": "^2.0.1",
|
||||
"karma-typescript": "^5.5.4",
|
||||
"mocha": "^10.2.0",
|
||||
"prettier": "^3.0.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.1.6"
|
||||
},
|
||||
"exports": {
|
||||
"types": "./dist/binary_parser.d.ts",
|
||||
"require": "./dist/binary_parser.js",
|
||||
"import": "./dist/esm/binary_parser.mjs",
|
||||
"default": "./dist/esm/binary_parser.mjs"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "npm run build:cjs && npm run build:esm",
|
||||
"build:cjs": "tsc --module commonjs --outDir dist",
|
||||
"build:esm": "tsc --module esnext --outDir dist/esm && mv dist/esm/binary_parser.js dist/esm/binary_parser.mjs",
|
||||
"format": "prettier --list-different \"{lib,example,test,benchmark}/**/*.{ts,js}\"",
|
||||
"format:fix": "prettier --write \"{lib,example,test,benchmark}/**/*.{ts,js}\"",
|
||||
"test": "mocha --require ts-node/register test/*.ts",
|
||||
"test:browser": "karma start --single-run --browsers ChromeHeadless karma.conf.js",
|
||||
"prepare": "npm run build"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"lib"
|
||||
],
|
||||
"keywords": [
|
||||
"binary",
|
||||
"parser",
|
||||
"decode",
|
||||
"unpack",
|
||||
"struct",
|
||||
"buffer",
|
||||
"bit"
|
||||
],
|
||||
"author": {
|
||||
"name": "Keichi Takahashi",
|
||||
"email": "hello@keichi.dev",
|
||||
"url": "https://keichi.dev/"
|
||||
},
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "http://github.com/keichi/binary-parser.git"
|
||||
},
|
||||
"bugs": "http://github.com/keichi/binary-parser/issues",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,471 @@
|
||||
import { deepStrictEqual, notDeepStrictEqual, ok } from "assert";
|
||||
import { Parser } from "../lib/binary_parser";
|
||||
|
||||
function primitiveParserTests(
|
||||
name: string,
|
||||
factory: (array: Uint8Array | number[]) => Uint8Array,
|
||||
) {
|
||||
describe(`Primitive parser (${name})`, () => {
|
||||
function hexToBuf(hex: string): Uint8Array {
|
||||
return factory(hex.match(/.{1,2}/g)!.map((byte) => parseInt(byte, 16)));
|
||||
}
|
||||
|
||||
describe("Primitive parsers", () => {
|
||||
it("should nothing", () => {
|
||||
const parser = Parser.start();
|
||||
const buffer = factory([0xa, 0x14, 0x1e, 0x28, 0x32]);
|
||||
|
||||
deepStrictEqual(parser.parse(buffer), {});
|
||||
});
|
||||
it("should parse integer types", () => {
|
||||
const parser = Parser.start().uint8("a").int16le("b").uint32be("c");
|
||||
|
||||
const buffer = factory([0x00, 0xd2, 0x04, 0x00, 0xbc, 0x61, 0x4e]);
|
||||
deepStrictEqual(parser.parse(buffer), {
|
||||
a: 0,
|
||||
b: 1234,
|
||||
c: 12345678,
|
||||
});
|
||||
});
|
||||
describe("BigInt64 parsers", () => {
|
||||
it("should parse uint64", () => {
|
||||
const parser = Parser.start().uint64be("a").uint64le("b");
|
||||
// from https://nodejs.org/api/buffer.html#buffer_buf_readbiguint64le_offset
|
||||
const buf = factory([
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00,
|
||||
0x00, 0xff, 0xff, 0xff, 0xff,
|
||||
]);
|
||||
deepStrictEqual(parser.parse(buf), {
|
||||
a: BigInt("4294967295"),
|
||||
b: BigInt("18446744069414584320"),
|
||||
});
|
||||
});
|
||||
|
||||
it("should parse int64", () => {
|
||||
const parser = Parser.start()
|
||||
.int64be("a")
|
||||
.int64le("b")
|
||||
.int64be("c")
|
||||
.int64le("d");
|
||||
// from https://nodejs.org/api/buffer.html#buffer_buf_readbiguint64le_offset
|
||||
const buf = factory([
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00,
|
||||
0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
|
||||
0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
|
||||
]);
|
||||
deepStrictEqual(parser.parse(buf), {
|
||||
a: BigInt("4294967295"),
|
||||
b: BigInt("-4294967295"),
|
||||
c: BigInt("4294967295"),
|
||||
d: BigInt("-4294967295"),
|
||||
});
|
||||
});
|
||||
});
|
||||
it("should use formatter to transform parsed integer", () => {
|
||||
const parser = Parser.start()
|
||||
.uint8("a", {
|
||||
formatter: (val: number) => val * 2,
|
||||
})
|
||||
.int16le("b", {
|
||||
formatter: (val: number) => "test" + String(val),
|
||||
});
|
||||
|
||||
const buffer = factory([0x01, 0xd2, 0x04]);
|
||||
deepStrictEqual(parser.parse(buffer), { a: 2, b: "test1234" });
|
||||
});
|
||||
it("should parse floating point types", () => {
|
||||
const parser = Parser.start().floatbe("a").doublele("b");
|
||||
|
||||
const FLT_EPSILON = 0.00001;
|
||||
const buffer = factory([
|
||||
0x41, 0x45, 0x85, 0x1f, 0x7a, 0x36, 0xab, 0x3e, 0x57, 0x5b, 0xb1,
|
||||
0xbf,
|
||||
]);
|
||||
const result = parser.parse(buffer);
|
||||
|
||||
ok(Math.abs(result.a - 12.345) < FLT_EPSILON);
|
||||
ok(Math.abs(result.b - -0.0678) < FLT_EPSILON);
|
||||
});
|
||||
it("should handle endianness", () => {
|
||||
const parser = Parser.start().int32le("little").int32be("big");
|
||||
|
||||
const buffer = factory([
|
||||
0x4e, 0x61, 0xbc, 0x00, 0x00, 0xbc, 0x61, 0x4e,
|
||||
]);
|
||||
deepStrictEqual(parser.parse(buffer), {
|
||||
little: 12345678,
|
||||
big: 12345678,
|
||||
});
|
||||
});
|
||||
it("should seek offset", () => {
|
||||
const parser = Parser.start()
|
||||
.uint8("a")
|
||||
.seek(3)
|
||||
.uint16le("b")
|
||||
.uint32be("c");
|
||||
|
||||
const buffer = factory([
|
||||
0x00, 0xff, 0xff, 0xfe, 0xd2, 0x04, 0x00, 0xbc, 0x61, 0x4e,
|
||||
]);
|
||||
deepStrictEqual(parser.parse(buffer), {
|
||||
a: 0,
|
||||
b: 1234,
|
||||
c: 12345678,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Bit field parsers", () => {
|
||||
function binaryLiteral(s: string): Uint8Array {
|
||||
const bytes = Array<number>();
|
||||
|
||||
s = s.replace(/\s/g, "");
|
||||
deepStrictEqual(s.length % 8, 0);
|
||||
for (let i = 0; i < s.length; i += 8) {
|
||||
bytes.push(parseInt(s.slice(i, i + 8), 2));
|
||||
}
|
||||
|
||||
return factory(bytes);
|
||||
}
|
||||
|
||||
it("binary literal helper should work", () => {
|
||||
deepStrictEqual(binaryLiteral("11110000"), factory([0xf0]));
|
||||
deepStrictEqual(
|
||||
binaryLiteral("11110000 10100101"),
|
||||
factory([0xf0, 0xa5]),
|
||||
);
|
||||
});
|
||||
|
||||
it("should parse 1-byte-length bit field sequence", () => {
|
||||
const parser1 = new Parser().bit1("a").bit2("b").bit4("c").bit1("d");
|
||||
|
||||
const buf = binaryLiteral("1 10 1010 0");
|
||||
deepStrictEqual(parser1.parse(buf), {
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: 10,
|
||||
d: 0,
|
||||
});
|
||||
|
||||
const parser2 = new Parser()
|
||||
.endianness("little")
|
||||
.bit1("a")
|
||||
.bit2("b")
|
||||
.bit4("c")
|
||||
.bit1("d");
|
||||
|
||||
deepStrictEqual(parser2.parse(buf), {
|
||||
a: 0,
|
||||
b: 2,
|
||||
c: 10,
|
||||
d: 1,
|
||||
});
|
||||
});
|
||||
it("should parse 2-byte-length bit field sequence", () => {
|
||||
const parser1 = new Parser().bit3("a").bit9("b").bit4("c");
|
||||
|
||||
const buf = binaryLiteral("101 111000111 0111");
|
||||
deepStrictEqual(parser1.parse(buf), {
|
||||
a: 5,
|
||||
b: 455,
|
||||
c: 7,
|
||||
});
|
||||
|
||||
const parser2 = new Parser()
|
||||
.endianness("little")
|
||||
.bit3("a")
|
||||
.bit9("b")
|
||||
.bit4("c");
|
||||
deepStrictEqual(parser2.parse(buf), {
|
||||
a: 7,
|
||||
b: 398,
|
||||
c: 11,
|
||||
});
|
||||
});
|
||||
it("should parse 4-byte-length bit field sequence", () => {
|
||||
const parser1 = new Parser()
|
||||
.bit1("a")
|
||||
.bit24("b")
|
||||
.bit4("c")
|
||||
.bit2("d")
|
||||
.bit1("e");
|
||||
const buf = binaryLiteral("1 101010101010101010101010 1111 01 1");
|
||||
deepStrictEqual(parser1.parse(buf), {
|
||||
a: 1,
|
||||
b: 11184810,
|
||||
c: 15,
|
||||
d: 1,
|
||||
e: 1,
|
||||
});
|
||||
|
||||
const parser2 = new Parser()
|
||||
.endianness("little")
|
||||
.bit1("a")
|
||||
.bit24("b")
|
||||
.bit4("c")
|
||||
.bit2("d")
|
||||
.bit1("e");
|
||||
deepStrictEqual(parser2.parse(buf), {
|
||||
a: 1,
|
||||
b: 11184829,
|
||||
c: 10,
|
||||
d: 2,
|
||||
e: 1,
|
||||
});
|
||||
});
|
||||
it("should parse 32-bit fields", () => {
|
||||
const parser1 = new Parser().bit32("a");
|
||||
const buf1 = binaryLiteral("10110101011101010111001010011101");
|
||||
deepStrictEqual(parser1.parse(buf1), { a: 3044373149 });
|
||||
const parser2 = new Parser().bit6("a").bit32("b").bit2("c");
|
||||
const buf2 = binaryLiteral(
|
||||
"101101 10110101011101010111001010011101 11",
|
||||
);
|
||||
deepStrictEqual(parser2.parse(buf2), { a: 45, b: 3044373149, c: 3 });
|
||||
});
|
||||
it("should parse arbitrarily large bit field sequence", () => {
|
||||
const parser1 = new Parser()
|
||||
.bit1("a")
|
||||
.bit24("b")
|
||||
.bit4("c")
|
||||
.bit2("d")
|
||||
.bit9("e");
|
||||
const buf = binaryLiteral(
|
||||
"1 101010101010101010101010 1111 01 110100110",
|
||||
);
|
||||
deepStrictEqual(parser1.parse(buf), {
|
||||
a: 1,
|
||||
b: 11184810,
|
||||
c: 15,
|
||||
d: 1,
|
||||
e: 422,
|
||||
});
|
||||
|
||||
const parser2 = new Parser()
|
||||
.endianness("little")
|
||||
.bit1("a")
|
||||
.bit24("b")
|
||||
.bit4("c")
|
||||
.bit2("d")
|
||||
.bit9("e");
|
||||
deepStrictEqual(parser2.parse(buf), {
|
||||
a: 1,
|
||||
b: 11184829,
|
||||
c: 10,
|
||||
d: 2,
|
||||
e: 422,
|
||||
});
|
||||
});
|
||||
it("should parse nested bit fields", () => {
|
||||
const parser = new Parser().bit1("a").nest("x", {
|
||||
type: new Parser().bit2("b").bit4("c").bit1("d"),
|
||||
});
|
||||
|
||||
const buf = binaryLiteral("11010100");
|
||||
|
||||
deepStrictEqual(parser.parse(buf), {
|
||||
a: 1,
|
||||
x: {
|
||||
b: 2,
|
||||
c: 10,
|
||||
d: 0,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should assert bit fields", () => {
|
||||
const parser = new Parser()
|
||||
.bit4("a", { assert: (v) => v === 10 })
|
||||
.bit3("b", { assert: (v) => v === 7 })
|
||||
.bit1("c", { assert: (v) => v === 1 });
|
||||
const buf = binaryLiteral("1010 111 1");
|
||||
deepStrictEqual(parser.parse(buf), { a: 10, b: 7, c: 1 });
|
||||
});
|
||||
|
||||
it("should format bit fields", () => {
|
||||
const parser = new Parser()
|
||||
.bit4("a", { formatter: (v) => v * 2 })
|
||||
.bit3("b", { formatter: (v) => v * 3 })
|
||||
.bit1("c", { formatter: (v) => v * 4 });
|
||||
const buf = binaryLiteral("1010 111 1");
|
||||
deepStrictEqual(parser.parse(buf), { a: 20, b: 21, c: 4 });
|
||||
});
|
||||
});
|
||||
|
||||
describe("String parser", () => {
|
||||
it("should parse UTF8 encoded string (ASCII only)", () => {
|
||||
const text = "hello, world";
|
||||
const buffer = factory(new TextEncoder().encode(text));
|
||||
const parser = Parser.start().string("msg", {
|
||||
length: buffer.length,
|
||||
encoding: "utf8",
|
||||
});
|
||||
|
||||
deepStrictEqual(parser.parse(buffer).msg, text);
|
||||
});
|
||||
it("should parse UTF8 encoded string", () => {
|
||||
const text = "こんにちは、せかい。";
|
||||
const buffer = factory(new TextEncoder().encode(text));
|
||||
const parser = Parser.start().string("msg", {
|
||||
length: buffer.length,
|
||||
encoding: "utf8",
|
||||
});
|
||||
|
||||
deepStrictEqual(parser.parse(buffer).msg, text);
|
||||
});
|
||||
it("should parse HEX encoded string", () => {
|
||||
const text = "cafebabe";
|
||||
const buffer = hexToBuf(text);
|
||||
const parser = Parser.start().string("msg", {
|
||||
length: buffer.length,
|
||||
encoding: "hex",
|
||||
});
|
||||
|
||||
deepStrictEqual(parser.parse(buffer).msg, text);
|
||||
});
|
||||
it("should parse variable length string", () => {
|
||||
const buffer = hexToBuf("0c68656c6c6f2c20776f726c64");
|
||||
const parser = Parser.start()
|
||||
.uint8("length")
|
||||
.string("msg", { length: "length", encoding: "utf8" });
|
||||
|
||||
deepStrictEqual(parser.parse(buffer).msg, "hello, world");
|
||||
});
|
||||
it("should parse zero terminated string", () => {
|
||||
const buffer = hexToBuf("68656c6c6f2c20776f726c6400");
|
||||
const parser = Parser.start().string("msg", {
|
||||
zeroTerminated: true,
|
||||
encoding: "utf8",
|
||||
});
|
||||
|
||||
deepStrictEqual(parser.parse(buffer), { msg: "hello, world" });
|
||||
});
|
||||
it("should parser zero terminated fixed-length string", () => {
|
||||
const buffer = factory(
|
||||
new TextEncoder().encode("abc\u0000defghij\u0000"),
|
||||
);
|
||||
const parser = Parser.start()
|
||||
.string("a", { length: 5, zeroTerminated: true })
|
||||
.string("b", { length: 5, zeroTerminated: true })
|
||||
.string("c", { length: 5, zeroTerminated: true });
|
||||
|
||||
deepStrictEqual(parser.parse(buffer), {
|
||||
a: "abc",
|
||||
b: "defgh",
|
||||
c: "ij",
|
||||
});
|
||||
});
|
||||
it("should strip trailing null characters", () => {
|
||||
const buffer = hexToBuf("746573740000");
|
||||
const parser1 = Parser.start().string("str", {
|
||||
length: 7,
|
||||
stripNull: false,
|
||||
});
|
||||
const parser2 = Parser.start().string("str", {
|
||||
length: 7,
|
||||
stripNull: true,
|
||||
});
|
||||
|
||||
deepStrictEqual(parser1.parse(buffer).str, "test\u0000\u0000");
|
||||
deepStrictEqual(parser2.parse(buffer).str, "test");
|
||||
});
|
||||
it("should parse string greedily with zero-bytes internally", () => {
|
||||
const buffer = factory(
|
||||
new TextEncoder().encode("abc\u0000defghij\u0000"),
|
||||
);
|
||||
const parser = Parser.start().string("a", { greedy: true });
|
||||
|
||||
deepStrictEqual(parser.parse(buffer), {
|
||||
a: "abc\u0000defghij\u0000",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Bytes parser", () => {
|
||||
it("should parse as buffer", () => {
|
||||
const parser = new Parser().uint8("len").buffer("raw", {
|
||||
length: "len",
|
||||
});
|
||||
|
||||
const hex = "deadbeefdeadbeef";
|
||||
|
||||
deepStrictEqual(parser.parse(hexToBuf("08" + hex)).raw, hexToBuf(hex));
|
||||
});
|
||||
|
||||
it("should clone buffer if options.clone is true", () => {
|
||||
const parser = new Parser().buffer("raw", {
|
||||
length: 8,
|
||||
clone: true,
|
||||
});
|
||||
|
||||
const buf = hexToBuf("deadbeefdeadbeef");
|
||||
const result = parser.parse(buf);
|
||||
deepStrictEqual(result.raw, buf);
|
||||
result.raw[0] = 0xff;
|
||||
notDeepStrictEqual(result.raw, buf);
|
||||
});
|
||||
|
||||
it("should parse until function returns true when readUntil is function", () => {
|
||||
const parser = new Parser()
|
||||
.endianness("big")
|
||||
.uint8("cmd")
|
||||
.buffer("data", {
|
||||
readUntil: (item: number) => item === 2,
|
||||
});
|
||||
|
||||
const result1 = parser.parse(hexToBuf("aa"));
|
||||
deepStrictEqual(result1, { cmd: 0xaa, data: factory([]) });
|
||||
|
||||
const result2 = parser.parse(hexToBuf("aabbcc"));
|
||||
deepStrictEqual(result2, { cmd: 0xaa, data: hexToBuf("bbcc") });
|
||||
|
||||
const result3 = parser.parse(hexToBuf("aa02bbcc"));
|
||||
deepStrictEqual(result3, { cmd: 0xaa, data: factory([]) });
|
||||
|
||||
const result4 = parser.parse(hexToBuf("aabbcc02"));
|
||||
deepStrictEqual(result4, { cmd: 0xaa, data: hexToBuf("bbcc") });
|
||||
|
||||
const result5 = parser.parse(hexToBuf("aabbcc02dd"));
|
||||
deepStrictEqual(result5, { cmd: 0xaa, data: hexToBuf("bbcc") });
|
||||
});
|
||||
|
||||
// this is a test for testing a fix of a bug, that removed the last byte
|
||||
// of the buffer parser
|
||||
it("should return a buffer with same size", () => {
|
||||
const bufferParser = new Parser().buffer("buf", {
|
||||
readUntil: "eof",
|
||||
formatter: (buffer: Uint8Array) => buffer,
|
||||
});
|
||||
|
||||
const buffer = factory(new TextEncoder().encode("John\0Doe\0"));
|
||||
deepStrictEqual(bufferParser.parse(buffer), { buf: buffer });
|
||||
});
|
||||
});
|
||||
|
||||
describe("Security", () => {
|
||||
it("should throw an error on invalid field name", () => {
|
||||
try {
|
||||
new Parser().uint8('a; console.log("INJECTED CODE EXECUTED"); //');
|
||||
throw new Error("Should have thrown error");
|
||||
} catch (e: any) {
|
||||
ok(e.message.includes("Invalid field name"));
|
||||
}
|
||||
});
|
||||
|
||||
it("should throw an error on invalid encoding name", () => {
|
||||
try {
|
||||
new Parser().string("s", {
|
||||
length: 1,
|
||||
encoding: "utf8'); console.log('INJECTED ENCODING EXECUTED'); //",
|
||||
});
|
||||
throw new Error("Should have thrown error");
|
||||
} catch (e: any) {
|
||||
ok(e.message.includes("Invalid encoding"));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
primitiveParserTests("Buffer", (arr) => Buffer.from(arr));
|
||||
primitiveParserTests("Uint8Array", (arr) => Uint8Array.from(arr));
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"types": ["node", "mocha"],
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"files": [
|
||||
"lib/binary_parser.ts"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user