/*
 *    Copyright (c) 2016-2017, 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 general thread device required Spinel interface to the OpenThread stack.
 */

#include "ncp_base.hpp"

#include <stdarg.h>
#include <stdlib.h>

#include <openthread/diag.h>
#include <openthread/icmp6.h>
#include <openthread/link.h>
#include <openthread/logging.h>
#include <openthread/ncp.h>
#include <openthread/network_time.h>
#include <openthread/platform/misc.h>
#include <openthread/platform/radio.h>

#include "common/code_utils.hpp"
#include "common/debug.hpp"
#include "radio/radio.hpp"

namespace ot {
namespace Ncp {

// ----------------------------------------------------------------------------
// MARK: Utility Functions
// ----------------------------------------------------------------------------

#if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE
static bool HasOnly1BitSet(uint32_t aValue)
{
    return aValue != 0 && ((aValue & (aValue - 1)) == 0);
}

static uint8_t IndexOfMSB(uint32_t aValue)
{
    uint8_t index = 0;

    while (aValue >>= 1)
    {
        index++;
    }

    return index;
}
#endif // OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE

spinel_status_t NcpBase::ThreadErrorToSpinelStatus(otError aError)
{
    spinel_status_t ret;

    switch (aError)
    {
    case OT_ERROR_NONE:
        ret = SPINEL_STATUS_OK;
        break;

    case OT_ERROR_FAILED:
        ret = SPINEL_STATUS_FAILURE;
        break;

    case OT_ERROR_DROP:
        ret = SPINEL_STATUS_DROPPED;
        break;

    case OT_ERROR_NO_BUFS:
        ret = SPINEL_STATUS_NOMEM;
        break;

    case OT_ERROR_BUSY:
        ret = SPINEL_STATUS_BUSY;
        break;

    case OT_ERROR_PARSE:
        ret = SPINEL_STATUS_PARSE_ERROR;
        break;

    case OT_ERROR_INVALID_ARGS:
        ret = SPINEL_STATUS_INVALID_ARGUMENT;
        break;

    case OT_ERROR_NOT_IMPLEMENTED:
        ret = SPINEL_STATUS_UNIMPLEMENTED;
        break;

    case OT_ERROR_INVALID_STATE:
        ret = SPINEL_STATUS_INVALID_STATE;
        break;

    case OT_ERROR_NO_ACK:
        ret = SPINEL_STATUS_NO_ACK;
        break;

    case OT_ERROR_CHANNEL_ACCESS_FAILURE:
        ret = SPINEL_STATUS_CCA_FAILURE;
        break;

    case OT_ERROR_ALREADY:
        ret = SPINEL_STATUS_ALREADY;
        break;

    case OT_ERROR_NOT_FOUND:
        ret = SPINEL_STATUS_ITEM_NOT_FOUND;
        break;

    case OT_ERROR_UNKNOWN_NEIGHBOR:
        ret = SPINEL_STATUS_UNKNOWN_NEIGHBOR;
        break;

    case OT_ERROR_NOT_CAPABLE:
        ret = SPINEL_STATUS_NOT_CAPABLE;
        break;

    case OT_ERROR_RESPONSE_TIMEOUT:
        ret = SPINEL_STATUS_RESPONSE_TIMEOUT;
        break;

    default:
        // Unknown error code. Wrap it as a Spinel status and return that.
        ret = static_cast<spinel_status_t>(SPINEL_STATUS_STACK_NATIVE__BEGIN + static_cast<uint32_t>(aError));
        break;
    }

    return ret;
}

static spinel_status_t ResetReasonToSpinelStatus(otPlatResetReason aReason)
{
    spinel_status_t ret;

    switch (aReason)
    {
    case OT_PLAT_RESET_REASON_POWER_ON:
        ret = SPINEL_STATUS_RESET_POWER_ON;
        break;

    case OT_PLAT_RESET_REASON_EXTERNAL:
        ret = SPINEL_STATUS_RESET_EXTERNAL;
        break;

    case OT_PLAT_RESET_REASON_SOFTWARE:
        ret = SPINEL_STATUS_RESET_SOFTWARE;
        break;

    case OT_PLAT_RESET_REASON_FAULT:
        ret = SPINEL_STATUS_RESET_FAULT;
        break;

    case OT_PLAT_RESET_REASON_CRASH:
        ret = SPINEL_STATUS_RESET_CRASH;
        break;

    case OT_PLAT_RESET_REASON_ASSERT:
        ret = SPINEL_STATUS_RESET_ASSERT;
        break;

    case OT_PLAT_RESET_REASON_WATCHDOG:
        ret = SPINEL_STATUS_RESET_WATCHDOG;
        break;

    case OT_PLAT_RESET_REASON_OTHER:
        ret = SPINEL_STATUS_RESET_OTHER;
        break;

    default:
        ret = SPINEL_STATUS_RESET_UNKNOWN;
        break;
    }

    return ret;
}

// ----------------------------------------------------------------------------
// MARK: Class Boilerplate
// ----------------------------------------------------------------------------

NcpBase *NcpBase::sNcpInstance = nullptr;

NcpBase::NcpBase(Instance *aInstance)
    : mInstance(aInstance)
    , mTxFrameBuffer(mTxBuffer, sizeof(mTxBuffer))
    , mEncoder(mTxFrameBuffer)
    , mHostPowerStateInProgress(false)
    , mLastStatus(SPINEL_STATUS_OK)
    , mScanChannelMask(Radio::kSupportedChannels)
    , mScanPeriod(200)
    , mDiscoveryScanJoinerFlag(false)
    , mDiscoveryScanEnableFiltering(false)
    , mDiscoveryScanPanId(0xffff)
    , mUpdateChangedPropsTask(*aInstance, NcpBase::UpdateChangedProps)
    , mThreadChangedFlags(0)
    , mHostPowerState(SPINEL_HOST_POWER_STATE_ONLINE)
    , mHostPowerReplyFrameTag(Spinel::Buffer::kInvalidTag)
    , mHostPowerStateHeader(0)
#if OPENTHREAD_CONFIG_NCP_ENABLE_PEEK_POKE
    , mAllowPeekDelegate(nullptr)
    , mAllowPokeDelegate(nullptr)
#endif
    , mNextExpectedTid(0)
    , mResponseQueueHead(0)
    , mResponseQueueTail(0)
    , mAllowLocalNetworkDataChange(false)
    , mRequireJoinExistingNetwork(false)
    , mIsRawStreamEnabled(false)
    , mPcapEnabled(false)
    , mDisableStreamWrite(false)
    , mShouldEmitChildTableUpdate(false)
#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
    , mAllowLocalServerDataChange(false)
#endif
#if OPENTHREAD_FTD
    , mPreferredRouteId(0)
#endif
#if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE
    , mCurTransmitTID(0)
    , mCurScanChannel(kInvalidScanChannel)
    , mSrcMatchEnabled(false)
#endif // OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE
#if OPENTHREAD_MTD || OPENTHREAD_FTD
    , mInboundSecureIpFrameCounter(0)
    , mInboundInsecureIpFrameCounter(0)
    , mOutboundSecureIpFrameCounter(0)
    , mOutboundInsecureIpFrameCounter(0)
    , mDroppedOutboundIpFrameCounter(0)
    , mDroppedInboundIpFrameCounter(0)
#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
    , mSrpClientCallbackEnabled(false)
#endif
#endif // OPENTHREAD_MTD || OPENTHREAD_FTD
    , mFramingErrorCounter(0)
    , mRxSpinelFrameCounter(0)
    , mRxSpinelOutOfOrderTidCounter(0)
    , mTxSpinelFrameCounter(0)
    , mDidInitialUpdates(false)
    , mLogTimestampBase(0)
{
    OT_ASSERT(mInstance != nullptr);

    sNcpInstance = this;

    mTxFrameBuffer.SetFrameRemovedCallback(&NcpBase::HandleFrameRemovedFromNcpBuffer, this);

    memset(&mResponseQueue, 0, sizeof(mResponseQueue));

#if OPENTHREAD_MTD || OPENTHREAD_FTD
    otMessageQueueInit(&mMessageQueue);
    IgnoreError(otSetStateChangedCallback(mInstance, &NcpBase::HandleStateChanged, this));
    otIp6SetReceiveCallback(mInstance, &NcpBase::HandleDatagramFromStack, this);
    otIp6SetReceiveFilterEnabled(mInstance, true);
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
    otNetworkTimeSyncSetCallback(mInstance, &NcpBase::HandleTimeSyncUpdate, this);
#endif // OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
#if OPENTHREAD_CONFIG_UDP_FORWARD_ENABLE
    otUdpForwardSetForwarder(mInstance, &NcpBase::HandleUdpForwardStream, this);
#endif
    otIcmp6SetEchoMode(mInstance, OT_ICMP6_ECHO_HANDLER_DISABLED);
#if OPENTHREAD_FTD
    otThreadRegisterNeighborTableCallback(mInstance, &NcpBase::HandleNeighborTableChanged);
#if OPENTHREAD_CONFIG_MLE_STEERING_DATA_SET_OOB_ENABLE
    memset(&mSteeringDataAddress, 0, sizeof(mSteeringDataAddress));
#endif
    otThreadRegisterParentResponseCallback(mInstance, &NcpBase::HandleParentResponseInfo, static_cast<void *>(this));
#endif // OPENTHREAD_FTD
#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
    otSrpClientSetCallback(mInstance, HandleSrpClientCallback, this);
#endif
#if OPENTHREAD_CONFIG_LEGACY_ENABLE
    mLegacyNodeDidJoin = false;
    mLegacyHandlers    = nullptr;
    memset(mLegacyUlaPrefix, 0, sizeof(mLegacyUlaPrefix));
    memset(&mLegacyLastJoinedNode, 0, sizeof(mLegacyLastJoinedNode));
#endif
#endif // OPENTHREAD_MTD || OPENTHREAD_FTD
    mChangedPropsSet.AddLastStatus(SPINEL_STATUS_RESET_UNKNOWN);
    mUpdateChangedPropsTask.Post();

#if OPENTHREAD_ENABLE_VENDOR_EXTENSION
    aInstance->Get<Extension::ExtensionBase>().SignalNcpInit(*this);
#endif
}

NcpBase *NcpBase::GetNcpInstance(void)
{
    return sNcpInstance;
}

void NcpBase::ResetCounters(void)
{
    mFramingErrorCounter          = 0;
    mRxSpinelFrameCounter         = 0;
    mRxSpinelOutOfOrderTidCounter = 0;
    mTxSpinelFrameCounter         = 0;

#if OPENTHREAD_MTD || OPENTHREAD_FTD
    mInboundSecureIpFrameCounter    = 0;
    mInboundInsecureIpFrameCounter  = 0;
    mOutboundSecureIpFrameCounter   = 0;
    mOutboundInsecureIpFrameCounter = 0;
    mDroppedOutboundIpFrameCounter  = 0;
    mDroppedInboundIpFrameCounter   = 0;
#endif
}

// ----------------------------------------------------------------------------
// MARK: Serial Traffic Glue
// ----------------------------------------------------------------------------

Spinel::Buffer::FrameTag NcpBase::GetLastOutboundFrameTag(void)
{
    return mTxFrameBuffer.InFrameGetLastTag();
}

void NcpBase::HandleReceive(const uint8_t *aBuf, uint16_t aBufLength)
{
    otError      error  = OT_ERROR_NONE;
    uint8_t      header = 0;
    spinel_tid_t tid    = 0;

    mDisableStreamWrite = true;

    // Initialize the decoder with the newly received spinel frame.
    mDecoder.Init(aBuf, aBufLength);

    // Receiving any message from the host has the side effect of transitioning the host power state to online.
    mHostPowerState           = SPINEL_HOST_POWER_STATE_ONLINE;
    mHostPowerStateInProgress = false;

    // Skip if there is no header byte to read or this isn't a spinel frame.

    SuccessOrExit(mDecoder.ReadUint8(header));
    VerifyOrExit((SPINEL_HEADER_FLAG & header) == SPINEL_HEADER_FLAG);

    mRxSpinelFrameCounter++;

    // We only support IID zero for now.
    if (SPINEL_HEADER_GET_IID(header) != 0)
    {
        IgnoreError(WriteLastStatusFrame(header, SPINEL_STATUS_INVALID_INTERFACE));
        ExitNow();
    }

    error = HandleCommand(header);

    if (error != OT_ERROR_NONE)
    {
        IgnoreError(PrepareLastStatusResponse(header, ThreadErrorToSpinelStatus(error)));
    }

    if (!IsResponseQueueEmpty())
    {
        // A response may have been prepared and queued for this command,
        // so we attempt to send/write any queued responses. Note that
        // if the response was prepared but cannot be sent now (not
        // enough buffer space available), it will be attempted again
        // from `HandleFrameRemovedFromNcpBuffer()` when buffer space
        // becomes available.

        IgnoreError(SendQueuedResponses());
    }

    // Check for out of sequence TIDs and update `mNextExpectedTid`,

    tid = SPINEL_HEADER_GET_TID(header);

    if ((mNextExpectedTid != 0) && (tid != mNextExpectedTid))
    {
        mRxSpinelOutOfOrderTidCounter++;
    }

    mNextExpectedTid = SPINEL_GET_NEXT_TID(tid);

exit:
    mDisableStreamWrite = false;
}

void NcpBase::HandleFrameRemovedFromNcpBuffer(void *                   aContext,
                                              Spinel::Buffer::FrameTag aFrameTag,
                                              Spinel::Buffer::Priority aPriority,
                                              Spinel::Buffer *         aNcpBuffer)
{
    OT_UNUSED_VARIABLE(aNcpBuffer);
    OT_UNUSED_VARIABLE(aPriority);

    static_cast<NcpBase *>(aContext)->HandleFrameRemovedFromNcpBuffer(aFrameTag);
}

void NcpBase::HandleFrameRemovedFromNcpBuffer(Spinel::Buffer::FrameTag aFrameTag)
{
    if (mHostPowerStateInProgress)
    {
        if (aFrameTag == mHostPowerReplyFrameTag)
        {
            mHostPowerStateInProgress = false;
        }
    }

    // A frame was removed from NCP TX buffer, so more space is now available.
    // We attempt to write/send any pending frames. Order of the checks
    // below is important: First any queued command responses, then
    // any queued IPv6 datagram messages, then any asynchronous property updates.
    // If a frame still can not fit in the available buffer, we exit immediately
    // and wait for next time this callback is invoked (when another frame is
    // removed and more buffer space becomes available).

    SuccessOrExit(SendQueuedResponses());

#if OPENTHREAD_ENABLE_NCP_VENDOR_HOOK
    VendorHandleFrameRemovedFromNcpBuffer(aFrameTag);
#endif

    // Check if `HOST_POWER_STATE` property update is required.

    if (mHostPowerStateHeader)
    {
        SuccessOrExit(WritePropertyValueIsFrame(mHostPowerStateHeader, SPINEL_PROP_HOST_POWER_STATE));

        mHostPowerStateHeader = 0;

        if (mHostPowerState != SPINEL_HOST_POWER_STATE_ONLINE)
        {
            mHostPowerReplyFrameTag   = GetLastOutboundFrameTag();
            mHostPowerStateInProgress = true;
        }
    }

#if OPENTHREAD_MTD || OPENTHREAD_FTD

    // Send any queued IPv6 datagram message.

    SuccessOrExit(SendQueuedDatagramMessages());
#endif

    // Send any unsolicited event-triggered property updates.

    UpdateChangedProps();

exit:
    return;
}

bool NcpBase::ShouldWakeHost(void)
{
    return (mHostPowerState != SPINEL_HOST_POWER_STATE_ONLINE && !mHostPowerStateInProgress);
}

bool NcpBase::ShouldDeferHostSend(void)
{
    return (mHostPowerState == SPINEL_HOST_POWER_STATE_DEEP_SLEEP && !mHostPowerStateInProgress);
}

void NcpBase::IncrementFrameErrorCounter(void)
{
    mFramingErrorCounter++;
}

otError NcpBase::StreamWrite(int aStreamId, const uint8_t *aDataPtr, int aDataLen)
{
    otError           error  = OT_ERROR_NONE;
    uint8_t           header = SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0;
    spinel_prop_key_t streamPropKey;

    if (aStreamId == 0)
    {
        streamPropKey = SPINEL_PROP_STREAM_DEBUG;
    }
    else
    {
        streamPropKey = static_cast<spinel_prop_key_t>(aStreamId);
    }

    VerifyOrExit(!mDisableStreamWrite, error = OT_ERROR_INVALID_STATE);
    VerifyOrExit(!mChangedPropsSet.IsPropertyFiltered(streamPropKey), error = OT_ERROR_INVALID_STATE);

    // If there is a pending queued response we do not allow any new log
    // stream writes. This is to ensure that log messages can not continue
    // to use the NCP buffer space and block other spinel frames.

    VerifyOrExit(IsResponseQueueEmpty(), error = OT_ERROR_NO_BUFS);

    SuccessOrExit(error = mEncoder.BeginFrame(header, SPINEL_CMD_PROP_VALUE_IS, streamPropKey));
    SuccessOrExit(error = mEncoder.WriteData(aDataPtr, static_cast<uint16_t>(aDataLen)));
    SuccessOrExit(error = mEncoder.EndFrame());

exit:

    if (error == OT_ERROR_NO_BUFS)
    {
        mChangedPropsSet.AddLastStatus(SPINEL_STATUS_NOMEM);
        mUpdateChangedPropsTask.Post();
    }

    return error;
}

uint8_t NcpBase::ConvertLogLevel(otLogLevel aLogLevel)
{
    uint8_t spinelLogLevel = SPINEL_NCP_LOG_LEVEL_EMERG;

    switch (aLogLevel)
    {
    case OT_LOG_LEVEL_NONE:
        spinelLogLevel = SPINEL_NCP_LOG_LEVEL_EMERG;
        break;

    case OT_LOG_LEVEL_CRIT:
        spinelLogLevel = SPINEL_NCP_LOG_LEVEL_CRIT;
        break;

    case OT_LOG_LEVEL_WARN:
        spinelLogLevel = SPINEL_NCP_LOG_LEVEL_WARN;
        break;

    case OT_LOG_LEVEL_NOTE:
        spinelLogLevel = SPINEL_NCP_LOG_LEVEL_NOTICE;
        break;

    case OT_LOG_LEVEL_INFO:
        spinelLogLevel = SPINEL_NCP_LOG_LEVEL_INFO;
        break;

    case OT_LOG_LEVEL_DEBG:
        spinelLogLevel = SPINEL_NCP_LOG_LEVEL_DEBUG;
        break;
    }

    return spinelLogLevel;
}

unsigned int NcpBase::ConvertLogRegion(otLogRegion aLogRegion)
{
    unsigned int spinelLogRegion = SPINEL_NCP_LOG_REGION_NONE;

    switch (aLogRegion)
    {
    case OT_LOG_REGION_API:
        spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_API;
        break;

    case OT_LOG_REGION_MLE:
        spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_MLE;
        break;

    case OT_LOG_REGION_ARP:
        spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_ARP;
        break;

    case OT_LOG_REGION_NET_DATA:
        spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_NET_DATA;
        break;

    case OT_LOG_REGION_ICMP:
        spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_ICMP;
        break;

    case OT_LOG_REGION_IP6:
        spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_IP6;
        break;

    case OT_LOG_REGION_TCP:
        spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_TCP;
        break;

    case OT_LOG_REGION_MAC:
        spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_MAC;
        break;

    case OT_LOG_REGION_MEM:
        spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_MEM;
        break;

    case OT_LOG_REGION_NCP:
        spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_NCP;
        break;

    case OT_LOG_REGION_MESH_COP:
        spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_MESH_COP;
        break;

    case OT_LOG_REGION_NET_DIAG:
        spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_NET_DIAG;
        break;

    case OT_LOG_REGION_PLATFORM:
        spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_PLATFORM;
        break;

    case OT_LOG_REGION_COAP:
        spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_COAP;
        break;

    case OT_LOG_REGION_CLI:
        spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_CLI;
        break;

    case OT_LOG_REGION_CORE:
        spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_CORE;
        break;

    case OT_LOG_REGION_UTIL:
        spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_UTIL;
        break;

    case OT_LOG_REGION_BBR:
        spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_BBR;
        break;

    case OT_LOG_REGION_MLR:
        spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_MLR;
        break;

    case OT_LOG_REGION_DUA:
        spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_DUA;
        break;

    case OT_LOG_REGION_BR:
        spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_BR;
        break;

    case OT_LOG_REGION_SRP:
        spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_SRP;
        break;

    case OT_LOG_REGION_DNS:
        spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_DNS;
        break;
    }

    return spinelLogRegion;
}

void NcpBase::Log(otLogLevel aLogLevel, otLogRegion aLogRegion, const char *aLogString)
{
    otError error  = OT_ERROR_NONE;
    uint8_t header = SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0;

    VerifyOrExit(!mDisableStreamWrite, error = OT_ERROR_INVALID_STATE);
    VerifyOrExit(!mChangedPropsSet.IsPropertyFiltered(SPINEL_PROP_STREAM_LOG));

    // If there is a pending queued response we do not allow any new log
    // stream writes. This is to ensure that log messages can not continue
    // to use the NCP buffer space and block other spinel frames.

    VerifyOrExit(IsResponseQueueEmpty(), error = OT_ERROR_NO_BUFS);

    SuccessOrExit(error = mEncoder.BeginFrame(header, SPINEL_CMD_PROP_VALUE_IS, SPINEL_PROP_STREAM_LOG));
    SuccessOrExit(error = mEncoder.WriteUtf8(aLogString));
    SuccessOrExit(error = mEncoder.WriteUint8(ConvertLogLevel(aLogLevel)));
    SuccessOrExit(error = mEncoder.WriteUintPacked(ConvertLogRegion(aLogRegion)));
    SuccessOrExit(error = mEncoder.WriteUint64(mLogTimestampBase + otPlatAlarmMilliGetNow()));
    SuccessOrExit(error = mEncoder.EndFrame());

exit:

    if (error == OT_ERROR_NO_BUFS)
    {
        mChangedPropsSet.AddLastStatus(SPINEL_STATUS_NOMEM);
        mUpdateChangedPropsTask.Post();
    }
}

#if OPENTHREAD_CONFIG_NCP_ENABLE_PEEK_POKE

void NcpBase::RegisterPeekPokeDelagates(otNcpDelegateAllowPeekPoke aAllowPeekDelegate,
                                        otNcpDelegateAllowPeekPoke aAllowPokeDelegate)
{
    mAllowPeekDelegate = aAllowPeekDelegate;
    mAllowPokeDelegate = aAllowPokeDelegate;
}

#endif // OPENTHREAD_CONFIG_NCP_ENABLE_PEEK_POKE

// ----------------------------------------------------------------------------
// MARK: Spinel Response Handling
// ----------------------------------------------------------------------------

uint8_t NcpBase::GetWrappedResponseQueueIndex(uint8_t aPosition)
{
    while (aPosition >= kResponseQueueSize)
    {
        aPosition -= kResponseQueueSize;
    }

    return aPosition;
}

otError NcpBase::EnqueueResponse(uint8_t aHeader, ResponseType aType, unsigned int aPropKeyOrStatus)
{
    otError        error = OT_ERROR_NONE;
    spinel_tid_t   tid   = SPINEL_HEADER_GET_TID(aHeader);
    ResponseEntry *entry;

    if (tid == 0)
    {
        // No response is required for TID zero. But we may emit a
        // `LAST_STATUS` error status (if not filtered) for TID
        // zero (e.g., for a dropped `STREAM_NET` set command).

        if (aType == kResponseTypeLastStatus)
        {
            mChangedPropsSet.AddLastStatus(static_cast<spinel_status_t>(aPropKeyOrStatus));
        }

        ExitNow();
    }

    if ((mResponseQueueTail - mResponseQueueHead) >= kResponseQueueSize)
    {
        // If there is no room a for a response, emit an unsolicited
        // `DROPPED` error status to indicate a spinel response was
        // dropped.

        mChangedPropsSet.AddLastStatus(SPINEL_STATUS_DROPPED);

        ExitNow(error = OT_ERROR_NO_BUFS);
    }

    // Transaction IDs are expected to come in sequence, if however, we
    // get an out of sequence TID, check if we already have a response
    // queued for this TID and if so mark the old entry as deleted.

    if (tid != mNextExpectedTid)
    {
        for (uint8_t cur = mResponseQueueHead; cur < mResponseQueueTail; cur++)
        {
            entry = &mResponseQueue[GetWrappedResponseQueueIndex(cur)];

            if (entry->mIsInUse && (entry->mTid == tid))
            {
                // Entry is just marked here and will be removed
                // from `SendQueuedResponses()`.

                entry->mIsInUse = false;
                break;
            }
        }
    }

    // Add the new entry in the queue at tail.

    entry = &mResponseQueue[GetWrappedResponseQueueIndex(mResponseQueueTail)];

    entry->mTid             = tid;
    entry->mIsInUse         = true;
    entry->mType            = aType;
    entry->mPropKeyOrStatus = aPropKeyOrStatus;

    mResponseQueueTail++;

exit:
    return error;
}

otError NcpBase::SendQueuedResponses(void)
{
    otError error = OT_ERROR_NONE;

    while (mResponseQueueHead != mResponseQueueTail)
    {
        ResponseEntry &entry = mResponseQueue[mResponseQueueHead];

        if (entry.mIsInUse)
        {
            uint8_t header = SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0;

            header |= static_cast<uint8_t>(entry.mTid << SPINEL_HEADER_TID_SHIFT);

            if (entry.mType == kResponseTypeLastStatus)
            {
                spinel_status_t status = static_cast<spinel_status_t>(entry.mPropKeyOrStatus);

                SuccessOrExit(error = WriteLastStatusFrame(header, status));
            }
            else
            {
                spinel_prop_key_t propKey       = static_cast<spinel_prop_key_t>(entry.mPropKeyOrStatus);
                bool              isGetResponse = (entry.mType == kResponseTypeGet);

                SuccessOrExit(error = WritePropertyValueIsFrame(header, propKey, isGetResponse));
            }
        }

        // Remove the response entry.

        entry.mIsInUse = false;

        mResponseQueueHead++;

        if (mResponseQueueHead == kResponseQueueSize)
        {
            // Only when `head` wraps, the `tail` will be wrapped as well.
            //
            // This ensures that `tail` is always bigger than `head` and
            // `(tail - head)` to correctly give the number of items in
            // the queue.

            mResponseQueueHead = 0;
            mResponseQueueTail = GetWrappedResponseQueueIndex(mResponseQueueTail);
        }
    }

exit:
    return error;
}

// ----------------------------------------------------------------------------
// MARK: Property/Status Changed
// ----------------------------------------------------------------------------

void NcpBase::UpdateChangedProps(Tasklet &aTasklet)
{
    OT_UNUSED_VARIABLE(aTasklet);
    GetNcpInstance()->UpdateChangedProps();
}

void NcpBase::UpdateChangedProps(void)
{
    uint8_t                       numEntries;
    spinel_prop_key_t             propKey;
    const ChangedPropsSet::Entry *entry;

#if OPENTHREAD_MTD || OPENTHREAD_FTD
    ProcessThreadChangedFlags();
#endif

    VerifyOrExit(!mChangedPropsSet.IsEmpty());

    entry = mChangedPropsSet.GetSupportedEntries(numEntries);

    for (uint8_t index = 0; index < numEntries; index++, entry++)
    {
        if (!mChangedPropsSet.IsEntryChanged(index))
        {
            continue;
        }

        propKey = entry->mPropKey;

        if (propKey == SPINEL_PROP_LAST_STATUS)
        {
            spinel_status_t status = entry->mStatus;

            if (status == SPINEL_STATUS_RESET_UNKNOWN)
            {
                status = ResetReasonToSpinelStatus(otPlatGetResetReason(mInstance));
            }

            SuccessOrExit(WriteLastStatusFrame(SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, status));
        }
        else if (mDidInitialUpdates)
        {
            SuccessOrExit(WritePropertyValueIsFrame(SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, propKey));
        }

        mChangedPropsSet.RemoveEntry(index);
        VerifyOrExit(!mChangedPropsSet.IsEmpty());
    }

exit:
    mDidInitialUpdates = true;
}

// ----------------------------------------------------------------------------
// MARK: Inbound Command Handler
// ----------------------------------------------------------------------------

otError NcpBase::HandleCommand(uint8_t aHeader)
{
    otError      error = OT_ERROR_NONE;
    unsigned int command;

    SuccessOrExit(error = mDecoder.ReadUintPacked(command));

    switch (command)
    {
    case SPINEL_CMD_NOOP:
        error = CommandHandler_NOOP(aHeader);
        break;

    case SPINEL_CMD_RESET:
        error = CommandHandler_RESET(aHeader);
        break;

    case SPINEL_CMD_PROP_VALUE_GET:
    case SPINEL_CMD_PROP_VALUE_SET:
    case SPINEL_CMD_PROP_VALUE_INSERT:
    case SPINEL_CMD_PROP_VALUE_REMOVE:
        error = CommandHandler_PROP_VALUE_update(aHeader, command);
        break;

#if OPENTHREAD_CONFIG_NCP_ENABLE_PEEK_POKE
    case SPINEL_CMD_PEEK:
        error = CommandHandler_PEEK(aHeader);
        break;

    case SPINEL_CMD_POKE:
        error = CommandHandler_POKE(aHeader);
        break;
#endif

#if OPENTHREAD_MTD || OPENTHREAD_FTD
    case SPINEL_CMD_NET_SAVE:
    case SPINEL_CMD_NET_RECALL:
        error = OT_ERROR_NOT_IMPLEMENTED;
        break;

    case SPINEL_CMD_NET_CLEAR:
        error = CommandHandler_NET_CLEAR(aHeader);
        break;
#endif // OPENTHREAD_MTD || OPENTHREAD_FTD

    default:

#if OPENTHREAD_ENABLE_NCP_VENDOR_HOOK
        if (command >= SPINEL_CMD_VENDOR__BEGIN && command < SPINEL_CMD_VENDOR__END)
        {
            error = VendorCommandHandler(aHeader, command);
            break;
        }
#endif

        error = PrepareLastStatusResponse(aHeader, SPINEL_STATUS_INVALID_COMMAND);
        break;
    }

exit:
    return error;
}

// ----------------------------------------------------------------------------
// MARK: Property Get/Set/Insert/Remove Commands
// ----------------------------------------------------------------------------

// Returns `true` and updates the `aError` on success.
bool NcpBase::HandlePropertySetForSpecialProperties(uint8_t aHeader, spinel_prop_key_t aKey, otError &aError)
{
    bool didHandle = true;

    // Here the properties that require special treatment are handled.
    // These properties are expected to form/write the response from
    // their set handler directly.

    switch (aKey)
    {
    case SPINEL_PROP_HOST_POWER_STATE:
        ExitNow(aError = HandlePropertySet_SPINEL_PROP_HOST_POWER_STATE(aHeader));

#if OPENTHREAD_CONFIG_DIAG_ENABLE
    case SPINEL_PROP_NEST_STREAM_MFG:
        ExitNow(aError = HandlePropertySet_SPINEL_PROP_NEST_STREAM_MFG(aHeader));
#endif

#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
    case SPINEL_PROP_MESHCOP_COMMISSIONER_GENERATE_PSKC:
        ExitNow(aError = HandlePropertySet_SPINEL_PROP_MESHCOP_COMMISSIONER_GENERATE_PSKC(aHeader));

    case SPINEL_PROP_THREAD_COMMISSIONER_ENABLED:
        ExitNow(aError = HandlePropertySet_SPINEL_PROP_THREAD_COMMISSIONER_ENABLED(aHeader));
#endif

#if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE
    case SPINEL_PROP_STREAM_RAW:
        ExitNow(aError = HandlePropertySet_SPINEL_PROP_STREAM_RAW(aHeader));
#endif

    default:
        didHandle = false;
        break;
    }

exit:
    return didHandle;
}

otError NcpBase::HandleCommandPropertySet(uint8_t aHeader, spinel_prop_key_t aKey)
{
    otError         error   = OT_ERROR_NONE;
    PropertyHandler handler = FindSetPropertyHandler(aKey);

    if (handler != nullptr)
    {
        mDisableStreamWrite = false;
        error               = (this->*handler)();
        mDisableStreamWrite = true;
    }
    else
    {
        // If there is no "set" handler, check if this property is one of the
        // ones that require different treatment.

        bool didHandle = HandlePropertySetForSpecialProperties(aHeader, aKey, error);

        VerifyOrExit(!didHandle);

#if OPENTHREAD_ENABLE_NCP_VENDOR_HOOK
        if (aKey >= SPINEL_PROP_VENDOR__BEGIN && aKey < SPINEL_PROP_VENDOR__END)
        {
            mDisableStreamWrite = false;
            error               = VendorSetPropertyHandler(aKey);
            mDisableStreamWrite = true;

            // An `OT_ERROR_NOT_FOUND` status from vendor handler indicates
            // that it does not support the given property key. In that
            // case, `didHandle` is set to `false` so a `LAST_STATUS` with
            // `PROP_NOT_FOUND` is emitted. Otherwise, we fall through to
            // prepare the response.

            didHandle = (error != OT_ERROR_NOT_FOUND);
        }
#endif

        VerifyOrExit(didHandle, error = PrepareLastStatusResponse(aHeader, SPINEL_STATUS_PROP_NOT_FOUND));
    }

    if (error == OT_ERROR_NONE)
    {
        error = PrepareSetResponse(aHeader, aKey);
    }
    else
    {
        error = PrepareLastStatusResponse(aHeader, ThreadErrorToSpinelStatus(error));
    }

exit:
    return error;
}

otError NcpBase::HandleCommandPropertyInsertRemove(uint8_t aHeader, spinel_prop_key_t aKey, unsigned int aCommand)
{
    otError         error           = OT_ERROR_NONE;
    PropertyHandler handler         = nullptr;
    unsigned int    responseCommand = 0;
    const uint8_t * valuePtr;
    uint16_t        valueLen;

    switch (aCommand)
    {
    case SPINEL_CMD_PROP_VALUE_INSERT:
        handler         = FindInsertPropertyHandler(aKey);
        responseCommand = SPINEL_CMD_PROP_VALUE_INSERTED;
        break;

    case SPINEL_CMD_PROP_VALUE_REMOVE:
        handler         = FindRemovePropertyHandler(aKey);
        responseCommand = SPINEL_CMD_PROP_VALUE_REMOVED;
        break;

    default:
        OT_ASSERT(false);
        OT_UNREACHABLE_CODE(break);
    }

    VerifyOrExit(handler != nullptr, error = PrepareLastStatusResponse(aHeader, SPINEL_STATUS_PROP_NOT_FOUND));

    // Save current read position in the decoder. Read the entire
    // content as a data blob (which is used in forming the response
    // in case of success), then reset the read position back so
    // that the `PropertyHandler` method can parse the content.

    mDecoder.SavePosition();
    IgnoreError(mDecoder.ReadData(valuePtr, valueLen));
    IgnoreError(mDecoder.ResetToSaved());

    mDisableStreamWrite = false;

    error = (this->*handler)();

    mDisableStreamWrite = true;

    VerifyOrExit(error == OT_ERROR_NONE, error = PrepareLastStatusResponse(aHeader, ThreadErrorToSpinelStatus(error)));

    error = WritePropertyValueInsertedRemovedFrame(aHeader, responseCommand, aKey, valuePtr, valueLen);

    // If the full response cannot be written now, instead prepare
    // a `LAST_STATUS(STATUS_OK)` update as response.

    if (error != OT_ERROR_NONE)
    {
        error = PrepareLastStatusResponse(aHeader, SPINEL_STATUS_OK);
    }

exit:
    return error;
}

// ----------------------------------------------------------------------------
// MARK: Outbound Frame Methods
// ----------------------------------------------------------------------------

otError NcpBase::WriteLastStatusFrame(uint8_t aHeader, spinel_status_t aLastStatus)
{
    otError error = OT_ERROR_NONE;

    if (SPINEL_HEADER_GET_IID(aHeader) == 0)
    {
        mLastStatus = aLastStatus;
    }

    SuccessOrExit(error = mEncoder.BeginFrame(aHeader, SPINEL_CMD_PROP_VALUE_IS, SPINEL_PROP_LAST_STATUS));
    SuccessOrExit(error = mEncoder.WriteUintPacked(aLastStatus));
    SuccessOrExit(error = mEncoder.EndFrame());

exit:
    return error;
}

otError NcpBase::WritePropertyValueIsFrame(uint8_t aHeader, spinel_prop_key_t aPropKey, bool aIsGetResponse)
{
    otError         error   = OT_ERROR_NONE;
    PropertyHandler handler = FindGetPropertyHandler(aPropKey);

    if (handler != nullptr)
    {
        SuccessOrExit(error = mEncoder.BeginFrame(aHeader, SPINEL_CMD_PROP_VALUE_IS, aPropKey));
        SuccessOrExit(error = (this->*handler)());
        ExitNow(error = mEncoder.EndFrame());
    }

#if OPENTHREAD_ENABLE_NCP_VENDOR_HOOK
    if (aPropKey >= SPINEL_PROP_VENDOR__BEGIN && aPropKey < SPINEL_PROP_VENDOR__END)
    {
        SuccessOrExit(error = mEncoder.BeginFrame(aHeader, SPINEL_CMD_PROP_VALUE_IS, aPropKey));

        error = VendorGetPropertyHandler(aPropKey);

        // An `OT_ERROR_NOT_FOUND` status from vendor handler indicates that
        // it did not support the given property key. In that case, we fall
        // through to prepare a `LAST_STATUS` response.

        if (error != OT_ERROR_NOT_FOUND)
        {
            SuccessOrExit(error);
            ExitNow(error = mEncoder.EndFrame());
        }
    }
#endif

    if (aIsGetResponse)
    {
        SuccessOrExit(error = WriteLastStatusFrame(aHeader, SPINEL_STATUS_PROP_NOT_FOUND));
    }
    else
    {
        // Send a STATUS_OK for "set" response to a property that
        // has no corresponding get handler.

        SuccessOrExit(error = WriteLastStatusFrame(aHeader, SPINEL_STATUS_OK));
    }

exit:
    return error;
}

otError NcpBase::WritePropertyValueInsertedRemovedFrame(uint8_t           aHeader,
                                                        unsigned int      aResponseCommand,
                                                        spinel_prop_key_t aPropKey,
                                                        const uint8_t *   aValuePtr,
                                                        uint16_t          aValueLen)
{
    otError error = OT_ERROR_NONE;

    SuccessOrExit(error = mEncoder.BeginFrame(aHeader, aResponseCommand, aPropKey));
    SuccessOrExit(error = mEncoder.WriteData(aValuePtr, aValueLen));
    SuccessOrExit(error = mEncoder.EndFrame());

exit:
    return error;
}

// ----------------------------------------------------------------------------
// MARK: Individual Command Handlers
// ----------------------------------------------------------------------------

otError NcpBase::CommandHandler_NOOP(uint8_t aHeader)
{
    return PrepareLastStatusResponse(aHeader, SPINEL_STATUS_OK);
}

otError NcpBase::CommandHandler_RESET(uint8_t aHeader)
{
    OT_UNUSED_VARIABLE(aHeader);

    otError error      = OT_ERROR_NONE;
    uint8_t reset_type = SPINEL_RESET_STACK;

    if (mDecoder.GetRemainingLengthInStruct() > 0)
    {
        SuccessOrAssert(error = mDecoder.ReadUint8(reset_type));
    }

#if OPENTHREAD_RADIO
    if (reset_type == SPINEL_RESET_STACK)
    {
        otInstanceResetRadioStack(mInstance);

        mIsRawStreamEnabled = false;
        mCurTransmitTID     = 0;
        mCurScanChannel     = kInvalidScanChannel;
        mSrcMatchEnabled    = false;

        ResetCounters();

        SuccessOrAssert(
            error = WriteLastStatusFrame(SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, SPINEL_STATUS_RESET_POWER_ON));
    }
    else
#endif
    {
        // Signal a platform reset. If implemented, this function
        // shouldn't return.
        otInstanceReset(mInstance);

#if OPENTHREAD_MTD || OPENTHREAD_FTD
        // We only get to this point if the
        // platform doesn't support resetting.
        // In such a case we fake it.
        IgnoreError(otThreadSetEnabled(mInstance, false));
        IgnoreError(otIp6SetEnabled(mInstance, false));
#endif

        sNcpInstance = nullptr;
    }

    return error;
}

otError NcpBase::CommandHandler_PROP_VALUE_update(uint8_t aHeader, unsigned int aCommand)
{
    otError      error   = OT_ERROR_NONE;
    unsigned int propKey = 0;

    error = mDecoder.ReadUintPacked(propKey);

    VerifyOrExit(error == OT_ERROR_NONE, error = PrepareLastStatusResponse(aHeader, ThreadErrorToSpinelStatus(error)));

    switch (aCommand)
    {
    case SPINEL_CMD_PROP_VALUE_GET:
        error = PrepareGetResponse(aHeader, static_cast<spinel_prop_key_t>(propKey));
        break;

    case SPINEL_CMD_PROP_VALUE_SET:
        error = HandleCommandPropertySet(aHeader, static_cast<spinel_prop_key_t>(propKey));
        break;

    case SPINEL_CMD_PROP_VALUE_INSERT:
    case SPINEL_CMD_PROP_VALUE_REMOVE:
        error = HandleCommandPropertyInsertRemove(aHeader, static_cast<spinel_prop_key_t>(propKey), aCommand);
        break;

    default:
        break;
    }

exit:
    return error;
}

#if OPENTHREAD_CONFIG_NCP_ENABLE_PEEK_POKE

otError NcpBase::CommandHandler_PEEK(uint8_t aHeader)
{
    otError  parseError    = OT_ERROR_NONE;
    otError  responseError = OT_ERROR_NONE;
    uint32_t address;
    uint16_t count;

    SuccessOrExit(parseError = mDecoder.ReadUint32(address));
    SuccessOrExit(parseError = mDecoder.ReadUint16(count));

    VerifyOrExit(count != 0, parseError = OT_ERROR_INVALID_ARGS);

    if (mAllowPeekDelegate != nullptr)
    {
        VerifyOrExit(mAllowPeekDelegate(address, count), parseError = OT_ERROR_INVALID_ARGS);
    }

    SuccessOrExit(responseError = mEncoder.BeginFrame(aHeader, SPINEL_CMD_PEEK_RET));
    SuccessOrExit(responseError = mEncoder.WriteUint32(address));
    SuccessOrExit(responseError = mEncoder.WriteUint16(count));
    SuccessOrExit(responseError = mEncoder.WriteData(reinterpret_cast<const uint8_t *>(address), count));
    SuccessOrExit(responseError = mEncoder.EndFrame());

exit:
    if (parseError != OT_ERROR_NONE)
    {
        responseError = PrepareLastStatusResponse(aHeader, ThreadErrorToSpinelStatus(parseError));
    }

    return responseError;
}

otError NcpBase::CommandHandler_POKE(uint8_t aHeader)
{
    otError        parseError = OT_ERROR_NONE;
    uint32_t       address;
    uint16_t       count;
    const uint8_t *dataPtr = nullptr;
    uint16_t       dataLen;

    SuccessOrExit(parseError = mDecoder.ReadUint32(address));
    SuccessOrExit(parseError = mDecoder.ReadUint16(count));
    SuccessOrExit(parseError = mDecoder.ReadData(dataPtr, dataLen));

    VerifyOrExit(count != 0, parseError = OT_ERROR_INVALID_ARGS);
    VerifyOrExit(count <= dataLen, parseError = OT_ERROR_INVALID_ARGS);

    if (mAllowPokeDelegate != nullptr)
    {
        VerifyOrExit(mAllowPokeDelegate(address, count), parseError = OT_ERROR_INVALID_ARGS);
    }

    memcpy(reinterpret_cast<uint8_t *>(address), dataPtr, count);

exit:
    return PrepareLastStatusResponse(aHeader, ThreadErrorToSpinelStatus(parseError));
}

#endif // OPENTHREAD_CONFIG_NCP_ENABLE_PEEK_POKE

// ----------------------------------------------------------------------------
// MARK: Individual Property Getters and Setters
// ----------------------------------------------------------------------------

#if OPENTHREAD_CONFIG_DIAG_ENABLE

otError NcpBase::HandlePropertySet_SPINEL_PROP_NEST_STREAM_MFG(uint8_t aHeader)
{
    const char *string = nullptr;
    char        output[OPENTHREAD_CONFIG_DIAG_OUTPUT_BUFFER_SIZE];
    otError     error = OT_ERROR_NONE;

    error = mDecoder.ReadUtf8(string);

    VerifyOrExit(error == OT_ERROR_NONE, error = WriteLastStatusFrame(aHeader, ThreadErrorToSpinelStatus(error)));

#if OPENTHREAD_MTD || OPENTHREAD_FTD
    // TODO do not pass mfg prefix
    // skip mfg prefix from wpantund
    if (memcmp(string, "mfg ", 4) == 0)
    {
        string += 4;
    }
#endif

    otDiagProcessCmdLine(mInstance, string, output, sizeof(output));

    // Prepare the response
    SuccessOrExit(error = mEncoder.BeginFrame(aHeader, SPINEL_CMD_PROP_VALUE_IS, SPINEL_PROP_NEST_STREAM_MFG));
    SuccessOrExit(error = mEncoder.WriteUtf8(output));
    SuccessOrExit(error = mEncoder.EndFrame());

exit:
    return error;
}

#endif // OPENTHREAD_CONFIG_DIAG_ENABLE

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_PHY_ENABLED>(void)
{
#if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE
    return mEncoder.WriteBool(otLinkRawIsEnabled(mInstance));
#else
    return mEncoder.WriteBool(false);
#endif
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_PHY_CHAN>(void)
{
    return mEncoder.WriteUint8(otLinkGetChannel(mInstance));
}

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_PHY_CHAN>(void)
{
    unsigned int channel = 0;
    otError      error   = OT_ERROR_NONE;

    SuccessOrExit(error = mDecoder.ReadUintPacked(channel));

    error = otLinkSetChannel(mInstance, static_cast<uint8_t>(channel));

#if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE

    SuccessOrExit(error);

    // Make sure we are update the receiving channel if raw link is enabled and we have raw
    // stream enabled already
    if (otLinkRawIsEnabled(mInstance) && mIsRawStreamEnabled)
    {
        error = otLinkRawReceive(mInstance);
    }

#endif // OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE

exit:
    return error;
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_MAC_PROMISCUOUS_MODE>(void)
{
    bool isPromiscuous;

#if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE
    isPromiscuous = otLinkRawGetPromiscuous(mInstance);
#else
    isPromiscuous = otLinkIsPromiscuous(mInstance);
#endif

    return mEncoder.WriteUint8(isPromiscuous ? SPINEL_MAC_PROMISCUOUS_MODE_FULL : SPINEL_MAC_PROMISCUOUS_MODE_OFF);
}

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MAC_PROMISCUOUS_MODE>(void)
{
    uint8_t mode  = 0;
    otError error = OT_ERROR_NONE;

    SuccessOrExit(error = mDecoder.ReadUint8(mode));

    switch (mode)
    {
    case SPINEL_MAC_PROMISCUOUS_MODE_OFF:
#if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE
        error = otLinkRawSetPromiscuous(mInstance, false);
#else
        error = otLinkSetPromiscuous(mInstance, false);
#endif
        break;

    case SPINEL_MAC_PROMISCUOUS_MODE_NETWORK:
    case SPINEL_MAC_PROMISCUOUS_MODE_FULL:
#if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE
        error = otLinkRawSetPromiscuous(mInstance, true);
#else
        error = otLinkSetPromiscuous(mInstance, true);
#endif
        break;

    default:
        error = OT_ERROR_INVALID_ARGS;
        break;
    }

exit:
    return error;
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_MAC_15_4_PANID>(void)
{
    return mEncoder.WriteUint16(otLinkGetPanId(mInstance));
}

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MAC_15_4_PANID>(void)
{
    uint16_t panid;
    otError  error = OT_ERROR_NONE;

    SuccessOrExit(error = mDecoder.ReadUint16(panid));

    error = otLinkSetPanId(mInstance, panid);

exit:
    return error;
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_MAC_15_4_LADDR>(void)
{
    return mEncoder.WriteEui64(*otLinkGetExtendedAddress(mInstance));
}

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MAC_15_4_LADDR>(void)
{
    const otExtAddress *extAddress;
    otError             error = OT_ERROR_NONE;

    SuccessOrExit(error = mDecoder.ReadEui64(extAddress));

    error = otLinkSetExtendedAddress(mInstance, extAddress);

exit:
    return error;
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_MAC_15_4_SADDR>(void)
{
    return mEncoder.WriteUint16(otLinkGetShortAddress(mInstance));
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_MAC_RAW_STREAM_ENABLED>(void)
{
    return mEncoder.WriteBool(mIsRawStreamEnabled);
}

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MAC_RAW_STREAM_ENABLED>(void)
{
    bool    enabled = false;
    otError error   = OT_ERROR_NONE;

    SuccessOrExit(error = mDecoder.ReadBool(enabled));

#if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE

    if (otLinkRawIsEnabled(mInstance))
    {
        if (enabled)
        {
            error = otLinkRawReceive(mInstance);
        }
        else
        {
            error = otLinkRawSleep(mInstance);
        }
    }

#endif // OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE

    mIsRawStreamEnabled = enabled;

exit:
    return error;
}

#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_RCP_CSL_ACCURACY>(void)
{
    return mEncoder.WriteUint8(otPlatRadioGetCslAccuracy(mInstance));
}
#endif

#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_RCP_CSL_UNCERTAINTY>(void)
{
    return mEncoder.WriteUint8(otPlatRadioGetCslUncertainty(mInstance));
}
#endif

otError NcpBase::EncodeChannelMask(uint32_t aChannelMask)
{
    otError error = OT_ERROR_NONE;

    for (uint8_t i = 0; i < 32; i++)
    {
        if (0 != (aChannelMask & (1 << i)))
        {
            SuccessOrExit(error = mEncoder.WriteUint8(i));
        }
    }

exit:
    return error;
}

otError NcpBase::DecodeChannelMask(uint32_t &aChannelMask)
{
    otError error = OT_ERROR_NONE;
    uint8_t channel;

    aChannelMask = 0;

    while (!mDecoder.IsAllReadInStruct())
    {
        SuccessOrExit(error = mDecoder.ReadUint8(channel));
        VerifyOrExit(channel <= 31, error = OT_ERROR_INVALID_ARGS);
        aChannelMask |= (1UL << channel);
    }

exit:
    return error;
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_MAC_SCAN_MASK>(void)
{
    return EncodeChannelMask(mScanChannelMask);
}

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MAC_SCAN_MASK>(void)
{
    uint32_t newMask = 0;
    otError  error   = OT_ERROR_NONE;

    SuccessOrExit(error = DecodeChannelMask(newMask));
    mScanChannelMask = newMask;

exit:
    return error;
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_MAC_SCAN_PERIOD>(void)
{
    return mEncoder.WriteUint16(mScanPeriod);
}

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MAC_SCAN_PERIOD>(void)
{
    return mDecoder.ReadUint16(mScanPeriod);
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_MAC_SCAN_STATE>(void)
{
    uint8_t scanState = SPINEL_SCAN_STATE_IDLE;

#if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE

    if (otLinkRawIsEnabled(mInstance))
    {
        scanState = (mCurScanChannel == kInvalidScanChannel) ? SPINEL_SCAN_STATE_IDLE : SPINEL_SCAN_STATE_ENERGY;
    }
    else

#endif // OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE

    {
#if OPENTHREAD_MTD || OPENTHREAD_FTD
        if (otLinkIsActiveScanInProgress(mInstance))
        {
            scanState = SPINEL_SCAN_STATE_BEACON;
        }
        else if (otLinkIsEnergyScanInProgress(mInstance))
        {
            scanState = SPINEL_SCAN_STATE_ENERGY;
        }
        else if (otThreadIsDiscoverInProgress(mInstance))
        {
            scanState = SPINEL_SCAN_STATE_DISCOVER;
        }
        else
        {
            scanState = SPINEL_SCAN_STATE_IDLE;
        }
#endif // OPENTHREAD_MTD || OPENTHREAD_FTD
    }

    return mEncoder.WriteUint8(scanState);
}

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MAC_SCAN_STATE>(void)
{
    uint8_t state = 0;
    otError error = OT_ERROR_NONE;

    SuccessOrExit(error = mDecoder.ReadUint8(state));

    switch (state)
    {
    case SPINEL_SCAN_STATE_IDLE:
        error = OT_ERROR_NONE;
        break;

#if OPENTHREAD_MTD || OPENTHREAD_FTD
    case SPINEL_SCAN_STATE_BEACON:
        error = otLinkActiveScan(mInstance, mScanChannelMask, mScanPeriod, &HandleActiveScanResult_Jump, this);
        SuccessOrExit(error);
        break;
#endif // OPENTHREAD_MTD || OPENTHREAD_FTD

    case SPINEL_SCAN_STATE_ENERGY:
#if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE
        if (otLinkRawIsEnabled(mInstance))
        {
            uint8_t scanChannel;

            // Make sure we aren't already scanning and that we have
            // only 1 bit set for the channel mask.
            VerifyOrExit(mCurScanChannel == kInvalidScanChannel, error = OT_ERROR_INVALID_STATE);
            VerifyOrExit(HasOnly1BitSet(mScanChannelMask), error = OT_ERROR_INVALID_ARGS);

            scanChannel     = IndexOfMSB(mScanChannelMask);
            mCurScanChannel = static_cast<int8_t>(scanChannel);

            error = otLinkRawEnergyScan(mInstance, scanChannel, mScanPeriod, LinkRawEnergyScanDone);
        }
        else
#endif // OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE
        {
#if OPENTHREAD_MTD || OPENTHREAD_FTD
            error = otLinkEnergyScan(mInstance, mScanChannelMask, mScanPeriod, &HandleEnergyScanResult_Jump, this);
#endif // OPENTHREAD_MTD || OPENTHREAD_FTD
        }

        SuccessOrExit(error);
        break;

#if OPENTHREAD_MTD || OPENTHREAD_FTD
    case SPINEL_SCAN_STATE_DISCOVER:
        error = otThreadDiscover(mInstance, mScanChannelMask, mDiscoveryScanPanId, mDiscoveryScanJoinerFlag,
                                 mDiscoveryScanEnableFiltering, &HandleActiveScanResult_Jump, this);

        SuccessOrExit(error);
        break;
#endif // OPENTHREAD_MTD || OPENTHREAD_FTD

    default:
        error = OT_ERROR_NOT_IMPLEMENTED;
        break;
    }

exit:
    return error;
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_UNSOL_UPDATE_FILTER>(void)
{
    otError                       error = OT_ERROR_NONE;
    uint8_t                       numEntries;
    const ChangedPropsSet::Entry *entry;

    entry = mChangedPropsSet.GetSupportedEntries(numEntries);

    for (uint8_t index = 0; index < numEntries; index++, entry++)
    {
        if (mChangedPropsSet.IsEntryFiltered(index))
        {
            SuccessOrExit(error = mEncoder.WriteUintPacked(entry->mPropKey));
        }
    }

exit:
    return error;
}

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_UNSOL_UPDATE_FILTER>(void)
{
    unsigned int propKey;
    otError      error = OT_ERROR_NONE;

    // First clear the current filter.
    mChangedPropsSet.ClearFilter();

    while (mDecoder.GetRemainingLengthInStruct() > 0)
    {
        SuccessOrExit(error = mDecoder.ReadUintPacked(propKey));

        IgnoreError(mChangedPropsSet.EnablePropertyFilter(static_cast<spinel_prop_key_t>(propKey), true));
    }

exit:
    // If we had an error, we may have actually changed
    // the state of the filter, So we need to report
    // those incomplete changes via an asynchronous
    // change event.

    if (error != OT_ERROR_NONE)
    {
        IgnoreError(
            WritePropertyValueIsFrame(SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, SPINEL_PROP_UNSOL_UPDATE_FILTER));
    }

    return error;
}

template <> otError NcpBase::HandlePropertyInsert<SPINEL_PROP_UNSOL_UPDATE_FILTER>(void)
{
    otError      error = OT_ERROR_NONE;
    unsigned int propKey;

    SuccessOrExit(error = mDecoder.ReadUintPacked(propKey));

    error = mChangedPropsSet.EnablePropertyFilter(static_cast<spinel_prop_key_t>(propKey), true);

exit:
    return error;
}

template <> otError NcpBase::HandlePropertyRemove<SPINEL_PROP_UNSOL_UPDATE_FILTER>(void)
{
    otError      error = OT_ERROR_NONE;
    unsigned int propKey;

    SuccessOrExit(error = mDecoder.ReadUintPacked(propKey));

    error = mChangedPropsSet.EnablePropertyFilter(static_cast<spinel_prop_key_t>(propKey), false);

exit:
    return error;
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_LAST_STATUS>(void)
{
    return mEncoder.WriteUintPacked(mLastStatus);
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_PROTOCOL_VERSION>(void)
{
    otError error = OT_ERROR_NONE;

    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_PROTOCOL_VERSION_THREAD_MAJOR));
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_PROTOCOL_VERSION_THREAD_MINOR));

exit:
    return error;
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_INTERFACE_TYPE>(void)
{
    return mEncoder.WriteUintPacked(SPINEL_PROTOCOL_TYPE_THREAD);
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_VENDOR_ID>(void)
{
    return mEncoder.WriteUintPacked(0); // Vendor ID. Zero for unknown.
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_CAPS>(void)
{
    otError error = OT_ERROR_NONE;

    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_COUNTERS));
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_UNSOL_UPDATE_FILTER));

#if OPENTHREAD_CONFIG_NCP_ENABLE_MCU_POWER_STATE_CONTROL
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_MCU_POWER_STATE));
#endif

#if OPENTHREAD_CONFIG_RADIO_2P4GHZ_OQPSK_SUPPORT
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_802_15_4_2450MHZ_OQPSK));
#endif

#if OPENTHREAD_CONFIG_RADIO_915MHZ_OQPSK_SUPPORT
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_802_15_4_915MHZ_OQPSK));
#endif

#if OPENTHREAD_FTD
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_CONFIG_FTD));
#elif OPENTHREAD_MTD
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_CONFIG_MTD));
#elif OPENTHREAD_RADIO
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_CONFIG_RADIO));
#endif

