UNPKG

zigbee-on-host

Version:

ZigBee stack designed to run on a host and communicate with a radio co-processor (RCP)

242 lines 12 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.decodeZigbeeNWKGPFrameControl = decodeZigbeeNWKGPFrameControl; exports.decodeZigbeeNWKGPHeader = decodeZigbeeNWKGPHeader; exports.decodeZigbeeNWKGPPayload = decodeZigbeeNWKGPPayload; exports.encodeZigbeeNWKGPFrame = encodeZigbeeNWKGPFrame; const zigbee_js_1 = require("./zigbee.js"); function decodeZigbeeNWKGPFrameControl(data, offset) { const fcf = data.readUInt8(offset); offset += 1; return [ { frameType: fcf & 3 /* ZigbeeNWKGPConsts.FCF_FRAME_TYPE */, protocolVersion: (fcf & 60 /* ZigbeeNWKGPConsts.FCF_VERSION */) >> 2, autoCommissioning: Boolean((fcf & 64 /* ZigbeeNWKGPConsts.FCF_AUTO_COMMISSIONING */) >> 6), nwkFrameControlExtension: Boolean((fcf & 128 /* ZigbeeNWKGPConsts.FCF_CONTROL_EXTENSION */) >> 7), }, offset, ]; } function encodeZigbeeNWKGPFrameControl(data, offset, fcf) { data.writeUInt8((fcf.frameType & 3 /* ZigbeeNWKGPConsts.FCF_FRAME_TYPE */) | ((fcf.protocolVersion << 2) & 60 /* ZigbeeNWKGPConsts.FCF_VERSION */) | (((fcf.autoCommissioning ? 1 : 0) << 6) & 64 /* ZigbeeNWKGPConsts.FCF_AUTO_COMMISSIONING */) | (((fcf.nwkFrameControlExtension ? 1 : 0) << 7) & 128 /* ZigbeeNWKGPConsts.FCF_CONTROL_EXTENSION */), offset); offset += 1; return offset; } function decodeZigbeeNWKGPFrameControlExt(data, offset) { const fcf = data.readUInt8(offset); offset += 1; return [ { appId: fcf & 7 /* ZigbeeNWKGPConsts.FCF_EXT_APP_ID */, securityLevel: (fcf & 24 /* ZigbeeNWKGPConsts.FCF_EXT_SECURITY_LEVEL */) >> 3, securityKey: Boolean((fcf & 32 /* ZigbeeNWKGPConsts.FCF_EXT_SECURITY_KEY */) >> 5), rxAfterTx: Boolean((fcf & 64 /* ZigbeeNWKGPConsts.FCF_EXT_RX_AFTER_TX */) >> 6), direction: (fcf & 128 /* ZigbeeNWKGPConsts.FCF_EXT_DIRECTION */) >> 7, }, offset, ]; } function encodeZigbeeNWKGPFrameControlExt(data, offset, fcExt) { data.writeUInt8((fcExt.appId & 7 /* ZigbeeNWKGPConsts.FCF_EXT_APP_ID */) | ((fcExt.securityLevel << 3) & 24 /* ZigbeeNWKGPConsts.FCF_EXT_SECURITY_LEVEL */) | (((fcExt.securityKey ? 1 : 0) << 5) & 32 /* ZigbeeNWKGPConsts.FCF_EXT_SECURITY_KEY */) | (((fcExt.rxAfterTx ? 1 : 0) << 6) & 64 /* ZigbeeNWKGPConsts.FCF_EXT_RX_AFTER_TX */) | ((fcExt.direction << 7) & 128 /* ZigbeeNWKGPConsts.FCF_EXT_DIRECTION */), offset); offset += 1; return offset; } function decodeZigbeeNWKGPHeader(data, offset, frameControl) { let frameControlExt; if (frameControl.nwkFrameControlExtension) { [frameControlExt, offset] = decodeZigbeeNWKGPFrameControlExt(data, offset); } let sourceId; let endpoint; let micSize = 0; let securityFrameCounter; let mic; if ((frameControl.frameType === 0 /* ZigbeeNWKGPFrameType.DATA */ && !frameControl.nwkFrameControlExtension) || (frameControl.frameType === 0 /* ZigbeeNWKGPFrameType.DATA */ && frameControl.nwkFrameControlExtension && frameControlExt.appId === 0 /* ZigbeeNWKGPAppId.DEFAULT */) || (frameControl.frameType === 1 /* ZigbeeNWKGPFrameType.MAINTENANCE */ && frameControl.nwkFrameControlExtension && frameControlExt.appId === 0 /* ZigbeeNWKGPAppId.DEFAULT */ && data.readUInt8(offset) !== 243 /* ZigbeeNWKGPCommandId.CHANNEL_CONFIGURATION */)) { sourceId = data.readUInt32LE(offset); offset += 4; } if (frameControl.nwkFrameControlExtension && frameControlExt.appId === 2 /* ZigbeeNWKGPAppId.ZGP */) { endpoint = data.readUInt8(offset); offset += 1; } if (frameControl.nwkFrameControlExtension && (frameControlExt.appId === 0 /* ZigbeeNWKGPAppId.DEFAULT */ || frameControlExt.appId === 2 /* ZigbeeNWKGPAppId.ZGP */ || frameControlExt.appId === 1 /* ZigbeeNWKGPAppId.LPED */)) { if (frameControlExt.securityLevel === 1 /* ZigbeeNWKGPSecurityLevel.ONELSB */ && frameControlExt.appId !== 1 /* ZigbeeNWKGPAppId.LPED */) { micSize = 2; } else if (frameControlExt.securityLevel === 2 /* ZigbeeNWKGPSecurityLevel.FULL */ || frameControlExt.securityLevel === 3 /* ZigbeeNWKGPSecurityLevel.FULLENCR */) { micSize = 4; securityFrameCounter = data.readUInt32LE(offset); offset += 4; } } //-- here `offset` is "start of payload" const payloadLength = data.byteLength - offset - micSize; if (payloadLength <= 0) { throw new Error("Zigbee NWK GP frame without payload"); } if (micSize === 2) { mic = data.readUInt16LE(offset + payloadLength); // at end } else if (micSize === 4) { mic = data.readUInt32LE(offset + payloadLength); // at end } return [ { frameControl, frameControlExt, sourceId, endpoint, micSize, securityFrameCounter, payloadLength, mic, }, offset, ]; } function encodeZigbeeNWKGPHeader(data, offset, header) { offset = encodeZigbeeNWKGPFrameControl(data, offset, header.frameControl); if (header.frameControl.nwkFrameControlExtension) { offset = encodeZigbeeNWKGPFrameControlExt(data, offset, header.frameControlExt); } if ((header.frameControl.frameType === 0 /* ZigbeeNWKGPFrameType.DATA */ && !header.frameControl.nwkFrameControlExtension) || (header.frameControl.frameType === 0 /* ZigbeeNWKGPFrameType.DATA */ && header.frameControl.nwkFrameControlExtension && header.frameControlExt.appId === 0 /* ZigbeeNWKGPAppId.DEFAULT */) || (header.frameControl.frameType === 1 /* ZigbeeNWKGPFrameType.MAINTENANCE */ && header.frameControl.nwkFrameControlExtension && header.frameControlExt.appId === 0 /* ZigbeeNWKGPAppId.DEFAULT */ && data.readUInt8(offset) !== 243 /* ZigbeeNWKGPCommandId.CHANNEL_CONFIGURATION */)) { data.writeUInt32LE(header.sourceId, offset); offset += 4; } if (header.frameControl.nwkFrameControlExtension && header.frameControlExt.appId === 2 /* ZigbeeNWKGPAppId.ZGP */) { data.writeUInt8(header.endpoint, offset); offset += 1; } if (header.frameControl.nwkFrameControlExtension && (header.frameControlExt.appId === 0 /* ZigbeeNWKGPAppId.DEFAULT */ || header.frameControlExt.appId === 2 /* ZigbeeNWKGPAppId.ZGP */ || header.frameControlExt.appId === 1 /* ZigbeeNWKGPAppId.LPED */)) { if (header.frameControlExt.securityLevel === 2 /* ZigbeeNWKGPSecurityLevel.FULL */ || header.frameControlExt.securityLevel === 3 /* ZigbeeNWKGPSecurityLevel.FULLENCR */) { data.writeUInt32LE(header.securityFrameCounter, offset); offset += 4; } } //-- here `offset` is "start of payload" return offset; } function makeGPNonce(header, macSource64) { const nonce = Buffer.alloc(13 /* ZigbeeConsts.SEC_NONCE_LEN */); let offset = 0; if (header.frameControlExt.appId === 0 /* ZigbeeNWKGPAppId.DEFAULT */) { if (header.frameControlExt.direction === 0 /* ZigbeeNWKGPDirection.DIRECTION_FROM_ZGPD */) { nonce.writeUInt32LE(header.sourceId, offset); offset += 4; } nonce.writeUInt32LE(header.sourceId, offset); offset += 4; } else if (header.frameControlExt.appId === 2 /* ZigbeeNWKGPAppId.ZGP */) { nonce.writeBigUInt64LE(macSource64, offset); offset += 8; } nonce.writeUInt32LE(header.securityFrameCounter, offset); offset += 4; if (header.frameControlExt.appId === 2 /* ZigbeeNWKGPAppId.ZGP */ && header.frameControlExt.direction === 0 /* ZigbeeNWKGPDirection.DIRECTION_FROM_ZGPD */) { // Security level = 0b101, Key Identifier = 0x00, Extended nonce = 0b0, Reserved = 0b00 nonce.writeUInt8(0xc5, offset); offset += 1; } else { // Security level = 0b101, Key Identifier = 0x00, Extended nonce = 0b0, Reserved = 0b11 nonce.writeUInt8(0x05, offset); offset += 1; } return nonce; } function decodeZigbeeNWKGPPayload(data, offset, decryptKey, macSource64, _frameControl, header) { let authTag; let decryptedPayload; if (header.frameControlExt?.securityLevel === 3 /* ZigbeeNWKGPSecurityLevel.FULLENCR */) { const nonce = makeGPNonce(header, macSource64); [authTag, decryptedPayload] = (0, zigbee_js_1.aes128CcmStar)(header.micSize, decryptKey, nonce, data.subarray(offset)); const computedAuthTag = (0, zigbee_js_1.computeAuthTag)(data.subarray(0, offset), header.micSize, decryptKey, nonce, decryptedPayload); if (!computedAuthTag.equals(authTag)) { throw new Error("Auth tag mismatch while decrypting Zigbee NWK GP payload with FULLENCR security level"); } } else if (header.frameControlExt?.securityLevel === 2 /* ZigbeeNWKGPSecurityLevel.FULL */) { // TODO: Works against spec test vectors but not actual sniffed frame... // const nonce = makeGPNonce(header, macSource64); // [authTag] = aes128CcmStar(header.micSize, decryptKey, nonce, data.subarray(offset)); // const computedAuthTag = computeAuthTag(data.subarray(0, offset + header.payloadLength), header.micSize!, decryptKey, nonce, Buffer.alloc(0)); // if (!computedAuthTag.equals(authTag)) { // throw new Error("Auth tag mismatch while decrypting Zigbee NWK GP payload with FULL security level"); // } decryptedPayload = data.subarray(offset, offset + header.payloadLength); // no MIC } else { decryptedPayload = data.subarray(offset, offset + header.payloadLength); // no MIC // TODO mic/authTag? } if (!decryptedPayload) { throw new Error("Unable to decrypt Zigbee NWK GP payload"); } return decryptedPayload; } function encodeZigbeeNWKGPFrame(header, payload, decryptKey, macSource64) { let offset = 0; const data = Buffer.alloc(116 /* ZigbeeNWKGPConsts.FRAME_MAX_SIZE */); offset = encodeZigbeeNWKGPHeader(data, offset, header); if (header.frameControlExt?.securityLevel === 3 /* ZigbeeNWKGPSecurityLevel.FULLENCR */) { const nonce = makeGPNonce(header, macSource64); const decryptedData = Buffer.alloc(payload.byteLength + header.micSize); // payload + auth tag decryptedData.set(payload, 0); const computedAuthTag = (0, zigbee_js_1.computeAuthTag)(data.subarray(0, offset), header.micSize, decryptKey, nonce, payload); decryptedData.set(computedAuthTag, payload.byteLength); const [authTag, encryptedPayload] = (0, zigbee_js_1.aes128CcmStar)(header.micSize, decryptKey, nonce, decryptedData); data.set(encryptedPayload, offset); offset += encryptedPayload.byteLength; data.set(authTag, offset); // at end offset += header.micSize; } else if (header.frameControlExt?.securityLevel === 2 /* ZigbeeNWKGPSecurityLevel.FULL */) { const nonce = makeGPNonce(header, macSource64); const decryptedData = Buffer.alloc(payload.byteLength + header.micSize); // payload + auth tag decryptedData.set(payload, 0); data.set(payload, offset); offset += payload.byteLength; const computedAuthTag = (0, zigbee_js_1.computeAuthTag)(data.subarray(0, offset), header.micSize, decryptKey, nonce, Buffer.alloc(0)); decryptedData.set(computedAuthTag, payload.byteLength); const [authTag] = (0, zigbee_js_1.aes128CcmStar)(header.micSize, decryptKey, nonce, decryptedData); data.set(authTag, offset); // at end offset += header.micSize; } else { data.set(payload, offset); offset += payload.byteLength; } return data.subarray(0, offset); } //# sourceMappingURL=zigbee-nwkgp.js.map