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

#define OTBR_LOG_TAG "NcpSpinel"

#include "ncp_spinel.hpp"

#include <stdarg.h>

#include <algorithm>

#include <openthread/dataset.h>
#include <openthread/thread.h>

#include "common/code_utils.hpp"
#include "common/logging.hpp"
#include "lib/spinel/spinel.h"
#include "lib/spinel/spinel_decoder.hpp"
#include "lib/spinel/spinel_driver.hpp"
#include "lib/spinel/spinel_helper.hpp"

namespace otbr {
namespace Ncp {

static constexpr char kSpinelDataUnpackFormat[] = "CiiD";

NcpSpinel::NcpSpinel(void)
    : mSpinelDriver(nullptr)
    , mCmdTidsInUse(0)
    , mCmdNextTid(1)
    , mNcpBuffer(mTxBuffer, kTxBufferSize)
    , mEncoder(mNcpBuffer)
    , mIid(SPINEL_HEADER_INVALID_IID)
    , mPropsObserver(nullptr)
{
    std::fill_n(mWaitingKeyTable, SPINEL_PROP_LAST_STATUS, sizeof(mWaitingKeyTable));
    memset(mCmdTable, 0, sizeof(mCmdTable));
}

void NcpSpinel::Init(ot::Spinel::SpinelDriver &aSpinelDriver, PropsObserver &aObserver)
{
    mSpinelDriver  = &aSpinelDriver;
    mPropsObserver = &aObserver;
    mIid           = mSpinelDriver->GetIid();
    mSpinelDriver->SetFrameHandler(&HandleReceivedFrame, &HandleSavedFrame, this);
}

void NcpSpinel::Deinit(void)
{
    mSpinelDriver              = nullptr;
    mIp6AddressTableCallback   = nullptr;
    mNetifStateChangedCallback = nullptr;
}

otbrError NcpSpinel::SpinelDataUnpack(const uint8_t *aDataIn, spinel_size_t aDataLen, const char *aPackFormat, ...)
{
    otbrError      error = OTBR_ERROR_NONE;
    spinel_ssize_t unpacked;
    va_list        args;

    va_start(args, aPackFormat);
    unpacked = spinel_datatype_vunpack(aDataIn, aDataLen, aPackFormat, args);
    va_end(args);

    VerifyOrExit(unpacked > 0, error = OTBR_ERROR_PARSE);

exit:
    return error;
}

void NcpSpinel::DatasetSetActiveTlvs(const otOperationalDatasetTlvs &aActiveOpDatasetTlvs, AsyncTaskPtr aAsyncTask)
{
    otError      error        = OT_ERROR_NONE;
    EncodingFunc encodingFunc = [this, &aActiveOpDatasetTlvs] {
        return mEncoder.WriteData(aActiveOpDatasetTlvs.mTlvs, aActiveOpDatasetTlvs.mLength);
    };

    VerifyOrExit(mDatasetSetActiveTask == nullptr, error = OT_ERROR_BUSY);

    SuccessOrExit(error = SetProperty(SPINEL_PROP_THREAD_ACTIVE_DATASET_TLVS, encodingFunc));
    mDatasetSetActiveTask = aAsyncTask;

exit:
    if (error != OT_ERROR_NONE)
    {
        mTaskRunner.Post([aAsyncTask, error](void) { aAsyncTask->SetResult(error, "Failed to set active dataset!"); });
    }
}

void NcpSpinel::DatasetMgmtSetPending(std::shared_ptr<otOperationalDatasetTlvs> aPendingOpDatasetTlvsPtr,
                                      AsyncTaskPtr                              aAsyncTask)
{
    otError      error        = OT_ERROR_NONE;
    EncodingFunc encodingFunc = [this, aPendingOpDatasetTlvsPtr] {
        return mEncoder.WriteData(aPendingOpDatasetTlvsPtr->mTlvs, aPendingOpDatasetTlvsPtr->mLength);
    };

    VerifyOrExit(mDatasetMgmtSetPendingTask == nullptr, error = OT_ERROR_BUSY);

    SuccessOrExit(error = SetProperty(SPINEL_PROP_THREAD_MGMT_SET_PENDING_DATASET_TLVS, encodingFunc));
    mDatasetMgmtSetPendingTask = aAsyncTask;

exit:
    if (error != OT_ERROR_NONE)
    {
        mTaskRunner.Post([aAsyncTask, error] { aAsyncTask->SetResult(error, "Failed to set pending dataset!"); });
    }
}

void NcpSpinel::Ip6SetEnabled(bool aEnable, AsyncTaskPtr aAsyncTask)
{
    otError      error        = OT_ERROR_NONE;
    EncodingFunc encodingFunc = [this, aEnable] { return mEncoder.WriteBool(aEnable); };

    VerifyOrExit(mIp6SetEnabledTask == nullptr, error = OT_ERROR_BUSY);

    SuccessOrExit(error = SetProperty(SPINEL_PROP_NET_IF_UP, encodingFunc));
    mIp6SetEnabledTask = aAsyncTask;

exit:
    if (error != OT_ERROR_NONE)
    {
        mTaskRunner.Post(
            [aAsyncTask, error](void) { aAsyncTask->SetResult(error, "Failed to enable the network interface!"); });
    }
    return;
}

otbrError NcpSpinel::Ip6Send(const uint8_t *aData, uint16_t aLength)
{
    // TODO: Impelement this function.
    OTBR_UNUSED_VARIABLE(aData);
    OTBR_UNUSED_VARIABLE(aLength);

    return OTBR_ERROR_NONE;
}

void NcpSpinel::ThreadSetEnabled(bool aEnable, AsyncTaskPtr aAsyncTask)
{
    otError      error        = OT_ERROR_NONE;
    EncodingFunc encodingFunc = [this, aEnable] { return mEncoder.WriteBool(aEnable); };

    VerifyOrExit(mThreadSetEnabledTask == nullptr, error = OT_ERROR_BUSY);

    SuccessOrExit(error = SetProperty(SPINEL_PROP_NET_STACK_UP, encodingFunc));
    mThreadSetEnabledTask = aAsyncTask;

exit:
    if (error != OT_ERROR_NONE)
    {
        mTaskRunner.Post(
            [aAsyncTask, error](void) { aAsyncTask->SetResult(error, "Failed to enable the Thread network!"); });
    }
    return;
}

void NcpSpinel::ThreadDetachGracefully(AsyncTaskPtr aAsyncTask)
{
    otError      error        = OT_ERROR_NONE;
    EncodingFunc encodingFunc = [] { return OT_ERROR_NONE; };

    VerifyOrExit(mThreadDetachGracefullyTask == nullptr, error = OT_ERROR_BUSY);

    SuccessOrExit(error = SetProperty(SPINEL_PROP_NET_LEAVE_GRACEFULLY, encodingFunc));
    mThreadDetachGracefullyTask = aAsyncTask;

exit:
    if (error != OT_ERROR_NONE)
    {
        mTaskRunner.Post([aAsyncTask, error](void) { aAsyncTask->SetResult(error, "Failed to detach gracefully!"); });
    }
    return;
}

void NcpSpinel::ThreadErasePersistentInfo(AsyncTaskPtr aAsyncTask)
{
    otError      error = OT_ERROR_NONE;
    spinel_tid_t tid   = GetNextTid();

    VerifyOrExit(mThreadErasePersistentInfoTask == nullptr, error = OT_ERROR_BUSY);

    SuccessOrExit(error = mSpinelDriver->SendCommand(SPINEL_CMD_NET_CLEAR, SPINEL_PROP_LAST_STATUS, tid));

    mWaitingKeyTable[tid]          = SPINEL_PROP_LAST_STATUS;
    mCmdTable[tid]                 = SPINEL_CMD_NET_CLEAR;
    mThreadErasePersistentInfoTask = aAsyncTask;

exit:
    if (error != OT_ERROR_NONE)
    {
        FreeTidTableItem(tid);
        mTaskRunner.Post(
            [aAsyncTask, error](void) { aAsyncTask->SetResult(error, "Failed to erase persistent info!"); });
    }
}

void NcpSpinel::HandleReceivedFrame(const uint8_t *aFrame,
                                    uint16_t       aLength,
                                    uint8_t        aHeader,
                                    bool          &aSave,
                                    void          *aContext)
{
    static_cast<NcpSpinel *>(aContext)->HandleReceivedFrame(aFrame, aLength, aHeader, aSave);
}

void NcpSpinel::HandleReceivedFrame(const uint8_t *aFrame, uint16_t aLength, uint8_t aHeader, bool &aShouldSaveFrame)
{
    spinel_tid_t tid = SPINEL_HEADER_GET_TID(aHeader);

    if (tid == 0)
    {
        HandleNotification(aFrame, aLength);
    }
    else if (tid < kMaxTids)
    {
        HandleResponse(tid, aFrame, aLength);
    }
    else
    {
        otbrLogCrit("Received unexpected tid: %u", tid);
    }

    aShouldSaveFrame = false;
}

void NcpSpinel::HandleSavedFrame(const uint8_t *aFrame, uint16_t aLength, void *aContext)
{
    /* Intentionally Empty */
    OT_UNUSED_VARIABLE(aFrame);
    OT_UNUSED_VARIABLE(aLength);
    OT_UNUSED_VARIABLE(aContext);
}

void NcpSpinel::HandleNotification(const uint8_t *aFrame, uint16_t aLength)
{
    spinel_prop_key_t key;
    spinel_size_t     len  = 0;
    uint8_t          *data = nullptr;
    uint32_t          cmd;
    uint8_t           header;
    otbrError         error = OTBR_ERROR_NONE;

    SuccessOrExit(error = SpinelDataUnpack(aFrame, aLength, kSpinelDataUnpackFormat, &header, &cmd, &key, &data, &len));
    VerifyOrExit(SPINEL_HEADER_GET_TID(header) == 0, error = OTBR_ERROR_PARSE);
    VerifyOrExit(cmd == SPINEL_CMD_PROP_VALUE_IS);
    HandleValueIs(key, data, static_cast<uint16_t>(len));

exit:
    otbrLogResult(error, "HandleNotification: %s", __FUNCTION__);
}

void NcpSpinel::HandleResponse(spinel_tid_t aTid, const uint8_t *aFrame, uint16_t aLength)
{
    spinel_prop_key_t key;
    spinel_size_t     len  = 0;
    uint8_t          *data = nullptr;
    uint32_t          cmd;
    uint8_t           header;
    otbrError         error          = OTBR_ERROR_NONE;
    FailureHandler    failureHandler = nullptr;

    SuccessOrExit(error = SpinelDataUnpack(aFrame, aLength, kSpinelDataUnpackFormat, &header, &cmd, &key, &data, &len));

    VerifyOrExit(cmd == SPINEL_CMD_PROP_VALUE_IS, error = OTBR_ERROR_INVALID_STATE);

    switch (mCmdTable[aTid])
    {
    case SPINEL_CMD_PROP_VALUE_SET:
    {
        error = HandleResponseForPropSet(aTid, key, data, len);
        break;
    }
    case SPINEL_CMD_NET_CLEAR:
    {
        spinel_status_t status = SPINEL_STATUS_OK;

        SuccessOrExit(error = SpinelDataUnpack(data, len, SPINEL_DATATYPE_UINT_PACKED_S, &status));
        CallAndClear(mThreadErasePersistentInfoTask, ot::Spinel::SpinelStatusToOtError(status));
        break;
    }
    default:
        break;
    }

exit:
    if (error == OTBR_ERROR_INVALID_STATE)
    {
        otbrLogCrit("Received unexpected response with (cmd:%u, key:%u), waiting (cmd:%u, key:%u) for tid:%u", cmd, key,
                    mCmdTable[aTid], mWaitingKeyTable[aTid], aTid);
    }
    else if (error == OTBR_ERROR_PARSE)
    {
        otbrLogCrit("Error parsing response with tid:%u", aTid);
    }
    FreeTidTableItem(aTid);
}

void NcpSpinel::HandleValueIs(spinel_prop_key_t aKey, const uint8_t *aBuffer, uint16_t aLength)
{
    otbrError error = OTBR_ERROR_NONE;

    switch (aKey)
    {
    case SPINEL_PROP_LAST_STATUS:
    {
        spinel_status_t status = SPINEL_STATUS_OK;

        SuccessOrExit(error = SpinelDataUnpack(aBuffer, aLength, SPINEL_DATATYPE_UINT_PACKED_S, &status));

        otbrLogInfo("NCP last status: %s", spinel_status_to_cstr(status));
        break;
    }

    case SPINEL_PROP_NET_ROLE:
    {
        spinel_net_role_t role = SPINEL_NET_ROLE_DISABLED;
        otDeviceRole      deviceRole;

        SuccessOrExit(error = SpinelDataUnpack(aBuffer, aLength, SPINEL_DATATYPE_UINT8_S, &role));

        deviceRole = SpinelRoleToDeviceRole(role);
        mPropsObserver->SetDeviceRole(deviceRole);

        otbrLogInfo("Device role changed to %s", otThreadDeviceRoleToString(deviceRole));
        break;
    }

    case SPINEL_PROP_NET_LEAVE_GRACEFULLY:
    {
        CallAndClear(mThreadDetachGracefullyTask, OT_ERROR_NONE);
        break;
    }

    case SPINEL_PROP_THREAD_MGMT_SET_PENDING_DATASET_TLVS:
    {
        spinel_status_t status = SPINEL_STATUS_OK;

        SuccessOrExit(error = SpinelDataUnpack(aBuffer, aLength, SPINEL_DATATYPE_UINT_PACKED_S, &status));
        CallAndClear(mDatasetMgmtSetPendingTask, ot::Spinel::SpinelStatusToOtError(status));
        break;
    }

    case SPINEL_PROP_IPV6_ADDRESS_TABLE:
    {
        std::vector<Ip6AddressInfo> addressInfoTable;

        VerifyOrExit(ParseIp6AddressTable(aBuffer, aLength, addressInfoTable) == OT_ERROR_NONE,
                     error = OTBR_ERROR_PARSE);
        SafeInvoke(mIp6AddressTableCallback, addressInfoTable);
        break;
    }

    case SPINEL_PROP_IPV6_MULTICAST_ADDRESS_TABLE:
    {
        std::vector<Ip6Address> addressTable;

        VerifyOrExit(ParseIp6MulticastAddresses(aBuffer, aLength, addressTable) == OT_ERROR_NONE,
                     error = OTBR_ERROR_PARSE);
        SafeInvoke(mIp6MulticastAddressTableCallback, addressTable);
        break;
    }

    case SPINEL_PROP_NET_IF_UP:
    {
        bool isUp;
        SuccessOrExit(error = SpinelDataUnpack(aBuffer, aLength, SPINEL_DATATYPE_BOOL_S, &isUp));
        SafeInvoke(mNetifStateChangedCallback, isUp);
        break;
    }

    default:
        otbrLogWarning("Received uncognized key: %u", aKey);
        break;
    }

exit:
    otbrLogResult(error, "NcpSpinel: %s", __FUNCTION__);
    return;
}

otbrError NcpSpinel::HandleResponseForPropSet(spinel_tid_t      aTid,
                                              spinel_prop_key_t aKey,
                                              const uint8_t    *aData,
                                              uint16_t          aLength)
{
    OTBR_UNUSED_VARIABLE(aData);
    OTBR_UNUSED_VARIABLE(aLength);

    otbrError error = OTBR_ERROR_NONE;

    switch (mWaitingKeyTable[aTid])
    {
    case SPINEL_PROP_THREAD_ACTIVE_DATASET_TLVS:
        VerifyOrExit(aKey == SPINEL_PROP_THREAD_ACTIVE_DATASET_TLVS, error = OTBR_ERROR_INVALID_STATE);
        CallAndClear(mDatasetSetActiveTask, OT_ERROR_NONE);
        {
            otOperationalDatasetTlvs datasetTlvs;
            VerifyOrExit(ParseOperationalDatasetTlvs(aData, aLength, datasetTlvs) == OT_ERROR_NONE,
                         error = OTBR_ERROR_PARSE);
            mPropsObserver->SetDatasetActiveTlvs(datasetTlvs);
        }
        break;

    case SPINEL_PROP_NET_IF_UP:
        VerifyOrExit(aKey == SPINEL_PROP_NET_IF_UP, error = OTBR_ERROR_INVALID_STATE);
        CallAndClear(mIp6SetEnabledTask, OT_ERROR_NONE);
        {
            bool isUp;
            SuccessOrExit(error = SpinelDataUnpack(aData, aLength, SPINEL_DATATYPE_BOOL_S, &isUp));
            SafeInvoke(mNetifStateChangedCallback, isUp);
        }
        break;

    case SPINEL_PROP_NET_STACK_UP:
        VerifyOrExit(aKey == SPINEL_PROP_NET_STACK_UP, error = OTBR_ERROR_INVALID_STATE);
        CallAndClear(mThreadSetEnabledTask, OT_ERROR_NONE);
        break;

    case SPINEL_PROP_THREAD_MGMT_SET_PENDING_DATASET_TLVS:
        if (aKey == SPINEL_PROP_LAST_STATUS)
        { // Failed case
            spinel_status_t status = SPINEL_STATUS_OK;

            SuccessOrExit(error = SpinelDataUnpack(aData, aLength, SPINEL_DATATYPE_UINT_PACKED_S, &status));
            CallAndClear(mDatasetMgmtSetPendingTask, ot::Spinel::SpinelStatusToOtError(status));
        }
        else if (aKey != SPINEL_PROP_THREAD_MGMT_SET_PENDING_DATASET_TLVS)
        {
            ExitNow(error = OTBR_ERROR_INVALID_STATE);
        }
        break;

    default:
        VerifyOrExit(aKey == mWaitingKeyTable[aTid], error = OTBR_ERROR_INVALID_STATE);
        break;
    }

exit:
    return error;
}

spinel_tid_t NcpSpinel::GetNextTid(void)
{
    spinel_tid_t tid = mCmdNextTid;

    while (((1 << tid) & mCmdTidsInUse) != 0)
    {
        tid = SPINEL_GET_NEXT_TID(tid);

        if (tid == mCmdNextTid)
        {
            // We looped back to `mCmdNextTid` indicating that all
            // TIDs are in-use.

            ExitNow(tid = 0);
        }
    }

    mCmdTidsInUse |= (1 << tid);
    mCmdNextTid = SPINEL_GET_NEXT_TID(tid);

exit:
    return tid;
}

void NcpSpinel::FreeTidTableItem(spinel_tid_t aTid)
{
    mCmdTidsInUse &= ~(1 << aTid);

    mCmdTable[aTid]        = SPINEL_CMD_NOOP;
    mWaitingKeyTable[aTid] = SPINEL_PROP_LAST_STATUS;
}

otError NcpSpinel::SetProperty(spinel_prop_key_t aKey, const EncodingFunc &aEncodingFunc)
{
    otError      error  = OT_ERROR_NONE;
    spinel_tid_t tid    = GetNextTid();
    uint8_t      header = SPINEL_HEADER_FLAG | SPINEL_HEADER_IID(mIid) | tid;

    VerifyOrExit(tid != 0, error = OT_ERROR_BUSY);
    SuccessOrExit(error = mEncoder.BeginFrame(header, SPINEL_CMD_PROP_VALUE_SET, aKey));
    SuccessOrExit(error = aEncodingFunc());
    SuccessOrExit(error = mEncoder.EndFrame());
    SuccessOrExit(error = SendEncodedFrame());

    mCmdTable[tid]        = SPINEL_CMD_PROP_VALUE_SET;
    mWaitingKeyTable[tid] = aKey;
exit:
    if (error != OT_ERROR_NONE)
    {
        FreeTidTableItem(tid);
    }
    return error;
}

otError NcpSpinel::SendEncodedFrame(void)
{
    otError  error = OT_ERROR_NONE;
    uint8_t  frame[kTxBufferSize];
    uint16_t frameLength;

    SuccessOrExit(error = mNcpBuffer.OutFrameBegin());
    frameLength = mNcpBuffer.OutFrameGetLength();
    VerifyOrExit(mNcpBuffer.OutFrameRead(frameLength, frame) == frameLength, error = OT_ERROR_FAILED);
    SuccessOrExit(error = mSpinelDriver->GetSpinelInterface()->SendFrame(frame, frameLength));

exit:
    error = mNcpBuffer.OutFrameRemove();
    return error;
}

otError NcpSpinel::ParseIp6AddressTable(const uint8_t               *aBuf,
                                        uint16_t                     aLength,
                                        std::vector<Ip6AddressInfo> &aAddressTable)
{
    otError             error = OT_ERROR_NONE;
    ot::Spinel::Decoder decoder;

    VerifyOrExit(aBuf != nullptr, error = OT_ERROR_INVALID_ARGS);
    decoder.Init(aBuf, aLength);

    while (!decoder.IsAllReadInStruct())
    {
        Ip6AddressInfo      cur;
        const otIp6Address *addr;
        uint8_t             prefixLength;
        uint32_t            preferredLifetime;
        uint32_t            validLifetime;

        SuccessOrExit(error = decoder.OpenStruct());
        SuccessOrExit(error = decoder.ReadIp6Address(addr));
        memcpy(&cur.mAddress, addr, sizeof(otIp6Address));
        SuccessOrExit(error = decoder.ReadUint8(prefixLength));
        cur.mPrefixLength = prefixLength;
        SuccessOrExit(error = decoder.ReadUint32(preferredLifetime));
        cur.mPreferred = preferredLifetime ? true : false;
        SuccessOrExit(error = decoder.ReadUint32(validLifetime));
        OTBR_UNUSED_VARIABLE(validLifetime);
        SuccessOrExit((error = decoder.CloseStruct()));

        aAddressTable.push_back(cur);
    }

exit:
    return error;
}

otError NcpSpinel::ParseIp6MulticastAddresses(const uint8_t *aBuf, uint8_t aLen, std::vector<Ip6Address> &aAddressList)
{
    otError             error = OT_ERROR_NONE;
    ot::Spinel::Decoder decoder;

    VerifyOrExit(aBuf != nullptr, error = OT_ERROR_INVALID_ARGS);

    decoder.Init(aBuf, aLen);

    while (!decoder.IsAllReadInStruct())
    {
        const otIp6Address *addr;

        SuccessOrExit(error = decoder.OpenStruct());
        SuccessOrExit(error = decoder.ReadIp6Address(addr));
        aAddressList.emplace_back(Ip6Address(*addr));
        SuccessOrExit((error = decoder.CloseStruct()));
    }

exit:
    return error;
}

otError NcpSpinel::ParseIp6StreamNet(const uint8_t *aBuf, uint8_t aLen, const uint8_t *&aData, uint16_t &aDataLen)
{
    otError             error = OT_ERROR_NONE;
    ot::Spinel::Decoder decoder;

    VerifyOrExit(aBuf != nullptr, error = OT_ERROR_INVALID_ARGS);

    decoder.Init(aBuf, aLen);
    error = decoder.ReadDataWithLen(aData, aDataLen);

exit:
    return error;
}

otError NcpSpinel::ParseOperationalDatasetTlvs(const uint8_t            *aBuf,
                                               uint8_t                   aLen,
                                               otOperationalDatasetTlvs &aDatasetTlvs)
{
    otError             error = OT_ERROR_NONE;
    ot::Spinel::Decoder decoder;
    const uint8_t      *datasetTlvsData;
    uint16_t            datasetTlvsLen;

    decoder.Init(aBuf, aLen);
    SuccessOrExit(error = decoder.ReadData(datasetTlvsData, datasetTlvsLen));
    VerifyOrExit(datasetTlvsLen <= sizeof(aDatasetTlvs.mTlvs), error = OT_ERROR_PARSE);

    memcpy(aDatasetTlvs.mTlvs, datasetTlvsData, datasetTlvsLen);
    aDatasetTlvs.mLength = datasetTlvsLen;

exit:
    return error;
}

otDeviceRole NcpSpinel::SpinelRoleToDeviceRole(spinel_net_role_t aRole)
{
    otDeviceRole role = OT_DEVICE_ROLE_DISABLED;

    switch (aRole)
    {
    case SPINEL_NET_ROLE_DISABLED:
        role = OT_DEVICE_ROLE_DISABLED;
        break;
    case SPINEL_NET_ROLE_DETACHED:
        role = OT_DEVICE_ROLE_DETACHED;
        break;
    case SPINEL_NET_ROLE_CHILD:
        role = OT_DEVICE_ROLE_CHILD;
        break;
    case SPINEL_NET_ROLE_ROUTER:
        role = OT_DEVICE_ROLE_ROUTER;
        break;
    case SPINEL_NET_ROLE_LEADER:
        role = OT_DEVICE_ROLE_LEADER;
        break;
    default:
        otbrLogWarning("Unsupported spinel net role: %u", aRole);
        break;
    }

    return role;
}

} // namespace Ncp
} // namespace otbr
