@matter/protocol 0.16.0-alpha.0-20251103-b47ffa15b → 0.16.0-alpha.0-20251106-4e10fd77b

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 (120) hide show
  1. package/dist/cjs/action/client/ClientInteraction.d.ts +1 -0
  2. package/dist/cjs/action/client/ClientInteraction.d.ts.map +1 -1
  3. package/dist/cjs/action/client/ClientInteraction.js +3 -0
  4. package/dist/cjs/action/client/ClientInteraction.js.map +1 -1
  5. package/dist/cjs/action/client/subscription/ClientSubscriptionHandler.js +2 -4
  6. package/dist/cjs/action/client/subscription/ClientSubscriptionHandler.js.map +1 -1
  7. package/dist/cjs/certificate/index.d.ts +1 -0
  8. package/dist/cjs/certificate/index.d.ts.map +1 -1
  9. package/dist/cjs/certificate/index.js +1 -0
  10. package/dist/cjs/certificate/index.js.map +1 -1
  11. package/dist/cjs/certificate/kinds/Icac.d.ts.map +1 -1
  12. package/dist/cjs/certificate/kinds/Icac.js +3 -0
  13. package/dist/cjs/certificate/kinds/Icac.js.map +1 -1
  14. package/dist/cjs/certificate/kinds/Noc.d.ts.map +1 -1
  15. package/dist/cjs/certificate/kinds/Noc.js +3 -0
  16. package/dist/cjs/certificate/kinds/Noc.js.map +1 -1
  17. package/dist/cjs/certificate/kinds/Rcac.d.ts.map +1 -1
  18. package/dist/cjs/certificate/kinds/Rcac.js +3 -0
  19. package/dist/cjs/certificate/kinds/Rcac.js.map +1 -1
  20. package/dist/cjs/certificate/kinds/Vvsc.d.ts +29 -0
  21. package/dist/cjs/certificate/kinds/Vvsc.d.ts.map +1 -0
  22. package/dist/cjs/certificate/kinds/Vvsc.js +79 -0
  23. package/dist/cjs/certificate/kinds/Vvsc.js.map +6 -0
  24. package/dist/cjs/certificate/kinds/X509Base.d.ts.map +1 -1
  25. package/dist/cjs/certificate/kinds/X509Base.js +3 -0
  26. package/dist/cjs/certificate/kinds/X509Base.js.map +1 -1
  27. package/dist/cjs/certificate/kinds/definitions/asn.d.ts +2 -0
  28. package/dist/cjs/certificate/kinds/definitions/asn.d.ts.map +1 -1
  29. package/dist/cjs/certificate/kinds/definitions/asn.js +2 -0
  30. package/dist/cjs/certificate/kinds/definitions/asn.js.map +1 -1
  31. package/dist/cjs/certificate/kinds/definitions/operational.d.ts +109 -0
  32. package/dist/cjs/certificate/kinds/definitions/operational.d.ts.map +1 -1
  33. package/dist/cjs/certificate/kinds/definitions/operational.js +6 -0
  34. package/dist/cjs/certificate/kinds/definitions/operational.js.map +1 -1
  35. package/dist/cjs/certificate/kinds/index.d.ts +1 -0
  36. package/dist/cjs/certificate/kinds/index.d.ts.map +1 -1
  37. package/dist/cjs/certificate/kinds/index.js +1 -0
  38. package/dist/cjs/certificate/kinds/index.js.map +1 -1
  39. package/dist/cjs/fabric/Fabric.d.ts +6 -1
  40. package/dist/cjs/fabric/Fabric.d.ts.map +1 -1
  41. package/dist/cjs/fabric/Fabric.js +53 -2
  42. package/dist/cjs/fabric/Fabric.js.map +1 -1
  43. package/dist/cjs/fabric/FabricManager.d.ts.map +1 -1
  44. package/dist/cjs/fabric/FabricManager.js +24 -9
  45. package/dist/cjs/fabric/FabricManager.js.map +1 -1
  46. package/dist/cjs/peer/PhysicalDeviceProperties.d.ts +11 -7
  47. package/dist/cjs/peer/PhysicalDeviceProperties.d.ts.map +1 -1
  48. package/dist/cjs/peer/PhysicalDeviceProperties.js +34 -25
  49. package/dist/cjs/peer/PhysicalDeviceProperties.js.map +1 -1
  50. package/dist/cjs/protocol/DeviceAdvertiser.d.ts.map +1 -1
  51. package/dist/cjs/protocol/DeviceAdvertiser.js +17 -0
  52. package/dist/cjs/protocol/DeviceAdvertiser.js.map +1 -1
  53. package/dist/esm/action/client/ClientInteraction.d.ts +1 -0
  54. package/dist/esm/action/client/ClientInteraction.d.ts.map +1 -1
  55. package/dist/esm/action/client/ClientInteraction.js +3 -0
  56. package/dist/esm/action/client/ClientInteraction.js.map +1 -1
  57. package/dist/esm/action/client/subscription/ClientSubscriptionHandler.js +2 -4
  58. package/dist/esm/action/client/subscription/ClientSubscriptionHandler.js.map +1 -1
  59. package/dist/esm/certificate/index.d.ts +1 -0
  60. package/dist/esm/certificate/index.d.ts.map +1 -1
  61. package/dist/esm/certificate/index.js +1 -0
  62. package/dist/esm/certificate/index.js.map +1 -1
  63. package/dist/esm/certificate/kinds/Icac.d.ts.map +1 -1
  64. package/dist/esm/certificate/kinds/Icac.js +3 -0
  65. package/dist/esm/certificate/kinds/Icac.js.map +1 -1
  66. package/dist/esm/certificate/kinds/Noc.d.ts.map +1 -1
  67. package/dist/esm/certificate/kinds/Noc.js +3 -0
  68. package/dist/esm/certificate/kinds/Noc.js.map +1 -1
  69. package/dist/esm/certificate/kinds/Rcac.d.ts.map +1 -1
  70. package/dist/esm/certificate/kinds/Rcac.js +3 -0
  71. package/dist/esm/certificate/kinds/Rcac.js.map +1 -1
  72. package/dist/esm/certificate/kinds/Vvsc.d.ts +29 -0
  73. package/dist/esm/certificate/kinds/Vvsc.d.ts.map +1 -0
  74. package/dist/esm/certificate/kinds/Vvsc.js +59 -0
  75. package/dist/esm/certificate/kinds/Vvsc.js.map +6 -0
  76. package/dist/esm/certificate/kinds/X509Base.d.ts.map +1 -1
  77. package/dist/esm/certificate/kinds/X509Base.js +5 -1
  78. package/dist/esm/certificate/kinds/X509Base.js.map +1 -1
  79. package/dist/esm/certificate/kinds/definitions/asn.d.ts +2 -0
  80. package/dist/esm/certificate/kinds/definitions/asn.d.ts.map +1 -1
  81. package/dist/esm/certificate/kinds/definitions/asn.js +2 -0
  82. package/dist/esm/certificate/kinds/definitions/asn.js.map +1 -1
  83. package/dist/esm/certificate/kinds/definitions/operational.d.ts +109 -0
  84. package/dist/esm/certificate/kinds/definitions/operational.d.ts.map +1 -1
  85. package/dist/esm/certificate/kinds/definitions/operational.js +6 -0
  86. package/dist/esm/certificate/kinds/definitions/operational.js.map +1 -1
  87. package/dist/esm/certificate/kinds/index.d.ts +1 -0
  88. package/dist/esm/certificate/kinds/index.d.ts.map +1 -1
  89. package/dist/esm/certificate/kinds/index.js +1 -0
  90. package/dist/esm/certificate/kinds/index.js.map +1 -1
  91. package/dist/esm/fabric/Fabric.d.ts +6 -1
  92. package/dist/esm/fabric/Fabric.d.ts.map +1 -1
  93. package/dist/esm/fabric/Fabric.js +54 -3
  94. package/dist/esm/fabric/Fabric.js.map +1 -1
  95. package/dist/esm/fabric/FabricManager.d.ts.map +1 -1
  96. package/dist/esm/fabric/FabricManager.js +25 -9
  97. package/dist/esm/fabric/FabricManager.js.map +1 -1
  98. package/dist/esm/peer/PhysicalDeviceProperties.d.ts +11 -7
  99. package/dist/esm/peer/PhysicalDeviceProperties.d.ts.map +1 -1
  100. package/dist/esm/peer/PhysicalDeviceProperties.js +35 -26
  101. package/dist/esm/peer/PhysicalDeviceProperties.js.map +1 -1
  102. package/dist/esm/protocol/DeviceAdvertiser.d.ts.map +1 -1
  103. package/dist/esm/protocol/DeviceAdvertiser.js +17 -0
  104. package/dist/esm/protocol/DeviceAdvertiser.js.map +1 -1
  105. package/package.json +6 -6
  106. package/src/action/client/ClientInteraction.ts +5 -1
  107. package/src/action/client/subscription/ClientSubscriptionHandler.ts +2 -4
  108. package/src/certificate/index.ts +1 -0
  109. package/src/certificate/kinds/Icac.ts +5 -0
  110. package/src/certificate/kinds/Noc.ts +5 -0
  111. package/src/certificate/kinds/Rcac.ts +5 -0
  112. package/src/certificate/kinds/Vvsc.ts +72 -0
  113. package/src/certificate/kinds/X509Base.ts +4 -0
  114. package/src/certificate/kinds/definitions/asn.ts +3 -0
  115. package/src/certificate/kinds/definitions/operational.ts +8 -0
  116. package/src/certificate/kinds/index.ts +1 -0
  117. package/src/fabric/Fabric.ts +66 -3
  118. package/src/fabric/FabricManager.ts +32 -9
  119. package/src/peer/PhysicalDeviceProperties.ts +52 -35
  120. package/src/protocol/DeviceAdvertiser.ts +30 -0
