/*
 *  Copyright (c) 2020, 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.
 */

/**
 * @file
 *   This file includes implementation for SRP server.
 */

#include "srp_server.hpp"

#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE

#include "common/as_core_type.hpp"
#include "common/const_cast.hpp"
#include "common/instance.hpp"
#include "common/locator_getters.hpp"
#include "common/log.hpp"
#include "common/new.hpp"
#include "common/random.hpp"
#include "net/dns_types.hpp"
#include "thread/thread_netif.hpp"

namespace ot {
namespace Srp {

RegisterLogModule("SrpServer");

static const char kDefaultDomain[]       = "default.service.arpa.";
static const char kServiceSubTypeLabel[] = "._sub.";

static Dns::UpdateHeader::Response ErrorToDnsResponseCode(Error aError)
{
    Dns::UpdateHeader::Response responseCode;

    switch (aError)
    {
    case kErrorNone:
        responseCode = Dns::UpdateHeader::kResponseSuccess;
        break;
    case kErrorNoBufs:
        responseCode = Dns::UpdateHeader::kResponseServerFailure;
        break;
    case kErrorParse:
        responseCode = Dns::UpdateHeader::kResponseFormatError;
        break;
    case kErrorDuplicated:
        responseCode = Dns::UpdateHeader::kResponseNameExists;
        break;
    default:
        responseCode = Dns::UpdateHeader::kResponseRefused;
        break;
    }

    return responseCode;
}

//---------------------------------------------------------------------------------------------------------------------
// Server

Server::Server(Instance &aInstance)
    : InstanceLocator(aInstance)
    , mSocket(aInstance)
    , mServiceUpdateHandler(nullptr)
    , mServiceUpdateHandlerContext(nullptr)
    , mLeaseTimer(aInstance, HandleLeaseTimer)
    , mOutstandingUpdatesTimer(aInstance, HandleOutstandingUpdatesTimer)
    , mServiceUpdateId(Random::NonCrypto::GetUint32())
    , mPort(kUdpPortMin)
    , mState(kStateDisabled)
    , mAddressMode(kDefaultAddressMode)
    , mAnycastSequenceNumber(0)
    , mHasRegisteredAnyService(false)
{
    IgnoreError(SetDomain(kDefaultDomain));
}

void Server::SetServiceHandler(otSrpServerServiceUpdateHandler aServiceHandler, void *aServiceHandlerContext)
{
    mServiceUpdateHandler        = aServiceHandler;
    mServiceUpdateHandlerContext = aServiceHandlerContext;
}

Error Server::SetAddressMode(AddressMode aMode)
{
    Error error = kErrorNone;

    VerifyOrExit(mState == kStateDisabled, error = kErrorInvalidState);
    VerifyOrExit(mAddressMode != aMode);
    LogInfo("Address Mode: %s -> %s", AddressModeToString(mAddressMode), AddressModeToString(aMode));
    mAddressMode = aMode;

exit:
    return error;
}

Error Server::SetAnycastModeSequenceNumber(uint8_t aSequenceNumber)
{
    Error error = kErrorNone;

    VerifyOrExit(mState == kStateDisabled, error = kErrorInvalidState);
    mAnycastSequenceNumber = aSequenceNumber;

    LogInfo("Set Anycast Address Mode Seq Number to %d", aSequenceNumber);

exit:
    return error;
}

void Server::SetEnabled(bool aEnabled)
{
    if (aEnabled)
    {
        VerifyOrExit(mState == kStateDisabled);
        mState = kStateStopped;

        // Request publishing of "DNS/SRP Address Service" entry in the
        // Thread Network Data based of `mAddressMode`. Then wait for
        // callback `HandleNetDataPublisherEntryChange()` from the
        // `Publisher` to start the SRP server.

        switch (mAddressMode)
        {
        case kAddressModeUnicast:
            SelectPort();
            Get<NetworkData::Publisher>().PublishDnsSrpServiceUnicast(mPort);
            break;

        case kAddressModeAnycast:
            mPort = kAnycastAddressModePort;
            Get<NetworkData::Publisher>().PublishDnsSrpServiceAnycast(mAnycastSequenceNumber);
            break;
        }
    }
    else
    {
        VerifyOrExit(mState != kStateDisabled);
        Get<NetworkData::Publisher>().UnpublishDnsSrpService();
        Stop();
        mState = kStateDisabled;
    }

exit:
    return;
}

Server::TtlConfig::TtlConfig(void)
{
    mMinTtl = kDefaultMinTtl;
    mMaxTtl = kDefaultMaxTtl;
}

Error Server::SetTtlConfig(const TtlConfig &aTtlConfig)
{
    Error error = kErrorNone;

    VerifyOrExit(aTtlConfig.IsValid(), error = kErrorInvalidArgs);
    mTtlConfig = aTtlConfig;

exit:
    return error;
}

uint32_t Server::TtlConfig::GrantTtl(uint32_t aLease, uint32_t aTtl) const
{
    OT_ASSERT(mMinTtl <= mMaxTtl);

    return OT_MAX(mMinTtl, OT_MIN(OT_MIN(mMaxTtl, aLease), aTtl));
}

Server::LeaseConfig::LeaseConfig(void)
{
    mMinLease    = kDefaultMinLease;
    mMaxLease    = kDefaultMaxLease;
    mMinKeyLease = kDefaultMinKeyLease;
    mMaxKeyLease = kDefaultMaxKeyLease;
}

bool Server::LeaseConfig::IsValid(void) const
{
    bool valid = false;

    // TODO: Support longer LEASE.
    // We use milliseconds timer for LEASE & KEY-LEASE, this is to avoid overflow.
    VerifyOrExit(mMaxKeyLease <= Time::MsecToSec(TimerMilli::kMaxDelay));
    VerifyOrExit(mMinLease <= mMaxLease);
    VerifyOrExit(mMinKeyLease <= mMaxKeyLease);
    VerifyOrExit(mMinLease <= mMinKeyLease);
    VerifyOrExit(mMaxLease <= mMaxKeyLease);

    valid = true;

exit:
    return valid;
}

uint32_t Server::LeaseConfig::GrantLease(uint32_t aLease) const
{
    OT_ASSERT(mMinLease <= mMaxLease);

    return (aLease == 0) ? 0 : OT_MAX(mMinLease, OT_MIN(mMaxLease, aLease));
}

uint32_t Server::LeaseConfig::GrantKeyLease(uint32_t aKeyLease) const
{
    OT_ASSERT(mMinKeyLease <= mMaxKeyLease);

    return (aKeyLease == 0) ? 0 : OT_MAX(mMinKeyLease, OT_MIN(mMaxKeyLease, aKeyLease));
}

Error Server::SetLeaseConfig(const LeaseConfig &aLeaseConfig)
{
    Error error = kErrorNone;

    VerifyOrExit(aLeaseConfig.IsValid(), error = kErrorInvalidArgs);
    mLeaseConfig = aLeaseConfig;

exit:
    return error;
}

Error Server::SetDomain(const char *aDomain)
{
    Error    error = kErrorNone;
    uint16_t length;

    VerifyOrExit(mState == kStateDisabled, error = kErrorInvalidState);

    length = StringLength(aDomain, Dns::Name::kMaxNameSize);
    VerifyOrExit((length > 0) && (length < Dns::Name::kMaxNameSize), error = kErrorInvalidArgs);

    if (aDomain[length - 1] == '.')
    {
        error = mDomain.Set(aDomain);
    }
    else
    {
        // Need to append dot at the end

        char buf[Dns::Name::kMaxNameSize];

        VerifyOrExit(length < Dns::Name::kMaxNameSize - 1, error = kErrorInvalidArgs);

        memcpy(buf, aDomain, length);
        buf[length]     = '.';
        buf[length + 1] = '\0';

        error = mDomain.Set(buf);
    }

exit:
    return error;
}

const Server::Host *Server::GetNextHost(const Server::Host *aHost)
{
    return (aHost == nullptr) ? mHosts.GetHead() : aHost->GetNext();
}

// This method adds a SRP service host and takes ownership of it.
// The caller MUST make sure that there is no existing host with the same hostname.
void Server::AddHost(Host &aHost)
{
    LogInfo("Add new host %s", aHost.GetFullName());

    OT_ASSERT(mHosts.FindMatching(aHost.GetFullName()) == nullptr);
    IgnoreError(mHosts.Add(aHost));
}
void Server::RemoveHost(Host *aHost, RetainName aRetainName, NotifyMode aNotifyServiceHandler)
{
    VerifyOrExit(aHost != nullptr);

    aHost->mLease = 0;
    aHost->ClearResources();

    if (aRetainName)
    {
        LogInfo("Remove host %s (but retain its name)", aHost->GetFullName());
    }
    else
    {
        aHost->mKeyLease = 0;
        IgnoreError(mHosts.Remove(*aHost));
        LogInfo("Fully remove host %s", aHost->GetFullName());
    }

    if (aNotifyServiceHandler && mServiceUpdateHandler != nullptr)
    {
        uint32_t updateId = AllocateId();

        LogInfo("SRP update handler is notified (updatedId = %u)", updateId);
        mServiceUpdateHandler(updateId, aHost, kDefaultEventsHandlerTimeout, mServiceUpdateHandlerContext);
        // We don't wait for the reply from the service update handler,
        // but always remove the host (and its services) regardless of
        // host/service update result. Because removing a host should fail
        // only when there is system failure of the platform mDNS implementation
        // and in which case the host is not expected to be still registered.
    }

    if (!aRetainName)
    {
        aHost->Free();
    }

exit:
    return;
}

bool Server::HasNameConflictsWith(Host &aHost) const
{
    bool        hasConflicts = false;
    const Host *existingHost = mHosts.FindMatching(aHost.GetFullName());

    if (existingHost != nullptr && aHost.GetKeyRecord()->GetKey() != existingHost->GetKeyRecord()->GetKey())
    {
        LogWarn("Name conflict: host name %s has already been allocated", aHost.GetFullName());
        ExitNow(hasConflicts = true);
    }

    for (const Service &service : aHost.mServices)
    {
        // Check on all hosts for a matching service with the same
        // instance name and if found, verify that it has the same
        // key.

        for (const Host &host : mHosts)
        {
            if (host.HasServiceInstance(service.GetInstanceName()) &&
                aHost.GetKeyRecord()->GetKey() != host.GetKeyRecord()->GetKey())
            {
                LogWarn("Name conflict: service name %s has already been allocated", service.GetInstanceName());
                ExitNow(hasConflicts = true);
            }
        }
    }

exit:
    return hasConflicts;
}

void Server::HandleServiceUpdateResult(ServiceUpdateId aId, Error aError)
{
    UpdateMetadata *update = mOutstandingUpdates.FindMatching(aId);

    if (update != nullptr)
    {
        HandleServiceUpdateResult(update, aError);
    }
    else
    {
        LogInfo("Delayed SRP host update result, the SRP update has been committed (updateId = %u)", aId);
    }
}

void Server::HandleServiceUpdateResult(UpdateMetadata *aUpdate, Error aError)
{
    LogInfo("Handler result of SRP update (id = %u) is received: %s", aUpdate->GetId(), ErrorToString(aError));

    IgnoreError(mOutstandingUpdates.Remove(*aUpdate));
    CommitSrpUpdate(aError, *aUpdate);
    aUpdate->Free();

    if (mOutstandingUpdates.IsEmpty())
    {
        mOutstandingUpdatesTimer.Stop();
    }
    else
    {
        mOutstandingUpdatesTimer.FireAt(mOutstandingUpdates.GetTail()->GetExpireTime());
    }
}

void Server::CommitSrpUpdate(Error aError, Host &aHost, const MessageMetadata &aMessageMetadata)
{
    CommitSrpUpdate(aError, aHost, aMessageMetadata.mDnsHeader, aMessageMetadata.mMessageInfo,
                    aMessageMetadata.mTtlConfig, aMessageMetadata.mLeaseConfig);
}

void Server::CommitSrpUpdate(Error aError, UpdateMetadata &aUpdateMetadata)
{
    CommitSrpUpdate(aError, aUpdateMetadata.GetHost(), aUpdateMetadata.GetDnsHeader(),
                    aUpdateMetadata.IsDirectRxFromClient() ? &aUpdateMetadata.GetMessageInfo() : nullptr,
                    aUpdateMetadata.GetTtlConfig(), aUpdateMetadata.GetLeaseConfig());
}

void Server::CommitSrpUpdate(Error                    aError,
                             Host &                   aHost,
                             const Dns::UpdateHeader &aDnsHeader,
                             const Ip6::MessageInfo * aMessageInfo,
                             const TtlConfig &        aTtlConfig,
                             const LeaseConfig &      aLeaseConfig)
{
    Host *   existingHost;
    uint32_t hostLease;
    uint32_t hostKeyLease;
    uint32_t grantedLease;
    uint32_t grantedKeyLease;
    uint32_t grantedTtl;
    bool     shouldFreeHost = true;

    SuccessOrExit(aError);

    hostLease       = aHost.GetLease();
    hostKeyLease    = aHost.GetKeyLease();
    grantedLease    = aLeaseConfig.GrantLease(hostLease);
    grantedKeyLease = aLeaseConfig.GrantKeyLease(hostKeyLease);
    grantedTtl      = aTtlConfig.GrantTtl(grantedLease, aHost.GetTtl());

    aHost.SetLease(grantedLease);
    aHost.SetKeyLease(grantedKeyLease);
    aHost.SetTtl(grantedTtl);

    for (Service &service : aHost.mServices)
    {
        service.mDescription->mLease    = grantedLease;
        service.mDescription->mKeyLease = grantedKeyLease;
        service.mDescription->mTtl      = grantedTtl;
    }

    existingHost = mHosts.FindMatching(aHost.GetFullName());

    if (aHost.GetLease() == 0)
    {
        if (aHost.GetKeyLease() == 0)
        {
            LogInfo("Remove key of host %s", aHost.GetFullName());
            RemoveHost(existingHost, kDeleteName, kDoNotNotifyServiceHandler);
        }
        else if (existingHost != nullptr)
        {
            existingHost->SetKeyLease(aHost.GetKeyLease());
            RemoveHost(existingHost, kRetainName, kDoNotNotifyServiceHandler);

            for (Service &service : existingHost->mServices)
            {
                existingHost->RemoveService(&service, kRetainName, kDoNotNotifyServiceHandler);
            }
        }
    }
    else if (existingHost != nullptr)
    {
        SuccessOrExit(aError = existingHost->MergeServicesAndResourcesFrom(aHost));
    }
    else
    {
        AddHost(aHost);
        shouldFreeHost = false;

        for (Service &service : aHost.GetServices())
        {
            service.mIsCommitted = true;
            service.Log(Service::kAddNew);
        }

#if OPENTHREAD_CONFIG_SRP_SERVER_PORT_SWITCH_ENABLE
        if (!mHasRegisteredAnyService && (mAddressMode == kAddressModeUnicast))
        {
            Settings::SrpServerInfo info;

            mHasRegisteredAnyService = true;
            info.SetPort(GetSocket().mSockName.mPort);
            IgnoreError(Get<Settings>().Save(info));
        }
#endif
    }

    // Re-schedule the lease timer.
    HandleLeaseTimer();

exit:
    if (aMessageInfo != nullptr)
    {
        if (aError == kErrorNone && !(grantedLease == hostLease && grantedKeyLease == hostKeyLease))
        {
            SendResponse(aDnsHeader, grantedLease, grantedKeyLease, *aMessageInfo);
        }
        else
        {
            SendResponse(aDnsHeader, ErrorToDnsResponseCode(aError), *aMessageInfo);
        }
    }

    if (shouldFreeHost)
    {
        aHost.Free();
    }
}

void Server::SelectPort(void)
{
    mPort = kUdpPortMin;

#if OPENTHREAD_CONFIG_SRP_SERVER_PORT_SWITCH_ENABLE
    {
        Settings::SrpServerInfo info;

        if (Get<Settings>().Read(info) == kErrorNone)
        {
            mPort = info.GetPort() + 1;
            if (mPort < kUdpPortMin || mPort > kUdpPortMax)
            {
                mPort = kUdpPortMin;
            }
        }
    }
#endif

    LogInfo("Selected port %u", mPort);
}

void Server::Start(void)
{
    VerifyOrExit(mState == kStateStopped);

    mState = kStateRunning;
    PrepareSocket();
    LogInfo("Start listening on port %u", mPort);

exit:
    return;
}

void Server::PrepareSocket(void)
{
    Error error = kErrorNone;

#if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE
    Ip6::Udp::Socket &dnsSocket = Get<Dns::ServiceDiscovery::Server>().mSocket;

    if (dnsSocket.GetSockName().GetPort() == mPort)
    {
        // If the DNS-SD socket matches our port number, we use the
        // same socket so we close our own socket (in case it was
        // open). `GetSocket()` will now return the DNS-SD socket.

        IgnoreError(mSocket.Close());
        ExitNow();
    }
#endif

    VerifyOrExit(!mSocket.IsOpen());
    SuccessOrExit(error = mSocket.Open(HandleUdpReceive, this));
    error = mSocket.Bind(mPort, OT_NETIF_THREAD);

exit:
    if (error != kErrorNone)
    {
        LogCrit("Failed to prepare socket: %s", ErrorToString(error));
        Stop();
    }
}

Ip6::Udp::Socket &Server::GetSocket(void)
{
    Ip6::Udp::Socket *socket = &mSocket;

#if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE
    Ip6::Udp::Socket &dnsSocket = Get<Dns::ServiceDiscovery::Server>().mSocket;

    if (dnsSocket.GetSockName().GetPort() == mPort)
    {
        socket = &dnsSocket;
    }
#endif

    return *socket;
}

#if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE

void Server::HandleDnssdServerStateChange(void)
{
    // This is called from` Dns::ServiceDiscovery::Server` to notify
    // that it has started or stopped. We check whether we need to
    // share the socket.

    if (mState == kStateRunning)
    {
        PrepareSocket();
    }
}

Error Server::HandleDnssdServerUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
    // This is called from` Dns::ServiceDiscovery::Server` when a UDP
    // message is received on its socket. We check whether we are
    // sharing socket and if so we process the received message. We
    // return `kErrorNone` to indicate that message was successfully
    // processed by `Srp::Server`, otherwise `kErrorDrop` is returned.

