@matter/protocol 0.12.4-alpha.0-20250215-5af08a8d6 → 0.12.4-alpha.0-20250223-1e0341a1a

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 (48) hide show
  1. package/dist/cjs/interaction/InteractionMessenger.d.ts.map +1 -1
  2. package/dist/cjs/interaction/InteractionMessenger.js +5 -1
  3. package/dist/cjs/interaction/InteractionMessenger.js.map +1 -1
  4. package/dist/cjs/protocol/ExchangeManager.d.ts.map +1 -1
  5. package/dist/cjs/protocol/ExchangeManager.js +12 -8
  6. package/dist/cjs/protocol/ExchangeManager.js.map +1 -1
  7. package/dist/cjs/session/InsecureSession.d.ts.map +1 -1
  8. package/dist/cjs/session/InsecureSession.js +1 -0
  9. package/dist/cjs/session/InsecureSession.js.map +1 -1
  10. package/dist/cjs/session/SecureSession.d.ts.map +1 -1
  11. package/dist/cjs/session/SecureSession.js +4 -0
  12. package/dist/cjs/session/SecureSession.js.map +1 -1
  13. package/dist/cjs/session/Session.d.ts +3 -1
  14. package/dist/cjs/session/Session.d.ts.map +1 -1
  15. package/dist/cjs/session/Session.js +5 -1
  16. package/dist/cjs/session/Session.js.map +1 -1
  17. package/dist/cjs/session/case/CaseClient.d.ts.map +1 -1
  18. package/dist/cjs/session/case/CaseClient.js +22 -15
  19. package/dist/cjs/session/case/CaseClient.js.map +1 -1
  20. package/dist/cjs/session/case/CaseServer.js +1 -1
  21. package/dist/esm/interaction/InteractionMessenger.d.ts.map +1 -1
  22. package/dist/esm/interaction/InteractionMessenger.js +5 -1
  23. package/dist/esm/interaction/InteractionMessenger.js.map +1 -1
  24. package/dist/esm/protocol/ExchangeManager.d.ts.map +1 -1
  25. package/dist/esm/protocol/ExchangeManager.js +12 -8
  26. package/dist/esm/protocol/ExchangeManager.js.map +1 -1
  27. package/dist/esm/session/InsecureSession.d.ts.map +1 -1
  28. package/dist/esm/session/InsecureSession.js +1 -0
  29. package/dist/esm/session/InsecureSession.js.map +1 -1
  30. package/dist/esm/session/SecureSession.d.ts.map +1 -1
  31. package/dist/esm/session/SecureSession.js +4 -0
  32. package/dist/esm/session/SecureSession.js.map +1 -1
  33. package/dist/esm/session/Session.d.ts +3 -1
  34. package/dist/esm/session/Session.d.ts.map +1 -1
  35. package/dist/esm/session/Session.js +6 -2
  36. package/dist/esm/session/Session.js.map +1 -1
  37. package/dist/esm/session/case/CaseClient.d.ts.map +1 -1
  38. package/dist/esm/session/case/CaseClient.js +22 -15
  39. package/dist/esm/session/case/CaseClient.js.map +1 -1
  40. package/dist/esm/session/case/CaseServer.js +1 -1
  41. package/package.json +6 -6
  42. package/src/interaction/InteractionMessenger.ts +7 -2
  43. package/src/protocol/ExchangeManager.ts +12 -8
  44. package/src/session/InsecureSession.ts +1 -0
  45. package/src/session/SecureSession.ts +4 -0
  46. package/src/session/Session.ts +7 -2
  47. package/src/session/case/CaseClient.ts +18 -12
  48. package/src/session/case/CaseServer.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matter/protocol",
3
- "version": "0.12.4-alpha.0-20250215-5af08a8d6",
3
+ "version": "0.12.4-alpha.0-20250223-1e0341a1a",
4
4
  "description": "Low-level APIs for Matter interaction",
5
5
  "keywords": [
6
6
  "iot",
@@ -40,14 +40,14 @@
40
40
  "#*": "./src/*"
41
41
  },
42
42
  "dependencies": {
43
- "@matter/general": "0.12.4-alpha.0-20250215-5af08a8d6",
44
- "@matter/model": "0.12.4-alpha.0-20250215-5af08a8d6",
45
- "@matter/types": "0.12.4-alpha.0-20250215-5af08a8d6",
43
+ "@matter/general": "0.12.4-alpha.0-20250223-1e0341a1a",
44
+ "@matter/model": "0.12.4-alpha.0-20250223-1e0341a1a",
45
+ "@matter/types": "0.12.4-alpha.0-20250223-1e0341a1a",
46
46
  "@noble/curves": "^1.8.1"
47
47
  },
