5033 lines
174 KiB
JavaScript
5033 lines
174 KiB
JavaScript
|
|
(function (chai, stream, module, fs) {
|
|||
|
|
'use strict';
|
|||
|
|
|
|||
|
|
var decoder;
|
|||
|
|
try {
|
|||
|
|
decoder = new TextDecoder();
|
|||
|
|
} catch(error) {}
|
|||
|
|
var src;
|
|||
|
|
var srcEnd;
|
|||
|
|
var position$1 = 0;
|
|||
|
|
const EMPTY_ARRAY = [];
|
|||
|
|
var strings = EMPTY_ARRAY;
|
|||
|
|
var stringPosition = 0;
|
|||
|
|
var currentUnpackr = {};
|
|||
|
|
var currentStructures;
|
|||
|
|
var srcString;
|
|||
|
|
var srcStringStart = 0;
|
|||
|
|
var srcStringEnd = 0;
|
|||
|
|
var bundledStrings$1;
|
|||
|
|
var referenceMap;
|
|||
|
|
var currentExtensions = [];
|
|||
|
|
var dataView;
|
|||
|
|
var defaultOptions = {
|
|||
|
|
useRecords: false,
|
|||
|
|
mapsAsObjects: true
|
|||
|
|
};
|
|||
|
|
class C1Type {}
|
|||
|
|
const C1 = new C1Type();
|
|||
|
|
C1.name = 'MessagePack 0xC1';
|
|||
|
|
var sequentialMode = false;
|
|||
|
|
var inlineObjectReadThreshold = 2;
|
|||
|
|
var readStruct$1, onLoadedStructures$1, onSaveState;
|
|||
|
|
|
|||
|
|
let Unpackr$1 = class Unpackr {
|
|||
|
|
constructor(options) {
|
|||
|
|
if (options) {
|
|||
|
|
if (options.useRecords === false && options.mapsAsObjects === undefined)
|
|||
|
|
options.mapsAsObjects = true;
|
|||
|
|
if (options.sequential && options.trusted !== false) {
|
|||
|
|
options.trusted = true;
|
|||
|
|
if (!options.structures && options.useRecords != false) {
|
|||
|
|
options.structures = [];
|
|||
|
|
if (!options.maxSharedStructures)
|
|||
|
|
options.maxSharedStructures = 0;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
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;
|
|||
|
|
}
|
|||
|
|
if (options.int64AsNumber) {
|
|||
|
|
options.int64AsType = 'number';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
Object.assign(this, options);
|
|||
|
|
}
|
|||
|
|
unpack(source, options) {
|
|||
|
|
if (src) {
|
|||
|
|
// re-entrant execution, save the state and restore it after we do this unpack
|
|||
|
|
return saveState$1(() => {
|
|||
|
|
clearSource();
|
|||
|
|
return this ? this.unpack(source, options) : Unpackr$1.prototype.unpack.call(defaultOptions, source, options)
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
if (!source.buffer && source.constructor === ArrayBuffer)
|
|||
|
|
source = typeof Buffer !== 'undefined' ? Buffer.from(source) : new Uint8Array(source);
|
|||
|
|
if (typeof options === 'object') {
|
|||
|
|
srcEnd = options.end || source.length;
|
|||
|
|
position$1 = options.start || 0;
|
|||
|
|
} else {
|
|||
|
|
position$1 = 0;
|
|||
|
|
srcEnd = options > -1 ? options : source.length;
|
|||
|
|
}
|
|||
|
|
stringPosition = 0;
|
|||
|
|
srcStringEnd = 0;
|
|||
|
|
srcString = null;
|
|||
|
|
strings = EMPTY_ARRAY;
|
|||
|
|
bundledStrings$1 = null;
|
|||
|
|
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
|
|||
|
|
try {
|
|||
|
|
dataView = source.dataView || (source.dataView = new DataView(source.buffer, source.byteOffset, source.byteLength));
|
|||
|
|
} catch(error) {
|
|||
|
|
// if it doesn't have a buffer, maybe it is the wrong type of object
|
|||
|
|
src = null;
|
|||
|
|
if (source instanceof Uint8Array)
|
|||
|
|
throw error
|
|||
|
|
throw new Error('Source must be a Uint8Array or Buffer but was a ' + ((source && typeof source == 'object') ? source.constructor.name : typeof source))
|
|||
|
|
}
|
|||
|
|
if (this instanceof Unpackr$1) {
|
|||
|
|
currentUnpackr = this;
|
|||
|
|
if (this.structures) {
|
|||
|
|
currentStructures = this.structures;
|
|||
|
|
return checkedRead(options)
|
|||
|
|
} else if (!currentStructures || currentStructures.length > 0) {
|
|||
|
|
currentStructures = [];
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
currentUnpackr = defaultOptions;
|
|||
|
|
if (!currentStructures || currentStructures.length > 0)
|
|||
|
|
currentStructures = [];
|
|||
|
|
}
|
|||
|
|
return checkedRead(options)
|
|||
|
|
}
|
|||
|
|
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) {
|
|||
|
|
if (forEach(value, lastPosition, position$1) === false) return;
|
|||
|
|
while(position$1 < size) {
|
|||
|
|
lastPosition = position$1;
|
|||
|
|
if (forEach(checkedRead(), lastPosition, position$1) === false) {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
values = [ value ];
|
|||
|
|
while(position$1 < size) {
|
|||
|
|
lastPosition = position$1;
|
|||
|
|
values.push(checkedRead());
|
|||
|
|
}
|
|||
|
|
return values
|
|||
|
|
}
|
|||
|
|
} catch(error) {
|
|||
|
|
error.lastPosition = lastPosition;
|
|||
|
|
error.values = values;
|
|||
|
|
throw error
|
|||
|
|
} finally {
|
|||
|
|
sequentialMode = false;
|
|||
|
|
clearSource();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
_mergeStructures(loadedStructures, existingStructures) {
|
|||
|
|
if (onLoadedStructures$1)
|
|||
|
|
loadedStructures = onLoadedStructures$1.call(this, loadedStructures);
|
|||
|
|
loadedStructures = loadedStructures || [];
|
|||
|
|
if (Object.isFrozen(loadedStructures))
|
|||
|
|
loadedStructures = loadedStructures.map(structure => structure.slice(0));
|
|||
|
|
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, options) {
|
|||
|
|
return this.unpack(source, options)
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
function checkedRead(options) {
|
|||
|
|
try {
|
|||
|
|
if (!currentUnpackr.trusted && !sequentialMode) {
|
|||
|
|
let sharedLength = currentStructures.sharedLength || 0;
|
|||
|
|
if (sharedLength < currentStructures.length)
|
|||
|
|
currentStructures.length = sharedLength;
|
|||
|
|
}
|
|||
|
|
let result;
|
|||
|
|
if (currentUnpackr.randomAccessStructure && src[position$1] < 0x40 && src[position$1] >= 0x20 && readStruct$1) {
|
|||
|
|
result = readStruct$1(src, position$1, srcEnd, currentUnpackr);
|
|||
|
|
src = null; // dispose of this so that recursive unpack calls don't save state
|
|||
|
|
if (!(options && options.lazy) && result)
|
|||
|
|
result = result.toJSON();
|
|||
|
|
position$1 = srcEnd;
|
|||
|
|
} else
|
|||
|
|
result = read();
|
|||
|
|
if (bundledStrings$1) { // bundled strings to skip past
|
|||
|
|
position$1 = bundledStrings$1.postBundlePosition;
|
|||
|
|
bundledStrings$1 = null;
|
|||
|
|
}
|
|||
|
|
if (sequentialMode)
|
|||
|
|
// we only need to restore the structures if there was an error, but if we completed a read,
|
|||
|
|
// we can clear this out and keep the structures we read
|
|||
|
|
currentStructures.restoreStructures = null;
|
|||
|
|
|
|||
|
|
if (position$1 == srcEnd) {
|
|||
|
|
// finished reading this source, cleanup references
|
|||
|
|
if (currentStructures && currentStructures.restoreStructures)
|
|||
|
|
restoreStructures();
|
|||
|
|
currentStructures = null;
|
|||
|
|
src = null;
|
|||
|
|
if (referenceMap)
|
|||
|
|
referenceMap = null;
|
|||
|
|
} else if (position$1 > srcEnd) {
|
|||
|
|
// over read
|
|||
|
|
throw new Error('Unexpected end of MessagePack data')
|
|||
|
|
} else if (!sequentialMode) {
|
|||
|
|
let jsonView;
|
|||
|
|
try {
|
|||
|
|
jsonView = JSON.stringify(result, (_, value) => typeof value === "bigint" ? `${value}n` : value).slice(0, 100);
|
|||
|
|
} catch(error) {
|
|||
|
|
jsonView = '(JSON view not available ' + error + ')';
|
|||
|
|
}
|
|||
|
|
throw new Error('Data read, but end of buffer not reached ' + jsonView)
|
|||
|
|
}
|
|||
|
|
// else more to read, but we are reading sequentially, so don't clear source yet
|
|||
|
|
return result
|
|||
|
|
} catch(error) {
|
|||
|
|
if (currentStructures && currentStructures.restoreStructures)
|
|||
|
|
restoreStructures();
|
|||
|
|
clearSource();
|
|||
|
|
if (error instanceof RangeError || error.message.startsWith('Unexpected end of buffer') || position$1 > srcEnd) {
|
|||
|
|
error.incomplete = true;
|
|||
|
|
}
|
|||
|
|
throw error
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function restoreStructures() {
|
|||
|
|
for (let id in currentStructures.restoreStructures) {
|
|||
|
|
currentStructures[id] = currentStructures.restoreStructures[id];
|
|||
|
|
}
|
|||
|
|
currentStructures.restoreStructures = null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function read() {
|
|||
|
|
let token = src[position$1++];
|
|||
|
|
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++) {
|
|||
|
|
let key = readKey();
|
|||
|
|
if (key === '__proto__')
|
|||
|
|
key = '__proto_';
|
|||
|
|
object[key] = 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();
|
|||
|
|
}
|
|||
|
|
if (currentUnpackr.freezeData)
|
|||
|
|
return Object.freeze(array)
|
|||
|
|
return array
|
|||
|
|
}
|
|||
|
|
} else if (token < 0xc0) {
|
|||
|
|
// fixstr
|
|||
|
|
let length = token - 0xa0;
|
|||
|
|
if (srcStringEnd >= position$1) {
|
|||
|
|
return srcString.slice(position$1 - srcStringStart, (position$1 += 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:
|
|||
|
|
if (bundledStrings$1) {
|
|||
|
|
value = read(); // followed by the length of the string in characters (not bytes!)
|
|||
|
|
if (value > 0)
|
|||
|
|
return bundledStrings$1[1].slice(bundledStrings$1.position1, bundledStrings$1.position1 += value)
|
|||
|
|
else
|
|||
|
|
return bundledStrings$1[0].slice(bundledStrings$1.position0, bundledStrings$1.position0 -= value)
|
|||
|
|
}
|
|||
|
|
return C1; // "never-used", return special object to denote that
|
|||
|
|
case 0xc2: return false
|
|||
|
|
case 0xc3: return true
|
|||
|
|
case 0xc4:
|
|||
|
|
// bin 8
|
|||
|
|
value = src[position$1++];
|
|||
|
|
if (value === undefined)
|
|||
|
|
throw new Error('Unexpected end of buffer')
|
|||
|
|
return readBin(value)
|
|||
|
|
case 0xc5:
|
|||
|
|
// bin 16
|
|||
|
|
value = dataView.getUint16(position$1);
|
|||
|
|
position$1 += 2;
|
|||
|
|
return readBin(value)
|
|||
|
|
case 0xc6:
|
|||
|
|
// bin 32
|
|||
|
|
value = dataView.getUint32(position$1);
|
|||
|
|
position$1 += 4;
|
|||
|
|
return readBin(value)
|
|||
|
|
case 0xc7:
|
|||
|
|
// ext 8
|
|||
|
|
return readExt(src[position$1++])
|
|||
|
|
case 0xc8:
|
|||
|
|
// ext 16
|
|||
|
|
value = dataView.getUint16(position$1);
|
|||
|
|
position$1 += 2;
|
|||
|
|
return readExt(value)
|
|||
|
|
case 0xc9:
|
|||
|
|
// ext 32
|
|||
|
|
value = dataView.getUint32(position$1);
|
|||
|
|
position$1 += 4;
|
|||
|
|
return readExt(value)
|
|||
|
|
case 0xca:
|
|||
|
|
value = dataView.getFloat32(position$1);
|
|||
|
|
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$1] & 0x7f) << 1) | (src[position$1 + 1] >> 7)];
|
|||
|
|
position$1 += 4;
|
|||
|
|
return ((multiplier * value + (value > 0 ? 0.5 : -0.5)) >> 0) / multiplier
|
|||
|
|
}
|
|||
|
|
position$1 += 4;
|
|||
|
|
return value
|
|||
|
|
case 0xcb:
|
|||
|
|
value = dataView.getFloat64(position$1);
|
|||
|
|
position$1 += 8;
|
|||
|
|
return value
|
|||
|
|
// uint handlers
|
|||
|
|
case 0xcc:
|
|||
|
|
return src[position$1++]
|
|||
|
|
case 0xcd:
|
|||
|
|
value = dataView.getUint16(position$1);
|
|||
|
|
position$1 += 2;
|
|||
|
|
return value
|
|||
|
|
case 0xce:
|
|||
|
|
value = dataView.getUint32(position$1);
|
|||
|
|
position$1 += 4;
|
|||
|
|
return value
|
|||
|
|
case 0xcf:
|
|||
|
|
if (currentUnpackr.int64AsType === 'number') {
|
|||
|
|
value = dataView.getUint32(position$1) * 0x100000000;
|
|||
|
|
value += dataView.getUint32(position$1 + 4);
|
|||
|
|
} else if (currentUnpackr.int64AsType === 'string') {
|
|||
|
|
value = dataView.getBigUint64(position$1).toString();
|
|||
|
|
} else if (currentUnpackr.int64AsType === 'auto') {
|
|||
|
|
value = dataView.getBigUint64(position$1);
|
|||
|
|
if (value<=BigInt(2)<<BigInt(52)) value=Number(value);
|
|||
|
|
} else
|
|||
|
|
value = dataView.getBigUint64(position$1);
|
|||
|
|
position$1 += 8;
|
|||
|
|
return value
|
|||
|
|
|
|||
|
|
// int handlers
|
|||
|
|
case 0xd0:
|
|||
|
|
return dataView.getInt8(position$1++)
|
|||
|
|
case 0xd1:
|
|||
|
|
value = dataView.getInt16(position$1);
|
|||
|
|
position$1 += 2;
|
|||
|
|
return value
|
|||
|
|
case 0xd2:
|
|||
|
|
value = dataView.getInt32(position$1);
|
|||
|
|
position$1 += 4;
|
|||
|
|
return value
|
|||
|
|
case 0xd3:
|
|||
|
|
if (currentUnpackr.int64AsType === 'number') {
|
|||
|
|
value = dataView.getInt32(position$1) * 0x100000000;
|
|||
|
|
value += dataView.getUint32(position$1 + 4);
|
|||
|
|
} else if (currentUnpackr.int64AsType === 'string') {
|
|||
|
|
value = dataView.getBigInt64(position$1).toString();
|
|||
|
|
} else if (currentUnpackr.int64AsType === 'auto') {
|
|||
|
|
value = dataView.getBigInt64(position$1);
|
|||
|
|
if (value>=BigInt(-2)<<BigInt(52)&&value<=BigInt(2)<<BigInt(52)) value=Number(value);
|
|||
|
|
} else
|
|||
|
|
value = dataView.getBigInt64(position$1);
|
|||
|
|
position$1 += 8;
|
|||
|
|
return value
|
|||
|
|
|
|||
|
|
case 0xd4:
|
|||
|
|
// fixext 1
|
|||
|
|
value = src[position$1++];
|
|||
|
|
if (value == 0x72) {
|
|||
|
|
return recordDefinition(src[position$1++] & 0x3f)
|
|||
|
|
} else {
|
|||
|
|
let extension = currentExtensions[value];
|
|||
|
|
if (extension) {
|
|||
|
|
if (extension.read) {
|
|||
|
|
position$1++; // skip filler byte
|
|||
|
|
return extension.read(read())
|
|||
|
|
} else if (extension.noBuffer) {
|
|||
|
|
position$1++; // skip filler byte
|
|||
|
|
return extension()
|
|||
|
|
} else
|
|||
|
|
return extension(src.subarray(position$1, ++position$1))
|
|||
|
|
} else
|
|||
|
|
throw new Error('Unknown extension ' + value)
|
|||
|
|
}
|
|||
|
|
case 0xd5:
|
|||
|
|
// fixext 2
|
|||
|
|
value = src[position$1];
|
|||
|
|
if (value == 0x72) {
|
|||
|
|
position$1++;
|
|||
|
|
return recordDefinition(src[position$1++] & 0x3f, src[position$1++])
|
|||
|
|
} 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$1++];
|
|||
|
|
if (srcStringEnd >= position$1) {
|
|||
|
|
return srcString.slice(position$1 - srcStringStart, (position$1 += value) - srcStringStart)
|
|||
|
|
}
|
|||
|
|
return readString8(value)
|
|||
|
|
case 0xda:
|
|||
|
|
// str 16
|
|||
|
|
value = dataView.getUint16(position$1);
|
|||
|
|
position$1 += 2;
|
|||
|
|
if (srcStringEnd >= position$1) {
|
|||
|
|
return srcString.slice(position$1 - srcStringStart, (position$1 += value) - srcStringStart)
|
|||
|
|
}
|
|||
|
|
return readString16(value)
|
|||
|
|
case 0xdb:
|
|||
|
|
// str 32
|
|||
|
|
value = dataView.getUint32(position$1);
|
|||
|
|
position$1 += 4;
|
|||
|
|
if (srcStringEnd >= position$1) {
|
|||
|
|
return srcString.slice(position$1 - srcStringStart, (position$1 += value) - srcStringStart)
|
|||
|
|
}
|
|||
|
|
return readString32(value)
|
|||
|
|
case 0xdc:
|
|||
|
|
// array 16
|
|||
|
|
value = dataView.getUint16(position$1);
|
|||
|
|
position$1 += 2;
|
|||
|
|
return readArray(value)
|
|||
|
|
case 0xdd:
|
|||
|
|
// array 32
|
|||
|
|
value = dataView.getUint32(position$1);
|
|||
|
|
position$1 += 4;
|
|||
|
|
return readArray(value)
|
|||
|
|
case 0xde:
|
|||
|
|
// map 16
|
|||
|
|
value = dataView.getUint16(position$1);
|
|||
|
|
position$1 += 2;
|
|||
|
|
return readMap(value)
|
|||
|
|
case 0xdf:
|
|||
|
|
// map 32
|
|||
|
|
value = dataView.getUint32(position$1);
|
|||
|
|
position$1 += 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++ > inlineObjectReadThreshold) {
|
|||
|
|
let optimizedReadObject;
|
|||
|
|
try {
|
|||
|
|
optimizedReadObject = structure.read = (new Function('r', 'return function(){return ' + (currentUnpackr.freezeData ? 'Object.freeze' : '') +
|
|||
|
|
'({' + structure.map(key => key === '__proto__' ? '__proto_:r()' : validName.test(key) ? key + ':r()' : ('[' + JSON.stringify(key) + ']:r()')).join(',') + '})}'))(read);
|
|||
|
|
} catch(error) {
|
|||
|
|
// in CF workers, the new Function call could begin to fail at any point in time
|
|||
|
|
inlineObjectReadThreshold = Infinity; // disable going forward
|
|||
|
|
return readObject(); // recursively try again
|
|||
|
|
}
|
|||
|
|
structure.read0 = optimizedReadObject; // keep the un-wrapped body reader in sync
|
|||
|
|
if (structure.highByte === 0)
|
|||
|
|
structure.read = createSecondByteReader(firstId, structure.read);
|
|||
|
|
return optimizedReadObject() // 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];
|
|||
|
|
if (key === '__proto__')
|
|||
|
|
key = '__proto_';
|
|||
|
|
object[key] = read();
|
|||
|
|
}
|
|||
|
|
if (currentUnpackr.freezeData)
|
|||
|
|
return Object.freeze(object);
|
|||
|
|
return object
|
|||
|
|
}
|
|||
|
|
readObject.count = 0;
|
|||
|
|
// read0 is the un-wrapped body reader: it reads the record's values directly without
|
|||
|
|
// consuming a leading high byte. recordDefinition uses it for the immediate read that follows
|
|||
|
|
// a record definition (the high byte, if present, was already consumed). For highByte === 0
|
|||
|
|
// structures the public reader is a second-byte reader (used by later references), but the
|
|||
|
|
// definition read itself must not consume that byte.
|
|||
|
|
structure.read0 = readObject;
|
|||
|
|
if (structure.highByte === 0) {
|
|||
|
|
return createSecondByteReader(firstId, readObject)
|
|||
|
|
}
|
|||
|
|
return readObject
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const createSecondByteReader = (firstId, read0) => {
|
|||
|
|
return function() {
|
|||
|
|
let highByte = src[position$1++];
|
|||
|
|
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$1(() => {
|
|||
|
|
// 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;
|
|||
|
|
|
|||
|
|
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) {
|
|||
|
|
if (bundledStrings$1)
|
|||
|
|
return readStringJS(length)
|
|||
|
|
let byteOffset = src.byteOffset;
|
|||
|
|
let extraction = extractStrings(position$1 - headerLength + byteOffset, srcEnd + byteOffset, src.buffer);
|
|||
|
|
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$1 += length;
|
|||
|
|
return string
|
|||
|
|
}
|
|||
|
|
srcString = string;
|
|||
|
|
srcStringStart = position$1;
|
|||
|
|
srcStringEnd = position$1 + srcStringLength;
|
|||
|
|
position$1 += 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$1, position$1 += length))
|
|||
|
|
const end = position$1 + length;
|
|||
|
|
const units = [];
|
|||
|
|
result = '';
|
|||
|
|
while (position$1 < end) {
|
|||
|
|
const byte1 = src[position$1++];
|
|||
|
|
if ((byte1 & 0x80) === 0) {
|
|||
|
|
// 1 byte
|
|||
|
|
units.push(byte1);
|
|||
|
|
} else if ((byte1 & 0xe0) === 0xc0) {
|
|||
|
|
// 2 bytes
|
|||
|
|
const byte2 = src[position$1++] & 0x3f;
|
|||
|
|
const codePoint = ((byte1 & 0x1f) << 6) | byte2;
|
|||
|
|
// Reject overlong encoding: 2-byte sequences must encode values >= 0x80
|
|||
|
|
if (codePoint < 0x80) {
|
|||
|
|
units.push(0xFFFD); // replacement character
|
|||
|
|
} else {
|
|||
|
|
units.push(codePoint);
|
|||
|
|
}
|
|||
|
|
} else if ((byte1 & 0xf0) === 0xe0) {
|
|||
|
|
// 3 bytes
|
|||
|
|
const byte2 = src[position$1++] & 0x3f;
|
|||
|
|
const byte3 = src[position$1++] & 0x3f;
|
|||
|
|
const codePoint = ((byte1 & 0x1f) << 12) | (byte2 << 6) | byte3;
|
|||
|
|
// Reject overlong encoding: 3-byte sequences must encode values >= 0x800
|
|||
|
|
// Also reject surrogates (0xD800-0xDFFF)
|
|||
|
|
if (codePoint < 0x800 || (codePoint >= 0xD800 && codePoint <= 0xDFFF)) {
|
|||
|
|
units.push(0xFFFD); // replacement character
|
|||
|
|
} else {
|
|||
|
|
units.push(codePoint);
|
|||
|
|
}
|
|||
|
|
} else if ((byte1 & 0xf8) === 0xf0) {
|
|||
|
|
// 4 bytes
|
|||
|
|
const byte2 = src[position$1++] & 0x3f;
|
|||
|
|
const byte3 = src[position$1++] & 0x3f;
|
|||
|
|
const byte4 = src[position$1++] & 0x3f;
|
|||
|
|
let unit = ((byte1 & 0x07) << 0x12) | (byte2 << 0x0c) | (byte3 << 0x06) | byte4;
|
|||
|
|
// Reject overlong encoding: 4-byte sequences must encode values >= 0x10000
|
|||
|
|
// Also reject values > 0x10FFFF (maximum valid Unicode)
|
|||
|
|
if (unit < 0x10000 || unit > 0x10FFFF) {
|
|||
|
|
units.push(0xFFFD); // replacement character
|
|||
|
|
} else if (unit > 0xffff) {
|
|||
|
|
unit -= 0x10000;
|
|||
|
|
units.push(((unit >>> 10) & 0x3ff) | 0xd800);
|
|||
|
|
unit = 0xdc00 | (unit & 0x3ff);
|
|||
|
|
units.push(unit);
|
|||
|
|
} else {
|
|||
|
|
units.push(unit);
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
units.push(0xFFFD); // replacement character for invalid lead byte
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (units.length >= 0x1000) {
|
|||
|
|
result += fromCharCode.apply(String, units);
|
|||
|
|
units.length = 0;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (units.length > 0) {
|
|||
|
|
result += fromCharCode.apply(String, units);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return result
|
|||
|
|
}
|
|||
|
|
function readString(source, start, length) {
|
|||
|
|
let existingSrc = src;
|
|||
|
|
src = source;
|
|||
|
|
position$1 = start;
|
|||
|
|
try {
|
|||
|
|
return readStringJS(length);
|
|||
|
|
} finally {
|
|||
|
|
src = existingSrc;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function readArray(length) {
|
|||
|
|
let array = new Array(length);
|
|||
|
|
for (let i = 0; i < length; i++) {
|
|||
|
|
array[i] = read();
|
|||
|
|
}
|
|||
|
|
if (currentUnpackr.freezeData)
|
|||
|
|
return Object.freeze(array)
|
|||
|
|
return array
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function readMap(length) {
|
|||
|
|
if (currentUnpackr.mapsAsObjects) {
|
|||
|
|
let object = {};
|
|||
|
|
for (let i = 0; i < length; i++) {
|
|||
|
|
let key = readKey();
|
|||
|
|
if (key === '__proto__')
|
|||
|
|
key = '__proto_';
|
|||
|
|
object[key] = 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$1;
|
|||
|
|
let bytes = new Array(length);
|
|||
|
|
for (let i = 0; i < length; i++) {
|
|||
|
|
const byte = src[position$1++];
|
|||
|
|
if ((byte & 0x80) > 0) {
|
|||
|
|
position$1 = 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$1++];
|
|||
|
|
if ((a & 0x80) > 1) {
|
|||
|
|
position$1 -= 1;
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
return fromCharCode(a)
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
let a = src[position$1++];
|
|||
|
|
let b = src[position$1++];
|
|||
|
|
if ((a & 0x80) > 0 || (b & 0x80) > 0) {
|
|||
|
|
position$1 -= 2;
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
if (length < 3)
|
|||
|
|
return fromCharCode(a, b)
|
|||
|
|
let c = src[position$1++];
|
|||
|
|
if ((c & 0x80) > 0) {
|
|||
|
|
position$1 -= 3;
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
return fromCharCode(a, b, c)
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
let a = src[position$1++];
|
|||
|
|
let b = src[position$1++];
|
|||
|
|
let c = src[position$1++];
|
|||
|
|
let d = src[position$1++];
|
|||
|
|
if ((a & 0x80) > 0 || (b & 0x80) > 0 || (c & 0x80) > 0 || (d & 0x80) > 0) {
|
|||
|
|
position$1 -= 4;
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
if (length < 6) {
|
|||
|
|
if (length === 4)
|
|||
|
|
return fromCharCode(a, b, c, d)
|
|||
|
|
else {
|
|||
|
|
let e = src[position$1++];
|
|||
|
|
if ((e & 0x80) > 0) {
|
|||
|
|
position$1 -= 5;
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
return fromCharCode(a, b, c, d, e)
|
|||
|
|
}
|
|||
|
|
} else if (length < 8) {
|
|||
|
|
let e = src[position$1++];
|
|||
|
|
let f = src[position$1++];
|
|||
|
|
if ((e & 0x80) > 0 || (f & 0x80) > 0) {
|
|||
|
|
position$1 -= 6;
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
if (length < 7)
|
|||
|
|
return fromCharCode(a, b, c, d, e, f)
|
|||
|
|
let g = src[position$1++];
|
|||
|
|
if ((g & 0x80) > 0) {
|
|||
|
|
position$1 -= 7;
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
return fromCharCode(a, b, c, d, e, f, g)
|
|||
|
|
} else {
|
|||
|
|
let e = src[position$1++];
|
|||
|
|
let f = src[position$1++];
|
|||
|
|
let g = src[position$1++];
|
|||
|
|
let h = src[position$1++];
|
|||
|
|
if ((e & 0x80) > 0 || (f & 0x80) > 0 || (g & 0x80) > 0 || (h & 0x80) > 0) {
|
|||
|
|
position$1 -= 8;
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
if (length < 10) {
|
|||
|
|
if (length === 8)
|
|||
|
|
return fromCharCode(a, b, c, d, e, f, g, h)
|
|||
|
|
else {
|
|||
|
|
let i = src[position$1++];
|
|||
|
|
if ((i & 0x80) > 0) {
|
|||
|
|
position$1 -= 9;
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
return fromCharCode(a, b, c, d, e, f, g, h, i)
|
|||
|
|
}
|
|||
|
|
} else if (length < 12) {
|
|||
|
|
let i = src[position$1++];
|
|||
|
|
let j = src[position$1++];
|
|||
|
|
if ((i & 0x80) > 0 || (j & 0x80) > 0) {
|
|||
|
|
position$1 -= 10;
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
if (length < 11)
|
|||
|
|
return fromCharCode(a, b, c, d, e, f, g, h, i, j)
|
|||
|
|
let k = src[position$1++];
|
|||
|
|
if ((k & 0x80) > 0) {
|
|||
|
|
position$1 -= 11;
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k)
|
|||
|
|
} else {
|
|||
|
|
let i = src[position$1++];
|
|||
|
|
let j = src[position$1++];
|
|||
|
|
let k = src[position$1++];
|
|||
|
|
let l = src[position$1++];
|
|||
|
|
if ((i & 0x80) > 0 || (j & 0x80) > 0 || (k & 0x80) > 0 || (l & 0x80) > 0) {
|
|||
|
|
position$1 -= 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$1++];
|
|||
|
|
if ((m & 0x80) > 0) {
|
|||
|
|
position$1 -= 13;
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m)
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
let m = src[position$1++];
|
|||
|
|
let n = src[position$1++];
|
|||
|
|
if ((m & 0x80) > 0 || (n & 0x80) > 0) {
|
|||
|
|
position$1 -= 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$1++];
|
|||
|
|
if ((o & 0x80) > 0) {
|
|||
|
|
position$1 -= 15;
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function readOnlyJSString() {
|
|||
|
|
let token = src[position$1++];
|
|||
|
|
let length;
|
|||
|
|
if (token < 0xc0) {
|
|||
|
|
// fixstr
|
|||
|
|
length = token - 0xa0;
|
|||
|
|
} else {
|
|||
|
|
switch(token) {
|
|||
|
|
case 0xd9:
|
|||
|
|
// str 8
|
|||
|
|
length = src[position$1++];
|
|||
|
|
break
|
|||
|
|
case 0xda:
|
|||
|
|
// str 16
|
|||
|
|
length = dataView.getUint16(position$1);
|
|||
|
|
position$1 += 2;
|
|||
|
|
break
|
|||
|
|
case 0xdb:
|
|||
|
|
// str 32
|
|||
|
|
length = dataView.getUint32(position$1);
|
|||
|
|
position$1 += 4;
|
|||
|
|
break
|
|||
|
|
default:
|
|||
|
|
throw new Error('Expected string')
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return readStringJS(length)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
function readBin(length) {
|
|||
|
|
return currentUnpackr.copyBuffers ?
|
|||
|
|
// specifically use the copying slice (not the node one)
|
|||
|
|
Uint8Array.prototype.slice.call(src, position$1, position$1 += length) :
|
|||
|
|
src.subarray(position$1, position$1 += length)
|
|||
|
|
}
|
|||
|
|
function readExt(length) {
|
|||
|
|
let type = src[position$1++];
|
|||
|
|
if (currentExtensions[type]) {
|
|||
|
|
let end;
|
|||
|
|
return currentExtensions[type](src.subarray(position$1, end = (position$1 += length)), (readPosition) => {
|
|||
|
|
position$1 = readPosition;
|
|||
|
|
try {
|
|||
|
|
return read();
|
|||
|
|
} finally {
|
|||
|
|
position$1 = end;
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
throw new Error('Unknown extension type ' + type)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var keyCache = new Array(4096);
|
|||
|
|
function readKey() {
|
|||
|
|
let length = src[position$1++];
|
|||
|
|
if (length >= 0xa0 && length < 0xc0) {
|
|||
|
|
// fixstr, potentially use key cache
|
|||
|
|
length = length - 0xa0;
|
|||
|
|
if (srcStringEnd >= position$1) // if it has been extracted, must use it (and faster anyway)
|
|||
|
|
return srcString.slice(position$1 - srcStringStart, (position$1 += length) - srcStringStart)
|
|||
|
|
else if (!(srcStringEnd == 0 && srcEnd < 180))
|
|||
|
|
return readFixedString(length)
|
|||
|
|
} else { // not cacheable, go back and do a standard read
|
|||
|
|
position$1--;
|
|||
|
|
return asSafeString(read())
|
|||
|
|
}
|
|||
|
|
let key = ((length << 5) ^ (length > 1 ? dataView.getUint16(position$1) : length > 0 ? src[position$1] : 0)) & 0xfff;
|
|||
|
|
let entry = keyCache[key];
|
|||
|
|
let checkPosition = position$1;
|
|||
|
|
let end = position$1 + 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$1 = checkPosition;
|
|||
|
|
return entry.string
|
|||
|
|
}
|
|||
|
|
end -= 3;
|
|||
|
|
checkPosition = position$1;
|
|||
|
|
}
|
|||
|
|
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)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function asSafeString(property) {
|
|||
|
|
// protect against expensive (DoS) string conversions
|
|||
|
|
if (typeof property === 'string') return property;
|
|||
|
|
if (typeof property === 'number' || typeof property === 'boolean' || typeof property === 'bigint') return property.toString();
|
|||
|
|
if (property == null) return property + '';
|
|||
|
|
if (currentUnpackr.allowArraysInMapKeys && Array.isArray(property) && property.flat().every(item => ['string', 'number', 'boolean', 'bigint'].includes(typeof item))) {
|
|||
|
|
return property.flat().toString();
|
|||
|
|
}
|
|||
|
|
throw new Error(`Invalid property type for record: ${typeof property}`);
|
|||
|
|
}
|
|||
|
|
// the registration of the record definition extension (as "r")
|
|||
|
|
const recordDefinition = (id, highByte) => {
|
|||
|
|
let structure = read().map(asSafeString); // ensure that all keys are strings and
|
|||
|
|
// that the array is mutable
|
|||
|
|
let firstByte = id;
|
|||
|
|
if (highByte !== undefined) {
|
|||
|
|
id = id < 32 ? -((highByte << 5) + id) : ((highByte << 5) + id);
|
|||
|
|
structure.highByte = highByte;
|
|||
|
|
}
|
|||
|
|
let existingStructure = currentStructures[id];
|
|||
|
|
// If it is a shared structure, we need to restore any changes after reading.
|
|||
|
|
// Also in sequential mode, we may get incomplete reads and thus errors, and we need to restore
|
|||
|
|
// to the state prior to an incomplete read in order to properly resume.
|
|||
|
|
if (existingStructure && (existingStructure.isShared || sequentialMode)) {
|
|||
|
|
(currentStructures.restoreStructures || (currentStructures.restoreStructures = []))[id] = existingStructure;
|
|||
|
|
}
|
|||
|
|
currentStructures[id] = structure;
|
|||
|
|
structure.read = createStructureReader(structure, firstByte);
|
|||
|
|
// The high byte (if any) was already consumed as the `highByte` argument above, so read the
|
|||
|
|
// record body directly. Going through structure.read (a second-byte reader when highByte === 0)
|
|||
|
|
// would misinterpret the first value byte as a high byte — corrupting two-byte own-record
|
|||
|
|
// definitions (0xd5 0x72 ...). createStructureReader stashes the un-wrapped body reader on
|
|||
|
|
// structure.read0 precisely for this immediate post-definition read.
|
|||
|
|
return (structure.read0 || structure.read)()
|
|||
|
|
};
|
|||
|
|
currentExtensions[0] = () => {}; // notepack defines extension 0 to mean undefined, so use that as the default here
|
|||
|
|
currentExtensions[0].noBuffer = true;
|
|||
|
|
|
|||
|
|
currentExtensions[0x42] = data => {
|
|||
|
|
let headLength = (data.byteLength % 8) || 8;
|
|||
|
|
let head = BigInt(data[0] & 0x80 ? data[0] - 0x100 : data[0]);
|
|||
|
|
for (let i = 1; i < headLength; i++) {
|
|||
|
|
head <<= BigInt(8);
|
|||
|
|
head += BigInt(data[i]);
|
|||
|
|
}
|
|||
|
|
if (data.byteLength !== headLength) {
|
|||
|
|
let view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|||
|
|
let decode = (start, end) => {
|
|||
|
|
let length = end - start;
|
|||
|
|
if (length <= 40) {
|
|||
|
|
let out = view.getBigUint64(start);
|
|||
|
|
for (let i = start + 8; i < end; i += 8) {
|
|||
|
|
out <<= BigInt(64);
|
|||
|
|
out |= view.getBigUint64(i);
|
|||
|
|
}
|
|||
|
|
return out
|
|||
|
|
}
|
|||
|
|
// if (length === 8) return view.getBigUint64(start)
|
|||
|
|
let middle = start + (length >> 4 << 3);
|
|||
|
|
let left = decode(start, middle);
|
|||
|
|
let right = decode(middle, end);
|
|||
|
|
return (left << BigInt((end - middle) * 8)) | right
|
|||
|
|
};
|
|||
|
|
head = (head << BigInt((view.byteLength - headLength) * 8)) | decode(headLength, view.byteLength);
|
|||
|
|
}
|
|||
|
|
return head
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
let errors = {
|
|||
|
|
Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError, AggregateError: typeof AggregateError === 'function' ? AggregateError : null,
|
|||
|
|
};
|
|||
|
|
currentExtensions[0x65] = () => {
|
|||
|
|
let data = read();
|
|||
|
|
if (!errors[data[0]]) {
|
|||
|
|
let error = Error(data[1], { cause: data[2] });
|
|||
|
|
error.name = data[0];
|
|||
|
|
return error
|
|||
|
|
}
|
|||
|
|
return errors[data[0]](data[1], { cause: data[2] })
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
currentExtensions[0x69] = (data) => {
|
|||
|
|
// id extension (for structured clones)
|
|||
|
|
if (currentUnpackr.structuredClone === false) throw new Error('Structured clone extension is disabled')
|
|||
|
|
let id = dataView.getUint32(position$1 - 4);
|
|||
|
|
if (!referenceMap)
|
|||
|
|
referenceMap = new Map();
|
|||
|
|
let token = src[position$1];
|
|||
|
|
let target;
|
|||
|
|
// TODO: handle any other types that can cycle and make the code more robust if there are other extensions
|
|||
|
|
if (token >= 0x90 && token < 0xa0 || token == 0xdc || token == 0xdd)
|
|||
|
|
target = [];
|
|||
|
|
else if (token >= 0x80 && token < 0x90 || token == 0xde || token == 0xdf)
|
|||
|
|
target = new Map();
|
|||
|
|
else if ((token >= 0xc7 && token <= 0xc9 || token >= 0xd4 && token <= 0xd8) && src[position$1 + 1] === 0x73)
|
|||
|
|
target = new Set();
|
|||
|
|
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) {
|
|||
|
|
// no cycle, can just use the returned read object
|
|||
|
|
return refEntry.target = targetProperties // replace the placeholder with the real one
|
|||
|
|
} else {
|
|||
|
|
// there is a cycle, so we have to assign properties to original target
|
|||
|
|
Object.assign(target, targetProperties);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// copy over map/set entries if we're able to
|
|||
|
|
if (target instanceof Map)
|
|||
|
|
for (let [k, v] of targetProperties.entries()) target.set(k, v);
|
|||
|
|
if (target instanceof Set)
|
|||
|
|
for (let i of Array.from(targetProperties)) target.add(i);
|
|||
|
|
return target
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
currentExtensions[0x70] = (data) => {
|
|||
|
|
// pointer extension (for structured clones)
|
|||
|
|
if (currentUnpackr.structuredClone === false) throw new Error('Structured clone extension is disabled')
|
|||
|
|
let id = dataView.getUint32(position$1 - 4);
|
|||
|
|
let refEntry = referenceMap.get(id);
|
|||
|
|
refEntry.used = true;
|
|||
|
|
return refEntry.target
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
currentExtensions[0x73] = () => new Set(read());
|
|||
|
|
|
|||
|
|
const typedArrays = ['Int8','Uint8','Uint8Clamped','Int16','Uint16','Int32','Uint32','Float32','Float64','BigInt64','BigUint64'].map(type => type + 'Array');
|
|||
|
|
|
|||
|
|
let glbl = typeof globalThis === 'object' ? globalThis : window;
|
|||
|
|
currentExtensions[0x74] = (data) => {
|
|||
|
|
let typeCode = data[0];
|
|||
|
|
// we always have to slice to get a new ArrayBuffer that is aligned
|
|||
|
|
let buffer = Uint8Array.prototype.slice.call(data, 1).buffer;
|
|||
|
|
|
|||
|
|
let typedArrayName = typedArrays[typeCode];
|
|||
|
|
if (!typedArrayName) {
|
|||
|
|
if (typeCode === 16) return buffer
|
|||
|
|
if (typeCode === 17) return new DataView(buffer)
|
|||
|
|
throw new Error('Could not find typed array for code ' + typeCode)
|
|||
|
|
}
|
|||
|
|
return new glbl[typedArrayName](buffer)
|
|||
|
|
};
|
|||
|
|
currentExtensions[0x78] = () => {
|
|||
|
|
let data = read();
|
|||
|
|
return new RegExp(data[0], data[1])
|
|||
|
|
};
|
|||
|
|
const TEMP_BUNDLE = [];
|
|||
|
|
currentExtensions[0x62] = (data) => {
|
|||
|
|
let dataSize = (data[0] << 24) + (data[1] << 16) + (data[2] << 8) + data[3];
|
|||
|
|
let dataPosition = position$1;
|
|||
|
|
position$1 += dataSize - data.length;
|
|||
|
|
bundledStrings$1 = TEMP_BUNDLE;
|
|||
|
|
bundledStrings$1 = [readOnlyJSString(), readOnlyJSString()];
|
|||
|
|
bundledStrings$1.position0 = 0;
|
|||
|
|
bundledStrings$1.position1 = 0;
|
|||
|
|
bundledStrings$1.postBundlePosition = position$1;
|
|||
|
|
position$1 = dataPosition;
|
|||
|
|
return read()
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
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)
|
|||
|
|
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
|
|||
|
|
return new Date('invalid')
|
|||
|
|
};
|
|||
|
|
// registration of bulk record definition?
|
|||
|
|
// currentExtensions[0x52] = () =>
|
|||
|
|
|
|||
|
|
function saveState$1(callback) {
|
|||
|
|
if (onSaveState)
|
|||
|
|
onSaveState();
|
|||
|
|
let savedSrcEnd = srcEnd;
|
|||
|
|
let savedPosition = position$1;
|
|||
|
|
let savedStringPosition = stringPosition;
|
|||
|
|
let savedSrcStringStart = srcStringStart;
|
|||
|
|
let savedSrcStringEnd = srcStringEnd;
|
|||
|
|
let savedSrcString = srcString;
|
|||
|
|
let savedStrings = strings;
|
|||
|
|
let savedReferenceMap = referenceMap;
|
|||
|
|
let savedBundledStrings = bundledStrings$1;
|
|||
|
|
|
|||
|
|
// 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$1 = savedPosition;
|
|||
|
|
stringPosition = savedStringPosition;
|
|||
|
|
srcStringStart = savedSrcStringStart;
|
|||
|
|
srcStringEnd = savedSrcStringEnd;
|
|||
|
|
srcString = savedSrcString;
|
|||
|
|
strings = savedStrings;
|
|||
|
|
referenceMap = savedReferenceMap;
|
|||
|
|
bundledStrings$1 = savedBundledStrings;
|
|||
|
|
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
|
|||
|
|
}
|
|||
|
|
function clearSource() {
|
|||
|
|
src = null;
|
|||
|
|
referenceMap = null;
|
|||
|
|
currentStructures = null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function addExtension$2(extension) {
|
|||
|
|
if (extension.unpack)
|
|||
|
|
currentExtensions[extension.type] = extension.unpack;
|
|||
|
|
else
|
|||
|
|
currentExtensions[extension.type] = extension;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
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));
|
|||
|
|
}
|
|||
|
|
var defaultUnpackr = new Unpackr$1({ useRecords: false });
|
|||
|
|
const unpack$1 = defaultUnpackr.unpack;
|
|||
|
|
const unpackMultiple$1 = defaultUnpackr.unpackMultiple;
|
|||
|
|
defaultUnpackr.unpack;
|
|||
|
|
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);
|
|||
|
|
function roundFloat32$1(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
|
|||
|
|
}
|
|||
|
|
function setReadStruct(updatedReadStruct, loadedStructs, saveState) {
|
|||
|
|
readStruct$1 = updatedReadStruct;
|
|||
|
|
onLoadedStructures$1 = loadedStructs;
|
|||
|
|
onSaveState = saveState;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let textEncoder$1;
|
|||
|
|
try {
|
|||
|
|
textEncoder$1 = new TextEncoder();
|
|||
|
|
} catch (error) {}
|
|||
|
|
let extensions, extensionClasses;
|
|||
|
|
const hasNodeBuffer$1 = typeof Buffer !== 'undefined';
|
|||
|
|
const ByteArrayAllocate = hasNodeBuffer$1 ?
|
|||
|
|
function(length) { return Buffer.allocUnsafeSlow(length) } : Uint8Array;
|
|||
|
|
const ByteArray = hasNodeBuffer$1 ? Buffer : Uint8Array;
|
|||
|
|
const MAX_BUFFER_SIZE = hasNodeBuffer$1 ? 0x100000000 : 0x7fd00000;
|
|||
|
|
let target, keysTarget;
|
|||
|
|
let targetView;
|
|||
|
|
let position = 0;
|
|||
|
|
let safeEnd;
|
|||
|
|
let bundledStrings = null;
|
|||
|
|
let writeStructSlots;
|
|||
|
|
const MAX_BUNDLE_SIZE = 0x5500; // maximum characters such that the encoded bytes fits in 16 bits.
|
|||
|
|
const hasNonLatin = /[\u0080-\uFFFF]/;
|
|||
|
|
const RECORD_SYMBOL = Symbol('record-id');
|
|||
|
|
let Packr$1 = class Packr extends Unpackr$1 {
|
|||
|
|
constructor(options) {
|
|||
|
|
super(options);
|
|||
|
|
this.offset = 0;
|
|||
|
|
let start;
|
|||
|
|
let hasSharedUpdate;
|
|||
|
|
let structures;
|
|||
|
|
let referenceMap;
|
|||
|
|
let encodeUtf8 = ByteArray.prototype.utf8Write ? function(string, position) {
|
|||
|
|
return target.utf8Write(string, position, target.byteLength - position)
|
|||
|
|
} : (textEncoder$1 && textEncoder$1.encodeInto) ?
|
|||
|
|
function(string, position) {
|
|||
|
|
return textEncoder$1.encodeInto(string, target.subarray(position)).written
|
|||
|
|
} : false;
|
|||
|
|
|
|||
|
|
let packr = this;
|
|||
|
|
if (!options)
|
|||
|
|
options = {};
|
|||
|
|
let isSequential = options && options.sequential;
|
|||
|
|
let hasSharedStructures = options.structures || options.saveStructures;
|
|||
|
|
let maxSharedStructures = options.maxSharedStructures;
|
|||
|
|
if (maxSharedStructures == null)
|
|||
|
|
maxSharedStructures = hasSharedStructures ? 32 : 0;
|
|||
|
|
if (maxSharedStructures > 8160)
|
|||
|
|
throw new Error('Maximum maxSharedStructure is 8160')
|
|||
|
|
if (options.structuredClone && options.moreTypes == undefined) {
|
|||
|
|
this.moreTypes = true;
|
|||
|
|
}
|
|||
|
|
let maxOwnStructures = options.maxOwnStructures;
|
|||
|
|
if (maxOwnStructures == null)
|
|||
|
|
maxOwnStructures = hasSharedStructures ? 32 : 64;
|
|||
|
|
if (!this.structures && options.useRecords != false)
|
|||
|
|
this.structures = [];
|
|||
|
|
// two byte record ids for shared structures
|
|||
|
|
let useTwoByteRecords = maxSharedStructures > 32 || (maxOwnStructures + maxSharedStructures > 64);
|
|||
|
|
let sharedLimitId = maxSharedStructures + 0x40;
|
|||
|
|
let maxStructureId = maxSharedStructures + maxOwnStructures + 0x40;
|
|||
|
|
if (maxStructureId > 8256) {
|
|||
|
|
throw new Error('Maximum maxSharedStructure + maxOwnStructure is 8192')
|
|||
|
|
}
|
|||
|
|
let recordIdsToRemove = [];
|
|||
|
|
let transitionsCount = 0;
|
|||
|
|
let serializationsSinceTransitionRebuild = 0;
|
|||
|
|
|
|||
|
|
this.pack = this.encode = function(value, encodeOptions) {
|
|||
|
|
if (!target) {
|
|||
|
|
target = new ByteArrayAllocate(8192);
|
|||
|
|
targetView = target.dataView || (target.dataView = new DataView(target.buffer, 0, 8192));
|
|||
|
|
position = 0;
|
|||
|
|
}
|
|||
|
|
safeEnd = target.length - 10;
|
|||
|
|
if (safeEnd - position < 0x800) {
|
|||
|
|
// don't start too close to the end,
|
|||
|
|
target = new ByteArrayAllocate(target.length);
|
|||
|
|
targetView = target.dataView || (target.dataView = new DataView(target.buffer, 0, target.length));
|
|||
|
|
safeEnd = target.length - 10;
|
|||
|
|
position = 0;
|
|||
|
|
} else
|
|||
|
|
position = (position + 7) & 0x7ffffff8; // Word align to make any future copying of this buffer faster
|
|||
|
|
start = position;
|
|||
|
|
if (encodeOptions & RESERVE_START_SPACE) position += (encodeOptions & 0xff);
|
|||
|
|
referenceMap = packr.structuredClone ? new Map() : null;
|
|||
|
|
if (packr.bundleStrings && typeof value !== 'string') {
|
|||
|
|
bundledStrings = [];
|
|||
|
|
bundledStrings.size = Infinity; // force a new bundle start on first string
|
|||
|
|
} else
|
|||
|
|
bundledStrings = null;
|
|||
|
|
structures = packr.structures;
|
|||
|
|
if (structures) {
|
|||
|
|
if (structures.uninitialized)
|
|||
|
|
structures = packr._mergeStructures(packr.getStructures());
|
|||
|
|
let sharedLength = structures.sharedLength || 0;
|
|||
|
|
if (sharedLength > maxSharedStructures) {
|
|||
|
|
//if (maxSharedStructures <= 32 && structures.sharedLength > 32) // TODO: could support this, but would need to update the limit ids
|
|||
|
|
throw new Error('Shared structures is larger than maximum shared structures, try increasing maxSharedStructures to ' + structures.sharedLength)
|
|||
|
|
}
|
|||
|
|
if (!structures.transitions) {
|
|||
|
|
// rebuild our structure transitions
|
|||
|
|
structures.transitions = Object.create(null);
|
|||
|
|
for (let i = 0; i < sharedLength; i++) {
|
|||
|
|
let keys = structures[i];
|
|||
|
|
if (!keys)
|
|||
|
|
continue
|
|||
|
|
let nextTransition, transition = structures.transitions;
|
|||
|
|
for (let j = 0, l = keys.length; j < l; j++) {
|
|||
|
|
let key = keys[j];
|
|||
|
|
nextTransition = transition[key];
|
|||
|
|
if (!nextTransition) {
|
|||
|
|
nextTransition = transition[key] = Object.create(null);
|
|||
|
|
}
|
|||
|
|
transition = nextTransition;
|
|||
|
|
}
|
|||
|
|
transition[RECORD_SYMBOL] = i + 0x40;
|
|||
|
|
}
|
|||
|
|
this.lastNamedStructuresLength = sharedLength;
|
|||
|
|
}
|
|||
|
|
if (!isSequential) {
|
|||
|
|
structures.nextId = sharedLength + 0x40;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (hasSharedUpdate)
|
|||
|
|
hasSharedUpdate = false;
|
|||
|
|
let encodingError;
|
|||
|
|
try {
|
|||
|
|
// readOnlyStructures: skip the random-access struct write path so NO new struct is
|
|||
|
|
// minted. randomAccessStructure stays true (the struct READ path and the struct-safe
|
|||
|
|
// integer boundary are preserved, so existing struct data still decodes), but objects
|
|||
|
|
// fall through to the normal pack()->writeObject->writeRecord path and are written as
|
|||
|
|
// classic shared-structure records (byte range 0x40-0x7f, disjoint from struct headers
|
|||
|
|
// at 0x20-0x3f) — the bounded, width-agnostic encoding used before struct mode.
|
|||
|
|
if (packr.randomAccessStructure && !packr.readOnlyStructures && value && typeof value === 'object') {
|
|||
|
|
if (value.constructor === Object) writeStruct(value); // simple object
|
|||
|
|
else if (value.constructor !== Map && !Array.isArray(value) && !extensionClasses.some(extClass => value instanceof extClass)) {
|
|||
|
|
// allow user classes, if they don't need special handling (but do use toJSON if available)
|
|||
|
|
writeStruct(value.toJSON ? value.toJSON() : value);
|
|||
|
|
} else pack(value);
|
|||
|
|
} else
|
|||
|
|
pack(value);
|
|||
|
|
let lastBundle = bundledStrings;
|
|||
|
|
if (bundledStrings)
|
|||
|
|
writeBundles(start, pack, 0);
|
|||
|
|
if (referenceMap && referenceMap.idsToInsert) {
|
|||
|
|
let idsToInsert = referenceMap.idsToInsert.sort((a, b) => a.offset > b.offset ? 1 : -1);
|
|||
|
|
let i = idsToInsert.length;
|
|||
|
|
let incrementPosition = -1;
|
|||
|
|
while (lastBundle && i > 0) {
|
|||
|
|
let insertionPoint = idsToInsert[--i].offset + start;
|
|||
|
|
if (insertionPoint < (lastBundle.stringsPosition + start) && incrementPosition === -1)
|
|||
|
|
incrementPosition = 0;
|
|||
|
|
if (insertionPoint > (lastBundle.position + start)) {
|
|||
|
|
if (incrementPosition >= 0)
|
|||
|
|
incrementPosition += 6;
|
|||
|
|
} else {
|
|||
|
|
if (incrementPosition >= 0) {
|
|||
|
|
// update the bundle reference now
|
|||
|
|
targetView.setUint32(lastBundle.position + start,
|
|||
|
|
targetView.getUint32(lastBundle.position + start) + incrementPosition);
|
|||
|
|
incrementPosition = -1; // reset
|
|||
|
|
}
|
|||
|
|
lastBundle = lastBundle.previous;
|
|||
|
|
i++;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (incrementPosition >= 0 && lastBundle) {
|
|||
|
|
// update the bundle reference now
|
|||
|
|
targetView.setUint32(lastBundle.position + start,
|
|||
|
|
targetView.getUint32(lastBundle.position + start) + incrementPosition);
|
|||
|
|
}
|
|||
|
|
position += idsToInsert.length * 6;
|
|||
|
|
if (position > safeEnd)
|
|||
|
|
makeRoom(position);
|
|||
|
|
packr.offset = position;
|
|||
|
|
let serialized = insertIds(target.subarray(start, position), idsToInsert);
|
|||
|
|
referenceMap = null;
|
|||
|
|
return serialized
|
|||
|
|
}
|
|||
|
|
packr.offset = position; // update the offset so next serialization doesn't write over our buffer, but can continue writing to same buffer sequentially
|
|||
|
|
if (encodeOptions & REUSE_BUFFER_MODE) {
|
|||
|
|
target.start = start;
|
|||
|
|
target.end = position;
|
|||
|
|
return target
|
|||
|
|
}
|
|||
|
|
return target.subarray(start, position) // position can change if we call pack again in saveStructures, so we get the buffer now
|
|||
|
|
} catch(error) {
|
|||
|
|
encodingError = error;
|
|||
|
|
throw error;
|
|||
|
|
} finally {
|
|||
|
|
if (structures) {
|
|||
|
|
resetStructures();
|
|||
|
|
if (hasSharedUpdate && packr.saveStructures) {
|
|||
|
|
let sharedLength = structures.sharedLength || 0;
|
|||
|
|
// we can't rely on start/end with REUSE_BUFFER_MODE since they will (probably) change when we save
|
|||
|
|
let returnBuffer = target.subarray(start, position);
|
|||
|
|
let newSharedData = prepareStructures$1(structures, packr);
|
|||
|
|
if (!encodingError) { // TODO: If there is an encoding error, should make the structures as uninitialized so they get rebuilt next time
|
|||
|
|
if (packr.saveStructures(newSharedData, newSharedData.isCompatible) === false) {
|
|||
|
|
// The save was declined (a concurrent writer updated the shared structures,
|
|||
|
|
// or the store transaction did not durably commit). Our in-memory
|
|||
|
|
// structures + transition trie may now reference record ids that were
|
|||
|
|
// never persisted; re-packing as-is would re-emit the same record pointing
|
|||
|
|
// at an unpersisted structure (-> "Record id is not defined" on decode).
|
|||
|
|
// Mark structures uninitialized so the re-pack reloads durable structures
|
|||
|
|
// via getStructures, rebuilds the transition trie, and re-mints + re-saves.
|
|||
|
|
structures.uninitialized = true;
|
|||
|
|
return packr.pack(value, encodeOptions)
|
|||
|
|
}
|
|||
|
|
packr.lastNamedStructuresLength = sharedLength;
|
|||
|
|
// don't keep large buffers around
|
|||
|
|
if (target.length > 0x40000000) target = null;
|
|||
|
|
return returnBuffer
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// don't keep large buffers around, they take too much memory and cause problems (limit at 1GB)
|
|||
|
|
if (target.length > 0x40000000) target = null;
|
|||
|
|
if (encodeOptions & RESET_BUFFER_MODE)
|
|||
|
|
position = start;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
const resetStructures = () => {
|
|||
|
|
if (serializationsSinceTransitionRebuild < 10)
|
|||
|
|
serializationsSinceTransitionRebuild++;
|
|||
|
|
let sharedLength = structures.sharedLength || 0;
|
|||
|
|
if (structures.length > sharedLength && !isSequential)
|
|||
|
|
structures.length = sharedLength;
|
|||
|
|
if (transitionsCount > 10000) {
|
|||
|
|
// force a rebuild occasionally after a lot of transitions so it can get cleaned up
|
|||
|
|
structures.transitions = null;
|
|||
|
|
serializationsSinceTransitionRebuild = 0;
|
|||
|
|
transitionsCount = 0;
|
|||
|
|
if (recordIdsToRemove.length > 0)
|
|||
|
|
recordIdsToRemove = [];
|
|||
|
|
} else if (recordIdsToRemove.length > 0 && !isSequential) {
|
|||
|
|
for (let i = 0, l = recordIdsToRemove.length; i < l; i++) {
|
|||
|
|
recordIdsToRemove[i][RECORD_SYMBOL] = 0;
|
|||
|
|
}
|
|||
|
|
recordIdsToRemove = [];
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
const packArray = (value) => {
|
|||
|
|
var length = value.length;
|
|||
|
|
if (length < 0x10) {
|
|||
|
|
target[position++] = 0x90 | length;
|
|||
|
|
} else if (length < 0x10000) {
|
|||
|
|
target[position++] = 0xdc;
|
|||
|
|
target[position++] = length >> 8;
|
|||
|
|
target[position++] = length & 0xff;
|
|||
|
|
} else {
|
|||
|
|
target[position++] = 0xdd;
|
|||
|
|
targetView.setUint32(position, length);
|
|||
|
|
position += 4;
|
|||
|
|
}
|
|||
|
|
for (let i = 0; i < length; i++) {
|
|||
|
|
pack(value[i]);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
const pack = (value) => {
|
|||
|
|
if (position > safeEnd)
|
|||
|
|
target = makeRoom(position);
|
|||
|
|
|
|||
|
|
var type = typeof value;
|
|||
|
|
var length;
|
|||
|
|
if (type === 'string') {
|
|||
|
|
let strLength = value.length;
|
|||
|
|
if (bundledStrings && strLength >= 4 && strLength < 0x1000) {
|
|||
|
|
if ((bundledStrings.size += strLength) > MAX_BUNDLE_SIZE) {
|
|||
|
|
let extStart;
|
|||
|
|
let maxBytes = (bundledStrings[0] ? bundledStrings[0].length * 3 + bundledStrings[1].length : 0) + 10;
|
|||
|
|
if (position + maxBytes > safeEnd)
|
|||
|
|
target = makeRoom(position + maxBytes);
|
|||
|
|
let lastBundle;
|
|||
|
|
if (bundledStrings.position) { // here we use the 0x62 extension to write the last bundle and reserve space for the reference pointer to the next/current bundle
|
|||
|
|
lastBundle = bundledStrings;
|
|||
|
|
target[position] = 0xc8; // ext 16
|
|||
|
|
position += 3; // reserve for the writing bundle size
|
|||
|
|
target[position++] = 0x62; // 'b'
|
|||
|
|
extStart = position - start;
|
|||
|
|
position += 4; // reserve for writing bundle reference
|
|||
|
|
writeBundles(start, pack, 0); // write the last bundles
|
|||
|
|
targetView.setUint16(extStart + start - 3, position - start - extStart);
|
|||
|
|
} else { // here we use the 0x62 extension just to reserve the space for the reference pointer to the bundle (will be updated once the bundle is written)
|
|||
|
|
target[position++] = 0xd6; // fixext 4
|
|||
|
|
target[position++] = 0x62; // 'b'
|
|||
|
|
extStart = position - start;
|
|||
|
|
position += 4; // reserve for writing bundle reference
|
|||
|
|
}
|
|||
|
|
bundledStrings = ['', '']; // create new ones
|
|||
|
|
bundledStrings.previous = lastBundle;
|
|||
|
|
bundledStrings.size = 0;
|
|||
|
|
bundledStrings.position = extStart;
|
|||
|
|
}
|
|||
|
|
let twoByte = hasNonLatin.test(value);
|
|||
|
|
bundledStrings[twoByte ? 0 : 1] += value;
|
|||
|
|
target[position++] = 0xc1;
|
|||
|
|
pack(twoByte ? -strLength : strLength);
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
let headerSize;
|
|||
|
|
// first we estimate the header size, so we can write to the correct location
|
|||
|
|
if (strLength < 0x20) {
|
|||
|
|
headerSize = 1;
|
|||
|
|
} else if (strLength < 0x100) {
|
|||
|
|
headerSize = 2;
|
|||
|
|
} else if (strLength < 0x10000) {
|
|||
|
|
headerSize = 3;
|
|||
|
|
} else {
|
|||
|
|
headerSize = 5;
|
|||
|
|
}
|
|||
|
|
let maxBytes = strLength * 3;
|
|||
|
|
if (position + maxBytes > safeEnd)
|
|||
|
|
target = makeRoom(position + maxBytes);
|
|||
|
|
|
|||
|
|
if (strLength < 0x40 || !encodeUtf8) {
|
|||
|
|
let i, c1, c2, strPosition = position + headerSize;
|
|||
|
|
for (i = 0; i < strLength; i++) {
|
|||
|
|
c1 = value.charCodeAt(i);
|
|||
|
|
if (c1 < 0x80) {
|
|||
|
|
target[strPosition++] = c1;
|
|||
|
|
} else if (c1 < 0x800) {
|
|||
|
|
target[strPosition++] = c1 >> 6 | 0xc0;
|
|||
|
|
target[strPosition++] = c1 & 0x3f | 0x80;
|
|||
|
|
} else if (
|
|||
|
|
(c1 & 0xfc00) === 0xd800 &&
|
|||
|
|
((c2 = value.charCodeAt(i + 1)) & 0xfc00) === 0xdc00
|
|||
|
|
) {
|
|||
|
|
c1 = 0x10000 + ((c1 & 0x03ff) << 10) + (c2 & 0x03ff);
|
|||
|
|
i++;
|
|||
|
|
target[strPosition++] = c1 >> 18 | 0xf0;
|
|||
|
|
target[strPosition++] = c1 >> 12 & 0x3f | 0x80;
|
|||
|
|
target[strPosition++] = c1 >> 6 & 0x3f | 0x80;
|
|||
|
|
target[strPosition++] = c1 & 0x3f | 0x80;
|
|||
|
|
} else {
|
|||
|
|
target[strPosition++] = c1 >> 12 | 0xe0;
|
|||
|
|
target[strPosition++] = c1 >> 6 & 0x3f | 0x80;
|
|||
|
|
target[strPosition++] = c1 & 0x3f | 0x80;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
length = strPosition - position - headerSize;
|
|||
|
|
} else {
|
|||
|
|
length = encodeUtf8(value, position + headerSize);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (length < 0x20) {
|
|||
|
|
target[position++] = 0xa0 | length;
|
|||
|
|
} else if (length < 0x100) {
|
|||
|
|
if (headerSize < 2) {
|
|||
|
|
target.copyWithin(position + 2, position + 1, position + 1 + length);
|
|||
|
|
}
|
|||
|
|
target[position++] = 0xd9;
|
|||
|
|
target[position++] = length;
|
|||
|
|
} else if (length < 0x10000) {
|
|||
|
|
if (headerSize < 3) {
|
|||
|
|
target.copyWithin(position + 3, position + 2, position + 2 + length);
|
|||
|
|
}
|
|||
|
|
target[position++] = 0xda;
|
|||
|
|
target[position++] = length >> 8;
|
|||
|
|
target[position++] = length & 0xff;
|
|||
|
|
} else {
|
|||
|
|
if (headerSize < 5) {
|
|||
|
|
target.copyWithin(position + 5, position + 3, position + 3 + length);
|
|||
|
|
}
|
|||
|
|
target[position++] = 0xdb;
|
|||
|
|
targetView.setUint32(position, length);
|
|||
|
|
position += 4;
|
|||
|
|
}
|
|||
|
|
position += length;
|
|||
|
|
} else if (type === 'number') {
|
|||
|
|
if (value >>> 0 === value) {// positive integer, 32-bit or less
|
|||
|
|
// positive uint
|
|||
|
|
if (value < 0x20 || (value < 0x80 && this.useRecords === false) || (value < 0x40 && !this.randomAccessStructure)) {
|
|||
|
|
target[position++] = value;
|
|||
|
|
} else if (value < 0x100) {
|
|||
|
|
target[position++] = 0xcc;
|
|||
|
|
target[position++] = value;
|
|||
|
|
} else if (value < 0x10000) {
|
|||
|
|
target[position++] = 0xcd;
|
|||
|
|
target[position++] = value >> 8;
|
|||
|
|
target[position++] = value & 0xff;
|
|||
|
|
} else {
|
|||
|
|
target[position++] = 0xce;
|
|||
|
|
targetView.setUint32(position, value);
|
|||
|
|
position += 4;
|
|||
|
|
}
|
|||
|
|
} else if (value >> 0 === value) { // negative integer
|
|||
|
|
if (value >= -0x20) {
|
|||
|
|
target[position++] = 0x100 + value;
|
|||
|
|
} else if (value >= -0x80) {
|
|||
|
|
target[position++] = 0xd0;
|
|||
|
|
target[position++] = value + 0x100;
|
|||
|
|
} else if (value >= -0x8000) {
|
|||
|
|
target[position++] = 0xd1;
|
|||
|
|
targetView.setInt16(position, value);
|
|||
|
|
position += 2;
|
|||
|
|
} else {
|
|||
|
|
target[position++] = 0xd2;
|
|||
|
|
targetView.setInt32(position, value);
|
|||
|
|
position += 4;
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
let useFloat32;
|
|||
|
|
if ((useFloat32 = this.useFloat32) > 0 && value < 0x100000000 && value >= -0x80000000) {
|
|||
|
|
target[position++] = 0xca;
|
|||
|
|
targetView.setFloat32(position, value);
|
|||
|
|
let xShifted;
|
|||
|
|
if (useFloat32 < 4 ||
|
|||
|
|
// this checks for rounding of numbers that were encoded in 32-bit float to nearest significant decimal digit that could be preserved
|
|||
|
|
((xShifted = value * mult10[((target[position] & 0x7f) << 1) | (target[position + 1] >> 7)]) >> 0) === xShifted) {
|
|||
|
|
position += 4;
|
|||
|
|
return
|
|||
|
|
} else
|
|||
|
|
position--; // move back into position for writing a double
|
|||
|
|
}
|
|||
|
|
target[position++] = 0xcb;
|
|||
|
|
targetView.setFloat64(position, value);
|
|||
|
|
position += 8;
|
|||
|
|
}
|
|||
|
|
} else if (type === 'object' || type === 'function') {
|
|||
|
|
if (!value)
|
|||
|
|
target[position++] = 0xc0;
|
|||
|
|
else {
|
|||
|
|
if (referenceMap) {
|
|||
|
|
let referee = referenceMap.get(value);
|
|||
|
|
if (referee) {
|
|||
|
|
if (!referee.id) {
|
|||
|
|
let idsToInsert = referenceMap.idsToInsert || (referenceMap.idsToInsert = []);
|
|||
|
|
referee.id = idsToInsert.push(referee);
|
|||
|
|
}
|
|||
|
|
target[position++] = 0xd6; // fixext 4
|
|||
|
|
target[position++] = 0x70; // "p" for pointer
|
|||
|
|
targetView.setUint32(position, referee.id);
|
|||
|
|
position += 4;
|
|||
|
|
return
|
|||
|
|
} else
|
|||
|
|
referenceMap.set(value, { offset: position - start });
|
|||
|
|
}
|
|||
|
|
let constructor = value.constructor;
|
|||
|
|
if (constructor === Object) {
|
|||
|
|
writeObject(value);
|
|||
|
|
} else if (constructor === Array) {
|
|||
|
|
packArray(value);
|
|||
|
|
} else if (constructor === Map) {
|
|||
|
|
if (this.mapAsEmptyObject) target[position++] = 0x80;
|
|||
|
|
else {
|
|||
|
|
length = value.size;
|
|||
|
|
if (length < 0x10) {
|
|||
|
|
target[position++] = 0x80 | length;
|
|||
|
|
} else if (length < 0x10000) {
|
|||
|
|
target[position++] = 0xde;
|
|||
|
|
target[position++] = length >> 8;
|
|||
|
|
target[position++] = length & 0xff;
|
|||
|
|
} else {
|
|||
|
|
target[position++] = 0xdf;
|
|||
|
|
targetView.setUint32(position, length);
|
|||
|
|
position += 4;
|
|||
|
|
}
|
|||
|
|
for (let [key, entryValue] of value) {
|
|||
|
|
pack(key);
|
|||
|
|
pack(entryValue);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
for (let i = 0, l = extensions.length; i < l; i++) {
|
|||
|
|
let extensionClass = extensionClasses[i];
|
|||
|
|
if (value instanceof extensionClass) {
|
|||
|
|
let extension = extensions[i];
|
|||
|
|
if (extension.write) {
|
|||
|
|
if (extension.type) {
|
|||
|
|
target[position++] = 0xd4; // one byte "tag" extension
|
|||
|
|
target[position++] = extension.type;
|
|||
|
|
target[position++] = 0;
|
|||
|
|
}
|
|||
|
|
let writeResult = extension.write.call(this, value);
|
|||
|
|
if (writeResult === value) { // avoid infinite recursion
|
|||
|
|
if (Array.isArray(value)) {
|
|||
|
|
packArray(value);
|
|||
|
|
} else {
|
|||
|
|
writeObject(value);
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
pack(writeResult);
|
|||
|
|
}
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
let currentTarget = target;
|
|||
|
|
let currentTargetView = targetView;
|
|||
|
|
let currentPosition = position;
|
|||
|
|
target = null;
|
|||
|
|
let result;
|
|||
|
|
try {
|
|||
|
|
result = extension.pack.call(this, value, (size) => {
|
|||
|
|
// restore target and use it
|
|||
|
|
target = currentTarget;
|
|||
|
|
currentTarget = null;
|
|||
|
|
position += size;
|
|||
|
|
if (position > safeEnd)
|
|||
|
|
makeRoom(position);
|
|||
|
|
return {
|
|||
|
|
target, targetView, position: position - size
|
|||
|
|
}
|
|||
|
|
}, pack);
|
|||
|
|
} finally {
|
|||
|
|
// restore current target information (unless already restored)
|
|||
|
|
if (currentTarget) {
|
|||
|
|
target = currentTarget;
|
|||
|
|
targetView = currentTargetView;
|
|||
|
|
position = currentPosition;
|
|||
|
|
safeEnd = target.length - 10;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (result) {
|
|||
|
|
if (result.length + position > safeEnd)
|
|||
|
|
makeRoom(result.length + position);
|
|||
|
|
position = writeExtensionData(result, target, position, extension.type);
|
|||
|
|
}
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// check isArray after extensions, because extensions can extend Array
|
|||
|
|
if (Array.isArray(value)) {
|
|||
|
|
packArray(value);
|
|||
|
|
} else {
|
|||
|
|
// use this as an alternate mechanism for expressing how to serialize
|
|||
|
|
if (value.toJSON) {
|
|||
|
|
const json = value.toJSON();
|
|||
|
|
// if for some reason value.toJSON returns itself it'll loop forever
|
|||
|
|
if (json !== value)
|
|||
|
|
return pack(json)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// if there is a writeFunction, use it, otherwise just encode as undefined
|
|||
|
|
if (type === 'function')
|
|||
|
|
return pack(this.writeFunction && this.writeFunction(value));
|
|||
|
|
|
|||
|
|
// no extension found, write as plain object
|
|||
|
|
writeObject(value);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else if (type === 'boolean') {
|
|||
|
|
target[position++] = value ? 0xc3 : 0xc2;
|
|||
|
|
} else if (type === 'bigint') {
|
|||
|
|
if (value < 0x8000000000000000 && value >= -0x8000000000000000) {
|
|||
|
|
// use a signed int as long as it fits
|
|||
|
|
target[position++] = 0xd3;
|
|||
|
|
targetView.setBigInt64(position, value);
|
|||
|
|
} else if (value < 0x10000000000000000 && value > 0) {
|
|||
|
|
// if we can fit an unsigned int, use that
|
|||
|
|
target[position++] = 0xcf;
|
|||
|
|
targetView.setBigUint64(position, value);
|
|||
|
|
} else {
|
|||
|
|
// overflow
|
|||
|
|
if (this.largeBigIntToFloat) {
|
|||
|
|
target[position++] = 0xcb;
|
|||
|
|
targetView.setFloat64(position, Number(value));
|
|||
|
|
} else if (this.largeBigIntToString) {
|
|||
|
|
return pack(value.toString());
|
|||
|
|
} else if (this.useBigIntExtension || this.moreTypes) {
|
|||
|
|
let empty = value < 0 ? BigInt(-1) : BigInt(0);
|
|||
|
|
|
|||
|
|
let array;
|
|||
|
|
if (value >> BigInt(0x10000) === empty) {
|
|||
|
|
let mask = BigInt(0x10000000000000000) - BigInt(1); // literal would overflow
|
|||
|
|
let chunks = [];
|
|||
|
|
while (true) {
|
|||
|
|
chunks.push(value & mask);
|
|||
|
|
if ((value >> BigInt(63)) === empty) break
|
|||
|
|
value >>= BigInt(64);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
array = new Uint8Array(new BigUint64Array(chunks).buffer);
|
|||
|
|
array.reverse();
|
|||
|
|
} else {
|
|||
|
|
let invert = value < 0;
|
|||
|
|
let string = (invert ? ~value : value).toString(16);
|
|||
|
|
if (string.length % 2) {
|
|||
|
|
string = '0' + string;
|
|||
|
|
} else if (parseInt(string.charAt(0), 16) >= 8) {
|
|||
|
|
string = '00' + string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (hasNodeBuffer$1) {
|
|||
|
|
array = Buffer.from(string, 'hex');
|
|||
|
|
} else {
|
|||
|
|
array = new Uint8Array(string.length / 2);
|
|||
|
|
for (let i = 0; i < array.length; i++) {
|
|||
|
|
array[i] = parseInt(string.slice(i * 2, i * 2 + 2), 16);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (invert) {
|
|||
|
|
for (let i = 0; i < array.length; i++) array[i] = ~array[i];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (array.length + position > safeEnd)
|
|||
|
|
makeRoom(array.length + position);
|
|||
|
|
position = writeExtensionData(array, target, position, 0x42);
|
|||
|
|
return
|
|||
|
|
} else {
|
|||
|
|
throw new RangeError(value + ' was too large to fit in MessagePack 64-bit integer format, use' +
|
|||
|
|
' useBigIntExtension, or set largeBigIntToFloat to convert to float-64, or set' +
|
|||
|
|
' largeBigIntToString to convert to string')
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
position += 8;
|
|||
|
|
} else if (type === 'undefined') {
|
|||
|
|
if (this.encodeUndefinedAsNil)
|
|||
|
|
target[position++] = 0xc0;
|
|||
|
|
else {
|
|||
|
|
target[position++] = 0xd4; // a number of implementations use fixext1 with type 0, data 0 to denote undefined, so we follow suite
|
|||
|
|
target[position++] = 0;
|
|||
|
|
target[position++] = 0;
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
throw new Error('Unknown type: ' + type)
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const writePlainObject = (this.variableMapSize || this.coercibleKeyAsNumber || this.skipValues) ? (object) => {
|
|||
|
|
// this method is slightly slower, but generates "preferred serialization" (optimally small for smaller objects)
|
|||
|
|
let keys;
|
|||
|
|
if (this.skipValues) {
|
|||
|
|
keys = [];
|
|||
|
|
for (let key in object) {
|
|||
|
|
if ((typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) &&
|
|||
|
|
!this.skipValues.includes(object[key]))
|
|||
|
|
keys.push(key);
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
keys = Object.keys(object);
|
|||
|
|
}
|
|||
|
|
let length = keys.length;
|
|||
|
|
if (length < 0x10) {
|
|||
|
|
target[position++] = 0x80 | length;
|
|||
|
|
} else if (length < 0x10000) {
|
|||
|
|
target[position++] = 0xde;
|
|||
|
|
target[position++] = length >> 8;
|
|||
|
|
target[position++] = length & 0xff;
|
|||
|
|
} else {
|
|||
|
|
target[position++] = 0xdf;
|
|||
|
|
targetView.setUint32(position, length);
|
|||
|
|
position += 4;
|
|||
|
|
}
|
|||
|
|
let key;
|
|||
|
|
if (this.coercibleKeyAsNumber) {
|
|||
|
|
for (let i = 0; i < length; i++) {
|
|||
|
|
key = keys[i];
|
|||
|
|
let num = Number(key);
|
|||
|
|
pack(isNaN(num) ? key : num);
|
|||
|
|
pack(object[key]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
} else {
|
|||
|
|
for (let i = 0; i < length; i++) {
|
|||
|
|
pack(key = keys[i]);
|
|||
|
|
pack(object[key]);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} :
|
|||
|
|
(object) => {
|
|||
|
|
target[position++] = 0xde; // always using map 16, so we can preallocate and set the length afterwards
|
|||
|
|
let objectOffset = position - start;
|
|||
|
|
position += 2;
|
|||
|
|
let size = 0;
|
|||
|
|
for (let key in object) {
|
|||
|
|
if (typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) {
|
|||
|
|
pack(key);
|
|||
|
|
pack(object[key]);
|
|||
|
|
size++;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (size > 0xffff) {
|
|||
|
|
throw new Error('Object is too large to serialize with fast 16-bit map size,' +
|
|||
|
|
' use the "variableMapSize" option to serialize this object');
|
|||
|
|
}
|
|||
|
|
target[objectOffset++ + start] = size >> 8;
|
|||
|
|
target[objectOffset + start] = size & 0xff;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const writeRecord = this.useRecords === false ? writePlainObject :
|
|||
|
|
(options.progressiveRecords && !useTwoByteRecords) ? // this is about 2% faster for highly stable structures, since it only requires one for-in loop (but much more expensive when new structure needs to be written)
|
|||
|
|
(object) => {
|
|||
|
|
let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null));
|
|||
|
|
let objectOffset = position++ - start;
|
|||
|
|
let wroteKeys;
|
|||
|
|
for (let key in object) {
|
|||
|
|
if (typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) {
|
|||
|
|
nextTransition = transition[key];
|
|||
|
|
if (nextTransition)
|
|||
|
|
transition = nextTransition;
|
|||
|
|
else {
|
|||
|
|
// record doesn't exist, create full new record and insert it
|
|||
|
|
let keys = Object.keys(object);
|
|||
|
|
let lastTransition = transition;
|
|||
|
|
transition = structures.transitions;
|
|||
|
|
let newTransitions = 0;
|
|||
|
|
for (let i = 0, l = keys.length; i < l; i++) {
|
|||
|
|
let key = keys[i];
|
|||
|
|
nextTransition = transition[key];
|
|||
|
|
if (!nextTransition) {
|
|||
|
|
nextTransition = transition[key] = Object.create(null);
|
|||
|
|
newTransitions++;
|
|||
|
|
}
|
|||
|
|
transition = nextTransition;
|
|||
|
|
}
|
|||
|
|
if (objectOffset + start + 1 == position) {
|
|||
|
|
// first key, so we don't need to insert, we can just write record directly
|
|||
|
|
position--;
|
|||
|
|
newRecord(transition, keys, newTransitions);
|
|||
|
|
} else // otherwise we need to insert the record, moving existing data after the record
|
|||
|
|
insertNewRecord(transition, keys, objectOffset, newTransitions);
|
|||
|
|
wroteKeys = true;
|
|||
|
|
transition = lastTransition[key];
|
|||
|
|
}
|
|||
|
|
pack(object[key]);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (!wroteKeys) {
|
|||
|
|
let recordId = transition[RECORD_SYMBOL];
|
|||
|
|
if (recordId)
|
|||
|
|
target[objectOffset + start] = recordId;
|
|||
|
|
else
|
|||
|
|
insertNewRecord(transition, Object.keys(object), objectOffset, 0);
|
|||
|
|
}
|
|||
|
|
} :
|
|||
|
|
(object) => {
|
|||
|
|
let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null));
|
|||
|
|
let newTransitions = 0;
|
|||
|
|
for (let key in object) if (typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) {
|
|||
|
|
nextTransition = transition[key];
|
|||
|
|
if (!nextTransition) {
|
|||
|
|
nextTransition = transition[key] = Object.create(null);
|
|||
|
|
newTransitions++;
|
|||
|
|
}
|
|||
|
|
transition = nextTransition;
|
|||
|
|
}
|
|||
|
|
let recordId = transition[RECORD_SYMBOL];
|
|||
|
|
if (recordId) {
|
|||
|
|
if (recordId >= 0x60 && useTwoByteRecords) {
|
|||
|
|
target[position++] = ((recordId -= 0x60) & 0x1f) + 0x60;
|
|||
|
|
target[position++] = recordId >> 5;
|
|||
|
|
} else
|
|||
|
|
target[position++] = recordId;
|
|||
|
|
} else {
|
|||
|
|
newRecord(transition, transition.__keys__ || Object.keys(object), newTransitions);
|
|||
|
|
}
|
|||
|
|
// now write the values
|
|||
|
|
for (let key in object)
|
|||
|
|
if (typeof object.hasOwnProperty !== 'function' || object.hasOwnProperty(key)) {
|
|||
|
|
pack(object[key]);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// create reference to useRecords if useRecords is a function
|
|||
|
|
const checkUseRecords = typeof this.useRecords == 'function' && this.useRecords;
|
|||
|
|
|
|||
|
|
const writeObject = checkUseRecords ? (object) => {
|
|||
|
|
checkUseRecords(object) ? writeRecord(object) : writePlainObject(object);
|
|||
|
|
} : writeRecord;
|
|||
|
|
|
|||
|
|
const makeRoom = (end) => {
|
|||
|
|
let newSize;
|
|||
|
|
if (end > 0x1000000) {
|
|||
|
|
// special handling for really large buffers
|
|||
|
|
if ((end - start) > MAX_BUFFER_SIZE)
|
|||
|
|
throw new Error('Packed buffer would be larger than maximum buffer size')
|
|||
|
|
newSize = Math.min(MAX_BUFFER_SIZE,
|
|||
|
|
Math.round(Math.max((end - start) * (end > 0x4000000 ? 1.25 : 2), 0x400000) / 0x1000) * 0x1000);
|
|||
|
|
} else // faster handling for smaller buffers
|
|||
|
|
newSize = ((Math.max((end - start) << 2, target.length - 1) >> 12) + 1) << 12;
|
|||
|
|
let newBuffer = new ByteArrayAllocate(newSize);
|
|||
|
|
targetView = newBuffer.dataView || (newBuffer.dataView = new DataView(newBuffer.buffer, 0, newSize));
|
|||
|
|
end = Math.min(end, target.length);
|
|||
|
|
if (target.copy)
|
|||
|
|
target.copy(newBuffer, 0, start, end);
|
|||
|
|
else
|
|||
|
|
newBuffer.set(target.slice(start, end));
|
|||
|
|
position -= start;
|
|||
|
|
start = 0;
|
|||
|
|
safeEnd = newBuffer.length - 10;
|
|||
|
|
return target = newBuffer
|
|||
|
|
};
|
|||
|
|
const newRecord = (transition, keys, newTransitions) => {
|
|||
|
|
let recordId = structures.nextId;
|
|||
|
|
if (!recordId)
|
|||
|
|
recordId = 0x40;
|
|||
|
|
if (recordId < sharedLimitId && this.shouldShareStructure && !this.shouldShareStructure(keys)) {
|
|||
|
|
recordId = structures.nextOwnId;
|
|||
|
|
if (!(recordId < maxStructureId))
|
|||
|
|
recordId = sharedLimitId;
|
|||
|
|
structures.nextOwnId = recordId + 1;
|
|||
|
|
} else {
|
|||
|
|
if (recordId >= maxStructureId)// cycle back around
|
|||
|
|
recordId = sharedLimitId;
|
|||
|
|
structures.nextId = recordId + 1;
|
|||
|
|
}
|
|||
|
|
let highByte = keys.highByte = recordId >= 0x60 && useTwoByteRecords ? (recordId - 0x60) >> 5 : -1;
|
|||
|
|
transition[RECORD_SYMBOL] = recordId;
|
|||
|
|
transition.__keys__ = keys;
|
|||
|
|
structures[recordId - 0x40] = keys;
|
|||
|
|
|
|||
|
|
if (recordId < sharedLimitId) {
|
|||
|
|
keys.isShared = true;
|
|||
|
|
structures.sharedLength = recordId - 0x3f;
|
|||
|
|
hasSharedUpdate = true;
|
|||
|
|
if (highByte >= 0) {
|
|||
|
|
target[position++] = (recordId & 0x1f) + 0x60;
|
|||
|
|
target[position++] = highByte;
|
|||
|
|
} else {
|
|||
|
|
target[position++] = recordId;
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
if (highByte >= 0) {
|
|||
|
|
target[position++] = 0xd5; // fixext 2
|
|||
|
|
target[position++] = 0x72; // "r" record defintion extension type
|
|||
|
|
target[position++] = (recordId & 0x1f) + 0x60;
|
|||
|
|
target[position++] = highByte;
|
|||
|
|
} else {
|
|||
|
|
target[position++] = 0xd4; // fixext 1
|
|||
|
|
target[position++] = 0x72; // "r" record defintion extension type
|
|||
|
|
target[position++] = recordId;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (newTransitions)
|
|||
|
|
transitionsCount += serializationsSinceTransitionRebuild * newTransitions;
|
|||
|
|
// record the removal of the id, we can maintain our shared structure
|
|||
|
|
if (recordIdsToRemove.length >= maxOwnStructures)
|
|||
|
|
recordIdsToRemove.shift()[RECORD_SYMBOL] = 0; // we are cycling back through, and have to remove old ones
|
|||
|
|
recordIdsToRemove.push(transition);
|
|||
|
|
pack(keys);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
const insertNewRecord = (transition, keys, insertionOffset, newTransitions) => {
|
|||
|
|
let mainTarget = target;
|
|||
|
|
let mainPosition = position;
|
|||
|
|
let mainSafeEnd = safeEnd;
|
|||
|
|
let mainStart = start;
|
|||
|
|
target = keysTarget;
|
|||
|
|
position = 0;
|
|||
|
|
start = 0;
|
|||
|
|
if (!target)
|
|||
|
|
keysTarget = target = new ByteArrayAllocate(8192);
|
|||
|
|
safeEnd = target.length - 10;
|
|||
|
|
newRecord(transition, keys, newTransitions);
|
|||
|
|
keysTarget = target;
|
|||
|
|
let keysPosition = position;
|
|||
|
|
target = mainTarget;
|
|||
|
|
position = mainPosition;
|
|||
|
|
safeEnd = mainSafeEnd;
|
|||
|
|
start = mainStart;
|
|||
|
|
if (keysPosition > 1) {
|
|||
|
|
let newEnd = position + keysPosition - 1;
|
|||
|
|
if (newEnd > safeEnd)
|
|||
|
|
makeRoom(newEnd);
|
|||
|
|
let insertionPosition = insertionOffset + start;
|
|||
|
|
target.copyWithin(insertionPosition + keysPosition, insertionPosition + 1, position);
|
|||
|
|
target.set(keysTarget.slice(0, keysPosition), insertionPosition);
|
|||
|
|
position = newEnd;
|
|||
|
|
} else {
|
|||
|
|
target[insertionOffset + start] = keysTarget[0];
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
const writeStruct = (object) => {
|
|||
|
|
let newPosition = writeStructSlots(object, target, start, position, structures, makeRoom, (value, newPosition, notifySharedUpdate) => {
|
|||
|
|
if (notifySharedUpdate)
|
|||
|
|
return hasSharedUpdate = true;
|
|||
|
|
position = newPosition;
|
|||
|
|
let startTarget = target;
|
|||
|
|
pack(value);
|
|||
|
|
resetStructures();
|
|||
|
|
if (startTarget !== target) {
|
|||
|
|
return { position, targetView, target }; // indicate the buffer was re-allocated
|
|||
|
|
}
|
|||
|
|
return position;
|
|||
|
|
}, this);
|
|||
|
|
if (newPosition === 0) // bail and go to a msgpack object
|
|||
|
|
return writeObject(object);
|
|||
|
|
position = newPosition;
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
useBuffer(buffer) {
|
|||
|
|
// this means we are finished using our own buffer and we can write over it safely
|
|||
|
|
target = buffer;
|
|||
|
|
target.dataView || (target.dataView = new DataView(target.buffer, target.byteOffset, target.byteLength));
|
|||
|
|
targetView = target.dataView;
|
|||
|
|
position = 0;
|
|||
|
|
}
|
|||
|
|
set position (value) {
|
|||
|
|
position = value;
|
|||
|
|
}
|
|||
|
|
get position() {
|
|||
|
|
return position;
|
|||
|
|
}
|
|||
|
|
clearSharedData() {
|
|||
|
|
if (this.structures)
|
|||
|
|
this.structures = [];
|
|||
|
|
if (this.typedStructs)
|
|||
|
|
this.typedStructs = [];
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
extensionClasses = [ Date, Set, Error, RegExp, ArrayBuffer, Object.getPrototypeOf(Uint8Array.prototype).constructor /*TypedArray*/, DataView, C1Type ];
|
|||
|
|
extensions = [{
|
|||
|
|
pack(date, allocateForWrite, pack) {
|
|||
|
|
let seconds = date.getTime() / 1000;
|
|||
|
|
if ((this.useTimestamp32 || date.getMilliseconds() === 0) && seconds >= 0 && seconds < 0x100000000) {
|
|||
|
|
// Timestamp 32
|
|||
|
|
let { target, targetView, position} = allocateForWrite(6);
|
|||
|
|
target[position++] = 0xd6;
|
|||
|
|
target[position++] = 0xff;
|
|||
|
|
targetView.setUint32(position, seconds);
|
|||
|
|
} else if (seconds > 0 && seconds < 0x100000000) {
|
|||
|
|
// Timestamp 64
|
|||
|
|
let { target, targetView, position} = allocateForWrite(10);
|
|||
|
|
target[position++] = 0xd7;
|
|||
|
|
target[position++] = 0xff;
|
|||
|
|
targetView.setUint32(position, date.getMilliseconds() * 4000000 + ((seconds / 1000 / 0x100000000) >> 0));
|
|||
|
|
targetView.setUint32(position + 4, seconds);
|
|||
|
|
} else if (isNaN(seconds)) {
|
|||
|
|
if (this.onInvalidDate) {
|
|||
|
|
allocateForWrite(0);
|
|||
|
|
return pack(this.onInvalidDate())
|
|||
|
|
}
|
|||
|
|
// Intentionally invalid timestamp
|
|||
|
|
let { target, targetView, position} = allocateForWrite(3);
|
|||
|
|
target[position++] = 0xd4;
|
|||
|
|
target[position++] = 0xff;
|
|||
|
|
target[position++] = 0xff;
|
|||
|
|
} else {
|
|||
|
|
// Timestamp 96
|
|||
|
|
let { target, targetView, position} = allocateForWrite(15);
|
|||
|
|
target[position++] = 0xc7;
|
|||
|
|
target[position++] = 12;
|
|||
|
|
target[position++] = 0xff;
|
|||
|
|
targetView.setUint32(position, date.getMilliseconds() * 1000000);
|
|||
|
|
targetView.setBigInt64(position + 4, BigInt(Math.floor(seconds)));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}, {
|
|||
|
|
pack(set, allocateForWrite, pack) {
|
|||
|
|
if (this.setAsEmptyObject) {
|
|||
|
|
allocateForWrite(0);
|
|||
|
|
return pack({})
|
|||
|
|
}
|
|||
|
|
let array = Array.from(set);
|
|||
|
|
let { target, position} = allocateForWrite(this.moreTypes ? 3 : 0);
|
|||
|
|
if (this.moreTypes) {
|
|||
|
|
target[position++] = 0xd4;
|
|||
|
|
target[position++] = 0x73; // 's' for Set
|
|||
|
|
target[position++] = 0;
|
|||
|
|
}
|
|||
|
|
pack(array);
|
|||
|
|
}
|
|||
|
|
}, {
|
|||
|
|
pack(error, allocateForWrite, pack) {
|
|||
|
|
let { target, position} = allocateForWrite(this.moreTypes ? 3 : 0);
|
|||
|
|
if (this.moreTypes) {
|
|||
|
|
target[position++] = 0xd4;
|
|||
|
|
target[position++] = 0x65; // 'e' for error
|
|||
|
|
target[position++] = 0;
|
|||
|
|
}
|
|||
|
|
pack([ error.name, error.message, error.cause ]);
|
|||
|
|
}
|
|||
|
|
}, {
|
|||
|
|
pack(regex, allocateForWrite, pack) {
|
|||
|
|
let { target, position} = allocateForWrite(this.moreTypes ? 3 : 0);
|
|||
|
|
if (this.moreTypes) {
|
|||
|
|
target[position++] = 0xd4;
|
|||
|
|
target[position++] = 0x78; // 'x' for regeXp
|
|||
|
|
target[position++] = 0;
|
|||
|
|
}
|
|||
|
|
pack([ regex.source, regex.flags ]);
|
|||
|
|
}
|
|||
|
|
}, {
|
|||
|
|
pack(arrayBuffer, allocateForWrite) {
|
|||
|
|
if (this.moreTypes)
|
|||
|
|
writeExtBuffer(arrayBuffer, 0x10, allocateForWrite);
|
|||
|
|
else
|
|||
|
|
writeBuffer(hasNodeBuffer$1 ? Buffer.from(arrayBuffer) : new Uint8Array(arrayBuffer), allocateForWrite);
|
|||
|
|
}
|
|||
|
|
}, {
|
|||
|
|
pack(typedArray, allocateForWrite) {
|
|||
|
|
let constructor = typedArray.constructor;
|
|||
|
|
if (constructor !== ByteArray && this.moreTypes)
|
|||
|
|
writeExtBuffer(typedArray, typedArrays.indexOf(constructor.name), allocateForWrite);
|
|||
|
|
else
|
|||
|
|
writeBuffer(typedArray, allocateForWrite);
|
|||
|
|
}
|
|||
|
|
}, {
|
|||
|
|
pack(arrayBuffer, allocateForWrite) {
|
|||
|
|
if (this.moreTypes)
|
|||
|
|
writeExtBuffer(arrayBuffer, 0x11, allocateForWrite);
|
|||
|
|
else
|
|||
|
|
writeBuffer(hasNodeBuffer$1 ? Buffer.from(arrayBuffer) : new Uint8Array(arrayBuffer), allocateForWrite);
|
|||
|
|
}
|
|||
|
|
}, {
|
|||
|
|
pack(c1, allocateForWrite) { // specific 0xC1 object
|
|||
|
|
let { target, position} = allocateForWrite(1);
|
|||
|
|
target[position] = 0xc1;
|
|||
|
|
}
|
|||
|
|
}];
|
|||
|
|
|
|||
|
|
function writeExtBuffer(typedArray, type, allocateForWrite, encode) {
|
|||
|
|
let length = typedArray.byteLength;
|
|||
|
|
if (length + 1 < 0x100) {
|
|||
|
|
var { target, position } = allocateForWrite(4 + length);
|
|||
|
|
target[position++] = 0xc7;
|
|||
|
|
target[position++] = length + 1;
|
|||
|
|
} else if (length + 1 < 0x10000) {
|
|||
|
|
var { target, position } = allocateForWrite(5 + length);
|
|||
|
|
target[position++] = 0xc8;
|
|||
|
|
target[position++] = (length + 1) >> 8;
|
|||
|
|
target[position++] = (length + 1) & 0xff;
|
|||
|
|
} else {
|
|||
|
|
var { target, position, targetView } = allocateForWrite(7 + length);
|
|||
|
|
target[position++] = 0xc9;
|
|||
|
|
targetView.setUint32(position, length + 1); // plus one for the type byte
|
|||
|
|
position += 4;
|
|||
|
|
}
|
|||
|
|
target[position++] = 0x74; // "t" for typed array
|
|||
|
|
target[position++] = type;
|
|||
|
|
if (!typedArray.buffer) typedArray = new Uint8Array(typedArray);
|
|||
|
|
target.set(new Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength), position);
|
|||
|
|
}
|
|||
|
|
function writeBuffer(buffer, allocateForWrite) {
|
|||
|
|
let length = buffer.byteLength;
|
|||
|
|
var target, position;
|
|||
|
|
if (length < 0x100) {
|
|||
|
|
var { target, position } = allocateForWrite(length + 2);
|
|||
|
|
target[position++] = 0xc4;
|
|||
|
|
target[position++] = length;
|
|||
|
|
} else if (length < 0x10000) {
|
|||
|
|
var { target, position } = allocateForWrite(length + 3);
|
|||
|
|
target[position++] = 0xc5;
|
|||
|
|
target[position++] = length >> 8;
|
|||
|
|
target[position++] = length & 0xff;
|
|||
|
|
} else {
|
|||
|
|
var { target, position, targetView } = allocateForWrite(length + 5);
|
|||
|
|
target[position++] = 0xc6;
|
|||
|
|
targetView.setUint32(position, length);
|
|||
|
|
position += 4;
|
|||
|
|
}
|
|||
|
|
target.set(buffer, position);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function writeExtensionData(result, target, position, type) {
|
|||
|
|
let length = result.length;
|
|||
|
|
switch (length) {
|
|||
|
|
case 1:
|
|||
|
|
target[position++] = 0xd4;
|
|||
|
|
break
|
|||
|
|
case 2:
|
|||
|
|
target[position++] = 0xd5;
|
|||
|
|
break
|
|||
|
|
case 4:
|
|||
|
|
target[position++] = 0xd6;
|
|||
|
|
break
|
|||
|
|
case 8:
|
|||
|
|
target[position++] = 0xd7;
|
|||
|
|
break
|
|||
|
|
case 16:
|
|||
|
|
target[position++] = 0xd8;
|
|||
|
|
break
|
|||
|
|
default:
|
|||
|
|
if (length < 0x100) {
|
|||
|
|
target[position++] = 0xc7;
|
|||
|
|
target[position++] = length;
|
|||
|
|
} else if (length < 0x10000) {
|
|||
|
|
target[position++] = 0xc8;
|
|||
|
|
target[position++] = length >> 8;
|
|||
|
|
target[position++] = length & 0xff;
|
|||
|
|
} else {
|
|||
|
|
target[position++] = 0xc9;
|
|||
|
|
target[position++] = length >> 24;
|
|||
|
|
target[position++] = (length >> 16) & 0xff;
|
|||
|
|
target[position++] = (length >> 8) & 0xff;
|
|||
|
|
target[position++] = length & 0xff;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
target[position++] = type;
|
|||
|
|
target.set(result, position);
|
|||
|
|
position += length;
|
|||
|
|
return position
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function insertIds(serialized, idsToInsert) {
|
|||
|
|
// insert the ids that need to be referenced for structured clones
|
|||
|
|
let nextId;
|
|||
|
|
let distanceToMove = idsToInsert.length * 6;
|
|||
|
|
let lastEnd = serialized.length - distanceToMove;
|
|||
|
|
while (nextId = idsToInsert.pop()) {
|
|||
|
|
let offset = nextId.offset;
|
|||
|
|
let id = nextId.id;
|
|||
|
|
serialized.copyWithin(offset + distanceToMove, offset, lastEnd);
|
|||
|
|
distanceToMove -= 6;
|
|||
|
|
let position = offset + distanceToMove;
|
|||
|
|
serialized[position++] = 0xd6;
|
|||
|
|
serialized[position++] = 0x69; // 'i'
|
|||
|
|
serialized[position++] = id >> 24;
|
|||
|
|
serialized[position++] = (id >> 16) & 0xff;
|
|||
|
|
serialized[position++] = (id >> 8) & 0xff;
|
|||
|
|
serialized[position++] = id & 0xff;
|
|||
|
|
lastEnd = offset;
|
|||
|
|
}
|
|||
|
|
return serialized
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function writeBundles(start, pack, incrementPosition) {
|
|||
|
|
if (bundledStrings.length > 0) {
|
|||
|
|
targetView.setUint32(bundledStrings.position + start, position + incrementPosition - bundledStrings.position - start);
|
|||
|
|
bundledStrings.stringsPosition = position - start;
|
|||
|
|
let writeStrings = bundledStrings;
|
|||
|
|
bundledStrings = null;
|
|||
|
|
pack(writeStrings[0]);
|
|||
|
|
pack(writeStrings[1]);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function addExtension$1(extension) {
|
|||
|
|
if (extension.Class) {
|
|||
|
|
if (!extension.pack && !extension.write)
|
|||
|
|
throw new Error('Extension has no pack or write function')
|
|||
|
|
if (extension.pack && !extension.type)
|
|||
|
|
throw new Error('Extension has no type (numeric code to identify the extension)')
|
|||
|
|
extensionClasses.unshift(extension.Class);
|
|||
|
|
extensions.unshift(extension);
|
|||
|
|
}
|
|||
|
|
addExtension$2(extension);
|
|||
|
|
}
|
|||
|
|
function prepareStructures$1(structures, packr) {
|
|||
|
|
structures.isCompatible = (existingStructures) => {
|
|||
|
|
let compatible = !existingStructures || ((packr.lastNamedStructuresLength || 0) === existingStructures.length);
|
|||
|
|
if (!compatible) // we want to merge these existing structures immediately since we already have it and we are in the right transaction
|
|||
|
|
packr._mergeStructures(existingStructures);
|
|||
|
|
return compatible;
|
|||
|
|
};
|
|||
|
|
return structures
|
|||
|
|
}
|
|||
|
|
function setWriteStructSlots(writeSlots, makeStructures) {
|
|||
|
|
writeStructSlots = writeSlots;
|
|||
|
|
prepareStructures$1 = makeStructures;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let defaultPackr = new Packr$1({ useRecords: false });
|
|||
|
|
const pack$1 = defaultPackr.pack;
|
|||
|
|
defaultPackr.pack;
|
|||
|
|
const REUSE_BUFFER_MODE = 512;
|
|||
|
|
const RESET_BUFFER_MODE = 1024;
|
|||
|
|
const RESERVE_START_SPACE = 2048;
|
|||
|
|
|
|||
|
|
const ASCII = 3; // the MIBenum from https://www.iana.org/assignments/character-sets/character-sets.xhtml (and other character encodings could be referenced by MIBenum)
|
|||
|
|
const NUMBER = 0;
|
|||
|
|
const UTF8 = 2;
|
|||
|
|
const OBJECT_DATA = 1;
|
|||
|
|
const DATE = 16;
|
|||
|
|
const TYPE_NAMES = ['num', 'object', 'string', 'ascii'];
|
|||
|
|
TYPE_NAMES[DATE] = 'date';
|
|||
|
|
const float32Headers = [false, true, true, false, false, true, true, false];
|
|||
|
|
let evalSupported;
|
|||
|
|
try {
|
|||
|
|
new Function('');
|
|||
|
|
evalSupported = true;
|
|||
|
|
} catch(error) {
|
|||
|
|
// if eval variants are not supported, do not create inline object readers ever
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let updatedPosition;
|
|||
|
|
const hasNodeBuffer = typeof Buffer !== 'undefined';
|
|||
|
|
let textEncoder, currentSource;
|
|||
|
|
try {
|
|||
|
|
textEncoder = new TextEncoder();
|
|||
|
|
} catch (error) {}
|
|||
|
|
const encodeUtf8 = hasNodeBuffer ? function(target, string, position) {
|
|||
|
|
return target.utf8Write(string, position, target.byteLength - position)
|
|||
|
|
} : (textEncoder && textEncoder.encodeInto) ?
|
|||
|
|
function(target, string, position) {
|
|||
|
|
return textEncoder.encodeInto(string, target.subarray(position)).written
|
|||
|
|
} : false;
|
|||
|
|
setWriteStructSlots(writeStruct, prepareStructures);
|
|||
|
|
function writeStruct(object, target, encodingStart, position, structures, makeRoom, pack, packr, structureKnown) {
|
|||
|
|
let typedStructs = packr.typedStructs || (packr.typedStructs = []);
|
|||
|
|
// note that we rely on pack.js to load stored structures before we get to this point
|
|||
|
|
// structureKnown is set only on the internal layout-retry below: attempt 1 already minted
|
|||
|
|
// this record's structure, so the retry re-encodes a known shape and must not re-apply the
|
|||
|
|
// cap (which could otherwise bail after attempt 1 already packed refs → corrupt fallback).
|
|||
|
|
// `frozen` is a local (from this instance's typedStructs) — never a shared global — so a
|
|||
|
|
// re-entrant encode on another instance (e.g. via an enumerable getter) can't flip it.
|
|||
|
|
const cap = packr.maxOwnStructures ?? Infinity;
|
|||
|
|
const frozen = !structureKnown && typedStructs.length >= cap;
|
|||
|
|
let targetView = target.dataView;
|
|||
|
|
let refsStartPosition = (typedStructs.lastStringStart || 100) + position;
|
|||
|
|
let safeEnd = target.length - 10;
|
|||
|
|
let start = position;
|
|||
|
|
if (position > safeEnd) {
|
|||
|
|
target = makeRoom(position);
|
|||
|
|
targetView = target.dataView;
|
|||
|
|
position -= encodingStart;
|
|||
|
|
start -= encodingStart;
|
|||
|
|
refsStartPosition -= encodingStart;
|
|||
|
|
encodingStart = 0;
|
|||
|
|
safeEnd = target.length - 10;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let refOffset, refPosition = refsStartPosition;
|
|||
|
|
|
|||
|
|
let transition = typedStructs.transitions || (typedStructs.transitions = Object.create(null));
|
|||
|
|
let nextId = typedStructs.nextId || typedStructs.length;
|
|||
|
|
let headerSize =
|
|||
|
|
nextId < 0xf ? 1 :
|
|||
|
|
nextId < 0xf0 ? 2 :
|
|||
|
|
nextId < 0xf000 ? 3 :
|
|||
|
|
nextId < 0xf00000 ? 4 : 0;
|
|||
|
|
if (headerSize === 0)
|
|||
|
|
return 0;
|
|||
|
|
position += headerSize;
|
|||
|
|
let queuedReferences = [];
|
|||
|
|
let usedAscii0;
|
|||
|
|
let keyIndex = 0;
|
|||
|
|
for (let key in object) {
|
|||
|
|
let nextTransition = transition[key];
|
|||
|
|
// Resolve the key transition BEFORE reading the value: when frozen and the key is new we
|
|||
|
|
// bail here, so an enumerable getter isn't invoked during this (failed) struct attempt and
|
|||
|
|
// then again by the plain fallback (which would double-read a side-effecting accessor).
|
|||
|
|
if (!nextTransition) {
|
|||
|
|
if (frozen) return 0;
|
|||
|
|
transition[key] = nextTransition = {
|
|||
|
|
key,
|
|||
|
|
parent: transition,
|
|||
|
|
enumerationOffset: 0,
|
|||
|
|
ascii0: null,
|
|||
|
|
ascii8: null,
|
|||
|
|
num8: null,
|
|||
|
|
string16: null,
|
|||
|
|
object16: null,
|
|||
|
|
num32: null,
|
|||
|
|
float64: null,
|
|||
|
|
date64: null
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
let value = object[key];
|
|||
|
|
if (position > safeEnd) {
|
|||
|
|
target = makeRoom(position);
|
|||
|
|
targetView = target.dataView;
|
|||
|
|
position -= encodingStart;
|
|||
|
|
start -= encodingStart;
|
|||
|
|
refsStartPosition -= encodingStart;
|
|||
|
|
refPosition -= encodingStart;
|
|||
|
|
encodingStart = 0;
|
|||
|
|
safeEnd = target.length - 10;
|
|||
|
|
}
|
|||
|
|
switch (typeof value) {
|
|||
|
|
case 'number':
|
|||
|
|
let number = value;
|
|||
|
|
// first check to see if we are using a lot of ids and should default to wide/common format
|
|||
|
|
if (nextId < 200 || !nextTransition.num64) {
|
|||
|
|
if (number >> 0 === number && number < 0x20000000 && number > -0x1f000000) {
|
|||
|
|
if (number < 0xf6 && number >= 0 && (nextTransition.num8 && !(nextId > 200 && nextTransition.num32) || number < 0x20 && !nextTransition.num32)) {
|
|||
|
|
transition = nextTransition.num8 || createTypeTransition(nextTransition, NUMBER, 1, frozen);
|
|||
|
|
target[position++] = number;
|
|||
|
|
} else {
|
|||
|
|
transition = nextTransition.num32 || createTypeTransition(nextTransition, NUMBER, 4, frozen);
|
|||
|
|
targetView.setUint32(position, number, true);
|
|||
|
|
position += 4;
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
} else if (number < 0x100000000 && number >= -0x80000000) {
|
|||
|
|
targetView.setFloat32(position, number, true);
|
|||
|
|
if (float32Headers[target[position + 3] >>> 5]) {
|
|||
|
|
let xShifted;
|
|||
|
|
// this checks for rounding of numbers that were encoded in 32-bit float to nearest significant decimal digit that could be preserved
|
|||
|
|
if (((xShifted = number * mult10[((target[position + 3] & 0x7f) << 1) | (target[position + 2] >> 7)]) >> 0) === xShifted) {
|
|||
|
|
transition = nextTransition.num32 || createTypeTransition(nextTransition, NUMBER, 4, frozen);
|
|||
|
|
position += 4;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
transition = nextTransition.num64 || createTypeTransition(nextTransition, NUMBER, 8, frozen);
|
|||
|
|
targetView.setFloat64(position, number, true);
|
|||
|
|
position += 8;
|
|||
|
|
break;
|
|||
|
|
case 'string':
|
|||
|
|
let strLength = value.length;
|
|||
|
|
refOffset = refPosition - refsStartPosition;
|
|||
|
|
if ((strLength << 2) + refPosition > safeEnd) {
|
|||
|
|
target = makeRoom((strLength << 2) + refPosition);
|
|||
|
|
targetView = target.dataView;
|
|||
|
|
position -= encodingStart;
|
|||
|
|
start -= encodingStart;
|
|||
|
|
refsStartPosition -= encodingStart;
|
|||
|
|
refPosition -= encodingStart;
|
|||
|
|
encodingStart = 0;
|
|||
|
|
safeEnd = target.length - 10;
|
|||
|
|
}
|
|||
|
|
if (strLength > ((0xff00 + refOffset) >> 2)) {
|
|||
|
|
queuedReferences.push(key, value, position - start);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
let isNotAscii;
|
|||
|
|
let strStart = refPosition;
|
|||
|
|
if (strLength < 0x40) {
|
|||
|
|
let i, c1, c2;
|
|||
|
|
for (i = 0; i < strLength; i++) {
|
|||
|
|
c1 = value.charCodeAt(i);
|
|||
|
|
if (c1 < 0x80) {
|
|||
|
|
target[refPosition++] = c1;
|
|||
|
|
} else if (c1 < 0x800) {
|
|||
|
|
isNotAscii = true;
|
|||
|
|
target[refPosition++] = c1 >> 6 | 0xc0;
|
|||
|
|
target[refPosition++] = c1 & 0x3f | 0x80;
|
|||
|
|
} else if (
|
|||
|
|
(c1 & 0xfc00) === 0xd800 &&
|
|||
|
|
((c2 = value.charCodeAt(i + 1)) & 0xfc00) === 0xdc00
|
|||
|
|
) {
|
|||
|
|
isNotAscii = true;
|
|||
|
|
c1 = 0x10000 + ((c1 & 0x03ff) << 10) + (c2 & 0x03ff);
|
|||
|
|
i++;
|
|||
|
|
target[refPosition++] = c1 >> 18 | 0xf0;
|
|||
|
|
target[refPosition++] = c1 >> 12 & 0x3f | 0x80;
|
|||
|
|
target[refPosition++] = c1 >> 6 & 0x3f | 0x80;
|
|||
|
|
target[refPosition++] = c1 & 0x3f | 0x80;
|
|||
|
|
} else {
|
|||
|
|
isNotAscii = true;
|
|||
|
|
target[refPosition++] = c1 >> 12 | 0xe0;
|
|||
|
|
target[refPosition++] = c1 >> 6 & 0x3f | 0x80;
|
|||
|
|
target[refPosition++] = c1 & 0x3f | 0x80;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
refPosition += encodeUtf8(target, value, refPosition);
|
|||
|
|
isNotAscii = refPosition - strStart > strLength;
|
|||
|
|
}
|
|||
|
|
if (refOffset < 0xa0 || (refOffset < 0xf6 && (nextTransition.ascii8 || nextTransition.string8))) {
|
|||
|
|
// short strings
|
|||
|
|
if (isNotAscii) {
|
|||
|
|
if (!(transition = nextTransition.string8)) {
|
|||
|
|
if (typedStructs.length > 10 && (transition = nextTransition.ascii8)) {
|
|||
|
|
// we can safely change ascii to utf8 in place since they are compatible
|
|||
|
|
transition.__type = UTF8;
|
|||
|
|
nextTransition.ascii8 = null;
|
|||
|
|
nextTransition.string8 = transition;
|
|||
|
|
pack(null, 0, true); // special call to notify that structures have been updated
|
|||
|
|
} else {
|
|||
|
|
transition = createTypeTransition(nextTransition, UTF8, 1, frozen);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else if (refOffset === 0 && !usedAscii0) {
|
|||
|
|
usedAscii0 = true;
|
|||
|
|
transition = nextTransition.ascii0 || createTypeTransition(nextTransition, ASCII, 0, frozen);
|
|||
|
|
break; // don't increment position
|
|||
|
|
}// else ascii:
|
|||
|
|
else if (!(transition = nextTransition.ascii8) && !(typedStructs.length > 10 && (transition = nextTransition.string8)))
|
|||
|
|
transition = createTypeTransition(nextTransition, ASCII, 1, frozen);
|
|||
|
|
target[position++] = refOffset;
|
|||
|
|
} else {
|
|||
|
|
// TODO: Enable ascii16 at some point, but get the logic right
|
|||
|
|
//if (isNotAscii)
|
|||
|
|
transition = nextTransition.string16 || createTypeTransition(nextTransition, UTF8, 2, frozen);
|
|||
|
|
//else
|
|||
|
|
//transition = nextTransition.ascii16 || createTypeTransition(nextTransition, ASCII, 2);
|
|||
|
|
targetView.setUint16(position, refOffset, true);
|
|||
|
|
position += 2;
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
case 'object':
|
|||
|
|
if (value) {
|
|||
|
|
if (value.constructor === Date) {
|
|||
|
|
transition = nextTransition.date64 || createTypeTransition(nextTransition, DATE, 8, frozen);
|
|||
|
|
targetView.setFloat64(position, value.getTime(), true);
|
|||
|
|
position += 8;
|
|||
|
|
} else {
|
|||
|
|
queuedReferences.push(key, value, keyIndex);
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
} else { // null
|
|||
|
|
nextTransition = anyType(nextTransition, position, targetView, -10); // match CBOR with this
|
|||
|
|
if (nextTransition) {
|
|||
|
|
transition = nextTransition;
|
|||
|
|
position = updatedPosition;
|
|||
|
|
} else queuedReferences.push(key, value, keyIndex);
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
case 'boolean':
|
|||
|
|
transition = nextTransition.num8 || nextTransition.ascii8 || createTypeTransition(nextTransition, NUMBER, 1, frozen);
|
|||
|
|
target[position++] = value ? 0xf9 : 0xf8; // match CBOR with these
|
|||
|
|
break;
|
|||
|
|
case 'undefined':
|
|||
|
|
nextTransition = anyType(nextTransition, position, targetView, -9); // match CBOR with this
|
|||
|
|
if (nextTransition) {
|
|||
|
|
transition = nextTransition;
|
|||
|
|
position = updatedPosition;
|
|||
|
|
} else queuedReferences.push(key, value, keyIndex);
|
|||
|
|
break;
|
|||
|
|
default:
|
|||
|
|
queuedReferences.push(key, value, keyIndex);
|
|||
|
|
}
|
|||
|
|
if (transition === undefined) return 0; // frozen: structure cap reached
|
|||
|
|
keyIndex++;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Cap enforcement for queued (nested-object / null) references. pack() advances msgpackr's
|
|||
|
|
// shared write position and we cannot cleanly bail afterward, so preflight the whole queued
|
|||
|
|
// chain through EXISTING transitions first: if the cap is reached and any field would need a
|
|||
|
|
// new structure, fall back to plain encoding now (return 0) — before touching the shared
|
|||
|
|
// position. Uses a FRESH length read (not the entry-time `frozen`): a getter invoked while
|
|||
|
|
// reading values above may have minted on this same instance since entry.
|
|||
|
|
if (!structureKnown && queuedReferences.length > 0 && typedStructs.length >= cap) {
|
|||
|
|
let t = transition;
|
|||
|
|
for (let i = 0, l = queuedReferences.length; i < l; i += 3) {
|
|||
|
|
// A non-null (object/Date) ref is pack()ed into the shared buffer, advancing
|
|||
|
|
// msgpackr's write position. Its structure variant (object16 vs object32) depends on
|
|||
|
|
// the runtime ref-section offset (inline strings + earlier refs), which we can't know
|
|||
|
|
// before packing — and we can't bail after a pack without corrupting the fallback. So
|
|||
|
|
// under the cap, any record with a packing ref falls back to plain encoding now,
|
|||
|
|
// before any pack(). null/undefined refs don't pack, so they're walked normally.
|
|||
|
|
if (queuedReferences[i + 1] != null) return 0;
|
|||
|
|
const nt = t[queuedReferences[i]];
|
|||
|
|
if (!nt) return 0;
|
|||
|
|
const next = nt.object16; // null/undefined ref → OBJECT_DATA size 2
|
|||
|
|
if (!next) return 0;
|
|||
|
|
t = next;
|
|||
|
|
}
|
|||
|
|
if (t[RECORD_SYMBOL] == null) return 0; // exact structure not yet minted
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Past the preflight the chain is known, so no minting happens — except a rare offset
|
|||
|
|
// divergence (a known shape whose ref section now crosses 0xff00 and needs object32 where
|
|||
|
|
// the preflight matched object16). Once a ref is packed we can no longer bail, so we finish
|
|||
|
|
// via the unfrozen forceTypeTransition: a bounded, self-converging overshoot for that one
|
|||
|
|
// record. packedRef keeps the record-id mint from bailing after a pack.
|
|||
|
|
let packedRef = false;
|
|||
|
|
for (let i = 0, l = queuedReferences.length; i < l;) {
|
|||
|
|
let key = queuedReferences[i++];
|
|||
|
|
let value = queuedReferences[i++];
|
|||
|
|
let propertyIndex = queuedReferences[i++];
|
|||
|
|
let nextTransition = transition[key];
|
|||
|
|
if (!nextTransition) {
|
|||
|
|
transition[key] = nextTransition = {
|
|||
|
|
key,
|
|||
|
|
parent: transition,
|
|||
|
|
enumerationOffset: propertyIndex - keyIndex,
|
|||
|
|
ascii0: null,
|
|||
|
|
ascii8: null,
|
|||
|
|
num8: null,
|
|||
|
|
string16: null,
|
|||
|
|
object16: null,
|
|||
|
|
num32: null,
|
|||
|
|
float64: null
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
let newPosition;
|
|||
|
|
if (value) {
|
|||
|
|
let size;
|
|||
|
|
refOffset = refPosition - refsStartPosition;
|
|||
|
|
if (refOffset < 0xff00) {
|
|||
|
|
transition = nextTransition.object16;
|
|||
|
|
if (transition)
|
|||
|
|
size = 2;
|
|||
|
|
else if ((transition = nextTransition.object32))
|
|||
|
|
size = 4;
|
|||
|
|
else {
|
|||
|
|
transition = forceTypeTransition(nextTransition, OBJECT_DATA, 2);
|
|||
|
|
size = 2;
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
transition = nextTransition.object32 || forceTypeTransition(nextTransition, OBJECT_DATA, 4);
|
|||
|
|
size = 4;
|
|||
|
|
}
|
|||
|
|
newPosition = pack(value, refPosition);
|
|||
|
|
packedRef = true;
|
|||
|
|
if (typeof newPosition === 'object') {
|
|||
|
|
// re-allocated
|
|||
|
|
refPosition = newPosition.position;
|
|||
|
|
targetView = newPosition.targetView;
|
|||
|
|
target = newPosition.target;
|
|||
|
|
refsStartPosition -= encodingStart;
|
|||
|
|
position -= encodingStart;
|
|||
|
|
start -= encodingStart;
|
|||
|
|
encodingStart = 0;
|
|||
|
|
} else
|
|||
|
|
refPosition = newPosition;
|
|||
|
|
if (size === 2) {
|
|||
|
|
targetView.setUint16(position, refOffset, true);
|
|||
|
|
position += 2;
|
|||
|
|
} else {
|
|||
|
|
targetView.setUint32(position, refOffset, true);
|
|||
|
|
position += 4;
|
|||
|
|
}
|
|||
|
|
} else { // null or undefined
|
|||
|
|
transition = nextTransition.object16 || forceTypeTransition(nextTransition, OBJECT_DATA, 2);
|
|||
|
|
targetView.setInt16(position, value === null ? -10 : -9, true);
|
|||
|
|
position += 2;
|
|||
|
|
}
|
|||
|
|
keyIndex++;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let recordId = transition[RECORD_SYMBOL];
|
|||
|
|
if (recordId == null) {
|
|||
|
|
// Flat records (no queued refs) reach here without packing, so the cap is enforced
|
|||
|
|
// cleanly. Records that packed nested refs already passed the preflight; either way
|
|||
|
|
// bailing now after refs were packed would corrupt the fallback.
|
|||
|
|
if (!packedRef && typedStructs.length >= cap) return 0;
|
|||
|
|
recordId = packr.typedStructs.length;
|
|||
|
|
let structure = [];
|
|||
|
|
let nextTransition = transition;
|
|||
|
|
let key, type;
|
|||
|
|
while ((type = nextTransition.__type) !== undefined) {
|
|||
|
|
let size = nextTransition.__size;
|
|||
|
|
nextTransition = nextTransition.__parent;
|
|||
|
|
key = nextTransition.key;
|
|||
|
|
let property = [type, size, key];
|
|||
|
|
if (nextTransition.enumerationOffset)
|
|||
|
|
property.push(nextTransition.enumerationOffset);
|
|||
|
|
structure.push(property);
|
|||
|
|
nextTransition = nextTransition.parent;
|
|||
|
|
}
|
|||
|
|
structure.reverse();
|
|||
|
|
transition[RECORD_SYMBOL] = recordId;
|
|||
|
|
packr.typedStructs[recordId] = structure;
|
|||
|
|
pack(null, 0, true); // special call to notify that structures have been updated
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
switch (headerSize) {
|
|||
|
|
case 1:
|
|||
|
|
if (recordId >= 0x10) return 0;
|
|||
|
|
target[start] = recordId + 0x20;
|
|||
|
|
break;
|
|||
|
|
case 2:
|
|||
|
|
if (recordId >= 0x100) return 0;
|
|||
|
|
target[start] = 0x38;
|
|||
|
|
target[start + 1] = recordId;
|
|||
|
|
break;
|
|||
|
|
case 3:
|
|||
|
|
if (recordId >= 0x10000) return 0;
|
|||
|
|
target[start] = 0x39;
|
|||
|
|
targetView.setUint16(start + 1, recordId, true);
|
|||
|
|
break;
|
|||
|
|
case 4:
|
|||
|
|
if (recordId >= 0x1000000) return 0;
|
|||
|
|
targetView.setUint32(start, (recordId << 8) + 0x3a, true);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (position < refsStartPosition) {
|
|||
|
|
if (refsStartPosition === refPosition)
|
|||
|
|
return position; // no refs
|
|||
|
|
// adjust positioning
|
|||
|
|
target.copyWithin(position, refsStartPosition, refPosition);
|
|||
|
|
refPosition += position - refsStartPosition;
|
|||
|
|
typedStructs.lastStringStart = position - start;
|
|||
|
|
} else if (position > refsStartPosition) {
|
|||
|
|
if (refsStartPosition === refPosition)
|
|||
|
|
return position; // no refs
|
|||
|
|
typedStructs.lastStringStart = position - start;
|
|||
|
|
// Fixed section overflowed our estimate — retry with the corrected size. The structure
|
|||
|
|
// is already minted at this point, so pass structureKnown=true to skip the cap check
|
|||
|
|
// (otherwise a record that became frozen during attempt 1 would bail mid-retry, after
|
|||
|
|
// refs were already packed, and corrupt the fallback).
|
|||
|
|
return writeStruct(object, target, encodingStart, start, structures, makeRoom, pack, packr, true);
|
|||
|
|
}
|
|||
|
|
return refPosition;
|
|||
|
|
}
|
|||
|
|
function anyType(transition, position, targetView, value) {
|
|||
|
|
let nextTransition;
|
|||
|
|
if ((nextTransition = transition.ascii8 || transition.num8)) {
|
|||
|
|
targetView.setInt8(position, value, true);
|
|||
|
|
updatedPosition = position + 1;
|
|||
|
|
return nextTransition;
|
|||
|
|
}
|
|||
|
|
if ((nextTransition = transition.string16 || transition.object16)) {
|
|||
|
|
targetView.setInt16(position, value, true);
|
|||
|
|
updatedPosition = position + 2;
|
|||
|
|
return nextTransition;
|
|||
|
|
}
|
|||
|
|
if (nextTransition = transition.num32) {
|
|||
|
|
targetView.setUint32(position, 0xe0000100 + value, true);
|
|||
|
|
updatedPosition = position + 4;
|
|||
|
|
return nextTransition;
|
|||
|
|
}
|
|||
|
|
// transition.float64
|
|||
|
|
if (nextTransition = transition.num64) {
|
|||
|
|
targetView.setFloat64(position, NaN, true);
|
|||
|
|
targetView.setInt8(position, value);
|
|||
|
|
updatedPosition = position + 8;
|
|||
|
|
return nextTransition;
|
|||
|
|
}
|
|||
|
|
updatedPosition = position;
|
|||
|
|
// TODO: can we do an "any" type where we defer the decision?
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
// When the typed-structure dictionary reaches maxOwnStructures we stop minting new
|
|||
|
|
// structures/transitions. typedStructs is append-only and pinned on the long-lived
|
|||
|
|
// encoder (records reference structures by recordId), so an unbounded shape space —
|
|||
|
|
// e.g. a wide, sparsely/variably-populated schema — would otherwise grow the
|
|||
|
|
// dictionary + transition trie without limit. `frozen` is passed in (derived from the
|
|||
|
|
// encoding instance's own typedStructs.length, never a shared global) so a re-entrant
|
|||
|
|
// encode on another instance can't flip it; while frozen, a missing transition returns
|
|||
|
|
// undefined so the caller bails and the record falls back to plain encoding.
|
|||
|
|
function createTypeTransition(transition, type, size, frozen) {
|
|||
|
|
let typeName = TYPE_NAMES[type] + (size << 3);
|
|||
|
|
let newTransition = transition[typeName];
|
|||
|
|
if (newTransition) return newTransition;
|
|||
|
|
if (frozen) return undefined;
|
|||
|
|
newTransition = transition[typeName] = Object.create(null);
|
|||
|
|
newTransition.__type = type;
|
|||
|
|
newTransition.__size = size;
|
|||
|
|
newTransition.__parent = transition;
|
|||
|
|
return newTransition;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Unfrozen variant: always mints. Used in the queued-ref loop once a nested value has
|
|||
|
|
// already been pack()ed — at that point pack() has advanced msgpackr's shared write
|
|||
|
|
// position, so bailing with `return 0` would corrupt the fallback. We must finish the
|
|||
|
|
// encode instead, even if that means minting a (bounded) handful of structures past the
|
|||
|
|
// cap. The cap is still enforced up front via the preflight, before the first pack().
|
|||
|
|
function forceTypeTransition(transition, type, size) {
|
|||
|
|
let typeName = TYPE_NAMES[type] + (size << 3);
|
|||
|
|
let newTransition = transition[typeName];
|
|||
|
|
if (newTransition) return newTransition;
|
|||
|
|
newTransition = transition[typeName] = Object.create(null);
|
|||
|
|
newTransition.__type = type;
|
|||
|
|
newTransition.__size = size;
|
|||
|
|
newTransition.__parent = transition;
|
|||
|
|
return newTransition;
|
|||
|
|
}
|
|||
|
|
function onLoadedStructures(sharedData) {
|
|||
|
|
if (!(sharedData instanceof Map))
|
|||
|
|
return sharedData;
|
|||
|
|
let typed = sharedData.get('typed') || [];
|
|||
|
|
if (Object.isFrozen(typed))
|
|||
|
|
typed = typed.map(structure => structure.slice(0));
|
|||
|
|
let named = sharedData.get('named');
|
|||
|
|
let transitions = Object.create(null);
|
|||
|
|
for (let i = 0, l = typed.length; i < l; i++) {
|
|||
|
|
let structure = typed[i];
|
|||
|
|
let transition = transitions;
|
|||
|
|
for (let [type, size, key] of structure) {
|
|||
|
|
let nextTransition = transition[key];
|
|||
|
|
if (!nextTransition) {
|
|||
|
|
transition[key] = nextTransition = {
|
|||
|
|
key,
|
|||
|
|
parent: transition,
|
|||
|
|
enumerationOffset: 0,
|
|||
|
|
ascii0: null,
|
|||
|
|
ascii8: null,
|
|||
|
|
num8: null,
|
|||
|
|
string16: null,
|
|||
|
|
object16: null,
|
|||
|
|
num32: null,
|
|||
|
|
float64: null,
|
|||
|
|
date64: null,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
// Replaying persisted structures is never subject to the cap — always mint.
|
|||
|
|
transition = createTypeTransition(nextTransition, type, size, false);
|
|||
|
|
}
|
|||
|
|
transition[RECORD_SYMBOL] = i;
|
|||
|
|
}
|
|||
|
|
typed.transitions = transitions;
|
|||
|
|
this.typedStructs = typed;
|
|||
|
|
this.lastTypedStructuresLength = typed.length;
|
|||
|
|
return named;
|
|||
|
|
}
|
|||
|
|
var sourceSymbol = Symbol.for('source');
|
|||
|
|
function readStruct(src, position, srcEnd, unpackr) {
|
|||
|
|
let recordId = src[position++] - 0x20;
|
|||
|
|
if (recordId >= 24) {
|
|||
|
|
switch(recordId) {
|
|||
|
|
case 24: recordId = src[position++]; break;
|
|||
|
|
// little endian:
|
|||
|
|
case 25: recordId = src[position++] + (src[position++] << 8); break;
|
|||
|
|
case 26: recordId = src[position++] + (src[position++] << 8) + (src[position++] << 16); break;
|
|||
|
|
case 27: recordId = src[position++] + (src[position++] << 8) + (src[position++] << 16) + (src[position++] << 24); break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
let structure = unpackr.typedStructs && unpackr.typedStructs[recordId];
|
|||
|
|
if (!structure) {
|
|||
|
|
// copy src buffer because getStructures will override it
|
|||
|
|
src = Uint8Array.prototype.slice.call(src, position, srcEnd);
|
|||
|
|
srcEnd -= position;
|
|||
|
|
position = 0;
|
|||
|
|
if (!unpackr.getStructures)
|
|||
|
|
throw new Error(`Reference to shared structure ${recordId} without getStructures method`);
|
|||
|
|
unpackr._mergeStructures(unpackr.getStructures());
|
|||
|
|
if (!unpackr.typedStructs)
|
|||
|
|
throw new Error('Could not find any shared typed structures');
|
|||
|
|
unpackr.lastTypedStructuresLength = unpackr.typedStructs.length;
|
|||
|
|
structure = unpackr.typedStructs[recordId];
|
|||
|
|
if (!structure)
|
|||
|
|
throw new Error('Could not find typed structure ' + recordId);
|
|||
|
|
}
|
|||
|
|
var construct = structure.construct;
|
|||
|
|
var fullConstruct = structure.fullConstruct;
|
|||
|
|
if (!construct) {
|
|||
|
|
construct = structure.construct = function LazyObject() {
|
|||
|
|
};
|
|||
|
|
fullConstruct = structure.fullConstruct = function LoadedObject() {
|
|||
|
|
};
|
|||
|
|
fullConstruct.prototype = unpackr.structPrototype || {};
|
|||
|
|
var prototype = construct.prototype = unpackr.structPrototype ? Object.create(unpackr.structPrototype) : {};
|
|||
|
|
let properties = [];
|
|||
|
|
let currentOffset = 0;
|
|||
|
|
let lastRefProperty;
|
|||
|
|
for (let i = 0, l = structure.length; i < l; i++) {
|
|||
|
|
let definition = structure[i];
|
|||
|
|
let [ type, size, key, enumerationOffset ] = definition;
|
|||
|
|
if (key === '__proto__')
|
|||
|
|
key = '__proto_';
|
|||
|
|
let property = {
|
|||
|
|
key,
|
|||
|
|
offset: currentOffset,
|
|||
|
|
};
|
|||
|
|
if (enumerationOffset)
|
|||
|
|
properties.splice(i + enumerationOffset, 0, property);
|
|||
|
|
else
|
|||
|
|
properties.push(property);
|
|||
|
|
let getRef;
|
|||
|
|
switch(size) { // TODO: Move into a separate function
|
|||
|
|
case 0: getRef = () => 0; break;
|
|||
|
|
case 1:
|
|||
|
|
getRef = (source, position) => {
|
|||
|
|
let ref = source.bytes[position + property.offset];
|
|||
|
|
return ref >= 0xf6 ? toConstant(ref) : ref;
|
|||
|
|
};
|
|||
|
|
break;
|
|||
|
|
case 2:
|
|||
|
|
getRef = (source, position) => {
|
|||
|
|
let src = source.bytes;
|
|||
|
|
let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
|
|||
|
|
let ref = dataView.getUint16(position + property.offset, true);
|
|||
|
|
return ref >= 0xff00 ? toConstant(ref & 0xff) : ref;
|
|||
|
|
};
|
|||
|
|
break;
|
|||
|
|
case 4:
|
|||
|
|
getRef = (source, position) => {
|
|||
|
|
let src = source.bytes;
|
|||
|
|
let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
|
|||
|
|
let ref = dataView.getUint32(position + property.offset, true);
|
|||
|
|
return ref >= 0xffffff00 ? toConstant(ref & 0xff) : ref;
|
|||
|
|
};
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
property.getRef = getRef;
|
|||
|
|
currentOffset += size;
|
|||
|
|
let get;
|
|||
|
|
switch(type) {
|
|||
|
|
case ASCII:
|
|||
|
|
if (lastRefProperty && !lastRefProperty.next)
|
|||
|
|
lastRefProperty.next = property;
|
|||
|
|
lastRefProperty = property;
|
|||
|
|
property.multiGetCount = 0;
|
|||
|
|
get = function(source) {
|
|||
|
|
let src = source.bytes;
|
|||
|
|
let position = source.position;
|
|||
|
|
let refStart = currentOffset + position;
|
|||
|
|
let ref = getRef(source, position);
|
|||
|
|
if (typeof ref !== 'number') return ref;
|
|||
|
|
|
|||
|
|
let end, next = property.next;
|
|||
|
|
while(next) {
|
|||
|
|
end = next.getRef(source, position);
|
|||
|
|
if (typeof end === 'number')
|
|||
|
|
break;
|
|||
|
|
else
|
|||
|
|
end = null;
|
|||
|
|
next = next.next;
|
|||
|
|
}
|
|||
|
|
if (end == null)
|
|||
|
|
end = source.bytesEnd - refStart;
|
|||
|
|
if (source.srcString) {
|
|||
|
|
return source.srcString.slice(ref, end);
|
|||
|
|
}
|
|||
|
|
/*if (property.multiGetCount > 0) {
|
|||
|
|
let asciiEnd;
|
|||
|
|
next = firstRefProperty;
|
|||
|
|
let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
|
|||
|
|
do {
|
|||
|
|
asciiEnd = dataView.getUint16(source.position + next.offset, true);
|
|||
|
|
if (asciiEnd < 0xff00)
|
|||
|
|
break;
|
|||
|
|
else
|
|||
|
|
asciiEnd = null;
|
|||
|
|
} while((next = next.next));
|
|||
|
|
if (asciiEnd == null)
|
|||
|
|
asciiEnd = source.bytesEnd - refStart
|
|||
|
|
source.srcString = src.toString('latin1', refStart, refStart + asciiEnd);
|
|||
|
|
return source.srcString.slice(ref, end);
|
|||
|
|
}
|
|||
|
|
if (source.prevStringGet) {
|
|||
|
|
source.prevStringGet.multiGetCount += 2;
|
|||
|
|
} else {
|
|||
|
|
source.prevStringGet = property;
|
|||
|
|
property.multiGetCount--;
|
|||
|
|
}*/
|
|||
|
|
return readString(src, ref + refStart, end - ref);
|
|||
|
|
//return src.toString('latin1', ref + refStart, end + refStart);
|
|||
|
|
};
|
|||
|
|
break;
|
|||
|
|
case UTF8: case OBJECT_DATA:
|
|||
|
|
if (lastRefProperty && !lastRefProperty.next)
|
|||
|
|
lastRefProperty.next = property;
|
|||
|
|
lastRefProperty = property;
|
|||
|
|
get = function(source) {
|
|||
|
|
let position = source.position;
|
|||
|
|
let refStart = currentOffset + position;
|
|||
|
|
let ref = getRef(source, position);
|
|||
|
|
if (typeof ref !== 'number') return ref;
|
|||
|
|
let src = source.bytes;
|
|||
|
|
let end, next = property.next;
|
|||
|
|
while(next) {
|
|||
|
|
end = next.getRef(source, position);
|
|||
|
|
if (typeof end === 'number')
|
|||
|
|
break;
|
|||
|
|
else
|
|||
|
|
end = null;
|
|||
|
|
next = next.next;
|
|||
|
|
}
|
|||
|
|
if (end == null)
|
|||
|
|
end = source.bytesEnd - refStart;
|
|||
|
|
if (type === UTF8) {
|
|||
|
|
return src.toString('utf8', ref + refStart, end + refStart);
|
|||
|
|
} else {
|
|||
|
|
currentSource = source;
|
|||
|
|
try {
|
|||
|
|
return unpackr.unpack(src, { start: ref + refStart, end: end + refStart });
|
|||
|
|
} finally {
|
|||
|
|
currentSource = null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
break;
|
|||
|
|
case NUMBER:
|
|||
|
|
switch(size) {
|
|||
|
|
case 4:
|
|||
|
|
get = function (source) {
|
|||
|
|
let src = source.bytes;
|
|||
|
|
let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
|
|||
|
|
let position = source.position + property.offset;
|
|||
|
|
let value = dataView.getInt32(position, true);
|
|||
|
|
if (value < 0x20000000) {
|
|||
|
|
if (value > -0x1f000000)
|
|||
|
|
return value;
|
|||
|
|
if (value > -0x20000000)
|
|||
|
|
return toConstant(value & 0xff);
|
|||
|
|
}
|
|||
|
|
let fValue = dataView.getFloat32(position, true);
|
|||
|
|
// 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 + 3] & 0x7f) << 1) | (src[position + 2] >> 7)];
|
|||
|
|
return ((multiplier * fValue + (fValue > 0 ? 0.5 : -0.5)) >> 0) / multiplier;
|
|||
|
|
};
|
|||
|
|
break;
|
|||
|
|
case 8:
|
|||
|
|
get = function (source) {
|
|||
|
|
let src = source.bytes;
|
|||
|
|
let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
|
|||
|
|
let value = dataView.getFloat64(source.position + property.offset, true);
|
|||
|
|
if (isNaN(value)) {
|
|||
|
|
let byte = src[source.position + property.offset];
|
|||
|
|
if (byte >= 0xf6)
|
|||
|
|
return toConstant(byte);
|
|||
|
|
}
|
|||
|
|
return value;
|
|||
|
|
};
|
|||
|
|
break;
|
|||
|
|
case 1:
|
|||
|
|
get = function (source) {
|
|||
|
|
let src = source.bytes;
|
|||
|
|
let value = src[source.position + property.offset];
|
|||
|
|
return value < 0xf6 ? value : toConstant(value);
|
|||
|
|
};
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
case DATE:
|
|||
|
|
get = function (source) {
|
|||
|
|
let src = source.bytes;
|
|||
|
|
let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
|
|||
|
|
return new Date(dataView.getFloat64(source.position + property.offset, true));
|
|||
|
|
};
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
property.get = get;
|
|||
|
|
}
|
|||
|
|
// TODO: load the srcString for faster string decoding on toJSON
|
|||
|
|
if (evalSupported) {
|
|||
|
|
let objectLiteralProperties = [];
|
|||
|
|
let args = [];
|
|||
|
|
let i = 0;
|
|||
|
|
let hasInheritedProperties;
|
|||
|
|
for (let property of properties) { // assign in enumeration order
|
|||
|
|
if (unpackr.alwaysLazyProperty && unpackr.alwaysLazyProperty(property.key)) {
|
|||
|
|
// these properties are not eagerly evaluated and this can be used for creating properties
|
|||
|
|
// that are not serialized as JSON
|
|||
|
|
hasInheritedProperties = true;
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
Object.defineProperty(prototype, property.key, { get: withSource(property.get), enumerable: true });
|
|||
|
|
let valueFunction = 'v' + i++;
|
|||
|
|
args.push(valueFunction);
|
|||
|
|
objectLiteralProperties.push('o[' + JSON.stringify(property.key) + ']=' + valueFunction + '(s)');
|
|||
|
|
}
|
|||
|
|
if (hasInheritedProperties) {
|
|||
|
|
objectLiteralProperties.push('__proto__:this');
|
|||
|
|
}
|
|||
|
|
let toObject = (new Function(...args, 'var c=this;return function(s){var o=new c();' + objectLiteralProperties.join(';') + ';return o;}')).apply(fullConstruct, properties.map(prop => prop.get));
|
|||
|
|
Object.defineProperty(prototype, 'toJSON', {
|
|||
|
|
value(omitUnderscoredProperties) {
|
|||
|
|
return toObject.call(this, this[sourceSymbol]);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
} else {
|
|||
|
|
Object.defineProperty(prototype, 'toJSON', {
|
|||
|
|
value(omitUnderscoredProperties) {
|
|||
|
|
// return an enumerable object with own properties to JSON stringify
|
|||
|
|
let resolved = {};
|
|||
|
|
for (let i = 0, l = properties.length; i < l; i++) {
|
|||
|
|
// TODO: check alwaysLazyProperty
|
|||
|
|
let key = properties[i].key;
|
|||
|
|
|
|||
|
|
resolved[key] = this[key];
|
|||
|
|
}
|
|||
|
|
return resolved;
|
|||
|
|
},
|
|||
|
|
// not enumerable or anything
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
var instance = new construct();
|
|||
|
|
instance[sourceSymbol] = {
|
|||
|
|
bytes: src,
|
|||
|
|
position,
|
|||
|
|
srcString: '',
|
|||
|
|
bytesEnd: srcEnd
|
|||
|
|
};
|
|||
|
|
return instance;
|
|||
|
|
}
|
|||
|
|
function toConstant(code) {
|
|||
|
|
switch(code) {
|
|||
|
|
case 0xf6: return null;
|
|||
|
|
case 0xf7: return undefined;
|
|||
|
|
case 0xf8: return false;
|
|||
|
|
case 0xf9: return true;
|
|||
|
|
}
|
|||
|
|
throw new Error('Unknown constant');
|
|||
|
|
}
|
|||
|
|
function withSource(get) {
|
|||
|
|
return function() {
|
|||
|
|
return get(this[sourceSymbol]);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function saveState() {
|
|||
|
|
if (currentSource) {
|
|||
|
|
currentSource.bytes = Uint8Array.prototype.slice.call(currentSource.bytes, currentSource.position, currentSource.bytesEnd);
|
|||
|
|
currentSource.position = 0;
|
|||
|
|
currentSource.bytesEnd = currentSource.bytes.length;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
function prepareStructures(structures, packr) {
|
|||
|
|
if (packr.typedStructs) {
|
|||
|
|
let structMap = new Map();
|
|||
|
|
structMap.set('named', structures);
|
|||
|
|
structMap.set('typed', packr.typedStructs);
|
|||
|
|
structures = structMap;
|
|||
|
|
}
|
|||
|
|
let lastTypedStructuresLength = packr.lastTypedStructuresLength || 0;
|
|||
|
|
structures.isCompatible = existing => {
|
|||
|
|
let compatible = true;
|
|||
|
|
if (existing instanceof Map) {
|
|||
|
|
let named = existing.get('named') || [];
|
|||
|
|
if (named.length !== (packr.lastNamedStructuresLength || 0))
|
|||
|
|
compatible = false;
|
|||
|
|
let typed = existing.get('typed') || [];
|
|||
|
|
if (typed.length !== lastTypedStructuresLength)
|
|||
|
|
compatible = false;
|
|||
|
|
} else if (existing instanceof Array || Array.isArray(existing)) {
|
|||
|
|
if (existing.length !== (packr.lastNamedStructuresLength || 0))
|
|||
|
|
compatible = false;
|
|||
|
|
}
|
|||
|
|
if (!compatible)
|
|||
|
|
packr._mergeStructures(existing);
|
|||
|
|
return compatible;
|
|||
|
|
};
|
|||
|
|
packr.lastTypedStructuresLength = packr.typedStructs && packr.typedStructs.length;
|
|||
|
|
return structures;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
setReadStruct(readStruct, onLoadedStructures, saveState);
|
|||
|
|
|
|||
|
|
const nativeAccelerationDisabled = process.env.MSGPACKR_NATIVE_ACCELERATION_DISABLED !== undefined && process.env.MSGPACKR_NATIVE_ACCELERATION_DISABLED.toLowerCase() === 'true';
|
|||
|
|
|
|||
|
|
if (!nativeAccelerationDisabled) {
|
|||
|
|
let extractor;
|
|||
|
|
try {
|
|||
|
|
if (typeof require == 'function')
|
|||
|
|
extractor = require('msgpackr-extract');
|
|||
|
|
else
|
|||
|
|
extractor = module.createRequire((document.currentScript && document.currentScript.src || new URL('test.js', document.baseURI).href))('msgpackr-extract');
|
|||
|
|
if (extractor)
|
|||
|
|
setExtractor(extractor.extractStrings);
|
|||
|
|
} catch (error) {
|
|||
|
|
// native module is optional
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let allSampleData = [];
|
|||
|
|
for (let i = 1; i < 6; i++) {
|
|||
|
|
allSampleData.push(JSON.parse(fs.readFileSync(new URL(`./example${i > 1 ? i : ''}.json`, (document.currentScript && document.currentScript.src || new URL('test.js', document.baseURI).href)))));
|
|||
|
|
}
|
|||
|
|
allSampleData.push({
|
|||
|
|
name: 'some other types',
|
|||
|
|
date: new Date(),
|
|||
|
|
empty: '',
|
|||
|
|
});
|
|||
|
|
const sampleData = allSampleData[3];
|
|||
|
|
function tryRequire(module) {
|
|||
|
|
try {
|
|||
|
|
return require(module)
|
|||
|
|
} catch(error) {
|
|||
|
|
return {}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let seed = 0;
|
|||
|
|
function random() {
|
|||
|
|
seed++;
|
|||
|
|
let a = seed * 15485863;
|
|||
|
|
return (a * a * a % 2038074743) / 2038074743;
|
|||
|
|
}
|
|||
|
|
//if (typeof chai === 'undefined') { chai = require('chai') }
|
|||
|
|
var assert = chai.assert;
|
|||
|
|
//if (typeof msgpackr === 'undefined') { msgpackr = require('..') }
|
|||
|
|
var Packr = Packr$1;
|
|||
|
|
var Unpackr = Unpackr$1;
|
|||
|
|
var unpack = unpack$1;
|
|||
|
|
var unpackMultiple = unpackMultiple$1;
|
|||
|
|
var roundFloat32 = roundFloat32$1;
|
|||
|
|
var pack = pack$1;
|
|||
|
|
var DECIMAL_FIT = FLOAT32_OPTIONS.DECIMAL_FIT;
|
|||
|
|
|
|||
|
|
var addExtension = addExtension$1;
|
|||
|
|
var zlib = tryRequire('zlib');
|
|||
|
|
zlib.deflateSync;
|
|||
|
|
zlib.inflateSync;
|
|||
|
|
zlib.brotliCompressSync;
|
|||
|
|
zlib.brotliDecompressSync;
|
|||
|
|
zlib.constants;
|
|||
|
|
|
|||
|
|
var ITERATIONS = 4000;
|
|||
|
|
|
|||
|
|
class ExtendArray extends Array {
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class ExtendArray2 extends Array {
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class ExtendArray3 extends Array {
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
class ExtendObject {
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
suite('msgpackr basic tests', function() {
|
|||
|
|
test('pack/unpack data', function () {
|
|||
|
|
var data = {
|
|||
|
|
data: [
|
|||
|
|
{a: 1, name: 'one', type: 'odd', isOdd: true},
|
|||
|
|
{a: 2, name: 'two', type: 'even'},
|
|||
|
|
{a: 3, name: 'three', type: 'odd', isOdd: true},
|
|||
|
|
{a: 4, name: 'four', type: 'even'},
|
|||
|
|
{a: 5, name: 'five', type: 'odd', isOdd: true},
|
|||
|
|
{a: 6, name: 'six', type: 'even', isOdd: null}
|
|||
|
|
],
|
|||
|
|
description: 'some names',
|
|||
|
|
types: ['odd', 'even'],
|
|||
|
|
convertEnumToNum: [
|
|||
|
|
{prop: 'test'},
|
|||
|
|
{prop: 'test'},
|
|||
|
|
{prop: 'test'},
|
|||
|
|
{prop: 1},
|
|||
|
|
{prop: 2},
|
|||
|
|
{prop: [undefined]},
|
|||
|
|
{prop: null}
|
|||
|
|
]
|
|||
|
|
};
|
|||
|
|
let structures = [];
|
|||
|
|
let packr = new Packr({structures});
|
|||
|
|
var serialized = packr.pack(data);
|
|||
|
|
serialized = packr.pack(data);
|
|||
|
|
serialized = packr.pack(data);
|
|||
|
|
var deserialized = packr.unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized, data);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('mixed structures', function () {
|
|||
|
|
let data1 = {a: 1, b: 2, c: 3};
|
|||
|
|
let data2 = {a: 1, b: 2, d: 4};
|
|||
|
|
let data3 = {a: 1, b: 2, e: 5};
|
|||
|
|
let structures = [];
|
|||
|
|
let packr = new Packr({structures});
|
|||
|
|
var serialized = packr.pack(data1);
|
|||
|
|
var deserialized = packr.unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized, data1);
|
|||
|
|
var serialized = packr.pack(data2);
|
|||
|
|
var deserialized = packr.unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized, data2);
|
|||
|
|
var serialized = packr.pack(data3);
|
|||
|
|
var deserialized = packr.unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized, data3);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('mixed array', function () {
|
|||
|
|
var data = [
|
|||
|
|
'one',
|
|||
|
|
'two',
|
|||
|
|
'one',
|
|||
|
|
10,
|
|||
|
|
11,
|
|||
|
|
null,
|
|||
|
|
true,
|
|||
|
|
'three',
|
|||
|
|
'three',
|
|||
|
|
'one', [
|
|||
|
|
3, -5, -50, -400, 1.3, -5.3, true
|
|||
|
|
]
|
|||
|
|
];
|
|||
|
|
let structures = [];
|
|||
|
|
let packr = new Packr({structures});
|
|||
|
|
var serialized = packr.pack(data);
|
|||
|
|
var deserialized = packr.unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized, data);
|
|||
|
|
});
|
|||
|
|
test('255 chars', function () {
|
|||
|
|
const data = 'RRZG9A6I7xupPeOZhxcOcioFsuhszGOdyDUcbRf4Zef2kdPIfC9RaLO4jTM5JhuZvTsF09fbRHMGtqk7YAgu3vespeTe9l61ziZ6VrMnYu2CamK96wCkmz0VUXyqaiUoTPgzk414LS9yYrd5uh7w18ksJF5SlC2e91rukWvNqAZJjYN3jpkqHNOFchCwFrhbxq2Lrv1kSJPYCx9blRg2hGmYqTbElLTZHv20iNqwZeQbRMgSBPT6vnbCBPnOh1W';
|
|||
|
|
var serialized = pack(data);
|
|||
|
|
var deserialized = unpack(serialized);
|
|||
|
|
assert.equal(deserialized, data);
|
|||
|
|
});
|
|||
|
|
test('overlong UTF-8 string', function () {
|
|||
|
|
const payload = Buffer.concat([
|
|||
|
|
Buffer.from([0xa2]), // msgpack fixstr, 2 bytes
|
|||
|
|
Buffer.from([0xc0, 0xaf]), // overlong "/"
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
const result = unpack(payload);
|
|||
|
|
assert.notEqual(result, '/');
|
|||
|
|
});
|
|||
|
|
test('use ArrayBuffer', function () {
|
|||
|
|
const data = {prop: 'a test'};
|
|||
|
|
var serialized = pack(data);
|
|||
|
|
let ab = new ArrayBuffer(serialized.length);
|
|||
|
|
let u8 = new Uint8Array(ab);
|
|||
|
|
u8.set(serialized);
|
|||
|
|
var deserialized = unpack(ab);
|
|||
|
|
assert.deepEqual(deserialized, data);
|
|||
|
|
});
|
|||
|
|
test('pack/unpack varying data with random access structures', function () {
|
|||
|
|
let structures = [];
|
|||
|
|
let packr = new Packr({
|
|||
|
|
structures, useRecords: true, randomAccessStructure: true, freezeData: true, saveStructures(structures) {
|
|||
|
|
}, getStructures() {
|
|||
|
|
console.log('getStructures');
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
for (let i = 0; i < 2000; i++) {
|
|||
|
|
let data = {};
|
|||
|
|
let props = ['foo', 'bar', 'a', 'b', 'c', 'name', 'age', 'd'];
|
|||
|
|
|
|||
|
|
function makeString() {
|
|||
|
|
let str = '';
|
|||
|
|
while (random() < 0.9) {
|
|||
|
|
str += random() < 0.8 ? 'hello world' : String.fromCharCode(300);
|
|||
|
|
}
|
|||
|
|
return str;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for (let i = 0; i < random() * 20; i++) {
|
|||
|
|
data[props[Math.floor(random() * 8)]] =
|
|||
|
|
random() < 0.3 ? Math.floor(random() * 400) / 2 :
|
|||
|
|
random() < 0.3 ? makeString() : random() < 0.3 ? true : random() < 0.3 ? sampleData : null;
|
|||
|
|
}
|
|||
|
|
var serialized = packr.pack(data);
|
|||
|
|
var deserialized = packr.unpack(serialized);
|
|||
|
|
for (let key in deserialized) {
|
|||
|
|
deserialized[key];
|
|||
|
|
}
|
|||
|
|
assert.deepEqual(deserialized, data);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
for (let sampleData of allSampleData) {
|
|||
|
|
let snippet = JSON.stringify(sampleData).slice(0, 20) + '...';
|
|||
|
|
test('pack/unpack sample data ' + snippet, function () {
|
|||
|
|
var data = sampleData;
|
|||
|
|
var serialized = pack(data);
|
|||
|
|
var deserialized = unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized, data);
|
|||
|
|
var serialized = pack(data);
|
|||
|
|
var deserialized = unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized, data);
|
|||
|
|
});
|
|||
|
|
test('pack/unpack sample data with Uint8Array encoding' + snippet, function () {
|
|||
|
|
var data = sampleData;
|
|||
|
|
var serialized = pack(data);
|
|||
|
|
serialized = new Uint8Array(serialized);
|
|||
|
|
var deserialized = unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized, data);
|
|||
|
|
var serialized = pack(data);
|
|||
|
|
var deserialized = unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized, data);
|
|||
|
|
});
|
|||
|
|
test('pack/unpack sample data with random access structures ' + snippet, function () {
|
|||
|
|
var data = sampleData;
|
|||
|
|
let structures = [];
|
|||
|
|
let packr = new Packr({
|
|||
|
|
structures, useRecords: true, randomAccessStructure: true, freezeData: true, saveStructures(structures) {
|
|||
|
|
}, getStructures() {
|
|||
|
|
console.log('getStructures');
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
for (let i = 0; i < 20; i++) {
|
|||
|
|
var serialized = packr.pack(data);
|
|||
|
|
var deserialized = packr.unpack(serialized, {lazy: true});
|
|||
|
|
var copied = {};
|
|||
|
|
for (let key in deserialized) {
|
|||
|
|
copied[key] = deserialized[key];
|
|||
|
|
}
|
|||
|
|
assert.deepEqual(copied, data);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
test('pack/unpack sample data with bundled strings ' + snippet, function () {
|
|||
|
|
var data = sampleData;
|
|||
|
|
let packr = new Packr({ /*structures,*/ useRecords: false, bundleStrings: true});
|
|||
|
|
var serialized = packr.pack(data);
|
|||
|
|
var deserialized = packr.unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized, data);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
test('pack/unpack sample data with useRecords function', function () {
|
|||
|
|
var data = [
|
|||
|
|
{id: 1, type: 1, labels: {a: 1, b: 2}},
|
|||
|
|
{id: 2, type: 1, labels: {b: 1, c: 2}},
|
|||
|
|
{id: 3, type: 1, labels: {d: 1, e: 2}}
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
var alternatives = [
|
|||
|
|
{useRecords: false}, // 88 bytes
|
|||
|
|
{useRecords: true}, // 58 bytes
|
|||
|
|
{mapsAsObjects: true, useRecords: (v)=>!!v.id}, // 55 bytes
|
|||
|
|
{mapsAsObjects: true, variableMapSize: true, useRecords: (v)=>!!v.id} // 49 bytes
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
for(let o of alternatives) {
|
|||
|
|
let packr = new Packr(o);
|
|||
|
|
var serialized = packr.pack(data);
|
|||
|
|
var deserialized = packr.unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized, data);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('mapAsEmptyObject combination', function () {
|
|||
|
|
const msgpackr = new Packr({ useRecords: false, encodeUndefinedAsNil: true, variableMapSize: true, mapAsEmptyObject: true, setAsEmptyObject: true });
|
|||
|
|
|
|||
|
|
const map = new Map();
|
|||
|
|
map.set('a', 1);
|
|||
|
|
map.set('b', 2);
|
|||
|
|
const set = new Set();
|
|||
|
|
set.add('a');
|
|||
|
|
set.add('b');
|
|||
|
|
const input = { map, set };
|
|||
|
|
|
|||
|
|
const packed = msgpackr.pack(input);
|
|||
|
|
const unpacked = msgpackr.unpack(packed);
|
|||
|
|
assert.deepEqual(unpacked.map, {});
|
|||
|
|
assert.deepEqual(unpacked.set, {});
|
|||
|
|
});
|
|||
|
|
test('pack/unpack numeric coercible keys', function () {
|
|||
|
|
var data = { a: 1, 2: 'test', '-3.45': 'test2'};
|
|||
|
|
let packr = new Packr({variableMapSize: true, coercibleKeyAsNumber: true, useRecords: false});
|
|||
|
|
var serialized = packr.pack(data);
|
|||
|
|
var deserialized = packr.unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized, data);
|
|||
|
|
});
|
|||
|
|
test('pack/unpack empty data with bundled strings', function () {
|
|||
|
|
var data = {};
|
|||
|
|
let packr = new Packr({bundleStrings: true});
|
|||
|
|
var serialized = packr.pack(data);
|
|||
|
|
var deserialized = packr.unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized, data);
|
|||
|
|
});
|
|||
|
|
test('pack/unpack large amount of chinese characters', function() {
|
|||
|
|
const MSGPACK_OPTIONS = {bundleStrings: true};
|
|||
|
|
|
|||
|
|
const item = {
|
|||
|
|
message: '你好你好你好你好你好你好你好你好你好', // some Chinese characters
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
testSize(100);
|
|||
|
|
testSize(1000);
|
|||
|
|
testSize(10000);
|
|||
|
|
function testSize(size) {
|
|||
|
|
const list = [];
|
|||
|
|
for (let i = 0; i < size; i++) {
|
|||
|
|
list.push({...item});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const packer = new Packr(MSGPACK_OPTIONS);
|
|||
|
|
const unpacker = new Unpackr(MSGPACK_OPTIONS);
|
|||
|
|
const encoded = packer.pack(list);
|
|||
|
|
const decoded = unpacker.unpack(encoded);
|
|||
|
|
assert.deepEqual(list, decoded);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
test('pack/unpack sequential data', function () {
|
|||
|
|
var data = {foo: 1, bar: 2};
|
|||
|
|
let packr = new Packr({sequential: true});
|
|||
|
|
let unpackr = new Unpackr({sequential: true});
|
|||
|
|
var serialized = packr.pack(data);
|
|||
|
|
var deserialized = unpackr.unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized, data);
|
|||
|
|
var serialized = packr.pack(data);
|
|||
|
|
var deserialized = unpackr.unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized, data);
|
|||
|
|
});
|
|||
|
|
test('pack/unpack with bundled strings and sequential', function () {
|
|||
|
|
const options = {
|
|||
|
|
bundleStrings: true,
|
|||
|
|
sequential: true,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const packer = new Packr(options);
|
|||
|
|
const unpacker = new Packr(options);
|
|||
|
|
|
|||
|
|
const data = {data: 42}; // key length >= 4
|
|||
|
|
|
|||
|
|
unpacker.unpackMultiple(Buffer.concat([
|
|||
|
|
packer.pack(data),
|
|||
|
|
packer.pack(data)
|
|||
|
|
]));
|
|||
|
|
});
|
|||
|
|
if (typeof Buffer != 'undefined')
|
|||
|
|
test('replace data', function(){
|
|||
|
|
var data1 = {
|
|||
|
|
data: [
|
|||
|
|
{ a: 1, name: 'one', type: 'odd', isOdd: true, a: '13 characters' },
|
|||
|
|
{ a: 2, name: 'two', type: 'even', a: '11 characte' },
|
|||
|
|
{ a: 3, name: 'three', type: 'odd', isOdd: true, a: '12 character' },
|
|||
|
|
{ a: 4, name: 'four', type: 'even', a: '9 charact'},
|
|||
|
|
{ a: 5, name: 'five', type: 'odd', isOdd: true, a: '14 characters!' },
|
|||
|
|
{ a: 6, name: 'six', type: 'even', isOdd: null }
|
|||
|
|
],
|
|||
|
|
};
|
|||
|
|
var data2 = {
|
|||
|
|
data: [
|
|||
|
|
{ foo: 7, name: 'one', type: 'odd', isOdd: true },
|
|||
|
|
{ foo: 8, name: 'two', type: 'even'},
|
|||
|
|
{ foo: 9, name: 'three', type: 'odd', isOdd: true },
|
|||
|
|
{ foo: 10, name: 'four', type: 'even'},
|
|||
|
|
{ foo: 11, name: 'five', type: 'odd', isOdd: true },
|
|||
|
|
{ foo: 12, name: 'six', type: 'even', isOdd: null }
|
|||
|
|
],
|
|||
|
|
};
|
|||
|
|
var serialized1 = pack(data1);
|
|||
|
|
var serialized2 = pack(data2);
|
|||
|
|
var b = Buffer.alloc(8000);
|
|||
|
|
serialized1.copy(b);
|
|||
|
|
var deserialized1 = unpack(b, serialized1.length);
|
|||
|
|
serialized2.copy(b);
|
|||
|
|
var deserialized2 = unpack(b, serialized2.length);
|
|||
|
|
assert.deepEqual(deserialized1, data1);
|
|||
|
|
assert.deepEqual(deserialized2, data2);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('compact 123', function() {
|
|||
|
|
assert.equal(pack(123).length, 1);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('BigInt', function() {
|
|||
|
|
let packr = new Packr({ useBigIntExtension: true });
|
|||
|
|
let data = {
|
|||
|
|
a: 3333333333333333333333333333n,
|
|||
|
|
b: 1234567890123456789012345678901234567890n,
|
|||
|
|
c: -3333333333333333333333333333n,
|
|||
|
|
d: -352523523642364364364264264264264264262642642n,
|
|||
|
|
e: 0xffffffffffffffffffffffffffn,
|
|||
|
|
f: -0xffffffffffffffffffffffffffn,
|
|||
|
|
g: (1234n << 123n) ^ (5678n << 56n) ^ 890n,
|
|||
|
|
h: (-1234n << 123n) ^ (5678n << 56n) ^ 890n,
|
|||
|
|
i: (1234n << 1234n) ^ (5678n << 567n) ^ 890n,
|
|||
|
|
j: (-1234n << 1234n) ^ (5678n << 567n) ^ 890n,
|
|||
|
|
k: 0xdeadn << 0xbeefn,
|
|||
|
|
l: -0xdeadn << 0xbeefn,
|
|||
|
|
m: 11n << 0x11111n ^ 111n,
|
|||
|
|
n: -11n << 0x11111n ^ 111n,
|
|||
|
|
o: 12345678901234567890n,
|
|||
|
|
p: -12345678901234567890n,
|
|||
|
|
exp: [],
|
|||
|
|
expexp: [],
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
for (let n = 1n; n.toString(16).length * 4 < 1500; n <<= 1n, n |= BigInt(Math.floor(Math.random() * 2))) {
|
|||
|
|
data.exp.push(n, -n);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for (let n = 7n; n.toString(16).length * 4 < 150000; n *= n) {
|
|||
|
|
data.expexp.push(n, -n);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let serialized = packr.pack(data);
|
|||
|
|
let deserialized = packr.unpack(serialized);
|
|||
|
|
assert.deepEqual(data, deserialized);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
|
|||
|
|
test('extended class pack/unpack', function(){
|
|||
|
|
function Extended() {
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
Extended.prototype.getDouble = function() {
|
|||
|
|
return this.value * 2
|
|||
|
|
};
|
|||
|
|
var instance = new Extended();
|
|||
|
|
instance.value = 4;
|
|||
|
|
instance.string = 'decode this: ᾜ';
|
|||
|
|
var data = {
|
|||
|
|
prop1: 'has multi-byte: ᾜ',
|
|||
|
|
extendedInstance: instance,
|
|||
|
|
prop2: 'more string',
|
|||
|
|
num: 3,
|
|||
|
|
};
|
|||
|
|
let packr = new Packr();
|
|||
|
|
addExtension({
|
|||
|
|
Class: Extended,
|
|||
|
|
type: 11,
|
|||
|
|
unpack: function(buffer) {
|
|||
|
|
let e = new Extended();
|
|||
|
|
let data = packr.unpack(buffer);
|
|||
|
|
e.value = data[0];
|
|||
|
|
e.string = data[1];
|
|||
|
|
return e
|
|||
|
|
},
|
|||
|
|
pack: function(instance) {
|
|||
|
|
return packr.pack([instance.value, instance.string])
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
var serialized = pack(data);
|
|||
|
|
var deserialized = unpack(serialized);
|
|||
|
|
assert.deepEqual(data, deserialized);
|
|||
|
|
assert.equal(deserialized.extendedInstance.getDouble(), 8);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('extended Array class read/write', function(){
|
|||
|
|
var instance = new ExtendArray();
|
|||
|
|
instance.push(0);
|
|||
|
|
instance.push(1);
|
|||
|
|
instance.push(2);
|
|||
|
|
var data = {
|
|||
|
|
prop1: 'has multi-byte: ᾜ',
|
|||
|
|
extendedInstance: instance,
|
|||
|
|
prop2: 'more string',
|
|||
|
|
num: 3,
|
|||
|
|
};
|
|||
|
|
new Packr();
|
|||
|
|
addExtension({
|
|||
|
|
Class: ExtendArray,
|
|||
|
|
type: 12,
|
|||
|
|
read: function(data) {
|
|||
|
|
Object.setPrototypeOf(data, ExtendArray.prototype);
|
|||
|
|
return data
|
|||
|
|
},
|
|||
|
|
write: function(instance) {
|
|||
|
|
return [...instance]
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
var serialized = pack(data);
|
|||
|
|
var deserialized = unpack(serialized);
|
|||
|
|
assert.strictEqual(Object.getPrototypeOf(deserialized.extendedInstance), ExtendArray.prototype);
|
|||
|
|
assert.deepEqual(data, deserialized);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('unregistered extended Array class read/write', function(){
|
|||
|
|
var instance = new ExtendArray2();
|
|||
|
|
instance.push(0);
|
|||
|
|
instance.push(1);
|
|||
|
|
instance.push(2);
|
|||
|
|
var data = {
|
|||
|
|
prop1: 'has multi-byte: ᾜ',
|
|||
|
|
extendedInstance: instance,
|
|||
|
|
prop2: 'more string',
|
|||
|
|
num: 3,
|
|||
|
|
};
|
|||
|
|
new Packr();
|
|||
|
|
var serialized = pack(data);
|
|||
|
|
var deserialized = unpack(serialized);
|
|||
|
|
assert.strictEqual(Object.getPrototypeOf(deserialized.extendedInstance), Array.prototype);
|
|||
|
|
assert.deepEqual(data, deserialized);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
|
|||
|
|
test('unregistered extended Object class read/write', function(){
|
|||
|
|
var instance = new ExtendObject();
|
|||
|
|
instance.test1 = "string";
|
|||
|
|
instance.test2 = 3421321;
|
|||
|
|
var data = {
|
|||
|
|
prop1: 'has multi-byte: ᾜ',
|
|||
|
|
extendedInstance: instance,
|
|||
|
|
prop2: 'more string',
|
|||
|
|
num: 3,
|
|||
|
|
};
|
|||
|
|
new Packr();
|
|||
|
|
var serialized = pack(data);
|
|||
|
|
var deserialized = unpack(serialized);
|
|||
|
|
assert.strictEqual(Object.getPrototypeOf(deserialized.extendedInstance), Object.prototype);
|
|||
|
|
assert.deepEqual(data, deserialized);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('extended class pack/unpack custom size', function(){
|
|||
|
|
function TestClass() {
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
addExtension({
|
|||
|
|
Class: TestClass,
|
|||
|
|
type: 0x01,
|
|||
|
|
pack() {
|
|||
|
|
return typeof Buffer != 'undefined' ? Buffer.alloc(256) : new Uint8Array(256)
|
|||
|
|
},
|
|||
|
|
unpack(data) {
|
|||
|
|
return data.length
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
let result = unpack(pack(new TestClass()));
|
|||
|
|
assert.equal(result, 256);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('extended class read/write', function(){
|
|||
|
|
function Extended() {
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
Extended.prototype.getDouble = function() {
|
|||
|
|
return this.value * 2
|
|||
|
|
};
|
|||
|
|
var instance = new Extended();
|
|||
|
|
instance.value = 4;
|
|||
|
|
instance.string = 'decode this: ᾜ';
|
|||
|
|
var data = {
|
|||
|
|
prop1: 'has multi-byte: ᾜ',
|
|||
|
|
extendedInstance: instance,
|
|||
|
|
prop2: 'more string',
|
|||
|
|
num: 3,
|
|||
|
|
};
|
|||
|
|
new Packr();
|
|||
|
|
addExtension({
|
|||
|
|
Class: Extended,
|
|||
|
|
type: 12,
|
|||
|
|
read: function(data) {
|
|||
|
|
let e = new Extended();
|
|||
|
|
e.value = data[0];
|
|||
|
|
e.string = data[1];
|
|||
|
|
return e
|
|||
|
|
},
|
|||
|
|
write: function(instance) {
|
|||
|
|
return [instance.value, instance.string]
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
var serialized = pack(data);
|
|||
|
|
var deserialized = unpack(serialized);
|
|||
|
|
assert.deepEqual(data, deserialized);
|
|||
|
|
assert.equal(deserialized.extendedInstance.getDouble(), 8);
|
|||
|
|
});
|
|||
|
|
test('extended class return self', function(){
|
|||
|
|
function Extended() {
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
Extended.prototype.getDouble = function() {
|
|||
|
|
return this.value * 2
|
|||
|
|
};
|
|||
|
|
var instance = new Extended();
|
|||
|
|
instance.value = 4;
|
|||
|
|
instance.string = 'decode this: ᾜ';
|
|||
|
|
var data = {
|
|||
|
|
prop1: 'has multi-byte: ᾜ',
|
|||
|
|
extendedInstance: instance,
|
|||
|
|
prop2: 'more string',
|
|||
|
|
num: 3,
|
|||
|
|
};
|
|||
|
|
new Packr();
|
|||
|
|
addExtension({
|
|||
|
|
Class: Extended,
|
|||
|
|
type: 13,
|
|||
|
|
read: function(data) {
|
|||
|
|
Object.setPrototypeOf(data, Extended.prototype);
|
|||
|
|
return data
|
|||
|
|
},
|
|||
|
|
write: function(data) {
|
|||
|
|
return data
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
var serialized = pack(data);
|
|||
|
|
var deserialized = unpack(serialized);
|
|||
|
|
assert.deepEqual(data, deserialized);
|
|||
|
|
assert.strictEqual(Object.getPrototypeOf(deserialized.extendedInstance), Extended.prototype);
|
|||
|
|
assert.equal(deserialized.extendedInstance.getDouble(), 8);
|
|||
|
|
});
|
|||
|
|
test('extended Array class return self', function(){
|
|||
|
|
var instance = new ExtendArray3();
|
|||
|
|
instance.push(0);
|
|||
|
|
instance.push('has multi-byte: ᾜ');
|
|||
|
|
var data = {
|
|||
|
|
prop1: 'has multi-byte: ᾜ',
|
|||
|
|
extendedInstance: instance,
|
|||
|
|
prop2: 'more string',
|
|||
|
|
num: 3,
|
|||
|
|
};
|
|||
|
|
new Packr();
|
|||
|
|
addExtension({
|
|||
|
|
Class: ExtendArray3,
|
|||
|
|
type: 14,
|
|||
|
|
read: function(data) {
|
|||
|
|
Object.setPrototypeOf(data, ExtendArray3.prototype);
|
|||
|
|
return data
|
|||
|
|
},
|
|||
|
|
write: function(data) {
|
|||
|
|
return data
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
var serialized = pack(data);
|
|||
|
|
var deserialized = unpack(serialized);
|
|||
|
|
assert.deepEqual(data, deserialized);
|
|||
|
|
assert.strictEqual(Object.getPrototypeOf(deserialized.extendedInstance), ExtendArray3.prototype);
|
|||
|
|
assert.equal(deserialized.extendedInstance[0], 0);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('extended class pack/unpack proxied', function(){
|
|||
|
|
function Extended() {
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
Extended.prototype.__call__ = function(){
|
|||
|
|
return this.value * 4
|
|||
|
|
};
|
|||
|
|
Extended.prototype.getDouble = function() {
|
|||
|
|
return this.value * 2
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
var instance = function() { instance.__call__();/* callable stuff */ };
|
|||
|
|
Object.setPrototypeOf(instance,Extended.prototype);
|
|||
|
|
|
|||
|
|
instance.value = 4;
|
|||
|
|
var data = instance;
|
|||
|
|
|
|||
|
|
let packr = new Packr();
|
|||
|
|
addExtension({
|
|||
|
|
Class: Extended,
|
|||
|
|
type: 15,
|
|||
|
|
unpack: function(buffer) {
|
|||
|
|
var e = function() { e.__call__(); };
|
|||
|
|
Object.setPrototypeOf(e,Extended.prototype);
|
|||
|
|
e.value = packr.unpack(buffer);
|
|||
|
|
return e
|
|||
|
|
},
|
|||
|
|
pack: function(instance) {
|
|||
|
|
return packr.pack(instance.value)
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
var serialized = pack(data);
|
|||
|
|
var deserialized = unpack(serialized);
|
|||
|
|
assert.equal(deserialized.getDouble(), 8);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test.skip('convert Date to string', function(){
|
|||
|
|
var data = {
|
|||
|
|
aDate: new Date(),
|
|||
|
|
};
|
|||
|
|
new Packr();
|
|||
|
|
addExtension({
|
|||
|
|
Class: Date,
|
|||
|
|
write(date) {
|
|||
|
|
return date.toString()
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
var serialized = pack(data);
|
|||
|
|
var deserialized = unpack(serialized);
|
|||
|
|
assert.equal(deserialized.aDate, data.aDate.toString());
|
|||
|
|
});
|
|||
|
|
test('standard pack fails on circular reference with shared structures', function () {
|
|||
|
|
var data = {};
|
|||
|
|
data.self = data;
|
|||
|
|
let structures = [];
|
|||
|
|
let packr = new Packr({
|
|||
|
|
structures,
|
|||
|
|
saveStructures(structures) {
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
assert.throws(function () {
|
|||
|
|
packr.pack(data);
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('proto handling', function() {
|
|||
|
|
var objectWithProto = JSON.parse('{"__proto__":{"foo":3}}');
|
|||
|
|
var decoded = unpack(pack(objectWithProto));
|
|||
|
|
assert(!decoded.foo);
|
|||
|
|
var objectsWithProto = [objectWithProto, objectWithProto, objectWithProto, objectWithProto, objectWithProto, objectWithProto];
|
|||
|
|
let packr = new Packr();
|
|||
|
|
var decoded = packr.unpack(packr.pack(objectsWithProto));
|
|||
|
|
for (let object of decoded) {
|
|||
|
|
assert(!decoded.foo);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test.skip('text decoder', function() {
|
|||
|
|
let td = new TextDecoder('ISO-8859-15');
|
|||
|
|
let b = Buffer.alloc(3);
|
|||
|
|
for (var i = 0; i < 256; i++) {
|
|||
|
|
b[0] = i;
|
|||
|
|
b[1] = 0;
|
|||
|
|
b[2] = 0;
|
|||
|
|
let s = td.decode(b);
|
|||
|
|
if (!require('msgpackr-extract').isOneByte(s)) {
|
|||
|
|
console.log(i.toString(16), s.length);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('moreTypes: Error with causes', function() {
|
|||
|
|
const object = {
|
|||
|
|
error: new Error('test'),
|
|||
|
|
errorWithCause: new Error('test-1', { cause: new Error('test-2') }),
|
|||
|
|
type: new TypeError(),
|
|||
|
|
range: new RangeError('test', { cause: [1, 2] }),
|
|||
|
|
};
|
|||
|
|
const packr = new Packr({
|
|||
|
|
moreTypes: true,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const serialized = packr.pack(object);
|
|||
|
|
const deserialized = packr.unpack(serialized);
|
|||
|
|
assert.equal(deserialized.error.message, object.error.message);
|
|||
|
|
assert.equal(deserialized.error.cause, object.error.cause);
|
|||
|
|
assert.equal(deserialized.errorWithCause.message, object.errorWithCause.message);
|
|||
|
|
assert.equal(deserialized.errorWithCause.cause.message, object.errorWithCause.cause.message);
|
|||
|
|
assert.equal(deserialized.errorWithCause.cause.cause, object.errorWithCause.cause.cause);
|
|||
|
|
assert.equal(deserialized.type.message, object.type.message);
|
|||
|
|
assert.equal(deserialized.range.message, object.range.message);
|
|||
|
|
assert.deepEqual(deserialized.range.cause, object.range.cause);
|
|||
|
|
assert(deserialized.error instanceof Error);
|
|||
|
|
assert(deserialized.type instanceof TypeError);
|
|||
|
|
assert(deserialized.range instanceof RangeError);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('structured cloning: self reference', function() {
|
|||
|
|
let object = {
|
|||
|
|
test: 'string',
|
|||
|
|
children: [
|
|||
|
|
{ name: 'child' }
|
|||
|
|
],
|
|||
|
|
value: new ArrayBuffer(10)
|
|||
|
|
};
|
|||
|
|
let u8 = new Uint8Array(object.value);
|
|||
|
|
u8[0] = 1;
|
|||
|
|
u8[1] = 2;
|
|||
|
|
object.self = object;
|
|||
|
|
object.children[1] = object;
|
|||
|
|
object.children[2] = object.children[0];
|
|||
|
|
object.childrenAgain = object.children;
|
|||
|
|
let packr = new Packr({
|
|||
|
|
moreTypes: true,
|
|||
|
|
structuredClone: true,
|
|||
|
|
});
|
|||
|
|
var serialized = packr.pack(object);
|
|||
|
|
var deserialized = packr.unpack(serialized);
|
|||
|
|
assert.equal(deserialized.self, deserialized);
|
|||
|
|
assert.equal(deserialized.children[0].name, 'child');
|
|||
|
|
assert.equal(deserialized.children[1], deserialized);
|
|||
|
|
assert.equal(deserialized.children[0], deserialized.children[2]);
|
|||
|
|
assert.equal(deserialized.children, deserialized.childrenAgain);
|
|||
|
|
assert.equal(deserialized.value.constructor.name, 'ArrayBuffer');
|
|||
|
|
u8 = new Uint8Array(deserialized.value);
|
|||
|
|
assert.equal(u8[0], 1);
|
|||
|
|
assert.equal(u8[1], 2);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('structured cloning: self reference with more types', function() {
|
|||
|
|
let set = new Set();
|
|||
|
|
set.add(['hello', 1, 2, { map: new Map([[set, set], ['a', 'b']]) }]);
|
|||
|
|
|
|||
|
|
let packr = new Packr({
|
|||
|
|
moreTypes: true,
|
|||
|
|
structuredClone: true,
|
|||
|
|
});
|
|||
|
|
let serialized = packr.pack(set);
|
|||
|
|
let deserialized = packr.unpack(serialized);
|
|||
|
|
assert.equal(deserialized.constructor.name, 'Set');
|
|||
|
|
let map = Array.from(deserialized)[0][3].map;
|
|||
|
|
assert.equal(map.get(deserialized), deserialized);
|
|||
|
|
|
|||
|
|
let sizeTestMap = new Map();
|
|||
|
|
for (let i = 0; i < 50; i++) {
|
|||
|
|
sizeTestMap.set(i || sizeTestMap, sizeTestMap);
|
|||
|
|
let deserialized = packr.unpack(packr.pack(sizeTestMap));
|
|||
|
|
assert.equal(deserialized.size, i + 1);
|
|||
|
|
assert(deserialized.has(deserialized));
|
|||
|
|
assert(deserialized.has(i || deserialized));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let sizeTestSet = new Set();
|
|||
|
|
for (let i = 0; i < 50; i++) {
|
|||
|
|
sizeTestSet.add(i || sizeTestSet);
|
|||
|
|
let deserialized = packr.unpack(packr.pack(sizeTestSet));
|
|||
|
|
assert.equal(deserialized.size, i + 1);
|
|||
|
|
assert(deserialized.has(deserialized));
|
|||
|
|
assert(deserialized.has(i || deserialized));
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('structured cloning: types', function() {
|
|||
|
|
let b = typeof Buffer != 'undefined' ? Buffer.alloc(20) : new Uint8Array(20);
|
|||
|
|
let fa = new Float32Array(b.buffer, 8, 2);
|
|||
|
|
fa[0] = 2.25;
|
|||
|
|
fa[1] = 6;
|
|||
|
|
let object = {
|
|||
|
|
error: new Error('test'),
|
|||
|
|
set: new Set(['a', 'b']),
|
|||
|
|
regexp: /test/gi,
|
|||
|
|
float32Array: fa,
|
|||
|
|
uint16Array: new Uint16Array([3, 4]),
|
|||
|
|
arrayBuffer: new Uint8Array([0xde, 0xad]).buffer,
|
|||
|
|
dataView: new DataView(new Uint8Array([0xbe, 0xef]).buffer),
|
|||
|
|
};
|
|||
|
|
let packr = new Packr({
|
|||
|
|
moreTypes: true,
|
|||
|
|
structuredClone: true,
|
|||
|
|
});
|
|||
|
|
var serialized = packr.pack(object);
|
|||
|
|
var deserialized = packr.unpack(serialized);
|
|||
|
|
assert.deepEqual(Array.from(deserialized.set), Array.from(object.set));
|
|||
|
|
assert.equal(deserialized.error.message, object.error.message);
|
|||
|
|
assert.equal(deserialized.regexp.test('TEST'), true);
|
|||
|
|
assert.equal(deserialized.float32Array.constructor.name, 'Float32Array');
|
|||
|
|
assert.equal(deserialized.float32Array[0], 2.25);
|
|||
|
|
assert.equal(deserialized.float32Array[1], 6);
|
|||
|
|
assert.equal(deserialized.uint16Array.constructor.name, 'Uint16Array');
|
|||
|
|
assert.equal(deserialized.uint16Array[0], 3);
|
|||
|
|
assert.equal(deserialized.uint16Array[1], 4);
|
|||
|
|
assert.equal(deserialized.arrayBuffer.constructor.name, 'ArrayBuffer');
|
|||
|
|
assert.equal(new DataView(deserialized.arrayBuffer).getUint16(), 0xdead);
|
|||
|
|
assert.equal(deserialized.dataView.constructor.name, 'DataView');
|
|||
|
|
assert.equal(deserialized.dataView.getUint16(), 0xbeef);
|
|||
|
|
});
|
|||
|
|
test('big bundledStrings', function() {
|
|||
|
|
const MSGPACK_OPTIONS = {bundleStrings: true};
|
|||
|
|
const packer = new Packr(MSGPACK_OPTIONS);
|
|||
|
|
const unpacker = new Unpackr(MSGPACK_OPTIONS);
|
|||
|
|
|
|||
|
|
const payload = {
|
|||
|
|
output: [
|
|||
|
|
{
|
|||
|
|
url: 'https://www.example.com/',
|
|||
|
|
},
|
|||
|
|
],
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
for (let i = 0; i < 10000; i++) {
|
|||
|
|
payload.output.push(payload.output[0]);
|
|||
|
|
}
|
|||
|
|
let deserialized = unpacker.unpack(packer.pack(payload));
|
|||
|
|
assert.equal(deserialized.output[0].url, payload.output[0].url);
|
|||
|
|
});
|
|||
|
|
test('structured clone with bundled strings', function() {
|
|||
|
|
const packer = new Packr({
|
|||
|
|
structuredClone: true, // both options must be enabled
|
|||
|
|
bundleStrings: true,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const v = {};
|
|||
|
|
|
|||
|
|
let shared = {
|
|||
|
|
name1: v,
|
|||
|
|
name2: v,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
let deserialized = packer.unpack(packer.pack(shared));
|
|||
|
|
assert.equal(deserialized.name1, deserialized.name2);
|
|||
|
|
|
|||
|
|
shared = {};
|
|||
|
|
shared.aaaa = shared; // key length >= 4
|
|||
|
|
|
|||
|
|
deserialized = packer.unpack(packer.pack(shared));
|
|||
|
|
assert.equal(deserialized.aaaa, deserialized);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('object without prototype', function(){
|
|||
|
|
var data = Object.create(null);
|
|||
|
|
data.test = 3;
|
|||
|
|
var serialized = pack(data);
|
|||
|
|
var deserialized = unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized, data);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('object with __proto__', function(){
|
|||
|
|
const data = { foo: 'bar', __proto__: { isAdmin: true } };
|
|||
|
|
var serialized = pack(data);
|
|||
|
|
var deserialized = unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized, { foo: 'bar' });
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('separate instances', function() {
|
|||
|
|
const packr = new Packr({
|
|||
|
|
structures: [['m', 'e'], ['action', 'share']]
|
|||
|
|
});
|
|||
|
|
const packr2 = new Packr({
|
|||
|
|
structures: [['m', 'e'], ['action', 'share']]
|
|||
|
|
});
|
|||
|
|
let packed = packr.pack([{m: 1, e: 2}, {action: 3, share: 4}]);
|
|||
|
|
// also tried directly decoding this without the first Packr instance packed = new Uint8Array([0x92, 0x40, 0x01, 0x02, 0x41, 0x03, 0x04]);
|
|||
|
|
console.log(packr2.unpack(packed));
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('many shared structures', function() {
|
|||
|
|
let data = [];
|
|||
|
|
for (let i = 0; i < 200; i++) {
|
|||
|
|
data.push({['a' + i]: i});
|
|||
|
|
}
|
|||
|
|
let structures = [];
|
|||
|
|
let savedStructures;
|
|||
|
|
let packr = new Packr({
|
|||
|
|
structures,
|
|||
|
|
saveStructures(structures) {
|
|||
|
|
savedStructures = structures;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
var serializedWith32 = packr.pack(data);
|
|||
|
|
assert.equal(savedStructures.length, 32);
|
|||
|
|
var deserialized = packr.unpack(serializedWith32);
|
|||
|
|
assert.deepEqual(deserialized, data);
|
|||
|
|
structures = structures.slice(0, 32);
|
|||
|
|
packr = new Packr({
|
|||
|
|
structures,
|
|||
|
|
maxSharedStructures: 100,
|
|||
|
|
saveStructures(structures) {
|
|||
|
|
savedStructures = structures;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
deserialized = packr.unpack(serializedWith32);
|
|||
|
|
assert.deepEqual(deserialized, data);
|
|||
|
|
structures = structures.slice(0, 32);
|
|||
|
|
packr = new Packr({
|
|||
|
|
structures,
|
|||
|
|
maxSharedStructures: 100,
|
|||
|
|
saveStructures(structures) {
|
|||
|
|
savedStructures = structures;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
let serialized = packr.pack(data);
|
|||
|
|
assert.equal(savedStructures.length, 100);
|
|||
|
|
deserialized = packr.unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized, data);
|
|||
|
|
|
|||
|
|
deserialized = packr.unpack(serializedWith32);
|
|||
|
|
assert.deepEqual(deserialized, data);
|
|||
|
|
assert.equal(savedStructures.length, 100);
|
|||
|
|
|
|||
|
|
deserialized = packr.unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized, data);
|
|||
|
|
assert.equal(packr.structures.sharedLength, 100);
|
|||
|
|
});
|
|||
|
|
test('more shared structures', function() {
|
|||
|
|
const structures = [];
|
|||
|
|
for (let i = 0; i < 40; i++) {
|
|||
|
|
structures.push(['a' + i]);
|
|||
|
|
}
|
|||
|
|
const structures2 = [...structures];
|
|||
|
|
const packr = new Packr({
|
|||
|
|
getStructures() {
|
|||
|
|
return structures
|
|||
|
|
},
|
|||
|
|
saveStructures(structures) {
|
|||
|
|
},
|
|||
|
|
maxSharedStructures: 100
|
|||
|
|
});
|
|||
|
|
const packr2 = new Packr({
|
|||
|
|
getStructures() {
|
|||
|
|
return structures2
|
|||
|
|
},
|
|||
|
|
saveStructures(structures) {
|
|||
|
|
},
|
|||
|
|
maxSharedStructures: 100
|
|||
|
|
});
|
|||
|
|
const inputData = {a35: 35};
|
|||
|
|
const buffer = packr.pack(inputData);
|
|||
|
|
const outputData = packr2.decode(buffer);
|
|||
|
|
assert.deepEqual(inputData, outputData);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('declined structure save re-packs against durable structures (no dangling record)', function() {
|
|||
|
|
// Regression: when saveStructures declines a save (a concurrent writer updated the shared
|
|||
|
|
// structures, or the store txn did not durably commit), the in-memory structures/transition
|
|||
|
|
// trie reference a record id that was never persisted. The re-pack must reload the durable
|
|||
|
|
// structures and re-mint/re-save — otherwise it re-emits the same record pointing at an
|
|||
|
|
// unsaved structure, and reads throw "Record id is not defined".
|
|||
|
|
const meta = new Packr();
|
|||
|
|
let store = null;
|
|||
|
|
let declinedOnce = false;
|
|||
|
|
const packr = new Packr({
|
|||
|
|
useRecords: true,
|
|||
|
|
getStructures() { return store ? meta.unpack(store) : undefined },
|
|||
|
|
saveStructures(structures) {
|
|||
|
|
if (!declinedOnce) { declinedOnce = true; return false } // decline the first save
|
|||
|
|
store = meta.pack(structures); return true
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
const a = packr.pack({ x: 9, y: 8 }); // first mint is declined -> must re-pack + re-save
|
|||
|
|
const b = packr.pack({ x: 7, y: 6 }); // same shape -> must still reference a saved structure
|
|||
|
|
// A fresh reader sees only the durably-saved structures (another thread / post-restart):
|
|||
|
|
const reader = new Packr({ getStructures() { return store ? meta.unpack(store) : undefined } });
|
|||
|
|
assert.deepEqual(reader.unpack(a), { x: 9, y: 8 });
|
|||
|
|
assert.deepEqual(reader.unpack(b), { x: 7, y: 6 });
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('big buffer', function() {
|
|||
|
|
var size = 100000000;
|
|||
|
|
var data = new Uint8Array(size).fill(1);
|
|||
|
|
var packed = pack(data);
|
|||
|
|
var unpacked = unpack(packed);
|
|||
|
|
assert.equal(unpacked.length, size);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('random strings', function(){
|
|||
|
|
var data = [];
|
|||
|
|
for (var i = 0; i < 2000; i++) {
|
|||
|
|
var str = 'test';
|
|||
|
|
while (Math.random() < 0.7 && str.length < 0x100000) {
|
|||
|
|
str = str + String.fromCharCode(90/(Math.random() + 0.01)) + str;
|
|||
|
|
}
|
|||
|
|
data.push(str);
|
|||
|
|
}
|
|||
|
|
var serialized = pack(data);
|
|||
|
|
var deserialized = unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized, data);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('map/date', function(){
|
|||
|
|
var map = new Map();
|
|||
|
|
map.set(4, 'four');
|
|||
|
|
map.set('three', 3);
|
|||
|
|
|
|||
|
|
|
|||
|
|
var data = {
|
|||
|
|
map: map,
|
|||
|
|
date: new Date(1532219539733),
|
|||
|
|
farFutureDate: new Date(3532219539133),
|
|||
|
|
fartherFutureDate: new Date('2106-08-05T18:48:20.323Z'),
|
|||
|
|
ancient: new Date(-3532219539133),
|
|||
|
|
invalidDate: new Date('invalid')
|
|||
|
|
};
|
|||
|
|
let packr = new Packr();
|
|||
|
|
var serialized = packr.pack(data);
|
|||
|
|
var deserialized = packr.unpack(serialized);
|
|||
|
|
assert.equal(deserialized.map.get(4), 'four');
|
|||
|
|
assert.equal(deserialized.map.get('three'), 3);
|
|||
|
|
assert.equal(deserialized.date.getTime(), 1532219539733);
|
|||
|
|
assert.equal(deserialized.farFutureDate.getTime(), 3532219539133);
|
|||
|
|
assert.equal(deserialized.fartherFutureDate.toISOString(), '2106-08-05T18:48:20.323Z');
|
|||
|
|
assert.equal(deserialized.ancient.getTime(), -3532219539133);
|
|||
|
|
assert.equal(deserialized.invalidDate.toString(), 'Invalid Date');
|
|||
|
|
});
|
|||
|
|
test('map/date with options', function(){
|
|||
|
|
var map = new Map();
|
|||
|
|
map.set(4, 'four');
|
|||
|
|
map.set('three', 3);
|
|||
|
|
|
|||
|
|
|
|||
|
|
var data = {
|
|||
|
|
map: map,
|
|||
|
|
date: new Date(1532219539011),
|
|||
|
|
invalidDate: new Date('invalid')
|
|||
|
|
};
|
|||
|
|
let packr = new Packr({
|
|||
|
|
mapsAsObjects: true,
|
|||
|
|
useTimestamp32: true,
|
|||
|
|
onInvalidDate: () => 'Custom invalid date'
|
|||
|
|
});
|
|||
|
|
var serialized = packr.pack(data);
|
|||
|
|
var deserialized = packr.unpack(serialized);
|
|||
|
|
assert.equal(deserialized.map[4], 'four');
|
|||
|
|
assert.equal(deserialized.map.three, 3);
|
|||
|
|
assert.equal(deserialized.date.getTime(), 1532219539000);
|
|||
|
|
assert.equal(deserialized.invalidDate, 'Custom invalid date');
|
|||
|
|
});
|
|||
|
|
test('key caching', function() {
|
|||
|
|
var data = {
|
|||
|
|
foo: 2,
|
|||
|
|
bar: 'test',
|
|||
|
|
four: 4,
|
|||
|
|
seven: 7,
|
|||
|
|
foz: 3,
|
|||
|
|
};
|
|||
|
|
var serialized = pack(data);
|
|||
|
|
var deserialized = unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized, data);
|
|||
|
|
// do multiple times to test caching
|
|||
|
|
var serialized = pack(data);
|
|||
|
|
var deserialized = unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized, data);
|
|||
|
|
var serialized = pack(data);
|
|||
|
|
var deserialized = unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized, data);
|
|||
|
|
});
|
|||
|
|
test('strings', function() {
|
|||
|
|
var data = [''];
|
|||
|
|
var serialized = pack(data);
|
|||
|
|
var deserialized = unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized, data);
|
|||
|
|
// do multiple times
|
|||
|
|
var serialized = pack(data);
|
|||
|
|
var deserialized = unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized, data);
|
|||
|
|
data = 'decode this: ᾜ';
|
|||
|
|
var serialized = pack(data);
|
|||
|
|
var deserialized = unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized, data);
|
|||
|
|
data = 'decode this that is longer but without any non-latin characters';
|
|||
|
|
var serialized = pack(data);
|
|||
|
|
var deserialized = unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized, data);
|
|||
|
|
});
|
|||
|
|
test('decimal float32', function() {
|
|||
|
|
var data = {
|
|||
|
|
a: 2.526,
|
|||
|
|
b: 0.0035235,
|
|||
|
|
c: 0.00000000000352501,
|
|||
|
|
d: 3252.77,
|
|||
|
|
};
|
|||
|
|
let packr = new Packr({
|
|||
|
|
useFloat32: DECIMAL_FIT
|
|||
|
|
});
|
|||
|
|
var serialized = packr.pack(data);
|
|||
|
|
assert.equal(serialized.length, 32);
|
|||
|
|
var deserialized = packr.unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized, data);
|
|||
|
|
});
|
|||
|
|
test('int64/uint64 should be bigints by default', function() {
|
|||
|
|
var data = {
|
|||
|
|
a: 325283295382932843n
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
let packr = new Packr();
|
|||
|
|
var serialized = packr.pack(data);
|
|||
|
|
var deserialized = packr.unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized.a, 325283295382932843n);
|
|||
|
|
});
|
|||
|
|
test('bigint to float', function() {
|
|||
|
|
var data = {
|
|||
|
|
a: 325283295382932843n
|
|||
|
|
};
|
|||
|
|
let packr = new Packr({
|
|||
|
|
int64AsType: 'number'
|
|||
|
|
});
|
|||
|
|
var serialized = packr.pack(data);
|
|||
|
|
var deserialized = packr.unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized.a, 325283295382932843);
|
|||
|
|
});
|
|||
|
|
test('int64AsNumber compatibility', function() {
|
|||
|
|
// https://github.com/kriszyp/msgpackr/pull/85
|
|||
|
|
var data = {
|
|||
|
|
a: 325283295382932843n
|
|||
|
|
};
|
|||
|
|
let packr = new Packr({
|
|||
|
|
int64AsNumber: true
|
|||
|
|
});
|
|||
|
|
var serialized = packr.pack(data);
|
|||
|
|
var deserialized = packr.unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized.a, 325283295382932843);
|
|||
|
|
});
|
|||
|
|
test('bigint to auto (float or bigint)', function() {
|
|||
|
|
var data = {
|
|||
|
|
a: -9007199254740993n,
|
|||
|
|
b: -9007199254740992n,
|
|||
|
|
c: 0n,
|
|||
|
|
d: 9007199254740992n,
|
|||
|
|
e: 9007199254740993n,
|
|||
|
|
};
|
|||
|
|
let packr = new Packr({
|
|||
|
|
int64AsType: 'auto'
|
|||
|
|
});
|
|||
|
|
var serialized = packr.pack(data);
|
|||
|
|
var deserialized = packr.unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized.a, -9007199254740993n);
|
|||
|
|
assert.deepEqual(deserialized.b, -9007199254740992);
|
|||
|
|
assert.deepEqual(deserialized.c, 0);
|
|||
|
|
assert.deepEqual(deserialized.d, 9007199254740992);
|
|||
|
|
assert.deepEqual(deserialized.e, 9007199254740993n);
|
|||
|
|
});
|
|||
|
|
test('bigint to string', function() {
|
|||
|
|
var data = {
|
|||
|
|
a: 325283295382932843n,
|
|||
|
|
};
|
|||
|
|
let packr = new Packr({
|
|||
|
|
int64AsType: 'string'
|
|||
|
|
});
|
|||
|
|
var serialized = packr.pack(data);
|
|||
|
|
var deserialized = packr.unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized.a, '325283295382932843');
|
|||
|
|
});
|
|||
|
|
test('fixint should be one byte', function(){
|
|||
|
|
let encoded = pack(123);
|
|||
|
|
assert.equal(encoded.length, 1);
|
|||
|
|
});
|
|||
|
|
test('numbers', function(){
|
|||
|
|
var data = {
|
|||
|
|
bigEncodable: 48978578104322,
|
|||
|
|
dateEpoch: 1530886513200,
|
|||
|
|
realBig: 3432235352353255323,
|
|||
|
|
decimal: 32.55234,
|
|||
|
|
negative: -34.11,
|
|||
|
|
exponential: 0.234e123,
|
|||
|
|
tiny: 3.233e-120,
|
|||
|
|
zero: 0,
|
|||
|
|
//negativeZero: -0,
|
|||
|
|
Infinity: Infinity
|
|||
|
|
};
|
|||
|
|
var serialized = pack(data);
|
|||
|
|
var deserialized = unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized, data);
|
|||
|
|
});
|
|||
|
|
test('bigint', function(){
|
|||
|
|
var data = {
|
|||
|
|
bigintSmall: 352n,
|
|||
|
|
bigintSmallNegative: -333335252n,
|
|||
|
|
bigintBig: 2n**64n - 1n, // biggest possible
|
|||
|
|
bigintBigNegative: -(2n**63n), // largest negative
|
|||
|
|
mixedWithNormal: 44,
|
|||
|
|
};
|
|||
|
|
var serialized = pack(data);
|
|||
|
|
var deserialized = unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized, data);
|
|||
|
|
var tooBigInt = {
|
|||
|
|
tooBig: 2n**66n
|
|||
|
|
};
|
|||
|
|
assert.throws(function(){ serialized = pack(tooBigInt); });
|
|||
|
|
let packr = new Packr({
|
|||
|
|
largeBigIntToFloat: true
|
|||
|
|
});
|
|||
|
|
serialized = packr.pack(tooBigInt);
|
|||
|
|
deserialized = unpack(serialized);
|
|||
|
|
assert.isTrue(deserialized.tooBig > 2n**65n);
|
|||
|
|
|
|||
|
|
packr = new Packr({
|
|||
|
|
largeBigIntToString: true
|
|||
|
|
});
|
|||
|
|
serialized = packr.pack(tooBigInt);
|
|||
|
|
deserialized = unpack(serialized);
|
|||
|
|
assert.equal(deserialized.tooBig, (2n**66n).toString());
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('roundFloat32', function() {
|
|||
|
|
assert.equal(roundFloat32(0.00333000003), 0.00333);
|
|||
|
|
assert.equal(roundFloat32(43.29999999993), 43.3);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('buffers', function(){
|
|||
|
|
var data = {
|
|||
|
|
buffer1: new Uint8Array([2,3,4]),
|
|||
|
|
buffer2: new Uint8Array(pack(sampleData))
|
|||
|
|
};
|
|||
|
|
var serialized = pack(data);
|
|||
|
|
var deserialized = unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized, data);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('notepack test', function() {
|
|||
|
|
const data = {
|
|||
|
|
foo: 1,
|
|||
|
|
bar: [1, 2, 3, 4, 'abc', 'def'],
|
|||
|
|
foobar: {
|
|||
|
|
foo: true,
|
|||
|
|
bar: -2147483649,
|
|||
|
|
foobar: {
|
|||
|
|
foo: new Uint8Array([1, 2, 3, 4, 5]),
|
|||
|
|
bar: 1.5,
|
|||
|
|
foobar: [true, false, 'abcdefghijkmonpqrstuvwxyz']
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
var serialized = pack(data);
|
|||
|
|
var deserialized = unpack(serialized);
|
|||
|
|
var deserialized = unpack(serialized);
|
|||
|
|
var deserialized = unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized, data);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('arrays in map keys', function() {
|
|||
|
|
const msgpackr = new Packr({ mapsAsObjects: true, allowArraysInMapKeys: true });
|
|||
|
|
|
|||
|
|
const map = new Map();
|
|||
|
|
map.set([1, 2, 3], 1);
|
|||
|
|
map.set([1, 2, ['foo', 3.14]], 2);
|
|||
|
|
|
|||
|
|
const packed = msgpackr.pack(map);
|
|||
|
|
const unpacked = msgpackr.unpack(packed);
|
|||
|
|
assert.deepEqual(unpacked, { '1,2,3': 1, '1,2,foo,3.14': 2 });
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('utf16 causing expansion', function() {
|
|||
|
|
this.timeout(10000);
|
|||
|
|
let data = {fixstr: 'ᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝ', str8:'ᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝᾐᾑᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜᾝ'};
|
|||
|
|
var serialized = pack(data);
|
|||
|
|
var deserialized = unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized, data);
|
|||
|
|
});
|
|||
|
|
test('unpackMultiple', () => {
|
|||
|
|
let values = unpackMultiple(new Uint8Array([1, 2, 3, 4]));
|
|||
|
|
assert.deepEqual(values, [1, 2, 3, 4]);
|
|||
|
|
values = [];
|
|||
|
|
unpackMultiple(new Uint8Array([1, 2, 3, 4]), value => values.push(value));
|
|||
|
|
assert.deepEqual(values, [1, 2, 3, 4]);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('unpackMultiple with positions', () => {
|
|||
|
|
let values = unpackMultiple(new Uint8Array([1, 2, 3, 4]));
|
|||
|
|
assert.deepEqual(values, [1, 2, 3, 4]);
|
|||
|
|
values = [];
|
|||
|
|
unpackMultiple(new Uint8Array([1, 2, 3, 4]), (value,start,end) => values.push([value,start,end]));
|
|||
|
|
assert.deepEqual(values, [[1,0,1], [2,1,2], [3,2,3], [4,3,4]]);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('pack toJSON returning this', () => {
|
|||
|
|
class Serializable {
|
|||
|
|
someData = [1, 2, 3, 4]
|
|||
|
|
toJSON() {
|
|||
|
|
return this
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
const serialized = pack(new Serializable);
|
|||
|
|
const deserialized = unpack(serialized);
|
|||
|
|
assert.deepStrictEqual(deserialized, { someData: [1, 2, 3, 4] });
|
|||
|
|
});
|
|||
|
|
test('skip values', function () {
|
|||
|
|
var data = {
|
|||
|
|
data: [
|
|||
|
|
{ a: 1, name: 'one', type: 'odd', isOdd: true },
|
|||
|
|
{ a: 2, name: 'two', type: 'even', isOdd: undefined },
|
|||
|
|
{ a: 3, name: 'three', type: 'odd', isOdd: true },
|
|||
|
|
{ a: 4, name: 'four', type: 'even', isOdd: null},
|
|||
|
|
{ a: 5, name: 'five', type: 'odd', isOdd: true },
|
|||
|
|
{ a: 6, name: 'six', type: 'even', isOdd: null }
|
|||
|
|
],
|
|||
|
|
description: 'some names',
|
|||
|
|
types: ['odd', 'even'],
|
|||
|
|
convertEnumToNum: [
|
|||
|
|
{ prop: 'test' },
|
|||
|
|
{ prop: 'test' },
|
|||
|
|
{ prop: 'test' },
|
|||
|
|
{ prop: 1 },
|
|||
|
|
{ prop: 2 },
|
|||
|
|
{ prop: [undefined, null] },
|
|||
|
|
{ prop: null }
|
|||
|
|
]
|
|||
|
|
};
|
|||
|
|
var expected = {
|
|||
|
|
data: [
|
|||
|
|
{ a: 1, name: 'one', type: 'odd', isOdd: true },
|
|||
|
|
{ a: 2, name: 'two', type: 'even' },
|
|||
|
|
{ a: 3, name: 'three', type: 'odd', isOdd: true },
|
|||
|
|
{ a: 4, name: 'four', type: 'even', },
|
|||
|
|
{ a: 5, name: 'five', type: 'odd', isOdd: true },
|
|||
|
|
{ a: 6, name: 'six', type: 'even' }
|
|||
|
|
],
|
|||
|
|
description: 'some names',
|
|||
|
|
types: ['odd', 'even'],
|
|||
|
|
convertEnumToNum: [
|
|||
|
|
{ prop: 'test' },
|
|||
|
|
{ prop: 'test' },
|
|||
|
|
{ prop: 'test' },
|
|||
|
|
{ prop: 1 },
|
|||
|
|
{ prop: 2 },
|
|||
|
|
{ prop: [undefined, null] },
|
|||
|
|
{}
|
|||
|
|
]
|
|||
|
|
};
|
|||
|
|
let packr = new Packr({ useRecords: false, skipValues: [undefined, null] });
|
|||
|
|
var serialized = packr.pack(data);
|
|||
|
|
var deserialized = packr.unpack(serialized);
|
|||
|
|
assert.deepEqual(deserialized, expected);
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
suite('msgpackr performance tests', function(){
|
|||
|
|
test('performance JSON.parse', function() {
|
|||
|
|
var data = sampleData;
|
|||
|
|
this.timeout(10000);
|
|||
|
|
var serialized = JSON.stringify(data);
|
|||
|
|
console.log('JSON size', serialized.length);
|
|||
|
|
for (var i = 0; i < ITERATIONS; i++) {
|
|||
|
|
JSON.parse(serialized);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
test('performance JSON.stringify', function() {
|
|||
|
|
var data = sampleData;
|
|||
|
|
this.timeout(10000);
|
|||
|
|
for (var i = 0; i < ITERATIONS; i++) {
|
|||
|
|
JSON.stringify(data);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
test('performance unpack', function() {
|
|||
|
|
var data = sampleData;
|
|||
|
|
this.timeout(10000);
|
|||
|
|
let structures = [];
|
|||
|
|
var serialized = pack(data);
|
|||
|
|
console.log('MessagePack size', serialized.length);
|
|||
|
|
let packr = new Packr({ structures, bundleStrings: false });
|
|||
|
|
var serialized = packr.pack(data);
|
|||
|
|
console.log('msgpackr w/ record ext size', serialized.length);
|
|||
|
|
for (var i = 0; i < ITERATIONS; i++) {
|
|||
|
|
packr.unpack(serialized);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
test('performance pack', function() {
|
|||
|
|
var data = sampleData;
|
|||
|
|
this.timeout(10000);
|
|||
|
|
let structures = [];
|
|||
|
|
let packr = new Packr({ structures, bundleStrings: false });
|
|||
|
|
let buffer = typeof Buffer != 'undefined' ? Buffer.alloc(0x10000) : new Uint8Array(0x10000);
|
|||
|
|
|
|||
|
|
for (var i = 0; i < ITERATIONS; i++) {
|
|||
|
|
//serialized = pack(data, { shared: sharedStructure })
|
|||
|
|
packr.useBuffer(buffer);
|
|||
|
|
packr.pack(data);
|
|||
|
|
//var serializedGzip = deflateSync(serialized)
|
|||
|
|
}
|
|||
|
|
//console.log('serialized', serialized.length, global.propertyComparisons)
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
suite('msgpackr – maxOwnStructures cap (randomAccessStructure)', function () {
|
|||
|
|
// Helper: create a width-heterogeneous record generator using a deterministic PRNG.
|
|||
|
|
// Objects have up to 6 fields drawn from a sparse set, each value is an integer whose
|
|||
|
|
// width (num8 / num32 / num64) varies per value, producing many distinct typed structures.
|
|||
|
|
// useRecords: false keeps the classic named-record encoder out of the way so all structure
|
|||
|
|
// creation goes through the typed-struct path and the cap is exercised in isolation.
|
|||
|
|
function makeCapRunner(cap) {
|
|||
|
|
const fields = ['a','b','c','d','e','f','g','h'];
|
|||
|
|
let seed = 42;
|
|||
|
|
const rnd = () => { seed = (seed * 1664525 + 1013904223) >>> 0; return seed / 0xffffffff; };
|
|||
|
|
const packr = new Packr({
|
|||
|
|
structures: [],
|
|||
|
|
useRecords: false,
|
|||
|
|
randomAccessStructure: true,
|
|||
|
|
maxOwnStructures: cap,
|
|||
|
|
});
|
|||
|
|
const norm = r => packr.unpack(packr.pack(r));
|
|||
|
|
for (let i = 0; i < 4000; i++) {
|
|||
|
|
const r = {};
|
|||
|
|
for (let j = 0; j < Math.ceil(rnd() * 6); j++) {
|
|||
|
|
// values cycle across num8 / num32 / float64 ranges to force distinct structures
|
|||
|
|
const mag = rnd() < 0.33 ? 10 : rnd() < 0.5 ? 400000 : 1e13;
|
|||
|
|
r[fields[Math.floor(rnd() * 8)]] = Math.floor(rnd() * mag);
|
|||
|
|
}
|
|||
|
|
assert.deepEqual(norm(r), r);
|
|||
|
|
}
|
|||
|
|
return packr.typedStructs ? packr.typedStructs.length : 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
test('uncapped (default) grows well past 256 for width-heterogeneous records', function () {
|
|||
|
|
assert.ok(makeCapRunner(undefined) > 256, 'expected uncapped typedStructs to exceed 256');
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('cap=64 bounds typedStructs.length and preserves round-trips', function () {
|
|||
|
|
assert.ok(makeCapRunner(64) <= 64, 'typedStructs should not exceed cap of 64');
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('cap=256 bounds typedStructs.length and preserves round-trips', function () {
|
|||
|
|
assert.ok(makeCapRunner(256) <= 256, 'typedStructs should not exceed cap of 256');
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('flat-record streams stay a strict hard bound', function () {
|
|||
|
|
// With maxOwnStructures=16, pack 2000 flat records; typedStructs must never exceed 16.
|
|||
|
|
const packr = new Packr({
|
|||
|
|
structures: [],
|
|||
|
|
useRecords: false,
|
|||
|
|
randomAccessStructure: true,
|
|||
|
|
maxOwnStructures: 16,
|
|||
|
|
});
|
|||
|
|
let seed = 99;
|
|||
|
|
const rnd = () => { seed = (seed * 1664525 + 1013904223) >>> 0; return seed / 0xffffffff; };
|
|||
|
|
for (let i = 0; i < 2000; i++) {
|
|||
|
|
const r = { x: Math.floor(rnd() * 1e6), y: Math.floor(rnd() * 200), z: Math.floor(rnd() * 1e12) };
|
|||
|
|
assert.deepEqual(packr.unpack(packr.pack(r)), r);
|
|||
|
|
}
|
|||
|
|
assert.ok(packr.typedStructs.length <= 16, 'flat records must stay within cap, got ' + packr.typedStructs.length);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('capped-out records fall back to plain encoding and still round-trip', function () {
|
|||
|
|
// Once the cap is hit, novel shapes must decode correctly via plain msgpack fallback.
|
|||
|
|
const packr = new Packr({
|
|||
|
|
structures: [],
|
|||
|
|
useRecords: false,
|
|||
|
|
randomAccessStructure: true,
|
|||
|
|
maxOwnStructures: 2,
|
|||
|
|
});
|
|||
|
|
const norm = r => packr.unpack(packr.pack(r));
|
|||
|
|
assert.deepEqual(norm({ a: 1 }), { a: 1 }); // mints structure 0
|
|||
|
|
assert.deepEqual(norm({ b: 'hello' }), { b: 'hello' }); // mints structure 1, cap hit
|
|||
|
|
// These novel shapes fall back to plain msgpack — must still decode correctly:
|
|||
|
|
assert.deepEqual(norm({ c: 42 }), { c: 42 });
|
|||
|
|
assert.deepEqual(norm({ a: 1, b: 'hi', c: 99 }), { a: 1, b: 'hi', c: 99 });
|
|||
|
|
assert.strictEqual(packr.typedStructs.length, 2, 'cap must be exact');
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('a known key later seen as a nested object falls back cleanly', function () {
|
|||
|
|
// Once frozen, a previously-learned scalar key carrying an object must bail BEFORE pack()
|
|||
|
|
// advances the shared encoder position — otherwise the plain fallback gets corrupt bytes.
|
|||
|
|
const packr = new Packr({
|
|||
|
|
structures: [],
|
|||
|
|
useRecords: false,
|
|||
|
|
randomAccessStructure: true,
|
|||
|
|
maxOwnStructures: 1,
|
|||
|
|
});
|
|||
|
|
const norm = r => packr.unpack(packr.pack(r));
|
|||
|
|
assert.deepEqual(norm({ a: 1 }), { a: 1 }); // mints structure 0, cap hit
|
|||
|
|
const r2 = { a: { x: 1 } };
|
|||
|
|
assert.deepEqual(norm(r2), r2); // 'a' known but now carries object — must fall back cleanly
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('nested records do not overshoot the cap and still round-trip', function () {
|
|||
|
|
// A nested object mints its own structure before the outer record, so a stale frozen flag
|
|||
|
|
// could push the outer record past the cap. The record-id mint guard re-checks the live
|
|||
|
|
// length, keeping typedStructs.length a strict bound.
|
|||
|
|
const packr = new Packr({
|
|||
|
|
structures: [],
|
|||
|
|
useRecords: false,
|
|||
|
|
randomAccessStructure: true,
|
|||
|
|
maxOwnStructures: 4,
|
|||
|
|
});
|
|||
|
|
let seed = 7;
|
|||
|
|
const rnd = () => { seed = (seed * 1103515245 + 12345) & 0x7fffffff; return seed / 0x7fffffff; };
|
|||
|
|
for (let i = 0; i < 1000; i++) {
|
|||
|
|
const r = { outer: { inner: (rnd() * 1e7 | 0) * 1000 }, tag: 't' + (i % 20), n: (rnd() * 300 | 0) };
|
|||
|
|
assert.deepEqual(packr.unpack(packr.pack(r)), r);
|
|||
|
|
}
|
|||
|
|
assert.ok(packr.typedStructs.length <= 4, 'nested encodes must not push typedStructs past the cap, got ' + packr.typedStructs.length);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('persisted typed structures still load after a capped encoder froze the dictionary', function () {
|
|||
|
|
// Replaying persisted structures in onLoadedStructures must always succeed, regardless of
|
|||
|
|
// maxOwnStructures — the cap only limits minting NEW structures during encode.
|
|||
|
|
let saved = null;
|
|||
|
|
const writer = new Packr({
|
|||
|
|
structures: [],
|
|||
|
|
useRecords: false,
|
|||
|
|
randomAccessStructure: true,
|
|||
|
|
saveStructures(s) { saved = s; return true; },
|
|||
|
|
getStructures() { return saved; },
|
|||
|
|
});
|
|||
|
|
const buf = writer.pack({ name: 'Alice', age: 30 });
|
|||
|
|
|
|||
|
|
// Warm up a capped encoder so the module-global-if-any freeze state is set.
|
|||
|
|
const capped = new Packr({
|
|||
|
|
structures: [],
|
|||
|
|
useRecords: false,
|
|||
|
|
randomAccessStructure: true,
|
|||
|
|
maxOwnStructures: 1,
|
|||
|
|
});
|
|||
|
|
capped.pack({ x: 1 });
|
|||
|
|
capped.pack({ y: 2, z: 3 }); // cap reached
|
|||
|
|
|
|||
|
|
// A fresh reader must still rebuild the transition trie from saved structures.
|
|||
|
|
const reader = new Packr({
|
|||
|
|
structures: [],
|
|||
|
|
useRecords: false,
|
|||
|
|
randomAccessStructure: true,
|
|||
|
|
getStructures() { return saved; },
|
|||
|
|
});
|
|||
|
|
const result = reader.unpack(buf);
|
|||
|
|
assert.equal(result.name, 'Alice');
|
|||
|
|
assert.equal(result.age, 30);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('the cap is per-instance: an uncapped sibling cannot lift this instance\'s cap', function () {
|
|||
|
|
// frozen is derived from each encoder's own typedStructs.length — not a shared global —
|
|||
|
|
// so an uncapped sibling churning out structures cannot lift the cap on the bounded one.
|
|||
|
|
const uncapped = new Packr({
|
|||
|
|
structures: [],
|
|||
|
|
useRecords: false,
|
|||
|
|
randomAccessStructure: true,
|
|||
|
|
});
|
|||
|
|
const capped = new Packr({
|
|||
|
|
structures: [],
|
|||
|
|
useRecords: false,
|
|||
|
|
randomAccessStructure: true,
|
|||
|
|
maxOwnStructures: 2,
|
|||
|
|
});
|
|||
|
|
let seed = 1;
|
|||
|
|
const rnd = () => { seed = (seed * 1664525 + 1013904223) >>> 0; return seed / 0xffffffff; };
|
|||
|
|
const mk = () => { const o = {}; for (let f = 0; f < 20; f++) if (rnd() < 0.5) o['f' + f] = Math.floor(rnd() * 1e7); return o; };
|
|||
|
|
for (let i = 0; i < 500; i++) {
|
|||
|
|
uncapped.pack(mk()); // grows the sibling's dictionary freely
|
|||
|
|
const r = mk();
|
|||
|
|
assert.deepEqual(capped.unpack(capped.pack(r)), r);
|
|||
|
|
}
|
|||
|
|
assert.ok(capped.typedStructs.length <= 2, 'capped must stay bounded, got ' + capped.typedStructs.length);
|
|||
|
|
assert.ok(uncapped.typedStructs.length > 2, 'uncapped sibling should grow freely');
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('a layout-retry record (large fixed section + nested refs) does not corrupt', function () {
|
|||
|
|
// A large fixed section overflows the ref-start estimate and triggers the internal retry
|
|||
|
|
// (which re-invokes writeStruct after refs were packed). The retry passes structureKnown=true
|
|||
|
|
// to re-encode the already-minted structure rather than bailing under the now-reached cap —
|
|||
|
|
// bailing there would write the fallback at an advanced position and corrupt the bytes.
|
|||
|
|
const packr = new Packr({ structures: [], useRecords: false, randomAccessStructure: true, maxOwnStructures: 1 });
|
|||
|
|
const norm = r => packr.unpack(packr.pack(r));
|
|||
|
|
const mk = base => { const r = {}; for (let i = 0; i < 40; i++) r['n' + i] = base + i; r.a = { x: base }; r.b = { y: base + 1 }; return r; };
|
|||
|
|
assert.deepEqual(norm(mk(1000000)), mk(1000000));
|
|||
|
|
assert.deepEqual(norm(mk(2000000)), mk(2000000));
|
|||
|
|
assert.ok(packr.typedStructs.length <= 1, 'retry-path records must not exceed the cap, got ' + packr.typedStructs.length);
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
suite('msgpackr – readOnlyStructures (write-disable, read-compatible)', function () {
|
|||
|
|
// readOnlyStructures keeps randomAccessStructure decode semantics — existing random-access
|
|||
|
|
// struct data still decodes and the struct-safe integer boundary is preserved — but never mints
|
|||
|
|
// a new random-access structure on write. Objects fall through to the classic shared-structure
|
|||
|
|
// record path (bytes 0x40-0x7f, disjoint from struct headers at 0x20-0x3f), the bounded,
|
|||
|
|
// width-agnostic encoding used before struct mode. This is the mechanism for disabling typed
|
|||
|
|
// structures on an existing store without breaking reads.
|
|||
|
|
const sharedStore = () => {
|
|||
|
|
let saved;
|
|||
|
|
return { getStructures: () => saved, saveStructures: (s) => { saved = s; return true; } };
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
test('decodes pre-existing random-access structs (read stays enabled)', function () {
|
|||
|
|
const full = new Packr({ randomAccessStructure: true, useRecords: true });
|
|||
|
|
const rec = { a: 1, b: 'hello', c: true, n: 42 };
|
|||
|
|
const buf = full.pack(rec);
|
|||
|
|
assert.ok(buf[0] >= 0x20 && buf[0] < 0x40, 'control: full encoder emits a random-access struct');
|
|||
|
|
|
|||
|
|
const ro = new Packr({ randomAccessStructure: true, useRecords: true, readOnlyStructures: true });
|
|||
|
|
ro.typedStructs = full.typedStructs; // share the existing random-access dictionary
|
|||
|
|
assert.deepEqual(ro.unpack(buf), rec);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('writes classic shared-structure records (0x40-0x7f), never a random-access struct', function () {
|
|||
|
|
const ro = new Packr({ randomAccessStructure: true, useRecords: true, readOnlyStructures: true, ...sharedStore() });
|
|||
|
|
const rec = { x: 9, y: 'world', z: [1, 2, 3], nested: { deep: 50, k: 42 } };
|
|||
|
|
const b1 = ro.pack(rec);
|
|||
|
|
assert.ok(b1[0] >= 0x40 && b1[0] < 0x80, 'expected a classic record (0x40-0x7f), got 0x' + b1[0].toString(16));
|
|||
|
|
assert.strictEqual(ro.typedStructs ? ro.typedStructs.length : 0, 0, 'no random-access struct minted');
|
|||
|
|
assert.deepEqual(ro.unpack(b1), rec);
|
|||
|
|
// a second record of the same shape reuses the shared classic structure (still classic)
|
|||
|
|
const b2 = ro.pack({ x: 1, y: 'a', z: [], nested: { deep: 0, k: 0 } });
|
|||
|
|
assert.ok(b2[0] >= 0x40 && b2[0] < 0x80, 'second record also classic');
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('preserves the struct-safe integer boundary (nested 0x20-0x3f ints do not collide)', function () {
|
|||
|
|
const ro = new Packr({ randomAccessStructure: true, useRecords: true, readOnlyStructures: true, ...sharedStore() });
|
|||
|
|
for (const v of [0x1f, 0x20, 0x2a, 0x3f, 0x40, 0x7f]) {
|
|||
|
|
assert.deepEqual(ro.unpack(ro.pack({ v, nested: { w: v } })), { v, nested: { w: v } });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('a normal randomAccessStructure reader decodes readOnly classic records (replication compat)', function () {
|
|||
|
|
let saved;
|
|||
|
|
const store = { getStructures: () => saved, saveStructures: (s) => { saved = s; return true; } };
|
|||
|
|
const ro = new Packr({ randomAccessStructure: true, useRecords: true, readOnlyStructures: true, ...store });
|
|||
|
|
const roBuf = ro.pack({ p: 'q', n: 42 });
|
|||
|
|
assert.ok(roBuf[0] >= 0x40 && roBuf[0] < 0x80, 'readOnly writes a classic record');
|
|||
|
|
|
|||
|
|
// A peer that is NOT readOnly (a normal struct-writing node) shares the classic-structure
|
|||
|
|
// store and decodes the classic record correctly — no mapsAsObjects needed, since classic
|
|||
|
|
// records decode to objects natively.
|
|||
|
|
const peer = new Packr({ randomAccessStructure: true, useRecords: true, ...store });
|
|||
|
|
assert.deepEqual(peer.unpack(roBuf), { p: 'q', n: 42 });
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('every object shape uses the classic path (null-proto, shadowed ctor) — no random-access struct', function () {
|
|||
|
|
const ro = new Packr({ randomAccessStructure: true, useRecords: true, readOnlyStructures: true, ...sharedStore() });
|
|||
|
|
|
|||
|
|
const np = Object.create(null); np.a = 1; np.b = 'x';
|
|||
|
|
assert.deepEqual(ro.unpack(ro.pack(np)), { a: 1, b: 'x' });
|
|||
|
|
|
|||
|
|
const cs = { constructor: 'shadow', a: 1 };
|
|||
|
|
assert.deepEqual(ro.unpack(ro.pack(cs)), { constructor: 'shadow', a: 1 });
|
|||
|
|
|
|||
|
|
assert.strictEqual(ro.typedStructs ? ro.typedStructs.length : 0, 0, 'no random-access struct minted for any shape');
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
suite('msgpackr – two-byte record definitions (over-cap own records with a shared store)', function () {
|
|||
|
|
// Regression for a data-corruption bug: with a shared-structures store (getStructures/
|
|||
|
|
// saveStructures) and maxOwnStructures > 32, msgpackr uses two-byte record ids
|
|||
|
|
// (useTwoByteRecords). Over-cap "own" record shapes fall back to the classic record encoder,
|
|||
|
|
// which emits a SELF-CONTAINED record definition (0xd5 0x72 <firstByte> <highByte> <keys>
|
|||
|
|
// <values>). On read, recordDefinition() consumed the high byte itself, then invoked
|
|||
|
|
// structure.read() — which, for a highByte === 0 structure, was a second-byte reader that
|
|||
|
|
// consumed the FIRST VALUE BYTE as a phantom high byte, mis-resolving to a never-defined id
|
|||
|
|
// ("Record id is not defined for N"). The one-byte path (maxOwnStructures <= 32, fixext 1
|
|||
|
|
// 0xd4 0x72) was unaffected because no high byte / second-byte reader is involved.
|
|||
|
|
// Fix: recordDefinition reads the body via the un-wrapped reader (structure.read0).
|
|||
|
|
const sharedStore = () => {
|
|||
|
|
let saved = [];
|
|||
|
|
return { getStructures: () => saved, saveStructures: (s) => { saved = s; return true; }, peek: () => saved };
|
|||
|
|
};
|
|||
|
|
// Width-heterogeneous record generator: many distinct shapes, exceeding any modest cap, so
|
|||
|
|
// the over-cap classic-record fallback path is exercised heavily.
|
|||
|
|
const makeRecords = (count) => {
|
|||
|
|
const out = [];
|
|||
|
|
for (let i = 0; i < count; i++) {
|
|||
|
|
const rec = { id: i };
|
|||
|
|
const n = 1 + (i % 6);
|
|||
|
|
for (let a = 0; a < n; a++) rec['attr_' + ((i * 7 + a) % 400)] = i % 2 ? 's' + i : i;
|
|||
|
|
out.push(rec);
|
|||
|
|
}
|
|||
|
|
return out;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
test('cap=256 + shared store round-trips every over-cap shape (two-byte mode)', function () {
|
|||
|
|
const store = sharedStore();
|
|||
|
|
const o = { randomAccessStructure: true, useRecords: true, useBigIntExtension: true, maxOwnStructures: 256, ...store };
|
|||
|
|
const w = new Packr(o), r = new Packr(o);
|
|||
|
|
const records = makeRecords(3000);
|
|||
|
|
let sawTwoByteDef = false;
|
|||
|
|
for (const rec of records) {
|
|||
|
|
const buf = w.pack(rec);
|
|||
|
|
if (buf[0] === 0xd5 && buf[1] === 0x72) sawTwoByteDef = true;
|
|||
|
|
assert.deepEqual(r.unpack(buf), rec);
|
|||
|
|
}
|
|||
|
|
assert.ok(sawTwoByteDef, 'expected at least one two-byte (0xd5 0x72) record definition to be exercised');
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('readOnlyStructures + cap=256 + shared store round-trips (two-byte mode)', function () {
|
|||
|
|
const store = sharedStore();
|
|||
|
|
const o = { randomAccessStructure: true, useRecords: true, useBigIntExtension: true, readOnlyStructures: true, maxOwnStructures: 256, ...store };
|
|||
|
|
const w = new Packr(o), r = new Packr(o);
|
|||
|
|
for (const rec of makeRecords(3000)) {
|
|||
|
|
assert.deepEqual(r.unpack(w.pack(rec)), rec);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('over-cap own records do not grow the shared dictionary without bound', function () {
|
|||
|
|
const store = sharedStore();
|
|||
|
|
const o = { randomAccessStructure: true, useRecords: true, useBigIntExtension: true, maxOwnStructures: 256, ...store };
|
|||
|
|
const w = new Packr(o);
|
|||
|
|
for (const rec of makeRecords(3000)) w.pack(rec);
|
|||
|
|
const saved = store.peek();
|
|||
|
|
const named = saved instanceof Map ? (saved.get('named') || []) : (saved || []);
|
|||
|
|
// The classic named-structures store must stay tiny (bounded by the few genuinely shared
|
|||
|
|
// shapes); over-cap shapes are inlined as self-contained definitions, not minted as
|
|||
|
|
// ever-growing shared ids.
|
|||
|
|
assert.ok(named.length < 100, 'named structures should stay bounded, got ' + named.length);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('two-byte record REFERENCES (shared, maxShared=64) round-trip with stable shapes', function () {
|
|||
|
|
// maxSharedStructures > 32 forces two-byte mode; stable repeated shapes get a definition
|
|||
|
|
// once then are emitted as two-byte references (0x60-0x7f + high byte) — the path that
|
|||
|
|
// relies on structure.read remaining a second-byte reader after the fix.
|
|||
|
|
const store = sharedStore();
|
|||
|
|
const o = { useRecords: true, maxSharedStructures: 64, maxOwnStructures: 64, ...store };
|
|||
|
|
const w = new Packr(o), r = new Packr(o);
|
|||
|
|
const shapes = [];
|
|||
|
|
for (let s = 0; s < 100; s++) { const rec = {}; for (let f = 0; f <= s % 8; f++) rec['k' + s + '_' + f] = s * 10 + f; shapes.push(rec); }
|
|||
|
|
let sawRef = false;
|
|||
|
|
for (let round = 0; round < 4; round++) for (const shp of shapes) {
|
|||
|
|
const buf = w.pack(shp);
|
|||
|
|
if (buf[0] >= 0x60 && buf[0] < 0x80) sawRef = true;
|
|||
|
|
assert.deepEqual(r.unpack(buf), shp);
|
|||
|
|
}
|
|||
|
|
assert.ok(sawRef, 'expected at least one two-byte record reference (0x60-0x7f) to be exercised');
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('one-byte path (maxOwn=32) keeps working with a shared store', function () {
|
|||
|
|
const store = sharedStore();
|
|||
|
|
const o = { randomAccessStructure: true, useRecords: true, useBigIntExtension: true, maxOwnStructures: 32, ...store };
|
|||
|
|
const w = new Packr(o), r = new Packr(o);
|
|||
|
|
for (const rec of makeRecords(3000)) {
|
|||
|
|
assert.deepEqual(r.unpack(w.pack(rec)), rec);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
})(chai, null, module, fs);
|
|||
|
|
//# sourceMappingURL=test.js.map
|