packet_device
Version:
`packet_device` is a Node.js library for secure, packetized communication with microcontrollers (Arduino, ESP32, etc.) running the [Packet Device Arduino library](https://github.com/Pallob-Gain/packet_device).
458 lines (412 loc) • 18.1 kB
JavaScript
/**
* @file struct.js
* @author Pallob K. Gain (pallobkgain@gmail.com)
* @M10Xcore V2.0 board library for c-struct
* @version 0.1
* @date 2025-12-17
*
* @copyright Copyright (c) 2023
*
* Known types: https://github.com/TooTallNate/ref/wiki/Known-%22types%22
*/
const UINT8_T = 1;
const UINT16_T = 2;
const UINT32_T = 3;
const UINT64_T = 4;
const CHAR = 5;
const UCHAR = 6;
const BYTE = 7;
const INT = 8;
const UINT = 9;
const FLOAT = 10;
const DOUBLE = 11;
const LONG = 12;
const ULONG = 13;
const INT8_T = 14;
const INT16_T = 15;
const INT32_T = 16;
const INT64_T = 17;
const BOOL = 18;
class Struct {
static type = {
uint8_t: { state: UINT8_T, signed: false, size: 1 },
uint16_t: { state: UINT16_T, signed: false, size: 2 },
uint32_t: { state: UINT32_T, signed: false, size: 4 },
uint64_t: { state: UINT64_T, signed: false, size: 8 },
char: { state: CHAR, signed: true, size: 1 },
uchar: { state: UCHAR, signed: false, size: 1 },
byte: { state: BYTE, signed: false, size: 1 },
bool: { state: BOOL, signed: false, size: 1 },
int: { state: INT, signed: true, size: 4 },
uint: { state: UINT, signed: false, size: 4 },
float: { state: FLOAT, signed: true, size: 4 },
double: { state: DOUBLE, signed: true, size: 8 },
long: { state: LONG, signed: true, size: 8 },
ulong: { state: ULONG, signed: false, size: 8 },
int8_t: { state: INT8_T, signed: true, size: 1 },
int16_t: { state: INT16_T, signed: true, size: 2 },
int32_t: { state: INT32_T, signed: true, size: 4 },
int64_t: { state: INT64_T, signed: true, size: 8 },
};
static sizeOf(struct_type) {
if (!struct_type || !struct_type.size) return 0;
return struct_type.size;
}
static arrayType(type, length, converter) {
return { array_type: true, type, length, size: type.size * length, converter };
}
static stringType(max_length) {
return this.arrayType(this.type.char, max_length, (buff) => {
let firstZero = buff.indexOf('\0');
firstZero = firstZero == -1 ? buff.length : firstZero;
let cBuff = Buffer.alloc(firstZero);
buff.copy(cBuff, 0, 0, firstZero);
return cBuff.toString();
});
}
static makeType(struct_type) {
let size = 0;
let details = {};
for (let [label, info] of Object.entries(struct_type)) {
let __info = Object.assign({ offset: size }, info);
size += info.size;
details[label] = __info;
}
return { details, size };
}
holder; //buffer holder
struct;
constructor(struct, source = null) {
if (source && !(Buffer.isBuffer(source))) throw new Error('source must be an instance of Buffer');
if (source && source.length < struct.size) throw new Error('Buffer size is smaller than struct size');
this.struct = struct;
//console.log({struct});
this.holder = source !== null ? source : Buffer.alloc(struct.size, 0);
}
ref() {
return this.holder;
}
useRef(source) {
if (!Buffer.isBuffer(source)) throw new Error('Buffer is not a Buffer instance');
if (source.length < this.struct.size) throw new Error('Buffer size is smaller than struct size');
this.holder = source;
}
collect(source, source_start = 0) {
//check if source is buffer
if (!Buffer.isBuffer(source)) throw new Error('Source is not a Buffer');
//check size
if (source.length - source_start < this.holder.length) throw new Error('Source buffer size is smaller than struct size');
source.copy(this.holder, 0, source_start, source_start + this.holder.length);
}
size() {
return this.holder.length;
}
setJson(value) {
if (!(typeof value == 'object')) throw new Error('Value is not an object');
for (let name in this.struct.details) {
//only if the name exist in the value
if (name in value) {
if (typeof value[name] == 'object') {
if (!('details' in this.struct.details[name])) throw new Error('Type is not a Struct');
let child = new Struct(this.struct.details[name]);
child.setJson(value[name]);
this.set(name, child);
}
else if (Array.isArray(value[name]) && ('array_type' in this.struct.details[name] && this.struct.details[name].array_type && 'details' in this.struct.details[name].type)) {
this.set(name, value[name].map(item => {
let child = new Struct(this.struct.details[name].type);
child.setJson(item);
return child;
}));
}
else {
this.set(name, value[name]);
}
}
}
return false;
}
valueExtractor(value) {
if (value instanceof Struct) {
return value.getJson();
}
else if (Array.isArray(value)) {
return value.map(item => this.valueExtractor(item));
}
return value;
}
getJson() {
let result = {};
for (let name in this.struct.details) {
let value = this.get(name);
result[name] = this.valueExtractor(value);
}
return result;
}
set(label, value) {
if (!(label in this.struct.details)) throw new Error('Unknown struct property: ' + label);
let info = this.struct.details[label];
let data_holder = this.holder;
if ('details' in info && info.details) {
if (!(value instanceof Struct)) throw new Error('Value is not a Struct instance');
let buff = value.ref();
buff.copy(data_holder, info.offset, 0, Math.min(info.size, buff.length));
}
else if ('array_type' in info && info.array_type) {
//if array of struct
if ('details' in info.type && info.type.details) {
let array_length = info.length;
for (let idx = 0; idx < array_length; idx++) {
let struct_instance = value[idx];
if (!(struct_instance instanceof Struct)) throw new Error('Value is not a Struct instance');
let offset = info.offset + idx * info.type.size;
let buff = struct_instance.ref();
buff.copy(data_holder, offset, 0, Math.min(info.type.size, buff.length));
}
}
else {
let type_state = info.type.state;
switch (type_state) {
case CHAR:
case UCHAR:
value = value;
break;
case BOOL:
value = Uint8Array.from(value.map(v => v ? 1 : 0)).buffer;
break;
case BYTE:
case UINT8_T:
value = Uint8Array.from(value).buffer;
break;
case UINT16_T:
value = Uint16Array.from(value).buffer;
break;
case UINT32_T:
case UINT:
value = Uint32Array.from(value).buffer;
break;
case UINT64_T:
case ULONG:
value = BigUint64Array.from(value).buffer;
break;
case INT32_T:
case INT:
value = Int32Array.from(value).buffer;
break;
case FLOAT:
value = Float32Array.from(value).buffer;
break;
case DOUBLE:
value = Float64Array.from(value).buffer;
break;
case INT64_T:
case LONG:
value = BigInt64Array.from(value).buffer;
break;
case INT8_T:
value = Int8Array.from(value).buffer;
break;
case UINT16_T:
value = Int16Array.from(value).buffer;
break;
default:
throw new Error('Unknown array type: ' + type_state);
}
let buff = Buffer.from(value);
//console.log('buff',buff,'Copy:',Math.min(info.size,buff.length));
buff.copy(data_holder, info.offset, 0, Math.min(info.size, buff.length));
}
}
else {
let type_state = info.state;
switch (type_state) {
case CHAR:
case UCHAR:
data_holder.write(value, info.offset, info.size);
break;
case BOOL:
data_holder.writeUInt8(value ? 1 : 0, info.offset);
break;
case BYTE:
case UINT8_T:
data_holder.writeUInt8(value, info.offset);
break;
case UINT16_T:
data_holder.writeUInt16LE(value, info.offset);
break;
case UINT32_T:
case UINT:
data_holder.writeUInt32LE(value, info.offset);
break;
case UINT64_T:
case ULONG:
data_holder.writeBigUInt64LE(value, info.offset);
break;
case INT32_T:
case INT:
data_holder.writeInt32LE(value, info.offset);
break;
case FLOAT:
data_holder.writeFloatLE(value, info.offset);
break;
case DOUBLE:
data_holder.writeDoubleLE(value, info.offset);
break;
case INT64_T:
case LONG:
data_holder.writeBigInt64LE(value, info.offset);
break;
case INT8_T:
data_holder.writeInt8(value, info.offset);
break;
case INT16_T:
data_holder.writeInt16LE(value, info.offset);
break;
default:
throw new Error('Unknown data type: ' + type_state);
}
}
return true;
}
get(label) {
if (!(label in this.struct.details)) throw new Error('Unknown struct property: ' + label);
let info = this.struct.details[label];
let data_holder = this.holder;
let value = null;
//if another struct
if ('details' in info && info.details) {
let section_data = Buffer.from(data_holder.buffer, info.offset, info.size);
value = new Struct(info);
value.collect(section_data);
}
else if ('array_type' in info && info.array_type) {
//if array of struct
if ('details' in info.type && info.type.details) {
let section_data = Buffer.from(data_holder.buffer, info.offset, info.size);
let array_length = info.length;
value = (new Array(array_length).fill(null)).map((_, idx) => {
let struct_instance = new Struct(info.type);
let offset = idx * info.type.size;
struct_instance.collect(section_data, offset);
return struct_instance;
});
}
else {
//we need to copy the buffer section to the a new buffer otherwise array will fail as the offset need to be multiplied by element size
const section_data = Buffer.alloc(info.size);
data_holder.copy(section_data, 0, info.offset, info.offset + info.size);
//let section_data = Buffer.from(data_holder.buffer, info.offset, info.size);
let type_state = info.type.state;
switch (type_state) {
case CHAR:
case UCHAR:
value = Buffer.from(section_data);
break;
case BOOL:
value = Array.from(new Uint8Array(section_data.buffer, section_data.byteOffset, section_data.length / Uint8Array.BYTES_PER_ELEMENT)).map(v => v ? true : false);
break;
case BYTE:
case UINT8_T:
value = new Uint8Array(section_data.buffer, section_data.byteOffset, section_data.length / Uint8Array.BYTES_PER_ELEMENT);
break;
case UINT16_T:
value = new Uint16Array(section_data.buffer, section_data.byteOffset, section_data.length / Uint16Array.BYTES_PER_ELEMENT);
break;
case UINT32_T:
case UINT:
value = new Uint32Array(section_data.buffer, section_data.byteOffset, section_data.length / Uint32Array.BYTES_PER_ELEMENT);
break;
case UINT64_T:
case ULONG:
value = new BigUint64Array(section_data.buffer, section_data.byteOffset, section_data.length / BigUint64Array.BYTES_PER_ELEMENT);
break;
case INT32_T:
case INT:
value = new Int32Array(section_data.buffer, section_data.byteOffset, section_data.length / Int32Array.BYTES_PER_ELEMENT);
break;
case FLOAT:
value = new Float32Array(section_data.buffer, section_data.byteOffset, section_data.length / Float32Array.BYTES_PER_ELEMENT);
break;
case DOUBLE:
value = new Float64Array(section_data.buffer, section_data.byteOffset, section_data.length / Float64Array.BYTES_PER_ELEMENT);
break;
case INT64_T:
case LONG:
value = new BigInt64Array(section_data.buffer, section_data.byteOffset, section_data.length / BigInt64Array.BYTES_PER_ELEMENT);
break;
case INT8_T:
value = new Int8Array(section_data.buffer, section_data.byteOffset, section_data.length / Int8Array.BYTES_PER_ELEMENT);
break;
case INT16_T:
value = new Int16Array(section_data.buffer, section_data.byteOffset, section_data.length / Int16Array.BYTES_PER_ELEMENT);
break;
default:
throw new Error('Unknown array type: ' + type_state);
}
if ('converter' in info && info.converter) {
value = info.converter(value);
}
}
//console.log('array check:',value);
}
else {
let section_data = data_holder;//Buffer.from(data_holder.buffer, info.offset, info.size);
let type_state = info.state;
switch (type_state) {
case CHAR:
case UCHAR:
value = section_data.buffer[info.offset];
break;
case BOOL:
value = section_data.readUInt8(info.offset) ? true : false;
break;
case BYTE:
case UINT8_T:
value = section_data.readUInt8(info.offset);
break;
case UINT16_T:
value = section_data.readUInt16LE(info.offset);
break;
case UINT32_T:
case UINT:
value = section_data.readUInt32LE(info.offset);
break;
case UINT64_T:
case ULONG:
value = section_data.readBigUInt64LE(info.offset);
break;
case INT32_T:
case INT:
value = section_data.readInt32LE(info.offset);
break;
case FLOAT:
value = section_data.readFloatLE(info.offset);
break;
case DOUBLE:
value = section_data.readDoubleLE(info.offset);
break;
case INT64_T:
case LONG:
value = section_data.readBigInt64LE(info.offset);
break;
case INT8_T:
value = section_data.readInt8(info.offset);
break;
case INT16_T:
value = section_data.readInt16LE(info.offset);
break;
default:
throw new Error('Unknown data type: ' + type_state);
}
}
return value;
}
data() {
let result = {};
for (let [label, info] of Object.entries(this.struct.details)) {
result[label] = this.get(label);
}
return result;
}
}
module.exports = Struct;