@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.
@@ -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
- FabricBuilder,
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
- rootCertificateAuthority?: CertificateAuthority;
112
- rootFabric?: Fabric;
113
- crypto?: Crypto;
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.crypto ?? Environment.default.get(Crypto);
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
- const fabricStorage = controllerStore.fabricStorage;
128
+ environment.set(CertificateAuthority, ca);
134
129
 
135
130
  let controller: MatterController | undefined = undefined;
136
- // Check if we have a fabric stored in the storage, if yes initialize this one, else build a new one
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
- const fabric = rootFabric ?? new Fabric(crypto, await fabricStorage.get<Fabric.Config>("fabric"));
137
+ fabric = rootFabric ?? new Fabric(crypto, await fabricStorage.get<Fabric.Config>("fabric"));
139
138
  if (Bytes.areEqual(fabric.rootCert, ca.rootCert)) {
140
- logger.info("Used existing fabric");
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
- if (controller === undefined) {
164
- logger.info("Creating new fabric");
165
- const controllerNodeId = rootNodeId ?? NodeId.randomOperationalNodeId(crypto);
166
- const ipkValue = crypto.randomBytes(CRYPTO_SYMMETRIC_KEY_LENGTH);
167
- const fabricBuilder = await FabricBuilder.create(crypto);
168
- await fabricBuilder.setRootCert(ca.rootCert);
169
- fabricBuilder
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
- sessionClosedCallback?: (peerNodeId: NodeId) => void;
202
- crypto?: Crypto;
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
- certificateAuthorityConfig,
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
- const certificateManager =
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
- #store: ControllerStoreInterface;
262
- readonly nodesStore: CommissionedNodeStore;
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
- scanners: ScannerSet;
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
- this.exchangeManager = new ExchangeManager({
319
- crypto: fabric.crypto,
320
- sessionManager: this.sessionManager,
321
- channelManager: this.channelManager,
322
- netInterface: this.transports,
323
- });
324
- this.exchangeManager.addProtocolHandler(new SecureChannelProtocol(this.sessionManager, fabricManager));
325
- this.exchangeManager.addProtocolHandler(subscriptionClient);
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.clients = new InteractionClientProvider(this.peers);
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
- this.commissioner = new ControllerCommissioner({
343
- peers: this.peers,
344
- clients: this.clients,
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
- this.#advertiser = new DeviceAdvertiser({
353
- fabrics: fabricManager,
354
- sessions: this.sessionManager,
355
- });
333
+ this.#peers = this.#node.env.get(PeerSet);
334
+ nodesStore.peers = this.#peers;
356
335
 
357
- this.#construction = Construction(this, async () => {
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
- return this.fabric.rootNodeId;
348
+ this.#construction.assert();
349
+ return this.#fabric!.rootNodeId;
368
350
  }
369
351
 
370
352
  get caConfig() {
371
- return this.ca.config;
353
+ this.#construction.assert();
354
+ return this.#node!.env.get(CertificateAuthority).config;
372
355
  }
373
356
 
374
357
  get fabricConfig() {
375
- return this.fabric.config;
358
+ this.#construction.assert();
359
+ return this.#fabric!.config;
376
360
  }
377
361
 
378
362
  get sessions() {
379
- return this.sessionManager.sessions;
363
+ this.#construction.assert();
364
+ return this.#node!.env.get(SessionManager).sessions;
380
365
  }
381
366
 
382
367
  getFabrics() {
383
- return [this.fabric];
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
- addAdvertiser(advertiser: Advertiser) {
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.scanners.filter(
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.fabric,
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.commissioner.commissionWithDiscovery(commissioningOptions);
421
+ const address = await this.#node!.env.get(ControllerCommissioner).commissionWithDiscovery(commissioningOptions);
446
422
 
447
- await this.fabric.persist();
423
+ await this.#fabric!.persist();
448
424
 
449
425
  return address.nodeId;
450
426
  }
451
427
 
452
428
  async disconnect(nodeId: NodeId) {
453
- return this.peers.disconnect(this.fabric.addressOf(nodeId));
429
+ this.#construction.assert();
430
+ return this.#peers!.disconnect(this.#fabric!.addressOf(nodeId));
454
431
  }
455
432
 
456
433
  async connectPaseChannel(options: NodeCommissioningOptions) {
457
- const { paseSecureChannel } = await this.commissioner.discoverAndEstablishPase({
434
+ this.#construction.assert();
435
+ const { paseSecureChannel } = await this.#node!.env.get(ControllerCommissioner).discoverAndEstablishPase({
458
436
  ...options.commissioning,
459
- fabric: this.fabric,
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
- return this.peers.delete(this.fabric.addressOf(nodeId));
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.peers.delete(this.fabric.addressOf(peerNodeId));
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.fabric.persist();
477
+ await this.#fabric!.persist();
498
478
  }
499
479
 
500
480
  isCommissioned() {
501
- return this.peers.size > 0;
481
+ this.#construction.assert();
482
+ return !!this.#peers!.size;
502
483
  }
503
484
 
504
485
  getCommissionedNodes() {
505
- return this.peers.map(peer => peer.address.nodeId);
486
+ this.#construction.assert();
487
+ return this.#peers!.map(peer => peer.address.nodeId);
506
488
  }
507
489
 
508
490
  getCommissionedNodesDetails() {
509
- return this.peers.map(peer => {
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
- const nodeDetails = this.peers.get(this.fabric.addressOf(nodeId)) as CommissionedPeer;
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
- const nodeDetails = this.peers.get(this.fabric.addressOf(nodeId)) as CommissionedPeer;
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.nodesStore.save();
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
- return this.clients.connect(this.fabric.addressOf(peerNodeId), options);
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.clients.getInteractionClientForChannel(peerNodeIdOrChannel);
541
+ return this.#node!.env.get(InteractionClientProvider).getInteractionClientForChannel(peerNodeIdOrChannel);
556
542
  }
557
- return this.clients.getInteractionClient(this.fabric.addressOf(peerNodeIdOrChannel), options);
558
- }
559
-
560
- async getNextAvailableSessionId() {
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
- announce() {
577
- // Announce the controller itself
578
- this.#advertiser.enterOperationalMode();
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.peers.close();
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
- return this.sessionManager.getActiveSessionInformation();
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
- const peer = this.peers.get(this.fabric.addressOf(nodeId));
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
- const peer = this.peers.get(this.fabric.addressOf(nodeId));
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
- await this.fabric.setLabel(label);
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.fabric.addressOf(nodeId);
631
+ const address = this.#fabric.addressOf(nodeId);
660
632
  nodes.push({
661
633
  address,
662
634
  operationalAddress: operationalServerAddress,