48
48
  "devDependencies": {
49
- "@matter/tools": "0.12.4-alpha.0-20250215-5af08a8d6",
50
- "@matter/testing": "0.12.4-alpha.0-20250215-5af08a8d6"
49
+ "@matter/tools": "0.12.4-alpha.0-20250223-1e0341a1a",
50
+ "@matter/testing": "0.12.4-alpha.0-20250223-1e0341a1a"
51
51
  },
52
52
  "files": [
53
53
  "dist/**/*",
@@ -542,7 +542,6 @@ export class InteractionServerMessenger extends InteractionMessenger {
542
542
  allowMissingFieldsForNonFabricFilteredRead,
543
543
  }),
544
544
  );
545
-
546
545
  break;
547
546
  }
548
547
  availableBytes -= encodedChunkDataSize;
@@ -583,6 +582,12 @@ export class InteractionServerMessenger extends InteractionMessenger {
583
582
  attributeReportsToSend.push(attributeToSend);
584
583
  continue;
585
584
  }
585
+ if (encodedSize > this.exchange.maxPayloadSize - emptyDataReportBytes.length - 3) {
586
+ // We sent the message but the current attribute is too big for a message alone so needs to
587
+ // be chunked, so add it to the queue at the beginning
588
+ attributeReportsToSend.unshift(attributeToSend);
589
+ continue;
590
+ }
586
591
  }
587
592
  messageSize += encodedSize;