    Error error = kErrorDrop;

    VerifyOrExit((mState == kStateRunning) && !mSocket.IsOpen());

    error = ProcessMessage(aMessage, aMessageInfo);

exit:
    return error;
}

#endif // OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE

void Server::Stop(void)
{
    VerifyOrExit(mState == kStateRunning);

    mState = kStateStopped;

    while (!mHosts.IsEmpty())
    {
        RemoveHost(mHosts.GetHead(), kDeleteName, kNotifyServiceHandler);
    }

    // TODO: We should cancel any outstanding service updates, but current
    // OTBR mDNS publisher cannot properly handle it.
    while (!mOutstandingUpdates.IsEmpty())
    {
        mOutstandingUpdates.Pop()->Free();
    }

    mLeaseTimer.Stop();
    mOutstandingUpdatesTimer.Stop();

    LogInfo("Stop listening on %u", mPort);
    IgnoreError(mSocket.Close());
    mHasRegisteredAnyService = false;

exit:
    return;
}

void Server::HandleNetDataPublisherEvent(NetworkData::Publisher::Event aEvent)
{
    switch (aEvent)
    {
    case NetworkData::Publisher::kEventEntryAdded:
        Start();
        break;

    case NetworkData::Publisher::kEventEntryRemoved:
        Stop();
        break;
    }
}

const Server::UpdateMetadata *Server::FindOutstandingUpdate(const MessageMetadata &aMessageMetadata) const
{
    const UpdateMetadata *ret = nullptr;

    VerifyOrExit(aMessageMetadata.IsDirectRxFromClient());

    for (const UpdateMetadata &update : mOutstandingUpdates)
    {
        if (aMessageMetadata.mDnsHeader.GetMessageId() == update.GetDnsHeader().GetMessageId() &&
            aMessageMetadata.mMessageInfo->GetPeerAddr() == update.GetMessageInfo().GetPeerAddr() &&
            aMessageMetadata.mMessageInfo->GetPeerPort() == update.GetMessageInfo().GetPeerPort())
        {
            ExitNow(ret = &update);
        }
    }

exit:
    return ret;
}

void Server::ProcessDnsUpdate(Message &aMessage, MessageMetadata &aMetadata)
{
    Error error = kErrorNone;
    Host *host  = nullptr;

    LogInfo("Received DNS update from %s", aMetadata.IsDirectRxFromClient()
                                               ? aMetadata.mMessageInfo->GetPeerAddr().ToString().AsCString()
                                               : "an SRPL Partner");

    SuccessOrExit(error = ProcessZoneSection(aMessage, aMetadata));

    if (FindOutstandingUpdate(aMetadata) != nullptr)
    {
        LogInfo("Drop duplicated SRP update request: MessageId=%hu", aMetadata.mDnsHeader.GetMessageId());

        // Silently drop duplicate requests.
        // This could rarely happen, because the outstanding SRP update timer should
        // be shorter than the SRP update retransmission timer.
        ExitNow(error = kErrorNone);
    }

    // Per 2.3.2 of SRP draft 6, no prerequisites should be included in a SRP update.
    VerifyOrExit(aMetadata.mDnsHeader.GetPrerequisiteRecordCount() == 0, error = kErrorFailed);

    host = Host::Allocate(GetInstance(), aMetadata.mRxTime);
    VerifyOrExit(host != nullptr, error = kErrorNoBufs);
    SuccessOrExit(error = ProcessUpdateSection(*host, aMessage, aMetadata));

    // Parse lease time and validate signature.
    SuccessOrExit(error = ProcessAdditionalSection(host, aMessage, aMetadata));

    SuccessOrExit(error = ValidateServiceSubTypes(*host, aMetadata));

    HandleUpdate(*host, aMetadata);

exit:
    if (error != kErrorNone)
    {
        if (host != nullptr)
        {
            host->Free();
        }

        if (aMetadata.IsDirectRxFromClient())
        {
            SendResponse(aMetadata.mDnsHeader, ErrorToDnsResponseCode(error), *aMetadata.mMessageInfo);
        }
    }
}

Error Server::ProcessZoneSection(const Message &aMessage, MessageMetadata &aMetadata) const
{
    Error    error = kErrorNone;
    char     name[Dns::Name::kMaxNameSize];
    uint16_t offset = aMetadata.mOffset;

    VerifyOrExit(aMetadata.mDnsHeader.GetZoneRecordCount() == 1, error = kErrorParse);

    SuccessOrExit(error = Dns::Name::ReadName(aMessage, offset, name, sizeof(name)));
    // TODO: return `Dns::kResponseNotAuth` for not authorized zone names.
    VerifyOrExit(StringMatch(name, GetDomain(), kStringCaseInsensitiveMatch), error = kErrorSecurity);
    SuccessOrExit(error = aMessage.Read(offset, aMetadata.mDnsZone));
    offset += sizeof(Dns::Zone);

    VerifyOrExit(aMetadata.mDnsZone.GetType() == Dns::ResourceRecord::kTypeSoa, error = kErrorParse);
    aMetadata.mOffset = offset;

exit:
    if (error != kErrorNone)
    {
        LogWarn("Failed to process DNS Zone section: %s", ErrorToString(error));
    }

    return error;
}

Error Server::ProcessUpdateSection(Host &aHost, const Message &aMessage, MessageMetadata &aMetadata) const
{
    Error error = kErrorNone;

    // Process Service Discovery, Host and Service Description Instructions with
    // 3 times iterations over all DNS update RRs. The order of those processes matters.

    // 0. Enumerate over all Service Discovery Instructions before processing any other records.
    // So that we will know whether a name is a hostname or service instance name when processing
    // a "Delete All RRsets from a name" record.
    SuccessOrExit(error = ProcessServiceDiscoveryInstructions(aHost, aMessage, aMetadata));

    // 1. Enumerate over all RRs to build the Host Description Instruction.
    SuccessOrExit(error = ProcessHostDescriptionInstruction(aHost, aMessage, aMetadata));

    // 2. Enumerate over all RRs to build the Service Description Instructions.
    SuccessOrExit(error = ProcessServiceDescriptionInstructions(aHost, aMessage, aMetadata));

    // 3. Verify that there are no name conflicts.
    VerifyOrExit(!HasNameConflictsWith(aHost), error = kErrorDuplicated);

exit:
    if (error != kErrorNone)
    {
        LogWarn("Failed to process DNS Update section: %s", ErrorToString(error));
    }

    return error;
}

Error Server::ProcessHostDescriptionInstruction(Host &                 aHost,
                                                const Message &        aMessage,
                                                const MessageMetadata &aMetadata) const
{
    Error    error;
    uint16_t offset = aMetadata.mOffset;

    OT_ASSERT(aHost.GetFullName() == nullptr);

    for (uint16_t numRecords = aMetadata.mDnsHeader.GetUpdateRecordCount(); numRecords > 0; numRecords--)
    {
        char                name[Dns::Name::kMaxNameSize];
        Dns::ResourceRecord record;

        SuccessOrExit(error = Dns::Name::ReadName(aMessage, offset, name, sizeof(name)));

        SuccessOrExit(error = aMessage.Read(offset, record));

        if (record.GetClass() == Dns::ResourceRecord::kClassAny)
        {
            // Delete All RRsets from a name.
            VerifyOrExit(IsValidDeleteAllRecord(record), error = kErrorFailed);

            // A "Delete All RRsets from a name" RR can only apply to a Service or Host Description.

            if (!aHost.HasServiceInstance(name))
            {
                // If host name is already set to a different name, `SetFullName()`
                // will return `kErrorFailed`.
                SuccessOrExit(error = aHost.SetFullName(name));
                aHost.ClearResources();
            }
        }
        else if (record.GetType() == Dns::ResourceRecord::kTypeAaaa)
        {
            Dns::AaaaRecord aaaaRecord;

            VerifyOrExit(record.GetClass() == aMetadata.mDnsZone.GetClass(), error = kErrorFailed);

            SuccessOrExit(error = aHost.ProcessTtl(record.GetTtl()));

            SuccessOrExit(error = aHost.SetFullName(name));

            SuccessOrExit(error = aMessage.Read(offset, aaaaRecord));
            VerifyOrExit(aaaaRecord.IsValid(), error = kErrorParse);

            // Tolerate kErrorDrop for AAAA Resources.
            VerifyOrExit(aHost.AddIp6Address(aaaaRecord.GetAddress()) != kErrorNoBufs, error = kErrorNoBufs);
        }
        else if (record.GetType() == Dns::ResourceRecord::kTypeKey)
        {
            // We currently support only ECDSA P-256.
            Dns::Ecdsa256KeyRecord keyRecord;

            VerifyOrExit(record.GetClass() == aMetadata.mDnsZone.GetClass(), error = kErrorFailed);

            SuccessOrExit(error = aHost.ProcessTtl(record.GetTtl()));

            SuccessOrExit(error = aMessage.Read(offset, keyRecord));
            VerifyOrExit(keyRecord.IsValid(), error = kErrorParse);

            VerifyOrExit(aHost.GetKeyRecord() == nullptr || *aHost.GetKeyRecord() == keyRecord, error = kErrorSecurity);
            aHost.SetKeyRecord(keyRecord);
        }

        offset += record.GetSize();
    }

    // Verify that we have a complete Host Description Instruction.

    VerifyOrExit(aHost.GetFullName() != nullptr, error = kErrorFailed);
    VerifyOrExit(aHost.GetKeyRecord() != nullptr, error = kErrorFailed);

    // We check the number of host addresses after processing of the
    // Lease Option in the Addition Section and determining whether
    // the host is being removed or registered.

exit:
    if (error != kErrorNone)
    {
        LogWarn("Failed to process Host Description instructions: %s", ErrorToString(error));
    }

    return error;
}

Error Server::ProcessServiceDiscoveryInstructions(Host &                 aHost,
                                                  const Message &        aMessage,
                                                  const MessageMetadata &aMetadata) const
{
    Error    error  = kErrorNone;
    uint16_t offset = aMetadata.mOffset;

    for (uint16_t numRecords = aMetadata.mDnsHeader.GetUpdateRecordCount(); numRecords > 0; numRecords--)
    {
        char           serviceName[Dns::Name::kMaxNameSize];
        char           instanceName[Dns::Name::kMaxNameSize];
        Dns::PtrRecord ptrRecord;
        const char *   subServiceName;
        Service *      service;
        bool           isSubType;

        SuccessOrExit(error = Dns::Name::ReadName(aMessage, offset, serviceName, sizeof(serviceName)));
        VerifyOrExit(Dns::Name::IsSubDomainOf(serviceName, GetDomain()), error = kErrorSecurity);

        error = Dns::ResourceRecord::ReadRecord(aMessage, offset, ptrRecord);

        if (error == kErrorNotFound)
        {
            // `ReadRecord()` updates `aOffset` to skip over a
            // non-matching record.
            error = kErrorNone;
            continue;
        }

        SuccessOrExit(error);

        SuccessOrExit(error = Dns::Name::ReadName(aMessage, offset, instanceName, sizeof(instanceName)));

        VerifyOrExit(ptrRecord.GetClass() == Dns::ResourceRecord::kClassNone ||
                         ptrRecord.GetClass() == aMetadata.mDnsZone.GetClass(),
                     error = kErrorFailed);

        // Check if the `serviceName` is a subtype with the name
        // format: "<sub-label>._sub.<service-labels>.<domain>."

        subServiceName = StringFind(serviceName, kServiceSubTypeLabel, kStringCaseInsensitiveMatch);
        isSubType      = (subServiceName != nullptr);

        if (isSubType)
        {
            // Skip over the "._sub." label to get to the base
            // service name.
            subServiceName += sizeof(kServiceSubTypeLabel) - 1;
        }

        // Verify that instance name and service name are related.

        VerifyOrExit(
            StringEndsWith(instanceName, isSubType ? subServiceName : serviceName, kStringCaseInsensitiveMatch),
            error = kErrorFailed);

        // Ensure the same service does not exist already.
        VerifyOrExit(aHost.FindService(serviceName, instanceName) == nullptr, error = kErrorFailed);

        service = aHost.AddNewService(serviceName, instanceName, isSubType, aMetadata.mRxTime);
        VerifyOrExit(service != nullptr, error = kErrorNoBufs);

        // This RR is a "Delete an RR from an RRset" update when the CLASS is NONE.
        service->mIsDeleted = (ptrRecord.GetClass() == Dns::ResourceRecord::kClassNone);

        if (!service->mIsDeleted)
        {
            SuccessOrExit(error = aHost.ProcessTtl(ptrRecord.GetTtl()));
        }
    }

exit:
    if (error != kErrorNone)
    {
        LogWarn("Failed to process Service Discovery instructions: %s", ErrorToString(error));
    }

    return error;
}

Error Server::ProcessServiceDescriptionInstructions(Host &           aHost,
                                                    const Message &  aMessage,
                                                    MessageMetadata &aMetadata) const
{
    Error    error  = kErrorNone;
    uint16_t offset = aMetadata.mOffset;

    for (uint16_t numRecords = aMetadata.mDnsHeader.GetUpdateRecordCount(); numRecords > 0; numRecords--)
    {
        RetainPtr<Service::Description> desc;
        char                            name[Dns::Name::kMaxNameSize];
        Dns::ResourceRecord             record;

        SuccessOrExit(error = Dns::Name::ReadName(aMessage, offset, name, sizeof(name)));
        SuccessOrExit(error = aMessage.Read(offset, record));

        if (record.GetClass() == Dns::ResourceRecord::kClassAny)
        {
            // Delete All RRsets from a name.
            VerifyOrExit(IsValidDeleteAllRecord(record), error = kErrorFailed);

            desc = aHost.FindServiceDescription(name);

            if (desc != nullptr)
            {
                desc->ClearResources();
                desc->mUpdateTime = aMetadata.mRxTime;
            }

            offset += record.GetSize();
            continue;
        }

        if (record.GetType() == Dns::ResourceRecord::kTypeSrv)
        {
            Dns::SrvRecord srvRecord;
            char           hostName[Dns::Name::kMaxNameSize];
            uint16_t       hostNameLength = sizeof(hostName);

            VerifyOrExit(record.GetClass() == aMetadata.mDnsZone.GetClass(), error = kErrorFailed);

            SuccessOrExit(error = aHost.ProcessTtl(record.GetTtl()));

            SuccessOrExit(error = aMessage.Read(offset, srvRecord));
            offset += sizeof(srvRecord);

            SuccessOrExit(error = Dns::Name::ReadName(aMessage, offset, hostName, hostNameLength));
            VerifyOrExit(Dns::Name::IsSubDomainOf(name, GetDomain()), error = kErrorSecurity);
            VerifyOrExit(aHost.Matches(hostName), error = kErrorFailed);

            desc = aHost.FindServiceDescription(name);
            VerifyOrExit(desc != nullptr, error = kErrorFailed);

            // Make sure that this is the first SRV RR for this service description
            VerifyOrExit(desc->mPort == 0, error = kErrorFailed);
            desc->mTtl        = srvRecord.GetTtl();
            desc->mPriority   = srvRecord.GetPriority();
            desc->mWeight     = srvRecord.GetWeight();
            desc->mPort       = srvRecord.GetPort();
            desc->mUpdateTime = aMetadata.mRxTime;
        }
        else if (record.GetType() == Dns::ResourceRecord::kTypeTxt)
        {
            VerifyOrExit(record.GetClass() == aMetadata.mDnsZone.GetClass(), error = kErrorFailed);

            SuccessOrExit(error = aHost.ProcessTtl(record.GetTtl()));

            desc = aHost.FindServiceDescription(name);
            VerifyOrExit(desc != nullptr, error = kErrorFailed);

            offset += sizeof(record);
            SuccessOrExit(error = desc->SetTxtDataFromMessage(aMessage, offset, record.GetLength()));
            offset += record.GetLength();
        }
        else
        {
            offset += record.GetSize();
        }
    }

    // Verify that all service descriptions on `aHost` are updated. Note
    // that `mUpdateTime` on a new `Service::Description` is set to
    // `GetNow().GetDistantPast()`.

    for (Service &service : aHost.mServices)
    {
        VerifyOrExit(service.mDescription->mUpdateTime == aMetadata.mRxTime, error = kErrorFailed);

        // Check that either both `mPort` and `mTxtData` are set
        // (i.e., we saw both SRV and TXT record) or both are default
        // (cleared) value (i.e., we saw neither of them).

        VerifyOrExit((service.mDescription->mPort == 0) == service.mDescription->mTxtData.IsNull(),
                     error = kErrorFailed);
    }

    aMetadata.mOffset = offset;

exit:
    if (error != kErrorNone)
    {
        LogWarn("Failed to process Service Description instructions: %s", ErrorToString(error));
    }

    return error;
}

bool Server::IsValidDeleteAllRecord(const Dns::ResourceRecord &aRecord)
{
    return aRecord.GetClass() == Dns::ResourceRecord::kClassAny && aRecord.GetType() == Dns::ResourceRecord::kTypeAny &&
           aRecord.GetTtl() == 0 && aRecord.GetLength() == 0;
}

Error Server::ProcessAdditionalSection(Host *aHost, const Message &aMessage, MessageMetadata &aMetadata) const
{
    Error            error = kErrorNone;
    Dns::OptRecord   optRecord;
    Dns::LeaseOption leaseOption;
    Dns::SigRecord   sigRecord;
    char             name[2]; // The root domain name (".") is expected.
    uint16_t         offset = aMetadata.mOffset;
    uint16_t         sigOffset;
    uint16_t         sigRdataOffset;
    char             signerName[Dns::Name::kMaxNameSize];
    uint16_t         signatureLength;

    VerifyOrExit(aMetadata.mDnsHeader.GetAdditionalRecordCount() == 2, error = kErrorFailed);

    // EDNS(0) Update Lease Option.

    SuccessOrExit(error = Dns::Name::ReadName(aMessage, offset, name, sizeof(name)));
    SuccessOrExit(error = aMessage.Read(offset, optRecord));
    SuccessOrExit(error = aMessage.Read(offset + sizeof(optRecord), leaseOption));
    VerifyOrExit(leaseOption.IsValid(), error = kErrorFailed);
    VerifyOrExit(optRecord.GetSize() == sizeof(optRecord) + sizeof(leaseOption), error = kErrorParse);

    offset += optRecord.GetSize();

    aHost->SetLease(leaseOption.GetLeaseInterval());
    aHost->SetKeyLease(leaseOption.GetKeyLeaseInterval());

    if (aHost->GetLease() > 0)
    {
        uint8_t hostAddressesNum;

        aHost->GetAddresses(hostAddressesNum);

        // There MUST be at least one valid address if we have nonzero lease.
        VerifyOrExit(hostAddressesNum > 0, error = kErrorFailed);
    }

    // SIG(0).

    sigOffset = offset;
    SuccessOrExit(error = Dns::Name::ReadName(aMessage, offset, name, sizeof(name)));
    SuccessOrExit(error = aMessage.Read(offset, sigRecord));
    VerifyOrExit(sigRecord.IsValid(), error = kErrorParse);

    sigRdataOffset = offset + sizeof(Dns::ResourceRecord);
    offset += sizeof(sigRecord);

    // TODO: Verify that the signature doesn't expire. This is not
    // implemented because the end device may not be able to get
    // the synchronized date/time.

    SuccessOrExit(error = Dns::Name::ReadName(aMessage, offset, signerName, sizeof(signerName)));

    signatureLength = sigRecord.GetLength() - (offset - sigRdataOffset);
    offset += signatureLength;

    // Verify the signature. Currently supports only ECDSA.

    VerifyOrExit(sigRecord.GetAlgorithm() == Dns::KeyRecord::kAlgorithmEcdsaP256Sha256, error = kErrorFailed);
    VerifyOrExit(sigRecord.GetTypeCovered() == 0, error = kErrorFailed);
    VerifyOrExit(signatureLength == Crypto::Ecdsa::P256::Signature::kSize, error = kErrorParse);

    SuccessOrExit(error = VerifySignature(*aHost->GetKeyRecord(), aMessage, aMetadata.mDnsHeader, sigOffset,
                                          sigRdataOffset, sigRecord.GetLength(), signerName));

    aMetadata.mOffset = offset;

exit:
    if (error != kErrorNone)
    {
        LogWarn("Failed to process DNS Additional section: %s", ErrorToString(error));
    }

    return error;
}

Error Server::VerifySignature(const Dns::Ecdsa256KeyRecord &aKeyRecord,
                              const Message &               aMessage,
                              Dns::UpdateHeader             aDnsHeader,
                              uint16_t                      aSigOffset,
                              uint16_t                      aSigRdataOffset,
                              uint16_t                      aSigRdataLength,
                              const char *                  aSignerName) const
{
    Error                          error;
    uint16_t                       offset = aMessage.GetOffset();
    uint16_t                       signatureOffset;
    Crypto::Sha256                 sha256;
    Crypto::Sha256::Hash           hash;
    Crypto::Ecdsa::P256::Signature signature;
    Message *                      signerNameMessage = nullptr;

    VerifyOrExit(aSigRdataLength >= Crypto::Ecdsa::P256::Signature::kSize, error = kErrorInvalidArgs);

    sha256.Start();

    // SIG RDATA less signature.
    sha256.Update(aMessage, aSigRdataOffset, sizeof(Dns::SigRecord) - sizeof(Dns::ResourceRecord));

    // The uncompressed (canonical) form of the signer name should be used for signature
    // verification. See https://tools.ietf.org/html/rfc2931#section-3.1 for details.
    signerNameMessage = Get<Ip6::Udp>().NewMessage(0);
    VerifyOrExit(signerNameMessage != nullptr, error = kErrorNoBufs);
    SuccessOrExit(error = Dns::Name::AppendName(aSignerName, *signerNameMessage));
    sha256.Update(*signerNameMessage, signerNameMessage->GetOffset(), signerNameMessage->GetLength());

    // We need the DNS header before appending the SIG RR.
    aDnsHeader.SetAdditionalRecordCount(aDnsHeader.GetAdditionalRecordCount() - 1);
    sha256.Update(aDnsHeader);
    sha256.Update(aMessage, offset + sizeof(aDnsHeader), aSigOffset - offset - sizeof(aDnsHeader));

    sha256.Finish(hash);

    signatureOffset = aSigRdataOffset + aSigRdataLength - Crypto::Ecdsa::P256::Signature::kSize;
    SuccessOrExit(error = aMessage.Read(signatureOffset, signature));

    error = aKeyRecord.GetKey().Verify(hash, signature);

exit:
    if (error != kErrorNone)
    {
        LogWarn("Failed to verify message signature: %s", ErrorToString(error));
    }

    FreeMessage(signerNameMessage);
    return error;
}

Error Server::ValidateServiceSubTypes(Host &aHost, const MessageMetadata &aMetadata)
{
    Error error = kErrorNone;
    Host *existingHost;

    // Verify that there is a matching base type service for all
    // sub-type services in `aHost` (which is from the received
    // and parsed SRP Update message).

    for (const Service &service : aHost.GetServices())
    {
        if (service.IsSubType() && (aHost.FindBaseService(service.GetInstanceName()) == nullptr))
        {
#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_WARN)
            char subLabel[Dns::Name::kMaxLabelSize];

            IgnoreError(service.GetServiceSubTypeLabel(subLabel, sizeof(subLabel)));
            LogWarn("Message contains instance %s with subtype %s without base type", service.GetInstanceName(),
                    subLabel);
#endif

            ExitNow(error = kErrorParse);
        }
    }

    // SRP server must treat the update instructions for a service type
    // and all its sub-types as atomic, i.e., when a service and its
    // sub-types are being updated, whatever information appears in the
    // SRP Update is the entirety of information about that service and
    // its sub-types. Any previously registered sub-type that does not
    // appear in a new SRP Update, must be removed.
    //
    // We go though the list of registered services for the same host
    // and if the base service is included in the new SRP Update
    // message, we add any previously registered service sub-type that
    // does not appear in new Update message as "deleted".

    existingHost = mHosts.FindMatching(aHost.GetFullName());
    VerifyOrExit(existingHost != nullptr);

    for (const Service &baseService : existingHost->GetServices())
    {
        if (baseService.IsSubType() || (aHost.FindBaseService(baseService.GetInstanceName()) == nullptr))
        {
            continue;
        }

        for (const Service &subService : existingHost->GetServices())
        {
            if (!subService.IsSubType() || !subService.MatchesInstanceName(baseService.GetInstanceName()))
            {
                continue;
            }

            SuccessOrExit(error = aHost.AddCopyOfServiceAsDeletedIfNotPresent(subService, aMetadata.mRxTime));
        }
    }

exit:
    return error;
}

void Server::HandleUpdate(Host &aHost, const MessageMetadata &aMetadata)
{
    Error error = kErrorNone;
    Host *existingHost;

    // Check whether the SRP update wants to remove `aHost`.

    VerifyOrExit(aHost.GetLease() == 0);

    aHost.ClearResources();

    existingHost = mHosts.FindMatching(aHost.GetFullName());
    VerifyOrExit(existingHost != nullptr);

    // The client may not include all services it has registered before
    // when removing a host. We copy and append any missing services to
    // `aHost` from the `existingHost` and mark them as deleted.

    for (Service &service : existingHost->mServices)
    {
        if (service.mIsDeleted)
        {
            continue;
        }

        SuccessOrExit(error = aHost.AddCopyOfServiceAsDeletedIfNotPresent(service, aMetadata.mRxTime));
    }

exit:
    InformUpdateHandlerOrCommit(error, aHost, aMetadata);
}

void Server::InformUpdateHandlerOrCommit(Error aError, Host &aHost, const MessageMetadata &aMetadata)
{
    if ((aError == kErrorNone) && (mServiceUpdateHandler != nullptr))
    {
        UpdateMetadata *update = UpdateMetadata::Allocate(GetInstance(), aHost, aMetadata);

        if (update != nullptr)
        {
            mOutstandingUpdates.Push(*update);
            mOutstandingUpdatesTimer.FireAtIfEarlier(update->GetExpireTime());

            LogInfo("SRP update handler is notified (updatedId = %u)", update->GetId());
            mServiceUpdateHandler(update->GetId(), &aHost, kDefaultEventsHandlerTimeout, mServiceUpdateHandlerContext);
            ExitNow();
        }

        aError = kErrorNoBufs;
    }

    CommitSrpUpdate(aError, aHost, aMetadata);

exit:
    return;
}

void Server::SendResponse(const Dns::UpdateHeader &   aHeader,
                          Dns::UpdateHeader::Response aResponseCode,
                          const Ip6::MessageInfo &    aMessageInfo)
{
    Error             error;
    Message *         response = nullptr;
    Dns::UpdateHeader header;

    response = GetSocket().NewMessage(0);
    VerifyOrExit(response != nullptr, error = kErrorNoBufs);

    header.SetMessageId(aHeader.GetMessageId());
    header.SetType(Dns::UpdateHeader::kTypeResponse);
    header.SetQueryType(aHeader.GetQueryType());
    header.SetResponseCode(aResponseCode);
    SuccessOrExit(error = response->Append(header));

    SuccessOrExit(error = GetSocket().SendTo(*response, aMessageInfo));

    if (aResponseCode != Dns::UpdateHeader::kResponseSuccess)
    {
        LogWarn("Send fail response: %d", aResponseCode);
    }
    else
    {
        LogInfo("Send success response");
    }

    UpdateResponseCounters(aResponseCode);

exit:
    if (error != kErrorNone)
    {
        LogWarn("Failed to send response: %s", ErrorToString(error));
        FreeMessage(response);
    }
}

void Server::SendResponse(const Dns::UpdateHeader &aHeader,
                          uint32_t                 aLease,
                          uint32_t                 aKeyLease,
                          const Ip6::MessageInfo & aMessageInfo)
{
    Error             error;
    Message *         response = nullptr;
    Dns::UpdateHeader header;
    Dns::OptRecord    optRecord;
    Dns::LeaseOption  leaseOption;

    response = GetSocket().NewMessage(0);
    VerifyOrExit(response != nullptr, error = kErrorNoBufs);

    header.SetMessageId(aHeader.GetMessageId());
    header.SetType(Dns::UpdateHeader::kTypeResponse);
    header.SetQueryType(aHeader.GetQueryType());
    header.SetResponseCode(Dns::UpdateHeader::kResponseSuccess);
    header.SetAdditionalRecordCount(1);
    SuccessOrExit(error = response->Append(header));

    // Append the root domain (".").
    SuccessOrExit(error = Dns::Name::AppendTerminator(*response));

    optRecord.Init();
    optRecord.SetUdpPayloadSize(kUdpPayloadSize);
    optRecord.SetDnsSecurityFlag();
    optRecord.SetLength(sizeof(Dns::LeaseOption));
    SuccessOrExit(error = response->Append(optRecord));

    leaseOption.Init();
    leaseOption.SetLeaseInterval(aLease);
    leaseOption.SetKeyLeaseInterval(aKeyLease);
    SuccessOrExit(error = response->Append(leaseOption));

    SuccessOrExit(error = GetSocket().SendTo(*response, aMessageInfo));

    LogInfo("Send success response with granted lease: %u and key lease: %u", aLease, aKeyLease);

    UpdateResponseCounters(Dns::UpdateHeader::kResponseSuccess);

exit:
    if (error != kErrorNone)
    {
        LogWarn("Failed to send response: %s", ErrorToString(error));
        FreeMessage(response);
    }
}

void Server::HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo)
{
    static_cast<Server *>(aContext)->HandleUdpReceive(AsCoreType(aMessage), AsCoreType(aMessageInfo));
}

