/*
 *    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 full thread device specified Spinel interface to the OpenThread stack.
 */

#include "ncp_base.hpp"
#include <openthread/config.h>

#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
#include <openthread/backbone_router_ftd.h>
#endif
#if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE
#include <openthread/border_agent.h>
#endif
#if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE
#include <openthread/channel_manager.h>
#endif
#include <openthread/child_supervision.h>
#include <openthread/dataset.h>
#include <openthread/dataset_ftd.h>
#include <openthread/diag.h>
#include <openthread/icmp6.h>
#include <openthread/ncp.h>
#include <openthread/thread_ftd.h>
#include <openthread/platform/misc.h>

#include "common/code_utils.hpp"
#include "common/debug.hpp"
#include "instance/instance.hpp"
#if OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
#include "meshcop/commissioner.hpp"
#endif

#if OPENTHREAD_FTD
namespace ot {
namespace Ncp {

otError NcpBase::EncodeChildInfo(const otChildInfo &aChildInfo)
{
    otError error = OT_ERROR_NONE;
    uint8_t modeFlags;

    modeFlags =
        LinkFlagsToFlagByte(aChildInfo.mRxOnWhenIdle, aChildInfo.mFullThreadDevice, aChildInfo.mFullNetworkData);

    SuccessOrExit(error = mEncoder.WriteEui64(aChildInfo.mExtAddress));
    SuccessOrExit(error = mEncoder.WriteUint16(aChildInfo.mRloc16));
    SuccessOrExit(error = mEncoder.WriteUint32(aChildInfo.mTimeout));
    SuccessOrExit(error = mEncoder.WriteUint32(aChildInfo.mAge));
    SuccessOrExit(error = mEncoder.WriteUint8(aChildInfo.mNetworkDataVersion));
    SuccessOrExit(error = mEncoder.WriteUint8(aChildInfo.mLinkQualityIn));
    SuccessOrExit(error = mEncoder.WriteInt8(aChildInfo.mAverageRssi));
    SuccessOrExit(error = mEncoder.WriteUint8(modeFlags));
    SuccessOrExit(error = mEncoder.WriteInt8(aChildInfo.mLastRssi));

exit:
    return error;
}

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

#if OPENTHREAD_CONFIG_MLE_PARENT_RESPONSE_CALLBACK_API_ENABLE
void NcpBase::HandleParentResponseInfo(otThreadParentResponseInfo *aInfo, void *aContext)
{
    VerifyOrExit(aInfo && aContext);

    static_cast<NcpBase *>(aContext)->HandleParentResponseInfo(*aInfo);

exit:
    return;
}

void NcpBase::HandleParentResponseInfo(const otThreadParentResponseInfo &aInfo)
{
    VerifyOrExit(!mChangedPropsSet.IsPropertyFiltered(SPINEL_PROP_PARENT_RESPONSE_INFO));

    SuccessOrExit(mEncoder.BeginFrame(SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, SPINEL_CMD_PROP_VALUE_IS,
                                      SPINEL_PROP_PARENT_RESPONSE_INFO));

    SuccessOrExit(mEncoder.WriteEui64(aInfo.mExtAddr));
    SuccessOrExit(mEncoder.WriteUint16(aInfo.mRloc16));
    SuccessOrExit(mEncoder.WriteInt8(aInfo.mRssi));
    SuccessOrExit(mEncoder.WriteInt8(aInfo.mPriority));
    SuccessOrExit(mEncoder.WriteUint8(aInfo.mLinkQuality3));
    SuccessOrExit(mEncoder.WriteUint8(aInfo.mLinkQuality2));
    SuccessOrExit(mEncoder.WriteUint8(aInfo.mLinkQuality1));
    SuccessOrExit(mEncoder.WriteBool(aInfo.mIsAttached));

    SuccessOrExit(mEncoder.EndFrame());

exit:
    return;
}
#endif // OPENTHREAD_CONFIG_MLE_PARENT_RESPONSE_CALLBACK_API_ENABLE

void NcpBase::HandleNeighborTableChanged(otNeighborTableEvent aEvent, const otNeighborTableEntryInfo *aEntry)
{
    GetNcpInstance()->HandleNeighborTableChanged(aEvent, *aEntry);
}

void NcpBase::HandleNeighborTableChanged(otNeighborTableEvent aEvent, const otNeighborTableEntryInfo &aEntry)
{
    otError           error   = OT_ERROR_NONE;
    unsigned int      command = SPINEL_CMD_PROP_VALUE_REMOVED;
    spinel_prop_key_t property;

    switch (aEvent)
    {
    case OT_NEIGHBOR_TABLE_EVENT_CHILD_ADDED:
        command = SPINEL_CMD_PROP_VALUE_INSERTED;

        OT_FALL_THROUGH;

    case OT_NEIGHBOR_TABLE_EVENT_CHILD_REMOVED:
        property = SPINEL_PROP_THREAD_CHILD_TABLE;
        VerifyOrExit(!aEntry.mInfo.mChild.mIsStateRestoring);
        break;

    case OT_NEIGHBOR_TABLE_EVENT_ROUTER_ADDED:
        command = SPINEL_CMD_PROP_VALUE_INSERTED;

        OT_FALL_THROUGH;

    case OT_NEIGHBOR_TABLE_EVENT_ROUTER_REMOVED:
        property = SPINEL_PROP_THREAD_NEIGHBOR_TABLE;
        break;

    default:
        ExitNow();
    }

    VerifyOrExit(!mChangedPropsSet.IsPropertyFiltered(property));

    SuccessOrExit(error = mEncoder.BeginFrame(SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, command, property));

    if (property == SPINEL_PROP_THREAD_CHILD_TABLE)
    {
        SuccessOrExit(error = EncodeChildInfo(aEntry.mInfo.mChild));
    }
    else
    {
        SuccessOrExit(error = EncodeNeighborInfo(aEntry.mInfo.mRouter));
    }

    SuccessOrExit(error = mEncoder.EndFrame());

exit:

    // If the frame can not be added (out of NCP buffer space), we remember
    // to send an async `LAST_STATUS(NOMEM)` when buffer space becomes
    // available. Also `mShouldEmitChildTableUpdate` flag is set to `true` so
    // that the entire child table is later emitted as `VALUE_IS` spinel frame
    // update from `ProcessThreadChangedFlags()`.

    if (error != OT_ERROR_NONE)
    {
        if (property == SPINEL_PROP_THREAD_CHILD_TABLE)
        {
            mShouldEmitChildTableUpdate = true;
        }

        mChangedPropsSet.AddLastStatus(SPINEL_STATUS_NOMEM);
        mUpdateChangedPropsTask.Post();
    }
}

#if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE

void NcpBase::HandleBorderAgentMeshCoPServiceChanged(void *aContext)
{
    static_cast<NcpBase *>(aContext)->HandleBorderAgentMeshCoPServiceChanged();
}

void NcpBase::HandleBorderAgentMeshCoPServiceChanged(void)
{
    mChangedPropsSet.AddProperty(SPINEL_PROP_BORDER_AGENT_MESHCOP_SERVICE_STATE);
    mUpdateChangedPropsTask.Post();
}

#endif // OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE

#if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE
void NcpBase::HandleBorderAgentEphemeralKeyStateChanged(void *aContext)
{
    static_cast<NcpBase *>(aContext)->HandleBorderAgentEphemeralKeyStateChanged();
}

void NcpBase::HandleBorderAgentEphemeralKeyStateChanged(void)
{
    mChangedPropsSet.AddProperty(SPINEL_PROP_BORDER_AGENT_EPHEMERAL_KEY_STATE);
    mUpdateChangedPropsTask.Post();
}
#endif

// ----------------------------------------------------------------------------
// MARK: Individual Property Handlers
// ----------------------------------------------------------------------------

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

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

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_THREAD_CHILD_TABLE>(void)
{
    otError     error = OT_ERROR_NONE;
    otChildInfo childInfo;
    uint16_t    maxChildren;

    maxChildren = otThreadGetMaxAllowedChildren(mInstance);

    for (uint16_t index = 0; index < maxChildren; index++)
    {
        if ((otThreadGetChildInfoByIndex(mInstance, index, &childInfo) != OT_ERROR_NONE) || childInfo.mIsStateRestoring)
        {
            continue;
        }

        SuccessOrExit(error = mEncoder.OpenStruct());
        SuccessOrExit(error = EncodeChildInfo(childInfo));
        SuccessOrExit(error = mEncoder.CloseStruct());
    }

exit:
    return error;
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_THREAD_ROUTER_TABLE>(void)
{
    otError      error = OT_ERROR_NONE;
    otRouterInfo routerInfo;
    uint8_t      maxRouterId;

    maxRouterId = otThreadGetMaxRouterId(mInstance);

    for (uint8_t routerId = 0; routerId <= maxRouterId; routerId++)
    {
        if ((otThreadGetRouterInfo(mInstance, routerId, &routerInfo) != OT_ERROR_NONE) || !routerInfo.mAllocated)
        {
            continue;
        }

        SuccessOrExit(error = mEncoder.OpenStruct());

        SuccessOrExit(error = mEncoder.WriteEui64(routerInfo.mExtAddress));
        SuccessOrExit(error = mEncoder.WriteUint16(routerInfo.mRloc16));
        SuccessOrExit(error = mEncoder.WriteUint8(routerInfo.mRouterId));
        SuccessOrExit(error = mEncoder.WriteUint8(routerInfo.mNextHop));
        SuccessOrExit(error = mEncoder.WriteUint8(routerInfo.mPathCost));
        SuccessOrExit(error = mEncoder.WriteUint8(routerInfo.mLinkQualityIn));
        SuccessOrExit(error = mEncoder.WriteUint8(routerInfo.mLinkQualityOut));
        SuccessOrExit(error = mEncoder.WriteUint8(routerInfo.mAge));
        SuccessOrExit(error = mEncoder.WriteBool(routerInfo.mLinkEstablished));

        SuccessOrExit(error = mEncoder.CloseStruct());
    }

exit:
    return error;
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_THREAD_CHILD_TABLE_ADDRESSES>(void)
{
    otError                   error = OT_ERROR_NONE;
    otChildInfo               childInfo;
    uint16_t                  maxChildren;
    otIp6Address              ip6Address;
    otChildIp6AddressIterator iterator = OT_CHILD_IP6_ADDRESS_ITERATOR_INIT;

    maxChildren = otThreadGetMaxAllowedChildren(mInstance);

    for (uint16_t childIndex = 0; childIndex < maxChildren; childIndex++)
    {
        if ((otThreadGetChildInfoByIndex(mInstance, childIndex, &childInfo) != OT_ERROR_NONE) ||
            childInfo.mIsStateRestoring)
        {
            continue;
        }

        SuccessOrExit(error = mEncoder.OpenStruct());

        SuccessOrExit(error = mEncoder.WriteEui64(childInfo.mExtAddress));
        SuccessOrExit(error = mEncoder.WriteUint16(childInfo.mRloc16));

        iterator = OT_CHILD_IP6_ADDRESS_ITERATOR_INIT;

        while (otThreadGetChildNextIp6Address(mInstance, childIndex, &iterator, &ip6Address) == OT_ERROR_NONE)
        {
            SuccessOrExit(error = mEncoder.WriteIp6Address(ip6Address));
        }

        SuccessOrExit(error = mEncoder.CloseStruct());
    }

exit:
    return error;
}

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

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_THREAD_ROUTER_ROLE_ENABLED>(void)
{
    bool    eligible;
    otError error = OT_ERROR_NONE;

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

    error = otThreadSetRouterEligible(mInstance, eligible);

exit:
    return error;
}

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

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MAC_MAX_RETRY_NUMBER_INDIRECT>(void)
{
    uint8_t maxFrameRetriesIndirect;
    otError error = OT_ERROR_NONE;

    SuccessOrExit(error = mDecoder.ReadUint8(maxFrameRetriesIndirect));
    otLinkSetMaxFrameRetriesIndirect(mInstance, maxFrameRetriesIndirect);

exit:
    return error;
}

#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_THREAD_DOMAIN_NAME>(void)
{
    return mEncoder.WriteUtf8(otThreadGetDomainName(mInstance));
}

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_THREAD_DOMAIN_NAME>(void)
{
    otError     error = OT_ERROR_NONE;
    const char *domainName;

    SuccessOrExit(error = mDecoder.ReadUtf8(domainName));

    error = otThreadSetDomainName(mInstance, domainName);

exit:
    return error;
}
#endif

#if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE

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

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

exit:
    return error;
}

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_BORDER_AGENT_EPHEMERAL_KEY_ACTIVATE>(void)
{
    otError     error = OT_ERROR_NONE;
    const char *epskc;
    uint32_t    durationMilli;
    uint16_t    port;

    SuccessOrExit(error = mDecoder.ReadUtf8(epskc));
    SuccessOrExit(error = mDecoder.ReadUint32(durationMilli));
    SuccessOrExit(error = mDecoder.ReadUint16(port));

    VerifyOrExit(otBorderAgentEphemeralKeyGetState(mInstance) != OT_BORDER_AGENT_STATE_DISABLED,
                 error = OT_ERROR_NOT_CAPABLE);
    error = otBorderAgentEphemeralKeyStart(mInstance, epskc, durationMilli, port);

exit:
    return error;
}

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_BORDER_AGENT_EPHEMERAL_KEY_DEACTIVATE>(void)
{
    otError error = OT_ERROR_NONE;

    bool retainActiveSession;

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

    switch (otBorderAgentEphemeralKeyGetState(mInstance))
    {
    case OT_BORDER_AGENT_STATE_STARTED:
        break;
    case OT_BORDER_AGENT_STATE_CONNECTED:
    case OT_BORDER_AGENT_STATE_ACCEPTED:
        VerifyOrExit(!retainActiveSession);
        break;
    case OT_BORDER_AGENT_STATE_DISABLED:
        error = OT_ERROR_NOT_CAPABLE;
        OT_FALL_THROUGH;
    case OT_BORDER_AGENT_STATE_STOPPED:
        ExitNow();
    }

    otBorderAgentEphemeralKeyStop(mInstance);

exit:
    return error;
}

#endif // OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE

#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_THREAD_BACKBONE_ROUTER_LOCAL_STATE>(void)
{
    uint8_t state = SPINEL_THREAD_BACKBONE_ROUTER_STATE_DISABLED;

    switch (otBackboneRouterGetState(mInstance))
    {
    case OT_BACKBONE_ROUTER_STATE_DISABLED:
        state = SPINEL_THREAD_BACKBONE_ROUTER_STATE_DISABLED;
        break;

    case OT_BACKBONE_ROUTER_STATE_SECONDARY:
        state = SPINEL_THREAD_BACKBONE_ROUTER_STATE_SECONDARY;
        break;

    case OT_BACKBONE_ROUTER_STATE_PRIMARY:
        state = SPINEL_THREAD_BACKBONE_ROUTER_STATE_PRIMARY;
        break;
    }

    return mEncoder.WriteUint8(state);
}

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

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

    if (state)
    {
        otBackboneRouterSetEnabled(mInstance, true);
    }
    else
    {
        otBackboneRouterSetEnabled(mInstance, false);
    }

exit:
    return error;
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_THREAD_BACKBONE_ROUTER_LOCAL_CONFIG>(void)
{
    otError                error = OT_ERROR_NONE;
    otBackboneRouterConfig bbrConfig;

    otBackboneRouterGetConfig(mInstance, &bbrConfig);

    SuccessOrExit(error = mEncoder.WriteUint16(bbrConfig.mReregistrationDelay));
    SuccessOrExit(error = mEncoder.WriteUint32(bbrConfig.mMlrTimeout));
    SuccessOrExit(error = mEncoder.WriteUint8(bbrConfig.mSequenceNumber));

exit:
    return error;
}

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_THREAD_BACKBONE_ROUTER_LOCAL_CONFIG>(void)
{
    otError                error = OT_ERROR_NONE;
    otBackboneRouterConfig bbrConfig;

    SuccessOrExit(error = mDecoder.ReadUint16(bbrConfig.mReregistrationDelay));
    SuccessOrExit(error = mDecoder.ReadUint32(bbrConfig.mMlrTimeout));
    SuccessOrExit(error = mDecoder.ReadUint8(bbrConfig.mSequenceNumber));

    SuccessOrExit(error = otBackboneRouterSetConfig(mInstance, &bbrConfig));

exit:
    return error;
}

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_THREAD_BACKBONE_ROUTER_LOCAL_REGISTER>(void)
{
    return otBackboneRouterRegister(mInstance);
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_THREAD_BACKBONE_ROUTER_LOCAL_REGISTRATION_JITTER>(void)
{
    uint8_t jitter = otBackboneRouterGetRegistrationJitter(mInstance);

    return mEncoder.WriteUint8(jitter);
}

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_THREAD_BACKBONE_ROUTER_LOCAL_REGISTRATION_JITTER>(void)
{
    otError error = OT_ERROR_NONE;
    uint8_t jitter;

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

    otBackboneRouterSetRegistrationJitter(mInstance, jitter);

exit:
    return error;
}
#endif // OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_NET_PSKC>(void)
{
    Pskc pskc;

    otThreadGetPskc(mInstance, &pskc);

    return mEncoder.WriteData(pskc.m8, sizeof(spinel_net_pskc_t));
}

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_NET_PSKC>(void)
{
    const uint8_t *ptr = nullptr;
    uint16_t       len;
    otError        error = OT_ERROR_NONE;

    SuccessOrExit(error = mDecoder.ReadData(ptr, len));

    VerifyOrExit(len == sizeof(spinel_net_pskc_t), error = OT_ERROR_PARSE);

    error = otThreadSetPskc(mInstance, reinterpret_cast<const otPskc *>(ptr));

exit:
    return error;
}

#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_NET_PARTITION_ID>(void)
{
    uint32_t partitionId = 0;
    otError  error       = OT_ERROR_NONE;

    SuccessOrExit(error = mDecoder.ReadUint32(partitionId));

    otThreadSetPreferredLeaderPartitionId(mInstance, partitionId);

exit:
    return error;
}
#endif

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_THREAD_CHILD_COUNT_MAX>(void)
{
    return mEncoder.WriteUint8(static_cast<uint8_t>(otThreadGetMaxAllowedChildren(mInstance)));
}

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

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

    error = otThreadSetMaxAllowedChildren(mInstance, maxChildren);

exit:
    return error;
}

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

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

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

    otThreadSetRouterUpgradeThreshold(mInstance, threshold);

exit:
    return error;
}

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

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

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

