@zwave-js/cc 15.31.2 → 15.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/build/cjs/cc/AlarmSensorCC.js +2 -3
  2. package/build/cjs/cc/AlarmSensorCC.js.map +2 -2
  3. package/build/cjs/cc/AssociationGroupInfoCC.js +5 -2
  4. package/build/cjs/cc/AssociationGroupInfoCC.js.map +2 -2
  5. package/build/cjs/cc/ColorSwitchCC.js +2 -3
  6. package/build/cjs/cc/ColorSwitchCC.js.map +2 -2
  7. package/build/cjs/cc/ConfigurationCC.d.ts +2 -0
  8. package/build/cjs/cc/ConfigurationCC.js +28 -8
  9. package/build/cjs/cc/ConfigurationCC.js.map +2 -2
  10. package/build/cjs/cc/EntryControlCC.js +3 -2
  11. package/build/cjs/cc/EntryControlCC.js.map +2 -2
  12. package/build/cjs/cc/IndicatorCC.js +3 -3
  13. package/build/cjs/cc/IndicatorCC.js.map +2 -2
  14. package/build/cjs/cc/IrrigationCC.js.map +1 -1
  15. package/build/cjs/cc/LanguageCC.js +6 -2
  16. package/build/cjs/cc/LanguageCC.js.map +2 -2
  17. package/build/cjs/cc/ManufacturerSpecificCC.js +1 -1
  18. package/build/cjs/cc/ManufacturerSpecificCC.js.map +2 -2
  19. package/build/cjs/cc/MeterCC.js +25 -4
  20. package/build/cjs/cc/MeterCC.js.map +2 -2
  21. package/build/cjs/cc/NodeNamingCC.js +2 -2
  22. package/build/cjs/cc/NodeNamingCC.js.map +2 -2
  23. package/build/cjs/cc/NotificationCC.js +10 -11
  24. package/build/cjs/cc/NotificationCC.js.map +2 -2
  25. package/build/cjs/cc/Security2CC.js +11 -6
  26. package/build/cjs/cc/Security2CC.js.map +2 -2
  27. package/build/cjs/cc/SecurityCC.js +2 -1
  28. package/build/cjs/cc/SecurityCC.js.map +2 -2
  29. package/build/cjs/cc/SoundSwitchCC.js +1 -1
  30. package/build/cjs/cc/SoundSwitchCC.js.map +2 -2
  31. package/build/cjs/cc/ThermostatSetpointCC.js +2 -1
  32. package/build/cjs/cc/ThermostatSetpointCC.js.map +2 -2
  33. package/build/cjs/cc/UserCredentialCC.d.ts +6 -0
  34. package/build/cjs/cc/UserCredentialCC.js +81 -6
  35. package/build/cjs/cc/UserCredentialCC.js.map +2 -2
  36. package/build/cjs/lib/Security2/Extension.js +2 -1
  37. package/build/cjs/lib/Security2/Extension.js.map +2 -2
  38. package/build/cjs/lib/securityLogging.d.ts +6 -0
  39. package/build/cjs/lib/securityLogging.js +34 -0
  40. package/build/cjs/lib/securityLogging.js.map +7 -0
  41. package/build/cjs/lib/serializers.js +3 -0
  42. package/build/cjs/lib/serializers.js.map +2 -2
  43. package/build/esm/cc/AlarmSensorCC.d.ts.map +1 -1
  44. package/build/esm/cc/AlarmSensorCC.js +5 -3
  45. package/build/esm/cc/AlarmSensorCC.js.map +1 -1
  46. package/build/esm/cc/AssociationGroupInfoCC.d.ts.map +1 -1
  47. package/build/esm/cc/AssociationGroupInfoCC.js +10 -6
  48. package/build/esm/cc/AssociationGroupInfoCC.js.map +1 -1
  49. package/build/esm/cc/ColorSwitchCC.d.ts.map +1 -1
  50. package/build/esm/cc/ColorSwitchCC.js +2 -4
  51. package/build/esm/cc/ColorSwitchCC.js.map +1 -1
  52. package/build/esm/cc/ConfigurationCC.d.ts +2 -0
  53. package/build/esm/cc/ConfigurationCC.d.ts.map +1 -1
  54. package/build/esm/cc/ConfigurationCC.js +34 -13
  55. package/build/esm/cc/ConfigurationCC.js.map +1 -1
  56. package/build/esm/cc/EntryControlCC.d.ts.map +1 -1
  57. package/build/esm/cc/EntryControlCC.js +4 -3
  58. package/build/esm/cc/EntryControlCC.js.map +1 -1
  59. package/build/esm/cc/IndicatorCC.d.ts.map +1 -1
  60. package/build/esm/cc/IndicatorCC.js +4 -6
  61. package/build/esm/cc/IndicatorCC.js.map +1 -1
  62. package/build/esm/cc/IrrigationCC.js.map +1 -1
  63. package/build/esm/cc/LanguageCC.d.ts.map +1 -1
  64. package/build/esm/cc/LanguageCC.js +8 -3
  65. package/build/esm/cc/LanguageCC.js.map +1 -1
  66. package/build/esm/cc/ManufacturerSpecificCC.d.ts.map +1 -1
  67. package/build/esm/cc/ManufacturerSpecificCC.js +2 -2
  68. package/build/esm/cc/ManufacturerSpecificCC.js.map +1 -1
  69. package/build/esm/cc/MeterCC.d.ts.map +1 -1
  70. package/build/esm/cc/MeterCC.js +27 -5
  71. package/build/esm/cc/MeterCC.js.map +1 -1
  72. package/build/esm/cc/NodeNamingCC.d.ts.map +1 -1
  73. package/build/esm/cc/NodeNamingCC.js +3 -3
  74. package/build/esm/cc/NodeNamingCC.js.map +1 -1
  75. package/build/esm/cc/NotificationCC.d.ts.map +1 -1
  76. package/build/esm/cc/NotificationCC.js +22 -24
  77. package/build/esm/cc/NotificationCC.js.map +1 -1
  78. package/build/esm/cc/Security2CC.d.ts.map +1 -1
  79. package/build/esm/cc/Security2CC.js +21 -10
  80. package/build/esm/cc/Security2CC.js.map +1 -1
  81. package/build/esm/cc/SecurityCC.d.ts.map +1 -1
  82. package/build/esm/cc/SecurityCC.js +4 -4
  83. package/build/esm/cc/SecurityCC.js.map +1 -1
  84. package/build/esm/cc/SoundSwitchCC.d.ts.map +1 -1
  85. package/build/esm/cc/SoundSwitchCC.js +2 -2
  86. package/build/esm/cc/SoundSwitchCC.js.map +1 -1
  87. package/build/esm/cc/ThermostatSetpointCC.d.ts.map +1 -1
  88. package/build/esm/cc/ThermostatSetpointCC.js +5 -1
  89. package/build/esm/cc/ThermostatSetpointCC.js.map +1 -1
  90. package/build/esm/cc/UserCredentialCC.d.ts +6 -0
  91. package/build/esm/cc/UserCredentialCC.d.ts.map +1 -1
  92. package/build/esm/cc/UserCredentialCC.js +103 -13
  93. package/build/esm/cc/UserCredentialCC.js.map +1 -1
  94. package/build/esm/lib/Security2/Extension.d.ts.map +1 -1
  95. package/build/esm/lib/Security2/Extension.js +3 -3
  96. package/build/esm/lib/Security2/Extension.js.map +1 -1
  97. package/build/esm/lib/securityLogging.d.ts +6 -0
  98. package/build/esm/lib/securityLogging.d.ts.map +1 -0
  99. package/build/esm/lib/securityLogging.js +10 -0
  100. package/build/esm/lib/securityLogging.js.map +1 -0
  101. package/build/esm/lib/serializers.d.ts.map +1 -1
  102. package/build/esm/lib/serializers.js +4 -0
  103. package/build/esm/lib/serializers.js.map +1 -1
  104. package/package.json +7 -7
