binary-data
Advanced tools
Comparing version
'use strict'; | ||
const { types, decode } = require('..'); | ||
const binary = require('binary'); // eslint-disable-line node/no-unpublished-require | ||
@@ -62,4 +63,41 @@ /* eslint-disable no-useless-concat */ | ||
function testBinary(i) { | ||
while (--i > 0) { | ||
parseBinary(); | ||
} | ||
} | ||
function parseBinary() { | ||
// Incomplete ClientHello packet | ||
const record = binary | ||
.parse(packet) | ||
.word8u('contentType') | ||
.word16bu('version') | ||
.word16bu('epoch') | ||
.buffer('sequenceNumber', 6) | ||
.word16bu('length') | ||
.buffer('body', 'length').vars; | ||
const handshake = binary | ||
.parse(record.body) | ||
.word8u('type') | ||
.buffer('length', 3) | ||
.word16bu('messageSeq') | ||
.buffer('fragment_offset', 3) | ||
.buffer('fragment_length', 3) | ||
.word16bu('clientVersion') | ||
.word32bu('gmtunixtime') | ||
.buffer('randomBytes', 28) | ||
.word8u('sessionId_length') | ||
.buffer('sessionId', 'sessionId_length') | ||
.word8u('cookie_length') | ||
.buffer('cookie', 'cookie_length').vars; | ||
return [record, handshake]; | ||
} | ||
console.time('binary data'); | ||
test(count); | ||
console.timeEnd('binary data'); | ||
console.time('binary'); | ||
testBinary(count); | ||
console.timeEnd('binary'); |
{ | ||
"name": "binary-data", | ||
"version": "0.5.0", | ||
"description": "Declarative encoder/decoder of various binary data.", | ||
"version": "0.6.0", | ||
"description": "Declarative binary data encoder / decoder.", | ||
"main": "src/index.js", | ||
@@ -25,3 +25,4 @@ "scripts": { | ||
"bin-protocol", | ||
"restructure" | ||
"restructure", | ||
"varstruct" | ||
], | ||
@@ -43,2 +44,4 @@ "author": "Dmitry Tsvettsikh <me@reklatsmasters.com>", | ||
"@nodertc/eslint-config": "0.2.1", | ||
"binary": "^0.3.0", | ||
"bl": "^2.1.2", | ||
"eslint": "^5.6.1", | ||
@@ -45,0 +48,0 @@ "jest": "^23.6.0", |
111
README.md
@@ -10,4 +10,8 @@ # binary-data | ||
Declarative encoder/decoder of various binary data. This module works almost like as [`binary`](https://www.npmjs.com/package/binary) or [`restructure`](https://www.npmjs.com/package/restructure) but provided modern and clean api. | ||
Declarative binary data encoder / decoder. This module works almost like as [`binary`](https://www.npmjs.com/package/binary) or [`restructure`](https://www.npmjs.com/package/restructure) but provided modern and clean api. It inspired by [abstract-encoding](https://github.com/mafintosh/abstract-encoding) interface. | ||
### Support | ||
[](https://www.buymeacoffee.com/reklatsmasters) | ||
## Usage | ||
@@ -18,5 +22,5 @@ | ||
```js | ||
const { decode, createDecodeStream, types: { uint8, array, string } } = require('binary-data') | ||
const { decode, createDecode, types: { uint8, array, string } } = require('binary-data') | ||
// 1.1 define your own schema as plain object | ||
// 1. Define your own schema as plain object | ||
const protocol = { | ||
@@ -27,17 +31,16 @@ type: uint8, | ||
socket.on('message', (message) => { | ||
// 1.2 decode message | ||
const packet = decode(message, protocol) | ||
}) | ||
const message = Buffer.from([1, 2, 3, 4, 5, 6, 0]); | ||
// 2.1 also you may decode messages from streams | ||
const unicast = require('unicast') | ||
// Just decode message | ||
const packet = decode(message, protocol) | ||
const socket = unicast.createSocket({ /* options */ }) | ||
// 2 Also you may decode messages from streams | ||
const net = require('net'); | ||
// 2.2 create stream | ||
const input = createDecodeStream(protocol) | ||
const socket = net.createConnection({ port: 8124 }); | ||
const istream = createDecode(protocol); | ||
// 2.3 connect streams | ||
socket.pipe(input).on('data', packet => { /* do stuff */ }) | ||
socket.pipe(istream).on('data', packet => { | ||
console.log(packet.type, packet.value); | ||
}); | ||
``` | ||
@@ -48,30 +51,37 @@ | ||
```js | ||
const { encode, createEncodeStream, types: { uint8, buffer } } = require('binary-data') | ||
const { encode, createEncode, types: { uint8, string } } = require('binary-data') | ||
// 1. define schema | ||
const protocol = { | ||
type: uint8, | ||
data: buffer(uint8) | ||
value: string(uint8) | ||
} | ||
// 2. create data object (string, array - what you want) | ||
const hello = { | ||
type: 12, | ||
data: Buffer.from('my random data') | ||
value: 'my random data' | ||
} | ||
// 3. create encode stream | ||
const wstream = createEncodeStream(protocol) | ||
// Just encode message | ||
const ostream = encode(hello, protocol); | ||
const packet = ostream.slice(); | ||
// 4. connect streams | ||
wstream.pipe(socket) | ||
// Or you may encode messages into a stream | ||
const net = require('net'); | ||
// 5.1. encode all your data | ||
wstream.write(hello) | ||
const ostream = createEncode(protocol); | ||
const socket = net.createConnection({ port: 8124 }, () => { | ||
ostream.write(hello); | ||
}); | ||
// 5.2 or use another schema | ||
encode(anotherPacket, wstream, anotherSchema) | ||
ostream.pipe(socket); | ||
// 5.3 or convert to a buffer | ||
const buf = wstream.slice() | ||
// You may combine multiple schemes into one stream | ||
const ostream = createEncode(); | ||
encode(obj1, ostream, protocol1); | ||
encode(obj2, ostream, protocol2); | ||
encode(obj3, ostream, protocol3); | ||
const packet = ostream.slice(); | ||
``` | ||
@@ -81,9 +91,20 @@ | ||
## Perfomance | ||
Decoding DTLS ClientHello packet, *nodejs 10.14.1 / Ubuntu 16.04 x64* | ||
|name|time| | ||
|---|---| | ||
|binary data|637.900ms| | ||
|binary|2229.218ms| | ||
## API | ||
* [`decode(rstream: DecodeStream|Buffer, type: PrimitiveType|Object): any`](#decode) | ||
* [`encode(item: any, wstream: EncodeStream, type: PrimitiveType|Object): void`](#encode) | ||
* [`encodingLength(item: any, type: PrimitiveType|Object): Number`](#encoding-length) | ||
* [`createEncodeStream(): EncodeStream`](#create-encode-stream) | ||
* [`createDecodeStream([buf: Buffer]): DecodeStream`](#create-decode-stream) | ||
* [`encode(obj: any, [target: BinaryStream], type: Object): BinaryStream`](#encode) | ||
* [`decode(source: BinaryStream|Buffer, type: Object): any`](#decode) | ||
* [`encodingLength(item: any, type: Object): Number`](#encoding-length) | ||
* [`createEncodeStream([type: Object]): BinaryStream`](#create-encode-stream) | ||
* [`createDecodeStream([type: Object|Buffer]): BinaryStream`](#create-decode-stream) | ||
* [`createEncode([type: Object]): BinaryStream`](#create-encode-stream) | ||
* [`createDecode([type: Object|Buffer]): BinaryStream`](#create-decode-stream) | ||
* [Types](#types) | ||
@@ -101,3 +122,3 @@ * [`(u)int(8, 16, 24, 32, 40, 48)(be, le)`](#types-int) | ||
#### `decode(rstream: DecodeStream|Buffer, type: PrimitiveType|Object): any` | ||
#### `decode(source: BinaryStream|Buffer, type: Object): any` | ||
@@ -108,9 +129,9 @@ Reads any data from stream `rstream` using data type `type`. See examples above. | ||
#### `encode(item: any, wstream: EncodeStream, type: PrimitiveType|Object): void` | ||
#### `encode(obj: any, [target: BinaryStream], type: Object): BinaryStream` | ||
Writes any data `item` to stream `wstream` using data type `type`. See examples above. | ||
Writes any data `obj` to stream `target` using data type `type`. See examples above. | ||
<a name='encoding-length' /> | ||
#### `encodingLength(item: any, type: PrimitiveType|Object): Number` | ||
#### `encodingLength(item: any, type: Object): Number` | ||
@@ -121,11 +142,13 @@ Return the amount of bytes needed to encode `item` using `type`. | ||
#### `createEncodeStream(): EncodeStream` | ||
#### `createEncodeStream([type: Object]): BinaryStream` | ||
#### `createEncode([type: Object]): BinaryStream` | ||
Create instance of EncodeStream. | ||
Create instance of BinaryStream. | ||
<a name='create-decode-stream' /> | ||
#### `createDecodeStream([buf: Buffer]): DecodeStream` | ||
#### `createDecodeStream([type: Object|Buffer]): BinaryStream` | ||
#### `createDecode([type: Object|Buffer]): BinaryStream` | ||
Create instance of DecodeStream using buffer `buf`. | ||
Create instance of BinaryStream. | ||
@@ -217,5 +240,5 @@ <a name='types' /> | ||
#### `buffer(length)` | ||
#### `buffer(length: Object|null|number)` | ||
Low-level buffer type. Argument `length` can be _number_, number _type_ for size-prefixed data or _function_. | ||
Low-level buffer type. Argument `length` can be _number_, number _type_ for size-prefixed data, _function_ or _null_. | ||
@@ -222,0 +245,0 @@ ```js |
'use strict'; | ||
const EncodeStream = require('streams/encode'); | ||
const DecodeStream = require('streams/decode'); | ||
const BinaryStream = require('lib/binary-stream'); | ||
const array = require('types/array'); | ||
@@ -33,2 +32,4 @@ const buffer = require('types/buffer'); | ||
const kschema = Symbol('schema'); | ||
/** | ||
@@ -40,6 +41,10 @@ * Create transform stream to encode objects into Buffer. | ||
function createEncodeStream(schema) { | ||
return new EncodeStream({ | ||
schema, | ||
const stream = new BinaryStream({ | ||
readableObjectMode: false, | ||
writableObjectMode: true, | ||
transform: transformEncode, | ||
}); | ||
stream[kschema] = schema; | ||
return stream; | ||
} | ||
@@ -60,7 +65,10 @@ | ||
const stream = new DecodeStream({ | ||
schema, | ||
const stream = new BinaryStream({ | ||
transform: transformDecode, | ||
readableObjectMode: true, | ||
writableObjectMode: false, | ||
}); | ||
stream[kschema] = schema; | ||
if (isBuffer) { | ||
@@ -81,3 +89,3 @@ stream.append(bufOrSchema); | ||
try { | ||
encode(chunk, this, this.schema); | ||
encode(chunk, this[kschema], this); | ||
@@ -105,3 +113,3 @@ const buf = this.slice(); | ||
const transaction = new Transaction(this); | ||
const data = decode(transaction, this.schema); | ||
const data = decode(transaction, this[kschema]); | ||
@@ -130,2 +138,6 @@ transaction.commit(); | ||
/* aliases */ | ||
createEncode: createEncodeStream, | ||
createDecode: createDecodeStream, | ||
/* Data types */ | ||
@@ -135,5 +147,4 @@ types, | ||
/* Re-export utils */ | ||
EncodeStream, | ||
DecodeStream, | ||
BinaryStream, | ||
NotEnoughDataError, | ||
}; |
@@ -28,3 +28,3 @@ 'use strict'; | ||
* Adds an additional buffer or BufferList to the internal list. | ||
* @param {Buffer|Buffer[]} buf | ||
* @param {Buffer|Buffer[]|BufferList|BufferList[]} buf | ||
*/ | ||
@@ -45,2 +45,23 @@ append(buf) { | ||
} | ||
} else if (buf instanceof BufferList) { | ||
if (this.offset > 0) { | ||
const head = this.queue.shift(); | ||
this.queue.unshift(head.slice(this.offset)); | ||
this.offset = 0; | ||
} | ||
if (buf.offset > 0) { | ||
const head = buf.queue.shift(); | ||
buf.queue.unshift(head.slice(buf.offset)); | ||
buf.offset = 0; | ||
} | ||
let leaf = buf.queue.head; | ||
while (leaf) { | ||
this.queue.push(leaf.buffer); | ||
leaf = leaf.next; | ||
} | ||
} | ||
@@ -122,11 +143,13 @@ } | ||
const bufs = new Array(subset.count); | ||
let leaf = subset.head; | ||
const target = Buffer.allocUnsafe(subset.length); | ||
let offset = 0; | ||
for (let i = 0; i < bufs.length; i += 1) { | ||
bufs[i] = leaf.buffer; | ||
for (let i = 0; i < subset.count; i += 1) { | ||
target.set(leaf.buffer, offset); | ||
offset += leaf.buffer.length; | ||
leaf = leaf.next; | ||
} | ||
return Buffer.concat(bufs, subset.length); | ||
return target; | ||
/* eslint-enable no-param-reassign */ | ||
@@ -167,2 +190,76 @@ } | ||
} | ||
/** | ||
* Returns the first (least) index of an element | ||
* within the list equal to the specified value, | ||
* or -1 if none is found. | ||
* @param {number} byte | ||
* @param {number} [offset] | ||
* @returns {number} | ||
*/ | ||
indexOf(byte, offset = 0) { | ||
/* eslint-disable no-param-reassign */ | ||
if (!Number.isInteger(byte)) { | ||
throw new TypeError('Invalid argument 1'); | ||
} | ||
if (byte < 0 || byte > 0xff) { | ||
throw new Error('Invalid argument 1'); | ||
} | ||
if (!Number.isInteger(offset)) { | ||
offset = 0; | ||
} | ||
while (offset >= this.length) { | ||
offset -= this.length; | ||
} | ||
while (offset < 0) { | ||
offset += this.length; | ||
} | ||
let leaf = this.queue.head; | ||
let bias = 0; | ||
const next = () => { | ||
bias += leaf.buffer.length; | ||
leaf = leaf.next; | ||
}; | ||
while (leaf) { | ||
let byteOffset = 0; | ||
if (leaf === this.queue.head) { | ||
byteOffset += this.offset; | ||
} | ||
// `offset` is point to next chunk | ||
if (offset >= leaf.buffer.length - byteOffset) { | ||
offset -= leaf.buffer.length - byteOffset; | ||
next(); | ||
continue; // eslint-disable-line no-continue | ||
} | ||
// `offset` is point to current chunk | ||
if (offset < leaf.buffer.length) { | ||
byteOffset += offset; | ||
} | ||
const index = leaf.buffer.indexOf(byte, byteOffset); | ||
if (index > -1) { | ||
return index + bias - this.offset; | ||
} | ||
next(); | ||
if (byteOffset > this.offset) { | ||
offset = 0; | ||
} | ||
} | ||
return -1; | ||
/* eslint-enable no-param-reassign */ | ||
} | ||
} | ||
@@ -169,0 +266,0 @@ |
'use strict'; | ||
const { isType, isUserType, isDecodeType } = require('lib/util'); | ||
const DecodeStream = require('streams/decode'); | ||
const BinaryStream = require('lib/binary-stream'); | ||
const symbols = require('internal/symbols'); | ||
@@ -15,3 +15,3 @@ const Metadata = require('internal/meta'); | ||
* Decode any data from provided stream using schema. | ||
* @param {DecodeStream} rstream Read stream to decode. | ||
* @param {BinaryStream} rstream Read stream to decode. | ||
* @param {Object} typeOrSchema Builtin data type or schema. | ||
@@ -24,3 +24,4 @@ * @returns {*} | ||
if (Buffer.isBuffer(rstream)) { | ||
decodeStream = new DecodeStream(rstream); | ||
decodeStream = new BinaryStream(); | ||
decodeStream.append(rstream); | ||
} | ||
@@ -39,3 +40,3 @@ | ||
* @private | ||
* @param {DecodeStream|Buffer} rstream | ||
* @param {BinaryStream|Buffer} rstream | ||
* @param {Object} typeOrSchema | ||
@@ -57,3 +58,3 @@ * @param {Metadata} meta | ||
* @private | ||
* @param {DecodeStream} rstream | ||
* @param {BinaryStream} rstream | ||
* @param {Object} schema | ||
@@ -60,0 +61,0 @@ * @param {Metadata} meta |
@@ -6,2 +6,3 @@ 'use strict'; | ||
const Metadata = require('internal/meta'); | ||
const BinaryStream = require('lib/binary-stream'); | ||
@@ -14,13 +15,27 @@ module.exports = { | ||
/** | ||
* @param {any} object | ||
* @param {EncodeStream} wstream | ||
* @param {any} typeOrSchema | ||
* @param {any} obj | ||
* @param {any} type | ||
* @param {BinaryStream} [target] | ||
* @returns {BinaryStream} | ||
*/ | ||
function encode(object, wstream, typeOrSchema) { | ||
function encode(obj, type, target) { | ||
const meta = new Metadata(); | ||
encodeCommon(object, wstream, typeOrSchema, meta); | ||
// Check for legacy interface. | ||
if (type instanceof BinaryStream) { | ||
const tmp = target; | ||
target = type; // eslint-disable-line no-param-reassign | ||
type = tmp; // eslint-disable-line no-param-reassign | ||
} | ||
if (!(target instanceof BinaryStream)) { | ||
target = new BinaryStream(); // eslint-disable-line no-param-reassign | ||
} | ||
encodeCommon(obj, target, type, meta); | ||
encode.bytes = meta.bytes; | ||
Metadata.clean(meta); | ||
return target; | ||
} | ||
@@ -27,0 +42,0 @@ |
@@ -82,2 +82,11 @@ 'use strict'; | ||
} | ||
/** | ||
* @param {number} byte | ||
* @param {number} [offset] | ||
* @returns {number} | ||
*/ | ||
indexOf(byte, offset = 0) { | ||
return this.stream.indexOf(byte, this.index + offset) - this.index; | ||
} | ||
} | ||
@@ -84,0 +93,0 @@ |
'use strict'; | ||
const { isType, isFunction } = require('lib/util'); | ||
const NotEnoughDataError = require('lib/not-enough-data-error'); | ||
const BinaryStream = require('lib/binary-stream'); | ||
@@ -16,4 +18,5 @@ module.exports = buffer; | ||
const isfunc = isFunction(length); | ||
const isNull = length === null; | ||
if (!isnum && !istype && !isfunc) { | ||
if (!isnum && !istype && !isfunc && !isNull) { | ||
throw new TypeError('Unknown type of argument #1.'); | ||
@@ -56,4 +59,9 @@ } | ||
wstream.writeBuffer(buf); | ||
wstream.writeBuffer(Buffer.isBuffer(buf) ? buf : buf.buffer); | ||
encode.bytes += buf.length; | ||
if (isNull) { | ||
wstream.writeUInt8(0); | ||
encode.bytes += 1; | ||
} | ||
} | ||
@@ -83,2 +91,8 @@ | ||
checkLengthType(size); | ||
} else if (isNull) { | ||
size = rstream.indexOf(0); | ||
if (size === -1) { | ||
throw new NotEnoughDataError(rstream.length + 1, rstream.length); | ||
} | ||
} | ||
@@ -89,2 +103,7 @@ | ||
if (isNull) { | ||
decode.bytes += 1; | ||
rstream.consume(1); | ||
} | ||
return buf; | ||
@@ -106,4 +125,6 @@ } | ||
if (istype) { | ||
size += length.encodingLength(buf.length); | ||
if (isNull) { | ||
size = 1; | ||
} else if (istype) { | ||
size = length.encodingLength(buf.length); | ||
} | ||
@@ -121,4 +142,4 @@ | ||
function checkBuffer(buf) { | ||
if (!Buffer.isBuffer(buf)) { | ||
throw new TypeError('Argument 1 should be a Buffer.'); | ||
if (!Buffer.isBuffer(buf) && !(buf instanceof BinaryStream)) { | ||
throw new TypeError('Argument 1 should be a Buffer or a BinaryStream.'); | ||
} | ||
@@ -125,0 +146,0 @@ } |
@@ -93,10 +93,6 @@ 'use strict'; | ||
return function decode(rstream) { | ||
let bytes = 0; | ||
const bytes = rstream.indexOf(0); | ||
while (rstream.get(bytes) !== 0) { | ||
bytes += 1; | ||
if (bytes >= rstream.length) { | ||
throw new NotEnoughDataError(bytes, rstream.length); | ||
} | ||
if (bytes === -1) { | ||
throw new NotEnoughDataError(rstream.length + 1, rstream.length); | ||
} | ||
@@ -103,0 +99,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
AI-detected possible typosquat
Supply chain riskAI has identified this package as a potential typosquat of a more popular package. This suggests that the package may be intentionally mimicking another package's name, description, or other metadata.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
1246693
19.77%110
4.76%2377
8.14%296
8.42%6
50%