    otThreadSetRouterDowngradeThreshold(mInstance, threshold);

exit:
    return error;
}

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

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

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

    otThreadSetRouterSelectionJitter(mInstance, jitter);

exit:
    return error;
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_THREAD_CONTEXT_REUSE_DELAY>(void)
{
    return mEncoder.WriteUint32(otThreadGetContextIdReuseDelay(mInstance));
}

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

    SuccessOrExit(error = mDecoder.ReadUint32(delay));

    otThreadSetContextIdReuseDelay(mInstance, delay);

exit:
    return error;
}

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

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

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

    otThreadSetNetworkIdTimeout(mInstance, timeout);

exit:
    return error;
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_THREAD_NEW_DATASET>(void)
{
    otError              error;
    otOperationalDataset dataset;

    error = otDatasetCreateNewNetwork(mInstance, &dataset);

    if (error == OT_ERROR_NONE)
    {
        error = EncodeOperationalDataset(dataset);
    }
    else
    {
        error = mEncoder.OverwriteWithLastStatusError(ThreadErrorToSpinelStatus(error));
    }

    return error;
}

#if OPENTHREAD_CONFIG_COMMISSIONER_ENABLE

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_MESHCOP_COMMISSIONER_STATE>(void)
{
    uint8_t state = SPINEL_MESHCOP_COMMISSIONER_STATE_DISABLED;

    switch (otCommissionerGetState(mInstance))
    {
    case OT_COMMISSIONER_STATE_DISABLED:
        state = SPINEL_MESHCOP_COMMISSIONER_STATE_DISABLED;
        break;

    case OT_COMMISSIONER_STATE_PETITION:
        state = SPINEL_MESHCOP_COMMISSIONER_STATE_PETITION;
        break;

    case OT_COMMISSIONER_STATE_ACTIVE:
        state = SPINEL_MESHCOP_COMMISSIONER_STATE_ACTIVE;
        break;
    }

    return mEncoder.WriteUint8(state);
}

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

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

    switch (state)
    {
    case SPINEL_MESHCOP_COMMISSIONER_STATE_DISABLED:
        error = otCommissionerStop(mInstance);
        break;

    case SPINEL_MESHCOP_COMMISSIONER_STATE_ACTIVE:
        error = otCommissionerStart(mInstance, nullptr, nullptr, nullptr);
        break;

    default:
        error = OT_ERROR_INVALID_ARGS;
        break;
    }

