@matter/protocol 0.16.0-alpha.0-20251004-92135c7df → 0.16.0-alpha.0-20251011-d8942d7d5

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 (131) hide show
  1. package/dist/cjs/action/client/ClientInteraction.d.ts +1 -0
  2. package/dist/cjs/action/client/ClientInteraction.d.ts.map +1 -1
  3. package/dist/cjs/action/client/ClientInteraction.js +6 -2
  4. package/dist/cjs/action/client/ClientInteraction.js.map +1 -1
  5. package/dist/cjs/action/protocols.d.ts +10 -11
  6. package/dist/cjs/action/protocols.d.ts.map +1 -1
  7. package/dist/cjs/action/request/Invoke.d.ts.map +1 -1
  8. package/dist/cjs/action/request/Invoke.js +3 -0
  9. package/dist/cjs/action/request/Invoke.js.map +1 -1
  10. package/dist/cjs/action/request/Read.d.ts.map +1 -1
  11. package/dist/cjs/action/request/Read.js +3 -0
  12. package/dist/cjs/action/request/Read.js.map +1 -1
  13. package/dist/cjs/action/request/Write.d.ts.map +1 -1
  14. package/dist/cjs/action/request/Write.js +3 -0
  15. package/dist/cjs/action/request/Write.js.map +1 -1
  16. package/dist/cjs/action/server/AttributeSubscriptionResponse.d.ts +14 -13
  17. package/dist/cjs/action/server/AttributeSubscriptionResponse.d.ts.map +1 -1
  18. package/dist/cjs/action/server/AttributeSubscriptionResponse.js +18 -18
  19. package/dist/cjs/action/server/AttributeSubscriptionResponse.js.map +1 -1
  20. package/dist/cjs/action/server/ServerInteraction.d.ts +1 -0
  21. package/dist/cjs/action/server/ServerInteraction.d.ts.map +1 -1
  22. package/dist/cjs/action/server/ServerInteraction.js +3 -0
  23. package/dist/cjs/action/server/ServerInteraction.js.map +1 -1
  24. package/dist/cjs/fabric/Fabric.d.ts +2 -4
  25. package/dist/cjs/fabric/Fabric.d.ts.map +1 -1
  26. package/dist/cjs/fabric/Fabric.js.map +1 -1
  27. package/dist/cjs/fabric/FabricAuthority.d.ts +0 -1
  28. package/dist/cjs/fabric/FabricAuthority.d.ts.map +1 -1
  29. package/dist/cjs/fabric/FabricAuthority.js +2 -3
  30. package/dist/cjs/fabric/FabricAuthority.js.map +1 -1
  31. package/dist/cjs/fabric/TestFabric.d.ts.map +1 -1
  32. package/dist/cjs/fabric/TestFabric.js +2 -1
  33. package/dist/cjs/fabric/TestFabric.js.map +1 -1
  34. package/dist/cjs/interaction/InteractionClient.js +1 -1
  35. package/dist/cjs/interaction/InteractionClient.js.map +1 -1
  36. package/dist/cjs/interaction/InteractionMessenger.js +1 -1
  37. package/dist/cjs/interaction/InteractionMessenger.js.map +1 -1
  38. package/dist/cjs/mdns/MdnsService.d.ts +1 -1
  39. package/dist/cjs/mdns/MdnsService.d.ts.map +1 -1
  40. package/dist/cjs/mdns/MdnsService.js +1 -1
  41. package/dist/cjs/mdns/MdnsService.js.map +1 -1
  42. package/dist/cjs/peer/ControllerCommissioner.d.ts.map +1 -1
  43. package/dist/cjs/peer/ControllerCommissioner.js +26 -21
  44. package/dist/cjs/peer/ControllerCommissioner.js.map +1 -1
  45. package/dist/cjs/peer/PeerSet.d.ts +5 -6
  46. package/dist/cjs/peer/PeerSet.d.ts.map +1 -1
  47. package/dist/cjs/peer/PeerSet.js +19 -6
  48. package/dist/cjs/peer/PeerSet.js.map +1 -1
  49. package/dist/cjs/protocol/ExchangeManager.js +2 -2
  50. package/dist/cjs/protocol/ExchangeManager.js.map +1 -1
  51. package/dist/cjs/protocol/MessageExchange.d.ts.map +1 -1
  52. package/dist/cjs/protocol/MessageExchange.js +3 -2
  53. package/dist/cjs/protocol/MessageExchange.js.map +1 -1
  54. package/dist/cjs/session/SessionManager.d.ts.map +1 -1
  55. package/dist/cjs/session/SessionManager.js +17 -9
  56. package/dist/cjs/session/SessionManager.js.map +1 -1
  57. package/dist/esm/action/client/ClientInteraction.d.ts +1 -0
  58. package/dist/esm/action/client/ClientInteraction.d.ts.map +1 -1
  59. package/dist/esm/action/client/ClientInteraction.js +6 -2
  60. package/dist/esm/action/client/ClientInteraction.js.map +1 -1
  61. package/dist/esm/action/protocols.d.ts +10 -11
  62. package/dist/esm/action/protocols.d.ts.map +1 -1
  63. package/dist/esm/action/request/Invoke.d.ts.map +1 -1
  64. package/dist/esm/action/request/Invoke.js +3 -0
  65. package/dist/esm/action/request/Invoke.js.map +1 -1
  66. package/dist/esm/action/request/Read.d.ts.map +1 -1
  67. package/dist/esm/action/request/Read.js +3 -0
  68. package/dist/esm/action/request/Read.js.map +1 -1
  69. package/dist/esm/action/request/Write.d.ts.map +1 -1
  70. package/dist/esm/action/request/Write.js +3 -0
  71. package/dist/esm/action/request/Write.js.map +1 -1
  72. package/dist/esm/action/server/AttributeSubscriptionResponse.d.ts +14 -13
  73. package/dist/esm/action/server/AttributeSubscriptionResponse.d.ts.map +1 -1
  74. package/dist/esm/action/server/AttributeSubscriptionResponse.js +18 -18
  75. package/dist/esm/action/server/AttributeSubscriptionResponse.js.map +1 -1
  76. package/dist/esm/action/server/ServerInteraction.d.ts +1 -0
  77. package/dist/esm/action/server/ServerInteraction.d.ts.map +1 -1
  78. package/dist/esm/action/server/ServerInteraction.js +3 -0
  79. package/dist/esm/action/server/ServerInteraction.js.map +1 -1
  80. package/dist/esm/fabric/Fabric.d.ts +2 -4
  81. package/dist/esm/fabric/Fabric.d.ts.map +1 -1
  82. package/dist/esm/fabric/Fabric.js.map +1 -1
  83. package/dist/esm/fabric/FabricAuthority.d.ts +0 -1
  84. package/dist/esm/fabric/FabricAuthority.d.ts.map +1 -1
  85. package/dist/esm/fabric/FabricAuthority.js +2 -3
  86. package/dist/esm/fabric/FabricAuthority.js.map +1 -1
  87. package/dist/esm/fabric/TestFabric.d.ts.map +1 -1
  88. package/dist/esm/fabric/TestFabric.js +3 -2
  89. package/dist/esm/fabric/TestFabric.js.map +1 -1
  90. package/dist/esm/interaction/InteractionClient.js +1 -1
  91. package/dist/esm/interaction/InteractionClient.js.map +1 -1
  92. package/dist/esm/interaction/InteractionMessenger.js +1 -1
  93. package/dist/esm/interaction/InteractionMessenger.js.map +1 -1
  94. package/dist/esm/mdns/MdnsService.d.ts +1 -1
  95. package/dist/esm/mdns/MdnsService.d.ts.map +1 -1
  96. package/dist/esm/mdns/MdnsService.js +1 -1
  97. package/dist/esm/mdns/MdnsService.js.map +1 -1
  98. package/dist/esm/peer/ControllerCommissioner.d.ts.map +1 -1
  99. package/dist/esm/peer/ControllerCommissioner.js +26 -21
  100. package/dist/esm/peer/ControllerCommissioner.js.map +1 -1
  101. package/dist/esm/peer/PeerSet.d.ts +5 -6
  102. package/dist/esm/peer/PeerSet.d.ts.map +1 -1
  103. package/dist/esm/peer/PeerSet.js +19 -6
  104. package/dist/esm/peer/PeerSet.js.map +1 -1
  105. package/dist/esm/protocol/ExchangeManager.js +2 -2
  106. package/dist/esm/protocol/ExchangeManager.js.map +1 -1
  107. package/dist/esm/protocol/MessageExchange.d.ts.map +1 -1
  108. package/dist/esm/protocol/MessageExchange.js +3 -2
  109. package/dist/esm/protocol/MessageExchange.js.map +1 -1
  110. package/dist/esm/session/SessionManager.d.ts.map +1 -1
  111. package/dist/esm/session/SessionManager.js +19 -10
  112. package/dist/esm/session/SessionManager.js.map +1 -1
  113. package/package.json +6 -6
  114. package/src/action/client/ClientInteraction.ts +6 -2
  115. package/src/action/protocols.ts +11 -10
  116. package/src/action/request/Invoke.ts +4 -0
  117. package/src/action/request/Read.ts +4 -0
  118. package/src/action/request/Write.ts +4 -0
  119. package/src/action/server/AttributeSubscriptionResponse.ts +30 -27
  120. package/src/action/server/ServerInteraction.ts +5 -0
  121. package/src/fabric/Fabric.ts +1 -1
  122. package/src/fabric/FabricAuthority.ts +2 -2
  123. package/src/fabric/TestFabric.ts +2 -1
  124. package/src/interaction/InteractionClient.ts +1 -1
  125. package/src/interaction/InteractionMessenger.ts +1 -1
  126. package/src/mdns/MdnsService.ts +1 -1
  127. package/src/peer/ControllerCommissioner.ts +31 -23
  128. package/src/peer/PeerSet.ts +23 -7
  129. package/src/protocol/ExchangeManager.ts +2 -2
  130. package/src/protocol/MessageExchange.ts +3 -2
  131. package/src/session/SessionManager.ts +35 -24
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matter/protocol",
3
- "version": "0.16.0-alpha.0-20251004-92135c7df",
3
+ "version": "0.16.0-alpha.0-20251011-d8942d7d5",
4
4
  "description": "Low-level APIs for Matter interaction",
