diff --git a/src/core/net/mdns.cpp b/src/core/net/mdns.cpp index bd7774bf4..2d0303a4e 100644 --- a/src/core/net/mdns.cpp +++ b/src/core/net/mdns.cpp @@ -4243,15 +4243,18 @@ exit: void Core::RxMessage::ProcessOtherRecord(const Name &aName, const ResourceRecord &aRecord, uint16_t aRecordOffset) { - RecordCache *recordCache; + // Unlike other `Process{Specific}Record()` methods where + // we know for sure that we can have only one match, for + // `RecordQuerier` we may have multiple matches, due to + // the possibility of using `ANY` for record type. - recordCache = Get().mRecordCacheList.FindMatching(aName, aRecord.GetType()); - VerifyOrExit(recordCache != nullptr); - - recordCache->ProcessResponseRecord(*mMessagePtr, aRecord, aRecordOffset); - -exit: - return; + for (RecordCache &recordCache : Get().mRecordCacheList) + { + if (recordCache.Matches(aName, aRecord.GetType())) + { + recordCache.ProcessResponseRecord(*mMessagePtr, aRecord, aRecordOffset); + } + } } //--------------------------------------------------------------------------------------------------------------------- @@ -4911,19 +4914,31 @@ void Core::CacheEntry::SetIsActive(bool aIsActive) // considered "active" when associated with at least one // resolver/browser. "Passive" entries (without a resolver/browser) // continue to process mDNS responses for updates but will not send - // queries. Passive entries are deleted after `kNonActiveDeleteTimeout` - // if no resolver/browser is added. + // queries. Passive entries are deleted after the "delete timeout" + // if no resolver/browser/querier is added. mIsActive = aIsActive; if (!mIsActive) { mQueryPending = false; - mDeleteTime = TimerMilli::GetNow() + kNonActiveDeleteTimeout; + mDeleteTime = TimerMilli::GetNow() + DetermineDeleteTimeout(); SetFireTime(mDeleteTime); } } +uint32_t Core::CacheEntry::DetermineDeleteTimeout(void) const +{ + uint32_t timeout = kNonActiveDeleteTimeout; + + if ((mType == kRecordCache) && (As().mRecordType == ResourceRecord::kTypeAny)) + { + timeout = kNonActiveDeleteTimeoutForAnyRecord; + } + + return timeout; +} + bool Core::CacheEntry::ShouldDelete(TimeMilli aNow) const { return !mIsActive && (mDeleteTime <= aNow); } void Core::CacheEntry::StartInitialQueries(void) @@ -6509,8 +6524,7 @@ Error Core::RecordCache::Init(Instance &aInstance, const RecordQuerier &aQuerier CacheEntry::Init(aInstance, kRecordCache); - mNext = nullptr; - mShouldFlush = false; + mNext = nullptr; SuccessOrExit(error = mFirstLabel.Set(aQuerier.mFirstLabel)); SuccessOrExit(error = mNextLabels.Set(aQuerier.mNextLabels)); mRecordType = aQuerier.mRecordType; @@ -6521,7 +6535,7 @@ exit: bool Core::RecordCache::Matches(const Name &aFullName, uint16_t aRecordType) const { - return (mRecordType == aRecordType) && + return QuestionMatches(mRecordType, aRecordType) && aFullName.Matches(mFirstLabel.AsCString(), mNextLabels.AsCString(), kLocalDomain); } @@ -6529,7 +6543,7 @@ bool Core::RecordCache::Matches(const RecordQuerier &aQuerier) const { bool matches = false; - VerifyOrExit(mRecordType == aQuerier.mRecordType); + VerifyOrExit(aQuerier.mRecordType == mRecordType); VerifyOrExit(NameMatch(mFirstLabel, aQuerier.mFirstLabel)); @@ -6596,7 +6610,7 @@ exit: void Core::RecordCache::UpdateRecordStateAfterQuery(TimeMilli aNow) { - for (RecordDataEntry &entry : mCommittedEntries) + for (RecordEntry &entry : mCommittedEntries) { entry.mRecord.UpdateStateAfterQuery(aNow); } @@ -6613,35 +6627,74 @@ void Core::RecordCache::ProcessResponseRecord(const Message &aMessage, // Once all records are processed `CommitNewResponseEntries()` is // called to update the list. - Heap::Data data; - RecordDataEntry *entry; + Heap::Data data; + NewRecordEntry *entry; - SuccessOrExit(data.SetFrom(aMessage, aRecordOffset + sizeof(ResourceRecord), aRecord.GetLength())); + SuccessOrAssert(data.SetFrom(aMessage, aRecordOffset + sizeof(ResourceRecord), aRecord.GetLength())); - if (aRecord.GetClass() & kClassCacheFlushFlag) + // Check for duplicates in the same response. If there + // are exact duplicates, we remember the last one in the + // response message. + + entry = mNewEntries.FindMatching(aRecord.GetType(), data); + + if (entry != nullptr) { - mShouldFlush = true; + entry->mCacheFlush = (aRecord.GetClass() & kClassCacheFlushFlag); + entry->mTtl = aRecord.GetTtl(); } - - // Check for duplicates in the same response. - - entry = mNewEntries.FindMatching(data); - - if (entry == nullptr) + else { - entry = RecordDataEntry::Allocate(data); + entry = NewRecordEntry::Allocate(aRecord, data); OT_ASSERT(entry != nullptr); + mNewEntries.Push(*entry); } - - entry->mRecord.RefreshTtl(aRecord.GetTtl()); - -exit: - return; } void Core::RecordCache::CommitNewResponseEntries(void) { + // If `RecordQuerier` is used for record type ANY, multiple new + // records with different types may be included in the received + // response. We process and commit all the new records matching + // the same type, together. + + while (!mNewEntries.IsEmpty()) + { + uint16_t recordType = mNewEntries.GetHead()->mType; + + CommitNewEntriesForType(recordType); + } + + mCommittedEntries.RemoveAndFreeAllMatching(EmptyChecker()); + + DetermineNextFireTime(); + ScheduleTimer(); +} + +void Core::RecordCache::CommitNewEntriesForType(uint16_t aRecordType) +{ + bool shouldFlush = false; + OwningList newMatchingEntries; + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Filter and remove all new entries that match `aRecordType`. + + mNewEntries.RemoveAllMatching(newMatchingEntries, aRecordType); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Determine whether we should flush cache for previously + // committed records of `aRecordType`. + + for (const NewRecordEntry &newEntry : newMatchingEntries) + { + if (newEntry.mCacheFlush) + { + shouldFlush = true; + break; + } + } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Invoke callbacks if there is any change. @@ -6649,14 +6702,19 @@ void Core::RecordCache::CommitNewResponseEntries(void) // `mCommittedEntries` that does not appear in the new list // and signal their removal. - if (mShouldFlush) + if (shouldFlush) { - for (RecordDataEntry &exitingEntry : mCommittedEntries) + for (RecordEntry &entry : mCommittedEntries) { - if (!mNewEntries.ContainsMatching(exitingEntry.mData)) + if (!entry.Matches(aRecordType)) { - exitingEntry.mRecord.RefreshTtl(0); - PrepareResultAndInvokeCallbacks(exitingEntry); + continue; + } + + if (!newMatchingEntries.ContainsMatching(entry.mType, entry.mData)) + { + entry.mRecord.RefreshTtl(0); + PrepareResultAndInvokeCallbacks(entry); } } } @@ -6664,18 +6722,18 @@ void Core::RecordCache::CommitNewResponseEntries(void) // Signal addition of any new entries or if there is any // change to an existing entry (TTL value changed). - for (const RecordDataEntry &newEntry : mNewEntries) + for (const NewRecordEntry &newEntry : newMatchingEntries) { - RecordDataEntry *exitingEntry = mCommittedEntries.FindMatching(newEntry.mData); - bool shouldSignal = false; + RecordEntry *entry = mCommittedEntries.FindMatching(newEntry.mType, newEntry.mData); + bool shouldSignal = false; - if (exitingEntry == nullptr) + if (entry == nullptr) { - shouldSignal = (newEntry.GetTtl() > 0); + shouldSignal = (newEntry.mTtl > 0); } else { - shouldSignal = (exitingEntry->GetTtl() != newEntry.GetTtl()); + shouldSignal = (entry->GetTtl() != newEntry.mTtl); } if (shouldSignal) @@ -6687,39 +6745,40 @@ void Core::RecordCache::CommitNewResponseEntries(void) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Now merge the new entries into the `mCommittedEntries` list. - if (mShouldFlush) + if (shouldFlush) { - mCommittedEntries.Clear(); - StopInitialQueries(); - mShouldFlush = false; + mCommittedEntries.RemoveAndFreeAllMatching(aRecordType); + + if (mRecordType != ResourceRecord::kTypeAny) + { + StopInitialQueries(); + } } - while (!mNewEntries.IsEmpty()) + while (!newMatchingEntries.IsEmpty()) { - OwnedPtr newEntry = mNewEntries.Pop(); - RecordDataEntry *entry; + OwnedPtr newEntry = newMatchingEntries.Pop(); + RecordEntry *entry; - entry = mCommittedEntries.FindMatching(newEntry->mData); + entry = mCommittedEntries.FindMatching(newEntry->mType, newEntry->mData); if (entry != nullptr) { - entry->mRecord.RefreshTtl(newEntry->GetTtl()); + entry->mRecord.RefreshTtl(newEntry->mTtl); } else { - mCommittedEntries.Push(*newEntry.Release()); + entry = RecordEntry::Allocate(*newEntry); + OT_ASSERT(entry != nullptr); + + mCommittedEntries.Push(*entry); } } - - mCommittedEntries.RemoveAndFreeAllMatching(EmptyChecker()); - - DetermineNextFireTime(); - ScheduleTimer(); } void Core::RecordCache::DetermineRecordFireTime(void) { - for (RecordDataEntry &entry : mCommittedEntries) + for (RecordEntry &entry : mCommittedEntries) { entry.mRecord.UpdateQueryAndFireTimeOn(*this); } @@ -6727,11 +6786,11 @@ void Core::RecordCache::DetermineRecordFireTime(void) void Core::RecordCache::ProcessExpiredRecords(TimeMilli aNow) { - OwningList expiredEntries; + OwningList expiredEntries; mCommittedEntries.RemoveAllMatching(expiredEntries, ExpireChecker(aNow)); - for (RecordDataEntry &entry : expiredEntries) + for (RecordEntry &entry : expiredEntries) { entry.mRecord.RefreshTtl(0); PrepareResultAndInvokeCallbacks(entry); @@ -6740,32 +6799,45 @@ void Core::RecordCache::ProcessExpiredRecords(TimeMilli aNow) void Core::RecordCache::ReportResultsTo(ResultCallback &aCallback) const { - for (const RecordDataEntry &entry : mCommittedEntries) + for (const RecordEntry &entry : mCommittedEntries) { RecordResult result; - PreareResultFor(entry, result); + PreareResultFor(entry.mType, entry.mData, entry.GetTtl(), result); aCallback.Invoke(GetInstance(), result); } } -void Core::RecordCache::PreareResultFor(const RecordDataEntry &aEntry, RecordResult &aResult) const +void Core::RecordCache::PreareResultFor(uint16_t aType, + const Heap::Data &aData, + uint32_t aTtl, + RecordResult &aResult) const { ClearAllBytes(aResult); aResult.mFirstLabel = mFirstLabel.AsCString(); aResult.mNextLabels = mNextLabels.AsCString(); - aResult.mRecordType = mRecordType; - aResult.mRecordData = aEntry.mData.GetBytes(); - aResult.mRecordDataLength = aEntry.mData.GetLength(); - aResult.mTtl = aEntry.mRecord.GetTtl(); + aResult.mRecordType = aType; + aResult.mRecordData = aData.GetBytes(); + aResult.mRecordDataLength = aData.GetLength(); + aResult.mTtl = aTtl; aResult.mInfraIfIndex = Get().mInfraIfIndex; } -void Core::RecordCache::PrepareResultAndInvokeCallbacks(const RecordDataEntry &aEntry) +void Core::RecordCache::PrepareResultAndInvokeCallbacks(const RecordEntry &aEntry) +{ + PrepareResultAndInvokeCallbacks(aEntry.mType, aEntry.mData, aEntry.GetTtl()); +} + +void Core::RecordCache::PrepareResultAndInvokeCallbacks(const NewRecordEntry &aNewEntry) +{ + PrepareResultAndInvokeCallbacks(aNewEntry.mType, aNewEntry.mData, aNewEntry.mTtl); +} + +void Core::RecordCache::PrepareResultAndInvokeCallbacks(uint16_t aType, const Heap::Data &aData, uint32_t aTtl) { RecordResult result; - PreareResultFor(aEntry, result); + PreareResultFor(aType, aData, aTtl, result); InvokeCallbacks(result); } @@ -6785,20 +6857,48 @@ void Core::RecordCache::CopyInfoTo(RecordQuerier &aQuerier, CacheInfo &aInfo) co #endif //--------------------------------------------------------------------------------------------------------------------- -// Core::RecordCache::RecordDataEntry +// Core::RecordCache::NewRecordEntry -Core::RecordCache::RecordDataEntry::RecordDataEntry(Heap::Data &aData) +Core::RecordCache::NewRecordEntry::NewRecordEntry(const ResourceRecord &aRecord, Heap::Data &aData) : mNext(nullptr) + , mCacheFlush(aRecord.GetClass() & kClassCacheFlushFlag) + , mType(aRecord.GetType()) + , mTtl(aRecord.GetTtl()) , mData(static_cast(aData)) { } -bool Core::RecordCache::RecordDataEntry::Matches(const ExpireChecker &aExpireChecker) const +bool Core::RecordCache::NewRecordEntry::Matches(uint16_t aType) const { return (mType == aType); } + +bool Core::RecordCache::NewRecordEntry::Matches(uint16_t aType, const Heap::Data &aData) const +{ + return (mType == aType) && (aData == mData); +} + +//--------------------------------------------------------------------------------------------------------------------- +// Core::RecordCache::RecordEntry + +Core::RecordCache::RecordEntry::RecordEntry(NewRecordEntry &aNewEntry) + : mNext(nullptr) + , mType(aNewEntry.mType) + , mData(static_cast(aNewEntry.mData)) +{ + mRecord.RefreshTtl(aNewEntry.mTtl); +} + +bool Core::RecordCache::RecordEntry::Matches(uint16_t aType) const { return (mType == aType); } + +bool Core::RecordCache::RecordEntry::Matches(uint16_t aType, const Heap::Data &aData) const +{ + return (mType == aType) && (mData == aData); +} + +bool Core::RecordCache::RecordEntry::Matches(const ExpireChecker &aExpireChecker) const { return mRecord.ShouldExpire(aExpireChecker.mNow); } -bool Core::RecordCache::RecordDataEntry::Matches(EmptyChecker aChecker) const +bool Core::RecordCache::RecordEntry::Matches(EmptyChecker aChecker) const { OT_UNUSED_VARIABLE(aChecker); diff --git a/src/core/net/mdns.hpp b/src/core/net/mdns.hpp index cfd96e635..7c54290b0 100644 --- a/src/core/net/mdns.hpp +++ b/src/core/net/mdns.hpp @@ -1660,16 +1660,18 @@ private: template void InvokeCallbacks(const ResultType &aResult); private: - static constexpr uint32_t kMinIntervalBetweenQueries = 1000; // In msec - static constexpr uint32_t kNonActiveDeleteTimeout = 7 * Time::kOneMinuteInMsec; + static constexpr uint32_t kMinIntervalBetweenQueries = 1000; // In msec + static constexpr uint32_t kNonActiveDeleteTimeout = 7 * Time::kOneMinuteInMsec; + static constexpr uint32_t kNonActiveDeleteTimeoutForAnyRecord = 1 * Time::kOneSecondInMsec; typedef OwningList CallbackList; - void SetIsActive(bool aIsActive); - bool ShouldQuery(TimeMilli aNow); - void PrepareQuery(CacheContext &aContext); - void ProcessExpiredRecords(TimeMilli aNow); - void DetermineNextInitialQueryTime(void); + void SetIsActive(bool aIsActive); + uint32_t DetermineDeleteTimeout(void) const; + bool ShouldQuery(TimeMilli aNow); + void PrepareQuery(CacheContext &aContext); + void ProcessExpiredRecords(TimeMilli aNow); + void DetermineNextInitialQueryTime(void); ResultCallback *FindCallbackMatching(const ResultCallback &aCallback); @@ -1974,17 +1976,34 @@ private: #endif private: - struct RecordDataEntry : public LinkedListEntry, public Heap::Allocatable + struct NewRecordEntry : public LinkedListEntry, public Heap::Allocatable { - explicit RecordDataEntry(Heap::Data &aData); - bool Matches(const Heap::Data &aData) const { return (mData == aData); } + NewRecordEntry(const ResourceRecord &aRecord, Heap::Data &aData); + + bool Matches(uint16_t aType) const; + bool Matches(uint16_t aType, const Heap::Data &aData) const; + + NewRecordEntry *mNext; + bool mCacheFlush; + uint16_t mType; + uint32_t mTtl; + Heap::Data mData; + }; + + struct RecordEntry : public LinkedListEntry, public Heap::Allocatable + { + explicit RecordEntry(NewRecordEntry &aNewEntry); + + bool Matches(uint16_t aType) const; + bool Matches(uint16_t aType, const Heap::Data &aData) const; bool Matches(const ExpireChecker &aExpireChecker) const; bool Matches(EmptyChecker aChecker) const; uint32_t GetTtl(void) const { return mRecord.GetTtl(); } - RecordDataEntry *mNext; - Heap::Data mData; - CacheRecordInfo mRecord; + RecordEntry *mNext; + uint16_t mType; + Heap::Data mData; + CacheRecordInfo mRecord; }; // Called by base class `CacheEntry` @@ -1995,17 +2014,19 @@ private: void ReportResultsTo(ResultCallback &aCallback) const; Error Init(Instance &aInstance, const RecordQuerier &aQuerier); + void CommitNewEntriesForType(uint16_t aRecordType); void AppendNameTo(TxMessage &aTxMessage, Section aSection); - void PreareResultFor(const RecordDataEntry &aEntry, RecordResult &aResult) const; - void PrepareResultAndInvokeCallbacks(const RecordDataEntry &aEntry); + void PreareResultFor(uint16_t aType, const Heap::Data &aData, uint32_t aTtl, RecordResult &aResult) const; + void PrepareResultAndInvokeCallbacks(const NewRecordEntry &aNewEntry); + void PrepareResultAndInvokeCallbacks(const RecordEntry &aEntry); + void PrepareResultAndInvokeCallbacks(uint16_t aType, const Heap::Data &aData, uint32_t aTtl); - RecordCache *mNext; - Heap::String mFirstLabel; - Heap::String mNextLabels; - uint16_t mRecordType; - OwningList mCommittedEntries; - OwningList mNewEntries; - bool mShouldFlush; + RecordCache *mNext; + Heap::String mFirstLabel; + Heap::String mNextLabels; + uint16_t mRecordType; + OwningList mNewEntries; + OwningList mCommittedEntries; }; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/unit/test_mdns.cpp b/tests/unit/test_mdns.cpp index bb3edf809..70702cf78 100644 --- a/tests/unit/test_mdns.cpp +++ b/tests/unit/test_mdns.cpp @@ -1376,16 +1376,14 @@ static void SendHostAddrResponse(const char *aHostName, struct RecordData { + uint16_t mType; const uint8_t *mData; uint16_t mLength; uint32_t mTtl; + bool mCacheFlush; }; -static void SendRecordResponse(const char *aName, - uint16_t aRecordType, - bool aCacheFlush, - uint16_t aNumRecords, - const RecordData *aRecords) +static void SendRecordResponse(const char *aName, uint16_t aNumRecords, const RecordData *aRecords) { Message *message; Header header; @@ -1401,29 +1399,33 @@ static void SendRecordResponse(const char *aName, SuccessOrQuit(message->Append(header)); + Log("Sending response with %u records", aNumRecords); + for (uint16_t index = 0; index < aNumRecords; index++) { + const RecordData &record = aRecords[index]; + SuccessOrQuit(Name::AppendName(aName, *message)); - rr.Init(aRecordType); + rr.Init(record.mType); - if (aCacheFlush) + if (record.mCacheFlush) { rr.SetClass(rr.GetClass() | kClassCacheFlushFlag); } - rr.SetTtl(aRecords[index].mTtl); - rr.SetLength(aRecords[index].mLength); + rr.SetTtl(record.mTtl); + rr.SetLength(record.mLength); SuccessOrQuit(message->Append(rr)); - SuccessOrQuit(message->AppendBytes(aRecords[index].mData, aRecords[index].mLength)); + SuccessOrQuit(message->AppendBytes(record.mData, record.mLength)); + + Log(" Record %u (cache-flush:%u) for %s", record.mType, record.mCacheFlush, aName); } SuccessOrQuit(AsCoreType(&senderAddrInfo.mAddress).FromString(kDeviceIp6Address)); senderAddrInfo.mPort = kMdnsPort; senderAddrInfo.mInfraIfIndex = 0; - Log("Sending record %u response for %s, num-records %u", aRecordType, aName, aNumRecords); - otPlatMdnsHandleReceive(sInstance, message, /* aIsUnicast */ false, &senderAddrInfo); } @@ -6929,12 +6931,14 @@ void TestRecordQuerier(void) Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); Log("Send a response. Validate callback result."); - records[0].mData = kKey1; - records[0].mLength = sizeof(kKey1); - records[0].mTtl = 120; + records[0].mType = ResourceRecord::kTypeKey; + records[0].mData = kKey1; + records[0].mLength = sizeof(kKey1); + records[0].mTtl = 120; + records[0].mCacheFlush = false; sRecordCallbacks.Clear(); - SendRecordResponse("mysrv._srv._udp.local.", ResourceRecord::kTypeKey, /* aCacheFlush */ false, 1, records); + SendRecordResponse("mysrv._srv._udp.local.", 1, records); AdvanceTime(1); @@ -6952,12 +6956,14 @@ void TestRecordQuerier(void) Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); Log("Send a second response (without cache-flush). Validate callback result."); - records[0].mData = kKey2; - records[0].mLength = sizeof(kKey2); - records[0].mTtl = 120; + records[0].mType = ResourceRecord::kTypeKey; + records[0].mData = kKey2; + records[0].mLength = sizeof(kKey2); + records[0].mTtl = 120; + records[0].mCacheFlush = false; sRecordCallbacks.Clear(); - SendRecordResponse("mysrv._srv._udp.local.", ResourceRecord::kTypeKey, /* aCacheFlush */ false, 1, records); + SendRecordResponse("mysrv._srv._udp.local.", 1, records); AdvanceTime(1); @@ -7017,16 +7023,20 @@ void TestRecordQuerier(void) Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); Log("Send a response (without cache-flush) with one previous record and a new record."); - records[0].mData = kKey1; - records[0].mLength = sizeof(kKey1); - records[0].mTtl = 120; + records[0].mType = ResourceRecord::kTypeKey; + records[0].mData = kKey1; + records[0].mLength = sizeof(kKey1); + records[0].mTtl = 120; + records[0].mCacheFlush = false; - records[1].mData = kKey3; - records[1].mLength = sizeof(kKey3); - records[1].mTtl = 120; + records[1].mType = ResourceRecord::kTypeKey; + records[1].mData = kKey3; + records[1].mLength = sizeof(kKey3); + records[1].mTtl = 120; + records[1].mCacheFlush = false; sRecordCallbacks.Clear(); - SendRecordResponse("mysrv._srv._udp.local.", ResourceRecord::kTypeKey, /* aCacheFlush */ false, 2, records); + SendRecordResponse("mysrv._srv._udp.local.", 2, records); AdvanceTime(1); @@ -7046,14 +7056,27 @@ void TestRecordQuerier(void) AdvanceTime(5000); Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); - Log("Send a response (with cache-flush) with only one record, `key3`."); + Log("Send a response with one record, `key3` (cached-flush) and an extra record of different type"); - records[0].mData = kKey3; - records[0].mLength = sizeof(kKey3); - records[0].mTtl = 120; + records[0].mType = ResourceRecord::kTypeKey; + records[0].mData = kKey3; + records[0].mLength = sizeof(kKey3); + records[0].mTtl = 120; + records[0].mCacheFlush = true; + + // The second record is of type TXT, which should be ignored + // because it doesn't match the `RecordQuerier`. We intentionally + // use `kKey2` as record data to validate that the record type + // (TXT) and not just is indeed checked by the mDNS module. + + records[1].mType = ResourceRecord::kTypeTxt; + records[1].mData = kKey2; + records[1].mLength = sizeof(kKey2); + records[1].mTtl = 120; + records[1].mCacheFlush = true; sRecordCallbacks.Clear(); - SendRecordResponse("mysrv._srv._udp.local.", ResourceRecord::kTypeKey, /* aCacheFlush */ true, 1, records); + SendRecordResponse("mysrv._srv._udp.local.", 2, records); AdvanceTime(1); @@ -7082,20 +7105,26 @@ void TestRecordQuerier(void) Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); Log("Send a response removing key3 and other keys."); - records[0].mData = kKey1; - records[0].mLength = sizeof(kKey1); - records[0].mTtl = 0; + records[0].mType = ResourceRecord::kTypeKey; + records[0].mData = kKey1; + records[0].mLength = sizeof(kKey1); + records[0].mTtl = 0; + records[0].mCacheFlush = false; - records[1].mData = kKey2; - records[1].mLength = sizeof(kKey2); - records[1].mTtl = 0; + records[1].mType = ResourceRecord::kTypeKey; + records[1].mData = kKey2; + records[1].mLength = sizeof(kKey2); + records[1].mTtl = 0; + records[1].mCacheFlush = false; - records[2].mData = kKey3; - records[2].mLength = sizeof(kKey3); - records[2].mTtl = 0; + records[2].mType = ResourceRecord::kTypeKey; + records[2].mData = kKey3; + records[2].mLength = sizeof(kKey3); + records[2].mTtl = 0; + records[2].mCacheFlush = false; sRecordCallbacks.Clear(); - SendRecordResponse("mysrv._srv._udp.local.", ResourceRecord::kTypeKey, /* aCacheFlush */ false, 3, records); + SendRecordResponse("mysrv._srv._udp.local.", 3, records); AdvanceTime(1); @@ -7116,16 +7145,20 @@ void TestRecordQuerier(void) Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); Log("Send a response adding two keys"); - records[0].mData = kKey1; - records[0].mLength = sizeof(kKey1); - records[0].mTtl = 500; + records[0].mType = ResourceRecord::kTypeKey; + records[0].mData = kKey1; + records[0].mLength = sizeof(kKey1); + records[0].mTtl = 500; + records[0].mCacheFlush = true; - records[1].mData = kKey2; - records[1].mLength = sizeof(kKey2); - records[1].mTtl = 500; + records[1].mType = ResourceRecord::kTypeKey; + records[1].mData = kKey2; + records[1].mLength = sizeof(kKey2); + records[1].mTtl = 500; + records[1].mCacheFlush = true; sRecordCallbacks.Clear(); - SendRecordResponse("mysrv._srv._udp.local.", ResourceRecord::kTypeKey, /* aCacheFlush */ true, 2, records); + SendRecordResponse("mysrv._srv._udp.local.", 2, records); AdvanceTime(1); @@ -7154,12 +7187,14 @@ void TestRecordQuerier(void) Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); Log("Send a response changing the TTL for key1"); - records[0].mData = kKey1; - records[0].mLength = sizeof(kKey1); - records[0].mTtl = 120; + records[0].mType = ResourceRecord::kTypeKey; + records[0].mData = kKey1; + records[0].mLength = sizeof(kKey1); + records[0].mTtl = 120; + records[0].mCacheFlush = false; sRecordCallbacks.Clear(); - SendRecordResponse("mysrv._srv._udp.local.", ResourceRecord::kTypeKey, /* aCacheFlush */ false, 1, records); + SendRecordResponse("mysrv._srv._udp.local.", 1, records); AdvanceTime(1); @@ -7203,7 +7238,7 @@ void TestRecordQuerier(void) AdvanceTime(10); - SendRecordResponse("mysrv._srv._udp.local.", ResourceRecord::kTypeKey, /* aCacheFlush */ false, 1, records); + SendRecordResponse("mysrv._srv._udp.local.", 1, records); Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); Log("Check queries are sent at 80, 85, 90, 95 percentages of TTL."); @@ -7328,6 +7363,301 @@ void TestRecordQuerier(void) testFreeInstance(sInstance); } +void TestRecordQuerierForAny(void) +{ + static constexpr uint8_t kMaxResponseRecords = 6; + + Core *mdns = InitTest(); + Core::RecordQuerier querier; + Core::RecordQuerier querier2; + Core::Iterator *iterator; + Core::CacheInfo cacheInfo; + const DnsMessage *dnsMsg; + const RecordCallback *recordCallback; + uint16_t heapAllocations; + RecordData records[kMaxResponseRecords]; + + Log("-------------------------------------------------------------------------------------------"); + Log("TestRecordQuerierForAny"); + + AdvanceTime(1); + + heapAllocations = sHeapAllocatedPtrs.GetLength(); + SuccessOrQuit(mdns->SetEnabled(true, kInfraIfIndex)); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + Log("Start a record querier for ANY record type. Validate initial queries."); + + ClearAllBytes(querier); + + querier.mFirstLabel = "mysrv"; + querier.mNextLabels = "_srv._udp"; + querier.mRecordType = ResourceRecord::kTypeAny; + querier.mInfraIfIndex = kInfraIfIndex; + querier.mCallback = HandleRecordResult; + + sDnsMessages.Clear(); + SuccessOrQuit(mdns->StartRecordQuerier(querier)); + + for (uint8_t queryCount = 0; queryCount < kNumInitalQueries; queryCount++) + { + sDnsMessages.Clear(); + + AdvanceTime((queryCount == 0) ? 125 : (1U << (queryCount - 1)) * 1000); + + VerifyOrQuit(!sDnsMessages.IsEmpty()); + dnsMsg = sDnsMessages.GetHead(); + dnsMsg->ValidateHeader(kMulticastQuery, /* Q */ 1, /* Ans */ 0, /* Auth */ 0, /* Addnl */ 0); + dnsMsg->ValidateAsQueryFor(querier); + VerifyOrQuit(dnsMsg->GetNext() == nullptr); + } + + sDnsMessages.Clear(); + + AdvanceTime(20 * 1000); + VerifyOrQuit(sDnsMessages.IsEmpty()); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + Log("Send a response. Validate callback result."); + + records[0].mType = ResourceRecord::kTypeKey; + records[0].mData = kKey1; + records[0].mLength = sizeof(kKey1); + records[0].mTtl = 120; + records[0].mCacheFlush = false; + + sRecordCallbacks.Clear(); + SendRecordResponse("mysrv._srv._udp.local.", 1, records); + + AdvanceTime(1); + + VerifyOrQuit(!sRecordCallbacks.IsEmpty()); + recordCallback = sRecordCallbacks.GetHead(); + VerifyOrQuit(recordCallback->mFirstLabel.Matches("mysrv")); + VerifyOrQuit(recordCallback->mNextLabels.Matches("_srv._udp")); + VerifyOrQuit(recordCallback->mRecordType == ResourceRecord::kTypeKey); + VerifyOrQuit(recordCallback->MatchesData(kKey1)); + VerifyOrQuit(recordCallback->mTtl == 120); + VerifyOrQuit(recordCallback->GetNext() == nullptr); + + VerifyOrQuit(sDnsMessages.IsEmpty()); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + Log("Send a second response for a different record type. Validate callback result."); + + records[0].mType = ResourceRecord::kTypeTxt; + records[0].mData = kKey2; + records[0].mLength = sizeof(kKey2); + records[0].mTtl = 400; + records[0].mCacheFlush = true; + + sRecordCallbacks.Clear(); + SendRecordResponse("mysrv._srv._udp.local.", 1, records); + + AdvanceTime(1); + + VerifyOrQuit(!sRecordCallbacks.IsEmpty()); + recordCallback = sRecordCallbacks.GetHead(); + VerifyOrQuit(recordCallback->mFirstLabel.Matches("mysrv")); + VerifyOrQuit(recordCallback->mNextLabels.Matches("_srv._udp")); + VerifyOrQuit(recordCallback->mRecordType == ResourceRecord::kTypeTxt); + VerifyOrQuit(recordCallback->MatchesData(kKey2)); + VerifyOrQuit(recordCallback->mTtl == 400); + VerifyOrQuit(recordCallback->GetNext() == nullptr); + + VerifyOrQuit(sDnsMessages.IsEmpty()); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + Log("Send a response with multiple record of different types"); + + records[0].mType = ResourceRecord::kTypeKey; + records[0].mData = kKey1; + records[0].mLength = sizeof(kKey1); + records[0].mTtl = 200; + records[0].mCacheFlush = false; + + records[1].mType = ResourceRecord::kTypeTxt; + records[1].mData = kKey2; + records[1].mLength = sizeof(kKey2); + records[1].mTtl = 300; + records[1].mCacheFlush = true; + + records[2].mType = ResourceRecord::kTypeKey; + records[2].mData = kKey2; + records[2].mLength = sizeof(kKey2); + records[2].mTtl = 200; + records[2].mCacheFlush = false; + + records[3].mType = ResourceRecord::kTypeKey; + records[3].mData = kKey3; + records[3].mLength = sizeof(kKey3); + records[3].mTtl = 200; + records[3].mCacheFlush = false; + + sRecordCallbacks.Clear(); + SendRecordResponse("mysrv._srv._udp.local.", 4, records); + + AdvanceTime(1); + + VerifyOrQuit(!sRecordCallbacks.IsEmpty()); + recordCallback = sRecordCallbacks.GetHead(); + + for (uint8_t num = 4; num > 0; num--) + { + VerifyOrQuit(recordCallback != nullptr); + VerifyOrQuit(recordCallback->mFirstLabel.Matches("mysrv")); + VerifyOrQuit(recordCallback->mNextLabels.Matches("_srv._udp")); + + if (recordCallback->mRecordType == ResourceRecord::kTypeTxt) + { + VerifyOrQuit(recordCallback->MatchesData(kKey2)); + VerifyOrQuit(recordCallback->mTtl == 300); + } + else if (recordCallback->mRecordType == ResourceRecord::kTypeKey) + { + VerifyOrQuit(recordCallback->mTtl == 200); + VerifyOrQuit(recordCallback->MatchesData(kKey1) || recordCallback->MatchesData(kKey2) || + recordCallback->MatchesData(kKey3)); + } + else + { + VerifyOrQuit(false); + } + + recordCallback = recordCallback->GetNext(); + } + + VerifyOrQuit(recordCallback == nullptr); + VerifyOrQuit(sDnsMessages.IsEmpty()); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + Log("Start another record querier for the same name with different callback."); + + ClearAllBytes(querier2); + + querier2.mFirstLabel = "mysrv"; + querier2.mNextLabels = "_srv._udp"; + querier2.mRecordType = ResourceRecord::kTypeAny; + querier2.mInfraIfIndex = kInfraIfIndex; + querier2.mCallback = HandleRecordResultAlternate; + + sRecordCallbacks.Clear(); + SuccessOrQuit(mdns->StartRecordQuerier(querier2)); + + AdvanceTime(1); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + Log("Validate callback result from cache for the new querier"); + + VerifyOrQuit(!sRecordCallbacks.IsEmpty()); + recordCallback = sRecordCallbacks.GetHead(); + + for (uint8_t num = 4; num > 0; num--) + { + VerifyOrQuit(recordCallback != nullptr); + VerifyOrQuit(recordCallback->mFirstLabel.Matches("mysrv")); + VerifyOrQuit(recordCallback->mNextLabels.Matches("_srv._udp")); + + if (recordCallback->mRecordType == ResourceRecord::kTypeTxt) + { + VerifyOrQuit(recordCallback->MatchesData(kKey2)); + VerifyOrQuit(recordCallback->mTtl == 300); + } + else if (recordCallback->mRecordType == ResourceRecord::kTypeKey) + { + VerifyOrQuit(recordCallback->mTtl == 200); + VerifyOrQuit(recordCallback->MatchesData(kKey1) || recordCallback->MatchesData(kKey2) || + recordCallback->MatchesData(kKey3)); + } + else + { + VerifyOrQuit(false); + } + + recordCallback = recordCallback->GetNext(); + } + + VerifyOrQuit(recordCallback == nullptr); + + VerifyOrQuit(sDnsMessages.IsEmpty()); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + Log("Stop the second querier."); + + SuccessOrQuit(mdns->StopRecordQuerier(querier2)); + +#if OPENTHREAD_CONFIG_MULTICAST_DNS_ENTRY_ITERATION_API_ENABLE + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + Log("Check the list of `RecordQueier` entries and the cache-info"); + + iterator = mdns->AllocateIterator(); + VerifyOrQuit(iterator != nullptr); + + SuccessOrQuit(mdns->GetNextRecordQuerier(*iterator, querier2, cacheInfo)); + VerifyOrQuit(querier2.mRecordType == ResourceRecord::kTypeAny); + VerifyOrQuit(StringMatch(querier2.mFirstLabel, "mysrv", kStringCaseInsensitiveMatch)); + VerifyOrQuit(StringMatch(querier2.mNextLabels, "_srv._udp", kStringCaseInsensitiveMatch)); + + VerifyOrQuit(cacheInfo.mIsActive); + VerifyOrQuit(cacheInfo.mHasCachedResults); + + VerifyOrQuit(mdns->GetNextRecordQuerier(*iterator, querier2, cacheInfo) == kErrorNotFound); + + mdns->FreeIterator(*iterator); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + Log("Stop the record querier"); + + SuccessOrQuit(mdns->StopRecordQuerier(querier)); + + sDnsMessages.Clear(); + + AdvanceTime(10); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + Log("Check the list of `RecordQueier` entries and cache-info after stop (no longer active)"); + + iterator = mdns->AllocateIterator(); + VerifyOrQuit(iterator != nullptr); + + SuccessOrQuit(mdns->GetNextRecordQuerier(*iterator, querier2, cacheInfo)); + VerifyOrQuit(querier2.mRecordType == ResourceRecord::kTypeAny); + VerifyOrQuit(StringMatch(querier2.mFirstLabel, "mysrv", kStringCaseInsensitiveMatch)); + VerifyOrQuit(StringMatch(querier2.mNextLabels, "_srv._udp", kStringCaseInsensitiveMatch)); + + VerifyOrQuit(!cacheInfo.mIsActive); + VerifyOrQuit(cacheInfo.mHasCachedResults); + + VerifyOrQuit(mdns->GetNextRecordQuerier(*iterator, querier2, cacheInfo) == kErrorNotFound); + + mdns->FreeIterator(*iterator); + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + Log("Check the `RecordQuerier` is correctly removed after 'remove timeout' of 1 minutes"); + + AdvanceTime(1 * 60 * 1000); + VerifyOrQuit(sDnsMessages.IsEmpty()); + + iterator = mdns->AllocateIterator(); + VerifyOrQuit(iterator != nullptr); + + VerifyOrQuit(mdns->GetNextRecordQuerier(*iterator, querier2, cacheInfo) == kErrorNotFound); + + mdns->FreeIterator(*iterator); + +#endif // OPENTHREAD_CONFIG_MULTICAST_DNS_ENTRY_ITERATION_API_ENABLE + + Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + + SuccessOrQuit(mdns->SetEnabled(false, kInfraIfIndex)); + VerifyOrQuit(sHeapAllocatedPtrs.GetLength() <= heapAllocations); + + Log("End of test"); + + testFreeInstance(sInstance); +} + void TestPassiveCache(void) { static const char *const kSubTypes[] = {"_sub1", "_xyzw"}; @@ -7870,6 +8200,7 @@ int main(void) ot::Dns::Multicast::TestTxtResolver(); ot::Dns::Multicast::TestIp6AddrResolver(); ot::Dns::Multicast::TestRecordQuerier(); + ot::Dns::Multicast::TestRecordQuerierForAny(); ot::Dns::Multicast::TestPassiveCache(); ot::Dns::Multicast::TestLegacyUnicastResponse();