@matter/protocol 0.16.8-alpha.0-20260123-dff2cae52 → 0.16.8-alpha.0-20260125-38e62bc3e

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 (79) hide show
  1. package/dist/cjs/action/client/ClientInteraction.d.ts +4 -4
  2. package/dist/cjs/action/client/ClientInteraction.d.ts.map +1 -1
  3. package/dist/cjs/action/client/ClientInteraction.js +48 -6
  4. package/dist/cjs/action/client/ClientInteraction.js.map +1 -1
  5. package/dist/cjs/action/client/QueuedClientInteraction.d.ts +0 -1
  6. package/dist/cjs/action/client/QueuedClientInteraction.d.ts.map +1 -1
  7. package/dist/cjs/action/client/QueuedClientInteraction.js +0 -1
  8. package/dist/cjs/action/client/QueuedClientInteraction.js.map +1 -1
  9. package/dist/cjs/action/client/subscription/ClientSubscriptionHandler.d.ts.map +1 -1
  10. package/dist/cjs/action/client/subscription/ClientSubscriptionHandler.js +5 -2
  11. package/dist/cjs/action/client/subscription/ClientSubscriptionHandler.js.map +1 -1
  12. package/dist/cjs/action/server/AttributeWriteResponse.d.ts +1 -1
  13. package/dist/cjs/action/server/AttributeWriteResponse.d.ts.map +1 -1
  14. package/dist/cjs/action/server/AttributeWriteResponse.js +0 -6
  15. package/dist/cjs/action/server/AttributeWriteResponse.js.map +1 -1
  16. package/dist/cjs/action/server/DataResponse.d.ts +5 -0
  17. package/dist/cjs/action/server/DataResponse.d.ts.map +1 -1
  18. package/dist/cjs/action/server/DataResponse.js +7 -0
  19. package/dist/cjs/action/server/DataResponse.js.map +1 -1
  20. package/dist/cjs/action/server/ServerInteraction.js.map +1 -1
  21. package/dist/cjs/interaction/InteractionMessenger.d.ts +30 -30
  22. package/dist/cjs/interaction/InteractionMessenger.d.ts.map +1 -1
  23. package/dist/cjs/interaction/InteractionMessenger.js +81 -12
  24. package/dist/cjs/interaction/InteractionMessenger.js.map +1 -1
  25. package/dist/cjs/mdns/MdnsClient.d.ts.map +1 -1
  26. package/dist/cjs/mdns/MdnsClient.js +157 -100
  27. package/dist/cjs/mdns/MdnsClient.js.map +1 -1
  28. package/dist/cjs/mdns/MdnsServer.d.ts +2 -0
  29. package/dist/cjs/mdns/MdnsServer.d.ts.map +1 -1
  30. package/dist/cjs/mdns/MdnsServer.js +45 -5
  31. package/dist/cjs/mdns/MdnsServer.js.map +1 -1
  32. package/dist/cjs/peer/ControllerCommissioningFlow.d.ts.map +1 -1
  33. package/dist/cjs/peer/ControllerCommissioningFlow.js +3 -1
  34. package/dist/cjs/peer/ControllerCommissioningFlow.js.map +1 -1
  35. package/dist/esm/action/client/ClientInteraction.d.ts +4 -4
  36. package/dist/esm/action/client/ClientInteraction.d.ts.map +1 -1
  37. package/dist/esm/action/client/ClientInteraction.js +49 -6
  38. package/dist/esm/action/client/ClientInteraction.js.map +1 -1
  39. package/dist/esm/action/client/QueuedClientInteraction.d.ts +0 -1
  40. package/dist/esm/action/client/QueuedClientInteraction.d.ts.map +1 -1
  41. package/dist/esm/action/client/QueuedClientInteraction.js +0 -1
  42. package/dist/esm/action/client/QueuedClientInteraction.js.map +1 -1
  43. package/dist/esm/action/client/subscription/ClientSubscriptionHandler.d.ts.map +1 -1
  44. package/dist/esm/action/client/subscription/ClientSubscriptionHandler.js +5 -2
  45. package/dist/esm/action/client/subscription/ClientSubscriptionHandler.js.map +1 -1
  46. package/dist/esm/action/server/AttributeWriteResponse.d.ts +1 -1
  47. package/dist/esm/action/server/AttributeWriteResponse.d.ts.map +1 -1
  48. package/dist/esm/action/server/AttributeWriteResponse.js +0 -6
  49. package/dist/esm/action/server/AttributeWriteResponse.js.map +1 -1
  50. package/dist/esm/action/server/DataResponse.d.ts +5 -0
  51. package/dist/esm/action/server/DataResponse.d.ts.map +1 -1
  52. package/dist/esm/action/server/DataResponse.js +7 -0
  53. package/dist/esm/action/server/DataResponse.js.map +1 -1
  54. package/dist/esm/action/server/ServerInteraction.js.map +1 -1
  55. package/dist/esm/interaction/InteractionMessenger.d.ts +30 -30
  56. package/dist/esm/interaction/InteractionMessenger.d.ts.map +1 -1
  57. package/dist/esm/interaction/InteractionMessenger.js +82 -12
  58. package/dist/esm/interaction/InteractionMessenger.js.map +1 -1
  59. package/dist/esm/mdns/MdnsClient.d.ts.map +1 -1
  60. package/dist/esm/mdns/MdnsClient.js +157 -100
  61. package/dist/esm/mdns/MdnsClient.js.map +1 -1
  62. package/dist/esm/mdns/MdnsServer.d.ts +2 -0
  63. package/dist/esm/mdns/MdnsServer.d.ts.map +1 -1
  64. package/dist/esm/mdns/MdnsServer.js +45 -5
  65. package/dist/esm/mdns/MdnsServer.js.map +1 -1
  66. package/dist/esm/peer/ControllerCommissioningFlow.d.ts.map +1 -1
  67. package/dist/esm/peer/ControllerCommissioningFlow.js +3 -1
  68. package/dist/esm/peer/ControllerCommissioningFlow.js.map +1 -1
  69. package/package.json +6 -6
  70. package/src/action/client/ClientInteraction.ts +62 -6
  71. package/src/action/client/QueuedClientInteraction.ts +0 -1
  72. package/src/action/client/subscription/ClientSubscriptionHandler.ts +5 -2
  73. package/src/action/server/AttributeWriteResponse.ts +4 -16
  74. package/src/action/server/DataResponse.ts +8 -0
  75. package/src/action/server/ServerInteraction.ts +2 -2
  76. package/src/interaction/InteractionMessenger.ts +113 -15
  77. package/src/mdns/MdnsClient.ts +207 -102
  78. package/src/mdns/MdnsServer.ts +79 -6
  79. package/src/peer/ControllerCommissioningFlow.ts +5 -1