5
5
  "keywords": [
6
6
  "iot",
@@ -40,13 +40,13 @@
40
40
  "#*": "./src/*"
41
41
  },
42
42
  "dependencies": {
43
- "@matter/general": "0.16.0-alpha.0-20251004-92135c7df",
44
- "@matter/model": "0.16.0-alpha.0-20251004-92135c7df",
45
- "@matter/types": "0.16.0-alpha.0-20251004-92135c7df"
43
+ "@matter/general": "0.16.0-alpha.0-20251011-d8942d7d5",
44
+ "@matter/model": "0.16.0-alpha.0-20251011-d8942d7d5",
45
+ "@matter/types": "0.16.0-alpha.0-20251011-d8942d7d5"
46
46
  },
47
47
  "devDependencies": {
48
- "@matter/tools": "0.16.0-alpha.0-20251004-92135c7df",
49
- "@matter/testing": "0.16.0-alpha.0-20251004-92135c7df"
48
+ "@matter/tools": "0.16.0-alpha.0-20251011-d8942d7d5",
49
+ "@matter/testing": "0.16.0-alpha.0-20251011-d8942d7d5"
50
50
  },
51
51
  "files": [
52
52
  "dist/**/*",
@@ -194,9 +194,9 @@ export class ClientInteraction<SessionT extends InteractionSession = Interaction
194
194
  messenger = await InteractionClientMessenger.create(this.#exchanges);
195
195
 
196
196
  await messenger.sendSubscribeRequest({
197
- ...request,
198
197
  minIntervalFloorSeconds: Seconds.of(DEFAULT_MIN_INTERVAL_FLOOR),
199
- maxIntervalCeilingSeconds: Seconds.of(DEFAULT_MIN_INTERVAL_FLOOR),
198
+ maxIntervalCeilingSeconds: Seconds.of(DEFAULT_MIN_INTERVAL_FLOOR), // TODO use better max fallback
199
+ ...request,
200
200
  });
201
201
 
202
202
  await this.#handleSubscriptionResponse(request, readChunks(messenger));
@@ -211,6 +211,10 @@ export class ClientInteraction<SessionT extends InteractionSession = Interaction
211
211
  }
212
212
  }
