/*
 *  Copyright (c) 2016, 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 "coap.hpp"

#include "instance/instance.hpp"

/**
 * @file
 *   This file contains common code base for CoAP client and server.
 */

namespace ot {
namespace Coap {

RegisterLogModule("Coap");

//---------------------------------------------------------------------------------------------------------------------
// Msg

Error Msg::ParseHeaderAndOptions(PayloadMarkerMode aPayloadMarkerMode)
{
    // Parses and validates CoAP headers and options. Assumes
    // `GetHeaderOffset()` points to the header start.
    // `aPayloadMarkerMode` determines behavior regarding "payload
    // marker". Either rejects the message if payload marker is present
    // with no payload (used on a received message), or removes the
    // payload marker in such a situation (used on a message to be
    // sent). Upon completion, `GetOffset()` is updated to the start of
    // the payload.

    Error            error;
    Option::Iterator iterator;
    uint16_t         payloadOffset;
    bool             emptyPayload;

    SuccessOrExit(error = mMessage.ParseHeaderInfo(*this));

    SuccessOrExit(error = iterator.Init(mMessage));

    while (!iterator.IsDone())
    {
        SuccessOrExit(error = iterator.Advance());
    }

    payloadOffset = iterator.GetPayloadMessageOffset();
    emptyPayload  = (payloadOffset == mMessage.GetLength());

    if (iterator.HasPayloadMarker())
    {
        switch (aPayloadMarkerMode)
        {
        case kRejectIfNoPayloadWithPayloadMarker:
            VerifyOrExit(!emptyPayload, error = kErrorParse);
            break;

        case kRemovePayloadMarkerIfNoPayload:
            if (emptyPayload)
            {
                mMessage.RemoveFooter(sizeof(uint8_t));
                payloadOffset--;
            }
            break;
        }
    }

    mMessage.SetOffset(payloadOffset);

exit:
    return error;
}

uint16_t Msg::GetHeaderSize(void) const
{
    // Determines the size of the CoAP header including the token
    // but excluding any appended Options.

    return sizeof(Message::Header) + GetToken().GetLength();
}

void Msg::UpdateType(Type aType)
{
    mType = aType;
    mMessage.WriteType(aType);
}

void Msg::UpdateMessageId(uint16_t aMessageId)
{
    mMessageId = aMessageId;
    mMessage.WriteMessageId(aMessageId);
}

//---------------------------------------------------------------------------------------------------------------------
// CoapBase

CoapBase::CoapBase(Instance &aInstance, Transmitter aTransmitter)
    : InstanceLocator(aInstance)
    , mPendingRequests(aInstance, *this)
    , mResponseCache(aInstance)
    , mResourceHandler(nullptr)
    , mTransmitter(aTransmitter)
    , mMessageId(Random::NonCrypto::Generate<uint16_t>())
#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
    , mLastResponse(nullptr)
#endif
{
}

void CoapBase::ClearAllRequestsAndResponses(void)
{
    mPendingRequests.AbortAllRequests();
    mResponseCache.RemoveAll();
}

void CoapBase::AddResource(Resource &aResource) { IgnoreError(mResources.Add(aResource)); }

void CoapBase::RemoveResource(Resource &aResource)
{
    IgnoreError(mResources.Remove(aResource));
    aResource.SetNext(nullptr);
}

Message *CoapBase::AllocateAndInitPriorityConfirmablePostMessage(Uri aUri)
{
    return InitMessage(NewNetPriorityMessage(), kTypeConfirmable, aUri);
}

Message *CoapBase::AllocateAndInitConfirmablePostMessage(Uri aUri)
{
    return InitMessage(NewMessage(), kTypeConfirmable, aUri);
}

Message *CoapBase::AllocateAndInitPriorityNonConfirmablePostMessage(Uri aUri)
{
    return InitMessage(NewNetPriorityMessage(), kTypeNonConfirmable, aUri);
}

Message *CoapBase::AllocateAndInitNonConfirmablePostMessage(Uri aUri)
{
    return InitMessage(NewMessage(), kTypeNonConfirmable, aUri);
}

Message *CoapBase::AllocateAndInitPostMessageTo(Uri aUri, const Ip6::Address &aDestination)
{
    return InitMessage(NewMessage(), aDestination.IsMulticast() ? kTypeNonConfirmable : kTypeConfirmable, aUri);
}

Message *CoapBase::AllocateAndInitPriorityPostMessageTo(Uri aUri, const Ip6::Address &aDestination)
{
    return InitMessage(NewNetPriorityMessage(), aDestination.IsMulticast() ? kTypeNonConfirmable : kTypeConfirmable,
                       aUri);
}

Message *CoapBase::AllocateAndInitPriorityResponseFor(const Message &aRequest)
{
    return InitResponse(NewNetPriorityMessage(), aRequest);
}

Message *CoapBase::AllocateAndInitResponseFor(const Message &aRequest) { return InitResponse(NewMessage(), aRequest); }

Message *CoapBase::InitMessage(Message *aMessage, Type aType, Uri aUri)
{
    Error error = kErrorNone;

    VerifyOrExit(aMessage != nullptr);

    SuccessOrExit(error = aMessage->Init(aType, kCodePost, aUri));
    SuccessOrExit(error = aMessage->AppendPayloadMarker());

exit:
    FreeAndNullMessageOnError(aMessage, error);
    return aMessage;
}

Message *CoapBase::InitResponse(Message *aMessage, const Message &aRequest)
{
    Error error = kErrorNone;

    VerifyOrExit(aMessage != nullptr);

    SuccessOrExit(error = aMessage->InitAsResponse(kTypeAck, kCodeChanged, aRequest));
    SuccessOrExit(error = aMessage->AppendPayloadMarker());

exit:
    FreeAndNullMessageOnError(aMessage, error);
    return aMessage;
}

Error CoapBase::Transmit(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
    Error error;

#if OPENTHREAD_CONFIG_OTNS_ENABLE
    Get<Utils::Otns>().EmitCoapSend(aMessage, aMessageInfo);
#endif

    error = mTransmitter(*this, aMessage, aMessageInfo);

#if OPENTHREAD_CONFIG_OTNS_ENABLE
    if (error != kErrorNone)
    {
        Get<Utils::Otns>().EmitCoapSendFailure(error, aMessage, aMessageInfo);
    }
#endif
    return error;
}

Error CoapBase::SendMessage(Message                &aMessage,
                            const Ip6::MessageInfo &aMessageInfo,
                            const TxParameters     *aTxParameters,
                            const SendCallbacks    &aCallbacks)
{
    Error   error;
    Request request;
    Msg     txMsg(aMessage, aMessageInfo);

    request.Clear();

    SuccessOrExit(error = txMsg.ParseHeaderAndOptions(Msg::kRemovePayloadMarkerIfNoPayload));

    if (aTxParameters == nullptr)
    {
        aTxParameters = &TxParameters::GetDefault();
    }
    else
    {
        SuccessOrExit(error = aTxParameters->ValidateFor(txMsg));
    }

#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
    SuccessOrExit(error = ProcessBlockwiseSend(txMsg, aCallbacks));
#endif

    switch (txMsg.GetType())
    {
    case kTypeAck:
        mResponseCache.Add(txMsg, aTxParameters->CalculateExchangeLifetime());
        break;
    case kTypeReset:
        break;
    case kTypeConfirmable:
    case kTypeNonConfirmable:
        txMsg.UpdateMessageId(mMessageId++);
        break;
    }

    switch (txMsg.GetType())
    {
    case kTypeAck:
    case kTypeReset:
        break;

    case kTypeNonConfirmable:
        if (!aCallbacks.HasResponseHandler())
        {
            // Since a non-confirmable request is not retransmitted,
            // we only save it when a response handler is provided.
            break;
        }

        OT_FALL_THROUGH;

    case kTypeConfirmable:
        SuccessOrExit(error = mPendingRequests.Add(txMsg, *aTxParameters, aCallbacks, request));
        break;
    }

    SuccessOrExit(error = Transmit(txMsg.mMessage, txMsg.mMessageInfo));

exit:
    if (error != kErrorNone)
    {
        mPendingRequests.Remove(request);
    }

    return error;
}

Error CoapBase::SendMessage(Message                &aMessage,
                            const Ip6::MessageInfo &aMessageInfo,
                            const TxParameters     *aTxParameters,
                            ResponseHandler         aHandler,
                            void                   *aContext)
{
    SendCallbacks callbacks;

    callbacks.Clear();
    callbacks.mResponseHandler = aHandler;
    callbacks.mContext         = aContext;

    return SendMessage(aMessage, aMessageInfo, aTxParameters, callbacks);
}

Error CoapBase::SendMessage(Message &aMessage, const Ip6::MessageInfo &aMessageInfo, const TxParameters &aTxParameters)
{
    SendCallbacks callbacks;

    callbacks.Clear();

    return SendMessage(aMessage, aMessageInfo, &aTxParameters, callbacks);
}

Error CoapBase::SendMessage(Message                &aMessage,
                            const Ip6::MessageInfo &aMessageInfo,
                            const ResponseHandler   aHandler,
                            void                   *aContext)
{
    SendCallbacks callbacks;

    callbacks.Clear();
    callbacks.mContext         = aContext;
    callbacks.mResponseHandler = aHandler;

    return SendMessage(aMessage, aMessageInfo, /* aTxParameters */ nullptr, callbacks);
}

Error CoapBase::SendMessage(OwnedPtr<Message>       aMessage,
                            const Ip6::MessageInfo &aMessageInfo,
                            const ResponseHandler   aHandler,
                            void                   *aContext)
{
    Error error;

    OT_ASSERT(aMessage != nullptr);

    SuccessOrExit(error = SendMessage(*aMessage, aMessageInfo, aHandler, aContext));
    aMessage.Release();

exit:
    return error;
}

Error CoapBase::SendMessage(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
    return SendMessage(aMessage, aMessageInfo, nullptr, nullptr);
}

Error CoapBase::SendMessage(OwnedPtr<Message> aMessage, const Ip6::MessageInfo &aMessageInfo)
{
    Error error;

    OT_ASSERT(aMessage != nullptr);

    SuccessOrExit(error = SendMessage(*aMessage, aMessageInfo));
    aMessage.Release();

exit:
    return error;
}

Error CoapBase::SendMessageWithResponseHandlerSeparateParams(Message                      &aMessage,
                                                             const Ip6::MessageInfo       &aMessageInfo,
                                                             const TxParameters           *aTxParameters,
                                                             ResponseHandlerSeparateParams aHandler,
#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
                                                             BlockwiseTransmitHook aTransmitHook,
                                                             BlockwiseReceiveHook  aReceiveHook,
#endif
                                                             void *aContext)
{
    SendCallbacks callbacks;

    callbacks.Clear();
    callbacks.mResponseHandlerSeparateParams = aHandler;
    callbacks.mContext                       = aContext;
#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
    callbacks.mBlockwiseTransmitHook = aTransmitHook;
    callbacks.mBlockwiseReceiveHook  = aReceiveHook;
#endif

    return SendMessage(aMessage, aMessageInfo, aTxParameters, callbacks);
}

Error CoapBase::SendAckResponse(const Msg &aRxMsg, Code aCode)
{
    return (aRxMsg.IsConfirmable() ? SendResponse(aCode, aRxMsg) : kErrorInvalidArgs);
}

Error CoapBase::SendAckResponse(const Msg &aRxMsg) { return SendAckResponse(aRxMsg, kCodeChanged); }

Error CoapBase::SendAckResponseIfUnicastRequest(const Msg &aRxMsg, Error aError)
{
    Error error;

    VerifyOrExit(aRxMsg.IsConfirmable(), error = kErrorInvalidArgs);
    VerifyOrExit(!aRxMsg.mMessageInfo.GetSockAddr().IsMulticast(), error = kErrorInvalidArgs);

    error = SendResponse(Message::MapErrorToCoapCode(aError), aRxMsg);

exit:
    return error;
}

Error CoapBase::SendEmptyMessage(Type aType, const Msg &aRxMsg)
{
    Error    error   = kErrorNone;
    Message *message = nullptr;

    switch (aType)
    {
    case kTypeConfirmable:
        // An empty confirmable message is not used in normal
        // operation but only to elicit a Reset response. This is
        // used as "CoAP ping" (RFC 7573 section 4.3).
        break;

    case kTypeAck:
        VerifyOrExit(aRxMsg.IsConfirmable(), error = kErrorInvalidArgs);
        break;

    case kTypeReset:
        break;

    case kTypeNonConfirmable:
        ExitNow(error = kErrorInvalidArgs);
    }

    VerifyOrExit((message = NewMessage()) != nullptr, error = kErrorNoBufs);

    SuccessOrExit(error = message->Init(aType, kCodeEmpty, aRxMsg.GetMessageId()));
    SuccessOrExit(error = Transmit(*message, aRxMsg.mMessageInfo));

exit:
    FreeMessageOnError(message, error);
    return error;
}

Error CoapBase::SendResponse(Message::Code aCode, const Msg &aRxMsg)
{
    Error    error   = kErrorNone;
    Message *message = nullptr;

    VerifyOrExit(aRxMsg.IsRequest(), error = kErrorInvalidArgs);
    VerifyOrExit((message = NewMessage()) != nullptr, error = kErrorNoBufs);

    switch (aRxMsg.GetType())
    {
    case kTypeConfirmable:
        SuccessOrExit(error = message->Init(kTypeAck, aCode, aRxMsg.GetMessageId()));
        break;

    case kTypeNonConfirmable:
        SuccessOrExit(error = message->Init(kTypeNonConfirmable, aCode));
        break;

    default:
        ExitNow(error = kErrorInvalidArgs);
    }

    SuccessOrExit(error = message->WriteTokenFromMessage(aRxMsg.mMessage));

    SuccessOrExit(error = SendMessage(*message, aRxMsg.mMessageInfo));

exit:
    FreeMessageOnError(message, error);
    return error;
}

Error CoapBase::AbortTransaction(ResponseHandler aHandler, void *aContext)
{
    return mPendingRequests.AbortRequestsMatching(aHandler, aContext);
}

void CoapBase::GetRequestAndCachedResponsesQueueInfo(MessageQueue::Info &aQueueInfo) const
{
    MessageQueue::Info info;

    mPendingRequests.GetInfo(aQueueInfo);
    mResponseCache.GetInfo(info);
    MessageQueue::AddQueueInfos(aQueueInfo, info);
}

void CoapBase::Receive(ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
    Msg rxMsg(AsCoapMessage(&aMessage), aMessageInfo);

    rxMsg.mMessage.SetHeaderOffset(rxMsg.mMessage.GetOffset());

    if (rxMsg.ParseHeaderAndOptions(Msg::kRejectIfNoPayloadWithPayloadMarker) != kErrorNone)
    {
        LogDebg("Failed to parse CoAP header");

        if (!aMessageInfo.GetSockAddr().IsMulticast() && rxMsg.IsConfirmable())
        {
            IgnoreError(SendEmptyMessage(kTypeReset, rxMsg));
        }

        ExitNow();
    }

    if (rxMsg.IsRequest())
    {
        ProcessReceivedRequest(rxMsg);
    }
    else
    {
        ProcessReceivedResponse(rxMsg);
    }

#if OPENTHREAD_CONFIG_OTNS_ENABLE
    Get<Utils::Otns>().EmitCoapReceive(rxMsg.mMessage, aMessageInfo);
#endif

exit:
    return;
}

void CoapBase::ProcessReceivedResponse(Msg &aRxMsg)
{
    Error   error;
    Request request;
#if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
    bool shouldObserve = false;
#endif

    error = mPendingRequests.FindRelatedRequest(aRxMsg, request);

    if (error != kErrorNone)
    {
        bool didHandle = InvokeResponseFallback(aRxMsg);

        if (!didHandle && aRxMsg.RequireResetOnError())
        {
            // Successfully parsed a header but no matching request was
            // found - reject the message by sending reset.

            IgnoreError(SendEmptyMessage(kTypeReset, aRxMsg));
        }

        ExitNow();
    }

#if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
    // If there's an Observe option present in both request and
    // response, and we have a response handler; then we're dealing
    // with RFC7641 rules here. If there is no response handler, then
    // we're wasting our time!
    if (request.IsObserve() && request.IsRequest() && request.HasResponseHandler())
    {
        Option::Iterator iterator;

        SuccessOrExit(error = iterator.Init(aRxMsg.mMessage, kOptionObserve));
        shouldObserve = !iterator.IsDone();
    }
#endif

    switch (aRxMsg.GetType())
    {
    case kTypeReset:
        // Silently ignore non-empty reset messages (RFC 7252, Section 4.2).
        VerifyOrExit(aRxMsg.IsEmpty());
        mPendingRequests.FinalizeRequest(request, kErrorAbort);
        break;

    case kTypeAck:
        if (aRxMsg.IsEmpty())
        {
            // Empty acknowledgment.

#if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
            if (request.IsObserve() && !request.IsRequest())
            {
                // This is the ACK to our RFC7641 CON notification.
                // There will be no "separate" response so pass it back
                // as if it were a piggy-backed response so we can stop
                // re-sending and the application can move on.

                mPendingRequests.FinalizeRequest(request, kErrorNone, &aRxMsg);
                ExitNow();
            }
#endif

            if (request.IsConfirmable())
            {
                request.MarkAsAcknowledged();
            }

            // Remove the message if response is not expected, otherwise await
            // response.
            if (!request.HasResponseHandler())
            {
                mPendingRequests.Remove(request);
            }

            ExitNow();
        }

        if (aRxMsg.IsResponse() && aRxMsg.mMessage.HasSameTokenAs(request.GetMessage()))
        {
            // Piggybacked response.

#if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
            if (shouldObserve)
            {
                // This is a RFC7641 notification.  The request is *not* done!
                mPendingRequests.DispatchResponse(request, kErrorNone, &aRxMsg);

                request.MarkAsAcknowledged();
                ExitNow();
            }
#endif

#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
            SuccessOrExit(error = ProcessBlockwiseResponse(aRxMsg, request));
#else
            mPendingRequests.FinalizeRequest(request, kErrorNone, &aRxMsg);
#endif
        }

        // Silently ignore acknowledgments carrying requests (RFC 7252, p. 4.2)
        // or with no token match (RFC 7252, p. 5.3.2)

        break;

    case kTypeConfirmable:
        // Received a confirmable response, send an Empty Ack message.
        IgnoreError(SendEmptyMessage(kTypeAck, aRxMsg));

        OT_FALL_THROUGH;

    case kTypeNonConfirmable:

#if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
        if (shouldObserve)
        {
            mPendingRequests.DispatchResponse(request, kErrorNone, &aRxMsg);

            // When any Observe response is seen, consider a NON observe
            // request "acknowledged" at this point. This will keep the
            // Observe request active indefinitely until it is
            // canceled.

            if (!request.IsConfirmable())
            {
                request.MarkAsAcknowledged();
            }

            ExitNow();
        }
#endif

        // If the request was to a multicast address, then this is NOT
        // the final message, we may see more.

        if (request.HasResponseHandler() && request.GetDestinationAddress().IsMulticast())
        {
            mPendingRequests.DispatchResponse(request, kErrorNone, &aRxMsg);
        }
        else
        {
            mPendingRequests.FinalizeRequest(request, kErrorNone, &aRxMsg);
        }

        break;
    }

exit:
    return;
}

bool CoapBase::InvokeResponseFallback(Msg &aRxMsg) const
{
    bool didHandle = false;

    VerifyOrExit(mResponseFallback.IsSet());
    didHandle = mResponseFallback.Invoke(&aRxMsg.mMessage, &aRxMsg.mMessageInfo);

exit:
    return didHandle;
}

void CoapBase::ProcessReceivedRequest(Msg &aRxMsg)
{
    Message::UriPathStringBuffer uriPath;
    Error                        error = kErrorNone;

    if (mInterceptor.IsSet())
    {
        SuccessOrExit(error = mInterceptor.Invoke(aRxMsg));
    }

    // Check if `mResponseCache` has a matching cached response for this
    // request and send it. Only if not found (`kErrorNotFound`), we
    // continue to process the `aRxMsg.mMessage` further.

    error = mResponseCache.SendCachedResponse(aRxMsg, *this);

    switch (error)
    {
    case kErrorNotFound:
        break;
    case kErrorNone:
    default:
        ExitNow();
    }

    SuccessOrExit(error = aRxMsg.mMessage.ReadUriPathOptions(uriPath));

#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
    {
        bool didHandle = false;

        SuccessOrExit(error = ProcessBlockwiseRequest(aRxMsg, uriPath, didHandle));
        VerifyOrExit(!didHandle);
    }
#endif

    if ((mResourceHandler != nullptr) && mResourceHandler(*this, uriPath, aRxMsg))
    {
        error = kErrorNone;
        ExitNow();
    }

    for (const Resource &resource : mResources)
    {
        if (StringMatch(resource.mUriPath, uriPath))
        {
            resource.HandleRequest(aRxMsg);
            error = kErrorNone;
            ExitNow();
        }
    }

    if (mDefaultHandler.IsSet())
    {
        mDefaultHandler.Invoke(&aRxMsg.mMessage, &aRxMsg.mMessageInfo);
        error = kErrorNone;
        ExitNow();
    }

    error = kErrorNotFound;

exit:
    LogInfoOnError(error, "process request");

    if (error == kErrorNotFound && !aRxMsg.mMessageInfo.GetSockAddr().IsMulticast())
    {
        IgnoreError(SendResponse(kCodeNotFound, aRxMsg));
    }
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// `CoapBase` - BLockwise transfer methods

#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE

void CoapBase::AddBlockWiseResource(ResourceBlockWise &aResource) { IgnoreError(mBlockWiseResources.Add(aResource)); }

void CoapBase::RemoveBlockWiseResource(ResourceBlockWise &aResource)
{
    IgnoreError(mBlockWiseResources.Remove(aResource));
    aResource.SetNext(nullptr);
}

Error CoapBase::ProcessBlockwiseSend(Msg &aMsg, const SendCallbacks &aCallbacks)
{
    Error     error      = kErrorNone;
    uint8_t   type       = aMsg.GetType();
    bool      moreBlocks = false;
    uint16_t  blockSize;
    uint8_t   buf[kMaxBlockSize];
    BlockInfo blockInfo;

    VerifyOrExit(type != kTypeReset);

    VerifyOrExit(aCallbacks.HasBlockwiseTransmitHook());

    SuccessOrExit(aMsg.mMessage.ReadBlockOptionValues(type == kTypeAck ? kOptionBlock2 : kOptionBlock1, blockInfo));

    VerifyOrExit(blockInfo.mBlockNumber == 0);

    blockSize = blockInfo.GetBlockSize();
    VerifyOrExit(blockSize <= kMaxBlockSize, error = kErrorNoBufs);

    SuccessOrExit(error = aCallbacks.mBlockwiseTransmitHook(aCallbacks.mContext, buf, 0, &blockSize, &moreBlocks));

    SuccessOrExit(error = aMsg.mMessage.AppendPayloadMarker());
    SuccessOrExit(error = aMsg.mMessage.AppendBytes(buf, blockSize));

    switch (type)
    {
    case kTypeAck:
        SuccessOrExit(error = CacheLastBlockResponse(&aMsg.mMessage));
        break;

    case kTypeNonConfirmable:
        // Block-Wise messages always have to be confirmable
        aMsg.UpdateType(kTypeConfirmable);
        break;

    default:
        break;
    }

exit:
    return error;
}

Error CoapBase::ProcessBlockwiseResponse(Msg &aRxMsg, Request &aRequest)
{
    Error    error             = kErrorNone;
    uint8_t  blockOptionType   = 0;
    uint32_t totalTransferSize = 0;

    if (aRequest.HasBlockwiseTransmitHook() || aRequest.HasBlockwiseReceiveHook())
    {
        // Search for CoAP Block-Wise Option [RFC7959]
        Option::Iterator iterator;

        SuccessOrExit(error = iterator.Init(aRxMsg.mMessage));

        while (!iterator.IsDone())
        {
            switch (iterator.GetOption()->GetNumber())
            {
            case kOptionBlock1:
                blockOptionType += 1;
                break;

            case kOptionBlock2:
                blockOptionType += 2;
                break;

            case kOptionSize2:
                // ToDo: wait for method to read uint option values
                totalTransferSize = 0;
                break;

            default:
                break;
            }

            SuccessOrExit(error = iterator.Advance());
        }
    }

    switch (blockOptionType)
    {
    case 0:
        // Piggybacked response.
        mPendingRequests.FinalizeRequest(aRequest, kErrorNone, &aRxMsg);
        break;
    case 1: // Block1 option
        if (aRxMsg.GetCode() == kCodeContinue && aRequest.HasBlockwiseTransmitHook())
        {
            error = SendNextBlock1Request(aRequest, aRxMsg);
        }

        if (aRxMsg.GetCode() != kCodeContinue || !aRequest.HasBlockwiseTransmitHook() || error != kErrorNone)
        {
            mPendingRequests.FinalizeRequest(aRequest, error, &aRxMsg);
        }
        break;
    case 2: // Block2 option
        if (aRxMsg.GetCode() < kCodeBadRequest && aRequest.HasBlockwiseReceiveHook())
        {
            error = SendNextBlock2Request(aRequest, aRxMsg, totalTransferSize, false);
        }

        if (aRxMsg.GetCode() >= kCodeBadRequest || !aRequest.HasBlockwiseReceiveHook() || error != kErrorNone)
        {
            mPendingRequests.FinalizeRequest(aRequest, error, &aRxMsg);
        }
        break;
    case 3: // Block1 & Block2 option
        if (aRxMsg.GetCode() < kCodeBadRequest && aRequest.HasBlockwiseReceiveHook())
        {
            error = SendNextBlock2Request(aRequest, aRxMsg, totalTransferSize, true);
        }

        mPendingRequests.FinalizeRequest(aRequest, error, &aRxMsg);
        break;
    default:
        error = kErrorAbort;
        mPendingRequests.FinalizeRequest(aRequest, error, &aRxMsg);
        break;
    }

exit:
    return error;
}

Error CoapBase::ProcessBlockwiseRequest(Msg &aRxMsg, const Message::UriPathStringBuffer &aUriPath, bool &aDidHandle)
{
    Error            error = kErrorNone;
    Option::Iterator iterator;
    uint8_t          blockOptionType   = 0;
    uint32_t         totalTransferSize = 0;

    SuccessOrExit(error = iterator.Init(aRxMsg.mMessage));

    while (!iterator.IsDone())
    {
        switch (iterator.GetOption()->GetNumber())
        {
        case kOptionBlock1:
            blockOptionType += 1;
            break;

        case kOptionBlock2:
            blockOptionType += 2;
            break;

        case kOptionSize1:
            // ToDo: wait for method to read uint option values
            totalTransferSize = 0;
            break;

        default:
            break;
        }

        SuccessOrExit(error = iterator.Advance());
    }

    for (const ResourceBlockWise &resource : mBlockWiseResources)
    {
        if (!StringMatch(resource.GetUriPath(), aUriPath))
        {
            continue;
        }

        if ((resource.mReceiveHook != nullptr || resource.mTransmitHook != nullptr) && blockOptionType != 0)
        {
            switch (blockOptionType)
            {
            case 1:
                if (resource.mReceiveHook != nullptr)
                {
                    switch (ProcessBlock1Request(aRxMsg, resource, totalTransferSize))
                    {
                    case kErrorNone:
                        resource.HandleRequest(aRxMsg);
                        OT_FALL_THROUGH;
                    case kErrorBusy:
                        error = kErrorNone;
                        break;
                    case kErrorNoBufs:
                        IgnoreError(SendResponse(kCodeRequestTooLarge, aRxMsg));
                        error = kErrorDrop;
                        break;
                    case kErrorNoFrameReceived:
                        IgnoreError(SendResponse(kCodeRequestIncomplete, aRxMsg));
                        error = kErrorDrop;
                        break;
                    default:
                        IgnoreError(SendResponse(kCodeInternalError, aRxMsg));
                        error = kErrorDrop;
                        break;
                    }
                }
                break;
            case 2:
                if (resource.mTransmitHook != nullptr)
                {
                    switch (ProcessBlock2Request(aRxMsg, resource))
                    {
                    case kErrorNone:
                        break;
                    case kErrorNoFrameReceived:
                        IgnoreError(SendResponse(kCodeRequestIncomplete, aRxMsg));
                        error = kErrorDrop;
                        break;
                    default:
                        IgnoreError(SendResponse(kCodeInternalError, aRxMsg));
                        error = kErrorDrop;
                        break;
                    }
                }
                break;
            }

            aDidHandle = true;
            ExitNow();
        }
        else
        {
            resource.HandleRequest(aRxMsg);
            error      = kErrorNone;
            aDidHandle = true;
            ExitNow();
        }
    }

exit:
    return error;
}

void CoapBase::FreeLastBlockResponse(void)
{
    if (mLastResponse != nullptr)
    {
        mLastResponse->Free();
        mLastResponse = nullptr;
    }
}

Error CoapBase::CacheLastBlockResponse(Message *aResponse)
{
    Error error = kErrorNone;

    FreeLastBlockResponse();

    mLastResponse = AsCoapMessagePtr(aResponse->Clone<kNoReservedHeader>());
    VerifyOrExit(mLastResponse != nullptr, error = kErrorNoBufs);

exit:
    return error;
}

Error CoapBase::PrepareNextBlockRequest(uint16_t         aBlockOptionNumber,
                                        Request         &aRequestOld,
                                        Message         &aRequest,
                                        const BlockInfo &aBlockInfo)
{
    Error            error;
    bool             isOptionSet = false;
    Option::Iterator iterator;

    SuccessOrExit(
        error = aRequest.Init(kTypeConfirmable, static_cast<ot::Coap::Code>(aRequestOld.GetMessage().ReadCode())));

    aRequestOld.RemoveMetadataFromMessage();

    // Per RFC 7959, all requests in a block-wise transfer MUST use the
    // same token.
    IgnoreError(aRequest.WriteTokenFromMessage(aRequestOld.GetMessage()));

    // Copy options from last response to next message

    SuccessOrExit(error = iterator.Init(aRequestOld.GetMessage()));

    for (; !iterator.IsDone() && iterator.GetOption()->GetLength() != 0; error = iterator.Advance())
    {
        uint16_t optionNumber = iterator.GetOption()->GetNumber();

        SuccessOrExit(error);

        // Check if option to copy next is higher than or equal to Block1 option
        if (optionNumber >= aBlockOptionNumber && !isOptionSet)
        {
            SuccessOrExit(error = aRequest.AppendBlockOption(aBlockOptionNumber, aBlockInfo));

            isOptionSet = true;

            // If option to copy next is Block1 or Block2 option, option is not copied
            if (optionNumber == kOptionBlock1 || optionNumber == kOptionBlock2)
            {
                continue;
            }
        }

        // Copy option
        SuccessOrExit(error = aRequest.AppendOptionFromMessage(optionNumber, iterator.GetOption()->GetLength(),
                                                               iterator.GetMessage(),
                                                               iterator.GetOptionValueMessageOffset()));
    }

    if (!isOptionSet)
    {
        SuccessOrExit(error = aRequest.AppendBlockOption(aBlockOptionNumber, aBlockInfo));
    }

    error = aRequestOld.AppendMetadataToMessage();

exit:
    return error;
}

Error CoapBase::SendNextBlock1Request(Request &aRequest, Msg &aRxMsg)
{
    Error     error              = kErrorNone;
    Message  *request            = nullptr;
    uint8_t   buf[kMaxBlockSize] = {0};
    uint16_t  blockSize;
    BlockInfo msgBlockInfo;
    BlockInfo requestBlockInfo;

    SuccessOrExit(error = aRequest.GetMessage().ReadBlockOptionValues(kOptionBlock1, requestBlockInfo));
    SuccessOrExit(error = aRxMsg.mMessage.ReadBlockOptionValues(kOptionBlock1, msgBlockInfo));

    // Conclude block-wise transfer if last block has been received
    if (!requestBlockInfo.mMoreBlocks)
    {
        mPendingRequests.FinalizeRequest(aRequest, kErrorNone, &aRxMsg);
        ExitNow();
    }

    blockSize = msgBlockInfo.GetBlockSize();
    VerifyOrExit(blockSize <= kMaxBlockSize, error = kErrorNoBufs);

    requestBlockInfo.mBlockNumber = msgBlockInfo.mBlockNumber + 1;
    requestBlockInfo.mBlockSzx    = msgBlockInfo.mBlockSzx;
    requestBlockInfo.mMoreBlocks  = false;

    SuccessOrExit(error = aRequest.GetCallbacks().mBlockwiseTransmitHook(aRequest.GetCallbacks().mContext, buf,
                                                                         requestBlockInfo.GetBlockOffsetPosition(),
                                                                         &blockSize, &requestBlockInfo.mMoreBlocks));

    VerifyOrExit(blockSize <= msgBlockInfo.GetBlockSize(), error = kErrorInvalidArgs);

    VerifyOrExit((request = NewMessage()) != nullptr, error = kErrorNoBufs);

    SuccessOrExit(error = PrepareNextBlockRequest(kOptionBlock1, aRequest, *request, requestBlockInfo));

    SuccessOrExit(error = request->AppendPayloadMarker());

    SuccessOrExit(error = request->AppendBytes(buf, blockSize));

    mPendingRequests.Remove(aRequest);

    LogInfo("Send Block1 Nr. %d, Size: %d bytes, More Blocks Flag: %d", requestBlockInfo.mBlockNumber,
            requestBlockInfo.GetBlockSize(), requestBlockInfo.mMoreBlocks);

    SuccessOrExit(error =
                      SendMessage(*request, aRxMsg.mMessageInfo, /* aTxParamters */ nullptr, aRequest.GetCallbacks()));

exit:
    FreeMessageOnError(request, error);

    return error;
}

Error CoapBase::SendNextBlock2Request(Request &aRequest, Msg &aRxMsg, uint32_t aTotalLength, bool aBeginBlock1Transfer)
{
    Error         error   = kErrorNone;
    Message      *request = nullptr;
    uint8_t       buf[kMaxBlockSize];
    OffsetRange   offsetRange;
    BlockInfo     msgBlockInfo;
    BlockInfo     requestBlockInfo;
    SendCallbacks callbacks;

    SuccessOrExit(error = aRxMsg.mMessage.ReadBlockOptionValues(kOptionBlock2, msgBlockInfo));

    VerifyOrExit(msgBlockInfo.GetBlockSize() <= kMaxBlockSize, error = kErrorNoBufs);

    offsetRange.InitFromMessageOffsetToEnd(aRxMsg.mMessage);
    VerifyOrExit(offsetRange.GetLength() <= msgBlockInfo.GetBlockSize(), error = kErrorNoBufs);

    aRxMsg.mMessage.ReadBytes(offsetRange, buf);
    SuccessOrExit(error = aRequest.GetCallbacks().mBlockwiseReceiveHook(
                      aRequest.GetCallbacks().mContext, buf, msgBlockInfo.GetBlockOffsetPosition(),
                      offsetRange.GetLength(), msgBlockInfo.mMoreBlocks, aTotalLength));

    LogInfo("Received Block2 Nr. %d , Size: %d bytes, More Blocks Flag: %d", msgBlockInfo.mBlockNumber,
            msgBlockInfo.GetBlockSize(), msgBlockInfo.mMoreBlocks);

    if (!msgBlockInfo.mMoreBlocks)
    {
        mPendingRequests.FinalizeRequest(aRequest, kErrorNone, &aRxMsg);
        ExitNow();
    }

    VerifyOrExit((request = NewMessage()) != nullptr, error = kErrorNoBufs);

    requestBlockInfo = msgBlockInfo;
    requestBlockInfo.mBlockNumber++;
    requestBlockInfo.mMoreBlocks = false; // RFC 7959 Section 2.3 second bullet: MUST be 0 in request.

    SuccessOrExit(error = PrepareNextBlockRequest(kOptionBlock2, aRequest, *request, requestBlockInfo));

    if (!aBeginBlock1Transfer)
    {
        mPendingRequests.Remove(aRequest);
    }

    LogInfo("Request Block2 Nr. %d, Size: %d bytes", requestBlockInfo.mBlockNumber, requestBlockInfo.GetBlockSize());

    callbacks                        = aRequest.GetCallbacks();
    callbacks.mBlockwiseTransmitHook = nullptr;

    SuccessOrExit(error = SendMessage(*request, aRxMsg.mMessageInfo, /* aTxParameters */ nullptr, callbacks));

exit:
    FreeMessageOnError(request, error);

    return error;
}

Error CoapBase::ProcessBlock1Request(Msg &aRxMsg, const ResourceBlockWise &aResource, uint32_t aTotalLength)
{
    Error       error    = kErrorNone;
    Message    *response = nullptr;
    uint8_t     buf[kMaxBlockSize];
    OffsetRange offsetRange;
    BlockInfo   msgBlockInfo;

    SuccessOrExit(error = aRxMsg.mMessage.ReadBlockOptionValues(kOptionBlock1, msgBlockInfo));

    offsetRange.InitFromMessageOffsetToEnd(aRxMsg.mMessage);
    VerifyOrExit(offsetRange.GetLength() <= kMaxBlockSize, error = kErrorNoBufs);

    aRxMsg.mMessage.ReadBytes(offsetRange, buf);
    SuccessOrExit(error =
                      aResource.HandleBlockReceive(buf, msgBlockInfo.GetBlockOffsetPosition(), offsetRange.GetLength(),
                                                   msgBlockInfo.mMoreBlocks, aTotalLength));

    if (msgBlockInfo.mMoreBlocks)
    {
        // Set up next response
        VerifyOrExit((response = NewMessage()) != nullptr, error = kErrorFailed);
        SuccessOrExit(error = response->Init(kTypeAck, kCodeContinue, aRxMsg.GetMessageId()));
        SuccessOrExit(error = response->WriteTokenFromMessage(aRxMsg.mMessage));

        SuccessOrExit(error = response->AppendBlockOption(kOptionBlock1, msgBlockInfo));

        SuccessOrExit(error = CacheLastBlockResponse(response));

        LogInfo("Acknowledge Block1 Nr. %d, Size: %d bytes", msgBlockInfo.mBlockNumber, msgBlockInfo.GetBlockSize());

        SuccessOrExit(error = SendMessage(*response, aRxMsg.mMessageInfo));

        error = kErrorBusy;
    }
    else
    {
        // Conclude block-wise transfer if last block has been received
        FreeLastBlockResponse();
        error = kErrorNone;
    }

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

    return error;
}

Error CoapBase::ProcessBlock2Request(Msg &aRxMsg, const ResourceBlockWise &aResource)
{
    Error            error              = kErrorNone;
    Message         *response           = nullptr;
    uint64_t         optionBuf          = 0;
    uint8_t          buf[kMaxBlockSize] = {0};
    uint16_t         blockSize;
    Option::Iterator iterator;
    BlockInfo        msgBlockInfo;
    BlockInfo        responseBlockInfo;

    SuccessOrExit(error = aRxMsg.mMessage.ReadBlockOptionValues(kOptionBlock2, msgBlockInfo));

    LogInfo("Request for Block2 Nr. %d, Size: %d bytes received", msgBlockInfo.mBlockNumber,
            msgBlockInfo.GetBlockSize());

    if (msgBlockInfo.mBlockNumber == 0)
    {
        aResource.HandleRequest(aRxMsg);
        ExitNow();
    }

    VerifyOrExit(mLastResponse != nullptr, error = kErrorNoFrameReceived);

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

    SuccessOrExit(error = response->Init(kTypeAck, kCodeContent, aRxMsg.GetMessageId()));
    SuccessOrExit(error = response->WriteTokenFromMessage(aRxMsg.mMessage));

    responseBlockInfo.mMoreBlocks = false;

    VerifyOrExit((blockSize = msgBlockInfo.GetBlockSize()) <= kMaxBlockSize, error = kErrorNoBufs);
    SuccessOrExit(error = aResource.HandleBlockTransmit(buf, msgBlockInfo.GetBlockOffsetPosition(), &blockSize,
                                                        &responseBlockInfo.mMoreBlocks));

    if (responseBlockInfo.mMoreBlocks)
    {
        SuccessOrExit(error = DetermineBlockSzxFromSize(blockSize, responseBlockInfo.mBlockSzx));
    }
    else
    {
        VerifyOrExit(blockSize <= msgBlockInfo.GetBlockSize(), error = kErrorInvalidArgs);
        responseBlockInfo.mBlockSzx = msgBlockInfo.mBlockSzx;
    }

    responseBlockInfo.mBlockNumber = msgBlockInfo.GetBlockOffsetPosition() / responseBlockInfo.GetBlockSize();

    // Copy options from last response
    SuccessOrExit(error = iterator.Init(*mLastResponse));

    while (!iterator.IsDone())
    {
        uint16_t optionNumber = iterator.GetOption()->GetNumber();

        if (optionNumber == kOptionBlock2)
        {
            SuccessOrExit(error = response->AppendBlockOption(kOptionBlock2, responseBlockInfo));
        }
        else if (optionNumber == kOptionBlock1)
        {
            SuccessOrExit(error = iterator.ReadOptionValue(&optionBuf));
            SuccessOrExit(error = response->AppendOption(optionNumber, iterator.GetOption()->GetLength(), &optionBuf));
        }

        SuccessOrExit(error = iterator.Advance());
    }

    SuccessOrExit(error = response->AppendPayloadMarker());
    SuccessOrExit(error = response->AppendBytes(buf, blockSize));

    if (responseBlockInfo.mMoreBlocks)
    {
        SuccessOrExit(error = CacheLastBlockResponse(response));
    }
    else
    {
        // Conclude block-wise transfer if last block has been received
        FreeLastBlockResponse();
    }

    LogInfo("Send Block2 Nr. %d, Size: %d bytes, More Blocks Flag %d", responseBlockInfo.mBlockNumber,
            responseBlockInfo.GetBlockSize(), responseBlockInfo.mMoreBlocks);

    SuccessOrExit(error = SendMessage(*response, aRxMsg.mMessageInfo));

exit:
    FreeMessageOnError(response, error);

    return error;
}

Error CoapBase::DetermineBlockSzxFromSize(uint16_t aSize, BlockSzx &aBlockSzx)
{
    Error error = kErrorNone;

    for (uint8_t szx = kBlockSzx16; szx <= kBlockSzx1024; szx++)
    {
        aBlockSzx = static_cast<BlockSzx>(szx);

        if (BlockSizeFromExponent(aBlockSzx) == aSize)
        {
            ExitNow();
        }
    }

    error = kErrorInvalidArgs;

exit:
    return error;
}

#endif // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// `CoapBase` - Observe methods

#if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE

Error CoapBase::PendingRequests::ProcessObserveSend(const Msg &aTxMsg, Request &aRequest)
{
    Error            error;
    Option::Iterator iterator;

    aRequest.mMetadata.mObserve   = false;
    aRequest.mMetadata.mIsRequest = aTxMsg.IsRequest();

    SuccessOrExit(error = iterator.Init(aTxMsg.mMessage, kOptionObserve));
    aRequest.mMetadata.mObserve = !iterator.IsDone();

    // Special case, if we're sending a GET with Observe=1, that is a
    // cancellation.

    if (aRequest.mMetadata.mObserve && aTxMsg.IsGetRequest())
    {
        uint64_t value = 0;

        SuccessOrExit(error = iterator.ReadOptionValue(value));

        if (value == 1)
        {
            Request request;

            aRequest.mMetadata.mObserve = false;

            // If we can find the previous matching request, cancel that too.

            if (FindRelatedRequest(aTxMsg, request) == kErrorNone)
            {
                FinalizeRequest(request, kErrorNone);
            }
        }
    }

exit:
    return error;
}

#endif // OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE

//---------------------------------------------------------------------------------------------------------------------
// CoapBase::SendCallbacks

void CoapBase::SendCallbacks::Clear(void)
{
    // We avoid using `ClearAllBytes()` or `Clearable` because they
    // zero out all object memory. Unlike standard data pointers, the
    // C++ standard does not strictly guarantee that a `nullptr`
    // function pointer is represented by an "all-bits-zero" memory
    // pattern.

    mContext                       = nullptr;
    mResponseHandler               = nullptr;
    mResponseHandlerSeparateParams = nullptr;
#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
    mBlockwiseReceiveHook  = nullptr;
    mBlockwiseTransmitHook = nullptr;
#endif
}

bool CoapBase::SendCallbacks::HasResponseHandler(void) const
{
    return (mResponseHandler != nullptr) || (mResponseHandlerSeparateParams != nullptr);
}

bool CoapBase::SendCallbacks::Matches(ResponseHandler aHandler, void *aContext) const
{
    return (mResponseHandler == aHandler) && (mContext == aContext);
}

void CoapBase::SendCallbacks::InvokeResponseHandler(Msg *aMsg, Error aResult) const
{
    if (mResponseHandler != nullptr)
    {
        mResponseHandler(mContext, aMsg, aResult);
    }
    else if (mResponseHandlerSeparateParams != nullptr)
    {
        Message                *message     = (aMsg != nullptr) ? &aMsg->mMessage : nullptr;
        const Ip6::MessageInfo *messageInfo = (aMsg != nullptr) ? &aMsg->mMessageInfo : nullptr;

        mResponseHandlerSeparateParams(mContext, message, messageInfo, aResult);
    }
}

//---------------------------------------------------------------------------------------------------------------------
// CoapBase::Request::Metadata

void CoapBase::Request::Metadata::Init(const Msg           &aTxMsg,
                                       const TxParameters  &aTxParams,
                                       const SendCallbacks &aCallbacks)
{
    mSourceAddress      = aTxMsg.mMessageInfo.GetSockAddr();
    mDestinationPort    = aTxMsg.mMessageInfo.GetPeerPort();
    mDestinationAddress = aTxMsg.mMessageInfo.GetPeerAddr();
    mMulticastLoop      = aTxMsg.mMessageInfo.GetMulticastLoop();
    mCallbacks          = aCallbacks;
    mRetxRemaining      = aTxParams.mMaxRetransmit;
    mRetxTimeout        = aTxParams.CalculateInitialRetransmissionTimeout();
    mAcknowledged       = false;
    mConfirmable        = aTxMsg.IsConfirmable();
#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
    mHopLimit        = aTxMsg.mMessageInfo.GetHopLimit();
    mIsHostInterface = aTxMsg.mMessageInfo.IsHostInterface();
#endif

    mTimerFireTime = TimerMilli::GetNow() + (mConfirmable ? mRetxTimeout : aTxParams.CalculateMaxTransmitWait());
}

void CoapBase::Request::Metadata::CopyInfoTo(Ip6::MessageInfo &aMessageInfo) const
{
    aMessageInfo.SetPeerAddr(mDestinationAddress);
    aMessageInfo.SetPeerPort(mDestinationPort);
    aMessageInfo.SetSockAddr(mSourceAddress);
    aMessageInfo.SetMulticastLoop(mMulticastLoop);
#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
    aMessageInfo.SetHopLimit(mHopLimit);
    aMessageInfo.SetIsHostInterface(mIsHostInterface);
#endif
}

//---------------------------------------------------------------------------------------------------------------------
// CoapBase::Request

void CoapBase::Request::MarkAsAcknowledged(void)
{
    mMetadata.mAcknowledged = true;
    WriteMetadataInMessage();
}

bool CoapBase::Request::HasSamePeerAddrAndPort(const Ip6::MessageInfo &aMessageInfo) const
{
    return (mMetadata.mDestinationPort == aMessageInfo.GetPeerPort()) &&
           (mMetadata.mDestinationAddress == aMessageInfo.GetPeerAddr());
}

bool CoapBase::Request::ShouldRetransmit(void) const { return IsConfirmable() && (mMetadata.mRetxRemaining > 0); }

void CoapBase::Request::UpdateRetxCounterAndTimeout(TimeMilli aNow)
{
    mMetadata.mRetxRemaining--;
    mMetadata.mRetxTimeout *= 2;

    mMetadata.mTimerFireTime = aNow + mMetadata.mRetxTimeout;
    WriteMetadataInMessage();
}

#if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
bool CoapBase::Request::IsObserveSubscription(void) const
{
    // Indicate whether the message is an RFC7641 subscription which
    // is already acknowledged.

    return IsRequest() && IsObserve() && IsAcknowledged();
}
#endif

//---------------------------------------------------------------------------------------------------------------------
// CoapBase::PendingRequests

CoapBase::PendingRequests::PendingRequests(Instance &aInstance, CoapBase &aCoapBase)
    : mCoapBase(aCoapBase)
    , mDispatchingRequest(nullptr)
    , mTimer(aInstance, HandleTimer, this)
{
}

Error CoapBase::PendingRequests::Add(const Msg           &aTxMsg,
                                     const TxParameters  &aTxParams,
                                     const SendCallbacks &aCallbacks,
                                     Request             &aRequest)
{
    Error    error = kErrorNone;
    uint16_t cloneLength;

    aRequest.mMetadata.Init(aTxMsg, aTxParams, aCallbacks);

#if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
    SuccessOrExit(error = ProcessObserveSend(aTxMsg, aRequest));
#endif

    // We clone the full message for confirmable requests to allow for
    // retransmits, but only the header for non-confirmable requests
    // to preserve the token.

    cloneLength = aTxMsg.IsConfirmable() ? aTxMsg.mMessage.GetLength() : aTxMsg.GetHeaderSize();

    aRequest.mMessage = AsCoapMessagePtr(aTxMsg.mMessage.Clone<kNoReservedHeader>(cloneLength));
    VerifyOrExit(aRequest.HasMessage(), error = kErrorNoBufs);

    SuccessOrExit(error = aRequest.AppendMetadataToMessage());

    mRequestMessages.Enqueue(*aRequest.mMessage);

    mTimer.FireAtIfEarlier(aRequest.GetTimerFireTime());

exit:
    FreeAndNullMessageOnError(aRequest.mMessage, error);
    return error;
}

void CoapBase::PendingRequests::Remove(Request &aRequest)
{
    VerifyOrExit(aRequest.HasMessage());
    mRequestMessages.DequeueAndFree(*aRequest.mMessage);
    aRequest.Clear();

exit:
    return;
}

Error CoapBase::PendingRequests::FindRelatedRequest(const Msg &aMsg, Request &aRequest)
{
    Error error = kErrorNotFound;

    for (Message &message : mRequestMessages)
    {
        aRequest.InitFrom(message);

        if (aRequest.HasSamePeerAddrAndPort(aMsg.mMessageInfo) || aRequest.GetDestinationAddress().IsMulticast() ||
            aRequest.GetDestinationAddress().GetIid().IsAnycastLocator())
        {
            switch (aMsg.GetType())
            {
            case kTypeReset:
            case kTypeAck:
                if (aMsg.GetMessageId() == message.ReadMessageId())
                {
                    ExitNow(error = kErrorNone);
                }

                break;

            case kTypeConfirmable:
            case kTypeNonConfirmable:
                if (aMsg.mMessage.HasSameTokenAs(message))
                {
                    ExitNow(error = kErrorNone);
                }

                break;
            }
        }
    }

    aRequest.Clear();

exit:
    return error;
}

void CoapBase::PendingRequests::FinalizeRequest(Request &aRequest, Error aResult)
{
    FinalizeRequest(aRequest, aResult, /* aResponse */ nullptr);
}

void CoapBase::PendingRequests::FinalizeRequest(Request &aRequest, Error aResult, Msg *aResponse)
{
    VerifyOrExit(aRequest.HasMessage());

    mRequestMessages.Dequeue(*aRequest.mMessage);

    DispatchResponse(aRequest, aResult, aResponse);

    aRequest.mMessage->Free();
    aRequest.Clear();

exit:
    return;
}

void CoapBase::PendingRequests::DispatchResponse(Request &aRequest, Error aResult)
{
    DispatchResponse(aRequest, aResult, /* aResponse */ nullptr);
}

void CoapBase::PendingRequests::DispatchResponse(Request &aRequest, Error aResult, Msg *aResponse)
{
    const Request *prev = mDispatchingRequest;

    mDispatchingRequest = &aRequest;
    aRequest.GetCallbacks().InvokeResponseHandler(aResponse, aResult);
    mDispatchingRequest = prev;
}

Error CoapBase::PendingRequests::GetDispatchingRequest(OwnedPtr<Message> &aMessage) const
{
    Error error = kErrorNotFound;

    VerifyOrExit(mDispatchingRequest != nullptr);
    VerifyOrExit(mDispatchingRequest->IsConfirmable());
    VerifyOrExit(mDispatchingRequest->HasMessage());

    aMessage.Reset(mCoapBase.CloneMessageWithout<Request::Metadata>(mDispatchingRequest->GetMessage()));
    VerifyOrExit(aMessage != nullptr, error = kErrorNoBufs);

    error = kErrorNone;

exit:
    return error;
}

void CoapBase::PendingRequests::AbortAllRequests(void)
{
    IgnoreError(AbortAllMatching(Matcher()));
    mTimer.Stop();
}

void CoapBase::PendingRequests::AbortRequestsMatching(const Ip6::Address &aAddress)
{
    IgnoreError(AbortAllMatching(Matcher(aAddress)));
}

Error CoapBase::PendingRequests::AbortRequestsMatching(ResponseHandler aHandler, void *aContext)
{
    return AbortAllMatching(Matcher(aHandler, aContext));
}

Error CoapBase::PendingRequests::AbortAllMatching(const Matcher &aMatcher)
{
    Error        error = kErrorNotFound;
    MessageQueue abortedMessages;

    for (Message &message : mRequestMessages)
    {
        Request request;

        request.InitFrom(message);

        if (aMatcher.Matches(request))
        {
            mRequestMessages.Dequeue(message);
            abortedMessages.Enqueue(message);
            error = kErrorNone;
        }
    }

    FinalizeRemovedRequestsIn(abortedMessages, kErrorAbort);

    return error;
}

void CoapBase::PendingRequests::FinalizeRemovedRequestsIn(MessageQueue &aQueue, Error aResult)
{
    for (Message &message : aQueue)
    {
        Request request;

        request.InitFrom(message);
        DispatchResponse(request, aResult);
    }

    aQueue.DequeueAndFreeAll();
}

void CoapBase::PendingRequests::RetransmitRequest(const Request &aRequest)
{
    Error            error;
    Message         *clone;
    Ip6::MessageInfo messageInfo;

    clone = mCoapBase.CloneMessageWithout<Request::Metadata>(aRequest.GetMessage());
    VerifyOrExit(clone != nullptr, error = kErrorNoBufs);

    aRequest.mMetadata.CopyInfoTo(messageInfo);

    SuccessOrExit(error = mCoapBase.Transmit(*clone, messageInfo));

exit:
    FreeMessageOnError(clone, error);
}

void CoapBase::PendingRequests::HandleTimer(Timer &aTimer)
{
    static_cast<PendingRequests *>(static_cast<TimerMilliContext &>(aTimer).GetContext())->HandleTimer();
}

void CoapBase::PendingRequests::HandleTimer(void)
{
    NextFireTime nextTime;
    MessageQueue expiredMessages;

    for (Message &message : mRequestMessages)
    {
        Request request;

        request.InitFrom(message);

#if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
        if (request.IsObserveSubscription())
        {
            // This is an RFC7641 subscription which is already
            // acknowledged. We do not time it out, so skip it when
            // determining the next fire time.
            continue;
        }
#endif

        if (nextTime.GetNow() >= request.GetTimerFireTime())
        {
            if (!request.ShouldRetransmit())
            {
                // We move the expired request to a separate queue to
                // finalize it after the loop. This ensures that the
                // iterator over `mRequestMessages` remains valid
                // even if the user callback (invoked during
                // finalization) modifies any pending requests

                mRequestMessages.Dequeue(message);
                expiredMessages.Enqueue(message);
                continue;
            }

            request.UpdateRetxCounterAndTimeout(nextTime.GetNow());

            if (!request.IsAcknowledged())
            {
                RetransmitRequest(request);
            }
        }

        nextTime.UpdateIfEarlier(request.GetTimerFireTime());
    }

    mTimer.FireAt(nextTime);

    FinalizeRemovedRequestsIn(expiredMessages, kErrorResponseTimeout);
}

//---------------------------------------------------------------------------------------------------------------------
// CoapBase::PendingRequests::Matcher

bool CoapBase::PendingRequests::Matcher::Matches(const Request &aRequest) const
{
    bool matches = false;

    switch (mMode)
    {
    case kAny:
        break;
    case kAddress:
        VerifyOrExit(aRequest.GetSourceAddress() == *mAddress);
        break;
    case kHandler:
        VerifyOrExit(aRequest.GetCallbacks().Matches(mHandler, mContext));
        break;
    }

    matches = true;

exit:
    return matches;
}

//---------------------------------------------------------------------------------------------------------------------
// CoapBase::ResponseCache

CoapBase::ResponseCache::ResponseCache(Instance &aInstance)
    : mTimer(aInstance, ResponseCache::HandleTimer, this)
{
}

Error CoapBase::ResponseCache::SendCachedResponse(const Msg &aRxMsg, CoapBase &aCoapBase)
{
    // Search `ResponseCache` for a cached response matching the given
    // request `aRxMsg`. If found, clone the response and send it. Returns
    // `kErrorNotFound` if no match is found, `kErrorNone` on success,
    // or other errors if send fails.

    Error          error    = kErrorNone;
    const Message *match    = FindMatching(aRxMsg);
    Message       *response = nullptr;

    VerifyOrExit(match != nullptr, error = kErrorNotFound);

    response = aCoapBase.CloneMessageWithout<ResponseMetadata>(*match);
    VerifyOrExit(response != nullptr, error = kErrorNoBufs);

    error = aCoapBase.Transmit(*response, aRxMsg.mMessageInfo);

exit:
    FreeMessageOnError(response, error);
    return error;
}

const Message *CoapBase::ResponseCache::FindMatching(const Msg &aRxMsg) const
{
    const Message *match        = nullptr;
    uint16_t       requestMsgId = aRxMsg.GetMessageId();

    for (const Message &response : mResponses)
    {
        if (response.ReadMessageId() == requestMsgId)
        {
            ResponseMetadata metadata;

            metadata.ReadFrom(response);

            if (metadata.mMessageInfo.HasSamePeerAddrAndPort(aRxMsg.mMessageInfo))
            {
                match = &response;
                break;
            }
        }
    }

    return match;
}

void CoapBase::ResponseCache::Add(const Msg &aTxMsg, uint32_t aExchangeLifetime)
{
    // Adds a clone of the `aTxMsg` to the cache if a matching
    // entry does not already exist.

    Message         *responseClone = nullptr;
    ResponseMetadata metadata;

    VerifyOrExit(FindMatching(aTxMsg) == nullptr);

    MaintainCacheSize();

    responseClone = AsCoapMessagePtr(aTxMsg.mMessage.Clone<kNoReservedHeader>());
    VerifyOrExit(responseClone != nullptr);

    metadata.mExpireTime  = TimerMilli::GetNow() + aExchangeLifetime;
    metadata.mMessageInfo = aTxMsg.mMessageInfo;

    SuccessOrExit(metadata.AppendTo(*responseClone));

    mResponses.Enqueue(*responseClone);
    responseClone = nullptr;

    mTimer.FireAtIfEarlier(metadata.mExpireTime);

exit:
    FreeMessage(responseClone);
}

void CoapBase::ResponseCache::MaintainCacheSize(void)
{
    // Checks the cache size. If the limit (`kMaxCacheSize`) is
    // reached, removes the entry with the earliest expire time.

    uint16_t  count       = 0;
    Message  *msgToRemove = nullptr;
    TimeMilli earliestExpireTime;

    for (Message &response : mResponses)
    {
        ResponseMetadata metadata;

        metadata.ReadFrom(response);

        if ((msgToRemove == nullptr) || (metadata.mExpireTime < earliestExpireTime))
        {
            msgToRemove        = &response;
            earliestExpireTime = metadata.mExpireTime;
        }

        count++;
    }

    if (count >= kMaxCacheSize)
    {
        mResponses.DequeueAndFree(*msgToRemove);
    }
}

void CoapBase::ResponseCache::RemoveAll(void)
{
    mResponses.DequeueAndFreeAll();
    mTimer.Stop();
}

void CoapBase::ResponseCache::HandleTimer(Timer &aTimer)
{
    static_cast<ResponseCache *>(static_cast<TimerMilliContext &>(aTimer).GetContext())->HandleTimer();
}

void CoapBase::ResponseCache::HandleTimer(void)
{
    NextFireTime expireTime;

    for (Message &response : mResponses)
    {
        ResponseMetadata metadata;

        metadata.ReadFrom(response);

        if (expireTime.GetNow() >= metadata.mExpireTime)
        {
            mResponses.DequeueAndFree(response);
        }
        else
        {
            expireTime.UpdateIfEarlier(metadata.mExpireTime);
        }
    }

    mTimer.FireAt(expireTime);
}

//---------------------------------------------------------------------------------------------------------------------
// TxParameters

const otCoapTxParameters TxParameters::kDefaultTxParameters = {
    kDefaultAckTimeout,
    kDefaultAckRandomFactorNumerator,
    kDefaultAckRandomFactorDenominator,
    kDefaultMaxRetransmit,
};

const TxParameters &TxParameters::GetDefault(void)
{
    // Validate the default `TxParameters` at compile-time

    static constexpr uint64_t kMaxDuration = static_cast<uint64_t>(kDefaultAckTimeout) *
                                                 kDefaultAckRandomFactorNumerator *
                                                 (1UL << (kDefaultMaxRetransmit + 1)) +
                                             2 * kDefaultMaxLatency;

    static_assert(kDefaultAckRandomFactorDenominator > 0, "kDefaultAckRandomFactorDenominator MUST be non-zero");
    static_assert(kDefaultAckRandomFactorNumerator >= kDefaultAckRandomFactorDenominator, "Numerator is invalid");
    static_assert(kMinAckTimeout > 0, "kMinAckTimeout MUST be non-zero");
    static_assert(kDefaultAckTimeout >= kMinAckTimeout, "kDefaultAckTimeout is invalid");
    static_assert(kMaxRetransmit > 0, "kMaxRetransmit MUST be non-zero");
    static_assert(kMaxRetransmit < 31, "kMaxRetransmit is not valid");
    static_assert(kDefaultMaxRetransmit <= kMaxRetransmit, "kDefaultMaxRetransmit is invalid");
    static_assert(kMaxDuration < NumericLimits<uint32_t>::kMax, "Default `TxParameters` is invalid");

    return AsCoreType(&kDefaultTxParameters);
}

Error TxParameters::ValidateFor(const Msg &aMsg) const
{
    Error    error = kErrorInvalidArgs;
    uint32_t duration;
    uint32_t retryFactor;

    if (mAckTimeout == 0)
    {
        // Fire and forget is only allowed for non-confirmable messages.
        VerifyOrExit(aMsg.IsNonConfirmable());
        error = kErrorNone;
        ExitNow();
    }

    VerifyOrExit(mAckRandomFactorDenominator > 0);
    VerifyOrExit(mAckRandomFactorNumerator >= mAckRandomFactorDenominator);
    VerifyOrExit(mAckTimeout >= kMinAckTimeout);
    VerifyOrExit(mMaxRetransmit <= kMaxRetransmit);

    // Calculate exchange lifetime max duration step by step and verify no overflow.

    retryFactor = static_cast<uint32_t>((1U << (mMaxRetransmit + 1)) - 1);
    SuccessOrExit(SafeMultiply<uint32_t>(mAckTimeout, retryFactor, duration));

    SuccessOrExit(SafeMultiply<uint32_t>(duration, mAckRandomFactorNumerator, duration));
    duration /= mAckRandomFactorDenominator;

    VerifyOrExit(duration > 0);
    VerifyOrExit(CanAddSafely<uint32_t>(mAckTimeout, 2 * kDefaultMaxLatency));
    VerifyOrExit(CanAddSafely<uint32_t>(duration, mAckTimeout + 2 * kDefaultMaxLatency));

    error = kErrorNone;

exit:
    return error;
}

uint32_t TxParameters::CalculateInitialRetransmissionTimeout(void) const
{
    return Random::NonCrypto::GenerateInClosedRange<uint32_t>(mAckTimeout, mAckTimeout * mAckRandomFactorNumerator /
                                                                               mAckRandomFactorDenominator);
}

uint32_t TxParameters::CalculateExchangeLifetime(void) const
{
    // Final `mAckTimeout` is to account for processing delay.
    return CalculateSpan(mMaxRetransmit) + 2 * kDefaultMaxLatency + mAckTimeout;
}

uint32_t TxParameters::CalculateMaxTransmitWait(void) const { return CalculateSpan(mMaxRetransmit + 1); }

uint32_t TxParameters::CalculateSpan(uint8_t aMaxRetx) const
{
    return static_cast<uint32_t>(mAckTimeout * ((1U << aMaxRetx) - 1) / mAckRandomFactorDenominator *
                                 mAckRandomFactorNumerator);
}

//---------------------------------------------------------------------------------------------------------------------
// Resource

Resource::Resource(const char *aUriPath, RequestHandler aHandler, void *aContext)
{
    mUriPath = aUriPath;
    mHandler = aHandler;
    mContext = aContext;
    mNext    = nullptr;
}

Resource::Resource(Uri aUri, RequestHandler aHandler, void *aContext)
    : Resource(PathForUri(aUri), aHandler, aContext)
{
}

//---------------------------------------------------------------------------------------------------------------------
// Coap

Coap::Coap(Instance &aInstance)
    : CoapBase(aInstance, Coap::Transmit)
    , mSocket(aInstance, *this)
{
}

Error Coap::Start(uint16_t aPort, Ip6::NetifIdentifier aNetifIdentifier)
{
    Error error        = kErrorNone;
    bool  socketOpened = false;

    VerifyOrExit(!mSocket.IsBound());

    SuccessOrExit(error = mSocket.Open(aNetifIdentifier));
    socketOpened = true;

    SuccessOrExit(error = mSocket.Bind(aPort));

exit:
    if (error != kErrorNone && socketOpened)
    {
        IgnoreError(mSocket.Close());
    }

    return error;
}

Error Coap::Stop(void)
{
    Error error = kErrorNone;

    VerifyOrExit(mSocket.IsBound());

    SuccessOrExit(error = mSocket.Close());
    ClearAllRequestsAndResponses();

exit:
    return error;
}

void Coap::HandleUdpReceive(ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
    Receive(AsCoapMessage(&aMessage), aMessageInfo);
}

Error Coap::Transmit(CoapBase &aCoapBase, ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
    return static_cast<Coap &>(aCoapBase).Transmit(aMessage, aMessageInfo);
}

Error Coap::Transmit(ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
    return mSocket.IsBound() ? mSocket.SendTo(aMessage, aMessageInfo) : kErrorInvalidState;
}

} // namespace Coap
} // namespace ot
