|
|
|
"use strict"
|
|
|
|
var decoder
|
|
|
|
try {
|
|
|
|
decoder = new TextDecoder()
|
|
|
|
} catch(error) {}
|
|
|
|
var src
|
|
|
|
var srcEnd
|
|
|
|
var position = 0
|
|
|
|
var alreadySet
|
|
|
|
const EMPTY_ARRAY = []
|
|
|
|
var strings = EMPTY_ARRAY
|
|
|
|
var stringPosition = 0
|
|
|
|
var currentUnpackr = {}
|
|
|
|
var currentStructures
|
|
|
|
var srcString
|
|
|
|
var srcStringStart = 0
|
|
|
|
var srcStringEnd = 0
|
|
|
|
var referenceMap
|
|
|
|
var currentExtensions = []
|
|
|
|
var dataView
|
|
|
|
var defaultOptions = {
|
|
|
|
useRecords: false,
|
|
|
|
mapsAsObjects: true
|
|
|
|
}
|
|
|
|
export class C1Type {}
|
|
|
|
export const C1 = new C1Type()
|
|
|
|
C1.name = 'MessagePack 0xC1'
|
|
|
|
var sequentialMode = false
|
|
|
|
|
|
|
|
export class Unpackr {
|
|
|
|
constructor(options) {
|
|
|
|
if (options) {
|
|
|
|
if (options.useRecords === false && options.mapsAsObjects === undefined)
|
|
|
|
options.mapsAsObjects = true
|
|
|
|
if (options.structures)
|
|
|
|
options.structures.sharedLength = options.structures.length
|
|
|
|
else if (options.getStructures) {
|
|
|
|
(options.structures = []).uninitialized = true // this is what we use to denote an uninitialized structures
|
|
|
|
options.structures.sharedLength = 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Object.assign(this, options)
|
|
|
|
}
|
|
|
|
unpack(source, end) {
|
|
|
|
if (src) {
|
|
|
|
// re-entrant execution, save the state and restore it after we do this unpack
|
|
|
|
return saveState(() => {
|
|
|
|
clearSource()
|
|
|
|
return this ? this.unpack(source, end) : Unpackr.prototype.unpack.call(defaultOptions, source, end)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
srcEnd = end > -1 ? end : source.length
|
|
|
|
position = 0
|
|
|
|
stringPosition = 0
|
|
|
|
srcStringEnd = 0
|
|
|
|
srcString = null
|
|
|
|
strings = EMPTY_ARRAY
|
|
|
|
src = source
|
|
|
|
// this provides cached access to the data view for a buffer if it is getting reused, which is a recommend
|
|
|
|
// technique for getting data from a database where it can be copied into an existing buffer instead of creating
|
|
|
|
// new ones
|
|
|
|
dataView = source.dataView || (source.dataView = new DataView(source.buffer, source.byteOffset, source.byteLength))
|
|
|
|
if (this) {
|
|
|
|
currentUnpackr = this
|
|
|
|
if (this.structures) {
|
|
|
|
currentStructures = this.structures
|
|
|
|
return checkedRead()
|
|
|
|
} else if (!currentStructures || currentStructures.length > 0) {
|
|
|
|
currentStructures = []
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
currentUnpackr = defaultOptions
|
|
|
|
if (!currentStructures || currentStructures.length > 0)
|
|
|
|
currentStructures = []
|
|
|
|
}
|
|
|
|
return checkedRead()
|
|
|
|
}
|
|
|
|
unpackMultiple(source, forEach) {
|
|
|
|
let values, lastPosition = 0
|
|
|
|
try {
|
|
|
|
sequentialMode = true
|
|
|
|
let size = source.length
|
|
|
|
let value = this ? this.unpack(source, size) : defaultUnpackr.unpack(source, size)
|
|
|
|
if (forEach) {
|
|
|
|
forEach(value)
|
|
|
|
while(position < size) {
|
|
|
|
lastPosition = position
|
|
|
|
if (forEach(checkedRead()) === false) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
values = [ value ]
|
|
|
|
while(position < size) {
|
|
|
|
lastPosition = position
|
|
|
|
values.push(checkedRead())
|
|
|
|
}
|
|
|
|
return values
|
|
|
|
}
|
|
|
|
} catch(error) {
|
|
|
|
error.lastPosition = lastPosition
|
|
|
|
error.values = values
|
|
|
|
throw error
|
|
|
|
} finally {
|
|
|
|
sequentialMode = false
|
|
|
|
clearSource()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_mergeStructures(loadedStructures, existingStructures) {
|
|
|
|
loadedStructures = loadedStructures || []
|
|
|
|
for (let i = 0, l = loadedStructures.length; i < l; i++) {
|
|
|
|
let structure = loadedStructures[i]
|
|
|
|
if (structure) {
|
|
|
|
structure.isShared = true
|
|
|
|
if (i >= 32)
|
|
|
|
structure.highByte = (i - 32) >> 5
|
|
|
|
}
|
|
|
|
}
|
|
|
|
loadedStructures.sharedLength = loadedStructures.length
|
|
|
|
for (let id in existingStructures || []) {
|
|
|
|
if (id >= 0) {
|
|
|
|
let structure = loadedStructures[id]
|
|
|
|
let existing = existingStructures[id]
|
|
|
|
if (existing) {
|
|
|
|
if (structure)
|
|
|
|
(loadedStructures.restoreStructures || (loadedStructures.restoreStructures = []))[id] = structure
|
|
|
|
loadedStructures[id] = existing
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return this.structures = loadedStructures
|
|
|
|
}
|
|
|
|
decode(source, end) {
|
|
|
|
return this.unpack(source, end)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
export function getPosition() {
|
|
|
|
return position
|
|
|
|
}
|
|
|
|
export function checkedRead() {
|
|
|
|
try {
|
|
|
|
if (!currentUnpackr.trusted && !sequentialMode) {
|
|
|
|
let sharedLength = currentStructures.sharedLength || 0
|
|
|
|
if (sharedLength < currentStructures.length)
|
|
|
|
currentStructures.length = sharedLength
|
|
|
|
}
|
|
|
|
let result = read()
|
|
|
|
if (position == srcEnd) {
|
|
|
|
// finished reading this source, cleanup references
|
|
|
|
if (currentStructures.restoreStructures)
|
|
|
|
restoreStructures()
|
|
|
|
currentStructures = null
|
|
|
|
src = null
|
|
|
|
if (referenceMap)
|
|
|
|
referenceMap = null
|
|
|
|
} else if (position > srcEnd) {
|
|
|
|
// over read
|
|
|
|
let error = new Error('Unexpected end of MessagePack data')
|
|
|
|
error.incomplete = true
|
|
|
|
throw error
|
|
|
|
} else if (!sequentialMode) {
|
|
|
|
throw new Error('Data read, but end of buffer not reached')
|
|
|
|
}
|
|
|
|
// else more to read, but we are reading sequentially, so don't clear source yet
|
|
|
|
return result
|
|
|
|
} catch(error) {
|
|
|
|
if (currentStructures.restoreStructures)
|
|
|
|
restoreStructures()
|
|
|
|
clearSource()
|
|
|
|
if (error instanceof RangeError || error.message.startsWith('Unexpected end of buffer')) {
|
|
|
|
error.incomplete = true
|
|
|
|
}
|
|
|
|
throw error
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function restoreStructures() {
|
|
|
|
for (let id in currentStructures.restoreStructures) {
|
|
|
|
currentStructures[id] = currentStructures.restoreStructures[id]
|
|
|
|
}
|
|
|
|
currentStructures.restoreStructures = null
|
|
|
|
}
|
|
|
|
|
|
|
|
export function read() {
|
|
|
|
let token = src[position++]
|
|
|
|
if (token < 0xa0) {
|
|
|
|
if (token < 0x80) {
|
|
|
|
if (token < 0x40)
|
|
|
|
return token
|
|
|
|
else {
|
|
|
|
let structure = currentStructures[token & 0x3f] ||
|
|
|
|
currentUnpackr.getStructures && loadStructures()[token & 0x3f]
|
|
|
|
if (structure) {
|
|
|
|
if (!structure.read) {
|
|
|
|
structure.read = createStructureReader(structure, token & 0x3f)
|
|
|
|
}
|
|
|
|
return structure.read()
|
|
|
|
} else
|
|
|
|
return token
|
|
|
|
}
|
|
|
|
} else if (token < 0x90) {
|
|
|
|
// map
|
|
|
|
token -= 0x80
|
|
|
|
if (currentUnpackr.mapsAsObjects) {
|
|
|
|
let object = {}
|
|
|
|
for (let i = 0; i < token; i++) {
|
|
|
|
object[readKey()] = read()
|
|
|
|
}
|
|
|
|
return object
|
|
|
|
} else {
|
|
|
|
let map = new Map()
|
|
|
|
for (let i = 0; i < token; i++) {
|
|
|
|
map.set(read(), read())
|
|
|
|
}
|
|
|
|
return map
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
token -= 0x90
|
|
|
|
let array = new Array(token)
|
|
|
|
for (let i = 0; i < token; i++) {
|
|
|
|
array[i] = read()
|
|
|
|
}
|
|
|
|
return array
|
|
|
|
}
|
|
|
|
} else if (token < 0xc0) {
|
|
|
|
// fixstr
|
|
|
|
let length = token - 0xa0
|
|
|
|
if (srcStringEnd >= position) {
|
|
|
|
return srcString.slice(position - srcStringStart, (position += length) - srcStringStart)
|
|
|
|
}
|
|
|
|
if (srcStringEnd == 0 && srcEnd < 140) {
|
|
|
|
// for small blocks, avoiding the overhead of the extract call is helpful
|
|
|
|
let string = length < 16 ? shortStringInJS(length) : longStringInJS(length)
|
|
|
|
if (string != null)
|
|
|
|
return string
|
|
|
|
}
|
|
|
|
return readFixedString(length)
|
|
|
|
} else {
|
|
|
|
let value
|
|
|
|
switch (token) {
|
|
|
|
case 0xc0: return null
|
|
|
|
case 0xc1: return C1; // "never-used", return special object to denote that
|
|
|
|
case 0xc2: return false
|
|
|
|
case 0xc3: return true
|
|
|
|
case 0xc4:
|
|
|
|
// bin 8
|
|
|
|
return readBin(src[position++])
|
|
|
|
case 0xc5:
|
|
|
|
// bin 16
|
|
|
|
value = dataView.getUint16(position)
|
|
|
|
position += 2
|
|
|
|
return readBin(value)
|
|
|
|
case 0xc6:
|
|
|
|
// bin 32
|
|
|
|
value = dataView.getUint32(position)
|
|
|
|
position += 4
|
|
|
|
return readBin(value)
|
|
|
|
case 0xc7:
|
|
|
|
// ext 8
|
|
|
|
return readExt(src[position++])
|
|
|
|
case 0xc8:
|
|
|
|
// ext 16
|
|
|
|
value = dataView.getUint16(position)
|
|
|
|
position += 2
|
|
|
|
return readExt(value)
|
|
|
|
case 0xc9:
|
|
|
|
// ext 32
|
|
|
|
value = dataView.getUint32(position)
|
|
|
|
position += 4
|
|
|
|
return readExt(value)
|
|
|
|
case 0xca:
|
|
|
|
value = dataView.getFloat32(position)
|
|
|
|
if (currentUnpackr.useFloat32 > 2) {
|
|
|
|
// this does rounding of numbers that were encoded in 32-bit float to nearest significant decimal digit that could be preserved
|
|
|
|
let multiplier = mult10[((src[position] & 0x7f) << 1) | (src[position + 1] >> 7)]
|
|
|
|
position += 4
|
|
|
|
return ((multiplier * value + (value > 0 ? 0.5 : -0.5)) >> 0) / multiplier
|
|
|
|
}
|
|
|
|
position += 4
|
|
|
|
return value
|
|
|
|
case 0xcb:
|
|
|
|
value = dataView.getFloat64(position)
|
|
|
|
position += 8
|
|
|
|
return value
|
|
|
|
// uint handlers
|
|
|
|
case 0xcc:
|
|
|
|
return src[position++]
|
|
|
|
case 0xcd:
|
|
|
|
value = dataView.getUint16(position)
|
|
|
|
position += 2
|
|
|
|
return value
|
|
|
|
case 0xce:
|
|
|
|
value = dataView.getUint32(position)
|
|
|
|
position += 4
|
|
|
|
return value
|
|
|
|
case 0xcf:
|
|
|
|
if (currentUnpackr.uint64AsNumber)
|
|
|
|
return src[position++] * 0x100000000000000 + src[position++] * 0x1000000000000 + src[position++] * 0x10000000000 + src[position++] * 0x100000000 +
|
|
|
|
src[position++] * 0x1000000 + (src[position++] << 16) + (src[position++] << 8) + src[position++]
|
|
|
|
value = dataView.getBigUint64(position)
|
|
|
|
position += 8
|
|
|
|
return value
|
|
|
|
|
|
|
|
// int handlers
|
|
|
|
case 0xd0:
|
|
|
|
return dataView.getInt8(position++)
|
|
|
|
case 0xd1:
|
|
|
|
value = dataView.getInt16(position)
|
|
|
|
position += 2
|
|
|
|
return value
|
|
|
|
case 0xd2:
|
|
|
|
value = dataView.getInt32(position)
|
|
|
|
position += 4
|
|
|
|
return value
|
|
|
|
case 0xd3:
|
|
|
|
value = dataView.getBigInt64(position)
|
|
|
|
position += 8
|
|
|
|
return value
|
|
|
|
|
|
|
|
case 0xd4:
|
|
|
|
// fixext 1
|
|
|
|
value = src[position++]
|
|
|
|
if (value == 0x72) {
|
|
|
|
return recordDefinition(src[position++] & 0x3f)
|
|
|
|
} else {
|
|
|
|
let extension = currentExtensions[value]
|
|
|
|
if (extension) {
|
|
|
|
if (extension.read) {
|
|
|
|
position++ // skip filler byte
|
|
|
|
return extension.read(read())
|
|
|
|
} else if (extension.noBuffer) {
|
|
|
|
position++ // skip filler byte
|
|
|
|
return extension()
|
|
|
|
} else
|
|
|
|
return extension(src.subarray(position, ++position))
|
|
|
|
} else
|
|
|
|
throw new Error('Unknown extension ' + value)
|
|
|
|
}
|
|
|
|
case 0xd5:
|
|
|
|
// fixext 2
|
|
|
|
value = src[position]
|
|
|
|
if (value == 0x72) {
|
|
|
|
position++
|
|
|
|
return recordDefinition(src[position++] & 0x3f, src[position++])
|
|
|
|
} else
|
|
|
|
return readExt(2)
|
|
|
|
case 0xd6:
|
|
|
|
// fixext 4
|
|
|
|
return readExt(4)
|
|
|
|
case 0xd7:
|
|
|
|
// fixext 8
|
|
|
|
return readExt(8)
|
|
|
|
case 0xd8:
|
|
|
|
// fixext 16
|
|
|
|
return readExt(16)
|
|
|
|
case 0xd9:
|
|
|
|
// str 8
|
|
|
|
value = src[position++]
|
|
|
|
if (srcStringEnd >= position) {
|
|
|
|
return srcString.slice(position - srcStringStart, (position += value) - srcStringStart)
|
|
|
|
}
|
|
|
|
return readString8(value)
|
|
|
|
case 0xda:
|
|
|
|
// str 16
|
|
|
|
value = dataView.getUint16(position)
|
|
|
|
position += 2
|
|
|
|
if (srcStringEnd >= position) {
|
|
|
|
return srcString.slice(position - srcStringStart, (position += value) - srcStringStart)
|
|
|
|
}
|
|
|
|
return readString16(value)
|
|
|
|
case 0xdb:
|
|
|
|
// str 32
|
|
|
|
value = dataView.getUint32(position)
|
|
|
|
position += 4
|
|
|
|
if (srcStringEnd >= position) {
|
|
|
|
return srcString.slice(position - srcStringStart, (position += value) - srcStringStart)
|
|
|
|
}
|
|
|
|
return readString32(value)
|
|
|
|
case 0xdc:
|
|
|
|
// array 16
|
|
|
|
value = dataView.getUint16(position)
|
|
|
|
position += 2
|
|
|
|
return readArray(value)
|
|
|
|
case 0xdd:
|
|
|
|
// array 32
|
|
|
|
value = dataView.getUint32(position)
|
|
|
|
position += 4
|
|
|
|
return readArray(value)
|
|
|
|
case 0xde:
|
|
|
|
// map 16
|
|
|
|
value = dataView.getUint16(position)
|
|
|
|
position += 2
|
|
|
|
return readMap(value)
|
|
|
|
case 0xdf:
|
|
|
|
// map 32
|
|
|
|
value = dataView.getUint32(position)
|
|
|
|
position += 4
|
|
|
|
return readMap(value)
|
|
|
|
default: // negative int
|
|
|
|
if (token >= 0xe0)
|
|
|
|
return token - 0x100
|
|
|
|
if (token === undefined) {
|
|
|
|
let error = new Error('Unexpected end of MessagePack data')
|
|
|
|
error.incomplete = true
|
|
|
|
throw error
|
|
|
|
}
|
|
|
|
throw new Error('Unknown MessagePack token ' + token)
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const validName = /^[a-zA-Z_$][a-zA-Z\d_$]*$/
|
|
|
|
function createStructureReader(structure, firstId) {
|
|
|
|
function readObject() {
|
|
|
|
// This initial function is quick to instantiate, but runs slower. After several iterations pay the cost to build the faster function
|
|
|
|
if (readObject.count++ > 2) {
|
|
|
|
let readObject = structure.read = (new Function('r', 'return function(){return {' + structure.map(key => validName.test(key) ? key + ':r()' : ('[' + JSON.stringify(key) + ']:r()')).join(',') + '}}'))(read)
|
|
|
|
if (structure.highByte === 0)
|
|
|
|
structure.read = createSecondByteReader(firstId, structure.read)
|
|
|
|
return readObject() // second byte is already read, if there is one so immediately read object
|
|
|
|
}
|
|
|
|
let object = {}
|
|
|
|
for (let i = 0, l = structure.length; i < l; i++) {
|
|
|
|
let key = structure[i]
|
|
|
|
object[key] = read()
|
|
|
|
}
|
|
|
|
return object
|
|
|
|
}
|
|
|
|
readObject.count = 0
|
|
|
|
if (structure.highByte === 0) {
|
|
|
|
return createSecondByteReader(firstId, readObject)
|
|
|
|
}
|
|
|
|
return readObject
|
|
|
|
}
|
|
|
|
|
|
|
|
const createSecondByteReader = (firstId, read0) => {
|
|
|
|
return function() {
|
|
|
|
let highByte = src[position++]
|
|
|
|
if (highByte === 0)
|
|
|
|
return read0()
|
|
|
|
let id = firstId < 32 ? -(firstId + (highByte << 5)) : firstId + (highByte << 5)
|
|
|
|
let structure = currentStructures[id] || loadStructures()[id]
|
|
|
|
if (!structure) {
|
|
|
|
throw new Error('Record id is not defined for ' + id)
|
|
|
|
}
|
|
|
|
if (!structure.read)
|
|
|
|
structure.read = createStructureReader(structure, firstId)
|
|
|
|
return structure.read()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function loadStructures() {
|
|
|
|
let loadedStructures = saveState(() => {
|
|
|
|
// save the state in case getStructures modifies our buffer
|
|
|
|
src = null
|
|
|
|
return currentUnpackr.getStructures()
|
|
|
|
})
|
|
|
|
return currentStructures = currentUnpackr._mergeStructures(loadedStructures, currentStructures)
|
|
|
|
}
|
|
|
|
|
|
|
|
var readFixedString = readStringJS
|
|
|
|
var readString8 = readStringJS
|
|
|
|
var readString16 = readStringJS
|
|
|
|
var readString32 = readStringJS
|
|
|
|
|
|
|
|
export function setExtractor(extractStrings) {
|
|
|
|
readFixedString = readString(1)
|
|
|
|
readString8 = readString(2)
|
|
|
|
readString16 = readString(3)
|
|
|
|
readString32 = readString(5)
|
|
|
|
function readString(headerLength) {
|
|
|
|
return function readString(length) {
|
|
|
|
let string = strings[stringPosition++]
|
|
|
|
if (string == null) {
|
|
|
|
let extraction = extractStrings(position - headerLength, srcEnd, src)
|
|
|
|
if (typeof extraction == 'string') {
|
|
|
|
string = extraction
|
|
|
|
strings = EMPTY_ARRAY
|
|
|
|
} else {
|
|
|
|
strings = extraction
|
|
|
|
stringPosition = 1
|
|
|
|
srcStringEnd = 1 // even if a utf-8 string was decoded, must indicate we are in the midst of extracted strings and can't skip strings
|
|
|
|
string = strings[0]
|
|
|
|
if (string === undefined)
|
|
|
|
throw new Error('Unexpected end of buffer')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let srcStringLength = string.length
|
|
|
|
if (srcStringLength <= length) {
|
|
|
|
position += length
|
|
|
|
return string
|
|
|
|
}
|
|
|
|
srcString = string
|
|
|
|
srcStringStart = position
|
|
|
|
srcStringEnd = position + srcStringLength
|
|
|
|
position += length
|
|
|
|
return string.slice(0, length) // we know we just want the beginning
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function readStringJS(length) {
|
|
|
|
let result
|
|
|
|
if (length < 16) {
|
|
|
|
if (result = shortStringInJS(length))
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
if (length > 64 && decoder)
|
|
|
|
return decoder.decode(src.subarray(position, position += length))
|
|
|
|
const end = position + length
|
|
|
|
const units = []
|
|
|
|
result = ''
|
|
|
|
while (position < end) {
|
|
|
|
const byte1 = src[position++]
|
|
|
|
if ((byte1 & 0x80) === 0) {
|
|
|
|
// 1 byte
|
|
|
|
units.push(byte1)
|
|
|
|
} else if ((byte1 & 0xe0) === 0xc0) {
|
|
|
|
// 2 bytes
|
|
|
|
const byte2 = src[position++] & 0x3f
|
|
|
|
units.push(((byte1 & 0x1f) << 6) | byte2)
|
|
|
|
} else if ((byte1 & 0xf0) === 0xe0) {
|
|
|
|
// 3 bytes
|
|
|
|
const byte2 = src[position++] & 0x3f
|
|
|
|
const byte3 = src[position++] & 0x3f
|
|
|
|
units.push(((byte1 & 0x1f) << 12) | (byte2 << 6) | byte3)
|
|
|
|
} else if ((byte1 & 0xf8) === 0xf0) {
|
|
|
|
// 4 bytes
|
|
|
|
const byte2 = src[position++] & 0x3f
|
|
|
|
const byte3 = src[position++] & 0x3f
|
|
|
|
const byte4 = src[position++] & 0x3f
|
|
|
|
let unit = ((byte1 & 0x07) << 0x12) | (byte2 << 0x0c) | (byte3 << 0x06) | byte4
|
|
|
|
if (unit > 0xffff) {
|
|
|
|
unit -= 0x10000
|
|
|
|
units.push(((unit >>> 10) & 0x3ff) | 0xd800)
|
|
|
|
unit = 0xdc00 | (unit & 0x3ff)
|
|
|
|
}
|
|
|
|
units.push(unit)
|
|
|
|
} else {
|
|
|
|
units.push(byte1)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (units.length >= 0x1000) {
|
|
|
|
result += fromCharCode.apply(String, units)
|
|
|
|
units.length = 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (units.length > 0) {
|
|
|
|
result += fromCharCode.apply(String, units)
|
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
function readArray(length) {
|
|
|
|
let array = new Array(length)
|
|
|
|
for (let i = 0; i < length; i++) {
|
|
|
|
array[i] = read()
|
|
|
|
}
|
|
|
|
return array
|
|
|
|
}
|
|
|
|
|
|
|
|
function readMap(length) {
|
|
|
|
if (currentUnpackr.mapsAsObjects) {
|
|
|
|
let object = {}
|
|
|
|
for (let i = 0; i < length; i++) {
|
|
|
|
object[readKey()] = read()
|
|
|
|
}
|
|
|
|
return object
|
|
|
|
} else {
|
|
|
|
let map = new Map()
|
|
|
|
for (let i = 0; i < length; i++) {
|
|
|
|
map.set(read(), read())
|
|
|
|
}
|
|
|
|
return map
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var fromCharCode = String.fromCharCode
|
|
|
|
function longStringInJS(length) {
|
|
|
|
let start = position
|
|
|
|
let bytes = new Array(length)
|
|
|
|
for (let i = 0; i < length; i++) {
|
|
|
|
const byte = src[position++];
|
|
|
|
if ((byte & 0x80) > 0) {
|
|
|
|
position = start
|
|
|
|
return
|
|
|
|
}
|
|
|
|
bytes[i] = byte
|
|
|
|
}
|
|
|
|
return fromCharCode.apply(String, bytes)
|
|
|
|
}
|
|
|
|
function shortStringInJS(length) {
|
|
|
|
if (length < 4) {
|
|
|
|
if (length < 2) {
|
|
|
|
if (length === 0)
|
|
|
|
return ''
|
|
|
|
else {
|
|
|
|
let a = src[position++]
|
|
|
|
if ((a & 0x80) > 1) {
|
|
|
|
position -= 1
|
|
|
|
return
|
|
|
|
}
|
|
|
|
return fromCharCode(a)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
let a = src[position++]
|
|
|
|
let b = src[position++]
|
|
|
|
if ((a & 0x80) > 0 || (b & 0x80) > 0) {
|
|
|
|
position -= 2
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if (length < 3)
|
|
|
|
return fromCharCode(a, b)
|
|
|
|
let c = src[position++]
|
|
|
|
if ((c & 0x80) > 0) {
|
|
|
|
position -= 3
|
|
|
|
return
|
|
|
|
}
|
|
|
|
return fromCharCode(a, b, c)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
let a = src[position++]
|
|
|
|
let b = src[position++]
|
|
|
|
let c = src[position++]
|
|
|
|
let d = src[position++]
|
|
|
|
if ((a & 0x80) > 0 || (b & 0x80) > 0 || (c & 0x80) > 0 || (d & 0x80) > 0) {
|
|
|
|
position -= 4
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if (length < 6) {
|
|
|
|
if (length === 4)
|
|
|
|
return fromCharCode(a, b, c, d)
|
|
|
|
else {
|
|
|
|
let e = src[position++]
|
|
|
|
if ((e & 0x80) > 0) {
|
|
|
|
position -= 5
|
|
|
|
return
|
|
|
|
}
|
|
|
|
return fromCharCode(a, b, c, d, e)
|
|
|
|
}
|
|
|
|
} else if (length < 8) {
|
|
|
|
let e = src[position++]
|
|
|
|
let f = src[position++]
|
|
|
|
if ((e & 0x80) > 0 || (f & 0x80) > 0) {
|
|
|
|
position -= 6
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if (length < 7)
|
|
|
|
return fromCharCode(a, b, c, d, e, f)
|
|
|
|
let g = src[position++]
|
|
|
|
if ((g & 0x80) > 0) {
|
|
|
|
position -= 7
|
|
|
|
return
|
|
|
|
}
|
|
|
|
return fromCharCode(a, b, c, d, e, f, g)
|
|
|
|
} else {
|
|
|
|
let e = src[position++]
|
|
|
|
let f = src[position++]
|
|
|
|
let g = src[position++]
|
|
|
|
let h = src[position++]
|
|
|
|
if ((e & 0x80) > 0 || (f & 0x80) > 0 || (g & 0x80) > 0 || (h & 0x80) > 0) {
|
|
|
|
position -= 8
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if (length < 10) {
|
|
|
|
if (length === 8)
|
|
|
|
return fromCharCode(a, b, c, d, e, f, g, h)
|
|
|
|
else {
|
|
|
|
let i = src[position++]
|
|
|
|
if ((i & 0x80) > 0) {
|
|
|
|
position -= 9
|
|
|
|
return
|
|
|
|
}
|
|
|
|
return fromCharCode(a, b, c, d, e, f, g, h, i)
|
|
|
|
}
|
|
|
|
} else if (length < 12) {
|
|
|
|
let i = src[position++]
|
|
|
|
let j = src[position++]
|
|
|
|
if ((i & 0x80) > 0 || (j & 0x80) > 0) {
|
|
|
|
position -= 10
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if (length < 11)
|
|
|
|
return fromCharCode(a, b, c, d, e, f, g, h, i, j)
|
|
|
|
let k = src[position++]
|
|
|
|
if ((k & 0x80) > 0) {
|
|
|
|
position -= 11
|
|
|
|
return
|
|
|
|
}
|
|
|
|
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k)
|
|
|
|
} else {
|
|
|
|
let i = src[position++]
|
|
|
|
let j = src[position++]
|
|
|
|
let k = src[position++]
|
|
|
|
let l = src[position++]
|
|
|
|
if ((i & 0x80) > 0 || (j & 0x80) > 0 || (k & 0x80) > 0 || (l & 0x80) > 0) {
|
|
|
|
position -= 12
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if (length < 14) {
|
|
|
|
if (length === 12)
|
|
|
|
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l)
|
|
|
|
else {
|
|
|
|
let m = src[position++]
|
|
|
|
if ((m & 0x80) > 0) {
|
|
|
|
position -= 13
|
|
|
|
return
|
|
|
|
}
|
|
|
|
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
let m = src[position++]
|
|
|
|
let n = src[position++]
|
|
|
|
if ((m & 0x80) > 0 || (n & 0x80) > 0) {
|
|
|
|
position -= 14
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if (length < 15)
|
|
|
|
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m, n)
|
|
|
|
let o = src[position++]
|
|
|
|
if ((o & 0x80) > 0) {
|
|
|
|
position -= 15
|
|
|
|
return
|
|
|
|
}
|
|
|
|
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function readBin(length) {
|
|
|
|
return currentUnpackr.copyBuffers ?
|
|
|
|
// specifically use the copying slice (not the node one)
|
|
|
|
Uint8Array.prototype.slice.call(src, position, position += length) :
|
|
|
|
src.subarray(position, position += length)
|
|
|
|
}
|
|
|
|
function readExt(length) {
|
|
|
|
let type = src[position++]
|
|
|
|
if (currentExtensions[type]) {
|
|
|
|
return currentExtensions[type](src.subarray(position, position += length))
|
|
|
|
}
|
|
|
|
else
|
|
|
|
throw new Error('Unknown extension type ' + type)
|
|
|
|
}
|
|
|
|
|
|
|
|
var keyCache = new Array(4096)
|
|
|
|
function readKey() {
|
|
|
|
let length = src[position++]
|
|
|
|
if (length >= 0xa0 && length < 0xc0) {
|
|
|
|
// fixstr, potentially use key cache
|
|
|
|
length = length - 0xa0
|
|
|
|
if (srcStringEnd >= position) // if it has been extracted, must use it (and faster anyway)
|
|
|
|
return srcString.slice(position - srcStringStart, (position += length) - srcStringStart)
|
|
|
|
else if (!(srcStringEnd == 0 && srcEnd < 180))
|
|
|
|
return readFixedString(length)
|
|
|
|
} else { // not cacheable, go back and do a standard read
|
|
|
|
position--
|
|
|
|
return read()
|
|
|
|
}
|
|
|
|
let key = ((length << 5) ^ (length > 1 ? dataView.getUint16(position) : length > 0 ? src[position] : 0)) & 0xfff
|
|
|
|
let entry = keyCache[key]
|
|
|
|
let checkPosition = position
|
|
|
|
let end = position + length - 3
|
|
|
|
let chunk
|
|
|
|
let i = 0
|
|
|
|
if (entry && entry.bytes == length) {
|
|
|
|
while (checkPosition < end) {
|
|
|
|
chunk = dataView.getUint32(checkPosition)
|
|
|
|
if (chunk != entry[i++]) {
|
|
|
|
checkPosition = 0x70000000
|
|
|
|
break
|
|
|
|
}
|
|
|
|
checkPosition += 4
|
|
|
|
}
|
|
|
|
end += 3
|
|
|
|
while (checkPosition < end) {
|
|
|
|
chunk = src[checkPosition++]
|
|
|
|
if (chunk != entry[i++]) {
|
|
|
|
checkPosition = 0x70000000
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (checkPosition === end) {
|
|
|
|
position = checkPosition
|
|
|
|
return entry.string
|
|
|
|
}
|
|
|
|
end -= 3
|
|
|
|
checkPosition = position
|
|
|
|
}
|
|
|
|
entry = []
|
|
|
|
keyCache[key] = entry
|
|
|
|
entry.bytes = length
|
|
|
|
while (checkPosition < end) {
|
|
|
|
chunk = dataView.getUint32(checkPosition)
|
|
|
|
entry.push(chunk)
|
|
|
|
checkPosition += 4
|
|
|
|
}
|
|
|
|
end += 3
|
|
|
|
while (checkPosition < end) {
|
|
|
|
chunk = src[checkPosition++]
|
|
|
|
entry.push(chunk)
|
|
|
|
}
|
|
|
|
// for small blocks, avoiding the overhead of the extract call is helpful
|
|
|
|
let string = length < 16 ? shortStringInJS(length) : longStringInJS(length)
|
|
|
|
if (string != null)
|
|
|
|
return entry.string = string
|
|
|
|
return entry.string = readFixedString(length)
|
|
|
|
}
|
|
|
|
|
|
|
|
// the registration of the record definition extension (as "r")
|
|
|
|
const recordDefinition = (id, highByte) => {
|
|
|
|
var structure = read()
|
|
|
|
let firstByte = id
|
|
|
|
if (highByte !== undefined) {
|
|
|
|
id = id < 32 ? -((highByte << 5) + id) : ((highByte << 5) + id)
|
|
|
|
structure.highByte = highByte
|
|
|
|
}
|
|
|
|
let existingStructure = currentStructures[id]
|
|
|
|
if (existingStructure && existingStructure.isShared) {
|
|
|
|
(currentStructures.restoreStructures || (currentStructures.restoreStructures = []))[id] = existingStructure
|
|
|
|
}
|
|
|
|
currentStructures[id] = structure
|
|
|
|
structure.read = createStructureReader(structure, firstByte)
|
|
|
|
return structure.read()
|
|
|
|
}
|
|
|
|
var glbl = typeof self == 'object' ? self : global
|
|
|
|
currentExtensions[0] = () => {} // notepack defines extension 0 to mean undefined, so use that as the default here
|
|
|
|
currentExtensions[0].noBuffer = true
|
|
|
|
|
|
|
|
currentExtensions[0x65] = () => {
|
|
|
|
let data = read()
|
|
|
|
return (glbl[data[0]] || Error)(data[1])
|
|
|
|
}
|
|
|
|
|
|
|
|
currentExtensions[0x69] = (data) => {
|
|
|
|
// id extension (for structured clones)
|
|
|
|
let id = dataView.getUint32(position - 4)
|
|
|
|
if (!referenceMap)
|
|
|
|
referenceMap = new Map()
|
|
|
|
let token = src[position]
|
|
|
|
let target
|
|
|
|
// TODO: handle Maps, Sets, and other types that can cycle; this is complicated, because you potentially need to read
|
|
|
|
// ahead past references to record structure definitions
|
|
|
|
if (token >= 0x90 && token < 0xa0 || token == 0xdc || token == 0xdd)
|
|
|
|
target = []
|
|
|
|
else
|
|
|
|
target = {}
|
|
|
|
|
|
|
|
let refEntry = { target } // a placeholder object
|
|
|
|
referenceMap.set(id, refEntry)
|
|
|
|
let targetProperties = read() // read the next value as the target object to id
|
|
|
|
if (refEntry.used) // there is a cycle, so we have to assign properties to original target
|
|
|
|
return Object.assign(target, targetProperties)
|
|
|
|
refEntry.target = targetProperties // the placeholder wasn't used, replace with the deserialized one
|
|
|
|
return targetProperties // no cycle, can just use the returned read object
|
|
|
|
}
|
|
|
|
|
|
|
|
currentExtensions[0x70] = (data) => {
|
|
|
|
// pointer extension (for structured clones)
|
|
|
|
let id = dataView.getUint32(position - 4)
|
|
|
|
let refEntry = referenceMap.get(id)
|
|
|
|
refEntry.used = true
|
|
|
|
return refEntry.target
|
|
|
|
}
|
|
|
|
|
|
|
|
currentExtensions[0x73] = () => new Set(read())
|
|
|
|
|
|
|
|
export const typedArrays = ['Int8','Uint8','Uint8Clamped','Int16','Uint16','Int32','Uint32','Float32','Float64','BigInt64','BigUint64'].map(type => type + 'Array')
|
|
|
|
|
|
|
|
currentExtensions[0x74] = (data) => {
|
|
|
|
let typeCode = data[0]
|
|
|
|
let typedArrayName = typedArrays[typeCode]
|
|
|
|
if (!typedArrayName)
|
|
|
|
throw new Error('Could not find typed array for code ' + typeCode)
|
|
|
|
// we have to always slice/copy here to get a new ArrayBuffer that is word/byte aligned
|
|
|
|
return new glbl[typedArrayName](Uint8Array.prototype.slice.call(data, 1).buffer)
|
|
|
|
}
|
|
|
|
currentExtensions[0x78] = () => {
|
|
|
|
let data = read()
|
|
|
|
return new RegExp(data[0], data[1])
|
|
|
|
}
|
|
|
|
|
|
|
|
currentExtensions[0xff] = (data) => {
|
|
|
|
// 32-bit date extension
|
|
|
|
if (data.length == 4)
|
|
|
|
return new Date((data[0] * 0x1000000 + (data[1] << 16) + (data[2] << 8) + data[3]) * 1000)
|
|
|
|
else if (data.length == 8)
|
|
|
|
return new Date(
|
|
|
|
((data[0] << 22) + (data[1] << 14) + (data[2] << 6) + (data[3] >> 2)) / 1000000 +
|
|
|
|
((data[3] & 0x3) * 0x100000000 + data[4] * 0x1000000 + (data[5] << 16) + (data[6] << 8) + data[7]) * 1000)
|
|
|
|
else if (data.length == 12)// TODO: Implement support for negative
|
|
|
|
return new Date(
|
|
|
|
((data[0] << 24) + (data[1] << 16) + (data[2] << 8) + data[3]) / 1000000 +
|
|
|
|
(((data[4] & 0x80) ? -0x1000000000000 : 0) + data[6] * 0x10000000000 + data[7] * 0x100000000 + data[8] * 0x1000000 + (data[9] << 16) + (data[10] << 8) + data[11]) * 1000)
|
|
|
|
else
|
|
|
|
throw new Error('Invalid timestamp length')
|
|
|
|
} // notepack defines extension 0 to mean undefined, so use that as the default here
|
|
|
|
// registration of bulk record definition?
|
|
|
|
// currentExtensions[0x52] = () =>
|
|
|
|
|
|
|
|
function saveState(callback) {
|
|
|
|
let savedSrcEnd = srcEnd
|
|
|
|
let savedPosition = position
|
|
|
|
let savedStringPosition = stringPosition
|
|
|
|
let savedSrcStringStart = srcStringStart
|
|
|
|
let savedSrcStringEnd = srcStringEnd
|
|
|
|
let savedSrcString = srcString
|
|
|
|
let savedStrings = strings
|
|
|
|
let savedReferenceMap = referenceMap
|
|
|
|
|
|
|
|
// TODO: We may need to revisit this if we do more external calls to user code (since it could be slow)
|
|
|
|
let savedSrc = new Uint8Array(src.slice(0, srcEnd)) // we copy the data in case it changes while external data is processed
|
|
|
|
let savedStructures = currentStructures
|
|
|
|
let savedStructuresContents = currentStructures.slice(0, currentStructures.length)
|
|
|
|
let savedPackr = currentUnpackr
|
|
|
|
let savedSequentialMode = sequentialMode
|
|
|
|
let value = callback()
|
|
|
|
srcEnd = savedSrcEnd
|
|
|
|
position = savedPosition
|
|
|
|
stringPosition = savedStringPosition
|
|
|
|
srcStringStart = savedSrcStringStart
|
|
|
|
srcStringEnd = savedSrcStringEnd
|
|
|
|
srcString = savedSrcString
|
|
|
|
strings = savedStrings
|
|
|
|
referenceMap = savedReferenceMap
|
|
|
|
src = savedSrc
|
|
|
|
sequentialMode = savedSequentialMode
|
|
|
|
currentStructures = savedStructures
|
|
|
|
currentStructures.splice(0, currentStructures.length, ...savedStructuresContents)
|
|
|
|
currentUnpackr = savedPackr
|
|
|
|
dataView = new DataView(src.buffer, src.byteOffset, src.byteLength)
|
|
|
|
return value
|
|
|
|
}
|
|
|
|
export function clearSource() {
|
|
|
|
src = null
|
|
|
|
referenceMap = null
|
|
|
|
currentStructures = null
|
|
|
|
}
|
|
|
|
|
|
|
|
export function addExtension(extension) {
|
|
|
|
if (extension.unpack)
|
|
|
|
currentExtensions[extension.type] = extension.unpack
|
|
|
|
else
|
|
|
|
currentExtensions[extension.type] = extension
|
|
|
|
}
|
|
|
|
|
|
|
|
export const mult10 = new Array(147) // this is a table matching binary exponents to the multiplier to determine significant digit rounding
|
|
|
|
for (let i = 0; i < 256; i++) {
|
|
|
|
mult10[i] = +('1e' + Math.floor(45.15 - i * 0.30103))
|
|
|
|
}
|
|
|
|
export const useRecords = false
|
|
|
|
export const mapsAsObjects = true
|
|
|
|
export const Decoder = Unpackr
|
|
|
|
var defaultUnpackr = new Unpackr({ useRecords: false })
|
|
|
|
export const unpack = defaultUnpackr.unpack
|
|
|
|
export const unpackMultiple = defaultUnpackr.unpackMultiple
|
|
|
|
export const decode = defaultUnpackr.unpack
|
|
|
|
export const FLOAT32_OPTIONS = {
|
|
|
|
NEVER: 0,
|
|
|
|
ALWAYS: 1,
|
|
|
|
DECIMAL_ROUND: 3,
|
|
|
|
DECIMAL_FIT: 4
|
|
|
|
}
|
|
|
|
let f32Array = new Float32Array(1)
|
|
|
|
let u8Array = new Uint8Array(f32Array.buffer, 0, 4)
|
|
|
|
export function roundFloat32(float32Number) {
|
|
|
|
f32Array[0] = float32Number
|
|
|
|
let multiplier = mult10[((u8Array[3] & 0x7f) << 1) | (u8Array[2] >> 7)]
|
|
|
|
return ((multiplier * float32Number + (float32Number > 0 ? 0.5 : -0.5)) >> 0) / multiplier
|
|
|
|
}
|