Skip to content
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

very basic implementation of protobuf decoding #532

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
172 changes: 172 additions & 0 deletions app/src/components/Sidebar/ValueRenderer/Protobuf.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
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
13 changes: 13 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,18 @@ 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