void Server::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
    Error error = ProcessMessage(aMessage, aMessageInfo);

    if (error != kErrorNone)
    {
        LogInfo("Failed to handle DNS message: %s", ErrorToString(error));
    }
}

Error Server::ProcessMessage(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
    return ProcessMessage(aMessage, TimerMilli::GetNow(), mTtlConfig, mLeaseConfig, &aMessageInfo);
}

Error Server::ProcessMessage(Message &               aMessage,
                             TimeMilli               aRxTime,
                             const TtlConfig &       aTtlConfig,
                             const LeaseConfig &     aLeaseConfig,
                             const Ip6::MessageInfo *aMessageInfo)
{
    Error           error;
    MessageMetadata metadata;

    metadata.mOffset      = aMessage.GetOffset();
    metadata.mRxTime      = aRxTime;
    metadata.mTtlConfig   = aTtlConfig;
    metadata.mLeaseConfig = aLeaseConfig;
    metadata.mMessageInfo = aMessageInfo;

    SuccessOrExit(error = aMessage.Read(metadata.mOffset, metadata.mDnsHeader));
    metadata.mOffset += sizeof(Dns::UpdateHeader);

    VerifyOrExit(metadata.mDnsHeader.GetType() == Dns::UpdateHeader::Type::kTypeQuery, error = kErrorDrop);
    VerifyOrExit(metadata.mDnsHeader.GetQueryType() == Dns::UpdateHeader::kQueryTypeUpdate, error = kErrorDrop);

    ProcessDnsUpdate(aMessage, metadata);

exit:
    return error;
}

