Parse and consume binary streams with a neat DSL.
Dissolve allows you to parse packed binary data into numbers, buffers, strings and more*! With a simple syntax inspired by node-binary and a solid, minimal implementation, you can be up and running in no time.
(* implementing "more" is left as an exercise to the reader)
If you want to produce binary data, might I suggest concentrate?
- Accurate handling of [u]int{8,16,32} numbers in both signed and unsigned variants using fast, built-in Buffer methods
- Fast approximation of [u]int64 numbers in signed and unsigned variants
- Extendable base class for building your own parsers and implementing custom types
- Tiny (~250 LoC) implementation, allowing for easy debugging
Available via npm:
$ npm install dissolve
Or via git:
$ git clone git://github.com/deoxxa/dissolve.git node_modules/dissolve
Also see example.js, example-complex.js and example-loop.js.
#!/usr/bin/env node
var Dissolve = require("./index");
var parser = Dissolve().loop(function(end) {
this.uint8("id").tap(function() {
if (this.vars.id === 0x01) {
this.uint16be("a").uint16be("b");
} else if (this.vars.id === 0x02) {
this.uint32be("x").uint32be("y");
}
}).tap(function() {
this.push(this.vars);
this.vars = {};
});
});
parser.on("readable", function() {
var e;
while (e = parser.read()) {
console.log(e);
}
});
parser.write(new Buffer([0x01, 0x00, 0x02, 0x00, 0x03])); // {id: 1, a: 2, b: 3}
parser.write(new Buffer([0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05])); // {id: 2, x: 4, y: 5}
parser.write(new Buffer([0x01]));
parser.write(new Buffer([0x00, 0x02, 0x00]));
parser.write(new Buffer([0x03])); // {id: 1, a: 2, b: 3}
All parser methods are chainable and return the same parser instance they were called on.
tap(name, callback)
This method allows you to "tap" into the parser at an arbitrary point. The
callback will be called bound to the parser instance, so you can use parser
methods on this
. Any additional parser steps you introduce inside the callback
will be executed before any existing steps that are already scheduled to run
after the tap
call.
If you provide a name
parameter, all actions performed in the callback will be
applied to a child object that will be put into a new property named after
name
. Note that in the callback, even if you provide a name
parameter, you
can still pretend you were in the outer "scope" because of some prototype
trickery done with the vars
object under the hood. You don't need to worry
about that too much, the examples should make it a bit clearer.
loop(name, callback)
This method is like tap
except that the callback is called over and over until
signalled to stop. You do this by calling the end
function that's provided as
the first argument to your callback. When you call the end
function, you can
provide an optional truthy/non-truthy flag to tell Dissolve to ignore the result
of the iteration of the loop where end
was called. This is useful if you are
reading until a null entry or similar.
If you provide a name
parameter, a new array will be placed into a property
named for that parameter, and after each iteration of the loop, any new values
will be appended to the array as an object. As with the name
stuff on tap
,
the examples will make that explanation a lot clearer.
The same semantics for job ordering and "scoping" apply as for tap
.
For each basic parsing method, the name
value is the key under which the value
will be attached to this.vars
.
For these methods, the length
parameter tells the parser how many bytes to
pull out. If it's a string, it will be assumed that it is the name of a
previously-set this.vars
entry. If it's a number, it will be used as-is.
buffer(name, length)
- binary slicestring(name, length)
- utf8 string sliceskip(length)
- skiplength
bytes
int8(name)
- signed 8 bit integersint8(name)
- signed 8 bit integeruint8(name)
- unsigned 8 bit integerint16(name)
- signed, little endian 16 bit integerint16le(name)
- signed, little endian 16 bit integerint16be(name)
- signed, big endian 16 bit integersint16(name)
- signed, little endian 16 bit integersint16le(name)
- signed, little endian 16 bit integersint16be(name)
- signed, big endian 16 bit integeruint16(name)
- unsigned, little endian 16 bit integeruint16le(name)
- unsigned, little endian 16 bit integeruint16be(name)
- unsigned, big endian 16 bit integerint32(name)
- signed, little endian 32 bit integerint32le(name)
- signed, little endian 32 bit integerint32be(name)
- signed, big endian 32 bit integersint32(name)
- signed, little endian 32 bit integersint32le(name)
- signed, little endian 32 bit integersint32be(name)
- signed, big endian 32 bit integeruint32(name)
- unsigned, little endian 32 bit integeruint32le(name)
- unsigned, little endian 32 bit integeruint32be(name)
- unsigned, big endian 32 bit integerint64(name)
- signed, little endian 64 bit integerint64le(name)
- signed, little endian 64 bit integerint64be(name)
- signed, big endian 64 bit integersint64(name)
- signed, little endian 64 bit integersint64le(name)
- signed, little endian 64 bit integersint64be(name)
- signed, big endian 64 bit integeruint64(name)
- unsigned, little endian 64 bit integeruint64le(name)
- unsigned, little endian 64 bit integeruint64be(name)
- unsigned, big endian 64 bit integerfloatbe(data)
- big endian 32 bit floatfloatle(data)
- little endian 32 bit floatdoublebe(data)
- big endian 64 bit doubledoublele(data)
- little endian 64 bit double
3-clause BSD. A copy is included with the source.
- GitHub (deoxxa)
- Twitter (@deoxxa)
- ADN (@deoxxa)
- Email ([email protected])