[srp-client] update how auto-start mode prefers netdata entries (#7503)

This commit updates the auto-start behavior in `Srp::Client` which
monitors the Thread Network Data entries to discover and select SRP
sever to register with. There are three types of entries in Network
Data and are selected in the order below:

1) Preferred unicast entries with address in service data,
2) Anycast entries (each having a seq number,
3) Unicast entries with address info included in server data.

This commit also defines `AutoStart` class which includes all the
state and data variables related to auto-start feature. It also
updates `test_srp_auto_start_mode` test to cover the newly added
behaviors.
diff --git a/src/core/net/srp_client.cpp b/src/core/net/srp_client.cpp
index 7eeb2db..0667d30 100644
--- a/src/core/net/srp_client.cpp
+++ b/src/core/net/srp_client.cpp
@@ -35,12 +35,9 @@
 #include "common/debug.hpp"
 #include "common/instance.hpp"
 #include "common/locator_getters.hpp"
-
-#include "common/numeric_limits.hpp"
 #include "common/random.hpp"
 #include "common/settings.hpp"
 #include "common/string.hpp"
-#include "thread/network_data_service.hpp"
 
 /**
  * @file
@@ -156,6 +153,83 @@
 }
 
 //---------------------------------------------------------------------
+// Client::AutoStart
+
+#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
+
+Client::AutoStart::AutoStart(void)
+{
+    Clear();
+    mState = kDefaultMode ? kSelectedNone : kDisabled;
+}
+
+bool Client::AutoStart::HasSelectedServer(void) const
+{
+    bool hasSelected = false;
+
+    switch (mState)
+    {
+    case kDisabled:
+    case kSelectedNone:
+        break;
+
+    case kSelectedUnicastPreferred:
+    case kSelectedUnicast:
+    case kSelectedAnycast:
+        hasSelected = true;
+        break;
+    }
+
+    return hasSelected;
+}
+
+void Client::AutoStart::SetState(State aState)
+{
+    if (mState != aState)
+    {
+        LogInfo("AutoStartState %s -> %s", StateToString(mState), StateToString(aState));
+        mState = aState;
+    }
+}
+
+void Client::AutoStart::SetCallback(AutoStartCallback aCallback, void *aContext)
+{
+    mCallback = aCallback;
+    mContext  = aContext;
+}
+
+void Client::AutoStart::InvokeCallback(const Ip6::SockAddr *aServerSockAddr) const
+{
+    if (mCallback != nullptr)
+    {
+        mCallback(aServerSockAddr, mContext);
+    }
+}
+
+#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
+const char *Client::AutoStart::StateToString(State aState)
+{
+    static const char *const kStateStrings[] = {
+        "Disabled",    // (0) kDisabled
+        "Idle",        // (1) kSelectedNone
+        "Unicast-prf", // (2) kSelectedUnicastPreferred
+        "Anycast",     // (3) kSelectedAnycast
+        "Unicast",     // (4) kSelectedUnicast
+    };
+
+    static_assert(0 == kDisabled, "kDisabled value is incorrect");
+    static_assert(1 == kSelectedNone, "kSelectedNone value is incorrect");
+    static_assert(2 == kSelectedUnicastPreferred, "kSelectedUnicastPreferred value is incorrect");
+    static_assert(3 == kSelectedAnycast, "kSelectedAnycast value is incorrect");
+    static_assert(4 == kSelectedUnicast, "kSelectedUnicast value is incorrect");
+
+    return kStateStrings[aState];
+}
+#endif
+
+#endif // OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
+
+//---------------------------------------------------------------------
 // Client
 
 const char Client::kDefaultDomainName[] = "default.service.arpa";
@@ -165,11 +239,6 @@
     , mState(kStateStopped)
     , mTxFailureRetryCount(0)
     , mShouldRemoveKeyLease(false)
-#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
-    , mAutoStartModeEnabled(kAutoStartDefaultMode)
-    , mAutoStartDidSelectServer(false)
-    , mAutoStartIsUsingAnycastAddress(false)
-#endif
 #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
     , mServiceKeyRecordEnabled(false)
 #endif
@@ -181,14 +250,6 @@
     , mSocket(aInstance)
     , mCallback(nullptr)
     , mCallbackContext(nullptr)
-#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
-    , mAutoStartCallback(nullptr)
-    , mAutoStartContext(nullptr)
-    , mServerSequenceNumber(0)
-#if OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE
-    , mTimoutFailureCount(0)
-#endif
-#endif
     , mDomainName(kDefaultDomainName)
     , mTimer(aInstance, Client::HandleTimer)
 {
@@ -226,18 +287,12 @@
     Resume();
 
 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
-    mAutoStartDidSelectServer = (aRequester == kRequesterAuto);
-
-    if (mAutoStartDidSelectServer)
+    if (aRequester == kRequesterAuto)
     {
 #if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE && OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_SERVER_ADDRESS_AUTO_SET_ENABLE
         Get<Dns::Client>().UpdateDefaultConfigAddress();
 #endif
-
-        if (mAutoStartCallback != nullptr)
-        {
-            mAutoStartCallback(&aServerSockAddr, mAutoStartContext);
-        }
+        mAutoStart.InvokeCallback(&aServerSockAddr);
     }
 #endif
 
@@ -290,14 +345,11 @@
 
 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
 #if OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE
-    mTimoutFailureCount = 0;
+    mAutoStart.ResetTimoutFailureCount();
 #endif
-
-    mAutoStartDidSelectServer = false;
-
-    if ((aRequester == kRequesterAuto) && (mAutoStartCallback != nullptr))
+    if (aRequester == kRequesterAuto)
     {
-        mAutoStartCallback(nullptr, mAutoStartContext);
+        mAutoStart.InvokeCallback(nullptr);
     }
 #endif
 
@@ -611,24 +663,29 @@
     }
 
 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE && OPENTHREAD_CONFIG_SRP_CLIENT_SAVE_SELECTED_SERVER_ENABLE
-    if (mAutoStartModeEnabled && mAutoStartDidSelectServer && (oldHostState != kRegistered) &&
-        (mHostInfo.GetState() == kRegistered))
+    if ((oldHostState != kRegistered) && (mHostInfo.GetState() == kRegistered))
     {
-        if (mAutoStartIsUsingAnycastAddress)
-        {
-            IgnoreError(Get<Settings>().Delete<Settings::SrpClientInfo>());
-        }
-        else
-        {
-            Settings::SrpClientInfo info;
+        Settings::SrpClientInfo info;
 
+        switch (mAutoStart.GetState())
+        {
+        case AutoStart::kDisabled:
+        case AutoStart::kSelectedNone:
+            break;
+
+        case AutoStart::kSelectedUnicastPreferred:
+        case AutoStart::kSelectedUnicast:
             info.SetServerAddress(GetServerAddress().GetAddress());
             info.SetServerPort(GetServerAddress().GetPort());
-
             IgnoreError(Get<Settings>().Save(info));
+            break;
+
+        case AutoStart::kSelectedAnycast:
+            IgnoreError(Get<Settings>().Delete<Settings::SrpClientInfo>());
+            break;
         }
     }
-#endif
+#endif // OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE && OPENTHREAD_CONFIG_SRP_CLIENT_SAVE_SELECTED_SERVER_ENABLE
 }
 
 void Client::InvokeCallback(Error aError) const
@@ -1211,7 +1268,7 @@
     LogInfo("Received response");
 
 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE && OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE
-    mTimoutFailureCount = 0;
+    mAutoStart.ResetTimoutFailureCount();
 #endif
 
     error = Dns::Header::ResponseCodeToError(header.GetResponseCode());
@@ -1609,12 +1666,9 @@
         // callback. It works correctly due to the guard check at the
         // top of `SelectNextServer()`.
 
-        if (mTimoutFailureCount < NumericLimits<uint8_t>::kMax)
-        {
-            mTimoutFailureCount++;
-        }
+        mAutoStart.IncrementTimoutFailureCount();
 
-        if (mTimoutFailureCount >= kMaxTimeoutFailuresToSwitchServer)
+        if (mAutoStart.GetTimoutFailureCount() >= kMaxTimeoutFailuresToSwitchServer)
         {
             SelectNextServer(kDisallowSwitchOnRegisteredHost);
         }
@@ -1631,11 +1685,11 @@
 
 void Client::EnableAutoStartMode(AutoStartCallback aCallback, void *aContext)
 {
-    mAutoStartCallback = aCallback;
-    mAutoStartContext  = aContext;
+    mAutoStart.SetCallback(aCallback, aContext);
 
-    VerifyOrExit(!mAutoStartModeEnabled);
-    mAutoStartModeEnabled = true;
+    VerifyOrExit(mAutoStart.GetState() == AutoStart::kDisabled);
+
+    mAutoStart.SetState(AutoStart::kSelectedNone);
     ProcessAutoStart();
 
 exit:
@@ -1644,130 +1698,181 @@
 
 void Client::ProcessAutoStart(void)
 {
-    Ip6::SockAddr                             serverSockAddr;
-    bool                                      serverIsAnycast = false;
-    NetworkData::Service::DnsSrpAnycast::Info anycastInfo;
-#if OPENTHREAD_CONFIG_SRP_CLIENT_SAVE_SELECTED_SERVER_ENABLE
-    Settings::SrpClientInfo savedInfo;
-    bool                    hasSavedServerInfo = false;
-#endif
+    Ip6::SockAddr       serverSockAddr;
+    DnsSrpAnycast::Info anycastInfo;
+    DnsSrpUnicast::Info unicastInfo;
+    bool                shouldRestart = false;
 
-    VerifyOrExit(mAutoStartModeEnabled);
+    // If auto start mode is enabled, we check the Network Data entries
+    // to discover and select the preferred SRP server to register with.
+    // If we currently have a selected server, we ensure that it is
+    // still present in the Network Data and is still the preferred one.
+
+    VerifyOrExit(mAutoStart.GetState() != AutoStart::kDisabled);
+
+    // If SRP client is running, we check to make sure that auto-start
+    // did select the current server, and server was not specified by
+    // user directly.
+
+    if (IsRunning())
+    {
+        VerifyOrExit(mAutoStart.GetState() != AutoStart::kSelectedNone);
+    }
+
+    // There are three types of entries in Network Data:
+    //
+    // 1) Preferred unicast entries with address included in service data.
+    // 2) Anycast entries (each having a seq number).
+    // 3) Unicast entries with address info included in server data.
 
     serverSockAddr.Clear();
 
-    // If the SRP client is not running and auto start mode is
-    // enabled, we check if we can find any SRP server info in the
-    // Thread Network Data. If it is already running and the server
-    // was chosen by the auto-start feature, then we ensure that the
-    // selected server is still present in the Network Data.
-    //
-    // Two types of "DNS/SRP Service" entries can be present in
-    // Network Data, "DNS/SRP Service Anycast Address" model and
-    // "DNS/SRP Service Unicast" model. The Anycast entries are
-    // preferred over the Unicast entries.
+    if (SelectUnicastEntry(DnsSrpUnicast::kFromServiceData, unicastInfo) == kErrorNone)
+    {
+        mAutoStart.SetState(AutoStart::kSelectedUnicastPreferred);
+        serverSockAddr = unicastInfo.mSockAddr;
+    }
+    else if (Get<NetworkData::Service::Manager>().FindPreferredDnsSrpAnycastInfo(anycastInfo) == kErrorNone)
+    {
+        serverSockAddr.SetAddress(anycastInfo.mAnycastAddress);
+        serverSockAddr.SetPort(kAnycastServerPort);
 
-    VerifyOrExit(!IsRunning() || mAutoStartDidSelectServer);
+        // We check if we are selecting an anycast entry for first
+        // time, or if the seq number has changed. Even if the
+        // anycast address remains the same as before, on a seq
+        // number change, the client still needs to restart to
+        // re-register its info.
 
-    // Now `IsRunning()` implies `mAutoStartDidSelectServer`.
+        if ((mAutoStart.GetState() != AutoStart::kSelectedAnycast) ||
+            (mAutoStart.GetAnycastSeqNum() != anycastInfo.mSequenceNumber))
+        {
+            shouldRestart = true;
+            mAutoStart.SetAnycastSeqNum(anycastInfo.mSequenceNumber);
+        }
 
+        mAutoStart.SetState(AutoStart::kSelectedAnycast);
+    }
+    else if (SelectUnicastEntry(DnsSrpUnicast::kFromServerData, unicastInfo) == kErrorNone)
+    {
+        mAutoStart.SetState(AutoStart::kSelectedUnicast);
+        serverSockAddr = unicastInfo.mSockAddr;
+    }
+
+    if (IsRunning())
+    {
+        VerifyOrExit((GetServerAddress() != serverSockAddr) || shouldRestart);
+        Stop(kRequesterAuto, kResetRetryInterval);
+    }
+
+    if (!serverSockAddr.GetAddress().IsUnspecified())
+    {
+        IgnoreError(Start(serverSockAddr, kRequesterAuto));
+    }
+    else
+    {
+        mAutoStart.SetState(AutoStart::kSelectedNone);
+    }
+
+exit:
+    return;
+}
+
+Error Client::SelectUnicastEntry(DnsSrpUnicast::Origin aOrigin, DnsSrpUnicast::Info &aInfo) const
+{
+    Error                                   error = kErrorNotFound;
+    DnsSrpUnicast::Info                     unicastInfo;
+    NetworkData::Service::Manager::Iterator iterator;
+    uint16_t                                numServers = 0;
 #if OPENTHREAD_CONFIG_SRP_CLIENT_SAVE_SELECTED_SERVER_ENABLE
+    Settings::SrpClientInfo savedInfo;
+    bool                    hasSavedServerInfo = false;
+
     if (!IsRunning())
     {
         hasSavedServerInfo = (Get<Settings>().Read(savedInfo) == kErrorNone);
     }
 #endif
 
-    if (Get<NetworkData::Service::Manager>().FindPreferredDnsSrpAnycastInfo(anycastInfo) == kErrorNone)
+    while (Get<NetworkData::Service::Manager>().GetNextDnsSrpUnicastInfo(iterator, unicastInfo) == kErrorNone)
     {
-        if (IsRunning() && mAutoStartIsUsingAnycastAddress && (mServerSequenceNumber == anycastInfo.mSequenceNumber) &&
-            (GetServerAddress().GetAddress() == anycastInfo.mAnycastAddress))
+        if (unicastInfo.mOrigin != aOrigin)
         {
-            // Client is already using the same anycast address.
+            continue;
+        }
+
+        if (mAutoStart.HasSelectedServer() && (GetServerAddress() == unicastInfo.mSockAddr))
+        {
+            aInfo = unicastInfo;
+            error = kErrorNone;
             ExitNow();
         }
 
-        LogInfo("Found anycast server %d", anycastInfo.mSequenceNumber);
-
-        serverSockAddr.SetAddress(anycastInfo.mAnycastAddress);
-        serverSockAddr.SetPort(kAnycastServerPort);
-        mServerSequenceNumber = anycastInfo.mSequenceNumber;
-        serverIsAnycast       = true;
-    }
-    else
-    {
-        uint16_t                                  numServers = 0;
-        NetworkData::Service::DnsSrpUnicast::Info unicastInfo;
-        NetworkData::Service::Manager::Iterator   iterator;
-
-        while (Get<NetworkData::Service::Manager>().GetNextDnsSrpUnicastInfo(iterator, unicastInfo) == kErrorNone)
-        {
-            if (IsRunning() && !mAutoStartIsUsingAnycastAddress && (GetServerAddress() == unicastInfo.mSockAddr))
-            {
-                ExitNow();
-            }
-
 #if OPENTHREAD_CONFIG_SRP_CLIENT_SAVE_SELECTED_SERVER_ENABLE
-            if (hasSavedServerInfo && (unicastInfo.mSockAddr.GetAddress() == savedInfo.GetServerAddress()) &&
-                (unicastInfo.mSockAddr.GetPort() == savedInfo.GetServerPort()))
-            {
-                // Stop the search if we see a match for the previously
-                // saved server info in the network data entries.
+        if (hasSavedServerInfo && (unicastInfo.mSockAddr.GetAddress() == savedInfo.GetServerAddress()) &&
+            (unicastInfo.mSockAddr.GetPort() == savedInfo.GetServerPort()))
+        {
+            // Stop the search if we see a match for the previously
+            // saved server info in the network data entries.
 
-                serverSockAddr  = unicastInfo.mSockAddr;
-                serverIsAnycast = false;
-                break;
-            }
+            aInfo = unicastInfo;
+            error = kErrorNone;
+            ExitNow();
+        }
 #endif
+        numServers++;
 
-            numServers++;
+        // Choose a server randomly (with uniform distribution) from
+        // the list of servers. As we iterate through server entries,
+        // with probability `1/numServers`, we choose to switch the
+        // current selected server with the new entry. This approach
+        // results in a uniform/same probability of selection among
+        // all server entries.
 
-            // Choose a server randomly (with uniform distribution) from
-            // the list of servers. As we iterate through server entries,
-            // with probability `1/numServers`, we choose to switch the
-            // current selected server with the new entry. This approach
-            // results in a uniform/same probability of selection among
-            // all server entries.
-
-            if ((numServers == 1) || (Random::NonCrypto::GetUint16InRange(0, numServers) == 0))
-            {
-                serverSockAddr  = unicastInfo.mSockAddr;
-                serverIsAnycast = false;
-            }
+        if ((numServers == 1) || (Random::NonCrypto::GetUint16InRange(0, numServers) == 0))
+        {
+            aInfo = unicastInfo;
+            error = kErrorNone;
         }
     }
 
-    if (IsRunning())
-    {
-        Stop(kRequesterAuto, kResetRetryInterval);
-    }
-
-    VerifyOrExit(!serverSockAddr.GetAddress().IsUnspecified());
-
-    mAutoStartIsUsingAnycastAddress = serverIsAnycast;
-    IgnoreError(Start(serverSockAddr, kRequesterAuto));
-
 exit:
-    return;
+    return error;
 }
 
 #if OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE
 void Client::SelectNextServer(bool aDisallowSwitchOnRegisteredHost)
 {
-    // This method tries to find the next server info entry in the
+    // This method tries to find the next unicast server info entry in the
     // Network Data after the current one selected. If found, it
     // restarts the client with the new server (keeping the retry wait
     // interval as before).
 
-    Ip6::SockAddr serverSockAddr;
-    bool          selectNext = false;
+    Ip6::SockAddr         serverSockAddr;
+    bool                  selectNext = false;
+    DnsSrpUnicast::Origin origin     = DnsSrpUnicast::kFromServiceData;
 
     serverSockAddr.Clear();
 
     // Ensure that client is running, auto-start is enabled and
-    // auto-start selected the server.
+    // auto-start selected the server and it is a unicast entry.
 
-    VerifyOrExit(IsRunning() && mAutoStartModeEnabled && mAutoStartDidSelectServer);
+    VerifyOrExit(IsRunning());
+
+    switch (mAutoStart.GetState())
+    {
+    case AutoStart::kSelectedUnicastPreferred:
+        origin = DnsSrpUnicast::kFromServiceData;
+        break;
+
+    case AutoStart::kSelectedUnicast:
+        origin = DnsSrpUnicast::kFromServerData;
+        break;
+
+    case AutoStart::kSelectedAnycast:
+    case AutoStart::kDisabled:
+    case AutoStart::kSelectedNone:
+        ExitNow();
+    }
 
     if (aDisallowSwitchOnRegisteredHost)
     {
@@ -1782,36 +1887,19 @@
 
     do
     {
-        NetworkData::Service::DnsSrpAnycast::Info anycastInfo;
-        NetworkData::Service::DnsSrpUnicast::Info unicastInfo;
-        NetworkData::Service::Manager::Iterator   iterator;
-
-        while (Get<NetworkData::Service::Manager>().GetNextDnsSrpAnycastInfo(iterator, anycastInfo) == kErrorNone)
-        {
-            if (selectNext)
-            {
-                serverSockAddr.SetAddress(anycastInfo.mAnycastAddress);
-                serverSockAddr.SetPort(kAnycastServerPort);
-                mServerSequenceNumber           = anycastInfo.mSequenceNumber;
-                mAutoStartIsUsingAnycastAddress = true;
-                ExitNow();
-            }
-
-            if (mAutoStartIsUsingAnycastAddress && (GetServerAddress().GetAddress() == anycastInfo.mAnycastAddress) &&
-                (GetServerAddress().GetPort() == kAnycastServerPort))
-            {
-                selectNext = true;
-            }
-        }
-
-        iterator.Reset();
+        DnsSrpUnicast::Info                     unicastInfo;
+        NetworkData::Service::Manager::Iterator iterator;
 
         while (Get<NetworkData::Service::Manager>().GetNextDnsSrpUnicastInfo(iterator, unicastInfo) == kErrorNone)
         {
+            if (unicastInfo.mOrigin != origin)
+            {
+                continue;
+            }
+
             if (selectNext)
             {
-                serverSockAddr                  = unicastInfo.mSockAddr;
-                mAutoStartIsUsingAnycastAddress = false;
+                serverSockAddr = unicastInfo.mSockAddr;
                 ExitNow();
             }
 
diff --git a/src/core/net/srp_client.hpp b/src/core/net/srp_client.hpp
index ebec59d..80b381f 100644
--- a/src/core/net/srp_client.hpp
+++ b/src/core/net/srp_client.hpp
@@ -43,11 +43,13 @@
 #include "common/message.hpp"
 #include "common/non_copyable.hpp"
 #include "common/notifier.hpp"
+#include "common/numeric_limits.hpp"
 #include "common/timer.hpp"
 #include "crypto/ecdsa.hpp"
 #include "net/dns_types.hpp"
 #include "net/ip6.hpp"
 #include "net/udp6.hpp"
+#include "thread/network_data_service.hpp"
 
 /**
  * @file
@@ -69,6 +71,9 @@
 {
     friend class ot::Notifier;
 
+    using DnsSrpUnicast = NetworkData::Service::DnsSrpUnicast;
+    using DnsSrpAnycast = NetworkData::Service::DnsSrpAnycast;
+
 public:
     /**
      * This enumeration types represents an SRP client item (service or host info) state.
@@ -355,7 +360,7 @@
      * Note that a call to `Stop()` will also disable the auto-start mode.
      *
      */