void Server::HandleLeaseTimer(Timer &aTimer)
{
    aTimer.Get<Server>().HandleLeaseTimer();
}

void Server::HandleLeaseTimer(void)
{
    TimeMilli now                = TimerMilli::GetNow();
    TimeMilli earliestExpireTime = now.GetDistantFuture();
    Host *    nextHost;

    for (Host *host = mHosts.GetHead(); host != nullptr; host = nextHost)
    {
        nextHost = host->GetNext();

        if (host->GetKeyExpireTime() <= now)
        {
            LogInfo("KEY LEASE of host %s expired", host->GetFullName());

            // Removes the whole host and all services if the KEY RR expired.
            RemoveHost(host, kDeleteName, kNotifyServiceHandler);
        }
        else if (host->IsDeleted())
        {
            // The host has been deleted, but the hostname & service instance names retain.

            Service *next;

            earliestExpireTime = OT_MIN(earliestExpireTime, host->GetKeyExpireTime());

            // Check if any service instance name expired.
            for (Service *service = host->mServices.GetHead(); service != nullptr; service = next)
            {
                next = service->GetNext();

                OT_ASSERT(service->mIsDeleted);

                if (service->GetKeyExpireTime() <= now)
                {
                    service->Log(Service::kKeyLeaseExpired);
                    host->RemoveService(service, kDeleteName, kNotifyServiceHandler);
                }
                else
                {
                    earliestExpireTime = OT_MIN(earliestExpireTime, service->GetKeyExpireTime());
                }
            }
        }
        else if (host->GetExpireTime() <= now)
        {
            LogInfo("LEASE of host %s expired", host->GetFullName());

            // If the host expired, delete all resources of this host and its services.
            for (Service &service : host->mServices)
            {
                // Don't need to notify the service handler as `RemoveHost` at below will do.
                host->RemoveService(&service, kRetainName, kDoNotNotifyServiceHandler);
            }

            RemoveHost(host, kRetainName, kNotifyServiceHandler);

            earliestExpireTime = OT_MIN(earliestExpireTime, host->GetKeyExpireTime());
        }
        else
        {
            // The host doesn't expire, check if any service expired or is explicitly removed.

            Service *next;

            OT_ASSERT(!host->IsDeleted());

            earliestExpireTime = OT_MIN(earliestExpireTime, host->GetExpireTime());

            for (Service *service = host->mServices.GetHead(); service != nullptr; service = next)
            {
                next = service->GetNext();

                if (service->GetKeyExpireTime() <= now)
                {
                    service->Log(Service::kKeyLeaseExpired);
                    host->RemoveService(service, kDeleteName, kNotifyServiceHandler);
                }
                else if (service->mIsDeleted)
                {
                    // The service has been deleted but the name retains.
                    earliestExpireTime = OT_MIN(earliestExpireTime, service->GetKeyExpireTime());
                }
                else if (service->GetExpireTime() <= now)
                {
                    service->Log(Service::kLeaseExpired);

                    // The service is expired, delete it.
                    host->RemoveService(service, kRetainName, kNotifyServiceHandler);
                    earliestExpireTime = OT_MIN(earliestExpireTime, service->GetKeyExpireTime());
                }
                else
                {
                    earliestExpireTime = OT_MIN(earliestExpireTime, service->GetExpireTime());
                }
            }
        }
    }

    if (earliestExpireTime != now.GetDistantFuture())
    {
        OT_ASSERT(earliestExpireTime >= now);
        if (!mLeaseTimer.IsRunning() || earliestExpireTime <= mLeaseTimer.GetFireTime())
        {
            LogInfo("Lease timer is scheduled for %u seconds", Time::MsecToSec(earliestExpireTime - now));
            mLeaseTimer.StartAt(earliestExpireTime, 0);
        }
    }
    else
    {
        LogInfo("Lease timer is stopped");
        mLeaseTimer.Stop();
    }
}

