/*
 *  Copyright (c) 2023, The OpenThread Authors.
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *  3. Neither the name of the copyright holder nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 */

#include <openthread/config.h>

#include "test_platform.h"
#include "test_util.hpp"

#include <openthread/dataset_ftd.h>
#include <openthread/srp_client.h>
#include <openthread/srp_server.h>
#include <openthread/thread.h>

#include "common/arg_macros.hpp"
#include "common/array.hpp"
#include "common/string.hpp"
#include "common/time.hpp"
#include "instance/instance.hpp"

#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE && OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE &&                   \
    OPENTHREAD_CONFIG_SRP_SERVER_ADVERTISING_PROXY_ENABLE && !OPENTHREAD_CONFIG_TIME_SYNC_ENABLE && \
    !OPENTHREAD_PLATFORM_POSIX && OPENTHREAD_CONFIG_PLATFORM_DNSSD_ALLOW_RUN_TIME_SELECTION
#define ENABLE_ADV_PROXY_TEST 1
#else
#define ENABLE_ADV_PROXY_TEST 0
#endif

#if ENABLE_ADV_PROXY_TEST

using namespace ot;

// Logs a message and adds current time (sNow) as "<hours>:<min>:<secs>.<msec>"
#define Log(...)                                                                                          \
    printf("%02u:%02u:%02u.%03u " OT_FIRST_ARG(__VA_ARGS__) "\n", (sNow / 36000000), (sNow / 60000) % 60, \
           (sNow / 1000) % 60, sNow % 1000 OT_REST_ARGS(__VA_ARGS__))

static constexpr uint16_t kMaxRaSize = 800;

static ot::Instance *sInstance;

static uint32_t sNow = 0;
static uint32_t sAlarmTime;
static bool     sAlarmOn = false;

static otRadioFrame sRadioTxFrame;
static uint8_t      sRadioTxFramePsdu[OT_RADIO_FRAME_MAX_SIZE];
static bool         sRadioTxOngoing = false;

//----------------------------------------------------------------------------------------------------------------------
// Function prototypes

void ProcessRadioTxAndTasklets(void);
void AdvanceTime(uint32_t aDuration);

//----------------------------------------------------------------------------------------------------------------------
// `otPlatRadio`

extern "C" {

otRadioCaps otPlatRadioGetCaps(otInstance *) { return OT_RADIO_CAPS_ACK_TIMEOUT | OT_RADIO_CAPS_CSMA_BACKOFF; }

otError otPlatRadioTransmit(otInstance *, otRadioFrame *)
{
    sRadioTxOngoing = true;

    return OT_ERROR_NONE;
}

otRadioFrame *otPlatRadioGetTransmitBuffer(otInstance *) { return &sRadioTxFrame; }

//----------------------------------------------------------------------------------------------------------------------
// `otPlatAlarm`

void otPlatAlarmMilliStop(otInstance *) { sAlarmOn = false; }

void otPlatAlarmMilliStartAt(otInstance *, uint32_t aT0, uint32_t aDt)
{
    sAlarmOn   = true;
    sAlarmTime = aT0 + aDt;
}

uint32_t otPlatAlarmMilliGetNow(void) { return sNow; }

//----------------------------------------------------------------------------------------------------------------------
// `otPlatDnssd`

static constexpr uint16_t kDnssdArraySize = 128;

struct DnssdRequest
{
    DnssdRequest(void) = default;

    DnssdRequest(otPlatDnssdRequestId aId, otPlatDnssdRegisterCallback aCallback)
        : mId(aId)
        , mCallback(aCallback)
    {
    }

    otPlatDnssdRequestId        mId;
    otPlatDnssdRegisterCallback mCallback;
};

static Array<DnssdRequest, kDnssdArraySize> sDnssdRegHostRequests;
static Array<DnssdRequest, kDnssdArraySize> sDnssdUnregHostRequests;
static Array<DnssdRequest, kDnssdArraySize> sDnssdRegServiceRequests;
static Array<DnssdRequest, kDnssdArraySize> sDnssdUnregServiceRequests;
static Array<DnssdRequest, kDnssdArraySize> sDnssdRegKeyRequests;
static Array<DnssdRequest, kDnssdArraySize> sDnssdUnregKeyRequests;

static bool             sDnssdShouldCheckWithClient = true;
static Error            sDnssdCallbackError         = kErrorPending;
static otPlatDnssdState sDnssdState                 = OT_PLAT_DNSSD_READY;
static uint16_t         sDnssdNumHostAddresses      = 0;

constexpr uint32_t kInfraIfIndex = 1;

otPlatDnssdState otPlatDnssdGetState(otInstance *aInstance)
{
    OT_UNUSED_VARIABLE(aInstance);

    return sDnssdState;
}

void otPlatDnssdRegisterService(otInstance                 *aInstance,
                                const otPlatDnssdService   *aService,
                                otPlatDnssdRequestId        aRequestId,
                                otPlatDnssdRegisterCallback aCallback)
{
    Log("otPlatDnssdRegisterService(aRequestId: %lu)", ToUlong(aRequestId));
    Log("   hostName       : %s", aService->mHostName);
    Log("   serviceInstance: %s", aService->mServiceInstance);
    Log("   serviceType    : %s", aService->mServiceType);
    Log("   num sub-types  : %u", aService->mSubTypeLabelsLength);

    for (uint16_t index = 0; index < aService->mSubTypeLabelsLength; index++)
    {
        Log("   sub-type %-4u  : %s", index, aService->mSubTypeLabels[index]);
    }

    Log("   TXT data len   : %u", aService->mTxtDataLength);
    Log("   port           : %u", aService->mPort);
    Log("   priority       : %u", aService->mPriority);
    Log("   weight         : %u", aService->mWeight);
    Log("   TTL            : %u", aService->mTtl);
    Log("   Infra-if index : %u", aService->mInfraIfIndex);

    VerifyOrQuit(aInstance == sInstance);
    VerifyOrQuit(aService->mInfraIfIndex == kInfraIfIndex);

    if (sDnssdShouldCheckWithClient)
    {
        Srp::Client &srpClient = AsCoreType(aInstance).Get<Srp::Client>();
        bool         didFind   = false;

        VerifyOrQuit(StringMatch(srpClient.GetHostInfo().GetName(), aService->mHostName));

        didFind = false;

        for (const Srp::Client::Service &service : srpClient.GetServices())
        {
            if (StringMatch(service.GetInstanceName(), aService->mServiceInstance))
            {
                didFind = true;
                VerifyOrQuit(StringMatch(service.GetName(), aService->mServiceType));
                VerifyOrQuit(service.GetPort() == aService->mPort);
                VerifyOrQuit(service.GetWeight() == aService->mWeight);
                VerifyOrQuit(service.GetPriority() == aService->mPriority);
                VerifyOrQuit(service.HasSubType() == (aService->mSubTypeLabelsLength != 0));
            }
        }

        VerifyOrQuit(didFind);
    }

    SuccessOrQuit(sDnssdRegServiceRequests.PushBack(DnssdRequest(aRequestId, aCallback)));

    if ((sDnssdCallbackError != kErrorPending) && (aCallback != nullptr))
    {
        aCallback(aInstance, aRequestId, sDnssdCallbackError);
    }
}

void otPlatDnssdUnregisterService(otInstance                 *aInstance,
                                  const otPlatDnssdService   *aService,
                                  otPlatDnssdRequestId        aRequestId,
                                  otPlatDnssdRegisterCallback aCallback)
{
    Log("otPlatDnssdUnregisterService(aRequestId: %lu)", ToUlong(aRequestId));
    Log("   hostName       : %s", aService->mHostName);
    Log("   serviceInstance: %s", aService->mServiceInstance);
    Log("   serviceName    : %s", aService->mServiceType);
    Log("   Infra-if index : %u", aService->mInfraIfIndex);

    VerifyOrQuit(aInstance == sInstance);
    VerifyOrQuit(aService->mInfraIfIndex == kInfraIfIndex);

    if (sDnssdShouldCheckWithClient)
    {
        // Validate the received service info matches one of the services
        // on SRP client.

        Srp::Client &srpClient = AsCoreType(aInstance).Get<Srp::Client>();
        bool         didFind   = false;

        VerifyOrQuit(StringMatch(srpClient.GetHostInfo().GetName(), aService->mHostName));

        for (const Srp::Client::Service &service : srpClient.GetServices())
        {
            if (StringMatch(service.GetInstanceName(), aService->mServiceInstance))
            {
                didFind = true;
                VerifyOrQuit(StringMatch(service.GetName(), aService->mServiceType));
            }
        }

        VerifyOrQuit(didFind);
    }

    SuccessOrQuit(sDnssdUnregServiceRequests.PushBack(DnssdRequest(aRequestId, aCallback)));

    if ((sDnssdCallbackError != kErrorPending) && (aCallback != nullptr))
    {
        aCallback(aInstance, aRequestId, sDnssdCallbackError);
    }
}

void otPlatDnssdRegisterHost(otInstance                 *aInstance,
                             const otPlatDnssdHost      *aHost,
                             otPlatDnssdRequestId        aRequestId,
                             otPlatDnssdRegisterCallback aCallback)
{
    Log("otPlatDnssdRegisterHost(aRequestId: %lu)", ToUlong(aRequestId));
    Log("   hostName       : %s", aHost->mHostName);
    Log("   numAddresses   : %u", aHost->mAddressesLength);

    for (uint16_t index = 0; index < aHost->mAddressesLength; index++)
    {
        Log("   Address %-4u   : %s", index, AsCoreType(&aHost->mAddresses[index]).ToString().AsCString());
    }

    Log("   TTL            : %u", aHost->mTtl);
    Log("   Infra-if index : %u", aHost->mInfraIfIndex);

    VerifyOrQuit(aInstance == sInstance);
    VerifyOrQuit(aHost->mInfraIfIndex == kInfraIfIndex);

    sDnssdNumHostAddresses = aHost->mAddressesLength;

    if (sDnssdShouldCheckWithClient)
    {
        VerifyOrQuit(StringMatch(AsCoreType(aInstance).Get<Srp::Client>().GetHostInfo().GetName(), aHost->mHostName));
    }

    SuccessOrQuit(sDnssdRegHostRequests.PushBack(DnssdRequest(aRequestId, aCallback)));

    if ((sDnssdCallbackError != kErrorPending) && (aCallback != nullptr))
    {
        aCallback(aInstance, aRequestId, sDnssdCallbackError);
    }
}

void otPlatDnssdUnregisterHost(otInstance                 *aInstance,
                               const otPlatDnssdHost      *aHost,
                               otPlatDnssdRequestId        aRequestId,
                               otPlatDnssdRegisterCallback aCallback)
{
    Log("otPlatDnssdUnregisterHost(aRequestId: %lu)", ToUlong(aRequestId));
    Log("   hostName       : %s", aHost->mHostName);
    Log("   Infra-if index : %u", aHost->mInfraIfIndex);

    VerifyOrQuit(sInstance == aInstance);
    VerifyOrQuit(aHost->mInfraIfIndex == kInfraIfIndex);

    if (sDnssdShouldCheckWithClient)
    {
        VerifyOrQuit(StringMatch(AsCoreType(aInstance).Get<Srp::Client>().GetHostInfo().GetName(), aHost->mHostName));
    }

    SuccessOrQuit(sDnssdUnregHostRequests.PushBack(DnssdRequest(aRequestId, aCallback)));

    if ((sDnssdCallbackError != kErrorPending) && (aCallback != nullptr))
    {
        aCallback(aInstance, aRequestId, sDnssdCallbackError);
    }
}

void otPlatDnssdRegisterKey(otInstance                 *aInstance,
                            const otPlatDnssdKey       *aKey,
                            otPlatDnssdRequestId        aRequestId,
                            otPlatDnssdRegisterCallback aCallback)
{
    Log("otPlatDnssdRegisterKey(aRequestId: %lu)", ToUlong(aRequestId));
    Log("   name           : %s", aKey->mName);
    Log("   serviceType    : %s", aKey->mServiceType == nullptr ? "(null)" : aKey->mServiceType);
    Log("   key data-len   : %u", aKey->mKeyDataLength);
    Log("   TTL            : %u", aKey->mTtl);

    VerifyOrQuit(aInstance == sInstance);
    VerifyOrQuit(aKey->mInfraIfIndex == kInfraIfIndex);

    if (sDnssdShouldCheckWithClient)
    {
        if (aKey->mServiceType == nullptr)
        {
            VerifyOrQuit(StringMatch(AsCoreType(aInstance).Get<Srp::Client>().GetHostInfo().GetName(), aKey->mName));
        }
        else
        {
            bool didFind = false;

            for (const Srp::Client::Service &service : AsCoreType(aInstance).Get<Srp::Client>().GetServices())
            {
                if (StringMatch(service.GetInstanceName(), aKey->mName))
                {
                    didFind = true;
                    VerifyOrQuit(StringMatch(service.GetName(), aKey->mServiceType));
                }
            }

            VerifyOrQuit(didFind);
        }
    }

    SuccessOrQuit(sDnssdRegKeyRequests.PushBack(DnssdRequest(aRequestId, aCallback)));

    if ((sDnssdCallbackError != kErrorPending) && (aCallback != nullptr))
    {
        aCallback(aInstance, aRequestId, sDnssdCallbackError);
    }
}

void otPlatDnssdUnregisterKey(otInstance                 *aInstance,
                              const otPlatDnssdKey       *aKey,
                              otPlatDnssdRequestId        aRequestId,
                              otPlatDnssdRegisterCallback aCallback)
{
    Log("otPlatDnssdUnregisterKey(aRequestId: %lu)", ToUlong(aRequestId));
    Log("   name           : %s", aKey->mName);

    VerifyOrQuit(aInstance == sInstance);
    VerifyOrQuit(aKey->mInfraIfIndex == kInfraIfIndex);

    if (sDnssdShouldCheckWithClient)
    {
        if (aKey->mServiceType == nullptr)
        {
            VerifyOrQuit(StringMatch(AsCoreType(aInstance).Get<Srp::Client>().GetHostInfo().GetName(), aKey->mName));
        }
        else
        {
            bool didFind = false;

            for (const Srp::Client::Service &service : AsCoreType(aInstance).Get<Srp::Client>().GetServices())
            {
                if (StringMatch(service.GetInstanceName(), aKey->mName))
                {
                    didFind = true;
                    VerifyOrQuit(StringMatch(service.GetName(), aKey->mServiceType));
                }
            }

            VerifyOrQuit(didFind);
        }
    }

    SuccessOrQuit(sDnssdUnregKeyRequests.PushBack(DnssdRequest(aRequestId, aCallback)));

    if ((sDnssdCallbackError != kErrorPending) && (aCallback != nullptr))
    {
        aCallback(aInstance, aRequestId, sDnssdCallbackError);
    }
}

// Number of times we expect each `otPlatDnssd` request to be called
struct DnssdRequestCounts : public Clearable<DnssdRequestCounts>
{
    DnssdRequestCounts(void) { Clear(); }

    uint16_t mKeyReg;
    uint16_t mHostReg;
    uint16_t mServiceReg;
    uint16_t mKeyUnreg;
    uint16_t mHostUnreg;
    uint16_t mServiceUnreg;
};

void VerifyDnnsdRequests(const DnssdRequestCounts &aRequestCounts, bool aAllowMoreUnregs = false)
{
    VerifyOrQuit(sDnssdRegKeyRequests.GetLength() == aRequestCounts.mKeyReg);
    VerifyOrQuit(sDnssdRegHostRequests.GetLength() == aRequestCounts.mHostReg);
    VerifyOrQuit(sDnssdRegServiceRequests.GetLength() == aRequestCounts.mServiceReg);

    if (aAllowMoreUnregs)
    {
        VerifyOrQuit(sDnssdUnregKeyRequests.GetLength() >= aRequestCounts.mKeyUnreg);
        VerifyOrQuit(sDnssdUnregHostRequests.GetLength() >= aRequestCounts.mHostUnreg);
        VerifyOrQuit(sDnssdUnregServiceRequests.GetLength() >= aRequestCounts.mServiceUnreg);
    }
    else
    {
        VerifyOrQuit(sDnssdUnregKeyRequests.GetLength() == aRequestCounts.mKeyUnreg);
        VerifyOrQuit(sDnssdUnregHostRequests.GetLength() == aRequestCounts.mHostUnreg);
        VerifyOrQuit(sDnssdUnregServiceRequests.GetLength() == aRequestCounts.mServiceUnreg);
    }
}

//----------------------------------------------------------------------------------------------------------------------

Array<void *, 500> sHeapAllocatedPtrs;

#if OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE
void *otPlatCAlloc(size_t aNum, size_t aSize)
{
    void *ptr = calloc(aNum, aSize);

    SuccessOrQuit(sHeapAllocatedPtrs.PushBack(ptr));

    return ptr;
}

void otPlatFree(void *aPtr)
{
    if (aPtr != nullptr)
    {
        void **entry = sHeapAllocatedPtrs.Find(aPtr);

        VerifyOrQuit(entry != nullptr, "A heap allocated item is freed twice");
        sHeapAllocatedPtrs.Remove(*entry);
    }

    free(aPtr);
}
#endif

#if OPENTHREAD_CONFIG_LOG_OUTPUT == OPENTHREAD_CONFIG_LOG_OUTPUT_PLATFORM_DEFINED
void otPlatLog(otLogLevel aLogLevel, otLogRegion aLogRegion, const char *aFormat, ...)
{
    OT_UNUSED_VARIABLE(aLogLevel);
    OT_UNUSED_VARIABLE(aLogRegion);

    va_list args;

    printf("   ");
    va_start(args, aFormat);
    vprintf(aFormat, args);
    va_end(args);
    printf("\n");
}
#endif

} // extern "C"

