From e124291abe1438212831fe1bd402ec293a109cf9 Mon Sep 17 00:00:00 2001 From: Matt Ickstadt Date: Wed, 1 Jul 2026 14:06:40 -0500 Subject: [PATCH] Honor byteOffset when constructing the DataView PbfReader and PbfWriter build their DataView over `this.buf.buffer` but read/write fixed-width fields (double/float/fixed32/fixed64) at offsets relative to `this.buf`. When the backing view has a non-zero byteOffset (which Node Buffers from the shared pool routinely do) every fixed-width access lands at the wrong absolute offset and returns garbage. Varint/string reads were unaffected because they index `this.buf` directly. Construct the DataView with the view's byteOffset and byteLength so `this.pos` (a buf-relative offset) resolves correctly. Adds regression tests covering fixed-width read and write through a view with a non-zero byteOffset. --- index.js | 4 ++-- test/pbf.test.js | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index a6b1a55..e44c5d2 100644 --- a/index.js +++ b/index.js @@ -18,7 +18,7 @@ export class PbfReader { */ constructor(buf) { this.buf = ArrayBuffer.isView(buf) ? buf : new Uint8Array(buf); - this.dataView = new DataView(this.buf.buffer); + this.dataView = new DataView(this.buf.buffer, this.buf.byteOffset, this.buf.byteLength); this.pos = 0; this.type = 0; this._valueStart = -1; @@ -225,7 +225,7 @@ export class PbfWriter { */ constructor(buf = new Uint8Array(16)) { this.buf = ArrayBuffer.isView(buf) ? buf : new Uint8Array(buf); - this.dataView = new DataView(this.buf.buffer); + this.dataView = new DataView(this.buf.buffer, this.buf.byteOffset, this.buf.byteLength); this.pos = 0; this.length = this.buf.length; } diff --git a/test/pbf.test.js b/test/pbf.test.js index 0ff128c..414ebf5 100644 --- a/test/pbf.test.js +++ b/test/pbf.test.js @@ -162,6 +162,33 @@ test('readDouble', () => { assert.equal(Math.round(buf.readDouble() * 1e10) / 1e10, 12345.6789012345); }); +test('reads fixed-width fields from a view with a non-zero byteOffset', () => { + const backing = new ArrayBuffer(64); + const offset = 16; + const view = new Uint8Array(backing, offset, 48); + const dv = new DataView(backing, offset); + dv.setFloat64(0, 12345.6789012345, true); + dv.setFloat32(8, 42.5, true); + dv.setUint32(12, 0xdeadbeef, true); + + const reader = new PbfReader(view); + assert.equal(Math.round(reader.readDouble() * 1e10) / 1e10, 12345.6789012345); + assert.equal(reader.readFloat(), 42.5); + assert.equal(reader.readFixed32(), 0xdeadbeef); +}); + +test('writes fixed-width fields to a view with a non-zero byteOffset', () => { + const backing = new ArrayBuffer(64); + const writer = new PbfWriter(new Uint8Array(backing, 16, 48)); + writer.writeDoubleField(1, 12345.6789012345); + + const reader = new PbfReader(new Uint8Array(writer.finish())); + reader.readFields((tag) => { + assert.equal(tag, 1); + assert.equal(Math.round(reader.readDouble() * 1e10) / 1e10, 12345.6789012345); + }); +}); + test('readPacked and writePacked', () => { const testNumbers2 = testNumbers.slice(0, 10);