From a03cf1b2a10786759ba20b70385b5378556354d3 Mon Sep 17 00:00:00 2001 From: szdytom Date: Sat, 10 Feb 2024 14:16:27 +0800 Subject: [PATCH] [js] add UUID and type-handlers Signed-off-by: szdytom --- shared/bstruct/index.mjs | 1 + shared/bstruct/package.json | 9 + shared/bstruct/type-handler.mjs | 455 ++++++++++++++++++++++++++++++++ shared/error-utils/index.mjs | 5 + shared/error-utils/package.json | 6 + shared/uuid/index.mjs | 50 ++++ shared/uuid/package.json | 9 + shared/uuid/test/uuid.test.js | 51 ++++ 8 files changed, 586 insertions(+) create mode 100644 shared/bstruct/index.mjs create mode 100644 shared/bstruct/package.json create mode 100644 shared/bstruct/type-handler.mjs create mode 100644 shared/error-utils/index.mjs create mode 100644 shared/error-utils/package.json create mode 100644 shared/uuid/index.mjs create mode 100644 shared/uuid/package.json create mode 100644 shared/uuid/test/uuid.test.js diff --git a/shared/bstruct/index.mjs b/shared/bstruct/index.mjs new file mode 100644 index 0000000..4dc4363 --- /dev/null +++ b/shared/bstruct/index.mjs @@ -0,0 +1 @@ +import { VirtualMethodNotImplementedError } from '@og/error-utils'; diff --git a/shared/bstruct/package.json b/shared/bstruct/package.json new file mode 100644 index 0000000..8b8fa8c --- /dev/null +++ b/shared/bstruct/package.json @@ -0,0 +1,9 @@ +{ + "name": "@og/binary-struct", + "type": "module", + "main": "index.mjs", + "dependencies": { + "@og/error-utils": "file:../error-utils", + "@og/uuid": "file:../uuid" + } +} \ No newline at end of file diff --git a/shared/bstruct/type-handler.mjs b/shared/bstruct/type-handler.mjs new file mode 100644 index 0000000..af0c058 --- /dev/null +++ b/shared/bstruct/type-handler.mjs @@ -0,0 +1,455 @@ +import { VirtualMethodNotImplementedError } from '@og/error-utils'; + +export class BasicTypeHandler { + sizeof(value) { + throw new VirtualMethodNotImplementedError(); + } + + serialize(view, offset, value) { + throw new VirtualMethodNotImplementedError(); + } + + deserialize(view, offset) { + throw new VirtualMethodNotImplementedError(); + } +}; + +export class Int8Handler extends BasicTypeHandler { + sizeof(_value) { + return 1; + } + + /** + * + * @param {DataView} view + * @param {number} offset + * @param {number} value + */ + serialize(view, offset, value) { + view.setInt8(offset, value); + } + + /** + * + * @param {DataView} view + * @param {number} offset + * @returns {number} + */ + deserialize(view, offset) { + return view.getInt8(offset); + } +} + +export class Int16Handler extends BasicTypeHandler { + /** + * @param {number} _value + * @returns {number} + */ + sizeof(_value) { + return 2; + } + + /** + * @param {DataView} view + * @param {number} offset + * @param {number} value + */ + serialize(view, offset, value) { + view.setInt16(offset, value); + } + + /** + * @param {DataView} view + * @param {number} offset + * @returns {number} + */ + deserialize(view, offset) { + return view.getInt16(offset); + } +} + +export class Int32Handler extends BasicTypeHandler { + /** + * @param {number} _value + * @returns {number} + */ + sizeof(_value) { + return 4; + } + + /** + * @param {DataView} view + * @param {number} offset + * @param {number} value + */ + serialize(view, offset, value) { + view.setInt32(offset, value); + } + + /** + * @param {DataView} view + * @param {number} offset + * @returns {number} + */ + deserialize(view, offset) { + return view.getInt32(offset); + } +} + +export class Int64Handler extends BasicTypeHandler { + /** + * @param {number} _value + * @returns {number} + */ + sizeof(_value) { + return 8; + } + + /** + * @param {DataView} view + * @param {number} offset + * @param {BigInt} value + */ + serialize(view, offset, value) { + view.setBigInt64(offset, value); + } + + /** + * @param {DataView} view + * @param {number} offset + * @returns {BigInt} + */ + deserialize(view, offset) { + return view.getBigInt64(offset); + } +} + +export class Uint8Handler extends BasicTypeHandler { + /** + * @param {number} _value + * @returns {number} + */ + sizeof(_value) { + return 1; + } + + /** + * @param {DataView} view + * @param {number} offset + * @param {number} value + */ + serialize(view, offset, value) { + view.setUint8(offset, value); + } + + /** + * @param {DataView} view + * @param {number} offset + * @returns {number} + */ + deserialize(view, offset) { + return view.getUint8(offset); + } +} + +export class Uint16Handler extends BasicTypeHandler { + /** + * @param {number} _value + * @returns {number} + */ + sizeof(_value) { + return 2; + } + + /** + * @param {DataView} view + * @param {number} offset + * @param {number} value + */ + serialize(view, offset, value) { + view.setUint16(offset, value); + } + + /** + * @param {DataView} view + * @param {number} offset + * @returns {number} + */ + deserialize(view, offset) { + return view.getUint16(offset); + } +} + +export class Uint32Handler extends BasicTypeHandler { + /** + * @param {number} _value + * @returns {number} + */ + sizeof(_value) { + return 4; + } + + /** + * @param {DataView} view + * @param {number} offset + * @param {number} value + */ + serialize(view, offset, value) { + view.setUint32(offset, value); + } + + /** + * @param {DataView} view + * @param {number} offset + * @returns {number} + */ + deserialize(view, offset) { + return view.getUint32(offset); + } +} + +export class Uint64Handler extends BasicTypeHandler { + /** + * @param {number} _value + * @returns {number} + */ + sizeof(_value) { + return 8; + } + + /** + * @param {DataView} view + * @param {number} offset + * @param {BigInt} value + */ + serialize(view, offset, value) { + view.setBigUint64(offset, value); + } + + /** + * @param {DataView} view + * @param {number} offset + * @returns {BigInt} + */ + deserialize(view, offset) { + return view.getBigUint64(offset); + } +} + +export class Float32Handler extends BasicTypeHandler { + /** + * @param {number} _value + * @returns {number} + */ + sizeof(_value) { + return 4; + } + + /** + * @param {DataView} view + * @param {number} offset + * @param {number} value + */ + serialize(view, offset, value) { + view.setFloat32(offset, value); + } + + /** + * @param {DataView} view + * @param {number} offset + * @returns {number} + */ + deserialize(view, offset) { + return view.getFloat32(offset); + } +} + +export class Float64Handler extends BasicTypeHandler { + /** + * @param {number} _value + * @returns {number} + */ + sizeof(_value) { + return 8; + } + + /** + * @param {DataView} view + * @param {number} offset + * @param {number} value + */ + serialize(view, offset, value) { + view.setFloat64(offset, value); + } + + /** + * @param {DataView} view + * @param {number} offset + * @returns {number} + */ + deserialize(view, offset) { + return view.getFloat64(offset); + } +} + +export class BoolHandler extends BasicTypeHandler { + /** + * @param {boolean} _value + * @returns {number} + */ + sizeof(_value) { + return 1; + } + + /** + * @param {DataView} view + * @param {number} offset + * @param {boolean} value + */ + serialize(view, offset, value) { + view.setUint8(offset, value ? 1 : 0); + } + + /** + * @param {DataView} view + * @param {number} offset + * @returns {boolean} + */ + deserialize(view, offset) { + return view.getUint8(offset) !== 0; + } +} + +export class VoidHandler extends BasicTypeHandler { + /** + * @returns {number} + */ + sizeof() { + return 0; + } + + /** + * @param {DataView} _view + * @param {number} _offset + * @param {undefined} _value + */ + serialize(_view, _offset, _value) { + // Do nothing for Void + } + + /** + * @param {DataView} _view + * @param {number} _offset + * @returns {undefined} + */ + deserialize(_view, _offset) { + // Do nothing for Void + } +} + +export class UUIDHandler extends BasicTypeHandler { + /** + * @param {UUID} _value + * @returns {number} + */ + sizeof(_value) { + return 16; + } + + /** + * @param {DataView} view + * @param {number} offset + * @param {UUID} value + */ + serialize(view, offset, value) { + const buffer = value.buffer; + for (let i = 0; i < buffer.length; i++) { + view.setUint8(offset + i, buffer[i]); + } + } + + /** + * @param {DataView} view + * @param {number} offset + * @returns {UUID} + */ + deserialize(view, offset) { + const buffer = new Uint8Array(16); + for (let i = 0; i < 16; i++) { + buffer[i] = view.getUint8(offset + i); + } + return new UUID(buffer); + } +} + +export class StringHandler extends BasicTypeHandler { + /** + * @param {string} _value + * @returns {number} + */ + sizeof(_value) { + // Calculate the size of string in bytes including the 4 bytes for length + return 4 + this.utf8ByteLength(_value); + } + + /** + * @param {DataView} view + * @param {number} offset + * @param {string} value + */ + serialize(view, offset, value) { + const utf8_bytes = this.encodeUTF8(value); + // Write string length as 4-byte unsigned integer + view.setUint32(offset, utf8_bytes.length, true); + // Write UTF-8 bytes + for (let i = 0; i < utf8_bytes.length; i++) { + view.setUint8(offset + 4 + i, utf8_bytes[i]); + } + } + + /** + * @param {DataView} view + * @param {number} offset + * @returns {string} + */ + deserialize(view, offset) { + // Read string length as 4-byte unsigned integer + const length = view.getUint32(offset, true); + // Read UTF-8 bytes + const utf8_bytes = new Uint8Array(length); + for (let i = 0; i < length; i++) { + utf8_bytes[i] = view.getUint8(offset + 4 + i); + } + // Decode UTF-8 bytes to string + return this.decodeUTF8(utf8_bytes); + } + + /** + * Encodes a string to UTF-8 bytes. + * @param {string} str + * @returns {Uint8Array} + */ + encodeUTF8(str) { + const encoder = new TextEncoder(); + return encoder.encode(str); + } + + /** + * Decodes UTF-8 bytes to a string. + * @param {Uint8Array} bytes + * @returns {string} + */ + decodeUTF8(bytes) { + const decoder = new TextDecoder(); + return decoder.decode(bytes); + } + + /** + * Calculates the byte length of a string encoded in UTF-8. + * @param {string} str + * @returns {number} + */ + utf8ByteLength(str) { + return new TextEncoder().encode(str).length; + } +} diff --git a/shared/error-utils/index.mjs b/shared/error-utils/index.mjs new file mode 100644 index 0000000..8a6f22f --- /dev/null +++ b/shared/error-utils/index.mjs @@ -0,0 +1,5 @@ +export class VirtualMethodNotImplementedError extends Error { + constructor() { + super('virtual method not implemented.'); + } +}; diff --git a/shared/error-utils/package.json b/shared/error-utils/package.json new file mode 100644 index 0000000..9559452 --- /dev/null +++ b/shared/error-utils/package.json @@ -0,0 +1,6 @@ +{ + "name": "@og/error-utils", + "type": "module", + "main": "index.mjs", + "dependencies": {} +} \ No newline at end of file diff --git a/shared/uuid/index.mjs b/shared/uuid/index.mjs new file mode 100644 index 0000000..4294317 --- /dev/null +++ b/shared/uuid/index.mjs @@ -0,0 +1,50 @@ +import { v4 as uuidv4, v5 as uuidv5, stringify, parse } from 'uuid'; +import { Buffer } from 'buffer'; + +/** + * Represents a UUID (Universally Unique Identifier) object. + */ +export class UUID { + /** + * Constructs a UUID object. + * @param {string|ArrayBuffer} input - The input to construct the UUID from. It can be either a hexadecimal string or an ArrayBuffer. + * @throws {Error} Will throw an error if the input type is invalid. Input must be either a string or an ArrayBuffer. + */ + constructor(input) { + if (typeof input === 'string') { + this.buffer = parse(input); + } else if (input instanceof ArrayBuffer) { + this.buffer = Buffer.from(input); + } else if (input instanceof UUID) { + this.buffer = Buffer.from(input.buffer); + } else { + throw new Error('Invalid input type. Input must be either a string or an ArrayBuffer.'); + } + } + + /** + * Generates a random UUID using version 4 (random). + * @returns {UUID} A UUID object representing a randomly generated UUID. + */ + static v4() { + return new UUID(uuidv4()); + } + + /** + * Generates a UUID using version 5 (namespace-based) with the specified namespace and name. + * @param {string} name - The name to be used for generating the UUID. + * @param {string|Buffer|UUID} namespace - The namespace UUID in string or Buffer format. + * @returns {UUID} A UUID object representing a version 5 UUID. + */ + static v5(name, namespace) { + return new UUID(uuidv5(name, new UUID(namespace).toString())); + } + + /** + * Converts the UUID object to a string representation. + * @returns {string} A hexadecimal string representation of the UUID. + */ + toString() { + return stringify(this.buffer); + } +}; \ No newline at end of file diff --git a/shared/uuid/package.json b/shared/uuid/package.json new file mode 100644 index 0000000..ff1d88d --- /dev/null +++ b/shared/uuid/package.json @@ -0,0 +1,9 @@ +{ + "name": "@og/uuid", + "type": "module", + "main": "index.mjs", + "dependencies": { + "buffer": "^6.0.3", + "uuid": "^9.0.1" + } +} diff --git a/shared/uuid/test/uuid.test.js b/shared/uuid/test/uuid.test.js new file mode 100644 index 0000000..a931f02 --- /dev/null +++ b/shared/uuid/test/uuid.test.js @@ -0,0 +1,51 @@ +import { UUID } from '../index.mjs'; +import assert from 'node:assert'; + +describe('UUID', () => { + describe('#constructor()', () => { + it('should create UUID from string', () => { + const uuid_str = '123e4567-e89b-12d3-a456-426614171440'; + const uuid = new UUID(uuid_str); + assert.strictEqual(uuid.toString(), uuid_str); + }); + + it('should create UUID from ArrayBuffer', () => { + const buffer = new Uint8Array([ + 18, 62, 69, 103, 232, 155, 18, 211, + 164, 86, 66, 102, 20, 23, 20, 64 + ]).buffer; + const uuid = new UUID(buffer); + assert.strictEqual(uuid.toString(), '123e4567-e89b-12d3-a456-426614171440'); + }); + + it('should throw error for invalid input type', () => { + assert.throws(() => { + new UUID({}); // passing invalid input + }, /^Error: Invalid input type/); + }); + }); + + describe('#v4()', () => { + it('should generate a version 4 UUID', () => { + const uuid = UUID.v4(); + assert.strictEqual(uuid.toString().length, 36); + }); + }); + + describe('#v5()', () => { + it('should generate a version 5 UUID', () => { + const namespace = '123e4567-e89b-12d3-a456-426614171440'; // Sample namespace + const name = 'test'; + const uuid = UUID.v5(name, namespace); + assert.strictEqual(uuid.toString().length, 36); + }); + }); + + describe('#toString()', () => { + it('should convert UUID to string', () => { + const uuid_str = '123e4567-e89b-12d3-a456-426614174000'; + const uuid = new UUID(uuid_str); + assert.strictEqual(uuid.toString(), uuid_str); + }); + }); +}); \ No newline at end of file