//---------------------------------------------------------------------------------------------------------------------

void ProcessRadioTxAndTasklets(void)
{
    do
    {
        if (sRadioTxOngoing)
        {
            sRadioTxOngoing = false;
            otPlatRadioTxStarted(sInstance, &sRadioTxFrame);
            otPlatRadioTxDone(sInstance, &sRadioTxFrame, nullptr, OT_ERROR_NONE);
        }

        otTaskletsProcess(sInstance);
    } while (otTaskletsArePending(sInstance));
}

void AdvanceTime(uint32_t aDuration)
{
    uint32_t time = sNow + aDuration;

    Log("AdvanceTime for %u.%03u", aDuration / 1000, aDuration % 1000);

    while (TimeMilli(sAlarmTime) <= TimeMilli(time))
    {
        ProcessRadioTxAndTasklets();
        sNow = sAlarmTime;
        otPlatAlarmMilliFired(sInstance);
    }

    ProcessRadioTxAndTasklets();
    sNow = time;
}

void InitTest(void)
{
    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Initialize OT instance.

    sNow      = 0;
    sAlarmOn  = false;
    sInstance = static_cast<Instance *>(testInitInstance());

    memset(&sRadioTxFrame, 0, sizeof(sRadioTxFrame));
    sRadioTxFrame.mPsdu = sRadioTxFramePsdu;
    sRadioTxOngoing     = false;

    sDnssdShouldCheckWithClient = true;
    sDnssdState                 = OT_PLAT_DNSSD_READY;
    sDnssdCallbackError         = kErrorPending;
    sDnssdRegHostRequests.Clear();
    sDnssdUnregHostRequests.Clear();
    sDnssdRegServiceRequests.Clear();
    sDnssdUnregServiceRequests.Clear();

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Initialize Border Router and start Thread operation.

    otOperationalDataset     dataset;
    otOperationalDatasetTlvs datasetTlvs;

    SuccessOrQuit(otDatasetCreateNewNetwork(sInstance, &dataset));
    SuccessOrQuit(otDatasetConvertToTlvs(&dataset, &datasetTlvs));
    SuccessOrQuit(otDatasetSetActiveTlvs(sInstance, &datasetTlvs));

    SuccessOrQuit(otIp6SetEnabled(sInstance, true));
    SuccessOrQuit(otThreadSetEnabled(sInstance, true));

    // Configure the `Dnssd` module to use `otPlatDnssd` APIs.

    sInstance->Get<Dnssd>().SetUseNativeMdns(false);

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Ensure device starts as leader.

    AdvanceTime(10000);

    VerifyOrQuit(otThreadGetDeviceRole(sInstance) == OT_DEVICE_ROLE_LEADER);
}

void FinalizeTest(void)
{
    SuccessOrQuit(otIp6SetEnabled(sInstance, false));
    SuccessOrQuit(otThreadSetEnabled(sInstance, false));
    SuccessOrQuit(otInstanceErasePersistentInfo(sInstance));
    testFreeInstance(sInstance);
}

//---------------------------------------------------------------------------------------------------------------------
// SRP Client callback

static bool  sProcessedClientCallback = false;
static Error sLastClientCallbackError = kErrorNone;

void HandleSrpClientCallback(otError                    aError,
                             const otSrpClientHostInfo *aHostInfo,
                             const otSrpClientService  *aServices,
                             const otSrpClientService  *aRemovedServices,
                             void                      *aContext)
{
    Log("HandleSrpClientCallback() called with error %s", ErrorToString(aError));

    VerifyOrQuit(aContext == sInstance);

    sProcessedClientCallback = true;
    sLastClientCallbackError = aError;

    OT_UNUSED_VARIABLE(aHostInfo);
    OT_UNUSED_VARIABLE(aServices);
    OT_UNUSED_VARIABLE(aRemovedServices);
}

static const char kHostName[] = "awesomehost";

void PrepareService1(Srp::Client::Service &aService)
{
    static const char          kServiceName[]   = "_srv1._udp";
    static const char          kInstanceLabel[] = "service1";
    static const char          kSub1[]          = "_sub1";
    static const char          kSub2[]          = "_sub2";
    static const char          kSub3[]          = "_sub3";
    static const char         *kSubLabels[]     = {kSub1, kSub2, kSub3, nullptr};
    static const char          kTxtKey1[]       = "ABCD";
    static const uint8_t       kTxtValue1[]     = {'a', '0'};
    static const char          kTxtKey2[]       = "Z0";
    static const uint8_t       kTxtValue2[]     = {'1', '2', '3'};
    static const char          kTxtKey3[]       = "D";
    static const uint8_t       kTxtValue3[]     = {0};
    static const otDnsTxtEntry kTxtEntries[]    = {
           {kTxtKey1, kTxtValue1, sizeof(kTxtValue1)},
           {kTxtKey2, kTxtValue2, sizeof(kTxtValue2)},
           {kTxtKey3, kTxtValue3, sizeof(kTxtValue3)},
    };

    memset(&aService, 0, sizeof(aService));
    aService.mName          = kServiceName;
    aService.mInstanceName  = kInstanceLabel;
    aService.mSubTypeLabels = kSubLabels;
    aService.mTxtEntries    = kTxtEntries;
    aService.mNumTxtEntries = 3;
    aService.mPort          = 777;
    aService.mWeight        = 1;
    aService.mPriority      = 2;
}

void PrepareService2(Srp::Client::Service &aService)
{
    static const char  kService2Name[]   = "_matter._udp";
    static const char  kInstance2Label[] = "service2";
    static const char  kSub4[]           = "_44444444";
    static const char *kSubLabels2[]     = {kSub4, nullptr};

    memset(&aService, 0, sizeof(aService));
    aService.mName          = kService2Name;
    aService.mInstanceName  = kInstance2Label;
    aService.mSubTypeLabels = kSubLabels2;
    aService.mTxtEntries    = nullptr;
    aService.mNumTxtEntries = 0;
    aService.mPort          = 555;
    aService.mWeight        = 0;
    aService.mPriority      = 3;
}

//----------------------------------------------------------------------------------------------------------------------

typedef Dnssd::RequestId      RequestId;
typedef Dnssd::RequestIdRange RequestIdRange;

void ValidateRequestIdRange(const RequestIdRange &aIdRange, RequestId aStart, RequestId aEnd)
{
    RequestId maxId         = NumericLimits<RequestId>::kMax;
    bool      shouldContain = false;

    VerifyOrQuit(!aIdRange.IsEmpty());

    for (RequestId id = aStart - 5; id != aEnd + 6; id++)
    {
        // `idRange` should contain IDs within range `[aStart, aEnd]`

        if (id == aStart)
        {
            shouldContain = true;
        }

        if (id == aEnd + 1)
        {
            shouldContain = false;
        }

        VerifyOrQuit(aIdRange.Contains(id) == shouldContain);
    }

    // Test values that half the range apart

    for (RequestId id = aStart + maxId / 2 - 10; id != aEnd + maxId / 2 + 10; id++)
    {
        VerifyOrQuit(!aIdRange.Contains(id));
    }
}

