zwave-js 13.2.0 → 13.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/build/lib/controller/Controller.d.ts +118 -9
  2. package/build/lib/controller/Controller.d.ts.map +1 -1
  3. package/build/lib/controller/Controller.js +1179 -81
  4. package/build/lib/controller/Controller.js.map +1 -1
  5. package/build/lib/controller/Inclusion.d.ts +44 -0
  6. package/build/lib/controller/Inclusion.d.ts.map +1 -1
  7. package/build/lib/controller/Inclusion.js +42 -1
  8. package/build/lib/controller/Inclusion.js.map +1 -1
  9. package/build/lib/driver/Driver.d.ts +16 -1
  10. package/build/lib/driver/Driver.d.ts.map +1 -1
  11. package/build/lib/driver/Driver.js +286 -95
  12. package/build/lib/driver/Driver.js.map +1 -1
  13. package/build/lib/driver/NetworkCache.d.ts +3 -0
  14. package/build/lib/driver/NetworkCache.d.ts.map +1 -1
  15. package/build/lib/driver/NetworkCache.js +37 -9
  16. package/build/lib/driver/NetworkCache.js.map +1 -1
  17. package/build/lib/driver/ZWaveOptions.d.ts +10 -3
  18. package/build/lib/driver/ZWaveOptions.d.ts.map +1 -1
  19. package/build/lib/driver/ZWaveOptions.js.map +1 -1
  20. package/build/lib/node/Node.d.ts +4 -3
  21. package/build/lib/node/Node.d.ts.map +1 -1
  22. package/build/lib/node/Node.js +130 -34
  23. package/build/lib/node/Node.js.map +1 -1
  24. package/build/lib/serialapi/application/ApplicationUpdateRequest.d.ts +5 -0
  25. package/build/lib/serialapi/application/ApplicationUpdateRequest.d.ts.map +1 -1
  26. package/build/lib/serialapi/application/ApplicationUpdateRequest.js +24 -1
  27. package/build/lib/serialapi/application/ApplicationUpdateRequest.js.map +1 -1
  28. package/build/lib/serialapi/capability/GetControllerCapabilitiesMessages.d.ts +2 -0
  29. package/build/lib/serialapi/capability/GetControllerCapabilitiesMessages.d.ts.map +1 -1
  30. package/build/lib/serialapi/capability/GetControllerCapabilitiesMessages.js +6 -0
  31. package/build/lib/serialapi/capability/GetControllerCapabilitiesMessages.js.map +1 -1
  32. package/build/lib/serialapi/capability/GetSerialApiInitDataMessages.d.ts +2 -9
  33. package/build/lib/serialapi/capability/GetSerialApiInitDataMessages.d.ts.map +1 -1
  34. package/build/lib/serialapi/capability/GetSerialApiInitDataMessages.js.map +1 -1
  35. package/build/lib/serialapi/capability/SerialAPISetupMessages.d.ts +27 -1
  36. package/build/lib/serialapi/capability/SerialAPISetupMessages.d.ts.map +1 -1
  37. package/build/lib/serialapi/capability/SerialAPISetupMessages.js +96 -1
  38. package/build/lib/serialapi/capability/SerialAPISetupMessages.js.map +1 -1
  39. package/build/lib/serialapi/network-mgmt/SetLearnModeMessages.d.ts +47 -0
  40. package/build/lib/serialapi/network-mgmt/SetLearnModeMessages.d.ts.map +1 -0
  41. package/build/lib/serialapi/network-mgmt/SetLearnModeMessages.js +137 -0
  42. package/build/lib/serialapi/network-mgmt/SetLearnModeMessages.js.map +1 -0
  43. package/build/lib/serialapi/nvm/ExtendedNVMOperationsMessages.d.ts +60 -0
  44. package/build/lib/serialapi/nvm/ExtendedNVMOperationsMessages.d.ts.map +1 -0
  45. package/build/lib/serialapi/nvm/ExtendedNVMOperationsMessages.js +201 -0
  46. package/build/lib/serialapi/nvm/ExtendedNVMOperationsMessages.js.map +1 -0
  47. package/package.json +9 -9
@@ -21,7 +21,6 @@ const deferred_promise_1 = require("alcalzone-shared/deferred-promise");
21
21
  const math_1 = require("alcalzone-shared/math");
22
22
  const typeguards_1 = require("alcalzone-shared/typeguards");
23
23
  const node_crypto_1 = __importDefault(require("node:crypto"));
24
- const node_util_1 = __importDefault(require("node:util"));
25
24
  const NetworkCache_1 = require("../driver/NetworkCache");
26
25
  const DeviceClass_1 = require("../node/DeviceClass");
27
26
  const Node_1 = require("../node/Node");
@@ -60,12 +59,14 @@ const RemoveFailedNodeMessages_1 = require("../serialapi/network-mgmt/RemoveFail
60
59
  const RemoveNodeFromNetworkRequest_1 = require("../serialapi/network-mgmt/RemoveNodeFromNetworkRequest");
61
60
  const ReplaceFailedNodeRequest_1 = require("../serialapi/network-mgmt/ReplaceFailedNodeRequest");
62
61
  const RequestNodeNeighborUpdateMessages_1 = require("../serialapi/network-mgmt/RequestNodeNeighborUpdateMessages");
62
+ const SetLearnModeMessages_1 = require("../serialapi/network-mgmt/SetLearnModeMessages");
63
63
  const SetPriorityRouteMessages_1 = require("../serialapi/network-mgmt/SetPriorityRouteMessages");
64
64
  const SetSUCNodeIDMessages_1 = require("../serialapi/network-mgmt/SetSUCNodeIDMessages");
65
65
  const ExtNVMReadLongBufferMessages_1 = require("../serialapi/nvm/ExtNVMReadLongBufferMessages");
66
66
  const ExtNVMReadLongByteMessages_1 = require("../serialapi/nvm/ExtNVMReadLongByteMessages");
67
67
  const ExtNVMWriteLongBufferMessages_1 = require("../serialapi/nvm/ExtNVMWriteLongBufferMessages");
68
68
  const ExtNVMWriteLongByteMessages_1 = require("../serialapi/nvm/ExtNVMWriteLongByteMessages");
69
+ const ExtendedNVMOperationsMessages_1 = require("../serialapi/nvm/ExtendedNVMOperationsMessages");
69
70
  const FirmwareUpdateNVMMessages_1 = require("../serialapi/nvm/FirmwareUpdateNVMMessages");
70
71
  const GetNVMIdMessages_1 = require("../serialapi/nvm/GetNVMIdMessages");
71
72
  const NVMOperationsMessages_1 = require("../serialapi/nvm/NVMOperationsMessages");
@@ -93,6 +94,7 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
93
94
  driver.registerRequestHandler(serial_1.FunctionType.AddNodeToNetwork, this.handleAddNodeStatusReport.bind(this));
94
95
  driver.registerRequestHandler(serial_1.FunctionType.RemoveNodeFromNetwork, this.handleRemoveNodeStatusReport.bind(this));
95
96
  driver.registerRequestHandler(serial_1.FunctionType.ReplaceFailedNode, this.handleReplaceNodeStatusReport.bind(this));
97
+ driver.registerRequestHandler(serial_1.FunctionType.SetLearnMode, this.handleLearnModeCallback.bind(this));
96
98
  }
97
99
  _type;
98
100
  get type() {
@@ -124,11 +126,32 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
124
126
  get ownNodeId() {
125
127
  return this._ownNodeId;
126
128
  }
127
- _isPrimary;
129
+ _dsk;
130
+ /**
131
+ * The device specific key (DSK) of the controller in binary format.
132
+ */
133
+ get dsk() {
134
+ if (this._dsk == undefined) {
135
+ const keyPair = this.driver.getLearnModeAuthenticatedKeyPair();
136
+ const publicKey = (0, core_1.extractRawECDHPublicKey)(keyPair.publicKey);
137
+ this._dsk = publicKey.subarray(0, 16);
138
+ }
139
+ return this._dsk;
140
+ }
141
+ /** @deprecated Use {@link role} instead */
128
142
  get isPrimary() {
129
- return this._isPrimary;
143
+ switch (this.role) {
144
+ case core_1.NOT_KNOWN:
145
+ return core_1.NOT_KNOWN;
146
+ case core_1.ControllerRole.Primary:
147
+ return true;
148
+ default:
149
+ return false;
150
+ }
130
151
  }
152
+ _isSecondary;
131
153
  _isUsingHomeIdFromOtherNetwork;
154
+ /** @deprecated Use {@link role} instead */
132
155
  get isUsingHomeIdFromOtherNetwork() {
133
156
  return this._isUsingHomeIdFromOtherNetwork;
134
157
  }
@@ -137,6 +160,7 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
137
160
  return this._isSISPresent;
138
161
  }
139
162
  _wasRealPrimary;
163
+ /** @deprecated Use {@link role} instead */
140
164
  get wasRealPrimary() {
141
165
  return this._wasRealPrimary;
142
166
  }
@@ -148,6 +172,7 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
148
172
  get isSUC() {
149
173
  return this._isSUC;
150
174
  }
175
+ _noNodesIncluded;
151
176
  _nodeType;
