diff --git a/README.md b/README.md index a3e3200..9b979a0 100644 --- a/README.md +++ b/README.md @@ -61,16 +61,27 @@ 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) { + 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); + } + 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(); + } + return layer; } ``` @@ -137,29 +148,22 @@ pbf.pos; // current offset for reading or writing #### Reading -Read a sequence of fields: +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 -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(); +} ``` -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 +172,21 @@ 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.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 +200,11 @@ Scalar reading methods: * `readDouble()` * `readString()` * `readBytes()` -* `skip(value)` + +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 c0559d0..aaf6cc8 100644 --- a/compile.js +++ b/compile.js @@ -29,25 +29,33 @@ 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`; - - for (let i = 0; i < fields.length; i++) { - code += ` ${i ? 'else ' : ''}if (field === ${fields[i].tag}) ${compileFieldRead(ctx, fields[i])}\n`; + code += `${writeFunctionExport(options, readName)}function ${readName}(pbf, end) {\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 += ' }\n return obj;\n'; } - code += ` ${fields.length ? 'else ' : ''}pbf.skip(tag);\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/index.js b/index.js index 2a623f7..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; } @@ -31,15 +32,9 @@ 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))) { + readField(field, result, this); } return result; } @@ -200,6 +195,19 @@ 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 === 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; + } + /** @param {number} val */ skip(val) { const type = val & 0x7; 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", diff --git a/test/fixtures/defaults.js b/test/fixtures/defaults.js index 67946d6..41505c9 100644 --- a/test/fixtures/defaults.js +++ b/test/fixtures/defaults.js @@ -4,16 +4,15 @@ 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}; - 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); } return obj; } diff --git a/test/fixtures/defaults_implicit.js b/test/fixtures/defaults_implicit.js index ba6ec02..b7d8a0c 100644 --- a/test/fixtures/defaults_implicit.js +++ b/test/fixtures/defaults_implicit.js @@ -4,21 +4,16 @@ export const MessageType = { "GREETING": 1 }; -export function readCustomType(pbf, end = pbf.length) { - const obj = {}; - while (pbf.pos < end) { - const tag = pbf.readVarint(), field = tag >>> 3; - pbf.skip(tag); - } - return obj; -} -export function writeCustomType(obj, pbf) { +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: []}; - 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 +24,6 @@ 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); } return obj; } diff --git a/test/fixtures/defaults_proto3.js b/test/fixtures/defaults_proto3.js index 0827af1..2f0589f 100644 --- a/test/fixtures/defaults_proto3.js +++ b/test/fixtures/defaults_proto3.js @@ -4,16 +4,15 @@ 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}; - 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); } return obj; } diff --git a/test/fixtures/embedded_type.js b/test/fixtures/embedded_type.js index 5ede605..ee9244a 100644 --- a/test/fixtures/embedded_type.js +++ b/test/fixtures/embedded_type.js @@ -1,12 +1,11 @@ -export function readEmbeddedType(pbf, end = pbf.length) { +export function readEmbeddedType(pbf, end) { 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); } return obj; } @@ -16,12 +15,11 @@ 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: []}; - 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); } return obj; } @@ -29,12 +27,11 @@ 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: ""}; - 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); } return obj; } diff --git a/test/fixtures/map.js b/test/fixtures/map.js index 04b254b..6966a31 100644 --- a/test/fixtures/map.js +++ b/test/fixtures/map.js @@ -1,11 +1,10 @@ -export function readEnvelope(pbf, end = pbf.length) { +export function readEnvelope(pbf, end) { 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); } return obj; } @@ -14,13 +13,12 @@ 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: ""}; - 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); } return obj; } @@ -29,13 +27,12 @@ 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}; - 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); } return obj; } diff --git a/test/fixtures/map_collision.js b/test/fixtures/map_collision.js index 2347a83..60c507e 100644 --- a/test/fixtures/map_collision.js +++ b/test/fixtures/map_collision.js @@ -1,13 +1,12 @@ -export function readEnvelope(pbf, end = pbf.length) { +export function readEnvelope(pbf, end) { 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); } return obj; } @@ -23,12 +22,11 @@ export const EnvelopeKnEntry = { "ONE": 1 }; -export function readEnvelopeKvEntry(pbf, end = pbf.length) { +export function readEnvelopeKvEntry(pbf, end) { 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); } return obj; } @@ -36,13 +34,12 @@ 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: ""}; - 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); } return obj; } @@ -51,13 +48,12 @@ 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}; - 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); } return obj; } diff --git a/test/fixtures/oneof.js b/test/fixtures/oneof.js index ec8502b..5ac7bb1 100644 --- a/test/fixtures/oneof.js +++ b/test/fixtures/oneof.js @@ -1,13 +1,12 @@ -export function readEnvelope(pbf, end = pbf.length) { +export function readEnvelope(pbf, end) { 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); } return obj; } diff --git a/test/fixtures/packed.js b/test/fixtures/packed.js index d8ce77e..1e6a7f0 100644 --- a/test/fixtures/packed.js +++ b/test/fixtures/packed.js @@ -1,11 +1,10 @@ -export function readNotPacked(pbf, end = pbf.length) { +export function readNotPacked(pbf, end) { 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); } return obj; } @@ -14,13 +13,12 @@ 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: []}; - 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); } return obj; } @@ -29,13 +27,12 @@ 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: []}; - 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); } return obj; } diff --git a/test/fixtures/packed_proto3.js b/test/fixtures/packed_proto3.js index e14a56e..9935d32 100644 --- a/test/fixtures/packed_proto3.js +++ b/test/fixtures/packed_proto3.js @@ -4,13 +4,12 @@ export const MessageType = { "GREETING": 1 }; -export function readNotPacked(pbf, end = pbf.length) { +export function readNotPacked(pbf, end) { 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); } return obj; } @@ -19,13 +18,12 @@ 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: []}; - 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); } return obj; } @@ -34,12 +32,11 @@ 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: []}; - 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); } return obj; } @@ -47,12 +44,11 @@ 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: []}; - 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); } return obj; } diff --git a/test/fixtures/type_string.js b/test/fixtures/type_string.js index d8a4228..236adac 100644 --- a/test/fixtures/type_string.js +++ b/test/fixtures/type_string.js @@ -1,15 +1,14 @@ -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"}; - 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); } return obj; } @@ -22,15 +21,14 @@ 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}; - 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); } return obj; } diff --git a/test/fixtures/varint.js b/test/fixtures/varint.js index 5f23f34..9592b6c 100644 --- a/test/fixtures/varint.js +++ b/test/fixtures/varint.js @@ -1,13 +1,12 @@ -export function readEnvelope(pbf, end = pbf.length) { +export function readEnvelope(pbf, end) { 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); } return obj; } diff --git a/test/fixtures/vector_tile.js b/test/fixtures/vector_tile.js index 4bb2377..fd91b4a 100644 --- a/test/fixtures/vector_tile.js +++ b/test/fixtures/vector_tile.js @@ -1,10 +1,9 @@ -export function readTile(pbf, end = pbf.length) { +export function readTile(pbf, end) { 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); } return obj; } @@ -19,10 +18,10 @@ 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}; - 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 +29,6 @@ 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); } return obj; } @@ -44,15 +42,14 @@ 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: []}; - 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); } return obj; } @@ -63,17 +60,16 @@ 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}; - 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); } return obj; } diff --git a/test/pbf.test.js b/test/pbf.test.js index ce4a020..0ff128c 100644 --- a/test/pbf.test.js +++ b/test/pbf.test.js @@ -444,3 +444,40 @@ 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 implicitly skips unread fields', () => { + // 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 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(); + } + 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]); +});