@matter/protocol 0.13.1-alpha.0-20250509-28e1567e1 → 0.13.1-alpha.0-20250511-74ef153aa

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 (118) hide show
  1. package/dist/cjs/action/protocols.d.ts +59 -2
  2. package/dist/cjs/action/protocols.d.ts.map +1 -1
  3. package/dist/cjs/action/request/Read.d.ts +2 -2
  4. package/dist/cjs/action/request/Read.d.ts.map +1 -1
  5. package/dist/cjs/action/request/Read.js +4 -4
  6. package/dist/cjs/action/request/Read.js.map +1 -1
  7. package/dist/cjs/action/response/ReadResult.d.ts +6 -2
  8. package/dist/cjs/action/response/ReadResult.d.ts.map +1 -1
  9. package/dist/cjs/action/server/AttributeResponse.d.ts +46 -17
  10. package/dist/cjs/action/server/AttributeResponse.d.ts.map +1 -1
  11. package/dist/cjs/action/server/AttributeResponse.js +128 -110
  12. package/dist/cjs/action/server/AttributeResponse.js.map +2 -2
  13. package/dist/cjs/action/server/AttributeSubscriptionResponse.d.ts +36 -0
  14. package/dist/cjs/action/server/AttributeSubscriptionResponse.d.ts.map +1 -0
  15. package/dist/cjs/action/server/AttributeSubscriptionResponse.js +86 -0
  16. package/dist/cjs/action/server/AttributeSubscriptionResponse.js.map +6 -0
  17. package/dist/cjs/action/server/DataResponse.d.ts +45 -0
  18. package/dist/cjs/action/server/DataResponse.d.ts.map +1 -0
  19. package/dist/cjs/action/server/DataResponse.js +69 -0
  20. package/dist/cjs/action/server/DataResponse.js.map +6 -0
  21. package/dist/cjs/action/server/EventResponse.d.ts +28 -0
  22. package/dist/cjs/action/server/EventResponse.d.ts.map +1 -0
  23. package/dist/cjs/action/server/EventResponse.js +318 -0
  24. package/dist/cjs/action/server/EventResponse.js.map +6 -0
  25. package/dist/cjs/action/server/ServerInteraction.d.ts.map +1 -1
  26. package/dist/cjs/action/server/ServerInteraction.js +15 -2
  27. package/dist/cjs/action/server/ServerInteraction.js.map +1 -1
  28. package/dist/cjs/action/server/index.d.ts +3 -0
  29. package/dist/cjs/action/server/index.d.ts.map +1 -1
  30. package/dist/cjs/action/server/index.js +3 -0
  31. package/dist/cjs/action/server/index.js.map +1 -1
  32. package/dist/cjs/events/OccurrenceManager.d.ts +20 -11
  33. package/dist/cjs/events/OccurrenceManager.d.ts.map +1 -1
  34. package/dist/cjs/events/OccurrenceManager.js +113 -74
  35. package/dist/cjs/events/OccurrenceManager.js.map +1 -1
  36. package/dist/cjs/interaction/InteractionMessenger.d.ts +14 -2
  37. package/dist/cjs/interaction/InteractionMessenger.d.ts.map +1 -1
  38. package/dist/cjs/interaction/InteractionMessenger.js +87 -3
  39. package/dist/cjs/interaction/InteractionMessenger.js.map +1 -1
  40. package/dist/cjs/interaction/index.d.ts +0 -1
  41. package/dist/cjs/interaction/index.d.ts.map +1 -1
  42. package/dist/cjs/interaction/index.js +0 -1
  43. package/dist/cjs/interaction/index.js.map +1 -1
  44. package/dist/cjs/peer/ControllerCommissioningFlow.js +1 -1
  45. package/dist/cjs/protocol/MessageExchange.d.ts.map +1 -1
  46. package/dist/cjs/protocol/MessageExchange.js +11 -1
  47. package/dist/cjs/protocol/MessageExchange.js.map +1 -1
  48. package/dist/esm/action/protocols.d.ts +59 -2
  49. package/dist/esm/action/protocols.d.ts.map +1 -1
  50. package/dist/esm/action/request/Read.d.ts +2 -2
  51. package/dist/esm/action/request/Read.d.ts.map +1 -1
  52. package/dist/esm/action/request/Read.js +4 -4
  53. package/dist/esm/action/request/Read.js.map +1 -1
  54. package/dist/esm/action/response/ReadResult.d.ts +6 -2
  55. package/dist/esm/action/response/ReadResult.d.ts.map +1 -1
  56. package/dist/esm/action/server/AttributeResponse.d.ts +46 -17
  57. package/dist/esm/action/server/AttributeResponse.d.ts.map +1 -1
  58. package/dist/esm/action/server/AttributeResponse.js +129 -113
  59. package/dist/esm/action/server/AttributeResponse.js.map +1 -1
  60. package/dist/esm/action/server/AttributeSubscriptionResponse.d.ts +36 -0
  61. package/dist/esm/action/server/AttributeSubscriptionResponse.d.ts.map +1 -0
  62. package/dist/esm/action/server/AttributeSubscriptionResponse.js +66 -0
  63. package/dist/esm/action/server/AttributeSubscriptionResponse.js.map +6 -0
  64. package/dist/esm/action/server/DataResponse.d.ts +45 -0
  65. package/dist/esm/action/server/DataResponse.d.ts.map +1 -0
  66. package/dist/esm/action/server/DataResponse.js +49 -0
  67. package/dist/esm/action/server/DataResponse.js.map +6 -0
  68. package/dist/esm/action/server/EventResponse.d.ts +28 -0
  69. package/dist/esm/action/server/EventResponse.d.ts.map +1 -0
  70. package/dist/esm/action/server/EventResponse.js +305 -0
  71. package/dist/esm/action/server/EventResponse.js.map +6 -0
  72. package/dist/esm/action/server/ServerInteraction.d.ts.map +1 -1
  73. package/dist/esm/action/server/ServerInteraction.js +16 -3
  74. package/dist/esm/action/server/ServerInteraction.js.map +1 -1
  75. package/dist/esm/action/server/index.d.ts +3 -0
  76. package/dist/esm/action/server/index.d.ts.map +1 -1
  77. package/dist/esm/action/server/index.js +3 -0
  78. package/dist/esm/action/server/index.js.map +1 -1
  79. package/dist/esm/events/OccurrenceManager.d.ts +20 -11
  80. package/dist/esm/events/OccurrenceManager.d.ts.map +1 -1
  81. package/dist/esm/events/OccurrenceManager.js +117 -80
  82. package/dist/esm/events/OccurrenceManager.js.map +1 -1
  83. package/dist/esm/interaction/InteractionMessenger.d.ts +14 -2
  84. package/dist/esm/interaction/InteractionMessenger.d.ts.map +1 -1
  85. package/dist/esm/interaction/InteractionMessenger.js +87 -3
  86. package/dist/esm/interaction/InteractionMessenger.js.map +1 -1
  87. package/dist/esm/interaction/index.d.ts +0 -1
  88. package/dist/esm/interaction/index.d.ts.map +1 -1
  89. package/dist/esm/interaction/index.js +0 -1
  90. package/dist/esm/interaction/index.js.map +1 -1
  91. package/dist/esm/peer/ControllerCommissioningFlow.js +1 -1
  92. package/dist/esm/protocol/MessageExchange.d.ts.map +1 -1
  93. package/dist/esm/protocol/MessageExchange.js +11 -1
  94. package/dist/esm/protocol/MessageExchange.js.map +1 -1
  95. package/package.json +6 -6
  96. package/src/action/protocols.ts +68 -2
  97. package/src/action/request/Read.ts +2 -2
  98. package/src/action/response/ReadResult.ts +8 -1
  99. package/src/action/server/AttributeResponse.ts +145 -118
  100. package/src/action/server/AttributeSubscriptionResponse.ts +90 -0
  101. package/src/action/server/DataResponse.ts +70 -0
  102. package/src/action/server/EventResponse.ts +381 -0
  103. package/src/action/server/ServerInteraction.ts +18 -4
  104. package/src/action/server/index.ts +3 -0
  105. package/src/events/OccurrenceManager.ts +126 -100
  106. package/src/interaction/InteractionMessenger.ts +93 -8
  107. package/src/interaction/index.ts +0 -1
  108. package/src/peer/ControllerCommissioningFlow.ts +1 -1
  109. package/src/protocol/MessageExchange.ts +13 -1
  110. package/dist/cjs/interaction/ServerSubscription.d.ts +0 -116
  111. package/dist/cjs/interaction/ServerSubscription.d.ts.map +0 -1
  112. package/dist/cjs/interaction/ServerSubscription.js +0 -778
  113. package/dist/cjs/interaction/ServerSubscription.js.map +0 -6
  114. package/dist/esm/interaction/ServerSubscription.d.ts +0 -116
  115. package/dist/esm/interaction/ServerSubscription.d.ts.map +0 -1
  116. package/dist/esm/interaction/ServerSubscription.js +0 -778
  117. package/dist/esm/interaction/ServerSubscription.js.map +0 -6
  118. package/src/interaction/ServerSubscription.ts +0 -1038
