Add option to parse non-varint Protobuf fields as floats

This commit is contained in:
Chris Pinola 2020-09-14 13:26:45 -04:00
parent c9d9730726
commit 6d2cacc30c
3 changed files with 149 additions and 8 deletions

View file

@ -17,8 +17,9 @@ class Protobuf {
* Protobuf constructor * Protobuf constructor
* *
* @param {byteArray|Uint8Array} data * @param {byteArray|Uint8Array} data
* @param {boolean} parseFixedAsFloats
*/ */
constructor(data) { constructor(data, parseFixedAsFloats=false) {
// Check we have a byteArray or Uint8Array // Check we have a byteArray or Uint8Array
if (data instanceof Array || data instanceof Uint8Array) { if (data instanceof Array || data instanceof Uint8Array) {
this.data = data; this.data = data;
@ -26,6 +27,9 @@ class Protobuf {
throw new Error("Protobuf input must be a byteArray or Uint8Array"); throw new Error("Protobuf input must be a byteArray or Uint8Array");
} }
// Unpack config
this.parseFixedAsFloats = parseFixedAsFloats;
// Set up masks // Set up masks
this.TYPE = 0x07; this.TYPE = 0x07;
this.NUMBER = 0x78; this.NUMBER = 0x78;
@ -80,10 +84,11 @@ class Protobuf {
* Parse Protobuf data * Parse Protobuf data
* *
* @param {byteArray} input * @param {byteArray} input
* @param {boolean} parseFixedAsFloats
* @returns {Object} * @returns {Object}
*/ */
static decode(input) { static decode(input, parseFixedAsFloats=false) {
const pb = new Protobuf(input); const pb = new Protobuf(input, parseFixedAsFloats);
return pb._parse(); return pb._parse();
} }
@ -149,13 +154,13 @@ class Protobuf {
return { "key": key, "value": this._varInt() }; return { "key": key, "value": this._varInt() };
// fixed 64 // fixed 64
case 1: case 1:
return { "key": key, "value": this._uint64() }; return { "key": key, "value": this.parseFixedAsFloats ? this._float64() : this._uint64() };
// length delimited // length delimited
case 2: case 2:
return { "key": key, "value": this._lenDelim() }; return { "key": key, "value": this._lenDelim() };
// fixed 32 // fixed 32
case 5: case 5:
return { "key": key, "value": this._uint32() }; return { "key": key, "value": this.parseFixedAsFloats ? this._float32() : this._uint32() };
// unknown type // unknown type
default: default:
throw new Error("Unknown type 0x" + type.toString(16)); throw new Error("Unknown type 0x" + type.toString(16));
@ -256,7 +261,7 @@ class Protobuf {
let field; let field;
try { try {
// Attempt to parse as a new Protobuf Object // Attempt to parse as a new Protobuf Object
const pbObject = new Protobuf(fieldBytes); const pbObject = new Protobuf(fieldBytes, this.parseFixedAsFloats);
field = pbObject._parse(); field = pbObject._parse();
} catch (err) { } catch (err) {
// Otherwise treat as bytes // Otherwise treat as bytes
@ -280,6 +285,34 @@ class Protobuf {
this.offset += 4; this.offset += 4;
return value; 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; export default Protobuf;

View file

@ -25,7 +25,13 @@ class ProtobufDecode extends Operation {
this.infoURL = "https://wikipedia.org/wiki/Protocol_Buffers"; this.infoURL = "https://wikipedia.org/wiki/Protocol_Buffers";
this.inputType = "ArrayBuffer"; this.inputType = "ArrayBuffer";
this.outputType = "JSON"; 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) { run(input, args) {
input = new Uint8Array(input); input = new Uint8Array(input);
try { try {
return Protobuf.decode(input); return Protobuf.decode(input, args[0]);
} catch (err) { } catch (err) {
throw new OperationError(err); throw new OperationError(err);
} }

View file

@ -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]
}
]
}
]); ]);