/*
 *  Copyright (c) 2022, 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_TIME_SYNC_ENABLE && !OPENTHREAD_PLATFORM_POSIX
#define ENABLE_SRP_TEST 1
#else
#define ENABLE_SRP_TEST 0
#endif

namespace ot {

#if ENABLE_SRP_TEST

// 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 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; }

//----------------------------------------------------------------------------------------------------------------------
// `otPlatAlaram`

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; }

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

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;

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

    otOperationalDataset     dataset;
    otOperationalDatasetTlvs datasetTlvs;

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

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

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // 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);
}

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

enum UpdateHandlerMode
{
    kAccept, // Accept all updates.
    kReject, // Reject all updates.
    kIgnore  // Ignore all updates (do not call `otSrpServerHandleServiceUpdateResult()`).
};

static UpdateHandlerMode    sUpdateHandlerMode       = kAccept;
static bool                 sProcessedUpdateCallback = false;
static otSrpServerLeaseInfo sUpdateHostLeaseInfo;
static uint32_t             sUpdateHostKeyLease;

void HandleSrpServerUpdate(otSrpServerServiceUpdateId aId,
                           const otSrpServerHost     *aHost,
                           uint32_t                   aTimeout,
                           void                      *aContext)
{
    Log("HandleSrpServerUpdate() called with %u, timeout:%u", aId, aTimeout);

    VerifyOrQuit(aHost != nullptr);
    VerifyOrQuit(aContext == sInstance);

    sProcessedUpdateCallback = true;

    otSrpServerHostGetLeaseInfo(aHost, &sUpdateHostLeaseInfo);

    switch (sUpdateHandlerMode)
    {
    case kAccept:
        otSrpServerHandleServiceUpdateResult(sInstance, aId, kErrorNone);
        break;
    case kReject:
        otSrpServerHandleServiceUpdateResult(sInstance, aId, kErrorFailed);
        break;
    case kIgnore:
        break;
    }
}

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[] = "myhost";

void PrepareService1(Srp::Client::Service &aService)
{
    static const char          kServiceName[]   = "_srv._udp";
    static const char          kInstanceLabel[] = "srv.instance";
    static const char          kSub1[]          = "_sub1";
    static const char          kSub2[]          = "_V1234567";
    static const char          kSub3[]          = "_XYZWS";
    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[]   = "_00112233667882554._matter._udp";
    static const char  kInstance2Label[] = "ABCDEFGHI";
    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;
}

void ValidateHost(Srp::Server &aServer, const char *aHostName)
{
    // Validate that only a host with `aHostName` is
    // registered on SRP server.

    const Srp::Server::Host *host;
    const char              *name;

    Log("ValidateHost()");

    host = aServer.GetNextHost(nullptr);
    VerifyOrQuit(host != nullptr);

    name = host->GetFullName();
    Log("Hostname: %s", name);

    VerifyOrQuit(StringStartsWith(name, aHostName, kStringCaseInsensitiveMatch));
    VerifyOrQuit(name[strlen(aHostName)] == '.');

    // Only one host on server
    VerifyOrQuit(aServer.GetNextHost(host) == nullptr);
}

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

void TestSrpServerBase(void)
{
    Srp::Server         *srpServer;
    Srp::Client         *srpClient;
    Srp::Client::Service service1;
    Srp::Client::Service service2;
    uint16_t             heapAllocations;

    Log("--------------------------------------------------------------------------------------------");
    Log("TestSrpServerBase");

    InitTest();

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

    heapAllocations = sHeapAllocatedPtrs.GetLength();

    PrepareService1(service1);
    PrepareService2(service2);

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Start SRP server.

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

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

    srpServer->SetServiceHandler(HandleSrpServerUpdate, sInstance);

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

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

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // 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());

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Register a service, validate that update handler is called.

    SuccessOrQuit(srpClient->AddService(service1));

    sUpdateHandlerMode       = kAccept;
    sProcessedUpdateCallback = false;
    sProcessedClientCallback = false;

    AdvanceTime(2 * 1000);

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

    VerifyOrQuit(service1.GetState() == Srp::Client::kRegistered);
    ValidateHost(*srpServer, kHostName);

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Register a second service, validate that update handler is called.

    SuccessOrQuit(srpClient->AddService(service2));

    sProcessedUpdateCallback = false;
    sProcessedClientCallback = false;

    AdvanceTime(2 * 1000);

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

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

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Unregister first service, validate that update handler is called.

    SuccessOrQuit(srpClient->RemoveService(service1));

    sProcessedUpdateCallback = false;
    sProcessedClientCallback = false;

    AdvanceTime(2 * 1000);

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

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

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Disable SRP server, verify that all heap allocations by SRP server
    // are freed.

    Log("Disabling SRP server");

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

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

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Finalize OT instance and validate all heap allocations are freed.

    Log("Finalizing OT instance");
    FinalizeTest();

    VerifyOrQuit(sHeapAllocatedPtrs.IsEmpty());

    Log("End of TestSrpServerBase");
}

void TestSrpServerReject(void)
{
    Srp::Server         *srpServer;
    Srp::Client         *srpClient;
    Srp::Client::Service service1;
    Srp::Client::Service service2;
    uint16_t             heapAllocations;

    Log("--------------------------------------------------------------------------------------------");
    Log("TestSrpServerReject");

    InitTest();

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

    heapAllocations = sHeapAllocatedPtrs.GetLength();

    PrepareService1(service1);
    PrepareService2(service2);

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Start SRP server.

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

    srpServer->SetServiceHandler(HandleSrpServerUpdate, sInstance);

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

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

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Start SRP client.

    srpClient->SetCallback(HandleSrpClientCallback, sInstance);

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

    AdvanceTime(15 * 1000);
    VerifyOrQuit(srpClient->IsRunning());

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

    sUpdateHandlerMode = kReject;

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Register a service, validate that update handler is called
    // and rejected and no service is registered.

    SuccessOrQuit(srpClient->AddService(service1));

    sProcessedUpdateCallback = false;
    sProcessedClientCallback = false;

    AdvanceTime(2 * 1000);

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

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

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

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Register a second service, validate that update handler is
    // again called and update is rejected.

    SuccessOrQuit(srpClient->AddService(service2));

    sProcessedUpdateCallback = false;
    sProcessedClientCallback = false;

    AdvanceTime(2 * 1000);

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

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

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

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Disable SRP server, verify that all heap allocations by SRP server
    // are freed.

    Log("Disabling SRP server");

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

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

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Finalize OT instance and validate all heap allocations are freed.

    Log("Finalizing OT instance");
    FinalizeTest();

    VerifyOrQuit(sHeapAllocatedPtrs.IsEmpty());

    Log("End of TestSrpServerReject");
}

void TestSrpServerIgnore(void)
{
    Srp::Server         *srpServer;
    Srp::Client         *srpClient;
    Srp::Client::Service service1;
    Srp::Client::Service service2;
    uint16_t             heapAllocations;

    Log("--------------------------------------------------------------------------------------------");
    Log("TestSrpServerIgnore");

    InitTest();

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

    heapAllocations = sHeapAllocatedPtrs.GetLength();

    PrepareService1(service1);
    PrepareService2(service2);

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Start SRP server.

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

    srpServer->SetServiceHandler(HandleSrpServerUpdate, sInstance);

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

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

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Start SRP client.

    srpClient->SetCallback(HandleSrpClientCallback, sInstance);

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

    AdvanceTime(15 * 1000);
    VerifyOrQuit(srpClient->IsRunning());

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

    sUpdateHandlerMode = kIgnore;

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Register a service, validate that update handler is called
    // and ignored the update and no service is registered.

    SuccessOrQuit(srpClient->AddService(service1));

    sProcessedUpdateCallback = false;
    sProcessedClientCallback = false;

    AdvanceTime(2 * 1000);

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

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

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

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Register a second service, validate that update handler is
    // again called and update is still ignored.

    SuccessOrQuit(srpClient->AddService(service2));

    sProcessedUpdateCallback = false;
    sProcessedClientCallback = false;

    AdvanceTime(2 * 1000);

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

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

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

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Disable SRP server, verify that all heap allocations by SRP server
    // are freed.

    Log("Disabling SRP server");

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

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

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Finalize OT instance and validate all heap allocations are freed.

    Log("Finalizing OT instance");
    FinalizeTest();

    VerifyOrQuit(sHeapAllocatedPtrs.IsEmpty());

    Log("End of TestSrpServerIgnore");
}

void TestSrpServerClientRemove(bool aShouldRemoveKeyLease)
{
    Srp::Server         *srpServer;
    Srp::Client         *srpClient;
    Srp::Client::Service service1;
    Srp::Client::Service service2;
    uint16_t             heapAllocations;

    Log("--------------------------------------------------------------------------------------------");
    Log("TestSrpServerClientRemove(aShouldRemoveKeyLease:%u)", aShouldRemoveKeyLease);

    InitTest();

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

    heapAllocations = sHeapAllocatedPtrs.GetLength();

    PrepareService1(service1);
    PrepareService2(service2);

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Start SRP server.

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

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

    srpServer->SetServiceHandler(HandleSrpServerUpdate, sInstance);

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

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

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Start SRP client.

    srpClient->SetCallback(HandleSrpClientCallback, sInstance);

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

    AdvanceTime(15 * 1000);
    VerifyOrQuit(srpClient->IsRunning());

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

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Register two services, validate that update handler is called.

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

    sUpdateHandlerMode       = kAccept;
    sProcessedUpdateCallback = false;
    sProcessedClientCallback = false;

    AdvanceTime(2 * 1000);

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

    VerifyOrQuit(service1.GetState() == Srp::Client::kRegistered);
    VerifyOrQuit(service2.GetState() == Srp::Client::kRegistered);
    ValidateHost(*srpServer, kHostName);

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Remove two services and clear key-lease, validate that update handler is called.

    SuccessOrQuit(srpClient->RemoveHostAndServices(aShouldRemoveKeyLease));

    AdvanceTime(2 * 1000);

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

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

    if (aShouldRemoveKeyLease)
    {
        VerifyOrQuit(srpServer->GetNextHost(nullptr) == nullptr);
    }
    else
    {
        ValidateHost(*srpServer, kHostName);
    }

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Disable SRP server, verify that all heap allocations by SRP server
    // are freed.

    Log("Disabling SRP server");

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

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

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Finalize OT instance and validate all heap allocations are freed.

    Log("Finalizing OT instance");
    FinalizeTest();

    VerifyOrQuit(sHeapAllocatedPtrs.IsEmpty());

    Log("End of TestSrpServerClientRemove");
}

#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
void TestUpdateLeaseShortVariant(void)
{
    // Test behavior of SRP client and server when short variant of
    // Update Lease Option is used (which only include lease interval).
    // This test uses `SetUseShortLeaseOption()` method of `Srp::Client`
    // which changes the default behavior and is available under the
    // `REFERENCE_DEVICE` config.

    Srp::Server                *srpServer;
    Srp::Server::LeaseConfig    leaseConfig;
    const Srp::Server::Service *service;
    Srp::Client                *srpClient;
    Srp::Client::Service        service1;
    uint16_t                    heapAllocations;

    Log("--------------------------------------------------------------------------------------------");
    Log("TestUpdateLeaseShortVariant");

    InitTest();

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

    heapAllocations = sHeapAllocatedPtrs.GetLength();

    PrepareService1(service1);

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Start SRP server.

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

    srpServer->SetServiceHandler(HandleSrpServerUpdate, sInstance);

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

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

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Check the default Lease Config on SRP server.
    // Server to accept lease in [30 sec, 27 hours] and
    // key-lease in [30 sec, 189 hours].

    srpServer->GetLeaseConfig(leaseConfig);

    VerifyOrQuit(leaseConfig.mMinLease == 30);             // 30 seconds
    VerifyOrQuit(leaseConfig.mMaxLease == 27u * 3600);     // 27 hours
    VerifyOrQuit(leaseConfig.mMinKeyLease == 30);          // 30 seconds
    VerifyOrQuit(leaseConfig.mMaxKeyLease == 189u * 3600); // 189 hours

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Start SRP client.

    srpClient->SetCallback(HandleSrpClientCallback, sInstance);

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

    AdvanceTime(15 * 1000);
    VerifyOrQuit(srpClient->IsRunning());

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

    sUpdateHandlerMode = kAccept;

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Change default lease intervals on SRP client and enable
    // "use short Update Lease Option" mode.

    srpClient->SetLeaseInterval(15u * 3600);
    srpClient->SetKeyLeaseInterval(40u * 3600);

    srpClient->SetUseShortLeaseOption(true);

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Register a service, validate that update handler is called
    // and service is successfully registered.

    SuccessOrQuit(srpClient->AddService(service1));

    sProcessedUpdateCallback = false;
    sProcessedClientCallback = false;

    AdvanceTime(2 * 1000);

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

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

    ValidateHost(*srpServer, kHostName);

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Validate the lease info for service on SRP server. The client
    // is set up to use "short Update Lease Option format, so it only
    // include the lease interval as 15 hours in its request
    // message. Server should then see 15 hours for both lease and
    // key lease

    VerifyOrQuit(sUpdateHostLeaseInfo.mLease == 15u * 3600 * 1000);
    VerifyOrQuit(sUpdateHostLeaseInfo.mKeyLease == 15u * 3600 * 1000);

    // Check that SRP server granted 15 hours for both lease and
    // key lease.

    service = srpServer->GetNextHost(nullptr)->GetServices().GetHead();
    VerifyOrQuit(service != nullptr);
    VerifyOrQuit(service->GetLease() == 15u * 3600);
    VerifyOrQuit(service->GetKeyLease() == 15u * 3600);

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Remove the service.

    SuccessOrQuit(srpClient->RemoveService(service1));

    sProcessedUpdateCallback = false;
    sProcessedClientCallback = false;

    AdvanceTime(2 * 1000);

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

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

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Register the service again, but this time change it to request
    // a lease time that is larger than the `LeaseConfig.mMinLease` of
    // 27 hours. This ensures that server needs to include the Lease
    // Option in its response (since it need to grant a different
    // lease interval).

    service1.mLease    = 100u * 3600; // 100 hours >= 27 hours.
    service1.mKeyLease = 110u * 3600;

    SuccessOrQuit(srpClient->AddService(service1));

    sProcessedUpdateCallback = false;
    sProcessedClientCallback = false;

    AdvanceTime(2 * 1000);

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

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

    ValidateHost(*srpServer, kHostName);

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Validate the lease info for service on SRP server.

    // We should see the 100 hours in request from client
    VerifyOrQuit(sUpdateHostLeaseInfo.mLease == 100u * 3600 * 1000);
    VerifyOrQuit(sUpdateHostLeaseInfo.mKeyLease == 100u * 3600 * 1000);

    // Check that SRP server granted  27 hours for both lease and
    // key lease.

    service = srpServer->GetNextHost(nullptr)->GetServices().GetHead();
    VerifyOrQuit(service != nullptr);
    VerifyOrQuit(service->GetLease() == 27u * 3600);
    VerifyOrQuit(service->GetKeyLease() == 27u * 3600);

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Disable SRP server, verify that all heap allocations by SRP server
    // are freed.

    Log("Disabling SRP server");

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

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

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Finalize OT instance and validate all heap allocations are freed.

    Log("Finalizing OT instance");
    FinalizeTest();

    VerifyOrQuit(sHeapAllocatedPtrs.IsEmpty());

    Log("End of TestUpdateLeaseShortVariant");
}

static uint16_t         sServerRxCount;
static Ip6::MessageInfo sServerMsgInfo;
static uint16_t         sServerLastMsgId;

void HandleServerUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo)
{
    Dns::Header header;

    VerifyOrQuit(aContext == nullptr);
    VerifyOrQuit(aMessage != nullptr);
    VerifyOrQuit(aMessageInfo != nullptr);

    SuccessOrQuit(AsCoreType(aMessage).Read(0, header));

    sServerMsgInfo   = AsCoreType(aMessageInfo);
    sServerLastMsgId = header.GetMessageId();
    sServerRxCount++;

    Log("HandleServerUdpReceive(), message-id: 0x%x", header.GetMessageId());
}

void TestSrpClientDelayedResponse(void)
{
    static constexpr uint16_t kServerPort = 53535;

    Srp::Client         *srpClient;
    Srp::Client::Service service1;
    Srp::Client::Service service2;

    Log("--------------------------------------------------------------------------------------------");
    Log("TestSrpClientDelayedResponse");

    InitTest();

    srpClient = &sInstance->Get<Srp::Client>();

    for (uint8_t testIter = 0; testIter < 3; testIter++)
    {
        Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
        Log("testIter = %u", testIter);

        //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        // Prepare a socket to act as SRP server.

        Ip6::Udp::Socket  udpSocket(*sInstance, HandleServerUdpReceive, nullptr);
        Ip6::SockAddr     serverSockAddr;
        uint16_t          firstMsgId;
        Message          *response;
        Dns::UpdateHeader header;

        sServerRxCount = 0;

        SuccessOrQuit(udpSocket.Open());
        SuccessOrQuit(udpSocket.Bind(kServerPort, Ip6::kNetifThread));

        //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        // Manually start the client with a message ID based on `testIter`
        // We use zero in the first iteration, `0xffff` in the second
        // iteration to test wrapping of 16-bit message ID.

        switch (testIter)
        {
        case 0:
            srpClient->SetNextMessageId(0);
            break;
        case 1:
            srpClient->SetNextMessageId(0xffff);
            break;
        case 2:
            srpClient->SetNextMessageId(0xaaaa);
            break;
        }

        serverSockAddr.SetAddress(sInstance->Get<Mle::Mle>().GetMeshLocalRloc());
        serverSockAddr.SetPort(kServerPort);
        SuccessOrQuit(srpClient->Start(serverSockAddr));

        //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        // Register a service

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

        PrepareService1(service1);
        SuccessOrQuit(srpClient->AddService(service1));

        //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        // Wait for short time and make sure server receives an SRP
        // update message from client.

        AdvanceTime(1 * 1000);

        VerifyOrQuit(sServerRxCount == 1);
        firstMsgId = sServerLastMsgId;

        switch (testIter)
        {
        case 0:
            VerifyOrQuit(firstMsgId == 0);
            break;
        case 1:
            VerifyOrQuit(firstMsgId == 0xffff);
            break;
        case 2:
            VerifyOrQuit(firstMsgId == 0xaaaa);
            break;
        }

        if (testIter == 2)
        {
            AdvanceTime(2 * 1000);

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

        //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        // Wait for longer to allow client to retry a bunch of times

        AdvanceTime(20 * 1000);
        VerifyOrQuit(sServerRxCount > 1);
        VerifyOrQuit(sServerLastMsgId != firstMsgId);

        VerifyOrQuit(srpClient->GetHostInfo().GetState() != Srp::Client::kRegistered);
        VerifyOrQuit(service1.GetState() != Srp::Client::kRegistered);

        //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        // Now send a delayed response from server using the first
        // message ID.

        response = udpSocket.NewMessage();
        VerifyOrQuit(response != nullptr);

        Log("Sending response with msg-id: 0x%x", firstMsgId);

        header.SetMessageId(firstMsgId);
        header.SetType(Dns::UpdateHeader::kTypeResponse);
        header.SetResponseCode(Dns::UpdateHeader::kResponseSuccess);
        SuccessOrQuit(response->Append(header));
        SuccessOrQuit(udpSocket.SendTo(*response, sServerMsgInfo));

        AdvanceTime(10);

        //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        // In the first two iterations, we ensure that client
        // did successfully accept the response with older message ID.
        // This should not be the case in the third iteration due to
        // changes to client services after first UPdate message was
        // sent by client.

        switch (testIter)
        {
        case 0:
        case 1:
            VerifyOrQuit(srpClient->GetHostInfo().GetState() == Srp::Client::kRegistered);
            VerifyOrQuit(service1.GetState() == Srp::Client::kRegistered);
            break;
        case 2:
            VerifyOrQuit(srpClient->GetHostInfo().GetState() != Srp::Client::kRegistered);
            VerifyOrQuit(service1.GetState() != Srp::Client::kRegistered);
            break;
        }

        //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        // Remove service and close socket.

        srpClient->ClearHostAndServices();
        srpClient->Stop();

        SuccessOrQuit(udpSocket.Close());
    }

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Finalize OT instance

    Log("Finalizing OT instance");
    FinalizeTest();

    Log("End of TestSrpClientDelayedResponse");
}

#endif // OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE

#endif // ENABLE_SRP_TEST

} // namespace ot

int main(void)
{
#if ENABLE_SRP_TEST
    ot::TestSrpServerBase();
    ot::TestSrpServerReject();
    ot::TestSrpServerIgnore();
    ot::TestSrpServerClientRemove(/* aShouldRemoveKeyLease */ true);
    ot::TestSrpServerClientRemove(/* aShouldRemoveKeyLease */ false);
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
    ot::TestUpdateLeaseShortVariant();
    ot::TestSrpClientDelayedResponse();
#endif

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

    return 0;
}
