@matter/protocol 0.16.0-alpha.0-20251001-7eb06da95 → 0.16.0-alpha.0-20251004-92135c7df

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 (191) 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/ble/Ble.d.ts +3 -3
  6. package/dist/cjs/ble/Ble.d.ts.map +1 -1
  7. package/dist/cjs/ble/Ble.js.map +1 -1
  8. package/dist/cjs/common/Scanner.d.ts +2 -2
  9. package/dist/cjs/common/Scanner.d.ts.map +1 -1
  10. package/dist/cjs/interaction/AttributeDataDecoder.d.ts +9 -7
  11. package/dist/cjs/interaction/AttributeDataDecoder.d.ts.map +1 -1
  12. package/dist/cjs/interaction/AttributeDataDecoder.js.map +1 -1
  13. package/dist/cjs/interaction/DecodedDataReport.d.ts +1 -0
  14. package/dist/cjs/interaction/DecodedDataReport.d.ts.map +1 -1
  15. package/dist/cjs/interaction/DecodedDataReport.js.map +1 -1
  16. package/dist/cjs/interaction/InteractionClient.d.ts +13 -3
  17. package/dist/cjs/interaction/InteractionClient.d.ts.map +1 -1
  18. package/dist/cjs/interaction/InteractionClient.js +94 -67
  19. package/dist/cjs/interaction/InteractionClient.js.map +1 -1
  20. package/dist/cjs/interaction/InteractionMessenger.d.ts +10 -90
  21. package/dist/cjs/interaction/InteractionMessenger.d.ts.map +1 -1
  22. package/dist/cjs/interaction/InteractionMessenger.js +98 -22
  23. package/dist/cjs/interaction/InteractionMessenger.js.map +2 -2
  24. package/dist/cjs/interaction/SubscriptionClient.d.ts +2 -2
  25. package/dist/cjs/interaction/SubscriptionClient.d.ts.map +1 -1
  26. package/dist/cjs/interaction/SubscriptionClient.js +1 -1
  27. package/dist/cjs/interaction/SubscriptionClient.js.map +1 -1
  28. package/dist/cjs/mdns/MdnsClient.d.ts +3 -3
  29. package/dist/cjs/mdns/MdnsClient.d.ts.map +1 -1
  30. package/dist/cjs/mdns/MdnsClient.js +28 -14
  31. package/dist/cjs/mdns/MdnsClient.js.map +1 -1
  32. package/dist/cjs/mdns/MdnsServer.d.ts.map +1 -1
  33. package/dist/cjs/mdns/MdnsServer.js +59 -5
  34. package/dist/cjs/mdns/MdnsServer.js.map +1 -1
  35. package/dist/cjs/mdns/MdnsSocket.d.ts.map +1 -1
  36. package/dist/cjs/mdns/MdnsSocket.js +10 -1
  37. package/dist/cjs/mdns/MdnsSocket.js.map +1 -1
  38. package/dist/cjs/peer/ControllerCommissioner.d.ts +2 -2
  39. package/dist/cjs/peer/ControllerCommissioner.d.ts.map +1 -1
  40. package/dist/cjs/peer/ControllerCommissioner.js +7 -5
  41. package/dist/cjs/peer/ControllerCommissioner.js.map +1 -1
  42. package/dist/cjs/peer/OperationalPeer.d.ts +2 -2
  43. package/dist/cjs/peer/OperationalPeer.d.ts.map +1 -1
  44. package/dist/cjs/peer/PeerAddressStore.d.ts +3 -1
  45. package/dist/cjs/peer/PeerAddressStore.d.ts.map +1 -1
  46. package/dist/cjs/peer/PeerAddressStore.js.map +1 -1
  47. package/dist/cjs/peer/PeerSet.d.ts +3 -3
  48. package/dist/cjs/peer/PeerSet.d.ts.map +1 -1
  49. package/dist/cjs/peer/PeerSet.js +14 -9
  50. package/dist/cjs/peer/PeerSet.js.map +1 -1
  51. package/dist/cjs/protocol/ExchangeManager.d.ts +2 -2
  52. package/dist/cjs/protocol/ExchangeManager.d.ts.map +1 -1
  53. package/dist/cjs/protocol/ExchangeManager.js +21 -17
  54. package/dist/cjs/protocol/ExchangeManager.js.map +1 -1
  55. package/dist/cjs/protocol/MessageChannel.d.ts +1 -1
  56. package/dist/cjs/protocol/MessageChannel.d.ts.map +1 -1
  57. package/dist/cjs/protocol/MessageChannel.js +7 -5
  58. package/dist/cjs/protocol/MessageChannel.js.map +1 -1
  59. package/dist/cjs/protocol/MessageExchange.d.ts +0 -1
  60. package/dist/cjs/protocol/MessageExchange.d.ts.map +1 -1
  61. package/dist/cjs/protocol/MessageExchange.js +6 -17
  62. package/dist/cjs/protocol/MessageExchange.js.map +1 -1
  63. package/dist/cjs/session/NodeSession.d.ts.map +1 -1
  64. package/dist/cjs/session/NodeSession.js +3 -3
  65. package/dist/cjs/session/NodeSession.js.map +1 -1
  66. package/dist/cjs/session/SessionIntervals.d.ts +1 -5
  67. package/dist/cjs/session/SessionIntervals.d.ts.map +1 -1
  68. package/dist/cjs/session/SessionIntervals.js.map +1 -1
  69. package/dist/cjs/session/case/CaseMessages.d.ts +18 -9
  70. package/dist/cjs/session/case/CaseMessages.d.ts.map +1 -1
  71. package/dist/cjs/session/case/CaseMessages.js.map +1 -1
  72. package/dist/cjs/session/case/CaseMessenger.d.ts +4 -62
  73. package/dist/cjs/session/case/CaseMessenger.d.ts.map +1 -1
  74. package/dist/cjs/session/case/CaseMessenger.js.map +1 -1
  75. package/dist/cjs/session/case/CaseServer.js.map +1 -1
  76. package/dist/cjs/session/pase/PaseMessages.d.ts +20 -9
  77. package/dist/cjs/session/pase/PaseMessages.d.ts.map +1 -1
  78. package/dist/cjs/session/pase/PaseMessages.js +3 -3
  79. package/dist/cjs/session/pase/PaseMessages.js.map +1 -1
  80. package/dist/cjs/session/pase/PaseMessenger.d.ts +3 -51
  81. package/dist/cjs/session/pase/PaseMessenger.d.ts.map +1 -1
  82. package/dist/cjs/session/pase/PaseMessenger.js.map +1 -1
  83. package/dist/esm/action/client/ReadScope.d.ts +4 -0
  84. package/dist/esm/action/client/ReadScope.d.ts.map +1 -1
  85. package/dist/esm/action/client/ReadScope.js +2 -1
  86. package/dist/esm/action/client/ReadScope.js.map +1 -1
  87. package/dist/esm/ble/Ble.d.ts +3 -3
  88. package/dist/esm/ble/Ble.d.ts.map +1 -1
  89. package/dist/esm/ble/Ble.js.map +1 -1
  90. package/dist/esm/common/Scanner.d.ts +2 -2
  91. package/dist/esm/common/Scanner.d.ts.map +1 -1
  92. package/dist/esm/interaction/AttributeDataDecoder.d.ts +9 -7
  93. package/dist/esm/interaction/AttributeDataDecoder.d.ts.map +1 -1
  94. package/dist/esm/interaction/AttributeDataDecoder.js.map +1 -1
  95. package/dist/esm/interaction/DecodedDataReport.d.ts +1 -0
  96. package/dist/esm/interaction/DecodedDataReport.d.ts.map +1 -1
  97. package/dist/esm/interaction/DecodedDataReport.js.map +1 -1
  98. package/dist/esm/interaction/InteractionClient.d.ts +13 -3
  99. package/dist/esm/interaction/InteractionClient.d.ts.map +1 -1
  100. package/dist/esm/interaction/InteractionClient.js +96 -68
  101. package/dist/esm/interaction/InteractionClient.js.map +1 -1
  102. package/dist/esm/interaction/InteractionMessenger.d.ts +10 -90
  103. package/dist/esm/interaction/InteractionMessenger.d.ts.map +1 -1
  104. package/dist/esm/interaction/InteractionMessenger.js +98 -22
  105. package/dist/esm/interaction/InteractionMessenger.js.map +2 -2
  106. package/dist/esm/interaction/SubscriptionClient.d.ts +2 -2
  107. package/dist/esm/interaction/SubscriptionClient.d.ts.map +1 -1
  108. package/dist/esm/interaction/SubscriptionClient.js +1 -1
  109. package/dist/esm/interaction/SubscriptionClient.js.map +1 -1
  110. package/dist/esm/mdns/MdnsClient.d.ts +3 -3
  111. package/dist/esm/mdns/MdnsClient.d.ts.map +1 -1
  112. package/dist/esm/mdns/MdnsClient.js +28 -14
  113. package/dist/esm/mdns/MdnsClient.js.map +1 -1
  114. package/dist/esm/mdns/MdnsServer.d.ts.map +1 -1
  115. package/dist/esm/mdns/MdnsServer.js +60 -5
  116. package/dist/esm/mdns/MdnsServer.js.map +1 -1
  117. package/dist/esm/mdns/MdnsSocket.d.ts.map +1 -1
  118. package/dist/esm/mdns/MdnsSocket.js +12 -1
  119. package/dist/esm/mdns/MdnsSocket.js.map +1 -1
  120. package/dist/esm/peer/ControllerCommissioner.d.ts +2 -2
  121. package/dist/esm/peer/ControllerCommissioner.d.ts.map +1 -1
  122. package/dist/esm/peer/ControllerCommissioner.js +9 -6
  123. package/dist/esm/peer/ControllerCommissioner.js.map +1 -1
  124. package/dist/esm/peer/OperationalPeer.d.ts +2 -2
  125. package/dist/esm/peer/OperationalPeer.d.ts.map +1 -1
  126. package/dist/esm/peer/PeerAddressStore.d.ts +3 -1
  127. package/dist/esm/peer/PeerAddressStore.d.ts.map +1 -1
  128. package/dist/esm/peer/PeerAddressStore.js.map +1 -1
  129. package/dist/esm/peer/PeerSet.d.ts +3 -3
  130. package/dist/esm/peer/PeerSet.d.ts.map +1 -1
  131. package/dist/esm/peer/PeerSet.js +15 -10
  132. package/dist/esm/peer/PeerSet.js.map +1 -1
  133. package/dist/esm/protocol/ExchangeManager.d.ts +2 -2
  134. package/dist/esm/protocol/ExchangeManager.d.ts.map +1 -1
  135. package/dist/esm/protocol/ExchangeManager.js +22 -18
  136. package/dist/esm/protocol/ExchangeManager.js.map +1 -1
  137. package/dist/esm/protocol/MessageChannel.d.ts +1 -1
  138. package/dist/esm/protocol/MessageChannel.d.ts.map +1 -1
  139. package/dist/esm/protocol/MessageChannel.js +7 -5
  140. package/dist/esm/protocol/MessageChannel.js.map +1 -1
  141. package/dist/esm/protocol/MessageExchange.d.ts +0 -1
  142. package/dist/esm/protocol/MessageExchange.d.ts.map +1 -1
  143. package/dist/esm/protocol/MessageExchange.js +6 -17
  144. package/dist/esm/protocol/MessageExchange.js.map +1 -1
  145. package/dist/esm/session/NodeSession.d.ts.map +1 -1
  146. package/dist/esm/session/NodeSession.js +4 -3
  147. package/dist/esm/session/NodeSession.js.map +1 -1
  148. package/dist/esm/session/SessionIntervals.d.ts +1 -5
  149. package/dist/esm/session/SessionIntervals.d.ts.map +1 -1
  150. package/dist/esm/session/SessionIntervals.js.map +1 -1
  151. package/dist/esm/session/case/CaseMessages.d.ts +18 -9
  152. package/dist/esm/session/case/CaseMessages.d.ts.map +1 -1
  153. package/dist/esm/session/case/CaseMessages.js.map +1 -1
  154. package/dist/esm/session/case/CaseMessenger.d.ts +4 -62
  155. package/dist/esm/session/case/CaseMessenger.d.ts.map +1 -1
  156. package/dist/esm/session/case/CaseMessenger.js +6 -1
  157. package/dist/esm/session/case/CaseMessenger.js.map +1 -1
  158. package/dist/esm/session/case/CaseServer.js.map +1 -1
  159. package/dist/esm/session/pase/PaseMessages.d.ts +20 -9
  160. package/dist/esm/session/pase/PaseMessages.d.ts.map +1 -1
  161. package/dist/esm/session/pase/PaseMessages.js +3 -3
  162. package/dist/esm/session/pase/PaseMessages.js.map +1 -1
  163. package/dist/esm/session/pase/PaseMessenger.d.ts +3 -51
  164. package/dist/esm/session/pase/PaseMessenger.d.ts.map +1 -1
  165. package/dist/esm/session/pase/PaseMessenger.js.map +1 -1
  166. package/package.json +6 -6
  167. package/src/action/client/ReadScope.ts +7 -0
  168. package/src/ble/Ble.ts +3 -3
  169. package/src/common/Scanner.ts +2 -2
  170. package/src/interaction/AttributeDataDecoder.ts +4 -1
  171. package/src/interaction/DecodedDataReport.ts +1 -0
  172. package/src/interaction/InteractionClient.ts +154 -79
  173. package/src/interaction/InteractionMessenger.ts +126 -22
  174. package/src/interaction/SubscriptionClient.ts +6 -5
  175. package/src/mdns/MdnsClient.ts +33 -23
  176. package/src/mdns/MdnsServer.ts +75 -5
  177. package/src/mdns/MdnsSocket.ts +16 -1
  178. package/src/peer/ControllerCommissioner.ts +10 -7
  179. package/src/peer/OperationalPeer.ts +2 -2
  180. package/src/peer/PeerAddressStore.ts +3 -1
  181. package/src/peer/PeerSet.ts +23 -18
  182. package/src/protocol/ExchangeManager.ts +29 -21
  183. package/src/protocol/MessageChannel.ts +13 -11
  184. package/src/protocol/MessageExchange.ts +6 -18
  185. package/src/session/NodeSession.ts +4 -3
  186. package/src/session/SessionIntervals.ts +1 -1
  187. package/src/session/case/CaseMessages.ts +12 -2
  188. package/src/session/case/CaseMessenger.ts +12 -4
  189. package/src/session/case/CaseServer.ts +5 -5
  190. package/src/session/pase/PaseMessages.ts +27 -3
  191. package/src/session/pase/PaseMessenger.ts +8 -9
