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