@@ -9,20 +9,14 @@ import {
9
9
  Construction,
10
10
  Diagnostic,
11
11
  ImplementationError,
12
+ InternalError,
12
13
  isObject,
13
14
  Logger,
14
15
  MatterAggregateError,
15
16
  MaybePromise,
17
+ Observable,
16
18
  } from "#general";
17
- import {
18
- EventNumber,
19
- EventPriority,
20
- FabricIndex,
21
- resolveEventName,
22
- TlvEventFilter,
23
- TlvEventPath,
24
- TypeFromSchema,
25
- } from "#types";
19
+ import { EventNumber, FabricIndex, resolveEventName, TlvEventFilter, TlvEventPath, TypeFromSchema } from "#types";
26
20
  import { EventStore, OccurrenceSummary } from "./EventStore.js";
27
21
  import { NumberedOccurrence, Occurrence } from "./Occurrence.js";
28
22
 
@@ -50,14 +44,12 @@ export class OccurrenceManager {
50
44
  #storedEventCount = 0;
51
45
  #bufferConfig: OccurrenceManager.BufferConfig;
52
46
  #cull?: Promise<void>;
47
+ #iteratingValuesInProgress = false;
48
+ #added = new Observable<[occurrence: NumberedOccurrence]>();
53
49
 
54
50
  // As we don't (yet) have storage with secondary indices we currently maintain indices in memory regardless of
55
51
  // whether underlying store is volatile
56
- readonly #occurrences = {
57
- [EventPriority.Critical]: new Array<OccurrenceSummary>(),
58
- [EventPriority.Info]: new Array<OccurrenceSummary>(),
59
- [EventPriority.Debug]: new Array<OccurrenceSummary>(),
60
- };
52
+ readonly #occurrences = new Array<OccurrenceSummary>();
61
53
 