void TestDnssdRequestIdRange(void)
{
    RequestId      maxId = NumericLimits<RequestId>::kMax;
    RequestIdRange idRange;

    Log("--------------------------------------------------------------------------------------------");
    Log("TestDnssdRequestIdRange");

    VerifyOrQuit(idRange.IsEmpty());

    idRange.Add(5);
    ValidateRequestIdRange(idRange, 5, 5);

    idRange.Remove(4);
    ValidateRequestIdRange(idRange, 5, 5);

    idRange.Remove(6);
    ValidateRequestIdRange(idRange, 5, 5);

    idRange.Remove(5);
    VerifyOrQuit(idRange.IsEmpty());
    VerifyOrQuit(!idRange.Contains(5));

    // Adding and removing multiple IDs

    idRange.Add(10);
    idRange.Add(15);
    ValidateRequestIdRange(idRange, 10, 15);

    idRange.Add(12);
    ValidateRequestIdRange(idRange, 10, 15);
    idRange.Add(15);
    ValidateRequestIdRange(idRange, 10, 15);
    idRange.Add(10);
    ValidateRequestIdRange(idRange, 10, 15);

    idRange.Add(9);
    ValidateRequestIdRange(idRange, 9, 15);
    idRange.Add(16);
    ValidateRequestIdRange(idRange, 9, 16);

    idRange.Remove(10);
    ValidateRequestIdRange(idRange, 9, 16);
    idRange.Remove(15);
    ValidateRequestIdRange(idRange, 9, 16);

    idRange.Remove(8);
    ValidateRequestIdRange(idRange, 9, 16);
    idRange.Remove(17);
    ValidateRequestIdRange(idRange, 9, 16);

    idRange.Remove(9);
    ValidateRequestIdRange(idRange, 10, 16);
    idRange.Remove(16);
    ValidateRequestIdRange(idRange, 10, 15);

    idRange.Clear();
    VerifyOrQuit(idRange.IsEmpty());
    VerifyOrQuit(!idRange.Contains(10));

    // Ranges close to roll-over max value

    idRange.Add(maxId);
    ValidateRequestIdRange(idRange, maxId, maxId);

    idRange.Remove(0);
    ValidateRequestIdRange(idRange, maxId, maxId);
    idRange.Remove(maxId - 1);
    ValidateRequestIdRange(idRange, maxId, maxId);

    idRange.Add(0);
    ValidateRequestIdRange(idRange, maxId, 0);

    idRange.Add(maxId - 2);
    ValidateRequestIdRange(idRange, maxId - 2, 0);

    idRange.Add(3);
    ValidateRequestIdRange(idRange, maxId - 2, 3);
    idRange.Add(3);
    ValidateRequestIdRange(idRange, maxId - 2, 3);

    idRange.Remove(4);
    ValidateRequestIdRange(idRange, maxId - 2, 3);
    idRange.Remove(maxId - 3);
    ValidateRequestIdRange(idRange, maxId - 2, 3);

    idRange.Remove(3);
    ValidateRequestIdRange(idRange, maxId - 2, 2);

    idRange.Remove(maxId - 2);
    ValidateRequestIdRange(idRange, maxId - 1, 2);

    Log("End of TestDnssdRequestIdRange");
}

void TestSrpAdvProxy(void)
{
    NetworkData::OnMeshPrefixConfig prefixConfig;
    Srp::Server                    *srpServer;
    Srp::Client                    *srpClient;
    Srp::AdvertisingProxy          *advProxy;
    Srp::Client::Service            service1;
    Srp::Client::Service            service2;
    DnssdRequestCounts              dnssdCounts;
    uint16_t                        heapAllocations;

    Log("--------------------------------------------------------------------------------------------");
    Log("TestSrpAdvProxy");

    InitTest();

    srpServer = &sInstance->Get<Srp::Server>();
    srpClient = &sInstance->Get<Srp::Client>();
    advProxy  = &sInstance->Get<Srp::AdvertisingProxy>();

    heapAllocations = sHeapAllocatedPtrs.GetLength();

    PrepareService1(service1);
    PrepareService2(service2);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Add an on-mesh prefix (with SLAAC) to network data");

    prefixConfig.Clear();
    SuccessOrQuit(AsCoreType(&prefixConfig.mPrefix.mPrefix).FromString("fd00:cafe:beef::"));
    prefixConfig.mPrefix.mLength = 64;
    prefixConfig.mStable         = true;
    prefixConfig.mSlaac          = true;
    prefixConfig.mPreferred      = true;
    prefixConfig.mOnMesh         = true;
    prefixConfig.mDefaultRoute   = false;
    prefixConfig.mPreference     = NetworkData::kRoutePreferenceMedium;

    SuccessOrQuit(otBorderRouterAddOnMeshPrefix(sInstance, &prefixConfig));
    SuccessOrQuit(otBorderRouterRegister(sInstance));

    // Configure Dnssd platform API behavior

    sDnssdRegHostRequests.Clear();
    sDnssdRegServiceRequests.Clear();
    sDnssdUnregHostRequests.Clear();
    sDnssdUnregServiceRequests.Clear();
    sDnssdRegKeyRequests.Clear();
    sDnssdUnregKeyRequests.Clear();

    sDnssdState                 = OT_PLAT_DNSSD_READY;
    sDnssdShouldCheckWithClient = true;
    sDnssdCallbackError         = kErrorNone; // Invoke callback directly from dnssd APIs

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Start SRP server");

    SuccessOrQuit(otBorderRoutingInit(sInstance, /* aInfraIfIndex */ kInfraIfIndex, /* aInfraIfIsRunning */ true));

    SuccessOrQuit(srpServer->SetAddressMode(Srp::Server::kAddressModeUnicast));
    VerifyOrQuit(srpServer->GetAddressMode() == Srp::Server::kAddressModeUnicast);

    VerifyOrQuit(srpServer->GetState() == Srp::Server::kStateDisabled);

    srpServer->SetServiceHandler(nullptr, sInstance);

    srpServer->SetEnabled(true);
    VerifyOrQuit(srpServer->GetState() != Srp::Server::kStateDisabled);

    AdvanceTime(10000);
    VerifyOrQuit(srpServer->GetState() == Srp::Server::kStateRunning);
    VerifyOrQuit(advProxy->IsRunning());

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Start SRP client");

    srpClient->SetCallback(HandleSrpClientCallback, sInstance);
    srpClient->SetLeaseInterval(180);

    srpClient->EnableAutoStartMode(nullptr, nullptr);
    VerifyOrQuit(srpClient->IsAutoStartModeEnabled());

    AdvanceTime(2000);
    VerifyOrQuit(srpClient->IsRunning());

    SuccessOrQuit(srpClient->SetHostName(kHostName));
    SuccessOrQuit(srpClient->EnableAutoHostAddress());

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Register a service");

    SuccessOrQuit(srpClient->AddService(service1));

    sProcessedClientCallback = false;

    AdvanceTime(2 * 1000);

    dnssdCounts.mKeyReg += 2;
    dnssdCounts.mHostReg++;
    dnssdCounts.mServiceReg++;
    VerifyDnnsdRequests(dnssdCounts);

    VerifyOrQuit(sProcessedClientCallback);
    VerifyOrQuit(sLastClientCallbackError == kErrorNone);

    VerifyOrQuit(service1.GetState() == Srp::Client::kRegistered);

    VerifyOrQuit(advProxy->GetCounters().mAdvTotal == 1);
    VerifyOrQuit(advProxy->GetCounters().mAdvSuccessful == 1);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Register a second service");

    SuccessOrQuit(srpClient->AddService(service2));

    sProcessedClientCallback = false;

    AdvanceTime(2 * 1000);

    // This time we should only see the new service and its key being
    // registered as the host is same as before and already registered

    dnssdCounts.mKeyReg++;
    dnssdCounts.mServiceReg++;
    VerifyDnnsdRequests(dnssdCounts);

    VerifyOrQuit(sProcessedClientCallback);
    VerifyOrQuit(sLastClientCallbackError == kErrorNone);

    VerifyOrQuit(service1.GetState() == Srp::Client::kRegistered);
    VerifyOrQuit(service2.GetState() == Srp::Client::kRegistered);

    VerifyOrQuit(advProxy->GetCounters().mAdvTotal == 2);
    VerifyOrQuit(advProxy->GetCounters().mAdvSuccessful == 2);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Wait for longer than lease interval for client to refresh");

    sProcessedClientCallback = false;

    AdvanceTime(181 * 1000);

    VerifyOrQuit(sProcessedClientCallback);

    // Validate that adv-proxy does not update any of registration on
    // DNS-SD platform since there is no change.

    VerifyDnnsdRequests(dnssdCounts);

    VerifyOrQuit(advProxy->GetCounters().mAdvTotal >= 3);
    VerifyOrQuit(advProxy->GetCounters().mAdvSuccessful == advProxy->GetCounters().mAdvTotal);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Add a new on-mesh prefix so to get a new host address");

    prefixConfig.Clear();
    SuccessOrQuit(AsCoreType(&prefixConfig.mPrefix.mPrefix).FromString("fd00:abba::"));
    prefixConfig.mPrefix.mLength = 64;
    prefixConfig.mStable         = true;
    prefixConfig.mSlaac          = true;
    prefixConfig.mPreferred      = true;
    prefixConfig.mOnMesh         = true;
    prefixConfig.mDefaultRoute   = false;
    prefixConfig.mPreference     = NetworkData::kRoutePreferenceMedium;

    SuccessOrQuit(otBorderRouterAddOnMeshPrefix(sInstance, &prefixConfig));
    SuccessOrQuit(otBorderRouterRegister(sInstance));

    sProcessedClientCallback = false;

    AdvanceTime(5 * 1000);

    // This time we should only see new host registration
    // since that's the only thing that changes

    dnssdCounts.mHostReg++;
    VerifyDnnsdRequests(dnssdCounts);

    VerifyOrQuit(sProcessedClientCallback);
    VerifyOrQuit(sLastClientCallbackError == kErrorNone);

    VerifyOrQuit(service1.GetState() == Srp::Client::kRegistered);
    VerifyOrQuit(service2.GetState() == Srp::Client::kRegistered);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Remove the first service on client");

    SuccessOrQuit(srpClient->RemoveService(service1));

    sProcessedClientCallback = false;

    AdvanceTime(2 * 1000);

    // We should see the service being unregistered
    // by advertising proxy on DNS-SD platform but its key
    // remains registered.

    dnssdCounts.mServiceUnreg++;
    VerifyDnnsdRequests(dnssdCounts);

    VerifyOrQuit(sProcessedClientCallback);
    VerifyOrQuit(sLastClientCallbackError == kErrorNone);

    VerifyOrQuit(service1.GetState() == Srp::Client::kRemoved);
    VerifyOrQuit(service2.GetState() == Srp::Client::kRegistered);

    // Wait for more than lease interval again and make sure
    // there is no change in DNS-SD platform API calls.

    sProcessedClientCallback = false;

    AdvanceTime(181 * 1000);

    VerifyOrQuit(sProcessedClientCallback);
    VerifyOrQuit(sLastClientCallbackError == kErrorNone);

    VerifyDnnsdRequests(dnssdCounts);

    VerifyOrQuit(service1.GetState() == Srp::Client::kRemoved);
    VerifyOrQuit(service2.GetState() == Srp::Client::kRegistered);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Change service 2 on client, remove its sub-type");

    SuccessOrQuit(srpClient->ClearService(service2));
    PrepareService2(service2);
    service2.mSubTypeLabels = nullptr;

    SuccessOrQuit(srpClient->AddService(service2));

    sProcessedClientCallback = false;

    AdvanceTime(2 * 1000);

    // Since the service is now changed, advertising proxy
    // should update it (re-register it) on DNS-SD APIs.

    dnssdCounts.mServiceReg++;
    VerifyDnnsdRequests(dnssdCounts);

    VerifyOrQuit(sProcessedClientCallback);
    VerifyOrQuit(sLastClientCallbackError == kErrorNone);

    VerifyOrQuit(service1.GetState() == Srp::Client::kRemoved);
    VerifyOrQuit(service2.GetState() == Srp::Client::kRegistered);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Remove the host on client");

    SuccessOrQuit(srpClient->RemoveHostAndServices(/* aShouldRemoveKeyLease */ false));

    sProcessedClientCallback = false;

    AdvanceTime(2 * 1000);

    // We should see the host and service being unregistered
    // on DNS-SD APIs but keys remain unchanged.

    dnssdCounts.mHostUnreg++;
    dnssdCounts.mServiceUnreg++;
    VerifyDnnsdRequests(dnssdCounts);

    VerifyOrQuit(sProcessedClientCallback);
    VerifyOrQuit(sLastClientCallbackError == kErrorNone);

    VerifyOrQuit(service1.GetState() == Srp::Client::kRemoved);
    VerifyOrQuit(service2.GetState() == Srp::Client::kRemoved);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Remove the host on client again and force an update to be sent to server");

    SuccessOrQuit(srpClient->SetHostName(kHostName));
    SuccessOrQuit(srpClient->RemoveHostAndServices(/* aShouldRemoveKeyLease */ false, /* aSendUnregToServer */ true));

    sProcessedClientCallback = false;

    AdvanceTime(2 * 1000);

    // We should see no changes (no calls) to DNS-SD APIs.

    VerifyDnnsdRequests(dnssdCounts);

    VerifyOrQuit(sProcessedClientCallback);
    VerifyOrQuit(sLastClientCallbackError == kErrorNone);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Re-add service 1 on client and register with server");

    SuccessOrQuit(srpClient->SetHostName(kHostName));
    SuccessOrQuit(srpClient->EnableAutoHostAddress());
    PrepareService1(service1);
    SuccessOrQuit(srpClient->AddService(service1));

    sProcessedClientCallback = false;

    AdvanceTime(2 * 1000);

    // We should see one host register and one service register
    // on DNS-SD APIs. Keys are already registered.

    dnssdCounts.mHostReg++;
    dnssdCounts.mServiceReg++;
    VerifyDnnsdRequests(dnssdCounts);

    VerifyOrQuit(sProcessedClientCallback);
    VerifyOrQuit(sLastClientCallbackError == kErrorNone);

    VerifyOrQuit(service1.GetState() == Srp::Client::kRegistered);

    // Wait for more than lease interval again and make sure
    // there is no change in DNS-SD platform API calls.

    sProcessedClientCallback = false;

    AdvanceTime(181 * 1000);

    VerifyOrQuit(sProcessedClientCallback);
    VerifyOrQuit(sLastClientCallbackError == kErrorNone);

    VerifyDnnsdRequests(dnssdCounts);

    VerifyOrQuit(service1.GetState() == Srp::Client::kRegistered);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Disable SRP client and wait for lease time to expire");

    srpClient->ClearHostAndServices(); // does not signal removal to server

    // Since we clear everything on SRP client, we disable
    // matching the services with client from `otPlatDnssd`
    // APIs.
    sDnssdShouldCheckWithClient = false;

    AdvanceTime(181 * 1000);

    // Make sure host and service are unregistered.

    dnssdCounts.mHostUnreg++;
    dnssdCounts.mServiceUnreg++;
    VerifyDnnsdRequests(dnssdCounts);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Disable SRP server");

    // Verify that all heap allocations by SRP server
    // and Advertising Proxy are freed.

    srpServer->SetEnabled(false);
    AdvanceTime(100);
    VerifyOrQuit(!advProxy->IsRunning());

    VerifyOrQuit(advProxy->GetCounters().mAdvSuccessful == advProxy->GetCounters().mAdvTotal);
    VerifyOrQuit(advProxy->GetCounters().mAdvTimeout == 0);
    VerifyOrQuit(advProxy->GetCounters().mAdvRejected == 0);
    VerifyOrQuit(advProxy->GetCounters().mAdvSkipped == 0);
    VerifyOrQuit(advProxy->GetCounters().mAdvReplaced == 0);

    dnssdCounts.mKeyUnreg += 3;
    VerifyDnnsdRequests(dnssdCounts);

    VerifyOrQuit(heapAllocations == sHeapAllocatedPtrs.GetLength());

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Finalize OT instance and validate all heap allocations are freed");

    FinalizeTest();

    VerifyOrQuit(sHeapAllocatedPtrs.IsEmpty());

    Log("End of TestSrpAdvProxy");
}

