1 /*
2  *  Copyright (c) 2020, 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 "srp_client.hpp"
30 
31 #if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
32 
33 #include "common/code_utils.hpp"
34 #include "common/debug.hpp"
35 #include "common/instance.hpp"
36 #include "common/locator_getters.hpp"
37 #include "common/logging.hpp"
38 #include "common/numeric_limits.hpp"
39 #include "common/random.hpp"
40 #include "common/settings.hpp"
41 #include "common/string.hpp"
42 #include "thread/network_data_service.hpp"
43 
44 /**
45  * @file
46  *   This file implements the SRP client.
47  */
48 
49 namespace ot {
50 namespace Srp {
51 
52 //---------------------------------------------------------------------
53 // Client::HostInfo
54 
Init(void)55 void Client::HostInfo::Init(void)
56 {
57     Clearable<HostInfo>::Clear();
58 
59     // State is directly set on `mState` instead of using `SetState()`
60     // to avoid logging.
61     mState = OT_SRP_CLIENT_ITEM_STATE_REMOVED;
62 }
63 
Clear(void)64 void Client::HostInfo::Clear(void)
65 {
66     Clearable<HostInfo>::Clear();
67     SetState(kRemoved);
68 }
69 
SetState(ItemState aState)70 void Client::HostInfo::SetState(ItemState aState)
71 {
72     if (aState != GetState())
73     {
74         otLogInfoSrp("[client] HostInfo %s -> %s", ItemStateToString(GetState()), ItemStateToString(aState));
75         mState = static_cast<otSrpClientItemState>(aState);
76     }
77 }
78 
SetAddresses(const Ip6::Address * aAddresses,uint8_t aNumAddresses)79 void Client::HostInfo::SetAddresses(const Ip6::Address *aAddresses, uint8_t aNumAddresses)
80 {
81     mAddresses    = aAddresses;
82     mNumAddresses = aNumAddresses;
83 
84     otLogInfoSrp("[client] HostInfo set %d addrs", GetNumAddresses());
85 
86     for (uint8_t index = 0; index < GetNumAddresses(); index++)
87     {
88         otLogInfoSrp("[client] %s", GetAddress(index).ToString().AsCString());
89     }
90 }
91 
92 //---------------------------------------------------------------------
93 // Client::Service
94 
Init(void)95 Error Client::Service::Init(void)
96 {
97     Error error = kErrorNone;
98 
99     VerifyOrExit((GetName() != nullptr) && (GetInstanceName() != nullptr), error = kErrorInvalidArgs);
100     VerifyOrExit((GetTxtEntries() != nullptr) || (GetNumTxtEntries() == 0), error = kErrorInvalidArgs);
101 
102     // State is directly set on `mState` instead of using `SetState()`
103     // to avoid logging.
104     mState = OT_SRP_CLIENT_ITEM_STATE_REMOVED;
105 
106 exit:
107     return error;
108 }
109 
SetState(ItemState aState)110 void Client::Service::SetState(ItemState aState)
111 {
112     VerifyOrExit(GetState() != aState);
113 
114     otLogInfoSrp("[client] Service %s -> %s, \"%s\" \"%s\"", ItemStateToString(GetState()), ItemStateToString(aState),
115                  GetInstanceName(), GetName());
116 
117     if (aState == kToAdd)
118     {
119         constexpr uint16_t kSubTypeLabelStringSize = 80;
120 
121         String<kSubTypeLabelStringSize> string;
122 
123         // Log more details only when entering `kToAdd` state.
124 
125         if (HasSubType())
126         {
127             const char *label;
128 
129             for (uint16_t index = 0; (label = GetSubTypeLabelAt(index)) != nullptr; index++)
130             {
131                 string.Append("%s\"%s\"", (index != 0) ? ", " : "", label);
132             }
133         }
134 
135         otLogInfoSrp("[client] subtypes:[%s] port:%d weight:%d prio:%d txts:%d", string.AsCString(), GetPort(),
136                      GetWeight(), GetPriority(), GetNumTxtEntries());
137     }
138 
139     mState = static_cast<otSrpClientItemState>(aState);
140 
141 exit:
142     return;
143 }
144 
Matches(const Service & aOther) const145 bool Client::Service::Matches(const Service &aOther) const
146 {
147     // This method indicates whether or not two service entries match,
148     // i.e., have the same service and instance names. This is intended
149     // for use by `LinkedList::FindMatching()` to search within the
150     // `mServices` list.
151 
152     return (strcmp(GetName(), aOther.GetName()) == 0) && (strcmp(GetInstanceName(), aOther.GetInstanceName()) == 0);
153 }
154 
155 //---------------------------------------------------------------------
156 // Client
157 
158 const char Client::kDefaultDomainName[] = "default.service.arpa";
159 
Client(Instance & aInstance)160 Client::Client(Instance &aInstance)
161     : InstanceLocator(aInstance)
162     , mState(kStateStopped)
163     , mTxFailureRetryCount(0)
164     , mShouldRemoveKeyLease(false)
165 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
166     , mAutoStartModeEnabled(kAutoStartDefaultMode)
167     , mAutoStartDidSelectServer(false)
168     , mAutoStartIsUsingAnycastAddress(false)
169 #endif
170 #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
171     , mServiceKeyRecordEnabled(false)
172 #endif
173     , mUpdateMessageId(0)
174     , mRetryWaitInterval(kMinRetryWaitInterval)
175     , mAcceptedLeaseInterval(0)
176     , mLeaseInterval(kDefaultLease)
177     , mKeyLeaseInterval(kDefaultKeyLease)
178     , mSocket(aInstance)
179     , mCallback(nullptr)
180     , mCallbackContext(nullptr)
181 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
182     , mAutoStartCallback(nullptr)
183     , mAutoStartContext(nullptr)
184     , mServerSequenceNumber(0)
185 #if OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE
186     , mTimoutFailureCount(0)
187 #endif
188 #endif
189     , mDomainName(kDefaultDomainName)
190     , mTimer(aInstance, Client::HandleTimer)
191 {
192     mHostInfo.Init();
193 
194     // The `Client` implementation uses different constant array of
195     // `ItemState` to define transitions between states in `Pause()`,
196     // `Stop()`, `SendUpdate`, and `ProcessResponse()`, or to convert
197     // an `ItemState` to string. Here, we assert that the enumeration
198     // values are correct.
199 
200     static_assert(kToAdd == 0, "kToAdd value is not correct");
201     static_assert(kAdding == 1, "kAdding value is not correct");
202     static_assert(kToRefresh == 2, "kToRefresh value is not correct");
203     static_assert(kRefreshing == 3, "kRefreshing value is not correct");
204     static_assert(kToRemove == 4, "kToRemove value is not correct");
205     static_assert(kRemoving == 5, "kRemoving value is not correct");
206     static_assert(kRegistered == 6, "kRegistered value is not correct");
207     static_assert(kRemoved == 7, "kRemoved value is not correct");
208 }
209 
Start(const Ip6::SockAddr & aServerSockAddr,Requester aRequester)210 Error Client::Start(const Ip6::SockAddr &aServerSockAddr, Requester aRequester)
211 {
212     Error error;
213 
214     VerifyOrExit(GetState() == kStateStopped,
215                  error = (aServerSockAddr == GetServerAddress()) ? kErrorNone : kErrorBusy);
216 
217     SuccessOrExit(error = mSocket.Open(Client::HandleUdpReceive, this));
218     SuccessOrExit(error = mSocket.Connect(aServerSockAddr));
219 
220     otLogInfoSrp("[client] %starting, server %s", (aRequester == kRequesterUser) ? "S" : "Auto-s",
221                  aServerSockAddr.ToString().AsCString());
222 
223     Resume();
224 
225 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
226     mAutoStartDidSelectServer = (aRequester == kRequesterAuto);
227 
228     if (mAutoStartDidSelectServer)
229     {
230 #if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE && OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_SERVER_ADDRESS_AUTO_SET_ENABLE
231         Get<Dns::Client>().UpdateDefaultConfigAddress();
232 #endif
233 
234         if (mAutoStartCallback != nullptr)
235         {
236             mAutoStartCallback(&aServerSockAddr, mAutoStartContext);
237         }
238     }
239 #endif
240 
241 exit:
242     return error;
243 }
244 
Stop(Requester aRequester,StopMode aMode)245 void Client::Stop(Requester aRequester, StopMode aMode)
246 {
247     // Change the state of host info and services so that they are
248     // added/removed again once the client is started back. In the
249     // case of `kAdding`, we intentionally move to `kToRefresh`
250     // instead of `kToAdd` since the server may receive our add
251     // request and the item may be registered on the server. This
252     // ensures that if we are later asked to remove the item, we do
253     // notify server.
254 
255     static const ItemState kNewStateOnStop[]{
256         /* (0) kToAdd      -> */ kToAdd,
257         /* (1) kAdding     -> */ kToRefresh,
258         /* (2) kToRefresh  -> */ kToRefresh,
259         /* (3) kRefreshing -> */ kToRefresh,
260         /* (4) kToRemove   -> */ kToRemove,
261         /* (5) kRemoving   -> */ kToRemove,
262         /* (6) kRegistered -> */ kToRefresh,
263         /* (7) kRemoved    -> */ kRemoved,
264     };
265 
266     VerifyOrExit(GetState() != kStateStopped);
267 
268     // State changes:
269     //   kAdding     -> kToRefresh
270     //   kRefreshing -> kToRefresh
271     //   kRemoving   -> kToRemove
272     //   kRegistered -> kToRefresh
273 
274     ChangeHostAndServiceStates(kNewStateOnStop);
275 
276     IgnoreError(mSocket.Close());
277 
278     mShouldRemoveKeyLease = false;
279     mTxFailureRetryCount  = 0;
280 
281     if (aMode == kResetRetryInterval)
282     {
283         ResetRetryWaitInterval();
284     }
285 
286     SetState(kStateStopped);
287 
288 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
289 #if OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE
290     mTimoutFailureCount = 0;
291 #endif
292 
293     mAutoStartDidSelectServer = false;
294 
295     if ((aRequester == kRequesterAuto) && (mAutoStartCallback != nullptr))
296     {
297         mAutoStartCallback(nullptr, mAutoStartContext);
298     }
299 #endif
300 
301 exit:
302 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
303     if (aRequester == kRequesterUser)
304     {
305         DisableAutoStartMode();
306     }
307 #endif
308 }
309 
SetCallback(Callback aCallback,void * aContext)310 void Client::SetCallback(Callback aCallback, void *aContext)
311 {
312     mCallback        = aCallback;
313     mCallbackContext = aContext;
314 }
315 
Resume(void)316 void Client::Resume(void)
317 {
318     SetState(kStateUpdated);
319     UpdateState();
320 }
321 
Pause(void)322 void Client::Pause(void)
323 {
324     // Change the state of host info and services that are are being
325     // added or removed so that they are added/removed again once the
326     // client is resumed or started back.
327 
328     static const ItemState kNewStateOnPause[]{
329         /* (0) kToAdd      -> */ kToAdd,
330         /* (1) kAdding     -> */ kToRefresh,
331         /* (2) kToRefresh  -> */ kToRefresh,
332         /* (3) kRefreshing -> */ kToRefresh,
333         /* (4) kToRemove   -> */ kToRemove,
334         /* (5) kRemoving   -> */ kToRemove,
335         /* (6) kRegistered -> */ kRegistered,
336         /* (7) kRemoved    -> */ kRemoved,
337     };
338 
339     // State changes:
340     //   kAdding     -> kToRefresh
341     //   kRefreshing -> kToRefresh
342     //   kRemoving   -> kToRemove
343 
344     ChangeHostAndServiceStates(kNewStateOnPause);
345 
346     SetState(kStatePaused);
347 }
348 
HandleNotifierEvents(Events aEvents)349 void Client::HandleNotifierEvents(Events aEvents)
350 {
351     if (aEvents.Contains(kEventThreadRoleChanged))
352     {
353         HandleRoleChanged();
354     }
355 
356 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
357     if (aEvents.ContainsAny(kEventThreadNetdataChanged | kEventThreadMeshLocalAddrChanged))
358     {
359         ProcessAutoStart();
360     }
361 #endif
362 }
363 
HandleRoleChanged(void)364 void Client::HandleRoleChanged(void)
365 {
366     if (Get<Mle::Mle>().IsAttached())
367     {
368         VerifyOrExit(GetState() == kStatePaused);
369         Resume();
370     }
371     else
372     {
373         VerifyOrExit(GetState() != kStateStopped);
374         Pause();
375     }
376 
377 exit:
378     return;
379 }
380 
381 #if OPENTHREAD_CONFIG_SRP_CLIENT_DOMAIN_NAME_API_ENABLE
SetDomainName(const char * aName)382 Error Client::SetDomainName(const char *aName)
383 {
384     Error error = kErrorNone;
385 
386     VerifyOrExit((mHostInfo.GetState() == kToAdd) || (mHostInfo.GetState() == kRemoved), error = kErrorInvalidState);
387 
388     mDomainName = (aName != nullptr) ? aName : kDefaultDomainName;
389     otLogInfoSrp("[client] Domain name \"%s\"", mDomainName);
390 
391 exit:
392     return error;
393 }
394 #endif
395 
SetHostName(const char * aName)396 Error Client::SetHostName(const char *aName)
397 {
398     Error error = kErrorNone;
399 
400     VerifyOrExit(aName != nullptr, error = kErrorInvalidArgs);
401 
402     VerifyOrExit((mHostInfo.GetState() == kToAdd) || (mHostInfo.GetState() == kRemoved), error = kErrorInvalidState);
403 
404     otLogInfoSrp("[client] Host name \"%s\"", aName);
405     mHostInfo.SetName(aName);
406     mHostInfo.SetState(kToAdd);
407     UpdateState();
408 
409 exit:
410     return error;
411 }
412 
SetHostAddresses(const Ip6::Address * aAddresses,uint8_t aNumAddresses)413 Error Client::SetHostAddresses(const Ip6::Address *aAddresses, uint8_t aNumAddresses)
414 {
415     Error error = kErrorNone;
416 
417     VerifyOrExit((aAddresses != nullptr) && (aNumAddresses > 0), error = kErrorInvalidArgs);
418 
419     VerifyOrExit((mHostInfo.GetState() != kToRemove) && (mHostInfo.GetState() != kRemoving),
420                  error = kErrorInvalidState);
421 
422     if (mHostInfo.GetState() == kRemoved)
423     {
424         mHostInfo.SetState(kToAdd);
425     }
426     else if (mHostInfo.GetState() != kToAdd)
427     {
428         mHostInfo.SetState(kToRefresh);
429     }
430 
431     mHostInfo.SetAddresses(aAddresses, aNumAddresses);
432     UpdateState();
433 
434 exit:
435     return error;
436 }
437 
AddService(Service & aService)438 Error Client::AddService(Service &aService)
439 {
440     Error error;
441 
442     VerifyOrExit(mServices.FindMatching(aService) == nullptr, error = kErrorAlready);
443 
444     SuccessOrExit(error = aService.Init());
445     mServices.Push(aService);
446 
447     aService.SetState(kToAdd);
448     UpdateState();
449 
450 exit:
451     return error;
452 }
453 
RemoveService(Service & aService)454 Error Client::RemoveService(Service &aService)
455 {
456     Error               error = kErrorNone;
457     LinkedList<Service> removedServices;
458 
459     VerifyOrExit(mServices.Contains(aService), error = kErrorNotFound);
460 
461     UpdateServiceStateToRemove(aService);
462 
463     // Check if the service was removed immediately, if so
464     // invoke the callback to report the removed service.
465     GetRemovedServices(removedServices);
466 
467     if (!removedServices.IsEmpty())
468     {
469         InvokeCallback(kErrorNone, mHostInfo, removedServices.GetHead());
470     }
471 
472     UpdateState();
473 
474 exit:
475     return error;
476 }
477 
UpdateServiceStateToRemove(Service & aService)478 void Client::UpdateServiceStateToRemove(Service &aService)
479 {
480     if (aService.GetState() == kToAdd)
481     {
482         // If the service has not been added yet, we can remove it immediately.
483         aService.SetState(kRemoved);
484     }
485     else if (aService.GetState() != kRemoving)
486     {
487         aService.SetState(kToRemove);
488     }
489 }
490 
ClearService(Service & aService)491 Error Client::ClearService(Service &aService)
492 {
493     Error error;
494 
495     SuccessOrExit(error = mServices.Remove(aService));
496     aService.SetNext(nullptr);
497     aService.SetState(kRemoved);
498     UpdateState();
499 
500 exit:
501     return error;
502 }
503 
RemoveHostAndServices(bool aShouldRemoveKeyLease,bool aSendUnregToServer)504 Error Client::RemoveHostAndServices(bool aShouldRemoveKeyLease, bool aSendUnregToServer)
505 {
506     Error error = kErrorNone;
507 
508     otLogInfoSrp("[client] Remove host & services");
509 
510     VerifyOrExit(mHostInfo.GetState() != kRemoved, error = kErrorAlready);
511 
512     if ((mHostInfo.GetState() == kToRemove) || (mHostInfo.GetState() == kRemoving))
513     {
514         // Host info remove is already ongoing, if "key lease" remove mode is
515         // the same, there is no need to send a new update message.
516         VerifyOrExit(mShouldRemoveKeyLease != aShouldRemoveKeyLease);
517     }
518 
519     mShouldRemoveKeyLease = aShouldRemoveKeyLease;
520 
521     for (Service &service : mServices)
522     {
523         UpdateServiceStateToRemove(service);
524     }
525 
526     if ((mHostInfo.GetState() == kToAdd) && !aSendUnregToServer)
527     {
528         // Host info is not added yet (not yet registered with
529         // server), so we can remove it and all services immediately.
530         mHostInfo.SetState(kRemoved);
531         HandleUpdateDone();
532         ExitNow();
533     }
534 
535     mHostInfo.SetState(kToRemove);
536     UpdateState();
537 
538 exit:
539     return error;
540 }
541 
ClearHostAndServices(void)542 void Client::ClearHostAndServices(void)
543 {
544     otLogInfoSrp("[client] Clear host & services");
545 
546     switch (GetState())
547     {
548     case kStateStopped:
549     case kStatePaused:
550         break;
551 
552     case kStateToUpdate:
553     case kStateUpdating:
554     case kStateUpdated:
555     case kStateToRetry:
556         SetState(kStateUpdated);
557         break;
558     }
559 
560     mTxFailureRetryCount = 0;
561     ResetRetryWaitInterval();
562 
563     mServices.Clear();
564     mHostInfo.Clear();
565 }
566 
SetState(State aState)567 void Client::SetState(State aState)
568 {
569     VerifyOrExit(aState != mState);
570 
571     otLogInfoSrp("[client] State %s -> %s", StateToString(mState), StateToString(aState));
572     mState = aState;
573 
574     switch (mState)
575     {
576     case kStateStopped:
577     case kStatePaused:
578     case kStateUpdated:
579         mTimer.Stop();
580         break;
581 
582     case kStateToUpdate:
583         mTimer.Start(kUpdateTxDelay);
584         break;
585 
586     case kStateUpdating:
587         mTimer.Start(GetRetryWaitInterval());
588         break;
589 
590     case kStateToRetry:
591         break;
592     }
593 exit:
594     return;
595 }
596 
ChangeHostAndServiceStates(const ItemState * aNewStates)597 void Client::ChangeHostAndServiceStates(const ItemState *aNewStates)
598 {
599 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE && OPENTHREAD_CONFIG_SRP_CLIENT_SAVE_SELECTED_SERVER_ENABLE
600     ItemState oldHostState = mHostInfo.GetState();
601 #endif
602 
603     mHostInfo.SetState(aNewStates[mHostInfo.GetState()]);
604 
605     for (Service &service : mServices)
606     {
607         service.SetState(aNewStates[service.GetState()]);
608     }
609 
610 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE && OPENTHREAD_CONFIG_SRP_CLIENT_SAVE_SELECTED_SERVER_ENABLE
611     if (mAutoStartModeEnabled && mAutoStartDidSelectServer && (oldHostState != kRegistered) &&
612         (mHostInfo.GetState() == kRegistered))
613     {
614         if (mAutoStartIsUsingAnycastAddress)
615         {
616             IgnoreError(Get<Settings>().Delete<Settings::SrpClientInfo>());
617         }
618         else
619         {
620             Settings::SrpClientInfo info;
621 
622             info.SetServerAddress(GetServerAddress().GetAddress());
623             info.SetServerPort(GetServerAddress().GetPort());
624 
625             IgnoreError(Get<Settings>().Save(info));
626         }
627     }
628 #endif
629 }
630 
InvokeCallback(Error aError) const631 void Client::InvokeCallback(Error aError) const
632 {
633     InvokeCallback(aError, mHostInfo, nullptr);
634 }
635 
InvokeCallback(Error aError,const HostInfo & aHostInfo,const Service * aRemovedServices) const636 void Client::InvokeCallback(Error aError, const HostInfo &aHostInfo, const Service *aRemovedServices) const
637 {
638     VerifyOrExit(mCallback != nullptr);
639     mCallback(aError, &aHostInfo, mServices.GetHead(), aRemovedServices, mCallbackContext);
640 
641 exit:
642     return;
643 }
644 
SendUpdate(void)645 void Client::SendUpdate(void)
646 {
647     static const ItemState kNewStateOnMessageTx[]{
648         /* (0) kToAdd      -> */ kAdding,
649         /* (1) kAdding     -> */ kAdding,
650         /* (2) kToRefresh  -> */ kRefreshing,
651         /* (3) kRefreshing -> */ kRefreshing,
652         /* (4) kToRemove   -> */ kRemoving,
653         /* (5) kRemoving   -> */ kRemoving,
654         /* (6) kRegistered -> */ kRegistered,
655         /* (7) kRemoved    -> */ kRemoved,
656     };
657 
658     Error    error   = kErrorNone;
659     Message *message = mSocket.NewMessage(0);
660 
661     VerifyOrExit(message != nullptr, error = kErrorNoBufs);
662     SuccessOrExit(error = PrepareUpdateMessage(*message));
663     SuccessOrExit(error = mSocket.SendTo(*message, Ip6::MessageInfo()));
664 
665     otLogInfoSrp("[client] Send update");
666 
667     // State changes:
668     //   kToAdd     -> kAdding
669     //   kToRefresh -> kRefreshing
670     //   kToRemove  -> kRemoving
671 
672     ChangeHostAndServiceStates(kNewStateOnMessageTx);
673 
674     // Remember the update message tx time to use later to determine the
675     // lease renew time.
676     mLeaseRenewTime      = TimerMilli::GetNow();
677     mTxFailureRetryCount = 0;
678 
679     SetState(kStateUpdating);
680 
681     if (!Get<Mle::Mle>().IsRxOnWhenIdle())
682     {
683         // If device is sleepy send fast polls while waiting for
684         // the response from server.
685         Get<DataPollSender>().SendFastPolls(kFastPollsAfterUpdateTx);
686     }
687 
688 exit:
689     if (error != kErrorNone)
690     {
691         // If there is an error in preparation or transmission of the
692         // update message (e.g., no buffer to allocate message), up to
693         // `kMaxTxFailureRetries` times, we wait for a short interval
694         // `kTxFailureRetryInterval` and try again. After this, we
695         // continue to retry using the `mRetryWaitInterval` (which keeps
696         // growing on each failure).
697 
698         otLogInfoSrp("[client] Failed to send update: %s", ErrorToString(error));
699 
700         FreeMessage(message);
701 
702         SetState(kStateToRetry);
703 
704         if (mTxFailureRetryCount < kMaxTxFailureRetries)
705         {
706             uint32_t interval;
707 
708             mTxFailureRetryCount++;
709             interval = Random::NonCrypto::AddJitter(kTxFailureRetryInterval, kTxFailureRetryJitter);
710             mTimer.Start(interval);
711 
712             otLogInfoSrp("[client] Quick retry %d in %u msec", mTxFailureRetryCount, interval);
713 
714             // Do not report message preparation errors to user
715             // until `kMaxTxFailureRetries` are exhausted.
716         }
717         else
718         {
719             LogRetryWaitInterval();
720             mTimer.Start(Random::NonCrypto::AddJitter(GetRetryWaitInterval(), kRetryIntervalJitter));
721             GrowRetryWaitInterval();
722             InvokeCallback(error);
723         }
724     }
725 }
726 
PrepareUpdateMessage(Message & aMessage)727 Error Client::PrepareUpdateMessage(Message &aMessage)
728 {
729     constexpr uint16_t kHeaderOffset = 0;
730 
731     Error             error = kErrorNone;
732     Dns::UpdateHeader header;
733     Info              info;
734 
735     info.Clear();
736 
737     SuccessOrExit(error = ReadOrGenerateKey(info.mKeyPair));
738 
739     // Generate random Message ID and ensure it is different from last one
740     do
741     {
742         SuccessOrExit(error = header.SetRandomMessageId());
743     } while (header.GetMessageId() == mUpdateMessageId);
744 
745     mUpdateMessageId = header.GetMessageId();
746 
747     // SRP Update (DNS Update) message must have exactly one record in
748     // Zone section, no records in Prerequisite Section, can have
749     // multiple records in Update Section (tracked as they are added),
750     // and two records in Additional Data Section (OPT and SIG records).
751     // The SIG record itself should not be included in calculation of
752     // SIG(0) signature, so the addition record count is set to one
753     // here. After signature calculation and appending of SIG record,
754     // the additional record count is updated to two and the header is
755     // rewritten in the message.
756 
757     header.SetZoneRecordCount(1);
758     header.SetAdditionalRecordCount(1);
759     SuccessOrExit(error = aMessage.Append(header));
760 
761     // Prepare Zone section
762 
763     info.mDomainNameOffset = aMessage.GetLength();
764     SuccessOrExit(error = Dns::Name::AppendName(mDomainName, aMessage));
765     SuccessOrExit(error = aMessage.Append(Dns::Zone()));
766 
767     // Prepare Update section
768 
769     if (mHostInfo.GetState() != kToRemove)
770     {
771         for (Service &service : mServices)
772         {
773             SuccessOrExit(error = AppendServiceInstructions(service, aMessage, info));
774         }
775     }
776 
777     SuccessOrExit(error = AppendHostDescriptionInstruction(aMessage, info));
778 
779     header.SetUpdateRecordCount(info.mRecordCount);
780     aMessage.Write(kHeaderOffset, header);
781 
782     // Prepare Additional Data section
783 
784     SuccessOrExit(error = AppendUpdateLeaseOptRecord(aMessage));
785     SuccessOrExit(error = AppendSignature(aMessage, info));
786 
787     header.SetAdditionalRecordCount(2); // Lease OPT and SIG RRs
788     aMessage.Write(kHeaderOffset, header);
789 
790 exit:
791     return error;
792 }
793 
ReadOrGenerateKey(Crypto::Ecdsa::P256::KeyPair & aKeyPair)794 Error Client::ReadOrGenerateKey(Crypto::Ecdsa::P256::KeyPair &aKeyPair)
795 {
796     Error error;
797 
798     error = Get<Settings>().Read<Settings::SrpEcdsaKey>(aKeyPair);
799 
800     if (error == kErrorNone)
801     {
802         Crypto::Ecdsa::P256::PublicKey publicKey;
803 
804         if (aKeyPair.GetPublicKey(publicKey) == kErrorNone)
805         {
806             ExitNow();
807         }
808     }
809 
810     SuccessOrExit(error = aKeyPair.Generate());
811     IgnoreError(Get<Settings>().Save<Settings::SrpEcdsaKey>(aKeyPair));
812 
813 exit:
814     return error;
815 }
816 
AppendServiceInstructions(Service & aService,Message & aMessage,Info & aInfo)817 Error Client::AppendServiceInstructions(Service &aService, Message &aMessage, Info &aInfo)
818 {
819     Error               error = kErrorNone;
820     Dns::ResourceRecord rr;
821     Dns::SrvRecord      srv;
822     bool                removing;
823     uint16_t            serviceNameOffset;
824     uint16_t            instanceNameOffset;
825     uint16_t            offset;
826 
827     if (aService.GetState() == kRegistered)
828     {
829         // If the lease needs to be renewed or if we are close to the
830         // renewal time of a registered service, we refresh the service
831         // early and include it in this update. This helps put more
832         // services on the same lease refresh schedule.
833 
834         VerifyOrExit(ShouldRenewEarly(aService));
835         aService.SetState(kToRefresh);
836     }
837 
838     removing = ((aService.GetState() == kToRemove) || (aService.GetState() == kRemoving));
839 
840     //----------------------------------
841     // Service Discovery Instruction
842 
843     // PTR record
844 
845     // "service name labels" + (pointer to) domain name.
846     serviceNameOffset = aMessage.GetLength();
847     SuccessOrExit(error = Dns::Name::AppendMultipleLabels(aService.GetName(), aMessage));
848     SuccessOrExit(error = Dns::Name::AppendPointerLabel(aInfo.mDomainNameOffset, aMessage));
849 
850     // On remove, we use "Delete an RR from an RRSet" where class is set
851     // to NONE and TTL to zero (RFC 2136 - section 2.5.4).
852 
853     rr.Init(Dns::ResourceRecord::kTypePtr, removing ? Dns::PtrRecord::kClassNone : Dns::PtrRecord::kClassInternet);
854     rr.SetTtl(removing ? 0 : mLeaseInterval);
855     offset = aMessage.GetLength();
856     SuccessOrExit(error = aMessage.Append(rr));
857 
858     // "Instance name" + (pointer to) service name.
859     instanceNameOffset = aMessage.GetLength();
860     SuccessOrExit(error = Dns::Name::AppendLabel(aService.GetInstanceName(), aMessage));
861     SuccessOrExit(error = Dns::Name::AppendPointerLabel(serviceNameOffset, aMessage));
862 
863     UpdateRecordLengthInMessage(rr, offset, aMessage);
864     aInfo.mRecordCount++;
865 
866     if (aService.HasSubType())
867     {
868         const char *subTypeLabel;
869         uint16_t    subServiceNameOffset = 0;
870 
871         for (uint16_t index = 0; (subTypeLabel = aService.GetSubTypeLabelAt(index)) != nullptr; ++index)
872         {
873             // subtype label + "_sub" label + (pointer to) service name.
874 
875             SuccessOrExit(error = Dns::Name::AppendLabel(subTypeLabel, aMessage));
876 
877             if (index == 0)
878             {
879                 subServiceNameOffset = aMessage.GetLength();
880                 SuccessOrExit(error = Dns::Name::AppendLabel("_sub", aMessage));
881                 SuccessOrExit(error = Dns::Name::AppendPointerLabel(serviceNameOffset, aMessage));
882             }
883             else
884             {
885                 SuccessOrExit(error = Dns::Name::AppendPointerLabel(subServiceNameOffset, aMessage));
886             }
887 
888             // `rr` is already initialized as PTR (add or remove).
889             offset = aMessage.GetLength();
890             SuccessOrExit(error = aMessage.Append(rr));
891 
892             SuccessOrExit(error = Dns::Name::AppendPointerLabel(instanceNameOffset, aMessage));
893             UpdateRecordLengthInMessage(rr, offset, aMessage);
894             aInfo.mRecordCount++;
895         }
896     }
897 
898     //----------------------------------
899     // Service Description Instruction
900 
901     // "Delete all RRsets from a name" for Instance Name.
902 
903     SuccessOrExit(error = Dns::Name::AppendPointerLabel(instanceNameOffset, aMessage));
904     SuccessOrExit(error = AppendDeleteAllRrsets(aMessage));
905     aInfo.mRecordCount++;
906 
907     VerifyOrExit(!removing);
908 
909     // SRV RR
910 
911     SuccessOrExit(error = Dns::Name::AppendPointerLabel(instanceNameOffset, aMessage));
912     srv.Init();
913     srv.SetTtl(mLeaseInterval);
914     srv.SetPriority(aService.GetPriority());
915     srv.SetWeight(aService.GetWeight());
916     srv.SetPort(aService.GetPort());
917     offset = aMessage.GetLength();
918     SuccessOrExit(error = aMessage.Append(srv));
919     SuccessOrExit(error = AppendHostName(aMessage, aInfo));
920     UpdateRecordLengthInMessage(srv, offset, aMessage);
921     aInfo.mRecordCount++;
922 
923     // TXT RR
924 
925     SuccessOrExit(error = Dns::Name::AppendPointerLabel(instanceNameOffset, aMessage));
926     rr.Init(Dns::ResourceRecord::kTypeTxt);
927     offset = aMessage.GetLength();
928     SuccessOrExit(error = aMessage.Append(rr));
929     SuccessOrExit(error =
930                       Dns::TxtEntry::AppendEntries(aService.GetTxtEntries(), aService.GetNumTxtEntries(), aMessage));
931     UpdateRecordLengthInMessage(rr, offset, aMessage);
932     aInfo.mRecordCount++;
933 
934 #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
935     if (mServiceKeyRecordEnabled)
936     {
937         // KEY RR is optional in "Service Description Instruction". It
938         // is added here under `REFERENCE_DEVICE` config and is intended
939         // for testing only.
940 
941         SuccessOrExit(error = Dns::Name::AppendPointerLabel(instanceNameOffset, aMessage));
942         SuccessOrExit(error = AppendKeyRecord(aMessage, aInfo));
943     }
944 #endif
945 
946 exit:
947     return error;
948 }
949 
AppendHostDescriptionInstruction(Message & aMessage,Info & aInfo) const950 Error Client::AppendHostDescriptionInstruction(Message &aMessage, Info &aInfo) const
951 {
952     Error               error = kErrorNone;
953     Dns::ResourceRecord rr;
954 
955     //----------------------------------
956     // Host Description Instruction
957 
958     // "Delete all RRsets from a name" for Host Name.
959 
960     SuccessOrExit(error = AppendHostName(aMessage, aInfo));
961     SuccessOrExit(error = AppendDeleteAllRrsets(aMessage));
962     aInfo.mRecordCount++;
963 
964     // AAAA RRs
965 
966     rr.Init(Dns::ResourceRecord::kTypeAaaa);
967     rr.SetTtl(mLeaseInterval);
968     rr.SetLength(sizeof(Ip6::Address));
969 
970     for (uint8_t index = 0; index < mHostInfo.GetNumAddresses(); index++)
971     {
972         SuccessOrExit(error = AppendHostName(aMessage, aInfo));
973         SuccessOrExit(error = aMessage.Append(rr));
974         SuccessOrExit(error = aMessage.Append(mHostInfo.GetAddress(index)));
975         aInfo.mRecordCount++;
976     }
977 
978     // KEY RR
979 
980     SuccessOrExit(error = AppendHostName(aMessage, aInfo));
981     SuccessOrExit(error = AppendKeyRecord(aMessage, aInfo));
982 
983 exit:
984     return error;
985 }
986 
AppendKeyRecord(Message & aMessage,Info & aInfo) const987 Error Client::AppendKeyRecord(Message &aMessage, Info &aInfo) const
988 {
989     Error                          error;
990     Dns::KeyRecord                 key;
991     Crypto::Ecdsa::P256::PublicKey publicKey;
992 
993     key.Init();
994     key.SetTtl(mLeaseInterval);
995     key.SetFlags(Dns::KeyRecord::kAuthConfidPermitted, Dns::KeyRecord::kOwnerNonZone,
996                  Dns::KeyRecord::kSignatoryFlagGeneral);
997     key.SetProtocol(Dns::KeyRecord::kProtocolDnsSec);
998     key.SetAlgorithm(Dns::KeyRecord::kAlgorithmEcdsaP256Sha256);
999     key.SetLength(sizeof(Dns::KeyRecord) - sizeof(Dns::ResourceRecord) + sizeof(Crypto::Ecdsa::P256::PublicKey));
1000     SuccessOrExit(error = aMessage.Append(key));
1001     SuccessOrExit(error = aInfo.mKeyPair.GetPublicKey(publicKey));
1002     SuccessOrExit(error = aMessage.Append(publicKey));
1003     aInfo.mRecordCount++;
1004 
1005 exit:
1006     return error;
1007 }
1008 
AppendDeleteAllRrsets(Message & aMessage) const1009 Error Client::AppendDeleteAllRrsets(Message &aMessage) const
1010 {
1011     // "Delete all RRsets from a name" (RFC 2136 - 2.5.3)
1012     // Name should be already appended in the message.
1013 
1014     Dns::ResourceRecord rr;
1015 
1016     rr.Init(Dns::ResourceRecord::kTypeAny, Dns::ResourceRecord::kClassAny);
1017     rr.SetTtl(0);
1018     rr.SetLength(0);
1019 
1020     return aMessage.Append(rr);
1021 }
1022 
AppendHostName(Message & aMessage,Info & aInfo,bool aDoNotCompress) const1023 Error Client::AppendHostName(Message &aMessage, Info &aInfo, bool aDoNotCompress) const
1024 {
1025     Error error;
1026 
1027     if (aDoNotCompress)
1028     {
1029         // Uncompressed (canonical form) of host name is used for SIG(0)
1030         // calculation.
1031         SuccessOrExit(error = Dns::Name::AppendMultipleLabels(mHostInfo.GetName(), aMessage));
1032         error = Dns::Name::AppendName(mDomainName, aMessage);
1033         ExitNow();
1034     }
1035 
1036     // If host name was previously added in the message, add it
1037     // compressed as pointer to the previous one. Otherwise,
1038     // append it and remember the offset.
1039 
1040     if (aInfo.mHostNameOffset != Info::kUnknownOffset)
1041     {
1042         ExitNow(error = Dns::Name::AppendPointerLabel(aInfo.mHostNameOffset, aMessage));
1043     }
1044 
1045     aInfo.mHostNameOffset = aMessage.GetLength();
1046     SuccessOrExit(error = Dns::Name::AppendMultipleLabels(mHostInfo.GetName(), aMessage));
1047     error = Dns::Name::AppendPointerLabel(aInfo.mDomainNameOffset, aMessage);
1048 
1049 exit:
1050     return error;
1051 }
1052 
AppendUpdateLeaseOptRecord(Message & aMessage) const1053 Error Client::AppendUpdateLeaseOptRecord(Message &aMessage) const
1054 {
1055     Error            error;
1056     Dns::OptRecord   optRecord;
1057     Dns::LeaseOption leaseOption;
1058 
1059     // Append empty (root domain) as OPT RR name.
1060     SuccessOrExit(error = Dns::Name::AppendTerminator(aMessage));
1061 
1062     // `Init()` sets the type and clears (set to zero) the extended
1063     // Response Code, version and all flags.
1064     optRecord.Init();
1065     optRecord.SetUdpPayloadSize(kUdpPayloadSize);
1066     optRecord.SetDnsSecurityFlag();
1067     optRecord.SetLength(sizeof(Dns::LeaseOption));
1068 
1069     SuccessOrExit(error = aMessage.Append(optRecord));
1070 
1071     leaseOption.Init();
1072 
1073     if (mHostInfo.GetState() == kToRemove)
1074     {
1075         leaseOption.SetLeaseInterval(0);
1076         leaseOption.SetKeyLeaseInterval(mShouldRemoveKeyLease ? 0 : mKeyLeaseInterval);
1077     }
1078     else
1079     {
1080         leaseOption.SetLeaseInterval(mLeaseInterval);
1081         leaseOption.SetKeyLeaseInterval(mKeyLeaseInterval);
1082     }
1083 
1084     error = aMessage.Append(leaseOption);
1085 
1086 exit:
1087     return error;
1088 }
1089 
AppendSignature(Message & aMessage,Info & aInfo)1090 Error Client::AppendSignature(Message &aMessage, Info &aInfo)
1091 {
1092     Error                          error;
1093     Dns::SigRecord                 sig;
1094     Crypto::Sha256                 sha256;
1095     Crypto::Sha256::Hash           hash;
1096     Crypto::Ecdsa::P256::Signature signature;
1097     uint16_t                       offset;
1098     uint16_t                       len;
1099 
1100     // Prepare SIG RR: TTL, type covered, labels count should be set
1101     // to zero. Since we have no clock, inception and expiration time
1102     // are also set to zero. The RDATA length will be set later (not
1103     // yet known due to variably (and possible compression) of signer's
1104     // name.
1105 
1106     sig.Clear();
1107     sig.Init(Dns::ResourceRecord::kClassAny);
1108     sig.SetAlgorithm(Dns::KeyRecord::kAlgorithmEcdsaP256Sha256);
1109 
1110     // Append the SIG RR with full uncompressed form of the host name
1111     // as the signer's name. This is used for SIG(0) calculation only.
1112     // It will be overwritten with host name compressed.
1113 
1114     offset = aMessage.GetLength();
1115     SuccessOrExit(error = aMessage.Append(sig));
1116     SuccessOrExit(error = AppendHostName(aMessage, aInfo, /* aDoNotCompress */ true));
1117 
1118     // Calculate signature (RFC 2931): Calculated over "data" which is
1119     // concatenation of (1) the SIG RR RDATA wire format (including
1120     // the canonical form of the signer's name), entirely omitting the
1121     // signature subfield, (2) DNS query message, including DNS header
1122     // but not UDP/IP header before the header RR counts have been
1123     // adjusted for the inclusion of SIG(0).
1124 
1125     sha256.Start();
1126 
1127     // (1) SIG RR RDATA wire format
1128     len = aMessage.GetLength() - offset - sizeof(Dns::ResourceRecord);
1129     sha256.Update(aMessage, offset + sizeof(Dns::ResourceRecord), len);
1130 
1131     // (2) Message from DNS header before SIG
1132     sha256.Update(aMessage, 0, offset);
1133 
1134     sha256.Finish(hash);
1135     SuccessOrExit(error = aInfo.mKeyPair.Sign(hash, signature));
1136 
1137     // Move back in message and append SIG RR now with compressed host
1138     // name (as signer's name) along with the calculated signature.
1139 
1140     IgnoreError(aMessage.SetLength(offset));
1141 
1142     // SIG(0) uses owner name of root (single zero byte).
1143     SuccessOrExit(error = Dns::Name::AppendTerminator(aMessage));
1144 
1145     offset = aMessage.GetLength();
1146     SuccessOrExit(error = aMessage.Append(sig));
1147     SuccessOrExit(error = AppendHostName(aMessage, aInfo));
1148     SuccessOrExit(error = aMessage.Append(signature));
1149     UpdateRecordLengthInMessage(sig, offset, aMessage);
1150 
1151 exit:
1152     return error;
1153 }
1154 
UpdateRecordLengthInMessage(Dns::ResourceRecord & aRecord,uint16_t aOffset,Message & aMessage) const1155 void Client::UpdateRecordLengthInMessage(Dns::ResourceRecord &aRecord, uint16_t aOffset, Message &aMessage) const
1156 {
1157     // This method is used to calculate an RR DATA length and update
1158     // (rewrite) it in a message. This should be called immediately
1159     // after all the fields in the record are written in the message.
1160     // `aOffset` gives the offset in the message to the start of the
1161     // record.
1162 
1163     aRecord.SetLength(aMessage.GetLength() - aOffset - sizeof(Dns::ResourceRecord));
1164     aMessage.Write(aOffset, aRecord);
1165 }
1166 
HandleUdpReceive(void * aContext,otMessage * aMessage,const otMessageInfo * aMessageInfo)1167 void Client::HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo)
1168 {
1169     OT_UNUSED_VARIABLE(aMessageInfo);
1170 
1171     static_cast<Client *>(aContext)->ProcessResponse(*static_cast<Message *>(aMessage));
1172 }
1173 
ProcessResponse(Message & aMessage)1174 void Client::ProcessResponse(Message &aMessage)
1175 {
1176     static const ItemState kNewStateOnUpdateDone[]{
1177         /* (0) kToAdd      -> */ kToAdd,
1178         /* (1) kAdding     -> */ kRegistered,
1179         /* (2) kToRefresh  -> */ kToRefresh,
1180         /* (3) kRefreshing -> */ kRegistered,
1181         /* (4) kToRemove   -> */ kToRemove,
1182         /* (5) kRemoving   -> */ kRemoved,
1183         /* (6) kRegistered -> */ kRegistered,
1184         /* (7) kRemoved    -> */ kRemoved,
1185     };
1186 
1187     Error               error = kErrorNone;
1188     Dns::UpdateHeader   header;
1189     uint16_t            offset = aMessage.GetOffset();
1190     uint16_t            recordCount;
1191     LinkedList<Service> removedServices;
1192 
1193     VerifyOrExit(GetState() == kStateUpdating);
1194 
1195     SuccessOrExit(error = aMessage.Read(offset, header));
1196 
1197     VerifyOrExit(header.GetType() == Dns::Header::kTypeResponse, error = kErrorParse);
1198     VerifyOrExit(header.GetQueryType() == Dns::Header::kQueryTypeUpdate, error = kErrorParse);
1199     VerifyOrExit(header.GetMessageId() == mUpdateMessageId, error = kErrorDrop);
1200 
1201     if (!Get<Mle::Mle>().IsRxOnWhenIdle())
1202     {
1203         Get<DataPollSender>().StopFastPolls();
1204     }
1205 
1206     // Response is for the earlier request message.
1207 
1208     otLogInfoSrp("[client] Received response");
1209 
1210 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE && OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE
1211     mTimoutFailureCount = 0;
1212 #endif
1213 
1214     error = Dns::Header::ResponseCodeToError(header.GetResponseCode());
1215 
1216     if (error != kErrorNone)
1217     {
1218         otLogInfoSrp("[client] Server rejected %s code:%d", ErrorToString(error), header.GetResponseCode());
1219 
1220         if (mHostInfo.GetState() == kAdding)
1221         {
1222             // Since server rejected the update message, we go back to
1223             // `kToAdd` state to allow user to give a new name using
1224             // `SetHostName()`.
1225             mHostInfo.SetState(kToAdd);
1226         }
1227 
1228         // Wait for the timer to expire to retry. Note that timer is
1229         // already scheduled for the current wait interval when state
1230         // was changed to `kStateUpdating`.
1231 
1232         LogRetryWaitInterval();
1233         GrowRetryWaitInterval();
1234         SetState(kStateToRetry);
1235         InvokeCallback(error);
1236 
1237 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE && OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE
1238         if ((error == kErrorDuplicated) || (error == kErrorSecurity))
1239         {
1240             // If the server rejects the update with specific errors
1241             // (indicating duplicate name and/or security error), we
1242             // try to switch the server (we check if another can be
1243             // found in the Network Data).
1244             //
1245             // Note that this is done after invoking the callback and
1246             // notifying the user of the error from server. This works
1247             // correctly even if user makes changes from callback
1248             // (e.g., calls SRP client APIs like `Stop` or disables
1249             // auto-start), since we have a guard check at the top of
1250             // `SelectNextServer()` to verify that client is still
1251             // running and auto-start is enabled and selected the
1252             // server.
1253 
1254             SelectNextServer(/* aDisallowSwitchOnRegisteredHost */ true);
1255         }
1256 #endif
1257         ExitNow(error = kErrorNone);
1258     }
1259 
1260     offset += sizeof(header);
1261 
1262     // Skip over all sections till Additional Data section
1263     // SPEC ENHANCEMENT: Server can echo the request back or not
1264     // include any of RRs. Would be good to explicitly require SRP server
1265     // to not echo back RRs.
1266 
1267     if (header.GetZoneRecordCount() != 0)
1268     {
1269         VerifyOrExit(header.GetZoneRecordCount() == 1, error = kErrorParse);
1270         SuccessOrExit(error = Dns::Name::ParseName(aMessage, offset));
1271         VerifyOrExit(offset + sizeof(Dns::Zone) <= aMessage.GetLength(), error = kErrorParse);
1272         offset += sizeof(Dns::Zone);
1273     }
1274 
1275     // Check for Update Lease OPT RR. This determines the lease
1276     // interval accepted by server. If not present, then use the
1277     // transmitted lease interval from the update request message.
1278 
1279     mAcceptedLeaseInterval = mLeaseInterval;
1280     recordCount =
1281         header.GetPrerequisiteRecordCount() + header.GetUpdateRecordCount() + header.GetAdditionalRecordCount();
1282 
1283     while (recordCount > 0)
1284     {
1285         uint16_t            startOffset = offset;
1286         Dns::ResourceRecord rr;
1287 
1288         SuccessOrExit(error = ReadResourceRecord(aMessage, offset, rr));
1289         recordCount--;
1290 
1291         if (rr.GetType() == Dns::ResourceRecord::kTypeOpt)
1292         {
1293             SuccessOrExit(error = ProcessOptRecord(aMessage, startOffset, static_cast<Dns::OptRecord &>(rr)));
1294         }
1295     }
1296 
1297     // Calculate the lease renew time based on update message tx time
1298     // and the lease time. `kLeaseRenewGuardInterval` is used to
1299     // ensure that we renew the lease before server expires it. In the
1300     // unlikely (but maybe useful for testing) case where the accepted
1301     // lease interval is too short (shorter than the guard time) we
1302     // just use half of the accepted lease interval.
1303 
1304     if (mAcceptedLeaseInterval > kLeaseRenewGuardInterval)
1305     {
1306         mLeaseRenewTime += Time::SecToMsec(mAcceptedLeaseInterval - kLeaseRenewGuardInterval);
1307     }
1308     else
1309     {
1310         mLeaseRenewTime += Time::SecToMsec(mAcceptedLeaseInterval) / 2;
1311     }
1312 
1313     for (Service &service : mServices)
1314     {
1315         if ((service.GetState() == kAdding) || (service.GetState() == kRefreshing))
1316         {
1317             service.SetLeaseRenewTime(mLeaseRenewTime);
1318         }
1319     }
1320 
1321     // State changes:
1322     //   kAdding     -> kRegistered
1323     //   kRefreshing -> kRegistered
1324     //   kRemoving   -> kRemoved
1325 
1326     ChangeHostAndServiceStates(kNewStateOnUpdateDone);
1327 
1328     HandleUpdateDone();
1329     UpdateState();
1330 
1331 exit:
1332     if (error != kErrorNone)
1333     {
1334         otLogInfoSrp("[client] Failed to process response %s", ErrorToString(error));
1335     }
1336 }
1337 
HandleUpdateDone(void)1338 void Client::HandleUpdateDone(void)
1339 {
1340     HostInfo            hostInfoCopy = mHostInfo;
1341     LinkedList<Service> removedServices;
1342 
1343     if (mHostInfo.GetState() == kRemoved)
1344     {
1345         mHostInfo.Clear();
1346     }
1347 
1348     ResetRetryWaitInterval();
1349     SetState(kStateUpdated);
1350 
1351     GetRemovedServices(removedServices);
1352     InvokeCallback(kErrorNone, hostInfoCopy, removedServices.GetHead());
1353 }
1354 
GetRemovedServices(LinkedList<Service> & aRemovedServices)1355 void Client::GetRemovedServices(LinkedList<Service> &aRemovedServices)
1356 {
1357     Service *service;
1358     Service *prev;
1359     Service *next;
1360 
1361     for (prev = nullptr, service = mServices.GetHead(); service != nullptr; service = next)
1362     {
1363         next = service->GetNext();
1364 
1365         if (service->GetState() == kRemoved)
1366         {
1367             mServices.PopAfter(prev);
1368             aRemovedServices.Push(*service);
1369 
1370             // When the service is removed from the list
1371             // we keep the `prev` pointer same as before.
1372         }
1373         else
1374         {
1375             prev = service;
1376         }
1377     }
1378 }
1379 
ReadResourceRecord(const Message & aMessage,uint16_t & aOffset,Dns::ResourceRecord & aRecord)1380 Error Client::ReadResourceRecord(const Message &aMessage, uint16_t &aOffset, Dns::ResourceRecord &aRecord)
1381 {
1382     // Reads and skips over a Resource Record (RR) from message at
1383     // given offset. On success, `aOffset` is updated to point to end
1384     // of RR.
1385 
1386     Error error;
1387 
1388     SuccessOrExit(error = Dns::Name::ParseName(aMessage, aOffset));
1389     SuccessOrExit(error = aMessage.Read(aOffset, aRecord));
1390     VerifyOrExit(aOffset + aRecord.GetSize() <= aMessage.GetLength(), error = kErrorParse);
1391     aOffset += static_cast<uint16_t>(aRecord.GetSize());
1392 
1393 exit:
1394     return error;
1395 }
1396 
ProcessOptRecord(const Message & aMessage,uint16_t aOffset,const Dns::OptRecord & aOptRecord)1397 Error Client::ProcessOptRecord(const Message &aMessage, uint16_t aOffset, const Dns::OptRecord &aOptRecord)
1398 {
1399     // Read and process all options (in an OPT RR) from a message.
1400     // The `aOffset` points to beginning of record in `aMessage`.
1401 
1402     Error    error = kErrorNone;
1403     uint16_t len;
1404 
1405     IgnoreError(Dns::Name::ParseName(aMessage, aOffset));
1406     aOffset += sizeof(Dns::OptRecord);
1407 
1408     len = aOptRecord.GetLength();
1409 
1410     while (len > 0)
1411     {
1412         Dns::LeaseOption leaseOption;
1413         Dns::Option &    option = leaseOption;
1414         uint16_t         size;
1415 
1416         SuccessOrExit(error = aMessage.Read(aOffset, option));
1417 
1418         VerifyOrExit(aOffset + option.GetSize() <= aMessage.GetLength(), error = kErrorParse);
1419 
1420         if ((option.GetOptionCode() == Dns::Option::kUpdateLease) &&
1421             (option.GetOptionLength() >= Dns::LeaseOption::kOptionLength))
1422         {
1423             SuccessOrExit(error = aMessage.Read(aOffset, leaseOption));
1424 
1425             mAcceptedLeaseInterval = leaseOption.GetLeaseInterval();
1426 
1427             if (mAcceptedLeaseInterval > kMaxLease)
1428             {
1429                 mAcceptedLeaseInterval = kMaxLease;
1430             }
1431         }
1432 
1433         size = static_cast<uint16_t>(option.GetSize());
1434         aOffset += size;
1435         len -= size;
1436     }
1437 
1438 exit:
1439     return error;
1440 }
1441 
UpdateState(void)1442 void Client::UpdateState(void)
1443 {
1444     TimeMilli now               = TimerMilli::GetNow();
1445     TimeMilli earliestRenewTime = now.GetDistantFuture();
1446     bool      shouldUpdate      = false;
1447 
1448     VerifyOrExit((GetState() != kStateStopped) && (GetState() != kStatePaused));
1449     VerifyOrExit(mHostInfo.GetName() != nullptr);
1450 
1451     // Go through the host info and all the services to check if there
1452     // are any new changes (i.e., anything new to add or remove). This
1453     // is used to determine whether to send an SRP update message or
1454     // not. Also keep track of the earliest renew time among the
1455     // previously registered services. This is used to schedule the
1456     // timer for next refresh.
1457 
1458     switch (mHostInfo.GetState())
1459     {
1460     case kAdding:
1461     case kRefreshing:
1462     case kRemoving:
1463         break;
1464 
1465     case kRegistered:
1466         if (now < mLeaseRenewTime)
1467         {
1468             break;
1469         }
1470 
1471         mHostInfo.SetState(kToRefresh);
1472 
1473         // Fall through
1474 
1475     case kToAdd:
1476     case kToRefresh:
1477         // Make sure we have at least one service and at least one
1478         // host address, otherwise no need to send SRP update message.
1479         // The exception is when removing host info where we allow
1480         // for empty service list.
1481         VerifyOrExit(!mServices.IsEmpty() && (mHostInfo.GetNumAddresses() > 0));
1482 
1483         // Fall through
1484 
1485     case kToRemove:
1486         shouldUpdate = true;
1487         break;
1488 
1489     case kRemoved:
1490         ExitNow();
1491     }
1492 
1493     // If host info is being removed, we skip over checking service list
1494     // for new adds (or removes). This handles the situation where while
1495     // remove is ongoing and before we get a response from the server,
1496     // user adds a new service to be registered. We wait for remove to
1497     // finish (receive response from server) before starting with a new
1498     // service adds.
1499 
1500     if (mHostInfo.GetState() != kRemoving)
1501     {
1502         for (Service &service : mServices)
1503         {
1504             switch (service.GetState())
1505             {
1506             case kToAdd:
1507             case kToRefresh:
1508             case kToRemove:
1509                 shouldUpdate = true;
1510                 break;
1511 
1512             case kRegistered:
1513                 if (service.GetLeaseRenewTime() <= now)
1514                 {
1515                     service.SetState(kToRefresh);
1516                     shouldUpdate = true;
1517                 }
1518                 else if (service.GetLeaseRenewTime() < earliestRenewTime)
1519                 {
1520                     earliestRenewTime = service.GetLeaseRenewTime();
1521                 }
1522 
1523                 break;
1524 
1525             case kAdding:
1526             case kRefreshing:
1527             case kRemoving:
1528             case kRemoved:
1529                 break;
1530             }
1531         }
1532     }
1533 
1534     if (shouldUpdate)
1535     {
1536         SetState(kStateToUpdate);
1537         ExitNow();
1538     }
1539 
1540     if ((GetState() == kStateUpdated) && (earliestRenewTime != now.GetDistantFuture()))
1541     {
1542         mTimer.FireAt(earliestRenewTime);
1543     }
1544 
1545 exit:
1546     return;
1547 }
1548 
GrowRetryWaitInterval(void)1549 void Client::GrowRetryWaitInterval(void)
1550 {
1551     mRetryWaitInterval =
1552         mRetryWaitInterval / kRetryIntervalGrowthFactorDenominator * kRetryIntervalGrowthFactorNumerator;
1553 
1554     if (mRetryWaitInterval > kMaxRetryWaitInterval)
1555     {
1556         mRetryWaitInterval = kMaxRetryWaitInterval;
1557     }
1558 }
1559 
GetBoundedLeaseInterval(uint32_t aInterval,uint32_t aDefaultInterval) const1560 uint32_t Client::GetBoundedLeaseInterval(uint32_t aInterval, uint32_t aDefaultInterval) const
1561 {
1562     uint32_t boundedInterval = aDefaultInterval;
1563 
1564     if (aInterval != 0)
1565     {
1566         boundedInterval = OT_MIN(aInterval, static_cast<uint32_t>(kMaxLease));
1567     }
1568 
1569     return boundedInterval;
1570 }
1571 
ShouldRenewEarly(const Service & aService) const1572 bool Client::ShouldRenewEarly(const Service &aService) const
1573 {
1574     // Check if we reached the service renew time or close to it. The
1575     // "early renew interval" is used to allow early refresh. It is
1576     // calculated as a factor of the `mAcceptedLeaseInterval`. The
1577     // "early lease renew factor" is given as a fraction (numerator and
1578     // denominator). If the denominator is set to zero (i.e., factor is
1579     // set to infinity), then service is always included in all SRP
1580     // update messages.
1581 
1582     bool shouldRenew;
1583 
1584 #if OPENTHREAD_CONFIG_SRP_CLIENT_EARLY_LEASE_RENEW_FACTOR_DENOMINATOR != 0
1585     uint32_t earlyRenewInterval =
1586         Time::SecToMsec(mAcceptedLeaseInterval) / kEarlyLeaseRenewFactorDenominator * kEarlyLeaseRenewFactorNumerator;
1587 
1588     shouldRenew = (aService.GetLeaseRenewTime() <= TimerMilli::GetNow() + earlyRenewInterval);
1589 #else
1590     OT_UNUSED_VARIABLE(aService);
1591     shouldRenew = true;
1592 #endif
1593 
1594     return shouldRenew;
1595 }
1596 
HandleTimer(Timer & aTimer)1597 void Client::HandleTimer(Timer &aTimer)
1598 {
1599     aTimer.Get<Client>().HandleTimer();
1600 }
1601 
HandleTimer(void)1602 void Client::HandleTimer(void)
1603 {
1604     switch (GetState())
1605     {
1606     case kStateStopped:
1607     case kStatePaused:
1608         break;
1609 
1610     case kStateToUpdate:
1611     case kStateToRetry:
1612         SendUpdate();
1613         break;
1614 
1615     case kStateUpdating:
1616         LogRetryWaitInterval();
1617         otLogInfoSrp("[client] Timed out, no response");
1618         GrowRetryWaitInterval();
1619         SetState(kStateToUpdate);
1620         InvokeCallback(kErrorResponseTimeout);
1621 
1622 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE && OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE
1623 
1624         // After certain number of back-to-back timeout failures, we try
1625         // to switch the server. This is again done after invoking the
1626         // callback. It works correctly due to the guard check at the
1627         // top of `SelectNextServer()`.
1628 
1629         if (mTimoutFailureCount < NumericLimits<uint8_t>::kMax)
1630         {
1631             mTimoutFailureCount++;
1632         }
1633 
1634         if (mTimoutFailureCount >= kMaxTimeoutFailuresToSwitchServer)
1635         {
1636             SelectNextServer(kDisallowSwitchOnRegisteredHost);
1637         }
1638 #endif
1639         break;
1640 
1641     case kStateUpdated:
1642         UpdateState();
1643         break;
1644     }
1645 }
1646 
1647 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
1648 
EnableAutoStartMode(AutoStartCallback aCallback,void * aContext)1649 void Client::EnableAutoStartMode(AutoStartCallback aCallback, void *aContext)
1650 {
1651     mAutoStartCallback = aCallback;
1652     mAutoStartContext  = aContext;
1653 
1654     VerifyOrExit(!mAutoStartModeEnabled);
1655     mAutoStartModeEnabled = true;
1656     ProcessAutoStart();
1657 
1658 exit:
1659     return;
1660 }
1661 
ProcessAutoStart(void)1662 void Client::ProcessAutoStart(void)
1663 {
1664     Ip6::SockAddr                             serverSockAddr;
1665     bool                                      serverIsAnycast = false;
1666     NetworkData::Service::DnsSrpAnycast::Info anycastInfo;
1667 #if OPENTHREAD_CONFIG_SRP_CLIENT_SAVE_SELECTED_SERVER_ENABLE
1668     Settings::SrpClientInfo savedInfo;
1669     bool                    hasSavedServerInfo = false;
1670 #endif
1671 
1672     VerifyOrExit(mAutoStartModeEnabled);
1673 
1674     serverSockAddr.Clear();
1675 
1676     // If the SRP client is not running and auto start mode is
1677     // enabled, we check if we can find any SRP server info in the
1678     // Thread Network Data. If it is already running and the server
1679     // was chosen by the auto-start feature, then we ensure that the
1680     // selected server is still present in the Network Data.
1681     //
1682     // Two types of "DNS/SRP Service" entries can be present in
1683     // Network Data, "DNS/SRP Service Anycast Address" model and
1684     // "DNS/SRP Service Unicast" model. The Anycast entries are
1685     // preferred over the Unicast entries.
1686 
1687     VerifyOrExit(!IsRunning() || mAutoStartDidSelectServer);
1688 
1689     // Now `IsRunning()` implies `mAutoStartDidSelectServer`.
1690 
1691 #if OPENTHREAD_CONFIG_SRP_CLIENT_SAVE_SELECTED_SERVER_ENABLE
1692     if (!IsRunning())
1693     {
1694         hasSavedServerInfo = (Get<Settings>().Read(savedInfo) == kErrorNone);
1695     }
1696 #endif
1697 
1698     if (Get<NetworkData::Service::Manager>().FindPreferredDnsSrpAnycastInfo(anycastInfo) == kErrorNone)
1699     {
1700         if (IsRunning() && mAutoStartIsUsingAnycastAddress && (mServerSequenceNumber == anycastInfo.mSequenceNumber) &&
1701             (GetServerAddress().GetAddress() == anycastInfo.mAnycastAddress))
1702         {
1703             // Client is already using the same anycast address.
1704             ExitNow();
1705         }
1706 
1707         otLogInfoSrp("[client] Found anycast server %d", anycastInfo.mSequenceNumber);
1708 
1709         serverSockAddr.SetAddress(anycastInfo.mAnycastAddress);
1710         serverSockAddr.SetPort(kAnycastServerPort);
1711         mServerSequenceNumber = anycastInfo.mSequenceNumber;
1712         serverIsAnycast       = true;
1713     }
1714     else
1715     {
1716         uint16_t                                  numServers = 0;
1717         NetworkData::Service::DnsSrpUnicast::Info unicastInfo;
1718         NetworkData::Service::Manager::Iterator   iterator;
1719 
1720         while (Get<NetworkData::Service::Manager>().GetNextDnsSrpUnicastInfo(iterator, unicastInfo) == kErrorNone)
1721         {
1722             if (IsRunning() && !mAutoStartIsUsingAnycastAddress && (GetServerAddress() == unicastInfo.mSockAddr))
1723             {
1724                 ExitNow();
1725             }
1726 
1727 #if OPENTHREAD_CONFIG_SRP_CLIENT_SAVE_SELECTED_SERVER_ENABLE
1728             if (hasSavedServerInfo && (unicastInfo.mSockAddr.GetAddress() == savedInfo.GetServerAddress()) &&
1729                 (unicastInfo.mSockAddr.GetPort() == savedInfo.GetServerPort()))
1730             {
1731                 // Stop the search if we see a match for the previously
1732                 // saved server info in the network data entries.
1733 
1734                 serverSockAddr  = unicastInfo.mSockAddr;
1735                 serverIsAnycast = false;
1736                 break;
1737             }
1738 #endif
1739 
1740             numServers++;
1741 
1742             // Choose a server randomly (with uniform distribution) from
1743             // the list of servers. As we iterate through server entries,
1744             // with probability `1/numServers`, we choose to switch the
1745             // current selected server with the new entry. This approach
1746             // results in a uniform/same probability of selection among
1747             // all server entries.
1748 
1749             if ((numServers == 1) || (Random::NonCrypto::GetUint16InRange(0, numServers) == 0))
1750             {
1751                 serverSockAddr  = unicastInfo.mSockAddr;
1752                 serverIsAnycast = false;
1753             }
1754         }
1755     }
1756 
1757     if (IsRunning())
1758     {
1759         Stop(kRequesterAuto, kResetRetryInterval);
1760     }
1761 
1762     VerifyOrExit(!serverSockAddr.GetAddress().IsUnspecified());
1763 
1764     mAutoStartIsUsingAnycastAddress = serverIsAnycast;
1765     IgnoreError(Start(serverSockAddr, kRequesterAuto));
1766 
1767 exit:
1768     return;
1769 }
1770 
1771 #if OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE
SelectNextServer(bool aDisallowSwitchOnRegisteredHost)1772 void Client::SelectNextServer(bool aDisallowSwitchOnRegisteredHost)
1773 {
1774     // This method tries to find the next server info entry in the
1775     // Network Data after the current one selected. If found, it
1776     // restarts the client with the new server (keeping the retry wait
1777     // interval as before).
1778 
1779     Ip6::SockAddr serverSockAddr;
1780     bool          selectNext = false;
1781 
1782     serverSockAddr.Clear();
1783 
1784     // Ensure that client is running, auto-start is enabled and
1785     // auto-start selected the server.
1786 
1787     VerifyOrExit(IsRunning() && mAutoStartModeEnabled && mAutoStartDidSelectServer);
1788 
1789     if (aDisallowSwitchOnRegisteredHost)
1790     {
1791         // Ensure that host info is not yet registered (indicating that no
1792         // service has yet been registered either).
1793         VerifyOrExit((mHostInfo.GetState() == kAdding) || (mHostInfo.GetState() == kToAdd));
1794     }
1795 
1796     // We go through all entries to find the one matching the currently
1797     // selected one, then set `selectNext` to `true` so to select the
1798     // next one.
1799 
1800     do
1801     {
1802         NetworkData::Service::DnsSrpAnycast::Info anycastInfo;
1803         NetworkData::Service::DnsSrpUnicast::Info unicastInfo;
1804         NetworkData::Service::Manager::Iterator   iterator;
1805 
1806         while (Get<NetworkData::Service::Manager>().GetNextDnsSrpAnycastInfo(iterator, anycastInfo) == kErrorNone)
1807         {
1808             if (selectNext)
1809             {
1810                 serverSockAddr.SetAddress(anycastInfo.mAnycastAddress);
1811                 serverSockAddr.SetPort(kAnycastServerPort);
1812                 mServerSequenceNumber           = anycastInfo.mSequenceNumber;
1813                 mAutoStartIsUsingAnycastAddress = true;
1814                 ExitNow();
1815             }
1816 
1817             if (mAutoStartIsUsingAnycastAddress && (GetServerAddress().GetAddress() == anycastInfo.mAnycastAddress) &&
1818                 (GetServerAddress().GetPort() == kAnycastServerPort))
1819             {
1820                 selectNext = true;
1821             }
1822         }
1823 
1824         iterator.Reset();
1825 
1826         while (Get<NetworkData::Service::Manager>().GetNextDnsSrpUnicastInfo(iterator, unicastInfo) == kErrorNone)
1827         {
1828             if (selectNext)
1829             {
1830                 serverSockAddr                  = unicastInfo.mSockAddr;
1831                 mAutoStartIsUsingAnycastAddress = false;
1832                 ExitNow();
1833             }
1834 
1835             if (GetServerAddress() == unicastInfo.mSockAddr)
1836             {
1837                 selectNext = true;
1838             }
1839         }
1840 
1841         // We loop back to handle the case where the current entry
1842         // is the last one.
1843 
1844     } while (selectNext);
1845 
1846     // If we reach here it indicates we could not find the entry
1847     // associated with currently selected server in the list. This
1848     // situation is rather unlikely but can still happen if Network
1849     // Data happens to be changed and the entry removed but
1850     // the "changed" event from `Notifier` may have not yet been
1851     // processed (note that events are emitted from their own
1852     // tasklet). In such a case we keep `serverSockAddr` as empty.
1853 
1854 exit:
1855     if (!serverSockAddr.GetAddress().IsUnspecified() && (GetServerAddress() != serverSockAddr))
1856     {
1857         // We specifically update `mHostInfo` to `kToAdd` state. This
1858         // ensures that `Stop()` will keep it as kToAdd` and we detect
1859         // that the host info has not been registered yet and allow the
1860         // `SelectNextServer()` to happen again if the timeouts/failures
1861         // continue to happen with the new server.
1862 
1863         mHostInfo.SetState(kToAdd);
1864         Stop(kRequesterAuto, kKeepRetryInterval);
1865         IgnoreError(Start(serverSockAddr, kRequesterAuto));
1866     }
1867 }
1868 #endif // OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE
1869 
1870 #endif // OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
1871 
ItemStateToString(ItemState aState)1872 const char *Client::ItemStateToString(ItemState aState)
1873 {
1874     static const char *const kItemStateStrings[] = {
1875         "ToAdd",      // kToAdd      (0)
1876         "Adding",     // kAdding     (1)
1877         "ToRefresh",  // kToRefresh  (2)
1878         "Refreshing", // kRefreshing (3)
1879         "ToRemove",   // kToRemove   (4)
1880         "Removing",   // kRemoving   (5)
1881         "Registered", // kRegistered (6)
1882         "Removed",    // kRemoved    (7)
1883     };
1884 
1885     return kItemStateStrings[aState];
1886 }
1887 
1888 #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_INFO) && (OPENTHREAD_CONFIG_LOG_SRP == 1)
1889 
StateToString(State aState)1890 const char *Client::StateToString(State aState)
1891 {
1892     static const char *const kStateStrings[] = {
1893         "Stopped",  // kStateStopped  (0)
1894         "Paused",   // kStatePaused   (1)
1895         "ToUpdate", // kStateToUpdate (2)
1896         "Updating", // kStateUpdating (3)
1897         "Updated",  // kStateUpdated  (4)
1898         "ToRetry",  // kStateToRetry  (5)
1899     };
1900 
1901     static_assert(kStateStopped == 0, "kStateStopped value is not correct");
1902     static_assert(kStatePaused == 1, "kStatePaused value is not correct");
1903     static_assert(kStateToUpdate == 2, "kStateToUpdate value is not correct");
1904     static_assert(kStateUpdating == 3, "kStateUpdating value is not correct");
1905     static_assert(kStateUpdated == 4, "kStateUpdated value is not correct");
1906     static_assert(kStateToRetry == 5, "kStateToRetry value is not correct");
1907 
1908     return kStateStrings[aState];
1909 }
1910 
LogRetryWaitInterval(void) const1911 void Client::LogRetryWaitInterval(void) const
1912 {
1913     constexpr uint16_t kLogInMsecLimit = 5000; // Max interval (in msec) to log the value in msec unit
1914 
1915     uint32_t interval = GetRetryWaitInterval();
1916 
1917     otLogInfoSrp("[client] Retry interval %u %s", (interval < kLogInMsecLimit) ? interval : Time::MsecToSec(interval),
1918                  (interval < kLogInMsecLimit) ? "ms" : "sec");
1919 }
1920 
1921 #endif // #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_INFO) && (OPENTHREAD_CONFIG_LOG_SRP == 1)
1922 
1923 } // namespace Srp
1924 } // namespace ot
1925 
1926 #endif // OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
1927