@project-chip/matter.js 0.16.0-alpha.0-20251020-3f6e46245 → 0.16.0-alpha.0-20251022-5a69ce65a
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/CommissioningController.d.ts +4 -42
- package/dist/cjs/CommissioningController.d.ts.map +1 -1
- package/dist/cjs/CommissioningController.js +17 -93
- package/dist/cjs/CommissioningController.js.map +1 -1
- package/dist/cjs/MatterController.d.ts +39 -68
- package/dist/cjs/MatterController.d.ts.map +1 -1
- package/dist/cjs/MatterController.js +175 -212
- package/dist/cjs/MatterController.js.map +2 -2
- package/dist/cjs/PaseCommissioner.d.ts.map +1 -1
- package/dist/cjs/PaseCommissioner.js +4 -14
- package/dist/cjs/PaseCommissioner.js.map +2 -2
- package/dist/esm/CommissioningController.d.ts +4 -42
- package/dist/esm/CommissioningController.d.ts.map +1 -1
- package/dist/esm/CommissioningController.js +18 -101
- package/dist/esm/CommissioningController.js.map +1 -1
- package/dist/esm/MatterController.d.ts +39 -68
- package/dist/esm/MatterController.d.ts.map +1 -1
- package/dist/esm/MatterController.js +178 -221
- package/dist/esm/MatterController.js.map +2 -2
- package/dist/esm/PaseCommissioner.d.ts.map +1 -1
- package/dist/esm/PaseCommissioner.js +5 -17
- package/dist/esm/PaseCommissioner.js.map +1 -1
- package/package.json +8 -8
- package/src/CommissioningController.ts +22 -126
- package/src/MatterController.ts +240 -268
- package/src/PaseCommissioner.ts +4 -18
- package/src/tsconfig.json +2 -2
package/src/MatterController.ts
CHANGED
|
@@ -11,17 +11,17 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { GeneralCommissioning } from "#clusters";
|
|
14
|
-
import { NodeCommissioningOptions } from "#CommissioningController.js";
|
|
14
|
+
import type { NodeCommissioningOptions } from "#CommissioningController.js";
|
|
15
15
|
import { ControllerStoreInterface } from "#ControllerStore.js";
|
|
16
16
|
import { CachedClientNodeStore } from "#device/CachedClientNodeStore.js";
|
|
17
17
|
import { DeviceInformationData } from "#device/DeviceInformation.js";
|
|
18
18
|
import {
|
|
19
19
|
Bytes,
|
|
20
20
|
ChannelType,
|
|
21
|
+
ClassExtends,
|
|
21
22
|
ConnectionlessTransportSet,
|
|
22
23
|
Construction,
|
|
23
24
|
Crypto,
|
|
24
|
-
CRYPTO_SYMMETRIC_KEY_LENGTH,
|
|
25
25
|
Environment,
|
|
26
26
|
ImplementationError,
|
|
27
27
|
Logger,
|
|
@@ -33,33 +33,29 @@ import {
|
|
|
33
33
|
StorageManager,
|
|
34
34
|
} from "#general";
|
|
35
35
|
import { LegacyControllerStore } from "#LegacyControllerStore.js";
|
|
36
|
+
import { InteractionServer, ServerNode } from "#node";
|
|
36
37
|
import {
|
|
37
|
-
Advertiser,
|
|
38
38
|
CertificateAuthority,
|
|
39
|
-
ChannelManager,
|
|
40
39
|
ClusterClient,
|
|
41
40
|
CommissioningError,
|
|
42
41
|
ControllerCommissioner,
|
|
42
|
+
ControllerCommissioningFlow,
|
|
43
43
|
DecodedAttributeReportValue,
|
|
44
|
-
DEFAULT_ADMIN_VENDOR_ID,
|
|
45
|
-
DeviceAdvertiser,
|
|
46
44
|
DiscoveryAndCommissioningOptions,
|
|
47
45
|
DiscoveryData,
|
|
48
|
-
ExchangeManager,
|
|
49
46
|
Fabric,
|
|
50
|
-
|
|
47
|
+
FabricAuthority,
|
|
51
48
|
FabricManager,
|
|
52
49
|
InteractionClientProvider,
|
|
50
|
+
MessageChannel,
|
|
53
51
|
NodeDiscoveryType,
|
|
54
52
|
OperationalPeer,
|
|
55
53
|
PeerAddress,
|
|
56
54
|
PeerAddressStore,
|
|
57
55
|
PeerConnectionOptions,
|
|
58
56
|
PeerSet,
|
|
59
|
-
ResumptionRecord,
|
|
60
57
|
RetransmissionLimitReachedError,
|
|
61
58
|
ScannerSet,
|
|
62
|
-
SecureChannelProtocol,
|
|
63
59
|
SessionManager,
|
|
64
60
|
SubscriptionClient,
|
|
65
61
|
} from "#protocol";
|
|
@@ -74,8 +70,6 @@ import {
|
|
|
74
70
|
TypeFromPartialBitSchema,
|
|
75
71
|
VendorId,
|
|
76
72
|
} from "#types";
|
|
77
|
-
import { ClassExtends } from "@matter/general";
|
|
78
|
-
import { ControllerCommissioningFlow, MessageChannel } from "@matter/protocol";
|
|
79
73
|
|
|
80
74
|
export type CommissionedNodeDetails = {
|
|
81
75
|
operationalServerAddress?: ServerAddressUdp;
|
|
@@ -85,9 +79,6 @@ export type CommissionedNodeDetails = {
|
|
|
85
79
|
|
|
86
80
|
const DEFAULT_FABRIC_INDEX = FabricIndex(1);
|
|
87
81
|
|
|
88
|
-
const CONTROLLER_CONNECTIONS_PER_FABRIC_AND_NODE = 3;
|
|
89
|
-
const CONTROLLER_MAX_PATHS_PER_INVOKE = 10;
|
|
90
|
-
|
|
91
82
|
const logger = Logger.get("MatterController");
|
|
92
83
|
|
|
93
84
|
// Operational peer extended with basic information as required for conversion to CommissionedNodeDetails
|
|
@@ -98,56 +89,54 @@ type StoredOperationalPeer = [NodeId, CommissionedNodeDetails];
|
|
|
98
89
|
|
|
99
90
|
export class MatterController {
|
|
100
91
|
public static async create(options: {
|
|
92
|
+
id: string;
|
|
101
93
|
controllerStore: ControllerStoreInterface;
|
|
102
|
-
scanners: ScannerSet;
|
|
103
|
-
transports: ConnectionlessTransportSet;
|
|
104
94
|
sessionClosedCallback?: (peerNodeId: NodeId) => void;
|
|
95
|
+
rootCertificateAuthority?: CertificateAuthority;
|
|
96
|
+
rootFabric?: Fabric;
|
|
105
97
|
adminVendorId?: VendorId;
|
|
106
|
-
adminFabricId?: FabricId;
|
|
107
98
|
adminFabricIndex?: FabricIndex;
|
|
108
99
|
caseAuthenticatedTags?: CaseAuthenticatedTag[];
|
|
109
|
-
adminFabricLabel: string;
|
|
110
100
|
rootNodeId?: NodeId;
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
101
|
+
adminFabricId?: FabricId;
|
|
102
|
+
adminFabricLabel: string;
|
|
103
|
+
ble?: boolean;
|
|
104
|
+
ipv4?: boolean;
|
|
105
|
+
listeningAddressIpv4?: string;
|
|
106
|
+
listeningAddressIpv6?: string;
|
|
107
|
+
localPort?: number;
|
|
108
|
+
environment: Environment;
|
|
114
109
|
}): Promise<MatterController> {
|
|
115
|
-
const crypto = options.
|
|
116
|
-
|
|
110
|
+
const crypto = options.environment.get(Crypto);
|
|
117
111
|
const {
|
|
118
112
|
controllerStore,
|
|
119
|
-
scanners,
|
|
120
|
-
transports: netInterfaces,
|
|
121
|
-
sessionClosedCallback,
|
|
122
|
-
adminVendorId,
|
|
123
|
-
adminFabricId = FabricId(crypto.randomBigInt(8)),
|
|
124
|
-
adminFabricIndex = FabricIndex(DEFAULT_FABRIC_INDEX),
|
|
125
|
-
caseAuthenticatedTags,
|
|
126
|
-
adminFabricLabel,
|
|
127
|
-
rootNodeId,
|
|
128
|
-
rootCertificateAuthority,
|
|
129
113
|
rootFabric,
|
|
114
|
+
rootCertificateAuthority,
|
|
115
|
+
adminFabricIndex = FabricIndex(DEFAULT_FABRIC_INDEX),
|
|
116
|
+
environment,
|
|
130
117
|
} = options;
|
|
131
118
|
|
|
119
|
+
if (adminFabricIndex !== FabricIndex(1)) {
|
|
120
|
+
logger.warn(
|
|
121
|
+
"Fabric Indices will be assigned automatically from now on. Specifying a custom Fabric Index is deprecated.",
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Use provided CA or create a new CA pointing to the legacy storage location
|
|
126
|
+
// Needs migration of data
|
|
132
127
|
const ca = rootCertificateAuthority ?? (await CertificateAuthority.create(crypto, controllerStore.caStorage));
|
|
133
|
-
|
|
128
|
+
environment.set(CertificateAuthority, ca);
|
|
134
129
|
|
|
135
130
|
let controller: MatterController | undefined = undefined;
|
|
136
|
-
|
|
131
|
+
let fabric: Fabric | undefined = undefined;
|
|
132
|
+
|
|
133
|
+
// Initializes Fabric from legacy storage location, or validate the provided fabric with the CA
|
|
134
|
+
// Requires data migration later maybe
|
|
135
|
+
const fabricStorage = controllerStore.fabricStorage;
|
|
137
136
|
if (rootFabric !== undefined || (await fabricStorage.has("fabric"))) {
|
|
138
|
-
|
|
137
|
+
fabric = rootFabric ?? new Fabric(crypto, await fabricStorage.get<Fabric.Config>("fabric"));
|
|
139
138
|
if (Bytes.areEqual(fabric.rootCert, ca.rootCert)) {
|
|
140
|
-
logger.info("
|
|
141
|
-
controller = new MatterController({
|
|
142
|
-
controllerStore,
|
|
143
|
-
scanners,
|
|
144
|
-
transports: netInterfaces,
|
|
145
|
-
certificateManager: ca,
|
|
146
|
-
fabric,
|
|
147
|
-
adminFabricLabel,
|
|
148
|
-
sessionClosedCallback,
|
|
149
|
-
});
|
|
150
|
-
fabric.storage = fabricStorage;
|
|
139
|
+
logger.info("Using existing fabric");
|
|
151
140
|
} else {
|
|
152
141
|
if (rootFabric !== undefined) {
|
|
153
142
|
throw new MatterError("Fabric CA certificate is not in sync with CA.");
|
|
@@ -158,248 +147,234 @@ export class MatterController {
|
|
|
158
147
|
"Fabric certificate changed, but commissioned nodes are still present. Please clear the storage.",
|
|
159
148
|
);
|
|
160
149
|
}
|
|
150
|
+
fabric = undefined; // Force re-creation of fabric
|
|
161
151
|
}
|
|
162
152
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
.setRootNodeId(controllerNodeId)
|
|
171
|
-
.setIdentityProtectionKey(ipkValue)
|
|
172
|
-
.setRootVendorId(adminVendorId ?? DEFAULT_ADMIN_VENDOR_ID)
|
|
173
|
-
.setLabel(adminFabricLabel);
|
|
174
|
-
await fabricBuilder.setOperationalCert(
|
|
175
|
-
await ca.generateNoc(fabricBuilder.publicKey, adminFabricId, controllerNodeId, caseAuthenticatedTags),
|
|
176
|
-
);
|
|
177
|
-
const fabric = await fabricBuilder.build(adminFabricIndex);
|
|
178
|
-
fabric.storage = fabricStorage;
|
|
179
|
-
|
|
180
|
-
controller = new MatterController({
|
|
181
|
-
controllerStore,
|
|
182
|
-
scanners,
|
|
183
|
-
transports: netInterfaces,
|
|
184
|
-
certificateManager: ca,
|
|
185
|
-
fabric,
|
|
186
|
-
adminFabricLabel,
|
|
187
|
-
sessionClosedCallback,
|
|
188
|
-
});
|
|
189
|
-
}
|
|
153
|
+
|
|
154
|
+
controller = new MatterController({
|
|
155
|
+
...options,
|
|
156
|
+
controllerStore,
|
|
157
|
+
fabric,
|
|
158
|
+
});
|
|
159
|
+
|
|
190
160
|
await controller.construction;
|
|
191
161
|
return controller;
|
|
192
162
|
}
|
|
193
163
|
|
|
194
164
|
public static async createAsPaseCommissioner(options: {
|
|
165
|
+
id: string;
|
|
166
|
+
sessionClosedCallback?: (peerNodeId: NodeId) => void;
|
|
195
167
|
certificateAuthorityConfig?: CertificateAuthority.Configuration;
|
|
196
168
|
rootCertificateAuthority?: CertificateAuthority;
|
|
197
169
|
fabricConfig: Fabric.Config;
|
|
198
|
-
scanners: ScannerSet;
|
|
199
|
-
transports: ConnectionlessTransportSet;
|
|
200
170
|
adminFabricLabel: string;
|
|
201
|
-
|
|
202
|
-
|
|
171
|
+
adminFabricId?: FabricId;
|
|
172
|
+
ble?: boolean;
|
|
173
|
+
ipv4?: boolean;
|
|
174
|
+
listeningAddressIpv4?: string;
|
|
175
|
+
listeningAddressIpv6?: string;
|
|
176
|
+
localPort?: number;
|
|
177
|
+
environment: Environment;
|
|
203
178
|
}): Promise<MatterController> {
|
|
204
|
-
const {
|
|
205
|
-
|
|
206
|
-
rootCertificateAuthority,
|
|
207
|
-
fabricConfig,
|
|
208
|
-
adminFabricLabel,
|
|
209
|
-
scanners,
|
|
210
|
-
transports: netInterfaces,
|
|
211
|
-
sessionClosedCallback,
|
|
212
|
-
} = options;
|
|
213
|
-
|
|
214
|
-
const crypto = options.crypto ?? Environment.default.get(Crypto);
|
|
215
|
-
|
|
216
|
-
// Verify an appropriate network interface is available
|
|
217
|
-
if (!netInterfaces.hasInterfaceFor(ChannelType.BLE)) {
|
|
218
|
-
if (!scanners.hasScannerFor(ChannelType.UDP) || !netInterfaces.hasInterfaceFor(ChannelType.UDP, "::")) {
|
|
219
|
-
throw new ImplementationError(
|
|
220
|
-
"Ble must be initialized to create a Sub Commissioner without an IP network!",
|
|
221
|
-
);
|
|
222
|
-
}
|
|
223
|
-
logger.info("BLE is not enabled. Using only IP network for commissioning.");
|
|
224
|
-
}
|
|
179
|
+
const { certificateAuthorityConfig, rootCertificateAuthority, fabricConfig, environment } = options;
|
|
180
|
+
const crypto = environment.get(Crypto);
|
|
225
181
|
|
|
226
182
|
if (rootCertificateAuthority === undefined && certificateAuthorityConfig === undefined) {
|
|
227
183
|
throw new ImplementationError("Either rootCertificateAuthority or certificateAuthorityConfig must be set.");
|
|
228
184
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
rootCertificateAuthority ?? (await CertificateAuthority.create(crypto, certificateAuthorityConfig));
|
|
185
|
+
const ca = rootCertificateAuthority ?? (await CertificateAuthority.create(crypto, certificateAuthorityConfig));
|
|
186
|
+
environment.set(CertificateAuthority, ca);
|
|
232
187
|
|
|
233
188
|
// Stored data are temporary anyway and no node will be connected, so just use an in-memory storage
|
|
234
189
|
const storageManager = new StorageManager(new StorageBackendMemory());
|
|
235
190
|
await storageManager.initialize();
|
|
236
191
|
|
|
237
192
|
const fabric = new Fabric(crypto, fabricConfig);
|
|
193
|
+
if (!Bytes.areEqual(fabric.rootCert, ca.rootCert)) {
|
|
194
|
+
throw new MatterError("Fabric CA certificate is not in sync with CA.");
|
|
195
|
+
}
|
|
196
|
+
|
|
238
197
|
// Check if we have a fabric stored in the storage, if yes initialize this one, else build a new one
|
|
239
198
|
const controller = new MatterController({
|
|
199
|
+
...options,
|
|
240
200
|
controllerStore: new LegacyControllerStore(storageManager.createContext("Commissioner")),
|
|
241
|
-
scanners,
|
|
242
|
-
transports: netInterfaces,
|
|
243
|
-
certificateManager,
|
|
244
201
|
fabric,
|
|
245
|
-
adminFabricLabel,
|
|
246
|
-
sessionClosedCallback,
|
|
247
202
|
});
|
|
248
203
|
await controller.construction;
|
|
204
|
+
|
|
205
|
+
// Verify an appropriate network interface is available
|
|
206
|
+
const netInterfaces = environment.get(ConnectionlessTransportSet);
|
|
207
|
+
if (!netInterfaces.hasInterfaceFor(ChannelType.BLE)) {
|
|
208
|
+
if (
|
|
209
|
+
!environment.get(ScannerSet).hasScannerFor(ChannelType.UDP) ||
|
|
210
|
+
!netInterfaces.hasInterfaceFor(ChannelType.UDP, "::")
|
|
211
|
+
) {
|
|
212
|
+
throw new ImplementationError(
|
|
213
|
+
"Ble must be initialized to create a Sub Commissioner without an IP network!",
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
logger.info("BLE is not enabled. Using only IP network for commissioning.");
|
|
217
|
+
}
|
|
218
|
+
|
|
249
219
|
return controller;
|
|
250
220
|
}
|
|
251
221
|
|
|
252
|
-
readonly sessionManager: SessionManager;
|
|
253
|
-
private readonly transports = new ConnectionlessTransportSet();
|
|
254
|
-
private readonly channelManager = new ChannelManager(CONTROLLER_CONNECTIONS_PER_FABRIC_AND_NODE);
|
|
255
|
-
private readonly exchangeManager: ExchangeManager;
|
|
256
|
-
private readonly peers: PeerSet;
|
|
257
|
-
private readonly clients: InteractionClientProvider;
|
|
258
|
-
private readonly commissioner: ControllerCommissioner;
|
|
259
222
|
#construction: Construction<MatterController>;
|
|
260
|
-
|
|
261
|
-
#
|
|
262
|
-
|
|
263
|
-
private readonly scanners: ScannerSet;
|
|
264
|
-
private readonly ca: CertificateAuthority;
|
|
265
|
-
private readonly fabric: Fabric;
|
|
266
|
-
private readonly sessionClosedCallback?: (peerNodeId: NodeId) => void;
|
|
267
|
-
#advertiser: DeviceAdvertiser;
|
|
223
|
+
#node?: ServerNode;
|
|
224
|
+
#peers?: PeerSet;
|
|
225
|
+
#fabric?: Fabric;
|
|
268
226
|
|
|
269
227
|
get construction() {
|
|
270
228
|
return this.#construction;
|
|
271
229
|
}
|
|
272
230
|
|
|
273
231
|
constructor(options: {
|
|
232
|
+
id: string;
|
|
274
233
|
controllerStore: ControllerStoreInterface;
|
|
275
|
-
|
|
276
|
-
transports: ConnectionlessTransportSet;
|
|
277
|
-
certificateManager: CertificateAuthority;
|
|
278
|
-
fabric: Fabric;
|
|
279
|
-
adminFabricLabel: string;
|
|
234
|
+
fabric?: Fabric;
|
|
280
235
|
sessionClosedCallback?: (peerNodeId: NodeId) => void;
|
|
236
|
+
ble?: boolean;
|
|
237
|
+
adminFabricId?: FabricId;
|
|
238
|
+
adminFabricLabel: string;
|
|
239
|
+
adminVendorId?: VendorId;
|
|
240
|
+
rootNodeId?: NodeId;
|
|
241
|
+
caseAuthenticatedTags?: CaseAuthenticatedTag[];
|
|
242
|
+
ipv4?: boolean;
|
|
243
|
+
listeningAddressIpv4?: string;
|
|
244
|
+
listeningAddressIpv6?: string;
|
|
245
|
+
localPort?: number;
|
|
246
|
+
environment: Environment;
|
|
281
247
|
}) {
|
|
248
|
+
const crypto = options.environment.get(Crypto);
|
|
282
249
|
const {
|
|
283
250
|
controllerStore,
|
|
284
|
-
scanners,
|
|
285
|
-
transports: netInterfaces,
|
|
286
|
-
certificateManager,
|
|
287
|
-
fabric,
|
|
288
251
|
sessionClosedCallback,
|
|
252
|
+
ble = false,
|
|
289
253
|
adminFabricLabel,
|
|
254
|
+
adminFabricId = FabricId(crypto.randomBigInt(8)),
|
|
255
|
+
adminVendorId,
|
|
256
|
+
rootNodeId,
|
|
257
|
+
caseAuthenticatedTags,
|
|
258
|
+
ipv4 = true,
|
|
259
|
+
listeningAddressIpv4,
|
|
260
|
+
listeningAddressIpv6,
|
|
261
|
+
localPort,
|
|
262
|
+
environment,
|
|
263
|
+
id,
|
|
264
|
+
fabric,
|
|
290
265
|
} = options;
|
|
291
|
-
this.#store = controllerStore;
|
|
292
|
-
this.scanners = scanners;
|
|
293
|
-
this.transports = netInterfaces;
|
|
294
|
-
this.ca = certificateManager;
|
|
295
|
-
this.fabric = fabric;
|
|
296
|
-
this.sessionClosedCallback = sessionClosedCallback;
|
|
297
|
-
|
|
298
|
-
const fabricManager = new FabricManager(fabric.crypto);
|
|
299
|
-
fabricManager.addFabric(fabric);
|
|
300
|
-
// Overwrite the persist callback and store fabric when needed
|
|
301
|
-
fabric.persistCallback = async () => {
|
|
302
|
-
await this.#store.fabricStorage.set("fabric", this.fabric.config);
|
|
303
|
-
};
|
|
304
|
-
|
|
305
|
-
this.sessionManager = new SessionManager({
|
|
306
|
-
fabrics: fabricManager,
|
|
307
|
-
storage: controllerStore.sessionStorage,
|
|
308
|
-
parameters: {
|
|
309
|
-
maxPathsPerInvoke: CONTROLLER_MAX_PATHS_PER_INVOKE,
|
|
310
|
-
},
|
|
311
|
-
});
|
|
312
|
-
this.sessionManager.sessions.deleted.on(async session => {
|
|
313
|
-
this.sessionClosedCallback?.(session.peerNodeId);
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
const subscriptionClient = new SubscriptionClient();
|
|
317
266
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
// Adapts the historical storage format for MatterController to OperationalPeer objects
|
|
328
|
-
this.nodesStore = new CommissionedNodeStore(controllerStore, fabric);
|
|
329
|
-
|
|
330
|
-
this.nodesStore.peers = this.peers = new PeerSet({
|
|
331
|
-
sessions: this.sessionManager,
|
|
332
|
-
channels: this.channelManager,
|
|
333
|
-
exchanges: this.exchangeManager,
|
|
334
|
-
subscriptionClient,
|
|
335
|
-
scanners: this.scanners,
|
|
336
|
-
transports: this.transports,
|
|
337
|
-
store: this.nodesStore,
|
|
338
|
-
});
|
|
267
|
+
// Initialize a Fabric Manager without a connected storage because we only have one fabric, and we manage the
|
|
268
|
+
// storage ourselves.
|
|
269
|
+
// Data migration needed
|
|
270
|
+
const fabricManager = new FabricManager(crypto);
|
|
271
|
+
environment.set(FabricManager, fabricManager);
|
|
272
|
+
if (fabric !== undefined) {
|
|
273
|
+
fabricManager.addFabric(fabric);
|
|
274
|
+
}
|
|
339
275
|
|
|
340
|
-
this
|
|
276
|
+
this.#construction = Construction(this, async () => {
|
|
277
|
+
const persistFabric = async (fabric: Fabric) => controllerStore.fabricStorage.set("fabric", fabric.config);
|
|
278
|
+
|
|
279
|
+
// Initialize Fabric Authority to retrieve the self-added fabric or create a new one
|
|
280
|
+
// Also tweak the storage as needed because we manage storage ourselves
|
|
281
|
+
// Data migration needed
|
|
282
|
+
// Can be removed when we use "commission" via Commissioning behavior
|
|
283
|
+
const fabricAuth = environment.get(FabricAuthority);
|
|
284
|
+
fabricAuth.fabricAdded.on(persistFabric);
|
|
285
|
+
const fabric = await fabricAuth.defaultFabric({
|
|
286
|
+
adminFabricLabel,
|
|
287
|
+
adminVendorId,
|
|
288
|
+
adminFabricId,
|
|
289
|
+
caseAuthenticatedTags,
|
|
290
|
+
adminNodeId: rootNodeId,
|
|
291
|
+
});
|
|
292
|
+
fabric.storage = controllerStore.fabricStorage;
|
|
293
|
+
fabric.persistCallback = () => persistFabric(fabric);
|
|
294
|
+
this.#fabric = fabric;
|
|
295
|
+
|
|
296
|
+
// Initialize custom PeerAddressStore to manage commissioned nodes storage in legacy storage format
|
|
297
|
+
// Data migration needed
|
|
298
|
+
const nodesStore = new CommissionedNodeStore(controllerStore, fabric);
|
|
299
|
+
environment.set(PeerAddressStore, nodesStore);
|
|
300
|
+
|
|
301
|
+
// Now after all Legacy stuff is prepared, initialize the ServerNode
|
|
302
|
+
this.#node = await ServerNode.create({
|
|
303
|
+
environment,
|
|
304
|
+
id,
|
|
305
|
+
network: {
|
|
306
|
+
ble,
|
|
307
|
+
ipv4,
|
|
308
|
+
listeningAddressIpv4,
|
|
309
|
+
listeningAddressIpv6,
|
|
310
|
+
port: localPort,
|
|
311
|
+
},
|
|
312
|
+
basicInformation: {
|
|
313
|
+
vendorId: adminVendorId,
|
|
314
|
+
},
|
|
315
|
+
controller: {
|
|
316
|
+
adminFabricLabel,
|
|
317
|
+
adminFabricId,
|
|
318
|
+
adminNodeId: rootNodeId,
|
|
319
|
+
caseAuthenticatedTags,
|
|
320
|
+
},
|
|
321
|
+
commissioning: {
|
|
322
|
+
enabled: false, // The node is never commissionable directly
|
|
323
|
+
},
|
|
324
|
+
subscriptions: {
|
|
325
|
+
persistenceEnabled: false, // We do not want to reestablish subscriptions on restart
|
|
326
|
+
},
|
|
327
|
+
});
|
|
341
328
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
scanners: this.scanners,
|
|
346
|
-
transports: this.transports,
|
|
347
|
-
exchanges: this.exchangeManager,
|
|
348
|
-
sessions: this.sessionManager,
|
|
349
|
-
ca: this.ca,
|
|
350
|
-
});
|
|
329
|
+
this.#node.env
|
|
330
|
+
.get(SessionManager)
|
|
331
|
+
.sessions.deleted.on(async session => sessionClosedCallback?.(session.peerNodeId));
|
|
351
332
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
sessions: this.sessionManager,
|
|
355
|
-
});
|
|
333
|
+
this.#peers = this.#node.env.get(PeerSet);
|
|
334
|
+
nodesStore.peers = this.#peers;
|
|
356
335
|
|
|
357
|
-
|
|
358
|
-
await this.peers.construction.ready;
|
|
359
|
-
await this.sessionManager.construction.ready;
|
|
360
|
-
if (this.fabric.label !== adminFabricLabel) {
|
|
336
|
+
if (this.#fabric.label !== adminFabricLabel) {
|
|
361
337
|
await fabric.setLabel(adminFabricLabel);
|
|
362
338
|
}
|
|
363
339
|
});
|
|
364
340
|
}
|
|
365
341
|
|
|
342
|
+
get ble() {
|
|
343
|
+
this.#construction.assert();
|
|
344
|
+
return this.#node!.state.network.ble ?? false;
|
|
345
|
+
}
|
|
346
|
+
|
|
366
347
|
get nodeId() {
|
|
367
|
-
|
|
348
|
+
this.#construction.assert();
|
|
349
|
+
return this.#fabric!.rootNodeId;
|
|
368
350
|
}
|
|
369
351
|
|
|
370
352
|
get caConfig() {
|
|
371
|
-
|
|
353
|
+
this.#construction.assert();
|
|
354
|
+
return this.#node!.env.get(CertificateAuthority).config;
|
|
372
355
|
}
|
|
373
356
|
|
|
374
357
|
get fabricConfig() {
|
|
375
|
-
|
|
358
|
+
this.#construction.assert();
|
|
359
|
+
return this.#fabric!.config;
|
|
376
360
|
}
|
|
377
361
|
|
|
378
362
|
get sessions() {
|
|
379
|
-
|
|
363
|
+
this.#construction.assert();
|
|
364
|
+
return this.#node!.env.get(SessionManager).sessions;
|
|
380
365
|
}
|
|
381
366
|
|
|
382
367
|
getFabrics() {
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
hasAdvertiser(advertiser: Advertiser) {
|
|
387
|
-
return this.#advertiser.hasAdvertiser(advertiser);
|
|
368
|
+
this.#construction.assert();
|
|
369
|
+
return [this.#fabric!];
|
|
388
370
|
}
|
|
389
371
|
|
|
390
|
-
|
|
391
|
-
this.#advertiser.addAdvertiser(advertiser);
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
async deleteBroadcaster(advertiser: Advertiser) {
|
|
395
|
-
await this.#advertiser.deleteAdvertiser(advertiser);
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
public collectScanners(
|
|
372
|
+
collectScanners(
|
|
399
373
|
discoveryCapabilities: TypeFromPartialBitSchema<typeof DiscoveryCapabilitiesBitmap> = { onIpNetwork: true },
|
|
400
374
|
) {
|
|
375
|
+
this.#construction.assert();
|
|
401
376
|
// Note we always scan via MDNS if available
|
|
402
|
-
return this.
|
|
377
|
+
return this.#node!.env.get(ScannerSet).filter(
|
|
403
378
|
scanner =>
|
|
404
379
|
scanner.type === ChannelType.UDP || (discoveryCapabilities.ble && scanner.type === ChannelType.BLE),
|
|
405
380
|
);
|
|
@@ -423,9 +398,10 @@ export class MatterController {
|
|
|
423
398
|
commissioningFlowImpl?: ClassExtends<ControllerCommissioningFlow>;
|
|
424
399
|
},
|
|
425
400
|
): Promise<NodeId> {
|
|
401
|
+
this.#construction.assert();
|
|
426
402
|
const commissioningOptions: DiscoveryAndCommissioningOptions = {
|
|
427
403
|
...options.commissioning,
|
|
428
|
-
fabric: this
|
|
404
|
+
fabric: this.#fabric!,
|
|
429
405
|
discovery: options.discovery,
|
|
430
406
|
passcode: options.passcode,
|
|
431
407
|
};
|
|
@@ -442,21 +418,23 @@ export class MatterController {
|
|
|
442
418
|
}
|
|
443
419
|
commissioningOptions.commissioningFlowImpl = commissioningFlowImpl;
|
|
444
420
|
|
|
445
|
-
const address = await this.
|
|
421
|
+
const address = await this.#node!.env.get(ControllerCommissioner).commissionWithDiscovery(commissioningOptions);
|
|
446
422
|
|
|
447
|
-
await this
|
|
423
|
+
await this.#fabric!.persist();
|
|
448
424
|
|
|
449
425
|
return address.nodeId;
|
|
450
426
|
}
|
|
451
427
|
|
|
452
428
|
async disconnect(nodeId: NodeId) {
|
|
453
|
-
|
|
429
|
+
this.#construction.assert();
|
|
430
|
+
return this.#peers!.disconnect(this.#fabric!.addressOf(nodeId));
|
|
454
431
|
}
|
|
455
432
|
|
|
456
433
|
async connectPaseChannel(options: NodeCommissioningOptions) {
|
|
457
|
-
|
|
434
|
+
this.#construction.assert();
|
|
435
|
+
const { paseSecureChannel } = await this.#node!.env.get(ControllerCommissioner).discoverAndEstablishPase({
|
|
458
436
|
...options.commissioning,
|
|
459
|
-
fabric: this
|
|
437
|
+
fabric: this.#fabric!,
|
|
460
438
|
discovery: options.discovery,
|
|
461
439
|
passcode: options.passcode,
|
|
462
440
|
});
|
|
@@ -465,13 +443,15 @@ export class MatterController {
|
|
|
465
443
|
}
|
|
466
444
|
|
|
467
445
|
async removeNode(nodeId: NodeId) {
|
|
468
|
-
|
|
446
|
+
this.#construction.assert();
|
|
447
|
+
return this.#peers!.delete(this.#fabric!.addressOf(nodeId));
|
|
469
448
|
}
|
|
470
449
|
|
|
471
450
|
/**
|
|
472
451
|
* Method to complete the commissioning process to a node which was initialized with a PASE secure channel.
|
|
473
452
|
*/
|
|
474
453
|
async completeCommissioning(peerNodeId: NodeId, discoveryData?: DiscoveryData) {
|
|
454
|
+
this.#construction.assert();
|
|
475
455
|
// Look for the device broadcast over MDNS and do CASE pairing
|
|
476
456
|
const interactionClient = await this.connect(peerNodeId, {
|
|
477
457
|
discoveryOptions: {
|
|
@@ -491,22 +471,25 @@ export class MatterController {
|
|
|
491
471
|
});
|
|
492
472
|
if (errorCode !== GeneralCommissioning.CommissioningError.Ok) {
|
|
493
473
|
// We might have added data for an operational address that we need to cleanup
|
|
494
|
-
await this
|
|
474
|
+
await this.#peers!.delete(this.#fabric!.addressOf(peerNodeId));
|
|
495
475
|
throw new CommissioningError(`Commission error on commissioningComplete: ${errorCode}, ${debugText}`);
|
|
496
476
|
}
|
|
497
|
-
await this
|
|
477
|
+
await this.#fabric!.persist();
|
|
498
478
|
}
|
|
499
479
|
|
|
500
480
|
isCommissioned() {
|
|
501
|
-
|
|
481
|
+
this.#construction.assert();
|
|
482
|
+
return !!this.#peers!.size;
|
|
502
483
|
}
|
|
503
484
|
|
|
504
485
|
getCommissionedNodes() {
|
|
505
|
-
|
|
486
|
+
this.#construction.assert();
|
|
487
|
+
return this.#peers!.map(peer => peer.address.nodeId);
|
|
506
488
|
}
|
|
507
489
|
|
|
508
490
|
getCommissionedNodesDetails() {
|
|
509
|
-
|
|
491
|
+
this.#construction.assert();
|
|
492
|
+
return this.#peers!.map(peer => {
|
|
510
493
|
const { address, operationalAddress, discoveryData, deviceData } = peer as CommissionedPeer;
|
|
511
494
|
return {
|
|
512
495
|
nodeId: address.nodeId,
|
|
@@ -519,7 +502,8 @@ export class MatterController {
|
|
|
519
502
|
}
|
|
520
503
|
|
|
521
504
|
getCommissionedNodeDetails(nodeId: NodeId) {
|
|
522
|
-
|
|
505
|
+
this.#construction.assert();
|
|
506
|
+
const nodeDetails = this.#peers!.get(this.#fabric!.addressOf(nodeId)) as CommissionedPeer;
|
|
523
507
|
if (nodeDetails === undefined) {
|
|
524
508
|
throw new Error(`Node ${nodeId} is not commissioned.`);
|
|
525
509
|
}
|
|
@@ -534,12 +518,13 @@ export class MatterController {
|
|
|
534
518
|
}
|
|
535
519
|
|
|
536
520
|
async enhanceCommissionedNodeDetails(nodeId: NodeId, deviceData: DeviceInformationData) {
|
|
537
|
-
|
|
521
|
+
this.#construction.assert();
|
|
522
|
+
const nodeDetails = this.#peers!.get(this.#fabric!.addressOf(nodeId)) as CommissionedPeer;
|
|
538
523
|
if (nodeDetails === undefined) {
|
|
539
524
|
throw new Error(`Node ${nodeId} is not commissioned.`);
|
|
540
525
|
}
|
|
541
526
|
nodeDetails.deviceData = deviceData;
|
|
542
|
-
await this.
|
|
527
|
+
await (this.#node!.env.get(PeerAddressStore) as CommissionedNodeStore).save();
|
|
543
528
|
}
|
|
544
529
|
|
|
545
530
|
/**
|
|
@@ -547,48 +532,33 @@ export class MatterController {
|
|
|
547
532
|
* Returns a InteractionClient on success.
|
|
548
533
|
*/
|
|
549
534
|
async connect(peerNodeId: NodeId, options: MatterController.ConnectOptions) {
|
|
550
|
-
|
|
535
|
+
this.#construction.assert();
|
|
536
|
+
return this.#node!.env.get(InteractionClientProvider).connect(this.#fabric!.addressOf(peerNodeId), options);
|
|
551
537
|
}
|
|
552
538
|
|
|
553
539
|
createInteractionClient(peerNodeIdOrChannel: NodeId | MessageChannel, options: PeerConnectionOptions = {}) {
|
|
554
540
|
if (peerNodeIdOrChannel instanceof MessageChannel) {
|
|
555
|
-
return this.
|
|
541
|
+
return this.#node!.env.get(InteractionClientProvider).getInteractionClientForChannel(peerNodeIdOrChannel);
|
|
556
542
|
}
|
|
557
|
-
return this.
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
return this.sessionManager.getNextAvailableSessionId();
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
getResumptionRecord(resumptionId: Bytes) {
|
|
565
|
-
return this.sessionManager.findResumptionRecordById(resumptionId);
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
findResumptionRecordByNodeId(nodeId: NodeId) {
|
|
569
|
-
return this.sessionManager.findResumptionRecordByAddress(this.fabric.addressOf(nodeId));
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
async saveResumptionRecord(resumptionRecord: ResumptionRecord) {
|
|
573
|
-
return this.sessionManager.saveResumptionRecord(resumptionRecord);
|
|
543
|
+
return this.#node!.env.get(InteractionClientProvider).getInteractionClient(
|
|
544
|
+
this.#fabric!.addressOf(peerNodeIdOrChannel),
|
|
545
|
+
options,
|
|
546
|
+
);
|
|
574
547
|
}
|
|
575
548
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
this.#
|
|
549
|
+
async start() {
|
|
550
|
+
this.#construction.assert();
|
|
551
|
+
await this.#node!.start();
|
|
552
|
+
this.#node!.env.get(InteractionServer).clientHandler = this.#node!.env.get(SubscriptionClient);
|
|
579
553
|
}
|
|
580
554
|
|
|
581
555
|
async close() {
|
|
582
|
-
await this
|
|
583
|
-
await this.exchangeManager.close();
|
|
584
|
-
await this.sessionManager.close();
|
|
585
|
-
await this.channelManager.close();
|
|
586
|
-
await this.transports.close();
|
|
587
|
-
await this.#advertiser.close();
|
|
556
|
+
await this.#node?.close();
|
|
588
557
|
}
|
|
589
558
|
|
|
590
559
|
getActiveSessionInformation() {
|
|
591
|
-
|
|
560
|
+
this.#construction.assert();
|
|
561
|
+
return this.#node!.env.get(SessionManager).getActiveSessionInformation();
|
|
592
562
|
}
|
|
593
563
|
|
|
594
564
|
async getStoredClusterDataVersions(
|
|
@@ -596,7 +566,8 @@ export class MatterController {
|
|
|
596
566
|
filterEndpointId?: EndpointNumber,
|
|
597
567
|
filterClusterId?: ClusterId,
|
|
598
568
|
): Promise<{ endpointId: EndpointNumber; clusterId: ClusterId; dataVersion: number }[]> {
|
|
599
|
-
|
|
569
|
+
this.#construction.assert();
|
|
570
|
+
const peer = this.#peers!.get(this.#fabric!.addressOf(nodeId));
|
|
600
571
|
if (peer === undefined || peer.dataStore === undefined) {
|
|
601
572
|
return []; // We have no store, also no data
|
|
602
573
|
}
|
|
@@ -609,7 +580,8 @@ export class MatterController {
|
|
|
609
580
|
endpointId: EndpointNumber,
|
|
610
581
|
clusterId: ClusterId,
|
|
611
582
|
): Promise<DecodedAttributeReportValue<any>[]> {
|
|
612
|
-
|
|
583
|
+
this.#construction.assert();
|
|
584
|
+
const peer = this.#peers!.get(this.#fabric!.addressOf(nodeId));
|
|
613
585
|
if (peer === undefined || peer.dataStore === undefined) {
|
|
614
586
|
return []; // We have no store, also no data
|
|
615
587
|
}
|
|
@@ -618,7 +590,8 @@ export class MatterController {
|
|
|
618
590
|
}
|
|
619
591
|
|
|
620
592
|
async updateFabricLabel(label: string) {
|
|
621
|
-
|
|
593
|
+
this.#construction.assert();
|
|
594
|
+
await this.#fabric!.setLabel(label);
|
|
622
595
|
}
|
|
623
596
|
}
|
|
624
597
|
|
|
@@ -632,13 +605,12 @@ export namespace MatterController {
|
|
|
632
605
|
class CommissionedNodeStore extends PeerAddressStore {
|
|
633
606
|
declare peers: PeerSet;
|
|
634
607
|
#controllerStore: ControllerStoreInterface;
|
|
608
|
+
#fabric: Fabric;
|
|
635
609
|
|
|
636
|
-
constructor(
|
|
637
|
-
controllerStore: ControllerStoreInterface,
|
|
638
|
-
private fabric: Fabric,
|
|
639
|
-
) {
|
|
610
|
+
constructor(controllerStore: ControllerStoreInterface, fabric: Fabric) {
|
|
640
611
|
super();
|
|
641
612
|
this.#controllerStore = controllerStore;
|
|
613
|
+
this.#fabric = fabric;
|
|
642
614
|
}
|
|
643
615
|
|
|
644
616
|
async createNodeStore(address: PeerAddress, load = true) {
|
|
@@ -656,7 +628,7 @@ class CommissionedNodeStore extends PeerAddressStore {
|
|
|
656
628
|
const nodes = new Array<CommissionedPeer>();
|
|
657
629
|
|
|
658
630
|
for (const [nodeId, { operationalServerAddress, discoveryData, deviceData }] of commissionedNodes) {
|
|
659
|
-
const address = this
|
|
631
|
+
const address = this.#fabric.addressOf(nodeId);
|
|
660
632
|
nodes.push({
|
|
661
633
|
address,
|
|
662
634
|
operationalAddress: operationalServerAddress,
|