@@ -2,7 +2,7 @@ import { __esDecorate, __runInitializers } from "tslib";
2
2
  import * as __validateArgs from "./UserCredentialCC._validateArgs.js";
3
3
  const { validateArgs_UserCredentialCCAPI_setUser, validateArgs_UserCredentialCCAPI_getUser, validateArgs_UserCredentialCCAPI_setCredential, validateArgs_UserCredentialCCAPI_getCredential, validateArgs_UserCredentialCCAPI_startCredentialLearn, validateArgs_UserCredentialCCAPI_setUserCredentialAssociation, validateArgs_UserCredentialCCAPI_getUserChecksum, validateArgs_UserCredentialCCAPI_getCredentialChecksum, validateArgs_UserCredentialCCAPI_setAdminPinCode, validateArgs_UserCredentialCCAPI_setKeyLockerEntry, validateArgs_UserCredentialCCAPI_getKeyLockerEntry, validateArgs_UserCredentialCCAPI_sendUserCapabilitiesReport, validateArgs_UserCredentialCCAPI_sendCredentialCapabilitiesReport, validateArgs_UserCredentialCCAPI_sendKeyLockerCapabilitiesReport, validateArgs_UserCredentialCCAPI_sendUserReport, validateArgs_UserCredentialCCAPI_sendCredentialReport, validateArgs_UserCredentialCCAPI_sendCredentialLearnReport, validateArgs_UserCredentialCCAPI_sendUserCredentialAssociationReport, validateArgs_UserCredentialCCAPI_sendAllUsersChecksumReport, validateArgs_UserCredentialCCAPI_sendUserChecksumReport, validateArgs_UserCredentialCCAPI_sendCredentialChecksumReport, validateArgs_UserCredentialCCAPI_sendAdminPinCodeReport, validateArgs_UserCredentialCCAPI_sendKeyLockerEntryReport } = __validateArgs;
