365 lines
9.4 KiB
JavaScript
365 lines
9.4 KiB
JavaScript
import * as I from 'immutable';
|
|
import msgpack from 'msgpack-lite';
|
|
|
|
const PACKER_STEP_OP_ADD = 0;
|
|
const PACKER_STEP_OP_REPLACE = 1;
|
|
const PACKER_STEP_OP_REMOVE = 2;
|
|
|
|
const PACKER_STEP_VALUE_BOOLEAN = 0;
|
|
const PACKER_STEP_VALUE_NULL = 1;
|
|
const PACKER_STEP_VALUE_UNDEFINED = 2;
|
|
const PACKER_STEP_VALUE_BYTE = 3;
|
|
const PACKER_STEP_VALUE_UBYTE = 4;
|
|
const PACKER_STEP_VALUE_SHORT = 5;
|
|
const PACKER_STEP_VALUE_USHORT = 6;
|
|
const PACKER_STEP_VALUE_INT = 7;
|
|
const PACKER_STEP_VALUE_UINT = 8;
|
|
const PACKER_STEP_VALUE_FLOAT = 9;
|
|
const PACKER_STEP_VALUE_STRING = 10;
|
|
const PACKER_STEP_VALUE_JSON = 11;
|
|
|
|
export class Packer {
|
|
|
|
constructor() {
|
|
this._keyCaret = 0;
|
|
this._keys = {};
|
|
}
|
|
|
|
computeMessageLength(preparedSteps) {
|
|
// num of steps + packed.
|
|
return 4 + preparedSteps.reduce((length, step) => {
|
|
// op + path + value type + value.
|
|
let valueLength;
|
|
if (PACKER_STEP_OP_REMOVE === step.get('op')) {
|
|
valueLength = 0;
|
|
}
|
|
else {
|
|
valueLength = this.constructor.packedValueLength(
|
|
step.get('value')
|
|
);
|
|
}
|
|
return length + 1 + 4 + 1 + valueLength;
|
|
}, 0);
|
|
}
|
|
|
|
computeNewKeys(steps) {
|
|
const newKeys = [[], []];
|
|
steps.forEach((step) => {
|
|
const path = step.get('path');
|
|
if (!this._keys[path]) {
|
|
this._keys[path] = this._keyCaret++;
|
|
newKeys[0].push(path);
|
|
newKeys[1].push(this._keys[path]);
|
|
}
|
|
});
|
|
return newKeys;
|
|
}
|
|
|
|
pack(steps) {
|
|
return this.packSteps(steps);
|
|
}
|
|
|
|
// Binary packed steps.
|
|
packSteps(steps) {
|
|
const preparedSteps = this.prepareSteps(steps);
|
|
const messageLength = this.computeMessageLength(preparedSteps);
|
|
const packedSteps = Buffer.allocUnsafe(messageLength);
|
|
let caret = 0;
|
|
// Number of steps.
|
|
packedSteps.writeUInt32LE(preparedSteps.size, caret)
|
|
caret += 4;
|
|
preparedSteps.forEach((step) => {
|
|
// Op.
|
|
const op = step.get('op');
|
|
packedSteps.writeUInt8(op, caret)
|
|
caret += 1;
|
|
// Path.
|
|
const path = step.get('path');
|
|
packedSteps.writeUInt32LE(path, caret)
|
|
caret += 4;
|
|
if (PACKER_STEP_OP_REMOVE === op) {
|
|
return;
|
|
}
|
|
// Value.
|
|
const value = step.get('value');
|
|
packedSteps.writeUInt8(value.type, caret)
|
|
caret += 1;
|
|
switch (value.type) {
|
|
case PACKER_STEP_VALUE_BOOLEAN:
|
|
packedSteps.writeInt8(value.data ? 1 : 0, caret)
|
|
caret += 1;
|
|
break;
|
|
case PACKER_STEP_VALUE_BYTE:
|
|
packedSteps.writeInt8(value.data, caret)
|
|
caret += 1;
|
|
break;
|
|
case PACKER_STEP_VALUE_UBYTE:
|
|
packedSteps.writeUInt8(value.data, caret)
|
|
caret += 1;
|
|
break;
|
|
case PACKER_STEP_VALUE_SHORT:
|
|
packedSteps.writeInt16LE(value.data, caret)
|
|
caret += 2;
|
|
break;
|
|
case PACKER_STEP_VALUE_USHORT:
|
|
packedSteps.writeUInt16LE(value.data, caret)
|
|
caret += 2;
|
|
break;
|
|
case PACKER_STEP_VALUE_INT:
|
|
packedSteps.writeInt32LE(value.data, caret)
|
|
caret += 4;
|
|
break;
|
|
case PACKER_STEP_VALUE_UINT:
|
|
packedSteps.writeUInt32LE(value.data, caret)
|
|
caret += 4;
|
|
break;
|
|
case PACKER_STEP_VALUE_FLOAT:
|
|
packedSteps.writeFloatLE(value.data, caret);
|
|
caret += 4;
|
|
break;
|
|
case PACKER_STEP_VALUE_STRING:
|
|
case PACKER_STEP_VALUE_JSON:
|
|
const bufferLength = value.data.length;
|
|
packedSteps.writeUInt32LE(bufferLength, caret)
|
|
caret += 4;
|
|
for (let i = 0; i < bufferLength; ++i) {
|
|
packedSteps.writeUInt8(value.data.readUInt8(i), caret)
|
|
caret += 1;
|
|
}
|
|
break;
|
|
case PACKER_STEP_VALUE_NULL:
|
|
case PACKER_STEP_VALUE_UNDEFINED:
|
|
break;
|
|
}
|
|
});
|
|
return packedSteps;
|
|
}
|
|
|
|
static prepareOpType(op) {
|
|
switch (op) {
|
|
case 'add':
|
|
return PACKER_STEP_OP_ADD;
|
|
case 'replace':
|
|
return PACKER_STEP_OP_REPLACE;
|
|
case 'remove':
|
|
return PACKER_STEP_OP_REMOVE;
|
|
}
|
|
}
|
|
|
|
prepareSteps(steps) {
|
|
return steps.map((step) => {
|
|
const op = step.get('op');
|
|
const rawStep = {
|
|
op: this.constructor.prepareOpType(op),
|
|
path: this._keys[step.get('path')],
|
|
};
|
|
if ('remove' !== op) {
|
|
rawStep.value = this.constructor.prepareValue(step.get('value'));
|
|
}
|
|
return I.Map(rawStep);
|
|
});
|
|
}
|
|
|
|
static prepareValue(value) {
|
|
if (undefined === value) {
|
|
return {
|
|
type: PACKER_STEP_VALUE_UNDEFINED,
|
|
};
|
|
}
|
|
if (null === value) {
|
|
return {
|
|
type: PACKER_STEP_VALUE_NULL,
|
|
};
|
|
}
|
|
if ('boolean' === typeof value) {
|
|
return {
|
|
type: PACKER_STEP_VALUE_BOOLEAN,
|
|
data: value,
|
|
};
|
|
}
|
|
if (Number.isInteger(value)) {
|
|
if (value < 0) {
|
|
if (value >= -128 && value < 128) {
|
|
return {
|
|
type: PACKER_STEP_VALUE_BYTE,
|
|
data: value,
|
|
};
|
|
}
|
|
if (value >= -32768 && value < 32768) {
|
|
return {
|
|
type: PACKER_STEP_VALUE_SHORT,
|
|
data: value,
|
|
};
|
|
}
|
|
return {
|
|
type: PACKER_STEP_VALUE_INT,
|
|
data: value,
|
|
};
|
|
}
|
|
if (value < 256) {
|
|
return {
|
|
type: PACKER_STEP_VALUE_UBYTE,
|
|
data: value,
|
|
};
|
|
}
|
|
if (value < 65536) {
|
|
return {
|
|
type: PACKER_STEP_VALUE_USHORT,
|
|
data: value,
|
|
};
|
|
}
|
|
return {
|
|
type: PACKER_STEP_VALUE_UINT,
|
|
data: value,
|
|
};
|
|
}
|
|
if (Number.isFinite(value)) {
|
|
return {
|
|
type: PACKER_STEP_VALUE_FLOAT,
|
|
data: value,
|
|
};
|
|
}
|
|
if ('string' === typeof value) {
|
|
return {
|
|
type: PACKER_STEP_VALUE_STRING,
|
|
data: Buffer.from(value),
|
|
};
|
|
}
|
|
return {
|
|
type: PACKER_STEP_VALUE_JSON,
|
|
data: msgpack.encode(value.toJS()),
|
|
};
|
|
}
|
|
|
|
static packedValueLength({type, data}) {
|
|
switch (type) {
|
|
case PACKER_STEP_VALUE_NULL:
|
|
case PACKER_STEP_VALUE_UNDEFINED:
|
|
return 0;
|
|
case PACKER_STEP_VALUE_BOOLEAN:
|
|
case PACKER_STEP_VALUE_BYTE:
|
|
case PACKER_STEP_VALUE_UBYTE:
|
|
return 1;
|
|
case PACKER_STEP_VALUE_SHORT:
|
|
case PACKER_STEP_VALUE_USHORT:
|
|
return 2;
|
|
case PACKER_STEP_VALUE_INT:
|
|
case PACKER_STEP_VALUE_UINT:
|
|
return 4;
|
|
case PACKER_STEP_VALUE_FLOAT:
|
|
return 4;
|
|
case PACKER_STEP_VALUE_STRING:
|
|
case PACKER_STEP_VALUE_JSON:
|
|
return 4 + data.length;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
export class Unpacker {
|
|
|
|
constructor() {
|
|
this._keys = {};
|
|
}
|
|
|
|
registerKeys(keys) {
|
|
for (let i = 0; i < keys[0].length; ++i) {
|
|
this._keys[keys[1][i]] = keys[0][i];
|
|
}
|
|
}
|
|
|
|
unpack(packedSteps) {
|
|
packedSteps = Buffer.from(packedSteps.buffer);
|
|
let caret = 0;
|
|
const numberOfSteps = packedSteps.readUInt32LE(caret);
|
|
caret += 4;
|
|
const steps = new Array(numberOfSteps);
|
|
for (let i = 0; i < numberOfSteps; ++i) {
|
|
const packedOp = packedSteps.readUInt8(caret);
|
|
caret += 1;
|
|
const packedPath = packedSteps.readUInt32LE(caret);
|
|
caret += 4;
|
|
steps[i] = {
|
|
op: this.constructor.unpackOp(packedOp),
|
|
path: this._keys[packedPath],
|
|
};
|
|
if (PACKER_STEP_OP_REMOVE === packedOp) {
|
|
continue;
|
|
}
|
|
const valueType = packedSteps.readUInt8(caret);
|
|
caret += 1;
|
|
let value;
|
|
switch (valueType) {
|
|
case PACKER_STEP_VALUE_BOOLEAN:
|
|
value = !!packedSteps.readInt8(caret);
|
|
caret += 1;
|
|
break;
|
|
case PACKER_STEP_VALUE_BYTE:
|
|
value = packedSteps.readInt8(caret);
|
|
caret += 1;
|
|
break;
|
|
case PACKER_STEP_VALUE_UBYTE:
|
|
value = packedSteps.readUInt8(caret);
|
|
caret += 1;
|
|
break;
|
|
case PACKER_STEP_VALUE_SHORT:
|
|
value = packedSteps.readInt16LE(caret);
|
|
caret += 2;
|
|
break;
|
|
case PACKER_STEP_VALUE_USHORT:
|
|
value = packedSteps.readUInt16LE(caret);
|
|
caret += 2;
|
|
break;
|
|
case PACKER_STEP_VALUE_INT:
|
|
value = packedSteps.readInt32LE(caret);
|
|
caret += 4;
|
|
break;
|
|
case PACKER_STEP_VALUE_UINT:
|
|
value = packedSteps.readUInt32LE(caret);
|
|
caret += 4;
|
|
break;
|
|
case PACKER_STEP_VALUE_FLOAT:
|
|
value = packedSteps.readFloatLE(caret);
|
|
caret += 4;
|
|
break;
|
|
case PACKER_STEP_VALUE_STRING:
|
|
case PACKER_STEP_VALUE_JSON:
|
|
const bufferLength = packedSteps.readUInt32LE(caret);
|
|
caret += 4;
|
|
value = Buffer.allocUnsafe(bufferLength);
|
|
for (let j = 0; j < bufferLength; ++j) {
|
|
value.writeUInt8(packedSteps.readUInt8(caret), j);
|
|
caret += 1;
|
|
}
|
|
break;
|
|
case PACKER_STEP_VALUE_NULL:
|
|
value = null;
|
|
break;
|
|
case PACKER_STEP_VALUE_UNDEFINED:
|
|
value = undefined;
|
|
break;
|
|
}
|
|
if (PACKER_STEP_VALUE_STRING === valueType) {
|
|
value = value.toString();
|
|
}
|
|
if (PACKER_STEP_VALUE_JSON === valueType) {
|
|
value = msgpack.decode(value);
|
|
}
|
|
steps[i].value = value;
|
|
}
|
|
return steps;
|
|
}
|
|
|
|
static unpackOp(packedOp) {
|
|
switch (packedOp) {
|
|
case PACKER_STEP_OP_ADD:
|
|
return 'add';
|
|
case PACKER_STEP_OP_REPLACE:
|
|
return 'replace';
|
|
case PACKER_STEP_OP_REMOVE:
|
|
return 'remove';
|
|
}
|
|
}
|
|
|
|
}
|