@@ -24,10 +24,14 @@ import {
24
24
  ObserverGroup,
25
25
  Time,
26
26
  Timer,
27
+ Timestamp,
27
28
  } from "#general";
28
29
 
29
30
  const logger = Logger.get("MdnsServer");
30
31
 
32
+ /** RFC 6762 §7.3 - Window for duplicate question suppression (999ms per python-zeroconf) */
33
+ export const QUESTION_SUPPRESSION_WINDOW = Millis(999);
34
+
31
35
  export class MdnsServer {
32
36
  #lifetime: Lifetime;
33
37
  #observers = new ObserverGroup();
@@ -51,6 +55,14 @@ export class MdnsServer {
51
55
  );
52
56
  readonly #recordLastSentAsMulticastAnswer = new Map<string, number>();
53
57
  readonly #truncatedQueryCache = new Map<string, { message: MdnsSocket.Message; timer: Timer }>();
58
+ /** RFC 6762 §7.3 - Tracks recently answered queries for duplicate suppression */
59
+ readonly #recentlyAnsweredQueries = new Map<
60
+ string,
61
+ {
62
+ knownAnswerHashes: Set<string>;
63
+ timestamp: Timestamp;
64
+ }
65
+ >();
54
66
 
55
67
  readonly #socket: MdnsSocket;
56
68
 
@@ -135,6 +147,11 @@ export class MdnsServer {
135
147
  );
136
148
  if (answers.length === 0) continue; // Nothing to send
137
149
 
150
+ // RFC 6762 §7.3 - Check for duplicate question suppression
151
+ if (this.#shouldSuppressResponse(queries, knownAnswers, sourceIntf, answers)) {
152
+ continue; // Another responder already answered
153
+ }
154
+
138
155
  answers.forEach(answer =>
139
156
  this.#recordLastSentAsMulticastAnswer.set(this.buildDnsRecordKey(answer, sourceIntf), now),
140
157
  );
@@ -181,7 +198,6 @@ export class MdnsServer {
181
198
  for (const [service, serviceRecords] of records) {
182
199
  if (services.length && !services.includes(service)) continue;
183
200
 
184
- // TODO: try to combine the messages to avoid sending multiple messages but keep under 1500 bytes per message
185
201
  await this.#announceRecordsForInterface(netInterface, serviceRecords);
186
202
  await Time.sleep("MDNS delay", Millis(20 + Math.floor(Math.random() * 100))); // as per DNS-SD spec wait 20-120ms before sending more packets
187
203
  }
@@ -198,15 +214,12 @@ export class MdnsServer {
198
214
  const records = await this.#records.get(netInterface);
199
215
  for (const [service, serviceRecords] of records) {
200
216
  if (services.length && !services.includes(service)) continue;
201
- const instanceSet = new Set<string>();
217
+
218
+ // Set TTL to Instant for all records to expire them
202
219
  serviceRecords.forEach(record => {
203
220
  record.ttl = Instant;
204
- if (record.recordType === DnsRecordType.TXT) {
205
- instanceSet.add(record.name);
206
- }
207
221
  });
208
222
 
209
- // TODO: try to combine the messages to avoid sending multiple messages but keep under 1500 bytes per message
210
223
  await this.#announceRecordsForInterface(netInterface, serviceRecords);
211
224
  this.#recordsGenerator.delete(service);
212
225
  await Time.sleep("MDNS delay", Millis(20 + Math.floor(Math.random() * 100))); // as per DNS-SD spec wait 20-120ms before sending more packets
@@ -220,12 +233,14 @@ export class MdnsServer {
220
233
  async setRecordsGenerator(service: string, generator: MdnsServer.RecordGenerator) {
221
234
  await this.#records.clear();
222
235
  this.#recordLastSentAsMulticastAnswer.clear();
236
+ this.#recentlyAnsweredQueries.clear();
223
237
  this.#recordsGenerator.set(service, generator);
224
238
  }
225
239
 
226
240
  async #resetServices() {
227
241
  await this.#records.clear();
228
242
  this.#recordLastSentAsMulticastAnswer.clear();
243
+ this.#recentlyAnsweredQueries.clear();
229
244
  }
230
245
 
231
246
  async close() {
@@ -237,6 +252,7 @@ export class MdnsServer {
237
252
  }
238
253
  this.#truncatedQueryCache.clear();
239
254
  this.#recordLastSentAsMulticastAnswer.clear();
255
+ this.#recentlyAnsweredQueries.clear();
240
256
  }
241
257
 
242
258
  #getMulticastInterfacesForAnnounce() {
@@ -252,6 +268,63 @@ export class MdnsServer {
252
268
  }
253
269
  }
254
270
 
271
+ /**
272
+ * RFC 6762 §7.3 - Checks if we should suppress a response because another responder
273
+ * has recently answered the same question with answers that cover what we'd send.
274
+ * Also, lazily cleans up expired entries from the cache.
275
+ */
276
+ #shouldSuppressResponse(
277
+ queries: { name: string; recordType: DnsRecordType }[],
278
+ knownAnswers: DnsRecord<any>[],
279
+ sourceIntf: string,
280
+ answers: DnsRecord<any>[],
281
+ ): boolean {
282
+ const now = Time.nowMs;
283
+
284
+ // Clean up expired entries
285
+ for (const [key, entry] of this.#recentlyAnsweredQueries) {
286
+ if (now - entry.timestamp >= QUESTION_SUPPRESSION_WINDOW) {
287
+ this.#recentlyAnsweredQueries.delete(key);
288
+ }
289
+ }
290
+
291
+ // Build query signature
292
+ const queryKey =
293
+ queries
294
+ .map(q => `${q.name}-${q.recordType}`)
295
+ .sort()
296
+ .join("|") + `-${sourceIntf}`;
297
+
298
+ const cached = this.#recentlyAnsweredQueries.get(queryKey);
299
+
300
+ if (cached && now - cached.timestamp < QUESTION_SUPPRESSION_WINDOW) {
301
+ // Check if all our answers are already in the known answers from the cached response
302
+ const answerHashes = answers.map(a => this.buildDnsRecordKey(a, sourceIntf));
303
+ const allAnswersKnown = answerHashes.every(h => cached.knownAnswerHashes.has(h));
304
+
305
+ if (allAnswersKnown) {
306
+ return true; // Suppress - another responder already answered with our records
307
+ }
308
+ }
309
+
310
+ // Record that we're answering this question
311
+ const knownAnswerHashes = new Set<string>();
312
+ for (const answer of knownAnswers) {
313
+ knownAnswerHashes.add(this.buildDnsRecordKey(answer, sourceIntf));
314
+ }
315
+ // Also add our answers to the known set
316
+ for (const answer of answers) {
317
+ knownAnswerHashes.add(this.buildDnsRecordKey(answer, sourceIntf));
318
+ }
319
+
320
+ this.#recentlyAnsweredQueries.set(queryKey, {
321
+ knownAnswerHashes,
322
+ timestamp: now,
323
+ });
324
+
325
+ return false;
326
+ }
327
+
255
328
  async #processTruncatedQuery(key: string) {
