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