588
593
  if (dataReport.attributeReports === undefined) {
@@ -626,7 +631,7 @@ export class InteractionServerMessenger extends InteractionMessenger {
626
631
  const encodedMessage = TlvDataReportForSend.encode(dataReportToSend);
627
632
  if (encodedMessage.length > this.exchange.maxPayloadSize) {
628
633
  throw new MatterFlowError(
629
- `DataReport is too long to fit in a single chunk, This should not happen! Data: ${Logger.toJSON(
634
+ `DataReport with ${encodedMessage.length}bytes is too long to fit in a single chunk (${this.exchange.maxPayloadSize}bytes), This should not happen! Data: ${Logger.toJSON(
630
635
  dataReportToSend,
631
636
  )}`,
632
637
  );
@@ -48,6 +48,7 @@ export class MessageChannel implements Channel<Message> {
48
48
  closeCallback?: () => Promise<void>,
49
49
  ) {
50
50
  this.#closeCallback = closeCallback;
51
+ this.session.destroyed.on(() => this.close());
51
52
  }
52
53
 
53
54
  set closeCallback(callback: () => Promise<void>) {
@@ -272,7 +273,7 @@ export class ExchangeManager {
272
273
  !message.payloadHeader.requiresAck
273
274
  ) {
274
275
  logger.debug(
275
- `Ignoring unsolicited standalone ack message ${messageId} for protocol ${message.payloadHeader.protocolId} and exchange id ${message.payloadHeader.exchangeId}.`,
276
+ `Ignoring unsolicited standalone ack message ${messageId} for protocol ${message.payloadHeader.protocolId} and exchange id ${message.payloadHeader.exchangeId} on channel ${channel.name}`,
276
277
  );
277
278
  return;
278
279
  }
@@ -295,7 +296,7 @@ export class ExchangeManager {
295
296
  });
296
297
  await exchange.close();
297
298
  logger.debug(
298
- `Ignoring unsolicited message ${messageId} for protocol ${message.payloadHeader.protocolId}.`,
299
+ `Ignoring unsolicited message ${messageId} for protocol ${message.payloadHeader.protocolId} on channel ${channel.name}`,
299
300
  );
300
301
  } else {
301
302
  if (protocolHandler === undefined) {
@@ -303,14 +304,14 @@ export class ExchangeManager {
303
304
  }
304
305
  if (isDuplicate) {
305
306
  logger.info(
306
- `Ignoring duplicate message ${messageId} (requires no ack) for protocol ${message.payloadHeader.protocolId}.`,
307
+ `Ignoring duplicate message ${messageId} (requires no ack) for protocol ${message.payloadHeader.protocolId} on channel ${channel.name}`,
307
308
  );
308
309
  return;
309
310
  } else {
310
311
  logger.info(
311
312
  `Discarding unexpected message ${messageId} for protocol ${
312
313
  message.payloadHeader.protocolId
313
- }, exchangeIndex ${exchangeIndex} and sessionId ${session.id} : ${Logger.toJSON(message)}`,
314
+ }, exchangeIndex ${exchangeIndex} and sessionId ${session.id} on channel ${channel.name}: ${Logger.toJSON(message)}`,
314
315
  );
315
316
  }
316
317
  }
@@ -355,7 +356,7 @@ export class ExchangeManager {
355
356
  }
356
357
  if (session.sendCloseMessageWhenClosing) {
357
358
  const channel = this.#channelManager.getChannelForSession(session);
358
- logger.debug(`Channel for session ${session.name} is ${channel?.name}`);
359
+ logger.debug(`Channel for session ${sessionName} is ${channel?.name}`);
359
360
  if (channel !== undefined) {
360
361
  const exchange = this.initiateExchangeWithChannel(channel, SECURE_CHANNEL_PROTOCOL_ID);
361
362
  if (exchange !== undefined) {
@@ -422,18 +423,21 @@ export class ExchangeManager {
422
423
  transportInterface.onData((socket, data) => {
423
424
  if (udpInterface && data.length > socket.maxPayloadSize) {
424
425
  logger.warn(
425
- `Ignoring UDP message with size ${data.length} from ${socket.name}, which is larger than the maximum allowed size of ${socket.maxPayloadSize}.`,
426
+ `Ignoring UDP message on channel ${socket.name} with size ${data.length} from ${socket.name}, which is larger than the maximum allowed size of ${socket.maxPayloadSize}.`,
426
427
  );
427
428
  return;
428
429
  }
429
430
 
430
431
  try {
431
432
  this.onMessage(socket, data).catch(error =>
432
- logger.info(error instanceof MatterError ? error.message : error),
433
+ logger.info(
434
+ `Error on channel ${socket.name}:`,
435
+ error instanceof MatterError ? error.message : error,
436
+ ),
433
437
  );
434
438
  } catch (error) {
435
439
  logger.info(
436
- "Ignoring UDP message with error",
440
+ `Ignoring UDP message on channel ${socket.name} with error`,
437
441
  error instanceof MatterError ? error.message : error,
438
442
  );
439
443
  }
@@ -87,6 +87,7 @@ export class InsecureSession extends Session {
87
87
 
88
88
  async destroy() {
89
89
  await this.end();
90
+ await this.destroyed.emit();
90
91
  }
91
92
 
92
93
  async end() {
@@ -327,9 +327,13 @@ export class SecureSession extends Session {
327
327
  await this.closer;
328
328
  } catch (error) {
329
329
  NoChannelError.accept(error);
330
+ } finally {
331
+ await this.destroyed.emit();
330
332
  }
333
+ return;
331
334
  }
332
335
  }
336
+ await this.destroyed.emit();
333
337
  }
334
338
 
335
339
  /**
@@ -4,7 +4,7 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
- import { Time } from "#general";
7
+ import { AsyncObservable, Time } from "#general";
8
8
  import { NodeId } from "#types";
9
9
  import { DecodedMessage, DecodedPacket, Message, Packet } from "../codec/MessageCodec.js";
10
10
  import { Fabric } from "../fabric/Fabric.js";
@@ -76,9 +76,10 @@ export abstract class Session {
76
76
  /**
77
77
  * If the ExchangeManager performs async work to clean up a session it sets this promise. This is because
78
78
  * historically we didn't return from destroy() until ExchangeManager was complete. Not sure if this is entirely
79
- * necessary but it makes sense so this allows us to maintain the old behavior.
79
+ * necessary, but it makes sense so this allows us to maintain the old behavior.
80
80
  */
81
81
  closer?: Promise<void>;
82
+ #destroyed = AsyncObservable<[]>();
82
83
 
83
84
  constructor(args: {
84
85
  manager?: SessionManager;
@@ -117,6 +118,10 @@ export abstract class Session {
117
118
  }
118
119
  }
119
120
 
121
+ get destroyed() {
122
+ return this.#destroyed;
123
+ }
124
+
120
125
  notifyActivity(messageReceived: boolean) {
121
126
  this.timestamp = Time.nowMs();
122
127
  if (messageReceived) {
@@ -79,7 +79,12 @@ export class CaseClient {
79
79
  if (sigma2Resume !== undefined) {
80
80
  // Process sigma2 resume
81
81
  if (resumptionRecord === undefined) throw new UnexpectedDataError("Received an unexpected sigma2Resume.");
82
- const { sharedSecret, fabric, sessionParameters: resumptionSessionParams } = resumptionRecord;
82
+ const {
83
+ sharedSecret,
84
+ fabric,
85
+ sessionParameters: resumptionSessionParams,
86
+ caseAuthenticatedTags,
87
+ } = resumptionRecord;
83
88
  const { responderSessionId: peerSessionId, resumptionId, resumeMic } = sigma2Resume;
84
89
 
85
90
  // We use the Fallbacks for the session parameters overridden by our stored ones from the resumption record
@@ -103,10 +108,11 @@ export class CaseClient {
103
108
  isInitiator: true,
104
109
  isResumption: true,
105
110
  peerSessionParameters: sessionParameters,
111
+ caseAuthenticatedTags,
106
112
  });
107
113
  await messenger.sendSuccess();
108
114
  logger.info(
109
- `Case client: Session resumed with ${messenger.getChannelName()} and parameters`,
115
+ `Case client: Session ${secureSession.id} successfully resumed with ${messenger.getChannelName()} for Fabric ${NodeId.toHexString(fabric.nodeId)}(index ${fabric.fabricIndex}) and PeerNode ${NodeId.toHexString(peerNodeId)} and parameters`,
110
116
  secureSession.parameterDiagnostics(),
111
117
  );
112
118
 
@@ -157,17 +163,12 @@ export class CaseClient {
157
163
 
158
164
  if (peerNodeIdNOCert !== peerNodeId) {
159
165
  throw new UnexpectedDataError(
160
- "The node ID in the peer certificate doesn't match the expected peer node ID",
161
- );
162
- }
163
- if (peerNodeIdNOCert !== peerNodeId) {
164
- throw new UnexpectedDataError(
165
- "The node ID in the peer certificate doesn't match the expected peer node ID",
166
+ `The node ID in the peer certificate ${peerNodeIdNOCert} doesn't match the expected peer node ID ${peerNodeId}`,
166
167
  );
167
168
  }
168
169
  if (peerFabricIdNOCert !== fabric.fabricId) {
169
170
  throw new UnexpectedDataError(
170
- "The fabric ID in the peer certificate doesn't match the expected fabric ID",
171
+ `The fabric ID in the peer certificate ${peerFabricIdNOCert} doesn't match the expected fabric ID ${fabric.fabricId}`,
171
172
  );
172
173
  }
173
174
  if (peerIntermediateCACert !== undefined) {
@@ -175,9 +176,9 @@ export class CaseClient {
175
176
  subject: { fabricId: peerFabricIdIcaCert },
176
177
  } = TlvIntermediateCertificate.decode(peerIntermediateCACert);
177
178
 
178
- if (peerFabricIdIcaCert !== fabric.fabricId) {
179
+ if (peerFabricIdIcaCert !== undefined && peerFabricIdIcaCert !== fabric.fabricId) {
179
180
  throw new UnexpectedDataError(
180
- "The fabric ID in the peer intermediate CA certificate doesn't match the expected fabric ID",
181
+ `The fabric ID in the peer intermediate CA certificate ${peerFabricIdIcaCert} doesn't match the expected fabric ID ${fabric.fabricId}`,
181
182
  );
182
183
  }
183
184
  }
@@ -199,6 +200,7 @@ export class CaseClient {
199
200
  await messenger.waitForSuccess("Sigma3-Success");
200
201
 
201
202
  // All good! Create secure session
203
+ const { caseAuthenticatedTags } = resumptionRecord ?? {}; // Even if resumption does not work try to reuse the caseAuthenticatedTags
202
204
  const secureSessionSalt = Bytes.concat(
203
205
  operationalIdentityProtectionKey,
204
206
  Crypto.hash([sigma1Bytes, sigma2Bytes, sigma3Bytes]),
@@ -213,9 +215,12 @@ export class CaseClient {
213
215
  isInitiator: true,
214
216
  isResumption: false,
215
217
  peerSessionParameters: sessionParameters,
218
+ caseAuthenticatedTags,
216
219
  });
217
220
  logger.info(
218
- `Case client: Paired successfully with ${messenger.getChannelName()} and parameters`,
221
+ `Case client Session ${secureSession.id} established successfully with ${messenger.getChannelName()} for Fabric ${NodeId.toHexString(
222
+ fabric.nodeId,
223
+ )}(index ${fabric.fabricIndex}) and PeerNode ${NodeId.toHexString(peerNodeId)}and parameters`,
219
224
  secureSession.parameterDiagnostics(),
220
225
  );
221
226
  resumptionRecord = {
@@ -224,6 +229,7 @@ export class CaseClient {
224
229
  sharedSecret,
225
230
  resumptionId: peerResumptionId,
226
231
  sessionParameters: secureSession.parameters,
232
+ caseAuthenticatedTags,
227
233
  };
228
234
  }
229
235
 
@@ -234,7 +234,7 @@ export class CaseServer implements ProtocolHandler {
234
234
  caseAuthenticatedTags,
235
235
  });
236
236
  logger.info(
237
- `session ${secureSession.id} created with ${messenger.getChannelName()} for Fabric ${NodeId.toHexString(
237
+ `Session ${secureSession.id} created with ${messenger.getChannelName()} for Fabric ${NodeId.toHexString(
238
238
  fabric.nodeId,
239
239
  )}(index ${fabric.fabricIndex}) and PeerNode ${NodeId.toHexString(peerNodeId)}`,
240
240
  "with CATs",