diff --git a/packages/pg-protocol/src/buffer-reader.ts b/packages/pg-protocol/src/buffer-reader.ts index 42a4a23fa..9bfbadb11 100644 --- a/packages/pg-protocol/src/buffer-reader.ts +++ b/packages/pg-protocol/src/buffer-reader.ts @@ -51,7 +51,9 @@ export class BufferReader { } public bytes(length: number): Buffer { - const result = this.buffer.slice(this.offset, this.offset + length) + // a copy, not a view: the parser reuses its buffer for the next chunk, and a view would + // change under a message that was already delivered + const result = Buffer.from(this.buffer.subarray(this.offset, this.offset + length)) this.offset += length return result } diff --git a/packages/pg-protocol/src/inbound-parser.test.ts b/packages/pg-protocol/src/inbound-parser.test.ts index 8687194c3..68430e4cf 100644 --- a/packages/pg-protocol/src/inbound-parser.test.ts +++ b/packages/pg-protocol/src/inbound-parser.test.ts @@ -577,6 +577,17 @@ describe('PgPacketStream', function () { }) }) + // the parser moves what is left of a chunk to the front of its buffer before reading the next + // one, so a message that kept a view into that buffer would change after being delivered + it('keeps a copyData chunk intact after the parser reuses its buffer', async function () { + const copyData = buffers.copyData(Buffer.alloc(64, 0xaa)) + const commandComplete = buffers.commandComplete('COPY 1') + const fullBuffer = Buffer.concat([copyData, commandComplete]) + const messages = await parseBuffers([fullBuffer.subarray(0, fullBuffer.length - 1), fullBuffer.subarray(-1)]) + assert.strictEqual(messages.length, 2) + assert.deepEqual((messages[0] as any).chunk, Buffer.alloc(64, 0xaa)) + }) + it('cleans up the reader after handling a packet', function () { const parser = new Parser() parser.parse(oneFieldBuf, () => {})