@matter/protocol 0.16.8-alpha.0-20260125-38e62bc3e → 0.16.8-alpha.0-20260127-65e1b40e2

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 (130) hide show
  1. package/dist/cjs/dcl/DclCertificateService.d.ts.map +1 -1
  2. package/dist/cjs/dcl/DclCertificateService.js +3 -0
  3. package/dist/cjs/dcl/DclCertificateService.js.map +1 -1
  4. package/dist/cjs/dcl/DclOtaUpdateService.d.ts.map +1 -1
  5. package/dist/cjs/dcl/DclOtaUpdateService.js +6 -4
  6. package/dist/cjs/dcl/DclOtaUpdateService.js.map +1 -1
  7. package/dist/cjs/mdns/MdnsClient.d.ts.map +1 -1
  8. package/dist/cjs/mdns/MdnsClient.js +6 -2
  9. package/dist/cjs/mdns/MdnsClient.js.map +1 -1
  10. package/dist/cjs/peer/Peer.d.ts +2 -1
  11. package/dist/cjs/peer/Peer.d.ts.map +1 -1
  12. package/dist/cjs/peer/Peer.js +20 -3
  13. package/dist/cjs/peer/Peer.js.map +1 -1
  14. package/dist/cjs/peer/PeerAddressStore.d.ts +1 -11
  15. package/dist/cjs/peer/PeerAddressStore.d.ts.map +1 -1
  16. package/dist/cjs/peer/PeerAddressStore.js +1 -4
  17. package/dist/cjs/peer/PeerAddressStore.js.map +1 -1
  18. package/dist/cjs/peer/PeerDescriptor.d.ts +1 -9
  19. package/dist/cjs/peer/PeerDescriptor.d.ts.map +1 -1
  20. package/dist/cjs/peer/PeerDescriptor.js +1 -6
  21. package/dist/cjs/peer/PeerDescriptor.js.map +1 -1
  22. package/dist/cjs/peer/PeerSet.d.ts +1 -1
  23. package/dist/cjs/peer/PeerSet.d.ts.map +1 -1
  24. package/dist/cjs/peer/PeerSet.js +55 -22
  25. package/dist/cjs/peer/PeerSet.js.map +2 -2
  26. package/dist/cjs/protocol/ExchangeManager.d.ts.map +1 -1
  27. package/dist/cjs/protocol/ExchangeManager.js +2 -2
  28. package/dist/cjs/protocol/ExchangeManager.js.map +1 -1
  29. package/dist/cjs/protocol/ExchangeProvider.d.ts.map +1 -1
  30. package/dist/cjs/protocol/ExchangeProvider.js +3 -3
  31. package/dist/cjs/protocol/ExchangeProvider.js.map +1 -1
  32. package/dist/cjs/protocol/MRP.d.ts +54 -0
  33. package/dist/cjs/protocol/MRP.d.ts.map +1 -0
  34. package/dist/cjs/protocol/MRP.js +96 -0
  35. package/dist/cjs/protocol/MRP.js.map +6 -0
  36. package/dist/cjs/protocol/MessageChannel.d.ts +0 -23
  37. package/dist/cjs/protocol/MessageChannel.d.ts.map +1 -1
  38. package/dist/cjs/protocol/MessageChannel.js +15 -47
  39. package/dist/cjs/protocol/MessageChannel.js.map +2 -2
  40. package/dist/cjs/protocol/MessageExchange.d.ts.map +1 -1
  41. package/dist/cjs/protocol/MessageExchange.js +7 -7
  42. package/dist/cjs/protocol/MessageExchange.js.map +1 -1
  43. package/dist/cjs/protocol/index.d.ts +1 -0
  44. package/dist/cjs/protocol/index.d.ts.map +1 -1
  45. package/dist/cjs/protocol/index.js +1 -0
  46. package/dist/cjs/protocol/index.js.map +1 -1
  47. package/dist/cjs/session/NodeSession.js +2 -2
  48. package/dist/cjs/session/NodeSession.js.map +1 -1
  49. package/dist/cjs/session/Session.d.ts +1 -0
  50. package/dist/cjs/session/Session.d.ts.map +1 -1
  51. package/dist/cjs/session/case/CaseClient.d.ts.map +1 -1
  52. package/dist/cjs/session/case/CaseClient.js +1 -1
  53. package/dist/cjs/session/case/CaseClient.js.map +1 -1
  54. package/dist/cjs/session/case/CaseServer.d.ts.map +1 -1
  55. package/dist/cjs/session/case/CaseServer.js +4 -1
  56. package/dist/cjs/session/case/CaseServer.js.map +1 -1
  57. package/dist/esm/dcl/DclCertificateService.d.ts.map +1 -1
  58. package/dist/esm/dcl/DclCertificateService.js +3 -0
  59. package/dist/esm/dcl/DclCertificateService.js.map +1 -1
  60. package/dist/esm/dcl/DclOtaUpdateService.d.ts.map +1 -1
  61. package/dist/esm/dcl/DclOtaUpdateService.js +6 -4
  62. package/dist/esm/dcl/DclOtaUpdateService.js.map +1 -1
  63. package/dist/esm/mdns/MdnsClient.d.ts.map +1 -1
  64. package/dist/esm/mdns/MdnsClient.js +6 -2
  65. package/dist/esm/mdns/MdnsClient.js.map +1 -1
  66. package/dist/esm/peer/Peer.d.ts +2 -1
  67. package/dist/esm/peer/Peer.d.ts.map +1 -1
  68. package/dist/esm/peer/Peer.js +20 -3
  69. package/dist/esm/peer/Peer.js.map +1 -1
  70. package/dist/esm/peer/PeerAddressStore.d.ts +1 -11
  71. package/dist/esm/peer/PeerAddressStore.d.ts.map +1 -1
  72. package/dist/esm/peer/PeerAddressStore.js +1 -4
  73. package/dist/esm/peer/PeerAddressStore.js.map +1 -1
  74. package/dist/esm/peer/PeerDescriptor.d.ts +1 -9
  75. package/dist/esm/peer/PeerDescriptor.d.ts.map +1 -1
  76. package/dist/esm/peer/PeerDescriptor.js +1 -6
  77. package/dist/esm/peer/PeerDescriptor.js.map +1 -1
  78. package/dist/esm/peer/PeerSet.d.ts +1 -1
  79. package/dist/esm/peer/PeerSet.d.ts.map +1 -1
  80. package/dist/esm/peer/PeerSet.js +60 -24
  81. package/dist/esm/peer/PeerSet.js.map +2 -2
  82. package/dist/esm/protocol/ExchangeManager.d.ts.map +1 -1
  83. package/dist/esm/protocol/ExchangeManager.js +2 -2
  84. package/dist/esm/protocol/ExchangeManager.js.map +1 -1
  85. package/dist/esm/protocol/ExchangeProvider.d.ts.map +1 -1
  86. package/dist/esm/protocol/ExchangeProvider.js +3 -3
  87. package/dist/esm/protocol/ExchangeProvider.js.map +1 -1
  88. package/dist/esm/protocol/MRP.d.ts +54 -0
  89. package/dist/esm/protocol/MRP.d.ts.map +1 -0
  90. package/dist/esm/protocol/MRP.js +76 -0
  91. package/dist/esm/protocol/MRP.js.map +6 -0
  92. package/dist/esm/protocol/MessageChannel.d.ts +0 -23
  93. package/dist/esm/protocol/MessageChannel.d.ts.map +1 -1
  94. package/dist/esm/protocol/MessageChannel.js +16 -54
  95. package/dist/esm/protocol/MessageChannel.js.map +2 -2
  96. package/dist/esm/protocol/MessageExchange.d.ts.map +1 -1
  97. package/dist/esm/protocol/MessageExchange.js +3 -3
  98. package/dist/esm/protocol/MessageExchange.js.map +1 -1
  99. package/dist/esm/protocol/index.d.ts +1 -0
  100. package/dist/esm/protocol/index.d.ts.map +1 -1
  101. package/dist/esm/protocol/index.js +1 -0
  102. package/dist/esm/protocol/index.js.map +1 -1
  103. package/dist/esm/session/NodeSession.js +2 -2
  104. package/dist/esm/session/NodeSession.js.map +1 -1
  105. package/dist/esm/session/Session.d.ts +1 -0
  106. package/dist/esm/session/Session.d.ts.map +1 -1
  107. package/dist/esm/session/case/CaseClient.d.ts.map +1 -1
  108. package/dist/esm/session/case/CaseClient.js +2 -2
  109. package/dist/esm/session/case/CaseClient.js.map +1 -1
  110. package/dist/esm/session/case/CaseServer.d.ts.map +1 -1
  111. package/dist/esm/session/case/CaseServer.js +4 -1
  112. package/dist/esm/session/case/CaseServer.js.map +1 -1
  113. package/package.json +6 -6
  114. package/src/dcl/DclCertificateService.ts +3 -0
  115. package/src/dcl/DclOtaUpdateService.ts +11 -5
  116. package/src/mdns/MdnsClient.ts +9 -2
  117. package/src/peer/Peer.ts +28 -5
  118. package/src/peer/PeerAddressStore.ts +1 -19
  119. package/src/peer/PeerDescriptor.ts +1 -15
  120. package/src/peer/PeerSet.ts +82 -35
  121. package/src/protocol/ExchangeManager.ts +5 -2
  122. package/src/protocol/ExchangeProvider.ts +3 -3
  123. package/src/protocol/MRP.ts +146 -0
  124. package/src/protocol/MessageChannel.ts +16 -101
  125. package/src/protocol/MessageExchange.ts +4 -3
  126. package/src/protocol/index.ts +1 -0
  127. package/src/session/NodeSession.ts +3 -3
  128. package/src/session/Session.ts +1 -0
  129. package/src/session/case/CaseClient.ts +8 -2
  130. package/src/session/case/CaseServer.ts +4 -0