152
177
  get nodeType() {
153
178
  return this._nodeType;
@@ -269,6 +294,11 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
269
294
  get supportsTimers() {
270
295
  return this._supportsTimers;
271
296
  }
297
+ _supportedRegions;
298
+ /** Which RF regions are supported by the controller, including information about them */
299
+ get supportedRegions() {
300
+ return this._supportedRegions;
301
+ }
272
302
  _rfRegion;
273
303
  /** Which RF region the controller is currently set to, or `undefined` if it could not be determined (yet). This value is cached and can be changed through {@link setRFRegion}. */
274
304
  get rfRegion() {
@@ -363,6 +393,30 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
363
393
  set powerlevel(value) {
364
394
  this._powerlevel = value;
365
395
  }
396
+ /** The role of the controller on the network */
397
+ get role() {
398
+ if (this._wasRealPrimary)
399
+ return core_1.ControllerRole.Primary;
400
+ switch (this._isSecondary) {
401
+ case true:
402
+ return core_1.ControllerRole.Secondary;
403
+ case false:
404
+ return core_1.ControllerRole.Inclusion;
405
+ default:
406
+ return core_1.NOT_KNOWN;
407
+ }
408
+ }
409
+ /** Returns whether learn mode may be enabled on this controller */
410
+ get isLearnModePermitted() {
411
+ // The primary controller may only enter learn mode, if hasn't included nodes yet
412
+ if (this.role === core_1.ControllerRole.Primary) {
413
+ return !!this._noNodesIncluded;
414
+ }
415
+ else {
416
+ // Secondary controllers may only enter learn mode if they are not the SUC
417
+ return this._isSUC === false;
418
+ }
419
+ }
366
420
  /**
367
421
  * @internal
368
422
  * Remembers the indicator values set by another node
@@ -549,28 +603,7 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
549
603
  .map((fn) => `\n · ${serial_1.FunctionType[fn]} (${(0, shared_1.num2hex)(fn)})`)
550
604
  .join("")}`);
551
605
  // Request additional information about the controller/Z-Wave chip
552
- this.driver.controllerLog.print(`querying additional controller information...`);
553
- const initData = await this.driver.sendMessage(new GetSerialApiInitDataMessages_1.GetSerialApiInitDataRequest(this.driver));
554
- // and remember the new info
555
- this._zwaveApiVersion = initData.zwaveApiVersion;
556
- this._zwaveChipType = initData.zwaveChipType;
557
- this._isPrimary = initData.isPrimary;
558
- this._isSIS = initData.isSIS;
559
- this._nodeType = initData.nodeType;
560
- this._supportsTimers = initData.supportsTimers;
561
- // ignore the initVersion, no clue what to do with it
562
- this.driver.controllerLog.print(`received additional controller information:
563
- Z-Wave API version: ${this._zwaveApiVersion.version} (${this._zwaveApiVersion.kind})${this._zwaveChipType
564
- ? `
565
- Z-Wave chip type: ${typeof this._zwaveChipType === "string"
566
- ? this._zwaveChipType
567
- : `unknown (type: ${(0, shared_1.num2hex)(this._zwaveChipType.type)}, version: ${(0, shared_1.num2hex)(this._zwaveChipType.version)})`}`
568
- : ""}
569
- node type ${(0, shared_1.getEnumMemberName)(core_1.NodeType, this._nodeType)}
570
- controller role: ${this._isPrimary ? "primary" : "secondary"}
571
- controller is the SIS: ${this._isSIS}
572
- controller supports timers: ${this._supportsTimers}
573
- Z-Wave Classic nodes: ${initData.nodeIds.join(", ")}`);
606
+ const initData = await this.getSerialApiInitData();
574
607
  // Get basic controller version info
575
608
  this.driver.controllerLog.print(`querying version info...`);
576
609
  const version = await this.driver.sendMessage(new GetControllerVersionMessages_1.GetControllerVersionRequest(this.driver), {
@@ -602,22 +635,7 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
602
635
  // The SDK version cannot be queried directly, but we can deduce it from the protocol version
603
636
  this._sdkVersion = (0, ZWaveSDKVersions_1.protocolVersionToSDKVersion)(this._protocolVersion);
604
637
  // find out what the controller can do
605
- this.driver.controllerLog.print(`querying controller capabilities...`);
606
- const ctrlCaps = await this.driver.sendMessage(new GetControllerCapabilitiesMessages_1.GetControllerCapabilitiesRequest(this.driver), {
607
- supportCheck: false,
608
- });
609
- this._isPrimary = !ctrlCaps.isSecondary;
610
- this._isUsingHomeIdFromOtherNetwork =
611
- ctrlCaps.isUsingHomeIdFromOtherNetwork;
612
- this._isSISPresent = ctrlCaps.isSISPresent;
613
- this._wasRealPrimary = ctrlCaps.wasRealPrimary;
614
- this._isSUC = ctrlCaps.isStaticUpdateController;
615
- this.driver.controllerLog.print(`received controller capabilities:
616
- controller role: ${this._isPrimary ? "primary" : "secondary"}
617
- is the SUC: ${this._isSUC}
618
- started this network: ${!this._isUsingHomeIdFromOtherNetwork}
619
- SIS is present: ${this._isSISPresent}
620
- was real primary: ${this._wasRealPrimary}`);
638
+ await this.getControllerCapabilities();
621
639
  // If the serial API can be configured, figure out which sub commands are supported
622
640
  // This MUST be done after querying the SDK version due to a bug in some 7.xx firmwares, which incorrectly encode the bitmask
623
641
  if (this.isFunctionSupported(serial_1.FunctionType.SerialAPISetup)) {
@@ -679,8 +697,18 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
679
697
  }
680
698
  /** Tries to determine the LR capable replacement of the given region. If none is found, the given region is returned. */
681
699
  tryGetLRCapableRegion(region) {
682
- // There is no official API to query whether a given region is supported,
683
- // but there are ways to figure out if LR regions are.
700
+ if (this._supportedRegions) {
701
+ // If the region supports LR, use it
702
+ if (this._supportedRegions.get(region)?.supportsLongRange) {
703
+ return region;
704
+ }
705
+ // Find a possible LR capable superset for this region
706
+ for (const info of this._supportedRegions.values()) {
707
+ if (info.supportsLongRange && info.includesRegion === region) {
708
+ return info.region;
709
+ }
710
+ }
711
+ }
684
712
  // US_LR is the first supported LR region, so if the controller supports LR, US_LR is supported
685
713
  if (region === core_1.RFRegion.USA && this.isLongRangeCapable()) {
686
714
  return core_1.RFRegion["USA (Long Range)"];
@@ -692,6 +720,38 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
692
720
  * Queries the region and powerlevel settings and configures them if necessary
693
721
  */
694
722
  async queryAndConfigureRF() {
723
+ // Figure out which regions are supported
724
+ if (this.isSerialAPISetupCommandSupported(SerialAPISetupMessages_1.SerialAPISetupCommand.GetSupportedRegions)) {
725
+ this.driver.controllerLog.print(`Querying supported RF regions and their information...`);
726
+ const supportedRegions = await this.querySupportedRFRegions().catch(() => []);
727
+ this._supportedRegions = new Map();
728
+ for (const region of supportedRegions) {
729
+ try {
730
+ const info = await this.queryRFRegionInfo(region);
731
+ if (info.region === core_1.RFRegion.Unknown)
732
+ continue;
733
+ this._supportedRegions.set(region, info);
734
+ }
735
+ catch {
736
+ continue;
737
+ }
738
+ }
739
+ this.driver.controllerLog.print(`supported regions:${[...this._supportedRegions.values()]
740
+ .map((info) => {
741
+ let ret = `\n· ${(0, shared_1.getEnumMemberName)(core_1.RFRegion, info.region)}`;
742
+ if (info.includesRegion != undefined) {
743
+ ret += ` · superset of ${(0, shared_1.getEnumMemberName)(core_1.RFRegion, info.includesRegion)}`;
744
+ }
745
+ if (info.supportsLongRange) {
746
+ ret += " · ZWLR";
747
+ if (!info.supportsZWave) {
748
+ ret += " only";
749
+ }
750
+ }
751
+ return ret;
752
+ })
753
+ .join("")}`);
754
+ }
695
755
  // Check and possibly update the RF region to the desired value
696
756
  if (this.isSerialAPISetupCommandSupported(SerialAPISetupMessages_1.SerialAPISetupCommand.GetRFRegion)) {
697
757
  this.driver.controllerLog.print(`Querying configured RF region...`);
@@ -862,8 +922,9 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
862
922
  this.driver.controllerLog.print(`SUC has node ID ${this.sucNodeId}`);
863
923
  }
864
924
  // There needs to be a SUC/SIS in the network. If not, we promote ourselves to one if the following conditions are met:
865
- // We are the primary controller, but we are not SUC, there is no SUC and there is no SIS
866
- if (this._isPrimary
925
+ // We are the primary controller, but we are not SUC, there is no SUC and there is no SIS, and there are no nodes in the network yet
926
+ if (this.role === core_1.ControllerRole.Primary
927
+ && this._noNodesIncluded
867
928
  && this._sucNodeId === 0
868
929
  && !this._isSUC
869
930
  && !this._isSISPresent) {
@@ -872,6 +933,8 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
872
933
  const result = await this.configureSUC(this._ownNodeId, true, true);
873
934
  if (result) {
874
935
  this._sucNodeId = this._ownNodeId;
936
+ this._isSUC = true;
937
+ this._isSISPresent = true;
875
938
  }
876
939
  this.driver.controllerLog.print(`Promotion to SUC/SIS ${result ? "succeeded" : "failed"}.`, result ? undefined : "warn");
877
940
  }
@@ -879,11 +942,7 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
879
942
  this.driver.controllerLog.print(`Error while promoting to SUC/SIS: ${(0, shared_1.getErrorMessage)(e)}`, "error");
880
943
  }
881
944
  }
882
- // if it's a bridge controller, request the virtual nodes
883
- if (this.type === _Types_2.ZWaveLibraryTypes["Bridge Controller"]
884
- && this.isFunctionSupported(serial_1.FunctionType.FUNC_ID_ZW_GET_VIRTUAL_NODES)) {
885
- // TODO: send FUNC_ID_ZW_GET_VIRTUAL_NODES message
886
- }
945
+ // TODO: if it's a bridge controller, request the virtual nodes
887
946
  if (this.type !== _Types_2.ZWaveLibraryTypes["Bridge Controller"]
888
947
  && this.isFunctionSupported(serial_1.FunctionType.SetSerialApiTimeouts)) {
889
948
  const { ack, byte } = this.driver.options.timeouts;
@@ -1640,6 +1699,10 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
1640
1699
  }
1641
1700
  });
1642
1701
  }
1702
+ else if (msg instanceof ApplicationUpdateRequest_1.ApplicationUpdateRequestSUCIdChanged) {
1703
+ this._sucNodeId = msg.sucNodeID;
1704
+ // TODO: Emit event or what?
1705
+ }
1643
1706
  }
