@project-chip/matter.js 0.11.0-alpha.0-20240928-08865c2ce → 0.11.0-alpha.0-20241002-e7b377c34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/cjs/CommissioningController.d.ts +7 -35
  2. package/dist/cjs/CommissioningController.d.ts.map +1 -1
  3. package/dist/cjs/CommissioningController.js +2 -1
  4. package/dist/cjs/CommissioningController.js.map +1 -1
  5. package/dist/cjs/CommissioningServer.d.ts.map +1 -1
  6. package/dist/cjs/CommissioningServer.js +2 -2
  7. package/dist/cjs/CommissioningServer.js.map +1 -1
  8. package/dist/cjs/MatterController.d.ts +21 -49
  9. package/dist/cjs/MatterController.d.ts.map +1 -1
  10. package/dist/cjs/MatterController.js +102 -502
  11. package/dist/cjs/MatterController.js.map +2 -2
  12. package/dist/cjs/compat/protocol.d.ts +1 -1
  13. package/dist/cjs/compat/protocol.d.ts.map +1 -1
  14. package/dist/cjs/compat/protocol.js +1 -2
  15. package/dist/cjs/compat/protocol.js.map +1 -1
  16. package/dist/cjs/device/LegacyInteractionServer.d.ts.map +1 -1
  17. package/dist/cjs/device/PairedNode.d.ts +1 -2
  18. package/dist/cjs/device/PairedNode.d.ts.map +1 -1
  19. package/dist/cjs/device/PairedNode.js +2 -3
  20. package/dist/cjs/device/PairedNode.js.map +1 -1
  21. package/dist/esm/CommissioningController.d.ts +7 -35
  22. package/dist/esm/CommissioningController.d.ts.map +1 -1
  23. package/dist/esm/CommissioningController.js +2 -1
  24. package/dist/esm/CommissioningController.js.map +1 -1
  25. package/dist/esm/CommissioningServer.d.ts.map +1 -1
  26. package/dist/esm/CommissioningServer.js +2 -2
  27. package/dist/esm/CommissioningServer.js.map +1 -1
  28. package/dist/esm/MatterController.d.ts +21 -49
  29. package/dist/esm/MatterController.d.ts.map +1 -1
  30. package/dist/esm/MatterController.js +105 -519
  31. package/dist/esm/MatterController.js.map +2 -2
  32. package/dist/esm/compat/protocol.d.ts +1 -1
  33. package/dist/esm/compat/protocol.d.ts.map +1 -1
  34. package/dist/esm/compat/protocol.js +2 -4
  35. package/dist/esm/compat/protocol.js.map +1 -1
  36. package/dist/esm/device/LegacyInteractionServer.d.ts.map +1 -1
  37. package/dist/esm/device/PairedNode.d.ts +1 -2
  38. package/dist/esm/device/PairedNode.d.ts.map +1 -1
  39. package/dist/esm/device/PairedNode.js +1 -1
  40. package/dist/esm/device/PairedNode.js.map +1 -1
  41. package/package.json +8 -8
  42. package/src/CommissioningController.ts +7 -42
  43. package/src/CommissioningServer.ts +2 -2
  44. package/src/MatterController.ts +126 -713
  45. package/src/compat/protocol.ts +2 -3
  46. package/src/device/PairedNode.ts +1 -1
@@ -12,35 +12,22 @@ import {
12
12
  ImplementationError,
13
13
  Logger,
14
14
  NetInterfaceSet,
15
- NoResponseTimeoutError,
16
15
  StorageBackendMemory,
17
16
  StorageManager,
18
- Time,
19
- anyPromise,
20
- createPromise,
21
- isIPv6,
22
17
  serverAddressToString
23
18
  } from "#general";