exit:
    return error;
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_MESHCOP_COMMISSIONER_JOINERS>(void)
{
    otError      error = OT_ERROR_NONE;
    uint16_t     iter  = 0;
    otJoinerInfo joinerInfo;

    while (otCommissionerGetNextJoinerInfo(mInstance, &iter, &joinerInfo) == OT_ERROR_NONE)
    {
        SuccessOrExit(error = mEncoder.OpenStruct());

        SuccessOrExit(error = mEncoder.OpenStruct()); // Joiner Id (any, EUI64 or a Joiner Discerner) struct

        switch (joinerInfo.mType)
        {
        case OT_JOINER_INFO_TYPE_ANY:
            break;

        case OT_JOINER_INFO_TYPE_EUI64:
            SuccessOrExit(error = mEncoder.WriteEui64(joinerInfo.mSharedId.mEui64));
            break;

        case OT_JOINER_INFO_TYPE_DISCERNER:
            SuccessOrExit(error = mEncoder.WriteUint8(joinerInfo.mSharedId.mDiscerner.mLength));
            SuccessOrExit(error = mEncoder.WriteUint64(joinerInfo.mSharedId.mDiscerner.mValue));
            break;
        }

        SuccessOrExit(error = mEncoder.CloseStruct());

        SuccessOrExit(error = mEncoder.WriteUint32(joinerInfo.mExpirationTime));
        SuccessOrExit(error = mEncoder.WriteUtf8(joinerInfo.mPskd.m8));

        SuccessOrExit(error = mEncoder.CloseStruct());
    }

exit:
    return error;
}

