Skip to content

Issues/775 #779

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
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
3 changes: 2 additions & 1 deletion src/decoder.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ function decoder(mtype) {
// Unknown fields
} gen
("default:")
("r.skipType(t&7)")
("Object.defineProperty(m,\"__unknownFields\",{writable:true,configurable:true,enumerable:false })")
("m[\"__unknownFields\"]=r.rawBytes(t,m[\"__unknownFields\"])")
("break")

("}")
Expand Down
4 changes: 4 additions & 0 deletions src/encoder.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ function encoder(mtype) {
}
}

// unkown fields
gen("if(m[\"__unknownFields\"])")
("w.rawBytes(m[\"__unknownFields\"]);");

return gen
("return w");
/* eslint-enable no-unexpected-multiline, block-scoped-var, no-redeclare */
Expand Down
40 changes: 39 additions & 1 deletion src/reader.js
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,45 @@ Reader.prototype.skipType = function(wireType) {
return this;
};

Reader._configure = function(BufferReader_) {
/**
* Returns the next element of the specified wire type as bytes
* @param {number} id_wireType field id and wire type
* @param {Uint8Array} append previously encountered unknown fields, if any
* @returns {Uint8Array} value read
*/
Reader.prototype.rawBytes = function read_raw_bytes(id_wireType, append) {
var start = this.pos;
do { // roll id_wireType back
--start;
this.pos = start;
} while (this.uint32() !== id_wireType);

this.skipType(id_wireType & 7);

var skipped;

/* istanbul ignore if */
if (Array.isArray(this.buf)) { // plain array
skipped = this.buf.slice(start, this.pos);
if (append)
skipped = append.concat(skipped);
}
else {
skipped = this._slice.call(this.buf, start, this.pos);
if (append) {
var merged = new this.buf.constructor(skipped.length + append.length);
for (var i = 0; i < append.length; ++i)
merged[i] = append[i];
for (var j = 0; j < skipped.length; ++j)
merged[j + append.length] = skipped[j];
skipped = merged;
}
}

return skipped;
};

Reader._configure = function (BufferReader_) {
BufferReader = BufferReader_;

var fn = util.Long ? "toLong" : /* istanbul ignore next */ "toNumber";
Expand Down
9 changes: 9 additions & 0 deletions src/type.js
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,15 @@ Type.prototype.decodeDelimited = function decodeDelimited(reader) {
return this.decode(reader, reader.uint32());
};

/**
* Removes unknown fields that have been deserialized into the message
* @param {Message<{}>|Object.<string,*>} message Message instance or plain object
* @returns {Message<{}>|Object.<string,*>} message with unknown fields stripped
*/
Type.prototype.discardUnknownFields = function discardUnknownFields(message) {
delete message["__unknownFields"];
};

/**
* Verifies that field values are valid and that required fields are present.
* @param {Object.<string,*>} message Plain object to verify
Expand Down
18 changes: 12 additions & 6 deletions src/writer.js
Original file line number Diff line number Diff line change
Expand Up @@ -352,12 +352,8 @@ Writer.prototype.double = function write_double(value) {
return this._push(util.float.writeDoubleLE, 8, value);
};

var writeBytes = util.Array.prototype.set
? function writeBytes_set(val, buf, pos) {
buf.set(val, pos); // also works for plain array values
}
/* istanbul ignore next */
: function writeBytes_for(val, buf, pos) {
/* istanbul ignore next */
var writeBytes = function writeBytes_for(val, buf, pos) {
for (var i = 0; i < val.length; ++i)
buf[pos + i] = val[i];
};
Expand All @@ -379,6 +375,16 @@ Writer.prototype.bytes = function write_bytes(value) {
return this.uint32(len)._push(writeBytes, len, value);
};

/**
* Writes raw bytes with no wire type of length prefixed
* @param {Uint8Array} value bytes to add
* @returns {Writer} `this`
*/
Writer.prototype.rawBytes = function write_raw_bytes(value) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems that writeBytes (the one using util.Array) might be incorrect on older node versions, but it was probably never called until now

return this._push(writeBytes, value.length, value);
};


/**
* Writes a string.
* @param {string} value Value to write
Expand Down
127 changes: 127 additions & 0 deletions tests/comp_unknown-fields.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
var tape = require("tape");

var protobuf = require("..");

var proto = "message Simple_v1 {\
optional string knownName = 1;\
optional string knownValue = 3;\
}\
message Simple_v2 {\
optional string knownName = 1;\
optional int32 unknownFlags = 2;\
optional string knownValue = 3;\
}";

var msg = { inner: [{}, {}, {}] };

tape.test("unknown fields", function (test) {
var root = protobuf.parse(proto).root,
Simple_v1 = root.lookup("Simple_v1");
Simple_v2 = root.lookup("Simple_v2");

var s2 = Simple_v2.create({ knownName: "v2", unknownFlags: 2, knownValue: "dummy" });
var s1 = Simple_v1.decode(Simple_v2.encode(s2).finish());

var restored = Simple_v2.decode(Simple_v1.encode(s1).finish());

test.equal(s2.knownName, restored.knownName, "assert: even known fields are missing");

test.equal(2, restored.unknownFlags, "are preserved by default");
test.end();
});

tape.test("discarded unknown fields", function (test) {
var root = protobuf.parse(proto).root,
Simple_v1 = root.lookup("Simple_v1");
Simple_v2 = root.lookup("Simple_v2");

var s2 = Simple_v2.create({ knownName: "v2", unknownFlags: 2, knownValue: "dummy" });
var s1 = Simple_v1.decode(Simple_v2.encode(s2).finish());

try {
Simple_v1.discardUnknownFields(s1);
}
catch (ex) {
test.end("discardUnknownFields() exception: " + ex);
return;
}

var restored = Simple_v2.decode(Simple_v1.encode(s1).finish());

test.equal(0, restored.unknownFlags, "are removed from the message");
test.end();
});

tape.test("multiple unknown fields", function (test) {
var proto = "message Simple_v1 {\
optional string knownName = 1;\
optional string knownValue = 3;\
}\
message Simple_v2 {\
optional string knownName = 1;\
optional int32 unknownFlags = 2;\
optional string knownValue = 3;\
optional int32 unknownOptions = 4;\
}";

var root = protobuf.parse(proto).root,
Simple_v1 = root.lookup("Simple_v1");
Simple_v2 = root.lookup("Simple_v2");

var s2 = Simple_v2.create({ knownName: "v2", unknownFlags: 2, knownValue: "dummy", unknownOptions: 3 });
var s1 = Simple_v1.decode(Simple_v2.encode(s2).finish());

var restored = Simple_v2.decode(Simple_v1.encode(s1).finish());

test.equal(2, restored.unknownFlags, "(2) are preserved by default");
test.equal(3, restored.unknownOptions, "(4) are preserved by default");
test.end();
});

tape.test("nested unknown fields", function (test) {
var nproto = proto + "\
message Container_v1 {\
optional Simple_v1 elem = 1;\
}\
message Container_v2 {\
optional Simple_v2 elem = 1;\
}";

var root = protobuf.parse(nproto).root,
Container_v1 = root.lookup("Container_v1");
Container_v2 = root.lookup("Container_v2");

var c2 = Container_v2.create({ elem: { knownName: "v2", unknownFlags: 2, knownValue: "dummy" } });
var c1 = Container_v1.decode(Container_v2.encode(c2).finish());

var restored = Container_v2.decode(Container_v1.encode(c1).finish());

test.equal(c2.elem.knownName, restored.elem.knownName, "assert: even known fields are missing");

test.equal(2, restored.elem.unknownFlags, "are preserved by default");
test.end();
});

tape.test("multibyte-id unknown fields", function (test) {
var proto = "message Simple_v1 {\
optional string knownName = 1;\
optional string knownValue = 3;\
}\
message Simple_v2 {\
optional string knownName = 1;\
optional int32 unknownFlags = 296;\
optional string knownValue = 3;\
}";

var root = protobuf.parse(proto).root,
Simple_v1 = root.lookup("Simple_v1");
Simple_v2 = root.lookup("Simple_v2");

var s2 = Simple_v2.create({ knownName: "v2", unknownFlags: 2, knownValue: "dummy", unknownOptions: 3 });
var s1 = Simple_v1.decode(Simple_v2.encode(s2).finish());

var restored = Simple_v2.decode(Simple_v1.encode(s1).finish());

test.equal(2, restored.unknownFlags, "are preserved by default");
test.end();
});