@zwave-js/cc 15.23.0 → 15.23.3

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 (45) hide show
  1. package/build/cjs/cc/DoorLockCC.d.ts +7 -3
  2. package/build/cjs/cc/DoorLockCC.js +164 -15
  3. package/build/cjs/cc/DoorLockCC.js.map +2 -2
  4. package/build/cjs/cc/LockCC.d.ts +2 -1
  5. package/build/cjs/cc/LockCC.js +11 -2
  6. package/build/cjs/cc/LockCC.js.map +2 -2
  7. package/build/cjs/cc/NotificationCC.js +4 -0
  8. package/build/cjs/cc/NotificationCC.js.map +2 -2
  9. package/build/cjs/cc/UserCredentialCC._validateArgs.js +4 -4
  10. package/build/cjs/cc/UserCredentialCC._validateArgs.js.map +2 -2
  11. package/build/cjs/cc/UserCredentialCC.d.ts +75 -37
  12. package/build/cjs/cc/UserCredentialCC.js +203 -156
  13. package/build/cjs/cc/UserCredentialCC.js.map +3 -3
  14. package/build/cjs/cc/_CCValues.generated.d.ts +62 -6
  15. package/build/cjs/cc/_CCValues.generated.js +38 -6
  16. package/build/cjs/cc/_CCValues.generated.js.map +2 -2
  17. package/build/cjs/cc/index.d.ts +3 -3
  18. package/build/cjs/cc/index.js +6 -6
  19. package/build/cjs/cc/index.js.map +1 -1
  20. package/build/esm/cc/DoorLockCC.d.ts +7 -3
  21. package/build/esm/cc/DoorLockCC.d.ts.map +1 -1
  22. package/build/esm/cc/DoorLockCC.js +192 -27
  23. package/build/esm/cc/DoorLockCC.js.map +1 -1
  24. package/build/esm/cc/LockCC.d.ts +2 -1
  25. package/build/esm/cc/LockCC.d.ts.map +1 -1
  26. package/build/esm/cc/LockCC.js +12 -7
  27. package/build/esm/cc/LockCC.js.map +1 -1
  28. package/build/esm/cc/NotificationCC.d.ts.map +1 -1
  29. package/build/esm/cc/NotificationCC.js +6 -1
  30. package/build/esm/cc/NotificationCC.js.map +1 -1
  31. package/build/esm/cc/UserCredentialCC._validateArgs.js +4 -4
  32. package/build/esm/cc/UserCredentialCC._validateArgs.js.map +1 -1
  33. package/build/esm/cc/UserCredentialCC.d.ts +75 -37
  34. package/build/esm/cc/UserCredentialCC.d.ts.map +1 -1
  35. package/build/esm/cc/UserCredentialCC.js +237 -152
  36. package/build/esm/cc/UserCredentialCC.js.map +1 -1
  37. package/build/esm/cc/_CCValues.generated.d.ts +62 -6
  38. package/build/esm/cc/_CCValues.generated.d.ts.map +1 -1
  39. package/build/esm/cc/_CCValues.generated.js +41 -6
  40. package/build/esm/cc/_CCValues.generated.js.map +1 -1
  41. package/build/esm/cc/index.d.ts +3 -3
  42. package/build/esm/cc/index.d.ts.map +1 -1
  43. package/build/esm/cc/index.js +4 -4
  44. package/build/esm/cc/index.js.map +1 -1
  45. package/package.json +4 -4
@@ -12,6 +12,18 @@ function credentialToLogString(credential) {
12
12
  return "(empty)";
13
13
  return "*".repeat(credential.length);
14
14
  }
15
+ /**
16
+ * Returns the canonical form of a credential for storage and reporting:
17
+ * text credentials (PIN codes, passwords) are returned as strings, binary
18
+ * credentials (biometric data, BLE/NFC/RFID identifiers, ...) as raw bytes.
19
+ */
20
+ export function normalizeCredentialData(credentialType, credentialData) {
21
+ if (credentialType === UserCredentialType.PINCode
22
+ || credentialType === UserCredentialType.Password) {
23
+ return credentialData.toString("utf8");
24
+ }
25
+ return credentialData;
26
+ }
15
27
  import { UserCredentialCCValues } from "./_CCValues.generated.js";
16
28
  export { UserCredentialCCValues };
17
29
  // API class