256
329
  const { message, timer } = this.#truncatedQueryCache.get(key) ?? {};
257
330
  this.#truncatedQueryCache.delete(key);
@@ -6,6 +6,7 @@
6
6
 
7
7
  import { ClientInteraction } from "#action/client/ClientInteraction.js";
8
8
  import { ClientRead } from "#action/client/ClientRead.js";
9
+ import { WriteResult } from "#action/index.js";
9
10
  import { Invoke } from "#action/request/Invoke.js";
10
11
  import { Read } from "#action/request/Read.js";
11
12
  import { Write } from "#action/request/Write.js";
@@ -1673,7 +1674,7 @@ export class ControllerCommissioningFlow {
1673
1674
  for (const requestorEndpoint of this.collectedCommissioningData.otaRequestorList) {
1674
1675
  try {
1675
1676
  // Fabric scoped attribute, so we just overwrite our value
1676
- await this.interaction.write(
1677
+ const writeResult = await this.interaction.write(
1677
1678
  Write(
1678
1679
  Write.Attribute({
1679
1680
  endpoint: requestorEndpoint,
@@ -1689,6 +1690,9 @@ export class ControllerCommissioningFlow {
1689
1690
  }),
1690
1691
  ),
1691
1692
  );
1693
+
1694
+ WriteResult.assertSuccess(writeResult);
1695
+
1692
1696
  success = true;
1693
1697
  logger.debug(`Added default OTA provider on endpoint ${endpoint}`);
1694
1698
  } catch (error) {