void TestSrpAdvProxyDnssdStateChange(void)
{
    NetworkData::OnMeshPrefixConfig prefixConfig;
    Srp::Server                    *srpServer;
    Srp::Client                    *srpClient;
    Srp::AdvertisingProxy          *advProxy;
    Srp::Client::Service            service1;
    Srp::Client::Service            service2;
    DnssdRequestCounts              dnssdCounts;
    uint16_t                        heapAllocations;

    Log("--------------------------------------------------------------------------------------------");
    Log("TestSrpAdvProxyDnssdStateChange");

    InitTest();

    srpServer = &sInstance->Get<Srp::Server>();
    srpClient = &sInstance->Get<Srp::Client>();
    advProxy  = &sInstance->Get<Srp::AdvertisingProxy>();

    heapAllocations = sHeapAllocatedPtrs.GetLength();

    PrepareService1(service1);
    PrepareService2(service2);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Add an on-mesh prefix (with SLAAC) to network data");

    prefixConfig.Clear();
    SuccessOrQuit(AsCoreType(&prefixConfig.mPrefix.mPrefix).FromString("fd00:cafe:beef::"));
    prefixConfig.mPrefix.mLength = 64;
    prefixConfig.mStable         = true;
    prefixConfig.mSlaac          = true;
    prefixConfig.mPreferred      = true;
    prefixConfig.mOnMesh         = true;
    prefixConfig.mDefaultRoute   = false;
    prefixConfig.mPreference     = NetworkData::kRoutePreferenceMedium;

    SuccessOrQuit(otBorderRouterAddOnMeshPrefix(sInstance, &prefixConfig));
    SuccessOrQuit(otBorderRouterRegister(sInstance));

    // Configure Dnssd platform API behavior

    sDnssdRegHostRequests.Clear();
    sDnssdRegServiceRequests.Clear();
    sDnssdUnregHostRequests.Clear();
    sDnssdUnregServiceRequests.Clear();
    sDnssdRegKeyRequests.Clear();
    sDnssdUnregKeyRequests.Clear();

    sDnssdState                 = OT_PLAT_DNSSD_STOPPED;
    sDnssdShouldCheckWithClient = true;
    sDnssdCallbackError         = kErrorNone; // Invoke callback directly

    VerifyOrQuit(!advProxy->IsRunning());

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Start SRP server");

    SuccessOrQuit(otBorderRoutingInit(sInstance, /* aInfraIfIndex */ kInfraIfIndex, /* aInfraIfIsRunning */ true));

    SuccessOrQuit(srpServer->SetAddressMode(Srp::Server::kAddressModeUnicast));
    VerifyOrQuit(srpServer->GetAddressMode() == Srp::Server::kAddressModeUnicast);

    VerifyOrQuit(srpServer->GetState() == Srp::Server::kStateDisabled);

    srpServer->SetServiceHandler(nullptr, sInstance);

    srpServer->SetEnabled(true);
    VerifyOrQuit(srpServer->GetState() != Srp::Server::kStateDisabled);

    AdvanceTime(10000);
    VerifyOrQuit(srpServer->GetState() == Srp::Server::kStateRunning);
    VerifyOrQuit(!advProxy->IsRunning());

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Start SRP client");

    srpClient->SetCallback(HandleSrpClientCallback, sInstance);
    srpClient->SetLeaseInterval(180);

    srpClient->EnableAutoStartMode(nullptr, nullptr);
    VerifyOrQuit(srpClient->IsAutoStartModeEnabled());

    AdvanceTime(2000);
    VerifyOrQuit(srpClient->IsRunning());

    SuccessOrQuit(srpClient->SetHostName(kHostName));
    SuccessOrQuit(srpClient->EnableAutoHostAddress());

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Register a services");

    SuccessOrQuit(srpClient->AddService(service1));

    sProcessedClientCallback = false;

    AdvanceTime(2 * 1000);

    VerifyOrQuit(sProcessedClientCallback);
    VerifyOrQuit(sLastClientCallbackError == kErrorNone);

    VerifyOrQuit(service1.GetState() == Srp::Client::kRegistered);

    VerifyDnnsdRequests(dnssdCounts);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Register a second service");

    SuccessOrQuit(srpClient->AddService(service2));

    sProcessedClientCallback = false;

    AdvanceTime(2 * 1000);

    VerifyOrQuit(sProcessedClientCallback);
    VerifyOrQuit(sLastClientCallbackError == kErrorNone);

    VerifyOrQuit(service1.GetState() == Srp::Client::kRegistered);
    VerifyOrQuit(service2.GetState() == Srp::Client::kRegistered);

    // None of the DNS-SD APIs should be called since its state
    // `OT_PLAT_DNSSD_STOPPED` (`dnssdCounts` is all zeros).
    VerifyDnnsdRequests(dnssdCounts);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Update DNS-SD state and signal that state is changed");

    sDnssdState = OT_PLAT_DNSSD_READY;
    otPlatDnssdStateHandleStateChange(sInstance);

    AdvanceTime(5);

    VerifyOrQuit(advProxy->IsRunning());
    VerifyOrQuit(advProxy->GetCounters().mStateChanges == 1);

    // Now the host and two services should be registered on
    // DNS-SD platform

    dnssdCounts.mHostReg++;
    dnssdCounts.mServiceReg += 2;
    dnssdCounts.mKeyReg += 3;
    VerifyDnnsdRequests(dnssdCounts);

    VerifyOrQuit(service1.GetState() == Srp::Client::kRegistered);
    VerifyOrQuit(service2.GetState() == Srp::Client::kRegistered);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Wait for longer than lease interval for client to refresh");

    sProcessedClientCallback = false;

    AdvanceTime(181 * 1000);

    VerifyOrQuit(sProcessedClientCallback);

    // Validate that adv-proxy does not update any of registration on
    // DNS-SD platform since there is no change.
    VerifyDnnsdRequests(dnssdCounts);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Update DNS-SD state to `STOPPED` and signal its change");

    sDnssdState = OT_PLAT_DNSSD_STOPPED;
    otPlatDnssdStateHandleStateChange(sInstance);

    AdvanceTime(5);

    VerifyOrQuit(!advProxy->IsRunning());
    VerifyOrQuit(advProxy->GetCounters().mStateChanges == 2);

    // Since DNS-SD platform signal that it is stopped,
    // there should be no calls to any of APIs.

    VerifyDnnsdRequests(dnssdCounts);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Wait for longer than lease interval for client to refresh");

    sProcessedClientCallback = false;

    AdvanceTime(181 * 1000);

    VerifyOrQuit(sProcessedClientCallback);
    VerifyOrQuit(service1.GetState() == Srp::Client::kRegistered);
    VerifyOrQuit(service2.GetState() == Srp::Client::kRegistered);

    // The DNS-SD API counters should remain unchanged

    VerifyDnnsdRequests(dnssdCounts);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Update DNS-SD state to `READY` and signal its change");

    sDnssdState = OT_PLAT_DNSSD_READY;
    otPlatDnssdStateHandleStateChange(sInstance);

    AdvanceTime(5);

    VerifyOrQuit(advProxy->IsRunning());
    VerifyOrQuit(advProxy->GetCounters().mStateChanges == 3);

    // Check that the host and two services are again registered
    // on DNS-SD platform by advertising proxy.

    dnssdCounts.mHostReg++;
    dnssdCounts.mServiceReg += 2;
    dnssdCounts.mKeyReg += 3;
    VerifyDnnsdRequests(dnssdCounts);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Update DNS-SD state to `STOPPED` and signal its change");

    sDnssdState = OT_PLAT_DNSSD_STOPPED;
    otPlatDnssdStateHandleStateChange(sInstance);

    AdvanceTime(5);

    VerifyOrQuit(!advProxy->IsRunning());
    VerifyOrQuit(advProxy->GetCounters().mStateChanges == 4);

    // Since DNS-SD platform signal that it is stopped,
    // there should be no calls to any of APIs.

    VerifyDnnsdRequests(dnssdCounts);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Remove the first service on client");

    SuccessOrQuit(srpClient->RemoveService(service1));

    sProcessedClientCallback = false;

    AdvanceTime(2 * 1000);

    VerifyOrQuit(sProcessedClientCallback);
    VerifyOrQuit(sLastClientCallbackError == kErrorNone);
    VerifyOrQuit(service1.GetState() == Srp::Client::kRemoved);
    VerifyOrQuit(service2.GetState() == Srp::Client::kRegistered);

    // No changes to DNS-SD API counters (since it is stopped)

    VerifyDnnsdRequests(dnssdCounts);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Update DNS-SD state to `READY` and signal its change #2");

    // Since the already removed `service1` is no longer available
    // on SRP client, we disable checking the services with client
    // from `otPlatDnssd` APIs.
    sDnssdShouldCheckWithClient = false;

    sDnssdState = OT_PLAT_DNSSD_READY;
    otPlatDnssdStateHandleStateChange(sInstance);

    AdvanceTime(5);

    VerifyOrQuit(advProxy->IsRunning());
    VerifyOrQuit(advProxy->GetCounters().mStateChanges == 5);

    // We should see the host and `service2` registered again.
    // And all 3 keys (even for removed `service1`) to be
    // registered.

    dnssdCounts.mHostReg++;
    dnssdCounts.mServiceReg++;
    dnssdCounts.mKeyReg += 3;
    VerifyDnnsdRequests(dnssdCounts);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Disable SRP server");

    // Verify that all heap allocations by SRP server
    // and Advertising Proxy are freed.

    srpServer->SetEnabled(false);
    AdvanceTime(100);

    VerifyOrQuit(!advProxy->IsRunning());
    VerifyOrQuit(advProxy->GetCounters().mStateChanges == 6);
    VerifyOrQuit(advProxy->GetCounters().mAdvSkipped > 0);
    VerifyOrQuit(advProxy->GetCounters().mAdvTotal ==
                 (advProxy->GetCounters().mAdvSuccessful + advProxy->GetCounters().mAdvSkipped));
    VerifyOrQuit(advProxy->GetCounters().mAdvTimeout == 0);
    VerifyOrQuit(advProxy->GetCounters().mAdvRejected == 0);
    VerifyOrQuit(advProxy->GetCounters().mAdvReplaced == 0);

    dnssdCounts.mHostUnreg++;
    dnssdCounts.mServiceUnreg++;
    dnssdCounts.mKeyUnreg += 3;
    VerifyDnnsdRequests(dnssdCounts);

    VerifyOrQuit(heapAllocations == sHeapAllocatedPtrs.GetLength());

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Finalize OT instance and validate all heap allocations are freed");

    FinalizeTest();

    VerifyOrQuit(sHeapAllocatedPtrs.IsEmpty());

    Log("End of TestSrpAdvProxyDnssdStateChange");
}

