@matter/protocol 0.16.8-alpha.0-20260123-dff2cae52 → 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 (201) hide show
  1. package/dist/cjs/action/client/ClientInteraction.d.ts +4 -4
  2. package/dist/cjs/action/client/ClientInteraction.d.ts.map +1 -1
  3. package/dist/cjs/action/client/ClientInteraction.js +48 -6
  4. package/dist/cjs/action/client/ClientInteraction.js.map +1 -1
  5. package/dist/cjs/action/client/QueuedClientInteraction.d.ts +0 -1
  6. package/dist/cjs/action/client/QueuedClientInteraction.d.ts.map +1 -1
  7. package/dist/cjs/action/client/QueuedClientInteraction.js +0 -1
  8. package/dist/cjs/action/client/QueuedClientInteraction.js.map +1 -1
  9. package/dist/cjs/action/client/subscription/ClientSubscriptionHandler.d.ts.map +1 -1
  10. package/dist/cjs/action/client/subscription/ClientSubscriptionHandler.js +5 -2
  11. package/dist/cjs/action/client/subscription/ClientSubscriptionHandler.js.map +1 -1
  12. package/dist/cjs/action/server/AttributeWriteResponse.d.ts +1 -1
  13. package/dist/cjs/action/server/AttributeWriteResponse.d.ts.map +1 -1
  14. package/dist/cjs/action/server/AttributeWriteResponse.js +0 -6
  15. package/dist/cjs/action/server/AttributeWriteResponse.js.map +1 -1
  16. package/dist/cjs/action/server/DataResponse.d.ts +5 -0
  17. package/dist/cjs/action/server/DataResponse.d.ts.map +1 -1
  18. package/dist/cjs/action/server/DataResponse.js +7 -0
  19. package/dist/cjs/action/server/DataResponse.js.map +1 -1
  20. package/dist/cjs/action/server/ServerInteraction.js.map +1 -1
  21. package/dist/cjs/dcl/DclCertificateService.d.ts.map +1 -1
  22. package/dist/cjs/dcl/DclCertificateService.js +3 -0
  23. package/dist/cjs/dcl/DclCertificateService.js.map +1 -1
  24. package/dist/cjs/dcl/DclOtaUpdateService.d.ts.map +1 -1
  25. package/dist/cjs/dcl/DclOtaUpdateService.js +6 -4
  26. package/dist/cjs/dcl/DclOtaUpdateService.js.map +1 -1
  27. package/dist/cjs/interaction/InteractionMessenger.d.ts +30 -30
  28. package/dist/cjs/interaction/InteractionMessenger.d.ts.map +1 -1
  29. package/dist/cjs/interaction/InteractionMessenger.js +81 -12
  30. package/dist/cjs/interaction/InteractionMessenger.js.map +1 -1
  31. package/dist/cjs/mdns/MdnsClient.d.ts.map +1 -1
  32. package/dist/cjs/mdns/MdnsClient.js +163 -102
  33. package/dist/cjs/mdns/MdnsClient.js.map +1 -1
  34. package/dist/cjs/mdns/MdnsServer.d.ts +2 -0
  35. package/dist/cjs/mdns/MdnsServer.d.ts.map +1 -1
  36. package/dist/cjs/mdns/MdnsServer.js +45 -5
  37. package/dist/cjs/mdns/MdnsServer.js.map +1 -1
  38. package/dist/cjs/peer/ControllerCommissioningFlow.d.ts.map +1 -1
  39. package/dist/cjs/peer/ControllerCommissioningFlow.js +3 -1
  40. package/dist/cjs/peer/ControllerCommissioningFlow.js.map +1 -1
  41. package/dist/cjs/peer/Peer.d.ts +2 -1
  42. package/dist/cjs/peer/Peer.d.ts.map +1 -1
  43. package/dist/cjs/peer/Peer.js +20 -3
  44. package/dist/cjs/peer/Peer.js.map +1 -1
  45. package/dist/cjs/peer/PeerAddressStore.d.ts +1 -11
  46. package/dist/cjs/peer/PeerAddressStore.d.ts.map +1 -1
  47. package/dist/cjs/peer/PeerAddressStore.js +1 -4
  48. package/dist/cjs/peer/PeerAddressStore.js.map +1 -1
  49. package/dist/cjs/peer/PeerDescriptor.d.ts +1 -9
  50. package/dist/cjs/peer/PeerDescriptor.d.ts.map +1 -1
  51. package/dist/cjs/peer/PeerDescriptor.js +1 -6
  52. package/dist/cjs/peer/PeerDescriptor.js.map +1 -1
  53. package/dist/cjs/peer/PeerSet.d.ts +1 -1
  54. package/dist/cjs/peer/PeerSet.d.ts.map +1 -1
  55. package/dist/cjs/peer/PeerSet.js +55 -22
  56. package/dist/cjs/peer/PeerSet.js.map +2 -2
  57. package/dist/cjs/protocol/ExchangeManager.d.ts.map +1 -1
  58. package/dist/cjs/protocol/ExchangeManager.js +2 -2
  59. package/dist/cjs/protocol/ExchangeManager.js.map +1 -1
  60. package/dist/cjs/protocol/ExchangeProvider.d.ts.map +1 -1
  61. package/dist/cjs/protocol/ExchangeProvider.js +3 -3
  62. package/dist/cjs/protocol/ExchangeProvider.js.map +1 -1
  63. package/dist/cjs/protocol/MRP.d.ts +54 -0
  64. package/dist/cjs/protocol/MRP.d.ts.map +1 -0
  65. package/dist/cjs/protocol/MRP.js +96 -0
  66. package/dist/cjs/protocol/MRP.js.map +6 -0
  67. package/dist/cjs/protocol/MessageChannel.d.ts +0 -23
  68. package/dist/cjs/protocol/MessageChannel.d.ts.map +1 -1
  69. package/dist/cjs/protocol/MessageChannel.js +15 -47
  70. package/dist/cjs/protocol/MessageChannel.js.map +2 -2
  71. package/dist/cjs/protocol/MessageExchange.d.ts.map +1 -1
  72. package/dist/cjs/protocol/MessageExchange.js +7 -7
  73. package/dist/cjs/protocol/MessageExchange.js.map +1 -1
  74. package/dist/cjs/protocol/index.d.ts +1 -0
  75. package/dist/cjs/protocol/index.d.ts.map +1 -1
  76. package/dist/cjs/protocol/index.js +1 -0
  77. package/dist/cjs/protocol/index.js.map +1 -1
  78. package/dist/cjs/session/NodeSession.js +2 -2
  79. package/dist/cjs/session/NodeSession.js.map +1 -1
  80. package/dist/cjs/session/Session.d.ts +1 -0
  81. package/dist/cjs/session/Session.d.ts.map +1 -1
  82. package/dist/cjs/session/case/CaseClient.d.ts.map +1 -1
  83. package/dist/cjs/session/case/CaseClient.js +1 -1
  84. package/dist/cjs/session/case/CaseClient.js.map +1 -1
  85. package/dist/cjs/session/case/CaseServer.d.ts.map +1 -1
  86. package/dist/cjs/session/case/CaseServer.js +4 -1
  87. package/dist/cjs/session/case/CaseServer.js.map +1 -1
  88. package/dist/esm/action/client/ClientInteraction.d.ts +4 -4
  89. package/dist/esm/action/client/ClientInteraction.d.ts.map +1 -1
  90. package/dist/esm/action/client/ClientInteraction.js +49 -6
  91. package/dist/esm/action/client/ClientInteraction.js.map +1 -1
  92. package/dist/esm/action/client/QueuedClientInteraction.d.ts +0 -1
  93. package/dist/esm/action/client/QueuedClientInteraction.d.ts.map +1 -1
  94. package/dist/esm/action/client/QueuedClientInteraction.js +0 -1
  95. package/dist/esm/action/client/QueuedClientInteraction.js.map +1 -1
  96. package/dist/esm/action/client/subscription/ClientSubscriptionHandler.d.ts.map +1 -1
  97. package/dist/esm/action/client/subscription/ClientSubscriptionHandler.js +5 -2
  98. package/dist/esm/action/client/subscription/ClientSubscriptionHandler.js.map +1 -1
  99. package/dist/esm/action/server/AttributeWriteResponse.d.ts +1 -1
  100. package/dist/esm/action/server/AttributeWriteResponse.d.ts.map +1 -1
  101. package/dist/esm/action/server/AttributeWriteResponse.js +0 -6
  102. package/dist/esm/action/server/AttributeWriteResponse.js.map +1 -1
  103. package/dist/esm/action/server/DataResponse.d.ts +5 -0
  104. package/dist/esm/action/server/DataResponse.d.ts.map +1 -1
  105. package/dist/esm/action/server/DataResponse.js +7 -0
  106. package/dist/esm/action/server/DataResponse.js.map +1 -1
  107. package/dist/esm/action/server/ServerInteraction.js.map +1 -1
  108. package/dist/esm/dcl/DclCertificateService.d.ts.map +1 -1
  109. package/dist/esm/dcl/DclCertificateService.js +3 -0
  110. package/dist/esm/dcl/DclCertificateService.js.map +1 -1
  111. package/dist/esm/dcl/DclOtaUpdateService.d.ts.map +1 -1
  112. package/dist/esm/dcl/DclOtaUpdateService.js +6 -4
  113. package/dist/esm/dcl/DclOtaUpdateService.js.map +1 -1
  114. package/dist/esm/interaction/InteractionMessenger.d.ts +30 -30
  115. package/dist/esm/interaction/InteractionMessenger.d.ts.map +1 -1
  116. package/dist/esm/interaction/InteractionMessenger.js +82 -12
  117. package/dist/esm/interaction/InteractionMessenger.js.map +1 -1
  118. package/dist/esm/mdns/MdnsClient.d.ts.map +1 -1
  119. package/dist/esm/mdns/MdnsClient.js +163 -102
  120. package/dist/esm/mdns/MdnsClient.js.map +1 -1
  121. package/dist/esm/mdns/MdnsServer.d.ts +2 -0
  122. package/dist/esm/mdns/MdnsServer.d.ts.map +1 -1
  123. package/dist/esm/mdns/MdnsServer.js +45 -5
  124. package/dist/esm/mdns/MdnsServer.js.map +1 -1
  125. package/dist/esm/peer/ControllerCommissioningFlow.d.ts.map +1 -1
  126. package/dist/esm/peer/ControllerCommissioningFlow.js +3 -1
  127. package/dist/esm/peer/ControllerCommissioningFlow.js.map +1 -1
  128. package/dist/esm/peer/Peer.d.ts +2 -1
  129. package/dist/esm/peer/Peer.d.ts.map +1 -1
  130. package/dist/esm/peer/Peer.js +20 -3
  131. package/dist/esm/peer/Peer.js.map +1 -1
  132. package/dist/esm/peer/PeerAddressStore.d.ts +1 -11
  133. package/dist/esm/peer/PeerAddressStore.d.ts.map +1 -1
  134. package/dist/esm/peer/PeerAddressStore.js +1 -4
  135. package/dist/esm/peer/PeerAddressStore.js.map +1 -1
  136. package/dist/esm/peer/PeerDescriptor.d.ts +1 -9
  137. package/dist/esm/peer/PeerDescriptor.d.ts.map +1 -1
  138. package/dist/esm/peer/PeerDescriptor.js +1 -6
  139. package/dist/esm/peer/PeerDescriptor.js.map +1 -1
  140. package/dist/esm/peer/PeerSet.d.ts +1 -1
  141. package/dist/esm/peer/PeerSet.d.ts.map +1 -1
  142. package/dist/esm/peer/PeerSet.js +60 -24
  143. package/dist/esm/peer/PeerSet.js.map +2 -2
  144. package/dist/esm/protocol/ExchangeManager.d.ts.map +1 -1
  145. package/dist/esm/protocol/ExchangeManager.js +2 -2
  146. package/dist/esm/protocol/ExchangeManager.js.map +1 -1
  147. package/dist/esm/protocol/ExchangeProvider.d.ts.map +1 -1
  148. package/dist/esm/protocol/ExchangeProvider.js +3 -3
  149. package/dist/esm/protocol/ExchangeProvider.js.map +1 -1
  150. package/dist/esm/protocol/MRP.d.ts +54 -0
  151. package/dist/esm/protocol/MRP.d.ts.map +1 -0
  152. package/dist/esm/protocol/MRP.js +76 -0
  153. package/dist/esm/protocol/MRP.js.map +6 -0
  154. package/dist/esm/protocol/MessageChannel.d.ts +0 -23
  155. package/dist/esm/protocol/MessageChannel.d.ts.map +1 -1
  156. package/dist/esm/protocol/MessageChannel.js +16 -54
  157. package/dist/esm/protocol/MessageChannel.js.map +2 -2
  158. package/dist/esm/protocol/MessageExchange.d.ts.map +1 -1
  159. package/dist/esm/protocol/MessageExchange.js +3 -3
  160. package/dist/esm/protocol/MessageExchange.js.map +1 -1
  161. package/dist/esm/protocol/index.d.ts +1 -0
  162. package/dist/esm/protocol/index.d.ts.map +1 -1
  163. package/dist/esm/protocol/index.js +1 -0
  164. package/dist/esm/protocol/index.js.map +1 -1
  165. package/dist/esm/session/NodeSession.js +2 -2
  166. package/dist/esm/session/NodeSession.js.map +1 -1
  167. package/dist/esm/session/Session.d.ts +1 -0
  168. package/dist/esm/session/Session.d.ts.map +1 -1
  169. package/dist/esm/session/case/CaseClient.d.ts.map +1 -1
  170. package/dist/esm/session/case/CaseClient.js +2 -2
  171. package/dist/esm/session/case/CaseClient.js.map +1 -1
  172. package/dist/esm/session/case/CaseServer.d.ts.map +1 -1
  173. package/dist/esm/session/case/CaseServer.js +4 -1
  174. package/dist/esm/session/case/CaseServer.js.map +1 -1
  175. package/package.json +6 -6
  176. package/src/action/client/ClientInteraction.ts +62 -6
  177. package/src/action/client/QueuedClientInteraction.ts +0 -1
  178. package/src/action/client/subscription/ClientSubscriptionHandler.ts +5 -2
  179. package/src/action/server/AttributeWriteResponse.ts +4 -16
  180. package/src/action/server/DataResponse.ts +8 -0
  181. package/src/action/server/ServerInteraction.ts +2 -2
  182. package/src/dcl/DclCertificateService.ts +3 -0
  183. package/src/dcl/DclOtaUpdateService.ts +11 -5
  184. package/src/interaction/InteractionMessenger.ts +113 -15
  185. package/src/mdns/MdnsClient.ts +216 -104
  186. package/src/mdns/MdnsServer.ts +79 -6
  187. package/src/peer/ControllerCommissioningFlow.ts +5 -1
  188. package/src/peer/Peer.ts +28 -5
  189. package/src/peer/PeerAddressStore.ts +1 -19
  190. package/src/peer/PeerDescriptor.ts +1 -15
  191. package/src/peer/PeerSet.ts +82 -35
  192. package/src/protocol/ExchangeManager.ts +5 -2
  193. package/src/protocol/ExchangeProvider.ts +3 -3
  194. package/src/protocol/MRP.ts +146 -0
  195. package/src/protocol/MessageChannel.ts +16 -101
  196. package/src/protocol/MessageExchange.ts +4 -3
  197. package/src/protocol/index.ts +1 -0
  198. package/src/session/NodeSession.ts +3 -3
  199. package/src/session/Session.ts +1 -0
  200. package/src/session/case/CaseClient.ts +8 -2
  201. package/src/session/case/CaseServer.ts +4 -0
