avocado-old/packages/state/packer.js
2019-04-12 17:59:28 -05:00

309 lines
7.8 KiB
JavaScript

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_BYTE = 0;
const PACKER_STEP_VALUE_UBYTE = 1;
const PACKER_STEP_VALUE_SHORT = 2;
const PACKER_STEP_VALUE_USHORT = 3;
const PACKER_STEP_VALUE_INT = 4;
const PACKER_STEP_VALUE_UINT = 5;
const PACKER_STEP_VALUE_FLOAT = 6;
const PACKER_STEP_VALUE_STRING = 7;
const PACKER_STEP_VALUE_JSON = 8;
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.
return length + 1 + 4 + 1 + this.constructor.packedValueLength(
step.value
);
}, 0);
}
computeNewKeys(steps) {
const newKeys = [[], []];
steps.forEach(({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.length, caret)
caret += 4;
preparedSteps.forEach(({op, path, value}) => {
// Op.
packedSteps.writeUInt8(op, caret)
caret += 1;
// Path.
packedSteps.writeUInt32LE(path, caret)
caret += 4;
if (PACKER_STEP_OP_REMOVE === op) {
return;
}
// Value.
packedSteps.writeUInt8(value.type, caret)
caret += 1;
switch (value.type) {
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;
}
});
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(({op, path, value}) => {
return {
op: this.constructor.prepareOpType(op),
path: this._keys[path],
value: this.constructor.prepareValue(value),
};
});
}
static prepareValue(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),
};
}
static packedValueLength({type, data}) {
switch (type) {
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) {
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_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;
}
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';
}
}
}