void TestSrpAdvProxyDelayedCallback(void)
{
    NetworkData::OnMeshPrefixConfig prefixConfig;
    Srp::Server                    *srpServer;
    Srp::Client                    *srpClient;
    Srp::AdvertisingProxy          *advProxy;
    Srp::Client::Service            service1;
    Srp::Client::Service            service2;
    DnssdRequestCounts              dnssdCounts;
    uint16_t                        heapAllocations;
    const DnssdRequest             *request;

    Log("--------------------------------------------------------------------------------------------");
    Log("TestSrpAdvProxyDelayedCallback");

    InitTest();

    srpServer = &sInstance->Get<Srp::Server>();
    srpClient = &sInstance->Get<Srp::Client>();
    advProxy  = &sInstance->Get<Srp::AdvertisingProxy>();

    heapAllocations = sHeapAllocatedPtrs.GetLength();

    PrepareService1(service1);
    PrepareService2(service2);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Add an on-mesh prefix (with SLAAC) to network data");

    prefixConfig.Clear();
    SuccessOrQuit(AsCoreType(&prefixConfig.mPrefix.mPrefix).FromString("fd00:cafe:beef::"));
    prefixConfig.mPrefix.mLength = 64;
    prefixConfig.mStable         = true;
    prefixConfig.mSlaac          = true;
    prefixConfig.mPreferred      = true;
    prefixConfig.mOnMesh         = true;
    prefixConfig.mDefaultRoute   = false;
    prefixConfig.mPreference     = NetworkData::kRoutePreferenceMedium;

    SuccessOrQuit(otBorderRouterAddOnMeshPrefix(sInstance, &prefixConfig));
    SuccessOrQuit(otBorderRouterRegister(sInstance));

    // Configure Dnssd platform API behavior

    sDnssdRegHostRequests.Clear();
    sDnssdRegServiceRequests.Clear();
    sDnssdUnregHostRequests.Clear();
    sDnssdUnregServiceRequests.Clear();
    sDnssdRegKeyRequests.Clear();
    sDnssdUnregKeyRequests.Clear();

    sDnssdState                 = OT_PLAT_DNSSD_READY;
    sDnssdShouldCheckWithClient = true;
    sDnssdCallbackError         = kErrorPending; // Do not call the callbacks directly

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Start SRP server");

    SuccessOrQuit(otBorderRoutingInit(sInstance, /* aInfraIfIndex */ kInfraIfIndex, /* aInfraIfIsRunning */ true));

    SuccessOrQuit(srpServer->SetAddressMode(Srp::Server::kAddressModeUnicast));
    VerifyOrQuit(srpServer->GetAddressMode() == Srp::Server::kAddressModeUnicast);

    VerifyOrQuit(srpServer->GetState() == Srp::Server::kStateDisabled);

    srpServer->SetServiceHandler(nullptr, sInstance);

    srpServer->SetEnabled(true);
    VerifyOrQuit(srpServer->GetState() != Srp::Server::kStateDisabled);

    AdvanceTime(10000);
    VerifyOrQuit(srpServer->GetState() == Srp::Server::kStateRunning);
    VerifyOrQuit(advProxy->IsRunning());

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Start SRP client");

    srpClient->SetCallback(HandleSrpClientCallback, sInstance);
    srpClient->SetLeaseInterval(180);

    srpClient->EnableAutoStartMode(nullptr, nullptr);
    VerifyOrQuit(srpClient->IsAutoStartModeEnabled());

    AdvanceTime(2000);
    VerifyOrQuit(srpClient->IsRunning());

    SuccessOrQuit(srpClient->SetHostName(kHostName));
    SuccessOrQuit(srpClient->EnableAutoHostAddress());

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Register a service, invoke the registration callback after some delay");

    SuccessOrQuit(srpClient->AddService(service1));

    sProcessedClientCallback = false;

    AdvanceTime(1000);

    dnssdCounts.mHostReg++;
    dnssdCounts.mServiceReg++;
    dnssdCounts.mKeyReg += 2;
    VerifyDnnsdRequests(dnssdCounts);

    VerifyOrQuit(advProxy->GetCounters().mAdvTotal == 1);

    VerifyOrQuit(!sProcessedClientCallback);
    VerifyOrQuit(srpServer->GetNextHost(nullptr) == nullptr);

    // Invoke the service and key callbacks first
    request = &sDnssdRegServiceRequests[0];
    VerifyOrQuit(request->mCallback != nullptr);
    request->mCallback(sInstance, request->mId, kErrorNone);

    request = &sDnssdRegKeyRequests[0];
    VerifyOrQuit(request->mCallback != nullptr);
    request->mCallback(sInstance, request->mId, kErrorNone);

    request = &sDnssdRegKeyRequests[1];
    VerifyOrQuit(request->mCallback != nullptr);
    request->mCallback(sInstance, request->mId, kErrorNone);

    AdvanceTime(10);
    VerifyOrQuit(!sProcessedClientCallback);
    VerifyOrQuit(srpServer->GetNextHost(nullptr) == nullptr);

    // Invoke the host registration callback next
    request = &sDnssdRegHostRequests[0];
    VerifyOrQuit(request->mCallback != nullptr);
    request->mCallback(sInstance, request->mId, kErrorNone);

    AdvanceTime(10);
    VerifyOrQuit(srpServer->GetNextHost(nullptr) != nullptr);

    AdvanceTime(100);
    VerifyOrQuit(sProcessedClientCallback);
    VerifyOrQuit(sLastClientCallbackError == kErrorNone);
    VerifyOrQuit(service1.GetState() == Srp::Client::kRegistered);

    VerifyOrQuit(advProxy->GetCounters().mAdvTotal == 1);
    VerifyOrQuit(advProxy->GetCounters().mAdvSuccessful == 1);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Register a second service, invoke registration callback with `kErrorDuplicated`");

    SuccessOrQuit(srpClient->AddService(service2));

    sProcessedClientCallback = false;

    AdvanceTime(1000);

    VerifyOrQuit(!sProcessedClientCallback);

    dnssdCounts.mServiceReg++;
    dnssdCounts.mKeyReg++;
    VerifyDnnsdRequests(dnssdCounts);

    VerifyOrQuit(advProxy->GetCounters().mAdvTotal == 2);
    VerifyOrQuit(advProxy->GetCounters().mAdvSuccessful == 1);

    // Invoke the service callback with kErrorDuplicated

    request = &sDnssdRegServiceRequests[1];
    VerifyOrQuit(request->mCallback != nullptr);
    request->mCallback(sInstance, request->mId, kErrorDuplicated);

    AdvanceTime(100);

    VerifyOrQuit(sProcessedClientCallback);
    VerifyOrQuit(sLastClientCallbackError == kErrorDuplicated);

    VerifyOrQuit(advProxy->GetCounters().mAdvTotal == 2);
    VerifyOrQuit(advProxy->GetCounters().mAdvSuccessful == 1);
    VerifyOrQuit(advProxy->GetCounters().mAdvRejected == 1);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Try registering service again from client, invoke callback with success");

    SuccessOrQuit(srpClient->ClearService(service2));
    PrepareService2(service2);
    SuccessOrQuit(srpClient->AddService(service2));

    sProcessedClientCallback = false;

    AdvanceTime(1000);

    VerifyOrQuit(!sProcessedClientCallback);

    // We should see a new service registration request.

    dnssdCounts.mServiceReg++;
    dnssdCounts.mKeyReg++;
    VerifyDnnsdRequests(dnssdCounts);

    VerifyOrQuit(advProxy->GetCounters().mAdvTotal == 3);
    VerifyOrQuit(advProxy->GetCounters().mAdvSuccessful == 1);
    VerifyOrQuit(advProxy->GetCounters().mAdvRejected == 1);

    // Invoked the service and key callback with success.

    request = &sDnssdRegKeyRequests[sDnssdRegKeyRequests.GetLength() - 1];
    VerifyOrQuit(request->mCallback != nullptr);
    request->mCallback(sInstance, request->mId, kErrorNone);

    request = &sDnssdRegServiceRequests[sDnssdRegServiceRequests.GetLength() - 1];
    VerifyOrQuit(request->mCallback != nullptr);
    request->mCallback(sInstance, request->mId, kErrorNone);

    AdvanceTime(100);

    VerifyOrQuit(sProcessedClientCallback);
    VerifyOrQuit(sLastClientCallbackError == kErrorNone);
    VerifyOrQuit(service1.GetState() == Srp::Client::kRegistered);
    VerifyOrQuit(service2.GetState() == Srp::Client::kRegistered);

    VerifyOrQuit(advProxy->GetCounters().mAdvTotal == 3);
    VerifyOrQuit(advProxy->GetCounters().mAdvSuccessful == 2);
    VerifyOrQuit(advProxy->GetCounters().mAdvRejected == 1);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Change the service and register again, but ignore the registration callback");

    SuccessOrQuit(srpClient->ClearService(service2));
    PrepareService2(service2);
    service2.mSubTypeLabels = nullptr;
    SuccessOrQuit(srpClient->AddService(service2));

    sProcessedClientCallback = false;

    AdvanceTime(1000);

    VerifyOrQuit(!sProcessedClientCallback);

    // We should see a new service registration request.

    dnssdCounts.mServiceReg++;
    VerifyDnnsdRequests(dnssdCounts);

    // Wait for advertising proxy timeout (there will be no callback from
    // platform) so validate that registration failure is reported to
    // the SRP client.

    AdvanceTime(2 * 1000);

    VerifyOrQuit(sProcessedClientCallback);
    VerifyOrQuit(sLastClientCallbackError != kErrorNone);

    VerifyOrQuit(advProxy->GetCounters().mAdvTimeout == 1);

    // Wait for longer than client retry time.

    AdvanceTime(3 * 1000);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Disable SRP server");

    // Verify that all heap allocations by SRP server
    // and Advertising Proxy are freed.

    srpServer->SetEnabled(false);
    AdvanceTime(100);

    // Make sure the host and two services are unregistered
    // (even though the second service was not successfully
    // registered yet).

    VerifyOrQuit(sDnssdRegHostRequests.GetLength() == 1);
    VerifyOrQuit(sDnssdRegServiceRequests.GetLength() >= 4);
    VerifyOrQuit(sDnssdRegKeyRequests.GetLength() >= 3);
    VerifyOrQuit(sDnssdUnregHostRequests.GetLength() == 1);
    VerifyOrQuit(sDnssdUnregServiceRequests.GetLength() == 2);
    VerifyOrQuit(sDnssdUnregKeyRequests.GetLength() == 3);

    VerifyOrQuit(heapAllocations == sHeapAllocatedPtrs.GetLength());

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Finalize OT instance and validate all heap allocations are freed");

    FinalizeTest();

    VerifyOrQuit(sHeapAllocatedPtrs.IsEmpty());

    Log("End of TestSrpAdvProxyDelayedCallback");
}

