UNPKG

streamstructure

Version:
465 lines 22.7 kB
"use strict"; function isProperty(key, inObject, ...valueType) { return key in inObject && (!valueType.length || valueType.includes(typeof inObject[key])); } ; function assertIsNotProperty(key, inObject, path, ...valueType) { const exists = key in inObject; const propertyType = typeof inObject[key]; const testTypes = !!valueType.length; if (!exists || testTypes && !valueType.includes(propertyType)) { if (!testTypes) { throw new TypeError(`Expected some value but got ${propertyType}: ${path}.${key}`); } const expectedTypes = valueType.map(v => `'${v}'`).reduce((c, t, i) => i === 0 ? t : i === 1 ? t + " or " : t + ", ", ""); throw new TypeError(`Expected ${expectedTypes} but got ${propertyType}: ${path}.${key}`); } } ; function assertNotObject(data, path) { if (!data || (typeof data !== "object" && typeof data !== "function")) { throw new TypeError(`Expected 'object' but got '${typeof data}': ${path}`); } } class StreamStructure { /** * Create a StreamStructure, must be created using the sequence `key: type` * * @example //Creating a structure for a simple object `{name: string,age: number}` * cosnt SS = new StreamStructure("name: string", "age: byte"); */ constructor(...types) { this.endian = "BE"; this.typesDefinitions = {}; this.typeConditions = {}; this.preProcessing = {}; this.posProcessing = {}; this.structure = types; const err = types.find(str => !StreamStructure.typeObjectReader.test(str)); if (err) throw new Error(`The string "${err}" don't match with pattern "key: type"`); } toBuffer(data) { assertNotObject(data, ""); let outBuffers = []; /** * Pega uma valor com um tipo e envia para o buffer * @param type o tipo da data que foi encaminhada * @param size a lista em formato de string ex: '[2][1]' | '' * @param data o valor para ser passado adiante * @param path caminho a ser utilizado caso ocorra algum erro */ const transformVal = (type, size, data, path) => { // If the type is a array if (size.length) { if (!Array.isArray(data)) { throw new TypeError(`Expected array (${type}${size}) but got "${typeof data}": ${path}`); } const [, invertEndian, indexSize, rest] = StreamStructure.typeArrayBreaker.exec(size); const buff = Buffer.allocUnsafe(+indexSize); if ((this.endian === "BE") === (!invertEndian)) { buff.writeUIntBE(data.length, 0, +indexSize); outBuffers.push(buff); } else { buff.writeUIntLE(data.length, 0, +indexSize); outBuffers.push(buff); } for (let i = 0; i < data.length; i++) { transformVal(type, rest, data[i], `${path}[${i}]`); } return; } //make pre-process if can if (type in this.preProcessing) data = this.preProcessing[type](data); //if is another structure if (type in this.typesDefinitions) { assertNotObject(data, path); for (const ObjType of this.typesDefinitions[type]) { const [, key, ArrType] = StreamStructure.typeObjectReader.exec(ObjType); const [, type, size] = StreamStructure.typeReader.exec(ArrType); transformVal(type, size, data[key], `${path}.${key}`); } return; } //If is a condition if (type in this.typeConditions) { // Detect wrong inputs assertNotObject(data, path); assertIsNotProperty("type", data, path, "string", "number"); assertIsNotProperty("data", data, path, "object", "function"); // detect if input exist if (!(data.type in this.typeConditions[type].data)) throw new TypeError(`Don't have any condition in '${type}' when value is '${data.type}'`); (() => { const [, ntype, size] = StreamStructure.typeReader.exec(this.typeConditions[type].indexType); transformVal(ntype, size, data.type, `${path}.key(${type})`); })(); for (const ObjType of this.typeConditions[type].data[data.type]) { const [, key, ArrType] = StreamStructure.typeObjectReader.exec(ObjType); const [, ntype, size] = StreamStructure.typeReader.exec(ArrType); transformVal(ntype, size, data.data[key], `${path}.${key}`); } return; } // Detect the types and send to the buffers switch (type) { case "boolean": { if (typeof data !== "boolean") throw new TypeError(`Expected 'boolean', got '${typeof data}': ${path}`); let buff = Buffer.allocUnsafe(1); buff.writeInt8(+data); return outBuffers.push(buff); } case "char": { if (typeof data !== "string") throw new TypeError(`Expected 'string', got '${typeof data}': ${path}`); let buff = Buffer.allocUnsafe(1); buff.write(data); return outBuffers.push(buff); } case "string": { if (typeof data !== "string") throw new TypeError(`Expected 'string', got '${typeof data}': ${path}`); let buff = Buffer.allocUnsafe(data.length + 2); buff[`writeInt16${this.endian}`](data.length); buff.write(data, 2); return outBuffers.push(buff); } case "byte": case "ubyte": { const usig = type.startsWith("u"); const sigChar = usig ? "U" : ""; const min = (+usig - 1) * 128; const max = (+usig + 1) * 128; if (typeof data !== "number" && typeof data !== "bigint") throw new TypeError(`Expected 'number' or 'bigint', got '${typeof data}': ${path}`); if (data < min || data >= max) throw new RangeError(`The number '${data}' must be in range ${min} ~ ${max - 1}: ${path}`); let buff = Buffer.allocUnsafe(1); buff[`write${sigChar}Int8`](Number(data)); return outBuffers.push(buff); } case "short": case "ushort": { const usig = type.startsWith("u"); const sigChar = usig ? "U" : ""; const min = (+usig - 1) * 32768; const max = (+usig + 1) * 32768; if (typeof data !== "number" && typeof data !== "bigint") throw new TypeError(`Expected 'number' or 'bigint', got '${typeof data}': ${path}`); if (data < min || data >= max) throw new RangeError(`The number '${data}' must be in range ${min} ~ ${max - 1}: ${path}`); let buff = Buffer.allocUnsafe(2); buff[`write${sigChar}Int16${this.endian}`](Number(data)); return outBuffers.push(buff); } case "int": case "uint": { const usig = type.startsWith("u"); const sigChar = usig ? "U" : ""; const min = (+usig - 1) * 2147483648; const max = (+usig + 1) * 2147483648; if (typeof data !== "number" && typeof data !== "bigint") throw new TypeError(`Expected 'number' or 'bigint', got '${typeof data}': ${path}`); if (data < min || data >= max) throw new RangeError(`The number '${data}' must be in range ${min} ~ ${max - 1}: ${path}`); let buff = Buffer.allocUnsafe(4); buff[`write${sigChar}Int32${this.endian}`](Number(data)); return outBuffers.push(buff); } case "long": case "ulong": { const usig = type.startsWith("u"); const sigChar = usig ? "U" : ""; const min = BigInt(+usig - 1) * 9223372036854775808n; const max = BigInt(+usig + 1) * 9223372036854775808n; if (typeof data !== "number" && typeof data !== "bigint") throw new TypeError(`Expected 'number' or 'bigint', got '${typeof data}': ${path}`); if (data < min || data >= max) throw new RangeError(`The number '${data}' must be in range ${min} ~ ${max - 1n}: ${path}`); let buff = Buffer.allocUnsafe(8); buff[`writeBig${sigChar}Int64${this.endian}`](BigInt(data)); return outBuffers.push(buff); } case "float": { if (typeof data !== "number") throw new TypeError(`Expected 'number', got '${typeof data}': ${path}`); let buff = Buffer.allocUnsafe(4); buff[`writeFloat${this.endian}`](data); return outBuffers.push(buff); } case "double": { if (typeof data !== "number") throw new TypeError(`Expected 'number', got '${typeof data}': ${path}`); let buff = Buffer.allocUnsafe(8); buff[`writeDouble${this.endian}`](data); return outBuffers.push(buff); } } //If don't have registred throw new TypeError(`Unknown type "${type}"`); }; for (const ObjType of this.structure) { const [, key, ArrType] = StreamStructure.typeObjectReader.exec(ObjType); const [, type, size] = StreamStructure.typeReader.exec(ArrType); transformVal(type, size, data[key], `.${key}`); } return Buffer.concat(outBuffers); } fromBuffer(buffer) { if (!Buffer.isBuffer(buffer)) throw new TypeError(`The input must be a buffer`); let bufferIndex = 0; let result = {}; /** * Picks the buffer value at actual index * @param type Type of property to be extracted * @param endian in case of been BigEndian or LowEndian * @param path path to actual variable (debug) * @returns value */ const getValue = (type, endian, path) => { let data = {}; if (type in this.typesDefinitions) { // for (const ObjType of this.typesDefinitions[type]) { const [, key, ArrType] = StreamStructure.typeObjectReader.exec(ObjType); const [, type, size] = StreamStructure.typeReader.exec(ArrType); transformVal(key, type, size, data, `${path}.${key}`); } } else if (type in this.typeConditions) { const index = getValue(this.typeConditions[type].indexType, this.endian, `${path}.key(${type})`); if (typeof index !== "string" && typeof index !== "number") throw new TypeError(`Expected a 'string' or 'number' but got '${typeof index}'`); if (!(index in this.typeConditions[type].data)) { console.log(type, path, index); throw new TypeError(`Don't exist any index '${index}' at type '${type}': ${path}`); } for (const ObjType of this.typeConditions[type].data[index]) { const [, key, ArrType] = StreamStructure.typeObjectReader.exec(ObjType); const [, type, size] = StreamStructure.typeReader.exec(ArrType); transformVal(key, type, size, data, `${path}.${key}`); } data = { type: index, data: data }; } else { if (bufferIndex > buffer.length) throw new RangeError(`The Buffer suddenly end when reading the type '${type}': ${path}`); try { switch (type) { case "boolean": bufferIndex += 1; return !!buffer.readInt8(bufferIndex - 1); case "char": bufferIndex += 1; return buffer.toString("ascii", bufferIndex - 1, bufferIndex); case "string": { bufferIndex += 2; const size = buffer[`readInt16${endian}`](bufferIndex - 2); bufferIndex += size; return buffer.toString("ascii", bufferIndex - size, bufferIndex); } case "byte": bufferIndex += 1; return buffer.readInt8(bufferIndex - 1); case "ubyte": bufferIndex += 1; return buffer.readUInt8(bufferIndex - 1); case "short": bufferIndex += 2; return buffer[`readInt16${endian}`](bufferIndex - 2); case "ushort": bufferIndex += 2; return buffer[`readUInt16${endian}`](bufferIndex - 2); case "int": bufferIndex += 4; return buffer[`readInt32${endian}`](bufferIndex - 4); case "uint": bufferIndex += 4; return buffer[`readUInt32${endian}`](bufferIndex - 4); case "long": bufferIndex += 8; return buffer[`readBigInt64${endian}`](bufferIndex - 8); case "ulong": bufferIndex += 8; return buffer[`readBigUInt64${endian}`](bufferIndex - 8); case "float": bufferIndex += 4; return buffer[`readFloat${endian}`](bufferIndex - 4); case "double": bufferIndex += 8; return buffer[`readDouble${endian}`](bufferIndex - 8); } } catch (err) { throw new RangeError(`The Buffer suddenly end when reading the type ${type}: ${path}`); } } if (type in this.posProcessing) { return this.posProcessing[type](data); } return data; }; /** * Picks 'type' from the buffer and add to the 'data' in property 'key' * @param key field to be inserted in 'data' * @param type type to be extracted from buffer * @param size if is a array, and tell your size. ex: '[2][6]' * @param data object to be added * @param path path to actual variable (debug) */ const transformVal = (key, type, size, data, path) => { if (size) { const [, invertEndian, indexSize, rest] = StreamStructure.typeArrayBreaker.exec(size); const indexEndian = (this.endian === "BE") === (!invertEndian) ? "BE" : "LE"; let arrayLength; try { arrayLength = buffer[`readInt${indexEndian}`](bufferIndex, +indexSize); } catch (err) { throw new RangeError(`The Buffer suddenly end while iterating: ${path}`); } bufferIndex += +indexSize; data[key] = []; for (let i = 0; i < arrayLength; i++) { data[key][i] = getValue(type, this.endian, `${path}[${i}]`); ; } return; } data[key] = getValue(type, this.endian, path); }; for (const ObjType of this.structure) { const [, key, ArrType] = StreamStructure.typeObjectReader.exec(ObjType); const [, type, size] = StreamStructure.typeReader.exec(ArrType); transformVal(key, type, size, result, `.${key}`); } return result; } /** * Creates a Complex type, maded of anothers types. * * @param type the type that will be created * @param structure a sequence of `key: type` */ setType(type, ...structure) { if (typeof type !== "string") throw new TypeError(`The type must be string`); const err = structure.find(str => !StreamStructure.typeObjectReader.test(str)); if (err) throw new Error(`The string '${err}' don't match with pattern 'key: type'`); this.typesDefinitions[type] = structure; return this; } /** * Creates a pre-process and post-process for any type, userful for get a better reading out or input. * @param type the type that will be pre-processed and post-processed * @param preProcessing the pre-processor used to change this type when is going to transform into buffer * @param postProcessing the post-processor used to change this type when is going to transform from buffer */ setTypeProcess(type, preProcessing, postProcessing) { if (typeof type !== "string") throw new TypeError(`The type must be string`); if (typeof preProcessing !== "function") throw new TypeError(`The preProcessing must be function`); if (typeof postProcessing !== "function") throw new TypeError(`The postProcessing must be function`); this.preProcessing[type] = preProcessing; this.posProcessing[type] = postProcessing; return this; } /** * Sets the type of key from a typeConditional, normally used only with "string" or "byte". * @param type * @param indexType * @returns */ setTypeConditionalIndex(type, indexType) { if (typeof type !== "string") throw new TypeError(`The type must be string`); if (typeof indexType !== "string") throw new TypeError(`The indexType must be string`); if (type in this.typeConditions) this.typeConditions[type].indexType = indexType; else this.typeConditions[type] = { indexType: indexType, data: {} }; return this; } /** * Creates a type that changes the structure based on the key setted before, usefull for recursive objects * @param type the type to be created * @param condition if the key is equal to this argument, will use this structure * @param structure structure to be used */ setTypeConditional(type, condition, ...structure) { if (typeof type !== "string") throw new TypeError(`The type must be string`); if (!["string", "number", "symbol"].includes(typeof condition)) throw new TypeError(`The type must be string or number`); const err = structure.find(str => !StreamStructure.typeObjectReader.test(str)); if (err) throw new Error(`The string '${err}' don't match with pattern 'key: type'`); if (!(type in this.typeConditions)) this.setTypeConditionalIndex(type, "string"); this.typeConditions[type].data[condition] = structure; return this; } /** * @deprecated mismatch :P, instead use the `SS.setTypeConditionalIndex()` */ setTypeCondicionalIndex(type, indexType) { if (type in this.typeConditions) this.typeConditions[type].indexType = indexType; else this.typeConditions[type] = { indexType: indexType, data: {} }; return this; } /** * @deprecated mismatch :P, instead use the `SS.setTypeConditional()` */ setTypeCondicional(type, condition, structure) { const err = structure.find(str => !StreamStructure.typeObjectReader.test(str)); if (err) throw new Error(`The structure's string "${err}" don't match with pattern "key: type"`); if (!(type in this.typeConditions)) this.setTypeConditionalIndex(type, "string"); this.typeConditions[type].data[condition] = structure; return this; } /** * Set the default endian for the numbers, arrays, etc. * @param endian the default endian * @returns */ setDefaultEndian(endian) { if (endian !== "BE" && endian !== "LE") throw new Error("The endian must be 'BE' or 'LE'"); this.endian = endian; return this; } } StreamStructure.primitivesLength = Object.freeze({ "boolean": 1, "char": 1, "string": 2, "byte": 1, "ubyte": 1, "short": 2, "ushort": 2, "int": 4, "uint": 4, "long": 8, "ulong": 8, "float": 4, "double": 8, }); /** Picks the value from `(key): (value[size])` */ StreamStructure.typeObjectReader = /^(\w*)\s*:\s*(\w*\s*(?:\[!?[1-6]\]\s*)*)$/i; /** Picks the value from `(value)([size])` */ StreamStructure.typeReader = /^(\w*)\s*((?:\[!?[1-6]\]\s*)*)$/i; /** Breaks the array size from `[(!)(size1)]([size2][size3])` */ StreamStructure.typeArrayBreaker = /\[(!?)([1-6])\]((?:\[!?[1-6]\])*)/; module.exports = StreamStructure; //# sourceMappingURL=index.js.map