#if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_MAC_RAW));
#endif

#if OPENTHREAD_RADIO
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_RCP_API_VERSION));
#endif

#if OPENTHREAD_PLATFORM_POSIX
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_POSIX));
#endif

#if (OPENTHREAD_CONFIG_LOG_OUTPUT == OPENTHREAD_CONFIG_LOG_OUTPUT_APP)
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_OPENTHREAD_LOG_METADATA));
#endif

#if OPENTHREAD_MTD || OPENTHREAD_FTD

    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_NET_THREAD_1_1));

#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_NET_THREAD_1_2));
#endif

    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_PCAP));

#if OPENTHREAD_CONFIG_MAC_FILTER_ENABLE
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_MAC_ALLOWLIST));
#endif

#if OPENTHREAD_CONFIG_JAM_DETECTION_ENABLE
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_JAM_DETECT));
#endif

#if OPENTHREAD_CONFIG_CHILD_SUPERVISION_ENABLE
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_CHILD_SUPERVISION));
#endif

#if OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_CHANNEL_MONITOR));
#endif

#if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE && OPENTHREAD_FTD
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_CHANNEL_MANAGER));
#endif

#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_TIME_SYNC));
#endif

    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_ERROR_RATE_TRACKING));

#if OPENTHREAD_CONFIG_MLE_STEERING_DATA_SET_OOB_ENABLE
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_OOB_STEERING_DATA));
#endif

