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 c044688
Show file tree
Hide file tree
Showing 5 changed files with 199 additions and 6 deletions.
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

0 comments on commit c044688

Please sign in to comment.