void Server::HandleOutstandingUpdatesTimer(Timer &aTimer)
{
    aTimer.Get<Server>().HandleOutstandingUpdatesTimer();
}

void Server::HandleOutstandingUpdatesTimer(void)
{
    while (!mOutstandingUpdates.IsEmpty() && mOutstandingUpdates.GetTail()->GetExpireTime() <= TimerMilli::GetNow())
    {
        LogInfo("Outstanding service update timeout (updateId = %u)", mOutstandingUpdates.GetTail()->GetId());
        HandleServiceUpdateResult(mOutstandingUpdates.GetTail(), kErrorResponseTimeout);
    }
}

const char *Server::AddressModeToString(AddressMode aMode)
{
    static const char *const kAddressModeStrings[] = {
        "unicast", // (0) kAddressModeUnicast
        "anycast", // (1) kAddressModeAnycast
    };

    static_assert(kAddressModeUnicast == 0, "kAddressModeUnicast value is incorrect");
    static_assert(kAddressModeAnycast == 1, "kAddressModeAnycast value is incorrect");

    return kAddressModeStrings[aMode];
}

void Server::UpdateResponseCounters(Dns::UpdateHeader::Response aResponseCode)
{
    switch (aResponseCode)
    {
    case Dns::UpdateHeader::kResponseSuccess:
        ++mResponseCounters.mSuccess;
        break;
    case Dns::UpdateHeader::kResponseServerFailure:
        ++mResponseCounters.mServerFailure;
        break;
    case Dns::UpdateHeader::kResponseFormatError:
        ++mResponseCounters.mFormatError;
        break;
    case Dns::UpdateHeader::kResponseNameExists:
        ++mResponseCounters.mNameExists;
        break;
    case Dns::UpdateHeader::kResponseRefused:
        ++mResponseCounters.mRefused;
        break;
    default:
        ++mResponseCounters.mOther;
        break;
    }
}