62
54
  #construction: Construction<OccurrenceManager>;
63
55
 
@@ -82,70 +74,105 @@ export class OccurrenceManager {
82
74
  );
83
75
  }
84
76
 
85
- const totalPriorityAllowance = Object.values(bufferConfig.minPriorityEventAllowance).reduce(
86
- (sum, value) => sum + value,
87
- 0,
88
- );
89
- if (totalPriorityAllowance > minEventAllowance) {
90
- throw new ImplementationError(
91
- `Total priority allowance ${totalPriorityAllowance} is greater than minimum allowance of ${minEventAllowance}`,
92
- );
93
- }
94
-
95
77
  this.#store = store;
96
78
  this.#bufferConfig = bufferConfig;
97
79
 
98
80
  this.#construction = Construction(this, () => {
99
81
  return MaybePromise.then(this.#store.load(), index => {
100
82
  this.#storedEventCount = index.length;
101
- for (const entry of index) {
102
- this.#occurrences[entry.priority].push(entry);
83
+ // To be sure, sort the entries by number
84
+ index.sort(
85
+ // sort that way because Bigint & Number mix
86
+ ({ number: numberA }, { number: numberB }) => (numberA < numberB ? -1 : numberA > numberB ? 1 : 0),
87
+ );
88
+ this.#occurrences.push(...index);
89
+ if (this.#occurrences.length > this.#bufferConfig.minEventAllowance) {
90
+ this.#startCull();
103
91
  }
104
92
  });
105
93
  });
106
94
  }
107
95
 