template <> otError NcpBase::HandlePropertyInsert<SPINEL_PROP_MESHCOP_COMMISSIONER_JOINERS>(void)
{
    otError             error = OT_ERROR_NONE;
    otJoinerDiscerner   discerner;
    bool                withDiscerner = false;
    const otExtAddress *eui64;
    uint32_t            timeout;
    const char         *psk;

    SuccessOrExit(error = mDecoder.OpenStruct());

    switch (mDecoder.GetRemainingLengthInStruct())
    {
    case 0:
        // Empty struct indicates any joiner
        eui64 = nullptr;
        break;

    case sizeof(spinel_eui64_t):
        SuccessOrExit(error = mDecoder.ReadEui64(eui64));
        break;

    default:
        SuccessOrExit(error = mDecoder.ReadUint8(discerner.mLength));
        SuccessOrExit(error = mDecoder.ReadUint64(discerner.mValue));
        withDiscerner = true;
        break;
    }

    SuccessOrExit(error = mDecoder.CloseStruct());

    SuccessOrExit(error = mDecoder.ReadUint32(timeout));
    SuccessOrExit(error = mDecoder.ReadUtf8(psk));

    if (withDiscerner)
    {
        error = otCommissionerAddJoinerWithDiscerner(mInstance, &discerner, psk, timeout);
    }
    else
    {
        error = otCommissionerAddJoiner(mInstance, eui64, psk, timeout);
    }

exit:
    return error;
}

