1 /*
2  *  Copyright (c) 2024, The OpenThread Authors.
3  *  All rights reserved.
4  *
5  *  Redistribution and use in source and binary forms, with or without
6  *  modification, are permitted provided that the following conditions are met:
7  *  1. Redistributions of source code must retain the above copyright
8  *     notice, this list of conditions and the following disclaimer.
9  *  2. Redistributions in binary form must reproduce the above copyright
10  *     notice, this list of conditions and the following disclaimer in the
11  *     documentation and/or other materials provided with the distribution.
12  *  3. Neither the name of the copyright holder nor the
13  *     names of its contributors may be used to endorse or promote products
14  *     derived from this software without specific prior written permission.
15  *
16  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20  *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  *  POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 #include "mdns.hpp"
30 
31 #if OPENTHREAD_CONFIG_MULTICAST_DNS_ENABLE
32 
33 #include "common/code_utils.hpp"
34 #include "common/locator_getters.hpp"
35 #include "common/log.hpp"
36 #include "common/numeric_limits.hpp"
37 #include "common/type_traits.hpp"
38 #include "instance/instance.hpp"
39 
40 /**
41  * @file
42  *   This file implements the Multicast DNS (mDNS) per RFC 6762.
43  */
44 
45 namespace ot {
46 namespace Dns {
47 namespace Multicast {
48 
49 RegisterLogModule("MulticastDns");
50 
51 //---------------------------------------------------------------------------------------------------------------------
52 // otPlatMdns callbacks
53 
otPlatMdnsHandleReceive(otInstance * aInstance,otMessage * aMessage,bool aIsUnicast,const otPlatMdnsAddressInfo * aAddress)54 extern "C" void otPlatMdnsHandleReceive(otInstance                  *aInstance,
55                                         otMessage                   *aMessage,
56                                         bool                         aIsUnicast,
57                                         const otPlatMdnsAddressInfo *aAddress)
58 {
59     AsCoreType(aInstance).Get<Core>().HandleMessage(AsCoreType(aMessage), aIsUnicast, AsCoreType(aAddress));
60 }
61 
62 //----------------------------------------------------------------------------------------------------------------------
63 // Core
64 
65 const char Core::kLocalDomain[]         = "local.";
66 const char Core::kUdpServiceLabel[]     = "_udp";
67 const char Core::kTcpServiceLabel[]     = "_tcp";
68 const char Core::kSubServiceLabel[]     = "_sub";
69 const char Core::kServicesDnssdLabels[] = "_services._dns-sd._udp";
70 
Core(Instance & aInstance)71 Core::Core(Instance &aInstance)
72     : InstanceLocator(aInstance)
73     , mIsEnabled(false)
74     , mIsQuestionUnicastAllowed(kDefaultQuAllowed)
75     , mMaxMessageSize(kMaxMessageSize)
76     , mInfraIfIndex(0)
77     , mMultiPacketRxMessages(aInstance)
78     , mNextProbeTxTime(TimerMilli::GetNow() - 1)
79     , mEntryTimer(aInstance)
80     , mEntryTask(aInstance)
81     , mTxMessageHistory(aInstance)
82     , mConflictCallback(nullptr)
83     , mNextQueryTxTime(TimerMilli::GetNow() - 1)
84     , mCacheTimer(aInstance)
85     , mCacheTask(aInstance)
86 {
87 }
88 
SetEnabled(bool aEnable,uint32_t aInfraIfIndex)89 Error Core::SetEnabled(bool aEnable, uint32_t aInfraIfIndex)
90 {
91     Error error = kErrorNone;
92 
93     VerifyOrExit(aEnable != mIsEnabled, error = kErrorAlready);
94     SuccessOrExit(error = otPlatMdnsSetListeningEnabled(&GetInstance(), aEnable, aInfraIfIndex));
95 
96     mIsEnabled    = aEnable;
97     mInfraIfIndex = aInfraIfIndex;
98 
99     if (mIsEnabled)
100     {
101         LogInfo("Enabling on infra-if-index %lu", ToUlong(mInfraIfIndex));
102     }
103     else
104     {
105         LogInfo("Disabling");
106     }
107 
108     if (!mIsEnabled)
109     {
110         mHostEntries.Clear();
111         mServiceEntries.Clear();
112         mServiceTypes.Clear();
113         mMultiPacketRxMessages.Clear();
114         mTxMessageHistory.Clear();
115         mEntryTimer.Stop();
116 
117         mBrowseCacheList.Clear();
118         mSrvCacheList.Clear();
119         mTxtCacheList.Clear();
120         mIp6AddrCacheList.Clear();
121         mIp4AddrCacheList.Clear();
122         mCacheTimer.Stop();
123     }
124 
125     Get<Dnssd>().HandleMdnsCoreStateChange();
126 
127 exit:
128     return error;
129 }
130 
131 #if OPENTHREAD_CONFIG_MULTICAST_DNS_AUTO_ENABLE_ON_INFRA_IF
HandleInfraIfStateChanged(void)132 void Core::HandleInfraIfStateChanged(void)
133 {
134     IgnoreError(SetEnabled(Get<BorderRouter::InfraIf>().IsRunning(), Get<BorderRouter::InfraIf>().GetIfIndex()));
135 }
136 #endif
137 
138 template <typename EntryType, typename ItemInfo>
Register(const ItemInfo & aItemInfo,RequestId aRequestId,RegisterCallback aCallback)139 Error Core::Register(const ItemInfo &aItemInfo, RequestId aRequestId, RegisterCallback aCallback)
140 {
141     Error      error = kErrorNone;
142     EntryType *entry;
143 
144     VerifyOrExit(mIsEnabled, error = kErrorInvalidState);
145 
146     entry = GetEntryList<EntryType>().FindMatching(aItemInfo);
147 
148     if (entry == nullptr)
149     {
150         entry = EntryType::AllocateAndInit(GetInstance(), aItemInfo);
151         OT_ASSERT(entry != nullptr);
152         GetEntryList<EntryType>().Push(*entry);
153     }
154 
155     entry->Register(aItemInfo, Callback(aRequestId, aCallback));
156 
157 exit:
158     return error;
159 }
160 
Unregister(const ItemInfo & aItemInfo)161 template <typename EntryType, typename ItemInfo> Error Core::Unregister(const ItemInfo &aItemInfo)
162 {
163     Error      error = kErrorNone;
164     EntryType *entry;
165 
166     VerifyOrExit(mIsEnabled, error = kErrorInvalidState);
167 
168     entry = GetEntryList<EntryType>().FindMatching(aItemInfo);
169 
170     if (entry != nullptr)
171     {
172         entry->Unregister(aItemInfo);
173     }
174 
175 exit:
176     return error;
177 }
178 
RegisterHost(const Host & aHost,RequestId aRequestId,RegisterCallback aCallback)179 Error Core::RegisterHost(const Host &aHost, RequestId aRequestId, RegisterCallback aCallback)
180 {
181     return Register<HostEntry>(aHost, aRequestId, aCallback);
182 }
183 
UnregisterHost(const Host & aHost)184 Error Core::UnregisterHost(const Host &aHost) { return Unregister<HostEntry>(aHost); }
185 
RegisterService(const Service & aService,RequestId aRequestId,RegisterCallback aCallback)186 Error Core::RegisterService(const Service &aService, RequestId aRequestId, RegisterCallback aCallback)
187 {
188     return Register<ServiceEntry>(aService, aRequestId, aCallback);
189 }
190 
UnregisterService(const Service & aService)191 Error Core::UnregisterService(const Service &aService) { return Unregister<ServiceEntry>(aService); }
192 
RegisterKey(const Key & aKey,RequestId aRequestId,RegisterCallback aCallback)193 Error Core::RegisterKey(const Key &aKey, RequestId aRequestId, RegisterCallback aCallback)
194 {
195     return IsKeyForService(aKey) ? Register<ServiceEntry>(aKey, aRequestId, aCallback)
196                                  : Register<HostEntry>(aKey, aRequestId, aCallback);
197 }
198 
UnregisterKey(const Key & aKey)199 Error Core::UnregisterKey(const Key &aKey)
200 {
201     return IsKeyForService(aKey) ? Unregister<ServiceEntry>(aKey) : Unregister<HostEntry>(aKey);
202 }
203 
204 #if OPENTHREAD_CONFIG_MULTICAST_DNS_ENTRY_ITERATION_API_ENABLE
205 
AllocateIterator(void)206 Core::Iterator *Core::AllocateIterator(void) { return EntryIterator::Allocate(GetInstance()); }
207 
FreeIterator(Iterator & aIterator)208 void Core::FreeIterator(Iterator &aIterator) { static_cast<EntryIterator &>(aIterator).Free(); }
209 
GetNextHost(Iterator & aIterator,Host & aHost,EntryState & aState) const210 Error Core::GetNextHost(Iterator &aIterator, Host &aHost, EntryState &aState) const
211 {
212     return static_cast<EntryIterator &>(aIterator).GetNextHost(aHost, aState);
213 }
214 
GetNextService(Iterator & aIterator,Service & aService,EntryState & aState) const215 Error Core::GetNextService(Iterator &aIterator, Service &aService, EntryState &aState) const
216 {
217     return static_cast<EntryIterator &>(aIterator).GetNextService(aService, aState);
218 }
219 
GetNextKey(Iterator & aIterator,Key & aKey,EntryState & aState) const220 Error Core::GetNextKey(Iterator &aIterator, Key &aKey, EntryState &aState) const
221 {
222     return static_cast<EntryIterator &>(aIterator).GetNextKey(aKey, aState);
223 }
224 
GetNextBrowser(Iterator & aIterator,Browser & aBrowser,CacheInfo & aInfo) const225 Error Core::GetNextBrowser(Iterator &aIterator, Browser &aBrowser, CacheInfo &aInfo) const
226 {
227     return static_cast<EntryIterator &>(aIterator).GetNextBrowser(aBrowser, aInfo);
228 }
229 
GetNextSrvResolver(Iterator & aIterator,SrvResolver & aResolver,CacheInfo & aInfo) const230 Error Core::GetNextSrvResolver(Iterator &aIterator, SrvResolver &aResolver, CacheInfo &aInfo) const
231 {
232     return static_cast<EntryIterator &>(aIterator).GetNextSrvResolver(aResolver, aInfo);
233 }
234 
GetNextTxtResolver(Iterator & aIterator,TxtResolver & aResolver,CacheInfo & aInfo) const235 Error Core::GetNextTxtResolver(Iterator &aIterator, TxtResolver &aResolver, CacheInfo &aInfo) const
236 {
237     return static_cast<EntryIterator &>(aIterator).GetNextTxtResolver(aResolver, aInfo);
238 }
239 
GetNextIp6AddressResolver(Iterator & aIterator,AddressResolver & aResolver,CacheInfo & aInfo) const240 Error Core::GetNextIp6AddressResolver(Iterator &aIterator, AddressResolver &aResolver, CacheInfo &aInfo) const
241 {
242     return static_cast<EntryIterator &>(aIterator).GetNextIp6AddressResolver(aResolver, aInfo);
243 }
244 
GetNextIp4AddressResolver(Iterator & aIterator,AddressResolver & aResolver,CacheInfo & aInfo) const245 Error Core::GetNextIp4AddressResolver(Iterator &aIterator, AddressResolver &aResolver, CacheInfo &aInfo) const
246 {
247     return static_cast<EntryIterator &>(aIterator).GetNextIp4AddressResolver(aResolver, aInfo);
248 }
249 
250 #endif // OPENTHREAD_CONFIG_MULTICAST_DNS_ENTRY_ITERATION_API_ENABLE
251 
InvokeConflictCallback(const char * aName,const char * aServiceType)252 void Core::InvokeConflictCallback(const char *aName, const char *aServiceType)
253 {
254     if (mConflictCallback != nullptr)
255     {
256         mConflictCallback(&GetInstance(), aName, aServiceType);
257     }
258 }
HandleMessage(Message & aMessage,bool aIsUnicast,const AddressInfo & aSenderAddress)259 void Core::HandleMessage(Message &aMessage, bool aIsUnicast, const AddressInfo &aSenderAddress)
260 {
261     OwnedPtr<Message>   messagePtr(&aMessage);
262     OwnedPtr<RxMessage> rxMessagePtr;
263 
264     VerifyOrExit(mIsEnabled);
265 
266     rxMessagePtr.Reset(RxMessage::AllocateAndInit(GetInstance(), messagePtr, aIsUnicast, aSenderAddress));
267     VerifyOrExit(!rxMessagePtr.IsNull());
268 
269     if (rxMessagePtr->IsQuery())
270     {
271         // Check if this is a continuation of a multi-packet query.
272         // Initial query message sets the "Truncated" flag.
273         // Subsequent messages from the same sender contain no
274         // question and only known-answer records.
275 
276         if ((rxMessagePtr->GetRecordCounts().GetFor(kQuestionSection) == 0) &&
277             (rxMessagePtr->GetRecordCounts().GetFor(kAnswerSection) > 0))
278         {
279             mMultiPacketRxMessages.AddToExisting(rxMessagePtr);
280             ExitNow();
281         }
282 
283         switch (rxMessagePtr->ProcessQuery(/* aShouldProcessTruncated */ false))
284         {
285         case RxMessage::kProcessed:
286             break;
287 
288         case RxMessage::kSaveAsMultiPacket:
289             // This is a truncated multi-packet query and we can
290             // answer some questions in this query. We save it in
291             // `mMultiPacketRxMessages` list and defer its response
292             // for a random time waiting to receive next messages
293             // containing additional known-answer records.
294 
295             mMultiPacketRxMessages.AddNew(rxMessagePtr);
296             break;
297         }
298     }
299     else
300     {
301         rxMessagePtr->ProcessResponse();
302     }
303 
304 exit:
305     return;
306 }
307 
HandleEntryTimer(void)308 void Core::HandleEntryTimer(void)
309 {
310     EntryTimerContext context(GetInstance());
311 
312     // We process host entries before service entries. This order
313     // ensures we can determine whether host addresses have already
314     // been appended to the Answer section (when processing service entries),
315     // preventing duplicates.
316 
317     for (HostEntry &entry : mHostEntries)
318     {
319         entry.HandleTimer(context);
320     }
321 
322     for (ServiceEntry &entry : mServiceEntries)
323     {
324         entry.HandleTimer(context);
325     }
326 
327     for (ServiceType &serviceType : mServiceTypes)
328     {
329         serviceType.HandleTimer(context);
330     }
331 
332     context.GetProbeMessage().Send();
333     context.GetResponseMessage().Send();
334 
335     RemoveEmptyEntries();
336 
337     mEntryTimer.FireAtIfEarlier(context.GetNextFireTime());
338 }
339 
RemoveEmptyEntries(void)340 void Core::RemoveEmptyEntries(void)
341 {
342     mHostEntries.RemoveAndFreeAllMatching(Entry::kRemoving);
343     mServiceEntries.RemoveAndFreeAllMatching(Entry::kRemoving);
344 }
345 
HandleEntryTask(void)346 void Core::HandleEntryTask(void)
347 {
348     // `mEntryTask` serves two purposes:
349     //
350     // Invoking callbacks: This ensures `Register()` calls will always
351     // return before invoking the callback, even when entry is
352     // already in `kRegistered` state and registration is immediately
353     // successful.
354     //
355     // Removing empty entries after `Unregister()` calls: This
356     // prevents modification of `mHostEntries` and `mServiceEntries`
357     // during callback execution while we are iterating over these
358     // lists. Allows us to safely call `Register()` or `Unregister()`
359     // from callbacks without iterator invalidation.
360 
361     for (HostEntry &entry : mHostEntries)
362     {
363         entry.InvokeCallbacks();
364     }
365 
366     for (ServiceEntry &entry : mServiceEntries)
367     {
368         entry.InvokeCallbacks();
369     }
370 
371     RemoveEmptyEntries();
372 }
373 
DetermineTtl(uint32_t aTtl,uint32_t aDefaultTtl)374 uint32_t Core::DetermineTtl(uint32_t aTtl, uint32_t aDefaultTtl)
375 {
376     return (aTtl == kUnspecifiedTtl) ? aDefaultTtl : aTtl;
377 }
378 
NameMatch(const Heap::String & aHeapString,const char * aName)379 bool Core::NameMatch(const Heap::String &aHeapString, const char *aName)
380 {
381     // Compares a DNS name given as a `Heap::String` with a
382     // `aName` C string.
383 
384     return !aHeapString.IsNull() && StringMatch(aHeapString.AsCString(), aName, kStringCaseInsensitiveMatch);
385 }
386 
NameMatch(const Heap::String & aFirst,const Heap::String & aSecond)387 bool Core::NameMatch(const Heap::String &aFirst, const Heap::String &aSecond)
388 {
389     // Compares two DNS names given as `Heap::String`.
390 
391     return !aSecond.IsNull() && NameMatch(aFirst, aSecond.AsCString());
392 }
393 
UpdateCacheFlushFlagIn(ResourceRecord & aResourceRecord,Section aSection)394 void Core::UpdateCacheFlushFlagIn(ResourceRecord &aResourceRecord, Section aSection)
395 {
396     // Do not set the cache-flush flag is the record is
397     // appended in Authority Section in a probe message.
398 
399     if (aSection != kAuthoritySection)
400     {
401         aResourceRecord.SetClass(aResourceRecord.GetClass() | kClassCacheFlushFlag);
402     }
403 }
404 
UpdateRecordLengthInMessage(ResourceRecord & aRecord,Message & aMessage,uint16_t aOffset)405 void Core::UpdateRecordLengthInMessage(ResourceRecord &aRecord, Message &aMessage, uint16_t aOffset)
406 {
407     // Determines the records DATA length and updates it in a message.
408     // Should be called immediately after all the fields in the
409     // record are appended to the message. `aOffset` gives the offset
410     // in the message to the start of the record.
411 
412     aRecord.SetLength(aMessage.GetLength() - aOffset - sizeof(ResourceRecord));
413     aMessage.Write(aOffset, aRecord);
414 }
415 
UpdateCompressOffset(uint16_t & aOffset,uint16_t aNewOffset)416 void Core::UpdateCompressOffset(uint16_t &aOffset, uint16_t aNewOffset)
417 {
418     if ((aOffset == kUnspecifiedOffset) && (aNewOffset != kUnspecifiedOffset))
419     {
420         aOffset = aNewOffset;
421     }
422 }
423 
QuestionMatches(uint16_t aQuestionRrType,uint16_t aRrType)424 bool Core::QuestionMatches(uint16_t aQuestionRrType, uint16_t aRrType)
425 {
426     return (aQuestionRrType == aRrType) || (aQuestionRrType == ResourceRecord::kTypeAny);
427 }
428 
RrClassIsInternetOrAny(uint16_t aRrClass)429 bool Core::RrClassIsInternetOrAny(uint16_t aRrClass)
430 {
431     aRrClass &= kClassMask;
432 
433     return (aRrClass == ResourceRecord::kClassInternet) || (aRrClass == ResourceRecord::kClassAny);
434 }
435 
436 //----------------------------------------------------------------------------------------------------------------------
437 // Core::Callback
438 
Callback(RequestId aRequestId,RegisterCallback aCallback)439 Core::Callback::Callback(RequestId aRequestId, RegisterCallback aCallback)
440     : mRequestId(aRequestId)
441     , mCallback(aCallback)
442 {
443 }
444 
InvokeAndClear(Instance & aInstance,Error aError)445 void Core::Callback::InvokeAndClear(Instance &aInstance, Error aError)
446 {
447     if (mCallback != nullptr)
448     {
449         RegisterCallback callback  = mCallback;
450         RequestId        requestId = mRequestId;
451 
452         Clear();
453 
454         callback(&aInstance, requestId, aError);
455     }
456 }
457 
458 //----------------------------------------------------------------------------------------------------------------------
459 // Core::RecordCounts
460 
ReadFrom(const Header & aHeader)461 void Core::RecordCounts::ReadFrom(const Header &aHeader)
462 {
463     mCounts[kQuestionSection]       = aHeader.GetQuestionCount();
464     mCounts[kAnswerSection]         = aHeader.GetAnswerCount();
465     mCounts[kAuthoritySection]      = aHeader.GetAuthorityRecordCount();
466     mCounts[kAdditionalDataSection] = aHeader.GetAdditionalRecordCount();
467 }
468 
WriteTo(Header & aHeader) const469 void Core::RecordCounts::WriteTo(Header &aHeader) const
470 {
471     aHeader.SetQuestionCount(mCounts[kQuestionSection]);
472     aHeader.SetAnswerCount(mCounts[kAnswerSection]);
473     aHeader.SetAuthorityRecordCount(mCounts[kAuthoritySection]);
474     aHeader.SetAdditionalRecordCount(mCounts[kAdditionalDataSection]);
475 }
476 
IsEmpty(void) const477 bool Core::RecordCounts::IsEmpty(void) const
478 {
479     // Indicates whether or not all counts are zero.
480 
481     bool isEmpty = true;
482 
483     for (uint16_t count : mCounts)
484     {
485         if (count != 0)
486         {
487             isEmpty = false;
488             break;
489         }
490     }
491 
492     return isEmpty;
493 }
494 
495 //----------------------------------------------------------------------------------------------------------------------
496 // Core::AddressArray
497 
Matches(const Ip6::Address * aAddresses,uint16_t aNumAddresses) const498 bool Core::AddressArray::Matches(const Ip6::Address *aAddresses, uint16_t aNumAddresses) const
499 {
500     bool matches = false;
501 
502     VerifyOrExit(aNumAddresses == GetLength());
503 
504     for (uint16_t i = 0; i < aNumAddresses; i++)
505     {
506         VerifyOrExit(Contains(aAddresses[i]));
507     }
508 
509     matches = true;
510 
511 exit:
512     return matches;
513 }
514 
SetFrom(const Ip6::Address * aAddresses,uint16_t aNumAddresses)515 void Core::AddressArray::SetFrom(const Ip6::Address *aAddresses, uint16_t aNumAddresses)
516 {
517     Free();
518     SuccessOrAssert(ReserveCapacity(aNumAddresses));
519 
520     for (uint16_t i = 0; i < aNumAddresses; i++)
521     {
522         IgnoreError(PushBack(aAddresses[i]));
523     }
524 }
525 
526 //----------------------------------------------------------------------------------------------------------------------
527 // Core::RecordInfo
528 
UpdateProperty(UintType & aProperty,UintType aValue)529 template <typename UintType> void Core::RecordInfo::UpdateProperty(UintType &aProperty, UintType aValue)
530 {
531     // Updates a property variable associated with this record. The
532     // `aProperty` is updated if the record is empty (has no value
533     // yet) or if its current value differs from the new `aValue`. If
534     // the property is changed, we prepare the record to be announced.
535 
536     // This template version works with `UintType` properties. There
537     // are similar overloads for `Heap::Data` and `Heap::String` and
538     // `AddressArray` property types below.
539 
540     static_assert(TypeTraits::IsSame<UintType, uint8_t>::kValue || TypeTraits::IsSame<UintType, uint16_t>::kValue ||
541                       TypeTraits::IsSame<UintType, uint32_t>::kValue || TypeTraits::IsSame<UintType, uint64_t>::kValue,
542                   "UintType must be `uint8_t`, `uint16_t`, `uint32_t`, or `uint64_t`");
543 
544     if (!mIsPresent || (aProperty != aValue))
545     {
546         mIsPresent = true;
547         aProperty  = aValue;
548         StartAnnouncing();
549     }
550 }
551 
UpdateProperty(Heap::String & aStringProperty,const char * aString)552 void Core::RecordInfo::UpdateProperty(Heap::String &aStringProperty, const char *aString)
553 {
554     if (!mIsPresent || !NameMatch(aStringProperty, aString))
555     {
556         mIsPresent = true;
557         SuccessOrAssert(aStringProperty.Set(aString));
558         StartAnnouncing();
559     }
560 }
561 
UpdateProperty(Heap::Data & aDataProperty,const uint8_t * aData,uint16_t aLength)562 void Core::RecordInfo::UpdateProperty(Heap::Data &aDataProperty, const uint8_t *aData, uint16_t aLength)
563 {
564     if (!mIsPresent || !aDataProperty.Matches(aData, aLength))
565     {
566         mIsPresent = true;
567         SuccessOrAssert(aDataProperty.SetFrom(aData, aLength));
568         StartAnnouncing();
569     }
570 }
571 
UpdateProperty(AddressArray & aAddrProperty,const Ip6::Address * aAddrs,uint16_t aNumAddrs)572 void Core::RecordInfo::UpdateProperty(AddressArray &aAddrProperty, const Ip6::Address *aAddrs, uint16_t aNumAddrs)
573 {
574     if (!mIsPresent || !aAddrProperty.Matches(aAddrs, aNumAddrs))
575     {
576         mIsPresent = true;
577         aAddrProperty.SetFrom(aAddrs, aNumAddrs);
578         StartAnnouncing();
579     }
580 }
581 
UpdateTtl(uint32_t aTtl)582 void Core::RecordInfo::UpdateTtl(uint32_t aTtl) { return UpdateProperty(mTtl, aTtl); }
583 
StartAnnouncing(void)584 void Core::RecordInfo::StartAnnouncing(void)
585 {
586     if (mIsPresent)
587     {
588         mAnnounceCounter = 0;
589         mAnnounceTime    = TimerMilli::GetNow();
590     }
591 }
592 
CanAnswer(void) const593 bool Core::RecordInfo::CanAnswer(void) const { return (mIsPresent && (mTtl > 0)); }
594 
ScheduleAnswer(const AnswerInfo & aInfo)595 void Core::RecordInfo::ScheduleAnswer(const AnswerInfo &aInfo)
596 {
597     VerifyOrExit(CanAnswer());
598 
599     if (aInfo.mUnicastResponse)
600     {
601         mUnicastAnswerPending = true;
602         ExitNow();
603     }
604 
605     if (!aInfo.mIsProbe)
606     {
607         // Rate-limiting multicasts to prevent excessive packet flooding
608         // (RFC 6762 section 6): We enforce a minimum interval of one
609         // second (`kMinIntervalBetweenMulticast`) between multicast
610         // transmissions of the same record. Skip the new request if the
611         // answer time is too close to the last multicast time. A querier
612         // that did not receive and cache the previous transmission will
613         // retry its request.
614 
615         VerifyOrExit(GetDurationSinceLastMulticast(aInfo.mAnswerTime) >= kMinIntervalBetweenMulticast);
616     }
617 
618     if (mMulticastAnswerPending)
619     {
620         VerifyOrExit(aInfo.mAnswerTime < mAnswerTime);
621     }
622 
623     mMulticastAnswerPending = true;
624     mAnswerTime             = aInfo.mAnswerTime;
625 
626 exit:
627     return;
628 }
629 
ShouldAppendTo(TxMessage & aResponse,TimeMilli aNow) const630 bool Core::RecordInfo::ShouldAppendTo(TxMessage &aResponse, TimeMilli aNow) const
631 {
632     bool shouldAppend = false;
633 
634     VerifyOrExit(mIsPresent);
635 
636     switch (aResponse.GetType())
637     {
638     case TxMessage::kMulticastResponse:
639 
640         if ((mAnnounceCounter < kNumberOfAnnounces) && (mAnnounceTime <= aNow))
641         {
642             shouldAppend = true;
643             ExitNow();
644         }
645 
646         shouldAppend = mMulticastAnswerPending && (mAnswerTime <= aNow);
647         break;
648 
649     case TxMessage::kUnicastResponse:
650         shouldAppend = mUnicastAnswerPending;
651         break;
652 
653     default:
654         break;
655     }
656 
657 exit:
658     return shouldAppend;
659 }
660 
UpdateStateAfterAnswer(const TxMessage & aResponse)661 void Core::RecordInfo::UpdateStateAfterAnswer(const TxMessage &aResponse)
662 {
663     // Updates the state after a unicast or multicast response is
664     // prepared containing the record in the Answer section.
665 
666     VerifyOrExit(mIsPresent);
667 
668     switch (aResponse.GetType())
669     {
670     case TxMessage::kMulticastResponse:
671         VerifyOrExit(mAppendState == kAppendedInMulticastMsg);
672         VerifyOrExit(mAppendSection == kAnswerSection);
673 
674         mMulticastAnswerPending = false;
675 
676         if (mAnnounceCounter < kNumberOfAnnounces)
677         {
678             mAnnounceCounter++;
679 
680             if (mAnnounceCounter < kNumberOfAnnounces)
681             {
682                 uint32_t delay = (1U << (mAnnounceCounter - 1)) * kAnnounceInterval;
683 
684                 mAnnounceTime = TimerMilli::GetNow() + delay;
685             }
686             else if (mTtl == 0)
687             {
688                 // We are done announcing the removed record with zero TTL.
689                 mIsPresent = false;
690             }
691         }
692 
693         break;
694 
695     case TxMessage::kUnicastResponse:
696         VerifyOrExit(IsAppended());
697         VerifyOrExit(mAppendSection == kAnswerSection);
698         mUnicastAnswerPending = false;
699         break;
700 
701     default:
702         break;
703     }
704 
705 exit:
706     return;
707 }
708 
UpdateFireTimeOn(FireTime & aFireTime)709 void Core::RecordInfo::UpdateFireTimeOn(FireTime &aFireTime)
710 {
711     VerifyOrExit(mIsPresent);
712 
713     if (mAnnounceCounter < kNumberOfAnnounces)
714     {
715         aFireTime.SetFireTime(mAnnounceTime);
716     }
717 
718     if (mMulticastAnswerPending)
719     {
720         aFireTime.SetFireTime(mAnswerTime);
721     }
722 
723     if (mIsLastMulticastValid)
724     {
725         // `mLastMulticastTime` tracks the timestamp of the last
726         // multicast of this record. To handle potential 32-bit
727         // `TimeMilli` rollover, an aging mechanism is implemented.
728         // If the record isn't multicast again within a given age
729         // interval `kLastMulticastTimeAge`, `mIsLastMulticastValid`
730         // is cleared, indicating outdated multicast information.
731 
732         TimeMilli lastMulticastAgeTime = mLastMulticastTime + kLastMulticastTimeAge;
733 
734         if (lastMulticastAgeTime <= TimerMilli::GetNow())
735         {
736             mIsLastMulticastValid = false;
737         }
738         else
739         {
740             aFireTime.SetFireTime(lastMulticastAgeTime);
741         }
742     }
743 
744 exit:
745     return;
746 }
747 
MarkAsAppended(TxMessage & aTxMessage,Section aSection)748 void Core::RecordInfo::MarkAsAppended(TxMessage &aTxMessage, Section aSection)
749 {
750     mAppendSection = aSection;
751 
752     switch (aTxMessage.GetType())
753     {
754     case TxMessage::kMulticastResponse:
755     case TxMessage::kMulticastProbe:
756 
757         mAppendState = kAppendedInMulticastMsg;
758 
759         if ((aSection == kAnswerSection) || (aSection == kAdditionalDataSection))
760         {
761             mLastMulticastTime    = TimerMilli::GetNow();
762             mIsLastMulticastValid = true;
763         }
764 
765         break;
766 
767     case TxMessage::kUnicastResponse:
768         mAppendState = kAppendedInUnicastMsg;
769         break;
770 
771     case TxMessage::kMulticastQuery:
772         break;
773     }
774 }
775 
MarkToAppendInAdditionalData(void)776 void Core::RecordInfo::MarkToAppendInAdditionalData(void)
777 {
778     if (mAppendState == kNotAppended)
779     {
780         mAppendState = kToAppendInAdditionalData;
781     }
782 }
783 
IsAppended(void) const784 bool Core::RecordInfo::IsAppended(void) const
785 {
786     bool isAppended = false;
787 
788     switch (mAppendState)
789     {
790     case kNotAppended:
791     case kToAppendInAdditionalData:
792         break;
793     case kAppendedInMulticastMsg:
794     case kAppendedInUnicastMsg:
795         isAppended = true;
796         break;
797     }
798 
799     return isAppended;
800 }
801 
CanAppend(void) const802 bool Core::RecordInfo::CanAppend(void) const { return mIsPresent && !IsAppended(); }
803 
GetLastMulticastTime(TimeMilli & aLastMulticastTime) const804 Error Core::RecordInfo::GetLastMulticastTime(TimeMilli &aLastMulticastTime) const
805 {
806     Error error = kErrorNotFound;
807 
808     VerifyOrExit(mIsPresent && mIsLastMulticastValid);
809     aLastMulticastTime = mLastMulticastTime;
810 
811 exit:
812     return error;
813 }
814 
GetDurationSinceLastMulticast(TimeMilli aTime) const815 uint32_t Core::RecordInfo::GetDurationSinceLastMulticast(TimeMilli aTime) const
816 {
817     uint32_t duration = NumericLimits<uint32_t>::kMax;
818 
819     VerifyOrExit(mIsPresent && mIsLastMulticastValid);
820     VerifyOrExit(aTime > mLastMulticastTime, duration = 0);
821     duration = aTime - mLastMulticastTime;
822 
823 exit:
824     return duration;
825 }
826 
827 //----------------------------------------------------------------------------------------------------------------------
828 // Core::FireTime
829 
SetFireTime(TimeMilli aFireTime)830 void Core::FireTime::SetFireTime(TimeMilli aFireTime)
831 {
832     if (mHasFireTime)
833     {
834         VerifyOrExit(aFireTime < mFireTime);
835     }
836 
837     mFireTime    = aFireTime;
838     mHasFireTime = true;
839 
840 exit:
841     return;
842 }
843 
ScheduleFireTimeOn(TimerMilli & aTimer)844 void Core::FireTime::ScheduleFireTimeOn(TimerMilli &aTimer)
845 {
846     if (mHasFireTime)
847     {
848         aTimer.FireAtIfEarlier(mFireTime);
849     }
850 }
851 
UpdateNextFireTimeOn(NextFireTime & aNextFireTime) const852 void Core::FireTime::UpdateNextFireTimeOn(NextFireTime &aNextFireTime) const
853 {
854     if (mHasFireTime)
855     {
856         aNextFireTime.UpdateIfEarlier(mFireTime);
857     }
858 }
859 
860 //----------------------------------------------------------------------------------------------------------------------
861 // Core::Entry
862 
Entry(void)863 Core::Entry::Entry(void)
864     : mState(kProbing)
865     , mProbeCount(0)
866     , mMulticastNsecPending(false)
867     , mUnicastNsecPending(false)
868     , mAppendedNsec(false)
869     , mBypassCallbackStateCheck(false)
870 {
871 }
872 
Init(Instance & aInstance)873 void Core::Entry::Init(Instance &aInstance)
874 {
875     // Initializes a newly allocated entry (host or service)
876     // and starts it in `kProbing` state.
877 
878     InstanceLocatorInit::Init(aInstance);
879     StartProbing();
880 }
881 
SetState(State aState)882 void Core::Entry::SetState(State aState)
883 {
884     mState = aState;
885     ScheduleCallbackTask();
886 }
887 
Register(const Key & aKey,const Callback & aCallback)888 void Core::Entry::Register(const Key &aKey, const Callback &aCallback)
889 {
890     if (GetState() == kRemoving)
891     {
892         StartProbing();
893     }
894 
895     mKeyRecord.UpdateTtl(DetermineTtl(aKey.mTtl, kDefaultKeyTtl));
896     mKeyRecord.UpdateProperty(mKeyData, aKey.mKeyData, aKey.mKeyDataLength);
897 
898     mKeyCallback = aCallback;
899     ScheduleCallbackTask();
900 }
901 
Unregister(const Key & aKey)902 void Core::Entry::Unregister(const Key &aKey)
903 {
904     OT_UNUSED_VARIABLE(aKey);
905 
906     VerifyOrExit(mKeyRecord.IsPresent());
907 
908     mKeyCallback.Clear();
909 
910     switch (GetState())
911     {
912     case kRegistered:
913         mKeyRecord.UpdateTtl(0);
914         break;
915 
916     case kProbing:
917     case kConflict:
918         ClearKey();
919         break;
920 
921     case kRemoving:
922         break;
923     }
924 
925 exit:
926     return;
927 }
928 
ClearKey(void)929 void Core::Entry::ClearKey(void)
930 {
931     mKeyRecord.Clear();
932     mKeyData.Free();
933 }
934 
SetCallback(const Callback & aCallback)935 void Core::Entry::SetCallback(const Callback &aCallback)
936 {
937     mCallback = aCallback;
938     ScheduleCallbackTask();
939 }
940 
MarkToInvokeCallbackUnconditionally(void)941 void Core::Entry::MarkToInvokeCallbackUnconditionally(void)
942 {
943     mBypassCallbackStateCheck = true;
944     Get<Core>().mEntryTask.Post();
945 }
946 
ScheduleCallbackTask(void)947 void Core::Entry::ScheduleCallbackTask(void)
948 {
949     switch (GetState())
950     {
951     case kRegistered:
952     case kConflict:
953         VerifyOrExit(!mCallback.IsEmpty() || !mKeyCallback.IsEmpty());
954         Get<Core>().mEntryTask.Post();
955         break;
956 
957     case kProbing:
958     case kRemoving:
959         break;
960     }
961 
962 exit:
963     return;
964 }
965 
InvokeCallbacks(void)966 void Core::Entry::InvokeCallbacks(void)
967 {
968     Error error = kErrorNone;
969 
970     // `mBypassCallbackStateCheck` is used when host is registered
971     // with no address, which is treated as unregistering the host.
972     // This ensures host registration callback is invoked properly.
973 
974     if (mBypassCallbackStateCheck)
975     {
976         mBypassCallbackStateCheck = false;
977         mCallback.InvokeAndClear(GetInstance(), error);
978     }
979 
980     switch (GetState())
981     {
982     case kConflict:
983         error = kErrorDuplicated;
984         OT_FALL_THROUGH;
985 
986     case kRegistered:
987         mKeyCallback.InvokeAndClear(GetInstance(), error);
988         mCallback.InvokeAndClear(GetInstance(), error);
989         break;
990 
991     case kProbing:
992     case kRemoving:
993         break;
994     }
995 }
996 
StartProbing(void)997 void Core::Entry::StartProbing(void)
998 {
999     SetState(kProbing);
1000     mProbeCount = 0;
1001     SetFireTime(Get<Core>().RandomizeFirstProbeTxTime());
1002     ScheduleTimer();
1003 }
1004 
SetStateToConflict(void)1005 void Core::Entry::SetStateToConflict(void)
1006 {
1007     switch (GetState())
1008     {
1009     case kProbing:
1010     case kRegistered:
1011         SetState(kConflict);
1012         break;
1013     case kConflict:
1014     case kRemoving:
1015         break;
1016     }
1017 }
1018 
SetStateToRemoving(void)1019 void Core::Entry::SetStateToRemoving(void)
1020 {
1021     VerifyOrExit(GetState() != kRemoving);
1022     SetState(kRemoving);
1023 
1024 exit:
1025     return;
1026 }
1027 
ClearAppendState(void)1028 void Core::Entry::ClearAppendState(void)
1029 {
1030     mKeyRecord.MarkAsNotAppended();
1031     mAppendedNsec = false;
1032 }
1033 
UpdateRecordsState(const TxMessage & aResponse)1034 void Core::Entry::UpdateRecordsState(const TxMessage &aResponse)
1035 {
1036     mKeyRecord.UpdateStateAfterAnswer(aResponse);
1037 
1038     if (mAppendedNsec)
1039     {
1040         switch (aResponse.GetType())
1041         {
1042         case TxMessage::kMulticastResponse:
1043             mMulticastNsecPending = false;
1044             break;
1045         case TxMessage::kUnicastResponse:
1046             mUnicastNsecPending = false;
1047             break;
1048         default:
1049             break;
1050         }
1051     }
1052 }
1053 
ScheduleNsecAnswer(const AnswerInfo & aInfo)1054 void Core::Entry::ScheduleNsecAnswer(const AnswerInfo &aInfo)
1055 {
1056     // Schedules NSEC record to be included in a response message.
1057     // Used to answer to query for a record that is not present.
1058 
1059     VerifyOrExit(GetState() == kRegistered);
1060 
1061     if (aInfo.mUnicastResponse)
1062     {
1063         mUnicastNsecPending = true;
1064     }
1065     else
1066     {
1067         if (mMulticastNsecPending)
1068         {
1069             VerifyOrExit(aInfo.mAnswerTime < mNsecAnswerTime);
1070         }
1071 
1072         mMulticastNsecPending = true;
1073         mNsecAnswerTime       = aInfo.mAnswerTime;
1074     }
1075 
1076 exit:
1077     return;
1078 }
1079 
ShouldAnswerNsec(TimeMilli aNow) const1080 bool Core::Entry::ShouldAnswerNsec(TimeMilli aNow) const { return mMulticastNsecPending && (mNsecAnswerTime <= aNow); }
1081 
AnswerNonProbe(const AnswerInfo & aInfo,RecordAndType * aRecords,uint16_t aRecordsLength)1082 void Core::Entry::AnswerNonProbe(const AnswerInfo &aInfo, RecordAndType *aRecords, uint16_t aRecordsLength)
1083 {
1084     // Schedule answers for all matching records in `aRecords` array
1085     // to a given non-probe question.
1086 
1087     bool allEmptyOrZeroTtl = true;
1088     bool answerNsec        = true;
1089 
1090     for (uint16_t index = 0; index < aRecordsLength; index++)
1091     {
1092         RecordInfo &record = aRecords[index].mRecord;
1093 
1094         if (!record.CanAnswer())
1095         {
1096             // Cannot answer if record is not present or has zero TTL.
1097             continue;
1098         }
1099 
1100         allEmptyOrZeroTtl = false;
1101 
1102         if (QuestionMatches(aInfo.mQuestionRrType, aRecords[index].mType))
1103         {
1104             answerNsec = false;
1105             record.ScheduleAnswer(aInfo);
1106         }
1107     }
1108 
1109     // If all records are removed or have zero TTL (we are still
1110     // sending "Goodbye" announces), we should not provide any answer
1111     // even NSEC.
1112 
1113     if (!allEmptyOrZeroTtl && answerNsec)
1114     {
1115         ScheduleNsecAnswer(aInfo);
1116     }
1117 }
1118 
AnswerProbe(const AnswerInfo & aInfo,RecordAndType * aRecords,uint16_t aRecordsLength)1119 void Core::Entry::AnswerProbe(const AnswerInfo &aInfo, RecordAndType *aRecords, uint16_t aRecordsLength)
1120 {
1121     bool       allEmptyOrZeroTtl = true;
1122     bool       shouldDelay       = false;
1123     TimeMilli  now               = TimerMilli::GetNow();
1124     AnswerInfo info              = aInfo;
1125 
1126     info.mAnswerTime = now;
1127 
1128     OT_ASSERT(info.mIsProbe);
1129 
1130     for (uint16_t index = 0; index < aRecordsLength; index++)
1131     {
1132         RecordInfo &record = aRecords[index].mRecord;
1133         TimeMilli   lastMulticastTime;
1134 
1135         if (!record.CanAnswer())
1136         {
1137             continue;
1138         }
1139 
1140         allEmptyOrZeroTtl = false;
1141 
1142         if (!info.mUnicastResponse)
1143         {
1144             // Rate limiting multicast probe responses
1145             //
1146             // We delay the response if all records were multicast
1147             // recently within an interval `kMinIntervalProbeResponse`
1148             // (250 msec).
1149 
1150             if (record.GetDurationSinceLastMulticast(now) >= kMinIntervalProbeResponse)
1151             {
1152                 shouldDelay = false;
1153             }
1154             else if (record.GetLastMulticastTime(lastMulticastTime) == kErrorNone)
1155             {
1156                 info.mAnswerTime = Max(info.mAnswerTime, lastMulticastTime + kMinIntervalProbeResponse);
1157             }
1158         }
1159     }
1160 
1161     if (allEmptyOrZeroTtl)
1162     {
1163         // All records are removed or being removed.
1164 
1165         // Enhancement for future: If someone is probing for
1166         // our name, we can stop announcement of removed records
1167         // to let the new probe requester take over the name.
1168 
1169         ExitNow();
1170     }
1171 
1172     if (!shouldDelay)
1173     {
1174         info.mAnswerTime = now;
1175     }
1176 
1177     for (uint16_t index = 0; index < aRecordsLength; index++)
1178     {
1179         aRecords[index].mRecord.ScheduleAnswer(info);
1180     }
1181 
1182 exit:
1183     return;
1184 }
1185 
DetermineNextFireTime(void)1186 void Core::Entry::DetermineNextFireTime(void)
1187 {
1188     mKeyRecord.UpdateFireTimeOn(*this);
1189 
1190     if (mMulticastNsecPending)
1191     {
1192         SetFireTime(mNsecAnswerTime);
1193     }
1194 }
1195 
ScheduleTimer(void)1196 void Core::Entry::ScheduleTimer(void) { ScheduleFireTimeOn(Get<Core>().mEntryTimer); }
1197 
HandleTimer(EntryTimerContext & aContext)1198 template <typename EntryType> void Core::Entry::HandleTimer(EntryTimerContext &aContext)
1199 {
1200     EntryType *thisAsEntryType = static_cast<EntryType *>(this);
1201 
1202     thisAsEntryType->ClearAppendState();
1203 
1204     VerifyOrExit(HasFireTime());
1205     VerifyOrExit(GetFireTime() <= aContext.GetNow());
1206     ClearFireTime();
1207 
1208     switch (GetState())
1209     {
1210     case kProbing:
1211         if (mProbeCount < kNumberOfProbes)
1212         {
1213             mProbeCount++;
1214             SetFireTime(aContext.GetNow() + kProbeWaitTime);
1215             thisAsEntryType->PrepareProbe(aContext.GetProbeMessage());
1216             break;
1217         }
1218 
1219         SetState(kRegistered);
1220         thisAsEntryType->StartAnnouncing();
1221 
1222         OT_FALL_THROUGH;
1223 
1224     case kRegistered:
1225         thisAsEntryType->PrepareResponse(aContext.GetResponseMessage(), aContext.GetNow());
1226         break;
1227 
1228     case kConflict:
1229     case kRemoving:
1230         ExitNow();
1231     }
1232 
1233     thisAsEntryType->DetermineNextFireTime();
1234 
1235 exit:
1236     UpdateNextFireTimeOn(aContext.GetNextFireTime());
1237 }
1238 
AppendQuestionTo(TxMessage & aTxMessage) const1239 void Core::Entry::AppendQuestionTo(TxMessage &aTxMessage) const
1240 {
1241     Message &message = aTxMessage.SelectMessageFor(kQuestionSection);
1242     uint16_t rrClass = ResourceRecord::kClassInternet;
1243     Question question;
1244 
1245     if ((mProbeCount == 1) && Get<Core>().IsQuestionUnicastAllowed())
1246     {
1247         rrClass |= kClassQuestionUnicastFlag;
1248     }
1249 
1250     question.SetType(ResourceRecord::kTypeAny);
1251     question.SetClass(rrClass);
1252     SuccessOrAssert(message.Append(question));
1253 
1254     aTxMessage.IncrementRecordCount(kQuestionSection);
1255 }
1256 
AppendKeyRecordTo(TxMessage & aTxMessage,Section aSection,NameAppender aNameAppender)1257 void Core::Entry::AppendKeyRecordTo(TxMessage &aTxMessage, Section aSection, NameAppender aNameAppender)
1258 {
1259     Message       *message;
1260     ResourceRecord record;
1261 
1262     VerifyOrExit(mKeyRecord.CanAppend());
1263     mKeyRecord.MarkAsAppended(aTxMessage, aSection);
1264 
1265     message = &aTxMessage.SelectMessageFor(aSection);
1266 
1267     // Use the `aNameAppender` function to allow sub-class
1268     // to append the proper name.
1269 
1270     aNameAppender(*this, aTxMessage, aSection);
1271 
1272     record.Init(ResourceRecord::kTypeKey);
1273     record.SetTtl(mKeyRecord.GetTtl());
1274     record.SetLength(mKeyData.GetLength());
1275     UpdateCacheFlushFlagIn(record, aSection);
1276 
1277     SuccessOrAssert(message->Append(record));
1278     SuccessOrAssert(message->AppendBytes(mKeyData.GetBytes(), mKeyData.GetLength()));
1279 
1280     aTxMessage.IncrementRecordCount(aSection);
1281 
1282 exit:
1283     return;
1284 }
1285 
AppendNsecRecordTo(TxMessage & aTxMessage,Section aSection,const TypeArray & aTypes,NameAppender aNameAppender)1286 void Core::Entry::AppendNsecRecordTo(TxMessage       &aTxMessage,
1287                                      Section          aSection,
1288                                      const TypeArray &aTypes,
1289                                      NameAppender     aNameAppender)
1290 {
1291     Message               &message = aTxMessage.SelectMessageFor(aSection);
1292     NsecRecord             nsec;
1293     NsecRecord::TypeBitMap bitmap;
1294     uint16_t               offset;
1295 
1296     nsec.Init();
1297     nsec.SetTtl(kNsecTtl);
1298     UpdateCacheFlushFlagIn(nsec, aSection);
1299 
1300     bitmap.Clear();
1301 
1302     for (uint16_t type : aTypes)
1303     {
1304         bitmap.AddType(type);
1305     }
1306 
1307     aNameAppender(*this, aTxMessage, aSection);
1308 
1309     offset = message.GetLength();
1310     SuccessOrAssert(message.Append(nsec));
1311 
1312     // Next Domain Name (should be same as record name).
1313     aNameAppender(*this, aTxMessage, aSection);
1314 
1315     SuccessOrAssert(message.AppendBytes(&bitmap, bitmap.GetSize()));
1316 
1317     UpdateRecordLengthInMessage(nsec, message, offset);
1318     aTxMessage.IncrementRecordCount(aSection);
1319 
1320     mAppendedNsec = true;
1321 }
1322 
CopyKeyInfoTo(Key & aKey,EntryState & aState) const1323 Error Core::Entry::CopyKeyInfoTo(Key &aKey, EntryState &aState) const
1324 {
1325     Error error = kErrorNone;
1326 
1327     VerifyOrExit(mKeyRecord.IsPresent(), error = kErrorNotFound);
1328 
1329     aKey.mKeyData       = mKeyData.GetBytes();
1330     aKey.mKeyDataLength = mKeyData.GetLength();
1331     aKey.mClass         = ResourceRecord::kClassInternet;
1332     aKey.mTtl           = mKeyRecord.GetTtl();
1333     aKey.mInfraIfIndex  = Get<Core>().mInfraIfIndex;
1334     aState              = static_cast<EntryState>(GetState());
1335 
1336 exit:
1337     return error;
1338 }
1339 
1340 //----------------------------------------------------------------------------------------------------------------------
1341 // Core::HostEntry
1342 
HostEntry(void)1343 Core::HostEntry::HostEntry(void)
1344     : mNext(nullptr)
1345     , mNameOffset(kUnspecifiedOffset)
1346 {
1347 }
1348 
Init(Instance & aInstance,const char * aName)1349 Error Core::HostEntry::Init(Instance &aInstance, const char *aName)
1350 {
1351     Entry::Init(aInstance);
1352 
1353     return mName.Set(aName);
1354 }
1355 
Matches(const Name & aName) const1356 bool Core::HostEntry::Matches(const Name &aName) const
1357 {
1358     return aName.Matches(/* aFirstLabel */ nullptr, mName.AsCString(), kLocalDomain);
1359 }
1360 
Matches(const Host & aHost) const1361 bool Core::HostEntry::Matches(const Host &aHost) const { return NameMatch(mName, aHost.mHostName); }
1362 
Matches(const Key & aKey) const1363 bool Core::HostEntry::Matches(const Key &aKey) const { return !IsKeyForService(aKey) && NameMatch(mName, aKey.mName); }
1364 
Matches(const Heap::String & aName) const1365 bool Core::HostEntry::Matches(const Heap::String &aName) const { return NameMatch(mName, aName); }
1366 
IsEmpty(void) const1367 bool Core::HostEntry::IsEmpty(void) const { return !mAddrRecord.IsPresent() && !mKeyRecord.IsPresent(); }
1368 
Register(const Host & aHost,const Callback & aCallback)1369 void Core::HostEntry::Register(const Host &aHost, const Callback &aCallback)
1370 {
1371     if (GetState() == kRemoving)
1372     {
1373         StartProbing();
1374     }
1375 
1376     SetCallback(aCallback);
1377 
1378     if (aHost.mAddressesLength == 0)
1379     {
1380         // If host is registered with no addresses, treat it
1381         // as host being unregistered and announce removal of
1382         // the old addresses.
1383 
1384         Unregister(aHost);
1385 
1386         // Set the callback again as `Unregister()` may clear it.
1387         // Also mark to invoke the callback unconditionally (bypassing
1388         // entry state check). The callback will be invoked
1389         // after returning from this method from the posted tasklet.
1390 
1391         SetCallback(aCallback);
1392         MarkToInvokeCallbackUnconditionally();
1393         ExitNow();
1394     }
1395 
1396     mAddrRecord.UpdateTtl(DetermineTtl(aHost.mTtl, kDefaultTtl));
1397     mAddrRecord.UpdateProperty(mAddresses, AsCoreTypePtr(aHost.mAddresses), aHost.mAddressesLength);
1398 
1399     DetermineNextFireTime();
1400     ScheduleTimer();
1401 
1402 exit:
1403     return;
1404 }
1405 
Register(const Key & aKey,const Callback & aCallback)1406 void Core::HostEntry::Register(const Key &aKey, const Callback &aCallback)
1407 {
1408     Entry::Register(aKey, aCallback);
1409 
1410     DetermineNextFireTime();
1411     ScheduleTimer();
1412 }
1413 
Unregister(const Host & aHost)1414 void Core::HostEntry::Unregister(const Host &aHost)
1415 {
1416     OT_UNUSED_VARIABLE(aHost);
1417 
1418     VerifyOrExit(mAddrRecord.IsPresent());
1419 
1420     ClearCallback();
1421 
1422     switch (GetState())
1423     {
1424     case kRegistered:
1425         mAddrRecord.UpdateTtl(0);
1426         DetermineNextFireTime();
1427         ScheduleTimer();
1428         break;
1429 
1430     case kProbing:
1431     case kConflict:
1432         ClearHost();
1433         ScheduleToRemoveIfEmpty();
1434         break;
1435 
1436     case kRemoving:
1437         break;
1438     }
1439 
1440 exit:
1441     return;
1442 }
1443 
Unregister(const Key & aKey)1444 void Core::HostEntry::Unregister(const Key &aKey)
1445 {
1446     Entry::Unregister(aKey);
1447 
1448     DetermineNextFireTime();
1449     ScheduleTimer();
1450 
1451     ScheduleToRemoveIfEmpty();
1452 }
1453 
ClearHost(void)1454 void Core::HostEntry::ClearHost(void)
1455 {
1456     mAddrRecord.Clear();
1457     mAddresses.Free();
1458 }
1459 
ScheduleToRemoveIfEmpty(void)1460 void Core::HostEntry::ScheduleToRemoveIfEmpty(void)
1461 {
1462     if (IsEmpty())
1463     {
1464         SetStateToRemoving();
1465         Get<Core>().mEntryTask.Post();
1466     }
1467 }
1468 
HandleConflict(void)1469 void Core::HostEntry::HandleConflict(void)
1470 {
1471     State oldState = GetState();
1472 
1473     SetStateToConflict();
1474     VerifyOrExit(oldState == kRegistered);
1475     Get<Core>().InvokeConflictCallback(mName.AsCString(), nullptr);
1476 
1477 exit:
1478     return;
1479 }
1480 
AnswerQuestion(const AnswerInfo & aInfo)1481 void Core::HostEntry::AnswerQuestion(const AnswerInfo &aInfo)
1482 {
1483     RecordAndType records[] = {
1484         {mAddrRecord, ResourceRecord::kTypeAaaa},
1485         {mKeyRecord, ResourceRecord::kTypeKey},
1486     };
1487 
1488     VerifyOrExit(GetState() == kRegistered);
1489 
1490     if (aInfo.mIsProbe)
1491     {
1492         AnswerProbe(aInfo, records, GetArrayLength(records));
1493     }
1494     else
1495     {
1496         AnswerNonProbe(aInfo, records, GetArrayLength(records));
1497     }
1498 
1499     DetermineNextFireTime();
1500     ScheduleTimer();
1501 
1502 exit:
1503     return;
1504 }
1505 
HandleTimer(EntryTimerContext & aContext)1506 void Core::HostEntry::HandleTimer(EntryTimerContext &aContext) { Entry::HandleTimer<HostEntry>(aContext); }
1507 
ClearAppendState(void)1508 void Core::HostEntry::ClearAppendState(void)
1509 {
1510     // Clears `HostEntry` records and all tracked saved name
1511     // compression offsets.
1512 
1513     Entry::ClearAppendState();
1514 
1515     mAddrRecord.MarkAsNotAppended();
1516 
1517     mNameOffset = kUnspecifiedOffset;
1518 }
1519 
PrepareProbe(TxMessage & aProbe)1520 void Core::HostEntry::PrepareProbe(TxMessage &aProbe)
1521 {
1522     bool prepareAgain = false;
1523 
1524     do
1525     {
1526         aProbe.SaveCurrentState();
1527 
1528         AppendNameTo(aProbe, kQuestionSection);
1529         AppendQuestionTo(aProbe);
1530 
1531         AppendAddressRecordsTo(aProbe, kAuthoritySection);
1532         AppendKeyRecordTo(aProbe, kAuthoritySection);
1533 
1534         aProbe.CheckSizeLimitToPrepareAgain(prepareAgain);
1535 
1536     } while (prepareAgain);
1537 }
1538 
StartAnnouncing(void)1539 void Core::HostEntry::StartAnnouncing(void)
1540 {
1541     mAddrRecord.StartAnnouncing();
1542     mKeyRecord.StartAnnouncing();
1543 }
1544 
PrepareResponse(TxMessage & aResponse,TimeMilli aNow)1545 void Core::HostEntry::PrepareResponse(TxMessage &aResponse, TimeMilli aNow)
1546 {
1547     bool prepareAgain = false;
1548 
1549     do
1550     {
1551         aResponse.SaveCurrentState();
1552         PrepareResponseRecords(aResponse, aNow);
1553         aResponse.CheckSizeLimitToPrepareAgain(prepareAgain);
1554 
1555     } while (prepareAgain);
1556 
1557     UpdateRecordsState(aResponse);
1558 }
1559 
PrepareResponseRecords(TxMessage & aResponse,TimeMilli aNow)1560 void Core::HostEntry::PrepareResponseRecords(TxMessage &aResponse, TimeMilli aNow)
1561 {
1562     bool appendNsec = false;
1563 
1564     if (mAddrRecord.ShouldAppendTo(aResponse, aNow))
1565     {
1566         AppendAddressRecordsTo(aResponse, kAnswerSection);
1567         appendNsec = true;
1568     }
1569 
1570     if (mKeyRecord.ShouldAppendTo(aResponse, aNow))
1571     {
1572         AppendKeyRecordTo(aResponse, kAnswerSection);
1573         appendNsec = true;
1574     }
1575 
1576     if (appendNsec || ShouldAnswerNsec(aNow))
1577     {
1578         AppendNsecRecordTo(aResponse, kAdditionalDataSection);
1579     }
1580 }
1581 
UpdateRecordsState(const TxMessage & aResponse)1582 void Core::HostEntry::UpdateRecordsState(const TxMessage &aResponse)
1583 {
1584     // Updates state after a response is prepared.
1585 
1586     Entry::UpdateRecordsState(aResponse);
1587     mAddrRecord.UpdateStateAfterAnswer(aResponse);
1588 
1589     if (IsEmpty())
1590     {
1591         SetStateToRemoving();
1592     }
1593 }
1594 
DetermineNextFireTime(void)1595 void Core::HostEntry::DetermineNextFireTime(void)
1596 {
1597     VerifyOrExit(GetState() == kRegistered);
1598 
1599     Entry::DetermineNextFireTime();
1600     mAddrRecord.UpdateFireTimeOn(*this);
1601 
1602 exit:
1603     return;
1604 }
1605 
AppendAddressRecordsTo(TxMessage & aTxMessage,Section aSection)1606 void Core::HostEntry::AppendAddressRecordsTo(TxMessage &aTxMessage, Section aSection)
1607 {
1608     Message *message;
1609 
1610     VerifyOrExit(mAddrRecord.CanAppend());
1611     mAddrRecord.MarkAsAppended(aTxMessage, aSection);
1612 
1613     message = &aTxMessage.SelectMessageFor(aSection);
1614 
1615     for (const Ip6::Address &address : mAddresses)
1616     {
1617         AaaaRecord aaaaRecord;
1618 
1619         aaaaRecord.Init();
1620         aaaaRecord.SetTtl(mAddrRecord.GetTtl());
1621         aaaaRecord.SetAddress(address);
1622         UpdateCacheFlushFlagIn(aaaaRecord, aSection);
1623 
1624         AppendNameTo(aTxMessage, aSection);
1625         SuccessOrAssert(message->Append(aaaaRecord));
1626 
1627         aTxMessage.IncrementRecordCount(aSection);
1628     }
1629 
1630 exit:
1631     return;
1632 }
1633 
AppendKeyRecordTo(TxMessage & aTxMessage,Section aSection)1634 void Core::HostEntry::AppendKeyRecordTo(TxMessage &aTxMessage, Section aSection)
1635 {
1636     Entry::AppendKeyRecordTo(aTxMessage, aSection, &AppendEntryName);
1637 }
1638 
AppendNsecRecordTo(TxMessage & aTxMessage,Section aSection)1639 void Core::HostEntry::AppendNsecRecordTo(TxMessage &aTxMessage, Section aSection)
1640 {
1641     TypeArray types;
1642 
1643     if (mAddrRecord.IsPresent() && (mAddrRecord.GetTtl() > 0))
1644     {
1645         types.Add(ResourceRecord::kTypeAaaa);
1646     }
1647 
1648     if (mKeyRecord.IsPresent() && (mKeyRecord.GetTtl() > 0))
1649     {
1650         types.Add(ResourceRecord::kTypeKey);
1651     }
1652 
1653     if (!types.IsEmpty())
1654     {
1655         Entry::AppendNsecRecordTo(aTxMessage, aSection, types, &AppendEntryName);
1656     }
1657 }
1658 
AppendEntryName(Entry & aEntry,TxMessage & aTxMessage,Section aSection)1659 void Core::HostEntry::AppendEntryName(Entry &aEntry, TxMessage &aTxMessage, Section aSection)
1660 {
1661     static_cast<HostEntry &>(aEntry).AppendNameTo(aTxMessage, aSection);
1662 }
1663 
AppendNameTo(TxMessage & aTxMessage,Section aSection)1664 void Core::HostEntry::AppendNameTo(TxMessage &aTxMessage, Section aSection)
1665 {
1666     AppendOutcome outcome;
1667 
1668     outcome = aTxMessage.AppendMultipleLabels(aSection, mName.AsCString(), mNameOffset);
1669     VerifyOrExit(outcome != kAppendedFullNameAsCompressed);
1670 
1671     aTxMessage.AppendDomainName(aSection);
1672 
1673 exit:
1674     return;
1675 }
1676 
1677 #if OPENTHREAD_CONFIG_MULTICAST_DNS_ENTRY_ITERATION_API_ENABLE
1678 
CopyInfoTo(Host & aHost,EntryState & aState) const1679 Error Core::HostEntry::CopyInfoTo(Host &aHost, EntryState &aState) const
1680 {
1681     Error error = kErrorNone;
1682 
1683     VerifyOrExit(mAddrRecord.IsPresent(), error = kErrorNotFound);
1684 
1685     aHost.mHostName        = mName.AsCString();
1686     aHost.mAddresses       = mAddresses.AsCArray();
1687     aHost.mAddressesLength = mAddresses.GetLength();
1688     aHost.mTtl             = mAddrRecord.GetTtl();
1689     aHost.mInfraIfIndex    = Get<Core>().mInfraIfIndex;
1690     aState                 = static_cast<EntryState>(GetState());
1691 
1692 exit:
1693     return error;
1694 }
1695 
CopyInfoTo(Key & aKey,EntryState & aState) const1696 Error Core::HostEntry::CopyInfoTo(Key &aKey, EntryState &aState) const
1697 {
1698     Error error;
1699 
1700     SuccessOrExit(error = CopyKeyInfoTo(aKey, aState));
1701 
1702     aKey.mName        = mName.AsCString();
1703     aKey.mServiceType = nullptr;
1704 
1705 exit:
1706     return error;
1707 }
1708 
1709 #endif // OPENTHREAD_CONFIG_MULTICAST_DNS_ENTRY_ITERATION_API_ENABLE
1710 
1711 //----------------------------------------------------------------------------------------------------------------------
1712 // Core::ServiceEntry
1713 
1714 const uint8_t Core::ServiceEntry::kEmptyTxtData[] = {0};
1715 
ServiceEntry(void)1716 Core::ServiceEntry::ServiceEntry(void)
1717     : mNext(nullptr)
1718     , mPriority(0)
1719     , mWeight(0)
1720     , mPort(0)
1721     , mServiceNameOffset(kUnspecifiedOffset)
1722     , mServiceTypeOffset(kUnspecifiedOffset)
1723     , mSubServiceTypeOffset(kUnspecifiedOffset)
1724     , mHostNameOffset(kUnspecifiedOffset)
1725     , mIsAddedInServiceTypes(false)
1726 {
1727 }
1728 
Init(Instance & aInstance,const char * aServiceInstance,const char * aServiceType)1729 Error Core::ServiceEntry::Init(Instance &aInstance, const char *aServiceInstance, const char *aServiceType)
1730 {
1731     Error error;
1732 
1733     Entry::Init(aInstance);
1734 
1735     SuccessOrExit(error = mServiceInstance.Set(aServiceInstance));
1736     SuccessOrExit(error = mServiceType.Set(aServiceType));
1737 
1738 exit:
1739     return error;
1740 }
1741 
Init(Instance & aInstance,const Service & aService)1742 Error Core::ServiceEntry::Init(Instance &aInstance, const Service &aService)
1743 {
1744     return Init(aInstance, aService.mServiceInstance, aService.mServiceType);
1745 }
1746 
Init(Instance & aInstance,const Key & aKey)1747 Error Core::ServiceEntry::Init(Instance &aInstance, const Key &aKey)
1748 {
1749     return Init(aInstance, aKey.mName, aKey.mServiceType);
1750 }
1751 
Matches(const Name & aFullName) const1752 bool Core::ServiceEntry::Matches(const Name &aFullName) const
1753 {
1754     return aFullName.Matches(mServiceInstance.AsCString(), mServiceType.AsCString(), kLocalDomain);
1755 }
1756 
MatchesServiceType(const Name & aServiceType) const1757 bool Core::ServiceEntry::MatchesServiceType(const Name &aServiceType) const
1758 {
1759     // When matching service type, PTR record should be
1760     // present with non-zero TTL (checked by `CanAnswer()`).
1761 
1762     return mPtrRecord.CanAnswer() && aServiceType.Matches(nullptr, mServiceType.AsCString(), kLocalDomain);
1763 }
1764 
Matches(const Service & aService) const1765 bool Core::ServiceEntry::Matches(const Service &aService) const
1766 {
1767     return NameMatch(mServiceInstance, aService.mServiceInstance) && NameMatch(mServiceType, aService.mServiceType);
1768 }
1769 
Matches(const Key & aKey) const1770 bool Core::ServiceEntry::Matches(const Key &aKey) const
1771 {
1772     return IsKeyForService(aKey) && NameMatch(mServiceInstance, aKey.mName) &&
1773            NameMatch(mServiceType, aKey.mServiceType);
1774 }
1775 
IsEmpty(void) const1776 bool Core::ServiceEntry::IsEmpty(void) const { return !mPtrRecord.IsPresent() && !mKeyRecord.IsPresent(); }
1777 
CanAnswerSubType(const char * aSubLabel) const1778 bool Core::ServiceEntry::CanAnswerSubType(const char *aSubLabel) const
1779 {
1780     bool           canAnswer = false;
1781     const SubType *subType;
1782 
1783     VerifyOrExit(mPtrRecord.CanAnswer());
1784 
1785     subType = mSubTypes.FindMatching(aSubLabel);
1786     VerifyOrExit(subType != nullptr);
1787 
1788     canAnswer = subType->mPtrRecord.CanAnswer();
1789 
1790 exit:
1791     return canAnswer;
1792 }
1793 
Register(const Service & aService,const Callback & aCallback)1794 void Core::ServiceEntry::Register(const Service &aService, const Callback &aCallback)
1795 {
1796     uint32_t ttl = DetermineTtl(aService.mTtl, kDefaultTtl);
1797 
1798     if (GetState() == kRemoving)
1799     {
1800         StartProbing();
1801     }
1802 
1803     SetCallback(aCallback);
1804 
1805     // Register sub-types PTRs.
1806 
1807     // First we check for any removed sub-types. We keep removed
1808     // sub-types marked with zero TTL so to announce their removal
1809     // before fully removing them from the list.
1810 
1811     for (SubType &subType : mSubTypes)
1812     {
1813         uint32_t subTypeTtl = subType.IsContainedIn(aService) ? ttl : 0;
1814 
1815         subType.mPtrRecord.UpdateTtl(subTypeTtl);
1816     }
1817 
1818     // Next we add any new sub-types in `aService`.
1819 
1820     for (uint16_t i = 0; i < aService.mSubTypeLabelsLength; i++)
1821     {
1822         const char *label = aService.mSubTypeLabels[i];
1823 
1824         if (!mSubTypes.ContainsMatching(label))
1825         {
1826             SubType *newSubType = SubType::AllocateAndInit(label);
1827 
1828             OT_ASSERT(newSubType != nullptr);
1829             mSubTypes.Push(*newSubType);
1830 
1831             newSubType->mPtrRecord.UpdateTtl(ttl);
1832         }
1833     }
1834 
1835     // Register base PTR service.
1836 
1837     mPtrRecord.UpdateTtl(ttl);
1838 
1839     // Register SRV record info.
1840 
1841     mSrvRecord.UpdateTtl(ttl);
1842     mSrvRecord.UpdateProperty(mHostName, aService.mHostName);
1843     mSrvRecord.UpdateProperty(mPriority, aService.mPriority);
1844     mSrvRecord.UpdateProperty(mWeight, aService.mWeight);
1845     mSrvRecord.UpdateProperty(mPort, aService.mPort);
1846 
1847     // Register TXT record info.
1848 
1849     mTxtRecord.UpdateTtl(ttl);
1850 
1851     if ((aService.mTxtData == nullptr) || (aService.mTxtDataLength == 0))
1852     {
1853         mTxtRecord.UpdateProperty(mTxtData, kEmptyTxtData, sizeof(kEmptyTxtData));
1854     }
1855     else
1856     {
1857         mTxtRecord.UpdateProperty(mTxtData, aService.mTxtData, aService.mTxtDataLength);
1858     }
1859 
1860     UpdateServiceTypes();
1861 
1862     DetermineNextFireTime();
1863     ScheduleTimer();
1864 }
1865 
Register(const Key & aKey,const Callback & aCallback)1866 void Core::ServiceEntry::Register(const Key &aKey, const Callback &aCallback)
1867 {
1868     Entry::Register(aKey, aCallback);
1869 
1870     DetermineNextFireTime();
1871     ScheduleTimer();
1872 }
1873 
Unregister(const Service & aService)1874 void Core::ServiceEntry::Unregister(const Service &aService)
1875 {
1876     OT_UNUSED_VARIABLE(aService);
1877 
1878     VerifyOrExit(mPtrRecord.IsPresent());
1879 
1880     ClearCallback();
1881 
1882     switch (GetState())
1883     {
1884     case kRegistered:
1885         for (SubType &subType : mSubTypes)
1886         {
1887             subType.mPtrRecord.UpdateTtl(0);
1888         }
1889 
1890         mPtrRecord.UpdateTtl(0);
1891         mSrvRecord.UpdateTtl(0);
1892         mTxtRecord.UpdateTtl(0);
1893         DetermineNextFireTime();
1894         ScheduleTimer();
1895         break;
1896 
1897     case kProbing:
1898     case kConflict:
1899         ClearService();
1900         ScheduleToRemoveIfEmpty();
1901         break;
1902 
1903     case kRemoving:
1904         break;
1905     }
1906 
1907     UpdateServiceTypes();
1908 
1909 exit:
1910     return;
1911 }
1912 
Unregister(const Key & aKey)1913 void Core::ServiceEntry::Unregister(const Key &aKey)
1914 {
1915     Entry::Unregister(aKey);
1916 
1917     DetermineNextFireTime();
1918     ScheduleTimer();
1919 
1920     ScheduleToRemoveIfEmpty();
1921 }
1922 
ClearService(void)1923 void Core::ServiceEntry::ClearService(void)
1924 {
1925     mPtrRecord.Clear();
1926     mSrvRecord.Clear();
1927     mTxtRecord.Clear();
1928     mSubTypes.Free();
1929     mHostName.Free();
1930     mTxtData.Free();
1931 }
1932 
ScheduleToRemoveIfEmpty(void)1933 void Core::ServiceEntry::ScheduleToRemoveIfEmpty(void)
1934 {
1935     mSubTypes.RemoveAndFreeAllMatching(EmptyChecker());
1936 
1937     if (IsEmpty())
1938     {
1939         SetStateToRemoving();
1940         Get<Core>().mEntryTask.Post();
1941     }
1942 }
1943 
HandleConflict(void)1944 void Core::ServiceEntry::HandleConflict(void)
1945 {
1946     State oldState = GetState();
1947 
1948     SetStateToConflict();
1949     UpdateServiceTypes();
1950 
1951     VerifyOrExit(oldState == kRegistered);
1952     Get<Core>().InvokeConflictCallback(mServiceInstance.AsCString(), mServiceType.AsCString());
1953 
1954 exit:
1955     return;
1956 }
1957 
AnswerServiceNameQuestion(const AnswerInfo & aInfo)1958 void Core::ServiceEntry::AnswerServiceNameQuestion(const AnswerInfo &aInfo)
1959 {
1960     RecordAndType records[] = {
1961         {mSrvRecord, ResourceRecord::kTypeSrv},
1962         {mTxtRecord, ResourceRecord::kTypeTxt},
1963         {mKeyRecord, ResourceRecord::kTypeKey},
1964     };
1965 
1966     VerifyOrExit(GetState() == kRegistered);
1967 
1968     if (aInfo.mIsProbe)
1969     {
1970         AnswerProbe(aInfo, records, GetArrayLength(records));
1971     }
1972     else
1973     {
1974         AnswerNonProbe(aInfo, records, GetArrayLength(records));
1975     }
1976 
1977     DetermineNextFireTime();
1978     ScheduleTimer();
1979 
1980 exit:
1981     return;
1982 }
1983 
AnswerServiceTypeQuestion(const AnswerInfo & aInfo,const char * aSubLabel)1984 void Core::ServiceEntry::AnswerServiceTypeQuestion(const AnswerInfo &aInfo, const char *aSubLabel)
1985 {
1986     VerifyOrExit(GetState() == kRegistered);
1987 
1988     if (aSubLabel == nullptr)
1989     {
1990         mPtrRecord.ScheduleAnswer(aInfo);
1991     }
1992     else
1993     {
1994         SubType *subType = mSubTypes.FindMatching(aSubLabel);
1995 
1996         VerifyOrExit(subType != nullptr);
1997         subType->mPtrRecord.ScheduleAnswer(aInfo);
1998     }
1999 
2000     DetermineNextFireTime();
2001     ScheduleTimer();
2002 
2003 exit:
2004     return;
2005 }
2006 
ShouldSuppressKnownAnswer(uint32_t aTtl,const char * aSubLabel) const2007 bool Core::ServiceEntry::ShouldSuppressKnownAnswer(uint32_t aTtl, const char *aSubLabel) const
2008 {
2009     // Check `aTtl` of a matching record in known-answer section of
2010     // a query with the corresponding PTR record's TTL and suppress
2011     // answer if it is at least at least half the correct value.
2012 
2013     bool     shouldSuppress = false;
2014     uint32_t ttl;
2015 
2016     if (aSubLabel == nullptr)
2017     {
2018         ttl = mPtrRecord.GetTtl();
2019     }
2020     else
2021     {
2022         const SubType *subType = mSubTypes.FindMatching(aSubLabel);
2023 
2024         VerifyOrExit(subType != nullptr);
2025         ttl = subType->mPtrRecord.GetTtl();
2026     }
2027 
2028     shouldSuppress = (aTtl > ttl / 2);
2029 
2030 exit:
2031     return shouldSuppress;
2032 }
2033 
HandleTimer(EntryTimerContext & aContext)2034 void Core::ServiceEntry::HandleTimer(EntryTimerContext &aContext) { Entry::HandleTimer<ServiceEntry>(aContext); }
2035 
ClearAppendState(void)2036 void Core::ServiceEntry::ClearAppendState(void)
2037 {
2038     // Clear the append state for all `ServiceEntry` records,
2039     // along with all tracked name compression offsets.
2040 
2041     Entry::ClearAppendState();
2042 
2043     mPtrRecord.MarkAsNotAppended();
2044     mSrvRecord.MarkAsNotAppended();
2045     mTxtRecord.MarkAsNotAppended();
2046 
2047     mServiceNameOffset    = kUnspecifiedOffset;
2048     mServiceTypeOffset    = kUnspecifiedOffset;
2049     mSubServiceTypeOffset = kUnspecifiedOffset;
2050     mHostNameOffset       = kUnspecifiedOffset;
2051 
2052     for (SubType &subType : mSubTypes)
2053     {
2054         subType.mPtrRecord.MarkAsNotAppended();
2055         subType.mSubServiceNameOffset = kUnspecifiedOffset;
2056     }
2057 }
2058 
PrepareProbe(TxMessage & aProbe)2059 void Core::ServiceEntry::PrepareProbe(TxMessage &aProbe)
2060 {
2061     bool prepareAgain = false;
2062 
2063     do
2064     {
2065         HostEntry *hostEntry = nullptr;
2066 
2067         aProbe.SaveCurrentState();
2068 
2069         DiscoverOffsetsAndHost(hostEntry);
2070 
2071         AppendServiceNameTo(aProbe, kQuestionSection);
2072         AppendQuestionTo(aProbe);
2073 
2074         // Append records (if present) in authority section
2075 
2076         AppendSrvRecordTo(aProbe, kAuthoritySection);
2077         AppendTxtRecordTo(aProbe, kAuthoritySection);
2078         AppendKeyRecordTo(aProbe, kAuthoritySection);
2079 
2080         aProbe.CheckSizeLimitToPrepareAgain(prepareAgain);
2081 
2082     } while (prepareAgain);
2083 }
2084 
StartAnnouncing(void)2085 void Core::ServiceEntry::StartAnnouncing(void)
2086 {
2087     for (SubType &subType : mSubTypes)
2088     {
2089         subType.mPtrRecord.StartAnnouncing();
2090     }
2091 
2092     mPtrRecord.StartAnnouncing();
2093     mSrvRecord.StartAnnouncing();
2094     mTxtRecord.StartAnnouncing();
2095     mKeyRecord.StartAnnouncing();
2096 
2097     UpdateServiceTypes();
2098 }
2099 
PrepareResponse(TxMessage & aResponse,TimeMilli aNow)2100 void Core::ServiceEntry::PrepareResponse(TxMessage &aResponse, TimeMilli aNow)
2101 {
2102     bool prepareAgain = false;
2103 
2104     do
2105     {
2106         aResponse.SaveCurrentState();
2107         PrepareResponseRecords(aResponse, aNow);
2108         aResponse.CheckSizeLimitToPrepareAgain(prepareAgain);
2109 
2110     } while (prepareAgain);
2111 
2112     UpdateRecordsState(aResponse);
2113 }
2114 
PrepareResponseRecords(TxMessage & aResponse,TimeMilli aNow)2115 void Core::ServiceEntry::PrepareResponseRecords(TxMessage &aResponse, TimeMilli aNow)
2116 {
2117     bool       appendNsec                    = false;
2118     bool       appendAdditionalRecordsForPtr = false;
2119     HostEntry *hostEntry                     = nullptr;
2120 
2121     DiscoverOffsetsAndHost(hostEntry);
2122 
2123     // We determine records to include in Additional Data section
2124     // per RFC 6763 section 12:
2125     //
2126     // - For PTR (base or sub-type), we include SRV, TXT, and host
2127     //   addresses.
2128     // - For SRV, we include host addresses only (TXT record not
2129     //   recommended).
2130     //
2131     // Records already appended in Answer section are excluded from
2132     // Additional Data. Host Entries are processed before Service
2133     // Entries which ensures address inclusion accuracy.
2134     // `MarkToAppendInAdditionalData()` marks a record for potential
2135     // Additional Data inclusion, but this is skipped if the record
2136     // is already appended in the Answer section.
2137 
2138     if (mPtrRecord.ShouldAppendTo(aResponse, aNow))
2139     {
2140         AppendPtrRecordTo(aResponse, kAnswerSection);
2141 
2142         if (mPtrRecord.GetTtl() > 0)
2143         {
2144             appendAdditionalRecordsForPtr = true;
2145         }
2146     }
2147 
2148     for (SubType &subType : mSubTypes)
2149     {
2150         if (subType.mPtrRecord.ShouldAppendTo(aResponse, aNow))
2151         {
2152             AppendPtrRecordTo(aResponse, kAnswerSection, &subType);
2153 
2154             if (subType.mPtrRecord.GetTtl() > 0)
2155             {
2156                 appendAdditionalRecordsForPtr = true;
2157             }
2158         }
2159     }
2160 
2161     if (appendAdditionalRecordsForPtr)
2162     {
2163         mSrvRecord.MarkToAppendInAdditionalData();
2164         mTxtRecord.MarkToAppendInAdditionalData();
2165 
2166         if (hostEntry != nullptr)
2167         {
2168             hostEntry->mAddrRecord.MarkToAppendInAdditionalData();
2169         }
2170     }
2171 
2172     if (mSrvRecord.ShouldAppendTo(aResponse, aNow))
2173     {
2174         AppendSrvRecordTo(aResponse, kAnswerSection);
2175         appendNsec = true;
2176 
2177         if ((mSrvRecord.GetTtl() > 0) && (hostEntry != nullptr))
2178         {
2179             hostEntry->mAddrRecord.MarkToAppendInAdditionalData();
2180         }
2181     }
2182 
2183     if (mTxtRecord.ShouldAppendTo(aResponse, aNow))
2184     {
2185         AppendTxtRecordTo(aResponse, kAnswerSection);
2186         appendNsec = true;
2187     }
2188 
2189     if (mKeyRecord.ShouldAppendTo(aResponse, aNow))
2190     {
2191         AppendKeyRecordTo(aResponse, kAnswerSection);
2192         appendNsec = true;
2193     }
2194 
2195     // Append records in Additional Data section
2196 
2197     if (mSrvRecord.ShouldAppendInAdditionalDataSection())
2198     {
2199         AppendSrvRecordTo(aResponse, kAdditionalDataSection);
2200     }
2201 
2202     if (mTxtRecord.ShouldAppendInAdditionalDataSection())
2203     {
2204         AppendTxtRecordTo(aResponse, kAdditionalDataSection);
2205     }
2206 
2207     if ((hostEntry != nullptr) && (hostEntry->mAddrRecord.ShouldAppendInAdditionalDataSection()))
2208     {
2209         hostEntry->AppendAddressRecordsTo(aResponse, kAdditionalDataSection);
2210     }
2211 
2212     if (appendNsec || ShouldAnswerNsec(aNow))
2213     {
2214         AppendNsecRecordTo(aResponse, kAdditionalDataSection);
2215     }
2216 }
2217 
UpdateRecordsState(const TxMessage & aResponse)2218 void Core::ServiceEntry::UpdateRecordsState(const TxMessage &aResponse)
2219 {
2220     Entry::UpdateRecordsState(aResponse);
2221 
2222     mPtrRecord.UpdateStateAfterAnswer(aResponse);
2223     mSrvRecord.UpdateStateAfterAnswer(aResponse);
2224     mTxtRecord.UpdateStateAfterAnswer(aResponse);
2225 
2226     for (SubType &subType : mSubTypes)
2227     {
2228         subType.mPtrRecord.UpdateStateAfterAnswer(aResponse);
2229     }
2230 
2231     mSubTypes.RemoveAndFreeAllMatching(EmptyChecker());
2232 
2233     if (IsEmpty())
2234     {
2235         SetStateToRemoving();
2236     }
2237 }
2238 
DetermineNextFireTime(void)2239 void Core::ServiceEntry::DetermineNextFireTime(void)
2240 {
2241     VerifyOrExit(GetState() == kRegistered);
2242 
2243     Entry::DetermineNextFireTime();
2244 
2245     mPtrRecord.UpdateFireTimeOn(*this);
2246     mSrvRecord.UpdateFireTimeOn(*this);
2247     mTxtRecord.UpdateFireTimeOn(*this);
2248 
2249     for (SubType &subType : mSubTypes)
2250     {
2251         subType.mPtrRecord.UpdateFireTimeOn(*this);
2252     }
2253 
2254 exit:
2255     return;
2256 }
2257 
DiscoverOffsetsAndHost(HostEntry * & aHostEntry)2258 void Core::ServiceEntry::DiscoverOffsetsAndHost(HostEntry *&aHostEntry)
2259 {
2260     // Discovers the `HostEntry` associated with this `ServiceEntry`
2261     // and name compression offsets from the previously appended
2262     // entries.
2263 
2264     aHostEntry = Get<Core>().mHostEntries.FindMatching(mHostName);
2265 
2266     if ((aHostEntry != nullptr) && (aHostEntry->GetState() != GetState()))
2267     {
2268         aHostEntry = nullptr;
2269     }
2270 
2271     if (aHostEntry != nullptr)
2272     {
2273         UpdateCompressOffset(mHostNameOffset, aHostEntry->mNameOffset);
2274     }
2275 
2276     for (ServiceEntry &other : Get<Core>().mServiceEntries)
2277     {
2278         // We only need to search up to `this` entry in the list,
2279         // since entries after `this` are not yet processed and not
2280         // yet appended in the response or the probe message.
2281 
2282         if (&other == this)
2283         {
2284             break;
2285         }
2286 
2287         if (other.GetState() != GetState())
2288         {
2289             // Validate that both entries are in the same state,
2290             // ensuring their records are appended in the same
2291             // message, i.e., a probe or a response message.
2292 
2293             continue;
2294         }
2295 
2296         if (NameMatch(mHostName, other.mHostName))
2297         {
2298             UpdateCompressOffset(mHostNameOffset, other.mHostNameOffset);
2299         }
2300 
2301         if (NameMatch(mServiceType, other.mServiceType))
2302         {
2303             UpdateCompressOffset(mServiceTypeOffset, other.mServiceTypeOffset);
2304 
2305             if (GetState() == kProbing)
2306             {
2307                 // No need to search for sub-type service offsets when
2308                 // we are still probing.
2309 
2310                 continue;
2311             }
2312 
2313             UpdateCompressOffset(mSubServiceTypeOffset, other.mSubServiceTypeOffset);
2314 
2315             for (SubType &subType : mSubTypes)
2316             {
2317                 const SubType *otherSubType = other.mSubTypes.FindMatching(subType.mLabel.AsCString());
2318 
2319                 if (otherSubType != nullptr)
2320                 {
2321                     UpdateCompressOffset(subType.mSubServiceNameOffset, otherSubType->mSubServiceNameOffset);
2322                 }
2323             }
2324         }
2325     }
2326 }
2327 
UpdateServiceTypes(void)2328 void Core::ServiceEntry::UpdateServiceTypes(void)
2329 {
2330     // This method updates the `mServiceTypes` list adding or
2331     // removing this `ServiceEntry` info.
2332     //
2333     // It is called whenever the `ServiceEntry` state gets changed
2334     // or a PTR record is added or removed. The service is valid
2335     // when entry is registered and we have a PTR with non-zero
2336     // TTL.
2337 
2338     bool         shouldAdd = (GetState() == kRegistered) && mPtrRecord.CanAnswer();
2339     ServiceType *serviceType;
2340 
2341     VerifyOrExit(shouldAdd != mIsAddedInServiceTypes);
2342 
2343     mIsAddedInServiceTypes = shouldAdd;
2344 
2345     serviceType = Get<Core>().mServiceTypes.FindMatching(mServiceType);
2346 
2347     if (shouldAdd && (serviceType == nullptr))
2348     {
2349         serviceType = ServiceType::AllocateAndInit(GetInstance(), mServiceType.AsCString());
2350         OT_ASSERT(serviceType != nullptr);
2351         Get<Core>().mServiceTypes.Push(*serviceType);
2352     }
2353 
2354     VerifyOrExit(serviceType != nullptr);
2355 
2356     if (shouldAdd)
2357     {
2358         serviceType->IncrementNumEntries();
2359     }
2360     else
2361     {
2362         serviceType->DecrementNumEntries();
2363 
2364         if (serviceType->GetNumEntries() == 0)
2365         {
2366             // If there are no more `ServiceEntry` with
2367             // this service type, we remove the it from
2368             // the `mServiceTypes` list. It is safe to
2369             // remove here as this method will never be
2370             // called while we are iterating over the
2371             // `mServiceTypes` list.
2372 
2373             Get<Core>().mServiceTypes.RemoveMatching(*serviceType);
2374         }
2375     }
2376 
2377 exit:
2378     return;
2379 }
2380 
AppendSrvRecordTo(TxMessage & aTxMessage,Section aSection)2381 void Core::ServiceEntry::AppendSrvRecordTo(TxMessage &aTxMessage, Section aSection)
2382 {
2383     Message  *message;
2384     SrvRecord srv;
2385     uint16_t  offset;
2386 
2387     VerifyOrExit(mSrvRecord.CanAppend());
2388     mSrvRecord.MarkAsAppended(aTxMessage, aSection);
2389 
2390     message = &aTxMessage.SelectMessageFor(aSection);
2391 
2392     srv.Init();
2393     srv.SetTtl(mSrvRecord.GetTtl());
2394     srv.SetPriority(mPriority);
2395     srv.SetWeight(mWeight);
2396     srv.SetPort(mPort);
2397     UpdateCacheFlushFlagIn(srv, aSection);
2398 
2399     AppendServiceNameTo(aTxMessage, aSection);
2400     offset = message->GetLength();
2401     SuccessOrAssert(message->Append(srv));
2402     AppendHostNameTo(aTxMessage, aSection);
2403     UpdateRecordLengthInMessage(srv, *message, offset);
2404 
2405     aTxMessage.IncrementRecordCount(aSection);
2406 
2407 exit:
2408     return;
2409 }
2410 
AppendTxtRecordTo(TxMessage & aTxMessage,Section aSection)2411 void Core::ServiceEntry::AppendTxtRecordTo(TxMessage &aTxMessage, Section aSection)
2412 {
2413     Message  *message;
2414     TxtRecord txt;
2415 
2416     VerifyOrExit(mTxtRecord.CanAppend());
2417     mTxtRecord.MarkAsAppended(aTxMessage, aSection);
2418 
2419     message = &aTxMessage.SelectMessageFor(aSection);
2420 
2421     txt.Init();
2422     txt.SetTtl(mTxtRecord.GetTtl());
2423     txt.SetLength(mTxtData.GetLength());
2424     UpdateCacheFlushFlagIn(txt, aSection);
2425 
2426     AppendServiceNameTo(aTxMessage, aSection);
2427     SuccessOrAssert(message->Append(txt));
2428     SuccessOrAssert(message->AppendBytes(mTxtData.GetBytes(), mTxtData.GetLength()));
2429 
2430     aTxMessage.IncrementRecordCount(aSection);
2431 
2432 exit:
2433     return;
2434 }
2435 
AppendPtrRecordTo(TxMessage & aTxMessage,Section aSection,SubType * aSubType)2436 void Core::ServiceEntry::AppendPtrRecordTo(TxMessage &aTxMessage, Section aSection, SubType *aSubType)
2437 {
2438     // Appends PTR record for base service (when `aSubType == nullptr`) or
2439     // for the given `aSubType`.
2440 
2441     Message    *message;
2442     RecordInfo &ptrRecord = (aSubType == nullptr) ? mPtrRecord : aSubType->mPtrRecord;
2443     PtrRecord   ptr;
2444     uint16_t    offset;
2445 
2446     VerifyOrExit(ptrRecord.CanAppend());
2447     ptrRecord.MarkAsAppended(aTxMessage, aSection);
2448 
2449     message = &aTxMessage.SelectMessageFor(aSection);
2450 
2451     ptr.Init();
2452     ptr.SetTtl(ptrRecord.GetTtl());
2453 
2454     if (aSubType == nullptr)
2455     {
2456         AppendServiceTypeTo(aTxMessage, aSection);
2457     }
2458     else
2459     {
2460         AppendSubServiceNameTo(aTxMessage, aSection, *aSubType);
2461     }
2462 
2463     offset = message->GetLength();
2464     SuccessOrAssert(message->Append(ptr));
2465     AppendServiceNameTo(aTxMessage, aSection);
2466     UpdateRecordLengthInMessage(ptr, *message, offset);
2467 
2468     aTxMessage.IncrementRecordCount(aSection);
2469 
2470 exit:
2471     return;
2472 }
2473 
AppendKeyRecordTo(TxMessage & aTxMessage,Section aSection)2474 void Core::ServiceEntry::AppendKeyRecordTo(TxMessage &aTxMessage, Section aSection)
2475 {
2476     Entry::AppendKeyRecordTo(aTxMessage, aSection, &AppendEntryName);
2477 }
2478 
AppendNsecRecordTo(TxMessage & aTxMessage,Section aSection)2479 void Core::ServiceEntry::AppendNsecRecordTo(TxMessage &aTxMessage, Section aSection)
2480 {
2481     TypeArray types;
2482 
2483     if (mSrvRecord.IsPresent() && (mSrvRecord.GetTtl() > 0))
2484     {
2485         types.Add(ResourceRecord::kTypeSrv);
2486     }
2487 
2488     if (mTxtRecord.IsPresent() && (mTxtRecord.GetTtl() > 0))
2489     {
2490         types.Add(ResourceRecord::kTypeTxt);
2491     }
2492 
2493     if (mKeyRecord.IsPresent() && (mKeyRecord.GetTtl() > 0))
2494     {
2495         types.Add(ResourceRecord::kTypeKey);
2496     }
2497 
2498     if (!types.IsEmpty())
2499     {
2500         Entry::AppendNsecRecordTo(aTxMessage, aSection, types, &AppendEntryName);
2501     }
2502 }
2503 
AppendEntryName(Entry & aEntry,TxMessage & aTxMessage,Section aSection)2504 void Core::ServiceEntry::AppendEntryName(Entry &aEntry, TxMessage &aTxMessage, Section aSection)
2505 {
2506     static_cast<ServiceEntry &>(aEntry).AppendServiceNameTo(aTxMessage, aSection);
2507 }
2508 
AppendServiceNameTo(TxMessage & aTxMessage,Section aSection)2509 void Core::ServiceEntry::AppendServiceNameTo(TxMessage &aTxMessage, Section aSection)
2510 {
2511     AppendOutcome outcome;
2512 
2513     outcome = aTxMessage.AppendLabel(aSection, mServiceInstance.AsCString(), mServiceNameOffset);
2514     VerifyOrExit(outcome != kAppendedFullNameAsCompressed);
2515 
2516     AppendServiceTypeTo(aTxMessage, aSection);
2517 
2518 exit:
2519     return;
2520 }
2521 
AppendServiceTypeTo(TxMessage & aTxMessage,Section aSection)2522 void Core::ServiceEntry::AppendServiceTypeTo(TxMessage &aTxMessage, Section aSection)
2523 {
2524     aTxMessage.AppendServiceType(aSection, mServiceType.AsCString(), mServiceTypeOffset);
2525 }
2526 
AppendSubServiceTypeTo(TxMessage & aTxMessage,Section aSection)2527 void Core::ServiceEntry::AppendSubServiceTypeTo(TxMessage &aTxMessage, Section aSection)
2528 {
2529     AppendOutcome outcome;
2530 
2531     outcome = aTxMessage.AppendLabel(aSection, kSubServiceLabel, mSubServiceTypeOffset);
2532     VerifyOrExit(outcome != kAppendedFullNameAsCompressed);
2533 
2534     AppendServiceTypeTo(aTxMessage, aSection);
2535 
2536 exit:
2537     return;
2538 }
2539 
AppendSubServiceNameTo(TxMessage & aTxMessage,Section aSection,SubType & aSubType)2540 void Core::ServiceEntry::AppendSubServiceNameTo(TxMessage &aTxMessage, Section aSection, SubType &aSubType)
2541 {
2542     AppendOutcome outcome;
2543 
2544     outcome = aTxMessage.AppendLabel(aSection, aSubType.mLabel.AsCString(), aSubType.mSubServiceNameOffset);
2545     VerifyOrExit(outcome != kAppendedFullNameAsCompressed);
2546 
2547     AppendSubServiceTypeTo(aTxMessage, aSection);
2548 
2549 exit:
2550     return;
2551 }
2552 
AppendHostNameTo(TxMessage & aTxMessage,Section aSection)2553 void Core::ServiceEntry::AppendHostNameTo(TxMessage &aTxMessage, Section aSection)
2554 {
2555     AppendOutcome outcome;
2556 
2557     outcome = aTxMessage.AppendMultipleLabels(aSection, mHostName.AsCString(), mHostNameOffset);
2558     VerifyOrExit(outcome != kAppendedFullNameAsCompressed);
2559 
2560     aTxMessage.AppendDomainName(aSection);
2561 
2562 exit:
2563     return;
2564 }
2565 
2566 #if OPENTHREAD_CONFIG_MULTICAST_DNS_ENTRY_ITERATION_API_ENABLE
2567 
CopyInfoTo(Service & aService,EntryState & aState,EntryIterator & aIterator) const2568 Error Core::ServiceEntry::CopyInfoTo(Service &aService, EntryState &aState, EntryIterator &aIterator) const
2569 {
2570     Error error = kErrorNone;
2571 
2572     VerifyOrExit(mPtrRecord.IsPresent(), error = kErrorNotFound);
2573 
2574     aIterator.mSubTypeArray.Free();
2575 
2576     for (const SubType &subType : mSubTypes)
2577     {
2578         SuccessOrAssert(aIterator.mSubTypeArray.PushBack(subType.mLabel.AsCString()));
2579     }
2580 
2581     aService.mHostName            = mHostName.AsCString();
2582     aService.mServiceInstance     = mServiceInstance.AsCString();
2583     aService.mServiceType         = mServiceType.AsCString();
2584     aService.mSubTypeLabels       = aIterator.mSubTypeArray.AsCArray();
2585     aService.mSubTypeLabelsLength = aIterator.mSubTypeArray.GetLength();
2586     aService.mTxtData             = mTxtData.GetBytes();
2587     aService.mTxtDataLength       = mTxtData.GetLength();
2588     aService.mPort                = mPort;
2589     aService.mPriority            = mPriority;
2590     aService.mWeight              = mWeight;
2591     aService.mTtl                 = mPtrRecord.GetTtl();
2592     aService.mInfraIfIndex        = Get<Core>().mInfraIfIndex;
2593     aState                        = static_cast<EntryState>(GetState());
2594 
2595 exit:
2596     return error;
2597 }
2598 
CopyInfoTo(Key & aKey,EntryState & aState) const2599 Error Core::ServiceEntry::CopyInfoTo(Key &aKey, EntryState &aState) const
2600 {
2601     Error error;
2602 
2603     SuccessOrExit(error = CopyKeyInfoTo(aKey, aState));
2604 
2605     aKey.mName        = mServiceInstance.AsCString();
2606     aKey.mServiceType = mServiceType.AsCString();
2607 
2608 exit:
2609     return error;
2610 }
2611 
2612 #endif // OPENTHREAD_CONFIG_MULTICAST_DNS_ENTRY_ITERATION_API_ENABLE
2613 
2614 //----------------------------------------------------------------------------------------------------------------------
2615 // Core::ServiceEntry::SubType
2616 
Init(const char * aLabel)2617 Error Core::ServiceEntry::SubType::Init(const char *aLabel)
2618 {
2619     mSubServiceNameOffset = kUnspecifiedOffset;
2620 
2621     return mLabel.Set(aLabel);
2622 }
2623 
Matches(const EmptyChecker & aChecker) const2624 bool Core::ServiceEntry::SubType::Matches(const EmptyChecker &aChecker) const
2625 {
2626     OT_UNUSED_VARIABLE(aChecker);
2627 
2628     return !mPtrRecord.IsPresent();
2629 }
2630 
IsContainedIn(const Service & aService) const2631 bool Core::ServiceEntry::SubType::IsContainedIn(const Service &aService) const
2632 {
2633     bool contains = false;
2634 
2635     for (uint16_t i = 0; i < aService.mSubTypeLabelsLength; i++)
2636     {
2637         if (NameMatch(mLabel, aService.mSubTypeLabels[i]))
2638         {
2639             contains = true;
2640             break;
2641         }
2642     }
2643 
2644     return contains;
2645 }
2646 
2647 //----------------------------------------------------------------------------------------------------------------------
2648 // Core::ServiceType
2649 
Init(Instance & aInstance,const char * aServiceType)2650 Error Core::ServiceType::Init(Instance &aInstance, const char *aServiceType)
2651 {
2652     Error error;
2653 
2654     InstanceLocatorInit::Init(aInstance);
2655 
2656     mNext       = nullptr;
2657     mNumEntries = 0;
2658     SuccessOrExit(error = mServiceType.Set(aServiceType));
2659 
2660     mServicesPtr.UpdateTtl(kServicesPtrTtl);
2661     mServicesPtr.StartAnnouncing();
2662 
2663     mServicesPtr.UpdateFireTimeOn(*this);
2664     ScheduleFireTimeOn(Get<Core>().mEntryTimer);
2665 
2666 exit:
2667     return error;
2668 }
2669 
Matches(const Name & aServiceTypeName) const2670 bool Core::ServiceType::Matches(const Name &aServiceTypeName) const
2671 {
2672     return aServiceTypeName.Matches(/* aFirstLabel */ nullptr, mServiceType.AsCString(), kLocalDomain);
2673 }
2674 
Matches(const Heap::String & aServiceType) const2675 bool Core::ServiceType::Matches(const Heap::String &aServiceType) const
2676 {
2677     return NameMatch(aServiceType, mServiceType);
2678 }
2679 
ClearAppendState(void)2680 void Core::ServiceType::ClearAppendState(void) { mServicesPtr.MarkAsNotAppended(); }
2681 
AnswerQuestion(const AnswerInfo & aInfo)2682 void Core::ServiceType::AnswerQuestion(const AnswerInfo &aInfo)
2683 {
2684     VerifyOrExit(mServicesPtr.CanAnswer());
2685     mServicesPtr.ScheduleAnswer(aInfo);
2686     mServicesPtr.UpdateFireTimeOn(*this);
2687     ScheduleFireTimeOn(Get<Core>().mEntryTimer);
2688 
2689 exit:
2690     return;
2691 }
2692 
ShouldSuppressKnownAnswer(uint32_t aTtl) const2693 bool Core::ServiceType::ShouldSuppressKnownAnswer(uint32_t aTtl) const
2694 {
2695     // Check `aTtl` of a matching record in known-answer section of
2696     // a query with the corresponding PTR record's TTL and suppress
2697     // answer if it is at least at least half the correct value.
2698 
2699     return (aTtl > mServicesPtr.GetTtl() / 2);
2700 }
2701 
HandleTimer(EntryTimerContext & aContext)2702 void Core::ServiceType::HandleTimer(EntryTimerContext &aContext)
2703 {
2704     ClearAppendState();
2705 
2706     VerifyOrExit(HasFireTime());
2707     VerifyOrExit(GetFireTime() <= aContext.GetNow());
2708     ClearFireTime();
2709 
2710     PrepareResponse(aContext.GetResponseMessage(), aContext.GetNow());
2711 
2712     mServicesPtr.UpdateFireTimeOn(*this);
2713 
2714 exit:
2715     UpdateNextFireTimeOn(aContext.GetNextFireTime());
2716 }
2717 
PrepareResponse(TxMessage & aResponse,TimeMilli aNow)2718 void Core::ServiceType::PrepareResponse(TxMessage &aResponse, TimeMilli aNow)
2719 {
2720     bool prepareAgain = false;
2721 
2722     do
2723     {
2724         aResponse.SaveCurrentState();
2725         PrepareResponseRecords(aResponse, aNow);
2726         aResponse.CheckSizeLimitToPrepareAgain(prepareAgain);
2727 
2728     } while (prepareAgain);
2729 
2730     mServicesPtr.UpdateStateAfterAnswer(aResponse);
2731 }
2732 
PrepareResponseRecords(TxMessage & aResponse,TimeMilli aNow)2733 void Core::ServiceType::PrepareResponseRecords(TxMessage &aResponse, TimeMilli aNow)
2734 {
2735     uint16_t serviceTypeOffset = kUnspecifiedOffset;
2736 
2737     VerifyOrExit(mServicesPtr.ShouldAppendTo(aResponse, aNow));
2738 
2739     // Discover compress offset for `mServiceType` if previously
2740     // appended from any `ServiceEntry`.
2741 
2742     for (const ServiceEntry &serviceEntry : Get<Core>().mServiceEntries)
2743     {
2744         if (serviceEntry.GetState() != Entry::kRegistered)
2745         {
2746             continue;
2747         }
2748 
2749         if (NameMatch(mServiceType, serviceEntry.mServiceType))
2750         {
2751             UpdateCompressOffset(serviceTypeOffset, serviceEntry.mServiceTypeOffset);
2752 
2753             if (serviceTypeOffset != kUnspecifiedOffset)
2754             {
2755                 break;
2756             }
2757         }
2758     }
2759 
2760     AppendPtrRecordTo(aResponse, serviceTypeOffset);
2761 
2762 exit:
2763     return;
2764 }
2765 
AppendPtrRecordTo(TxMessage & aResponse,uint16_t aServiceTypeOffset)2766 void Core::ServiceType::AppendPtrRecordTo(TxMessage &aResponse, uint16_t aServiceTypeOffset)
2767 {
2768     Message  *message;
2769     PtrRecord ptr;
2770     uint16_t  offset;
2771 
2772     VerifyOrExit(mServicesPtr.CanAppend());
2773     mServicesPtr.MarkAsAppended(aResponse, kAnswerSection);
2774 
2775     message = &aResponse.SelectMessageFor(kAnswerSection);
2776 
2777     ptr.Init();
2778     ptr.SetTtl(mServicesPtr.GetTtl());
2779 
2780     aResponse.AppendServicesDnssdName(kAnswerSection);
2781     offset = message->GetLength();
2782     SuccessOrAssert(message->Append(ptr));
2783     aResponse.AppendServiceType(kAnswerSection, mServiceType.AsCString(), aServiceTypeOffset);
2784     UpdateRecordLengthInMessage(ptr, *message, offset);
2785 
2786     aResponse.IncrementRecordCount(kAnswerSection);
2787 
2788 exit:
2789     return;
2790 }
2791 
2792 //----------------------------------------------------------------------------------------------------------------------
2793 // Core::TxMessage
2794 
TxMessage(Instance & aInstance,Type aType)2795 Core::TxMessage::TxMessage(Instance &aInstance, Type aType)
2796     : InstanceLocator(aInstance)
2797 {
2798     Init(aType);
2799 }
2800 
TxMessage(Instance & aInstance,Type aType,const AddressInfo & aUnicastDest)2801 Core::TxMessage::TxMessage(Instance &aInstance, Type aType, const AddressInfo &aUnicastDest)
2802     : TxMessage(aInstance, aType)
2803 {
2804     mUnicastDest = aUnicastDest;
2805 }
2806 
Init(Type aType)2807 void Core::TxMessage::Init(Type aType)
2808 {
2809     Header header;
2810 
2811     mRecordCounts.Clear();
2812     mSavedRecordCounts.Clear();
2813     mSavedMsgLength      = 0;
2814     mSavedExtraMsgLength = 0;
2815     mDomainOffset        = kUnspecifiedOffset;
2816     mUdpOffset           = kUnspecifiedOffset;
2817     mTcpOffset           = kUnspecifiedOffset;
2818     mServicesDnssdOffset = kUnspecifiedOffset;
2819     mType                = aType;
2820 
2821     // Allocate messages. The main `mMsgPtr` is always allocated.
2822     // The Authority and Addition section messages are allocated
2823     // the first time they are used.
2824 
2825     mMsgPtr.Reset(Get<MessagePool>().Allocate(Message::kTypeOther));
2826     OT_ASSERT(!mMsgPtr.IsNull());
2827 
2828     mExtraMsgPtr.Reset();
2829 
2830     header.Clear();
2831 
2832     switch (aType)
2833     {
2834     case kMulticastProbe:
2835     case kMulticastQuery:
2836         header.SetType(Header::kTypeQuery);
2837         break;
2838     case kMulticastResponse:
2839     case kUnicastResponse:
2840         header.SetType(Header::kTypeResponse);
2841         break;
2842     }
2843 
2844     SuccessOrAssert(mMsgPtr->Append(header));
2845 }
2846 
SelectMessageFor(Section aSection)2847 Message &Core::TxMessage::SelectMessageFor(Section aSection)
2848 {
2849     // Selects the `Message` to use for a given `aSection` based
2850     // the message type.
2851 
2852     Message *message      = nullptr;
2853     Section  mainSection  = kAnswerSection;
2854     Section  extraSection = kAdditionalDataSection;
2855 
2856     switch (mType)
2857     {
2858     case kMulticastProbe:
2859         mainSection  = kQuestionSection;
2860         extraSection = kAuthoritySection;
2861         break;
2862 
2863     case kMulticastQuery:
2864         mainSection  = kQuestionSection;
2865         extraSection = kAnswerSection;
2866         break;
2867 
2868     case kMulticastResponse:
2869     case kUnicastResponse:
2870         break;
2871     }
2872 
2873     if (aSection == mainSection)
2874     {
2875         message = mMsgPtr.Get();
2876     }
2877     else if (aSection == extraSection)
2878     {
2879         if (mExtraMsgPtr.IsNull())
2880         {
2881             mExtraMsgPtr.Reset(Get<MessagePool>().Allocate(Message::kTypeOther));
2882             OT_ASSERT(!mExtraMsgPtr.IsNull());
2883         }
2884 
2885         message = mExtraMsgPtr.Get();
2886     }
2887 
2888     OT_ASSERT(message != nullptr);
2889 
2890     return *message;
2891 }
2892 
AppendLabel(Section aSection,const char * aLabel,uint16_t & aCompressOffset)2893 Core::AppendOutcome Core::TxMessage::AppendLabel(Section aSection, const char *aLabel, uint16_t &aCompressOffset)
2894 {
2895     return AppendLabels(aSection, aLabel, kIsSingleLabel, aCompressOffset);
2896 }
2897 
AppendMultipleLabels(Section aSection,const char * aLabels,uint16_t & aCompressOffset)2898 Core::AppendOutcome Core::TxMessage::AppendMultipleLabels(Section     aSection,
2899                                                           const char *aLabels,
2900                                                           uint16_t   &aCompressOffset)
2901 {
2902     return AppendLabels(aSection, aLabels, !kIsSingleLabel, aCompressOffset);
2903 }
2904 
AppendLabels(Section aSection,const char * aLabels,bool aIsSingleLabel,uint16_t & aCompressOffset)2905 Core::AppendOutcome Core::TxMessage::AppendLabels(Section     aSection,
2906                                                   const char *aLabels,
2907                                                   bool        aIsSingleLabel,
2908                                                   uint16_t   &aCompressOffset)
2909 {
2910     // Appends DNS name label(s) to the message in the specified section,
2911     // using compression if possible.
2912     //
2913     // - If a valid `aCompressOffset` is given (indicating name was appended before)
2914     //   a compressed pointer label is used, and `kAppendedFullNameAsCompressed`
2915     //   is returned.
2916     // - Otherwise, `aLabels` is appended, `aCompressOffset` is also updated for
2917     //   future compression, and `kAppendedLabels` is returned.
2918     //
2919     // `aIsSingleLabel` indicates that `aLabels` string should be appended
2920     // as a single label. This is useful for service instance label which
2921     // can itself contain the dot `.` character.
2922 
2923     AppendOutcome outcome = kAppendedLabels;
2924     Message      &message = SelectMessageFor(aSection);
2925 
2926     if (aCompressOffset != kUnspecifiedOffset)
2927     {
2928         SuccessOrAssert(Name::AppendPointerLabel(aCompressOffset, message));
2929         outcome = kAppendedFullNameAsCompressed;
2930         ExitNow();
2931     }
2932 
2933     SaveOffset(aCompressOffset, message, aSection);
2934 
2935     if (aIsSingleLabel)
2936     {
2937         SuccessOrAssert(Name::AppendLabel(aLabels, message));
2938     }
2939     else
2940     {
2941         SuccessOrAssert(Name::AppendMultipleLabels(aLabels, message));
2942     }
2943 
2944 exit:
2945     return outcome;
2946 }
2947 
AppendServiceType(Section aSection,const char * aServiceType,uint16_t & aCompressOffset)2948 void Core::TxMessage::AppendServiceType(Section aSection, const char *aServiceType, uint16_t &aCompressOffset)
2949 {
2950     // Appends DNS service type name to the message in the specified
2951     // section, using compression if possible.
2952 
2953     const char   *serviceLabels = aServiceType;
2954     bool          isUdp         = false;
2955     bool          isTcp         = false;
2956     Name::Buffer  labelsBuffer;
2957     AppendOutcome outcome;
2958 
2959     if (Name::ExtractLabels(serviceLabels, kUdpServiceLabel, labelsBuffer) == kErrorNone)
2960     {
2961         isUdp         = true;
2962         serviceLabels = labelsBuffer;
2963     }
2964     else if (Name::ExtractLabels(serviceLabels, kTcpServiceLabel, labelsBuffer) == kErrorNone)
2965     {
2966         isTcp         = true;
2967         serviceLabels = labelsBuffer;
2968     }
2969 
2970     outcome = AppendMultipleLabels(aSection, serviceLabels, aCompressOffset);
2971     VerifyOrExit(outcome != kAppendedFullNameAsCompressed);
2972 
2973     if (isUdp)
2974     {
2975         outcome = AppendLabel(aSection, kUdpServiceLabel, mUdpOffset);
2976     }
2977     else if (isTcp)
2978     {
2979         outcome = AppendLabel(aSection, kTcpServiceLabel, mTcpOffset);
2980     }
2981 
2982     VerifyOrExit(outcome != kAppendedFullNameAsCompressed);
2983 
2984     AppendDomainName(aSection);
2985 
2986 exit:
2987     return;
2988 }
2989 
AppendDomainName(Section aSection)2990 void Core::TxMessage::AppendDomainName(Section aSection)
2991 {
2992     Message &message = SelectMessageFor(aSection);
2993 
2994     if (mDomainOffset != kUnspecifiedOffset)
2995     {
2996         SuccessOrAssert(Name::AppendPointerLabel(mDomainOffset, message));
2997         ExitNow();
2998     }
2999 
3000     SaveOffset(mDomainOffset, message, aSection);
3001     SuccessOrAssert(Name::AppendName(kLocalDomain, message));
3002 
3003 exit:
3004     return;
3005 }
3006 
AppendServicesDnssdName(Section aSection)3007 void Core::TxMessage::AppendServicesDnssdName(Section aSection)
3008 {
3009     Message &message = SelectMessageFor(aSection);
3010 
3011     if (mServicesDnssdOffset != kUnspecifiedOffset)
3012     {
3013         SuccessOrAssert(Name::AppendPointerLabel(mServicesDnssdOffset, message));
3014         ExitNow();
3015     }
3016 
3017     SaveOffset(mServicesDnssdOffset, message, aSection);
3018     SuccessOrAssert(Name::AppendMultipleLabels(kServicesDnssdLabels, message));
3019     AppendDomainName(aSection);
3020 
3021 exit:
3022     return;
3023 }
3024 
SaveOffset(uint16_t & aCompressOffset,const Message & aMessage,Section aSection)3025 void Core::TxMessage::SaveOffset(uint16_t &aCompressOffset, const Message &aMessage, Section aSection)
3026 {
3027     // Saves the current message offset in `aCompressOffset` for name
3028     // compression, but only when appending to the question or answer
3029     // sections.
3030     //
3031     // This is necessary because other sections use separate message,
3032     // and their offsets can shift when records are added to the main
3033     // message.
3034     //
3035     // While current record types guarantee name inclusion in
3036     // question/answer sections before their use in other sections,
3037     // this check allows future extensions.
3038 
3039     switch (aSection)
3040     {
3041     case kQuestionSection:
3042     case kAnswerSection:
3043         aCompressOffset = aMessage.GetLength();
3044         break;
3045 
3046     case kAuthoritySection:
3047     case kAdditionalDataSection:
3048         break;
3049     }
3050 }
3051 
IsOverSizeLimit(void) const3052 bool Core::TxMessage::IsOverSizeLimit(void) const
3053 {
3054     uint32_t size = mMsgPtr->GetLength();
3055 
3056     if (!mExtraMsgPtr.IsNull())
3057     {
3058         size += mExtraMsgPtr->GetLength();
3059     }
3060 
3061     return (size > Get<Core>().mMaxMessageSize);
3062 }
3063 
SaveCurrentState(void)3064 void Core::TxMessage::SaveCurrentState(void)
3065 {
3066     mSavedRecordCounts   = mRecordCounts;
3067     mSavedMsgLength      = mMsgPtr->GetLength();
3068     mSavedExtraMsgLength = mExtraMsgPtr.IsNull() ? 0 : mExtraMsgPtr->GetLength();
3069 }
3070 
RestoreToSavedState(void)3071 void Core::TxMessage::RestoreToSavedState(void)
3072 {
3073     mRecordCounts = mSavedRecordCounts;
3074 
3075     IgnoreError(mMsgPtr->SetLength(mSavedMsgLength));
3076 
3077     if (!mExtraMsgPtr.IsNull())
3078     {
3079         IgnoreError(mExtraMsgPtr->SetLength(mSavedExtraMsgLength));
3080     }
3081 }
3082 
CheckSizeLimitToPrepareAgain(bool & aPrepareAgain)3083 void Core::TxMessage::CheckSizeLimitToPrepareAgain(bool &aPrepareAgain)
3084 {
3085     // Manages message size limits by re-preparing messages when
3086     // necessary:
3087     // - Checks if `TxMessage` exceeds the size limit.
3088     // - If so, restores the `TxMessage` to its previously saved
3089     //   state, sends it, and re-initializes it which will also
3090     //   clear the "AppendState" of the related host and service
3091     //   entries to ensure correct re-processing.
3092     // - Sets `aPrepareAgain` to `true` to signal that records should
3093     //   be prepared and added to the new message.
3094     //
3095     // We allow the `aPrepareAgain` to happen once. The very unlikely
3096     // case where the `Entry` itself has so many records that its
3097     // contents exceed the message size limit, is not handled, i.e.
3098     // we always include all records of a single `Entry` within the same
3099     // message. In future, the code can be updated to allow truncated
3100     // messages.
3101 
3102     if (aPrepareAgain)
3103     {
3104         aPrepareAgain = false;
3105         ExitNow();
3106     }
3107 
3108     VerifyOrExit(IsOverSizeLimit());
3109 
3110     aPrepareAgain = true;
3111 
3112     RestoreToSavedState();
3113     Send();
3114     Reinit();
3115 
3116 exit:
3117     return;
3118 }
3119 
Send(void)3120 void Core::TxMessage::Send(void)
3121 {
3122     static constexpr uint16_t kHeaderOffset = 0;
3123 
3124     Header header;
3125 
3126     VerifyOrExit(!mRecordCounts.IsEmpty());
3127 
3128     SuccessOrAssert(mMsgPtr->Read(kHeaderOffset, header));
3129     mRecordCounts.WriteTo(header);
3130     mMsgPtr->Write(kHeaderOffset, header);
3131 
3132     if (!mExtraMsgPtr.IsNull())
3133     {
3134         SuccessOrAssert(mMsgPtr->AppendBytesFromMessage(*mExtraMsgPtr, 0, mExtraMsgPtr->GetLength()));
3135     }
3136 
3137     Get<Core>().mTxMessageHistory.Add(*mMsgPtr);
3138 
3139     // We pass ownership of message to the platform layer.
3140 
3141     switch (mType)
3142     {
3143     case kMulticastProbe:
3144     case kMulticastQuery:
3145     case kMulticastResponse:
3146         otPlatMdnsSendMulticast(&GetInstance(), mMsgPtr.Release(), Get<Core>().mInfraIfIndex);
3147         break;
3148 
3149     case kUnicastResponse:
3150         otPlatMdnsSendUnicast(&GetInstance(), mMsgPtr.Release(), &mUnicastDest);
3151         break;
3152     }
3153 
3154 exit:
3155     return;
3156 }
3157 
Reinit(void)3158 void Core::TxMessage::Reinit(void)
3159 {
3160     Init(GetType());
3161 
3162     // After re-initializing `TxMessage`, we clear the "AppendState"
3163     // on all related host and service entries, and service types
3164     // or all cache entries (depending on the `GetType()`).
3165 
3166     switch (GetType())
3167     {
3168     case kMulticastProbe:
3169     case kMulticastResponse:
3170     case kUnicastResponse:
3171         for (HostEntry &entry : Get<Core>().mHostEntries)
3172         {
3173             if (ShouldClearAppendStateOnReinit(entry))
3174             {
3175                 entry.ClearAppendState();
3176             }
3177         }
3178 
3179         for (ServiceEntry &entry : Get<Core>().mServiceEntries)
3180         {
3181             if (ShouldClearAppendStateOnReinit(entry))
3182             {
3183                 entry.ClearAppendState();
3184             }
3185         }
3186 
3187         for (ServiceType &serviceType : Get<Core>().mServiceTypes)
3188         {
3189             if ((GetType() == kMulticastResponse) || (GetType() == kUnicastResponse))
3190             {
3191                 serviceType.ClearAppendState();
3192             }
3193         }
3194 
3195         break;
3196 
3197     case kMulticastQuery:
3198 
3199         for (BrowseCache &browseCache : Get<Core>().mBrowseCacheList)
3200         {
3201             browseCache.ClearCompressOffsets();
3202         }
3203 
3204         for (SrvCache &srvCache : Get<Core>().mSrvCacheList)
3205         {
3206             srvCache.ClearCompressOffsets();
3207         }
3208 
3209         for (TxtCache &txtCache : Get<Core>().mTxtCacheList)
3210         {
3211             txtCache.ClearCompressOffsets();
3212         }
3213 
3214         // `Ip6AddrCache` entries do not track any append state or
3215         // compress offset since the host name should not be used
3216         // in any other query question.
3217 
3218         break;
3219     }
3220 }
3221 
ShouldClearAppendStateOnReinit(const Entry & aEntry) const3222 bool Core::TxMessage::ShouldClearAppendStateOnReinit(const Entry &aEntry) const
3223 {
3224     // Determines whether we should clear "append state" on `aEntry`
3225     // when re-initializing the `TxMessage`. If message is a probe, we
3226     // check that entry is in `kProbing` state, if message is a
3227     // unicast/multicast response, we check for `kRegistered` state.
3228 
3229     bool shouldClear = false;
3230 
3231     switch (aEntry.GetState())
3232     {
3233     case Entry::kProbing:
3234         shouldClear = (GetType() == kMulticastProbe);
3235         break;
3236 
3237     case Entry::kRegistered:
3238         shouldClear = (GetType() == kMulticastResponse) || (GetType() == kUnicastResponse);
3239         break;
3240 
3241     case Entry::kConflict:
3242     case Entry::kRemoving:
3243         shouldClear = true;
3244         break;
3245     }
3246 
3247     return shouldClear;
3248 }
3249 
3250 //----------------------------------------------------------------------------------------------------------------------
3251 // Core::EntryTimerContext
3252 
EntryTimerContext(Instance & aInstance)3253 Core::EntryTimerContext::EntryTimerContext(Instance &aInstance)
3254     : InstanceLocator(aInstance)
3255     , mProbeMessage(aInstance, TxMessage::kMulticastProbe)
3256     , mResponseMessage(aInstance, TxMessage::kMulticastResponse)
3257 {
3258 }
3259 
3260 //----------------------------------------------------------------------------------------------------------------------
3261 // Core::RxMessage
3262 
Init(Instance & aInstance,OwnedPtr<Message> & aMessagePtr,bool aIsUnicast,const AddressInfo & aSenderAddress)3263 Error Core::RxMessage::Init(Instance          &aInstance,
3264                             OwnedPtr<Message> &aMessagePtr,
3265                             bool               aIsUnicast,
3266                             const AddressInfo &aSenderAddress)
3267 {
3268     static const Section kSections[] = {kAnswerSection, kAuthoritySection, kAdditionalDataSection};
3269 
3270     Error    error = kErrorNone;
3271     Header   header;
3272     uint16_t offset;
3273     uint16_t numRecords;
3274 
3275     InstanceLocatorInit::Init(aInstance);
3276 
3277     mNext = nullptr;
3278 
3279     VerifyOrExit(!aMessagePtr.IsNull(), error = kErrorInvalidArgs);
3280 
3281     offset = aMessagePtr->GetOffset();
3282 
3283     SuccessOrExit(error = aMessagePtr->Read(offset, header));
3284     offset += sizeof(Header);
3285 
3286     // RFC 6762 Section 18: Query type (OPCODE) must be zero
3287     // (standard query). All other flags must be ignored. Messages
3288     // with non-zero RCODE MUST be silently ignored.
3289 
3290     VerifyOrExit(header.GetQueryType() == Header::kQueryTypeStandard, error = kErrorParse);
3291     VerifyOrExit(header.GetResponseCode() == Header::kResponseSuccess, error = kErrorParse);
3292 
3293     mIsQuery       = (header.GetType() == Header::kTypeQuery);
3294     mIsUnicast     = aIsUnicast;
3295     mTruncated     = header.IsTruncationFlagSet();
3296     mSenderAddress = aSenderAddress;
3297 
3298     if (aSenderAddress.mPort != kUdpPort)
3299     {
3300         if (mIsQuery)
3301         {
3302             // Section 6.7 Legacy Unicast
3303             LogInfo("We do not yet support legacy unicast message (source port not matching mDNS port)");
3304             ExitNow(error = kErrorNotCapable);
3305         }
3306         else
3307         {
3308             // The source port in a response MUST be mDNS port.
3309             // Otherwise response message MUST be silently ignored.
3310 
3311             ExitNow(error = kErrorParse);
3312         }
3313     }
3314 
3315     if (mIsUnicast && mIsQuery)
3316     {
3317         // Direct Unicast Queries to Port 5353 (RFC 6762 - section 5.5).
3318         // Responders SHOULD check that the source address in the query
3319         // packet matches the local subnet for that link and silently ignore
3320         // the packet if not.
3321 
3322         LogInfo("We do not yet support unicast query to mDNS port");
3323         ExitNow(error = kErrorNotCapable);
3324     }
3325 
3326     mRecordCounts.ReadFrom(header);
3327 
3328     // Parse questions
3329 
3330     mStartOffset[kQuestionSection] = offset;
3331 
3332     SuccessOrAssert(mQuestions.ReserveCapacity(mRecordCounts.GetFor(kQuestionSection)));
3333 
3334     for (numRecords = mRecordCounts.GetFor(kQuestionSection); numRecords > 0; numRecords--)
3335     {
3336         Question         *question = mQuestions.PushBack();
3337         ot::Dns::Question record;
3338         uint16_t          rrClass;
3339 
3340         OT_ASSERT(question != nullptr);
3341 
3342         question->mNameOffset = offset;
3343 
3344         SuccessOrExit(error = Name::ParseName(*aMessagePtr, offset));
3345         SuccessOrExit(error = aMessagePtr->Read(offset, record));
3346         offset += sizeof(record);
3347 
3348         question->mRrType = record.GetType();
3349 
3350         rrClass                      = record.GetClass();
3351         question->mUnicastResponse   = rrClass & kClassQuestionUnicastFlag;
3352         question->mIsRrClassInternet = RrClassIsInternetOrAny(rrClass);
3353     }
3354 
3355     // Parse and validate records in Answer, Authority and Additional
3356     // Data sections.
3357 
3358     for (Section section : kSections)
3359     {
3360         mStartOffset[section] = offset;
3361         SuccessOrExit(error = ResourceRecord::ParseRecords(*aMessagePtr, offset, mRecordCounts.GetFor(section)));
3362     }
3363 
3364     // Determine which questions are probes by searching in the
3365     // Authority section for records matching the question name.
3366 
3367     for (Question &question : mQuestions)
3368     {
3369         Name name(*aMessagePtr, question.mNameOffset);
3370 
3371         offset     = mStartOffset[kAuthoritySection];
3372         numRecords = mRecordCounts.GetFor(kAuthoritySection);
3373 
3374         if (ResourceRecord::FindRecord(*aMessagePtr, offset, numRecords, name) == kErrorNone)
3375         {
3376             question.mIsProbe = true;
3377         }
3378     }
3379 
3380     mIsSelfOriginating = Get<Core>().mTxMessageHistory.Contains(*aMessagePtr);
3381 
3382     mMessagePtr = aMessagePtr.PassOwnership();
3383 
3384 exit:
3385     if (error != kErrorNone)
3386     {
3387         LogInfo("Failed to parse message from %s, error:%s", aSenderAddress.GetAddress().ToString().AsCString(),
3388                 ErrorToString(error));
3389     }
3390 
3391     return error;
3392 }
3393 
ClearProcessState(void)3394 void Core::RxMessage::ClearProcessState(void)
3395 {
3396     for (Question &question : mQuestions)
3397     {
3398         question.ClearProcessState();
3399     }
3400 }
3401 
ProcessQuery(bool aShouldProcessTruncated)3402 Core::RxMessage::ProcessOutcome Core::RxMessage::ProcessQuery(bool aShouldProcessTruncated)
3403 {
3404     ProcessOutcome outcome             = kProcessed;
3405     bool           shouldDelay         = false;
3406     bool           canAnswer           = false;
3407     bool           needUnicastResponse = false;
3408     TimeMilli      answerTime;
3409 
3410     for (Question &question : mQuestions)
3411     {
3412         question.ClearProcessState();
3413 
3414         ProcessQuestion(question);
3415 
3416         // Check if we can answer every question in the query and all
3417         // answers are for unique records (where we own the name). This
3418         // determines whether we need to add any random delay before
3419         // responding.
3420 
3421         if (!question.mCanAnswer || !question.mIsUnique)
3422         {
3423             shouldDelay = true;
3424         }
3425 
3426         if (question.mCanAnswer)
3427         {
3428             canAnswer = true;
3429 
3430             if (question.mUnicastResponse)
3431             {
3432                 needUnicastResponse = true;
3433             }
3434         }
3435     }
3436 
3437     VerifyOrExit(canAnswer);
3438 
3439     if (mTruncated && !aShouldProcessTruncated)
3440     {
3441         outcome = kSaveAsMultiPacket;
3442         ExitNow();
3443     }
3444 
3445     answerTime = TimerMilli::GetNow();
3446 
3447     if (shouldDelay)
3448     {
3449         answerTime += Random::NonCrypto::GetUint32InRange(kMinResponseDelay, kMaxResponseDelay);
3450     }
3451 
3452     for (const Question &question : mQuestions)
3453     {
3454         AnswerQuestion(question, answerTime);
3455     }
3456 
3457     if (needUnicastResponse)
3458     {
3459         SendUnicastResponse(mSenderAddress);
3460     }
3461 
3462 exit:
3463     return outcome;
3464 }
3465 
ProcessQuestion(Question & aQuestion)3466 void Core::RxMessage::ProcessQuestion(Question &aQuestion)
3467 {
3468     Name name(*mMessagePtr, aQuestion.mNameOffset);
3469 
3470     VerifyOrExit(aQuestion.mIsRrClassInternet);
3471 
3472     // Check if question name matches "_services._dns-sd._udp" (all services)
3473 
3474     if (name.Matches(/* aFirstLabel */ nullptr, kServicesDnssdLabels, kLocalDomain))
3475     {
3476         VerifyOrExit(QuestionMatches(aQuestion.mRrType, ResourceRecord::kTypePtr));
3477         VerifyOrExit(!Get<Core>().mServiceTypes.IsEmpty());
3478 
3479         aQuestion.mCanAnswer             = true;
3480         aQuestion.mIsForAllServicesDnssd = true;
3481 
3482         ExitNow();
3483     }
3484 
3485     // Check if question name matches a `HostEntry` or a `ServiceEntry`
3486 
3487     aQuestion.mEntry = Get<Core>().mHostEntries.FindMatching(name);
3488 
3489     if (aQuestion.mEntry == nullptr)
3490     {
3491         aQuestion.mEntry        = Get<Core>().mServiceEntries.FindMatching(name);
3492         aQuestion.mIsForService = (aQuestion.mEntry != nullptr);
3493     }
3494 
3495     if (aQuestion.mEntry != nullptr)
3496     {
3497         switch (aQuestion.mEntry->GetState())
3498         {
3499         case Entry::kProbing:
3500             if (aQuestion.mIsProbe)
3501             {
3502                 // Handling probe conflicts deviates from RFC 6762.
3503                 // We allow the conflict to happen and report it
3504                 // let the caller handle it. In future, TSR can
3505                 // help select the winner.
3506             }
3507             break;
3508 
3509         case Entry::kRegistered:
3510             aQuestion.mCanAnswer = true;
3511             aQuestion.mIsUnique  = true;
3512             break;
3513 
3514         case Entry::kConflict:
3515         case Entry::kRemoving:
3516             break;
3517         }
3518     }
3519     else
3520     {
3521         // Check if question matches a service type or sub-type. We
3522         // can answer PTR or ANY questions. There may be multiple
3523         // service entries matching the question. We find and save
3524         // the first match. `AnswerServiceTypeQuestion()` will start
3525         // from the saved entry and finds all the other matches.
3526 
3527         bool              isSubType;
3528         Name::LabelBuffer subLabel;
3529         Name              baseType;
3530 
3531         VerifyOrExit(QuestionMatches(aQuestion.mRrType, ResourceRecord::kTypePtr));
3532 
3533         isSubType = ParseQuestionNameAsSubType(aQuestion, subLabel, baseType);
3534 
3535         if (!isSubType)
3536         {
3537             baseType = name;
3538         }
3539 
3540         for (ServiceEntry &serviceEntry : Get<Core>().mServiceEntries)
3541         {
3542             if ((serviceEntry.GetState() != Entry::kRegistered) || !serviceEntry.MatchesServiceType(baseType))
3543             {
3544                 continue;
3545             }
3546 
3547             if (isSubType && !serviceEntry.CanAnswerSubType(subLabel))
3548             {
3549                 continue;
3550             }
3551 
3552             aQuestion.mCanAnswer     = true;
3553             aQuestion.mEntry         = &serviceEntry;
3554             aQuestion.mIsForService  = true;
3555             aQuestion.mIsServiceType = true;
3556             ExitNow();
3557         }
3558     }
3559 
3560 exit:
3561     return;
3562 }
3563 
AnswerQuestion(const Question & aQuestion,TimeMilli aAnswerTime)3564 void Core::RxMessage::AnswerQuestion(const Question &aQuestion, TimeMilli aAnswerTime)
3565 {
3566     HostEntry    *hostEntry;
3567     ServiceEntry *serviceEntry;
3568     AnswerInfo    answerInfo;
3569 
3570     VerifyOrExit(aQuestion.mCanAnswer);
3571 
3572     answerInfo.mQuestionRrType  = aQuestion.mRrType;
3573     answerInfo.mAnswerTime      = aAnswerTime;
3574     answerInfo.mIsProbe         = aQuestion.mIsProbe;
3575     answerInfo.mUnicastResponse = aQuestion.mUnicastResponse;
3576 
3577     if (aQuestion.mIsForAllServicesDnssd)
3578     {
3579         AnswerAllServicesQuestion(aQuestion, answerInfo);
3580         ExitNow();
3581     }
3582 
3583     hostEntry    = aQuestion.mIsForService ? nullptr : static_cast<HostEntry *>(aQuestion.mEntry);
3584     serviceEntry = aQuestion.mIsForService ? static_cast<ServiceEntry *>(aQuestion.mEntry) : nullptr;
3585 
3586     if (hostEntry != nullptr)
3587     {
3588         hostEntry->AnswerQuestion(answerInfo);
3589         ExitNow();
3590     }
3591 
3592     // Question is for `ServiceEntry`
3593 
3594     VerifyOrExit(serviceEntry != nullptr);
3595 
3596     if (!aQuestion.mIsServiceType)
3597     {
3598         serviceEntry->AnswerServiceNameQuestion(answerInfo);
3599     }
3600     else
3601     {
3602         AnswerServiceTypeQuestion(aQuestion, answerInfo, *serviceEntry);
3603     }
3604 
3605 exit:
3606     return;
3607 }
3608 
AnswerServiceTypeQuestion(const Question & aQuestion,const AnswerInfo & aInfo,ServiceEntry & aFirstEntry)3609 void Core::RxMessage::AnswerServiceTypeQuestion(const Question   &aQuestion,
3610                                                 const AnswerInfo &aInfo,
3611                                                 ServiceEntry     &aFirstEntry)
3612 {
3613     Name              serviceType(*mMessagePtr, aQuestion.mNameOffset);
3614     Name              baseType;
3615     Name::LabelBuffer labelBuffer;
3616     const char       *subLabel;
3617 
3618     if (ParseQuestionNameAsSubType(aQuestion, labelBuffer, baseType))
3619     {
3620         subLabel = labelBuffer;
3621     }
3622     else
3623     {
3624         baseType = serviceType;
3625         subLabel = nullptr;
3626     }
3627 
3628     for (ServiceEntry *serviceEntry = &aFirstEntry; serviceEntry != nullptr; serviceEntry = serviceEntry->GetNext())
3629     {
3630         bool shouldSuppress = false;
3631 
3632         if ((serviceEntry->GetState() != Entry::kRegistered) || !serviceEntry->MatchesServiceType(baseType))
3633         {
3634             continue;
3635         }
3636 
3637         if ((subLabel != nullptr) && !serviceEntry->CanAnswerSubType(subLabel))
3638         {
3639             continue;
3640         }
3641 
3642         // Check for known-answer in this `RxMessage` and all its
3643         // related messages in case it is multi-packet query.
3644 
3645         for (const RxMessage *rxMessage = this; rxMessage != nullptr; rxMessage = rxMessage->GetNext())
3646         {
3647             if (rxMessage->ShouldSuppressKnownAnswer(serviceType, subLabel, *serviceEntry))
3648             {
3649                 shouldSuppress = true;
3650                 break;
3651             }
3652         }
3653 
3654         if (!shouldSuppress)
3655         {
3656             serviceEntry->AnswerServiceTypeQuestion(aInfo, subLabel);
3657         }
3658     }
3659 }
3660 
ShouldSuppressKnownAnswer(const Name & aServiceType,const char * aSubLabel,const ServiceEntry & aServiceEntry) const3661 bool Core::RxMessage::ShouldSuppressKnownAnswer(const Name         &aServiceType,
3662                                                 const char         *aSubLabel,
3663                                                 const ServiceEntry &aServiceEntry) const
3664 {
3665     bool     shouldSuppress = false;
3666     uint16_t offset         = mStartOffset[kAnswerSection];
3667     uint16_t numRecords     = mRecordCounts.GetFor(kAnswerSection);
3668 
3669     while (ResourceRecord::FindRecord(*mMessagePtr, offset, numRecords, aServiceType) == kErrorNone)
3670     {
3671         Error     error;
3672         PtrRecord ptr;
3673 
3674         error = ResourceRecord::ReadRecord(*mMessagePtr, offset, ptr);
3675 
3676         if (error == kErrorNotFound)
3677         {
3678             // `ReadRecord()` will update the `offset` to skip over
3679             // the entire record if it does not match the expected
3680             // record type (PTR in this case).
3681             continue;
3682         }
3683 
3684         SuccessOrExit(error);
3685 
3686         // `offset` is now pointing to PTR name
3687 
3688         if (aServiceEntry.Matches(Name(*mMessagePtr, offset)))
3689         {
3690             shouldSuppress = aServiceEntry.ShouldSuppressKnownAnswer(ptr.GetTtl(), aSubLabel);
3691             ExitNow();
3692         }
3693 
3694         // Parse the name and skip over it and update `offset`
3695         // to the start of the next record.
3696 
3697         SuccessOrExit(Name::ParseName(*mMessagePtr, offset));
3698     }
3699 
3700 exit:
3701     return shouldSuppress;
3702 }
3703 
ParseQuestionNameAsSubType(const Question & aQuestion,Name::LabelBuffer & aSubLabel,Name & aServiceType) const3704 bool Core::RxMessage::ParseQuestionNameAsSubType(const Question    &aQuestion,
3705                                                  Name::LabelBuffer &aSubLabel,
3706                                                  Name              &aServiceType) const
3707 {
3708     bool     isSubType = false;
3709     uint16_t offset    = aQuestion.mNameOffset;
3710     uint8_t  length    = sizeof(aSubLabel);
3711 
3712     SuccessOrExit(Name::ReadLabel(*mMessagePtr, offset, aSubLabel, length));
3713     SuccessOrExit(Name::CompareLabel(*mMessagePtr, offset, kSubServiceLabel));
3714     aServiceType.SetFromMessage(*mMessagePtr, offset);
3715     isSubType = true;
3716 
3717 exit:
3718     return isSubType;
3719 }
3720 
AnswerAllServicesQuestion(const Question & aQuestion,const AnswerInfo & aInfo)3721 void Core::RxMessage::AnswerAllServicesQuestion(const Question &aQuestion, const AnswerInfo &aInfo)
3722 {
3723     for (ServiceType &serviceType : Get<Core>().mServiceTypes)
3724     {
3725         bool shouldSuppress = false;
3726 
3727         // Check for known-answer in this `RxMessage` and all its
3728         // related messages in case it is multi-packet query.
3729 
3730         for (const RxMessage *rxMessage = this; rxMessage != nullptr; rxMessage = rxMessage->GetNext())
3731         {
3732             if (rxMessage->ShouldSuppressKnownAnswer(aQuestion, serviceType))
3733             {
3734                 shouldSuppress = true;
3735                 break;
3736             }
3737         }
3738 
3739         if (!shouldSuppress)
3740         {
3741             serviceType.AnswerQuestion(aInfo);
3742         }
3743     }
3744 }
3745 
ShouldSuppressKnownAnswer(const Question & aQuestion,const ServiceType & aServiceType) const3746 bool Core::RxMessage::ShouldSuppressKnownAnswer(const Question &aQuestion, const ServiceType &aServiceType) const
3747 {
3748     // Check answer section to determine whether to suppress answering
3749     // to "_services._dns-sd._udp" query with `aServiceType`
3750 
3751     bool     shouldSuppress = false;
3752     uint16_t offset         = mStartOffset[kAnswerSection];
3753     uint16_t numRecords     = mRecordCounts.GetFor(kAnswerSection);
3754     Name     name(*mMessagePtr, aQuestion.mNameOffset);
3755 
3756     while (ResourceRecord::FindRecord(*mMessagePtr, offset, numRecords, name) == kErrorNone)
3757     {
3758         Error     error;
3759         PtrRecord ptr;
3760 
3761         error = ResourceRecord::ReadRecord(*mMessagePtr, offset, ptr);
3762 
3763         if (error == kErrorNotFound)
3764         {
3765             // `ReadRecord()` will update the `offset` to skip over
3766             // the entire record if it does not match the expected
3767             // record type (PTR in this case).
3768             continue;
3769         }
3770 
3771         SuccessOrExit(error);
3772 
3773         // `offset` is now pointing to PTR name
3774 
3775         if (aServiceType.Matches(Name(*mMessagePtr, offset)))
3776         {
3777             shouldSuppress = aServiceType.ShouldSuppressKnownAnswer(ptr.GetTtl());
3778             ExitNow();
3779         }
3780 
3781         // Parse the name and skip over it and update `offset`
3782         // to the start of the next record.
3783 
3784         SuccessOrExit(Name::ParseName(*mMessagePtr, offset));
3785     }
3786 
3787 exit:
3788     return shouldSuppress;
3789 }
3790 
SendUnicastResponse(const AddressInfo & aUnicastDest)3791 void Core::RxMessage::SendUnicastResponse(const AddressInfo &aUnicastDest)
3792 {
3793     TxMessage response(GetInstance(), TxMessage::kUnicastResponse, aUnicastDest);
3794     TimeMilli now = TimerMilli::GetNow();
3795 
3796     for (HostEntry &entry : Get<Core>().mHostEntries)
3797     {
3798         entry.ClearAppendState();
3799         entry.PrepareResponse(response, now);
3800     }
3801 
3802     for (ServiceEntry &entry : Get<Core>().mServiceEntries)
3803     {
3804         entry.ClearAppendState();
3805         entry.PrepareResponse(response, now);
3806     }
3807 
3808     for (ServiceType &serviceType : Get<Core>().mServiceTypes)
3809     {
3810         serviceType.ClearAppendState();
3811         serviceType.PrepareResponse(response, now);
3812     }
3813 
3814     response.Send();
3815 }
3816 
ProcessResponse(void)3817 void Core::RxMessage::ProcessResponse(void)
3818 {
3819     if (!IsSelfOriginating())
3820     {
3821         IterateOnAllRecordsInResponse(&RxMessage::ProcessRecordForConflict);
3822     }
3823 
3824     // We process record types in a specific order to ensure correct
3825     // passive cache creation: First PTR records are processed, which
3826     // may create passive SRV/TXT cache entries for discovered
3827     // services. Next SRV records are processed which may create TXT
3828     // cache entries for service names and IPv6 address cache entries
3829     // for associated host name.
3830 
3831     if (!Get<Core>().mBrowseCacheList.IsEmpty())
3832     {
3833         IterateOnAllRecordsInResponse(&RxMessage::ProcessPtrRecord);
3834     }
3835 
3836     if (!Get<Core>().mSrvCacheList.IsEmpty())
3837     {
3838         IterateOnAllRecordsInResponse(&RxMessage::ProcessSrvRecord);
3839     }
3840 
3841     if (!Get<Core>().mTxtCacheList.IsEmpty())
3842     {
3843         IterateOnAllRecordsInResponse(&RxMessage::ProcessTxtRecord);
3844     }
3845 
3846     if (!Get<Core>().mIp6AddrCacheList.IsEmpty())
3847     {
3848         IterateOnAllRecordsInResponse(&RxMessage::ProcessAaaaRecord);
3849 
3850         for (Ip6AddrCache &addrCache : Get<Core>().mIp6AddrCacheList)
3851         {
3852             addrCache.CommitNewResponseEntries();
3853         }
3854     }
3855 
3856     if (!Get<Core>().mIp4AddrCacheList.IsEmpty())
3857     {
3858         IterateOnAllRecordsInResponse(&RxMessage::ProcessARecord);
3859 
3860         for (Ip4AddrCache &addrCache : Get<Core>().mIp4AddrCacheList)
3861         {
3862             addrCache.CommitNewResponseEntries();
3863         }
3864     }
3865 }
3866 
IterateOnAllRecordsInResponse(RecordProcessor aRecordProcessor)3867 void Core::RxMessage::IterateOnAllRecordsInResponse(RecordProcessor aRecordProcessor)
3868 {
3869     // Iterates over all records in the response, calling
3870     // `aRecordProcessor` for each.
3871 
3872     static const Section kSections[] = {kAnswerSection, kAdditionalDataSection};
3873 
3874     for (Section section : kSections)
3875     {
3876         uint16_t offset = mStartOffset[section];
3877 
3878         for (uint16_t numRecords = mRecordCounts.GetFor(section); numRecords > 0; numRecords--)
3879         {
3880             Name           name(*mMessagePtr, offset);
3881             ResourceRecord record;
3882 
3883             IgnoreError(Name::ParseName(*mMessagePtr, offset));
3884             IgnoreError(mMessagePtr->Read(offset, record));
3885 
3886             if (!RrClassIsInternetOrAny(record.GetClass()))
3887             {
3888                 continue;
3889             }
3890 
3891             (this->*aRecordProcessor)(name, record, offset);
3892 
3893             offset += static_cast<uint16_t>(record.GetSize());
3894         }
3895     }
3896 }
3897 
ProcessRecordForConflict(const Name & aName,const ResourceRecord & aRecord,uint16_t aRecordOffset)3898 void Core::RxMessage::ProcessRecordForConflict(const Name &aName, const ResourceRecord &aRecord, uint16_t aRecordOffset)
3899 {
3900     HostEntry    *hostEntry;
3901     ServiceEntry *serviceEntry;
3902 
3903     VerifyOrExit(aRecord.GetTtl() > 0);
3904 
3905     hostEntry = Get<Core>().mHostEntries.FindMatching(aName);
3906 
3907     if (hostEntry != nullptr)
3908     {
3909         hostEntry->HandleConflict();
3910     }
3911 
3912     serviceEntry = Get<Core>().mServiceEntries.FindMatching(aName);
3913 
3914     if (serviceEntry != nullptr)
3915     {
3916         serviceEntry->HandleConflict();
3917     }
3918 
3919 exit:
3920     OT_UNUSED_VARIABLE(aRecordOffset);
3921 }
3922 
ProcessPtrRecord(const Name & aName,const ResourceRecord & aRecord,uint16_t aRecordOffset)3923 void Core::RxMessage::ProcessPtrRecord(const Name &aName, const ResourceRecord &aRecord, uint16_t aRecordOffset)
3924 {
3925     BrowseCache *browseCache;
3926 
3927     VerifyOrExit(aRecord.GetType() == ResourceRecord::kTypePtr);
3928 
3929     browseCache = Get<Core>().mBrowseCacheList.FindMatching(aName);
3930     VerifyOrExit(browseCache != nullptr);
3931 
3932     browseCache->ProcessResponseRecord(*mMessagePtr, aRecordOffset);
3933 
3934 exit:
3935     return;
3936 }
3937 
ProcessSrvRecord(const Name & aName,const ResourceRecord & aRecord,uint16_t aRecordOffset)3938 void Core::RxMessage::ProcessSrvRecord(const Name &aName, const ResourceRecord &aRecord, uint16_t aRecordOffset)
3939 {
3940     SrvCache *srvCache;
3941 
3942     VerifyOrExit(aRecord.GetType() == ResourceRecord::kTypeSrv);
3943 
3944     srvCache = Get<Core>().mSrvCacheList.FindMatching(aName);
3945     VerifyOrExit(srvCache != nullptr);
3946 
3947     srvCache->ProcessResponseRecord(*mMessagePtr, aRecordOffset);
3948 
3949 exit:
3950     return;
3951 }
3952 
ProcessTxtRecord(const Name & aName,const ResourceRecord & aRecord,uint16_t aRecordOffset)3953 void Core::RxMessage::ProcessTxtRecord(const Name &aName, const ResourceRecord &aRecord, uint16_t aRecordOffset)
3954 {
3955     TxtCache *txtCache;
3956 
3957     VerifyOrExit(aRecord.GetType() == ResourceRecord::kTypeTxt);
3958 
3959     txtCache = Get<Core>().mTxtCacheList.FindMatching(aName);
3960     VerifyOrExit(txtCache != nullptr);
3961 
3962     txtCache->ProcessResponseRecord(*mMessagePtr, aRecordOffset);
3963 
3964 exit:
3965     return;
3966 }
3967 
ProcessAaaaRecord(const Name & aName,const ResourceRecord & aRecord,uint16_t aRecordOffset)3968 void Core::RxMessage::ProcessAaaaRecord(const Name &aName, const ResourceRecord &aRecord, uint16_t aRecordOffset)
3969 {
3970     Ip6AddrCache *ip6AddrCache;
3971 
3972     VerifyOrExit(aRecord.GetType() == ResourceRecord::kTypeAaaa);
3973 
3974     ip6AddrCache = Get<Core>().mIp6AddrCacheList.FindMatching(aName);
3975     VerifyOrExit(ip6AddrCache != nullptr);
3976 
3977     ip6AddrCache->ProcessResponseRecord(*mMessagePtr, aRecordOffset);
3978 
3979 exit:
3980     return;
3981 }
3982 
ProcessARecord(const Name & aName,const ResourceRecord & aRecord,uint16_t aRecordOffset)3983 void Core::RxMessage::ProcessARecord(const Name &aName, const ResourceRecord &aRecord, uint16_t aRecordOffset)
3984 {
3985     Ip4AddrCache *ip4AddrCache;
3986 
3987     VerifyOrExit(aRecord.GetType() == ResourceRecord::kTypeA);
3988 
3989     ip4AddrCache = Get<Core>().mIp4AddrCacheList.FindMatching(aName);
3990     VerifyOrExit(ip4AddrCache != nullptr);
3991 
3992     ip4AddrCache->ProcessResponseRecord(*mMessagePtr, aRecordOffset);
3993 
3994 exit:
3995     return;
3996 }
3997 
3998 //---------------------------------------------------------------------------------------------------------------------
3999 // Core::RxMessage::Question
4000 
ClearProcessState(void)4001 void Core::RxMessage::Question::ClearProcessState(void)
4002 {
4003     mCanAnswer             = false;
4004     mIsUnique              = false;
4005     mIsForService          = false;
4006     mIsServiceType         = false;
4007     mIsForAllServicesDnssd = false;
4008     mEntry                 = nullptr;
4009 }
4010 
4011 //---------------------------------------------------------------------------------------------------------------------
4012 // Core::MultiPacketRxMessages
4013 
MultiPacketRxMessages(Instance & aInstance)4014 Core::MultiPacketRxMessages::MultiPacketRxMessages(Instance &aInstance)
4015     : InstanceLocator(aInstance)
4016     , mTimer(aInstance)
4017 {
4018 }
4019 
AddToExisting(OwnedPtr<RxMessage> & aRxMessagePtr)4020 void Core::MultiPacketRxMessages::AddToExisting(OwnedPtr<RxMessage> &aRxMessagePtr)
4021 {
4022     RxMsgEntry *msgEntry = mRxMsgEntries.FindMatching(aRxMessagePtr->GetSenderAddress());
4023 
4024     VerifyOrExit(msgEntry != nullptr);
4025     msgEntry->Add(aRxMessagePtr);
4026 
4027 exit:
4028     return;
4029 }
4030 
AddNew(OwnedPtr<RxMessage> & aRxMessagePtr)4031 void Core::MultiPacketRxMessages::AddNew(OwnedPtr<RxMessage> &aRxMessagePtr)
4032 {
4033     RxMsgEntry *newEntry = RxMsgEntry::Allocate(GetInstance());
4034 
4035     OT_ASSERT(newEntry != nullptr);
4036     newEntry->Add(aRxMessagePtr);
4037 
4038     // First remove an existing entries matching same sender
4039     // before adding the new entry to the list.
4040 
4041     mRxMsgEntries.RemoveMatching(aRxMessagePtr->GetSenderAddress());
4042     mRxMsgEntries.Push(*newEntry);
4043 }
4044 
HandleTimer(void)4045 void Core::MultiPacketRxMessages::HandleTimer(void)
4046 {
4047     TimeMilli              now      = TimerMilli::GetNow();
4048     TimeMilli              nextTime = now.GetDistantFuture();
4049     OwningList<RxMsgEntry> expiredEntries;
4050 
4051     mRxMsgEntries.RemoveAllMatching(ExpireChecker(now), expiredEntries);
4052 
4053     for (RxMsgEntry &expiredEntry : expiredEntries)
4054     {
4055         expiredEntry.mRxMessages.GetHead()->ProcessQuery(/* aShouldProcessTruncated */ true);
4056     }
4057 
4058     for (const RxMsgEntry &msgEntry : mRxMsgEntries)
4059     {
4060         nextTime = Min(nextTime, msgEntry.mProcessTime);
4061     }
4062 
4063     if (nextTime != now.GetDistantFuture())
4064     {
4065         mTimer.FireAtIfEarlier(nextTime);
4066     }
4067 }
4068 
Clear(void)4069 void Core::MultiPacketRxMessages::Clear(void)
4070 {
4071     mTimer.Stop();
4072     mRxMsgEntries.Clear();
4073 }
4074 
4075 //---------------------------------------------------------------------------------------------------------------------
4076 // Core::MultiPacketRxMessage::RxMsgEntry
4077 
RxMsgEntry(Instance & aInstance)4078 Core::MultiPacketRxMessages::RxMsgEntry::RxMsgEntry(Instance &aInstance)
4079     : InstanceLocator(aInstance)
4080     , mNext(nullptr)
4081 {
4082 }
4083 
Matches(const AddressInfo & aAddress) const4084 bool Core::MultiPacketRxMessages::RxMsgEntry::Matches(const AddressInfo &aAddress) const
4085 {
4086     bool matches = false;
4087 
4088     VerifyOrExit(!mRxMessages.IsEmpty());
4089     matches = (mRxMessages.GetHead()->GetSenderAddress() == aAddress);
4090 
4091 exit:
4092     return matches;
4093 }
4094 
Matches(const ExpireChecker & aExpireChecker) const4095 bool Core::MultiPacketRxMessages::RxMsgEntry::Matches(const ExpireChecker &aExpireChecker) const
4096 {
4097     return (mProcessTime <= aExpireChecker.mNow);
4098 }
4099 
Add(OwnedPtr<RxMessage> & aRxMessagePtr)4100 void Core::MultiPacketRxMessages::RxMsgEntry::Add(OwnedPtr<RxMessage> &aRxMessagePtr)
4101 {
4102     uint16_t numMsgs = 0;
4103 
4104     for (const RxMessage &rxMsg : mRxMessages)
4105     {
4106         // If a subsequent received `RxMessage` is also marked as
4107         // truncated, we again delay the process time. To avoid
4108         // continuous delay and piling up of messages in the list,
4109         // we limit the number of messages.
4110 
4111         numMsgs++;
4112         VerifyOrExit(numMsgs < kMaxNumMessages);
4113 
4114         OT_UNUSED_VARIABLE(rxMsg);
4115     }
4116 
4117     mProcessTime = TimerMilli::GetNow();
4118 
4119     if (aRxMessagePtr->IsTruncated())
4120     {
4121         mProcessTime += Random::NonCrypto::GetUint32InRange(kMinProcessDelay, kMaxProcessDelay);
4122     }
4123 
4124     // We push the new `RxMessage` at tail of the list to keep the
4125     // first query containing questions at the head of the list.
4126 
4127     mRxMessages.PushAfterTail(*aRxMessagePtr.Release());
4128 
4129     Get<Core>().mMultiPacketRxMessages.mTimer.FireAtIfEarlier(mProcessTime);
4130 
4131 exit:
4132     return;
4133 }
4134 
4135 //---------------------------------------------------------------------------------------------------------------------
4136 // Core::TxMessageHistory
4137 
TxMessageHistory(Instance & aInstance)4138 Core::TxMessageHistory::TxMessageHistory(Instance &aInstance)
4139     : InstanceLocator(aInstance)
4140     , mTimer(aInstance)
4141 {
4142 }
4143 
Clear(void)4144 void Core::TxMessageHistory::Clear(void)
4145 {
4146     mHashEntries.Clear();
4147     mTimer.Stop();
4148 }
4149 
Add(const Message & aMessage)4150 void Core::TxMessageHistory::Add(const Message &aMessage)
4151 {
4152     Hash       hash;
4153     HashEntry *entry;
4154 
4155     CalculateHash(aMessage, hash);
4156 
4157     entry = mHashEntries.FindMatching(hash);
4158 
4159     if (entry == nullptr)
4160     {
4161         entry = HashEntry::Allocate();
4162         OT_ASSERT(entry != nullptr);
4163         entry->mHash = hash;
4164         mHashEntries.Push(*entry);
4165     }
4166 
4167     entry->mExpireTime = TimerMilli::GetNow() + kExpireInterval;
4168     mTimer.FireAtIfEarlier(entry->mExpireTime);
4169 }
4170 
Contains(const Message & aMessage) const4171 bool Core::TxMessageHistory::Contains(const Message &aMessage) const
4172 {
4173     Hash hash;
4174 
4175     CalculateHash(aMessage, hash);
4176     return mHashEntries.ContainsMatching(hash);
4177 }
4178 
CalculateHash(const Message & aMessage,Hash & aHash)4179 void Core::TxMessageHistory::CalculateHash(const Message &aMessage, Hash &aHash)
4180 {
4181     Crypto::Sha256 sha256;
4182 
4183     sha256.Start();
4184     sha256.Update(aMessage, /* aOffset */ 0, aMessage.GetLength());
4185     sha256.Finish(aHash);
4186 }
4187 
HandleTimer(void)4188 void Core::TxMessageHistory::HandleTimer(void)
4189 {
4190     TimeMilli now      = TimerMilli::GetNow();
4191     TimeMilli nextTime = now.GetDistantFuture();
4192 
4193     mHashEntries.RemoveAndFreeAllMatching(ExpireChecker(now));
4194 
4195     for (const HashEntry &entry : mHashEntries)
4196     {
4197         nextTime = Min(nextTime, entry.mExpireTime);
4198     }
4199 
4200     if (nextTime != now.GetDistantFuture())
4201     {
4202         mTimer.FireAtIfEarlier(nextTime);
4203     }
4204 }
4205 
4206 template <typename CacheType, typename BrowserResolverType>
Start(const BrowserResolverType & aBrowserOrResolver)4207 Error Core::Start(const BrowserResolverType &aBrowserOrResolver)
4208 {
4209     Error      error = kErrorNone;
4210     CacheType *cacheEntry;
4211 
4212     VerifyOrExit(mIsEnabled, error = kErrorInvalidState);
4213     VerifyOrExit(aBrowserOrResolver.mCallback != nullptr, error = kErrorInvalidArgs);
4214 
4215     cacheEntry = GetCacheList<CacheType>().FindMatching(aBrowserOrResolver);
4216 
4217     if (cacheEntry == nullptr)
4218     {
4219         cacheEntry = CacheType::AllocateAndInit(GetInstance(), aBrowserOrResolver);
4220         OT_ASSERT(cacheEntry != nullptr);
4221 
4222         GetCacheList<CacheType>().Push(*cacheEntry);
4223     }
4224 
4225     error = cacheEntry->Add(aBrowserOrResolver);
4226 
4227 exit:
4228     return error;
4229 }
4230 
4231 template <typename CacheType, typename BrowserResolverType>
Stop(const BrowserResolverType & aBrowserOrResolver)4232 Error Core::Stop(const BrowserResolverType &aBrowserOrResolver)
4233 {
4234     Error      error = kErrorNone;
4235     CacheType *cacheEntry;
4236 
4237     VerifyOrExit(mIsEnabled, error = kErrorInvalidState);
4238     VerifyOrExit(aBrowserOrResolver.mCallback != nullptr, error = kErrorInvalidArgs);
4239 
4240     cacheEntry = GetCacheList<CacheType>().FindMatching(aBrowserOrResolver);
4241     VerifyOrExit(cacheEntry != nullptr);
4242 
4243     cacheEntry->Remove(aBrowserOrResolver);
4244 
4245 exit:
4246     return error;
4247 }
4248 
StartBrowser(const Browser & aBrowser)4249 Error Core::StartBrowser(const Browser &aBrowser) { return Start<BrowseCache, Browser>(aBrowser); }
4250 
StopBrowser(const Browser & aBrowser)4251 Error Core::StopBrowser(const Browser &aBrowser) { return Stop<BrowseCache, Browser>(aBrowser); }
4252 
StartSrvResolver(const SrvResolver & aResolver)4253 Error Core::StartSrvResolver(const SrvResolver &aResolver) { return Start<SrvCache, SrvResolver>(aResolver); }
4254 
StopSrvResolver(const SrvResolver & aResolver)4255 Error Core::StopSrvResolver(const SrvResolver &aResolver) { return Stop<SrvCache, SrvResolver>(aResolver); }
4256 
StartTxtResolver(const TxtResolver & aResolver)4257 Error Core::StartTxtResolver(const TxtResolver &aResolver) { return Start<TxtCache, TxtResolver>(aResolver); }
4258 
StopTxtResolver(const TxtResolver & aResolver)4259 Error Core::StopTxtResolver(const TxtResolver &aResolver) { return Stop<TxtCache, TxtResolver>(aResolver); }
4260 
StartIp6AddressResolver(const AddressResolver & aResolver)4261 Error Core::StartIp6AddressResolver(const AddressResolver &aResolver)
4262 {
4263     return Start<Ip6AddrCache, AddressResolver>(aResolver);
4264 }
4265 
StopIp6AddressResolver(const AddressResolver & aResolver)4266 Error Core::StopIp6AddressResolver(const AddressResolver &aResolver)
4267 {
4268     return Stop<Ip6AddrCache, AddressResolver>(aResolver);
4269 }
4270 
StartIp4AddressResolver(const AddressResolver & aResolver)4271 Error Core::StartIp4AddressResolver(const AddressResolver &aResolver)
4272 {
4273     return Start<Ip4AddrCache, AddressResolver>(aResolver);
4274 }
4275 
StopIp4AddressResolver(const AddressResolver & aResolver)4276 Error Core::StopIp4AddressResolver(const AddressResolver &aResolver)
4277 {
4278     return Stop<Ip4AddrCache, AddressResolver>(aResolver);
4279 }
4280 
AddPassiveSrvTxtCache(const char * aServiceInstance,const char * aServiceType)4281 void Core::AddPassiveSrvTxtCache(const char *aServiceInstance, const char *aServiceType)
4282 {
4283     ServiceName serviceName(aServiceInstance, aServiceType);
4284 
4285     if (!mSrvCacheList.ContainsMatching(serviceName))
4286     {
4287         SrvCache *srvCache = SrvCache::AllocateAndInit(GetInstance(), serviceName);
4288 
4289         OT_ASSERT(srvCache != nullptr);
4290         mSrvCacheList.Push(*srvCache);
4291     }
4292 
4293     if (!mTxtCacheList.ContainsMatching(serviceName))
4294     {
4295         TxtCache *txtCache = TxtCache::AllocateAndInit(GetInstance(), serviceName);
4296 
4297         OT_ASSERT(txtCache != nullptr);
4298         mTxtCacheList.Push(*txtCache);
4299     }
4300 }
4301 
AddPassiveIp6AddrCache(const char * aHostName)4302 void Core::AddPassiveIp6AddrCache(const char *aHostName)
4303 {
4304     if (!mIp6AddrCacheList.ContainsMatching(aHostName))
4305     {
4306         Ip6AddrCache *ip6AddrCache = Ip6AddrCache::AllocateAndInit(GetInstance(), aHostName);
4307 
4308         OT_ASSERT(ip6AddrCache != nullptr);
4309         mIp6AddrCacheList.Push(*ip6AddrCache);
4310     }
4311 }
4312 
HandleCacheTimer(void)4313 void Core::HandleCacheTimer(void)
4314 {
4315     CacheTimerContext context(GetInstance());
4316     ExpireChecker     expireChecker(context.GetNow());
4317 
4318     // First remove all expired entries.
4319 
4320     mBrowseCacheList.RemoveAndFreeAllMatching(expireChecker);
4321     mSrvCacheList.RemoveAndFreeAllMatching(expireChecker);
4322     mTxtCacheList.RemoveAndFreeAllMatching(expireChecker);
4323     mIp6AddrCacheList.RemoveAndFreeAllMatching(expireChecker);
4324     mIp4AddrCacheList.RemoveAndFreeAllMatching(expireChecker);
4325 
4326     // Process cache types in a specific order to optimize name
4327     // compression when constructing query messages.
4328 
4329     for (SrvCache &srvCache : mSrvCacheList)
4330     {
4331         srvCache.HandleTimer(context);
4332     }
4333 
4334     for (TxtCache &txtCache : mTxtCacheList)
4335     {
4336         txtCache.HandleTimer(context);
4337     }
4338 
4339     for (BrowseCache &browseCache : mBrowseCacheList)
4340     {
4341         browseCache.HandleTimer(context);
4342     }
4343 
4344     for (Ip6AddrCache &addrCache : mIp6AddrCacheList)
4345     {
4346         addrCache.HandleTimer(context);
4347     }
4348 
4349     for (Ip4AddrCache &addrCache : mIp4AddrCacheList)
4350     {
4351         addrCache.HandleTimer(context);
4352     }
4353 
4354     context.GetQueryMessage().Send();
4355 
4356     mCacheTimer.FireAtIfEarlier(context.GetNextFireTime());
4357 }
4358 
HandleCacheTask(void)4359 void Core::HandleCacheTask(void)
4360 {
4361     // `CacheTask` is used to remove empty/null callbacks
4362     // from cache entries. and also removing "passive"
4363     // cache entries that timed out.
4364 
4365     for (BrowseCache &browseCache : mBrowseCacheList)
4366     {
4367         browseCache.ClearEmptyCallbacks();
4368     }
4369 
4370     for (SrvCache &srvCache : mSrvCacheList)
4371     {
4372         srvCache.ClearEmptyCallbacks();
4373     }
4374 
4375     for (TxtCache &txtCache : mTxtCacheList)
4376     {
4377         txtCache.ClearEmptyCallbacks();
4378     }
4379 
4380     for (Ip6AddrCache &addrCache : mIp6AddrCacheList)
4381     {
4382         addrCache.ClearEmptyCallbacks();
4383     }
4384 
4385     for (Ip4AddrCache &addrCache : mIp4AddrCacheList)
4386     {
4387         addrCache.ClearEmptyCallbacks();
4388     }
4389 }
4390 
RandomizeFirstProbeTxTime(void)4391 TimeMilli Core::RandomizeFirstProbeTxTime(void)
4392 {
4393     // Randomizes the transmission time of the first probe, adding a
4394     // delay between 20-250 msec. Subsequent probes within a short
4395     // window reuse the same delay for efficient aggregation.
4396 
4397     TimeMilli now = TimerMilli::GetNow();
4398 
4399     // The comparison using `(mNextProbeTxTime - now)` will work
4400     // correctly even in the unlikely case that `now` has wrapped
4401     // (49 days has passed) since `mNextProbeTxTime` was last set.
4402 
4403     if ((mNextProbeTxTime - now) >= kMaxProbeDelay)
4404     {
4405         mNextProbeTxTime = now + Random::NonCrypto::GetUint32InRange(kMinProbeDelay, kMaxProbeDelay);
4406     }
4407 
4408     return mNextProbeTxTime;
4409 }
4410 
RandomizeInitialQueryTxTime(void)4411 TimeMilli Core::RandomizeInitialQueryTxTime(void)
4412 {
4413     TimeMilli now = TimerMilli::GetNow();
4414 
4415     if ((mNextQueryTxTime - now) >= kMaxInitialQueryDelay)
4416     {
4417         mNextQueryTxTime = now + Random::NonCrypto::GetUint32InRange(kMinInitialQueryDelay, kMaxInitialQueryDelay);
4418     }
4419 
4420     return mNextQueryTxTime;
4421 }
4422 
4423 //---------------------------------------------------------------------------------------------------------------------
4424 // Core::ResultCallback
4425 
Invoke(Instance & aInstance,const BrowseResult & aResult) const4426 void Core::ResultCallback::Invoke(Instance &aInstance, const BrowseResult &aResult) const
4427 {
4428     if (mSharedCallback.mBrowse != nullptr)
4429     {
4430         mSharedCallback.mBrowse(&aInstance, &aResult);
4431     }
4432 }
4433 
Invoke(Instance & aInstance,const SrvResult & aResult) const4434 void Core::ResultCallback::Invoke(Instance &aInstance, const SrvResult &aResult) const
4435 {
4436     if (mSharedCallback.mSrv != nullptr)
4437     {
4438         mSharedCallback.mSrv(&aInstance, &aResult);
4439     }
4440 }
4441 
Invoke(Instance & aInstance,const TxtResult & aResult) const4442 void Core::ResultCallback::Invoke(Instance &aInstance, const TxtResult &aResult) const
4443 {
4444     if (mSharedCallback.mTxt != nullptr)
4445     {
4446         mSharedCallback.mTxt(&aInstance, &aResult);
4447     }
4448 }
4449 
Invoke(Instance & aInstance,const AddressResult & aResult) const4450 void Core::ResultCallback::Invoke(Instance &aInstance, const AddressResult &aResult) const
4451 {
4452     if (mSharedCallback.mAddress != nullptr)
4453     {
4454         mSharedCallback.mAddress(&aInstance, &aResult);
4455     }
4456 }
4457 
4458 //---------------------------------------------------------------------------------------------------------------------
4459 // Core::CacheTimerContext
4460 
CacheTimerContext(Instance & aInstance)4461 Core::CacheTimerContext::CacheTimerContext(Instance &aInstance)
4462     : InstanceLocator(aInstance)
4463     , mQueryMessage(aInstance, TxMessage::kMulticastQuery)
4464 {
4465 }
4466 
4467 //---------------------------------------------------------------------------------------------------------------------
4468 // Core::CacheRecordInfo
4469 
CacheRecordInfo(void)4470 Core::CacheRecordInfo::CacheRecordInfo(void)
4471     : mTtl(0)
4472     , mQueryCount(0)
4473 {
4474 }
4475 
RefreshTtl(uint32_t aTtl)4476 bool Core::CacheRecordInfo::RefreshTtl(uint32_t aTtl)
4477 {
4478     // Called when cached record is refreshed.
4479     // Returns a boolean to indicate if TTL value
4480     // was changed or not.
4481 
4482     bool changed = (aTtl != mTtl);
4483 
4484     mLastRxTime = TimerMilli::GetNow();
4485     mTtl        = aTtl;
4486     mQueryCount = 0;
4487 
4488     return changed;
4489 }
4490 
ShouldExpire(TimeMilli aNow) const4491 bool Core::CacheRecordInfo::ShouldExpire(TimeMilli aNow) const { return IsPresent() && (GetExpireTime() <= aNow); }
4492 
UpdateStateAfterQuery(TimeMilli aNow)4493 void Core::CacheRecordInfo::UpdateStateAfterQuery(TimeMilli aNow)
4494 {
4495     VerifyOrExit(IsPresent());
4496 
4497     // If the less than half TTL remains, then this record would not
4498     // be included as "Known-Answer" in the send query, so we can
4499     // count it towards queries to refresh this record.
4500 
4501     VerifyOrExit(LessThanHalfTtlRemains(aNow));
4502 
4503     if (mQueryCount < kNumberOfQueries)
4504     {
4505         mQueryCount++;
4506     }
4507 
4508 exit:
4509     return;
4510 }
4511 
UpdateQueryAndFireTimeOn(CacheEntry & aCacheEntry)4512 void Core::CacheRecordInfo::UpdateQueryAndFireTimeOn(CacheEntry &aCacheEntry)
4513 {
4514     TimeMilli now;
4515     TimeMilli expireTime;
4516 
4517     VerifyOrExit(IsPresent());
4518 
4519     now        = TimerMilli::GetNow();
4520     expireTime = GetExpireTime();
4521 
4522     aCacheEntry.SetFireTime(expireTime);
4523 
4524     // Determine next query time
4525 
4526     for (uint8_t attemptIndex = mQueryCount; attemptIndex < kNumberOfQueries; attemptIndex++)
4527     {
4528         TimeMilli queryTime = GetQueryTime(attemptIndex);
4529 
4530         if (queryTime > now)
4531         {
4532             queryTime += Random::NonCrypto::GetUint32InRange(0, GetClampedTtl() * kQueryTtlVariation);
4533             aCacheEntry.ScheduleQuery(queryTime);
4534             break;
4535         }
4536     }
4537 
4538 exit:
4539     return;
4540 }
4541 
LessThanHalfTtlRemains(TimeMilli aNow) const4542 bool Core::CacheRecordInfo::LessThanHalfTtlRemains(TimeMilli aNow) const
4543 {
4544     return IsPresent() && ((aNow - mLastRxTime) > TimeMilli::SecToMsec(GetClampedTtl()) / 2);
4545 }
4546 
GetRemainingTtl(TimeMilli aNow) const4547 uint32_t Core::CacheRecordInfo::GetRemainingTtl(TimeMilli aNow) const
4548 {
4549     uint32_t  remainingTtl = 0;
4550     TimeMilli expireTime;
4551 
4552     VerifyOrExit(IsPresent());
4553 
4554     expireTime = GetExpireTime();
4555     VerifyOrExit(aNow < expireTime);
4556 
4557     remainingTtl = TimeMilli::MsecToSec(expireTime - aNow);
4558 
4559 exit:
4560     return remainingTtl;
4561 }
4562 
GetClampedTtl(void) const4563 uint32_t Core::CacheRecordInfo::GetClampedTtl(void) const
4564 {
4565     // We clamp TTL to `kMaxTtl` (one day) to prevent `TimeMilli`
4566     // calculation overflow.
4567 
4568     return Min(mTtl, kMaxTtl);
4569 }
4570 
GetExpireTime(void) const4571 TimeMilli Core::CacheRecordInfo::GetExpireTime(void) const
4572 {
4573     return mLastRxTime + TimeMilli::SecToMsec(GetClampedTtl());
4574 }
4575 
GetQueryTime(uint8_t aAttemptIndex) const4576 TimeMilli Core::CacheRecordInfo::GetQueryTime(uint8_t aAttemptIndex) const
4577 {
4578     // Queries are sent at 80%, 85%, 90% and 95% of TTL plus a random
4579     // variation of 2% (added when sceduling)
4580 
4581     static const uint32_t kTtlFactors[kNumberOfQueries] = {
4582         80 * 1000 / 100,
4583         85 * 1000 / 100,
4584         90 * 1000 / 100,
4585         95 * 1000 / 100,
4586     };
4587 
4588     OT_ASSERT(aAttemptIndex < kNumberOfQueries);
4589 
4590     return mLastRxTime + kTtlFactors[aAttemptIndex] * GetClampedTtl();
4591 }
4592 
4593 //---------------------------------------------------------------------------------------------------------------------
4594 // Core::CacheEntry
4595 
Init(Instance & aInstance,Type aType)4596 void Core::CacheEntry::Init(Instance &aInstance, Type aType)
4597 {
4598     InstanceLocatorInit::Init(aInstance);
4599 
4600     mType               = aType;
4601     mInitalQueries      = 0;
4602     mQueryPending       = false;
4603     mLastQueryTimeValid = false;
4604     mIsActive           = false;
4605     mDeleteTime         = TimerMilli::GetNow() + kNonActiveDeleteTimeout;
4606 }
4607 
SetIsActive(bool aIsActive)4608 void Core::CacheEntry::SetIsActive(bool aIsActive)
4609 {
4610     // Sets the active/passive state of a cache entry. An entry is
4611     // considered "active" when associated with at least one
4612     // resolver/browser. "Passive" entries (without a resolver/browser)
4613     // continue to process mDNS responses for updates but will not send
4614     // queries. Passive entries are deleted after `kNonActiveDeleteTimeout`
4615     // if no resolver/browser is added.
4616 
4617     mIsActive = aIsActive;
4618 
4619     if (!mIsActive)
4620     {
4621         mQueryPending = false;
4622         mDeleteTime   = TimerMilli::GetNow() + kNonActiveDeleteTimeout;
4623         SetFireTime(mDeleteTime);
4624     }
4625 }
4626 
ShouldDelete(TimeMilli aNow) const4627 bool Core::CacheEntry::ShouldDelete(TimeMilli aNow) const { return !mIsActive && (mDeleteTime <= aNow); }
4628 
StartInitialQueries(void)4629 void Core::CacheEntry::StartInitialQueries(void)
4630 {
4631     mInitalQueries      = 0;
4632     mLastQueryTimeValid = false;
4633     mLastQueryTime      = Get<Core>().RandomizeInitialQueryTxTime();
4634 
4635     ScheduleQuery(mLastQueryTime);
4636 }
4637 
ShouldQuery(TimeMilli aNow)4638 bool Core::CacheEntry::ShouldQuery(TimeMilli aNow) { return mQueryPending && (mNextQueryTime <= aNow); }
4639 
ScheduleQuery(TimeMilli aQueryTime)4640 void Core::CacheEntry::ScheduleQuery(TimeMilli aQueryTime)
4641 {
4642     VerifyOrExit(mIsActive);
4643 
4644     if (mQueryPending)
4645     {
4646         VerifyOrExit(aQueryTime < mNextQueryTime);
4647     }
4648 
4649     if (mLastQueryTimeValid)
4650     {
4651         aQueryTime = Max(aQueryTime, mLastQueryTime + kMinIntervalBetweenQueries);
4652     }
4653 
4654     mQueryPending  = true;
4655     mNextQueryTime = aQueryTime;
4656     SetFireTime(mNextQueryTime);
4657 
4658 exit:
4659     return;
4660 }
4661 
Add(const ResultCallback & aCallback)4662 Error Core::CacheEntry::Add(const ResultCallback &aCallback)
4663 {
4664     Error           error = kErrorNone;
4665     bool            isFirst;
4666     ResultCallback *callback;
4667 
4668     callback = FindCallbackMatching(aCallback);
4669     VerifyOrExit(callback == nullptr, error = kErrorAlready);
4670 
4671     isFirst = mCallbacks.IsEmpty();
4672 
4673     callback = ResultCallback::Allocate(aCallback);
4674     OT_ASSERT(callback != nullptr);
4675 
4676     mCallbacks.Push(*callback);
4677 
4678     // If this is the first active resolver/browser for this cache
4679     // entry, we mark it as active which allows queries to be sent We
4680     // decide whether or not to send initial queries (e.g., for
4681     // SRV/TXT cache entries we send initial queries if there is no
4682     // record, or less than half TTL remains).
4683 
4684     if (isFirst)
4685     {
4686         bool shouldStart = false;
4687 
4688         SetIsActive(true);
4689 
4690         switch (mType)
4691         {
4692         case kBrowseCache:
4693             shouldStart = true;
4694             break;
4695         case kSrvCache:
4696         case kTxtCache:
4697             shouldStart = As<ServiceCache>().ShouldStartInitialQueries();
4698             break;
4699         case kIp6AddrCache:
4700         case kIp4AddrCache:
4701             shouldStart = As<AddrCache>().ShouldStartInitialQueries();
4702             break;
4703         }
4704 
4705         if (shouldStart)
4706         {
4707             StartInitialQueries();
4708         }
4709 
4710         DetermineNextFireTime();
4711         ScheduleTimer();
4712     }
4713 
4714     // Report any discovered/cached result to the newly added
4715     // callback.
4716 
4717     switch (mType)
4718     {
4719     case kBrowseCache:
4720         As<BrowseCache>().ReportResultsTo(*callback);
4721         break;
4722     case kSrvCache:
4723         As<SrvCache>().ReportResultTo(*callback);
4724         break;
4725     case kTxtCache:
4726         As<TxtCache>().ReportResultTo(*callback);
4727         break;
4728     case kIp6AddrCache:
4729     case kIp4AddrCache:
4730         As<AddrCache>().ReportResultsTo(*callback);
4731         break;
4732     }
4733 
4734 exit:
4735     return error;
4736 }
4737 
Remove(const ResultCallback & aCallback)4738 void Core::CacheEntry::Remove(const ResultCallback &aCallback)
4739 {
4740     ResultCallback *callback = FindCallbackMatching(aCallback);
4741 
4742     VerifyOrExit(callback != nullptr);
4743 
4744     // We clear the callback setting it to `nullptr` without removing
4745     // it from the list here, since the `Remove()` method may be
4746     // called from a callback while we are iterating over the list.
4747     // Removal from the list is deferred to `mCacheTask` which will
4748     // later call `ClearEmptyCallbacks()`.
4749 
4750     callback->ClearCallback();
4751     Get<Core>().mCacheTask.Post();
4752 
4753 exit:
4754     return;
4755 }
4756 
ClearEmptyCallbacks(void)4757 void Core::CacheEntry::ClearEmptyCallbacks(void)
4758 {
4759     mCallbacks.RemoveAndFreeAllMatching(EmptyChecker());
4760 
4761     if (mCallbacks.IsEmpty())
4762     {
4763         SetIsActive(false);
4764         DetermineNextFireTime();
4765         ScheduleTimer();
4766     }
4767 }
4768 
HandleTimer(CacheTimerContext & aContext)4769 void Core::CacheEntry::HandleTimer(CacheTimerContext &aContext)
4770 {
4771     switch (mType)
4772     {
4773     case kBrowseCache:
4774         As<BrowseCache>().ClearCompressOffsets();
4775         break;
4776 
4777     case kSrvCache:
4778     case kTxtCache:
4779         As<ServiceCache>().ClearCompressOffsets();
4780         break;
4781 
4782     case kIp6AddrCache:
4783     case kIp4AddrCache:
4784         // `AddrCache` entries do not track any append state or
4785         // compress offset since the host name would not be used
4786         // in any other query question.
4787         break;
4788     }
4789 
4790     VerifyOrExit(HasFireTime());
4791     VerifyOrExit(GetFireTime() <= aContext.GetNow());
4792     ClearFireTime();
4793 
4794     if (ShouldDelete(aContext.GetNow()))
4795     {
4796         ExitNow();
4797     }
4798 
4799     if (ShouldQuery(aContext.GetNow()))
4800     {
4801         mQueryPending = false;
4802         PrepareQuery(aContext);
4803     }
4804 
4805     switch (mType)
4806     {
4807     case kBrowseCache:
4808         As<BrowseCache>().ProcessExpiredRecords(aContext.GetNow());
4809         break;
4810     case kSrvCache:
4811         As<SrvCache>().ProcessExpiredRecords(aContext.GetNow());
4812         break;
4813     case kTxtCache:
4814         As<TxtCache>().ProcessExpiredRecords(aContext.GetNow());
4815         break;
4816     case kIp6AddrCache:
4817     case kIp4AddrCache:
4818         As<AddrCache>().ProcessExpiredRecords(aContext.GetNow());
4819         break;
4820     }
4821 
4822     DetermineNextFireTime();
4823 
4824 exit:
4825     UpdateNextFireTimeOn(aContext.GetNextFireTime());
4826 }
4827 
FindCallbackMatching(const ResultCallback & aCallback)4828 Core::ResultCallback *Core::CacheEntry::FindCallbackMatching(const ResultCallback &aCallback)
4829 {
4830     ResultCallback *callback = nullptr;
4831 
4832     switch (mType)
4833     {
4834     case kBrowseCache:
4835         callback = mCallbacks.FindMatching(aCallback.mSharedCallback.mBrowse);
4836         break;
4837     case kSrvCache:
4838         callback = mCallbacks.FindMatching(aCallback.mSharedCallback.mSrv);
4839         break;
4840     case kTxtCache:
4841         callback = mCallbacks.FindMatching(aCallback.mSharedCallback.mTxt);
4842         break;
4843     case kIp6AddrCache:
4844     case kIp4AddrCache:
4845         callback = mCallbacks.FindMatching(aCallback.mSharedCallback.mAddress);
4846         break;
4847     }
4848 
4849     return callback;
4850 }
4851 
DetermineNextFireTime(void)4852 void Core::CacheEntry::DetermineNextFireTime(void)
4853 {
4854     mQueryPending = false;
4855 
4856     if (mInitalQueries < kNumberOfInitalQueries)
4857     {
4858         uint32_t interval = (mInitalQueries == 0) ? 0 : (1U << (mInitalQueries - 1)) * kInitialQueryInterval;
4859 
4860         ScheduleQuery(mLastQueryTime + interval);
4861     }
4862 
4863     if (!mIsActive)
4864     {
4865         SetFireTime(mDeleteTime);
4866     }
4867 
4868     // Let each cache entry type schedule query and
4869     // fire times based on the state of its discovered
4870     // records.
4871 
4872     switch (mType)
4873     {
4874     case kBrowseCache:
4875         As<BrowseCache>().DetermineRecordFireTime();
4876         break;
4877     case kSrvCache:
4878     case kTxtCache:
4879         As<ServiceCache>().DetermineRecordFireTime();
4880         break;
4881     case kIp6AddrCache:
4882     case kIp4AddrCache:
4883         As<AddrCache>().DetermineRecordFireTime();
4884         break;
4885     }
4886 }
4887 
ScheduleTimer(void)4888 void Core::CacheEntry::ScheduleTimer(void) { ScheduleFireTimeOn(Get<Core>().mCacheTimer); }
4889 
PrepareQuery(CacheTimerContext & aContext)4890 void Core::CacheEntry::PrepareQuery(CacheTimerContext &aContext)
4891 {
4892     bool prepareAgain = false;
4893 
4894     do
4895     {
4896         TxMessage &query = aContext.GetQueryMessage();
4897 
4898         query.SaveCurrentState();
4899 
4900         switch (mType)
4901         {
4902         case kBrowseCache:
4903             As<BrowseCache>().PreparePtrQuestion(query, aContext.GetNow());
4904             break;
4905         case kSrvCache:
4906             As<SrvCache>().PrepareSrvQuestion(query);
4907             break;
4908         case kTxtCache:
4909             As<TxtCache>().PrepareTxtQuestion(query);
4910             break;
4911         case kIp6AddrCache:
4912             As<Ip6AddrCache>().PrepareAaaaQuestion(query);
4913             break;
4914         case kIp4AddrCache:
4915             As<Ip4AddrCache>().PrepareAQuestion(query);
4916             break;
4917         }
4918 
4919         query.CheckSizeLimitToPrepareAgain(prepareAgain);
4920 
4921     } while (prepareAgain);
4922 
4923     mLastQueryTimeValid = true;
4924     mLastQueryTime      = aContext.GetNow();
4925 
4926     if (mInitalQueries < kNumberOfInitalQueries)
4927     {
4928         mInitalQueries++;
4929     }
4930 
4931     // Let the cache entry super-classes update their state
4932     // after query was sent.
4933 
4934     switch (mType)
4935     {
4936     case kBrowseCache:
4937         As<BrowseCache>().UpdateRecordStateAfterQuery(aContext.GetNow());
4938         break;
4939     case kSrvCache:
4940     case kTxtCache:
4941         As<ServiceCache>().UpdateRecordStateAfterQuery(aContext.GetNow());
4942         break;
4943     case kIp6AddrCache:
4944     case kIp4AddrCache:
4945         As<AddrCache>().UpdateRecordStateAfterQuery(aContext.GetNow());
4946         break;
4947     }
4948 }
4949 
InvokeCallbacks(const ResultType & aResult)4950 template <typename ResultType> void Core::CacheEntry::InvokeCallbacks(const ResultType &aResult)
4951 {
4952     for (const ResultCallback &callback : mCallbacks)
4953     {
4954         callback.Invoke(GetInstance(), aResult);
4955     }
4956 }
4957 
4958 //---------------------------------------------------------------------------------------------------------------------
4959 // Core::BrowseCache
4960 
Init(Instance & aInstance,const char * aServiceType,const char * aSubTypeLabel)4961 Error Core::BrowseCache::Init(Instance &aInstance, const char *aServiceType, const char *aSubTypeLabel)
4962 {
4963     Error error = kErrorNone;
4964 
4965     CacheEntry::Init(aInstance, kBrowseCache);
4966     mNext = nullptr;
4967 
4968     ClearCompressOffsets();
4969     SuccessOrExit(error = mServiceType.Set(aServiceType));
4970     SuccessOrExit(error = mSubTypeLabel.Set(aSubTypeLabel));
4971 
4972 exit:
4973     return error;
4974 }
4975 
Init(Instance & aInstance,const Browser & aBrowser)4976 Error Core::BrowseCache::Init(Instance &aInstance, const Browser &aBrowser)
4977 {
4978     return Init(aInstance, aBrowser.mServiceType, aBrowser.mSubTypeLabel);
4979 }
4980 
ClearCompressOffsets(void)4981 void Core::BrowseCache::ClearCompressOffsets(void)
4982 {
4983     mServiceTypeOffset    = kUnspecifiedOffset;
4984     mSubServiceTypeOffset = kUnspecifiedOffset;
4985     mSubServiceNameOffset = kUnspecifiedOffset;
4986 }
4987 
Matches(const Name & aFullName) const4988 bool Core::BrowseCache::Matches(const Name &aFullName) const
4989 {
4990     bool matches   = false;
4991     bool isSubType = !mSubTypeLabel.IsNull();
4992     Name name      = aFullName;
4993 
4994     OT_ASSERT(name.IsFromMessage());
4995 
4996     if (isSubType)
4997     {
4998         uint16_t       offset;
4999         const Message &message = name.GetAsMessage(offset);
5000 
5001         SuccessOrExit(Name::CompareLabel(message, offset, mSubTypeLabel.AsCString()));
5002         name.SetFromMessage(message, offset);
5003     }
5004 
5005     matches = name.Matches(isSubType ? kSubServiceLabel : nullptr, mServiceType.AsCString(), kLocalDomain);
5006 
5007 exit:
5008     return matches;
5009 }
5010 
Matches(const char * aServiceType,const char * aSubTypeLabel) const5011 bool Core::BrowseCache::Matches(const char *aServiceType, const char *aSubTypeLabel) const
5012 {
5013     bool matches = false;
5014 
5015     if (aSubTypeLabel == nullptr)
5016     {
5017         VerifyOrExit(mSubTypeLabel.IsNull());
5018     }
5019     else
5020     {
5021         VerifyOrExit(NameMatch(mSubTypeLabel, aSubTypeLabel));
5022     }
5023 
5024     matches = NameMatch(mServiceType, aServiceType);
5025 
5026 exit:
5027     return matches;
5028 }
5029 
Matches(const Browser & aBrowser) const5030 bool Core::BrowseCache::Matches(const Browser &aBrowser) const
5031 {
5032     return Matches(aBrowser.mServiceType, aBrowser.mSubTypeLabel);
5033 }
5034 
Matches(const ExpireChecker & aExpireChecker) const5035 bool Core::BrowseCache::Matches(const ExpireChecker &aExpireChecker) const { return ShouldDelete(aExpireChecker.mNow); }
5036 
Add(const Browser & aBrowser)5037 Error Core::BrowseCache::Add(const Browser &aBrowser) { return CacheEntry::Add(ResultCallback(aBrowser.mCallback)); }
5038 
Remove(const Browser & aBrowser)5039 void Core::BrowseCache::Remove(const Browser &aBrowser) { CacheEntry::Remove(ResultCallback(aBrowser.mCallback)); }
5040 
ProcessResponseRecord(const Message & aMessage,uint16_t aRecordOffset)5041 void Core::BrowseCache::ProcessResponseRecord(const Message &aMessage, uint16_t aRecordOffset)
5042 {
5043     // Name and record type in `aMessage` are already matched.
5044 
5045     uint16_t     offset = aRecordOffset;
5046     PtrRecord    ptr;
5047     Name::Buffer fullServiceType;
5048     Name::Buffer serviceInstance;
5049     BrowseResult result;
5050     PtrEntry    *ptrEntry;
5051     bool         changed = false;
5052 
5053     // Read the PTR record. `ReadPtrName()` validates that
5054     // PTR record is well-formed.
5055 
5056     SuccessOrExit(aMessage.Read(offset, ptr));
5057     offset += sizeof(ptr);
5058     SuccessOrExit(ptr.ReadPtrName(aMessage, offset, serviceInstance, fullServiceType));
5059 
5060     VerifyOrExit(Name(fullServiceType).Matches(nullptr, mServiceType.AsCString(), kLocalDomain));
5061 
5062     ptrEntry = mPtrEntries.FindMatching(serviceInstance);
5063 
5064     if (ptr.GetTtl() == 0)
5065     {
5066         VerifyOrExit((ptrEntry != nullptr) && ptrEntry->mRecord.IsPresent());
5067 
5068         ptrEntry->mRecord.RefreshTtl(0);
5069         changed = true;
5070     }
5071     else
5072     {
5073         if (ptrEntry == nullptr)
5074         {
5075             ptrEntry = PtrEntry::AllocateAndInit(serviceInstance);
5076             VerifyOrExit(ptrEntry != nullptr);
5077             mPtrEntries.Push(*ptrEntry);
5078         }
5079 
5080         if (ptrEntry->mRecord.RefreshTtl(ptr.GetTtl()))
5081         {
5082             changed = true;
5083         }
5084     }
5085 
5086     VerifyOrExit(changed);
5087 
5088     if (ptrEntry->mRecord.IsPresent() && IsActive())
5089     {
5090         Get<Core>().AddPassiveSrvTxtCache(ptrEntry->mServiceInstance.AsCString(), mServiceType.AsCString());
5091     }
5092 
5093     ptrEntry->ConvertTo(result, *this);
5094     InvokeCallbacks(result);
5095 
5096 exit:
5097     DetermineNextFireTime();
5098     ScheduleTimer();
5099 }
5100 
PreparePtrQuestion(TxMessage & aQuery,TimeMilli aNow)5101 void Core::BrowseCache::PreparePtrQuestion(TxMessage &aQuery, TimeMilli aNow)
5102 {
5103     Question question;
5104 
5105     DiscoverCompressOffsets();
5106 
5107     question.SetType(ResourceRecord::kTypePtr);
5108     question.SetClass(ResourceRecord::kClassInternet);
5109 
5110     AppendServiceTypeOrSubTypeTo(aQuery, kQuestionSection);
5111     SuccessOrAssert(aQuery.SelectMessageFor(kQuestionSection).Append(question));
5112 
5113     aQuery.IncrementRecordCount(kQuestionSection);
5114 
5115     for (const PtrEntry &ptrEntry : mPtrEntries)
5116     {
5117         if (!ptrEntry.mRecord.IsPresent() || ptrEntry.mRecord.LessThanHalfTtlRemains(aNow))
5118         {
5119             continue;
5120         }
5121 
5122         AppendKnownAnswer(aQuery, ptrEntry, aNow);
5123     }
5124 }
5125 
DiscoverCompressOffsets(void)5126 void Core::BrowseCache::DiscoverCompressOffsets(void)
5127 {
5128     for (const BrowseCache &browseCache : Get<Core>().mBrowseCacheList)
5129     {
5130         if (&browseCache == this)
5131         {
5132             break;
5133         }
5134 
5135         if (NameMatch(browseCache.mServiceType, mServiceType))
5136         {
5137             UpdateCompressOffset(mServiceTypeOffset, browseCache.mServiceTypeOffset);
5138             UpdateCompressOffset(mSubServiceTypeOffset, browseCache.mSubServiceTypeOffset);
5139             VerifyOrExit(mSubServiceTypeOffset == kUnspecifiedOffset);
5140         }
5141     }
5142 
5143     VerifyOrExit(mServiceTypeOffset == kUnspecifiedOffset);
5144 
5145     for (const SrvCache &srvCache : Get<Core>().mSrvCacheList)
5146     {
5147         if (NameMatch(srvCache.mServiceType, mServiceType))
5148         {
5149             UpdateCompressOffset(mServiceTypeOffset, srvCache.mServiceTypeOffset);
5150             VerifyOrExit(mServiceTypeOffset == kUnspecifiedOffset);
5151         }
5152     }
5153 
5154     for (const TxtCache &txtCache : Get<Core>().mTxtCacheList)
5155     {
5156         if (NameMatch(txtCache.mServiceType, mServiceType))
5157         {
5158             UpdateCompressOffset(mServiceTypeOffset, txtCache.mServiceTypeOffset);
5159             VerifyOrExit(mServiceTypeOffset == kUnspecifiedOffset);
5160         }
5161     }
5162 
5163 exit:
5164     return;
5165 }
5166 
AppendServiceTypeOrSubTypeTo(TxMessage & aTxMessage,Section aSection)5167 void Core::BrowseCache::AppendServiceTypeOrSubTypeTo(TxMessage &aTxMessage, Section aSection)
5168 {
5169     if (!mSubTypeLabel.IsNull())
5170     {
5171         AppendOutcome outcome;
5172 
5173         outcome = aTxMessage.AppendLabel(aSection, mSubTypeLabel.AsCString(), mSubServiceNameOffset);
5174         VerifyOrExit(outcome != kAppendedFullNameAsCompressed);
5175 
5176         outcome = aTxMessage.AppendLabel(aSection, kSubServiceLabel, mSubServiceTypeOffset);
5177         VerifyOrExit(outcome != kAppendedFullNameAsCompressed);
5178     }
5179 
5180     aTxMessage.AppendServiceType(aSection, mServiceType.AsCString(), mServiceTypeOffset);
5181 
5182 exit:
5183     return;
5184 }
5185 
AppendKnownAnswer(TxMessage & aTxMessage,const PtrEntry & aPtrEntry,TimeMilli aNow)5186 void Core::BrowseCache::AppendKnownAnswer(TxMessage &aTxMessage, const PtrEntry &aPtrEntry, TimeMilli aNow)
5187 {
5188     Message  &message = aTxMessage.SelectMessageFor(kAnswerSection);
5189     PtrRecord ptr;
5190     uint16_t  offset;
5191 
5192     ptr.Init();
5193     ptr.SetTtl(aPtrEntry.mRecord.GetRemainingTtl(aNow));
5194 
5195     AppendServiceTypeOrSubTypeTo(aTxMessage, kAnswerSection);
5196 
5197     offset = message.GetLength();
5198     SuccessOrAssert(message.Append(ptr));
5199 
5200     SuccessOrAssert(Name::AppendLabel(aPtrEntry.mServiceInstance.AsCString(), message));
5201     aTxMessage.AppendServiceType(kAnswerSection, mServiceType.AsCString(), mServiceTypeOffset);
5202 
5203     UpdateRecordLengthInMessage(ptr, message, offset);
5204 
5205     aTxMessage.IncrementRecordCount(kAnswerSection);
5206 }
5207 
UpdateRecordStateAfterQuery(TimeMilli aNow)5208 void Core::BrowseCache::UpdateRecordStateAfterQuery(TimeMilli aNow)
5209 {
5210     for (PtrEntry &ptrEntry : mPtrEntries)
5211     {
5212         ptrEntry.mRecord.UpdateStateAfterQuery(aNow);
5213     }
5214 }
5215 
DetermineRecordFireTime(void)5216 void Core::BrowseCache::DetermineRecordFireTime(void)
5217 {
5218     for (PtrEntry &ptrEntry : mPtrEntries)
5219     {
5220         ptrEntry.mRecord.UpdateQueryAndFireTimeOn(*this);
5221     }
5222 }
5223 
ProcessExpiredRecords(TimeMilli aNow)5224 void Core::BrowseCache::ProcessExpiredRecords(TimeMilli aNow)
5225 {
5226     OwningList<PtrEntry> expiredEntries;
5227 
5228     mPtrEntries.RemoveAllMatching(ExpireChecker(aNow), expiredEntries);
5229 
5230     for (PtrEntry &exiredEntry : expiredEntries)
5231     {
5232         BrowseResult result;
5233 
5234         exiredEntry.mRecord.RefreshTtl(0);
5235 
5236         exiredEntry.ConvertTo(result, *this);
5237         InvokeCallbacks(result);
5238     }
5239 }
5240 
ReportResultsTo(ResultCallback & aCallback) const5241 void Core::BrowseCache::ReportResultsTo(ResultCallback &aCallback) const
5242 {
5243     for (const PtrEntry &ptrEntry : mPtrEntries)
5244     {
5245         if (ptrEntry.mRecord.IsPresent())
5246         {
5247             BrowseResult result;
5248 
5249             ptrEntry.ConvertTo(result, *this);
5250             aCallback.Invoke(GetInstance(), result);
5251         }
5252     }
5253 }
5254 
5255 #if OPENTHREAD_CONFIG_MULTICAST_DNS_ENTRY_ITERATION_API_ENABLE
5256 
CopyInfoTo(Browser & aBrowser,CacheInfo & aInfo) const5257 void Core::BrowseCache::CopyInfoTo(Browser &aBrowser, CacheInfo &aInfo) const
5258 {
5259     aBrowser.mServiceType   = mServiceType.AsCString();
5260     aBrowser.mSubTypeLabel  = mSubTypeLabel.AsCString();
5261     aBrowser.mInfraIfIndex  = Get<Core>().mInfraIfIndex;
5262     aBrowser.mCallback      = nullptr;
5263     aInfo.mIsActive         = IsActive();
5264     aInfo.mHasCachedResults = !mPtrEntries.IsEmpty();
5265 }
5266 
5267 #endif
5268 
5269 //---------------------------------------------------------------------------------------------------------------------
5270 // Core::BrowseCache::PtrEntry
5271 
Init(const char * aServiceInstance)5272 Error Core::BrowseCache::PtrEntry::Init(const char *aServiceInstance)
5273 {
5274     mNext = nullptr;
5275 
5276     return mServiceInstance.Set(aServiceInstance);
5277 }
5278 
Matches(const ExpireChecker & aExpireChecker) const5279 bool Core::BrowseCache::PtrEntry::Matches(const ExpireChecker &aExpireChecker) const
5280 {
5281     return mRecord.ShouldExpire(aExpireChecker.mNow);
5282 }
5283 
ConvertTo(BrowseResult & aResult,const BrowseCache & aBrowseCache) const5284 void Core::BrowseCache::PtrEntry::ConvertTo(BrowseResult &aResult, const BrowseCache &aBrowseCache) const
5285 {
5286     ClearAllBytes(aResult);
5287 
5288     aResult.mServiceType     = aBrowseCache.mServiceType.AsCString();
5289     aResult.mSubTypeLabel    = aBrowseCache.mSubTypeLabel.AsCString();
5290     aResult.mServiceInstance = mServiceInstance.AsCString();
5291     aResult.mTtl             = mRecord.GetTtl();
5292     aResult.mInfraIfIndex    = aBrowseCache.Get<Core>().mInfraIfIndex;
5293 }
5294 
5295 //---------------------------------------------------------------------------------------------------------------------
5296 // Core::ServiceCache
5297 
Init(Instance & aInstance,Type aType,const char * aServiceInstance,const char * aServiceType)5298 Error Core::ServiceCache::Init(Instance &aInstance, Type aType, const char *aServiceInstance, const char *aServiceType)
5299 {
5300     Error error = kErrorNone;
5301 
5302     CacheEntry::Init(aInstance, aType);
5303     ClearCompressOffsets();
5304     SuccessOrExit(error = mServiceInstance.Set(aServiceInstance));
5305     SuccessOrExit(error = mServiceType.Set(aServiceType));
5306 
5307 exit:
5308     return error;
5309 }
5310 
ClearCompressOffsets(void)5311 void Core::ServiceCache::ClearCompressOffsets(void)
5312 {
5313     mServiceNameOffset = kUnspecifiedOffset;
5314     mServiceTypeOffset = kUnspecifiedOffset;
5315 }
5316 
Matches(const Name & aFullName) const5317 bool Core::ServiceCache::Matches(const Name &aFullName) const
5318 {
5319     return aFullName.Matches(mServiceInstance.AsCString(), mServiceType.AsCString(), kLocalDomain);
5320 }
5321 
Matches(const char * aServiceInstance,const char * aServiceType) const5322 bool Core::ServiceCache::Matches(const char *aServiceInstance, const char *aServiceType) const
5323 {
5324     return NameMatch(mServiceInstance, aServiceInstance) && NameMatch(mServiceType, aServiceType);
5325 }
5326 
PrepareQueryQuestion(TxMessage & aQuery,uint16_t aRrType)5327 void Core::ServiceCache::PrepareQueryQuestion(TxMessage &aQuery, uint16_t aRrType)
5328 {
5329     Message &message = aQuery.SelectMessageFor(kQuestionSection);
5330     Question question;
5331 
5332     question.SetType(aRrType);
5333     question.SetClass(ResourceRecord::kClassInternet);
5334 
5335     AppendServiceNameTo(aQuery, kQuestionSection);
5336     SuccessOrAssert(message.Append(question));
5337 
5338     aQuery.IncrementRecordCount(kQuestionSection);
5339 }
5340 
AppendServiceNameTo(TxMessage & aTxMessage,Section aSection)5341 void Core::ServiceCache::AppendServiceNameTo(TxMessage &aTxMessage, Section aSection)
5342 {
5343     AppendOutcome outcome;
5344 
5345     outcome = aTxMessage.AppendLabel(aSection, mServiceInstance.AsCString(), mServiceNameOffset);
5346     VerifyOrExit(outcome != kAppendedFullNameAsCompressed);
5347 
5348     aTxMessage.AppendServiceType(aSection, mServiceType.AsCString(), mServiceTypeOffset);
5349 
5350 exit:
5351     return;
5352 }
5353 
UpdateRecordStateAfterQuery(TimeMilli aNow)5354 void Core::ServiceCache::UpdateRecordStateAfterQuery(TimeMilli aNow) { mRecord.UpdateStateAfterQuery(aNow); }
5355 
DetermineRecordFireTime(void)5356 void Core::ServiceCache::DetermineRecordFireTime(void) { mRecord.UpdateQueryAndFireTimeOn(*this); }
5357 
ShouldStartInitialQueries(void) const5358 bool Core::ServiceCache::ShouldStartInitialQueries(void) const
5359 {
5360     // This is called when the first active resolver is added
5361     // for this cache entry to determine whether we should
5362     // send initial queries. It is possible that we were passively
5363     // monitoring and have some cached record for this entry.
5364     // We send initial queries if there is no record or less than
5365     // half of the original TTL remains.
5366 
5367     return !mRecord.IsPresent() || mRecord.LessThanHalfTtlRemains(TimerMilli::GetNow());
5368 }
5369 
5370 //---------------------------------------------------------------------------------------------------------------------
5371 // Core::SrvCache
5372 
Init(Instance & aInstance,const char * aServiceInstance,const char * aServiceType)5373 Error Core::SrvCache::Init(Instance &aInstance, const char *aServiceInstance, const char *aServiceType)
5374 {
5375     mNext     = nullptr;
5376     mPort     = 0;
5377     mPriority = 0;
5378     mWeight   = 0;
5379 
5380     return ServiceCache::Init(aInstance, kSrvCache, aServiceInstance, aServiceType);
5381 }
5382 
Init(Instance & aInstance,const ServiceName & aServiceName)5383 Error Core::SrvCache::Init(Instance &aInstance, const ServiceName &aServiceName)
5384 {
5385     return Init(aInstance, aServiceName.mServiceInstance, aServiceName.mServiceType);
5386 }
5387 
Init(Instance & aInstance,const SrvResolver & aResolver)5388 Error Core::SrvCache::Init(Instance &aInstance, const SrvResolver &aResolver)
5389 {
5390     return Init(aInstance, aResolver.mServiceInstance, aResolver.mServiceType);
5391 }
5392 
Matches(const Name & aFullName) const5393 bool Core::SrvCache::Matches(const Name &aFullName) const { return ServiceCache::Matches(aFullName); }
5394 
Matches(const ServiceName & aServiceName) const5395 bool Core::SrvCache::Matches(const ServiceName &aServiceName) const
5396 {
5397     return ServiceCache::Matches(aServiceName.mServiceInstance, aServiceName.mServiceType);
5398 }
5399 
Matches(const SrvResolver & aResolver) const5400 bool Core::SrvCache::Matches(const SrvResolver &aResolver) const
5401 {
5402     return ServiceCache::Matches(aResolver.mServiceInstance, aResolver.mServiceType);
5403 }
5404 
Matches(const ExpireChecker & aExpireChecker) const5405 bool Core::SrvCache::Matches(const ExpireChecker &aExpireChecker) const { return ShouldDelete(aExpireChecker.mNow); }
5406 
Add(const SrvResolver & aResolver)5407 Error Core::SrvCache::Add(const SrvResolver &aResolver) { return CacheEntry::Add(ResultCallback(aResolver.mCallback)); }
5408 
Remove(const SrvResolver & aResolver)5409 void Core::SrvCache::Remove(const SrvResolver &aResolver) { CacheEntry::Remove(ResultCallback(aResolver.mCallback)); }
5410 
ProcessResponseRecord(const Message & aMessage,uint16_t aRecordOffset)5411 void Core::SrvCache::ProcessResponseRecord(const Message &aMessage, uint16_t aRecordOffset)
5412 {
5413     // Name and record type in `aMessage` are already matched.
5414 
5415     uint16_t     offset = aRecordOffset;
5416     SrvRecord    srv;
5417     Name::Buffer hostFullName;
5418     Name::Buffer hostName;
5419     SrvResult    result;
5420     bool         changed = false;
5421 
5422     // Read the SRV record. `ReadTargetHostName()` validates that
5423     // SRV record is well-formed.
5424 
5425     SuccessOrExit(aMessage.Read(offset, srv));
5426     offset += sizeof(srv);
5427     SuccessOrExit(srv.ReadTargetHostName(aMessage, offset, hostFullName));
5428 
5429     SuccessOrExit(Name::ExtractLabels(hostFullName, kLocalDomain, hostName));
5430 
5431     if (srv.GetTtl() == 0)
5432     {
5433         VerifyOrExit(mRecord.IsPresent());
5434 
5435         mHostName.Free();
5436         mRecord.RefreshTtl(0);
5437         changed = true;
5438     }
5439     else
5440     {
5441         if (!mRecord.IsPresent() || !NameMatch(mHostName, hostName))
5442         {
5443             SuccessOrAssert(mHostName.Set(hostName));
5444             changed = true;
5445         }
5446 
5447         if (!mRecord.IsPresent() || (mPort != srv.GetPort()))
5448         {
5449             mPort   = srv.GetPort();
5450             changed = true;
5451         }
5452 
5453         if (!mRecord.IsPresent() || (mPriority != srv.GetPriority()))
5454         {
5455             mPriority = srv.GetPriority();
5456             changed   = true;
5457         }
5458 
5459         if (!mRecord.IsPresent() || (mWeight != srv.GetWeight()))
5460         {
5461             mWeight = srv.GetWeight();
5462             changed = true;
5463         }
5464 
5465         if (mRecord.RefreshTtl(srv.GetTtl()))
5466         {
5467             changed = true;
5468         }
5469     }
5470 
5471     VerifyOrExit(changed);
5472 
5473     if (mRecord.IsPresent())
5474     {
5475         StopInitialQueries();
5476 
5477         // If not present already, we add a passive `TxtCache` for the
5478         // same service name, and an `Ip6AddrCache` for the host name.
5479 
5480         Get<Core>().AddPassiveSrvTxtCache(mServiceInstance.AsCString(), mServiceType.AsCString());
5481         Get<Core>().AddPassiveIp6AddrCache(mHostName.AsCString());
5482     }
5483 
5484     ConvertTo(result);
5485     InvokeCallbacks(result);
5486 
5487 exit:
5488     DetermineNextFireTime();
5489     ScheduleTimer();
5490 }
5491 
PrepareSrvQuestion(TxMessage & aQuery)5492 void Core::SrvCache::PrepareSrvQuestion(TxMessage &aQuery)
5493 {
5494     DiscoverCompressOffsets();
5495     PrepareQueryQuestion(aQuery, ResourceRecord::kTypeSrv);
5496 }
5497 
DiscoverCompressOffsets(void)5498 void Core::SrvCache::DiscoverCompressOffsets(void)
5499 {
5500     for (const SrvCache &srvCache : Get<Core>().mSrvCacheList)
5501     {
5502         if (&srvCache == this)
5503         {
5504             break;
5505         }
5506 
5507         if (NameMatch(srvCache.mServiceType, mServiceType))
5508         {
5509             UpdateCompressOffset(mServiceTypeOffset, srvCache.mServiceTypeOffset);
5510         }
5511 
5512         if (mServiceTypeOffset != kUnspecifiedOffset)
5513         {
5514             break;
5515         }
5516     }
5517 }
5518 
ProcessExpiredRecords(TimeMilli aNow)5519 void Core::SrvCache::ProcessExpiredRecords(TimeMilli aNow)
5520 {
5521     if (mRecord.ShouldExpire(aNow))
5522     {
5523         SrvResult result;
5524 
5525         mRecord.RefreshTtl(0);
5526 
5527         ConvertTo(result);
5528         InvokeCallbacks(result);
5529     }
5530 }
5531 
ReportResultTo(ResultCallback & aCallback) const5532 void Core::SrvCache::ReportResultTo(ResultCallback &aCallback) const
5533 {
5534     if (mRecord.IsPresent())
5535     {
5536         SrvResult result;
5537 
5538         ConvertTo(result);
5539         aCallback.Invoke(GetInstance(), result);
5540     }
5541 }
5542 
ConvertTo(SrvResult & aResult) const5543 void Core::SrvCache::ConvertTo(SrvResult &aResult) const
5544 {
5545     ClearAllBytes(aResult);
5546 
5547     aResult.mServiceInstance = mServiceInstance.AsCString();
5548     aResult.mServiceType     = mServiceType.AsCString();
5549     aResult.mHostName        = mHostName.AsCString();
5550     aResult.mPort            = mPort;
5551     aResult.mPriority        = mPriority;
5552     aResult.mWeight          = mWeight;
5553     aResult.mTtl             = mRecord.GetTtl();
5554     aResult.mInfraIfIndex    = Get<Core>().mInfraIfIndex;
5555 }
5556 
5557 #if OPENTHREAD_CONFIG_MULTICAST_DNS_ENTRY_ITERATION_API_ENABLE
5558 
CopyInfoTo(SrvResolver & aResolver,CacheInfo & aInfo) const5559 void Core::SrvCache::CopyInfoTo(SrvResolver &aResolver, CacheInfo &aInfo) const
5560 {
5561     aResolver.mServiceInstance = mServiceInstance.AsCString();
5562     aResolver.mServiceType     = mServiceType.AsCString();
5563     aResolver.mInfraIfIndex    = Get<Core>().mInfraIfIndex;
5564     aResolver.mCallback        = nullptr;
5565     aInfo.mIsActive            = IsActive();
5566     aInfo.mHasCachedResults    = mRecord.IsPresent();
5567 }
5568 
5569 #endif
5570 
5571 //---------------------------------------------------------------------------------------------------------------------
5572 // Core::TxtCache
5573 
Init(Instance & aInstance,const char * aServiceInstance,const char * aServiceType)5574 Error Core::TxtCache::Init(Instance &aInstance, const char *aServiceInstance, const char *aServiceType)
5575 {
5576     mNext = nullptr;
5577 
5578     return ServiceCache::Init(aInstance, kTxtCache, aServiceInstance, aServiceType);
5579 }
5580 
Init(Instance & aInstance,const ServiceName & aServiceName)5581 Error Core::TxtCache::Init(Instance &aInstance, const ServiceName &aServiceName)
5582 {
5583     return Init(aInstance, aServiceName.mServiceInstance, aServiceName.mServiceType);
5584 }
5585 
Init(Instance & aInstance,const TxtResolver & aResolver)5586 Error Core::TxtCache::Init(Instance &aInstance, const TxtResolver &aResolver)
5587 {
5588     return Init(aInstance, aResolver.mServiceInstance, aResolver.mServiceType);
5589 }
5590 
Matches(const Name & aFullName) const5591 bool Core::TxtCache::Matches(const Name &aFullName) const { return ServiceCache::Matches(aFullName); }
5592 
Matches(const ServiceName & aServiceName) const5593 bool Core::TxtCache::Matches(const ServiceName &aServiceName) const
5594 {
5595     return ServiceCache::Matches(aServiceName.mServiceInstance, aServiceName.mServiceType);
5596 }
5597 
Matches(const TxtResolver & aResolver) const5598 bool Core::TxtCache::Matches(const TxtResolver &aResolver) const
5599 {
5600     return ServiceCache::Matches(aResolver.mServiceInstance, aResolver.mServiceType);
5601 }
5602 
Matches(const ExpireChecker & aExpireChecker) const5603 bool Core::TxtCache::Matches(const ExpireChecker &aExpireChecker) const { return ShouldDelete(aExpireChecker.mNow); }
5604 
Add(const TxtResolver & aResolver)5605 Error Core::TxtCache::Add(const TxtResolver &aResolver) { return CacheEntry::Add(ResultCallback(aResolver.mCallback)); }
5606 
Remove(const TxtResolver & aResolver)5607 void Core::TxtCache::Remove(const TxtResolver &aResolver) { CacheEntry::Remove(ResultCallback(aResolver.mCallback)); }
5608 
ProcessResponseRecord(const Message & aMessage,uint16_t aRecordOffset)5609 void Core::TxtCache::ProcessResponseRecord(const Message &aMessage, uint16_t aRecordOffset)
5610 {
5611     // Name and record type are already matched.
5612 
5613     uint16_t  offset = aRecordOffset;
5614     TxtRecord txt;
5615     TxtResult result;
5616     bool      changed = false;
5617 
5618     SuccessOrExit(aMessage.Read(offset, txt));
5619     offset += sizeof(txt);
5620 
5621     if (txt.GetTtl() == 0)
5622     {
5623         VerifyOrExit(mRecord.IsPresent());
5624 
5625         mTxtData.Free();
5626         mRecord.RefreshTtl(0);
5627         changed = true;
5628     }
5629     else
5630     {
5631         VerifyOrExit(txt.GetLength() > 0);
5632         VerifyOrExit(aMessage.GetLength() >= offset + txt.GetLength());
5633 
5634         if (!mRecord.IsPresent() || (mTxtData.GetLength() != txt.GetLength()) ||
5635             !aMessage.CompareBytes(offset, mTxtData.GetBytes(), mTxtData.GetLength()))
5636         {
5637             SuccessOrAssert(mTxtData.SetFrom(aMessage, offset, txt.GetLength()));
5638             changed = true;
5639         }
5640 
5641         if (mRecord.RefreshTtl(txt.GetTtl()))
5642         {
5643             changed = true;
5644         }
5645     }
5646 
5647     VerifyOrExit(changed);
5648 
5649     if (mRecord.IsPresent())
5650     {
5651         StopInitialQueries();
5652     }
5653 
5654     ConvertTo(result);
5655     InvokeCallbacks(result);
5656 
5657 exit:
5658     DetermineNextFireTime();
5659     ScheduleTimer();
5660 }
5661 
PrepareTxtQuestion(TxMessage & aQuery)5662 void Core::TxtCache::PrepareTxtQuestion(TxMessage &aQuery)
5663 {
5664     DiscoverCompressOffsets();
5665     PrepareQueryQuestion(aQuery, ResourceRecord::kTypeTxt);
5666 }
5667 
DiscoverCompressOffsets(void)5668 void Core::TxtCache::DiscoverCompressOffsets(void)
5669 {
5670     for (const SrvCache &srvCache : Get<Core>().mSrvCacheList)
5671     {
5672         if (!NameMatch(srvCache.mServiceType, mServiceType))
5673         {
5674             continue;
5675         }
5676 
5677         UpdateCompressOffset(mServiceTypeOffset, srvCache.mServiceTypeOffset);
5678 
5679         if (NameMatch(srvCache.mServiceInstance, mServiceInstance))
5680         {
5681             UpdateCompressOffset(mServiceNameOffset, srvCache.mServiceNameOffset);
5682         }
5683 
5684         VerifyOrExit(mServiceNameOffset == kUnspecifiedOffset);
5685     }
5686 
5687     for (const TxtCache &txtCache : Get<Core>().mTxtCacheList)
5688     {
5689         if (&txtCache == this)
5690         {
5691             break;
5692         }
5693 
5694         if (NameMatch(txtCache.mServiceType, mServiceType))
5695         {
5696             UpdateCompressOffset(mServiceTypeOffset, txtCache.mServiceTypeOffset);
5697         }
5698 
5699         VerifyOrExit(mServiceTypeOffset == kUnspecifiedOffset);
5700     }
5701 
5702 exit:
5703     return;
5704 }
5705 
ProcessExpiredRecords(TimeMilli aNow)5706 void Core::TxtCache::ProcessExpiredRecords(TimeMilli aNow)
5707 {
5708     if (mRecord.ShouldExpire(aNow))
5709     {
5710         TxtResult result;
5711 
5712         mRecord.RefreshTtl(0);
5713 
5714         ConvertTo(result);
5715         InvokeCallbacks(result);
5716     }
5717 }
5718 
ReportResultTo(ResultCallback & aCallback) const5719 void Core::TxtCache::ReportResultTo(ResultCallback &aCallback) const
5720 {
5721     if (mRecord.IsPresent())
5722     {
5723         TxtResult result;
5724 
5725         ConvertTo(result);
5726         aCallback.Invoke(GetInstance(), result);
5727     }
5728 }
5729 
ConvertTo(TxtResult & aResult) const5730 void Core::TxtCache::ConvertTo(TxtResult &aResult) const
5731 {
5732     ClearAllBytes(aResult);
5733 
5734     aResult.mServiceInstance = mServiceInstance.AsCString();
5735     aResult.mServiceType     = mServiceType.AsCString();
5736     aResult.mTxtData         = mTxtData.GetBytes();
5737     aResult.mTxtDataLength   = mTxtData.GetLength();
5738     aResult.mTtl             = mRecord.GetTtl();
5739     aResult.mInfraIfIndex    = Get<Core>().mInfraIfIndex;
5740 }
5741 
5742 #if OPENTHREAD_CONFIG_MULTICAST_DNS_ENTRY_ITERATION_API_ENABLE
5743 
CopyInfoTo(TxtResolver & aResolver,CacheInfo & aInfo) const5744 void Core::TxtCache::CopyInfoTo(TxtResolver &aResolver, CacheInfo &aInfo) const
5745 {
5746     aResolver.mServiceInstance = mServiceInstance.AsCString();
5747     aResolver.mServiceType     = mServiceType.AsCString();
5748     aResolver.mInfraIfIndex    = Get<Core>().mInfraIfIndex;
5749     aResolver.mCallback        = nullptr;
5750     aInfo.mIsActive            = IsActive();
5751     aInfo.mHasCachedResults    = mRecord.IsPresent();
5752 }
5753 
5754 #endif
5755 
5756 //---------------------------------------------------------------------------------------------------------------------
5757 // Core::AddrCache
5758 
Init(Instance & aInstance,Type aType,const char * aHostName)5759 Error Core::AddrCache::Init(Instance &aInstance, Type aType, const char *aHostName)
5760 {
5761     CacheEntry::Init(aInstance, aType);
5762 
5763     mNext        = nullptr;
5764     mShouldFlush = false;
5765 
5766     return mName.Set(aHostName);
5767 }
5768 
Init(Instance & aInstance,Type aType,const AddressResolver & aResolver)5769 Error Core::AddrCache::Init(Instance &aInstance, Type aType, const AddressResolver &aResolver)
5770 {
5771     return Init(aInstance, aType, aResolver.mHostName);
5772 }
5773 
Matches(const Name & aFullName) const5774 bool Core::AddrCache::Matches(const Name &aFullName) const
5775 {
5776     return aFullName.Matches(nullptr, mName.AsCString(), kLocalDomain);
5777 }
5778 
Matches(const char * aName) const5779 bool Core::AddrCache::Matches(const char *aName) const { return NameMatch(mName, aName); }
5780 
Matches(const AddressResolver & aResolver) const5781 bool Core::AddrCache::Matches(const AddressResolver &aResolver) const { return Matches(aResolver.mHostName); }
5782 
Matches(const ExpireChecker & aExpireChecker) const5783 bool Core::AddrCache::Matches(const ExpireChecker &aExpireChecker) const { return ShouldDelete(aExpireChecker.mNow); }
5784 
Add(const AddressResolver & aResolver)5785 Error Core::AddrCache::Add(const AddressResolver &aResolver)
5786 {
5787     return CacheEntry::Add(ResultCallback(aResolver.mCallback));
5788 }
5789 
Remove(const AddressResolver & aResolver)5790 void Core::AddrCache::Remove(const AddressResolver &aResolver)
5791 {
5792     CacheEntry::Remove(ResultCallback(aResolver.mCallback));
5793 }
5794 
PrepareQueryQuestion(TxMessage & aQuery,uint16_t aRrType)5795 void Core::AddrCache::PrepareQueryQuestion(TxMessage &aQuery, uint16_t aRrType)
5796 {
5797     Question question;
5798 
5799     question.SetType(aRrType);
5800     question.SetClass(ResourceRecord::kClassInternet);
5801 
5802     AppendNameTo(aQuery, kQuestionSection);
5803     SuccessOrAssert(aQuery.SelectMessageFor(kQuestionSection).Append(question));
5804 
5805     aQuery.IncrementRecordCount(kQuestionSection);
5806 }
5807 
AppendNameTo(TxMessage & aTxMessage,Section aSection)5808 void Core::AddrCache::AppendNameTo(TxMessage &aTxMessage, Section aSection)
5809 {
5810     uint16_t compressOffset = kUnspecifiedOffset;
5811 
5812     AppendOutcome outcome;
5813 
5814     outcome = aTxMessage.AppendMultipleLabels(aSection, mName.AsCString(), compressOffset);
5815     VerifyOrExit(outcome != kAppendedFullNameAsCompressed);
5816 
5817     aTxMessage.AppendDomainName(aSection);
5818 
5819 exit:
5820     return;
5821 }
5822 
UpdateRecordStateAfterQuery(TimeMilli aNow)5823 void Core::AddrCache::UpdateRecordStateAfterQuery(TimeMilli aNow)
5824 {
5825     for (AddrEntry &entry : mCommittedEntries)
5826     {
5827         entry.mRecord.UpdateStateAfterQuery(aNow);
5828     }
5829 }
5830 
DetermineRecordFireTime(void)5831 void Core::AddrCache::DetermineRecordFireTime(void)
5832 {
5833     for (AddrEntry &entry : mCommittedEntries)
5834     {
5835         entry.mRecord.UpdateQueryAndFireTimeOn(*this);
5836     }
5837 }
5838 
ProcessExpiredRecords(TimeMilli aNow)5839 void Core::AddrCache::ProcessExpiredRecords(TimeMilli aNow)
5840 {
5841     Heap::Array<AddressAndTtl> addrArray;
5842     AddressResult              result;
5843     bool                       didRemoveAny;
5844 
5845     didRemoveAny = mCommittedEntries.RemoveAndFreeAllMatching(ExpireChecker(aNow));
5846 
5847     VerifyOrExit(didRemoveAny);
5848 
5849     ConstructResult(result, addrArray);
5850     InvokeCallbacks(result);
5851 
5852 exit:
5853     return;
5854 }
5855 
ReportResultsTo(ResultCallback & aCallback) const5856 void Core::AddrCache::ReportResultsTo(ResultCallback &aCallback) const
5857 {
5858     Heap::Array<AddressAndTtl> addrArray;
5859     AddressResult              result;
5860 
5861     ConstructResult(result, addrArray);
5862 
5863     if (result.mAddressesLength > 0)
5864     {
5865         aCallback.Invoke(GetInstance(), result);
5866     }
5867 }
5868 
ConstructResult(AddressResult & aResult,Heap::Array<AddressAndTtl> & aAddrArray) const5869 void Core::AddrCache::ConstructResult(AddressResult &aResult, Heap::Array<AddressAndTtl> &aAddrArray) const
5870 {
5871     // Prepares an `AddressResult` populating it with discovered
5872     // addresses from the `AddrCache` entry. Uses a caller-provided
5873     // `Heap::Array` reference (`aAddrArray`) to ensure that the
5874     // allocated array for `aResult.mAddresses` remains valid until
5875     // after the `aResult` is used (passed as input to
5876     // `ResultCallback`).
5877 
5878     uint16_t addrCount = 0;
5879 
5880     ClearAllBytes(aResult);
5881     aAddrArray.Free();
5882 
5883     for (const AddrEntry &entry : mCommittedEntries)
5884     {
5885         if (entry.mRecord.IsPresent())
5886         {
5887             addrCount++;
5888         }
5889     }
5890 
5891     if (addrCount > 0)
5892     {
5893         SuccessOrAssert(aAddrArray.ReserveCapacity(addrCount));
5894 
5895         for (const AddrEntry &entry : mCommittedEntries)
5896         {
5897             AddressAndTtl *addr;
5898 
5899             if (!entry.mRecord.IsPresent())
5900             {
5901                 continue;
5902             }
5903 
5904             addr = aAddrArray.PushBack();
5905             OT_ASSERT(addr != nullptr);
5906 
5907             addr->mAddress = entry.mAddress;
5908             addr->mTtl     = entry.mRecord.GetTtl();
5909         }
5910     }
5911 
5912     aResult.mHostName        = mName.AsCString();
5913     aResult.mAddresses       = aAddrArray.AsCArray();
5914     aResult.mAddressesLength = aAddrArray.GetLength();
5915     aResult.mInfraIfIndex    = Get<Core>().mInfraIfIndex;
5916 }
5917 
ShouldStartInitialQueries(void) const5918 bool Core::AddrCache::ShouldStartInitialQueries(void) const
5919 {
5920     // This is called when the first active resolver is added
5921     // for this cache entry to determine whether we should
5922     // send initial queries. It is possible that we were passively
5923     // monitoring and has some cached records for this entry.
5924     // We send initial queries if there is no record or less than
5925     // half of original TTL remains on any record.
5926 
5927     bool      shouldStart = false;
5928     TimeMilli now         = TimerMilli::GetNow();
5929 
5930     if (mCommittedEntries.IsEmpty())
5931     {
5932         shouldStart = true;
5933         ExitNow();
5934     }
5935 
5936     for (const AddrEntry &entry : mCommittedEntries)
5937     {
5938         if (entry.mRecord.LessThanHalfTtlRemains(now))
5939         {
5940             shouldStart = true;
5941             ExitNow();
5942         }
5943     }
5944 
5945 exit:
5946     return shouldStart;
5947 }
5948 
AddNewResponseAddress(const Ip6::Address & aAddress,uint32_t aTtl,bool aCacheFlush)5949 void Core::AddrCache::AddNewResponseAddress(const Ip6::Address &aAddress, uint32_t aTtl, bool aCacheFlush)
5950 {
5951     // Adds a new address record to `mNewEntries` list. This called as
5952     // the records in a received response are processed one by one.
5953     // Once all records are processed `CommitNewResponseEntries()` is
5954     // called to update the list of addresses.
5955 
5956     AddrEntry *entry;
5957 
5958     if (aCacheFlush)
5959     {
5960         mShouldFlush = true;
5961     }
5962 
5963     // Check for duplicate addresses in the same response.
5964 
5965     entry = mNewEntries.FindMatching(aAddress);
5966 
5967     if (entry == nullptr)
5968     {
5969         entry = AddrEntry::Allocate(aAddress);
5970         OT_ASSERT(entry != nullptr);
5971         mNewEntries.Push(*entry);
5972     }
5973 
5974     entry->mRecord.RefreshTtl(aTtl);
5975 }
5976 
CommitNewResponseEntries(void)5977 void Core::AddrCache::CommitNewResponseEntries(void)
5978 {
5979     bool changed = false;
5980 
5981     // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
5982     // Determine whether there is any changes to the list of addresses
5983     // between the `mNewEntries` and `mCommittedEntries` lists.
5984     //
5985     // First, we verify if all new entries are present in the
5986     // `mCommittedEntries` list with the same TTL value. Next, if we
5987     // need to flush the old cache list, we check if any existing
5988     // `mCommittedEntries` is absent in `mNewEntries` list.
5989 
5990     for (const AddrEntry &newEntry : mNewEntries)
5991     {
5992         AddrEntry *exitingEntry = mCommittedEntries.FindMatching(newEntry.mAddress);
5993 
5994         if (newEntry.GetTtl() == 0)
5995         {
5996             // New entry has zero TTL, removing the address. If we
5997             // have a matching `exitingEntry` we set its TTL to zero
5998             // so to remove it in the next step when updating the
5999             // `mCommittedEntries` list.
6000 
6001             if (exitingEntry != nullptr)
6002             {
6003                 exitingEntry->mRecord.RefreshTtl(0);
6004                 changed = true;
6005             }
6006         }
6007         else if ((exitingEntry == nullptr) || (exitingEntry->GetTtl() != newEntry.GetTtl()))
6008         {
6009             changed = true;
6010         }
6011     }
6012 
6013     if (mShouldFlush && !changed)
6014     {
6015         for (const AddrEntry &exitingEntry : mCommittedEntries)
6016         {
6017             if ((exitingEntry.GetTtl() > 0) && !mNewEntries.ContainsMatching(exitingEntry.mAddress))
6018             {
6019                 changed = true;
6020                 break;
6021             }
6022         }
6023     }
6024 
6025     // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6026     // Update the `mCommittedEntries` list.
6027 
6028     // First remove entries, if we need to flush clear everything,
6029     // otherwise remove the ones with zero TTL marked in previous
6030     // step. Then, add or update new entries to `mCommittedEntries`
6031 
6032     if (mShouldFlush)
6033     {
6034         mCommittedEntries.Clear();
6035         mShouldFlush = false;
6036     }
6037     else
6038     {
6039         mCommittedEntries.RemoveAndFreeAllMatching(EmptyChecker());
6040     }
6041 
6042     while (!mNewEntries.IsEmpty())
6043     {
6044         OwnedPtr<AddrEntry> newEntry = mNewEntries.Pop();
6045         AddrEntry          *entry;
6046 
6047         if (newEntry->GetTtl() == 0)
6048         {
6049             continue;
6050         }
6051 
6052         entry = mCommittedEntries.FindMatching(newEntry->mAddress);
6053 
6054         if (entry != nullptr)
6055         {
6056             entry->mRecord.RefreshTtl(newEntry->GetTtl());
6057         }
6058         else
6059         {
6060             mCommittedEntries.Push(*newEntry.Release());
6061         }
6062     }
6063 
6064     StopInitialQueries();
6065 
6066     // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6067     // Invoke callbacks if there is any change.
6068 
6069     if (changed)
6070     {
6071         Heap::Array<AddressAndTtl> addrArray;
6072         AddressResult              result;
6073 
6074         ConstructResult(result, addrArray);
6075         InvokeCallbacks(result);
6076     }
6077 
6078     DetermineNextFireTime();
6079     ScheduleTimer();
6080 }
6081 
6082 #if OPENTHREAD_CONFIG_MULTICAST_DNS_ENTRY_ITERATION_API_ENABLE
6083 
CopyInfoTo(AddressResolver & aResolver,CacheInfo & aInfo) const6084 void Core::AddrCache::CopyInfoTo(AddressResolver &aResolver, CacheInfo &aInfo) const
6085 {
6086     aResolver.mHostName     = mName.AsCString();
6087     aResolver.mInfraIfIndex = Get<Core>().mInfraIfIndex;
6088     aResolver.mCallback     = nullptr;
6089     aInfo.mIsActive         = IsActive();
6090     aInfo.mHasCachedResults = !mCommittedEntries.IsEmpty();
6091 }
6092 
6093 #endif
6094 
6095 //---------------------------------------------------------------------------------------------------------------------
6096 // Core::AddrCache::AddrEntry
6097 
AddrEntry(const Ip6::Address & aAddress)6098 Core::AddrCache::AddrEntry::AddrEntry(const Ip6::Address &aAddress)
6099     : mNext(nullptr)
6100     , mAddress(aAddress)
6101 {
6102 }
6103 
Matches(const ExpireChecker & aExpireChecker) const6104 bool Core::AddrCache::AddrEntry::Matches(const ExpireChecker &aExpireChecker) const
6105 {
6106     return mRecord.ShouldExpire(aExpireChecker.mNow);
6107 }
6108 
Matches(EmptyChecker aChecker) const6109 bool Core::AddrCache::AddrEntry::Matches(EmptyChecker aChecker) const
6110 {
6111     OT_UNUSED_VARIABLE(aChecker);
6112 
6113     return !mRecord.IsPresent();
6114 }
6115 
6116 //---------------------------------------------------------------------------------------------------------------------
6117 // Core::Ip6AddrCache
6118 
Init(Instance & aInstance,const char * aHostName)6119 Error Core::Ip6AddrCache::Init(Instance &aInstance, const char *aHostName)
6120 {
6121     return AddrCache::Init(aInstance, kIp6AddrCache, aHostName);
6122 }
6123 
Init(Instance & aInstance,const AddressResolver & aResolver)6124 Error Core::Ip6AddrCache::Init(Instance &aInstance, const AddressResolver &aResolver)
6125 {
6126     return AddrCache::Init(aInstance, kIp6AddrCache, aResolver);
6127 }
6128 
ProcessResponseRecord(const Message & aMessage,uint16_t aRecordOffset)6129 void Core::Ip6AddrCache::ProcessResponseRecord(const Message &aMessage, uint16_t aRecordOffset)
6130 {
6131     // Name and record type in `aMessage` are already matched.
6132 
6133     AaaaRecord aaaaRecord;
6134 
6135     SuccessOrExit(aMessage.Read(aRecordOffset, aaaaRecord));
6136     VerifyOrExit(aaaaRecord.GetLength() >= sizeof(Ip6::Address));
6137 
6138     AddNewResponseAddress(aaaaRecord.GetAddress(), aaaaRecord.GetTtl(), aaaaRecord.GetClass() & kClassCacheFlushFlag);
6139 
6140 exit:
6141     return;
6142 }
6143 
PrepareAaaaQuestion(TxMessage & aQuery)6144 void Core::Ip6AddrCache::PrepareAaaaQuestion(TxMessage &aQuery)
6145 {
6146     PrepareQueryQuestion(aQuery, ResourceRecord::kTypeAaaa);
6147 }
6148 
6149 //---------------------------------------------------------------------------------------------------------------------
6150 // Core::Ip4AddrCache
6151 
Init(Instance & aInstance,const char * aHostName)6152 Error Core::Ip4AddrCache::Init(Instance &aInstance, const char *aHostName)
6153 {
6154     return AddrCache::Init(aInstance, kIp4AddrCache, aHostName);
6155 }
6156 
Init(Instance & aInstance,const AddressResolver & aResolver)6157 Error Core::Ip4AddrCache::Init(Instance &aInstance, const AddressResolver &aResolver)
6158 {
6159     return AddrCache::Init(aInstance, kIp4AddrCache, aResolver);
6160 }
6161 
ProcessResponseRecord(const Message & aMessage,uint16_t aRecordOffset)6162 void Core::Ip4AddrCache::ProcessResponseRecord(const Message &aMessage, uint16_t aRecordOffset)
6163 {
6164     // Name and record type in `aMessage` are already matched.
6165 
6166     ARecord      aRecord;
6167     Ip6::Address address;
6168 
6169     SuccessOrExit(aMessage.Read(aRecordOffset, aRecord));
6170     VerifyOrExit(aRecord.GetLength() >= sizeof(Ip4::Address));
6171 
6172     address.SetToIp4Mapped(aRecord.GetAddress());
6173 
6174     AddNewResponseAddress(address, aRecord.GetTtl(), aRecord.GetClass() & kClassCacheFlushFlag);
6175 
6176 exit:
6177     return;
6178 }
6179 
PrepareAQuestion(TxMessage & aQuery)6180 void Core::Ip4AddrCache::PrepareAQuestion(TxMessage &aQuery) { PrepareQueryQuestion(aQuery, ResourceRecord::kTypeA); }
6181 
6182 //---------------------------------------------------------------------------------------------------------------------
6183 // Core::Iterator
6184 
6185 #if OPENTHREAD_CONFIG_MULTICAST_DNS_ENTRY_ITERATION_API_ENABLE
6186 
EntryIterator(Instance & aInstance)6187 Core::EntryIterator::EntryIterator(Instance &aInstance)
6188     : InstanceLocator(aInstance)
6189     , mType(kUnspecified)
6190 {
6191 }
6192 
GetNextHost(Host & aHost,EntryState & aState)6193 Error Core::EntryIterator::GetNextHost(Host &aHost, EntryState &aState)
6194 {
6195     Error error = kErrorNotFound;
6196 
6197     if (mType == kUnspecified)
6198     {
6199         mHostEntry = Get<Core>().mHostEntries.GetHead();
6200         mType      = kHost;
6201     }
6202     else
6203     {
6204         VerifyOrExit(mType == kHost, error = kErrorInvalidArgs);
6205     }
6206 
6207     while (error == kErrorNotFound)
6208     {
6209         VerifyOrExit(mHostEntry != nullptr);
6210         error      = mHostEntry->CopyInfoTo(aHost, aState);
6211         mHostEntry = mHostEntry->GetNext();
6212     }
6213 
6214 exit:
6215     return error;
6216 }
6217 
GetNextService(Service & aService,EntryState & aState)6218 Error Core::EntryIterator::GetNextService(Service &aService, EntryState &aState)
6219 {
6220     Error error = kErrorNotFound;
6221 
6222     if (mType == kUnspecified)
6223     {
6224         mServiceEntry = Get<Core>().mServiceEntries.GetHead();
6225         mType         = kService;
6226     }
6227     else
6228     {
6229         VerifyOrExit(mType == kService, error = kErrorInvalidArgs);
6230     }
6231 
6232     while (error == kErrorNotFound)
6233     {
6234         VerifyOrExit(mServiceEntry != nullptr);
6235         error         = mServiceEntry->CopyInfoTo(aService, aState, *this);
6236         mServiceEntry = mServiceEntry->GetNext();
6237     }
6238 
6239 exit:
6240     return error;
6241 }
6242 
GetNextKey(Key & aKey,EntryState & aState)6243 Error Core::EntryIterator::GetNextKey(Key &aKey, EntryState &aState)
6244 {
6245     Error error = kErrorNotFound;
6246 
6247     if (mType == kUnspecified)
6248     {
6249         mHostEntry = Get<Core>().mHostEntries.GetHead();
6250         mType      = kHostKey;
6251     }
6252     else
6253     {
6254         VerifyOrExit((mType == kServiceKey) || (mType == kHostKey), error = kErrorInvalidArgs);
6255     }
6256 
6257     while ((error == kErrorNotFound) && (mType == kHostKey))
6258     {
6259         if (mHostEntry == nullptr)
6260         {
6261             mServiceEntry = Get<Core>().mServiceEntries.GetHead();
6262             mType         = kServiceKey;
6263             break;
6264         }
6265 
6266         error      = mHostEntry->CopyInfoTo(aKey, aState);
6267         mHostEntry = mHostEntry->GetNext();
6268     }
6269 
6270     while ((error == kErrorNotFound) && (mType == kServiceKey))
6271     {
6272         VerifyOrExit(mServiceEntry != nullptr);
6273         error         = mServiceEntry->CopyInfoTo(aKey, aState);
6274         mServiceEntry = mServiceEntry->GetNext();
6275     }
6276 
6277 exit:
6278     return error;
6279 }
6280 
GetNextBrowser(Browser & aBrowser,CacheInfo & aInfo)6281 Error Core::EntryIterator::GetNextBrowser(Browser &aBrowser, CacheInfo &aInfo)
6282 {
6283     Error error = kErrorNone;
6284 
6285     if (mType == kUnspecified)
6286     {
6287         mBrowseCache = Get<Core>().mBrowseCacheList.GetHead();
6288         mType        = kBrowser;
6289     }
6290     else
6291     {
6292         VerifyOrExit(mType == kBrowser, error = kErrorInvalidArgs);
6293     }
6294 
6295     VerifyOrExit(mBrowseCache != nullptr, error = kErrorNotFound);
6296 
6297     mBrowseCache->CopyInfoTo(aBrowser, aInfo);
6298     mBrowseCache = mBrowseCache->GetNext();
6299 
6300 exit:
6301     return error;
6302 }
6303 
GetNextSrvResolver(SrvResolver & aResolver,CacheInfo & aInfo)6304 Error Core::EntryIterator::GetNextSrvResolver(SrvResolver &aResolver, CacheInfo &aInfo)
6305 {
6306     Error error = kErrorNone;
6307 
6308     if (mType == kUnspecified)
6309     {
6310         mSrvCache = Get<Core>().mSrvCacheList.GetHead();
6311         mType     = kSrvResolver;
6312     }
6313     else
6314     {
6315         VerifyOrExit(mType == kSrvResolver, error = kErrorInvalidArgs);
6316     }
6317 
6318     VerifyOrExit(mSrvCache != nullptr, error = kErrorNotFound);
6319 
6320     mSrvCache->CopyInfoTo(aResolver, aInfo);
6321     mSrvCache = mSrvCache->GetNext();
6322 
6323 exit:
6324     return error;
6325 }
6326 
GetNextTxtResolver(TxtResolver & aResolver,CacheInfo & aInfo)6327 Error Core::EntryIterator::GetNextTxtResolver(TxtResolver &aResolver, CacheInfo &aInfo)
6328 {
6329     Error error = kErrorNone;
6330 
6331     if (mType == kUnspecified)
6332     {
6333         mTxtCache = Get<Core>().mTxtCacheList.GetHead();
6334         mType     = kTxtResolver;
6335     }
6336     else
6337     {
6338         VerifyOrExit(mType == kTxtResolver, error = kErrorInvalidArgs);
6339     }
6340 
6341     VerifyOrExit(mTxtCache != nullptr, error = kErrorNotFound);
6342 
6343     mTxtCache->CopyInfoTo(aResolver, aInfo);
6344     mTxtCache = mTxtCache->GetNext();
6345 
6346 exit:
6347     return error;
6348 }
6349 
GetNextIp6AddressResolver(AddressResolver & aResolver,CacheInfo & aInfo)6350 Error Core::EntryIterator::GetNextIp6AddressResolver(AddressResolver &aResolver, CacheInfo &aInfo)
6351 {
6352     Error error = kErrorNone;
6353 
6354     if (mType == kUnspecified)
6355     {
6356         mIp6AddrCache = Get<Core>().mIp6AddrCacheList.GetHead();
6357         mType         = kIp6AddrResolver;
6358     }
6359     else
6360     {
6361         VerifyOrExit(mType == kIp6AddrResolver, error = kErrorInvalidArgs);
6362     }
6363 
6364     VerifyOrExit(mIp6AddrCache != nullptr, error = kErrorNotFound);
6365 
6366     mIp6AddrCache->CopyInfoTo(aResolver, aInfo);
6367     mIp6AddrCache = mIp6AddrCache->GetNext();
6368 
6369 exit:
6370     return error;
6371 }
6372 
GetNextIp4AddressResolver(AddressResolver & aResolver,CacheInfo & aInfo)6373 Error Core::EntryIterator::GetNextIp4AddressResolver(AddressResolver &aResolver, CacheInfo &aInfo)
6374 {
6375     Error error = kErrorNone;
6376 
6377     if (mType == kUnspecified)
6378     {
6379         mIp4AddrCache = Get<Core>().mIp4AddrCacheList.GetHead();
6380         mType         = kIp4AddrResolver;
6381     }
6382     else
6383     {
6384         VerifyOrExit(mType == kIp4AddrResolver, error = kErrorInvalidArgs);
6385     }
6386 
6387     VerifyOrExit(mIp4AddrCache != nullptr, error = kErrorNotFound);
6388 
6389     mIp4AddrCache->CopyInfoTo(aResolver, aInfo);
6390     mIp4AddrCache = mIp4AddrCache->GetNext();
6391 
6392 exit:
6393     return error;
6394 }
6395 
6396 #endif // OPENTHREAD_CONFIG_MULTICAST_DNS_ENTRY_ITERATION_API_ENABLE
6397 
6398 } // namespace Multicast
6399 } // namespace Dns
6400 } // namespace ot
6401 
6402 //---------------------------------------------------------------------------------------------------------------------
6403 
6404 #if OPENTHREAD_CONFIG_MULTICAST_DNS_MOCK_PLAT_APIS_ENABLE
6405 
otPlatMdnsSetListeningEnabled(otInstance * aInstance,bool aEnable,uint32_t aInfraIfIndex)6406 OT_TOOL_WEAK otError otPlatMdnsSetListeningEnabled(otInstance *aInstance, bool aEnable, uint32_t aInfraIfIndex)
6407 {
6408     OT_UNUSED_VARIABLE(aInstance);
6409     OT_UNUSED_VARIABLE(aEnable);
6410     OT_UNUSED_VARIABLE(aInfraIfIndex);
6411 
6412     return OT_ERROR_FAILED;
6413 }
6414 
otPlatMdnsSendMulticast(otInstance * aInstance,otMessage * aMessage,uint32_t aInfraIfIndex)6415 OT_TOOL_WEAK void otPlatMdnsSendMulticast(otInstance *aInstance, otMessage *aMessage, uint32_t aInfraIfIndex)
6416 {
6417     OT_UNUSED_VARIABLE(aInstance);
6418     OT_UNUSED_VARIABLE(aMessage);
6419     OT_UNUSED_VARIABLE(aInfraIfIndex);
6420 }
6421 
otPlatMdnsSendUnicast(otInstance * aInstance,otMessage * aMessage,const otPlatMdnsAddressInfo * aAddress)6422 OT_TOOL_WEAK void otPlatMdnsSendUnicast(otInstance                  *aInstance,
6423                                         otMessage                   *aMessage,
6424                                         const otPlatMdnsAddressInfo *aAddress)
6425 {
6426     OT_UNUSED_VARIABLE(aInstance);
6427     OT_UNUSED_VARIABLE(aMessage);
6428     OT_UNUSED_VARIABLE(aAddress);
6429 }
6430 
6431 #endif // OPENTHREAD_CONFIG_MULTICAST_DNS_MOCK_PLAT_APIS_ENABLE
6432 
6433 #endif // OPENTHREAD_CONFIG_MULTICAST_DNS_ENABLE
6434