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