template <> otError NcpBase::HandlePropertyRemove<SPINEL_PROP_MESHCOP_COMMISSIONER_JOINERS>(void)
{
    otError             error = OT_ERROR_NONE;
    otJoinerDiscerner   discerner;
    bool                withDiscerner = false;
    const otExtAddress *eui64;

    SuccessOrExit(error = mDecoder.OpenStruct());

    switch (mDecoder.GetRemainingLengthInStruct())
    {
    case 0:
        // Empty struct indicates any joiner
        eui64 = nullptr;
        break;

    case sizeof(spinel_eui64_t):
        SuccessOrExit(error = mDecoder.ReadEui64(eui64));
        break;

    default:
        SuccessOrExit(error = mDecoder.ReadUint8(discerner.mLength));
        SuccessOrExit(error = mDecoder.ReadUint64(discerner.mValue));
        withDiscerner = true;
        break;
    }

    SuccessOrExit(error = mDecoder.CloseStruct());

    if (withDiscerner)
    {
        error = otCommissionerRemoveJoinerWithDiscerner(mInstance, &discerner);
    }
    else
    {
        error = otCommissionerRemoveJoiner(mInstance, eui64);
    }

exit:
    return error;
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_MESHCOP_COMMISSIONER_PROVISIONING_URL>(void)
{
    return mEncoder.WriteUtf8(otCommissionerGetProvisioningUrl(mInstance));
}

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MESHCOP_COMMISSIONER_PROVISIONING_URL>(void)
{
    otError     error = OT_ERROR_NONE;
    const char *url;

    SuccessOrExit(error = mDecoder.ReadUtf8(url));

    error = otCommissionerSetProvisioningUrl(mInstance, url);

exit:
    return error;
}

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

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MESHCOP_COMMISSIONER_ANNOUNCE_BEGIN>(void)
{
    otError             error = OT_ERROR_NONE;
    uint32_t            channelMask;
    uint8_t             count;
    uint16_t            period;
    const otIp6Address *address;

    SuccessOrExit(error = mDecoder.ReadUint32(channelMask));
    SuccessOrExit(error = mDecoder.ReadUint8(count));
    SuccessOrExit(error = mDecoder.ReadUint16(period));
    SuccessOrExit(error = mDecoder.ReadIp6Address(address));

    error = otCommissionerAnnounceBegin(mInstance, channelMask, count, period, address);

exit:
    return error;
}

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MESHCOP_COMMISSIONER_ENERGY_SCAN>(void)
{
    otError             error = OT_ERROR_NONE;
    uint32_t            channelMask;
    uint8_t             count;
    uint16_t            period;
    uint16_t            scanDuration;
    const otIp6Address *address;

    SuccessOrExit(error = mDecoder.ReadUint32(channelMask));
    SuccessOrExit(error = mDecoder.ReadUint8(count));
    SuccessOrExit(error = mDecoder.ReadUint16(period));
    SuccessOrExit(error = mDecoder.ReadUint16(scanDuration));
    SuccessOrExit(error = mDecoder.ReadIp6Address(address));

    error = otCommissionerEnergyScan(mInstance, channelMask, count, period, scanDuration, address,
                                     &NcpBase::HandleCommissionerEnergyReport_Jump, this);

exit:
    return error;
}

void NcpBase::HandleCommissionerEnergyReport_Jump(uint32_t       aChannelMask,
                                                  const uint8_t *aEnergyData,
                                                  uint8_t        aLength,
                                                  void          *aContext)
{
    static_cast<NcpBase *>(aContext)->HandleCommissionerEnergyReport(aChannelMask, aEnergyData, aLength);
}

void NcpBase::HandleCommissionerEnergyReport(uint32_t aChannelMask, const uint8_t *aEnergyData, uint8_t aLength)
{
    otError error = OT_ERROR_NONE;

    SuccessOrExit(error = mEncoder.BeginFrame(SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, SPINEL_CMD_PROP_VALUE_INSERTED,
                                              SPINEL_PROP_MESHCOP_COMMISSIONER_ENERGY_SCAN_RESULT));
    SuccessOrExit(error = mEncoder.WriteUint32(aChannelMask));
    SuccessOrExit(error = mEncoder.WriteDataWithLen(aEnergyData, aLength));
    SuccessOrExit(error = mEncoder.EndFrame());

exit:

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

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MESHCOP_COMMISSIONER_PAN_ID_QUERY>(void)
{
    otError             error = OT_ERROR_NONE;
    uint16_t            panId;
    uint32_t            channelMask;
    const otIp6Address *address;

    SuccessOrExit(error = mDecoder.ReadUint16(panId));
    SuccessOrExit(error = mDecoder.ReadUint32(channelMask));
    SuccessOrExit(error = mDecoder.ReadIp6Address(address));

    error = otCommissionerPanIdQuery(mInstance, panId, channelMask, address,
                                     &NcpBase::HandleCommissionerPanIdConflict_Jump, this);

exit:
    return error;
}

void NcpBase::HandleCommissionerPanIdConflict_Jump(uint16_t aPanId, uint32_t aChannelMask, void *aContext)
{
    static_cast<NcpBase *>(aContext)->HandleCommissionerPanIdConflict(aPanId, aChannelMask);
}

void NcpBase::HandleCommissionerPanIdConflict(uint16_t aPanId, uint32_t aChannelMask)
{
    otError error = OT_ERROR_NONE;

    SuccessOrExit(error = mEncoder.BeginFrame(SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, SPINEL_CMD_PROP_VALUE_INSERTED,
                                              SPINEL_PROP_MESHCOP_COMMISSIONER_PAN_ID_CONFLICT_RESULT));

    SuccessOrExit(error = mEncoder.WriteUint16(aPanId));
    SuccessOrExit(error = mEncoder.WriteUint32(aChannelMask));
    SuccessOrExit(error = mEncoder.EndFrame());

exit:

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

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MESHCOP_COMMISSIONER_MGMT_GET>(void)
{
    otError        error = OT_ERROR_NONE;
    const uint8_t *tlvs;
    uint16_t       length;

    SuccessOrExit(error = mDecoder.ReadDataWithLen(tlvs, length));
    VerifyOrExit(length <= 255, error = OT_ERROR_INVALID_ARGS);

    error = otCommissionerSendMgmtGet(mInstance, tlvs, static_cast<uint8_t>(length));

exit:
    return error;
}

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MESHCOP_COMMISSIONER_MGMT_SET>(void)
{
    otError                error = OT_ERROR_NONE;
    const uint8_t         *tlvs;
    uint16_t               length;
    otCommissioningDataset dataset;

    SuccessOrExit(error = mDecoder.ReadDataWithLen(tlvs, length));
    VerifyOrExit(length <= 255, error = OT_ERROR_INVALID_ARGS);

    memset(&dataset, 0, sizeof(otCommissioningDataset));
    error = otCommissionerSendMgmtSet(mInstance, &dataset, tlvs, static_cast<uint8_t>(length));

exit:
    return error;
}

otError NcpBase::HandlePropertySet_SPINEL_PROP_MESHCOP_COMMISSIONER_GENERATE_PSKC(uint8_t aHeader)
{
    otError        error = OT_ERROR_NONE;
    const char    *passPhrase;
    const char    *networkName;
    const uint8_t *extPanIdData;
    uint16_t       length;
    otPskc         pskc;

    SuccessOrExit(error = mDecoder.ReadUtf8(passPhrase));
    SuccessOrExit(error = mDecoder.ReadUtf8(networkName));
    SuccessOrExit(error = mDecoder.ReadDataWithLen(extPanIdData, length));
    VerifyOrExit(length == sizeof(spinel_net_xpanid_t), error = OT_ERROR_PARSE);

    SuccessOrExit(error = otDatasetGeneratePskc(passPhrase, reinterpret_cast<const otNetworkName *>(networkName),
                                                reinterpret_cast<const otExtendedPanId *>(extPanIdData), &pskc));

    SuccessOrExit(
        error = mEncoder.BeginFrame(aHeader, SPINEL_CMD_PROP_VALUE_IS, SPINEL_PROP_MESHCOP_COMMISSIONER_GENERATE_PSKC));
    SuccessOrExit(error = mEncoder.WriteData(pskc.m8, sizeof(pskc)));
    SuccessOrExit(error = mEncoder.EndFrame());

exit:
    return error;
}

// SPINEL_PROP_THREAD_COMMISSIONER_ENABLED is replaced by SPINEL_PROP_MESHCOP_COMMISSIONER_STATE. Please use the new
// property. The old property/implementation remains for backward compatibility.

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_THREAD_COMMISSIONER_ENABLED>(void)
{
    return mEncoder.WriteBool(otCommissionerGetState(mInstance) == OT_COMMISSIONER_STATE_ACTIVE);
}

otError NcpBase::HandlePropertySet_SPINEL_PROP_THREAD_COMMISSIONER_ENABLED(uint8_t aHeader)
{
    bool    enabled = false;
    otError error   = OT_ERROR_NONE;

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

    if (!enabled)
    {
        error = otCommissionerStop(mInstance);
    }
    else
    {
        error = otCommissionerStart(mInstance, nullptr, nullptr, nullptr);
    }

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

// SPINEL_PROP_THREAD_JOINERS is replaced by SPINEL_PROP_MESHCOP_COMMISSIONER_JOINERS. Please us the new property.
// The old property/implementation remains for backward compatibility.

template <> otError NcpBase::HandlePropertyInsert<SPINEL_PROP_THREAD_JOINERS>(void)
{
    otError             error         = OT_ERROR_NONE;
    const otExtAddress *eui64         = nullptr;
    const char         *pskd          = nullptr;
    uint32_t            joinerTimeout = 0;

    SuccessOrExit(error = mDecoder.ReadUtf8(pskd));
    SuccessOrExit(error = mDecoder.ReadUint32(joinerTimeout));

    if (mDecoder.ReadEui64(eui64) != OT_ERROR_NONE)
    {
        eui64 = nullptr;
    }

    error = otCommissionerAddJoiner(mInstance, eui64, pskd, joinerTimeout);

exit:
    return error;
}
#endif // OPENTHREAD_CONFIG_COMMISSIONER_ENABLE

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_THREAD_LOCAL_LEADER_WEIGHT>(void)
{
    uint8_t weight;
    otError error = OT_ERROR_NONE;

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

    otThreadSetLocalLeaderWeight(mInstance, weight);

exit:
    return error;
}

#if OPENTHREAD_CONFIG_MLE_STEERING_DATA_SET_OOB_ENABLE

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_THREAD_STEERING_DATA>(void)
{
    return mEncoder.WriteEui64(mSteeringDataAddress);
}

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_THREAD_STEERING_DATA>(void)
{
    otError error = OT_ERROR_NONE;

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

    otThreadSetSteeringData(mInstance, &mSteeringDataAddress);

exit:
    return error;
}
#endif // #if OPENTHREAD_CONFIG_MLE_STEERING_DATA_SET_OOB_ENABLE

#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE

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

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_THREAD_PREFERRED_ROUTER_ID>(void)
{
    otError error = OT_ERROR_NONE;

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

    SuccessOrExit(error = otThreadSetPreferredRouterId(mInstance, mPreferredRouteId));

exit:
    return error;
}

#endif

template <> otError NcpBase::HandlePropertyRemove<SPINEL_PROP_THREAD_ACTIVE_ROUTER_IDS>(void)
{
    otError error = OT_ERROR_NONE;
    uint8_t routerId;

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

    error = otThreadReleaseRouterId(mInstance, routerId);

    // `INVALID_STATE` is returned when router ID was not allocated (i.e. not in the list)
    // in such a case, the "remove" operation can be considered successful.

    if (error == OT_ERROR_INVALID_STATE)
    {
        error = OT_ERROR_NONE;
    }

exit:
    return error;
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_THREAD_ADDRESS_CACHE_TABLE>(void)
{
    otError              error = OT_ERROR_NONE;
    otCacheEntryIterator iterator;
    otCacheEntryInfo     entry;

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

    for (uint8_t index = 0;; index++)
    {
        SuccessOrExit(otThreadGetNextCacheEntry(mInstance, &entry, &iterator));

        SuccessOrExit(error = mEncoder.OpenStruct());
        SuccessOrExit(error = mEncoder.WriteIp6Address(entry.mTarget));
        SuccessOrExit(error = mEncoder.WriteUint16(entry.mRloc16));
        SuccessOrExit(error = mEncoder.WriteUint8(index));

        switch (entry.mState)
        {
        case OT_CACHE_ENTRY_STATE_CACHED:
            SuccessOrExit(error = mEncoder.WriteUint8(SPINEL_ADDRESS_CACHE_ENTRY_STATE_CACHED));
            break;
        case OT_CACHE_ENTRY_STATE_SNOOPED:
            SuccessOrExit(error = mEncoder.WriteUint8(SPINEL_ADDRESS_CACHE_ENTRY_STATE_SNOOPED));
            break;
        case OT_CACHE_ENTRY_STATE_QUERY:
            SuccessOrExit(error = mEncoder.WriteUint8(SPINEL_ADDRESS_CACHE_ENTRY_STATE_QUERY));
            break;
        case OT_CACHE_ENTRY_STATE_RETRY_QUERY:
            SuccessOrExit(error = mEncoder.WriteUint8(SPINEL_ADDRESS_CACHE_ENTRY_STATE_RETRY_QUERY));
            break;
        }

        SuccessOrExit(error = mEncoder.OpenStruct());

        if (entry.mState == OT_CACHE_ENTRY_STATE_CACHED)
        {
            SuccessOrExit(error = mEncoder.WriteBool(entry.mValidLastTrans));
            SuccessOrExit(error = mEncoder.WriteUint32(entry.mLastTransTime));
            SuccessOrExit(error = mEncoder.WriteIp6Address(entry.mMeshLocalEid));
        }

        SuccessOrExit(error = mEncoder.CloseStruct());

        SuccessOrExit(error = mEncoder.OpenStruct());

        if (entry.mState != OT_CACHE_ENTRY_STATE_CACHED)
        {
            SuccessOrExit(error = mEncoder.WriteBool(entry.mCanEvict));
            SuccessOrExit(error = mEncoder.WriteUint16(entry.mRampDown ? 0 : entry.mTimeout));
            SuccessOrExit(error = mEncoder.WriteUint16(entry.mRetryDelay));
        }

        SuccessOrExit(error = mEncoder.CloseStruct());

        SuccessOrExit(error = mEncoder.CloseStruct());
    }

exit:
    return error;
}

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

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

    SuccessOrExit(error = mDecoder.ReadUint16(interval));
    otChildSupervisionSetInterval(mInstance, interval);

exit:
    return error;
}

#if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE

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

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

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

    otChannelManagerRequestChannelChange(mInstance, channel);

exit:
    return error;
}

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

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

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

    error = otChannelManagerSetDelay(mInstance, delay);

exit:
    return error;
}

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

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

    SuccessOrExit(error = DecodeChannelMask(channelMask));
    otChannelManagerSetSupportedChannels(mInstance, channelMask);

exit:
    return error;
}

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

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

    SuccessOrExit(error = DecodeChannelMask(channelMask));
    otChannelManagerSetFavoredChannels(mInstance, channelMask);

exit:
    return error;
}

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

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

    SuccessOrExit(error = mDecoder.ReadBool(skipQualityCheck));
