yjs/src/Util/Binary/Decoder.mjs
2018-06-02 13:33:04 +02:00

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())
}
}