blob: 7e2f6503cbc12097e12cb01f46c30f2c0c51354c [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/array.hpp"
#include "common/as_core_type.hpp"
#include "common/code_utils.hpp"
#include "common/debug.hpp"
#include "common/encoding.hpp"
#include "common/instance.hpp"
#include "common/locator_getters.hpp"
#include "common/random.hpp"
#include "common/string.hpp"
#include "crypto/aes_ccm.hpp"
#include "crypto/sha256.hpp"
#include "mac/mac_frame.hpp"
#include "radio/radio.hpp"
#include "thread/child_table.hpp"
#include "thread/link_quality.hpp"
#include "thread/mle_router.hpp"
#include "thread/thread_netif.hpp"
#include "thread/topology.hpp"
namespace ot {
namespace Mac {
RegisterLogModule("Mac");
const otExtAddress Mac::sMode2ExtAddress = {
{0x35, 0x06, 0xfe, 0xb8, 0x23, 0xd4, 0x87, 0x12},
};
Mac::Mac(Instance &aInstance)
: InstanceLocator(aInstance)
, mEnabled(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)
, mPendingOperations(0)
, 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())
, mScanChannel(Radio::kChannelMin)
, mScanDuration(0)
, mMaxFrameRetriesDirect(kDefaultMaxFrameRetriesDirect)
#if OPENTHREAD_FTD
, mMaxFrameRetriesIndirect(kDefaultMaxFrameRetriesIndirect)
#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
, mCslTxFireTime(TimeMilli::kMaxDuration)
#endif
#endif
, mActiveScanHandler(nullptr) // Initialize `mActiveScanHandler` and `mEnergyScanHandler` union
, mScanHandlerContext(nullptr)
, mLinks(aInstance)
, mOperationTask(aInstance, Mac::HandleOperationTask)
, mTimer(aInstance, Mac::HandleTimer)
, mKeyIdMode2FrameCounter(0)
, mCcaSampleCount(0)
#if OPENTHREAD_CONFIG_MULTI_RADIO
, mTxError(kErrorNone)
#endif
{
ExtAddress randomExtAddress;
static const otMacKey sMode2Key = {
{0x78, 0x58, 0x16, 0x86, 0xfd, 0xb4, 0x58, 0x0f, 0xb0, 0x92, 0x54, 0x6a, 0xec, 0xbd, 0x15, 0x66}};
randomExtAddress.GenerateRandom();
mCcaSuccessRateTracker.Clear();
ResetCounters();
SetEnabled(true);
mLinks.Enable();
Get<KeyManager>().UpdateKeyMaterial();
SetPanId(mPanId);
SetExtAddress(randomExtAddress);
SetShortAddress(GetShortAddress());
mMode2KeyMaterial.SetFrom(AsCoreType(&sMode2Key));
}
Error Mac::ActiveScan(uint32_t aScanChannels, uint16_t aScanDuration, ActiveScanHandler aHandler, void *aContext)
{
Error error = kErrorNone;
VerifyOrExit(IsEnabled(), error = kErrorInvalidState);
VerifyOrExit(!IsActiveScanInProgress() && !IsEnergyScanInProgress(), error = kErrorBusy);
mActiveScanHandler = aHandler;
mScanHandlerContext = aContext;
if (aScanDuration == 0)
{
aScanDuration = kScanDurationDefault;
}
Scan(kOperationActiveScan, aScanChannels, aScanDuration);
exit:
return error;
}
Error Mac::EnergyScan(uint32_t aScanChannels, uint16_t aScanDuration, EnergyScanHandler aHandler, void *aContext)
{
Error error = kErrorNone;
VerifyOrExit(IsEnabled(), error = kErrorInvalidState);
VerifyOrExit(!IsActiveScanInProgress() && !IsEnergyScanInProgress(), error = kErrorBusy);
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:
#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
case kOperationTransmitDataCsl:
#endif
#endif
case kOperationTransmitBeacon:
case kOperationTransmitPoll:
retval = true;
break;
case kOperationIdle:
case kOperationActiveScan:
case kOperationEnergyScan:
case kOperationWaitingForData:
retval = false;
break;
}
return retval;
}
Error Mac::ConvertBeaconToActiveScanResult(const RxFrame *aBeaconFrame, ActiveScanResult &aResult)
{
Error error = kErrorNone;
Address address;
memset(&aResult, 0, sizeof(ActiveScanResult));
VerifyOrExit(aBeaconFrame != nullptr, error = kErrorInvalidArgs);
VerifyOrExit(aBeaconFrame->GetType() == Frame::kFcfFrameBeacon, error = kErrorParse);
SuccessOrExit(error = aBeaconFrame->GetSrcAddr(address));
VerifyOrExit(address.IsExtended(), error = kErrorParse);
aResult.mExtAddress = address.GetExtended();
if (kErrorNone != aBeaconFrame->GetSrcPanId(aResult.mPanId))
{
IgnoreError(aBeaconFrame->GetDstPanId(aResult.mPanId));
}
aResult.mChannel = aBeaconFrame->GetChannel();
aResult.mRssi = aBeaconFrame->GetRssi();
aResult.mLqi = aBeaconFrame->GetLqi();
LogBeacon("Received");
exit:
return error;
}
Error Mac::UpdateScanChannel(void)
{
Error error;
VerifyOrExit(IsEnabled(), error = kErrorAbort);
error = mScanChannelMask.GetNextChannel(mScanChannel);
exit:
return error;
}
void Mac::PerformActiveScan(void)
{
if (UpdateScanChannel() == kErrorNone)
{
// If there are more channels to scan, send the beacon request.
BeginTransmit();
}
else
{
mLinks.SetPanId(mPanId);
FinishOperation();
ReportActiveScanResult(nullptr);
PerformNextOperation();
}
}
void Mac::ReportActiveScanResult(const RxFrame *aBeaconFrame)
{
VerifyOrExit(mActiveScanHandler != nullptr);
if (aBeaconFrame == nullptr)
{
mActiveScanHandler(nullptr, mScanHandlerContext);
}
else
{
ActiveScanResult result;
SuccessOrExit(ConvertBeaconToActiveScanResult(aBeaconFrame, result));
mActiveScanHandler(&result, mScanHandlerContext);
}
exit:
return;
}
void Mac::PerformEnergyScan(void)
{
Error error = kErrorNone;
SuccessOrExit(error = UpdateScanChannel());
if (mScanDuration == 0)
{
while (true)
{
mLinks.Receive(mScanChannel);
ReportEnergyScanResult(mLinks.GetRssi());
SuccessOrExit(error = UpdateScanChannel());
}
}
else
{
error = mLinks.EnergyScan(mScanChannel, mScanDuration);
}
exit:
if (error != kErrorNone)
{
FinishOperation();
if (mEnergyScanHandler != nullptr)
{
mEnergyScanHandler(nullptr, mScanHandlerContext);
}
PerformNextOperation();
}
}
void Mac::ReportEnergyScanResult(int8_t aRssi)
{
EnergyScanResult result;
VerifyOrExit((mEnergyScanHandler != nullptr) && (aRssi != kInvalidRssiValue));
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);
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 (IsPending(kOperationWaitingForData))
{
mTimer.Stop();
ClearPending(kOperationWaitingForData);
}
if (mOperation == kOperationWaitingForData)
{
mTimer.Stop();
FinishOperation();
mOperationTask.Post();
}
#if OPENTHREAD_CONFIG_MAC_STAY_AWAKE_BETWEEN_FRAGMENTS
mDelayingSleep = false;
mShouldDelaySleep = false;
#endif
}
mLinks.SetRxOnWhenBackoff(mRxOnWhenIdle || mPromiscuous);
UpdateIdleMode();
exit:
return;
}
Error Mac::SetPanChannel(uint8_t aChannel)
{
Error error = kErrorNone;
VerifyOrExit(mSupportedChannelMask.ContainsChannel(aChannel), error = kErrorInvalidArgs);
SuccessOrExit(Get<Notifier>().Update(mPanChannel, aChannel, kEventThreadChannelChanged));
mCcaSuccessRateTracker.Clear();
VerifyOrExit(!mUsingTemporaryChannel);
mRadioChannel = mPanChannel;
UpdateIdleMode();
exit:
return error;
}
Error Mac::SetTemporaryChannel(uint8_t aChannel)
{
Error error = kErrorNone;
VerifyOrExit(mSupportedChannelMask.ContainsChannel(aChannel), error = kErrorInvalidArgs);
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));
}
void Mac::SetPanId(PanId aPanId)
{
SuccessOrExit(Get<Notifier>().Update(mPanId, aPanId, kEventThreadPanIdChanged));
mLinks.SetPanId(mPanId);
exit:
return;
}
void Mac::RequestDirectFrameTransmission(void)
{
VerifyOrExit(IsEnabled());
VerifyOrExit(!IsActiveOrPending(kOperationTransmitDataDirect));
StartOperation(kOperationTransmitDataDirect);
exit:
return;
}
#if OPENTHREAD_FTD
void Mac::RequestIndirectFrameTransmission(void)
{
VerifyOrExit(IsEnabled());
VerifyOrExit(!IsActiveOrPending(kOperationTransmitDataIndirect));
StartOperation(kOperationTransmitDataIndirect);
exit:
return;
}
#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
void Mac::RequestCslFrameTransmission(uint32_t aDelay)
{
VerifyOrExit(mEnabled);
mCslTxFireTime = TimerMilli::GetNow() + aDelay;
StartOperation(kOperationTransmitDataCsl);
exit:
return;
}
#endif
#endif // OPENTHREAD_FTD
Error Mac::RequestDataPollTransmission(void)
{
Error error = kErrorNone;
VerifyOrExit(IsEnabled(), error = kErrorInvalidState);
VerifyOrExit(!IsActiveOrPending(kOperationTransmitPoll), error = kErrorAlready);
// 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 = !IsPending(kOperationTransmitDataDirect);
StartOperation(kOperationTransmitPoll);
exit:
return error;
}
void Mac::UpdateIdleMode(void)
{
bool shouldSleep = !mRxOnWhenIdle && !mPromiscuous;
VerifyOrExit(mOperation == kOperationIdle);
if (!mRxOnWhenIdle)
{
#if OPENTHREAD_CONFIG_MAC_STAY_AWAKE_BETWEEN_FRAGMENTS
if (mShouldDelaySleep)
{
mTimer.Start(kSleepDelay);
mShouldDelaySleep = false;
mDelayingSleep = true;
LogDebg("Idle mode: Sleep delayed");
}
if (mDelayingSleep)
{
shouldSleep = false;
}
#endif
}
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
else if (IsPending(kOperationTransmitDataCsl))
{
mTimer.FireAt(mCslTxFireTime);
}
#endif
if (shouldSleep)
{
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
if (IsCslEnabled())
{
mLinks.CslSample(mRadioChannel);
ExitNow();
}
#endif
mLinks.Sleep();
LogDebg("Idle mode: Radio sleeping");
}
else
{
mLinks.Receive(mRadioChannel);
LogDebg("Idle mode: Radio receiving on channel %d", mRadioChannel);
}
exit:
return;
}
bool Mac::IsActiveOrPending(Operation aOperation) const
{
return (mOperation == aOperation) || IsPending(aOperation);
}
void Mac::StartOperation(Operation aOperation)
{
if (aOperation != kOperationIdle)
{
SetPending(aOperation);
LogDebg("Request to start operation \"%s\"", OperationToString(aOperation));
#if OPENTHREAD_CONFIG_MAC_STAY_AWAKE_BETWEEN_FRAGMENTS
if (mDelayingSleep)
{
LogDebg("Canceling sleep delay");
mTimer.Stop();
mDelayingSleep = false;
mShouldDelaySleep = false;
}
#endif
}
if (mOperation == kOperationIdle)
{
mOperationTask.Post();
}
}
void Mac::HandleOperationTask(Tasklet &aTasklet)
{
aTasklet.Get<Mac>().PerformNextOperation();
}
void Mac::PerformNextOperation(void)
{
VerifyOrExit(mOperation == kOperationIdle);
if (!IsEnabled())
{
mPendingOperations = 0;
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 (IsPending(kOperationWaitingForData))
{
mOperation = kOperationWaitingForData;
}
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
else if (IsPending(kOperationTransmitDataCsl) && TimerMilli::GetNow() >= mCslTxFireTime)
{
mOperation = kOperationTransmitDataCsl;
}
#endif
else if (IsPending(kOperationActiveScan))
{
mOperation = kOperationActiveScan;
}
else if (IsPending(kOperationEnergyScan))
{
mOperation = kOperationEnergyScan;
}
else if (IsPending(kOperationTransmitBeacon))
{
mOperation = kOperationTransmitBeacon;
}
#if OPENTHREAD_FTD
else if (IsPending(kOperationTransmitDataIndirect))
{
mOperation = kOperationTransmitDataIndirect;
}
#endif // OPENTHREAD_FTD
else if (IsPending(kOperationTransmitPoll) && (!IsPending(kOperationTransmitDataDirect) || mShouldTxPollBeforeData))
{
mOperation = kOperationTransmitPoll;
}
else if (IsPending(kOperationTransmitDataDirect))
{
mOperation = kOperationTransmitDataDirect;
if (IsPending(kOperationTransmitPoll))
{
// Ensure that a pending "transmit poll" operation request
// is prioritized over any future "transmit data" requests.
mShouldTxPollBeforeData = true;
}
}
if (mOperation != kOperationIdle)
{
ClearPending(mOperation);
LogDebg("Starting operation \"%s\"", OperationToString(mOperation));
mTimer.Stop(); // Stop the timer before any non-idle operation, have the operation itself be responsible to
// start the timer (if it wants to).
}
switch (mOperation)
{
case kOperationIdle:
UpdateIdleMode();
break;
case kOperationActiveScan:
PerformActiveScan();
break;
case kOperationEnergyScan:
PerformEnergyScan();
break;
case kOperationTransmitBeacon:
case kOperationTransmitDataDirect:
#if OPENTHREAD_FTD
case kOperationTransmitDataIndirect:
#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
case kOperationTransmitDataCsl:
#endif
#endif
case kOperationTransmitPoll:
BeginTransmit();
break;
case kOperationWaitingForData:
mLinks.Receive(mRadioChannel);
mTimer.Start(kDataPollTimeout);
break;
}
exit:
return;
}
void Mac::FinishOperation(void)
{
LogDebg("Finishing operation \"%s\"", OperationToString(mOperation));
mOperation = kOperationIdle;
}
TxFrame *Mac::PrepareBeaconRequest(void)
{
TxFrame &frame = mLinks.GetTxFrames().GetBroadcastTxFrame();
uint16_t fcf = Frame::kFcfFrameMacCmd | Frame::kFcfDstAddrShort | Frame::kFcfSrcAddrNone;
frame.InitMacHeader(fcf, Frame::kSecNone);
frame.SetDstPanId(kShortAddrBroadcast);
frame.SetDstAddr(kShortAddrBroadcast);
IgnoreError(frame.SetCommandId(Frame::kMacCmdBeaconRequest));
LogInfo("Sending Beacon Request");
return &frame;
}
TxFrame *Mac::PrepareBeacon(void)
{
TxFrame *frame;
uint16_t fcf;
Beacon * beacon = nullptr;
#if OPENTHREAD_CONFIG_MULTI_RADIO
OT_ASSERT(!mTxBeaconRadioLinks.IsEmpty());
frame = &mLinks.GetTxFrames().GetTxFrame(mTxBeaconRadioLinks);
mTxBeaconRadioLinks.Clear();
#else
frame = &mLinks.GetTxFrames().GetBroadcastTxFrame();
#endif
fcf = Frame::kFcfFrameBeacon | Frame::kFcfDstAddrNone | Frame::kFcfSrcAddrExt;
frame->InitMacHeader(fcf, Frame::kSecNone);
IgnoreError(frame->SetSrcPanId(mPanId));
frame->SetSrcAddr(GetExtAddress());
beacon = reinterpret_cast<Beacon *>(frame->GetPayload());
beacon->Init();
LogBeacon("Sending");
return frame;
}
bool Mac::ShouldSendBeacon(void) const
{
bool shouldSend = false;
VerifyOrExit(IsEnabled());
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());
IgnoreError(aFrame.GetKeyIdMode(keyIdMode));
switch (keyIdMode)
{
case Frame::kKeyIdMode0:
aFrame.SetAesKey(keyManager.GetKek());
extAddress = &GetExtAddress();
if (!aFrame.IsHeaderUpdated())
{
aFrame.SetFrameCounter(keyManager.GetKekFrameCounter());
keyManager.IncrementKekFrameCounter();
}
break;
case Frame::kKeyIdMode1:
// For 15.4 radio link, the AES CCM* and frame security counter (under MAC
// key ID mode 1) are managed by `SubMac` or `Radio` modules.
#if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
#if !OPENTHREAD_CONFIG_MULTI_RADIO
ExitNow();
#else
VerifyOrExit(aFrame.GetRadioType() != kRadioTypeIeee802154);
#endif
#endif
#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
aFrame.SetAesKey(*mLinks.GetCurrentMacKey(aFrame));
extAddress = &GetExtAddress();
// If the frame header is marked as updated, `MeshForwarder` which
// prepared the frame should set the frame counter and key id to the
// same values used in the earlier transmit attempt. For a new frame (header
// not updated), we get a new frame counter and key id from the key
// manager.
if (!aFrame.IsHeaderUpdated())
{
mLinks.SetMacFrameCounter(aFrame);
aFrame.SetKeyId((keyManager.GetCurrentKeySequence() & 0x7f) + 1);
}
#endif
break;
case Frame::kKeyIdMode2:
{
const uint8_t keySource[] = {0xff, 0xff, 0xff, 0xff};
aFrame.SetAesKey(mMode2KeyMaterial);
mKeyIdMode2FrameCounter++;
aFrame.SetFrameCounter(mKeyIdMode2FrameCounter);
aFrame.SetKeySource(keySource);
aFrame.SetKeyId(0xff);
extAddress = &AsCoreType(&sMode2ExtAddress);
break;
}
default:
OT_ASSERT(false);
}
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
// Transmit security will be processed after time IE content is updated.
VerifyOrExit(aFrame.GetTimeIeOffset() == 0);
#endif
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
// Transmit security will be processed after time IE content is updated.
VerifyOrExit(aFrame.mInfo.mTxInfo.mCslPresent == 0);
#endif
aFrame.ProcessTransmitAesCcm(*extAddress);
exit:
return;
}
void Mac::BeginTransmit(void)
{
TxFrame * frame = nullptr;
TxFrames &txFrames = mLinks.GetTxFrames();
Address dstAddr;
txFrames.Clear();
#if OPENTHREAD_CONFIG_MULTI_RADIO
mTxPendingRadioLinks.Clear();
mTxError = kErrorAbort;
#endif
VerifyOrExit(IsEnabled());
switch (mOperation)
{
case kOperationActiveScan:
mLinks.SetPanId(kPanIdBroadcast);
frame = PrepareBeaconRequest();
VerifyOrExit(frame != nullptr);
frame->SetChannel(mScanChannel);
frame->SetSequence(0);
frame->SetMaxCsmaBackoffs(kMaxCsmaBackoffsDirect);
frame->SetMaxFrameRetries(mMaxFrameRetriesDirect);
break;
case kOperationTransmitBeacon:
frame = PrepareBeacon();
VerifyOrExit(frame != nullptr);
frame->SetChannel(mRadioChannel);
frame->SetSequence(mBeaconSequence++);
frame->SetMaxCsmaBackoffs(kMaxCsmaBackoffsDirect);
frame->SetMaxFrameRetries(mMaxFrameRetriesDirect);
break;
case kOperationTransmitPoll:
txFrames.SetChannel(mRadioChannel);
txFrames.SetMaxCsmaBackoffs(kMaxCsmaBackoffsDirect);
txFrames.SetMaxFrameRetries(mMaxFrameRetriesDirect);
frame = Get<DataPollSender>().PrepareDataRequest(txFrames);
VerifyOrExit(frame != nullptr);
frame->SetSequence(mDataSequence++);
break;
case kOperationTransmitDataDirect:
// Set channel and retry counts on all TxFrames before asking
// the next layer (`MeshForwarder`) to prepare the frame. This
// allows next layer to possibility change these parameters.
txFrames.SetChannel(mRadioChannel);
txFrames.SetMaxCsmaBackoffs(kMaxCsmaBackoffsDirect);
txFrames.SetMaxFrameRetries(mMaxFrameRetriesDirect);
frame = Get<MeshForwarder>().HandleFrameRequest(txFrames);
VerifyOrExit(frame != nullptr);
frame->SetSequence(mDataSequence++);
break;
#if OPENTHREAD_FTD
case kOperationTransmitDataIndirect:
txFrames.SetChannel(mRadioChannel);
txFrames.SetMaxCsmaBackoffs(kMaxCsmaBackoffsIndirect);
txFrames.SetMaxFrameRetries(mMaxFrameRetriesIndirect);
frame = Get<DataPollHandler>().HandleFrameRequest(txFrames);
VerifyOrExit(frame != nullptr);
// If the frame is marked as retransmission, then data sequence number is already set.
if (!frame->IsARetransmission())
{
frame->SetSequence(mDataSequence++);
}
break;
#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
case kOperationTransmitDataCsl:
txFrames.SetMaxCsmaBackoffs(kMaxCsmaBackoffsCsl);
txFrames.SetMaxFrameRetries(kMaxFrameRetriesCsl);
frame = Get<CslTxScheduler>().HandleFrameRequest(txFrames);
VerifyOrExit(frame != nullptr);
// If the frame is marked as retransmission, then data sequence number is already set.
if (!frame->IsARetransmission())
{
frame->SetSequence(mDataSequence++);
}
break;
#endif
#endif // OPENTHREAD_FTD
default:
OT_ASSERT(false);
}
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
{
uint8_t timeIeOffset = GetTimeIeOffset(*frame);
frame->SetTimeIeOffset(timeIeOffset);
if (timeIeOffset != 0)
{
frame->SetTimeSyncSeq(Get<TimeSync>().GetTimeSyncSeq());
frame->SetNetworkTimeOffset(Get<TimeSync>().GetNetworkTimeOffset());
}
}
#endif
if (!frame->IsSecurityProcessed())
{
#if OPENTHREAD_CONFIG_MULTI_RADIO
// Go through all selected radio link types for this tx and
// copy the frame into correct `TxFrame` for each radio type
// (if it is not already prepared).
for (uint8_t index = 0; index < GetArrayLength(RadioTypes::kAllRadioTypes); index++)
{
RadioType radio = RadioTypes::kAllRadioTypes[index];
if (txFrames.GetSelectedRadioTypes().Contains(radio))
{
TxFrame &txFrame = txFrames.GetTxFrame(radio);
if (txFrame.IsEmpty())
{
txFrame.CopyFrom(*frame);
}
}
}
// Go through all selected radio link types for this tx and
// process security for each radio type separately. This
// allows radio links to handle security differently, e.g.,
// with different keys or link frame counters.
for (uint8_t index = 0; index < GetArrayLength(RadioTypes::kAllRadioTypes); index++)
{
RadioType radio = RadioTypes::kAllRadioTypes[index];
if (txFrames.GetSelectedRadioTypes().Contains(radio))
{
ProcessTransmitSecurity(txFrames.GetTxFrame(radio));
}
}
#else
ProcessTransmitSecurity(*frame);
#endif
}
mBroadcastTransmitCount = 0;
#if OPENTHREAD_CONFIG_MULTI_RADIO
mTxPendingRadioLinks = txFrames.GetSelectedRadioTypes();
// If the "required radio type set" is empty,`mTxError` starts as
// `kErrorAbort`. In this case, successful tx over any radio
// link is sufficient for overall tx to be considered successful.
// When the "required radio type set" is not empty, `mTxError`
// starts as `kErrorNone` and we update it if tx over any link
// in the required set fails.
if (!txFrames.GetRequiredRadioTypes().IsEmpty())
{
mTxError = kErrorNone;
}
#endif
#if OPENTHREAD_CONFIG_MAC_STAY_AWAKE_BETWEEN_FRAGMENTS
if (!mRxOnWhenIdle && !mPromiscuous)
{
mShouldDelaySleep = frame->GetFramePending();
LogDebg("Delay sleep for pending tx");
}
#endif
#if OPENTHREAD_CONFIG_MULTI_RADIO
mLinks.Send(*frame, mTxPendingRadioLinks);
#else
mLinks.Send();
#endif
exit:
if (frame == nullptr)
{
// If the frame 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.
frame = &txFrames.GetBroadcastTxFrame();
frame->SetLength(0);
HandleTransmitDone(*frame, nullptr, kErrorAbort);
}
}
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,
Error aError,
uint8_t aRetryCount,
bool aWillRetx)
{
bool ackRequested = aFrame.GetAckRequest();
Address dstAddr;
Neighbor *neighbor;
VerifyOrExit(!aFrame.IsEmpty());
IgnoreError(aFrame.GetDstAddr(dstAddr));
neighbor = Get<NeighborTable>().FindNeighbor(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 kErrorNoAck:
frameTxSuccess = false;
OT_FALL_THROUGH;
case kErrorNone:
neighbor->GetLinkInfo().AddFrameTxStatus(frameTxSuccess);
break;
default:
break;
}
}
// Log frame transmission failure.
if (aError != kErrorNone)
{
LogFrameTxFailure(aFrame, aError, aRetryCount, aWillRetx);
DumpDebg("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 == kErrorNone) && ackRequested && (aAckFrame != nullptr) && (neighbor != nullptr))
{
neighbor->GetLinkInfo().AddRss(aAckFrame->GetRssi());
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
neighbor->AggregateLinkMetrics(/* aSeriesId */ 0, aAckFrame->GetType(), aAckFrame->GetLqi(),
aAckFrame->GetRssi());
ProcessEnhAckProbing(*aAckFrame, *neighbor);
#endif
#if OPENTHREAD_FTD
if (aAckFrame->GetVersion() == Frame::kFcfFrameVersion2015)
{
#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
ProcessCsl(*aAckFrame, dstAddr);
#endif
}
#endif // OPENTHREAD_FTD
}
// Update MAC counters.
mCounters.mTxTotal++;
if (aError == kErrorAbort)
{
mCounters.mTxErrAbort++;
}
if (aError == kErrorChannelAccessFailure)
{
mCounters.mTxErrBusyChannel++;
}
if (ackRequested)
{
mCounters.mTxAckRequested++;
if (aError == kErrorNone)
{
mCounters.mTxAcked++;
}
}
else
{
mCounters.mTxNoAckRequested++;
}
if (dstAddr.IsBroadcast())
{
mCounters.mTxBroadcast++;
}
else
{
mCounters.mTxUnicast++;
}
exit:
return;
}
void Mac::HandleTransmitDone(TxFrame &aFrame, RxFrame *aAckFrame, Error aError)
{
#if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
if (!aFrame.IsEmpty()
#if OPENTHREAD_CONFIG_MULTI_RADIO
&& (aFrame.GetRadioType() == kRadioTypeIeee802154)
#endif
)
{
Address dstAddr;
// Determine whether to re-transmit a broadcast frame.
IgnoreError(aFrame.GetDstAddr(dstAddr));
if (dstAddr.IsBroadcast())
{
mBroadcastTransmitCount++;
if (mBroadcastTransmitCount < kTxNumBcast)
{
#if OPENTHREAD_CONFIG_MULTI_RADIO
{
RadioTypes radioTypes;
radioTypes.Add(kRadioTypeIeee802154);
mLinks.Send(aFrame, radioTypes);
}
#else
mLinks.Send();
#endif
ExitNow();
}
mBroadcastTransmitCount = 0;
}
#if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2
// Verify Enh-ACK integrity by checking its MIC
if ((aError == kErrorNone) && (aAckFrame != nullptr) &&
(ProcessEnhAckSecurity(aFrame, *aAckFrame) != kErrorNone))
{
aError = kErrorNoAck;
}
#endif
}
#endif // #if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
#if OPENTHREAD_CONFIG_MULTI_RADIO
if (!aFrame.IsEmpty())
{
RadioType radio = aFrame.GetRadioType();
RadioTypes requriedRadios = mLinks.GetTxFrames().GetRequiredRadioTypes();
Get<RadioSelector>().UpdateOnSendDone(aFrame, aError);
if (requriedRadios.IsEmpty())
{
// If the "required radio type set" is empty, successful
// tx over any radio link is sufficient for overall tx to
// be considered successful. In this case `mTxError`
// starts as `kErrorAbort` and we update it only when
// it is not already `kErrorNone`.
if (mTxError != kErrorNone)
{
mTxError = aError;
}
}
else
{
// When the "required radio type set" is not empty we
// expect the successful frame tx on all links in this set
// to consider the overall tx successful. In this case,
// `mTxError` starts as `kErrorNone` and we update it
// if tx over any link in the set fails.
if (requriedRadios.Contains(radio) && (aError != kErrorNone))
{
LogDebg("Frame tx failed on required radio link %s with error %s", RadioTypeToString(radio),
ErrorToString(aError));
mTxError = aError;
}
}
// Keep track of radio links on which the frame is sent
// and wait for all radio links to finish.
mTxPendingRadioLinks.Remove(radio);
VerifyOrExit(mTxPendingRadioLinks.IsEmpty());
aError = mTxError;
}
#endif // OPENTHREAD_CONFIG_MULTI_RADIO
// 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 == kErrorNone) && (aAckFrame != nullptr))
{
bool framePending = aAckFrame->GetFramePending();
if (IsEnabled() && framePending)
{
StartOperation(kOperationWaitingForData);
}
LogInfo("Sent data poll, fp:%s", ToYesNo(framePending));
}
mCounters.mTxDataPoll++;
FinishOperation();
Get<DataPollSender>().HandlePollSent(aFrame, aError);
PerformNextOperation();
break;
case kOperationTransmitDataDirect:
mCounters.mTxData++;
if (aError != kErrorNone)
{
mCounters.mTxDirectMaxRetryExpiry++;
}
#if OPENTHREAD_CONFIG_MAC_RETRY_SUCCESS_HISTOGRAM_ENABLE
else if (mLinks.GetTransmitRetries() < OPENTHREAD_CONFIG_MAC_RETRY_SUCCESS_HISTOGRAM_MAX_SIZE_COUNT_DIRECT)
{
mRetryHistogram.mTxDirectRetrySuccess[mLinks.GetTransmitRetries()]++;
}
#endif
DumpDebg("TX", aFrame.GetHeader(), aFrame.GetLength());
FinishOperation();
Get<MeshForwarder>().HandleSentFrame(aFrame, aError);
#if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2
Get<DataPollSender>().ProcessTxDone(aFrame, aAckFrame, aError);
#endif
PerformNextOperation();
break;
#if OPENTHREAD_FTD
#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
case kOperationTransmitDataCsl:
mCounters.mTxData++;
DumpDebg("TX", aFrame.GetHeader(), aFrame.GetLength());
FinishOperation();
Get<CslTxScheduler>().HandleSentFrame(aFrame, aError);
PerformNextOperation();
break;
#endif
case kOperationTransmitDataIndirect:
mCounters.mTxData++;
if (aError != kErrorNone)
{
mCounters.mTxIndirectMaxRetryExpiry++;
}
#if OPENTHREAD_CONFIG_MAC_RETRY_SUCCESS_HISTOGRAM_ENABLE
else if (mLinks.GetTransmitRetries() < OPENTHREAD_CONFIG_MAC_RETRY_SUCCESS_HISTOGRAM_MAX_SIZE_COUNT_INDIRECT)
{
mRetryHistogram.mTxIndirectRetrySuccess[mLinks.GetTransmitRetries()]++;
}
#endif
DumpDebg("TX", aFrame.GetHeader(), aFrame.GetLength());
FinishOperation();
Get<DataPollHandler>().HandleSentFrame(aFrame, aError);
PerformNextOperation();
break;
#endif // OPENTHREAD_FTD
default:
OT_ASSERT(false);
}
ExitNow(); // Added to suppress "unused label exit" warning (in TREL radio only).
exit:
return;
}
void Mac::HandleTimer(Timer &aTimer)
{
aTimer.Get<Mac>().HandleTimer();
}
void Mac::HandleTimer(void)
{
switch (mOperation)
{
case kOperationActiveScan:
PerformActiveScan();
break;
case kOperationWaitingForData:
LogDebg("Data poll timeout");
FinishOperation();
Get<DataPollSender>().HandlePollTimeout();
PerformNextOperation();
break;
case kOperationIdle:
if (!mRxOnWhenIdle)
{
#if OPENTHREAD_CONFIG_MAC_STAY_AWAKE_BETWEEN_FRAGMENTS
if (mDelayingSleep)
{
LogDebg("Sleep delay timeout expired");
mDelayingSleep = false;
UpdateIdleMode();
}
#endif
}
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
else if (IsPending(kOperationTransmitDataCsl))
{
PerformNextOperation();
}
#endif
break;
default:
OT_ASSERT(false);
}
}
Error Mac::ProcessReceiveSecurity(RxFrame &aFrame, const Address &aSrcAddr, Neighbor *aNeighbor)
{
KeyManager & keyManager = Get<KeyManager>();
Error error = kErrorSecurity;
uint8_t securityLevel;
uint8_t keyIdMode;
uint32_t frameCounter;
uint8_t keyid;
uint32_t keySequence = 0;
const KeyMaterial *macKey;
const ExtAddress * extAddress;
VerifyOrExit(aFrame.GetSecurityEnabled(), error = kErrorNone);
IgnoreError(aFrame.GetSecurityLevel(securityLevel));
VerifyOrExit(securityLevel == Frame::kSecEncMic32);
IgnoreError(aFrame.GetFrameCounter(frameCounter));
LogDebg("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);
IgnoreError(aFrame.GetKeyId(keyid));
keyid--;
if (keyid == (keyManager.GetCurrentKeySequence() & 0x7f))
{
keySequence = keyManager.GetCurrentKeySequence();
macKey = mLinks.GetCurrentMacKey(aFrame);
}
else if (keyid == ((keyManager.GetCurrentKeySequence() - 1) & 0x7f))
{
keySequence = keyManager.GetCurrentKeySequence() - 1;
macKey = mLinks.GetTemporaryMacKey(aFrame, keySequence);
}
else if (keyid == ((keyManager.GetCurrentKeySequence() + 1) & 0x7f))
{
keySequence = keyManager.GetCurrentKeySequence() + 1;
macKey = mLinks.GetTemporaryMacKey(aFrame, keySequence);
}
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());
if (keySequence == aNeighbor->GetKeySequence())
{
uint32_t neighborFrameCounter;
#if OPENTHREAD_CONFIG_MULTI_RADIO
neighborFrameCounter = aNeighbor->GetLinkFrameCounters().Get(aFrame.GetRadioType());
#else
neighborFrameCounter = aNeighbor->GetLinkFrameCounters().Get();
#endif
// If frame counter is one off, then frame is a duplicate.
VerifyOrExit((frameCounter + 1) != neighborFrameCounter, error = kErrorDuplicated);
VerifyOrExit(frameCounter >= neighborFrameCounter);
}
}
extAddress = &aSrcAddr.GetExtended();
break;
case Frame::kKeyIdMode2:
macKey = &mMode2KeyMaterial;
extAddress = &AsCoreType(&sMode2ExtAddress);
break;
default:
ExitNow();
}
SuccessOrExit(aFrame.ProcessReceiveAesCcm(*extAddress, *macKey));
if ((keyIdMode == Frame::kKeyIdMode1) && aNeighbor->IsStateValid())
{
if (aNeighbor->GetKeySequence() != keySequence)
{
aNeighbor->SetKeySequence(keySequence);
aNeighbor->SetMleFrameCounter(0);
}
#if OPENTHREAD_CONFIG_MULTI_RADIO
aNeighbor->GetLinkFrameCounters().Set(aFrame.GetRadioType(), frameCounter + 1);
#else
aNeighbor->GetLinkFrameCounters().Set(frameCounter + 1);
#endif
#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) && OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
#if OPENTHREAD_CONFIG_MULTI_RADIO
if (aFrame.GetRadioType() == kRadioTypeIeee802154)
#endif
{
if ((frameCounter + 1) > aNeighbor->GetLinkAckFrameCounter())
{
aNeighbor->SetLinkAckFrameCounter(frameCounter + 1);
}
}
#endif
if (keySequence > keyManager.GetCurrentKeySequence())
{
keyManager.SetCurrentKeySequence(keySequence);
}
}
error = kErrorNone;
exit:
return error;
}
#if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2
Error Mac::ProcessEnhAckSecurity(TxFrame &aTxFrame, RxFrame &aAckFrame)
{
Error error = kErrorSecurity;
uint8_t securityLevel;
uint8_t txKeyId;
uint8_t ackKeyId;
uint8_t keyIdMode;
uint32_t frameCounter;
Address srcAddr;
Address dstAddr;
Neighbor * neighbor = nullptr;
KeyManager & keyManager = Get<KeyManager>();
const KeyMaterial *macKey;
VerifyOrExit(aAckFrame.GetSecurityEnabled(), error = kErrorNone);
VerifyOrExit(aAckFrame.IsVersion2015());
IgnoreError(aAckFrame.GetSecurityLevel(securityLevel));
VerifyOrExit(securityLevel == Frame::kSecEncMic32);
IgnoreError(aAckFrame.GetKeyIdMode(keyIdMode));
VerifyOrExit(keyIdMode == Frame::kKeyIdMode1, error = kErrorNone);
IgnoreError(aTxFrame.GetKeyId(txKeyId));
IgnoreError(aAckFrame.GetKeyId(ackKeyId));
VerifyOrExit(txKeyId == ackKeyId);
IgnoreError(aAckFrame.GetFrameCounter(frameCounter));
LogDebg("Rx security - Ack frame counter %u", frameCounter);
IgnoreError(aAckFrame.GetSrcAddr(srcAddr));
if (!srcAddr.IsNone())
{
neighbor = Get<NeighborTable>().FindNeighbor(srcAddr);
}
else
{
IgnoreError(aTxFrame.GetDstAddr(dstAddr));
if (!dstAddr.IsNone())
{
// Get neighbor from destination address of transmitted frame
neighbor = Get<NeighborTable>().FindNeighbor(dstAddr);
}
}
if (!srcAddr.IsExtended() && neighbor != nullptr)
{
srcAddr.SetExtended(neighbor->GetExtAddress());
}
VerifyOrExit(srcAddr.IsExtended() && neighbor != nullptr);
ackKeyId--;
if (ackKeyId == (keyManager.GetCurrentKeySequence() & 0x7f))
{
macKey = &mLinks.GetSubMac().GetCurrentMacKey();
}
else if (ackKeyId == ((keyManager.GetCurrentKeySequence() - 1) & 0x7f))
{
macKey = &mLinks.GetSubMac().GetPreviousMacKey();
}
else if (ackKeyId == ((keyManager.GetCurrentKeySequence() + 1) & 0x7f))
{
macKey = &mLinks.GetSubMac().GetNextMacKey();
}
else
{
ExitNow();
}
if (neighbor->IsStateValid())
{
VerifyOrExit(frameCounter >= neighbor->GetLinkAckFrameCounter());
}
error = aAckFrame.ProcessReceiveAesCcm(srcAddr.GetExtended(), *macKey);
SuccessOrExit(error);
if (neighbor->IsStateValid())
{
neighbor->SetLinkAckFrameCounter(frameCounter + 1);
}
exit:
if (error != kErrorNone)
{
LogInfo("Frame tx attempt failed, error: Enh-ACK security check fail");
}
return error;
}
#endif // OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2
void Mac::HandleReceivedFrame(RxFrame *aFrame, Error aError)
{
Address srcaddr;
Address dstaddr;
PanId panid;
Neighbor *neighbor;
Error error = aError;
mCounters.mRxTotal++;
SuccessOrExit(error);
VerifyOrExit(aFrame != nullptr, error = kErrorNoFrameReceived);
VerifyOrExit(IsEnabled(), error = kErrorInvalidState);
// 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<NeighborTable>().FindNeighbor(srcaddr);
// Destination Address Filtering
switch (dstaddr.GetType())
{
case Address::kTypeNone:
break;
case Address::kTypeShort:
VerifyOrExit((mRxOnWhenIdle && dstaddr.IsBroadcast()) || dstaddr.GetShort() == GetShortAddress(),
error = kErrorDestinationAddressFiltered);
#if OPENTHREAD_FTD
// Allow multicasts from neighbor routers if FTD
if (neighbor == nullptr && dstaddr.IsBroadcast() && Get<Mle::MleRouter>().IsFullThreadDevice())
{
neighbor = Get<NeighborTable>().FindRxOnlyNeighborRouter(srcaddr);
}
#endif
break;
case Address::kTypeExtended:
VerifyOrExit(dstaddr.GetExtended() == GetExtAddress(), error = kErrorDestinationAddressFiltered);
break;
}
// Verify destination PAN ID if present
if (kErrorNone == aFrame->GetDstPanId(panid))
{
VerifyOrExit(panid == kShortAddrBroadcast || panid == mPanId, error = kErrorDestinationAddressFiltered);
}
// Source Address Filtering
switch (srcaddr.GetType())
{
case Address::kTypeNone:
break;
case Address::kTypeShort:
LogDebg("Received frame from short address 0x%04x", srcaddr.GetShort());
VerifyOrExit(neighbor != nullptr, error = kErrorUnknownNeighbor);
srcaddr.SetExtended(neighbor->GetExtAddress());
OT_FALL_THROUGH;
case Address::kTypeExtended:
// Duplicate Address Protection
VerifyOrExit(srcaddr.GetExtended() != GetExtAddress(), error = kErrorInvalidSourceAddress);
#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 kErrorDuplicated:
// 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_FALL_THROUGH;
case kErrorNone:
break;
default:
ExitNow();
}
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
if (aFrame->GetVersion() == Frame::kFcfFrameVersion2015)
{
ProcessCsl(*aFrame, srcaddr);
}
#endif
Get<DataPollSender>().ProcessRxFrame(*aFrame);
if (neighbor != nullptr)
{
neighbor->GetLinkInfo().AddRss(aFrame->GetRssi());
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
neighbor->AggregateLinkMetrics(/* aSeriesId */ 0, aFrame->GetType(), aFrame->GetLqi(), aFrame->GetRssi());
#endif
if (aFrame->GetSecurityEnabled())
{
uint8_t keyIdMode;
IgnoreError(aFrame->GetKeyIdMode(keyIdMode));
if (keyIdMode == Frame::kKeyIdMode1)
{
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 = kErrorDrop);
break;
default:
ExitNow(error = kErrorUnknownNeighbor);
}
#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
}
#if OPENTHREAD_CONFIG_MULTI_RADIO
Get<RadioSelector>().UpdateOnReceive(*neighbor, aFrame->GetRadioType(), /* aIsDuplicate */ false);
#endif
}
}
switch (mOperation)
{
case kOperationActiveScan:
if (aFrame->GetType() == Frame::kFcfFrameBeacon)
{
mCounters.mRxBeacon++;
ReportActiveScanResult(aFrame);
ExitNow();
}
OT_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;
LogDebg("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 = kErrorNone);
}
break;
case Frame::kFcfFrameBeacon:
mCounters.mRxBeacon++;
break;
case Frame::kFcfFrameData:
mCounters.mRxData++;
break;
default:
mCounters.mRxOther++;
ExitNow();
}
DumpDebg("RX", aFrame->GetHeader(), aFrame->GetLength());
Get<MeshForwarder>().HandleReceivedFrame(*aFrame);
UpdateIdleMode();
exit:
if (error != kErrorNone)
{
LogFrameRxFailure(aFrame, error);
switch (error)
{
case kErrorSecurity:
mCounters.mRxErrSec++;
break;
case kErrorFcs:
mCounters.mRxErrFcs++;
break;
case kErrorNoFrameReceived:
mCounters.mRxErrNoFrame++;
break;
case kErrorUnknownNeighbor:
mCounters.mRxErrUnknownNeighbor++;
break;
case kErrorInvalidSourceAddress:
mCounters.mRxErrInvalidSrcAddr++;
break;
case kErrorAddressFiltered:
mCounters.mRxAddressFiltered++;
break;
case kErrorDestinationAddressFiltered:
mCounters.mRxDestAddrFiltered++;
break;
case kErrorDuplicated:
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++;
LogInfo("Received Beacon Request");
if (ShouldSendBeacon())
{
#if OPENTHREAD_CONFIG_MULTI_RADIO
mTxBeaconRadioLinks.Add(aFrame.GetRadioType());
#endif
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
mLinks.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 OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
const char *Mac::OperationToString(Operation aOperation)
{
static const char *const kOperationStrings[] = {
"Idle", // (0) kOperationIdle
"ActiveScan", // (1) kOperationActiveScan
"EnergyScan", // (2) kOperationEnergyScan
"TransmitBeacon", // (3) kOperationTransmitBeacon
"TransmitDataDirect", // (4) kOperationTransmitDataDirect
"TransmitPoll", // (5) kOperationTransmitPoll
"WaitingForData", // (6) kOperationWaitingForData
#if OPENTHREAD_FTD
"TransmitDataIndirect", // (7) kOperationTransmitDataIndirect
#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
"TransmitDataCsl", // (8) kOperationTransmitDataCsl
#endif
#endif
};
static_assert(kOperationIdle == 0, "kOperationIdle value is incorrect");
static_assert(kOperationActiveScan == 1, "kOperationActiveScan value is incorrect");
static_assert(kOperationEnergyScan == 2, "kOperationEnergyScan value is incorrect");
static_assert(kOperationTransmitBeacon == 3, "kOperationTransmitBeacon value is incorrect");
static_assert(kOperationTransmitDataDirect == 4, "kOperationTransmitDataDirect value is incorrect");
static_assert(kOperationTransmitPoll == 5, "kOperationTransmitPoll value is incorrect");
static_assert(kOperationWaitingForData == 6, "kOperationWaitingForData value is incorrect");
#if OPENTHREAD_FTD
static_assert(kOperationTransmitDataIndirect == 7, "kOperationTransmitDataIndirect value is incorrect");
#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
static_assert(kOperationTransmitDataCsl == 8, "TransmitDataCsl value is incorrect");
#endif
#endif
return kOperationStrings[aOperation];
}
void Mac::LogFrameRxFailure(const RxFrame *aFrame, Error aError) const
{
LogLevel logLevel;
switch (aError)
{
case kErrorAbort:
case kErrorNoFrameReceived:
case kErrorAddressFiltered:
case kErrorDestinationAddressFiltered:
logLevel = kLogLevelDebg;
break;
default:
logLevel = kLogLevelInfo;
break;
}
if (aFrame == nullptr)
{
LogAt(logLevel, "Frame rx failed, error:%s", ErrorToString(aError));
}
else
{
LogAt(logLevel, "Frame rx failed, error:%s, %s", ErrorToString(aError), aFrame->ToInfoString().AsCString());
}
}
void Mac::LogFrameTxFailure(const TxFrame &aFrame, Error aError, uint8_t aRetryCount, bool aWillRetx) const
{
#if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE && OPENTHREAD_CONFIG_MULTI_RADIO
if (aFrame.GetRadioType() == kRadioTypeIeee802154)
#elif OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
if (true)
#else
if (false)
#endif
{
uint8_t maxAttempts = aFrame.GetMaxFrameRetries() + 1;
uint8_t curAttempt = aWillRetx ? (aRetryCount + 1) : maxAttempts;
LogInfo("Frame tx attempt %d/%d failed, error:%s, %s", curAttempt, maxAttempts, ErrorToString(aError),
aFrame.ToInfoString().AsCString());
}
else
{
LogInfo("Frame tx failed, error:%s, %s", ErrorToString(aError), aFrame.ToInfoString().AsCString());
}
}
void Mac::LogBeacon(const char *aActionText) const
{
LogInfo("%s Beacon", aActionText);
}
#else // #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
void Mac::LogFrameRxFailure(const RxFrame *, Error) const
{
}
void Mac::LogBeacon(const char *) const
{
}
void Mac::LogFrameTxFailure(const TxFrame &, Error, uint8_t, bool) const
{
}
#endif // #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
// 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);
cur += sizeof(VendorIeHeader);
offset = static_cast<uint8_t>(cur - base);
exit:
return offset;
}
#endif
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
void Mac::SetCslChannel(uint8_t aChannel)
{
VerifyOrExit(GetCslChannel() != aChannel);
mLinks.GetSubMac().SetCslChannel(aChannel);
mLinks.GetSubMac().SetCslChannelSpecified(aChannel != 0);
if (IsCslEnabled())
{
Get<Mle::Mle>().ScheduleChildUpdateRequest();
}
exit:
return;
}
void Mac::SetCslPeriod(uint16_t aPeriod)
{
mLinks.GetSubMac().SetCslPeriod(aPeriod);
Get<DataPollSender>().RecalculatePollPeriod();
if ((GetCslPeriod() == 0) || IsCslEnabled())
{
IgnoreError(Get<Radio>().EnableCsl(GetCslPeriod(), Get<Mle::Mle>().GetParent().GetRloc16(),
&Get<Mle::Mle>().GetParent().GetExtAddress()));
}
if (IsCslEnabled())
{
Get<Mle::Mle>().ScheduleChildUpdateRequest();
}
UpdateIdleMode();
}
bool Mac::IsCslEnabled(void) const
{
return !GetRxOnWhenIdle() && IsCslCapable();
}
bool Mac::IsCslCapable(void) const
{
return (GetCslPeriod() > 0) && Get<Mle::MleRouter>().IsChild() &&
Get<Mle::Mle>().GetParent().IsEnhancedKeepAliveSupported();
}
#endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
void Mac::ProcessCsl(const RxFrame &aFrame, const Address &aSrcAddr)
{
const uint8_t *cur = aFrame.GetHeaderIe(CslIe::kHeaderIeId);
Child * child = Get<ChildTable>().FindChild(aSrcAddr, Child::kInStateAnyExceptInvalid);
const CslIe * csl;
VerifyOrExit(cur != nullptr && child != nullptr && aFrame.GetSecurityEnabled());
csl = reinterpret_cast<const CslIe *>(cur + sizeof(HeaderIe));
child->SetCslPeriod(csl->GetPeriod());
// Use ceiling to ensure the the time diff will be within kUsPerTenSymbols
child->SetCslPhase(csl->GetPhase());
child->SetCslSynchronized(true);
child->SetCslLastHeard(TimerMilli::GetNow());
child->SetLastRxTimestamp(aFrame.GetTimestamp());
LogDebg("Timestamp=%u Sequence=%u CslPeriod=%hu CslPhase=%hu TransmitPhase=%hu",
static_cast<uint32_t>(aFrame.GetTimestamp()), aFrame.GetSequence(), csl->GetPeriod(), csl->GetPhase(),
child->GetCslPhase());
Get<CslTxScheduler>().Update();
exit:
return;
}
#endif // OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
void Mac::ProcessEnhAckProbing(const RxFrame &aFrame, const Neighbor &aNeighbor)
{
constexpr uint8_t kEnhAckProbingIeMaxLen = 2;
const HeaderIe *enhAckProbingIe =
reinterpret_cast<const HeaderIe *>(aFrame.GetThreadIe(ThreadIe::kEnhAckProbingIe));
const uint8_t *data =
reinterpret_cast<const uint8_t *>(enhAckProbingIe) + sizeof(HeaderIe) + sizeof(VendorIeHeader);
uint8_t dataLen = 0;
VerifyOrExit(enhAckProbingIe != nullptr);
dataLen = enhAckProbingIe->GetLength() - sizeof(VendorIeHeader);
VerifyOrExit(dataLen <= kEnhAckProbingIeMaxLen);
Get<LinkMetrics::LinkMetrics>().ProcessEnhAckIeData(data, dataLen, aNeighbor);
exit:
return;
}
#endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
#if OPENTHREAD_CONFIG_MAC_FILTER_ENABLE && OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
void Mac::SetRadioFilterEnabled(bool aFilterEnabled)
{
mLinks.GetSubMac().SetRadioFilterEnabled(aFilterEnabled);
UpdateIdleMode();
}
#endif
} // namespace Mac
} // namespace ot