160 lines
3.6 KiB
JavaScript
160 lines
3.6 KiB
JavaScript
import ID from '../ID/ID.mjs'
|
|
import { default as RootID, RootFakeUserID } from '../ID/RootID.mjs'
|
|
|
|
/**
|
|
* A BinaryDecoder handles the decoding of an ArrayBuffer.
|
|
*/
|
|
export default class BinaryDecoder {
|
|
/**
|
|
* @param {Uint8Array|Buffer} buffer The binary data that this instance
|
|
* decodes.
|
|
*/
|
|
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
|
|
}
|
|
|
|
hasContent () {
|
|
return this.pos !== this.uint8arr.length
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* @return {number} An 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.
|
|
*
|
|
* @return {number} An 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.
|
|
*
|
|
* @return {number} An unsigned integer.
|
|
*/
|
|
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
|
|
*
|
|
* @return {String} The read String.
|
|
*/
|
|
readVarString () {
|
|
let len = this.readVarUint()
|
|
let encodedString = ''
|
|
//let bytes = new Array(len)
|
|
for (let i = 0; i < len; i++) {
|
|
//bytes[i] = this.uint8arr[this.pos++]
|
|
// encodedString += String.fromCodePoint(this.uint8arr[this.pos++])
|
|
encodedString += String(this.uint8arr[this.pos++])
|
|
}
|
|
//let encodedString = String.fromCodePoint.apply(null, bytes)
|
|
//return decodeURIComponent(escape(encodedString))
|
|
return encodedString
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* @return ID
|
|
*/
|
|
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())
|
|
}
|
|
}
|