@@ -88,6 +88,11 @@ export class Icac extends OperationalBase<OperationalCertificate.Icac> {
88
88
  throw new CertificateError(`Ica certificate must not contain a caseAuthenticatedTags.`);
89
89
  }
90
90
 
91
+ // The subject DN SHALL NOT encode any matter-vvs-id attribute.
92
+ if ("vvsId" in subject) {
93
+ throw new CertificateError(`Ica certificate must not contain a vvsId.`);
94
+ }
95
+
91
96
  // When any matter-fabric-id attributes are present in either the Matter Root CA Certificate or the Matter ICA
92
97
  // Certificate, the value SHALL match the one present in the Matter Node Operational Certificate (NOC) within
93
98
  // the same certificate chain.
@@ -89,6 +89,11 @@ export class Noc extends OperationalBase<OperationalCertificate.Noc> {
89
89
  throw new CertificateError(`Noc certificate must not contain an rcacId.`);
90
90
  }
91
91
 
92
+ // The subject DN SHALL NOT encode any matter-vvs-id attribute.
93
+ if ("vvsId" in subject) {
94
+ throw new CertificateError(`Noc certificate must not contain a vvsId.`);
95
+ }
96
+
92
97
  // The subject DN MAY encode at most three matter-noc-cat attributes.
