@@ -8,7 +8,13 @@ import {
88 type MACHeader ,
99 ZigbeeMACConsts ,
1010} from "../zigbee/mac.js" ;
11- import { GlobalTlv , readZigbeeTlvs } from "../zigbee/tlvs.js" ;
11+ import {
12+ GlobalTlv ,
13+ type KeyNegotationProtocol ,
14+ type PreSharedSecret ,
15+ readZigbeeTlvs ,
16+ writeZigbeeTlvFragmentationParameters ,
17+ } from "../zigbee/tlvs.js" ;
1218import { ZigbeeConsts , ZigbeeKeyType , type ZigbeeSecurityHeader , ZigbeeSecurityLevel } from "../zigbee/zigbee.js" ;
1319import {
1420 decodeZigbeeAPSFrameControl ,
@@ -1053,57 +1059,78 @@ export class APSHandler {
10531059 ) ;
10541060
10551061 if ( apsHeader . profileId === ZigbeeConsts . ZDO_PROFILE_ID ) {
1056- if ( apsHeader . clusterId === ZigbeeConsts . END_DEVICE_ANNOUNCE ) {
1057- let offset = 1 ; // skip seq num
1058- const address16 = data . readUInt16LE ( offset ) ;
1059- offset += 2 ;
1060- const address64 = data . readBigUInt64LE ( offset ) ;
1061- offset += 8 ;
1062- const capabilities = data . readUInt8 ( offset ) ;
1063- offset += 1 ;
1064-
1065- const device = this . #context. deviceTable . get ( address64 ) ;
1066-
1067- if ( device === undefined ) {
1068- // unknown device, should have been added by `associate`, something's not right, ignore it
1069- return ;
1070- }
1062+ switch ( apsHeader . clusterId ) {
1063+ case ZigbeeConsts . END_DEVICE_ANNOUNCE : {
1064+ let offset = 1 ; // skip seq num
1065+ const address16 = data . readUInt16LE ( offset ) ;
1066+ offset += 2 ;
1067+ const address64 = data . readBigUInt64LE ( offset ) ;
1068+ offset += 8 ;
1069+ const capabilities = data . readUInt8 ( offset ) ;
1070+ offset += 1 ;
1071+
1072+ const device = this . #context. deviceTable . get ( address64 ) ;
1073+
1074+ if ( device === undefined ) {
1075+ // unknown device, should have been added by `associate`, something's not right, ignore it
1076+ return ;
1077+ }
10711078
1072- const decodedCap = decodeMACCapabilities ( capabilities ) ;
1079+ const decodedCap = decodeMACCapabilities ( capabilities ) ;
10731080
1074- if ( device . address16 !== address16 ) {
1075- this . #context. address16ToAddress64 . delete ( device . address16 ) ;
1076- this . #context. address16ToAddress64 . set ( address16 , address64 ) ;
1081+ if ( device . address16 !== address16 ) {
1082+ // change of address
1083+ this . #context. address16ToAddress64 . delete ( device . address16 ) ;
1084+ this . #context. address16ToAddress64 . set ( address16 , address64 ) ;
10771085
1078- device . address16 = address16 ;
1079- }
1086+ device . address16 = address16 ;
1087+ }
10801088
1081- // just in case
1082- device . capabilities = decodedCap ;
1089+ // just in case
1090+ device . capabilities = decodedCap ;
10831091
1084- await this . #context. savePeriodicState ( ) ;
1092+ await this . #context. savePeriodicState ( ) ;
10851093
1086- // TODO: ideally, this shouldn't trigger (prevents early interview process from app) until AFTER authorized=true
1087- setImmediate ( ( ) => {
1088- // if device is authorized, it means it completed the TC link key update, so, a rejoin
1089- // TODO: could flip authorized to true before the announce and count as rejoin when it shouldn't
1090- if ( device . authorized ) {
1091- this . #callbacks. onDeviceRejoined ( address16 , address64 , decodedCap ) ;
1092- } else {
1093- this . #callbacks. onDeviceJoined ( address16 , address64 , decodedCap ) ;
1094- }
1095- } ) ;
1096- } else {
1097- const isRequest = ( apsHeader . clusterId ! & 0x8000 ) === 0 ;
1094+ // TODO: ideally, this shouldn't trigger (prevents early interview process from app) until AFTER authorized=true
1095+ setImmediate ( ( ) => {
1096+ // if device is authorized, it means it completed the TC link key update, so, a rejoin
1097+ // TODO: could flip authorized to true before the announce and count as rejoin when it shouldn't
1098+ if ( device . authorized ) {
1099+ this . #callbacks. onDeviceRejoined ( address16 , address64 , decodedCap ) ;
1100+ } else {
1101+ this . #callbacks. onDeviceJoined ( address16 , address64 , decodedCap ) ;
1102+ }
1103+ } ) ;
10981104
1099- if ( isRequest ) {
1100- if ( this . isZDORequestForCoordinator ( apsHeader . clusterId ! , nwkHeader . destination16 , nwkHeader . destination64 , data ) ) {
1101- await this . respondToCoordinatorZDORequest ( data , apsHeader . clusterId ! , nwkHeader . source16 , nwkHeader . source64 ) ;
1105+ break ;
1106+ }
1107+ case ZigbeeConsts . START_KEY_UPDATE_RESPONSE : {
1108+ // skip seq num
1109+ const status = data . readUInt8 ( 1 ) ;
1110+
1111+ if ( status !== ZigbeeConsts . ZDO_SUCCESS ) {
1112+ // failed security, cleanup
1113+ await this . #context. disassociate ( nwkHeader . source16 , nwkHeader . source64 ) ;
11021114 }
11031115
1104- // don't emit received ZDO requests
1116+ // handled internally, don't emit
1117+ return ;
1118+ }
1119+ case undefined : {
11051120 return ;
11061121 }
1122+ default : {
1123+ const isRequest = ( apsHeader . clusterId & 0x8000 ) === 0 ;
1124+
1125+ if ( isRequest ) {
1126+ if ( this . isZDORequestForCoordinator ( apsHeader . clusterId , nwkHeader . destination16 , nwkHeader . destination64 , data ) ) {
1127+ await this . respondToCoordinatorZDORequest ( data , apsHeader . clusterId , nwkHeader . source16 , nwkHeader . source64 ) ;
1128+ }
1129+
1130+ // don't emit received ZDO requests
1131+ return ;
1132+ }
1133+ }
11071134 }
11081135 }
11091136
@@ -2169,6 +2196,60 @@ export class APSHandler {
21692196
21702197 // NOTE: sendRelayMessageUpstream DEVICE SCOPE: [unauthorized] routers (N/A), end devices (N/A)
21712198
2199+ /**
2200+ * 06-3474-23 #4.4.9.1 APSME-KEY-NEGOTIATION.request
2201+ *
2202+ * SPEC COMPLIANCE:
2203+ * - TODO
2204+ *
2205+ * DEVICE SCOPE: Trust Center
2206+ */
2207+ public async sendStartKeyUpdateRequest (
2208+ nwkDest16 : number ,
2209+ nwkDest64 : bigint ,
2210+ keyNegotiationProtocol : KeyNegotationProtocol ,
2211+ preSharedSecret : PreSharedSecret ,
2212+ ) : Promise < boolean > {
2213+ const ackHeader : ZigbeeAPSHeader = {
2214+ frameControl : {
2215+ frameType : ZigbeeAPSFrameType . DATA ,
2216+ deliveryMode : ZigbeeAPSDeliveryMode . UNICAST ,
2217+ ackFormat : false ,
2218+ security : false ,
2219+ ackRequest : true ,
2220+ extendedHeader : false ,
2221+ } ,
2222+ destEndpoint : ZigbeeConsts . ZDO_ENDPOINT ,
2223+ clusterId : ZigbeeConsts . START_KEY_UPDATE_REQUEST ,
2224+ profileId : ZigbeeConsts . ZDO_PROFILE_ID ,
2225+ sourceEndpoint : ZigbeeConsts . ZDO_ENDPOINT ,
2226+ counter : this . nextCounter ( ) ,
2227+ } ;
2228+ const apsPayload = Buffer . allocUnsafe ( 20 ) ;
2229+ let offset = 0 ;
2230+ offset = apsPayload . writeUInt8 ( this . nextZDOSeqNum ( ) , offset ) ;
2231+ // local TLV: Selected Key Negotiation Method
2232+ offset = apsPayload . writeUInt8 ( 0x00 , offset ) ;
2233+ offset = apsPayload . writeUInt8 ( 9 , offset ) ; // per spec, actual data length is `length field + 1`
2234+ offset = apsPayload . writeUInt8 ( keyNegotiationProtocol , offset ) ;
2235+ offset = apsPayload . writeUInt8 ( preSharedSecret , offset ) ;
2236+ offset = apsPayload . writeBigUInt64LE ( this . #context. netParams . eui64 , offset ) ;
2237+ writeZigbeeTlvFragmentationParameters ( apsPayload , offset , {
2238+ nwkAddress : ZigbeeConsts . COORDINATOR_ADDRESS ,
2239+ fragmentationOptions : 0b1 ,
2240+ maxIncomingTransferUnit : ZigbeeMACConsts . FRAME_MAX_SIZE ,
2241+ } ) ;
2242+
2243+ const apsFrame = encodeZigbeeAPSFrame (
2244+ ackHeader ,
2245+ apsPayload ,
2246+ // undefined,
2247+ // undefined,
2248+ ) ;
2249+
2250+ return await this . sendRelayMessageDownstream ( nwkDest16 , nwkDest64 , nwkDest64 , apsFrame ) ;
2251+ }
2252+
21722253 // #endregion
21732254
21742255 // #region ZDO Helpers
0 commit comments