@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.
- package/dist/cjs/dcl/DclCertificateService.d.ts.map +1 -1
- package/dist/cjs/dcl/DclCertificateService.js +3 -0
- package/dist/cjs/dcl/DclCertificateService.js.map +1 -1
- package/dist/cjs/dcl/DclOtaUpdateService.d.ts.map +1 -1
- package/dist/cjs/dcl/DclOtaUpdateService.js +6 -4
- package/dist/cjs/dcl/DclOtaUpdateService.js.map +1 -1
- package/dist/cjs/mdns/MdnsClient.d.ts.map +1 -1
- package/dist/cjs/mdns/MdnsClient.js +6 -2
- package/dist/cjs/mdns/MdnsClient.js.map +1 -1
- package/dist/cjs/peer/Peer.d.ts +2 -1
- package/dist/cjs/peer/Peer.d.ts.map +1 -1
- package/dist/cjs/peer/Peer.js +20 -3
- package/dist/cjs/peer/Peer.js.map +1 -1
- package/dist/cjs/peer/PeerAddressStore.d.ts +1 -11
- package/dist/cjs/peer/PeerAddressStore.d.ts.map +1 -1
- package/dist/cjs/peer/PeerAddressStore.js +1 -4
- package/dist/cjs/peer/PeerAddressStore.js.map +1 -1
- package/dist/cjs/peer/PeerDescriptor.d.ts +1 -9
- package/dist/cjs/peer/PeerDescriptor.d.ts.map +1 -1
- package/dist/cjs/peer/PeerDescriptor.js +1 -6
- package/dist/cjs/peer/PeerDescriptor.js.map +1 -1
- package/dist/cjs/peer/PeerSet.d.ts +1 -1
- package/dist/cjs/peer/PeerSet.d.ts.map +1 -1
- package/dist/cjs/peer/PeerSet.js +55 -22
- package/dist/cjs/peer/PeerSet.js.map +2 -2
- package/dist/cjs/protocol/ExchangeManager.d.ts.map +1 -1
- package/dist/cjs/protocol/ExchangeManager.js +2 -2
- package/dist/cjs/protocol/ExchangeManager.js.map +1 -1
- package/dist/cjs/protocol/ExchangeProvider.d.ts.map +1 -1
- package/dist/cjs/protocol/ExchangeProvider.js +3 -3
- package/dist/cjs/protocol/ExchangeProvider.js.map +1 -1
- package/dist/cjs/protocol/MRP.d.ts +54 -0
- package/dist/cjs/protocol/MRP.d.ts.map +1 -0
- package/dist/cjs/protocol/MRP.js +96 -0
- package/dist/cjs/protocol/MRP.js.map +6 -0
- package/dist/cjs/protocol/MessageChannel.d.ts +0 -23
- package/dist/cjs/protocol/MessageChannel.d.ts.map +1 -1
- package/dist/cjs/protocol/MessageChannel.js +15 -47
- package/dist/cjs/protocol/MessageChannel.js.map +2 -2
- package/dist/cjs/protocol/MessageExchange.d.ts.map +1 -1
- package/dist/cjs/protocol/MessageExchange.js +7 -7
- package/dist/cjs/protocol/MessageExchange.js.map +1 -1
- package/dist/cjs/protocol/index.d.ts +1 -0
- package/dist/cjs/protocol/index.d.ts.map +1 -1
- package/dist/cjs/protocol/index.js +1 -0
- package/dist/cjs/protocol/index.js.map +1 -1
- package/dist/cjs/session/NodeSession.js +2 -2
- package/dist/cjs/session/NodeSession.js.map +1 -1
- package/dist/cjs/session/Session.d.ts +1 -0
- package/dist/cjs/session/Session.d.ts.map +1 -1
- package/dist/cjs/session/case/CaseClient.d.ts.map +1 -1
- package/dist/cjs/session/case/CaseClient.js +1 -1
- package/dist/cjs/session/case/CaseClient.js.map +1 -1
- package/dist/cjs/session/case/CaseServer.d.ts.map +1 -1
- package/dist/cjs/session/case/CaseServer.js +4 -1
- package/dist/cjs/session/case/CaseServer.js.map +1 -1
- package/dist/esm/dcl/DclCertificateService.d.ts.map +1 -1
- package/dist/esm/dcl/DclCertificateService.js +3 -0
- package/dist/esm/dcl/DclCertificateService.js.map +1 -1
- package/dist/esm/dcl/DclOtaUpdateService.d.ts.map +1 -1
- package/dist/esm/dcl/DclOtaUpdateService.js +6 -4
- package/dist/esm/dcl/DclOtaUpdateService.js.map +1 -1
- package/dist/esm/mdns/MdnsClient.d.ts.map +1 -1
- package/dist/esm/mdns/MdnsClient.js +6 -2
- package/dist/esm/mdns/MdnsClient.js.map +1 -1
- package/dist/esm/peer/Peer.d.ts +2 -1
- package/dist/esm/peer/Peer.d.ts.map +1 -1
- package/dist/esm/peer/Peer.js +20 -3
- package/dist/esm/peer/Peer.js.map +1 -1
- package/dist/esm/peer/PeerAddressStore.d.ts +1 -11
- package/dist/esm/peer/PeerAddressStore.d.ts.map +1 -1
- package/dist/esm/peer/PeerAddressStore.js +1 -4
- package/dist/esm/peer/PeerAddressStore.js.map +1 -1
- package/dist/esm/peer/PeerDescriptor.d.ts +1 -9
- package/dist/esm/peer/PeerDescriptor.d.ts.map +1 -1
- package/dist/esm/peer/PeerDescriptor.js +1 -6
- package/dist/esm/peer/PeerDescriptor.js.map +1 -1
- package/dist/esm/peer/PeerSet.d.ts +1 -1
- package/dist/esm/peer/PeerSet.d.ts.map +1 -1
- package/dist/esm/peer/PeerSet.js +60 -24
- package/dist/esm/peer/PeerSet.js.map +2 -2
- package/dist/esm/protocol/ExchangeManager.d.ts.map +1 -1
- package/dist/esm/protocol/ExchangeManager.js +2 -2
- package/dist/esm/protocol/ExchangeManager.js.map +1 -1
- package/dist/esm/protocol/ExchangeProvider.d.ts.map +1 -1
- package/dist/esm/protocol/ExchangeProvider.js +3 -3
- package/dist/esm/protocol/ExchangeProvider.js.map +1 -1
- package/dist/esm/protocol/MRP.d.ts +54 -0
- package/dist/esm/protocol/MRP.d.ts.map +1 -0
- package/dist/esm/protocol/MRP.js +76 -0
- package/dist/esm/protocol/MRP.js.map +6 -0
- package/dist/esm/protocol/MessageChannel.d.ts +0 -23
- package/dist/esm/protocol/MessageChannel.d.ts.map +1 -1
- package/dist/esm/protocol/MessageChannel.js +16 -54
- package/dist/esm/protocol/MessageChannel.js.map +2 -2
- package/dist/esm/protocol/MessageExchange.d.ts.map +1 -1
- package/dist/esm/protocol/MessageExchange.js +3 -3
- package/dist/esm/protocol/MessageExchange.js.map +1 -1
- package/dist/esm/protocol/index.d.ts +1 -0
- package/dist/esm/protocol/index.d.ts.map +1 -1
- package/dist/esm/protocol/index.js +1 -0
- package/dist/esm/protocol/index.js.map +1 -1
- package/dist/esm/session/NodeSession.js +2 -2
- package/dist/esm/session/NodeSession.js.map +1 -1
- package/dist/esm/session/Session.d.ts +1 -0
- package/dist/esm/session/Session.d.ts.map +1 -1
- package/dist/esm/session/case/CaseClient.d.ts.map +1 -1
- package/dist/esm/session/case/CaseClient.js +2 -2
- package/dist/esm/session/case/CaseClient.js.map +1 -1
- package/dist/esm/session/case/CaseServer.d.ts.map +1 -1
- package/dist/esm/session/case/CaseServer.js +4 -1
- package/dist/esm/session/case/CaseServer.js.map +1 -1
- package/package.json +6 -6
- package/src/dcl/DclCertificateService.ts +3 -0
- package/src/dcl/DclOtaUpdateService.ts +11 -5
- package/src/mdns/MdnsClient.ts +9 -2
- package/src/peer/Peer.ts +28 -5
- package/src/peer/PeerAddressStore.ts +1 -19
- package/src/peer/PeerDescriptor.ts +1 -15
- package/src/peer/PeerSet.ts +82 -35
- package/src/protocol/ExchangeManager.ts +5 -2
- package/src/protocol/ExchangeProvider.ts +3 -3
- package/src/protocol/MRP.ts +146 -0
- package/src/protocol/MessageChannel.ts +16 -101
- package/src/protocol/MessageExchange.ts +4 -3
- package/src/protocol/index.ts +1 -0
- package/src/session/NodeSession.ts +3 -3
- package/src/session/Session.ts +1 -0
- package/src/session/case/CaseClient.ts +8 -2
- 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 {
|
|
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
|
|
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
|
}
|
package/src/peer/PeerSet.ts
CHANGED
|
@@ -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
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
|
661
|
+
const session = await this.#pair(address, operationalAddress, peerData, {
|
|
626
662
|
caseAuthenticatedTags,
|
|
627
663
|
});
|
|
628
|
-
|
|
629
|
-
return
|
|
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
|
-
|
|
707
|
+
this.#addOrUpdatePeer(address, session, discoveryData);
|
|
672
708
|
return session;
|
|
673
709
|
} catch (error) {
|
|
674
|
-
if (
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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
|
-
|
|
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
|
|
857
|
+
peer = new Peer({ address }, this.#peerContext);
|
|
820
858
|
this.#peers.add(peer);
|
|
821
859
|
}
|
|
822
|
-
if (
|
|
823
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
+
}
|