1 /*
2 * Copyright (c) 2023, 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 /**
30 * @file
31 * This file includes implementation of SRP Advertising Proxy.
32 */
33
34 #include "srp_advertising_proxy.hpp"
35
36 #if OPENTHREAD_CONFIG_SRP_SERVER_ADVERTISING_PROXY_ENABLE
37
38 #include "common/as_core_type.hpp"
39 #include "common/debug.hpp"
40 #include "common/locator_getters.hpp"
41 #include "common/log.hpp"
42 #include "common/serial_number.hpp"
43 #include "common/type_traits.hpp"
44 #include "instance/instance.hpp"
45
46 namespace ot {
47 namespace Srp {
48
49 RegisterLogModule("SrpAdvProxy");
50
51 //---------------------------------------------------------------------------------------------------------------------
52 // AdvertisingProxy
53
AdvertisingProxy(Instance & aInstance)54 AdvertisingProxy::AdvertisingProxy(Instance &aInstance)
55 : InstanceLocator(aInstance)
56 , mState(kStateStopped)
57 , mCurrentRequestId(0)
58 , mAdvTimeout(kAdvTimeout)
59 , mTimer(aInstance)
60 , mTasklet(aInstance)
61 {
62 mCounters.Clear();
63 }
64
Start(void)65 void AdvertisingProxy::Start(void)
66 {
67 VerifyOrExit(mState != kStateRunning);
68
69 mState = kStateRunning;
70 mCounters.mStateChanges++;
71 LogInfo("Started");
72
73 // Advertise all existing and committed entries on SRP sever.
74
75 for (Host &host : Get<Server>().mHosts)
76 {
77 LogInfo("Adv existing host '%s'", host.GetFullName());
78 Advertise(host);
79 }
80
81 exit:
82 return;
83 }
84
Stop(void)85 void AdvertisingProxy::Stop(void)
86 {
87 VerifyOrExit(mState != kStateStopped);
88
89 mState = kStateStopped;
90 mCounters.mStateChanges++;
91
92 while (true)
93 {
94 OwnedPtr<AdvInfo> advPtr = mAdvInfoList.Pop();
95
96 if (advPtr.IsNull())
97 {
98 break;
99 }
100
101 mCounters.mAdvRejected++;
102
103 UnregisterHostAndItsServicesAndKeys(advPtr->mHost);
104
105 advPtr->mError = kErrorAbort;
106 advPtr->mHost.mAdvIdRange.Clear();
107 advPtr->mBlockingAdv = nullptr;
108 advPtr->SignalServerToCommit();
109 }
110
111 for (Host &host : Get<Server>().GetHosts())
112 {
113 UnregisterHostAndItsServicesAndKeys(host);
114
115 host.mAdvIdRange.Clear();
116 host.mAdvId = kInvalidRequestId;
117 host.mIsRegistered = false;
118
119 for (Service &service : host.mServices)
120 {
121 service.mAdvId = kInvalidRequestId;
122 service.mIsRegistered = false;
123 }
124 }
125
126 LogInfo("Stopped");
127
128 exit:
129 return;
130 }
131
UpdateState(void)132 void AdvertisingProxy::UpdateState(void)
133 {
134 if (!Get<Dnssd>().IsReady() || !Get<BorderRouter::InfraIf>().IsRunning())
135 {
136 Stop();
137 ExitNow();
138 }
139
140 switch (Get<Server>().GetState())
141 {
142 case Server::kStateDisabled:
143 case Server::kStateStopped:
144 Stop();
145 break;
146
147 case Server::kStateRunning:
148 Start();
149 break;
150 }
151
152 exit:
153 return;
154 }
155
AllocateNextRequestId(void)156 AdvertisingProxy::RequestId AdvertisingProxy::AllocateNextRequestId(void)
157 {
158 mCurrentRequestId++;
159
160 if (kInvalidRequestId == mCurrentRequestId)
161 {
162 mCurrentRequestId++;
163 }
164
165 return mCurrentRequestId;
166 }
167
168 // NOLINTNEXTLINE(readability-inconsistent-declaration-parameter-name)
UpdateAdvIdRangeOn(Host & aHost)169 template <> void AdvertisingProxy::UpdateAdvIdRangeOn(Host &aHost)
170 {
171 // Determine and update `mAdvIdRange` on `aHost` based on
172 // `mAdvId` and `mKeyAdvId` of host and its services.
173
174 aHost.mAdvIdRange.Clear();
175
176 for (const Service &service : aHost.mServices)
177 {
178 if (service.mKeyAdvId != kInvalidRequestId)
179 {
180 aHost.mAdvIdRange.Add(service.mKeyAdvId);
181 }
182
183 if (service.mAdvId != kInvalidRequestId)
184 {
185 aHost.mAdvIdRange.Add(service.mAdvId);
186 }
187 }
188
189 if (aHost.mKeyAdvId != kInvalidRequestId)
190 {
191 aHost.mAdvIdRange.Add(aHost.mKeyAdvId);
192 }
193
194 if (aHost.mAdvId != kInvalidRequestId)
195 {
196 aHost.mAdvIdRange.Add(aHost.mAdvId);
197 }
198
199 if (aHost.mAdvIdRange.IsEmpty())
200 {
201 mTasklet.Post();
202 }
203 }
204
205 // NOLINTNEXTLINE(readability-inconsistent-declaration-parameter-name)
UpdateAdvIdRangeOn(Service & aService)206 template <> void AdvertisingProxy::UpdateAdvIdRangeOn(Service &aService)
207 {
208 // Updates `mAdvIdRange` on the `Host` associated with
209 // `aService`.
210
211 UpdateAdvIdRangeOn<Host>(*aService.mHost);
212 }
213
AdvertiseRemovalOf(Host & aHost)214 void AdvertisingProxy::AdvertiseRemovalOf(Host &aHost)
215 {
216 LogInfo("Adv removal of host '%s'", aHost.GetFullName());
217 mCounters.mAdvHostRemovals++;
218
219 VerifyOrExit(mState == kStateRunning);
220 VerifyOrExit(aHost.IsDeleted());
221
222 aHost.mShouldAdvertise = aHost.mIsRegistered;
223
224 for (Service &service : aHost.mServices)
225 {
226 if (!service.mIsDeleted)
227 {
228 service.mIsDeleted = true;
229 }
230
231 service.mShouldAdvertise = service.mIsRegistered;
232 }
233
234 // Reject any outstanding `AdvInfo` that matches `aHost` that is
235 // being removed.
236
237 for (AdvInfo &adv : mAdvInfoList)
238 {
239 Host &advHost = adv.mHost;
240
241 if (!aHost.Matches(advHost.GetFullName()) || advHost.IsDeleted())
242 {
243 continue;
244 }
245
246 for (Service &advService : advHost.mServices)
247 {
248 Service *service;
249
250 service = aHost.FindService(advService.GetInstanceName());
251
252 if (service == nullptr)
253 {
254 // `AdvInfo` contains a service that is not present in
255 // `aHost`, we unregister the service and its key.
256
257 if (!advService.IsDeleted())
258 {
259 UnregisterService(advService);
260 }
261
262 UnregisterKey(advService);
263 }
264 else
265 {
266 service->mShouldAdvertise = true;
267
268 if (aHost.mKeyLease == 0)
269 {
270 advService.mIsKeyRegistered = false;
271 }
272 }
273
274 advService.mAdvId = kInvalidRequestId;
275 advService.mKeyAdvId = kInvalidRequestId;
276 advService.mIsReplaced = true;
277 }
278
279 if (aHost.mKeyLease == 0)
280 {
281 advHost.mIsKeyRegistered = false;
282 }
283
284 advHost.mAdvId = kInvalidRequestId;
285 advHost.mKeyAdvId = kInvalidRequestId;
286 advHost.mIsReplaced = true;
287 advHost.mAdvIdRange.Clear();
288
289 adv.mError = kErrorAbort;
290 mTasklet.Post();
291 }
292
293 for (Service &service : aHost.mServices)
294 {
295 if (service.mShouldAdvertise)
296 {
297 UnregisterService(service);
298 }
299
300 if (aHost.mKeyLease == 0)
301 {
302 UnregisterKey(service);
303 }
304 }
305
306 if (aHost.mShouldAdvertise)
307 {
308 UnregisterHost(aHost);
309 }
310
311 if (aHost.mKeyLease == 0)
312 {
313 UnregisterKey(aHost);
314 }
315
316 exit:
317 return;
318 }
319
AdvertiseRemovalOf(Service & aService)320 void AdvertisingProxy::AdvertiseRemovalOf(Service &aService)
321 {
322 LogInfo("Adv removal of service '%s' '%s'", aService.GetInstanceLabel(), aService.GetServiceName());
323 mCounters.mAdvServiceRemovals++;
324
325 VerifyOrExit(mState == kStateRunning);
326
327 aService.mShouldAdvertise = aService.mIsRegistered;
328
329 // Check if any outstanding `AdvInfo` is re-adding the `aService`
330 // (which is being removed), and if so, skip unregistering the
331 // service and its key.
332
333 for (const AdvInfo &adv : mAdvInfoList)
334 {
335 const Host &advHost = adv.mHost;
336 const Service *advService;
337
338 if (!aService.mHost->Matches(advHost.GetFullName()))
339 {
340 continue;
341 }
342
343 if (advHost.IsDeleted())
344 {
345 break;
346 }
347
348 advService = advHost.FindService(aService.GetInstanceName());
349
350 if ((advService != nullptr) && !advService->IsDeleted())
351 {
352 ExitNow();
353 }
354 }
355
356 if (aService.mShouldAdvertise)
357 {
358 UnregisterService(aService);
359 }
360
361 if (aService.mKeyLease == 0)
362 {
363 UnregisterKey(aService);
364 }
365
366 exit:
367 return;
368 }
369
Advertise(Host & aHost,const Server::MessageMetadata & aMetadata)370 void AdvertisingProxy::Advertise(Host &aHost, const Server::MessageMetadata &aMetadata)
371 {
372 AdvInfo *advPtr = nullptr;
373 Host *existingHost;
374
375 LogInfo("Adv update for '%s'", aHost.GetFullName());
376
377 mCounters.mAdvTotal++;
378
379 VerifyOrExit(mState == kStateRunning);
380
381 advPtr = AdvInfo::Allocate(aHost, aMetadata, mAdvTimeout);
382 VerifyOrExit(advPtr != nullptr);
383 mAdvInfoList.Push(*advPtr);
384
385 // Compare the new `aHost` with outstanding advertisements and
386 // already committed entries on server.
387
388 for (AdvInfo &adv : mAdvInfoList)
389 {
390 if (!aHost.Matches(adv.mHost.GetFullName()))
391 {
392 continue;
393 }
394
395 if (CompareAndUpdateHostAndServices(aHost, adv.mHost))
396 {
397 // If the new `aHost` replaces an entry in the outstanding
398 // `adv`, we mark the new advertisement as blocked so
399 // that it is not committed before the earlier one. This
400 // ensures that SRP Updates are committed in the order
401 // they are advertised, avoiding issues such as re-adding
402 // a removed entry due to a delay in registration on
403 // infra DNS-SD.
404
405 if (advPtr->mBlockingAdv == nullptr)
406 {
407 mCounters.mAdvReplaced++;
408 advPtr->mBlockingAdv = &adv;
409 }
410 }
411 }
412
413 existingHost = Get<Server>().mHosts.FindMatching(aHost.GetFullName());
414
415 if (existingHost != nullptr)
416 {
417 CompareAndUpdateHostAndServices(aHost, *existingHost);
418 }
419
420 Advertise(aHost);
421
422 exit:
423 if (advPtr != nullptr)
424 {
425 if (advPtr->IsCompleted())
426 {
427 mTasklet.Post();
428 }
429 else
430 {
431 mTimer.FireAtIfEarlier(advPtr->mExpireTime);
432 }
433 }
434 else
435 {
436 LogInfo("Adv skipped '%s'", aHost.GetFullName());
437 mCounters.mAdvSkipped++;
438 Get<Server>().CommitSrpUpdate(kErrorNone, aHost, aMetadata);
439 }
440 }
441
IsKeyRegisteredOrRegistering(const Entry & aEntry) const442 template <typename Entry> bool AdvertisingProxy::IsKeyRegisteredOrRegistering(const Entry &aEntry) const
443 {
444 return (aEntry.mIsKeyRegistered || (aEntry.mKeyAdvId != kInvalidRequestId));
445 }
446
IsRegisteredOrRegistering(const Entry & aEntry) const447 template <typename Entry> bool AdvertisingProxy::IsRegisteredOrRegistering(const Entry &aEntry) const
448 {
449 return (aEntry.mIsRegistered || (aEntry.mAdvId != kInvalidRequestId));
450 }
451
452 template <typename Entry>
DecideToAdvertise(Entry & aEntry,bool aUnregisterEntry,bool aUnregisterKey)453 void AdvertisingProxy::DecideToAdvertise(Entry &aEntry, bool aUnregisterEntry, bool aUnregisterKey)
454 {
455 // Decides whether to advertise `aEntry` or register its key.
456
457 if (!aUnregisterKey && !IsKeyRegisteredOrRegistering(aEntry))
458 {
459 aEntry.mShouldRegisterKey = true;
460 aEntry.mKeyAdvId = AllocateNextRequestId();
461 }
462
463 VerifyOrExit(!aEntry.mShouldAdvertise);
464
465 if (aUnregisterEntry || aEntry.IsDeleted())
466 {
467 aEntry.mShouldAdvertise = aEntry.mIsRegistered;
468 }
469 else if (!IsRegisteredOrRegistering(aEntry))
470 {
471 aEntry.mShouldAdvertise = true;
472 aEntry.mAdvId = AllocateNextRequestId();
473 }
474
475 exit:
476 return;
477 }
478
Advertise(Host & aHost)479 void AdvertisingProxy::Advertise(Host &aHost)
480 {
481 bool shouldUnregisterHostAndServices = aHost.IsDeleted();
482 bool shouldUnregisterKeys = (aHost.mKeyLease == 0);
483
484 DecideToAdvertise(aHost, shouldUnregisterHostAndServices, shouldUnregisterKeys);
485
486 for (Service &service : aHost.mServices)
487 {
488 DecideToAdvertise(service, shouldUnregisterHostAndServices, shouldUnregisterKeys);
489 }
490
491 // We call `UpdateAdvIdRangeOn()` to determine the `mAdvIdRange`
492 // on `aHost` before we call any of `UnregisterHost()`,
493 // `UnregisterService()`, or `UnregisterKey()` methods, and
494 // and receive any `HandleRegistered()` callbacks. The DNS-SD
495 // platform may invoke `HandleRegistered()` callbacks from within
496 // the `Register{Host/Service/Key}()` calls.
497
498 UpdateAdvIdRangeOn(aHost);
499
500 if (shouldUnregisterKeys)
501 {
502 UnregisterKey(aHost);
503 }
504 else if (aHost.mShouldRegisterKey)
505 {
506 RegisterKey(aHost);
507 }
508
509 // We register host first before any of its services.
510 // But if we need to unregister host, it is done after
511 // all services.
512
513 if (aHost.mShouldAdvertise && !shouldUnregisterHostAndServices)
514 {
515 RegisterHost(aHost);
516 }
517
518 for (Service &service : aHost.mServices)
519 {
520 if (shouldUnregisterKeys)
521 {
522 UnregisterKey(service);
523 }
524 else if (service.mShouldRegisterKey)
525 {
526 RegisterKey(service);
527 }
528
529 if (service.mShouldAdvertise)
530 {
531 if (shouldUnregisterHostAndServices || service.IsDeleted())
532 {
533 UnregisterService(service);
534 }
535 else
536 {
537 RegisterService(service);
538 }
539 }
540 }
541
542 if (aHost.mShouldAdvertise && shouldUnregisterHostAndServices)
543 {
544 UnregisterHost(aHost);
545 }
546 }
547
UnregisterHostAndItsServicesAndKeys(Host & aHost)548 void AdvertisingProxy::UnregisterHostAndItsServicesAndKeys(Host &aHost)
549 {
550 for (Service &service : aHost.mServices)
551 {
552 if (service.mIsKeyRegistered)
553 {
554 UnregisterKey(service);
555 }
556
557 if (!service.mIsReplaced && IsRegisteredOrRegistering(service))
558 {
559 UnregisterService(service);
560 }
561 }
562
563 if (aHost.mIsKeyRegistered)
564 {
565 UnregisterKey(aHost);
566 }
567
568 if (!aHost.mIsReplaced && IsRegisteredOrRegistering(aHost))
569 {
570 UnregisterHost(aHost);
571 }
572 }
573
CompareAndUpdateHostAndServices(Host & aHost,Host & aExistingHost)574 bool AdvertisingProxy::CompareAndUpdateHostAndServices(Host &aHost, Host &aExistingHost)
575 {
576 // This method compares and updates flags used by `AdvertisingProxy`
577 // on new `aHost` and `aExistingHost` and their services.
578 //
579 // It returns a boolean indicating whether the new `aHost` replaced
580 // any of the entries on the `aExistingHost`.
581 //
582 // The `AdvertisingProxy` uses the following flags and variables
583 // on `Host` and `Service` entries:
584 //
585 // - `mIsRegistered` indicates whether or not the entry has been
586 // successfully registered by the proxy.
587 //
588 // - `mIsKeyRegistered` indicates whether or not a key record
589 // associated with the entry name has been successfully
590 // registered by the proxy on infrastructure DNS-SD.
591 //
592 // - `mAdvId` specifies the ongoing registration request ID
593 // associated with this entry by the proxy. A value of zero or
594 // `kInvalidRequestId` indicates that there is no ongoing
595 // registration for this entry.
596 //
597 // - `mKeyAdvId` is similar to `mAdvId` but for registering the
598 // key record.
599 //
600 // - `mIsReplaced` tracks whether this entry has been replaced by
601 // a newer advertisement request that changes some of its
602 // parameters. For example, the address list could have been
603 // changed on a `Host`, or TXT Data, or the list of sub-types,
604 // or port number could have been changed on a `Service`.
605 //
606 // - `mShouldAdvertise` is only used in the `Advertise()` call
607 // chain to track whether we need to advertise the entry.
608 //
609 // - `mShouldRegisterKey` is similar to `mShouldAdvertise` and
610 // only used in `Advertise()` call chain.
611
612 bool replaced = false;
613
614 VerifyOrExit(&aHost != &aExistingHost);
615
616 replaced = CompareAndUpdateHost(aHost, aExistingHost);
617
618 // Compare services of `aHost` against services of
619 // `aExistingHost`.
620
621 for (Service &service : aHost.mServices)
622 {
623 Service *existingService = aExistingHost.mServices.FindMatching(service.GetInstanceName());
624
625 if (existingService != nullptr)
626 {
627 replaced |= CompareAndUpdateService(service, *existingService);
628 }
629 }
630
631 exit:
632 return replaced;
633 }
634
UpdateKeyRegistrationStatus(Entry & aEntry,const Entry & aExistingEntry)635 template <typename Entry> void AdvertisingProxy::UpdateKeyRegistrationStatus(Entry &aEntry, const Entry &aExistingEntry)
636 {
637 // Updates key registration status on `aEntry` based
638 // on its state on `aExistingEntry`.
639
640 static_assert(TypeTraits::IsSame<Entry, Host>::kValue || TypeTraits::IsSame<Entry, Service>::kValue,
641 "`Entry` must be `Host` or `Service` types");
642
643 // If the new `aEntry` has a zero key lease, we always unregister
644 // it, just to be safe. Therefore, there is no need to check the
645 // key registration status of the existing `aExistingEntry`.
646
647 VerifyOrExit(aEntry.GetKeyLease() != 0);
648
649 VerifyOrExit(!IsKeyRegisteredOrRegistering(aEntry));
650
651 if (aExistingEntry.mIsKeyRegistered)
652 {
653 aEntry.mIsKeyRegistered = true;
654 }
655 else
656 {
657 // Use the key registration request ID by `aExistingEntry` for
658 // the new `aEntry` if there is any. If there is none the
659 // `mKeyAdvId` remains as `kInvalidRequestId`.
660
661 aEntry.mKeyAdvId = aExistingEntry.mKeyAdvId;
662 }
663
664 exit:
665 return;
666 }
667
668 // NOLINTNEXTLINE(readability-inconsistent-declaration-parameter-name)
EntriesMatch(const Host & aFirstHost,const Host & aSecondHost)669 template <> bool AdvertisingProxy::EntriesMatch(const Host &aFirstHost, const Host &aSecondHost)
670 {
671 bool match = false;
672
673 VerifyOrExit(aFirstHost.IsDeleted() == aSecondHost.IsDeleted());
674
675 if (aFirstHost.IsDeleted())
676 {
677 match = true;
678 ExitNow();
679 }
680
681 VerifyOrExit(aFirstHost.mAddresses.GetLength() == aSecondHost.mAddresses.GetLength());
682
683 for (const Ip6::Address &address : aFirstHost.mAddresses)
684 {
685 VerifyOrExit(aSecondHost.mAddresses.Contains(address));
686 }
687
688 match = true;
689
690 exit:
691 return match;
692 }
693
694 // NOLINTNEXTLINE(readability-inconsistent-declaration-parameter-name)
EntriesMatch(const Service & aFirstService,const Service & aSecondService)695 template <> bool AdvertisingProxy::EntriesMatch(const Service &aFirstService, const Service &aSecondService)
696 {
697 bool match = false;
698
699 VerifyOrExit(aFirstService.IsDeleted() == aSecondService.IsDeleted());
700
701 if (aFirstService.IsDeleted())
702 {
703 match = true;
704 ExitNow();
705 }
706
707 VerifyOrExit(aFirstService.GetPort() == aSecondService.GetPort());
708 VerifyOrExit(aFirstService.GetWeight() == aSecondService.GetWeight());
709 VerifyOrExit(aFirstService.GetPriority() == aSecondService.GetPriority());
710 VerifyOrExit(aFirstService.GetTtl() == aSecondService.GetTtl());
711
712 VerifyOrExit(aFirstService.GetNumberOfSubTypes() == aSecondService.GetNumberOfSubTypes());
713
714 for (uint16_t index = 0; index < aFirstService.GetNumberOfSubTypes(); index++)
715 {
716 VerifyOrExit(aSecondService.HasSubTypeServiceName(aFirstService.GetSubTypeServiceNameAt(index)));
717 }
718
719 VerifyOrExit(aFirstService.GetTxtDataLength() == aSecondService.GetTxtDataLength());
720 VerifyOrExit(!memcmp(aFirstService.GetTxtData(), aSecondService.GetTxtData(), aFirstService.GetTxtDataLength()));
721
722 match = true;
723
724 exit:
725 return match;
726 }
727
CompareAndUpdate(Entry & aEntry,Entry & aExistingEntry)728 template <typename Entry> bool AdvertisingProxy::CompareAndUpdate(Entry &aEntry, Entry &aExistingEntry)
729 {
730 // This is called when the new `aEntry` is not deleted.
731
732 bool replaced = false;
733
734 // If we previously determined that `aEntry` is registered,
735 // nothing else to do.
736
737 VerifyOrExit(!aEntry.mIsRegistered);
738
739 if (aEntry.mShouldAdvertise || aExistingEntry.mIsReplaced || !EntriesMatch(aEntry, aExistingEntry))
740 {
741 // If we previously determined that we should advertise the
742 // new `aEntry`, we enter this block to mark `aExistingEntry`
743 // as being replaced.
744 //
745 // If `aExistingEntry` was already marked as replaced, we
746 // cannot compare it to the new `aEntry`. Therefore, we assume
747 // that there may be a change and always advertise the new
748 // `aEntry`. Otherwise, we compare it to the new `aEntry` using
749 // `EntriesMatch()` and only if there are any differences, we
750 // mark that `aEntry` needs to be advertised.
751
752 aExistingEntry.mIsReplaced = true;
753 replaced = true;
754
755 if (aEntry.mAdvId == kInvalidRequestId)
756 {
757 aEntry.mShouldAdvertise = true;
758 aEntry.mAdvId = AllocateNextRequestId();
759 }
760
761 // If there is an outstanding registration request for
762 // `aExistingEntry` we replace it with the request ID of the
763 // new `aEntry` registration.
764
765 if (aExistingEntry.mAdvId != kInvalidRequestId)
766 {
767 aExistingEntry.mAdvId = aEntry.mAdvId;
768 UpdateAdvIdRangeOn(aExistingEntry);
769 }
770
771 ExitNow();
772 }
773
774 // `aEntry` fully matches `aExistingEntry` and `aExistingEntry` was
775 // not replaced.
776
777 VerifyOrExit(aEntry.mAdvId == kInvalidRequestId);
778
779 if (aExistingEntry.mIsRegistered)
780 {
781 aEntry.mIsRegistered = true;
782 }
783 else if (aExistingEntry.mAdvId != kInvalidRequestId)
784 {
785 // There is an outstanding registration request for
786 // `aExistingEntry`. We use the same ID for the new `aEntry`.
787
788 aEntry.mAdvId = aExistingEntry.mAdvId;
789 }
790 else
791 {
792 // The earlier advertisement of `aExistingEntry` seems to have
793 // failed since there is no outstanding registration request
794 // (no ID) and it is not marked as registered. We mark the
795 // new `aEntry` to be advertised (to try again).
796
797 aEntry.mShouldAdvertise = true;
798 aEntry.mAdvId = AllocateNextRequestId();
799 aExistingEntry.mIsReplaced = true;
800 }
801
802 exit:
803 return replaced;
804 }
805
CompareAndUpdateHost(Host & aHost,Host & aExistingHost)806 bool AdvertisingProxy::CompareAndUpdateHost(Host &aHost, Host &aExistingHost)
807 {
808 bool replaced = false;
809
810 UpdateKeyRegistrationStatus(aHost, aExistingHost);
811
812 if (!aHost.IsDeleted())
813 {
814 replaced = CompareAndUpdate(aHost, aExistingHost);
815 ExitNow();
816 }
817
818 // The new `aHost` is removing the host and all its services.
819
820 if (aExistingHost.IsDeleted())
821 {
822 // If `aHost` has zero key-lease (fully removed),
823 // we need to unregister keys for any services on
824 // existing host that are not present in `aHost`.
825
826 if (aHost.mKeyLease == 0)
827 {
828 for (Service &existingService : aExistingHost.mServices)
829 {
830 if (!aHost.HasService(existingService.GetInstanceName()))
831 {
832 UnregisterKey(existingService);
833 }
834 }
835 }
836
837 ExitNow();
838 }
839
840 // `aExistingHost` is updating the same host that is being
841 // removed by the new `aHost` update. We need to advertise
842 // the new `aHost` to make sure it is unregistered.
843
844 aHost.mShouldAdvertise = true;
845
846 // We unregister any services that were registered by
847 // `aExistingHost` but are not included in the now being
848 // removed `aHost`, and unregister any registered keys when
849 // `aHost` has zero key lease.
850
851 for (Service &existingService : aExistingHost.mServices)
852 {
853 if (existingService.IsDeleted())
854 {
855 if (aHost.GetKeyLease() == 0)
856 {
857 existingService.mIsReplaced = true;
858 UnregisterKey(existingService);
859 }
860
861 continue;
862 }
863
864 if (aHost.HasService(existingService.GetInstanceName()))
865 {
866 // The `existingService` that are contained in `aHost`
867 // are updated in `CompareAndUpdateService()`.
868 continue;
869 }
870
871 UnregisterService(existingService);
872
873 existingService.mIsReplaced = true;
874
875 if (aHost.GetKeyLease() == 0)
876 {
877 UnregisterKey(existingService);
878 }
879 }
880
881 aExistingHost.mAdvId = kInvalidRequestId;
882 aExistingHost.mIsReplaced = true;
883 replaced = true;
884
885 if (aHost.GetKeyLease() == 0)
886 {
887 UnregisterKey(aExistingHost);
888 }
889
890 UpdateAdvIdRangeOn(aExistingHost);
891
892 exit:
893 return replaced;
894 }
895
CompareAndUpdateService(Service & aService,Service & aExistingService)896 bool AdvertisingProxy::CompareAndUpdateService(Service &aService, Service &aExistingService)
897 {
898 bool replaced = false;
899
900 UpdateKeyRegistrationStatus(aService, aExistingService);
901
902 if (!aService.IsDeleted())
903 {
904 replaced = CompareAndUpdate(aService, aExistingService);
905 ExitNow();
906 }
907
908 if (aExistingService.IsDeleted())
909 {
910 ExitNow();
911 }
912
913 aService.mShouldAdvertise = true;
914
915 aExistingService.mIsReplaced = true;
916 replaced = true;
917
918 if (aExistingService.mAdvId != kInvalidRequestId)
919 {
920 // If there is an outstanding registration request for the
921 // existing service, clear its request ID.
922
923 aExistingService.mAdvId = kInvalidRequestId;
924
925 UpdateAdvIdRangeOn(*aExistingService.mHost);
926 }
927
928 exit:
929 return replaced;
930 }
931
RegisterHost(Host & aHost)932 void AdvertisingProxy::RegisterHost(Host &aHost)
933 {
934 Error error = kErrorNone;
935 Dnssd::Host hostInfo;
936 DnsName hostName;
937 Heap::Array<Ip6::Address> hostAddresses;
938
939 aHost.mShouldAdvertise = false;
940
941 CopyNameAndRemoveDomain(hostName, aHost.GetFullName());
942
943 SuccessOrExit(error = hostAddresses.ReserveCapacity(aHost.mAddresses.GetLength()));
944
945 for (const Ip6::Address &address : aHost.mAddresses)
946 {
947 if (!address.IsLinkLocalUnicast() && !Get<Mle::Mle>().IsMeshLocalAddress(address))
948 {
949 IgnoreError(hostAddresses.PushBack(address));
950 }
951 }
952
953 LogInfo("Registering host '%s', id:%lu", hostName, ToUlong(aHost.mAdvId));
954
955 hostInfo.Clear();
956 hostInfo.mHostName = hostName;
957 hostInfo.mAddresses = hostAddresses.AsCArray();
958 hostInfo.mAddressesLength = hostAddresses.GetLength();
959 hostInfo.mTtl = aHost.GetTtl();
960 hostInfo.mInfraIfIndex = Get<BorderRouter::InfraIf>().GetIfIndex();
961
962 Get<Dnssd>().RegisterHost(hostInfo, aHost.mAdvId, HandleRegistered);
963
964 exit:
965 if (error != kErrorNone)
966 {
967 LogWarn("Error %s registering host '%s'", ErrorToString(error), hostName);
968 }
969 }
970
UnregisterHost(Host & aHost)971 void AdvertisingProxy::UnregisterHost(Host &aHost)
972 {
973 Dnssd::Host hostInfo;
974 DnsName hostName;
975
976 aHost.mShouldAdvertise = false;
977 aHost.mIsRegistered = false;
978 aHost.mAdvId = false;
979
980 CopyNameAndRemoveDomain(hostName, aHost.GetFullName());
981
982 LogInfo("Unregistering host '%s'", hostName);
983
984 hostInfo.Clear();
985 hostInfo.mHostName = hostName;
986 hostInfo.mInfraIfIndex = Get<BorderRouter::InfraIf>().GetIfIndex();
987
988 Get<Dnssd>().UnregisterHost(hostInfo, 0, nullptr);
989 }
990
RegisterService(Service & aService)991 void AdvertisingProxy::RegisterService(Service &aService)
992 {
993 Error error = kErrorNone;
994 Dnssd::Service serviceInfo;
995 DnsName hostName;
996 DnsName serviceName;
997 Heap::Array<Heap::String> subTypeHeapStrings;
998 Heap::Array<const char *> subTypeLabels;
999
1000 aService.mShouldAdvertise = false;
1001
1002 CopyNameAndRemoveDomain(hostName, aService.GetHost().GetFullName());
1003 CopyNameAndRemoveDomain(serviceName, aService.GetServiceName());
1004
1005 SuccessOrExit(error = subTypeHeapStrings.ReserveCapacity(aService.mSubTypes.GetLength()));
1006 SuccessOrExit(error = subTypeLabels.ReserveCapacity(aService.mSubTypes.GetLength()));
1007
1008 for (const Heap::String &subTypeName : aService.mSubTypes)
1009 {
1010 char label[Dns::Name::kMaxLabelSize];
1011 Heap::String labelString;
1012
1013 IgnoreError(Server::Service::ParseSubTypeServiceName(subTypeName.AsCString(), label, sizeof(label)));
1014 SuccessOrExit(error = labelString.Set(label));
1015 IgnoreError(subTypeHeapStrings.PushBack(static_cast<Heap::String &&>(labelString)));
1016 IgnoreError(subTypeLabels.PushBack(subTypeHeapStrings.Back()->AsCString()));
1017 }
1018
1019 LogInfo("Registering service '%s' '%s' on '%s', id:%lu", aService.GetInstanceLabel(), serviceName, hostName,
1020 ToUlong(aService.mAdvId));
1021
1022 serviceInfo.Clear();
1023 serviceInfo.mHostName = hostName;
1024 serviceInfo.mServiceInstance = aService.GetInstanceLabel();
1025 serviceInfo.mServiceType = serviceName;
1026 serviceInfo.mSubTypeLabels = subTypeLabels.AsCArray();
1027 serviceInfo.mSubTypeLabelsLength = subTypeLabels.GetLength();
1028 serviceInfo.mTxtData = aService.GetTxtData();
1029 serviceInfo.mTxtDataLength = aService.GetTxtDataLength();
1030 serviceInfo.mPort = aService.GetPort();
1031 serviceInfo.mWeight = aService.GetWeight();
1032 serviceInfo.mPriority = aService.GetPriority();
1033 serviceInfo.mTtl = aService.GetTtl();
1034 serviceInfo.mInfraIfIndex = Get<BorderRouter::InfraIf>().GetIfIndex();
1035
1036 Get<Dnssd>().RegisterService(serviceInfo, aService.mAdvId, HandleRegistered);
1037
1038 exit:
1039 if (error != kErrorNone)
1040 {
1041 LogWarn("Error %s registering service '%s' '%s'", ErrorToString(error), aService.GetInstanceLabel(),
1042 serviceName);
1043 }
1044 }
1045
UnregisterService(Service & aService)1046 void AdvertisingProxy::UnregisterService(Service &aService)
1047 {
1048 Dnssd::Service serviceInfo;
1049 DnsName hostName;
1050 DnsName serviceName;
1051
1052 aService.mShouldAdvertise = false;
1053 aService.mIsRegistered = false;
1054 aService.mAdvId = kInvalidRequestId;
1055
1056 CopyNameAndRemoveDomain(hostName, aService.GetHost().GetFullName());
1057 CopyNameAndRemoveDomain(serviceName, aService.GetServiceName());
1058
1059 LogInfo("Unregistering service '%s' '%s' on '%s'", aService.GetInstanceLabel(), serviceName, hostName);
1060
1061 serviceInfo.Clear();
1062 serviceInfo.mHostName = hostName;
1063 serviceInfo.mServiceInstance = aService.GetInstanceLabel();
1064 serviceInfo.mServiceType = serviceName;
1065 serviceInfo.mInfraIfIndex = Get<BorderRouter::InfraIf>().GetIfIndex();
1066
1067 Get<Dnssd>().UnregisterService(serviceInfo, 0, nullptr);
1068 }
1069
RegisterKey(Host & aHost)1070 void AdvertisingProxy::RegisterKey(Host &aHost)
1071 {
1072 DnsName hostName;
1073
1074 aHost.mShouldRegisterKey = false;
1075
1076 CopyNameAndRemoveDomain(hostName, aHost.GetFullName());
1077
1078 LogInfo("Registering key for host '%s', id:%lu", hostName, ToUlong(aHost.mKeyAdvId));
1079
1080 RegisterKey(hostName, /* aServiceType */ nullptr, aHost.mKey, aHost.mKeyAdvId, aHost.GetTtl());
1081 }
1082
RegisterKey(Service & aService)1083 void AdvertisingProxy::RegisterKey(Service &aService)
1084 {
1085 DnsName serviceType;
1086
1087 aService.mShouldRegisterKey = false;
1088
1089 CopyNameAndRemoveDomain(serviceType, aService.GetServiceName());
1090
1091 LogInfo("Registering key for service '%s' '%s', id:%lu", aService.GetInstanceLabel(), serviceType,
1092 ToUlong(aService.mKeyAdvId));
1093
1094 RegisterKey(aService.GetInstanceLabel(), serviceType, aService.mHost->mKey, aService.mKeyAdvId, aService.GetTtl());
1095 }
1096
RegisterKey(const char * aName,const char * aServiceType,const Host::Key & aKey,RequestId aRequestId,uint32_t aTtl)1097 void AdvertisingProxy::RegisterKey(const char *aName,
1098 const char *aServiceType,
1099 const Host::Key &aKey,
1100 RequestId aRequestId,
1101 uint32_t aTtl)
1102 {
1103 Dnssd::Key keyInfo;
1104 Dns::Ecdsa256KeyRecord keyRecord;
1105
1106 keyRecord.Init();
1107 keyRecord.SetFlags(Dns::KeyRecord::kAuthConfidPermitted, Dns::KeyRecord::kOwnerNonZone,
1108 Dns::KeyRecord::kSignatoryFlagGeneral);
1109 keyRecord.SetProtocol(Dns::KeyRecord::kProtocolDnsSec);
1110 keyRecord.SetAlgorithm(Dns::KeyRecord::kAlgorithmEcdsaP256Sha256);
1111 keyRecord.SetLength(sizeof(Dns::Ecdsa256KeyRecord) - sizeof(Dns::ResourceRecord));
1112 keyRecord.SetKey(aKey);
1113
1114 keyInfo.Clear();
1115 keyInfo.mName = aName;
1116 keyInfo.mServiceType = aServiceType;
1117 keyInfo.mKeyData = reinterpret_cast<uint8_t *>(&keyRecord) + sizeof(Dns::ResourceRecord);
1118 keyInfo.mKeyDataLength = keyRecord.GetLength();
1119 keyInfo.mClass = Dns::ResourceRecord::kClassInternet;
1120 keyInfo.mTtl = aTtl;
1121 keyInfo.mInfraIfIndex = Get<BorderRouter::InfraIf>().GetIfIndex();
1122
1123 Get<Dnssd>().RegisterKey(keyInfo, aRequestId, HandleRegistered);
1124 }
1125
UnregisterKey(Host & aHost)1126 void AdvertisingProxy::UnregisterKey(Host &aHost)
1127 {
1128 DnsName hostName;
1129
1130 aHost.mIsKeyRegistered = false;
1131 aHost.mKeyAdvId = kInvalidRequestId;
1132
1133 CopyNameAndRemoveDomain(hostName, aHost.GetFullName());
1134
1135 LogInfo("Unregistering key for host '%s'", hostName);
1136
1137 UnregisterKey(hostName, /* aServiceType */ nullptr);
1138 }
1139
UnregisterKey(Service & aService)1140 void AdvertisingProxy::UnregisterKey(Service &aService)
1141 {
1142 DnsName serviceType;
1143
1144 aService.mIsKeyRegistered = false;
1145 aService.mKeyAdvId = kInvalidRequestId;
1146
1147 CopyNameAndRemoveDomain(serviceType, aService.GetServiceName());
1148
1149 LogInfo("Unregistering key for service '%s' '%s'", aService.GetInstanceLabel(), serviceType);
1150
1151 UnregisterKey(aService.GetInstanceLabel(), serviceType);
1152 }
1153
UnregisterKey(const char * aName,const char * aServiceType)1154 void AdvertisingProxy::UnregisterKey(const char *aName, const char *aServiceType)
1155 {
1156 Dnssd::Key keyInfo;
1157
1158 keyInfo.Clear();
1159 keyInfo.mName = aName;
1160 keyInfo.mServiceType = aServiceType;
1161 keyInfo.mInfraIfIndex = Get<BorderRouter::InfraIf>().GetIfIndex();
1162
1163 Get<Dnssd>().UnregisterKey(keyInfo, 0, nullptr);
1164 }
1165
CopyNameAndRemoveDomain(DnsName & aName,const char * aFullName)1166 void AdvertisingProxy::CopyNameAndRemoveDomain(DnsName &aName, const char *aFullName)
1167 {
1168 IgnoreError(Dns::Name::ExtractLabels(aFullName, Get<Server>().GetDomain(), aName, sizeof(aName)));
1169 }
1170
HandleRegistered(otInstance * aInstance,otPlatDnssdRequestId aRequestId,otError aError)1171 void AdvertisingProxy::HandleRegistered(otInstance *aInstance, otPlatDnssdRequestId aRequestId, otError aError)
1172 {
1173 AsCoreType(aInstance).Get<AdvertisingProxy>().HandleRegistered(aRequestId, aError);
1174 }
1175
HandleRegistered(RequestId aRequestId,Error aError)1176 void AdvertisingProxy::HandleRegistered(RequestId aRequestId, Error aError)
1177 {
1178 LogInfo("Register callback, id:%lu, error:%s", ToUlong(aRequestId), ErrorToString(aError));
1179
1180 VerifyOrExit(mState == kStateRunning);
1181
1182 for (Host &host : Get<Server>().mHosts)
1183 {
1184 HandleRegisteredRequestIdOn(host, aRequestId, aError);
1185 }
1186
1187 for (AdvInfo &adv : mAdvInfoList)
1188 {
1189 if (HandleRegisteredRequestIdOn(adv.mHost, aRequestId, aError))
1190 {
1191 if (adv.mError == kErrorNone)
1192 {
1193 adv.mError = aError;
1194 }
1195
1196 if (adv.IsCompleted())
1197 {
1198 mTasklet.Post();
1199 }
1200 }
1201 }
1202
1203 exit:
1204 return;
1205 }
1206
HandleRegisteredRequestIdOn(Host & aHost,RequestId aRequestId,Error aError)1207 bool AdvertisingProxy::HandleRegisteredRequestIdOn(Host &aHost, RequestId aRequestId, Error aError)
1208 {
1209 // Handles "registration request callback" for `aRequestId` on a
1210 // given `aHost`. Returns `true`, if the ID matched an entry
1211 // on `aHost` and `aHost` was updated, `false` otherwise.
1212
1213 bool didUpdate = false;
1214
1215 VerifyOrExit(aHost.mAdvIdRange.Contains(aRequestId));
1216
1217 if (aHost.mAdvId == aRequestId)
1218 {
1219 aHost.mAdvId = kInvalidRequestId;
1220 aHost.mIsRegistered = (aError == kErrorNone);
1221 didUpdate = true;
1222 }
1223
1224 if (aHost.mKeyAdvId == aRequestId)
1225 {
1226 aHost.mKeyAdvId = kInvalidRequestId;
1227 aHost.mIsKeyRegistered = true;
1228 didUpdate = true;
1229 }
1230
1231 for (Service &service : aHost.mServices)
1232 {
1233 if (service.mAdvId == aRequestId)
1234 {
1235 service.mAdvId = kInvalidRequestId;
1236 service.mIsRegistered = (aError == kErrorNone);
1237 didUpdate = true;
1238 }
1239
1240 if (service.mKeyAdvId == aRequestId)
1241 {
1242 service.mKeyAdvId = kInvalidRequestId;
1243 service.mIsKeyRegistered = true;
1244 didUpdate = true;
1245 }
1246 }
1247
1248 UpdateAdvIdRangeOn(aHost);
1249
1250 exit:
1251 return didUpdate;
1252 }
1253
HandleTimer(void)1254 void AdvertisingProxy::HandleTimer(void)
1255 {
1256 NextFireTime nextTime;
1257 OwningList<AdvInfo> expiredList;
1258
1259 VerifyOrExit(mState == kStateRunning);
1260
1261 mAdvInfoList.RemoveAllMatching(AdvInfo::ExpirationChecker(nextTime.GetNow()), expiredList);
1262
1263 for (AdvInfo &adv : mAdvInfoList)
1264 {
1265 nextTime.UpdateIfEarlier(adv.mExpireTime);
1266 }
1267
1268 mTimer.FireAtIfEarlier(nextTime);
1269
1270 for (AdvInfo &adv : expiredList)
1271 {
1272 adv.mError = kErrorResponseTimeout;
1273 adv.mBlockingAdv = nullptr;
1274 adv.mHost.mAdvIdRange.Clear();
1275 SignalAdvCompleted(adv);
1276 }
1277
1278 exit:
1279 return;
1280 }
1281
HandleTasklet(void)1282 void AdvertisingProxy::HandleTasklet(void)
1283 {
1284 VerifyOrExit(mState == kStateRunning);
1285
1286 while (true)
1287 {
1288 OwningList<AdvInfo> completedList;
1289
1290 mAdvInfoList.RemoveAllMatching(AdvInfo::CompletionChecker(), completedList);
1291
1292 VerifyOrExit(!completedList.IsEmpty());
1293
1294 // `RemoveAllMatching()` reverses the order of removed entries
1295 // from `mAdvInfoList` (which itself keeps the later requests
1296 // towards the head of the list). This means that the
1297 // `completedList` will be sorted from earliest request to
1298 // latest and this is the order that we want to notify
1299 // `Srp::Server`.
1300
1301 for (AdvInfo &adv : completedList)
1302 {
1303 SignalAdvCompleted(adv);
1304 }
1305
1306 completedList.Clear();
1307 }
1308
1309 exit:
1310 return;
1311 }
1312
SignalAdvCompleted(AdvInfo & aAdvInfo)1313 void AdvertisingProxy::SignalAdvCompleted(AdvInfo &aAdvInfo)
1314 {
1315 // Check if any outstanding advertisements in the list
1316 // is blocked by `aAdvInfo` and unblock.
1317
1318 for (AdvInfo &adv : mAdvInfoList)
1319 {
1320 if (adv.mBlockingAdv == &aAdvInfo)
1321 {
1322 adv.mBlockingAdv = nullptr;
1323
1324 if (adv.IsCompleted())
1325 {
1326 mTasklet.Post();
1327 }
1328 }
1329 }
1330
1331 switch (aAdvInfo.mError)
1332 {
1333 case kErrorNone:
1334 mCounters.mAdvSuccessful++;
1335 break;
1336 case kErrorResponseTimeout:
1337 mCounters.mAdvTimeout++;
1338 break;
1339 default:
1340 mCounters.mAdvRejected++;
1341 break;
1342 }
1343
1344 aAdvInfo.SignalServerToCommit();
1345 }
1346
1347 //---------------------------------------------------------------------------------------------------------------------
1348 // AdvertisingProxy::AdvInfo
1349
AdvInfo(Host & aHost,const Server::MessageMetadata & aMetadata,uint32_t aTimeout)1350 AdvertisingProxy::AdvInfo::AdvInfo(Host &aHost, const Server::MessageMetadata &aMetadata, uint32_t aTimeout)
1351 : mNext(nullptr)
1352 , mBlockingAdv(nullptr)
1353 , mHost(aHost)
1354 , mExpireTime(TimerMilli::GetNow() + aTimeout)
1355 , mMessageMetadata(aMetadata)
1356 , mError(kErrorNone)
1357 {
1358 if (aMetadata.mMessageInfo != nullptr)
1359 {
1360 // If `mMessageInfo` is not null in the given `aMetadata` keep
1361 // a copy of it in `AdvInfo` structure and update the
1362 // `mMessageMetadata` to point to the local copy instead.
1363
1364 mMessageInfo = *aMetadata.mMessageInfo;
1365 mMessageMetadata.mMessageInfo = &mMessageInfo;
1366 }
1367 }
1368
SignalServerToCommit(void)1369 void AdvertisingProxy::AdvInfo::SignalServerToCommit(void)
1370 {
1371 LogInfo("Adv done '%s', error:%s", mHost.GetFullName(), ErrorToString(mError));
1372 Get<Server>().CommitSrpUpdate(mError, mHost, mMessageMetadata);
1373 }
1374
IsCompleted(void) const1375 bool AdvertisingProxy::AdvInfo::IsCompleted(void) const
1376 {
1377 bool isCompleted = false;
1378
1379 VerifyOrExit(mBlockingAdv == nullptr);
1380 isCompleted = (mError != kErrorNone) || mHost.mAdvIdRange.IsEmpty();
1381
1382 exit:
1383 return isCompleted;
1384 }
1385
1386 } // namespace Srp
1387 } // namespace ot
1388
1389 #endif // OPENTHREAD_CONFIG_SRP_SERVER_ADVERTISING_PROXY_ENABLE
1390