#if OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE
    error = otChannelManagerRequestChannelSelect(mInstance, skipQualityCheck);
#else
    OT_UNUSED_VARIABLE(skipQualityCheck);
    error = OT_ERROR_NOT_CAPABLE;
#endif

exit:
    return error;
}

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

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

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

exit:
    return error;
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_CHANNEL_MANAGER_AUTO_SELECT_INTERVAL>(void)
{
    return mEncoder.WriteUint32(otChannelManagerGetAutoChannelSelectionInterval(mInstance));
}

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_CHANNEL_MANAGER_AUTO_SELECT_INTERVAL>(void)
{
    uint32_t interval;
    otError  error = OT_ERROR_NONE;

    SuccessOrExit(error = mDecoder.ReadUint32(interval));
    error = otChannelManagerSetAutoChannelSelectionInterval(mInstance, interval);

exit:
    return error;
}

#endif // OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE

#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_TIME_SYNC_PERIOD>(void)
{
    return mEncoder.WriteUint16(otNetworkTimeGetSyncPeriod(mInstance));
}

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

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

    SuccessOrExit(error = otNetworkTimeSetSyncPeriod(mInstance, timeSyncPeriod));

exit:
    return error;
}

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

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

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

    SuccessOrExit(error = otNetworkTimeSetXtalThreshold(mInstance, xtalThreshold));

exit:
    return error;
}
#endif // OPENTHREAD_CONFIG_TIME_SYNC_ENABLE

#if OPENTHREAD_CONFIG_NCP_INFRA_IF_ENABLE && OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_INFRA_IF_STATE>(void)
{
    otError  error = OT_ERROR_NONE;
    uint32_t infraIfIndex;
    bool     isInfraRunning;

    SuccessOrExit(error = mDecoder.ReadUint32(infraIfIndex));
    SuccessOrExit(error = mDecoder.ReadBool(isInfraRunning));

    mInfraIfAddrCount = 0;
    while (!mDecoder.IsAllReadInStruct())
    {
        const otIp6Address *addr;

        SuccessOrExit(error = mDecoder.ReadIp6Address(addr));
        SuccessOrExit(error = InfraIfAddAddress(*addr));
    }

    if (infraIfIndex != mInfraIfIndex)
    {
        mInfraIfIndex = infraIfIndex;
        IgnoreError(otBorderRoutingSetEnabled(mInstance, /* aEnabled */ false));
        SuccessOrExit(error = otBorderRoutingInit(mInstance, mInfraIfIndex, isInfraRunning));
        SuccessOrExit(error = otBorderRoutingSetEnabled(mInstance, /* aEnabled */ true));
    }
    else
    {
        SuccessOrExit(error = otPlatInfraIfStateChanged(mInstance, mInfraIfIndex, isInfraRunning));
    }

exit:
    return error;
}

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_INFRA_IF_RECV_ICMP6>(void)
{
    otError             error = OT_ERROR_NONE;
    uint32_t            infraIfIndex;
    const otIp6Address *address;
    const uint8_t      *icmp6Data = nullptr;
    uint16_t            len;

    SuccessOrExit(error = mDecoder.ReadUint32(infraIfIndex));
    VerifyOrExit(mInfraIfIndex == infraIfIndex, error = OT_ERROR_DROP);
    SuccessOrExit(error = mDecoder.ReadIp6Address(address));
    SuccessOrExit(error = mDecoder.ReadData(icmp6Data, len));

    // Currently the ICMP6 messages can only be ND messages.
    otPlatInfraIfRecvIcmp6Nd(mInstance, infraIfIndex, address, icmp6Data, len);

exit:
    return error;
}

