@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.
- package/dist/cjs/action/client/ClientInteraction.d.ts +4 -4
- package/dist/cjs/action/client/ClientInteraction.d.ts.map +1 -1
- package/dist/cjs/action/client/ClientInteraction.js +48 -6
- package/dist/cjs/action/client/ClientInteraction.js.map +1 -1
- package/dist/cjs/action/client/QueuedClientInteraction.d.ts +0 -1
- package/dist/cjs/action/client/QueuedClientInteraction.d.ts.map +1 -1
- package/dist/cjs/action/client/QueuedClientInteraction.js +0 -1
- package/dist/cjs/action/client/QueuedClientInteraction.js.map +1 -1
- package/dist/cjs/action/client/subscription/ClientSubscriptionHandler.d.ts.map +1 -1
- package/dist/cjs/action/client/subscription/ClientSubscriptionHandler.js +5 -2
- package/dist/cjs/action/client/subscription/ClientSubscriptionHandler.js.map +1 -1
- package/dist/cjs/action/server/AttributeWriteResponse.d.ts +1 -1
- package/dist/cjs/action/server/AttributeWriteResponse.d.ts.map +1 -1
- package/dist/cjs/action/server/AttributeWriteResponse.js +0 -6
- package/dist/cjs/action/server/AttributeWriteResponse.js.map +1 -1
- package/dist/cjs/action/server/DataResponse.d.ts +5 -0
- package/dist/cjs/action/server/DataResponse.d.ts.map +1 -1
- package/dist/cjs/action/server/DataResponse.js +7 -0
- package/dist/cjs/action/server/DataResponse.js.map +1 -1
- package/dist/cjs/action/server/ServerInteraction.js.map +1 -1
- package/dist/cjs/interaction/InteractionMessenger.d.ts +30 -30
- package/dist/cjs/interaction/InteractionMessenger.d.ts.map +1 -1
- package/dist/cjs/interaction/InteractionMessenger.js +81 -12
- package/dist/cjs/interaction/InteractionMessenger.js.map +1 -1
- package/dist/cjs/mdns/MdnsClient.d.ts.map +1 -1
- package/dist/cjs/mdns/MdnsClient.js +157 -100
- package/dist/cjs/mdns/MdnsClient.js.map +1 -1
- package/dist/cjs/mdns/MdnsServer.d.ts +2 -0
- package/dist/cjs/mdns/MdnsServer.d.ts.map +1 -1
- package/dist/cjs/mdns/MdnsServer.js +45 -5
- package/dist/cjs/mdns/MdnsServer.js.map +1 -1
- package/dist/cjs/peer/ControllerCommissioningFlow.d.ts.map +1 -1
- package/dist/cjs/peer/ControllerCommissioningFlow.js +3 -1
- package/dist/cjs/peer/ControllerCommissioningFlow.js.map +1 -1
- package/dist/esm/action/client/ClientInteraction.d.ts +4 -4
- package/dist/esm/action/client/ClientInteraction.d.ts.map +1 -1
- package/dist/esm/action/client/ClientInteraction.js +49 -6
- package/dist/esm/action/client/ClientInteraction.js.map +1 -1
- package/dist/esm/action/client/QueuedClientInteraction.d.ts +0 -1
- package/dist/esm/action/client/QueuedClientInteraction.d.ts.map +1 -1
- package/dist/esm/action/client/QueuedClientInteraction.js +0 -1
- package/dist/esm/action/client/QueuedClientInteraction.js.map +1 -1
- package/dist/esm/action/client/subscription/ClientSubscriptionHandler.d.ts.map +1 -1
- package/dist/esm/action/client/subscription/ClientSubscriptionHandler.js +5 -2
- package/dist/esm/action/client/subscription/ClientSubscriptionHandler.js.map +1 -1
- package/dist/esm/action/server/AttributeWriteResponse.d.ts +1 -1
- package/dist/esm/action/server/AttributeWriteResponse.d.ts.map +1 -1
- package/dist/esm/action/server/AttributeWriteResponse.js +0 -6
- package/dist/esm/action/server/AttributeWriteResponse.js.map +1 -1
- package/dist/esm/action/server/DataResponse.d.ts +5 -0
- package/dist/esm/action/server/DataResponse.d.ts.map +1 -1
- package/dist/esm/action/server/DataResponse.js +7 -0
- package/dist/esm/action/server/DataResponse.js.map +1 -1
- package/dist/esm/action/server/ServerInteraction.js.map +1 -1
- package/dist/esm/interaction/InteractionMessenger.d.ts +30 -30
- package/dist/esm/interaction/InteractionMessenger.d.ts.map +1 -1
- package/dist/esm/interaction/InteractionMessenger.js +82 -12
- package/dist/esm/interaction/InteractionMessenger.js.map +1 -1
- package/dist/esm/mdns/MdnsClient.d.ts.map +1 -1
- package/dist/esm/mdns/MdnsClient.js +157 -100
- package/dist/esm/mdns/MdnsClient.js.map +1 -1
- package/dist/esm/mdns/MdnsServer.d.ts +2 -0
- package/dist/esm/mdns/MdnsServer.d.ts.map +1 -1
- package/dist/esm/mdns/MdnsServer.js +45 -5
- package/dist/esm/mdns/MdnsServer.js.map +1 -1
- package/dist/esm/peer/ControllerCommissioningFlow.d.ts.map +1 -1
- package/dist/esm/peer/ControllerCommissioningFlow.js +3 -1
- package/dist/esm/peer/ControllerCommissioningFlow.js.map +1 -1
- package/package.json +6 -6
- package/src/action/client/ClientInteraction.ts +62 -6
- package/src/action/client/QueuedClientInteraction.ts +0 -1
- package/src/action/client/subscription/ClientSubscriptionHandler.ts +5 -2
- package/src/action/server/AttributeWriteResponse.ts +4 -16
- package/src/action/server/DataResponse.ts +8 -0
- package/src/action/server/ServerInteraction.ts +2 -2
- package/src/interaction/InteractionMessenger.ts +113 -15
- package/src/mdns/MdnsClient.ts +207 -102
- package/src/mdns/MdnsServer.ts +79 -6
- package/src/peer/ControllerCommissioningFlow.ts +5 -1
package/src/mdns/MdnsServer.ts
CHANGED
|
@@ -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
|
-
|
|
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) {
|