@matter/protocol 0.15.4 → 0.15.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/dist/cjs/action/client/ReadScope.d.ts +4 -0
  2. package/dist/cjs/action/client/ReadScope.d.ts.map +1 -1
  3. package/dist/cjs/action/client/ReadScope.js +2 -1
  4. package/dist/cjs/action/client/ReadScope.js.map +1 -1
  5. package/dist/cjs/action/server/EventReadResponse.d.ts.map +1 -1
  6. package/dist/cjs/action/server/EventReadResponse.js +2 -2
  7. package/dist/cjs/action/server/EventReadResponse.js.map +1 -1
  8. package/dist/cjs/events/OccurrenceManager.d.ts.map +1 -1
  9. package/dist/cjs/events/OccurrenceManager.js +2 -1
  10. package/dist/cjs/events/OccurrenceManager.js.map +1 -1
  11. package/dist/cjs/fabric/Fabric.d.ts.map +1 -1
  12. package/dist/cjs/fabric/Fabric.js.map +1 -1
  13. package/dist/cjs/fabric/FabricAuthority.d.ts +0 -1
  14. package/dist/cjs/fabric/FabricAuthority.d.ts.map +1 -1
  15. package/dist/cjs/fabric/FabricAuthority.js +2 -3
  16. package/dist/cjs/fabric/FabricAuthority.js.map +1 -1
  17. package/dist/cjs/fabric/TestFabric.d.ts.map +1 -1
  18. package/dist/cjs/fabric/TestFabric.js +2 -1
  19. package/dist/cjs/fabric/TestFabric.js.map +1 -1
  20. package/dist/cjs/interaction/AttributeDataDecoder.d.ts +9 -7
  21. package/dist/cjs/interaction/AttributeDataDecoder.d.ts.map +1 -1
  22. package/dist/cjs/interaction/AttributeDataDecoder.js.map +1 -1
  23. package/dist/cjs/interaction/DecodedDataReport.d.ts +1 -0
  24. package/dist/cjs/interaction/DecodedDataReport.d.ts.map +1 -1
  25. package/dist/cjs/interaction/DecodedDataReport.js.map +1 -1
  26. package/dist/cjs/interaction/InteractionClient.d.ts +12 -2
  27. package/dist/cjs/interaction/InteractionClient.d.ts.map +1 -1
  28. package/dist/cjs/interaction/InteractionClient.js +94 -67
  29. package/dist/cjs/interaction/InteractionClient.js.map +1 -1
  30. package/dist/cjs/interaction/InteractionMessenger.d.ts +10 -90
  31. package/dist/cjs/interaction/InteractionMessenger.d.ts.map +1 -1
  32. package/dist/cjs/interaction/InteractionMessenger.js +113 -31
  33. package/dist/cjs/interaction/InteractionMessenger.js.map +2 -2
  34. package/dist/cjs/interaction/SubscriptionClient.d.ts +2 -2
  35. package/dist/cjs/interaction/SubscriptionClient.d.ts.map +1 -1
  36. package/dist/cjs/interaction/SubscriptionClient.js +1 -1
  37. package/dist/cjs/interaction/SubscriptionClient.js.map +1 -1
  38. package/dist/cjs/mdns/MdnsScanner.d.ts.map +1 -1
  39. package/dist/cjs/mdns/MdnsScanner.js +1 -2
  40. package/dist/cjs/mdns/MdnsScanner.js.map +1 -1
  41. package/dist/cjs/mdns/MdnsServer.js +1 -1
  42. package/dist/cjs/mdns/MdnsServer.js.map +1 -1
  43. package/dist/cjs/peer/PeerAddressStore.d.ts +3 -1
  44. package/dist/cjs/peer/PeerAddressStore.d.ts.map +1 -1
  45. package/dist/cjs/peer/PeerAddressStore.js.map +1 -1
  46. package/dist/cjs/protocol/ExchangeManager.js +2 -2
  47. package/dist/cjs/protocol/ExchangeManager.js.map +1 -1
  48. package/dist/cjs/protocol/MessageExchange.d.ts.map +1 -1
  49. package/dist/cjs/protocol/MessageExchange.js +1 -0
  50. package/dist/cjs/protocol/MessageExchange.js.map +1 -1
  51. package/dist/cjs/session/SessionManager.d.ts.map +1 -1
  52. package/dist/cjs/session/SessionManager.js +17 -9
  53. package/dist/cjs/session/SessionManager.js.map +1 -1
  54. package/dist/cjs/session/pase/PaseServer.d.ts.map +1 -1
  55. package/dist/cjs/session/pase/PaseServer.js +2 -1
  56. package/dist/cjs/session/pase/PaseServer.js.map +1 -1
  57. package/dist/esm/action/client/ReadScope.d.ts +4 -0
  58. package/dist/esm/action/client/ReadScope.d.ts.map +1 -1
  59. package/dist/esm/action/client/ReadScope.js +2 -1
  60. package/dist/esm/action/client/ReadScope.js.map +1 -1
  61. package/dist/esm/action/server/EventReadResponse.d.ts.map +1 -1
  62. package/dist/esm/action/server/EventReadResponse.js +3 -2
  63. package/dist/esm/action/server/EventReadResponse.js.map +1 -1
  64. package/dist/esm/events/OccurrenceManager.d.ts.map +1 -1
  65. package/dist/esm/events/OccurrenceManager.js +2 -1
  66. package/dist/esm/events/OccurrenceManager.js.map +1 -1
  67. package/dist/esm/fabric/Fabric.d.ts.map +1 -1
  68. package/dist/esm/fabric/Fabric.js.map +1 -1
  69. package/dist/esm/fabric/FabricAuthority.d.ts +0 -1
  70. package/dist/esm/fabric/FabricAuthority.d.ts.map +1 -1
  71. package/dist/esm/fabric/FabricAuthority.js +2 -3
  72. package/dist/esm/fabric/FabricAuthority.js.map +1 -1
  73. package/dist/esm/fabric/TestFabric.d.ts.map +1 -1
  74. package/dist/esm/fabric/TestFabric.js +3 -2
  75. package/dist/esm/fabric/TestFabric.js.map +1 -1
  76. package/dist/esm/interaction/AttributeDataDecoder.d.ts +9 -7
  77. package/dist/esm/interaction/AttributeDataDecoder.d.ts.map +1 -1
  78. package/dist/esm/interaction/AttributeDataDecoder.js.map +1 -1
  79. package/dist/esm/interaction/DecodedDataReport.d.ts +1 -0
  80. package/dist/esm/interaction/DecodedDataReport.d.ts.map +1 -1
  81. package/dist/esm/interaction/DecodedDataReport.js.map +1 -1
  82. package/dist/esm/interaction/InteractionClient.d.ts +12 -2
  83. package/dist/esm/interaction/InteractionClient.d.ts.map +1 -1
  84. package/dist/esm/interaction/InteractionClient.js +96 -68
  85. package/dist/esm/interaction/InteractionClient.js.map +1 -1
  86. package/dist/esm/interaction/InteractionMessenger.d.ts +10 -90
  87. package/dist/esm/interaction/InteractionMessenger.d.ts.map +1 -1
  88. package/dist/esm/interaction/InteractionMessenger.js +113 -31
  89. package/dist/esm/interaction/InteractionMessenger.js.map +2 -2
  90. package/dist/esm/interaction/SubscriptionClient.d.ts +2 -2
  91. package/dist/esm/interaction/SubscriptionClient.d.ts.map +1 -1
  92. package/dist/esm/interaction/SubscriptionClient.js +1 -1
  93. package/dist/esm/interaction/SubscriptionClient.js.map +1 -1
  94. package/dist/esm/mdns/MdnsScanner.d.ts.map +1 -1
  95. package/dist/esm/mdns/MdnsScanner.js +1 -2
  96. package/dist/esm/mdns/MdnsScanner.js.map +1 -1
  97. package/dist/esm/mdns/MdnsServer.js +1 -1
  98. package/dist/esm/mdns/MdnsServer.js.map +1 -1
  99. package/dist/esm/peer/PeerAddressStore.d.ts +3 -1
  100. package/dist/esm/peer/PeerAddressStore.d.ts.map +1 -1
  101. package/dist/esm/peer/PeerAddressStore.js.map +1 -1
  102. package/dist/esm/protocol/ExchangeManager.js +2 -2
  103. package/dist/esm/protocol/ExchangeManager.js.map +1 -1
  104. package/dist/esm/protocol/MessageExchange.d.ts.map +1 -1
  105. package/dist/esm/protocol/MessageExchange.js +1 -0
  106. package/dist/esm/protocol/MessageExchange.js.map +1 -1
  107. package/dist/esm/session/SessionManager.d.ts.map +1 -1
  108. package/dist/esm/session/SessionManager.js +19 -10
  109. package/dist/esm/session/SessionManager.js.map +1 -1
  110. package/dist/esm/session/pase/PaseServer.d.ts.map +1 -1
  111. package/dist/esm/session/pase/PaseServer.js +2 -1
  112. package/dist/esm/session/pase/PaseServer.js.map +1 -1
  113. package/package.json +6 -6
  114. package/src/action/client/ReadScope.ts +7 -0
  115. package/src/action/server/EventReadResponse.ts +7 -3
  116. package/src/events/OccurrenceManager.ts +2 -1
  117. package/src/fabric/Fabric.ts +1 -1
  118. package/src/fabric/FabricAuthority.ts +2 -2
  119. package/src/fabric/TestFabric.ts +2 -1
  120. package/src/interaction/AttributeDataDecoder.ts +4 -1
  121. package/src/interaction/DecodedDataReport.ts +1 -0
  122. package/src/interaction/InteractionClient.ts +152 -77
  123. package/src/interaction/InteractionMessenger.ts +143 -31
  124. package/src/interaction/SubscriptionClient.ts +6 -5
  125. package/src/mdns/MdnsScanner.ts +1 -2
  126. package/src/mdns/MdnsServer.ts +2 -2
  127. package/src/peer/PeerAddressStore.ts +3 -1
  128. package/src/protocol/ExchangeManager.ts +2 -2
  129. package/src/protocol/MessageExchange.ts +1 -0
  130. package/src/session/SessionManager.ts +35 -24
  131. package/src/session/pase/PaseServer.ts +2 -1