@@ -19,6 +19,7 @@ import { BdxMessenger } from "#bdx/BdxMessenger.js";
19
19
  import { Mark } from "#common/Mark.js";
20
20
  import {
21
21
  Abort,
22
+ AsyncIterator,
22
23
  BasicSet,
23
24
  Diagnostic,
24
25
  Duration,
@@ -38,6 +39,7 @@ import { InteractionClientMessenger, MessageType } from "#interaction/Interactio
38
39
  import { Subscription } from "#interaction/Subscription.js";
39
40
  import { PeerAddress } from "#peer/PeerAddress.js";
40
41
  import { ExchangeProvider } from "#protocol/ExchangeProvider.js";
42
+ import { SessionClosedError } from "#protocol/index.js";
41
43
  import { SecureSession } from "#session/SecureSession.js";
42
44
  import { Status, TlvAttributeReport, TlvNoResponse, TlvSubscribeResponse, TypeFromSchema } from "#types";
43
45
  import { ClientWrite } from "./ClientWrite.js";
@@ -186,10 +188,7 @@ export class ClientInteraction<
186
188
  }
187
189
 
188
190
  /**
189
- * Update node attributes.
190
- *
191
- * Writes with the Matter protocol are generally not atomic, so this method only throws if the entire action fails.
192
- * You must check each {@link WriteResult.AttributeStatus} to determine whether individual updates failed.
191
+ * Write to node attributes.
193
192
  */
