@zwave-js/cc 15.23.1 → 15.23.5

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 (31) hide show
  1. package/build/cjs/cc/NotificationCC.js +4 -0
  2. package/build/cjs/cc/NotificationCC.js.map +2 -2
  3. package/build/cjs/cc/UserCredentialCC._validateArgs.js +4 -4
  4. package/build/cjs/cc/UserCredentialCC._validateArgs.js.map +2 -2
  5. package/build/cjs/cc/UserCredentialCC.d.ts +75 -37
  6. package/build/cjs/cc/UserCredentialCC.js +212 -156
  7. package/build/cjs/cc/UserCredentialCC.js.map +3 -3
  8. package/build/cjs/cc/_CCValues.generated.d.ts +62 -6
  9. package/build/cjs/cc/_CCValues.generated.js +38 -6
  10. package/build/cjs/cc/_CCValues.generated.js.map +2 -2
  11. package/build/cjs/cc/index.d.ts +3 -3
  12. package/build/cjs/cc/index.js +6 -6
  13. package/build/cjs/cc/index.js.map +1 -1
  14. package/build/esm/cc/NotificationCC.d.ts.map +1 -1
  15. package/build/esm/cc/NotificationCC.js +6 -1
  16. package/build/esm/cc/NotificationCC.js.map +1 -1
  17. package/build/esm/cc/UserCredentialCC._validateArgs.js +4 -4
  18. package/build/esm/cc/UserCredentialCC._validateArgs.js.map +1 -1
  19. package/build/esm/cc/UserCredentialCC.d.ts +75 -37
  20. package/build/esm/cc/UserCredentialCC.d.ts.map +1 -1
  21. package/build/esm/cc/UserCredentialCC.js +247 -152
  22. package/build/esm/cc/UserCredentialCC.js.map +1 -1
  23. package/build/esm/cc/_CCValues.generated.d.ts +62 -6
  24. package/build/esm/cc/_CCValues.generated.d.ts.map +1 -1
  25. package/build/esm/cc/_CCValues.generated.js +41 -6
  26. package/build/esm/cc/_CCValues.generated.js.map +1 -1
  27. package/build/esm/cc/index.d.ts +3 -3
  28. package/build/esm/cc/index.d.ts.map +1 -1
  29. package/build/esm/cc/index.js +4 -4
  30. package/build/esm/cc/index.js.map +1 -1
  31. package/package.json +2 -2
