@zwave-js/cc 13.10.1 → 14.0.0-beta.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 (293) hide show
  1. package/build/cc/AlarmSensorCC.d.ts +31 -20
  2. package/build/cc/AlarmSensorCC.d.ts.map +1 -1
  3. package/build/cc/AlarmSensorCC.js +106 -87
  4. package/build/cc/AlarmSensorCC.js.map +1 -1
  5. package/build/cc/AssociationCC.d.ts +38 -32
  6. package/build/cc/AssociationCC.d.ts.map +1 -1
  7. package/build/cc/AssociationCC.js +193 -176
  8. package/build/cc/AssociationCC.js.map +1 -1
  9. package/build/cc/AssociationGroupInfoCC.d.ts +42 -36
  10. package/build/cc/AssociationGroupInfoCC.d.ts.map +1 -1
  11. package/build/cc/AssociationGroupInfoCC.js +218 -200
  12. package/build/cc/AssociationGroupInfoCC.js.map +1 -1
  13. package/build/cc/BarrierOperatorCC.d.ts +41 -24
  14. package/build/cc/BarrierOperatorCC.d.ts.map +1 -1
  15. package/build/cc/BarrierOperatorCC.js +143 -109
  16. package/build/cc/BarrierOperatorCC.js.map +1 -1
  17. package/build/cc/BasicCC.d.ts +22 -23
  18. package/build/cc/BasicCC.d.ts.map +1 -1
  19. package/build/cc/BasicCC.js +83 -77
  20. package/build/cc/BasicCC.js.map +1 -1
  21. package/build/cc/BatteryCC.d.ts +22 -14
  22. package/build/cc/BatteryCC.d.ts.map +1 -1
  23. package/build/cc/BatteryCC.js +116 -87
  24. package/build/cc/BatteryCC.js.map +1 -1
  25. package/build/cc/BinarySensorCC.d.ts +22 -19
  26. package/build/cc/BinarySensorCC.d.ts.map +1 -1
  27. package/build/cc/BinarySensorCC.js +103 -93
  28. package/build/cc/BinarySensorCC.js.map +1 -1
  29. package/build/cc/BinarySwitchCC.d.ts +20 -20
  30. package/build/cc/BinarySwitchCC.d.ts.map +1 -1
  31. package/build/cc/BinarySwitchCC.js +71 -59
  32. package/build/cc/BinarySwitchCC.js.map +1 -1
  33. package/build/cc/CRC16CC.d.ts +9 -9
  34. package/build/cc/CRC16CC.d.ts.map +1 -1
  35. package/build/cc/CRC16CC.js +36 -36
  36. package/build/cc/CRC16CC.js.map +1 -1
  37. package/build/cc/CentralSceneCC.d.ts +34 -16
  38. package/build/cc/CentralSceneCC.d.ts.map +1 -1
  39. package/build/cc/CentralSceneCC.js +116 -79
  40. package/build/cc/CentralSceneCC.js.map +1 -1
  41. package/build/cc/ClimateControlScheduleCC.d.ts +38 -21
  42. package/build/cc/ClimateControlScheduleCC.d.ts.map +1 -1
  43. package/build/cc/ClimateControlScheduleCC.js +113 -81
  44. package/build/cc/ClimateControlScheduleCC.js.map +1 -1
  45. package/build/cc/ClockCC.d.ts +18 -11
  46. package/build/cc/ClockCC.d.ts.map +1 -1
  47. package/build/cc/ClockCC.js +57 -43
  48. package/build/cc/ClockCC.js.map +1 -1
  49. package/build/cc/ColorSwitchCC.d.ts +37 -33
  50. package/build/cc/ColorSwitchCC.d.ts.map +1 -1
  51. package/build/cc/ColorSwitchCC.js +216 -189
  52. package/build/cc/ColorSwitchCC.js.map +1 -1
  53. package/build/cc/ConfigurationCC.d.ts +89 -74
  54. package/build/cc/ConfigurationCC.d.ts.map +1 -1
  55. package/build/cc/ConfigurationCC.js +541 -485
  56. package/build/cc/ConfigurationCC.js.map +1 -1
  57. package/build/cc/DeviceResetLocallyCC.d.ts +3 -3
  58. package/build/cc/DeviceResetLocallyCC.d.ts.map +1 -1
  59. package/build/cc/DeviceResetLocallyCC.js +14 -14
  60. package/build/cc/DeviceResetLocallyCC.js.map +1 -1
  61. package/build/cc/DoorLockCC.d.ts +66 -27
  62. package/build/cc/DoorLockCC.d.ts.map +1 -1
  63. package/build/cc/DoorLockCC.js +288 -197
  64. package/build/cc/DoorLockCC.js.map +1 -1
  65. package/build/cc/DoorLockLoggingCC.d.ts +23 -13
  66. package/build/cc/DoorLockLoggingCC.d.ts.map +1 -1
  67. package/build/cc/DoorLockLoggingCC.js +81 -63
  68. package/build/cc/DoorLockLoggingCC.js.map +1 -1
  69. package/build/cc/EnergyProductionCC.d.ts +16 -14
  70. package/build/cc/EnergyProductionCC.d.ts.map +1 -1
  71. package/build/cc/EnergyProductionCC.js +58 -52
  72. package/build/cc/EnergyProductionCC.js.map +1 -1
  73. package/build/cc/EntryControlCC.d.ts +44 -18
  74. package/build/cc/EntryControlCC.d.ts.map +1 -1
  75. package/build/cc/EntryControlCC.js +163 -112
  76. package/build/cc/EntryControlCC.js.map +1 -1
  77. package/build/cc/FirmwareUpdateMetaDataCC.d.ts +69 -34
  78. package/build/cc/FirmwareUpdateMetaDataCC.d.ts.map +1 -1
  79. package/build/cc/FirmwareUpdateMetaDataCC.js +311 -245
  80. package/build/cc/FirmwareUpdateMetaDataCC.js.map +1 -1
  81. package/build/cc/HumidityControlModeCC.d.ts +24 -16
  82. package/build/cc/HumidityControlModeCC.d.ts.map +1 -1
  83. package/build/cc/HumidityControlModeCC.js +80 -65
  84. package/build/cc/HumidityControlModeCC.js.map +1 -1
  85. package/build/cc/HumidityControlOperatingStateCC.d.ts +11 -7
  86. package/build/cc/HumidityControlOperatingStateCC.d.ts.map +1 -1
  87. package/build/cc/HumidityControlOperatingStateCC.js +31 -22
  88. package/build/cc/HumidityControlOperatingStateCC.js.map +1 -1
  89. package/build/cc/HumidityControlSetpointCC.d.ts +68 -49
  90. package/build/cc/HumidityControlSetpointCC.d.ts.map +1 -1
  91. package/build/cc/HumidityControlSetpointCC.js +203 -181
  92. package/build/cc/HumidityControlSetpointCC.js.map +1 -1
  93. package/build/cc/InclusionControllerCC.d.ts +13 -11
  94. package/build/cc/InclusionControllerCC.d.ts.map +1 -1
  95. package/build/cc/InclusionControllerCC.js +49 -42
  96. package/build/cc/InclusionControllerCC.js.map +1 -1
  97. package/build/cc/IndicatorCC.d.ts +46 -39
  98. package/build/cc/IndicatorCC.d.ts.map +1 -1
  99. package/build/cc/IndicatorCC.js +298 -277
  100. package/build/cc/IndicatorCC.js.map +1 -1
  101. package/build/cc/IrrigationCC.d.ts +127 -57
  102. package/build/cc/IrrigationCC.d.ts.map +1 -1
  103. package/build/cc/IrrigationCC.js +496 -352
  104. package/build/cc/IrrigationCC.js.map +1 -1
  105. package/build/cc/LanguageCC.d.ts +17 -11
  106. package/build/cc/LanguageCC.d.ts.map +1 -1
  107. package/build/cc/LanguageCC.js +54 -44
  108. package/build/cc/LanguageCC.js.map +1 -1
  109. package/build/cc/LockCC.d.ts +16 -11
  110. package/build/cc/LockCC.d.ts.map +1 -1
  111. package/build/cc/LockCC.js +48 -38
  112. package/build/cc/LockCC.js.map +1 -1
  113. package/build/cc/ManufacturerProprietaryCC.d.ts +12 -11
  114. package/build/cc/ManufacturerProprietaryCC.d.ts.map +1 -1
  115. package/build/cc/ManufacturerProprietaryCC.js +45 -50
  116. package/build/cc/ManufacturerProprietaryCC.js.map +1 -1
  117. package/build/cc/ManufacturerSpecificCC.d.ts +20 -13
  118. package/build/cc/ManufacturerSpecificCC.d.ts.map +1 -1
  119. package/build/cc/ManufacturerSpecificCC.js +79 -63
  120. package/build/cc/ManufacturerSpecificCC.js.map +1 -1
  121. package/build/cc/MeterCC.d.ts +30 -26
  122. package/build/cc/MeterCC.d.ts.map +1 -1
  123. package/build/cc/MeterCC.js +236 -204
  124. package/build/cc/MeterCC.js.map +1 -1
  125. package/build/cc/MultiChannelAssociationCC.d.ts +31 -26
  126. package/build/cc/MultiChannelAssociationCC.d.ts.map +1 -1
  127. package/build/cc/MultiChannelAssociationCC.js +177 -168
  128. package/build/cc/MultiChannelAssociationCC.js.map +1 -1
  129. package/build/cc/MultiChannelCC.d.ts +70 -49
  130. package/build/cc/MultiChannelCC.d.ts.map +1 -1
  131. package/build/cc/MultiChannelCC.js +377 -318
  132. package/build/cc/MultiChannelCC.js.map +1 -1
  133. package/build/cc/MultiCommandCC.d.ts +9 -9
  134. package/build/cc/MultiCommandCC.d.ts.map +1 -1
  135. package/build/cc/MultiCommandCC.js +35 -36
  136. package/build/cc/MultiCommandCC.js.map +1 -1
  137. package/build/cc/MultilevelSensorCC.d.ts +38 -33
  138. package/build/cc/MultilevelSensorCC.d.ts.map +1 -1
  139. package/build/cc/MultilevelSensorCC.js +172 -152
  140. package/build/cc/MultilevelSensorCC.js.map +1 -1
  141. package/build/cc/MultilevelSwitchCC.d.ts +29 -25
  142. package/build/cc/MultilevelSwitchCC.d.ts.map +1 -1
  143. package/build/cc/MultilevelSwitchCC.js +147 -127
  144. package/build/cc/MultilevelSwitchCC.js.map +1 -1
  145. package/build/cc/NoOperationCC.d.ts +0 -8
  146. package/build/cc/NoOperationCC.d.ts.map +1 -1
  147. package/build/cc/NoOperationCC.js +2 -11
  148. package/build/cc/NoOperationCC.js.map +1 -1
  149. package/build/cc/NodeNamingCC.d.ts +27 -17
  150. package/build/cc/NodeNamingCC.d.ts.map +1 -1
  151. package/build/cc/NodeNamingCC.js +86 -69
  152. package/build/cc/NodeNamingCC.js.map +1 -1
  153. package/build/cc/NotificationCC.d.ts +46 -43
  154. package/build/cc/NotificationCC.d.ts.map +1 -1
  155. package/build/cc/NotificationCC.js +317 -303
  156. package/build/cc/NotificationCC.js.map +1 -1
  157. package/build/cc/PowerlevelCC.d.ts +22 -18
  158. package/build/cc/PowerlevelCC.d.ts.map +1 -1
  159. package/build/cc/PowerlevelCC.js +125 -95
  160. package/build/cc/PowerlevelCC.js.map +1 -1
  161. package/build/cc/ProtectionCC.d.ts +49 -26
  162. package/build/cc/ProtectionCC.d.ts.map +1 -1
  163. package/build/cc/ProtectionCC.js +172 -126
  164. package/build/cc/ProtectionCC.js.map +1 -1
  165. package/build/cc/SceneActivationCC.d.ts +8 -7
  166. package/build/cc/SceneActivationCC.d.ts.map +1 -1
  167. package/build/cc/SceneActivationCC.js +28 -23
  168. package/build/cc/SceneActivationCC.js.map +1 -1
  169. package/build/cc/SceneActuatorConfigurationCC.d.ts +23 -15
  170. package/build/cc/SceneActuatorConfigurationCC.d.ts.map +1 -1
  171. package/build/cc/SceneActuatorConfigurationCC.js +79 -62
  172. package/build/cc/SceneActuatorConfigurationCC.js.map +1 -1
  173. package/build/cc/SceneControllerConfigurationCC.d.ts +25 -17
  174. package/build/cc/SceneControllerConfigurationCC.d.ts.map +1 -1
  175. package/build/cc/SceneControllerConfigurationCC.js +92 -78
  176. package/build/cc/SceneControllerConfigurationCC.js.map +1 -1
  177. package/build/cc/ScheduleEntryLockCC.d.ts +86 -72
  178. package/build/cc/ScheduleEntryLockCC.d.ts.map +1 -1
  179. package/build/cc/ScheduleEntryLockCC.js +607 -455
  180. package/build/cc/ScheduleEntryLockCC.js.map +1 -1
  181. package/build/cc/Security2CC.d.ts +78 -87
  182. package/build/cc/Security2CC.d.ts.map +1 -1
  183. package/build/cc/Security2CC.js +798 -765
  184. package/build/cc/Security2CC.js.map +1 -1
  185. package/build/cc/SecurityCC.d.ts +47 -42
  186. package/build/cc/SecurityCC.d.ts.map +1 -1
  187. package/build/cc/SecurityCC.js +240 -218
  188. package/build/cc/SecurityCC.js.map +1 -1
  189. package/build/cc/SoundSwitchCC.d.ts +36 -29
  190. package/build/cc/SoundSwitchCC.d.ts.map +1 -1
  191. package/build/cc/SoundSwitchCC.js +180 -157
  192. package/build/cc/SoundSwitchCC.js.map +1 -1
  193. package/build/cc/SupervisionCC.d.ts +17 -14
  194. package/build/cc/SupervisionCC.d.ts.map +1 -1
  195. package/build/cc/SupervisionCC.js +86 -66
  196. package/build/cc/SupervisionCC.js.map +1 -1
  197. package/build/cc/ThermostatFanModeCC.d.ts +25 -15
  198. package/build/cc/ThermostatFanModeCC.d.ts.map +1 -1
  199. package/build/cc/ThermostatFanModeCC.js +79 -60
  200. package/build/cc/ThermostatFanModeCC.js.map +1 -1
  201. package/build/cc/ThermostatFanStateCC.d.ts +11 -7
  202. package/build/cc/ThermostatFanStateCC.d.ts.map +1 -1
  203. package/build/cc/ThermostatFanStateCC.js +31 -22
  204. package/build/cc/ThermostatFanStateCC.js.map +1 -1
  205. package/build/cc/ThermostatModeCC.d.ts +24 -21
  206. package/build/cc/ThermostatModeCC.d.ts.map +1 -1
  207. package/build/cc/ThermostatModeCC.js +106 -90
  208. package/build/cc/ThermostatModeCC.js.map +1 -1
  209. package/build/cc/ThermostatOperatingStateCC.d.ts +11 -7
  210. package/build/cc/ThermostatOperatingStateCC.d.ts.map +1 -1
  211. package/build/cc/ThermostatOperatingStateCC.js +31 -22
  212. package/build/cc/ThermostatOperatingStateCC.js.map +1 -1
  213. package/build/cc/ThermostatSetbackCC.d.ts +14 -13
  214. package/build/cc/ThermostatSetbackCC.d.ts.map +1 -1
  215. package/build/cc/ThermostatSetbackCC.js +64 -57
  216. package/build/cc/ThermostatSetbackCC.js.map +1 -1
  217. package/build/cc/ThermostatSetpointCC.d.ts +38 -57
  218. package/build/cc/ThermostatSetpointCC.d.ts.map +1 -1
  219. package/build/cc/ThermostatSetpointCC.js +200 -264
  220. package/build/cc/ThermostatSetpointCC.js.map +1 -1
  221. package/build/cc/TimeCC.d.ts +24 -20
  222. package/build/cc/TimeCC.d.ts.map +1 -1
  223. package/build/cc/TimeCC.js +119 -108
  224. package/build/cc/TimeCC.js.map +1 -1
  225. package/build/cc/TimeParametersCC.d.ts +18 -14
  226. package/build/cc/TimeParametersCC.d.ts.map +1 -1
  227. package/build/cc/TimeParametersCC.js +77 -69
  228. package/build/cc/TimeParametersCC.js.map +1 -1
  229. package/build/cc/TransportServiceCC.d.ts +29 -32
  230. package/build/cc/TransportServiceCC.d.ts.map +1 -1
  231. package/build/cc/TransportServiceCC.js +159 -161
  232. package/build/cc/TransportServiceCC.js.map +1 -1
  233. package/build/cc/UserCodeCC.d.ts +83 -66
  234. package/build/cc/UserCodeCC.d.ts.map +1 -1
  235. package/build/cc/UserCodeCC.js +449 -402
  236. package/build/cc/UserCodeCC.js.map +1 -1
  237. package/build/cc/VersionCC.d.ts +36 -20
  238. package/build/cc/VersionCC.d.ts.map +1 -1
  239. package/build/cc/VersionCC.js +190 -149
  240. package/build/cc/VersionCC.js.map +1 -1
  241. package/build/cc/WakeUpCC.d.ts +27 -13
  242. package/build/cc/WakeUpCC.d.ts.map +1 -1
  243. package/build/cc/WakeUpCC.js +105 -77
  244. package/build/cc/WakeUpCC.js.map +1 -1
  245. package/build/cc/WindowCoveringCC.d.ts +39 -27
  246. package/build/cc/WindowCoveringCC.d.ts.map +1 -1
  247. package/build/cc/WindowCoveringCC.js +169 -137
  248. package/build/cc/WindowCoveringCC.js.map +1 -1
  249. package/build/cc/ZWavePlusCC.d.ts +8 -7
  250. package/build/cc/ZWavePlusCC.d.ts.map +1 -1
  251. package/build/cc/ZWavePlusCC.js +44 -37
  252. package/build/cc/ZWavePlusCC.js.map +1 -1
  253. package/build/cc/ZWaveProtocolCC.d.ts +111 -84
  254. package/build/cc/ZWaveProtocolCC.d.ts.map +1 -1
  255. package/build/cc/ZWaveProtocolCC.js +583 -508
  256. package/build/cc/ZWaveProtocolCC.js.map +1 -1
  257. package/build/cc/index.d.ts +37 -34
  258. package/build/cc/index.d.ts.map +1 -1
  259. package/build/cc/index.js +6 -7
  260. package/build/cc/index.js.map +1 -1
  261. package/build/cc/manufacturerProprietary/FibaroCC.d.ts +29 -24
  262. package/build/cc/manufacturerProprietary/FibaroCC.d.ts.map +1 -1
  263. package/build/cc/manufacturerProprietary/FibaroCC.js +109 -112
  264. package/build/cc/manufacturerProprietary/FibaroCC.js.map +1 -1
  265. package/build/index.d.ts +0 -1
  266. package/build/index.d.ts.map +1 -1
  267. package/build/index.js +0 -1
  268. package/build/index.js.map +1 -1
  269. package/build/lib/API.d.ts +21 -18
  270. package/build/lib/API.d.ts.map +1 -1
  271. package/build/lib/API.js +25 -49
  272. package/build/lib/API.js.map +1 -1
  273. package/build/lib/CommandClass.d.ts +51 -89
  274. package/build/lib/CommandClass.d.ts.map +1 -1
  275. package/build/lib/CommandClass.js +177 -249
  276. package/build/lib/CommandClass.js.map +1 -1
  277. package/build/lib/EncapsulatingCommandClass.d.ts +1 -15
  278. package/build/lib/EncapsulatingCommandClass.d.ts.map +1 -1
  279. package/build/lib/EncapsulatingCommandClass.js.map +1 -1
  280. package/build/lib/ICommandClassContainer.d.ts +1 -8
  281. package/build/lib/ICommandClassContainer.d.ts.map +1 -1
  282. package/build/lib/ICommandClassContainer.js +0 -8
  283. package/build/lib/ICommandClassContainer.js.map +1 -1
  284. package/build/lib/Values.d.ts +3 -3
  285. package/build/lib/Values.d.ts.map +1 -1
  286. package/build/lib/_Types.d.ts.map +1 -1
  287. package/build/lib/_Types.js +1 -0
  288. package/build/lib/_Types.js.map +1 -1
  289. package/build/lib/utils.d.ts +14 -13
  290. package/build/lib/utils.d.ts.map +1 -1
  291. package/build/lib/utils.js +94 -92
  292. package/build/lib/utils.js.map +1 -1
  293. package/package.json +4 -5
