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