UNPKG

zigbee-on-host

Version:

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

169 lines 7.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.NWKGPHandler = void 0; const logger_js_1 = require("../utils/logger.js"); const NS = "nwk-gp-handler"; /** Duration while duplicate table entries remain valid (milliseconds). */ const CONFIG_NWK_GP_DUPLICATE_TIMEOUT_MS = 2000; /** * NWK GP Handler - Zigbee Green Power Network Layer */ class NWKGPHandler { #callbacks; #commissioningMode = false; #commissioningWindowTimeout; /** Recently seen frames for duplicate rejection by source ID */ #duplicateTableId = new Map(); /** Recently seen frames for duplicate rejection by source 64 + endpoint */ #duplicateTable64 = new Map(); constructor(callbacks) { this.#callbacks = callbacks; } async start() { } stop() { this.exitCommissioningMode(); this.#duplicateTableId.clear(); this.#duplicateTable64.clear(); } /** * 14-0563-19 #A.3.8.2 (Commissioning mode control) * * Put the coordinator in Green Power commissioning mode. * * SPEC COMPLIANCE NOTES: * - ✅ Enables commissioning for bounded window (defaults to 180 s, clamped to 0xfe) * - ✅ Clears existing timer before starting new session to avoid overlap * - ✅ Logs entry for diagnostics; required commands are gated on this flag elsewhere * - ⚠️ Does not broadcast commissioning state to sinks; assumes host-only coordination * * @param commissioningWindow Defaults to 180 if unspecified. Max 254. 0 means exit. */ enterCommissioningMode(commissioningWindow = 180) { if (commissioningWindow > 0) { clearTimeout(this.#commissioningWindowTimeout); this.#commissioningMode = true; this.#commissioningWindowTimeout = setTimeout(this.exitCommissioningMode.bind(this), Math.min(commissioningWindow, 0xfe) * 1000); logger_js_1.logger.info(`Entered Green Power commissioning mode for ${commissioningWindow} seconds`, NS); } else { this.exitCommissioningMode(); } } /** * 14-0563-19 #A.3.8.2 (Commissioning mode control) * * SPEC COMPLIANCE NOTES: * - ✅ Cancels active commissioning timer and resets state flag * - ✅ Logs exit for diagnostics, matching spec recommendation for operator visibility * - ✅ Clears duplicate tables only when stop() invoked to preserve replay protection during window * - ⚠️ Additional cleanup (e.g., pending key distribution) not yet triggered here */ exitCommissioningMode() { clearTimeout(this.#commissioningWindowTimeout); this.#commissioningWindowTimeout = undefined; this.#commissioningMode = false; logger_js_1.logger.info("Exited Green Power commissioning mode", NS); } /** * 14-0563-19 #A.3.5.2 (Duplicate filtering) * * SPEC COMPLIANCE NOTES: * - ✅ Maintains per-source security frame counter as primary replay protection metric * - ✅ Falls back to MAC sequence number when security not in use, per spec guidance * - ✅ Applies timeout window to age entries out of duplicate tables * - ✅ Handles both source ID and IEEE/endpoint addressing forms * - ⚠️ Does not persist counters across restarts; relies on runtime tables only */ isDuplicateFrame(macHeader, nwkHeader, now = Date.now()) { const hasSourceId = nwkHeader.sourceId !== undefined; if (!hasSourceId && nwkHeader.source64 === undefined) { // skip check if no identifier return false; } // prune expired duplicates, only for relevant table to avoid pointless looping for current frame if (hasSourceId) { for (const [key, entry] of this.#duplicateTableId) { if (entry.expiresAt <= now) { this.#duplicateTableId.delete(key); } } } else { for (const [key, entry] of this.#duplicateTable64) { if (entry.expiresAt <= now) { this.#duplicateTable64.delete(key); } } } const entry = hasSourceId ? this.#duplicateTableId.get(nwkHeader.sourceId) : this.#duplicateTable64.get(`${nwkHeader.source64}-${nwkHeader.endpoint ?? 0xff}`); if (nwkHeader.securityFrameCounter !== undefined) { if (entry?.securityFrameCounter !== undefined && nwkHeader.securityFrameCounter <= entry.securityFrameCounter) { return true; } const newEntry = { securityFrameCounter: nwkHeader.securityFrameCounter, macSequenceNumber: macHeader.sequenceNumber, expiresAt: now + CONFIG_NWK_GP_DUPLICATE_TIMEOUT_MS, }; if (hasSourceId) { this.#duplicateTableId.set(nwkHeader.sourceId, newEntry); } else { this.#duplicateTable64.set(`${nwkHeader.source64}-${nwkHeader.endpoint ?? 0xff}`, newEntry); } return false; } if (macHeader.sequenceNumber === undefined) { return false; } if (entry?.macSequenceNumber !== undefined && macHeader.sequenceNumber === entry.macSequenceNumber) { return true; } const newEntry = { macSequenceNumber: macHeader.sequenceNumber, expiresAt: now + CONFIG_NWK_GP_DUPLICATE_TIMEOUT_MS, }; if (hasSourceId) { this.#duplicateTableId.set(nwkHeader.sourceId, newEntry); } else { this.#duplicateTable64.set(`${nwkHeader.source64}-${nwkHeader.endpoint ?? 0xff}`, newEntry); } return false; } /** * 14-0563-19 (Green Power) #A.3.8.2 * * SPEC COMPLIANCE NOTES: * - ✅ Parses NWK GP command identifier and forwards payload to Stack callbacks * - ✅ Enforces commissioning-mode requirement for commissioning/success/channel request commands * - ✅ Applies duplicate filtering prior to forwarding (isDuplicateFrame) * - ⚠️ Does not validate security parameters beyond duplicate table (future enhancement) * - ⚠️ TLV decoding delegated to consumer (payload forwarded raw) * * @param data * @param macHeader * @param nwkHeader * @param rssi * @returns */ processFrame(data, macHeader, nwkHeader, lqa) { let offset = 0; const cmdId = data.readUInt8(offset); offset += 1; const framePayload = data.subarray(offset); if (!this.#commissioningMode && (cmdId === 224 /* ZigbeeNWKGPCommandId.COMMISSIONING */ || cmdId === 226 /* ZigbeeNWKGPCommandId.SUCCESS */ || cmdId === 227 /* ZigbeeNWKGPCommandId.CHANNEL_REQUEST */)) { logger_js_1.logger.debug(() => `<=~= NWKGP[cmdId=${cmdId} src=${nwkHeader.sourceId}:${macHeader.source64}] Not in commissioning mode`, NS); return; } logger_js_1.logger.debug(() => `<=== NWKGP[cmdId=${cmdId} src=${nwkHeader.sourceId}:${macHeader.source64}]`, NS); setImmediate(() => { this.#callbacks.onGPFrame(cmdId, framePayload, macHeader, nwkHeader, lqa); }); } } exports.NWKGPHandler = NWKGPHandler; //# sourceMappingURL=nwk-gp-handler.js.map