@matter/protocol 0.12.4-alpha.0-20250223-1e0341a1a → 0.12.4-alpha.0-20250224-e0964a795

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 (189) hide show
  1. package/dist/cjs/action/client/ClientInteraction.d.ts +38 -0
  2. package/dist/cjs/action/client/ClientInteraction.d.ts.map +1 -0
  3. package/dist/cjs/action/client/ClientInteraction.js +91 -0
  4. package/dist/cjs/action/client/ClientInteraction.js.map +6 -0
  5. package/dist/cjs/action/client/index.d.ts +7 -0
  6. package/dist/cjs/action/client/index.d.ts.map +1 -0
  7. package/dist/cjs/action/client/index.js +24 -0
  8. package/dist/cjs/action/client/index.js.map +6 -0
  9. package/dist/cjs/action/index.d.ts +1 -0
  10. package/dist/cjs/action/index.d.ts.map +1 -1
  11. package/dist/cjs/action/index.js +1 -0
  12. package/dist/cjs/action/index.js.map +1 -1
  13. package/dist/cjs/interaction/DecodedDataReport.d.ts +15 -0
  14. package/dist/cjs/interaction/DecodedDataReport.d.ts.map +1 -0
  15. package/dist/cjs/interaction/DecodedDataReport.js +42 -0
  16. package/dist/cjs/interaction/DecodedDataReport.js.map +6 -0
  17. package/dist/cjs/interaction/InteractionClient.d.ts +13 -23
  18. package/dist/cjs/interaction/InteractionClient.d.ts.map +1 -1
  19. package/dist/cjs/interaction/InteractionClient.js +99 -127
  20. package/dist/cjs/interaction/InteractionClient.js.map +1 -1
  21. package/dist/cjs/interaction/InteractionMessenger.d.ts +94 -1
  22. package/dist/cjs/interaction/InteractionMessenger.d.ts.map +1 -1
  23. package/dist/cjs/interaction/InteractionMessenger.js +56 -37
  24. package/dist/cjs/interaction/InteractionMessenger.js.map +1 -1
  25. package/dist/cjs/interaction/InteractionServer.d.ts +4 -2
  26. package/dist/cjs/interaction/InteractionServer.d.ts.map +1 -1
  27. package/dist/cjs/interaction/InteractionServer.js +12 -4
  28. package/dist/cjs/interaction/InteractionServer.js.map +1 -1
  29. package/dist/cjs/interaction/SubscriptionClient.d.ts +38 -0
  30. package/dist/cjs/interaction/SubscriptionClient.d.ts.map +1 -0
  31. package/dist/cjs/interaction/SubscriptionClient.js +98 -0
  32. package/dist/cjs/interaction/SubscriptionClient.js.map +6 -0
  33. package/dist/cjs/interaction/index.d.ts +1 -0
  34. package/dist/cjs/interaction/index.d.ts.map +1 -1
  35. package/dist/cjs/interaction/index.js +1 -0
  36. package/dist/cjs/interaction/index.js.map +1 -1
  37. package/dist/cjs/peer/ControllerCommissioner.d.ts +2 -2
  38. package/dist/cjs/peer/ControllerCommissioner.d.ts.map +1 -1
  39. package/dist/cjs/peer/ControllerCommissioner.js +4 -3
  40. package/dist/cjs/peer/ControllerCommissioner.js.map +1 -1
  41. package/dist/cjs/peer/InteractionQueue.d.ts +11 -0
  42. package/dist/cjs/peer/InteractionQueue.d.ts.map +1 -0
  43. package/dist/cjs/peer/InteractionQueue.js +42 -0
  44. package/dist/cjs/peer/InteractionQueue.js.map +6 -0
  45. package/dist/cjs/peer/PeerAddressStore.d.ts +1 -1
  46. package/dist/cjs/peer/PeerAddressStore.d.ts.map +1 -1
  47. package/dist/cjs/peer/PeerSet.d.ts +16 -7
  48. package/dist/cjs/peer/PeerSet.d.ts.map +1 -1
  49. package/dist/cjs/peer/PeerSet.js +58 -61
  50. package/dist/cjs/peer/PeerSet.js.map +1 -1
  51. package/dist/cjs/peer/PhysicalDeviceProperties.d.ts +26 -0
  52. package/dist/cjs/peer/PhysicalDeviceProperties.d.ts.map +1 -0
  53. package/dist/cjs/peer/PhysicalDeviceProperties.js +74 -0
  54. package/dist/cjs/peer/PhysicalDeviceProperties.js.map +6 -0
  55. package/dist/cjs/peer/index.d.ts +1 -0
  56. package/dist/cjs/peer/index.d.ts.map +1 -1
  57. package/dist/cjs/peer/index.js +1 -0
  58. package/dist/cjs/peer/index.js.map +1 -1
  59. package/dist/cjs/protocol/ExchangeManager.d.ts +1 -0
  60. package/dist/cjs/protocol/ExchangeManager.d.ts.map +1 -1
  61. package/dist/cjs/protocol/ExchangeManager.js +6 -3
  62. package/dist/cjs/protocol/ExchangeManager.js.map +1 -1
  63. package/dist/cjs/protocol/ExchangeProvider.d.ts +13 -1
  64. package/dist/cjs/protocol/ExchangeProvider.d.ts.map +1 -1
  65. package/dist/cjs/protocol/ExchangeProvider.js +2 -0
  66. package/dist/cjs/protocol/ExchangeProvider.js.map +1 -1
  67. package/dist/cjs/protocol/ProtocolHandler.d.ts +1 -1
  68. package/dist/cjs/protocol/ProtocolHandler.d.ts.map +1 -1
  69. package/dist/cjs/securechannel/SecureChannelProtocol.d.ts +1 -1
  70. package/dist/cjs/securechannel/SecureChannelProtocol.d.ts.map +1 -1
  71. package/dist/cjs/securechannel/SecureChannelProtocol.js +1 -3
  72. package/dist/cjs/securechannel/SecureChannelProtocol.js.map +1 -1
  73. package/dist/cjs/session/SessionManager.d.ts.map +1 -1
  74. package/dist/cjs/session/SessionManager.js +1 -0
  75. package/dist/cjs/session/SessionManager.js.map +1 -1
  76. package/dist/cjs/session/case/CaseServer.d.ts +1 -1
  77. package/dist/cjs/session/case/CaseServer.d.ts.map +1 -1
  78. package/dist/cjs/session/case/CaseServer.js +1 -3
  79. package/dist/cjs/session/case/CaseServer.js.map +1 -1
  80. package/dist/cjs/session/pase/PaseServer.d.ts +2 -3
  81. package/dist/cjs/session/pase/PaseServer.d.ts.map +1 -1
  82. package/dist/cjs/session/pase/PaseServer.js +12 -14
  83. package/dist/cjs/session/pase/PaseServer.js.map +1 -1
  84. package/dist/esm/action/client/ClientInteraction.d.ts +38 -0
  85. package/dist/esm/action/client/ClientInteraction.d.ts.map +1 -0
  86. package/dist/esm/action/client/ClientInteraction.js +71 -0
  87. package/dist/esm/action/client/ClientInteraction.js.map +6 -0
  88. package/dist/esm/action/client/index.d.ts +7 -0
  89. package/dist/esm/action/client/index.d.ts.map +1 -0
  90. package/dist/esm/action/client/index.js +7 -0
  91. package/dist/esm/action/client/index.js.map +6 -0
  92. package/dist/esm/action/index.d.ts +1 -0
  93. package/dist/esm/action/index.d.ts.map +1 -1
  94. package/dist/esm/action/index.js +1 -0
  95. package/dist/esm/action/index.js.map +1 -1
  96. package/dist/esm/interaction/DecodedDataReport.d.ts +15 -0
  97. package/dist/esm/interaction/DecodedDataReport.d.ts.map +1 -0
  98. package/dist/esm/interaction/DecodedDataReport.js +22 -0
  99. package/dist/esm/interaction/DecodedDataReport.js.map +6 -0
  100. package/dist/esm/interaction/InteractionClient.d.ts +13 -23
  101. package/dist/esm/interaction/InteractionClient.d.ts.map +1 -1
  102. package/dist/esm/interaction/InteractionClient.js +101 -134
  103. package/dist/esm/interaction/InteractionClient.js.map +1 -1
  104. package/dist/esm/interaction/InteractionMessenger.d.ts +94 -1
  105. package/dist/esm/interaction/InteractionMessenger.d.ts.map +1 -1
  106. package/dist/esm/interaction/InteractionMessenger.js +56 -37
  107. package/dist/esm/interaction/InteractionMessenger.js.map +1 -1
  108. package/dist/esm/interaction/InteractionServer.d.ts +4 -2
  109. package/dist/esm/interaction/InteractionServer.d.ts.map +1 -1
  110. package/dist/esm/interaction/InteractionServer.js +12 -4
  111. package/dist/esm/interaction/InteractionServer.js.map +1 -1
  112. package/dist/esm/interaction/SubscriptionClient.d.ts +38 -0
  113. package/dist/esm/interaction/SubscriptionClient.d.ts.map +1 -0
  114. package/dist/esm/interaction/SubscriptionClient.js +78 -0
  115. package/dist/esm/interaction/SubscriptionClient.js.map +6 -0
  116. package/dist/esm/interaction/index.d.ts +1 -0
  117. package/dist/esm/interaction/index.d.ts.map +1 -1
  118. package/dist/esm/interaction/index.js +1 -0
  119. package/dist/esm/interaction/index.js.map +1 -1
  120. package/dist/esm/peer/ControllerCommissioner.d.ts +2 -2
  121. package/dist/esm/peer/ControllerCommissioner.d.ts.map +1 -1
  122. package/dist/esm/peer/ControllerCommissioner.js +6 -5
  123. package/dist/esm/peer/ControllerCommissioner.js.map +1 -1
  124. package/dist/esm/peer/InteractionQueue.d.ts +11 -0
  125. package/dist/esm/peer/InteractionQueue.d.ts.map +1 -0
  126. package/dist/esm/peer/InteractionQueue.js +22 -0
  127. package/dist/esm/peer/InteractionQueue.js.map +6 -0
  128. package/dist/esm/peer/PeerAddressStore.d.ts +1 -1
  129. package/dist/esm/peer/PeerAddressStore.d.ts.map +1 -1
  130. package/dist/esm/peer/PeerSet.d.ts +16 -7
  131. package/dist/esm/peer/PeerSet.d.ts.map +1 -1
  132. package/dist/esm/peer/PeerSet.js +59 -62
  133. package/dist/esm/peer/PeerSet.js.map +1 -1
  134. package/dist/esm/peer/PhysicalDeviceProperties.d.ts +26 -0
  135. package/dist/esm/peer/PhysicalDeviceProperties.d.ts.map +1 -0
  136. package/dist/esm/peer/PhysicalDeviceProperties.js +54 -0
  137. package/dist/esm/peer/PhysicalDeviceProperties.js.map +6 -0
  138. package/dist/esm/peer/index.d.ts +1 -0
  139. package/dist/esm/peer/index.d.ts.map +1 -1
  140. package/dist/esm/peer/index.js +1 -0
  141. package/dist/esm/peer/index.js.map +1 -1
  142. package/dist/esm/protocol/ExchangeManager.d.ts +1 -0
  143. package/dist/esm/protocol/ExchangeManager.d.ts.map +1 -1
  144. package/dist/esm/protocol/ExchangeManager.js +6 -3
  145. package/dist/esm/protocol/ExchangeManager.js.map +1 -1
  146. package/dist/esm/protocol/ExchangeProvider.d.ts +13 -1
  147. package/dist/esm/protocol/ExchangeProvider.d.ts.map +1 -1
  148. package/dist/esm/protocol/ExchangeProvider.js +2 -0
  149. package/dist/esm/protocol/ExchangeProvider.js.map +1 -1
  150. package/dist/esm/protocol/ProtocolHandler.d.ts +1 -1
  151. package/dist/esm/protocol/ProtocolHandler.d.ts.map +1 -1
  152. package/dist/esm/securechannel/SecureChannelProtocol.d.ts +1 -1
  153. package/dist/esm/securechannel/SecureChannelProtocol.d.ts.map +1 -1
  154. package/dist/esm/securechannel/SecureChannelProtocol.js +1 -3
  155. package/dist/esm/securechannel/SecureChannelProtocol.js.map +1 -1
  156. package/dist/esm/session/SessionManager.d.ts.map +1 -1
  157. package/dist/esm/session/SessionManager.js +1 -0
  158. package/dist/esm/session/SessionManager.js.map +1 -1
  159. package/dist/esm/session/case/CaseServer.d.ts +1 -1
  160. package/dist/esm/session/case/CaseServer.d.ts.map +1 -1
  161. package/dist/esm/session/case/CaseServer.js +1 -3
  162. package/dist/esm/session/case/CaseServer.js.map +1 -1
  163. package/dist/esm/session/pase/PaseServer.d.ts +2 -3
  164. package/dist/esm/session/pase/PaseServer.d.ts.map +1 -1
  165. package/dist/esm/session/pase/PaseServer.js +12 -14
  166. package/dist/esm/session/pase/PaseServer.js.map +1 -1
  167. package/package.json +6 -6
  168. package/src/action/client/ClientInteraction.ts +110 -0
  169. package/src/action/client/index.ts +7 -0
  170. package/src/action/index.ts +1 -0
  171. package/src/interaction/DecodedDataReport.ts +29 -0
  172. package/src/interaction/InteractionClient.ts +112 -164
  173. package/src/interaction/InteractionMessenger.ts +63 -43
  174. package/src/interaction/InteractionServer.ts +18 -5
  175. package/src/interaction/SubscriptionClient.ts +107 -0
  176. package/src/interaction/index.ts +1 -0
  177. package/src/peer/ControllerCommissioner.ts +7 -6
  178. package/src/peer/InteractionQueue.ts +22 -0
  179. package/src/peer/PeerAddressStore.ts +1 -1
  180. package/src/peer/PeerSet.ts +69 -76
  181. package/src/peer/PhysicalDeviceProperties.ts +80 -0
  182. package/src/peer/index.ts +1 -0
  183. package/src/protocol/ExchangeManager.ts +7 -3
  184. package/src/protocol/ExchangeProvider.ts +14 -1
  185. package/src/protocol/ProtocolHandler.ts +1 -1
  186. package/src/securechannel/SecureChannelProtocol.ts +1 -3
  187. package/src/session/SessionManager.ts +1 -0
  188. package/src/session/case/CaseServer.ts +2 -4
  189. package/src/session/pase/PaseServer.ts +13 -15
