Skip to content

Commit a3c3493

Browse files
committed
fix: network commissioning
1 parent 5d7dfdc commit a3c3493

File tree

16 files changed

+311
-93
lines changed

16 files changed

+311
-93
lines changed

src/drivers/ot-rcp-driver.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ export class OTRCPDriver {
133133
onAPSSendTransportKeyNWK: async (address16, key, keySeqNum, destination64) => {
134134
await this.apsHandler.sendTransportKeyNWK(address16, key, keySeqNum, destination64);
135135
},
136+
onAPSSendStartKeyUpdateRequest: async (nwkDest16, nwkDest64, keyNegotiationProtocol, preSharedSecret) => {
137+
await this.apsHandler.sendStartKeyUpdateRequest(nwkDest16, nwkDest64, keyNegotiationProtocol, preSharedSecret);
138+
},
136139
};
137140

138141
this.nwkHandler = new NWKHandler(this.context, this.macHandler, nwkCallbacks);

src/zigbee-stack/aps-handler.ts

Lines changed: 122 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -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";
1218
import { ZigbeeConsts, ZigbeeKeyType, type ZigbeeSecurityHeader, ZigbeeSecurityLevel } from "../zigbee/zigbee.js";
1319
import {
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

Comments
 (0)