#if OPENTHREAD_CONFIG_IP6_SLAAC_ENABLE
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_SLAAC));
#endif

#if OPENTHREAD_CONFIG_PLATFORM_RADIO_COEX_ENABLE
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_RADIO_COEX));
#endif

#if OPENTHREAD_CONFIG_MAC_RETRY_SUCCESS_HISTOGRAM_ENABLE
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_MAC_RETRY_HISTOGRAM));
#endif

#if OPENTHREAD_CONFIG_NCP_ENABLE_PEEK_POKE
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_PEEK_POKE));
#endif

#if OPENTHREAD_CONFIG_MLE_MAX_CHILDREN > 0
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_ROLE_ROUTER));
#endif

    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_ROLE_SLEEPY));

#if OPENTHREAD_CONFIG_LEGACY_ENABLE
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_NEST_LEGACY_INTERFACE));
#endif

#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_THREAD_COMMISSIONER));
#endif

#if OPENTHREAD_CONFIG_JOINER_ENABLE
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_THREAD_JOINER));
#endif

#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_THREAD_BORDER_ROUTER));
#endif

#if OPENTHREAD_CONFIG_UDP_FORWARD_ENABLE
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_THREAD_UDP_FORWARD));
#endif

#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_THREAD_SERVICE));
#endif

#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_THREAD_CSL_RECEIVER));
#endif

#if OPENTHREAD_CONFIG_MULTI_RADIO
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_MULTI_RADIO));
#endif

#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_SRP_CLIENT));
#endif

#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_THREAD_LINK_METRICS));
#endif

#if OPENTHREAD_CONFIG_DUA_ENABLE
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_DUA));
#endif

#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_REFERENCE_DEVICE));
#endif

#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_THREAD_BACKBONE_ROUTER));
#endif

#endif // OPENTHREAD_MTD || OPENTHREAD_FTD

exit:
    return error;
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_NCP_VERSION>(void)
{
    return mEncoder.WriteUtf8(otGetVersionString());
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_INTERFACE_COUNT>(void)
{
    return mEncoder.WriteUint8(1); // Only one interface for now
}

#if OPENTHREAD_CONFIG_NCP_ENABLE_MCU_POWER_STATE_CONTROL

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_MCU_POWER_STATE>(void)
{
    spinel_mcu_power_state_t state = SPINEL_MCU_POWER_STATE_ON;

    switch (otPlatGetMcuPowerState(mInstance))
    {
    case OT_PLAT_MCU_POWER_STATE_ON:
        state = SPINEL_MCU_POWER_STATE_ON;
        break;

    case OT_PLAT_MCU_POWER_STATE_LOW_POWER:
        state = SPINEL_MCU_POWER_STATE_LOW_POWER;
        break;

    case OT_PLAT_MCU_POWER_STATE_OFF:
        state = SPINEL_MCU_POWER_STATE_OFF;
        break;
    }

    return mEncoder.WriteUint8(state);
}

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MCU_POWER_STATE>(void)
{
    otError             error = OT_ERROR_NONE;
    otPlatMcuPowerState powerState;
    uint8_t             state;

    SuccessOrExit(error = mDecoder.ReadUint8(state));

    switch (state)
    {
    case SPINEL_MCU_POWER_STATE_ON:
        powerState = OT_PLAT_MCU_POWER_STATE_ON;
        break;

    case SPINEL_MCU_POWER_STATE_LOW_POWER:
        powerState = OT_PLAT_MCU_POWER_STATE_LOW_POWER;
        break;

    case SPINEL_MCU_POWER_STATE_OFF:
        powerState = OT_PLAT_MCU_POWER_STATE_OFF;
        break;

    default:
        ExitNow(error = OT_ERROR_INVALID_ARGS);
    }

    SuccessOrExit(error = otPlatSetMcuPowerState(mInstance, powerState));

#if OPENTHREAD_FTD || OPENTHREAD_MTD

    // If the call `otPlatSetMcuPowerState()` was successful and the desire
    // state is `OFF`, ensure to disable Thread (MLE) operation (and stop
    // legacy) and also bring the IPv6 interface down.

    if (powerState == OT_PLAT_MCU_POWER_STATE_OFF)
    {
        if (otThreadGetDeviceRole(mInstance) != OT_DEVICE_ROLE_DISABLED)
        {
            IgnoreError(otThreadSetEnabled(mInstance, false));
            StopLegacy();
        }

        if (otIp6IsEnabled(mInstance))
        {
            IgnoreError(otIp6SetEnabled(mInstance, false));
        }
    }
#endif // #if OPENTHREAD_FTD || OPENTHREAD_MTD

exit:
    return error;
}

#else // OPENTHREAD_CONFIG_NCP_ENABLE_MCU_POWER_STATE_CONTROL

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_MCU_POWER_STATE>(void)
{
    return mEncoder.WriteUint8(SPINEL_MCU_POWER_STATE_ON);
}

#endif // OPENTHREAD_CONFIG_NCP_ENABLE_MCU_POWER_STATE_CONTROL

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_POWER_STATE>(void)
{
    return mEncoder.WriteUint8(SPINEL_POWER_STATE_ONLINE);
}

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_POWER_STATE>(void)
{
    return OT_ERROR_NOT_IMPLEMENTED;
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_HWADDR>(void)
{
    otExtAddress hwAddr;

    otLinkGetFactoryAssignedIeeeEui64(mInstance, &hwAddr);

    return mEncoder.WriteEui64(hwAddr);
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_LOCK>(void)
{
    // TODO: Implement property lock (Needs API!)
    return mEncoder.OverwriteWithLastStatusError(SPINEL_STATUS_UNIMPLEMENTED);
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_HOST_POWER_STATE>(void)
{
    return mEncoder.WriteUint8(mHostPowerState);
}

// Setting `HOST_POWER_STATE` is treated and implemented differently from other
// handlers as it requires two special behaviors (a) the response frame for the
// set operation should be tracked and only when it is delivered we can assume
// that host is sleep (b) the response is critical so if there is no spinel
// buffer to prepare the response, the current spinel header is saved to
// prepare and send the response as soon as buffer space becomes available.
otError NcpBase::HandlePropertySet_SPINEL_PROP_HOST_POWER_STATE(uint8_t aHeader)
{
    uint8_t powerState;
    otError error = OT_ERROR_NONE;

    error = mDecoder.ReadUint8(powerState);

    if (error == OT_ERROR_NONE)
    {
        switch (powerState)
        {
        case SPINEL_HOST_POWER_STATE_OFFLINE:
        case SPINEL_HOST_POWER_STATE_DEEP_SLEEP:
        case SPINEL_HOST_POWER_STATE_LOW_POWER:
        case SPINEL_HOST_POWER_STATE_ONLINE:
            // Adopt the requested power state.
            mHostPowerState = static_cast<spinel_host_power_state_t>(powerState);
            break;

        case SPINEL_HOST_POWER_STATE_RESERVED:
            // Per the specification, treat this as synonymous with SPINEL_HOST_POWER_STATE_DEEP_SLEEP.
            mHostPowerState = SPINEL_HOST_POWER_STATE_DEEP_SLEEP;
            break;

        default:
            // Per the specification, treat unrecognized values as synonymous with SPINEL_HOST_POWER_STATE_LOW_POWER.
            mHostPowerState = SPINEL_HOST_POWER_STATE_LOW_POWER;
            break;
        }

        mHostPowerStateHeader = 0;

        error = WritePropertyValueIsFrame(aHeader, SPINEL_PROP_HOST_POWER_STATE);

        if (mHostPowerState != SPINEL_HOST_POWER_STATE_ONLINE)
        {
            if (error == OT_ERROR_NONE)
            {
                mHostPowerReplyFrameTag = GetLastOutboundFrameTag();
            }
            else
            {
                mHostPowerReplyFrameTag = Spinel::Buffer::kInvalidTag;
            }

            mHostPowerStateInProgress = true;
        }

        if (error != OT_ERROR_NONE)
        {
            mHostPowerStateHeader = aHeader;

            // The reply will be queued when buffer space becomes available
            // in the NCP tx buffer so we return `success` to avoid sending a
            // NOMEM status for the same tid through `mDroppedReplyTid` list.

            error = OT_ERROR_NONE;
        }
    }
    else
    {
        error = WriteLastStatusFrame(aHeader, ThreadErrorToSpinelStatus(error));
    }

    return error;
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_UNSOL_UPDATE_LIST>(void)
{
    otError                       error = OT_ERROR_NONE;
    uint8_t                       numEntries;
    const ChangedPropsSet::Entry *entry;

    entry = mChangedPropsSet.GetSupportedEntries(numEntries);

    for (uint8_t index = 0; index < numEntries; index++, entry++)
    {
        if (entry->mFilterable)
        {
            SuccessOrExit(error = mEncoder.WriteUintPacked(entry->mPropKey));
        }
    }

exit:
    return error;
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_PHY_RSSI>(void)
{
    return mEncoder.WriteInt8(otPlatRadioGetRssi(mInstance));
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_PHY_RX_SENSITIVITY>(void)
{
    return mEncoder.WriteInt8(otPlatRadioGetReceiveSensitivity(mInstance));
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_PHY_FREQ>(void)
{
    uint32_t      freq_khz(0);
    const uint8_t chan(otLinkGetChannel(mInstance));

    if (chan == 0)
    {
        freq_khz = 868300;
    }
    else if (chan < 11)
    {
        freq_khz = 906000 - (2000 * 1) + 2000 * (chan);
    }
    else if (chan < 26)
    {
        freq_khz = 2405000 - (5000 * 11) + 5000 * (chan);
    }

    return mEncoder.WriteUint32(freq_khz);
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_PHY_CCA_THRESHOLD>(void)
{
    int8_t  threshold;
    otError error = OT_ERROR_NONE;

    error = otPlatRadioGetCcaEnergyDetectThreshold(mInstance, &threshold);

    if (error == OT_ERROR_NONE)
    {
        error = mEncoder.WriteInt8(threshold);
    }
    else
    {
        error = mEncoder.OverwriteWithLastStatusError(ThreadErrorToSpinelStatus(error));
    }

    return error;
}

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_PHY_CCA_THRESHOLD>(void)
{
    int8_t  threshold = 0;
    otError error     = OT_ERROR_NONE;

    SuccessOrExit(error = mDecoder.ReadInt8(threshold));
    error = otPlatRadioSetCcaEnergyDetectThreshold(mInstance, threshold);

exit:
    return error;
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_PHY_TX_POWER>(void)
{
    int8_t  power;
    otError error;

    error = otPlatRadioGetTransmitPower(mInstance, &power);

    if (error == OT_ERROR_NONE)
    {
        error = mEncoder.WriteInt8(power);
    }
    else
    {
        error = mEncoder.OverwriteWithLastStatusError(ThreadErrorToSpinelStatus(error));
    }

    return error;
}

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_PHY_TX_POWER>(void)
{
    int8_t  txPower = 0;
    otError error   = OT_ERROR_NONE;

    SuccessOrExit(error = mDecoder.ReadInt8(txPower));
    error = otPlatRadioSetTransmitPower(mInstance, txPower);

exit:
    return error;
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_PHY_FEM_LNA_GAIN>(void)
{
    int8_t  gain;
    otError error = OT_ERROR_NONE;

    error = otPlatRadioGetFemLnaGain(mInstance, &gain);

    if (error == OT_ERROR_NONE)
    {
        error = mEncoder.WriteInt8(gain);
    }
    else
    {
        error = mEncoder.OverwriteWithLastStatusError(ThreadErrorToSpinelStatus(error));
    }

    return error;
}

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_PHY_FEM_LNA_GAIN>(void)
{
    int8_t  gain  = 0;
    otError error = OT_ERROR_NONE;

    SuccessOrExit(error = mDecoder.ReadInt8(gain));
    error = otPlatRadioSetFemLnaGain(mInstance, gain);

exit:
    return error;
}

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_PHY_CHAN_MAX_POWER>(void)
{
    uint8_t channel;
    int8_t  maxPower;
    otError error = OT_ERROR_NONE;

    SuccessOrExit(error = mDecoder.ReadUint8(channel));
    SuccessOrExit(error = mDecoder.ReadInt8(maxPower));
    error = otPlatRadioSetChannelMaxTransmitPower(mInstance, channel, maxPower);

exit:
    return error;
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_PHY_REGION_CODE>(void)
{
    uint16_t regionCode;
    otError  error = OT_ERROR_NONE;

    error = otPlatRadioGetRegion(mInstance, &regionCode);
    if (error == OT_ERROR_NONE)
    {
        error = mEncoder.WriteUint16(regionCode);
    }
    else
    {
        error = mEncoder.OverwriteWithLastStatusError(ThreadErrorToSpinelStatus(error));
    }

    return error;
}

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_PHY_REGION_CODE>(void)
{
    uint16_t regionCode;
    otError  error = OT_ERROR_NONE;

    SuccessOrExit(error = mDecoder.ReadUint16(regionCode));
    error = otPlatRadioSetRegion(mInstance, regionCode);

exit:
    return error;
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_DEBUG_TEST_ASSERT>(void)
{
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
    OT_ASSERT(false);
#endif

    // We only get to this point if `OT_ASSERT(false)`
    // does not cause an NCP reset on the platform.
    // In such a case we return `false` as the
    // property value to indicate this.

    OT_UNREACHABLE_CODE(return mEncoder.WriteBool(false);)
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_DEBUG_TEST_WATCHDOG>(void)
{
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
    while (true)
        ;
#endif

    OT_UNREACHABLE_CODE(return OT_ERROR_NONE;)
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_DEBUG_NCP_LOG_LEVEL>(void)
{
    return mEncoder.WriteUint8(ConvertLogLevel(otLoggingGetLevel()));
}

#if OPENTHREAD_CONFIG_LOG_LEVEL_DYNAMIC_ENABLE
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_DEBUG_NCP_LOG_LEVEL>(void)
{
    otError    error;
    uint8_t    spinelNcpLogLevel = 0;
    otLogLevel logLevel;

    SuccessOrExit(error = mDecoder.ReadUint8(spinelNcpLogLevel));

    switch (spinelNcpLogLevel)
    {
    case SPINEL_NCP_LOG_LEVEL_EMERG:
    case SPINEL_NCP_LOG_LEVEL_ALERT:
        logLevel = OT_LOG_LEVEL_NONE;
        break;

    case SPINEL_NCP_LOG_LEVEL_CRIT:
        logLevel = OT_LOG_LEVEL_CRIT;
        break;

    case SPINEL_NCP_LOG_LEVEL_ERR:
    case SPINEL_NCP_LOG_LEVEL_WARN:
        logLevel = OT_LOG_LEVEL_WARN;
        break;

    case SPINEL_NCP_LOG_LEVEL_NOTICE:
        logLevel = OT_LOG_LEVEL_NOTE;
        break;

    case SPINEL_NCP_LOG_LEVEL_INFO:
        logLevel = OT_LOG_LEVEL_INFO;
        break;

    case SPINEL_NCP_LOG_LEVEL_DEBUG:
        logLevel = OT_LOG_LEVEL_DEBG;
        break;

    default:
        ExitNow(error = OT_ERROR_INVALID_ARGS);
        OT_UNREACHABLE_CODE(break);
    }

    IgnoreError(otLoggingSetLevel(logLevel));

exit:
    return error;
}
#endif // OPENTHREAD_CONFIG_LOG_LEVEL_DYNAMIC_ENABLE

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_DEBUG_LOG_TIMESTAMP_BASE>(void)
{
    uint64_t timestampBase = 0;
    otError  error         = OT_ERROR_NONE;
    uint32_t currentTime   = otPlatAlarmMilliGetNow();

    SuccessOrExit(error = mDecoder.ReadUint64(timestampBase));
    VerifyOrExit(timestampBase >= currentTime, error = OT_ERROR_INVALID_ARGS);

    mLogTimestampBase = timestampBase - currentTime;

exit:
    return error;
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_DEBUG_LOG_TIMESTAMP_BASE>(void)
{
    return mEncoder.WriteUint64(mLogTimestampBase);
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_PHY_CHAN_SUPPORTED>(void)
{
#if OPENTHREAD_RADIO
    return EncodeChannelMask(otPlatRadioGetSupportedChannelMask(mInstance));
#else
    return EncodeChannelMask(otLinkGetSupportedChannelMask(mInstance));
#endif // OPENTHREAD_RADIO
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_PHY_CHAN_PREFERRED>(void)
{
    return EncodeChannelMask(otPlatRadioGetPreferredChannelMask(mInstance));
}

#if OPENTHREAD_CONFIG_PLATFORM_RADIO_COEX_ENABLE
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_RADIO_COEX_ENABLE>(void)
{
    bool    enabled;
    otError error = OT_ERROR_NONE;

    SuccessOrExit(error = mDecoder.ReadBool(enabled));
    error = otPlatRadioSetCoexEnabled(mInstance, enabled);

exit:
    return error;
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_RADIO_COEX_ENABLE>(void)
{
    return mEncoder.WriteBool(otPlatRadioIsCoexEnabled(mInstance));
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_RADIO_COEX_METRICS>(void)
{
    otRadioCoexMetrics coexMetrics;
    otError            error = otPlatRadioGetCoexMetrics(mInstance, &coexMetrics);

    if (error != OT_ERROR_NONE)
    {
        error = mEncoder.OverwriteWithLastStatusError(ThreadErrorToSpinelStatus(error));
        ExitNow();
    }

    // Encode Tx Request related metrics
    SuccessOrExit(error = mEncoder.OpenStruct());
    SuccessOrExit(error = mEncoder.WriteUint32(coexMetrics.mNumTxRequest));
    SuccessOrExit(error = mEncoder.WriteUint32(coexMetrics.mNumTxGrantImmediate));
    SuccessOrExit(error = mEncoder.WriteUint32(coexMetrics.mNumTxGrantWait));
    SuccessOrExit(error = mEncoder.WriteUint32(coexMetrics.mNumTxGrantWaitActivated));
    SuccessOrExit(error = mEncoder.WriteUint32(coexMetrics.mNumTxGrantWaitTimeout));
    SuccessOrExit(error = mEncoder.WriteUint32(coexMetrics.mNumTxGrantDeactivatedDuringRequest));
    SuccessOrExit(error = mEncoder.WriteUint32(coexMetrics.mNumTxDelayedGrant));
    SuccessOrExit(error = mEncoder.WriteUint32(coexMetrics.mAvgTxRequestToGrantTime));
    SuccessOrExit(error = mEncoder.CloseStruct());

    // Encode Rx Request related metrics
    SuccessOrExit(error = mEncoder.OpenStruct());
    SuccessOrExit(error = mEncoder.WriteUint32(coexMetrics.mNumRxRequest));
    SuccessOrExit(error = mEncoder.WriteUint32(coexMetrics.mNumRxGrantImmediate));
    SuccessOrExit(error = mEncoder.WriteUint32(coexMetrics.mNumRxGrantWait));
    SuccessOrExit(error = mEncoder.WriteUint32(coexMetrics.mNumRxGrantWaitActivated));
    SuccessOrExit(error = mEncoder.WriteUint32(coexMetrics.mNumRxGrantWaitTimeout));
    SuccessOrExit(error = mEncoder.WriteUint32(coexMetrics.mNumRxGrantDeactivatedDuringRequest));
    SuccessOrExit(error = mEncoder.WriteUint32(coexMetrics.mNumRxDelayedGrant));
    SuccessOrExit(error = mEncoder.WriteUint32(coexMetrics.mAvgRxRequestToGrantTime));
    SuccessOrExit(error = mEncoder.WriteUint32(coexMetrics.mNumRxGrantNone));
    SuccessOrExit(error = mEncoder.CloseStruct());

    // Encode common metrics
    SuccessOrExit(error = mEncoder.WriteBool(coexMetrics.mStopped));
    SuccessOrExit(error = mEncoder.WriteUint32(coexMetrics.mNumGrantGlitch));

exit:
    return error;
}
#endif

#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE || OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
otError NcpBase::DecodeLinkMetrics(otLinkMetrics *aMetrics, bool aAllowPduCount)
{
    otError error   = OT_ERROR_NONE;
    uint8_t metrics = 0;

    SuccessOrExit(error = mDecoder.ReadUint8(metrics));

    if (metrics & SPINEL_THREAD_LINK_METRIC_PDU_COUNT)
    {
        VerifyOrExit(aAllowPduCount, error = OT_ERROR_INVALID_ARGS);
        aMetrics->mPduCount = true;
    }

    if (metrics & SPINEL_THREAD_LINK_METRIC_LQI)
    {
        aMetrics->mLqi = true;
    }

    if (metrics & SPINEL_THREAD_LINK_METRIC_LINK_MARGIN)
    {
        aMetrics->mLinkMargin = true;
    }

    if (metrics & SPINEL_THREAD_LINK_METRIC_RSSI)
    {
        aMetrics->mRssi = true;
    }

exit:
    return error;
}
#endif

} // namespace Ncp
} // namespace ot

// ----------------------------------------------------------------------------
// MARK: Peek/Poke delegate API
// ----------------------------------------------------------------------------

#if OPENTHREAD_CONFIG_NCP_ENABLE_PEEK_POKE
void otNcpRegisterPeekPokeDelagates(otNcpDelegateAllowPeekPoke aAllowPeekDelegate,
                                    otNcpDelegateAllowPeekPoke aAllowPokeDelegate)
{
    ot::Ncp::NcpBase *ncp = ot::Ncp::NcpBase::GetNcpInstance();

    if (ncp != nullptr)
    {
        ncp->RegisterPeekPokeDelagates(aAllowPeekDelegate, aAllowPokeDelegate);
    }
}
#endif // OPENTHREAD_CONFIG_NCP_ENABLE_PEEK_POKE

// ----------------------------------------------------------------------------
// MARK: Virtual Datastream I/O (Public API)
// ----------------------------------------------------------------------------

otError otNcpStreamWrite(int aStreamId, const uint8_t *aDataPtr, int aDataLen)
{
    otError           error = OT_ERROR_INVALID_STATE;
    ot::Ncp::NcpBase *ncp   = ot::Ncp::NcpBase::GetNcpInstance();

    if (ncp != nullptr)
    {
        error = ncp->StreamWrite(aStreamId, aDataPtr, aDataLen);
    }

    return error;
}

#if (OPENTHREAD_CONFIG_LOG_OUTPUT == OPENTHREAD_CONFIG_LOG_OUTPUT_APP)

extern "C" void otPlatLog(otLogLevel aLogLevel, otLogRegion aLogRegion, const char *aFormat, ...)
{
    va_list           args;
    char              logString[OPENTHREAD_CONFIG_NCP_SPINEL_LOG_MAX_SIZE];
    ot::Ncp::NcpBase *ncp = ot::Ncp::NcpBase::GetNcpInstance();

    va_start(args, aFormat);

    if (vsnprintf(logString, sizeof(logString), aFormat, args) > 0)
    {
        if (ncp != nullptr)
        {
            ncp->Log(aLogLevel, aLogRegion, logString);
        }
    }

    va_end(args);
}

#endif // (OPENTHREAD_CONFIG_LOG_OUTPUT == OPENTHREAD_CONFIG_LOG_OUTPUT_APP)
