@matter/protocol 0.15.0-alpha.0-20250624-e8c89f458 → 0.15.0-alpha.0-20250625-c7634df96

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 (171) hide show
  1. package/dist/cjs/cluster/client/ClusterClient.d.ts.map +1 -1
  2. package/dist/cjs/cluster/client/ClusterClient.js +7 -1
  3. package/dist/cjs/cluster/client/ClusterClient.js.map +1 -1
  4. package/dist/cjs/cluster/client/ClusterClientTypes.d.ts +10 -0
  5. package/dist/cjs/cluster/client/ClusterClientTypes.d.ts.map +1 -1
  6. package/dist/cjs/interaction/InteractionClient.d.ts +8 -1
  7. package/dist/cjs/interaction/InteractionClient.d.ts.map +1 -1
  8. package/dist/cjs/interaction/InteractionClient.js +15 -10
  9. package/dist/cjs/interaction/InteractionClient.js.map +1 -1
  10. package/dist/cjs/interaction/InteractionMessenger.d.ts +0 -1
  11. package/dist/cjs/interaction/InteractionMessenger.d.ts.map +1 -1
  12. package/dist/cjs/interaction/InteractionMessenger.js +0 -3
  13. package/dist/cjs/interaction/InteractionMessenger.js.map +1 -1
  14. package/dist/cjs/interaction/SubscriptionClient.d.ts +1 -1
  15. package/dist/cjs/interaction/SubscriptionClient.d.ts.map +1 -1
  16. package/dist/cjs/interaction/SubscriptionClient.js +1 -1
  17. package/dist/cjs/peer/ControllerCommissioner.d.ts +3 -2
  18. package/dist/cjs/peer/ControllerCommissioner.d.ts.map +1 -1
  19. package/dist/cjs/peer/ControllerCommissioner.js +6 -5
  20. package/dist/cjs/peer/ControllerCommissioner.js.map +1 -1
  21. package/dist/cjs/peer/ControllerCommissioningFlow.d.ts.map +1 -1
  22. package/dist/cjs/peer/ControllerCommissioningFlow.js +81 -52
  23. package/dist/cjs/peer/ControllerCommissioningFlow.js.map +1 -1
  24. package/dist/cjs/peer/PeerSet.d.ts +4 -3
  25. package/dist/cjs/peer/PeerSet.d.ts.map +1 -1
  26. package/dist/cjs/peer/PeerSet.js +10 -9
  27. package/dist/cjs/peer/PeerSet.js.map +1 -1
  28. package/dist/cjs/protocol/ChannelManager.d.ts +2 -2
  29. package/dist/cjs/protocol/ChannelManager.d.ts.map +1 -1
  30. package/dist/cjs/protocol/ChannelManager.js +4 -4
  31. package/dist/cjs/protocol/ChannelManager.js.map +1 -1
  32. package/dist/cjs/protocol/ExchangeManager.d.ts +5 -24
  33. package/dist/cjs/protocol/ExchangeManager.d.ts.map +1 -1
  34. package/dist/cjs/protocol/ExchangeManager.js +12 -55
  35. package/dist/cjs/protocol/ExchangeManager.js.map +1 -1
  36. package/dist/cjs/protocol/ExchangeProvider.d.ts +10 -6
  37. package/dist/cjs/protocol/ExchangeProvider.d.ts.map +1 -1
  38. package/dist/cjs/protocol/ExchangeProvider.js +12 -1
  39. package/dist/cjs/protocol/ExchangeProvider.js.map +1 -1
  40. package/dist/cjs/protocol/MessageChannel.d.ts +52 -0
  41. package/dist/cjs/protocol/MessageChannel.d.ts.map +1 -0
  42. package/dist/cjs/protocol/MessageChannel.js +130 -0
  43. package/dist/cjs/protocol/MessageChannel.js.map +6 -0
  44. package/dist/cjs/protocol/MessageExchange.d.ts +5 -5
  45. package/dist/cjs/protocol/MessageExchange.d.ts.map +1 -1
  46. package/dist/cjs/protocol/MessageExchange.js +34 -78
  47. package/dist/cjs/protocol/MessageExchange.js.map +1 -1
  48. package/dist/cjs/protocol/ProtocolStatusMessage.d.ts +20 -0
  49. package/dist/cjs/protocol/ProtocolStatusMessage.d.ts.map +1 -0
  50. package/dist/cjs/protocol/ProtocolStatusMessage.js +61 -0
  51. package/dist/cjs/protocol/ProtocolStatusMessage.js.map +6 -0
  52. package/dist/cjs/protocol/index.d.ts +2 -0
  53. package/dist/cjs/protocol/index.d.ts.map +1 -1
  54. package/dist/cjs/protocol/index.js +2 -0
  55. package/dist/cjs/protocol/index.js.map +1 -1
  56. package/dist/cjs/securechannel/SecureChannelMessenger.d.ts +4 -5
  57. package/dist/cjs/securechannel/SecureChannelMessenger.d.ts.map +1 -1
  58. package/dist/cjs/securechannel/SecureChannelMessenger.js +7 -8
  59. package/dist/cjs/securechannel/SecureChannelMessenger.js.map +1 -1
  60. package/dist/cjs/securechannel/SecureChannelProtocol.js +2 -2
  61. package/dist/cjs/securechannel/SecureChannelProtocol.js.map +1 -1
  62. package/dist/cjs/securechannel/SecureChannelStatusMessageSchema.d.ts +4 -15
  63. package/dist/cjs/securechannel/SecureChannelStatusMessageSchema.d.ts.map +1 -1
  64. package/dist/cjs/securechannel/SecureChannelStatusMessageSchema.js +3 -21
  65. package/dist/cjs/securechannel/SecureChannelStatusMessageSchema.js.map +1 -1
  66. package/dist/cjs/session/case/CaseClient.d.ts.map +1 -1
  67. package/dist/cjs/session/case/CaseClient.js +1 -1
  68. package/dist/cjs/session/case/CaseClient.js.map +1 -1
  69. package/dist/cjs/session/case/CaseServer.js +2 -2
  70. package/dist/cjs/session/case/CaseServer.js.map +1 -1
  71. package/dist/cjs/session/pase/PaseClient.js +2 -2
  72. package/dist/cjs/session/pase/PaseClient.js.map +1 -1
  73. package/dist/cjs/session/pase/PaseServer.js +1 -1
  74. package/dist/cjs/session/pase/PaseServer.js.map +1 -1
  75. package/dist/esm/cluster/client/ClusterClient.d.ts.map +1 -1
  76. package/dist/esm/cluster/client/ClusterClient.js +7 -1
  77. package/dist/esm/cluster/client/ClusterClient.js.map +1 -1
  78. package/dist/esm/cluster/client/ClusterClientTypes.d.ts +10 -0
  79. package/dist/esm/cluster/client/ClusterClientTypes.d.ts.map +1 -1
  80. package/dist/esm/interaction/InteractionClient.d.ts +8 -1
  81. package/dist/esm/interaction/InteractionClient.d.ts.map +1 -1
  82. package/dist/esm/interaction/InteractionClient.js +15 -10
  83. package/dist/esm/interaction/InteractionClient.js.map +1 -1
  84. package/dist/esm/interaction/InteractionMessenger.d.ts +0 -1
  85. package/dist/esm/interaction/InteractionMessenger.d.ts.map +1 -1
  86. package/dist/esm/interaction/InteractionMessenger.js +0 -3
  87. package/dist/esm/interaction/InteractionMessenger.js.map +1 -1
  88. package/dist/esm/interaction/SubscriptionClient.d.ts +1 -1
  89. package/dist/esm/interaction/SubscriptionClient.d.ts.map +1 -1
  90. package/dist/esm/interaction/SubscriptionClient.js +1 -1
  91. package/dist/esm/peer/ControllerCommissioner.d.ts +3 -2
  92. package/dist/esm/peer/ControllerCommissioner.d.ts.map +1 -1
  93. package/dist/esm/peer/ControllerCommissioner.js +4 -3
  94. package/dist/esm/peer/ControllerCommissioner.js.map +1 -1
  95. package/dist/esm/peer/ControllerCommissioningFlow.d.ts.map +1 -1
  96. package/dist/esm/peer/ControllerCommissioningFlow.js +81 -52
  97. package/dist/esm/peer/ControllerCommissioningFlow.js.map +1 -1
  98. package/dist/esm/peer/PeerSet.d.ts +4 -3
  99. package/dist/esm/peer/PeerSet.d.ts.map +1 -1
  100. package/dist/esm/peer/PeerSet.js +7 -6
  101. package/dist/esm/peer/PeerSet.js.map +1 -1
  102. package/dist/esm/protocol/ChannelManager.d.ts +2 -2
  103. package/dist/esm/protocol/ChannelManager.d.ts.map +1 -1
  104. package/dist/esm/protocol/ChannelManager.js +2 -2
  105. package/dist/esm/protocol/ChannelManager.js.map +1 -1
  106. package/dist/esm/protocol/ExchangeManager.d.ts +5 -24
  107. package/dist/esm/protocol/ExchangeManager.d.ts.map +1 -1
  108. package/dist/esm/protocol/ExchangeManager.js +13 -56
  109. package/dist/esm/protocol/ExchangeManager.js.map +1 -1
  110. package/dist/esm/protocol/ExchangeProvider.d.ts +10 -6
  111. package/dist/esm/protocol/ExchangeProvider.d.ts.map +1 -1
  112. package/dist/esm/protocol/ExchangeProvider.js +12 -1
  113. package/dist/esm/protocol/ExchangeProvider.js.map +1 -1
  114. package/dist/esm/protocol/MessageChannel.d.ts +52 -0
  115. package/dist/esm/protocol/MessageChannel.d.ts.map +1 -0
  116. package/dist/esm/protocol/MessageChannel.js +110 -0
  117. package/dist/esm/protocol/MessageChannel.js.map +6 -0
  118. package/dist/esm/protocol/MessageExchange.d.ts +5 -5
  119. package/dist/esm/protocol/MessageExchange.d.ts.map +1 -1
  120. package/dist/esm/protocol/MessageExchange.js +39 -83
  121. package/dist/esm/protocol/MessageExchange.js.map +1 -1
  122. package/dist/esm/protocol/ProtocolStatusMessage.d.ts +20 -0
  123. package/dist/esm/protocol/ProtocolStatusMessage.d.ts.map +1 -0
  124. package/dist/esm/protocol/ProtocolStatusMessage.js +41 -0
  125. package/dist/esm/protocol/ProtocolStatusMessage.js.map +6 -0
  126. package/dist/esm/protocol/index.d.ts +2 -0
  127. package/dist/esm/protocol/index.d.ts.map +1 -1
  128. package/dist/esm/protocol/index.js +2 -0
  129. package/dist/esm/protocol/index.js.map +1 -1
  130. package/dist/esm/securechannel/SecureChannelMessenger.d.ts +4 -5
  131. package/dist/esm/securechannel/SecureChannelMessenger.d.ts.map +1 -1
  132. package/dist/esm/securechannel/SecureChannelMessenger.js +8 -14
  133. package/dist/esm/securechannel/SecureChannelMessenger.js.map +1 -1
  134. package/dist/esm/securechannel/SecureChannelProtocol.js +3 -3
  135. package/dist/esm/securechannel/SecureChannelProtocol.js.map +1 -1
  136. package/dist/esm/securechannel/SecureChannelStatusMessageSchema.d.ts +4 -15
  137. package/dist/esm/securechannel/SecureChannelStatusMessageSchema.d.ts.map +1 -1
  138. package/dist/esm/securechannel/SecureChannelStatusMessageSchema.js +4 -22
  139. package/dist/esm/securechannel/SecureChannelStatusMessageSchema.js.map +1 -1
  140. package/dist/esm/session/case/CaseClient.d.ts.map +1 -1
  141. package/dist/esm/session/case/CaseClient.js +2 -2
  142. package/dist/esm/session/case/CaseClient.js.map +1 -1
  143. package/dist/esm/session/case/CaseServer.js +3 -3
  144. package/dist/esm/session/case/CaseServer.js.map +1 -1
  145. package/dist/esm/session/pase/PaseClient.js +3 -3
  146. package/dist/esm/session/pase/PaseClient.js.map +1 -1
  147. package/dist/esm/session/pase/PaseServer.js +2 -2
  148. package/dist/esm/session/pase/PaseServer.js.map +1 -1
  149. package/package.json +6 -6
  150. package/src/cluster/client/ClusterClient.ts +8 -1
  151. package/src/cluster/client/ClusterClientTypes.ts +12 -0
  152. package/src/interaction/InteractionClient.ts +29 -16
  153. package/src/interaction/InteractionMessenger.ts +0 -4
  154. package/src/interaction/SubscriptionClient.ts +2 -2
  155. package/src/peer/ControllerCommissioner.ts +4 -3
  156. package/src/peer/ControllerCommissioningFlow.ts +96 -57
  157. package/src/peer/PeerSet.ts +7 -6
  158. package/src/protocol/ChannelManager.ts +3 -3
  159. package/src/protocol/ExchangeManager.ts +18 -67
  160. package/src/protocol/ExchangeProvider.ts +20 -6
  161. package/src/protocol/MessageChannel.ts +163 -0
  162. package/src/protocol/MessageExchange.ts +40 -119
  163. package/src/protocol/ProtocolStatusMessage.ts +51 -0
  164. package/src/protocol/index.ts +2 -0
  165. package/src/securechannel/SecureChannelMessenger.ts +11 -18
  166. package/src/securechannel/SecureChannelProtocol.ts +3 -3
  167. package/src/securechannel/SecureChannelStatusMessageSchema.ts +5 -31
  168. package/src/session/case/CaseClient.ts +2 -2
  169. package/src/session/case/CaseServer.ts +3 -3
  170. package/src/session/pase/PaseClient.ts +3 -3
  171. package/src/session/pase/PaseServer.ts +2 -2
