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