//---------------------------------------------------------------------------------------------------------------------
// Server::Service

Error Server::Service::Init(const char *aServiceName, Description &aDescription, bool aIsSubType, TimeMilli aUpdateTime)
{
    mDescription.Reset(&aDescription);
    mNext        = nullptr;
    mUpdateTime  = aUpdateTime;
    mIsDeleted   = false;
    mIsSubType   = aIsSubType;
    mIsCommitted = false;

    return mServiceName.Set(aServiceName);
}

Error Server::Service::GetServiceSubTypeLabel(char *aLabel, uint8_t aMaxSize) const
{
    Error       error       = kErrorNone;
    const char *serviceName = GetServiceName();
    const char *subServiceName;
    uint8_t     labelLength;

    memset(aLabel, 0, aMaxSize);

    VerifyOrExit(IsSubType(), error = kErrorInvalidArgs);

    subServiceName = StringFind(serviceName, kServiceSubTypeLabel, kStringCaseInsensitiveMatch);
    OT_ASSERT(subServiceName != nullptr);

    if (subServiceName - serviceName < aMaxSize)
    {
        labelLength = static_cast<uint8_t>(subServiceName - serviceName);
    }
    else
    {
        labelLength = aMaxSize - 1;
        error       = kErrorNoBufs;
    }

    memcpy(aLabel, serviceName, labelLength);

exit:
    return error;
}

