/*
 *  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.
 */

/**
 * @file
 *   This file implements the message buffer pool and message buffers.
 */

#include "message.hpp"

#include "instance/instance.hpp"

#if OPENTHREAD_MTD || OPENTHREAD_FTD

#if OPENTHREAD_CONFIG_MESSAGE_USE_HEAP_ENABLE && OPENTHREAD_CONFIG_PLATFORM_MESSAGE_MANAGEMENT
#error "OPENTHREAD_CONFIG_MESSAGE_USE_HEAP_ENABLE conflicts with OPENTHREAD_CONFIG_PLATFORM_MESSAGE_MANAGEMENT."
#endif

namespace ot {

RegisterLogModule("Message");

//---------------------------------------------------------------------------------------------------------------------
// MessagePool

MessagePool::MessagePool(Instance &aInstance)
    : InstanceLocator(aInstance)
    , mNumAllocated(0)
    , mMaxAllocated(0)
{
#if OPENTHREAD_CONFIG_PLATFORM_MESSAGE_MANAGEMENT
    otPlatMessagePoolInit(&GetInstance(), kNumBuffers, sizeof(Buffer));
#endif
}

#if OPENTHREAD_CONFIG_PLATFORM_MESSAGE_MANAGEMENT
MessagePool::~MessagePool(void) { otPlatMessagePoolDeinit(&GetInstance()); }
#endif

Message *MessagePool::Allocate(Message::Type aType, uint16_t aReserveHeader, const Message::Settings &aSettings)
{
    Error    error = kErrorNone;
    Message *message;

    VerifyOrExit((message = static_cast<Message *>(NewBuffer(aSettings.GetPriority()))) != nullptr);

    ClearAllBytes(*message);

#if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE
    message->GetMetadata().mInstance = &GetInstance();
#endif
    message->SetType(aType);
    message->SetReserved(aReserveHeader);
    message->SetLinkSecurityEnabled(aSettings.IsLinkSecurityEnabled());
    message->SetLoopbackToHostAllowed(OPENTHREAD_CONFIG_IP6_ALLOW_LOOP_BACK_HOST_DATAGRAMS);
    message->SetOrigin(Message::kOriginHostTrusted);
    message->MarkAsNotInAQueue();

    SuccessOrExit(error = message->SetPriority(aSettings.GetPriority()));
    SuccessOrExit(error = message->SetLength(0));

exit:
    if (error != kErrorNone)
    {
        Free(message);
        message = nullptr;
    }

    return message;
}

Message *MessagePool::Allocate(Message::Type aType) { return Allocate(aType, 0, Message::Settings::GetDefault()); }

Message *MessagePool::Allocate(Message::Type aType, uint16_t aReserveHeader)
{
    return Allocate(aType, aReserveHeader, Message::Settings::GetDefault());
}

void MessagePool::Free(Message *aMessage)
{
    OT_ASSERT(!aMessage->IsInAQueue());

    FreeBuffers(static_cast<Buffer *>(aMessage));
}

Buffer *MessagePool::NewBuffer(Message::Priority aPriority)
{
    Buffer *buffer = nullptr;

    while ((
#if OPENTHREAD_CONFIG_MESSAGE_USE_HEAP_ENABLE
               buffer = static_cast<Buffer *>(Heap::CAlloc(1, sizeof(Buffer)))
#elif OPENTHREAD_CONFIG_PLATFORM_MESSAGE_MANAGEMENT
               buffer = static_cast<Buffer *>(otPlatMessagePoolNew(&GetInstance()))
#else
               buffer = mBufferPool.Allocate()
#endif
                   ) == nullptr)
    {
        SuccessOrExit(ReclaimBuffers(aPriority));
    }

    mNumAllocated++;
    mMaxAllocated = Max(mMaxAllocated, mNumAllocated);

    buffer->SetNextBuffer(nullptr);

exit:
    if (buffer == nullptr)
    {
        LogInfo("No available message buffer");
    }

    return buffer;
}

void MessagePool::FreeBuffers(Buffer *aBuffer)
{
    while (aBuffer != nullptr)
    {
        Buffer *next = aBuffer->GetNextBuffer();
#if OPENTHREAD_CONFIG_MESSAGE_USE_HEAP_ENABLE
        Heap::Free(aBuffer);
#elif OPENTHREAD_CONFIG_PLATFORM_MESSAGE_MANAGEMENT
        otPlatMessagePoolFree(&GetInstance(), aBuffer);
#else
        mBufferPool.Free(*aBuffer);
#endif
        mNumAllocated--;

        aBuffer = next;
    }
}

Error MessagePool::ReclaimBuffers(Message::Priority aPriority)
{
    return Get<MeshForwarder>().EvictMessage(aPriority, MeshForwarder::kEvictReasonNoMessageBuffer);
}

uint16_t MessagePool::GetFreeBufferCount(void) const
{
    uint16_t rval;

#if OPENTHREAD_CONFIG_MESSAGE_USE_HEAP_ENABLE
#if !OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE
    rval = static_cast<uint16_t>(Instance::GetHeap().GetFreeSize() / sizeof(Buffer));
#else
    SetToUintMax(rval);
#endif
#elif OPENTHREAD_CONFIG_PLATFORM_MESSAGE_MANAGEMENT
    rval = otPlatMessagePoolNumFreeBuffers(&GetInstance());
#else
    rval = kNumBuffers - mNumAllocated;
#endif

    return rval;
}

uint16_t MessagePool::GetTotalBufferCount(void) const
{
    uint16_t rval;

#if OPENTHREAD_CONFIG_MESSAGE_USE_HEAP_ENABLE
#if !OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE
    rval = static_cast<uint16_t>(Instance::GetHeap().GetCapacity() / sizeof(Buffer));
#else
    SetToUintMax(rval);
#endif
#else
    rval = OPENTHREAD_CONFIG_NUM_MESSAGE_BUFFERS;
#endif

    return rval;
}

//---------------------------------------------------------------------------------------------------------------------
// Message::Settings

const otMessageSettings Message::Settings::kDefault = {kWithLinkSecurity, kPriorityNormal};

Message::Settings::Settings(LinkSecurityMode aSecurityMode, Priority aPriority)
{
    mLinkSecurityEnabled = aSecurityMode;
    mPriority            = aPriority;
}

const Message::Settings &Message::Settings::From(const otMessageSettings *aSettings)
{
    return (aSettings == nullptr) ? GetDefault() : AsCoreType(aSettings);
}

//---------------------------------------------------------------------------------------------------------------------
// Message::Iterator

void Message::Iterator::Advance(void)
{
    mItem = mNext;
    mNext = NextMessage(mNext);
}

//---------------------------------------------------------------------------------------------------------------------
// Message

Error Message::ResizeMessage(uint16_t aLength)
{
    // This method adds or frees message buffers to meet the
    // requested length.

    Error    error     = kErrorNone;
    Buffer  *curBuffer = this;
    Buffer  *lastBuffer;
    uint16_t curLength = kHeadBufferDataSize;

    while (curLength < aLength)
    {
        if (curBuffer->GetNextBuffer() == nullptr)
        {
            curBuffer->SetNextBuffer(Get<MessagePool>().NewBuffer(GetPriority()));
            VerifyOrExit(curBuffer->GetNextBuffer() != nullptr, error = kErrorNoBufs);
        }

        curBuffer = curBuffer->GetNextBuffer();
        curLength += kBufferDataSize;
    }

    lastBuffer = curBuffer;
    curBuffer  = curBuffer->GetNextBuffer();
    lastBuffer->SetNextBuffer(nullptr);

    Get<MessagePool>().FreeBuffers(curBuffer);

exit:
    return error;
}

void Message::Free(void)
{
    // `TxCallback` is cleared once it is invoked. If the message is
    // freed before we know the TX outcome, it's treated as a dropped
    // message, signaling `kErrorDrop`.

    InvokeTxCallback(kErrorDrop);
    Get<MessagePool>().Free(this);
}

Error Message::SetLength(uint16_t aLength)
{
    Error    error;
    uint16_t size;

    VerifyOrExit(CanAddSafely<uint16_t>(GetReserved(), aLength), error = kErrorNoBufs);

    size = GetReserved() + aLength;
    SuccessOrExit(error = ResizeMessage(size));

    GetMetadata().mLength = aLength;

    // Correct the offset in case shorter length is set.
    SetOffset(GetOffset());

exit:
    return error;
}

Error Message::IncreaseLength(uint16_t aSize)
{
    Error    error;
    uint16_t length = GetLength();

    VerifyOrExit(CanAddSafely<uint16_t>(length, aSize), error = kErrorNoBufs);
    error = SetLength(length + aSize);

exit:
    return error;
}

uint8_t Message::GetBufferCount(void) const
{
    uint8_t rval = 1;

    for (const Buffer *curBuffer = GetNextBuffer(); curBuffer; curBuffer = curBuffer->GetNextBuffer())
    {
        rval++;
    }

    return rval;
}

void Message::MoveOffset(int16_t aDelta)
{
    int32_t newOffset = static_cast<int32_t>(GetOffset()) + aDelta;

    newOffset = Clamp<int32_t>(newOffset, 0, NumericLimits<uint16_t>::kMax);

    SetOffset(static_cast<uint16_t>(newOffset));
}

void Message::SetOffset(uint16_t aOffset) { GetMetadata().mOffset = Min(aOffset, GetLength()); }

uint16_t Message::DetermineLengthAfterOffset(void) const
{
    return (GetOffset() <= GetLength()) ? GetLength() - GetOffset() : 0;
}

bool Message::IsMleCommand(Mle::Command aMleCommand) const
{
    return (GetSubType() == kSubTypeMle) && (GetMetadata().mMleCommand == aMleCommand);
}

Error Message::SetPriority(Priority aPriority)
{
    Error   error    = kErrorNone;
    uint8_t priority = static_cast<uint8_t>(aPriority);

    static_assert(kNumPriorities <= 4, "`Metadata::mPriority` as a 2-bit field cannot fit all `Priority` values");

    VerifyOrExit(priority < kNumPriorities, error = kErrorInvalidArgs);

    VerifyOrExit(!IsInAPriorityQueue(), error = kErrorInvalidState);

    GetMetadata().mPriority = priority;

exit:
    return error;
}

const char *Message::PriorityToString(Priority aPriority)
{
#define PriorityMapList(_)       \
    _(kPriorityLow, "low")       \
    _(kPriorityNormal, "normal") \
    _(kPriorityHigh, "high")     \
    _(kPriorityNet, "net")

    DefineEnumStringArray(PriorityMapList);

    return kStrings[aPriority];
}

void Message::RegisterTxCallback(TxCallback aCallback, void *aContext)
{
    GetMetadata().mTxCallback = aCallback;
    GetMetadata().mTxContext  = aContext;
}

void Message::InvokeTxCallback(Error aError)
{
    TxCallback callback = GetMetadata().mTxCallback;

    if (callback != nullptr)
    {
        GetMetadata().mTxCallback = nullptr;
        callback(this, aError, GetMetadata().mTxContext);
    }
}

Error Message::AppendBytes(const void *aBuf, uint16_t aLength)
{
    Error    error;
    uint16_t offset = GetLength();

    SuccessOrExit(error = IncreaseLength(aLength));
    WriteBytes(offset, aBuf, aLength);

exit:
    return error;
}

Error Message::AppendBytesFromMessage(const Message &aMessage, const OffsetRange &aOffsetRange)
{
    return AppendBytesFromMessage(aMessage, aOffsetRange.GetOffset(), aOffsetRange.GetLength());
}

Error Message::AppendBytesFromMessage(const Message &aMessage, uint16_t aOffset, uint16_t aLength)
{
    Error    error;
    uint16_t writeOffset = GetLength();
    Chunk    chunk;

    VerifyOrExit(CanAddSafely<uint16_t>(aOffset, aLength), error = kErrorInvalidArgs);

    VerifyOrExit(aMessage.GetLength() >= aOffset + aLength, error = kErrorParse);

    SuccessOrExit(error = IncreaseLength(aLength));

    aMessage.GetFirstChunk(aOffset, aLength, chunk);

    while (chunk.GetLength() > 0)
    {
        WriteBytes(writeOffset, chunk.GetBytes(), chunk.GetLength());
        writeOffset += chunk.GetLength();
        aMessage.GetNextChunk(aLength, chunk);
    }

exit:
    return error;
}

Error Message::PrependBytes(const void *aBuf, uint16_t aLength)
{
    Error   error     = kErrorNone;
    Buffer *newBuffer = nullptr;

    while (aLength > GetReserved())
    {
        VerifyOrExit((newBuffer = Get<MessagePool>().NewBuffer(GetPriority())) != nullptr, error = kErrorNoBufs);

        newBuffer->SetNextBuffer(GetNextBuffer());
        SetNextBuffer(newBuffer);

        if (GetReserved() < sizeof(mBuffer.mHead.mData))
        {
            // Copy payload from the first buffer.
            memcpy(newBuffer->mBuffer.mHead.mData + GetReserved(), mBuffer.mHead.mData + GetReserved(),
                   sizeof(mBuffer.mHead.mData) - GetReserved());
        }

        SetReserved(GetReserved() + kBufferDataSize);
    }

    SetReserved(GetReserved() - aLength);
    GetMetadata().mLength += aLength;
    SetOffset(GetOffset() + aLength);

    if (aBuf != nullptr)
    {
        WriteBytes(0, aBuf, aLength);
    }

exit:
    return error;
}

void Message::RemoveHeader(uint16_t aLength)
{
    OT_ASSERT(aLength <= GetMetadata().mLength);

    GetMetadata().mReserved += aLength;
    GetMetadata().mLength -= aLength;

    if (GetMetadata().mOffset > aLength)
    {
        GetMetadata().mOffset -= aLength;
    }
    else
    {
        GetMetadata().mOffset = 0;
    }
}

void Message::RemoveHeader(uint16_t aOffset, uint16_t aLength)
{
    // To shrink the header, we copy the header byte before `aOffset`
    // forward. Starting at offset `aLength`, we write bytes we read
    // from offset `0` onward and copy a total of `aOffset` bytes.
    // Then remove the first `aLength` bytes from message.
    //
    //
    // 0                   aOffset  aOffset + aLength
    // +-----------------------+---------+------------------------+
    // | / / / / / / / / / / / | x x x x |                        |
    // +-----------------------+---------+------------------------+
    //
    // 0       aLength                aOffset + aLength
    // +---------+-----------------------+------------------------+
    // |         | / / / / / / / / / / / |                        |
    // +---------+-----------------------+------------------------+
    //
    //  0                    aOffset
    //  +-----------------------+------------------------+
    //  | / / / / / / / / / / / |                        |
    //  +-----------------------+------------------------+
    //

    WriteBytesFromMessage(/* aWriteOffset */ aLength, *this, /* aReadOffset */ 0, /* aLength */ aOffset);
    RemoveHeader(aLength);
}

Error Message::InsertHeader(uint16_t aOffset, uint16_t aLength)
{
    Error error;

    // To make space in header at `aOffset`, we first prepend
    // `aLength` bytes at front. Then copy the existing bytes
    // backwards. Starting at offset `0`, we write bytes we read
    // from offset `aLength` onward and copy a total of `aOffset`
    // bytes.
    //
    // 0                    aOffset
    // +-----------------------+------------------------+
    // | / / / / / / / / / / / |                        |
    // +-----------------------+------------------------+
    //
    // 0       aLength                aOffset + aLength
    // +---------+-----------------------+------------------------+
    // |         | / / / / / / / / / / / |                        |
    // +---------+-----------------------+------------------------+
    //
    // 0                   aOffset  aOffset + aLength
    // +-----------------------+---------+------------------------+
    // | / / / / / / / / / / / |  N E W  |                        |
    // +-----------------------+---------+------------------------+
    //

    SuccessOrExit(error = PrependBytes(nullptr, aLength));
    WriteBytesFromMessage(/* aWriteOffset */ 0, *this, /* aReadOffset */ aLength, /* aLength */ aOffset);

exit:
    return error;
}

void Message::RemoveFooter(uint16_t aLength) { IgnoreError(SetLength(GetLength() - Min(aLength, GetLength()))); }

void Message::GetFirstChunk(uint16_t aOffset, uint16_t &aLength, Chunk &aChunk) const
{
    // This method gets the first message chunk (contiguous data
    // buffer) corresponding to a given offset and length. On exit
    // `aChunk` is updated such that `aChunk.GetBytes()` gives the
    // pointer to the start of chunk and `aChunk.GetLength()` gives
    // its length. The `aLength` is also decreased by the chunk
    // length.

    VerifyOrExit(aOffset < GetLength(), aChunk.SetLength(0));

    if (!CanAddSafely<uint16_t>(aOffset, aLength) || (aOffset + aLength >= GetLength()))
    {
        aLength = GetLength() - aOffset;
    }

    aOffset += GetReserved();

    aChunk.SetBuffer(this);

    // Special case for the first buffer

    if (aOffset < kHeadBufferDataSize)
    {
        aChunk.Init(GetFirstData() + aOffset, kHeadBufferDataSize - aOffset);
        ExitNow();
    }

    aOffset -= kHeadBufferDataSize;

    // Find the `Buffer` matching the offset

    while (true)
    {
        aChunk.SetBuffer(aChunk.GetBuffer()->GetNextBuffer());

        OT_ASSERT(aChunk.GetBuffer() != nullptr);

        if (aOffset < kBufferDataSize)
        {
            aChunk.Init(aChunk.GetBuffer()->GetData() + aOffset, kBufferDataSize - aOffset);
            ExitNow();
        }

        aOffset -= kBufferDataSize;
    }

exit:
    if (aChunk.GetLength() > aLength)
    {
        aChunk.SetLength(aLength);
    }

    aLength -= aChunk.GetLength();
}

void Message::GetNextChunk(uint16_t &aLength, Chunk &aChunk) const
{
    // This method gets the next message chunk. On input, the
    // `aChunk` should be the previous chunk. On exit, it is
    // updated to provide info about next chunk, and `aLength`
    // is decreased by the chunk length. If there is no more
    // chunk, `aChunk.GetLength()` would be zero.

    VerifyOrExit(aLength > 0, aChunk.SetLength(0));

    aChunk.SetBuffer(aChunk.GetBuffer()->GetNextBuffer());

    OT_ASSERT(aChunk.GetBuffer() != nullptr);

    aChunk.Init(aChunk.GetBuffer()->GetData(), kBufferDataSize);

    if (aChunk.GetLength() > aLength)
    {
        aChunk.SetLength(aLength);
    }

    aLength -= aChunk.GetLength();

exit:
    return;
}

uint16_t Message::ReadBytes(uint16_t aOffset, void *aBuf, uint16_t aLength) const
{
    uint8_t *bufPtr = reinterpret_cast<uint8_t *>(aBuf);
    Chunk    chunk;

    GetFirstChunk(aOffset, aLength, chunk);

    while (chunk.GetLength() > 0)
    {
        chunk.CopyBytesTo(bufPtr);
        bufPtr += chunk.GetLength();
        GetNextChunk(aLength, chunk);
    }

    return static_cast<uint16_t>(bufPtr - reinterpret_cast<uint8_t *>(aBuf));
}

uint16_t Message::ReadBytes(const OffsetRange &aOffsetRange, void *aBuf) const
{
    return ReadBytes(aOffsetRange.GetOffset(), aBuf, aOffsetRange.GetLength());
}

Error Message::Read(uint16_t aOffset, void *aBuf, uint16_t aLength) const
{
    Error error = kErrorNone;

    VerifyOrExit(aOffset + aLength <= GetLength(), error = kErrorParse);
    ReadBytes(aOffset, aBuf, aLength);

exit:
    return error;
}

Error Message::Read(const OffsetRange &aOffsetRange, void *aBuf, uint16_t aLength) const
{
    Error error = kErrorNone;

    VerifyOrExit(aOffsetRange.Contains(aLength), error = kErrorParse);
    error = Read(aOffsetRange.GetOffset(), aBuf, aLength);

exit:
    return error;
}

Error Message::ReadAndAdvance(OffsetRange &aOffsetRange, void *aBuf, uint16_t aLength) const
{
    Error error = Read(aOffsetRange, aBuf, aLength);

    if (error == kErrorNone)
    {
        aOffsetRange.AdvanceOffset(aLength);
    }

    return error;
}

Error Message::ReadAtAndAdvanceOffset(void *aBuf, uint16_t aLength)
{
    Error error;

    SuccessOrExit(error = Read(GetOffset(), aBuf, aLength));
    MoveOffset(aLength);

exit:
    return error;
}

bool Message::CompareBytes(uint16_t aOffset, const void *aBuf, uint16_t aLength, ByteMatcher aMatcher) const
{
    uint16_t       bytesToCompare = aLength;
    const uint8_t *bufPtr         = reinterpret_cast<const uint8_t *>(aBuf);
    Chunk          chunk;

    GetFirstChunk(aOffset, aLength, chunk);

    while (chunk.GetLength() > 0)
    {
        VerifyOrExit(chunk.MatchesBytesIn(bufPtr, aMatcher));
        bufPtr += chunk.GetLength();
        bytesToCompare -= chunk.GetLength();
        GetNextChunk(aLength, chunk);
    }

exit:
    return (bytesToCompare == 0);
}

bool Message::CompareBytes(const OffsetRange &aOffsetRange, const void *aBuf, ByteMatcher aMatcher) const
{
    return CompareBytes(aOffsetRange.GetOffset(), aBuf, aOffsetRange.GetLength(), aMatcher);
}

bool Message::CompareBytes(uint16_t       aOffset,
                           const Message &aOtherMessage,
                           uint16_t       aOtherOffset,
                           uint16_t       aLength,
                           ByteMatcher    aMatcher) const
{
    uint16_t bytesToCompare = aLength;
    Chunk    chunk;

    GetFirstChunk(aOffset, aLength, chunk);

    while (chunk.GetLength() > 0)
    {
        VerifyOrExit(aOtherMessage.CompareBytes(aOtherOffset, chunk.GetBytes(), chunk.GetLength(), aMatcher));
        aOtherOffset += chunk.GetLength();
        bytesToCompare -= chunk.GetLength();
        GetNextChunk(aLength, chunk);
    }

exit:
    return (bytesToCompare == 0);
}

void Message::WriteBytes(uint16_t aOffset, const void *aBuf, uint16_t aLength)
{
    const uint8_t *bufPtr = reinterpret_cast<const uint8_t *>(aBuf);
    MutableChunk   chunk;

    OT_ASSERT(CanAddSafely<uint16_t>(aOffset, aLength));
    OT_ASSERT(aOffset + aLength <= GetLength());

    GetFirstChunk(aOffset, aLength, chunk);

    while (chunk.GetLength() > 0)
    {
        memmove(chunk.GetBytes(), bufPtr, chunk.GetLength());
        bufPtr += chunk.GetLength();
        GetNextChunk(aLength, chunk);
    }
}

void Message::WriteBytesFromMessage(uint16_t       aWriteOffset,
                                    const Message &aMessage,
                                    uint16_t       aReadOffset,
                                    uint16_t       aLength)
{
    if ((&aMessage != this) || (aReadOffset >= aWriteOffset))
    {
        Chunk chunk;

        aMessage.GetFirstChunk(aReadOffset, aLength, chunk);

        while (chunk.GetLength() > 0)
        {
            WriteBytes(aWriteOffset, chunk.GetBytes(), chunk.GetLength());
            aWriteOffset += chunk.GetLength();
            aMessage.GetNextChunk(aLength, chunk);
        }
    }
    else
    {
        // We are copying bytes within the same message forward.
        // To ensure copy forward works, we read and write from
        // end of range and move backwards.

        static constexpr uint16_t kBufSize = 32;

        uint8_t buf[kBufSize];

        aWriteOffset += aLength;
        aReadOffset += aLength;

        while (aLength > 0)
        {
            uint16_t copyLength = Min(kBufSize, aLength);

            aLength -= copyLength;
            aReadOffset -= copyLength;
            aWriteOffset -= copyLength;

            ReadBytes(aReadOffset, buf, copyLength);
            WriteBytes(aWriteOffset, buf, copyLength);
        }
    }
}

Message *Message::Clone(uint16_t aLength, uint16_t aReserveHeader) const
{
    Error            error = kErrorNone;
    Message         *clone;
    LinkSecurityMode linkSecurityMode = IsLinkSecurityEnabled() ? kWithLinkSecurity : kNoLinkSecurity;

    clone = Get<MessagePool>().Allocate(GetType(), aReserveHeader, Settings(linkSecurityMode, GetPriority()));
    VerifyOrExit(clone != nullptr, error = kErrorNoBufs);

    aLength = Min(aLength, GetLength());

    SuccessOrExit(error = clone->AppendBytesFromMessage(*this, 0, aLength));

    // Copy selected message information.

    clone->SetOffset(Min(GetOffset(), aLength));

    clone->SetSubType(GetSubType());
    clone->SetLoopbackToHostAllowed(IsLoopbackToHostAllowed());
    clone->SetOrigin(GetOrigin());
    clone->SetTimestamp(GetTimestamp());
    clone->SetMeshDest(GetMeshDest());
    clone->SetPanId(GetPanId());
    clone->SetChannel(GetChannel());
    clone->SetRssAverager(GetRssAverager());
    clone->SetLqiAverager(GetLqiAverager());
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
    clone->SetTimeSync(IsTimeSync());
#endif

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

template <> Message *Message::Clone<kNoReservedHeader>(void) const { return Clone(GetLength(), 0); }

template <> Message *Message::Clone<kSameReservedHeader>(void) const { return Clone(GetLength(), GetReserved()); }

template <> Message *Message::Clone<kNoReservedHeader>(uint16_t aLength) const { return Clone(aLength, 0); }

template <> Message *Message::Clone<kSameReservedHeader>(uint16_t aLength) const
{
    return Clone(aLength, GetReserved());
}

Error Message::GetLinkInfo(ThreadLinkInfo &aLinkInfo) const
{
    Error error = kErrorNone;

    VerifyOrExit(IsOriginThreadNetif(), error = kErrorNotFound);

    aLinkInfo.Clear();

    aLinkInfo.mPanId               = GetPanId();
    aLinkInfo.mChannel             = GetChannel();
    aLinkInfo.mRss                 = GetAverageRss();
    aLinkInfo.mLqi                 = GetAverageLqi();
    aLinkInfo.mLinkSecurity        = IsLinkSecurityEnabled();
    aLinkInfo.mIsDstPanIdBroadcast = IsDstPanIdBroadcast();

#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
    aLinkInfo.mTimeSyncSeq       = GetTimeSyncSeq();
    aLinkInfo.mNetworkTimeOffset = GetNetworkTimeOffset();
#endif

#if OPENTHREAD_CONFIG_MULTI_RADIO
    aLinkInfo.mRadioType = GetRadioType();
#endif

exit:
    return error;
}

void Message::UpdateLinkInfoFrom(const ThreadLinkInfo &aLinkInfo)
{
    SetPanId(aLinkInfo.mPanId);
    SetChannel(aLinkInfo.mChannel);
    AddRss(aLinkInfo.mRss);
    AddLqi(aLinkInfo.mLqi);
    SetLinkSecurityEnabled(aLinkInfo.mLinkSecurity);
    GetMetadata().mIsDstPanIdBroadcast = aLinkInfo.IsDstPanIdBroadcast();

#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
    SetTimeSyncSeq(aLinkInfo.mTimeSyncSeq);
    SetNetworkTimeOffset(aLinkInfo.mNetworkTimeOffset);
#endif

#if OPENTHREAD_CONFIG_MULTI_RADIO
    SetRadioType(static_cast<Mac::RadioType>(aLinkInfo.mRadioType));
#endif
}

bool Message::IsTimeSync(void) const
{
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
    return GetMetadata().mTimeSync;
#else
    return false;
#endif
}

void Message::MarkAsNotInAQueue(void)
{
    // To indicate that a message is no longer in a queue, we set
    // its 'mPrev' pointer to point back to itself. This state is
    // unique and won't occur if the message is part of a queue. We
    // also set 'mNext' to 'nullptr' so that 'GetNext()' correctly
    // returns 'nullptr' for a dequeued message.

    Next() = nullptr;
    Prev() = this;

    GetMetadata().mInPriorityQ = false;
}

//---------------------------------------------------------------------------------------------------------------------
// MessageQueue

void MessageQueue::Enqueue(Message &aMessage, QueuePosition aPosition)
{
    OT_ASSERT(!aMessage.IsInAQueue());

    aMessage.GetMetadata().mInPriorityQ = false;

    if (GetHead() == nullptr)
    {
        aMessage.Next() = nullptr;
        aMessage.Prev() = nullptr;

        SetHead(&aMessage);
        SetTail(&aMessage);
    }
    else
    {
        switch (aPosition)
        {
        case kQueuePositionHead:
            aMessage.Next()   = GetHead();
            aMessage.Prev()   = nullptr;
            GetHead()->Prev() = &aMessage;
            SetHead(&aMessage);
            break;

        case kQueuePositionTail:
            aMessage.Next()   = nullptr;
            aMessage.Prev()   = GetTail();
            GetTail()->Next() = &aMessage;
            SetTail(&aMessage);
            break;
        }
    }
}

void MessageQueue::Dequeue(Message &aMessage)
{
    if (&aMessage == GetHead())
    {
        SetHead(aMessage.Next());
    }

    if (&aMessage == GetTail())
    {
        SetTail(aMessage.Prev());
    }

    if (aMessage.Prev() != nullptr)
    {
        aMessage.Prev()->Next() = aMessage.Next();
    }

    if (aMessage.Next() != nullptr)
    {
        aMessage.Next()->Prev() = aMessage.Prev();
    }

    aMessage.MarkAsNotInAQueue();
}

void MessageQueue::DequeueAndFree(Message &aMessage)
{
    Dequeue(aMessage);
    aMessage.Free();
}

void MessageQueue::DequeueAndFreeAll(void)
{
    Message *message;

    while ((message = GetHead()) != nullptr)
    {
        DequeueAndFree(*message);
    }
}

void MessageQueue::EnqueueAllFrom(MessageQueue &aOtherQueue)
{
    VerifyOrExit(&aOtherQueue != this);

    VerifyOrExit(aOtherQueue.GetHead() != nullptr);

    if (GetHead() == nullptr)
    {
        SetHead(aOtherQueue.GetHead());
    }
    else
    {
        GetTail()->Next()             = aOtherQueue.GetHead();
        aOtherQueue.GetHead()->Prev() = GetTail();
    }

    SetTail(aOtherQueue.GetTail());

    aOtherQueue.Clear();

exit:
    return;
}

Message::Iterator MessageQueue::begin(void) { return Message::Iterator(GetHead()); }

Message::ConstIterator MessageQueue::begin(void) const { return Message::ConstIterator(GetHead()); }

void MessageQueue::GetInfo(Info &aInfo) const
{
    ClearAllBytes(aInfo);

    for (const Message &message : *this)
    {
        aInfo.mNumMessages++;
        aInfo.mNumBuffers += message.GetBufferCount();
        aInfo.mTotalBytes += message.GetLength();
    }
}

void MessageQueue::AddQueueInfos(Info &aInfo, const Info &aOther)
{
    aInfo.mNumMessages += aOther.mNumMessages;
    aInfo.mNumBuffers += aOther.mNumBuffers;
    aInfo.mTotalBytes += aOther.mTotalBytes;
}

//---------------------------------------------------------------------------------------------------------------------
// PriorityQueue

const Message *PriorityQueue::FindTailForPriorityOrHigher(uint8_t aPriority) const
{
    // This method finds the tail (last message entry in the list)
    // that has priority level that is greater than or equal to
    // `aPriority` and currently has messages in the queue.
    //
    // The `PriorityQueue` uses the `mTails` array to store pointers
    // to the last message for each priority level. If a specific
    // priority level has no messages, its corresponding `mTails`
    // entry will be `nullptr`.
    //
    // The method iterates from `aPriority` upwards through higher
    // priority levels, returning the first non-null tail pointer it
    // encounters.
    //
    // The returned `Message` pointer indicates where a new message
    // with `aPriority` should be inserted: it should be placed
    // immediately after this returned message. If `nullptr` is
    // returned, it means the new message will be the first of its
    // priority level (or any higher priority) in the queue, and
    // should be placed at the head.

    const Message *tail = nullptr;

    for (uint8_t priority = aPriority; priority < Message::kNumPriorities; priority++)
    {
        tail = mTails[priority];

        if (tail != nullptr)
        {
            break;
        }
    }

    return tail;
}

const Message *PriorityQueue::GetHeadForPriority(Message::Priority aPriority) const
{
    const Message *head     = nullptr;
    uint8_t        priority = static_cast<uint8_t>(aPriority);

    if (mTails[priority] == nullptr)
    {
        head = nullptr;
    }
    else if (mHead->GetPriority() == aPriority)
    {
        head = mHead;
    }
    else
    {
        const Message *previousTail = FindTailForPriorityOrHigher(priority + 1);

        OT_ASSERT(previousTail != nullptr);
        head = previousTail->Next();
    }

    return head;
}

const Message *PriorityQueue::GetTail(void) const { return FindTailForPriorityOrHigher(Message::kPriorityLow); }

void PriorityQueue::Enqueue(Message &aMessage)
{
    uint8_t priority;

    OT_ASSERT(!aMessage.IsInAQueue());

    aMessage.GetMetadata().mInPriorityQ = true;

    priority = aMessage.GetPriority();

    // We insert the new `aMessage` immediately after the message
    // returned by `FindTailForPriorityOrHigher()`.
    //
    // If `FindTailForPriorityOrHigher()` returns `nullptr`, it means
    // `aMessage` has the highest priority of all existing messages
    // or is the first message in the queue, so we add it at the head
    // and update `mHead` accordingly.
    //
    // We first set the `Prev` and `Next` pointers within `aMessage`
    // itself. Afterward, we update the `Next()` pointer of the
    // preceding message (if any) and the `Prev()` pointer of the
    // succeeding message (if any) to point back to the newly
    // inserted `aMessage`, maintaining the linked list.

    aMessage.Prev() = FindTailForPriorityOrHigher(priority);

    if (aMessage.Prev() == nullptr)
    {
        aMessage.Next() = mHead;
        mHead           = &aMessage;
    }
    else
    {
        aMessage.Next()         = aMessage.Prev()->Next();
        aMessage.Prev()->Next() = &aMessage;
    }

    if (aMessage.Next() != nullptr)
    {
        aMessage.Next()->Prev() = &aMessage;
    }

    mTails[priority] = &aMessage;
}

void PriorityQueue::Dequeue(Message &aMessage)
{
    Message::Priority priority;

    OT_ASSERT(aMessage.IsInAPriorityQueue());

    priority = aMessage.GetPriority();

    // If `aMessage` is the current tail for its priority, update
    // `mTails[priority]`. The new tail becomes the preceding
    // message entry. If the preceding message has a different
    // (higher) priority, or if there's no preceding message, it
    // means `aMessage` was the last of its priority, and the
    // `mTails[priority]` is set to `nullptr`.

    if (&aMessage == mTails[priority])
    {
        mTails[priority] = aMessage.Prev();

        if ((mTails[priority] != nullptr) && (mTails[priority]->GetPriority() != priority))
        {
            mTails[priority] = nullptr;
        }
    }

    if (&aMessage == mHead)
    {
        mHead = aMessage.Next();
    }

    if (aMessage.Next() != nullptr)
    {
        aMessage.Next()->Prev() = aMessage.Prev();
    }

    if (aMessage.Prev() != nullptr)
    {
        aMessage.Prev()->Next() = aMessage.Next();
    }

    aMessage.MarkAsNotInAQueue();
}

void PriorityQueue::DequeueAndFree(Message &aMessage)
{
    Dequeue(aMessage);
    aMessage.Free();
}

void PriorityQueue::DequeueAndFreeAll(void)
{
    Message *message;

    while ((message = GetHead()) != nullptr)
    {
        DequeueAndFree(*message);
    }
}

Message::Iterator PriorityQueue::begin(void) { return Message::Iterator(GetHead()); }

Message::ConstIterator PriorityQueue::begin(void) const { return Message::ConstIterator(GetHead()); }

void PriorityQueue::GetInfo(Info &aInfo) const
{
    ClearAllBytes(aInfo);

    for (const Message &message : *this)
    {
        aInfo.mNumMessages++;
        aInfo.mNumBuffers += message.GetBufferCount();
        aInfo.mTotalBytes += message.GetLength();
    }
}

} // namespace ot
#endif // OPENTHREAD_MTD || OPENTHREAD_FTD