@@ -4,8 +4,10 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
+ import { Message, MessageCodec, PacketHeader, SessionType } from "#codec/MessageCodec.js";
7
8
  import {
8
9
  AsyncObservable,
10
+ createPromise,
9
11
  CRYPTO_AEAD_MIC_LENGTH_BYTES,
10
12
  DataReadQueue,
11
13
  Diagnostic,
@@ -16,9 +18,16 @@ import {
16
18
  NoResponseTimeoutError,
17
19
  Time,
18
20
  Timer,
19
- createPromise,
20
21
  } from "#general";
21
- import { GroupSession } from "#session/index.js";
22
+ import { MessageChannel, MRP } from "#protocol/MessageChannel.js";
23
+ import { SecureChannelProtocol } from "#securechannel/SecureChannelProtocol.js";
24
+ import { GroupSession } from "#session/GroupSession.js";
25
+ import {
26
+ SESSION_ACTIVE_INTERVAL_MS,
27
+ SESSION_ACTIVE_THRESHOLD_MS,
28
+ SESSION_IDLE_INTERVAL_MS,
29
+ SessionParameters,
30
+ } from "#session/Session.js";
22
31
  import {
23
32
  GroupId,
24
33
  NodeId,
@@ -27,15 +36,7 @@ import {
27
36
  StatusCode,
28
37
  StatusResponseError,
29
38
  } from "#types";
30
- import { Message, MessageCodec, PacketHeader, SessionType } from "../codec/MessageCodec.js";
31
- import { SecureChannelProtocol } from "../securechannel/SecureChannelProtocol.js";
32
- import {
33
- SESSION_ACTIVE_INTERVAL_MS,
34
- SESSION_ACTIVE_THRESHOLD_MS,
35
- SESSION_IDLE_INTERVAL_MS,
36
- SessionParameters,
37
- } from "../session/Session.js";
38
- import { ChannelNotConnectedError, MessageChannel } from "./ExchangeManager.js";
39
+ import { ChannelNotConnectedError } from "./ExchangeManager.js";
39
40
 
40
41
  const logger = Logger.get("MessageExchange");
41
42
 
@@ -86,27 +87,6 @@ export type ExchangeSendOptions = {
86
87
  logContext?: ExchangeLogContext;
87
88
  };
88
89
 
89
- /**
90
- * The maximum number of transmission attempts for a given reliable message. The sender MAY choose this value as it
91
- * sees fit.
92
- */
93
- const MRP_MAX_TRANSMISSIONS = 5;
94
-
95
- /** The base number for the exponential backoff equation. */
96
- const MRP_BACKOFF_BASE = 1.6;
97
-
98
- /** The scaler for random jitter in the backoff equation. */
99
- const MRP_BACKOFF_JITTER = 0.25;
100
-
101
- /** The scaler margin increase to backoff over the peer sleepy interval. */
102
- const MRP_BACKOFF_MARGIN = 1.1;
103
-
104
- /** The number of retransmissions before transitioning from linear to exponential backoff. */
105
- const MRP_BACKOFF_THRESHOLD = 1;
106
-
107
- /** @see {@link MatterSpecification.v12.Core}, section 4.11.8 */
108
- const MRP_STANDALONE_ACK_TIMEOUT_MS = 200;
109
-
110
90
  /**
111
91
  * Default expected processing time for a messages in milliseconds. The value is derived from kExpectedIMProcessingTime
112
92
  * from chip implementation. This is basically the default used with different names, also kExpectedLowProcessingTime or
@@ -114,12 +94,6 @@ const MRP_STANDALONE_ACK_TIMEOUT_MS = 200;
114
94
  */
115
95
  export const DEFAULT_EXPECTED_PROCESSING_TIME_MS = 2_000;
116
96
 
117
- /**
118
- * The buffer time in milliseconds to add to the peer response time to also consider network delays and other factors.
119
- * TODO: This is a pure guess and should be adjusted in the future.
120
- */
121
- const PEER_RESPONSE_TIME_BUFFER_MS = 5_000;
122
-
123
97
  /**
124
98
  * Message size overhead of a Matter message:
125
99
  * 26 (Matter Message Header) + 12 (Matter Payload Header) taken from https://github.com/project-chip/connectedhomeip/blob/2d97cda23024e72f36216900ca667bf1a0d9499f/src/system/SystemConfig.h#L327
@@ -173,10 +147,9 @@ export class MessageExchange {
173
147
  readonly #activeIntervalMs: number;
174
148
  readonly #idleIntervalMs: number;
175
149
  readonly #activeThresholdMs: number;
176
- readonly #maxTransmissions: number;
177
150
  readonly #messagesQueue = new DataReadQueue<Message>();
178
151
  #receivedMessageToAck: Message | undefined;
179
- #receivedMessageAckTimer = Time.getTimer("Ack receipt timeout", MRP_STANDALONE_ACK_TIMEOUT_MS, () => {
152
+ #receivedMessageAckTimer = Time.getTimer("Ack receipt timeout", MRP.STANDALONE_ACK_TIMEOUT_MS, () => {
180
153
  if (this.#receivedMessageToAck !== undefined) {
181
154
  const messageToAck = this.#receivedMessageToAck;
182
155
  this.#receivedMessageToAck = undefined;
@@ -203,7 +176,6 @@ export class MessageExchange {
203
176
  readonly #protocolId: number;
204
177
  readonly #closed = AsyncObservable<[]>();
205
178
  readonly #closing = AsyncObservable<[]>();
206
- readonly #useMRP: boolean;
207
179
 
208
180
  constructor(
209
181
  readonly context: MessageExchangeContext,
@@ -227,10 +199,7 @@ export class MessageExchange {
227
199
  this.#activeIntervalMs = activeIntervalMs ?? SESSION_ACTIVE_INTERVAL_MS;
228
200
  this.#idleIntervalMs = idleIntervalMs ?? SESSION_IDLE_INTERVAL_MS;
229
201
  this.#activeThresholdMs = activeThresholdMs ?? SESSION_ACTIVE_THRESHOLD_MS;
230
- this.#maxTransmissions = MRP_MAX_TRANSMISSIONS;
231
202
 
232
- // When the session is supporting MRP and the channel is not reliable, use MRP handling
233
- this.#useMRP = session.supportsMRP && !channel.isReliable;
234
203
  this.#used = !isInitiator; // If we are the initiator then exchange was not used yet, so track it
235
204
 
236
205
  logger.debug(
@@ -244,9 +213,9 @@ export class MessageExchange {
244
213
  SAT: this.#activeThresholdMs,
245
214
  SAI: this.#activeIntervalMs,
246
215
  SII: this.#idleIntervalMs,
247
- maxTrans: this.#maxTransmissions,
216
+ maxTrans: MRP.MAX_TRANSMISSIONS,
248
217
  exchangeFlags: Diagnostic.asFlags({
249
- MRP: this.#useMRP,
218
+ MRP: this.channel.usesMrp,
250
219
  I: this.isInitiator,
251
220
  }),
252
221
  }),
@@ -297,7 +266,7 @@ export class MessageExchange {
297
266
  packetHeader: { messageId },
298
267
  payloadHeader: { requiresAck },
299
268
  } = message;
300
- if (!requiresAck || !this.#useMRP) return;
269
+ if (!requiresAck || !this.channel.usesMrp) return;
301
270
 
302
271
  await this.send(SecureMessageType.StandaloneAck, new Uint8Array(0), { includeAcknowledgeMessageId: messageId });
303
272
  }
@@ -306,7 +275,7 @@ export class MessageExchange {
306
275
  logger.debug("Message «", MessageCodec.messageDiagnostics(message, { duplicate }));
307
276
 
308
277
  // Adjust the incoming message when ack was required, but this exchange does not use it to skip all relevant logic
309
- if (message.payloadHeader.requiresAck && !this.#useMRP) {
278
+ if (message.payloadHeader.requiresAck && !this.channel.usesMrp) {
310
279
  logger.debug("Ignoring ack-required flag because MRP is not used for this exchange");
311
280
  message.payloadHeader.requiresAck = false;
312
281
  }
@@ -384,7 +353,7 @@ export class MessageExchange {
384
353
  }
385
354
 
386
355
  async send(messageType: number, payload: Uint8Array, options?: ExchangeSendOptions) {
387
- if (options?.requiresAck && !this.#useMRP) {
356
+ if (options?.requiresAck && !this.channel.usesMrp) {
388
357
  options.requiresAck = false;
389
358
  }
390
359
 
@@ -396,11 +365,11 @@ export class MessageExchange {
396
365
  includeAcknowledgeMessageId,
397
366
  logContext,
398
367
  } = options ?? {};
399
- if (!this.#useMRP && includeAcknowledgeMessageId !== undefined) {
368
+ if (!this.channel.usesMrp && includeAcknowledgeMessageId !== undefined) {
400
369
  throw new InternalError("Cannot include an acknowledge message ID when MRP is not used");
401
370
  }
402
371
  if (messageType === SecureMessageType.StandaloneAck) {
403
- if (!this.#useMRP) {
372
+ if (!this.channel.usesMrp) {
404
373
  return;
405
374
  }
406
375
  if (requiresAck) {
@@ -414,7 +383,7 @@ export class MessageExchange {
414
383
  this.session.notifyActivity(false);
415
384
 
416
385
  let ackedMessageId = includeAcknowledgeMessageId;
417
- if (ackedMessageId === undefined && this.#useMRP) {
386
+ if (ackedMessageId === undefined && this.channel.usesMrp) {
418
387
  ackedMessageId = this.#receivedMessageToAck?.packetHeader.messageId;
419
388
  if (ackedMessageId !== undefined) {
420
389
  this.#receivedMessageAckTimer.stop();
@@ -465,7 +434,7 @@ export class MessageExchange {
465
434
  messageType === SecureMessageType.StandaloneAck ? SECURE_CHANNEL_PROTOCOL_ID : this.#protocolId,
466
435
  messageType,
467
436
  isInitiatorMessage: this.isInitiator,
468
- requiresAck: requiresAck ?? (this.#useMRP && messageType !== SecureMessageType.StandaloneAck),
437
+ requiresAck: requiresAck ?? (this.channel.usesMrp && messageType !== SecureMessageType.StandaloneAck),
469
438
  ackedMessageId,
470
439
  hasSecuredExtension: false,
471
440
  },
@@ -473,11 +442,11 @@ export class MessageExchange {
473
442
  };
474
443
 
475
444
  let ackPromise: Promise<Message> | undefined;
476
- if (this.#useMRP && message.payloadHeader.requiresAck && !disableMrpLogic) {
445
+ if (this.channel.usesMrp && message.payloadHeader.requiresAck && !disableMrpLogic) {
477
446
  this.#sentMessageToAck = message;
478
447
  this.#retransmissionTimer = Time.getTimer(
479
448
  `Message retransmission ${message.packetHeader.messageId}`,
480
- this.#getResubmissionBackOffTime(0),
449
+ this.channel.getMrpResubmissionBackOffTime(0),
481
450
  () => this.#retransmitMessage(message, expectedProcessingTimeMs),
482
451
  );
483
452
  const { promise, resolver, rejecter } = createPromise<Message>();
@@ -512,81 +481,33 @@ export class MessageExchange {
512
481
  } else if (this.#messagesQueue.size > 0) {
513
482
  timeout = 0; // If we have messages in the queue, we can return them immediately
514
483
  } else {
515
- switch (this.channel.type) {
516
- case "tcp":
517
- // TCP uses 30s timeout according to chip sdk implementation, so do the same
518
- timeout = 30_000;
519
- break;
520
- case "udp":
521
- // UDP normally uses MRP, if not we have Group communication which normally have no responses
522
- if (!this.#useMRP) {
523
- throw new MatterFlowError(
524
- "No response expected for this message exchange because UDP and no MRP.",
525
- );
526
- }
527
- const { expectedProcessingTimeMs } = options ?? {};
528
- timeout = this.calculateMaximumPeerResponseTime(expectedProcessingTimeMs);
529
- break;
530
- case "ble":
531
- // chip sdk uses BTP_ACK_TIMEOUT_MS which is wrong in my eyes, so we use static 30s as like TCP here
532
- timeout = 30_000;
533
- break;
534
- default:
535
- throw new MatterFlowError(
536
- `Can not calculate expected timeout for unknown channel type: ${this.channel.type}`,
537
- );
538
- }
539
- timeout += PEER_RESPONSE_TIME_BUFFER_MS;
484
+ timeout = this.channel.calculateMaximumPeerResponseTimeMs(
485
+ this.context.localSessionParameters,
486
+ options?.expectedProcessingTimeMs,
487
+ );
540
488
  }
541
489
  return this.#messagesQueue.read(timeout);
542
490
  }
543
491
 
544
- /**
545
- * Calculates the backoff time for a resubmission based on the current retransmission count.
546
- * If no session parameters are provided, the parameters of the current session are used.
547
- * If session parameters are provided, the method can be used to calculate the maximum backoff time for the other
548
- * side of the exchange.
549
- *
550
- * @see {@link MatterSpecification.v10.Core}, section 4.11.2.1
551
- */
552
- #getResubmissionBackOffTime(retransmissionCount: number, sessionParameters?: SessionParameters) {
553
- const { activeIntervalMs, idleIntervalMs } = sessionParameters ?? {
554
- activeIntervalMs: this.#activeIntervalMs,
555
- idleIntervalMs: this.#idleIntervalMs,
556
- };
557
- const baseInterval =
558
- sessionParameters !== undefined || this.session.isPeerActive() ? activeIntervalMs : idleIntervalMs;
559
- return Math.floor(
560
- MRP_BACKOFF_MARGIN *
561
- baseInterval *
562
- Math.pow(MRP_BACKOFF_BASE, Math.max(0, retransmissionCount - MRP_BACKOFF_THRESHOLD)) *
563
- (1 + (sessionParameters !== undefined ? 1 : Math.random()) * MRP_BACKOFF_JITTER),
492
+ calculateMaximumPeerResponseTimeMs(expectedProcessingTimeMs = DEFAULT_EXPECTED_PROCESSING_TIME_MS) {
493
+ return this.channel.calculateMaximumPeerResponseTimeMs(
494
+ this.context.localSessionParameters,
495
+ expectedProcessingTimeMs,
564
496
  );
565
497
  }
566
498
 
567
- calculateMaximumPeerResponseTime(expectedProcessingTimeMs = DEFAULT_EXPECTED_PROCESSING_TIME_MS) {
568
- // We use the expected processing time and deduct the time we already waited since last resubmission
569
- let finalWaitTime = expectedProcessingTimeMs;
570
-
571
- // and then add the time the other side needs for a full resubmission cycle under the assumption we are active
572
- for (let i = 0; i < this.#maxTransmissions; i++) {
573
- finalWaitTime += this.#getResubmissionBackOffTime(i, this.context.localSessionParameters);
574
- }
575
-
576
- // TODO: Also add any network latency buffer, for now lets consider it's included in the processing time already
577
- return finalWaitTime;
578
- }
579
-
580
499
  #retransmitMessage(message: Message, expectedProcessingTimeMs?: number) {
581
500
  this.#retransmissionCounter++;
582
- if (this.#retransmissionCounter >= this.#maxTransmissions) {
501
+ if (this.#retransmissionCounter >= MRP.MAX_TRANSMISSIONS) {
583
502
  // Ok all 4 resubmissions are done, but we need to wait a bit longer because of processing time and
584
503
  // the resubmissions from the other side
585
504
  if (expectedProcessingTimeMs !== undefined) {
586
505
  // We already have waited after the last message was sent, so deduct this time from the final wait time
587
506
  const finalWaitTime =
588
- this.calculateMaximumPeerResponseTime(expectedProcessingTimeMs) -
589
- (this.#retransmissionTimer?.intervalMs ?? 0);
507
+ this.channel.calculateMaximumPeerResponseTimeMs(
508
+ this.context.localSessionParameters,
509
+ expectedProcessingTimeMs,
510
+ ) - (this.#retransmissionTimer?.intervalMs ?? 0);
590
511
  if (finalWaitTime > 0) {
591
512
  this.#retransmissionCounter--; // We will not resubmit the message again
592
513
  logger.debug(
@@ -620,7 +541,7 @@ export class MessageExchange {
620
541
  if (this.#retransmissionCounter === 1) {
621
542
  this.context.resubmissionStarted();
622
543
  }
623
- const resubmissionBackoffTime = this.#getResubmissionBackOffTime(this.#retransmissionCounter);
544
+ const resubmissionBackoffTime = this.channel.getMrpResubmissionBackOffTime(this.#retransmissionCounter);
624
545
  logger.debug(
625
546
  `Resubmit message ${message.packetHeader.messageId} (retransmission attempt ${this.#retransmissionCounter}, backoff time ${resubmissionBackoffTime}ms))`,
626
547
  );
@@ -742,8 +663,8 @@ export class MessageExchange {
742
663
  // We might wait a bit longer then needed but because this is mainly a failsafe mechanism it is acceptable.
743
664
  // in normal case this timer is cancelled before it triggers when all retries are done.
744
665
  let maxResubmissionTime = 0;
745
- for (let i = this.#retransmissionCounter; i <= this.#maxTransmissions; i++) {
746
- maxResubmissionTime += this.#getResubmissionBackOffTime(i);
666
+ for (let i = this.#retransmissionCounter; i <= MRP.MAX_TRANSMISSIONS; i++) {
667
+ maxResubmissionTime += this.channel.getMrpResubmissionBackOffTime(i);
747
668
  }
748
669
  this.#closeTimer = Time.getTimer(
749
670
  `Message exchange cleanup ${this.session.name} / ${this.#exchangeId}`,
@@ -0,0 +1,51 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2025 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import { DataReader, DataWriter, Endian } from "#general";
8
+ import { GeneralStatusCode, Schema } from "#types";
9
+
10
+ export type ProtocolStatusMessage<T> = {
11
+ generalStatus: GeneralStatusCode;
12
+ protocolId: number;
13
+ protocolStatus: T;
14
+ protocolData?: Uint8Array;
15
+ };
16
+
17
+ export abstract class ProtocolStatusMessageSchema<T extends ProtocolStatusMessage<any>> extends Schema<T, Uint8Array> {
18
+ #protocolId: number;
19
+ #protocolSpecificDataAllowed: boolean;
20
+
21
+ constructor(protocolId: number, protocolSpecificDataAllowed = true) {
22
+ super();
23
+ this.#protocolId = protocolId;
24
+ this.#protocolSpecificDataAllowed = protocolSpecificDataAllowed;
25
+ }
26
+
27
+ override encode(message: Omit<T, "protocolId">): Uint8Array {
28
+ return super.encode({ ...message, protocolId: this.#protocolId } as T);
29
+ }
30
+
31
+ encodeInternal({ generalStatus, protocolStatus, protocolId, protocolData }: T) {
32
+ const writer = new DataWriter(Endian.Little);
33
+ writer.writeUInt16(generalStatus);
34
+ writer.writeUInt32(protocolId);
35
+ writer.writeUInt16(protocolStatus);
36
+ if (this.#protocolSpecificDataAllowed && protocolData !== undefined && protocolData.length > 0) {
37
+ writer.writeByteArray(protocolData);
38
+ }
39
+ return writer.toByteArray();
40
+ }
41
+
42
+ decodeInternal(bytes: Uint8Array) {
43
+ const reader = new DataReader(bytes, Endian.Little);
44
+ const generalStatus = reader.readUInt16();
45
+ const protocolId = reader.readUInt32();
46
+ const protocolStatus = reader.readUInt16();
47
+ const remainingBytes = reader.remainingBytesCount > 0 ? reader.remainingBytes : undefined;
48
+
49
+ return { generalStatus, protocolId, protocolStatus, remainingBytes } as unknown as T;
50
+ }
51
+ }
@@ -9,7 +9,9 @@ export * from "./DeviceAdvertiser.js";
9
9
  export * from "./DeviceCommissioner.js";
10
10
  export * from "./ExchangeManager.js";
11
11
  export * from "./ExchangeProvider.js";
12
+ export * from "./MessageChannel.js";
12
13
  export * from "./MessageCounter.js";
13
14
  export * from "./MessageExchange.js";
14
15
  export * from "./MessageReceptionState.js";
15
16
  export * from "./ProtocolHandler.js";
17
+ export * from "./ProtocolStatusMessage.js";
@@ -5,13 +5,7 @@
5
5
  */
6
6
 
7
7
  import { Diagnostic, MatterError, UnexpectedDataError } from "#general";
8
- import {
9
- GeneralStatusCode,
10
- ProtocolStatusCode,
11
- SECURE_CHANNEL_PROTOCOL_ID,
12
- SecureMessageType,
13
- TlvSchema,
14
- } from "#types";
8
+ import { GeneralStatusCode, SecureChannelStatusCode, SecureMessageType, TlvSchema } from "#types";
15
9
  import { Message } from "../codec/MessageCodec.js";
16
10
  import { ExchangeSendOptions, MessageExchange } from "../protocol/MessageExchange.js";
17
11
  import { TlvSecureChannelStatusMessage } from "./SecureChannelStatusMessageSchema.js";
@@ -21,10 +15,10 @@ export class ChannelStatusResponseError extends MatterError {
21
15
  public constructor(
22
16
  message: string,
23
17
  public readonly generalStatusCode: GeneralStatusCode,
24
- public readonly protocolStatusCode: ProtocolStatusCode,
18
+ public readonly protocolStatusCode: SecureChannelStatusCode,
25
19
  ) {
26
20
  super(
27
- `(${GeneralStatusCode[generalStatusCode]} (${generalStatusCode}) / ${ProtocolStatusCode[protocolStatusCode]} (${protocolStatusCode})) ${message}`,
21
+ `(${GeneralStatusCode[generalStatusCode]} (${generalStatusCode}) / ${SecureChannelStatusCode[protocolStatusCode]} (${protocolStatusCode})) ${message}`,
28
22
  );
29
23
  }
30
24
  }
@@ -125,16 +119,16 @@ export class SecureChannelMessenger {
125
119
  return payload;
126
120
  }
127
121
 
128
- sendError(code: ProtocolStatusCode) {
129
- return this.sendStatusReport(GeneralStatusCode.Failure, code);
122
+ sendError(code: SecureChannelStatusCode) {
123
+ return this.#sendStatusReport(GeneralStatusCode.Failure, code);
130
124
  }
131
125
 
132
126
  sendSuccess() {
133
- return this.sendStatusReport(GeneralStatusCode.Success, ProtocolStatusCode.Success);
127
+ return this.#sendStatusReport(GeneralStatusCode.Success, SecureChannelStatusCode.Success);
134
128
  }
135
129
 
136
130
  sendCloseSession() {
137
- return this.sendStatusReport(GeneralStatusCode.Success, ProtocolStatusCode.CloseSession, false);
131
+ return this.#sendStatusReport(GeneralStatusCode.Success, SecureChannelStatusCode.CloseSession, false);
138
132
  }
139
133
 
140
134
  getChannelName() {
@@ -145,23 +139,22 @@ export class SecureChannelMessenger {
145
139
  await this.exchange.close();
146
140
  }
147
141
 
148
- private async sendStatusReport(
142
+ async #sendStatusReport(
149
143
  generalStatus: GeneralStatusCode,
150
- protocolStatus: ProtocolStatusCode,
144
+ protocolStatus: SecureChannelStatusCode,
151
145
  requiresAck?: boolean,
152
146
  ) {
153
147
  await this.exchange.send(
154
148
  SecureMessageType.StatusReport,
155
149
  TlvSecureChannelStatusMessage.encode({
156
150
  generalStatus,
157
- protocolId: SECURE_CHANNEL_PROTOCOL_ID,
158
151
  protocolStatus,
159
152
  }),
160
153
  {
161
154
  requiresAck,
162
155
  logContext: {
163
156
  generalStatus: GeneralStatusCode[generalStatus] ?? Diagnostic.hex(generalStatus),
164
- protocolStatus: ProtocolStatusCode[protocolStatus] ?? Diagnostic.hex(protocolStatus),
157
+ protocolStatus: SecureChannelStatusCode[protocolStatus] ?? Diagnostic.hex(protocolStatus),
165
158
  },
166
159
  },
167
160
  );
@@ -182,7 +175,7 @@ export class SecureChannelMessenger {
182
175
  protocolStatus,
183
176
  );
184
177
  }
185
- if (protocolStatus !== ProtocolStatusCode.Success) {
178
+ if (protocolStatus !== SecureChannelStatusCode.Success) {
186
179
  throw new ChannelStatusResponseError(
187
180
  `Received general success status, but protocol status is not Success${logHint ? ` (${logHint})` : ""}`,
188
181
  generalStatus,
@@ -10,8 +10,8 @@ import { ExchangeManager } from "#protocol/ExchangeManager.js";
10
10
  import { SessionManager } from "#session/SessionManager.js";
11
11
  import {
12
12
  GeneralStatusCode,
13
- ProtocolStatusCode,
14
13
  SECURE_CHANNEL_PROTOCOL_ID,
14
+ SecureChannelStatusCode,
15
15
  SecureMessageType,
16
16
  StatusCode,
17
17
  StatusResponseError,
@@ -70,7 +70,7 @@ export class StatusReportOnlySecureChannelProtocol implements ProtocolHandler {
70
70
  protocolStatus,
71
71
  );
72
72
  }
73
- if (protocolStatus !== ProtocolStatusCode.CloseSession) {
73
+ if (protocolStatus !== SecureChannelStatusCode.CloseSession) {
74
74
  throw new ChannelStatusResponseError(
75
75
  `Received general success status, but protocol status is not CloseSession`,
76
76
  generalStatus,
@@ -131,7 +131,7 @@ export class SecureChannelProtocol extends StatusReportOnlySecureChannelProtocol
131
131
  // Cleaner to return an error (ok for chip-tool as it seems)?
132
132
  // Formally we should not respond at all which leads to retries and such
133
133
  const messenger = new SecureChannelMessenger(exchange);
134
- await messenger.sendError(ProtocolStatusCode.InvalidParam);
134
+ await messenger.sendError(SecureChannelStatusCode.InvalidParam);
135
135
  await messenger.close(); // also closes exchange
136
136
  return;
137
137
  }
@@ -4,37 +4,11 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
- import { DataReader, DataWriter, Endian } from "#general";
8
- import { GeneralStatusCode, ProtocolStatusCode, Schema } from "#types";
7
+ import { ProtocolStatusMessage, ProtocolStatusMessageSchema } from "#protocol/ProtocolStatusMessage.js";
8
+ import { SECURE_CHANNEL_PROTOCOL_ID, SecureChannelStatusCode } from "#types";
9
9
 
10
- export type StatusMessage = {
11
- generalStatus: GeneralStatusCode;
12
- protocolId: number;
13
- protocolStatus: ProtocolStatusCode;
14
- protocolData?: Uint8Array;
15
- };
10
+ export type SecureChannelStatusMessage = ProtocolStatusMessage<SecureChannelStatusCode>;
16
11
 
17
- export class SecureChannelStatusMessageSchema extends Schema<StatusMessage, Uint8Array> {
18
- encodeInternal({ generalStatus, protocolId, protocolStatus, protocolData }: StatusMessage) {
19
- const writer = new DataWriter(Endian.Little);
20
- writer.writeUInt16(generalStatus);
21
- writer.writeUInt32(protocolId);
22
- writer.writeUInt16(protocolStatus);
23
- if (protocolData !== undefined && protocolData.length > 0) {
24
- writer.writeByteArray(protocolData);
25
- }
26
- return writer.toByteArray();
27
- }
12
+ export class SecureChannelStatusMessageSchema extends ProtocolStatusMessageSchema<SecureChannelStatusMessage> {}
28
13
 
29
- decodeInternal(bytes: Uint8Array) {
30
- const reader = new DataReader(bytes, Endian.Little);
31
- const generalStatus = reader.readUInt16();
32
- const protocolId = reader.readUInt32();
33
- const protocolStatus = reader.readUInt16();
34
- const remainingBytes = reader.remainingBytesCount > 0 ? reader.remainingBytes : undefined;
35
-
36
- return { generalStatus, protocolId, protocolStatus, remainingBytes };
37
- }
38
- }
39
-
40
- export const TlvSecureChannelStatusMessage = new SecureChannelStatusMessageSchema();
14
+ export const TlvSecureChannelStatusMessage = new SecureChannelStatusMessageSchema(SECURE_CHANNEL_PROTOCOL_ID);
@@ -8,7 +8,7 @@ import { Icac, Noc } from "#certificate/index.js";
8
8
  import { Bytes, Logger, PublicKey, UnexpectedDataError } from "#general";
9
9
  import { ChannelStatusResponseError } from "#securechannel/index.js";
10
10
  import { SessionManager } from "#session/SessionManager.js";
11
- import { NodeId, ProtocolStatusCode } from "#types";
11
+ import { NodeId, SecureChannelStatusCode } from "#types";
12
12
  import { Fabric } from "../../fabric/Fabric.js";
13
13
  import { MessageExchange } from "../../protocol/MessageExchange.js";
14
14
  import {
@@ -42,7 +42,7 @@ export class CaseClient {
42
42
  return await this.#doPair(messenger, exchange, fabric, peerNodeId);
43
43
  } catch (error) {
44
44
  if (!(error instanceof ChannelStatusResponseError)) {
45
- await messenger.sendError(ProtocolStatusCode.InvalidParam);
45
+ await messenger.sendError(SecureChannelStatusCode.InvalidParam);
46
46
  }
47
47
  throw error;
48
48
  }
@@ -8,7 +8,7 @@ import { Noc } from "#certificate/index.js";
8
8
  import { Bytes, Crypto, CryptoDecryptError, Logger, PublicKey, UnexpectedDataError } from "#general";
9
9
  import { TlvSessionParameters } from "#session/pase/PaseMessages.js";
10
10
  import { ResumptionRecord, SessionManager } from "#session/SessionManager.js";
11
- import { NodeId, ProtocolStatusCode, SECURE_CHANNEL_PROTOCOL_ID, TypeFromSchema } from "#types";
11
+ import { NodeId, SECURE_CHANNEL_PROTOCOL_ID, SecureChannelStatusCode, TypeFromSchema } from "#types";
12
12
  import { FabricManager, FabricNotFoundError } from "../../fabric/FabricManager.js";
13
13
  import { MessageExchange } from "../../protocol/MessageExchange.js";
14
14
  import { ProtocolHandler } from "../../protocol/ProtocolHandler.js";
@@ -51,11 +51,11 @@ export class CaseServer implements ProtocolHandler {
51
51
  logger.error("An error occurred during the commissioning", error);
52
52
 
53
53
  if (error instanceof FabricNotFoundError) {
54
- await messenger.sendError(ProtocolStatusCode.NoSharedTrustRoots);
54
+ await messenger.sendError(SecureChannelStatusCode.NoSharedTrustRoots);
55
55
  }
56
56
  // If we received a ChannelStatusResponseError we do not need to send one back, so just cancel pairing
57
57
  else if (!(error instanceof ChannelStatusResponseError)) {
58
- await messenger.sendError(ProtocolStatusCode.InvalidParam);
58
+ await messenger.sendError(SecureChannelStatusCode.InvalidParam);
59
59
  }
60
60
  } finally {
61
61
  // Destroy the unsecure session used to establish the secure Case session
@@ -6,7 +6,7 @@
6
6
 
7
7
  import { Bytes, Crypto, ec, Logger, PbkdfParameters, Spake2p, UnexpectedDataError } from "#general";
8
8
  import { SessionManager } from "#session/SessionManager.js";
9
- import { CommissioningOptions, NodeId, ProtocolStatusCode } from "#types";
9
+ import { CommissioningOptions, NodeId, SecureChannelStatusCode } from "#types";
10
10
  import { MessageExchange } from "../../protocol/MessageExchange.js";
11
11
  import { SessionParameters } from "../Session.js";
12
12
  import { DEFAULT_PASSCODE_ID, PaseClientMessenger, SPAKE_CONTEXT } from "./PaseMessenger.js";
@@ -70,7 +70,7 @@ export class PaseClient {
70
70
  if (pbkdfParameters === undefined) {
71
71
  // Sending this error is not defined in the specs and should normally never happen, but better inform device
72
72
  // that we cancel the pairing
73
- await messenger.sendError(ProtocolStatusCode.InvalidParam);
73
+ await messenger.sendError(SecureChannelStatusCode.InvalidParam);
74
74
  throw new UnexpectedDataError("Missing requested PbkdfParameters in the response. Commissioning failed.");
75
75
  }
76
76
 
@@ -94,7 +94,7 @@ export class PaseClient {
94
94
  const { y: Y, verifier } = await messenger.readPasePake2();
95
95
  const { Ke, hAY, hBX } = await spake2p.computeSecretAndVerifiersFromY(w1, X, Y);
96
96
  if (!Bytes.areEqual(verifier, hBX)) {
97
- await messenger.sendError(ProtocolStatusCode.InvalidParam);
97
+ await messenger.sendError(SecureChannelStatusCode.InvalidParam);
98
98
  throw new UnexpectedDataError(
99
99
  "Received incorrect key confirmation from the receiver. Commissioning failed.",
100
100
  );
@@ -17,7 +17,7 @@ import {
17
17
  UnexpectedDataError,
18
18
  } from "#general";
19
19
  import { SessionManager } from "#session/SessionManager.js";
20
- import { NodeId, ProtocolStatusCode, SECURE_CHANNEL_PROTOCOL_ID } from "#types";
20
+ import { NodeId, SECURE_CHANNEL_PROTOCOL_ID, SecureChannelStatusCode } from "#types";
21
21
  import { MessageExchange } from "../../protocol/MessageExchange.js";
22
22
  import { ProtocolHandler } from "../../protocol/ProtocolHandler.js";
23
23
  import { ChannelStatusResponseError } from "../../securechannel/SecureChannelMessenger.js";
@@ -186,7 +186,7 @@ export class PaseServer implements ProtocolHandler {
186
186
  this.#pairingTimer = undefined;
187
187
 
188
188
  if (sendError) {
189
- await messenger.sendError(ProtocolStatusCode.InvalidParam);
189
+ await messenger.sendError(SecureChannelStatusCode.InvalidParam);
190
190
  }
191
191
  await messenger.close();
192
192
  }