@matter/protocol 0.17.4 → 0.17.5-alpha.0-20260704-9ea70abfa

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 (221) hide show
  1. package/dist/cjs/action/client/ClientInteraction.d.ts.map +1 -1
  2. package/dist/cjs/action/client/ClientInteraction.js +21 -5
  3. package/dist/cjs/action/client/ClientInteraction.js.map +1 -1
  4. package/dist/cjs/action/client/InputChunk.d.ts +2 -2
  5. package/dist/cjs/action/server/AttributeReadResponse.d.ts.map +1 -1
  6. package/dist/cjs/action/server/AttributeReadResponse.js +20 -2
  7. package/dist/cjs/action/server/AttributeReadResponse.js.map +1 -1
  8. package/dist/cjs/advertisement/mdns/MdnsAdvertisement.d.ts.map +1 -1
  9. package/dist/cjs/advertisement/mdns/MdnsAdvertisement.js +1 -1
  10. package/dist/cjs/advertisement/mdns/MdnsAdvertisement.js.map +1 -1
  11. package/dist/cjs/bdx/schema/BdxAcceptMessagesSchema.d.ts +2 -2
  12. package/dist/cjs/bdx/schema/BdxAcceptMessagesSchema.d.ts.map +1 -1
  13. package/dist/cjs/bdx/schema/BdxBlockMessagesSchema.d.ts +6 -6
  14. package/dist/cjs/bdx/schema/BdxBlockMessagesSchema.d.ts.map +1 -1
  15. package/dist/cjs/bdx/schema/BdxInitMessagesSchema.d.ts +3 -3
  16. package/dist/cjs/bdx/schema/BdxInitMessagesSchema.d.ts.map +1 -1
  17. package/dist/cjs/ble/BleConsts.d.ts +1 -1
  18. package/dist/cjs/ble/BleConsts.d.ts.map +1 -1
  19. package/dist/cjs/certificate/kinds/Icac.d.ts +1 -1
  20. package/dist/cjs/certificate/kinds/Icac.js +1 -1
  21. package/dist/cjs/certificate/kinds/Noc.d.ts +1 -1
  22. package/dist/cjs/certificate/kinds/Noc.js +1 -1
  23. package/dist/cjs/certificate/kinds/OperationalBase.d.ts +1 -1
  24. package/dist/cjs/certificate/kinds/OperationalBase.js +1 -1
  25. package/dist/cjs/certificate/kinds/Rcac.d.ts +1 -1
  26. package/dist/cjs/certificate/kinds/Rcac.js +1 -1
  27. package/dist/cjs/certificate/kinds/Vvsc.d.ts +1 -1
  28. package/dist/cjs/certificate/kinds/Vvsc.js +1 -1
  29. package/dist/cjs/certificate/kinds/common.d.ts +1 -1
  30. package/dist/cjs/certificate/kinds/definitions/asn.d.ts +1 -1
  31. package/dist/cjs/certificate/kinds/definitions/operational.d.ts +1 -1
  32. package/dist/cjs/codec/MessageCodec.d.ts +20 -0
  33. package/dist/cjs/codec/MessageCodec.d.ts.map +1 -1
  34. package/dist/cjs/codec/MessageCodec.js +68 -14
  35. package/dist/cjs/codec/MessageCodec.js.map +1 -1
  36. package/dist/cjs/codec/MessagePrivacy.d.ts +2 -1
  37. package/dist/cjs/codec/MessagePrivacy.d.ts.map +1 -1
  38. package/dist/cjs/codec/MessagePrivacy.js.map +1 -1
  39. package/dist/cjs/common/OperationalCredentialsTypes.d.ts +2 -2
  40. package/dist/cjs/groups/FabricGroups.d.ts +5 -0
  41. package/dist/cjs/groups/FabricGroups.d.ts.map +1 -1
  42. package/dist/cjs/groups/FabricGroups.js +8 -0
  43. package/dist/cjs/groups/FabricGroups.js.map +1 -1
  44. package/dist/cjs/groups/Groups.d.ts +8 -0
  45. package/dist/cjs/groups/Groups.d.ts.map +1 -1
  46. package/dist/cjs/groups/Groups.js +16 -1
  47. package/dist/cjs/groups/Groups.js.map +1 -1
  48. package/dist/cjs/groups/index.d.ts +1 -0
  49. package/dist/cjs/groups/index.d.ts.map +1 -1
  50. package/dist/cjs/groups/index.js +1 -0
  51. package/dist/cjs/groups/index.js.map +1 -1
  52. package/dist/cjs/interaction/AttributeDataEncoder.d.ts +1 -0
  53. package/dist/cjs/interaction/AttributeDataEncoder.d.ts.map +1 -1
  54. package/dist/cjs/interaction/FabricAccessControl.d.ts +3 -2
  55. package/dist/cjs/interaction/FabricAccessControl.d.ts.map +1 -1
  56. package/dist/cjs/interaction/FabricAccessControl.js +7 -0
  57. package/dist/cjs/interaction/FabricAccessControl.js.map +1 -1
  58. package/dist/cjs/interaction/InteractionMessenger.d.ts +3 -0
  59. package/dist/cjs/interaction/InteractionMessenger.d.ts.map +1 -1
  60. package/dist/cjs/ota/OtaImageHeader.d.ts +1 -1
  61. package/dist/cjs/peer/ControllerCommissioningFlow.js +10 -10
  62. package/dist/cjs/peer/Peer.d.ts +4 -0
  63. package/dist/cjs/peer/Peer.d.ts.map +1 -1
  64. package/dist/cjs/peer/PeerExchangeProvider.d.ts +2 -0
  65. package/dist/cjs/peer/PeerExchangeProvider.d.ts.map +1 -1
  66. package/dist/cjs/peer/PeerExchangeProvider.js +6 -0
  67. package/dist/cjs/peer/PeerExchangeProvider.js.map +1 -1
  68. package/dist/cjs/protocol/ExchangeManager.d.ts +1 -1
  69. package/dist/cjs/protocol/ExchangeManager.d.ts.map +1 -1
  70. package/dist/cjs/protocol/ExchangeManager.js +8 -2
  71. package/dist/cjs/protocol/ExchangeManager.js.map +1 -1
  72. package/dist/cjs/protocol/ExchangeProvider.d.ts +6 -0
  73. package/dist/cjs/protocol/ExchangeProvider.d.ts.map +1 -1
  74. package/dist/cjs/protocol/ExchangeProvider.js +10 -0
  75. package/dist/cjs/protocol/ExchangeProvider.js.map +1 -1
  76. package/dist/cjs/protocol/MRP.d.ts +2 -2
  77. package/dist/cjs/protocol/MessageChannel.d.ts +1 -1
  78. package/dist/cjs/protocol/MessageChannel.js +1 -1
  79. package/dist/cjs/protocol/MessageExchange.d.ts +10 -0
  80. package/dist/cjs/protocol/MessageExchange.d.ts.map +1 -1
  81. package/dist/cjs/protocol/MessageExchange.js +24 -2
  82. package/dist/cjs/protocol/MessageExchange.js.map +1 -1
  83. package/dist/cjs/session/GroupSession.d.ts +12 -1
  84. package/dist/cjs/session/GroupSession.d.ts.map +1 -1
  85. package/dist/cjs/session/GroupSession.js +23 -3
  86. package/dist/cjs/session/GroupSession.js.map +1 -1
  87. package/dist/cjs/session/SessionManager.d.ts +29 -2
  88. package/dist/cjs/session/SessionManager.d.ts.map +1 -1
  89. package/dist/cjs/session/SessionManager.js +36 -10
  90. package/dist/cjs/session/SessionManager.js.map +1 -1
  91. package/dist/cjs/session/case/CaseMessages.d.ts +7 -7
  92. package/dist/cjs/session/pase/PaseMessages.d.ts +6 -6
  93. package/dist/esm/action/client/ClientInteraction.d.ts.map +1 -1
  94. package/dist/esm/action/client/ClientInteraction.js +21 -5
  95. package/dist/esm/action/client/ClientInteraction.js.map +1 -1
  96. package/dist/esm/action/client/InputChunk.d.ts +2 -2
  97. package/dist/esm/action/server/AttributeReadResponse.d.ts.map +1 -1
  98. package/dist/esm/action/server/AttributeReadResponse.js +20 -2
  99. package/dist/esm/action/server/AttributeReadResponse.js.map +1 -1
  100. package/dist/esm/advertisement/mdns/MdnsAdvertisement.d.ts.map +1 -1
  101. package/dist/esm/advertisement/mdns/MdnsAdvertisement.js +1 -1
  102. package/dist/esm/advertisement/mdns/MdnsAdvertisement.js.map +1 -1
  103. package/dist/esm/bdx/schema/BdxAcceptMessagesSchema.d.ts +2 -2
  104. package/dist/esm/bdx/schema/BdxAcceptMessagesSchema.d.ts.map +1 -1
  105. package/dist/esm/bdx/schema/BdxBlockMessagesSchema.d.ts +6 -6
  106. package/dist/esm/bdx/schema/BdxBlockMessagesSchema.d.ts.map +1 -1
  107. package/dist/esm/bdx/schema/BdxInitMessagesSchema.d.ts +3 -3
  108. package/dist/esm/bdx/schema/BdxInitMessagesSchema.d.ts.map +1 -1
  109. package/dist/esm/ble/BleConsts.d.ts +1 -1
  110. package/dist/esm/ble/BleConsts.d.ts.map +1 -1
  111. package/dist/esm/certificate/kinds/Icac.d.ts +1 -1
  112. package/dist/esm/certificate/kinds/Icac.js +1 -1
  113. package/dist/esm/certificate/kinds/Noc.d.ts +1 -1
  114. package/dist/esm/certificate/kinds/Noc.js +1 -1
  115. package/dist/esm/certificate/kinds/OperationalBase.d.ts +1 -1
  116. package/dist/esm/certificate/kinds/OperationalBase.js +1 -1
  117. package/dist/esm/certificate/kinds/Rcac.d.ts +1 -1
  118. package/dist/esm/certificate/kinds/Rcac.js +1 -1
  119. package/dist/esm/certificate/kinds/Vvsc.d.ts +1 -1
  120. package/dist/esm/certificate/kinds/Vvsc.js +1 -1
  121. package/dist/esm/certificate/kinds/common.d.ts +1 -1
  122. package/dist/esm/certificate/kinds/definitions/asn.d.ts +1 -1
  123. package/dist/esm/certificate/kinds/definitions/operational.d.ts +1 -1
  124. package/dist/esm/codec/MessageCodec.d.ts +20 -0
  125. package/dist/esm/codec/MessageCodec.d.ts.map +1 -1
  126. package/dist/esm/codec/MessageCodec.js +68 -14
  127. package/dist/esm/codec/MessageCodec.js.map +1 -1
  128. package/dist/esm/codec/MessagePrivacy.d.ts +2 -1
  129. package/dist/esm/codec/MessagePrivacy.d.ts.map +1 -1
  130. package/dist/esm/codec/MessagePrivacy.js.map +1 -1
  131. package/dist/esm/common/OperationalCredentialsTypes.d.ts +2 -2
  132. package/dist/esm/groups/FabricGroups.d.ts +5 -0
  133. package/dist/esm/groups/FabricGroups.d.ts.map +1 -1
  134. package/dist/esm/groups/FabricGroups.js +8 -0
  135. package/dist/esm/groups/FabricGroups.js.map +1 -1
  136. package/dist/esm/groups/Groups.d.ts +8 -0
  137. package/dist/esm/groups/Groups.d.ts.map +1 -1
  138. package/dist/esm/groups/Groups.js +16 -1
  139. package/dist/esm/groups/Groups.js.map +1 -1
  140. package/dist/esm/groups/index.d.ts +1 -0
  141. package/dist/esm/groups/index.d.ts.map +1 -1
  142. package/dist/esm/groups/index.js +1 -0
  143. package/dist/esm/groups/index.js.map +1 -1
  144. package/dist/esm/interaction/AttributeDataEncoder.d.ts +1 -0
  145. package/dist/esm/interaction/AttributeDataEncoder.d.ts.map +1 -1
  146. package/dist/esm/interaction/FabricAccessControl.d.ts +3 -2
  147. package/dist/esm/interaction/FabricAccessControl.d.ts.map +1 -1
  148. package/dist/esm/interaction/FabricAccessControl.js +8 -0
  149. package/dist/esm/interaction/FabricAccessControl.js.map +1 -1
  150. package/dist/esm/interaction/InteractionMessenger.d.ts +3 -0
  151. package/dist/esm/interaction/InteractionMessenger.d.ts.map +1 -1
  152. package/dist/esm/ota/OtaImageHeader.d.ts +1 -1
  153. package/dist/esm/peer/ControllerCommissioningFlow.js +10 -10
  154. package/dist/esm/peer/Peer.d.ts +4 -0
  155. package/dist/esm/peer/Peer.d.ts.map +1 -1
  156. package/dist/esm/peer/PeerExchangeProvider.d.ts +2 -0
  157. package/dist/esm/peer/PeerExchangeProvider.d.ts.map +1 -1
  158. package/dist/esm/peer/PeerExchangeProvider.js +6 -0
  159. package/dist/esm/peer/PeerExchangeProvider.js.map +1 -1
  160. package/dist/esm/protocol/ExchangeManager.d.ts +1 -1
  161. package/dist/esm/protocol/ExchangeManager.d.ts.map +1 -1
  162. package/dist/esm/protocol/ExchangeManager.js +9 -3
  163. package/dist/esm/protocol/ExchangeManager.js.map +1 -1
  164. package/dist/esm/protocol/ExchangeProvider.d.ts +6 -0
  165. package/dist/esm/protocol/ExchangeProvider.d.ts.map +1 -1
  166. package/dist/esm/protocol/ExchangeProvider.js +10 -0
  167. package/dist/esm/protocol/ExchangeProvider.js.map +1 -1
  168. package/dist/esm/protocol/MRP.d.ts +2 -2
  169. package/dist/esm/protocol/MessageChannel.d.ts +1 -1
  170. package/dist/esm/protocol/MessageChannel.js +1 -1
  171. package/dist/esm/protocol/MessageExchange.d.ts +10 -0
  172. package/dist/esm/protocol/MessageExchange.d.ts.map +1 -1
  173. package/dist/esm/protocol/MessageExchange.js +24 -2
  174. package/dist/esm/protocol/MessageExchange.js.map +1 -1
  175. package/dist/esm/session/GroupSession.d.ts +12 -1
  176. package/dist/esm/session/GroupSession.d.ts.map +1 -1
  177. package/dist/esm/session/GroupSession.js +23 -3
  178. package/dist/esm/session/GroupSession.js.map +1 -1
  179. package/dist/esm/session/SessionManager.d.ts +29 -2
  180. package/dist/esm/session/SessionManager.d.ts.map +1 -1
  181. package/dist/esm/session/SessionManager.js +38 -11
  182. package/dist/esm/session/SessionManager.js.map +1 -1
  183. package/dist/esm/session/case/CaseMessages.d.ts +7 -7
  184. package/dist/esm/session/pase/PaseMessages.d.ts +6 -6
  185. package/package.json +5 -5
  186. package/src/action/client/ClientInteraction.ts +27 -5
  187. package/src/action/client/InputChunk.ts +2 -2
  188. package/src/action/server/AttributeReadResponse.ts +30 -2
  189. package/src/advertisement/mdns/MdnsAdvertisement.ts +2 -1
  190. package/src/bdx/schema/BdxAcceptMessagesSchema.ts +2 -2
  191. package/src/bdx/schema/BdxBlockMessagesSchema.ts +6 -6
  192. package/src/bdx/schema/BdxInitMessagesSchema.ts +3 -3
  193. package/src/ble/BleConsts.ts +1 -1
  194. package/src/certificate/kinds/Icac.ts +1 -1
  195. package/src/certificate/kinds/Noc.ts +1 -1
  196. package/src/certificate/kinds/OperationalBase.ts +1 -1
  197. package/src/certificate/kinds/Rcac.ts +1 -1
  198. package/src/certificate/kinds/Vvsc.ts +1 -1
  199. package/src/certificate/kinds/common.ts +1 -1
  200. package/src/certificate/kinds/definitions/asn.ts +1 -1
  201. package/src/certificate/kinds/definitions/operational.ts +1 -1
  202. package/src/codec/MessageCodec.ts +88 -14
  203. package/src/codec/MessagePrivacy.ts +6 -2
  204. package/src/common/OperationalCredentialsTypes.ts +2 -2
  205. package/src/groups/FabricGroups.ts +11 -1
  206. package/src/groups/Groups.ts +24 -2
  207. package/src/groups/KeySets.ts +1 -1
  208. package/src/groups/index.ts +1 -0
  209. package/src/interaction/FabricAccessControl.ts +16 -3
  210. package/src/ota/OtaImageHeader.ts +1 -1
  211. package/src/peer/ControllerCommissioningFlow.ts +11 -11
  212. package/src/peer/PeerExchangeProvider.ts +8 -0
  213. package/src/protocol/ExchangeManager.ts +14 -3
  214. package/src/protocol/ExchangeProvider.ts +11 -0
  215. package/src/protocol/MRP.ts +2 -2
  216. package/src/protocol/MessageChannel.ts +1 -1
  217. package/src/protocol/MessageExchange.ts +28 -4
  218. package/src/session/GroupSession.ts +26 -2
  219. package/src/session/SessionManager.ts +65 -13
  220. package/src/session/case/CaseMessages.ts +7 -7
  221. package/src/session/pase/PaseMessages.ts +6 -6