void TestSrpAdvProxyReplacedEntries(void)
{
    NetworkData::OnMeshPrefixConfig prefixConfig;
    Srp::Server                    *srpServer;
    Srp::Client                    *srpClient;
    Srp::AdvertisingProxy          *advProxy;
    Srp::Client::Service            service1;
    Srp::Client::Service            service2;
    DnssdRequestCounts              dnssdCounts;
    uint16_t                        heapAllocations;
    const DnssdRequest             *request;
    uint16_t                        numServices;

    Log("--------------------------------------------------------------------------------------------");
    Log("TestSrpAdvProxyReplacedEntries");

    InitTest();

    srpServer = &sInstance->Get<Srp::Server>();
    srpClient = &sInstance->Get<Srp::Client>();
    advProxy  = &sInstance->Get<Srp::AdvertisingProxy>();

    heapAllocations = sHeapAllocatedPtrs.GetLength();

    PrepareService1(service1);
    PrepareService2(service2);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Add an on-mesh prefix (with SLAAC) to network data");

    prefixConfig.Clear();
    SuccessOrQuit(AsCoreType(&prefixConfig.mPrefix.mPrefix).FromString("fd00:cafe:beef::"));
    prefixConfig.mPrefix.mLength = 64;
    prefixConfig.mStable         = true;
    prefixConfig.mSlaac          = true;
    prefixConfig.mPreferred      = true;
    prefixConfig.mOnMesh         = true;
    prefixConfig.mDefaultRoute   = false;
    prefixConfig.mPreference     = NetworkData::kRoutePreferenceMedium;

    SuccessOrQuit(otBorderRouterAddOnMeshPrefix(sInstance, &prefixConfig));
    SuccessOrQuit(otBorderRouterRegister(sInstance));

    // Configure Dnssd platform API behavior

    sDnssdRegHostRequests.Clear();
    sDnssdRegServiceRequests.Clear();
    sDnssdUnregHostRequests.Clear();
    sDnssdUnregServiceRequests.Clear();
    sDnssdRegKeyRequests.Clear();
    sDnssdUnregKeyRequests.Clear();

    sDnssdState                 = OT_PLAT_DNSSD_READY;
    sDnssdShouldCheckWithClient = true;
    sDnssdCallbackError         = kErrorPending; // Do not call the callbacks directly

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Start SRP server");

    SuccessOrQuit(otBorderRoutingInit(sInstance, /* aInfraIfIndex */ kInfraIfIndex, /* aInfraIfIsRunning */ true));

    SuccessOrQuit(srpServer->SetAddressMode(Srp::Server::kAddressModeUnicast));
    VerifyOrQuit(srpServer->GetAddressMode() == Srp::Server::kAddressModeUnicast);

    VerifyOrQuit(srpServer->GetState() == Srp::Server::kStateDisabled);

    srpServer->SetServiceHandler(nullptr, sInstance);

    srpServer->SetEnabled(true);
    VerifyOrQuit(srpServer->GetState() != Srp::Server::kStateDisabled);

    AdvanceTime(10000);
    VerifyOrQuit(srpServer->GetState() == Srp::Server::kStateRunning);
    VerifyOrQuit(advProxy->IsRunning());

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Set AdvTimeout to 5 minutes on AdvProxy");

    // Change the timeout on AvdertisingProxy to 5 minutes
    // so that we can send multiple SRP updates and create
    // situations where previous advertisement are replaced.

    advProxy->SetAdvTimeout(5 * 60 * 1000);
    VerifyOrQuit(advProxy->GetAdvTimeout() == 5 * 60 * 1000);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Start SRP client");

    srpClient->SetCallback(HandleSrpClientCallback, sInstance);

    srpClient->EnableAutoStartMode(nullptr, nullptr);
    VerifyOrQuit(srpClient->IsAutoStartModeEnabled());

    AdvanceTime(2000);
    VerifyOrQuit(srpClient->IsRunning());

    SuccessOrQuit(srpClient->SetHostName(kHostName));
    SuccessOrQuit(srpClient->EnableAutoHostAddress());

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Register a service and do not invoke the registration request callbacks");

    SuccessOrQuit(srpClient->AddService(service1));

    sProcessedClientCallback = false;

    AdvanceTime(1200);

    dnssdCounts.mHostReg++;
    dnssdCounts.mServiceReg++;
    dnssdCounts.mKeyReg += 2;
    VerifyDnnsdRequests(dnssdCounts);

    VerifyOrQuit(advProxy->GetCounters().mAdvTotal == 1);
    VerifyOrQuit(advProxy->GetCounters().mAdvReplaced == 0);

    VerifyOrQuit(!sProcessedClientCallback);
    VerifyOrQuit(srpServer->GetNextHost(nullptr) == nullptr);

    // SRP client min retry is 1800 msec, we wait for longer
    // to make sure client retries.

    AdvanceTime(2000);

    VerifyOrQuit(advProxy->GetCounters().mAdvTotal == 2);
    VerifyOrQuit(advProxy->GetCounters().mAdvReplaced == 0);

    // We should see no new service or host registrations on
    // DNS-SD platform APIs as the requests should be same
    // and fully matching the outstanding ones.

    VerifyDnnsdRequests(dnssdCounts);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Invoke the DNS-SD API callbacks");

    request = &sDnssdRegServiceRequests[0];
    VerifyOrQuit(request->mCallback != nullptr);
    request->mCallback(sInstance, request->mId, kErrorNone);

    request = &sDnssdRegHostRequests[0];
    VerifyOrQuit(request->mCallback != nullptr);
    request->mCallback(sInstance, request->mId, kErrorNone);

    for (uint16_t index = 0; index < 2; index++)
    {
        request = &sDnssdRegKeyRequests[index];
        VerifyOrQuit(request->mCallback != nullptr);
        request->mCallback(sInstance, request->mId, kErrorNone);
    }

    AdvanceTime(100);

    VerifyOrQuit(sProcessedClientCallback);
    VerifyOrQuit(sLastClientCallbackError == kErrorNone);
    VerifyOrQuit(srpServer->GetNextHost(nullptr) != nullptr);

    VerifyOrQuit(advProxy->GetCounters().mAdvTotal == 2);
    VerifyOrQuit(advProxy->GetCounters().mAdvSuccessful == 2);
    VerifyOrQuit(advProxy->GetCounters().mAdvReplaced == 0);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Check outstanding Adv being replaced");

    // Change service 1
    SuccessOrQuit(srpClient->ClearService(service1));
    PrepareService1(service1);
    service1.mSubTypeLabels = nullptr; // No sub-types
    SuccessOrQuit(srpClient->AddService(service1));

    sProcessedClientCallback = false;

    AdvanceTime(1200);

    VerifyOrQuit(advProxy->GetCounters().mAdvTotal == 3);
    VerifyOrQuit(advProxy->GetCounters().mAdvSuccessful == 2);
    VerifyOrQuit(advProxy->GetCounters().mAdvReplaced == 0);

    // We should see the changed service registered on DNS-SD
    // platform APIs.

    dnssdCounts.mServiceReg++;
    VerifyDnnsdRequests(dnssdCounts);

    // Change service 1 again (add sub-types back).
    SuccessOrQuit(srpClient->ClearService(service1));
    PrepareService1(service1);
    SuccessOrQuit(srpClient->AddService(service1));

    AdvanceTime(1200);

    VerifyOrQuit(advProxy->GetCounters().mAdvTotal == 4);
    VerifyOrQuit(advProxy->GetCounters().mAdvSuccessful == 2);
    VerifyOrQuit(advProxy->GetCounters().mAdvReplaced == 1);

    // We should see the changed service registered on DNS-SD
    // platform APIs again.

    dnssdCounts.mServiceReg++;
    VerifyDnnsdRequests(dnssdCounts);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Invoke the replaced entry DNS-SD API callback");

    request = &sDnssdRegServiceRequests[1];
    VerifyOrQuit(request->mCallback != nullptr);
    request->mCallback(sInstance, request->mId, kErrorNone);

    AdvanceTime(100);

    // Since adv is replaced invoking the old registration callback
    // should not complete it.

    VerifyOrQuit(!sProcessedClientCallback);
    VerifyOrQuit(advProxy->GetCounters().mAdvTotal == 4);
    VerifyOrQuit(advProxy->GetCounters().mAdvSuccessful == 2);
    VerifyOrQuit(advProxy->GetCounters().mAdvReplaced == 1);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Invoke the new entry DNS-SD API callback");

    request = &sDnssdRegServiceRequests[2];
    VerifyOrQuit(request->mCallback != nullptr);
    request->mCallback(sInstance, request->mId, kErrorNone);

    AdvanceTime(100);

    VerifyOrQuit(sProcessedClientCallback);
    VerifyOrQuit(sLastClientCallbackError == kErrorNone);

    VerifyOrQuit(advProxy->GetCounters().mAdvTotal == 4);
    VerifyOrQuit(advProxy->GetCounters().mAdvSuccessful == 4);
    VerifyOrQuit(advProxy->GetCounters().mAdvReplaced == 1);

    // Make sure the service entry on the SRP server is the
    // last (most recent) request with three sub-types

    VerifyOrQuit(srpServer->GetNextHost(nullptr)->GetServices().GetHead() != nullptr);
    VerifyOrQuit(srpServer->GetNextHost(nullptr)->GetServices().GetHead()->GetNumberOfSubTypes() == 3);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Check replacing Adv being blocked till old Adv is completed");

    // Change service 1 and add service 2
    SuccessOrQuit(srpClient->ClearService(service1));
    PrepareService1(service1);
    service1.mSubTypeLabels = nullptr; // No sub-types
    SuccessOrQuit(srpClient->AddService(service1));
    SuccessOrQuit(srpClient->AddService(service2));

    sProcessedClientCallback = false;

    AdvanceTime(1200);

    // We should see a new Adv with two new service registrations
    // on DNS-SD APIs.

    VerifyOrQuit(advProxy->GetCounters().mAdvTotal == 5);
    VerifyOrQuit(advProxy->GetCounters().mAdvSuccessful == 4);
    VerifyOrQuit(advProxy->GetCounters().mAdvReplaced == 1);

    dnssdCounts.mServiceReg += 2;
    dnssdCounts.mKeyReg++;
    VerifyDnnsdRequests(dnssdCounts);

    // Invoke the key registration callback

    request = &sDnssdRegKeyRequests[sDnssdRegKeyRequests.GetLength() - 1];
    VerifyOrQuit(request->mCallback != nullptr);
    request->mCallback(sInstance, request->mId, kErrorNone);

    // Now have SRP client send a new SRP update message
    // just changing `service2`. We clear `servcie1` on client
    // so it is not included in new SRP update message.

    SuccessOrQuit(srpClient->ClearService(service1));
    SuccessOrQuit(srpClient->ClearService(service2));
    PrepareService2(service2);
    service2.mPort = 2222; // Use a different port number
    SuccessOrQuit(srpClient->AddService(service2));

    AdvanceTime(1200);

    // We should see the new Adv (total increasing) and
    // also replacing the outstanding one

    VerifyOrQuit(advProxy->GetCounters().mAdvTotal == 6);
    VerifyOrQuit(advProxy->GetCounters().mAdvSuccessful == 4);
    VerifyOrQuit(advProxy->GetCounters().mAdvReplaced == 2);

    // We should see new registration for the changed `service2`

    dnssdCounts.mServiceReg++;
    VerifyDnnsdRequests(dnssdCounts);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Invoke the callback for new registration replacing old one first");

    request = &sDnssdRegServiceRequests[5];
    VerifyOrQuit(request->mCallback != nullptr);
    request->mCallback(sInstance, request->mId, kErrorNone);

    AdvanceTime(100);

    // This should not change anything, since the new Avd should
    // be still blocked by the earlier Adv that it replaced.

    VerifyOrQuit(!sProcessedClientCallback);

    VerifyOrQuit(advProxy->GetCounters().mAdvTotal == 6);
    VerifyOrQuit(advProxy->GetCounters().mAdvSuccessful == 4);
    VerifyOrQuit(advProxy->GetCounters().mAdvReplaced == 2);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Invoke the callback for replaced Adv services");

    request = &sDnssdRegServiceRequests[4];
    VerifyOrQuit(request->mCallback != nullptr);
    request->mCallback(sInstance, request->mId, kErrorNone);

    request = &sDnssdRegServiceRequests[3];
    VerifyOrQuit(request->mCallback != nullptr);
    request->mCallback(sInstance, request->mId, kErrorNone);

    AdvanceTime(100);

    // This should trigger both Adv to complete.

    VerifyOrQuit(sProcessedClientCallback);
    VerifyOrQuit(sLastClientCallbackError == kErrorNone);

    VerifyOrQuit(advProxy->GetCounters().mAdvTotal == 6);
    VerifyOrQuit(advProxy->GetCounters().mAdvSuccessful == 6);
    VerifyOrQuit(advProxy->GetCounters().mAdvReplaced == 2);

    VerifyOrQuit(service2.GetState() == Srp::Client::kRegistered);

    // Make sure the `service2` entry on the SRP server is the
    // last (most recent) request with new port number.

    VerifyOrQuit(srpServer->GetNextHost(nullptr)->GetServices().GetHead() != nullptr);

    numServices = 0;

    for (const Srp::Server::Service &service : srpServer->GetNextHost(nullptr)->GetServices())
    {
        numServices++;

        if (StringMatch(service.GetInstanceLabel(), service2.GetInstanceName(), kStringCaseInsensitiveMatch))
        {
            VerifyOrQuit(service.GetPort() == service2.GetPort());
        }
        else if (StringMatch(service.GetInstanceLabel(), service1.GetInstanceName(), kStringCaseInsensitiveMatch))
        {
            // Service 1 was changed to have no sub-types
            VerifyOrQuit(service.GetPort() == service1.GetPort());
            VerifyOrQuit(service.GetNumberOfSubTypes() == 0);
        }
        else
        {
            VerifyOrQuit(false); // Unexpected extra service on SRP server.
        }
    }

    VerifyOrQuit(numServices == 2);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Check replacing Adv being blocked till old Adv is completed when removing services");

    // Change and re-add both services so they are both
    // included in a new SRP update message from client.

    SuccessOrQuit(srpClient->ClearService(service2));
    PrepareService1(service1);
    PrepareService2(service2);
    SuccessOrQuit(srpClient->AddService(service1));
    SuccessOrQuit(srpClient->AddService(service2));

    sProcessedClientCallback = false;

    AdvanceTime(1200);

    // We should see a new Adv with two new service registrations
    // on DNS-SD APIs.

    VerifyOrQuit(advProxy->GetCounters().mAdvTotal == 7);
    VerifyOrQuit(advProxy->GetCounters().mAdvSuccessful == 6);
    VerifyOrQuit(advProxy->GetCounters().mAdvReplaced == 2);

    dnssdCounts.mServiceReg += 2;
    VerifyDnnsdRequests(dnssdCounts);

    // Now have SRP client send a new SRP update message
    // just removing `service1`. We clear `servcie2` on client
    // so it is not included in new SRP update message.

    SuccessOrQuit(srpClient->RemoveService(service1));
    SuccessOrQuit(srpClient->ClearService(service2));

    AdvanceTime(1200);

    // We should see a new Adv added replacing the outstanding
    // one.

    VerifyOrQuit(advProxy->GetCounters().mAdvTotal == 8);
    VerifyOrQuit(advProxy->GetCounters().mAdvSuccessful == 6);
    VerifyOrQuit(advProxy->GetCounters().mAdvReplaced == 3);

    dnssdCounts.mServiceUnreg++;
    VerifyDnnsdRequests(dnssdCounts);

    // Even though the new SRP update which removed `servcie2`
    // is already unregistered, it should be blocked by the
    // earlier Adv.

    VerifyOrQuit(!sProcessedClientCallback);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Invoke the callback for replaced Adv services");

    request = &sDnssdRegServiceRequests[6];
    VerifyOrQuit(request->mCallback != nullptr);
    request->mCallback(sInstance, request->mId, kErrorNone);

    request = &sDnssdRegServiceRequests[7];
    VerifyOrQuit(request->mCallback != nullptr);
    request->mCallback(sInstance, request->mId, kErrorNone);

    AdvanceTime(100);

    // This should trigger both Adv to complete, and first one
    // should be committed before the second one removing the
    // `service2`.

    VerifyOrQuit(sProcessedClientCallback);
    VerifyOrQuit(sLastClientCallbackError == kErrorNone);

    VerifyOrQuit(advProxy->GetCounters().mAdvTotal == 8);
    VerifyOrQuit(advProxy->GetCounters().mAdvSuccessful == 8);
    VerifyOrQuit(advProxy->GetCounters().mAdvReplaced == 3);

    VerifyOrQuit(service1.GetState() == Srp::Client::kRemoved);

    // Check services on server and make sure `service2`
    // is marked as deleted.

    VerifyOrQuit(srpServer->GetNextHost(nullptr)->GetServices().GetHead() != nullptr);

    numServices = 0;

    for (const Srp::Server::Service &service : srpServer->GetNextHost(nullptr)->GetServices())
    {
        numServices++;

        if (StringMatch(service.GetInstanceLabel(), service1.GetInstanceName(), kStringCaseInsensitiveMatch))
        {
            VerifyOrQuit(service.IsDeleted());
        }
        else if (StringMatch(service.GetInstanceLabel(), service2.GetInstanceName(), kStringCaseInsensitiveMatch))
        {
            VerifyOrQuit(!service.IsDeleted());
        }
        else
        {
            VerifyOrQuit(false); // Unexpected extra service on SRP server.
        }
    }

    VerifyOrQuit(numServices == 2);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Disable SRP server");

    sDnssdShouldCheckWithClient = false;

    // Verify that all heap allocations by SRP server
    // and Advertising Proxy are freed.

    srpServer->SetEnabled(false);
    AdvanceTime(100);

    VerifyOrQuit(heapAllocations == sHeapAllocatedPtrs.GetLength());

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Finalize OT instance and validate all heap allocations are freed");

    FinalizeTest();

    VerifyOrQuit(sHeapAllocatedPtrs.IsEmpty());

    Log("End of TestSrpAdvProxyReplacedEntries");
}