TimeMilli Server::Service::GetExpireTime(void) const
{
    OT_ASSERT(!mIsDeleted);
    OT_ASSERT(!GetHost().IsDeleted());

    return mUpdateTime + Time::SecToMsec(mDescription->mLease);
}

TimeMilli Server::Service::GetKeyExpireTime(void) const
{
    return mUpdateTime + Time::SecToMsec(mDescription->mKeyLease);
}

void Server::Service::GetLeaseInfo(LeaseInfo &aLeaseInfo) const
{
    TimeMilli now           = TimerMilli::GetNow();
    TimeMilli expireTime    = GetExpireTime();
    TimeMilli keyExpireTime = GetKeyExpireTime();

    aLeaseInfo.mLease             = Time::SecToMsec(GetLease());
    aLeaseInfo.mKeyLease          = Time::SecToMsec(GetKeyLease());
    aLeaseInfo.mRemainingLease    = (now <= expireTime) ? (expireTime - now) : 0;
    aLeaseInfo.mRemainingKeyLease = (now <= keyExpireTime) ? (keyExpireTime - now) : 0;
}

bool Server::Service::MatchesInstanceName(const char *aInstanceName) const
{
    return StringMatch(mDescription->mInstanceName.AsCString(), aInstanceName, kStringCaseInsensitiveMatch);
}

bool Server::Service::MatchesServiceName(const char *aServiceName) const
{
    return StringMatch(mServiceName.AsCString(), aServiceName, kStringCaseInsensitiveMatch);
}

bool Server::Service::MatchesFlags(Flags aFlags) const
{
    bool matches = false;

    if (IsSubType())
    {
        VerifyOrExit(aFlags & kFlagSubType);
    }
    else
    {
        VerifyOrExit(aFlags & kFlagBaseType);
    }

    if (IsDeleted())
    {
        VerifyOrExit(aFlags & kFlagDeleted);
    }
    else
    {
        VerifyOrExit(aFlags & kFlagActive);
    }

    matches = true;

exit:
    return matches;
}

#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
void Server::Service::Log(Action aAction) const
{
    static const char *const kActionStrings[] = {
        "Add new",                   // (0) kAddNew
        "Update existing",           // (1) kUpdateExisting
        "Remove but retain name of", // (2) kRemoveButRetainName
        "Fully remove",              // (3) kFullyRemove
        "LEASE expired for ",        // (4) kLeaseExpired
        "KEY LEASE expired for",     // (5) kKeyLeaseExpired
    };

    char subLabel[Dns::Name::kMaxLabelSize];

    static_assert(0 == kAddNew, "kAddNew value is incorrect");
    static_assert(1 == kUpdateExisting, "kUpdateExisting value is incorrect");
    static_assert(2 == kRemoveButRetainName, "kRemoveButRetainName value is incorrect");
    static_assert(3 == kFullyRemove, "kFullyRemove value is incorrect");
    static_assert(4 == kLeaseExpired, "kLeaseExpired value is incorrect");
    static_assert(5 == kKeyLeaseExpired, "kKeyLeaseExpired value is incorrect");

    // We only log if the `Service` is marked as committed. This
    // ensures that temporary `Service` entries associated with a
    // newly received SRP update message are not logged (e.g., when
    // associated `Host` is being freed).

    if (mIsCommitted)
    {
        IgnoreError(GetServiceSubTypeLabel(subLabel, sizeof(subLabel)));

        LogInfo("%s service '%s'%s%s", kActionStrings[aAction], GetInstanceName(), IsSubType() ? " subtype:" : "",
                subLabel);
    }
}
#else
void Server::Service::Log(Action) const
{
}
#endif // #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)

//---------------------------------------------------------------------------------------------------------------------
// Server::Service::Description

Error Server::Service::Description::Init(const char *aInstanceName, Host &aHost)
{
    mNext       = nullptr;
    mHost       = &aHost;
    mPriority   = 0;
    mWeight     = 0;
    mTtl        = 0;
    mPort       = 0;
    mLease      = 0;
    mKeyLease   = 0;
    mUpdateTime = TimerMilli::GetNow().GetDistantPast();
    mTxtData.Free();

    return mInstanceName.Set(aInstanceName);
}

bool Server::Service::Description::Matches(const char *aInstanceName) const
{
    return StringMatch(mInstanceName.AsCString(), aInstanceName, kStringCaseInsensitiveMatch);
}

void Server::Service::Description::ClearResources(void)
{
    mPort = 0;
    mTxtData.Free();
}

void Server::Service::Description::TakeResourcesFrom(Description &aDescription)
{
    mTxtData.SetFrom(static_cast<Heap::Data &&>(aDescription.mTxtData));

    mPriority = aDescription.mPriority;
    mWeight   = aDescription.mWeight;
    mPort     = aDescription.mPort;

    mTtl        = aDescription.mTtl;
    mLease      = aDescription.mLease;
    mKeyLease   = aDescription.mKeyLease;
    mUpdateTime = TimerMilli::GetNow();
}

Error Server::Service::Description::SetTxtDataFromMessage(const Message &aMessage, uint16_t aOffset, uint16_t aLength)
{
    Error error;

    SuccessOrExit(error = mTxtData.SetFrom(aMessage, aOffset, aLength));
    VerifyOrExit(Dns::TxtRecord::VerifyTxtData(mTxtData.GetBytes(), mTxtData.GetLength(), /* aAllowEmpty */ false),
                 error = kErrorParse);

exit:
    if (error != kErrorNone)
    {
        mTxtData.Free();
    }

    return error;
}

//---------------------------------------------------------------------------------------------------------------------
// Server::Host

Server::Host::Host(Instance &aInstance, TimeMilli aUpdateTime)
    : InstanceLocator(aInstance)
    , mNext(nullptr)
    , mTtl(0)
    , mLease(0)
    , mKeyLease(0)
    , mUpdateTime(aUpdateTime)
{
    mKeyRecord.Clear();
}

Server::Host::~Host(void)
{
    FreeAllServices();
}

Error Server::Host::SetFullName(const char *aFullName)
{
    // `mFullName` becomes immutable after it is set, so if it is
    // already set, we only accept a `aFullName` that matches the
    // current name.

    Error error;

    if (mFullName.IsNull())
    {
        error = mFullName.Set(aFullName);
    }
    else
    {
        error = Matches(aFullName) ? kErrorNone : kErrorFailed;
    }

    return error;
}

bool Server::Host::Matches(const char *aFullName) const
{
    return StringMatch(mFullName.AsCString(), aFullName, kStringCaseInsensitiveMatch);
}

void Server::Host::SetKeyRecord(Dns::Ecdsa256KeyRecord &aKeyRecord)
{
    OT_ASSERT(aKeyRecord.IsValid());

    mKeyRecord = aKeyRecord;
}

TimeMilli Server::Host::GetExpireTime(void) const
{
    OT_ASSERT(!IsDeleted());

    return mUpdateTime + Time::SecToMsec(mLease);
}

TimeMilli Server::Host::GetKeyExpireTime(void) const
{
    return mUpdateTime + Time::SecToMsec(mKeyLease);
}

void Server::Host::GetLeaseInfo(LeaseInfo &aLeaseInfo) const
{
    TimeMilli now           = TimerMilli::GetNow();
    TimeMilli expireTime    = GetExpireTime();
    TimeMilli keyExpireTime = GetKeyExpireTime();

    aLeaseInfo.mLease             = Time::SecToMsec(GetLease());
    aLeaseInfo.mKeyLease          = Time::SecToMsec(GetKeyLease());
    aLeaseInfo.mRemainingLease    = (now <= expireTime) ? (expireTime - now) : 0;
    aLeaseInfo.mRemainingKeyLease = (now <= keyExpireTime) ? (keyExpireTime - now) : 0;
}