96
+ get added() {
97
+ return this.#added;
98
+ }
99
+
108
100
  async clear() {
109
101
  await this.construction;
110
102
  await this.#store.clear();
111
103
  this.#storedEventCount = 0;
112
- for (const list of Object.values(this.#occurrences)) {
113
- list.length = 0;
104
+ this.#occurrences.length = 0;
105
+ }
106
+
107
+ /**
108
+ * Find the index of the first event number in the list that is greater than or equal to eventMin to optimize
109
+ * searching.
110
+ */
111
+ #findMinEventNumberIndex(eventMin: EventNumber) {
112
+ // if the list is empty or the eventMin is less than the last event number, no entry is relevant
113
+
114
+ if (this.#occurrences.length === 0 || eventMin > this.#occurrences[this.#occurrences.length - 1].number) {
115
+ return -1;
116
+ }
117
+ if (eventMin <= this.#occurrences[0].number) {
118
+ return 0; // The first event number is greater than or equal to eventMin, so all entries are relevant
119
+ }
120
+
121
+ let low = this.#occurrences.length - 1;
122
+ let high = low;
123
+ let step = 1;
124
+
125
+ // Because it is more likely that events we want are in the upper ranges we first fine a starting point from there
126
+ while (low > 0 && this.#occurrences[low].number >= eventMin) {
127
+ high = low;
128
+ low = Math.max(0, low - step);
129
+ step *= 2; // increase the step size to skip more entries
130
+ }
131
+
132
+ while (low <= high) {
133
+ const mid = Math.floor((low + high) / 2);
134
+ if (this.#occurrences[mid].number < eventMin) {
135
+ low = mid + 1;
136
+ } else {
137
+ high = mid - 1;
138
+ }
114
139
  }
140
+
141
+ return low; // The index of the first event number that is greater than or equal to eventMin
115
142
  }
116
143
 
144
+ /**
145
+ * Query the event store for events matching the given path and filters.
146
+ * @deprecated
147
+ */
117
148
  query(
118
149
  eventPath: TypeFromSchema<typeof TlvEventPath>,
119
150
  filters?: TypeFromSchema<typeof TlvEventFilter>[],
120
151
  filterForFabricIndex?: FabricIndex,
121
152
  ): MaybePromise<NumberedOccurrence[]> {
122
- const entryFilter =
123
- filters !== undefined && filters.length > 0
124
- ? (event: OccurrenceSummary) =>
125
- filters.some(
126
- filter => filter.eventMin !== undefined && event.number >= EventNumber(filter.eventMin),
127
- )
128
- : undefined; // TODO - filter on node ID too?
129
-
153
+ if (filters !== undefined && filters.length > 1) {
154
+ throw new InternalError("Multiple filters not supported");
155
+ }
130
156
  // Search the index and load applicable events
157
+ const startIndex = filters?.length ? this.#findMinEventNumberIndex(EventNumber(filters[0].eventMin)) : 0;
158
+ if (startIndex === -1) {
159
+ return []; // No entry matches the filter
160
+ }
161
+
131
162
  let isAsyncLoad = false;
132
- const occurrences = new Array<MaybePromise<NumberedOccurrence>>();
133
163
  const { endpointId, clusterId, eventId } = eventPath;
134
- for (const priority of [EventPriority.Critical, EventPriority.Info, EventPriority.Debug]) {
135
- const entriesToCheck = this.#occurrences[priority];
136
- for (const entry of entriesToCheck) {
137
- if (endpointId === entry.endpointId && clusterId === entry.clusterId && eventId === entry.eventId) {
138
- if (entryFilter?.(entry) !== false) {
139
- let occurrence = this.#store.get(entry.number) as MaybePromise<NumberedOccurrence>;
140
- occurrence = MaybePromise.then(occurrence, occurrence => {
141
- occurrence.number = entry.number;
142
- return occurrence;
143
- });
144
- occurrences.push(occurrence);
145
- if (MaybePromise.is(occurrence)) {
146
- isAsyncLoad = true;
147
- }
148
- }
164
+ const occurrences = new Array<MaybePromise<NumberedOccurrence>>();
165
+ for (let i = startIndex; i < this.#occurrences.length; i++) {
166
+ const entry = this.#occurrences[i];
167
+ if (endpointId === entry.endpointId && clusterId === entry.clusterId && eventId === entry.eventId) {
168
+ let occurrence = this.#store.get(entry.number) as MaybePromise<NumberedOccurrence>;
169
+ occurrence = MaybePromise.then(occurrence, occurrence => {
170
+ occurrence.number = entry.number;
171
+ return occurrence;
172
+ });
173
+ occurrences.push(occurrence);
174
+ if (MaybePromise.is(occurrence)) {
175
+ isAsyncLoad = true;
149
176
  }
150
177
  }
151
178
  }
@@ -205,6 +232,40 @@ export class OccurrenceManager {
205
232
  return result;
206
233
  }
207
234
 
235
+ /**
236
+ * Return an iterator over all occurrences in the store that are bigger or equal to the minimum eventNumber,
237
+ * if provided.
238
+ */
239
+ async *get(eventMin?: EventNumber) {
240
+ if (this.#cull) {
241
+ await this.#cull;
242
+ }
243
+ const startIndex = eventMin === undefined ? 0 : this.#findMinEventNumberIndex(eventMin);
244
+ if (startIndex === -1) {
245
+ return; // No entry matches the filter
246
+ }
247
+ this.#iteratingValuesInProgress = true;
248
+ try {
249
+ for (let i = startIndex; i < this.#occurrences.length; i++) {
250
+ const eventNumber = this.#occurrences[i].number;
251
+ const occurrence = this.#store.get(eventNumber);
252
+ if (MaybePromise.is(occurrence)) {
253
+ yield {
254
+ ...(await occurrence),
255
+ number: eventNumber,
256
+ };
257
+ } else {
258
+ yield {
259
+ ...occurrence,
260
+ number: eventNumber,
261
+ };
262
+ }
263
+ }
264
+ } finally {
265
+ this.#iteratingValuesInProgress = false;
266
+ }
267
+ }
268
+
208
269
  close(): MaybePromise<void> {
209
270
  MaybePromise.then(this.#cull, () => this.#store.close());
210
271
  }
@@ -212,20 +273,22 @@ export class OccurrenceManager {
212
273
  add(occurrence: Occurrence): MaybePromise<NumberedOccurrence> {
213
274
  return MaybePromise.then(this.#store.add(occurrence), entry => {
214
275
  logger.debug(`Recorded event #${entry.number}: ${Diagnostic.json(occurrence)}`);
215
- this.#occurrences[occurrence.priority].push(entry);
276
+ this.#occurrences.push(entry);
216
277
  this.#storedEventCount++;
217
278
  if (this.#storedEventCount > this.#bufferConfig.maxEventAllowance) {
218
279
  this.#startCull();
219
280
  }
220
- return {
281
+ const numberedOccurrence = {
221
282
  number: entry.number,
222
283
  ...occurrence,
223
284
  };
285
+ this.#added.emit(numberedOccurrence);
286
+ return numberedOccurrence;
224
287
  });
225
288
  }
226
289
 
227
290
  #startCull() {
228
- if (this.#cull) {
291
+ if (this.#cull || this.#iteratingValuesInProgress) {
229
292
  return;
230
293
  }
231
294
  const cull = this.#dropOldOccurrences();
@@ -235,7 +298,7 @@ export class OccurrenceManager {
235
298
  }
236
299
 
237
300
  #dropOldOccurrences() {
238
- let count = this.#storedEventCount - this.#bufferConfig.minEventAllowance;
301
+ const count = this.#storedEventCount - this.#bufferConfig.minEventAllowance;
239
302
  if (count <= 0) {
240
303
  return;
241
304
  }
@@ -244,31 +307,15 @@ export class OccurrenceManager {
244
307
 
245
308
  const asyncDrops = Array<PromiseLike<void>>();
246
309
 
247
- let totalCulled = 0;
248
- for (const priority of [EventPriority.Debug, EventPriority.Info, EventPriority.Critical]) {
249
- const occurrences = this.#occurrences[priority];
250
- const reservation = this.#bufferConfig.minPriorityEventAllowance[PriorityNames[priority]];
251
- let countThisPriority = 0;
252
- while (count && occurrences.length > reservation) {
253
- count--;
254
- countThisPriority++;
255
- }
256
-
257
- totalCulled += countThisPriority;
258
- for (const entry of occurrences.splice(0, countThisPriority)) {
259
- const drop = MaybePromise.catch(this.#store.delete(entry.number), error =>
260
- logger.warn(`Error dropping occurrence #${entry}: ${error}`),
261
- );
262
- if (MaybePromise.is(drop)) {
263
- asyncDrops.push(drop);
264
- }
265
- }
266
-
267
- if (!count) {
268
- break;
310
+ for (const entry of this.#occurrences.splice(0, count)) {
311
+ const drop = MaybePromise.catch(this.#store.delete(entry.number), error =>
312
+ logger.warn(`Error dropping occurrence #${entry}: ${error}`),
313
+ );
314
+ if (MaybePromise.is(drop)) {
315
+ asyncDrops.push(drop);
269
316
  }
270
317
  }
271
- this.#storedEventCount -= totalCulled;
318
+ this.#storedEventCount = this.#occurrences.length;
272
319
 
273
320
  if (asyncDrops.length) {
274
321
  return MatterAggregateError.allSettled(asyncDrops, "Error dropping occurrences")
@@ -278,12 +325,6 @@ export class OccurrenceManager {
278
325
  }
279
326
  }
280
327
 
281
- const PriorityNames = {
282
- [EventPriority.Critical]: "critical",
283
- [EventPriority.Info]: "info",
284
- [EventPriority.Debug]: "debug",
285
- } as const;
286
-
287
328
  export namespace OccurrenceManager {
288
329
  /**
289
330
  * Buffer management configuration. Controls
@@ -301,25 +342,10 @@ export namespace OccurrenceManager {
301
342
  * {@link minimumEventAllowance}.
302
343
  */
303
344
  maxEventAllowance: number;
304
-
305
- /**
306
- * Minimum allowances by priority. This ensures a minimum number of events for each priority avoid LRU
307
- * harvesting.
308
- */
309
- minPriorityEventAllowance: {
310
- critical: number;
311
- info: number;
312
- debug: number;
313
- };
314
345
  }
315
346
 
316
347
  export const DefaultBufferConfig: BufferConfig = {
317
348
  minEventAllowance: 10_000,
318
349
  maxEventAllowance: 11_000,
319
- minPriorityEventAllowance: {
320
- critical: 2_000,
321
- info: 2_000,
322
- debug: 2_000,
323
- },
324
350
  };
325
351
  }
@@ -4,6 +4,7 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
+ import { ReadResult } from "#action/index.js";
7
8
  import {
8
9
  Diagnostic,
9
10
  InternalError,
@@ -244,7 +245,11 @@ export class InteractionServerMessenger extends InteractionMessenger {
244
245
  );
245
246
 
246
247
  // This potentially sends multiple DataReport Messages
247
- await this.sendDataReport(dataReport, readRequest.isFabricFiltered, payload);
248
+ await this.sendDataReport({
249
+ baseDataReport: dataReport,
250
+ forFabricFilteredRead: readRequest.isFabricFiltered,
251
+ payload,
252
+ });
248
253
  break;
249
254
  }
250
255
  case MessageType.WriteRequest: {
@@ -306,12 +311,20 @@ export class InteractionServerMessenger extends InteractionMessenger {
306
311
  * Handle a DataReport with a Payload Iterator for a DataReport to send, split them into multiple DataReport
307
312
  * messages and send them out based on the size.
308
313
  */
309
- async sendDataReport(
310
- baseDataReport: BaseDataReport,
311
- forFabricFilteredRead: boolean,
312
- payload?: DataReportPayloadIterator,
313
- waitForAck = true,
314
- ) {
314
+ async sendDataReport(options: {
315
+ baseDataReport: BaseDataReport;
316
+ forFabricFilteredRead: boolean;
317
+ payload?: DataReportPayloadIterator;
318
+ waitForAck?: boolean;
319
+ suppressEmptyReport?: boolean;
320
+ }) {
321
+ const {
322
+ baseDataReport,
323
+ forFabricFilteredRead,
324
+ payload,
325
+ waitForAck = true,
326
+ suppressEmptyReport = false,
327
+ } = options;
315
328
  const { subscriptionId, suppressResponse, interactionModelRevision } = baseDataReport;
316
329
 
317
330
  const dataReport: TypeFromSchema<typeof TlvDataReportForSend> = {
@@ -622,7 +635,9 @@ export class InteractionServerMessenger extends InteractionMessenger {
622
635
  }
623
636
  }
624
637
 
625
- await this.sendDataReportMessage(dataReport, waitForAck);
638
+ if (!suppressEmptyReport || dataReport.attributeReports?.length || dataReport.eventReports?.length) {
639
+ await this.sendDataReportMessage(dataReport, waitForAck);
640
+ }
626
641
  }
627
642
 
628
643
  async sendDataReportMessage(dataReport: TypeFromSchema<typeof TlvDataReportForSend>, waitForAck = true) {
@@ -673,6 +688,76 @@ export class InteractionServerMessenger extends InteractionMessenger {
673
688
  await this.waitForSuccess("DataReport", { timeoutMs: waitForAck ? undefined : 500 });
674
689
  }
675
690
  }
691
+
692
+ /**
693
+ * Convert a server interaction report to a DataReport entry
694
+ * TODO remove when anything is migrated completely
695
+ */
696
+ static convertServerInteractionReport(report: ReadResult.Report) {
697
+ switch (report.kind) {
698
+ case "attr-value": {
699
+ const { path, value: payload, version: dataVersion, tlv: schema } = report;
700
+ if (schema === undefined) {
701
+ throw new InternalError(`Attribute ${path.clusterId}/${path.attributeId} not found`);
702
+ }
703
+ const data: AttributeReportPayload = {
704
+ attributeData: {
705
+ path,
706
+ payload,
707
+ schema,
708
+ dataVersion,
709
+ },
710
+ hasFabricSensitiveData: true, // With this we disable the validation for missing data in encoding, we trust behavior logic
711
+ };
712
+ return data;
713
+ }
714
+ case "attr-status": {
715
+ const { path, status } = report;
716
+ const statusReport: AttributeReportPayload = {
717
+ attributeStatus: {
718
+ path,
719
+ status: { status },
720
+ },
721
+ hasFabricSensitiveData: false,
722
+ };
723
+ return statusReport;
724
+ }
725
+ case "event-value": {
726
+ const {
727
+ path,
728
+ value: payload,
729
+ number: eventNumber,
730
+ priority,
731
+ timestamp: epochTimestamp,
732
+ tlv: schema,
733
+ } = report;
734
+ const data: EventReportPayload = {
735
+ eventData: {
736
+ path,
737
+ eventNumber,
738
+ priority,
739
+ epochTimestamp,
740
+ payload,
741
+ schema,
742
+ },
743
+ hasFabricSensitiveData: true, // There are no Fabric sensitive events as of now. If ever added sanitizing needs to be added
744
+ };
745
+ return data;
746
+ }
747
+ case "event-status": {
748
+ const { path, status } = report;
749
+ const statusReport: EventReportPayload = {
750
+ eventStatus: {
751
+ path,
752
+ status: { status },
753
+ },
754
+ hasFabricSensitiveData: false,
755
+ };
756
+ return statusReport;
757
+ }
758
+ }
759
+ throw new InternalError(`Unknown report type: ${report.kind}`);
760
+ }
676
761
  }
677
762
 
678
763
  export class IncomingInteractionClientMessenger extends InteractionMessenger {
@@ -11,6 +11,5 @@ export * from "./EventDataDecoder.js";
11
11
  export * from "./InteractionClient.js";
12
12
  export * from "./InteractionEndpointStructure.js";
13
13
  export * from "./InteractionMessenger.js";
14
- export * from "./ServerSubscription.js";
15
14
  export * from "./Subscription.js";
16
15
  export * from "./SubscriptionClient.js";
@@ -250,7 +250,7 @@ export class ControllerCommissioningFlow {
250
250
  }
251
251
  } catch (error) {
252
252
  if (error instanceof RecoverableCommissioningError) {
253
- logger.error(
253
+ logger.warn(
254
254
  `Commissioning step ${step.stepNumber}.${step.subStepNumber}: ${step.name} failed with recoverable error: ${error.message} ... Continuing with process`,
255
255
  );
256
256
  } else if (error instanceof CommissioningError || error instanceof StatusResponseError) {
@@ -186,6 +186,7 @@ export class MessageExchange {
186
186
  #closeTimer: Timer | undefined;
187
187
  #isClosing = false;
188
188
  #timedInteractionTimer: Timer | undefined;
189
+ #used: boolean;
189
190
 
190
191
  readonly #peerSessionId: number;
191
192
  readonly #nodeId: NodeId | undefined;
@@ -221,6 +222,7 @@ export class MessageExchange {
221
222
 
222
223
  // When the session is supporting MRP and the channel is not reliable, use MRP handling
223
224
  this.#useMRP = session.supportsMRP && !channel.isReliable;
225
+ this.#used = !isInitiator; // If we are the initiator then exchange was not used yet, so track it
224
226
 
225
227
  logger.debug(
226
228
  "New exchange",
@@ -390,6 +392,7 @@ export class MessageExchange {
390
392
  if (this.#sentMessageToAck !== undefined && messageType !== SecureMessageType.StandaloneAck)
391
393
  throw new MatterFlowError("The previous message has not been acked yet, cannot send a new message.");
392
394
 
395
+ this.#used = true;
393
396
  this.session.notifyActivity(false);
394
397
 
395
398
  let ackedMessageId = includeAcknowledgeMessageId;
@@ -660,6 +663,12 @@ export class MessageExchange {
660
663
  // close was already called, so let retries happen because close not forced
661
664
  return;
662
665
  }
666
+ if (!this.#used) {
667
+ // The exchange was never in use, so we can close it directly
668
+ // If we see that in the wild we should fix the reasons
669
+ logger.info(`Exchange ${this.session.name} / ${this.#exchangeId} was never used, closing directly`);
670
+ return this.#close();
671
+ }
663
672
  this.#isClosing = true;
664
673
 
665
674
  if (this.#receivedMessageToAck !== undefined) {
@@ -669,7 +678,10 @@ export class MessageExchange {
669
678
  try {
670
679
  await this.sendStandaloneAckForMessage(messageToAck);
671
680
  } catch (error) {
672
- logger.error("An error happened when closing the exchange", error);
681
+ logger.error(
682
+ `An error happened when closing the exchange ${this.session.name} / ${this.#exchangeId}`,
683
+ error,
684
+ );
673
685
  }
674
686
  if (force) {
675
687
  // We have sent the Ack, so close here, no retries because close is forced
@@ -1,116 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2022-2025 Matter.js Authors
4
- * SPDX-License-Identifier: Apache-2.0
5
- */
6
- import { NumberedOccurrence } from "#events/Occurrence.js";
7
- import { MaybePromise } from "#general";
8
- import { PeerAddress } from "#peer/PeerAddress.js";
9
- import type { MessageExchange } from "#protocol/MessageExchange.js";
10
- import { SecureSession } from "#session/SecureSession.js";
11
- import { TlvEventFilter, TlvSchema, TypeFromSchema } from "#types";
12
- import { AnyAttributeServer } from "../cluster/server/AttributeServer.js";
13
- import { AnyEventServer } from "../cluster/server/EventServer.js";
14
- import { AttributePath, EventPath, InteractionEndpointStructure } from "./InteractionEndpointStructure.js";
15
- import { InteractionServerMessenger } from "./InteractionMessenger.js";
16
- import { Subscription, SubscriptionCriteria } from "./Subscription.js";
17
- export declare const MAX_INTERVAL_PUBLISHER_LIMIT_S: number; /** 1 hour */
18
- export declare const INTERNAL_INTERVAL_PUBLISHER_LIMIT_S: number; /** 3 min */
19
- export declare const MIN_INTERVAL_S = 2;
20
- export declare const DEFAULT_RANDOMIZATION_WINDOW_S = 10;
21
- /**
22
- * Server options that control subscription handling.
23
- */
24
- export interface ServerSubscriptionConfig {
25
- /**
26
- * Optional maximum subscription interval to use for sending subscription reports. It will be used if not too
27
- * low and inside the range requested by the connected controller.
28
- */
29
- maxIntervalSeconds: number;
30
- /**
31
- * Optional minimum subscription interval to use for sending subscription reports. It will be used when other
32
- * calculated values are smaller than it. Use this to make sure your device hardware can handle the load and to
33
- * set limits.
34
- */
35
- minIntervalSeconds: number;
36
- /**
37
- * Optional subscription randomization window to use for sending subscription reports. This specifies a window
38
- * in seconds from which a random part is added to the calculated maximum interval to make sure that devices
39
- * that get powered on in parallel not all send at the same timepoint.
40
- */
41
- randomizationWindowSeconds: number;
42
- }
43
- export declare namespace ServerSubscriptionConfig {
44
- /**
45
- * Validate options and set defaults.
46
- *
47
- * @returns the resulting options
48
- */
49
- function of(options?: Partial<ServerSubscriptionConfig>): {
50
- maxIntervalSeconds: number;
51
- minIntervalSeconds: number;
52
- randomizationWindowSeconds: number;
53
- };
54
- }
55
- /**
56
- * Interface between {@link ServerSubscription} and the local Matter environment.
57
- */
58
- export interface ServerSubscriptionContext {
59
- session: SecureSession;
60
- structure: InteractionEndpointStructure;
61
- readAttribute(path: AttributePath, attribute: AnyAttributeServer<unknown>, offline?: boolean): {
62
- version: number;
63
- value: unknown;
64
- };
65
- readEndpointAttributesForSubscription(attributes: {
66
- path: AttributePath;
67
- attribute: AnyAttributeServer<unknown>;
68
- offline?: boolean;
69
- }[]): {
70
- path: AttributePath;
71
- attribute: AnyAttributeServer<unknown>;
72
- version: number;
73
- value: unknown;
74
- }[];
75
- readEvent(path: EventPath, event: AnyEventServer<any, any>, eventFilters: TypeFromSchema<typeof TlvEventFilter>[] | undefined): Promise<NumberedOccurrence[]>;
76
- initiateExchange(address: PeerAddress, protocolId: number): MessageExchange;
77
- }
78
- /**
79
- * Implements the server side of a single subscription.
80
- */
81
- export declare class ServerSubscription extends Subscription {
82
- #private;
83
- constructor(options: {
84
- id: number;
85
- context: ServerSubscriptionContext;
86
- criteria: SubscriptionCriteria;
87
- minIntervalFloorSeconds: number;
88
- maxIntervalCeilingSeconds: number;
89
- subscriptionOptions: ServerSubscriptionConfig;
90
- useAsMaxInterval?: number;
91
- useAsSendInterval?: number;
92
- });
93
- unregisterAttributeListeners(list: Array<string>): void;
94
- unregisterEventListeners(list: Array<string>): void;
95
- /**
96
- * Update the session after an endpoint structure change. The method will initialize all missing new attributes and
97
- * events and will remove listeners no longer needed.
98
- * Newly added attributes are then treated as "changed values" and will be sent as subscription data update to the
99
- * controller. The data of newly added events are not sent automatically.
100
- */
101
- updateSubscription(): Promise<void>;
102
- get sendInterval(): number;
103
- get minIntervalFloorSeconds(): number;
104
- get maxIntervalCeilingSeconds(): number;
105
- activate(): void;
106
- sendInitialReport(messenger: InteractionServerMessenger): Promise<void>;
107
- attributeChangeListener<T>(path: AttributePath, schema: TlvSchema<T>, version: number, value: T): void;
108
- attributeChangeHandler<T>(path: AttributePath, schema: TlvSchema<T>, version: number, value: T): MaybePromise<void>;
109
- eventChangeListener<T>(path: EventPath, schema: TlvSchema<T>, newEvent: NumberedOccurrence): void;
110
- protected destroy(): Promise<void>;
111
- /**
112
- * Closes the subscription and flushes all outstanding data updates if requested.
113
- */
114
- close(graceful?: boolean, cancelledByPeer?: boolean): Promise<void>;
115
- }
116
- //# sourceMappingURL=ServerSubscription.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ServerSubscription.d.ts","sourceRoot":"","sources":["../../../src/interaction/ServerSubscription.ts"],"names":[],"mappings":"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"}