213
213
 
214
+ cancelSubscription(id: number) {
215
+ this.#subscriptions.get(id)?.close();
216
+ }
217
+
214
218
  async #handleSubscriptionResponse(request: Subscribe, result: ReadResult) {
215
219
  if (request.updated) {
216
220
  await request.updated(result);
@@ -64,12 +64,9 @@ export interface NodeProtocol extends CollectionProtocol<EndpointProtocol> {
64
64
  eventHandler: OccurrenceManager;
65
65
 
66
66
  /**
67
- * Event when data on the node changes.
67
+ * Emitted when attributes change.
68
68
  */
69
- stateChanged: Observable<
70
- [endpointId: EndpointNumber, clusterId: ClusterId, changes: AttributeId[], version: number],
71
- MaybePromise
72
- >;
69
+ attrsChanged: Observable<NodeProtocol.AttrChange, MaybePromise>;
73
70
 
74
71
  /**
75
72
  * Inspects an Attribute- or Event path and log in human-readable form if possible
@@ -77,6 +74,15 @@ export interface NodeProtocol extends CollectionProtocol<EndpointProtocol> {
77
74
  inspectPath(path: AttributePath | EventPath | CommandPath): string;
78
75
  }
79
76
 
77
+ export namespace NodeProtocol {
78
+ export type AttrChange = [
79
+ endpointId: EndpointNumber,
80
+ clusterId: ClusterId,
81
+ changes: AttributeId[],
82
+ version: number,
83
+ ];
84
+ }
85
+
80
86
  /**
81
87
  * Protocol contract for a specific endpoint.
82
88
  */
@@ -115,11 +121,6 @@ export interface ClusterProtocol {
115
121
  */
116
122
  location: AccessControl.Location;
117
123
 
118
- /**
119
- * The cluster datasource state change event
120
- */
121
- stateChanged: Observable<[changes: AttributeId[], version: number], MaybePromise>;
122
-
123
124
  /**
124
125
  * Read-only state of the cluster
125
126
  */
@@ -30,6 +30,10 @@ export function Invoke(options: Invoke.Definition, ...commands: CommandData[]):
30
30
  export function Invoke(...commands: CommandData[]): Invoke;
31
31
 
32
32
  export function Invoke(optionsOrData: Invoke.Definition | CommandData, ...commands: CommandData[]): Invoke {
33
+ if (optionsOrData === undefined) {
34
+ throw new MalformedRequestError(`Invocation requires at least one command`);
35
+ }
36
+
33
37
  let options;
34
38
  if ("commands" in optionsOrData) {
35
39
  options = optionsOrData;
@@ -34,6 +34,10 @@ export function Read(options: Read.Options, ...selectors: Read.Selector[]): Read
34
34
  export function Read(...selectors: Read.Selector[]): Read;
35
35
 
36
36
  export function Read(optionsOrSelector: Read.Options | Read.Selector, ...selectors: Read.Selector[]): Read {
37
+ if (optionsOrSelector === undefined) {
38
+ throw new MalformedRequestError(`Read action designates no attributes or events`);
39
+ }
40
+
37
41
  let options;
38
42
  if ("kind" in optionsOrSelector) {
39
43
  selectors = [optionsOrSelector, ...selectors];
@@ -30,6 +30,10 @@ export function Write(options: Write.Options, ...data: Write.Attribute[]): Write
30
30
  export function Write(...data: Write.Attribute[]): Write;
31
31
 
32
32
  export function Write(optionsOrData: Write.Options | Write.Attribute, ...data: Write.Attribute[]): Write {
33
+ if (optionsOrData === undefined) {
34
+ throw new MalformedRequestError(`Write action must have options or data`);
35
+ }
36
+
33
37
  let options;
34
38
  if ("kind" in optionsOrData) {
35
39
  data = [optionsOrData, ...data];
@@ -11,75 +11,78 @@ import { InternalError } from "#general";
11
11
  import { AttributeId, AttributePath, ClusterId, EndpointNumber } from "#types";
12
12
  import { AttributeReadResponse } from "./AttributeReadResponse.js";
13
13
 
14
- type ClusterFilter = {
15
- [clusterId: ClusterId]: Set<AttributeId>;
16
- };
17
- export type AttributeResponseFilter = {
18
- [endpointId: EndpointNumber]: ClusterFilter;
19
- };
14
+ export namespace DirtyState {
15
+ export type ForCluster = {
16
+ [clusterId: ClusterId]: Set<AttributeId>;
17
+ };
18
+
19
+ export type ForNode = {
20
+ [endpointId: EndpointNumber]: ForCluster;
21
+ };
22
+ }
20
23
 
21
24
  /**
22
- * AttributeSubscriptionResponse is a specialized version of AttributeReadResponse that processes a read/subscribe request
23
- * with a filter applied to the attributes. Only the attributes that match the filter will be processed.
25
+ * A specialization of {@link AttributeReadResponse} that processes a read/subscribe request with a filter applied to
26
+ * the attributes. Only processes attributes that match the filter.
24
27
  */
25
28
  export class AttributeSubscriptionResponse<
26
29
  SessionT extends InteractionSession = InteractionSession,
27
30
  > extends AttributeReadResponse<SessionT> {
28
- #filter: AttributeResponseFilter;
29
- #currentEndpointFilter?: ClusterFilter;
30
- #currentClusterFilter?: Set<number>;
31
+ #dirty: DirtyState.ForNode;
32
+ #currentEndpointDirty?: DirtyState.ForCluster;
33
+ #currentClusterDirty?: Set<number>;
31
34
 
32
- constructor(node: NodeProtocol, session: SessionT, filter: AttributeResponseFilter) {
35
+ constructor(node: NodeProtocol, session: SessionT, filter: DirtyState.ForNode) {
33
36
  super(node, session);
34
- this.#filter = filter;
37
+ this.#dirty = filter;
35
38
  }
36
39
 
37
- get filter() {
38
- return this.#filter;
40
+ get dirty() {
41
+ return this.#dirty;
39
42
  }
40
43
 
41
44
  /** Guarded accessor for this.#currentEndpointFilter. This should never be undefined */
42
- protected get currentEndpointFilter() {
43
- if (!this.#currentEndpointFilter) {
45
+ protected get currentEndpointDirty() {
46
+ if (!this.#currentEndpointDirty) {
44
47
  throw new InternalError("currentEndpointFilter is not set. Should never happen");
45
48
  }
46
- return this.#currentEndpointFilter;
49
+ return this.#currentEndpointDirty;
47
50
  }
48
51
 
49
52
  /** Guarded accessor for this.#currentCLusterFilter. This should never be undefined */
50
- protected get currentClusterFilter() {
51
- if (!this.#currentClusterFilter) {
53
+ protected get currentClusterDirty() {
54
+ if (!this.#currentClusterDirty) {
52
55
  throw new InternalError("currentClusterFilter is not set. Should never happen");
53
56
  }
54
- return this.#currentClusterFilter;
57
+ return this.#currentClusterDirty;
55
58
  }
56
59
 
57
60
  protected override addConcrete(path: ReadResult.ConcreteAttributePath) {
58
61
  const { endpointId, clusterId, attributeId } = path;
59
- if (this.#filter[endpointId]?.[clusterId]?.has(attributeId) === undefined) {
62
+ if (this.#dirty[endpointId]?.[clusterId]?.has(attributeId) === undefined) {
60
63
  return;
61
64
  }
62
65
  super.addConcrete(path);
63
66
  }
64
67
 
65
68
  protected override *readEndpointForWildcard(endpoint: EndpointProtocol, path: AttributePath) {
66
- this.#currentEndpointFilter = this.#filter[endpoint.id];
67
- if (this.#currentEndpointFilter === undefined) {
69
+ this.#currentEndpointDirty = this.#dirty[endpoint.id];
70
+ if (this.#currentEndpointDirty === undefined) {
68
71
  return;
69
72
  }
70
73
  yield* super.readEndpointForWildcard(endpoint, path);
71
74
  }
72
75
 
73
76
  protected override readClusterForWildcard(cluster: ClusterProtocol, path: AttributePath) {
74
- this.#currentClusterFilter = this.currentEndpointFilter[cluster.type.id];
75
- if (this.#currentClusterFilter === undefined) {
77
+ this.#currentClusterDirty = this.currentEndpointDirty[cluster.type.id];
78
+ if (this.#currentClusterDirty === undefined) {
76
79
  return;
77
80
  }
78
81
  super.readClusterForWildcard(cluster, path);
79
82
  }
80
83
 
81
84
  protected override readAttributeForWildcard(attribute: AttributeTypeProtocol, path: AttributePath) {
82
- if (!this.currentClusterFilter.has(attribute.id)) {
85
+ if (!this.currentClusterDirty.has(attribute.id)) {
83
86
  return;
84
87
  }
85
88
  super.readAttributeForWildcard(attribute, path);
@@ -65,6 +65,11 @@ export class ServerInteraction<SessionT extends InteractionSession = Interaction
65
65
  throw new NotImplementedError();
66
66
  }
67
67
 
68
+ cancelSubscription(_id: number): void {
69
+ // TODO
70
+ throw new NotImplementedError();
71
+ }
72
+
68
73
  write<T extends Write>(request: T, session: SessionT): WriteResult<T> {
69
74
  // TODO - validate request
70
75
 
@@ -259,7 +259,7 @@ export class Fabric {
259
259
  };
260
260
  }
261
261
 
262
- addressOf(nodeId: NodeId) {
262
+ addressOf(nodeId: NodeId): PeerAddress {
263
263
  return PeerAddress({ fabricIndex: this.fabricIndex, nodeId });
264
264
  }
265
265
 
@@ -51,7 +51,6 @@ export interface FabricAuthorityContext {
51
51
  }
52
52
 
53
53
  export const DEFAULT_ADMIN_VENDOR_ID = VendorId(0xfff1);
54
- export const DEFAULT_FABRIC_ID = FabricId(1);
55
54
 
56
55
  /**
57
56
  * Manages fabrics controlled locally associated with a specific CA.
@@ -144,10 +143,11 @@ export class FabricAuthority {
144
143
  .setRootVendorId(this.#config.adminVendorId ?? DEFAULT_ADMIN_VENDOR_ID)
145
144
  .setLabel(this.#config.adminFabricLabel);
146
145
 
146
+ const fabricId = this.#config.fabricId ?? FabricId(this.#fabrics.crypto.randomBigInt(8));
147
147
  await fabricBuilder.setOperationalCert(
148
148
  await this.#ca.generateNoc(
149
149
  fabricBuilder.publicKey,
150
- this.#config.fabricId ?? DEFAULT_FABRIC_ID,
150
+ fabricId,
151
151
  rootNodeId,
152
152
  this.#config.caseAuthenticatedTags,
153
153
  ),
@@ -6,7 +6,7 @@
6
6
 
7
7
  import { CertificateAuthority } from "#certificate/CertificateAuthority.js";
8
8
  import { ImplementationError, MockCrypto } from "#general";
9
- import { FabricIndex, VendorId } from "#types";
9
+ import { FabricId, FabricIndex, VendorId } from "#types";
10
10
  import { FabricAuthority } from "./FabricAuthority.js";
11
11
  import { FabricManager } from "./FabricManager.js";
12
12
 
@@ -54,6 +54,7 @@ export namespace TestFabric {
54
54
  adminFabricLabel: `mock-fabric-${index}`,
55
55
  adminVendorId: VendorId(0xfff1),
56
56
  fabricIndex: FabricIndex(index),
57
+ fabricId: FabricId(1),
57
58
  },
58
59
  fabrics,
59
60
  });
@@ -116,7 +116,7 @@ export class InteractionClientProvider {
116
116
  operationalAddress?: ServerAddressUdp;
117
117
  },
118
118
  ): Promise<InteractionClient> {
119
- await this.#peers.ensureConnection(address, options);
119
+ await this.#peers.connect(address, options);
120
120
 
121
121
  return this.getInteractionClient(address, options);
122
122
  }
@@ -301,7 +301,7 @@ export class InteractionServerMessenger extends InteractionMessenger {
301
301
  }
302
302
  default:
303
303
  throw new StatusResponseError(
304
- `Unsupported message type ${message.payloadHeader.messageType}`,
304
+ `Unsupported message type ${MessageType[message.payloadHeader.messageType]} (${message.payloadHeader.messageType})`,
305
305
  Status.InvalidAction,
306
306
  );
307
307
  }
@@ -76,7 +76,7 @@ export class MdnsService {
76
76
  return this.#construction;
77
77
  }
78
78
 
79
- async [Symbol.asyncDispose]() {
79
+ async close() {
80
80
  this.#env.delete(MdnsService, this);
81
81
 
82
82
  await this.#construction.close(async () => {
@@ -313,32 +313,40 @@ export class ControllerCommissioner {
313
313
  if (device !== undefined) {
314
314
  logger.info(`Establish PASE to device`, MdnsClient.discoveryDataDiagnostics(device));
315
315
  }
316
- if (address.type === "udp") {
317
- const { ip } = address;
318
316
 
319
- const isIpv6Address = isIPv6(ip);
320
- const paseInterface = this.#context.transports.interfaceFor(
321
- ChannelType.UDP,
322
- isIpv6Address ? "::" : "0.0.0.0",
323
- );
324
- if (paseInterface === undefined) {
325
- // mainly IPv6 address when IPv4 is disabled
326
- throw new PairRetransmissionLimitReachedError(
327
- `IPv${isIpv6Address ? "6" : "4"} interface not initialized. Cannot use ${ip} for commissioning.`,
317
+ switch (address.type) {
318
+ case "udp":
319
+ const { ip } = address;
320
+
321
+ const isIpv6Address = isIPv6(ip);
322
+ const paseInterface = this.#context.transports.interfaceFor(
323
+ ChannelType.UDP,
324
+ isIpv6Address ? "::" : "0.0.0.0",
328
325
  );
329
- }
330
- paseChannel = await paseInterface.openChannel(address);
331
- } else if (address.type === "ble") {
332
- const ble = this.#context.transports.interfaceFor(ChannelType.BLE);
333
- if (!ble) {
334
- throw new PairRetransmissionLimitReachedError(
335
- `BLE interface not initialized. Cannot use ${address.peripheralAddress} for commissioning.`,
326
+ if (paseInterface === undefined) {
327
+ // mainly IPv6 address when IPv4 is disabled
328
+ throw new PairRetransmissionLimitReachedError(
329
+ `IPv${isIpv6Address ? "6" : "4"} interface not initialized. Cannot use ${ip} for commissioning.`,
330
+ );
331
+ }
332
+ paseChannel = await paseInterface.openChannel(address);
333
+ break;
334
+
335
+ case "ble":
336
+ const ble = this.#context.transports.interfaceFor(ChannelType.BLE);
337
+ if (!ble) {
338
+ throw new PairRetransmissionLimitReachedError(
339
+ `BLE interface not initialized. Cannot use ${address.peripheralAddress} for commissioning.`,
340
+ );
341
+ }
342
+ // TODO Have a Timeout mechanism here for connections
343
+ paseChannel = await ble.openChannel(address);
344
+ break;
345
+
346
+ default:
347
+ throw new ImplementationError(
348
+ `Unsupported address type ${(address as ServerAddress).type} for Matter protocol`,
336
349
  );
337
- }
338
- // TODO Have a Timeout mechanism here for connections
339
- paseChannel = await ble.openChannel(address);
340
- } else {
341
- throw new ImplementationError(`Unsupported PASE address type ${address.type}`);
342
350
  }
343
351
 
344
352
  // Do PASE paring
@@ -258,10 +258,14 @@ export class PeerSet implements ImmutableSet<OperationalPeer>, ObservableSet<Ope
258
258
  return this.#interactionQueue;
259
259
  }
260
260
 
261
+ async connect(address: PeerAddress, options: PeerConnectionOptions & { operationalAddress?: ServerAddressUdp }) {
262
+ await this.#ensureConnection(address, { ...options, allowUnknownPeer: true });
263
+ }
264
+
261
265
  /**
262
266
  * Ensure there is a channel to the designated peer.
263
267
  */
264
- async ensureConnection(
268
+ async #ensureConnection(
265
269
  address: PeerAddress,
266
270
  options: PeerConnectionOptions & {
267
271
  allowUnknownPeer?: boolean;
@@ -306,6 +310,7 @@ export class PeerSet implements ImmutableSet<OperationalPeer>, ObservableSet<Ope
306
310
 
307
311
  /**
308
312
  * Obtain an exchange provider for the designated peer.
313
+ * TODO enhance PeerConnectionOptions.discoveryOptions.discoveryData with "addresses" for known operational addresses
309
314
  */
310
315
  async exchangeProviderFor(addressOrChannel: PeerAddress | MessageChannel, options: PeerConnectionOptions = {}) {
311
316
  if (addressOrChannel instanceof MessageChannel) {
@@ -325,7 +330,7 @@ export class PeerSet implements ImmutableSet<OperationalPeer>, ObservableSet<Ope
325
330
 
326
331
  if (!initiallyConnected && !this.#channels.hasChannel(address)) {
327
332
  // We got an uninitialized node, so do the first connection as usual
328
- await this.ensureConnection(address, {
333
+ await this.#ensureConnection(address, {
329
334
  discoveryOptions: { discoveryType: NodeDiscoveryType.None },
330
335
  caseAuthenticatedTags,
331
336
  });
@@ -376,11 +381,17 @@ export class PeerSet implements ImmutableSet<OperationalPeer>, ObservableSet<Ope
376
381
 
377
382
  /**
378
383
  * Terminate any active peer connection.
384
+ * Also handles unknown peers
379
385
  */
380
386
  async disconnect(peer: PeerAddress | OperationalPeer, sendSessionClose = true) {
381
- const address = this.get(peer)?.address;
387
+ let address = this.get(peer)?.address; // Check known Peers
382
388
  if (address === undefined) {
383
- return;
389
+ // We did not find a ClientNode for this peer, so check if it is a PeerAddress
390
+ if ("nodeId" in peer && "fabricIndex" in peer) {
391
+ address = peer;
392
+ } else {
393
+ return;
394
+ }
384
395
  }
385
396
 
386
397
  await this.#sessions.removeAllSessionsForNode(address, sendSessionClose);
@@ -720,6 +731,7 @@ export class PeerSet implements ImmutableSet<OperationalPeer>, ObservableSet<Ope
720
731
  const unsecureSession = this.#sessions.createInsecureSession({
721
732
  // Use the session parameters from MDNS announcements when available and rest is assumed to be fallbacks
722
733
  sessionParameters: {
734
+ ...sessionParameters,
723
735
  idleInterval: discoveryData?.SII ?? sessionParameters?.idleInterval,
724
736
  activeInterval: discoveryData?.SAI ?? sessionParameters?.activeInterval,
725
737
  activeThreshold: discoveryData?.SAT ?? sessionParameters?.activeThreshold,
@@ -817,7 +829,7 @@ export class PeerSet implements ImmutableSet<OperationalPeer>, ObservableSet<Ope
817
829
 
818
830
  async #addOrUpdatePeer(
819
831
  address: PeerAddress,
820
- operationalServerAddress: ServerAddressUdp,
832
+ operationalServerAddress?: ServerAddressUdp,
821
833
  discoveryData?: DiscoveryData,
822
834
  ) {
823
835
  let peer = this.#peersByAddress.get(address);
@@ -825,7 +837,7 @@ export class PeerSet implements ImmutableSet<OperationalPeer>, ObservableSet<Ope
825
837
  peer = { address, dataStore: await this.#store.createNodeStore(address) };
826
838
  this.#peers.add(peer);
827
839
  }
828
- peer.operationalAddress = operationalServerAddress;
840
+ peer.operationalAddress = operationalServerAddress ?? peer.operationalAddress;
829
841
  if (discoveryData !== undefined) {
830
842
  peer.discoveryData = {
831
843
  ...peer.discoveryData,
@@ -835,7 +847,7 @@ export class PeerSet implements ImmutableSet<OperationalPeer>, ObservableSet<Ope
835
847
  await this.#store.updatePeer(peer);
836
848
 
837
849
  // If we got a new channel and have a running discovery we can end it
838
- if (this.#runningPeerDiscoveries.has(address)) {
850
+ if (peer.operationalAddress !== undefined && this.#runningPeerDiscoveries.has(address)) {
839
851
  logger.info(`Found ${address} during discovery, cancel discovery.`);
840
852
  // We are currently discovering this node, so we need to update the discovery data
841
853
  const { mdnsClient: mdnsScanner } = this.#runningPeerDiscoveries.get(address) ?? {};
@@ -845,6 +857,10 @@ export class PeerSet implements ImmutableSet<OperationalPeer>, ObservableSet<Ope
845
857
  }
846
858
  }
847
859
 
860
+ addKnownPeer(address: PeerAddress, operationalServerAddress?: ServerAddressUdp, discoveryData?: DiscoveryData) {
861
+ return this.#addOrUpdatePeer(address, operationalServerAddress, discoveryData);
862
+ }
863
+
848
864
  #getLastOperationalAddress(address: PeerAddress) {
849
865
  return this.#peersByAddress.get(address)?.operationalAddress;
850
866
  }
@@ -335,9 +335,10 @@ export class ExchangeManager {
335
335
  return;
336
336
  }
337
337
  const { session } = exchange;
338
+ this.#exchanges.delete(exchangeIndex);
338
339
  if (NodeSession.is(session) && session.closingAfterExchangeFinished) {
339
340
  logger.debug(
340
- `Exchange index ${exchangeIndex} Session ${session.name} is already marked for closure. Close session now.`,
341
+ `Exchange index ${exchangeIndex} on Session ${session.name} is already marked for closure. Close session now.`,
341
342
  );
342
343
  try {
343
344
  await this.#closeSession(session);
@@ -345,7 +346,6 @@ export class ExchangeManager {
345
346
  logger.error(`Error closing session ${session.name}. Ignoring.`, error);
346
347
  }
347
348
  }
348
- this.#exchanges.delete(exchangeIndex);
349
349
  }
350
350
 
351
351
  async #closeSession(session: NodeSession) {
@@ -198,6 +198,7 @@ export class MessageExchange {
198
198
 
199
199
  logger.debug(
200
200
  "New exchange",
201
+ isInitiator ? "»" : "«",
201
202
  Diagnostic.dict({
202
203
  channel: channel.name,
203
204
  protocol: this.#protocolId,
@@ -507,7 +508,7 @@ export class MessageExchange {
507
508
  if (finalWaitTime > 0) {
508
509
  this.#retransmissionCounter--; // We will not resubmit the message again
509
510
  logger.debug(
510
- `Message ${message.packetHeader.messageId}: Wait additional ${finalWaitTime}ms for processing time and peer resubmissions after all our resubmissions`,
511
+ `Message ${message.packetHeader.messageId}: Wait additional ${Duration.format(finalWaitTime)} for processing time and peer resubmissions after all our resubmissions`,
511
512
  );
512
513
  this.#retransmissionTimer = Time.getTimer(
513
514
  `Message wait time after resubmissions ${message.packetHeader.messageId}`,
@@ -537,7 +538,7 @@ export class MessageExchange {
537
538
  this.context.retry(this.#retransmissionCounter);
538
539
  const resubmissionBackoffTime = this.channel.getMrpResubmissionBackOffTime(this.#retransmissionCounter);
539
540
  logger.debug(
540
- `Resubmit message ${message.packetHeader.messageId} (retransmission attempt ${this.#retransmissionCounter}, backoff time ${resubmissionBackoffTime}ms))`,
541
+ `Resubmit message ${message.packetHeader.messageId} (retransmission attempt ${this.#retransmissionCounter}, backoff time ${Duration.format(resubmissionBackoffTime)}))`,
541
542
  );
542
543
 
543
544
  this.channel
@@ -23,6 +23,7 @@ import {
23
23
  ObserverGroup,
24
24
  StorageContext,
25
25
  StorageManager,
26
+ toHex,
26
27
  } from "#general";
27
28
  import { Subscription } from "#interaction/Subscription.js";
28
29
  import { Specification } from "#model";
@@ -73,6 +74,7 @@ type ResumptionStorageRecord = {
73
74
  sharedSecret: Bytes;
74
75
  resumptionId: Bytes;
75
76
  fabricId: FabricId;
77
+ fabricIndex: FabricIndex;
76
78
  peerNodeId: NodeId;
77
79
  sessionParameters: {
78
80
  idleInterval: Duration;
@@ -557,21 +559,21 @@ export class SessionManager {
557
559
  ([
558
560
  address,
559
561
  { sharedSecret, resumptionId, peerNodeId, fabric, sessionParameters, caseAuthenticatedTags },
560
- ]) =>
561
- ({
562
- nodeId: address.nodeId,
563
- sharedSecret,
564
- resumptionId,
565
- fabricId: fabric.fabricId,
566
- peerNodeId: peerNodeId,
567
- sessionParameters: {
568
- ...sessionParameters,
569
- supportedTransports: sessionParameters.supportedTransports
570
- ? SupportedTransportsSchema.encode(sessionParameters.supportedTransports)
571
- : undefined,
572
- },
573
- caseAuthenticatedTags,
574
- }) as ResumptionStorageRecord,
562
+ ]): ResumptionStorageRecord => ({
563
+ nodeId: address.nodeId,
564
+ sharedSecret,
565
+ resumptionId,
566
+ fabricId: fabric.fabricId,
567
+ fabricIndex: fabric.fabricIndex,
568
+ peerNodeId: peerNodeId,
569
+ sessionParameters: {
570
+ ...sessionParameters,
571
+ supportedTransports: sessionParameters.supportedTransports
572
+ ? SupportedTransportsSchema.encode(sessionParameters.supportedTransports)
573
+ : undefined,
574
+ },
575
+ caseAuthenticatedTags,
576
+ }),
575
577
  ),
576
578
  );
577
579
  }
@@ -590,6 +592,7 @@ export class SessionManager {
590
592
  sharedSecret,
591
593
  resumptionId,
592
594
  fabricId,
595
+ fabricIndex,
593
596
  peerNodeId,
594
597
  sessionParameters: {
595
598
  idleInterval,
@@ -604,19 +607,27 @@ export class SessionManager {
604
607
  } = {},
605
608
  caseAuthenticatedTags,
606
609
  }) => {
607
- const fabric = this.#context.fabrics.find(fabric => fabric.fabricId === fabricId);
608
- logger.info(
609
- "restoring resumption record for node",
610
- nodeId,
611
- "and peer node",
612
- peerNodeId,
613
- "for fabric index",
614
- fabric?.fabricIndex,
610
+ const fabric = this.#context.fabrics.find(
611
+ fabric =>
612
+ fabric.fabricId === fabricId &&
613
+ // Backward compatibility logic: fabricIndex was added later (0.15.5), so it might be undefined in older records
614
+ (fabricIndex === undefined || fabric.fabricIndex === fabricIndex),
615
615
  );
616
616
  if (!fabric) {
617
- logger.error("fabric not found for resumption record", fabricId);
617
+ logger.warn(
618
+ `Ignoring resumption record for fabric 0x${toHex(fabricId)} and index ${fabricIndex} because we cannot find a matching fabric`,
619
+ );
618
620
  return;
619
621
  }
622
+ logger.info(
623
+ "restoring resumption record for node",
624
+ fabric.addressOf(nodeId).toString(),
625
+ "and peer node",
626
+ fabric.addressOf(peerNodeId).toString(),
627
+ "for fabric id",
628
+ `0x${toHex(fabric.fabricId)}`,
629
+ `(0x${toHex(fabric.rootVendorId)}, "${fabric?.label}")`,
630
+ );
620
631
  this.#resumptionRecords.set(fabric.addressOf(nodeId), {
621
632
  sharedSecret,
622
633
  resumptionId,