Coverage Report

Created: 2025-05-12 06:47

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