93
98
  if (caseAuthenticatedTags !== undefined) {
94
99
  CaseAuthenticatedTag.validateNocTagList(caseAuthenticatedTags); // throws ValidationError
@@ -79,6 +79,11 @@ export class Rcac extends OperationalBase<OperationalCertificate.Rcac> {
79
79
  throw new CertificateError(`Root certificate must not contain a caseAuthenticatedTags.`);
80
80
  }
81
81
 
82
+ // The subject DN SHALL NOT encode any matter-vsc-id attribute.
83
+ if ("vvsId" in subject) {
84
+ throw new CertificateError(`Root certificate must not contain a vvsId.`);
85
+ }
86
+
82
87
  // The basic constraints extension SHALL be encoded with is-ca set to true.
83
88
  if (basicConstraints.isCa !== true) {
84
89
  throw new CertificateError(`Root certificate must have isCa set to true.`);
@@ -0,0 +1,72 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2025 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import { Bytes, Crypto, Diagnostic } from "#general";
8
+ import { CertificateError } from "./common.js";
9
+ import { OperationalCertificate } from "./definitions/operational.js";
10
+ import { OperationalBase } from "./OperationalBase.js";
11
+
12
+ /**
13
+ * Represents an Vendor Verification Signer Certificate
14
+ */
15
+ export class Vvsc extends OperationalBase<OperationalCertificate.Vvsc> {
16
+ /** Construct the class from a Tlv version of the certificate */
17
+ static fromTlv(tlv: Bytes): Vvsc {
18
+ return new Vvsc(OperationalCertificate.TlvVvsc.decode(tlv));
19
+ }
20
+
21
+ /** Validates all basic certificate fields on construction. */
22
+ protected validateFields() {
23
+ const {
24
+ extensions: {
25
+ basicConstraints: { isCa },
26
+ },
27
+ } = this.cert;
28
+ if (!isCa) {
29
+ throw new CertificateError("Intermediate certificate must be a CA.");
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Encodes the certificate with the signature as Matter Tlv.
35
+ * If the certificate is not signed, it throws a CertificateError.
36
+ */
37
+ asSignedTlv() {
38
+ return OperationalCertificate.TlvVvsc.encode({ ...this.cert, signature: this.signature });
39
+ }
40
+
41
+ /**
42
+ * Verify requirements a Matter Intermediate CA certificate must fulfill.
43
+ * Rules for this are listed in @see {@link MatterSpecification.v12.Core} §6.5.x
44
+ * // TODO ADD Verification once we know more about the chain
45
+ */
46
+ async verify(_crypto: Crypto) {
47
+ this.generalVerify();
48
+
49
+ const { subject } = this.cert;
50
+ const { vvsId } = subject;
51
+
52
+ // The subject DN SHALL encode exactly one matter-vvs-id attribute.
53
+ if (vvsId === undefined || Array.isArray(vvsId)) {
54
+ throw new CertificateError(`Invalid vvsId in Vsc certificate: ${Diagnostic.json(vvsId)}`);
55
+ }
56
+
57
+ // The subject DN SHALL NOT encode any matter-node-id attribute.
58
+ if ("nodeId" in subject) {
59
+ throw new CertificateError(`Vsc certificate must not contain a nodeId.`);
60
+ }
61
+
62
+ // The subject DN SHALL NOT encode any matter-fabric-id attribute.
63
+ if ("fabricId" in subject) {
64
+ throw new CertificateError(`Vsc certificate must not contain a fabricId.`);
65
+ }
66
+
67
+ // The subject DN SHALL NOT encode any matter-noc-cat attribute.
68
+ if ("caseAuthenticatedTags" in subject) {
69
+ throw new CertificateError(`Vvsc certificate must not contain a caseAuthenticatedTags.`);
70
+ }
71
+ }
72
+ }
@@ -32,6 +32,7 @@ import {
32
32
  ProductId_Matter,
33
33
  RcacId_Matter,
34
34
  VendorId_Matter,
35
+ VvsId_Matter,
35
36
  } from "./definitions/asn.js";
36
37
  import { ExtensionKeyUsageBitmap, ExtensionKeyUsageSchema, X509Certificate } from "./definitions/base.js";
37
38
  import { CertificateExtension } from "./definitions/operational.js";
@@ -170,6 +171,9 @@ export abstract class X509Base<CT extends X509Certificate> {
170
171
  case "rcacId":
171
172
  asn.rcacId = RcacId_Matter(value as number | bigint);
172
173
  break;
174
+ case "vvsId":
175
+ asn.vvsId = VvsId_Matter(value as number | bigint);
176
+ break;
173
177
  case "fabricId":
174
178
  asn.fabricId = FabricId_Matter(value as FabricId);
175
179
  break;
@@ -90,6 +90,9 @@ export const FabricId_Matter = GenericMatterOpCertObject<FabricId>(5);
90
90
  /** matter-noc-cat = ASN.1 OID 1.3.6.1.4.1.37244.1.6 */
91
91
  export const NocCat_Matter = GenericMatterOpCertObject<number>(6, uInt16To8Chars);
92
92
 
93
+ /** matter-vvs-id = ASN.1 OID 1.3.6.1.4.1.37244.1.7 */
94
+ export const VvsId_Matter = GenericMatterOpCertObject<bigint | number>(7);
95
+
93
96
  /** matter-oid-vid = ASN.1 OID 1.3.6.1.4.1.37244.2.1 */
94
97
  export const VendorId_Matter = GenericMatterAttCertObject<VendorId>(1, uInt16To4Chars);
95
98
 
@@ -173,7 +173,15 @@ export namespace OperationalCertificate {
173
173
  issuer: AllowedSubjectAndIssuerMatterFields,
174
174
  });
175
175
 
176
+ export const TlvVvsc = BaseMatterCertificate({
177
+ subject: {
178
+ vvsId: TlvField(23, TlvUInt64),
179
+ },
180
+ issuer: AllowedSubjectAndIssuerMatterFields,
181
+ });
182
+
176
183
  export type Rcac = TypeFromSchema<typeof TlvRcac>;
177
184
  export type Icac = TypeFromSchema<typeof TlvIcac>;
178
185
  export type Noc = TypeFromSchema<typeof TlvNoc>;
186
+ export type Vvsc = TypeFromSchema<typeof TlvVvsc>;
179
187
  }
@@ -9,4 +9,5 @@ export * from "./CertificationDeclaration.js";
9
9
  export * from "./Icac.js";
10
10
  export * from "./Noc.js";
11
11
  export * from "./Rcac.js";
12
+ export * from "./Vvsc.js";
12
13
  export * from "./X509Base.js";
@@ -29,7 +29,7 @@ import { FabricGroups, GROUP_SECURITY_INFO } from "#groups/FabricGroups.js";
29
29
  import { FabricAccessControl } from "#interaction/FabricAccessControl.js";
30
30
  import { PeerAddress } from "#peer/PeerAddress.js";
31
31
  import { Session } from "#session/Session.js";
32
- import { CaseAuthenticatedTag, FabricId, FabricIndex, GroupId, NodeId, VendorId } from "#types";
32
+ import { CaseAuthenticatedTag, FabricId, FabricIndex, GroupId, NodeId, StatusResponse, VendorId } from "#types";
33
33
 
34
34
  const logger = Logger.get("Fabric");
35
35
 
@@ -54,7 +54,7 @@ export class Fabric {
54
54
  readonly rootNodeId: NodeId;
55
55
  readonly operationalId: Bytes;
56
56
  readonly rootPublicKey: Bytes;
57
- readonly rootVendorId: VendorId;
57
+ #rootVendorId: VendorId;
58
58
  readonly rootCert: Bytes;
59
59
  readonly identityProtectionKey: Bytes;
60
60
  readonly operationalIdentityProtectionKey: Bytes;
@@ -64,6 +64,8 @@ export class Fabric {
64
64
  readonly #sessions = new Set<Session>();
65
65
  readonly #groups: FabricGroups;
66
66
  readonly #accessControl: FabricAccessControl;
67
+ #vidVerificationStatement?: Bytes;
68
+ #vvsc?: Bytes;
67
69
  #label: string;
68
70
  #removeCallbacks = new Array<() => MaybePromise<void>>();
69
71
  #persistCallback: ((isUpdate?: boolean) => MaybePromise<void>) | undefined;
@@ -77,12 +79,14 @@ export class Fabric {
77
79
  this.rootNodeId = config.rootNodeId;
78
80
  this.operationalId = config.operationalId;
79
81
  this.rootPublicKey = config.rootPublicKey;
80
- this.rootVendorId = config.rootVendorId;
82
+ this.#rootVendorId = config.rootVendorId;
81
83
  this.rootCert = config.rootCert;
82
84
  this.identityProtectionKey = config.identityProtectionKey;
83
85
  this.operationalIdentityProtectionKey = config.operationalIdentityProtectionKey;
84
86
  this.intermediateCACert = config.intermediateCACert;
85
87
  this.operationalCert = config.operationalCert;
88
+ this.#vidVerificationStatement = config.vidVerificationStatement;
89
+ this.#vvsc = config.vvsc;
86
90
  this.#label = config.label;
87
91
  this.#keyPair = PrivateKey(config.keyPair);
88
92
  this.#accessControl = new FabricAccessControl(this);
@@ -108,6 +112,7 @@ export class Fabric {
108
112
  operationalIdentityProtectionKey: this.operationalIdentityProtectionKey,
109
113
  intermediateCACert: this.intermediateCACert,
110
114
  operationalCert: this.operationalCert,
115
+ vidVerificationStatement: this.vidVerificationStatement,
111
116
  label: this.#label,
112
117
  };
113
118
  }
@@ -127,6 +132,56 @@ export class Fabric {
127
132
  await this.persist();
128
133
  }
129
134
 
135
+ get vidVerificationStatement() {
136
+ return this.#vidVerificationStatement;
137
+ }
138
+
139
+ async updateVendorVerificationData(
140
+ vendorId: VendorId | undefined,
141
+ vidVerificationStatement: Bytes | undefined,
142
+ vvsc: Bytes | undefined,
143
+ ) {
144
+ if (vvsc !== undefined && this.intermediateCACert !== undefined) {
145
+ throw new StatusResponse.InvalidCommandError("A VVSC is only allowed without an ICAC.");
146
+ }
147
+
148
+ if (vidVerificationStatement !== undefined) {
149
+ if (vidVerificationStatement.byteLength === 0) {
150
+ this.#vidVerificationStatement = undefined;
151
+ } else if (vidVerificationStatement.byteLength === 85) {
152
+ // VERIFICATION_STATEMENT_SIZE
153
+ this.#vidVerificationStatement = vidVerificationStatement;
154
+ } else {
155
+ throw new StatusResponse.ConstraintErrorError("VID Verification Statement must be 0 or 85 bytes long.");
156
+ }
157
+ }
158
+ if (vendorId !== undefined) {
159
+ this.#rootVendorId = vendorId;
160
+ }
161
+ if (vvsc !== undefined) {
162
+ if (vvsc.byteLength === 0) {
163
+ this.#vvsc = undefined;
164
+ } else {
165
+ this.#vvsc = vvsc;
166
+ }
167
+ }
168
+ logger.info(
169
+ "Updated Vendor Verification Data for Fabric",
170
+ this.#rootVendorId,
171
+ this.#vidVerificationStatement,
172
+ this.#vvsc,
173
+ );
174
+ await this.persist();
175
+ }
176
+
177
+ get vvsc() {
178
+ return this.#vvsc;
179
+ }
180
+
181
+ get rootVendorId() {
182
+ return this.#rootVendorId;
183
+ }
184
+
130
185
  set storage(storage: StorageContext) {
131
186
  this.#storage = storage;
132
187
  this.#groups.storage = storage;
@@ -282,6 +337,8 @@ export class FabricBuilder {
282
337
  #rootNodeId?: NodeId;
283
338
  #rootPublicKey?: Bytes;
284
339
  #identityProtectionKey?: Bytes;
340
+ #vidVerificationStatement?: Bytes;
341
+ #vvsc?: Bytes;
285
342
  #fabricIndex?: FabricIndex;
286
343
  #label = "";
287
344
 
@@ -387,6 +444,8 @@ export class FabricBuilder {
387
444
  this.#identityProtectionKey = fabric.identityProtectionKey;
388
445
  this.#rootCert = fabric.rootCert;
389
446
  this.#rootPublicKey = fabric.rootPublicKey;
447
+ this.#vidVerificationStatement = fabric.vidVerificationStatement;
448
+ this.#vvsc = fabric.vvsc;
390
449
  this.#label = fabric.label;
391
450
  }
392
451
 
@@ -447,6 +506,8 @@ export class FabricBuilder {
447
506
  ),
448
507
  intermediateCACert: this.#intermediateCACert,
449
508
  operationalCert: this.#operationalCert,
509
+ vidVerificationStatement: this.#vidVerificationStatement,
510
+ vvsc: this.#vvsc,
450
511
  label: this.#label,
451
512
  });
452
513
  }
@@ -464,6 +525,8 @@ export namespace Fabric {
464
525
  rootVendorId: VendorId;
465
526
  rootCert: Bytes;
466
527
  identityProtectionKey: Bytes;
528
+ vidVerificationStatement?: Bytes;
529
+ vvsc?: Bytes;
467
530
  operationalIdentityProtectionKey: Bytes;
468
531
  intermediateCACert: Bytes | undefined;
469
532
  operationalCert: Bytes;
@@ -12,6 +12,7 @@ import {
12
12
  Environmental,
13
13
  ImplementationError,
14
14
  Key,
15
+ Logger,
15
16
  MatterError,
16
17
  MatterFlowError,
17
18
  MaybePromise,
@@ -23,6 +24,8 @@ import { PeerAddress } from "#peer/PeerAddress.js";
23
24
  import { FabricIndex } from "#types";
24
25
  import { Fabric } from "./Fabric.js";
25
26
 
27
+ const logger = Logger.get("FabricManager");
28
+
26
29
  /** Specific Error for when a fabric is not found. */
27
30
  export class FabricNotFoundError extends MatterError {}
28
31
  export class FabricTableFullError extends MatterError {}
@@ -63,7 +66,7 @@ export class FabricManager {
63
66
 
64
67
  const fabrics = await this.#storage.get<Fabric.Config[]>("fabrics", []);
65
68
  for (const fabricConfig of fabrics) {
66
- this.#addFabric(new Fabric(crypto, fabricConfig));
69
+ this.#addNewFabric(new Fabric(crypto, fabricConfig));
67
70
  }
68
71
 
69
72
  this.#nextFabricIndex = await this.#storage.get("nextFabricIndex", this.#nextFabricIndex);
@@ -156,18 +159,34 @@ export class FabricManager {
156
159
 
157
160
  addFabric(fabric: Fabric) {
158
161
  this.#construction.assert();
159
- this.#addFabric(fabric);
162
+ this.#addNewFabric(fabric);
160
163
  }
161
164
 
162
- #addFabric(fabric: Fabric) {
165
+ #addNewFabric(fabric: Fabric) {
163
166
  const { fabricIndex } = fabric;
164
167
  if (this.#fabrics.has(fabricIndex)) {
165
168
  throw new MatterFlowError(`Fabric with index ${fabricIndex} already exists.`);
166
169
  }
170
+
171
+ this.#addOrUpdateFabricEntry(fabric);
172
+
173
+ if (this.#initializationDone) {
174
+ this.#events.added.emit(fabric);
175
+ }
176
+ }
177
+
178
+ /** Insert Fabric into the manager without emitting events */
179
+ #addOrUpdateFabricEntry(fabric: Fabric) {
180
+ const { fabricIndex } = fabric;
167
181
  this.#fabrics.set(fabricIndex, fabric);
168
182
  fabric.addRemoveCallback(async () => this.removeFabric(fabricIndex));
169
183
  fabric.persistCallback = (isUpdate = true) => {
170
184
  if (!this.#storage) {
185
+ if (isUpdate) {
186
+ logger.warn(
187
+ "Fabric can not be persisted because FabricManager has no storage but it is a fabric update.",
188
+ );
189
+ }
171
190
  return;
172
191
  }
173
192
  const persistResult = this.persistFabrics();
@@ -177,12 +196,9 @@ export class FabricManager {
177
196
  }
178
197
  });
179
198
  };
180
- if (this.#storage !== undefined) {
199
+ if (this.#storage !== undefined && fabric.storage === undefined) {
181
200
  fabric.storage = this.#storage.createContext(`fabric-${fabricIndex}`);
182
201
  }
183
- if (this.#initializationDone) {
184
- this.#events.added.emit(fabric);
185
- }
186
202
  }
187
203
 
188
204
  async removeFabric(fabricIndex: FabricIndex) {
@@ -259,12 +275,19 @@ export class FabricManager {
259
275
  await this.#construction;
260
276
 
261
277
  const { fabricIndex } = fabric;
262
- if (!this.#fabrics.has(fabricIndex)) {
278
+ const existingFabric = this.#fabrics.get(fabricIndex);
279
+ if (existingFabric === undefined) {
263
280
  throw new FabricNotFoundError(
264
281
  `Fabric with index ${fabricIndex} cannot be updated because it does not exist.`,
265
282
  );
266
283
  }
267
- this.#fabrics.set(fabricIndex, fabric);
284
+ if (existingFabric === fabric) {
285
+ // Nothing changed, so it is a restore without any change
286
+ return;
287
+ }
288
+
289
+ this.#addOrUpdateFabricEntry(fabric);
290
+
268
291
  if (this.#storage) {
269
292
  await this.persistFabrics();
270
293
  }
@@ -4,77 +4,94 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
- import { Logger } from "#general";
7
+ import { Duration, Instant, Logger, Minutes, Seconds } from "#general";
8
8
 
9
9
  const logger = Logger.get("PhysicalDeviceProperties");
10
10
 
11
- const DEFAULT_SUBSCRIPTION_FLOOR_DEFAULT_S = 1;
12
- const DEFAULT_SUBSCRIPTION_FLOOR_ICD_S = 0;
13
- const DEFAULT_SUBSCRIPTION_CEILING_WIFI_S = 60;
14
- const DEFAULT_SUBSCRIPTION_CEILING_THREAD_S = 60;
15
- const DEFAULT_SUBSCRIPTION_CEILING_THREAD_SLEEPY_S = 180;
16
- const DEFAULT_SUBSCRIPTION_CEILING_BATTERY_POWERED_S = 600;
11
+ const DEFAULT_SUBSCRIPTION_FLOOR_DEFAULT = Seconds(1);
12
+ const DEFAULT_SUBSCRIPTION_FLOOR_ICD = Instant;
13
+ const DEFAULT_SUBSCRIPTION_CEILING_WIFI = Minutes(1);
14
+ const DEFAULT_SUBSCRIPTION_CEILING_THREAD = Minutes(1);
15
+ const DEFAULT_SUBSCRIPTION_CEILING_THREAD_SLEEPY = Minutes(3);
16
+ const DEFAULT_SUBSCRIPTION_CEILING_BATTERY_POWERED = Minutes(10);
17
17
 
18
18
  export interface PhysicalDeviceProperties {
19
19
  threadConnected: boolean;
20
20
  wifiConnected: boolean;
21
21
  ethernetConnected: boolean;
22
22
  rootEndpointServerList: number[];
23
+ isMainsPowered: boolean;
23
24
  isBatteryPowered: boolean;
24
25
  isIntermittentlyConnected: boolean;
25
26
  isThreadSleepyEndDevice: boolean;
26
27
  }
27
28
 
28
29
  export namespace PhysicalDeviceProperties {
29
- export function determineSubscriptionParameters(options?: {
30
+ export function subscriptionIntervalBoundsFor(options?: {
30
31
  properties?: PhysicalDeviceProperties;
31
32
  description?: string;
32
- subscribeMinIntervalFloorSeconds?: number;
33
- subscribeMaxIntervalCeilingSeconds?: number;
34
- }) {
35
- const { properties } = options ?? {};
33
+ request?: Partial<PhysicalDeviceProperties.IntervalBounds>;
34
+ }): PhysicalDeviceProperties.IntervalBounds {
35
+ const { properties, request } = options ?? {};
36
36
 
37
- let {
38
- description,
39
- subscribeMinIntervalFloorSeconds: minIntervalFloorSeconds,
40
- subscribeMaxIntervalCeilingSeconds: maxIntervalCeilingSeconds,
41
- } = options ?? {};
37
+ let { description } = options ?? {};
38
+
39
+ let minIntervalFloor, maxIntervalCeiling;
40
+ if (request) {
41
+ ({ minIntervalFloor, maxIntervalCeiling } = request);
42
+ }
42
43
 
43
44
  if (description === undefined) {
44
45
  description = "Node";
45
46
  }
46
47
 
47
- const { isBatteryPowered, isIntermittentlyConnected, threadConnected, isThreadSleepyEndDevice } =
48
- properties ?? {};
48
+ const {
49
+ isMainsPowered,
50
+ isBatteryPowered,
51
+ isIntermittentlyConnected,
52
+ threadConnected,
53
+ isThreadSleepyEndDevice,
54
+ } = properties ?? {};
49
55
 
50
56
  if (isIntermittentlyConnected) {
51
- if (minIntervalFloorSeconds !== undefined && minIntervalFloorSeconds !== DEFAULT_SUBSCRIPTION_FLOOR_ICD_S) {
57
+ if (minIntervalFloor !== undefined && minIntervalFloor !== DEFAULT_SUBSCRIPTION_FLOOR_ICD) {
52
58
  logger.info(
53
- `${description}: Overwriting minIntervalFloorSeconds for intermittently connected device to ${DEFAULT_SUBSCRIPTION_FLOOR_ICD_S}`,
59
+ `${description}: Overwriting minIntervalFloorSeconds for intermittently connected device to ${Duration.format(DEFAULT_SUBSCRIPTION_FLOOR_ICD)}`,
54
60
  );
55
- minIntervalFloorSeconds = DEFAULT_SUBSCRIPTION_FLOOR_ICD_S;
61
+ minIntervalFloor = DEFAULT_SUBSCRIPTION_FLOOR_ICD;
56
62
  }
57
63
  }
64
+ if (minIntervalFloor === undefined) {
65
+ minIntervalFloor = DEFAULT_SUBSCRIPTION_FLOOR_DEFAULT;
66
+ }
58
67
 
59
- const defaultCeiling = isBatteryPowered
60
- ? DEFAULT_SUBSCRIPTION_CEILING_BATTERY_POWERED_S
61
- : isThreadSleepyEndDevice
62
- ? DEFAULT_SUBSCRIPTION_CEILING_THREAD_SLEEPY_S
63
- : threadConnected
64
- ? DEFAULT_SUBSCRIPTION_CEILING_THREAD_S
65
- : DEFAULT_SUBSCRIPTION_CEILING_WIFI_S;
66
- if (maxIntervalCeilingSeconds === undefined) {
67
- maxIntervalCeilingSeconds = defaultCeiling;
68
+ const defaultCeiling =
69
+ isBatteryPowered && !isMainsPowered
70
+ ? DEFAULT_SUBSCRIPTION_CEILING_BATTERY_POWERED
71
+ : isThreadSleepyEndDevice
72
+ ? DEFAULT_SUBSCRIPTION_CEILING_THREAD_SLEEPY
73
+ : threadConnected
74
+ ? DEFAULT_SUBSCRIPTION_CEILING_THREAD
75
+ : DEFAULT_SUBSCRIPTION_CEILING_WIFI;
76
+ if (maxIntervalCeiling === undefined) {
77
+ maxIntervalCeiling = defaultCeiling;
68
78
  }
69
- if (maxIntervalCeilingSeconds < defaultCeiling) {
79
+ if (maxIntervalCeiling < defaultCeiling) {
70
80
  logger.debug(
71
- `${description}: maxIntervalCeilingSeconds ideally is ${defaultCeiling}s instead of ${maxIntervalCeilingSeconds}s due to device type`,
81
+ `${description}: maxIntervalCeilingSeconds ideally is ${defaultCeiling}s instead of ${maxIntervalCeiling}s due to device type`,
72
82
  );
73
83
  }
74
84
 
75
85
  return {
76
- minIntervalFloorSeconds: minIntervalFloorSeconds ?? DEFAULT_SUBSCRIPTION_FLOOR_DEFAULT_S,
77
- maxIntervalCeilingSeconds,
86
+ minIntervalFloor,
87
+ maxIntervalCeiling,
78
88
  };
79
89
  }
80
90
  }
91
+
92
+ export namespace PhysicalDeviceProperties {
93
+ export interface IntervalBounds {
94
+ minIntervalFloor: Duration;
95
+ maxIntervalCeiling: Duration;
96
+ }
97
+ }
@@ -47,6 +47,36 @@ export class DeviceAdvertiser {
47
47
  this.#advertiseFabric(fabric, "startup");
48
48
  });
49
49
 
50
+ // When a fabric is updated we might need to adjust announcements
51
+ this.#observers.on(fabrics.events.updated, async fabric => {
52
+ if (!this.#isOperational) {
53
+ return;
54
+ }
55
+
56
+ // Look for update for this fabricIndex because other Ids might have changed and object is different
57
+ const fabricIndexAdvertisements = this.#advertisements(
58
+ ad => ad.isOperational() && ad.description.fabric.fabricIndex === fabric.fabricIndex,
59
+ );
60
+
61
+ // If the announcement relevant IDs are unchanged, do nothing
62
+ if (
63
+ fabricIndexAdvertisements.every(
64
+ ad =>
65
+ ad.isOperational() &&
66
+ ad.description.fabric.operationalId === fabric.operationalId &&
67
+ ad.description.fabric.nodeId === fabric.nodeId,
68
+ )
69
+ ) {
70
+ return;
71
+ }
72
+
73
+ for (const ad of fabricIndexAdvertisements) {
74
+ await ad.close();
75
+ }
76
+
77
+ this.#advertiseFabric(fabric, "startup");
78
+ });
79
+
50
80
  // When a fabric is deleted, cancel any active advertisement
51
81
  this.#observers.on(fabrics.events.deleted, fabric => {
52
82
  Advertisement.cancelAll(this.#advertisements(ad => ad.isOperational() && ad.description.fabric === fabric));