void TestSrpAdvProxyHostWithOffMeshRoutableAddress(void)
{
    NetworkData::OnMeshPrefixConfig prefixConfig;
    Srp::Server                    *srpServer;
    Srp::Client                    *srpClient;
    Srp::AdvertisingProxy          *advProxy;
    Srp::Client::Service            service1;
    Srp::Client::Service            service2;
    DnssdRequestCounts              dnssdCounts;
    uint16_t                        heapAllocations;
    const DnssdRequest             *request;

    Log("--------------------------------------------------------------------------------------------");
    Log("TestSrpAdvProxyHostWithOffMeshRoutableAddress");

    InitTest();

    srpServer = &sInstance->Get<Srp::Server>();
    srpClient = &sInstance->Get<Srp::Client>();
    advProxy  = &sInstance->Get<Srp::AdvertisingProxy>();

    heapAllocations = sHeapAllocatedPtrs.GetLength();

    PrepareService1(service1);
    PrepareService2(service2);

    // Configure Dnssd platform API behavior

    sDnssdRegHostRequests.Clear();
    sDnssdRegServiceRequests.Clear();
    sDnssdUnregHostRequests.Clear();
    sDnssdUnregServiceRequests.Clear();
    sDnssdRegKeyRequests.Clear();
    sDnssdUnregKeyRequests.Clear();

    sDnssdState                 = OT_PLAT_DNSSD_READY;
    sDnssdShouldCheckWithClient = true;
    sDnssdCallbackError         = kErrorNone; // Invoke callback directly from dnssd APIs

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Start SRP server");

    SuccessOrQuit(otBorderRoutingInit(sInstance, /* aInfraIfIndex */ kInfraIfIndex, /* aInfraIfIsRunning */ true));

    SuccessOrQuit(srpServer->SetAddressMode(Srp::Server::kAddressModeUnicast));
    VerifyOrQuit(srpServer->GetAddressMode() == Srp::Server::kAddressModeUnicast);

    VerifyOrQuit(srpServer->GetState() == Srp::Server::kStateDisabled);

    srpServer->SetServiceHandler(nullptr, sInstance);

    srpServer->SetEnabled(true);
    VerifyOrQuit(srpServer->GetState() != Srp::Server::kStateDisabled);

    AdvanceTime(10000);
    VerifyOrQuit(srpServer->GetState() == Srp::Server::kStateRunning);
    VerifyOrQuit(advProxy->IsRunning());

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Start SRP client");

    srpClient->SetCallback(HandleSrpClientCallback, sInstance);
    srpClient->SetLeaseInterval(400);

    srpClient->EnableAutoStartMode(nullptr, nullptr);
    VerifyOrQuit(srpClient->IsAutoStartModeEnabled());

    AdvanceTime(2000);
    VerifyOrQuit(srpClient->IsRunning());

    SuccessOrQuit(srpClient->SetHostName(kHostName));
    SuccessOrQuit(srpClient->EnableAutoHostAddress());

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Register a service");

    SuccessOrQuit(srpClient->AddService(service1));

    sProcessedClientCallback = false;

    AdvanceTime(2 * 1000);

    dnssdCounts.mHostReg++;
    dnssdCounts.mServiceReg++;
    dnssdCounts.mKeyReg += 2;

    VerifyDnnsdRequests(dnssdCounts);
    VerifyOrQuit(sDnssdNumHostAddresses == 0);

    VerifyOrQuit(sProcessedClientCallback);
    VerifyOrQuit(sLastClientCallbackError == kErrorNone);

    VerifyOrQuit(service1.GetState() == Srp::Client::kRegistered);

    VerifyOrQuit(advProxy->GetCounters().mAdvTotal == 1);
    VerifyOrQuit(advProxy->GetCounters().mAdvSuccessful == 1);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Register a second service");

    SuccessOrQuit(srpClient->AddService(service2));

    sProcessedClientCallback = false;

    AdvanceTime(2 * 1000);

    dnssdCounts.mServiceReg++;
    dnssdCounts.mKeyReg++;
    VerifyDnnsdRequests(dnssdCounts);

    VerifyOrQuit(sProcessedClientCallback);
    VerifyOrQuit(sLastClientCallbackError == kErrorNone);

    VerifyOrQuit(service1.GetState() == Srp::Client::kRegistered);
    VerifyOrQuit(service2.GetState() == Srp::Client::kRegistered);

    VerifyOrQuit(advProxy->GetCounters().mAdvTotal == 2);
    VerifyOrQuit(advProxy->GetCounters().mAdvSuccessful == 2);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Disable SRP server");

    // Verify that all heap allocations by SRP server
    // and Advertising Proxy are freed.

    srpServer->SetEnabled(false);
    AdvanceTime(100);
    VerifyOrQuit(!advProxy->IsRunning());

    VerifyOrQuit(advProxy->GetCounters().mAdvSuccessful == advProxy->GetCounters().mAdvTotal);
    VerifyOrQuit(advProxy->GetCounters().mAdvTimeout == 0);
    VerifyOrQuit(advProxy->GetCounters().mAdvRejected == 0);
    VerifyOrQuit(advProxy->GetCounters().mAdvSkipped == 0);
    VerifyOrQuit(advProxy->GetCounters().mAdvReplaced == 0);

    VerifyOrQuit(heapAllocations == sHeapAllocatedPtrs.GetLength());

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Finalize OT instance and validate all heap allocations are freed");

    FinalizeTest();

    VerifyOrQuit(sHeapAllocatedPtrs.IsEmpty());

    Log("End of TestSrpAdvProxyHostWithOffMeshRoutableAddress");
}

