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

#include "common/code_utils.hpp"
#include "common/instance.hpp"
#include "common/message.hpp"
#include "common/random.hpp"
#include "lib/spinel/spinel_buffer.hpp"

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

namespace ot {
namespace Spinel {

// This module implements unit-test for Spinel::Buffer class.

// Test related constants:
enum
{
    kTestBufferSize       = 800,
    kTestIterationAttemps = 10000,
    kTagArraySize         = 1000,
};

//  Messages used for building frames...
static const uint8_t sOpenThreadText[] = "OpenThread Rocks";
static const uint8_t sHelloText[]      = "Hello there!";
static const uint8_t sMottoText[]      = "Think good thoughts, say good words, do good deeds!";
static const uint8_t sMysteryText[]    = "4871(\\):|(3$}{4|/4/2%14(\\)";
static const uint8_t sHexText[]        = "0123456789abcdef";

static ot::Instance *sInstance;
static MessagePool * sMessagePool;

struct CallbackContext
{
    uint32_t mFrameAddedCount;   // Number of times FrameAddedCallback is invoked.
    uint32_t mFrameRemovedCount; // Number of times FrameRemovedCallback is invoked.
};

CallbackContext sContext;

enum
{
    kNumPrios = 2, // Number of priority levels.

    kTestFrame1Size = sizeof(sMottoText) + sizeof(sMysteryText) + sizeof(sMottoText) + sizeof(sHelloText),
    kTestFrame2Size = sizeof(sMysteryText) + sizeof(sHelloText) + sizeof(sOpenThreadText),
    kTestFrame3Size = sizeof(sMysteryText),
    kTestFrame4Size = sizeof(sOpenThreadText),
};

Spinel::Buffer::FrameTag sTagHistoryArray[kNumPrios][kTagArraySize];
uint32_t                 sTagHistoryHead[kNumPrios] = {0};
uint32_t                 sTagHistoryTail[kNumPrios] = {0};
Spinel::Buffer::FrameTag sExpectedRemovedTag        = Spinel::Buffer::kInvalidTag;

void ClearTagHistory(void)
{
    for (uint8_t priority = 0; priority < kNumPrios; priority++)
    {
        sTagHistoryHead[priority] = sTagHistoryTail[priority];
    }
}

void AddTagToHistory(Spinel::Buffer::FrameTag aTag, Spinel::Buffer::Priority aPriority)
{
    uint8_t priority = static_cast<uint8_t>(aPriority);

    sTagHistoryArray[priority][sTagHistoryTail[priority]] = aTag;

    if (++sTagHistoryTail[priority] == kTagArraySize)
    {
        sTagHistoryTail[priority] = 0;
    }

    VerifyOrQuit(sTagHistoryTail[priority] != sTagHistoryHead[priority],
                 "Ran out of space in `TagHistoryArray`, increase its size.");
}

void VerifyAndRemoveTagFromHistory(Spinel::Buffer::FrameTag aTag, Spinel::Buffer::Priority aPriority)
{
    uint8_t priority = static_cast<uint8_t>(aPriority);

    VerifyOrQuit(sTagHistoryHead[priority] != sTagHistoryTail[priority], "Tag history is empty,");
    VerifyOrQuit(aTag == sTagHistoryArray[priority][sTagHistoryHead[priority]],
                 "Removed tag does not match the added one");

    if (++sTagHistoryHead[priority] == kTagArraySize)
    {
        sTagHistoryHead[priority] = 0;
    }

    if (sExpectedRemovedTag != Spinel::Buffer::kInvalidTag)
    {
        VerifyOrQuit(sExpectedRemovedTag == aTag, "Removed tag does match the previous OutFrameGetTag()");
        sExpectedRemovedTag = Spinel::Buffer::kInvalidTag;
    }
}

void FrameAddedCallback(void *                   aContext,
                        Spinel::Buffer::FrameTag aTag,
                        Spinel::Buffer::Priority aPriority,
                        Spinel::Buffer *         aNcpBuffer)
{
    CallbackContext *callbackContext = reinterpret_cast<CallbackContext *>(aContext);

    VerifyOrQuit(aNcpBuffer != nullptr, "Null Spinel::Buffer in the callback");
    VerifyOrQuit(callbackContext != nullptr, "Null context in the callback");
    VerifyOrQuit(aTag != Spinel::Buffer::kInvalidTag, "Invalid tag in the callback");
    VerifyOrQuit(aTag == aNcpBuffer->InFrameGetLastTag(), "InFrameGetLastTag() does not match the tag from callback");

    AddTagToHistory(aTag, aPriority);

    callbackContext->mFrameAddedCount++;
}

void FrameRemovedCallback(void *                   aContext,
                          Spinel::Buffer::FrameTag aTag,
                          Spinel::Buffer::Priority aPriority,
                          Spinel::Buffer *         aNcpBuffer)
{
    CallbackContext *callbackContext = reinterpret_cast<CallbackContext *>(aContext);

    VerifyOrQuit(aNcpBuffer != nullptr, "Null Spinel::Buffer in the callback");
    VerifyOrQuit(callbackContext != nullptr, "Null context in the callback");
    VerifyOrQuit(aTag != Spinel::Buffer::kInvalidTag, "Invalid tag in the callback");

    VerifyAndRemoveTagFromHistory(aTag, aPriority);

    callbackContext->mFrameRemovedCount++;
}

// Reads bytes from the ncp buffer, and verifies that it matches with the given content buffer.
void ReadAndVerifyContent(Spinel::Buffer &aNcpBuffer, const uint8_t *aContentBuffer, uint16_t aBufferLength)
{
    while (aBufferLength--)
    {
        VerifyOrQuit(aNcpBuffer.OutFrameHasEnded() == false, "Out frame ended before end of expected content.");

        VerifyOrQuit(aNcpBuffer.OutFrameReadByte() == *aContentBuffer++,
                     "Out frame read byte does not match expected content");
    }
}

void WriteTestFrame1(Spinel::Buffer &aNcpBuffer, Spinel::Buffer::Priority aPriority)
{
    Message *       message;
    CallbackContext oldContext;

    message = sMessagePool->Allocate(Message::kTypeIp6);
    VerifyOrQuit(message != nullptr, "Null Message");
    SuccessOrQuit(message->SetLength(sizeof(sMottoText)));
    message->Write(0, sMottoText);

    oldContext = sContext;
    aNcpBuffer.InFrameBegin(aPriority);
    SuccessOrQuit(aNcpBuffer.InFrameFeedData(sMottoText, sizeof(sMottoText)));
    SuccessOrQuit(aNcpBuffer.InFrameFeedData(sMysteryText, sizeof(sMysteryText)));
    SuccessOrQuit(aNcpBuffer.InFrameFeedMessage(message));
    SuccessOrQuit(aNcpBuffer.InFrameFeedData(sHelloText, sizeof(sHelloText)));
    SuccessOrQuit(aNcpBuffer.InFrameEnd());
    VerifyOrQuit(oldContext.mFrameAddedCount + 1 == sContext.mFrameAddedCount, "FrameAddedCallback failed.");
    VerifyOrQuit(oldContext.mFrameRemovedCount == sContext.mFrameRemovedCount, "FrameRemovedCallback failed.");
}

void VerifyAndRemoveFrame1(Spinel::Buffer &aNcpBuffer)
{
    CallbackContext oldContext = sContext;

    sExpectedRemovedTag = aNcpBuffer.OutFrameGetTag();
    VerifyOrQuit(aNcpBuffer.OutFrameGetLength() == kTestFrame1Size);
    SuccessOrQuit(aNcpBuffer.OutFrameBegin());
    VerifyOrQuit(sExpectedRemovedTag == aNcpBuffer.OutFrameGetTag());
    VerifyOrQuit(aNcpBuffer.OutFrameGetLength() == kTestFrame1Size);
    ReadAndVerifyContent(aNcpBuffer, sMottoText, sizeof(sMottoText));
    ReadAndVerifyContent(aNcpBuffer, sMysteryText, sizeof(sMysteryText));
    ReadAndVerifyContent(aNcpBuffer, sMottoText, sizeof(sMottoText));
    ReadAndVerifyContent(aNcpBuffer, sHelloText, sizeof(sHelloText));
    VerifyOrQuit(aNcpBuffer.OutFrameHasEnded(), "Frame longer than expected.");
    VerifyOrQuit(aNcpBuffer.OutFrameReadByte() == 0, "ReadByte() returned non-zero after end of frame.");
    VerifyOrQuit(sExpectedRemovedTag == aNcpBuffer.OutFrameGetTag());
    VerifyOrQuit(aNcpBuffer.OutFrameGetLength() == kTestFrame1Size);
    SuccessOrQuit(aNcpBuffer.OutFrameRemove());
    VerifyOrQuit(oldContext.mFrameAddedCount == sContext.mFrameAddedCount, "FrameAddedCallback failed.");
    VerifyOrQuit(oldContext.mFrameRemovedCount + 1 == sContext.mFrameRemovedCount, "FrameRemovedCallback failed.");
}

void WriteTestFrame2(Spinel::Buffer &aNcpBuffer, Spinel::Buffer::Priority aPriority)
{
    Message *       message1;
    Message *       message2;
    CallbackContext oldContext = sContext;

    message1 = sMessagePool->Allocate(Message::kTypeIp6);
    VerifyOrQuit(message1 != nullptr, "Null Message");
    SuccessOrQuit(message1->SetLength(sizeof(sMysteryText)));
    message1->Write(0, sMysteryText);

    message2 = sMessagePool->Allocate(Message::kTypeIp6);
    VerifyOrQuit(message2 != nullptr, "Null Message");
    SuccessOrQuit(message2->SetLength(sizeof(sHelloText)));
    message2->Write(0, sHelloText);

    aNcpBuffer.InFrameBegin(aPriority);
    SuccessOrQuit(aNcpBuffer.InFrameFeedMessage(message1));
    SuccessOrQuit(aNcpBuffer.InFrameFeedData(sOpenThreadText, sizeof(sOpenThreadText)));
    SuccessOrQuit(aNcpBuffer.InFrameFeedMessage(message2));
    SuccessOrQuit(aNcpBuffer.InFrameEnd());

    VerifyOrQuit(oldContext.mFrameAddedCount + 1 == sContext.mFrameAddedCount, "FrameAddedCallback failed.");
    VerifyOrQuit(oldContext.mFrameRemovedCount == sContext.mFrameRemovedCount, "FrameRemovedCallback failed.");
}

void VerifyAndRemoveFrame2(Spinel::Buffer &aNcpBuffer)
{
    CallbackContext oldContext = sContext;

    VerifyOrQuit(aNcpBuffer.OutFrameGetLength() == kTestFrame2Size);
    SuccessOrQuit(aNcpBuffer.OutFrameBegin());
    VerifyOrQuit(aNcpBuffer.OutFrameGetLength() == kTestFrame2Size);
    ReadAndVerifyContent(aNcpBuffer, sMysteryText, sizeof(sMysteryText));
    ReadAndVerifyContent(aNcpBuffer, sOpenThreadText, sizeof(sOpenThreadText));
    ReadAndVerifyContent(aNcpBuffer, sHelloText, sizeof(sHelloText));
    VerifyOrQuit(aNcpBuffer.OutFrameHasEnded(), "Frame longer than expected.");
    VerifyOrQuit(aNcpBuffer.OutFrameReadByte() == 0, "ReadByte() returned non-zero after end of frame.");
    sExpectedRemovedTag = aNcpBuffer.OutFrameGetTag();
    VerifyOrQuit(aNcpBuffer.OutFrameGetLength() == kTestFrame2Size);
    SuccessOrQuit(aNcpBuffer.OutFrameRemove());

    VerifyOrQuit(oldContext.mFrameAddedCount == sContext.mFrameAddedCount, "FrameAddedCallback failed.");
    VerifyOrQuit(oldContext.mFrameRemovedCount + 1 == sContext.mFrameRemovedCount, "FrameRemovedCallback failed.");
}

void WriteTestFrame3(Spinel::Buffer &aNcpBuffer, Spinel::Buffer::Priority aPriority)
{
    Message *       message1;
    CallbackContext oldContext = sContext;

    message1 = sMessagePool->Allocate(Message::kTypeIp6);
    VerifyOrQuit(message1 != nullptr, "Null Message");

    // An empty message with no content.
    SuccessOrQuit(message1->SetLength(0));

    aNcpBuffer.InFrameBegin(aPriority);
    SuccessOrQuit(aNcpBuffer.InFrameFeedMessage(message1));
    SuccessOrQuit(aNcpBuffer.InFrameFeedData(sMysteryText, sizeof(sMysteryText)));
    SuccessOrQuit(aNcpBuffer.InFrameEnd());

    VerifyOrQuit(oldContext.mFrameAddedCount + 1 == sContext.mFrameAddedCount, "FrameAddedCallback failed.");
    VerifyOrQuit(oldContext.mFrameRemovedCount == sContext.mFrameRemovedCount, "FrameRemovedCallback failed.");
}

void VerifyAndRemoveFrame3(Spinel::Buffer &aNcpBuffer)
{
    CallbackContext oldContext = sContext;

    VerifyOrQuit(aNcpBuffer.OutFrameGetLength() == sizeof(sMysteryText));
    SuccessOrQuit(aNcpBuffer.OutFrameBegin());
    VerifyOrQuit(aNcpBuffer.OutFrameGetLength() == sizeof(sMysteryText));
    ReadAndVerifyContent(aNcpBuffer, sMysteryText, sizeof(sMysteryText));
    VerifyOrQuit(aNcpBuffer.OutFrameHasEnded(), "Frame longer than expected.");
    VerifyOrQuit(aNcpBuffer.OutFrameReadByte() == 0, "ReadByte() returned non-zero after end of frame.");
    sExpectedRemovedTag = aNcpBuffer.OutFrameGetTag();
    VerifyOrQuit(aNcpBuffer.OutFrameGetLength() == sizeof(sMysteryText));
    SuccessOrQuit(aNcpBuffer.OutFrameRemove());

    VerifyOrQuit(oldContext.mFrameAddedCount == sContext.mFrameAddedCount, "FrameAddedCallback failed.");
    VerifyOrQuit(oldContext.mFrameRemovedCount + 1 == sContext.mFrameRemovedCount, "FrameRemovedCallback failed.");
}

void WriteTestFrame4(Spinel::Buffer &aNcpBuffer, Spinel::Buffer::Priority aPriority)
{
    CallbackContext oldContext = sContext;

    aNcpBuffer.InFrameBegin(aPriority);
    SuccessOrQuit(aNcpBuffer.InFrameFeedData(sOpenThreadText, sizeof(sOpenThreadText)));
    SuccessOrQuit(aNcpBuffer.InFrameEnd());

    VerifyOrQuit(oldContext.mFrameAddedCount + 1 == sContext.mFrameAddedCount, "FrameAddedCallback failed.");
    VerifyOrQuit(oldContext.mFrameRemovedCount == sContext.mFrameRemovedCount, "FrameRemovedCallback failed.");
}

void VerifyAndRemoveFrame4(Spinel::Buffer &aNcpBuffer)
{
    CallbackContext oldContext = sContext;

    VerifyOrQuit(aNcpBuffer.OutFrameGetLength() == sizeof(sOpenThreadText));
    SuccessOrQuit(aNcpBuffer.OutFrameBegin());
    VerifyOrQuit(aNcpBuffer.OutFrameGetLength() == sizeof(sOpenThreadText));
    ReadAndVerifyContent(aNcpBuffer, sOpenThreadText, sizeof(sOpenThreadText));
    VerifyOrQuit(aNcpBuffer.OutFrameHasEnded(), "Frame longer than expected.");
    VerifyOrQuit(aNcpBuffer.OutFrameReadByte() == 0, "ReadByte() returned non-zero after end of frame.");
    sExpectedRemovedTag = aNcpBuffer.OutFrameGetTag();
    VerifyOrQuit(aNcpBuffer.OutFrameGetLength() == sizeof(sOpenThreadText));
    SuccessOrQuit(aNcpBuffer.OutFrameRemove());

    VerifyOrQuit(oldContext.mFrameAddedCount == sContext.mFrameAddedCount, "FrameAddedCallback failed.");
    VerifyOrQuit(oldContext.mFrameRemovedCount + 1 == sContext.mFrameRemovedCount, "FrameRemovedCallback failed.");
}

// This function implements the Spinel::Buffer tests
void TestBuffer(void)
{
    unsigned       i, j;
    uint8_t        buffer[kTestBufferSize];
    Spinel::Buffer ncpBuffer(buffer, kTestBufferSize);

    Message *                     message;
    uint8_t                       readBuffer[16];
    uint16_t                      readLen, readOffset;
    Spinel::Buffer::WritePosition pos1, pos2;

    sInstance    = testInitInstance();
    sMessagePool = &sInstance->Get<MessagePool>();

    for (i = 0; i < sizeof(buffer); i++)
    {
        buffer[i] = 0;
    }

    sContext.mFrameAddedCount   = 0;
    sContext.mFrameRemovedCount = 0;
    ClearTagHistory();

    // Set the callbacks.
    ncpBuffer.SetFrameAddedCallback(FrameAddedCallback, &sContext);
    ncpBuffer.SetFrameRemovedCallback(FrameRemovedCallback, &sContext);

    printf("\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
    printf("\nTest 1: Check initial buffer state");

    VerifyOrQuit(ncpBuffer.IsEmpty(), "Not empty after init.");
    VerifyOrQuit(ncpBuffer.InFrameGetLastTag() == Spinel::Buffer::kInvalidTag, "Incorrect tag after init.");
    VerifyOrQuit(ncpBuffer.OutFrameGetTag() == Spinel::Buffer::kInvalidTag, "Incorrect OutFrameTag after init.");

    printf("\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
    printf("\nTest 2: Write and read a single frame");

    WriteTestFrame1(ncpBuffer, Spinel::Buffer::kPriorityLow);
    DumpBuffer("\nBuffer after frame1 (low priority)", buffer, kTestBufferSize);
    printf("\nFrameLen is %u", ncpBuffer.OutFrameGetLength());
    VerifyAndRemoveFrame1(ncpBuffer);

    WriteTestFrame1(ncpBuffer, Spinel::Buffer::kPriorityHigh);
    DumpBuffer("\nBuffer after frame1 (high priority)", buffer, kTestBufferSize);
    printf("\nFrameLen is %u", ncpBuffer.OutFrameGetLength());
    VerifyAndRemoveFrame1(ncpBuffer);

    printf("\nIterations: ");

    // Always add as low priority.
    for (j = 0; j < kTestIterationAttemps; j++)
    {
        printf("*");
        WriteTestFrame1(ncpBuffer, Spinel::Buffer::kPriorityLow);
        VerifyOrQuit(ncpBuffer.IsEmpty() == false, "IsEmpty() is incorrect when buffer is non-empty");

        VerifyAndRemoveFrame1(ncpBuffer);
        VerifyOrQuit(ncpBuffer.IsEmpty(), "IsEmpty() is incorrect when buffer is empty.");
    }

    // Always add as high priority.
    for (j = 0; j < kTestIterationAttemps; j++)
    {
        printf("*");
        WriteTestFrame1(ncpBuffer, Spinel::Buffer::kPriorityHigh);
        VerifyOrQuit(ncpBuffer.IsEmpty() == false, "IsEmpty() is incorrect when buffer is non-empty");

        VerifyAndRemoveFrame1(ncpBuffer);
        VerifyOrQuit(ncpBuffer.IsEmpty(), "IsEmpty() is incorrect when buffer is empty.");
    }

    // Every 5th add as high priority.
    for (j = 0; j < kTestIterationAttemps; j++)
    {
        printf("*");
        WriteTestFrame1(ncpBuffer, ((j % 5) == 0) ? Spinel::Buffer::kPriorityHigh : Spinel::Buffer::kPriorityLow);
        VerifyOrQuit(ncpBuffer.IsEmpty() == false, "IsEmpty() is incorrect when buffer is non-empty");

        VerifyAndRemoveFrame1(ncpBuffer);
        VerifyOrQuit(ncpBuffer.IsEmpty(), "IsEmpty() is incorrect when buffer is empty.");
    }

    printf(" -- PASS\n");

    printf("\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
    printf("\nTest 3: Multiple frames write and read (same priority)");

    WriteTestFrame2(ncpBuffer, Spinel::Buffer::kPriorityLow);
    WriteTestFrame3(ncpBuffer, Spinel::Buffer::kPriorityLow);
    WriteTestFrame2(ncpBuffer, Spinel::Buffer::kPriorityLow);
    WriteTestFrame2(ncpBuffer, Spinel::Buffer::kPriorityLow);

    DumpBuffer("\nBuffer after multiple frames", buffer, kTestBufferSize);

    VerifyAndRemoveFrame2(ncpBuffer);
    VerifyAndRemoveFrame3(ncpBuffer);
    VerifyAndRemoveFrame2(ncpBuffer);
    VerifyAndRemoveFrame2(ncpBuffer);

    printf("\nIterations: ");

    // Repeat this multiple times.
    for (j = 0; j < kTestIterationAttemps; j++)
    {
        printf("*");

        WriteTestFrame2(ncpBuffer, Spinel::Buffer::kPriorityLow);
        WriteTestFrame3(ncpBuffer, Spinel::Buffer::kPriorityLow);
        WriteTestFrame2(ncpBuffer, Spinel::Buffer::kPriorityLow);

        VerifyAndRemoveFrame2(ncpBuffer);
        VerifyAndRemoveFrame3(ncpBuffer);

        WriteTestFrame2(ncpBuffer, Spinel::Buffer::kPriorityLow);
        WriteTestFrame3(ncpBuffer, Spinel::Buffer::kPriorityLow);

        VerifyAndRemoveFrame2(ncpBuffer);
        VerifyAndRemoveFrame2(ncpBuffer);
        VerifyAndRemoveFrame3(ncpBuffer);

        VerifyOrQuit(ncpBuffer.IsEmpty(), "IsEmpty() is incorrect when buffer is empty.");
    }

    printf(" -- PASS\n");

    printf("\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
    printf("\nTest 4: Multiple frames write and read (mixed priority)");

    WriteTestFrame2(ncpBuffer, Spinel::Buffer::kPriorityLow);
    WriteTestFrame3(ncpBuffer, Spinel::Buffer::kPriorityHigh);
    VerifyAndRemoveFrame3(ncpBuffer);
    VerifyAndRemoveFrame2(ncpBuffer);

    WriteTestFrame1(ncpBuffer, Spinel::Buffer::kPriorityLow);
    WriteTestFrame2(ncpBuffer, Spinel::Buffer::kPriorityLow);
    WriteTestFrame3(ncpBuffer, Spinel::Buffer::kPriorityHigh);
    WriteTestFrame4(ncpBuffer, Spinel::Buffer::kPriorityHigh);
    VerifyAndRemoveFrame3(ncpBuffer);
    VerifyAndRemoveFrame4(ncpBuffer);
    VerifyAndRemoveFrame1(ncpBuffer);
    VerifyAndRemoveFrame2(ncpBuffer);

    WriteTestFrame1(ncpBuffer, Spinel::Buffer::kPriorityLow);
    WriteTestFrame2(ncpBuffer, Spinel::Buffer::kPriorityHigh);
    WriteTestFrame3(ncpBuffer, Spinel::Buffer::kPriorityLow);
    WriteTestFrame4(ncpBuffer, Spinel::Buffer::kPriorityHigh);
    VerifyAndRemoveFrame2(ncpBuffer);
    VerifyAndRemoveFrame4(ncpBuffer);
    VerifyAndRemoveFrame1(ncpBuffer);
    VerifyAndRemoveFrame3(ncpBuffer);

    WriteTestFrame1(ncpBuffer, Spinel::Buffer::kPriorityLow);
    WriteTestFrame2(ncpBuffer, Spinel::Buffer::kPriorityHigh);
    WriteTestFrame3(ncpBuffer, Spinel::Buffer::kPriorityLow);
    WriteTestFrame4(ncpBuffer, Spinel::Buffer::kPriorityHigh);
    VerifyAndRemoveFrame2(ncpBuffer);
    VerifyAndRemoveFrame4(ncpBuffer);
    VerifyAndRemoveFrame1(ncpBuffer);
    VerifyAndRemoveFrame3(ncpBuffer);

    WriteTestFrame1(ncpBuffer, Spinel::Buffer::kPriorityHigh);
    WriteTestFrame2(ncpBuffer, Spinel::Buffer::kPriorityHigh);
    WriteTestFrame3(ncpBuffer, Spinel::Buffer::kPriorityLow);
    WriteTestFrame4(ncpBuffer, Spinel::Buffer::kPriorityLow);
    VerifyAndRemoveFrame1(ncpBuffer);
    VerifyAndRemoveFrame2(ncpBuffer);
    VerifyAndRemoveFrame3(ncpBuffer);
    VerifyAndRemoveFrame4(ncpBuffer);

    WriteTestFrame1(ncpBuffer, Spinel::Buffer::kPriorityLow);
    WriteTestFrame2(ncpBuffer, Spinel::Buffer::kPriorityHigh);
    WriteTestFrame3(ncpBuffer, Spinel::Buffer::kPriorityHigh);
    VerifyAndRemoveFrame2(ncpBuffer);
    WriteTestFrame4(ncpBuffer, Spinel::Buffer::kPriorityHigh);
    VerifyAndRemoveFrame3(ncpBuffer);
    VerifyAndRemoveFrame4(ncpBuffer);
    VerifyAndRemoveFrame1(ncpBuffer);

    printf(" -- PASS\n");

    printf("\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
    printf("\nTest 5: Frame discard when buffer full and partial read restart");

    printf("\nIterations: ");

    for (j = 0; j < kTestIterationAttemps; j++)
    {
        bool frame1IsHighPriority = ((j % 3) == 0);

        printf("*");

        WriteTestFrame2(ncpBuffer, Spinel::Buffer::kPriorityLow);
        WriteTestFrame3(ncpBuffer, Spinel::Buffer::kPriorityHigh);

        ncpBuffer.InFrameBegin((j % 2) == 0 ? Spinel::Buffer::kPriorityHigh : Spinel::Buffer::kPriorityLow);
        SuccessOrQuit(ncpBuffer.InFrameFeedData(sHelloText, sizeof(sHelloText)));

        message = sMessagePool->Allocate(Message::kTypeIp6);
        VerifyOrQuit(message != nullptr, "Null Message");
        SuccessOrQuit(message->SetLength(sizeof(sMysteryText)));
        message->Write(0, sMysteryText);

        SuccessOrQuit(ncpBuffer.InFrameFeedMessage(message));

        // Start writing a new frame in middle of an unfinished frame. Ensure the first one is discarded.
        WriteTestFrame1(ncpBuffer, frame1IsHighPriority ? Spinel::Buffer::kPriorityHigh : Spinel::Buffer::kPriorityLow);

        // Note that message will not be freed by the NCP buffer since the frame associated with it was discarded and
        // not yet finished/ended.
        otMessageFree(message);

        VerifyAndRemoveFrame3(ncpBuffer);

        // Start reading few bytes from the frame
        SuccessOrQuit(ncpBuffer.OutFrameBegin());
        ncpBuffer.OutFrameReadByte();
        ncpBuffer.OutFrameReadByte();
        ncpBuffer.OutFrameReadByte();

        // Now reset the read pointer and read/verify the frame from start.
        if (frame1IsHighPriority)
        {
            VerifyAndRemoveFrame1(ncpBuffer);
            VerifyAndRemoveFrame2(ncpBuffer);
        }
        else
        {
            VerifyAndRemoveFrame2(ncpBuffer);
            VerifyAndRemoveFrame1(ncpBuffer);
        }

        VerifyOrQuit(ncpBuffer.IsEmpty(), "IsEmpty() is incorrect when buffer is empty.");
    }

    printf(" -- PASS\n");

    printf("\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
    printf("\nTest 6: Clear() and empty buffer method tests");

    WriteTestFrame1(ncpBuffer, Spinel::Buffer::kPriorityLow);

    ncpBuffer.Clear();
    ClearTagHistory();

    VerifyOrQuit(ncpBuffer.InFrameGetLastTag() == Spinel::Buffer::kInvalidTag, "Incorrect last tag after Clear().");
    VerifyOrQuit(ncpBuffer.OutFrameGetTag() == Spinel::Buffer::kInvalidTag, "Incorrect OutFrameTag after Clear().");
    VerifyOrQuit(ncpBuffer.IsEmpty(), "IsEmpty() is incorrect when buffer is empty.");
    VerifyOrQuit(ncpBuffer.OutFrameHasEnded(), "OutFrameHasEnded() is incorrect when no data in buffer.");
    VerifyOrQuit(ncpBuffer.OutFrameRemove() == OT_ERROR_NOT_FOUND,
                 "Remove() returned incorrect error status when buffer is empty.");
    VerifyOrQuit(ncpBuffer.OutFrameGetLength() == 0,
                 "OutFrameGetLength() returned non-zero length when buffer is empty.");

    WriteTestFrame1(ncpBuffer, Spinel::Buffer::kPriorityLow);
    VerifyAndRemoveFrame1(ncpBuffer);

    VerifyOrQuit(ncpBuffer.IsEmpty(), "IsEmpty() is incorrect when buffer is empty.");
    VerifyOrQuit(ncpBuffer.OutFrameHasEnded(), "OutFrameHasEnded() is incorrect when no data in buffer.");
    VerifyOrQuit(ncpBuffer.OutFrameRemove() == OT_ERROR_NOT_FOUND,
                 "Remove() returned incorrect error status when buffer is empty.");
    VerifyOrQuit(ncpBuffer.OutFrameGetLength() == 0,
                 "OutFrameGetLength() returned non-zero length when buffer is empty.");

    printf(" -- PASS\n");

    printf("\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
    printf("\nTest 7: OutFrameRead() in parts\n");

    ncpBuffer.InFrameBegin(Spinel::Buffer::kPriorityLow);
    SuccessOrQuit(ncpBuffer.InFrameFeedData(sMottoText, sizeof(sMottoText)));
    SuccessOrQuit(ncpBuffer.InFrameEnd());

    SuccessOrQuit(ncpBuffer.OutFrameBegin());
    readOffset = 0;

    while ((readLen = ncpBuffer.OutFrameRead(sizeof(readBuffer), readBuffer)) != 0)
    {
        DumpBuffer("Read() returned", readBuffer, readLen);

        VerifyOrQuit(memcmp(readBuffer, sMottoText + readOffset, readLen) == 0,
                     "Read() does not match expected content.");

        readOffset += readLen;
    }

    VerifyOrQuit(readOffset == sizeof(sMottoText), "Read len does not match expected length.");

    SuccessOrQuit(ncpBuffer.OutFrameRemove());

    printf("\n -- PASS\n");

    printf("\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
    printf("\nTest 8: Remove a frame without reading it first");

    WriteTestFrame1(ncpBuffer, Spinel::Buffer::kPriorityLow);
    WriteTestFrame2(ncpBuffer, Spinel::Buffer::kPriorityLow);
    VerifyOrQuit(ncpBuffer.OutFrameGetLength() == kTestFrame1Size);
    SuccessOrQuit(ncpBuffer.OutFrameRemove());
    VerifyAndRemoveFrame2(ncpBuffer);
    printf(" -- PASS\n");

    printf("\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
    printf("\nTest 9: Check length when front frame gets changed (a higher priority frame is added)");
    WriteTestFrame1(ncpBuffer, Spinel::Buffer::kPriorityLow);
    VerifyOrQuit(ncpBuffer.OutFrameGetLength() == kTestFrame1Size);
    WriteTestFrame3(ncpBuffer, Spinel::Buffer::kPriorityHigh);
    VerifyOrQuit(ncpBuffer.OutFrameGetLength() == kTestFrame3Size);
    VerifyAndRemoveFrame3(ncpBuffer);
    VerifyAndRemoveFrame1(ncpBuffer);
    printf(" -- PASS\n");

    printf("\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
    printf("\nTest 10: Active out frame remaining unchanged when a higher priority frame is written while reading it");
    WriteTestFrame1(ncpBuffer, Spinel::Buffer::kPriorityLow);
    VerifyOrQuit(ncpBuffer.OutFrameGetLength() == kTestFrame1Size);
    SuccessOrQuit(ncpBuffer.OutFrameBegin());
    VerifyOrQuit(ncpBuffer.OutFrameGetLength() == kTestFrame1Size);
    ReadAndVerifyContent(ncpBuffer, sMottoText, sizeof(sMottoText));
    WriteTestFrame2(ncpBuffer, Spinel::Buffer::kPriorityHigh);
    VerifyOrQuit(ncpBuffer.OutFrameGetLength() == kTestFrame1Size);
    ReadAndVerifyContent(ncpBuffer, sMysteryText, sizeof(sMysteryText));
    SuccessOrQuit(ncpBuffer.OutFrameBegin());
    VerifyOrQuit(ncpBuffer.OutFrameGetLength() == kTestFrame1Size);
    ReadAndVerifyContent(ncpBuffer, sMottoText, sizeof(sMottoText));
    ReadAndVerifyContent(ncpBuffer, sMysteryText, sizeof(sMysteryText));
    ReadAndVerifyContent(ncpBuffer, sMottoText, sizeof(sMottoText));
    ReadAndVerifyContent(ncpBuffer, sHelloText, sizeof(sHelloText));
    VerifyOrQuit(ncpBuffer.OutFrameHasEnded(), "Frame longer than expected.");
    WriteTestFrame3(ncpBuffer, Spinel::Buffer::kPriorityHigh);
    WriteTestFrame4(ncpBuffer, Spinel::Buffer::kPriorityLow);
    VerifyOrQuit(ncpBuffer.OutFrameGetLength() == kTestFrame1Size);
    VerifyAndRemoveFrame1(ncpBuffer);
    VerifyAndRemoveFrame2(ncpBuffer);
    VerifyAndRemoveFrame3(ncpBuffer);
    VerifyAndRemoveFrame4(ncpBuffer);
    // Repeat test reversing frame priority orders.
    WriteTestFrame1(ncpBuffer, Spinel::Buffer::kPriorityHigh);
    VerifyOrQuit(ncpBuffer.OutFrameGetLength() == kTestFrame1Size);
    SuccessOrQuit(ncpBuffer.OutFrameBegin());
    VerifyOrQuit(ncpBuffer.OutFrameGetLength() == kTestFrame1Size);
    ReadAndVerifyContent(ncpBuffer, sMottoText, sizeof(sMottoText));
    WriteTestFrame2(ncpBuffer, Spinel::Buffer::kPriorityLow);
    VerifyOrQuit(ncpBuffer.OutFrameGetLength() == kTestFrame1Size);
    ReadAndVerifyContent(ncpBuffer, sMysteryText, sizeof(sMysteryText));
    SuccessOrQuit(ncpBuffer.OutFrameBegin());
    VerifyOrQuit(ncpBuffer.OutFrameGetLength() == kTestFrame1Size);
    ReadAndVerifyContent(ncpBuffer, sMottoText, sizeof(sMottoText));
    ReadAndVerifyContent(ncpBuffer, sMysteryText, sizeof(sMysteryText));
    ReadAndVerifyContent(ncpBuffer, sMottoText, sizeof(sMottoText));
    ReadAndVerifyContent(ncpBuffer, sHelloText, sizeof(sHelloText));
    VerifyOrQuit(ncpBuffer.OutFrameHasEnded(), "Frame longer than expected.");
    WriteTestFrame3(ncpBuffer, Spinel::Buffer::kPriorityHigh);
    WriteTestFrame4(ncpBuffer, Spinel::Buffer::kPriorityLow);
    VerifyOrQuit(ncpBuffer.OutFrameGetLength() == kTestFrame1Size);
    VerifyAndRemoveFrame1(ncpBuffer);
    VerifyAndRemoveFrame3(ncpBuffer);
    VerifyAndRemoveFrame2(ncpBuffer);
    VerifyAndRemoveFrame4(ncpBuffer);
    printf(" -- PASS\n");

    printf("\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
    printf("\n Test 11: Read and remove in middle of an active input frame write");
    WriteTestFrame1(ncpBuffer, Spinel::Buffer::kPriorityLow);
    ncpBuffer.InFrameBegin(Spinel::Buffer::kPriorityHigh);
    SuccessOrQuit(ncpBuffer.InFrameFeedData(sOpenThreadText, sizeof(sOpenThreadText)));
    VerifyAndRemoveFrame1(ncpBuffer);
    VerifyOrQuit(ncpBuffer.IsEmpty());
    SuccessOrQuit(ncpBuffer.InFrameEnd());
    VerifyAndRemoveFrame4(ncpBuffer);
    // Repeat the test reversing priorities
    WriteTestFrame1(ncpBuffer, Spinel::Buffer::kPriorityHigh);
    ncpBuffer.InFrameBegin(Spinel::Buffer::kPriorityLow);
    SuccessOrQuit(ncpBuffer.InFrameFeedData(sOpenThreadText, sizeof(sOpenThreadText)));
    VerifyAndRemoveFrame1(ncpBuffer);
    VerifyOrQuit(ncpBuffer.IsEmpty());
    SuccessOrQuit(ncpBuffer.InFrameEnd());
    VerifyAndRemoveFrame4(ncpBuffer);
    // Repeat the test with same priorities
    WriteTestFrame1(ncpBuffer, Spinel::Buffer::kPriorityHigh);
    ncpBuffer.InFrameBegin(Spinel::Buffer::kPriorityHigh);
    SuccessOrQuit(ncpBuffer.InFrameFeedData(sOpenThreadText, sizeof(sOpenThreadText)));
    VerifyAndRemoveFrame1(ncpBuffer);
    VerifyOrQuit(ncpBuffer.IsEmpty());
    SuccessOrQuit(ncpBuffer.InFrameEnd());
    VerifyAndRemoveFrame4(ncpBuffer);
    printf(" -- PASS\n");

    printf("\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
    printf("\n Test 12: Check returned error status");
    WriteTestFrame1(ncpBuffer, Spinel::Buffer::kPriorityLow);
    ncpBuffer.InFrameBegin(Spinel::Buffer::kPriorityHigh);
    VerifyOrQuit(ncpBuffer.InFrameFeedData(buffer, sizeof(buffer)) == OT_ERROR_NO_BUFS);
    VerifyAndRemoveFrame1(ncpBuffer);
    VerifyOrQuit(ncpBuffer.IsEmpty());

    WriteTestFrame1(ncpBuffer, Spinel::Buffer::kPriorityLow);
    WriteTestFrame2(ncpBuffer, Spinel::Buffer::kPriorityHigh);
    // Ensure writes with starting `InFrameBegin()` fail
    VerifyOrQuit(ncpBuffer.InFrameFeedData(sOpenThreadText, 1) == OT_ERROR_INVALID_STATE);
    VerifyOrQuit(ncpBuffer.InFrameFeedData(sOpenThreadText, 0) == OT_ERROR_INVALID_STATE);
    VerifyOrQuit(ncpBuffer.InFrameFeedData(sOpenThreadText, 0) == OT_ERROR_INVALID_STATE);
    VerifyOrQuit(ncpBuffer.InFrameEnd() == OT_ERROR_INVALID_STATE);
    message = sMessagePool->Allocate(Message::kTypeIp6);
    VerifyOrQuit(message != nullptr, "Null Message");
    SuccessOrQuit(message->SetLength(sizeof(sMysteryText)));
    message->Write(0, sMysteryText);
    VerifyOrQuit(ncpBuffer.InFrameFeedMessage(message) == OT_ERROR_INVALID_STATE);
    message->Free();
    VerifyOrQuit(ncpBuffer.InFrameEnd() == OT_ERROR_INVALID_STATE);
    VerifyAndRemoveFrame2(ncpBuffer);
    VerifyAndRemoveFrame1(ncpBuffer);
    VerifyOrQuit(ncpBuffer.IsEmpty());
    VerifyOrQuit(ncpBuffer.OutFrameBegin() == OT_ERROR_NOT_FOUND, "OutFrameBegin() failed on empty queue");
    WriteTestFrame1(ncpBuffer, Spinel::Buffer::kPriorityHigh);
    VerifyAndRemoveFrame1(ncpBuffer);
    VerifyOrQuit(ncpBuffer.IsEmpty());
    printf(" -- PASS\n");

    printf("\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
    printf("\n Test 13: Ensure we can utilize the full buffer size when frames removed during write");
    WriteTestFrame1(ncpBuffer, Spinel::Buffer::kPriorityHigh);
    WriteTestFrame2(ncpBuffer, Spinel::Buffer::kPriorityLow);
    ncpBuffer.InFrameBegin(Spinel::Buffer::kPriorityHigh);
    VerifyAndRemoveFrame1(ncpBuffer);
    VerifyAndRemoveFrame2(ncpBuffer);
    SuccessOrQuit(ncpBuffer.InFrameFeedData(buffer, sizeof(buffer) - 4));
    SuccessOrQuit(ncpBuffer.InFrameEnd());
    SuccessOrQuit(ncpBuffer.OutFrameRemove());
    // Repeat the test with a low priority buffer write
    WriteTestFrame1(ncpBuffer, Spinel::Buffer::kPriorityHigh);
    WriteTestFrame2(ncpBuffer, Spinel::Buffer::kPriorityLow);
    ncpBuffer.InFrameBegin(Spinel::Buffer::kPriorityLow);
    VerifyAndRemoveFrame1(ncpBuffer);
    VerifyAndRemoveFrame2(ncpBuffer);
    SuccessOrQuit(ncpBuffer.InFrameFeedData(buffer, sizeof(buffer) - 4));
    SuccessOrQuit(ncpBuffer.InFrameEnd());
    SuccessOrQuit(ncpBuffer.OutFrameRemove());
    printf(" -- PASS\n");

    printf("\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
    printf("\n Test 14: Test InFrameOverwrite ");
    printf("\nIterations: ");

    for (j = 0; j < kTestIterationAttemps; j++)
    {
        uint16_t                 index;
        bool                     addExtra = ((j % 7) != 0);
        Spinel::Buffer::Priority priority;

        printf("*");
        priority = ((j % 3) == 0) ? Spinel::Buffer::kPriorityHigh : Spinel::Buffer::kPriorityLow;
        index    = static_cast<uint16_t>(j % sizeof(sHexText));
        ncpBuffer.InFrameBegin(priority);
        SuccessOrQuit(ncpBuffer.InFrameFeedData(sHexText, index));
        SuccessOrQuit(ncpBuffer.InFrameGetPosition(pos1));
        SuccessOrQuit(ncpBuffer.InFrameFeedData(sMysteryText, sizeof(sHexText) - index));
        VerifyOrQuit(ncpBuffer.InFrameGetDistance(pos1) == sizeof(sHexText) - index);

        if (addExtra)
        {
            SuccessOrQuit(ncpBuffer.InFrameFeedData(sHelloText, sizeof(sHelloText)));
        }

        SuccessOrQuit(ncpBuffer.InFrameOverwrite(pos1, sHexText + index, sizeof(sHexText) - index),
                      "InFrameOverwrite() failed.");
        VerifyOrQuit(ncpBuffer.InFrameGetDistance(pos1) ==
                         sizeof(sHexText) - index + (addExtra ? sizeof(sHelloText) : 0),
                     "InFrameGetDistance() failed");
        SuccessOrQuit(ncpBuffer.InFrameEnd());
        VerifyOrQuit(ncpBuffer.InFrameGetPosition(pos2) == OT_ERROR_INVALID_STATE);
        VerifyOrQuit(ncpBuffer.InFrameOverwrite(pos1, sHexText, 0) != OT_ERROR_NONE);
        SuccessOrQuit(ncpBuffer.OutFrameBegin());
        ReadAndVerifyContent(ncpBuffer, sHexText, sizeof(sHexText));

        if (addExtra)
        {
            ReadAndVerifyContent(ncpBuffer, sHelloText, sizeof(sHelloText));
        }

        SuccessOrQuit(ncpBuffer.OutFrameRemove());
        VerifyOrQuit(ncpBuffer.InFrameGetPosition(pos2) == OT_ERROR_INVALID_STATE);
    }

    printf(" -- PASS\n");

    printf("\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
    printf("\n Test 15: Test InFrameReset()");
    printf("\nIterations: ");

    for (j = 0; j < kTestIterationAttemps; j++)
    {
        uint16_t                 index;
        bool                     addExtra = ((j % 7) != 0);
        Spinel::Buffer::Priority priority;

        printf("*");
        priority = ((j % 3) == 0) ? Spinel::Buffer::kPriorityHigh : Spinel::Buffer::kPriorityLow;
        index    = static_cast<uint16_t>(j % sizeof(sHexText));
        ncpBuffer.InFrameBegin(priority);
        SuccessOrQuit(ncpBuffer.InFrameFeedData(sHexText, index));
        SuccessOrQuit(ncpBuffer.InFrameGetPosition(pos1));
        SuccessOrQuit(ncpBuffer.InFrameFeedData(sMysteryText, sizeof(sHexText) - index));
        VerifyOrQuit(ncpBuffer.InFrameGetDistance(pos1) == sizeof(sHexText) - index);

        if (addExtra)
        {
            SuccessOrQuit(ncpBuffer.InFrameFeedData(sHelloText, sizeof(sHelloText)));
        }

        SuccessOrQuit(ncpBuffer.InFrameReset(pos1));
        SuccessOrQuit(ncpBuffer.InFrameFeedData(sHexText + index, sizeof(sHexText) - index));

        if (addExtra)
        {
            SuccessOrQuit(ncpBuffer.InFrameReset(pos1));
            SuccessOrQuit(ncpBuffer.InFrameFeedData(sHexText + index, sizeof(sHexText) - index));
        }

        VerifyOrQuit(ncpBuffer.InFrameGetDistance(pos1) == sizeof(sHexText) - index);
        SuccessOrQuit(ncpBuffer.InFrameEnd());
        SuccessOrQuit(ncpBuffer.OutFrameBegin());
        ReadAndVerifyContent(ncpBuffer, sHexText, sizeof(sHexText));
        SuccessOrQuit(ncpBuffer.OutFrameRemove());
    }

    printf(" -- PASS\n");

    testFreeInstance(sInstance);
}

/**
 * NCP Buffer Fuzz testing
 *
 * Randomly decide if to read or write a frame to the NCP buffer (use `kReadProbability` in percent to control the
 * behavior).
 *
 * When writing a frame, use a random length (1 up to `kMaxFrameLen`) and generate random byte sequences.
 * When reading a frame ensure the length and the content matches what was written earlier.
 * Handle the cases where buffer gets full or empty.
 *
 */

enum
{
    kFuzTestBufferSize            = 2000,   // Size of the buffer used during fuzz testing
    kFuzTestIterationAttempts     = 500000, // Number of iterations  to run
    kLensArraySize                = 500,    // Size of "Lengths" array.
    kMaxFrameLen                  = 400,    // Maximum frame length
    kReadProbability              = 50,     // Probability (in percent) to randomly choose to read vs write frame
    kHighPriorityProbability      = 20,     // Probability (in percent) to write a high priority frame
    kUseTrueRandomNumberGenerator = 1,      // To use true random number generator or not.
};

uint8_t  sFrameBuffer[kNumPrios][kFuzTestBufferSize];
uint32_t sFrameBufferTailIndex[kNumPrios] = {0};

uint32_t GetRandom(uint32_t max)
{
    uint32_t value;

    if (kUseTrueRandomNumberGenerator)
    {
        SuccessOrQuit(Random::Crypto::FillBuffer(reinterpret_cast<uint8_t *>(&value), sizeof(value)));
    }
    else
    {
        value = Random::NonCrypto::GetUint32();
    }

    return value % max;
}

otError WriteRandomFrame(uint32_t aLength, Spinel::Buffer &aNcpBuffer, Spinel::Buffer::Priority aPriority)
{
    otError         error;
    uint8_t         byte;
    uint8_t         priority   = static_cast<uint8_t>(aPriority);
    CallbackContext oldContext = sContext;
    uint32_t        tail       = sFrameBufferTailIndex[priority];

    aNcpBuffer.InFrameBegin(aPriority);

    while (aLength--)
    {
        byte = static_cast<uint8_t>(GetRandom(256));
        SuccessOrExit(error = aNcpBuffer.InFrameFeedData(&byte, sizeof(byte)));
        sFrameBuffer[priority][tail++] = byte;
    }

    SuccessOrExit(error = aNcpBuffer.InFrameEnd());

    sFrameBufferTailIndex[priority] = tail;

    // check the callbacks
    VerifyOrQuit(oldContext.mFrameAddedCount + 1 == sContext.mFrameAddedCount, "FrameAddedCallback failed.");
    VerifyOrQuit(oldContext.mFrameRemovedCount == sContext.mFrameRemovedCount, "FrameRemovedCallback failed.");

exit:
    return error;
}

otError ReadRandomFrame(uint32_t aLength, Spinel::Buffer &aNcpBuffer, uint8_t priority)
{
    CallbackContext oldContext = sContext;

    SuccessOrQuit(aNcpBuffer.OutFrameBegin());
    VerifyOrQuit(aNcpBuffer.OutFrameGetLength() == aLength);

    // Read and verify that the content is same as sFrameBuffer values...
    ReadAndVerifyContent(aNcpBuffer, sFrameBuffer[priority], static_cast<uint16_t>(aLength));
    sExpectedRemovedTag = aNcpBuffer.OutFrameGetTag();

    SuccessOrQuit(aNcpBuffer.OutFrameRemove());

    sFrameBufferTailIndex[priority] -= aLength;
    memmove(sFrameBuffer[priority], sFrameBuffer[priority] + aLength, sFrameBufferTailIndex[priority]);

    // If successful check the callbacks
    VerifyOrQuit(oldContext.mFrameAddedCount == sContext.mFrameAddedCount, "FrameAddedCallback failed.");
    VerifyOrQuit(oldContext.mFrameRemovedCount + 1 == sContext.mFrameRemovedCount, "FrameRemovedCallback failed.");

    return OT_ERROR_NONE;
}

// This runs a fuzz test of NCP buffer
void TestFuzzBuffer(void)
{
    uint8_t        buffer[kFuzTestBufferSize];
    Spinel::Buffer ncpBuffer(buffer, kFuzTestBufferSize);

    uint32_t lensArray[kNumPrios][kLensArraySize]; // Keeps track of length of written frames so far
    uint32_t lensArrayStart[kNumPrios];
    uint32_t lensArrayCount[kNumPrios];

    sInstance    = testInitInstance();
    sMessagePool = &sInstance->Get<MessagePool>();

    memset(buffer, 0, sizeof(buffer));

    memset(lensArray, 0, sizeof(lensArray));
    memset(lensArrayStart, 0, sizeof(lensArrayStart));
    memset(lensArrayCount, 0, sizeof(lensArrayCount));

    sContext.mFrameAddedCount   = 0;
    sContext.mFrameRemovedCount = 0;
    ClearTagHistory();

    ncpBuffer.SetFrameAddedCallback(FrameAddedCallback, &sContext);
    ncpBuffer.SetFrameRemovedCallback(FrameRemovedCallback, &sContext);

    for (uint32_t iter = 0; iter < kFuzTestIterationAttempts; iter++)
    {
        bool shouldRead;

        if (lensArrayCount[0] == 0 && lensArrayCount[1] == 0)
        {
            shouldRead = false;
        }
        else if (lensArrayCount[0] == kLensArraySize - 1 || lensArrayCount[1] == kLensArraySize - 1)
        {
            shouldRead = true;
        }
        else
        {
            // Randomly decide to read or write.
            shouldRead = (GetRandom(100) < kReadProbability);
        }

        if (shouldRead)
        {
            uint32_t len;
            uint8_t  priority;

            priority = (lensArrayCount[Spinel::Buffer::kPriorityHigh] != 0) ? Spinel::Buffer::kPriorityHigh
                                                                            : Spinel::Buffer::kPriorityLow;

            len                      = lensArray[priority][lensArrayStart[priority]];
            lensArrayStart[priority] = (lensArrayStart[priority] + 1) % kLensArraySize;
            lensArrayCount[priority]--;

            printf("R%c%d ", priority == Spinel::Buffer::kPriorityHigh ? 'H' : 'L', len);

            SuccessOrQuit(ReadRandomFrame(len, ncpBuffer, priority), "Failed to read random frame.");
        }
        else
        {
            uint32_t                 len = GetRandom(kMaxFrameLen) + 1;
            Spinel::Buffer::Priority priority;

            if (GetRandom(100) < kHighPriorityProbability)
            {
                priority = Spinel::Buffer::kPriorityHigh;
            }
            else
            {
                priority = Spinel::Buffer::kPriorityLow;
            }

            if (WriteRandomFrame(len, ncpBuffer, priority) == OT_ERROR_NONE)
            {
                lensArray[priority][(lensArrayStart[priority] + lensArrayCount[priority]) % kLensArraySize] = len;
                lensArrayCount[priority]++;

                printf("W%c%d ", priority == Spinel::Buffer::kPriorityHigh ? 'H' : 'L', len);
            }
            else
            {
                printf("FULL ");
            }
        }

        if (lensArrayCount[0] == 0 && lensArrayCount[1] == 0)
        {
            VerifyOrQuit(ncpBuffer.IsEmpty());
            printf("EMPTY ");
        }
    }

    printf("\n -- PASS\n");

    testFreeInstance(sInstance);
}

} // namespace Spinel
} // namespace ot

int main(void)
{
    ot::Spinel::TestBuffer();
    ot::Spinel::TestFuzzBuffer();
    printf("\nAll tests passed.\n");
    return 0;
}