1644
1707
  /**
1645
1708
  * @internal
@@ -1824,6 +1887,10 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
1824
1887
  }
1825
1888
  // Remember that the node was granted the S0 security class
1826
1889
  node.securityClasses.set(core_1.SecurityClass.S0_Legacy, true);
1890
+ this.driver.controllerLog.logNode(node.id, {
1891
+ message: `Security S0 bootstrapping successful`,
1892
+ });
1893
+ // success 🎉
1827
1894
  }
1828
1895
  catch (e) {
1829
1896
  let errorMessage = `Security S0 bootstrapping failed, the node was not granted the S0 security class`;
@@ -2090,11 +2157,8 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
2090
2157
  const timerStartTAI2 = Date.now();
2091
2158
  // Generate ECDH key pair. We need to immediately send the other node our public key,
2092
2159
  // so it won't abort bootstrapping
2093
- const keyPair = await node_util_1.default.promisify(node_crypto_1.default.generateKeyPair)("x25519");
2094
- const publicKey = (0, core_1.decodeX25519KeyDER)(keyPair.publicKey.export({
2095
- type: "spki",
2096
- format: "der",
2097
- }));
2160
+ const keyPair = (0, core_1.generateECDHKeyPair)();
2161
+ const publicKey = (0, core_1.extractRawECDHPublicKey)(keyPair.publicKey);
2098
2162
  await api.sendPublicKey(publicKey);
2099
2163
  // After this, the node will start sending us a KEX SET every 10 seconds.
2100
2164
  // We won't be able to decode it until the DSK was verified
@@ -2137,11 +2201,7 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
2137
2201
  // After the user has verified the DSK, we can derive the shared secret
2138
2202
  // Z-Wave works with the "raw" keys, so this is a tad complicated
2139
2203
  const sharedSecret = node_crypto_1.default.diffieHellman({
2140
- publicKey: node_crypto_1.default.createPublicKey({
2141
- key: (0, core_1.encodeX25519KeyDERSPKI)(nodePublicKey),
2142
- format: "der",
2143
- type: "spki",
2144
- }),
2204
+ publicKey: (0, core_1.importRawECDHPublicKey)(nodePublicKey),
2145
2205
  privateKey: keyPair.privateKey,
2146
2206
  });
2147
2207
  // Derive temporary key from ECDH key pair - this will allow us to receive the node's KEX SET commands
@@ -2221,7 +2281,7 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
2221
2281
  return Inclusion_1.SecurityBootstrapFailure.S2WrongSecurityLevel;
2222
2282
  }
2223
2283
  // Confirm the keys - the node will start requesting the granted keys in response
2224
- await api.confirmGrantedKeys({
2284
+ await api.confirmRequestedKeys({
2225
2285
  requestCSA: kexParams.requestCSA,
2226
2286
  requestedKeys: [...kexParams.requestedKeys],
2227
2287
  supportedECDHProfiles: [...kexParams.supportedECDHProfiles],
@@ -2272,11 +2332,12 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
2272
2332
  await abort(cc_1.KEXFailType.KeyNotGranted);
2273
2333
  return Inclusion_1.SecurityBootstrapFailure.S2WrongSecurityLevel;
2274
2334
  }
2275
- // Send the node the requested key
2276
- await api.sendNetworkKey(securityClass, securityManager.getKeysForSecurityClass(securityClass).pnk);
2277
2335
  // We need to temporarily mark this security class as granted, so the following exchange will use this
2278
2336
  // key for decryption
2337
+ // FIXME: Is this actually necessary?
2279
2338
  node.securityClasses.set(securityClass, true);
2339
+ // Send the node the requested key
2340
+ await api.sendNetworkKey(securityClass, securityManager.getKeysForSecurityClass(securityClass).pnk);
2280
2341
  // And wait for verification
2281
2342
  const verify = await this.driver.waitForCommand((cc) => cc instanceof cc_1.Security2CCNetworkKeyVerify
2282
2343
  || cc instanceof cc_1.Security2CCKEXFail, cc_1.inclusionTimeouts.TA4).catch(() => "timeout");
@@ -4014,6 +4075,35 @@ ${associatedNodes.join(", ")}`,
4014
4075
  this._rfRegion = result.region;
4015
4076
  return result.region;
4016
4077
  }
4078
+ /**
4079
+ * Query the supported regions of the Z-Wave API Module
4080
+ *
4081
+ * **Note:** Applications should prefer using {@link getSupportedRFRegions} instead
4082
+ */
4083
+ async querySupportedRFRegions() {
4084
+ const result = await this.driver.sendMessage(new SerialAPISetupMessages_1.SerialAPISetup_GetSupportedRegionsRequest(this.driver));
4085
+ if (result instanceof SerialAPISetupMessages_1.SerialAPISetup_CommandUnsupportedResponse) {
4086
+ throw new core_1.ZWaveError(`Your hardware does not support getting the supported RF regions!`, core_1.ZWaveErrorCodes.Driver_NotSupported);
4087
+ }
4088
+ return result.supportedRegions;
4089
+ }
4090
+ /**
4091
+ * Query the supported regions of the Z-Wave API Module
4092
+ *
4093
+ * **Note:** Applications should prefer reading the cached value from {@link supportedRFRegions} instead
4094
+ */
4095
+ async queryRFRegionInfo(region) {
4096
+ const result = await this.driver.sendMessage(new SerialAPISetupMessages_1.SerialAPISetup_GetRegionInfoRequest(this.driver, { region }));
4097
+ if (result instanceof SerialAPISetupMessages_1.SerialAPISetup_CommandUnsupportedResponse) {
4098
+ throw new core_1.ZWaveError(`Your hardware does not support getting the RF region info!`, core_1.ZWaveErrorCodes.Driver_NotSupported);
4099
+ }
4100
+ return (0, shared_1.pick)(result, [
4101
+ "region",
4102
+ "supportsZWave",
4103
+ "supportsLongRange",
4104
+ "includesRegion",
4105
+ ]);
4106
+ }
4017
4107
  /**
4018
4108
  * Returns the RF regions supported by this controller, or `undefined` if the information is not known yet.
4019
4109
  *
@@ -4021,7 +4111,21 @@ ${associatedNodes.join(", ")}`,
4021
4111
  * for example `USA` which is a subset of `USA (Long Range)`
4022
4112
  */