Error Server::Host::ProcessTtl(uint32_t aTtl)
{
    // This method processes the TTL value received in a resource record.
    //
    // If no TTL value is stored, this method wil set the stored value to @p aTtl and return `kErrorNone`.
    // If a TTL value is stored and @p aTtl equals the stored value, this method returns `kErrorNone`.
    // Otherwise, this method returns `kErrorRejected`.

    Error error = kErrorRejected;

    VerifyOrExit(aTtl && (mTtl == 0 || mTtl == aTtl));

    mTtl = aTtl;

    error = kErrorNone;

exit:
    return error;
}

const Server::Service *Server::Host::FindNextService(const Service *aPrevService,
                                                     Service::Flags aFlags,
                                                     const char *   aServiceName,
                                                     const char *   aInstanceName) const
{
    const Service *service = (aPrevService == nullptr) ? GetServices().GetHead() : aPrevService->GetNext();

    for (; service != nullptr; service = service->GetNext())
    {
        if (!service->MatchesFlags(aFlags))
        {
            continue;
        }

        if ((aServiceName != nullptr) && !service->MatchesServiceName(aServiceName))
        {
            continue;
        }

        if ((aInstanceName != nullptr) && !service->MatchesInstanceName(aInstanceName))
        {
            continue;
        }

        break;
    }

    return service;
}

Server::Service *Server::Host::AddNewService(const char *aServiceName,
                                             const char *aInstanceName,
                                             bool        aIsSubType,
                                             TimeMilli   aUpdateTime)
{
    Service *                       service = nullptr;
    RetainPtr<Service::Description> desc(FindServiceDescription(aInstanceName));

    if (desc == nullptr)
    {
        desc.Reset(Service::Description::AllocateAndInit(aInstanceName, *this));
        VerifyOrExit(desc != nullptr);
    }

    service = Service::AllocateAndInit(aServiceName, *desc, aIsSubType, aUpdateTime);
    VerifyOrExit(service != nullptr);

    mServices.Push(*service);

exit:
    return service;
}

void Server::Host::RemoveService(Service *aService, RetainName aRetainName, NotifyMode aNotifyServiceHandler)
{
    Server &server = Get<Server>();

    VerifyOrExit(aService != nullptr);

    aService->mIsDeleted = true;

    aService->Log(aRetainName ? Service::kRemoveButRetainName : Service::kFullyRemove);

    if (aNotifyServiceHandler && server.mServiceUpdateHandler != nullptr)
    {
        uint32_t updateId = server.AllocateId();

        LogInfo("SRP update handler is notified (updatedId = %u)", updateId);
        server.mServiceUpdateHandler(updateId, this, kDefaultEventsHandlerTimeout, server.mServiceUpdateHandlerContext);
        // We don't wait for the reply from the service update handler,
        // but always remove the service regardless of service update result.
        // Because removing a service should fail only when there is system
        // failure of the platform mDNS implementation and in which case the
        // service is not expected to be still registered.
    }

    if (!aRetainName)
    {
        IgnoreError(mServices.Remove(*aService));
        aService->Free();
    }

exit:
    return;
}

Error Server::Host::AddCopyOfServiceAsDeletedIfNotPresent(const Service &aService, TimeMilli aUpdateTime)
{
    Error    error = kErrorNone;
    Service *newService;

    VerifyOrExit(FindService(aService.GetServiceName(), aService.GetInstanceName()) == nullptr);

    newService =
        AddNewService(aService.GetServiceName(), aService.GetInstanceName(), aService.IsSubType(), aUpdateTime);

    VerifyOrExit(newService != nullptr, error = kErrorNoBufs);

    newService->mDescription->mUpdateTime = aUpdateTime;
    newService->mIsDeleted                = true;

exit:
    return error;
}

void Server::Host::FreeAllServices(void)
{
    while (!mServices.IsEmpty())
    {
        RemoveService(mServices.GetHead(), kDeleteName, kDoNotNotifyServiceHandler);
    }
}

void Server::Host::ClearResources(void)
{
    mAddresses.Free();
}

Error Server::Host::MergeServicesAndResourcesFrom(Host &aHost)
{
    // This method merges services, service descriptions, and other
    // resources from another `aHost` into current host. It can
    // possibly take ownership of some items from `aHost`.

    Error error = kErrorNone;

    LogInfo("Update host %s", GetFullName());

    mAddresses.TakeFrom(static_cast<Heap::Array<Ip6::Address> &&>(aHost.mAddresses));
    mKeyRecord  = aHost.mKeyRecord;
    mTtl        = aHost.mTtl;
    mLease      = aHost.mLease;
    mKeyLease   = aHost.mKeyLease;
    mUpdateTime = TimerMilli::GetNow();

    for (Service &service : aHost.mServices)
    {
        Service *existingService = FindService(service.GetServiceName(), service.GetInstanceName());
        Service *newService;

        if (service.mIsDeleted)
        {
            // `RemoveService()` does nothing if `exitsingService` is `nullptr`.
            RemoveService(existingService, kRetainName, kDoNotNotifyServiceHandler);
            continue;
        }

        // Add/Merge `service` into the existing service or a allocate a new one

        newService = (existingService != nullptr) ? existingService
                                                  : AddNewService(service.GetServiceName(), service.GetInstanceName(),
                                                                  service.IsSubType(), service.GetUpdateTime());

        VerifyOrExit(newService != nullptr, error = kErrorNoBufs);

        newService->mIsDeleted   = false;
        newService->mIsCommitted = true;
        newService->mUpdateTime  = TimerMilli::GetNow();

        if (!service.mIsSubType)
        {
            // (1) Service description is shared across a base type and all its subtypes.
            // (2) `TakeResourcesFrom()` releases resources pinned to its argument.
            // Therefore, make sure the function is called only for the base type.
            newService->mDescription->TakeResourcesFrom(*service.mDescription);
        }

        newService->Log((existingService != nullptr) ? Service::kUpdateExisting : Service::kAddNew);
    }

exit:
    return error;
}

bool Server::Host::HasServiceInstance(const char *aInstanceName) const
{
    return (FindServiceDescription(aInstanceName) != nullptr);
}

const RetainPtr<Server::Service::Description> Server::Host::FindServiceDescription(const char *aInstanceName) const
{
    const Service::Description *desc = nullptr;

    for (const Service &service : mServices)
    {
        if (service.mDescription->Matches(aInstanceName))
        {
            desc = service.mDescription.Get();
            break;
        }
    }

    return RetainPtr<Service::Description>(AsNonConst(desc));
}

RetainPtr<Server::Service::Description> Server::Host::FindServiceDescription(const char *aInstanceName)
{
    return AsNonConst(AsConst(this)->FindServiceDescription(aInstanceName));
}

const Server::Service *Server::Host::FindService(const char *aServiceName, const char *aInstanceName) const
{
    return FindNextService(/* aPrevService */ nullptr, kFlagsAnyService, aServiceName, aInstanceName);
}

Server::Service *Server::Host::FindService(const char *aServiceName, const char *aInstanceName)
{
    return AsNonConst(AsConst(this)->FindService(aServiceName, aInstanceName));
}

const Server::Service *Server::Host::FindBaseService(const char *aInstanceName) const
{
    return FindNextService(/*a PrevService */ nullptr, kFlagsBaseTypeServiceOnly, /* aServiceName */ nullptr,
                           aInstanceName);
}

Server::Service *Server::Host::FindBaseService(const char *aInstanceName)
{
    return AsNonConst(AsConst(this)->FindBaseService(aInstanceName));
}

Error Server::Host::AddIp6Address(const Ip6::Address &aIp6Address)
{
    Error error = kErrorNone;

    if (aIp6Address.IsMulticast() || aIp6Address.IsUnspecified() || aIp6Address.IsLoopback())
    {
        // We don't like those address because they cannot be used
        // for communication with exterior devices.
        ExitNow(error = kErrorDrop);
    }

    // Drop duplicate addresses.
    VerifyOrExit(!mAddresses.Contains(aIp6Address), error = kErrorDrop);

    error = mAddresses.PushBack(aIp6Address);

    if (error == kErrorNoBufs)
    {
        LogWarn("Too many addresses for host %s", GetFullName());
    }

exit:
    return error;
}

//---------------------------------------------------------------------------------------------------------------------
// Server::UpdateMetadata

Server::UpdateMetadata::UpdateMetadata(Instance &aInstance, Host &aHost, const MessageMetadata &aMessageMetadata)
    : InstanceLocator(aInstance)
    , mNext(nullptr)
    , mExpireTime(TimerMilli::GetNow() + kDefaultEventsHandlerTimeout)
    , mDnsHeader(aMessageMetadata.mDnsHeader)
    , mId(Get<Server>().AllocateId())
    , mTtlConfig(aMessageMetadata.mTtlConfig)
    , mLeaseConfig(aMessageMetadata.mLeaseConfig)
    , mHost(aHost)
    , mIsDirectRxFromClient(aMessageMetadata.IsDirectRxFromClient())
{
    if (aMessageMetadata.mMessageInfo != nullptr)
    {
        mMessageInfo = *aMessageMetadata.mMessageInfo;
    }
}

} // namespace Srp
} // namespace ot

#endif // OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