@@ -4,9 +4,7 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
- import { Construction, MaybePromise } from "#general";
8
- import { DecodedAttributeReportValue } from "#interaction/AttributeDataDecoder.js";
9
- import { AttributeId, ClusterId, EndpointNumber, EventNumber } from "#types";
7
+ import { MaybePromise } from "#general";
10
8
  import { PeerAddress } from "./PeerAddress.js";
11
9
  import { PeerDescriptor } from "./PeerDescriptor.js";
12
10
  import type { PeerSet } from "./PeerSet.js";
@@ -18,20 +16,4 @@ export abstract class PeerAddressStore {
18
16
  abstract loadPeers(): MaybePromise<Iterable<PeerDescriptor>>;
19
17
  abstract updatePeer(peer: PeerDescriptor): MaybePromise<void>;
20
18
  abstract deletePeer(address: PeerAddress): MaybePromise<void>;
21
- abstract createNodeStore(address: PeerAddress): MaybePromise<PeerDataStore | undefined>;
22
- }
23
-
24
- export abstract class PeerDataStore {
25
- abstract construction: Construction<PeerDataStore>;
26
-
27
- abstract maxEventNumber: EventNumber;
28
- abstract updateLastEventNumber(eventNumber: EventNumber): MaybePromise<void>;
29
-
30
- // TODO: Find a maybe better way to achieve this without functions
31
- abstract retrieveAttribute(
32
- endpointId: EndpointNumber,
33
- clusterId: ClusterId,
34
- attributeId: AttributeId,
35
- ): DecodedAttributeReportValue<any> | undefined;
36
- abstract retrieveAttributes(endpointId: EndpointNumber, clusterId: ClusterId): DecodedAttributeReportValue<any>[];
37
19
  }