4023
4113
  getSupportedRFRegions(filterSubsets = true) {
4024
- // FIXME: Once supported in firmware, query the controller for supported regions instead of hardcoding
4114
+ // If supported by the firmware, rely on the queried information
4115
+ if (this.isSerialAPISetupCommandSupported(SerialAPISetupMessages_1.SerialAPISetupCommand.GetSupportedRegions)) {
4116
+ if (this._supportedRegions == core_1.NOT_KNOWN)
4117
+ return core_1.NOT_KNOWN;
4118
+ const allRegions = new Set(this._supportedRegions.keys());
4119
+ if (filterSubsets) {
4120
+ for (const region of this._supportedRegions.values()) {
4121
+ if (region.includesRegion != undefined) {
4122
+ allRegions.delete(region.includesRegion);
4123
+ }
4124
+ }
4125
+ }
4126
+ return [...allRegions].sort((a, b) => a - b);
4127
+ }
4128
+ // Fallback: Hardcoded list of known supported regions
4025
4129
  const ret = new Set([
4026
4130
  // Always supported
4027
4131
  core_1.RFRegion.Europe,
@@ -4037,9 +4141,19 @@ ${associatedNodes.join(", ")}`,
4037
4141
  core_1.RFRegion["Default (EU)"],
4038
4142
  ]);
4039
4143
  if (this.isLongRangeCapable()) {
4144
+ // All LR capable controllers support USA Long Range
4040
4145
  ret.add(core_1.RFRegion["USA (Long Range)"]);
4041
- if (filterSubsets) {
4146
+ if (filterSubsets)
4042
4147
  ret.delete(core_1.RFRegion.USA);
4148
+ // EU Long Range was added in SDK 7.22 for 800 series chips
4149
+ // 7.22.1 adds support for querying the supported regions, so the following
4150
+ // is really only necessary for 7.22.0.
4151
+ if (typeof this._zwaveChipType === "string"
4152
+ && (0, core_1.getChipTypeAndVersion)(this._zwaveChipType)?.type === 8
4153
+ && this.sdkVersionGte("7.22")) {
4154
+ ret.add(core_1.RFRegion["Europe (Long Range)"]);
4155
+ if (filterSubsets)
4156
+ ret.delete(core_1.RFRegion.Europe);
4043
4157
  }
4044
4158
  }
4045
4159
  return [...ret].sort((a, b) => a - b);
@@ -4269,6 +4383,69 @@ ${associatedNodes.join(", ")}`,
4269
4383
  }
4270
4384
  return ret;
4271
4385
  }
4386
+ /** Request additional information about the controller/Z-Wave chip */
4387
+ async getSerialApiInitData() {
4388
+ this.driver.controllerLog.print(`querying additional controller information...`);
4389
+ const initData = await this.driver.sendMessage(new GetSerialApiInitDataMessages_1.GetSerialApiInitDataRequest(this.driver));
4390
+ this.driver.controllerLog.print(`received additional controller information:
4391
+ Z-Wave API version: ${initData.zwaveApiVersion.version} (${initData.zwaveApiVersion.kind})${initData.zwaveChipType
4392
+ ? `
4393
+ Z-Wave chip type: ${typeof initData.zwaveChipType === "string"
4394
+ ? initData.zwaveChipType
4395
+ : `unknown (type: ${(0, shared_1.num2hex)(initData.zwaveChipType.type)}, version: ${(0, shared_1.num2hex)(initData.zwaveChipType.version)})`}`
4396
+ : ""}
4397
+ node type ${(0, shared_1.getEnumMemberName)(core_1.NodeType, initData.nodeType)}
4398
+ controller role: ${initData.isPrimary ? "primary" : "secondary"}
4399
+ controller is the SIS: ${initData.isSIS}
4400
+ controller supports timers: ${initData.supportsTimers}
4401
+ Z-Wave Classic nodes: ${initData.nodeIds.join(", ")}`);
4402
+ const ret = {
4403
+ ...(0, shared_1.pick)(initData, [
4404
+ "zwaveApiVersion",
4405
+ "zwaveChipType",
4406
+ "isPrimary",
4407
+ "isSIS",
4408
+ "nodeType",
4409
+ "supportsTimers",
4410
+ ]),
4411
+ nodeIds: [...initData.nodeIds],
4412
+ // ignore the initVersion, no clue what to do with it
4413
+ };
4414
+ // and remember the new info
4415
+ this._zwaveApiVersion = initData.zwaveApiVersion;
4416
+ this._zwaveChipType = initData.zwaveChipType;
4417
+ this._isSecondary = !initData.isPrimary;
4418
+ this._isSIS = initData.isSIS;
4419
+ this._nodeType = initData.nodeType;
4420
+ this._supportsTimers = initData.supportsTimers;
4421
+ return ret;
4422
+ }
4423
+ /** Determines the controller's network role/capabilities */
4424
+ async getControllerCapabilities() {
4425
+ this.driver.controllerLog.print(`querying controller capabilities...`);
4426
+ const result = await this.driver.sendMessage(new GetControllerCapabilitiesMessages_1.GetControllerCapabilitiesRequest(this.driver), { supportCheck: false });
4427
+ const ret = {
4428
+ isSecondary: result.isSecondary,
4429
+ isUsingHomeIdFromOtherNetwork: result.isUsingHomeIdFromOtherNetwork,
4430
+ isSISPresent: result.isSISPresent,
4431
+ wasRealPrimary: result.wasRealPrimary,
4432
+ isSUC: result.isStaticUpdateController,
4433
+ noNodesIncluded: result.noNodesIncluded,
4434
+ };
4435
+ this._isSecondary = ret.isSecondary;
4436
+ this._isUsingHomeIdFromOtherNetwork = ret.isUsingHomeIdFromOtherNetwork;
4437
+ this._isSISPresent = ret.isSISPresent;
4438
+ this._wasRealPrimary = ret.wasRealPrimary;
4439
+ this._isSUC = ret.isSUC;
4440
+ this._noNodesIncluded = ret.noNodesIncluded;
4441
+ this.driver.controllerLog.print(`received controller capabilities:
4442
+ controller role: ${(0, shared_1.getEnumMemberName)(core_1.ControllerRole, this.role)}
4443
+ is the SUC: ${ret.isSUC}
4444
+ started this network: ${!ret.isUsingHomeIdFromOtherNetwork}
4445
+ SIS is present: ${ret.isSISPresent}
4446
+ was real primary: ${ret.wasRealPrimary}`);
4447
+ return ret;
4448
+ }
4272
4449
  /**
4273
4450
  * @internal
4274
4451
  * Deserializes the controller information and all nodes from the cache.
@@ -4407,9 +4584,11 @@ ${associatedNodes.join(", ")}`,
4407
4584
  return ret.buffer;
4408
4585
  }
4409
4586
  /**
4410
- * **Z-Wave 700 series only**
4587
+ * **Z-Wave 700+ series only**
4411
4588
  *
4412
4589
  * Reads a buffer from the external NVM at the given offset
4590
+ *
4591
+ * **Note:** Prefer {@link externalNVMReadBufferExt} if supported, as that command supports larger NVMs than 64 KiB.
4413
4592
  */
4414
4593
  async externalNVMReadBuffer700(offset, length) {
4415
4594
  const ret = await this.driver.sendMessage(new NVMOperationsMessages_1.NVMOperationsReadRequest(this.driver, {
@@ -4431,6 +4610,35 @@ ${associatedNodes.join(", ")}`,
4431
4610
  endOfFile: ret.status === NVMOperationsMessages_1.NVMOperationStatus.EndOfFile,
4432
4611
  };
4433
4612
  }
4613
+ /**
4614
+ * **Z-Wave 700+ series only**
4615
+ *
4616
+ * Reads a buffer from the external NVM at the given offset
4617
+ *
4618
+ * **Note:** If supported, this command should be preferred over {@link externalNVMReadBuffer700} as it supports larger NVMs than 64 KiB.
4619
+ */
4620
+ async externalNVMReadBufferExt(offset, length) {
4621
+ const ret = await this.driver.sendMessage(new ExtendedNVMOperationsMessages_1.ExtendedNVMOperationsReadRequest(this.driver, {
4622
+ offset,
4623
+ length,
4624
+ }));
4625
+ if (!ret.isOK()) {
4626
+ let message = "Could not read from the external NVM";
4627
+ if (ret.status
4628
+ === ExtendedNVMOperationsMessages_1.ExtendedNVMOperationStatus.Error_OperationInterference) {
4629
+ message += ": interference between read and write operation.";
4630
+ }
4631
+ else if (ret.status
4632
+ === ExtendedNVMOperationsMessages_1.ExtendedNVMOperationStatus.Error_OperationMismatch) {
4633
+ message += ": wrong operation requested.";
4634
+ }
4635
+ throw new core_1.ZWaveError(message, core_1.ZWaveErrorCodes.Controller_CommandError);
4636
+ }
4637
+ return {
4638
+ buffer: ret.bufferOrBitmask,
4639
+ endOfFile: ret.status === ExtendedNVMOperationsMessages_1.ExtendedNVMOperationStatus.EndOfFile,
4640
+ };
4641
+ }
4434
4642
  /**
4435
4643
  * **Z-Wave 500 series only**
4436
4644
  *
@@ -4448,9 +4656,12 @@ ${associatedNodes.join(", ")}`,
4448
4656
  return ret.success;
4449
4657
  }
4450
4658
  /**
4451
- * **Z-Wave 700 series only**
4659
+ * **Z-Wave 700+ series only**
4452
4660
  *
4453
4661
  * Writes a buffer to the external NVM at the given offset
4662
+ *
4663
+ * **Note:** Prefer {@link externalNVMWriteBufferExt} if supported, as that command supports larger NVMs than 64 KiB.
4664
+ *
4454
4665
  * **WARNING:** This function can write in the full NVM address space and is not offset to start at the application area.
4455
4666
  * Take care not to accidentally overwrite the protocol NVM area!
4456
4667
  */
@@ -4474,9 +4685,46 @@ ${associatedNodes.join(", ")}`,
4474
4685
  };
4475
4686
  }
4476
4687
  /**
4477
- * **Z-Wave 700 series only**
4688
+ * **Z-Wave 700+ series only**
4689
+ *
4690
+ * Writes a buffer to the external NVM at the given offset
4691
+ *
4692
+ * **Note:** If supported, this command should be preferred over {@link externalNVMWriteBuffer700} as it supports larger NVMs than 64 KiB.
4693
+ *
4694
+ * **WARNING:** This function can write in the full NVM address space and is not offset to start at the application area.
4695
+ * Take care not to accidentally overwrite the protocol NVM area!
4696
+ */
4697
+ async externalNVMWriteBufferExt(offset, buffer) {
4698
+ const ret = await this.driver.sendMessage(new ExtendedNVMOperationsMessages_1.ExtendedNVMOperationsWriteRequest(this.driver, {
4699
+ offset,
4700
+ buffer,
4701
+ }));
4702
+ if (!ret.isOK()) {
4703
+ let message = "Could not write to the external NVM";
4704
+ if (ret.status
4705
+ === ExtendedNVMOperationsMessages_1.ExtendedNVMOperationStatus.Error_OperationInterference) {
4706
+ message += ": interference between read and write operation.";
4707
+ }
4708
+ else if (ret.status
4709
+ === ExtendedNVMOperationsMessages_1.ExtendedNVMOperationStatus.Error_OperationMismatch) {
4710
+ message += ": wrong operation requested.";
4711
+ }
4712
+ else if (ret.status
4713
+ === ExtendedNVMOperationsMessages_1.ExtendedNVMOperationStatus.Error_SubCommandNotSupported) {
4714
+ message += ": sub-command not supported.";
4715
+ }
4716
+ throw new core_1.ZWaveError(message, core_1.ZWaveErrorCodes.Controller_CommandError);
4717
+ }
4718
+ return {
4719
+ endOfFile: ret.status === ExtendedNVMOperationsMessages_1.ExtendedNVMOperationStatus.EndOfFile,
4720
+ };
4721
+ }
4722
+ /**
4723
+ * **Z-Wave 700+ series only**
4478
4724
  *
4479
4725
  * Opens the controller's external NVM for reading/writing and returns the NVM size
4726
+ *
4727
+ * **Note:** Prefer {@link externalNVMOpenExt} if supported, as that command supports larger NVMs than 64 KiB.
4480
4728
  */
4481
4729
  async externalNVMOpen() {
4482
4730
  const ret = await this.driver.sendMessage(new NVMOperationsMessages_1.NVMOperationsOpenRequest(this.driver));
@@ -4486,9 +4734,30 @@ ${associatedNodes.join(", ")}`,
4486
4734
  return ret.offsetOrSize;
4487
4735
  }
4488
4736
  /**
4489
- * **Z-Wave 700 series only**
4737
+ * **Z-Wave 700+ series only**
4738
+ *
4739
+ * Opens the controller's external NVM for reading/writing and returns the NVM size and supported operations.
4740
+ *
4741
+ * **Note:** If supported, this command should be preferred over {@link externalNVMOpen} as it supports larger NVMs than 64 KiB.
4742
+ */
4743
+ async externalNVMOpenExt() {
4744
+ const ret = await this.driver.sendMessage(new ExtendedNVMOperationsMessages_1.ExtendedNVMOperationsOpenRequest(this.driver));
4745
+ if (!ret.isOK()) {
4746
+ throw new core_1.ZWaveError("Failed to open the external NVM", core_1.ZWaveErrorCodes.Controller_CommandError);
4747
+ }
4748
+ const size = ret.offsetOrSize;
4749
+ const supportedOperations = (0, core_1.parseBitMask)(ret.bufferOrBitmask, ExtendedNVMOperationsMessages_1.ExtendedNVMOperationsCommand.Open);
4750
+ return {
4751
+ size,
4752
+ supportedOperations,
4753
+ };
4754
+ }
4755
+ /**
4756
+ * **Z-Wave 700+ series only**
4490
4757
  *
4491
4758
  * Closes the controller's external NVM
4759
+ *
4760
+ * **Note:** Prefer {@link externalNVMCloseExt} if supported, as that command supports larger NVMs than 64 KiB.
4492
4761
  */
4493
4762
  async externalNVMClose() {
4494
4763
  const ret = await this.driver.sendMessage(new NVMOperationsMessages_1.NVMOperationsCloseRequest(this.driver));
@@ -4496,6 +4765,19 @@ ${associatedNodes.join(", ")}`,
4496
4765
  throw new core_1.ZWaveError("Failed to close the external NVM", core_1.ZWaveErrorCodes.Controller_CommandError);
4497
4766
  }
4498
4767
  }
4768
+ /**
4769
+ * **Z-Wave 700+ series only**
4770
+ *
4771
+ * Closes the controller's external NVM
4772
+ *
4773
+ * **Note:** If supported, this command should be preferred over {@link externalNVMClose} as it supports larger NVMs than 64 KiB.
4774
+ */
4775
+ async externalNVMCloseExt() {
4776
+ const ret = await this.driver.sendMessage(new ExtendedNVMOperationsMessages_1.ExtendedNVMOperationsCloseRequest(this.driver));
4777
+ if (!ret.isOK()) {
4778
+ throw new core_1.ZWaveError("Failed to close the external NVM", core_1.ZWaveErrorCodes.Controller_CommandError);
4779
+ }
4780
+ }
4499
4781
  /**
4500
4782
  * Creates a backup of the NVM and returns the raw data as a Buffer. The Z-Wave radio is turned off/on automatically.
4501
4783
  * @param onProgress Can be used to monitor the progress of the operation, which may take several seconds up to a few minutes depending on the NVM size
@@ -4561,8 +4843,24 @@ ${associatedNodes.join(", ")}`,
4561
4843
  return ret;
4562
4844
  }
4563
4845
  async backupNVMRaw700(onProgress) {
4846
+ let open;
4847
+ let read;
4848
+ let close;
4849
+ if (this.supportedFunctionTypes?.includes(serial_1.FunctionType.ExtendedNVMOperations)) {
4850
+ open = async () => {
4851
+ const { size } = await this.externalNVMOpenExt();
4852
+ return size;
4853
+ };
4854
+ read = (offset, length) => this.externalNVMReadBufferExt(offset, length);
4855
+ close = () => this.externalNVMCloseExt();
4856
+ }
4857
+ else {
4858
+ open = () => this.externalNVMOpen();
4859
+ read = (offset, length) => this.externalNVMReadBuffer700(offset, length);
4860
+ close = () => this.externalNVMClose();
4861
+ }
4564
4862
  // Open NVM for reading
4565
- const size = await this.externalNVMOpen();
4863
+ const size = await open();
4566
4864
  const ret = Buffer.allocUnsafe(size);
4567
4865
  let offset = 0;
4568
4866
  // Try reading the maximum size at first, the Serial API should return chunks in a size it supports
@@ -4570,8 +4868,7 @@ ${associatedNodes.join(", ")}`,
4570
4868
  let chunkSize = Math.min(0xff, ret.length);
4571
4869
  try {
4572
4870
  while (offset < ret.length) {
4573
- const { buffer: chunk, endOfFile } = await this
4574
- .externalNVMReadBuffer700(offset, Math.min(chunkSize, ret.length - offset));
4871
+ const { buffer: chunk, endOfFile } = await read(offset, Math.min(chunkSize, ret.length - offset));
4575
4872
  if (chunkSize === 0xff && chunk.length === 0) {
4576
4873
  // Some SDK versions return an empty buffer when trying to read a buffer that is too long
4577
4874
  // Fallback to a sane (but maybe slow) size
@@ -4591,7 +4888,7 @@ ${associatedNodes.join(", ")}`,
4591
4888
  }
4592
4889
  finally {
4593
4890
  // Whatever happens, close the NVM
4594
- await this.externalNVMClose();
4891
+ await close();
4595
4892
  }
4596
4893
  return ret;
4597
4894
  }
@@ -4735,8 +5032,27 @@ ${associatedNodes.join(", ")}`,
4735
5032
  }
4736
5033
  }
4737
5034
  async restoreNVMRaw700(nvmData, onProgress) {
5035
+ let open;
5036
+ let read;
5037
+ let write;
5038
+ let close;
5039
+ if (this.supportedFunctionTypes?.includes(serial_1.FunctionType.ExtendedNVMOperations)) {
5040
+ open = async () => {
5041
+ const { size } = await this.externalNVMOpenExt();
5042
+ return size;
5043
+ };
5044
+ read = (offset, length) => this.externalNVMReadBufferExt(offset, length);
5045
+ write = (offset, buffer) => this.externalNVMWriteBufferExt(offset, buffer);
5046
+ close = () => this.externalNVMCloseExt();
5047
+ }
5048
+ else {
5049
+ open = () => this.externalNVMOpen();
5050
+ read = (offset, length) => this.externalNVMReadBuffer700(offset, length);
5051
+ write = (offset, buffer) => this.externalNVMWriteBuffer700(offset, buffer);
5052
+ close = () => this.externalNVMClose();
5053
+ }
4738
5054
  // Open NVM for reading
4739
- const size = await this.externalNVMOpen();
5055
+ const size = await open();
4740
5056
  if (size !== nvmData.length) {
4741
5057
  throw new core_1.ZWaveError("The given data does not match the NVM size - cannot restore!", core_1.ZWaveErrorCodes.Argument_Invalid);
4742
5058
  }
@@ -4744,12 +5060,12 @@ ${associatedNodes.join(", ")}`,
4744
5060
  // For some reason, there is no documentation and no official command for this
4745
5061
  // The write requests have the same size as the read response - if this yields no
4746
5062
  // data, default to a sane (but maybe slow) size
4747
- const chunkSize = (await this.externalNVMReadBuffer700(0, 0xff)).buffer.length || 48;
5063
+ const chunkSize = (await read(0, 0xff)).buffer.length || 48;
4748
5064
  // Close NVM and re-open again for writing
4749
- await this.externalNVMClose();
4750
- await this.externalNVMOpen();
5065
+ await close();
5066
+ await open();
4751
5067
  for (let offset = 0; offset < nvmData.length; offset += chunkSize) {
4752
- const { endOfFile } = await this.externalNVMWriteBuffer700(offset, nvmData.subarray(offset, offset + chunkSize));
5068
+ const { endOfFile } = await write(offset, nvmData.subarray(offset, offset + chunkSize));
4753
5069
  // Report progress for listeners
4754
5070
  if (onProgress)
4755
5071
  setImmediate(() => onProgress(offset, size));
@@ -4757,7 +5073,7 @@ ${associatedNodes.join(", ")}`,
4757
5073
  break;
4758
5074
  }
4759
5075
  // Close NVM
4760
- await this.externalNVMClose();
5076
+ await close();
4761
5077
  }
4762
5078
  /**
4763
5079
  * Request the most recent background RSSI levels detected by the controller.
@@ -4907,7 +5223,7 @@ ${associatedNodes.join(", ")}`,
4907
5223
  * The return value indicates whether the update was successful.
4908
5224
  * **WARNING:** This method will throw instead of returning `false` if invalid arguments are passed or downloading files or starting an update fails.
4909
5225
  */
4910
- async firmwareUpdateOTA(nodeId, updateInfo) {
5226
+ async firmwareUpdateOTA(nodeId, updateInfo, options) {
4911
5227
  // Don't let two firmware updates happen in parallel
4912
5228
  if (this.isAnyOTAFirmwareUpdateInProgress()) {
4913
5229
  const message = `Failed to start the update: A firmware update is already in progress on this network!`;
@@ -4977,7 +5293,7 @@ ${associatedNodes.join(", ")}`,
4977
5293
  else {
4978
5294
  this.driver.controllerLog.logNode(nodeId, `All updates downloaded, installing...`);
4979
5295
  }
4980
- return node.updateFirmware(firmwares);
5296
+ return node.updateFirmware(firmwares, options);
4981
5297
  }
4982
5298
  _firmwareUpdateInProgress = false;
4983
5299
  /**
@@ -5231,6 +5547,788 @@ ${associatedNodes.join(", ")}`,
5231
5547
  this._firmwareUpdateInProgress = false;
5232
5548
  }
5233
5549
  }
5550
+ _currentLearnMode;
5551
+ _joinNetworkOptions;
5552
+ async beginJoiningNetwork(options) {
5553
+ if (this._currentLearnMode != undefined) {
5554
+ return Inclusion_1.JoinNetworkResult.Error_Busy;
5555
+ }
5556
+ else if (!this.isLearnModePermitted) {
5557
+ return Inclusion_1.JoinNetworkResult.Error_NotPermitted;
5558
+ }
5559
+ // FIXME: If the join strategy says S0, remove S2 from the NIF before joining
5560
+ try {
5561
+ const result = await this.driver.sendMessage(new SetLearnModeMessages_1.SetLearnModeRequest(this.driver, {
5562
+ intent: SetLearnModeMessages_1.LearnModeIntent.Inclusion,
5563
+ }));
5564
+ if (result.isOK()) {
5565
+ this._currentLearnMode = SetLearnModeMessages_1.LearnModeIntent.Inclusion;
5566
+ this._joinNetworkOptions = options;
5567
+ return Inclusion_1.JoinNetworkResult.OK;
5568
+ }
5569
+ }
5570
+ catch (e) {
5571
+ this.driver.controllerLog.print(`Joining a network failed: ${(0, shared_1.getErrorMessage)(e)}`, "error");
5572
+ }
5573
+ this._currentLearnMode = undefined;
5574
+ return Inclusion_1.JoinNetworkResult.Error_Failed;
5575
+ }
5576
+ async stopJoiningNetwork() {
5577
+ if (this._currentLearnMode !== SetLearnModeMessages_1.LearnModeIntent.LegacyInclusionExclusion
5578
+ // FIXME: ^ only for actual exclusion
5579
+ && this._currentLearnMode !== SetLearnModeMessages_1.LearnModeIntent.Inclusion) {
5580
+ return false;
5581
+ }
5582
+ try {
5583
+ const result = await this.driver.sendMessage(new SetLearnModeMessages_1.SetLearnModeRequest(this.driver, {
5584
+ // TODO: We should be using .Stop here for the non-legacy
5585
+ // inclusion/exclusion, but that command results in a
5586
+ // negative response on current firmwares, even though it works.
5587
+ // Using LegacyStop avoids that, but results in an unexpected
5588
+ // LearnModeFailed callback.
5589
+ intent: SetLearnModeMessages_1.LearnModeIntent.LegacyStop,
5590
+ }));
5591
+ if (result.isOK()) {
5592
+ this._currentLearnMode = undefined;
5593
+ this._joinNetworkOptions = undefined;
5594
+ return true;
5595
+ }
5596
+ }
5597
+ catch (e) {
5598
+ this.driver.controllerLog.print(`Failed to stop joining a network: ${(0, shared_1.getErrorMessage)(e)}`, "error");
5599
+ }
5600
+ return false;
5601
+ }
5602
+ async beginLeavingNetwork() {
5603
+ if (this._currentLearnMode != undefined) {
5604
+ return Inclusion_1.LeaveNetworkResult.Error_Busy;
5605
+ }
5606
+ else if (!this.isLearnModePermitted) {
5607
+ return Inclusion_1.LeaveNetworkResult.Error_NotPermitted;
5608
+ }
5609
+ try {
5610
+ const result = await this.driver.sendMessage(new SetLearnModeMessages_1.SetLearnModeRequest(this.driver, {
5611
+ intent: SetLearnModeMessages_1.LearnModeIntent.NetworkWideExclusion,
5612
+ }));
5613
+ if (result.isOK()) {
5614
+ this._currentLearnMode = SetLearnModeMessages_1.LearnModeIntent.NetworkWideExclusion;
5615
+ return Inclusion_1.LeaveNetworkResult.OK;
5616
+ }
5617
+ }
5618
+ catch (e) {
5619
+ this.driver.controllerLog.print(`Leaving the current network failed: ${(0, shared_1.getErrorMessage)(e)}`, "error");
5620
+ }
5621
+ this._currentLearnMode = undefined;
5622
+ return Inclusion_1.LeaveNetworkResult.Error_Failed;
5623
+ }
5624
+ async stopLeavingNetwork() {
5625
+ if (this._currentLearnMode !== SetLearnModeMessages_1.LearnModeIntent.LegacyInclusionExclusion
5626
+ // FIXME: ^ only for actual exclusion
5627
+ && this._currentLearnMode
5628
+ !== SetLearnModeMessages_1.LearnModeIntent.LegacyNetworkWideExclusion
5629
+ && this._currentLearnMode !== SetLearnModeMessages_1.LearnModeIntent.DirectExclusion
5630
+ && this._currentLearnMode !== SetLearnModeMessages_1.LearnModeIntent.NetworkWideExclusion) {
5631
+ return false;
5632
+ }
5633
+ try {
5634
+ const result = await this.driver.sendMessage(new SetLearnModeMessages_1.SetLearnModeRequest(this.driver, {
5635
+ // TODO: We should be using .Stop here for the non-legacy
5636
+ // inclusion/exclusion, but that command results in a
5637
+ // negative response on current firmwares, even though it works.
5638
+ // Using LegacyStop avoids that, but results in an unexpected
5639
+ // LearnModeFailed callback.
5640
+ intent: SetLearnModeMessages_1.LearnModeIntent.LegacyStop,
5641
+ }));
5642
+ if (result.isOK()) {
5643
+ this._currentLearnMode = undefined;
5644
+ return true;
5645
+ }
5646
+ }
5647
+ catch (e) {
5648
+ this.driver.controllerLog.print(`Failed to stop leaving a network: ${(0, shared_1.getErrorMessage)(e)}`, "error");
5649
+ }
5650
+ return false;
5651
+ }
5652
+ /**
5653
+ * Is called when a RemoveNode request is received from the controller.
5654
+ * Handles and controls the exclusion process.
5655
+ */
5656
+ handleLearnModeCallback(msg) {
5657
+ // not sure what to do with this message, we're not in learn mode
5658
+ if (this._currentLearnMode == undefined)
5659
+ return false;
5660
+ // FIXME: Reset security manager on successful join or leave
5661
+ const wasJoining = this._currentLearnMode === SetLearnModeMessages_1.LearnModeIntent.Inclusion
5662
+ || this._currentLearnMode === SetLearnModeMessages_1.LearnModeIntent.SmartStart
5663
+ || this._currentLearnMode
5664
+ === SetLearnModeMessages_1.LearnModeIntent.LegacyNetworkWideInclusion
5665
+ || (this._currentLearnMode
5666
+ === SetLearnModeMessages_1.LearnModeIntent.LegacyInclusionExclusion
5667
+ // TODO: Secondary controller may also use this to accept controller shift
5668
+ // Figure out how to detect that.
5669
+ && this.role === core_1.ControllerRole.Primary);
5670
+ const wasLeaving = this._currentLearnMode === SetLearnModeMessages_1.LearnModeIntent.DirectExclusion
5671
+ || this._currentLearnMode
5672
+ === SetLearnModeMessages_1.LearnModeIntent.NetworkWideExclusion
5673
+ || this._currentLearnMode
5674
+ === SetLearnModeMessages_1.LearnModeIntent.LegacyNetworkWideExclusion
5675
+ || (this._currentLearnMode
5676
+ === SetLearnModeMessages_1.LearnModeIntent.LegacyInclusionExclusion
5677
+ && this.role !== core_1.ControllerRole.Primary);
5678
+ if (msg.status === SetLearnModeMessages_1.LearnModeStatus.Started) {
5679
+ // cool, cool, cool...
5680
+ return true;
5681
+ }
5682
+ else if (msg.status === SetLearnModeMessages_1.LearnModeStatus.Failed) {
5683
+ if (wasJoining) {
5684
+ this._currentLearnMode = undefined;
5685
+ this._joinNetworkOptions = undefined;
5686
+ this.emit("joining network failed");
5687
+ return true;
5688
+ }
5689
+ else if (wasLeaving) {
5690
+ this._currentLearnMode = undefined;
5691
+ this.emit("leaving network failed");
5692
+ return true;
5693
+ }
5694
+ }
5695
+ else if (msg.status === SetLearnModeMessages_1.LearnModeStatus.Completed
5696
+ || (this._currentLearnMode >= SetLearnModeMessages_1.LearnModeIntent.Inclusion
5697
+ && msg.status === SetLearnModeMessages_1.LearnModeStatus.ProtocolDone)) {
5698
+ if (wasJoining) {
5699
+ this._currentLearnMode = undefined;
5700
+ this.driver["_securityManager"] = undefined;
5701
+ this.driver["_securityManager2"] = new core_1.SecurityManager2();
5702
+ this.driver["_securityManagerLR"] = new core_1.SecurityManager2();
5703
+ this._nodes.clear();
5704
+ process.nextTick(() => this.afterJoiningNetwork().catch(shared_1.noop));
5705
+ return true;
5706
+ }
5707
+ else if (wasLeaving) {
5708
+ this._currentLearnMode = undefined;
5709
+ this.emit("network left");
5710
+ return true;
5711
+ }
5712
+ }
5713
+ // not sure what to do with this message
5714
+ return false;
5715
+ }
5716
+ async expectSecurityBootstrapS0(bootstrappingNode) {
5717
+ // When bootstrapping with S0, no other keys are granted
5718
+ for (const secClass of core_1.securityClassOrder) {
5719
+ if (secClass !== core_1.SecurityClass.S0_Legacy) {
5720
+ bootstrappingNode.securityClasses.set(secClass, false);
5721
+ }
5722
+ }
5723
+ const unGrantSecurityClass = () => {
5724
+ this.driver["_securityManager"] = undefined;
5725
+ bootstrappingNode.securityClasses.set(core_1.SecurityClass.S0_Legacy, false);
5726
+ };
5727
+ const abortTimeout = () => {
5728
+ this.driver.controllerLog.logNode(bootstrappingNode.id, {
5729
+ message: `Security S0 bootstrapping failed: a secure inclusion timer has elapsed`,
5730
+ level: "warn",
5731
+ });
5732
+ unGrantSecurityClass();
5733
+ return Inclusion_1.SecurityBootstrapFailure.Timeout;
5734
+ };
5735
+ try {
5736
+ const api = bootstrappingNode.commandClasses.Security;
5737
+ // For the first part of the bootstrapping, a temporary key needs to be used
5738
+ this.driver["_securityManager"] = new core_1.SecurityManager({
5739
+ ownNodeId: this._ownNodeId,
5740
+ networkKey: Buffer.alloc(16, 0),
5741
+ nonceTimeout: this.driver.options.timeouts.nonce,
5742
+ });
5743
+ // Report the supported schemes
5744
+ await api.reportSecurityScheme(false);
5745
+ // Expect a NonceGet within 10 seconds
5746
+ let nonceGet = await this.driver.waitForCommand((cc) => cc instanceof cc_1.SecurityCCNonceGet, 10000).catch(() => "timeout");
5747
+ if (nonceGet === "timeout")
5748
+ return abortTimeout();
5749
+ // Send nonce
5750
+ await api.sendNonce();
5751
+ // Expect NetworkKeySet within 10 seconds
5752
+ const networkKeySet = await this.driver.waitForCommand((cc) => cc instanceof cc_1.SecurityCCNetworkKeySet, 10000).catch(() => "timeout");
5753
+ if (networkKeySet === "timeout")
5754
+ return abortTimeout();
5755
+ // Now that the key is known, we can create the real security manager
5756
+ this.driver["_securityManager"] = new core_1.SecurityManager({
5757
+ ownNodeId: this._ownNodeId,
5758
+ networkKey: networkKeySet.networkKey,
5759
+ nonceTimeout: this.driver.options.timeouts.nonce,
5760
+ });
5761
+ // Request a new nonce to respond, which should be answered within 10 seconds
5762
+ let nonce = await api.withOptions({ reportTimeoutMs: 10000 })
5763
+ .getNonce();
5764
+ if (!nonce)
5765
+ return abortTimeout();
5766
+ // Verify the key
5767
+ await api.verifyNetworkKey();
5768
+ // We are a controller, so continue with scheme inherit
5769
+ // Expect a NonceGet within 10 seconds
5770
+ nonceGet = await this.driver.waitForCommand((cc) => cc instanceof cc_1.SecurityCCNonceGet, 10000).catch(() => "timeout");
5771
+ if (nonceGet === "timeout")
5772
+ return abortTimeout();
5773
+ // Send nonce
5774
+ await api.sendNonce();
5775
+ // Expect SchemeInherit within 10 seconds
5776
+ const schemeInherit = await this.driver.waitForCommand((cc) => cc instanceof cc_1.SecurityCCSchemeInherit, 10000).catch(() => "timeout");
5777
+ if (schemeInherit === "timeout")
5778
+ return abortTimeout();
5779
+ // Request a new nonce to respond, which should be answered within 10 seconds
5780
+ nonce = await api.withOptions({ reportTimeoutMs: 10000 })
5781
+ .getNonce();
5782
+ if (!nonce)
5783
+ return abortTimeout();
5784
+ // Report the supported schemes. This isn't technically correct, but since
5785
+ // S0 won't get any extensions, we can just report the default scheme again
5786
+ await api.reportSecurityScheme(true);
5787
+ // Remember that the S0 key was granted
5788
+ bootstrappingNode.securityClasses.set(core_1.SecurityClass.S0_Legacy, true);
5789
+ // Store the key
5790
+ this.driver.cacheSet(NetworkCache_1.cacheKeys.controller.securityKeys(core_1.SecurityClass.S0_Legacy), networkKeySet.networkKey);
5791
+ this.driver.driverLog.print(`Security S0 bootstrapping successful`);
5792
+ // success 🎉
5793
+ }
5794
+ catch (e) {
5795
+ let errorMessage = `Security S0 bootstrapping failed`;
5796
+ let result = Inclusion_1.SecurityBootstrapFailure.Unknown;
5797
+ if (!(0, core_1.isZWaveError)(e)) {
5798
+ errorMessage += `: ${e}`;
5799
+ }
5800
+ else if (e.code === core_1.ZWaveErrorCodes.Controller_MessageExpired) {
5801
+ errorMessage += ": a secure inclusion timer has elapsed.";
5802
+ result = Inclusion_1.SecurityBootstrapFailure.Timeout;
5803
+ }
5804
+ else if (e.code !== core_1.ZWaveErrorCodes.Controller_MessageDropped
5805
+ && e.code !== core_1.ZWaveErrorCodes.Controller_NodeTimeout) {
5806
+ errorMessage += `: ${e.message}`;
5807
+ }
5808
+ this.driver.controllerLog.logNode(bootstrappingNode.id, errorMessage, "warn");
5809
+ unGrantSecurityClass();
5810
+ return result;
5811
+ }
5812
+ }
5813
+ async expectSecurityBootstrapS2(bootstrappingNode, requested, userCallbacks = this.driver.options.joinNetworkUserCallbacks) {
5814
+ const api = bootstrappingNode.commandClasses["Security 2"]
5815
+ .withOptions({
5816
+ // Do not wait for Nonce Reports after SET-type commands.
5817
+ // Timing is critical here
5818
+ s2VerifyDelivery: false,
5819
+ });
5820
+ const unGrantSecurityClasses = () => {
5821
+ for (const secClass of core_1.securityClassOrder) {
5822
+ bootstrappingNode.securityClasses.set(secClass, false);
5823
+ }
5824
+ };
5825
+ // FIXME: Abstract this out so it can be reused as primary and secondary
5826
+ const securityManager = (0, core_1.isLongRangeNodeId)(this._ownNodeId)
5827
+ ? this.driver.securityManagerLR
5828
+ : this.driver.securityManager2;
5829
+ if (!securityManager) {
5830
+ // This should not happen when joining a network.
5831
+ unGrantSecurityClasses();
5832
+ return Inclusion_1.SecurityBootstrapFailure.NoKeysConfigured;
5833
+ }
5834
+ const receivedKeys = new Map();
5835
+ const deleteTempKey = () => {
5836
+ // Whatever happens, no further communication needs the temporary key
5837
+ securityManager.deleteNonce(bootstrappingNode.id);
5838
+ securityManager.tempKeys.delete(bootstrappingNode.id);
5839
+ };
5840
+ let dskHidden = false;
5841
+ const applicationHideDSK = () => {
5842
+ if (dskHidden)
5843
+ return;
5844
+ dskHidden = true;
5845
+ try {
5846
+ userCallbacks?.done();
5847
+ }
5848
+ catch {
5849
+ // ignore application-level errors
5850
+ }
5851
+ };
5852
+ const abort = async (failType) => {
5853
+ applicationHideDSK();
5854
+ if (failType != undefined) {
5855
+ try {
5856
+ await api.abortKeyExchange(failType);
5857
+ }
5858
+ catch {
5859
+ // ignore
5860
+ }
5861
+ }
5862
+ // Un-grant S2 security classes we might have granted
5863
+ unGrantSecurityClasses();
5864
+ deleteTempKey();
5865
+ };
5866
+ const abortTimeout = async () => {
5867
+ this.driver.controllerLog.logNode(bootstrappingNode.id, {
5868
+ message: `Security S2 bootstrapping failed: a secure inclusion timer has elapsed`,
5869
+ level: "warn",
5870
+ });
5871
+ await abort();
5872
+ return Inclusion_1.SecurityBootstrapFailure.Timeout;
5873
+ };
5874
+ const abortCanceled = async () => {
5875
+ this.driver.controllerLog.logNode(bootstrappingNode.id, {
5876
+ message: `The including node canceled the Security S2 bootstrapping.`,
5877
+ direction: "inbound",
5878
+ level: "warn",
5879
+ });
5880
+ await abort();
5881
+ return Inclusion_1.SecurityBootstrapFailure.NodeCanceled;
5882
+ };
5883
+ try {
5884
+ // Send with our desired keys
5885
+ await api.requestKeys({
5886
+ requestedKeys: requested.securityClasses,
5887
+ requestCSA: false,
5888
+ supportedECDHProfiles: [cc_1.ECDHProfiles.Curve25519],
5889
+ supportedKEXSchemes: [cc_1.KEXSchemes.KEXScheme1],
5890
+ });
5891
+ // Wait for including node to grant keys
5892
+ const kexSet = await this.driver.waitForCommand((cc) => cc instanceof cc_1.Security2CCKEXSet
5893
+ || cc instanceof cc_1.Security2CCKEXFail, cc_1.inclusionTimeouts.TB2).catch(() => "timeout");
5894
+ if (kexSet === "timeout")
5895
+ return abortTimeout();
5896
+ if (kexSet instanceof cc_1.Security2CCKEXFail) {
5897
+ return abortCanceled();
5898
+ }
5899
+ // Validate the command
5900
+ // Echo flag must be false
5901
+ if (kexSet.echo) {
5902
+ this.driver.controllerLog.logNode(bootstrappingNode.id, {
5903
+ message: `Security S2 bootstrapping failed: KEX Set unexpectedly has the echo flag set.`,
5904
+ level: "warn",
5905
+ });
5906
+ await abort(cc_1.KEXFailType.NoVerify);
5907
+ return Inclusion_1.SecurityBootstrapFailure.ParameterMismatch;
5908
+ }
5909
+ else if (kexSet.selectedKEXScheme !== cc_1.KEXSchemes.KEXScheme1) {
5910
+ this.driver.controllerLog.logNode(bootstrappingNode.id, {
5911
+ message: `Security S2 bootstrapping failed: Unsupported key exchange scheme.`,
5912
+ level: "warn",
5913
+ });
5914
+ await abort(cc_1.KEXFailType.NoSupportedScheme);
5915
+ return Inclusion_1.SecurityBootstrapFailure.ParameterMismatch;
5916
+ }
5917
+ else if (kexSet.selectedECDHProfile !== cc_1.ECDHProfiles.Curve25519) {
5918
+ this.driver.controllerLog.logNode(bootstrappingNode.id, {
5919
+ message: `Security S2 bootstrapping failed: Unsupported ECDH profile.`,
5920
+ level: "warn",
5921
+ });
5922
+ await abort(cc_1.KEXFailType.NoSupportedCurve);
5923
+ return Inclusion_1.SecurityBootstrapFailure.ParameterMismatch;
5924
+ }
5925
+ else if (kexSet.permitCSA !== false) {
5926
+ // We do not support CSA at the moment, so it is never requested.
5927
+ this.driver.controllerLog.logNode(bootstrappingNode.id, {
5928
+ message: `Security S2 bootstrapping failed: CSA granted but not requested.`,
5929
+ level: "warn",
5930
+ });
5931
+ await abort(cc_1.KEXFailType.BootstrappingCanceled);
5932
+ return Inclusion_1.SecurityBootstrapFailure.ParameterMismatch;
5933
+ }
5934
+ const matchingKeys = kexSet.grantedKeys.filter((k) => core_1.securityClassOrder.includes(k)
5935
+ && requested.securityClasses.includes(k));
5936
+ if (!matchingKeys.length) {
5937
+ this.driver.controllerLog.logNode(bootstrappingNode.id, {
5938
+ message: `Security S2 bootstrapping failed: None of the requested security classes are granted.`,
5939
+ level: "warn",
5940
+ });
5941
+ await abort(cc_1.KEXFailType.NoKeyMatch);
5942
+ return Inclusion_1.SecurityBootstrapFailure.ParameterMismatch;
5943
+ }
5944
+ const highestGranted = (0, core_1.getHighestSecurityClass)(matchingKeys);
5945
+ const requiresAuthentication = highestGranted === core_1.SecurityClass.S2_AccessControl
5946
+ || highestGranted === core_1.SecurityClass.S2_Authenticated;
5947
+ // If authentication is required, use the (static) authenticated ECDH key pair,
5948
+ // otherwise generate a new one
5949
+ const keyPair = requiresAuthentication
5950
+ ? this.driver.getLearnModeAuthenticatedKeyPair()
5951
+ : (0, core_1.generateECDHKeyPair)();
5952
+ const publicKey = (0, core_1.extractRawECDHPublicKey)(keyPair.publicKey);
5953
+ const transmittedPublicKey = Buffer.from(publicKey);
5954
+ if (requiresAuthentication) {
5955
+ // Authentication requires obfuscating the public key
5956
+ transmittedPublicKey.writeUInt16BE(0x0000, 0);
5957
+ // Show the DSK to the user
5958
+ const dsk = (0, core_1.dskToString)(publicKey.subarray(0, 16));
5959
+ try {
5960
+ userCallbacks?.showDSK(dsk);
5961
+ }
5962
+ catch {
5963
+ // ignore application-level errors
5964
+ }
5965
+ }
5966
+ await api.sendPublicKey(transmittedPublicKey, false);
5967
+ // Wait for including node to send its public key
5968
+ const pubKeyReport = await this.driver.waitForCommand((cc) => cc instanceof cc_1.Security2CCPublicKeyReport
5969
+ || cc instanceof cc_1.Security2CCKEXFail, cc_1.inclusionTimeouts.TB3).catch(() => "timeout");
5970
+ if (pubKeyReport === "timeout")
5971
+ return abortTimeout();
5972
+ if (pubKeyReport instanceof cc_1.Security2CCKEXFail) {
5973
+ return abortCanceled();
5974
+ }
5975
+ const includingNodePubKey = pubKeyReport.publicKey;
5976
+ const sharedSecret = node_crypto_1.default.diffieHellman({
5977
+ publicKey: (0, core_1.importRawECDHPublicKey)(includingNodePubKey),
5978
+ privateKey: keyPair.privateKey,
5979
+ });
5980
+ // Derive temporary key from ECDH key pair - this will allow us to receive the node's KEX SET commands
5981
+ const tempKeys = (0, core_1.deriveTempKeys)((0, core_1.computePRK)(sharedSecret, includingNodePubKey, publicKey));
5982
+ securityManager.deleteNonce(bootstrappingNode.id);
5983
+ securityManager.tempKeys.set(bootstrappingNode.id, {
5984
+ keyCCM: tempKeys.tempKeyCCM,
5985
+ personalizationString: tempKeys.tempPersonalizationString,
5986
+ });
5987
+ // Wait for the confirmation of the requested keys and
5988
+ // retransmit the KEXSet echo every 10 seconds until a response is
5989
+ // received or the process timed out.
5990
+ const confirmKeysStartTime = Date.now();
5991
+ let kexReportEcho;
5992
+ for (let i = 0; i <= 25; i++) {
5993
+ try {
5994
+ kexReportEcho = await api.withOptions({
5995
+ reportTimeoutMs: 10000,
5996
+ }).confirmGrantedKeys({
5997
+ grantedKeys: kexSet.grantedKeys,
5998
+ permitCSA: kexSet.permitCSA,
5999
+ selectedECDHProfile: kexSet.selectedECDHProfile,
6000
+ selectedKEXScheme: kexSet.selectedKEXScheme,
6001
+ _reserved: kexSet._reserved,
6002
+ });
6003
+ }
6004
+ catch {
6005
+ // ignore
6006
+ }
6007
+ if (kexReportEcho != undefined)
6008
+ break;
6009
+ if (Date.now() - confirmKeysStartTime > 240000)
6010
+ break;
6011
+ }
6012
+ if (!kexReportEcho || kexReportEcho === "timeout") {
6013
+ return abortTimeout();
6014
+ }
6015
+ else if (kexReportEcho instanceof cc_1.Security2CCKEXFail) {
6016
+ return abortCanceled();
6017
+ }
6018
+ // The application no longer needs to show the DSK
6019
+ applicationHideDSK();
6020
+ // Validate the response
6021
+ if (!kexReportEcho.echo) {
6022
+ this.driver.controllerLog.logNode(bootstrappingNode.id, {
6023
+ message: `Security S2 bootstrapping failed: KEXReport received without echo flag`,
6024
+ direction: "inbound",
6025
+ level: "warn",
6026
+ });
6027
+ await abort(cc_1.KEXFailType.WrongSecurityLevel);
6028
+ return Inclusion_1.SecurityBootstrapFailure.NodeCanceled;
6029
+ }
6030
+ else if (kexReportEcho.requestCSA !== false) {
6031
+ // We don't request CSA
6032
+ this.driver.controllerLog.logNode(bootstrappingNode.id, {
6033
+ message: `Security S2 bootstrapping failed: Invalid KEXReport received`,
6034
+ level: "warn",
6035
+ });
6036
+ await abort(cc_1.KEXFailType.WrongSecurityLevel);
6037
+ return Inclusion_1.SecurityBootstrapFailure.NodeCanceled;
6038
+ }
6039
+ else if (kexReportEcho._reserved !== 0) {
6040
+ this.driver.controllerLog.logNode(bootstrappingNode.id, {
6041
+ message: `Security S2 bootstrapping failed: Invalid KEXReport received`,
6042
+ direction: "inbound",
6043
+ level: "warn",
6044
+ });
6045
+ await abort(cc_1.KEXFailType.WrongSecurityLevel);
6046
+ return Inclusion_1.SecurityBootstrapFailure.NodeCanceled;
6047
+ }
6048
+ else if (!kexReportEcho.isEncapsulatedWith(core_1.CommandClasses["Security 2"], cc_1.Security2Command.MessageEncapsulation)) {
6049
+ this.driver.controllerLog.logNode(bootstrappingNode.id, {
6050
+ message: `Security S2 bootstrapping failed: Command received without encryption`,
6051
+ direction: "inbound",
6052
+ level: "warn",
6053
+ });
6054
+ await abort(cc_1.KEXFailType.WrongSecurityLevel);
6055
+ return Inclusion_1.SecurityBootstrapFailure.S2WrongSecurityLevel;
6056
+ }
6057
+ else if (kexReportEcho.requestedKeys.length
6058
+ !== requested.securityClasses.length
6059
+ || !kexReportEcho.requestedKeys.every((k) => requested.securityClasses.includes(k))) {
6060
+ this.driver.controllerLog.logNode(bootstrappingNode.id, {
6061
+ message: `Security S2 bootstrapping failed: Granted key mismatch.`,
6062
+ level: "warn",
6063
+ });
6064
+ await abort(cc_1.KEXFailType.WrongSecurityLevel);
6065
+ return Inclusion_1.SecurityBootstrapFailure.S2WrongSecurityLevel;
6066
+ }
6067
+ for (const key of kexSet.grantedKeys) {
6068
+ // Request network key and wait for including node to respond
6069
+ const keyReportPromise = this.driver.waitForCommand((cc) => cc instanceof cc_1.Security2CCNetworkKeyReport
6070
+ || cc instanceof cc_1.Security2CCKEXFail, cc_1.inclusionTimeouts.TB4).catch(() => "timeout");
6071
+ await api.requestNetworkKey(key);
6072
+ const keyReport = await keyReportPromise;
6073
+ if (keyReport === "timeout")
6074
+ return abortTimeout();
6075
+ if (keyReport instanceof cc_1.Security2CCKEXFail) {
6076
+ return abortCanceled();
6077
+ }
6078
+ if (!keyReport.isEncapsulatedWith(core_1.CommandClasses["Security 2"], cc_1.Security2Command.MessageEncapsulation)) {
6079
+ this.driver.controllerLog.logNode(bootstrappingNode.id, {
6080
+ message: `Security S2 bootstrapping failed: Command received without encryption`,
6081
+ direction: "inbound",
6082
+ level: "warn",
6083
+ });
6084
+ await abort(cc_1.KEXFailType.WrongSecurityLevel);
6085
+ return Inclusion_1.SecurityBootstrapFailure.S2WrongSecurityLevel;
6086
+ }
6087
+ // Ensure it was received encrypted with the temporary key
6088
+ if (!securityManager.hasUsedSecurityClass(bootstrappingNode.id, core_1.SecurityClass.Temporary)) {
6089
+ this.driver.controllerLog.logNode(bootstrappingNode.id, {
6090
+ message: `Security S2 bootstrapping failed: Node used wrong key to communicate.`,
6091
+ level: "warn",
6092
+ });
6093
+ await abort(cc_1.KEXFailType.WrongSecurityLevel);
6094
+ return Inclusion_1.SecurityBootstrapFailure.S2WrongSecurityLevel;
6095
+ }
6096
+ const securityClass = keyReport.grantedKey;
6097
+ if (securityClass !== key) {
6098
+ // and that the granted key is the requested key
6099
+ this.driver.controllerLog.logNode(bootstrappingNode.id, {
6100
+ message: `Security S2 bootstrapping failed: Received key for wrong security class`,
6101
+ direction: "inbound",
6102
+ level: "warn",
6103
+ });
6104
+ await abort(cc_1.KEXFailType.DifferentKey);
6105
+ return Inclusion_1.SecurityBootstrapFailure.ParameterMismatch;
6106
+ }
6107
+ // Store the network key
6108
+ receivedKeys.set(securityClass, keyReport.networkKey);
6109
+ securityManager.setKey(securityClass, keyReport.networkKey);
6110
+ if (securityClass === core_1.SecurityClass.S0_Legacy) {
6111
+ // TODO: This is awkward to have here
6112
+ this.driver["_securityManager"] = new core_1.SecurityManager({
6113
+ ownNodeId: this._ownNodeId,
6114
+ networkKey: keyReport.networkKey,
6115
+ nonceTimeout: this.driver.options.timeouts.nonce,
6116
+ });
6117
+ }
6118
+ // Force nonce synchronization, then verify the network key
6119
+ securityManager.deleteNonce(bootstrappingNode.id);
6120
+ await api.withOptions({
6121
+ s2OverrideSecurityClass: securityClass,
6122
+ }).verifyNetworkKey();
6123
+ // Force nonce synchronization again for the temporary key
6124
+ securityManager.deleteNonce(bootstrappingNode.id);
6125
+ // Wait for including node to send its public key
6126
+ const transferEnd = await this.driver.waitForCommand((cc) => cc instanceof cc_1.Security2CCTransferEnd
6127
+ || cc instanceof cc_1.Security2CCKEXFail, cc_1.inclusionTimeouts.TB5).catch(() => "timeout");
6128
+ if (transferEnd === "timeout")
6129
+ return abortTimeout();
6130
+ if (transferEnd instanceof cc_1.Security2CCKEXFail) {
6131
+ return abortCanceled();
6132
+ }
6133
+ if (!keyReport.isEncapsulatedWith(core_1.CommandClasses["Security 2"], cc_1.Security2Command.MessageEncapsulation)) {
6134
+ this.driver.controllerLog.logNode(bootstrappingNode.id, {
6135
+ message: `Security S2 bootstrapping failed: Command received without encryption`,
6136
+ direction: "inbound",
6137
+ level: "warn",
6138
+ });
6139
+ await abort(cc_1.KEXFailType.WrongSecurityLevel);
6140
+ return Inclusion_1.SecurityBootstrapFailure.S2WrongSecurityLevel;
6141
+ }
6142
+ else if (!transferEnd.keyVerified || transferEnd.keyRequestComplete) {
6143
+ this.driver.controllerLog.logNode(bootstrappingNode.id, {
6144
+ message: `Security S2 bootstrapping failed: Invalid TransferEnd received`,
6145
+ direction: "inbound",
6146
+ level: "warn",
6147
+ });
6148
+ await abort(cc_1.KEXFailType.WrongSecurityLevel);
6149
+ return Inclusion_1.SecurityBootstrapFailure.NodeCanceled;
6150
+ }
6151
+ }
6152
+ // Confirm end of bootstrapping
6153
+ await api.endKeyExchange();
6154
+ // Remember all security classes we were granted
6155
+ for (const securityClass of core_1.securityClassOrder) {
6156
+ bootstrappingNode.securityClasses.set(securityClass, kexSet.grantedKeys.includes(securityClass));
6157
+ }
6158
+ // And store the keys
6159
+ for (const [secClass, key] of receivedKeys) {
6160
+ this.driver.cacheSet(NetworkCache_1.cacheKeys.controller.securityKeys(secClass), key);
6161
+ }
6162
+ this.driver.driverLog.print(`Security S2 bootstrapping successful with these security classes:${[
6163
+ ...bootstrappingNode.securityClasses.entries(),
6164
+ ]
6165
+ .filter(([, v]) => v)
6166
+ .map(([k]) => `\n· ${(0, shared_1.getEnumMemberName)(core_1.SecurityClass, k)}`)
6167
+ .join("")}`);
6168
+ // success 🎉
6169
+ }
6170
+ catch (e) {
6171
+ let errorMessage = `Security S2 bootstrapping failed, no S2 security classes were granted`;
6172
+ let result = Inclusion_1.SecurityBootstrapFailure.Unknown;
6173
+ if (!(0, core_1.isZWaveError)(e)) {
6174
+ errorMessage += `: ${e}`;
6175
+ }
6176
+ else if (e.code === core_1.ZWaveErrorCodes.Controller_MessageExpired) {
6177
+ errorMessage += ": a secure inclusion timer has elapsed.";
6178
+ result = Inclusion_1.SecurityBootstrapFailure.Timeout;
6179
+ }
6180
+ else if (e.code !== core_1.ZWaveErrorCodes.Controller_MessageDropped
6181
+ && e.code !== core_1.ZWaveErrorCodes.Controller_NodeTimeout) {
6182
+ errorMessage += `: ${e.message}`;
6183
+ }
6184
+ this.driver.controllerLog.logNode(bootstrappingNode.id, errorMessage, "warn");
6185
+ // Remember that we were NOT granted any S2 security classes
6186
+ unGrantSecurityClasses();
6187
+ bootstrappingNode.removeCC(core_1.CommandClasses["Security 2"]);
6188
+ return result;
6189
+ }
6190
+ finally {
6191
+ // Whatever happens, no further communication needs the temporary key
6192
+ deleteTempKey();
6193
+ }
6194
+ }
6195
+ async afterJoiningNetwork() {
6196
+ this.driver.driverLog.print("waiting for security bootstrapping...");
6197
+ const bootstrapInitStart = Date.now();
6198
+ const supportedCCs = (0, NodeInformationFrame_1.determineNIF)().supportedCCs;
6199
+ const supportsS0 = supportedCCs.includes(core_1.CommandClasses.Security);
6200
+ const supportsS2 = supportedCCs.includes(core_1.CommandClasses["Security 2"]);
6201
+ let initTimeout;
6202
+ let initPredicate;
6203
+ // KEX Get must be received:
6204
+ // - no later than 10..30 seconds after the inclusion if S0 is supported
6205
+ // - no later than 30 seconds after the inclusion if only S2 is supported
6206
+ // For simplicity, we wait the full 30s.
6207
+ // SecurityCCSchemeGet must be received no later than 10 seconds
6208
+ // after the inclusion if S0 is supported
6209
+ if (supportsS0 && supportsS2) {
6210
+ initTimeout = cc_1.inclusionTimeouts.TB1;
6211
+ initPredicate = (cc) => cc instanceof cc_1.SecurityCCSchemeGet
6212
+ || cc instanceof cc_1.Security2CCKEXGet;
6213
+ }
6214
+ else if (supportsS2) {
6215
+ initTimeout = cc_1.inclusionTimeouts.TB1;
6216
+ initPredicate = (cc) => cc instanceof cc_1.Security2CCKEXGet;
6217
+ }
6218
+ else if (supportsS0) {
6219
+ initTimeout = 10000;
6220
+ initPredicate = (cc) => cc instanceof cc_1.SecurityCCSchemeGet;
6221
+ }
6222
+ else {
6223
+ initTimeout = 0;
6224
+ initPredicate = () => false;
6225
+ }
6226
+ const bootstrapInitPromise = this.driver.waitForCommand(initPredicate, initTimeout).catch(() => "timeout");
6227
+ const identifySelf = async () => {
6228
+ // Update own node ID and other controller flags.
6229
+ await this.identify().catch(shared_1.noop);
6230
+ // Notify applications that we're now part of a new network
6231
+ // The driver will point the databases to the new home ID
6232
+ this.emit("network found", this._homeId, this._ownNodeId);
6233
+ // Figure out the controller's network role
6234
+ await this.getControllerCapabilities().catch(shared_1.noop);
6235
+ // Create new node instances
6236
+ const { nodeIds } = await this.getSerialApiInitData();
6237
+ await this.initNodes(nodeIds, [], () => Promise.resolve());
6238
+ };
6239
+ // Do the self-identification while waiting for the bootstrap init command
6240
+ const [bootstrapInit] = await Promise.all([
6241
+ bootstrapInitPromise,
6242
+ identifySelf(),
6243
+ ]);
6244
+ if (bootstrapInit === "timeout") {
6245
+ this.driver.controllerLog.print("No security bootstrapping command received, continuing without encryption...");
6246
+ }
6247
+ else if (bootstrapInit instanceof cc_1.SecurityCCSchemeGet) {
6248
+ const nodeId = bootstrapInit.nodeId;
6249
+ const bootstrappingNode = this.nodes.get(nodeId);
6250
+ if (!bootstrappingNode) {
6251
+ this.driver.controllerLog.logNode(nodeId, {
6252
+ message: "Received S2 bootstrap initiation from unknown node, ignoring...",
6253
+ level: "warn",
6254
+ });
6255
+ }
6256
+ else if (Date.now() - bootstrapInitStart > 10000) {
6257
+ // Received too late, S0 bootstrapping must not continue
6258
+ this.driver.controllerLog.print("Security S0 bootstrapping command received too late, continuing without encryption...");
6259
+ }
6260
+ else {
6261
+ // We definitely know that the node supports S0
6262
+ bootstrappingNode.addCC(core_1.CommandClasses.Security, {
6263
+ secure: true,
6264
+ isSupported: true,
6265
+ });
6266
+ this.driver.controllerLog.logNode(nodeId, {
6267
+ message: `Received S0 bootstrap initiation`,
6268
+ });
6269
+ const bootstrapResult = await this.expectSecurityBootstrapS0(bootstrappingNode);
6270
+ if (bootstrapResult !== undefined) {
6271
+ // If there was a failure, mark S0 as not supported
6272
+ bootstrappingNode.removeCC(core_1.CommandClasses.Security);
6273
+ }
6274
+ }
6275
+ }
6276
+ else if (bootstrapInit instanceof cc_1.Security2CCKEXGet) {
6277
+ const nodeId = bootstrapInit.nodeId;
6278
+ const bootstrappingNode = this.nodes.get(nodeId);
6279
+ if (!bootstrappingNode) {
6280
+ this.driver.controllerLog.logNode(nodeId, {
6281
+ message: "Received S2 bootstrap initiation from unknown node, ignoring...",
6282
+ level: "warn",
6283
+ });
6284
+ }
6285
+ else {
6286
+ // We definitely know that the node supports S2
6287
+ bootstrappingNode.addCC(core_1.CommandClasses["Security 2"], {
6288
+ secure: true,
6289
+ isSupported: true,
6290
+ });
6291
+ let grant;
6292
+ switch (this._joinNetworkOptions?.strategy) {
6293
+ // case JoinNetworkStrategy.Security_S2: {
6294
+ // grant = this._joinNetworkOptions.requested;
6295
+ // break;
6296
+ // }
6297
+ // case JoinNetworkStrategy.SmartStart:
6298
+ case Inclusion_1.JoinNetworkStrategy.Default:
6299
+ default: {
6300
+ // No options given, just request all keys
6301
+ grant = {
6302
+ securityClasses: [...core_1.securityClassOrder],
6303
+ clientSideAuth: false,
6304
+ };
6305
+ break;
6306
+ }
6307
+ }
6308
+ if (grant) {
6309
+ this.driver.controllerLog.logNode(nodeId, {
6310
+ message: `Received S2 bootstrap initiation, requesting keys: ${grant.securityClasses.map((sc) => `\n· ${(0, shared_1.getEnumMemberName)(core_1.SecurityClass, sc)}\n`).join("")}
6311
+ client-side auth: ${grant.clientSideAuth}`,
6312
+ });
6313
+ const bootstrapResult = await this
6314
+ .expectSecurityBootstrapS2(bootstrappingNode, grant);
6315
+ if (bootstrapResult !== undefined) {
6316
+ // If there was a failure, mark S2 as not supported
6317
+ bootstrappingNode.removeCC(core_1.CommandClasses["Security 2"]);
6318
+ }
6319
+ }
6320
+ }
6321
+ }
6322
+ this._joinNetworkOptions = undefined;
6323
+ // Read protocol information of all nodes
6324
+ for (const node of this.nodes.values()) {
6325
+ if (node.isControllerNode)
6326
+ continue;
6327
+ await node["queryProtocolInfo"]();
6328
+ }
6329
+ // Notify applications that joining the network is complete
6330
+ this.emit("network joined");
6331
+ }
5234
6332
  };
5235
6333
  exports.ZWaveController = ZWaveController;
5236
6334
  exports.ZWaveController = ZWaveController = __decorate([