@@ -196,6 +208,10 @@ let UserCredentialCCAPI = (() => {
196
208
  return response?.keyLockerCapabilities;
197
209
  }
198
210
  // User Management
211
+ /**
212
+ * Applications should not use this method directly. Prefer the
213
+ * `endpoint.accessControl` API for managing users.
214
+ */
199
215
  async setUser(options) {
200
216
  this.assertSupportsCommand(UserCredentialCommand, UserCredentialCommand.UserSet);
201
217
  const cc = new UserCredentialCCUserSet({
@@ -205,6 +221,10 @@ let UserCredentialCCAPI = (() => {
205
221
  });
206
222
  return this.host.sendCommand(cc, this.commandOptions);
207
223
  }
224
+ /**
225
+ * Applications should not use this method directly. Prefer the
226
+ * `endpoint.accessControl` API for querying users.
227
+ */
208
228
  async getUser(userId) {
209
229
  this.assertSupportsCommand(UserCredentialCommand, UserCredentialCommand.UserGet);
210
230
  const cc = new UserCredentialCCUserGet({
@@ -212,23 +232,13 @@ let UserCredentialCCAPI = (() => {
212
232
  endpointIndex: this.endpoint.index,
213
233
  userId,
214
234
  });
215
- const response = await this.host.sendCommand(cc, this.commandOptions);
216
- if (response) {
217
- return pick(response, [
218
- "nextUserId",
219
- "modifierType",
220
- "modifierNodeId",
221
- "userId",
222
- "userType",
223
- "active",
224
- "credentialRule",
225
- "expiringTimeoutMinutes",
226
- "nameEncoding",
227
- "userName",
228
- ]);
229
- }
235
+ return this.host.sendCommand(cc, this.commandOptions);
230
236
  }
231
237
  // Credential Management
238
+ /**
239
+ * Applications should not use this method directly. Prefer the
240
+ * `endpoint.accessControl` API for managing credentials.
241
+ */
232
242
  async setCredential(options) {
233
243
  this.assertSupportsCommand(UserCredentialCommand, UserCredentialCommand.CredentialSet);
234
244
  const cc = new UserCredentialCCCredentialSet({
@@ -238,6 +248,10 @@ let UserCredentialCCAPI = (() => {
238
248
  });
239
249
  return this.host.sendCommand(cc, this.commandOptions);
240
250
  }
251
+ /**
252
+ * Applications should not use this method directly. Prefer the
253
+ * `endpoint.accessControl` API for querying credentials.
254
+ */
241
255
  async getCredential(userId, credentialType, credentialSlot) {
242
256
  this.assertSupportsCommand(UserCredentialCommand, UserCredentialCommand.CredentialGet);
243
257
  const cc = new UserCredentialCCCredentialGet({
@@ -247,24 +261,7 @@ let UserCredentialCCAPI = (() => {
247
261
  credentialType,
248
262
  credentialSlot,
249
263
  });
250
- const response = await this.host.sendCommand(cc, this.commandOptions);
251
- if (response) {
252
- return pick(response, [
253
- // UserId, credential type and slot can be zero in the
254
- // request to return the first credential, so we cannot
255
- // simply omit them here
256
- "userId",
257
- "credentialType",
258
- "credentialSlot",
259
- "credentialReadBack",
260
- "credentialLength",
261
- "credentialData",
262
- "modifierType",
263
- "modifierNodeId",
264
- "nextCredentialType",
265
- "nextCredentialSlot",
266
- ]);
267
- }
264
+ return this.host.sendCommand(cc, this.commandOptions);
268
265
  }
269
266
  async startCredentialLearn(options) {
270
267
  this.assertSupportsCommand(UserCredentialCommand, UserCredentialCommand.CredentialLearnStart);
@@ -289,9 +286,13 @@ let UserCredentialCCAPI = (() => {
289
286
  });
290
287
  return this.host.sendCommand(cc, this.commandOptions);
291
288
  }
289
+ /**
290
+ * Applications should not use this method directly. Prefer the
291
+ * `endpoint.accessControl` API for reassigning credentials between users.
292
+ */
292
293
  async setUserCredentialAssociation(options) {
293
294
  this.assertSupportsCommand(UserCredentialCommand, UserCredentialCommand.UserCredentialAssociationSet);
294
- const cc = new UserCredentialCCUserCredentialAssociationSet({
295
+ const cc = new UserCredentialCCAssociationSet({
295
296
  nodeId: this.endpoint.nodeId,
296
297
  endpointIndex: this.endpoint.index,
297
298
  ...options,
@@ -437,7 +438,7 @@ let UserCredentialCCAPI = (() => {
437
438
  }
438
439
  async sendUserCredentialAssociationReport(options) {
439
440
  this.assertSupportsCommand(UserCredentialCommand, UserCredentialCommand.UserCredentialAssociationReport);
440
- const cc = new UserCredentialCCUserCredentialAssociationReport({
441
+ const cc = new UserCredentialCCAssociationReport({
441
442
  nodeId: this.endpoint.nodeId,
442
443
  endpointIndex: this.endpoint.index,
443
444
  ...options,
@@ -629,7 +630,7 @@ let UserCredentialCC = (() => {
629
630
  }
630
631
  }
631
632
  previousUserId = currentUserId;
632
- nextUserId = user.nextUserId;
633
+ nextUserId = user.nextUserId ?? 0;
633
634
  } while (nextUserId > 0);
634
635
  }
635
636
  // After querying all users, save the checksum for the next refresh.
@@ -674,18 +675,18 @@ let UserCredentialCC = (() => {
674
675
  this.removeValue(ctx, UserCredentialCCValues.userChecksum(userId));
675
676
  // Remove all credential values for this user
676
677
  const valueDB = this.getValueDB(ctx);
677
- const credentialValues = valueDB.findValues((vid) => (UserCredentialCCValues.credential.is(vid)
678
- || UserCredentialCCValues.credentialModifierType.is(vid)
679
- || UserCredentialCCValues.credentialModifierNodeId.is(vid))
680
- // The property key is constructed as (userId << 24) | (type << 16) | slot, so we can filter by userId
681
- && (vid.propertyKey >> 24) === userId);
682
- for (const { commandClass, endpoint, property, propertyKey } of credentialValues) {
683
- valueDB.removeValue({
684
- commandClass,
685
- endpoint,
686
- property,
687
- propertyKey,
688
- });
678
+ const credentialOwners = valueDB.findValues((vid) => UserCredentialCCValues.credentialOwner.is(vid)
679
+ && vid.endpoint === this.endpointIndex);
680
+ for (const { endpoint, propertyKey, value } of credentialOwners) {
681
+ if (value !== userId)
682
+ continue;
683
+ const key = propertyKey;
684
+ const credentialType = key >>> 16;
685
+ const credentialSlot = key & 0xffff;
686
+ valueDB.removeValue(UserCredentialCCValues.credential(credentialType, credentialSlot).endpoint(endpoint));
687
+ valueDB.removeValue(UserCredentialCCValues.credentialOwner(credentialType, credentialSlot).endpoint(endpoint));
688
+ valueDB.removeValue(UserCredentialCCValues.credentialModifierType(credentialType, credentialSlot).endpoint(endpoint));
689
+ valueDB.removeValue(UserCredentialCCValues.credentialModifierNodeId(credentialType, credentialSlot).endpoint(endpoint));
689
690
  }
690
691
  }
691
692
  async queryCredentialsForUser(ctx, api, nodeId, userId) {
@@ -701,8 +702,8 @@ let UserCredentialCC = (() => {
701
702
  const cred = await api.getCredential(userId, nextCredType, nextCredSlot);
702
703
  if (!cred?.credentialSlot)
703
704
  break; // No credential found, stop iterating
704
- nextCredType = cred.nextCredentialType;
705
- nextCredSlot = cred.nextCredentialSlot;
705
+ nextCredType = cred.nextCredentialType ?? UserCredentialType.None;
706
+ nextCredSlot = cred.nextCredentialSlot ?? 0;
706
707
  } while (nextCredType !== UserCredentialType.None || nextCredSlot !== 0);
707
708
  }
708
709
  static getSupportedUsersCached(ctx, endpoint) {
@@ -1139,8 +1140,17 @@ let UserCredentialCCKeyLockerCapabilitiesGet = (() => {
1139
1140
  return UserCredentialCCKeyLockerCapabilitiesGet = _classThis;
1140
1141
  })();
1141
1142
  export { UserCredentialCCKeyLockerCapabilitiesGet };
1143
+ function testResponseForUserCredentialUserSet(sent, received) {
1144
+ // CC:0083.01.05.11.010, CC:0083.01.05.11.011, CC:0083.01.05.11.012,
1145
+ // CC:0083.01.05.11.013: UserSet MUST be answered by a UserReport.
1146
+ // Match any report type other than ResponseToGet. For Add/Modify/Delete
1147
+ // the report echoes the requested userId; for DeleteAll (userId 0) the
1148
+ // report contains userId 0.
1149
+ return (received.reportType !== UserCredentialUserReportType.ResponseToGet
1150
+ && received.userId === sent.userId);
1151
+ }
1142
1152
  let UserCredentialCCUserSet = (() => {
1143
- let _classDecorators = [CCCommand(UserCredentialCommand.UserSet), useSupervision()];
1153
+ let _classDecorators = [CCCommand(UserCredentialCommand.UserSet), expectedCCResponse(() => UserCredentialCCUserReport, testResponseForUserCredentialUserSet)];
1144
1154
  let _classDescriptor;
1145
1155
  let _classExtraInitializers = [];
1146
1156
  let _classThis;
@@ -1335,7 +1345,9 @@ let UserCredentialCCUserReport = (() => {
1335
1345
  constructor(options) {
1336
1346
  super(options);
1337
1347
  this.reportType = options.reportType;
1338
- this.nextUserId = options.nextUserId;
1348
+ if (options.reportType === UserCredentialUserReportType.ResponseToGet) {
1349
+ this.nextUserId = options.nextUserId;
1350
+ }
1339
1351
  this.modifierType = options.modifierType;
1340
1352
  this.modifierNodeId = options.modifierNodeId;
1341
1353
  this.userId = options.userId;
@@ -1371,10 +1383,8 @@ let UserCredentialCCUserReport = (() => {
1371
1383
  .subarray(15, 15 + nameLength)
1372
1384
  .toString("ascii");
1373
1385
  }
1374
- return new this({
1386
+ const common = {
1375
1387
  nodeId: ctx.sourceNodeId,
1376
- reportType,
1377
- nextUserId,
1378
1388
  modifierType,
1379
1389
  modifierNodeId,
1380
1390
  userId,
@@ -1384,7 +1394,11 @@ let UserCredentialCCUserReport = (() => {
1384
1394
  expiringTimeoutMinutes,
1385
1395
  nameEncoding,
1386
1396
  userName,
1387
- });
1397
+ };
1398
+ if (reportType === UserCredentialUserReportType.ResponseToGet) {
1399
+ return new this({ ...common, reportType, nextUserId });
1400
+ }
1401
+ return new this({ ...common, reportType });
1388
1402
  }
1389
1403
  reportType;
1390
1404
  nextUserId;
@@ -1407,7 +1421,7 @@ let UserCredentialCCUserReport = (() => {
1407
1421
  }
1408
1422
  this.payload = Bytes.alloc(15 + nameBuffer.length);
1409
1423
  this.payload[0] = this.reportType;
1410
- this.payload.writeUInt16BE(this.nextUserId, 1);
1424
+ this.payload.writeUInt16BE(this.nextUserId ?? 0, 1);
1411
1425
  this.payload[3] = this.modifierType;
1412
1426
  this.payload.writeUInt16BE(this.modifierNodeId, 4);
1413
1427
  this.payload.writeUInt16BE(this.userId, 6);
@@ -1427,9 +1441,12 @@ let UserCredentialCCUserReport = (() => {
1427
1441
  return false;
1428
1442
  if (this.userId === 0)
1429
1443
  return true;
1430
- // Only persist data for successful responses
1444
+ // A UserModifyRejectedLocationEmpty report means we have a stale cache
1445
+ // entry for a user that no longer exists on the device.
1431
1446
  if (this.reportType
1432
- === UserCredentialUserReportType.UserDeleted) {
1447
+ === UserCredentialUserReportType.UserDeleted
1448
+ || this.reportType
1449
+ === UserCredentialUserReportType.UserModifyRejectedLocationEmpty) {
1433
1450
  // Remove all values for this user
1434
1451
  const userId = this.userId;
1435
1452
  this.removeValue(ctx, UserCredentialCCValues.userType(userId));
@@ -1441,12 +1458,16 @@ let UserCredentialCCUserReport = (() => {
1441
1458
  this.removeValue(ctx, UserCredentialCCValues.userModifierNodeId(userId));
1442
1459
  return true;
1443
1460
  }
1461
+ // A UserAddRejectedLocationOccupied report contains the data for the
1462
+ // existing user, so we can update our cache with the actual state.
1444
1463
  if (this.reportType
1445
1464
  !== UserCredentialUserReportType.ResponseToGet
1446
1465
  && this.reportType
1447
1466
  !== UserCredentialUserReportType.UserAdded
1448
1467
  && this.reportType
1449
- !== UserCredentialUserReportType.UserModified) {
1468
+ !== UserCredentialUserReportType.UserModified
1469
+ && this.reportType
1470
+ !== UserCredentialUserReportType.UserAddRejectedLocationOccupied) {
1450
1471
  return true;
1451
1472
  }
1452
1473
  const userId = this.userId;
@@ -1465,22 +1486,22 @@ let UserCredentialCCUserReport = (() => {
1465
1486
  return true;
1466
1487
  }
1467
1488
  toLogEntry(ctx) {
1468
- return {
1469
- ...super.toLogEntry(ctx),
1470
- message: {
1471
- "report type": getEnumMemberName(UserCredentialUserReportType, this.reportType),
1472
- "next user ID": this.nextUserId,
1473
- "modifier type": getEnumMemberName(UserCredentialModifierType, this.modifierType),
1474
- "modifier node id": this.modifierNodeId,
1475
- "user ID": this.userId,
1476
- "user type": getEnumMemberName(UserCredentialUserType, this.userType),
1477
- active: this.active,
1478
- "credential rule": getEnumMemberName(UserCredentialRule, this.credentialRule),
1479
- "expiring timeout (minutes)": this.expiringTimeoutMinutes,
1480
- "name encoding": getEnumMemberName(UserCredentialNameEncoding, this.nameEncoding),
1481
- "user name": this.userName,
1482
- },
1489
+ const message = {
1490
+ "report type": getEnumMemberName(UserCredentialUserReportType, this.reportType),
1483
1491
  };
1492
+ if (this.nextUserId != undefined) {
1493
+ message["next user ID"] = this.nextUserId;
1494
+ }
1495
+ message["modifier type"] = getEnumMemberName(UserCredentialModifierType, this.modifierType);
1496
+ message["modifier node id"] = this.modifierNodeId;
1497
+ message["user ID"] = this.userId;
1498
+ message["user type"] = getEnumMemberName(UserCredentialUserType, this.userType);
1499
+ message.active = this.active;
1500
+ message["credential rule"] = getEnumMemberName(UserCredentialRule, this.credentialRule);
1501
+ message["expiring timeout (minutes)"] = this.expiringTimeoutMinutes;
1502
+ message["name encoding"] = getEnumMemberName(UserCredentialNameEncoding, this.nameEncoding);
1503
+ message["user name"] = this.userName;
1504
+ return { ...super.toLogEntry(ctx), message };
1484
1505
  }
1485
1506
  };
1486
1507
  return UserCredentialCCUserReport = _classThis;
@@ -1535,8 +1556,20 @@ let UserCredentialCCUserGet = (() => {
1535
1556
  return UserCredentialCCUserGet = _classThis;
1536
1557
  })();
1537
1558
  export { UserCredentialCCUserGet };
1559
+ function testResponseForUserCredentialCredentialSet(sent, received) {
1560
+ // CC:0083.01.0A.11.010, CC:0083.01.0A.11.011, CC:0083.01.0A.11.012,
1561
+ // CC:0083.01.0A.11.013: CredentialSet MUST be answered by a
1562
+ // CredentialReport. We correlate responses on the targeted (type, slot)
1563
+ // but not on userId, because rejection reports like DuplicateCredential
1564
+ // or WrongUserUniqueIdentifier may legitimately reference a different
1565
+ // user at the same credential location.
1566
+ return (received.reportType
1567
+ !== UserCredentialCredentialReportType.ResponseToGet
1568
+ && received.credentialType === sent.credentialType
1569
+ && received.credentialSlot === sent.credentialSlot);
1570
+ }
1538
1571
  let UserCredentialCCCredentialSet = (() => {
1539
- let _classDecorators = [CCCommand(UserCredentialCommand.CredentialSet), useSupervision()];
1572
+ let _classDecorators = [CCCommand(UserCredentialCommand.CredentialSet), expectedCCResponse(() => UserCredentialCCCredentialReport, testResponseForUserCredentialCredentialSet)];
1540
1573
  let _classDescriptor;
1541
1574
  let _classExtraInitializers = [];
1542
1575
  let _classThis;
@@ -1658,12 +1691,16 @@ let UserCredentialCCCredentialReport = (() => {
1658
1691
  this.credentialType = options.credentialType;
1659
1692
  this.credentialSlot = options.credentialSlot;
1660
1693
  this.credentialReadBack = options.credentialReadBack;
1661
- this.credentialLength = options.credentialLength;
1662
- this.credentialData = options.credentialData;
1694
+ if (options.credentialReadBack) {
1695
+ this.credentialData = options.credentialData;
1696
+ }
1663
1697
  this.modifierType = options.modifierType;
1664
1698
  this.modifierNodeId = options.modifierNodeId;
1665
- this.nextCredentialType = options.nextCredentialType;
1666
- this.nextCredentialSlot = options.nextCredentialSlot;
1699
+ if (options.reportType
1700
+ === UserCredentialCredentialReportType.ResponseToGet) {
1701
+ this.nextCredentialType = options.nextCredentialType;
1702
+ this.nextCredentialSlot = options.nextCredentialSlot;
1703
+ }
1667
1704
  }
1668
1705
  static from(raw, ctx) {
1669
1706
  validatePayload(raw.payload.length >= 8);
@@ -1674,25 +1711,36 @@ let UserCredentialCCCredentialReport = (() => {
1674
1711
  const credentialReadBack = !!(raw.payload[6] & 128);
1675
1712
  const credentialLength = raw.payload[7];
1676
1713
  validatePayload(raw.payload.length >= 8 + credentialLength + 6);
1677
- const credentialData = Bytes.from(raw.payload.subarray(8, 8 + credentialLength));
1678
- let offset = 8 + credentialLength;
1714
+ const offset = 8 + credentialLength;
1679
1715
  const modifierType = raw.payload[offset];
1680
1716
  const modifierNodeId = raw.payload.readUInt16BE(offset + 1);
1681
- const nextCredentialType = raw.payload[offset + 3];
1682
- const nextCredentialSlot = raw.payload.readUInt16BE(offset + 4);
1683
- return new this({
1717
+ const common = {
1684
1718
  nodeId: ctx.sourceNodeId,
1685
- reportType,
1686
1719
  userId,
1687
1720
  credentialType,
1688
1721
  credentialSlot,
1689
- credentialReadBack,
1690
- credentialLength,
1691
- credentialData,
1692
1722
  modifierType,
1693
1723
  modifierNodeId,
1694
- nextCredentialType,
1695
- nextCredentialSlot,
1724
+ };
1725
+ const dataOptions = credentialReadBack
1726
+ ? {
1727
+ credentialReadBack: true,
1728
+ credentialData: Bytes.from(raw.payload.subarray(8, 8 + credentialLength)),
1729
+ }
1730
+ : { credentialReadBack: false };
1731
+ if (reportType === UserCredentialCredentialReportType.ResponseToGet) {
1732
+ return new this({
1733
+ ...common,
1734
+ ...dataOptions,
1735
+ reportType,
1736
+ nextCredentialType: raw.payload[offset + 3],
1737
+ nextCredentialSlot: raw.payload.readUInt16BE(offset + 4),
1738
+ });
1739
+ }
1740
+ return new this({
1741
+ ...common,
1742
+ ...dataOptions,
1743
+ reportType,
1696
1744
  });
1697
1745
  }
1698
1746
  reportType;
@@ -1700,28 +1748,29 @@ let UserCredentialCCCredentialReport = (() => {
1700
1748
  credentialType;
1701
1749
  credentialSlot;
1702
1750
  credentialReadBack;
1703
- credentialLength;
1704
1751
  credentialData;
1705
1752
  modifierType;
1706
1753
  modifierNodeId;
1707
1754
  nextCredentialType;
1708
1755
  nextCredentialSlot;
1709
1756
  serialize(ctx) {
1710
- this.payload = Bytes.alloc(14 + this.credentialData.length);
1757
+ const credentialData = this.credentialData ?? new Bytes();
1758
+ this.payload = Bytes.alloc(14 + credentialData.length);
1711
1759
  this.payload[0] = this.reportType;
1712
1760
  this.payload.writeUInt16BE(this.userId, 1);
1713
1761
  this.payload[3] = this.credentialType;
1714
1762
  this.payload.writeUInt16BE(this.credentialSlot, 4);
1715
1763
  this.payload[6] = this.credentialReadBack ? 0x80 : 0x00;
1716
- this.payload[7] = this.credentialData.length;
1717
- if (this.credentialData.length > 0) {
1718
- this.payload.set(this.credentialData, 8);
1764
+ this.payload[7] = credentialData.length;
1765
+ if (credentialData.length > 0) {
1766
+ this.payload.set(credentialData, 8);
1719
1767
  }
1720
- let offset = 8 + this.credentialData.length;
1768
+ const offset = 8 + credentialData.length;
1721
1769
  this.payload[offset] = this.modifierType;
1722
1770
  this.payload.writeUInt16BE(this.modifierNodeId, offset + 1);
1723
- this.payload[offset + 3] = this.nextCredentialType;
1724
- this.payload.writeUInt16BE(this.nextCredentialSlot, offset + 4);
1771
+ this.payload[offset + 3] = this.nextCredentialType
1772
+ ?? UserCredentialType.None;
1773
+ this.payload.writeUInt16BE(this.nextCredentialSlot ?? 0, offset + 4);
1725
1774
  return super.serialize(ctx);
1726
1775
  }
1727
1776
  persistValues(ctx) {
@@ -1731,43 +1780,64 @@ let UserCredentialCCCredentialReport = (() => {
1731
1780
  || this.credentialSlot === 0) {
1732
1781
  return true;
1733
1782
  }
1783
+ // A CredentialModifyRejectedLocationEmpty report means we have a stale
1784
+ // cache entry for a credential that no longer exists on the device.
1734
1785
  if (this.reportType
1735
- === UserCredentialCredentialReportType.CredentialDeleted) {
1736
- this.removeValue(ctx, UserCredentialCCValues.credential(this.userId, this.credentialType, this.credentialSlot));
1737
- this.removeValue(ctx, UserCredentialCCValues.credentialModifierType(this.userId, this.credentialType, this.credentialSlot));
1738
- this.removeValue(ctx, UserCredentialCCValues.credentialModifierNodeId(this.userId, this.credentialType, this.credentialSlot));
1786
+ === UserCredentialCredentialReportType.CredentialDeleted
1787
+ || this.reportType
1788
+ === UserCredentialCredentialReportType
1789
+ .CredentialModifyRejectedLocationEmpty) {
1790
+ this.removeValue(ctx, UserCredentialCCValues.credential(this.credentialType, this.credentialSlot));
1791
+ this.removeValue(ctx, UserCredentialCCValues.credentialOwner(this.credentialType, this.credentialSlot));
1792
+ this.removeValue(ctx, UserCredentialCCValues.credentialModifierType(this.credentialType, this.credentialSlot));
1793
+ this.removeValue(ctx, UserCredentialCCValues.credentialModifierNodeId(this.credentialType, this.credentialSlot));
1739
1794
  return true;
1740
1795
  }
1796
+ // A CredentialAddRejectedLocationOccupied report includes the actual
1797
+ // credential data in the read-back field when the device chooses to
1798
+ // reveal it, so we can update our cache with the real state.
1799
+ const isRejectionWithReadback = this.reportType
1800
+ === UserCredentialCredentialReportType
1801
+ .CredentialAddRejectedLocationOccupied
1802
+ && this.credentialReadBack;
1741
1803
  if (this.reportType
1742
1804
  !== UserCredentialCredentialReportType.ResponseToGet
1743
1805
  && this.reportType
1744
1806
  !== UserCredentialCredentialReportType.CredentialAdded
1745
1807
  && this.reportType
1746
- !== UserCredentialCredentialReportType.CredentialModified) {
1808
+ !== UserCredentialCredentialReportType.CredentialModified
1809
+ && !isRejectionWithReadback) {
1747
1810
  return true;
1748
1811
  }
1749
- this.ensureMetadata(ctx, UserCredentialCCValues.credential(this.userId, this.credentialType, this.credentialSlot));
1750
- this.setValue(ctx, UserCredentialCCValues.credential(this.userId, this.credentialType, this.credentialSlot), this.credentialData);
1751
- this.setValue(ctx, UserCredentialCCValues.credentialModifierType(this.userId, this.credentialType, this.credentialSlot), this.modifierType);
1752
- this.setValue(ctx, UserCredentialCCValues.credentialModifierNodeId(this.userId, this.credentialType, this.credentialSlot), this.modifierNodeId);
1812
+ if (this.credentialData != undefined) {
1813
+ this.ensureMetadata(ctx, UserCredentialCCValues.credential(this.credentialType, this.credentialSlot));
1814
+ this.setValue(ctx, UserCredentialCCValues.credential(this.credentialType, this.credentialSlot), normalizeCredentialData(this.credentialType, this.credentialData));
1815
+ }
1816
+ this.setValue(ctx, UserCredentialCCValues.credentialOwner(this.credentialType, this.credentialSlot), this.userId);
1817
+ this.setValue(ctx, UserCredentialCCValues.credentialModifierType(this.credentialType, this.credentialSlot), this.modifierType);
1818
+ this.setValue(ctx, UserCredentialCCValues.credentialModifierNodeId(this.credentialType, this.credentialSlot), this.modifierNodeId);
1753
1819
  return true;
1754
1820
  }
1755
1821
  toLogEntry(ctx) {
1756
- return {
1757
- ...super.toLogEntry(ctx),
1758
- message: {
1759
- "report type": getEnumMemberName(UserCredentialCredentialReportType, this.reportType),
1760
- "user ID": this.userId,
1761
- "credential type": getEnumMemberName(UserCredentialType, this.credentialType),
1762
- "credential slot": this.credentialSlot,
1763
- "credential read-back": this.credentialReadBack,
1764
- "credential data": credentialToLogString(this.credentialData),
1765
- "modifier type": getEnumMemberName(UserCredentialModifierType, this.modifierType),
1766
- "modifier node id": this.modifierNodeId,
1767
- "next credential type": getEnumMemberName(UserCredentialType, this.nextCredentialType),
1768
- "next credential slot": this.nextCredentialSlot,
1769
- },
1822
+ const message = {
1823
+ "report type": getEnumMemberName(UserCredentialCredentialReportType, this.reportType),
1824
+ "user ID": this.userId,
1825
+ "credential type": getEnumMemberName(UserCredentialType, this.credentialType),
1826
+ "credential slot": this.credentialSlot,
1827
+ "credential read-back": this.credentialReadBack,
1770
1828
  };
1829
+ if (this.credentialData != undefined) {
1830
+ message["credential data"] = credentialToLogString(this.credentialData);
1831
+ }
1832
+ message["modifier type"] = getEnumMemberName(UserCredentialModifierType, this.modifierType);
1833
+ message["modifier node id"] = this.modifierNodeId;
1834
+ if (this.nextCredentialType != undefined) {
1835
+ message["next credential type"] = getEnumMemberName(UserCredentialType, this.nextCredentialType);
1836
+ }
1837
+ if (this.nextCredentialSlot != undefined) {
1838
+ message["next credential slot"] = this.nextCredentialSlot;
1839
+ }
1840
+ return { ...super.toLogEntry(ctx), message };
1771
1841
  }
1772
1842
  };
1773
1843
  return UserCredentialCCCredentialReport = _classThis;
@@ -1992,18 +2062,18 @@ let UserCredentialCCCredentialLearnReport = (() => {
1992
2062
  return UserCredentialCCCredentialLearnReport = _classThis;
1993
2063
  })();
1994
2064
  export { UserCredentialCCCredentialLearnReport };
1995
- let UserCredentialCCUserCredentialAssociationSet = (() => {
1996
- let _classDecorators = [CCCommand(UserCredentialCommand.UserCredentialAssociationSet), useSupervision()];
2065
+ let UserCredentialCCAssociationReport = (() => {
2066
+ let _classDecorators = [CCCommand(UserCredentialCommand.UserCredentialAssociationReport)];
1997
2067
  let _classDescriptor;
1998
2068
  let _classExtraInitializers = [];
1999
2069
  let _classThis;
2000
2070
  let _classSuper = UserCredentialCC;
2001
- var UserCredentialCCUserCredentialAssociationSet = class extends _classSuper {
2071
+ var UserCredentialCCAssociationReport = class extends _classSuper {
2002
2072
  static { _classThis = this; }
2003
2073
  static {
2004
2074
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
2005
2075
  __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
2006
- UserCredentialCCUserCredentialAssociationSet = _classThis = _classDescriptor.value;
2076
+ UserCredentialCCAssociationReport = _classThis = _classDescriptor.value;
2007
2077
  if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
2008
2078
  __runInitializers(_classThis, _classExtraInitializers);
2009
2079
  }
@@ -2012,29 +2082,45 @@ let UserCredentialCCUserCredentialAssociationSet = (() => {
2012
2082
  this.credentialType = options.credentialType;
2013
2083
  this.credentialSlot = options.credentialSlot;
2014
2084
  this.destinationUserId = options.destinationUserId;
2085
+ this.status = options.status;
2015
2086
  }
2016
- credentialType;
2017
- credentialSlot;
2018
- destinationUserId;
2019
2087
  static from(raw, ctx) {
2020
- validatePayload(raw.payload.length >= 5);
2088
+ validatePayload(raw.payload.length >= 6);
2021
2089
  const credentialType = raw.payload[0];
2022
2090
  const credentialSlot = raw.payload.readUInt16BE(1);
2023
2091
  const destinationUserId = raw.payload.readUInt16BE(3);
2092
+ const status = raw.payload[5];
2024
2093
  return new this({
2025
2094
  nodeId: ctx.sourceNodeId,
2026
2095
  credentialType,
2027
2096
  credentialSlot,
2028
- destinationUserId,
2097
+ destinationUserId: destinationUserId,
2098
+ status,
2029
2099
  });
2030
2100
  }
2101
+ credentialType;
2102
+ credentialSlot;
2103
+ destinationUserId;
2104
+ status;
2031
2105
  serialize(ctx) {
2032
- this.payload = Bytes.alloc(5);
2106
+ this.payload = Bytes.alloc(6);
2033
2107
  this.payload[0] = this.credentialType;
2034
2108
  this.payload.writeUInt16BE(this.credentialSlot, 1);
2035
2109
  this.payload.writeUInt16BE(this.destinationUserId, 3);
2110
+ this.payload[5] = this.status;
2036
2111
  return super.serialize(ctx);
2037
2112
  }
2113
+ persistValues(ctx) {
2114
+ if (!super.persistValues(ctx))
2115
+ return false;
2116
+ if (this.status !== 0
2117
+ || this.credentialType === UserCredentialType.None
2118
+ || this.credentialSlot === 0) {
2119
+ return true;
2120
+ }
2121
+ this.setValue(ctx, UserCredentialCCValues.credentialOwner(this.credentialType, this.credentialSlot), this.destinationUserId);
2122
+ return true;
2123
+ }
2038
2124
  toLogEntry(ctx) {
2039
2125
  return {
2040
2126
  ...super.toLogEntry(ctx),
@@ -2042,25 +2128,30 @@ let UserCredentialCCUserCredentialAssociationSet = (() => {
2042
2128
  "credential type": getEnumMemberName(UserCredentialType, this.credentialType),
2043
2129
  "credential slot": this.credentialSlot,
2044
2130
  "destination user ID": this.destinationUserId,
2131
+ status: this.status,
2045
2132
  },
2046
2133
  };
2047
2134
  }
2048
2135
  };
2049
- return UserCredentialCCUserCredentialAssociationSet = _classThis;
2136
+ return UserCredentialCCAssociationReport = _classThis;
2050
2137
  })();
2051
- export { UserCredentialCCUserCredentialAssociationSet };
2052
- let UserCredentialCCUserCredentialAssociationReport = (() => {
2053
- let _classDecorators = [CCCommand(UserCredentialCommand.UserCredentialAssociationReport)];
2138
+ export { UserCredentialCCAssociationReport };
2139
+ function testResponseForUserCredentialAssociationSet(sent, received) {
2140
+ return (sent.credentialType === received.credentialType
2141
+ && sent.credentialSlot === received.credentialSlot);
2142
+ }
2143
+ let UserCredentialCCAssociationSet = (() => {
2144
+ let _classDecorators = [CCCommand(UserCredentialCommand.UserCredentialAssociationSet), expectedCCResponse(UserCredentialCCAssociationReport, testResponseForUserCredentialAssociationSet)];
2054
2145
  let _classDescriptor;
2055
2146
  let _classExtraInitializers = [];
2056
2147
  let _classThis;
2057
2148
  let _classSuper = UserCredentialCC;
2058
- var UserCredentialCCUserCredentialAssociationReport = class extends _classSuper {
2149
+ var UserCredentialCCAssociationSet = class extends _classSuper {
2059
2150
  static { _classThis = this; }
2060
2151
  static {
2061
2152
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
2062
2153
  __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
2063
- UserCredentialCCUserCredentialAssociationReport = _classThis = _classDescriptor.value;
2154
+ UserCredentialCCAssociationSet = _classThis = _classDescriptor.value;
2064
2155
  if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
2065
2156
  __runInitializers(_classThis, _classExtraInitializers);
2066
2157
  }
@@ -2069,32 +2160,27 @@ let UserCredentialCCUserCredentialAssociationReport = (() => {
2069
2160
  this.credentialType = options.credentialType;
2070
2161
  this.credentialSlot = options.credentialSlot;
2071
2162
  this.destinationUserId = options.destinationUserId;
2072
- this.status = options.status;
2073
2163
  }
2164
+ credentialType;
2165
+ credentialSlot;
2166
+ destinationUserId;
2074
2167
  static from(raw, ctx) {
2075
- validatePayload(raw.payload.length >= 6);
2168
+ validatePayload(raw.payload.length >= 5);
2076
2169
  const credentialType = raw.payload[0];
2077
2170
  const credentialSlot = raw.payload.readUInt16BE(1);
2078
2171
  const destinationUserId = raw.payload.readUInt16BE(3);
2079
- const status = raw.payload[5];
2080
2172
  return new this({
2081
2173
  nodeId: ctx.sourceNodeId,
2082
2174
  credentialType,
2083
2175
  credentialSlot,
2084
- destinationUserId: destinationUserId,
2085
- status,
2176
+ destinationUserId,
2086
2177
  });
2087
2178
  }
2088
- credentialType;
2089
- credentialSlot;
2090
- destinationUserId;
2091
- status;
2092
2179
  serialize(ctx) {
2093
- this.payload = Bytes.alloc(6);
2180
+ this.payload = Bytes.alloc(5);
2094
2181
  this.payload[0] = this.credentialType;
2095
2182
  this.payload.writeUInt16BE(this.credentialSlot, 1);
2096
2183
  this.payload.writeUInt16BE(this.destinationUserId, 3);
2097
- this.payload[5] = this.status;
2098
2184
  return super.serialize(ctx);
2099
2185
  }
2100
2186
  toLogEntry(ctx) {
@@ -2104,14 +2190,13 @@ let UserCredentialCCUserCredentialAssociationReport = (() => {
2104
2190
  "credential type": getEnumMemberName(UserCredentialType, this.credentialType),
2105
2191
  "credential slot": this.credentialSlot,
2106
2192
  "destination user ID": this.destinationUserId,
2107
- status: this.status,
2108
2193
  },
2109
2194
  };
2110
2195
  }
2111
2196
  };
2112
- return UserCredentialCCUserCredentialAssociationReport = _classThis;
2197
+ return UserCredentialCCAssociationSet = _classThis;
2113
2198
  })();
2114
- export { UserCredentialCCUserCredentialAssociationReport };
2199
+ export { UserCredentialCCAssociationSet };
2115
2200
  let UserCredentialCCAllUsersChecksumReport = (() => {
2116
2201
  let _classDecorators = [CCCommand(UserCredentialCommand.AllUsersChecksumReport)];
2117
2202
  let _classDescriptor;