blob: 54404ba6d0bbcdf39ab4cf9eeddc4b649db997fe [file] [log] [blame]
/*
* 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/diag.h>
#include <openthread/icmp6.h>
#include <openthread/ncp.h>
#include <openthread/openthread.h>
#include <openthread/platform/misc.h>
#include <openthread/platform/radio.h>
#include <openthread/thread_ftd.h>
#if OPENTHREAD_ENABLE_TMF_PROXY
#include <openthread/tmf_proxy.h>
#endif
#include "openthread-instance.h"
#include "common/code_utils.hpp"
#include "common/debug.hpp"
#if OPENTHREAD_ENABLE_COMMISSIONER
#include "meshcop/commissioner.hpp"
#endif
#include "net/ip6.hpp"
#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.mSecureDataRequest,
aChildInfo.mFullFunction,
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
// ----------------------------------------------------------------------------
void NcpBase::HandleChildTableChanged(otThreadChildTableEvent aEvent, const otChildInfo *aChildInfo)
{
GetNcpInstance()->HandleChildTableChanged(aEvent, *aChildInfo);
}
void NcpBase::HandleChildTableChanged(otThreadChildTableEvent aEvent, const otChildInfo &aChildInfo)
{
otError error = OT_ERROR_NONE;
uint8_t header = SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0;
unsigned int command = 0;
VerifyOrExit(!mChangedPropsSet.IsPropertyFiltered(SPINEL_PROP_THREAD_CHILD_TABLE));
switch (aEvent)
{
case OT_THREAD_CHILD_TABLE_EVENT_CHILD_ADDED:
command = SPINEL_CMD_PROP_VALUE_INSERTED;
break;
case OT_THREAD_CHILD_TABLE_EVENT_CHILD_REMOVED:
command = SPINEL_CMD_PROP_VALUE_REMOVED;
break;
default:
ExitNow();
}
SuccessOrExit(error = mEncoder.BeginFrame(header, command, SPINEL_PROP_THREAD_CHILD_TABLE));
SuccessOrExit(error = EncodeChildInfo(aChildInfo));
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)
{
mShouldEmitChildTableUpdate = true;
mChangedPropsSet.AddLastStatus(SPINEL_STATUS_NOMEM);
mUpdateChangedPropsTask.Post();
}
}
// ----------------------------------------------------------------------------
// MARK: Individual Property Handlers
// ----------------------------------------------------------------------------
otError NcpBase::GetPropertyHandler_THREAD_LOCAL_LEADER_WEIGHT(void)
{
return mEncoder.WriteUint8(otThreadGetLocalLeaderWeight(mInstance));
}
otError NcpBase::GetPropertyHandler_THREAD_LEADER_WEIGHT(void)
{
return mEncoder.WriteUint8(otThreadGetLeaderWeight(mInstance));
}
otError NcpBase::GetPropertyHandler_THREAD_CHILD_TABLE(void)
{
otError error = OT_ERROR_NONE;
otChildInfo childInfo;
uint8_t maxChildren;
maxChildren = otThreadGetMaxAllowedChildren(mInstance);
for (uint8_t index = 0; index < maxChildren; index++)
{
if (otThreadGetChildInfoByIndex(mInstance, index, &childInfo) != OT_ERROR_NONE)
{
continue;
}
SuccessOrExit(error = mEncoder.OpenStruct());
SuccessOrExit(error = EncodeChildInfo(childInfo));
SuccessOrExit(error = mEncoder.CloseStruct());
}
exit:
return error;
}
otError NcpBase::GetPropertyHandler_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;
}
otError NcpBase::GetPropertyHandler_THREAD_ROUTER_ROLE_ENABLED(void)
{
return mEncoder.WriteBool(otThreadIsRouterRoleEnabled(mInstance));
}
otError NcpBase::SetPropertyHandler_THREAD_ROUTER_ROLE_ENABLED(void)
{
bool enabled;
otError error = OT_ERROR_NONE;
SuccessOrExit(error = mDecoder.ReadBool(enabled));
otThreadSetRouterRoleEnabled(mInstance, enabled);
exit:
return error;
}
otError NcpBase::GetPropertyHandler_NET_PSKC(void)
{
return mEncoder.WriteData(otThreadGetPSKc(mInstance), sizeof(spinel_net_pskc_t));
}
otError NcpBase::SetPropertyHandler_NET_PSKC(void)
{
const uint8_t *ptr = NULL;
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, ptr);
exit:
return error;
}
otError NcpBase::GetPropertyHandler_THREAD_CHILD_COUNT_MAX(void)
{
return mEncoder.WriteUint8(otThreadGetMaxAllowedChildren(mInstance));
}
otError NcpBase::SetPropertyHandler_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;
}
otError NcpBase::GetPropertyHandler_THREAD_ROUTER_UPGRADE_THRESHOLD(void)
{
return mEncoder.WriteUint8(otThreadGetRouterUpgradeThreshold(mInstance));
}
otError NcpBase::SetPropertyHandler_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;
}
otError NcpBase::GetPropertyHandler_THREAD_ROUTER_DOWNGRADE_THRESHOLD(void)
{
return mEncoder.WriteUint8(otThreadGetRouterDowngradeThreshold(mInstance));
}
otError NcpBase::SetPropertyHandler_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;
}
otError NcpBase::GetPropertyHandler_THREAD_ROUTER_SELECTION_JITTER(void)
{
return mEncoder.WriteUint8(otThreadGetRouterSelectionJitter(mInstance));
}
otError NcpBase::SetPropertyHandler_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;
}
otError NcpBase::GetPropertyHandler_THREAD_CONTEXT_REUSE_DELAY(void)
{
return mEncoder.WriteUint32(otThreadGetContextIdReuseDelay(mInstance));
}
otError NcpBase::SetPropertyHandler_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;
}
otError NcpBase::GetPropertyHandler_THREAD_NETWORK_ID_TIMEOUT(void)
{
return mEncoder.WriteUint8(otThreadGetNetworkIdTimeout(mInstance));
}
otError NcpBase::SetPropertyHandler_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;
}
#if OPENTHREAD_ENABLE_COMMISSIONER
otError NcpBase::GetPropertyHandler_THREAD_COMMISSIONER_ENABLED(void)
{
return mEncoder.WriteBool(otCommissionerGetState(mInstance) == OT_COMMISSIONER_STATE_ACTIVE);
}
otError NcpBase::SetPropertyHandler_THREAD_COMMISSIONER_ENABLED(uint8_t aHeader)
{
bool enabled = false;
otError error = OT_ERROR_NONE;
SuccessOrExit(error = mDecoder.ReadBool(enabled));
if (enabled == false)
{
error = otCommissionerStop(mInstance);
}
else
{
error = otCommissionerStart(mInstance);
}
exit:
return SendLastStatus(aHeader, ThreadErrorToSpinelStatus(error));
}
otError NcpBase::InsertPropertyHandler_THREAD_JOINERS(void)
{
otError error = OT_ERROR_NONE;
const otExtAddress *extAddress = NULL;
const char *aPSKd = NULL;
uint32_t joinerTimeout = 0;
VerifyOrExit(mAllowLocalNetworkDataChange == true, error = OT_ERROR_INVALID_STATE);
SuccessOrExit(error = mDecoder.ReadUtf8(aPSKd));
SuccessOrExit(error = mDecoder.ReadUint32(joinerTimeout));
if (mDecoder.ReadEui64(extAddress) != OT_ERROR_NONE)
{
extAddress = NULL;
}
error = otCommissionerAddJoiner(mInstance, extAddress, aPSKd, joinerTimeout);
exit:
return error;
}
#endif // OPENTHREAD_ENABLE_COMMISSIONER
otError NcpBase::SetPropertyHandler_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_ENABLE_STEERING_DATA_SET_OOB
otError NcpBase::SetPropertyHandler_THREAD_STEERING_DATA(void)
{
const otExtAddress *extAddress;
otError error = OT_ERROR_NONE;
SuccessOrExit(error = mDecoder.ReadEui64(extAddress));
SuccessOrExit(error = otThreadSetSteeringData(mInstance, extAddress));
// Note that there is no get handler for this property
// so the response becomes `VALUE_IS` echo of the
// received content.
exit:
return error;
}
#endif // #if OPENTHREAD_CONFIG_ENABLE_STEERING_DATA_SET_OOB
otError NcpBase::SetPropertyHandler_THREAD_CHILD_TIMEOUT(void)
{
uint32_t timeout = 0;
otError error = OT_ERROR_NONE;
SuccessOrExit(error = mDecoder.ReadUint32(timeout));
otThreadSetChildTimeout(mInstance, timeout);
exit:
return error;
}
otError NcpBase::SetPropertyHandler_THREAD_PREFERRED_ROUTER_ID(void)
{
uint8_t routerId = 0;
otError error = OT_ERROR_NONE;
SuccessOrExit(error = mDecoder.ReadUint8(routerId));
SuccessOrExit(error = otThreadSetPreferredRouterId(mInstance, routerId));
// Note that there is no get handler for this property
// so the response becomes `VALUE_IS` echo of the
// received content.
exit:
return error;
}
otError NcpBase::RemovePropertyHandler_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;
}
#if OPENTHREAD_ENABLE_TMF_PROXY
otError NcpBase::GetPropertyHandler_THREAD_TMF_PROXY_ENABLED(void)
{
return mEncoder.WriteBool(otTmfProxyIsEnabled(mInstance));
}
otError NcpBase::SetPropertyHandler_THREAD_TMF_PROXY_STREAM(void)
{
const uint8_t *framePtr = NULL;
uint16_t frameLen = 0;
uint16_t locator;
uint16_t port;
otMessage *message;
otError error = OT_ERROR_NONE;
// THREAD_TMF_PROXY_STREAM requires layer 2 security.
message = otIp6NewMessage(mInstance, true);
VerifyOrExit(message != NULL, error = OT_ERROR_NO_BUFS);
SuccessOrExit(error = mDecoder.ReadDataWithLen(framePtr, frameLen));
SuccessOrExit(error = mDecoder.ReadUint16(locator));
SuccessOrExit(error = mDecoder.ReadUint16(port));
SuccessOrExit(error = otMessageAppend(message, framePtr, static_cast<uint16_t>(frameLen)));
error = otTmfProxySend(mInstance, message, locator, port);
// `otTmfProxySend()` takes ownership of `message` (in both success
// or failure cases). `message` is set to NULL so it is not freed at
// exit.
message = NULL;
exit:
if (message != NULL)
{
otMessageFree(message);
}
return error;
}
otError NcpBase::SetPropertyHandler_THREAD_TMF_PROXY_ENABLED(void)
{
bool enabled;
otError error = OT_ERROR_NONE;
SuccessOrExit(error = mDecoder.ReadBool(enabled));
if (enabled)
{
error = otTmfProxyStart(mInstance, &NcpBase::HandleTmfProxyStream, this);
}
else
{
error = otTmfProxyStop(mInstance);
}
exit:
return error;
}
void NcpBase::HandleTmfProxyStream(otMessage *aMessage, uint16_t aLocator, uint16_t aPort, void *aContext)
{
static_cast<NcpBase *>(aContext)->HandleTmfProxyStream(aMessage, aLocator, aPort);
}
void NcpBase::HandleTmfProxyStream(otMessage *aMessage, uint16_t aLocator, uint16_t aPort)
{
otError error = OT_ERROR_NONE;
uint16_t length = otMessageGetLength(aMessage);
uint8_t header = SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0;
SuccessOrExit(error = mEncoder.BeginFrame(
header,
SPINEL_CMD_PROP_VALUE_IS,
SPINEL_PROP_THREAD_TMF_PROXY_STREAM
));
SuccessOrExit(error = mEncoder.WriteUint16(length));
SuccessOrExit(error = mEncoder.WriteMessage(aMessage));
SuccessOrExit(error = mEncoder.WriteUint16(aLocator));
SuccessOrExit(error = mEncoder.WriteUint16(aPort));
SuccessOrExit(error = mEncoder.EndFrame());
// The `aMessage` is owned by the outbound frame and NCP buffer
// after frame was finished/ended successfully. It will be freed
// when the frame is successfully sent and removed.
aMessage = NULL;
exit:
if (aMessage != NULL)
{
otMessageFree(aMessage);
}
if (error != OT_ERROR_NONE)
{
mChangedPropsSet.AddLastStatus(SPINEL_STATUS_DROPPED);
mUpdateChangedPropsTask.Post();
}
}
#endif // OPENTHREAD_ENABLE_TMF_PROXY
} // namespace Ncp
} // namespace ot
#endif // OPENTHREAD_FTD