@@ -6,7 +6,6 @@
6
6
 
7
7
  import { DiscoveryData } from "#common/Scanner.js";
8
8
  import { isDeepEqual, ServerAddressUdp } from "#general";
9
- import type { PeerDataStore } from "#peer/PeerAddressStore.js";
10
9
  import { PeerAddress } from "./PeerAddress.js";
11
10
 
12
11
  /**
@@ -29,27 +28,18 @@ export interface PeerDescriptor {
29
28
  * Additional information collected while locating the peer.
30
29
  */
31
30
  discoveryData?: DiscoveryData;
32
-
33
- /**
34
- * The data store for the peer.
35
- *
36
- * @deprecated
37
- */
38
- dataStore?: PeerDataStore;
39
31
  }
40
32
 
41
33
  export class ObservablePeerDescriptor implements PeerDescriptor {
42
34
  #address: PeerAddress;
43
35
  #operationalAddress?: ServerAddressUdp;
44
36
  #discoveryData?: DiscoveryData;
45
- #dataStore?: PeerDataStore;
46
37
  #onChange: () => void;
47
38
 
48
- constructor({ address, operationalAddress, discoveryData, dataStore }: PeerDescriptor, onChange: () => void) {
39
+ constructor({ address, operationalAddress, discoveryData }: PeerDescriptor, onChange: () => void) {
49
40
  this.#address = PeerAddress(address);
50
41
  this.#operationalAddress = operationalAddress;
51
42
  this.#discoveryData = discoveryData;
52
- this.#dataStore = dataStore;
53
43
  this.#onChange = onChange;
54
44
  }
55
45
 
@@ -82,8 +72,4 @@ export class ObservablePeerDescriptor implements PeerDescriptor {
82
72
  this.#discoveryData = { ...this.#discoveryData, ...value };
83
73
  this.#onChange();
84
74
  }
85
-
86
- get dataStore() {
87
- return this.#dataStore;
88
- }
89
75
  }
@@ -6,6 +6,7 @@
6
6
 
7
7
  import { DiscoveryData, ScannerSet } from "#common/Scanner.js";
8
8
  import {
9
+ AbortedError,
9
10
  anyPromise,
10
11
  AsyncObservable,
11
12
  BasicSet,
@@ -18,6 +19,7 @@ import {
18
19
  Environmental,
19
20
  ImmutableSet,
20
21
  ImplementationError,
22
+ isIpNetworkChannel,
21
23
  isIPv6,
22
24
  Lifetime,
23
25
  Logger,
@@ -31,9 +33,10 @@ import {
31
33
  ServerAddressUdp,
32
34
  Time,
33
35
  Timer,
36
+ Timestamp,
34
37
  } from "#general";
35
38
  import { MdnsClient } from "#mdns/MdnsClient.js";
36
- import { PeerAddress, PeerAddressMap } from "#peer/PeerAddress.js";
39
+ import { PeerAddress } from "#peer/PeerAddress.js";
37
40
  import { RetransmissionLimitReachedError } from "#protocol/errors.js";
38
41
  import { ExchangeManager } from "#protocol/ExchangeManager.js";
39
42
  import { DedicatedChannelExchangeProvider, ReconnectableExchangeProvider } from "#protocol/ExchangeProvider.js";
@@ -47,7 +50,7 @@ import { SessionParameters } from "#session/SessionParameters.js";
47
50
  import { CaseAuthenticatedTag, NodeId, SECURE_CHANNEL_PROTOCOL_ID, SecureChannelStatusCode } from "#types";
48
51
  import { ControllerDiscovery, DiscoveryError, PairRetransmissionLimitReachedError } from "./ControllerDiscovery.js";
49
52
  import { Peer } from "./Peer.js";
50
- import { PeerAddressStore, PeerDataStore } from "./PeerAddressStore.js";
53
+ import { PeerAddressStore } from "./PeerAddressStore.js";
51
54
  import { PeerDescriptor } from "./PeerDescriptor.js";
52
55
 
53
56
  const logger = Logger.get("PeerSet");
@@ -118,7 +121,6 @@ export class PeerSet implements ImmutableSet<Peer>, ObservableSet<Peer> {
118
121
  readonly #peers = new BasicSet<Peer>();
119
122
  readonly #construction: Construction<PeerSet>;
120
123
  readonly #store: PeerAddressStore;
121
- readonly #nodeCachedData = new PeerAddressMap<PeerDataStore>(); // Temporarily until we store it in new API
122
124
  readonly #disconnected = AsyncObservable<[peer: Peer]>();
123
125
  readonly #peerContext: Peer.Context;
124
126
 
@@ -149,6 +151,8 @@ export class PeerSet implements ImmutableSet<Peer>, ObservableSet<Peer> {
149
151
  });
150
152
  });
151
153
 
154
+ this.#sessions.sessions.added.on(session => this.#addOrUpdatePeer(session.peerAddress, session));
155
+
152
156
  this.#sessions.retry.on((session, count) => {
153
157
  if (count !== 1) {
154
158
  return;
@@ -291,8 +295,8 @@ export class PeerSet implements ImmutableSet<Peer>, ObservableSet<Peer> {
291
295
  }
292
296
  }
293
297
 
294
- const { promise, resolver, rejecter } = createPromise<SecureSession>();
295
- peer.activeReconnection = { promise, rejecter };
298
+ const { promise, resolver, rejecter } = createPromise<SecureSession | undefined>();
299
+ peer.activeReconnection = { promise, resolver, rejecter };
296
300
 
297
301
  this.#resume(address, options, operationalAddress)
298
302
  .then(channel => {
@@ -433,6 +437,7 @@ export class PeerSet implements ImmutableSet<Peer>, ObservableSet<Peer> {
433
437
  ? this.#getLastOperationalAddress(address)
434
438
  : this.#knownOperationalAddressFor(address));
435
439
 
440
+ const startTime = Time.nowMs;
436
441
  try {
437
442
  return await this.#connectOrDiscoverNode(address, operationalAddress, options);
438
443
  } catch (error) {
@@ -441,11 +446,17 @@ export class PeerSet implements ImmutableSet<Peer>, ObservableSet<Peer> {
441
446
  this.has(address) &&
442
447
  tryOperationalAddress === undefined
443
448
  ) {
444
- logger.info(`Resume failed, remove all sessions for ${PeerAddress(address)}`);
449
+ logger.info(
450
+ `Resume failed, remove all sessions for ${PeerAddress(address)} before ${Timestamp.dateOf(startTime)}`,
451
+ );
445
452
  // We remove all sessions, this also informs the PairedNode class
446
- await this.#sessions.handlePeerLoss(address);
453
+ await this.#sessions.handlePeerLoss(address, startTime);
454
+ }
455
+
456
+ // Ok, no new session
457
+ if (this.#sessions.maybeSessionFor(address) === undefined) {
458
+ throw error;
447
459
  }
448
- throw error;
449
460
  }
450
461
  }
451
462
 
@@ -453,7 +464,7 @@ export class PeerSet implements ImmutableSet<Peer>, ObservableSet<Peer> {
453
464
  address: PeerAddress,
454
465
  operationalAddress?: ServerAddressUdp,
455
466
  options?: PeerConnectionOptions,
456
- ) {
467
+ ): Promise<SecureSession> {
457
468
  address = PeerAddress(address);
458
469
  const {
459
470
  discoveryOptions: {
@@ -493,13 +504,24 @@ export class PeerSet implements ImmutableSet<Peer>, ObservableSet<Peer> {
493
504
 
494
505
  const { type: runningDiscoveryType, promises } = existingDiscoveryDetails;
495
506
 
496
- // If we have a last known address try to reach the device directly when we are not already discovering
507
+ // If we have a last known address, try to reach the device directly when we are not already discovering
497
508
  // In worst case parallel cases we do this step twice, but that's ok
498
509
  if (
499
510
  operationalAddress !== undefined &&
500
511
  (runningDiscoveryType === NodeDiscoveryType.None || requestedDiscoveryType === NodeDiscoveryType.None)
501
512
  ) {
513
+ const session = this.#sessions.maybeSessionFor(address);
502
514
  const queueSlot = await queue?.obtainSlot();
515
+
516
+ if (queueSlot !== undefined) {
517
+ // If we got a new session while waiting for the queue slot, we assume we are done here
518
+ const currentSession = this.#sessions.maybeSessionFor(address);
519
+ if (currentSession?.isSecure && session !== currentSession) {
520
+ queueSlot.close();
521
+ return currentSession;
522
+ }
523
+ }
524
+
503
525
  try {
504
526
  const directReconnection = await this.#reconnectKnownAddress(
505
527
  address,
@@ -539,7 +561,7 @@ export class PeerSet implements ImmutableSet<Peer>, ObservableSet<Peer> {
539
561
 
540
562
  const lastOperationalAddress = this.#getLastOperationalAddress(address);
541
563
  if (lastOperationalAddress !== undefined) {
542
- // Additionally to general discovery we also try to poll the formerly known operational address
564
+ // Additionally to general discovery, we also try to poll the formerly known operational address
543
565
  if (requestedDiscoveryType === NodeDiscoveryType.FullDiscovery) {
544
566
  const { promise, resolver, rejecter } = createPromise<SecureSession>();
545
567
 
@@ -601,7 +623,15 @@ export class PeerSet implements ImmutableSet<Peer>, ObservableSet<Peer> {
601
623
  timeout,
602
624
  timeout === undefined,
603
625
  );
604
- const { stopTimerFunc } = peer.activeDiscovery ?? {};
626
+ if (peer.activeDiscovery === undefined) {
627
+ // It seems the discovery was canceled outside of this function, so we can return early if we have a session
628
+ const session = this.#sessions.maybeSessionFor(address);
629
+ if (session !== undefined) {
630
+ return session;
631
+ }
632
+ throw new NoResponseTimeoutError("Discovery was cancelled but we have no session, retry");
633
+ }
634
+ const { stopTimerFunc } = peer.activeDiscovery;
605
635
  stopTimerFunc?.();
606
636
  peer.activeDiscovery = undefined;
607
637
 
@@ -616,17 +646,23 @@ export class PeerSet implements ImmutableSet<Peer>, ObservableSet<Peer> {
616
646
  return device !== undefined ? [device] : [];
617
647
  },
618
648
  async (operationalAddress, peer) => {
649
+ // When we got a session, we are done
650
+ const session = this.#sessions.maybeSessionFor(address);
651
+ if (session !== undefined) {
652
+ return session;
653
+ }
654
+
619
655
  const peerData = {
620
656
  ...discoveryData,
621
657
  ...peer,
622
658
  };
623
659
  const queueSlot = await queue?.obtainSlot();
624
660
  try {
625
- const result = await this.#pair(address, operationalAddress, peerData, {
661
+ const session = await this.#pair(address, operationalAddress, peerData, {
626
662
  caseAuthenticatedTags,
627
663
  });
628
- await this.#addOrUpdatePeer(address, operationalAddress, peerData);
629
- return result;
664
+ this.#addOrUpdatePeer(address, session, peerData);
665
+ return session;
630
666
  } finally {
631
667
  queueSlot?.close();
632
668
  }
@@ -668,17 +704,21 @@ export class PeerSet implements ImmutableSet<Peer>, ObservableSet<Peer> {
668
704
  }`,
669
705
  );
670
706
  const session = await this.#pair(address, operationalAddress, discoveryData, options);
671
- await this.#addOrUpdatePeer(address, operationalAddress);
707
+ this.#addOrUpdatePeer(address, session, discoveryData);
672
708
  return session;
673
709
  } catch (error) {
674
- if (error instanceof NoResponseTimeoutError || error instanceof ChannelStatusResponseError) {
710
+ if (
711
+ error instanceof NoResponseTimeoutError ||
712
+ error instanceof ChannelStatusResponseError ||
713
+ error instanceof AbortedError
714
+ ) {
675
715
  logger.debug(
676
716
  `Failed to resume connection to ${address} connection with ${ip}:${port}, discovering the node now:`,
677
717
  error.message ? error.message : error,
678
718
  );
679
- // We remove all sessions, this also informs the PairedNode class
719
+ // We remove all sessions that were created before we started the try, this also informs the PairedNode class
680
720
  await this.#sessions.handlePeerLoss(address, startTime);
681
- return undefined;
721
+ return this.#sessions.maybeSessionFor(address);
682
722
  } else {
683
723
  throw error;
684
724
  }
@@ -734,7 +774,13 @@ export class PeerSet implements ImmutableSet<Peer>, ObservableSet<Peer> {
734
774
  } catch (error) {
735
775
  NoResponseTimeoutError.accept(error);
736
776
 
737
- // Convert error
777
+ // It seems we got a new session while waiting for Case completion, so use this one
778
+ // TODO When case pairing can be aborted we can handle this cleanly
779
+ const currentSession = this.#sessions.maybeSessionFor(address);
780
+ if (currentSession !== undefined) {
781
+ return currentSession;
782
+ }
783
+
738
784
  throw new PairRetransmissionLimitReachedError(error.message);
739
785
  } finally {
740
786
  await unsecuredSession.initiateClose();
@@ -751,12 +797,8 @@ export class PeerSet implements ImmutableSet<Peer>, ObservableSet<Peer> {
751
797
  try {
752
798
  exchange = this.#exchanges.initiateExchangeForSession(paseSession, SECURE_CHANNEL_PROTOCOL_ID);
753
799
 
754
- const { session, resumed } = await this.#caseClient.pair(exchange, fabric, address.nodeId, options);
800
+ const { session } = await this.#caseClient.pair(exchange, fabric, address.nodeId, options);
755
801
 
756
- if (!resumed) {
757
- // When the session was not resumed then most likely the device firmware got updated, so we clear the cache
758
- this.#nodeCachedData.delete(address);
759
- }
760
802
  return session;
761
803
  } catch (error) {
762
804
  await exchange?.close();
@@ -805,22 +847,21 @@ export class PeerSet implements ImmutableSet<Peer>, ObservableSet<Peer> {
805
847
  discoveredAddresses.addresses.length = 0;
806
848
  }
807
849
 
808
- // Try to use first result for one last try before we need to reconnect
850
+ // Try to use the first result for one last try before we need to reconnect
809
851
  return discoveredAddresses?.addresses[0];
810
852
  }
811
853
 
812
- async #addOrUpdatePeer(
813
- address: PeerAddress,
814
- operationalServerAddress?: ServerAddressUdp,
815
- discoveryData?: DiscoveryData,
816
- ) {
854
+ #addOrUpdatePeer(address: PeerAddress, session?: SecureSession, discoveryData?: DiscoveryData) {
817
855
  let peer = this.get(address);
818
856
  if (peer === undefined) {
819
- peer = new Peer({ address, dataStore: await this.#store.createNodeStore(address) }, this.#peerContext);
857
+ peer = new Peer({ address }, this.#peerContext);
820
858
  this.#peers.add(peer);
821
859
  }
822
- if (operationalServerAddress !== undefined) {
823
- peer.descriptor.operationalAddress = operationalServerAddress;
860
+ if (session !== undefined && !session.isClosed) {
861
+ const channel = session.channel;
862
+ if (isIpNetworkChannel(channel)) {
863
+ peer.descriptor.operationalAddress = channel.networkAddress;
864
+ }
824
865
  }
825
866
  if (discoveryData !== undefined) {
826
867
  peer.descriptor.discoveryData = {
@@ -841,7 +882,13 @@ export class PeerSet implements ImmutableSet<Peer>, ObservableSet<Peer> {
841
882
  }
842
883
 
843
884
  addKnownPeer(address: PeerAddress, operationalServerAddress?: ServerAddressUdp, discoveryData?: DiscoveryData) {
844
- return this.#addOrUpdatePeer(address, operationalServerAddress, discoveryData);
885
+ this.#addOrUpdatePeer(address, undefined, discoveryData);
886
+ if (operationalServerAddress !== undefined) {
887
+ const peer = this.get(address);
888
+ if (peer !== undefined) {
889
+ peer.descriptor.operationalAddress = operationalServerAddress;
890
+ }
891
+ }
845
892
  }
846
893
 
847
894
  #getLastOperationalAddress(address: PeerAddress) {
@@ -27,7 +27,6 @@ import {
27
27
  UnexpectedDataError,
28
28
  } from "#general";
29
29
  import { PeerAddress } from "#peer/PeerAddress.js";
30
- import { DEFAULT_EXPECTED_PROCESSING_TIME } from "#protocol/MessageChannel.js";
31
30
  import { SecureChannelMessenger } from "#securechannel/SecureChannelMessenger.js";
32
31
  import { NodeSession } from "#session/NodeSession.js";
33
32
  import { Session } from "#session/Session.js";
@@ -36,6 +35,7 @@ import { UNICAST_UNSECURE_SESSION_ID } from "#session/UnsecuredSession.js";
36
35
  import { NodeId, SECURE_CHANNEL_PROTOCOL_ID, SecureMessageType } from "#types";
37
36
  import { MessageExchange, MessageExchangeContext } from "./MessageExchange.js";
38
37
  import { DuplicateMessageError } from "./MessageReceptionState.js";
38
+ import { MRP } from "./MRP.js";
39
39
  import { ProtocolHandler } from "./ProtocolHandler.js";
40
40
 
41
41
  const logger = Logger.get("ExchangeManager");
@@ -404,7 +404,10 @@ export class ExchangeManager {
404
404
  this.#workers.add(exchangeToClose.close());
405
405
  }
406
406
 
407
- calculateMaximumPeerResponseTimeMsFor(session: Session, expectedProcessingTime = DEFAULT_EXPECTED_PROCESSING_TIME) {
407
+ calculateMaximumPeerResponseTimeMsFor(
408
+ session: Session,
409
+ expectedProcessingTime = MRP.DEFAULT_EXPECTED_PROCESSING_TIME,
410
+ ) {
408
411
  return session.channel.calculateMaximumPeerResponseTime(
409
412
  session.parameters,
410
413
  this.#sessions.sessionParameters,
@@ -6,7 +6,6 @@
6
6
  import { Diagnostic, Duration, Observable, Timestamp } from "#general";
7
7
  import { PeerAddress } from "#peer/PeerAddress.js";
8
8
  import { ExchangeManager } from "#protocol/ExchangeManager.js";
9
- import { DEFAULT_EXPECTED_PROCESSING_TIME } from "#protocol/MessageChannel.js";
10
9
  import { MessageExchange } from "#protocol/MessageExchange.js";
11
10
  import { ProtocolHandler } from "#protocol/ProtocolHandler.js";
12
11
  import { SecureSession } from "#session/SecureSession.js";
@@ -14,6 +13,7 @@ import { Session } from "#session/Session.js";
14
13
  import { SessionManager } from "#session/SessionManager.js";
15
14
  import { INTERACTION_PROTOCOL_ID } from "#types";
16
15
  import { SessionClosedError } from "./errors.js";
16
+ import { MRP } from "./MRP.js";
17
17
 
18
18
  /**
19
19
  * Interface for obtaining an exchange with a specific peer.
@@ -69,7 +69,7 @@ export class DedicatedChannelExchangeProvider extends ExchangeProvider {
69
69
  return this.#session;
70
70
  }
71
71
 
72
- maximumPeerResponseTime(expectedProcessingTime = DEFAULT_EXPECTED_PROCESSING_TIME) {
72
+ maximumPeerResponseTime(expectedProcessingTime = MRP.DEFAULT_EXPECTED_PROCESSING_TIME) {
73
73
  return this.exchangeManager.calculateMaximumPeerResponseTimeMsFor(this.#session, expectedProcessingTime);
74
74
  }
75
75
  }
@@ -130,7 +130,7 @@ export class ReconnectableExchangeProvider extends ExchangeProvider {
130
130
  return this.sessions.sessionFor(this.#address);
131
131
  }
132
132
 
133
- maximumPeerResponseTime(expectedProcessingTimeMs = DEFAULT_EXPECTED_PROCESSING_TIME) {
133
+ maximumPeerResponseTime(expectedProcessingTimeMs = MRP.DEFAULT_EXPECTED_PROCESSING_TIME) {
134
134
  return this.exchangeManager.calculateMaximumPeerResponseTimeMsFor(
135
135
  this.sessions.sessionFor(this.#address),
136
136
  expectedProcessingTimeMs,
@@ -0,0 +1,146 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2026 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import { ChannelType, Duration, MatterFlowError, Millis, Seconds } from "#general";
8
+ import { SessionParameters } from "#session/SessionParameters.js";
9
+
10
+ export namespace MRP {
11
+ /**
12
+ * The maximum number of transmission attempts for a given reliable message. The sender MAY choose this value as it
13
+ * sees fit.
14
+ */
15
+ export const MAX_TRANSMISSIONS = 5;
16
+
17
+ /** The base number for the exponential backoff equation. */
18
+ export const BACKOFF_BASE = 1.6;
19
+
20
+ /** The scaler for random jitter in the backoff equation. */
21
+ export const BACKOFF_JITTER = 0.25;
22
+
23
+ /** The scaler margin increase to backoff over the peer sleepy interval. */
24
+ export const BACKOFF_MARGIN = 1.1;
25
+
26
+ /** The number of retransmissions before transitioning from linear to exponential backoff. */
27
+ export const BACKOFF_THRESHOLD = 1;
28
+
29
+ /** @see {@link MatterSpecification.v12.Core}, section 4.11.8 */
30
+ export const STANDALONE_ACK_TIMEOUT = Millis(200);
31
+
32
+ /**
33
+ * Default expected processing time for a messages in milliseconds. The value is derived from kExpectedIMProcessingTime
34
+ * from chip implementation. This is basically the default used with different names, also kExpectedLowProcessingTime or
35
+ * kExpectedSigma1ProcessingTime.
36
+ */
37
+ export const DEFAULT_EXPECTED_PROCESSING_TIME = Seconds(2);
38
+
39
+ /**
40
+ * The buffer time in milliseconds to add to the peer response time to also consider network delays and other factors.
41
+ * TODO: This is a pure guess and should be adjusted in the future.
42
+ */
43
+ const PEER_RESPONSE_TIME_BUFFER = Seconds(5);
44
+
45
+ export interface ResponseTimeInputs {
46
+ peerSessionParameters: SessionParameters;
47
+ localSessionParameters: SessionParameters;
48
+ channelType: ChannelType;
49
+ isPeerActive: boolean;
50
+ usesMrp?: boolean;
51
+ expectedProcessingTime?: Duration;
52
+ }
53
+
54
+ export function maxPeerResponseTimeOf({
55
+ peerSessionParameters,
56
+ localSessionParameters,
57
+ channelType,
58
+ isPeerActive,
59
+ usesMrp = channelType === ChannelType.UDP,
60
+ expectedProcessingTime = DEFAULT_EXPECTED_PROCESSING_TIME,
61
+ }: ResponseTimeInputs): Duration {
62
+ switch (channelType) {
63
+ case "tcp":
64
+ // TCP uses 30s timeout according to chip sdk implementation, so do the same
65
+ return Millis(Seconds(30) + PEER_RESPONSE_TIME_BUFFER);
66
+
67
+ case "udp":
68
+ // UDP normally uses MRP, if not we have Group communication, which normally have no responses
69
+ if (!usesMrp) {
70
+ throw new MatterFlowError("No response expected for this message exchange because UDP and no MRP");
71
+ }
72
+ // Calculate the maximum time till the peer got our last retry and worst case for the way back
73
+ return Millis(
74
+ maxResponseTimeOf(peerSessionParameters, isPeerActive) +
75
+ maxResponseTimeOf(localSessionParameters, isPeerActive) +
76
+ expectedProcessingTime +
77
+ PEER_RESPONSE_TIME_BUFFER,
78
+ );
79
+
80
+ case "ble":
81
+ // chip sdk uses BTP_ACK_TIMEOUT_MS which is wrong in my eyes, so we use static 30s as like TCP here
82
+ return Millis(Seconds(30) + PEER_RESPONSE_TIME_BUFFER);
83
+
84
+ default:
85
+ throw new MatterFlowError(
86
+ `Can not calculate expected timeout for unknown channel type: ${channelType}`,
87
+ );
88
+ }
89
+ }
90
+
91
+ export interface RetryDelayInputs {
92
+ transmissionNumber: number;
93
+ sessionParameters: SessionParameters;
94
+ isPeerActive: boolean;
95
+ }
96
+
97
+ /**
98
+ * Calculates the backoff time for a resubmission based on the current retransmission count.
99
+ * If no session parameters are provided, the parameters of the current session are used.
100
+ * If session parameters are provided, the method can be used to calculate the maximum backoff time for the other
101
+ * side of the exchange.
102
+ *
103
+ * @see {@link MatterSpecification.v10.Core}, section 4.11.2.1
104
+ */
105
+ export function maxRetransmissionIntervalOf({
106
+ transmissionNumber,
107
+ sessionParameters,
108
+ isPeerActive,
109
+ }: RetryDelayInputs) {
110
+ const { activeInterval, idleInterval } = sessionParameters;
111
+
112
+ // For the first message of a new exchange ... SHALL be set according to the idle state of the peer node.
113
+ // For all subsequent messages of the exchange, ... SHOULD be set according to the active state of the peer node
114
+ const peerActive = transmissionNumber > 0 && (sessionParameters !== undefined || isPeerActive);
115
+ const baseInterval = peerActive ? activeInterval : idleInterval;
116
+ return Millis.floor(
117
+ Millis(
118
+ baseInterval *
119
+ MRP.BACKOFF_MARGIN *
120
+ Math.pow(MRP.BACKOFF_BASE, Math.max(0, transmissionNumber - MRP.BACKOFF_THRESHOLD)) *
121
+ (1 + (sessionParameters !== undefined ? 1 : Math.random()) * MRP.BACKOFF_JITTER),
122
+ ),
123
+ );
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Calculates the maximum time the peer might take to respond when using MRP for one direction.
129
+ */
130
+ function maxResponseTimeOf(sessionParameters: SessionParameters, isPeerActive: boolean) {
131
+ let finalWaitTime = 0;
132
+
133
+ // and then add the time the other side needs for a full resubmission cycle under the assumption we are active
134
+ for (let i = 0; i < MRP.MAX_TRANSMISSIONS; i++) {
135
+ finalWaitTime = Millis(
136
+ finalWaitTime +
137
+ MRP.maxRetransmissionIntervalOf({
138
+ transmissionNumber: i,
139
+ sessionParameters,
140
+ isPeerActive,
141
+ }),
142
+ );
143
+ }
144
+
145
+ return finalWaitTime;
146
+ }