2368 lines
65 KiB
C++
2368 lines
65 KiB
C++
/*
|
|
* 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;
|
|
#if OPENTHREAD_CONFIG_MAC_BEACON_PAYLOAD_PARSING_ENABLE
|
|
const BeaconPayload *beaconPayload = nullptr;
|
|
const Beacon * beacon = nullptr;
|
|
uint16_t payloadLength;
|
|
#endif
|
|
|
|
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();
|
|
|
|
#if OPENTHREAD_CONFIG_MAC_BEACON_PAYLOAD_PARSING_ENABLE
|
|
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(AsCoreType(&aResult.mNetworkName).Set(beaconPayload->GetNetworkName()));
|
|
VerifyOrExit(IsValidUtf8String(aResult.mNetworkName.m8), error = kErrorParse);
|
|
aResult.mExtendedPanId = beaconPayload->GetExtendedPanId();
|
|
}
|
|
#endif
|
|
|
|
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));
|
|
|
|
// 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_MAC_OUTGOING_BEACON_PAYLOAD_ENABLE
|
|
uint8_t beaconLength;
|
|
BeaconPayload *beaconPayload = nullptr;
|
|
#endif
|
|
|
|
#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();
|
|
|
|
#if OPENTHREAD_CONFIG_MAC_OUTGOING_BEACON_PAYLOAD_ENABLE
|
|
beaconLength = sizeof(*beacon);
|
|
|
|
beaconPayload = reinterpret_cast<BeaconPayload *>(beacon->GetPayload());
|
|
|
|
beaconPayload->Init();
|
|
|
|
if (IsJoinable())
|
|
{
|
|
beaconPayload->SetJoiningPermitted();
|
|
}
|
|
else
|
|
{
|
|
beaconPayload->ClearJoiningPermitted();
|
|
}
|
|
|
|
beaconPayload->SetNetworkName(Get<MeshCoP::NetworkNameManager>().GetNetworkName().GetAsData());
|
|
beaconPayload->SetExtendedPanId(Get<MeshCoP::ExtendedPanIdManager>().GetExtPanId());
|
|
|
|
beaconLength += sizeof(*beaconPayload);
|
|
|
|
frame->SetPayloadLength(beaconLength);
|
|
#endif
|
|
|
|
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
|