otError NcpBase::InfraIfAddAddress(const otIp6Address &aAddress)
{
    otError error = OT_ERROR_NONE;

    VerifyOrExit(mInfraIfAddrCount < kMaxInfraIfAddrs, error = OT_ERROR_NO_BUFS);
    memcpy(&mInfraIfAddrs[mInfraIfAddrCount++], &aAddress, sizeof(aAddress));
exit:
    return error;
}

bool NcpBase::InfraIfContainsAddress(const otIp6Address &aAddress)
{
    bool result = false;

    for (uint8_t i = 0; i < mInfraIfAddrCount; i++)
    {
        if (memcmp(&mInfraIfAddrs[i], &aAddress, sizeof(aAddress)) == 0)
        {
            result = true;
            break;
        }
    }
    return result;
}

bool NcpBase::InfraIfHasAddress(uint32_t aInfraIfIndex, const otIp6Address *aAddress)
{
    return aInfraIfIndex == mInfraIfIndex && InfraIfContainsAddress(*aAddress);
}

otError NcpBase::InfraIfSendIcmp6Nd(uint32_t            aInfraIfIndex,
                                    const otIp6Address *aDestAddress,
                                    const uint8_t      *aBuffer,
                                    uint16_t            aBufferLength)
{
    otError error  = OT_ERROR_NONE;
    uint8_t header = SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0;

    SuccessOrExit(error = mEncoder.BeginFrame(header, SPINEL_CMD_PROP_VALUE_IS, SPINEL_PROP_INFRA_IF_SEND_ICMP6));
    SuccessOrExit(error = mEncoder.WriteUint32(aInfraIfIndex));
    SuccessOrExit(error = mEncoder.WriteIp6Address(*aDestAddress));
    SuccessOrExit(error = mEncoder.WriteDataWithLen(aBuffer, aBufferLength));
    SuccessOrExit(error = mEncoder.EndFrame());

exit:
    return error;
}

#endif // OPENTHREAD_CONFIG_NCP_INFRA_IF_ENABLE && OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE

#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_SRP_SERVER_ENABLED>(void)
{
    otError error = OT_ERROR_NONE;
    bool    enable;

    SuccessOrExit(error = mDecoder.ReadBool(enable));
    otSrpServerSetEnabled(mInstance, enable);

exit:
    return error;
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_SRP_SERVER_ENABLED>(void)
{
    otSrpServerState srpServerState = otSrpServerGetState(mInstance);

    return mEncoder.WriteBool(srpServerState != OT_SRP_SERVER_STATE_DISABLED);
}

#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_SRP_SERVER_AUTO_ENABLE_MODE>(void)
{
    otError error = OT_ERROR_NONE;
    bool    enable;

    SuccessOrExit(error = mDecoder.ReadBool(enable));
    otSrpServerSetAutoEnableMode(mInstance, enable);

exit:
    return error;
}

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_SRP_SERVER_AUTO_ENABLE_MODE>(void)
{
    return mEncoder.WriteBool(otSrpServerIsAutoEnableMode(mInstance));
}
#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
#endif // OPENTHREAD_CONFIG_SRP_SERVER_ENABLE

#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE && OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_BORDER_ROUTER_DHCP6_PD_ENABLE>(void)
{
    otError error = OT_ERROR_NONE;
    bool    enable;

    SuccessOrExit(error = mDecoder.ReadBool(enable));
    otBorderRoutingDhcp6PdSetEnabled(mInstance, enable);

exit:
    return error;
}

#if !OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_CLIENT_ENABLE
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_BORDER_ROUTER_DHCP6_PD_PREFIX>(void)
{
    otError                         error = OT_ERROR_NONE;
    otBorderRoutingPrefixTableEntry prefixEntry;
    const otIp6Address             *prefixAddr;

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

    SuccessOrExit(error = mDecoder.ReadIp6Address(prefixAddr));
    prefixEntry.mPrefix.mPrefix = *prefixAddr;
    SuccessOrExit(error = mDecoder.ReadUint8(prefixEntry.mPrefix.mLength));

    SuccessOrExit(error = mDecoder.ReadUint32(prefixEntry.mValidLifetime));
    SuccessOrExit(error = mDecoder.ReadUint32(prefixEntry.mPreferredLifetime));

    otPlatBorderRoutingProcessDhcp6PdPrefix(mInstance, &prefixEntry);

exit:
    return error;
}
#endif

#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE && OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE

#if OPENTHREAD_CONFIG_NCP_DNSSD_ENABLE && OPENTHREAD_CONFIG_PLATFORM_DNSSD_ENABLE

void NcpBase::DnssdRegisterHost(const otPlatDnssdHost      *aHost,
                                otPlatDnssdRequestId        aRequestId,
                                otPlatDnssdRegisterCallback aCallback)
{
    DnssdUpdate(aHost, aRequestId, aCallback, /* aRegister */ true);
}

void NcpBase::DnssdUnregisterHost(const otPlatDnssdHost      *aHost,
                                  otPlatDnssdRequestId        aRequestId,
                                  otPlatDnssdRegisterCallback aCallback)
{
    DnssdUpdate(aHost, aRequestId, aCallback, /* aRegister */ false);
}

void NcpBase::DnssdRegisterService(const otPlatDnssdService   *aService,
                                   otPlatDnssdRequestId        aRequestId,
                                   otPlatDnssdRegisterCallback aCallback)
{
    DnssdUpdate(aService, aRequestId, aCallback, /* aRegister */ true);
}

void NcpBase::DnssdUnregisterService(const otPlatDnssdService   *aService,
                                     otPlatDnssdRequestId        aRequestId,
                                     otPlatDnssdRegisterCallback aCallback)
{
    DnssdUpdate(aService, aRequestId, aCallback, /* aRegister */ false);
}

void NcpBase::DnssdRegisterKey(const otPlatDnssdKey       *aKey,
                               otPlatDnssdRequestId        aRequestId,
                               otPlatDnssdRegisterCallback aCallback)
{
    DnssdUpdate(aKey, aRequestId, aCallback, /* aRegister */ true);
}

void NcpBase::DnssdUnregisterKey(const otPlatDnssdKey       *aKey,
                                 otPlatDnssdRequestId        aRequestId,
                                 otPlatDnssdRegisterCallback aCallback)
{
    DnssdUpdate(aKey, aRequestId, aCallback, /* aRegister */ false);
}

void NcpBase::DnssdStartBrowser(const otPlatDnssdBrowser *aBrowser)
{
    DnssdUpdateDiscovery(aBrowser, /* aStart */ true);
}

void NcpBase::DnssdStopBrowser(const otPlatDnssdBrowser *aBrowser)
{
    DnssdUpdateDiscovery(aBrowser, /* aStart */ false);
}

void NcpBase::DnssdStartSrvResolver(const otPlatDnssdSrvResolver *aResolver)
{
    DnssdUpdateDiscovery(aResolver, /* aStart */ true);
}

void NcpBase::DnssdStopSrvResolver(const otPlatDnssdSrvResolver *aResolver)
{
    DnssdUpdateDiscovery(aResolver, /* aStart */ false);
}

otPlatDnssdState NcpBase::DnssdGetState(void) { return mDnssdState; }

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

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

    if (state != mDnssdState)
    {
        mDnssdState = static_cast<otPlatDnssdState>(state);
        otPlatDnssdStateHandleStateChange(mInstance);
    }

exit:
    return error;
}

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_DNSSD_REQUEST_RESULT>(void)
{
    otError                     error = OT_ERROR_NONE;
    otPlatDnssdRequestId        requestId;
    uint8_t                     result;
    otPlatDnssdRegisterCallback callback = nullptr;
    const uint8_t              *context;
    uint16_t                    contextLen;

    SuccessOrExit(error = mDecoder.ReadUint8(result));
    SuccessOrExit(error = mDecoder.ReadUint32(requestId));
    SuccessOrExit(error = mDecoder.ReadData(context, contextLen));
    VerifyOrExit(contextLen == sizeof(otPlatDnssdRegisterCallback), error = OT_ERROR_PARSE);
    callback = *reinterpret_cast<const otPlatDnssdRegisterCallback *>(context);
    callback(mInstance, requestId, static_cast<otError>(result));

exit:
    return error;
}

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_DNSSD_BROWSE_RESULT>(void)
{
    otError                   error = OT_ERROR_NONE;
    otPlatDnssdBrowseResult   browseResult;
    otPlatDnssdBrowseCallback callback = nullptr;
    const uint8_t            *context;
    uint16_t                  contextLen;

    SuccessOrExit(error = DecodeDnssdBrowseResult(mDecoder, browseResult, context, contextLen));
    VerifyOrExit(contextLen == sizeof(otPlatDnssdBrowseCallback), error = OT_ERROR_PARSE);
    callback = *reinterpret_cast<const otPlatDnssdBrowseCallback *>(context);
    callback(mInstance, &browseResult);

exit:
    return error;
}

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_DNSSD_SRV_RESULT>(void)
{
    otError                error = OT_ERROR_NONE;
    otPlatDnssdSrvResult   srvResult;
    otPlatDnssdSrvCallback callback = nullptr;
    const uint8_t         *context;
    uint16_t               contextLen;

    SuccessOrExit(error = DecodeDnssdSrvResult(mDecoder, srvResult, context, contextLen));
    VerifyOrExit(contextLen == sizeof(otPlatDnssdSrvCallback), error = OT_ERROR_PARSE);
    callback = *reinterpret_cast<const otPlatDnssdSrvCallback *>(context);
    callback(mInstance, &srvResult);

exit:
    return error;
}
#endif // OPENTHREAD_CONFIG_NCP_DNSSD_ENABLE && OPENTHREAD_CONFIG_PLATFORM_DNSSD_ENABLE

