@zwave-js/cc 10.0.0-beta.1 → 10.0.0-beta.11

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 (164) hide show
  1. package/build/cc/AlarmSensorCC.d.ts +2 -2
  2. package/build/cc/AssociationCC.d.ts +8 -8
  3. package/build/cc/AssociationCC.d.ts.map +1 -1
  4. package/build/cc/AssociationCC.js +7 -4
  5. package/build/cc/AssociationCC.js.map +1 -1
  6. package/build/cc/AssociationGroupInfoCC.d.ts +4 -4
  7. package/build/cc/BarrierOperatorCC.d.ts +2 -2
  8. package/build/cc/BarrierOperatorCC.d.ts.map +1 -1
  9. package/build/cc/BarrierOperatorCC.js +9 -3
  10. package/build/cc/BarrierOperatorCC.js.map +1 -1
  11. package/build/cc/BasicCC.d.ts +1 -1
  12. package/build/cc/BatteryCC.d.ts +10 -10
  13. package/build/cc/BinarySensorCC.d.ts +1 -1
  14. package/build/cc/BinarySwitchCC.d.ts +1 -1
  15. package/build/cc/CRC16CC.d.ts +3 -1
  16. package/build/cc/CRC16CC.d.ts.map +1 -1
  17. package/build/cc/CRC16CC.js +11 -1
  18. package/build/cc/CRC16CC.js.map +1 -1
  19. package/build/cc/CentralSceneCC.d.ts +5 -5
  20. package/build/cc/CentralSceneCC.d.ts.map +1 -1
  21. package/build/cc/CentralSceneCC.js +4 -4
  22. package/build/cc/CentralSceneCC.js.map +1 -1
  23. package/build/cc/ClimateControlScheduleCC.d.ts +3 -3
  24. package/build/cc/ClimateControlScheduleCC.d.ts.map +1 -1
  25. package/build/cc/ClimateControlScheduleCC.js +6 -4
  26. package/build/cc/ClimateControlScheduleCC.js.map +1 -1
  27. package/build/cc/ClockCC.d.ts +2 -2
  28. package/build/cc/ClockCC.d.ts.map +1 -1
  29. package/build/cc/ClockCC.js +3 -2
  30. package/build/cc/ClockCC.js.map +1 -1
  31. package/build/cc/ColorSwitchCC.d.ts +2 -2
  32. package/build/cc/ColorSwitchCC.d.ts.map +1 -1
  33. package/build/cc/ColorSwitchCC.js +3 -4
  34. package/build/cc/ColorSwitchCC.js.map +1 -1
  35. package/build/cc/ConfigurationCC.d.ts.map +1 -1
  36. package/build/cc/ConfigurationCC.js +6 -5
  37. package/build/cc/ConfigurationCC.js.map +1 -1
  38. package/build/cc/DoorLockCC.d.ts +14 -14
  39. package/build/cc/DoorLockCC.d.ts.map +1 -1
  40. package/build/cc/DoorLockCC.js +3 -1
  41. package/build/cc/DoorLockCC.js.map +1 -1
  42. package/build/cc/DoorLockLoggingCC.d.ts +1 -1
  43. package/build/cc/EntryControlCC.d.ts +5 -6
  44. package/build/cc/EntryControlCC.d.ts.map +1 -1
  45. package/build/cc/EntryControlCC.js +20 -11
  46. package/build/cc/EntryControlCC.js.map +1 -1
  47. package/build/cc/FirmwareUpdateMetaDataCC.d.ts +4 -4
  48. package/build/cc/HumidityControlModeCC.d.ts +1 -1
  49. package/build/cc/HumidityControlSetpointCC.d.ts +1 -1
  50. package/build/cc/IndicatorCC.d.ts +6 -6
  51. package/build/cc/IndicatorCC.d.ts.map +1 -1
  52. package/build/cc/IndicatorCC.js +6 -6
  53. package/build/cc/IndicatorCC.js.map +1 -1
  54. package/build/cc/IrrigationCC.d.ts +7 -7
  55. package/build/cc/IrrigationCC.d.ts.map +1 -1
  56. package/build/cc/IrrigationCC.js +11 -8
  57. package/build/cc/IrrigationCC.js.map +1 -1
  58. package/build/cc/LanguageCC.d.ts +2 -2
  59. package/build/cc/LanguageCC.d.ts.map +1 -1
  60. package/build/cc/LanguageCC.js +3 -2
  61. package/build/cc/LanguageCC.js.map +1 -1
  62. package/build/cc/ManufacturerSpecificCC.d.ts +1 -1
  63. package/build/cc/MeterCC.d.ts +6 -6
  64. package/build/cc/MeterCC.d.ts.map +1 -1
  65. package/build/cc/MeterCC.js +3 -2
  66. package/build/cc/MeterCC.js.map +1 -1
  67. package/build/cc/MultiChannelAssociationCC.d.ts +7 -7
  68. package/build/cc/MultiChannelAssociationCC.d.ts.map +1 -1
  69. package/build/cc/MultiChannelAssociationCC.js +5 -3
  70. package/build/cc/MultiChannelAssociationCC.js.map +1 -1
  71. package/build/cc/MultiChannelCC.d.ts +3 -3
  72. package/build/cc/MultiChannelCC.d.ts.map +1 -1
  73. package/build/cc/MultiChannelCC.js +7 -6
  74. package/build/cc/MultiChannelCC.js.map +1 -1
  75. package/build/cc/MultiCommandCC.d.ts +0 -1
  76. package/build/cc/MultiCommandCC.d.ts.map +1 -1
  77. package/build/cc/MultiCommandCC.js +10 -4
  78. package/build/cc/MultiCommandCC.js.map +1 -1
  79. package/build/cc/MultilevelSensorCC.d.ts +4 -4
  80. package/build/cc/MultilevelSensorCC.d.ts.map +1 -1
  81. package/build/cc/MultilevelSensorCC.js +3 -2
  82. package/build/cc/MultilevelSensorCC.js.map +1 -1
  83. package/build/cc/MultilevelSwitchCC.d.ts +2 -2
  84. package/build/cc/NodeNamingCC.d.ts +3 -3
  85. package/build/cc/NodeNamingCC.d.ts.map +1 -1
  86. package/build/cc/NodeNamingCC.js +8 -8
  87. package/build/cc/NodeNamingCC.js.map +1 -1
  88. package/build/cc/NotificationCC.d.ts +3 -3
  89. package/build/cc/NotificationCC.d.ts.map +1 -1
  90. package/build/cc/NotificationCC.js +11 -6
  91. package/build/cc/NotificationCC.js.map +1 -1
  92. package/build/cc/PowerlevelCC.d.ts +4 -4
  93. package/build/cc/PowerlevelCC.d.ts.map +1 -1
  94. package/build/cc/PowerlevelCC.js +7 -5
  95. package/build/cc/PowerlevelCC.js.map +1 -1
  96. package/build/cc/ProtectionCC.d.ts +7 -7
  97. package/build/cc/SceneActivationCC.d.ts +3 -3
  98. package/build/cc/SceneActivationCC.d.ts.map +1 -1
  99. package/build/cc/SceneActivationCC.js +4 -4
  100. package/build/cc/SceneActivationCC.js.map +1 -1
  101. package/build/cc/SceneActuatorConfigurationCC.d.ts +2 -2
  102. package/build/cc/SceneActuatorConfigurationCC.d.ts.map +1 -1
  103. package/build/cc/SceneActuatorConfigurationCC.js +5 -5
  104. package/build/cc/SceneActuatorConfigurationCC.js.map +1 -1
  105. package/build/cc/SceneControllerConfigurationCC.d.ts +3 -3
  106. package/build/cc/SceneControllerConfigurationCC.d.ts.map +1 -1
  107. package/build/cc/SceneControllerConfigurationCC.js +6 -6
  108. package/build/cc/SceneControllerConfigurationCC.js.map +1 -1
  109. package/build/cc/Security2CC.d.ts +2 -1
  110. package/build/cc/Security2CC.d.ts.map +1 -1
  111. package/build/cc/Security2CC.js +159 -78
  112. package/build/cc/Security2CC.js.map +1 -1
  113. package/build/cc/SecurityCC.d.ts.map +1 -1
  114. package/build/cc/SecurityCC.js +11 -7
  115. package/build/cc/SecurityCC.js.map +1 -1
  116. package/build/cc/SoundSwitchCC.d.ts +1 -1
  117. package/build/cc/SupervisionCC.d.ts +2 -5
  118. package/build/cc/SupervisionCC.d.ts.map +1 -1
  119. package/build/cc/SupervisionCC.js +15 -31
  120. package/build/cc/SupervisionCC.js.map +1 -1
  121. package/build/cc/ThermostatFanModeCC.d.ts +2 -2
  122. package/build/cc/ThermostatModeCC.d.ts +1 -1
  123. package/build/cc/ThermostatSetbackCC.d.ts +2 -2
  124. package/build/cc/ThermostatSetbackCC.d.ts.map +1 -1
  125. package/build/cc/ThermostatSetbackCC.js +3 -2
  126. package/build/cc/ThermostatSetbackCC.js.map +1 -1
  127. package/build/cc/ThermostatSetpointCC.d.ts +3 -3
  128. package/build/cc/TimeCC.d.ts +17 -11
  129. package/build/cc/TimeCC.d.ts.map +1 -1
  130. package/build/cc/TimeCC.js +96 -51
  131. package/build/cc/TimeCC.js.map +1 -1
  132. package/build/cc/TimeParametersCC.d.ts +2 -2
  133. package/build/cc/TimeParametersCC.d.ts.map +1 -1
  134. package/build/cc/TimeParametersCC.js +4 -4
  135. package/build/cc/TimeParametersCC.js.map +1 -1
  136. package/build/cc/UserCodeCC.d.ts +19 -19
  137. package/build/cc/UserCodeCC.d.ts.map +1 -1
  138. package/build/cc/UserCodeCC.js +47 -35
  139. package/build/cc/UserCodeCC.js.map +1 -1
  140. package/build/cc/VersionCC.d.ts +1 -1
  141. package/build/cc/WakeUpCC.d.ts +2 -2
  142. package/build/cc/WakeUpCC.d.ts.map +1 -1
  143. package/build/cc/WakeUpCC.js +5 -5
  144. package/build/cc/WakeUpCC.js.map +1 -1
  145. package/build/cc/ZWavePlusCC.d.ts +2 -2
  146. package/build/index.d.ts +2 -1
  147. package/build/index.d.ts.map +1 -1
  148. package/build/index.js +5 -4
  149. package/build/index.js.map +1 -1
  150. package/build/lib/CommandClass.d.ts +6 -11
  151. package/build/lib/CommandClass.d.ts.map +1 -1
  152. package/build/lib/CommandClass.js +14 -7
  153. package/build/lib/CommandClass.js.map +1 -1
  154. package/build/lib/Values.d.ts +2 -2
  155. package/build/lib/_Types.d.ts +5 -82
  156. package/build/lib/_Types.d.ts.map +1 -1
  157. package/build/lib/_Types.js.map +1 -1
  158. package/build/lib/serializers.d.ts +11 -1
  159. package/build/lib/serializers.d.ts.map +1 -1
  160. package/build/lib/serializers.js +42 -3
  161. package/build/lib/serializers.js.map +1 -1
  162. package/build/lib/utils.js +1 -1
  163. package/build/lib/utils.js.map +1 -1
  164. package/package.json +13 -10