@@ -31,6 +31,14 @@ export class PeerExchangeProvider extends ExchangeProvider {
31
31
  return this.#peer.sessionParameters.maxPathsPerInvoke;
32
32
  }
33
33
 
34
+ override get readPathsSupported() {
35
+ return this.#peer.limits.readPathsSupported ?? super.readPathsSupported;
36
+ }
37
+
38
+ override get subscribePathsSupported() {
39
+ return this.#peer.limits.subscribePathsSupported ?? super.subscribePathsSupported;
40
+ }
41
+
34
42
  get peerAddress() {
35
43
  return this.#peer.address;
36
44
  }
@@ -8,6 +8,7 @@ import { DecodedMessage, Message, MessageCodec, SessionType } from "#codec/Messa
8
8
  import { Mark } from "#common/Mark.js";
9
9
  import { PeerAddress } from "#peer/PeerAddress.js";
10
10
  import { SecureChannelMessenger } from "#securechannel/SecureChannelMessenger.js";
11
+ import { GroupSession } from "#session/GroupSession.js";
11
12
  import { NodeSession } from "#session/NodeSession.js";
12
13
  import { Session } from "#session/Session.js";
13
14
  import { SessionManager, ShutdownError } from "#session/SessionManager.js";
@@ -36,7 +37,8 @@ import {
36
37
  UdpTransport,
37
38
  UnexpectedDataError,
38
39
  } from "@matter/general";