void TestSrpAdvProxyRemoveBeforeCommitted(void)
{
    NetworkData::OnMeshPrefixConfig prefixConfig;
    Srp::Server                    *srpServer;
    Srp::Client                    *srpClient;
    Srp::AdvertisingProxy          *advProxy;
    Srp::Client::Service            service1;
    Srp::Client::Service            service2;
    DnssdRequestCounts              dnssdCounts;
    uint16_t                        heapAllocations;
    const DnssdRequest             *request;

    Log("--------------------------------------------------------------------------------------------");
    Log("TestSrpAdvProxyRemoveBeforeCommitted");

    InitTest();

    srpServer = &sInstance->Get<Srp::Server>();
    srpClient = &sInstance->Get<Srp::Client>();
    advProxy  = &sInstance->Get<Srp::AdvertisingProxy>();

    heapAllocations = sHeapAllocatedPtrs.GetLength();

    PrepareService1(service1);
    PrepareService2(service2);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Add an on-mesh prefix (with SLAAC) to network data");

    prefixConfig.Clear();
    SuccessOrQuit(AsCoreType(&prefixConfig.mPrefix.mPrefix).FromString("fd00:cafe:beef::"));
    prefixConfig.mPrefix.mLength = 64;
    prefixConfig.mStable         = true;
    prefixConfig.mSlaac          = true;
    prefixConfig.mPreferred      = true;
    prefixConfig.mOnMesh         = true;
    prefixConfig.mDefaultRoute   = false;
    prefixConfig.mPreference     = NetworkData::kRoutePreferenceMedium;

    SuccessOrQuit(otBorderRouterAddOnMeshPrefix(sInstance, &prefixConfig));
    SuccessOrQuit(otBorderRouterRegister(sInstance));

    // Configure Dnssd platform API behavior

    sDnssdRegHostRequests.Clear();
    sDnssdRegServiceRequests.Clear();
    sDnssdUnregHostRequests.Clear();
    sDnssdUnregServiceRequests.Clear();
    sDnssdRegKeyRequests.Clear();
    sDnssdUnregKeyRequests.Clear();

    sDnssdState                 = OT_PLAT_DNSSD_READY;
    sDnssdShouldCheckWithClient = true;
    sDnssdCallbackError         = kErrorNone; // Do not call the callbacks directly

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Start SRP server");

    SuccessOrQuit(otBorderRoutingInit(sInstance, /* aInfraIfIndex */ kInfraIfIndex, /* aInfraIfIsRunning */ true));

    SuccessOrQuit(srpServer->SetAddressMode(Srp::Server::kAddressModeUnicast));
    VerifyOrQuit(srpServer->GetAddressMode() == Srp::Server::kAddressModeUnicast);

    VerifyOrQuit(srpServer->GetState() == Srp::Server::kStateDisabled);

    srpServer->SetServiceHandler(nullptr, sInstance);

    srpServer->SetEnabled(true);
    VerifyOrQuit(srpServer->GetState() != Srp::Server::kStateDisabled);

    AdvanceTime(10000);
    VerifyOrQuit(srpServer->GetState() == Srp::Server::kStateRunning);
    VerifyOrQuit(advProxy->IsRunning());

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Start SRP client");

    srpClient->SetCallback(HandleSrpClientCallback, sInstance);

    srpClient->EnableAutoStartMode(nullptr, nullptr);
    VerifyOrQuit(srpClient->IsAutoStartModeEnabled());

    AdvanceTime(2000);
    VerifyOrQuit(srpClient->IsRunning());

    SuccessOrQuit(srpClient->SetHostName(kHostName));
    SuccessOrQuit(srpClient->EnableAutoHostAddress());

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Register host and one service");

    SuccessOrQuit(srpClient->AddService(service1));

    sProcessedClientCallback = false;

    AdvanceTime(2000);

    dnssdCounts.mHostReg++;
    dnssdCounts.mServiceReg++;
    dnssdCounts.mKeyReg += 2;
    VerifyDnnsdRequests(dnssdCounts);

    VerifyOrQuit(advProxy->GetCounters().mAdvTotal == 1);
    VerifyOrQuit(advProxy->GetCounters().mAdvSuccessful == 1);
    VerifyOrQuit(advProxy->GetCounters().mAdvReplaced == 0);

    VerifyOrQuit(sProcessedClientCallback);
    VerifyOrQuit(sLastClientCallbackError == kErrorNone);

    VerifyOrQuit(service1.GetState() == Srp::Client::kRegistered);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Set AdvTimeout to 5 minutes on AdvProxy");

    // Change the timeout on AvdertisingProxy to 5 minutes
    // so that we can send multiple SRP updates and create
    // situations where previous advertisement are replaced.

    advProxy->SetAdvTimeout(5 * 60 * 1000);
    VerifyOrQuit(advProxy->GetAdvTimeout() == 5 * 60 * 1000);

    sDnssdCallbackError = kErrorPending; // Do not invoke callback

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Remove service1 while adding a new service2 and do not invoke callback from DNSSD plat");

    SuccessOrQuit(srpClient->RemoveService(service1));
    SuccessOrQuit(srpClient->AddService(service2));

    sProcessedClientCallback = false;

    AdvanceTime(1000);

    dnssdCounts.mServiceReg++;
    dnssdCounts.mServiceUnreg++;
    dnssdCounts.mKeyReg++;
    VerifyDnnsdRequests(dnssdCounts);

    VerifyOrQuit(advProxy->GetCounters().mAdvTotal == 2);
    VerifyOrQuit(advProxy->GetCounters().mAdvSuccessful == 1);
    VerifyOrQuit(advProxy->GetCounters().mAdvReplaced == 0);

    VerifyOrQuit(!sProcessedClientCallback);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Remove host and its services without removing key-lease");

    SuccessOrQuit(srpClient->RemoveHostAndServices(/* aShouldRemoveKeyLease */ false));

    AdvanceTime(1000);

    // Proxy will unregister both services again
    // (to be safe).

    dnssdCounts.mHostUnreg++;
    dnssdCounts.mServiceUnreg++;
    VerifyDnnsdRequests(dnssdCounts, /* aAllowMoreUnregs */ true);

    VerifyOrQuit(advProxy->GetCounters().mAdvTotal == 3);
    VerifyOrQuit(advProxy->GetCounters().mAdvSuccessful == 1);
    VerifyOrQuit(advProxy->GetCounters().mAdvReplaced == 1);

    VerifyOrQuit(!sProcessedClientCallback);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Invoke callback for last key registration");

    // This should be enough for all `AdvInfo` entries to be finished.

    request = &sDnssdRegKeyRequests[sDnssdRegKeyRequests.GetLength() - 1];
    VerifyOrQuit(request->mCallback != nullptr);
    request->mCallback(sInstance, request->mId, kErrorNone);

    AdvanceTime(50);

    VerifyOrQuit(advProxy->GetCounters().mAdvTotal == 3);
    VerifyOrQuit(advProxy->GetCounters().mAdvSuccessful == 3);
    VerifyOrQuit(advProxy->GetCounters().mAdvReplaced == 1);

    VerifyOrQuit(sProcessedClientCallback);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Disable SRP server");

    sDnssdShouldCheckWithClient = false;

    // Verify that all heap allocations by SRP server
    // and Advertising Proxy are freed.

    srpServer->SetEnabled(false);
    AdvanceTime(100);

    VerifyOrQuit(heapAllocations == sHeapAllocatedPtrs.GetLength());

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Finalize OT instance and validate all heap allocations are freed");

    FinalizeTest();

    VerifyOrQuit(sHeapAllocatedPtrs.IsEmpty());

    Log("End of TestSrpAdvProxyRemoveBeforeCommitted");
}

void TestSrpAdvProxyFullyRemoveBeforeCommitted(void)
{
    NetworkData::OnMeshPrefixConfig prefixConfig;
    Srp::Server                    *srpServer;
    Srp::Client                    *srpClient;
    Srp::AdvertisingProxy          *advProxy;
    Srp::Client::Service            service1;
    Srp::Client::Service            service2;
    DnssdRequestCounts              dnssdCounts;
    uint16_t                        heapAllocations;
    const DnssdRequest             *request;

    Log("--------------------------------------------------------------------------------------------");
    Log("TestSrpAdvProxyFullyRemoveBeforeCommitted");

    InitTest();

    srpServer = &sInstance->Get<Srp::Server>();
    srpClient = &sInstance->Get<Srp::Client>();
    advProxy  = &sInstance->Get<Srp::AdvertisingProxy>();

    heapAllocations = sHeapAllocatedPtrs.GetLength();

    PrepareService1(service1);
    PrepareService2(service2);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Add an on-mesh prefix (with SLAAC) to network data");

    prefixConfig.Clear();
    SuccessOrQuit(AsCoreType(&prefixConfig.mPrefix.mPrefix).FromString("fd00:cafe:beef::"));
    prefixConfig.mPrefix.mLength = 64;
    prefixConfig.mStable         = true;
    prefixConfig.mSlaac          = true;
    prefixConfig.mPreferred      = true;
    prefixConfig.mOnMesh         = true;
    prefixConfig.mDefaultRoute   = false;
    prefixConfig.mPreference     = NetworkData::kRoutePreferenceMedium;

    SuccessOrQuit(otBorderRouterAddOnMeshPrefix(sInstance, &prefixConfig));
    SuccessOrQuit(otBorderRouterRegister(sInstance));

    // Configure Dnssd platform API behavior

    sDnssdRegHostRequests.Clear();
    sDnssdRegServiceRequests.Clear();
    sDnssdUnregHostRequests.Clear();
    sDnssdUnregServiceRequests.Clear();
    sDnssdRegKeyRequests.Clear();
    sDnssdUnregKeyRequests.Clear();

    sDnssdState                 = OT_PLAT_DNSSD_READY;
    sDnssdShouldCheckWithClient = true;
    sDnssdCallbackError         = kErrorNone; // Do not call the callbacks directly

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Start SRP server");

    SuccessOrQuit(otBorderRoutingInit(sInstance, /* aInfraIfIndex */ kInfraIfIndex, /* aInfraIfIsRunning */ true));

    SuccessOrQuit(srpServer->SetAddressMode(Srp::Server::kAddressModeUnicast));
    VerifyOrQuit(srpServer->GetAddressMode() == Srp::Server::kAddressModeUnicast);

    VerifyOrQuit(srpServer->GetState() == Srp::Server::kStateDisabled);

    srpServer->SetServiceHandler(nullptr, sInstance);

    srpServer->SetEnabled(true);
    VerifyOrQuit(srpServer->GetState() != Srp::Server::kStateDisabled);

    AdvanceTime(10000);
    VerifyOrQuit(srpServer->GetState() == Srp::Server::kStateRunning);
    VerifyOrQuit(advProxy->IsRunning());

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Start SRP client");

    srpClient->SetCallback(HandleSrpClientCallback, sInstance);

    srpClient->EnableAutoStartMode(nullptr, nullptr);
    VerifyOrQuit(srpClient->IsAutoStartModeEnabled());

    AdvanceTime(2000);
    VerifyOrQuit(srpClient->IsRunning());

    SuccessOrQuit(srpClient->SetHostName(kHostName));
    SuccessOrQuit(srpClient->EnableAutoHostAddress());

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Register host and one service");

    SuccessOrQuit(srpClient->AddService(service1));

    sProcessedClientCallback = false;

    AdvanceTime(2000);

    dnssdCounts.mHostReg++;
    dnssdCounts.mServiceReg++;
    dnssdCounts.mKeyReg += 2;
    VerifyDnnsdRequests(dnssdCounts);

    VerifyOrQuit(advProxy->GetCounters().mAdvTotal == 1);
    VerifyOrQuit(advProxy->GetCounters().mAdvSuccessful == 1);
    VerifyOrQuit(advProxy->GetCounters().mAdvReplaced == 0);

    VerifyOrQuit(sProcessedClientCallback);
    VerifyOrQuit(sLastClientCallbackError == kErrorNone);

    VerifyOrQuit(service1.GetState() == Srp::Client::kRegistered);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Set AdvTimeout to 5 minutes on AdvProxy");

    // Change the timeout on AvdertisingProxy to 5 minutes
    // so that we can send multiple SRP updates and create
    // situations where previous advertisement are replaced.

    advProxy->SetAdvTimeout(5 * 60 * 1000);
    VerifyOrQuit(advProxy->GetAdvTimeout() == 5 * 60 * 1000);

    sDnssdCallbackError = kErrorPending; // Do not invoke callback

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Remove service1 while adding a new service2 and do not invoke callback from DNSSD plat");

    SuccessOrQuit(srpClient->RemoveService(service1));
    SuccessOrQuit(srpClient->AddService(service2));

    sProcessedClientCallback = false;

    AdvanceTime(1000);

    dnssdCounts.mServiceReg++;
    dnssdCounts.mServiceUnreg++;
    dnssdCounts.mKeyReg++;
    VerifyDnnsdRequests(dnssdCounts);

    VerifyOrQuit(advProxy->GetCounters().mAdvTotal == 2);
    VerifyOrQuit(advProxy->GetCounters().mAdvSuccessful == 1);
    VerifyOrQuit(advProxy->GetCounters().mAdvReplaced == 0);

    VerifyOrQuit(!sProcessedClientCallback);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Remove host and its services and remove key-lease");

    SuccessOrQuit(srpClient->RemoveHostAndServices(/* aShouldRemoveKeyLease */ true));

    AdvanceTime(1000);

    // Proxy should unregister everything.
    //  Keys may be unregistered multiple times.

    dnssdCounts.mHostUnreg++;
    dnssdCounts.mServiceUnreg++;
    dnssdCounts.mKeyUnreg += 3;
    VerifyDnnsdRequests(dnssdCounts, /* aAllowMoreUnregs */ true);

    VerifyOrQuit(advProxy->GetCounters().mAdvTotal == 3);
    VerifyOrQuit(advProxy->GetCounters().mAdvSuccessful == 3);
    VerifyOrQuit(advProxy->GetCounters().mAdvReplaced == 1);

    VerifyOrQuit(sProcessedClientCallback);

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Disable SRP server");

    sDnssdShouldCheckWithClient = false;

    // Verify that all heap allocations by SRP server
    // and Advertising Proxy are freed.

    srpServer->SetEnabled(false);
    AdvanceTime(100);

    VerifyOrQuit(heapAllocations == sHeapAllocatedPtrs.GetLength());

    Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
    Log("Finalize OT instance and validate all heap allocations are freed");

    FinalizeTest();

    VerifyOrQuit(sHeapAllocatedPtrs.IsEmpty());

    Log("End of TestSrpAdvProxyFullyRemoveBeforeCommitted");
}

#endif // ENABLE_ADV_PROXY_TEST

int main(void)
{
#if ENABLE_ADV_PROXY_TEST
    TestDnssdRequestIdRange();
    TestSrpAdvProxy();
    TestSrpAdvProxyDnssdStateChange();
    TestSrpAdvProxyDelayedCallback();
    TestSrpAdvProxyReplacedEntries();
    TestSrpAdvProxyHostWithOffMeshRoutableAddress();
    TestSrpAdvProxyRemoveBeforeCommitted();
    TestSrpAdvProxyFullyRemoveBeforeCommitted();

    printf("All tests passed\n");
#else
    printf("SRP_ADV_PROXY feature is not enabled\n");
#endif

    return 0;
}
