121 lines
2.9 KiB
JavaScript
121 lines
2.9 KiB
JavaScript
import utf8 from 'utf-8'
|
|
import ID from '../Util/ID.js'
|
|
import { default as RootID, RootFakeUserID } from '../Util/RootID.js'
|
|
|
|
export default class BinaryDecoder {
|
|
constructor (buffer) {
|
|
if (buffer instanceof ArrayBuffer) {
|
|
this.uint8arr = new Uint8Array(buffer)
|
|
} else if (buffer instanceof Uint8Array || (typeof Buffer !== 'undefined' && buffer instanceof Buffer)) {
|
|
this.uint8arr = buffer
|
|
} else {
|
|
throw new Error('Expected an ArrayBuffer or Uint8Array!')
|
|
}
|
|
this.pos = 0
|
|
}
|
|
/**
|
|
* Clone this decoder instance
|
|
* Optionally set a new position parameter
|
|
*/
|
|
clone (newPos = this.pos) {
|
|
let decoder = new BinaryDecoder(this.uint8arr)
|
|
decoder.pos = newPos
|
|
return decoder
|
|
}
|
|
/**
|
|
* Number of bytes
|
|
*/
|
|
get length () {
|
|
return this.uint8arr.length
|
|
}
|
|
/**
|
|
* Skip one byte, jump to the next position
|
|
*/
|
|
skip8 () {
|
|
this.pos++
|
|
}
|
|
/**
|
|
* Read one byte as unsigned integer
|
|
*/
|
|
readUint8 () {
|
|
return this.uint8arr[this.pos++]
|
|
}
|
|
/**
|
|
* Read 4 bytes as unsigned integer
|
|
*/
|
|
readUint32 () {
|
|
let uint =
|
|
this.uint8arr[this.pos] +
|
|
(this.uint8arr[this.pos + 1] << 8) +
|
|
(this.uint8arr[this.pos + 2] << 16) +
|
|
(this.uint8arr[this.pos + 3] << 24)
|
|
this.pos += 4
|
|
return uint
|
|
}
|
|
/**
|
|
* Look ahead without incrementing position
|
|
* to the next byte and read it as unsigned integer
|
|
*/
|
|
peekUint8 () {
|
|
return this.uint8arr[this.pos]
|
|
}
|
|
/**
|
|
* Read unsigned integer (32bit) with variable length
|
|
* 1/8th of the storage is used as encoding overhead
|
|
* - numbers < 2^7 is stored in one byte
|
|
* - numbers < 2^14 is stored in two bytes
|
|
* ..
|
|
*/
|
|
readVarUint () {
|
|
let num = 0
|
|
let len = 0
|
|
while (true) {
|
|
let r = this.uint8arr[this.pos++]
|
|
num = num | ((r & 0b1111111) << len)
|
|
len += 7
|
|
if (r < 1 << 7) {
|
|
return num >>> 0 // return unsigned number!
|
|
}
|
|
if (len > 35) {
|
|
throw new Error('Integer out of range!')
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Read string of variable length
|
|
* - varUint is used to store the length of the string
|
|
*/
|
|
readVarString () {
|
|
let len = this.readVarUint()
|
|
let bytes = new Array(len)
|
|
for (let i = 0; i < len; i++) {
|
|
bytes[i] = this.uint8arr[this.pos++]
|
|
}
|
|
return utf8.getStringFromBytes(bytes)
|
|
}
|
|
/**
|
|
* Look ahead and read varString without incrementing position
|
|
*/
|
|
peekVarString () {
|
|
let pos = this.pos
|
|
let s = this.readVarString()
|
|
this.pos = pos
|
|
return s
|
|
}
|
|
/**
|
|
* Read ID
|
|
* - If first varUint read is 0xFFFFFF a RootID is returned
|
|
* - Otherwise an ID is returned
|
|
*/
|
|
readID () {
|
|
let user = this.readVarUint()
|
|
if (user === RootFakeUserID) {
|
|
// read property name and type id
|
|
const rid = new RootID(this.readVarString(), null)
|
|
rid.type = this.readVarUint()
|
|
return rid
|
|
}
|
|
return new ID(user, this.readVarUint())
|
|
}
|
|
}
|