-    void DisableAutoStartMode(void) { mAutoStartModeEnabled = false; }
+    void DisableAutoStartMode(void) { mAutoStart.SetState(AutoStart::kDisabled); }
 
     /**
      * This method indicates the current state of auto-start mode (enabled or disabled).
@@ -363,7 +368,7 @@
      * @returns TRUE if the auto-start mode is enabled, FALSE otherwise.
      *
      */
-    bool IsAutoStartModeEnabled(void) const { return mAutoStartModeEnabled; }
+    bool IsAutoStartModeEnabled(void) const { return mAutoStart.GetState() != AutoStart::kDisabled; }
 
     /**
      * This method indicates whether or not the current SRP server's address is selected by auto-start.
@@ -371,7 +376,7 @@
      * @returns TRUE if the SRP server's address is selected by auto-start, FALSE otherwise.
      *
      */
-    bool IsServerSelectedByAutoStart(void) const { return mAutoStartDidSelectServer; }
+    bool IsServerSelectedByAutoStart(void) const { return mAutoStart.HasSelectedServer(); }
 #endif // OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
 
     /**
@@ -790,6 +795,54 @@
         kKeepRetryInterval,
     };
 
+#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
+    class AutoStart : Clearable<AutoStart>
+    {
+    public:
+        enum State : uint8_t{
+            kDisabled,                 // AutoStart is disabled.
+            kSelectedNone,             // AutoStart is enabled but not yet selected any servers.
+            kSelectedUnicastPreferred, // AutoStart selected a preferred unicast entry (address in service data).
+            kSelectedAnycast,          // AutoStart selected an anycast entry with `mAnycastSeqNum`.
+            kSelectedUnicast,          // AutoStart selected a unicast entry (address in server data).
+        };
+
+        AutoStart(void);
+        bool    HasSelectedServer(void) const;
+        State   GetState(void) const { return mState; }
+        void    SetState(State aState);
+        uint8_t GetAnycastSeqNum(void) const { return mAnycastSeqNum; }
+        void    SetAnycastSeqNum(uint8_t aAnycastSeqNum) { mAnycastSeqNum = aAnycastSeqNum; }
+        void    SetCallback(AutoStartCallback aCallback, void *aContext);
+        void    InvokeCallback(const Ip6::SockAddr *aServerSockAddr) const;
+
+#if OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE
+        uint8_t GetTimoutFailureCount(void) const { return mTimoutFailureCount; }
+        void    ResetTimoutFailureCount(void) { mTimoutFailureCount = 0; }
+        void    IncrementTimoutFailureCount(void)
+        {
+            if (mTimoutFailureCount < NumericLimits<uint8_t>::kMax)
+            {
+                mTimoutFailureCount++;
+            }
+        }
+#endif
+
+    private:
+        static constexpr bool kDefaultMode = OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_DEFAULT_MODE;
+
+        static const char *StateToString(State aState);
+
+        AutoStartCallback mCallback;
+        void *            mContext;
+        State             mState;
+        uint8_t           mAnycastSeqNum;
+#if OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE
+        uint8_t mTimoutFailureCount; // Number of no-response timeout failures with the currently selected server.
+#endif
+    };
+#endif // OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
+
     struct Info : public Clearable<Info>
     {
         static constexpr uint16_t kUnknownOffset = 0; // Unknown offset value (used when offset is not yet set).
@@ -839,7 +892,8 @@
     static void  HandleTimer(Timer &aTimer);
     void         HandleTimer(void);
 #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
-    void ProcessAutoStart(void);
+    void  ProcessAutoStart(void);
+    Error SelectUnicastEntry(DnsSrpUnicast::Origin aOrigin, DnsSrpUnicast::Info &aInfo) const;
 #if OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE
     void SelectNextServer(bool aDisallowSwitchOnRegisteredHost);
 #endif
@@ -859,11 +913,6 @@
     State   mState;
     uint8_t mTxFailureRetryCount : 4;
     bool    mShouldRemoveKeyLease : 1;
-#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
-    bool mAutoStartModeEnabled : 1;
-    bool mAutoStartDidSelectServer : 1;
-    bool mAutoStartIsUsingAnycastAddress : 1;
-#endif
 #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
     bool mServiceKeyRecordEnabled : 1;
 #endif
@@ -878,22 +927,15 @@
 
     Ip6::Udp::Socket mSocket;
 
-    Callback mCallback;
-    void *   mCallbackContext;
-
-#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
-    AutoStartCallback mAutoStartCallback;
-    void *            mAutoStartContext;
-    uint8_t           mServerSequenceNumber;
-#if OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE
-    uint8_t mTimoutFailureCount;
-#endif
-#endif
-
+    Callback            mCallback;
+    void *              mCallbackContext;
     const char *        mDomainName;
     HostInfo            mHostInfo;
     LinkedList<Service> mServices;
     TimerMilli          mTimer;
+#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
+    AutoStart mAutoStart;
+#endif
 };
 
 } // namespace Srp
diff --git a/tests/scripts/thread-cert/test_srp_auto_start_mode.py b/tests/scripts/thread-cert/test_srp_auto_start_mode.py
index 2b18264..9fc4c09 100755
--- a/tests/scripts/thread-cert/test_srp_auto_start_mode.py
+++ b/tests/scripts/thread-cert/test_srp_auto_start_mode.py
@@ -31,23 +31,22 @@
 import unittest
 
 import command
+import config
 import thread_cert
 
 # Test description:
 #   This test verifies SRP client auto-start functionality that SRP client can
-#   correctly discover and connect to SRP server.
+#   correctly discovers and connects to SRP server.
 #
 # Topology:
 #
-#   CLIENT (leader) -- SERVER1 (router)
-#      |
-#      |
-#   SERVER2 (router)
+#   Four routers, one acting as SRP client, others as SRP server.
 #
 
 CLIENT = 1
 SERVER1 = 2
 SERVER2 = 3
+SERVER3 = 4
 
 
 class SrpAutoStartMode(thread_cert.TestCase):
@@ -57,17 +56,18 @@
     TOPOLOGY = {
         CLIENT: {
             'name': 'SRP_CLIENT',
-            'networkkey': '00112233445566778899aabbccddeeff',
             'mode': 'rdn',
         },
         SERVER1: {
             'name': 'SRP_SERVER1',
-            'networkkey': '00112233445566778899aabbccddeeff',
             'mode': 'rdn',
         },
         SERVER2: {
             'name': 'SRP_SERVER2',
-            'networkkey': '00112233445566778899aabbccddeeff',
+            'mode': 'rdn',
+        },
+        SERVER3: {
+            'name': 'SRP_SERVER3',
             'mode': 'rdn',
         },
     }
@@ -76,27 +76,38 @@
         client = self.nodes[CLIENT]
         server1 = self.nodes[SERVER1]
         server2 = self.nodes[SERVER2]
+        server3 = self.nodes[SERVER3]
 
-        #
-        # 0. Start the server & client devices.
-        #
+        #-------------------------------------------------------------------
+        # Form the network.
 
         client.srp_server_set_enabled(False)
         client.start()
         self.simulator.go(5)
         self.assertEqual(client.get_state(), 'leader')
 
-        server1.srp_server_set_enabled(True)
-        server2.srp_server_set_enabled(False)
         server1.start()
         server2.start()
+        server3.start()
         self.simulator.go(5)
         self.assertEqual(server1.get_state(), 'router')
         self.assertEqual(server2.get_state(), 'router')
+        self.assertEqual(server3.get_state(), 'router')
 
-        #
-        # 1. Enable auto start mode on client and check that server1 is used.
-        #
+        server1_mleid = server1.get_mleid()
+        server2_mleid = server2.get_mleid()
+        server3_mleid = server3.get_mleid()
+        anycast_port = 53
+
+        #-------------------------------------------------------------------
+        # Enable server1 with unicast address mode
+
+        server1.srp_server_set_addr_mode('unicast')
+        server1.srp_server_set_enabled(True)
+        self.simulator.go(5)
+
+        #-------------------------------------------------------------------
+        # Enable auto start mode on client and check that server1 is selected
 
         self.assertEqual(client.srp_client_get_state(), 'Disabled')
         client.srp_client_enable_auto_start_mode()
@@ -104,42 +115,174 @@
         self.simulator.go(2)
 
         self.assertEqual(client.srp_client_get_state(), 'Enabled')
-        self.assertTrue(server1.has_ipaddr(client.srp_client_get_server_address()))
+        self.assertEqual(client.srp_client_get_server_address(), server1_mleid)
 
-        #
-        # 2. Disable server1 and check client is stopped/disabled.
-        #
+        #-------------------------------------------------------------------
+        # Disable server1 and check client is stopped/disabled.
 
         server1.srp_server_set_enabled(False)
         self.simulator.go(5)
         self.assertEqual(client.srp_client_get_state(), 'Disabled')
 
-        #
-        # 3. Enable server2 and check client starts again.
-        #
+        #-------------------------------------------------------------------
+        # Enable server2 with unicast address mode and check client starts
+        # again.
 
+        server1.srp_server_set_addr_mode('unicast')
         server2.srp_server_set_enabled(True)
         self.simulator.go(5)
         self.assertEqual(client.srp_client_get_state(), 'Enabled')
-        server2_address = client.srp_client_get_server_address()
+        self.assertEqual(client.srp_client_get_server_address(), server2_mleid)
 
-        #
-        # 4. Enable both servers and check client stays with server2.
-        #
+        #-------------------------------------------------------------------
+        # Enable server1 and check that client stays with server2
 
         server1.srp_server_set_enabled(True)
         self.simulator.go(5)
         self.assertEqual(client.srp_client_get_state(), 'Enabled')
-        self.assertEqual(client.srp_client_get_server_address(), server2_address)
+        self.assertEqual(client.srp_client_get_server_address(), server2_mleid)
 
-        #
-        # 5. Disable server2 and check client switches to server1.
-        #
+        #-------------------------------------------------------------------
+        # Disable server2 and check client switches to server1.
 
         server2.srp_server_set_enabled(False)
         self.simulator.go(5)
         self.assertEqual(client.srp_client_get_state(), 'Enabled')
-        self.assertNotEqual(client.srp_client_get_server_address(), server2_address)
+        self.assertEqual(client.srp_client_get_server_address(), server1_mleid)
+
+        #-------------------------------------------------------------------
+        # Enable server2 with anycast mode seq-num 1, and check that client
+        # switched to it.
+
+        server2.srp_server_set_addr_mode('anycast')
+        server2.srp_server_set_anycast_seq_num(1)
+        server2.srp_server_set_enabled(True)
+        self.simulator.go(5)
+        server2_alocs = server2.get_ip6_address(config.ADDRESS_TYPE.ALOC)
+        self.assertEqual(server2.srp_server_get_anycast_seq_num(), 1)
+        self.assertEqual(client.srp_client_get_state(), 'Enabled')
+        self.assertIn(client.srp_client_get_server_address(), server2_alocs)
+        self.assertEqual(client.srp_client_get_server_port(), anycast_port)
+
+        #-------------------------------------------------------------------
+        # Enable server3 with anycast mode seq-num 2, and check that client
+        # switched to it since seq number is higher.
+
+        server3.srp_server_set_addr_mode('anycast')
+        server3.srp_server_set_anycast_seq_num(2)
+        server3.srp_server_set_enabled(True)
+        self.simulator.go(5)
+        server3_alocs = server3.get_ip6_address(config.ADDRESS_TYPE.ALOC)
+        self.assertEqual(server3.srp_server_get_anycast_seq_num(), 2)
+        self.assertEqual(client.srp_client_get_state(), 'Enabled')
+        self.assertIn(client.srp_client_get_server_address(), server3_alocs)
+        self.assertEqual(client.srp_client_get_server_port(), anycast_port)
+
+        #-------------------------------------------------------------------
+        # Disable server3 and check that client goes back to server2.
+
+        server3.srp_server_set_enabled(False)
+        self.simulator.go(5)
+        self.assertEqual(client.srp_client_get_state(), 'Enabled')
+        self.assertIn(client.srp_client_get_server_address(), server2_alocs)
+        self.assertEqual(client.srp_client_get_server_port(), anycast_port)
+
+        #-------------------------------------------------------------------
+        # Enable server3 with anycast mode seq-num 0 (which is smaller than
+        # server2 seq-num 1) and check that client stays with server2.
+
+        server3.srp_server_set_anycast_seq_num(0)
+        server3.srp_server_set_enabled(True)
+        self.simulator.go(5)
+        self.assertEqual(server3.srp_server_get_anycast_seq_num(), 0)
+        self.assertEqual(client.srp_client_get_state(), 'Enabled')
+        self.assertIn(client.srp_client_get_server_address(), server2_alocs)
+        self.assertEqual(client.srp_client_get_server_port(), anycast_port)
+
+        #-------------------------------------------------------------------
+        # Disable server2 and check that client goes back to server3.
+
+        server2.srp_server_set_enabled(False)
+        self.simulator.go(5)
+        self.assertEqual(client.srp_client_get_state(), 'Enabled')
+        server3_alocs = server3.get_ip6_address(config.ADDRESS_TYPE.ALOC)
+        self.assertIn(client.srp_client_get_server_address(), server3_alocs)
+        self.assertEqual(client.srp_client_get_server_port(), anycast_port)
+
+        #-------------------------------------------------------------------
+        # Disable server3 and check that client goes back to server1 with
+        # unicast address.
+
+        server3.srp_server_set_enabled(False)
+        self.simulator.go(5)
+        self.assertEqual(client.srp_client_get_state(), 'Enabled')
+        self.assertEqual(client.srp_client_get_server_address(), server1_mleid)
+
+        #-------------------------------------------------------------------
+        # Enable server2 with anycast mode seq-num 5, and check that client
+        # switched to it.
+
+        server2.srp_server_set_addr_mode('anycast')
+        server2.srp_server_set_anycast_seq_num(5)
+        server2.srp_server_set_enabled(True)
+        self.simulator.go(5)
+        server2_alocs = server2.get_ip6_address(config.ADDRESS_TYPE.ALOC)
+        self.assertEqual(server2.srp_server_get_anycast_seq_num(), 5)
+        self.assertEqual(client.srp_client_get_state(), 'Enabled')
+        self.assertIn(client.srp_client_get_server_address(), server2_alocs)
+        self.assertEqual(client.srp_client_get_server_port(), anycast_port)
+
+        #-------------------------------------------------------------------
+        # Publish an entry on server3 with specific unicast address
+        # This entry should be now preferred over anycast of server2.
+
+        unicast_addr3 = 'fd00:0:0:0:0:3333:beef:cafe'
+        unicast_port3 = 1234
+        server3.netdata_publish_dnssrp_unicast(unicast_addr3, unicast_port3)
+        self.simulator.go(65)
+        self.assertEqual(client.srp_client_get_state(), 'Enabled')
+        self.assertEqual(client.srp_client_get_server_address(), unicast_addr3)
+        self.assertEqual(client.srp_client_get_server_port(), unicast_port3)
+
+        #-------------------------------------------------------------------
+        # Publish an entry on server1 with specific unicast address
+        # Client should still stay with server3 which it originally selected.
+
+        unicast_addr1 = 'fd00:0:0:0:0:2222:beef:cafe'
+        unicast_port1 = 10203
+        server1.srp_server_set_enabled(False)
+        server1.netdata_publish_dnssrp_unicast(unicast_addr1, unicast_port1)
+        self.simulator.go(65)
+        self.assertEqual(client.srp_client_get_state(), 'Enabled')
+        self.assertEqual(client.srp_client_get_server_address(), unicast_addr3)
+        self.assertEqual(client.srp_client_get_server_port(), unicast_port3)
+
+        #-------------------------------------------------------------------
+        # Unpublish the entry on server3. Now client should switch to entry
+        # from server1.
+
+        server3.netdata_unpublish_dnssrp()
+        self.simulator.go(65)
+        self.assertEqual(client.srp_client_get_state(), 'Enabled')
+        self.assertEqual(client.srp_client_get_server_address(), unicast_addr1)
+        self.assertEqual(client.srp_client_get_server_port(), unicast_port1)
+
+        #-------------------------------------------------------------------
+        # Unpublish the entry on server1 and check client goes back to anycast
+        # entry from server2.
+
+        server1.netdata_unpublish_dnssrp()
+        self.simulator.go(65)
+        self.assertEqual(client.srp_client_get_state(), 'Enabled')
+        self.assertIn(client.srp_client_get_server_address(), server2_alocs)
+        self.assertEqual(client.srp_client_get_server_port(), anycast_port)
+
+        #-------------------------------------------------------------------
+        # Finally disable server2, and check that client is disabled.
+
+        server2.srp_server_set_enabled(False)
+        self.simulator.go(5)
+        self.assertEqual(client.srp_client_get_state(), 'Disabled')
 
 
 if __name__ == '__main__':