blob: 30a77d5b4a15ed717e36af340c7311d4a095d55b [file] [log] [blame]
/*
* Copyright (c) 2016, 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 the subset of IEEE 802.15.4 primitives required for Thread.
*/
#include "mac.hpp"
#include <stdio.h>
#include "common/code_utils.hpp"
#include "common/debug.hpp"
#include "common/encoding.hpp"
#include "common/instance.hpp"
#include "common/locator-getters.hpp"
#include "common/logging.hpp"
#include "common/random.hpp"
#include "crypto/aes_ccm.hpp"
#include "crypto/sha256.hpp"
#include "mac/mac_frame.hpp"
#include "radio/radio.hpp"
#include "thread/link_quality.hpp"
#include "thread/mle_router.hpp"
#include "thread/thread_netif.hpp"
#include "thread/topology.hpp"
namespace ot {
namespace Mac {
const otMacKey Mac::sMode2Key = {
{0x78, 0x58, 0x16, 0x86, 0xfd, 0xb4, 0x58, 0x0f, 0xb0, 0x92, 0x54, 0x6a, 0xec, 0xbd, 0x15, 0x66}};
const otExtAddress Mac::sMode2ExtAddress = {
{0x35, 0x06, 0xfe, 0xb8, 0x23, 0xd4, 0x87, 0x12},
};
const otExtendedPanId Mac::sExtendedPanidInit = {
{0xde, 0xad, 0x00, 0xbe, 0xef, 0x00, 0xca, 0xfe},
};
const char Mac::sNetworkNameInit[] = "OpenThread";
#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
const char Mac::sDomainNameInit[] = "Thread";
#endif
Mac::Mac(Instance &aInstance)
: InstanceLocator(aInstance)
, mEnabled(false)
, mPendingActiveScan(false)
, mPendingEnergyScan(false)
, mPendingTransmitBeacon(false)
, mPendingTransmitDataDirect(false)
#if OPENTHREAD_FTD
, mPendingTransmitDataIndirect(false)
#endif
, mPendingTransmitPoll(false)
, mPendingTransmitOobFrame(false)
, mPendingWaitingForData(false)
, mShouldTxPollBeforeData(false)
, mRxOnWhenIdle(false)
, mPromiscuous(false)
, mBeaconsEnabled(false)
, mUsingTemporaryChannel(false)
#if OPENTHREAD_CONFIG_MAC_STAY_AWAKE_BETWEEN_FRAGMENTS
, mShouldDelaySleep(false)
, mDelayingSleep(false)
#endif
, mOperation(kOperationIdle)
, mBeaconSequence(Random::NonCrypto::GetUint8())
, mDataSequence(Random::NonCrypto::GetUint8())
, mBroadcastTransmitCount(0)
, mPanId(kPanIdBroadcast)
, mPanChannel(OPENTHREAD_CONFIG_DEFAULT_CHANNEL)
, mRadioChannel(OPENTHREAD_CONFIG_DEFAULT_CHANNEL)
, mSupportedChannelMask(Get<Radio>().GetSupportedChannelMask())
, mNetworkName()
#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
, mDomainName()
#endif
, mScanChannel(Radio::kChannelMin)
, mScanDuration(0)
, mScanChannelMask()
, mMaxFrameRetriesDirect(kDefaultMaxFrameRetriesDirect)
#if OPENTHREAD_FTD
, mMaxFrameRetriesIndirect(kDefaultMaxFrameRetriesIndirect)
#endif
, mActiveScanHandler(nullptr) // Initialize `mActiveScanHandler` and `mEnergyScanHandler` union
, mScanHandlerContext(nullptr)
, mSubMac(aInstance)
, mOperationTask(aInstance, Mac::HandleOperationTask, this)
, mTimer(aInstance, Mac::HandleTimer, this)
, mOobFrame(nullptr)
, mKeyIdMode2FrameCounter(0)
, mCcaSampleCount(0)
#if OPENTHREAD_CONFIG_MAC_FILTER_ENABLE
, mFilter()
#endif
{
ExtAddress randomExtAddress;
randomExtAddress.GenerateRandom();
mCcaSuccessRateTracker.Reset();
ResetCounters();
mExtendedPanId.Clear();
SetEnabled(true);
IgnoreError(mSubMac.Enable());
Get<KeyManager>().UpdateKeyMaterial();
SetExtendedPanId(static_cast<const ExtendedPanId &>(sExtendedPanidInit));
IgnoreError(SetNetworkName(sNetworkNameInit));
#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
IgnoreError(SetDomainName(sDomainNameInit));
#endif
SetPanId(mPanId);
SetExtAddress(randomExtAddress);
SetShortAddress(GetShortAddress());
}
otError Mac::ActiveScan(uint32_t aScanChannels, uint16_t aScanDuration, ActiveScanHandler aHandler, void *aContext)
{
otError error = OT_ERROR_NONE;
VerifyOrExit(IsEnabled(), error = OT_ERROR_INVALID_STATE);
VerifyOrExit(!IsActiveScanInProgress() && !IsEnergyScanInProgress(), error = OT_ERROR_BUSY);
mActiveScanHandler = aHandler;
mScanHandlerContext = aContext;
if (aScanDuration == 0)
{
aScanDuration = kScanDurationDefault;
}
Scan(kOperationActiveScan, aScanChannels, aScanDuration);
exit:
return error;
}
otError Mac::EnergyScan(uint32_t aScanChannels, uint16_t aScanDuration, EnergyScanHandler aHandler, void *aContext)
{
otError error = OT_ERROR_NONE;
VerifyOrExit(IsEnabled(), error = OT_ERROR_INVALID_STATE);
VerifyOrExit(!IsActiveScanInProgress() && !IsEnergyScanInProgress(), error = OT_ERROR_BUSY);
mEnergyScanHandler = aHandler;
mScanHandlerContext = aContext;
Scan(kOperationEnergyScan, aScanChannels, aScanDuration);
exit:
return error;
}
void Mac::Scan(Operation aScanOperation, uint32_t aScanChannels, uint16_t aScanDuration)
{
mScanDuration = aScanDuration;
mScanChannel = ChannelMask::kChannelIteratorFirst;
if (aScanChannels == 0)
{
aScanChannels = GetSupportedChannelMask().GetMask();
}
mScanChannelMask.SetMask(aScanChannels);
mScanChannelMask.Intersect(mSupportedChannelMask);
StartOperation(aScanOperation);
}
bool Mac::IsInTransmitState(void) const
{
bool retval = false;
switch (mOperation)
{
case kOperationTransmitDataDirect:
#if OPENTHREAD_FTD
case kOperationTransmitDataIndirect:
#endif
case kOperationTransmitBeacon:
case kOperationTransmitPoll:
case kOperationTransmitOutOfBandFrame:
retval = true;
break;
case kOperationIdle:
case kOperationActiveScan:
case kOperationEnergyScan:
case kOperationWaitingForData:
retval = false;
break;
}
return retval;
}
otError Mac::ConvertBeaconToActiveScanResult(const RxFrame *aBeaconFrame, ActiveScanResult &aResult)
{
otError error = OT_ERROR_NONE;
Address address;
const Beacon * beacon = nullptr;
const BeaconPayload *beaconPayload = nullptr;
uint16_t payloadLength;
memset(&aResult, 0, sizeof(ActiveScanResult));
VerifyOrExit(aBeaconFrame != nullptr, error = OT_ERROR_INVALID_ARGS);
VerifyOrExit(aBeaconFrame->GetType() == Frame::kFcfFrameBeacon, error = OT_ERROR_PARSE);
SuccessOrExit(error = aBeaconFrame->GetSrcAddr(address));
VerifyOrExit(address.IsExtended(), error = OT_ERROR_PARSE);
aResult.mExtAddress = address.GetExtended();
IgnoreError(aBeaconFrame->GetSrcPanId(aResult.mPanId));
aResult.mChannel = aBeaconFrame->GetChannel();
aResult.mRssi = aBeaconFrame->GetRssi();
aResult.mLqi = aBeaconFrame->GetLqi();
payloadLength = aBeaconFrame->GetPayloadLength();
beacon = reinterpret_cast<const Beacon *>(aBeaconFrame->GetPayload());
beaconPayload = reinterpret_cast<const BeaconPayload *>(beacon->GetPayload());
if ((payloadLength >= (sizeof(*beacon) + sizeof(*beaconPayload))) && beacon->IsValid() && beaconPayload->IsValid())
{
aResult.mVersion = beaconPayload->GetProtocolVersion();
aResult.mIsJoinable = beaconPayload->IsJoiningPermitted();
aResult.mIsNative = beaconPayload->IsNative();
IgnoreError(static_cast<NetworkName &>(aResult.mNetworkName).Set(beaconPayload->GetNetworkName()));
aResult.mExtendedPanId = beaconPayload->GetExtendedPanId();
}
LogBeacon("Received", *beaconPayload);
exit:
return error;
}
otError Mac::UpdateScanChannel(void)
{
otError error;
VerifyOrExit(IsEnabled(), error = OT_ERROR_ABORT);
error = mScanChannelMask.GetNextChannel(mScanChannel);
exit:
return error;
}
void Mac::PerformActiveScan(void)
{
if (UpdateScanChannel() == OT_ERROR_NONE)
{
// If there are more channels to scan, send the beacon request.
BeginTransmit();
}
else
{
mSubMac.SetPanId(mPanId);
FinishOperation();
ReportActiveScanResult(nullptr);
PerformNextOperation();
}
}
void Mac::ReportActiveScanResult(const RxFrame *aBeaconFrame)
{
VerifyOrExit(mActiveScanHandler != nullptr, OT_NOOP);
if (aBeaconFrame == nullptr)
{
mActiveScanHandler(nullptr, mScanHandlerContext);
}
else
{
ActiveScanResult result;
SuccessOrExit(ConvertBeaconToActiveScanResult(aBeaconFrame, result));
mActiveScanHandler(&result, mScanHandlerContext);
}
exit:
return;
}
void Mac::PerformEnergyScan(void)
{
otError error = OT_ERROR_NONE;
SuccessOrExit(error = UpdateScanChannel());
if (mScanDuration == 0)
{
while (true)
{
IgnoreError(mSubMac.Receive(mScanChannel));
ReportEnergyScanResult(mSubMac.GetRssi());
SuccessOrExit(error = UpdateScanChannel());
}
}
else
{
error = mSubMac.EnergyScan(mScanChannel, mScanDuration);
}
exit:
if (error != OT_ERROR_NONE)
{
FinishOperation();
if (mEnergyScanHandler != nullptr)
{
mEnergyScanHandler(nullptr, mScanHandlerContext);
}
PerformNextOperation();
}
}
void Mac::ReportEnergyScanResult(int8_t aRssi)
{
EnergyScanResult result;
VerifyOrExit((mEnergyScanHandler != nullptr) && (aRssi != kInvalidRssiValue), OT_NOOP);
result.mChannel = mScanChannel;
result.mMaxRssi = aRssi;
mEnergyScanHandler(&result, mScanHandlerContext);
exit:
return;
}
void Mac::EnergyScanDone(int8_t aEnergyScanMaxRssi)
{
ReportEnergyScanResult(aEnergyScanMaxRssi);
PerformEnergyScan();
}
void Mac::SetRxOnWhenIdle(bool aRxOnWhenIdle)
{
VerifyOrExit(mRxOnWhenIdle != aRxOnWhenIdle, OT_NOOP);
mRxOnWhenIdle = aRxOnWhenIdle;
// If the new value for `mRxOnWhenIdle` is `true` (i.e., radio should
// remain in Rx while idle) we stop any ongoing or pending `WaitingForData`
// operation (since this operation only applies to sleepy devices).
if (mRxOnWhenIdle)
{
if (mPendingWaitingForData)
{
mTimer.Stop();
mPendingWaitingForData = false;
}
if (mOperation == kOperationWaitingForData)
{
mTimer.Stop();
FinishOperation();
mOperationTask.Post();
}
#if OPENTHREAD_CONFIG_MAC_STAY_AWAKE_BETWEEN_FRAGMENTS
mDelayingSleep = false;
mShouldDelaySleep = false;
#endif
}
mSubMac.SetRxOnWhenBackoff(mRxOnWhenIdle || mPromiscuous);
UpdateIdleMode();
exit:
return;
}
otError Mac::SetPanChannel(uint8_t aChannel)
{
otError error = OT_ERROR_NONE;
VerifyOrExit(mSupportedChannelMask.ContainsChannel(aChannel), error = OT_ERROR_INVALID_ARGS);
SuccessOrExit(Get<Notifier>().Update(mPanChannel, aChannel, kEventThreadChannelChanged));
mCcaSuccessRateTracker.Reset();
VerifyOrExit(!mUsingTemporaryChannel, OT_NOOP);
mRadioChannel = mPanChannel;
UpdateIdleMode();
exit:
return error;
}
otError Mac::SetTemporaryChannel(uint8_t aChannel)
{
otError error = OT_ERROR_NONE;
VerifyOrExit(mSupportedChannelMask.ContainsChannel(aChannel), error = OT_ERROR_INVALID_ARGS);
mUsingTemporaryChannel = true;
mRadioChannel = aChannel;
UpdateIdleMode();
exit:
return error;
}
void Mac::ClearTemporaryChannel(void)
{
if (mUsingTemporaryChannel)
{
mUsingTemporaryChannel = false;
mRadioChannel = mPanChannel;
UpdateIdleMode();
}
}
void Mac::SetSupportedChannelMask(const ChannelMask &aMask)
{
ChannelMask newMask = aMask;
newMask.Intersect(ChannelMask(Get<Radio>().GetSupportedChannelMask()));
IgnoreError(Get<Notifier>().Update(mSupportedChannelMask, newMask, kEventSupportedChannelMaskChanged));
}
otError Mac::SetNetworkName(const char *aNameString)
{
// When setting Network Name from a string, we treat it as `NameData`
// with `kMaxSize + 1` chars. `NetworkName::Set(data)` will look
// for null char in the data (within its given size) to calculate
// the name's length and ensure that the name fits in `kMaxSize`
// chars. The `+ 1` ensures that a `aNameString` with length
// longer than `kMaxSize` is correctly rejected (returning error
// `OT_ERROR_INVALID_ARGS`).
NameData data(aNameString, NetworkName::kMaxSize + 1);
return SetNetworkName(data);
}
otError Mac::SetNetworkName(const NameData &aNameData)
{
otError error = mNetworkName.Set(aNameData);
if (error == OT_ERROR_ALREADY)
{
Get<Notifier>().SignalIfFirst(kEventThreadNetworkNameChanged);
error = OT_ERROR_NONE;
ExitNow();
}
SuccessOrExit(error);
Get<Notifier>().Signal(kEventThreadNetworkNameChanged);
exit:
return error;
}
#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
otError Mac::SetDomainName(const char *aNameString)
{
// When setting Domain Name from a string, we treat it as `NameData`
// with `kMaxSize + 1` chars. `DomainName::Set(data)` will look
// for null char in the data (within its given size) to calculate
// the name's length and ensure that the name fits in `kMaxSize`
// chars. The `+ 1` ensures that a `aNameString` with length
// longer than `kMaxSize` is correctly rejected (returning error
// `OT_ERROR_INVALID_ARGS`).
NameData data(aNameString, DomainName::kMaxSize + 1);
return SetDomainName(data);
}
otError Mac::SetDomainName(const NameData &aNameData)
{
otError error = mDomainName.Set(aNameData);
if (error == OT_ERROR_ALREADY)
{
error = OT_ERROR_NONE;
}
return error;
}
#endif // (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
void Mac::SetPanId(PanId aPanId)
{
SuccessOrExit(Get<Notifier>().Update(mPanId, aPanId, kEventThreadPanIdChanged));
mSubMac.SetPanId(mPanId);
exit:
return;
}
void Mac::SetExtendedPanId(const ExtendedPanId &aExtendedPanId)
{
IgnoreError(Get<Notifier>().Update(mExtendedPanId, aExtendedPanId, kEventThreadExtPanIdChanged));
}
void Mac::RequestDirectFrameTransmission(void)
{
VerifyOrExit(IsEnabled(), OT_NOOP);
VerifyOrExit(!mPendingTransmitDataDirect && (mOperation != kOperationTransmitDataDirect), OT_NOOP);
StartOperation(kOperationTransmitDataDirect);
exit:
return;
}
#if OPENTHREAD_FTD
void Mac::RequestIndirectFrameTransmission(void)
{
VerifyOrExit(IsEnabled(), OT_NOOP);
VerifyOrExit(!mPendingTransmitDataIndirect && (mOperation != kOperationTransmitDataIndirect), OT_NOOP);
StartOperation(kOperationTransmitDataIndirect);
exit:
return;
}
#endif
otError Mac::RequestOutOfBandFrameTransmission(otRadioFrame *aOobFrame)
{
otError error = OT_ERROR_NONE;
VerifyOrExit(aOobFrame != nullptr, error = OT_ERROR_INVALID_ARGS);
VerifyOrExit(IsEnabled(), error = OT_ERROR_INVALID_STATE);
VerifyOrExit(!mPendingTransmitOobFrame && (mOperation != kOperationTransmitOutOfBandFrame),
error = OT_ERROR_ALREADY);
mOobFrame = static_cast<TxFrame *>(aOobFrame);
StartOperation(kOperationTransmitOutOfBandFrame);
exit:
return error;
}
otError Mac::RequestDataPollTransmission(void)
{
otError error = OT_ERROR_NONE;
VerifyOrExit(IsEnabled(), error = OT_ERROR_INVALID_STATE);
VerifyOrExit(!mPendingTransmitPoll && (mOperation != kOperationTransmitPoll), error = OT_ERROR_ALREADY);
// We ensure data frame and data poll tx requests are handled in the
// order they are requested. So if we have a pending direct data frame
// tx request, it should be sent before the poll frame.
mShouldTxPollBeforeData = !mPendingTransmitDataDirect;
StartOperation(kOperationTransmitPoll);
exit:
return error;
}
void Mac::UpdateIdleMode(void)
{
bool shouldSleep = !mRxOnWhenIdle && !mPromiscuous;
VerifyOrExit(mOperation == kOperationIdle, OT_NOOP);
#if OPENTHREAD_CONFIG_MAC_STAY_AWAKE_BETWEEN_FRAGMENTS
if (mShouldDelaySleep)
{
mTimer.Start(kSleepDelay);
mShouldDelaySleep = false;
mDelayingSleep = true;
otLogDebgMac("Idle mode: Sleep delayed");
}
if (mDelayingSleep)
{
shouldSleep = false;
}
#endif
if (shouldSleep)
{
IgnoreError(mSubMac.Sleep());
otLogDebgMac("Idle mode: Radio sleeping");
}
else
{
IgnoreError(mSubMac.Receive(mRadioChannel));
otLogDebgMac("Idle mode: Radio receiving on channel %d", mRadioChannel);
}
exit:
return;
}
void Mac::StartOperation(Operation aOperation)
{
if (aOperation != kOperationIdle)
{
otLogDebgMac("Request to start operation \"%s\"", OperationToString(aOperation));
#if OPENTHREAD_CONFIG_MAC_STAY_AWAKE_BETWEEN_FRAGMENTS
if (mDelayingSleep)
{
otLogDebgMac("Canceling sleep delay");
mTimer.Stop();
mDelayingSleep = false;
mShouldDelaySleep = false;
}
#endif
}
switch (aOperation)
{
case kOperationIdle:
break;
case kOperationActiveScan:
mPendingActiveScan = true;
break;
case kOperationEnergyScan:
mPendingEnergyScan = true;
break;
case kOperationTransmitBeacon:
mPendingTransmitBeacon = true;
break;
case kOperationTransmitDataDirect:
mPendingTransmitDataDirect = true;
break;
#if OPENTHREAD_FTD
case kOperationTransmitDataIndirect:
mPendingTransmitDataIndirect = true;
break;
#endif
case kOperationTransmitPoll:
mPendingTransmitPoll = true;
break;
case kOperationWaitingForData:
mPendingWaitingForData = true;
break;
case kOperationTransmitOutOfBandFrame:
mPendingTransmitOobFrame = true;
break;
}
if (mOperation == kOperationIdle)
{
mOperationTask.Post();
}
}
void Mac::HandleOperationTask(Tasklet &aTasklet)
{
aTasklet.GetOwner<Mac>().PerformNextOperation();
}
void Mac::PerformNextOperation(void)
{
VerifyOrExit(mOperation == kOperationIdle, OT_NOOP);
if (!IsEnabled())
{
mPendingWaitingForData = false;
mPendingTransmitOobFrame = false;
mPendingActiveScan = false;
mPendingEnergyScan = false;
mPendingTransmitBeacon = false;
mPendingTransmitDataDirect = false;
#if OPENTHREAD_FTD
mPendingTransmitDataIndirect = false;
#endif
mPendingTransmitPoll = false;
mTimer.Stop();
#if OPENTHREAD_CONFIG_MAC_STAY_AWAKE_BETWEEN_FRAGMENTS
mDelayingSleep = false;
mShouldDelaySleep = false;
#endif
ExitNow();
}
// `WaitingForData` should be checked before any other pending
// operations since radio should remain in receive mode after
// a data poll ack indicating a pending frame from parent.
if (mPendingWaitingForData)
{
mPendingWaitingForData = false;
mOperation = kOperationWaitingForData;
}
else if (mPendingTransmitOobFrame)
{
mPendingTransmitOobFrame = false;
mOperation = kOperationTransmitOutOfBandFrame;
}
else if (mPendingActiveScan)
{
mPendingActiveScan = false;
mOperation = kOperationActiveScan;
}
else if (mPendingEnergyScan)
{
mPendingEnergyScan = false;
mOperation = kOperationEnergyScan;
}
else if (mPendingTransmitBeacon)
{
mPendingTransmitBeacon = false;
mOperation = kOperationTransmitBeacon;
}
#if OPENTHREAD_FTD
else if (mPendingTransmitDataIndirect)
{
mPendingTransmitDataIndirect = false;
mOperation = kOperationTransmitDataIndirect;
}
#endif
else if (mPendingTransmitPoll && (!mPendingTransmitDataDirect || mShouldTxPollBeforeData))
{
mPendingTransmitPoll = false;
mOperation = kOperationTransmitPoll;
}
else if (mPendingTransmitDataDirect)
{
mPendingTransmitDataDirect = false;
mOperation = kOperationTransmitDataDirect;
if (mPendingTransmitPoll)
{
// Ensure that a pending "transmit poll" operation request
// is prioritized over any future "transmit data" requests.
mShouldTxPollBeforeData = true;
}
}
if (mOperation != kOperationIdle)
{
otLogDebgMac("Starting operation \"%s\"", OperationToString(mOperation));
}
switch (mOperation)
{
case kOperationIdle:
UpdateIdleMode();
break;
case kOperationActiveScan:
PerformActiveScan();
break;
case kOperationEnergyScan:
PerformEnergyScan();
break;
case kOperationTransmitBeacon:
case kOperationTransmitDataDirect:
#if OPENTHREAD_FTD
case kOperationTransmitDataIndirect:
#endif
case kOperationTransmitPoll:
case kOperationTransmitOutOfBandFrame:
BeginTransmit();
break;
case kOperationWaitingForData:
IgnoreError(mSubMac.Receive(mRadioChannel));
break;
}
exit:
return;
}
void Mac::FinishOperation(void)
{
otLogDebgMac("Finishing operation \"%s\"", OperationToString(mOperation));
mOperation = kOperationIdle;
}
otError Mac::PrepareDataRequest(TxFrame &aFrame)
{
otError error = OT_ERROR_NONE;
Address src, dst;
uint16_t fcf;
SuccessOrExit(error = Get<DataPollSender>().GetPollDestinationAddress(dst));
VerifyOrExit(!dst.IsNone(), error = OT_ERROR_ABORT);
fcf = Frame::kFcfFrameMacCmd | Frame::kFcfPanidCompression | Frame::kFcfAckRequest | Frame::kFcfSecurityEnabled;
UpdateFrameControlField(/* aIsTimeSync */ false, fcf);
if (dst.IsExtended())
{
fcf |= Frame::kFcfDstAddrExt | Frame::kFcfSrcAddrExt;
src.SetExtended(GetExtAddress());
}
else
{
fcf |= Frame::kFcfDstAddrShort | Frame::kFcfSrcAddrShort;
src.SetShort(GetShortAddress());
}
aFrame.InitMacHeader(fcf, Frame::kKeyIdMode1 | Frame::kSecEncMic32);
aFrame.SetDstPanId(GetPanId());
aFrame.SetSrcAddr(src);
aFrame.SetDstAddr(dst);
IgnoreError(aFrame.SetCommandId(Frame::kMacCmdDataRequest));
exit:
return error;
}
void Mac::PrepareBeaconRequest(TxFrame &aFrame)
{
uint16_t fcf = Frame::kFcfFrameMacCmd | Frame::kFcfDstAddrShort | Frame::kFcfSrcAddrNone;
aFrame.InitMacHeader(fcf, Frame::kSecNone);
aFrame.SetDstPanId(kShortAddrBroadcast);
aFrame.SetDstAddr(kShortAddrBroadcast);
IgnoreError(aFrame.SetCommandId(Frame::kMacCmdBeaconRequest));
otLogInfoMac("Sending Beacon Request");
}
void Mac::PrepareBeacon(TxFrame &aFrame)
{
uint8_t beaconLength;
uint16_t fcf;
Beacon * beacon = nullptr;
BeaconPayload *beaconPayload = nullptr;
fcf = Frame::kFcfFrameBeacon | Frame::kFcfDstAddrNone | Frame::kFcfSrcAddrExt;
aFrame.InitMacHeader(fcf, Frame::kSecNone);
IgnoreError(aFrame.SetSrcPanId(mPanId));
aFrame.SetSrcAddr(GetExtAddress());
beacon = reinterpret_cast<Beacon *>(aFrame.GetPayload());
beacon->Init();
beaconLength = sizeof(*beacon);
beaconPayload = reinterpret_cast<BeaconPayload *>(beacon->GetPayload());
if (Get<KeyManager>().IsThreadBeaconEnabled())
{
beaconPayload->Init();
if (IsJoinable())
{
beaconPayload->SetJoiningPermitted();
}
else
{
beaconPayload->ClearJoiningPermitted();
}
beaconPayload->SetNetworkName(mNetworkName.GetAsData());
beaconPayload->SetExtendedPanId(mExtendedPanId);
beaconLength += sizeof(*beaconPayload);
}
aFrame.SetPayloadLength(beaconLength);
LogBeacon("Sending", *beaconPayload);
}
bool Mac::ShouldSendBeacon(void) const
{
bool shouldSend = false;
VerifyOrExit(IsEnabled(), OT_NOOP);
shouldSend = IsBeaconEnabled();
#if OPENTHREAD_CONFIG_MAC_BEACON_RSP_WHEN_JOINABLE_ENABLE
if (!shouldSend)
{
// When `ENABLE_BEACON_RSP_WHEN_JOINABLE` feature is enabled,
// the device should transmit IEEE 802.15.4 Beacons in response
// to IEEE 802.15.4 Beacon Requests even while the device is not
// router capable and detached (i.e., `IsBeaconEnabled()` is
// false) but only if it is in joinable state (unsecure port
// list is not empty).
shouldSend = IsJoinable();
}
#endif
exit:
return shouldSend;
}
bool Mac::IsJoinable(void) const
{
uint8_t numUnsecurePorts;
Get<Ip6::Filter>().GetUnsecurePorts(numUnsecurePorts);
return (numUnsecurePorts != 0);
}
void Mac::ProcessTransmitSecurity(TxFrame &aFrame)
{
KeyManager & keyManager = Get<KeyManager>();
uint8_t keyIdMode;
const ExtAddress *extAddress = nullptr;
VerifyOrExit(aFrame.GetSecurityEnabled(), OT_NOOP);
IgnoreError(aFrame.GetKeyIdMode(keyIdMode));
switch (keyIdMode)
{
case Frame::kKeyIdMode0:
aFrame.SetAesKey(keyManager.GetKek());
extAddress = &GetExtAddress();
if (!aFrame.IsARetransmission())
{
aFrame.SetFrameCounter(keyManager.GetKekFrameCounter());
keyManager.IncrementKekFrameCounter();
}
break;
case Frame::kKeyIdMode1:
// For MAC Key ID Mode 1, the security frame counter update and CCM* is done at SubMac or Radio depending on
// `OT_RADIO_CAPS_TRANSMIT_SEC`.
ExitNow();
break;
case Frame::kKeyIdMode2:
{
const uint8_t keySource[] = {0xff, 0xff, 0xff, 0xff};
aFrame.SetAesKey(static_cast<const Key &>(sMode2Key));
mKeyIdMode2FrameCounter++;
aFrame.SetFrameCounter(mKeyIdMode2FrameCounter);
aFrame.SetKeySource(keySource);
aFrame.SetKeyId(0xff);
extAddress = static_cast<const ExtAddress *>(&sMode2ExtAddress);
break;
}
default:
OT_ASSERT(false);
OT_UNREACHABLE_CODE(break);
}
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
// Transmit security will be processed after time IE content is updated.
VerifyOrExit(aFrame.GetTimeIeOffset() == 0, OT_NOOP);
#endif
aFrame.ProcessTransmitAesCcm(*extAddress);
exit:
return;
}
void Mac::BeginTransmit(void)
{
otError error = OT_ERROR_NONE;
TxFrame &sendFrame = mSubMac.GetTransmitFrame();
VerifyOrExit(IsEnabled(), error = OT_ERROR_ABORT);
sendFrame.SetIsARetransmission(false);
sendFrame.SetIsSecurityProcessed(false);
switch (mOperation)
{
case kOperationActiveScan:
mSubMac.SetPanId(kPanIdBroadcast);
sendFrame.SetChannel(mScanChannel);
PrepareBeaconRequest(sendFrame);
sendFrame.SetSequence(0);
sendFrame.SetMaxCsmaBackoffs(kMaxCsmaBackoffsDirect);
sendFrame.SetMaxFrameRetries(mMaxFrameRetriesDirect);
break;
case kOperationTransmitBeacon:
sendFrame.SetChannel(mRadioChannel);
PrepareBeacon(sendFrame);
sendFrame.SetSequence(mBeaconSequence++);
sendFrame.SetMaxCsmaBackoffs(kMaxCsmaBackoffsDirect);
sendFrame.SetMaxFrameRetries(mMaxFrameRetriesDirect);
break;
case kOperationTransmitPoll:
sendFrame.SetChannel(mRadioChannel);
SuccessOrExit(error = PrepareDataRequest(sendFrame));
sendFrame.SetSequence(mDataSequence++);
sendFrame.SetMaxCsmaBackoffs(kMaxCsmaBackoffsDirect);
sendFrame.SetMaxFrameRetries(mMaxFrameRetriesDirect);
break;
case kOperationTransmitDataDirect:
sendFrame.SetChannel(mRadioChannel);
sendFrame.SetMaxCsmaBackoffs(kMaxCsmaBackoffsDirect);
sendFrame.SetMaxFrameRetries(mMaxFrameRetriesDirect);
SuccessOrExit(error = Get<MeshForwarder>().HandleFrameRequest(sendFrame));
sendFrame.SetSequence(mDataSequence++);
break;
#if OPENTHREAD_FTD
case kOperationTransmitDataIndirect:
sendFrame.SetChannel(mRadioChannel);
sendFrame.SetMaxCsmaBackoffs(kMaxCsmaBackoffsIndirect);
sendFrame.SetMaxFrameRetries(mMaxFrameRetriesIndirect);
SuccessOrExit(error = Get<DataPollHandler>().HandleFrameRequest(sendFrame));
// If the frame is marked as a retransmission, then data sequence number is already set.
if (!sendFrame.IsARetransmission())
{
sendFrame.SetSequence(mDataSequence++);
}
break;
#endif
case kOperationTransmitOutOfBandFrame:
sendFrame.CopyFrom(*mOobFrame);
sendFrame.SetIsSecurityProcessed(true);
break;
default:
OT_ASSERT(false);
OT_UNREACHABLE_CODE(break);
}
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
{
uint8_t timeIeOffset = GetTimeIeOffset(sendFrame);
sendFrame.SetTimeIeOffset(timeIeOffset);
if (timeIeOffset != 0)
{
sendFrame.SetTimeSyncSeq(Get<TimeSync>().GetTimeSyncSeq());
sendFrame.SetNetworkTimeOffset(Get<TimeSync>().GetNetworkTimeOffset());
}
}
#endif
if (!sendFrame.IsSecurityProcessed())
{
ProcessTransmitSecurity(sendFrame);
}
mBroadcastTransmitCount = 0;
#if OPENTHREAD_CONFIG_MAC_STAY_AWAKE_BETWEEN_FRAGMENTS
if (!mRxOnWhenIdle && !mPromiscuous)
{
mShouldDelaySleep = sendFrame.GetFramePending();
otLogDebgMac("Delay sleep for pending tx");
}
#endif
error = mSubMac.Send();
exit:
if (error != OT_ERROR_NONE)
{
// If the sendFrame could not be prepared and the tx is being
// aborted, we set the frame length to zero to mark it as empty.
// The empty frame helps differentiate between an aborted tx due
// to OpenThread itself not being able to prepare the frame, versus
// the radio platform aborting the tx operation.
sendFrame.SetLength(0);
HandleTransmitDone(sendFrame, nullptr, OT_ERROR_ABORT);
}
}
void Mac::RecordCcaStatus(bool aCcaSuccess, uint8_t aChannel)
{
if (!aCcaSuccess)
{
mCounters.mTxErrCca++;
}
// Only track the CCA success rate for frame transmissions
// on the PAN channel.
if (aChannel == mPanChannel)
{
if (mCcaSampleCount < kMaxCcaSampleCount)
{
mCcaSampleCount++;
}
mCcaSuccessRateTracker.AddSample(aCcaSuccess, mCcaSampleCount);
}
}
void Mac::RecordFrameTransmitStatus(const TxFrame &aFrame,
const RxFrame *aAckFrame,
otError aError,
uint8_t aRetryCount,
bool aWillRetx)
{
bool ackRequested = aFrame.GetAckRequest();
Address dstAddr;
Neighbor *neighbor;
VerifyOrExit(!aFrame.IsEmpty(), OT_NOOP);
IgnoreError(aFrame.GetDstAddr(dstAddr));
neighbor = Get<Mle::MleRouter>().GetNeighbor(dstAddr);
// Record frame transmission success/failure state (for a neighbor).
if ((neighbor != nullptr) && ackRequested)
{
bool frameTxSuccess = true;
// CCA or abort errors are excluded from frame tx error
// rate tracking, since when they occur, the frame is
// not actually sent over the air.
switch (aError)
{
case OT_ERROR_NO_ACK:
frameTxSuccess = false;
// Fall through
case OT_ERROR_NONE:
neighbor->GetLinkInfo().AddFrameTxStatus(frameTxSuccess);
break;
default:
break;
}
}
// Log frame transmission failure.
if (aError != OT_ERROR_NONE)
{
LogFrameTxFailure(aFrame, aError, aRetryCount, aWillRetx);
otDumpDebgMac("TX ERR", aFrame.GetHeader(), 16);
if (aWillRetx)
{
mCounters.mTxRetry++;
// Since this failed transmission will be retried by `SubMac` layer
// there is no need to update any other MAC counter. MAC counters
// are updated on the final transmission attempt.
ExitNow();
}
}
// Update neighbor's RSSI link info from the received Ack.
if ((aError == OT_ERROR_NONE) && ackRequested && (aAckFrame != nullptr) && (neighbor != nullptr))
{
neighbor->GetLinkInfo().AddRss(aAckFrame->GetRssi());
}
// Update MAC counters.
mCounters.mTxTotal++;
if (aError == OT_ERROR_ABORT)
{
mCounters.mTxErrAbort++;
}
if (aError == OT_ERROR_CHANNEL_ACCESS_FAILURE)
{
mCounters.mTxErrBusyChannel++;
}
if (ackRequested)
{
mCounters.mTxAckRequested++;
if (aError == OT_ERROR_NONE)
{
mCounters.mTxAcked++;
}
}
else
{
mCounters.mTxNoAckRequested++;
}
if (dstAddr.IsBroadcast())
{
mCounters.mTxBroadcast++;
}
else
{
mCounters.mTxUnicast++;
}
exit:
return;
}
void Mac::HandleTransmitDone(TxFrame &aFrame, RxFrame *aAckFrame, otError aError)
{
if (!aFrame.IsEmpty())
{
Address dstAddr;
// Determine whether to re-transmit a broadcast frame.
IgnoreError(aFrame.GetDstAddr(dstAddr));
if (dstAddr.IsBroadcast())
{
mBroadcastTransmitCount++;
if (mBroadcastTransmitCount < kTxNumBcast)
{
IgnoreError(mSubMac.Send());
ExitNow();
}
mBroadcastTransmitCount = 0;
}
}
// Determine next action based on current operation.
switch (mOperation)
{
case kOperationActiveScan:
mCounters.mTxBeaconRequest++;
mTimer.Start(mScanDuration);
break;
case kOperationTransmitBeacon:
mCounters.mTxBeacon++;
FinishOperation();
PerformNextOperation();
break;
case kOperationTransmitPoll:
OT_ASSERT(aFrame.IsEmpty() || aFrame.GetAckRequest());
if ((aError == OT_ERROR_NONE) && (aAckFrame != nullptr))
{
bool framePending = aAckFrame->GetFramePending();
if (IsEnabled() && framePending)
{
mTimer.Start(kDataPollTimeout);
StartOperation(kOperationWaitingForData);
}
otLogInfoMac("Sent data poll, fp:%s", framePending ? "yes" : "no");
}
mCounters.mTxDataPoll++;
FinishOperation();
Get<DataPollSender>().HandlePollSent(aFrame, aError);
PerformNextOperation();
break;
case kOperationTransmitDataDirect:
mCounters.mTxData++;
if (aError != OT_ERROR_NONE)
{
mCounters.mTxDirectMaxRetryExpiry++;
}
#if OPENTHREAD_CONFIG_MAC_RETRY_SUCCESS_HISTOGRAM_ENABLE
else if (mSubMac.GetTransmitRetries() < OPENTHREAD_CONFIG_MAC_RETRY_SUCCESS_HISTOGRAM_MAX_SIZE_COUNT_DIRECT)
{
mRetryHistogram.mTxDirectRetrySuccess[mSubMac.GetTransmitRetries()]++;
}
#endif
otDumpDebgMac("TX", aFrame.GetHeader(), aFrame.GetLength());
FinishOperation();
Get<MeshForwarder>().HandleSentFrame(aFrame, aError);
#if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2
if (aError == OT_ERROR_NONE && Get<Mle::Mle>().GetParent().IsEnhancedKeepAliveSupported() &&
aFrame.GetSecurityEnabled() && aAckFrame != nullptr)
{
Get<DataPollSender>().ProcessFrame(*aAckFrame);
}
#endif
PerformNextOperation();
break;
#if OPENTHREAD_FTD
case kOperationTransmitDataIndirect:
mCounters.mTxData++;
if (aError != OT_ERROR_NONE)
{
mCounters.mTxIndirectMaxRetryExpiry++;
}
#if OPENTHREAD_CONFIG_MAC_RETRY_SUCCESS_HISTOGRAM_ENABLE
else if (mSubMac.GetTransmitRetries() < OPENTHREAD_CONFIG_MAC_RETRY_SUCCESS_HISTOGRAM_MAX_SIZE_COUNT_INDIRECT)
{
mRetryHistogram.mTxIndirectRetrySuccess[mSubMac.GetTransmitRetries()]++;
}
#endif
otDumpDebgMac("TX", aFrame.GetHeader(), aFrame.GetLength());
FinishOperation();
Get<DataPollHandler>().HandleSentFrame(aFrame, aError);
PerformNextOperation();
break;
#endif
case kOperationTransmitOutOfBandFrame:
// count Oob frames
mCounters.mTxOther++;
FinishOperation();
PerformNextOperation();
break;
default:
OT_ASSERT(false);
OT_UNREACHABLE_CODE(break);
}
exit:
return;
}
void Mac::HandleTimer(Timer &aTimer)
{
aTimer.GetOwner<Mac>().HandleTimer();
}
void Mac::HandleTimer(void)
{
switch (mOperation)
{
case kOperationActiveScan:
PerformActiveScan();
break;
case kOperationWaitingForData:
otLogDebgMac("Data poll timeout");
FinishOperation();
Get<DataPollSender>().HandlePollTimeout();
PerformNextOperation();
break;
#if OPENTHREAD_CONFIG_MAC_STAY_AWAKE_BETWEEN_FRAGMENTS
case kOperationIdle:
if (mDelayingSleep)
{
otLogDebgMac("Sleep delay timeout expired");
mDelayingSleep = false;
UpdateIdleMode();
}
break;
#endif
default:
OT_ASSERT(false);
OT_UNREACHABLE_CODE(break);
}
}
otError Mac::ProcessReceiveSecurity(RxFrame &aFrame, const Address &aSrcAddr, Neighbor *aNeighbor)
{
KeyManager & keyManager = Get<KeyManager>();
otError error = OT_ERROR_SECURITY;
uint8_t securityLevel;
uint8_t keyIdMode;
uint32_t frameCounter;
uint8_t nonce[Crypto::AesCcm::kNonceSize];
uint8_t tag[Frame::kMaxMicSize];
uint8_t tagLength;
uint8_t keyid;
uint32_t keySequence = 0;
const Key * macKey;
const ExtAddress *extAddress;
Crypto::AesCcm aesCcm;
VerifyOrExit(aFrame.GetSecurityEnabled(), error = OT_ERROR_NONE);
IgnoreError(aFrame.GetSecurityLevel(securityLevel));
VerifyOrExit(securityLevel == Frame::kSecEncMic32, OT_NOOP);
IgnoreError(aFrame.GetFrameCounter(frameCounter));
otLogDebgMac("Rx security - frame counter %u", frameCounter);
IgnoreError(aFrame.GetKeyIdMode(keyIdMode));
switch (keyIdMode)
{
case Frame::kKeyIdMode0:
macKey = &keyManager.GetKek();
extAddress = &aSrcAddr.GetExtended();
break;
case Frame::kKeyIdMode1:
VerifyOrExit(aNeighbor != nullptr, OT_NOOP);
IgnoreError(aFrame.GetKeyId(keyid));
keyid--;
if (keyid == (keyManager.GetCurrentKeySequence() & 0x7f))
{
keySequence = keyManager.GetCurrentKeySequence();
macKey = &mSubMac.GetCurrentMacKey();
}
else if (keyid == ((keyManager.GetCurrentKeySequence() - 1) & 0x7f))
{
keySequence = keyManager.GetCurrentKeySequence() - 1;
macKey = &mSubMac.GetPreviousMacKey();
}
else if (keyid == ((keyManager.GetCurrentKeySequence() + 1) & 0x7f))
{
keySequence = keyManager.GetCurrentKeySequence() + 1;
macKey = &mSubMac.GetNextMacKey();
}
else
{
ExitNow();
}
// If the frame is from a neighbor not in valid state (e.g., it is from a child being
// restored), skip the key sequence and frame counter checks but continue to verify
// the tag/MIC. Such a frame is later filtered in `RxDoneTask` which only allows MAC
// Data Request frames from a child being restored.
if (aNeighbor->IsStateValid())
{
VerifyOrExit(keySequence >= aNeighbor->GetKeySequence(), OT_NOOP);
if (keySequence == aNeighbor->GetKeySequence())
{
// If frame counter is one off, then frame is a duplicate.
VerifyOrExit((frameCounter + 1) != aNeighbor->GetLinkFrameCounter(), error = OT_ERROR_DUPLICATED);
VerifyOrExit(frameCounter >= aNeighbor->GetLinkFrameCounter(), OT_NOOP);
}
}
extAddress = &aSrcAddr.GetExtended();
break;
case Frame::kKeyIdMode2:
macKey = static_cast<const Key *>(&sMode2Key);
extAddress = static_cast<const ExtAddress *>(&sMode2ExtAddress);
break;
default:
ExitNow();
OT_UNREACHABLE_CODE(break);
}
Crypto::AesCcm::GenerateNonce(*extAddress, frameCounter, securityLevel, nonce);
tagLength = aFrame.GetFooterLength() - Frame::kFcsSize;
aesCcm.SetKey(*macKey);
aesCcm.Init(aFrame.GetHeaderLength(), aFrame.GetPayloadLength(), tagLength, nonce, sizeof(nonce));
aesCcm.Header(aFrame.GetHeader(), aFrame.GetHeaderLength());
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
aesCcm.Payload(aFrame.GetPayload(), aFrame.GetPayload(), aFrame.GetPayloadLength(), Crypto::AesCcm::kDecrypt);
#else
// For fuzz tests, execute AES but do not alter the payload
uint8_t fuzz[OT_RADIO_FRAME_MAX_SIZE];
aesCcm.Payload(fuzz, aFrame.GetPayload(), aFrame.GetPayloadLength(), Crypto::AesCcm::kDecrypt);
#endif
aesCcm.Finalize(tag);
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
VerifyOrExit(memcmp(tag, aFrame.GetFooter(), tagLength) == 0, OT_NOOP);
#endif
if ((keyIdMode == Frame::kKeyIdMode1) && aNeighbor->IsStateValid())
{
if (aNeighbor->GetKeySequence() != keySequence)
{
aNeighbor->SetKeySequence(keySequence);
aNeighbor->SetMleFrameCounter(0);
}
aNeighbor->SetLinkFrameCounter(frameCounter + 1);
if (keySequence > keyManager.GetCurrentKeySequence())
{
keyManager.SetCurrentKeySequence(keySequence);
}
}
error = OT_ERROR_NONE;
exit:
return error;
}
void Mac::HandleReceivedFrame(RxFrame *aFrame, otError aError)
{
Address srcaddr;
Address dstaddr;
PanId panid;
Neighbor *neighbor;
otError error = aError;
mCounters.mRxTotal++;
SuccessOrExit(error);
VerifyOrExit(aFrame != nullptr, error = OT_ERROR_NO_FRAME_RECEIVED);
VerifyOrExit(IsEnabled(), error = OT_ERROR_INVALID_STATE);
// Ensure we have a valid frame before attempting to read any contents of
// the buffer received from the radio.
SuccessOrExit(error = aFrame->ValidatePsdu());
IgnoreError(aFrame->GetSrcAddr(srcaddr));
IgnoreError(aFrame->GetDstAddr(dstaddr));
neighbor = Get<Mle::MleRouter>().GetNeighbor(srcaddr);
// Destination Address Filtering
switch (dstaddr.GetType())
{
case Address::kTypeNone:
break;
case Address::kTypeShort:
IgnoreError(aFrame->GetDstPanId(panid));
VerifyOrExit((panid == kShortAddrBroadcast || panid == mPanId) &&
((mRxOnWhenIdle && dstaddr.IsBroadcast()) || dstaddr.GetShort() == GetShortAddress()),
error = OT_ERROR_DESTINATION_ADDRESS_FILTERED);
#if OPENTHREAD_FTD
// Allow multicasts from neighbor routers if FTD
if (neighbor == nullptr && dstaddr.IsBroadcast() && Get<Mle::MleRouter>().IsFullThreadDevice())
{
neighbor = Get<Mle::MleRouter>().GetRxOnlyNeighborRouter(srcaddr);
}
#endif
break;
case Address::kTypeExtended:
IgnoreError(aFrame->GetDstPanId(panid));
VerifyOrExit(panid == mPanId && dstaddr.GetExtended() == GetExtAddress(),
error = OT_ERROR_DESTINATION_ADDRESS_FILTERED);
break;
}
// Source Address Filtering
switch (srcaddr.GetType())
{
case Address::kTypeNone:
break;
case Address::kTypeShort:
otLogDebgMac("Received frame from short address 0x%04x", srcaddr.GetShort());
VerifyOrExit(neighbor != nullptr, error = OT_ERROR_UNKNOWN_NEIGHBOR);
srcaddr.SetExtended(neighbor->GetExtAddress());
// Fall through
case Address::kTypeExtended:
// Duplicate Address Protection
VerifyOrExit(srcaddr.GetExtended() != GetExtAddress(), error = OT_ERROR_INVALID_SOURCE_ADDRESS);
#if OPENTHREAD_CONFIG_MAC_FILTER_ENABLE
{
int8_t fixedRss;
SuccessOrExit(error = mFilter.Apply(srcaddr.GetExtended(), fixedRss));
if (fixedRss != Filter::kFixedRssDisabled)
{
aFrame->SetRssi(fixedRss);
// Clear any previous link info to ensure the fixed RSSI
// value takes effect quickly.
if (neighbor != nullptr)
{
neighbor->GetLinkInfo().Clear();
}
}
}
#endif
break;
}
if (dstaddr.IsBroadcast())
{
mCounters.mRxBroadcast++;
}
else
{
mCounters.mRxUnicast++;
}
error = ProcessReceiveSecurity(*aFrame, srcaddr, neighbor);
switch (error)
{
case OT_ERROR_DUPLICATED:
// Allow a duplicate received frame pass, only if the
// current operation is `kOperationWaitingForData` (i.e.,
// the sleepy device is waiting to receive a frame after
// a data poll ack from parent indicating there is a
// pending frame for it). This ensures that the sleepy
// device goes to sleep faster and avoids a data poll
// timeout.
//
// Note that `error` is checked again later after the
// operation `kOperationWaitingForData` is processed
// so the duplicate frame will not be passed to next
// layer (`MeshForwarder`).
VerifyOrExit(mOperation == kOperationWaitingForData, OT_NOOP);
// Fall through
case OT_ERROR_NONE:
break;
default:
ExitNow();
}
Get<DataPollSender>().ProcessFrame(*aFrame);
if (neighbor != nullptr)
{
neighbor->GetLinkInfo().AddRss(aFrame->GetRssi());
if (aFrame->GetSecurityEnabled())
{
switch (neighbor->GetState())
{
case Neighbor::kStateValid:
break;
case Neighbor::kStateRestored:
case Neighbor::kStateChildUpdateRequest:
// Only accept a "MAC Data Request" frame from a child being restored.
VerifyOrExit(aFrame->IsDataRequestCommand(), error = OT_ERROR_DROP);
break;
default:
ExitNow(error = OT_ERROR_UNKNOWN_NEIGHBOR);
}
#if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 && OPENTHREAD_FTD
// From Thread 1.2, MAC Data Frame can also act as keep-alive message if child supports
if (aFrame->GetType() == Frame::kFcfFrameData && !neighbor->IsRxOnWhenIdle() &&
neighbor->IsEnhancedKeepAliveSupported())
{
neighbor->SetLastHeard(TimerMilli::GetNow());
}
#endif
}
}
switch (mOperation)
{
case kOperationActiveScan:
if (aFrame->GetType() == Frame::kFcfFrameBeacon)
{
mCounters.mRxBeacon++;
ReportActiveScanResult(aFrame);
ExitNow();
}
// Fall through
case kOperationEnergyScan:
// We can possibly receive a data frame while either active or
// energy scan is ongoing. We continue to process the frame only
// if the current scan channel matches `mPanChannel`.
VerifyOrExit(mScanChannel == mPanChannel, mCounters.mRxOther++);
break;
case kOperationWaitingForData:
if (!dstaddr.IsNone())
{
mTimer.Stop();
#if OPENTHREAD_CONFIG_MAC_STAY_AWAKE_BETWEEN_FRAGMENTS
if (!mRxOnWhenIdle && !mPromiscuous && aFrame->GetFramePending())
{
mShouldDelaySleep = true;
otLogDebgMac("Delay sleep for pending rx");
}
#endif
FinishOperation();
PerformNextOperation();
}
SuccessOrExit(error);
break;
default:
break;
}
switch (aFrame->GetType())
{
case Frame::kFcfFrameMacCmd:
if (HandleMacCommand(*aFrame)) // returns `true` when handled
{
ExitNow(error = OT_ERROR_NONE);
}
break;
case Frame::kFcfFrameBeacon:
mCounters.mRxBeacon++;
break;
case Frame::kFcfFrameData:
mCounters.mRxData++;
break;
default:
mCounters.mRxOther++;
ExitNow();
}
otDumpDebgMac("RX", aFrame->GetHeader(), aFrame->GetLength());
Get<MeshForwarder>().HandleReceivedFrame(*aFrame);
exit:
if (error != OT_ERROR_NONE)
{
LogFrameRxFailure(aFrame, error);
switch (error)
{
case OT_ERROR_SECURITY:
mCounters.mRxErrSec++;
break;
case OT_ERROR_FCS:
mCounters.mRxErrFcs++;
break;
case OT_ERROR_NO_FRAME_RECEIVED:
mCounters.mRxErrNoFrame++;
break;
case OT_ERROR_UNKNOWN_NEIGHBOR:
mCounters.mRxErrUnknownNeighbor++;
break;
case OT_ERROR_INVALID_SOURCE_ADDRESS:
mCounters.mRxErrInvalidSrcAddr++;
break;
case OT_ERROR_ADDRESS_FILTERED:
mCounters.mRxAddressFiltered++;
break;
case OT_ERROR_DESTINATION_ADDRESS_FILTERED:
mCounters.mRxDestAddrFiltered++;
break;
case OT_ERROR_DUPLICATED:
mCounters.mRxDuplicated++;
break;
default:
mCounters.mRxErrOther++;
break;
}
}
}
bool Mac::HandleMacCommand(RxFrame &aFrame)
{
bool didHandle = false;
uint8_t commandId;
IgnoreError(aFrame.GetCommandId(commandId));
switch (commandId)
{
case Frame::kMacCmdBeaconRequest:
mCounters.mRxBeaconRequest++;
otLogInfoMac("Received Beacon Request");
if (ShouldSendBeacon())
{
StartOperation(kOperationTransmitBeacon);
}
didHandle = true;
break;
case Frame::kMacCmdDataRequest:
mCounters.mRxDataPoll++;
#if OPENTHREAD_FTD
Get<DataPollHandler>().HandleDataPoll(aFrame);
didHandle = true;
#endif
break;
default:
mCounters.mRxOther++;
break;
}
return didHandle;
}
void Mac::SetPromiscuous(bool aPromiscuous)
{
mPromiscuous = aPromiscuous;
Get<Radio>().SetPromiscuous(aPromiscuous);
#if OPENTHREAD_CONFIG_MAC_STAY_AWAKE_BETWEEN_FRAGMENTS
mDelayingSleep = false;
mShouldDelaySleep = false;
#endif
mSubMac.SetRxOnWhenBackoff(mRxOnWhenIdle || mPromiscuous);
UpdateIdleMode();
}
#if OPENTHREAD_CONFIG_MAC_RETRY_SUCCESS_HISTOGRAM_ENABLE
const uint32_t *Mac::GetDirectRetrySuccessHistogram(uint8_t &aNumberOfEntries)
{
if (mMaxFrameRetriesDirect >= OPENTHREAD_CONFIG_MAC_RETRY_SUCCESS_HISTOGRAM_MAX_SIZE_COUNT_DIRECT)
{
aNumberOfEntries = OPENTHREAD_CONFIG_MAC_RETRY_SUCCESS_HISTOGRAM_MAX_SIZE_COUNT_DIRECT;
}
else
{
aNumberOfEntries = mMaxFrameRetriesDirect + 1;
}
return mRetryHistogram.mTxDirectRetrySuccess;
}
#if OPENTHREAD_FTD
const uint32_t *Mac::GetIndirectRetrySuccessHistogram(uint8_t &aNumberOfEntries)
{
if (mMaxFrameRetriesIndirect >= OPENTHREAD_CONFIG_MAC_RETRY_SUCCESS_HISTOGRAM_MAX_SIZE_COUNT_INDIRECT)
{
aNumberOfEntries = OPENTHREAD_CONFIG_MAC_RETRY_SUCCESS_HISTOGRAM_MAX_SIZE_COUNT_INDIRECT;
}
else
{
aNumberOfEntries = mMaxFrameRetriesIndirect + 1;
}
return mRetryHistogram.mTxIndirectRetrySuccess;
}
#endif
void Mac::ResetRetrySuccessHistogram()
{
memset(&mRetryHistogram, 0, sizeof(mRetryHistogram));
}
#endif // OPENTHREAD_CONFIG_MAC_RETRY_SUCCESS_HISTOGRAM_ENABLE
// LCOV_EXCL_START
#if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_INFO) && (OPENTHREAD_CONFIG_LOG_MAC == 1)
const char *Mac::OperationToString(Operation aOperation)
{
const char *retval = "";
switch (aOperation)
{
case kOperationIdle:
retval = "Idle";
break;
case kOperationActiveScan:
retval = "ActiveScan";
break;
case kOperationEnergyScan:
retval = "EnergyScan";
break;
case kOperationTransmitBeacon:
retval = "TransmitBeacon";
break;
case kOperationTransmitDataDirect:
retval = "TransmitDataDirect";
break;
#if OPENTHREAD_FTD
case kOperationTransmitDataIndirect:
retval = "TransmitDataIndirect";
break;
#endif
case kOperationTransmitPoll:
retval = "TransmitPoll";
break;
case kOperationWaitingForData:
retval = "WaitingForData";
break;
case kOperationTransmitOutOfBandFrame:
retval = "TransmitOobFrame";
break;
}
return retval;
}
void Mac::LogFrameRxFailure(const RxFrame *aFrame, otError aError) const
{
otLogLevel logLevel;
switch (aError)
{
case OT_ERROR_ABORT:
case OT_ERROR_NO_FRAME_RECEIVED:
case OT_ERROR_DESTINATION_ADDRESS_FILTERED:
logLevel = OT_LOG_LEVEL_DEBG;
break;
default:
logLevel = OT_LOG_LEVEL_INFO;
break;
}
if (aFrame == nullptr)
{
otLogMac(logLevel, "Frame rx failed, error:%s", otThreadErrorToString(aError));
}
else
{
otLogMac(logLevel, "Frame rx failed, error:%s, %s", otThreadErrorToString(aError),
aFrame->ToInfoString().AsCString());
}
}
void Mac::LogFrameTxFailure(const TxFrame &aFrame, otError aError, uint8_t aRetryCount, bool aWillRetx) const
{
uint8_t maxAttempts = aFrame.GetMaxFrameRetries() + 1;
uint8_t curAttempt = aWillRetx ? (aRetryCount + 1) : maxAttempts;
otLogInfoMac("Frame tx attempt %d/%d failed, error:%s, %s", curAttempt, maxAttempts, otThreadErrorToString(aError),
aFrame.ToInfoString().AsCString());
}
void Mac::LogBeacon(const char *aActionText, const BeaconPayload &aBeaconPayload) const
{
otLogInfoMac("%s Beacon, %s", aActionText, aBeaconPayload.ToInfoString().AsCString());
}
#else // #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_INFO) && (OPENTHREAD_CONFIG_LOG_MAC == 1)
void Mac::LogFrameRxFailure(const RxFrame *, otError) const
{
}
void Mac::LogBeacon(const char *, const BeaconPayload &) const
{
}
void Mac::LogFrameTxFailure(const TxFrame &, otError, uint8_t, bool) const
{
}
#endif // #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_INFO) && (OPENTHREAD_CONFIG_LOG_MAC == 1)
// LCOV_EXCL_STOP
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
uint8_t Mac::GetTimeIeOffset(const Frame &aFrame)
{
uint8_t offset = 0;
const uint8_t *base = aFrame.GetPsdu();
const uint8_t *cur = nullptr;
cur = reinterpret_cast<const uint8_t *>(aFrame.GetTimeIe());
VerifyOrExit(cur != nullptr, OT_NOOP);
cur += sizeof(VendorIeHeader);
offset = static_cast<uint8_t>(cur - base);
exit:
return offset;
}
#endif
#if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT
otError Mac::AppendHeaderIe(bool aIsTimeSync, TxFrame &aFrame)
{
OT_UNUSED_VARIABLE(aIsTimeSync);
const size_t kMaxNumHeaderIe = 3; // TimeSync + Csl + Termination2
HeaderIe ieList[kMaxNumHeaderIe];
otError error = OT_ERROR_NONE;
uint8_t ieCount = 0;
VerifyOrExit(aFrame.IsVersion2015() && aFrame.IsIePresent(), OT_NOOP);
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
if (aIsTimeSync)
{
ieList[ieCount].Init();
ieList[ieCount].SetId(Frame::kHeaderIeVendor);
ieList[ieCount].SetLength(sizeof(TimeIe));
ieCount++;
}
#endif
if (ieCount > 0)
{
ieList[ieCount].Init();
ieList[ieCount].SetId(Frame::kHeaderIeTermination2);
ieList[ieCount].SetLength(0);
SuccessOrExit(error = aFrame.AppendHeaderIe(ieList, ++ieCount));
}
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
if (aIsTimeSync)
{
uint8_t *cur = aFrame.GetHeaderIe(Frame::kHeaderIeVendor);
TimeIe * ie = reinterpret_cast<TimeIe *>(cur + sizeof(HeaderIe));
ie->Init();
}
#endif
exit:
return error;
}
#endif // OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT
void Mac::UpdateFrameControlField(bool aIsTimeSync, uint16_t &aFcf)
{
OT_UNUSED_VARIABLE(aIsTimeSync);
#if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
if (aIsTimeSync)
{
aFcf |= Frame::kFcfFrameVersion2015 | Frame::kFcfIePresent;
}
else
#endif
#endif
{
aFcf |= Frame::kFcfFrameVersion2006;
}
}
} // namespace Mac
} // namespace ot