@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.
- package/build/cc/AlarmSensorCC.d.ts +31 -20
- package/build/cc/AlarmSensorCC.d.ts.map +1 -1
- package/build/cc/AlarmSensorCC.js +106 -87
- package/build/cc/AlarmSensorCC.js.map +1 -1
- package/build/cc/AssociationCC.d.ts +38 -32
- package/build/cc/AssociationCC.d.ts.map +1 -1
- package/build/cc/AssociationCC.js +193 -176
- package/build/cc/AssociationCC.js.map +1 -1
- package/build/cc/AssociationGroupInfoCC.d.ts +42 -36
- package/build/cc/AssociationGroupInfoCC.d.ts.map +1 -1
- package/build/cc/AssociationGroupInfoCC.js +218 -200
- package/build/cc/AssociationGroupInfoCC.js.map +1 -1
- package/build/cc/BarrierOperatorCC.d.ts +41 -24
- package/build/cc/BarrierOperatorCC.d.ts.map +1 -1
- package/build/cc/BarrierOperatorCC.js +143 -109
- package/build/cc/BarrierOperatorCC.js.map +1 -1
- package/build/cc/BasicCC.d.ts +22 -23
- package/build/cc/BasicCC.d.ts.map +1 -1
- package/build/cc/BasicCC.js +83 -77
- package/build/cc/BasicCC.js.map +1 -1
- package/build/cc/BatteryCC.d.ts +22 -14
- package/build/cc/BatteryCC.d.ts.map +1 -1
- package/build/cc/BatteryCC.js +116 -87
- package/build/cc/BatteryCC.js.map +1 -1
- package/build/cc/BinarySensorCC.d.ts +22 -19
- package/build/cc/BinarySensorCC.d.ts.map +1 -1
- package/build/cc/BinarySensorCC.js +103 -93
- package/build/cc/BinarySensorCC.js.map +1 -1
- package/build/cc/BinarySwitchCC.d.ts +20 -20
- package/build/cc/BinarySwitchCC.d.ts.map +1 -1
- package/build/cc/BinarySwitchCC.js +71 -59
- package/build/cc/BinarySwitchCC.js.map +1 -1
- package/build/cc/CRC16CC.d.ts +9 -9
- package/build/cc/CRC16CC.d.ts.map +1 -1
- package/build/cc/CRC16CC.js +36 -36
- package/build/cc/CRC16CC.js.map +1 -1
- package/build/cc/CentralSceneCC.d.ts +34 -16
- package/build/cc/CentralSceneCC.d.ts.map +1 -1
- package/build/cc/CentralSceneCC.js +116 -79
- package/build/cc/CentralSceneCC.js.map +1 -1
- package/build/cc/ClimateControlScheduleCC.d.ts +38 -21
- package/build/cc/ClimateControlScheduleCC.d.ts.map +1 -1
- package/build/cc/ClimateControlScheduleCC.js +113 -81
- package/build/cc/ClimateControlScheduleCC.js.map +1 -1
- package/build/cc/ClockCC.d.ts +18 -11
- package/build/cc/ClockCC.d.ts.map +1 -1
- package/build/cc/ClockCC.js +57 -43
- package/build/cc/ClockCC.js.map +1 -1
- package/build/cc/ColorSwitchCC.d.ts +37 -33
- package/build/cc/ColorSwitchCC.d.ts.map +1 -1
- package/build/cc/ColorSwitchCC.js +216 -189
- package/build/cc/ColorSwitchCC.js.map +1 -1
- package/build/cc/ConfigurationCC.d.ts +89 -74
- package/build/cc/ConfigurationCC.d.ts.map +1 -1
- package/build/cc/ConfigurationCC.js +541 -485
- package/build/cc/ConfigurationCC.js.map +1 -1
- package/build/cc/DeviceResetLocallyCC.d.ts +3 -3
- package/build/cc/DeviceResetLocallyCC.d.ts.map +1 -1
- package/build/cc/DeviceResetLocallyCC.js +14 -14
- package/build/cc/DeviceResetLocallyCC.js.map +1 -1
- package/build/cc/DoorLockCC.d.ts +66 -27
- package/build/cc/DoorLockCC.d.ts.map +1 -1
- package/build/cc/DoorLockCC.js +288 -197
- package/build/cc/DoorLockCC.js.map +1 -1
- package/build/cc/DoorLockLoggingCC.d.ts +23 -13
- package/build/cc/DoorLockLoggingCC.d.ts.map +1 -1
- package/build/cc/DoorLockLoggingCC.js +81 -63
- package/build/cc/DoorLockLoggingCC.js.map +1 -1
- package/build/cc/EnergyProductionCC.d.ts +16 -14
- package/build/cc/EnergyProductionCC.d.ts.map +1 -1
- package/build/cc/EnergyProductionCC.js +58 -52
- package/build/cc/EnergyProductionCC.js.map +1 -1
- package/build/cc/EntryControlCC.d.ts +44 -18
- package/build/cc/EntryControlCC.d.ts.map +1 -1
- package/build/cc/EntryControlCC.js +163 -112
- package/build/cc/EntryControlCC.js.map +1 -1
- package/build/cc/FirmwareUpdateMetaDataCC.d.ts +69 -34
- package/build/cc/FirmwareUpdateMetaDataCC.d.ts.map +1 -1
- package/build/cc/FirmwareUpdateMetaDataCC.js +311 -245
- package/build/cc/FirmwareUpdateMetaDataCC.js.map +1 -1
- package/build/cc/HumidityControlModeCC.d.ts +24 -16
- package/build/cc/HumidityControlModeCC.d.ts.map +1 -1
- package/build/cc/HumidityControlModeCC.js +80 -65
- package/build/cc/HumidityControlModeCC.js.map +1 -1
- package/build/cc/HumidityControlOperatingStateCC.d.ts +11 -7
- package/build/cc/HumidityControlOperatingStateCC.d.ts.map +1 -1
- package/build/cc/HumidityControlOperatingStateCC.js +31 -22
- package/build/cc/HumidityControlOperatingStateCC.js.map +1 -1
- package/build/cc/HumidityControlSetpointCC.d.ts +68 -49
- package/build/cc/HumidityControlSetpointCC.d.ts.map +1 -1
- package/build/cc/HumidityControlSetpointCC.js +203 -181
- package/build/cc/HumidityControlSetpointCC.js.map +1 -1
- package/build/cc/InclusionControllerCC.d.ts +13 -11
- package/build/cc/InclusionControllerCC.d.ts.map +1 -1
- package/build/cc/InclusionControllerCC.js +49 -42
- package/build/cc/InclusionControllerCC.js.map +1 -1
- package/build/cc/IndicatorCC.d.ts +46 -39
- package/build/cc/IndicatorCC.d.ts.map +1 -1
- package/build/cc/IndicatorCC.js +298 -277
- package/build/cc/IndicatorCC.js.map +1 -1
- package/build/cc/IrrigationCC.d.ts +127 -57
- package/build/cc/IrrigationCC.d.ts.map +1 -1
- package/build/cc/IrrigationCC.js +496 -352
- package/build/cc/IrrigationCC.js.map +1 -1
- package/build/cc/LanguageCC.d.ts +17 -11
- package/build/cc/LanguageCC.d.ts.map +1 -1
- package/build/cc/LanguageCC.js +54 -44
- package/build/cc/LanguageCC.js.map +1 -1
- package/build/cc/LockCC.d.ts +16 -11
- package/build/cc/LockCC.d.ts.map +1 -1
- package/build/cc/LockCC.js +48 -38
- package/build/cc/LockCC.js.map +1 -1
- package/build/cc/ManufacturerProprietaryCC.d.ts +12 -11
- package/build/cc/ManufacturerProprietaryCC.d.ts.map +1 -1
- package/build/cc/ManufacturerProprietaryCC.js +45 -50
- package/build/cc/ManufacturerProprietaryCC.js.map +1 -1
- package/build/cc/ManufacturerSpecificCC.d.ts +20 -13
- package/build/cc/ManufacturerSpecificCC.d.ts.map +1 -1
- package/build/cc/ManufacturerSpecificCC.js +79 -63
- package/build/cc/ManufacturerSpecificCC.js.map +1 -1
- package/build/cc/MeterCC.d.ts +30 -26
- package/build/cc/MeterCC.d.ts.map +1 -1
- package/build/cc/MeterCC.js +236 -204
- package/build/cc/MeterCC.js.map +1 -1
- package/build/cc/MultiChannelAssociationCC.d.ts +31 -26
- package/build/cc/MultiChannelAssociationCC.d.ts.map +1 -1
- package/build/cc/MultiChannelAssociationCC.js +177 -168
- package/build/cc/MultiChannelAssociationCC.js.map +1 -1
- package/build/cc/MultiChannelCC.d.ts +70 -49
- package/build/cc/MultiChannelCC.d.ts.map +1 -1
- package/build/cc/MultiChannelCC.js +377 -318
- package/build/cc/MultiChannelCC.js.map +1 -1
- package/build/cc/MultiCommandCC.d.ts +9 -9
- package/build/cc/MultiCommandCC.d.ts.map +1 -1
- package/build/cc/MultiCommandCC.js +35 -36
- package/build/cc/MultiCommandCC.js.map +1 -1
- package/build/cc/MultilevelSensorCC.d.ts +38 -33
- package/build/cc/MultilevelSensorCC.d.ts.map +1 -1
- package/build/cc/MultilevelSensorCC.js +172 -152
- package/build/cc/MultilevelSensorCC.js.map +1 -1
- package/build/cc/MultilevelSwitchCC.d.ts +29 -25
- package/build/cc/MultilevelSwitchCC.d.ts.map +1 -1
- package/build/cc/MultilevelSwitchCC.js +147 -127
- package/build/cc/MultilevelSwitchCC.js.map +1 -1
- package/build/cc/NoOperationCC.d.ts +0 -8
- package/build/cc/NoOperationCC.d.ts.map +1 -1
- package/build/cc/NoOperationCC.js +2 -11
- package/build/cc/NoOperationCC.js.map +1 -1
- package/build/cc/NodeNamingCC.d.ts +27 -17
- package/build/cc/NodeNamingCC.d.ts.map +1 -1
- package/build/cc/NodeNamingCC.js +86 -69
- package/build/cc/NodeNamingCC.js.map +1 -1
- package/build/cc/NotificationCC.d.ts +46 -43
- package/build/cc/NotificationCC.d.ts.map +1 -1
- package/build/cc/NotificationCC.js +317 -303
- package/build/cc/NotificationCC.js.map +1 -1
- package/build/cc/PowerlevelCC.d.ts +22 -18
- package/build/cc/PowerlevelCC.d.ts.map +1 -1
- package/build/cc/PowerlevelCC.js +125 -95
- package/build/cc/PowerlevelCC.js.map +1 -1
- package/build/cc/ProtectionCC.d.ts +49 -26
- package/build/cc/ProtectionCC.d.ts.map +1 -1
- package/build/cc/ProtectionCC.js +172 -126
- package/build/cc/ProtectionCC.js.map +1 -1
- package/build/cc/SceneActivationCC.d.ts +8 -7
- package/build/cc/SceneActivationCC.d.ts.map +1 -1
- package/build/cc/SceneActivationCC.js +28 -23
- package/build/cc/SceneActivationCC.js.map +1 -1
- package/build/cc/SceneActuatorConfigurationCC.d.ts +23 -15
- package/build/cc/SceneActuatorConfigurationCC.d.ts.map +1 -1
- package/build/cc/SceneActuatorConfigurationCC.js +79 -62
- package/build/cc/SceneActuatorConfigurationCC.js.map +1 -1
- package/build/cc/SceneControllerConfigurationCC.d.ts +25 -17
- package/build/cc/SceneControllerConfigurationCC.d.ts.map +1 -1
- package/build/cc/SceneControllerConfigurationCC.js +92 -78
- package/build/cc/SceneControllerConfigurationCC.js.map +1 -1
- package/build/cc/ScheduleEntryLockCC.d.ts +86 -72
- package/build/cc/ScheduleEntryLockCC.d.ts.map +1 -1
- package/build/cc/ScheduleEntryLockCC.js +607 -455
- package/build/cc/ScheduleEntryLockCC.js.map +1 -1
- package/build/cc/Security2CC.d.ts +78 -87
- package/build/cc/Security2CC.d.ts.map +1 -1
- package/build/cc/Security2CC.js +798 -765
- package/build/cc/Security2CC.js.map +1 -1
- package/build/cc/SecurityCC.d.ts +47 -42
- package/build/cc/SecurityCC.d.ts.map +1 -1
- package/build/cc/SecurityCC.js +240 -218
- package/build/cc/SecurityCC.js.map +1 -1
- package/build/cc/SoundSwitchCC.d.ts +36 -29
- package/build/cc/SoundSwitchCC.d.ts.map +1 -1
- package/build/cc/SoundSwitchCC.js +180 -157
- package/build/cc/SoundSwitchCC.js.map +1 -1
- package/build/cc/SupervisionCC.d.ts +17 -14
- package/build/cc/SupervisionCC.d.ts.map +1 -1
- package/build/cc/SupervisionCC.js +86 -66
- package/build/cc/SupervisionCC.js.map +1 -1
- package/build/cc/ThermostatFanModeCC.d.ts +25 -15
- package/build/cc/ThermostatFanModeCC.d.ts.map +1 -1
- package/build/cc/ThermostatFanModeCC.js +79 -60
- package/build/cc/ThermostatFanModeCC.js.map +1 -1
- package/build/cc/ThermostatFanStateCC.d.ts +11 -7
- package/build/cc/ThermostatFanStateCC.d.ts.map +1 -1
- package/build/cc/ThermostatFanStateCC.js +31 -22
- package/build/cc/ThermostatFanStateCC.js.map +1 -1
- package/build/cc/ThermostatModeCC.d.ts +24 -21
- package/build/cc/ThermostatModeCC.d.ts.map +1 -1
- package/build/cc/ThermostatModeCC.js +106 -90
- package/build/cc/ThermostatModeCC.js.map +1 -1
- package/build/cc/ThermostatOperatingStateCC.d.ts +11 -7
- package/build/cc/ThermostatOperatingStateCC.d.ts.map +1 -1
- package/build/cc/ThermostatOperatingStateCC.js +31 -22
- package/build/cc/ThermostatOperatingStateCC.js.map +1 -1
- package/build/cc/ThermostatSetbackCC.d.ts +14 -13
- package/build/cc/ThermostatSetbackCC.d.ts.map +1 -1
- package/build/cc/ThermostatSetbackCC.js +64 -57
- package/build/cc/ThermostatSetbackCC.js.map +1 -1
- package/build/cc/ThermostatSetpointCC.d.ts +38 -57
- package/build/cc/ThermostatSetpointCC.d.ts.map +1 -1
- package/build/cc/ThermostatSetpointCC.js +200 -264
- package/build/cc/ThermostatSetpointCC.js.map +1 -1
- package/build/cc/TimeCC.d.ts +24 -20
- package/build/cc/TimeCC.d.ts.map +1 -1
- package/build/cc/TimeCC.js +119 -108
- package/build/cc/TimeCC.js.map +1 -1
- package/build/cc/TimeParametersCC.d.ts +18 -14
- package/build/cc/TimeParametersCC.d.ts.map +1 -1
- package/build/cc/TimeParametersCC.js +77 -69
- package/build/cc/TimeParametersCC.js.map +1 -1
- package/build/cc/TransportServiceCC.d.ts +29 -32
- package/build/cc/TransportServiceCC.d.ts.map +1 -1
- package/build/cc/TransportServiceCC.js +159 -161
- package/build/cc/TransportServiceCC.js.map +1 -1
- package/build/cc/UserCodeCC.d.ts +83 -66
- package/build/cc/UserCodeCC.d.ts.map +1 -1
- package/build/cc/UserCodeCC.js +449 -402
- package/build/cc/UserCodeCC.js.map +1 -1
- package/build/cc/VersionCC.d.ts +36 -20
- package/build/cc/VersionCC.d.ts.map +1 -1
- package/build/cc/VersionCC.js +190 -149
- package/build/cc/VersionCC.js.map +1 -1
- package/build/cc/WakeUpCC.d.ts +27 -13
- package/build/cc/WakeUpCC.d.ts.map +1 -1
- package/build/cc/WakeUpCC.js +105 -77
- package/build/cc/WakeUpCC.js.map +1 -1
- package/build/cc/WindowCoveringCC.d.ts +39 -27
- package/build/cc/WindowCoveringCC.d.ts.map +1 -1
- package/build/cc/WindowCoveringCC.js +169 -137
- package/build/cc/WindowCoveringCC.js.map +1 -1
- package/build/cc/ZWavePlusCC.d.ts +8 -7
- package/build/cc/ZWavePlusCC.d.ts.map +1 -1
- package/build/cc/ZWavePlusCC.js +44 -37
- package/build/cc/ZWavePlusCC.js.map +1 -1
- package/build/cc/ZWaveProtocolCC.d.ts +111 -84
- package/build/cc/ZWaveProtocolCC.d.ts.map +1 -1
- package/build/cc/ZWaveProtocolCC.js +583 -508
- package/build/cc/ZWaveProtocolCC.js.map +1 -1
- package/build/cc/index.d.ts +37 -34
- package/build/cc/index.d.ts.map +1 -1
- package/build/cc/index.js +6 -7
- package/build/cc/index.js.map +1 -1
- package/build/cc/manufacturerProprietary/FibaroCC.d.ts +29 -24
- package/build/cc/manufacturerProprietary/FibaroCC.d.ts.map +1 -1
- package/build/cc/manufacturerProprietary/FibaroCC.js +109 -112
- package/build/cc/manufacturerProprietary/FibaroCC.js.map +1 -1
- package/build/index.d.ts +0 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +0 -1
- package/build/index.js.map +1 -1
- package/build/lib/API.d.ts +21 -18
- package/build/lib/API.d.ts.map +1 -1
- package/build/lib/API.js +25 -49
- package/build/lib/API.js.map +1 -1
- package/build/lib/CommandClass.d.ts +51 -89
- package/build/lib/CommandClass.d.ts.map +1 -1
- package/build/lib/CommandClass.js +177 -249
- package/build/lib/CommandClass.js.map +1 -1
- package/build/lib/EncapsulatingCommandClass.d.ts +1 -15
- package/build/lib/EncapsulatingCommandClass.d.ts.map +1 -1
- package/build/lib/EncapsulatingCommandClass.js.map +1 -1
- package/build/lib/ICommandClassContainer.d.ts +1 -8
- package/build/lib/ICommandClassContainer.d.ts.map +1 -1
- package/build/lib/ICommandClassContainer.js +0 -8
- package/build/lib/ICommandClassContainer.js.map +1 -1
- package/build/lib/Values.d.ts +3 -3
- package/build/lib/Values.d.ts.map +1 -1
- package/build/lib/_Types.d.ts.map +1 -1
- package/build/lib/_Types.js +1 -0
- package/build/lib/_Types.js.map +1 -1
- package/build/lib/utils.d.ts +14 -13
- package/build/lib/utils.d.ts.map +1 -1
- package/build/lib/utils.js +94 -92
- package/build/lib/utils.js.map +1 -1
- package/package.json +4 -5
package/build/cc/Security2CC.js
CHANGED
|
@@ -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(
|
|
53
|
-
const longRange = (0, core_1.isLongRangeNodeId)(
|
|
54
|
-
|| (0, core_1.isLongRangeNodeId)((0, typeguards_1.isArray)(
|
|
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
|
-
?
|
|
57
|
-
:
|
|
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
|
-
|
|
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(
|
|
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.
|
|
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(
|
|
267
|
+
const cc = new Security2CCNonceReport({
|
|
99
268
|
nodeId: this.endpoint.nodeId,
|
|
100
|
-
|
|
269
|
+
endpointIndex: this.endpoint.index,
|
|
101
270
|
SOS: true,
|
|
102
271
|
MOS: false,
|
|
103
272
|
receiverEI,
|
|
104
273
|
});
|
|
105
274
|
try {
|
|
106
|
-
await this.
|
|
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(
|
|
306
|
+
const cc = new Security2CCNonceReport({
|
|
138
307
|
nodeId: this.endpoint.nodeId,
|
|
139
|
-
|
|
308
|
+
endpointIndex: this.endpoint.index,
|
|
140
309
|
SOS: false,
|
|
141
310
|
MOS: true,
|
|
142
311
|
});
|
|
143
312
|
try {
|
|
144
|
-
await this.
|
|
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(
|
|
341
|
+
const cc = new Security2CCMessageEncapsulation({
|
|
173
342
|
nodeId: this.endpoint.nodeId,
|
|
174
|
-
|
|
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.
|
|
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(
|
|
382
|
+
let cc = new Security2CCCommandsSupportedGet({
|
|
214
383
|
nodeId: this.endpoint.nodeId,
|
|
215
|
-
|
|
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
|
-
|
|
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.
|
|
224
|
-
const response = await this.
|
|
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(
|
|
404
|
+
const cc = new Security2CCCommandsSupportedReport({
|
|
233
405
|
nodeId: this.endpoint.nodeId,
|
|
234
|
-
|
|
406
|
+
endpointIndex: this.endpoint.index,
|
|
235
407
|
supportedCCs,
|
|
236
408
|
});
|
|
237
|
-
await this.
|
|
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(
|
|
414
|
+
const cc = new Security2CCKEXGet({
|
|
243
415
|
nodeId: this.endpoint.nodeId,
|
|
244
|
-
|
|
416
|
+
endpointIndex: this.endpoint.index,
|
|
245
417
|
});
|
|
246
|
-
const response = await this.
|
|
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(
|
|
433
|
+
const cc = new Security2CCKEXReport({
|
|
262
434
|
nodeId: this.endpoint.nodeId,
|
|
263
|
-
|
|
435
|
+
endpointIndex: this.endpoint.index,
|
|
264
436
|
...params,
|
|
265
437
|
echo: false,
|
|
266
438
|
});
|
|
267
|
-
await this.
|
|
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(
|
|
444
|
+
const cc = new Security2CCKEXSet({
|
|
273
445
|
nodeId: this.endpoint.nodeId,
|
|
274
|
-
|
|
446
|
+
endpointIndex: this.endpoint.index,
|
|
275
447
|
...params,
|
|
276
448
|
echo: false,
|
|
277
449
|
});
|
|
278
|
-
await this.
|
|
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(
|
|
455
|
+
const cc = new Security2CCKEXReport({
|
|
284
456
|
nodeId: this.endpoint.nodeId,
|
|
285
|
-
|
|
457
|
+
endpointIndex: this.endpoint.index,
|
|
286
458
|
...params,
|
|
287
459
|
echo: true,
|
|
288
460
|
});
|
|
289
|
-
await this.
|
|
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(
|
|
466
|
+
const cc = new Security2CCKEXSet({
|
|
295
467
|
nodeId: this.endpoint.nodeId,
|
|
296
|
-
|
|
468
|
+
endpointIndex: this.endpoint.index,
|
|
297
469
|
...params,
|
|
298
470
|
echo: true,
|
|
299
471
|
});
|
|
300
|
-
return this.
|
|
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(
|
|
477
|
+
const cc = new Security2CCKEXFail({
|
|
306
478
|
nodeId: this.endpoint.nodeId,
|
|
307
|
-
|
|
479
|
+
endpointIndex: this.endpoint.index,
|
|
308
480
|
failType,
|
|
309
481
|
});
|
|
310
|
-
await this.
|
|
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(
|
|
486
|
+
const cc = new Security2CCPublicKeyReport({
|
|
315
487
|
nodeId: this.endpoint.nodeId,
|
|
316
|
-
|
|
488
|
+
endpointIndex: this.endpoint.index,
|
|
317
489
|
includingNode,
|
|
318
490
|
publicKey,
|
|
319
491
|
});
|
|
320
|
-
await this.
|
|
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(
|
|
496
|
+
const cc = new Security2CCNetworkKeyGet({
|
|
325
497
|
nodeId: this.endpoint.nodeId,
|
|
326
|
-
|
|
498
|
+
endpointIndex: this.endpoint.index,
|
|
327
499
|
requestedKey: securityClass,
|
|
328
500
|
});
|
|
329
|
-
await this.
|
|
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(
|
|
505
|
+
const cc = new Security2CCNetworkKeyReport({
|
|
334
506
|
nodeId: this.endpoint.nodeId,
|
|
335
|
-
|
|
507
|
+
endpointIndex: this.endpoint.index,
|
|
336
508
|
grantedKey: securityClass,
|
|
337
509
|
networkKey,
|
|
338
510
|
});
|
|
339
|
-
await this.
|
|
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(
|
|
515
|
+
const cc = new Security2CCNetworkKeyVerify({
|
|
344
516
|
nodeId: this.endpoint.nodeId,
|
|
345
|
-
|
|
517
|
+
endpointIndex: this.endpoint.index,
|
|
346
518
|
});
|
|
347
|
-
await this.
|
|
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(
|
|
523
|
+
const cc = new Security2CCTransferEnd({
|
|
352
524
|
nodeId: this.endpoint.nodeId,
|
|
353
|
-
|
|
525
|
+
endpointIndex: this.endpoint.index,
|
|
354
526
|
keyVerified: true,
|
|
355
527
|
keyRequestComplete: false,
|
|
356
528
|
});
|
|
357
|
-
await this.
|
|
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(
|
|
537
|
+
const cc = new Security2CCTransferEnd({
|
|
366
538
|
nodeId: this.endpoint.nodeId,
|
|
367
|
-
|
|
539
|
+
endpointIndex: this.endpoint.index,
|
|
368
540
|
keyVerified: false,
|
|
369
541
|
keyRequestComplete: true,
|
|
370
542
|
});
|
|
371
|
-
await this.
|
|
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(
|
|
380
|
-
const
|
|
381
|
-
const
|
|
382
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
this.
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
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
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
if
|
|
764
|
-
|
|
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
|
-
|
|
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
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
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
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
const
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
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
|
-
|
|
934
|
+
return {
|
|
935
|
+
isMulticast: true,
|
|
936
|
+
groupId: multicastGroupId,
|
|
937
|
+
};
|
|
801
938
|
}
|
|
802
939
|
else {
|
|
803
|
-
|
|
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
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
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
|
-
|
|
847
|
-
|
|
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
|
-
|
|
859
|
-
|
|
860
|
-
|
|
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
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
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
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
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
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
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
|
-
|
|
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
|
-
|
|
899
|
-
|
|
900
|
-
|
|
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
|
-
|
|
903
|
-
|
|
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
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
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
|
-
|
|
926
|
-
|
|
927
|
-
|
|
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.
|
|
936
|
-
.nextSequenceNumber(this.nodeId);
|
|
1100
|
+
this.sequenceNumber = securityManager.nextSequenceNumber(this.nodeId);
|
|
937
1101
|
}
|
|
938
1102
|
else {
|
|
939
|
-
const groupId =
|
|
940
|
-
|
|
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.
|
|
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
|
-
|
|
985
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
&&
|
|
1006
|
-
|
|
1144
|
+
&& securityManager.tempKeys.has(receiverNodeId)) {
|
|
1145
|
+
securityManager.initializeTempSPAN(receiverNodeId, senderEI, receiverEI);
|
|
1007
1146
|
}
|
|
1008
1147
|
else {
|
|
1009
1148
|
const securityClass = this.securityClass
|
|
1010
|
-
??
|
|
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
|
-
|
|
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()
|
|
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 =
|
|
1188
|
+
const destinationTag = getDestinationIDTX.call(this);
|
|
1047
1189
|
const messageLength = this.computeEncapsulationOverhead()
|
|
1048
1190
|
+ serializedCC.length;
|
|
1049
|
-
const authData = getAuthenticationData(
|
|
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 =
|
|
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
|
-
?
|
|
1061
|
-
:
|
|
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 =
|
|
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(
|
|
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.
|
|
1130
|
-
|
|
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(
|
|
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(
|
|
1286
|
-
super(
|
|
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
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
(0, core_1.validatePayload)(
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
1365
|
-
super(
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
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
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
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(
|
|
1400
|
+
toLogEntry(ctx) {
|
|
1399
1401
|
return {
|
|
1400
|
-
...super.toLogEntry(
|
|
1401
|
-
message: {
|
|
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(
|
|
1412
|
-
super(
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
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(
|
|
1464
|
+
toLogEntry(ctx) {
|
|
1454
1465
|
return {
|
|
1455
|
-
...super.toLogEntry(
|
|
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(
|
|
1504
|
-
super(
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
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(
|
|
1566
|
+
toLogEntry(ctx) {
|
|
1549
1567
|
return {
|
|
1550
|
-
...super.toLogEntry(
|
|
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(
|
|
1570
|
-
super(
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
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(
|
|
1604
|
+
toLogEntry(ctx) {
|
|
1585
1605
|
return {
|
|
1586
|
-
...super.toLogEntry(
|
|
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(
|
|
1597
|
-
super(
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
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(
|
|
1640
|
+
toLogEntry(ctx) {
|
|
1618
1641
|
return {
|
|
1619
|
-
...super.toLogEntry(
|
|
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(
|
|
1633
|
-
super(
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
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(
|
|
1679
|
+
toLogEntry(ctx) {
|
|
1654
1680
|
return {
|
|
1655
|
-
...super.toLogEntry(
|
|
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(
|
|
1670
|
-
super(
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
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(
|
|
1712
|
+
toLogEntry(ctx) {
|
|
1685
1713
|
return {
|
|
1686
|
-
...super.toLogEntry(
|
|
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(
|
|
1707
|
-
super(
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
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(
|
|
1757
|
+
toLogEntry(ctx) {
|
|
1727
1758
|
return {
|
|
1728
|
-
...super.toLogEntry(
|
|
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(
|
|
1742
|
-
super(
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
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(
|
|
1793
|
+
toLogEntry(ctx) {
|
|
1761
1794
|
return {
|
|
1762
|
-
...super.toLogEntry(
|
|
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 {
|