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