4
4
  import { CommandClasses, MessagePriority, ZWaveError, ZWaveErrorCodes, encodeBitMask, parseBitMask, validatePayload, } from "@zwave-js/core";
5
- import { Bytes, getEnumMemberName, pick, stringToUint8ArrayUTF16BE, uint8ArrayToStringUTF16BE, } from "@zwave-js/shared";
5
+ import { Bytes, stripControlCharacters, getEnumMemberName, pick, stringToUint8ArrayUTF16BE, uint8ArrayToStringUTF16BE, } from "@zwave-js/shared";
6
6
  import { CCAPI, PhysicalCCAPI } from "../lib/API.js";
7
7
  import { CommandClass, getEffectiveCCVersion, } from "../lib/CommandClass.js";
8
8
  import { API, CCCommand, ccValueProperty, ccValues, commandClass, expectedCCResponse, implementedVersion, useSupervision, } from "../lib/CommandClassDecorators.js";
@@ -18,12 +18,30 @@ function credentialToLogString(credential) {
18
18
  * credentials (biometric data, BLE/NFC/RFID identifiers, ...) as raw bytes.
19
19
  */
20
20
  export function normalizeCredentialData(credentialType, credentialData) {
21
- if (credentialType === UserCredentialType.PINCode
22
- || credentialType === UserCredentialType.Password) {
21
+ if (credentialType === UserCredentialType.PINCode) {
23
22
  return credentialData.toString("utf8");
24
23
  }
24
+ if (credentialType === UserCredentialType.Password) {
25
+ // Passwords are UTF-16BE on the wire (CC:0083.01.0A.11.021). Received
26
+ // commands with an odd length are rejected when they are parsed.
27
+ return uint8ArrayToStringUTF16BE(credentialData);
28
+ }
25
29
  return credentialData;
26
30
  }
31
+ /**
32
+ * Converts a credential to the bytes transmitted to the node. Strings are
33
+ * encoded according to the credential type, the inverse of
34
+ * {@link normalizeCredentialData}.
35
+ */
36
+ export function encodeCredentialData(credentialType, credentialData) {
37
+ if (typeof credentialData !== "string")
38
+ return Bytes.from(credentialData);
39
+ if (credentialType === UserCredentialType.Password) {
40
+ // CC:0083.01.0A.11.021
41
+ return Bytes.from(stringToUint8ArrayUTF16BE(credentialData));
42
+ }
43
+ return Bytes.from(credentialData, "utf-8");
44
+ }
27
45
  import { UserCredentialCCValues } from "./_CCValues.generated.js";
28
46
  export { UserCredentialCCValues };
29
47
  // API class
@@ -213,6 +231,20 @@ let UserCredentialCCAPI = (() => {
213
231
  */
214
232
  async setUser(options) {
215
233
  this.assertSupportsCommand(UserCredentialCommand, UserCredentialCommand.UserSet);
234
+ if (options.operationType !== UserCredentialOperationType.Delete
235
+ && options.nameEncoding == undefined) {
236
+ const userName = options.userName ?? "";
237
+ const supportedEncodings = this.tryGetValueDB()?.getValue(UserCredentialCCValues.supportedUserNameEncodings.endpoint(this.endpoint.index)) ?? [
238
+ UserCredentialNameEncoding.ASCII,
239
+ UserCredentialNameEncoding.ExtendedASCII,
240
+ UserCredentialNameEncoding.UTF16BE,
241
+ ];
242
+ const nameEncoding = chooseNameEncoding(userName, supportedEncodings);
243
+ if (nameEncoding == undefined) {
244
+ throw new ZWaveError(`The user name "${userName}" cannot be encoded with any of the encodings supported by the node`, ZWaveErrorCodes.Argument_Invalid);
245
+ }
246
+ options = { ...options, nameEncoding };
247
+ }
216
248
  const cc = new UserCredentialCCUserSet({
217
249
  nodeId: this.endpoint.nodeId,
218
250
  endpointIndex: this.endpoint.index,
@@ -1179,6 +1211,43 @@ function testResponseForUserCredentialUserSet(sent, received) {
1179
1211
  return (received.reportType !== UserCredentialUserReportType.ResponseToGet
1180
1212
  && received.userId === sent.userId);
1181
1213
  }
1214
+ // ASCII and Extended ASCII user names use a single byte per character, so they are
1215
+ // interpreted as ISO-8859-1, whose code points match the byte values
1216
+ function decodeSingleByteName(data) {
1217
+ return String.fromCharCode(...data);
1218
+ }
1219
+ function encodeSingleByteName(name, encoding) {
1220
+ const maxCharCode = encoding === UserCredentialNameEncoding.ExtendedASCII ? 0xff : 0x7f;
1221
+ const ret = new Bytes(name.length);
1222
+ for (let i = 0; i < name.length; i++) {
1223
+ const code = name.charCodeAt(i);
1224
+ if (code > maxCharCode) {
1225
+ throw new ZWaveError(`The user name "${name}" cannot be encoded as ${encoding === UserCredentialNameEncoding.ExtendedASCII
1226
+ ? "Extended ASCII"
1227
+ : "ASCII"}`, ZWaveErrorCodes.Argument_Invalid);
1228
+ }
1229
+ ret[i] = code;
1230
+ }
1231
+ return ret;
1232
+ }
1233
+ /** Returns the most compact encoding out of `supported` that can represent the user name */
1234
+ function chooseNameEncoding(name, supported) {
1235
+ let maxCharCode = 0;
1236
+ for (let i = 0; i < name.length; i++) {
1237
+ maxCharCode = Math.max(maxCharCode, name.charCodeAt(i));
1238
+ }
1239
+ if (maxCharCode <= 0x7f
1240
+ && supported.includes(UserCredentialNameEncoding.ASCII)) {
1241
+ return UserCredentialNameEncoding.ASCII;
1242
+ }
1243
+ else if (maxCharCode <= 0xff
1244
+ && supported.includes(UserCredentialNameEncoding.ExtendedASCII)) {
1245
+ return UserCredentialNameEncoding.ExtendedASCII;
1246
+ }
1247
+ else if (supported.includes(UserCredentialNameEncoding.UTF16BE)) {
1248
+ return UserCredentialNameEncoding.UTF16BE;
1249
+ }
1250
+ }
1182
1251
  let UserCredentialCCUserSet = (() => {
1183
1252
  let _classDecorators = [CCCommand(UserCredentialCommand.UserSet), expectedCCResponse(() => UserCredentialCCUserReport, testResponseForUserCredentialUserSet)];
1184
1253
  let _classDescriptor;
@@ -1243,13 +1312,13 @@ let UserCredentialCCUserSet = (() => {
1243
1312
  validatePayload(raw.payload.length >= 10 + nameLength);
1244
1313
  let userName;
1245
1314
  if (nameEncoding === UserCredentialNameEncoding.UTF16BE) {
1315
+ validatePayload(nameLength % 2 === 0);
1246
1316
  userName = uint8ArrayToStringUTF16BE(raw.payload.subarray(10, 10 + nameLength));
1247
1317
  }
1248
1318
  else {
1249
- userName = raw.payload
1250
- .subarray(10, 10 + nameLength)
1251
- .toString("ascii");
1319
+ userName = decodeSingleByteName(raw.payload.subarray(10, 10 + nameLength));
1252
1320
  }
1321
+ userName = stripControlCharacters(userName);
1253
1322
  if (userType === UserCredentialUserType.Expiring) {
1254
1323
  return new this({
1255
1324
  nodeId: ctx.sourceNodeId,
@@ -1288,6 +1357,9 @@ let UserCredentialCCUserSet = (() => {
1288
1357
  if (this.active == undefined) {
1289
1358
  throw new ZWaveError(`${this.constructor.name}: The active flag must be set for Add/Modify operations`, ZWaveErrorCodes.Argument_Invalid);
1290
1359
  }
1360
+ if (this.nameEncoding == undefined) {
1361
+ throw new ZWaveError(`${this.constructor.name}: name encoding must be set for Add/Modify operations`, ZWaveErrorCodes.Argument_Invalid);
1362
+ }
1291
1363
  // Credential rule defaults to Single
1292
1364
  const credentialRule = this.credentialRule ?? UserCredentialRule.Single;
1293
1365
  // Expiring timeout MUST be 0 for non-Expiring user types
@@ -1301,16 +1373,18 @@ let UserCredentialCCUserSet = (() => {
1301
1373
  else {
1302
1374
  expiringTimeoutMinutes = 0;
1303
1375
  }
1304
- // Name encoding defaults to ASCII
1305
- const nameEncoding = this.nameEncoding ?? UserCredentialNameEncoding.ASCII;
1306
1376
  // User name defaults to empty string
1307
1377
  const userName = this.userName ?? "";
1378
+ const nameEncoding = this.nameEncoding;
1308
1379
  let nameBuffer;
1309
1380
  if (nameEncoding === UserCredentialNameEncoding.UTF16BE) {
1310
1381
  nameBuffer = stringToUint8ArrayUTF16BE(userName);
1311
1382
  }
1383
+ else if (nameEncoding === UserCredentialNameEncoding.ExtendedASCII) {
1384
+ nameBuffer = encodeSingleByteName(userName, UserCredentialNameEncoding.ExtendedASCII);
1385
+ }
1312
1386
  else {
1313
- nameBuffer = Bytes.from(userName, "ascii");
1387
+ nameBuffer = encodeSingleByteName(userName, UserCredentialNameEncoding.ASCII);
1314
1388
  }
1315
1389
  this.payload = Bytes.alloc(10 + nameBuffer.length);
1316
1390
  this.payload[0] = this.operationType & 0b11;
@@ -1403,13 +1477,13 @@ let UserCredentialCCUserReport = (() => {
1403
1477
  validatePayload(raw.payload.length >= 15 + nameLength);
1404
1478
  let userName;
1405
1479
  if (nameEncoding === UserCredentialNameEncoding.UTF16BE) {
1480
+ validatePayload(nameLength % 2 === 0);
1406
1481
  userName = uint8ArrayToStringUTF16BE(raw.payload.subarray(15, 15 + nameLength));
1407
1482
  }
1408
1483
  else {
1409
- userName = raw.payload
1410
- .subarray(15, 15 + nameLength)
1411
- .toString("ascii");
1484
+ userName = decodeSingleByteName(raw.payload.subarray(15, 15 + nameLength));
1412
1485
  }
1486
+ userName = stripControlCharacters(userName);
1413
1487
  const common = {
1414
1488
  nodeId: ctx.sourceNodeId,
1415
1489
  modifierType,
@@ -1443,8 +1517,11 @@ let UserCredentialCCUserReport = (() => {
1443
1517
  if (this.nameEncoding === UserCredentialNameEncoding.UTF16BE) {
1444
1518
  nameBuffer = stringToUint8ArrayUTF16BE(this.userName);
1445
1519
  }
1520
+ else if (this.nameEncoding === UserCredentialNameEncoding.ExtendedASCII) {
1521
+ nameBuffer = encodeSingleByteName(this.userName, UserCredentialNameEncoding.ExtendedASCII);
1522
+ }
1446
1523
  else {
1447
- nameBuffer = Bytes.from(this.userName, "ascii");
1524
+ nameBuffer = encodeSingleByteName(this.userName, UserCredentialNameEncoding.ASCII);
1448
1525
  }
1449
1526
  this.payload = Bytes.alloc(15 + nameBuffer.length);
1450
1527
  this.payload[0] = this.reportType;
@@ -1690,6 +1767,9 @@ let UserCredentialCCCredentialSet = (() => {
1690
1767
  if (raw.payload.length >= 7) {
1691
1768
  const credentialLength = raw.payload[6];
1692
1769
  validatePayload(raw.payload.length >= 7 + credentialLength);
1770
+ // Passwords are UTF-16BE (CC:0083.01.0A.11.021)
1771
+ validatePayload(credentialType !== UserCredentialType.Password
1772
+ || credentialLength % 2 === 0);
1693
1773
  credentialData = Bytes.from(raw.payload.subarray(7, 7 + credentialLength));
1694
1774
  }
1695
1775
  return new this({
@@ -1783,6 +1863,16 @@ let UserCredentialCCCredentialReport = (() => {
1783
1863
  const credentialReadBack = !!(raw.payload[6] & 128);
1784
1864
  const credentialLength = raw.payload[7];
1785
1865
  validatePayload(raw.payload.length >= 8 + credentialLength + 6);
1866
+ if (credentialType === UserCredentialType.Password
1867
+ && credentialLength % 2 !== 0) {
1868
+ // CC:0083.01.0C.11.000: If the credential cannot be read back
1869
+ // (CRB is zero), the node MUST instead report a hash sum of the credential.
1870
+ if (credentialReadBack) {
1871
+ // CC:0083.01.0A.11.021: Passwords MUST be transmitted in Unicode
1872
+ // UTF-16 format, in big endian order.
1873
+ validatePayload.fail("Password credentials must have an even length");
1874
+ }
1875
+ }
1786
1876
  const offset = 8 + credentialLength;
1787
1877
  const modifierType = raw.payload[offset];
1788
1878
  const modifierNodeId = raw.payload.readUInt16BE(offset + 1);