@matter/protocol 0.16.8-alpha.0-20260123-dff2cae52 → 0.16.8-alpha.0-20260127-65e1b40e2
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/dcl/DclCertificateService.d.ts.map +1 -1
- package/dist/cjs/dcl/DclCertificateService.js +3 -0
- package/dist/cjs/dcl/DclCertificateService.js.map +1 -1
- package/dist/cjs/dcl/DclOtaUpdateService.d.ts.map +1 -1
- package/dist/cjs/dcl/DclOtaUpdateService.js +6 -4
- package/dist/cjs/dcl/DclOtaUpdateService.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 +163 -102
- 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/cjs/peer/Peer.d.ts +2 -1
- package/dist/cjs/peer/Peer.d.ts.map +1 -1
- package/dist/cjs/peer/Peer.js +20 -3
- package/dist/cjs/peer/Peer.js.map +1 -1
- package/dist/cjs/peer/PeerAddressStore.d.ts +1 -11
- package/dist/cjs/peer/PeerAddressStore.d.ts.map +1 -1
- package/dist/cjs/peer/PeerAddressStore.js +1 -4
- package/dist/cjs/peer/PeerAddressStore.js.map +1 -1
- package/dist/cjs/peer/PeerDescriptor.d.ts +1 -9
- package/dist/cjs/peer/PeerDescriptor.d.ts.map +1 -1
- package/dist/cjs/peer/PeerDescriptor.js +1 -6
- package/dist/cjs/peer/PeerDescriptor.js.map +1 -1
- package/dist/cjs/peer/PeerSet.d.ts +1 -1
- package/dist/cjs/peer/PeerSet.d.ts.map +1 -1
- package/dist/cjs/peer/PeerSet.js +55 -22
- package/dist/cjs/peer/PeerSet.js.map +2 -2
- package/dist/cjs/protocol/ExchangeManager.d.ts.map +1 -1
- package/dist/cjs/protocol/ExchangeManager.js +2 -2
- package/dist/cjs/protocol/ExchangeManager.js.map +1 -1
- package/dist/cjs/protocol/ExchangeProvider.d.ts.map +1 -1
- package/dist/cjs/protocol/ExchangeProvider.js +3 -3
- package/dist/cjs/protocol/ExchangeProvider.js.map +1 -1
- package/dist/cjs/protocol/MRP.d.ts +54 -0
- package/dist/cjs/protocol/MRP.d.ts.map +1 -0
- package/dist/cjs/protocol/MRP.js +96 -0
- package/dist/cjs/protocol/MRP.js.map +6 -0
- package/dist/cjs/protocol/MessageChannel.d.ts +0 -23
- package/dist/cjs/protocol/MessageChannel.d.ts.map +1 -1
- package/dist/cjs/protocol/MessageChannel.js +15 -47
- package/dist/cjs/protocol/MessageChannel.js.map +2 -2
- package/dist/cjs/protocol/MessageExchange.d.ts.map +1 -1
- package/dist/cjs/protocol/MessageExchange.js +7 -7
- package/dist/cjs/protocol/MessageExchange.js.map +1 -1
- package/dist/cjs/protocol/index.d.ts +1 -0
- package/dist/cjs/protocol/index.d.ts.map +1 -1
- package/dist/cjs/protocol/index.js +1 -0
- package/dist/cjs/protocol/index.js.map +1 -1
- package/dist/cjs/session/NodeSession.js +2 -2
- package/dist/cjs/session/NodeSession.js.map +1 -1
- package/dist/cjs/session/Session.d.ts +1 -0
- package/dist/cjs/session/Session.d.ts.map +1 -1
- package/dist/cjs/session/case/CaseClient.d.ts.map +1 -1
- package/dist/cjs/session/case/CaseClient.js +1 -1
- package/dist/cjs/session/case/CaseClient.js.map +1 -1
- package/dist/cjs/session/case/CaseServer.d.ts.map +1 -1
- package/dist/cjs/session/case/CaseServer.js +4 -1
- package/dist/cjs/session/case/CaseServer.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/dcl/DclCertificateService.d.ts.map +1 -1
- package/dist/esm/dcl/DclCertificateService.js +3 -0
- package/dist/esm/dcl/DclCertificateService.js.map +1 -1
- package/dist/esm/dcl/DclOtaUpdateService.d.ts.map +1 -1
- package/dist/esm/dcl/DclOtaUpdateService.js +6 -4
- package/dist/esm/dcl/DclOtaUpdateService.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 +163 -102
- 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/dist/esm/peer/Peer.d.ts +2 -1
- package/dist/esm/peer/Peer.d.ts.map +1 -1
- package/dist/esm/peer/Peer.js +20 -3
- package/dist/esm/peer/Peer.js.map +1 -1
- package/dist/esm/peer/PeerAddressStore.d.ts +1 -11
- package/dist/esm/peer/PeerAddressStore.d.ts.map +1 -1
- package/dist/esm/peer/PeerAddressStore.js +1 -4
- package/dist/esm/peer/PeerAddressStore.js.map +1 -1
- package/dist/esm/peer/PeerDescriptor.d.ts +1 -9
- package/dist/esm/peer/PeerDescriptor.d.ts.map +1 -1
- package/dist/esm/peer/PeerDescriptor.js +1 -6
- package/dist/esm/peer/PeerDescriptor.js.map +1 -1
- package/dist/esm/peer/PeerSet.d.ts +1 -1
- package/dist/esm/peer/PeerSet.d.ts.map +1 -1
- package/dist/esm/peer/PeerSet.js +60 -24
- package/dist/esm/peer/PeerSet.js.map +2 -2
- package/dist/esm/protocol/ExchangeManager.d.ts.map +1 -1
- package/dist/esm/protocol/ExchangeManager.js +2 -2
- package/dist/esm/protocol/ExchangeManager.js.map +1 -1
- package/dist/esm/protocol/ExchangeProvider.d.ts.map +1 -1
- package/dist/esm/protocol/ExchangeProvider.js +3 -3
- package/dist/esm/protocol/ExchangeProvider.js.map +1 -1
- package/dist/esm/protocol/MRP.d.ts +54 -0
- package/dist/esm/protocol/MRP.d.ts.map +1 -0
- package/dist/esm/protocol/MRP.js +76 -0
- package/dist/esm/protocol/MRP.js.map +6 -0
- package/dist/esm/protocol/MessageChannel.d.ts +0 -23
- package/dist/esm/protocol/MessageChannel.d.ts.map +1 -1
- package/dist/esm/protocol/MessageChannel.js +16 -54
- package/dist/esm/protocol/MessageChannel.js.map +2 -2
- package/dist/esm/protocol/MessageExchange.d.ts.map +1 -1
- package/dist/esm/protocol/MessageExchange.js +3 -3
- package/dist/esm/protocol/MessageExchange.js.map +1 -1
- package/dist/esm/protocol/index.d.ts +1 -0
- package/dist/esm/protocol/index.d.ts.map +1 -1
- package/dist/esm/protocol/index.js +1 -0
- package/dist/esm/protocol/index.js.map +1 -1
- package/dist/esm/session/NodeSession.js +2 -2
- package/dist/esm/session/NodeSession.js.map +1 -1
- package/dist/esm/session/Session.d.ts +1 -0
- package/dist/esm/session/Session.d.ts.map +1 -1
- package/dist/esm/session/case/CaseClient.d.ts.map +1 -1
- package/dist/esm/session/case/CaseClient.js +2 -2
- package/dist/esm/session/case/CaseClient.js.map +1 -1
- package/dist/esm/session/case/CaseServer.d.ts.map +1 -1
- package/dist/esm/session/case/CaseServer.js +4 -1
- package/dist/esm/session/case/CaseServer.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/dcl/DclCertificateService.ts +3 -0
- package/src/dcl/DclOtaUpdateService.ts +11 -5
- package/src/interaction/InteractionMessenger.ts +113 -15
- package/src/mdns/MdnsClient.ts +216 -104
- package/src/mdns/MdnsServer.ts +79 -6
- package/src/peer/ControllerCommissioningFlow.ts +5 -1
- package/src/peer/Peer.ts +28 -5
- package/src/peer/PeerAddressStore.ts +1 -19
- package/src/peer/PeerDescriptor.ts +1 -15
- package/src/peer/PeerSet.ts +82 -35
- package/src/protocol/ExchangeManager.ts +5 -2
- package/src/protocol/ExchangeProvider.ts +3 -3
- package/src/protocol/MRP.ts +146 -0
- package/src/protocol/MessageChannel.ts +16 -101
- package/src/protocol/MessageExchange.ts +4 -3
- package/src/protocol/index.ts +1 -0
- package/src/session/NodeSession.ts +3 -3
- package/src/session/Session.ts +1 -0
- package/src/session/case/CaseClient.ts +8 -2
- package/src/session/case/CaseServer.ts +4 -0
package/src/mdns/MdnsClient.ts
CHANGED
|
@@ -63,6 +63,19 @@ const logger = Logger.get("MdnsClient");
|
|
|
63
63
|
|
|
64
64
|
const MDNS_EXPIRY_GRACE_PERIOD_FACTOR = 1.05;
|
|
65
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Protection window for out-of-order goodbye packets (RFC 6762).
|
|
68
|
+
* If a record was discovered within this window, ignore TTL=0 goodbye packets
|
|
69
|
+
* as they likely arrived out of order (goodbye sent before an announcement but received after).
|
|
70
|
+
*/
|
|
71
|
+
const GOODBYE_PROTECTION_WINDOW = Millis(1000);
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Minimum TTL for PTR records to prevent DoS attacks with very short TTLs.
|
|
75
|
+
* Value based on python-zeroconf/bonjour implementation.
|
|
76
|
+
*/
|
|
77
|
+
const RECORD_MIN_TTL = Seconds(15);
|
|
78
|
+
|
|
66
79
|
type MatterServerRecordWithExpire = ServerAddressUdp & AddressLifespan;
|
|
67
80
|
|
|
68
81
|
/** Type for commissionable Device records including Lifespan details. */
|
|
@@ -537,12 +550,19 @@ export class MdnsClient implements Scanner {
|
|
|
537
550
|
}
|
|
538
551
|
const deviceMatterQname = getOperationalDeviceQname(fabric.globalId, nodeId);
|
|
539
552
|
|
|
540
|
-
let storedDevice =
|
|
541
|
-
if (storedDevice === undefined) {
|
|
553
|
+
let storedDevice = this.#getOperationalDeviceRecords(deviceMatterQname);
|
|
554
|
+
if (storedDevice === undefined || ignoreExistingRecords) {
|
|
542
555
|
using _finding = this.#lifetime.join(
|
|
543
556
|
"finding peer",
|
|
544
557
|
PeerAddress({ fabricIndex: fabric.fabricIndex, nodeId }),
|
|
545
558
|
);
|
|
559
|
+
|
|
560
|
+
if (storedDevice) {
|
|
561
|
+
// We know the device including IPs but want to query anew, so forget the addresses to ensure they get updated
|
|
562
|
+
const device = this.#operationalDeviceRecords.get(deviceMatterQname);
|
|
563
|
+
device?.addresses.clear();
|
|
564
|
+
}
|
|
565
|
+
|
|
546
566
|
const promise = this.#registerWaiterPromise(deviceMatterQname, false, timeout, () =>
|
|
547
567
|
this.#getOperationalDeviceRecords(deviceMatterQname),
|
|
548
568
|
);
|
|
@@ -891,6 +911,13 @@ export class MdnsClient implements Scanner {
|
|
|
891
911
|
answersList.forEach(answers =>
|
|
892
912
|
answers.forEach(answer => {
|
|
893
913
|
const { name, recordType } = answer;
|
|
914
|
+
|
|
915
|
+
// Enforce minimum TTL for records to prevent DoS attacks with very short TTLs
|
|
916
|
+
// But don't modify TTL=0 goodbye packets - those need to be processed for record removal
|
|
917
|
+
if (answer.ttl > 0 && answer.ttl < RECORD_MIN_TTL) {
|
|
918
|
+
answer = { ...answer, ttl: RECORD_MIN_TTL };
|
|
919
|
+
}
|
|
920
|
+
|
|
894
921
|
if (name.endsWith(MATTER_SERVICE_QNAME)) {
|
|
895
922
|
structuredAnswers.operational = structuredAnswers.operational ?? {};
|
|
896
923
|
structuredAnswers.operational[recordType] = structuredAnswers.operational[recordType] ?? [];
|
|
@@ -926,8 +953,32 @@ export class MdnsClient implements Scanner {
|
|
|
926
953
|
return structuredAnswers;
|
|
927
954
|
}
|
|
928
955
|
|
|
956
|
+
/**
|
|
957
|
+
* Merge a record into a map with goodbye protection.
|
|
958
|
+
* Returns true if the record was processed (added or deleted), false if skipped due to protection.
|
|
959
|
+
*/
|
|
960
|
+
#mergeRecordWithGoodbyeProtection(
|
|
961
|
+
targetMap: Map<string, AnyDnsRecordWithExpiry>,
|
|
962
|
+
key: string,
|
|
963
|
+
record: AnyDnsRecordWithExpiry,
|
|
964
|
+
now: number,
|
|
965
|
+
): void {
|
|
966
|
+
const existingRecord = targetMap.get(key);
|
|
967
|
+
if (!existingRecord || existingRecord.discoveredAt < record.discoveredAt) {
|
|
968
|
+
if (record.ttl === 0) {
|
|
969
|
+
// Apply goodbye protection - ignore if the existing record is young
|
|
970
|
+
if (existingRecord && now - existingRecord.discoveredAt < GOODBYE_PROTECTION_WINDOW) {
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
targetMap.delete(key);
|
|
974
|
+
} else {
|
|
975
|
+
targetMap.set(key, record);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
|
|
929
980
|
#combineStructuredAnswers(...answersList: StructuredDnsAnswers[]): StructuredDnsAnswers {
|
|
930
|
-
// Special type for easier combination of answers
|
|
981
|
+
// Special type for an easier combination of answers
|
|
931
982
|
const combinedAnswers: {
|
|
932
983
|
operational?: Record<number, Map<string, AnyDnsRecordWithExpiry>>;
|
|
933
984
|
commissionable?: Record<number, Map<string, AnyDnsRecordWithExpiry>>;
|
|
@@ -935,7 +986,9 @@ export class MdnsClient implements Scanner {
|
|
|
935
986
|
addressesV6?: Record<string, Map<string, AnyDnsRecordWithExpiry>>;
|
|
936
987
|
} = {};
|
|
937
988
|
|
|
989
|
+
const now = Time.nowMs;
|
|
938
990
|
for (const answers of answersList) {
|
|
991
|
+
// Process operational records
|
|
939
992
|
if (answers.operational) {
|
|
940
993
|
combinedAnswers.operational = combinedAnswers.operational ?? {};
|
|
941
994
|
for (const [recordType, records] of Object.entries(answers.operational) as unknown as [
|
|
@@ -943,18 +996,18 @@ export class MdnsClient implements Scanner {
|
|
|
943
996
|
AnyDnsRecordWithExpiry[],
|
|
944
997
|
][]) {
|
|
945
998
|
combinedAnswers.operational[recordType] = combinedAnswers.operational[recordType] ?? new Map();
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
}
|
|
955
|
-
});
|
|
999
|
+
for (const record of records) {
|
|
1000
|
+
this.#mergeRecordWithGoodbyeProtection(
|
|
1001
|
+
combinedAnswers.operational[recordType],
|
|
1002
|
+
record.name,
|
|
1003
|
+
record,
|
|
1004
|
+
now,
|
|
1005
|
+
);
|
|
1006
|
+
}
|
|
956
1007
|
}
|
|
957
1008
|
}
|
|
1009
|
+
|
|
1010
|
+
// Process commissionable records
|
|
958
1011
|
if (answers.commissionable) {
|
|
959
1012
|
combinedAnswers.commissionable = combinedAnswers.commissionable ?? {};
|
|
960
1013
|
for (const [recordType, records] of Object.entries(answers.commissionable) as unknown as [
|
|
@@ -963,18 +1016,18 @@ export class MdnsClient implements Scanner {
|
|
|
963
1016
|
][]) {
|
|
964
1017
|
combinedAnswers.commissionable[recordType] =
|
|
965
1018
|
combinedAnswers.commissionable[recordType] ?? new Map();
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
}
|
|
975
|
-
});
|
|
1019
|
+
for (const record of records) {
|
|
1020
|
+
this.#mergeRecordWithGoodbyeProtection(
|
|
1021
|
+
combinedAnswers.commissionable[recordType],
|
|
1022
|
+
record.name,
|
|
1023
|
+
record,
|
|
1024
|
+
now,
|
|
1025
|
+
);
|
|
1026
|
+
}
|
|
976
1027
|
}
|
|
977
1028
|
}
|
|
1029
|
+
|
|
1030
|
+
// Process IPv6 addresses
|
|
978
1031
|
if (answers.addressesV6) {
|
|
979
1032
|
combinedAnswers.addressesV6 = combinedAnswers.addressesV6 ?? {};
|
|
980
1033
|
for (const [name, records] of Object.entries(answers.addressesV6) as unknown as [
|
|
@@ -982,18 +1035,18 @@ export class MdnsClient implements Scanner {
|
|
|
982
1035
|
Map<string, AnyDnsRecordWithExpiry>,
|
|
983
1036
|
][]) {
|
|
984
1037
|
combinedAnswers.addressesV6[name] = combinedAnswers.addressesV6[name] ?? new Map();
|
|
985
|
-
records.
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
}
|
|
994
|
-
});
|
|
1038
|
+
for (const record of records.values()) {
|
|
1039
|
+
this.#mergeRecordWithGoodbyeProtection(
|
|
1040
|
+
combinedAnswers.addressesV6[name],
|
|
1041
|
+
record.value,
|
|
1042
|
+
record,
|
|
1043
|
+
now,
|
|
1044
|
+
);
|
|
1045
|
+
}
|
|
995
1046
|
}
|
|
996
1047
|
}
|
|
1048
|
+
|
|
1049
|
+
// Process IPv4 addresses
|
|
997
1050
|
if (this.#socket.supportsIpv4 && answers.addressesV4) {
|
|
998
1051
|
combinedAnswers.addressesV4 = combinedAnswers.addressesV4 ?? {};
|
|
999
1052
|
for (const [name, records] of Object.entries(answers.addressesV4) as unknown as [
|
|
@@ -1001,16 +1054,14 @@ export class MdnsClient implements Scanner {
|
|
|
1001
1054
|
Map<string, AnyDnsRecordWithExpiry>,
|
|
1002
1055
|
][]) {
|
|
1003
1056
|
combinedAnswers.addressesV4[name] = combinedAnswers.addressesV4[name] ?? new Map();
|
|
1004
|
-
records.
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
}
|
|
1013
|
-
});
|
|
1057
|
+
for (const record of records.values()) {
|
|
1058
|
+
this.#mergeRecordWithGoodbyeProtection(
|
|
1059
|
+
combinedAnswers.addressesV4[name],
|
|
1060
|
+
record.value,
|
|
1061
|
+
record,
|
|
1062
|
+
now,
|
|
1063
|
+
);
|
|
1064
|
+
}
|
|
1014
1065
|
}
|
|
1015
1066
|
}
|
|
1016
1067
|
}
|
|
@@ -1065,43 +1116,60 @@ export class MdnsClient implements Scanner {
|
|
|
1065
1116
|
}
|
|
1066
1117
|
|
|
1067
1118
|
/**
|
|
1068
|
-
* Update
|
|
1119
|
+
* Update IP address records in a target map with goodbye protection.
|
|
1120
|
+
* Returns true if any records were updated.
|
|
1069
1121
|
*/
|
|
1070
|
-
#
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1122
|
+
#updateIpAddressRecords(
|
|
1123
|
+
sourceAddresses: Record<string, Map<string, AnyDnsRecordWithExpiry>> | undefined,
|
|
1124
|
+
targetAddresses: Record<string, Map<string, AnyDnsRecordWithExpiry>> | undefined,
|
|
1125
|
+
now: number,
|
|
1126
|
+
): boolean {
|
|
1127
|
+
if (!sourceAddresses || !targetAddresses) {
|
|
1128
|
+
return false;
|
|
1074
1129
|
}
|
|
1075
1130
|
let updated = false;
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
if (record.ttl === 0) {
|
|
1081
|
-
interfaceRecords.addressesV6[target].delete(ip);
|
|
1082
|
-
} else {
|
|
1083
|
-
interfaceRecords.addressesV6[target].set(ip, record);
|
|
1084
|
-
}
|
|
1085
|
-
updated = true;
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1131
|
+
for (const [target, ipAddresses] of Object.entries(sourceAddresses)) {
|
|
1132
|
+
const targetMap = targetAddresses[target];
|
|
1133
|
+
if (targetMap === undefined) {
|
|
1134
|
+
continue;
|
|
1088
1135
|
}
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
if (
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
interfaceRecords.addressesV4[target].set(ip, record);
|
|
1136
|
+
for (const [ip, record] of Object.entries(ipAddresses)) {
|
|
1137
|
+
if (record.ttl === 0) {
|
|
1138
|
+
const existingRecord = targetMap.get(ip);
|
|
1139
|
+
if (existingRecord) {
|
|
1140
|
+
const recordAge = now - existingRecord.discoveredAt;
|
|
1141
|
+
if (recordAge < GOODBYE_PROTECTION_WINDOW) {
|
|
1142
|
+
// Record was recently added - ignore goodbye (likely out-of-order packet)
|
|
1143
|
+
continue;
|
|
1098
1144
|
}
|
|
1145
|
+
targetMap.delete(ip);
|
|
1099
1146
|
updated = true;
|
|
1100
1147
|
}
|
|
1148
|
+
} else {
|
|
1149
|
+
targetMap.set(ip, record);
|
|
1150
|
+
updated = true;
|
|
1101
1151
|
}
|
|
1102
1152
|
}
|
|
1103
1153
|
}
|
|
1104
|
-
|
|
1154
|
+
return updated;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
/**
|
|
1158
|
+
* Update the discovered matter relevant IP records with the new data from the DNS message.
|
|
1159
|
+
*/
|
|
1160
|
+
#updateIpRecords(answers: StructuredDnsAnswers, netInterface: string) {
|
|
1161
|
+
const interfaceRecords = this.#discoveredIpRecords.get(netInterface);
|
|
1162
|
+
if (interfaceRecords === undefined) {
|
|
1163
|
+
return;
|
|
1164
|
+
}
|
|
1165
|
+
const now = Time.nowMs;
|
|
1166
|
+
|
|
1167
|
+
const updatedV6 = this.#updateIpAddressRecords(answers.addressesV6, interfaceRecords.addressesV6, now);
|
|
1168
|
+
const updatedV4 =
|
|
1169
|
+
this.#socket.supportsIpv4 &&
|
|
1170
|
+
this.#updateIpAddressRecords(answers.addressesV4, interfaceRecords.addressesV4, now);
|
|
1171
|
+
|
|
1172
|
+
if (updatedV6 || updatedV4) {
|
|
1105
1173
|
this.#discoveredIpRecords.set(netInterface, interfaceRecords);
|
|
1106
1174
|
}
|
|
1107
1175
|
}
|
|
@@ -1197,20 +1265,37 @@ export class MdnsClient implements Scanner {
|
|
|
1197
1265
|
);
|
|
1198
1266
|
}
|
|
1199
1267
|
|
|
1268
|
+
/**
|
|
1269
|
+
* Handle goodbye (TTL=0) for an operational device record with protection against out-of-order packets.
|
|
1270
|
+
* Returns true if the goodbye was processed (record deleted or protected), false if no action needed.
|
|
1271
|
+
*/
|
|
1272
|
+
#handleOperationalDeviceGoodbye(matterName: string, netInterface: string, now: number): boolean {
|
|
1273
|
+
const existingRecord = this.#operationalDeviceRecords.get(matterName);
|
|
1274
|
+
if (!existingRecord) {
|
|
1275
|
+
return false;
|
|
1276
|
+
}
|
|
1277
|
+
const recordAge = now - existingRecord.discoveredAt;
|
|
1278
|
+
if (recordAge < GOODBYE_PROTECTION_WINDOW) {
|
|
1279
|
+
// Record was recently added - ignore goodbye (likely out-of-order packet)
|
|
1280
|
+
return true;
|
|
1281
|
+
}
|
|
1282
|
+
logger.debug(
|
|
1283
|
+
`Removing operational device ${matterName} from cache (interface ${netInterface}) because of ttl=0`,
|
|
1284
|
+
);
|
|
1285
|
+
this.#operationalDeviceRecords.delete(matterName);
|
|
1286
|
+
return true;
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1200
1289
|
#handleOperationalTxtRecord(record: DnsRecord<any>, netInterface: string) {
|
|
1201
1290
|
const { name: matterName, value, ttl } = record as DnsRecord<string[]>;
|
|
1202
|
-
const
|
|
1291
|
+
const now = Time.nowMs;
|
|
1203
1292
|
|
|
1204
|
-
// we got
|
|
1293
|
+
// we got expiry info, so we can remove the record if we know it already and are done
|
|
1205
1294
|
if (ttl === 0) {
|
|
1206
|
-
|
|
1207
|
-
logger.debug(
|
|
1208
|
-
`Removing operational device ${matterName} from cache (interface ${netInterface}) because of ttl=0`,
|
|
1209
|
-
);
|
|
1210
|
-
this.#operationalDeviceRecords.delete(matterName);
|
|
1211
|
-
}
|
|
1295
|
+
this.#handleOperationalDeviceGoodbye(matterName, netInterface, now);
|
|
1212
1296
|
return;
|
|
1213
1297
|
}
|
|
1298
|
+
const discoveredAt = now;
|
|
1214
1299
|
if (!Array.isArray(value)) return;
|
|
1215
1300
|
|
|
1216
1301
|
// Existing records are always updated if relevant, but no new are added if they are not matching the criteria
|
|
@@ -1256,14 +1341,11 @@ export class MdnsClient implements Scanner {
|
|
|
1256
1341
|
value: { target, port },
|
|
1257
1342
|
} = record;
|
|
1258
1343
|
|
|
1344
|
+
const now = Time.nowMs;
|
|
1345
|
+
|
|
1259
1346
|
// We got device expiry info, so we can remove the record if we know it already and are done
|
|
1260
1347
|
if (ttl === 0) {
|
|
1261
|
-
|
|
1262
|
-
logger.debug(
|
|
1263
|
-
`Removing operational device ${matterName} from cache (interface ${netInterface}) because of ttl=0`,
|
|
1264
|
-
);
|
|
1265
|
-
this.#operationalDeviceRecords.delete(matterName);
|
|
1266
|
-
}
|
|
1348
|
+
this.#handleOperationalDeviceGoodbye(matterName, netInterface, now);
|
|
1267
1349
|
return;
|
|
1268
1350
|
}
|
|
1269
1351
|
|
|
@@ -1276,7 +1358,7 @@ export class MdnsClient implements Scanner {
|
|
|
1276
1358
|
return;
|
|
1277
1359
|
}
|
|
1278
1360
|
|
|
1279
|
-
const discoveredAt =
|
|
1361
|
+
const discoveredAt = now;
|
|
1280
1362
|
const device = this.#operationalDeviceRecords.get(matterName) ?? {
|
|
1281
1363
|
deviceIdentifier: matterName,
|
|
1282
1364
|
addresses: new Map<string, MatterServerRecordWithExpire>(),
|
|
@@ -1288,10 +1370,18 @@ export class MdnsClient implements Scanner {
|
|
|
1288
1370
|
if (ips.length > 0) {
|
|
1289
1371
|
for (const { value: ip, ttl } of ips) {
|
|
1290
1372
|
if (ttl === 0) {
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1373
|
+
const existingAddress = addresses.get(ip);
|
|
1374
|
+
if (existingAddress) {
|
|
1375
|
+
const addressAge = now - existingAddress.discoveredAt;
|
|
1376
|
+
if (addressAge < GOODBYE_PROTECTION_WINDOW) {
|
|
1377
|
+
// Address was recently added - ignore goodbye (likely out-of-order packet)
|
|
1378
|
+
continue;
|
|
1379
|
+
}
|
|
1380
|
+
logger.debug(
|
|
1381
|
+
`Removing IP ${ip} for operational device ${matterName} from cache (interface ${netInterface}) because of ttl=0`,
|
|
1382
|
+
);
|
|
1383
|
+
addresses.delete(ip);
|
|
1384
|
+
}
|
|
1295
1385
|
continue;
|
|
1296
1386
|
}
|
|
1297
1387
|
const address = addresses.get(ip) ?? ({ ip, port, type: "udp" } as MatterServerRecordWithExpire);
|
|
@@ -1321,6 +1411,25 @@ export class MdnsClient implements Scanner {
|
|
|
1321
1411
|
return;
|
|
1322
1412
|
}
|
|
1323
1413
|
|
|
1414
|
+
/**
|
|
1415
|
+
* Handle goodbye (TTL=0) for a commissionable device record with protection against out-of-order packets.
|
|
1416
|
+
* Returns true if the goodbye should be skipped (record is protected), false if processed or no record exists.
|
|
1417
|
+
*/
|
|
1418
|
+
#handleCommissionableDeviceGoodbye(name: string, netInterface: string, now: number): boolean {
|
|
1419
|
+
const existingRecord = this.#commissionableDeviceRecords.get(name);
|
|
1420
|
+
if (!existingRecord) {
|
|
1421
|
+
return false;
|
|
1422
|
+
}
|
|
1423
|
+
const recordAge = now - existingRecord.discoveredAt;
|
|
1424
|
+
if (recordAge < GOODBYE_PROTECTION_WINDOW) {
|
|
1425
|
+
// Record was recently added - ignore goodbye (likely out-of-order packet)
|
|
1426
|
+
return true;
|
|
1427
|
+
}
|
|
1428
|
+
logger.debug(`Removing commissionable device ${name} from cache (interface ${netInterface}) because of ttl=0`);
|
|
1429
|
+
this.#commissionableDeviceRecords.delete(name);
|
|
1430
|
+
return false;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1324
1433
|
#handleCommissionableRecords(
|
|
1325
1434
|
answers: StructuredDnsAnswers,
|
|
1326
1435
|
formerAnswers: StructuredDnsAnswers,
|
|
@@ -1341,17 +1450,14 @@ export class MdnsClient implements Scanner {
|
|
|
1341
1450
|
|
|
1342
1451
|
const queryMissingDataForInstances = new Set<string>();
|
|
1343
1452
|
|
|
1453
|
+
const now = Time.nowMs;
|
|
1454
|
+
|
|
1344
1455
|
// First process the TXT records
|
|
1345
1456
|
const txtRecords = commissionableRecords[DnsRecordType.TXT] ?? [];
|
|
1346
1457
|
for (const record of txtRecords) {
|
|
1347
1458
|
const { name, ttl } = record;
|
|
1348
1459
|
if (ttl === 0) {
|
|
1349
|
-
|
|
1350
|
-
logger.debug(
|
|
1351
|
-
`Removing commissionable device ${name} from cache (interface ${netInterface}) because of ttl=0`,
|
|
1352
|
-
);
|
|
1353
|
-
this.#commissionableDeviceRecords.delete(name);
|
|
1354
|
-
}
|
|
1460
|
+
this.#handleCommissionableDeviceGoodbye(name, netInterface, now);
|
|
1355
1461
|
continue;
|
|
1356
1462
|
}
|
|
1357
1463
|
const txtRecord = this.#parseCommissionableTxtRecord(record);
|
|
@@ -1392,10 +1498,8 @@ export class MdnsClient implements Scanner {
|
|
|
1392
1498
|
ttl,
|
|
1393
1499
|
} = record as DnsRecord<SrvRecordValue>;
|
|
1394
1500
|
if (ttl === 0) {
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
);
|
|
1398
|
-
this.#commissionableDeviceRecords.delete(record.name);
|
|
1501
|
+
// Handle goodbye - either deletes or protects the record
|
|
1502
|
+
this.#handleCommissionableDeviceGoodbye(record.name, netInterface, now);
|
|
1399
1503
|
continue;
|
|
1400
1504
|
}
|
|
1401
1505
|
|
|
@@ -1405,15 +1509,23 @@ export class MdnsClient implements Scanner {
|
|
|
1405
1509
|
if (ips.length > 0) {
|
|
1406
1510
|
for (const { value: ip, ttl } of ips) {
|
|
1407
1511
|
if (ttl === 0) {
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1512
|
+
const existingAddress = storedRecord.addresses.get(ip);
|
|
1513
|
+
if (existingAddress) {
|
|
1514
|
+
const addressAge = now - existingAddress.discoveredAt;
|
|
1515
|
+
if (addressAge < GOODBYE_PROTECTION_WINDOW) {
|
|
1516
|
+
// Address was recently added - ignore goodbye (likely out-of-order packet)
|
|
1517
|
+
continue;
|
|
1518
|
+
}
|
|
1519
|
+
logger.debug(
|
|
1520
|
+
`Removing IP ${ip} for commissionable device ${record.name} from cache (interface ${netInterface}) because of ttl=0`,
|
|
1521
|
+
);
|
|
1522
|
+
storedRecord.addresses.delete(ip);
|
|
1523
|
+
}
|
|
1412
1524
|
continue;
|
|
1413
1525
|
}
|
|
1414
1526
|
const matterServer =
|
|
1415
1527
|
storedRecord.addresses.get(ip) ?? ({ ip, port, type: "udp" } as MatterServerRecordWithExpire);
|
|
1416
|
-
matterServer.discoveredAt =
|
|
1528
|
+
matterServer.discoveredAt = now;
|
|
1417
1529
|
matterServer.ttl = ttl;
|
|
1418
1530
|
|
|
1419
1531
|
storedRecord.addresses.set(ip, matterServer);
|
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) {
|