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