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/as_core_type.hpp"
34 #include "common/code_utils.hpp"
35 #include "common/debug.hpp"
36 #include "common/locator_getters.hpp"
37 #include "common/num_utils.hpp"
38 #include "common/random.hpp"
39 #include "common/settings.hpp"
40 #include "common/string.hpp"
41 #include "instance/instance.hpp"
42 
43 /**
44  * @file
45  *   This file implements the SRP client.
46  */
47 
48 namespace ot {
49 namespace Srp {
50 
51 RegisterLogModule("SrpClient");
52 
53 //---------------------------------------------------------------------
54 // Client::HostInfo
55 
Init(void)56 void Client::HostInfo::Init(void)
57 {
58     Clearable<HostInfo>::Clear();
59 
60     // State is directly set on `mState` instead of using `SetState()`
61     // to avoid logging.
62     mState = OT_SRP_CLIENT_ITEM_STATE_REMOVED;
63 }
64 
Clear(void)65 void Client::HostInfo::Clear(void)
66 {
67     Clearable<HostInfo>::Clear();
68     SetState(kRemoved);
69 }
70 
SetState(ItemState aState)71 bool Client::HostInfo::SetState(ItemState aState)
72 {
73     bool didChange;
74 
75     VerifyOrExit(aState != GetState(), didChange = false);
76 
77     LogInfo("HostInfo %s -> %s", ItemStateToString(GetState()), ItemStateToString(aState));
78 
79     mState    = MapEnum(aState);
80     didChange = true;
81 
82 exit:
83     return didChange;
84 }
85 
EnableAutoAddress(void)86 void Client::HostInfo::EnableAutoAddress(void)
87 {
88     mAddresses    = nullptr;
89     mNumAddresses = 0;
90     mAutoAddress  = true;
91 
92     LogInfo("HostInfo enabled auto address");
93 }
94 
SetAddresses(const Ip6::Address * aAddresses,uint8_t aNumAddresses)95 void Client::HostInfo::SetAddresses(const Ip6::Address *aAddresses, uint8_t aNumAddresses)
96 {
97     mAddresses    = aAddresses;
98     mNumAddresses = aNumAddresses;
99     mAutoAddress  = false;
100 
101     LogInfo("HostInfo set %d addrs", GetNumAddresses());
102 
103     for (uint8_t index = 0; index < GetNumAddresses(); index++)
104     {
105         LogInfo("%s", GetAddress(index).ToString().AsCString());
106     }
107 }
108 
109 //---------------------------------------------------------------------
110 // Client::Service
111 
Init(void)112 Error Client::Service::Init(void)
113 {
114     Error error = kErrorNone;
115 
116     VerifyOrExit((GetName() != nullptr) && (GetInstanceName() != nullptr), error = kErrorInvalidArgs);
117     VerifyOrExit((GetTxtEntries() != nullptr) || (GetNumTxtEntries() == 0), error = kErrorInvalidArgs);
118 
119     // State is directly set on `mState` instead of using `SetState()`
120     // to avoid logging.
121     mState = OT_SRP_CLIENT_ITEM_STATE_REMOVED;
122 
123     mLease    = Min(mLease, kMaxLease);
124     mKeyLease = Min(mKeyLease, kMaxLease);
125 
126 exit:
127     return error;
128 }
129 
SetState(ItemState aState)130 bool Client::Service::SetState(ItemState aState)
131 {
132     bool didChange;
133 
134     VerifyOrExit(GetState() != aState, didChange = false);
135 
136     LogInfo("Service %s -> %s, \"%s\" \"%s\"", ItemStateToString(GetState()), ItemStateToString(aState),
137             GetInstanceName(), GetName());
138 
139     if (aState == kToAdd)
140     {
141         constexpr uint16_t kSubTypeLabelStringSize = 80;
142 
143         String<kSubTypeLabelStringSize> string;
144 
145         // Log more details only when entering `kToAdd` state.
146 
147         if (HasSubType())
148         {
149             const char *label;
150 
151             for (uint16_t index = 0; (label = GetSubTypeLabelAt(index)) != nullptr; index++)
152             {
153                 string.Append("%s\"%s\"", (index != 0) ? ", " : "", label);
154             }
155         }
156 
157         LogInfo("subtypes:[%s] port:%d weight:%d prio:%d txts:%d", string.AsCString(), GetPort(), GetWeight(),
158                 GetPriority(), GetNumTxtEntries());
159     }
160 
161     mState    = MapEnum(aState);
162     didChange = true;
163 
164 exit:
165     return didChange;
166 }
167 
Matches(const Service & aOther) const168 bool Client::Service::Matches(const Service &aOther) const
169 {
170     // This method indicates whether or not two service entries match,
171     // i.e., have the same service and instance names. This is intended
172     // for use by `LinkedList::FindMatching()` to search within the
173     // `mServices` list.
174 
175     return StringMatch(GetName(), aOther.GetName()) && StringMatch(GetInstanceName(), aOther.GetInstanceName());
176 }
177 
178 //---------------------------------------------------------------------
179 // Client::TxJitter
180 
181 const uint32_t Client::TxJitter::kMaxJitters[] = {
182     Client::kMaxTxJitterOnDeviceReboot,    // (0) kOnDeviceReboot
183     Client::kMaxTxJitterOnServerStart,     // (1) kOnServerStart
184     Client::kMaxTxJitterOnServerRestart,   // (2) kOnServerRestart
185     Client::kMaxTxJitterOnServerSwitch,    // (3) kOnServerSwitch
186     Client::kMaxTxJitterOnSlaacAddrAdd,    // (4) kOnSlaacAddrAdd
187     Client::kMaxTxJitterOnSlaacAddrRemove, // (5) kOnSlaacAddrRemove
188 };
189 
Request(Reason aReason)190 void Client::TxJitter::Request(Reason aReason)
191 {
192     static_assert(0 == kOnDeviceReboot, "kOnDeviceReboot value is incorrect");
193     static_assert(1 == kOnServerStart, "kOnServerStart value is incorrect");
194     static_assert(2 == kOnServerRestart, "kOnServerRestart value is incorrect");
195     static_assert(3 == kOnServerSwitch, "kOnServerSwitch value is incorrect");
196     static_assert(4 == kOnSlaacAddrAdd, "kOnSlaacAddrAdd value is incorrect");
197     static_assert(5 == kOnSlaacAddrRemove, "kOnSlaacAddrRemove value is incorrect");
198 
199     uint32_t maxJitter = kMaxJitters[aReason];
200 
201     LogInfo("Requesting max tx jitter %lu (%s)", ToUlong(maxJitter), ReasonToString(aReason));
202 
203     if (mRequestedMax != 0)
204     {
205         // If we have a previous request, adjust the `mRequestedMax`
206         // based on the time elapsed since that request was made.
207 
208         uint32_t duration = TimerMilli::GetNow() - mRequestTime;
209 
210         mRequestedMax = (mRequestedMax > duration) ? mRequestedMax - duration : 0;
211     }
212 
213     mRequestedMax = Max(mRequestedMax, maxJitter);
214     mRequestTime  = TimerMilli::GetNow();
215 }
216 
DetermineDelay(void)217 uint32_t Client::TxJitter::DetermineDelay(void)
218 {
219     uint32_t delay;
220     uint32_t maxJitter = kMaxTxJitterDefault;
221 
222     if (mRequestedMax != 0)
223     {
224         uint32_t duration = TimerMilli::GetNow() - mRequestTime;
225 
226         if (duration >= mRequestedMax)
227         {
228             LogInfo("Requested max tx jitter %lu already expired", ToUlong(mRequestedMax));
229         }
230         else
231         {
232             maxJitter = Max(mRequestedMax - duration, kMaxTxJitterDefault);
233             LogInfo("Applying remaining max jitter %lu", ToUlong(maxJitter));
234         }
235 
236         mRequestedMax = 0;
237     }
238 
239     delay = Random::NonCrypto::GetUint32InRange(kMinTxJitter, maxJitter);
240     LogInfo("Use random tx jitter %lu from [%lu, %lu]", ToUlong(delay), ToUlong(kMinTxJitter), ToUlong(maxJitter));
241 
242     return delay;
243 }
244 
245 #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
ReasonToString(Reason aReason)246 const char *Client::TxJitter::ReasonToString(Reason aReason)
247 {
248     static const char *const kReasonStrings[] = {
249         "OnDeviceReboot",    // (0) kOnDeviceReboot
250         "OnServerStart",     // (1) kOnServerStart
251         "OnServerRestart",   // (2) kOnServerRestart
252         "OnServerSwitch",    // (3) kOnServerSwitch
253         "OnSlaacAddrAdd",    // (4) kOnSlaacAddrAdd
254         "OnSlaacAddrRemove", // (5) kOnSlaacAddrRemove
255     };
256 
257     static_assert(0 == kOnDeviceReboot, "kOnDeviceReboot value is incorrect");
258     static_assert(1 == kOnServerStart, "kOnServerStart value is incorrect");
259     static_assert(2 == kOnServerRestart, "kOnServerRestart value is incorrect");
260     static_assert(3 == kOnServerSwitch, "kOnServerSwitch value is incorrect");
261     static_assert(4 == kOnSlaacAddrAdd, "kOnSlaacAddrAdd value is incorrect");
262     static_assert(5 == kOnSlaacAddrRemove, "kOnSlaacAddrRemove value is incorrect");
263 
264     return kReasonStrings[aReason];
265 }
266 #endif
267 
268 //---------------------------------------------------------------------
269 // Client::AutoStart
270 
271 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
272 
AutoStart(void)273 Client::AutoStart::AutoStart(void)
274 {
275     Clear();
276     mState = kDefaultMode ? kFirstTimeSelecting : kDisabled;
277 }
278 
HasSelectedServer(void) const279 bool Client::AutoStart::HasSelectedServer(void) const
280 {
281     bool hasSelected = false;
282 
283     switch (mState)
284     {
285     case kDisabled:
286     case kFirstTimeSelecting:
287     case kReselecting:
288         break;
289 
290     case kSelectedUnicastPreferred:
291     case kSelectedUnicast:
292     case kSelectedAnycast:
293         hasSelected = true;
294         break;
295     }
296 
297     return hasSelected;
298 }
299 
SetState(State aState)300 void Client::AutoStart::SetState(State aState)
301 {
302     if (mState != aState)
303     {
304         LogInfo("AutoStartState %s -> %s", StateToString(mState), StateToString(aState));
305         mState = aState;
306     }
307 }
308 
InvokeCallback(const Ip6::SockAddr * aServerSockAddr) const309 void Client::AutoStart::InvokeCallback(const Ip6::SockAddr *aServerSockAddr) const
310 {
311     mCallback.InvokeIfSet(aServerSockAddr);
312 }
313 
314 #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
StateToString(State aState)315 const char *Client::AutoStart::StateToString(State aState)
316 {
317     static const char *const kStateStrings[] = {
318         "Disabled",      // (0) kDisabled
319         "1stTimeSelect", // (1) kFirstTimeSelecting
320         "Reselect",      // (2) kReselecting
321         "Unicast-prf",   // (3) kSelectedUnicastPreferred
322         "Anycast",       // (4) kSelectedAnycast
323         "Unicast",       // (5) kSelectedUnicast
324     };
325 
326     static_assert(0 == kDisabled, "kDisabled value is incorrect");
327     static_assert(1 == kFirstTimeSelecting, "kFirstTimeSelecting value is incorrect");
328     static_assert(2 == kReselecting, "kReselecting value is incorrect");
329     static_assert(3 == kSelectedUnicastPreferred, "kSelectedUnicastPreferred value is incorrect");
330     static_assert(4 == kSelectedAnycast, "kSelectedAnycast value is incorrect");
331     static_assert(5 == kSelectedUnicast, "kSelectedUnicast value is incorrect");
332 
333     return kStateStrings[aState];
334 }
335 #endif
336 
337 #endif // OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
338 
339 //---------------------------------------------------------------------
340 // Client
341 
342 const char Client::kDefaultDomainName[] = "default.service.arpa";
343 
Client(Instance & aInstance)344 Client::Client(Instance &aInstance)
345     : InstanceLocator(aInstance)
346     , mState(kStateStopped)
347     , mTxFailureRetryCount(0)
348     , mShouldRemoveKeyLease(false)
349     , mSingleServiceMode(false)
350 #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
351     , mServiceKeyRecordEnabled(false)
352     , mUseShortLeaseOption(false)
353 #endif
354     , mNextMessageId(0)
355     , mResponseMessageId(0)
356     , mAutoHostAddressCount(0)
357     , mRetryWaitInterval(kMinRetryWaitInterval)
358     , mTtl(0)
359     , mLease(0)
360     , mKeyLease(0)
361     , mDefaultLease(kDefaultLease)
362     , mDefaultKeyLease(kDefaultKeyLease)
363     , mSocket(aInstance, *this)
364     , mDomainName(kDefaultDomainName)
365     , mTimer(aInstance)
366 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
367     , mGuardTimer(aInstance)
368 #endif
369 {
370     mHostInfo.Init();
371 
372     // The `Client` implementation uses different constant array of
373     // `ItemState` to define transitions between states in `Pause()`,
374     // `Stop()`, `SendUpdate`, and `ProcessResponse()`, or to convert
375     // an `ItemState` to string. Here, we assert that the enumeration
376     // values are correct.
377 
378     static_assert(kToAdd == 0, "kToAdd value is not correct");
379     static_assert(kAdding == 1, "kAdding value is not correct");
380     static_assert(kToRefresh == 2, "kToRefresh value is not correct");
381     static_assert(kRefreshing == 3, "kRefreshing value is not correct");
382     static_assert(kToRemove == 4, "kToRemove value is not correct");
383     static_assert(kRemoving == 5, "kRemoving value is not correct");
384     static_assert(kRegistered == 6, "kRegistered value is not correct");
385     static_assert(kRemoved == 7, "kRemoved value is not correct");
386 }
387 
Start(const Ip6::SockAddr & aServerSockAddr,Requester aRequester)388 Error Client::Start(const Ip6::SockAddr &aServerSockAddr, Requester aRequester)
389 {
390     Error error;
391 
392     VerifyOrExit(GetState() == kStateStopped,
393                  error = (aServerSockAddr == GetServerAddress()) ? kErrorNone : kErrorBusy);
394 
395     SuccessOrExit(error = mSocket.Open());
396     SuccessOrExit(error = mSocket.Connect(aServerSockAddr));
397 
398     LogInfo("%starting, server %s", (aRequester == kRequesterUser) ? "S" : "Auto-s",
399             aServerSockAddr.ToString().AsCString());
400 
401     Resume();
402 
403 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
404     if (aRequester == kRequesterAuto)
405     {
406 #if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE && OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_SERVER_ADDRESS_AUTO_SET_ENABLE
407         Get<Dns::Client>().UpdateDefaultConfigAddress();
408 #endif
409         mAutoStart.InvokeCallback(&aServerSockAddr);
410     }
411 #endif
412 
413 exit:
414     return error;
415 }
416 
Stop(Requester aRequester,StopMode aMode)417 void Client::Stop(Requester aRequester, StopMode aMode)
418 {
419     // Change the state of host info and services so that they are
420     // added/removed again once the client is started back. In the
421     // case of `kAdding`, we intentionally move to `kToRefresh`
422     // instead of `kToAdd` since the server may receive our add
423     // request and the item may be registered on the server. This
424     // ensures that if we are later asked to remove the item, we do
425     // notify server.
426 
427     static const ItemState kNewStateOnStop[]{
428         /* (0) kToAdd      -> */ kToAdd,
429         /* (1) kAdding     -> */ kToRefresh,
430         /* (2) kToRefresh  -> */ kToRefresh,
431         /* (3) kRefreshing -> */ kToRefresh,
432         /* (4) kToRemove   -> */ kToRemove,
433         /* (5) kRemoving   -> */ kToRemove,
434         /* (6) kRegistered -> */ kToRefresh,
435         /* (7) kRemoved    -> */ kRemoved,
436     };
437 
438     VerifyOrExit(GetState() != kStateStopped);
439 
440     mSingleServiceMode = false;
441 
442     // State changes:
443     //   kAdding     -> kToRefresh
444     //   kRefreshing -> kToRefresh
445     //   kRemoving   -> kToRemove
446     //   kRegistered -> kToRefresh
447 
448     ChangeHostAndServiceStates(kNewStateOnStop, kForAllServices);
449 
450     IgnoreError(mSocket.Close());
451 
452     mShouldRemoveKeyLease = false;
453     mTxFailureRetryCount  = 0;
454     mResponseMessageId    = mNextMessageId;
455 
456     if (aMode == kResetRetryInterval)
457     {
458         ResetRetryWaitInterval();
459     }
460 
461     SetState(kStateStopped);
462 
463 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
464 #if OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE
465     mAutoStart.ResetTimeoutFailureCount();
466 #endif
467     if (aRequester == kRequesterAuto)
468     {
469         mAutoStart.InvokeCallback(nullptr);
470     }
471 #endif
472 
473 exit:
474 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
475     if (aRequester == kRequesterUser)
476     {
477         DisableAutoStartMode();
478     }
479 #endif
480 }
481 
Resume(void)482 void Client::Resume(void)
483 {
484     SetState(kStateUpdated);
485     UpdateState();
486 }
487 
Pause(void)488 void Client::Pause(void)
489 {
490     // Change the state of host info and services that are are being
491     // added or removed so that they are added/removed again once the
492     // client is resumed or started back.
493 
494     static const ItemState kNewStateOnPause[]{
495         /* (0) kToAdd      -> */ kToAdd,
496         /* (1) kAdding     -> */ kToRefresh,
497         /* (2) kToRefresh  -> */ kToRefresh,
498         /* (3) kRefreshing -> */ kToRefresh,
499         /* (4) kToRemove   -> */ kToRemove,
500         /* (5) kRemoving   -> */ kToRemove,
501         /* (6) kRegistered -> */ kRegistered,
502         /* (7) kRemoved    -> */ kRemoved,
503     };
504 
505     mSingleServiceMode = false;
506 
507     // State changes:
508     //   kAdding     -> kToRefresh
509     //   kRefreshing -> kToRefresh
510     //   kRemoving   -> kToRemove
511 
512     ChangeHostAndServiceStates(kNewStateOnPause, kForAllServices);
513 
514     SetState(kStatePaused);
515 }
516 
HandleNotifierEvents(Events aEvents)517 void Client::HandleNotifierEvents(Events aEvents)
518 {
519     if (aEvents.Contains(kEventThreadRoleChanged))
520     {
521         HandleRoleChanged();
522     }
523 
524 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
525     if (aEvents.ContainsAny(kEventThreadNetdataChanged | kEventThreadMeshLocalAddrChanged))
526     {
527         ProcessAutoStart();
528     }
529 #endif
530 
531     if (aEvents.ContainsAny(kEventIp6AddressAdded | kEventIp6AddressRemoved | kEventThreadMeshLocalAddrChanged) &&
532         ShouldUpdateHostAutoAddresses())
533     {
534         IgnoreError(UpdateHostInfoStateOnAddressChange());
535         UpdateState();
536     }
537 }
538 
HandleRoleChanged(void)539 void Client::HandleRoleChanged(void)
540 {
541     if (Get<Mle::Mle>().IsAttached())
542     {
543 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
544         ApplyAutoStartGuardOnAttach();
545 #endif
546 
547         VerifyOrExit(GetState() == kStatePaused);
548         Resume();
549     }
550     else
551     {
552         VerifyOrExit(GetState() != kStateStopped);
553         Pause();
554     }
555 
556 exit:
557     return;
558 }
559 
560 #if OPENTHREAD_CONFIG_SRP_CLIENT_DOMAIN_NAME_API_ENABLE
SetDomainName(const char * aName)561 Error Client::SetDomainName(const char *aName)
562 {
563     Error error = kErrorNone;
564 
565     VerifyOrExit((mHostInfo.GetState() == kToAdd) || (mHostInfo.GetState() == kRemoved), error = kErrorInvalidState);
566 
567     mDomainName = (aName != nullptr) ? aName : kDefaultDomainName;
568     LogInfo("Domain name \"%s\"", mDomainName);
569 
570 exit:
571     return error;
572 }
573 #endif
574 
SetHostName(const char * aName)575 Error Client::SetHostName(const char *aName)
576 {
577     Error error = kErrorNone;
578 
579     VerifyOrExit(aName != nullptr, error = kErrorInvalidArgs);
580 
581     VerifyOrExit((mHostInfo.GetState() == kToAdd) || (mHostInfo.GetState() == kRemoved), error = kErrorInvalidState);
582 
583     LogInfo("Host name \"%s\"", aName);
584     mHostInfo.SetName(aName);
585     mHostInfo.SetState(kToAdd);
586     UpdateState();
587 
588 exit:
589     return error;
590 }
591 
EnableAutoHostAddress(void)592 Error Client::EnableAutoHostAddress(void)
593 {
594     Error error = kErrorNone;
595 
596     VerifyOrExit(!mHostInfo.IsAutoAddressEnabled());
597     SuccessOrExit(error = UpdateHostInfoStateOnAddressChange());
598 
599     for (Ip6::Netif::UnicastAddress &unicastAddress : Get<ThreadNetif>().GetUnicastAddresses())
600     {
601         unicastAddress.mSrpRegistered = false;
602     }
603 
604     mAutoHostAddressCount = 0;
605 
606     mHostInfo.EnableAutoAddress();
607     UpdateState();
608 
609 exit:
610     return error;
611 }
612 
SetHostAddresses(const Ip6::Address * aAddresses,uint8_t aNumAddresses)613 Error Client::SetHostAddresses(const Ip6::Address *aAddresses, uint8_t aNumAddresses)
614 {
615     Error error = kErrorNone;
616 
617     VerifyOrExit((aAddresses != nullptr) && (aNumAddresses > 0), error = kErrorInvalidArgs);
618     SuccessOrExit(error = UpdateHostInfoStateOnAddressChange());
619 
620     mHostInfo.SetAddresses(aAddresses, aNumAddresses);
621     UpdateState();
622 
623 exit:
624     return error;
625 }
626 
HandleUnicastAddressEvent(Ip6::Netif::AddressEvent aEvent,const Ip6::Netif::UnicastAddress & aAddress)627 void Client::HandleUnicastAddressEvent(Ip6::Netif::AddressEvent aEvent, const Ip6::Netif::UnicastAddress &aAddress)
628 {
629     // This callback from `Netif` signals an impending addition or
630     // removal of a unicast address, occurring before `Notifier`
631     // events. If `AutoAddress` is enabled, we check whether the
632     // address origin is SLAAC (e.g., an OMR address) and request a
633     // longer `TxJitter`. This helps randomize the next SRP
634     // update transmission time when triggered by an OMR prefix
635     // change.
636 
637     VerifyOrExit(IsRunning());
638     VerifyOrExit(mHostInfo.IsAutoAddressEnabled());
639 
640     VerifyOrExit(aAddress.GetOrigin() == Ip6::Netif::kOriginSlaac);
641 
642 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
643     // The `mGuardTimer`, started by `ApplyAutoStartGuardOnAttach()`,
644     // tracks a guard interval after the attach event. If an
645     // address change occurs within this short window, we do not
646     // apply a longer TX jitter, as this likely indicates a device
647     // reboot.
648     VerifyOrExit(!mGuardTimer.IsRunning());
649 #endif
650 
651     mTxJitter.Request((aEvent == Ip6::Netif::kAddressAdded) ? TxJitter::kOnSlaacAddrAdd : TxJitter::kOnSlaacAddrRemove);
652 
653 exit:
654     return;
655 }
656 
ShouldUpdateHostAutoAddresses(void) const657 bool Client::ShouldUpdateHostAutoAddresses(void) const
658 {
659     // Determine if any changes to the addresses on `ThreadNetif`
660     // require registration with the server when `AutoHostAddress` is
661     // enabled. This includes registering all preferred addresses,
662     // excluding link-local and mesh-local addresses. If no eligible
663     // address is available, the ML-EID will be registered.
664 
665     bool                        shouldUpdate    = false;
666     uint16_t                    registeredCount = 0;
667     Ip6::Netif::UnicastAddress &mlEid           = Get<Mle::Mle>().GetMeshLocalEidUnicastAddress();
668 
669     VerifyOrExit(mHostInfo.IsAutoAddressEnabled());
670 
671     // We iterate through all eligible addresses on the `ThreadNetif`.
672     // If we encounter a new address that should be registered but
673     // isn't, or a previously registered address has been removed, we
674     // trigger an SRP update to reflect these changes. However, if a
675     // previously registered address is being deprecated (e.g., due
676     // to an OMR prefix removal from Network Data), we defer the SRP
677     // update. The client will re-register after the deprecation
678     // time has elapsed and the address is removed. In the meantime,
679     // if any other event triggers the client to send an SRP update,
680     // the updated address list will be included in that update.
681 
682     for (const Ip6::Netif::UnicastAddress &unicastAddress : Get<ThreadNetif>().GetUnicastAddresses())
683     {
684         if (&unicastAddress == &mlEid)
685         {
686             continue;
687         }
688 
689         if (ShouldHostAutoAddressRegister(unicastAddress) != unicastAddress.mSrpRegistered)
690         {
691             // If this address was previously registered but is no
692             // longer eligible, we skip sending an immediate update
693             // only if the address is currently being deprecated
694             // (it's still valid but no longer preferred).
695 
696             bool skip = unicastAddress.mSrpRegistered && unicastAddress.mValid && !unicastAddress.mPreferred;
697 
698             if (!skip)
699             {
700                 ExitNow(shouldUpdate = true);
701             }
702         }
703 
704         if (unicastAddress.mSrpRegistered)
705         {
706             registeredCount++;
707         }
708     }
709 
710     if (registeredCount == 0)
711     {
712         ExitNow(shouldUpdate = !mlEid.mSrpRegistered);
713     }
714 
715     // Compare the current number of addresses that are marked as
716     // registered with the previous value `mAutoHostAddressCount`.
717     // This check handles the case where a previously registered address
718     // has been removed.
719 
720     shouldUpdate = (registeredCount != mAutoHostAddressCount);
721 
722 exit:
723     return shouldUpdate;
724 }
725 
ShouldHostAutoAddressRegister(const Ip6::Netif::UnicastAddress & aUnicastAddress) const726 bool Client::ShouldHostAutoAddressRegister(const Ip6::Netif::UnicastAddress &aUnicastAddress) const
727 {
728     bool shouldRegister = false;
729 
730     VerifyOrExit(aUnicastAddress.mValid);
731     VerifyOrExit(aUnicastAddress.mPreferred);
732     VerifyOrExit(!aUnicastAddress.GetAddress().IsLinkLocalUnicast());
733     VerifyOrExit(!Get<Mle::Mle>().IsMeshLocalAddress(aUnicastAddress.GetAddress()));
734 
735     shouldRegister = true;
736 
737 exit:
738     return shouldRegister;
739 }
740 
UpdateHostInfoStateOnAddressChange(void)741 Error Client::UpdateHostInfoStateOnAddressChange(void)
742 {
743     Error error = kErrorNone;
744 
745     VerifyOrExit((mHostInfo.GetState() != kToRemove) && (mHostInfo.GetState() != kRemoving),
746                  error = kErrorInvalidState);
747 
748     if (mHostInfo.GetState() == kRemoved)
749     {
750         mHostInfo.SetState(kToAdd);
751     }
752     else if (mHostInfo.GetState() != kToAdd)
753     {
754         mHostInfo.SetState(kToRefresh);
755     }
756 
757 exit:
758     return error;
759 }
760 
AddService(Service & aService)761 Error Client::AddService(Service &aService)
762 {
763     Error error;
764 
765     VerifyOrExit(mServices.FindMatching(aService) == nullptr, error = kErrorAlready);
766 
767     SuccessOrExit(error = aService.Init());
768     mServices.Push(aService);
769 
770     aService.SetState(kToAdd);
771     UpdateState();
772 
773 exit:
774     return error;
775 }
776 
RemoveService(Service & aService)777 Error Client::RemoveService(Service &aService)
778 {
779     Error               error = kErrorNone;
780     LinkedList<Service> removedServices;
781 
782     VerifyOrExit(mServices.Contains(aService), error = kErrorNotFound);
783 
784     UpdateServiceStateToRemove(aService);
785     UpdateState();
786 
787 exit:
788     return error;
789 }
790 
UpdateServiceStateToRemove(Service & aService)791 void Client::UpdateServiceStateToRemove(Service &aService)
792 {
793     if (aService.GetState() != kRemoving)
794     {
795         aService.SetState(kToRemove);
796     }
797 }
798 
ClearService(Service & aService)799 Error Client::ClearService(Service &aService)
800 {
801     Error error;
802 
803     SuccessOrExit(error = mServices.Remove(aService));
804     aService.SetNext(nullptr);
805     aService.SetState(kRemoved);
806     UpdateState();
807 
808 exit:
809     return error;
810 }
811 
RemoveHostAndServices(bool aShouldRemoveKeyLease,bool aSendUnregToServer)812 Error Client::RemoveHostAndServices(bool aShouldRemoveKeyLease, bool aSendUnregToServer)
813 {
814     Error error = kErrorNone;
815 
816     LogInfo("Remove host & services");
817 
818     VerifyOrExit(mHostInfo.GetState() != kRemoved, error = kErrorAlready);
819 
820     if ((mHostInfo.GetState() == kToRemove) || (mHostInfo.GetState() == kRemoving))
821     {
822         // Host info remove is already ongoing, if "key lease" remove mode is
823         // the same, there is no need to send a new update message.
824         VerifyOrExit(mShouldRemoveKeyLease != aShouldRemoveKeyLease);
825     }
826 
827     mShouldRemoveKeyLease = aShouldRemoveKeyLease;
828 
829     for (Service &service : mServices)
830     {
831         UpdateServiceStateToRemove(service);
832     }
833 
834     if ((mHostInfo.GetState() == kToAdd) && !aSendUnregToServer)
835     {
836         // Host info is not added yet (not yet registered with
837         // server), so we can remove it and all services immediately.
838         mHostInfo.SetState(kRemoved);
839         HandleUpdateDone();
840         ExitNow();
841     }
842 
843     mHostInfo.SetState(kToRemove);
844     UpdateState();
845 
846 exit:
847     return error;
848 }
849 
ClearHostAndServices(void)850 void Client::ClearHostAndServices(void)
851 {
852     LogInfo("Clear host & services");
853 
854     switch (GetState())
855     {
856     case kStateStopped:
857     case kStatePaused:
858         break;
859 
860     case kStateToUpdate:
861     case kStateUpdating:
862     case kStateUpdated:
863     case kStateToRetry:
864         SetState(kStateUpdated);
865         break;
866     }
867 
868     mTxFailureRetryCount = 0;
869     ResetRetryWaitInterval();
870 
871     mServices.Clear();
872     mHostInfo.Clear();
873 }
874 
SetState(State aState)875 void Client::SetState(State aState)
876 {
877     VerifyOrExit(aState != mState);
878 
879     LogInfo("State %s -> %s", StateToString(mState), StateToString(aState));
880     mState = aState;
881 
882     switch (mState)
883     {
884     case kStateStopped:
885     case kStatePaused:
886     case kStateUpdated:
887         mTimer.Stop();
888         break;
889 
890     case kStateToUpdate:
891         mTimer.Start(mTxJitter.DetermineDelay());
892         break;
893 
894     case kStateUpdating:
895         mTimer.Start(GetRetryWaitInterval());
896         break;
897 
898     case kStateToRetry:
899         break;
900     }
901 exit:
902     return;
903 }
904 
ChangeHostAndServiceStates(const ItemState * aNewStates,ServiceStateChangeMode aMode)905 bool Client::ChangeHostAndServiceStates(const ItemState *aNewStates, ServiceStateChangeMode aMode)
906 {
907     bool anyChanged;
908 
909 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE && OPENTHREAD_CONFIG_SRP_CLIENT_SAVE_SELECTED_SERVER_ENABLE
910     ItemState oldHostState = mHostInfo.GetState();
911 #endif
912 
913     anyChanged = mHostInfo.SetState(aNewStates[mHostInfo.GetState()]);
914 
915     for (Service &service : mServices)
916     {
917         if ((aMode == kForServicesAppendedInMessage) && !service.IsAppendedInMessage())
918         {
919             continue;
920         }
921 
922         anyChanged |= service.SetState(aNewStates[service.GetState()]);
923     }
924 
925 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE && OPENTHREAD_CONFIG_SRP_CLIENT_SAVE_SELECTED_SERVER_ENABLE
926     if ((oldHostState != kRegistered) && (mHostInfo.GetState() == kRegistered))
927     {
928         Settings::SrpClientInfo info;
929 
930         switch (mAutoStart.GetState())
931         {
932         case AutoStart::kDisabled:
933         case AutoStart::kFirstTimeSelecting:
934         case AutoStart::kReselecting:
935             break;
936 
937         case AutoStart::kSelectedUnicastPreferred:
938         case AutoStart::kSelectedUnicast:
939             info.SetServerAddress(GetServerAddress().GetAddress());
940             info.SetServerPort(GetServerAddress().GetPort());
941             IgnoreError(Get<Settings>().Save(info));
942             break;
943 
944         case AutoStart::kSelectedAnycast:
945             IgnoreError(Get<Settings>().Delete<Settings::SrpClientInfo>());
946             break;
947         }
948     }
949 #endif // OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE && OPENTHREAD_CONFIG_SRP_CLIENT_SAVE_SELECTED_SERVER_ENABLE
950 
951     return anyChanged;
952 }
953 
InvokeCallback(Error aError) const954 void Client::InvokeCallback(Error aError) const { InvokeCallback(aError, mHostInfo, nullptr); }
955 
InvokeCallback(Error aError,const HostInfo & aHostInfo,const Service * aRemovedServices) const956 void Client::InvokeCallback(Error aError, const HostInfo &aHostInfo, const Service *aRemovedServices) const
957 {
958     mCallback.InvokeIfSet(aError, &aHostInfo, mServices.GetHead(), aRemovedServices);
959 }
960 
SendUpdate(void)961 void Client::SendUpdate(void)
962 {
963     static const ItemState kNewStateOnMessageTx[]{
964         /* (0) kToAdd      -> */ kAdding,
965         /* (1) kAdding     -> */ kAdding,
966         /* (2) kToRefresh  -> */ kRefreshing,
967         /* (3) kRefreshing -> */ kRefreshing,
968         /* (4) kToRemove   -> */ kRemoving,
969         /* (5) kRemoving   -> */ kRemoving,
970         /* (6) kRegistered -> */ kRegistered,
971         /* (7) kRemoved    -> */ kRemoved,
972     };
973 
974     Error    error = kErrorNone;
975     MsgInfo  info;
976     uint32_t length;
977     bool     anyChanged;
978 
979     info.mMessage.Reset(mSocket.NewMessage());
980     VerifyOrExit(info.mMessage != nullptr, error = kErrorNoBufs);
981 
982     SuccessOrExit(error = PrepareUpdateMessage(info));
983 
984     length = info.mMessage->GetLength() + sizeof(Ip6::Udp::Header) + sizeof(Ip6::Header);
985 
986     if (length >= Ip6::kMaxDatagramLength)
987     {
988         LogInfo("Msg len %lu is larger than MTU, enabling single service mode", ToUlong(length));
989         mSingleServiceMode = true;
990         IgnoreError(info.mMessage->SetLength(0));
991         SuccessOrExit(error = PrepareUpdateMessage(info));
992     }
993 
994     SuccessOrExit(error = mSocket.SendTo(*info.mMessage, Ip6::MessageInfo()));
995 
996     // Ownership of the message is transferred to the socket upon a
997     // successful `SendTo()` call.
998 
999     info.mMessage.Release();
1000 
1001     LogInfo("Send update, msg-id:0x%x", mNextMessageId);
1002 
1003     // State changes:
1004     //   kToAdd     -> kAdding
1005     //   kToRefresh -> kRefreshing
1006     //   kToRemove  -> kRemoving
1007 
1008     anyChanged = ChangeHostAndServiceStates(kNewStateOnMessageTx, kForServicesAppendedInMessage);
1009 
1010     // `mNextMessageId` tracks the message ID used in the prepared
1011     // update message. It is incremented after a successful
1012     // `mSocket.SendTo()` call. If unsuccessful, the same ID can be
1013     // reused for the next update.
1014     //
1015     // Acceptable response message IDs fall within the range starting
1016     // at `mResponseMessageId ` and ending before `mNextMessageId`.
1017     //
1018     // `anyChanged` tracks if any host or service states have changed.
1019     // If not, the prepared message is identical to the last one with
1020     // the same hosts/services, allowing us to accept earlier message
1021     // IDs. If changes occur, `mResponseMessageId ` is updated to
1022     // ensure only responses to the latest message are accepted.
1023 
1024     if (anyChanged)
1025     {
1026         mResponseMessageId = mNextMessageId;
1027     }
1028 
1029     mNextMessageId++;
1030 
1031     // Remember the update message tx time to use later to determine the
1032     // lease renew time.
1033     mLeaseRenewTime      = TimerMilli::GetNow();
1034     mTxFailureRetryCount = 0;
1035 
1036     SetState(kStateUpdating);
1037 
1038     if (!Get<Mle::Mle>().IsRxOnWhenIdle())
1039     {
1040         // If device is sleepy send fast polls while waiting for
1041         // the response from server.
1042         Get<DataPollSender>().SendFastPolls(kFastPollsAfterUpdateTx);
1043     }
1044 
1045 exit:
1046     if (error != kErrorNone)
1047     {
1048         // If there is an error in preparation or transmission of the
1049         // update message (e.g., no buffer to allocate message), up to
1050         // `kMaxTxFailureRetries` times, we wait for a short interval
1051         // `kTxFailureRetryInterval` and try again. After this, we
1052         // continue to retry using the `mRetryWaitInterval` (which keeps
1053         // growing on each failure).
1054 
1055         LogInfo("Failed to send update: %s", ErrorToString(error));
1056 
1057         mSingleServiceMode = false;
1058 
1059         SetState(kStateToRetry);
1060 
1061         if (mTxFailureRetryCount < kMaxTxFailureRetries)
1062         {
1063             uint32_t interval;
1064 
1065             mTxFailureRetryCount++;
1066             interval = Random::NonCrypto::AddJitter(kTxFailureRetryInterval, kTxFailureRetryJitter);
1067             mTimer.Start(interval);
1068 
1069             LogInfo("Quick retry %u in %lu msec", mTxFailureRetryCount, ToUlong(interval));
1070 
1071             // Do not report message preparation errors to user
1072             // until `kMaxTxFailureRetries` are exhausted.
1073         }
1074         else
1075         {
1076             LogRetryWaitInterval();
1077             mTimer.Start(Random::NonCrypto::AddJitter(GetRetryWaitInterval(), kRetryIntervalJitter));
1078             GrowRetryWaitInterval();
1079             InvokeCallback(error);
1080         }
1081     }
1082 }
1083 
PrepareUpdateMessage(MsgInfo & aInfo)1084 Error Client::PrepareUpdateMessage(MsgInfo &aInfo)
1085 {
1086     constexpr uint16_t kHeaderOffset = 0;
1087 
1088     Error             error = kErrorNone;
1089     Dns::UpdateHeader header;
1090 
1091     aInfo.mDomainNameOffset = MsgInfo::kUnknownOffset;
1092     aInfo.mHostNameOffset   = MsgInfo::kUnknownOffset;
1093     aInfo.mRecordCount      = 0;
1094 
1095 #if OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE
1096     aInfo.mKeyInfo.SetKeyRef(kSrpEcdsaKeyRef);
1097 #endif
1098 
1099     SuccessOrExit(error = ReadOrGenerateKey(aInfo.mKeyInfo));
1100 
1101     header.SetMessageId(mNextMessageId);
1102 
1103     // SRP Update (DNS Update) message must have exactly one record in
1104     // Zone section, no records in Prerequisite Section, can have
1105     // multiple records in Update Section (tracked as they are added),
1106     // and two records in Additional Data Section (OPT and SIG records).
1107     // The SIG record itself should not be included in calculation of
1108     // SIG(0) signature, so the addition record count is set to one
1109     // here. After signature calculation and appending of SIG record,
1110     // the additional record count is updated to two and the header is
1111     // rewritten in the message.
1112 
1113     header.SetZoneRecordCount(1);
1114     header.SetAdditionalRecordCount(1);
1115     SuccessOrExit(error = aInfo.mMessage->Append(header));
1116 
1117     // Prepare Zone section
1118 
1119     aInfo.mDomainNameOffset = aInfo.mMessage->GetLength();
1120     SuccessOrExit(error = Dns::Name::AppendName(mDomainName, *aInfo.mMessage));
1121     SuccessOrExit(error = aInfo.mMessage->Append(Dns::Zone()));
1122 
1123     // Prepare Update section
1124 
1125     SuccessOrExit(error = AppendServiceInstructions(aInfo));
1126     SuccessOrExit(error = AppendHostDescriptionInstruction(aInfo));
1127 
1128     header.SetUpdateRecordCount(aInfo.mRecordCount);
1129     aInfo.mMessage->Write(kHeaderOffset, header);
1130 
1131     // Prepare Additional Data section
1132 
1133     SuccessOrExit(error = AppendUpdateLeaseOptRecord(aInfo));
1134     SuccessOrExit(error = AppendSignature(aInfo));
1135 
1136     header.SetAdditionalRecordCount(2); // Lease OPT and SIG RRs
1137     aInfo.mMessage->Write(kHeaderOffset, header);
1138 
1139 exit:
1140     return error;
1141 }
1142 
1143 #if OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE
ReadOrGenerateKey(KeyInfo & aKeyInfo)1144 Error Client::ReadOrGenerateKey(KeyInfo &aKeyInfo)
1145 {
1146     Error                        error = kErrorNone;
1147     Crypto::Ecdsa::P256::KeyPair keyPair;
1148 
1149     VerifyOrExit(!Crypto::Storage::HasKey(aKeyInfo.GetKeyRef()));
1150     error = Get<Settings>().Read<Settings::SrpEcdsaKey>(keyPair);
1151 
1152     if (error == kErrorNone)
1153     {
1154         if (aKeyInfo.ImportKeyPair(keyPair) != kErrorNone)
1155         {
1156             SuccessOrExit(error = aKeyInfo.Generate());
1157         }
1158         IgnoreError(Get<Settings>().Delete<Settings::SrpEcdsaKey>());
1159     }
1160     else
1161     {
1162         SuccessOrExit(error = aKeyInfo.Generate());
1163     }
1164 exit:
1165     return error;
1166 }
1167 #else
ReadOrGenerateKey(KeyInfo & aKeyInfo)1168 Error Client::ReadOrGenerateKey(KeyInfo &aKeyInfo)
1169 {
1170     Error error;
1171 
1172     error = Get<Settings>().Read<Settings::SrpEcdsaKey>(aKeyInfo);
1173 
1174     if (error == kErrorNone)
1175     {
1176         Crypto::Ecdsa::P256::PublicKey publicKey;
1177 
1178         if (aKeyInfo.GetPublicKey(publicKey) == kErrorNone)
1179         {
1180             ExitNow();
1181         }
1182     }
1183 
1184     SuccessOrExit(error = aKeyInfo.Generate());
1185     IgnoreError(Get<Settings>().Save<Settings::SrpEcdsaKey>(aKeyInfo));
1186 
1187 exit:
1188     return error;
1189 }
1190 #endif //  OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE
1191 
AppendServiceInstructions(MsgInfo & aInfo)1192 Error Client::AppendServiceInstructions(MsgInfo &aInfo)
1193 {
1194     Error error = kErrorNone;
1195 
1196     if ((mHostInfo.GetState() == kToRemove) || (mHostInfo.GetState() == kRemoving))
1197     {
1198         // When host is being removed, there is no need to include
1199         // services in the message (server is expected to remove any
1200         // previously registered services by this client). However, we
1201         // still mark all services as if they are appended in the message
1202         // so to ensure to update their state after sending the message.
1203 
1204         for (Service &service : mServices)
1205         {
1206             service.MarkAsAppendedInMessage();
1207         }
1208 
1209         mLease    = 0;
1210         mKeyLease = mShouldRemoveKeyLease ? 0 : mDefaultKeyLease;
1211         ExitNow();
1212     }
1213 
1214     mLease    = kUnspecifiedInterval;
1215     mKeyLease = kUnspecifiedInterval;
1216 
1217     // We first go through all services which are being updated (in any
1218     // of `...ing` states) and determine the lease and key lease intervals
1219     // associated with them. By the end of the loop either of `mLease` or
1220     // `mKeyLease` may be set or may still remain `kUnspecifiedInterval`.
1221 
1222     for (Service &service : mServices)
1223     {
1224         uint32_t lease    = DetermineLeaseInterval(service.GetLease(), mDefaultLease);
1225         uint32_t keyLease = Max(DetermineLeaseInterval(service.GetKeyLease(), mDefaultKeyLease), lease);
1226 
1227         service.ClearAppendedInMessageFlag();
1228 
1229         switch (service.GetState())
1230         {
1231         case kAdding:
1232         case kRefreshing:
1233             OT_ASSERT((mLease == kUnspecifiedInterval) || (mLease == lease));
1234             mLease = lease;
1235 
1236             OT_FALL_THROUGH;
1237 
1238         case kRemoving:
1239             OT_ASSERT((mKeyLease == kUnspecifiedInterval) || (mKeyLease == keyLease));
1240             mKeyLease = keyLease;
1241             break;
1242 
1243         case kToAdd:
1244         case kToRefresh:
1245         case kToRemove:
1246         case kRegistered:
1247         case kRemoved:
1248             break;
1249         }
1250     }
1251 
1252     // We go through all services again and append the services that
1253     // match the selected `mLease` and `mKeyLease`. If the lease intervals
1254     // are not yet set, the first appended service will determine them.
1255 
1256     for (Service &service : mServices)
1257     {
1258         // Skip over services that are already registered in this loop.
1259         // They may be added from the loop below once the lease intervals
1260         // are determined.
1261 
1262         if ((service.GetState() != kRegistered) && CanAppendService(service))
1263         {
1264             SuccessOrExit(error = AppendServiceInstruction(service, aInfo));
1265 
1266             if (mSingleServiceMode)
1267             {
1268                 // In "single service mode", we allow only one service
1269                 // to be appended in the message.
1270                 break;
1271             }
1272         }
1273     }
1274 
1275     if (!mSingleServiceMode)
1276     {
1277         for (Service &service : mServices)
1278         {
1279             if ((service.GetState() == kRegistered) && CanAppendService(service) && ShouldRenewEarly(service))
1280             {
1281                 // If the lease needs to be renewed or if we are close to the
1282                 // renewal time of a registered service, we refresh the service
1283                 // early and include it in this update. This helps put more
1284                 // services on the same lease refresh schedule.
1285 
1286                 service.SetState(kToRefresh);
1287                 SuccessOrExit(error = AppendServiceInstruction(service, aInfo));
1288             }
1289         }
1290     }
1291 
1292     // `mLease` or `mKeylease` may be determined from the set of
1293     // services included in the message. If they are not yet set we
1294     // use the default intervals.
1295 
1296     mLease    = DetermineLeaseInterval(mLease, mDefaultLease);
1297     mKeyLease = DetermineLeaseInterval(mKeyLease, mDefaultKeyLease);
1298 
1299     // When message only contains removal of a previously registered
1300     // service, then `mKeyLease` is set but `mLease` remains unspecified.
1301     // In such a case, we end up using `mDefaultLease` but then we need
1302     // to make sure it is not greater than the selected `mKeyLease`.
1303 
1304     mLease = Min(mLease, mKeyLease);
1305 
1306 exit:
1307     return error;
1308 }
1309 
CanAppendService(const Service & aService)1310 bool Client::CanAppendService(const Service &aService)
1311 {
1312     // Check the lease intervals associated with `aService` to see if
1313     // it can be included in this message. When removing a service,
1314     // only key lease interval should match. In all other cases, both
1315     // lease and key lease should match. The `mLease` and/or `mKeyLease`
1316     // may be updated if they were unspecified.
1317 
1318     bool     canAppend = false;
1319     uint32_t lease     = DetermineLeaseInterval(aService.GetLease(), mDefaultLease);
1320     uint32_t keyLease  = Max(DetermineLeaseInterval(aService.GetKeyLease(), mDefaultKeyLease), lease);
1321 
1322     switch (aService.GetState())
1323     {
1324     case kToAdd:
1325     case kAdding:
1326     case kToRefresh:
1327     case kRefreshing:
1328     case kRegistered:
1329         VerifyOrExit((mLease == kUnspecifiedInterval) || (mLease == lease));
1330         VerifyOrExit((mKeyLease == kUnspecifiedInterval) || (mKeyLease == keyLease));
1331         mLease    = lease;
1332         mKeyLease = keyLease;
1333         canAppend = true;
1334         break;
1335 
1336     case kToRemove:
1337     case kRemoving:
1338         VerifyOrExit((mKeyLease == kUnspecifiedInterval) || (mKeyLease == keyLease));
1339         mKeyLease = keyLease;
1340         canAppend = true;
1341         break;
1342 
1343     case kRemoved:
1344         break;
1345     }
1346 
1347 exit:
1348     return canAppend;
1349 }
1350 
AppendServiceInstruction(Service & aService,MsgInfo & aInfo)1351 Error Client::AppendServiceInstruction(Service &aService, MsgInfo &aInfo)
1352 {
1353     Error               error    = kErrorNone;
1354     bool                removing = ((aService.GetState() == kToRemove) || (aService.GetState() == kRemoving));
1355     Dns::ResourceRecord rr;
1356     Dns::SrvRecord      srv;
1357     uint16_t            serviceNameOffset;
1358     uint16_t            instanceNameOffset;
1359     uint16_t            offset;
1360 
1361     aService.MarkAsAppendedInMessage();
1362 
1363     //----------------------------------
1364     // Service Discovery Instruction
1365 
1366     // PTR record
1367 
1368     // "service name labels" + (pointer to) domain name.
1369     serviceNameOffset = aInfo.mMessage->GetLength();
1370     SuccessOrExit(error = Dns::Name::AppendMultipleLabels(aService.GetName(), *aInfo.mMessage));
1371     SuccessOrExit(error = Dns::Name::AppendPointerLabel(aInfo.mDomainNameOffset, *aInfo.mMessage));
1372 
1373     // On remove, we use "Delete an RR from an RRSet" where class is set
1374     // to NONE and TTL to zero (RFC 2136 - section 2.5.4).
1375 
1376     rr.Init(Dns::ResourceRecord::kTypePtr, removing ? Dns::PtrRecord::kClassNone : Dns::PtrRecord::kClassInternet);
1377     rr.SetTtl(removing ? 0 : DetermineTtl());
1378     offset = aInfo.mMessage->GetLength();
1379     SuccessOrExit(error = aInfo.mMessage->Append(rr));
1380 
1381     // "Instance name" + (pointer to) service name.
1382     instanceNameOffset = aInfo.mMessage->GetLength();
1383     SuccessOrExit(error = Dns::Name::AppendLabel(aService.GetInstanceName(), *aInfo.mMessage));
1384     SuccessOrExit(error = Dns::Name::AppendPointerLabel(serviceNameOffset, *aInfo.mMessage));
1385 
1386     UpdateRecordLengthInMessage(rr, offset, *aInfo.mMessage);
1387     aInfo.mRecordCount++;
1388 
1389     if (aService.HasSubType() && !removing)
1390     {
1391         const char *subTypeLabel;
1392         uint16_t    subServiceNameOffset = 0;
1393 
1394         for (uint16_t index = 0; (subTypeLabel = aService.GetSubTypeLabelAt(index)) != nullptr; ++index)
1395         {
1396             // subtype label + "_sub" label + (pointer to) service name.
1397 
1398             SuccessOrExit(error = Dns::Name::AppendLabel(subTypeLabel, *aInfo.mMessage));
1399 
1400             if (index == 0)
1401             {
1402                 subServiceNameOffset = aInfo.mMessage->GetLength();
1403                 SuccessOrExit(error = Dns::Name::AppendLabel("_sub", *aInfo.mMessage));
1404                 SuccessOrExit(error = Dns::Name::AppendPointerLabel(serviceNameOffset, *aInfo.mMessage));
1405             }
1406             else
1407             {
1408                 SuccessOrExit(error = Dns::Name::AppendPointerLabel(subServiceNameOffset, *aInfo.mMessage));
1409             }
1410 
1411             // `rr` is already initialized as PTR.
1412             offset = aInfo.mMessage->GetLength();
1413             SuccessOrExit(error = aInfo.mMessage->Append(rr));
1414 
1415             SuccessOrExit(error = Dns::Name::AppendPointerLabel(instanceNameOffset, *aInfo.mMessage));
1416             UpdateRecordLengthInMessage(rr, offset, *aInfo.mMessage);
1417             aInfo.mRecordCount++;
1418         }
1419     }
1420 
1421     //----------------------------------
1422     // Service Description Instruction
1423 
1424     // "Delete all RRsets from a name" for Instance Name.
1425 
1426     SuccessOrExit(error = Dns::Name::AppendPointerLabel(instanceNameOffset, *aInfo.mMessage));
1427     SuccessOrExit(error = AppendDeleteAllRrsets(aInfo));
1428     aInfo.mRecordCount++;
1429 
1430     VerifyOrExit(!removing);
1431 
1432     // SRV RR
1433 
1434     SuccessOrExit(error = Dns::Name::AppendPointerLabel(instanceNameOffset, *aInfo.mMessage));
1435     srv.Init();
1436     srv.SetTtl(DetermineTtl());
1437     srv.SetPriority(aService.GetPriority());
1438     srv.SetWeight(aService.GetWeight());
1439     srv.SetPort(aService.GetPort());
1440     offset = aInfo.mMessage->GetLength();
1441     SuccessOrExit(error = aInfo.mMessage->Append(srv));
1442     SuccessOrExit(error = AppendHostName(aInfo));
1443     UpdateRecordLengthInMessage(srv, offset, *aInfo.mMessage);
1444     aInfo.mRecordCount++;
1445 
1446     // TXT RR
1447 
1448     SuccessOrExit(error = Dns::Name::AppendPointerLabel(instanceNameOffset, *aInfo.mMessage));
1449     rr.Init(Dns::ResourceRecord::kTypeTxt);
1450     offset = aInfo.mMessage->GetLength();
1451     SuccessOrExit(error = aInfo.mMessage->Append(rr));
1452     SuccessOrExit(
1453         error = Dns::TxtEntry::AppendEntries(aService.GetTxtEntries(), aService.GetNumTxtEntries(), *aInfo.mMessage));
1454     UpdateRecordLengthInMessage(rr, offset, *aInfo.mMessage);
1455     aInfo.mRecordCount++;
1456 
1457 #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
1458     if (mServiceKeyRecordEnabled)
1459     {
1460         // KEY RR is optional in "Service Description Instruction". It
1461         // is added here under `REFERENCE_DEVICE` config and is intended
1462         // for testing only.
1463 
1464         SuccessOrExit(error = Dns::Name::AppendPointerLabel(instanceNameOffset, *aInfo.mMessage));
1465         SuccessOrExit(error = AppendKeyRecord(aInfo));
1466     }
1467 #endif
1468 
1469 exit:
1470     return error;
1471 }
1472 
AppendHostDescriptionInstruction(MsgInfo & aInfo)1473 Error Client::AppendHostDescriptionInstruction(MsgInfo &aInfo)
1474 {
1475     Error error = kErrorNone;
1476 
1477     //----------------------------------
1478     // Host Description Instruction
1479 
1480     // "Delete all RRsets from a name" for Host Name.
1481 
1482     SuccessOrExit(error = AppendHostName(aInfo));
1483     SuccessOrExit(error = AppendDeleteAllRrsets(aInfo));
1484     aInfo.mRecordCount++;
1485 
1486     // AAAA RRs
1487 
1488     if (mHostInfo.IsAutoAddressEnabled())
1489     {
1490         // Append all preferred addresses on Thread netif excluding link-local
1491         // and mesh-local addresses. If no address is appended, we include
1492         // the mesh local EID.
1493 
1494         mAutoHostAddressCount = 0;
1495 
1496         for (Ip6::Netif::UnicastAddress &unicastAddress : Get<ThreadNetif>().GetUnicastAddresses())
1497         {
1498             if (ShouldHostAutoAddressRegister(unicastAddress))
1499             {
1500                 SuccessOrExit(error = AppendAaaaRecord(unicastAddress.GetAddress(), aInfo));
1501                 unicastAddress.mSrpRegistered = true;
1502                 mAutoHostAddressCount++;
1503             }
1504             else
1505             {
1506                 unicastAddress.mSrpRegistered = false;
1507             }
1508         }
1509 
1510         if (mAutoHostAddressCount == 0)
1511         {
1512             Ip6::Netif::UnicastAddress &mlEid = Get<Mle::Mle>().GetMeshLocalEidUnicastAddress();
1513 
1514             SuccessOrExit(error = AppendAaaaRecord(mlEid.GetAddress(), aInfo));
1515             mlEid.mSrpRegistered = true;
1516             mAutoHostAddressCount++;
1517         }
1518     }
1519     else
1520     {
1521         for (uint8_t index = 0; index < mHostInfo.GetNumAddresses(); index++)
1522         {
1523             SuccessOrExit(error = AppendAaaaRecord(mHostInfo.GetAddress(index), aInfo));
1524         }
1525     }
1526 
1527     // KEY RR
1528 
1529     SuccessOrExit(error = AppendHostName(aInfo));
1530     SuccessOrExit(error = AppendKeyRecord(aInfo));
1531 
1532 exit:
1533     return error;
1534 }
1535 
AppendAaaaRecord(const Ip6::Address & aAddress,MsgInfo & aInfo) const1536 Error Client::AppendAaaaRecord(const Ip6::Address &aAddress, MsgInfo &aInfo) const
1537 {
1538     Error               error;
1539     Dns::ResourceRecord rr;
1540 
1541     rr.Init(Dns::ResourceRecord::kTypeAaaa);
1542     rr.SetTtl(DetermineTtl());
1543     rr.SetLength(sizeof(Ip6::Address));
1544 
1545     SuccessOrExit(error = AppendHostName(aInfo));
1546     SuccessOrExit(error = aInfo.mMessage->Append(rr));
1547     SuccessOrExit(error = aInfo.mMessage->Append(aAddress));
1548     aInfo.mRecordCount++;
1549 
1550 exit:
1551     return error;
1552 }
1553 
AppendKeyRecord(MsgInfo & aInfo) const1554 Error Client::AppendKeyRecord(MsgInfo &aInfo) const
1555 {
1556     Error                          error;
1557     Dns::KeyRecord                 key;
1558     Crypto::Ecdsa::P256::PublicKey publicKey;
1559 
1560     key.Init();
1561     key.SetTtl(DetermineTtl());
1562     key.SetFlags(Dns::KeyRecord::kAuthConfidPermitted, Dns::KeyRecord::kOwnerNonZone,
1563                  Dns::KeyRecord::kSignatoryFlagGeneral);
1564     key.SetProtocol(Dns::KeyRecord::kProtocolDnsSec);
1565     key.SetAlgorithm(Dns::KeyRecord::kAlgorithmEcdsaP256Sha256);
1566     key.SetLength(sizeof(Dns::KeyRecord) - sizeof(Dns::ResourceRecord) + sizeof(Crypto::Ecdsa::P256::PublicKey));
1567     SuccessOrExit(error = aInfo.mMessage->Append(key));
1568     SuccessOrExit(error = aInfo.mKeyInfo.GetPublicKey(publicKey));
1569     SuccessOrExit(error = aInfo.mMessage->Append(publicKey));
1570     aInfo.mRecordCount++;
1571 
1572 exit:
1573     return error;
1574 }
1575 
AppendDeleteAllRrsets(MsgInfo & aInfo) const1576 Error Client::AppendDeleteAllRrsets(MsgInfo &aInfo) const
1577 {
1578     // "Delete all RRsets from a name" (RFC 2136 - 2.5.3)
1579     // Name should be already appended in the message.
1580 
1581     Dns::ResourceRecord rr;
1582 
1583     rr.Init(Dns::ResourceRecord::kTypeAny, Dns::ResourceRecord::kClassAny);
1584     rr.SetTtl(0);
1585     rr.SetLength(0);
1586 
1587     return aInfo.mMessage->Append(rr);
1588 }
1589 
AppendHostName(MsgInfo & aInfo,bool aDoNotCompress) const1590 Error Client::AppendHostName(MsgInfo &aInfo, bool aDoNotCompress) const
1591 {
1592     Error error;
1593 
1594     if (aDoNotCompress)
1595     {
1596         // Uncompressed (canonical form) of host name is used for SIG(0)
1597         // calculation.
1598         SuccessOrExit(error = Dns::Name::AppendMultipleLabels(mHostInfo.GetName(), *aInfo.mMessage));
1599         error = Dns::Name::AppendName(mDomainName, *aInfo.mMessage);
1600         ExitNow();
1601     }
1602 
1603     // If host name was previously added in the message, add it
1604     // compressed as pointer to the previous one. Otherwise,
1605     // append it and remember the offset.
1606 
1607     if (aInfo.mHostNameOffset != MsgInfo::kUnknownOffset)
1608     {
1609         ExitNow(error = Dns::Name::AppendPointerLabel(aInfo.mHostNameOffset, *aInfo.mMessage));
1610     }
1611 
1612     aInfo.mHostNameOffset = aInfo.mMessage->GetLength();
1613     SuccessOrExit(error = Dns::Name::AppendMultipleLabels(mHostInfo.GetName(), *aInfo.mMessage));
1614     error = Dns::Name::AppendPointerLabel(aInfo.mDomainNameOffset, *aInfo.mMessage);
1615 
1616 exit:
1617     return error;
1618 }
1619 
AppendUpdateLeaseOptRecord(MsgInfo & aInfo)1620 Error Client::AppendUpdateLeaseOptRecord(MsgInfo &aInfo)
1621 {
1622     Error            error;
1623     Dns::OptRecord   optRecord;
1624     Dns::LeaseOption leaseOption;
1625     uint16_t         optionSize;
1626 
1627     // Append empty (root domain) as OPT RR name.
1628     SuccessOrExit(error = Dns::Name::AppendTerminator(*aInfo.mMessage));
1629 
1630     // `Init()` sets the type and clears (set to zero) the extended
1631     // Response Code, version and all flags.
1632     optRecord.Init();
1633     optRecord.SetUdpPayloadSize(kUdpPayloadSize);
1634     optRecord.SetDnsSecurityFlag();
1635 
1636 #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
1637     if (mUseShortLeaseOption)
1638     {
1639         LogInfo("Test mode - appending short variant of Lease Option");
1640         mKeyLease = mLease;
1641         leaseOption.InitAsShortVariant(mLease);
1642     }
1643     else
1644 #endif
1645     {
1646         leaseOption.InitAsLongVariant(mLease, mKeyLease);
1647     }
1648 
1649     optionSize = static_cast<uint16_t>(leaseOption.GetSize());
1650 
1651     optRecord.SetLength(optionSize);
1652 
1653     SuccessOrExit(error = aInfo.mMessage->Append(optRecord));
1654     error = aInfo.mMessage->AppendBytes(&leaseOption, optionSize);
1655 
1656 exit:
1657     return error;
1658 }
1659 
AppendSignature(MsgInfo & aInfo)1660 Error Client::AppendSignature(MsgInfo &aInfo)
1661 {
1662     Error                          error;
1663     Dns::SigRecord                 sig;
1664     Crypto::Sha256                 sha256;
1665     Crypto::Sha256::Hash           hash;
1666     Crypto::Ecdsa::P256::Signature signature;
1667     uint16_t                       offset;
1668     uint16_t                       len;
1669 
1670     // Prepare SIG RR: TTL, type covered, labels count should be set
1671     // to zero. Since we have no clock, inception and expiration time
1672     // are also set to zero. The RDATA length will be set later (not
1673     // yet known due to variably (and possible compression) of signer's
1674     // name.
1675 
1676     sig.Clear();
1677     sig.Init(Dns::ResourceRecord::kClassAny);
1678     sig.SetAlgorithm(Dns::KeyRecord::kAlgorithmEcdsaP256Sha256);
1679 
1680     // Append the SIG RR with full uncompressed form of the host name
1681     // as the signer's name. This is used for SIG(0) calculation only.
1682     // It will be overwritten with host name compressed.
1683 
1684     offset = aInfo.mMessage->GetLength();
1685     SuccessOrExit(error = aInfo.mMessage->Append(sig));
1686     SuccessOrExit(error = AppendHostName(aInfo, /* aDoNotCompress */ true));
1687 
1688     // Calculate signature (RFC 2931): Calculated over "data" which is
1689     // concatenation of (1) the SIG RR RDATA wire format (including
1690     // the canonical form of the signer's name), entirely omitting the
1691     // signature subfield, (2) DNS query message, including DNS header
1692     // but not UDP/IP header before the header RR counts have been
1693     // adjusted for the inclusion of SIG(0).
1694 
1695     sha256.Start();
1696 
1697     // (1) SIG RR RDATA wire format
1698     len = aInfo.mMessage->GetLength() - offset - sizeof(Dns::ResourceRecord);
1699     sha256.Update(*aInfo.mMessage, offset + sizeof(Dns::ResourceRecord), len);
1700 
1701     // (2) Message from DNS header before SIG
1702     sha256.Update(*aInfo.mMessage, 0, offset);
1703 
1704     sha256.Finish(hash);
1705     SuccessOrExit(error = aInfo.mKeyInfo.Sign(hash, signature));
1706 
1707     // Move back in message and append SIG RR now with compressed host
1708     // name (as signer's name) along with the calculated signature.
1709 
1710     IgnoreError(aInfo.mMessage->SetLength(offset));
1711 
1712     // SIG(0) uses owner name of root (single zero byte).
1713     SuccessOrExit(error = Dns::Name::AppendTerminator(*aInfo.mMessage));
1714 
1715     offset = aInfo.mMessage->GetLength();
1716     SuccessOrExit(error = aInfo.mMessage->Append(sig));
1717     SuccessOrExit(error = AppendHostName(aInfo));
1718     SuccessOrExit(error = aInfo.mMessage->Append(signature));
1719     UpdateRecordLengthInMessage(sig, offset, *aInfo.mMessage);
1720 
1721 exit:
1722     return error;
1723 }
1724 
UpdateRecordLengthInMessage(Dns::ResourceRecord & aRecord,uint16_t aOffset,Message & aMessage) const1725 void Client::UpdateRecordLengthInMessage(Dns::ResourceRecord &aRecord, uint16_t aOffset, Message &aMessage) const
1726 {
1727     // This method is used to calculate an RR DATA length and update
1728     // (rewrite) it in a message. This should be called immediately
1729     // after all the fields in the record are written in the message.
1730     // `aOffset` gives the offset in the message to the start of the
1731     // record.
1732 
1733     aRecord.SetLength(aMessage.GetLength() - aOffset - sizeof(Dns::ResourceRecord));
1734     aMessage.Write(aOffset, aRecord);
1735 }
1736 
HandleUdpReceive(Message & aMessage,const Ip6::MessageInfo & aMessageInfo)1737 void Client::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
1738 {
1739     OT_UNUSED_VARIABLE(aMessageInfo);
1740 
1741     ProcessResponse(aMessage);
1742 }
1743 
ProcessResponse(Message & aMessage)1744 void Client::ProcessResponse(Message &aMessage)
1745 {
1746     static const ItemState kNewStateOnUpdateDone[]{
1747         /* (0) kToAdd      -> */ kToAdd,
1748         /* (1) kAdding     -> */ kRegistered,
1749         /* (2) kToRefresh  -> */ kToRefresh,
1750         /* (3) kRefreshing -> */ kRegistered,
1751         /* (4) kToRemove   -> */ kToRemove,
1752         /* (5) kRemoving   -> */ kRemoved,
1753         /* (6) kRegistered -> */ kRegistered,
1754         /* (7) kRemoved    -> */ kRemoved,
1755     };
1756 
1757     Error               error = kErrorNone;
1758     Dns::UpdateHeader   header;
1759     uint16_t            offset = aMessage.GetOffset();
1760     uint16_t            recordCount;
1761     LinkedList<Service> removedServices;
1762 
1763     switch (GetState())
1764     {
1765     case kStateToUpdate:
1766     case kStateUpdating:
1767     case kStateToRetry:
1768         break;
1769     case kStateStopped:
1770     case kStatePaused:
1771     case kStateUpdated:
1772         ExitNow();
1773     }
1774 
1775     SuccessOrExit(error = aMessage.Read(offset, header));
1776 
1777     VerifyOrExit(header.GetType() == Dns::Header::kTypeResponse, error = kErrorParse);
1778     VerifyOrExit(header.GetQueryType() == Dns::Header::kQueryTypeUpdate, error = kErrorParse);
1779 
1780     VerifyOrExit(IsResponseMessageIdValid(header.GetMessageId()), error = kErrorDrop);
1781     mResponseMessageId = header.GetMessageId() + 1;
1782 
1783     if (!Get<Mle::Mle>().IsRxOnWhenIdle())
1784     {
1785         Get<DataPollSender>().StopFastPolls();
1786     }
1787 
1788     LogInfo("Received response, msg-id:0x%x", header.GetMessageId());
1789 
1790 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE && OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE
1791     mAutoStart.ResetTimeoutFailureCount();
1792 #endif
1793 
1794     error = Dns::Header::ResponseCodeToError(header.GetResponseCode());
1795 
1796     if (error != kErrorNone)
1797     {
1798         LogInfo("Server rejected %s code:%d", ErrorToString(error), header.GetResponseCode());
1799 
1800         if (mHostInfo.GetState() == kAdding)
1801         {
1802             // Since server rejected the update message, we go back to
1803             // `kToAdd` state to allow user to give a new name using
1804             // `SetHostName()`.
1805             mHostInfo.SetState(kToAdd);
1806         }
1807 
1808         // Wait for the timer to expire to retry. Note that timer is
1809         // already scheduled for the current wait interval when state
1810         // was changed to `kStateUpdating`.
1811 
1812         LogRetryWaitInterval();
1813         GrowRetryWaitInterval();
1814         SetState(kStateToRetry);
1815         InvokeCallback(error);
1816 
1817 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE && OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE
1818         if ((error == kErrorDuplicated) || (error == kErrorSecurity))
1819         {
1820             // If the server rejects the update with specific errors
1821             // (indicating duplicate name and/or security error), we
1822             // try to switch the server (we check if another can be
1823             // found in the Network Data).
1824             //
1825             // Note that this is done after invoking the callback and
1826             // notifying the user of the error from server. This works
1827             // correctly even if user makes changes from callback
1828             // (e.g., calls SRP client APIs like `Stop` or disables
1829             // auto-start), since we have a guard check at the top of
1830             // `SelectNextServer()` to verify that client is still
1831             // running and auto-start is enabled and selected the
1832             // server.
1833 
1834             SelectNextServer(/* aDisallowSwitchOnRegisteredHost */ true);
1835         }
1836 #endif
1837         ExitNow(error = kErrorNone);
1838     }
1839 
1840     offset += sizeof(header);
1841 
1842     // Skip over all sections till Additional Data section
1843     // SPEC ENHANCEMENT: Server can echo the request back or not
1844     // include any of RRs. Would be good to explicitly require SRP server
1845     // to not echo back RRs.
1846 
1847     if (header.GetZoneRecordCount() != 0)
1848     {
1849         VerifyOrExit(header.GetZoneRecordCount() == 1, error = kErrorParse);
1850         SuccessOrExit(error = Dns::Name::ParseName(aMessage, offset));
1851         VerifyOrExit(offset + sizeof(Dns::Zone) <= aMessage.GetLength(), error = kErrorParse);
1852         offset += sizeof(Dns::Zone);
1853     }
1854 
1855     // Check for Update Lease OPT RR. This determines the lease
1856     // interval accepted by server. If not present, then use the
1857     // transmitted lease interval from the update request message.
1858 
1859     recordCount =
1860         header.GetPrerequisiteRecordCount() + header.GetUpdateRecordCount() + header.GetAdditionalRecordCount();
1861 
1862     while (recordCount > 0)
1863     {
1864         uint16_t            startOffset = offset;
1865         Dns::ResourceRecord rr;
1866 
1867         SuccessOrExit(error = ReadResourceRecord(aMessage, offset, rr));
1868         recordCount--;
1869 
1870         if (rr.GetType() == Dns::ResourceRecord::kTypeOpt)
1871         {
1872             SuccessOrExit(error = ProcessOptRecord(aMessage, startOffset, static_cast<Dns::OptRecord &>(rr)));
1873         }
1874     }
1875 
1876     // Calculate the lease renew time based on update message tx time
1877     // and the lease time. `kLeaseRenewGuardInterval` is used to
1878     // ensure that we renew the lease before server expires it. In the
1879     // unlikely (but maybe useful for testing) case where the accepted
1880     // lease interval is too short (shorter than twice the guard time)
1881     // we just use half of the accepted lease interval.
1882 
1883     if (mLease > 2 * kLeaseRenewGuardInterval)
1884     {
1885         uint32_t interval = Time::SecToMsec(mLease - kLeaseRenewGuardInterval);
1886 
1887         mLeaseRenewTime += Random::NonCrypto::AddJitter(interval, kLeaseRenewJitter);
1888     }
1889     else
1890     {
1891         mLeaseRenewTime += Time::SecToMsec(mLease) / 2;
1892     }
1893 
1894     for (Service &service : mServices)
1895     {
1896         if ((service.GetState() == kAdding) || (service.GetState() == kRefreshing))
1897         {
1898             service.SetLeaseRenewTime(mLeaseRenewTime);
1899         }
1900     }
1901 
1902     // State changes:
1903     //   kAdding     -> kRegistered
1904     //   kRefreshing -> kRegistered
1905     //   kRemoving   -> kRemoved
1906 
1907     ChangeHostAndServiceStates(kNewStateOnUpdateDone, kForServicesAppendedInMessage);
1908 
1909     HandleUpdateDone();
1910     UpdateState();
1911 
1912 exit:
1913     if (error != kErrorNone)
1914     {
1915         LogInfo("Failed to process response %s", ErrorToString(error));
1916     }
1917 }
1918 
IsResponseMessageIdValid(uint16_t aId) const1919 bool Client::IsResponseMessageIdValid(uint16_t aId) const
1920 {
1921     // Semantically equivalent to `(aId >= mResponseMessageId) && (aId < mNextMessageId)`
1922 
1923     return !SerialNumber::IsLess(aId, mResponseMessageId) && SerialNumber::IsLess(aId, mNextMessageId);
1924 }
1925 
HandleUpdateDone(void)1926 void Client::HandleUpdateDone(void)
1927 {
1928     HostInfo            hostInfoCopy = mHostInfo;
1929     LinkedList<Service> removedServices;
1930 
1931     if (mHostInfo.GetState() == kRemoved)
1932     {
1933         mHostInfo.Clear();
1934     }
1935 
1936     ResetRetryWaitInterval();
1937     SetState(kStateUpdated);
1938 
1939     GetRemovedServices(removedServices);
1940     InvokeCallback(kErrorNone, hostInfoCopy, removedServices.GetHead());
1941 }
1942 
GetRemovedServices(LinkedList<Service> & aRemovedServices)1943 void Client::GetRemovedServices(LinkedList<Service> &aRemovedServices)
1944 {
1945     mServices.RemoveAllMatching(kRemoved, aRemovedServices);
1946 }
1947 
ReadResourceRecord(const Message & aMessage,uint16_t & aOffset,Dns::ResourceRecord & aRecord)1948 Error Client::ReadResourceRecord(const Message &aMessage, uint16_t &aOffset, Dns::ResourceRecord &aRecord)
1949 {
1950     // Reads and skips over a Resource Record (RR) from message at
1951     // given offset. On success, `aOffset` is updated to point to end
1952     // of RR.
1953 
1954     Error error;
1955 
1956     SuccessOrExit(error = Dns::Name::ParseName(aMessage, aOffset));
1957     SuccessOrExit(error = aMessage.Read(aOffset, aRecord));
1958     VerifyOrExit(aOffset + aRecord.GetSize() <= aMessage.GetLength(), error = kErrorParse);
1959     aOffset += static_cast<uint16_t>(aRecord.GetSize());
1960 
1961 exit:
1962     return error;
1963 }
1964 
ProcessOptRecord(const Message & aMessage,uint16_t aOffset,const Dns::OptRecord & aOptRecord)1965 Error Client::ProcessOptRecord(const Message &aMessage, uint16_t aOffset, const Dns::OptRecord &aOptRecord)
1966 {
1967     // Read and process all options (in an OPT RR) from a message.
1968     // The `aOffset` points to beginning of record in `aMessage`.
1969 
1970     Error            error = kErrorNone;
1971     Dns::LeaseOption leaseOption;
1972 
1973     IgnoreError(Dns::Name::ParseName(aMessage, aOffset));
1974     aOffset += sizeof(Dns::OptRecord);
1975 
1976     switch (error = leaseOption.ReadFrom(aMessage, aOffset, aOptRecord.GetLength()))
1977     {
1978     case kErrorNone:
1979         mLease    = Min(leaseOption.GetLeaseInterval(), kMaxLease);
1980         mKeyLease = Min(leaseOption.GetKeyLeaseInterval(), kMaxLease);
1981         break;
1982 
1983     case kErrorNotFound:
1984         // If server does not include a lease option in its response, it
1985         // indicates that it accepted what we requested.
1986         error = kErrorNone;
1987         break;
1988 
1989     default:
1990         ExitNow();
1991     }
1992 
1993 exit:
1994     return error;
1995 }
1996 
UpdateState(void)1997 void Client::UpdateState(void)
1998 {
1999     NextFireTime nextRenewTime;
2000     bool         shouldUpdate = false;
2001 
2002     VerifyOrExit((GetState() != kStateStopped) && (GetState() != kStatePaused));
2003     VerifyOrExit(mHostInfo.GetName() != nullptr);
2004 
2005     // Go through the host info and all the services to check if there
2006     // are any new changes (i.e., anything new to add or remove). This
2007     // is used to determine whether to send an SRP update message or
2008     // not. Also keep track of the earliest renew time among the
2009     // previously registered services. This is used to schedule the
2010     // timer for next refresh.
2011 
2012     switch (mHostInfo.GetState())
2013     {
2014     case kAdding:
2015     case kRefreshing:
2016     case kRemoving:
2017         break;
2018 
2019     case kRegistered:
2020         if (nextRenewTime.GetNow() < mLeaseRenewTime)
2021         {
2022             break;
2023         }
2024 
2025         mHostInfo.SetState(kToRefresh);
2026 
2027         // Fall through
2028 
2029     case kToAdd:
2030     case kToRefresh:
2031         // Make sure we have at least one service and at least one
2032         // host address, otherwise no need to send SRP update message.
2033         // The exception is when removing host info where we allow
2034         // for empty service list.
2035         VerifyOrExit(!mServices.IsEmpty() && (mHostInfo.IsAutoAddressEnabled() || (mHostInfo.GetNumAddresses() > 0)));
2036 
2037         // Fall through
2038 
2039     case kToRemove:
2040         shouldUpdate = true;
2041         break;
2042 
2043     case kRemoved:
2044         ExitNow();
2045     }
2046 
2047     // If host info is being removed, we skip over checking service list
2048     // for new adds (or removes). This handles the situation where while
2049     // remove is ongoing and before we get a response from the server,
2050     // user adds a new service to be registered. We wait for remove to
2051     // finish (receive response from server) before starting with a new
2052     // service adds.
2053 
2054     if (mHostInfo.GetState() != kRemoving)
2055     {
2056         for (Service &service : mServices)
2057         {
2058             switch (service.GetState())
2059             {
2060             case kToAdd:
2061             case kToRefresh:
2062             case kToRemove:
2063                 shouldUpdate = true;
2064                 break;
2065 
2066             case kRegistered:
2067                 if (service.GetLeaseRenewTime() <= nextRenewTime.GetNow())
2068                 {
2069                     service.SetState(kToRefresh);
2070                     shouldUpdate = true;
2071                 }
2072                 else
2073                 {
2074                     nextRenewTime.UpdateIfEarlier(service.GetLeaseRenewTime());
2075                 }
2076 
2077                 break;
2078 
2079             case kAdding:
2080             case kRefreshing:
2081             case kRemoving:
2082             case kRemoved:
2083                 break;
2084             }
2085         }
2086     }
2087 
2088     if (shouldUpdate)
2089     {
2090         SetState(kStateToUpdate);
2091         ExitNow();
2092     }
2093 
2094     if (GetState() == kStateUpdated)
2095     {
2096         mTimer.FireAt(nextRenewTime);
2097     }
2098 
2099 exit:
2100     return;
2101 }
2102 
GrowRetryWaitInterval(void)2103 void Client::GrowRetryWaitInterval(void)
2104 {
2105     mRetryWaitInterval =
2106         mRetryWaitInterval / kRetryIntervalGrowthFactorDenominator * kRetryIntervalGrowthFactorNumerator;
2107     mRetryWaitInterval = Min(mRetryWaitInterval, kMaxRetryWaitInterval);
2108 }
2109 
DetermineLeaseInterval(uint32_t aInterval,uint32_t aDefaultInterval) const2110 uint32_t Client::DetermineLeaseInterval(uint32_t aInterval, uint32_t aDefaultInterval) const
2111 {
2112     // Determine the lease or key lease interval.
2113     //
2114     // We use `aInterval` if it is non-zero, otherwise, use the
2115     // `aDefaultInterval`. We also ensure that the returned value is
2116     // never greater than `kMaxLease`. The `kMaxLease` is selected
2117     // such the lease intervals in msec can still fit in a `uint32_t`
2118     // `Time` variable (`kMaxLease` is ~ 24.8 days).
2119 
2120     return Min(kMaxLease, (aInterval != kUnspecifiedInterval) ? aInterval : aDefaultInterval);
2121 }
2122 
DetermineTtl(void) const2123 uint32_t Client::DetermineTtl(void) const
2124 {
2125     // Determine the TTL to use based on current `mLease`.
2126     // If `mLease == 0`, it indicates we are removing host
2127     // and so we use `mDefaultLease` instead.
2128 
2129     uint32_t lease = (mLease == 0) ? mDefaultLease : mLease;
2130 
2131     return (mTtl == kUnspecifiedInterval) ? lease : Min(mTtl, lease);
2132 }
2133 
ShouldRenewEarly(const Service & aService) const2134 bool Client::ShouldRenewEarly(const Service &aService) const
2135 {
2136     // Check if we reached the service renew time or close to it. The
2137     // "early renew interval" is used to allow early refresh. It is
2138     // calculated as a factor of the service requested lease interval.
2139     // The  "early lease renew factor" is given as a fraction (numerator
2140     // and denominator). If the denominator is set to zero (i.e., factor
2141     // is set to infinity), then service is always included in all SRP
2142     // update messages.
2143 
2144     bool shouldRenew;
2145 
2146 #if OPENTHREAD_CONFIG_SRP_CLIENT_EARLY_LEASE_RENEW_FACTOR_DENOMINATOR != 0
2147     uint32_t earlyRenewInterval;
2148 
2149     earlyRenewInterval = Time::SecToMsec(DetermineLeaseInterval(aService.GetLease(), mDefaultLease));
2150     earlyRenewInterval = earlyRenewInterval / kEarlyLeaseRenewFactorDenominator * kEarlyLeaseRenewFactorNumerator;
2151 
2152     shouldRenew = (aService.GetLeaseRenewTime() <= TimerMilli::GetNow() + earlyRenewInterval);
2153 #else
2154     OT_UNUSED_VARIABLE(aService);
2155     shouldRenew = true;
2156 #endif
2157 
2158     return shouldRenew;
2159 }
2160 
HandleTimer(void)2161 void Client::HandleTimer(void)
2162 {
2163     switch (GetState())
2164     {
2165     case kStateStopped:
2166     case kStatePaused:
2167         break;
2168 
2169     case kStateToUpdate:
2170     case kStateToRetry:
2171         SendUpdate();
2172         break;
2173 
2174     case kStateUpdating:
2175         mSingleServiceMode = false;
2176         LogRetryWaitInterval();
2177         LogInfo("Timed out, no response");
2178         GrowRetryWaitInterval();
2179         SetState(kStateToUpdate);
2180         InvokeCallback(kErrorResponseTimeout);
2181 
2182 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE && OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE
2183 
2184         // After certain number of back-to-back timeout failures, we try
2185         // to switch the server. This is again done after invoking the
2186         // callback. It works correctly due to the guard check at the
2187         // top of `SelectNextServer()`.
2188 
2189         mAutoStart.IncrementTimeoutFailureCount();
2190 
2191         if (mAutoStart.GetTimeoutFailureCount() >= kMaxTimeoutFailuresToSwitchServer)
2192         {
2193             SelectNextServer(kDisallowSwitchOnRegisteredHost);
2194         }
2195 #endif
2196         break;
2197 
2198     case kStateUpdated:
2199         UpdateState();
2200         break;
2201     }
2202 }
2203 
2204 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
2205 
EnableAutoStartMode(AutoStartCallback aCallback,void * aContext)2206 void Client::EnableAutoStartMode(AutoStartCallback aCallback, void *aContext)
2207 {
2208     mAutoStart.SetCallback(aCallback, aContext);
2209 
2210     VerifyOrExit(mAutoStart.GetState() == AutoStart::kDisabled);
2211 
2212     mAutoStart.SetState(AutoStart::kFirstTimeSelecting);
2213     ApplyAutoStartGuardOnAttach();
2214 
2215     ProcessAutoStart();
2216 
2217 exit:
2218     return;
2219 }
2220 
ApplyAutoStartGuardOnAttach(void)2221 void Client::ApplyAutoStartGuardOnAttach(void)
2222 {
2223     VerifyOrExit(Get<Mle::Mle>().IsAttached());
2224     VerifyOrExit(!IsRunning());
2225     VerifyOrExit(mAutoStart.GetState() == AutoStart::kFirstTimeSelecting);
2226 
2227     // The `mGuardTimer` tracks a guard interval after the attach
2228     // event while `AutoStart` has yet to select a server for the
2229     // first time.
2230     //
2231     // This is used by `ProcessAutoStart()` to apply different TX
2232     // jitter values. If server selection occurs within this short
2233     // window, a shorter TX jitter is used. This typically represents
2234     // the device rebooting or being paired.
2235     //
2236     // The guard time is also checked when handling SLAAC address change
2237     // events, to decide whether or not to request longer TX jitter.
2238 
2239     mGuardTimer.Start(kGuardTimeAfterAttachToUseShorterTxJitter);
2240 
2241 exit:
2242     return;
2243 }
2244 
ProcessAutoStart(void)2245 void Client::ProcessAutoStart(void)
2246 {
2247     Ip6::SockAddr     serverSockAddr;
2248     DnsSrpAnycastInfo anycastInfo;
2249     DnsSrpUnicastInfo unicastInfo;
2250     AutoStart::State  oldAutoStartState = mAutoStart.GetState();
2251     bool              shouldRestart     = false;
2252 
2253     // If auto start mode is enabled, we check the Network Data entries
2254     // to discover and select the preferred SRP server to register with.
2255     // If we currently have a selected server, we ensure that it is
2256     // still present in the Network Data and is still the preferred one.
2257 
2258     VerifyOrExit(mAutoStart.GetState() != AutoStart::kDisabled);
2259 
2260     // If SRP client is running, we check to make sure that auto-start
2261     // did select the current server, and server was not specified by
2262     // user directly.
2263 
2264     if (IsRunning())
2265     {
2266         VerifyOrExit(mAutoStart.HasSelectedServer());
2267     }
2268 
2269     // There are three types of entries in Network Data:
2270     //
2271     // 1) Preferred unicast entries with address included in service data.
2272     // 2) Anycast entries (each having a seq number).
2273     // 3) Unicast entries with address info included in server data.
2274 
2275     serverSockAddr.Clear();
2276 
2277     if (SelectUnicastEntry(NetworkData::Service::kAddrInServiceData, unicastInfo) == kErrorNone)
2278     {
2279         mAutoStart.SetState(AutoStart::kSelectedUnicastPreferred);
2280         serverSockAddr = unicastInfo.mSockAddr;
2281     }
2282     else if (Get<NetworkData::Service::Manager>().FindPreferredDnsSrpAnycastInfo(anycastInfo) == kErrorNone)
2283     {
2284         serverSockAddr.SetAddress(anycastInfo.mAnycastAddress);
2285         serverSockAddr.SetPort(kAnycastServerPort);
2286 
2287         // We check if we are selecting an anycast entry for first
2288         // time, or if the seq number has changed. Even if the
2289         // anycast address remains the same as before, on a seq
2290         // number change, the client still needs to restart to
2291         // re-register its info.
2292 
2293         if ((mAutoStart.GetState() != AutoStart::kSelectedAnycast) ||
2294             (mAutoStart.GetAnycastSeqNum() != anycastInfo.mSequenceNumber))
2295         {
2296             shouldRestart = true;
2297             mAutoStart.SetAnycastSeqNum(anycastInfo.mSequenceNumber);
2298         }
2299 
2300         mAutoStart.SetState(AutoStart::kSelectedAnycast);
2301     }
2302     else if (SelectUnicastEntry(NetworkData::Service::kAddrInServerData, unicastInfo) == kErrorNone)
2303     {
2304         mAutoStart.SetState(AutoStart::kSelectedUnicast);
2305         serverSockAddr = unicastInfo.mSockAddr;
2306     }
2307 
2308     if (IsRunning())
2309     {
2310         VerifyOrExit((GetServerAddress() != serverSockAddr) || shouldRestart);
2311         Stop(kRequesterAuto, kResetRetryInterval);
2312     }
2313 
2314     if (serverSockAddr.GetAddress().IsUnspecified())
2315     {
2316         if (mAutoStart.HasSelectedServer())
2317         {
2318             mAutoStart.SetState(AutoStart::kReselecting);
2319         }
2320 
2321         ExitNow();
2322     }
2323 
2324     // Before calling `Start()`, determine the trigger reason for
2325     // starting the client with the newly discovered server based on
2326     // `AutoStart` state transitions. This reason is then used to
2327     // select the appropriate TX jitter interval (randomizing the
2328     // initial SRP update transmission to the new server).
2329 
2330     switch (oldAutoStartState)
2331     {
2332     case AutoStart::kDisabled:
2333         break;
2334 
2335     case AutoStart::kFirstTimeSelecting:
2336 
2337         // If the device is attaching to an established Thread mesh
2338         // (e.g., after a reboot or pairing), the Network Data it
2339         // receives should already include a server entry, leading to
2340         // a quick server selection after attachment. The `mGuardTimer`,
2341         // started by `ApplyAutoStartGuardOnAttach()`, tracks a guard
2342         // interval after the attach event. If server selection
2343         // occurs within this short window, a shorter TX jitter is
2344         // used (`TxJitter::kOnDeviceReboot`), allowing the device to
2345         // register quickly and become discoverable.
2346         //
2347         // If server discovery takes longer, a longer TX jitter
2348         // is used (`TxJitter::kOnServerStart`). This situation
2349         // can indicate a server/BR starting up or a network-wide
2350         // restart of many nodes (e.g., due to a power outage).
2351 
2352         if (mGuardTimer.IsRunning())
2353         {
2354             mTxJitter.Request(TxJitter::kOnDeviceReboot);
2355         }
2356         else
2357         {
2358             mTxJitter.Request(TxJitter::kOnServerStart);
2359         }
2360 
2361         break;
2362 
2363     case AutoStart::kReselecting:
2364         // Server is restarted (or possibly a new server started).
2365         mTxJitter.Request(TxJitter::kOnServerRestart);
2366         break;
2367 
2368     case AutoStart::kSelectedUnicastPreferred:
2369     case AutoStart::kSelectedAnycast:
2370     case AutoStart::kSelectedUnicast:
2371         mTxJitter.Request(TxJitter::kOnServerSwitch);
2372         break;
2373     }
2374 
2375     IgnoreError(Start(serverSockAddr, kRequesterAuto));
2376 
2377 exit:
2378     return;
2379 }
2380 
SelectUnicastEntry(DnsSrpUnicastType aType,DnsSrpUnicastInfo & aInfo) const2381 Error Client::SelectUnicastEntry(DnsSrpUnicastType aType, DnsSrpUnicastInfo &aInfo) const
2382 {
2383     Error                                   error = kErrorNotFound;
2384     DnsSrpUnicastInfo                       unicastInfo;
2385     NetworkData::Service::Manager::Iterator iterator;
2386 #if OPENTHREAD_CONFIG_SRP_CLIENT_SAVE_SELECTED_SERVER_ENABLE
2387     Settings::SrpClientInfo savedInfo;
2388     bool                    hasSavedServerInfo = false;
2389 
2390     if (!IsRunning())
2391     {
2392         hasSavedServerInfo = (Get<Settings>().Read(savedInfo) == kErrorNone);
2393     }
2394 #endif
2395 
2396     while (Get<NetworkData::Service::Manager>().GetNextDnsSrpUnicastInfo(iterator, aType, unicastInfo) == kErrorNone)
2397     {
2398         if (mAutoStart.HasSelectedServer() && (GetServerAddress() == unicastInfo.mSockAddr))
2399         {
2400             aInfo = unicastInfo;
2401             error = kErrorNone;
2402             ExitNow();
2403         }
2404 
2405 #if OPENTHREAD_CONFIG_SRP_CLIENT_SAVE_SELECTED_SERVER_ENABLE
2406         if (hasSavedServerInfo && (unicastInfo.mSockAddr.GetAddress() == savedInfo.GetServerAddress()) &&
2407             (unicastInfo.mSockAddr.GetPort() == savedInfo.GetServerPort()))
2408         {
2409             // Stop the search if we see a match for the previously
2410             // saved server info in the network data entries.
2411 
2412             aInfo = unicastInfo;
2413             error = kErrorNone;
2414             ExitNow();
2415         }
2416 #endif
2417 
2418         // Prefer the numerically lowest server address
2419 
2420         if ((error == kErrorNotFound) || (unicastInfo.mSockAddr.GetAddress() < aInfo.mSockAddr.GetAddress()))
2421         {
2422             aInfo = unicastInfo;
2423             error = kErrorNone;
2424         }
2425     }
2426 
2427 exit:
2428     return error;
2429 }
2430 
2431 #if OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE
SelectNextServer(bool aDisallowSwitchOnRegisteredHost)2432 void Client::SelectNextServer(bool aDisallowSwitchOnRegisteredHost)
2433 {
2434     // This method tries to find the next unicast server info entry in the
2435     // Network Data after the current one selected. If found, it
2436     // restarts the client with the new server (keeping the retry wait
2437     // interval as before).
2438 
2439     Ip6::SockAddr     serverSockAddr;
2440     bool              selectNext = false;
2441     DnsSrpUnicastType type       = NetworkData::Service::kAddrInServiceData;
2442 
2443     serverSockAddr.Clear();
2444 
2445     // Ensure that client is running, auto-start is enabled and
2446     // auto-start selected the server and it is a unicast entry.
2447 
2448     VerifyOrExit(IsRunning());
2449 
2450     switch (mAutoStart.GetState())
2451     {
2452     case AutoStart::kSelectedUnicastPreferred:
2453         type = NetworkData::Service::kAddrInServiceData;
2454         break;
2455 
2456     case AutoStart::kSelectedUnicast:
2457         type = NetworkData::Service::kAddrInServerData;
2458         break;
2459 
2460     case AutoStart::kSelectedAnycast:
2461     case AutoStart::kDisabled:
2462     case AutoStart::kFirstTimeSelecting:
2463     case AutoStart::kReselecting:
2464         ExitNow();
2465     }
2466 
2467     if (aDisallowSwitchOnRegisteredHost)
2468     {
2469         // Ensure that host info is not yet registered (indicating that no
2470         // service has yet been registered either).
2471         VerifyOrExit((mHostInfo.GetState() == kAdding) || (mHostInfo.GetState() == kToAdd));
2472     }
2473 
2474     // We go through all entries to find the one matching the currently
2475     // selected one, then set `selectNext` to `true` so to select the
2476     // next one.
2477 
2478     do
2479     {
2480         DnsSrpUnicastInfo                       unicastInfo;
2481         NetworkData::Service::Manager::Iterator iterator;
2482 
2483         while (Get<NetworkData::Service::Manager>().GetNextDnsSrpUnicastInfo(iterator, type, unicastInfo) == kErrorNone)
2484         {
2485             if (selectNext)
2486             {
2487                 serverSockAddr = unicastInfo.mSockAddr;
2488                 ExitNow();
2489             }
2490 
2491             if (GetServerAddress() == unicastInfo.mSockAddr)
2492             {
2493                 selectNext = true;
2494             }
2495         }
2496 
2497         // We loop back to handle the case where the current entry
2498         // is the last one.
2499 
2500     } while (selectNext);
2501 
2502     // If we reach here it indicates we could not find the entry
2503     // associated with currently selected server in the list. This
2504     // situation is rather unlikely but can still happen if Network
2505     // Data happens to be changed and the entry removed but
2506     // the "changed" event from `Notifier` may have not yet been
2507     // processed (note that events are emitted from their own
2508     // tasklet). In such a case we keep `serverSockAddr` as empty.
2509 
2510 exit:
2511     if (!serverSockAddr.GetAddress().IsUnspecified() && (GetServerAddress() != serverSockAddr))
2512     {
2513         // We specifically update `mHostInfo` to `kToAdd` state. This
2514         // ensures that `Stop()` will keep it as kToAdd` and we detect
2515         // that the host info has not been registered yet and allow the
2516         // `SelectNextServer()` to happen again if the timeouts/failures
2517         // continue to happen with the new server.
2518 
2519         mHostInfo.SetState(kToAdd);
2520         Stop(kRequesterAuto, kKeepRetryInterval);
2521         IgnoreError(Start(serverSockAddr, kRequesterAuto));
2522     }
2523 }
2524 #endif // OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE
2525 
2526 #endif // OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
2527 
ItemStateToString(ItemState aState)2528 const char *Client::ItemStateToString(ItemState aState)
2529 {
2530     static const char *const kItemStateStrings[] = {
2531         "ToAdd",      // kToAdd      (0)
2532         "Adding",     // kAdding     (1)
2533         "ToRefresh",  // kToRefresh  (2)
2534         "Refreshing", // kRefreshing (3)
2535         "ToRemove",   // kToRemove   (4)
2536         "Removing",   // kRemoving   (5)
2537         "Registered", // kRegistered (6)
2538         "Removed",    // kRemoved    (7)
2539     };
2540 
2541     return kItemStateStrings[aState];
2542 }
2543 
2544 #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
2545 
StateToString(State aState)2546 const char *Client::StateToString(State aState)
2547 {
2548     static const char *const kStateStrings[] = {
2549         "Stopped",  // kStateStopped  (0)
2550         "Paused",   // kStatePaused   (1)
2551         "ToUpdate", // kStateToUpdate (2)
2552         "Updating", // kStateUpdating (3)
2553         "Updated",  // kStateUpdated  (4)
2554         "ToRetry",  // kStateToRetry  (5)
2555     };
2556 
2557     static_assert(kStateStopped == 0, "kStateStopped value is not correct");
2558     static_assert(kStatePaused == 1, "kStatePaused value is not correct");
2559     static_assert(kStateToUpdate == 2, "kStateToUpdate value is not correct");
2560     static_assert(kStateUpdating == 3, "kStateUpdating value is not correct");
2561     static_assert(kStateUpdated == 4, "kStateUpdated value is not correct");
2562     static_assert(kStateToRetry == 5, "kStateToRetry value is not correct");
2563 
2564     return kStateStrings[aState];
2565 }
2566 
LogRetryWaitInterval(void) const2567 void Client::LogRetryWaitInterval(void) const
2568 {
2569     constexpr uint16_t kLogInMsecLimit = 5000; // Max interval (in msec) to log the value in msec unit
2570 
2571     uint32_t interval = GetRetryWaitInterval();
2572 
2573     LogInfo("Retry interval %lu %s", ToUlong((interval < kLogInMsecLimit) ? interval : Time::MsecToSec(interval)),
2574             (interval < kLogInMsecLimit) ? "ms" : "sec");
2575 }
2576 
2577 #endif // #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
2578 
2579 } // namespace Srp
2580 } // namespace ot
2581 
2582 #endif // OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
2583