#if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_BORDER_AGENT_MESHCOP_SERVICE_STATE>(void)
{
    otError                            error = OT_ERROR_NONE;
    otBorderAgentMeshCoPServiceTxtData txtData;

    SuccessOrExit(error = otBorderAgentGetMeshCoPServiceTxtData(mInstance, &txtData));
    SuccessOrExit(error = mEncoder.WriteBool(otBorderAgentIsActive(mInstance)));
    SuccessOrExit(error = mEncoder.WriteUint16(otBorderAgentGetUdpPort(mInstance)));
    SuccessOrExit(error = mEncoder.WriteData(txtData.mData, txtData.mLength));

exit:
    return error;
}

#if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE

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

    SuccessOrExit(error = mEncoder.WriteUint8(static_cast<uint8_t>(otBorderAgentEphemeralKeyGetState(mInstance))));
    SuccessOrExit(error = mEncoder.WriteUint16(otBorderAgentEphemeralKeyGetUdpPort(mInstance)));

exit:
    return error;
}

#endif

#endif // OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE

#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_BACKBONE_ROUTER_STATE>(void)
{
    otBackboneRouterState state = otBackboneRouterGetState(mInstance);

    return mEncoder.WriteUint8(static_cast<uint8_t>(state));
}

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_BACKBONE_ROUTER_ENABLE>(void)
{
    otError error = OT_ERROR_NONE;
    bool    enable;

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

#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_MULTICAST_ROUTING_ENABLE
    if (enable)
    {
        otBackboneRouterSetMulticastListenerCallback(mInstance, HandleBackboneRouterMulticastListenerEvent, this);
    }
    else
    {
        otBackboneRouterSetMulticastListenerCallback(mInstance, nullptr, nullptr);
    }
#endif
    otBackboneRouterSetEnabled(mInstance, enable);

exit:
    return error;
}

#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_MULTICAST_ROUTING_ENABLE
void NcpBase::HandleBackboneRouterMulticastListenerEvent(void                                  *aContext,
                                                         otBackboneRouterMulticastListenerEvent aEvent,
                                                         const otIp6Address                    *aAddress)
{
    static_cast<NcpBase *>(aContext)->HandleBackboneRouterMulticastListenerEvent(aEvent, aAddress);
}

void NcpBase::HandleBackboneRouterMulticastListenerEvent(otBackboneRouterMulticastListenerEvent aEvent,
                                                         const otIp6Address                    *aAddress)
{
    uint8_t          header = SPINEL_HEADER_FLAG | SPINEL_HEADER_TX_NOTIFICATION_IID;
    spinel_command_t cmd    = aEvent == OT_BACKBONE_ROUTER_MULTICAST_LISTENER_ADDED ? SPINEL_CMD_PROP_VALUE_INSERTED
                                                                                    : SPINEL_CMD_PROP_VALUE_REMOVED;
    VerifyOrExit(aAddress != nullptr);

    SuccessOrExit(mEncoder.BeginFrame(header, cmd));
    SuccessOrExit(mEncoder.WriteUintPacked(SPINEL_PROP_BACKBONE_ROUTER_MULTICAST_LISTENER));
    SuccessOrExit(mEncoder.WriteIp6Address(*aAddress));
    SuccessOrExit(mEncoder.EndFrame());

exit:
    return;
}
#endif // OPENTHREAD_CONFIG_BACKBONE_ROUTER_MULTICAST_ROUTING_ENABLE
#endif // OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE

} // namespace Ncp
} // namespace ot

#endif // OPENTHREAD_FTD
