diff --git a/src/Decoder.ts b/src/Decoder.ts index 5b3c884..79ef188 100644 --- a/src/Decoder.ts +++ b/src/Decoder.ts @@ -430,13 +430,13 @@ export class Decoder { this.appendBuffer(buffer); - if (isArrayHeaderRequired) { - arrayItemsLeft = this.readArraySize(); - isArrayHeaderRequired = false; - this.complete(); - } - try { + if (isArrayHeaderRequired) { + arrayItemsLeft = this.readArraySize(); + isArrayHeaderRequired = false; + this.complete(); + } + while (true) { yield this.doDecodeSync(); if (--arrayItemsLeft === 0) { @@ -451,6 +451,10 @@ export class Decoder { } this.totalPos += this.pos; } + + if (isArrayHeaderRequired) { + throw new RangeError("Insufficient data in parsing array header"); + } } finally { this.entered = false; } diff --git a/test/decodeArrayStream.test.ts b/test/decodeArrayStream.test.ts index a8f7ed5..25eebb9 100644 --- a/test/decodeArrayStream.test.ts +++ b/test/decodeArrayStream.test.ts @@ -61,6 +61,54 @@ describe("decodeArrayStream", () => { assert.deepStrictEqual(result, [1, 2, 3]); }); + for (const [name, header] of [ + ["array16", [0xdc, 0, 1]], + ["array32", [0xdd, 0, 0, 0, 1]], + ] as const) { + it(`decodes ${name} split at every header boundary`, async () => { + const object = { nested: [1, "value", null] }; + const encoded = Uint8Array.from([...header, ...encode(object)]); + + for (let split = 1; split < header.length; split++) { + const stream = async function* () { + yield encoded.subarray(0, split); + yield encoded.subarray(split); + }; + const result: Array = []; + for await (const item of decodeArrayStream(stream())) { + result.push(item); + } + assert.deepStrictEqual(result, [object], `split at ${split}`); + } + }); + + it(`rejects ${name} truncated in the header`, async () => { + for (let length = 1; length < header.length; length++) { + const stream = async function* () { + yield header.slice(0, length); + yield new Uint8Array(0); + }; + await assert.rejects(decodeArrayStream(stream()).next(), RangeError); + } + }); + + it(`decodes ${name} byte-by-byte with empty chunks`, async () => { + const object = { nested: [1, "value", null] }; + const encoded = Uint8Array.from([...header, ...encode(object)]); + const stream = async function* () { + for (const byte of encoded) { + yield new Uint8Array(0); + yield Uint8Array.from([0xff, byte, 0xff]).subarray(1, 2); + } + }; + const result: Array = []; + for await (const item of decodeArrayStream(stream())) { + result.push(item); + } + assert.deepStrictEqual(result, [object]); + }); + } + it("decodes objects array", async () => { const objectsArrays: Array = [];