From 4b9eda19570bdbcef290cb72bf8fcbae9128392d Mon Sep 17 00:00:00 2001 From: bbeck Date: Sat, 23 Sep 2023 01:09:36 -0700 Subject: [PATCH] feat: add abilitiy for multiple delimitiers and multibyte delimiters to packet length parser --- .../parser-packet-length/lib/index.test.ts | 41 +++++++++++++++++++ packages/parser-packet-length/lib/index.ts | 29 ++++++++----- packages/parser-start-end/lib/index.test.ts | 18 ++++---- packages/parser-start-end/lib/index.ts | 2 +- 4 files changed, 70 insertions(+), 20 deletions(-) diff --git a/packages/parser-packet-length/lib/index.test.ts b/packages/parser-packet-length/lib/index.test.ts index 8e3b17c9c..0846cb723 100644 --- a/packages/parser-packet-length/lib/index.test.ts +++ b/packages/parser-packet-length/lib/index.test.ts @@ -90,4 +90,45 @@ describe('DelimiterParser', () => { assert.deepEqual(spy.getCall(1).args[0], Buffer.concat([Buffer.from([0xaa, 0x02, 0x13, 0x00]), Buffer.from('Each and Every One\n')])) assert(spy.calledTwice) }) + + it('works with multiple delimiters', () => { + const spy = sinon.spy() + const parser = new PacketLengthParser({ delimiter: [0xaa, 0xbb], lengthOffset: 2, packetOverhead: 4, lengthBytes: 2 }) + parser.on('data', spy) + parser.write(Buffer.from('\xbb\x01\x0d')) + parser.write(Buffer.from('\x00I love hobits\xaa\x02\x13\x00Each ')) + parser.write(Buffer.from('and Every One\n')) + + assert.deepEqual(spy.getCall(0).args[0], Buffer.concat([Buffer.from([0xbb, 0x01, 0x0d, 0x00]), Buffer.from('I love hobits')])) + assert.deepEqual(spy.getCall(1).args[0], Buffer.concat([Buffer.from([0xaa, 0x02, 0x13, 0x00]), Buffer.from('Each and Every One\n')])) + assert(spy.calledTwice) + }) + + it('works with multibyte delimiters', () => { + const spy = sinon.spy() + const parser = new PacketLengthParser({ delimiter: [0xababba], delimiterBytes: 3, lengthOffset: 3, packetOverhead: 4, lengthBytes: 1 }) + parser.on('data', spy) + parser.write(Buffer.from([0xab, 0xab, 0xba, 0x0d])) + parser.write(Buffer.from('I love hobits')) + parser.write(Buffer.from([0xab, 0xab, 0xba, 0x13])) + parser.write(Buffer.from('Each and Every One\n')) + + assert.deepEqual(spy.getCall(0).args[0], Buffer.concat([Buffer.from([0xab, 0xab, 0xba, 0x0d]), Buffer.from('I love hobits')])) + assert.deepEqual(spy.getCall(1).args[0], Buffer.concat([Buffer.from([0xab, 0xab, 0xba, 0x13]), Buffer.from('Each and Every One\n')])) + assert(spy.calledTwice) + }) + + it('works with multiple multibyte delimiters', () => { + const spy = sinon.spy() + const parser = new PacketLengthParser({ delimiter: [0xababba, 0xbaddad], delimiterBytes: 3, lengthOffset: 3, packetOverhead: 4, lengthBytes: 1 }) + parser.on('data', spy) + parser.write(Buffer.from([0xab, 0xab, 0xba, 0x0d])) + parser.write(Buffer.from('I love hobits')) + parser.write(Buffer.from([0xba, 0xdd, 0xad, 0x13])) + parser.write(Buffer.from('Each and Every One\n')) + + assert.deepEqual(spy.getCall(0).args[0], Buffer.concat([Buffer.from([0xab, 0xab, 0xba, 0x0d]), Buffer.from('I love hobits')])) + assert.deepEqual(spy.getCall(1).args[0], Buffer.concat([Buffer.from([0xba, 0xdd, 0xad, 0x13]), Buffer.from('Each and Every One\n')])) + assert(spy.calledTwice) + }) }) diff --git a/packages/parser-packet-length/lib/index.ts b/packages/parser-packet-length/lib/index.ts index 7d0874d56..d8442125f 100644 --- a/packages/parser-packet-length/lib/index.ts +++ b/packages/parser-packet-length/lib/index.ts @@ -1,8 +1,10 @@ import { Transform, TransformCallback, TransformOptions } from 'stream' export interface PacketLengthOptions extends TransformOptions { - /** delimiter to use defaults to 0xaa */ - delimiter?: number + /** delimiter(s) to use defaults to 0xaa */ + delimiter?: number | number[] + /** delimiter length in bytes defaults to 1 */ + delimiterBytes?: number /** overhead of packet (including length, delimiter and any checksum / packet footer) defaults to 2 */ packetOverhead?: number /** number of bytes containing length defaults to 1 */ @@ -29,14 +31,15 @@ export interface PacketLengthOptions extends TransformOptions { export class PacketLengthParser extends Transform { buffer: Buffer start: boolean - opts: { delimiter: number, packetOverhead: number, lengthBytes: number, lengthOffset: number, maxLen: number } + opts: { delimiter: number[], delimiterBytes: number, packetOverhead: number, lengthBytes: number, lengthOffset: number, maxLen: number } constructor(options: PacketLengthOptions = {}) { super(options) - const { delimiter = 0xaa, packetOverhead = 2, lengthBytes = 1, lengthOffset = 1, maxLen = 0xff } = options + const { delimiter = [0xaa], delimiterBytes = 1, packetOverhead = 2, lengthBytes = 1, lengthOffset = 1, maxLen = 0xff } = options this.opts = { - delimiter, + delimiter: ([] as number[]).concat(delimiter), + delimiterBytes, packetOverhead, lengthBytes, lengthOffset, @@ -51,13 +54,8 @@ export class PacketLengthParser extends Transform { for (let ndx = 0; ndx < chunk.length; ndx++) { const byte = chunk[ndx] - if (byte === this.opts.delimiter) { - this.start = true - } - if (true === this.start) { this.buffer = Buffer.concat([this.buffer, Buffer.from([byte])]) - if (this.buffer.length >= this.opts.lengthOffset + this.opts.lengthBytes) { const len = this.buffer.readUIntLE(this.opts.lengthOffset, this.opts.lengthBytes) @@ -67,6 +65,17 @@ export class PacketLengthParser extends Transform { this.start = false } } + } else { + this.buffer = Buffer.concat([Buffer.from([byte]), this.buffer]) + if (this.buffer.length === this.opts.delimiterBytes) { + const delimiter = this.buffer.readUIntLE(0, this.opts.delimiterBytes) + if (this.opts.delimiter.includes(delimiter)) { + this.start = true + this.buffer = Buffer.from([...this.buffer].reverse()) + } else { + this.buffer = Buffer.from(this.buffer.subarray(1, this.buffer.length)) + } + } } } diff --git a/packages/parser-start-end/lib/index.test.ts b/packages/parser-start-end/lib/index.test.ts index 4fb4d2363..6618b20bd 100644 --- a/packages/parser-start-end/lib/index.test.ts +++ b/packages/parser-start-end/lib/index.test.ts @@ -17,7 +17,7 @@ describe('StartEndParser', () => { parser.write(Buffer.from(`${STX}I love robots${ETX}${STX}Each `)) parser.write(Buffer.from(`and Every One${ETX}`)) parser.write(Buffer.from(STX)) - parser.write(Buffer.from(`even you!`)) + parser.write(Buffer.from('even you!')) assert.deepEqual(spy.getCall(0).args[0], Buffer.from('I love robots')) assert.deepEqual(spy.getCall(1).args[0], Buffer.from('Each and Every One')) @@ -35,7 +35,7 @@ describe('StartEndParser', () => { parser.write(Buffer.from(`${STX}I love robots${ETX}${STX}Each `)) parser.write(Buffer.from(`and Every One${ETX}`)) parser.write(Buffer.from(STX)) - parser.write(Buffer.from(`even you!`)) + parser.write(Buffer.from('even you!')) assert.deepEqual(spy.getCall(0).args[0], Buffer.from(`${STX}I love robots`)) assert.deepEqual(spy.getCall(1).args[0], Buffer.from(`${STX}Each and Every One`)) @@ -53,7 +53,7 @@ describe('StartEndParser', () => { parser.write(Buffer.from(`${STX}I love robots${ETX}${STX}Each `)) parser.write(Buffer.from(`and Every One${ETX}`)) parser.write(Buffer.from(STX)) - parser.write(Buffer.from(`even you!`)) + parser.write(Buffer.from('even you!')) assert.deepEqual(spy.getCall(0).args[0], Buffer.from(`I love robots${ETX}`)) assert.deepEqual(spy.getCall(1).args[0], Buffer.from(`Each and Every One${ETX}`)) @@ -72,7 +72,7 @@ describe('StartEndParser', () => { parser.write(Buffer.from(`${STX}I love robots${ETX}${STX}Each `)) parser.write(Buffer.from(`and Every One${ETX}`)) parser.write(Buffer.from(STX)) - parser.write(Buffer.from(`even you!`)) + parser.write(Buffer.from('even you!')) assert.deepEqual(spy.getCall(0).args[0], Buffer.from(`${STX}I love robots${ETX}`)) assert.deepEqual(spy.getCall(1).args[0], Buffer.from(`${STX}Each and Every One${ETX}`)) @@ -108,7 +108,7 @@ describe('StartEndParser', () => { }) }) - it(`throws when called with a 0 length startDelimiter`, () => { + it('throws when called with a 0 length startDelimiter', () => { assert.throws(() => { new StartEndParser({ startDelimiter: Buffer.alloc(0), @@ -128,7 +128,7 @@ describe('StartEndParser', () => { }) }) - it(`throws when called with a 0 length endDelimiter`, () => { + it('throws when called with a 0 length endDelimiter', () => { assert.throws(() => { new StartEndParser({ endDelimiter: Buffer.alloc(0), @@ -148,15 +148,15 @@ describe('StartEndParser', () => { }) }) - it(`allows setting of the startDelimiter and endDelimiter with strings`, () => { + it('allows setting of the startDelimiter and endDelimiter with strings', () => { new StartEndParser({ startDelimiter: 'string', endDelimiter: 'string' }) }) - it(`allows setting of the startDelimiter and endDelimiter with buffers`, () => { + it('allows setting of the startDelimiter and endDelimiter with buffers', () => { new StartEndParser({ startDelimiter: Buffer.from([1]), endDelimiter: Buffer.from([1]) }) }) - it(`allows setting of the startDelimiter and endDelimiter with arrays of bytes`, () => { + it('allows setting of the startDelimiter and endDelimiter with arrays of bytes', () => { new StartEndParser({ startDelimiter: [1], endDelimiter: [1] }) }) diff --git a/packages/parser-start-end/lib/index.ts b/packages/parser-start-end/lib/index.ts index 568c91de3..1c449a3bc 100644 --- a/packages/parser-start-end/lib/index.ts +++ b/packages/parser-start-end/lib/index.ts @@ -62,7 +62,7 @@ export class StartEndParser extends Transform { if (startIndex >= 0 && endIndex >= 0) { const block = data.slice( startIndex + (this.includeStartDelimiter ? 0 : this.startDelimiter.length), - endIndex + (this.includeEndDelimiter ? this.endDelimiter.length : 0) + endIndex + (this.includeEndDelimiter ? this.endDelimiter.length : 0), ) this.push(block)