39
- import { FabricIndex, NodeId, SECURE_CHANNEL_PROTOCOL_ID, SecureMessageType } from "@matter/types";
40
+ import { FabricIndex, GroupId, NodeId, SECURE_CHANNEL_PROTOCOL_ID, SecureMessageType } from "@matter/types";
41
+ import { Groupcast } from "@matter/types/clusters/groupcast";
40
42
  import { MessageExchange, MessageExchangeContext } from "./MessageExchange.js";
41
43
  import { DuplicateMessageError } from "./MessageReceptionState.js";
42
44
  import { MRP } from "./MRP.js";
@@ -127,8 +129,8 @@ export class ExchangeManager implements Transport.Provider {
127
129
  return this.#transports.hasInterfaceFor(type, address);
128
130
  }
129
131
 
130
- initiateExchange(address: PeerAddress, protocolId: number) {
131
- return this.initiateExchangeForSession(this.#sessions.sessionFor(address), protocolId);
132
+ initiateExchange(address: PeerAddress, protocolId: number, options?: MessageExchange.Options) {
133
+ return this.initiateExchangeForSession(this.#sessions.sessionFor(address), protocolId, options);
132
134
  }
133
135
 
134
136
  initiateExchangeForSession(session: Session, protocolId: number, options?: MessageExchange.Options) {
@@ -271,6 +273,15 @@ export class ExchangeManager implements Transport.Provider {
271
273
  } catch (e) {
272
274
  DuplicateMessageError.accept(e);
273
275
  isDuplicate = true;
276
+ // Report replay for Groupcast testing — observable is a no-op without listeners.
277
+ this.#sessions.emitGroupMessage({
278
+ result: Groupcast.GroupcastTestResult.MessageReplay,
279
+ fabric: (session as GroupSession).fabric,
280
+ groupId:
281
+ message.packetHeader.destGroupId !== undefined
282
+ ? GroupId(message.packetHeader.destGroupId)
283
+ : undefined,
284
+ });
274
285
  }
275
286
  } else {
276
287
  throw new MatterFlowError(`Unsupported session type: ${packet.header.sessionType}`);
@@ -76,6 +76,17 @@ export abstract class ExchangeProvider {
76
76
  abstract readonly peerAddress?: PeerAddress;
77
77
  abstract readonly maxPathsPerInvoke?: number;
78
78
 
79
+ /**
80
+ * Peer-advertised CapabilityMinima path floors. Base values are the spec minimums.
81
+ * @see {@link MatterSpecification.v16.Core} § 11.1
82
+ */
83
+ get readPathsSupported(): number {
84
+ return 9;
85
+ }
86
+ get subscribePathsSupported(): number {
87
+ return 3;
88
+ }
89
+
79
90
  /**
80
91
  * Dedicated secure session backing this provider, if any.
81
92
  *
@@ -26,7 +26,7 @@ export namespace MRP {
26
26
  /** The number of retransmissions before transitioning from linear to exponential backoff. */
27
27
  export const BACKOFF_THRESHOLD = 1;
28
28
 
29
- /** @see {@link MatterSpecification.v12.Core}, section 4.11.8 */
29
+ /** @see {@link MatterSpecification.v16.Core}, section 4.12.8 */
30
30
  export const STANDALONE_ACK_TIMEOUT = Millis(200);
31
31
 
32
32
  /**
@@ -120,7 +120,7 @@ export namespace MRP {
120
120
  * When `calculateMaximum` is set to true, we calculate the maximum time without any randomness.
121
121
  * Otherwise, the caller-supplied `additionalDelay` (default 0) is added to the base interval.
122
122
  *
123
- * @see {@link MatterSpecification.v10.Core}, section 4.11.2.1
123
+ * @see {@link MatterSpecification.v16.Core}, section 4.12.2.1
124
124
  */
125
125
  export function retransmissionIntervalOf(
126
126
  { transmissionNumber, sessionParameters, isPeerActive, additionalDelay = Millis(0) }: RetryDelayInputs,
@@ -218,7 +218,7 @@ export class MessageChannel implements Channel<Message> {
218
218
  *
219
219
  * When `calculateMaximum` is set to true, we calculate the maximum time without any randomness.
220
220
  *
221
- * @see {@link MatterSpecification.v10.Core}, section 4.11.2.1
221
+ * @see {@link MatterSpecification.v16.Core}, section 4.12.2.1
222
222
  */
223
223
  getMrpResubmissionBackOffTime(
224
224
  retransmissionCount: number,
@@ -196,8 +196,8 @@ export class MessageExchange {
196
196
  readonly #isInitiator: boolean;
197
197
  readonly #messagesQueue = new DataReadQueue<Message>();
198
198
  readonly #lifetime: Lifetime;
199
- readonly #onSend?: MessageExchange.SendNotifier;
200
- readonly #onReceive?: MessageExchange.ReceiveNotifier;
199
+ #onSend?: MessageExchange.SendNotifier;
200
+ #onReceive?: MessageExchange.ReceiveNotifier;
201
201
  readonly #addressOverride?: ServerAddressUdp;
202
202
  readonly #peerAdditionalMrpDelay?: Duration;
203
203
  #receivedMessageToAck: Message | undefined;
@@ -306,6 +306,26 @@ export class MessageExchange {
306
306
  return this.#isInitiator;
307
307
  }
308
308
 
309
+ /**
310
+ * Sets the receive-notifier when none was provided at construction time. This allows code that obtains an exchange
311
+ * after its creation (e.g. via protocol handler dispatch) to hook into message receipt.
312
+ */
313
+ set onReceive(fn: MessageExchange.ReceiveNotifier) {
314
+ if (this.#onReceive === undefined) {
315
+ this.#onReceive = fn;
316
+ }
317
+ }
318
+
319
+ /**
320
+ * Sets the send-notifier when none was provided at construction time. Mirrors {@link onReceive} for code that
321
+ * obtains an exchange after its creation and needs to observe transmissions.
322
+ */
323
+ set onSend(fn: MessageExchange.SendNotifier) {
324
+ if (this.#onSend === undefined) {
325
+ this.#onSend = fn;
326
+ }
327
+ }
328
+
309
329
  /** Emits when the exchange is actually closed. This happens after all Retries and Communication are done. */
310
330
  get closed() {
311
331
  return this.#closed;
@@ -570,9 +590,13 @@ export class MessageExchange {
570
590
  if (!GroupSession.is(session)) {
571
591
  throw new InternalError("Session is not a GroupSession, but session type is Group.");
572
592
  }
573
- const destGroupId = GroupId.fromNodeId(this.#peerNodeId!); // TODO !!! Where get from?
593
+ const peerNodeId = this.#peerNodeId;
594
+ if (peerNodeId === undefined) {
595
+ throw new InternalError("Group message exchange requires a peer NodeId.");
596
+ }
597
+ const destGroupId = GroupId.fromNodeId(peerNodeId);
574
598
  if (destGroupId === 0) {
575
- throw new InternalError(`Invalid GroupId extracted from NodeId ${this.#peerNodeId}`);
599
+ throw new InternalError(`Invalid GroupId extracted from NodeId ${peerNodeId}`);
576
600
  }
577
601
  const messageId = await abort.attempt(this.session.getIncrementedMessageCounter());
578
602
  if (messageId === undefined) {
@@ -32,6 +32,20 @@ import { SecureSession } from "./SecureSession.js";
32
32
  import { Session } from "./Session.js";
33
33
  import { SessionManager } from "./SessionManager.js";
34
34
 
35
+ /** Thrown by {@link GroupSession.decode} when no installed key can be a candidate for the received group message. */
36
+ export class GroupSessionNoKeyError extends MatterFlowError {
37
+ constructor(message = "No key candidate found for group session decryption") {
38
+ super(message);
39
+ }
40
+ }
41
+
42
+ /** Thrown by {@link GroupSession.decode} when decryption failed with all candidate keys. */
43
+ export class GroupSessionDecodeError extends MatterFlowError {
44
+ constructor(message = "Failed to decode group message with any key candidate") {
45
+ super(message);
46
+ }
47
+ }
48
+
35
49
  const logger = Logger.get("SecureGroupSession");
36
50
 
37
51
  /** Secure Group session instance */
@@ -41,6 +55,7 @@ export class GroupSession extends SecureSession {
41
55
  readonly #peerNodeId: NodeId;
42
56
  readonly #operationalGroupKey: Bytes;
43
57
  readonly #operationalPrivacyKey?: Bytes;
58
+ readonly #multicastAddress: string;
44
59
  readonly supportsMRP = false;
45
60
  readonly closingAfterExchangeFinished = false; // Group sessions do not close after exchange finished, they are long-lived
46
61
 
@@ -55,6 +70,7 @@ export class GroupSession extends SecureSession {
55
70
  id,
56
71
  peerNodeId,
57
72
  keySetId,
73
+ multicastAddress,
58
74
  messageCounter,
59
75
  } = config;
60
76
  super({
@@ -68,6 +84,7 @@ export class GroupSession extends SecureSession {
68
84
  this.keySetId = keySetId;
69
85
  this.#operationalGroupKey = operationalGroupKey;
70
86
  this.#operationalPrivacyKey = operationalPrivacyKey;
87
+ this.#multicastAddress = multicastAddress;
71
88
 
72
89
  manager?.registerGroupSession(this);
73
90
  fabric.addSession(this);
@@ -75,6 +92,11 @@ export class GroupSession extends SecureSession {
75
92
  logger.debug(this.via, `Created secure GROUP session for fabric index ${fabric.fabricIndex}`);
76
93
  }
77
94
 
95
+ /** IPv6 multicast destination address this group session sends to. */
96
+ get multicastAddress(): string {
97
+ return this.#multicastAddress;
98
+ }
99
+
78
100
  /**
79
101
  * Create an outbound group session.
80
102
  */
@@ -113,6 +135,7 @@ export class GroupSession extends SecureSession {
113
135
  peerNodeId: groupNodeId,
114
136
  operationalGroupKey,
115
137
  operationalPrivacyKey: Bytes.of(await MessagePrivacy.deriveKey(fabric.crypto, operationalGroupKey)),
138
+ multicastAddress,
116
139
  messageCounter,
117
140
  });
118
141
  }
@@ -263,7 +286,7 @@ export class GroupSession extends SecureSession {
263
286
  }
264
287
  }
265
288
  if (keys.length === 0) {
266
- throw new MatterFlowError("No key candidate found for group session decryption.");
289
+ throw new GroupSessionNoKeyError();
267
290
  }
268
291
 
269
292
  const messageFlags = Bytes.of(aad)[0];
@@ -330,7 +353,7 @@ export class GroupSession extends SecureSession {
330
353
  }
331
354
  }
332
355
  if (!found || !message || !key || keySetId === undefined || !fabric || sourceNodeId === undefined) {
333
- throw new MatterFlowError("Failed to decode group message with any key candidate.");
356
+ throw new GroupSessionDecodeError();
334
357
  }
335
358
 
336
359
  if (message.payloadHeader.hasSecuredExtension) {
@@ -356,6 +379,7 @@ export namespace GroupSession {
356
379
  peerNodeId: NodeId; //The Target Group Node Id
357
380
  operationalGroupKey: Bytes; // The Operational Group Key that was used to encrypt the incoming group message.
358
381
  operationalPrivacyKey?: Bytes;
382
+ multicastAddress: string; // IPv6 multicast destination address this session sends to.
359
383
  messageCounter: MessageCounter;
360
384
  }
361
385
 
@@ -12,10 +12,11 @@ import { PeerAddress, PeerAddressMap } from "#peer/PeerAddress.js";
12
12
  import { PeerShutdownError } from "#peer/PeerCommunicationError.js";
13
13
  import { PeerLossContext } from "#peer/PeerLossContext.js";
14
14
  import { SessionClosedError } from "#protocol/errors.js";
15
- import { GroupSession } from "#session/GroupSession.js";
15
+ import { GroupSession, GroupSessionDecodeError, GroupSessionNoKeyError } from "#session/GroupSession.js";
16
16
  import {
17
17
  BasicSet,
18
18
  Bytes,
19
+ causedBy,
19
20
  Channel,
20
21
  ClosedError,
21
22
  Construction,
@@ -41,7 +42,8 @@ import {
41
42
  Transport,
42
43
  UnexpectedDataError,
43
44
  } from "@matter/general";
44
- import { CaseAuthenticatedTag, FabricId, FabricIndex, GroupId, NodeId } from "@matter/types";
45
+ import { CaseAuthenticatedTag, ClusterId, EndpointNumber, FabricId, FabricIndex, GroupId, NodeId } from "@matter/types";
46
+ import { Groupcast } from "@matter/types/clusters/groupcast";
45
47
  import type { ExposedFabricInformation, Fabric } from "../fabric/Fabric.js";
46
48
  import { MessageCounter, PersistedMessageCounter } from "../protocol/MessageCounter.js";
47
49
  import { NodeSession } from "./NodeSession.js";
@@ -114,6 +116,24 @@ export interface ActiveSessionInformation {
114
116
  numberOfActiveSubscriptions: number;
115
117
  }
116
118
 
119
+ /**
120
+ * Metadata about a received group message, emitted via {@link SessionManager.onGroupMessage} for Groupcast testing.
121
+ * Uses {@link Groupcast.GroupcastTestResult} directly so consumers can forward the value without mapping.
122
+ */
123
+ export interface GroupMessageEventInfo {
124
+ result: Groupcast.GroupcastTestResult;
125
+ fabric?: Fabric;
126
+ groupId?: GroupId;
127
+ sourceIp?: string;
128
+ destIp?: string;
129
+ endpointId?: EndpointNumber;
130
+ clusterId?: ClusterId;
131
+ elementId?: number;
132
+
133
+ /** Whether the ACL granted access for the dispatched command (only set for Success). */
134
+ accessAllowed?: boolean;
135
+ }
136
+
117
137
  /**
118
138
  * Interfaces {@link SessionManager} with other components.
119
139
  */
@@ -141,7 +161,8 @@ const GROUP_DATA_COUNTER_KEY = "groupDataCounter";
141
161
 
142
162
  /**
143
163
  * Reserve block size for the persisted group data counter; matches CHIP `GROUP_MSG_COUNTER_MIN_INCREMENT`. The counter
144
- * is persisted this far ahead so an unclean restart never rolls it back (Matter spec §4.6.1.3).
164
+ * is persisted this far ahead so an unclean restart never rolls it back.
165
+ * @see {@link MatterSpecification.v16.Core} § 4.6.1.3
145
166
  */
146
167
  const GROUP_DATA_COUNTER_RESERVE = 1000;
147
168
 
@@ -180,6 +201,7 @@ export class SessionManager {
180
201
 
181
202
  readonly #subscriptionsChanged = Observable<[session: NodeSession, subscription: Subscription]>();
182
203
  readonly #retry = Observable<[session: Session, number: number]>();
204
+ readonly #onGroupMessage = Observable<[info: GroupMessageEventInfo]>();
183
205
 
184
206
  constructor(context: SessionManagerContext) {
185
207
  this.#context = context;
@@ -239,7 +261,10 @@ export class SessionManager {
239
261
  return this.#context.fabrics.crypto;
240
262
  }
241
263
 
242
- /** The single node-global Group Encrypted Data Message Counter shared by all group sessions (spec §4.6.1.3). */
264
+ /**
265
+ * The single node-global Group Encrypted Data Message Counter shared by all group sessions.
266
+ * @see {@link MatterSpecification.v16.Core} § 4.6.1.3
267
+ */
243
268
  get groupDataMessageCounter() {
244
269
  this.#construction.assert();
245
270
  return this.#groupDataMessageCounter;
@@ -304,6 +329,14 @@ export class SessionManager {
304
329
  return this.#retry;
305
330
  }
306
331
 
332
+ /**
333
+ * Emits for each received group message (success or failure). Observers remain inactive until at least one
334
+ * listener attaches, so fabrics not under test incur no emission overhead.
335
+ */
336
+ get onGroupMessage() {
337
+ return this.#onGroupMessage;
338
+ }
339
+
307
340
  /**
308
341
  * Convenience function for accessing a fabric by address.
309
342
  */
@@ -595,17 +628,29 @@ export class SessionManager {
595
628
  */
596
629
  groupSessionFromPacket(packet: DecodedPacket, aad: Bytes) {
597
630
  this.#construction.assert();
598
- const { message, key, privacyKey, sessionId, sourceNodeId, keySetId, fabric } = GroupSession.decode(
599
- this.#context.fabrics,
600
- packet,
601
- aad,
602
- );
631
+ let decoded;
632
+ try {
633
+ decoded = GroupSession.decode(this.#context.fabrics, packet, aad);
634
+ } catch (error) {
635
+ // Groupcast testing event on decode failure. Observable is a no-op unless a listener is attached. A failed
636
+ // decode is unauthenticated, so per the Groupcast spec we report only the result, never a group id.
637
+ if (causedBy(error, GroupSessionNoKeyError)) {
638
+ this.#onGroupMessage.emit({ result: Groupcast.GroupcastTestResult.NoAvailableKey });
639
+ } else if (causedBy(error, GroupSessionDecodeError)) {
640
+ this.#onGroupMessage.emit({ result: Groupcast.GroupcastTestResult.FailedAuth });
641
+ }
642
+ throw error;
643
+ }
603
644
 
604
- const groupId = message.packetHeader.destGroupId;
605
- if (groupId === undefined) {
645
+ const { message, key, privacyKey, sessionId, sourceNodeId, keySetId, fabric } = decoded;
646
+
647
+ // The group id is only authoritative after decode, since privacy obfuscates it in the wire header.
648
+ const rawGroupId = message.packetHeader.destGroupId;
649
+ if (rawGroupId === undefined) {
606
650
  throw new UnexpectedDataError("Group ID is required for GroupSession fromPacket.");
607
651
  }
608
- GroupId.assertGroupId(GroupId(groupId));
652
+ const groupId = GroupId(rawGroupId);
653
+ GroupId.assertGroupId(groupId);
609
654
 
610
655
  let session = this.#groupSessions.get(sourceNodeId)?.get("id", sessionId);
611
656
  if (session === undefined) {
@@ -617,6 +662,7 @@ export class SessionManager {
617
662
  operationalGroupKey: key,
618
663
  operationalPrivacyKey: privacyKey,
619
664
  peerNodeId: sourceNodeId,
665
+ multicastAddress: fabric.groups.multicastAddressFor(groupId),
620
666
  messageCounter: this.#groupDataMessageCounter,
621
667
  });
622
668
  }
@@ -624,6 +670,11 @@ export class SessionManager {
624
670
  return { session, message, key };
625
671
  }
626
672
 
673
+ /** Report a group message event (used by ExchangeManager for replay and InteractionServer for dispatch). */
674
+ emitGroupMessage(info: GroupMessageEventInfo) {
675
+ this.#onGroupMessage.emit(info);
676
+ }
677
+
627
678
  registerGroupSession(session: GroupSession) {
628
679
  const sourceNodeId = session.peerNodeId;
629
680
  const peerSessions = this.#groupSessions.get(sourceNodeId) ?? new BasicSet();
@@ -783,7 +834,8 @@ export class SessionManager {
783
834
  /**
784
835
  * Build the node-global group data message counter. On the first run after upgrading from the legacy per-key
785
836
  * model, seed it above every value any per-key counter could already have used so it never rolls back below a
786
- * value already sent with a surviving key (spec §4.6.1.3); then clear the legacy entries.
837
+ * value already sent with a surviving key; then clear the legacy entries.
838
+ * @see {@link MatterSpecification.v16.Core} § 4.6.1.3
787
839
  */
788
840
  async #createGroupDataMessageCounter() {
789
841
  const storage = this.#context.storage;
@@ -24,7 +24,7 @@ export const KDFSR3_INFO = Bytes.fromString("Sigma3");
24
24
  export const TBE_DATA2_NONCE = Bytes.fromString("NCASE_Sigma2N");
25
25
  export const TBE_DATA3_NONCE = Bytes.fromString("NCASE_Sigma3N");
26
26
 
27
- /** @see {@link MatterSpecification.v13.Core} § 4.14.2.3 */
27
+ /** @see {@link MatterSpecification.v16.Core} § 4.14.2.3 */
28
28
  export const TlvCaseSigma1 = TlvObject({
29
29
  initiatorRandom: TlvField(1, TlvByteString.bound({ length: 32 })),
30
30
  initiatorSessionId: TlvField(2, TlvUInt16),
@@ -36,7 +36,7 @@ export const TlvCaseSigma1 = TlvObject({
36
36
  });
37
37
  export type CaseSigma1 = WithDurationSessionParameters<TypeFromSchema<typeof TlvCaseSigma1>, "initiatorSessionParams">;
38
38
 
39
- /** @see {@link MatterSpecification.v13.Core} § 4.14.2.3 */
39
+ /** @see {@link MatterSpecification.v16.Core} § 4.14.2.3 */
40
40
  export const TlvCaseSigma2 = TlvObject({
41
41
  responderRandom: TlvField(1, TlvByteString.bound({ length: 32 })),
42
42
  responderSessionId: TlvField(2, TlvUInt16),
@@ -46,7 +46,7 @@ export const TlvCaseSigma2 = TlvObject({
46
46
  });
47
47
  export type CaseSigma2 = WithDurationSessionParameters<TypeFromSchema<typeof TlvCaseSigma2>, "responderSessionParams">;
48
48
 
49
- /** @see {@link MatterSpecification.v13.Core} § 4.14.2.3 */
49
+ /** @see {@link MatterSpecification.v16.Core} § 4.14.2.3 */
50
50
  export const TlvCaseSigma2Resume = TlvObject({
51
51
  resumptionId: TlvField(1, TlvByteString.bound({ length: 16 })),
52
52
  resumeMic: TlvField(2, TlvByteString.bound({ length: 16 })),
@@ -58,13 +58,13 @@ export type CaseSigma2Resume = WithDurationSessionParameters<
58
58
  "responderSessionParams"
59
59
  >;
60
60
 
61
- /** @see {@link MatterSpecification.v13.Core} § 4.14.2.3 */
61
+ /** @see {@link MatterSpecification.v16.Core} § 4.14.2.3 */
62
62
  export const TlvCaseSigma3 = TlvObject({
63
63
  encrypted: TlvField(1, TlvByteString),
64
64
  });
65
65
  export type CaseSigma3 = TypeFromSchema<typeof TlvCaseSigma3>;
66
66
 
67
- /** @see {@link MatterSpecification.v10.Core} § 4.13.2.3 */
67
+ /** @see {@link MatterSpecification.v16.Core} § 4.14.2.3 */
68
68
  export const TlvSignedData = TlvObject({
69
69
  responderNoc: TlvField(1, TlvByteString),
70
70
  responderIcac: TlvOptionalField(2, TlvByteString),
@@ -73,7 +73,7 @@ export const TlvSignedData = TlvObject({
73
73
  });
74
74
  export type SignedData = TypeFromSchema<typeof TlvSignedData>;
75
75
 
76
- /** @see {@link MatterSpecification.v10.Core} § 4.13.2.3 */
76
+ /** @see {@link MatterSpecification.v16.Core} § 4.14.2.3 */
77
77
  export const TlvEncryptedDataSigma2 = TlvObject({
78
78
  responderNoc: TlvField(1, TlvByteString),
79
79
  responderIcac: TlvOptionalField(2, TlvByteString),
@@ -82,7 +82,7 @@ export const TlvEncryptedDataSigma2 = TlvObject({
82
82
  });
83
83
  export type EncryptedDataSigma2 = TypeFromSchema<typeof TlvEncryptedDataSigma2>;
84
84
 
85
- /** @see {@link MatterSpecification.v10.Core} § 4.13.2.3 */
85
+ /** @see {@link MatterSpecification.v16.Core} § 4.14.2.3 */
86
86
  export const TlvEncryptedDataSigma3 = TlvObject({
87
87
  responderNoc: TlvField(1, TlvByteString),
88
88
  responderIcac: TlvOptionalField(2, TlvByteString),
@@ -19,7 +19,7 @@ import {
19
19
  TypeFromSchema,
20
20
  } from "@matter/types";
21
21
 
22
- /** @see {@link MatterSpecification.v13.Core} § 4.12.8 */
22
+ /** @see {@link MatterSpecification.v16.Core} § 4.13.1 */
23
23
  export const TlvSessionParameters = TlvObject({
24
24
  /** Maximum sleep interval of node when in idle mode. */
25
25
  idleInterval: TlvOptionalField(1, TlvUInt32) /* default: SESSION_IDLE_INTERVAL */,
@@ -60,7 +60,7 @@ export type WithDurationSessionParameters<T, K extends keyof T> = Omit<T, K> & {
60
60
  [P in K]?: SessionParametersWithDurations;
61
61
  };
62
62
 
63
- /** @see {@link MatterSpecification.v13.Core} § 4.14.1.2 */
63
+ /** @see {@link MatterSpecification.v16.Core} § 4.14.1.2 */
64
64
  export const TlvPbkdfParamRequest = TlvObject({
65
65
  initiatorRandom: TlvField(1, TlvByteString.bound({ length: 32 })),
66
66
  initiatorSessionId: TlvField(2, TlvUInt16), // Specs: range: 16bits
@@ -73,7 +73,7 @@ export type PbkdfParamRequest = WithDurationSessionParameters<
73
73
  "initiatorSessionParams"
74
74
  >;
75
75
 
76
- /** @see {@link MatterSpecification.v13.Core} § 4.14.1.2 */
76
+ /** @see {@link MatterSpecification.v16.Core} § 4.14.1.2 */
77
77
  export const TlvPbkdfParamResponse = TlvObject({
78
78
  initiatorRandom: TlvField(1, TlvByteString.bound({ length: 32 })),
79
79
  responderRandom: TlvField(2, TlvByteString.bound({ length: 32 })),
@@ -92,20 +92,20 @@ export type PbkdfParamResponse = WithDurationSessionParameters<
92
92
  "responderSessionParams"
93
93
  >;
94
94
 
95
- /** @see {@link MatterSpecification.v13.Core} § 4.14.1.2 */
95
+ /** @see {@link MatterSpecification.v16.Core} § 4.14.1.2 */
96
96
  export const TlvPasePake1 = TlvObject({
97
97
  x: TlvField(1, TlvByteString.bound({ length: CRYPTO_PUBLIC_KEY_SIZE_BYTES })), // pA
98
98
  });
99
99
  export type PasePake1 = TypeFromSchema<typeof TlvPasePake1>;
100
100
 
101
- /** @see {@link MatterSpecification.v13.Core} § 4.14.1.2 */
101
+ /** @see {@link MatterSpecification.v16.Core} § 4.14.1.2 */
102
102
  export const TlvPasePake2 = TlvObject({
103
103
  y: TlvField(1, TlvByteString.bound({ length: CRYPTO_PUBLIC_KEY_SIZE_BYTES })), // pB
104
104
  verifier: TlvField(2, TlvByteString.bound({ length: CRYPTO_HASH_LEN_BYTES })), // cB
105
105
  });
106
106
  export type PasePake2 = TypeFromSchema<typeof TlvPasePake2>;
107
107
 
108
- /** @see {@link MatterSpecification.v13.Core} § 4.14.1.2 */
108
+ /** @see {@link MatterSpecification.v16.Core} § 4.14.1.2 */
109
109
  export const TlvPasePake3 = TlvObject({
110
110
  verifier: TlvField(1, TlvByteString.bound({ length: CRYPTO_HASH_LEN_BYTES })), // cA
111
111
  });