Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 42 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
```

Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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:

Expand Down
34 changes: 21 additions & 13 deletions compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
26 changes: 17 additions & 9 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
7 changes: 3 additions & 4 deletions test/fixtures/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
20 changes: 7 additions & 13 deletions test/fixtures/defaults_implicit.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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;
}
Expand Down
7 changes: 3 additions & 4 deletions test/fixtures/defaults_proto3.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
21 changes: 9 additions & 12 deletions test/fixtures/embedded_type.js
Original file line number Diff line number Diff line change
@@ -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;
}
Expand All @@ -16,25 +15,23 @@ 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;
}
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;
}
Expand Down
Loading
Loading