blob: 3e3d7861953df9b335cffe836b26d99c80978171 [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 raw link required Spinel interface to the OpenThread stack.
*/
#include "ncp_base.hpp"
#include <openthread/link.h>
#include <openthread/link_raw.h>
#include <openthread/ncp.h>
#include <openthread/platform/radio.h>
#include <openthread/platform/time.h>
#include "common/code_utils.hpp"
#include "common/debug.hpp"
#include "common/instance.hpp"
#include "mac/mac_frame.hpp"
#if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE
namespace ot {
namespace Ncp {
#if OPENTHREAD_RADIO
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_RCP_API_VERSION>(void)
{
return mEncoder.WriteUintPacked(SPINEL_RCP_API_VERSION);
}
#endif
// ----------------------------------------------------------------------------
// MARK: Raw Link-Layer Datapath Glue
// ----------------------------------------------------------------------------
otError NcpBase::PackRadioFrame(otRadioFrame *aFrame, otError aError)
{
otError error = OT_ERROR_FAILED;
uint16_t flags = 0;
if (aFrame != nullptr && aError == OT_ERROR_NONE)
{
// Append the frame contents
SuccessOrExit(mEncoder.WriteDataWithLen(aFrame->mPsdu, aFrame->mLength));
}
else
{
// Append length
SuccessOrExit(mEncoder.WriteUint16(0));
}
// Append metadata (rssi, etc)
SuccessOrExit(mEncoder.WriteInt8(aFrame ? aFrame->mInfo.mRxInfo.mRssi : 0)); // RSSI
SuccessOrExit(mEncoder.WriteInt8(-128)); // Noise Floor (Currently unused)
if (aFrame != nullptr)
{
if (aFrame->mInfo.mRxInfo.mAckedWithFramePending)
{
flags |= SPINEL_MD_FLAG_ACKED_FP;
}
if (aFrame->mInfo.mRxInfo.mAckedWithSecEnhAck)
{
flags |= SPINEL_MD_FLAG_ACKED_SEC;
}
}
SuccessOrExit(mEncoder.WriteUint16(flags)); // Flags
SuccessOrExit(mEncoder.OpenStruct()); // PHY-data
SuccessOrExit(mEncoder.WriteUint8(aFrame ? aFrame->mChannel : 0)); // 802.15.4 channel (Receive channel)
SuccessOrExit(mEncoder.WriteUint8(aFrame ? aFrame->mInfo.mRxInfo.mLqi
: static_cast<uint8_t>(OT_RADIO_LQI_NONE))); // 802.15.4 LQI
SuccessOrExit(mEncoder.WriteUint64(aFrame ? aFrame->mInfo.mRxInfo.mTimestamp : 0)); // The timestamp in microseconds
SuccessOrExit(mEncoder.CloseStruct());
SuccessOrExit(mEncoder.OpenStruct()); // Vendor-data
SuccessOrExit(mEncoder.WriteUintPacked(aError)); // Receive error
SuccessOrExit(mEncoder.CloseStruct());
SuccessOrExit(mEncoder.OpenStruct()); // MAC-data
SuccessOrExit(mEncoder.WriteUint8(aFrame ? aFrame->mInfo.mRxInfo.mAckKeyId : 0)); // The ACK auxiliary key ID
SuccessOrExit(
mEncoder.WriteUint32(aFrame ? aFrame->mInfo.mRxInfo.mAckFrameCounter : 0)); // The ACK auxiliary frame counter
SuccessOrExit(mEncoder.CloseStruct());
error = OT_ERROR_NONE;
exit:
return error;
}
void NcpBase::LinkRawReceiveDone(otInstance *, otRadioFrame *aFrame, otError aError)
{
sNcpInstance->LinkRawReceiveDone(aFrame, aError);
}
void NcpBase::LinkRawReceiveDone(otRadioFrame *aFrame, otError aError)
{
uint8_t header = SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0;
// Append frame header
SuccessOrExit(mEncoder.BeginFrame(header, SPINEL_CMD_PROP_VALUE_IS, SPINEL_PROP_STREAM_RAW));
SuccessOrExit(PackRadioFrame(aFrame, aError));
SuccessOrExit(mEncoder.EndFrame());
exit:
return;
}
void NcpBase::LinkRawTransmitDone(otInstance *, otRadioFrame *aFrame, otRadioFrame *aAckFrame, otError aError)
{
sNcpInstance->LinkRawTransmitDone(aFrame, aAckFrame, aError);
}
void NcpBase::LinkRawTransmitDone(otRadioFrame *aFrame, otRadioFrame *aAckFrame, otError aError)
{
OT_UNUSED_VARIABLE(aFrame);
if (mCurTransmitTID)
{
uint8_t header = SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0 | mCurTransmitTID;
bool framePending = (aAckFrame != nullptr && static_cast<Mac::RxFrame *>(aAckFrame)->GetFramePending());
bool headerUpdated = static_cast<Mac::TxFrame *>(aFrame)->IsHeaderUpdated();
// Clear cached transmit TID
mCurTransmitTID = 0;
SuccessOrExit(mEncoder.BeginFrame(header, SPINEL_CMD_PROP_VALUE_IS, SPINEL_PROP_LAST_STATUS));
SuccessOrExit(mEncoder.WriteUintPacked(ThreadErrorToSpinelStatus(aError)));
SuccessOrExit(mEncoder.WriteBool(framePending));
SuccessOrExit(mEncoder.WriteBool(headerUpdated));
if (aError == OT_ERROR_NONE)
{
SuccessOrExit(PackRadioFrame(aAckFrame, aError));
}
if (static_cast<Mac::TxFrame *>(aFrame)->GetSecurityEnabled() && headerUpdated)
{
uint8_t keyId;
uint32_t frameCounter;
// Transmit frame auxiliary key index and frame counter
SuccessOrExit(static_cast<Mac::TxFrame *>(aFrame)->GetKeyId(keyId));
SuccessOrExit(static_cast<Mac::TxFrame *>(aFrame)->GetFrameCounter(frameCounter));
SuccessOrExit(mEncoder.WriteUint8(keyId));
SuccessOrExit(mEncoder.WriteUint32(frameCounter));
}
SuccessOrExit(mEncoder.EndFrame());
}
exit:
return;
}
void NcpBase::LinkRawEnergyScanDone(otInstance *, int8_t aEnergyScanMaxRssi)
{
sNcpInstance->LinkRawEnergyScanDone(aEnergyScanMaxRssi);
}
void NcpBase::LinkRawEnergyScanDone(int8_t aEnergyScanMaxRssi)
{
int8_t scanChannel = mCurScanChannel;
// Clear current scan channel
mCurScanChannel = kInvalidScanChannel;
// Make sure we are back listening on the original receive channel,
// since the energy scan could have been on a different channel.
IgnoreError(otLinkRawReceive(mInstance));
SuccessOrExit(mEncoder.BeginFrame(SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, SPINEL_CMD_PROP_VALUE_IS,
SPINEL_PROP_MAC_ENERGY_SCAN_RESULT));
SuccessOrExit(mEncoder.WriteUint8(static_cast<uint8_t>(scanChannel)));
SuccessOrExit(mEncoder.WriteInt8(aEnergyScanMaxRssi));
SuccessOrExit(mEncoder.EndFrame());
// We are finished with the scan, so send out
// a property update indicating such.
SuccessOrExit(mEncoder.BeginFrame(SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, SPINEL_CMD_PROP_VALUE_IS,
SPINEL_PROP_MAC_SCAN_STATE));
SuccessOrExit(mEncoder.WriteUint8(SPINEL_SCAN_STATE_IDLE));
SuccessOrExit(mEncoder.EndFrame());
exit:
return;
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_RADIO_CAPS>(void)
{
return mEncoder.WriteUintPacked(otLinkRawGetCaps(mInstance));
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_MAC_SRC_MATCH_ENABLED>(void)
{
// TODO: Would be good to add an `otLinkRaw` API to give the status of source match.
return mEncoder.WriteBool(mSrcMatchEnabled);
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_RCP_TIMESTAMP>(void)
{
otError error = OT_ERROR_NONE;
SuccessOrExit(error = mEncoder.WriteUint64(otLinkRawGetRadioTime(mInstance)));
exit:
return error;
}
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MAC_SRC_MATCH_ENABLED>(void)
{
otError error = OT_ERROR_NONE;
SuccessOrExit(error = mDecoder.ReadBool(mSrcMatchEnabled));
error = otLinkRawSrcMatchEnable(mInstance, mSrcMatchEnabled);
exit:
return error;
}
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MAC_SRC_MATCH_SHORT_ADDRESSES>(void)
{
otError error = OT_ERROR_NONE;
// Clear the list first
SuccessOrExit(error = otLinkRawSrcMatchClearShortEntries(mInstance));
// Loop through the addresses and add them
while (mDecoder.GetRemainingLengthInStruct() >= sizeof(uint16_t))
{
uint16_t shortAddress;
SuccessOrExit(error = mDecoder.ReadUint16(shortAddress));
SuccessOrExit(error = otLinkRawSrcMatchAddShortEntry(mInstance, shortAddress));
}
exit:
return error;
}
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MAC_SRC_MATCH_EXTENDED_ADDRESSES>(void)
{
otError error = OT_ERROR_NONE;
// Clear the list first
SuccessOrExit(error = otLinkRawSrcMatchClearExtEntries(mInstance));
// Loop through the addresses and add them
while (mDecoder.GetRemainingLengthInStruct() >= sizeof(otExtAddress))
{
const otExtAddress *extAddress;
SuccessOrExit(error = mDecoder.ReadEui64(extAddress));
SuccessOrExit(error = otLinkRawSrcMatchAddExtEntry(mInstance, extAddress));
}
exit:
return error;
}
template <> otError NcpBase::HandlePropertyRemove<SPINEL_PROP_MAC_SRC_MATCH_SHORT_ADDRESSES>(void)
{
otError error = OT_ERROR_NONE;
uint16_t shortAddress;
SuccessOrExit(error = mDecoder.ReadUint16(shortAddress));
error = otLinkRawSrcMatchClearShortEntry(mInstance, shortAddress);
exit:
return error;
}
template <> otError NcpBase::HandlePropertyRemove<SPINEL_PROP_MAC_SRC_MATCH_EXTENDED_ADDRESSES>(void)
{
otError error = OT_ERROR_NONE;
const otExtAddress *extAddress;
SuccessOrExit(error = mDecoder.ReadEui64(extAddress));
;
error = otLinkRawSrcMatchClearExtEntry(mInstance, extAddress);
exit:
return error;
}
template <> otError NcpBase::HandlePropertyInsert<SPINEL_PROP_MAC_SRC_MATCH_SHORT_ADDRESSES>(void)
{
otError error = OT_ERROR_NONE;
uint16_t shortAddress;
SuccessOrExit(error = mDecoder.ReadUint16(shortAddress));
error = otLinkRawSrcMatchAddShortEntry(mInstance, shortAddress);
exit:
return error;
}
template <> otError NcpBase::HandlePropertyInsert<SPINEL_PROP_MAC_SRC_MATCH_EXTENDED_ADDRESSES>(void)
{
otError error = OT_ERROR_NONE;
const otExtAddress *extAddress = nullptr;
SuccessOrExit(error = mDecoder.ReadEui64(extAddress));
error = otLinkRawSrcMatchAddExtEntry(mInstance, extAddress);
exit:
return error;
}
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_PHY_ENABLED>(void)
{
bool value = false;
otError error = OT_ERROR_NONE;
SuccessOrExit(error = mDecoder.ReadBool(value));
if (!value)
{
error = otLinkRawSetReceiveDone(mInstance, nullptr);
}
else
{
error = otLinkRawSetReceiveDone(mInstance, &NcpBase::LinkRawReceiveDone);
}
exit:
return error;
}
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MAC_15_4_SADDR>(void)
{
uint16_t shortAddress;
otError error = OT_ERROR_NONE;
SuccessOrExit(error = mDecoder.ReadUint16(shortAddress));
error = otLinkRawSetShortAddress(mInstance, shortAddress);
exit:
return error;
}
otError NcpBase::DecodeStreamRawTxRequest(otRadioFrame &aFrame)
{
otError error;
const uint8_t *payloadPtr;
uint16_t payloadLen;
bool csmaEnable;
bool isARetx;
bool isHeaderUpdated;
bool isSecurityProcessed;
SuccessOrExit(error = mDecoder.ReadDataWithLen(payloadPtr, payloadLen));
VerifyOrExit(payloadLen <= OT_RADIO_FRAME_MAX_SIZE, error = OT_ERROR_PARSE);
aFrame.mLength = static_cast<uint8_t>(payloadLen);
memcpy(aFrame.mPsdu, payloadPtr, aFrame.mLength);
// Parse the meta data
// Channel is a required parameter in meta data.
SuccessOrExit(error = mDecoder.ReadUint8(aFrame.mChannel));
// Set the default value for all optional parameters.
aFrame.mInfo.mTxInfo.mMaxCsmaBackoffs = OPENTHREAD_CONFIG_MAC_MAX_CSMA_BACKOFFS_DIRECT;
aFrame.mInfo.mTxInfo.mMaxFrameRetries = OPENTHREAD_CONFIG_MAC_DEFAULT_MAX_FRAME_RETRIES_DIRECT;
aFrame.mInfo.mTxInfo.mCsmaCaEnabled = true;
aFrame.mInfo.mTxInfo.mIsHeaderUpdated = false;
aFrame.mInfo.mTxInfo.mIsARetx = false;
aFrame.mInfo.mTxInfo.mIsSecurityProcessed = false;
aFrame.mInfo.mTxInfo.mTxDelay = 0;
aFrame.mInfo.mTxInfo.mTxDelayBaseTime = 0;
// All the next parameters are optional. Note that even if the
// decoder fails to parse any of optional parameters we still want to
// return `OT_ERROR_NONE` (so `error` is not updated after this
// point).
SuccessOrExit(mDecoder.ReadUint8(aFrame.mInfo.mTxInfo.mMaxCsmaBackoffs));
SuccessOrExit(mDecoder.ReadUint8(aFrame.mInfo.mTxInfo.mMaxFrameRetries));
SuccessOrExit(mDecoder.ReadBool(csmaEnable));
SuccessOrExit(mDecoder.ReadBool(isHeaderUpdated));
SuccessOrExit(mDecoder.ReadBool(isARetx));
SuccessOrExit(mDecoder.ReadBool(isSecurityProcessed));
SuccessOrExit(mDecoder.ReadUint32(aFrame.mInfo.mTxInfo.mTxDelay));
SuccessOrExit(mDecoder.ReadUint32(aFrame.mInfo.mTxInfo.mTxDelayBaseTime));
aFrame.mInfo.mTxInfo.mCsmaCaEnabled = csmaEnable;
aFrame.mInfo.mTxInfo.mIsHeaderUpdated = isHeaderUpdated;
aFrame.mInfo.mTxInfo.mIsARetx = isARetx;
aFrame.mInfo.mTxInfo.mIsSecurityProcessed = isSecurityProcessed;
exit:
return error;
}
otError NcpBase::HandlePropertySet_SPINEL_PROP_STREAM_RAW(uint8_t aHeader)
{
otError error = OT_ERROR_NONE;
otRadioFrame *frame;
VerifyOrExit(otLinkRawIsEnabled(mInstance), error = OT_ERROR_INVALID_STATE);
frame = otLinkRawGetTransmitBuffer(mInstance);
VerifyOrExit(frame != nullptr, error = OT_ERROR_NO_BUFS);
SuccessOrExit(error = DecodeStreamRawTxRequest(*frame));
// Cache the transaction ID for async response
mCurTransmitTID = SPINEL_HEADER_GET_TID(aHeader);
// Pass frame to the radio layer. Note, this fails if we
// haven't enabled raw stream or are already transmitting.
error = otLinkRawTransmit(mInstance, &NcpBase::LinkRawTransmitDone);
exit:
if (error == OT_ERROR_NONE)
{
// Don't do anything here yet. We will complete the transaction when we get a transmit done callback
}
else
{
error = WriteLastStatusFrame(aHeader, ThreadErrorToSpinelStatus(error));
}
return error;
}
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_RCP_MAC_KEY>(void)
{
otError error = OT_ERROR_NONE;
uint8_t keyIdMode;
uint8_t keyId;
uint16_t keySize;
const uint8_t *prevKey;
const uint8_t *currKey;
const uint8_t *nextKey;
SuccessOrExit(error = mDecoder.ReadUint8(keyIdMode));
VerifyOrExit(keyIdMode == Mac::Frame::kKeyIdMode1, error = OT_ERROR_INVALID_ARGS);
SuccessOrExit(error = mDecoder.ReadUint8(keyId));
SuccessOrExit(error = mDecoder.ReadDataWithLen(prevKey, keySize));
VerifyOrExit(keySize == sizeof(otMacKey), error = OT_ERROR_INVALID_ARGS);
SuccessOrExit(error = mDecoder.ReadDataWithLen(currKey, keySize));
VerifyOrExit(keySize == sizeof(otMacKey), error = OT_ERROR_INVALID_ARGS);
SuccessOrExit(error = mDecoder.ReadDataWithLen(nextKey, keySize));
VerifyOrExit(keySize == sizeof(otMacKey), error = OT_ERROR_INVALID_ARGS);
error =
otLinkRawSetMacKey(mInstance, keyIdMode, keyId, reinterpret_cast<const otMacKey *>(prevKey),
reinterpret_cast<const otMacKey *>(currKey), reinterpret_cast<const otMacKey *>(nextKey));
exit:
return error;
}
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_RCP_MAC_FRAME_COUNTER>(void)
{
otError error = OT_ERROR_NONE;
uint32_t frameCounter;
SuccessOrExit(error = mDecoder.ReadUint32(frameCounter));
error = otLinkRawSetMacFrameCounter(mInstance, frameCounter);
exit:
return error;
}
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_RCP_ENH_ACK_PROBING>(void)
{
otError error = OT_ERROR_NONE;
uint16_t shortAddress;
const otExtAddress *extAddress;
otLinkMetrics linkMetrics = {};
SuccessOrExit(error = mDecoder.ReadUint16(shortAddress));
SuccessOrExit(error = mDecoder.ReadEui64(extAddress));
SuccessOrExit(error = DecodeLinkMetrics(&linkMetrics, /* aAllowPduCount */ true));
error = otPlatRadioConfigureEnhAckProbing(mInstance, linkMetrics, shortAddress, extAddress);
exit:
return error;
}
#endif
} // namespace Ncp
} // namespace ot
#endif // OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE