From 0087395b45bec014d0f85e695905891809f5604b Mon Sep 17 00:00:00 2001 From: u9g Date: Tue, 8 Sep 2026 09:47:12 -0400 Subject: [PATCH] FullPacketParser: emit partialReadError with the chunk it could not read A chunk the definitions cannot read is dropped, and the only trace was console.log(e.stack), which noErrorLogging removes. The bytes themselves were never available to the consumer. Attach the chunk as error.buffer and emit it as 'partialReadError' before the log. It is not emitted as 'error', which would end the stream; the drop-and-continue behaviour is unchanged, and Parser is untouched since a partial read there means the packet is not complete yet. --- doc/api.md | 14 ++++++++++++++ index.d.ts | 4 ++++ src/serializer.js | 2 ++ test/misc.js | 28 +++++++++++++++++++++++++++- 4 files changed, 47 insertions(+), 1 deletion(-) diff --git a/doc/api.md b/doc/api.md index 465ed66..ddee454 100644 --- a/doc/api.md +++ b/doc/api.md @@ -66,6 +66,20 @@ Create a parser of `mainType` defined in `proto`. This is a Transform stream. Returns a parsed packet of `buffer`. +## FullPacketParser(proto,mainType,noErrorLogging) + +Create a parser of `mainType` defined in `proto` that reads one whole packet per chunk. This is a Transform stream. + +A chunk the definitions cannot read is dropped and the stream continues. Its stack is logged unless `noErrorLogging` is set. + +### FullPacketParser.parsePacketBuffer(buffer) + +Returns a parsed packet of `buffer`. + +### Event: 'partialReadError' (error) + +Emitted for each dropped chunk, before it is logged. `error.buffer` is the chunk. + ## types An object mapping the default type names to the corresponding `[read,write,sizeOf]` functions. diff --git a/index.d.ts b/index.d.ts index 70180af..19019e0 100644 --- a/index.d.ts +++ b/index.d.ts @@ -109,6 +109,8 @@ declare class CompiledProtoDef extends AbstractProtoDefInterface { declare class ProtodefPartialError extends Error { partialReadError: true + // The chunk a FullPacketParser could not read; set when it emits 'partialReadError'. + buffer?: Buffer constructor(message?: string) } @@ -134,6 +136,8 @@ declare module 'protodef' { noErrorLogging: boolean constructor(proto: ProtoDef, mainType: string, noErrorLogging = false) parsePacketBuffer(packet: any): Buffer + on(event: 'partialReadError', listener: (error: ProtodefPartialError) => void): this + on(event: string | symbol, listener: (...args: any[]) => void): this } export const Compiler: { ReadCompiler: typeof ProtodefReadCompiler diff --git a/src/serializer.js b/src/serializer.js index 8b52e95..d744141 100644 --- a/src/serializer.js +++ b/src/serializer.js @@ -78,6 +78,8 @@ class FullPacketParser extends Transform { } } catch (e) { if (e.partialReadError) { + e.buffer = chunk + this.emit('partialReadError', e) if (!this.noErrorLogging) { console.log(e.stack) } diff --git a/test/misc.js b/test/misc.js index dab6359..fc726e9 100644 --- a/test/misc.js +++ b/test/misc.js @@ -1,7 +1,7 @@ /* eslint-env mocha */ const assert = require('assert') -const { ProtoDef } = require('../') +const { ProtoDef, FullPacketParser } = require('../') const { ProtoDefCompiler } = require('../').Compiler it('example works', () => { @@ -25,3 +25,29 @@ describe('mapper', () => { }) } }) + +describe('FullPacketParser', () => { + const packet = ['container', [{ name: 'a', type: 'i32' }]] + const proto = new ProtoDef() + proto.addType('packet', packet) + const compiler = new ProtoDefCompiler() + compiler.addTypesToCompile({ packet }) + const compiled = compiler.compileProtoDefSync() + + for (const [label, p] of [['interpreted', proto], ['compiled', compiled]]) { + it(`emits partialReadError with the chunk it could not read, and keeps parsing (${label})`, async () => { + const parser = new FullPacketParser(p, 'packet', true) + const errors = [] + const packets = [] + parser.on('partialReadError', e => errors.push(e)) + parser.on('data', d => packets.push(d.data)) + parser.write(Buffer.from([0, 0])) + parser.write(Buffer.from([0, 0, 0, 7])) + await new Promise(resolve => parser.end(resolve)) + assert.strictEqual(errors.length, 1) + assert.strictEqual(errors[0].partialReadError, true) + assert.deepStrictEqual(errors[0].buffer, Buffer.from([0, 0])) + assert.deepStrictEqual(packets, [{ a: 7 }]) + }) + } +})