@@ -5,7 +5,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
5
5
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
6
  return c > 3 && r && Object.defineProperty(target, key, r), r;
7
7
  };
8
- var Security2CC_1;
8
+ var Security2CC_1, Security2CCMessageEncapsulation_1, Security2CCNonceReport_1, Security2CCNonceGet_1, Security2CCKEXReport_1, Security2CCKEXSet_1, Security2CCKEXFail_1, Security2CCPublicKeyReport_1, Security2CCNetworkKeyReport_1, Security2CCNetworkKeyGet_1, Security2CCTransferEnd_1, Security2CCCommandsSupportedReport_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");
@@ -49,32 +49,201 @@ function getAuthenticationData(sendingNodeId, destination, homeId, commandLength
49
49
  unencryptedPayload.copy(ret, offset, 0);
50
50
  return ret;
51
51
  }
52
- function getSecurityManager(host, destination) {
53
- const longRange = (0, core_1.isLongRangeNodeId)(host.ownNodeId)
54
- || (0, core_1.isLongRangeNodeId)((0, typeguards_1.isArray)(destination) ? destination[0] : destination);
52
+ function getSecurityManager(ownNodeId, securityManagers, otherNodeId) {
53
+ const longRange = (0, core_1.isLongRangeNodeId)(ownNodeId)
54
+ || (0, core_1.isLongRangeNodeId)((0, typeguards_1.isArray)(otherNodeId) ? otherNodeId[0] : otherNodeId);
55
55
  return longRange
56
- ? host.securityManagerLR
57
- : host.securityManager2;
56
+ ? securityManagers.securityManagerLR
57
+ : securityManagers.securityManager2;
58
58
  }
59
59
  /** 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. */
60
- function validateSequenceNumber(sequenceNumber) {
61
- const securityManager = getSecurityManager(this.host, this.nodeId);
62
- core_1.validatePayload.withReason(`Duplicate command (sequence number ${sequenceNumber})`)(!securityManager.isDuplicateSinglecast(this.nodeId, sequenceNumber));
60
+ function validateSequenceNumber(securityManager, sourceNodeId, sequenceNumber) {
61
+ core_1.validatePayload.withReason(`Duplicate command (sequence number ${sequenceNumber})`)(!securityManager.isDuplicateSinglecast(sourceNodeId, sequenceNumber));
63
62
  // Not a duplicate, store it
64
- return securityManager.storeSequenceNumber(this.nodeId, sequenceNumber);
65
- }
66
- function assertSecurity(options) {
67
- const verb = (0, CommandClass_1.gotDeserializationOptions)(options) ? "decoded" : "sent";
68
- if (!this.host.ownNodeId) {
69
- 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);
70
- }
71
- else if (!getSecurityManager(this.host, this.nodeId)) {
72
- throw new core_1.ZWaveError(`Secure commands (S2) can only be ${verb} when the network keys are configured!`, core_1.ZWaveErrorCodes.Driver_NoSecurity);
73
- }
63
+ return securityManager.storeSequenceNumber(sourceNodeId, sequenceNumber);
74
64
  }
75
65
  const MAX_DECRYPT_ATTEMPTS_SINGLECAST = 5;
76
66
  const MAX_DECRYPT_ATTEMPTS_MULTICAST = 5;
77
67
  const MAX_DECRYPT_ATTEMPTS_SC_FOLLOWUP = 1;
68
+ function assertSecurityRX(ctx) {
69
+ if (!ctx.ownNodeId) {
70
+ throw new core_1.ZWaveError(`Secure commands (S2) can only be decoded when the controller's node id is known!`, core_1.ZWaveErrorCodes.Driver_NotReady);
71
+ }
72
+ const ret = getSecurityManager(ctx.ownNodeId, ctx, ctx.sourceNodeId);
73
+ if (!ret) {
74
+ throw new core_1.ZWaveError(`Secure commands (S2) can only be decoded when the security manager is set up!`, core_1.ZWaveErrorCodes.Driver_NoSecurity);
75
+ }
76
+ return ret;
77
+ }
78
+ function assertSecurityTX(ctx, destination) {
79
+ if (!ctx.ownNodeId) {
80
+ throw new core_1.ZWaveError(`Secure commands (S2) can only be sent when the controller's node id is known!`, core_1.ZWaveErrorCodes.Driver_NotReady);
81
+ }
82
+ const ret = getSecurityManager(ctx.ownNodeId, ctx, destination);
83
+ if (!ret) {
84
+ throw new core_1.ZWaveError(`Secure commands (S2) can only be sent when the security manager is set up!`, core_1.ZWaveErrorCodes.Driver_NoSecurity);
85
+ }
86
+ return ret;
87
+ }
88
+ function decryptSinglecast(ctx, securityManager, sendingNodeId, curSequenceNumber, prevSequenceNumber, ciphertext, authData, authTag, spanState, extensions) {
89
+ const decryptWithNonce = (nonce) => {
90
+ const { keyCCM: key } = securityManager.getKeysForNode(sendingNodeId);
91
+ const iv = nonce;
92
+ return {
93
+ key,
94
+ iv,
95
+ ...(0, core_1.decryptAES128CCM)(key, iv, ciphertext, authData, authTag),
96
+ };
97
+ };
98
+ const getNonceAndDecrypt = () => {
99
+ const iv = securityManager.nextNonce(sendingNodeId);
100
+ return decryptWithNonce(iv);
101
+ };
102
+ if (spanState.type === core_1.SPANState.SPAN) {
103
+ // There SHOULD be a shared SPAN between both parties. But experience has shown that both could have
104
+ // sent a command at roughly the same time, using the same SPAN for encryption.
105
+ // To avoid a nasty desync and both nodes trying to resync at the same time, causing message loss,
106
+ // we accept commands encrypted with the previous SPAN under very specific circumstances:
107
+ if (
108
+ // The previous SPAN is still known, i.e. the node didn't send another command that was successfully decrypted
109
+ !!spanState.currentSPAN
110
+ // it is still valid
111
+ && spanState.currentSPAN.expires > (0, core_1.highResTimestamp)()
112
+ // The received command is exactly the next, expected one
113
+ && prevSequenceNumber != undefined
114
+ && curSequenceNumber === ((prevSequenceNumber + 1) & 0xff)
115
+ // And in case of a mock-based test, do this only on the controller
116
+ && !ctx.__internalIsMockNode) {
117
+ const nonce = spanState.currentSPAN.nonce;
118
+ spanState.currentSPAN = undefined;
119
+ // If we could decrypt this way, we're done...
120
+ const result = decryptWithNonce(nonce);
121
+ if (result.authOK) {
122
+ return {
123
+ ...result,
124
+ securityClass: spanState.securityClass,
125
+ };
126
+ }
127
+ // ...otherwise, we need to try the normal way
128
+ }
129
+ else {
130
+ // forgetting the current SPAN shouldn't be necessary but better be safe than sorry
131
+ spanState.currentSPAN = undefined;
132
+ }
133
+ // This can only happen if the security class is known
134
+ return {
135
+ ...getNonceAndDecrypt(),
136
+ securityClass: spanState.securityClass,
137
+ };
138
+ }
139
+ else if (spanState.type === core_1.SPANState.LocalEI) {
140
+ // We've sent the other our receiver's EI and received its sender's EI,
141
+ // meaning we can now establish an SPAN
142
+ const senderEI = getSenderEI(extensions);
143
+ if (!senderEI)
144
+ failNoSPAN();
145
+ const receiverEI = spanState.receiverEI;
146
+ // How we do this depends on whether we know the security class of the other node
147
+ const isBootstrappingNode = securityManager.tempKeys.has(sendingNodeId);
148
+ if (isBootstrappingNode) {
149
+ // We're currently bootstrapping the node, it might be using a temporary key
150
+ securityManager.initializeTempSPAN(sendingNodeId, senderEI, receiverEI);
151
+ const ret = getNonceAndDecrypt();
152
+ // Decryption with the temporary key worked
153
+ if (ret.authOK) {
154
+ return {
155
+ ...ret,
156
+ securityClass: core_1.SecurityClass.Temporary,
157
+ };
158
+ }
159
+ // Reset the SPAN state and try with the recently granted security class
160
+ securityManager.setSPANState(sendingNodeId, spanState);
161
+ }
162
+ // When ending up here, one of two situations has occured:
163
+ // a) We've taken over an existing network and do not know the node's security class
164
+ // b) We know the security class, but we're about to establish a new SPAN. This may happen at a lower
165
+ // security class than the one the node normally uses, e.g. when we're being queried for securely
166
+ // supported CCs.
167
+ // In both cases, we should simply try decoding with multiple security classes, starting from the highest one.
168
+ // If this fails, we restore the previous (partial) SPAN state.
169
+ // Try all security classes where we do not definitely know that it was not granted
170
+ // While bootstrapping a node, we consider the key that is being exchanged (including S0) to be the highest. No need to look at others
171
+ const possibleSecurityClasses = isBootstrappingNode
172
+ ? [ctx.getHighestSecurityClass(sendingNodeId)]
173
+ : core_1.securityClassOrder.filter((s) => ctx.hasSecurityClass(sendingNodeId, s)
174
+ !== false);
175
+ for (const secClass of possibleSecurityClasses) {
176
+ // Skip security classes we don't have keys for
177
+ if (!securityManager.hasKeysForSecurityClass(secClass)) {
178
+ continue;
179
+ }
180
+ // Initialize an SPAN with that security class
181
+ securityManager.initializeSPAN(sendingNodeId, secClass, senderEI, receiverEI);
182
+ const ret = getNonceAndDecrypt();
183
+ // It worked, return the result
184
+ if (ret.authOK) {
185
+ // Also if we weren't sure before, we now know that the security class is granted
186
+ if (ctx.hasSecurityClass(sendingNodeId, secClass)
187
+ === undefined) {
188
+ ctx.setSecurityClass(sendingNodeId, secClass, true);
189
+ }
190
+ return {
191
+ ...ret,
192
+ securityClass: secClass,
193
+ };
194
+ }
195
+ else {
196
+ // Reset the SPAN state and try with the next security class
197
+ securityManager.setSPANState(sendingNodeId, spanState);
198
+ }
199
+ }
200
+ }
201
+ // Nothing worked, fail the decryption
202
+ return {
203
+ plaintext: Buffer.from([]),
204
+ authOK: false,
205
+ securityClass: undefined,
206
+ };
207
+ }
208
+ function decryptMulticast(sendingNodeId, securityManager, groupId, ciphertext, authData, authTag) {
209
+ const iv = securityManager.nextPeerMPAN(sendingNodeId, groupId);
210
+ const { keyCCM: key } = securityManager.getKeysForNode(sendingNodeId);
211
+ return {
212
+ key,
213
+ iv,
214
+ ...(0, core_1.decryptAES128CCM)(key, iv, ciphertext, authData, authTag),
215
+ // The security class is irrelevant when decrypting multicast commands
216
+ securityClass: undefined,
217
+ };
218
+ }
219
+ function getDestinationIDTX() {
220
+ if (this.isSinglecast())
221
+ return this.nodeId;
222
+ const ret = getMulticastGroupId(this.extensions);
223
+ if (ret == undefined) {
224
+ throw new core_1.ZWaveError("Multicast Security S2 encapsulation requires the MGRP extension", core_1.ZWaveErrorCodes.Security2CC_MissingExtension);
225
+ }
226
+ return ret;
227
+ }
228
+ function getDestinationIDRX(ctx, extensions) {
229
+ if (ctx.frameType === "singlecast") {
230
+ return ctx.ownNodeId;
231
+ }
232
+ const ret = getMulticastGroupId(extensions);
233
+ if (ret == undefined) {
234
+ throw new core_1.ZWaveError("Multicast Security S2 encapsulation requires the MGRP extension", core_1.ZWaveErrorCodes.Security2CC_MissingExtension);
235
+ }
236
+ return ret;
237
+ }
238
+ function getMulticastGroupId(extensions) {
239
+ const mgrpExtension = extensions.find((e) => e instanceof Extension_1.MGRPExtension);
240
+ return mgrpExtension?.groupId;
241
+ }
242
+ /** Returns the Sender's Entropy Input if this command contains an SPAN extension */
243
+ function getSenderEI(extensions) {
244
+ const spanExtension = extensions.find((e) => e instanceof Extension_1.SPANExtension);
245
+ return spanExtension?.senderEI;
246
+ }
78
247
  // Encapsulation CCs are used internally and too frequently that we
79
248
  // want to pay the cost of validating each call
80
249
  /* eslint-disable @zwave-js/ccapi-validate-args */
@@ -90,20 +259,20 @@ let Security2CCAPI = class Security2CCAPI extends API_1.CCAPI {
90
259
  async sendNonce() {
91
260
  this.assertSupportsCommand(_Types_1.Security2Command, _Types_1.Security2Command.NonceReport);
92
261
  this.assertPhysicalEndpoint(this.endpoint);
93
- const securityManager = getSecurityManager(this.applHost, this.endpoint.nodeId);
262
+ const securityManager = getSecurityManager(this.host.ownNodeId, this.host, this.endpoint.nodeId);
94
263
  if (!securityManager) {
95
264
  throw new core_1.ZWaveError(`Nonces can only be sent if secure communication is set up!`, core_1.ZWaveErrorCodes.Driver_NoSecurity);
96
265
  }
97
266
  const receiverEI = securityManager.generateNonce(this.endpoint.nodeId);
98
- const cc = new Security2CCNonceReport(this.applHost, {
267
+ const cc = new Security2CCNonceReport({
99
268
  nodeId: this.endpoint.nodeId,
100
- endpoint: this.endpoint.index,
269
+ endpointIndex: this.endpoint.index,
101
270
  SOS: true,
102
271
  MOS: false,
103
272
  receiverEI,
104
273
  });
105
274
  try {
106
- await this.applHost.sendCommand(cc, {
275
+ await this.host.sendCommand(cc, {
107
276
  ...this.commandOptions,
108
277
  // Seems we need these options or some nodes won't accept the nonce
109
278
  transmitOptions: core_1.TransmitOptions.ACK
@@ -134,14 +303,14 @@ let Security2CCAPI = class Security2CCAPI extends API_1.CCAPI {
134
303
  async sendMOS() {
135
304
  this.assertSupportsCommand(_Types_1.Security2Command, _Types_1.Security2Command.NonceReport);
136
305
  this.assertPhysicalEndpoint(this.endpoint);
137
- const cc = new Security2CCNonceReport(this.applHost, {
306
+ const cc = new Security2CCNonceReport({
138
307
  nodeId: this.endpoint.nodeId,
139
- endpoint: this.endpoint.index,
308
+ endpointIndex: this.endpoint.index,
140
309
  SOS: false,
141
310
  MOS: true,
142
311
  });
143
312
  try {
144
- await this.applHost.sendCommand(cc, {
313
+ await this.host.sendCommand(cc, {
145
314
  ...this.commandOptions,
146
315
  // Seems we need these options or some nodes won't accept the nonce
147
316
  transmitOptions: core_1.TransmitOptions.ACK
@@ -169,9 +338,9 @@ let Security2CCAPI = class Security2CCAPI extends API_1.CCAPI {
169
338
  async sendMPAN(groupId, innerMPANState) {
170
339
  this.assertSupportsCommand(_Types_1.Security2Command, _Types_1.Security2Command.MessageEncapsulation);
171
340
  this.assertPhysicalEndpoint(this.endpoint);
172
- const cc = new Security2CCMessageEncapsulation(this.applHost, {
341
+ const cc = new Security2CCMessageEncapsulation({
173
342
  nodeId: this.endpoint.nodeId,
174
- endpoint: this.endpoint.index,
343
+ endpointIndex: this.endpoint.index,
175
344
  extensions: [
176
345
  new Extension_1.MPANExtension({
177
346
  groupId,
@@ -180,7 +349,7 @@ let Security2CCAPI = class Security2CCAPI extends API_1.CCAPI {
180
349
  ],
181
350
  });
182
351
  try {
183
- await this.applHost.sendCommand(cc, {
352
+ await this.host.sendCommand(cc, {
184
353
  ...this.commandOptions,
185
354
  // Seems we need these options or some nodes won't accept the nonce
186
355
  transmitOptions: core_1.TransmitOptions.ACK
@@ -210,18 +379,21 @@ let Security2CCAPI = class Security2CCAPI extends API_1.CCAPI {
210
379
  */
211
380
  async getSupportedCommands(securityClass) {
212
381
  this.assertSupportsCommand(_Types_1.Security2Command, _Types_1.Security2Command.CommandsSupportedGet);
213
- let cc = new Security2CCCommandsSupportedGet(this.applHost, {
382
+ let cc = new Security2CCCommandsSupportedGet({
214
383
  nodeId: this.endpoint.nodeId,
215
- endpoint: this.endpoint.index,
384
+ endpointIndex: this.endpoint.index,
216
385
  });
217
386
  // Security2CCCommandsSupportedGet is special because we cannot reply on the applHost to do the automatic
218
387
  // encapsulation because it would use a different security class. Therefore the entire possible stack
219
388
  // of encapsulation needs to be done here
220
389
  if (MultiChannelCC_1.MultiChannelCC.requiresEncapsulation(cc)) {
221
- cc = MultiChannelCC_1.MultiChannelCC.encapsulate(this.applHost, cc);
390
+ const multiChannelCCVersion = this.host.getSupportedCCVersion(core_1.CommandClasses["Multi Channel"], this.endpoint.nodeId);
391
+ cc = multiChannelCCVersion === 1
392
+ ? MultiChannelCC_1.MultiChannelCC.encapsulateV1(cc)
393
+ : MultiChannelCC_1.MultiChannelCC.encapsulate(cc);
222
394
  }
223
- cc = Security2CC.encapsulate(this.applHost, cc, { securityClass });
224
- const response = await this.applHost.sendCommand(cc, {
395
+ cc = Security2CC.encapsulate(cc, this.host.ownNodeId, this.host, { securityClass });
396
+ const response = await this.host.sendCommand(cc, {
225
397
  ...this.commandOptions,
226
398
  autoEncapsulate: false,
227
399
  });
@@ -229,21 +401,21 @@ let Security2CCAPI = class Security2CCAPI extends API_1.CCAPI {
229
401
  }
230
402
  async reportSupportedCommands(supportedCCs) {
231
403
  this.assertSupportsCommand(_Types_1.Security2Command, _Types_1.Security2Command.CommandsSupportedReport);
232
- const cc = new Security2CCCommandsSupportedReport(this.applHost, {
404
+ const cc = new Security2CCCommandsSupportedReport({
233
405
  nodeId: this.endpoint.nodeId,
234
- endpoint: this.endpoint.index,
406
+ endpointIndex: this.endpoint.index,
235
407
  supportedCCs,
236
408
  });
237
- await this.applHost.sendCommand(cc, this.commandOptions);
409
+ await this.host.sendCommand(cc, this.commandOptions);
238
410
  }
239
411
  // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
240
412
  async getKeyExchangeParameters() {
241
413
  this.assertSupportsCommand(_Types_1.Security2Command, _Types_1.Security2Command.KEXGet);
242
- const cc = new Security2CCKEXGet(this.applHost, {
414
+ const cc = new Security2CCKEXGet({
243
415
  nodeId: this.endpoint.nodeId,
244
- endpoint: this.endpoint.index,
416
+ endpointIndex: this.endpoint.index,
245
417
  });
246
- const response = await this.applHost.sendCommand(cc, this.commandOptions);
418
+ const response = await this.host.sendCommand(cc, this.commandOptions);
247
419
  if (response) {
248
420
  return (0, safe_2.pick)(response, [
249
421
  "requestCSA",
@@ -258,103 +430,103 @@ let Security2CCAPI = class Security2CCAPI extends API_1.CCAPI {
258
430
  /** Requests the given keys from an including node */
259
431
  async requestKeys(params) {
260
432
  this.assertSupportsCommand(_Types_1.Security2Command, _Types_1.Security2Command.KEXReport);
261
- const cc = new Security2CCKEXReport(this.applHost, {
433
+ const cc = new Security2CCKEXReport({
262
434
  nodeId: this.endpoint.nodeId,
263
- endpoint: this.endpoint.index,
435
+ endpointIndex: this.endpoint.index,
264
436
  ...params,
265
437
  echo: false,
266
438
  });
267
- await this.applHost.sendCommand(cc, this.commandOptions);
439
+ await this.host.sendCommand(cc, this.commandOptions);
268
440
  }
269
441
  /** Grants the joining node the given keys */
270
442
  async grantKeys(params) {
271
443
  this.assertSupportsCommand(_Types_1.Security2Command, _Types_1.Security2Command.KEXSet);
272
- const cc = new Security2CCKEXSet(this.applHost, {
444
+ const cc = new Security2CCKEXSet({
273
445
  nodeId: this.endpoint.nodeId,
274
- endpoint: this.endpoint.index,
446
+ endpointIndex: this.endpoint.index,
275
447
  ...params,
276
448
  echo: false,
277
449
  });
278
- await this.applHost.sendCommand(cc, this.commandOptions);
450
+ await this.host.sendCommand(cc, this.commandOptions);
279
451
  }
280
452
  /** Confirms the keys that were requested by a node */
281
453
  async confirmRequestedKeys(params) {
282
454
  this.assertSupportsCommand(_Types_1.Security2Command, _Types_1.Security2Command.KEXReport);
283
- const cc = new Security2CCKEXReport(this.applHost, {
455
+ const cc = new Security2CCKEXReport({
284
456
  nodeId: this.endpoint.nodeId,
285
- endpoint: this.endpoint.index,
457
+ endpointIndex: this.endpoint.index,
286
458
  ...params,
287
459
  echo: true,
288
460
  });
289
- await this.applHost.sendCommand(cc, this.commandOptions);
461
+ await this.host.sendCommand(cc, this.commandOptions);
290
462
  }
291
463
  /** Confirms the keys that were granted by the including node */
292
464
  async confirmGrantedKeys(params) {
293
465
  this.assertSupportsCommand(_Types_1.Security2Command, _Types_1.Security2Command.KEXSet);
294
- const cc = new Security2CCKEXSet(this.applHost, {
466
+ const cc = new Security2CCKEXSet({
295
467
  nodeId: this.endpoint.nodeId,
296
- endpoint: this.endpoint.index,
468
+ endpointIndex: this.endpoint.index,
297
469
  ...params,
298
470
  echo: true,
299
471
  });
300
- return this.applHost.sendCommand(cc, this.commandOptions);
472
+ return this.host.sendCommand(cc, this.commandOptions);
301
473
  }
302
474
  /** Notifies the other node that the ongoing key exchange was aborted */
303
475
  async abortKeyExchange(failType) {
304
476
  this.assertSupportsCommand(_Types_1.Security2Command, _Types_1.Security2Command.KEXFail);
305
- const cc = new Security2CCKEXFail(this.applHost, {
477
+ const cc = new Security2CCKEXFail({
306
478
  nodeId: this.endpoint.nodeId,
307
- endpoint: this.endpoint.index,
479
+ endpointIndex: this.endpoint.index,
308
480
  failType,
309
481
  });
310
- await this.applHost.sendCommand(cc, this.commandOptions);
482
+ await this.host.sendCommand(cc, this.commandOptions);
311
483
  }
312
484
  async sendPublicKey(publicKey, includingNode = true) {
313
485
  this.assertSupportsCommand(_Types_1.Security2Command, _Types_1.Security2Command.PublicKeyReport);
314
- const cc = new Security2CCPublicKeyReport(this.applHost, {
486
+ const cc = new Security2CCPublicKeyReport({
315
487
  nodeId: this.endpoint.nodeId,
316
- endpoint: this.endpoint.index,
488
+ endpointIndex: this.endpoint.index,
317
489
  includingNode,
318
490
  publicKey,
319
491
  });
320
- await this.applHost.sendCommand(cc, this.commandOptions);
492
+ await this.host.sendCommand(cc, this.commandOptions);
321
493
  }
322
494
  async requestNetworkKey(securityClass) {
323
495
  this.assertSupportsCommand(_Types_1.Security2Command, _Types_1.Security2Command.NetworkKeyGet);
324
- const cc = new Security2CCNetworkKeyGet(this.applHost, {
496
+ const cc = new Security2CCNetworkKeyGet({
325
497
  nodeId: this.endpoint.nodeId,
326
- endpoint: this.endpoint.index,
498
+ endpointIndex: this.endpoint.index,
327
499
  requestedKey: securityClass,
328
500
  });
329
- await this.applHost.sendCommand(cc, this.commandOptions);
501
+ await this.host.sendCommand(cc, this.commandOptions);
330
502
  }
331
503
  async sendNetworkKey(securityClass, networkKey) {
332
504
  this.assertSupportsCommand(_Types_1.Security2Command, _Types_1.Security2Command.NetworkKeyReport);
333
- const cc = new Security2CCNetworkKeyReport(this.applHost, {
505
+ const cc = new Security2CCNetworkKeyReport({
334
506
  nodeId: this.endpoint.nodeId,
335
- endpoint: this.endpoint.index,
507
+ endpointIndex: this.endpoint.index,
336
508
  grantedKey: securityClass,
337
509
  networkKey,
338
510
  });
339
- await this.applHost.sendCommand(cc, this.commandOptions);
511
+ await this.host.sendCommand(cc, this.commandOptions);
340
512
  }
341
513
  async verifyNetworkKey() {
342
514
  this.assertSupportsCommand(_Types_1.Security2Command, _Types_1.Security2Command.NetworkKeyVerify);
343
- const cc = new Security2CCNetworkKeyVerify(this.applHost, {
515
+ const cc = new Security2CCNetworkKeyVerify({
344
516
  nodeId: this.endpoint.nodeId,
345
- endpoint: this.endpoint.index,
517
+ endpointIndex: this.endpoint.index,
346
518
  });
347
- await this.applHost.sendCommand(cc, this.commandOptions);
519
+ await this.host.sendCommand(cc, this.commandOptions);
348
520
  }
349
521
  async confirmKeyVerification() {
350
522
  this.assertSupportsCommand(_Types_1.Security2Command, _Types_1.Security2Command.TransferEnd);
351
- const cc = new Security2CCTransferEnd(this.applHost, {
523
+ const cc = new Security2CCTransferEnd({
352
524
  nodeId: this.endpoint.nodeId,
353
- endpoint: this.endpoint.index,
525
+ endpointIndex: this.endpoint.index,
354
526
  keyVerified: true,
355
527
  keyRequestComplete: false,
356
528
  });
357
- await this.applHost.sendCommand(cc, {
529
+ await this.host.sendCommand(cc, {
358
530
  ...this.commandOptions,
359
531
  // Don't wait for an ACK from the node
360
532
  transmitOptions: core_1.TransmitOptions.DEFAULT & ~core_1.TransmitOptions.ACK,
@@ -362,13 +534,13 @@ let Security2CCAPI = class Security2CCAPI extends API_1.CCAPI {
362
534
  }
363
535
  async endKeyExchange() {
364
536
  this.assertSupportsCommand(_Types_1.Security2Command, _Types_1.Security2Command.TransferEnd);
365
- const cc = new Security2CCTransferEnd(this.applHost, {
537
+ const cc = new Security2CCTransferEnd({
366
538
  nodeId: this.endpoint.nodeId,
367
- endpoint: this.endpoint.index,
539
+ endpointIndex: this.endpoint.index,
368
540
  keyVerified: false,
369
541
  keyRequestComplete: true,
370
542
  });
371
- await this.applHost.sendCommand(cc, this.commandOptions);
543
+ await this.host.sendCommand(cc, this.commandOptions);
372
544
  }
373
545
  };
374
546
  exports.Security2CCAPI = Security2CCAPI;
@@ -376,13 +548,13 @@ exports.Security2CCAPI = Security2CCAPI = __decorate([
376
548
  (0, CommandClassDecorators_1.API)(core_1.CommandClasses["Security 2"])
377
549
  ], Security2CCAPI);
378
550
  let Security2CC = Security2CC_1 = class Security2CC extends CommandClass_1.CommandClass {
379
- async interview(applHost) {
380
- const node = this.getNode(applHost);
381
- const endpoint = this.getEndpoint(applHost);
382
- const api = API_1.CCAPI.create(core_1.CommandClasses["Security 2"], applHost, endpoint).withOptions({
551
+ async interview(ctx) {
552
+ const securityManager = getSecurityManager(ctx.ownNodeId, ctx, this.nodeId);
553
+ const node = this.getNode(ctx);
554
+ const endpoint = this.getEndpoint(ctx);
555
+ const api = API_1.CCAPI.create(core_1.CommandClasses["Security 2"], ctx, endpoint).withOptions({
383
556
  priority: core_1.MessagePriority.NodeQuery,
384
557
  });
385
- const securityManager = getSecurityManager(applHost, node.id);
386
558
  // Only on the highest security class the response includes the supported commands
387
559
  const secClass = node.getHighestSecurityClass();
388
560
  let hasReceivedSecureCommands = false;
@@ -402,7 +574,7 @@ let Security2CC = Security2CC_1 = class Security2CC extends CommandClass_1.Comma
402
574
  }
403
575
  else {
404
576
  // For endpoint interviews, the security class MUST be known
405
- applHost.controllerLog.logNode(node.id, {
577
+ ctx.logNode(node.id, {
406
578
  endpoint: endpoint.index,
407
579
  message: `Cannot query securely supported commands for endpoint because the node's security class isn't known...`,
408
580
  level: "error",
@@ -420,14 +592,14 @@ let Security2CC = Security2CC_1 = class Security2CC extends CommandClass_1.Comma
420
592
  continue;
421
593
  // If no key is configured for this security class, skip it
422
594
  if (!securityManager?.hasKeysForSecurityClass(secClass)) {
423
- applHost.controllerLog.logNode(node.id, {
595
+ ctx.logNode(node.id, {
424
596
  endpoint: endpoint.index,
425
597
  message: `Cannot query securely supported commands (${(0, safe_2.getEnumMemberName)(core_1.SecurityClass, secClass)}) - network key is not configured...`,
426
598
  level: "warn",
427
599
  });
428
600
  continue;
429
601
  }
430
- applHost.controllerLog.logNode(node.id, {
602
+ ctx.logNode(node.id, {
431
603
  endpoint: endpoint.index,
432
604
  message: `Querying securely supported commands (${(0, safe_2.getEnumMemberName)(core_1.SecurityClass, secClass)})...`,
433
605
  direction: "outbound",
@@ -455,7 +627,7 @@ let Security2CC = Security2CC_1 = class Security2CC extends CommandClass_1.Comma
455
627
  && possibleSecurityClasses.length === 1) {
456
628
  if (attempts < MAX_ATTEMPTS) {
457
629
  // We definitely know the highest security class
458
- applHost.controllerLog.logNode(node.id, {
630
+ ctx.logNode(node.id, {
459
631
  endpoint: endpoint.index,
460
632
  message: `Querying securely supported commands (${(0, safe_2.getEnumMemberName)(core_1.SecurityClass, secClass)}), attempt ${attempts}/${MAX_ATTEMPTS} failed. Retrying in 500ms...`,
461
633
  level: "warn",
@@ -464,7 +636,7 @@ let Security2CC = Security2CC_1 = class Security2CC extends CommandClass_1.Comma
464
636
  continue;
465
637
  }
466
638
  else if (endpoint.index > 0) {
467
- applHost.controllerLog.logNode(node.id, {
639
+ ctx.logNode(node.id, {
468
640
  endpoint: endpoint.index,
469
641
  message: `Querying securely supported commands (${(0, safe_2.getEnumMemberName)(core_1.SecurityClass, secClass)}) failed. Assuming the endpoint supports all its mandatory CCs securely...`,
470
642
  level: "warn",
@@ -477,7 +649,7 @@ let Security2CC = Security2CC_1 = class Security2CC extends CommandClass_1.Comma
477
649
  break;
478
650
  }
479
651
  else {
480
- applHost.controllerLog.logNode(node.id, {
652
+ ctx.logNode(node.id, {
481
653
  endpoint: endpoint.index,
482
654
  message: `Querying securely supported commands (${(0, safe_2.getEnumMemberName)(core_1.SecurityClass, secClass)}) failed. Let's hope for the best...`,
483
655
  level: "warn",
@@ -496,7 +668,7 @@ let Security2CC = Security2CC_1 = class Security2CC extends CommandClass_1.Comma
496
668
  // No supported commands found, mark the security class as not granted
497
669
  // unless we were sure about the security class
498
670
  node.setSecurityClass(secClass, false);
499
- applHost.controllerLog.logNode(node.id, {
671
+ ctx.logNode(node.id, {
500
672
  message: `The node was NOT granted the security class ${(0, safe_2.getEnumMemberName)(core_1.SecurityClass, secClass)}`,
501
673
  direction: "inbound",
502
674
  });
@@ -506,7 +678,7 @@ let Security2CC = Security2CC_1 = class Security2CC extends CommandClass_1.Comma
506
678
  if (endpoint.index === 0 && possibleSecurityClasses.length > 1) {
507
679
  // Mark the security class as granted unless we were sure about the security class
508
680
  node.setSecurityClass(secClass, true);
509
- applHost.controllerLog.logNode(node.id, {
681
+ ctx.logNode(node.id, {
510
682
  message: `The node was granted the security class ${(0, safe_2.getEnumMemberName)(core_1.SecurityClass, secClass)}`,
511
683
  direction: "inbound",
512
684
  });
@@ -520,7 +692,7 @@ let Security2CC = Security2CC_1 = class Security2CC extends CommandClass_1.Comma
520
692
  for (const cc of supportedCCs) {
521
693
  logLines.push(`· ${(0, core_1.getCCName)(cc)}`);
522
694
  }
523
- applHost.controllerLog.logNode(node.id, {
695
+ ctx.logNode(node.id, {
524
696
  endpoint: endpoint.index,
525
697
  message: logLines.join("\n"),
526
698
  direction: "inbound",
@@ -543,7 +715,7 @@ let Security2CC = Security2CC_1 = class Security2CC extends CommandClass_1.Comma
543
715
  }
544
716
  }
545
717
  // Remember that the interview is complete
546
- this.setInterviewComplete(applHost, true);
718
+ this.setInterviewComplete(ctx, true);
547
719
  }
548
720
  /** Tests if a command should be sent secure and thus requires encapsulation */
549
721
  static requiresEncapsulation(cc) {
@@ -591,7 +763,7 @@ let Security2CC = Security2CC_1 = class Security2CC extends CommandClass_1.Comma
591
763
  return true;
592
764
  }
593
765
  /** Encapsulates a command that should be sent encrypted */
594
- static encapsulate(host, cc, options) {
766
+ static encapsulate(cc, ownNodeId, securityManagers, options) {
595
767
  // Determine which extensions must be used on the command
596
768
  const extensions = [];
597
769
  if (options?.multicastOutOfSync) {
@@ -614,7 +786,7 @@ let Security2CC = Security2CC_1 = class Security2CC extends CommandClass_1.Comma
614
786
  else {
615
787
  nodeId = cc.nodeId;
616
788
  }
617
- const ret = new Security2CCMessageEncapsulation(host, {
789
+ const ret = new Security2CCMessageEncapsulation({
618
790
  nodeId,
619
791
  encapsulated: cc,
620
792
  securityClass: options?.securityClass,
@@ -633,6 +805,12 @@ exports.Security2CC = Security2CC = Security2CC_1 = __decorate([
633
805
  (0, CommandClassDecorators_1.commandClass)(core_1.CommandClasses["Security 2"]),
634
806
  (0, CommandClassDecorators_1.implementedVersion)(1)
635
807
  ], Security2CC);
808
+ function failNoSPAN() {
809
+ core_1.validatePayload.fail(core_1.ZWaveErrorCodes.Security2CC_NoSPAN);
810
+ }
811
+ function failNoMPAN() {
812
+ core_1.validatePayload.fail(core_1.ZWaveErrorCodes.Security2CC_NoMPAN);
813
+ }
636
814
  // An S2 encapsulated command may result in a NonceReport to be sent by the node if it couldn't decrypt the message
637
815
  function getCCResponseForMessageEncapsulation(sent) {
638
816
  if (sent.encapsulated?.expectsCCResponse()) {
@@ -661,258 +839,251 @@ function testCCResponseForMessageEncapsulation(sent, received) {
661
839
  return received.SOS && !!received.receiverEI;
662
840
  }
663
841
  }
664
- function failNoSPAN() {
665
- core_1.validatePayload.fail(core_1.ZWaveErrorCodes.Security2CC_NoSPAN);
666
- }
667
- function failNoMPAN() {
668
- core_1.validatePayload.fail(core_1.ZWaveErrorCodes.Security2CC_NoMPAN);
669
- }
670
- let Security2CCMessageEncapsulation = class Security2CCMessageEncapsulation extends Security2CC {
671
- constructor(host, options) {
672
- super(host, options);
673
- // Make sure that we can send/receive secure commands
674
- assertSecurity.call(this, options);
675
- this.securityManager = getSecurityManager(host, this.nodeId);
676
- if ((0, CommandClass_1.gotDeserializationOptions)(options)) {
677
- (0, core_1.validatePayload)(this.payload.length >= 2);
678
- // Check the sequence number to avoid duplicates
679
- this._sequenceNumber = this.payload[0];
680
- const sendingNodeId = this.nodeId;
681
- // Ensure the node has a security class
682
- const securityClass = this.host.getHighestSecurityClass(sendingNodeId);
683
- core_1.validatePayload.withReason("No security class granted")(securityClass !== core_1.SecurityClass.None);
684
- const hasExtensions = !!(this.payload[1] & 0b1);
685
- const hasEncryptedExtensions = !!(this.payload[1] & 0b10);
686
- let offset = 2;
687
- this.extensions = [];
688
- let mustDiscardCommand = false;
689
- const parseExtensions = (buffer, wasEncrypted) => {
690
- while (true) {
691
- if (buffer.length < offset + 2) {
692
- // An S2 extension was expected, but the buffer is too short
693
- mustDiscardCommand = true;
694
- return;
695
- }
696
- // The length field could be too large, which would cause part of the actual ciphertext
697
- // to be ignored. Try to avoid this for known extensions by checking the actual and expected length.
698
- const { actual: actualLength, expected: expectedLength } = Extension_1.Security2Extension
699
- .getExtensionLength(buffer.subarray(offset));
700
- // Parse the extension using the expected length if possible
701
- const extensionLength = expectedLength ?? actualLength;
702
- if (extensionLength < 2) {
703
- // An S2 extension was expected, but the length is too short
704
- mustDiscardCommand = true;
705
- return;
706
- }
707
- else if (extensionLength
708
- > buffer.length
709
- - offset
710
- - (wasEncrypted
711
- ? 0
712
- : core_1.SECURITY_S2_AUTH_TAG_LENGTH)) {
713
- // The supposed length is longer than the space the extensions may occupy
714
- mustDiscardCommand = true;
715
- return;
716
- }
717
- const extensionData = buffer.subarray(offset, offset + extensionLength);
718
- offset += extensionLength;
719
- const ext = Extension_1.Security2Extension.from(extensionData);
720
- switch ((0, Extension_1.validateS2Extension)(ext, wasEncrypted)) {
721
- case Extension_1.ValidateS2ExtensionResult.OK:
722
- if (expectedLength != undefined
723
- && actualLength !== expectedLength) {
724
- // The extension length field does not match, ignore the extension
725
- }
726
- else {
727
- this.extensions.push(ext);
728
- }
729
- break;
730
- case Extension_1.ValidateS2ExtensionResult.DiscardExtension:
731
- // Do nothing
732
- break;
733
- case Extension_1.ValidateS2ExtensionResult.DiscardCommand:
734
- mustDiscardCommand = true;
735
- break;
736
- }
737
- // Check if that was the last extension
738
- if (!ext.moreToFollow)
739
- break;
740
- }
741
- };
742
- if (hasExtensions)
743
- parseExtensions(this.payload, false);
744
- const ctx = (() => {
745
- const multicastGroupId = this.getMulticastGroupId();
746
- if (options.frameType === "multicast"
747
- || options.frameType === "broadcast") {
748
- if (multicastGroupId == undefined) {
749
- core_1.validatePayload.fail("Multicast frames without MGRP extension");
750
- }
751
- return {
752
- isMulticast: true,
753
- groupId: multicastGroupId,
754
- };
755
- }
756
- else {
757
- return { isMulticast: false, groupId: multicastGroupId };
842
+ let Security2CCMessageEncapsulation = Security2CCMessageEncapsulation_1 = class Security2CCMessageEncapsulation extends Security2CC {
843
+ constructor(options) {
844
+ super(options);
845
+ if (!options.encapsulated && !options.extensions?.length) {
846
+ throw new core_1.ZWaveError("Security S2 encapsulation requires an encapsulated CC and/or extensions", core_1.ZWaveErrorCodes.Argument_Invalid);
847
+ }
848
+ this.sequenceNumber = options.sequenceNumber;
849
+ this.securityClass = options.securityClass;
850
+ if (options.encapsulated) {
851
+ this.encapsulated = options.encapsulated;
852
+ options.encapsulated.encapsulatingCC = this;
853
+ }
854
+ this.verifyDelivery = options.verifyDelivery !== false;
855
+ this.extensions = options.extensions ?? [];
856
+ if (typeof this.nodeId !== "number"
857
+ && !this.extensions.some((e) => e instanceof Extension_1.MGRPExtension)) {
858
+ throw new core_1.ZWaveError("Multicast Security S2 encapsulation requires the MGRP extension", core_1.ZWaveErrorCodes.Security2CC_MissingExtension);
859
+ }
860
+ }
861
+ static from(raw, ctx) {
862
+ const securityManager = assertSecurityRX(ctx);
863
+ (0, core_1.validatePayload)(raw.payload.length >= 2);
864
+ // Check the sequence number to avoid duplicates
865
+ const sequenceNumber = raw.payload[0];
866
+ // Ensure the node has a security class
867
+ core_1.validatePayload.withReason("No security class granted")(ctx.getHighestSecurityClass(ctx.sourceNodeId) !== core_1.SecurityClass.None);
868
+ const hasExtensions = !!(raw.payload[1] & 0b1);
869
+ const hasEncryptedExtensions = !!(raw.payload[1] & 0b10);
870
+ let offset = 2;
871
+ const extensions = [];
872
+ let mustDiscardCommand = false;
873
+ const parseExtensions = (buffer, wasEncrypted) => {
874
+ while (true) {
875
+ if (buffer.length < offset + 2) {
876
+ // An S2 extension was expected, but the buffer is too short
877
+ mustDiscardCommand = true;
878
+ return;
758
879
  }
759
- })();
760
- // If a command is to be discarded before decryption,
761
- // we still need to increment the SPAN or MPAN state
762
- if (mustDiscardCommand) {
763
- if (ctx.isMulticast) {
764
- this.securityManager.nextPeerMPAN(sendingNodeId, ctx.groupId);
880
+ // The length field could be too large, which would cause part of the actual ciphertext
881
+ // to be ignored. Try to avoid this for known extensions by checking the actual and expected length.
882
+ const { actual: actualLength, expected: expectedLength } = Extension_1.Security2Extension
883
+ .getExtensionLength(buffer.subarray(offset));
884
+ // Parse the extension using the expected length if possible
885
+ const extensionLength = expectedLength ?? actualLength;
886
+ if (extensionLength < 2) {
887
+ // An S2 extension was expected, but the length is too short
888
+ mustDiscardCommand = true;
889
+ return;
765
890
  }
766
- else {
767
- this.securityManager.nextNonce(sendingNodeId);
891
+ else if (extensionLength
892
+ > buffer.length
893
+ - offset
894
+ - (wasEncrypted
895
+ ? 0
896
+ : core_1.SECURITY_S2_AUTH_TAG_LENGTH)) {
897
+ // The supposed length is longer than the space the extensions may occupy
898
+ mustDiscardCommand = true;
899
+ return;
768
900
  }
769
- core_1.validatePayload.fail("Invalid S2 extension");
770
- }
771
- let prevSequenceNumber;
772
- let mpanState;
773
- if (ctx.isMulticast) {
774
- mpanState = this.securityManager.getPeerMPAN(sendingNodeId, ctx.groupId);
775
- }
776
- else {
777
- // Don't accept duplicate Singlecast commands
778
- prevSequenceNumber = validateSequenceNumber.call(this, this._sequenceNumber);
779
- // When a node receives a singlecast message after a multicast group was marked out of sync,
780
- // it must forget about the group.
781
- if (ctx.groupId == undefined) {
782
- this.securityManager.resetOutOfSyncMPANs(sendingNodeId);
901
+ const extensionData = buffer.subarray(offset, offset + extensionLength);
902
+ offset += extensionLength;
903
+ const ext = Extension_1.Security2Extension.from(extensionData);
904
+ switch ((0, Extension_1.validateS2Extension)(ext, wasEncrypted)) {
905
+ case Extension_1.ValidateS2ExtensionResult.OK:
906
+ if (expectedLength != undefined
907
+ && actualLength !== expectedLength) {
908
+ // The extension length field does not match, ignore the extension
909
+ }
910
+ else {
911
+ extensions.push(ext);
912
+ }
913
+ break;
914
+ case Extension_1.ValidateS2ExtensionResult.DiscardExtension:
915
+ // Do nothing
916
+ break;
917
+ case Extension_1.ValidateS2ExtensionResult.DiscardCommand:
918
+ mustDiscardCommand = true;
919
+ break;
783
920
  }
921
+ // Check if that was the last extension
922
+ if (!ext.moreToFollow)
923
+ break;
784
924
  }
785
- const unencryptedPayload = this.payload.subarray(0, offset);
786
- const ciphertext = this.payload.subarray(offset, -core_1.SECURITY_S2_AUTH_TAG_LENGTH);
787
- const authTag = this.payload.subarray(-core_1.SECURITY_S2_AUTH_TAG_LENGTH);
788
- this.authTag = authTag;
789
- const messageLength = super.computeEncapsulationOverhead()
790
- + this.payload.length;
791
- const authData = getAuthenticationData(sendingNodeId, this.getDestinationIDRX(), this.host.homeId, messageLength, unencryptedPayload);
792
- let decrypt;
793
- if (ctx.isMulticast) {
794
- // For incoming multicast commands, make sure we have an MPAN
795
- if (mpanState?.type !== core_1.MPANState.MPAN) {
796
- // If we don't, mark the MPAN as out of sync, so we can respond accordingly on the singlecast followup
797
- this.securityManager.storePeerMPAN(sendingNodeId, ctx.groupId, { type: core_1.MPANState.OutOfSync });
798
- failNoMPAN();
925
+ };
926
+ if (hasExtensions)
927
+ parseExtensions(raw.payload, false);
928
+ const mcctx = (() => {
929
+ const multicastGroupId = getMulticastGroupId(extensions);
930
+ if (ctx.frameType === "multicast" || ctx.frameType === "broadcast") {
931
+ if (multicastGroupId == undefined) {
932
+ core_1.validatePayload.fail("Multicast frames without MGRP extension");
799
933
  }
800
- decrypt = () => this.decryptMulticast(sendingNodeId, ctx.groupId, ciphertext, authData, authTag);
934
+ return {
935
+ isMulticast: true,
936
+ groupId: multicastGroupId,
937
+ };
801
938
  }
802
939
  else {
803
- // Decrypt payload and verify integrity
804
- const spanState = this.securityManager.getSPANState(sendingNodeId);
805
- // If we are not able to establish an SPAN yet, fail the decryption
806
- if (spanState.type === core_1.SPANState.None) {
807
- failNoSPAN();
808
- }
809
- else if (spanState.type === core_1.SPANState.RemoteEI) {
810
- // TODO: The specs are not clear how to handle this case
811
- // For now, do the same as if we didn't have any EI
812
- failNoSPAN();
813
- }
814
- decrypt = () => this.decryptSinglecast(sendingNodeId, prevSequenceNumber, ciphertext, authData, authTag, spanState);
940
+ return { isMulticast: false, groupId: multicastGroupId };
815
941
  }
816
- let plaintext;
817
- let authOK = false;
818
- let key;
819
- let iv;
820
- let decryptionSecurityClass;
821
- // If the Receiver is unable to authenticate the singlecast message with the current SPAN,
822
- // the Receiver SHOULD try decrypting the message with one or more of the following SPAN values,
823
- // stopping when decryption is successful or the maximum number of iterations is reached.
824
- // If the Receiver is unable to decrypt the S2 MC frame with the current MPAN, the Receiver MAY try
825
- // decrypting the frame with one or more of the subsequent MPAN values, stopping when decryption is
826
- // successful or the maximum number of iterations is reached.
827
- const decryptAttempts = ctx.isMulticast
828
- ? MAX_DECRYPT_ATTEMPTS_MULTICAST
829
- : ctx.groupId != undefined
830
- ? MAX_DECRYPT_ATTEMPTS_SC_FOLLOWUP
831
- : MAX_DECRYPT_ATTEMPTS_SINGLECAST;
832
- for (let i = 0; i < decryptAttempts; i++) {
833
- ({
834
- plaintext,
835
- authOK,
836
- key,
837
- iv,
838
- securityClass: decryptionSecurityClass,
839
- } = decrypt());
840
- if (!!authOK && !!plaintext)
841
- break;
842
- // No need to try further SPANs if we just got the sender's EI
843
- if (!!this.getSenderEI())
844
- break;
942
+ })();
943
+ // If a command is to be discarded before decryption,
944
+ // we still need to increment the SPAN or MPAN state
945
+ if (mustDiscardCommand) {
946
+ if (mcctx.isMulticast) {
947
+ securityManager.nextPeerMPAN(ctx.sourceNodeId, mcctx.groupId);
845
948
  }
846
- // If authentication fails, do so with an error code that instructs the
847
- // applHost to tell the node we have no nonce
848
- if (!authOK || !plaintext) {
849
- if (ctx.isMulticast) {
850
- // Mark the MPAN as out of sync
851
- this.securityManager.storePeerMPAN(sendingNodeId, ctx.groupId, { type: core_1.MPANState.OutOfSync });
852
- core_1.validatePayload.fail(core_1.ZWaveErrorCodes.Security2CC_CannotDecodeMulticast);
853
- }
854
- else {
855
- core_1.validatePayload.fail(core_1.ZWaveErrorCodes.Security2CC_CannotDecode);
856
- }
949
+ else {
950
+ securityManager.nextNonce(ctx.sourceNodeId);
857
951
  }
858
- else if (!ctx.isMulticast && ctx.groupId != undefined) {
859
- // After reception of a singlecast followup, the MPAN state must be increased
860
- this.securityManager.tryIncrementPeerMPAN(sendingNodeId, ctx.groupId);
952
+ core_1.validatePayload.fail("Invalid S2 extension");
953
+ }
954
+ let prevSequenceNumber;
955
+ let mpanState;
956
+ if (mcctx.isMulticast) {
957
+ mpanState = securityManager.getPeerMPAN(ctx.sourceNodeId, mcctx.groupId);
958
+ }
959
+ else {
960
+ // Don't accept duplicate Singlecast commands
961
+ prevSequenceNumber = validateSequenceNumber(securityManager, ctx.sourceNodeId, sequenceNumber);
962
+ // When a node receives a singlecast message after a multicast group was marked out of sync,
963
+ // it must forget about the group.
964
+ if (mcctx.groupId == undefined) {
965
+ securityManager.resetOutOfSyncMPANs(ctx.sourceNodeId);
861
966
  }
862
- // Remember which security class was used to decrypt this message, so we can discard it later
863
- this.securityClass = decryptionSecurityClass;
864
- offset = 0;
865
- if (hasEncryptedExtensions)
866
- parseExtensions(plaintext, true);
867
- // Before we can continue, check if the command must be discarded
868
- if (mustDiscardCommand) {
869
- core_1.validatePayload.fail("Invalid extension");
967
+ }
968
+ const unencryptedPayload = raw.payload.subarray(0, offset);
969
+ const ciphertext = raw.payload.subarray(offset, -core_1.SECURITY_S2_AUTH_TAG_LENGTH);
970
+ const authTag = raw.payload.subarray(-core_1.SECURITY_S2_AUTH_TAG_LENGTH);
971
+ const messageLength = 2 /* CommandClass.computeEncapsulationOverhead() */
972
+ + raw.payload.length;
973
+ const authData = getAuthenticationData(ctx.sourceNodeId, getDestinationIDRX(ctx, extensions), ctx.homeId, messageLength, unencryptedPayload);
974
+ let decrypt;
975
+ if (mcctx.isMulticast) {
976
+ // For incoming multicast commands, make sure we have an MPAN
977
+ if (mpanState?.type !== core_1.MPANState.MPAN) {
978
+ // If we don't, mark the MPAN as out of sync, so we can respond accordingly on the singlecast followup
979
+ securityManager.storePeerMPAN(ctx.sourceNodeId, mcctx.groupId, { type: core_1.MPANState.OutOfSync });
980
+ failNoMPAN();
870
981
  }
871
- // If the MPAN extension was received, store the MPAN
872
- if (!ctx.isMulticast) {
873
- const mpanExtension = this.getMPANExtension();
874
- if (mpanExtension) {
875
- this.securityManager.storePeerMPAN(sendingNodeId, mpanExtension.groupId, {
876
- type: core_1.MPANState.MPAN,
877
- currentMPAN: mpanExtension.innerMPANState,
878
- });
879
- }
982
+ decrypt = () => decryptMulticast(ctx.sourceNodeId, securityManager, mcctx.groupId, ciphertext, authData, authTag);
983
+ }
984
+ else {
985
+ // Decrypt payload and verify integrity
986
+ const spanState = securityManager.getSPANState(ctx.sourceNodeId);
987
+ // If we are not able to establish an SPAN yet, fail the decryption
988
+ if (spanState.type === core_1.SPANState.None) {
989
+ failNoSPAN();
880
990
  }
881
- // Not every S2 message includes an encapsulated CC
882
- const decryptedCCBytes = plaintext.subarray(offset);
883
- if (decryptedCCBytes.length > 0) {
884
- // make sure this contains a complete CC command that's worth splitting
885
- (0, core_1.validatePayload)(decryptedCCBytes.length >= 2);
886
- // and deserialize the CC
887
- this.encapsulated = CommandClass_1.CommandClass.from(this.host, {
888
- data: decryptedCCBytes,
889
- fromEncapsulation: true,
890
- encapCC: this,
891
- frameType: options.frameType,
892
- });
991
+ else if (spanState.type === core_1.SPANState.RemoteEI) {
992
+ // TODO: The specs are not clear how to handle this case
993
+ // For now, do the same as if we didn't have any EI
994
+ failNoSPAN();
893
995
  }
894
- this.plaintext = decryptedCCBytes;
895
- this.key = key;
896
- this.iv = iv;
996
+ decrypt = () => decryptSinglecast(ctx, securityManager, ctx.sourceNodeId, sequenceNumber, prevSequenceNumber, ciphertext, authData, authTag, spanState, extensions);
897
997
  }
898
- else {
899
- if (!options.encapsulated && !options.extensions?.length) {
900
- throw new core_1.ZWaveError("Security S2 encapsulation requires an encapsulated CC and/or extensions", core_1.ZWaveErrorCodes.Argument_Invalid);
998
+ let plaintext;
999
+ let authOK = false;
1000
+ let key;
1001
+ let iv;
1002
+ let decryptionSecurityClass;
1003
+ // If the Receiver is unable to authenticate the singlecast message with the current SPAN,
1004
+ // the Receiver SHOULD try decrypting the message with one or more of the following SPAN values,
1005
+ // stopping when decryption is successful or the maximum number of iterations is reached.
1006
+ // If the Receiver is unable to decrypt the S2 MC frame with the current MPAN, the Receiver MAY try
1007
+ // decrypting the frame with one or more of the subsequent MPAN values, stopping when decryption is
1008
+ // successful or the maximum number of iterations is reached.
1009
+ const decryptAttempts = mcctx.isMulticast
1010
+ ? MAX_DECRYPT_ATTEMPTS_MULTICAST
1011
+ : mcctx.groupId != undefined
1012
+ ? MAX_DECRYPT_ATTEMPTS_SC_FOLLOWUP
1013
+ : MAX_DECRYPT_ATTEMPTS_SINGLECAST;
1014
+ for (let i = 0; i < decryptAttempts; i++) {
1015
+ ({
1016
+ plaintext,
1017
+ authOK,
1018
+ key,
1019
+ iv,
1020
+ securityClass: decryptionSecurityClass,
1021
+ } = decrypt());
1022
+ if (!!authOK && !!plaintext)
1023
+ break;
1024
+ // No need to try further SPANs if we just got the sender's EI
1025
+ if (!!getSenderEI(extensions))
1026
+ break;
1027
+ }
1028
+ // If authentication fails, do so with an error code that instructs the
1029
+ // applHost to tell the node we have no nonce
1030
+ if (!authOK || !plaintext) {
1031
+ if (mcctx.isMulticast) {
1032
+ // Mark the MPAN as out of sync
1033
+ securityManager.storePeerMPAN(ctx.sourceNodeId, mcctx.groupId, { type: core_1.MPANState.OutOfSync });
1034
+ core_1.validatePayload.fail(core_1.ZWaveErrorCodes.Security2CC_CannotDecodeMulticast);
901
1035
  }
902
- this.securityClass = options.securityClass;
903
- if (options.encapsulated) {
904
- this.encapsulated = options.encapsulated;
905
- options.encapsulated.encapsulatingCC = this;
1036
+ else {
1037
+ core_1.validatePayload.fail(core_1.ZWaveErrorCodes.Security2CC_CannotDecode);
906
1038
  }
907
- this.verifyDelivery = options.verifyDelivery !== false;
908
- this.extensions = options.extensions ?? [];
909
- if (typeof this.nodeId !== "number"
910
- && !this.extensions.some((e) => e instanceof Extension_1.MGRPExtension)) {
911
- throw new core_1.ZWaveError("Multicast Security S2 encapsulation requires the MGRP extension", core_1.ZWaveErrorCodes.Security2CC_MissingExtension);
1039
+ }
1040
+ else if (!mcctx.isMulticast && mcctx.groupId != undefined) {
1041
+ // After reception of a singlecast followup, the MPAN state must be increased
1042
+ securityManager.tryIncrementPeerMPAN(ctx.sourceNodeId, mcctx.groupId);
1043
+ }
1044
+ // Remember which security class was used to decrypt this message, so we can discard it later
1045
+ const securityClass = decryptionSecurityClass;
1046
+ offset = 0;
1047
+ if (hasEncryptedExtensions)
1048
+ parseExtensions(plaintext, true);
1049
+ // Before we can continue, check if the command must be discarded
1050
+ if (mustDiscardCommand) {
1051
+ core_1.validatePayload.fail("Invalid extension");
1052
+ }
1053
+ // If the MPAN extension was received, store the MPAN
1054
+ if (!mcctx.isMulticast) {
1055
+ const mpanExtension = extensions.find((e) => e instanceof Extension_1.MPANExtension);
1056
+ if (mpanExtension) {
1057
+ securityManager.storePeerMPAN(ctx.sourceNodeId, mpanExtension.groupId, {
1058
+ type: core_1.MPANState.MPAN,
1059
+ currentMPAN: mpanExtension.innerMPANState,
1060
+ });
912
1061
  }
913
1062
  }
1063
+ // Not every S2 message includes an encapsulated CC
1064
+ const decryptedCCBytes = plaintext.subarray(offset);
1065
+ let encapsulated;
1066
+ if (decryptedCCBytes.length > 0) {
1067
+ // make sure this contains a complete CC command that's worth splitting
1068
+ (0, core_1.validatePayload)(decryptedCCBytes.length >= 2);
1069
+ // and deserialize the CC
1070
+ encapsulated = CommandClass_1.CommandClass.parse(decryptedCCBytes, ctx);
1071
+ }
1072
+ const ret = new Security2CCMessageEncapsulation_1({
1073
+ nodeId: ctx.sourceNodeId,
1074
+ sequenceNumber,
1075
+ securityClass,
1076
+ extensions,
1077
+ encapsulated,
1078
+ });
1079
+ // Remember for debugging purposes
1080
+ ret.key = key;
1081
+ ret.iv = iv;
1082
+ ret.authData = authData;
1083
+ ret.authTag = authTag;
1084
+ ret.plaintext = decryptedCCBytes;
1085
+ return ret;
914
1086
  }
915
- securityManager;
916
1087
  securityClass;
917
1088
  // Only used for testing/debugging purposes
918
1089
  key;
@@ -922,73 +1093,41 @@ let Security2CCMessageEncapsulation = class Security2CCMessageEncapsulation exte
922
1093
  ciphertext;
923
1094
  plaintext;
924
1095
  verifyDelivery = true;
925
- _sequenceNumber;
926
- /**
927
- * Return the sequence number of this command.
928
- *
929
- * **WARNING:** If the sequence number hasn't been set before, this will create a new one.
930
- * When sending messages, this should only happen immediately before serializing.
931
- */
932
- get sequenceNumber() {
933
- if (this._sequenceNumber == undefined) {
1096
+ sequenceNumber;
1097
+ ensureSequenceNumber(securityManager) {
1098
+ if (this.sequenceNumber == undefined) {
934
1099
  if (this.isSinglecast()) {
935
- this._sequenceNumber = this.securityManager
936
- .nextSequenceNumber(this.nodeId);
1100
+ this.sequenceNumber = securityManager.nextSequenceNumber(this.nodeId);
937
1101
  }
938
1102
  else {
939
- const groupId = this.getDestinationIDTX();
940
- return this.securityManager.nextMulticastSequenceNumber(groupId);
1103
+ const groupId = getDestinationIDTX.call(this);
1104
+ this.sequenceNumber = securityManager
1105
+ .nextMulticastSequenceNumber(groupId);
941
1106
  }
942
1107
  }
943
- return this._sequenceNumber;
944
1108
  }
945
1109
  encapsulated;
946
1110
  extensions;
947
1111
  prepareRetransmission() {
948
1112
  super.prepareRetransmission();
949
- this._sequenceNumber = undefined;
950
- }
951
- getDestinationIDTX() {
952
- if (this.isSinglecast())
953
- return this.nodeId;
954
- const ret = this.getMulticastGroupId();
955
- if (ret == undefined) {
956
- throw new core_1.ZWaveError("Multicast Security S2 encapsulation requires the MGRP extension", core_1.ZWaveErrorCodes.Security2CC_MissingExtension);
957
- }
958
- return ret;
959
- }
960
- getDestinationIDRX() {
961
- if (this.isSinglecast())
962
- return this.host.ownNodeId;
963
- const ret = this.getMulticastGroupId();
964
- if (ret == undefined) {
965
- throw new core_1.ZWaveError("Multicast Security S2 encapsulation requires the MGRP extension", core_1.ZWaveErrorCodes.Security2CC_MissingExtension);
966
- }
967
- return ret;
968
- }
969
- getMGRPExtension() {
970
- return this.extensions.find((e) => e instanceof Extension_1.MGRPExtension);
971
- }
972
- getMulticastGroupId() {
973
- const mgrpExtension = this.getMGRPExtension();
974
- return mgrpExtension?.groupId;
975
- }
976
- getMPANExtension() {
977
- return this.extensions.find((e) => e instanceof Extension_1.MPANExtension);
1113
+ this.sequenceNumber = undefined;
978
1114
  }
979
1115
  hasMOSExtension() {
980
1116
  return this.extensions.some((e) => e instanceof Extension_1.MOSExtension);
981
1117
  }
982
1118
  /** Returns the Sender's Entropy Input if this command contains an SPAN extension */
983
1119
  getSenderEI() {
984
- const spanExtension = this.extensions.find((e) => e instanceof Extension_1.SPANExtension);
985
- return spanExtension?.senderEI;
1120
+ return getSenderEI(this.extensions);
1121
+ }
1122
+ /** Returns the multicast group ID if this command contains an MGRP extension */
1123
+ getMulticastGroupId() {
1124
+ return getMulticastGroupId(this.extensions);
986
1125
  }
987
- maybeAddSPANExtension() {
1126
+ maybeAddSPANExtension(ctx, securityManager) {
988
1127
  if (!this.isSinglecast())
989
1128
  return;
990
1129
  const receiverNodeId = this.nodeId;
991
- const spanState = this.securityManager.getSPANState(receiverNodeId);
1130
+ const spanState = securityManager.getSPANState(receiverNodeId);
992
1131
  if (spanState.type === core_1.SPANState.None
993
1132
  || spanState.type === core_1.SPANState.LocalEI) {
994
1133
  // Can't do anything here if we don't have the receiver's EI
@@ -997,21 +1136,21 @@ let Security2CCMessageEncapsulation = class Security2CCMessageEncapsulation exte
997
1136
  else if (spanState.type === core_1.SPANState.RemoteEI) {
998
1137
  // We have the receiver's EI, generate our input and send it over
999
1138
  // With both, we can create an SPAN
1000
- const senderEI = this.securityManager.generateNonce(undefined);
1139
+ const senderEI = securityManager.generateNonce(undefined);
1001
1140
  const receiverEI = spanState.receiverEI;
1002
1141
  // While bootstrapping a node, prefer the temporary key, unless the
1003
1142
  // specific command specifies a security class
1004
1143
  if (this.securityClass == undefined
1005
- && this.securityManager.tempKeys.has(receiverNodeId)) {
1006
- this.securityManager.initializeTempSPAN(receiverNodeId, senderEI, receiverEI);
1144
+ && securityManager.tempKeys.has(receiverNodeId)) {
1145
+ securityManager.initializeTempSPAN(receiverNodeId, senderEI, receiverEI);
1007
1146
  }
1008
1147
  else {
1009
1148
  const securityClass = this.securityClass
1010
- ?? this.host.getHighestSecurityClass(receiverNodeId);
1149
+ ?? ctx.getHighestSecurityClass(receiverNodeId);
1011
1150
  if (securityClass == undefined) {
1012
1151
  throw new core_1.ZWaveError("No security class defined for this command!", core_1.ZWaveErrorCodes.Security2CC_NoSPAN);
1013
1152
  }
1014
- this.securityManager.initializeSPAN(receiverNodeId, securityClass, senderEI, receiverEI);
1153
+ securityManager.initializeSPAN(receiverNodeId, securityClass, senderEI, receiverEI);
1015
1154
  }
1016
1155
  // Add or update the SPAN extension
1017
1156
  let spanExtension = this.extensions.find((e) => e instanceof Extension_1.SPANExtension);
@@ -1024,9 +1163,11 @@ let Security2CCMessageEncapsulation = class Security2CCMessageEncapsulation exte
1024
1163
  }
1025
1164
  }
1026
1165
  }
1027
- serialize() {
1166
+ serialize(ctx) {
1167
+ const securityManager = assertSecurityTX(ctx, this.nodeId);
1168
+ this.ensureSequenceNumber(securityManager);
1028
1169
  // Include Sender EI in the command if we only have the receiver's EI
1029
- this.maybeAddSPANExtension();
1170
+ this.maybeAddSPANExtension(ctx, securityManager);
1030
1171
  const unencryptedExtensions = this.extensions.filter((e) => !e.isEncrypted());
1031
1172
  const encryptedExtensions = this.extensions.filter((e) => e.isEncrypted());
1032
1173
  const unencryptedPayload = Buffer.concat([
@@ -1037,33 +1178,34 @@ let Security2CCMessageEncapsulation = class Security2CCMessageEncapsulation exte
1037
1178
  ]),
1038
1179
  ...unencryptedExtensions.map((e, index) => e.serialize(index < unencryptedExtensions.length - 1)),
1039
1180
  ]);
1040
- const serializedCC = this.encapsulated?.serialize() ?? Buffer.from([]);
1181
+ const serializedCC = this.encapsulated?.serialize(ctx)
1182
+ ?? Buffer.from([]);
1041
1183
  const plaintextPayload = Buffer.concat([
1042
1184
  ...encryptedExtensions.map((e, index) => e.serialize(index < encryptedExtensions.length - 1)),
1043
1185
  serializedCC,
1044
1186
  ]);
1045
1187
  // Generate the authentication data for CCM encryption
1046
- const destinationTag = this.getDestinationIDTX();
1188
+ const destinationTag = getDestinationIDTX.call(this);
1047
1189
  const messageLength = this.computeEncapsulationOverhead()
1048
1190
  + serializedCC.length;
1049
- const authData = getAuthenticationData(this.host.ownNodeId, destinationTag, this.host.homeId, messageLength, unencryptedPayload);
1191
+ const authData = getAuthenticationData(ctx.ownNodeId, destinationTag, ctx.homeId, messageLength, unencryptedPayload);
1050
1192
  let key;
1051
1193
  let iv;
1052
1194
  if (this.isSinglecast()) {
1053
1195
  // Singlecast:
1054
1196
  // Generate a nonce for encryption, and remember it to attempt decryption
1055
1197
  // of potential in-flight messages from the target node.
1056
- iv = this.securityManager.nextNonce(this.nodeId, true);
1198
+ iv = securityManager.nextNonce(this.nodeId, true);
1057
1199
  const { keyCCM } =
1058
1200
  // Prefer the overridden security class if it was given
1059
1201
  this.securityClass != undefined
1060
- ? this.securityManager.getKeysForSecurityClass(this.securityClass)
1061
- : this.securityManager.getKeysForNode(this.nodeId);
1202
+ ? securityManager.getKeysForSecurityClass(this.securityClass)
1203
+ : securityManager.getKeysForNode(this.nodeId);
1062
1204
  key = keyCCM;
1063
1205
  }
1064
1206
  else {
1065
1207
  // Multicast:
1066
- const keyAndIV = this.securityManager.getMulticastKeyAndIV(destinationTag);
1208
+ const keyAndIV = securityManager.getMulticastKeyAndIV(destinationTag);
1067
1209
  key = keyAndIV.key;
1068
1210
  iv = keyAndIV.iv;
1069
1211
  }
@@ -1079,7 +1221,7 @@ let Security2CCMessageEncapsulation = class Security2CCMessageEncapsulation exte
1079
1221
  ciphertextPayload,
1080
1222
  authTag,
1081
1223
  ]);
1082
- return super.serialize();
1224
+ return super.serialize(ctx);
1083
1225
  }
1084
1226
  computeEncapsulationOverhead() {
1085
1227
  // Security S2 adds:
@@ -1095,9 +1237,9 @@ let Security2CCMessageEncapsulation = class Security2CCMessageEncapsulation exte
1095
1237
  + core_1.SECURITY_S2_AUTH_TAG_LENGTH
1096
1238
  + extensionBytes);
1097
1239
  }
1098
- toLogEntry(host) {
1240
+ toLogEntry(ctx) {
1099
1241
  const message = {
1100
- "sequence number": this.sequenceNumber,
1242
+ "sequence number": this.sequenceNumber ?? "(not set)",
1101
1243
  };
1102
1244
  if (this.extensions.length > 0) {
1103
1245
  message.extensions = this.extensions
@@ -1126,210 +1268,77 @@ let Security2CCMessageEncapsulation = class Security2CCMessageEncapsulation exte
1126
1268
  message["auth tag"] = (0, safe_2.buffer2hex)(this.authTag);
1127
1269
  }
1128
1270
  }
1129
- if (this.isSinglecast()) {
1130
- // TODO: This is ugly, we should probably do this in the constructor or so
1131
- let securityClass = this.securityClass;
1132
- if (securityClass == undefined) {
1133
- const spanState = this.securityManager.getSPANState(this.nodeId);
1134
- if (spanState.type === core_1.SPANState.SPAN) {
1135
- securityClass = spanState.securityClass;
1136
- }
1137
- }
1138
- if (securityClass != undefined) {
1139
- message["security class"] = (0, safe_2.getEnumMemberName)(core_1.SecurityClass, securityClass);
1140
- }
1271
+ if (this.securityClass != undefined) {
1272
+ message["security class"] = (0, safe_2.getEnumMemberName)(core_1.SecurityClass, this.securityClass);
1141
1273
  }
1142
1274
  return {
1143
- ...super.toLogEntry(host),
1275
+ ...super.toLogEntry(ctx),
1144
1276
  message,
1145
1277
  };
1146
1278
  }
1147
- decryptSinglecast(sendingNodeId, prevSequenceNumber, ciphertext, authData, authTag, spanState) {
1148
- const decryptWithNonce = (nonce) => {
1149
- const { keyCCM: key } = this.securityManager.getKeysForNode(sendingNodeId);
1150
- const iv = nonce;
1151
- return {
1152
- key,
1153
- iv,
1154
- ...(0, core_1.decryptAES128CCM)(key, iv, ciphertext, authData, authTag),
1155
- };
1156
- };
1157
- const getNonceAndDecrypt = () => {
1158
- const iv = this.securityManager.nextNonce(sendingNodeId);
1159
- return decryptWithNonce(iv);
1160
- };
1161
- if (spanState.type === core_1.SPANState.SPAN) {
1162
- // There SHOULD be a shared SPAN between both parties. But experience has shown that both could have
1163
- // sent a command at roughly the same time, using the same SPAN for encryption.
1164
- // To avoid a nasty desync and both nodes trying to resync at the same time, causing message loss,
1165
- // we accept commands encrypted with the previous SPAN under very specific circumstances:
1166
- if (
1167
- // The previous SPAN is still known, i.e. the node didn't send another command that was successfully decrypted
1168
- !!spanState.currentSPAN
1169
- // it is still valid
1170
- && spanState.currentSPAN.expires > (0, core_1.highResTimestamp)()
1171
- // The received command is exactly the next, expected one
1172
- && prevSequenceNumber != undefined
1173
- && this["_sequenceNumber"] === ((prevSequenceNumber + 1) & 0xff)
1174
- // And in case of a mock-based test, do this only on the controller
1175
- && !this.host.__internalIsMockNode) {
1176
- const nonce = spanState.currentSPAN.nonce;
1177
- spanState.currentSPAN = undefined;
1178
- // If we could decrypt this way, we're done...
1179
- const result = decryptWithNonce(nonce);
1180
- if (result.authOK) {
1181
- return {
1182
- ...result,
1183
- securityClass: spanState.securityClass,
1184
- };
1185
- }
1186
- // ...otherwise, we need to try the normal way
1187
- }
1188
- else {
1189
- // forgetting the current SPAN shouldn't be necessary but better be safe than sorry
1190
- spanState.currentSPAN = undefined;
1191
- }
1192
- // This can only happen if the security class is known
1193
- return {
1194
- ...getNonceAndDecrypt(),
1195
- securityClass: spanState.securityClass,
1196
- };
1197
- }
1198
- else if (spanState.type === core_1.SPANState.LocalEI) {
1199
- // We've sent the other our receiver's EI and received its sender's EI,
1200
- // meaning we can now establish an SPAN
1201
- const senderEI = this.getSenderEI();
1202
- if (!senderEI)
1203
- failNoSPAN();
1204
- const receiverEI = spanState.receiverEI;
1205
- // How we do this depends on whether we know the security class of the other node
1206
- const isBootstrappingNode = this.securityManager.tempKeys.has(sendingNodeId);
1207
- if (isBootstrappingNode) {
1208
- // We're currently bootstrapping the node, it might be using a temporary key
1209
- this.securityManager.initializeTempSPAN(sendingNodeId, senderEI, receiverEI);
1210
- const ret = getNonceAndDecrypt();
1211
- // Decryption with the temporary key worked
1212
- if (ret.authOK) {
1213
- return {
1214
- ...ret,
1215
- securityClass: core_1.SecurityClass.Temporary,
1216
- };
1217
- }
1218
- // Reset the SPAN state and try with the recently granted security class
1219
- this.securityManager.setSPANState(sendingNodeId, spanState);
1220
- }
1221
- // When ending up here, one of two situations has occured:
1222
- // a) We've taken over an existing network and do not know the node's security class
1223
- // b) We know the security class, but we're about to establish a new SPAN. This may happen at a lower
1224
- // security class than the one the node normally uses, e.g. when we're being queried for securely
1225
- // supported CCs.
1226
- // In both cases, we should simply try decoding with multiple security classes, starting from the highest one.
1227
- // If this fails, we restore the previous (partial) SPAN state.
1228
- // Try all security classes where we do not definitely know that it was not granted
1229
- // While bootstrapping a node, we consider the key that is being exchanged (including S0) to be the highest. No need to look at others
1230
- const possibleSecurityClasses = isBootstrappingNode
1231
- ? [this.host.getHighestSecurityClass(sendingNodeId)]
1232
- : core_1.securityClassOrder.filter((s) => this.host.hasSecurityClass(sendingNodeId, s)
1233
- !== false);
1234
- for (const secClass of possibleSecurityClasses) {
1235
- // Skip security classes we don't have keys for
1236
- if (!this.securityManager.hasKeysForSecurityClass(secClass)) {
1237
- continue;
1238
- }
1239
- // Initialize an SPAN with that security class
1240
- this.securityManager.initializeSPAN(sendingNodeId, secClass, senderEI, receiverEI);
1241
- const ret = getNonceAndDecrypt();
1242
- // It worked, return the result
1243
- if (ret.authOK) {
1244
- // Also if we weren't sure before, we now know that the security class is granted
1245
- if (this.host.hasSecurityClass(sendingNodeId, secClass)
1246
- === undefined) {
1247
- this.host.setSecurityClass(sendingNodeId, secClass, true);
1248
- }
1249
- return {
1250
- ...ret,
1251
- securityClass: secClass,
1252
- };
1253
- }
1254
- else {
1255
- // Reset the SPAN state and try with the next security class
1256
- this.securityManager.setSPANState(sendingNodeId, spanState);
1257
- }
1258
- }
1259
- }
1260
- // Nothing worked, fail the decryption
1261
- return {
1262
- plaintext: Buffer.from([]),
1263
- authOK: false,
1264
- securityClass: undefined,
1265
- };
1266
- }
1267
- decryptMulticast(sendingNodeId, groupId, ciphertext, authData, authTag) {
1268
- const iv = this.securityManager.nextPeerMPAN(sendingNodeId, groupId);
1269
- const { keyCCM: key } = this.securityManager.getKeysForNode(sendingNodeId);
1270
- return {
1271
- key,
1272
- iv,
1273
- ...(0, core_1.decryptAES128CCM)(key, iv, ciphertext, authData, authTag),
1274
- // The security class is irrelevant when decrypting multicast commands
1275
- securityClass: undefined,
1276
- };
1277
- }
1278
1279
  };
1279
1280
  exports.Security2CCMessageEncapsulation = Security2CCMessageEncapsulation;
1280
- exports.Security2CCMessageEncapsulation = Security2CCMessageEncapsulation = __decorate([
1281
+ exports.Security2CCMessageEncapsulation = Security2CCMessageEncapsulation = Security2CCMessageEncapsulation_1 = __decorate([
1281
1282
  (0, CommandClassDecorators_1.CCCommand)(_Types_1.Security2Command.MessageEncapsulation),
1282
1283
  (0, CommandClassDecorators_1.expectedCCResponse)(getCCResponseForMessageEncapsulation, testCCResponseForMessageEncapsulation)
1283
1284
  ], Security2CCMessageEncapsulation);
1284
- let Security2CCNonceReport = class Security2CCNonceReport extends Security2CC {
1285
- constructor(host, options) {
1286
- super(host, options);
1285
+ let Security2CCNonceReport = Security2CCNonceReport_1 = class Security2CCNonceReport extends Security2CC {
1286
+ constructor(options) {
1287
+ super(options);
1288
+ this.SOS = options.SOS;
1289
+ this.MOS = options.MOS;
1290
+ this.sequenceNumber = options.sequenceNumber;
1291
+ if (options.SOS)
1292
+ this.receiverEI = options.receiverEI;
1293
+ }
1294
+ static from(raw, ctx) {
1287
1295
  // Make sure that we can send/receive secure commands
1288
- assertSecurity.call(this, options);
1289
- this.securityManager = getSecurityManager(host, this.nodeId);
1290
- if ((0, CommandClass_1.gotDeserializationOptions)(options)) {
1291
- (0, core_1.validatePayload)(this.payload.length >= 2);
1292
- this._sequenceNumber = this.payload[0];
1293
- // Don't accept duplicate commands
1294
- validateSequenceNumber.call(this, this._sequenceNumber);
1295
- this.MOS = !!(this.payload[1] & 0b10);
1296
- this.SOS = !!(this.payload[1] & 0b1);
1297
- (0, core_1.validatePayload)(this.MOS || this.SOS);
1298
- if (this.SOS) {
1299
- // If the SOS flag is set, the REI field MUST be included in the command
1300
- (0, core_1.validatePayload)(this.payload.length >= 18);
1301
- this.receiverEI = this.payload.subarray(2, 18);
1302
- // In that case we also need to store it, so the next sent command
1303
- // can use it for encryption
1304
- this.securityManager.storeRemoteEI(this.nodeId, this.receiverEI);
1305
- }
1296
+ const securityManager = assertSecurityRX(ctx);
1297
+ (0, core_1.validatePayload)(raw.payload.length >= 2);
1298
+ const sequenceNumber = raw.payload[0];
1299
+ // Don't accept duplicate commands
1300
+ validateSequenceNumber(securityManager, ctx.sourceNodeId, sequenceNumber);
1301
+ const MOS = !!(raw.payload[1] & 0b10);
1302
+ const SOS = !!(raw.payload[1] & 0b1);
1303
+ if (SOS) {
1304
+ // If the SOS flag is set, the REI field MUST be included in the command
1305
+ (0, core_1.validatePayload)(raw.payload.length >= 18);
1306
+ const receiverEI = raw.payload.subarray(2, 18);
1307
+ // In that case we also need to store it, so the next sent command
1308
+ // can use it for encryption
1309
+ securityManager.storeRemoteEI(ctx.sourceNodeId, receiverEI);
1310
+ return new Security2CCNonceReport_1({
1311
+ nodeId: ctx.sourceNodeId,
1312
+ sequenceNumber,
1313
+ MOS,
1314
+ SOS,
1315
+ receiverEI,
1316
+ });
1317
+ }
1318
+ else if (MOS) {
1319
+ return new Security2CCNonceReport_1({
1320
+ nodeId: ctx.sourceNodeId,
1321
+ sequenceNumber,
1322
+ MOS,
1323
+ SOS: false,
1324
+ });
1306
1325
  }
1307
1326
  else {
1308
- this.SOS = options.SOS;
1309
- this.MOS = options.MOS;
1310
- if (options.SOS)
1311
- this.receiverEI = options.receiverEI;
1327
+ core_1.validatePayload.fail("Either MOS or SOS must be set");
1312
1328
  }
1313
1329
  }
1314
- securityManager;
1315
- _sequenceNumber;
1316
- /**
1317
- * Return the sequence number of this command.
1318
- *
1319
- * **WARNING:** If the sequence number hasn't been set before, this will create a new one.
1320
- * When sending messages, this should only happen immediately before serializing.
1321
- */
1322
- get sequenceNumber() {
1323
- if (this._sequenceNumber == undefined) {
1324
- this._sequenceNumber = this.securityManager
1325
- .nextSequenceNumber(this.nodeId);
1330
+ sequenceNumber;
1331
+ ensureSequenceNumber(securityManager) {
1332
+ if (this.sequenceNumber == undefined) {
1333
+ this.sequenceNumber = securityManager.nextSequenceNumber(this.nodeId);
1326
1334
  }
1327
- return this._sequenceNumber;
1328
1335
  }
1329
1336
  SOS;
1330
1337
  MOS;
1331
1338
  receiverEI;
1332
- serialize() {
1339
+ serialize(ctx) {
1340
+ const securityManager = assertSecurityTX(ctx, this.nodeId);
1341
+ this.ensureSequenceNumber(securityManager);
1333
1342
  this.payload = Buffer.from([
1334
1343
  this.sequenceNumber,
1335
1344
  (this.MOS ? 0b10 : 0) + (this.SOS ? 0b1 : 0),
@@ -1337,11 +1346,11 @@ let Security2CCNonceReport = class Security2CCNonceReport extends Security2CC {
1337
1346
  if (this.SOS) {
1338
1347
  this.payload = Buffer.concat([this.payload, this.receiverEI]);
1339
1348
  }
1340
- return super.serialize();
1349
+ return super.serialize(ctx);
1341
1350
  }
1342
- toLogEntry(host) {
1351
+ toLogEntry(ctx) {
1343
1352
  const message = {
1344
- "sequence number": this.sequenceNumber,
1353
+ "sequence number": this.sequenceNumber ?? "(not set)",
1345
1354
  SOS: this.SOS,
1346
1355
  MOS: this.MOS,
1347
1356
  };
@@ -1349,86 +1358,88 @@ let Security2CCNonceReport = class Security2CCNonceReport extends Security2CC {
1349
1358
  message["receiver entropy"] = (0, safe_2.buffer2hex)(this.receiverEI);
1350
1359
  }
1351
1360
  return {
1352
- ...super.toLogEntry(host),
1361
+ ...super.toLogEntry(ctx),
1353
1362
  message,
1354
1363
  };
1355
1364
  }
1356
1365
  };
1357
1366
  exports.Security2CCNonceReport = Security2CCNonceReport;
1358
- exports.Security2CCNonceReport = Security2CCNonceReport = __decorate([
1367
+ exports.Security2CCNonceReport = Security2CCNonceReport = Security2CCNonceReport_1 = __decorate([
1359
1368
  (0, CommandClassDecorators_1.CCCommand)(_Types_1.Security2Command.NonceReport)
1360
1369
  ], Security2CCNonceReport);
1361
- let Security2CCNonceGet = class Security2CCNonceGet extends Security2CC {
1370
+ let Security2CCNonceGet = Security2CCNonceGet_1 = class Security2CCNonceGet extends Security2CC {
1362
1371
  // TODO: A node sending this command MUST accept a delay up to <Previous Round-trip-time to peer node> +
1363
1372
  // 250 ms before receiving the Security 2 Nonce Report Command.
1364
- constructor(host, options) {
1365
- super(host, options);
1366
- // Make sure that we can send/receive secure commands
1367
- assertSecurity.call(this, options);
1368
- this.securityManager = getSecurityManager(host, this.nodeId);
1369
- if ((0, CommandClass_1.gotDeserializationOptions)(options)) {
1370
- (0, core_1.validatePayload)(this.payload.length >= 1);
1371
- this._sequenceNumber = this.payload[0];
1372
- // Don't accept duplicate commands
1373
- validateSequenceNumber.call(this, this._sequenceNumber);
1374
- }
1375
- else {
1376
- // No options here
1377
- }
1373
+ constructor(options) {
1374
+ super(options);
1375
+ this.sequenceNumber = options.sequenceNumber;
1376
+ }
1377
+ static from(raw, ctx) {
1378
+ const securityManager = assertSecurityRX(ctx);
1379
+ (0, core_1.validatePayload)(raw.payload.length >= 1);
1380
+ const sequenceNumber = raw.payload[0];
1381
+ // Don't accept duplicate commands
1382
+ validateSequenceNumber(securityManager, ctx.sourceNodeId, sequenceNumber);
1383
+ return new Security2CCNonceGet_1({
1384
+ nodeId: ctx.sourceNodeId,
1385
+ sequenceNumber,
1386
+ });
1378
1387
  }
1379
- securityManager;
1380
- _sequenceNumber;
1381
- /**
1382
- * Return the sequence number of this command.
1383
- *
1384
- * **WARNING:** If the sequence number hasn't been set before, this will create a new one.
1385
- * When sending messages, this should only happen immediately before serializing.
1386
- */
1387
- get sequenceNumber() {
1388
- if (this._sequenceNumber == undefined) {
1389
- this._sequenceNumber = this.securityManager
1390
- .nextSequenceNumber(this.nodeId);
1388
+ sequenceNumber;
1389
+ ensureSequenceNumber(securityManager) {
1390
+ if (this.sequenceNumber == undefined) {
1391
+ this.sequenceNumber = securityManager.nextSequenceNumber(this.nodeId);
1391
1392
  }
1392
- return this._sequenceNumber;
1393
1393
  }
1394
- serialize() {
1394
+ serialize(ctx) {
1395
+ const securityManager = assertSecurityTX(ctx, this.nodeId);
1396
+ this.ensureSequenceNumber(securityManager);
1395
1397
  this.payload = Buffer.from([this.sequenceNumber]);
1396
- return super.serialize();
1398
+ return super.serialize(ctx);
1397
1399
  }
1398
- toLogEntry(host) {
1400
+ toLogEntry(ctx) {
1399
1401
  return {
1400
- ...super.toLogEntry(host),
1401
- message: { "sequence number": this.sequenceNumber },
1402
+ ...super.toLogEntry(ctx),
1403
+ message: {
1404
+ "sequence number": this.sequenceNumber ?? "(not set)",
1405
+ },
1402
1406
  };
1403
1407
  }
1404
1408
  };
1405
1409
  exports.Security2CCNonceGet = Security2CCNonceGet;
1406
- exports.Security2CCNonceGet = Security2CCNonceGet = __decorate([
1410
+ exports.Security2CCNonceGet = Security2CCNonceGet = Security2CCNonceGet_1 = __decorate([
1407
1411
  (0, CommandClassDecorators_1.CCCommand)(_Types_1.Security2Command.NonceGet),
1408
1412
  (0, CommandClassDecorators_1.expectedCCResponse)(Security2CCNonceReport)
1409
1413
  ], Security2CCNonceGet);
1410
- let Security2CCKEXReport = class Security2CCKEXReport extends Security2CC {
1411
- constructor(host, options) {
1412
- super(host, options);
1413
- if ((0, CommandClass_1.gotDeserializationOptions)(options)) {
1414
- (0, core_1.validatePayload)(this.payload.length >= 4);
1415
- this.requestCSA = !!(this.payload[0] & 0b10);
1416
- this.echo = !!(this.payload[0] & 0b1);
1417
- // Remember the reserved bits for the echo
1418
- this._reserved = this.payload[0] & 0b1111_1100;
1419
- // The bit mask starts at 0, but bit 0 is not used
1420
- this.supportedKEXSchemes = (0, core_1.parseBitMask)(this.payload.subarray(1, 2), 0).filter((s) => s !== 0);
1421
- this.supportedECDHProfiles = (0, core_1.parseBitMask)(this.payload.subarray(2, 3), shared_1.ECDHProfiles.Curve25519);
1422
- this.requestedKeys = (0, core_1.parseBitMask)(this.payload.subarray(3, 4), core_1.SecurityClass.S2_Unauthenticated);
1423
- }
1424
- else {
1425
- this.requestCSA = options.requestCSA;
1426
- this.echo = options.echo;
1427
- this._reserved = options._reserved ?? 0;
1428
- this.supportedKEXSchemes = options.supportedKEXSchemes;
1429
- this.supportedECDHProfiles = options.supportedECDHProfiles;
1430
- this.requestedKeys = options.requestedKeys;
1431
- }
1414
+ let Security2CCKEXReport = Security2CCKEXReport_1 = class Security2CCKEXReport extends Security2CC {
1415
+ constructor(options) {
1416
+ super(options);
1417
+ this.requestCSA = options.requestCSA;
1418
+ this.echo = options.echo;
1419
+ this._reserved = options._reserved ?? 0;
1420
+ this.supportedKEXSchemes = options.supportedKEXSchemes;
1421
+ this.supportedECDHProfiles = options.supportedECDHProfiles;
1422
+ this.requestedKeys = options.requestedKeys;
1423
+ }
1424
+ static from(raw, ctx) {
1425
+ (0, core_1.validatePayload)(raw.payload.length >= 4);
1426
+ const requestCSA = !!(raw.payload[0] & 0b10);
1427
+ const echo = !!(raw.payload[0] & 0b1);
1428
+ // Remember the reserved bits for the echo
1429
+ const _reserved = raw.payload[0] & 0b1111_1100;
1430
+ // The bit mask starts at 0, but bit 0 is not used
1431
+ const supportedKEXSchemes = (0, core_1.parseBitMask)(raw.payload.subarray(1, 2), 0).filter((s) => s !== 0);
1432
+ const supportedECDHProfiles = (0, core_1.parseBitMask)(raw.payload.subarray(2, 3), shared_1.ECDHProfiles.Curve25519);
1433
+ const requestedKeys = (0, core_1.parseBitMask)(raw.payload.subarray(3, 4), core_1.SecurityClass.S2_Unauthenticated);
1434
+ return new Security2CCKEXReport_1({
1435
+ nodeId: ctx.sourceNodeId,
1436
+ requestCSA,
1437
+ echo,
1438
+ _reserved,
1439
+ supportedKEXSchemes,
1440
+ supportedECDHProfiles,
1441
+ requestedKeys,
1442
+ });
1432
1443
  }
1433
1444
  _reserved;
1434
1445
  requestCSA;
@@ -1436,7 +1447,7 @@ let Security2CCKEXReport = class Security2CCKEXReport extends Security2CC {
1436
1447
  supportedKEXSchemes;
1437
1448
  supportedECDHProfiles;
1438
1449
  requestedKeys;
1439
- serialize() {
1450
+ serialize(ctx) {
1440
1451
  this.payload = Buffer.concat([
1441
1452
  Buffer.from([
1442
1453
  this._reserved
@@ -1448,11 +1459,11 @@ let Security2CCKEXReport = class Security2CCKEXReport extends Security2CC {
1448
1459
  (0, core_1.encodeBitMask)(this.supportedECDHProfiles, 7, shared_1.ECDHProfiles.Curve25519),
1449
1460
  (0, core_1.encodeBitMask)(this.requestedKeys, core_1.SecurityClass.S0_Legacy, core_1.SecurityClass.S2_Unauthenticated),
1450
1461
  ]);
1451
- return super.serialize();
1462
+ return super.serialize(ctx);
1452
1463
  }
1453
- toLogEntry(host) {
1464
+ toLogEntry(ctx) {
1454
1465
  return {
1455
- ...super.toLogEntry(host),
1466
+ ...super.toLogEntry(ctx),
1456
1467
  message: {
1457
1468
  echo: this.echo,
1458
1469
  "supported schemes": this.supportedKEXSchemes
@@ -1470,7 +1481,7 @@ let Security2CCKEXReport = class Security2CCKEXReport extends Security2CC {
1470
1481
  }
1471
1482
  };
1472
1483
  exports.Security2CCKEXReport = Security2CCKEXReport;
1473
- exports.Security2CCKEXReport = Security2CCKEXReport = __decorate([
1484
+ exports.Security2CCKEXReport = Security2CCKEXReport = Security2CCKEXReport_1 = __decorate([
1474
1485
  (0, CommandClassDecorators_1.CCCommand)(_Types_1.Security2Command.KEXReport)
1475
1486
  ], Security2CCKEXReport);
1476
1487
  let Security2CCKEXGet = class Security2CCKEXGet extends Security2CC {
@@ -1499,31 +1510,38 @@ function testExpectedResponseForKEXSet(sent, received) {
1499
1510
  }
1500
1511
  return false;
1501
1512
  }
1502
- let Security2CCKEXSet = class Security2CCKEXSet extends Security2CC {
1503
- constructor(host, options) {
1504
- super(host, options);
1505
- if ((0, CommandClass_1.gotDeserializationOptions)(options)) {
1506
- (0, core_1.validatePayload)(this.payload.length >= 4);
1507
- this._reserved = this.payload[0] & 0b1111_1100;
1508
- this.permitCSA = !!(this.payload[0] & 0b10);
1509
- this.echo = !!(this.payload[0] & 0b1);
1510
- // The bit mask starts at 0, but bit 0 is not used
1511
- const selectedKEXSchemes = (0, core_1.parseBitMask)(this.payload.subarray(1, 2), 0).filter((s) => s !== 0);
1512
- (0, core_1.validatePayload)(selectedKEXSchemes.length === 1);
1513
- this.selectedKEXScheme = selectedKEXSchemes[0];
1514
- const selectedECDHProfiles = (0, core_1.parseBitMask)(this.payload.subarray(2, 3), shared_1.ECDHProfiles.Curve25519);
1515
- (0, core_1.validatePayload)(selectedECDHProfiles.length === 1);
1516
- this.selectedECDHProfile = selectedECDHProfiles[0];
1517
- this.grantedKeys = (0, core_1.parseBitMask)(this.payload.subarray(3, 4), core_1.SecurityClass.S2_Unauthenticated);
1518
- }
1519
- else {
1520
- this.permitCSA = options.permitCSA;
1521
- this.echo = options.echo;
1522
- this._reserved = options._reserved ?? 0;
1523
- this.selectedKEXScheme = options.selectedKEXScheme;
1524
- this.selectedECDHProfile = options.selectedECDHProfile;
1525
- this.grantedKeys = options.grantedKeys;
1526
- }
1513
+ let Security2CCKEXSet = Security2CCKEXSet_1 = class Security2CCKEXSet extends Security2CC {
1514
+ constructor(options) {
1515
+ super(options);
1516
+ this.permitCSA = options.permitCSA;
1517
+ this.echo = options.echo;
1518
+ this._reserved = options._reserved ?? 0;
1519
+ this.selectedKEXScheme = options.selectedKEXScheme;
1520
+ this.selectedECDHProfile = options.selectedECDHProfile;
1521
+ this.grantedKeys = options.grantedKeys;
1522
+ }
1523
+ static from(raw, ctx) {
1524
+ (0, core_1.validatePayload)(raw.payload.length >= 4);
1525
+ const _reserved = raw.payload[0] & 0b1111_1100;
1526
+ const permitCSA = !!(raw.payload[0] & 0b10);
1527
+ const echo = !!(raw.payload[0] & 0b1);
1528
+ // The bit mask starts at 0, but bit 0 is not used
1529
+ const selectedKEXSchemes = (0, core_1.parseBitMask)(raw.payload.subarray(1, 2), 0).filter((s) => s !== 0);
1530
+ (0, core_1.validatePayload)(selectedKEXSchemes.length === 1);
1531
+ const selectedKEXScheme = selectedKEXSchemes[0];
1532
+ const selectedECDHProfiles = (0, core_1.parseBitMask)(raw.payload.subarray(2, 3), shared_1.ECDHProfiles.Curve25519);
1533
+ (0, core_1.validatePayload)(selectedECDHProfiles.length === 1);
1534
+ const selectedECDHProfile = selectedECDHProfiles[0];
1535
+ const grantedKeys = (0, core_1.parseBitMask)(raw.payload.subarray(3, 4), core_1.SecurityClass.S2_Unauthenticated);
1536
+ return new Security2CCKEXSet_1({
1537
+ nodeId: ctx.sourceNodeId,
1538
+ _reserved,
1539
+ permitCSA,
1540
+ echo,
1541
+ selectedKEXScheme,
1542
+ selectedECDHProfile,
1543
+ grantedKeys,
1544
+ });
1527
1545
  }
1528
1546
  _reserved;
1529
1547
  permitCSA;
@@ -1531,7 +1549,7 @@ let Security2CCKEXSet = class Security2CCKEXSet extends Security2CC {
1531
1549
  selectedKEXScheme;
1532
1550
  selectedECDHProfile;
1533
1551
  grantedKeys;
1534
- serialize() {
1552
+ serialize(ctx) {
1535
1553
  this.payload = Buffer.concat([
1536
1554
  Buffer.from([
1537
1555
  this._reserved
@@ -1543,11 +1561,11 @@ let Security2CCKEXSet = class Security2CCKEXSet extends Security2CC {
1543
1561
  (0, core_1.encodeBitMask)([this.selectedECDHProfile], 7, shared_1.ECDHProfiles.Curve25519),
1544
1562
  (0, core_1.encodeBitMask)(this.grantedKeys, core_1.SecurityClass.S0_Legacy, core_1.SecurityClass.S2_Unauthenticated),
1545
1563
  ]);
1546
- return super.serialize();
1564
+ return super.serialize(ctx);
1547
1565
  }
1548
- toLogEntry(host) {
1566
+ toLogEntry(ctx) {
1549
1567
  return {
1550
- ...super.toLogEntry(host),
1568
+ ...super.toLogEntry(ctx),
1551
1569
  message: {
1552
1570
  echo: this.echo,
1553
1571
  "selected scheme": (0, safe_2.getEnumMemberName)(shared_1.KEXSchemes, this.selectedKEXScheme),
@@ -1561,62 +1579,67 @@ let Security2CCKEXSet = class Security2CCKEXSet extends Security2CC {
1561
1579
  }
1562
1580
  };
1563
1581
  exports.Security2CCKEXSet = Security2CCKEXSet;
1564
- exports.Security2CCKEXSet = Security2CCKEXSet = __decorate([
1582
+ exports.Security2CCKEXSet = Security2CCKEXSet = Security2CCKEXSet_1 = __decorate([
1565
1583
  (0, CommandClassDecorators_1.CCCommand)(_Types_1.Security2Command.KEXSet),
1566
1584
  (0, CommandClassDecorators_1.expectedCCResponse)(getExpectedResponseForKEXSet, testExpectedResponseForKEXSet)
1567
1585
  ], Security2CCKEXSet);
1568
- let Security2CCKEXFail = class Security2CCKEXFail extends Security2CC {
1569
- constructor(host, options) {
1570
- super(host, options);
1571
- if ((0, CommandClass_1.gotDeserializationOptions)(options)) {
1572
- (0, core_1.validatePayload)(this.payload.length >= 1);
1573
- this.failType = this.payload[0];
1574
- }
1575
- else {
1576
- this.failType = options.failType;
1577
- }
1586
+ let Security2CCKEXFail = Security2CCKEXFail_1 = class Security2CCKEXFail extends Security2CC {
1587
+ constructor(options) {
1588
+ super(options);
1589
+ this.failType = options.failType;
1590
+ }
1591
+ static from(raw, ctx) {
1592
+ (0, core_1.validatePayload)(raw.payload.length >= 1);
1593
+ const failType = raw.payload[0];
1594
+ return new Security2CCKEXFail_1({
1595
+ nodeId: ctx.sourceNodeId,
1596
+ failType,
1597
+ });
1578
1598
  }
1579
1599
  failType;
1580
- serialize() {
1600
+ serialize(ctx) {
1581
1601
  this.payload = Buffer.from([this.failType]);
1582
- return super.serialize();
1602
+ return super.serialize(ctx);
1583
1603
  }
1584
- toLogEntry(host) {
1604
+ toLogEntry(ctx) {
1585
1605
  return {
1586
- ...super.toLogEntry(host),
1606
+ ...super.toLogEntry(ctx),
1587
1607
  message: { reason: (0, safe_2.getEnumMemberName)(shared_1.KEXFailType, this.failType) },
1588
1608
  };
1589
1609
  }
1590
1610
  };
1591
1611
  exports.Security2CCKEXFail = Security2CCKEXFail;
1592
- exports.Security2CCKEXFail = Security2CCKEXFail = __decorate([
1612
+ exports.Security2CCKEXFail = Security2CCKEXFail = Security2CCKEXFail_1 = __decorate([
1593
1613
  (0, CommandClassDecorators_1.CCCommand)(_Types_1.Security2Command.KEXFail)
1594
1614
  ], Security2CCKEXFail);
1595
- let Security2CCPublicKeyReport = class Security2CCPublicKeyReport extends Security2CC {
1596
- constructor(host, options) {
1597
- super(host, options);
1598
- if ((0, CommandClass_1.gotDeserializationOptions)(options)) {
1599
- (0, core_1.validatePayload)(this.payload.length >= 17);
1600
- this.includingNode = !!(this.payload[0] & 0b1);
1601
- this.publicKey = this.payload.subarray(1);
1602
- }
1603
- else {
1604
- this.includingNode = options.includingNode;
1605
- this.publicKey = options.publicKey;
1606
- }
1615
+ let Security2CCPublicKeyReport = Security2CCPublicKeyReport_1 = class Security2CCPublicKeyReport extends Security2CC {
1616
+ constructor(options) {
1617
+ super(options);
1618
+ this.includingNode = options.includingNode;
1619
+ this.publicKey = options.publicKey;
1620
+ }
1621
+ static from(raw, ctx) {
1622
+ (0, core_1.validatePayload)(raw.payload.length >= 17);
1623
+ const includingNode = !!(raw.payload[0] & 0b1);
1624
+ const publicKey = raw.payload.subarray(1);
1625
+ return new Security2CCPublicKeyReport_1({
1626
+ nodeId: ctx.sourceNodeId,
1627
+ includingNode,
1628
+ publicKey,
1629
+ });
1607
1630
  }
1608
1631
  includingNode;
1609
1632
  publicKey;
1610
- serialize() {
1633
+ serialize(ctx) {
1611
1634
  this.payload = Buffer.concat([
1612
1635
  Buffer.from([this.includingNode ? 1 : 0]),
1613
1636
  this.publicKey,
1614
1637
  ]);
1615
- return super.serialize();
1638
+ return super.serialize(ctx);
1616
1639
  }
1617
- toLogEntry(host) {
1640
+ toLogEntry(ctx) {
1618
1641
  return {
1619
- ...super.toLogEntry(host),
1642
+ ...super.toLogEntry(ctx),
1620
1643
  message: {
1621
1644
  "is including node": this.includingNode,
1622
1645
  "public key": (0, safe_2.buffer2hex)(this.publicKey),
@@ -1625,34 +1648,37 @@ let Security2CCPublicKeyReport = class Security2CCPublicKeyReport extends Securi
1625
1648
  }
1626
1649
  };
1627
1650
  exports.Security2CCPublicKeyReport = Security2CCPublicKeyReport;
1628
- exports.Security2CCPublicKeyReport = Security2CCPublicKeyReport = __decorate([
1651
+ exports.Security2CCPublicKeyReport = Security2CCPublicKeyReport = Security2CCPublicKeyReport_1 = __decorate([
1629
1652
  (0, CommandClassDecorators_1.CCCommand)(_Types_1.Security2Command.PublicKeyReport)
1630
1653
  ], Security2CCPublicKeyReport);
1631
- let Security2CCNetworkKeyReport = class Security2CCNetworkKeyReport extends Security2CC {
1632
- constructor(host, options) {
1633
- super(host, options);
1634
- if ((0, CommandClass_1.gotDeserializationOptions)(options)) {
1635
- (0, core_1.validatePayload)(this.payload.length >= 17);
1636
- this.grantedKey = bitMaskToSecurityClass(this.payload, 0);
1637
- this.networkKey = this.payload.subarray(1, 17);
1638
- }
1639
- else {
1640
- this.grantedKey = options.grantedKey;
1641
- this.networkKey = options.networkKey;
1642
- }
1654
+ let Security2CCNetworkKeyReport = Security2CCNetworkKeyReport_1 = class Security2CCNetworkKeyReport extends Security2CC {
1655
+ constructor(options) {
1656
+ super(options);
1657
+ this.grantedKey = options.grantedKey;
1658
+ this.networkKey = options.networkKey;
1659
+ }
1660
+ static from(raw, ctx) {
1661
+ (0, core_1.validatePayload)(raw.payload.length >= 17);
1662
+ const grantedKey = bitMaskToSecurityClass(raw.payload, 0);
1663
+ const networkKey = raw.payload.subarray(1, 17);
1664
+ return new Security2CCNetworkKeyReport_1({
1665
+ nodeId: ctx.sourceNodeId,
1666
+ grantedKey,
1667
+ networkKey,
1668
+ });
1643
1669
  }
1644
1670
  grantedKey;
1645
1671
  networkKey;
1646
- serialize() {
1672
+ serialize(ctx) {
1647
1673
  this.payload = Buffer.concat([
1648
1674
  securityClassToBitMask(this.grantedKey),
1649
1675
  this.networkKey,
1650
1676
  ]);
1651
- return super.serialize();
1677
+ return super.serialize(ctx);
1652
1678
  }
1653
- toLogEntry(host) {
1679
+ toLogEntry(ctx) {
1654
1680
  return {
1655
- ...super.toLogEntry(host),
1681
+ ...super.toLogEntry(ctx),
1656
1682
  message: {
1657
1683
  "security class": (0, safe_2.getEnumMemberName)(core_1.SecurityClass, this.grantedKey),
1658
1684
  // This shouldn't be logged, so users can safely post their logs online
@@ -1662,28 +1688,30 @@ let Security2CCNetworkKeyReport = class Security2CCNetworkKeyReport extends Secu
1662
1688
  }
1663
1689
  };
1664
1690
  exports.Security2CCNetworkKeyReport = Security2CCNetworkKeyReport;
1665
- exports.Security2CCNetworkKeyReport = Security2CCNetworkKeyReport = __decorate([
1691
+ exports.Security2CCNetworkKeyReport = Security2CCNetworkKeyReport = Security2CCNetworkKeyReport_1 = __decorate([
1666
1692
  (0, CommandClassDecorators_1.CCCommand)(_Types_1.Security2Command.NetworkKeyReport)
1667
1693
  ], Security2CCNetworkKeyReport);
1668
- let Security2CCNetworkKeyGet = class Security2CCNetworkKeyGet extends Security2CC {
1669
- constructor(host, options) {
1670
- super(host, options);
1671
- if ((0, CommandClass_1.gotDeserializationOptions)(options)) {
1672
- (0, core_1.validatePayload)(this.payload.length >= 1);
1673
- this.requestedKey = bitMaskToSecurityClass(this.payload, 0);
1674
- }
1675
- else {
1676
- this.requestedKey = options.requestedKey;
1677
- }
1694
+ let Security2CCNetworkKeyGet = Security2CCNetworkKeyGet_1 = class Security2CCNetworkKeyGet extends Security2CC {
1695
+ constructor(options) {
1696
+ super(options);
1697
+ this.requestedKey = options.requestedKey;
1698
+ }
1699
+ static from(raw, ctx) {
1700
+ (0, core_1.validatePayload)(raw.payload.length >= 1);
1701
+ const requestedKey = bitMaskToSecurityClass(raw.payload, 0);
1702
+ return new Security2CCNetworkKeyGet_1({
1703
+ nodeId: ctx.sourceNodeId,
1704
+ requestedKey,
1705
+ });
1678
1706
  }
1679
1707
  requestedKey;
1680
- serialize() {
1708
+ serialize(ctx) {
1681
1709
  this.payload = securityClassToBitMask(this.requestedKey);
1682
- return super.serialize();
1710
+ return super.serialize(ctx);
1683
1711
  }
1684
- toLogEntry(host) {
1712
+ toLogEntry(ctx) {
1685
1713
  return {
1686
- ...super.toLogEntry(host),
1714
+ ...super.toLogEntry(ctx),
1687
1715
  message: {
1688
1716
  "security class": (0, safe_2.getEnumMemberName)(core_1.SecurityClass, this.requestedKey),
1689
1717
  },
@@ -1691,7 +1719,7 @@ let Security2CCNetworkKeyGet = class Security2CCNetworkKeyGet extends Security2C
1691
1719
  }
1692
1720
  };
1693
1721
  exports.Security2CCNetworkKeyGet = Security2CCNetworkKeyGet;
1694
- exports.Security2CCNetworkKeyGet = Security2CCNetworkKeyGet = __decorate([
1722
+ exports.Security2CCNetworkKeyGet = Security2CCNetworkKeyGet = Security2CCNetworkKeyGet_1 = __decorate([
1695
1723
  (0, CommandClassDecorators_1.CCCommand)(_Types_1.Security2Command.NetworkKeyGet)
1696
1724
  // Don't expect a response - we need to distinguish between Report and Fail
1697
1725
  // FIXME: maybe use the dynamic @expectedCCResponse instead?
@@ -1702,30 +1730,33 @@ exports.Security2CCNetworkKeyVerify = Security2CCNetworkKeyVerify;
1702
1730
  exports.Security2CCNetworkKeyVerify = Security2CCNetworkKeyVerify = __decorate([
1703
1731
  (0, CommandClassDecorators_1.CCCommand)(_Types_1.Security2Command.NetworkKeyVerify)
1704
1732
  ], Security2CCNetworkKeyVerify);
1705
- let Security2CCTransferEnd = class Security2CCTransferEnd extends Security2CC {
1706
- constructor(host, options) {
1707
- super(host, options);
1708
- if ((0, CommandClass_1.gotDeserializationOptions)(options)) {
1709
- (0, core_1.validatePayload)(this.payload.length >= 1);
1710
- this.keyVerified = !!(this.payload[0] & 0b10);
1711
- this.keyRequestComplete = !!(this.payload[0] & 0b1);
1712
- }
1713
- else {
1714
- this.keyVerified = options.keyVerified;
1715
- this.keyRequestComplete = options.keyRequestComplete;
1716
- }
1733
+ let Security2CCTransferEnd = Security2CCTransferEnd_1 = class Security2CCTransferEnd extends Security2CC {
1734
+ constructor(options) {
1735
+ super(options);
1736
+ this.keyVerified = options.keyVerified;
1737
+ this.keyRequestComplete = options.keyRequestComplete;
1738
+ }
1739
+ static from(raw, ctx) {
1740
+ (0, core_1.validatePayload)(raw.payload.length >= 1);
1741
+ const keyVerified = !!(raw.payload[0] & 0b10);
1742
+ const keyRequestComplete = !!(raw.payload[0] & 0b1);
1743
+ return new Security2CCTransferEnd_1({
1744
+ nodeId: ctx.sourceNodeId,
1745
+ keyVerified,
1746
+ keyRequestComplete,
1747
+ });
1717
1748
  }
1718
1749
  keyVerified;
1719
1750
  keyRequestComplete;
1720
- serialize() {
1751
+ serialize(ctx) {
1721
1752
  this.payload = Buffer.from([
1722
1753
  (this.keyVerified ? 0b10 : 0) + (this.keyRequestComplete ? 0b1 : 0),
1723
1754
  ]);
1724
- return super.serialize();
1755
+ return super.serialize(ctx);
1725
1756
  }
1726
- toLogEntry(host) {
1757
+ toLogEntry(ctx) {
1727
1758
  return {
1728
- ...super.toLogEntry(host),
1759
+ ...super.toLogEntry(ctx),
1729
1760
  message: {
1730
1761
  "key verified": this.keyVerified,
1731
1762
  "request complete": this.keyRequestComplete,
@@ -1734,32 +1765,34 @@ let Security2CCTransferEnd = class Security2CCTransferEnd extends Security2CC {
1734
1765
  }
1735
1766
  };
1736
1767
  exports.Security2CCTransferEnd = Security2CCTransferEnd;
1737
- exports.Security2CCTransferEnd = Security2CCTransferEnd = __decorate([
1768
+ exports.Security2CCTransferEnd = Security2CCTransferEnd = Security2CCTransferEnd_1 = __decorate([
1738
1769
  (0, CommandClassDecorators_1.CCCommand)(_Types_1.Security2Command.TransferEnd)
1739
1770
  ], Security2CCTransferEnd);
1740
- let Security2CCCommandsSupportedReport = class Security2CCCommandsSupportedReport extends Security2CC {
1741
- constructor(host, options) {
1742
- super(host, options);
1743
- if ((0, CommandClass_1.gotDeserializationOptions)(options)) {
1744
- const CCs = (0, core_1.parseCCList)(this.payload);
1745
- // SDS13783: A sending node MAY terminate the list of supported command classes with the
1746
- // COMMAND_CLASS_MARK command class identifier.
1747
- // A receiving node MUST stop parsing the list of supported command classes if it detects the
1748
- // COMMAND_CLASS_MARK command class identifier in the Security 2 Commands Supported Report
1749
- this.supportedCCs = CCs.supportedCCs;
1750
- }
1751
- else {
1752
- this.supportedCCs = options.supportedCCs;
1753
- }
1771
+ let Security2CCCommandsSupportedReport = Security2CCCommandsSupportedReport_1 = class Security2CCCommandsSupportedReport extends Security2CC {
1772
+ constructor(options) {
1773
+ super(options);
1774
+ this.supportedCCs = options.supportedCCs;
1775
+ }
1776
+ static from(raw, ctx) {
1777
+ const CCs = (0, core_1.parseCCList)(raw.payload);
1778
+ // SDS13783: A sending node MAY terminate the list of supported command classes with the
1779
+ // COMMAND_CLASS_MARK command class identifier.
1780
+ // A receiving node MUST stop parsing the list of supported command classes if it detects the
1781
+ // COMMAND_CLASS_MARK command class identifier in the Security 2 Commands Supported Report
1782
+ const supportedCCs = CCs.supportedCCs;
1783
+ return new Security2CCCommandsSupportedReport_1({
1784
+ nodeId: ctx.sourceNodeId,
1785
+ supportedCCs,
1786
+ });
1754
1787
  }
1755
1788
  supportedCCs;
1756
- serialize() {
1789
+ serialize(ctx) {
1757
1790
  this.payload = (0, safe_1.encodeCCList)(this.supportedCCs, []);
1758
- return super.serialize();
1791
+ return super.serialize(ctx);
1759
1792
  }
1760
- toLogEntry(host) {
1793
+ toLogEntry(ctx) {
1761
1794
  return {
1762
- ...super.toLogEntry(host),
1795
+ ...super.toLogEntry(ctx),
1763
1796
  message: {
1764
1797
  "supported CCs": this.supportedCCs
1765
1798
  .map((cc) => (0, core_1.getCCName)(cc))
@@ -1770,7 +1803,7 @@ let Security2CCCommandsSupportedReport = class Security2CCCommandsSupportedRepor
1770
1803
  }
1771
1804
  };
1772
1805
  exports.Security2CCCommandsSupportedReport = Security2CCCommandsSupportedReport;
1773
- exports.Security2CCCommandsSupportedReport = Security2CCCommandsSupportedReport = __decorate([
1806
+ exports.Security2CCCommandsSupportedReport = Security2CCCommandsSupportedReport = Security2CCCommandsSupportedReport_1 = __decorate([
1774
1807
  (0, CommandClassDecorators_1.CCCommand)(_Types_1.Security2Command.CommandsSupportedReport)
1775
1808
  ], Security2CCCommandsSupportedReport);
1776
1809
  let Security2CCCommandsSupportedGet = class Security2CCCommandsSupportedGet extends Security2CC {