UNPKG

qunpack

Version:

unpack binary strings like perl and php

217 lines (156 loc) 9.12 kB
qunpack ======= [![Build Status](https://api.travis-ci.org/andrasq/node-qunpack.svg?branch=master)](https://travis-ci.org/andrasq/node-qunpack?branch=master) [![Coverage Status](https://codecov.io/github/andrasq/node-qunpack/coverage.svg?branch=master)](https://codecov.io/github/andrasq/node-qunpack?branch=master) `Unpack` decodes a binary string into numbers and strings according to the specified format. This implementation is a javascript-only mostly compatible work-alike of PERL and PHP [`unpack()`](http://php.net/manual/en/function.unpack.php). `qunpack` favors big-endian storage; I needed both signed and unsigned big-endian support, so I store generic "native" ("machine byte order") values in network byte order (big-endian). Later I added full little-endian support as well, but the default is still network byte order. The syntax is more like PERL than PHP; the format string is a concatenated series of conversion specifiers without names, like PERL "SL" meaning `[ short, long ]` and not PHP "S`sname`/L`lname`" for `{ sname: short, lname: long }`. Full signed little-endian support is now available with eg `s<` extensions. It is also possible to extract nested lists and nested hashes with with `qunpack`-specific extensions. Example: var bytes = new Buffer([129,2,3,4,5,6,7,8,9,10,11,12]); // interpret bytes as a signed short, two bytes and two unsigned shorts: var valuesArray = qunpack.unpack('sCCSS', bytes); // => [ -32510, 3, 4, 0x0506, 0x0708 ] // interpret as 6 bytes then a little-endian unsigned long: var valuesArray = qunpack.unpack('C6V', bytes); // => [ 129, 2, 3, 4, 5, 6, 0x0a090807 ] Api --- ### qunpack.unpack( format, bytes, [offset] ) Unpack the binary string `bytes` in the Buffer according to the `format` string. See below for the format specification. Returns an array of values. If an `offset` is given, unpack starting that many bytes from the start of the binary string. ### qunpack.pack( format, data ) TBD. Maybe later. Formats ------- The format string is a concatenated list of conversion specifiers, like for PERL `unpack`. Each conversion specifier is a format letter followed by an optional count (default `1`). Note that the `pack/unpack` conversions are incomplete, it is not possible to specify all four combinations of signed/unsigned + little-e/big-e: endianness can be controlled only for unsigneds and only for integers. I needed signed big-endian support, so this implementation interprets "machine byte order" as being big-endian. I later added full little-endian support, some via the `s<`, `l<` and `q<` conversion extensions. The conversion count is interpreted as: [aAHZ]<length> - a `length` byte string (default 1 byte) <CONV><count> - `count` instances of the type specified by CONV x<count> - skip ahead `count` bytes X<count> - back up `count` bytes @<offset> - seek to absolute position `offset` Z+<count> - `count` NUL-terminated variable-length strings The available conversion specifiers are: Strings, decoded as javascript utf8: a - NUL-padded string, retains NUL padding A - SPACE-padded string, trailing whitespace stripped Z - NUL-padded string, trailing NULs stripped H - hex string, high nybble first ABC => '414243' h - hex string, low nybble first ABC => '142434' Z+ - NUL-terminated variable-length string, NUL stripped (extension) Portable numeric conversions: n - unsigned 16-bit big-e short N - unsigned 32-bit big-e long J - unsigned 64-bit big-e long long, with 53 bits of precision v - unsigned 16-bit little-e short V - unsigned 32-bit little-e long P - unsigned 64-bit little-e long long Native numbers, stored in big-endian network byte order: c,C - signed, unsigned 8-bit char s,S - signed, unsigned 16-bit "native" (big-e) short (word) i,I - signed, unsigned "native" int (32-bit big-e) l,L - signed, unsigned 32-bit "native" (big-e) long (longword) q,Q - signed, unsigned 64-bit "native" (big-e) long long (quadword), extracted into a native JavaScript number. Note that JavaScript numbers are 64-bit doubles and support only 53 bits of precision. Larger values may lose least significant bits. f,d - 32-bit and 64-bit "native" big-e float and double G,E - 32-bit and 64-bit big-e float and double g,e - 32-bit and 64-bit little-e float and double g - 32-bit little-float e - 64-bit littl-e double s<,l<,q< - signed 16-bit, 32-bit and 64-bit little-e integers (extensions) Grouping extensions: [# ... ] - subformat inside the [ ] will be gathered into a sub-array. The subformat may have a non-zero repeat count, in which case that many sub-arrays will be extracted. A negative count is ignored. It is an error for the count to be zero. Eg, 'C[S2]' on [1,2,3,4,5,6] => [ 0x01, [0x0203, 0x0405] ] EXPERIMENTAL. {# ... } - extracts # objects with properties determined by the named conversion specifiers contained inside the `{ ... }` (default 1 object). A negative count is ignored. The count must not be zero. Eg, '{2 a:C, x:X1, b:S}' on [1,2,3,4] => [ {a:1, b:0x0102}, {a:3, b:0304} ] EXPERIMENTAL. Position control: x - skip a byte (NUL-fill if packing) X - back up a byte @ - seek to absolute offset (NUL-fill if packing) Not supported conversion specifiers (for completeness): * - "all remaining" repetition specifier ( ... ) - grouping specifier Examples: "a5" - extract a 5-byte NUL-padded string, eg "ABC\0\0" -> "ABC\0\0" "A5" - extract a 5-byte SPACE-padded string, eg "ABC " => "ABC" "S2" - extract two unsigned shorts, eg "\1\2\3\4" => [ 0x0102, 0x0304 ] "Z+3L" - extract three variable-length NUL-terminated strings, then a 32-bit long. Eg, '1\0two\0three\0\1\2\3\4' => [ '1', 'two', 'three', 0x01020304 ]. Differences ----------- - `qunpack` does not try to emulate PHP or full PERL `unpack`. It does borrow some of their syntax, but favors big-endian values and provides some non-standard extensios. - `qunpack` supports non-standard extensions like `s<` and `{# ... }` hashes - the `unpack` format is interpreted like PERL, where `S1L1` means one short followed by one long, not a short stored into property named 'L1'. The PHP syntax would be e.g. `S1shortName/L1longName`, returning `{ shortName: <16 bits>, longName: <32 bits> }`. - the `Z+#` conversion is a `qunpack` extension. It extracts `count` (default 1) variable-length NUL-terminated strings from the input. - the `[# ... ]` grouping conversion is a `qunpack` extension. It extracts `count` (default 1) sub-arrays with the format contained between the brackets. EXPERIMENTAL. - the `{# ... }` grouping conversion is a `qunpack` extension. It extracts `count` (default 1) objects with properties according to the named formats contained between the braces. EXPERIMENTAL. - the `s<` etc little-endian conversions are a `qunpack` extension. The two-character format specifiers `s<`, `l<` and `q<` read signed 16-bit, 32-bit and 64-bit integers from - 64-bit integer support is limited to 53 bits, since that's the precision available in javascript numbers. There is no built-in support for assembling quadword longs out of parts. Change Log ---------- - 0.5.2 - make {#...} hashes more tolerant of odd punctuation and spacing, error out on unrecognized conversion characters - 0.5.1 - fix node-v0.10 H,h hex conversion (use integer base,bound) - 0.5.0 - full little-endian support, fix 'h' and 'H' string length - 0.4.0 - initial version of `{# ... }` grouping, fix H conversion, add h conversion - 0.3.2 - support for `[# ... ]` sub-group count - 0.3.1 - speed up by passing a around state object, fewer arguments - 0.3.0 - `[ ... ]` sub-group parsing, also much faster due to faster state passing - 0.2.0 - `Z+#` countable variable-length asciiz string conversion specifier - 0.1.1 - fix c,C count, ignore negative unpack offset - 0.1.0 - initial implementation, with unit tests Todo ---- - implement `pack` - make bounds errors fatal, to not slip by undetected - omit the wrapping array if unpacking just 1 value and no count specified (ie, 'L2' => [0,0], 'L1' => [0], but 'L' => 0. However, 'A6' => 'string' because '6' is the size, not a count.) - speed up `{# ... }` extraction (avoid unused array) - fix nested hash extraction - fix multiple-subarray extraction - maybe an option for the default storage-endian operating mode - maybe `s>`, `l>`, `q>` explicit-endian short, long and quad support Related Work ------------ - [`hipack`](https://npmjs.com/package/hipack) - php compatible pack/unpack, uses C++ extension, needs node sources to install - [`php-pack`](https://npmjs.com/package/php-pack) - fork of `hipack` - [`qmsgpack`](https://github.com/andrasq/node-q-msgpack) - experimental nodejs `msgpack` with related works bibliography