zigbee-on-host
Version:
ZigBee stack designed to run on a host and communicate with a radio co-processor (RCP)
921 lines • 42.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MACAssociationStatus = void 0;
exports.getMICLength = getMICLength;
exports.decodeMACFrameControl = decodeMACFrameControl;
exports.decodeMACCapabilities = decodeMACCapabilities;
exports.encodeMACCapabilities = encodeMACCapabilities;
exports.decodeMACHeader = decodeMACHeader;
exports.decodeMACPayload = decodeMACPayload;
exports.encodeMACFrame = encodeMACFrame;
exports.encodeMACFrameZigbee = encodeMACFrameZigbee;
exports.decodeMACZigbeeBeacon = decodeMACZigbeeBeacon;
exports.encodeMACZigbeeBeacon = encodeMACZigbeeBeacon;
/** Definitions for Association Response Command */
var MACAssociationStatus;
(function (MACAssociationStatus) {
MACAssociationStatus[MACAssociationStatus["SUCCESS"] = 0] = "SUCCESS";
MACAssociationStatus[MACAssociationStatus["PAN_FULL"] = 1] = "PAN_FULL";
MACAssociationStatus[MACAssociationStatus["PAN_ACCESS_DENIED"] = 2] = "PAN_ACCESS_DENIED";
})(MACAssociationStatus || (exports.MACAssociationStatus = MACAssociationStatus = {}));
/* Compute the MIC length. */
function getMICLength(securityLevel) {
return (0x2 << (securityLevel & 0x3)) & ~0x3;
}
function decodeMACFrameControl(data, offset) {
const fcf = data.readUInt16LE(offset);
offset += 2;
const frameType = fcf & 7 /* ZigbeeMACConsts.FCF_TYPE_MASK */;
if (frameType === 5 /* MACFrameType.MULTIPURPOSE */) {
throw new Error(`Unsupported MAC frame type MULTIPURPOSE (${frameType})`);
}
return [
{
frameType,
securityEnabled: Boolean((fcf & 8 /* ZigbeeMACConsts.FCF_SEC_EN */) >> 3),
framePending: Boolean((fcf & 16 /* ZigbeeMACConsts.FCF_FRAME_PND */) >> 4),
ackRequest: Boolean((fcf & 32 /* ZigbeeMACConsts.FCF_ACK_REQ */) >> 5),
panIdCompression: Boolean((fcf & 64 /* ZigbeeMACConsts.FCF_PAN_ID_COMPRESSION */) >> 6),
/* bit 7 reserved */
seqNumSuppress: Boolean((fcf & 256 /* ZigbeeMACConsts.FCF_SEQNO_SUPPRESSION */) >> 8),
iePresent: Boolean((fcf & 512 /* ZigbeeMACConsts.FCF_IE_PRESENT */) >> 9),
destAddrMode: (fcf & 3072 /* ZigbeeMACConsts.FCF_DADDR_MASK */) >> 10,
frameVersion: (fcf & 12288 /* ZigbeeMACConsts.FCF_VERSION */) >> 12,
sourceAddrMode: (fcf & 49152 /* ZigbeeMACConsts.FCF_SADDR_MASK */) >> 14,
},
offset,
];
}
function encodeMACFrameControl(data, offset, fcf) {
if (fcf.frameType === 5 /* MACFrameType.MULTIPURPOSE */) {
throw new Error(`Unsupported MAC frame type MULTIPURPOSE (${fcf.frameType})`);
}
data.writeUInt16LE((fcf.frameType & 7 /* ZigbeeMACConsts.FCF_TYPE_MASK */) |
(((fcf.securityEnabled ? 1 : 0) << 3) & 8 /* ZigbeeMACConsts.FCF_SEC_EN */) |
(((fcf.framePending ? 1 : 0) << 4) & 16 /* ZigbeeMACConsts.FCF_FRAME_PND */) |
(((fcf.ackRequest ? 1 : 0) << 5) & 32 /* ZigbeeMACConsts.FCF_ACK_REQ */) |
(((fcf.panIdCompression ? 1 : 0) << 6) & 64 /* ZigbeeMACConsts.FCF_PAN_ID_COMPRESSION */) |
/* bit 7 reserved */
(((fcf.seqNumSuppress ? 1 : 0) << 8) & 256 /* ZigbeeMACConsts.FCF_SEQNO_SUPPRESSION */) |
(((fcf.iePresent ? 1 : 0) << 9) & 512 /* ZigbeeMACConsts.FCF_IE_PRESENT */) |
((fcf.destAddrMode << 10) & 3072 /* ZigbeeMACConsts.FCF_DADDR_MASK */) |
((fcf.frameVersion << 12) & 12288 /* ZigbeeMACConsts.FCF_VERSION */) |
((fcf.sourceAddrMode << 14) & 49152 /* ZigbeeMACConsts.FCF_SADDR_MASK */), offset);
offset += 2;
return offset;
}
function decodeMACAuxSecHeader(data, offset, frameControl) {
let frameCounterSuppression = false;
let asn;
let frameCounter;
let keySourceAddr32;
let keySourceAddr64;
let keyIndex;
const securityControl = data.readUInt8(offset);
offset += 1;
const securityLevel = securityControl & 7 /* ZigbeeMACConsts.AUX_SEC_LEVEL_MASK */;
const keyIdMode = (securityControl & 24 /* ZigbeeMACConsts.AUX_KEY_ID_MODE_MASK */) >> 3 /* ZigbeeMACConsts.AUX_KEY_ID_MODE_SHIFT */;
if (frameControl.frameVersion === 2 /* MACFrameVersion.V2015 */) {
frameCounterSuppression = Boolean(securityControl & 32 /* ZigbeeMACConsts.AUX_FRAME_COUNTER_SUPPRESSION_MASK */);
// TODO: correct??
asn = (securityControl & 64 /* ZigbeeMACConsts.AUX_ASN_IN_NONCE_MASK */) >> 6;
}
if (!frameCounterSuppression) {
frameCounter = data.readUInt32LE(offset);
offset += 4;
}
if (keyIdMode !== 0 /* MACSecurityKeyIdMode.IMPLICIT */) {
if (keyIdMode === 2 /* MACSecurityKeyIdMode.EXPLICIT_4 */) {
keySourceAddr32 = data.readUInt32LE(offset);
offset += 4;
}
else if (keyIdMode === 3 /* MACSecurityKeyIdMode.EXPLICIT_8 */) {
keySourceAddr64 = data.readBigUInt64LE(offset);
offset += 8;
}
keyIndex = data.readUInt8(offset);
offset += 1;
}
return [
{
securityLevel,
keyIdMode,
frameCounterSuppression,
asn,
frameCounter,
keySourceAddr32,
keySourceAddr64,
keyIndex,
},
offset,
];
}
// function encodeMACAuxSecHeader(data: Buffer, offset: number): number {}
function decodeMACSuperframeSpec(data, offset) {
const spec = data.readUInt16LE(offset);
offset += 2;
const beaconOrder = spec & 15 /* ZigbeeMACConsts.SUPERFRAME_BEACON_ORDER_MASK */;
const superframeOrder = (spec & 240 /* ZigbeeMACConsts.SUPERFRAME_ORDER_MASK */) >> 4 /* ZigbeeMACConsts.SUPERFRAME_ORDER_SHIFT */;
const finalCAPSlot = (spec & 3840 /* ZigbeeMACConsts.SUPERFRAME_CAP_MASK */) >> 8 /* ZigbeeMACConsts.SUPERFRAME_CAP_SHIFT */;
const batteryExtension = Boolean((spec & 4096 /* ZigbeeMACConsts.SUPERFRAME_BATT_EXTENSION_MASK */) >> 12 /* ZigbeeMACConsts.SUPERFRAME_BATT_EXTENSION_SHIFT */);
const panCoordinator = Boolean((spec & 16384 /* ZigbeeMACConsts.SUPERFRAME_COORD_MASK */) >> 14 /* ZigbeeMACConsts.SUPERFRAME_COORD_SHIFT */);
const associationPermit = Boolean((spec & 32768 /* ZigbeeMACConsts.SUPERFRAME_ASSOC_PERMIT_MASK */) >> 15 /* ZigbeeMACConsts.SUPERFRAME_ASSOC_PERMIT_SHIFT */);
return [
{
beaconOrder,
superframeOrder,
finalCAPSlot,
batteryExtension,
panCoordinator,
associationPermit,
},
offset,
];
}
function encodeMACSuperframeSpec(data, offset, header) {
const spec = header.superframeSpec;
data.writeUInt16LE((spec.beaconOrder & 15 /* ZigbeeMACConsts.SUPERFRAME_BEACON_ORDER_MASK */) |
((spec.superframeOrder << 4 /* ZigbeeMACConsts.SUPERFRAME_ORDER_SHIFT */) & 240 /* ZigbeeMACConsts.SUPERFRAME_ORDER_MASK */) |
((spec.finalCAPSlot << 8 /* ZigbeeMACConsts.SUPERFRAME_CAP_SHIFT */) & 3840 /* ZigbeeMACConsts.SUPERFRAME_CAP_MASK */) |
(((spec.batteryExtension ? 1 : 0) << 12 /* ZigbeeMACConsts.SUPERFRAME_BATT_EXTENSION_SHIFT */) & 4096 /* ZigbeeMACConsts.SUPERFRAME_BATT_EXTENSION_MASK */) |
(((spec.panCoordinator ? 1 : 0) << 14 /* ZigbeeMACConsts.SUPERFRAME_COORD_SHIFT */) & 16384 /* ZigbeeMACConsts.SUPERFRAME_COORD_MASK */) |
(((spec.associationPermit ? 1 : 0) << 15 /* ZigbeeMACConsts.SUPERFRAME_ASSOC_PERMIT_SHIFT */) & 32768 /* ZigbeeMACConsts.SUPERFRAME_ASSOC_PERMIT_MASK */), offset);
offset += 2;
return offset;
}
function decodeMACGtsInfo(data, offset) {
let directionByte;
let directions;
let addresses;
let timeLengths;
let slots;
const spec = data.readUInt8(offset);
offset += 1;
const count = spec & 7 /* ZigbeeMACConsts.GTS_COUNT_MASK */;
const permit = Boolean(spec & 128 /* ZigbeeMACConsts.GTS_PERMIT_MASK */);
if (count > 0) {
directionByte = data.readUInt8(offset);
offset += 1;
directions = [];
addresses = [];
timeLengths = [];
slots = [];
for (let i = 0; i < count; i++) {
directions.push(directionByte & (0x01 << i));
const addr = data.readUInt16LE(offset);
offset += 2;
const slotByte = data.readUInt8(offset);
offset += 1;
const timeLength = (slotByte & 240 /* ZigbeeMACConsts.GTS_LENGTH_MASK */) >> 4 /* ZigbeeMACConsts.GTS_LENGTH_SHIFT */;
const slot = slotByte & 15 /* ZigbeeMACConsts.GTS_SLOT_MASK */;
addresses.push(addr);
timeLengths.push(timeLength);
slots.push(slot);
}
}
return [
{
permit,
directionByte,
directions,
addresses,
timeLengths,
slots,
},
offset,
];
}
function encodeMACGtsInfo(data, offset, header) {
const info = header.gtsInfo;
const count = info.directions ? info.directions.length : 0;
data.writeUInt8((count & 7 /* ZigbeeMACConsts.GTS_COUNT_MASK */) | ((info.permit ? 1 : 0) & 128 /* ZigbeeMACConsts.GTS_PERMIT_MASK */), offset);
offset += 1;
if (count > 0) {
// assert(info.directionByte !== undefined);
data.writeUInt8(info.directionByte, offset);
offset += 1;
for (let i = 0; i < count; i++) {
data.writeUInt16LE(info.addresses[i], offset);
offset += 2;
const timeLength = info.timeLengths[i];
const slot = info.slots[i];
data.writeUInt8(((timeLength << 4 /* ZigbeeMACConsts.GTS_LENGTH_SHIFT */) & 240 /* ZigbeeMACConsts.GTS_LENGTH_MASK */) | (slot & 15 /* ZigbeeMACConsts.GTS_SLOT_MASK */), offset);
offset += 1;
}
}
return offset;
}
function decodeMACPendAddr(data, offset) {
const spec = data.readUInt8(offset);
offset += 1;
const num16 = spec & 7 /* ZigbeeMACConsts.PENDADDR_SHORT_MASK */;
const num64 = (spec & 112 /* ZigbeeMACConsts.PENDADDR_LONG_MASK */) >> 4 /* ZigbeeMACConsts.PENDADDR_LONG_SHIFT */;
let addr16List;
let addr64List;
if (num16 > 0) {
addr16List = [];
for (let i = 0; i < num16; i++) {
addr16List.push(data.readUInt16LE(offset));
offset += 2;
}
}
if (num64 > 0) {
addr64List = [];
for (let i = 0; i < num64; i++) {
addr64List.push(data.readBigUInt64LE(offset));
offset += 8;
}
}
return [
{
addr16List,
addr64List,
},
offset,
];
}
function encodeMACPendAddr(data, offset, header) {
const pendAddr = header.pendAddr;
const num16 = pendAddr.addr16List ? pendAddr.addr16List.length : 0;
const num64 = pendAddr.addr64List ? pendAddr.addr64List.length : 0;
data.writeUInt8((num16 & 7 /* ZigbeeMACConsts.PENDADDR_SHORT_MASK */) | ((num64 << 4 /* ZigbeeMACConsts.PENDADDR_LONG_SHIFT */) & 112 /* ZigbeeMACConsts.PENDADDR_LONG_MASK */), offset);
offset += 1;
for (let i = 0; i < num16; i++) {
data.writeUInt16LE(pendAddr.addr16List[i], offset);
offset += 2;
}
for (let i = 0; i < num64; i++) {
data.writeBigUInt64LE(pendAddr.addr64List[i], offset);
offset += 8;
}
return offset;
}
function decodeMACHeaderIEs(data, offset, auxSecHeader) {
let remaining = data.byteLength - offset - getMICLength(auxSecHeader?.securityLevel ?? 0);
let payloadIEPresent = false;
const ies = [];
do {
const header = data.readUInt16LE(offset);
offset += 2;
const id = (header & 32640 /* ZigbeeMACConsts.HEADER_IE_ID_MASK */) >> 7;
const length = header & 127 /* ZigbeeMACConsts.HEADER_IE_LENGTH_MASK */;
ies.push({ id, length });
offset += 2 + length;
remaining -= 2 + length;
if (id === 126 /* ZigbeeMACConsts.HEADER_IE_HT1 */ || id === 127 /* ZigbeeMACConsts.HEADER_IE_HT2 */) {
payloadIEPresent = id === 126 /* ZigbeeMACConsts.HEADER_IE_HT1 */;
break;
}
} while (remaining > 0);
return [
{
ies,
payloadIEPresent,
},
offset,
];
}
// function encodeMACHeaderIEs(data: Buffer, offset: number): number {}
// export type MACHeaderPayloadIE = {
// }
// /**
// * TODO: proper support for all IE stuff
// *
// * The Zigbee Payload IE is a Vendor Specific Payload IE (Group ID = 0x2) using the Zigbee OUI value of 0x4A191B.
// * - Bits: 0-5 6-15 Octets: Variable
// * - Length Sub-ID Content
// *
// * REJOIN:
// * - Octets: 8 2
// * - Network Extended PAN ID Sender Short Address
// *
// * TX_POWER:
// * - Octets: 1
// * - TX Power (in dBm - used to send the frame)
// *
// * EB_PAYLOAD:
// * - Octets: 15 2 2
// * - Beacon Payload Superframe Specification Sender Short Address
// */
// function decodeMACHeaderPayloadIEs(data: Buffer, offset: number, headerIE: MACHeaderIE): [MACHeaderPayloadIE[], offset: number] {
// return [[], offset];
// }
function decodeMACCapabilities(capabilities) {
return {
alternatePANCoordinator: Boolean(capabilities & 0x01),
deviceType: (capabilities & 0x02) >> 1,
powerSource: (capabilities & 0x04) >> 2,
rxOnWhenIdle: Boolean((capabilities & 0x08) >> 3),
// reserved1: (capabilities & 0x10) >> 4,
// reserved2: (capabilities & 0x20) >> 5,
securityCapability: Boolean((capabilities & 0x40) >> 6),
allocateAddress: Boolean((capabilities & 0x80) >> 7),
};
}
function encodeMACCapabilities(capabilities) {
return (((capabilities.alternatePANCoordinator ? 1 : 0) & 0x01) |
((capabilities.deviceType << 1) & 0x02) |
((capabilities.powerSource << 2) & 0x04) |
(((capabilities.rxOnWhenIdle ? 1 : 0) << 3) & 0x08) |
// (capabilities.reserved1 << 4) & 0x10) |
// (capabilities.reserved2 << 5) & 0x20) |
(((capabilities.securityCapability ? 1 : 0) << 6) & 0x40) |
(((capabilities.allocateAddress ? 1 : 0) << 7) & 0x80));
}
function decodeMACHeader(data, offset, frameControl) {
let sequenceNumber;
let destinationPANId;
let sourcePANId;
if (!frameControl.seqNumSuppress) {
sequenceNumber = data.readUInt8(offset);
offset += 1;
}
if (frameControl.destAddrMode === 1 /* MACFrameAddressMode.RESERVED */) {
throw new Error(`Invalid MAC frame: destination address mode ${frameControl.destAddrMode}`);
}
if (frameControl.sourceAddrMode === 1 /* MACFrameAddressMode.RESERVED */) {
throw new Error(`Invalid MAC frame: source address mode ${frameControl.sourceAddrMode}`);
}
let destPANPresent = false;
let sourcePANPresent = false;
if (frameControl.frameType === 5 /* MACFrameType.MULTIPURPOSE */) {
throw new Error("Unsupported MAC frame: MULTIPURPOSE");
}
if (frameControl.frameVersion === 0 /* MACFrameVersion.V2003 */ || frameControl.frameVersion === 1 /* MACFrameVersion.V2006 */) {
if (frameControl.destAddrMode !== 0 /* MACFrameAddressMode.NONE */ && frameControl.sourceAddrMode !== 0 /* MACFrameAddressMode.NONE */) {
// addressing information is present
if (frameControl.panIdCompression) {
// PAN IDs are identical
destPANPresent = true;
sourcePANPresent = false;
}
else {
// PAN IDs are different, both shall be included in the frame
destPANPresent = true;
sourcePANPresent = true;
}
}
else {
if (frameControl.panIdCompression) {
throw new Error("Invalid MAC frame: unexpected PAN ID compression");
}
// only either the destination or the source addressing information is present
if (frameControl.destAddrMode !== 0 /* MACFrameAddressMode.NONE */ && frameControl.sourceAddrMode === 0 /* MACFrameAddressMode.NONE */) {
destPANPresent = true;
sourcePANPresent = false;
}
else if (frameControl.destAddrMode === 0 /* MACFrameAddressMode.NONE */ && frameControl.sourceAddrMode !== 0 /* MACFrameAddressMode.NONE */) {
destPANPresent = false;
sourcePANPresent = true;
}
else if (frameControl.destAddrMode === 0 /* MACFrameAddressMode.NONE */ && frameControl.sourceAddrMode === 0 /* MACFrameAddressMode.NONE */) {
destPANPresent = false;
sourcePANPresent = false;
}
else {
throw new Error("Invalid MAC frame: invalid addressing");
}
}
}
else if (frameControl.frameVersion === 2 /* MACFrameVersion.V2015 */) {
if (frameControl.frameType === 0 /* MACFrameType.BEACON */ ||
frameControl.frameType === 1 /* MACFrameType.DATA */ ||
frameControl.frameType === 2 /* MACFrameType.ACK */ ||
frameControl.frameType === 3 /* MACFrameType.CMD */) {
if (frameControl.destAddrMode === 0 /* MACFrameAddressMode.NONE */ &&
frameControl.sourceAddrMode === 0 /* MACFrameAddressMode.NONE */ &&
!frameControl.panIdCompression) {
destPANPresent = false;
sourcePANPresent = false;
}
else if (frameControl.destAddrMode === 0 /* MACFrameAddressMode.NONE */ &&
frameControl.sourceAddrMode === 0 /* MACFrameAddressMode.NONE */ &&
frameControl.panIdCompression) {
destPANPresent = true;
sourcePANPresent = false;
}
else if (frameControl.destAddrMode !== 0 /* MACFrameAddressMode.NONE */ &&
frameControl.sourceAddrMode === 0 /* MACFrameAddressMode.NONE */ &&
!frameControl.panIdCompression) {
destPANPresent = true;
sourcePANPresent = false;
}
else if (frameControl.destAddrMode !== 0 /* MACFrameAddressMode.NONE */ &&
frameControl.sourceAddrMode === 0 /* MACFrameAddressMode.NONE */ &&
frameControl.panIdCompression) {
destPANPresent = false;
sourcePANPresent = false;
}
else if (frameControl.destAddrMode === 0 /* MACFrameAddressMode.NONE */ &&
frameControl.sourceAddrMode !== 0 /* MACFrameAddressMode.NONE */ &&
!frameControl.panIdCompression) {
destPANPresent = false;
sourcePANPresent = true;
}
else if (frameControl.destAddrMode === 0 /* MACFrameAddressMode.NONE */ &&
frameControl.sourceAddrMode !== 0 /* MACFrameAddressMode.NONE */ &&
frameControl.panIdCompression) {
destPANPresent = false;
sourcePANPresent = false;
}
else if (frameControl.destAddrMode === 3 /* MACFrameAddressMode.EXT */ &&
frameControl.sourceAddrMode === 3 /* MACFrameAddressMode.EXT */ &&
!frameControl.panIdCompression) {
destPANPresent = true;
sourcePANPresent = false;
}
else if (frameControl.destAddrMode === 3 /* MACFrameAddressMode.EXT */ &&
frameControl.sourceAddrMode === 3 /* MACFrameAddressMode.EXT */ &&
frameControl.panIdCompression) {
destPANPresent = false;
sourcePANPresent = false;
}
else if (frameControl.destAddrMode === 2 /* MACFrameAddressMode.SHORT */ &&
frameControl.sourceAddrMode === 2 /* MACFrameAddressMode.SHORT */ &&
!frameControl.panIdCompression) {
destPANPresent = true;
sourcePANPresent = true;
}
else if (frameControl.destAddrMode === 2 /* MACFrameAddressMode.SHORT */ &&
frameControl.sourceAddrMode === 3 /* MACFrameAddressMode.EXT */ &&
!frameControl.panIdCompression) {
destPANPresent = true;
sourcePANPresent = true;
}
else if (frameControl.destAddrMode === 3 /* MACFrameAddressMode.EXT */ &&
frameControl.sourceAddrMode === 2 /* MACFrameAddressMode.SHORT */ &&
!frameControl.panIdCompression) {
destPANPresent = true;
sourcePANPresent = true;
}
else if (frameControl.destAddrMode === 2 /* MACFrameAddressMode.SHORT */ &&
frameControl.sourceAddrMode === 3 /* MACFrameAddressMode.EXT */ &&
frameControl.panIdCompression) {
destPANPresent = true;
sourcePANPresent = false;
}
else if (frameControl.destAddrMode === 3 /* MACFrameAddressMode.EXT */ &&
frameControl.sourceAddrMode === 2 /* MACFrameAddressMode.SHORT */ &&
frameControl.panIdCompression) {
destPANPresent = true;
sourcePANPresent = false;
}
else if (frameControl.destAddrMode === 2 /* MACFrameAddressMode.SHORT */ &&
frameControl.sourceAddrMode === 2 /* MACFrameAddressMode.SHORT */ &&
frameControl.panIdCompression) {
destPANPresent = true;
sourcePANPresent = false;
}
else {
throw new Error("Invalid MAC frame: unexpected PAN ID compression");
}
}
else {
// PAN ID Compression is not used
destPANPresent = false;
sourcePANPresent = false;
}
}
else {
throw new Error("Invalid MAC frame: invalid version");
}
let destination16;
let destination64;
let source16;
let source64;
if (destPANPresent) {
destinationPANId = data.readUInt16LE(offset);
offset += 2;
}
if (frameControl.destAddrMode === 2 /* MACFrameAddressMode.SHORT */) {
destination16 = data.readUInt16LE(offset);
offset += 2;
}
else if (frameControl.destAddrMode === 3 /* MACFrameAddressMode.EXT */) {
destination64 = data.readBigUInt64LE(offset);
offset += 8;
}
if (sourcePANPresent) {
sourcePANId = data.readUInt16LE(offset);
offset += 2;
}
else {
sourcePANId = destPANPresent ? destinationPANId : 65535 /* ZigbeeMACConsts.BCAST_PAN */;
}
if (frameControl.sourceAddrMode === 2 /* MACFrameAddressMode.SHORT */) {
source16 = data.readUInt16LE(offset);
offset += 2;
}
else if (frameControl.sourceAddrMode === 3 /* MACFrameAddressMode.EXT */) {
source64 = data.readBigUInt64LE(offset);
offset += 8;
}
let auxSecHeader;
if (frameControl.securityEnabled &&
/*(frameControl.frameType === MACFrameType.MULTIPURPOSE || */ frameControl.frameVersion === 0 /* MACFrameVersion.V2003 */) {
[auxSecHeader, offset] = decodeMACAuxSecHeader(data, offset, frameControl);
}
let superframeSpec;
let gtsInfo;
let pendAddr;
let commandId;
let headerIE;
if (
/*frameControl.frameType !== MACFrameType.MULTIPURPOSE && */
frameControl.frameVersion === 0 /* MACFrameVersion.V2003 */ ||
frameControl.frameVersion === 1 /* MACFrameVersion.V2006 */) {
if (frameControl.frameType === 0 /* MACFrameType.BEACON */) {
[superframeSpec, offset] = decodeMACSuperframeSpec(data, offset);
[gtsInfo, offset] = decodeMACGtsInfo(data, offset);
[pendAddr, offset] = decodeMACPendAddr(data, offset);
}
else if (frameControl.frameType === 3 /* MACFrameType.CMD */) {
commandId = data.readUInt8(offset);
offset += 1;
}
}
else {
if (frameControl.iePresent) {
[headerIE, offset] = decodeMACHeaderIEs(data, offset, auxSecHeader);
// TODO: headerIE.payloadIEPresent === true, Zigbee OUI?
}
}
let frameCounter;
let keySeqCounter;
if (frameControl.securityEnabled &&
/*frameControl.frameType !== MACFrameType.MULTIPURPOSE && */
frameControl.frameVersion === 0 /* MACFrameVersion.V2003 */) {
// auxSecHeader?.securityLevel = ???;
const isEncrypted = auxSecHeader.securityLevel & 0x04;
if (isEncrypted) {
frameCounter = data.readUInt32LE(offset);
offset += 4;
keySeqCounter = data.readUInt8(offset);
offset += 1;
}
}
if (offset >= data.byteLength) {
throw new Error("Invalid MAC frame: no payload");
}
return [
{
frameControl,
sequenceNumber,
destinationPANId,
destination16,
destination64,
sourcePANId,
source16,
source64,
auxSecHeader,
superframeSpec,
gtsInfo,
pendAddr,
commandId,
headerIE,
frameCounter,
keySeqCounter,
fcs: 0, // set after decoded payload
},
offset,
];
}
function encodeMACHeader(data, offset, header, zigbee) {
offset = encodeMACFrameControl(data, offset, header.frameControl);
if (zigbee) {
data.writeUInt8(header.sequenceNumber, offset);
offset += 1;
data.writeUInt16LE(header.destinationPANId, offset);
offset += 2;
data.writeUInt16LE(header.destination16, offset);
offset += 2;
if (header.sourcePANId !== undefined) {
data.writeUInt16LE(header.sourcePANId, offset);
offset += 2;
}
// NWK GP can be NONE
if (header.frameControl.sourceAddrMode === 2 /* MACFrameAddressMode.SHORT */) {
data.writeUInt16LE(header.source16, offset);
offset += 2;
}
}
else {
if (!header.frameControl.seqNumSuppress) {
data.writeUInt8(header.sequenceNumber, offset);
offset += 1;
}
if (header.frameControl.destAddrMode === 1 /* MACFrameAddressMode.RESERVED */) {
throw new Error(`Invalid MAC frame: destination address mode ${header.frameControl.destAddrMode}`);
}
if (header.frameControl.sourceAddrMode === 1 /* MACFrameAddressMode.RESERVED */) {
throw new Error(`Invalid MAC frame: source address mode ${header.frameControl.sourceAddrMode}`);
}
let destPANPresent = false;
let sourcePANPresent = false;
if (header.frameControl.frameType === 5 /* MACFrameType.MULTIPURPOSE */) {
throw new Error("Unsupported MAC frame: MULTIPURPOSE");
}
if (header.frameControl.frameVersion === 0 /* MACFrameVersion.V2003 */ || header.frameControl.frameVersion === 1 /* MACFrameVersion.V2006 */) {
if (header.frameControl.destAddrMode !== 0 /* MACFrameAddressMode.NONE */ && header.frameControl.sourceAddrMode !== 0 /* MACFrameAddressMode.NONE */) {
// addressing information is present
if (header.frameControl.panIdCompression) {
// PAN IDs are identical
destPANPresent = true;
sourcePANPresent = false;
}
else {
// PAN IDs are different, both shall be included in the frame
destPANPresent = true;
sourcePANPresent = true;
}
}
else {
if (header.frameControl.panIdCompression) {
throw new Error("Invalid MAC frame: unexpected PAN ID compression");
}
// only either the destination or the source addressing information is present
if (header.frameControl.destAddrMode !== 0 /* MACFrameAddressMode.NONE */ &&
header.frameControl.sourceAddrMode === 0 /* MACFrameAddressMode.NONE */) {
destPANPresent = true;
sourcePANPresent = false;
}
else if (header.frameControl.destAddrMode === 0 /* MACFrameAddressMode.NONE */ &&
header.frameControl.sourceAddrMode !== 0 /* MACFrameAddressMode.NONE */) {
destPANPresent = false;
sourcePANPresent = true;
}
else if (header.frameControl.destAddrMode === 0 /* MACFrameAddressMode.NONE */ &&
header.frameControl.sourceAddrMode === 0 /* MACFrameAddressMode.NONE */) {
destPANPresent = false;
sourcePANPresent = false;
}
else {
throw new Error("Invalid MAC frame: invalid addressing");
}
}
}
else if (header.frameControl.frameVersion === 2 /* MACFrameVersion.V2015 */) {
if (header.frameControl.frameType === 0 /* MACFrameType.BEACON */ ||
header.frameControl.frameType === 1 /* MACFrameType.DATA */ ||
header.frameControl.frameType === 2 /* MACFrameType.ACK */ ||
header.frameControl.frameType === 3 /* MACFrameType.CMD */) {
if (header.frameControl.destAddrMode === 0 /* MACFrameAddressMode.NONE */ &&
header.frameControl.sourceAddrMode === 0 /* MACFrameAddressMode.NONE */ &&
!header.frameControl.panIdCompression) {
destPANPresent = false;
sourcePANPresent = false;
}
else if (header.frameControl.destAddrMode === 0 /* MACFrameAddressMode.NONE */ &&
header.frameControl.sourceAddrMode === 0 /* MACFrameAddressMode.NONE */ &&
header.frameControl.panIdCompression) {
destPANPresent = true;
sourcePANPresent = false;
}
else if (header.frameControl.destAddrMode !== 0 /* MACFrameAddressMode.NONE */ &&
header.frameControl.sourceAddrMode === 0 /* MACFrameAddressMode.NONE */ &&
!header.frameControl.panIdCompression) {
destPANPresent = true;
sourcePANPresent = false;
}
else if (header.frameControl.destAddrMode !== 0 /* MACFrameAddressMode.NONE */ &&
header.frameControl.sourceAddrMode === 0 /* MACFrameAddressMode.NONE */ &&
header.frameControl.panIdCompression) {
destPANPresent = false;
sourcePANPresent = false;
}
else if (header.frameControl.destAddrMode === 0 /* MACFrameAddressMode.NONE */ &&
header.frameControl.sourceAddrMode !== 0 /* MACFrameAddressMode.NONE */ &&
!header.frameControl.panIdCompression) {
destPANPresent = false;
sourcePANPresent = true;
}
else if (header.frameControl.destAddrMode === 0 /* MACFrameAddressMode.NONE */ &&
header.frameControl.sourceAddrMode !== 0 /* MACFrameAddressMode.NONE */ &&
header.frameControl.panIdCompression) {
destPANPresent = false;
sourcePANPresent = false;
}
else if (header.frameControl.destAddrMode === 3 /* MACFrameAddressMode.EXT */ &&
header.frameControl.sourceAddrMode === 3 /* MACFrameAddressMode.EXT */ &&
!header.frameControl.panIdCompression) {
destPANPresent = true;
sourcePANPresent = false;
}
else if (header.frameControl.destAddrMode === 3 /* MACFrameAddressMode.EXT */ &&
header.frameControl.sourceAddrMode === 3 /* MACFrameAddressMode.EXT */ &&
header.frameControl.panIdCompression) {
destPANPresent = false;
sourcePANPresent = false;
}
else if (header.frameControl.destAddrMode === 2 /* MACFrameAddressMode.SHORT */ &&
header.frameControl.sourceAddrMode === 2 /* MACFrameAddressMode.SHORT */ &&
!header.frameControl.panIdCompression) {
destPANPresent = true;
sourcePANPresent = true;
}
else if (header.frameControl.destAddrMode === 2 /* MACFrameAddressMode.SHORT */ &&
header.frameControl.sourceAddrMode === 3 /* MACFrameAddressMode.EXT */ &&
!header.frameControl.panIdCompression) {
destPANPresent = true;
sourcePANPresent = true;
}
else if (header.frameControl.destAddrMode === 3 /* MACFrameAddressMode.EXT */ &&
header.frameControl.sourceAddrMode === 2 /* MACFrameAddressMode.SHORT */ &&
!header.frameControl.panIdCompression) {
destPANPresent = true;
sourcePANPresent = true;
}
else if (header.frameControl.destAddrMode === 2 /* MACFrameAddressMode.SHORT */ &&
header.frameControl.sourceAddrMode === 3 /* MACFrameAddressMode.EXT */ &&
header.frameControl.panIdCompression) {
destPANPresent = true;
sourcePANPresent = false;
}
else if (header.frameControl.destAddrMode === 3 /* MACFrameAddressMode.EXT */ &&
header.frameControl.sourceAddrMode === 2 /* MACFrameAddressMode.SHORT */ &&
header.frameControl.panIdCompression) {
destPANPresent = true;
sourcePANPresent = false;
}
else if (header.frameControl.destAddrMode === 2 /* MACFrameAddressMode.SHORT */ &&
header.frameControl.sourceAddrMode === 2 /* MACFrameAddressMode.SHORT */ &&
header.frameControl.panIdCompression) {
destPANPresent = true;
sourcePANPresent = false;
}
else {
throw new Error("Invalid MAC frame: unexpected PAN ID compression");
}
}
else {
// PAN ID Compression is not used
destPANPresent = false;
sourcePANPresent = false;
}
}
else {
throw new Error("Invalid MAC frame: invalid version");
}
if (destPANPresent) {
data.writeUInt16LE(header.destinationPANId, offset);
offset += 2;
}
if (header.frameControl.destAddrMode === 2 /* MACFrameAddressMode.SHORT */) {
data.writeUInt16LE(header.destination16, offset);
offset += 2;
}
else if (header.frameControl.destAddrMode === 3 /* MACFrameAddressMode.EXT */) {
data.writeBigUInt64LE(header.destination64, offset);
offset += 8;
}
if (sourcePANPresent) {
data.writeUInt16LE(header.sourcePANId, offset);
offset += 2;
}
if (header.frameControl.sourceAddrMode === 2 /* MACFrameAddressMode.SHORT */) {
data.writeUInt16LE(header.source16, offset);
offset += 2;
}
else if (header.frameControl.sourceAddrMode === 3 /* MACFrameAddressMode.EXT */) {
data.writeBigUInt64LE(header.source64, offset);
offset += 8;
}
let auxSecHeader;
if (header.frameControl.securityEnabled &&
/*(header.frameControl.frameType === MACFrameType.MULTIPURPOSE || */ header.frameControl.frameVersion === 0 /* MACFrameVersion.V2003 */) {
throw new Error("Unsupported: securityEnabled");
// [auxSecHeader, offset] = encodeMACAuxSecHeader(data, offset, header.frameControl);
}
if (
/*header.frameControl.frameType !== MACFrameType.MULTIPURPOSE && */
header.frameControl.frameVersion === 0 /* MACFrameVersion.V2003 */ ||
header.frameControl.frameVersion === 1 /* MACFrameVersion.V2006 */) {
if (header.frameControl.frameType === 0 /* MACFrameType.BEACON */) {
offset = encodeMACSuperframeSpec(data, offset, header);
offset = encodeMACGtsInfo(data, offset, header);
offset = encodeMACPendAddr(data, offset, header);
}
else if (header.frameControl.frameType === 3 /* MACFrameType.CMD */) {
data.writeUInt8(header.commandId, offset);
offset += 1;
}
}
else {
if (header.frameControl.iePresent) {
throw new Error("Unsupported iePresent");
// offset = encodeMACHeaderIEs(data, offset, auxSecHeader);
}
}
if (header.frameControl.securityEnabled &&
/*header.frameControl.frameType !== MACFrameType.MULTIPURPOSE && */
header.frameControl.frameVersion === 0 /* MACFrameVersion.V2003 */) {
// auxSecHeader?.securityLevel = ???;
const isEncrypted = auxSecHeader.securityLevel & 0x04;
if (isEncrypted) {
data.writeUInt32LE(header.frameCounter, offset);
offset += 4;
data.writeUInt8(header.keySeqCounter, offset);
offset += 1;
}
}
}
return offset;
}
function crc16CCITT(data) {
let fcs = 0x0000;
for (const aByte of data) {
let q = (fcs ^ aByte) & 0x0f;
fcs = (fcs >> 4) ^ (q * 0x1081);
q = (fcs ^ (aByte >> 4)) & 0x0f;
fcs = (fcs >> 4) ^ (q * 0x1081);
}
return fcs;
}
function decryptPayload(data, offset, frameControl) {
if (frameControl.securityEnabled) {
// XXX: not needed for ZigBee
throw new Error("Unsupported MAC: security enabled");
}
const endOffset = data.byteLength - 2 /* ZigbeeMACConsts.FCS_LEN */;
return [data.subarray(offset, endOffset), endOffset];
}
// function encryptPayload(data: Buffer, offset: number): number {}
function decodeMACPayload(data, offset, frameControl, header) {
const [payload, pOutOffset] = decryptPayload(data, offset, frameControl);
if (pOutOffset >= data.byteLength) {
throw new Error("Invalid MAC frame: no FCS");
}
header.fcs = data.readUInt16LE(pOutOffset);
return payload;
}
function encodeMACFrame(header, payload) {
let offset = 0;
const data = Buffer.alloc(102 /* ZigbeeMACConsts.PAYLOAD_MAX_SAFE_SIZE */);
offset = encodeMACHeader(data, offset, header, false);
data.set(payload, offset);
offset += payload.byteLength;
data.writeUInt16LE(crc16CCITT(data.subarray(0, offset)), offset);
offset += 2;
return data.subarray(0, offset);
}
/** Encode MAC frame with hotpath for Zigbee NWK/APS payload */
function encodeMACFrameZigbee(header, payload) {
let offset = 0;
const data = Buffer.alloc(102 /* ZigbeeMACConsts.PAYLOAD_MAX_SAFE_SIZE */); // TODO: optimize with max ZigBee header length
// always transmit with v2003 (0) frame version @see D.6 Frame Version Value of 05-3474-23
header.frameControl.frameVersion = 0 /* MACFrameVersion.V2003 */;
offset = encodeMACHeader(data, offset, header, true); // zigbee hotpath
data.set(payload, offset);
offset += payload.byteLength;
data.writeUInt16LE(crc16CCITT(data.subarray(0, offset)), offset);
offset += 2;
return data.subarray(0, offset);
}
function decodeMACZigbeeBeacon(data, offset) {
const protocolId = data.readUInt8(offset);
offset += 1;
const beacon = data.readUInt16LE(offset);
offset += 2;
const profile = beacon & 15 /* ZigbeeMACConsts.ZIGBEE_BEACON_STACK_PROFILE_MASK */;
const version = (beacon & 240 /* ZigbeeMACConsts.ZIGBEE_BEACON_PROTOCOL_VERSION_MASK */) >> 4 /* ZigbeeMACConsts.ZIGBEE_BEACON_PROTOCOL_VERSION_SHIFT */;
const routerCapacity = Boolean((beacon & 1024 /* ZigbeeMACConsts.ZIGBEE_BEACON_ROUTER_CAPACITY_MASK */) >> 10 /* ZigbeeMACConsts.ZIGBEE_BEACON_ROUTER_CAPACITY_SHIFT */);
const deviceDepth = (beacon & 30720 /* ZigbeeMACConsts.ZIGBEE_BEACON_NETWORK_DEPTH_MASK */) >> 11 /* ZigbeeMACConsts.ZIGBEE_BEACON_NETWORK_DEPTH_SHIFT */;
const endDeviceCapacity = Boolean((beacon & 32768 /* ZigbeeMACConsts.ZIGBEE_BEACON_END_DEVICE_CAPACITY_MASK */) >> 15 /* ZigbeeMACConsts.ZIGBEE_BEACON_END_DEVICE_CAPACITY_SHIFT */);
const extendedPANId = data.readBigUInt64LE(offset);
offset += 8;
const endBytes = data.readUInt32LE(offset);
const txOffset = endBytes & 16777215 /* ZigbeeMACConsts.ZIGBEE_BEACON_TX_OFFSET_MASK */;
const updateId = (endBytes & 255 /* ZigbeeMACConsts.ZIGBEE_BEACON_UPDATE_ID_MASK */) >> 24 /* ZigbeeMACConsts.ZIGBEE_BEACON_UPDATE_ID_SHIFT */;
return {
protocolId,
profile,
version,
routerCapacity,
deviceDepth,
endDeviceCapacity,
extendedPANId,
txOffset,
updateId,
};
}
function encodeMACZigbeeBeacon(beacon) {
const payload = Buffer.alloc(15 /* ZigbeeMACConsts.ZIGBEE_BEACON_LENGTH */);
let offset = 0;
payload.writeUInt8(0, offset); // protocol ID always 0 on Zigbee beacons
offset += 1;
payload.writeUInt16LE((beacon.profile & 15 /* ZigbeeMACConsts.ZIGBEE_BEACON_STACK_PROFILE_MASK */) |
((beacon.version << 4 /* ZigbeeMACConsts.ZIGBEE_BEACON_PROTOCOL_VERSION_SHIFT */) & 240 /* ZigbeeMACConsts.ZIGBEE_BEACON_PROTOCOL_VERSION_MASK */) |
(((beacon.routerCapacity ? 1 : 0) << 10 /* ZigbeeMACConsts.ZIGBEE_BEACON_ROUTER_CAPACITY_SHIFT */) &
1024 /* ZigbeeMACConsts.ZIGBEE_BEACON_ROUTER_CAPACITY_MASK */) |
((beacon.deviceDepth << 11 /* ZigbeeMACConsts.ZIGBEE_BEACON_NETWORK_DEPTH_SHIFT */) & 30720 /* ZigbeeMACConsts.ZIGBEE_BEACON_NETWORK_DEPTH_MASK */) |
(((beacon.endDeviceCapacity ? 1 : 0) << 15 /* ZigbeeMACConsts.ZIGBEE_BEACON_END_DEVICE_CAPACITY_SHIFT */) &
32768 /* ZigbeeMACConsts.ZIGBEE_BEACON_END_DEVICE_CAPACITY_MASK */), offset);
offset += 2;
payload.writeBigUInt64LE(beacon.extendedPANId, offset);
offset += 8;
payload.writeUInt32LE((beacon.txOffset & 16777215 /* ZigbeeMACConsts.ZIGBEE_BEACON_TX_OFFSET_MASK */) |
((beacon.updateId << 24 /* ZigbeeMACConsts.ZIGBEE_BEACON_UPDATE_ID_SHIFT */) & 255 /* ZigbeeMACConsts.ZIGBEE_BEACON_UPDATE_ID_MASK */), offset);
return payload;
}
// #endregion
//# sourceMappingURL=mac.js.map