diff --git a/shared/bstruct/index.mjs b/shared/bstruct/index.mjs index 841c26e..20b5c2a 100644 --- a/shared/bstruct/index.mjs +++ b/shared/bstruct/index.mjs @@ -1,6 +1,6 @@ import { BaseTypeHandler, CompoundTypeHandler } from './type-handler.mjs'; -export { BASIC_TYPES, FixedArrayHandler, DynamicArrayHandler, CompoundTypeHandler } from './type-handler.mjs'; +export { BASIC_TYPES } from './type-handler.mjs'; /** * Serializes JavaScript value to binary. diff --git a/shared/bstruct/package.json b/shared/bstruct/package.json index 3d3c2d2..9600520 100644 --- a/shared/bstruct/package.json +++ b/shared/bstruct/package.json @@ -4,7 +4,8 @@ "main": "index.mjs", "dependencies": { "@og/error-utils": "file:../error-utils", + "@og/utility": "file:../utility", "@og/uuid": "file:../uuid", - "@og/utility": "file:../utility" + "buffer": "^6.0.3" } -} \ No newline at end of file +} diff --git a/shared/bstruct/test/index.test.js b/shared/bstruct/test/index.test.js index b68b225..dae87b2 100644 --- a/shared/bstruct/test/index.test.js +++ b/shared/bstruct/test/index.test.js @@ -1,4 +1,4 @@ -import { BASIC_TYPES, FixedArrayHandler, DynamicArrayHandler, CompoundTypeHandler, serializeToBinary, deserializeFromBinary } from '../index.mjs'; +import { BASIC_TYPES, serializeToBinary, deserializeFromBinary } from '../index.mjs'; import assert from 'node:assert/strict'; import { areArrayBuffersEqual } from '@og/utility'; import { UUID } from '@og/uuid'; @@ -82,13 +82,13 @@ describe('binary-struct', () => { }); it('type u16[]', () => { - let res = serializeToBinary([0x1770, 0x3c3a, 0x9012], new DynamicArrayHandler(BASIC_TYPES.u16)); + let res = serializeToBinary([0x1770, 0x3c3a, 0x9012], BASIC_TYPES.array(BASIC_TYPES.u16)); let ans = new Uint8Array([0x03, 0x00, 0x00, 0x00, 0x70, 0x17, 0x3a, 0x3c, 0x12, 0x90]); assert.ok(areArrayBuffersEqual(res, ans)); }); it('type u16[3]', () => { - let res = serializeToBinary([0x1770, 0x3c3a, 0x9012], new FixedArrayHandler(3, BASIC_TYPES.u16)); + let res = serializeToBinary([0x1770, 0x3c3a, 0x9012], BASIC_TYPES.FixedArray(3, BASIC_TYPES.u16)); let ans = new Uint8Array([0x70, 0x17, 0x3a, 0x3c, 0x12, 0x90]); assert.ok(areArrayBuffersEqual(res, ans)); }); @@ -104,6 +104,18 @@ describe('binary-struct', () => { let ans = new Uint8Array([0x80, 0x30, 0x18, 0x46, 0, 0, 0, 0]); assert.ok(areArrayBuffersEqual(res, ans)); }); + + it('type Buffer', () => { + let ans = new Uint8Array([0x3c, 0x3d, 0x3e, 0x3f]).buffer; + let res = serializeToBinary(Buffer.from(ans), BASIC_TYPES.raw(4)); + assert.ok(areArrayBuffersEqual(res, ans)); + }); + + it('type StringMap', () => { + let res = serializeToBinary(new Map([['aa', 'b'], ['hi', 'hello']]), BASIC_TYPES.StringMap); + let ans = new Uint8Array([0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x61, 0x61, 0x01, 0x00, 0x00, 0x00, 0x62, 0x02, 0x00, 0x00, 0x00, 0x68, 0x69, 0x05, 0x00, 0x00, 0x00, 0x68, 0x65, 0x6c, 0x6c, 0x6f]).buffer; + assert.ok(areArrayBuffersEqual(res, ans)); + }); }); describe('deserializeFromBinary', () => { @@ -179,14 +191,14 @@ describe('binary-struct', () => { it('type u16[]', () => { let binary_data = new Uint8Array([0x03, 0x00, 0x00, 0x00, 0x70, 0x17, 0x3a, 0x3c, 0x12, 0x90]).buffer; - let res = deserializeFromBinary(new DataView(binary_data), new DynamicArrayHandler(BASIC_TYPES.u16)); + let res = deserializeFromBinary(new DataView(binary_data), BASIC_TYPES.array(BASIC_TYPES.u16)); let ans = [0x1770, 0x3c3a, 0x9012]; assert.deepEqual(res, ans); }); it('type u16[3]', () => { let binary_data = new Uint8Array([0x70, 0x17, 0x3a, 0x3c, 0x12, 0x90]).buffer; - let res = deserializeFromBinary(new DataView(binary_data), new FixedArrayHandler(3, BASIC_TYPES.u16)); + let res = deserializeFromBinary(new DataView(binary_data), BASIC_TYPES.FixedArray(3, BASIC_TYPES.u16)); let ans = [0x1770, 0x3c3a, 0x9012]; assert.deepEqual(res, ans); }); @@ -206,5 +218,18 @@ describe('binary-struct', () => { let ans = new Date("2007-04-08"); assert.equal(ans.toUTCString(), res.toUTCString()); }); + + it('type Buffer', () => { + let binary_data = new Uint8Array([0x3c, 0x3d, 0x3e, 0x3f]).buffer; + let res = deserializeFromBinary(new DataView(binary_data), BASIC_TYPES.raw(4)); + assert.deepEqual(Buffer.from(binary_data), res); + }); + + it('type StringMap', () => { + let binary_data = new Uint8Array([0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x61, 0x61, 0x01, 0x00, 0x00, 0x00, 0x62, 0x02, 0x00, 0x00, 0x00, 0x68, 0x69, 0x05, 0x00, 0x00, 0x00, 0x68, 0x65, 0x6c, 0x6c, 0x6f]).buffer; + let res = deserializeFromBinary(new DataView(binary_data), BASIC_TYPES.StringMap); + let ans = new Map([['aa', 'b'], ['hi', 'hello']]); + assert.deepEqual(res, ans); + }); }); }); diff --git a/shared/bstruct/type-handler.mjs b/shared/bstruct/type-handler.mjs index a931983..cf0c2e4 100644 --- a/shared/bstruct/type-handler.mjs +++ b/shared/bstruct/type-handler.mjs @@ -1,5 +1,6 @@ import { VirtualMethodNotImplementedError } from '@og/error-utils'; import { UUID } from '@og/uuid'; +import { Buffer } from 'buffer'; /** * Represents the result of deserialization. @@ -551,6 +552,13 @@ export class VoidHandler extends BaseTypeHandler { } } +function getHandlerObject(type) { + if (type instanceof BaseTypeHandler) { + return type; + } + return new CompoundTypeHandler(type); +} + /** * Handles array of a fixed length with elements of the same type. * @extends {BaseTypeHandler} @@ -565,7 +573,7 @@ export class FixedArrayHandler extends BaseTypeHandler { constructor(n, element_handler) { super(); this.n = n; - this.element_handler = element_handler; + this.element_handler = getHandlerObject(element_handler); } /** @@ -612,6 +620,56 @@ export class FixedArrayHandler extends BaseTypeHandler { } } +/** + * Handles raw binary buffer of a fixed length. + * @extends {BaseTypeHandler} + * @class + */ +export class RawBufferHandler extends BaseTypeHandler { + /** + * Constructor for RawBufferHandler. + * @param {number} n - The fixed length of the buffer. + */ + constructor(n) { + super(); + this.n = n; + } + + /** + * Gets the size of the serialized fixed-length buffer in bytes. + * @param {Buffer} value - The array to calculate the size for. + * @returns {number} - The size of the serialized fixed-length array in bytes. + */ + sizeof(value) { + return value.byteLength; + } + + /** + * Serializes the fixed-length buffer and writes it to the DataView at the specified offset. + * @param {DataView} view - The DataView to write to. + * @param {number} offset - The offset to start writing at. + * @param {Buffer} value - The fixed-length buffer to be serialized. + * @returns {number} - The new offset after serialization. + */ + serialize(view, offset, value) { + for (let i = 0; i < this.n; i += 1) { + view.setUint8(offset + i, value.readUInt8(offset + i)); + } + return offset + this.n; + } + + /** + * Deserializes the fixed-length array from the DataView at the specified offset. + * @param {DataView} view - The DataView to read from. + * @param {number} offset - The offset to start reading from. + * @returns {DeserializedResult} - The deserialized result containing the fixed-length array and a new offset. + */ + deserialize(view, offset) { + const res = Buffer.from(view.buffer, offset, this.n); + return new DeserializedResult(res, offset + this.n); + } +} + /** * Handles dynamic arrays with elements of the same type. * @extends {BaseTypeHandler} @@ -624,7 +682,7 @@ export class DynamicArrayHandler extends BaseTypeHandler { */ constructor(element_handler) { super(); - this.element_handler = element_handler; + this.element_handler = getHandlerObject(element_handler); } /** @@ -675,6 +733,74 @@ export class DynamicArrayHandler extends BaseTypeHandler { } } +/** + * Handles map with keys and values are of the same type. + * @extends {BaseTypeHandler} + * @class + */ +export class MapHandler extends BaseTypeHandler { + /** + * Constructor for MapHandler. + * @param {BaseTypeHandler} key_handler - The handler for keys of the map. + * @param {BaseTypeHandler} value_handler - The handler for values of the map. + */ + constructor(key_handler, value_handler) { + super(); + this.key_handler = getHandlerObject(key_handler); + this.value_handler = getHandlerObject(value_handler); + } + + /** + * Gets the size of the serialized map in bytes. + * @param {Map} value - The map to calculate the size for. + * @returns {number} - The size of the serialized map in bytes. + */ + sizeof(value) { + let res = 4; + for (const [k, v] of value) { + res += this.key_handler.sizeof(k); + res += this.value_handler.sizeof(v); + } + return res; + } + + /** + * Serializes the map and writes it to the DataView at the specified offset. + * @param {DataView} view - The DataView to write to. + * @param {number} offset - The offset to start writing at. + * @param {Map} value - The map to be serialized. + * @returns {number} - The new offset after serialization. + */ + serialize(view, offset, value) { + view.setUint32(offset, value.size, true); + offset += 4; + for (const [k, v] of value) { + offset = this.key_handler.serialize(view, offset, k); + offset = this.value_handler.serialize(view, offset, v); + } + return offset; + } + + /** + * Deserializes the map from the DataView at the specified offset. + * @param {DataView} view - The DataView to read from. + * @param {number} offset - The offset to start reading from. + * @returns {DeserializedResult} - The deserialized result containing the map and a new offset. + */ + deserialize(view, offset) { + const size = view.getUint32(offset, true); + offset += 4; + const res = new Map(); + for (let i = 0; i < size; i += 1) { + const resk = this.key_handler.deserialize(view, offset); + const resv = this.value_handler.deserialize(view, resk.offset); + offset = resv.offset; + res.set(resk.value, resv.value); + } + return new DeserializedResult(res, offset); + } +} + /** * Handles storage and serialization of UUID objects using a 16-byte buffer. * @extends {BaseTypeHandler} @@ -848,7 +974,7 @@ export class CompoundTypeHandler extends BaseTypeHandler { let res = 0; for (let i = 0; i < this.typedef.length; i += 1) { const field_name = this.typedef[i].field; - const field_handler = this.typedef[i].type; + const field_handler = getHandlerObject(this.typedef[i].type); res += field_handler.sizeof(value[field_name]); } return res; @@ -864,7 +990,7 @@ export class CompoundTypeHandler extends BaseTypeHandler { serialize(view, offset, value) { for (let i = 0; i < this.typedef.length; i += 1) { const field_name = this.typedef[i].field; - const field_handler = this.typedef[i].type; + const field_handler = getHandlerObject(this.typedef[i].type); offset = field_handler.serialize(view, offset, value[field_name]); } return offset; @@ -880,7 +1006,7 @@ export class CompoundTypeHandler extends BaseTypeHandler { let res = new this.type(); for (let i = 0; i < this.typedef.length; i += 1) { const field_name = this.typedef[i].field; - const field_handler = this.typedef[i].type; + const field_handler = getHandlerObject(this.typedef[i].type); const tmp = field_handler.deserialize(view, offset); res[field_name] = tmp.value; offset = tmp.offset; @@ -908,4 +1034,10 @@ export const BASIC_TYPES = { uuid: new UUIDHandler(), str: new StringHandler(), DateTime: new DateHandler(), + + array: (type) => new DynamicArrayHandler(type), + FixedArray: (n, type) => new FixedArrayHandler(n, type), + raw: (n) => new RawBufferHandler(n), + map: (k, v) => new MapHandler(k, v), + StringMap: new MapHandler(new StringHandler(), new StringHandler()), };