@@ -9,7 +9,14 @@ import {
99 type MACHeader ,
1010 ZigbeeMACConsts ,
1111} from "../zigbee/mac.js" ;
12- import { GlobalTlv , GlobalTlvConsts , readZigbeeTlvs } from "../zigbee/tlvs.js" ;
12+ import {
13+ GlobalTlv ,
14+ KeyNegotationProtocol ,
15+ KeyNegotationProtocolMask ,
16+ type PreSharedSecret ,
17+ PreSharedSecretMask ,
18+ readZigbeeTlvs ,
19+ } from "../zigbee/tlvs.js" ;
1320import { ZigbeeConsts , ZigbeeKeyType , type ZigbeeSecurityHeader , ZigbeeSecurityLevel } from "../zigbee/zigbee.js" ;
1421import {
1522 encodeZigbeeNWKFrame ,
@@ -34,6 +41,13 @@ const NS = "nwk-handler";
3441export interface NWKHandlerCallbacks {
3542 /** Send APS TRANSPORT_KEY for network key */
3643 onAPSSendTransportKeyNWK : ( destination16 : number , networkKey : Buffer , keySequenceNumber : number , destination64 : bigint ) => Promise < void > ;
44+ /** Send APS ZDO START_KEY_UPDATE_REQUEST */
45+ onAPSSendStartKeyUpdateRequest : (
46+ nwkDest16 : number ,
47+ nwkDest64 : bigint ,
48+ keyNegotiationProtocol : KeyNegotationProtocol ,
49+ preSharedSecret : PreSharedSecret ,
50+ ) => Promise < void > ;
3751}
3852
3953/** The number of OctetDurations until a route discovery expires. */
@@ -1300,15 +1314,15 @@ export class NWKHandler {
13001314 status = MACAssociationStatus . PAN_FULL ;
13011315 } else {
13021316 status = ZigbeeNWKConsts . ASSOC_STATUS_ADDR_CONFLICT ;
1303- requiresTransportKey = ! secured ;
1317+ requiresTransportKey = ! secured ; // only if Trust Center Rejoin
13041318 const neighbor = macHeader . source16 === nwkHeader . source16 ;
13051319
13061320 await this . #context. associate ( newAddress16 , nwkHeader . source64 , decodedCap , neighbor , false ) ;
13071321 }
13081322 } else {
13091323 newAddress16 = nwkHeader . source16 ;
13101324 status = MACAssociationStatus . SUCCESS ;
1311- requiresTransportKey = ! secured ;
1325+ requiresTransportKey = ! secured ; // only if Trust Center Rejoin
13121326 const neighbor = macHeader . source16 === nwkHeader . source16 ;
13131327
13141328 await this . #context. associate ( newAddress16 , nwkHeader . source64 , decodedCap , neighbor , false ) ;
@@ -1318,7 +1332,7 @@ export class NWKHandler {
13181332 await this . sendRejoinResp ( nwkHeader . source16 , newAddress16 , status ) ;
13191333
13201334 if ( requiresTransportKey ) {
1321- // XXX: is this spec ?
1335+ // XXX: use tunnel even if direct Coordinator<>rejoiner ?
13221336 await this . #callbacks. onAPSSendTransportKeyNWK (
13231337 newAddress16 ,
13241338 this . #context. netParams . networkKey ,
@@ -1878,8 +1892,9 @@ export class NWKHandler {
18781892 const commissioningType = data . readUInt8 ( offset ) ;
18791893 offset += 1 ;
18801894 const secured = nwkHeader . frameControl . security ;
1895+ const initialJoin = commissioningType === ZigbeeNWKCommissioningType . INITIAL_JOIN ;
18811896
1882- if ( secured && commissioningType === ZigbeeNWKCommissioningType . INITIAL_JOIN ) {
1897+ if ( secured && initialJoin ) {
18831898 return ; // per spec, drop
18841899 }
18851900
@@ -1888,30 +1903,39 @@ export class NWKHandler {
18881903 const decodedCap = decodeMACCapabilities ( capabilities ) ;
18891904 const [ globalTlvs ] = readZigbeeTlvs ( data , offset ) ;
18901905 const joinerTlv = globalTlvs [ GlobalTlv . JOINER_ENCAPSULATION ] ;
1891- let selectedKeyNegotiationMethod = GlobalTlvConsts . KEY_NEGOTATION_METHOD_STATIC ; // fallback
18921906
1893- if ( joinerTlv !== undefined ) {
1894- // device is R23
1895- const fragmentationParametersTlv = joinerTlv . additionalTlvs [ GlobalTlv . FRAGMENTATION_PARAMETERS ] ;
1896- const supportedKeyNegotiationMethodsTlv = joinerTlv . additionalTlvs [ GlobalTlv . SUPPORTED_KEY_NEGOTIATION_METHODS ] ;
1907+ if ( joinerTlv === undefined ) {
1908+ return ; // invalid
1909+ }
1910+
1911+ const supportedKeyNegotiationMethodsTlv = joinerTlv . additionalTlvs [ GlobalTlv . SUPPORTED_KEY_NEGOTIATION_METHODS ] ;
1912+ const rejoin = commissioningType === ZigbeeNWKCommissioningType . REJOIN ;
18971913
1898- if ( fragmentationParametersTlv !== undefined ) {
1899- // TODO
1914+ if ( supportedKeyNegotiationMethodsTlv === undefined ) {
1915+ if ( ! rejoin ) {
1916+ return ; // invalid
1917+ }
1918+ } else {
1919+ // TODO: support other protocols
1920+ if ( ! ( supportedKeyNegotiationMethodsTlv . keyNegotiationProtocolsBitmask & KeyNegotationProtocolMask . Z3 ) ) {
1921+ return ; // per spec, must always be supported
19001922 }
19011923
1902- if ( supportedKeyNegotiationMethodsTlv !== undefined ) {
1903- // TODO
1904- const bitmask = supportedKeyNegotiationMethodsTlv . keyNegotiationProtocolsBitmask ;
1924+ if ( ! ( supportedKeyNegotiationMethodsTlv . preSharedSecretsBitmask & PreSharedSecretMask . INSTALL_CODE_KEY ) ) {
1925+ // XXX: spec?
1926+ await this . sendCommissioningResponse ( nwkHeader . source16 , 0xffff , MACAssociationStatus . PAN_ACCESS_DENIED ) ;
19051927
1906- // TODO: by order of "most security"?
1907- if ( bitmask & GlobalTlvConsts . KEY_NEGOTATION_METHOD_SHA256 ) {
1908- selectedKeyNegotiationMethod = GlobalTlvConsts . KEY_NEGOTATION_METHOD_SHA256 ;
1909- } else if ( bitmask & GlobalTlvConsts . KEY_NEGOTATION_METHOD_MMO128 ) {
1910- selectedKeyNegotiationMethod = GlobalTlvConsts . KEY_NEGOTATION_METHOD_MMO128 ;
1911- }
1928+ return ; // others currently not supported
19121929 }
19131930 }
19141931
1932+ // TODO: store in state?
1933+ const fragmentationParametersTlv = joinerTlv . additionalTlvs [ GlobalTlv . FRAGMENTATION_PARAMETERS ] ;
1934+
1935+ if ( fragmentationParametersTlv === undefined ) {
1936+ return ; // invalid
1937+ }
1938+
19151939 logger . debug (
19161940 ( ) =>
19171941 `<=== NWK COMMISSIONING_REQUEST[macSrc=${ macHeader . source16 } :${ macHeader . source64 } nwkSrc=${ nwkHeader . source16 } :${ nwkHeader . source64 } sec=${ secured } type=${ commissioningType } cap=${ capabilities } ]` ,
@@ -1924,8 +1948,8 @@ export class NWKHandler {
19241948 let status : MACAssociationStatus | number = MACAssociationStatus . PAN_ACCESS_DENIED ;
19251949 let requiresTransportKey = false ;
19261950
1927- if ( commissioningType === ZigbeeNWKCommissioningType . INITIAL_JOIN ) {
1928- if ( this . #context. trustCenterPolicies . allowJoins ) {
1951+ if ( initialJoin ) {
1952+ if ( this . #context. macAssociationPermit && this . #context . trustCenterPolicies . allowJoins ) {
19291953 if ( this . #context. address16ToAddress64 . has ( nwkHeader . source16 ) ) {
19301954 // device address is conflicting, assign new one
19311955 newAddress16 = this . #context. assignNetworkAddress ( ) ;
@@ -1948,7 +1972,7 @@ export class NWKHandler {
19481972 await this . #context. associate ( newAddress16 , nwkHeader . source64 , decodedCap , neighbor , true ) ;
19491973 }
19501974 }
1951- } else if ( commissioningType === ZigbeeNWKCommissioningType . REJOIN ) {
1975+ } else if ( rejoin ) {
19521976 const device = this . #context. deviceTable . get ( nwkHeader . source64 ) ;
19531977
19541978 if ( device ?. authorized ) {
@@ -1961,15 +1985,15 @@ export class NWKHandler {
19611985 status = MACAssociationStatus . PAN_FULL ;
19621986 } else {
19631987 status = ZigbeeNWKConsts . ASSOC_STATUS_ADDR_CONFLICT ;
1964- requiresTransportKey = ! secured ;
1988+ requiresTransportKey = ! secured ; // only if Trust Center Rejoin
19651989 const neighbor = macHeader . source16 === nwkHeader . source16 ;
19661990
19671991 await this . #context. associate ( newAddress16 , nwkHeader . source64 , decodedCap , neighbor , false ) ;
19681992 }
19691993 } else {
19701994 newAddress16 = nwkHeader . source16 ;
19711995 status = MACAssociationStatus . SUCCESS ;
1972- requiresTransportKey = ! secured ;
1996+ requiresTransportKey = ! secured ; // only if Trust Center Rejoin
19731997 const neighbor = macHeader . source16 === nwkHeader . source16 ;
19741998
19751999 await this . #context. associate ( newAddress16 , nwkHeader . source64 , decodedCap , neighbor , false ) ;
@@ -1980,21 +2004,29 @@ export class NWKHandler {
19802004 await this . sendCommissioningResponse ( nwkHeader . source16 , newAddress16 , status ) ;
19812005
19822006 if ( requiresTransportKey ) {
1983- // TODO: might need to be different if R23 or not
1984- if ( selectedKeyNegotiationMethod === GlobalTlvConsts . KEY_NEGOTATION_METHOD_STATIC ) {
1985- const dest64 = this . #context. address16ToAddress64 . get ( newAddress16 ) ;
1986-
1987- if ( dest64 !== undefined && requiresTransportKey ) {
1988- await this . #callbacks. onAPSSendTransportKeyNWK (
1989- newAddress16 ,
1990- this . #context. netParams . networkKey ,
1991- this . #context. netParams . networkKeySequenceNumber ,
1992- dest64 ,
1993- ) ;
1994- this . #context. markNetworkKeyTransported ( dest64 ) ;
1995- }
2007+ if ( this . #context. trustCenterPolicies . keyNegotiationProtocol === KeyNegotationProtocol . Z3 ) {
2008+ await this . #callbacks. onAPSSendTransportKeyNWK (
2009+ newAddress16 ,
2010+ this . #context. netParams . networkKey ,
2011+ this . #context. netParams . networkKeySequenceNumber ,
2012+ nwkHeader . source64 ,
2013+ ) ;
2014+ this . #context. markNetworkKeyTransported ( nwkHeader . source64 ) ;
19962015 } else {
1997- // TODO START_KEY_UPDATE ZDO
2016+ if ( rejoin ) {
2017+ // At this time this Revision of the specification does not support negotiating a new link key during rejoin.
2018+ // Therefore, devices certified to this Revision SHALL not include the Supported Key Negotiation Methods Global TLV
2019+ // inside the Joiner Encapsulation TLV so it is clear to the Trust Center that the device does not support this behavior.
2020+ // Future revisions of this specification that support this would include this TLV as a clear sign the rejoining device supports this new functionality.
2021+ return ;
2022+ }
2023+
2024+ await this . #callbacks. onAPSSendStartKeyUpdateRequest (
2025+ newAddress16 ,
2026+ nwkHeader . source64 ,
2027+ this . #context. trustCenterPolicies . keyNegotiationProtocol ,
2028+ this . #context. trustCenterPolicies . preSharedSecret ,
2029+ ) ;
19982030 }
19992031 }
20002032 }
@@ -2029,7 +2061,7 @@ export class NWKHandler {
20292061 false , // nwkSecurity
20302062 ZigbeeConsts . COORDINATOR_ADDRESS , // nwkSource16
20312063 requestSource16 , // nwkDest16
2032- this . #context. address16ToAddress64 . get ( requestSource16 ) , // nwkDest64
2064+ this . #context. address16ToAddress64 . get ( newAddress16 ) , // nwkDest64
20332065 CONFIG_NWK_MAX_HOPS , // nwkRadius
20342066 ) ;
20352067 }
0 commit comments