Skip to content

Commit

Permalink
very basic implementation of protobuf decoding
Browse files Browse the repository at this point in the history
  • Loading branch information
superkartoffel committed Apr 2, 2021
1 parent 9cdfa2d commit eab619d
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 6 deletions.
162 changes: 162 additions & 0 deletions app/src/components/Sidebar/ValueRenderer/Protobuf.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
interface Field {
key: number,
value: any
}

class Protobuf {
TYPE: number;
NUMBER: number;
MSB: number;
VALUE: number;
offset: number;
LENGTH: number;
data: (Int8Array | Uint8Array);

constructor(data: (Int8Array | Uint8Array)) {
this.data = data;

// Set up masks
this.TYPE = 0x07;
this.NUMBER = 0x78;
this.MSB = 0x80;
this.VALUE = 0x7f;

// Declare offset and length
this.offset = 0;
this.LENGTH = data.length;
}


static decode(input: (Int8Array | Uint8Array)) {
const pb = new Protobuf(input);
return pb._parse();
}

_parse() {
let object = {};
// Continue reading whilst we still have data
while (this.offset < this.LENGTH) {
const field = this._parseField();
object = this._addField(field, object);
}
// Throw an error if we have gone beyond the end of the data
if (this.offset > this.LENGTH) {
throw new Error("Exhausted Buffer");
}
return object;
}

_addField(field: Field, object: any) {
// Get the field key/values
const key = field.key;
const value = field.value;
object[key] = Object.prototype.hasOwnProperty.call(object, key) ?
object[key] instanceof Array ?
object[key].concat([value]) :
[object[key], value] :
value;
return object;
}

_parseField() {
// Get the field headers
const header = this._fieldHeader();
const type = header.type;
const key = header.key;
switch (type) {
// varint
case 0:
return { "key": key, "value": this._varInt() };
// fixed 64
case 1:
return { "key": key, "value": this._uint64() };
// length delimited
case 2:
return { "key": key, "value": this._lenDelim() };
// fixed 32
case 5:
return { "key": key, "value": this._uint32() };
// unknown type
default:
throw new Error("Unknown type 0x" + type.toString(16));
}
}

_fieldHeader() {
// Make sure we call type then number to preserve offset
return { "type": this._fieldType(), "key": this._fieldNumber() };
}

_fieldType() {
// Field type stored in lower 3 bits of tag byte
return this.data[this.offset] & this.TYPE;
}

_fieldNumber() {
let shift = -3;
let fieldNumber = 0;
do {
fieldNumber += shift < 28 ?
shift === -3 ?
(this.data[this.offset] & this.NUMBER) >> -shift :
(this.data[this.offset] & this.VALUE) << shift :
(this.data[this.offset] & this.VALUE) * Math.pow(2, shift);
shift += 7;
} while ((this.data[this.offset++] & this.MSB) === this.MSB);
return fieldNumber;
}

_varInt() {
let value = 0;
let shift = 0;
// Keep reading while upper bit set
do {
value += shift < 28 ?
(this.data[this.offset] & this.VALUE) << shift :
(this.data[this.offset] & this.VALUE) * Math.pow(2, shift);
shift += 7;
} while ((this.data[this.offset++] & this.MSB) === this.MSB);
return value;
}
_uint64() {
// Read off a Uint64
let num = this.data[this.offset++] * 0x1000000 + (this.data[this.offset++] << 16) + (this.data[this.offset++] << 8) + this.data[this.offset++];
num = num * 0x100000000 + this.data[this.offset++] * 0x1000000 + (this.data[this.offset++] << 16) + (this.data[this.offset++] << 8) + this.data[this.offset++];
return num;
}
_lenDelim() {
// Read off the field length
const length = this._varInt();
const fieldBytes = this.data.slice(this.offset, this.offset + length);
let field;
try {
// Attempt to parse as a new Protobuf Object
const pbObject = new Protobuf(fieldBytes);
field = pbObject._parse();
} catch (err) {
// Otherwise treat as bytes
field = this._byteArrayToChars(fieldBytes);
}
// Move the offset and return the field
this.offset += length;
return field;
}
_uint32() {
// Use a dataview to read off the integer
const dataview = new DataView(new Uint8Array(this.data.slice(this.offset, this.offset + 4)).buffer);
const value = dataview.getUint32(0);
this.offset += 4;
return value;
}
_byteArrayToChars(byteArray: (Int8Array | Uint8Array)) {
if (!byteArray) return "";
let str = "";
// String concatenation appears to be faster than an array join
for (let i = 0; i < byteArray.length;) {
str += String.fromCharCode(byteArray[i++]);
}
return str;
}
}

export default Protobuf;
14 changes: 14 additions & 0 deletions app/src/components/Sidebar/ValueRenderer/ValueRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { connect } from 'react-redux'
import { default as ReactResizeDetector } from 'react-resize-detector'
import { ValueRendererDisplayMode } from '../../../reducers/Settings'
import { Typography, Fade, Grow } from '@material-ui/core'
import Protobuf from './Protobuf';

interface Props {
message: q.Message
Expand Down Expand Up @@ -43,6 +44,19 @@ class ValueRenderer extends React.Component<Props, State> {
return [undefined, undefined]
}

let obj = {}
try {
const byteArray = Base64Message.ToByteArray(msg);
obj = Protobuf.decode(byteArray);
}
catch (e) {
console.log("Caught exception while decoding protobuf: ", e);
}

if (obj) {
return [JSON.stringify(obj, undefined, ' '), 'json'];
}

const str = Base64Message.toUnicodeString(msg)
try {
JSON.parse(str)
Expand Down
8 changes: 8 additions & 0 deletions backend/src/Model/Base64Message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,18 @@ export class Base64Message {
this.length = base64Str.length
}

public static toBase64(message: Base64Message) {
return message.base64Message || ''
}

public static toUnicodeString(message: Base64Message) {
return message.unicodeValue || ''
}

public static ToByteArray(message: Base64Message): Uint8Array {
return Base64.toUint8Array(message.base64Message);
}

public static fromBuffer(buffer: Buffer) {
return new Base64Message(buffer.toString('base64'))
}
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@
"electron-telemetry": "git+https://github.com/thomasnordquist/electron-telemetry.git#dist",
"electron-updater": "^4.0.6",
"fs-extra": "9",
"js-base64": "^2.5.1",
"js-base64": "^2.6.3",
"json-to-ast": "^2.1.0",
"lowdb": "^1.0.0",
"mime": "^2.4.4",
Expand All @@ -119,4 +119,4 @@
"yarn-run-all": "^3.1.1"
},
"optionalDependencies": {}
}
}
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3026,10 +3026,10 @@ jake@^10.6.1:
filelist "^1.0.1"
minimatch "^3.0.4"

js-base64@^2.5.1:
version "2.5.2"
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.2.tgz#313b6274dda718f714d00b3330bbae6e38e90209"
integrity sha512-Vg8czh0Q7sFBSUMWWArX/miJeBWYBPpdU/3M/DKSaekLMqrqVPaedp+5mZhie/r0lgrcaYBfwXatEew6gwgiQQ==
js-base64@^2.6.3:
version "2.6.4"
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4"
integrity sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==

js-tokens@^4.0.0:
version "4.0.0"
Expand Down

0 comments on commit eab619d

Please sign in to comment.