diff --git a/src/core/common/timer.cpp b/src/core/common/timer.cpp index 0c178f245..72b3a80a8 100644 --- a/src/core/common/timer.cpp +++ b/src/core/common/timer.cpp @@ -53,6 +53,14 @@ NextFireTime::NextFireTime(Time aNow) void NextFireTime::UpdateIfEarlier(Time aTime) { mNextTime = Min(mNextTime, Max(mNow, aTime)); } +void NextFireTime::UpdateIfEarlierAndInFuture(Time aTime) +{ + if (aTime > mNow) + { + mNextTime = Min(mNextTime, aTime); + } +} + //--------------------------------------------------------------------------------------------------------------------- // `Timer` diff --git a/src/core/common/timer.hpp b/src/core/common/timer.hpp index fb000f790..907f5ab61 100644 --- a/src/core/common/timer.hpp +++ b/src/core/common/timer.hpp @@ -95,6 +95,21 @@ public: */ void UpdateIfEarlier(Time aTime); + /** + * Updates the tracked next fire time with a new given time, but only if it is earlier than the current + * fire time and in the future relative to `GetNow()`. + * + * If the given @p aTime is not in the future relative to `GetNow()`, it is ignored. This is unlike + * `UpdateIfEarlier()`, which allows all `aTime` values, including ones that are in the past (where it uses + * `GetNow()`). + * + * This method can be used to track the next fire time among non-expired times, ensuring the tracked next fire time + * will be in the future relative to `GetNow()`. + * + * @param[in] aTime The new time. + */ + void UpdateIfEarlierAndInFuture(Time aTime); + /** * Indicates whether or not next fire time is set. * diff --git a/src/core/net/mdns.cpp b/src/core/net/mdns.cpp index 53f7ceb27..69023eee8 100644 --- a/src/core/net/mdns.cpp +++ b/src/core/net/mdns.cpp @@ -303,6 +303,31 @@ exit: void Core::HandleEntryTimer(void) { EntryContext context(GetInstance(), TxMessage::kMulticastResponse); + NextFireTime nextAggrTxTime(context.GetNow()); + + // Determine the next multicast transmission time that is explicitly + // after `GetNow()` to set `mNextAggrTxTime`. This is used for + // response aggregation. As `HandleTimer()` is called on different + // entries, they can decide to extend their answer delay to the + // determined `mNextAggrTxTime` so that all answers are included in + // the same response message. + + for (HostEntry &entry : mHostEntries) + { + entry.DetermineNextAggrTxTime(nextAggrTxTime); + } + + for (ServiceEntry &entry : mServiceEntries) + { + entry.DetermineNextAggrTxTime(nextAggrTxTime); + } + + for (ServiceType &serviceType : mServiceTypes) + { + serviceType.DetermineNextAggrTxTime(nextAggrTxTime); + } + + context.mNextAggrTxTime = nextAggrTxTime.GetNextTime(); // We process host entries before service entries. This order // ensures we can determine whether host addresses have already @@ -613,22 +638,35 @@ void Core::RecordInfo::ScheduleAnswer(const AnswerInfo &aInfo) // that did not receive and cache the previous transmission will // retry its request. - VerifyOrExit(GetDurationSinceLastMulticast(aInfo.mAnswerTime) >= kMinIntervalBetweenMulticast); + VerifyOrExit(GetDurationSinceLastMulticast(aInfo.GetAnswerTime()) >= kMinIntervalBetweenMulticast); } if (mMulticastAnswerPending) { - VerifyOrExit(aInfo.mAnswerTime < mAnswerTime); - } + TimeMilli targetAnswerTime; - mMulticastAnswerPending = true; - mAnswerTime = aInfo.mAnswerTime; + if (mCanExtendAnswerDelay && aInfo.mIsProbe) + { + mCanExtendAnswerDelay = false; + } + + targetAnswerTime = Min(aInfo.GetAnswerTime(), GetAnswerTime()); + mQueryRxTime = Min(aInfo.mQueryRxTime, mQueryRxTime); + mAnswerDelay = targetAnswerTime - mQueryRxTime; + } + else + { + mMulticastAnswerPending = true; + mCanExtendAnswerDelay = !aInfo.mIsProbe; + mQueryRxTime = aInfo.mQueryRxTime; + mAnswerDelay = aInfo.mAnswerDelay; + } exit: return; } -bool Core::RecordInfo::ShouldAppendTo(EntryContext &aContext) const +bool Core::RecordInfo::ShouldAppendTo(EntryContext &aContext) { bool shouldAppend = false; @@ -644,7 +682,20 @@ bool Core::RecordInfo::ShouldAppendTo(EntryContext &aContext) const ExitNow(); } - shouldAppend = mMulticastAnswerPending && (mAnswerTime <= aContext.GetNow()); + if (mMulticastAnswerPending && (GetAnswerTime() <= aContext.GetNow())) + { + // Check if we can delay the answer further so that it can + // be aggregated with other responses scheduled to go out a + // little later. + + if (ExtendAnswerDelay(aContext) == kErrorNone) + { + ExitNow(); + } + + shouldAppend = true; + } + break; case TxMessage::kUnicastResponse: @@ -660,6 +711,36 @@ exit: return shouldAppend; } +Error Core::RecordInfo::ExtendAnswerDelay(EntryContext &aContext) +{ + Error error = kErrorFailed; + + // Extend the answer delay for response aggregation when possible. + // + // This method is called when we have a pending multicast answer + // (`mMulticastAnswerPending`) and the answer time has already + // expired. We first check if the answer can be delayed (e.g., it + // is not allowed for probe responses) and that there is an + // upcoming `mNextAggrTxTime` within a short window of time from + // `GetNow()`, before extending the delay. We ensure that the + // overall answer delay does not exceed + // `kResponseAggregationMaxDelay`. + + VerifyOrExit(mCanExtendAnswerDelay); + + VerifyOrExit(aContext.mNextAggrTxTime != aContext.GetNow().GetDistantFuture()); + VerifyOrExit(aContext.mNextAggrTxTime - aContext.GetNow() < kResponseAggregationMaxDelay); + + VerifyOrExit(aContext.mNextAggrTxTime - mQueryRxTime < kResponseAggregationMaxDelay); + + mAnswerDelay = aContext.mNextAggrTxTime - mQueryRxTime; + + error = kErrorNone; + +exit: + return error; +} + void Core::RecordInfo::UpdateStateAfterAnswer(const TxMessage &aResponse) { // Updates the state after a unicast or multicast response is @@ -720,7 +801,7 @@ void Core::RecordInfo::UpdateFireTimeOn(FireTime &aFireTime) if (mMulticastAnswerPending) { - aFireTime.SetFireTime(mAnswerTime); + aFireTime.SetFireTime(GetAnswerTime()); } if (mIsLastMulticastValid) @@ -748,6 +829,24 @@ exit: return; } +void Core::RecordInfo::DetermineNextAggrTxTime(NextFireTime &aNextAggrTxTime) const +{ + VerifyOrExit(mIsPresent); + + if (mAnnounceCounter < kNumberOfAnnounces) + { + aNextAggrTxTime.UpdateIfEarlierAndInFuture(mAnnounceTime); + } + + if (mMulticastAnswerPending) + { + aNextAggrTxTime.UpdateIfEarlierAndInFuture(GetAnswerTime()); + } + +exit: + return; +} + void Core::RecordInfo::MarkAsAppended(TxMessage &aTxMessage, Section aSection) { mAppendSection = aSection; @@ -1070,18 +1169,27 @@ void Core::Entry::ScheduleNsecAnswer(const AnswerInfo &aInfo) { if (mMulticastNsecPending) { - VerifyOrExit(aInfo.mAnswerTime < mNsecAnswerTime); - } + TimeMilli targetAnswerTime = Min(aInfo.GetAnswerTime(), GetNsecAnswerTime()); - mMulticastNsecPending = true; - mNsecAnswerTime = aInfo.mAnswerTime; + mNsecQueryRxTime = Min(aInfo.mQueryRxTime, mNsecQueryRxTime); + mNsecAnswerDelay = targetAnswerTime - mNsecQueryRxTime; + } + else + { + mMulticastNsecPending = true; + mNsecQueryRxTime = aInfo.mQueryRxTime; + mNsecAnswerDelay = aInfo.mAnswerDelay; + } } exit: return; } -bool Core::Entry::ShouldAnswerNsec(TimeMilli aNow) const { return mMulticastNsecPending && (mNsecAnswerTime <= aNow); } +bool Core::Entry::ShouldAnswerNsec(TimeMilli aNow) const +{ + return mMulticastNsecPending && (GetNsecAnswerTime() <= aNow); +} void Core::Entry::AnswerNonProbe(const AnswerInfo &aInfo, RecordAndType *aRecords, uint16_t aRecordsLength) { @@ -1127,7 +1235,7 @@ void Core::Entry::AnswerProbe(const AnswerInfo &aInfo, RecordAndType *aRecords, TimeMilli now = TimerMilli::GetNow(); AnswerInfo info = aInfo; - info.mAnswerTime = now; + info.mAnswerDelay = 0; OT_ASSERT(info.mIsProbe); @@ -1157,7 +1265,8 @@ void Core::Entry::AnswerProbe(const AnswerInfo &aInfo, RecordAndType *aRecords, } else if (record.GetLastMulticastTime(lastMulticastTime) == kErrorNone) { - info.mAnswerTime = Max(info.mAnswerTime, lastMulticastTime + kMinIntervalProbeResponse); + info.mAnswerDelay = + Max(info.GetAnswerTime(), lastMulticastTime + kMinIntervalProbeResponse) - info.mQueryRxTime; } } } @@ -1175,7 +1284,7 @@ void Core::Entry::AnswerProbe(const AnswerInfo &aInfo, RecordAndType *aRecords, if (!shouldDelay) { - info.mAnswerTime = now; + info.mAnswerDelay = 0; } for (uint16_t index = 0; index < aRecordsLength; index++) @@ -1193,7 +1302,17 @@ void Core::Entry::DetermineNextFireTime(void) if (mMulticastNsecPending) { - SetFireTime(mNsecAnswerTime); + SetFireTime(GetNsecAnswerTime()); + } +} + +void Core::Entry::DetermineNextAggrTxTime(NextFireTime &aNextAggrTxTime) const +{ + mKeyRecord.DetermineNextAggrTxTime(aNextAggrTxTime); + + if (mMulticastNsecPending) + { + aNextAggrTxTime.UpdateIfEarlierAndInFuture(GetNsecAnswerTime()); } } @@ -1233,6 +1352,7 @@ template void Core::Entry::HandleTimer(EntryContext &aConte case kRemoving: ExitNow(); } + thisAsEntryType->DetermineNextFireTime(); exit: @@ -1610,6 +1730,17 @@ exit: return; } +void Core::HostEntry::DetermineNextAggrTxTime(NextFireTime &aNextAggrTxTime) const +{ + VerifyOrExit(GetState() == kRegistered); + + Entry::DetermineNextAggrTxTime(aNextAggrTxTime); + mAddrRecord.DetermineNextAggrTxTime(aNextAggrTxTime); + +exit: + return; +} + void Core::HostEntry::AppendAddressRecordsTo(TxMessage &aTxMessage, Section aSection) { Message *message; @@ -2265,6 +2396,25 @@ exit: return; } +void Core::ServiceEntry::DetermineNextAggrTxTime(NextFireTime &aNextAggrTxTime) const +{ + VerifyOrExit(GetState() == kRegistered); + + Entry::DetermineNextAggrTxTime(aNextAggrTxTime); + + mPtrRecord.DetermineNextAggrTxTime(aNextAggrTxTime); + mSrvRecord.DetermineNextAggrTxTime(aNextAggrTxTime); + mTxtRecord.DetermineNextAggrTxTime(aNextAggrTxTime); + + for (const SubType &subType : mSubTypes) + { + subType.mPtrRecord.DetermineNextAggrTxTime(aNextAggrTxTime); + } + +exit: + return; +} + void Core::ServiceEntry::DiscoverOffsetsAndHost(HostEntry *&aHostEntry) { // Discovers the `HostEntry` associated with this `ServiceEntry` @@ -2824,6 +2974,11 @@ exit: return; } +void Core::ServiceType::DetermineNextAggrTxTime(NextFireTime &aNextAggrTxTime) const +{ + mServicesPtr.DetermineNextAggrTxTime(aNextAggrTxTime); +} + //---------------------------------------------------------------------------------------------------------------------- // Core::TxMessage @@ -3304,6 +3459,7 @@ Core::EntryContext::EntryContext(Instance &aInstance, TxMessage::Type aResponseT : mProbeMessage(aInstance, TxMessage::kMulticastProbe) , mResponseMessage(aInstance, aResponseType) { + mNextAggrTxTime = mNextFireTime.GetNow().GetDistantFuture(); } Core::EntryContext::EntryContext(Instance &aInstance, @@ -3313,6 +3469,7 @@ Core::EntryContext::EntryContext(Instance &aInstance, : mProbeMessage(aInstance, TxMessage::kMulticastProbe) , mResponseMessage(aInstance, aResponseType, aDest, aQueryId) { + mNextAggrTxTime = mNextFireTime.GetNow().GetDistantFuture(); } //---------------------------------------------------------------------------------------------------------------------- @@ -3332,7 +3489,8 @@ Error Core::RxMessage::Init(Instance &aInstance, InstanceLocatorInit::Init(aInstance); - mNext = nullptr; + mNext = nullptr; + mRxTime = TimerMilli::GetNow(); VerifyOrExit(!aMessagePtr.IsNull(), error = kErrorInvalidArgs); @@ -3464,7 +3622,7 @@ Core::RxMessage::ProcessOutcome Core::RxMessage::ProcessQuery(bool aShouldProces bool shouldDelay = false; bool canAnswer = false; bool needUnicastResponse = false; - TimeMilli answerTime; + uint16_t delay = 0; for (Question &question : mQuestions) { @@ -3506,16 +3664,14 @@ Core::RxMessage::ProcessOutcome Core::RxMessage::ProcessQuery(bool aShouldProces ExitNow(); } - answerTime = TimerMilli::GetNow(); - if (shouldDelay) { - answerTime += Random::NonCrypto::GetUint32InRange(kMinResponseDelay, kMaxResponseDelay); + delay = Random::NonCrypto::GetUint32InRange(kMinResponseDelay, kMaxResponseDelay); } for (const Question &question : mQuestions) { - AnswerQuestion(question, answerTime); + AnswerQuestion(question, delay); } if (needUnicastResponse) @@ -3625,7 +3781,7 @@ exit: return; } -void Core::RxMessage::AnswerQuestion(const Question &aQuestion, TimeMilli aAnswerTime) +void Core::RxMessage::AnswerQuestion(const Question &aQuestion, uint16_t aDelay) { HostEntry *hostEntry; ServiceEntry *serviceEntry; @@ -3634,7 +3790,8 @@ void Core::RxMessage::AnswerQuestion(const Question &aQuestion, TimeMilli aAnswe VerifyOrExit(aQuestion.mCanAnswer); answerInfo.mQuestionRrType = aQuestion.mRrType; - answerInfo.mAnswerTime = aAnswerTime; + answerInfo.mAnswerDelay = aDelay; + answerInfo.mQueryRxTime = mRxTime; answerInfo.mIsProbe = aQuestion.mIsProbe; answerInfo.mUnicastResponse = aQuestion.mUnicastResponse; answerInfo.mLegacyUnicastResponse = mIsLegacyUnicast; diff --git a/src/core/net/mdns.hpp b/src/core/net/mdns.hpp index c260c0411..e5f7531b3 100644 --- a/src/core/net/mdns.hpp +++ b/src/core/net/mdns.hpp @@ -719,6 +719,10 @@ private: static constexpr uint32_t kMaxInitialQueryDelay = 120; // msec static constexpr uint32_t kRandomDelayReuseInterval = 2; // msec + static constexpr uint32_t kMinResponseDelay = 20; // msec + static constexpr uint32_t kMaxResponseDelay = 120; // msec + static constexpr uint32_t kResponseAggregationMaxDelay = 500; // msec + static constexpr uint32_t kUnspecifiedTtl = 0; static constexpr uint32_t kDefaultTtl = 120; static constexpr uint32_t kDefaultKeyTtl = kDefaultTtl; @@ -815,8 +819,11 @@ private: struct AnswerInfo { + TimeMilli GetAnswerTime(void) const { return (mQueryRxTime + mAnswerDelay); } + uint16_t mQuestionRrType; - TimeMilli mAnswerTime; + uint16_t mAnswerDelay; + TimeMilli mQueryRxTime; bool mIsProbe; bool mUnicastResponse; bool mLegacyUnicastResponse; @@ -873,11 +880,13 @@ private: void UpdateTtl(uint32_t aTtl); void StartAnnouncing(void); - bool ShouldAppendTo(EntryContext &aContext) const; + bool ShouldAppendTo(EntryContext &aContext); bool CanAnswer(void) const; void ScheduleAnswer(const AnswerInfo &aInfo); + Error ExtendAnswerDelay(EntryContext &aContext); void UpdateStateAfterAnswer(const TxMessage &aResponse); void UpdateFireTimeOn(FireTime &aFireTime); + void DetermineNextAggrTxTime(NextFireTime &aNextAggrTxTime) const; uint32_t GetDurationSinceLastMulticast(TimeMilli aTime) const; Error GetLastMulticastTime(TimeMilli &aLastMulticastTime) const; @@ -901,6 +910,8 @@ private: kAppendedInUnicastMsg, }; + TimeMilli GetAnswerTime(void) const { return mQueryRxTime + mAnswerDelay; } + static constexpr uint32_t kMinIntervalBetweenMulticast = 1000; // msec static constexpr uint32_t kLastMulticastTimeAge = 10 * Time::kOneHourInMsec; @@ -910,12 +921,14 @@ private: bool mMulticastAnswerPending : 1; bool mUnicastAnswerPending : 1; bool mIsLastMulticastValid : 1; + bool mCanExtendAnswerDelay : 1; uint8_t mAnnounceCounter; AppendState mAppendState; Section mAppendSection; + uint16_t mAnswerDelay; uint32_t mTtl; TimeMilli mAnnounceTime; - TimeMilli mAnswerTime; + TimeMilli mQueryRxTime; TimeMilli mLastMulticastTime; }; @@ -978,6 +991,7 @@ private: NameAppender aNameAppender); bool ShouldAnswerNsec(TimeMilli aNow) const; void DetermineNextFireTime(void); + void DetermineNextAggrTxTime(NextFireTime &aNextAggrTxTime) const; void ScheduleTimer(void); void AnswerProbe(const AnswerInfo &aInfo, RecordAndType *aRecords, uint16_t aRecordsLength); void AnswerNonProbe(const AnswerInfo &aInfo, RecordAndType *aRecords, uint16_t aRecordsLength); @@ -988,10 +1002,11 @@ private: RecordInfo mKeyRecord; private: - void SetState(State aState); - void ClearKey(void); - void ScheduleCallbackTask(void); - void CheckMessageSizeLimitToPrepareAgain(TxMessage &aTxMessage, bool &aPrepareAgain); + void SetState(State aState); + void ClearKey(void); + void ScheduleCallbackTask(void); + void CheckMessageSizeLimitToPrepareAgain(TxMessage &aTxMessage, bool &aPrepareAgain); + TimeMilli GetNsecAnswerTime(void) const { return mNsecQueryRxTime + mNsecAnswerDelay; } State mState; uint8_t mProbeCount; @@ -999,7 +1014,8 @@ private: bool mUnicastNsecPending : 1; bool mAppendedNsec : 1; bool mBypassCallbackStateCheck : 1; - TimeMilli mNsecAnswerTime; + uint16_t mNsecAnswerDelay; + TimeMilli mNsecQueryRxTime; Heap::Data mKeyData; Callback mCallback; Callback mKeyCallback; @@ -1033,6 +1049,7 @@ private: void ClearAppendState(void); void PrepareResponse(EntryContext &aContext); void HandleConflict(void); + void DetermineNextAggrTxTime(NextFireTime &aNextAggrTxTime) const; #if OPENTHREAD_CONFIG_MULTICAST_DNS_ENTRY_ITERATION_API_ENABLE Error CopyInfoTo(Host &aHost, EntryState &aState) const; Error CopyInfoTo(Key &aKey, EntryState &aState) const; @@ -1092,6 +1109,7 @@ private: void ClearAppendState(void); void PrepareResponse(EntryContext &aContext); void HandleConflict(void); + void DetermineNextAggrTxTime(NextFireTime &aNextAggrTxTime) const; #if OPENTHREAD_CONFIG_MULTICAST_DNS_ENTRY_ITERATION_API_ENABLE Error CopyInfoTo(Service &aService, EntryState &aState, EntryIterator &aIterator) const; Error CopyInfoTo(Key &aKey, EntryState &aState) const; @@ -1182,6 +1200,7 @@ private: bool ShouldSuppressKnownAnswer(uint32_t aTtl) const; void HandleTimer(EntryContext &aContext); void PrepareResponse(EntryContext &aContext); + void DetermineNextAggrTxTime(NextFireTime &aNextAggrTxTime) const; private: void PrepareResponseRecords(EntryContext &aContext); @@ -1263,6 +1282,7 @@ private: NextFireTime mNextFireTime; TxMessage mProbeMessage; TxMessage mResponseMessage; + TimeMilli mNextAggrTxTime; }; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1317,11 +1337,8 @@ private: bool mIsForAllServicesDnssd : 1; // Is for "_services._dns-sd._udp" (all service types). }; - static constexpr uint32_t kMinResponseDelay = 20; // msec - static constexpr uint32_t kMaxResponseDelay = 120; // msec - void ProcessQuestion(Question &aQuestion); - void AnswerQuestion(const Question &aQuestion, TimeMilli aAnswerTime); + void AnswerQuestion(const Question &aQuestion, uint16_t aDelay); void AnswerServiceTypeQuestion(const Question &aQuestion, const AnswerInfo &aInfo, ServiceEntry &aFirstEntry); bool ShouldSuppressKnownAnswer(const Name &aServiceType, const char *aSubLabel, @@ -1341,6 +1358,7 @@ private: void ProcessARecord(const Name &aName, const ResourceRecord &aRecord, uint16_t aRecordOffset); RxMessage *mNext; + TimeMilli mRxTime; OwnedPtr mMessagePtr; Heap::Array mQuestions; AddressInfo mSenderAddress; diff --git a/tests/unit/test_mdns.cpp b/tests/unit/test_mdns.cpp index 2f1fb562b..eef4bc39c 100644 --- a/tests/unit/test_mdns.cpp +++ b/tests/unit/test_mdns.cpp @@ -4048,6 +4048,158 @@ void TestMultiPacket(void) testFreeInstance(sInstance); } +void TestResponseAggregation(void) +{ + Core *mdns = InitTest(); + Core::Service tcpService; + Core::Service udpService; + const DnsMessage *dnsMsg; + uint16_t heapAllocations; + DnsNameString fullTcpServiceName; + DnsNameString fullTcpServiceType; + DnsNameString fullUdpServiceName; + DnsNameString fullUdpServiceType; + + Log("-------------------------------------------------------------------------------------------"); + Log("TestResponseAggregation"); + + AdvanceTime(1); + + heapAllocations = sHeapAllocatedPtrs.GetLength(); + SuccessOrQuit(mdns->SetEnabled(true, kInfraIfIndex)); + + tcpService.mHostName = "host"; + tcpService.mServiceInstance = "srv1"; + tcpService.mServiceType = "_matter._tcp"; + tcpService.mSubTypeLabels = nullptr; + tcpService.mSubTypeLabelsLength = 0; + tcpService.mTxtData = kTxtData1; + tcpService.mTxtDataLength = sizeof(kTxtData1); + tcpService.mPort = 1111; + tcpService.mPriority = 1; + tcpService.mWeight = 2; + tcpService.mTtl = 4500; + + udpService.mHostName = "host"; + udpService.mServiceInstance = "srv2"; + udpService.mServiceType = "_srv._udp"; + udpService.mSubTypeLabels = nullptr; + udpService.mSubTypeLabelsLength = 0; + udpService.mTxtData = kTxtData2; + udpService.mTxtDataLength = sizeof(kTxtData2); + udpService.mPort = 2222; + udpService.mPriority = 6; + udpService.mWeight = 2; + udpService.mTtl = 4500; + + fullTcpServiceName.Append("%s.%s.local.", tcpService.mServiceInstance, tcpService.mServiceType); + fullTcpServiceType.Append("%s.local.", tcpService.mServiceType); + + fullUdpServiceName.Append("%s.%s.local.", udpService.mServiceInstance, udpService.mServiceType); + fullUdpServiceType.Append("%s.local.", udpService.mServiceType); + + Log("-------------------------------------------------------------------------------------------"); + Log("Register a first `ServiceEntry`, check probes and announcements"); + + sDnsMessages.Clear(); + + sRegCallbacks[0].Reset(); + SuccessOrQuit(mdns->RegisterService(tcpService, 0, HandleSuccessCallback)); + + for (uint8_t probeCount = 0; probeCount < 3; probeCount++) + { + sDnsMessages.Clear(); + + VerifyOrQuit(!sRegCallbacks[0].mWasCalled); + AdvanceTime(250); + + VerifyOrQuit(!sDnsMessages.IsEmpty()); + dnsMsg = sDnsMessages.GetHead(); + dnsMsg->ValidateHeader(kMulticastQuery, /* Q */ 1, /* Ans */ 0, /* Auth */ 2, /* Addnl */ 0); + dnsMsg->ValidateAsProbeFor(tcpService, /* aUnicastRequest */ (probeCount == 0)); + VerifyOrQuit(dnsMsg->GetNext() == nullptr); + } + + for (uint8_t anncCount = 0; anncCount < kNumAnnounces; anncCount++) + { + sDnsMessages.Clear(); + + AdvanceTime((anncCount == 0) ? 250 : (1U << (anncCount - 1)) * 1000); + VerifyOrQuit(sRegCallbacks[0].mWasCalled); + + VerifyOrQuit(!sDnsMessages.IsEmpty()); + dnsMsg = sDnsMessages.GetHead(); + dnsMsg->ValidateHeader(kMulticastResponse, /* Q */ 0, /* Ans */ 4, /* Auth */ 0, /* Addnl */ 1); + dnsMsg->Validate(tcpService, kInAnswerSection, kCheckSrv | kCheckTxt | kCheckPtr | kCheckServicesPtr); + + VerifyOrQuit(dnsMsg->GetNext() == nullptr); + } + + Log("-------------------------------------------------------------------------------------------"); + Log("Register a second `ServiceEntry`, check probes and announcements"); + + sDnsMessages.Clear(); + + sRegCallbacks[0].Reset(); + SuccessOrQuit(mdns->RegisterService(udpService, 0, HandleSuccessCallback)); + + for (uint8_t probeCount = 0; probeCount < 3; probeCount++) + { + sDnsMessages.Clear(); + + VerifyOrQuit(!sRegCallbacks[0].mWasCalled); + AdvanceTime(250); + + VerifyOrQuit(!sDnsMessages.IsEmpty()); + dnsMsg = sDnsMessages.GetHead(); + dnsMsg->ValidateHeader(kMulticastQuery, /* Q */ 1, /* Ans */ 0, /* Auth */ 2, /* Addnl */ 0); + dnsMsg->ValidateAsProbeFor(udpService, /* aUnicastRequest */ (probeCount == 0)); + VerifyOrQuit(dnsMsg->GetNext() == nullptr); + } + + for (uint8_t anncCount = 0; anncCount < kNumAnnounces; anncCount++) + { + sDnsMessages.Clear(); + + AdvanceTime((anncCount == 0) ? 250 : (1U << (anncCount - 1)) * 1000); + VerifyOrQuit(sRegCallbacks[0].mWasCalled); + + VerifyOrQuit(!sDnsMessages.IsEmpty()); + dnsMsg = sDnsMessages.GetHead(); + dnsMsg->ValidateHeader(kMulticastResponse, /* Q */ 0, /* Ans */ 4, /* Auth */ 0, /* Addnl */ 1); + dnsMsg->Validate(udpService, kInAnswerSection, kCheckSrv | kCheckTxt | kCheckPtr | kCheckServicesPtr); + VerifyOrQuit(dnsMsg->GetNext() == nullptr); + } + + Log("-------------------------------------------------------------------------------------------"); + Log("Send two PTR queries back to back and validate the response is aggregated"); + + AdvanceTime(2000); + + sDnsMessages.Clear(); + SendQuery(fullTcpServiceType.AsCString(), ResourceRecord::kTypePtr); + AdvanceTime(5); + SendQuery(fullUdpServiceType.AsCString(), ResourceRecord::kTypePtr); + + AdvanceTime(1000); + + dnsMsg = sDnsMessages.GetHead(); + VerifyOrQuit(dnsMsg != nullptr); + dnsMsg->ValidateHeader(kMulticastResponse, /* Q */ 0, /* Ans */ 2, /* Auth */ 0, /* Addnl */ 4); + dnsMsg->Validate(tcpService, kInAnswerSection, kCheckPtr); + dnsMsg->Validate(tcpService, kInAdditionalSection, kCheckSrv | kCheckTxt); + dnsMsg->Validate(udpService, kInAnswerSection, kCheckPtr); + dnsMsg->Validate(udpService, kInAdditionalSection, kCheckSrv | kCheckTxt); + VerifyOrQuit(dnsMsg->GetNext() == nullptr); + + SuccessOrQuit(mdns->SetEnabled(false, kInfraIfIndex)); + VerifyOrQuit(sHeapAllocatedPtrs.GetLength() <= heapAllocations); + + Log("End of test"); + + testFreeInstance(sInstance); +} + //--------------------------------------------------------------------------------------------------------------------- void TestQuestionUnicastDisallowed(void) @@ -7124,6 +7276,7 @@ int main(void) ot::Dns::Multicast::TestHostOrServiceAndKeyReg(); ot::Dns::Multicast::TestQuery(); ot::Dns::Multicast::TestMultiPacket(); + ot::Dns::Multicast::TestResponseAggregation(); ot::Dns::Multicast::TestQuestionUnicastDisallowed(); ot::Dns::Multicast::TestTxMessageSizeLimit(); ot::Dns::Multicast::TestHostConflict();