@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.
- package/dist/cjs/action/client/ClientInteraction.d.ts +1 -0
- package/dist/cjs/action/client/ClientInteraction.d.ts.map +1 -1
- package/dist/cjs/action/client/ClientInteraction.js +3 -0
- package/dist/cjs/action/client/ClientInteraction.js.map +1 -1
- package/dist/cjs/action/client/subscription/ClientSubscriptionHandler.js +2 -4
- package/dist/cjs/action/client/subscription/ClientSubscriptionHandler.js.map +1 -1
- package/dist/cjs/certificate/index.d.ts +1 -0
- package/dist/cjs/certificate/index.d.ts.map +1 -1
- package/dist/cjs/certificate/index.js +1 -0
- package/dist/cjs/certificate/index.js.map +1 -1
- package/dist/cjs/certificate/kinds/Icac.d.ts.map +1 -1
- package/dist/cjs/certificate/kinds/Icac.js +3 -0
- package/dist/cjs/certificate/kinds/Icac.js.map +1 -1
- package/dist/cjs/certificate/kinds/Noc.d.ts.map +1 -1
- package/dist/cjs/certificate/kinds/Noc.js +3 -0
- package/dist/cjs/certificate/kinds/Noc.js.map +1 -1
- package/dist/cjs/certificate/kinds/Rcac.d.ts.map +1 -1
- package/dist/cjs/certificate/kinds/Rcac.js +3 -0
- package/dist/cjs/certificate/kinds/Rcac.js.map +1 -1
- package/dist/cjs/certificate/kinds/Vvsc.d.ts +29 -0
- package/dist/cjs/certificate/kinds/Vvsc.d.ts.map +1 -0
- package/dist/cjs/certificate/kinds/Vvsc.js +79 -0
- package/dist/cjs/certificate/kinds/Vvsc.js.map +6 -0
- package/dist/cjs/certificate/kinds/X509Base.d.ts.map +1 -1
- package/dist/cjs/certificate/kinds/X509Base.js +3 -0
- package/dist/cjs/certificate/kinds/X509Base.js.map +1 -1
- package/dist/cjs/certificate/kinds/definitions/asn.d.ts +2 -0
- package/dist/cjs/certificate/kinds/definitions/asn.d.ts.map +1 -1
- package/dist/cjs/certificate/kinds/definitions/asn.js +2 -0
- package/dist/cjs/certificate/kinds/definitions/asn.js.map +1 -1
- package/dist/cjs/certificate/kinds/definitions/operational.d.ts +109 -0
- package/dist/cjs/certificate/kinds/definitions/operational.d.ts.map +1 -1
- package/dist/cjs/certificate/kinds/definitions/operational.js +6 -0
- package/dist/cjs/certificate/kinds/definitions/operational.js.map +1 -1
- package/dist/cjs/certificate/kinds/index.d.ts +1 -0
- package/dist/cjs/certificate/kinds/index.d.ts.map +1 -1
- package/dist/cjs/certificate/kinds/index.js +1 -0
- package/dist/cjs/certificate/kinds/index.js.map +1 -1
- package/dist/cjs/fabric/Fabric.d.ts +6 -1
- package/dist/cjs/fabric/Fabric.d.ts.map +1 -1
- package/dist/cjs/fabric/Fabric.js +53 -2
- package/dist/cjs/fabric/Fabric.js.map +1 -1
- package/dist/cjs/fabric/FabricManager.d.ts.map +1 -1
- package/dist/cjs/fabric/FabricManager.js +24 -9
- package/dist/cjs/fabric/FabricManager.js.map +1 -1
- package/dist/cjs/peer/PhysicalDeviceProperties.d.ts +11 -7
- package/dist/cjs/peer/PhysicalDeviceProperties.d.ts.map +1 -1
- package/dist/cjs/peer/PhysicalDeviceProperties.js +34 -25
- package/dist/cjs/peer/PhysicalDeviceProperties.js.map +1 -1
- package/dist/cjs/protocol/DeviceAdvertiser.d.ts.map +1 -1
- package/dist/cjs/protocol/DeviceAdvertiser.js +17 -0
- package/dist/cjs/protocol/DeviceAdvertiser.js.map +1 -1
- package/dist/esm/action/client/ClientInteraction.d.ts +1 -0
- package/dist/esm/action/client/ClientInteraction.d.ts.map +1 -1
- package/dist/esm/action/client/ClientInteraction.js +3 -0
- package/dist/esm/action/client/ClientInteraction.js.map +1 -1
- package/dist/esm/action/client/subscription/ClientSubscriptionHandler.js +2 -4
- package/dist/esm/action/client/subscription/ClientSubscriptionHandler.js.map +1 -1
- package/dist/esm/certificate/index.d.ts +1 -0
- package/dist/esm/certificate/index.d.ts.map +1 -1
- package/dist/esm/certificate/index.js +1 -0
- package/dist/esm/certificate/index.js.map +1 -1
- package/dist/esm/certificate/kinds/Icac.d.ts.map +1 -1
- package/dist/esm/certificate/kinds/Icac.js +3 -0
- package/dist/esm/certificate/kinds/Icac.js.map +1 -1
- package/dist/esm/certificate/kinds/Noc.d.ts.map +1 -1
- package/dist/esm/certificate/kinds/Noc.js +3 -0
- package/dist/esm/certificate/kinds/Noc.js.map +1 -1
- package/dist/esm/certificate/kinds/Rcac.d.ts.map +1 -1
- package/dist/esm/certificate/kinds/Rcac.js +3 -0
- package/dist/esm/certificate/kinds/Rcac.js.map +1 -1
- package/dist/esm/certificate/kinds/Vvsc.d.ts +29 -0
- package/dist/esm/certificate/kinds/Vvsc.d.ts.map +1 -0
- package/dist/esm/certificate/kinds/Vvsc.js +59 -0
- package/dist/esm/certificate/kinds/Vvsc.js.map +6 -0
- package/dist/esm/certificate/kinds/X509Base.d.ts.map +1 -1
- package/dist/esm/certificate/kinds/X509Base.js +5 -1
- package/dist/esm/certificate/kinds/X509Base.js.map +1 -1
- package/dist/esm/certificate/kinds/definitions/asn.d.ts +2 -0
- package/dist/esm/certificate/kinds/definitions/asn.d.ts.map +1 -1
- package/dist/esm/certificate/kinds/definitions/asn.js +2 -0
- package/dist/esm/certificate/kinds/definitions/asn.js.map +1 -1
- package/dist/esm/certificate/kinds/definitions/operational.d.ts +109 -0
- package/dist/esm/certificate/kinds/definitions/operational.d.ts.map +1 -1
- package/dist/esm/certificate/kinds/definitions/operational.js +6 -0
- package/dist/esm/certificate/kinds/definitions/operational.js.map +1 -1
- package/dist/esm/certificate/kinds/index.d.ts +1 -0
- package/dist/esm/certificate/kinds/index.d.ts.map +1 -1
- package/dist/esm/certificate/kinds/index.js +1 -0
- package/dist/esm/certificate/kinds/index.js.map +1 -1
- package/dist/esm/fabric/Fabric.d.ts +6 -1
- package/dist/esm/fabric/Fabric.d.ts.map +1 -1
- package/dist/esm/fabric/Fabric.js +54 -3
- package/dist/esm/fabric/Fabric.js.map +1 -1
- package/dist/esm/fabric/FabricManager.d.ts.map +1 -1
- package/dist/esm/fabric/FabricManager.js +25 -9
- package/dist/esm/fabric/FabricManager.js.map +1 -1
- package/dist/esm/peer/PhysicalDeviceProperties.d.ts +11 -7
- package/dist/esm/peer/PhysicalDeviceProperties.d.ts.map +1 -1
- package/dist/esm/peer/PhysicalDeviceProperties.js +35 -26
- package/dist/esm/peer/PhysicalDeviceProperties.js.map +1 -1
- package/dist/esm/protocol/DeviceAdvertiser.d.ts.map +1 -1
- package/dist/esm/protocol/DeviceAdvertiser.js +17 -0
- package/dist/esm/protocol/DeviceAdvertiser.js.map +1 -1
- package/package.json +6 -6
- package/src/action/client/ClientInteraction.ts +5 -1
- package/src/action/client/subscription/ClientSubscriptionHandler.ts +2 -4
- package/src/certificate/index.ts +1 -0
- package/src/certificate/kinds/Icac.ts +5 -0
- package/src/certificate/kinds/Noc.ts +5 -0
- package/src/certificate/kinds/Rcac.ts +5 -0
- package/src/certificate/kinds/Vvsc.ts +72 -0
- package/src/certificate/kinds/X509Base.ts +4 -0
- package/src/certificate/kinds/definitions/asn.ts +3 -0
- package/src/certificate/kinds/definitions/operational.ts +8 -0
- package/src/certificate/kinds/index.ts +1 -0
- package/src/fabric/Fabric.ts +66 -3
- package/src/fabric/FabricManager.ts +32 -9
- package/src/peer/PhysicalDeviceProperties.ts +52 -35
- 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
|
}
|
package/src/fabric/Fabric.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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.#
|
|
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.#
|
|
162
|
+
this.#addNewFabric(fabric);
|
|
160
163
|
}
|
|
161
164
|
|
|
162
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const
|
|
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
|
|
30
|
+
export function subscriptionIntervalBoundsFor(options?: {
|
|
30
31
|
properties?: PhysicalDeviceProperties;
|
|
31
32
|
description?: string;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const { properties } = options ?? {};
|
|
33
|
+
request?: Partial<PhysicalDeviceProperties.IntervalBounds>;
|
|
34
|
+
}): PhysicalDeviceProperties.IntervalBounds {
|
|
35
|
+
const { properties, request } = options ?? {};
|
|
36
36
|
|
|
37
|
-
let {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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 {
|
|
48
|
-
|
|
48
|
+
const {
|
|
49
|
+
isMainsPowered,
|
|
50
|
+
isBatteryPowered,
|
|
51
|
+
isIntermittentlyConnected,
|
|
52
|
+
threadConnected,
|
|
53
|
+
isThreadSleepyEndDevice,
|
|
54
|
+
} = properties ?? {};
|
|
49
55
|
|
|
50
56
|
if (isIntermittentlyConnected) {
|
|
51
|
-
if (
|
|
57
|
+
if (minIntervalFloor !== undefined && minIntervalFloor !== DEFAULT_SUBSCRIPTION_FLOOR_ICD) {
|
|
52
58
|
logger.info(
|
|
53
|
-
`${description}: Overwriting minIntervalFloorSeconds for intermittently connected device to ${
|
|
59
|
+
`${description}: Overwriting minIntervalFloorSeconds for intermittently connected device to ${Duration.format(DEFAULT_SUBSCRIPTION_FLOOR_ICD)}`,
|
|
54
60
|
);
|
|
55
|
-
|
|
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 =
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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 (
|
|
79
|
+
if (maxIntervalCeiling < defaultCeiling) {
|
|
70
80
|
logger.debug(
|
|
71
|
-
`${description}: maxIntervalCeilingSeconds ideally is ${defaultCeiling}s instead of ${
|
|
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
|
-
|
|
77
|
-
|
|
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));
|