@@ -9,8 +9,9 @@ var Security2CC_1;
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.Security2CCCommandsSupportedGet = exports.Security2CCCommandsSupportedReport = exports.Security2CCTransferEnd = exports.Security2CCNetworkKeyVerify = exports.Security2CCNetworkKeyGet = exports.Security2CCNetworkKeyReport = exports.Security2CCPublicKeyReport = exports.Security2CCKEXFail = exports.Security2CCKEXSet = exports.Security2CCKEXGet = exports.Security2CCKEXReport = exports.Security2CCNonceGet = exports.Security2CCNonceReport = exports.Security2CCMessageEncapsulation = exports.Security2CC = exports.Security2CCAPI = void 0;
11
11
  const core_1 = require("@zwave-js/core");
12
- const serial_1 = require("@zwave-js/serial");
13
- const safe_1 = require("@zwave-js/shared/safe");
12
+ const safe_1 = require("@zwave-js/core/safe");
13
+ const safe_2 = require("@zwave-js/shared/safe");
14
+ const async_1 = require("alcalzone-shared/async");
14
15
  const API_1 = require("../lib/API");
15
16
  const CommandClass_1 = require("../lib/CommandClass");
16
17
  const CommandClassDecorators_1 = require("../lib/CommandClassDecorators");
@@ -36,15 +37,14 @@ function getAuthenticationData(sendingNodeId, destination, homeId, commandLength
36
37
  unencryptedPayload.copy(ret, 8, 0);
37
38
  return ret;
38
39
  }
