From 265ff6329cb634a81242bf80e5bc297d4020270e Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Tue, 26 May 2026 20:32:10 +0300 Subject: [PATCH 1/5] add nextField and skipField helpers --- README.md | 76 +++++++++++++++++------------- compile.js | 7 ++- index.js | 30 ++++++++---- test/fixtures/defaults.js | 6 +-- test/fixtures/defaults_implicit.js | 12 ++--- test/fixtures/defaults_proto3.js | 6 +-- test/fixtures/embedded_type.js | 18 +++---- test/fixtures/map.js | 18 +++---- test/fixtures/map_collision.js | 24 +++++----- test/fixtures/oneof.js | 6 +-- test/fixtures/packed.js | 18 +++---- test/fixtures/packed_proto3.js | 24 +++++----- test/fixtures/type_string.js | 12 ++--- test/fixtures/varint.js | 6 +-- test/fixtures/vector_tile.js | 24 +++++----- test/pbf.test.js | 38 +++++++++++++++ 16 files changed, 193 insertions(+), 132 deletions(-) diff --git a/README.md b/README.md index a3e3200..c238be0 100644 --- a/README.md +++ b/README.md @@ -61,16 +61,29 @@ const {readExample, writeExample} = compile(proto); #### Custom Reading ```js -const data = new PbfReader(buffer).readFields(readData, {}); - -function readData(tag, data, pbf) { - if (tag === 1) data.name = pbf.readString(); - else if (tag === 2) data.version = pbf.readVarint(); - else if (tag === 3) data.layer = pbf.readMessage(readLayer, {}); +const pbf = new PbfReader(buffer); +const data = readData(pbf); + +function readData(pbf, end = pbf.length) { + const data = {}; + let field; + while ((field = pbf.nextField(end))) { + if (field === 1) data.name = pbf.readString(); + else if (field === 2) data.version = pbf.readVarint(); + else if (field === 3) data.layer = readLayer(pbf, pbf.readVarint() + pbf.pos); + else pbf.skipField(); + } + return data; } -function readLayer(tag, layer, pbf) { - if (tag === 1) layer.name = pbf.readString(); - else if (tag === 3) layer.size = pbf.readVarint(); +function readLayer(pbf, end) { + const layer = {}; + let field; + while ((field = pbf.nextField(end))) { + if (field === 1) layer.name = pbf.readString(); + else if (field === 3) layer.size = pbf.readVarint(); + else pbf.skipField(); + } + return layer; } ``` @@ -137,29 +150,23 @@ pbf.pos; // current offset for reading or writing #### Reading -Read a sequence of fields: +Loop over a message's fields with `nextField`, dispatch on the field number, and call `skipField()` for anything you don't recognize: ```js -pbf.readFields((tag) => { - if (tag === 1) pbf.readVarint(); - else if (tag === 2) pbf.readString(); - else ... -}); +let field; +while ((field = pbf.nextField(end))) { + if (field === 1) obj.id = pbf.readVarint(); + else if (field === 2) obj.name = pbf.readString(); + else pbf.skipField(); +} ``` -It optionally accepts an object that will be passed to the reading function for easier construction of decoded data, -and also passes the `PbfReader` object as a third argument: +To read an embedded message, pass `pbf.readVarint() + pbf.pos` as `end` to a nested reader: ```js -const result = pbf.readFields(readField, {}) - -function readField(tag, result, pbf) { - if (tag === 1) result.id = pbf.readVarint(); -} +const msg = readSubMessage(pbf, pbf.readVarint() + pbf.pos); ``` -To read an embedded message, use `pbf.readMessage(fn[, obj])` (in the same way as `read`). - Read values: ```js @@ -168,19 +175,22 @@ const str = pbf.readString(); const numbers = pbf.readPackedVarint(); ``` -For lazy or partial decoding, simply save the position instead of reading a value, -then later set it back to the saved value and read: +For lazy or partial decoding, save the position and come back to it later: ```js -const fooPos = -1; -pbf.readFields((tag) => { - if (tag === 1) fooPos = pbf.pos; -}); +let fooPos = -1; +let field; +while ((field = pbf.nextField())) { + if (field === 1) fooPos = pbf.pos; + pbf.skipField(); +} ... pbf.pos = fooPos; -pbf.readMessage(readFoo); +const foo = readFoo(pbf, pbf.readVarint() + pbf.pos); ``` +A callback-based `readFields(fn, obj, end)` is also available for backward compatibility, but new code should prefer the `nextField` loop — it's significantly faster. + Scalar reading methods: * `readVarint(isSigned)` (pass `true` if you expect negative varints) @@ -194,7 +204,9 @@ Scalar reading methods: * `readDouble()` * `readString()` * `readBytes()` -* `skip(value)` +* `nextField(end)` — returns the next field number, or `0` at end-of-message +* `skipField()` — skips the current field +* `skip(value)` — skips a field given its raw tag varint Packed reading methods: diff --git a/compile.js b/compile.js index c0559d0..e238c51 100644 --- a/compile.js +++ b/compile.js @@ -29,17 +29,16 @@ function writeMessage(ctx, options) { if (!options.noRead) { const readName = `read${ctx._name}`; - const hasPackable = fields.some(f => f.repeated && willSupportPacked(ctx, f)); code += `${writeFunctionExport(options, readName)}function ${readName}(pbf, end = pbf.length) {\n`; code += ` const obj = ${compileDest(ctx)};\n`; - code += ' while (pbf.pos < end) {\n'; - code += ` const tag = pbf.readVarint(), field = tag >>> 3${hasPackable ? '; pbf.type = tag & 7' : ''};\n`; + code += ' let field;\n'; + code += ' while ((field = pbf.nextField(end))) {\n'; for (let i = 0; i < fields.length; i++) { code += ` ${i ? 'else ' : ''}if (field === ${fields[i].tag}) ${compileFieldRead(ctx, fields[i])}\n`; } - code += ` ${fields.length ? 'else ' : ''}pbf.skip(tag);\n`; + code += ` ${fields.length ? 'else ' : ''}pbf.skipField();\n`; code += ' }\n return obj;\n}\n'; } diff --git a/index.js b/index.js index 2a623f7..723f73d 100644 --- a/index.js +++ b/index.js @@ -31,15 +31,11 @@ export class PbfReader { * @param {number} [end] */ readFields(readField, result, end = this.length) { - while (this.pos < end) { - const val = this.readVarint(), - tag = val >> 3, - startPos = this.pos; - - this.type = val & 0x7; - readField(tag, result, this); - - if (this.pos === startPos) this.skip(val); + let field; + while ((field = this.nextField(end))) { + const startPos = this.pos; + readField(field, result, this); + if (this.pos === startPos) this.skipField(); } return result; } @@ -200,6 +196,22 @@ export class PbfReader { return this.type === PBF_BYTES ? this.readVarint() + this.pos : this.pos + 1; } + /** + * Advance to the next field. Returns the field number, or 0 at end-of-message. + * @param {number} [end] + */ + nextField(end = this.length) { + if (this.pos >= end) return 0; + const tag = this.readVarint(); + this.type = tag & 0x7; + return tag >>> 3; + } + + /** Skip the field most recently returned by `nextField`. */ + skipField() { + this.skip(this.type); + } + /** @param {number} val */ skip(val) { const type = val & 0x7; diff --git a/test/fixtures/defaults.js b/test/fixtures/defaults.js index 67946d6..88ff5a6 100644 --- a/test/fixtures/defaults.js +++ b/test/fixtures/defaults.js @@ -6,14 +6,14 @@ export const MessageType = { export function readEnvelope(pbf, end = pbf.length) { const obj = {type: 1, name: "test", flag: true, weight: 1.5, id: 1}; - while (pbf.pos < end) { - const tag = pbf.readVarint(), field = tag >>> 3; + let field; + while ((field = pbf.nextField(end))) { if (field === 1) obj.type = pbf.readVarint(); else if (field === 2) obj.name = pbf.readString(); else if (field === 3) obj.flag = pbf.readBoolean(); else if (field === 4) obj.weight = pbf.readFloat(); else if (field === 5) obj.id = pbf.readVarint(true); - else pbf.skip(tag); + else pbf.skipField(); } return obj; } diff --git a/test/fixtures/defaults_implicit.js b/test/fixtures/defaults_implicit.js index ba6ec02..ba2992d 100644 --- a/test/fixtures/defaults_implicit.js +++ b/test/fixtures/defaults_implicit.js @@ -6,9 +6,9 @@ export const MessageType = { export function readCustomType(pbf, end = pbf.length) { const obj = {}; - while (pbf.pos < end) { - const tag = pbf.readVarint(), field = tag >>> 3; - pbf.skip(tag); + let field; + while ((field = pbf.nextField(end))) { + pbf.skipField(); } return obj; } @@ -17,8 +17,8 @@ export function writeCustomType(obj, pbf) { export function readEnvelope(pbf, end = pbf.length) { const obj = {type: 0, name: "", flag: false, weight: 0, id: 0, tags: [], numbers: [], bytes: undefined, custom: undefined, types: []}; - while (pbf.pos < end) { - const tag = pbf.readVarint(), field = tag >>> 3; pbf.type = tag & 7; + let field; + while ((field = pbf.nextField(end))) { if (field === 1) obj.type = pbf.readVarint(); else if (field === 2) obj.name = pbf.readString(); else if (field === 3) obj.flag = pbf.readBoolean(); @@ -29,7 +29,7 @@ export function readEnvelope(pbf, end = pbf.length) { else if (field === 8) obj.bytes = pbf.readBytes(); else if (field === 9) obj.custom = readCustomType(pbf, pbf.readVarint() + pbf.pos); else if (field === 10) pbf.readPackedVarint(obj.types); - else pbf.skip(tag); + else pbf.skipField(); } return obj; } diff --git a/test/fixtures/defaults_proto3.js b/test/fixtures/defaults_proto3.js index 0827af1..1f7faab 100644 --- a/test/fixtures/defaults_proto3.js +++ b/test/fixtures/defaults_proto3.js @@ -6,14 +6,14 @@ export const MessageType = { export function readEnvelope(pbf, end = pbf.length) { const obj = {type: 0, name: "", flag: false, weight: 0, id: 0}; - while (pbf.pos < end) { - const tag = pbf.readVarint(), field = tag >>> 3; + let field; + while ((field = pbf.nextField(end))) { if (field === 1) obj.type = pbf.readVarint(); else if (field === 2) obj.name = pbf.readString(); else if (field === 3) obj.flag = pbf.readBoolean(); else if (field === 4) obj.weight = pbf.readFloat(); else if (field === 5) obj.id = pbf.readVarint(true); - else pbf.skip(tag); + else pbf.skipField(); } return obj; } diff --git a/test/fixtures/embedded_type.js b/test/fixtures/embedded_type.js index 5ede605..f95c987 100644 --- a/test/fixtures/embedded_type.js +++ b/test/fixtures/embedded_type.js @@ -1,12 +1,12 @@ export function readEmbeddedType(pbf, end = pbf.length) { const obj = {value: "test", sub_field: undefined, sub_sub_field: undefined}; - while (pbf.pos < end) { - const tag = pbf.readVarint(), field = tag >>> 3; + let field; + while ((field = pbf.nextField(end))) { if (field === 1) obj.value = pbf.readString(); else if (field === 4) obj.sub_field = readEmbeddedTypeContainer(pbf, pbf.readVarint() + pbf.pos); else if (field === 5) obj.sub_sub_field = readEmbeddedTypeContainerInner(pbf, pbf.readVarint() + pbf.pos); - else pbf.skip(tag); + else pbf.skipField(); } return obj; } @@ -18,10 +18,10 @@ export function writeEmbeddedType(obj, pbf) { export function readEmbeddedTypeContainer(pbf, end = pbf.length) { const obj = {values: []}; - while (pbf.pos < end) { - const tag = pbf.readVarint(), field = tag >>> 3; + let field; + while ((field = pbf.nextField(end))) { if (field === 1) obj.values.push(readEmbeddedTypeContainerInner(pbf, pbf.readVarint() + pbf.pos)); - else pbf.skip(tag); + else pbf.skipField(); } return obj; } @@ -31,10 +31,10 @@ export function writeEmbeddedTypeContainer(obj, pbf) { export function readEmbeddedTypeContainerInner(pbf, end = pbf.length) { const obj = {value: ""}; - while (pbf.pos < end) { - const tag = pbf.readVarint(), field = tag >>> 3; + let field; + while ((field = pbf.nextField(end))) { if (field === 1) obj.value = pbf.readString(); - else pbf.skip(tag); + else pbf.skipField(); } return obj; } diff --git a/test/fixtures/map.js b/test/fixtures/map.js index 04b254b..e452ea2 100644 --- a/test/fixtures/map.js +++ b/test/fixtures/map.js @@ -1,11 +1,11 @@ export function readEnvelope(pbf, end = pbf.length) { const obj = {kv: {}, kn: {}}; - while (pbf.pos < end) { - const tag = pbf.readVarint(), field = tag >>> 3; + let field; + while ((field = pbf.nextField(end))) { if (field === 1) { const {key, value} = readEnvelopeKvEntry(pbf, pbf.readVarint() + pbf.pos); obj.kv[key] = value; } else if (field === 2) { const {key, value} = readEnvelopeKnEntry(pbf, pbf.readVarint() + pbf.pos); obj.kn[key] = value; } - else pbf.skip(tag); + else pbf.skipField(); } return obj; } @@ -16,11 +16,11 @@ export function writeEnvelope(obj, pbf) { export function readEnvelopeKvEntry(pbf, end = pbf.length) { const obj = {key: "", value: ""}; - while (pbf.pos < end) { - const tag = pbf.readVarint(), field = tag >>> 3; + let field; + while ((field = pbf.nextField(end))) { if (field === 1) obj.key = pbf.readString(); else if (field === 2) obj.value = pbf.readString(); - else pbf.skip(tag); + else pbf.skipField(); } return obj; } @@ -31,11 +31,11 @@ export function writeEnvelopeKvEntry(obj, pbf) { export function readEnvelopeKnEntry(pbf, end = pbf.length) { const obj = {key: "", value: 0}; - while (pbf.pos < end) { - const tag = pbf.readVarint(), field = tag >>> 3; + let field; + while ((field = pbf.nextField(end))) { if (field === 1) obj.key = pbf.readString(); else if (field === 2) obj.value = pbf.readVarint(true); - else pbf.skip(tag); + else pbf.skipField(); } return obj; } diff --git a/test/fixtures/map_collision.js b/test/fixtures/map_collision.js index 2347a83..d3b569f 100644 --- a/test/fixtures/map_collision.js +++ b/test/fixtures/map_collision.js @@ -1,13 +1,13 @@ export function readEnvelope(pbf, end = pbf.length) { const obj = {kv: {}, kn: {}, sibling: undefined, flag: 0}; - while (pbf.pos < end) { - const tag = pbf.readVarint(), field = tag >>> 3; + let field; + while ((field = pbf.nextField(end))) { if (field === 1) { const {key, value} = readEnvelopeKvEntry$(pbf, pbf.readVarint() + pbf.pos); obj.kv[key] = value; } else if (field === 2) { const {key, value} = readEnvelopeKnEntry$(pbf, pbf.readVarint() + pbf.pos); obj.kn[key] = value; } else if (field === 3) obj.sibling = readEnvelopeKvEntry(pbf, pbf.readVarint() + pbf.pos); else if (field === 4) obj.flag = pbf.readVarint(); - else pbf.skip(tag); + else pbf.skipField(); } return obj; } @@ -25,10 +25,10 @@ export const EnvelopeKnEntry = { export function readEnvelopeKvEntry(pbf, end = pbf.length) { const obj = {marker: 0}; - while (pbf.pos < end) { - const tag = pbf.readVarint(), field = tag >>> 3; + let field; + while ((field = pbf.nextField(end))) { if (field === 1) obj.marker = pbf.readVarint(true); - else pbf.skip(tag); + else pbf.skipField(); } return obj; } @@ -38,11 +38,11 @@ export function writeEnvelopeKvEntry(obj, pbf) { export function readEnvelopeKvEntry$(pbf, end = pbf.length) { const obj = {key: "", value: ""}; - while (pbf.pos < end) { - const tag = pbf.readVarint(), field = tag >>> 3; + let field; + while ((field = pbf.nextField(end))) { if (field === 1) obj.key = pbf.readString(); else if (field === 2) obj.value = pbf.readString(); - else pbf.skip(tag); + else pbf.skipField(); } return obj; } @@ -53,11 +53,11 @@ export function writeEnvelopeKvEntry$(obj, pbf) { export function readEnvelopeKnEntry$(pbf, end = pbf.length) { const obj = {key: "", value: 0}; - while (pbf.pos < end) { - const tag = pbf.readVarint(), field = tag >>> 3; + let field; + while ((field = pbf.nextField(end))) { if (field === 1) obj.key = pbf.readString(); else if (field === 2) obj.value = pbf.readVarint(true); - else pbf.skip(tag); + else pbf.skipField(); } return obj; } diff --git a/test/fixtures/oneof.js b/test/fixtures/oneof.js index ec8502b..9a6c290 100644 --- a/test/fixtures/oneof.js +++ b/test/fixtures/oneof.js @@ -1,13 +1,13 @@ export function readEnvelope(pbf, end = pbf.length) { const obj = {id: 0, int: 0, value: undefined, float: 0, string: ""}; - while (pbf.pos < end) { - const tag = pbf.readVarint(), field = tag >>> 3; + let field; + while ((field = pbf.nextField(end))) { if (field === 1) obj.id = pbf.readVarint(true); else if (field === 2) { obj.int = pbf.readVarint(true); obj.value = "int"; } else if (field === 3) { obj.float = pbf.readFloat(); obj.value = "float"; } else if (field === 4) { obj.string = pbf.readString(); obj.value = "string"; } - else pbf.skip(tag); + else pbf.skipField(); } return obj; } diff --git a/test/fixtures/packed.js b/test/fixtures/packed.js index d8ce77e..6c08a20 100644 --- a/test/fixtures/packed.js +++ b/test/fixtures/packed.js @@ -1,11 +1,11 @@ export function readNotPacked(pbf, end = pbf.length) { const obj = {value: [], types: []}; - while (pbf.pos < end) { - const tag = pbf.readVarint(), field = tag >>> 3; pbf.type = tag & 7; + let field; + while ((field = pbf.nextField(end))) { if (field === 1) pbf.readPackedVarint(obj.value, true); else if (field === 2) pbf.readPackedVarint(obj.types, true); - else pbf.skip(tag); + else pbf.skipField(); } return obj; } @@ -16,11 +16,11 @@ export function writeNotPacked(obj, pbf) { export function readFalsePacked(pbf, end = pbf.length) { const obj = {value: [], types: []}; - while (pbf.pos < end) { - const tag = pbf.readVarint(), field = tag >>> 3; pbf.type = tag & 7; + let field; + while ((field = pbf.nextField(end))) { if (field === 1) pbf.readPackedVarint(obj.value, true); else if (field === 2) pbf.readPackedVarint(obj.types, true); - else pbf.skip(tag); + else pbf.skipField(); } return obj; } @@ -31,11 +31,11 @@ export function writeFalsePacked(obj, pbf) { export function readPacked(pbf, end = pbf.length) { const obj = {value: [], types: []}; - while (pbf.pos < end) { - const tag = pbf.readVarint(), field = tag >>> 3; pbf.type = tag & 7; + let field; + while ((field = pbf.nextField(end))) { if (field === 1) pbf.readPackedVarint(obj.value, true); else if (field === 2) pbf.readPackedVarint(obj.types, true); - else pbf.skip(tag); + else pbf.skipField(); } return obj; } diff --git a/test/fixtures/packed_proto3.js b/test/fixtures/packed_proto3.js index e14a56e..120f12a 100644 --- a/test/fixtures/packed_proto3.js +++ b/test/fixtures/packed_proto3.js @@ -6,11 +6,11 @@ export const MessageType = { export function readNotPacked(pbf, end = pbf.length) { const obj = {value: [], types: []}; - while (pbf.pos < end) { - const tag = pbf.readVarint(), field = tag >>> 3; pbf.type = tag & 7; + let field; + while ((field = pbf.nextField(end))) { if (field === 1) pbf.readPackedVarint(obj.value, true); else if (field === 2) pbf.readPackedVarint(obj.types); - else pbf.skip(tag); + else pbf.skipField(); } return obj; } @@ -21,11 +21,11 @@ export function writeNotPacked(obj, pbf) { export function readFalsePacked(pbf, end = pbf.length) { const obj = {value: [], types: []}; - while (pbf.pos < end) { - const tag = pbf.readVarint(), field = tag >>> 3; pbf.type = tag & 7; + let field; + while ((field = pbf.nextField(end))) { if (field === 1) pbf.readPackedVarint(obj.value, true); else if (field === 2) pbf.readPackedVarint(obj.types); - else pbf.skip(tag); + else pbf.skipField(); } return obj; } @@ -36,10 +36,10 @@ export function writeFalsePacked(obj, pbf) { export function readPacked(pbf, end = pbf.length) { const obj = {value: []}; - while (pbf.pos < end) { - const tag = pbf.readVarint(), field = tag >>> 3; pbf.type = tag & 7; + let field; + while ((field = pbf.nextField(end))) { if (field === 16) pbf.readPackedVarint(obj.value, true); - else pbf.skip(tag); + else pbf.skipField(); } return obj; } @@ -49,10 +49,10 @@ export function writePacked(obj, pbf) { export function readPackedFixed(pbf, end = pbf.length) { const obj = {value: []}; - while (pbf.pos < end) { - const tag = pbf.readVarint(), field = tag >>> 3; pbf.type = tag & 7; + let field; + while ((field = pbf.nextField(end))) { if (field === 1) pbf.readPackedSFixed64(obj.value); - else pbf.skip(tag); + else pbf.skipField(); } return obj; } diff --git a/test/fixtures/type_string.js b/test/fixtures/type_string.js index d8a4228..36f81ac 100644 --- a/test/fixtures/type_string.js +++ b/test/fixtures/type_string.js @@ -1,15 +1,15 @@ export function readTypeString(pbf, end = pbf.length) { const obj = {int: "0", long: "0", boolVal: false, float: "0", default_implicit: "0", default_explicit: "42"}; - while (pbf.pos < end) { - const tag = pbf.readVarint(), field = tag >>> 3; + let field; + while ((field = pbf.nextField(end))) { if (field === 1) obj.int = pbf.readVarint(true).toString(); else if (field === 2) obj.long = pbf.readVarint(true).toString(); else if (field === 3) obj.boolVal = pbf.readBoolean(); else if (field === 4) obj.float = pbf.readFloat().toString(); else if (field === 5) obj.default_implicit = pbf.readVarint(true).toString(); else if (field === 6) obj.default_explicit = pbf.readVarint(true).toString(); - else pbf.skip(tag); + else pbf.skipField(); } return obj; } @@ -24,13 +24,13 @@ export function writeTypeString(obj, pbf) { export function readTypeNotString(pbf, end = pbf.length) { const obj = {int: 0, long: 0, boolVal: false, float: 0}; - while (pbf.pos < end) { - const tag = pbf.readVarint(), field = tag >>> 3; + let field; + while ((field = pbf.nextField(end))) { if (field === 1) obj.int = pbf.readVarint(true); else if (field === 2) obj.long = pbf.readVarint(true); else if (field === 3) obj.boolVal = pbf.readBoolean(); else if (field === 4) obj.float = pbf.readFloat(); - else pbf.skip(tag); + else pbf.skipField(); } return obj; } diff --git a/test/fixtures/varint.js b/test/fixtures/varint.js index 5f23f34..ebc31c1 100644 --- a/test/fixtures/varint.js +++ b/test/fixtures/varint.js @@ -1,13 +1,13 @@ export function readEnvelope(pbf, end = pbf.length) { const obj = {int: 0, uint: 0, long: 0, ulong: 0}; - while (pbf.pos < end) { - const tag = pbf.readVarint(), field = tag >>> 3; + let field; + while ((field = pbf.nextField(end))) { if (field === 1) obj.int = pbf.readVarint(true); else if (field === 2) obj.uint = pbf.readVarint(); else if (field === 3) obj.long = pbf.readVarint(true); else if (field === 4) obj.ulong = pbf.readVarint(); - else pbf.skip(tag); + else pbf.skipField(); } return obj; } diff --git a/test/fixtures/vector_tile.js b/test/fixtures/vector_tile.js index 4bb2377..64e8909 100644 --- a/test/fixtures/vector_tile.js +++ b/test/fixtures/vector_tile.js @@ -1,10 +1,10 @@ export function readTile(pbf, end = pbf.length) { const obj = {layers: []}; - while (pbf.pos < end) { - const tag = pbf.readVarint(), field = tag >>> 3; + let field; + while ((field = pbf.nextField(end))) { if (field === 3) obj.layers.push(readTileLayer(pbf, pbf.readVarint() + pbf.pos)); - else pbf.skip(tag); + else pbf.skipField(); } return obj; } @@ -21,8 +21,8 @@ export const TileGeomType = { export function readTileValue(pbf, end = pbf.length) { const obj = {string_value: "", float_value: 0, double_value: 0, int_value: 0, uint_value: 0, sint_value: 0, bool_value: false}; - while (pbf.pos < end) { - const tag = pbf.readVarint(), field = tag >>> 3; + let field; + while ((field = pbf.nextField(end))) { if (field === 1) obj.string_value = pbf.readString(); else if (field === 2) obj.float_value = pbf.readFloat(); else if (field === 3) obj.double_value = pbf.readDouble(); @@ -30,7 +30,7 @@ export function readTileValue(pbf, end = pbf.length) { else if (field === 5) obj.uint_value = pbf.readVarint(); else if (field === 6) obj.sint_value = pbf.readSVarint(); else if (field === 7) obj.bool_value = pbf.readBoolean(); - else pbf.skip(tag); + else pbf.skipField(); } return obj; } @@ -46,13 +46,13 @@ export function writeTileValue(obj, pbf) { export function readTileFeature(pbf, end = pbf.length) { const obj = {id: 0, tags: [], type: 0, geometry: []}; - while (pbf.pos < end) { - const tag = pbf.readVarint(), field = tag >>> 3; pbf.type = tag & 7; + let field; + while ((field = pbf.nextField(end))) { if (field === 1) obj.id = pbf.readVarint(); else if (field === 2) pbf.readPackedVarint(obj.tags); else if (field === 3) obj.type = pbf.readVarint(); else if (field === 4) pbf.readPackedVarint(obj.geometry); - else pbf.skip(tag); + else pbf.skipField(); } return obj; } @@ -65,15 +65,15 @@ export function writeTileFeature(obj, pbf) { export function readTileLayer(pbf, end = pbf.length) { const obj = {version: 1, name: "", features: [], keys: [], values: [], extent: 4096}; - while (pbf.pos < end) { - const tag = pbf.readVarint(), field = tag >>> 3; + let field; + while ((field = pbf.nextField(end))) { if (field === 15) obj.version = pbf.readVarint(); else if (field === 1) obj.name = pbf.readString(); else if (field === 2) obj.features.push(readTileFeature(pbf, pbf.readVarint() + pbf.pos)); else if (field === 3) obj.keys.push(pbf.readString()); else if (field === 4) obj.values.push(readTileValue(pbf, pbf.readVarint() + pbf.pos)); else if (field === 5) obj.extent = pbf.readVarint(); - else pbf.skip(tag); + else pbf.skipField(); } return obj; } diff --git a/test/pbf.test.js b/test/pbf.test.js index ce4a020..77e7950 100644 --- a/test/pbf.test.js +++ b/test/pbf.test.js @@ -444,3 +444,41 @@ test('write a raw message > 0x10000000', () => { // Then the message itself. Verify that the first few bytes match the marker. assert.deepEqual(bytes.subarray(encodedSize.length, encodedSize.length + markerSize), encodedMarker); }); + +test('nextField & skipField', () => { + // Encode three fields: varint #1 = 42, string #2 = "hi", varint #3 = 7. + const w = new PbfWriter(); + w.writeVarintField(1, 42); + w.writeStringField(2, 'hi'); + w.writeVarintField(3, 7); + const buf = w.finish(); + + // Reader that only knows field 1 and field 3 — field 2 must be skipped via skipField. + const pbf = new PbfReader(buf); + const seen = {}; + let field; + while ((field = pbf.nextField())) { + if (field === 1) seen.a = pbf.readVarint(); + else if (field === 3) seen.b = pbf.readVarint(); + else pbf.skipField(); + } + assert.deepEqual(seen, {a: 42, b: 7}); + assert.equal(pbf.pos, buf.length); + + // nextField returns 0 at end-of-message. + assert.equal(pbf.nextField(), 0); +}); + +test('nextField sets pbf.type for packed reads', () => { + // A packed varint field encodes as wire-type 2 (BYTES). readPackedVarint relies on + // pbf.type to distinguish packed vs unpacked encodings; nextField must set it. + const w = new PbfWriter(); + w.writePackedVarint(1, [1, 2, 3]); + const buf = w.finish(); + + const pbf = new PbfReader(buf); + const field = pbf.nextField(); + assert.equal(field, 1); + assert.equal(pbf.type, 2); // PBF_BYTES + assert.deepEqual(pbf.readPackedVarint(), [1, 2, 3]); +}); From 45cda1998928c3e2e74de5fbbd62518947469a35 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Tue, 26 May 2026 20:49:28 +0300 Subject: [PATCH 2/5] more compact codegen for empty messages --- compile.js | 32 ++++++++++++++++++++---------- test/fixtures/defaults_implicit.js | 11 +++------- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/compile.js b/compile.js index e238c51..57bf59d 100644 --- a/compile.js +++ b/compile.js @@ -31,22 +31,32 @@ function writeMessage(ctx, options) { const readName = `read${ctx._name}`; code += `${writeFunctionExport(options, readName)}function ${readName}(pbf, end = pbf.length) {\n`; - code += ` const obj = ${compileDest(ctx)};\n`; - code += ' let field;\n'; - code += ' while ((field = pbf.nextField(end))) {\n'; - - for (let i = 0; i < fields.length; i++) { - code += ` ${i ? 'else ' : ''}if (field === ${fields[i].tag}) ${compileFieldRead(ctx, fields[i])}\n`; + if (fields.length === 0) { + code += ' pbf.pos = end;\n return {};\n'; + } else { + code += ` const obj = ${compileDest(ctx)};\n`; + code += ' let field;\n'; + code += ' while ((field = pbf.nextField(end))) {\n'; + + for (let i = 0; i < fields.length; i++) { + code += ` ${i ? 'else ' : ''}if (field === ${fields[i].tag}) ${compileFieldRead(ctx, fields[i])}\n`; + } + code += ' else pbf.skipField();\n'; + code += ' }\n return obj;\n'; } - code += ` ${fields.length ? 'else ' : ''}pbf.skipField();\n`; - code += ' }\n return obj;\n}\n'; + code += '}\n'; } if (!options.noWrite) { const writeName = `write${ctx._name}`; - code += `${writeFunctionExport(options, writeName)}function ${writeName}(obj, pbf) {\n`; - for (const field of fields) code += compileFieldWriteLine(ctx, field); - code += '}\n'; + code += `${writeFunctionExport(options, writeName)}function ${writeName}(obj, pbf) {`; + if (fields.length === 0) { + code += '}\n'; + } else { + code += '\n'; + for (const field of fields) code += compileFieldWriteLine(ctx, field); + code += '}\n'; + } } return code; } diff --git a/test/fixtures/defaults_implicit.js b/test/fixtures/defaults_implicit.js index ba2992d..7a5c764 100644 --- a/test/fixtures/defaults_implicit.js +++ b/test/fixtures/defaults_implicit.js @@ -5,15 +5,10 @@ export const MessageType = { }; export function readCustomType(pbf, end = pbf.length) { - const obj = {}; - let field; - while ((field = pbf.nextField(end))) { - pbf.skipField(); - } - return obj; -} -export function writeCustomType(obj, pbf) { + pbf.pos = end; + return {}; } +export function writeCustomType(obj, pbf) {} export function readEnvelope(pbf, end = pbf.length) { const obj = {type: 0, name: "", flag: false, weight: 0, id: 0, tags: [], numbers: [], bytes: undefined, custom: undefined, types: []}; From acb6bd6c79b2b0aa431fd5548ddd2eef6b2e682a Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Tue, 26 May 2026 21:42:18 +0300 Subject: [PATCH 3/5] drop redundant end default --- README.md | 2 +- compile.js | 2 +- test/fixtures/defaults.js | 2 +- test/fixtures/defaults_implicit.js | 4 ++-- test/fixtures/defaults_proto3.js | 2 +- test/fixtures/embedded_type.js | 6 +++--- test/fixtures/map.js | 6 +++--- test/fixtures/map_collision.js | 8 ++++---- test/fixtures/oneof.js | 2 +- test/fixtures/packed.js | 6 +++--- test/fixtures/packed_proto3.js | 8 ++++---- test/fixtures/type_string.js | 4 ++-- test/fixtures/varint.js | 2 +- test/fixtures/vector_tile.js | 8 ++++---- 14 files changed, 31 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index c238be0..9e349ac 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ const {readExample, writeExample} = compile(proto); const pbf = new PbfReader(buffer); const data = readData(pbf); -function readData(pbf, end = pbf.length) { +function readData(pbf, end) { const data = {}; let field; while ((field = pbf.nextField(end))) { diff --git a/compile.js b/compile.js index 57bf59d..cdbc4fd 100644 --- a/compile.js +++ b/compile.js @@ -30,7 +30,7 @@ function writeMessage(ctx, options) { if (!options.noRead) { const readName = `read${ctx._name}`; - code += `${writeFunctionExport(options, readName)}function ${readName}(pbf, end = pbf.length) {\n`; + code += `${writeFunctionExport(options, readName)}function ${readName}(pbf, end) {\n`; if (fields.length === 0) { code += ' pbf.pos = end;\n return {};\n'; } else { diff --git a/test/fixtures/defaults.js b/test/fixtures/defaults.js index 88ff5a6..61aa3f9 100644 --- a/test/fixtures/defaults.js +++ b/test/fixtures/defaults.js @@ -4,7 +4,7 @@ export const MessageType = { "GREETING": 1 }; -export function readEnvelope(pbf, end = pbf.length) { +export function readEnvelope(pbf, end) { const obj = {type: 1, name: "test", flag: true, weight: 1.5, id: 1}; let field; while ((field = pbf.nextField(end))) { diff --git a/test/fixtures/defaults_implicit.js b/test/fixtures/defaults_implicit.js index 7a5c764..90e325f 100644 --- a/test/fixtures/defaults_implicit.js +++ b/test/fixtures/defaults_implicit.js @@ -4,13 +4,13 @@ export const MessageType = { "GREETING": 1 }; -export function readCustomType(pbf, end = pbf.length) { +export function readCustomType(pbf, end) { pbf.pos = end; return {}; } export function writeCustomType(obj, pbf) {} -export function readEnvelope(pbf, end = pbf.length) { +export function readEnvelope(pbf, end) { const obj = {type: 0, name: "", flag: false, weight: 0, id: 0, tags: [], numbers: [], bytes: undefined, custom: undefined, types: []}; let field; while ((field = pbf.nextField(end))) { diff --git a/test/fixtures/defaults_proto3.js b/test/fixtures/defaults_proto3.js index 1f7faab..9e405f4 100644 --- a/test/fixtures/defaults_proto3.js +++ b/test/fixtures/defaults_proto3.js @@ -4,7 +4,7 @@ export const MessageType = { "GREETING": 1 }; -export function readEnvelope(pbf, end = pbf.length) { +export function readEnvelope(pbf, end) { const obj = {type: 0, name: "", flag: false, weight: 0, id: 0}; let field; while ((field = pbf.nextField(end))) { diff --git a/test/fixtures/embedded_type.js b/test/fixtures/embedded_type.js index f95c987..bbff113 100644 --- a/test/fixtures/embedded_type.js +++ b/test/fixtures/embedded_type.js @@ -1,5 +1,5 @@ -export function readEmbeddedType(pbf, end = pbf.length) { +export function readEmbeddedType(pbf, end) { const obj = {value: "test", sub_field: undefined, sub_sub_field: undefined}; let field; while ((field = pbf.nextField(end))) { @@ -16,7 +16,7 @@ export function writeEmbeddedType(obj, pbf) { if (obj.sub_sub_field) pbf.writeMessage(5, writeEmbeddedTypeContainerInner, obj.sub_sub_field); } -export function readEmbeddedTypeContainer(pbf, end = pbf.length) { +export function readEmbeddedTypeContainer(pbf, end) { const obj = {values: []}; let field; while ((field = pbf.nextField(end))) { @@ -29,7 +29,7 @@ export function writeEmbeddedTypeContainer(obj, pbf) { if (obj.values) for (const item of obj.values) pbf.writeMessage(1, writeEmbeddedTypeContainerInner, item); } -export function readEmbeddedTypeContainerInner(pbf, end = pbf.length) { +export function readEmbeddedTypeContainerInner(pbf, end) { const obj = {value: ""}; let field; while ((field = pbf.nextField(end))) { diff --git a/test/fixtures/map.js b/test/fixtures/map.js index e452ea2..9db7fe9 100644 --- a/test/fixtures/map.js +++ b/test/fixtures/map.js @@ -1,5 +1,5 @@ -export function readEnvelope(pbf, end = pbf.length) { +export function readEnvelope(pbf, end) { const obj = {kv: {}, kn: {}}; let field; while ((field = pbf.nextField(end))) { @@ -14,7 +14,7 @@ export function writeEnvelope(obj, pbf) { if (obj.kn) for (const key of Object.keys(obj.kn)) pbf.writeMessage(2, writeEnvelopeKnEntry, {key, value: obj.kn[key]}); } -export function readEnvelopeKvEntry(pbf, end = pbf.length) { +export function readEnvelopeKvEntry(pbf, end) { const obj = {key: "", value: ""}; let field; while ((field = pbf.nextField(end))) { @@ -29,7 +29,7 @@ export function writeEnvelopeKvEntry(obj, pbf) { if (obj.value) pbf.writeStringField(2, obj.value); } -export function readEnvelopeKnEntry(pbf, end = pbf.length) { +export function readEnvelopeKnEntry(pbf, end) { const obj = {key: "", value: 0}; let field; while ((field = pbf.nextField(end))) { diff --git a/test/fixtures/map_collision.js b/test/fixtures/map_collision.js index d3b569f..957638f 100644 --- a/test/fixtures/map_collision.js +++ b/test/fixtures/map_collision.js @@ -1,5 +1,5 @@ -export function readEnvelope(pbf, end = pbf.length) { +export function readEnvelope(pbf, end) { const obj = {kv: {}, kn: {}, sibling: undefined, flag: 0}; let field; while ((field = pbf.nextField(end))) { @@ -23,7 +23,7 @@ export const EnvelopeKnEntry = { "ONE": 1 }; -export function readEnvelopeKvEntry(pbf, end = pbf.length) { +export function readEnvelopeKvEntry(pbf, end) { const obj = {marker: 0}; let field; while ((field = pbf.nextField(end))) { @@ -36,7 +36,7 @@ export function writeEnvelopeKvEntry(obj, pbf) { if (obj.marker) pbf.writeVarintField(1, obj.marker); } -export function readEnvelopeKvEntry$(pbf, end = pbf.length) { +export function readEnvelopeKvEntry$(pbf, end) { const obj = {key: "", value: ""}; let field; while ((field = pbf.nextField(end))) { @@ -51,7 +51,7 @@ export function writeEnvelopeKvEntry$(obj, pbf) { if (obj.value) pbf.writeStringField(2, obj.value); } -export function readEnvelopeKnEntry$(pbf, end = pbf.length) { +export function readEnvelopeKnEntry$(pbf, end) { const obj = {key: "", value: 0}; let field; while ((field = pbf.nextField(end))) { diff --git a/test/fixtures/oneof.js b/test/fixtures/oneof.js index 9a6c290..8f87ab0 100644 --- a/test/fixtures/oneof.js +++ b/test/fixtures/oneof.js @@ -1,5 +1,5 @@ -export function readEnvelope(pbf, end = pbf.length) { +export function readEnvelope(pbf, end) { const obj = {id: 0, int: 0, value: undefined, float: 0, string: ""}; let field; while ((field = pbf.nextField(end))) { diff --git a/test/fixtures/packed.js b/test/fixtures/packed.js index 6c08a20..1081633 100644 --- a/test/fixtures/packed.js +++ b/test/fixtures/packed.js @@ -1,5 +1,5 @@ -export function readNotPacked(pbf, end = pbf.length) { +export function readNotPacked(pbf, end) { const obj = {value: [], types: []}; let field; while ((field = pbf.nextField(end))) { @@ -14,7 +14,7 @@ export function writeNotPacked(obj, pbf) { if (obj.types) for (const item of obj.types) pbf.writeVarintField(2, item); } -export function readFalsePacked(pbf, end = pbf.length) { +export function readFalsePacked(pbf, end) { const obj = {value: [], types: []}; let field; while ((field = pbf.nextField(end))) { @@ -29,7 +29,7 @@ export function writeFalsePacked(obj, pbf) { if (obj.types) for (const item of obj.types) pbf.writeVarintField(2, item); } -export function readPacked(pbf, end = pbf.length) { +export function readPacked(pbf, end) { const obj = {value: [], types: []}; let field; while ((field = pbf.nextField(end))) { diff --git a/test/fixtures/packed_proto3.js b/test/fixtures/packed_proto3.js index 120f12a..d0fefca 100644 --- a/test/fixtures/packed_proto3.js +++ b/test/fixtures/packed_proto3.js @@ -4,7 +4,7 @@ export const MessageType = { "GREETING": 1 }; -export function readNotPacked(pbf, end = pbf.length) { +export function readNotPacked(pbf, end) { const obj = {value: [], types: []}; let field; while ((field = pbf.nextField(end))) { @@ -19,7 +19,7 @@ export function writeNotPacked(obj, pbf) { if (obj.types) pbf.writePackedVarint(2, obj.types); } -export function readFalsePacked(pbf, end = pbf.length) { +export function readFalsePacked(pbf, end) { const obj = {value: [], types: []}; let field; while ((field = pbf.nextField(end))) { @@ -34,7 +34,7 @@ export function writeFalsePacked(obj, pbf) { if (obj.types) for (const item of obj.types) pbf.writeVarintField(2, item); } -export function readPacked(pbf, end = pbf.length) { +export function readPacked(pbf, end) { const obj = {value: []}; let field; while ((field = pbf.nextField(end))) { @@ -47,7 +47,7 @@ export function writePacked(obj, pbf) { if (obj.value) pbf.writePackedVarint(16, obj.value); } -export function readPackedFixed(pbf, end = pbf.length) { +export function readPackedFixed(pbf, end) { const obj = {value: []}; let field; while ((field = pbf.nextField(end))) { diff --git a/test/fixtures/type_string.js b/test/fixtures/type_string.js index 36f81ac..8d9dbae 100644 --- a/test/fixtures/type_string.js +++ b/test/fixtures/type_string.js @@ -1,5 +1,5 @@ -export function readTypeString(pbf, end = pbf.length) { +export function readTypeString(pbf, end) { const obj = {int: "0", long: "0", boolVal: false, float: "0", default_implicit: "0", default_explicit: "42"}; let field; while ((field = pbf.nextField(end))) { @@ -22,7 +22,7 @@ export function writeTypeString(obj, pbf) { if (obj.default_explicit != null && obj.default_explicit !== "42") pbf.writeVarintField(6, parseInt(obj.default_explicit, 10)); } -export function readTypeNotString(pbf, end = pbf.length) { +export function readTypeNotString(pbf, end) { const obj = {int: 0, long: 0, boolVal: false, float: 0}; let field; while ((field = pbf.nextField(end))) { diff --git a/test/fixtures/varint.js b/test/fixtures/varint.js index ebc31c1..919cb50 100644 --- a/test/fixtures/varint.js +++ b/test/fixtures/varint.js @@ -1,5 +1,5 @@ -export function readEnvelope(pbf, end = pbf.length) { +export function readEnvelope(pbf, end) { const obj = {int: 0, uint: 0, long: 0, ulong: 0}; let field; while ((field = pbf.nextField(end))) { diff --git a/test/fixtures/vector_tile.js b/test/fixtures/vector_tile.js index 64e8909..086614c 100644 --- a/test/fixtures/vector_tile.js +++ b/test/fixtures/vector_tile.js @@ -1,5 +1,5 @@ -export function readTile(pbf, end = pbf.length) { +export function readTile(pbf, end) { const obj = {layers: []}; let field; while ((field = pbf.nextField(end))) { @@ -19,7 +19,7 @@ export const TileGeomType = { "POLYGON": 3 }; -export function readTileValue(pbf, end = pbf.length) { +export function readTileValue(pbf, end) { const obj = {string_value: "", float_value: 0, double_value: 0, int_value: 0, uint_value: 0, sint_value: 0, bool_value: false}; let field; while ((field = pbf.nextField(end))) { @@ -44,7 +44,7 @@ export function writeTileValue(obj, pbf) { if (obj.bool_value) pbf.writeBooleanField(7, obj.bool_value); } -export function readTileFeature(pbf, end = pbf.length) { +export function readTileFeature(pbf, end) { const obj = {id: 0, tags: [], type: 0, geometry: []}; let field; while ((field = pbf.nextField(end))) { @@ -63,7 +63,7 @@ export function writeTileFeature(obj, pbf) { if (obj.geometry) pbf.writePackedVarint(4, obj.geometry); } -export function readTileLayer(pbf, end = pbf.length) { +export function readTileLayer(pbf, end) { const obj = {version: 1, name: "", features: [], keys: [], values: [], extent: 4096}; let field; while ((field = pbf.nextField(end))) { From bf66d3674495ecc34a76f7f2d84619ac6e44c073 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Tue, 26 May 2026 22:52:51 +0300 Subject: [PATCH 4/5] skip fields implicitly in nextField, drop skipField --- README.md | 12 +++++------- compile.js | 1 - index.js | 10 +++------- test/fixtures/defaults.js | 1 - test/fixtures/defaults_implicit.js | 1 - test/fixtures/defaults_proto3.js | 1 - test/fixtures/embedded_type.js | 3 --- test/fixtures/map.js | 3 --- test/fixtures/map_collision.js | 4 ---- test/fixtures/oneof.js | 1 - test/fixtures/packed.js | 3 --- test/fixtures/packed_proto3.js | 4 ---- test/fixtures/type_string.js | 2 -- test/fixtures/varint.js | 1 - test/fixtures/vector_tile.js | 4 ---- test/pbf.test.js | 5 ++--- 16 files changed, 10 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 9e349ac..9b979a0 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,6 @@ function readData(pbf, end) { if (field === 1) data.name = pbf.readString(); else if (field === 2) data.version = pbf.readVarint(); else if (field === 3) data.layer = readLayer(pbf, pbf.readVarint() + pbf.pos); - else pbf.skipField(); } return data; } @@ -81,7 +80,6 @@ function readLayer(pbf, end) { while ((field = pbf.nextField(end))) { if (field === 1) layer.name = pbf.readString(); else if (field === 3) layer.size = pbf.readVarint(); - else pbf.skipField(); } return layer; } @@ -150,14 +148,13 @@ pbf.pos; // current offset for reading or writing #### Reading -Loop over a message's fields with `nextField`, dispatch on the field number, and call `skipField()` for anything you don't recognize: +Loop over a message's fields with `nextField` and dispatch on the field number. Unrecognized or unread fields are skipped automatically on the next iteration: ```js let field; while ((field = pbf.nextField(end))) { if (field === 1) obj.id = pbf.readVarint(); else if (field === 2) obj.name = pbf.readString(); - else pbf.skipField(); } ``` @@ -182,7 +179,6 @@ let fooPos = -1; let field; while ((field = pbf.nextField())) { if (field === 1) fooPos = pbf.pos; - pbf.skipField(); } ... pbf.pos = fooPos; @@ -204,8 +200,10 @@ Scalar reading methods: * `readDouble()` * `readString()` * `readBytes()` -* `nextField(end)` — returns the next field number, or `0` at end-of-message -* `skipField()` — skips the current field + +Field iteration methods: + +* `nextField(end)` — returns the next field number, or `0` at end-of-message; skips the previous field's value if it wasn't consumed * `skip(value)` — skips a field given its raw tag varint Packed reading methods: diff --git a/compile.js b/compile.js index cdbc4fd..aaf6cc8 100644 --- a/compile.js +++ b/compile.js @@ -41,7 +41,6 @@ function writeMessage(ctx, options) { for (let i = 0; i < fields.length; i++) { code += ` ${i ? 'else ' : ''}if (field === ${fields[i].tag}) ${compileFieldRead(ctx, fields[i])}\n`; } - code += ' else pbf.skipField();\n'; code += ' }\n return obj;\n'; } code += '}\n'; diff --git a/index.js b/index.js index 723f73d..a6b1a55 100644 --- a/index.js +++ b/index.js @@ -21,6 +21,7 @@ export class PbfReader { this.dataView = new DataView(this.buf.buffer); this.pos = 0; this.type = 0; + this._valueStart = -1; this.length = this.buf.length; } @@ -33,9 +34,7 @@ export class PbfReader { readFields(readField, result, end = this.length) { let field; while ((field = this.nextField(end))) { - const startPos = this.pos; readField(field, result, this); - if (this.pos === startPos) this.skipField(); } return result; } @@ -201,17 +200,14 @@ export class PbfReader { * @param {number} [end] */ nextField(end = this.length) { + if (this.pos === this._valueStart) this.skip(this.type); if (this.pos >= end) return 0; const tag = this.readVarint(); this.type = tag & 0x7; + this._valueStart = this.pos; return tag >>> 3; } - /** Skip the field most recently returned by `nextField`. */ - skipField() { - this.skip(this.type); - } - /** @param {number} val */ skip(val) { const type = val & 0x7; diff --git a/test/fixtures/defaults.js b/test/fixtures/defaults.js index 61aa3f9..41505c9 100644 --- a/test/fixtures/defaults.js +++ b/test/fixtures/defaults.js @@ -13,7 +13,6 @@ export function readEnvelope(pbf, end) { else if (field === 3) obj.flag = pbf.readBoolean(); else if (field === 4) obj.weight = pbf.readFloat(); else if (field === 5) obj.id = pbf.readVarint(true); - else pbf.skipField(); } return obj; } diff --git a/test/fixtures/defaults_implicit.js b/test/fixtures/defaults_implicit.js index 90e325f..b7d8a0c 100644 --- a/test/fixtures/defaults_implicit.js +++ b/test/fixtures/defaults_implicit.js @@ -24,7 +24,6 @@ export function readEnvelope(pbf, end) { else if (field === 8) obj.bytes = pbf.readBytes(); else if (field === 9) obj.custom = readCustomType(pbf, pbf.readVarint() + pbf.pos); else if (field === 10) pbf.readPackedVarint(obj.types); - else pbf.skipField(); } return obj; } diff --git a/test/fixtures/defaults_proto3.js b/test/fixtures/defaults_proto3.js index 9e405f4..2f0589f 100644 --- a/test/fixtures/defaults_proto3.js +++ b/test/fixtures/defaults_proto3.js @@ -13,7 +13,6 @@ export function readEnvelope(pbf, end) { else if (field === 3) obj.flag = pbf.readBoolean(); else if (field === 4) obj.weight = pbf.readFloat(); else if (field === 5) obj.id = pbf.readVarint(true); - else pbf.skipField(); } return obj; } diff --git a/test/fixtures/embedded_type.js b/test/fixtures/embedded_type.js index bbff113..ee9244a 100644 --- a/test/fixtures/embedded_type.js +++ b/test/fixtures/embedded_type.js @@ -6,7 +6,6 @@ export function readEmbeddedType(pbf, end) { if (field === 1) obj.value = pbf.readString(); else if (field === 4) obj.sub_field = readEmbeddedTypeContainer(pbf, pbf.readVarint() + pbf.pos); else if (field === 5) obj.sub_sub_field = readEmbeddedTypeContainerInner(pbf, pbf.readVarint() + pbf.pos); - else pbf.skipField(); } return obj; } @@ -21,7 +20,6 @@ export function readEmbeddedTypeContainer(pbf, end) { let field; while ((field = pbf.nextField(end))) { if (field === 1) obj.values.push(readEmbeddedTypeContainerInner(pbf, pbf.readVarint() + pbf.pos)); - else pbf.skipField(); } return obj; } @@ -34,7 +32,6 @@ export function readEmbeddedTypeContainerInner(pbf, end) { let field; while ((field = pbf.nextField(end))) { if (field === 1) obj.value = pbf.readString(); - else pbf.skipField(); } return obj; } diff --git a/test/fixtures/map.js b/test/fixtures/map.js index 9db7fe9..6966a31 100644 --- a/test/fixtures/map.js +++ b/test/fixtures/map.js @@ -5,7 +5,6 @@ export function readEnvelope(pbf, end) { while ((field = pbf.nextField(end))) { if (field === 1) { const {key, value} = readEnvelopeKvEntry(pbf, pbf.readVarint() + pbf.pos); obj.kv[key] = value; } else if (field === 2) { const {key, value} = readEnvelopeKnEntry(pbf, pbf.readVarint() + pbf.pos); obj.kn[key] = value; } - else pbf.skipField(); } return obj; } @@ -20,7 +19,6 @@ export function readEnvelopeKvEntry(pbf, end) { while ((field = pbf.nextField(end))) { if (field === 1) obj.key = pbf.readString(); else if (field === 2) obj.value = pbf.readString(); - else pbf.skipField(); } return obj; } @@ -35,7 +33,6 @@ export function readEnvelopeKnEntry(pbf, end) { while ((field = pbf.nextField(end))) { if (field === 1) obj.key = pbf.readString(); else if (field === 2) obj.value = pbf.readVarint(true); - else pbf.skipField(); } return obj; } diff --git a/test/fixtures/map_collision.js b/test/fixtures/map_collision.js index 957638f..60c507e 100644 --- a/test/fixtures/map_collision.js +++ b/test/fixtures/map_collision.js @@ -7,7 +7,6 @@ export function readEnvelope(pbf, end) { else if (field === 2) { const {key, value} = readEnvelopeKnEntry$(pbf, pbf.readVarint() + pbf.pos); obj.kn[key] = value; } else if (field === 3) obj.sibling = readEnvelopeKvEntry(pbf, pbf.readVarint() + pbf.pos); else if (field === 4) obj.flag = pbf.readVarint(); - else pbf.skipField(); } return obj; } @@ -28,7 +27,6 @@ export function readEnvelopeKvEntry(pbf, end) { let field; while ((field = pbf.nextField(end))) { if (field === 1) obj.marker = pbf.readVarint(true); - else pbf.skipField(); } return obj; } @@ -42,7 +40,6 @@ export function readEnvelopeKvEntry$(pbf, end) { while ((field = pbf.nextField(end))) { if (field === 1) obj.key = pbf.readString(); else if (field === 2) obj.value = pbf.readString(); - else pbf.skipField(); } return obj; } @@ -57,7 +54,6 @@ export function readEnvelopeKnEntry$(pbf, end) { while ((field = pbf.nextField(end))) { if (field === 1) obj.key = pbf.readString(); else if (field === 2) obj.value = pbf.readVarint(true); - else pbf.skipField(); } return obj; } diff --git a/test/fixtures/oneof.js b/test/fixtures/oneof.js index 8f87ab0..5ac7bb1 100644 --- a/test/fixtures/oneof.js +++ b/test/fixtures/oneof.js @@ -7,7 +7,6 @@ export function readEnvelope(pbf, end) { else if (field === 2) { obj.int = pbf.readVarint(true); obj.value = "int"; } else if (field === 3) { obj.float = pbf.readFloat(); obj.value = "float"; } else if (field === 4) { obj.string = pbf.readString(); obj.value = "string"; } - else pbf.skipField(); } return obj; } diff --git a/test/fixtures/packed.js b/test/fixtures/packed.js index 1081633..1e6a7f0 100644 --- a/test/fixtures/packed.js +++ b/test/fixtures/packed.js @@ -5,7 +5,6 @@ export function readNotPacked(pbf, end) { while ((field = pbf.nextField(end))) { if (field === 1) pbf.readPackedVarint(obj.value, true); else if (field === 2) pbf.readPackedVarint(obj.types, true); - else pbf.skipField(); } return obj; } @@ -20,7 +19,6 @@ export function readFalsePacked(pbf, end) { while ((field = pbf.nextField(end))) { if (field === 1) pbf.readPackedVarint(obj.value, true); else if (field === 2) pbf.readPackedVarint(obj.types, true); - else pbf.skipField(); } return obj; } @@ -35,7 +33,6 @@ export function readPacked(pbf, end) { while ((field = pbf.nextField(end))) { if (field === 1) pbf.readPackedVarint(obj.value, true); else if (field === 2) pbf.readPackedVarint(obj.types, true); - else pbf.skipField(); } return obj; } diff --git a/test/fixtures/packed_proto3.js b/test/fixtures/packed_proto3.js index d0fefca..9935d32 100644 --- a/test/fixtures/packed_proto3.js +++ b/test/fixtures/packed_proto3.js @@ -10,7 +10,6 @@ export function readNotPacked(pbf, end) { while ((field = pbf.nextField(end))) { if (field === 1) pbf.readPackedVarint(obj.value, true); else if (field === 2) pbf.readPackedVarint(obj.types); - else pbf.skipField(); } return obj; } @@ -25,7 +24,6 @@ export function readFalsePacked(pbf, end) { while ((field = pbf.nextField(end))) { if (field === 1) pbf.readPackedVarint(obj.value, true); else if (field === 2) pbf.readPackedVarint(obj.types); - else pbf.skipField(); } return obj; } @@ -39,7 +37,6 @@ export function readPacked(pbf, end) { let field; while ((field = pbf.nextField(end))) { if (field === 16) pbf.readPackedVarint(obj.value, true); - else pbf.skipField(); } return obj; } @@ -52,7 +49,6 @@ export function readPackedFixed(pbf, end) { let field; while ((field = pbf.nextField(end))) { if (field === 1) pbf.readPackedSFixed64(obj.value); - else pbf.skipField(); } return obj; } diff --git a/test/fixtures/type_string.js b/test/fixtures/type_string.js index 8d9dbae..236adac 100644 --- a/test/fixtures/type_string.js +++ b/test/fixtures/type_string.js @@ -9,7 +9,6 @@ export function readTypeString(pbf, end) { else if (field === 4) obj.float = pbf.readFloat().toString(); else if (field === 5) obj.default_implicit = pbf.readVarint(true).toString(); else if (field === 6) obj.default_explicit = pbf.readVarint(true).toString(); - else pbf.skipField(); } return obj; } @@ -30,7 +29,6 @@ export function readTypeNotString(pbf, end) { else if (field === 2) obj.long = pbf.readVarint(true); else if (field === 3) obj.boolVal = pbf.readBoolean(); else if (field === 4) obj.float = pbf.readFloat(); - else pbf.skipField(); } return obj; } diff --git a/test/fixtures/varint.js b/test/fixtures/varint.js index 919cb50..9592b6c 100644 --- a/test/fixtures/varint.js +++ b/test/fixtures/varint.js @@ -7,7 +7,6 @@ export function readEnvelope(pbf, end) { else if (field === 2) obj.uint = pbf.readVarint(); else if (field === 3) obj.long = pbf.readVarint(true); else if (field === 4) obj.ulong = pbf.readVarint(); - else pbf.skipField(); } return obj; } diff --git a/test/fixtures/vector_tile.js b/test/fixtures/vector_tile.js index 086614c..fd91b4a 100644 --- a/test/fixtures/vector_tile.js +++ b/test/fixtures/vector_tile.js @@ -4,7 +4,6 @@ export function readTile(pbf, end) { let field; while ((field = pbf.nextField(end))) { if (field === 3) obj.layers.push(readTileLayer(pbf, pbf.readVarint() + pbf.pos)); - else pbf.skipField(); } return obj; } @@ -30,7 +29,6 @@ export function readTileValue(pbf, end) { else if (field === 5) obj.uint_value = pbf.readVarint(); else if (field === 6) obj.sint_value = pbf.readSVarint(); else if (field === 7) obj.bool_value = pbf.readBoolean(); - else pbf.skipField(); } return obj; } @@ -52,7 +50,6 @@ export function readTileFeature(pbf, end) { else if (field === 2) pbf.readPackedVarint(obj.tags); else if (field === 3) obj.type = pbf.readVarint(); else if (field === 4) pbf.readPackedVarint(obj.geometry); - else pbf.skipField(); } return obj; } @@ -73,7 +70,6 @@ export function readTileLayer(pbf, end) { else if (field === 3) obj.keys.push(pbf.readString()); else if (field === 4) obj.values.push(readTileValue(pbf, pbf.readVarint() + pbf.pos)); else if (field === 5) obj.extent = pbf.readVarint(); - else pbf.skipField(); } return obj; } diff --git a/test/pbf.test.js b/test/pbf.test.js index 77e7950..0ff128c 100644 --- a/test/pbf.test.js +++ b/test/pbf.test.js @@ -445,7 +445,7 @@ test('write a raw message > 0x10000000', () => { assert.deepEqual(bytes.subarray(encodedSize.length, encodedSize.length + markerSize), encodedMarker); }); -test('nextField & skipField', () => { +test('nextField implicitly skips unread fields', () => { // Encode three fields: varint #1 = 42, string #2 = "hi", varint #3 = 7. const w = new PbfWriter(); w.writeVarintField(1, 42); @@ -453,14 +453,13 @@ test('nextField & skipField', () => { w.writeVarintField(3, 7); const buf = w.finish(); - // Reader that only knows field 1 and field 3 — field 2 must be skipped via skipField. + // Reader that only knows field 1 and field 3 — field 2 is skipped implicitly. const pbf = new PbfReader(buf); const seen = {}; let field; while ((field = pbf.nextField())) { if (field === 1) seen.a = pbf.readVarint(); else if (field === 3) seen.b = pbf.readVarint(); - else pbf.skipField(); } assert.deepEqual(seen, {a: 42, b: 7}); assert.equal(pbf.pos, buf.length); From f85f99053933b9b55141201e98f9e0aa661aa6d6 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Thu, 28 May 2026 14:49:49 +0300 Subject: [PATCH 5/5] 5.1.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index a7e9101..10f2500 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pbf", - "version": "5.0.0", + "version": "5.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pbf", - "version": "5.0.0", + "version": "5.1.0", "license": "BSD-3-Clause", "dependencies": { "resolve-protobuf-schema": "^2.1.0" diff --git a/package.json b/package.json index bb9e97c..4a3b997 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pbf", - "version": "5.0.0", + "version": "5.1.0", "description": "a low-level, lightweight protocol buffers implementation in JavaScript", "main": "index.js", "type": "module",