194
193
  async write<T extends ClientWrite>(request: T, session?: SessionT): WriteResult<T> {
195
194
  await using context = await this.#begin("writing", request, session);
@@ -253,9 +252,9 @@ export class ClientInteraction<
253
252
  }
254
253
 
255
254
  /**
256
- * Invoke one or more commands.
255
+ * Invoke a single batch of commands (internal implementation).
257
256
  */
258
- async *invoke(request: ClientInvoke, session?: SessionT): DecodedInvokeResult {
257
+ async *#invokeSingle(request: ClientInvoke, session?: SessionT): DecodedInvokeResult {
259
258
  await using context = await this.#begin("invoking", request, session);
260
259
  const { checkAbort, messenger } = context;
261
260
 
@@ -358,6 +357,63 @@ export class ClientInteraction<
358
357
  }
359
358
  }
360
359
 
360
+ /**
361
+ * Split commands across multiple parallel invoke-exchanges.
362
+ * Results are streamed as they arrive from any batch, not buffered.
363
+ */
364
+ async *#invokeWithSplitting(
365
+ request: ClientInvoke,
366
+ maxPathsPerInvoke: number,
367
+ session?: SessionT,
368
+ ): DecodedInvokeResult {
369
+ // Split commands into batches
370
+ const allCommands = [...request.commands.entries()];
371
+ const batches = new Array<ClientInvoke["commands"]>();
372
+
373
+ for (let i = 0; i < allCommands.length; i += maxPathsPerInvoke) {
374
+ const batchEntries = allCommands.slice(i, i + maxPathsPerInvoke);
375
+ batches.push(new Map(batchEntries));
376
+ }
377
+
378
+ // Create async iterators for each batch and merge results as they arrive
379
+ const iterators = batches.map(batchCommands => {
380
+ const batchRequest: ClientInvoke = {
381
+ ...request,
382
+ commands: batchCommands,
383
+ };
384
+ return this.#invokeSingle(batchRequest, session);
385
+ });
386
+
387
+ yield* AsyncIterator.merge(iterators, "One or more invoke batches failed");
388
+ }
389
+
390
+ /** Get the effective MaxPathsPerInvoke parameter from the session, or 1 as a fallback as defined by spec. */
391
+ get #maxPathsPerInvoke(): number {
392
+ try {
393
+ return this.session.parameters.maxPathsPerInvoke;
394
+ } catch (error) {
395
+ SessionClosedError.accept(error);
396
+ return 1;
397
+ }
398
+ }
399
+
400
+ /**
401
+ * Invoke one or more commands.
402
+ *
403
+ * When the number of commands exceeds the peer's MaxPathsPerInvoke limit (or 1 for older nodes),
404
+ * commands are split across multiple parallel exchanges automatically.
405
+ */
406
+ async *invoke(request: ClientInvoke, session?: SessionT): DecodedInvokeResult {
407
+ const maxPathsPerInvoke = this.#maxPathsPerInvoke;
408
+ const commandCount = request.commands.size;
409
+
410
+ if (commandCount > maxPathsPerInvoke) {
411
+ yield* this.#invokeWithSplitting(request, maxPathsPerInvoke, session);
412
+ } else {
413
+ yield* this.#invokeSingle(request, session);
414
+ }
415
+ }
416
+
361
417
  /**
362
418
  * Subscribe to attribute values and events.
363
419
  */