39
- /** Validates that a sequence number is not a duplicate and updates the SPAN table if it is accepted */
40
+ /** Validates that a sequence number is not a duplicate and updates the SPAN table if it is accepted. Returns the previous sequence number if there is one. */
40
41
  function validateSequenceNumber(sequenceNumber) {
41
- const peerNodeID = this.nodeId;
42
- core_1.validatePayload.withReason("Duplicate command")(!this.host.securityManager2.isDuplicateSinglecast(peerNodeID, sequenceNumber));
42
+ core_1.validatePayload.withReason("Duplicate command")(!this.host.securityManager2.isDuplicateSinglecast(this.nodeId, sequenceNumber));
43
43
  // Not a duplicate, store it
44
- this.host.securityManager2.storeSequenceNumber(peerNodeID, sequenceNumber);
44
+ return this.host.securityManager2.storeSequenceNumber(this.nodeId, sequenceNumber);
45
45
  }
46
46
  function assertSecurity(options) {
47
- const verb = (0, serial_1.gotDeserializationOptions)(options) ? "decoded" : "sent";
47
+ const verb = (0, CommandClass_1.gotDeserializationOptions)(options) ? "decoded" : "sent";
48
48
  if (!this.host.ownNodeId) {
49
49
  throw new core_1.ZWaveError(`Secure commands (S2) can only be ${verb} when the controller's node id is known!`, core_1.ZWaveErrorCodes.Driver_NotReady);
50
50
  }
@@ -137,7 +137,7 @@ let Security2CCAPI = class Security2CCAPI extends API_1.CCAPI {
137
137
  });
138
138
  const response = await this.applHost.sendCommand(cc, this.commandOptions);