@@ -16,14 +16,20 @@ import {
16
16
  NoResponseTimeoutError,
17
17
  UnexpectedDataError,
18
18
  } from "#general";
19
+ import { DecodedAttributeReportValue } from "#interaction/AttributeDataDecoder.js";
20
+ import { DecodedDataReport } from "#interaction/DecodedDataReport.js";
19
21
  import { Specification } from "#model";
20
22
  import { ChannelNotConnectedError } from "#protocol/MessageChannel.js";
21
23
  import {
24
+ AttributeId,
25
+ ClusterId,
26
+ EndpointNumber,
22
27
  ReceivedStatusResponseError,
23
28
  Status,
24
29
  StatusCode,
25
30
  StatusResponseError,
26
31
  TlvAny,
32
+ TlvAttributeReport,
27
33
  TlvDataReport,
28
34
  TlvDataReportForSend,
29
35
  TlvDataVersionFilter,
@@ -799,8 +805,105 @@ export class IncomingInteractionClientMessenger extends InteractionMessenger {
799
805
  return message;
800
806
  }
801
807
 
802
- async readAggregateDataReport(expectedSubscriptionIds?: number[]): Promise<DataReport> {
803
- let result: DataReport | undefined;
808
+ /**
809
+ * Reads data report stream and aggregates them into a single report.
810
+ * Additionally, a callback can be provided that is called for each cluster chunk received.
811
+ */
812
+ async readAggregateDataReport(
813
+ chunkListener?: (chunk: DecodedAttributeReportValue<any>[]) => Promise<void>,
814
+ expectedSubscriptionIds?: number[],
815
+ ): Promise<DecodedDataReport> {
816
+ let result: DecodedDataReport | undefined = undefined;
817
+ let currentEndpointId: EndpointNumber | undefined = undefined;
818
+ let currentClusterId: ClusterId | undefined = undefined;
819
+ const currentClusterChunk = new Array<DecodedAttributeReportValue<any>>();
820
+ let pendingAttributeReports: TypeFromSchema<typeof TlvAttributeReport>[] | undefined = undefined;
821
+
822
+ const handleAttributeReportEntries = (
823
+ attributeReports: TypeFromSchema<typeof TlvAttributeReport>[] | undefined,
824
+ previousPendingAttributeReports: TypeFromSchema<typeof TlvAttributeReport>[] | undefined,
825
+ ) => {
826
+ if (previousPendingAttributeReports?.length) {
827
+ attributeReports = attributeReports ?? [];
828
+ attributeReports.unshift(...previousPendingAttributeReports);
829
+ }
830
+
831
+ let lastAttributeDataIndex = -1;
832
+ if (attributeReports?.length) {
833
+ let lastEndpointId: EndpointNumber | undefined = undefined;
834
+ let lastClusterId: ClusterId | undefined = undefined;
835
+ let lastAttributeId: AttributeId | undefined = undefined;
836
+ for (let i = attributeReports.length - 1; i >= 0; i--) {
837
+ const attributeReport = attributeReports[i];
838
+ if (attributeReport.attributeData === undefined) {
839
+ break; // No data report, so nothing more to search for
840
+ }
841
+ const {
842
+ path: { endpointId, clusterId, attributeId },
843
+ } = attributeReport.attributeData;
844
+ if (lastEndpointId === undefined && lastClusterId === undefined && lastAttributeId === undefined) {
845
+ // Remember path of the last attribute data entry and check if previous entries match
846
+ lastEndpointId = endpointId;
847
+ lastClusterId = clusterId;
848
+ lastAttributeId = attributeId;
849
+ }
850
+ if (
851
+ endpointId === lastEndpointId &&
852
+ clusterId === lastClusterId &&
853
+ attributeId === lastAttributeId
854
+ ) {
855
+ lastAttributeDataIndex = i;
856
+ continue;
857
+ }
858
+ break; // We found an attribute that does not match the last one, so we are done
859
+ }
860
+
861
+ if (lastAttributeDataIndex > 0) {
862
+ return attributeReports.splice(lastAttributeDataIndex);
863
+ }
864
+ }
865
+ };
866
+
867
+ const processDecodedReport = async (
868
+ decodedReport: DecodedDataReport,
869
+ result: DecodedDataReport | undefined,
870
+ ) => {
871
+ if (!result) {
872
+ result = decodedReport;
873
+ } else {
874
+ if (!result.attributeReports) {
875
+ result.attributeReports = decodedReport.attributeReports;
876
+ } else {
877
+ result.attributeReports.push(...decodedReport.attributeReports);
878
+ }
879
+ if (Array.isArray(decodedReport.eventReports)) {
880
+ if (!result.eventReports) {
881
+ result.eventReports = decodedReport.eventReports;
882
+ } else {
883
+ result.eventReports.push(...decodedReport.eventReports);
884
+ }
885
+ }
886
+ }
887
+
888
+ if (chunkListener !== undefined && decodedReport.attributeReports) {
889
+ for (const data of decodedReport.attributeReports) {
890
+ const {
891
+ path: { endpointId, clusterId },
892
+ } = data;
893
+ if (currentEndpointId !== endpointId || currentClusterId !== clusterId) {
894
+ // We switched the cluster, so we need to send the current chunk first
895
+ if (currentClusterChunk.length > 0) {
896
+ await chunkListener(currentClusterChunk);
897
+ currentClusterChunk.length = 0;
898
+ }
899
+ currentEndpointId = endpointId;
900
+ currentClusterId = clusterId;
901
+ }
902
+ currentClusterChunk.push(data);
903
+ }
904
+ }
905
+ return result;
906
+ };
804
907
 
805
908
  for await (const report of this.readDataReports()) {
806
909
  if (expectedSubscriptionIds !== undefined) {
@@ -823,24 +926,25 @@ export class IncomingInteractionClientMessenger extends InteractionMessenger {
823
926
  throw new UnexpectedDataError(`Invalid subscription ID ${report.subscriptionId} received`);
824
927
  }
825
928
 
826
- if (!result) {
827
- result = report;
828
- } else {
829
- if (Array.isArray(report.attributeReports)) {
830
- if (!result.attributeReports) {
831
- result.attributeReports = report.attributeReports;
832
- } else {
833
- result.attributeReports.push(...report.attributeReports);
834
- }
835
- }
836
- if (Array.isArray(report.eventReports)) {
837
- if (!result.eventReports) {
838
- result.eventReports = report.eventReports;
839
- } else {
840
- result.eventReports.push(...report.eventReports);
841
- }
842
- }
843
- }
929
+ report.attributeReports = report.attributeReports ?? [];
930
+ pendingAttributeReports = handleAttributeReportEntries(report.attributeReports, pendingAttributeReports);
931
+
932
+ result = await processDecodedReport(DecodedDataReport(report), result);
933
+ }
934
+
935
+ if (pendingAttributeReports?.length && result !== undefined) {
936
+ result = await processDecodedReport(
937
+ DecodedDataReport({
938
+ interactionModelRevision: result.interactionModelRevision,
939
+ attributeReports: pendingAttributeReports,
940
+ }),
941
+ result,
942
+ );
943
+ }
944
+
945
+ if (chunkListener !== undefined && currentClusterChunk.length > 0) {
946
+ await chunkListener(currentClusterChunk);
947
+ currentClusterChunk.length = 0;
844
948
  }
845
949
 
846
950
  if (result === undefined) {
@@ -1010,8 +1114,8 @@ export class InteractionClientMessenger extends IncomingInteractionClientMesseng
1010
1114
  await this.send(MessageType.SubscribeRequest, request);
1011
1115
  }
1012
1116
 
1013
- async readAggregateSubscribeResponse() {
1014
- const report = await this.readAggregateDataReport();
1117
+ async readAggregateSubscribeResponse(chunkListener?: (chunk: DecodedAttributeReportValue<any>[]) => Promise<void>) {
1118
+ const report = await this.readAggregateDataReport(chunkListener);
1015
1119
  const { subscriptionId } = report;
1016
1120
 
1017
1121
  if (subscriptionId === undefined) {
@@ -5,10 +5,11 @@
5
5
  */
6
6
 
7
7
  import { Duration, Environment, Environmental, Logger, MaybePromise, Millis, 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
  maximumPeerResponseTime: Duration;
18
19
  maxInterval: Duration;
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
  }
@@ -26,7 +26,7 @@ import {
26
26
  ObserverGroup,
27
27
  Seconds,
28
28
  ServerAddress,
29
- ServerAddressIp,
29
+ ServerAddressUdp,
30
30
  SrvRecordValue,
31
31
  Time,
32
32
  Timer,
@@ -60,7 +60,7 @@ const logger = Logger.get("MdnsClient");
60
60
 
61
61
  const MDNS_EXPIRY_GRACE_PERIOD_FACTOR = 1.05;
62
62
 
63
- type MatterServerRecordWithExpire = ServerAddressIp & Lifespan;
63
+ type MatterServerRecordWithExpire = ServerAddressUdp & Lifespan;
64
64
 
65
65
  /** Type for commissionable Device records including Lifespan details. */
66
66
  type CommissionableDeviceRecordWithExpire = Omit<CommissionableDevice, "addresses"> &
@@ -140,7 +140,8 @@ export class MdnsClient implements Scanner {
140
140
  readonly #recordWaiters = new Map<
141
141
  string,
142
142
  {
143
- resolver: () => void;
143
+ resolver: (value: any) => void;
144
+ responder?: () => any;
144
145
  timer?: Timer;
145
146
  resolveOnUpdatedRecords: boolean;
146
147
  cancelResolver?: (value: void) => void;
@@ -354,7 +355,7 @@ export class MdnsClient implements Scanner {
354
355
  ip,
355
356
  port,
356
357
  type: "udp",
357
- })) as ServerAddressIp[],
358
+ })) as ServerAddressUdp[],
358
359
  };
359
360
  }
360
361
 
@@ -400,14 +401,15 @@ export class MdnsClient implements Scanner {
400
401
  * Registers a deferred promise for a specific queryId together with a timeout and return the promise.
401
402
  * The promise will be resolved when the timer runs out latest.
402
403
  */
403
- async #registerWaiterPromise(
404
+ async #registerWaiterPromise<T = void>(
404
405
  queryId: string,
405
406
  commissionable: boolean,
406
- timeout?: Duration,
407
+ timeout: Duration | undefined,
408
+ responder?: () => T,
407
409
  resolveOnUpdatedRecords = true,
408
410
  cancelResolver?: (value: void) => void,
409
- ) {
410
- const { promise, resolver } = createPromise<void>();
411
+ ): Promise<T> {
412
+ const { promise, resolver } = createPromise<T>();
411
413
  const timer =
412
414
  timeout !== undefined
413
415
  ? Time.getTimer("MDNS timeout", timeout, () => {
@@ -416,7 +418,14 @@ export class MdnsClient implements Scanner {
416
418
  }).start()
417
419
  : undefined;
418
420
  this.#listening = true;
419
- this.#recordWaiters.set(queryId, { resolver, timer, resolveOnUpdatedRecords, cancelResolver, commissionable });
421
+ this.#recordWaiters.set(queryId, {
422
+ resolver,
423
+ responder,
424
+ timer,
425
+ resolveOnUpdatedRecords,
426
+ cancelResolver,
427
+ commissionable,
428
+ });
420
429
  this.#hasCommissionableWaiters = this.#hasCommissionableWaiters || commissionable;
421
430
  if (!commissionable) {
422
431
  this.#registerOperationalQuery(queryId);
@@ -426,7 +435,7 @@ export class MdnsClient implements Scanner {
426
435
  timeout !== undefined ? `timeout ${timeout}` : "no timeout"
427
436
  }${resolveOnUpdatedRecords ? "" : " (not resolving on updated records)"}`,
428
437
  );
429
- await promise;
438
+ return await promise;
430
439
  }
431
440
 
432
441
  /**
@@ -436,12 +445,12 @@ export class MdnsClient implements Scanner {
436
445
  #finishWaiter(queryId: string, resolvePromise: boolean, isUpdatedRecord = false) {
437
446
  const waiter = this.#recordWaiters.get(queryId);
438
447
  if (waiter === undefined) return;
439
- const { timer, resolver, resolveOnUpdatedRecords, commissionable } = waiter;
448
+ const { timer, resolver, responder, resolveOnUpdatedRecords, commissionable } = waiter;
440
449
  if (isUpdatedRecord && !resolveOnUpdatedRecords) return;
441
450
  logger.debug(`Finishing waiter for query ${queryId}, resolving: ${resolvePromise}`);
442
451
  timer?.stop();
443
452
  if (resolvePromise) {
444
- resolver();
453
+ resolver(responder?.());
445
454
  }
446
455
  this.#recordWaiters.delete(queryId);
447
456
  this.#removeQuery(queryId);
@@ -500,7 +509,9 @@ export class MdnsClient implements Scanner {
500
509
 
501
510
  let storedDevice = ignoreExistingRecords ? undefined : this.#getOperationalDeviceRecords(deviceMatterQname);
502
511
  if (storedDevice === undefined) {
503
- const promise = this.#registerWaiterPromise(deviceMatterQname, false, timeout);
512
+ const promise = this.#registerWaiterPromise(deviceMatterQname, false, timeout, () =>
513
+ this.#getOperationalDeviceRecords(deviceMatterQname),
514
+ );
504
515
 
505
516
  this.#setQueryRecords(deviceMatterQname, [
506
517
  {
@@ -510,8 +521,7 @@ export class MdnsClient implements Scanner {
510
521
  },
511
522
  ]);
512
523
 
513
- await promise;
514
- storedDevice = this.#getOperationalDeviceRecords(deviceMatterQname);
524
+ storedDevice = await promise;
515
525
  }
516
526
  return storedDevice;
517
527
  }
@@ -570,7 +580,7 @@ export class MdnsClient implements Scanner {
570
580
  ip,
571
581
  port,
572
582
  type: "udp",
573
- })) as ServerAddressIp[],
583
+ })) as ServerAddressUdp[],
574
584
  discoveredAt: undefined,
575
585
  ttl: undefined,
576
586
  };
@@ -727,12 +737,13 @@ export class MdnsClient implements Scanner {
727
737
  : this.#getCommissionableDeviceRecords(identifier).filter(({ addresses }) => addresses.length > 0);
728
738
  if (storedRecords.length === 0) {
729
739
  const queryId = this.#buildCommissionableQueryIdentifier(identifier);
730
- const promise = this.#registerWaiterPromise(queryId, true, timeout);
740
+ const promise = this.#registerWaiterPromise(queryId, true, timeout, () =>
741
+ this.#getCommissionableDeviceRecords(identifier),
742
+ );
731
743
 
732
744
  this.#setQueryRecords(queryId, this.#getCommissionableQueryRecords(identifier));
733
745
 
734
- await promise;
735
- storedRecords = this.#getCommissionableDeviceRecords(identifier);
746
+ storedRecords = await promise;
736
747
  }
737
748
 
738
749
  return storedRecords;
@@ -791,7 +802,7 @@ export class MdnsClient implements Scanner {
791
802
  break;
792
803
  }
793
804
  }
794
- await this.#registerWaiterPromise(queryId, true, remainingTime, false, queryResolver);
805
+ await this.#registerWaiterPromise(queryId, true, remainingTime, undefined, false, queryResolver);
795
806
  }
796
807
  return this.#getCommissionableDeviceRecords(identifier);
797
808
  }
@@ -981,8 +992,7 @@ export class MdnsClient implements Scanner {
981
992
  if (this.#closing) return;
982
993
 
983
994
  if (message === undefined) return; // The message cannot be parsed
984
- if (message.messageType !== DnsMessageType.Response && message.messageType !== DnsMessageType.TruncatedResponse)
985
- return;
995
+ if (!DnsMessageType.isResponse(message.messageType)) return;
986
996
 
987
997
  const answers = this.#structureAnswers([...message.answers, ...message.additionalRecords]);
988
998
 
@@ -1458,7 +1468,7 @@ export class MdnsClient implements Scanner {
1458
1468
  });
1459
1469
  }
1460
1470
  if (now > expires && !addresses.size) {
1461
- // device expired and also has no adresses anymore
1471
+ // device expired and also has no addresses anymore
1462
1472
  this.#operationalDeviceRecords.delete(recordKey);
1463
1473
  }
1464
1474
  });
@@ -7,6 +7,7 @@
7
7
  import {
8
8
  AsyncCache,
9
9
  DnsMessageType,
10
+ DnsMessageTypeFlag,
10
11
  DnsRecord,
11
12
  DnsRecordType,
12
13
  Instant,
@@ -18,6 +19,7 @@ import {
18
19
  NetworkInterfaceDetails,
19
20
  ObserverGroup,
20
21
  Time,
22
+ Timer,
21
23
  } from "#general";
22
24
  import { MdnsSocket } from "./MdnsSocket.js";
23
25
 
@@ -44,6 +46,7 @@ export class MdnsServer {
44
46
  Minutes(15) /* matches maximum standard commissioning window time */,
45
47
  );
46
48
  readonly #recordLastSentAsMulticastAnswer = new Map<string, number>();
49
+ readonly #truncatedQueryCache = new Map<string, { message: MdnsSocket.Message; timer: Timer }>();
47
50
 
48
51
  readonly #socket: MdnsSocket;
49
52
 
@@ -64,15 +67,19 @@ export class MdnsServer {
64
67
  return `${record.name}-${record.recordClass}-${record.recordType}-${netInterface}-${unicastTarget}`;
65
68
  }
66
69
 
67
- async #handleMessage(message: MdnsSocket.Message) {
68
- const records = await this.#records.get(message.sourceIntf);
70
+ async #handleMessage(incomingMessage: MdnsSocket.Message) {
71
+ const records = await this.#records.get(incomingMessage.sourceIntf);
69
72
 
70
73
  // Ignore if we have no records for interface
71
74
  if (records.size === 0) return;
72
75
 
73
- const { sourceIntf, sourceIp, transactionId, messageType, queries, answers: knownAnswers } = message;
74
- if (messageType !== DnsMessageType.Query && messageType !== DnsMessageType.TruncatedQuery) return;
75
- if (queries.length === 0) return; // No queries to answer, can happen in a TruncatedQuery, let's ignore for now
76
+ const message = this.#prepareMessage(incomingMessage);
77
+ if (message === undefined) {
78
+ return;
79
+ }
80
+
81
+ const { sourceIntf, sourceIp, transactionId, queries, answers: knownAnswers } = message;
82
+
76
83
  for (const portRecords of records.values()) {
77
84
  let answers = queries.flatMap(query => this.#queryRecords(query, portRecords));
78
85
  if (answers.length === 0) continue;
@@ -213,6 +220,10 @@ export class MdnsServer {
213
220
  async close() {
214
221
  this.#observers.close();
215
222
  await this.#records.close();
223
+ for (const { timer } of this.#truncatedQueryCache.values()) {
224
+ timer.stop();
225
+ }
226
+ this.#truncatedQueryCache.clear();
216
227
  this.#recordLastSentAsMulticastAnswer.clear();
217
228
  }
218
229
 
@@ -228,6 +239,65 @@ export class MdnsServer {
228
239
  return records.filter(record => record.name === name && record.recordType === recordType);
229
240
  }
230
241
  }
242
+
243
+ async #processTruncatedQuery(key: string) {
244
+ const { message, timer } = this.#truncatedQueryCache.get(key) ?? {};
245
+ this.#truncatedQueryCache.delete(key);
246
+ timer?.stop();
247
+ if (message) {
248
+ if (message.queries.length === 0) {
249
+ // Should not happen but ignore if it does
250
+ return;
251
+ }
252
+ message.messageType &= ~DnsMessageTypeFlag.TC; // Clear TC flag
253
+ await this.#handleMessage(message);
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Delays processing of truncated messages to allow combining multiple parts or combines them if possible
259
+ */
260
+ #prepareMessage(newMessage: MdnsSocket.Message): MdnsSocket.Message | undefined {
261
+ const { messageType, transactionId, sourceIntf, sourceIp } = newMessage;
262
+
263
+ if (!DnsMessageType.isQuery(messageType)) {
264
+ // We are only interested in queries
265
+ return;
266
+ }
267
+
268
+ const key = `${transactionId}-${sourceIntf}-${sourceIp}`;
269
+ const { message: existingMessage, timer } = this.#truncatedQueryCache.get(key) ?? {};
270
+ this.#truncatedQueryCache.delete(key);
271
+ timer?.stop();
272
+
273
+ const message = existingMessage
274
+ ? {
275
+ ...existingMessage,
276
+ queries: [...existingMessage.queries, ...newMessage.queries],
277
+ answers: [...existingMessage.answers, ...newMessage.answers],
278
+ additionalRecords: [...existingMessage.additionalRecords, ...newMessage.additionalRecords],
279
+ messageType: newMessage.messageType, // Keep TC flag as is from the latest message
280
+ }
281
+ : newMessage;
282
+
283
+ // Message was not truncated, or we have now received all details, process it
284
+ if ((messageType & DnsMessageTypeFlag.TC) === 0) {
285
+ if (message.queries.length === 0) {
286
+ // Should not happen but ignore if it does
287
+ return;
288
+ }
289
+ return message;
290
+ }
291
+
292
+ // We have stored a new or updated truncated message - store and wait for next part
293
+ // Delay should be max 400-500ms as per RFC 6762 section 7.2
294
+ this.#truncatedQueryCache.set(key, {
295
+ message,
296
+ timer: Time.getTimer(`Truncated MDNS message ${key}`, Millis(400 + Math.floor(Math.random() * 100)), () =>
297
+ this.#processTruncatedQuery(key),
298
+ ).start(),
299
+ });
300
+ }
231
301
  }
232
302
 
233
303
  export namespace MdnsServer {
@@ -12,6 +12,7 @@ import {
12
12
  DnsMessage,
13
13
  DnsMessagePartiallyPreEncoded,
14
14
  DnsMessageType,
15
+ DnsMessageTypeFlag,
15
16
  Logger,
16
17
  MatterAggregateError,
17
18
  MAX_MDNS_MESSAGE_SIZE,
@@ -71,6 +72,12 @@ export class MdnsSocket {
71
72
  }
72
73
 
73
74
  async send(message: Partial<DnsMessage> & { messageType: DnsMessageType }, intf?: string, unicastDest?: string) {
75
+ const { messageType } = message;
76
+ // When we send Queries that are too long they need to have the Truncated flag set
77
+ const truncatedMessageType = DnsMessageType.isQuery(messageType)
78
+ ? messageType | DnsMessageTypeFlag.TC
79
+ : messageType;
80
+
74
81
  const chunk: DnsMessagePartiallyPreEncoded = {
75
82
  transactionId: 0,
76
83
  queries: [],
@@ -101,7 +108,15 @@ export class MdnsSocket {
101
108
  }
102
109
 
103
110
  // New answer does not fit anymore, send out the message
104
- await this.#send(chunk, intf, unicastDest);
111
+ // When sending a query, we set the Truncated flag to indicate more answers are available
112
+ await this.#send(
113
+ {
114
+ ...chunk,
115
+ messageType: truncatedMessageType,
116
+ },
117
+ intf,
118
+ unicastDest,
119
+ );
105
120
 
106
121
  // Reset the message, length counter and included answers to count for next message
107
122
  if (chunk.queries.length) {
@@ -13,15 +13,16 @@ import {
13
13
  Channel,
14
14
  ChannelType,
15
15
  ClassExtends,
16
+ ConnectionlessTransportSet,
16
17
  Diagnostic,
17
18
  Duration,
18
19
  Environment,
19
20
  Environmental,
21
+ ImplementationError,
20
22
  isIPv6,
21
23
  Logger,
22
24
  Millis,
23
25
  Minutes,
24
- NetInterfaceSet,
25
26
  NoResponseTimeoutError,
26
27
  Seconds,
27
28
  ServerAddress,
@@ -129,7 +130,7 @@ export interface ControllerCommissionerContext {
129
130
  peers: PeerSet;
130
131
  clients: InteractionClientProvider;
131
132
  scanners: ScannerSet;
132
- netInterfaces: NetInterfaceSet;
133
+ transports: ConnectionlessTransportSet;
133
134
  sessions: SessionManager;
134
135
  exchanges: ExchangeManager;
135
136
  ca: CertificateAuthority;
@@ -152,7 +153,7 @@ export class ControllerCommissioner {
152
153
  peers: env.get(PeerSet),
153
154
  clients: env.get(InteractionClientProvider),
154
155
  scanners: env.get(ScannerSet),
155
- netInterfaces: env.get(NetInterfaceSet),
156
+ transports: env.get(ConnectionlessTransportSet),
156
157
  sessions: env.get(SessionManager),
157
158
  exchanges: env.get(ExchangeManager),
158
159
  ca: env.get(CertificateAuthority),
@@ -213,7 +214,7 @@ export class ControllerCommissioner {
213
214
 
214
215
  if (
215
216
  this.#context.scanners.hasScannerFor(ChannelType.UDP) &&
216
- this.#context.netInterfaces.hasInterfaceFor(ChannelType.UDP, "::") !== undefined
217
+ this.#context.transports.hasInterfaceFor(ChannelType.UDP, "::") !== undefined
217
218
  ) {
218
219
  discoveryCapabilities.onIpNetwork = true; // We always discover on network as defined by specs
219
220
  }
@@ -316,7 +317,7 @@ export class ControllerCommissioner {
316
317
  const { ip } = address;
317
318
 
318
319
  const isIpv6Address = isIPv6(ip);
319
- const paseInterface = this.#context.netInterfaces.interfaceFor(
320
+ const paseInterface = this.#context.transports.interfaceFor(
320
321
  ChannelType.UDP,
321
322
  isIpv6Address ? "::" : "0.0.0.0",
322
323
  );
@@ -327,8 +328,8 @@ export class ControllerCommissioner {
327
328
  );
328
329
  }
329
330
  paseChannel = await paseInterface.openChannel(address);
330
- } else {
331
- const ble = this.#context.netInterfaces.interfaceFor(ChannelType.BLE);
331
+ } else if (address.type === "ble") {
332
+ const ble = this.#context.transports.interfaceFor(ChannelType.BLE);
332
333
  if (!ble) {
333
334
  throw new PairRetransmissionLimitReachedError(
334
335
  `BLE interface not initialized. Cannot use ${address.peripheralAddress} for commissioning.`,
@@ -336,6 +337,8 @@ export class ControllerCommissioner {
336
337
  }
337
338
  // TODO Have a Timeout mechanism here for connections
338
339
  paseChannel = await ble.openChannel(address);
340
+ } else {
341
+ throw new ImplementationError(`Unsupported PASE address type ${address.type}`);
339
342
  }
340
343
 
341
344
  // Do PASE paring
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import { DiscoveryData } from "#common/Scanner.js";
8
- import { ServerAddressIp } from "#general";
8
+ import { ServerAddressUdp } from "#general";
9
9
  import type { PeerDataStore } from "#peer/PeerAddressStore.js";
10
10
  import { SessionParameters } from "#session/Session.js";
11
11
  import { PeerAddress } from "./PeerAddress.js";
@@ -24,7 +24,7 @@ export interface OperationalPeer {
24
24
  /**
25
25
  * A physical address the peer may be accessed at, if known.
26
26
  */
27
- operationalAddress?: ServerAddressIp;
27
+ operationalAddress?: ServerAddressUdp;
28
28
 
29
29
  /**
30
30
  * The peer's session parameters reported during discovery.
@@ -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
  }