24
19
  import {
25
- CaseClient,
26
20
  ChannelManager,
27
21
  ClusterClient,
28
22
  CommissioningError,
29
- CommissioningSuccessfullyFinished,
30
23
  ControllerCommissioner,
31
- ControllerDiscovery,
32
- DiscoveryError,
33
24
  ExchangeManager,
34
- ExchangeProvider,
35
25
  Fabric,
36
26
  FabricBuilder,
37
27
  FabricManager,
38
- InteractionClient,
39
- MdnsScanner,
40
- MessageChannel,
41
- NoChannelError,
42
- PairRetransmissionLimitReachedError,
43
- PaseClient,
28
+ NodeDiscoveryType,
29
+ PeerSet,
30
+ PeerStore,
44
31
  RetransmissionLimitReachedError,
45
32
  RootCertificateManager,
46
33
  SessionManager,
@@ -51,7 +38,6 @@ import {
51
38
  FabricId,
52
39
  FabricIndex,
53
40
  NodeId,
54
- SECURE_CHANNEL_PROTOCOL_ID,
55
41
  TlvEnum,
56
42
  TlvField,
57
43
  TlvObject,
@@ -64,21 +50,12 @@ const TlvCommissioningSuccessFailureResponse = TlvObject({
64
50
  /** Should help developers in troubleshooting errors. The value MAY go into logs or crash reports, not User UIs. */
65
51
  debugText: TlvField(1, TlvString.bound({ maxLength: 128 }))
66
52
  });
53
+ const DEFAULT_ADMIN_VENDOR_ID = VendorId(65521);
67
54
  const DEFAULT_FABRIC_INDEX = FabricIndex(1);
68
55
  const DEFAULT_FABRIC_ID = FabricId(1);
69
- const DEFAULT_ADMIN_VENDOR_ID = VendorId(65521);
70
- const RECONNECTION_POLLING_INTERVAL_MS = 6e5;
71
- const RETRANSMISSION_DISCOVERY_TIMEOUT_MS = 5e3;
72
56
  const CONTROLLER_CONNECTIONS_PER_FABRIC_AND_NODE = 3;
73
57
  const CONTROLLER_MAX_PATHS_PER_INVOKE = 10;
74
58
  const logger = Logger.get("MatterController");
75
- var NodeDiscoveryType = /* @__PURE__ */ ((NodeDiscoveryType2) => {
76
- NodeDiscoveryType2[NodeDiscoveryType2["None"] = 0] = "None";
77
- NodeDiscoveryType2[NodeDiscoveryType2["RetransmissionDiscovery"] = 1] = "RetransmissionDiscovery";
78
- NodeDiscoveryType2[NodeDiscoveryType2["TimedDiscovery"] = 2] = "TimedDiscovery";
79
- NodeDiscoveryType2[NodeDiscoveryType2["FullDiscovery"] = 3] = "FullDiscovery";
80
- return NodeDiscoveryType2;
81
- })(NodeDiscoveryType || {});
82
59
  class MatterController {
83
60
  static async create(options) {
84
61
  const {
@@ -89,7 +66,7 @@ class MatterController {
89
66
  scanners,
90
67
  netInterfaces,
91
68
  sessionClosedCallback,
92
- adminVendorId = VendorId(DEFAULT_ADMIN_VENDOR_ID),
69
+ adminVendorId,
93
70
  adminFabricId = FabricId(DEFAULT_FABRIC_ID),
94
71
  adminFabricIndex = FabricIndex(DEFAULT_FABRIC_INDEX),
95
72
  caseAuthenticatedTags
@@ -106,13 +83,12 @@ class MatterController {
106
83
  netInterfaces,
107
84
  certificateManager,
108
85
  fabric,
109
- adminVendorId: fabric.rootVendorId,
110
86
  sessionClosedCallback
111
87
  });
112
88
  } else {
113
89
  const rootNodeId = NodeId.randomOperationalNodeId();
114
90
  const ipkValue = Crypto.getRandomData(CRYPTO_SYMMETRIC_KEY_LENGTH);
115
- const fabricBuilder = new FabricBuilder().setRootCert(certificateManager.rootCert).setRootNodeId(rootNodeId).setIdentityProtectionKey(ipkValue).setRootVendorId(adminVendorId);
91
+ const fabricBuilder = new FabricBuilder().setRootCert(certificateManager.rootCert).setRootNodeId(rootNodeId).setIdentityProtectionKey(ipkValue).setRootVendorId(adminVendorId ?? DEFAULT_ADMIN_VENDOR_ID);
116
92
  fabricBuilder.setOperationalCert(
117
93
  certificateManager.generateNoc(
118
94
  fabricBuilder.publicKey,
@@ -130,7 +106,6 @@ class MatterController {
130
106
  netInterfaces,
131
107
  certificateManager,
132
108
  fabric,
133
- adminVendorId,
134
109
  sessionClosedCallback
135
110
  });
136
111
  }
@@ -160,7 +135,6 @@ class MatterController {
160
135
  netInterfaces,
161
136
  certificateManager,
162
137
  fabric,
163
- adminVendorId: fabric.rootVendorId,
164
138
  sessionClosedCallback
165
139
  });
166
140
  await controller.construction;
@@ -170,19 +144,16 @@ class MatterController {
170
144
  netInterfaces = new NetInterfaceSet();
171
145
  channelManager = new ChannelManager(CONTROLLER_CONNECTIONS_PER_FABRIC_AND_NODE);
172
146
  exchangeManager;
173
- paseClient;
174
- caseClient;
175
- commissionedNodes = /* @__PURE__ */ new Map();
147
+ peers;
148
+ commissioner;
176
149
  #construction;
177
150
  sessionStorage;
178
151
  fabricStorage;
179
- nodesStorage;
152
+ nodesStore;
180
153
  scanners;
181
154
  certificateManager;
182
155
  fabric;
183
- adminVendorId;
184
156
  sessionClosedCallback;
185
- #runningNodeDiscoveries = /* @__PURE__ */ new Map();
186
157
  get construction() {
187
158
  return this.#construction;
188
159
  }
@@ -195,18 +166,15 @@ class MatterController {
195
166
  netInterfaces,
196
167
  certificateManager,
197
168
  fabric,
198
- sessionClosedCallback,
199
- adminVendorId
169
+ sessionClosedCallback
200
170
  } = options;
201
171
  this.sessionStorage = sessionStorage;
202
172
  this.fabricStorage = fabricStorage;
203
- this.nodesStorage = nodesStorage;
204
173
  this.scanners = scanners;
205
174
  this.netInterfaces = netInterfaces;
206
175
  this.certificateManager = certificateManager;
207
176
  this.fabric = fabric;
208
177
  this.sessionClosedCallback = sessionClosedCallback;
209
- this.adminVendorId = adminVendorId;
210
178
  const fabricManager = new FabricManager();
211
179
  fabricManager.addFabric(fabric);
212
180
  this.sessionManager = new SessionManager({
@@ -216,26 +184,34 @@ class MatterController {
216
184
  maxPathsPerInvoke: CONTROLLER_MAX_PATHS_PER_INVOKE
217
185
  }
218
186
  });
219
- this.paseClient = new PaseClient(this.sessionManager);
220
- this.caseClient = new CaseClient(this.sessionManager);
221
187
  this.sessionManager.sessions.deleted.on(async (session) => {
222
188
  this.sessionClosedCallback?.(session.peerNodeId);
223
189
  });
224
- this.sessionManager.resubmissionStarted.on(this.#handleResubmissionStarted.bind(this));
225
190
  this.exchangeManager = new ExchangeManager({
226
191
  sessionManager: this.sessionManager,
227
192
  channelManager: this.channelManager,
228
193
  transportInterfaces: this.netInterfaces
229
194
  });
230
195
  this.exchangeManager.addProtocolHandler(new StatusReportOnlySecureChannelProtocol());
196
+ this.nodesStore = new CommissionedNodeStore(nodesStorage, fabric);
197
+ this.nodesStore.peers = this.peers = new PeerSet({
198
+ sessions: this.sessionManager,
199
+ channels: this.channelManager,
200
+ exchanges: this.exchangeManager,
201
+ scanners: this.scanners,
202
+ netInterfaces: this.netInterfaces,
203
+ store: this.nodesStore
204
+ });
205
+ this.commissioner = new ControllerCommissioner({
206
+ peers: this.peers,
207
+ scanners: this.scanners,
208
+ netInterfaces: this.netInterfaces,
209
+ exchanges: this.exchangeManager,
210
+ sessions: this.sessionManager,
211
+ certificates: this.certificateManager
212
+ });
231
213
  this.#construction = Construction(this, async () => {
232
- if (await this.nodesStorage.has("commissionedNodes")) {
233
- const commissionedNodes = await this.nodesStorage.get("commissionedNodes");
234
- this.commissionedNodes.clear();
235
- for (const [nodeId, details] of commissionedNodes) {
236
- this.commissionedNodes.set(nodeId, details);
237
- }
238
- }
214
+ await this.peers.construction.ready;
239
215
  await this.sessionManager.construction.ready;
240
216
  });
241
217
  }
@@ -268,191 +244,36 @@ class MatterController {
268
244
  * Return true when the commissioning process is completed successfully, false on error.
269
245
  */
270
246
  async commission(options, completeCommissioningCallback) {
271
- const {
272
- commissioning: commissioningOptions = {
273
- regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.Outdoor,
274
- // Set to the most restrictive if relevant
275
- regulatoryCountryCode: "XX"
276
- },
277
- discovery: { timeoutSeconds = 30 },
278
- passcode
279
- } = options;
280
- const commissionableDevice = "commissionableDevice" in options.discovery ? options.discovery.commissionableDevice : void 0;
281
- let {
282
- discovery: { discoveryCapabilities = {}, knownAddress }
283
- } = options;
284
- let identifierData = "identifierData" in options.discovery ? options.discovery.identifierData : {};
285
- if (this.scanners.hasScannerFor(ChannelType.UDP) && this.netInterfaces.hasInterfaceFor(ChannelType.UDP, "::") !== void 0) {
286
- discoveryCapabilities.onIpNetwork = true;
287
- }
288
- if (commissionableDevice !== void 0) {
289
- let { addresses } = commissionableDevice;
290
- if (discoveryCapabilities.ble === true) {
291
- discoveryCapabilities = { onIpNetwork: true, ble: addresses.some((address) => address.type === "ble") };
292
- } else if (discoveryCapabilities.onIpNetwork === true) {
293
- addresses = addresses.filter((address) => address.type !== "ble");
294
- }
295
- addresses.sort((a) => a.type === "udp" ? -1 : 1);
296
- knownAddress = addresses[0];
297
- if ("instanceId" in commissionableDevice && commissionableDevice.instanceId !== void 0) {
298
- identifierData = { instanceId: commissionableDevice.instanceId };
299
- } else {
300
- identifierData = { longDiscriminator: commissionableDevice.D };
301
- }
302
- }
303
- const scannersToUse = this.collectScanners(discoveryCapabilities);
304
- logger.info(
305
- `Commissioning device with identifier ${Logger.toJSON(identifierData)} and ${scannersToUse.length} scanners and knownAddress ${Logger.toJSON(knownAddress)}`
306
- );
307
- let paseSecureChannel;
308
- let discoveryData;
309
- if (knownAddress !== void 0) {
310
- try {
311
- paseSecureChannel = await this.initializePaseSecureChannel(knownAddress, passcode);
312
- } catch (error) {
313
- NoResponseTimeoutError.accept(error);
314
- }
315
- }
316
- if (paseSecureChannel === void 0) {
317
- const discoveredDevices = await ControllerDiscovery.discoverDeviceAddressesByIdentifier(
318
- scannersToUse,
319
- identifierData,
320
- timeoutSeconds
321
- );
322
- const { result } = await ControllerDiscovery.iterateServerAddresses(
323
- discoveredDevices,
324
- NoResponseTimeoutError,
325
- async () => scannersToUse.flatMap((scanner) => scanner.getDiscoveredCommissionableDevices(identifierData)),
326
- async (address, device) => {
327
- const channel = await this.initializePaseSecureChannel(address, passcode, device);
328
- discoveryData = device;
329
- return channel;
247
+ const commissioningOptions = {
248
+ ...options.commissioning,
249
+ fabric: this.fabric,
250
+ discovery: options.discovery,
251
+ passcode: options.passcode
252
+ };
253
+ if (completeCommissioningCallback) {
254
+ commissioningOptions.performCaseCommissioning = async (peerAddress, discoveryData) => {
255
+ const result = await completeCommissioningCallback(peerAddress.nodeId, discoveryData);
256
+ if (!result) {
257
+ throw new RetransmissionLimitReachedError("Device could not be discovered");
330
258
  }
331
- );
332
- paseSecureChannel = result;
259
+ };
333
260
  }
334
- return await this.commissionDevice(
335
- paseSecureChannel,
336
- commissioningOptions,
337
- discoveryData,
338
- completeCommissioningCallback
339
- );
261
+ const address = await this.commissioner.commission(commissioningOptions);
262
+ await this.fabricStorage?.set("fabric", this.fabric.toStorageObject());
263
+ return address.nodeId;
340
264
  }
341
265
  async disconnect(nodeId) {
342
- await this.sessionManager.removeAllSessionsForNode(nodeId, true);
343
- await this.channelManager.removeAllNodeChannels(this.fabric, nodeId);
266
+ return this.peers.disconnect(this.fabric.addressOf(nodeId));
344
267
  }
345
268
  async removeNode(nodeId) {
346
- logger.info(`Removing commissioned node ${nodeId} from controller.`);
347
- await this.sessionManager.removeAllSessionsForNode(nodeId);
348
- await this.sessionManager.removeResumptionRecord(nodeId);
349
- await this.channelManager.removeAllNodeChannels(this.fabric, nodeId);
350
- this.commissionedNodes.delete(nodeId);
351
- await this.storeCommissionedNodes();
352
- }
353
- /**
354
- * Method to start commission process with a PASE pairing.
355
- * If this not successful and throws an RetransmissionLimitReachedError the address is invalid or the passcode
356
- * is wrong.
357
- */
358
- async initializePaseSecureChannel(address, passcode, device) {
359
- let paseChannel;
360
- if (device !== void 0) {
361
- logger.info(`Commissioning device`, MdnsScanner.discoveryDataDiagnostics(device));
362
- }
363
- if (address.type === "udp") {
364
- const { ip } = address;
365
- const isIpv6Address = isIPv6(ip);
366
- const paseInterface = this.netInterfaces.interfaceFor(ChannelType.UDP, isIpv6Address ? "::" : "0.0.0.0");
367
- if (paseInterface === void 0) {
368
- throw new PairRetransmissionLimitReachedError(
369
- `IPv${isIpv6Address ? "6" : "4"} interface not initialized. Cannot use ${ip} for commissioning.`
370
- );
371
- }
372
- paseChannel = await paseInterface.openChannel(address);
373
- } else {
374
- const ble = this.netInterfaces.interfaceFor(ChannelType.BLE);
375
- if (!ble) {
376
- throw new PairRetransmissionLimitReachedError(
377
- `BLE interface not initialized. Cannot use ${address.peripheralAddress} for commissioning.`
378
- );
379
- }
380
- paseChannel = await ble.openChannel(address);
381
- }
382
- const unsecureSession = this.sessionManager.createInsecureSession({
383
- // Use the session parameters from MDNS announcements when available and rest is assumed to be fallbacks
384
- sessionParameters: {
385
- idleIntervalMs: device?.SII,
386
- activeIntervalMs: device?.SAI,
387
- activeThresholdMs: device?.SAT
388
- },
389
- isInitiator: true
390
- });
391
- const paseUnsecureMessageChannel = new MessageChannel(paseChannel, unsecureSession);
392
- const paseExchange = this.exchangeManager.initiateExchangeWithChannel(
393
- paseUnsecureMessageChannel,
394
- SECURE_CHANNEL_PROTOCOL_ID
395
- );
396
- let paseSecureSession;
397
- try {
398
- paseSecureSession = await this.paseClient.pair(
399
- this.sessionManager.sessionParameters,
400
- paseExchange,
401
- passcode
402
- );
403
- } catch (e) {
404
- await paseExchange.close();
405
- throw e;
406
- }
407
- await unsecureSession.destroy();
408
- return new MessageChannel(paseChannel, paseSecureSession);
409
- }
410
- /**
411
- * Method to commission a device with a PASE secure channel. It returns the NodeId of the commissioned device on
412
- * success.
413
- */
414
- async commissionDevice(paseSecureMessageChannel, commissioningOptions, discoveryData, completeCommissioningCallback) {
415
- const peerNodeId = commissioningOptions.nodeId ?? NodeId.randomOperationalNodeId();
416
- const commissioningManager = new ControllerCommissioner(
417
- // Use the created secure session to do the commissioning
418
- new InteractionClient(new ExchangeProvider(this.exchangeManager, paseSecureMessageChannel), peerNodeId),
419
- this.certificateManager,
420
- this.fabric,
421
- commissioningOptions,
422
- peerNodeId,
423
- this.adminVendorId,
424
- async () => {
425
- await paseSecureMessageChannel.close();
426
- if (completeCommissioningCallback !== void 0) {
427
- if (!await completeCommissioningCallback(peerNodeId, discoveryData)) {
428
- throw new RetransmissionLimitReachedError("Device could not be discovered");
429
- }
430
- throw new CommissioningSuccessfullyFinished();
431
- }
432
- return await this.connect(peerNodeId, {
433
- discoveryType: 2 /* TimedDiscovery */,
434
- timeoutSeconds: 120,
435
- discoveryData
436
- });
437
- }
438
- );
439
- try {
440
- await commissioningManager.executeCommissioning();
441
- } catch (error) {
442
- if (this.commissionedNodes.has(peerNodeId)) {
443
- this.commissionedNodes.delete(peerNodeId);
444
- }
445
- throw error;
446
- }
447
- await this.fabricStorage?.set("fabric", this.fabric.toStorageObject());
448
- return peerNodeId;
269
+ return this.peers.delete(this.fabric.addressOf(nodeId));
449
270
  }
450
271
  /**
451
272
  * Method to complete the commissioning process to a node which was initialized with a PASE secure channel.
452
273
  */
453
274
  async completeCommissioning(peerNodeId, discoveryData) {
454
275
  const interactionClient = await this.connect(peerNodeId, {
455
- discoveryType: 2 /* TimedDiscovery */,
276
+ discoveryType: NodeDiscoveryType.TimedDiscovery,
456
277
  timeoutSeconds: 120,
457
278
  discoveryData
458
279
  });
@@ -465,318 +286,44 @@ class MatterController {
465
286
  useExtendedFailSafeMessageResponseTimeout: true
466
287
  });
467
288
  if (errorCode !== GeneralCommissioning.CommissioningError.Ok) {
468
- if (this.commissionedNodes.has(peerNodeId)) {
469
- this.commissionedNodes.delete(peerNodeId);
470
- }
289
+ await this.peers.delete(this.fabric.addressOf(peerNodeId));
471
290
  throw new CommissioningError(`Commission error on commissioningComplete: ${errorCode}, ${debugText}`);
472
291
  }
473
292
  await this.fabricStorage?.set("fabric", this.fabric.toStorageObject());
474
293
  }
475
- #handleResubmissionStarted(peerNodeId) {
476
- if (peerNodeId === void 0) {
477
- return;
478
- }
479
- if (this.#runningNodeDiscoveries.has(peerNodeId)) {
480
- return;
481
- }
482
- this.#runningNodeDiscoveries.set(peerNodeId, { type: 1 /* RetransmissionDiscovery */ });
483
- this.scanners.scannerFor(ChannelType.UDP)?.findOperationalDevice(this.fabric, peerNodeId, RETRANSMISSION_DISCOVERY_TIMEOUT_MS, true).catch((error) => {
484
- logger.error(`Failed to discover device ${peerNodeId} after resubmission started.`, error);
485
- }).finally(() => {
486
- if (this.#runningNodeDiscoveries.get(peerNodeId)?.type === 1 /* RetransmissionDiscovery */) {
487
- this.#runningNodeDiscoveries.delete(peerNodeId);
488
- }
489
- });
490
- }
491
- async reconnectKnownAddress(peerNodeId, operationalAddress, discoveryData, expectedProcessingTimeMs) {
492
- const { ip, port } = operationalAddress;
493
- try {
494
- logger.debug(
495
- `Resume device connection to configured server at ${ip}:${port}${expectedProcessingTimeMs !== void 0 ? ` with expected processing time of ${expectedProcessingTimeMs}ms` : ""} ...`
496
- );
497
- const channel = await this.pair(peerNodeId, operationalAddress, discoveryData, expectedProcessingTimeMs);
498
- await this.setOperationalDeviceData(peerNodeId, operationalAddress);
499
- return channel;
500
- } catch (error) {
501
- if (error instanceof NoResponseTimeoutError) {
502
- logger.debug(
503
- `Failed to resume connection to node ${peerNodeId} connection with ${ip}:${port}, discover the device ...`,
504
- error
505
- );
506
- await this.sessionManager.removeAllSessionsForNode(peerNodeId);
507
- return void 0;
508
- } else {
509
- throw error;
510
- }
511
- }
512
- }
513
- async connectOrDiscoverNode(peerNodeId, operationalAddress, discoveryOptions = {}) {
514
- const {
515
- discoveryType: requestedDiscoveryType = 3 /* FullDiscovery */,
516
- timeoutSeconds,
517
- discoveryData = this.commissionedNodes.get(peerNodeId)?.discoveryData
518
- } = discoveryOptions;
519
- if (timeoutSeconds !== void 0 && requestedDiscoveryType !== 2 /* TimedDiscovery */) {
520
- throw new ImplementationError("Cannot set timeout without timed discovery.");
521
- }
522
- if (requestedDiscoveryType === 1 /* RetransmissionDiscovery */) {
523
- throw new ImplementationError("Cannot set retransmission discovery type.");
524
- }
525
- const mdnsScanner = this.scanners.scannerFor(ChannelType.UDP);
526
- if (!mdnsScanner) {
527
- throw new ImplementationError("Cannot discover device without mDNS scanner.");
528
- }
529
- const existingDiscoveryDetails = this.#runningNodeDiscoveries.get(peerNodeId) ?? {
530
- type: 0 /* None */
531
- };
532
- if (existingDiscoveryDetails.type !== 0 /* None */ && existingDiscoveryDetails.type < requestedDiscoveryType) {
533
- mdnsScanner.cancelOperationalDeviceDiscovery(this.fabric, peerNodeId);
534
- this.#runningNodeDiscoveries.delete(peerNodeId);
535
- existingDiscoveryDetails.type = 0 /* None */;
536
- }
537
- const { type: runningDiscoveryType, promises } = existingDiscoveryDetails;
538
- if (operationalAddress !== void 0 && (runningDiscoveryType === 0 /* None */ || requestedDiscoveryType === 0 /* None */)) {
539
- const directReconnection = await this.reconnectKnownAddress(peerNodeId, operationalAddress, discoveryData);
540
- if (directReconnection !== void 0) {
541
- return directReconnection;
542
- }
543
- if (requestedDiscoveryType === 0 /* None */) {
544
- throw new DiscoveryError(`Node ${peerNodeId} is not reachable right now.`);
545
- }
546
- }
547
- if (promises !== void 0) {
548
- if (runningDiscoveryType > requestedDiscoveryType) {
549
- throw new DiscoveryError(
550
- `Node ${peerNodeId} is not reachable right now and discovery already running.`
551
- );
552
- } else {
553
- return await anyPromise(promises);
554
- }
555
- }
556
- const discoveryPromises = new Array();
557
- let reconnectionPollingTimer;
558
- if (operationalAddress !== void 0) {
559
- if (requestedDiscoveryType === 3 /* FullDiscovery */) {
560
- const { promise, resolver, rejecter } = createPromise();
561
- reconnectionPollingTimer = Time.getPeriodicTimer(
562
- "Controller reconnect",
563
- RECONNECTION_POLLING_INTERVAL_MS,
564
- async () => {
565
- try {
566
- logger.debug(`Polling for device at ${serverAddressToString(operationalAddress)} ...`);
567
- const result = await this.reconnectKnownAddress(
568
- peerNodeId,
569
- operationalAddress,
570
- discoveryData
571
- );
572
- if (result !== void 0 && reconnectionPollingTimer?.isRunning) {
573
- reconnectionPollingTimer?.stop();
574
- mdnsScanner.cancelOperationalDeviceDiscovery(this.fabric, peerNodeId);
575
- this.#runningNodeDiscoveries.delete(peerNodeId);
576
- resolver(result);
577
- }
578
- } catch (error) {
579
- if (reconnectionPollingTimer?.isRunning) {
580
- reconnectionPollingTimer?.stop();
581
- mdnsScanner.cancelOperationalDeviceDiscovery(this.fabric, peerNodeId);
582
- this.#runningNodeDiscoveries.delete(peerNodeId);
583
- rejecter(error);
584
- }
585
- }
586
- }
587
- ).start();
588
- discoveryPromises.push(() => promise);
589
- }
590
- }
591
- discoveryPromises.push(async () => {
592
- const scanResult = await ControllerDiscovery.discoverOperationalDevice(
593
- this.fabric,
594
- peerNodeId,
595
- mdnsScanner,
596
- timeoutSeconds,
597
- timeoutSeconds === void 0
598
- );
599
- const { timer } = this.#runningNodeDiscoveries.get(peerNodeId) ?? {};
600
- timer?.stop();
601
- this.#runningNodeDiscoveries.delete(peerNodeId);
602
- const { result } = await ControllerDiscovery.iterateServerAddresses(
603
- [scanResult],
604
- NoResponseTimeoutError,
605
- async () => {
606
- const device = mdnsScanner.getDiscoveredOperationalDevice(this.fabric, peerNodeId);
607
- return device !== void 0 ? [device] : [];
608
- },
609
- async (address, device) => {
610
- const result2 = await this.pair(peerNodeId, address, device);
611
- await this.setOperationalDeviceData(peerNodeId, address, {
612
- ...discoveryData,
613
- ...device
614
- });
615
- return result2;
616
- }
617
- );
618
- return result;
619
- });
620
- this.#runningNodeDiscoveries.set(peerNodeId, {
621
- type: requestedDiscoveryType,
622
- promises: discoveryPromises,
623
- timer: reconnectionPollingTimer
624
- });
625
- return await anyPromise(discoveryPromises).finally(() => this.#runningNodeDiscoveries.delete(peerNodeId));
626
- }
627
- /**
628
- * Resume a device connection and establish a CASE session that was previously paired with the controller. This
629
- * method will try to connect to the device using the previously used server address (if set). If that fails, the
630
- * device is discovered again using its operational instance details.
631
- * It returns the operational MessageChannel on success.
632
- */
633
- async resume(peerNodeId, discoveryOptions) {
634
- const operationalAddress = this.getLastOperationalAddress(peerNodeId);
635
- try {
636
- return await this.connectOrDiscoverNode(peerNodeId, operationalAddress, discoveryOptions);
637
- } catch (error) {
638
- if ((error instanceof DiscoveryError || error instanceof NoResponseTimeoutError) && this.commissionedNodes.has(peerNodeId)) {
639
- logger.info(`Resume failed, remove all sessions for node ${peerNodeId}`);
640
- await this.sessionManager.removeAllSessionsForNode(peerNodeId);
641
- }
642
- throw error;
643
- }
644
- }
645
- /** Pair with an operational device (already commissioned) and establish a CASE session. */
646
- async pair(peerNodeId, operationalServerAddress, discoveryData, expectedProcessingTimeMs) {
647
- const { ip, port } = operationalServerAddress;
648
- const isIpv6Address = isIPv6(ip);
649
- const operationalInterface = this.netInterfaces.interfaceFor(ChannelType.UDP, isIpv6Address ? "::" : "0.0.0.0");
650
- if (operationalInterface === void 0) {
651
- throw new PairRetransmissionLimitReachedError(
652
- `IPv${isIpv6Address ? "6" : "4"} interface not initialized for port ${port}. Cannot use ${ip} for pairing.`
653
- );
654
- }
655
- const operationalChannel = await operationalInterface.openChannel(operationalServerAddress);
656
- const { sessionParameters } = this.findResumptionRecordByNodeId(peerNodeId) ?? {};
657
- const unsecureSession = this.sessionManager.createInsecureSession({
658
- // Use the session parameters from MDNS announcements when available and rest is assumed to be fallbacks
659
- sessionParameters: {
660
- idleIntervalMs: discoveryData?.SII ?? sessionParameters?.idleIntervalMs,
661
- activeIntervalMs: discoveryData?.SAI ?? sessionParameters?.activeIntervalMs,
662
- activeThresholdMs: discoveryData?.SAT ?? sessionParameters?.activeThresholdMs
663
- },
664
- isInitiator: true
665
- });
666
- const operationalUnsecureMessageExchange = new MessageChannel(operationalChannel, unsecureSession);
667
- let operationalSecureSession;
668
- try {
669
- const exchange = this.exchangeManager.initiateExchangeWithChannel(
670
- operationalUnsecureMessageExchange,
671
- SECURE_CHANNEL_PROTOCOL_ID
672
- );
673
- try {
674
- operationalSecureSession = await this.caseClient.pair(
675
- exchange,
676
- this.fabric,
677
- peerNodeId,
678
- expectedProcessingTimeMs
679
- );
680
- } catch (e) {
681
- await exchange.close();
682
- throw e;
683
- }
684
- } catch (e) {
685
- NoResponseTimeoutError.accept(e);
686
- throw new PairRetransmissionLimitReachedError(e.message);
687
- }
688
- await unsecureSession.destroy();
689
- const channel = new MessageChannel(operationalChannel, operationalSecureSession);
690
- await this.channelManager.setChannel(this.fabric, peerNodeId, channel);
691
- return channel;
692
- }
693
294
  isCommissioned() {
694
- return this.commissionedNodes.size > 0;
295
+ return this.peers.size > 0;
695
296
  }
696
297
  getCommissionedNodes() {
697
- return Array.from(this.commissionedNodes.keys());
298
+ return this.peers.map((peer) => peer.address.nodeId);
698
299
  }
699
300
  getCommissionedNodesDetails() {
700
- return Array.from(this.commissionedNodes.entries()).map(
701
- ([nodeId, { operationalServerAddress, discoveryData, basicInformationData }]) => ({
702
- nodeId,
703
- operationalAddress: operationalServerAddress ? serverAddressToString(operationalServerAddress) : void 0,
301
+ return this.peers.map((peer) => {
302
+ const { address, operationalAddress, discoveryData, basicInformationData } = peer;
303
+ return {
304
+ nodeId: address.nodeId,
305
+ operationalAddress: operationalAddress ? serverAddressToString(operationalAddress) : void 0,
704
306
  advertisedName: discoveryData?.DN,
705
307
  discoveryData,
706
308
  basicInformationData
707
- })
708
- );
709
- }
710
- async setOperationalDeviceData(nodeId, operationalServerAddress, discoveryData) {
711
- const nodeDetails = this.commissionedNodes.get(nodeId) ?? {};
712
- nodeDetails.operationalServerAddress = operationalServerAddress;
713
- if (discoveryData !== void 0) {
714
- nodeDetails.discoveryData = {
715
- ...nodeDetails.discoveryData,
716
- ...discoveryData
717
309
  };
718
- }
719
- this.commissionedNodes.set(nodeId, nodeDetails);
720
- await this.storeCommissionedNodes();
310
+ });
721
311
  }
722
312
  async enhanceCommissionedNodeDetails(nodeId, data) {
723
- const nodeDetails = this.commissionedNodes.get(nodeId);
313
+ const nodeDetails = this.peers.get(this.fabric.addressOf(nodeId));
724
314
  if (nodeDetails === void 0) {
725
315
  throw new Error(`Node ${nodeId} is not commissioned.`);
726
316
  }
727
317
  const { basicInformationData } = data;
728
318
  nodeDetails.basicInformationData = basicInformationData;
729
- this.commissionedNodes.set(nodeId, nodeDetails);
730
- await this.storeCommissionedNodes();
731
- }
732
- getLastOperationalAddress(nodeId) {
733
- return this.commissionedNodes.get(nodeId)?.operationalServerAddress;
734
- }
735
- async storeCommissionedNodes() {
736
- await this.nodesStorage.set("commissionedNodes", Array.from(this.commissionedNodes.entries()));
319
+ await this.nodesStore.save();
737
320
  }
738
321
  /**
739
322
  * Connect to the device by opening a channel and creating a new CASE session if necessary.
740
323
  * Returns a InteractionClient on success.
741
324
  */
742
325
  async connect(peerNodeId, discoveryOptions) {
743
- const { discoveryData } = discoveryOptions;
744
- let channel;
745
- try {
746
- channel = this.channelManager.getChannel(this.fabric, peerNodeId);
747
- } catch (error) {
748
- NoChannelError.accept(error);
749
- channel = await this.resume(peerNodeId, discoveryOptions);
750
- }
751
- return new InteractionClient(
752
- new ExchangeProvider(this.exchangeManager, channel, async () => {
753
- if (!this.channelManager.hasChannel(this.fabric, peerNodeId)) {
754
- throw new RetransmissionLimitReachedError(`Device ${peerNodeId} is currently not reachable.`);
755
- }
756
- await this.channelManager.removeAllNodeChannels(this.fabric, peerNodeId);
757
- const mdnsScanner = this.scanners.scannerFor(ChannelType.UDP);
758
- const discoveredAddresses = mdnsScanner?.getDiscoveredOperationalDevice(this.fabric, peerNodeId);
759
- const lastKnownAddress = this.getLastOperationalAddress(peerNodeId);
760
- if (lastKnownAddress !== void 0 && discoveredAddresses !== void 0 && discoveredAddresses.addresses.some(
761
- ({ ip, port }) => ip === lastKnownAddress.ip && port === lastKnownAddress.port
762
- )) {
763
- discoveredAddresses.addresses.length = 0;
764
- }
765
- const operationalAddress = discoveredAddresses?.addresses[0];
766
- if (operationalAddress === void 0) {
767
- logger.info(
768
- `Re-Discovering device failed (no address found), remove all sessions for node ${peerNodeId}`
769
- );
770
- await this.sessionManager.removeAllSessionsForNode(peerNodeId);
771
- throw new RetransmissionLimitReachedError(`No operational address found for node ${peerNodeId}`);
772
- }
773
- if (await this.reconnectKnownAddress(peerNodeId, operationalAddress, discoveryData, 2e3) === void 0) {
774
- throw new RetransmissionLimitReachedError(`Device ${peerNodeId} is not reachable.`);
775
- }
776
- return this.channelManager.getChannel(this.fabric, peerNodeId);
777
- }),
778
- peerNodeId
779
- );
326
+ return this.peers.connect(this.fabric.addressOf(peerNodeId), discoveryOptions);
780
327
  }
781
328
  async getNextAvailableSessionId() {
782
329
  return this.sessionManager.getNextAvailableSessionId();
@@ -785,7 +332,7 @@ class MatterController {
785
332
  return this.sessionManager.findResumptionRecordById(resumptionId);
786
333
  }
787
334
  findResumptionRecordByNodeId(nodeId) {
788
- return this.sessionManager.findResumptionRecordByNodeId(nodeId);
335
+ return this.sessionManager.findResumptionRecordByAddress(this.fabric.addressOf(nodeId));
789
336
  }
790
337
  async saveResumptionRecord(resumptionRecord) {
791
338
  return this.sessionManager.saveResumptionRecord(resumptionRecord);
@@ -793,11 +340,7 @@ class MatterController {
793
340
  announce() {
794
341
  }
795
342
  async close() {
796
- const mdnsScanner = this.scanners.scannerFor(ChannelType.UDP);
797
- for (const [nodeId, { timer }] of this.#runningNodeDiscoveries.entries()) {
798
- timer?.stop();
799
- mdnsScanner?.cancelOperationalDeviceDiscovery(this.fabric, nodeId, false);
800
- }
343
+ await this.peers.close();
801
344
  await this.exchangeManager.close();
802
345
  await this.sessionManager.close();
803
346
  await this.channelManager.close();
@@ -807,8 +350,51 @@ class MatterController {
807
350
  return this.sessionManager.getActiveSessionInformation();
808
351
  }
809
352
  }
353
+ class CommissionedNodeStore extends PeerStore {
354
+ constructor(nodesStorage, fabric) {
355
+ super();
356
+ this.nodesStorage = nodesStorage;
357
+ this.fabric = fabric;
358
+ }
359
+ async loadPeers() {
360
+ if (!await this.nodesStorage.has("commissionedNodes")) {
361
+ return [];
362
+ }
363
+ const commissionedNodes = await this.nodesStorage.get("commissionedNodes");
364
+ return commissionedNodes.map(
365
+ ([nodeId, { operationalServerAddress, discoveryData, basicInformationData }]) => ({
366
+ address: this.fabric.addressOf(nodeId),
367
+ operationalAddress: operationalServerAddress,
368
+ discoveryData,
369
+ basicInformationData
370
+ })
371
+ );
372
+ }
373
+ async updatePeer() {
374
+ return this.save();
375
+ }
376
+ async deletePeer() {
377
+ return this.save();
378
+ }
379
+ async save() {
380
+ await this.nodesStorage.set(
381
+ "commissionedNodes",
382
+ this.peers.map((peer) => {
383
+ const {
384
+ address,
385
+ operationalAddress: operationalServerAddress,
386
+ basicInformationData,
387
+ discoveryData
388
+ } = peer;
389
+ return [
390
+ address.nodeId,
391
+ { operationalServerAddress, basicInformationData, discoveryData }
392
+ ];
393
+ })
394
+ );
395
+ }
396
+ }
810
397
  export {
811
- MatterController,
812
- NodeDiscoveryType
398
+ MatterController
813
399
  };
814
400
  //# sourceMappingURL=MatterController.js.map