139
139
  if (response) {
140
- return (0, safe_1.pick)(response, [
140
+ return (0, safe_2.pick)(response, [
141
141
  "requestCSA",
142
142
  "echo",
143
143
  "supportedKEXSchemes",
@@ -224,7 +224,7 @@ let Security2CC = Security2CC_1 = class Security2CC extends CommandClass_1.Comma
224
224
  const api = API_1.CCAPI.create(core_1.CommandClasses["Security 2"], applHost, endpoint).withOptions({
225
225
  priority: core_1.MessagePriority.NodeQuery,
226
226
  });
227
- // Only on the highest security class the reponse includes the supported commands
227
+ // Only on the highest security class the response includes the supported commands
228
228
  const secClass = node.getHighestSecurityClass();
229
229
  let hasReceivedSecureCommands = false;
230
230
  let possibleSecurityClasses;
@@ -263,54 +263,85 @@ let Security2CC = Security2CC_1 = class Security2CC extends CommandClass_1.Comma
263
263
  if (!this.host.securityManager2?.hasKeysForSecurityClass(secClass)) {
264
264
  applHost.controllerLog.logNode(node.id, {
265
265
  endpoint: endpoint.index,
266
- message: `Cannot query securely supported commands (${(0, safe_1.getEnumMemberName)(core_1.SecurityClass, secClass)}) - network key is not configured...`,
266
+ message: `Cannot query securely supported commands (${(0, safe_2.getEnumMemberName)(core_1.SecurityClass, secClass)}) - network key is not configured...`,
267
267
  level: "warn",
268
268
  });
269
269
  continue;
270
270
  }
271
271
  applHost.controllerLog.logNode(node.id, {
272
272
  endpoint: endpoint.index,
273
- message: `Querying securely supported commands (${(0, safe_1.getEnumMemberName)(core_1.SecurityClass, secClass)})...`,
273
+ message: `Querying securely supported commands (${(0, safe_2.getEnumMemberName)(core_1.SecurityClass, secClass)})...`,
274
274
  direction: "outbound",
275
275
  });
276
276
  // Query the supported commands but avoid remembering the wrong security class in case of a failure
277
277
  let supportedCCs;
278
- try {
279
- supportedCCs = await api.getSupportedCommands(secClass);
280
- }
281
- catch (e) {
282
- if ((0, core_1.isZWaveError)(e) &&
283
- e.code === core_1.ZWaveErrorCodes.Security2CC_CannotDecode) {
284
- // This has to be expected when we're using a non-granted security class
285
- supportedCCs = undefined;
278
+ // Try up to 3 times. We REALLY don't want a spurious timeout or collision to cause us to discard a known good security class
279
+ for (let attempts = 1; attempts <= 3; attempts++) {
280
+ try {
281
+ supportedCCs = await api.getSupportedCommands(secClass);
282
+ }
283
+ catch (e) {
284
+ if ((0, core_1.isZWaveError)(e) &&
285
+ e.code === core_1.ZWaveErrorCodes.Security2CC_CannotDecode) {
286
+ // Either we were using a non-granted security class,
287
+ // or querying with the known highest security class had an issue
288
+ supportedCCs = undefined;
289
+ }
290
+ else {
291
+ throw e;
292
+ }
293
+ }
294
+ if (supportedCCs == undefined &&
295
+ possibleSecurityClasses.length === 1) {
296
+ if (attempts < 3) {
297
+ // We definitely know the highest security class
298
+ applHost.controllerLog.logNode(node.id, {
299
+ endpoint: endpoint.index,
300
+ message: `Querying securely supported commands (${(0, safe_2.getEnumMemberName)(core_1.SecurityClass, secClass)}), attempt ${attempts}/3 failed. Retrying in 500ms...`,
301
+ level: "warn",
302
+ });
303
+ await (0, async_1.wait)(500);
304
+ continue;
305
+ }
306
+ else {
307
+ applHost.controllerLog.logNode(node.id, {
308
+ endpoint: endpoint.index,
309
+ message: `Querying securely supported commands (${(0, safe_2.getEnumMemberName)(core_1.SecurityClass, secClass)}) failed. Let's hope for the best...`,
310
+ level: "warn",
311
+ });
312
+ break;
313
+ }
286
314
  }
287
315
  else {
288
- throw e;
316
+ // In any other case, we can stop trying
317
+ break;
289
318
  }
290
319
  }
291
320
  if (supportedCCs == undefined) {
292
- if (endpoint.index === 0) {
321
+ if (endpoint.index === 0 &&
322
+ possibleSecurityClasses.length > 1) {
293
323
  // No supported commands found, mark the security class as not granted
324
+ // unless we were sure about the security class
294
325
  node.setSecurityClass(secClass, false);
295
326
  applHost.controllerLog.logNode(node.id, {
296
- message: `The node was NOT granted the security class ${(0, safe_1.getEnumMemberName)(core_1.SecurityClass, secClass)}`,
327
+ message: `The node was NOT granted the security class ${(0, safe_2.getEnumMemberName)(core_1.SecurityClass, secClass)}`,
297
328
  direction: "inbound",
298
329
  });
299
330
  }
300
331
  continue;
301
332
  }
302
- if (endpoint.index === 0) {
303
- // Mark the security class as granted
333
+ if (endpoint.index === 0 && possibleSecurityClasses.length > 1) {
334
+ // Mark the security class as granted unless we were sure about the security class
304
335
  node.setSecurityClass(secClass, true);
305
336
  applHost.controllerLog.logNode(node.id, {
306
- message: `The node was granted the security class ${(0, safe_1.getEnumMemberName)(core_1.SecurityClass, secClass)}`,
337
+ message: `The node was granted the security class ${(0, safe_2.getEnumMemberName)(core_1.SecurityClass, secClass)}`,
307
338
  direction: "inbound",
308
339
  });
309
340
  }
310
341
  if (!hasReceivedSecureCommands && supportedCCs.length > 0) {
311
342
  hasReceivedSecureCommands = true;
312
343
  const logLines = [
313
- `received secure commands (${(0, safe_1.getEnumMemberName)(core_1.SecurityClass, secClass)})`,
344
+ `received secure commands (${(0, safe_2.getEnumMemberName)(core_1.SecurityClass, secClass)})`,
314
345
  "supported CCs:",
315
346
  ];
316
347
  for (const cc of supportedCCs) {
@@ -336,8 +367,9 @@ let Security2CC = Security2CC_1 = class Security2CC extends CommandClass_1.Comma
336
367
  /** Tests if a command should be sent secure and thus requires encapsulation */
337
368
  static requiresEncapsulation(cc) {
338
369
  // Everything that's not an S2 CC needs to be encapsulated if the CC is secure
339
- if (!cc.secure)
370
+ if (!(cc.encapsulationFlags & safe_1.EncapsulationFlags.Security)) {
340
371
  return false;
372
+ }
341
373
  if (!(cc instanceof Security2CC_1))
342
374
  return true;
343
375
  // These S2 commands need additional encapsulation
@@ -369,11 +401,16 @@ let Security2CC = Security2CC_1 = class Security2CC extends CommandClass_1.Comma
369
401
  }
370
402
  /** Encapsulates a command that should be sent encrypted */
371
403
  static encapsulate(host, cc, securityClass) {
372
- return new Security2CCMessageEncapsulation(host, {
404
+ const ret = new Security2CCMessageEncapsulation(host, {
373
405
  nodeId: cc.nodeId,
374
406
  encapsulated: cc,
375
407
  securityClass,
376
408
  });
409
+ // Copy the encapsulation flags from the encapsulated command
410
+ // but omit Security, since we're doing that right now
411
+ ret.encapsulationFlags =
412
+ cc.encapsulationFlags & ~safe_1.EncapsulationFlags.Security;
413
+ return ret;
377
414
  }
378
415
  };
379
416
  Security2CC = Security2CC_1 = __decorate([
@@ -403,18 +440,18 @@ let Security2CCMessageEncapsulation = class Security2CCMessageEncapsulation exte
403
440
  super(host, options);
404
441
  // Make sure that we can send/receive secure commands
405
442
  assertSecurity.call(this, options);
406
- if ((0, serial_1.gotDeserializationOptions)(options)) {
443
+ if ((0, CommandClass_1.gotDeserializationOptions)(options)) {
407
444
  (0, core_1.validatePayload)(this.payload.length >= 2);
408
445
  // Check the sequence number to avoid duplicates
409
446
  // TODO: distinguish between multicast and singlecast
410
447
  this._sequenceNumber = this.payload[0];
448
+ const sendingNodeId = this.nodeId;
411
449
  // Don't accept duplicate commands
412
- validateSequenceNumber.call(this, this._sequenceNumber);
450
+ const prevSequenceNumber = validateSequenceNumber.call(this, this._sequenceNumber);
413
451
  // Ensure the node has a security class
414
- const peerNodeID = this.nodeId;
415
452
  // const node = this.getNode()!;
416
453
  // validatePayload.withReason("The node is not included")(!!node);
417
- const securityClass = this.host.getHighestSecurityClass(peerNodeID);
454
+ const securityClass = this.host.getHighestSecurityClass(sendingNodeId);
418
455
  core_1.validatePayload.withReason("No security class granted")(securityClass !== core_1.SecurityClass.None);
419
456
  const hasExtensions = !!(this.payload[1] & 0b1);
420
457
  const hasEncryptedExtensions = !!(this.payload[1] & 0b10);
@@ -440,9 +477,9 @@ let Security2CCMessageEncapsulation = class Security2CCMessageEncapsulation exte
440
477
  const ciphertext = this.payload.slice(offset, -core_1.SECURITY_S2_AUTH_TAG_LENGTH);
441
478
  const authTag = this.payload.slice(-core_1.SECURITY_S2_AUTH_TAG_LENGTH);
442
479
  const messageLength = super.computeEncapsulationOverhead() + this.payload.length;
443
- const authData = getAuthenticationData(this.nodeId, this.getDestinationIDRX(), this.host.homeId, messageLength, unencryptedPayload);
480
+ const authData = getAuthenticationData(sendingNodeId, this.getDestinationIDRX(), this.host.homeId, messageLength, unencryptedPayload);
444
481
  // Decrypt payload and verify integrity
445
- const spanState = this.host.securityManager2.getSPANState(peerNodeID);
482
+ const spanState = this.host.securityManager2.getSPANState(sendingNodeId);
446
483
  const failNoSPAN = () => {
447
484
  return core_1.validatePayload.fail(core_1.ZWaveErrorCodes.Security2CC_NoSPAN);
448
485
  };
@@ -456,15 +493,43 @@ let Security2CCMessageEncapsulation = class Security2CCMessageEncapsulation exte
456
493
  return failNoSPAN();
457
494
  }
458
495
  const decrypt = () => {
459
- const getNonceAndDecrypt = () => {
460
- const iv = this.host.securityManager2.nextNonce(peerNodeID);
461
- const { keyCCM: key } = this.host.securityManager2.getKeysForNode(peerNodeID);
496
+ const decryptWithNonce = (nonce) => {
497
+ const { keyCCM: key } = this.host.securityManager2.getKeysForNode(sendingNodeId);
498
+ const iv = nonce;
462
499
  return {
463
- decryptionKey: key,
500
+ key,
501
+ iv,
464
502
  ...(0, core_1.decryptAES128CCM)(key, iv, ciphertext, authData, authTag),
465
503
  };
466
504
  };
505
+ const getNonceAndDecrypt = () => {
506
+ const iv = this.host.securityManager2.nextNonce(sendingNodeId);
507
+ return decryptWithNonce(iv);
508
+ };
467
509
  if (spanState.type === core_1.SPANState.SPAN) {
510
+ // There SHOULD be a shared SPAN between both parties. But experience has shown that both could have
511
+ // sent a command at roughly the same time, using the same SPAN for encryption.
512
+ // To avoid a nasty desync and both nodes trying to resync at the same time, causing message loss,
513
+ // we accept commands encrypted with the previous SPAN under very specific circumstances:
514
+ if (
515
+ // The previous SPAN is still known, i.e. the node didn't send another command that was successfully decrypted
516
+ !!spanState.currentSPAN &&
517
+ // it is still valid
518
+ spanState.currentSPAN.expires > (0, core_1.highResTimestamp)() &&
519
+ // The received command is exactly the next, expected one
520
+ prevSequenceNumber != undefined &&
521
+ this._sequenceNumber ===
522
+ ((prevSequenceNumber + 1) & 0xff) &&
523
+ // And in case of a mock-based test, do this only on the controller
524
+ !this.host.__internalIsMockNode) {
525
+ const nonce = spanState.currentSPAN.nonce;
526
+ spanState.currentSPAN = undefined;
527
+ return decryptWithNonce(nonce);
528
+ }
529
+ else {
530
+ // forgetting the current SPAN shouldn't be necessary but better be safe than sorry
531
+ spanState.currentSPAN = undefined;
532
+ }
468
533
  // This can only happen if the security class is known
469
534
  return getNonceAndDecrypt();
470
535
  }
@@ -476,18 +541,18 @@ let Security2CCMessageEncapsulation = class Security2CCMessageEncapsulation exte
476
541
  return failNoSPAN();
477
542
  const receiverEI = spanState.receiverEI;
478
543
  // How we do this depends on whether we know the security class of the other node
479
- if (this.host.securityManager2.tempKeys.has(peerNodeID)) {
544
+ if (this.host.securityManager2.tempKeys.has(sendingNodeId)) {
480
545
  // We're currently bootstrapping the node, it might be using a temporary key
481
- this.host.securityManager2.initializeTempSPAN(peerNodeID, senderEI, receiverEI);
546
+ this.host.securityManager2.initializeTempSPAN(sendingNodeId, senderEI, receiverEI);
482
547
  const ret = getNonceAndDecrypt();
483
548
  // Decryption with the temporary key worked
484
549
  if (ret.authOK)
485
550
  return ret;
486
551
  // Reset the SPAN state and try with the recently granted security class
487
- this.host.securityManager2.setSPANState(peerNodeID, spanState);
552
+ this.host.securityManager2.setSPANState(sendingNodeId, spanState);
488
553
  }
489
554
  if (securityClass != undefined) {
490
- this.host.securityManager2.initializeSPAN(peerNodeID, securityClass, senderEI, receiverEI);
555
+ this.host.securityManager2.initializeSPAN(sendingNodeId, securityClass, senderEI, receiverEI);
491
556
  return getNonceAndDecrypt();
492
557
  }
493
558
  else {
@@ -495,18 +560,18 @@ let Security2CCMessageEncapsulation = class Security2CCMessageEncapsulation exte
495
560
  // Try multiple security classes
496
561
  const possibleSecurityClasses = core_1.securityClassOrder
497
562
  .filter((s) => (0, core_1.securityClassIsS2)(s))
498
- .filter((s) => this.host.hasSecurityClass(peerNodeID, s) !== false);
563
+ .filter((s) => this.host.hasSecurityClass(sendingNodeId, s) !== false);
499
564
  for (const secClass of possibleSecurityClasses) {
500
565
  // Initialize an SPAN with that security class
501
- this.host.securityManager2.initializeSPAN(peerNodeID, secClass, senderEI, receiverEI);
566
+ this.host.securityManager2.initializeSPAN(sendingNodeId, secClass, senderEI, receiverEI);
502
567
  const ret = getNonceAndDecrypt();
503
568
  // It worked, return the result and remember the security class
504
569
  if (ret.authOK) {
505
- this.host.setSecurityClass(peerNodeID, secClass, true);
570
+ this.host.setSecurityClass(sendingNodeId, secClass, true);
506
571
  return ret;
507
572
  }
508
573
  // Reset the SPAN state and try with the next security class
509
- this.host.securityManager2.setSPANState(peerNodeID, spanState);
574
+ this.host.securityManager2.setSPANState(sendingNodeId, spanState);
510
575
  }
511
576
  }
512
577
  }
@@ -515,12 +580,13 @@ let Security2CCMessageEncapsulation = class Security2CCMessageEncapsulation exte
515
580
  };
516
581
  let plaintext;
517
582
  let authOK = false;
518
- let decryptionKey;
583
+ let key;
584
+ let iv;
519
585
  // If the Receiver is unable to authenticate the singlecast message with the current SPAN,
520
586
  // the Receiver SHOULD try decrypting the message with one or more of the following SPAN values,
521
587
  // stopping when decryption is successful or the maximum number of iterations is reached.
522
588
  for (let i = 0; i < DECRYPT_ATTEMPTS; i++) {
523
- ({ plaintext, authOK, decryptionKey } = decrypt());
589
+ ({ plaintext, authOK, key, iv } = decrypt());
524
590
  if (!!authOK && !!plaintext)
525
591
  break;
526
592
  }
@@ -544,7 +610,8 @@ let Security2CCMessageEncapsulation = class Security2CCMessageEncapsulation exte
544
610
  encapCC: this,
545
611
  });
546
612
  }
547
- this.decryptionKey = decryptionKey;
613
+ this.key = key;
614
+ this.iv = iv;
548
615
  }
549
616
  else {
550
617
  this._securityClass = options.securityClass;
@@ -594,9 +661,9 @@ let Security2CCMessageEncapsulation = class Security2CCMessageEncapsulation exte
594
661
  }
595
662
  serialize() {
596
663
  // TODO: Support Multicast
597
- const nodeId = this.nodeId;
598
664
  // Include Sender EI in the command if we only have the receiver's EI
599
- const spanState = this.host.securityManager2.getSPANState(nodeId);
665
+ const receiverNodeId = this.getDestinationIDTX();
666
+ const spanState = this.host.securityManager2.getSPANState(receiverNodeId);
600
667
  if (spanState.type === core_1.SPANState.None ||
601
668
  spanState.type === core_1.SPANState.LocalEI) {
602
669
  // Can't do anything here if we don't have the receiver's EI
@@ -609,16 +676,16 @@ let Security2CCMessageEncapsulation = class Security2CCMessageEncapsulation exte
609
676
  const receiverEI = spanState.receiverEI;
610
677
  // While bootstrapping a node, the controller only sends commands encrypted
611
678
  // with the temporary key
612
- if (this.host.securityManager2.tempKeys.has(nodeId)) {
613
- this.host.securityManager2.initializeTempSPAN(nodeId, senderEI, receiverEI);
679
+ if (this.host.securityManager2.tempKeys.has(receiverNodeId)) {
680
+ this.host.securityManager2.initializeTempSPAN(receiverNodeId, senderEI, receiverEI);
614
681
  }
615
682
  else {
616
683
  const securityClass = this._securityClass ??
617
- this.host.getHighestSecurityClass(nodeId);
684
+ this.host.getHighestSecurityClass(receiverNodeId);
618
685
  if (securityClass == undefined) {
619
686
  throw new core_1.ZWaveError("No security class defined for this command!", core_1.ZWaveErrorCodes.Security2CC_NoSPAN);
620
687
  }
621
- this.host.securityManager2.initializeSPAN(nodeId, securityClass, senderEI, receiverEI);
688
+ this.host.securityManager2.initializeSPAN(receiverNodeId, securityClass, senderEI, receiverEI);
622
689
  }
623
690
  // Add or update the SPAN extension
624
691
  let spanExtension = this.extensions.find((e) => e instanceof Extension_1.SPANExtension);
@@ -647,14 +714,19 @@ let Security2CCMessageEncapsulation = class Security2CCMessageEncapsulation exte
647
714
  ]);
648
715
  // Generate the authentication data for CCM encryption
649
716
  const messageLength = this.computeEncapsulationOverhead() + serializedCC.length;
650
- const authData = getAuthenticationData(this.host.ownNodeId, this.getDestinationIDTX(), this.host.homeId, messageLength, unencryptedPayload);
651
- const iv = this.host.securityManager2.nextNonce(nodeId);
717
+ const authData = getAuthenticationData(this.host.ownNodeId, receiverNodeId, this.host.homeId, messageLength, unencryptedPayload);
718
+ // Generate a nonce for encryption, and remember it to attempt decryption
719
+ // of potential in-flight messages from the target node.
720
+ const iv = this.host.securityManager2.nextNonce(receiverNodeId, true);
652
721
  const { keyCCM: key } =
653
722
  // Prefer the overridden security class if it was given
654
723
  this._securityClass != undefined
655
724
  ? this.host.securityManager2.getKeysForSecurityClass(this._securityClass)
656
- : this.host.securityManager2.getKeysForNode(nodeId);
725
+ : this.host.securityManager2.getKeysForNode(receiverNodeId);
657
726
  const { ciphertext: ciphertextPayload, authTag } = (0, core_1.encryptAES128CCM)(key, iv, plaintextPayload, authData, core_1.SECURITY_S2_AUTH_TAG_LENGTH);
727
+ // Remember key and IV for debugging purposes
728
+ this.key = key;
729
+ this.iv = iv;
658
730
  this.payload = Buffer.concat([
659
731
  unencryptedPayload,
660
732
  ciphertextPayload,
@@ -685,6 +757,15 @@ let Security2CCMessageEncapsulation = class Security2CCMessageEncapsulation exte
685
757
  .map((e) => e.toLogEntry())
686
758
  .join("");
687
759
  }
760
+ // Log the used keys in integration tests
761
+ if (process.env.NODE_ENV === "test") {
762
+ if (this.key) {
763
+ message.key = (0, safe_2.buffer2hex)(this.key);
764
+ }
765
+ if (this.iv) {
766
+ message.IV = (0, safe_2.buffer2hex)(this.iv);
767
+ }
768
+ }
688
769
  return {
689
770
  ...super.toLogEntry(applHost),
690
771
  message,
@@ -701,7 +782,7 @@ let Security2CCNonceReport = class Security2CCNonceReport extends Security2CC {
701
782
  super(host, options);
702
783
  // Make sure that we can send/receive secure commands
703
784
  assertSecurity.call(this, options);
704
- if ((0, serial_1.gotDeserializationOptions)(options)) {
785
+ if ((0, CommandClass_1.gotDeserializationOptions)(options)) {
705
786
  (0, core_1.validatePayload)(this.payload.length >= 2);
706
787
  this._sequenceNumber = this.payload[0];
707
788
  // Don't accept duplicate commands
@@ -755,7 +836,7 @@ let Security2CCNonceReport = class Security2CCNonceReport extends Security2CC {
755
836
  MOS: this.MOS,
756
837
  };
757
838
  if (this.receiverEI) {
758
- message["receiver entropy"] = (0, safe_1.buffer2hex)(this.receiverEI);
839
+ message["receiver entropy"] = (0, safe_2.buffer2hex)(this.receiverEI);
759
840
  }
760
841
  return {
761
842
  ...super.toLogEntry(applHost),
@@ -772,7 +853,7 @@ let Security2CCNonceGet = class Security2CCNonceGet extends Security2CC {
772
853
  super(host, options);
773
854
  // Make sure that we can send/receive secure commands
774
855
  assertSecurity.call(this, options);
775
- if ((0, serial_1.gotDeserializationOptions)(options)) {
856
+ if ((0, CommandClass_1.gotDeserializationOptions)(options)) {
776
857
  (0, core_1.validatePayload)(this.payload.length >= 1);
777
858
  this._sequenceNumber = this.payload[0];
778
859
  // Don't accept duplicate commands
@@ -814,7 +895,7 @@ exports.Security2CCNonceGet = Security2CCNonceGet;
814
895
  let Security2CCKEXReport = class Security2CCKEXReport extends Security2CC {
815
896
  constructor(host, options) {
816
897
  super(host, options);
817
- if ((0, serial_1.gotDeserializationOptions)(options)) {
898
+ if ((0, CommandClass_1.gotDeserializationOptions)(options)) {
818
899
  (0, core_1.validatePayload)(this.payload.length >= 4);
819
900
  this.requestCSA = !!(this.payload[0] & 0b10);
820
901
  this.echo = !!(this.payload[0] & 0b1);
@@ -847,14 +928,14 @@ let Security2CCKEXReport = class Security2CCKEXReport extends Security2CC {
847
928
  message: {
848
929
  echo: this.echo,
849
930
  "supported schemes": this.supportedKEXSchemes
850
- .map((s) => `\n· ${(0, safe_1.getEnumMemberName)(shared_1.KEXSchemes, s)}`)
931
+ .map((s) => `\n· ${(0, safe_2.getEnumMemberName)(shared_1.KEXSchemes, s)}`)
851
932
  .join(""),
852
933
  "supported ECDH profiles": this.supportedECDHProfiles
853
- .map((s) => `\n· ${(0, safe_1.getEnumMemberName)(shared_1.ECDHProfiles, s)}`)
934
+ .map((s) => `\n· ${(0, safe_2.getEnumMemberName)(shared_1.ECDHProfiles, s)}`)
854
935
  .join(""),
855
936
  "CSA requested": this.requestCSA,
856
937
  "requested security classes": this.requestedKeys
857
- .map((s) => `\n· ${(0, safe_1.getEnumMemberName)(core_1.SecurityClass, s)}`)
938
+ .map((s) => `\n· ${(0, safe_2.getEnumMemberName)(core_1.SecurityClass, s)}`)
858
939
  .join(""),
859
940
  },
860
941
  };
@@ -874,7 +955,7 @@ exports.Security2CCKEXGet = Security2CCKEXGet;
874
955
  let Security2CCKEXSet = class Security2CCKEXSet extends Security2CC {
875
956
  constructor(host, options) {
876
957
  super(host, options);
877
- if ((0, serial_1.gotDeserializationOptions)(options)) {
958
+ if ((0, CommandClass_1.gotDeserializationOptions)(options)) {
878
959
  (0, core_1.validatePayload)(this.payload.length >= 4);
879
960
  this.permitCSA = !!(this.payload[0] & 0b10);
880
961
  this.echo = !!(this.payload[0] & 0b1);
@@ -910,11 +991,11 @@ let Security2CCKEXSet = class Security2CCKEXSet extends Security2CC {
910
991
  ...super.toLogEntry(applHost),
911
992
  message: {
912
993
  echo: this.echo,
913
- "selected scheme": (0, safe_1.getEnumMemberName)(shared_1.KEXSchemes, this.selectedKEXScheme),
914
- "selected ECDH profile": (0, safe_1.getEnumMemberName)(shared_1.ECDHProfiles, this.selectedECDHProfile),
994
+ "selected scheme": (0, safe_2.getEnumMemberName)(shared_1.KEXSchemes, this.selectedKEXScheme),
995
+ "selected ECDH profile": (0, safe_2.getEnumMemberName)(shared_1.ECDHProfiles, this.selectedECDHProfile),
915
996
  "CSA permitted": this.permitCSA,
916
997
  "granted security classes": this.grantedKeys
917
- .map((s) => `\n· ${(0, safe_1.getEnumMemberName)(core_1.SecurityClass, s)}`)
998
+ .map((s) => `\n· ${(0, safe_2.getEnumMemberName)(core_1.SecurityClass, s)}`)
918
999
  .join(""),
919
1000
  },
920
1001
  };
@@ -927,7 +1008,7 @@ exports.Security2CCKEXSet = Security2CCKEXSet;
927
1008
  let Security2CCKEXFail = class Security2CCKEXFail extends Security2CC {
928
1009
  constructor(host, options) {
929
1010
  super(host, options);
930
- if ((0, serial_1.gotDeserializationOptions)(options)) {
1011
+ if ((0, CommandClass_1.gotDeserializationOptions)(options)) {
931
1012
  (0, core_1.validatePayload)(this.payload.length >= 1);
932
1013
  this.failType = this.payload[0];
933
1014
  }
@@ -942,7 +1023,7 @@ let Security2CCKEXFail = class Security2CCKEXFail extends Security2CC {
942
1023
  toLogEntry(applHost) {
943
1024
  return {
944
1025
  ...super.toLogEntry(applHost),
945
- message: { reason: (0, safe_1.getEnumMemberName)(shared_1.KEXFailType, this.failType) },
1026
+ message: { reason: (0, safe_2.getEnumMemberName)(shared_1.KEXFailType, this.failType) },
946
1027
  };
947
1028
  }
948
1029
  };
@@ -953,7 +1034,7 @@ exports.Security2CCKEXFail = Security2CCKEXFail;
953
1034
  let Security2CCPublicKeyReport = class Security2CCPublicKeyReport extends Security2CC {
954
1035
  constructor(host, options) {
955
1036
  super(host, options);
956
- if ((0, serial_1.gotDeserializationOptions)(options)) {
1037
+ if ((0, CommandClass_1.gotDeserializationOptions)(options)) {
957
1038
  (0, core_1.validatePayload)(this.payload.length >= 17);
958
1039
  this.includingNode = !!(this.payload[0] & 0b1);
959
1040
  this.publicKey = this.payload.slice(1);
@@ -975,7 +1056,7 @@ let Security2CCPublicKeyReport = class Security2CCPublicKeyReport extends Securi
975
1056
  ...super.toLogEntry(applHost),
976
1057
  message: {
977
1058
  "is including node": this.includingNode,
978
- "public key": (0, safe_1.buffer2hex)(this.publicKey),
1059
+ "public key": (0, safe_2.buffer2hex)(this.publicKey),
979
1060
  },
980
1061
  };
981
1062
  }
@@ -987,7 +1068,7 @@ exports.Security2CCPublicKeyReport = Security2CCPublicKeyReport;
987
1068
  let Security2CCNetworkKeyReport = class Security2CCNetworkKeyReport extends Security2CC {
988
1069
  constructor(host, options) {
989
1070
  super(host, options);
990
- if ((0, serial_1.gotDeserializationOptions)(options)) {
1071
+ if ((0, CommandClass_1.gotDeserializationOptions)(options)) {
991
1072
  // TODO: Deserialize payload
992
1073
  throw new core_1.ZWaveError(`${this.constructor.name}: deserialization not implemented`, core_1.ZWaveErrorCodes.Deserialization_NotImplemented);
993
1074
  }
@@ -1007,7 +1088,7 @@ let Security2CCNetworkKeyReport = class Security2CCNetworkKeyReport extends Secu
1007
1088
  return {
1008
1089
  ...super.toLogEntry(applHost),
1009
1090
  message: {
1010
- "security class": (0, safe_1.getEnumMemberName)(core_1.SecurityClass, this.grantedKey),
1091
+ "security class": (0, safe_2.getEnumMemberName)(core_1.SecurityClass, this.grantedKey),
1011
1092
  // This shouldn't be logged, so users can safely post their logs online
1012
1093
  // "network key": buffer2hex(this.networkKey),
1013
1094
  },
@@ -1021,7 +1102,7 @@ exports.Security2CCNetworkKeyReport = Security2CCNetworkKeyReport;
1021
1102
  let Security2CCNetworkKeyGet = class Security2CCNetworkKeyGet extends Security2CC {
1022
1103
  constructor(host, options) {
1023
1104
  super(host, options);
1024
- if ((0, serial_1.gotDeserializationOptions)(options)) {
1105
+ if ((0, CommandClass_1.gotDeserializationOptions)(options)) {
1025
1106
  (0, core_1.validatePayload)(this.payload.length >= 1);
1026
1107
  this.requestedKey = bitMaskToSecurityClass(this.payload, 0);
1027
1108
  }
@@ -1037,7 +1118,7 @@ let Security2CCNetworkKeyGet = class Security2CCNetworkKeyGet extends Security2C
1037
1118
  return {
1038
1119
  ...super.toLogEntry(applHost),
1039
1120
  message: {
1040
- "security class": (0, safe_1.getEnumMemberName)(core_1.SecurityClass, this.requestedKey),
1121
+ "security class": (0, safe_2.getEnumMemberName)(core_1.SecurityClass, this.requestedKey),
1041
1122
  },
1042
1123
  };
1043
1124
  }
@@ -1056,7 +1137,7 @@ exports.Security2CCNetworkKeyVerify = Security2CCNetworkKeyVerify;
1056
1137
  let Security2CCTransferEnd = class Security2CCTransferEnd extends Security2CC {
1057
1138
  constructor(host, options) {
1058
1139
  super(host, options);
1059
- if ((0, serial_1.gotDeserializationOptions)(options)) {
1140
+ if ((0, CommandClass_1.gotDeserializationOptions)(options)) {
1060
1141
  (0, core_1.validatePayload)(this.payload.length >= 1);
1061
1142
  this.keyVerified = !!(this.payload[0] & 0b10);
1062
1143
  this.keyRequestComplete = !!(this.payload[0] & 0b1);