zigbee-herdsman
Version:
An open source ZigBee gateway solution with node.js.
310 lines • 13.1 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Struct = void 0;
const assert_1 = __importDefault(require("assert"));
/**
* Struct provides a builder-like interface to create Buffer-based memory
* structures for read/write interfacing with data structures from adapters.
*/
class Struct {
/**
* Creates an empty struct. Further calls to `member()` and `method()` functions will form the structure.
* Finally call to `build()` will type the resulting structure appropriately without internal functions.
*/
static new() {
return new Struct();
}
// @ts-expect-error initialized in `build()`
buffer;
defaultData;
members = [];
childStructs = {};
length = 0;
paddingByte = 0x00;
constructor() { }
/**
* Returns raw contents of the structure as a sliced Buffer.
* Mutations to the returned buffer will not be reflected within struct.
*/
serialize(alignment = 'unaligned', padLength = true, parentOffset = 0) {
switch (alignment) {
case 'unaligned': {
/* update child struct values and return as-is (unaligned) */
/* istanbul ignore next */
for (const key of Object.keys(this.childStructs)) {
const child = this.childStructs[key];
this.buffer.set(child.struct.serialize(alignment), child.offset);
}
return Buffer.from(this.buffer);
}
case 'aligned': {
/* create 16-bit aligned buffer */
const aligned = Buffer.alloc(this.getLength(alignment, padLength, parentOffset), this.paddingByte);
let offset = 0;
for (const member of this.members) {
switch (member.type) {
case 'uint8':
aligned.set(this.buffer.slice(member.offset, member.offset + 1), offset);
offset += 1;
break;
case 'uint16':
offset += offset % 2;
aligned.set(this.buffer.slice(member.offset, member.offset + 2), offset);
offset += 2;
break;
case 'uint32':
offset += offset % 2;
aligned.set(this.buffer.slice(member.offset, member.offset + 4), offset);
offset += 4;
break;
case 'uint8array':
case 'uint8array-reversed':
(0, assert_1.default)(member.length !== undefined);
aligned.set(this.buffer.slice(member.offset, member.offset + member.length), offset);
offset += member.length;
break;
case 'struct': {
const structData = this.childStructs[member.key].struct.serialize(alignment, false, offset);
aligned.set(structData, offset);
offset += structData.length;
break;
}
}
}
return aligned;
}
}
}
/**
* Returns total length of the struct. Struct length is always fixed and configured
* by calls to `member()` methods.
*/
/* istanbul ignore next */
getLength(alignment = 'unaligned', padLength = true, parentOffset = 0) {
switch (alignment) {
case 'unaligned': {
/* return actual length */
return this.length;
}
case 'aligned': {
/* compute aligned length and return */
let length = this.members.reduce((offset, member) => {
switch (member.type) {
case 'uint8':
offset += 1;
break;
case 'uint16':
offset += ((parentOffset + offset) % 2) + 2;
break;
case 'uint32':
offset += ((parentOffset + offset) % 2) + 4;
break;
case 'uint8array':
case 'uint8array-reversed':
(0, assert_1.default)(member.length !== undefined);
offset += member.length;
break;
case 'struct':
offset += this.childStructs[member.key].struct.getLength(alignment, false);
break;
}
return offset;
}, 0);
if (padLength) {
length += length % 2;
}
return length;
}
}
}
/**
* Returns structure contents in JS object format.
*/
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
toJSON() {
return this.members.reduce((a, c) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
a[c.key] = this[c.key];
return a;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}, {});
}
member(type, name, lengthOrStructFactory) {
const offset = this.length;
const structFactory = type === 'struct' ? lengthOrStructFactory : undefined;
const length = structFactory ? structFactory().length : lengthOrStructFactory;
switch (type) {
case 'uint8': {
Object.defineProperty(this, name, {
enumerable: true,
get: () => this.buffer.readUInt8(offset),
set: (value) => this.buffer.writeUInt8(value, offset),
});
this.length += 1;
break;
}
case 'uint16': {
Object.defineProperty(this, name, {
enumerable: true,
get: () => this.buffer.readUInt16LE(offset),
set: (value) => this.buffer.writeUInt16LE(value, offset),
});
this.length += 2;
break;
}
case 'uint32': {
Object.defineProperty(this, name, {
enumerable: true,
get: () => this.buffer.readUInt32LE(offset),
set: (value) => this.buffer.writeUInt32LE(value, offset),
});
this.length += 4;
break;
}
case 'uint8array':
case 'uint8array-reversed': {
/* istanbul ignore next */
if (!length) {
throw new Error('Struct builder requires length for `uint8array` and `uint8array-reversed` type');
}
Object.defineProperty(this, name, {
enumerable: true,
get: () => type === 'uint8array-reversed'
? Buffer.from(this.buffer.slice(offset, offset + length)).reverse()
: Buffer.from(this.buffer.slice(offset, offset + length)),
set: (value) => {
if (value.length !== length) {
throw new Error(`Invalid length for member ${name} (expected=${length}, got=${value.length})`);
}
if (type === 'uint8array-reversed') {
value = Buffer.from(value).reverse();
}
for (let i = 0; i < length; i++) {
this.buffer[offset + i] = value[i];
}
},
});
this.length += length;
break;
}
case 'struct': {
(0, assert_1.default)(structFactory);
this.childStructs[name] = { offset, struct: structFactory() };
Object.defineProperty(this, name, {
enumerable: true,
get: () => this.childStructs[name].struct,
});
this.length += length;
}
}
this.members.push({ key: name, offset, type, length });
return this;
}
/**
* Adds a custom method to the struct.
*
* *This method is stripped from type on struct `build()`.*
*
* @param name Name of the method to be appended.
* @param returnType Return type (eg. `Buffer.prototype`).
* @param body Function implementation. Takes struct as a first and single input parameter.
*/
method(name, returnType, body) {
Object.defineProperty(this, name, {
enumerable: true,
configurable: false,
writable: false,
// @ts-expect-error ignore because we are using `this`
value: () => body.bind(this)(this),
});
return this;
}
/**
* Sets default data to initialize empty struct with.
*
* @param data Data to initialize empty struct with.
*/
/* istanbul ignore next */
default(data) {
if (data.length !== this.length) {
throw new Error('Default value needs to have the length of unaligned structure.');
}
this.defaultData = Buffer.from(data);
return this;
}
/**
* Sets byte to use for padding.
*
* @param padding Byte to use for padding
*/
/* istanbul ignore next */
padding(padding = 0x00) {
this.paddingByte = padding;
return this;
}
/**
* Creates the struct and optionally fills it with data. If data is provided, the length
* of the provided buffer needs to match the structure length.
*
* *This method is stripped from type on struct `build()`.*
*/
build(data) {
if (data) {
if (data.length === this.getLength('unaligned')) {
this.buffer = Buffer.from(data);
for (const key of Object.keys(this.childStructs)) {
const child = this.childStructs[key];
child.struct.build(this.buffer.slice(child.offset, child.offset + child.struct.length));
}
}
else if (data.length === this.getLength('aligned')) {
this.buffer = Buffer.alloc(this.length, this.paddingByte);
let offset = 0;
for (const member of this.members) {
switch (member.type) {
case 'uint8':
this.buffer.set(data.slice(offset, offset + 1), member.offset);
offset += 1;
break;
case 'uint16':
offset += offset % 2;
this.buffer.set(data.slice(offset, offset + 2), member.offset);
offset += 2;
break;
case 'uint32':
offset += offset % 2;
this.buffer.set(data.slice(offset, offset + 4), member.offset);
offset += 4;
break;
case 'uint8array':
case 'uint8array-reversed':
(0, assert_1.default)(member.length !== undefined);
this.buffer.set(data.slice(offset, offset + member.length), member.offset);
offset += member.length;
break;
case 'struct': {
const child = this.childStructs[member.key];
child.struct.build(data.slice(offset, offset + child.struct.length));
this.buffer.set(child.struct.serialize(), member.offset);
offset += child.struct.length;
break;
}
}
}
}
else {
const expectedLengths = `${this.getLength('unaligned')}/${this.getLength('aligned')}`;
throw new Error(`Struct length mismatch (expected=${expectedLengths}, got=${data.length})`);
}
}
else {
this.buffer = this.defaultData ? Buffer.from(this.defaultData) : Buffer.alloc(this.length);
}
return this;
}
}
exports.Struct = Struct;
//# sourceMappingURL=struct.js.map