@@ -69,7 +69,6 @@ export class QueuedClientInteraction<
69
69
 
70
70
  /**
71
71
  * Write chosen attributes remotely to the node.
72
- * The returned attribute writing status information is returned.
73
72
  */
74
73
  override async write<T extends ClientWrite>(request: T, session?: SessionT): WriteResult<T> {
75
74
  using _slot = await this.queue.obtainSlot();
@@ -44,7 +44,7 @@ export class ClientSubscriptionHandler implements ProtocolHandler {
44
44
  // Ensure there is a subscription ID present
45
45
  const { subscriptionId } = initialReport;
46
46
  if (subscriptionId === undefined) {
47
- logger.debug("Ignoring unsolicited data report with no subscription ID");
47
+ logger.debug(exchange.via, "Ignoring unsolicited data report with no subscription ID");
48
48
  await sendInvalid(messenger, undefined);
49
49
  return;
50
50
  }
@@ -55,6 +55,7 @@ export class ClientSubscriptionHandler implements ProtocolHandler {
55
55
  const subscription = this.#subscriptions.getPeer(session.peerAddress, subscriptionId);
56
56
  if (subscription === undefined) {
57
57
  logger.info(
58
+ exchange.via,
58
59
  "Ignoring data report for unknown subscription ID",
59
60
  Diagnostic.strong(Subscription.idStrOf(subscriptionId)),
60
61
  );
@@ -72,6 +73,7 @@ export class ClientSubscriptionHandler implements ProtocolHandler {
72
73
  const ending = await reports.next();
73
74
  if (!ending.done) {
74
75
  logger.warn(
76
+ exchange.via,
75
77
  "Unexpected data reports after empty report",
76
78
  Diagnostic.strong(Subscription.idStrOf(subscriptionId)),
77
79
  );
@@ -125,13 +127,14 @@ async function* processReports(
125
127
  for await (const report of otherReports) {
126
128
  const { subscriptionId: reportSubscriptionId } = report;
127
129
  if (reportSubscriptionId === undefined) {
128
- logger.debug("Ignoring data report with missing subscription id");
130
+ logger.debug(messenger.exchange.via, "Ignoring data report with missing subscription id");
129
131
  await sendInvalid(messenger, reportSubscriptionId);
130
132
  continue;
131
133
  }
132
134
 
133
135
  if (reportSubscriptionId !== subscriptionId) {
134
136
  logger.debug(
137
+ messenger.exchange.via,
135
138
  "Ignoring data report for incorrect subscription id",
136
139
  Diagnostic.strong(Subscription.idStrOf(reportSubscriptionId)),
137
140
  "expected",
@@ -40,7 +40,6 @@ export class AttributeWriteResponse<
40
40
  // a cache between producers that touch the same endpoint and/or cluster
41
41
  #currentEndpoint?: EndpointProtocol;
42
42
  #currentCluster?: ClusterProtocol;
43
- #previousProcessedAttributePath?: WriteResult.ConcreteAttributePath;
44
43
 
45
44
  // Count how many attribute status (on error) and attribute values (on success) we have emitted
46
45
  #statusCount = 0;
@@ -52,7 +51,7 @@ export class AttributeWriteResponse<
52
51
  this.#fabricIndex = session.fabric ?? FabricIndex.NO_FABRIC;
53
52
  }
54
53
 
55
- async process<T extends Write>({ writeRequests, suppressResponse }: T): WriteResult<T> {
54
+ async process({ writeRequests, suppressResponse }: Write): Promise<WriteResult.AttributeStatus[] | undefined> {
56
55
  using _writing = this.join("writing");
57
56
 
58
57
  const writeResponses = new Array<WriteResult.AttributeStatus>();
@@ -75,9 +74,9 @@ export class AttributeWriteResponse<
75
74
  }
76
75
 
77
76
  if (!suppressResponse) {
78
- return writeResponses as Awaited<WriteResult<T>>;
77
+ return writeResponses;
79
78
  }
80
- return undefined as Awaited<WriteResult<T>>;
79
+ return undefined;
81
80
  }
82
81
 
83
82
  /** Guarded accessor for this.#currentEndpoint. This should never be undefined */
@@ -394,9 +393,6 @@ export class AttributeWriteResponse<
394
393
  );
395
394
  }
396
395
 
397
- const previousPath = this.#previousProcessedAttributePath;
398
- this.#previousProcessedAttributePath = path;
399
-
400
396
  try {
401
397
  const { tlv } = attribute;
402
398
  if (listIndex === undefined) {
@@ -408,15 +404,7 @@ export class AttributeWriteResponse<
408
404
  writeState[attributeId] = decoded;
409
405
  await this.session.transaction?.commit();
410
406
  } else if (listIndex === null) {
411
- if (
412
- previousPath?.endpointId !== path.endpointId ||
413
- previousPath?.clusterId !== path.clusterId ||
414
- previousPath?.attributeId !== path.attributeId
415
- ) {
416
- // Mimic chip sdk behavior
417
- throw new StatusResponseError("ADD list action without a former REPLACE_ALL action", Status.Busy);
418
- }
419
- // ADD
407
+ // ADD - caller (InteractionServer) has already validated that this ADD is allowed
420
408
  if (!(tlv instanceof ArraySchema)) {
421
409
  throw new StatusResponseError(
422
410
  `Unsupported Write path provided: listIndex === ${listIndex} but attribute is not a list`,
@@ -42,6 +42,14 @@ export abstract class DataResponse<SessionT extends InteractionSession = Interac
42
42
  return this.#session;
43
43
  }
44
44
 
45
+ /**
46
+ * Update the session for processing subsequent chunks.
47
+ * This allows reusing the same response instance while maintaining state across chunks.
48
+ */
49
+ protected updateSession(session: SessionT) {
50
+ this.#session = session;
51
+ }
52
+
45
53
  /**
46
54
  * The node ID used to filter paths with node ID specified. Unsure if this is ever actually used.
47
55
  */
@@ -69,11 +69,11 @@ export class ServerInteraction<
69
69
  // TODO - validate request
70
70
 
71
71
  const writer = new AttributeWriteResponse(this.#node, session);
72
- return await writer.process(request);
72
+ return (await writer.process(request)) as Awaited<WriteResult<T>>;
73
73
  }
74
74
 
75
75
  async *invoke(request: Invoke, session: SessionT): InvokeResult {
76
- // TODO - validate request
76
+ // TODO - validate request
77
77
 
78
78
  const invoker = new CommandInvokeResponse(this.#node, session);
79
79
  yield* invoker.process(request);
@@ -218,6 +218,9 @@ export class DclCertificateService {
218
218
  async close() {
219
219
  this.#closed = true;
220
220
  this.#updateTimer?.stop();
221
+ if (this.#fetchPromise !== undefined) {
222
+ await this.#fetchPromise;
223
+ }
221
224
  await this.#storageManager?.close();
222
225
  }
223
226
 
@@ -227,11 +227,17 @@ export class DclOtaUpdateService {
227
227
  localUpdates[0].maxApplicableSoftwareVersion ?? localUpdates[0].softwareVersion - 1,
228
228
  source: localUpdates[0].mode === "prod" ? "dcl-prod" : "local",
229
229
  };
230
- logger.debug(`Found local update`, Diagnostic.dict(localUpdate));
231
- if (targetSoftwareVersion !== undefined && localUpdate.softwareVersion === targetSoftwareVersion) {
232
- return localUpdate;
230
+ if (
231
+ localUpdate.softwareVersion > currentSoftwareVersion &&
232
+ currentSoftwareVersion >= localUpdate.minApplicableSoftwareVersion &&
233
+ currentSoftwareVersion <= localUpdate.maxApplicableSoftwareVersion
234
+ ) {
235
+ logger.debug(`Found applicable local update`, Diagnostic.dict(localUpdate));
236
+ if (targetSoftwareVersion !== undefined && localUpdate.softwareVersion === targetSoftwareVersion) {
237
+ return localUpdate;
238
+ }
239
+ foundUpdates.push(localUpdate);
233
240
  }
234
- foundUpdates.push(localUpdate);
235
241
  }
236
242
  }
237
243
 
@@ -508,7 +514,7 @@ export class DclOtaUpdateService {
508
514
  return false;
509
515
  }
510
516
 
511
- // Current version must be within the applicable range if specified
517
+ // The current version must be within the applicable range if specified
512
518
  if (
513
519
  versionInfo.minApplicableSoftwareVersion !== undefined &&
514
520
  currentVersion < versionInfo.minApplicableSoftwareVersion
@@ -30,6 +30,7 @@ import {
30
30
  TlvDataVersionFilter,
31
31
  TlvInvokeRequest,
32
32
  TlvInvokeResponse,
33
+ TlvInvokeResponseForSend,
33
34
  TlvReadRequest,
34
35
  TlvSchema,
35
36
  TlvStatusResponse,
@@ -76,6 +77,7 @@ export type SubscribeRequest = TypeFromSchema<typeof TlvSubscribeRequest>;
76
77
  export type SubscribeResponse = TypeFromSchema<typeof TlvSubscribeResponse>;
77
78
  export type InvokeRequest = TypeFromSchema<typeof TlvInvokeRequest>;
78
79
  export type InvokeResponse = TypeFromSchema<typeof TlvInvokeResponse>;
80
+ export type InvokeResponseForSend = TypeFromSchema<typeof TlvInvokeResponseForSend>;
79
81
  export type TimedRequest = TypeFromSchema<typeof TlvTimedRequest>;
80
82
  export type WriteRequest = TypeFromSchema<typeof TlvWriteRequest>;
81
83
  export type WriteResponse = TypeFromSchema<typeof TlvWriteResponse>;
@@ -211,7 +213,12 @@ export interface InteractionRecipient {
211
213
  request: ReadRequest,
212
214
  message: Message,
213
215
  ): Promise<{ dataReport: DataReport; payload?: DataReportPayloadIterator }>;
214
- handleWriteRequest(exchange: MessageExchange, request: WriteRequest, message: Message): Promise<WriteResponse>;
216
+ handleWriteRequest(
217
+ exchange: MessageExchange,
218
+ request: WriteRequest,
219
+ messenger: InteractionServerMessenger,
220
+ message: Message,
221
+ ): Promise<void>;
215
222
  handleSubscribeRequest(
216
223
  exchange: MessageExchange,
217
224
  request: SubscribeRequest,
@@ -262,11 +269,8 @@ export class InteractionServerMessenger extends InteractionMessenger {
262
269
  }
263
270
  case MessageType.WriteRequest: {
264
271
  const writeRequest = TlvWriteRequest.decode(message.payload);
265
- const { suppressResponse } = writeRequest;
266
- const writeResponse = await recipient.handleWriteRequest(this.exchange, writeRequest, message);
267
- if (!suppressResponse && !isGroupSession) {
268
- await this.send(MessageType.WriteResponse, TlvWriteResponse.encode(writeResponse));
269
- }
272
+ await recipient.handleWriteRequest(this.exchange, writeRequest, this, message);
273
+ // response is sent by the handler
270
274
  break;
271
275
  }
272
276
  case MessageType.SubscribeRequest: {
@@ -278,7 +282,7 @@ export class InteractionServerMessenger extends InteractionMessenger {
278
282
  }
279
283
  const subscribeRequest = TlvSubscribeRequest.decode(message.payload);
280
284
  await recipient.handleSubscribeRequest(this.exchange, subscribeRequest, this, message);
281
- // response is sent by handler
285
+ // response is sent by the handler
282
286
  break;
283
287
  }
284
288
  case MessageType.InvokeRequest: {
@@ -797,6 +801,60 @@ export class InteractionServerMessenger extends InteractionMessenger {
797
801
  }
798
802
  }
799
803
  }
804
+
805
+ /**
806
+ * Send a WriteResponse message.
807
+ */
808
+ async sendWriteResponse(response: WriteResponse, options?: { logContext?: string }) {
809
+ await this.send(MessageType.WriteResponse, TlvWriteResponse.encode(response), {
810
+ logContext: options?.logContext ? { for: options.logContext } : undefined,
811
+ });
812
+ }
813
+
814
+ /**
815
+ * Wait for and decode the next WriteRequest message (for chunked writes).
816
+ */
817
+ async readNextWriteRequest(): Promise<{ writeRequest: WriteRequest; message: Message }> {
818
+ const message = await this.nextMessage(MessageType.WriteRequest, undefined, "WriteRequest-chunk");
819
+ return {
820
+ writeRequest: TlvWriteRequest.decode(message.payload),
821
+ message,
822
+ };
823
+ }
824
+
825
+ /**
826
+ * Send an intermediate InvokeResponse chunk with moreChunkedMessages=true and wait for Status.Success.
827
+ * Returns true if a client acknowledged, and we should continue, false if a client terminated the chunked series.
828
+ */
829
+ async sendInvokeResponseChunk(response: InvokeResponseForSend): Promise<boolean> {
830
+ await this.send(
831
+ MessageType.InvokeResponse,
832
+ TlvInvokeResponseForSend.encode({
833
+ ...response,
834
+ moreChunkedMessages: true,
835
+ }),
836
+ {
837
+ logContext: { for: "InvokeResponse-chunk" },
838
+ },
839
+ );
840
+
841
+ try {
842
+ await this.waitForSuccess("InvokeResponse-chunk");
843
+ return true; // Continue sending chunks
844
+ } catch (error) {
845
+ // Any non-success status or error terminate further transmission of InvokeResponseMessages,
846
+ // close the exchange, and consider the Invoke Interaction completed.
847
+ logger.debug("Chunked invoke response terminated by client", error);
848
+ return false;
849
+ }
850
+ }
851
+
852
+ /**
853
+ * Send the final InvokeResponse (without moreChunkedMessages flag).
854
+ */
855
+ async sendInvokeResponse(response: InvokeResponseForSend) {
856
+ await this.send(MessageType.InvokeResponse, TlvInvokeResponseForSend.encode(response));
857
+ }
800
858
  }
801
859
 
802
860
  export class IncomingInteractionClientMessenger extends InteractionMessenger {
@@ -972,7 +1030,14 @@ export class InteractionClientMessenger extends IncomingInteractionClientMesseng
972
1030
  await this.send(MessageType.SubscribeRequest, request);
973
1031
  }
974
1032
 
975
- async sendInvokeCommand(invokeRequest: InvokeRequest, expectedProcessingTime?: Duration) {
1033
+ /**
1034
+ * Send an invoke command and handle chunked responses.
1035
+ * Returns a combined InvokeResponse with all responses from all chunks, or undefined if suppressResponse
1036
+ */
1037
+ async sendInvokeCommand(
1038
+ invokeRequest: InvokeRequest,
1039
+ expectedProcessingTime?: Duration,
1040
+ ): Promise<InvokeResponse | undefined> {
976
1041
  if (invokeRequest.suppressResponse) {
977
1042
  await this.requestWithSuppressedResponse(
978
1043
  MessageType.InvokeRequest,
@@ -980,16 +1045,49 @@ export class InteractionClientMessenger extends IncomingInteractionClientMesseng
980
1045
  invokeRequest,
981
1046
  expectedProcessingTime,
982
1047
  );
983
- } else {
984
- return await this.request(
985
- MessageType.InvokeRequest,
986
- TlvInvokeRequest,
1048
+ return undefined;
1049
+ }
1050
+
1051
+ // Send invoke request
1052
+ await this.send(MessageType.InvokeRequest, TlvInvokeRequest.encode(invokeRequest), {
1053
+ expectAckOnly: false,
1054
+ expectedProcessingTime,
1055
+ });
1056
+
1057
+ // Receive and accumulate responses from potentially multiple chunks
1058
+ const allInvokeResponses: InvokeResponse["invokeResponses"] = [];
1059
+ let finalResponse: InvokeResponse | undefined;
1060
+
1061
+ while (true) {
1062
+ const responseMessage = await this.nextMessage(
987
1063
  MessageType.InvokeResponse,
988
- TlvInvokeResponse,
989
- invokeRequest,
990
- expectedProcessingTime,
1064
+ { expectedProcessingTime },
1065
+ "InvokeResponse",
991
1066
  );
1067
+ const response = TlvInvokeResponse.decode(responseMessage.payload);
1068
+
1069
+ // Accumulate responses from this chunk
1070
+ if (response.invokeResponses) {
1071
+ allInvokeResponses.push(...response.invokeResponses);
1072
+ }
1073
+
1074
+ // Check if more chunks are coming
1075
+ if (response.moreChunkedMessages) {
1076
+ await this.sendStatus(Status.Success, {
1077
+ multipleMessageInteraction: true,
1078
+ logContext: { for: "InvokeResponse-chunk" },
1079
+ });
1080
+ } else {
1081
+ // This is the final chunk
1082
+ finalResponse = {
1083
+ ...response,
1084
+ invokeResponses: allInvokeResponses,
1085
+ };
1086
+ break;
1087
+ }
992
1088
  }
1089
+
1090
+ return finalResponse;
993
1091
  }
994
1092
 
995
1093
  async sendWriteCommand(writeRequest: WriteRequest) {