From c8cb557a6bd38b90608558c8944c86c11b334d29 Mon Sep 17 00:00:00 2001 From: Nerivec <62446222+Nerivec@users.noreply.github.com> Date: Sat, 14 Sep 2024 20:15:03 +0200 Subject: [PATCH] fix: ZDO spec: improve build/read logic and typing (#1186) --- src/adapter/adapter.ts | 3 +- src/adapter/deconz/adapter/deconzAdapter.ts | 1 + src/adapter/ember/adapter/emberAdapter.ts | 137 +- src/adapter/ember/adapter/oneWaitress.ts | 3 +- src/adapter/ezsp/adapter/ezspAdapter.ts | 3 +- src/adapter/ezsp/driver/driver.ts | 7 +- src/adapter/tstype.ts | 12 - src/adapter/z-stack/adapter/zStackAdapter.ts | 95 +- src/adapter/z-stack/znp/zpiObject.ts | 4 +- src/adapter/zboss/adapter/zbossAdapter.ts | 1 + src/adapter/zigate/adapter/zigateAdapter.ts | 1 + src/controller/greenPower.ts | 24 +- src/zspec/zdo/buffaloZdo.ts | 1190 +++++++++------- src/zspec/zdo/definition/clusters.ts | 2 +- src/zspec/zdo/definition/enums.ts | 11 + src/zspec/zdo/definition/tstypes.ts | 19 +- src/zspec/zdo/index.ts | 1 + src/zspec/zdo/utils.ts | 4 +- test/adapter/ember/emberAdapter.test.ts | 23 +- test/adapter/z-stack/adapter.test.ts | 70 +- test/adapter/z-stack/znp.test.ts | 47 +- test/zspec/zdo/buffalo.test.ts | 1282 ++++++++++-------- test/zspec/zdo/utils.test.ts | 8 +- 23 files changed, 1694 insertions(+), 1254 deletions(-) diff --git a/src/adapter/adapter.ts b/src/adapter/adapter.ts index 1c9bfdc253..753127c025 100644 --- a/src/adapter/adapter.ts +++ b/src/adapter/adapter.ts @@ -21,7 +21,7 @@ interface AdapterEventMap { } abstract class Adapter extends events.EventEmitter { - public readonly greenPowerGroup = 0x0b84; + public hasZdoMessageOverhead: boolean; protected networkOptions: TsType.NetworkOptions; protected adapterOptions: TsType.AdapterOptions; protected serialPortOptions: TsType.SerialPortOptions; @@ -34,6 +34,7 @@ abstract class Adapter extends events.EventEmitter { adapterOptions: TsType.AdapterOptions, ) { super(); + this.hasZdoMessageOverhead = true; this.networkOptions = networkOptions; this.adapterOptions = adapterOptions; this.serialPortOptions = serialPortOptions; diff --git a/src/adapter/deconz/adapter/deconzAdapter.ts b/src/adapter/deconz/adapter/deconzAdapter.ts index f51c968d94..2bb2ffc98b 100644 --- a/src/adapter/deconz/adapter/deconzAdapter.ts +++ b/src/adapter/deconz/adapter/deconzAdapter.ts @@ -57,6 +57,7 @@ class DeconzAdapter extends Adapter { public constructor(networkOptions: NetworkOptions, serialPortOptions: SerialPortOptions, backupPath: string, adapterOptions: AdapterOptions) { super(networkOptions, serialPortOptions, backupPath, adapterOptions); + this.hasZdoMessageOverhead = true; const concurrent = this.adapterOptions && this.adapterOptions.concurrent ? this.adapterOptions.concurrent : 2; diff --git a/src/adapter/ember/adapter/emberAdapter.ts b/src/adapter/ember/adapter/emberAdapter.ts index dd73707c04..33d32b450e 100644 --- a/src/adapter/ember/adapter/emberAdapter.ts +++ b/src/adapter/ember/adapter/emberAdapter.ts @@ -12,7 +12,6 @@ import * as ZSpec from '../../../zspec'; import {EUI64, ExtendedPanId, NodeId, PanId} from '../../../zspec/tstypes'; import * as Zcl from '../../../zspec/zcl'; import * as Zdo from '../../../zspec/zdo'; -import {BuffaloZdo} from '../../../zspec/zdo/buffaloZdo'; import * as ZdoTypes from '../../../zspec/zdo/definition/tstypes'; import {DeviceAnnouncePayload, DeviceJoinedPayload, DeviceLeavePayload, NetworkAddressPayload, ZclPayload} from '../../events'; import SerialPortUtils from '../../serialPortUtils'; @@ -523,9 +522,9 @@ export class EmberAdapter extends Adapter { * @param messageContents The content of the response. */ private async onZDOResponse(apsFrame: EmberApsFrame, sender: NodeId, messageContents: Buffer): Promise { - try { - const payload = BuffaloZdo.readResponse(apsFrame.clusterId, messageContents, true); + const [status, payload] = Zdo.Buffalo.readResponse(this.hasZdoMessageOverhead, apsFrame.clusterId, messageContents); + if (status === Zdo.Status.SUCCESS) { logger.debug(() => `<~~~ [ZDO ${Zdo.ClusterId[apsFrame.clusterId]} from=${sender} ${payload ? JSON.stringify(payload) : 'OK'}]`, NS); this.oneWaitress.resolveZDO(sender, apsFrame, payload); @@ -540,8 +539,8 @@ export class EmberAdapter extends Adapter { ieeeAddr: (payload as ZdoTypes.EndDeviceAnnounce).eui64, } as DeviceAnnouncePayload); } - } catch (error) { - this.oneWaitress.resolveZDO(sender, apsFrame, error); + } else { + this.oneWaitress.resolveZDO(sender, apsFrame, new Zdo.StatusError(status)); } } @@ -1513,43 +1512,6 @@ export class EmberAdapter extends Adapter { return [status, reContext?.result]; } - /** - * Enable local permit join and optionally broadcast the ZDO Mgmt_Permit_Join_req message. - * This API can be called from any device type and still return EMBER_SUCCESS. - * If the API is called from an end device, the permit association bit will just be left off. - * - * @param duration uint8_t The duration that the permit join bit will remain on - * and other devices will be able to join the current network. - * @param broadcastMgmtPermitJoin whether or not to broadcast the ZDO Mgmt_Permit_Join_req message. - * - * @returns status of whether or not permit join was enabled. - * @returns apsFrame Will be null if not broadcasting. - * @returns messageTag The tag passed to ezspSend${x} function. - */ - private async emberPermitJoining( - duration: number, - broadcastMgmtPermitJoin: boolean, - ): Promise<[SLStatus, apsFrame: EmberApsFrame | undefined, messageTag: number | undefined]> { - let status = await this.ezsp.ezspPermitJoining(duration); - let apsFrame: EmberApsFrame | undefined; - let messageTag: number | undefined; - - logger.debug(`Permit joining for ${duration} sec. status=${[status]}`, NS); - - if (broadcastMgmtPermitJoin) { - // `authentication`: TC significance always 1 (zb specs) - const zdoPayload = BuffaloZdo.buildPermitJoining(duration, 1, []); - [status, apsFrame, messageTag] = await this.sendZDORequest( - ZSpec.BroadcastAddress.DEFAULT, - Zdo.ClusterId.PERMIT_JOINING_REQUEST, - zdoPayload, - DEFAULT_APS_OPTIONS, - ); - } - - return [status, apsFrame, messageTag]; - } - /** * Set the trust center policy bitmask using decision. * @param decision @@ -1854,7 +1816,15 @@ export class EmberAdapter extends Adapter { return await this.queue.execute(async () => { this.checkInterpanLock(); - const zdoPayload = BuffaloZdo.buildChannelChangeRequest(newChannel, null); + const zdoPayload = Zdo.Buffalo.buildRequest( + this.hasZdoMessageOverhead, + Zdo.ClusterId.NWK_UPDATE_REQUEST, + [newChannel], + 0xfe, + undefined, + undefined, + undefined, + ); const [status] = await this.sendZDORequest( ZSpec.BroadcastAddress.SLEEPY, Zdo.ClusterId.NWK_UPDATE_REQUEST, @@ -2012,7 +1982,7 @@ export class EmberAdapter extends Adapter { await preJoining(); // `authentication`: TC significance always 1 (zb specs) - const zdoPayload = BuffaloZdo.buildPermitJoining(seconds, 1, []); + const zdoPayload = Zdo.Buffalo.buildRequest(this.hasZdoMessageOverhead, Zdo.ClusterId.PERMIT_JOINING_REQUEST, seconds, 1, []); const [status, apsFrame] = await this.sendZDORequest( networkAddress, Zdo.ClusterId.PERMIT_JOINING_REQUEST, @@ -2034,35 +2004,36 @@ export class EmberAdapter extends Adapter { ); }); } else { - // coordinator-only, or all + // coordinator-only (0), or all return await this.queue.execute(async () => { this.checkInterpanLock(); await preJoining(); - // local permit join if `Coordinator`-only requested, else local + broadcast - const [status] = await this.emberPermitJoining(seconds, networkAddress === ZSpec.COORDINATOR_ADDRESS ? false : true); + const status = await this.ezsp.ezspPermitJoining(seconds); if (status !== SLStatus.OK) { - throw new Error(`[ZDO] Failed permit joining request with status=${SLStatus[status]}.`); + throw new Error(`[ZDO] Failed coordinator permit joining request with status=${SLStatus[status]}.`); } - // NOTE: because Z2M is refreshing the permit join duration early to prevent it from closing - // (every 200sec, even if only opened for 254sec), we can't wait for the stack opened status, - // as it won't trigger again if already opened... so instead we assume it worked - // NOTE2: with EZSP, 255=forever, and 254=max, but since upstream logic uses fixed 254 with interval refresh, - // we can't simply bypass upstream calls if called for "forever" to prevent useless NCP calls (3-4 each time), - // until called with 0 (disable), since we don't know if it was requested for forever or not... - // TLDR: upstream logic change required to allow this - // if (seconds) { - // await this.oneWaitress.startWaitingForEvent( - // {eventName: OneWaitressEvents.STACK_STATUS_NETWORK_OPENED}, - // DEFAULT_ZCL_REQUEST_TIMEOUT, - // '[ZDO] Permit Joining', - // ); - // } else { - // // NOTE: CLOSED stack status is not triggered if the network was not OPENED in the first place, so don't wait for it - // // same kind of problem as described above (upstream always tries to close after start, but EZSP already is) - // } + logger.debug(`Permit joining on coordinator for ${seconds} sec.`, NS); + + // broadcast permit joining ZDO + if (networkAddress === undefined) { + // `authentication`: TC significance always 1 (zb specs) + const zdoPayload = Zdo.Buffalo.buildRequest(this.hasZdoMessageOverhead, Zdo.ClusterId.PERMIT_JOINING_REQUEST, seconds, 1, []); + + const [bcStatus] = await this.sendZDORequest( + ZSpec.BroadcastAddress.DEFAULT, + Zdo.ClusterId.PERMIT_JOINING_REQUEST, + zdoPayload, + DEFAULT_APS_OPTIONS, + ); + + if (bcStatus !== SLStatus.OK) { + // don't throw, coordinator succeeded at least + logger.error(`[ZDO] Failed broadcast permit joining request with status=${SLStatus[bcStatus]}.`, NS); + } + } }); } } @@ -2074,7 +2045,7 @@ export class EmberAdapter extends Adapter { const neighbors: TsType.LQINeighbor[] = []; const request = async (startIndex: number): Promise<[tableEntries: number, entryCount: number]> => { - const zdoPayload = BuffaloZdo.buildLqiTableRequest(startIndex); + const zdoPayload = Zdo.Buffalo.buildRequest(this.hasZdoMessageOverhead, Zdo.ClusterId.LQI_TABLE_REQUEST, startIndex); const [status, apsFrame] = await this.sendZDORequest( networkAddress, Zdo.ClusterId.LQI_TABLE_REQUEST, @@ -2130,7 +2101,7 @@ export class EmberAdapter extends Adapter { const table: TsType.RoutingTableEntry[] = []; const request = async (startIndex: number): Promise<[tableEntries: number, entryCount: number]> => { - const zdoPayload = BuffaloZdo.buildRoutingTableRequest(startIndex); + const zdoPayload = Zdo.Buffalo.buildRequest(this.hasZdoMessageOverhead, Zdo.ClusterId.ROUTING_TABLE_REQUEST, startIndex); const [status, apsFrame] = await this.sendZDORequest( networkAddress, Zdo.ClusterId.ROUTING_TABLE_REQUEST, @@ -2156,7 +2127,7 @@ export class EmberAdapter extends Adapter { for (const entry of result.entryList) { table.push({ destinationAddress: entry.destinationAddress, - status: TsType.RoutingTableStatus[entry.status], // get str value from enum to satisfy upstream's needs + status: entry.status, nextHop: entry.nextHopAddress, }); } @@ -2184,7 +2155,7 @@ export class EmberAdapter extends Adapter { return await this.queue.execute(async () => { this.checkInterpanLock(); - const zdoPayload = BuffaloZdo.buildNodeDescriptorRequest(networkAddress); + const zdoPayload = Zdo.Buffalo.buildRequest(this.hasZdoMessageOverhead, Zdo.ClusterId.NODE_DESCRIPTOR_REQUEST, networkAddress); const [status, apsFrame] = await this.sendZDORequest( networkAddress, Zdo.ClusterId.NODE_DESCRIPTOR_REQUEST, @@ -2220,9 +2191,9 @@ export class EmberAdapter extends Adapter { } /* istanbul ignore else */ - if (result.serverMask.stackComplianceResivion < CURRENT_ZIGBEE_SPEC_REVISION) { + if (result.serverMask.stackComplianceRevision < CURRENT_ZIGBEE_SPEC_REVISION) { // always 0 before rev. 21 where field was added - const rev = result.serverMask.stackComplianceResivion < 21 ? 'pre-21' : result.serverMask.stackComplianceResivion; + const rev = result.serverMask.stackComplianceRevision < 21 ? 'pre-21' : result.serverMask.stackComplianceRevision; logger.warning( `[ZDO] Device '${networkAddress}' is only compliant to revision '${rev}' of the ZigBee specification (current revision: ${CURRENT_ZIGBEE_SPEC_REVISION}).`, @@ -2239,7 +2210,7 @@ export class EmberAdapter extends Adapter { return await this.queue.execute(async () => { this.checkInterpanLock(); - const zdoPayload = BuffaloZdo.buildActiveEndpointsRequest(networkAddress); + const zdoPayload = Zdo.Buffalo.buildRequest(this.hasZdoMessageOverhead, Zdo.ClusterId.ACTIVE_ENDPOINTS_REQUEST, networkAddress); const [status, apsFrame] = await this.sendZDORequest( networkAddress, Zdo.ClusterId.ACTIVE_ENDPOINTS_REQUEST, @@ -2269,7 +2240,12 @@ export class EmberAdapter extends Adapter { return await this.queue.execute(async () => { this.checkInterpanLock(); - const zdoPayload = BuffaloZdo.buildSimpleDescriptorRequest(networkAddress, endpointID); + const zdoPayload = Zdo.Buffalo.buildRequest( + this.hasZdoMessageOverhead, + Zdo.ClusterId.SIMPLE_DESCRIPTOR_REQUEST, + networkAddress, + endpointID, + ); const [status, apsFrame] = await this.sendZDORequest( networkAddress, Zdo.ClusterId.SIMPLE_DESCRIPTOR_REQUEST, @@ -2315,7 +2291,9 @@ export class EmberAdapter extends Adapter { return await this.queue.execute(async () => { this.checkInterpanLock(); - const zdoPayload = BuffaloZdo.buildBindRequest( + const zdoPayload = Zdo.Buffalo.buildRequest( + this.hasZdoMessageOverhead, + Zdo.ClusterId.BIND_REQUEST, sourceIeeeAddress as EUI64, sourceEndpoint, clusterID, @@ -2361,7 +2339,9 @@ export class EmberAdapter extends Adapter { return await this.queue.execute(async () => { this.checkInterpanLock(); - const zdoPayload = BuffaloZdo.buildUnbindRequest( + const zdoPayload = Zdo.Buffalo.buildRequest( + this.hasZdoMessageOverhead, + Zdo.ClusterId.UNBIND_REQUEST, sourceIeeeAddress as EUI64, sourceEndpoint, clusterID, @@ -2399,7 +2379,12 @@ export class EmberAdapter extends Adapter { return await this.queue.execute(async () => { this.checkInterpanLock(); - const zdoPayload = BuffaloZdo.buildLeaveRequest(ieeeAddr as EUI64, Zdo.LeaveRequestFlags.WITHOUT_REJOIN); + const zdoPayload = Zdo.Buffalo.buildRequest( + this.hasZdoMessageOverhead, + Zdo.ClusterId.LEAVE_REQUEST, + ieeeAddr as EUI64, + Zdo.LeaveRequestFlags.WITHOUT_REJOIN, + ); const [status, apsFrame] = await this.sendZDORequest(networkAddress, Zdo.ClusterId.LEAVE_REQUEST, zdoPayload, DEFAULT_APS_OPTIONS); if (status !== SLStatus.OK) { diff --git a/src/adapter/ember/adapter/oneWaitress.ts b/src/adapter/ember/adapter/oneWaitress.ts index 1199dded08..2ae16c5233 100644 --- a/src/adapter/ember/adapter/oneWaitress.ts +++ b/src/adapter/ember/adapter/oneWaitress.ts @@ -126,8 +126,7 @@ export class EmberOneWaitress { if ( sender === waiter.matcher.target && apsFrame.profileId === waiter.matcher.apsFrame.profileId && - apsFrame.clusterId === - (waiter.matcher.responseClusterId != null ? waiter.matcher.responseClusterId : waiter.matcher.apsFrame.clusterId) + apsFrame.clusterId === (waiter.matcher.responseClusterId ?? waiter.matcher.apsFrame.clusterId) ) { clearTimeout(waiter.timer); diff --git a/src/adapter/ezsp/adapter/ezspAdapter.ts b/src/adapter/ezsp/adapter/ezspAdapter.ts index 994acf2c5d..09dabad997 100644 --- a/src/adapter/ezsp/adapter/ezspAdapter.ts +++ b/src/adapter/ezsp/adapter/ezspAdapter.ts @@ -56,6 +56,7 @@ class EZSPAdapter extends Adapter { public constructor(networkOptions: NetworkOptions, serialPortOptions: SerialPortOptions, backupPath: string, adapterOptions: AdapterOptions) { super(networkOptions, serialPortOptions, backupPath, adapterOptions); + this.hasZdoMessageOverhead = true; this.waitress = new Waitress(this.waitressValidator, this.waitressTimeoutFormatter); this.interpanLock = false; @@ -65,7 +66,7 @@ class EZSPAdapter extends Adapter { logger.debug(`Adapter concurrent: ${concurrent}`, NS); this.queue = new Queue(concurrent); - this.driver = new Driver(this.serialPortOptions, this.networkOptions, this.greenPowerGroup, backupPath); + this.driver = new Driver(this.serialPortOptions, this.networkOptions, backupPath); this.driver.on('close', this.onDriverClose.bind(this)); this.driver.on('deviceJoined', this.handleDeviceJoin.bind(this)); this.driver.on('deviceLeft', this.handleDeviceLeft.bind(this)); diff --git a/src/adapter/ezsp/driver/driver.ts b/src/adapter/ezsp/driver/driver.ts index c73d47500d..1c6505cd89 100644 --- a/src/adapter/ezsp/driver/driver.ts +++ b/src/adapter/ezsp/driver/driver.ts @@ -6,6 +6,7 @@ import equals from 'fast-deep-equal/es6'; import {Wait, Waitress} from '../../../utils'; import {logger} from '../../../utils/logger'; +import * as ZSpec from '../../../zspec'; import {Clusters} from '../../../zspec/zcl/definition/cluster'; import {EZSPAdapterBackup} from '../adapter/backup'; import * as TsType from './../../tstype'; @@ -92,7 +93,6 @@ export class Driver extends EventEmitter { // @ts-expect-error XXX: init in startup public ezsp: Ezsp; private nwkOpt: TsType.NetworkOptions; - private greenPowerGroup: number; // @ts-expect-error XXX: init in startup public networkParams: EmberNetworkParameters; // @ts-expect-error XXX: init in startup @@ -114,12 +114,11 @@ export class Driver extends EventEmitter { private serialOpt: TsType.SerialPortOptions; public backupMan: EZSPAdapterBackup; - constructor(serialOpt: TsType.SerialPortOptions, nwkOpt: TsType.NetworkOptions, greenPowerGroup: number, backupPath: string) { + constructor(serialOpt: TsType.SerialPortOptions, nwkOpt: TsType.NetworkOptions, backupPath: string) { super(); this.nwkOpt = nwkOpt; this.serialOpt = serialOpt; - this.greenPowerGroup = greenPowerGroup; this.waitress = new Waitress(this.waitressValidator, this.waitressTimeoutFormatter); this.backupMan = new EZSPAdapterBackup(this, backupPath); } @@ -296,7 +295,7 @@ export class Driver extends EventEmitter { this.multicast = new Multicast(this); await this.multicast.startup([]); - await this.multicast.subscribe(this.greenPowerGroup, 242); + await this.multicast.subscribe(ZSpec.GP_GROUP_ID, ZSpec.GP_ENDPOINT); // await this.multicast.subscribe(1, 901); return result; diff --git a/src/adapter/tstype.ts b/src/adapter/tstype.ts index cdef2e629a..1363ca1d5a 100644 --- a/src/adapter/tstype.ts +++ b/src/adapter/tstype.ts @@ -51,17 +51,6 @@ interface LQI { neighbors: LQINeighbor[]; } -enum RoutingTableStatus { - ACTIVE = 0x0, - DISCOVERY_UNDERWAY = 0x1, - DISCOVERY_FAILED = 0x2, - INACTIVE = 0x3, - VALIDATION_UNDERWAY = 0x4, - RESERVED1 = 0x5, - RESERVED2 = 0x6, - RESERVED3 = 0x7, -} - interface RoutingTableEntry { destinationAddress: number; status: string; @@ -124,5 +113,4 @@ export { StartResult, RoutingTableEntry, AdapterOptions, - RoutingTableStatus, }; diff --git a/src/adapter/z-stack/adapter/zStackAdapter.ts b/src/adapter/z-stack/adapter/zStackAdapter.ts index aaeb5d4657..95ce964801 100644 --- a/src/adapter/z-stack/adapter/zStackAdapter.ts +++ b/src/adapter/z-stack/adapter/zStackAdapter.ts @@ -5,11 +5,12 @@ import debounce from 'debounce'; import * as Models from '../../../models'; import {Queue, Wait, Waitress} from '../../../utils'; import {logger} from '../../../utils/logger'; +import * as ZSpec from '../../../zspec'; import {BroadcastAddress} from '../../../zspec/enums'; import * as Zcl from '../../../zspec/zcl'; +import * as Zdo from '../../../zspec/zdo'; import { ActiveEndpointsResponse, - EndDeviceAnnounce, LQITableEntry, LQITableResponse, NetworkAddressResponse, @@ -33,7 +34,6 @@ import { NodeDescriptor, RoutingTable, RoutingTableEntry, - RoutingTableStatus, SerialPortOptions, SimpleDescriptor, StartResult, @@ -105,6 +105,7 @@ class ZStackAdapter extends Adapter { public constructor(networkOptions: NetworkOptions, serialPortOptions: SerialPortOptions, backupPath: string, adapterOptions: AdapterOptions) { super(networkOptions, serialPortOptions, backupPath, adapterOptions); + this.hasZdoMessageOverhead = false; this.znp = new Znp(this.serialPortOptions.path!, this.serialPortOptions.baudRate!, this.serialPortOptions.rtscts!); this.transactionID = 0; @@ -159,7 +160,7 @@ class ZStackAdapter extends Adapter { this.adapterManager = new ZnpAdapterManager(this, this.znp, { backupPath: this.backupPath, version: this.version.product, - greenPowerGroup: this.greenPowerGroup, + greenPowerGroup: ZSpec.GP_GROUP_ID, networkOptions: this.networkOptions, adapterOptions: this.adapterOptions, }); @@ -762,7 +763,7 @@ class ZStackAdapter extends Adapter { for (const entry of list) { table.push({ destinationAddress: entry.destinationAddress, - status: RoutingTableStatus[entry.status], + status: entry.status, nextHop: entry.nextHopAddress, }); } @@ -899,46 +900,54 @@ class ZStackAdapter extends Adapter { this.emit('deviceJoined', payload); } else if (object.command.name === 'endDeviceAnnceInd') { - const zdoPayload = object.parseZdoPayload(); - const payload: Events.DeviceAnnouncePayload = { - networkAddress: zdoPayload.nwkAddress, - ieeeAddr: zdoPayload.eui64, - }; + const zdoResult = object.parseZdoPayload(); + + /* istanbul ignore else */ + if (Zdo.Buffalo.checkStatus(zdoResult)) { + const payload: Events.DeviceAnnouncePayload = { + networkAddress: zdoResult[1].nwkAddress, + ieeeAddr: zdoResult[1].eui64, + }; - // Only discover routes to end devices, if bit 1 of capabilities === 0 it's an end device. - const isEndDevice = zdoPayload.capabilities.deviceType === 0; - if (isEndDevice) { - if (!this.deviceAnnounceRouteDiscoveryDebouncers.has(payload.networkAddress)) { - // If a device announces multiple times in a very short time, it makes no sense - // to rediscover the route every time. - const debouncer = debounce( - () => { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.queue.execute(async () => { - /* istanbul ignore next */ - this.discoverRoute(payload.networkAddress, false).catch(() => {}); - }, payload.networkAddress); - }, - 60 * 1000, - {immediate: true}, - ); - this.deviceAnnounceRouteDiscoveryDebouncers.set(payload.networkAddress, debouncer); + // Only discover routes to end devices, if bit 1 of capabilities === 0 it's an end device. + const isEndDevice = zdoResult[1].capabilities.deviceType === 0; + if (isEndDevice) { + if (!this.deviceAnnounceRouteDiscoveryDebouncers.has(payload.networkAddress)) { + // If a device announces multiple times in a very short time, it makes no sense + // to rediscover the route every time. + const debouncer = debounce( + () => { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + this.queue.execute(async () => { + /* istanbul ignore next */ + this.discoverRoute(payload.networkAddress, false).catch(() => {}); + }, payload.networkAddress); + }, + 60 * 1000, + {immediate: true}, + ); + this.deviceAnnounceRouteDiscoveryDebouncers.set(payload.networkAddress, debouncer); + } + + const debouncer = this.deviceAnnounceRouteDiscoveryDebouncers.get(payload.networkAddress); + assert(debouncer); + debouncer(); } - const debouncer = this.deviceAnnounceRouteDiscoveryDebouncers.get(payload.networkAddress); - assert(debouncer); - debouncer(); + this.emit('deviceAnnounce', payload); } - - this.emit('deviceAnnounce', payload); } else if (object.command.name === 'nwkAddrRsp') { - const zdoPayload = object.parseZdoPayload(); - const payload: Events.NetworkAddressPayload = { - networkAddress: zdoPayload.nwkAddress, - ieeeAddr: zdoPayload.eui64, - }; + const zdoResult = object.parseZdoPayload(); - this.emit('networkAddress', payload); + /* istanbul ignore else */ + if (Zdo.Buffalo.checkStatus(zdoResult)) { + const payload: Events.NetworkAddressPayload = { + networkAddress: zdoResult[1].nwkAddress, + ieeeAddr: zdoResult[1].eui64, + }; + + this.emit('networkAddress', payload); + } } else if (object.command.name === 'concentratorIndCb') { // Some routers may change short addresses and the announcement // is missed by the coordinator. This can happen when there are @@ -1123,10 +1132,12 @@ class ZStackAdapter extends Adapter { return new Promise((resolve, reject) => { startResult.promise .then((response) => { - try { - resolve(response.parseZdoPayload()); - } catch (error) { - reject(error); + const [status, payload] = response.parseZdoPayload(); + + if (status === Zdo.Status.SUCCESS) { + resolve(payload as T); + } else { + reject(new Zdo.StatusError(status)); } }) .catch(reject); diff --git a/src/adapter/z-stack/znp/zpiObject.ts b/src/adapter/z-stack/znp/zpiObject.ts index 03c703065f..b41b8c62e1 100755 --- a/src/adapter/z-stack/znp/zpiObject.ts +++ b/src/adapter/z-stack/znp/zpiObject.ts @@ -110,10 +110,10 @@ class ZpiObject { ); } - public parseZdoPayload(): T { + public parseZdoPayload(): ReturnType { assertIsMtCmdAreqZdo(this.command); const data = this.command.zdo.convert(this.unpiFrame.data); - return BuffaloZdo.readResponse(this.command.zdo.cluterId, data, false) as T; + return BuffaloZdo.readResponse(false, this.command.zdo.cluterId, data); } public toString(): string { diff --git a/src/adapter/zboss/adapter/zbossAdapter.ts b/src/adapter/zboss/adapter/zbossAdapter.ts index 71a6c0d992..8a1885f7fd 100644 --- a/src/adapter/zboss/adapter/zbossAdapter.ts +++ b/src/adapter/zboss/adapter/zbossAdapter.ts @@ -42,6 +42,7 @@ export class ZBOSSAdapter extends Adapter { adapterOptions: TsType.AdapterOptions, ) { super(networkOptions, serialPortOptions, backupPath, adapterOptions); + this.hasZdoMessageOverhead = false; const concurrent = adapterOptions && adapterOptions.concurrent ? adapterOptions.concurrent : 8; logger.debug(`Adapter concurrent: ${concurrent}`, NS); this.queue = new Queue(concurrent); diff --git a/src/adapter/zigate/adapter/zigateAdapter.ts b/src/adapter/zigate/adapter/zigateAdapter.ts index 5218187909..504e4af930 100644 --- a/src/adapter/zigate/adapter/zigateAdapter.ts +++ b/src/adapter/zigate/adapter/zigateAdapter.ts @@ -44,6 +44,7 @@ class ZiGateAdapter extends Adapter { adapterOptions: TsType.AdapterOptions, ) { super(networkOptions, serialPortOptions, backupPath, adapterOptions); + this.hasZdoMessageOverhead = false; this.joinPermitted = false; this.closing = false; diff --git a/src/controller/greenPower.ts b/src/controller/greenPower.ts index a8590a6bce..69485f0629 100644 --- a/src/controller/greenPower.ts +++ b/src/controller/greenPower.ts @@ -4,6 +4,7 @@ import events from 'events'; import {Adapter, Events as AdapterEvents} from '../adapter'; import {logger} from '../utils/logger'; +import {GP_ENDPOINT, GP_GROUP_ID} from '../zspec/consts'; import {BroadcastAddress} from '../zspec/enums'; import * as Zcl from '../zspec/zcl'; import ZclTransactionSequenceNumber from './helpers/zclTransactionSequenceNumber'; @@ -55,7 +56,7 @@ class GreenPower extends events.EventEmitter { switch ((payload.options >> 5) & 3) { case 0b10: // Groupcast to pre-commissioned GroupID case 0b01: // Groupcast to DGroupID - payload.sinkGroupID = this.adapter.greenPowerGroup; + payload.sinkGroupID = GP_GROUP_ID; break; /* istanbul ignore next */ case 0b00: // Full unicast forwarding @@ -89,11 +90,20 @@ class GreenPower extends events.EventEmitter { // the proxy MAY send it as unicast to selected proxy. // This attempts to mirror logic from commit 92f77cc5. if (dataPayload.wasBroadcast) { - return await this.adapter.sendZclFrameToAll(242, replyFrame, 242, BroadcastAddress.RX_ON_WHEN_IDLE); + return await this.adapter.sendZclFrameToAll(GP_ENDPOINT, replyFrame, GP_ENDPOINT, BroadcastAddress.RX_ON_WHEN_IDLE); } else { const device = Device.byNetworkAddress(frame.payload.gppNwkAddr); assert(device, 'Failed to find green power proxy device'); - return await this.adapter.sendZclFrameToEndpoint(device.ieeeAddr, frame.payload.gppNwkAddr, 242, replyFrame, 10000, false, false, 242); + return await this.adapter.sendZclFrameToEndpoint( + device.ieeeAddr, + frame.payload.gppNwkAddr, + GP_ENDPOINT, + replyFrame, + 10000, + false, + false, + GP_ENDPOINT, + ); } } @@ -151,7 +161,7 @@ class GreenPower extends events.EventEmitter { payloadReply, {}, ); - await this.adapter.sendZclFrameToAll(242, replyFrame, 242, BroadcastAddress.RX_ON_WHEN_IDLE); + await this.adapter.sendZclFrameToAll(GP_ENDPOINT, replyFrame, GP_ENDPOINT, BroadcastAddress.RX_ON_WHEN_IDLE); const payloadPairing = { options: 0b0000000110101000, // Disable encryption @@ -223,7 +233,7 @@ class GreenPower extends events.EventEmitter { {}, ); - await this.adapter.sendZclFrameToAll(242, replyFrame, 242, BroadcastAddress.RX_ON_WHEN_IDLE); + await this.adapter.sendZclFrameToAll(GP_ENDPOINT, replyFrame, GP_ENDPOINT, BroadcastAddress.RX_ON_WHEN_IDLE); break; } /* istanbul ignore next */ @@ -258,11 +268,11 @@ class GreenPower extends events.EventEmitter { ); if (networkAddress === undefined) { - await this.adapter.sendZclFrameToAll(242, frame, 242, BroadcastAddress.RX_ON_WHEN_IDLE); + await this.adapter.sendZclFrameToAll(GP_ENDPOINT, frame, GP_ENDPOINT, BroadcastAddress.RX_ON_WHEN_IDLE); } else { const device = Device.byNetworkAddress(networkAddress); assert(device, 'Failed to find device to permit GP join on'); - await this.adapter.sendZclFrameToEndpoint(device.ieeeAddr, networkAddress, 242, frame, 10000, false, false, 242); + await this.adapter.sendZclFrameToEndpoint(device.ieeeAddr, networkAddress, GP_ENDPOINT, frame, 10000, false, false, GP_ENDPOINT); } } } diff --git a/src/zspec/zdo/buffaloZdo.ts b/src/zspec/zdo/buffaloZdo.ts index f28281eacc..e9dda76214 100644 --- a/src/zspec/zdo/buffaloZdo.ts +++ b/src/zspec/zdo/buffaloZdo.ts @@ -5,7 +5,7 @@ import {ClusterId, EUI64, NodeId, ProfileId} from '../tstypes'; import * as ZSpecUtils from '../utils'; import {ClusterId as ZdoClusterId} from './definition/clusters'; import {CHALLENGE_VALUE_SIZE, CURVE_PUBLIC_POINT_SIZE, MULTICAST_BINDING, UNICAST_BINDING, ZDO_MESSAGE_OVERHEAD} from './definition/consts'; -import {GlobalTLV, LeaveRequestFlags} from './definition/enums'; +import {GlobalTLV, LeaveRequestFlags, RoutingTableStatus} from './definition/enums'; import {Status} from './definition/status'; import { ActiveEndpointsResponse, @@ -71,6 +71,144 @@ const NS = 'zh:zdo:buffalo'; const MAX_BUFFER_SIZE = 255; +interface RequestMap { + [ZdoClusterId.NETWORK_ADDRESS_REQUEST]: [target: EUI64, reportKids: boolean, childStartIndex: number]; + [ZdoClusterId.IEEE_ADDRESS_REQUEST]: [target: NodeId, reportKids: boolean, childStartIndex: number]; + [ZdoClusterId.NODE_DESCRIPTOR_REQUEST]: [target: NodeId, fragmentationParameters?: FragmentationParametersGlobalTLV]; + [ZdoClusterId.POWER_DESCRIPTOR_REQUEST]: [target: NodeId]; + [ZdoClusterId.SIMPLE_DESCRIPTOR_REQUEST]: [target: NodeId, targetEndpoint: number]; + [ZdoClusterId.ACTIVE_ENDPOINTS_REQUEST]: [target: NodeId]; + [ZdoClusterId.MATCH_DESCRIPTORS_REQUEST]: [target: NodeId, profileId: ProfileId, inClusterList: ClusterId[], outClusterList: ClusterId[]]; + [ZdoClusterId.SYSTEM_SERVER_DISCOVERY_REQUEST]: [serverMask: ServerMask]; + [ZdoClusterId.PARENT_ANNOUNCE]: [children: EUI64[]]; + [ZdoClusterId.BIND_REQUEST]: [ + source: EUI64, + sourceEndpoint: number, + clusterId: ClusterId, + type: number, + destination: EUI64, + groupAddress: number, + destinationEndpoint: number, + ]; + [ZdoClusterId.UNBIND_REQUEST]: [ + source: EUI64, + sourceEndpoint: number, + clusterId: ClusterId, + type: number, + destination: EUI64, + groupAddress: number, + destinationEndpoint: number, + ]; + [ZdoClusterId.CLEAR_ALL_BINDINGS_REQUEST]: [tlv: ClearAllBindingsReqEUI64TLV]; + [ZdoClusterId.LQI_TABLE_REQUEST]: [startIndex: number]; + [ZdoClusterId.ROUTING_TABLE_REQUEST]: [startIndex: number]; + [ZdoClusterId.BINDING_TABLE_REQUEST]: [startIndex: number]; + [ZdoClusterId.LEAVE_REQUEST]: [deviceAddress: EUI64, leaveRequestFlags: LeaveRequestFlags]; + [ZdoClusterId.PERMIT_JOINING_REQUEST]: [duration: number, authentication: number, tlvs: TLV[]]; + [ZdoClusterId.NWK_UPDATE_REQUEST]: [ + channels: number[], + duration: number, + count: number | undefined, + nwkUpdateId: number | undefined, + nwkManagerAddr: number | undefined, + ]; + [ZdoClusterId.NWK_ENHANCED_UPDATE_REQUEST]: [ + channelPages: number[], + duration: number, + count: number | undefined, + nwkUpdateId: number | undefined, + nwkManagerAddr: NodeId | undefined, + configurationBitmask: number | undefined, + ]; + [ZdoClusterId.NWK_IEEE_JOINING_LIST_REQUEST]: [startIndex: number]; + [ZdoClusterId.NWK_BEACON_SURVEY_REQUEST]: [tlv: BeaconSurveyConfigurationTLV]; + [ZdoClusterId.START_KEY_NEGOTIATION_REQUEST]: [tlv: Curve25519PublicPointTLV]; + [ZdoClusterId.RETRIEVE_AUTHENTICATION_TOKEN_REQUEST]: [tlv: AuthenticationTokenIdTLV]; + [ZdoClusterId.GET_AUTHENTICATION_LEVEL_REQUEST]: [tlv: TargetIEEEAddressTLV]; + [ZdoClusterId.SET_CONFIGURATION_REQUEST]: [ + nextPanIdChange: NextPanIdChangeGlobalTLV, + nextChannelChange: NextChannelChangeGlobalTLV, + configurationParameters: ConfigurationParametersGlobalTLV, + ]; + [ZdoClusterId.GET_CONFIGURATION_REQUEST]: [tlvIds: number[]]; + [ZdoClusterId.START_KEY_UPDATE_REQUEST]: [ + selectedKeyNegotiationMethod: SelectedKeyNegotiationMethodTLV, + fragmentationParameters: FragmentationParametersGlobalTLV, + ]; + [ZdoClusterId.DECOMMISSION_REQUEST]: [tlv: DeviceEUI64ListTLV]; + [ZdoClusterId.CHALLENGE_REQUEST]: [tlv: APSFrameCounterChallengeTLV]; +} + +interface ResponseMap { + [ZdoClusterId.NETWORK_ADDRESS_RESPONSE]: [Status, NetworkAddressResponse | undefined]; + [ZdoClusterId.IEEE_ADDRESS_RESPONSE]: [Status, IEEEAddressResponse | undefined]; + [ZdoClusterId.NODE_DESCRIPTOR_RESPONSE]: [Status, NodeDescriptorResponse | undefined]; + [ZdoClusterId.POWER_DESCRIPTOR_RESPONSE]: [Status, PowerDescriptorResponse | undefined]; + [ZdoClusterId.SIMPLE_DESCRIPTOR_RESPONSE]: [Status, SimpleDescriptorResponse | undefined]; + [ZdoClusterId.ACTIVE_ENDPOINTS_RESPONSE]: [Status, ActiveEndpointsResponse | undefined]; + [ZdoClusterId.MATCH_DESCRIPTORS_RESPONSE]: [Status, MatchDescriptorsResponse | undefined]; + [ZdoClusterId.END_DEVICE_ANNOUNCE]: [Status, EndDeviceAnnounce | undefined]; + [ZdoClusterId.SYSTEM_SERVER_DISCOVERY_RESPONSE]: [Status, SystemServerDiscoveryResponse | undefined]; + [ZdoClusterId.PARENT_ANNOUNCE_RESPONSE]: [Status, ParentAnnounceResponse | undefined]; + [ZdoClusterId.BIND_RESPONSE]: [Status, void | undefined]; + [ZdoClusterId.UNBIND_RESPONSE]: [Status, void | undefined]; + [ZdoClusterId.CLEAR_ALL_BINDINGS_RESPONSE]: [Status, void | undefined]; + [ZdoClusterId.LQI_TABLE_RESPONSE]: [Status, LQITableResponse | undefined]; + [ZdoClusterId.ROUTING_TABLE_RESPONSE]: [Status, RoutingTableResponse | undefined]; + [ZdoClusterId.BINDING_TABLE_RESPONSE]: [Status, BindingTableResponse | undefined]; + [ZdoClusterId.LEAVE_RESPONSE]: [Status, void | undefined]; + [ZdoClusterId.PERMIT_JOINING_RESPONSE]: [Status, void | undefined]; + [ZdoClusterId.NWK_UPDATE_RESPONSE]: [Status, NwkUpdateResponse | undefined]; + [ZdoClusterId.NWK_ENHANCED_UPDATE_RESPONSE]: [Status, NwkEnhancedUpdateResponse | undefined]; + [ZdoClusterId.NWK_IEEE_JOINING_LIST_RESPONSE]: [Status, NwkIEEEJoiningListResponse | undefined]; + [ZdoClusterId.NWK_UNSOLICITED_ENHANCED_UPDATE_RESPONSE]: [Status, NwkUnsolicitedEnhancedUpdateResponse | undefined]; + [ZdoClusterId.NWK_BEACON_SURVEY_RESPONSE]: [Status, NwkBeaconSurveyResponse | undefined]; + [ZdoClusterId.START_KEY_NEGOTIATION_RESPONSE]: [Status, StartKeyNegotiationResponse | undefined]; + [ZdoClusterId.RETRIEVE_AUTHENTICATION_TOKEN_RESPONSE]: [Status, RetrieveAuthenticationTokenResponse | undefined]; + [ZdoClusterId.GET_AUTHENTICATION_LEVEL_RESPONSE]: [Status, GetAuthenticationLevelResponse | undefined]; + [ZdoClusterId.SET_CONFIGURATION_RESPONSE]: [Status, SetConfigurationResponse | undefined]; + [ZdoClusterId.GET_CONFIGURATION_RESPONSE]: [Status, GetConfigurationResponse | undefined]; + [ZdoClusterId.START_KEY_UPDATE_RESPONSE]: [Status, void | undefined]; + [ZdoClusterId.DECOMMISSION_RESPONSE]: [Status, void | undefined]; + [ZdoClusterId.CHALLENGE_RESPONSE]: [Status, ChallengeResponse | undefined]; + // allow passing number to readResponse() from parsed payload without explicitly converting with `as` + [key: number]: [Status, unknown | undefined]; +} + +interface ValidResponseMap { + [ZdoClusterId.NETWORK_ADDRESS_RESPONSE]: [Status.SUCCESS, NetworkAddressResponse]; + [ZdoClusterId.IEEE_ADDRESS_RESPONSE]: [Status.SUCCESS, IEEEAddressResponse]; + [ZdoClusterId.NODE_DESCRIPTOR_RESPONSE]: [Status.SUCCESS, NodeDescriptorResponse]; + [ZdoClusterId.POWER_DESCRIPTOR_RESPONSE]: [Status.SUCCESS, PowerDescriptorResponse]; + [ZdoClusterId.SIMPLE_DESCRIPTOR_RESPONSE]: [Status.SUCCESS, SimpleDescriptorResponse]; + [ZdoClusterId.ACTIVE_ENDPOINTS_RESPONSE]: [Status.SUCCESS, ActiveEndpointsResponse]; + [ZdoClusterId.MATCH_DESCRIPTORS_RESPONSE]: [Status.SUCCESS, MatchDescriptorsResponse]; + [ZdoClusterId.END_DEVICE_ANNOUNCE]: [Status.SUCCESS, EndDeviceAnnounce]; + [ZdoClusterId.SYSTEM_SERVER_DISCOVERY_RESPONSE]: [Status.SUCCESS, SystemServerDiscoveryResponse]; + [ZdoClusterId.PARENT_ANNOUNCE_RESPONSE]: [Status.SUCCESS, ParentAnnounceResponse]; + [ZdoClusterId.BIND_RESPONSE]: [Status.SUCCESS, void]; + [ZdoClusterId.UNBIND_RESPONSE]: [Status.SUCCESS, void]; + [ZdoClusterId.CLEAR_ALL_BINDINGS_RESPONSE]: [Status.SUCCESS, void]; + [ZdoClusterId.LQI_TABLE_RESPONSE]: [Status.SUCCESS, LQITableResponse]; + [ZdoClusterId.ROUTING_TABLE_RESPONSE]: [Status.SUCCESS, RoutingTableResponse]; + [ZdoClusterId.BINDING_TABLE_RESPONSE]: [Status.SUCCESS, BindingTableResponse]; + [ZdoClusterId.LEAVE_RESPONSE]: [Status.SUCCESS, void]; + [ZdoClusterId.PERMIT_JOINING_RESPONSE]: [Status.SUCCESS, void]; + [ZdoClusterId.NWK_UPDATE_RESPONSE]: [Status.SUCCESS, NwkUpdateResponse]; + [ZdoClusterId.NWK_ENHANCED_UPDATE_RESPONSE]: [Status.SUCCESS, NwkEnhancedUpdateResponse]; + [ZdoClusterId.NWK_IEEE_JOINING_LIST_RESPONSE]: [Status.SUCCESS, NwkIEEEJoiningListResponse]; + [ZdoClusterId.NWK_UNSOLICITED_ENHANCED_UPDATE_RESPONSE]: [Status.SUCCESS, NwkUnsolicitedEnhancedUpdateResponse]; + [ZdoClusterId.NWK_BEACON_SURVEY_RESPONSE]: [Status.SUCCESS, NwkBeaconSurveyResponse]; + [ZdoClusterId.START_KEY_NEGOTIATION_RESPONSE]: [Status.SUCCESS, StartKeyNegotiationResponse]; + [ZdoClusterId.RETRIEVE_AUTHENTICATION_TOKEN_RESPONSE]: [Status.SUCCESS, RetrieveAuthenticationTokenResponse]; + [ZdoClusterId.GET_AUTHENTICATION_LEVEL_RESPONSE]: [Status.SUCCESS, GetAuthenticationLevelResponse]; + [ZdoClusterId.SET_CONFIGURATION_RESPONSE]: [Status.SUCCESS, SetConfigurationResponse]; + [ZdoClusterId.GET_CONFIGURATION_RESPONSE]: [Status.SUCCESS, GetConfigurationResponse]; + [ZdoClusterId.START_KEY_UPDATE_RESPONSE]: [Status.SUCCESS, void]; + [ZdoClusterId.DECOMMISSION_RESPONSE]: [Status.SUCCESS, void]; + [ZdoClusterId.CHALLENGE_RESPONSE]: [Status.SUCCESS, ChallengeResponse]; +} + export class BuffaloZdo extends Buffalo { /** * Set the position of the internal position tracker. @@ -711,7 +849,7 @@ export class BuffaloZdo extends Buffalo { } const nextTLVStart = this.getPosition() + length; - // null == unknown tag + // undefined == unknown tag let tlv: TLV['tlv'] | undefined; if (tagId < GlobalTLV.MANUFACTURER_SPECIFIC) { @@ -753,20 +891,144 @@ export class BuffaloZdo extends Buffalo { //-- REQUESTS + public static buildRequest(hasZdoMessageOverhead: boolean, clusterId: K, ...args: RequestMap[K]): Buffer { + const buffalo = new BuffaloZdo(Buffer.alloc(MAX_BUFFER_SIZE), hasZdoMessageOverhead ? ZDO_MESSAGE_OVERHEAD : 0); + + switch (clusterId) { + case ZdoClusterId.NETWORK_ADDRESS_REQUEST: { + return buffalo.buildNetworkAddressRequest(...(args as RequestMap[ZdoClusterId.NETWORK_ADDRESS_REQUEST])); + } + + case ZdoClusterId.IEEE_ADDRESS_REQUEST: { + return buffalo.buildIeeeAddressRequest(...(args as RequestMap[ZdoClusterId.IEEE_ADDRESS_REQUEST])); + } + + case ZdoClusterId.NODE_DESCRIPTOR_REQUEST: { + return buffalo.buildNodeDescriptorRequest(...(args as RequestMap[ZdoClusterId.NODE_DESCRIPTOR_REQUEST])); + } + + case ZdoClusterId.POWER_DESCRIPTOR_REQUEST: { + return buffalo.buildPowerDescriptorRequest(...(args as RequestMap[ZdoClusterId.POWER_DESCRIPTOR_REQUEST])); + } + + case ZdoClusterId.SIMPLE_DESCRIPTOR_REQUEST: { + return buffalo.buildSimpleDescriptorRequest(...(args as RequestMap[ZdoClusterId.SIMPLE_DESCRIPTOR_REQUEST])); + } + + case ZdoClusterId.ACTIVE_ENDPOINTS_REQUEST: { + return buffalo.buildActiveEndpointsRequest(...(args as RequestMap[ZdoClusterId.ACTIVE_ENDPOINTS_REQUEST])); + } + + case ZdoClusterId.MATCH_DESCRIPTORS_REQUEST: { + return buffalo.buildMatchDescriptorRequest(...(args as RequestMap[ZdoClusterId.MATCH_DESCRIPTORS_REQUEST])); + } + + case ZdoClusterId.SYSTEM_SERVER_DISCOVERY_REQUEST: { + return buffalo.buildSystemServiceDiscoveryRequest(...(args as RequestMap[ZdoClusterId.SYSTEM_SERVER_DISCOVERY_REQUEST])); + } + + case ZdoClusterId.PARENT_ANNOUNCE: { + return buffalo.buildParentAnnounce(...(args as RequestMap[ZdoClusterId.PARENT_ANNOUNCE])); + } + + case ZdoClusterId.BIND_REQUEST: { + return buffalo.buildBindRequest(...(args as RequestMap[ZdoClusterId.BIND_REQUEST])); + } + + case ZdoClusterId.UNBIND_REQUEST: { + return buffalo.buildUnbindRequest(...(args as RequestMap[ZdoClusterId.UNBIND_REQUEST])); + } + + case ZdoClusterId.CLEAR_ALL_BINDINGS_REQUEST: { + return buffalo.buildClearAllBindingsRequest(...(args as RequestMap[ZdoClusterId.CLEAR_ALL_BINDINGS_REQUEST])); + } + + case ZdoClusterId.LQI_TABLE_REQUEST: { + return buffalo.buildLqiTableRequest(...(args as RequestMap[ZdoClusterId.LQI_TABLE_REQUEST])); + } + + case ZdoClusterId.ROUTING_TABLE_REQUEST: { + return buffalo.buildRoutingTableRequest(...(args as RequestMap[ZdoClusterId.ROUTING_TABLE_REQUEST])); + } + + case ZdoClusterId.BINDING_TABLE_REQUEST: { + return buffalo.buildBindingTableRequest(...(args as RequestMap[ZdoClusterId.BINDING_TABLE_REQUEST])); + } + + case ZdoClusterId.LEAVE_REQUEST: { + return buffalo.buildLeaveRequest(...(args as RequestMap[ZdoClusterId.LEAVE_REQUEST])); + } + + case ZdoClusterId.PERMIT_JOINING_REQUEST: { + return buffalo.buildPermitJoining(...(args as RequestMap[ZdoClusterId.PERMIT_JOINING_REQUEST])); + } + + case ZdoClusterId.NWK_UPDATE_REQUEST: { + return buffalo.buildNwkUpdateRequest(...(args as RequestMap[ZdoClusterId.NWK_UPDATE_REQUEST])); + } + + case ZdoClusterId.NWK_ENHANCED_UPDATE_REQUEST: { + return buffalo.buildNwkEnhancedUpdateRequest(...(args as RequestMap[ZdoClusterId.NWK_ENHANCED_UPDATE_REQUEST])); + } + + case ZdoClusterId.NWK_IEEE_JOINING_LIST_REQUEST: { + return buffalo.buildNwkIEEEJoiningListRequest(...(args as RequestMap[ZdoClusterId.NWK_IEEE_JOINING_LIST_REQUEST])); + } + + case ZdoClusterId.NWK_BEACON_SURVEY_REQUEST: { + return buffalo.buildNwkBeaconSurveyRequest(...(args as RequestMap[ZdoClusterId.NWK_BEACON_SURVEY_REQUEST])); + } + + case ZdoClusterId.START_KEY_NEGOTIATION_REQUEST: { + return buffalo.buildStartKeyNegotiationRequest(...(args as RequestMap[ZdoClusterId.START_KEY_NEGOTIATION_REQUEST])); + } + + case ZdoClusterId.RETRIEVE_AUTHENTICATION_TOKEN_REQUEST: { + return buffalo.buildRetrieveAuthenticationTokenRequest(...(args as RequestMap[ZdoClusterId.RETRIEVE_AUTHENTICATION_TOKEN_REQUEST])); + } + + case ZdoClusterId.GET_AUTHENTICATION_LEVEL_REQUEST: { + return buffalo.buildGetAuthenticationLevelRequest(...(args as RequestMap[ZdoClusterId.GET_AUTHENTICATION_LEVEL_REQUEST])); + } + + case ZdoClusterId.SET_CONFIGURATION_REQUEST: { + return buffalo.buildSetConfigurationRequest(...(args as RequestMap[ZdoClusterId.SET_CONFIGURATION_REQUEST])); + } + + case ZdoClusterId.GET_CONFIGURATION_REQUEST: { + return buffalo.buildGetConfigurationRequest(...(args as RequestMap[ZdoClusterId.GET_CONFIGURATION_REQUEST])); + } + + case ZdoClusterId.START_KEY_UPDATE_REQUEST: { + return buffalo.buildStartKeyUpdateRequest(...(args as RequestMap[ZdoClusterId.START_KEY_UPDATE_REQUEST])); + } + + case ZdoClusterId.DECOMMISSION_REQUEST: { + return buffalo.buildDecommissionRequest(...(args as RequestMap[ZdoClusterId.DECOMMISSION_REQUEST])); + } + + case ZdoClusterId.CHALLENGE_REQUEST: { + return buffalo.buildChallengeRequest(...(args as RequestMap[ZdoClusterId.CHALLENGE_REQUEST])); + } + + default: { + throw new Error(`Unsupported request building for cluster ID '${clusterId}'.`); + } + } + } + /** * @see ClusterId.NETWORK_ADDRESS_REQUEST * @param target IEEE address for the request * @param reportKids True to request that the target list their children in the response. [request type = 0x01] * @param childStartIndex The index of the first child to list in the response. Ignored if reportKids is false. */ - public static buildNetworkAddressRequest(target: EUI64, reportKids: boolean, childStartIndex: number): Buffer { - const buffalo = new BuffaloZdo(Buffer.alloc(MAX_BUFFER_SIZE), ZDO_MESSAGE_OVERHEAD); - - buffalo.writeIeeeAddr(target); - buffalo.writeUInt8(reportKids ? 1 : 0); - buffalo.writeUInt8(childStartIndex); + private buildNetworkAddressRequest(target: EUI64, reportKids: boolean, childStartIndex: number): Buffer { + this.writeIeeeAddr(target); + this.writeUInt8(reportKids ? 1 : 0); + this.writeUInt8(childStartIndex); - return buffalo.getWritten(); + return this.getWritten(); } /** @@ -776,24 +1038,20 @@ export class BuffaloZdo extends Buffalo { * @param reportKids True to request that the target list their children in the response. [request type = 0x01] * @param childStartIndex The index of the first child to list in the response. Ignored if reportKids is false. */ - public static buildIeeeAddressRequest(target: NodeId, reportKids: boolean, childStartIndex: number): Buffer { - const buffalo = new BuffaloZdo(Buffer.alloc(MAX_BUFFER_SIZE), ZDO_MESSAGE_OVERHEAD); - - buffalo.writeUInt16(target); - buffalo.writeUInt8(reportKids ? 1 : 0); - buffalo.writeUInt8(childStartIndex); + private buildIeeeAddressRequest(target: NodeId, reportKids: boolean, childStartIndex: number): Buffer { + this.writeUInt16(target); + this.writeUInt8(reportKids ? 1 : 0); + this.writeUInt8(childStartIndex); - return buffalo.getWritten(); + return this.getWritten(); } /** * @see ClusterId.NODE_DESCRIPTOR_REQUEST * @param target NWK address for the request */ - public static buildNodeDescriptorRequest(target: NodeId, fragmentationParameters?: FragmentationParametersGlobalTLV): Buffer { - const buffalo = new BuffaloZdo(Buffer.alloc(MAX_BUFFER_SIZE), ZDO_MESSAGE_OVERHEAD); - - buffalo.writeUInt16(target); + private buildNodeDescriptorRequest(target: NodeId, fragmentationParameters?: FragmentationParametersGlobalTLV): Buffer { + this.writeUInt16(target); if (fragmentationParameters) { let length = 2; @@ -808,22 +1066,20 @@ export class BuffaloZdo extends Buffalo { length += 2; } - buffalo.writeGlobalTLV({tagId: GlobalTLV.FRAGMENTATION_PARAMETERS, length, tlv: fragmentationParameters}); + this.writeGlobalTLV({tagId: GlobalTLV.FRAGMENTATION_PARAMETERS, length, tlv: fragmentationParameters}); } - return buffalo.getWritten(); + return this.getWritten(); } /** * @see ClusterId.POWER_DESCRIPTOR_REQUEST * @param target NWK address for the request */ - public static buildPowerDescriptorRequest(target: NodeId): Buffer { - const buffalo = new BuffaloZdo(Buffer.alloc(MAX_BUFFER_SIZE), ZDO_MESSAGE_OVERHEAD); - - buffalo.writeUInt16(target); + private buildPowerDescriptorRequest(target: NodeId): Buffer { + this.writeUInt16(target); - return buffalo.getWritten(); + return this.getWritten(); } /** @@ -831,25 +1087,21 @@ export class BuffaloZdo extends Buffalo { * @param target NWK address for the request * @param targetEndpoint The endpoint on the destination */ - public static buildSimpleDescriptorRequest(target: NodeId, targetEndpoint: number): Buffer { - const buffalo = new BuffaloZdo(Buffer.alloc(MAX_BUFFER_SIZE), ZDO_MESSAGE_OVERHEAD); - - buffalo.writeUInt16(target); - buffalo.writeUInt8(targetEndpoint); + private buildSimpleDescriptorRequest(target: NodeId, targetEndpoint: number): Buffer { + this.writeUInt16(target); + this.writeUInt8(targetEndpoint); - return buffalo.getWritten(); + return this.getWritten(); } /** * @see ClusterId.ACTIVE_ENDPOINTS_REQUEST * @param target NWK address for the request */ - public static buildActiveEndpointsRequest(target: NodeId): Buffer { - const buffalo = new BuffaloZdo(Buffer.alloc(MAX_BUFFER_SIZE), ZDO_MESSAGE_OVERHEAD); + private buildActiveEndpointsRequest(target: NodeId): Buffer { + this.writeUInt16(target); - buffalo.writeUInt16(target); - - return buffalo.getWritten(); + return this.getWritten(); } /** @@ -859,45 +1111,39 @@ export class BuffaloZdo extends Buffalo { * @param inClusterList List of Input ClusterIDs to be used for matching * @param outClusterList List of Output ClusterIDs to be used for matching */ - public static buildMatchDescriptorRequest(target: NodeId, profileId: ProfileId, inClusterList: ClusterId[], outClusterList: ClusterId[]): Buffer { - const buffalo = new BuffaloZdo(Buffer.alloc(MAX_BUFFER_SIZE), ZDO_MESSAGE_OVERHEAD); - - buffalo.writeUInt16(target); - buffalo.writeUInt16(profileId); - buffalo.writeUInt8(inClusterList.length); - buffalo.writeListUInt16(inClusterList); - buffalo.writeUInt8(outClusterList.length); - buffalo.writeListUInt16(outClusterList); + private buildMatchDescriptorRequest(target: NodeId, profileId: ProfileId, inClusterList: ClusterId[], outClusterList: ClusterId[]): Buffer { + this.writeUInt16(target); + this.writeUInt16(profileId); + this.writeUInt8(inClusterList.length); + this.writeListUInt16(inClusterList); + this.writeUInt8(outClusterList.length); + this.writeListUInt16(outClusterList); - return buffalo.getWritten(); + return this.getWritten(); } /** * @see ClusterId.SYSTEM_SERVER_DISCOVERY_REQUEST * @param serverMask See Table 2-34 for bit assignments. */ - public static buildSystemServiceDiscoveryRequest(serverMask: ServerMask): Buffer { - const buffalo = new BuffaloZdo(Buffer.alloc(MAX_BUFFER_SIZE), ZDO_MESSAGE_OVERHEAD); - - buffalo.writeUInt16(Utils.createServerMask(serverMask)); + private buildSystemServiceDiscoveryRequest(serverMask: ServerMask): Buffer { + this.writeUInt16(Utils.createServerMask(serverMask)); - return buffalo.getWritten(); + return this.getWritten(); } /** * @see ClusterId.PARENT_ANNOUNCE * @param children The IEEE addresses of the children bound to the parent. */ - public static buildParentAnnounce(children: EUI64[]): Buffer { - const buffalo = new BuffaloZdo(Buffer.alloc(MAX_BUFFER_SIZE), ZDO_MESSAGE_OVERHEAD); - - buffalo.writeUInt8(children.length); + private buildParentAnnounce(children: EUI64[]): Buffer { + this.writeUInt8(children.length); for (const child of children) { - buffalo.writeIeeeAddr(child); + this.writeIeeeAddr(child); } - return buffalo.getWritten(); + return this.getWritten(); } /** @@ -911,7 +1157,7 @@ export class BuffaloZdo extends Buffalo { * @param groupAddress The destination address for the binding entry. Group ID for ::MULTICAST_BINDING. * @param destinationEndpoint The destination endpoint for the binding entry. Only if ::UNICAST_BINDING. */ - public static buildBindRequest( + private buildBindRequest( source: EUI64, sourceEndpoint: number, clusterId: ClusterId, @@ -920,28 +1166,26 @@ export class BuffaloZdo extends Buffalo { groupAddress: number, destinationEndpoint: number, ): Buffer { - const buffalo = new BuffaloZdo(Buffer.alloc(MAX_BUFFER_SIZE), ZDO_MESSAGE_OVERHEAD); - - buffalo.writeIeeeAddr(source); - buffalo.writeUInt8(sourceEndpoint); - buffalo.writeUInt16(clusterId); - buffalo.writeUInt8(type); + this.writeIeeeAddr(source); + this.writeUInt8(sourceEndpoint); + this.writeUInt16(clusterId); + this.writeUInt8(type); switch (type) { case UNICAST_BINDING: { - buffalo.writeIeeeAddr(destination); - buffalo.writeUInt8(destinationEndpoint); + this.writeIeeeAddr(destination); + this.writeUInt8(destinationEndpoint); break; } case MULTICAST_BINDING: { - buffalo.writeUInt16(groupAddress); + this.writeUInt16(groupAddress); break; } default: throw new ZdoStatusError(Status.NOT_SUPPORTED); } - return buffalo.getWritten(); + return this.getWritten(); } /** @@ -955,7 +1199,7 @@ export class BuffaloZdo extends Buffalo { * @param groupAddress The destination address for the binding entry. Group ID for ::MULTICAST_BINDING. * @param destinationEndpoint The destination endpoint for the binding entry. Only if ::UNICAST_BINDING. */ - public static buildUnbindRequest( + private buildUnbindRequest( source: EUI64, sourceEndpoint: number, clusterId: ClusterId, @@ -964,82 +1208,72 @@ export class BuffaloZdo extends Buffalo { groupAddress: number, destinationEndpoint: number, ): Buffer { - const buffalo = new BuffaloZdo(Buffer.alloc(MAX_BUFFER_SIZE), ZDO_MESSAGE_OVERHEAD); - - buffalo.writeIeeeAddr(source); - buffalo.writeUInt8(sourceEndpoint); - buffalo.writeUInt16(clusterId); - buffalo.writeUInt8(type); + this.writeIeeeAddr(source); + this.writeUInt8(sourceEndpoint); + this.writeUInt16(clusterId); + this.writeUInt8(type); switch (type) { case UNICAST_BINDING: { - buffalo.writeIeeeAddr(destination); - buffalo.writeUInt8(destinationEndpoint); + this.writeIeeeAddr(destination); + this.writeUInt8(destinationEndpoint); break; } case MULTICAST_BINDING: { - buffalo.writeUInt16(groupAddress); + this.writeUInt16(groupAddress); break; } default: throw new ZdoStatusError(Status.NOT_SUPPORTED); } - return buffalo.getWritten(); + return this.getWritten(); } /** * @see ClusterId.CLEAR_ALL_BINDINGS_REQUEST */ - public static buildClearAllBindingsRequest(tlv: ClearAllBindingsReqEUI64TLV): Buffer { - const buffalo = new BuffaloZdo(Buffer.alloc(MAX_BUFFER_SIZE), ZDO_MESSAGE_OVERHEAD); - + private buildClearAllBindingsRequest(tlv: ClearAllBindingsReqEUI64TLV): Buffer { // ClearAllBindingsReqEUI64TLV: Local: ID: 0x00 - buffalo.writeUInt8(0x00); - buffalo.writeUInt8(tlv.eui64List.length * EUI64_SIZE + 1 - 1); - buffalo.writeUInt8(tlv.eui64List.length); + this.writeUInt8(0x00); + this.writeUInt8(tlv.eui64List.length * EUI64_SIZE + 1 - 1); + this.writeUInt8(tlv.eui64List.length); for (const entry of tlv.eui64List) { - buffalo.writeIeeeAddr(entry); + this.writeIeeeAddr(entry); } - return buffalo.getWritten(); + return this.getWritten(); } /** * @see ClusterId.LQI_TABLE_REQUEST * @param startIndex Starting Index for the requested elements of the Neighbor Table. */ - public static buildLqiTableRequest(startIndex: number): Buffer { - const buffalo = new BuffaloZdo(Buffer.alloc(MAX_BUFFER_SIZE), ZDO_MESSAGE_OVERHEAD); - - buffalo.writeUInt8(startIndex); + private buildLqiTableRequest(startIndex: number): Buffer { + this.writeUInt8(startIndex); - return buffalo.getWritten(); + return this.getWritten(); } /** * @see ClusterId.ROUTING_TABLE_REQUEST * @param startIndex Starting Index for the requested elements of the Neighbor Table. */ - public static buildRoutingTableRequest(startIndex: number): Buffer { - const buffalo = new BuffaloZdo(Buffer.alloc(MAX_BUFFER_SIZE), ZDO_MESSAGE_OVERHEAD); - - buffalo.writeUInt8(startIndex); + private buildRoutingTableRequest(startIndex: number): Buffer { + this.writeUInt8(startIndex); - return buffalo.getWritten(); + return this.getWritten(); } /** * @see ClusterId.BINDING_TABLE_REQUEST * @param startIndex Starting Index for the requested elements of the Neighbor Table. */ - public static buildBindingTableRequest(startIndex: number): Buffer { - const buffalo = new BuffaloZdo(Buffer.alloc(MAX_BUFFER_SIZE), ZDO_MESSAGE_OVERHEAD); + private buildBindingTableRequest(startIndex: number): Buffer { + this.writeUInt8(startIndex); - buffalo.writeUInt8(startIndex); - - return buffalo.getWritten(); + return this.getWritten(); } /** @@ -1048,13 +1282,11 @@ export class BuffaloZdo extends Buffalo { * the EUI64 of a child of the target device to remove that child. * @param leaveRequestFlags A bitmask of leave options. Include ::AND_REJOIN if the target is to rejoin the network immediately after leaving. */ - public static buildLeaveRequest(deviceAddress: EUI64, leaveRequestFlags: LeaveRequestFlags): Buffer { - const buffalo = new BuffaloZdo(Buffer.alloc(MAX_BUFFER_SIZE), ZDO_MESSAGE_OVERHEAD); - - buffalo.writeIeeeAddr(deviceAddress); - buffalo.writeUInt8(leaveRequestFlags); + private buildLeaveRequest(deviceAddress: EUI64, leaveRequestFlags: LeaveRequestFlags): Buffer { + this.writeIeeeAddr(deviceAddress); + this.writeUInt8(leaveRequestFlags); - return buffalo.getWritten(); + return this.getWritten(); } /** @@ -1064,17 +1296,15 @@ export class BuffaloZdo extends Buffalo { * This field SHALL always have a value of 1, indicating a request to change the Trust Center policy. * If a frame is received with a value of 0, it shall be treated as having a value of 1. */ - public static buildPermitJoining(duration: number, authentication: number, tlvs: TLV[]): Buffer { - const buffalo = new BuffaloZdo(Buffer.alloc(MAX_BUFFER_SIZE), ZDO_MESSAGE_OVERHEAD); - - buffalo.writeUInt8(duration); - buffalo.writeUInt8(authentication); + private buildPermitJoining(duration: number, authentication: number, tlvs: TLV[]): Buffer { + this.writeUInt8(duration); + this.writeUInt8(authentication); // BeaconAppendixEncapsulationGlobalTLV // - SupportedKeyNegotiationMethodsGlobalTLV // - FragmentationParametersGlobalTLV - buffalo.writeGlobalTLVs(tlvs); + this.writeGlobalTLVs(tlvs); - return buffalo.getWritten(); + return this.getWritten(); } /** @@ -1093,56 +1323,54 @@ export class BuffaloZdo extends Buffalo { * @param nwkManagerAddr This field SHALL be present only if the duration is set to 0xff, and, where present, * indicates the NWK address for the device with the Network Manager bit set in its Node Descriptor. */ - private static buildNwkUpdateRequest( + private buildNwkUpdateRequest( channels: number[], duration: number, - count: number | null, - nwkUpdateId: number | null, - nwkManagerAddr: number | null, + count: number | undefined, + nwkUpdateId: number | undefined, + nwkManagerAddr: number | undefined, ): Buffer { - const buffalo = new BuffaloZdo(Buffer.alloc(MAX_BUFFER_SIZE), ZDO_MESSAGE_OVERHEAD); + this.writeUInt32(ZSpecUtils.channelsToUInt32Mask(channels)); + this.writeUInt8(duration); - buffalo.writeUInt32(ZSpecUtils.channelsToUInt32Mask(channels)); - buffalo.writeUInt8(duration); - - if (count != null && duration >= 0x00 && duration <= 0x05) { - buffalo.writeUInt8(count); + if (count !== undefined && duration >= 0x00 && duration <= 0x05) { + this.writeUInt8(count); } // TODO: What does "This value is set by the Network Channel Manager prior to sending the message." mean exactly?? // (isn't used/mentioned in EmberZNet, confirmed working if not set at all for channel change) - // for now, allow to bypass with null, otherwise should throw if null and duration passes below conditions (see NwkEnhancedUpdateRequest) - if (nwkUpdateId != null && (duration === 0xfe || duration === 0xff)) { - buffalo.writeUInt8(nwkUpdateId); + // for now, allow to bypass with undefined, otherwise should throw if undefined and duration passes below conditions (see NwkEnhancedUpdateRequest) + if (nwkUpdateId !== undefined && (duration === 0xfe || duration === 0xff)) { + this.writeUInt8(nwkUpdateId); } - if (nwkManagerAddr != null && duration === 0xff) { - buffalo.writeUInt16(nwkManagerAddr); + if (nwkManagerAddr !== undefined && duration === 0xff) { + this.writeUInt16(nwkManagerAddr); } - return buffalo.getWritten(); + return this.getWritten(); } - /** - * Shortcut for @see BuffaloZdo.buildNwkUpdateRequest - */ - public static buildScanChannelsRequest(scanChannels: number[], duration: number, count: number): Buffer { - return BuffaloZdo.buildNwkUpdateRequest(scanChannels, duration, count, null, null); - } + // /** + // * Shortcut for @see BuffaloZdo.buildNwkUpdateRequest + // */ + // private buildScanChannelsRequest(scanChannels: number[], duration: number, count: number): Buffer { + // return this.buildNwkUpdateRequest(scanChannels, duration, count, undefined, undefined); + // } - /** - * Shortcut for @see BuffaloZdo.buildNwkUpdateRequest - */ - public static buildChannelChangeRequest(channel: number, nwkUpdateId: number | null): Buffer { - return BuffaloZdo.buildNwkUpdateRequest([channel], 0xfe, null, nwkUpdateId, null); - } + // /** + // * Shortcut for @see BuffaloZdo.buildNwkUpdateRequest + // */ + // private buildChannelChangeRequest(channel: number, nwkUpdateId: number | undefined): Buffer { + // return this.buildNwkUpdateRequest([channel], 0xfe, undefined, nwkUpdateId, undefined); + // } - /** - * Shortcut for @see BuffaloZdo.buildNwkUpdateRequest - */ - public static buildSetActiveChannelsAndNwkManagerIdRequest(channels: number[], nwkUpdateId: number | null, nwkManagerAddr: NodeId): Buffer { - return BuffaloZdo.buildNwkUpdateRequest(channels, 0xff, null, nwkUpdateId, nwkManagerAddr); - } + // /** + // * Shortcut for @see BuffaloZdo.buildNwkUpdateRequest + // */ + // private buildSetActiveChannelsAndNwkManagerIdRequest(channels: number[], nwkUpdateId: number | undefined, nwkManagerAddr: NodeId): Buffer { + // return this.buildNwkUpdateRequest(channels, 0xff, undefined, nwkUpdateId, nwkManagerAddr); + // } /** * @see ClusterId.NWK_ENHANCED_UPDATE_REQUEST @@ -1172,161 +1400,142 @@ export class BuffaloZdo extends Buffalo { * And in case of Enhanced Active scan EBR shall be sent with EPID filter instead of PJOIN filter. * Bit 1-7: Reserved */ - private static buildNwkEnhancedUpdateRequest( + private buildNwkEnhancedUpdateRequest( channelPages: number[], duration: number, - count: number | null, - nwkUpdateId: number | null, - nwkManagerAddr: NodeId | null, - configurationBitmask: number | null, + count: number | undefined, + nwkUpdateId: number | undefined, + nwkManagerAddr: NodeId | undefined, + configurationBitmask: number | undefined, ): Buffer { - const buffalo = new BuffaloZdo(Buffer.alloc(MAX_BUFFER_SIZE), ZDO_MESSAGE_OVERHEAD); - - buffalo.writeUInt8(channelPages.length); + this.writeUInt8(channelPages.length); for (const channelPage of channelPages) { - buffalo.writeUInt32(channelPage); + this.writeUInt32(channelPage); } - buffalo.writeUInt8(duration); + this.writeUInt8(duration); - if (count != null && duration >= 0x00 && duration <= 0x05) { - buffalo.writeUInt8(count); + if (count !== undefined && duration >= 0x00 && duration <= 0x05) { + this.writeUInt8(count); } - if (nwkUpdateId != null && (duration === 0xfe || duration === 0xff)) { - buffalo.writeUInt8(nwkUpdateId); + if (nwkUpdateId !== undefined && (duration === 0xfe || duration === 0xff)) { + this.writeUInt8(nwkUpdateId); } - if (nwkManagerAddr != null && duration === 0xff) { - buffalo.writeUInt16(nwkManagerAddr); + if (nwkManagerAddr !== undefined && duration === 0xff) { + this.writeUInt16(nwkManagerAddr); } /* istanbul ignore else */ - if (configurationBitmask != null) { - buffalo.writeUInt8(configurationBitmask); + if (configurationBitmask !== undefined) { + this.writeUInt8(configurationBitmask); } - return buffalo.getWritten(); + return this.getWritten(); } - /** - * Shortcut for @see BuffaloZdo.buildNwkEnhancedUpdateRequest - */ - public static buildEnhancedScanChannelsRequest( - channelPages: number[], - duration: number, - count: number, - configurationBitmask: number | null, - ): Buffer { - return BuffaloZdo.buildNwkEnhancedUpdateRequest(channelPages, duration, count, null, null, configurationBitmask); - } + // /** + // * Shortcut for @see BuffaloZdo.buildNwkEnhancedUpdateRequest + // */ + // private buildEnhancedScanChannelsRequest(channelPages: number[], duration: number, count: number, configurationBitmask: number | undefined): Buffer { + // return this.buildNwkEnhancedUpdateRequest(channelPages, duration, count, undefined, undefined, configurationBitmask); + // } - /** - * Shortcut for @see BuffaloZdo.buildNwkEnhancedUpdateRequest - */ - public static buildEnhancedChannelChangeRequest(channelPage: number, nwkUpdateId: number | null, configurationBitmask: number | null): Buffer { - return BuffaloZdo.buildNwkEnhancedUpdateRequest([channelPage], 0xfe, null, nwkUpdateId, null, configurationBitmask); - } + // /** + // * Shortcut for @see BuffaloZdo.buildNwkEnhancedUpdateRequest + // */ + // private buildEnhancedChannelChangeRequest(channelPage: number, nwkUpdateId: number | undefined, configurationBitmask: number | undefined): Buffer { + // return this.buildNwkEnhancedUpdateRequest([channelPage], 0xfe, undefined, nwkUpdateId, undefined, configurationBitmask); + // } - /** - * Shortcut for @see BuffaloZdo.buildNwkEnhancedUpdateRequest - */ - public static buildEnhancedSetActiveChannelsAndNwkManagerIdRequest( - channelPages: number[], - nwkUpdateId: number | null, - nwkManagerAddr: NodeId, - configurationBitmask: number | null, - ): Buffer { - return BuffaloZdo.buildNwkEnhancedUpdateRequest(channelPages, 0xff, null, nwkUpdateId, nwkManagerAddr, configurationBitmask); - } + // /** + // * Shortcut for @see BuffaloZdo.buildNwkEnhancedUpdateRequest + // */ + // private buildEnhancedSetActiveChannelsAndNwkManagerIdRequest( + // channelPages: number[], + // nwkUpdateId: number | undefined, + // nwkManagerAddr: NodeId, + // configurationBitmask: number | undefined, + // ): Buffer { + // return this.buildNwkEnhancedUpdateRequest(channelPages, 0xff, undefined, nwkUpdateId, nwkManagerAddr, configurationBitmask); + // } /** * @see ClusterId.NWK_IEEE_JOINING_LIST_REQUEST * @param startIndex The starting index into the receiving device’s nwkIeeeJoiningList that SHALL be sent back. */ - public static buildNwkIEEEJoiningListRequest(startIndex: number): Buffer { - const buffalo = new BuffaloZdo(Buffer.alloc(MAX_BUFFER_SIZE), ZDO_MESSAGE_OVERHEAD); - - buffalo.writeUInt8(startIndex); + private buildNwkIEEEJoiningListRequest(startIndex: number): Buffer { + this.writeUInt8(startIndex); - return buffalo.getWritten(); + return this.getWritten(); } /** * @see ClusterId.NWK_BEACON_SURVEY_REQUEST */ - public static buildNwkBeaconSurveyRequest(tlv: BeaconSurveyConfigurationTLV): Buffer { - const buffalo = new BuffaloZdo(Buffer.alloc(MAX_BUFFER_SIZE), ZDO_MESSAGE_OVERHEAD); - + private buildNwkBeaconSurveyRequest(tlv: BeaconSurveyConfigurationTLV): Buffer { // BeaconSurveyConfigurationTLV: Local: ID: 0x00 - buffalo.writeUInt8(0x00); - buffalo.writeUInt8(2 + tlv.scanChannelList.length * 4 - 1); - buffalo.writeUInt8(tlv.scanChannelList.length); - buffalo.writeListUInt32(tlv.scanChannelList); - buffalo.writeUInt8(tlv.configurationBitmask); + this.writeUInt8(0x00); + this.writeUInt8(2 + tlv.scanChannelList.length * 4 - 1); + this.writeUInt8(tlv.scanChannelList.length); + this.writeListUInt32(tlv.scanChannelList); + this.writeUInt8(tlv.configurationBitmask); - return buffalo.getWritten(); + return this.getWritten(); } /** * @see ClusterId.START_KEY_NEGOTIATION_REQUEST */ - public static buildStartKeyNegotiationRequest(tlv: Curve25519PublicPointTLV): Buffer { - const buffalo = new BuffaloZdo(Buffer.alloc(MAX_BUFFER_SIZE), ZDO_MESSAGE_OVERHEAD); - + private buildStartKeyNegotiationRequest(tlv: Curve25519PublicPointTLV): Buffer { // Curve25519PublicPointTLV: Local: ID: 0x00 - buffalo.writeUInt8(0x00); - buffalo.writeUInt8(EUI64_SIZE + CURVE_PUBLIC_POINT_SIZE - 1); - buffalo.writeIeeeAddr(tlv.eui64); - buffalo.writeBuffer(tlv.publicPoint, CURVE_PUBLIC_POINT_SIZE); + this.writeUInt8(0x00); + this.writeUInt8(EUI64_SIZE + CURVE_PUBLIC_POINT_SIZE - 1); + this.writeIeeeAddr(tlv.eui64); + this.writeBuffer(tlv.publicPoint, CURVE_PUBLIC_POINT_SIZE); - return buffalo.getWritten(); + return this.getWritten(); } /** * @see ClusterId.RETRIEVE_AUTHENTICATION_TOKEN_REQUEST */ - public static buildRetrieveAuthenticationTokenRequest(tlv: AuthenticationTokenIdTLV): Buffer { - const buffalo = new BuffaloZdo(Buffer.alloc(MAX_BUFFER_SIZE), ZDO_MESSAGE_OVERHEAD); - + private buildRetrieveAuthenticationTokenRequest(tlv: AuthenticationTokenIdTLV): Buffer { // AuthenticationTokenIdTLV: Local: ID: 0x00 - buffalo.writeUInt8(0x00); - buffalo.writeUInt8(1 - 1); - buffalo.writeUInt8(tlv.tlvTypeTagId); + this.writeUInt8(0x00); + this.writeUInt8(1 - 1); + this.writeUInt8(tlv.tlvTypeTagId); - return buffalo.getWritten(); + return this.getWritten(); } /** * @see ClusterId.GET_AUTHENTICATION_LEVEL_REQUEST */ - public static buildGetAuthenticationLevelRequest(tlv: TargetIEEEAddressTLV): Buffer { - const buffalo = new BuffaloZdo(Buffer.alloc(MAX_BUFFER_SIZE), ZDO_MESSAGE_OVERHEAD); - + private buildGetAuthenticationLevelRequest(tlv: TargetIEEEAddressTLV): Buffer { // TargetIEEEAddressTLV: Local: ID: 0x00 - buffalo.writeUInt8(0x00); - buffalo.writeUInt8(EUI64_SIZE - 1); - buffalo.writeIeeeAddr(tlv.ieee); + this.writeUInt8(0x00); + this.writeUInt8(EUI64_SIZE - 1); + this.writeIeeeAddr(tlv.ieee); - return buffalo.getWritten(); + return this.getWritten(); } /** * @see ClusterId.SET_CONFIGURATION_REQUEST */ - public static buildSetConfigurationRequest( + private buildSetConfigurationRequest( nextPanIdChange: NextPanIdChangeGlobalTLV, nextChannelChange: NextChannelChangeGlobalTLV, configurationParameters: ConfigurationParametersGlobalTLV, ): Buffer { - const buffalo = new BuffaloZdo(Buffer.alloc(MAX_BUFFER_SIZE), ZDO_MESSAGE_OVERHEAD); + this.writeGlobalTLV({tagId: GlobalTLV.NEXT_PAN_ID_CHANGE, length: PAN_ID_SIZE, tlv: nextPanIdChange}); + this.writeGlobalTLV({tagId: GlobalTLV.NEXT_CHANNEL_CHANGE, length: 4, tlv: nextChannelChange}); + this.writeGlobalTLV({tagId: GlobalTLV.CONFIGURATION_PARAMETERS, length: 2, tlv: configurationParameters}); - buffalo.writeGlobalTLV({tagId: GlobalTLV.NEXT_PAN_ID_CHANGE, length: PAN_ID_SIZE, tlv: nextPanIdChange}); - buffalo.writeGlobalTLV({tagId: GlobalTLV.NEXT_CHANNEL_CHANGE, length: 4, tlv: nextChannelChange}); - buffalo.writeGlobalTLV({tagId: GlobalTLV.CONFIGURATION_PARAMETERS, length: 2, tlv: configurationParameters}); - - return buffalo.getWritten(); + return this.getWritten(); } /** @@ -1334,33 +1543,29 @@ export class BuffaloZdo extends Buffalo { * @param tlvIds The IDs of each TLV that are being requested. * Maximum number dependent on the underlying maximum size of the message as allowed by fragmentation. */ - public static buildGetConfigurationRequest(tlvIds: number[]): Buffer { - const buffalo = new BuffaloZdo(Buffer.alloc(MAX_BUFFER_SIZE), ZDO_MESSAGE_OVERHEAD); - - buffalo.writeUInt8(tlvIds.length); + private buildGetConfigurationRequest(tlvIds: number[]): Buffer { + this.writeUInt8(tlvIds.length); for (const tlvId of tlvIds) { - buffalo.writeUInt8(tlvId); + this.writeUInt8(tlvId); } - return buffalo.getWritten(); + return this.getWritten(); } /** * @see ClusterId.START_KEY_UPDATE_REQUEST */ - public static buildStartKeyUpdateRequest( + private buildStartKeyUpdateRequest( selectedKeyNegotiationMethod: SelectedKeyNegotiationMethodTLV, fragmentationParameters: FragmentationParametersGlobalTLV, ): Buffer { - const buffalo = new BuffaloZdo(Buffer.alloc(MAX_BUFFER_SIZE), ZDO_MESSAGE_OVERHEAD); - // SelectedKeyNegotiationMethodTLV: Local: ID: 0x00 - buffalo.writeUInt8(0x00); - buffalo.writeUInt8(EUI64_SIZE + 2 - 1); - buffalo.writeUInt8(selectedKeyNegotiationMethod.protocol); - buffalo.writeUInt8(selectedKeyNegotiationMethod.presharedSecret); - buffalo.writeIeeeAddr(selectedKeyNegotiationMethod.sendingDeviceEui64); + this.writeUInt8(0x00); + this.writeUInt8(EUI64_SIZE + 2 - 1); + this.writeUInt8(selectedKeyNegotiationMethod.protocol); + this.writeUInt8(selectedKeyNegotiationMethod.presharedSecret); + this.writeIeeeAddr(selectedKeyNegotiationMethod.sendingDeviceEui64); { let length = 2; @@ -1375,143 +1580,146 @@ export class BuffaloZdo extends Buffalo { length += 2; } - buffalo.writeGlobalTLV({tagId: GlobalTLV.FRAGMENTATION_PARAMETERS, length, tlv: fragmentationParameters}); + this.writeGlobalTLV({tagId: GlobalTLV.FRAGMENTATION_PARAMETERS, length, tlv: fragmentationParameters}); } - return buffalo.getWritten(); + return this.getWritten(); } /** * @see ClusterId.DECOMMISSION_REQUEST */ - public static buildDecommissionRequest(tlv: DeviceEUI64ListTLV): Buffer { - const buffalo = new BuffaloZdo(Buffer.alloc(MAX_BUFFER_SIZE), ZDO_MESSAGE_OVERHEAD); - + private buildDecommissionRequest(tlv: DeviceEUI64ListTLV): Buffer { // DeviceEUI64ListTLV: Local: ID: 0x00 - buffalo.writeUInt8(0x00); - buffalo.writeUInt8(tlv.eui64List.length * EUI64_SIZE + 1 - 1); - buffalo.writeUInt8(tlv.eui64List.length); + this.writeUInt8(0x00); + this.writeUInt8(tlv.eui64List.length * EUI64_SIZE + 1 - 1); + this.writeUInt8(tlv.eui64List.length); for (const eui64 of tlv.eui64List) { - buffalo.writeIeeeAddr(eui64); + this.writeIeeeAddr(eui64); } - return buffalo.getWritten(); + return this.getWritten(); } /** * @see ClusterId.CHALLENGE_REQUEST */ - public static buildChallengeRequest(tlv: APSFrameCounterChallengeTLV): Buffer { - const buffalo = new BuffaloZdo(Buffer.alloc(MAX_BUFFER_SIZE), ZDO_MESSAGE_OVERHEAD); - + private buildChallengeRequest(tlv: APSFrameCounterChallengeTLV): Buffer { // APSFrameCounterChallengeTLV: Local: ID: 0x00 - buffalo.writeUInt8(0x00); - buffalo.writeUInt8(EUI64_SIZE + CHALLENGE_VALUE_SIZE - 1); - buffalo.writeIeeeAddr(tlv.senderEui64); - buffalo.writeBuffer(tlv.challengeValue, CHALLENGE_VALUE_SIZE); + this.writeUInt8(0x00); + this.writeUInt8(EUI64_SIZE + CHALLENGE_VALUE_SIZE - 1); + this.writeIeeeAddr(tlv.senderEui64); + this.writeBuffer(tlv.challengeValue, CHALLENGE_VALUE_SIZE); - return buffalo.getWritten(); + return this.getWritten(); } - //-- RESPONSES - public static readResponse(clusterId: ZdoClusterId, buffer: Buffer, hasZdoMessageOverhead: boolean): unknown { + public static checkStatus(result: ResponseMap[number]): result is ValidResponseMap[K] { + return result[0] === Status.SUCCESS; + } + + public static readResponse( + hasZdoMessageOverhead: boolean, + clusterId: K extends keyof ResponseMap ? keyof ResponseMap : K, + buffer: Buffer, + ): ResponseMap[K] { const buffalo = new BuffaloZdo(buffer, hasZdoMessageOverhead ? ZDO_MESSAGE_OVERHEAD : 0); // set pos to skip `transaction sequence number` switch (clusterId) { case ZdoClusterId.NETWORK_ADDRESS_RESPONSE: { - return buffalo.readNetworkAddressResponse(); + return buffalo.readNetworkAddressResponse() as ResponseMap[K]; } case ZdoClusterId.IEEE_ADDRESS_RESPONSE: { - return buffalo.readIEEEAddressResponse(); + return buffalo.readIEEEAddressResponse() as ResponseMap[K]; } case ZdoClusterId.NODE_DESCRIPTOR_RESPONSE: { - return buffalo.readNodeDescriptorResponse(); + return buffalo.readNodeDescriptorResponse() as ResponseMap[K]; } case ZdoClusterId.POWER_DESCRIPTOR_RESPONSE: { - return buffalo.readPowerDescriptorResponse(); + return buffalo.readPowerDescriptorResponse() as ResponseMap[K]; } case ZdoClusterId.SIMPLE_DESCRIPTOR_RESPONSE: { - return buffalo.readSimpleDescriptorResponse(); + return buffalo.readSimpleDescriptorResponse() as ResponseMap[K]; } case ZdoClusterId.ACTIVE_ENDPOINTS_RESPONSE: { - return buffalo.readActiveEndpointsResponse(); + return buffalo.readActiveEndpointsResponse() as ResponseMap[K]; } case ZdoClusterId.MATCH_DESCRIPTORS_RESPONSE: { - return buffalo.readMatchDescriptorsResponse(); + return buffalo.readMatchDescriptorsResponse() as ResponseMap[K]; } case ZdoClusterId.END_DEVICE_ANNOUNCE: { - return buffalo.readEndDeviceAnnounce(); + return buffalo.readEndDeviceAnnounce() as ResponseMap[K]; } case ZdoClusterId.SYSTEM_SERVER_DISCOVERY_RESPONSE: { - return buffalo.readSystemServerDiscoveryResponse(); + return buffalo.readSystemServerDiscoveryResponse() as ResponseMap[K]; } case ZdoClusterId.PARENT_ANNOUNCE_RESPONSE: { - return buffalo.readParentAnnounceResponse(); + return buffalo.readParentAnnounceResponse() as ResponseMap[K]; } case ZdoClusterId.BIND_RESPONSE: { - return buffalo.readBindResponse(); + return buffalo.readBindResponse() as ResponseMap[K]; } case ZdoClusterId.UNBIND_RESPONSE: { - return buffalo.readUnbindResponse(); + return buffalo.readUnbindResponse() as ResponseMap[K]; } case ZdoClusterId.CLEAR_ALL_BINDINGS_RESPONSE: { - return buffalo.readClearAllBindingsResponse(); + return buffalo.readClearAllBindingsResponse() as ResponseMap[K]; } case ZdoClusterId.LQI_TABLE_RESPONSE: { - return buffalo.readLQITableResponse(); + return buffalo.readLQITableResponse() as ResponseMap[K]; } case ZdoClusterId.ROUTING_TABLE_RESPONSE: { - return buffalo.readRoutingTableResponse(); + return buffalo.readRoutingTableResponse() as ResponseMap[K]; } case ZdoClusterId.BINDING_TABLE_RESPONSE: { - return buffalo.readBindingTableResponse(); + return buffalo.readBindingTableResponse() as ResponseMap[K]; } case ZdoClusterId.LEAVE_RESPONSE: { - return buffalo.readLeaveResponse(); + return buffalo.readLeaveResponse() as ResponseMap[K]; } case ZdoClusterId.PERMIT_JOINING_RESPONSE: { - return buffalo.readPermitJoiningResponse(); + return buffalo.readPermitJoiningResponse() as ResponseMap[K]; } case ZdoClusterId.NWK_UPDATE_RESPONSE: { - return buffalo.readNwkUpdateResponse(); + return buffalo.readNwkUpdateResponse() as ResponseMap[K]; } case ZdoClusterId.NWK_ENHANCED_UPDATE_RESPONSE: { - return buffalo.readNwkEnhancedUpdateResponse(); + return buffalo.readNwkEnhancedUpdateResponse() as ResponseMap[K]; } - case ZdoClusterId.NWK_IEEE_JOINING_LIST_REPONSE: { - return buffalo.readNwkIEEEJoiningListResponse(); + case ZdoClusterId.NWK_IEEE_JOINING_LIST_RESPONSE: { + return buffalo.readNwkIEEEJoiningListResponse() as ResponseMap[K]; } case ZdoClusterId.NWK_UNSOLICITED_ENHANCED_UPDATE_RESPONSE: { - return buffalo.readNwkUnsolicitedEnhancedUpdateResponse(); + return buffalo.readNwkUnsolicitedEnhancedUpdateResponse() as ResponseMap[K]; } case ZdoClusterId.NWK_BEACON_SURVEY_RESPONSE: { - return buffalo.readNwkBeaconSurveyResponse(); + return buffalo.readNwkBeaconSurveyResponse() as ResponseMap[K]; } case ZdoClusterId.START_KEY_NEGOTIATION_RESPONSE: { - return buffalo.readStartKeyNegotiationResponse(); + return buffalo.readStartKeyNegotiationResponse() as ResponseMap[K]; } case ZdoClusterId.RETRIEVE_AUTHENTICATION_TOKEN_RESPONSE: { - return buffalo.readRetrieveAuthenticationTokenResponse(); + return buffalo.readRetrieveAuthenticationTokenResponse() as ResponseMap[K]; } case ZdoClusterId.GET_AUTHENTICATION_LEVEL_RESPONSE: { - return buffalo.readGetAuthenticationLevelResponse(); + return buffalo.readGetAuthenticationLevelResponse() as ResponseMap[K]; } case ZdoClusterId.SET_CONFIGURATION_RESPONSE: { - return buffalo.readSetConfigurationResponse(); + return buffalo.readSetConfigurationResponse() as ResponseMap[K]; } case ZdoClusterId.GET_CONFIGURATION_RESPONSE: { - return buffalo.readGetConfigurationResponse(); + return buffalo.readGetConfigurationResponse() as ResponseMap[K]; } case ZdoClusterId.START_KEY_UPDATE_RESPONSE: { - return buffalo.readStartKeyUpdateResponse(); + return buffalo.readStartKeyUpdateResponse() as ResponseMap[K]; } case ZdoClusterId.DECOMMISSION_RESPONSE: { - return buffalo.readDecommissionResponse(); + return buffalo.readDecommissionResponse() as ResponseMap[K]; } case ZdoClusterId.CHALLENGE_RESPONSE: { - return buffalo.readChallengeResponse(); + return buffalo.readChallengeResponse() as ResponseMap[K]; } default: { throw new Error(`Unsupported response reading for cluster ID '${clusterId}'.`); @@ -1522,13 +1730,12 @@ export class BuffaloZdo extends Buffalo { /** * @see ClusterId.NETWORK_ADDRESS_RESPONSE */ - public readNetworkAddressResponse(): NetworkAddressResponse { + public readNetworkAddressResponse(): ResponseMap[ZdoClusterId.NETWORK_ADDRESS_RESPONSE] { + // INV_REQUESTTYPE or DEVICE_NOT_FOUND const status: Status = this.readUInt8(); + let result: NetworkAddressResponse | undefined; - if (status !== Status.SUCCESS) { - // INV_REQUESTTYPE or DEVICE_NOT_FOUND - throw new ZdoStatusError(status); - } else { + if (status == Status.SUCCESS) { const eui64 = this.readIeeeAddr(); const nwkAddress = this.readUInt16(); let assocDevCount: number = 0; @@ -1542,25 +1749,26 @@ export class BuffaloZdo extends Buffalo { assocDevList = this.readListUInt16(assocDevCount); } - return { + result = { eui64, nwkAddress, startIndex, assocDevList, }; } + + return [status, result]; } /** * @see ClusterId.IEEE_ADDRESS_RESPONSE */ - public readIEEEAddressResponse(): IEEEAddressResponse { + public readIEEEAddressResponse(): ResponseMap[ZdoClusterId.IEEE_ADDRESS_RESPONSE] { + // INV_REQUESTTYPE or DEVICE_NOT_FOUND const status: Status = this.readUInt8(); + let result: IEEEAddressResponse | undefined; - if (status !== Status.SUCCESS) { - // INV_REQUESTTYPE or DEVICE_NOT_FOUND - throw new ZdoStatusError(status); - } else { + if (status === Status.SUCCESS) { const eui64 = this.readIeeeAddr(); const nwkAddress = this.readUInt16(); let assocDevCount: number = 0; @@ -1573,25 +1781,26 @@ export class BuffaloZdo extends Buffalo { assocDevList = this.readListUInt16(assocDevCount); } - return { + result = { eui64, nwkAddress, startIndex, assocDevList, }; } + + return [status, result]; } /** * @see ClusterId.NODE_DESCRIPTOR_RESPONSE */ - public readNodeDescriptorResponse(): NodeDescriptorResponse { + public readNodeDescriptorResponse(): ResponseMap[ZdoClusterId.NODE_DESCRIPTOR_RESPONSE] { + // DEVICE_NOT_FOUND, INV_REQUESTTYPE, or NO_DESCRIPTOR const status: Status = this.readUInt8(); + let result: NodeDescriptorResponse | undefined; - if (status !== Status.SUCCESS) { - // DEVICE_NOT_FOUND, INV_REQUESTTYPE, or NO_DESCRIPTOR - throw new ZdoStatusError(status); - } else { + if (status === Status.SUCCESS) { const nwkAddress = this.readUInt16(); // in bits: [logical type: 3] [deprecated: 1] [deprecated: 1] [fragmentation supported (R23): 1] [reserved/unused: 2] const nodeDescByte1 = this.readUInt8(); @@ -1607,10 +1816,10 @@ export class BuffaloZdo extends Buffalo { // Global: FragmentationParametersGlobalTLV const tlvs: TLV[] = this.readTLVs(); - return { + result = { nwkAddress, logicalType: nodeDescByte1 & 0x07, - fragmentationSupported: serverMask.stackComplianceResivion >= 23 ? (nodeDescByte1 & 0x20) >> 5 === 1 : null, + fragmentationSupported: serverMask.stackComplianceRevision >= 23 ? (nodeDescByte1 & 0x20) >> 5 === 1 : undefined, apsFlags: nodeDescByte2 & 0x07, frequencyBand: (nodeDescByte2 & 0xf8) >> 3, capabilities: macCapFlags, @@ -1623,23 +1832,24 @@ export class BuffaloZdo extends Buffalo { tlvs, }; } + + return [status, result]; } /** * @see ClusterId.POWER_DESCRIPTOR_RESPONSE */ - public readPowerDescriptorResponse(): PowerDescriptorResponse { + public readPowerDescriptorResponse(): ResponseMap[ZdoClusterId.POWER_DESCRIPTOR_RESPONSE] { + // DEVICE_NOT_FOUND, INV_REQUESTTYPE, or NO_DESCRIPTOR const status: Status = this.readUInt8(); + let result: PowerDescriptorResponse | undefined; - if (status !== Status.SUCCESS) { - // DEVICE_NOT_FOUND, INV_REQUESTTYPE, or NO_DESCRIPTOR - throw new ZdoStatusError(status); - } else { + if (status === Status.SUCCESS) { const nwkAddress = this.readUInt16(); const byte1 = this.readUInt8(); const byte2 = this.readUInt8(); - return { + result = { nwkAddress, currentPowerMode: byte1 & 0xf, availPowerSources: (byte1 >> 4) & 0xf, @@ -1647,21 +1857,21 @@ export class BuffaloZdo extends Buffalo { currentPowerSourceLevel: (byte2 >> 4) & 0xf, }; } + + return [status, result]; } /** * @see ClusterId.SIMPLE_DESCRIPTOR_RESPONSE */ - public readSimpleDescriptorResponse(): SimpleDescriptorResponse { + public readSimpleDescriptorResponse(): ResponseMap[ZdoClusterId.SIMPLE_DESCRIPTOR_RESPONSE] { + // INVALID_EP, NOT_ACTIVE, DEVICE_NOT_FOUND, INV_REQUESTTYPE or NO_DESCRIPTOR const status: Status = this.readUInt8(); + let result: SimpleDescriptorResponse | undefined; - if (status !== Status.SUCCESS) { - // INVALID_EP, NOT_ACTIVE, DEVICE_NOT_FOUND, INV_REQUESTTYPE or NO_DESCRIPTOR - throw new ZdoStatusError(status); - } else { + if (status === Status.SUCCESS) { const nwkAddress = this.readUInt16(); // Length in bytes of the Simple Descriptor to follow. [0x00-0xff] - // eslint-disable-next-line @typescript-eslint/no-unused-vars const length = this.readUInt8(); const endpoint = this.readUInt8(); const profileId = this.readUInt16(); @@ -1672,8 +1882,9 @@ export class BuffaloZdo extends Buffalo { const outClusterCount = this.readUInt8(); const outClusterList = this.readListUInt16(outClusterCount); // empty if outClusterCount==0 - return { + result = { nwkAddress, + length, endpoint, profileId, deviceId, @@ -1682,90 +1893,94 @@ export class BuffaloZdo extends Buffalo { outClusterList, }; } + + return [status, result]; } /** * @see ClusterId.ACTIVE_ENDPOINTS_RESPONSE */ - public readActiveEndpointsResponse(): ActiveEndpointsResponse { + public readActiveEndpointsResponse(): ResponseMap[ZdoClusterId.ACTIVE_ENDPOINTS_RESPONSE] { + // DEVICE_NOT_FOUND, INV_REQUESTTYPE, or NO_DESCRIPTOR const status: Status = this.readUInt8(); + let result: ActiveEndpointsResponse | undefined; - if (status !== Status.SUCCESS) { - // DEVICE_NOT_FOUND, INV_REQUESTTYPE, or NO_DESCRIPTOR - throw new ZdoStatusError(status); - } else { + if (status === Status.SUCCESS) { const nwkAddress = this.readUInt16(); const endpointCount = this.readUInt8(); const endpointList = this.readListUInt8(endpointCount); - return { + result = { nwkAddress, endpointList, }; } + + return [status, result]; } /** * @see ClusterId.MATCH_DESCRIPTORS_RESPONSE */ - public readMatchDescriptorsResponse(): MatchDescriptorsResponse { + public readMatchDescriptorsResponse(): ResponseMap[ZdoClusterId.MATCH_DESCRIPTORS_RESPONSE] { + // DEVICE_NOT_FOUND, INV_REQUESTTYPE, or NO_DESCRIPTOR const status: Status = this.readUInt8(); + let result: MatchDescriptorsResponse | undefined; - if (status !== Status.SUCCESS) { - // DEVICE_NOT_FOUND, INV_REQUESTTYPE, or NO_DESCRIPTOR - throw new ZdoStatusError(status); - } else { + if (status === Status.SUCCESS) { const nwkAddress = this.readUInt16(); const endpointCount = this.readUInt8(); const endpointList = this.readListUInt8(endpointCount); - return { + result = { nwkAddress, endpointList, }; } + + return [status, result]; } /** * @see ClusterId.END_DEVICE_ANNOUNCE */ - public readEndDeviceAnnounce(): EndDeviceAnnounce { + public readEndDeviceAnnounce(): ResponseMap[ZdoClusterId.END_DEVICE_ANNOUNCE] { const nwkAddress = this.readUInt16(); const eui64 = this.readIeeeAddr(); /** @see MACCapabilityFlags */ const capabilities = this.readUInt8(); - return {nwkAddress, eui64, capabilities: Utils.getMacCapFlags(capabilities)}; + return [Status.SUCCESS, {nwkAddress, eui64, capabilities: Utils.getMacCapFlags(capabilities)}]; } /** * @see ClusterId.SYSTEM_SERVER_DISCOVERY_RESPONSE */ - public readSystemServerDiscoveryResponse(): SystemServerDiscoveryResponse { + public readSystemServerDiscoveryResponse(): ResponseMap[ZdoClusterId.SYSTEM_SERVER_DISCOVERY_RESPONSE] { + // never expected !== SUCCESS const status: Status = this.readUInt8(); + let result: SystemServerDiscoveryResponse | undefined; - if (status !== Status.SUCCESS) { - // Shouldn't happen - throw new ZdoStatusError(status); - } else { + if (status === Status.SUCCESS) { const serverMask = Utils.getServerMask(this.readUInt16()); - return { + result = { serverMask, }; } + + return [status, result]; } /** * @see ClusterId.PARENT_ANNOUNCE_RESPONSE */ - public readParentAnnounceResponse(): ParentAnnounceResponse { + public readParentAnnounceResponse(): ResponseMap[ZdoClusterId.PARENT_ANNOUNCE_RESPONSE] { + // NOT_SUPPORTED const status: Status = this.readUInt8(); + let result: ParentAnnounceResponse | undefined; - if (status !== Status.SUCCESS) { - // NOT_SUPPORTED - throw new ZdoStatusError(status); - } else { + if (status === Status.SUCCESS) { const numberOfChildren = this.readUInt8(); const children: EUI64[] = []; @@ -1775,62 +1990,54 @@ export class BuffaloZdo extends Buffalo { children.push(childEui64); } - return {children}; + result = {children}; } + + return [status, result]; } /** * @see ClusterId.BIND_RESPONSE * @returns No response payload, throws if not success */ - public readBindResponse(): void { + public readBindResponse(): ResponseMap[ZdoClusterId.BIND_RESPONSE] { + // NOT_SUPPORTED, INVALID_EP, TABLE_FULL, or NOT_AUTHORIZED const status: Status = this.readUInt8(); - /* istanbul ignore else */ - if (status !== Status.SUCCESS) { - // NOT_SUPPORTED, INVALID_EP, TABLE_FULL, or NOT_AUTHORIZED - throw new ZdoStatusError(status); - } + return [status, undefined]; } /** * @see ClusterId.UNBIND_RESPONSE * @returns No response payload, throws if not success */ - public readUnbindResponse(): void { + public readUnbindResponse(): ResponseMap[ZdoClusterId.UNBIND_RESPONSE] { + // NOT_SUPPORTED, INVALID_EP, NO_ENTRY or NOT_AUTHORIZED const status: Status = this.readUInt8(); - /* istanbul ignore else */ - if (status !== Status.SUCCESS) { - // NOT_SUPPORTED, INVALID_EP, NO_ENTRY or NOT_AUTHORIZED - throw new ZdoStatusError(status); - } + return [status, undefined]; } /** * @see ClusterId.CLEAR_ALL_BINDINGS_RESPONSE * @returns No response payload, throws if not success */ - public readClearAllBindingsResponse(): void { + public readClearAllBindingsResponse(): ResponseMap[ZdoClusterId.CLEAR_ALL_BINDINGS_RESPONSE] { + // NOT_SUPPORTED, NOT_AUTHORIZED, INV_REQUESTTYPE, or NO_MATCH. const status: Status = this.readUInt8(); - /* istanbul ignore else */ - if (status !== Status.SUCCESS) { - // NOT_SUPPORTED, NOT_AUTHORIZED, INV_REQUESTTYPE, or NO_MATCH. - throw new ZdoStatusError(status); - } + return [status, undefined]; } /** * @see ClusterId.LQI_TABLE_RESPONSE */ - public readLQITableResponse(): LQITableResponse { + public readLQITableResponse(): ResponseMap[ZdoClusterId.LQI_TABLE_RESPONSE] { + // NOT_SUPPORTED or any status code returned from the NLME-GET.confirm primitive. const status: Status = this.readUInt8(); + let result: LQITableResponse | undefined; - if (status !== Status.SUCCESS) { - // NOT_SUPPORTED or any status code returned from the NLME-GET.confirm primitive. - throw new ZdoStatusError(status); - } else { + if (status === Status.SUCCESS) { const neighborTableEntries = this.readUInt8(); const startIndex = this.readUInt8(); // [0x00-0x02] @@ -1861,24 +2068,25 @@ export class BuffaloZdo extends Buffalo { }); } - return { + result = { neighborTableEntries, startIndex, entryList, }; } + + return [status, result]; } /** * @see ClusterId.ROUTING_TABLE_RESPONSE */ - public readRoutingTableResponse(): RoutingTableResponse { + public readRoutingTableResponse(): ResponseMap[ZdoClusterId.ROUTING_TABLE_RESPONSE] { + // NOT_SUPPORTED or any status code returned from the NLMEGET.confirm primitive. const status: Status = this.readUInt8(); + let result: RoutingTableResponse | undefined; - if (status !== Status.SUCCESS) { - // NOT_SUPPORTED or any status code returned from the NLMEGET.confirm primitive. - throw new ZdoStatusError(status); - } else { + if (status === Status.SUCCESS) { const routingTableEntries = this.readUInt8(); const startIndex = this.readUInt8(); // [0x00-0xFF] @@ -1892,7 +2100,7 @@ export class BuffaloZdo extends Buffalo { entryList.push({ destinationAddress, - status: statusByte & 0x07, + status: RoutingTableStatus[statusByte & 0x07] as keyof typeof RoutingTableStatus, memoryConstrained: (statusByte & 0x08) >> 3, manyToOne: (statusByte & 0x10) >> 4, routeRecordRequired: (statusByte & 0x20) >> 5, @@ -1901,24 +2109,25 @@ export class BuffaloZdo extends Buffalo { }); } - return { + result = { routingTableEntries, startIndex, entryList, }; } + + return [status, result]; } /** * @see ClusterId.BINDING_TABLE_RESPONSE */ - public readBindingTableResponse(): BindingTableResponse { + public readBindingTableResponse(): ResponseMap[ZdoClusterId.BINDING_TABLE_RESPONSE] { + // NOT_SUPPORTED or any status code returned from the APSMEGET.confirm primitive. const status: Status = this.readUInt8(); + let result: BindingTableResponse | undefined; - if (status !== Status.SUCCESS) { - // NOT_SUPPORTED or any status code returned from the APSMEGET.confirm primitive. - throw new ZdoStatusError(status); - } else { + if (status === Status.SUCCESS) { const bindingTableEntries = this.readUInt8(); const startIndex = this.readUInt8(); // [0x00-0xFF] @@ -1948,52 +2157,47 @@ export class BuffaloZdo extends Buffalo { }); } - return { + result = { bindingTableEntries, startIndex, entryList, }; } + + return [status, result]; } /** * @see ClusterId.LEAVE_RESPONSE * @returns No response payload, throws if not success */ - public readLeaveResponse(): void { + public readLeaveResponse(): ResponseMap[ZdoClusterId.LEAVE_RESPONSE] { + // NOT_SUPPORTED, NOT_AUTHORIZED or any status code returned from the NLMELEAVE.confirm primitive. const status: Status = this.readUInt8(); - /* istanbul ignore else */ - if (status !== Status.SUCCESS) { - // NOT_SUPPORTED, NOT_AUTHORIZED or any status code returned from the NLMELEAVE.confirm primitive. - throw new ZdoStatusError(status); - } + return [status, undefined]; } /** * @see ClusterId.PERMIT_JOINING_RESPONSE * @returns No response payload, throws if not success */ - public readPermitJoiningResponse(): void { + public readPermitJoiningResponse(): ResponseMap[ZdoClusterId.PERMIT_JOINING_RESPONSE] { + // INV_REQUESTTYPE, NOT_AUTHORIZED, or any status code returned from the NLME-PERMIT-JOINING.confirm primitive. const status: Status = this.readUInt8(); - /* istanbul ignore else */ - if (status !== Status.SUCCESS) { - // INV_REQUESTTYPE, NOT_AUTHORIZED, or any status code returned from the NLME-PERMIT-JOINING.confirm primitive. - throw new ZdoStatusError(status); - } + return [status, undefined]; } /** * @see ClusterId.NWK_UPDATE_RESPONSE */ - public readNwkUpdateResponse(): NwkUpdateResponse { + public readNwkUpdateResponse(): ResponseMap[ZdoClusterId.NWK_UPDATE_RESPONSE] { + // INV_REQUESTTYPE, NOT_SUPPORTED, or any status values returned from the MLME-SCAN.confirm primitive const status: Status = this.readUInt8(); + let result: NwkUpdateResponse | undefined; - if (status !== Status.SUCCESS) { - // INV_REQUESTTYPE, NOT_SUPPORTED, or any status values returned from the MLME-SCAN.confirm primitive - throw new ZdoStatusError(status); - } else { + if (status === Status.SUCCESS) { const scannedChannels = this.readUInt32(); const totalTransmissions = this.readUInt16(); const totalFailures = this.readUInt16(); @@ -2001,25 +2205,26 @@ export class BuffaloZdo extends Buffalo { const entryCount = this.readUInt8(); const entryList = this.readListUInt8(entryCount); - return { + result = { scannedChannels, totalTransmissions, totalFailures, entryList, }; } + + return [status, result]; } /** * @see ClusterId.NWK_ENHANCED_UPDATE_RESPONSE */ - public readNwkEnhancedUpdateResponse(): NwkEnhancedUpdateResponse { + public readNwkEnhancedUpdateResponse(): ResponseMap[ZdoClusterId.NWK_ENHANCED_UPDATE_RESPONSE] { + // INV_REQUESTTYPE, NOT_SUPPORTED, or any status values returned from the MLME-SCAN.confirm primitive. const status: Status = this.readUInt8(); + let result: NwkEnhancedUpdateResponse | undefined; - if (status !== Status.SUCCESS) { - // INV_REQUESTTYPE, NOT_SUPPORTED, or any status values returned from the MLME-SCAN.confirm primitive. - throw new ZdoStatusError(status); - } else { + if (status === Status.SUCCESS) { const scannedChannels = this.readUInt32(); const totalTransmissions = this.readUInt16(); const totalFailures = this.readUInt16(); @@ -2027,25 +2232,26 @@ export class BuffaloZdo extends Buffalo { const entryCount = this.readUInt8(); const entryList = this.readListUInt8(entryCount); - return { + result = { scannedChannels, totalTransmissions, totalFailures, entryList, }; } + + return [status, result]; } /** * @see ClusterId.NWK_IEEE_JOINING_LIST_REPONSE */ - public readNwkIEEEJoiningListResponse(): NwkIEEEJoiningListResponse { + public readNwkIEEEJoiningListResponse(): ResponseMap[ZdoClusterId.NWK_IEEE_JOINING_LIST_RESPONSE] { + // INV_REQUESTTYPE, or NOT_SUPPORTED const status: Status = this.readUInt8(); + let result: NwkIEEEJoiningListResponse | undefined; - if (status !== Status.SUCCESS) { - // INV_REQUESTTYPE, or NOT_SUPPORTED - throw new ZdoStatusError(status); - } else { + if (status === Status.SUCCESS) { const updateId = this.readUInt8(); const joiningPolicy = this.readUInt8(); // [0x00-0xFF] @@ -2065,7 +2271,7 @@ export class BuffaloZdo extends Buffalo { } } - return { + result = { updateId, joiningPolicy, entryListTotal, @@ -2073,25 +2279,26 @@ export class BuffaloZdo extends Buffalo { entryList, }; } + + return [status, result]; } /** * @see ClusterId.NWK_UNSOLICITED_ENHANCED_UPDATE_RESPONSE */ - public readNwkUnsolicitedEnhancedUpdateResponse(): NwkUnsolicitedEnhancedUpdateResponse { + public readNwkUnsolicitedEnhancedUpdateResponse(): ResponseMap[ZdoClusterId.NWK_UNSOLICITED_ENHANCED_UPDATE_RESPONSE] { + // ?? const status: Status = this.readUInt8(); + let result: NwkUnsolicitedEnhancedUpdateResponse | undefined; - if (status !== Status.SUCCESS) { - // ?? - throw new ZdoStatusError(status); - } else { + if (status === Status.SUCCESS) { const channelInUse = this.readUInt32(); const macTxUCastTotal = this.readUInt16(); const macTxUCastFailures = this.readUInt16(); const macTxUCastRetries = this.readUInt16(); const timePeriod = this.readUInt8(); - return { + result = { channelInUse, macTxUCastTotal, macTxUCastFailures, @@ -2099,18 +2306,19 @@ export class BuffaloZdo extends Buffalo { timePeriod, }; } + + return [status, result]; } /** * @see ClusterId.NWK_BEACON_SURVEY_RESPONSE */ - public readNwkBeaconSurveyResponse(): NwkBeaconSurveyResponse { + public readNwkBeaconSurveyResponse(): ResponseMap[ZdoClusterId.NWK_BEACON_SURVEY_RESPONSE] { + // INV_REQUESTTYPE, or NOT_SUPPORTED const status: Status = this.readUInt8(); + let result: NwkBeaconSurveyResponse | undefined; - if (status !== Status.SUCCESS) { - // INV_REQUESTTYPE, or NOT_SUPPORTED - throw new ZdoStatusError(status); - } else { + if (status === Status.SUCCESS) { const localTLVs = new Map([ // Local: ID: 0x01: BeaconSurveyResultsTLV [0x01, this.readBeaconSurveyResultsTLV], @@ -2119,161 +2327,163 @@ export class BuffaloZdo extends Buffalo { ]); const tlvs: TLV[] = this.readTLVs(localTLVs); - return { + result = { tlvs, }; } + + return [status, result]; } /** * @see ClusterId.START_KEY_NEGOTIATION_RESPONSE */ - public readStartKeyNegotiationResponse(): StartKeyNegotiationResponse { + public readStartKeyNegotiationResponse(): ResponseMap[ZdoClusterId.START_KEY_NEGOTIATION_RESPONSE] { + // INVALID_TLV, MISSING_TLV, TEMPORARY_FAILURE, NOT_AUTHORIZED const status: Status = this.readUInt8(); + let result: StartKeyNegotiationResponse | undefined; - if (status !== Status.SUCCESS) { - // INVALID_TLV, MISSING_TLV, TEMPORARY_FAILURE, NOT_AUTHORIZED - throw new ZdoStatusError(status); - } else { + if (status === Status.SUCCESS) { const localTLVs = new Map([ // Local: ID: 0x00: Curve25519PublicPointTLV [0x00, this.readCurve25519PublicPointTLV], ]); const tlvs: TLV[] = this.readTLVs(localTLVs); - return { + result = { tlvs, }; } + + return [status, result]; } /** * @see ClusterId.RETRIEVE_AUTHENTICATION_TOKEN_RESPONSE */ - public readRetrieveAuthenticationTokenResponse(): RetrieveAuthenticationTokenResponse { + public readRetrieveAuthenticationTokenResponse(): ResponseMap[ZdoClusterId.RETRIEVE_AUTHENTICATION_TOKEN_RESPONSE] { const status: Status = this.readUInt8(); + let result: RetrieveAuthenticationTokenResponse | undefined; - if (status !== Status.SUCCESS) { - throw new ZdoStatusError(status); - } else { + if (status === Status.SUCCESS) { // no local TLV const tlvs: TLV[] = this.readTLVs(); - return { + result = { tlvs, }; } + + return [status, result]; } /** * @see ClusterId.GET_AUTHENTICATION_LEVEL_RESPONSE */ - public readGetAuthenticationLevelResponse(): GetAuthenticationLevelResponse { + public readGetAuthenticationLevelResponse(): ResponseMap[ZdoClusterId.GET_AUTHENTICATION_LEVEL_RESPONSE] { + // NOT_SUPPORTED, INV_REQUESTTYPE, MISSING_TLV, and NOT_AUTHORIZED const status: Status = this.readUInt8(); + let result: GetAuthenticationLevelResponse | undefined; - if (status !== Status.SUCCESS) { - // NOT_SUPPORTED, INV_REQUESTTYPE, MISSING_TLV, and NOT_AUTHORIZED - throw new ZdoStatusError(status); - } else { + if (status === Status.SUCCESS) { const localTLVs = new Map([ // Local: ID: 0x00: DeviceAuthenticationLevelTLV [0x00, this.readDeviceAuthenticationLevelTLV], ]); const tlvs: TLV[] = this.readTLVs(localTLVs); - return { + result = { tlvs, }; } + + return [status, result]; } /** * @see ClusterId.SET_CONFIGURATION_RESPONSE */ - public readSetConfigurationResponse(): SetConfigurationResponse { + public readSetConfigurationResponse(): ResponseMap[ZdoClusterId.SET_CONFIGURATION_RESPONSE] { + // INV_REQUESTTYPE, or NOT_SUPPORTED const status: Status = this.readUInt8(); + let result: SetConfigurationResponse | undefined; - if (status !== Status.SUCCESS) { - // INV_REQUESTTYPE, or NOT_SUPPORTED - throw new ZdoStatusError(status); - } else { + if (status === Status.SUCCESS) { const localTLVs = new Map([ // Local: ID: 0x00: ProcessingStatusTLV [0x00, this.readProcessingStatusTLV], ]); const tlvs: TLV[] = this.readTLVs(localTLVs); - return { + result = { tlvs, }; } + + return [status, result]; } /** * @see ClusterId.GET_CONFIGURATION_RESPONSE */ - public readGetConfigurationResponse(): GetConfigurationResponse { + public readGetConfigurationResponse(): ResponseMap[ZdoClusterId.GET_CONFIGURATION_RESPONSE] { + // INV_REQUESTTYPE, or NOT_SUPPORTED const status: Status = this.readUInt8(); + let result: GetConfigurationResponse | undefined; - if (status !== Status.SUCCESS) { - // INV_REQUESTTYPE, or NOT_SUPPORTED - throw new ZdoStatusError(status); - } else { + if (status === Status.SUCCESS) { // Global: IDs: x, y, z const tlvs: TLV[] = this.readTLVs(); - return { + result = { tlvs, }; } + + return [status, result]; } /** * @see ClusterId.START_KEY_UPDATE_RESPONSE * @returns No response payload, throws if not success */ - public readStartKeyUpdateResponse(): void { + public readStartKeyUpdateResponse(): ResponseMap[ZdoClusterId.START_KEY_UPDATE_RESPONSE] { + // INV_REQUESTTYPE, NOT_AUTHORIZED or NOT_SUPPORTED const status: Status = this.readUInt8(); - /* istanbul ignore else */ - if (status !== Status.SUCCESS) { - // INV_REQUESTTYPE, NOT_AUTHORIZED or NOT_SUPPORTED - throw new ZdoStatusError(status); - } + return [status, undefined]; } /** * @see ClusterId.DECOMMISSION_RESPONSE * @returns No response payload, throws if not success */ - public readDecommissionResponse(): void { + public readDecommissionResponse(): ResponseMap[ZdoClusterId.DECOMMISSION_RESPONSE] { + // INV_REQUESTTYPE, NOT_AUTHORIZED or NOT_SUPPORTED const status: Status = this.readUInt8(); - /* istanbul ignore else */ - if (status !== Status.SUCCESS) { - // INV_REQUESTTYPE, NOT_AUTHORIZED or NOT_SUPPORTED - throw new ZdoStatusError(status); - } + return [status, undefined]; } /** * @see ClusterId.CHALLENGE_RESPONSE */ - public readChallengeResponse(): ChallengeResponse { + public readChallengeResponse(): ResponseMap[ZdoClusterId.CHALLENGE_RESPONSE] { const status: Status = this.readUInt8(); + let result: ChallengeResponse | undefined; - if (status !== Status.SUCCESS) { - throw new ZdoStatusError(status); - } else { + if (status === Status.SUCCESS) { const localTLVs = new Map([ // Local: ID: 0x00: APSFrameCounterResponseTLV [0x00, this.readAPSFrameCounterResponseTLV], ]); const tlvs: TLV[] = this.readTLVs(localTLVs); - return { + result = { tlvs, }; } + + return [status, result]; } } diff --git a/src/zspec/zdo/definition/clusters.ts b/src/zspec/zdo/definition/clusters.ts index 41f6c6ee10..90caee1a0c 100644 --- a/src/zspec/zdo/definition/clusters.ts +++ b/src/zspec/zdo/definition/clusters.ts @@ -501,7 +501,7 @@ export enum ClusterId { * Response: [transaction sequence number: 1] [status: 1] [ieee joining list update id: 1] [joining policy: 1] * [ieee joining list total: 1] [start index: 1] [ieee joining count: 1] [ieee:8]* */ - NWK_IEEE_JOINING_LIST_REPONSE = 0x803a, + NWK_IEEE_JOINING_LIST_RESPONSE = 0x803a, /** * Response: [transaction sequence number: 1] [status: 1] [channel in use: 4] [mac tx ucast total: 2] [mac tx ucast failures: 2] diff --git a/src/zspec/zdo/definition/enums.ts b/src/zspec/zdo/definition/enums.ts index 2a7559a49d..f90fb94402 100644 --- a/src/zspec/zdo/definition/enums.ts +++ b/src/zspec/zdo/definition/enums.ts @@ -86,3 +86,14 @@ export enum GlobalTLV { DEVICE_CAPABILITY_EXTENSION = 76, // Reserved = 77-255 } + +export enum RoutingTableStatus { + ACTIVE = 0x0, + DISCOVERY_UNDERWAY = 0x1, + DISCOVERY_FAILED = 0x2, + INACTIVE = 0x3, + VALIDATION_UNDERWAY = 0x4, + RESERVED1 = 0x5, + RESERVED2 = 0x6, + RESERVED3 = 0x7, +} diff --git a/src/zspec/zdo/definition/tstypes.ts b/src/zspec/zdo/definition/tstypes.ts index c2d765952a..96d5ea96b0 100644 --- a/src/zspec/zdo/definition/tstypes.ts +++ b/src/zspec/zdo/definition/tstypes.ts @@ -1,5 +1,12 @@ import {ClusterId, EUI64, ExtendedPanId, NodeId, PanId, ProfileId} from '../../tstypes'; -import {ActiveLinkKeyType, InitialJoinMethod, JoiningPolicy, SelectedKeyNegotiationProtocol, SelectedPreSharedSecret} from './enums'; +import { + ActiveLinkKeyType, + InitialJoinMethod, + JoiningPolicy, + RoutingTableStatus, + SelectedKeyNegotiationProtocol, + SelectedPreSharedSecret, +} from './enums'; import {Status} from './status'; /** @@ -59,7 +66,7 @@ export type MACCapabilityFlags = { * - [networkManager: 1] * - [reserved1: 1] * - [reserved2: 1] - * - [stackComplianceResivion: 7] + * - [stackComplianceRevision: 7] */ export type ServerMask = { primaryTrustCenter: number; @@ -77,7 +84,7 @@ export type ServerMask = { * A stack that is compliant to Revision 23 would set these bits to 23 (0010111b). * A stack SHALL indicate the Revision of the specification it is compliant to by setting these bits. */ - stackComplianceResivion: number; + stackComplianceRevision: number; }; //------------------------------------------------------------------------------------------------- @@ -172,7 +179,7 @@ export type RoutingTableEntry = { * * 3-bit */ - status: number; + status: keyof typeof RoutingTableStatus | 'UNKNOWN'; /** * A flag indicating whether the device is a memory constrained concentrator * @@ -261,7 +268,7 @@ export type NodeDescriptorResponse = { /** 000 == Zigbee Coordinator, 001 == Zigbee Router, 010 === Zigbee End Device, 011-111 === Reserved */ logicalType: number; /** R23 and above (determined via other means if not). Indicates whether the device supports fragmentation at the APS layer. */ - fragmentationSupported: boolean | null; + fragmentationSupported: boolean | undefined; /** Specifies the application support sub-layer capabilities of the node. Currently not supported, should be zero */ apsFlags: number; /** @@ -344,6 +351,8 @@ export type PowerDescriptorResponse = { export type SimpleDescriptorResponse = { /** NWK address for the request. */ nwkAddress: NodeId; + /** Length of the descriptor */ + length: number; /** * Specifies the endpoint within the node to which this description refers. * Applications SHALL only use endpoints 1-254. diff --git a/src/zspec/zdo/index.ts b/src/zspec/zdo/index.ts index 963beb9669..f8befd2e1f 100644 --- a/src/zspec/zdo/index.ts +++ b/src/zspec/zdo/index.ts @@ -3,4 +3,5 @@ export * from './definition/enums'; export {ClusterId} from './definition/clusters'; export {Status} from './definition/status'; export {ZdoStatusError as StatusError} from './zdoStatusError'; +export {BuffaloZdo as Buffalo} from './buffaloZdo'; export * as Utils from './utils'; diff --git a/src/zspec/zdo/utils.ts b/src/zspec/zdo/utils.ts index bbd03897d7..1d17c4f16a 100644 --- a/src/zspec/zdo/utils.ts +++ b/src/zspec/zdo/utils.ts @@ -57,7 +57,7 @@ export const getServerMask = (serverMask: number): ServerMask => { networkManager: (serverMask & 0x40) >> 6, reserved1: (serverMask & 0x80) >> 7, reserved2: (serverMask & 0x100) >> 8, - stackComplianceResivion: (serverMask & 0xfe00) >> 9, + stackComplianceRevision: (serverMask & 0xfe00) >> 9, }; }; @@ -72,6 +72,6 @@ export const createServerMask = (serverMask: ServerMask): number => { ((serverMask.networkManager << 6) & 0x40) | ((serverMask.reserved1 << 7) & 0x80) | ((serverMask.reserved2 << 8) & 0x100) | - ((serverMask.stackComplianceResivion << 9) & 0xfe00) + ((serverMask.stackComplianceRevision << 9) & 0xfe00) ); }; diff --git a/test/adapter/ember/emberAdapter.test.ts b/test/adapter/ember/emberAdapter.test.ts index 477731eb9c..fc72b345d5 100644 --- a/test/adapter/ember/emberAdapter.test.ts +++ b/test/adapter/ember/emberAdapter.test.ts @@ -2437,7 +2437,20 @@ describe('Ember Adapter Layer', () => { it('Adapter impl: throws when permitJoin on coordinator fails due to failed request', async () => { mockEzspPermitJoining.mockResolvedValueOnce(SLStatus.FAIL); - await expect(adapter.permitJoin(250, 0)).rejects.toThrow(`[ZDO] Failed permit joining request with status=${SLStatus[SLStatus.FAIL]}.`); + await expect(adapter.permitJoin(250, 0)).rejects.toThrow( + `[ZDO] Failed coordinator permit joining request with status=${SLStatus[SLStatus.FAIL]}.`, + ); + }); + + it('Adapter impl: log error when permitJoin broadcast fails due to failed request', async () => { + mockEzspSendBroadcast.mockResolvedValueOnce([SLStatus.FAIL, 0]); + + await adapter.permitJoin(250, undefined); + + expect(loggerSpies.error).toHaveBeenCalledWith( + `[ZDO] Failed broadcast permit joining request with status=${SLStatus[SLStatus.FAIL]}.`, + 'zh:ember', + ); }); it('Adapter impl: throws when permitJoin on router fails due to failed ZDO status', async () => { @@ -2762,7 +2775,7 @@ describe('Ember Adapter Layer', () => { groupId: 0, sequence: 0, }; - // for coverage of stackComplianceResivion detection + // for coverage of stackComplianceRevision detection const serverMask = Zdo.Utils.createServerMask({ primaryTrustCenter: 0, backupTrustCenter: 0, @@ -2773,7 +2786,7 @@ describe('Ember Adapter Layer', () => { networkManager: 0, reserved1: 0, reserved2: 0, - stackComplianceResivion: 0, + stackComplianceRevision: 0, }); mockEzspSendUnicast.mockImplementationOnce(() => { @@ -2832,7 +2845,7 @@ describe('Ember Adapter Layer', () => { groupId: 0, sequence: 0, }; - // for coverage of stackComplianceResivion detection + // for coverage of stackComplianceRevision detection const serverMask = Zdo.Utils.createServerMask({ primaryTrustCenter: 0, backupTrustCenter: 0, @@ -2843,7 +2856,7 @@ describe('Ember Adapter Layer', () => { networkManager: 0, reserved1: 0, reserved2: 0, - stackComplianceResivion: 21, + stackComplianceRevision: 21, }); mockEzspSendUnicast.mockImplementationOnce(() => { diff --git a/test/adapter/z-stack/adapter.test.ts b/test/adapter/z-stack/adapter.test.ts index 0cfdc656e0..87f3e0e035 100644 --- a/test/adapter/z-stack/adapter.test.ts +++ b/test/adapter/z-stack/adapter.test.ts @@ -20,7 +20,7 @@ import {UnifiedBackupStorage} from '../../../src/models'; import {setLogger} from '../../../src/utils/logger'; import {BroadcastAddress} from '../../../src/zspec/enums'; import * as Zcl from '../../../src/zspec/zcl'; -import {Status} from '../../../src/zspec/zdo'; +import * as Zdo from '../../../src/zspec/zdo'; import { ActiveEndpointsResponse, EndDeviceAnnounce, @@ -684,6 +684,7 @@ const baseZnpRequestMock = new ZnpRequestMockBuilder() let responsePayload: SimpleDescriptorResponse; if (payload.endpoint === 1) { responsePayload = { + length: 1, // bogus endpoint: 1, profileId: 123, deviceId: 5, @@ -694,6 +695,7 @@ const baseZnpRequestMock = new ZnpRequestMockBuilder() }; } else if (payload.endpoint === 99) { responsePayload = { + length: 1, // bogus endpoint: 99, profileId: 123, deviceId: 5, @@ -704,6 +706,7 @@ const baseZnpRequestMock = new ZnpRequestMockBuilder() }; } else { responsePayload = { + length: 1, // bogus endpoint: payload.endpoint, profileId: 124, deviceId: 7, @@ -719,7 +722,7 @@ const baseZnpRequestMock = new ZnpRequestMockBuilder() Subsystem.ZDO, 'simpleDescRsp', {srcaddr: payload.dstaddr}, - mockZdoZpiObject('simpleDescRsp', Status.SUCCESS, responsePayload), + mockZdoZpiObject('simpleDescRsp', Zdo.Status.SUCCESS, responsePayload), ); }) .nv(NvItemsIds.CHANLIST, Buffer.from([0, 8, 0, 0])) @@ -995,13 +998,13 @@ const mockZnpWaitForDefault = () => { if (type === Type.AREQ && subsystem === Subsystem.ZDO && command === 'activeEpRsp') { return waitForResult( - mockZdoZpiObject('activeEpRsp', Status.SUCCESS, { + mockZdoZpiObject('activeEpRsp', Zdo.Status.SUCCESS, { nwkAddress: 0, endpointList: [], }), ); } else if (type === Type.AREQ && subsystem === Subsystem.ZDO && command === 'mgmtPermitJoinRsp') { - return waitForResult(mockZdoZpiObject('mgmtPermitJoinRsp', Status.SUCCESS, {})); + return waitForResult(mockZdoZpiObject('mgmtPermitJoinRsp', Zdo.Status.SUCCESS, {})); } else if (type === Type.AREQ && subsystem === Subsystem.ZDO && command === 'stateChangeInd') { return waitForResult({payload: {state: 9}}); } else { @@ -1020,7 +1023,7 @@ const mockZnpWaitForStateChangeIndTimeout = () => { if (type === Type.AREQ && subsystem === Subsystem.ZDO && command === 'activeEpRsp') { return waitForResult( - mockZdoZpiObject('activeEpRsp', Status.SUCCESS, { + mockZdoZpiObject('activeEpRsp', Zdo.Status.SUCCESS, { nwkAddress: 0, endpointList: [], }), @@ -1035,7 +1038,7 @@ const mockZnpWaitForStateChangeIndTimeout = () => { let bindStatusResponse = 0; -const mockZdoZpiObject = (commandName: string, status: Status, payload: T | undefined = undefined): ZpiObject => { +const mockZdoZpiObject = (commandName: string, status: Zdo.Status, payload: T | undefined = undefined): ZpiObject => { const subsystem = Subsystem.ZDO; const command = Definition[subsystem].find((c) => c.name === commandName); return { @@ -1043,14 +1046,7 @@ const mockZdoZpiObject = (commandName: string, status: Status, payload: T | u subsystem, command, payload: {}, - parseZdoPayload: () => { - if (status !== Status.SUCCESS) { - throw new ZdoStatusError(status); - } else { - assert(payload !== undefined, 'Status.SUCCESS but no payload given'); - return payload; - } - }, + parseZdoPayload: () => [status, payload], }; }; @@ -1090,7 +1086,7 @@ const basicMocks = () => { if (type === Type.AREQ && subsystem === Subsystem.ZDO && command === 'activeEpRsp') { return waitForResult( - mockZdoZpiObject('activeEpRsp', Status.SUCCESS, { + mockZdoZpiObject('activeEpRsp', Zdo.Status.SUCCESS, { nwkAddress: 0, endpointList: [1, 2, 3, 4, 5, 6, 8, 10, 11, 110, 12, 13, 47, 242], }), @@ -1098,7 +1094,7 @@ const basicMocks = () => { } else if (type === Type.AREQ && subsystem === Subsystem.ZDO && command === 'stateChangeInd') { return waitForResult({payload: {}}); } else if (type === Type.AREQ && subsystem === Subsystem.ZDO && command === 'mgmtPermitJoinRsp') { - return waitForResult(mockZdoZpiObject('mgmtPermitJoinRsp', Status.SUCCESS, {})); + return waitForResult(mockZdoZpiObject('mgmtPermitJoinRsp', Zdo.Status.SUCCESS, {})); } else if (type === Type.AREQ && subsystem === Subsystem.ZDO && command === 'simpleDescRsp') { const promise = new Promise((resolve, reject) => { znpWaitFors.push({type, subsystem, command, payload, resolve, reject}); @@ -1120,7 +1116,7 @@ const basicMocks = () => { } return waitForResult( - mockZdoZpiObject('nodeDescRsp', Status.SUCCESS, { + mockZdoZpiObject('nodeDescRsp', Zdo.Status.SUCCESS, { manufacturerCode: payload.srcaddr * 2, apsFlags: 0, capabilities: DUMMY_NODE_DESC_RSP_CAPABILITIES, @@ -1142,7 +1138,7 @@ const basicMocks = () => { primaryTrustCenter: 0, reserved1: 0, reserved2: 0, - stackComplianceResivion: 0, + stackComplianceRevision: 0, }, tlvs: [], }), @@ -1172,7 +1168,7 @@ const basicMocks = () => { if (lastStartIndex === 0) { return waitForResult( - mockZdoZpiObject('mgmtLqiRsp', Status.SUCCESS, { + mockZdoZpiObject('mgmtLqiRsp', Zdo.Status.SUCCESS, { neighborTableEntries: 5, startIndex: 0, entryList: [ @@ -1183,7 +1179,7 @@ const basicMocks = () => { ); } else if (lastStartIndex === 2) { return waitForResult( - mockZdoZpiObject('mgmtLqiRsp', Status.SUCCESS, { + mockZdoZpiObject('mgmtLqiRsp', Zdo.Status.SUCCESS, { neighborTableEntries: 5, startIndex: 0, entryList: [ @@ -1194,7 +1190,7 @@ const basicMocks = () => { ); } else if (lastStartIndex === 4) { return waitForResult( - mockZdoZpiObject('mgmtLqiRsp', Status.SUCCESS, { + mockZdoZpiObject('mgmtLqiRsp', Zdo.Status.SUCCESS, { neighborTableEntries: 5, startIndex: 0, entryList: [{lqi: 10, nwkAddress: 9, eui64: '0x10', relationship: 3, depth: 1, ...defaults}], @@ -1202,12 +1198,18 @@ const basicMocks = () => { ); } } else if (type === Type.AREQ && subsystem === Subsystem.ZDO && command === 'mgmtLqiRsp' && equals(payload, {srcaddr: 204})) { - return waitForResult(mockZdoZpiObject('mgmtLqiRsp', Status.NOT_AUTHORIZED)); + return waitForResult(mockZdoZpiObject('mgmtLqiRsp', Zdo.Status.NOT_AUTHORIZED)); } else if (type === Type.AREQ && subsystem === Subsystem.ZDO && command === 'mgmtRtgRsp' && equals(payload, {srcaddr: 205})) { - const defaultEntryList = {manyToOne: 0, memoryConstrained: 0, reserved1: 0, routeRecordRequired: 0, status: 0}; + const defaultEntryList = { + manyToOne: 0, + memoryConstrained: 0, + reserved1: 0, + routeRecordRequired: 0, + status: Zdo.RoutingTableStatus[0] as keyof typeof Zdo.RoutingTableStatus, + }; if (lastStartIndex === 0) { return waitForResult( - mockZdoZpiObject('mgmtRtgRsp', Status.SUCCESS, { + mockZdoZpiObject('mgmtRtgRsp', Zdo.Status.SUCCESS, { startIndex: 0, routingTableEntries: 5, entryList: [ @@ -1218,7 +1220,7 @@ const basicMocks = () => { ); } else if (lastStartIndex === 2) { return waitForResult( - mockZdoZpiObject('mgmtRtgRsp', Status.SUCCESS, { + mockZdoZpiObject('mgmtRtgRsp', Zdo.Status.SUCCESS, { startIndex: 0, routingTableEntries: 5, entryList: [ @@ -1229,7 +1231,7 @@ const basicMocks = () => { ); } else if (lastStartIndex === 4) { return waitForResult( - mockZdoZpiObject('mgmtRtgRsp', Status.SUCCESS, { + mockZdoZpiObject('mgmtRtgRsp', Zdo.Status.SUCCESS, { startIndex: 0, routingTableEntries: 5, entryList: [{destinationAddress: 14, nextHopAddress: 3, ...defaultEntryList}], @@ -1237,16 +1239,16 @@ const basicMocks = () => { ); } } else if (type === Type.AREQ && subsystem === Subsystem.ZDO && command === 'mgmtRtgRsp' && equals(payload, {srcaddr: 206})) { - return waitForResult(mockZdoZpiObject('mgmtRtgRsp', Status.INSUFFICIENT_SPACE)); + return waitForResult(mockZdoZpiObject('mgmtRtgRsp', Zdo.Status.INSUFFICIENT_SPACE)); } else if (type === Type.AREQ && subsystem === Subsystem.ZDO && command === 'bindRsp' && equals(payload, {srcaddr: 301})) { return waitForResult(mockZdoZpiObject('bindRsp', bindStatusResponse, {})); } else if (type === Type.AREQ && subsystem === Subsystem.ZDO && command === 'unbindRsp' && equals(payload, {srcaddr: 301})) { return waitForResult(mockZdoZpiObject('unbindRsp', bindStatusResponse, {})); } else if (type === Type.AREQ && subsystem === Subsystem.ZDO && command === 'mgmtLeaveRsp' && equals(payload, {srcaddr: 401})) { - return waitForResult(mockZdoZpiObject('mgmtLeaveRsp', Status.SUCCESS, {})); + return waitForResult(mockZdoZpiObject('mgmtLeaveRsp', Zdo.Status.SUCCESS, {})); } else if (type === Type.AREQ && subsystem === Subsystem.ZDO && command === 'nwkAddrRsp' && payload.ieeeaddr === '0x03') { return waitForResult( - mockZdoZpiObject('nwkAddrRsp', Status.SUCCESS, { + mockZdoZpiObject('nwkAddrRsp', Zdo.Status.SUCCESS, { nwkAddress: 3, eui64: '0x03', assocDevList: [], @@ -1255,7 +1257,7 @@ const basicMocks = () => { ); } else if (type === Type.AREQ && subsystem === Subsystem.ZDO && command === 'nwkAddrRsp' && payload.ieeeaddr === '0x02') { return waitForResult( - mockZdoZpiObject('nwkAddrRsp', Status.SUCCESS, { + mockZdoZpiObject('nwkAddrRsp', Zdo.Status.SUCCESS, { nwkAddress: 2, eui64: '0x02', assocDevList: [], @@ -1907,7 +1909,7 @@ describe('zstack-adapter', () => { if (type === Type.AREQ && subsystem === Subsystem.ZDO && command === 'activeEpRsp') { return waitForResult( - mockZdoZpiObject('activeEpRsp', Status.SUCCESS, { + mockZdoZpiObject('activeEpRsp', Zdo.Status.SUCCESS, { nwkAddress: 0, endpointList: [1, 2, 3], }), @@ -3688,7 +3690,7 @@ describe('zstack-adapter', () => { let deviceAnnounce; mockZnpRequest.mockClear(); mockQueueExecute.mockClear(); - const object = mockZdoZpiObject('endDeviceAnnceInd', Status.SUCCESS, { + const object = mockZdoZpiObject('endDeviceAnnceInd', Zdo.Status.SUCCESS, { capabilities: DUMMY_NODE_DESC_RSP_CAPABILITIES, eui64: '0x123', nwkAddress: 123, @@ -3707,7 +3709,7 @@ describe('zstack-adapter', () => { let deviceAnnounce; mockZnpRequest.mockClear(); mockQueueExecute.mockClear(); - const object = mockZdoZpiObject('endDeviceAnnceInd', Status.SUCCESS, { + const object = mockZdoZpiObject('endDeviceAnnceInd', Zdo.Status.SUCCESS, { capabilities: {...DUMMY_NODE_DESC_RSP_CAPABILITIES, deviceType: 0}, eui64: '0x123', nwkAddress: 123, @@ -3731,7 +3733,7 @@ describe('zstack-adapter', () => { basicMocks(); await adapter.start(); let networkAddress; - const object = mockZdoZpiObject('nwkAddrRsp', Status.SUCCESS, { + const object = mockZdoZpiObject('nwkAddrRsp', Zdo.Status.SUCCESS, { eui64: '0x123', nwkAddress: 124, assocDevList: [], diff --git a/test/adapter/z-stack/znp.test.ts b/test/adapter/z-stack/znp.test.ts index 65d977a655..80f75d3aef 100644 --- a/test/adapter/z-stack/znp.test.ts +++ b/test/adapter/z-stack/znp.test.ts @@ -6,6 +6,7 @@ import {Znp, ZpiObject} from '../../../src/adapter/z-stack/znp'; import BuffaloZnp from '../../../src/adapter/z-stack/znp/buffaloZnp'; import ParameterType from '../../../src/adapter/z-stack/znp/parameterType'; import {logger, setLogger} from '../../../src/utils/logger'; +import * as Zdo from '../../../src/zspec/zdo'; import {duplicateArray, ieeeaAddr1, ieeeaAddr2} from '../../testUtils'; const mockLogger = { @@ -855,33 +856,39 @@ describe('ZNP', () => { const buffer = Buffer.from([0, 0, 0, 1, 1, 2, 3, 4, 5, 6, 7, 8, 5]); const frame = new UnpiFrame(UnpiConstants.Type.AREQ, UnpiConstants.Subsystem.ZDO, 193, buffer); const obj = ZpiObject.fromUnpiFrame(frame); - expect(obj.parseZdoPayload()).toStrictEqual({ - capabilities: { - allocateAddress: 0, - alternatePANCoordinator: 1, - deviceType: 0, - powerSource: 1, - reserved1: 0, - reserved2: 0, - rxOnWhenIdle: 0, - securityCapability: 0, + expect(obj.parseZdoPayload()).toStrictEqual([ + Zdo.Status.SUCCESS, + { + capabilities: { + allocateAddress: 0, + alternatePANCoordinator: 1, + deviceType: 0, + powerSource: 1, + reserved1: 0, + reserved2: 0, + rxOnWhenIdle: 0, + securityCapability: 0, + }, + eui64: '0x0807060504030201', + nwkAddress: 256, }, - eui64: '0x0807060504030201', - nwkAddress: 256, - }); + ]); }); it('ZpiObject parseZdoPayload - nwkAddrRsp', async () => { const buffer = Buffer.from([0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x01, 0x00, 0x02, 0x10, 0x10, 0x11, 0x11]); const frame = new UnpiFrame(UnpiConstants.Type.AREQ, UnpiConstants.Subsystem.ZDO, 128, buffer); const obj = ZpiObject.fromUnpiFrame(frame); - expect(obj.parseZdoPayload()).toStrictEqual({ - assocDevList: [4112, 4369], - eui64: '0x0807060504030201', - // numassocdev: 2, - nwkAddress: 257, - startIndex: 0, - }); + expect(obj.parseZdoPayload()).toStrictEqual([ + Zdo.Status.SUCCESS, + { + assocDevList: [4112, 4369], + eui64: '0x0807060504030201', + // numassocdev: 2, + nwkAddress: 257, + startIndex: 0, + }, + ]); }); it('Cant read unsupported type', () => { diff --git a/test/zspec/zdo/buffalo.test.ts b/test/zspec/zdo/buffalo.test.ts index 57dac17a22..25770648ae 100644 --- a/test/zspec/zdo/buffalo.test.ts +++ b/test/zspec/zdo/buffalo.test.ts @@ -79,7 +79,7 @@ const SERVER_MASK_R22: ServerMask = { networkManager: 1, reserved1: 0, reserved2: 0, - stackComplianceResivion: 22, + stackComplianceRevision: 22, }; const SERVER_MASK_R22_BYTE = Zdo.Utils.createServerMask(SERVER_MASK_R22); @@ -93,7 +93,7 @@ const SERVER_MASK_R23: ServerMask = { networkManager: 1, reserved1: 0, reserved2: 0, - stackComplianceResivion: 23, + stackComplianceRevision: 23, }; const SERVER_MASK_R23_BYTE = Zdo.Utils.createServerMask(SERVER_MASK_R23); @@ -400,25 +400,51 @@ describe('ZDO Buffalo', () => { expect(writeBuffer.getWritten()).toStrictEqual(Buffer.from(expected.length ? bytes : [])); }); + it('Throws when building unknown cluster ID', () => { + const clusterId = 0x8005; + + expect(() => { + // @ts-expect-error + BuffaloZdo.buildRequest(true, clusterId, Buffer.from([1, 123])); + }).toThrow(`Unsupported request building for cluster ID '${clusterId}'.`); + }); + + it('buildNetworkAddressRequest without ZDO_MESSAGE_OVERHEAD', () => { + expect(Zdo.Buffalo.buildRequest(false, Zdo.ClusterId.NETWORK_ADDRESS_REQUEST, IEEE_ADDRESS1, false, 1)).toStrictEqual( + Buffer.from([...IEEE_ADDRESS1_BYTES, 0, 1]), + ); + expect(Zdo.Buffalo.buildRequest(false, Zdo.ClusterId.NETWORK_ADDRESS_REQUEST, IEEE_ADDRESS2, true, 3)).toStrictEqual( + Buffer.from([...IEEE_ADDRESS2_BYTES, 1, 3]), + ); + }); + it('buildNetworkAddressRequest', () => { - expect(BuffaloZdo.buildNetworkAddressRequest(IEEE_ADDRESS1, false, 1)).toStrictEqual(Buffer.from([0, ...IEEE_ADDRESS1_BYTES, 0, 1])); - expect(BuffaloZdo.buildNetworkAddressRequest(IEEE_ADDRESS2, true, 3)).toStrictEqual(Buffer.from([0, ...IEEE_ADDRESS2_BYTES, 1, 3])); + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.NETWORK_ADDRESS_REQUEST, IEEE_ADDRESS1, false, 1)).toStrictEqual( + Buffer.from([0, ...IEEE_ADDRESS1_BYTES, 0, 1]), + ); + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.NETWORK_ADDRESS_REQUEST, IEEE_ADDRESS2, true, 3)).toStrictEqual( + Buffer.from([0, ...IEEE_ADDRESS2_BYTES, 1, 3]), + ); }); it('buildIeeeAddressRequest', () => { - expect(BuffaloZdo.buildIeeeAddressRequest(NODE_ID1, false, 1)).toStrictEqual(Buffer.from([0, ...NODE_ID1_BYTES, 0, 1])); - expect(BuffaloZdo.buildIeeeAddressRequest(NODE_ID1, true, 3)).toStrictEqual(Buffer.from([0, ...NODE_ID1_BYTES, 1, 3])); + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.IEEE_ADDRESS_REQUEST, NODE_ID1, false, 1)).toStrictEqual( + Buffer.from([0, ...NODE_ID1_BYTES, 0, 1]), + ); + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.IEEE_ADDRESS_REQUEST, NODE_ID1, true, 3)).toStrictEqual( + Buffer.from([0, ...NODE_ID1_BYTES, 1, 3]), + ); }); it('buildNodeDescriptorRequest', () => { - expect(BuffaloZdo.buildNodeDescriptorRequest(NODE_ID1)).toStrictEqual(Buffer.from([0, ...NODE_ID1_BYTES])); + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.NODE_DESCRIPTOR_REQUEST, NODE_ID1)).toStrictEqual(Buffer.from([0, ...NODE_ID1_BYTES])); const tlv: FragmentationParametersGlobalTLV = { nwkAddress: NODE_ID1, /*fragmentationOptions: undefined,*/ /*maxIncomingTransferUnit: undefined,*/ }; - expect(BuffaloZdo.buildNodeDescriptorRequest(NODE_ID1, tlv)).toStrictEqual( + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.NODE_DESCRIPTOR_REQUEST, NODE_ID1, tlv)).toStrictEqual( Buffer.from([0, ...NODE_ID1_BYTES, Zdo.GlobalTLV.FRAGMENTATION_PARAMETERS, 1, ...uint16To8Array(tlv.nwkAddress)]), ); @@ -427,7 +453,7 @@ describe('ZDO Buffalo', () => { fragmentationOptions: 1, /*maxIncomingTransferUnit: undefined,*/ }; - expect(BuffaloZdo.buildNodeDescriptorRequest(NODE_ID1, tlv2)).toStrictEqual( + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.NODE_DESCRIPTOR_REQUEST, NODE_ID1, tlv2)).toStrictEqual( Buffer.from([ 0, ...NODE_ID1_BYTES, @@ -443,7 +469,7 @@ describe('ZDO Buffalo', () => { /*fragmentationOptions: undefined,*/ maxIncomingTransferUnit: 256, }; - expect(BuffaloZdo.buildNodeDescriptorRequest(NODE_ID1, tlv3)).toStrictEqual( + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.NODE_DESCRIPTOR_REQUEST, NODE_ID1, tlv3)).toStrictEqual( Buffer.from([ 0, ...NODE_ID1_BYTES, @@ -459,7 +485,7 @@ describe('ZDO Buffalo', () => { fragmentationOptions: 1, maxIncomingTransferUnit: 65352, }; - expect(BuffaloZdo.buildNodeDescriptorRequest(NODE_ID1, tlv4)).toStrictEqual( + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.NODE_DESCRIPTOR_REQUEST, NODE_ID1, tlv4)).toStrictEqual( Buffer.from([ 0, ...NODE_ID1_BYTES, @@ -473,19 +499,23 @@ describe('ZDO Buffalo', () => { }); it('buildPowerDescriptorRequest', () => { - expect(BuffaloZdo.buildPowerDescriptorRequest(NODE_ID1)).toStrictEqual(Buffer.from([0, ...NODE_ID1_BYTES])); + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.POWER_DESCRIPTOR_REQUEST, NODE_ID1)).toStrictEqual(Buffer.from([0, ...NODE_ID1_BYTES])); }); it('buildSimpleDescriptorRequest', () => { - expect(BuffaloZdo.buildSimpleDescriptorRequest(NODE_ID1, 3)).toStrictEqual(Buffer.from([0, ...NODE_ID1_BYTES, 3])); + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.SIMPLE_DESCRIPTOR_REQUEST, NODE_ID1, 3)).toStrictEqual( + Buffer.from([0, ...NODE_ID1_BYTES, 3]), + ); }); it('buildActiveEndpointsRequest', () => { - expect(BuffaloZdo.buildActiveEndpointsRequest(NODE_ID1)).toStrictEqual(Buffer.from([0, ...NODE_ID1_BYTES])); + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.ACTIVE_ENDPOINTS_REQUEST, NODE_ID1)).toStrictEqual(Buffer.from([0, ...NODE_ID1_BYTES])); }); it('buildMatchDescriptorRequest', () => { - expect(BuffaloZdo.buildMatchDescriptorRequest(NODE_ID1, ZSpec.HA_PROFILE_ID, CLUSTER_LIST1, CLUSTER_LIST2)).toStrictEqual( + expect( + Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.MATCH_DESCRIPTORS_REQUEST, NODE_ID1, ZSpec.HA_PROFILE_ID, CLUSTER_LIST1, CLUSTER_LIST2), + ).toStrictEqual( Buffer.from([ 0, ...NODE_ID1_BYTES, @@ -496,14 +526,14 @@ describe('ZDO Buffalo', () => { ...CLUSTER_LIST2_BYTES, ]), ); - expect(BuffaloZdo.buildMatchDescriptorRequest(NODE_ID1, ZSpec.HA_PROFILE_ID, CLUSTER_LIST1, [])).toStrictEqual( - Buffer.from([0, ...NODE_ID1_BYTES, ...uint16To8Array(ZSpec.HA_PROFILE_ID), CLUSTER_LIST1.length, ...CLUSTER_LIST1_BYTES, 0]), - ); + expect( + Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.MATCH_DESCRIPTORS_REQUEST, NODE_ID1, ZSpec.HA_PROFILE_ID, CLUSTER_LIST1, []), + ).toStrictEqual(Buffer.from([0, ...NODE_ID1_BYTES, ...uint16To8Array(ZSpec.HA_PROFILE_ID), CLUSTER_LIST1.length, ...CLUSTER_LIST1_BYTES, 0])); }); it('buildSystemServiceDiscoveryRequest', () => { expect( - BuffaloZdo.buildSystemServiceDiscoveryRequest({ + Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.SYSTEM_SERVER_DISCOVERY_REQUEST, { primaryTrustCenter: 1, backupTrustCenter: 0, deprecated1: 0, @@ -513,11 +543,11 @@ describe('ZDO Buffalo', () => { networkManager: 0, reserved1: 0, reserved2: 0, - stackComplianceResivion: 0, + stackComplianceRevision: 0, }), ).toStrictEqual(Buffer.from([0, ...uint16To8Array(0b0000000000000001)])); expect( - BuffaloZdo.buildSystemServiceDiscoveryRequest({ + Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.SYSTEM_SERVER_DISCOVERY_REQUEST, { primaryTrustCenter: 1, backupTrustCenter: 0, deprecated1: 0, @@ -527,20 +557,32 @@ describe('ZDO Buffalo', () => { networkManager: 1, reserved1: 0, reserved2: 0, - stackComplianceResivion: 23, + stackComplianceRevision: 23, }), ).toStrictEqual(Buffer.from([0, ...uint16To8Array(0b0010111001000001)])); }); it('buildParentAnnounce', () => { const children = [IEEE_ADDRESS1, IEEE_ADDRESS2]; - expect(BuffaloZdo.buildParentAnnounce(children)).toStrictEqual( + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.PARENT_ANNOUNCE, children)).toStrictEqual( Buffer.from([0, children.length, ...IEEE_ADDRESS1_BYTES, ...IEEE_ADDRESS2_BYTES]), ); }); it('buildBindRequest', () => { - expect(BuffaloZdo.buildBindRequest(IEEE_ADDRESS1, 2, Zcl.Clusters.seMetering.ID, Zdo.UNICAST_BINDING, IEEE_ADDRESS2, 123, 64)).toStrictEqual( + expect( + Zdo.Buffalo.buildRequest( + true, + Zdo.ClusterId.BIND_REQUEST, + IEEE_ADDRESS1, + 2, + Zcl.Clusters.seMetering.ID, + Zdo.UNICAST_BINDING, + IEEE_ADDRESS2, + 123, + 64, + ), + ).toStrictEqual( Buffer.from([ 0, ...IEEE_ADDRESS1_BYTES, @@ -552,7 +594,17 @@ describe('ZDO Buffalo', () => { ]), ); expect( - BuffaloZdo.buildBindRequest(IEEE_ADDRESS1, 3, Zcl.Clusters.seMetering.ID, Zdo.MULTICAST_BINDING, IEEE_ADDRESS2, 123, 64), + Zdo.Buffalo.buildRequest( + true, + Zdo.ClusterId.BIND_REQUEST, + IEEE_ADDRESS1, + 3, + Zcl.Clusters.seMetering.ID, + Zdo.MULTICAST_BINDING, + IEEE_ADDRESS2, + 123, + 64, + ), ).toStrictEqual( Buffer.from([0, ...IEEE_ADDRESS1_BYTES, 3, ...uint16To8Array(Zcl.Clusters.seMetering.ID), Zdo.MULTICAST_BINDING, ...uint16To8Array(123)]), ); @@ -560,7 +612,17 @@ describe('ZDO Buffalo', () => { it('buildUnbindRequest', () => { expect( - BuffaloZdo.buildUnbindRequest(IEEE_ADDRESS1, 2, Zcl.Clusters.seMetering.ID, Zdo.UNICAST_BINDING, IEEE_ADDRESS2, 123, 64), + Zdo.Buffalo.buildRequest( + true, + Zdo.ClusterId.UNBIND_REQUEST, + IEEE_ADDRESS1, + 2, + Zcl.Clusters.seMetering.ID, + Zdo.UNICAST_BINDING, + IEEE_ADDRESS2, + 123, + 64, + ), ).toStrictEqual( Buffer.from([ 0, @@ -573,7 +635,17 @@ describe('ZDO Buffalo', () => { ]), ); expect( - BuffaloZdo.buildUnbindRequest(IEEE_ADDRESS1, 3, Zcl.Clusters.seMetering.ID, Zdo.MULTICAST_BINDING, IEEE_ADDRESS2, 123, 64), + Zdo.Buffalo.buildRequest( + true, + Zdo.ClusterId.UNBIND_REQUEST, + IEEE_ADDRESS1, + 3, + Zcl.Clusters.seMetering.ID, + Zdo.MULTICAST_BINDING, + IEEE_ADDRESS2, + 123, + 64, + ), ).toStrictEqual( Buffer.from([0, ...IEEE_ADDRESS1_BYTES, 3, ...uint16To8Array(Zcl.Clusters.seMetering.ID), Zdo.MULTICAST_BINDING, ...uint16To8Array(123)]), ); @@ -581,47 +653,49 @@ describe('ZDO Buffalo', () => { it('Throws when buildBindRequest/buildUnbindRequest invalid type', () => { expect(() => { - BuffaloZdo.buildBindRequest(IEEE_ADDRESS1, 2, Zcl.Clusters.seMetering.ID, 99, IEEE_ADDRESS2, 123, 64); + Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.BIND_REQUEST, IEEE_ADDRESS1, 2, Zcl.Clusters.seMetering.ID, 99, IEEE_ADDRESS2, 123, 64); }).toThrow(`Status 'NOT_SUPPORTED'`); expect(() => { - BuffaloZdo.buildUnbindRequest(IEEE_ADDRESS1, 2, Zcl.Clusters.seMetering.ID, 99, IEEE_ADDRESS2, 123, 64); + Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.UNBIND_REQUEST, IEEE_ADDRESS1, 2, Zcl.Clusters.seMetering.ID, 99, IEEE_ADDRESS2, 123, 64); }).toThrow(`Status 'NOT_SUPPORTED'`); }); it('buildClearAllBindingsRequest', () => { const eui64List = [IEEE_ADDRESS1, IEEE_ADDRESS2]; - expect(BuffaloZdo.buildClearAllBindingsRequest({eui64List} as ClearAllBindingsReqEUI64TLV)).toStrictEqual( + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.CLEAR_ALL_BINDINGS_REQUEST, {eui64List} as ClearAllBindingsReqEUI64TLV)).toStrictEqual( Buffer.from([0, 0, ZSpec.EUI64_SIZE * eui64List.length + 1 - 1, eui64List.length, ...IEEE_ADDRESS1_BYTES, ...IEEE_ADDRESS2_BYTES]), ); - expect(BuffaloZdo.buildClearAllBindingsRequest({eui64List: []} as ClearAllBindingsReqEUI64TLV)).toStrictEqual(Buffer.from([0, 0, 0, 0])); + expect( + Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.CLEAR_ALL_BINDINGS_REQUEST, {eui64List: []} as ClearAllBindingsReqEUI64TLV), + ).toStrictEqual(Buffer.from([0, 0, 0, 0])); }); it('buildLqiTableRequest', () => { - expect(BuffaloZdo.buildLqiTableRequest(1)).toStrictEqual(Buffer.from([0, 1])); - expect(BuffaloZdo.buildLqiTableRequest(254)).toStrictEqual(Buffer.from([0, 254])); + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.LQI_TABLE_REQUEST, 1)).toStrictEqual(Buffer.from([0, 1])); + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.LQI_TABLE_REQUEST, 254)).toStrictEqual(Buffer.from([0, 254])); }); it('buildRoutingTableRequest', () => { - expect(BuffaloZdo.buildRoutingTableRequest(1)).toStrictEqual(Buffer.from([0, 1])); - expect(BuffaloZdo.buildRoutingTableRequest(254)).toStrictEqual(Buffer.from([0, 254])); + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.ROUTING_TABLE_REQUEST, 1)).toStrictEqual(Buffer.from([0, 1])); + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.ROUTING_TABLE_REQUEST, 254)).toStrictEqual(Buffer.from([0, 254])); }); it('buildBindingTableRequest', () => { - expect(BuffaloZdo.buildBindingTableRequest(1)).toStrictEqual(Buffer.from([0, 1])); - expect(BuffaloZdo.buildBindingTableRequest(254)).toStrictEqual(Buffer.from([0, 254])); + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.BINDING_TABLE_REQUEST, 1)).toStrictEqual(Buffer.from([0, 1])); + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.BINDING_TABLE_REQUEST, 254)).toStrictEqual(Buffer.from([0, 254])); }); it('buildLeaveRequest', () => { - expect(BuffaloZdo.buildLeaveRequest(IEEE_ADDRESS2, Zdo.LeaveRequestFlags.WITHOUT_REJOIN)).toStrictEqual( + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.LEAVE_REQUEST, IEEE_ADDRESS2, Zdo.LeaveRequestFlags.WITHOUT_REJOIN)).toStrictEqual( Buffer.from([0, ...IEEE_ADDRESS2_BYTES, Zdo.LeaveRequestFlags.WITHOUT_REJOIN]), ); - expect(BuffaloZdo.buildLeaveRequest(IEEE_ADDRESS2, Zdo.LeaveRequestFlags.AND_REJOIN)).toStrictEqual( + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.LEAVE_REQUEST, IEEE_ADDRESS2, Zdo.LeaveRequestFlags.AND_REJOIN)).toStrictEqual( Buffer.from([0, ...IEEE_ADDRESS2_BYTES, Zdo.LeaveRequestFlags.AND_REJOIN]), ); }); it('buildPermitJoining', () => { - expect(BuffaloZdo.buildPermitJoining(254, 1, [])).toStrictEqual(Buffer.from([0, 254, 1])); + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.PERMIT_JOINING_REQUEST, 254, 1, [])).toStrictEqual(Buffer.from([0, 254, 1])); const tlvs = [ { @@ -632,7 +706,7 @@ describe('ZDO Buffalo', () => { }, }, ]; - expect(BuffaloZdo.buildPermitJoining(255, 1, tlvs)).toStrictEqual( + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.PERMIT_JOINING_REQUEST, 255, 1, tlvs)).toStrictEqual( Buffer.from([ 0, 255, @@ -647,29 +721,29 @@ describe('ZDO Buffalo', () => { }); it('buildScanChannelsRequest', () => { - expect(BuffaloZdo.buildScanChannelsRequest(ZSpec.ALL_802_15_4_CHANNELS, 3, 3)).toStrictEqual( - Buffer.from([0, ...uint32To8Array(ZSpec.ALL_802_15_4_CHANNELS_MASK), 3, 3]), - ); - expect(BuffaloZdo.buildScanChannelsRequest(ZSpec.ALL_802_15_4_CHANNELS, 64, 3)).toStrictEqual( - Buffer.from([0, ...uint32To8Array(ZSpec.ALL_802_15_4_CHANNELS_MASK), 64 /*, 3*/]), - ); + expect( + Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.NWK_UPDATE_REQUEST, ZSpec.ALL_802_15_4_CHANNELS, 3, 3, undefined, undefined), + ).toStrictEqual(Buffer.from([0, ...uint32To8Array(ZSpec.ALL_802_15_4_CHANNELS_MASK), 3, 3])); + expect( + Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.NWK_UPDATE_REQUEST, ZSpec.ALL_802_15_4_CHANNELS, 64, 3, undefined, undefined), + ).toStrictEqual(Buffer.from([0, ...uint32To8Array(ZSpec.ALL_802_15_4_CHANNELS_MASK), 64 /*, 3*/])); }); it('buildChannelChangeRequest', () => { - expect(BuffaloZdo.buildChannelChangeRequest(15, 1)).toStrictEqual( + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.NWK_UPDATE_REQUEST, [15], 0xfe, undefined, 1, undefined)).toStrictEqual( Buffer.from([0, ...uint32To8Array(ZSpec.Utils.channelsToUInt32Mask([15])), 0xfe, 1]), ); }); it('buildSetActiveChannelsAndNwkManagerIdRequest', () => { - expect(BuffaloZdo.buildSetActiveChannelsAndNwkManagerIdRequest(ZSpec.PREFERRED_802_15_4_CHANNELS, 3, 123)).toStrictEqual( - Buffer.from([0, ...uint32To8Array(ZSpec.PREFERRED_802_15_4_CHANNELS_MASK), 0xff, 3, ...uint16To8Array(123)]), - ); + expect( + Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.NWK_UPDATE_REQUEST, ZSpec.PREFERRED_802_15_4_CHANNELS, 0xff, undefined, 3, 123), + ).toStrictEqual(Buffer.from([0, ...uint32To8Array(ZSpec.PREFERRED_802_15_4_CHANNELS_MASK), 0xff, 3, ...uint16To8Array(123)])); }); it('buildEnhancedScanChannelsRequest', () => { const channelPages = [123, 54394, 29344]; - expect(BuffaloZdo.buildEnhancedScanChannelsRequest(channelPages, 5, 3, 1)).toStrictEqual( + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.NWK_ENHANCED_UPDATE_REQUEST, channelPages, 5, 3, undefined, undefined, 1)).toStrictEqual( Buffer.from([ 0, channelPages.length, @@ -681,7 +755,7 @@ describe('ZDO Buffalo', () => { 1, ]), ); - expect(BuffaloZdo.buildEnhancedScanChannelsRequest(channelPages, 6, 3, 1)).toStrictEqual( + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.NWK_ENHANCED_UPDATE_REQUEST, channelPages, 6, 3, undefined, undefined, 1)).toStrictEqual( Buffer.from([ 0, channelPages.length, @@ -696,15 +770,17 @@ describe('ZDO Buffalo', () => { it('buildEnhancedChannelChangeRequest', () => { const channelPage = 54394; - expect(BuffaloZdo.buildEnhancedChannelChangeRequest(channelPage, 3, 1)).toStrictEqual( - Buffer.from([0, 1, ...uint32To8Array(channelPage), 0xfe, 3, 1]), - ); + expect( + Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.NWK_ENHANCED_UPDATE_REQUEST, [channelPage], 0xfe, undefined, 3, undefined, 1), + ).toStrictEqual(Buffer.from([0, 1, ...uint32To8Array(channelPage), 0xfe, 3, 1])); }); it('buildEnhancedSetActiveChannelsAndNwkManagerIdRequest', () => { const channelPages = [123, 54394, 29344]; const nwkManagerAddr = 0xfe01; - expect(BuffaloZdo.buildEnhancedSetActiveChannelsAndNwkManagerIdRequest(channelPages, 2, nwkManagerAddr, 1)).toStrictEqual( + expect( + Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.NWK_ENHANCED_UPDATE_REQUEST, channelPages, 0xff, undefined, 2, nwkManagerAddr, 1), + ).toStrictEqual( Buffer.from([ 0, channelPages.length, @@ -720,7 +796,7 @@ describe('ZDO Buffalo', () => { }); it('buildNwkIEEEJoiningListRequest', () => { - expect(BuffaloZdo.buildNwkIEEEJoiningListRequest(3)).toStrictEqual(Buffer.from([0, 3])); + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.NWK_IEEE_JOINING_LIST_REQUEST, 3)).toStrictEqual(Buffer.from([0, 3])); }); it('buildNwkBeaconSurveyRequest', () => { @@ -728,19 +804,19 @@ describe('ZDO Buffalo', () => { scanChannelList: [], configurationBitmask: 0, }; - expect(BuffaloZdo.buildNwkBeaconSurveyRequest(tlv)).toStrictEqual(Buffer.from([0, 0, 2 - 1, 0, 0])); + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.NWK_BEACON_SURVEY_REQUEST, tlv)).toStrictEqual(Buffer.from([0, 0, 2 - 1, 0, 0])); const tlv2: BeaconSurveyConfigurationTLV = { scanChannelList: [34252], configurationBitmask: 1, }; - expect(BuffaloZdo.buildNwkBeaconSurveyRequest(tlv2)).toStrictEqual( + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.NWK_BEACON_SURVEY_REQUEST, tlv2)).toStrictEqual( Buffer.from([0, 0, 6 - 1, 1, ...uint32To8Array(tlv2.scanChannelList[0]), 1]), ); const tlv3: BeaconSurveyConfigurationTLV = { scanChannelList: [34252, 123], configurationBitmask: 1, }; - expect(BuffaloZdo.buildNwkBeaconSurveyRequest(tlv3)).toStrictEqual( + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.NWK_BEACON_SURVEY_REQUEST, tlv3)).toStrictEqual( Buffer.from([0, 0, 10 - 1, 2, ...uint32To8Array(tlv3.scanChannelList[0]), ...uint32To8Array(tlv3.scanChannelList[1]), 1]), ); }); @@ -750,28 +826,32 @@ describe('ZDO Buffalo', () => { eui64: IEEE_ADDRESS1, publicPoint: Buffer.alloc(Zdo.CURVE_PUBLIC_POINT_SIZE).fill(0xcd), }; - expect(BuffaloZdo.buildStartKeyNegotiationRequest(tlv)).toStrictEqual( + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.START_KEY_NEGOTIATION_REQUEST, tlv)).toStrictEqual( Buffer.from([0, 0, ZSpec.EUI64_SIZE + Zdo.CURVE_PUBLIC_POINT_SIZE - 1, ...IEEE_ADDRESS1_BYTES, ...tlv.publicPoint]), ); const tlv2: Curve25519PublicPointTLV = { eui64: IEEE_ADDRESS2, publicPoint: Buffer.alloc(Zdo.CURVE_PUBLIC_POINT_SIZE).fill(0x3c), }; - expect(BuffaloZdo.buildStartKeyNegotiationRequest(tlv2)).toStrictEqual( + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.START_KEY_NEGOTIATION_REQUEST, tlv2)).toStrictEqual( Buffer.from([0, 0, ZSpec.EUI64_SIZE + Zdo.CURVE_PUBLIC_POINT_SIZE - 1, ...IEEE_ADDRESS2_BYTES, ...tlv2.publicPoint]), ); }); it('buildRetrieveAuthenticationTokenRequest', () => { const tlv: AuthenticationTokenIdTLV = {tlvTypeTagId: 0}; - expect(BuffaloZdo.buildRetrieveAuthenticationTokenRequest(tlv)).toStrictEqual(Buffer.from([0, 0, 1 - 1, 0])); + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.RETRIEVE_AUTHENTICATION_TOKEN_REQUEST, tlv)).toStrictEqual(Buffer.from([0, 0, 1 - 1, 0])); const tlv2: AuthenticationTokenIdTLV = {tlvTypeTagId: 31}; - expect(BuffaloZdo.buildRetrieveAuthenticationTokenRequest(tlv2)).toStrictEqual(Buffer.from([0, 0, 1 - 1, 31])); + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.RETRIEVE_AUTHENTICATION_TOKEN_REQUEST, tlv2)).toStrictEqual( + Buffer.from([0, 0, 1 - 1, 31]), + ); }); it('buildGetAuthenticationLevelRequest', () => { const tlv: TargetIEEEAddressTLV = {ieee: IEEE_ADDRESS2}; - expect(BuffaloZdo.buildGetAuthenticationLevelRequest(tlv)).toStrictEqual(Buffer.from([0, 0, ZSpec.EUI64_SIZE - 1, ...IEEE_ADDRESS2_BYTES])); + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.GET_AUTHENTICATION_LEVEL_REQUEST, tlv)).toStrictEqual( + Buffer.from([0, 0, ZSpec.EUI64_SIZE - 1, ...IEEE_ADDRESS2_BYTES]), + ); }); it.each([ @@ -781,7 +861,9 @@ describe('ZDO Buffalo', () => { {panId: 0x6543, channel: 45, configurationParameters: 1}, ])('buildSetConfigurationRequest', ({panId, channel, configurationParameters}) => { expect( - BuffaloZdo.buildSetConfigurationRequest( + Zdo.Buffalo.buildRequest( + true, + Zdo.ClusterId.SET_CONFIGURATION_REQUEST, {panId} as NextPanIdChangeGlobalTLV, {channel} as NextChannelChangeGlobalTLV, {configurationParameters} as ConfigurationParametersGlobalTLV, @@ -803,8 +885,8 @@ describe('ZDO Buffalo', () => { }); it('buildGetConfigurationRequest', () => { - expect(BuffaloZdo.buildGetConfigurationRequest([84])).toStrictEqual(Buffer.from([0, 1, 84])); - expect(BuffaloZdo.buildGetConfigurationRequest([67, 71])).toStrictEqual(Buffer.from([0, 2, 67, 71])); + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.GET_CONFIGURATION_REQUEST, [84])).toStrictEqual(Buffer.from([0, 1, 84])); + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.GET_CONFIGURATION_REQUEST, [67, 71])).toStrictEqual(Buffer.from([0, 2, 67, 71])); }); it('buildStartKeyUpdateRequest', () => { @@ -818,7 +900,7 @@ describe('ZDO Buffalo', () => { fragmentationOptions: 1, maxIncomingTransferUnit: 2345, }; - expect(BuffaloZdo.buildStartKeyUpdateRequest(method, params)).toStrictEqual( + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.START_KEY_UPDATE_REQUEST, method, params)).toStrictEqual( Buffer.from([ 0, 0, @@ -839,14 +921,14 @@ describe('ZDO Buffalo', () => { const tlv: DeviceEUI64ListTLV = { eui64List: [IEEE_ADDRESS1], }; - expect(BuffaloZdo.buildDecommissionRequest(tlv)).toStrictEqual( + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.DECOMMISSION_REQUEST, tlv)).toStrictEqual( Buffer.from([0, 0, ZSpec.EUI64_SIZE * tlv.eui64List.length + 1 - 1, tlv.eui64List.length, ...IEEE_ADDRESS1_BYTES]), ); const tlv2: DeviceEUI64ListTLV = { eui64List: [IEEE_ADDRESS2, IEEE_ADDRESS1], }; - expect(BuffaloZdo.buildDecommissionRequest(tlv2)).toStrictEqual( + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.DECOMMISSION_REQUEST, tlv2)).toStrictEqual( Buffer.from([ 0, 0, @@ -860,7 +942,7 @@ describe('ZDO Buffalo', () => { const tlv3: DeviceEUI64ListTLV = { eui64List: [], }; - expect(BuffaloZdo.buildDecommissionRequest(tlv3)).toStrictEqual( + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.DECOMMISSION_REQUEST, tlv3)).toStrictEqual( Buffer.from([0, 0, ZSpec.EUI64_SIZE * tlv3.eui64List.length + 1 - 1, tlv3.eui64List.length]), ); }); @@ -870,7 +952,7 @@ describe('ZDO Buffalo', () => { senderEui64: IEEE_ADDRESS2, challengeValue: Buffer.alloc(Zdo.CHALLENGE_VALUE_SIZE).fill(0xfe), }; - expect(BuffaloZdo.buildChallengeRequest(tlv)).toStrictEqual( + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.CHALLENGE_REQUEST, tlv)).toStrictEqual( Buffer.from([0, 0, ZSpec.EUI64_SIZE + Zdo.CHALLENGE_VALUE_SIZE - 1, ...IEEE_ADDRESS2_BYTES, ...tlv.challengeValue]), ); @@ -878,105 +960,68 @@ describe('ZDO Buffalo', () => { senderEui64: IEEE_ADDRESS1, challengeValue: Buffer.from([0xfe, 0xac, 0x12, 0x23, 0x85, 0x8c, 0x7c, 0xa3]), }; - expect(BuffaloZdo.buildChallengeRequest(tlv2)).toStrictEqual( + expect(Zdo.Buffalo.buildRequest(true, Zdo.ClusterId.CHALLENGE_REQUEST, tlv2)).toStrictEqual( Buffer.from([0, 0, ZSpec.EUI64_SIZE + Zdo.CHALLENGE_VALUE_SIZE - 1, ...IEEE_ADDRESS1_BYTES, ...tlv2.challengeValue]), ); }); it.each([ - 'readNetworkAddressResponse', - 'readIEEEAddressResponse', - 'readNodeDescriptorResponse', - 'readPowerDescriptorResponse', - 'readSimpleDescriptorResponse', - 'readActiveEndpointsResponse', - 'readMatchDescriptorsResponse', - 'readSystemServerDiscoveryResponse', - 'readParentAnnounceResponse', - 'readBindResponse', - 'readUnbindResponse', - 'readClearAllBindingsResponse', - 'readLQITableResponse', - 'readRoutingTableResponse', - 'readBindingTableResponse', - 'readLeaveResponse', - 'readPermitJoiningResponse', - 'readNwkUpdateResponse', - 'readNwkEnhancedUpdateResponse', - 'readNwkIEEEJoiningListResponse', - 'readNwkUnsolicitedEnhancedUpdateResponse', - 'readNwkBeaconSurveyResponse', - 'readStartKeyNegotiationResponse', - 'readRetrieveAuthenticationTokenResponse', - 'readGetAuthenticationLevelResponse', - 'readSetConfigurationResponse', - 'readGetConfigurationResponse', - 'readStartKeyUpdateResponse', - 'readDecommissionResponse', - 'readChallengeResponse', - ])('Throws status error when reading unsuccessful response for %s', (func) => { - const buffalo = new BuffaloZdo(Buffer.from([Zdo.Status.INV_REQUESTTYPE, 1, 2, 3])); - expect(() => { - buffalo[func](); - }).toThrow(new Zdo.StatusError(Zdo.Status.INV_REQUESTTYPE)); - }); - - it.each([ - [Zdo.ClusterId.NETWORK_ADDRESS_RESPONSE, 'readNetworkAddressResponse'], - [Zdo.ClusterId.IEEE_ADDRESS_RESPONSE, 'readIEEEAddressResponse'], - [Zdo.ClusterId.NODE_DESCRIPTOR_RESPONSE, 'readNodeDescriptorResponse'], - [Zdo.ClusterId.POWER_DESCRIPTOR_RESPONSE, 'readPowerDescriptorResponse'], - [Zdo.ClusterId.SIMPLE_DESCRIPTOR_RESPONSE, 'readSimpleDescriptorResponse'], - [Zdo.ClusterId.ACTIVE_ENDPOINTS_RESPONSE, 'readActiveEndpointsResponse'], - [Zdo.ClusterId.MATCH_DESCRIPTORS_RESPONSE, 'readMatchDescriptorsResponse'], - [Zdo.ClusterId.END_DEVICE_ANNOUNCE, 'readEndDeviceAnnounce'], - [Zdo.ClusterId.SYSTEM_SERVER_DISCOVERY_RESPONSE, 'readSystemServerDiscoveryResponse'], - [Zdo.ClusterId.PARENT_ANNOUNCE_RESPONSE, 'readParentAnnounceResponse'], - [Zdo.ClusterId.BIND_RESPONSE, 'readBindResponse'], - [Zdo.ClusterId.UNBIND_RESPONSE, 'readUnbindResponse'], - [Zdo.ClusterId.CLEAR_ALL_BINDINGS_RESPONSE, 'readClearAllBindingsResponse'], - [Zdo.ClusterId.LQI_TABLE_RESPONSE, 'readLQITableResponse'], - [Zdo.ClusterId.ROUTING_TABLE_RESPONSE, 'readRoutingTableResponse'], - [Zdo.ClusterId.BINDING_TABLE_RESPONSE, 'readBindingTableResponse'], - [Zdo.ClusterId.LEAVE_RESPONSE, 'readLeaveResponse'], - [Zdo.ClusterId.PERMIT_JOINING_RESPONSE, 'readPermitJoiningResponse'], - [Zdo.ClusterId.NWK_UPDATE_RESPONSE, 'readNwkUpdateResponse'], - [Zdo.ClusterId.NWK_ENHANCED_UPDATE_RESPONSE, 'readNwkEnhancedUpdateResponse'], - [Zdo.ClusterId.NWK_IEEE_JOINING_LIST_REPONSE, 'readNwkIEEEJoiningListResponse'], - [Zdo.ClusterId.NWK_UNSOLICITED_ENHANCED_UPDATE_RESPONSE, 'readNwkUnsolicitedEnhancedUpdateResponse'], - [Zdo.ClusterId.NWK_BEACON_SURVEY_RESPONSE, 'readNwkBeaconSurveyResponse'], - [Zdo.ClusterId.START_KEY_NEGOTIATION_RESPONSE, 'readStartKeyNegotiationResponse'], - [Zdo.ClusterId.RETRIEVE_AUTHENTICATION_TOKEN_RESPONSE, 'readRetrieveAuthenticationTokenResponse'], - [Zdo.ClusterId.GET_AUTHENTICATION_LEVEL_RESPONSE, 'readGetAuthenticationLevelResponse'], - [Zdo.ClusterId.SET_CONFIGURATION_RESPONSE, 'readSetConfigurationResponse'], - [Zdo.ClusterId.GET_CONFIGURATION_RESPONSE, 'readGetConfigurationResponse'], - [Zdo.ClusterId.START_KEY_UPDATE_RESPONSE, 'readStartKeyUpdateResponse'], - [Zdo.ClusterId.DECOMMISSION_RESPONSE, 'readDecommissionResponse'], - [Zdo.ClusterId.CHALLENGE_RESPONSE, 'readChallengeResponse'], - ])('Reads response by cluster ID %s', (clusterId, func) => { - // @ts-expect-error TS typing is lost here ;( - const readSpy = jest.spyOn(BuffaloZdo.prototype, func).mockImplementationOnce(jest.fn()); // passing bogus data, don't want to actually call it - BuffaloZdo.readResponse(clusterId, Buffer.from([123])); - expect(readSpy).toHaveBeenCalledTimes(1); + Zdo.ClusterId.NETWORK_ADDRESS_RESPONSE, + Zdo.ClusterId.IEEE_ADDRESS_RESPONSE, + Zdo.ClusterId.NODE_DESCRIPTOR_RESPONSE, + Zdo.ClusterId.POWER_DESCRIPTOR_RESPONSE, + Zdo.ClusterId.SIMPLE_DESCRIPTOR_RESPONSE, + Zdo.ClusterId.ACTIVE_ENDPOINTS_RESPONSE, + Zdo.ClusterId.MATCH_DESCRIPTORS_RESPONSE, + Zdo.ClusterId.SYSTEM_SERVER_DISCOVERY_RESPONSE, + Zdo.ClusterId.PARENT_ANNOUNCE_RESPONSE, + Zdo.ClusterId.BIND_RESPONSE, + Zdo.ClusterId.UNBIND_RESPONSE, + Zdo.ClusterId.CLEAR_ALL_BINDINGS_RESPONSE, + Zdo.ClusterId.LQI_TABLE_RESPONSE, + Zdo.ClusterId.ROUTING_TABLE_RESPONSE, + Zdo.ClusterId.BINDING_TABLE_RESPONSE, + Zdo.ClusterId.LEAVE_RESPONSE, + Zdo.ClusterId.PERMIT_JOINING_RESPONSE, + Zdo.ClusterId.NWK_UPDATE_RESPONSE, + Zdo.ClusterId.NWK_ENHANCED_UPDATE_RESPONSE, + Zdo.ClusterId.NWK_IEEE_JOINING_LIST_RESPONSE, + Zdo.ClusterId.NWK_UNSOLICITED_ENHANCED_UPDATE_RESPONSE, + Zdo.ClusterId.NWK_BEACON_SURVEY_RESPONSE, + Zdo.ClusterId.START_KEY_NEGOTIATION_RESPONSE, + Zdo.ClusterId.RETRIEVE_AUTHENTICATION_TOKEN_RESPONSE, + Zdo.ClusterId.GET_AUTHENTICATION_LEVEL_RESPONSE, + Zdo.ClusterId.SET_CONFIGURATION_RESPONSE, + Zdo.ClusterId.GET_CONFIGURATION_RESPONSE, + Zdo.ClusterId.START_KEY_UPDATE_RESPONSE, + Zdo.ClusterId.DECOMMISSION_RESPONSE, + Zdo.ClusterId.CHALLENGE_RESPONSE, + ])('Returns status and undefined when reading unsuccessful response for %s', (clusterId) => { + const buffer = Buffer.from([1, Zdo.Status.INV_REQUESTTYPE, 1, 2, 3]); + expect(Zdo.Buffalo.readResponse(true, clusterId, buffer)).toStrictEqual([Zdo.Status.INV_REQUESTTYPE, undefined]); }); it('Throws when reading unknown cluster ID', () => { - const clusterId = Zdo.ClusterId.ACTIVE_ENDPOINTS_REQUEST; + const clusterId = 0x0005; expect(() => { - BuffaloZdo.readResponse(clusterId, Buffer.from([123])); + BuffaloZdo.readResponse(true, clusterId, Buffer.from([1, 123])); }).toThrow(`Unsupported response reading for cluster ID '${clusterId}'.`); }); it('readNetworkAddressResponse', () => { - const buffer = Buffer.from([Zdo.Status.SUCCESS, ...IEEE_ADDRESS1_BYTES, ...NODE_ID1_BYTES]); - expect(new BuffaloZdo(buffer).readNetworkAddressResponse()).toStrictEqual({ - eui64: IEEE_ADDRESS1, - nwkAddress: NODE_ID1, - startIndex: 0, - assocDevList: [], - } as NetworkAddressResponse); + const buffer = Buffer.from([1, Zdo.Status.SUCCESS, ...IEEE_ADDRESS1_BYTES, ...NODE_ID1_BYTES]); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.NETWORK_ADDRESS_RESPONSE, buffer)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + eui64: IEEE_ADDRESS1, + nwkAddress: NODE_ID1, + startIndex: 0, + assocDevList: [], + } as NetworkAddressResponse, + ]); const bufferWAssoc = Buffer.from([ + 1, Zdo.Status.SUCCESS, ...IEEE_ADDRESS2_BYTES, ...NODE_ID1_BYTES, @@ -985,23 +1030,30 @@ describe('ZDO Buffalo', () => { ...uint16To8Array(123), ...uint16To8Array(52523), ]); - expect(new BuffaloZdo(bufferWAssoc).readNetworkAddressResponse()).toStrictEqual({ - eui64: IEEE_ADDRESS2, - nwkAddress: NODE_ID1, - startIndex: 3, - assocDevList: [123, 52523], - } as NetworkAddressResponse); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.NETWORK_ADDRESS_RESPONSE, bufferWAssoc)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + eui64: IEEE_ADDRESS2, + nwkAddress: NODE_ID1, + startIndex: 3, + assocDevList: [123, 52523], + } as NetworkAddressResponse, + ]); }); it('readIEEEAddressResponse', () => { - const buffer = Buffer.from([Zdo.Status.SUCCESS, ...IEEE_ADDRESS1_BYTES, ...NODE_ID1_BYTES]); - expect(new BuffaloZdo(buffer).readIEEEAddressResponse()).toStrictEqual({ - eui64: IEEE_ADDRESS1, - nwkAddress: NODE_ID1, - startIndex: 0, - assocDevList: [], - } as IEEEAddressResponse); + const buffer = Buffer.from([1, Zdo.Status.SUCCESS, ...IEEE_ADDRESS1_BYTES, ...NODE_ID1_BYTES]); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.IEEE_ADDRESS_RESPONSE, buffer)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + eui64: IEEE_ADDRESS1, + nwkAddress: NODE_ID1, + startIndex: 0, + assocDevList: [], + } as IEEEAddressResponse, + ]); const bufferWAssoc = Buffer.from([ + 1, Zdo.Status.SUCCESS, ...IEEE_ADDRESS2_BYTES, ...NODE_ID1_BYTES, @@ -1010,16 +1062,20 @@ describe('ZDO Buffalo', () => { ...uint16To8Array(123), ...uint16To8Array(52523), ]); - expect(new BuffaloZdo(bufferWAssoc).readIEEEAddressResponse()).toStrictEqual({ - eui64: IEEE_ADDRESS2, - nwkAddress: NODE_ID1, - startIndex: 3, - assocDevList: [123, 52523], - } as IEEEAddressResponse); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.IEEE_ADDRESS_RESPONSE, bufferWAssoc)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + eui64: IEEE_ADDRESS2, + nwkAddress: NODE_ID1, + startIndex: 3, + assocDevList: [123, 52523], + } as IEEEAddressResponse, + ]); }); it('readNodeDescriptorResponse', () => { const buffer = Buffer.from([ + 1, Zdo.Status.SUCCESS, ...NODE_ID1_BYTES, 0b00100010, @@ -1032,30 +1088,33 @@ describe('ZDO Buffalo', () => { ...uint16To8Array(0x3cff), 0, ]); - expect(new BuffaloZdo(buffer).readNodeDescriptorResponse()).toStrictEqual({ - nwkAddress: NODE_ID1, - logicalType: 0b010, - fragmentationSupported: null, - apsFlags: 0, - frequencyBand: 0b00100, - capabilities: { - alternatePANCoordinator: 0, - deviceType: 1, - powerSource: 1, - rxOnWhenIdle: 1, - reserved1: 0, - reserved2: 0, - securityCapability: 0, - allocateAddress: 0, - }, - manufacturerCode: Zcl.ManufacturerCode.BARACODA_SA, - maxBufSize: 0x7c, - maxIncTxSize: 0x7eff, - serverMask: SERVER_MASK_R22, - maxOutTxSize: 0x3cff, - deprecated1: 0, - tlvs: [], - } as NodeDescriptorResponse); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.NODE_DESCRIPTOR_RESPONSE, buffer)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + nwkAddress: NODE_ID1, + logicalType: 0b010, + fragmentationSupported: undefined, + apsFlags: 0, + frequencyBand: 0b00100, + capabilities: { + alternatePANCoordinator: 0, + deviceType: 1, + powerSource: 1, + rxOnWhenIdle: 1, + reserved1: 0, + reserved2: 0, + securityCapability: 0, + allocateAddress: 0, + }, + manufacturerCode: Zcl.ManufacturerCode.BARACODA_SA, + maxBufSize: 0x7c, + maxIncTxSize: 0x7eff, + serverMask: SERVER_MASK_R22, + maxOutTxSize: 0x3cff, + deprecated1: 0, + tlvs: [], + } as NodeDescriptorResponse, + ]); const tlv: FragmentationParametersGlobalTLV = { nwkAddress: NODE_ID1, @@ -1063,6 +1122,7 @@ describe('ZDO Buffalo', () => { maxIncomingTransferUnit: 65352, }; const buffer2 = Buffer.from([ + 1, Zdo.Status.SUCCESS, ...NODE_ID1_BYTES, 0b00100010, @@ -1080,45 +1140,52 @@ describe('ZDO Buffalo', () => { tlv.fragmentationOptions!, ...uint16To8Array(tlv.maxIncomingTransferUnit!), ]); - expect(new BuffaloZdo(buffer2).readNodeDescriptorResponse()).toStrictEqual({ - nwkAddress: NODE_ID1, - logicalType: 0b010, - fragmentationSupported: true, - apsFlags: 0, - frequencyBand: 0b00100, - capabilities: { - alternatePANCoordinator: 0, - deviceType: 1, - powerSource: 1, - rxOnWhenIdle: 1, - reserved1: 0, - reserved2: 0, - securityCapability: 0, - allocateAddress: 0, - }, - manufacturerCode: Zcl.ManufacturerCode.BEIJING_RUYING_TECH_LIMITED, - maxBufSize: 0x3a, - maxIncTxSize: 0x7cff, - serverMask: SERVER_MASK_R23, - maxOutTxSize: 0x11ff, - deprecated1: 0, - tlvs: [{tagId: Zdo.GlobalTLV.FRAGMENTATION_PARAMETERS, length: 5, tlv}], - } as NodeDescriptorResponse); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.NODE_DESCRIPTOR_RESPONSE, buffer2)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + nwkAddress: NODE_ID1, + logicalType: 0b010, + fragmentationSupported: true, + apsFlags: 0, + frequencyBand: 0b00100, + capabilities: { + alternatePANCoordinator: 0, + deviceType: 1, + powerSource: 1, + rxOnWhenIdle: 1, + reserved1: 0, + reserved2: 0, + securityCapability: 0, + allocateAddress: 0, + }, + manufacturerCode: Zcl.ManufacturerCode.BEIJING_RUYING_TECH_LIMITED, + maxBufSize: 0x3a, + maxIncTxSize: 0x7cff, + serverMask: SERVER_MASK_R23, + maxOutTxSize: 0x11ff, + deprecated1: 0, + tlvs: [{tagId: Zdo.GlobalTLV.FRAGMENTATION_PARAMETERS, length: 5, tlv}], + } as NodeDescriptorResponse, + ]); }); it('readPowerDescriptorResponse', () => { - const buffer = Buffer.from([Zdo.Status.SUCCESS, ...NODE_ID1_BYTES, 0b10100100, 0b11110100]); - expect(new BuffaloZdo(buffer).readPowerDescriptorResponse()).toStrictEqual({ - nwkAddress: NODE_ID1, - currentPowerMode: 0b0100, - availPowerSources: 0b1010, - currentPowerSource: 0b0100, - currentPowerSourceLevel: 0b1111, - } as PowerDescriptorResponse); + const buffer = Buffer.from([1, Zdo.Status.SUCCESS, ...NODE_ID1_BYTES, 0b10100100, 0b11110100]); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.POWER_DESCRIPTOR_RESPONSE, buffer)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + nwkAddress: NODE_ID1, + currentPowerMode: 0b0100, + availPowerSources: 0b1010, + currentPowerSource: 0b0100, + currentPowerSourceLevel: 0b1111, + } as PowerDescriptorResponse, + ]); }); it('readSimpleDescriptorResponse', () => { const buffer = Buffer.from([ + 1, Zdo.Status.SUCCESS, ...NODE_ID1_BYTES, 8, @@ -1132,16 +1199,21 @@ describe('ZDO Buffalo', () => { 1, ...uint16To8Array(5322), ]); - expect(new BuffaloZdo(Buffer.from(buffer)).readSimpleDescriptorResponse()).toStrictEqual({ - nwkAddress: NODE_ID1, - endpoint: ZSpec.HA_ENDPOINT, - profileId: ZSpec.HA_PROFILE_ID, - deviceId: 123, - deviceVersion: 0, - inClusterList: [7653, 624], - outClusterList: [5322], - } as SimpleDescriptorResponse); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.SIMPLE_DESCRIPTOR_RESPONSE, buffer)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + nwkAddress: NODE_ID1, + length: 8, + endpoint: ZSpec.HA_ENDPOINT, + profileId: ZSpec.HA_PROFILE_ID, + deviceId: 123, + deviceVersion: 0, + inClusterList: [7653, 624], + outClusterList: [5322], + } as SimpleDescriptorResponse, + ]); const bufferEmpty = Buffer.from([ + 1, Zdo.Status.SUCCESS, ...NODE_ID1_BYTES, 8, @@ -1152,81 +1224,110 @@ describe('ZDO Buffalo', () => { 0, 0, ]); - expect(new BuffaloZdo(Buffer.from(bufferEmpty)).readSimpleDescriptorResponse()).toStrictEqual({ - nwkAddress: NODE_ID1, - endpoint: ZSpec.HA_ENDPOINT, - profileId: ZSpec.HA_PROFILE_ID, - deviceId: 123, - deviceVersion: 0, - inClusterList: [], - outClusterList: [], - } as SimpleDescriptorResponse); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.SIMPLE_DESCRIPTOR_RESPONSE, bufferEmpty)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + nwkAddress: NODE_ID1, + length: 8, + endpoint: ZSpec.HA_ENDPOINT, + profileId: ZSpec.HA_PROFILE_ID, + deviceId: 123, + deviceVersion: 0, + inClusterList: [], + outClusterList: [], + } as SimpleDescriptorResponse, + ]); }); it('readActiveEndpointsResponse', () => { - const buffer = Buffer.from([Zdo.Status.SUCCESS, ...NODE_ID1_BYTES, 2, 0xef, 0x87]); - expect(new BuffaloZdo(Buffer.from(buffer)).readActiveEndpointsResponse()).toStrictEqual({ - nwkAddress: NODE_ID1, - endpointList: [0xef, 0x87], - } as ActiveEndpointsResponse); - const bufferEmpty = Buffer.from([Zdo.Status.SUCCESS, ...NODE_ID1_BYTES, 0]); - expect(new BuffaloZdo(Buffer.from(bufferEmpty)).readActiveEndpointsResponse()).toStrictEqual({ - nwkAddress: NODE_ID1, - endpointList: [], - } as ActiveEndpointsResponse); + const buffer = Buffer.from([1, Zdo.Status.SUCCESS, ...NODE_ID1_BYTES, 2, 0xef, 0x87]); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.ACTIVE_ENDPOINTS_RESPONSE, buffer)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + nwkAddress: NODE_ID1, + endpointList: [0xef, 0x87], + } as ActiveEndpointsResponse, + ]); + const bufferEmpty = Buffer.from([1, Zdo.Status.SUCCESS, ...NODE_ID1_BYTES, 0]); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.ACTIVE_ENDPOINTS_RESPONSE, bufferEmpty)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + nwkAddress: NODE_ID1, + endpointList: [], + } as ActiveEndpointsResponse, + ]); }); it('readMatchDescriptorsResponse', () => { - const buffer = Buffer.from([Zdo.Status.SUCCESS, ...NODE_ID1_BYTES, 2, 0xef, 0x87]); - expect(new BuffaloZdo(Buffer.from(buffer)).readMatchDescriptorsResponse()).toStrictEqual({ - nwkAddress: NODE_ID1, - endpointList: [0xef, 0x87], - } as MatchDescriptorsResponse); - const bufferEmpty = Buffer.from([Zdo.Status.SUCCESS, ...NODE_ID1_BYTES, 0]); - expect(new BuffaloZdo(Buffer.from(bufferEmpty)).readMatchDescriptorsResponse()).toStrictEqual({ - nwkAddress: NODE_ID1, - endpointList: [], - } as MatchDescriptorsResponse); + const buffer = Buffer.from([1, Zdo.Status.SUCCESS, ...NODE_ID1_BYTES, 2, 0xef, 0x87]); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.MATCH_DESCRIPTORS_RESPONSE, buffer)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + nwkAddress: NODE_ID1, + endpointList: [0xef, 0x87], + } as MatchDescriptorsResponse, + ]); + const bufferEmpty = Buffer.from([1, Zdo.Status.SUCCESS, ...NODE_ID1_BYTES, 0]); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.MATCH_DESCRIPTORS_RESPONSE, bufferEmpty)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + nwkAddress: NODE_ID1, + endpointList: [], + } as MatchDescriptorsResponse, + ]); }); it('readEndDeviceAnnounce', () => { - const buffer = Buffer.from([...NODE_ID1_BYTES, ...IEEE_ADDRESS2_BYTES, 0b01000000]); - expect(new BuffaloZdo(Buffer.from(buffer)).readEndDeviceAnnounce()).toStrictEqual({ - nwkAddress: NODE_ID1, - eui64: IEEE_ADDRESS2, - capabilities: { - alternatePANCoordinator: 0, - deviceType: 0, - powerSource: 0, - rxOnWhenIdle: 0, - reserved1: 0, - reserved2: 0, - securityCapability: 1, - allocateAddress: 0, - }, - } as EndDeviceAnnounce); + const buffer = Buffer.from([1, ...NODE_ID1_BYTES, ...IEEE_ADDRESS2_BYTES, 0b01000000]); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.END_DEVICE_ANNOUNCE, buffer)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + nwkAddress: NODE_ID1, + eui64: IEEE_ADDRESS2, + capabilities: { + alternatePANCoordinator: 0, + deviceType: 0, + powerSource: 0, + rxOnWhenIdle: 0, + reserved1: 0, + reserved2: 0, + securityCapability: 1, + allocateAddress: 0, + }, + } as EndDeviceAnnounce, + ]); }); it('readSystemServerDiscoveryResponse', () => { - const buffer = Buffer.from([Zdo.Status.SUCCESS, ...uint16To8Array(SERVER_MASK_R23_BYTE)]); - expect(new BuffaloZdo(Buffer.from(buffer)).readSystemServerDiscoveryResponse()).toStrictEqual({ - serverMask: SERVER_MASK_R23, - } as SystemServerDiscoveryResponse); + const buffer = Buffer.from([1, Zdo.Status.SUCCESS, ...uint16To8Array(SERVER_MASK_R23_BYTE)]); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.SYSTEM_SERVER_DISCOVERY_RESPONSE, buffer)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + serverMask: SERVER_MASK_R23, + } as SystemServerDiscoveryResponse, + ]); }); it('readParentAnnounceResponse', () => { - const buffer = Buffer.from([Zdo.Status.SUCCESS, 2, ...IEEE_ADDRESS2_BYTES, ...IEEE_ADDRESS1_BYTES]); - expect(new BuffaloZdo(Buffer.from(buffer)).readParentAnnounceResponse()).toStrictEqual({ - children: [IEEE_ADDRESS2, IEEE_ADDRESS1], - }); - const bufferEmpty = Buffer.from([Zdo.Status.SUCCESS, 0]); - expect(new BuffaloZdo(Buffer.from(bufferEmpty)).readParentAnnounceResponse()).toStrictEqual({ - children: [], - }); + const buffer = Buffer.from([1, Zdo.Status.SUCCESS, 2, ...IEEE_ADDRESS2_BYTES, ...IEEE_ADDRESS1_BYTES]); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.PARENT_ANNOUNCE_RESPONSE, buffer)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + children: [IEEE_ADDRESS2, IEEE_ADDRESS1], + }, + ]); + const bufferEmpty = Buffer.from([1, Zdo.Status.SUCCESS, 0]); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.PARENT_ANNOUNCE_RESPONSE, bufferEmpty)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + children: [], + }, + ]); }); it('readLQITableResponse', () => { const buffer = Buffer.from([ + 1, Zdo.Status.SUCCESS, 16, 3, @@ -1246,75 +1347,88 @@ describe('ZDO Buffalo', () => { 1, 179, ]); - expect(new BuffaloZdo(Buffer.from(buffer)).readLQITableResponse()).toStrictEqual({ - neighborTableEntries: 16, - startIndex: 3, - entryList: [ - { - extendedPanId: EXT_PAN_ID2, - eui64: IEEE_ADDRESS1, - nwkAddress: NODE_ID2, - deviceType: 1, - rxOnWhenIdle: 1, - relationship: 2, - reserved1: 0, - permitJoining: 1, - reserved2: 0, - depth: 1, - lqi: 235, - }, - { - extendedPanId: EXT_PAN_ID1, - eui64: IEEE_ADDRESS2, - nwkAddress: NODE_ID1, - deviceType: 2, - rxOnWhenIdle: 0, - relationship: 4, - reserved1: 0, - permitJoining: 0, - reserved2: 0, - depth: 1, - lqi: 179, - }, - ], - } as LQITableResponse); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.LQI_TABLE_RESPONSE, buffer)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + neighborTableEntries: 16, + startIndex: 3, + entryList: [ + { + extendedPanId: EXT_PAN_ID2, + eui64: IEEE_ADDRESS1, + nwkAddress: NODE_ID2, + deviceType: 1, + rxOnWhenIdle: 1, + relationship: 2, + reserved1: 0, + permitJoining: 1, + reserved2: 0, + depth: 1, + lqi: 235, + }, + { + extendedPanId: EXT_PAN_ID1, + eui64: IEEE_ADDRESS2, + nwkAddress: NODE_ID1, + deviceType: 2, + rxOnWhenIdle: 0, + relationship: 4, + reserved1: 0, + permitJoining: 0, + reserved2: 0, + depth: 1, + lqi: 179, + }, + ], + } as LQITableResponse, + ]); - const bufferEmpty = Buffer.from([Zdo.Status.SUCCESS, 5, 4, 0]); - expect(new BuffaloZdo(Buffer.from(bufferEmpty)).readLQITableResponse()).toStrictEqual({ - neighborTableEntries: 5, - startIndex: 4, - entryList: [], - } as LQITableResponse); + const bufferEmpty = Buffer.from([1, Zdo.Status.SUCCESS, 5, 4, 0]); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.LQI_TABLE_RESPONSE, bufferEmpty)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + neighborTableEntries: 5, + startIndex: 4, + entryList: [], + } as LQITableResponse, + ]); }); it('readRoutingTableResponse', () => { - const buffer = Buffer.from([Zdo.Status.SUCCESS, 4, 3, 1, ...NODE_ID2_BYTES, 0b00101000, ...NODE_ID1_BYTES]); - expect(new BuffaloZdo(Buffer.from(buffer)).readRoutingTableResponse()).toStrictEqual({ - routingTableEntries: 4, - startIndex: 3, - entryList: [ - { - destinationAddress: NODE_ID2, - status: 0, - memoryConstrained: 1, - manyToOne: 0, - routeRecordRequired: 1, - reserved1: 0, - nextHopAddress: NODE_ID1, - }, - ], - } as RoutingTableResponse); + const buffer = Buffer.from([1, Zdo.Status.SUCCESS, 4, 3, 1, ...NODE_ID2_BYTES, 0b00101000, ...NODE_ID1_BYTES]); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.ROUTING_TABLE_RESPONSE, buffer)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + routingTableEntries: 4, + startIndex: 3, + entryList: [ + { + destinationAddress: NODE_ID2, + status: Zdo.RoutingTableStatus[0], + memoryConstrained: 1, + manyToOne: 0, + routeRecordRequired: 1, + reserved1: 0, + nextHopAddress: NODE_ID1, + }, + ], + } as RoutingTableResponse, + ]); - const bufferEmpty = Buffer.from([Zdo.Status.SUCCESS, 0, 0, 0]); - expect(new BuffaloZdo(Buffer.from(bufferEmpty)).readRoutingTableResponse()).toStrictEqual({ - routingTableEntries: 0, - startIndex: 0, - entryList: [], - } as RoutingTableResponse); + const bufferEmpty = Buffer.from([1, Zdo.Status.SUCCESS, 0, 0, 0]); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.ROUTING_TABLE_RESPONSE, bufferEmpty)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + routingTableEntries: 0, + startIndex: 0, + entryList: [], + } as RoutingTableResponse, + ]); }); it('readBindingTableResponse', () => { const buffer = Buffer.from([ + 1, Zdo.Status.SUCCESS, 1, 0, @@ -1335,48 +1449,55 @@ describe('ZDO Buffalo', () => { ...uint16To8Array(Zcl.Clusters.genAnalogInput.ID), 0x02, ]); - expect(new BuffaloZdo(Buffer.from(buffer)).readBindingTableResponse()).toStrictEqual({ - bindingTableEntries: 1, - startIndex: 0, - entryList: [ - { - sourceEui64: IEEE_ADDRESS1, - sourceEndpoint: 0xf0, - clusterId: Zcl.Clusters.barrierControl.ID, - destAddrMode: 0x03, - dest: IEEE_ADDRESS2, - destEndpoint: ZSpec.GP_ENDPOINT, - }, - { - sourceEui64: IEEE_ADDRESS2, - sourceEndpoint: 0x34, - clusterId: Zcl.Clusters.closuresShadeCfg.ID, - destAddrMode: 0x01, - dest: NODE_ID2, - destEndpoint: undefined, - }, - // invalid destAddrMode is ignored - // { - // sourceEui64: IEEE_ADDRESS2, - // sourceEndpoint: 0xf4, - // clusterId: Zcl.Clusters.genAnalogInput.ID, - // destAddrMode: 0x02, - // dest: undefined, - // destEndpoint: undefined, - // }, - ], - } as BindingTableResponse); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.BINDING_TABLE_RESPONSE, buffer)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + bindingTableEntries: 1, + startIndex: 0, + entryList: [ + { + sourceEui64: IEEE_ADDRESS1, + sourceEndpoint: 0xf0, + clusterId: Zcl.Clusters.barrierControl.ID, + destAddrMode: 0x03, + dest: IEEE_ADDRESS2, + destEndpoint: ZSpec.GP_ENDPOINT, + }, + { + sourceEui64: IEEE_ADDRESS2, + sourceEndpoint: 0x34, + clusterId: Zcl.Clusters.closuresShadeCfg.ID, + destAddrMode: 0x01, + dest: NODE_ID2, + destEndpoint: undefined, + }, + // invalid destAddrMode is ignored + // { + // sourceEui64: IEEE_ADDRESS2, + // sourceEndpoint: 0xf4, + // clusterId: Zcl.Clusters.genAnalogInput.ID, + // destAddrMode: 0x02, + // dest: undefined, + // destEndpoint: undefined, + // }, + ], + } as BindingTableResponse, + ]); - const bufferEmpty = Buffer.from([Zdo.Status.SUCCESS, 30, 2, 0]); - expect(new BuffaloZdo(Buffer.from(bufferEmpty)).readBindingTableResponse()).toStrictEqual({ - bindingTableEntries: 30, - startIndex: 2, - entryList: [], - } as BindingTableResponse); + const bufferEmpty = Buffer.from([1, Zdo.Status.SUCCESS, 30, 2, 0]); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.BINDING_TABLE_RESPONSE, bufferEmpty)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + bindingTableEntries: 30, + startIndex: 2, + entryList: [], + } as BindingTableResponse, + ]); }); it('readNwkUpdateResponse', () => { const buffer = Buffer.from([ + 1, Zdo.Status.SUCCESS, ...uint32To8Array(34732495), ...uint16To8Array(445), @@ -1386,16 +1507,20 @@ describe('ZDO Buffalo', () => { 0xff, 0x6f, ]); - expect(new BuffaloZdo(Buffer.from(buffer)).readNwkUpdateResponse()).toStrictEqual({ - scannedChannels: 34732495, - totalTransmissions: 445, - totalFailures: 34, - entryList: [0x43, 0xff, 0x6f], - } as NwkUpdateResponse); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.NWK_UPDATE_RESPONSE, buffer)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + scannedChannels: 34732495, + totalTransmissions: 445, + totalFailures: 34, + entryList: [0x43, 0xff, 0x6f], + } as NwkUpdateResponse, + ]); }); it('readNwkEnhancedUpdateResponse', () => { const buffer = Buffer.from([ + 1, Zdo.Status.SUCCESS, ...uint32To8Array(34732495), ...uint16To8Array(445), @@ -1405,35 +1530,55 @@ describe('ZDO Buffalo', () => { 0xff, 0x6f, ]); - expect(new BuffaloZdo(Buffer.from(buffer)).readNwkEnhancedUpdateResponse()).toStrictEqual({ - scannedChannels: 34732495, - totalTransmissions: 445, - totalFailures: 34, - entryList: [0x43, 0xff, 0x6f], - } as NwkEnhancedUpdateResponse); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.NWK_ENHANCED_UPDATE_RESPONSE, buffer)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + scannedChannels: 34732495, + totalTransmissions: 445, + totalFailures: 34, + entryList: [0x43, 0xff, 0x6f], + } as NwkEnhancedUpdateResponse, + ]); }); it('readNwkIEEEJoiningListResponse', () => { - const buffer = Buffer.from([Zdo.Status.SUCCESS, 3, Zdo.JoiningPolicy.IEEELIST_JOIN, 4, 0, 2, ...IEEE_ADDRESS2_BYTES, ...IEEE_ADDRESS1_BYTES]); - expect(new BuffaloZdo(Buffer.from(buffer)).readNwkIEEEJoiningListResponse()).toStrictEqual({ - updateId: 3, - joiningPolicy: Zdo.JoiningPolicy.IEEELIST_JOIN, - entryListTotal: 4, - startIndex: 0, - entryList: [IEEE_ADDRESS2, IEEE_ADDRESS1], - } as NwkIEEEJoiningListResponse); - const bufferEmpty = Buffer.from([Zdo.Status.SUCCESS, 0xff, Zdo.JoiningPolicy.ALL_JOIN, 0]); - expect(new BuffaloZdo(Buffer.from(bufferEmpty)).readNwkIEEEJoiningListResponse()).toStrictEqual({ - updateId: 0xff, - joiningPolicy: Zdo.JoiningPolicy.ALL_JOIN, - entryListTotal: 0, - startIndex: undefined, - entryList: undefined, - } as NwkIEEEJoiningListResponse); + const buffer = Buffer.from([ + 1, + Zdo.Status.SUCCESS, + 3, + Zdo.JoiningPolicy.IEEELIST_JOIN, + 4, + 0, + 2, + ...IEEE_ADDRESS2_BYTES, + ...IEEE_ADDRESS1_BYTES, + ]); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.NWK_IEEE_JOINING_LIST_RESPONSE, buffer)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + updateId: 3, + joiningPolicy: Zdo.JoiningPolicy.IEEELIST_JOIN, + entryListTotal: 4, + startIndex: 0, + entryList: [IEEE_ADDRESS2, IEEE_ADDRESS1], + } as NwkIEEEJoiningListResponse, + ]); + const bufferEmpty = Buffer.from([1, Zdo.Status.SUCCESS, 0xff, Zdo.JoiningPolicy.ALL_JOIN, 0]); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.NWK_IEEE_JOINING_LIST_RESPONSE, bufferEmpty)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + updateId: 0xff, + joiningPolicy: Zdo.JoiningPolicy.ALL_JOIN, + entryListTotal: 0, + startIndex: undefined, + entryList: undefined, + } as NwkIEEEJoiningListResponse, + ]); }); it('readNwkUnsolicitedEnhancedUpdateResponse', () => { const buffer = Buffer.from([ + 1, Zdo.Status.SUCCESS, ...uint32To8Array(9023342), ...uint16To8Array(3454), @@ -1441,68 +1586,81 @@ describe('ZDO Buffalo', () => { ...uint16To8Array(1239), 32, ]); - expect(new BuffaloZdo(Buffer.from(buffer)).readNwkUnsolicitedEnhancedUpdateResponse()).toStrictEqual({ - channelInUse: 9023342, - macTxUCastTotal: 3454, - macTxUCastFailures: 435, - macTxUCastRetries: 1239, - timePeriod: 32, - } as NwkUnsolicitedEnhancedUpdateResponse); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.NWK_UNSOLICITED_ENHANCED_UPDATE_RESPONSE, buffer)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + channelInUse: 9023342, + macTxUCastTotal: 3454, + macTxUCastFailures: 435, + macTxUCastRetries: 1239, + timePeriod: 32, + } as NwkUnsolicitedEnhancedUpdateResponse, + ]); }); it('readNwkBeaconSurveyResponse', () => { - const buffer = Buffer.from([Zdo.Status.SUCCESS, 0x01, 4 - 1, 14, 7, 5, 2, 0x02, 7 - 1, ...NODE_ID1_BYTES, 234, 1, ...NODE_ID2_BYTES, 223]); - expect(new BuffaloZdo(Buffer.from(buffer)).readNwkBeaconSurveyResponse()).toStrictEqual({ - tlvs: [ - { - tagId: 0x01, - length: 4, - tlv: { - totalBeaconsReceived: 14, - onNetworkBeacons: 7, - potentialParentBeacons: 5, - otherNetworkBeacons: 2, - } as BeaconSurveyResultsTLV, - }, - { - tagId: 0x02, - length: 7, - tlv: { - currentParentNwkAddress: NODE_ID1, - currentParentLQA: 234, - entryCount: 1, - potentialParents: [{nwkAddress: NODE_ID2, lqa: 223}], - } as PotentialParentsTLV, - }, - ], - } as NwkBeaconSurveyResponse); + const buffer = Buffer.from([1, Zdo.Status.SUCCESS, 0x01, 4 - 1, 14, 7, 5, 2, 0x02, 7 - 1, ...NODE_ID1_BYTES, 234, 1, ...NODE_ID2_BYTES, 223]); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.NWK_BEACON_SURVEY_RESPONSE, buffer)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + tlvs: [ + { + tagId: 0x01, + length: 4, + tlv: { + totalBeaconsReceived: 14, + onNetworkBeacons: 7, + potentialParentBeacons: 5, + otherNetworkBeacons: 2, + } as BeaconSurveyResultsTLV, + }, + { + tagId: 0x02, + length: 7, + tlv: { + currentParentNwkAddress: NODE_ID1, + currentParentLQA: 234, + entryCount: 1, + potentialParents: [{nwkAddress: NODE_ID2, lqa: 223}], + } as PotentialParentsTLV, + }, + ], + } as NwkBeaconSurveyResponse, + ]); - const bufferEmpty = Buffer.from([Zdo.Status.SUCCESS]); - expect(new BuffaloZdo(Buffer.from(bufferEmpty)).readNwkBeaconSurveyResponse()).toStrictEqual({ - tlvs: [], - } as NwkBeaconSurveyResponse); + const bufferEmpty = Buffer.from([1, Zdo.Status.SUCCESS]); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.NWK_BEACON_SURVEY_RESPONSE, bufferEmpty)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + tlvs: [], + } as NwkBeaconSurveyResponse, + ]); }); it('readStartKeyNegotiationResponse', () => { const buffer = Buffer.from([ + 1, Zdo.Status.SUCCESS, 0x00, ZSpec.EUI64_SIZE + Zdo.CURVE_PUBLIC_POINT_SIZE - 1, ...IEEE_ADDRESS2_BYTES, ...Buffer.alloc(Zdo.CURVE_PUBLIC_POINT_SIZE).fill(0xab), ]); - expect(new BuffaloZdo(Buffer.from(buffer)).readStartKeyNegotiationResponse()).toStrictEqual({ - tlvs: [ - { - tagId: 0x00, - length: ZSpec.EUI64_SIZE + Zdo.CURVE_PUBLIC_POINT_SIZE, - tlv: { - eui64: IEEE_ADDRESS2, - publicPoint: Buffer.alloc(Zdo.CURVE_PUBLIC_POINT_SIZE).fill(0xab), - } as Curve25519PublicPointTLV, - }, - ], - }); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.START_KEY_NEGOTIATION_RESPONSE, buffer)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + tlvs: [ + { + tagId: 0x00, + length: ZSpec.EUI64_SIZE + Zdo.CURVE_PUBLIC_POINT_SIZE, + tlv: { + eui64: IEEE_ADDRESS2, + publicPoint: Buffer.alloc(Zdo.CURVE_PUBLIC_POINT_SIZE).fill(0xab), + } as Curve25519PublicPointTLV, + }, + ], + }, + ]); }); it('readRetrieveAuthenticationTokenResponse', () => { @@ -1513,6 +1671,7 @@ describe('ZDO Buffalo', () => { maxIncomingTransferUnit: 65352, }; const buffer = Buffer.from([ + 1, Zdo.Status.SUCCESS, Zdo.GlobalTLV.FRAGMENTATION_PARAMETERS, 5 - 1, @@ -1520,18 +1679,25 @@ describe('ZDO Buffalo', () => { tlv.fragmentationOptions!, ...uint16To8Array(tlv.maxIncomingTransferUnit!), ]); - expect(new BuffaloZdo(Buffer.from(buffer)).readRetrieveAuthenticationTokenResponse()).toStrictEqual({ - tlvs: [{tagId: Zdo.GlobalTLV.FRAGMENTATION_PARAMETERS, length: 5, tlv}], - }); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.RETRIEVE_AUTHENTICATION_TOKEN_RESPONSE, buffer)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + tlvs: [{tagId: Zdo.GlobalTLV.FRAGMENTATION_PARAMETERS, length: 5, tlv}], + }, + ]); - const bufferEmpty = Buffer.from([Zdo.Status.SUCCESS]); - expect(new BuffaloZdo(Buffer.from(bufferEmpty)).readRetrieveAuthenticationTokenResponse()).toStrictEqual({ - tlvs: [], - }); + const bufferEmpty = Buffer.from([1, Zdo.Status.SUCCESS]); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.RETRIEVE_AUTHENTICATION_TOKEN_RESPONSE, bufferEmpty)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + tlvs: [], + }, + ]); }); it('readGetAuthenticationLevelResponse', () => { const buffer = Buffer.from([ + 1, Zdo.Status.SUCCESS, 0x00, 10 - 1, @@ -1539,23 +1705,27 @@ describe('ZDO Buffalo', () => { Zdo.InitialJoinMethod.INSTALL_CODE_KEY, Zdo.ActiveLinkKeyType.AUTHENTICATED_KEY_NEGOTIATION, ]); - expect(new BuffaloZdo(Buffer.from(buffer)).readGetAuthenticationLevelResponse()).toStrictEqual({ - tlvs: [ - { - tagId: 0x00, - length: 10, - tlv: { - remoteNodeIeee: IEEE_ADDRESS2, - initialJoinMethod: Zdo.InitialJoinMethod.INSTALL_CODE_KEY, - activeLinkKeyType: Zdo.ActiveLinkKeyType.AUTHENTICATED_KEY_NEGOTIATION, - } as DeviceAuthenticationLevelTLV, - }, - ], - }); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.GET_AUTHENTICATION_LEVEL_RESPONSE, buffer)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + tlvs: [ + { + tagId: 0x00, + length: 10, + tlv: { + remoteNodeIeee: IEEE_ADDRESS2, + initialJoinMethod: Zdo.InitialJoinMethod.INSTALL_CODE_KEY, + activeLinkKeyType: Zdo.ActiveLinkKeyType.AUTHENTICATED_KEY_NEGOTIATION, + } as DeviceAuthenticationLevelTLV, + }, + ], + }, + ]); }); it('readSetConfigurationResponse', () => { const buffer = Buffer.from([ + 1, Zdo.Status.SUCCESS, 0x00, 7 - 1, @@ -1567,36 +1737,42 @@ describe('ZDO Buffalo', () => { Zdo.GlobalTLV.ROUTER_INFORMATION, Zdo.Status.INV_REQUESTTYPE, ]); - expect(new BuffaloZdo(Buffer.from(buffer)).readSetConfigurationResponse()).toStrictEqual({ - tlvs: [ - { - tagId: 0x00, - length: 7, - tlv: { - count: 3, - tlvs: [ - {tagId: 0x00, processingStatus: Zdo.Status.SUCCESS}, - {tagId: Zdo.GlobalTLV.CONFIGURATION_PARAMETERS, processingStatus: Zdo.Status.SUCCESS}, - {tagId: Zdo.GlobalTLV.ROUTER_INFORMATION, processingStatus: Zdo.Status.INV_REQUESTTYPE}, - ], - } as ProcessingStatusTLV, - }, - ], - }); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.SET_CONFIGURATION_RESPONSE, buffer)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + tlvs: [ + { + tagId: 0x00, + length: 7, + tlv: { + count: 3, + tlvs: [ + {tagId: 0x00, processingStatus: Zdo.Status.SUCCESS}, + {tagId: Zdo.GlobalTLV.CONFIGURATION_PARAMETERS, processingStatus: Zdo.Status.SUCCESS}, + {tagId: Zdo.GlobalTLV.ROUTER_INFORMATION, processingStatus: Zdo.Status.INV_REQUESTTYPE}, + ], + } as ProcessingStatusTLV, + }, + ], + }, + ]); - const bufferEmpty = Buffer.from([Zdo.Status.SUCCESS, 0x00, 1 - 1, 0]); - expect(new BuffaloZdo(Buffer.from(bufferEmpty)).readSetConfigurationResponse()).toStrictEqual({ - tlvs: [ - { - tagId: 0x00, - length: 1, - tlv: { - count: 0, - tlvs: [], - } as ProcessingStatusTLV, - }, - ], - }); + const bufferEmpty = Buffer.from([1, Zdo.Status.SUCCESS, 0x00, 1 - 1, 0]); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.SET_CONFIGURATION_RESPONSE, bufferEmpty)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + tlvs: [ + { + tagId: 0x00, + length: 1, + tlv: { + count: 0, + tlvs: [], + } as ProcessingStatusTLV, + }, + ], + }, + ]); }); it('readGetConfigurationResponse', () => { @@ -1607,6 +1783,7 @@ describe('ZDO Buffalo', () => { maxIncomingTransferUnit: 65352, }; const buffer = Buffer.from([ + 1, Zdo.Status.SUCCESS, Zdo.GlobalTLV.FRAGMENTATION_PARAMETERS, 5 - 1, @@ -1614,18 +1791,25 @@ describe('ZDO Buffalo', () => { tlv.fragmentationOptions!, ...uint16To8Array(tlv.maxIncomingTransferUnit!), ]); - expect(new BuffaloZdo(Buffer.from(buffer)).readGetConfigurationResponse()).toStrictEqual({ - tlvs: [{tagId: Zdo.GlobalTLV.FRAGMENTATION_PARAMETERS, length: 5, tlv}], - }); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.GET_CONFIGURATION_RESPONSE, buffer)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + tlvs: [{tagId: Zdo.GlobalTLV.FRAGMENTATION_PARAMETERS, length: 5, tlv}], + }, + ]); - const bufferEmpty = Buffer.from([Zdo.Status.SUCCESS]); - expect(new BuffaloZdo(Buffer.from(bufferEmpty)).readGetConfigurationResponse()).toStrictEqual({ - tlvs: [], - }); + const bufferEmpty = Buffer.from([1, Zdo.Status.SUCCESS]); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.GET_CONFIGURATION_RESPONSE, bufferEmpty)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + tlvs: [], + }, + ]); }); it('readChallengeResponse', () => { const buffer = Buffer.from([ + 1, Zdo.Status.SUCCESS, 0x00, 32 - 1, @@ -1635,25 +1819,31 @@ describe('ZDO Buffalo', () => { ...uint32To8Array(12435682), ...Buffer.from([0xff, 0xfe, 0x34, 0x04, 0x49, 0x9f, 0x03, 0xbc]), ]); - expect(new BuffaloZdo(Buffer.from(buffer)).readChallengeResponse()).toStrictEqual({ - tlvs: [ - { - tagId: 0x00, - length: 32, - tlv: { - responderEui64: IEEE_ADDRESS1, - receivedChallengeValue: Buffer.alloc(Zdo.CHALLENGE_VALUE_SIZE).fill(0x39), - apsFrameCounter: 4302952, - challengeSecurityFrameCounter: 12435682, - mic: Buffer.from([0xff, 0xfe, 0x34, 0x04, 0x49, 0x9f, 0x03, 0xbc]), - } as APSFrameCounterResponseTLV, - }, - ], - }); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.CHALLENGE_RESPONSE, buffer)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + tlvs: [ + { + tagId: 0x00, + length: 32, + tlv: { + responderEui64: IEEE_ADDRESS1, + receivedChallengeValue: Buffer.alloc(Zdo.CHALLENGE_VALUE_SIZE).fill(0x39), + apsFrameCounter: 4302952, + challengeSecurityFrameCounter: 12435682, + mic: Buffer.from([0xff, 0xfe, 0x34, 0x04, 0x49, 0x9f, 0x03, 0xbc]), + } as APSFrameCounterResponseTLV, + }, + ], + }, + ]); - const bufferEmpty = Buffer.from([Zdo.Status.SUCCESS]); - expect(new BuffaloZdo(Buffer.from(bufferEmpty)).readChallengeResponse()).toStrictEqual({ - tlvs: [], - }); + const bufferEmpty = Buffer.from([1, Zdo.Status.SUCCESS]); + expect(Zdo.Buffalo.readResponse(true, Zdo.ClusterId.CHALLENGE_RESPONSE, bufferEmpty)).toStrictEqual([ + Zdo.Status.SUCCESS, + { + tlvs: [], + }, + ]); }); }); diff --git a/test/zspec/zdo/utils.test.ts b/test/zspec/zdo/utils.test.ts index dd4f6ba958..f0a84e5d3f 100644 --- a/test/zspec/zdo/utils.test.ts +++ b/test/zspec/zdo/utils.test.ts @@ -185,7 +185,7 @@ describe('ZDO Utils', () => { networkManager: 0, reserved1: 0, reserved2: 0, - stackComplianceResivion: 0, + stackComplianceRevision: 0, }, ], [ @@ -200,7 +200,7 @@ describe('ZDO Utils', () => { networkManager: 0, reserved1: 0, reserved2: 0, - stackComplianceResivion: 0, + stackComplianceRevision: 0, }, ], [ @@ -215,7 +215,7 @@ describe('ZDO Utils', () => { networkManager: 1, reserved1: 0, reserved2: 0, - stackComplianceResivion: 23, + stackComplianceRevision: 23, }, ], [ @@ -230,7 +230,7 @@ describe('ZDO Utils', () => { networkManager: 1, reserved1: 0, reserved2: 0, - stackComplianceResivion: 21, + stackComplianceRevision: 21, }, ], ])('Gets & Creates server mask for %s', (value, expected) => {