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