@@ -7,6 +7,7 @@
7
7
  import { DiscoveryData, ScannerSet } from "#common/Scanner.js";
8
8
  import {
9
9
  anyPromise,
10
+ AsyncObservable,
10
11
  BasicSet,
11
12
  ChannelType,
12
13
  Construction,
@@ -22,13 +23,12 @@ import {
22
23
  NetInterfaceSet,
23
24
  NoResponseTimeoutError,
24
25
  ObservableSet,
25
- PromiseQueue,
26
26
  ServerAddressIp,
27
27
  serverAddressToString,
28
28
  Time,
29
29
  Timer,
30
30
  } from "#general";
31
- import { InteractionClient } from "#interaction/InteractionClient.js";
31
+ import { SubscriptionClient } from "#interaction/SubscriptionClient.js";
32
32
  import { MdnsScanner } from "#mdns/MdnsScanner.js";
33
33
  import { PeerAddress, PeerAddressMap } from "#peer/PeerAddress.js";
34
34
  import { CaseClient, SecureSession, Session } from "#session/index.js";
@@ -39,6 +39,7 @@ import { ChannelNotConnectedError, ExchangeManager, MessageChannel } from "../pr
39
39
  import { ReconnectableExchangeProvider } from "../protocol/ExchangeProvider.js";
40
40
  import { RetransmissionLimitReachedError } from "../protocol/MessageExchange.js";
41
41
  import { ControllerDiscovery, DiscoveryError, PairRetransmissionLimitReachedError } from "./ControllerDiscovery.js";
42
+ import { InteractionQueue } from "./InteractionQueue.js";
42
43
  import { OperationalPeer } from "./OperationalPeer.js";
43
44
  import { PeerAddressStore, PeerDataStore } from "./PeerAddressStore.js";
44
45
 
@@ -47,9 +48,6 @@ const logger = Logger.get("PeerSet");
47
48
  const RECONNECTION_POLLING_INTERVAL_MS = 600_000; // 10 minutes
48
49
  const RETRANSMISSION_DISCOVERY_TIMEOUT_S = 5;
49
50
 
50
- const CONCURRENT_QUEUED_INTERACTIONS = 4;
51
- const INTERACTION_QUEUE_DELAY_MS = 100;
52
-
53
51
  /**
54
52
  * Types of discovery that may be performed when connecting operationally.
55
53
  */
@@ -93,6 +91,7 @@ export interface PeerSetContext {
93
91
  sessions: SessionManager;
94
92
  channels: ChannelManager;
95
93
  exchanges: ExchangeManager;
94
+ subscriptionClient: SubscriptionClient;
96
95
  scanners: ScannerSet;
97
96
  netInterfaces: NetInterfaceSet;
98
97
  store: PeerAddressStore;
@@ -105,6 +104,7 @@ export class PeerSet implements ImmutableSet<OperationalPeer>, ObservableSet<Ope
105
104
  readonly #sessions: SessionManager;
106
105
  readonly #channels: ChannelManager;
107
106
  readonly #exchanges: ExchangeManager;
107
+ readonly #subscriptionClient: SubscriptionClient;
108
108
  readonly #scanners: ScannerSet;
109
109
  readonly #netInterfaces: NetInterfaceSet;
110
110
  readonly #caseClient: CaseClient;
@@ -117,16 +117,17 @@ export class PeerSet implements ImmutableSet<OperationalPeer>, ObservableSet<Ope
117
117
  }>();
118
118
  readonly #construction: Construction<PeerSet>;
119
119
  readonly #store: PeerAddressStore;
120
- readonly #interactionQueue = new PromiseQueue(CONCURRENT_QUEUED_INTERACTIONS, INTERACTION_QUEUE_DELAY_MS);
120
+ readonly #interactionQueue = new InteractionQueue();
121
121
  readonly #nodeCachedData = new PeerAddressMap<PeerDataStore>(); // Temporarily until we store it in new API
122
- readonly #clients = new PeerAddressMap<InteractionClient>();
122
+ readonly #disconnected = AsyncObservable<[address: PeerAddress]>();
123
123
 
124
124
  constructor(context: PeerSetContext) {
125
- const { sessions, channels, exchanges, scanners, netInterfaces, store } = context;
125
+ const { sessions, channels, exchanges, subscriptionClient, scanners, netInterfaces, store } = context;
126
126
 
127
127
  this.#sessions = sessions;
128
128
  this.#channels = channels;
129
129
  this.#exchanges = exchanges;
130
+ this.#subscriptionClient = subscriptionClient;
130
131
  this.#scanners = scanners;
131
132
  this.#netInterfaces = netInterfaces;
132
133
  this.#store = store;
@@ -165,6 +166,10 @@ export class PeerSet implements ImmutableSet<OperationalPeer>, ObservableSet<Ope
165
166
  return this.#peers.deleted;
166
167
  }
167
168
 
169
+ get disconnected() {
170
+ return this.#disconnected;
171
+ }
172
+
168
173
  has(item: PeerAddress | OperationalPeer) {
169
174
  if ("address" in item) {
170
175
  return this.#peers.has(item);
@@ -201,6 +206,7 @@ export class PeerSet implements ImmutableSet<OperationalPeer>, ObservableSet<Ope
201
206
  sessions: env.get(SessionManager),
202
207
  channels: env.get(ChannelManager),
203
208
  exchanges: env.get(ExchangeManager),
209
+ subscriptionClient: env.get(SubscriptionClient),
204
210
  scanners: env.get(ScannerSet),
205
211
  netInterfaces: env.get(NetInterfaceSet),
206
212
  store: env.get(PeerAddressStore),
@@ -213,20 +219,18 @@ export class PeerSet implements ImmutableSet<OperationalPeer>, ObservableSet<Ope
213
219
  return this.#peers;
214
220
  }
215
221
 
216
- /**
217
- * Connect to a node on a fabric.
218
- */
219
- async connect(
220
- address: PeerAddress,
221
- discoveryOptions: DiscoveryOptions,
222
- allowUnknownPeer = false,
223
- ): Promise<InteractionClient> {
224
- await this.#ensureConnection(address, discoveryOptions, allowUnknownPeer);
222
+ get subscriptionClient() {
223
+ return this.#subscriptionClient;
224
+ }
225
225
 
226
- return this.initializeInteractionClient(address, discoveryOptions);
226
+ get interactionQueue() {
227
+ return this.#interactionQueue;
227
228
  }
228
229
 
229
- async #ensureConnection(address: PeerAddress, discoveryOptions: DiscoveryOptions, allowUnknownPeer = false) {
230
+ /**
231
+ * Ensure there is a channel to the designated peer.
232
+ */
233
+ async ensureConnection(address: PeerAddress, discoveryOptions: DiscoveryOptions, allowUnknownPeer = false) {
230
234
  if (!this.#peersByAddress.has(address) && !allowUnknownPeer) {
231
235
  throw new UnknownNodeError(`Cannot connect to unknown device ${PeerAddress(address)}`);
232
236
  }
@@ -256,62 +260,44 @@ export class PeerSet implements ImmutableSet<OperationalPeer>, ObservableSet<Ope
256
260
  }
257
261
  }
258
262
 
259
- async initializeInteractionClient(address: PeerAddress, discoveryOptions?: DiscoveryOptions) {
260
- const existingClient = this.#clients.get(address);
261
- if (existingClient !== undefined) {
262
- return existingClient;
263
- }
264
-
265
- const nodeStore = this.get(address)?.dataStore;
266
- await nodeStore?.construction; // Lazy initialize the data if not already done
267
-
263
+ /**
264
+ * Obtain an exchange provider for the designated peer.
265
+ */
266
+ async exchangeProviderFor(address: PeerAddress, discoveryOptions?: DiscoveryOptions) {
268
267
  let initiallyConnected = this.#channels.hasChannel(address);
269
- const client = new InteractionClient(
270
- new ReconnectableExchangeProvider(this.#exchanges, this.#channels, address, async () => {
271
- if (!initiallyConnected && !this.#channels.hasChannel(address)) {
272
- // We got an uninitialized node, so do the first connection as usual
273
- await this.#ensureConnection(address, { discoveryType: NodeDiscoveryType.None });
274
- initiallyConnected = true; // We only do this connection once, rest is handled in following code
275
- if (this.#channels.hasChannel(address)) {
276
- return;
277
- }
268
+ return new ReconnectableExchangeProvider(this.#exchanges, this.#channels, address, async () => {
269
+ if (!initiallyConnected && !this.#channels.hasChannel(address)) {
270
+ // We got an uninitialized node, so do the first connection as usual
271
+ await this.ensureConnection(address, { discoveryType: NodeDiscoveryType.None });
272
+ initiallyConnected = true; // We only do this connection once, rest is handled in following code
273
+ if (this.#channels.hasChannel(address)) {
274
+ return;
278
275
  }
276
+ }
279
277
 
280
- if (!this.#channels.hasChannel(address)) {
281
- throw new RetransmissionLimitReachedError(
282
- `Device ${PeerAddress(address)} is currently not reachable.`,
283
- );
284
- }
285
- await this.#channels.removeAllNodeChannels(address);
278
+ if (!this.#channels.hasChannel(address)) {
279
+ throw new RetransmissionLimitReachedError(`Device ${PeerAddress(address)} is currently not reachable.`);
280
+ }
281
+ await this.#channels.removeAllNodeChannels(address);
286
282
 
287
- // Enrich discoveryData with data from the node store when not provided
288
- const { discoveryData } = discoveryOptions ?? {
289
- discoveryData: this.#peersByAddress.get(address)?.discoveryData,
290
- };
291
- // Try to use first result for one last try before we need to reconnect
292
- const operationalAddress = this.#knownOperationalAddressFor(address, true);
293
- if (operationalAddress === undefined) {
294
- logger.info(
295
- `Re-discovering device failed (no address found), remove all sessions for ${PeerAddress(address)}`,
296
- );
297
- // We remove all sessions, this also informs the PairedNode class
298
- await this.#sessions.removeAllSessionsForNode(address);
299
- throw new RetransmissionLimitReachedError(
300
- `No operational address found for ${PeerAddress(address)}`,
301
- );
302
- }
303
- if (
304
- (await this.#reconnectKnownAddress(address, operationalAddress, discoveryData, 2_000)) === undefined
305
- ) {
306
- throw new RetransmissionLimitReachedError(`${PeerAddress(address)} is not reachable.`);
307
- }
308
- }),
309
- address,
310
- this.#interactionQueue,
311
- nodeStore,
312
- );
313
- this.#clients.set(address, client);
314
- return client;
283
+ // Enrich discoveryData with data from the node store when not provided
284
+ const { discoveryData } = discoveryOptions ?? {
285
+ discoveryData: this.#peersByAddress.get(address)?.discoveryData,
286
+ };
287
+ // Try to use first result for one last try before we need to reconnect
288
+ const operationalAddress = this.#knownOperationalAddressFor(address, true);
289
+ if (operationalAddress === undefined) {
290
+ logger.info(
291
+ `Re-discovering device failed (no address found), remove all sessions for ${PeerAddress(address)}`,
292
+ );
293
+ // We remove all sessions, this also informs the PairedNode class
294
+ await this.#sessions.removeAllSessionsForNode(address);
295
+ throw new RetransmissionLimitReachedError(`No operational address found for ${PeerAddress(address)}`);
296
+ }
297
+ if ((await this.#reconnectKnownAddress(address, operationalAddress, discoveryData, 2_000)) === undefined) {
298
+ throw new RetransmissionLimitReachedError(`${PeerAddress(address)} is not reachable.`);
299
+ }
300
+ });
315
301
  }
316
302
 
317
303
  /**
@@ -327,10 +313,15 @@ export class PeerSet implements ImmutableSet<OperationalPeer>, ObservableSet<Ope
327
313
  /**
328
314
  * Terminate any active peer connection.
329
315
  */
330
- async disconnect(address: PeerAddress, sendSessionClose = true) {
331
- this.#clients.get(address)?.removeAllSubscriptions();
316
+ async disconnect(peer: PeerAddress | OperationalPeer, sendSessionClose = true) {
317
+ const address = this.get(peer)?.address;
318
+ if (address === undefined) {
319
+ return;
320
+ }
321
+
332
322
  await this.#sessions.removeAllSessionsForNode(address, sendSessionClose);
333
323
  await this.#channels.removeAllNodeChannels(address);
324
+ await this.#disconnected.emit(address);
334
325
  }
335
326
 
336
327
  /**
@@ -348,7 +339,6 @@ export class PeerSet implements ImmutableSet<OperationalPeer>, ObservableSet<Ope
348
339
  await this.#store.deletePeer(address);
349
340
  await this.disconnect(address, false);
350
341
  await this.#sessions.deleteResumptionRecord(address);
351
- this.#clients.delete(address);
352
342
  }
353
343
 
354
344
  async close() {
@@ -358,8 +348,11 @@ export class PeerSet implements ImmutableSet<OperationalPeer>, ObservableSet<Ope
358
348
  // This ends discovery without triggering promises
359
349
  mdnsScanner?.cancelOperationalDeviceDiscovery(this.#sessions.fabricFor(address), address.nodeId, false);
360
350
  }
361
- this.#clients.forEach(client => client.close());
362
- this.#clients.clear();
351
+
352
+ for (const { address } of this.#peers) {
353
+ await this.disconnect(address, false);
354
+ }
355
+
363
356
  this.#interactionQueue.close();
364
357
  this.#runningPeerReconnections.forEach(({ rejecter }) =>
365
358
  rejecter(new ChannelNotConnectedError("PeerSet closed")),
@@ -0,0 +1,80 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2025 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import { Logger } from "#general";
8
+
9
+ const logger = Logger.get("PhysicalDeviceProperties");
10
+
11
+ const DEFAULT_SUBSCRIPTION_FLOOR_DEFAULT_S = 1;
12
+ const DEFAULT_SUBSCRIPTION_FLOOR_ICD_S = 0;
13
+ const DEFAULT_SUBSCRIPTION_CEILING_WIFI_S = 60;
14
+ const DEFAULT_SUBSCRIPTION_CEILING_THREAD_S = 60;
15
+ const DEFAULT_SUBSCRIPTION_CEILING_THREAD_SLEEPY_S = 180;
16
+ const DEFAULT_SUBSCRIPTION_CEILING_BATTERY_POWERED_S = 600;
17
+
18
+ export interface PhysicalDeviceProperties {
19
+ threadConnected: boolean;
20
+ wifiConnected: boolean;
21
+ ethernetConnected: boolean;
22
+ rootEndpointServerList: number[];
23
+ isBatteryPowered: boolean;
24
+ isIntermittentlyConnected: boolean;
25
+ isThreadSleepyEndDevice: boolean;
26
+ }
27
+
28
+ export namespace PhysicalDeviceProperties {
29
+ export function determineSubscriptionParameters(options?: {
30
+ properties?: PhysicalDeviceProperties;
31
+ description?: string;
32
+ subscribeMinIntervalFloorSeconds?: number;
33
+ subscribeMaxIntervalCeilingSeconds?: number;
34
+ }) {
35
+ const { properties } = options ?? {};
36
+
37
+ let {
38
+ description,
39
+ subscribeMinIntervalFloorSeconds: minIntervalFloorSeconds,
40
+ subscribeMaxIntervalCeilingSeconds: maxIntervalCeilingSeconds,
41
+ } = options ?? {};
42
+
43
+ if (description === undefined) {
44
+ description = "Node";
45
+ }
46
+
47
+ const { isBatteryPowered, isIntermittentlyConnected, threadConnected, isThreadSleepyEndDevice } =
48
+ properties ?? {};
49
+
50
+ if (isIntermittentlyConnected) {
51
+ if (minIntervalFloorSeconds !== undefined && minIntervalFloorSeconds !== DEFAULT_SUBSCRIPTION_FLOOR_ICD_S) {
52
+ logger.info(
53
+ `${description}: Overwriting minIntervalFloorSeconds for intermittently connected device to ${DEFAULT_SUBSCRIPTION_FLOOR_ICD_S}`,
54
+ );
55
+ minIntervalFloorSeconds = DEFAULT_SUBSCRIPTION_FLOOR_ICD_S;
56
+ }
57
+ }
58
+
59
+ const defaultCeiling = isBatteryPowered
60
+ ? DEFAULT_SUBSCRIPTION_CEILING_BATTERY_POWERED_S
61
+ : isThreadSleepyEndDevice
62
+ ? DEFAULT_SUBSCRIPTION_CEILING_THREAD_SLEEPY_S
63
+ : threadConnected
64
+ ? DEFAULT_SUBSCRIPTION_CEILING_THREAD_S
65
+ : DEFAULT_SUBSCRIPTION_CEILING_WIFI_S;
66
+ if (maxIntervalCeilingSeconds === undefined) {
67
+ maxIntervalCeilingSeconds = defaultCeiling;
68
+ }
69
+ if (maxIntervalCeilingSeconds < defaultCeiling) {
70
+ logger.debug(
71
+ `${description}: maxIntervalCeilingSeconds ideally is ${defaultCeiling}s instead of ${maxIntervalCeilingSeconds}s due to device type`,
72
+ );
73
+ }
74
+
75
+ return {
76
+ minIntervalFloorSeconds: minIntervalFloorSeconds ?? DEFAULT_SUBSCRIPTION_FLOOR_DEFAULT_S,
77
+ maxIntervalCeilingSeconds,
78
+ };
79
+ }
80
+ }
package/src/peer/index.ts CHANGED
@@ -11,3 +11,4 @@ export * from "./OperationalPeer.js";
11
11
  export * from "./PeerAddress.js";
12
12
  export * from "./PeerAddressStore.js";
13
13
  export * from "./PeerSet.js";
14
+ export * from "./PhysicalDeviceProperties.js";
@@ -150,6 +150,10 @@ export class ExchangeManager {
150
150
  return instance;
151
151
  }
152
152
 
153
+ get channels() {
154
+ return this.#channelManager;
155
+ }
156
+
153
157
  hasProtocolHandler(protocolId: number) {
154
158
  return this.#protocols.has(protocolId);
155
159
  }
@@ -159,10 +163,10 @@ export class ExchangeManager {
159
163
  }
160
164
 
161
165
  addProtocolHandler(protocol: ProtocolHandler) {
162
- if (this.hasProtocolHandler(protocol.getId())) {
163
- throw new ImplementationError(`Handler for protocol ${protocol.getId()} already registered.`);
166
+ if (this.hasProtocolHandler(protocol.id)) {
167
+ throw new ImplementationError(`Handler for protocol ${protocol.id} already registered.`);
164
168
  }
165
- this.#protocols.set(protocol.getId(), protocol);
169
+ this.#protocols.set(protocol.id, protocol);
166
170
  }
167
171
 
168
172
  initiateExchange(address: PeerAddress, protocolId: number) {
@@ -12,8 +12,13 @@ import { MessageExchange } from "../protocol/MessageExchange.js";
12
12
  import { ProtocolHandler } from "../protocol/ProtocolHandler.js";
13
13
  import { Session } from "../session/Session.js";
14
14
 
15
+ /**
16
+ * Interface for obtaining an exchange with a specific peer.
17
+ */
15
18
  export abstract class ExchangeProvider {
16
- protected constructor(protected readonly exchangeManager: ExchangeManager) {}
19
+ abstract readonly supportsReconnect: boolean;
20
+
21
+ constructor(protected readonly exchangeManager: ExchangeManager) {}
17
22
 
18
23
  hasProtocolHandler(protocolId: number) {
19
24
  return this.exchangeManager.hasProtocolHandler(protocolId);
@@ -33,8 +38,12 @@ export abstract class ExchangeProvider {
33
38
  abstract channelType: ChannelType;
34
39
  }
35
40
 
41
+ /**
42
+ * Manages an exchange over an established channel.
43
+ */
36
44
  export class DedicatedChannelExchangeProvider extends ExchangeProvider {
37
45
  #channel: MessageChannel;
46
+ readonly supportsReconnect = false;
38
47
 
39
48
  constructor(exchangeManager: ExchangeManager, channel: MessageChannel) {
40
49
  super(exchangeManager);
@@ -58,7 +67,11 @@ export class DedicatedChannelExchangeProvider extends ExchangeProvider {
58
67
  }
59
68
  }
60
69
 
70
+ /**
71
+ * Manages peer exchange that will reestablish automatically in the case of communication failure.
72
+ */
61
73
  export class ReconnectableExchangeProvider extends ExchangeProvider {
74
+ readonly supportsReconnect = true;
62
75
  readonly #address: PeerAddress;
63
76
  readonly #reconnectChannelFunc: () => Promise<void>;
64
77
  readonly #channelUpdated = Observable<[void]>();
@@ -8,7 +8,7 @@ import { Message } from "../codec/MessageCodec.js";
8
8
  import { MessageExchange } from "./MessageExchange.js";
9
9
 
10
10
  export interface ProtocolHandler {
11
- getId(): number;
11
+ readonly id: number;
12
12
  onNewExchange(exchange: MessageExchange, message: Message): Promise<void>;
13
13
  close(): Promise<void>;
14
14
  }
@@ -28,9 +28,7 @@ import { TlvSecureChannelStatusMessage } from "./SecureChannelStatusMessageSchem
28
28
  const logger = Logger.get("SecureChannelProtocol");
29
29
 
30
30
  export class StatusReportOnlySecureChannelProtocol implements ProtocolHandler {
31
- getId(): number {
32
- return SECURE_CHANNEL_PROTOCOL_ID;
33
- }
31
+ readonly id = SECURE_CHANNEL_PROTOCOL_ID;
34
32
 
35
33
  async onNewExchange(exchange: MessageExchange, message: Message) {
36
34
  const messageType = message.payloadHeader.messageType;
@@ -386,6 +386,7 @@ export class SessionManager {
386
386
  const secureSession = session;
387
387
  if (secureSession.peerIs(address)) {
388
388
  await secureSession.destroy(sendClose, false);
389
+ this.#sessions.delete(session);
389
390
  }
390
391
  }
391
392
  }
@@ -30,6 +30,8 @@ import { CaseServerMessenger } from "./CaseMessenger.js";
30
30
  const logger = Logger.get("CaseServer");
31
31
 
32
32
  export class CaseServer implements ProtocolHandler {
33
+ readonly id = SECURE_CHANNEL_PROTOCOL_ID;
34
+
33
35
  #sessions: SessionManager;
34
36
  #fabrics: FabricManager;
35
37
 
@@ -58,10 +60,6 @@ export class CaseServer implements ProtocolHandler {
58
60
  }
59
61
  }
60
62
 
61
- getId(): number {
62
- return SECURE_CHANNEL_PROTOCOL_ID;
63
- }
64
-
65
63
  private async handleSigma1(messenger: CaseServerMessenger) {
66
64
  logger.info(`Received pairing request from ${messenger.getChannelName()}`);
67
65
  // Generate pairing info
@@ -33,8 +33,10 @@ const PASE_COMMISSIONING_MAX_ERRORS = 20;
33
33
  export class MaximumPasePairingErrorsReachedError extends MatterFlowError {}
34
34
 
35
35
  export class PaseServer implements ProtocolHandler {
36
- private pairingTimer: Timer | undefined;
37
- private pairingErrors = 0;
36
+ readonly id = SECURE_CHANNEL_PROTOCOL_ID;
37
+
38
+ #pairingTimer: Timer | undefined;
39
+ #pairingErrors = 0;
38
40
 
39
41
  static async fromPin(sessions: SessionManager, setupPinCode: number, pbkdfParameters: PbkdfParameters) {
40
42
  const { w0, L } = await Spake2p.computeW0L(pbkdfParameters, setupPinCode);
@@ -58,18 +60,14 @@ export class PaseServer implements ProtocolHandler {
58
60
  private readonly pbkdfParameters?: PbkdfParameters,
59
61
  ) {}
60
62
 
61
- getId(): number {
62
- return SECURE_CHANNEL_PROTOCOL_ID;
63
- }
64
-
65
63
  async onNewExchange(exchange: MessageExchange) {
66
64
  const messenger = new PaseServerMessenger(exchange);
67
65
  try {
68
66
  await this.handlePairingRequest(messenger);
69
67
  } catch (error) {
70
- this.pairingErrors++;
68
+ this.#pairingErrors++;
71
69
  logger.error(
72
- `An error occurred during the PASE commissioning (${this.pairingErrors}/${PASE_COMMISSIONING_MAX_ERRORS}):`,
70
+ `An error occurred during the PASE commissioning (${this.#pairingErrors}/${PASE_COMMISSIONING_MAX_ERRORS}):`,
73
71
  error,
74
72
  );
75
73
 
@@ -77,7 +75,7 @@ export class PaseServer implements ProtocolHandler {
77
75
  const sendError = !(error instanceof ChannelStatusResponseError);
78
76
  await this.cancelPairing(messenger, sendError);
79
77
 
80
- if (this.pairingErrors >= PASE_COMMISSIONING_MAX_ERRORS) {
78
+ if (this.#pairingErrors >= PASE_COMMISSIONING_MAX_ERRORS) {
81
79
  throw new MaximumPasePairingErrorsReachedError(
82
80
  `Pase server: Too many errors during PASE commissioning, aborting commissioning window`,
83
81
  );
@@ -99,7 +97,7 @@ export class PaseServer implements ProtocolHandler {
99
97
  );
100
98
  }
101
99
 
102
- if (this.pairingTimer !== undefined && this.pairingTimer.isRunning) {
100
+ if (this.#pairingTimer !== undefined && this.#pairingTimer.isRunning) {
103
101
  throw new MatterFlowError(
104
102
  "Pase server: Pairing already in progress (PASE establishment Timer running), ignoring new exchange.",
105
103
  );
@@ -107,7 +105,7 @@ export class PaseServer implements ProtocolHandler {
107
105
 
108
106
  logger.info(`Received pairing request from ${messenger.getChannelName()}.`);
109
107
 
110
- this.pairingTimer = Time.getTimer("PASE pairing timeout", PASE_PAIRING_TIMEOUT_MS, () =>
108
+ this.#pairingTimer = Time.getTimer("PASE pairing timeout", PASE_PAIRING_TIMEOUT_MS, () =>
111
109
  this.cancelPairing(messenger),
112
110
  ).start();
113
111
 
@@ -167,13 +165,13 @@ export class PaseServer implements ProtocolHandler {
167
165
  await messenger.sendSuccess();
168
166
  await messenger.close();
169
167
 
170
- this.pairingTimer?.stop();
171
- this.pairingTimer = undefined;
168
+ this.#pairingTimer?.stop();
169
+ this.#pairingTimer = undefined;
172
170
  }
173
171
 
174
172
  async cancelPairing(messenger: PaseServerMessenger, sendError = true) {
175
- this.pairingTimer?.stop();
176
- this.pairingTimer = undefined;
173
+ this.#pairingTimer?.stop();
174
+ this.#pairingTimer = undefined;
177
175
 
178
176
  if (sendError) {
179
177
  await messenger.sendError(ProtocolStatusCode.InvalidParam);