@@ -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,30 @@ 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. The credentialType is always echoed, so we use it
1563
+ // as the primary correlation. DuplicateCredential and DuplicateAdminPINCode
1564
+ // reports reference the EXISTING duplicate's (userId, slot) rather than
1565
+ // the requested location, so we cannot correlate them on slot.
1566
+ if (received.reportType
1567
+ === UserCredentialCredentialReportType.ResponseToGet) {
1568
+ return false;
1569
+ }
1570
+ if (received.credentialType !== sent.credentialType) {
1571
+ return false;
1572
+ }
1573
+ if (received.reportType
1574
+ === UserCredentialCredentialReportType.DuplicateCredential
1575
+ || received.reportType
1576
+ === UserCredentialCredentialReportType.DuplicateAdminPINCode) {
1577
+ return true;
1578
+ }
1579
+ return received.credentialSlot === sent.credentialSlot;
1580
+ }
1538
1581
  let UserCredentialCCCredentialSet = (() => {
1539
- let _classDecorators = [CCCommand(UserCredentialCommand.CredentialSet), useSupervision()];
1582
+ let _classDecorators = [CCCommand(UserCredentialCommand.CredentialSet), expectedCCResponse(() => UserCredentialCCCredentialReport, testResponseForUserCredentialCredentialSet)];
1540
1583
  let _classDescriptor;
1541
1584
  let _classExtraInitializers = [];
1542
1585
  let _classThis;
@@ -1658,12 +1701,16 @@ let UserCredentialCCCredentialReport = (() => {
1658
1701
  this.credentialType = options.credentialType;
1659
1702
  this.credentialSlot = options.credentialSlot;
1660
1703
  this.credentialReadBack = options.credentialReadBack;
1661
- this.credentialLength = options.credentialLength;
1662
- this.credentialData = options.credentialData;
1704
+ if (options.credentialReadBack) {
1705
+ this.credentialData = options.credentialData;
1706
+ }
1663
1707
  this.modifierType = options.modifierType;
1664
1708
  this.modifierNodeId = options.modifierNodeId;
1665
- this.nextCredentialType = options.nextCredentialType;
1666
- this.nextCredentialSlot = options.nextCredentialSlot;
1709
+ if (options.reportType
1710
+ === UserCredentialCredentialReportType.ResponseToGet) {
1711
+ this.nextCredentialType = options.nextCredentialType;
1712
+ this.nextCredentialSlot = options.nextCredentialSlot;
1713
+ }
1667
1714
  }
1668
1715
  static from(raw, ctx) {
1669
1716
  validatePayload(raw.payload.length >= 8);
@@ -1674,25 +1721,36 @@ let UserCredentialCCCredentialReport = (() => {
1674
1721
  const credentialReadBack = !!(raw.payload[6] & 128);
1675
1722
  const credentialLength = raw.payload[7];
1676
1723
  validatePayload(raw.payload.length >= 8 + credentialLength + 6);
1677
- const credentialData = Bytes.from(raw.payload.subarray(8, 8 + credentialLength));
1678
- let offset = 8 + credentialLength;
1724
+ const offset = 8 + credentialLength;
1679
1725
  const modifierType = raw.payload[offset];
1680
1726
  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({
1727
+ const common = {
1684
1728
  nodeId: ctx.sourceNodeId,
1685
- reportType,
1686
1729
  userId,
1687
1730
  credentialType,
1688
1731
  credentialSlot,
1689
- credentialReadBack,
1690
- credentialLength,
1691
- credentialData,
1692
1732
  modifierType,
1693
1733
  modifierNodeId,
1694
- nextCredentialType,
1695
- nextCredentialSlot,
1734
+ };
1735
+ const dataOptions = credentialReadBack
1736
+ ? {
1737
+ credentialReadBack: true,
1738
+ credentialData: Bytes.from(raw.payload.subarray(8, 8 + credentialLength)),
1739
+ }
1740
+ : { credentialReadBack: false };
1741
+ if (reportType === UserCredentialCredentialReportType.ResponseToGet) {
1742
+ return new this({
1743
+ ...common,
1744
+ ...dataOptions,
1745
+ reportType,
1746
+ nextCredentialType: raw.payload[offset + 3],
1747
+ nextCredentialSlot: raw.payload.readUInt16BE(offset + 4),
1748
+ });
1749
+ }
1750
+ return new this({
1751
+ ...common,
1752
+ ...dataOptions,
1753
+ reportType,
1696
1754
  });
1697
1755
  }
1698
1756
  reportType;
@@ -1700,28 +1758,29 @@ let UserCredentialCCCredentialReport = (() => {
1700
1758
  credentialType;
1701
1759
  credentialSlot;
1702
1760
  credentialReadBack;
1703
- credentialLength;
1704
1761
  credentialData;
1705
1762
  modifierType;
1706
1763
  modifierNodeId;
1707
1764
  nextCredentialType;
1708
1765
  nextCredentialSlot;
1709
1766
  serialize(ctx) {
1710
- this.payload = Bytes.alloc(14 + this.credentialData.length);
1767
+ const credentialData = this.credentialData ?? new Bytes();
1768
+ this.payload = Bytes.alloc(14 + credentialData.length);
1711
1769
  this.payload[0] = this.reportType;
1712
1770
  this.payload.writeUInt16BE(this.userId, 1);
1713
1771
  this.payload[3] = this.credentialType;
1714
1772
  this.payload.writeUInt16BE(this.credentialSlot, 4);
1715
1773
  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);
1774
+ this.payload[7] = credentialData.length;
1775
+ if (credentialData.length > 0) {
1776
+ this.payload.set(credentialData, 8);
1719
1777
  }
1720
- let offset = 8 + this.credentialData.length;
1778
+ const offset = 8 + credentialData.length;
1721
1779
  this.payload[offset] = this.modifierType;
1722
1780
  this.payload.writeUInt16BE(this.modifierNodeId, offset + 1);
1723
- this.payload[offset + 3] = this.nextCredentialType;
1724
- this.payload.writeUInt16BE(this.nextCredentialSlot, offset + 4);
1781
+ this.payload[offset + 3] = this.nextCredentialType
1782
+ ?? UserCredentialType.None;
1783
+ this.payload.writeUInt16BE(this.nextCredentialSlot ?? 0, offset + 4);
1725
1784
  return super.serialize(ctx);
1726
1785
  }
1727
1786
  persistValues(ctx) {
@@ -1731,43 +1790,64 @@ let UserCredentialCCCredentialReport = (() => {
1731
1790
  || this.credentialSlot === 0) {
1732
1791
  return true;
1733
1792
  }
1793
+ // A CredentialModifyRejectedLocationEmpty report means we have a stale
1794
+ // cache entry for a credential that no longer exists on the device.
1734
1795
  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));
1796
+ === UserCredentialCredentialReportType.CredentialDeleted
1797
+ || this.reportType
1798
+ === UserCredentialCredentialReportType
1799
+ .CredentialModifyRejectedLocationEmpty) {
1800
+ this.removeValue(ctx, UserCredentialCCValues.credential(this.credentialType, this.credentialSlot));
1801
+ this.removeValue(ctx, UserCredentialCCValues.credentialOwner(this.credentialType, this.credentialSlot));
1802
+ this.removeValue(ctx, UserCredentialCCValues.credentialModifierType(this.credentialType, this.credentialSlot));
1803
+ this.removeValue(ctx, UserCredentialCCValues.credentialModifierNodeId(this.credentialType, this.credentialSlot));
1739
1804
  return true;
1740
1805
  }
1806
+ // A CredentialAddRejectedLocationOccupied report includes the actual
1807
+ // credential data in the read-back field when the device chooses to
1808
+ // reveal it, so we can update our cache with the real state.
1809
+ const isRejectionWithReadback = this.reportType
1810
+ === UserCredentialCredentialReportType
1811
+ .CredentialAddRejectedLocationOccupied
1812
+ && this.credentialReadBack;
1741
1813
  if (this.reportType
1742
1814
  !== UserCredentialCredentialReportType.ResponseToGet
1743
1815
  && this.reportType
1744
1816
  !== UserCredentialCredentialReportType.CredentialAdded
1745
1817
  && this.reportType
1746
- !== UserCredentialCredentialReportType.CredentialModified) {
1818
+ !== UserCredentialCredentialReportType.CredentialModified
1819
+ && !isRejectionWithReadback) {
1747
1820
  return true;
1748
1821
  }
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);
1822
+ if (this.credentialData != undefined) {
1823
+ this.ensureMetadata(ctx, UserCredentialCCValues.credential(this.credentialType, this.credentialSlot));
1824
+ this.setValue(ctx, UserCredentialCCValues.credential(this.credentialType, this.credentialSlot), normalizeCredentialData(this.credentialType, this.credentialData));
1825
+ }
1826
+ this.setValue(ctx, UserCredentialCCValues.credentialOwner(this.credentialType, this.credentialSlot), this.userId);
1827
+ this.setValue(ctx, UserCredentialCCValues.credentialModifierType(this.credentialType, this.credentialSlot), this.modifierType);
1828
+ this.setValue(ctx, UserCredentialCCValues.credentialModifierNodeId(this.credentialType, this.credentialSlot), this.modifierNodeId);
1753
1829
  return true;
1754
1830
  }
1755
1831
  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
- },
1832
+ const message = {
1833
+ "report type": getEnumMemberName(UserCredentialCredentialReportType, this.reportType),
1834
+ "user ID": this.userId,
1835
+ "credential type": getEnumMemberName(UserCredentialType, this.credentialType),
1836
+ "credential slot": this.credentialSlot,
1837
+ "credential read-back": this.credentialReadBack,
1770
1838
  };
1839
+ if (this.credentialData != undefined) {
1840
+ message["credential data"] = credentialToLogString(this.credentialData);
1841
+ }
1842
+ message["modifier type"] = getEnumMemberName(UserCredentialModifierType, this.modifierType);
1843
+ message["modifier node id"] = this.modifierNodeId;
1844
+ if (this.nextCredentialType != undefined) {
1845
+ message["next credential type"] = getEnumMemberName(UserCredentialType, this.nextCredentialType);
1846
+ }
1847
+ if (this.nextCredentialSlot != undefined) {
1848
+ message["next credential slot"] = this.nextCredentialSlot;
1849
+ }
1850
+ return { ...super.toLogEntry(ctx), message };
1771
1851
  }
1772
1852
  };
1773
1853
  return UserCredentialCCCredentialReport = _classThis;
@@ -1992,18 +2072,18 @@ let UserCredentialCCCredentialLearnReport = (() => {
1992
2072
  return UserCredentialCCCredentialLearnReport = _classThis;
1993
2073
  })();
1994
2074
  export { UserCredentialCCCredentialLearnReport };
1995
- let UserCredentialCCUserCredentialAssociationSet = (() => {
1996
- let _classDecorators = [CCCommand(UserCredentialCommand.UserCredentialAssociationSet), useSupervision()];
2075
+ let UserCredentialCCAssociationReport = (() => {
2076
+ let _classDecorators = [CCCommand(UserCredentialCommand.UserCredentialAssociationReport)];
1997
2077
  let _classDescriptor;
1998
2078
  let _classExtraInitializers = [];
1999
2079
  let _classThis;
2000
2080
  let _classSuper = UserCredentialCC;
2001
- var UserCredentialCCUserCredentialAssociationSet = class extends _classSuper {
2081
+ var UserCredentialCCAssociationReport = class extends _classSuper {
2002
2082
  static { _classThis = this; }
2003
2083
  static {
2004
2084
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
2005
2085
  __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
2006
- UserCredentialCCUserCredentialAssociationSet = _classThis = _classDescriptor.value;
2086
+ UserCredentialCCAssociationReport = _classThis = _classDescriptor.value;
2007
2087
  if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
2008
2088
  __runInitializers(_classThis, _classExtraInitializers);
2009
2089
  }
@@ -2012,29 +2092,45 @@ let UserCredentialCCUserCredentialAssociationSet = (() => {
2012
2092
  this.credentialType = options.credentialType;
2013
2093
  this.credentialSlot = options.credentialSlot;
2014
2094
  this.destinationUserId = options.destinationUserId;
2095
+ this.status = options.status;
2015
2096
  }
2016
- credentialType;
2017
- credentialSlot;
2018
- destinationUserId;
2019
2097
  static from(raw, ctx) {
2020
- validatePayload(raw.payload.length >= 5);
2098
+ validatePayload(raw.payload.length >= 6);
2021
2099
  const credentialType = raw.payload[0];
2022
2100
  const credentialSlot = raw.payload.readUInt16BE(1);
2023
2101
  const destinationUserId = raw.payload.readUInt16BE(3);
2102
+ const status = raw.payload[5];
2024
2103
  return new this({
2025
2104
  nodeId: ctx.sourceNodeId,
2026
2105
  credentialType,
2027
2106
  credentialSlot,
2028
- destinationUserId,
2107
+ destinationUserId: destinationUserId,
2108
+ status,
2029
2109
  });
2030
2110
  }
2111
+ credentialType;
2112
+ credentialSlot;
2113
+ destinationUserId;
2114
+ status;
2031
2115
  serialize(ctx) {
2032
- this.payload = Bytes.alloc(5);
2116
+ this.payload = Bytes.alloc(6);
2033
2117
  this.payload[0] = this.credentialType;
2034
2118
  this.payload.writeUInt16BE(this.credentialSlot, 1);
2035
2119
  this.payload.writeUInt16BE(this.destinationUserId, 3);
2120
+ this.payload[5] = this.status;
2036
2121
  return super.serialize(ctx);
2037
2122
  }
2123
+ persistValues(ctx) {
2124
+ if (!super.persistValues(ctx))
2125
+ return false;
2126
+ if (this.status !== 0
2127
+ || this.credentialType === UserCredentialType.None
2128
+ || this.credentialSlot === 0) {
2129
+ return true;
2130
+ }
2131
+ this.setValue(ctx, UserCredentialCCValues.credentialOwner(this.credentialType, this.credentialSlot), this.destinationUserId);
2132
+ return true;
2133
+ }
2038
2134
  toLogEntry(ctx) {
2039
2135
  return {
2040
2136
  ...super.toLogEntry(ctx),
@@ -2042,25 +2138,30 @@ let UserCredentialCCUserCredentialAssociationSet = (() => {
2042
2138
  "credential type": getEnumMemberName(UserCredentialType, this.credentialType),
2043
2139
  "credential slot": this.credentialSlot,
2044
2140
  "destination user ID": this.destinationUserId,
2141
+ status: this.status,
2045
2142
  },
2046
2143
  };
2047
2144
  }
2048
2145
  };
2049
- return UserCredentialCCUserCredentialAssociationSet = _classThis;
2146
+ return UserCredentialCCAssociationReport = _classThis;
2050
2147
  })();
2051
- export { UserCredentialCCUserCredentialAssociationSet };
2052
- let UserCredentialCCUserCredentialAssociationReport = (() => {
2053
- let _classDecorators = [CCCommand(UserCredentialCommand.UserCredentialAssociationReport)];
2148
+ export { UserCredentialCCAssociationReport };
2149
+ function testResponseForUserCredentialAssociationSet(sent, received) {
2150
+ return (sent.credentialType === received.credentialType
2151
+ && sent.credentialSlot === received.credentialSlot);
2152
+ }
2153
+ let UserCredentialCCAssociationSet = (() => {
2154
+ let _classDecorators = [CCCommand(UserCredentialCommand.UserCredentialAssociationSet), expectedCCResponse(UserCredentialCCAssociationReport, testResponseForUserCredentialAssociationSet)];
2054
2155
  let _classDescriptor;
2055
2156
  let _classExtraInitializers = [];
2056
2157
  let _classThis;
2057
2158
  let _classSuper = UserCredentialCC;
2058
- var UserCredentialCCUserCredentialAssociationReport = class extends _classSuper {
2159
+ var UserCredentialCCAssociationSet = class extends _classSuper {
2059
2160
  static { _classThis = this; }
2060
2161
  static {
2061
2162
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
2062
2163
  __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
2063
- UserCredentialCCUserCredentialAssociationReport = _classThis = _classDescriptor.value;
2164
+ UserCredentialCCAssociationSet = _classThis = _classDescriptor.value;
2064
2165
  if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
2065
2166
  __runInitializers(_classThis, _classExtraInitializers);
2066
2167
  }
@@ -2069,32 +2170,27 @@ let UserCredentialCCUserCredentialAssociationReport = (() => {
2069
2170
  this.credentialType = options.credentialType;
2070
2171
  this.credentialSlot = options.credentialSlot;
2071
2172
  this.destinationUserId = options.destinationUserId;
2072
- this.status = options.status;
2073
2173
  }
2174
+ credentialType;
2175
+ credentialSlot;
2176
+ destinationUserId;
2074
2177
  static from(raw, ctx) {
2075
- validatePayload(raw.payload.length >= 6);
2178
+ validatePayload(raw.payload.length >= 5);
2076
2179
  const credentialType = raw.payload[0];
2077
2180
  const credentialSlot = raw.payload.readUInt16BE(1);
2078
2181
  const destinationUserId = raw.payload.readUInt16BE(3);
2079
- const status = raw.payload[5];
2080
2182
  return new this({
2081
2183
  nodeId: ctx.sourceNodeId,
2082
2184
  credentialType,
2083
2185
  credentialSlot,
2084
- destinationUserId: destinationUserId,
2085
- status,
2186
+ destinationUserId,
2086
2187
  });
2087
2188
  }
2088
- credentialType;
2089
- credentialSlot;
2090
- destinationUserId;
2091
- status;
2092
2189
  serialize(ctx) {
2093
- this.payload = Bytes.alloc(6);
2190
+ this.payload = Bytes.alloc(5);
2094
2191
  this.payload[0] = this.credentialType;
2095
2192
  this.payload.writeUInt16BE(this.credentialSlot, 1);
2096
2193
  this.payload.writeUInt16BE(this.destinationUserId, 3);
2097
- this.payload[5] = this.status;
2098
2194
  return super.serialize(ctx);
2099
2195
  }
2100
2196
  toLogEntry(ctx) {
@@ -2104,14 +2200,13 @@ let UserCredentialCCUserCredentialAssociationReport = (() => {
2104
2200
  "credential type": getEnumMemberName(UserCredentialType, this.credentialType),
2105
2201
  "credential slot": this.credentialSlot,
2106
2202
  "destination user ID": this.destinationUserId,
2107
- status: this.status,
2108
2203
  },
2109
2204
  };
2110
2205
  }
2111
2206
  };
2112
- return UserCredentialCCUserCredentialAssociationReport = _classThis;
2207
+ return UserCredentialCCAssociationSet = _classThis;
2113
2208
  })();
2114
- export { UserCredentialCCUserCredentialAssociationReport };
2209
+ export { UserCredentialCCAssociationSet };
2115
2210
  let UserCredentialCCAllUsersChecksumReport = (() => {
2116
2211
  let _classDecorators = [CCCommand(UserCredentialCommand.AllUsersChecksumReport)];
2117
2212
  let _classDescriptor;