diff --git a/src/core/lib/Protobuf.mjs b/src/core/lib/Protobuf.mjs index 0cdf41f2..049f07c8 100644 --- a/src/core/lib/Protobuf.mjs +++ b/src/core/lib/Protobuf.mjs @@ -17,8 +17,9 @@ class Protobuf { * Protobuf constructor * * @param {byteArray|Uint8Array} data + * @param {boolean} parseFixedAsFloats */ - constructor(data) { + constructor(data, parseFixedAsFloats=false) { // Check we have a byteArray or Uint8Array if (data instanceof Array || data instanceof Uint8Array) { this.data = data; @@ -26,6 +27,9 @@ class Protobuf { throw new Error("Protobuf input must be a byteArray or Uint8Array"); } + // Unpack config + this.parseFixedAsFloats = parseFixedAsFloats; + // Set up masks this.TYPE = 0x07; this.NUMBER = 0x78; @@ -80,10 +84,11 @@ class Protobuf { * Parse Protobuf data * * @param {byteArray} input + * @param {boolean} parseFixedAsFloats * @returns {Object} */ - static decode(input) { - const pb = new Protobuf(input); + static decode(input, parseFixedAsFloats=false) { + const pb = new Protobuf(input, parseFixedAsFloats); return pb._parse(); } @@ -149,13 +154,13 @@ class Protobuf { return { "key": key, "value": this._varInt() }; // fixed 64 case 1: - return { "key": key, "value": this._uint64() }; + return { "key": key, "value": this.parseFixedAsFloats ? this._float64() : this._uint64() }; // length delimited case 2: return { "key": key, "value": this._lenDelim() }; // fixed 32 case 5: - return { "key": key, "value": this._uint32() }; + return { "key": key, "value": this.parseFixedAsFloats ? this._float32() : this._uint32() }; // unknown type default: throw new Error("Unknown type 0x" + type.toString(16)); @@ -256,7 +261,7 @@ class Protobuf { let field; try { // Attempt to parse as a new Protobuf Object - const pbObject = new Protobuf(fieldBytes); + const pbObject = new Protobuf(fieldBytes, this.parseFixedAsFloats); field = pbObject._parse(); } catch (err) { // Otherwise treat as bytes @@ -280,6 +285,34 @@ class Protobuf { this.offset += 4; return value; } + + /** + * Read a 32 bit floating point from the data + * + * @private + * @returns {number} + */ + _float32() { + const sizeInBytes = 4; + const dataview = new DataView(new Uint8Array(this.data.slice(this.offset, this.offset + sizeInBytes)).buffer); + const value = dataview.getFloat32(0, true); + this.offset += sizeInBytes; + return value; + } + + /** + * Read a 64 bit floating point from the data + * + * @private + * @returns {number} + */ + _float64() { + const sizeInBytes = 8; + const dataview = new DataView(new Uint8Array(this.data.slice(this.offset, this.offset + sizeInBytes)).buffer); + const value = dataview.getFloat64(0, true); + this.offset += sizeInBytes; + return value; + } } export default Protobuf; diff --git a/src/core/operations/ProtobufDecode.mjs b/src/core/operations/ProtobufDecode.mjs index 8470bdb7..7623099e 100644 --- a/src/core/operations/ProtobufDecode.mjs +++ b/src/core/operations/ProtobufDecode.mjs @@ -25,7 +25,13 @@ class ProtobufDecode extends Operation { this.infoURL = "https://wikipedia.org/wiki/Protocol_Buffers"; this.inputType = "ArrayBuffer"; this.outputType = "JSON"; - this.args = []; + this.args = [ + { + "name": "Decode non-varints as floats", + "type": "boolean", + "value": false + } + ]; } /** @@ -36,7 +42,7 @@ class ProtobufDecode extends Operation { run(input, args) { input = new Uint8Array(input); try { - return Protobuf.decode(input); + return Protobuf.decode(input, args[0]); } catch (err) { throw new OperationError(err); } diff --git a/tests/operations/tests/Protobuf.mjs b/tests/operations/tests/Protobuf.mjs index 0bdd6b19..77ada849 100644 --- a/tests/operations/tests/Protobuf.mjs +++ b/tests/operations/tests/Protobuf.mjs @@ -33,4 +33,106 @@ TestRegister.addTests([ } ] }, + /** + * Input generated from: + * ``` + $ cat test.proto + syntax = "proto3"; + + message Test { + float a = 1; + } + + $ protoc --version + libprotoc 3.11.1 + + $ echo a:1 | protoc --encode=Test test.proto | xxd -p + 0d0000803f + ``` + */ + { + name: "Protobuf Decode - parse fixed32 as integer", + input: "0d0000803f", + expectedOutput: JSON.stringify({ + "1": 32831, + }, null, 4), + recipeConfig: [ + { + "op": "From Hex", + "args": ["Auto"] + }, + { + "op": "Protobuf Decode", + "args": [false] + } + ] + }, + { + name: "Protobuf Decode - parse fixed32 as float32", + input: "0d0000803f", + expectedOutput: JSON.stringify({ + "1": 1, + }, null, 4), + recipeConfig: [ + { + "op": "From Hex", + "args": ["Auto"] + }, + { + "op": "Protobuf Decode", + "args": [true] + } + ] + }, + /** + * Input generated from: + * ``` + $ cat test.proto + syntax = "proto3"; + + message Test { + double a = 1; + } + + $ protoc --version + libprotoc 3.11.1 + + $ echo a:1 | protoc --encode=Test test.proto | xxd -p + 09000000000000f03f + ``` + */ + { + name: "Protobuf Decode - parse fixed64 as integer", + input: "09000000000000f03f", + expectedOutput: JSON.stringify({ + "1": 61503, + }, null, 4), + recipeConfig: [ + { + "op": "From Hex", + "args": ["Auto"] + }, + { + "op": "Protobuf Decode", + "args": [false] + } + ] + }, + { + name: "Protobuf Decode - parse fixed64 as float64", + input: "09000000000000f03f", + expectedOutput: JSON.stringify({ + "1": 1, + }, null, 4), + recipeConfig: [ + { + "op": "From Hex", + "args": ["Auto"] + }, + { + "op": "Protobuf Decode", + "args": [true] + } + ] + } ]);