zigbee-on-host
Version:
ZigBee stack designed to run on a host and communicate with a radio co-processor (RCP)
419 lines • 15.3 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.SPINEL_RCP_API_VERSION = exports.SPINEL_HEADER_FLG_SPINEL = void 0;
exports.decodeSpinelFrame = decodeSpinelFrame;
exports.encodeSpinelFrame = encodeSpinelFrame;
exports.getPackedUIntSize = getPackedUIntSize;
exports.setPackedUInt = setPackedUInt;
exports.getPackedUInt = getPackedUInt;
exports.writePropertyId = writePropertyId;
exports.writePropertyb = writePropertyb;
exports.readPropertyb = readPropertyb;
exports.writePropertyC = writePropertyC;
exports.readPropertyC = readPropertyC;
exports.writePropertyAC = writePropertyAC;
exports.writePropertyc = writePropertyc;
exports.readPropertyc = readPropertyc;
exports.writePropertyS = writePropertyS;
exports.readPropertyS = readPropertyS;
exports.writePropertys = writePropertys;
exports.writePropertyL = writePropertyL;
exports.writePropertyl = writePropertyl;
exports.writePropertyi = writePropertyi;
exports.readPropertyi = readPropertyi;
exports.readPropertyii = readPropertyii;
exports.readPropertyAi = readPropertyAi;
exports.writePropertyU = writePropertyU;
exports.readPropertyU = readPropertyU;
exports.writePropertyE = writePropertyE;
exports.readPropertyE = readPropertyE;
exports.writePropertyd = writePropertyd;
exports.readPropertyd = readPropertyd;
exports.writePropertyD = writePropertyD;
exports.readPropertyD = readPropertyD;
exports.writePropertyStreamRaw = writePropertyStreamRaw;
exports.readStreamRaw = readStreamRaw;
const node_assert_1 = __importDefault(require("node:assert"));
const hdlc_js_1 = require("./hdlc.js");
const SPINEL_HEADER_TID_MASK = 0x0f;
const SPINEL_HEADER_NLI_MASK = 0x30;
const SPINEL_HEADER_NLI_SHIFT = 4;
const SPINEL_HEADER_FLG_MASK = 0xc0;
const SPINEL_HEADER_FLG_SHIFT = 6;
/** @see SpinelFrameHeader.flg */
exports.SPINEL_HEADER_FLG_SPINEL = 2;
exports.SPINEL_RCP_API_VERSION = 11;
/**
* Decode HDLC frame into Spinel frame
*/
function decodeSpinelFrame(hdlcFrame) {
const header = hdlcFrame.data[0];
const tid = header & SPINEL_HEADER_TID_MASK;
const nli = (header & SPINEL_HEADER_NLI_MASK) >> SPINEL_HEADER_NLI_SHIFT;
const flg = (header & SPINEL_HEADER_FLG_MASK) >> SPINEL_HEADER_FLG_SHIFT;
const [commandId, outOffset] = getPackedUInt(hdlcFrame.data, 1);
const payload = hdlcFrame.data.subarray(outOffset, hdlcFrame.length);
return {
header: { tid, nli, flg },
commandId,
payload,
};
}
/**
* Encode Spinel frame into HDLC frame
*/
function encodeSpinelFrame(frame) {
const cmdIdSize = getPackedUIntSize(frame.commandId);
const buffer = Buffer.alloc(frame.payload.byteLength + 1 + cmdIdSize);
const headerByte = (frame.header.tid & SPINEL_HEADER_TID_MASK) |
((frame.header.nli << SPINEL_HEADER_NLI_SHIFT) & SPINEL_HEADER_NLI_MASK) |
((frame.header.flg << SPINEL_HEADER_FLG_SHIFT) & SPINEL_HEADER_FLG_MASK);
buffer[0] = headerByte;
const outOffset = setPackedUInt(buffer, 1, frame.commandId, cmdIdSize);
buffer.set(frame.payload, outOffset);
return (0, hdlc_js_1.encodeHdlcFrame)(buffer);
}
const SPINEL_PACKED_UINT_MASK = 0x80;
const SPINEL_PACKED_UINT_MSO_MASK = 0x7f;
function getPackedUIntSize(value) {
if (value < 1 << 7) {
return 1;
}
if (value < 1 << 14) {
return 2;
}
if (value < 1 << 21) {
return 3;
}
if (value < 1 << 28) {
return 4;
}
return 5;
}
function setPackedUInt(data, offset, value, size) {
if (!size) {
size = getPackedUIntSize(value);
}
for (let i = 0; i !== size - 1; i++) {
data[offset] = (value & SPINEL_PACKED_UINT_MSO_MASK) | SPINEL_PACKED_UINT_MASK;
offset += 1;
value >>= 7;
}
data[offset] = value & SPINEL_PACKED_UINT_MSO_MASK;
offset += 1;
return offset;
}
function getPackedUInt(data, offset) {
let value = 0;
let i = 0;
do {
if (i >= 40) {
throw new Error(`Invalid Packed UInt, got ${i}, expected < 40`);
}
value |= (data[offset] & SPINEL_PACKED_UINT_MSO_MASK) << i;
i += 7;
offset += 1;
} while ((data[offset - 1] & SPINEL_PACKED_UINT_MASK) === SPINEL_PACKED_UINT_MASK);
return [value, offset];
}
/** Create output array of given (size + property size) and set the property ID at index 0 */
function writePropertyId(propertyId, size) {
const propIdSize = getPackedUIntSize(propertyId);
const buf = Buffer.alloc(propIdSize + size);
const offset = setPackedUInt(buf, 0, propertyId, propIdSize);
return [buf, offset];
}
/** Write as boolean */
function writePropertyb(propertyId, value) {
const [buf, offset] = writePropertyId(propertyId, 1);
buf[offset] = value ? 1 : 0;
return buf;
}
/** Read as boolean */
function readPropertyb(propertyId, data, offset = 0) {
const [propId, pOutOffset] = getPackedUInt(data, offset);
(0, node_assert_1.default)(propId === propertyId);
return !!data[pOutOffset];
}
/** Write as uint8 */
function writePropertyC(propertyId, value) {
const [buf, offset] = writePropertyId(propertyId, 1);
buf[offset] = value;
return buf;
}
/** Read as uint8 */
function readPropertyC(propertyId, data, offset = 0) {
const [propId, pOutOffset] = getPackedUInt(data, offset);
(0, node_assert_1.default)(propId === propertyId);
return data[pOutOffset];
}
/** Write as list of uint8 */
function writePropertyAC(propertyId, values) {
const [buf, pOffset] = writePropertyId(propertyId, values.length);
let offset = pOffset;
for (const value of values) {
buf.writeUInt8(value, offset);
offset += 1;
}
return buf;
}
/** Write as int8 */
function writePropertyc(propertyId, value) {
const [buf, offset] = writePropertyId(propertyId, 1);
buf.writeInt8(value, offset);
return buf;
}
/** Read as int8 */
function readPropertyc(propertyId, data, offset = 0) {
const [propId, pOutOffset] = getPackedUInt(data, offset);
(0, node_assert_1.default)(propId === propertyId);
return data.readInt8(pOutOffset);
}
/** Write as uint16 */
function writePropertyS(propertyId, value) {
const [buf, offset] = writePropertyId(propertyId, 2);
buf.writeUInt16LE(value, offset);
return buf;
}
/** Read as uint16 */
function readPropertyS(propertyId, data, offset = 0) {
const [propId, pOutOffset] = getPackedUInt(data, offset);
(0, node_assert_1.default)(propId === propertyId);
return data.readUInt16LE(pOutOffset);
}
/** Write as int16 */
function writePropertys(propertyId, value) {
const [buf, offset] = writePropertyId(propertyId, 2);
buf.writeInt16LE(value, offset);
return buf;
}
/** Write as uint32 */
function writePropertyL(propertyId, value) {
const [buf, offset] = writePropertyId(propertyId, 4);
buf.writeUInt32LE(value, offset);
return buf;
}
/** Write as int32 */
function writePropertyl(propertyId, value) {
const [buf, offset] = writePropertyId(propertyId, 4);
buf.writeInt32LE(value, offset);
return buf;
}
/** Write as packed uint */
function writePropertyi(propertyId, value) {
const valueSize = getPackedUIntSize(value);
const [buf, offset] = writePropertyId(propertyId, valueSize);
setPackedUInt(buf, offset, value, valueSize);
return buf;
}
/** Read as packed uint */
function readPropertyi(propertyId, data, offset = 0) {
const [propId, pOutOffset] = getPackedUInt(data, offset);
(0, node_assert_1.default)(propId === propertyId);
const [i] = getPackedUInt(data, pOutOffset);
return i;
}
/** Read as packed uint x2 */
function readPropertyii(propertyId, data, offset = 0) {
const [propId, pOutOffset] = getPackedUInt(data, offset);
(0, node_assert_1.default)(propId === propertyId);
const [major, maOutOffset] = getPackedUInt(data, pOutOffset);
const [minor] = getPackedUInt(data, maOutOffset);
return [major, minor];
}
/** Read as list of packed uint */
function readPropertyAi(propertyId, data, offset = 0) {
const [propId, pOutOffset] = getPackedUInt(data, offset);
(0, node_assert_1.default)(propId === propertyId);
const caps = [];
for (let i = pOutOffset; i < data.byteLength;) {
const [cap, cOutOffset] = getPackedUInt(data, i);
caps.push(cap);
i += cOutOffset;
}
return caps;
}
/** Write as UTF8 string */
function writePropertyU(propertyId, value) {
const [buf, offset] = writePropertyId(propertyId, value.length);
buf.write(value, offset, "utf8");
return buf;
}
/** Read as UTF8 string */
function readPropertyU(propertyId, data, offset = 0) {
const [propId, pOutOffset] = getPackedUInt(data, offset);
(0, node_assert_1.default)(propId === propertyId);
return data.toString("utf8", pOutOffset);
}
/** Write as bigint */
function writePropertyE(propertyId, value) {
const [buf, offset] = writePropertyId(propertyId, 8);
buf.writeBigUInt64BE(value, offset);
return buf;
}
/** Read as bigint */
function readPropertyE(propertyId, data, offset = 0) {
const [propId, pOutOffset] = getPackedUInt(data, offset);
(0, node_assert_1.default)(propId === propertyId);
return data.readBigUInt64BE(pOutOffset);
}
/** Write as Buffer of specific length */
function writePropertyd(propertyId, value) {
const [buf, offset] = writePropertyId(propertyId, 2 + value.byteLength);
buf.writeUInt16LE(value.byteLength, offset);
buf.set(value, offset);
return buf;
}
/** Read as Buffer of specific length */
function readPropertyd(propertyId, data, offset = 0) {
const [propId, pOutOffset] = getPackedUInt(data, offset);
(0, node_assert_1.default)(propId === propertyId);
const length = data.readUInt16LE(pOutOffset);
const lOutOffset = pOutOffset + 2;
return data.subarray(lOutOffset, lOutOffset + length);
}
/** Write as Buffer of remaining length */
function writePropertyD(propertyId, value) {
const [buf, offset] = writePropertyId(propertyId, value.byteLength);
buf.set(value, offset);
return buf;
}
/** Read as Buffer of remaining length */
function readPropertyD(propertyId, data, offset = 0) {
const [propId, pOutOffset] = getPackedUInt(data, offset);
(0, node_assert_1.default)(propId === propertyId);
return data.subarray(pOutOffset);
}
/** @see https://datatracker.ietf.org/doc/html/draft-rquattle-spinel-unified#section-5.6.2 */
function writePropertyStreamRaw(data, config) {
const [buf, pOutOffset] = writePropertyId(113 /* SpinelPropertyId.STREAM_RAW */, data.byteLength + 18);
let offset = pOutOffset;
buf.writeUInt16LE(data.byteLength, offset);
offset += 2;
buf.set(data, offset);
offset += data.byteLength;
buf.writeUInt8(config.txChannel, offset);
offset += 1;
buf.writeUInt8(config.ccaBackoffAttempts, offset);
offset += 1;
buf.writeUInt8(config.ccaRetries, offset);
offset += 1;
buf.writeUInt8(config.enableCSMACA ? 1 : 0, offset);
offset += 1;
buf.writeUInt8(config.headerUpdated ? 1 : 0, offset);
offset += 1;
buf.writeUInt8(config.reTx ? 1 : 0, offset);
offset += 1;
buf.writeUInt8(config.securityProcessed ? 1 : 0, offset);
offset += 1;
buf.writeUInt32LE(config.txDelay, offset);
offset += 4;
buf.writeUInt32LE(config.txDelayBaseTime, offset);
offset += 4;
buf.writeUInt8(config.rxChannelAfterTxDone, offset);
offset += 1;
return buf;
}
/**
* @see https://datatracker.ietf.org/doc/html/draft-rquattle-spinel-unified#section-5.6.2.1
*
* Assumes payload comes from `spinel.payload` and offset is right after `SpinelPropertyId.STREAM_RAW`, per below
*
* Packed-Encoding: "dD"
*
* +---------+----------------+------------+----------------+
* | Octets: | 2 | n | n |
* +---------+----------------+------------+----------------+
* | Fields: | FRAME_DATA_LEN | FRAME_DATA | FRAME_METADATA |
* +---------+----------------+------------+----------------+
*
* from pyspinel (https://github.com/openthread/pyspinel/blob/main/sniffer.py#L283):
* metadata format (totally 19 bytes or 26 bytes):
* 0. RSSI(int8)
* 1. Noise Floor(int8)
* 2. Flags(uint16)
* 3. PHY-specific data struct contains:
* 3.0 Channel(uint8)
* 3.1 LQI(uint8)
* 3.2 Timestamp in microseconds(uint64)
* 4. Vendor data struct contains:
* 4.0 Receive error(uint8)
* 5. (optional) MAC data struct contains:
* 5.0 ACK key ID(uint8)
* 5.1 ACK frame counter(uint32)
*/
function readStreamRaw(payload, offset) {
const frameDataLen = payload.readUInt16LE(offset);
offset += 2;
let metaOffset = offset + frameDataLen;
let metadata;
if (payload.byteLength > metaOffset) {
const rssi = payload.readInt8(metaOffset);
metaOffset += 1;
const noiseFloor = payload.readInt8(metaOffset);
metaOffset += 1;
const flags = payload.readUInt16LE(metaOffset);
metaOffset += 2;
// Silabs EFR32 PHY: channel: ok, lqi: 0xff or 0x00 (not working?), timestamp: seems ok
// Silabs EFR32 VEN: error: 0x00 (not implemented?)
// Silabs EFR32 MAC: ackKeyId: 0x00 (not implemented?), ackFramceCounter: 0x00000000 (not implemented?)
// let phyChannel: number | undefined;
// let phyLQI: number | undefined;
// let phyTimestamp: bigint | undefined;
// const phyDataLen = payload.readUInt16LE(metaOffset);
// metaOffset += 2;
// if (phyDataLen >= 1) {
// phyChannel = payload.readUInt8(metaOffset);
// metaOffset += 1;
// }
// if (phyDataLen >= 2) {
// phyLQI = payload.readUInt8(metaOffset);
// metaOffset += 1;
// }
// if (phyDataLen >= 10) {
// phyTimestamp = payload.readBigUInt64LE(metaOffset);
// metaOffset += 8;
// }
// metaOffset += phyDataLen - 10;
// let vendorRxError: number | undefined;
// const vendorDataLen = payload.readUInt16LE(metaOffset);
// metaOffset += 2;
// if (vendorDataLen >= 1) {
// vendorRxError = payload.readUInt8(metaOffset);
// metaOffset += 1;
// }
// metaOffset += vendorDataLen - 1;
// let macACKKeyId: number | undefined;
// let macACKFrameCounter: number | undefined;
// const macDataLen = payload.readUInt16LE(metaOffset);
// metaOffset += 2;
// if (macDataLen >= 1) {
// vendorRxError = payload.readUInt8(metaOffset);
// metaOffset += 1;
// }
// if (macDataLen >= 5) {
// vendorRxError = payload.readUInt32LE(metaOffset);
// metaOffset += 4;
// }
// metaOffset += macDataLen - 5;
metadata = {
rssi,
noiseFloor,
flags,
// phyChannel,
// phyLQI,
// phyTimestamp,
// // phyOtherData,
// vendorRxError,
// // vendorOtherData,
// macACKKeyId,
// macACKFrameCounter,
// // macOtherData,
};
}
return [payload.subarray(offset, offset + frameDataLen), metadata];
}
//# sourceMappingURL=spinel.js.map