@@ -13,14 +13,20 @@ import {
13
13
  NoResponseTimeoutError,
14
14
  UnexpectedDataError,
15
15
  } from "#general";
16
+ import { DecodedAttributeReportValue } from "#interaction/AttributeDataDecoder.js";
17
+ import { DecodedDataReport } from "#interaction/DecodedDataReport.js";
16
18
  import { Specification } from "#model";
17
19
  import { ChannelNotConnectedError } from "#protocol/MessageChannel.js";
18
20
  import {
21
+ AttributeId,
22
+ ClusterId,
23
+ EndpointNumber,
19
24
  ReceivedStatusResponseError,
20
25
  Status,
21
26
  StatusCode,
22
27
  StatusResponseError,
23
28
  TlvAny,
29
+ TlvAttributeReport,
24
30
  TlvDataReport,
25
31
  TlvDataReportForSend,
26
32
  TlvDataVersionFilter,
@@ -796,8 +802,105 @@ export class IncomingInteractionClientMessenger extends InteractionMessenger {
796
802
  return message;
797
803
  }
798
804
 
799
- async readAggregateDataReport(expectedSubscriptionIds?: number[]): Promise<DataReport> {
800
- let result: DataReport | undefined;
805
+ /**
806
+ * Reads data report stream and aggregates them into a single report.
807
+ * Additionally, a callback can be provided that is called for each cluster chunk received.
808
+ */
809
+ async readAggregateDataReport(
810
+ chunkListener?: (chunk: DecodedAttributeReportValue<any>[]) => Promise<void>,
811
+ expectedSubscriptionIds?: number[],
812
+ ): Promise<DecodedDataReport> {
813
+ let result: DecodedDataReport | undefined = undefined;
814
+ let currentEndpointId: EndpointNumber | undefined = undefined;
815
+ let currentClusterId: ClusterId | undefined = undefined;
816
+ const currentClusterChunk = new Array<DecodedAttributeReportValue<any>>();
817
+ let pendingAttributeReports: TypeFromSchema<typeof TlvAttributeReport>[] | undefined = undefined;
818
+
819
+ const handleAttributeReportEntries = (
820
+ attributeReports: TypeFromSchema<typeof TlvAttributeReport>[] | undefined,
821
+ previousPendingAttributeReports: TypeFromSchema<typeof TlvAttributeReport>[] | undefined,
822
+ ) => {
823
+ if (previousPendingAttributeReports?.length) {
824
+ attributeReports = attributeReports ?? [];
825
+ attributeReports.unshift(...previousPendingAttributeReports);
826
+ }
827
+
828
+ let lastAttributeDataIndex = -1;
829
+ if (attributeReports?.length) {
830
+ let lastEndpointId: EndpointNumber | undefined = undefined;
831
+ let lastClusterId: ClusterId | undefined = undefined;
832
+ let lastAttributeId: AttributeId | undefined = undefined;
833
+ for (let i = attributeReports.length - 1; i >= 0; i--) {
834
+ const attributeReport = attributeReports[i];
835
+ if (attributeReport.attributeData === undefined) {
836
+ break; // No data report, so nothing more to search for
837
+ }
838
+ const {
839
+ path: { endpointId, clusterId, attributeId },
840
+ } = attributeReport.attributeData;
841
+ if (lastEndpointId === undefined && lastClusterId === undefined && lastAttributeId === undefined) {
842
+ // Remember path of the last attribute data entry and check if previous entries match
843
+ lastEndpointId = endpointId;
844
+ lastClusterId = clusterId;
845
+ lastAttributeId = attributeId;
846
+ }
847
+ if (
848
+ endpointId === lastEndpointId &&
849
+ clusterId === lastClusterId &&
850
+ attributeId === lastAttributeId
851
+ ) {
852
+ lastAttributeDataIndex = i;
853
+ continue;
854
+ }
855
+ break; // We found an attribute that does not match the last one, so we are done
856
+ }
857
+
858
+ if (lastAttributeDataIndex > 0) {
859
+ return attributeReports.splice(lastAttributeDataIndex);
860
+ }
861
+ }
862
+ };
863
+
864
+ const processDecodedReport = async (
865
+ decodedReport: DecodedDataReport,
866
+ result: DecodedDataReport | undefined,
867
+ ) => {
868
+ if (!result) {
869
+ result = decodedReport;
870
+ } else {
871
+ if (!result.attributeReports) {
872
+ result.attributeReports = decodedReport.attributeReports;
873
+ } else {
874
+ result.attributeReports.push(...decodedReport.attributeReports);
875
+ }
876
+ if (Array.isArray(decodedReport.eventReports)) {
877
+ if (!result.eventReports) {
878
+ result.eventReports = decodedReport.eventReports;
879
+ } else {
880
+ result.eventReports.push(...decodedReport.eventReports);
881
+ }
882
+ }
883
+ }
884
+
885
+ if (chunkListener !== undefined && decodedReport.attributeReports) {
886
+ for (const data of decodedReport.attributeReports) {
887
+ const {
888
+ path: { endpointId, clusterId },
889
+ } = data;
890
+ if (currentEndpointId !== endpointId || currentClusterId !== clusterId) {
891
+ // We switched the cluster, so we need to send the current chunk first
892
+ if (currentClusterChunk.length > 0) {
893
+ await chunkListener(currentClusterChunk);
894
+ currentClusterChunk.length = 0;
895
+ }
896
+ currentEndpointId = endpointId;
897
+ currentClusterId = clusterId;
898
+ }
899
+ currentClusterChunk.push(data);
900
+ }
901
+ }
902
+ return result;
903
+ };
801
904
 
802
905
  for await (const report of this.readDataReports()) {
803
906
  if (expectedSubscriptionIds !== undefined) {
@@ -820,24 +923,25 @@ export class IncomingInteractionClientMessenger extends InteractionMessenger {
820
923
  throw new UnexpectedDataError(`Invalid subscription ID ${report.subscriptionId} received`);
821
924
  }
822
925
 
823
- if (!result) {
824
- result = report;
825
- } else {
826
- if (Array.isArray(report.attributeReports)) {
827
- if (!result.attributeReports) {
828
- result.attributeReports = report.attributeReports;
829
- } else {
830
- result.attributeReports.push(...report.attributeReports);
831
- }
832
- }
833
- if (Array.isArray(report.eventReports)) {
834
- if (!result.eventReports) {
835
- result.eventReports = report.eventReports;
836
- } else {
837
- result.eventReports.push(...report.eventReports);
838
- }
839
- }
840
- }
926
+ report.attributeReports = report.attributeReports ?? [];
927
+ pendingAttributeReports = handleAttributeReportEntries(report.attributeReports, pendingAttributeReports);
928
+
929
+ result = await processDecodedReport(DecodedDataReport(report), result);
930
+ }
931
+
932
+ if (pendingAttributeReports?.length && result !== undefined) {
933
+ result = await processDecodedReport(
934
+ DecodedDataReport({
935
+ interactionModelRevision: result.interactionModelRevision,
936
+ attributeReports: pendingAttributeReports,
937
+ }),
938
+ result,
939
+ );
940
+ }
941
+
942
+ if (chunkListener !== undefined && currentClusterChunk.length > 0) {
943
+ await chunkListener(currentClusterChunk);
944
+ currentClusterChunk.length = 0;
841
945
  }
842
946
 
843
947
  if (result === undefined) {
@@ -1007,8 +1111,8 @@ export class InteractionClientMessenger extends IncomingInteractionClientMesseng
1007
1111
  await this.send(MessageType.SubscribeRequest, request);
1008
1112
  }
1009
1113
 
1010
- async readAggregateSubscribeResponse() {
1011
- const report = await this.readAggregateDataReport();
1114
+ async readAggregateSubscribeResponse(chunkListener?: (chunk: DecodedAttributeReportValue<any>[]) => Promise<void>) {
1115
+ const report = await this.readAggregateDataReport(chunkListener);
1012
1116
  const { subscriptionId } = report;
1013
1117
 
1014
1118
  if (subscriptionId === undefined) {
@@ -1077,15 +1181,23 @@ export class InteractionClientMessenger extends IncomingInteractionClientMesseng
1077
1181
  request: RequestT,
1078
1182
  expectedProcessingTimeMs?: number,
1079
1183
  ): Promise<void> {
1080
- await this.send(requestMessageType, requestSchema.encode(request), {
1081
- expectAckOnly: true,
1082
- expectedProcessingTimeMs,
1083
- logContext: {
1084
- invokeFlags: Diagnostic.asFlags({
1085
- suppressResponse: true,
1086
- }),
1087
- },
1088
- });
1184
+ try {
1185
+ await this.send(requestMessageType, requestSchema.encode(request), {
1186
+ expectAckOnly: true,
1187
+ expectedProcessingTimeMs,
1188
+ logContext: {
1189
+ invokeFlags: Diagnostic.asFlags({
1190
+ suppressResponse: true,
1191
+ }),
1192
+ },
1193
+ });
1194
+ } catch (e) {
1195
+ // If we got anything other than a standalone ack, we throw when receiving a status response, otherwise we ignore it
1196
+ // Mainly needed because of a Matter-SDK bug where a command response is sent even when suppressResponse is true
1197
+ UnexpectedMessageError.accept(e);
1198
+ const { receivedMessage } = e;
1199
+ this.throwIfErrorStatusMessage(receivedMessage);
1200
+ }
1089
1201
  }
1090
1202
 
1091
1203
  private async request<RequestT, ResponseT>(
@@ -5,10 +5,11 @@
5
5
  */
6
6
 
7
7
  import { Environment, Environmental, Logger, MaybePromise, Time, Timer } from "#general";
8
+ import { DecodedDataReport } from "#interaction/DecodedDataReport.js";
8
9
  import { MessageExchange } from "#protocol/MessageExchange.js";
9
10
  import { ProtocolHandler } from "#protocol/ProtocolHandler.js";
10
11
  import { INTERACTION_PROTOCOL_ID } from "#types";
11
- import { DataReport, IncomingInteractionClientMessenger } from "./InteractionMessenger.js";
12
+ import { IncomingInteractionClientMessenger } from "./InteractionMessenger.js";
12
13
 
13
14
  const logger = Logger.get("SubscriptionClient");
14
15
 
@@ -16,7 +17,7 @@ export interface RegisteredSubscription {
16
17
  id: number;
17
18
  maximumPeerResponseTimeMs: number;
18
19
  maxIntervalS: number;
19
- onData: (dataReport: DataReport) => MaybePromise<void>;
20
+ onData: (dataReport: DecodedDataReport) => MaybePromise<void>;
20
21
  onTimeout?: () => void;
21
22
  }
22
23
 
@@ -28,7 +29,7 @@ export interface RegisteredSubscription {
28
29
  export class SubscriptionClient implements ProtocolHandler {
29
30
  readonly id = INTERACTION_PROTOCOL_ID;
30
31
  readonly requiresSecureSession = true;
31
- readonly #listeners = new Map<number, (dataReport: DataReport) => MaybePromise<void>>();
32
+ readonly #listeners = new Map<number, (dataReport: DecodedDataReport) => MaybePromise<void>>();
32
33
  readonly #timeouts = new Map<number, Timer>();
33
34
 
34
35
  constructor() {}
@@ -80,10 +81,10 @@ export class SubscriptionClient implements ProtocolHandler {
80
81
  async onNewExchange(exchange: MessageExchange) {
81
82
  const messenger = new IncomingInteractionClientMessenger(exchange);
82
83
 
83
- let dataReport: DataReport;
84
+ let dataReport: DecodedDataReport;
84
85
  try {
85
86
  // TODO Adjust this to getting packages as callback when received to handle error cases and checks outside
86
- dataReport = await messenger.readAggregateDataReport([...this.#listeners.keys()]);
87
+ dataReport = await messenger.readAggregateDataReport(undefined, [...this.#listeners.keys()]);
87
88
  } finally {
88
89
  messenger.close().catch(error => logger.info("Error closing client messenger", error));
89
90
  }
@@ -1042,8 +1042,7 @@ export class MdnsScanner implements Scanner {
1042
1042
  if (this.#closing) return;
1043
1043
  const message = DnsCodec.decode(messageBytes);
1044
1044
  if (message === undefined) return; // The message cannot be parsed
1045
- if (message.messageType !== DnsMessageType.Response && message.messageType !== DnsMessageType.TruncatedResponse)
1046
- return;
1045
+ if (!DnsMessageType.isResponse(message.messageType)) return;
1047
1046
 
1048
1047
  const answers = this.#structureAnswers([...message.answers, ...message.additionalRecords]);
1049
1048
 
@@ -100,8 +100,8 @@ export class MdnsServer {
100
100
  const message = DnsCodec.decode(messageBytes);
101
101
  if (message === undefined) return; // The message cannot be parsed
102
102
  const { transactionId, messageType, queries, answers: knownAnswers } = message;
103
- if (messageType !== DnsMessageType.Query && messageType !== DnsMessageType.TruncatedQuery) return;
104
- if (queries.length === 0) return; // No queries to answer, can happen in a TruncatedQuery, let's ignore for now
103
+ if (!DnsMessageType.isQuery(messageType)) return;
104
+ if (queries.length === 0) return; // TODO correctly handle TruncatedQueries by waiting and combining multiple queries
105
105
  for (const portRecords of records.values()) {
106
106
  let answers = queries.flatMap(query => this.#queryRecords(query, portRecords));
107
107
  if (answers.length === 0) continue;
@@ -4,6 +4,7 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
+ import { ReadScope } from "#action/client/ReadScope.js";
7
8
  import { Construction, MaybePromise } from "#general";
8
9
  import { DecodedAttributeReportValue } from "#interaction/AttributeDataDecoder.js";
9
10
  import { AttributeId, ClusterId, EndpointNumber, EventNumber } from "#types";
@@ -27,7 +28,7 @@ export abstract class PeerDataStore {
27
28
  abstract maxEventNumber: EventNumber;
28
29
  abstract updateLastEventNumber(eventNumber: EventNumber): MaybePromise<void>;
29
30
 
30
- abstract persistAttributes(attributes: DecodedAttributeReportValue<any>[]): MaybePromise<void>;
31
+ abstract persistAttributes(attributes: DecodedAttributeReportValue<any>[], scope: ReadScope): MaybePromise<void>;
31
32
 
32
33
  // TODO: Find a maybe better way to achieve this without functions
33
34
  abstract retrieveAttribute(
@@ -41,4 +42,5 @@ export abstract class PeerDataStore {
41
42
  filterEndpointId?: EndpointNumber,
42
43
  filterClusterId?: ClusterId,
43
44
  ): { endpointId: EndpointNumber; clusterId: ClusterId; dataVersion: number }[];
45
+ abstract cleanupAttributeData(endpointId: EndpointNumber, clusterIds?: ClusterId[]): MaybePromise<void>;
44
46
  }
@@ -327,9 +327,10 @@ export class ExchangeManager {
327
327
  return;
328
328
  }
329
329
  const { session } = exchange;
330
+ this.#exchanges.delete(exchangeIndex);
330
331
  if (NodeSession.is(session) && session.closingAfterExchangeFinished) {
331
332
  logger.debug(
332
- `Exchange index ${exchangeIndex} Session ${session.name} is already marked for closure. Close session now.`,
333
+ `Exchange index ${exchangeIndex} on Session ${session.name} is already marked for closure. Close session now.`,
333
334
  );
334
335
  try {
335
336
  await this.#closeSession(session);
@@ -337,7 +338,6 @@ export class ExchangeManager {
337
338
  logger.error(`Error closing session ${session.name}. Ignoring.`, error);
338
339
  }
339
340
  }
340
- this.#exchanges.delete(exchangeIndex);
341
341
  }
342
342
 
343
343
  async #closeSession(session: NodeSession) {
@@ -200,6 +200,7 @@ export class MessageExchange {
200
200
 
201
201
  logger.debug(
202
202
  "New exchange",
203
+ isInitiator ? "»" : "«",
203
204
  Diagnostic.dict({
204
205
  channel: channel.name,
205
206
  protocol: this.#protocolId,
@@ -21,6 +21,7 @@ import {
21
21
  ObserverGroup,
22
22
  StorageContext,
23
23
  StorageManager,
24
+ toHex,
24
25
  } from "#general";
25
26
  import { Subscription } from "#interaction/Subscription.js";
26
27
  import { Specification } from "#model";
@@ -76,6 +77,7 @@ type ResumptionStorageRecord = {
76
77
  sharedSecret: Uint8Array;
77
78
  resumptionId: Uint8Array;
78
79
  fabricId: FabricId;
80
+ fabricIndex: FabricIndex;
79
81
  peerNodeId: NodeId;
80
82
  sessionParameters: {
81
83
  idleIntervalMs: number;
@@ -545,21 +547,21 @@ export class SessionManager {
545
547
  ([
546
548
  address,
547
549
  { sharedSecret, resumptionId, peerNodeId, fabric, sessionParameters, caseAuthenticatedTags },
548
- ]) =>
549
- ({
550
- nodeId: address.nodeId,
551
- sharedSecret,
552
- resumptionId,
553
- fabricId: fabric.fabricId,
554
- peerNodeId: peerNodeId,
555
- sessionParameters: {
556
- ...sessionParameters,
557
- supportedTransports: sessionParameters.supportedTransports
558
- ? SupportedTransportsSchema.encode(sessionParameters.supportedTransports)
559
- : undefined,
560
- },
561
- caseAuthenticatedTags,
562
- }) as ResumptionStorageRecord,
550
+ ]): ResumptionStorageRecord => ({
551
+ nodeId: address.nodeId,
552
+ sharedSecret,
553
+ resumptionId,
554
+ fabricId: fabric.fabricId,
555
+ fabricIndex: fabric.fabricIndex,
556
+ peerNodeId: peerNodeId,
557
+ sessionParameters: {
558
+ ...sessionParameters,
559
+ supportedTransports: sessionParameters.supportedTransports
560
+ ? SupportedTransportsSchema.encode(sessionParameters.supportedTransports)
561
+ : undefined,
562
+ },
563
+ caseAuthenticatedTags,
564
+ }),
563
565
  ),
564
566
  );
565
567
  }
@@ -578,6 +580,7 @@ export class SessionManager {
578
580
  sharedSecret,
579
581
  resumptionId,
580
582
  fabricId,
583
+ fabricIndex,
581
584
  peerNodeId,
582
585
  sessionParameters: {
583
586
  idleIntervalMs,
@@ -592,19 +595,27 @@ export class SessionManager {
592
595
  } = {},
593
596
  caseAuthenticatedTags,
594
597
  }) => {
595
- const fabric = this.#context.fabrics.find(fabric => fabric.fabricId === fabricId);
596
- logger.info(
597
- "restoring resumption record for node",
598
- nodeId,
599
- "and peer node",
600
- peerNodeId,
601
- "for fabric index",
602
- fabric?.fabricIndex,
598
+ const fabric = this.#context.fabrics.find(
599
+ fabric =>
600
+ fabric.fabricId === fabricId &&
601
+ // Backward compatibility logic: fabricIndex was added later (0.15.5), so it might be undefined in older records
602
+ (fabricIndex === undefined || fabric.fabricIndex === fabricIndex),
603
603
  );
604
604
  if (!fabric) {
605
- logger.error("fabric not found for resumption record", fabricId);
605
+ logger.warn(
606
+ `Ignoring resumption record for fabric 0x${toHex(fabricId)} and index ${fabricIndex} because we cannot find a matching fabric`,
607
+ );
606
608
  return;
607
609
  }
610
+ logger.info(
611
+ "restoring resumption record for node",
612
+ fabric.addressOf(nodeId).toString(),
613
+ "and peer node",
614
+ fabric.addressOf(peerNodeId).toString(),
615
+ "for fabric id",
616
+ `0x${toHex(fabric.fabricId)}`,
617
+ `(0x${toHex(fabric.rootVendorId)}, "${fabric?.label}")`,
618
+ );
608
619
  this.#resumptionRecords.set(fabric.addressOf(nodeId), {
609
620
  sharedSecret,
610
621
  resumptionId,
@@ -68,7 +68,8 @@ export class PaseServer implements ProtocolHandler {
68
68
  // successfully established a session, the Commissionee SHALL NOT accept any more requests for new PASE
69
69
  // sessions until session establishment fails or the successfully established PASE session is terminated on
70
70
  // the commissioning channel.
71
- if (this.sessions.getPaseSession()) {
71
+ const paseSession = this.sessions.getPaseSession();
72
+ if (paseSession !== undefined && !paseSession.isClosing) {
72
73
  logger.info("Pase server: Pairing already in progress (PASE session exists), ignoring new exchange.");
73
74
  } else if (this.#pairingTimer?.isRunning) {
74
75
  logger.info(