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