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