blob: fa67446df98bc9907418f642e6a8bb6da291fb5d [file] [log] [blame]
/*
* Copyright (c) 2016, The OpenThread Authors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @file
* This file implements MLE functionality required for the Thread Child, Router and Leader roles.
*/
#include "mle.hpp"
#include <openthread/platform/radio.h>
#include <openthread/platform/time.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/num_utils.hpp"
#include "common/random.hpp"
#include "common/serial_number.hpp"
#include "common/settings.hpp"
#include "meshcop/meshcop.hpp"
#include "meshcop/meshcop_tlvs.hpp"
#include "net/netif.hpp"
#include "net/udp6.hpp"
#include "thread/address_resolver.hpp"
#include "thread/key_manager.hpp"
#include "thread/link_metrics.hpp"
#include "thread/mle_router.hpp"
#include "thread/thread_netif.hpp"
#include "thread/time_sync_service.hpp"
#include "thread/version.hpp"
using ot::Encoding::BigEndian::HostSwap16;
namespace ot {
namespace Mle {
RegisterLogModule("Mle");
const otMeshLocalPrefix Mle::kMeshLocalPrefixInit = {
{0xfd, 0xde, 0xad, 0x00, 0xbe, 0xef, 0x00, 0x00},
};
Mle::Mle(Instance &aInstance)
: InstanceLocator(aInstance)
, mRetrieveNewNetworkData(false)
, mRequestRouteTlv(false)
, mHasRestored(false)
, mReceivedResponseFromParent(false)
, mInitiallyAttachedAsSleepy(false)
#if OPENTHREAD_FTD
, mWasLeader(false)
#endif
, mRole(kRoleDisabled)
, mDeviceMode(DeviceMode::kModeRxOnWhenIdle)
, mAttachState(kAttachStateIdle)
, mReattachState(kReattachStop)
, mAttachMode(kAnyPartition)
, mDataRequestState(kDataRequestNone)
, mAddressRegistrationMode(kAppendAllAddresses)
, mChildUpdateRequestState(kChildUpdateRequestNone)
, mParentRequestCounter(0)
, mChildUpdateAttempts(0)
, mDataRequestAttempts(0)
, mAnnounceChannel(0)
, mAlternateChannel(0)
, mRloc16(Mac::kShortAddrInvalid)
, mPreviousParentRloc(Mac::kShortAddrInvalid)
, mAttachCounter(0)
, mAnnounceDelay(kAnnounceTimeout)
, mAlternatePanId(Mac::kPanIdBroadcast)
, mTimeout(kDefaultChildTimeout)
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
, mCslTimeout(kDefaultCslTimeout)
#endif
, mAlternateTimestamp(0)
, mNeighborTable(aInstance)
, mSocket(aInstance)
#if OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE
, mParentSearch(aInstance)
#endif
, mAttachTimer(aInstance)
, mDelayedResponseTimer(aInstance)
, mMessageTransmissionTimer(aInstance)
, mDetachGracefullyTimer(aInstance)
{
mParent.Init(aInstance);
mParentCandidate.Init(aInstance);
mLeaderData.Clear();
mParent.Clear();
mParentCandidate.Clear();
ResetCounters();
mLinkLocal64.InitAsThreadOrigin(/* aPreferred */ true);
mLinkLocal64.GetAddress().SetToLinkLocalAddress(Get<Mac::Mac>().GetExtAddress());
mLeaderAloc.InitAsThreadOriginRealmLocalScope();
mMeshLocal64.InitAsThreadOriginRealmLocalScope();
mMeshLocal64.GetAddress().GetIid().GenerateRandom();
mMeshLocal16.InitAsThreadOriginRealmLocalScope();
mMeshLocal16.GetAddress().GetIid().SetToLocator(0);
mMeshLocal16.mRloc = true;
mLinkLocalAllThreadNodes.Clear();
mLinkLocalAllThreadNodes.GetAddress().mFields.m16[0] = HostSwap16(0xff32);
mLinkLocalAllThreadNodes.GetAddress().mFields.m16[7] = HostSwap16(0x0001);
mRealmLocalAllThreadNodes.Clear();
mRealmLocalAllThreadNodes.GetAddress().mFields.m16[0] = HostSwap16(0xff33);
mRealmLocalAllThreadNodes.GetAddress().mFields.m16[7] = HostSwap16(0x0001);
SetMeshLocalPrefix(AsCoreType(&kMeshLocalPrefixInit));
// `SetMeshLocalPrefix()` also adds the Mesh-Local EID and subscribes
// to the Link- and Realm-Local All Thread Nodes multicast addresses.
}
Error Mle::Enable(void)
{
Error error = kErrorNone;
UpdateLinkLocalAddress();
SuccessOrExit(error = mSocket.Open(&Mle::HandleUdpReceive, this));
SuccessOrExit(error = mSocket.Bind(kUdpPort));
#if OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE
mParentSearch.StartTimer();
#endif
exit:
return error;
}
void Mle::ScheduleChildUpdateRequest(void)
{
mChildUpdateRequestState = kChildUpdateRequestPending;
ScheduleMessageTransmissionTimer();
}
Error Mle::Disable(void)
{
Error error = kErrorNone;
Stop(kKeepNetworkDatasets);
SuccessOrExit(error = mSocket.Close());
Get<ThreadNetif>().RemoveUnicastAddress(mLinkLocal64);
exit:
return error;
}
Error Mle::Start(StartMode aMode)
{
Error error = kErrorNone;
// cannot bring up the interface if IEEE 802.15.4 promiscuous mode is enabled
VerifyOrExit(!Get<Radio>().GetPromiscuous(), error = kErrorInvalidState);
VerifyOrExit(Get<ThreadNetif>().IsUp(), error = kErrorInvalidState);
if (Get<Mac::Mac>().GetPanId() == Mac::kPanIdBroadcast)
{
Get<Mac::Mac>().SetPanId(Mac::GenerateRandomPanId());
}
SetStateDetached();
ApplyMeshLocalPrefix();
SetRloc16(GetRloc16());
mAttachCounter = 0;
Get<KeyManager>().Start();
if (aMode == kNormalAttach)
{
mReattachState = kReattachStart;
}
if ((aMode == kAnnounceAttach) || (GetRloc16() == Mac::kShortAddrInvalid))
{
Attach(kAnyPartition);
}
#if OPENTHREAD_FTD
else if (IsActiveRouter(GetRloc16()))
{
if (Get<MleRouter>().BecomeRouter(ThreadStatusTlv::kTooFewRouters) != kErrorNone)
{
Attach(kAnyPartition);
}
}
#endif
else
{
mChildUpdateAttempts = 0;
IgnoreError(SendChildUpdateRequest());
}
exit:
return error;
}
void Mle::Stop(StopMode aMode)
{
if (aMode == kUpdateNetworkDatasets)
{
Get<MeshCoP::ActiveDatasetManager>().HandleDetach();
Get<MeshCoP::PendingDatasetManager>().HandleDetach();
}
VerifyOrExit(!IsDisabled());
Get<KeyManager>().Stop();
SetStateDetached();
Get<ThreadNetif>().UnsubscribeMulticast(mRealmLocalAllThreadNodes);
Get<ThreadNetif>().UnsubscribeMulticast(mLinkLocalAllThreadNodes);
Get<ThreadNetif>().RemoveUnicastAddress(mMeshLocal16);
Get<ThreadNetif>().RemoveUnicastAddress(mMeshLocal64);
SetRole(kRoleDisabled);
exit:
mDetachGracefullyTimer.Stop();
if (mDetachGracefullyCallback.IsSet())
{
Callback<otDetachGracefullyCallback> callbackCopy = mDetachGracefullyCallback;
mDetachGracefullyCallback.Clear();
callbackCopy.Invoke();
}
}
void Mle::ResetCounters(void)
{
memset(&mCounters, 0, sizeof(mCounters));
#if OPENTHREAD_CONFIG_UPTIME_ENABLE
mLastUpdatedTimestamp = Get<Uptime>().GetUptime();
#endif
}
#if OPENTHREAD_CONFIG_UPTIME_ENABLE
void Mle::UpdateRoleTimeCounters(DeviceRole aRole)
{
uint64_t currentUptimeMsec = Get<Uptime>().GetUptime();
uint64_t durationMsec = currentUptimeMsec - mLastUpdatedTimestamp;
mLastUpdatedTimestamp = currentUptimeMsec;
mCounters.mTrackedTime += durationMsec;
switch (aRole)
{
case kRoleDisabled:
mCounters.mDisabledTime += durationMsec;
break;
case kRoleDetached:
mCounters.mDetachedTime += durationMsec;
break;
case kRoleChild:
mCounters.mChildTime += durationMsec;
break;
case kRoleRouter:
mCounters.mRouterTime += durationMsec;
break;
case kRoleLeader:
mCounters.mLeaderTime += durationMsec;
break;
}
}
#endif
void Mle::SetRole(DeviceRole aRole)
{
DeviceRole oldRole = mRole;
SuccessOrExit(Get<Notifier>().Update(mRole, aRole, kEventThreadRoleChanged));
LogNote("Role %s -> %s", RoleToString(oldRole), RoleToString(mRole));
#if OPENTHREAD_CONFIG_UPTIME_ENABLE
UpdateRoleTimeCounters(oldRole);
#endif
switch (mRole)
{
case kRoleDisabled:
mCounters.mDisabledRole++;
break;
case kRoleDetached:
mCounters.mDetachedRole++;
break;
case kRoleChild:
mCounters.mChildRole++;
break;
case kRoleRouter:
mCounters.mRouterRole++;
break;
case kRoleLeader:
mCounters.mLeaderRole++;
break;
}
// If the previous state is disabled, the parent can be in kStateRestored.
if (!IsChild() && oldRole != kRoleDisabled)
{
mParent.SetState(Neighbor::kStateInvalid);
}
if ((oldRole == kRoleDetached) && IsChild())
{
// On transition from detached to child, we remember whether we
// attached as sleepy or not. This is then used to determine
// whether or not we need to re-attach on mode changes between
// rx-on and sleepy (rx-off). If we initially attach as sleepy,
// then rx-on/off mode changes are allowed without re-attach.
mInitiallyAttachedAsSleepy = !GetDeviceMode().IsRxOnWhenIdle();
}
exit:
return;
}
void Mle::SetAttachState(AttachState aState)
{
VerifyOrExit(aState != mAttachState);
LogInfo("AttachState %s -> %s", AttachStateToString(mAttachState), AttachStateToString(aState));
mAttachState = aState;
exit:
return;
}
void Mle::Restore(void)
{
Settings::NetworkInfo networkInfo;
Settings::ParentInfo parentInfo;
IgnoreError(Get<MeshCoP::ActiveDatasetManager>().Restore());
IgnoreError(Get<MeshCoP::PendingDatasetManager>().Restore());
#if OPENTHREAD_CONFIG_DUA_ENABLE
Get<DuaManager>().Restore();
#endif
SuccessOrExit(Get<Settings>().Read(networkInfo));
Get<KeyManager>().SetCurrentKeySequence(networkInfo.GetKeySequence());
Get<KeyManager>().SetMleFrameCounter(networkInfo.GetMleFrameCounter());
Get<KeyManager>().SetAllMacFrameCounters(networkInfo.GetMacFrameCounter(), /* aSetIfLarger */ false);
#if OPENTHREAD_MTD
mDeviceMode.Set(networkInfo.GetDeviceMode() & ~DeviceMode::kModeFullThreadDevice);
#else
mDeviceMode.Set(networkInfo.GetDeviceMode());
#endif
// force re-attach when version mismatch.
VerifyOrExit(networkInfo.GetVersion() == kThreadVersion);
switch (networkInfo.GetRole())
{
case kRoleChild:
case kRoleRouter:
case kRoleLeader:
break;
default:
ExitNow();
}
#if OPENTHREAD_MTD
if (!IsActiveRouter(networkInfo.GetRloc16()))
#endif
{
Get<Mac::Mac>().SetShortAddress(networkInfo.GetRloc16());
mRloc16 = networkInfo.GetRloc16();
}
Get<Mac::Mac>().SetExtAddress(networkInfo.GetExtAddress());
mMeshLocal64.GetAddress().SetIid(networkInfo.GetMeshLocalIid());
if (networkInfo.GetRloc16() == Mac::kShortAddrInvalid)
{
ExitNow();
}
if (!IsActiveRouter(networkInfo.GetRloc16()))
{
if (Get<Settings>().Read(parentInfo) != kErrorNone)
{
// If the restored RLOC16 corresponds to an end-device, it
// is expected that the `ParentInfo` settings to be valid
// as well. The device can still recover from such an invalid
// setting by skipping the re-attach ("Child Update Request"
// exchange) and going through the full attach process.
LogWarn("Invalid settings - no saved parent info with valid end-device RLOC16 0x%04x",
networkInfo.GetRloc16());
ExitNow();
}
mParent.Clear();
mParent.SetExtAddress(parentInfo.GetExtAddress());
mParent.SetVersion(parentInfo.GetVersion());
mParent.SetDeviceMode(DeviceMode(DeviceMode::kModeFullThreadDevice | DeviceMode::kModeRxOnWhenIdle |
DeviceMode::kModeFullNetworkData));
mParent.SetRloc16(Rloc16FromRouterId(RouterIdFromRloc16(networkInfo.GetRloc16())));
mParent.SetState(Neighbor::kStateRestored);
mPreviousParentRloc = mParent.GetRloc16();
}
#if OPENTHREAD_FTD
else
{
Get<MleRouter>().SetRouterId(RouterIdFromRloc16(GetRloc16()));
Get<MleRouter>().SetPreviousPartitionId(networkInfo.GetPreviousPartitionId());
Get<ChildTable>().Restore();
}
mWasLeader = networkInfo.GetRole() == kRoleLeader;
#endif
// Successfully restored the network information from non-volatile settings after boot.
mHasRestored = true;
exit:
return;
}
Error Mle::Store(void)
{
Error error = kErrorNone;
Settings::NetworkInfo networkInfo;
networkInfo.Init();
if (IsAttached())
{
// Only update network information while we are attached to
// avoid losing/overwriting previous information when a reboot
// occurs after a message is sent but before attaching.
networkInfo.SetRole(mRole);
networkInfo.SetRloc16(GetRloc16());
networkInfo.SetPreviousPartitionId(mLeaderData.GetPartitionId());
networkInfo.SetExtAddress(Get<Mac::Mac>().GetExtAddress());
networkInfo.SetMeshLocalIid(mMeshLocal64.GetAddress().GetIid());
networkInfo.SetVersion(kThreadVersion);
if (IsChild())
{
Settings::ParentInfo parentInfo;
parentInfo.Init();
parentInfo.SetExtAddress(mParent.GetExtAddress());
parentInfo.SetVersion(mParent.GetVersion());
SuccessOrExit(error = Get<Settings>().Save(parentInfo));
}
}
else
{
// When not attached, read out any previous saved `NetworkInfo`.
// If there is none, it indicates that device was never attached
// before. In that case, no need to save any info (note that on
// a device reset the MLE/MAC frame counters would reset but
// device also starts with a new randomly generated extended
// address. If there is a previously saved `NetworkInfo`, we
// just update the key sequence and MAC and MLE frame counters.
SuccessOrExit(Get<Settings>().Read(networkInfo));
}
networkInfo.SetKeySequence(Get<KeyManager>().GetCurrentKeySequence());
networkInfo.SetMleFrameCounter(Get<KeyManager>().GetMleFrameCounter() + kStoreFrameCounterAhead);
networkInfo.SetMacFrameCounter(Get<KeyManager>().GetMaximumMacFrameCounter() + kStoreFrameCounterAhead);
networkInfo.SetDeviceMode(mDeviceMode.Get());
SuccessOrExit(error = Get<Settings>().Save(networkInfo));
Get<KeyManager>().SetStoredMleFrameCounter(networkInfo.GetMleFrameCounter());
Get<KeyManager>().SetStoredMacFrameCounter(networkInfo.GetMacFrameCounter());
LogDebg("Store Network Information");
exit:
return error;
}
Error Mle::BecomeDetached(void)
{
Error error = kErrorNone;
VerifyOrExit(!IsDisabled(), error = kErrorInvalidState);
// In case role is already detached and attach state is `kAttachStateStart`
// (i.e., waiting to start an attach attempt), there is no need to make any
// changes.
VerifyOrExit(!IsDetached() || mAttachState != kAttachStateStart);
// not in reattach stage after reset
if (mReattachState == kReattachStop)
{
Get<MeshCoP::PendingDatasetManager>().HandleDetach();
}
#if OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE
mParentSearch.SetRecentlyDetached();
#endif
SetStateDetached();
mParent.SetState(Neighbor::kStateInvalid);
SetRloc16(Mac::kShortAddrInvalid);
Attach(kAnyPartition);
exit:
return error;
}
Error Mle::BecomeChild(void)
{
Error error = kErrorNone;
VerifyOrExit(!IsDisabled(), error = kErrorInvalidState);
VerifyOrExit(!IsAttaching(), error = kErrorBusy);
Attach(kAnyPartition);
exit:
return error;
}
Error Mle::SearchForBetterParent(void)
{
Error error = kErrorNone;
VerifyOrExit(IsChild(), error = kErrorInvalidState);
Attach(kBetterParent);
exit:
return error;
}
void Mle::Attach(AttachMode aMode)
{
VerifyOrExit(!IsDisabled() && !IsAttaching());
if (!IsDetached())
{
mAttachCounter = 0;
}
if (mReattachState == kReattachStart)
{
if (Get<MeshCoP::ActiveDatasetManager>().Restore() == kErrorNone)
{
mReattachState = kReattachActive;
}
else
{
mReattachState = kReattachStop;
}
}
mParentCandidate.Clear();
SetAttachState(kAttachStateStart);
mAttachMode = aMode;
if (aMode != kBetterPartition)
{
#if OPENTHREAD_FTD
if (IsFullThreadDevice())
{
Get<MleRouter>().StopAdvertiseTrickleTimer();
}
#endif
}
else
{
mCounters.mBetterPartitionAttachAttempts++;
}
mAttachTimer.Start(GetAttachStartDelay());
if (IsDetached())
{
mAttachCounter++;
if (mAttachCounter == 0)
{
mAttachCounter--;
}
mCounters.mAttachAttempts++;
if (!IsRxOnWhenIdle())
{
Get<Mac::Mac>().SetRxOnWhenIdle(false);
}
}
exit:
return;
}
uint32_t Mle::GetAttachStartDelay(void) const
{
uint32_t delay = 1;
uint32_t jitter;
VerifyOrExit(IsDetached());
if (mAttachCounter == 0)
{
delay = 1 + Random::NonCrypto::GetUint32InRange(0, kParentRequestRouterTimeout);
ExitNow();
}
#if OPENTHREAD_CONFIG_MLE_ATTACH_BACKOFF_ENABLE
else
{
uint16_t counter = mAttachCounter - 1;
const uint32_t ratio = kAttachBackoffMaxInterval / kAttachBackoffMinInterval;
if ((counter < sizeof(ratio) * CHAR_BIT) && ((1UL << counter) <= ratio))
{
delay = kAttachBackoffMinInterval;
delay <<= counter;
}
else
{
delay = Random::NonCrypto::AddJitter(kAttachBackoffMaxInterval, kAttachBackoffJitter);
}
}
#endif // OPENTHREAD_CONFIG_MLE_ATTACH_BACKOFF_ENABLE
jitter = Random::NonCrypto::GetUint32InRange(0, kAttachStartJitter);
if (jitter + delay > delay) // check for overflow
{
delay += jitter;
}
LogNote("Attach attempt %u unsuccessful, will try again in %lu.%03u seconds", mAttachCounter, ToUlong(delay / 1000),
static_cast<uint16_t>(delay % 1000));
exit:
return delay;
}
bool Mle::IsAttached(void) const { return (IsChild() || IsRouter() || IsLeader()); }
bool Mle::IsRouterOrLeader(void) const { return (IsRouter() || IsLeader()); }
void Mle::SetStateDetached(void)
{
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
Get<BackboneRouter::Local>().Reset();
#endif
#if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2
Get<BackboneRouter::Leader>().Reset();
#endif
if (IsLeader())
{
Get<ThreadNetif>().RemoveUnicastAddress(mLeaderAloc);
}
SetRole(kRoleDetached);
SetAttachState(kAttachStateIdle);
mAttachTimer.Stop();
mMessageTransmissionTimer.Stop();
mChildUpdateRequestState = kChildUpdateRequestNone;
mChildUpdateAttempts = 0;
mDataRequestState = kDataRequestNone;
mDataRequestAttempts = 0;
mInitiallyAttachedAsSleepy = false;
Get<MeshForwarder>().SetRxOnWhenIdle(true);
Get<Mac::Mac>().SetBeaconEnabled(false);
#if OPENTHREAD_FTD
Get<MleRouter>().HandleDetachStart();
#endif
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
Get<Mac::Mac>().UpdateCsl();
#endif
}
void Mle::SetStateChild(uint16_t aRloc16)
{
if (IsLeader())
{
Get<ThreadNetif>().RemoveUnicastAddress(mLeaderAloc);
}
SetRloc16(aRloc16);
SetRole(kRoleChild);
SetAttachState(kAttachStateIdle);
mAttachTimer.Start(kAttachBackoffDelayToResetCounter);
mReattachState = kReattachStop;
mChildUpdateAttempts = 0;
mDataRequestAttempts = 0;
Get<Mac::Mac>().SetBeaconEnabled(false);
ScheduleMessageTransmissionTimer();
#if OPENTHREAD_FTD
if (IsFullThreadDevice())
{
Get<MleRouter>().HandleChildStart(mAttachMode);
}
#endif
// send announce after attached if needed
InformPreviousChannel();
#if OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE
mParentSearch.UpdateState();
#endif
if ((mPreviousParentRloc != Mac::kShortAddrInvalid) && (mPreviousParentRloc != mParent.GetRloc16()))
{
mCounters.mParentChanges++;
#if OPENTHREAD_CONFIG_MLE_INFORM_PREVIOUS_PARENT_ON_REATTACH
InformPreviousParent();
#endif
}
mPreviousParentRloc = mParent.GetRloc16();
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
Get<Mac::Mac>().UpdateCsl();
#endif
}
void Mle::InformPreviousChannel(void)
{
VerifyOrExit(mAlternatePanId != Mac::kPanIdBroadcast);
VerifyOrExit(IsChild() || IsRouter());
#if OPENTHREAD_FTD
VerifyOrExit(!IsFullThreadDevice() || IsRouter() || !Get<MleRouter>().IsRouterRoleTransitionPending());
#endif
mAlternatePanId = Mac::kPanIdBroadcast;
Get<AnnounceBeginServer>().SendAnnounce(1 << mAlternateChannel);
exit:
return;
}
void Mle::SetTimeout(uint32_t aTimeout)
{
// Determine `kMinTimeout` based on other parameters
static constexpr uint32_t kMinPollPeriod = OPENTHREAD_CONFIG_MAC_MINIMUM_POLL_PERIOD;
static constexpr uint32_t kRetxPollPeriod = OPENTHREAD_CONFIG_MAC_RETX_POLL_PERIOD;
static constexpr uint32_t kMinTimeoutDataPoll = kMinPollPeriod + kFailedChildTransmissions * kRetxPollPeriod;
static constexpr uint32_t kMinTimeoutKeepAlive = (kMaxChildKeepAliveAttempts + 1) * kUnicastRetxDelay;
static constexpr uint32_t kMinTimeout = Time::MsecToSec(OT_MAX(kMinTimeoutKeepAlive, kMinTimeoutDataPoll));
aTimeout = Max(aTimeout, kMinTimeout);
VerifyOrExit(mTimeout != aTimeout);
mTimeout = aTimeout;
Get<DataPollSender>().RecalculatePollPeriod();
if (IsChild())
{
IgnoreError(SendChildUpdateRequest());
}
exit:
return;
}
Error Mle::SetDeviceMode(DeviceMode aDeviceMode)
{
Error error = kErrorNone;
DeviceMode oldMode = mDeviceMode;
#if OPENTHREAD_MTD
VerifyOrExit(!aDeviceMode.IsFullThreadDevice(), error = kErrorInvalidArgs);
#endif
VerifyOrExit(aDeviceMode.IsValid(), error = kErrorInvalidArgs);
VerifyOrExit(mDeviceMode != aDeviceMode);
mDeviceMode = aDeviceMode;
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
Get<Utils::HistoryTracker>().RecordNetworkInfo();
#endif
#if OPENTHREAD_CONFIG_OTNS_ENABLE
Get<Utils::Otns>().EmitDeviceMode(mDeviceMode);
#endif
LogNote("Mode 0x%02x -> 0x%02x [%s]", oldMode.Get(), mDeviceMode.Get(), mDeviceMode.ToString().AsCString());
IgnoreError(Store());
if (IsAttached())
{
bool shouldReattach = false;
// We need to re-attach when switching between MTD/FTD modes.
if (oldMode.IsFullThreadDevice() != mDeviceMode.IsFullThreadDevice())
{
shouldReattach = true;
}
// If we initially attached as sleepy we allow mode changes
// between rx-on/off without a re-attach (we send "Child Update
// Request" to update the parent). But if we initially attached
// as rx-on, we require a re-attach on switching from rx-on to
// sleepy (rx-off) mode.
if (!mInitiallyAttachedAsSleepy && oldMode.IsRxOnWhenIdle() && !mDeviceMode.IsRxOnWhenIdle())
{
shouldReattach = true;
}
if (shouldReattach)
{
mAttachCounter = 0;
IgnoreError(BecomeDetached());
ExitNow();
}
}
if (IsDetached())
{
mAttachCounter = 0;
SetStateDetached();
Attach(kAnyPartition);
}
else if (IsChild())
{
SetStateChild(GetRloc16());
IgnoreError(SendChildUpdateRequest());
}
exit:
return error;
}
void Mle::UpdateLinkLocalAddress(void)
{
Get<ThreadNetif>().RemoveUnicastAddress(mLinkLocal64);
mLinkLocal64.GetAddress().GetIid().SetFromExtAddress(Get<Mac::Mac>().GetExtAddress());
Get<ThreadNetif>().AddUnicastAddress(mLinkLocal64);
Get<Notifier>().Signal(kEventThreadLinkLocalAddrChanged);
}
void Mle::SetMeshLocalPrefix(const Ip6::NetworkPrefix &aMeshLocalPrefix)
{
VerifyOrExit(GetMeshLocalPrefix() != aMeshLocalPrefix,
Get<Notifier>().SignalIfFirst(kEventThreadMeshLocalAddrChanged));
if (Get<ThreadNetif>().IsUp())
{
Get<ThreadNetif>().RemoveUnicastAddress(mLeaderAloc);
// We must remove the old addresses before adding the new ones.
Get<ThreadNetif>().RemoveUnicastAddress(mMeshLocal64);
Get<ThreadNetif>().RemoveUnicastAddress(mMeshLocal16);
Get<ThreadNetif>().UnsubscribeMulticast(mLinkLocalAllThreadNodes);
Get<ThreadNetif>().UnsubscribeMulticast(mRealmLocalAllThreadNodes);
}
mMeshLocal64.GetAddress().SetPrefix(aMeshLocalPrefix);
mMeshLocal16.GetAddress().SetPrefix(aMeshLocalPrefix);
mLeaderAloc.GetAddress().SetPrefix(aMeshLocalPrefix);
// Just keep mesh local prefix if network interface is down
VerifyOrExit(Get<ThreadNetif>().IsUp());
ApplyMeshLocalPrefix();
exit:
return;
}
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
Error Mle::SetMeshLocalIid(const Ip6::InterfaceIdentifier &aMlIid)
{
Error error = kErrorNone;
VerifyOrExit(!Get<ThreadNetif>().HasUnicastAddress(mMeshLocal64), error = kErrorInvalidState);
mMeshLocal64.GetAddress().SetIid(aMlIid);
exit:
return error;
}
#endif
void Mle::ApplyMeshLocalPrefix(void)
{
mLinkLocalAllThreadNodes.GetAddress().SetMulticastNetworkPrefix(GetMeshLocalPrefix());
mRealmLocalAllThreadNodes.GetAddress().SetMulticastNetworkPrefix(GetMeshLocalPrefix());
VerifyOrExit(!IsDisabled());
// Add the addresses back into the table.
Get<ThreadNetif>().AddUnicastAddress(mMeshLocal64);
Get<ThreadNetif>().SubscribeMulticast(mLinkLocalAllThreadNodes);
Get<ThreadNetif>().SubscribeMulticast(mRealmLocalAllThreadNodes);
if (IsAttached())
{
Get<ThreadNetif>().AddUnicastAddress(mMeshLocal16);
}
// update Leader ALOC
if (IsLeader())
{
Get<ThreadNetif>().AddUnicastAddress(mLeaderAloc);
}
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
Get<MeshCoP::Commissioner>().ApplyMeshLocalPrefix();
#endif
#if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE
Get<MeshCoP::BorderAgent>().ApplyMeshLocalPrefix();
#endif
#if OPENTHREAD_CONFIG_DHCP6_SERVER_ENABLE
Get<Dhcp6::Server>().ApplyMeshLocalPrefix();
#endif
#if OPENTHREAD_CONFIG_NEIGHBOR_DISCOVERY_AGENT_ENABLE
Get<NeighborDiscovery::Agent>().ApplyMeshLocalPrefix();
#endif
#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
for (ServiceAloc &serviceAloc : mServiceAlocs)
{
if (serviceAloc.IsInUse())
{
Get<ThreadNetif>().RemoveUnicastAddress(serviceAloc);
}
serviceAloc.ApplyMeshLocalPrefix(GetMeshLocalPrefix());
if (serviceAloc.IsInUse())
{
Get<ThreadNetif>().AddUnicastAddress(serviceAloc);
}
}
#endif
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
Get<BackboneRouter::Local>().ApplyMeshLocalPrefix();
#endif
exit:
// Changing the prefix also causes the mesh local address to be different.
Get<Notifier>().Signal(kEventThreadMeshLocalAddrChanged);
}
void Mle::SetRloc16(uint16_t aRloc16)
{
uint16_t oldRloc16 = GetRloc16();
if (aRloc16 != oldRloc16)
{
LogNote("RLOC16 %04x -> %04x", oldRloc16, aRloc16);
}
if (Get<ThreadNetif>().HasUnicastAddress(mMeshLocal16) &&
(mMeshLocal16.GetAddress().GetIid().GetLocator() != aRloc16))
{
Get<ThreadNetif>().RemoveUnicastAddress(mMeshLocal16);
Get<Tmf::Agent>().ClearRequests(mMeshLocal16.GetAddress());
}
Get<Mac::Mac>().SetShortAddress(aRloc16);
mRloc16 = aRloc16;
if (aRloc16 != Mac::kShortAddrInvalid)
{
// We can always call `AddUnicastAddress(mMeshLocat16)` and if
// the address is already added, it will perform no action.
mMeshLocal16.GetAddress().GetIid().SetLocator(aRloc16);
Get<ThreadNetif>().AddUnicastAddress(mMeshLocal16);
#if OPENTHREAD_FTD
Get<AddressResolver>().RestartAddressQueries();
#endif
}
}
void Mle::SetLeaderData(uint32_t aPartitionId, uint8_t aWeighting, uint8_t aLeaderRouterId)
{
if (mLeaderData.GetPartitionId() != aPartitionId)
{
#if OPENTHREAD_FTD
Get<MleRouter>().HandlePartitionChange();
#endif
Get<Notifier>().Signal(kEventThreadPartitionIdChanged);
mCounters.mPartitionIdChanges++;
}
else
{
Get<Notifier>().SignalIfFirst(kEventThreadPartitionIdChanged);
}
mLeaderData.SetPartitionId(aPartitionId);
mLeaderData.SetWeighting(aWeighting);
mLeaderData.SetLeaderRouterId(aLeaderRouterId);
}
Error Mle::GetLeaderAddress(Ip6::Address &aAddress) const
{
Error error = kErrorNone;
VerifyOrExit(GetRloc16() != Mac::kShortAddrInvalid, error = kErrorDetached);
aAddress.SetToRoutingLocator(GetMeshLocalPrefix(), Rloc16FromRouterId(mLeaderData.GetLeaderRouterId()));
exit:
return error;
}
Error Mle::GetLocatorAddress(Ip6::Address &aAddress, uint16_t aLocator) const
{
Error error = kErrorNone;
VerifyOrExit(GetRloc16() != Mac::kShortAddrInvalid, error = kErrorDetached);
memcpy(&aAddress, &mMeshLocal16.GetAddress(), 14);
aAddress.GetIid().SetLocator(aLocator);
exit:
return error;
}
Error Mle::GetServiceAloc(uint8_t aServiceId, Ip6::Address &aAddress) const
{
Error error = kErrorNone;
VerifyOrExit(GetRloc16() != Mac::kShortAddrInvalid, error = kErrorDetached);
aAddress.SetToAnycastLocator(GetMeshLocalPrefix(), ServiceAlocFromId(aServiceId));
exit:
return error;
}
const LeaderData &Mle::GetLeaderData(void)
{
mLeaderData.SetDataVersion(Get<NetworkData::Leader>().GetVersion(NetworkData::kFullSet));
mLeaderData.SetStableDataVersion(Get<NetworkData::Leader>().GetVersion(NetworkData::kStableSubset));
return mLeaderData;
}
bool Mle::HasUnregisteredAddress(void)
{
bool retval = false;
// Checks whether there are any addresses in addition to the mesh-local
// address that need to be registered.
for (const Ip6::Netif::UnicastAddress &addr : Get<ThreadNetif>().GetUnicastAddresses())
{
if (!addr.GetAddress().IsLinkLocal() && !IsRoutingLocator(addr.GetAddress()) &&
!IsAnycastLocator(addr.GetAddress()) && addr.GetAddress() != GetMeshLocal64())
{
ExitNow(retval = true);
}
}
if (!IsRxOnWhenIdle())
{
// For sleepy end-device, we register any external multicast
// addresses.
retval = Get<ThreadNetif>().HasAnyExternalMulticastAddress();
}
exit:
return retval;
}
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
void Mle::SetCslTimeout(uint32_t aTimeout)
{
VerifyOrExit(mCslTimeout != aTimeout);
mCslTimeout = aTimeout;
Get<DataPollSender>().RecalculatePollPeriod();
if (Get<Mac::Mac>().IsCslEnabled())
{
ScheduleChildUpdateRequest();
}
exit:
return;
}
#endif
void Mle::InitNeighbor(Neighbor &aNeighbor, const RxInfo &aRxInfo)
{
aRxInfo.mMessageInfo.GetPeerAddr().GetIid().ConvertToExtAddress(aNeighbor.GetExtAddress());
aNeighbor.GetLinkInfo().Clear();
aNeighbor.GetLinkInfo().AddRss(aRxInfo.mMessageInfo.GetThreadLinkInfo()->GetRss());
aNeighbor.ResetLinkFailures();
aNeighbor.SetLastHeard(TimerMilli::GetNow());
}
void Mle::HandleNotifierEvents(Events aEvents)
{
VerifyOrExit(!IsDisabled());
if (aEvents.Contains(kEventThreadRoleChanged))
{
if (IsChild() && !IsFullThreadDevice() && mAddressRegistrationMode == kAppendMeshLocalOnly)
{
// If only mesh-local address was registered in the "Child
// ID Request" message, after device is attached, trigger a
// "Child Update Request" to register the remaining
// addresses.
mAddressRegistrationMode = kAppendAllAddresses;
ScheduleChildUpdateRequest();
}
}
if (aEvents.ContainsAny(kEventIp6AddressAdded | kEventIp6AddressRemoved))
{
if (!Get<ThreadNetif>().HasUnicastAddress(mMeshLocal64.GetAddress()))
{
// Mesh Local EID was removed, choose a new one and add it back
mMeshLocal64.GetAddress().GetIid().GenerateRandom();
Get<ThreadNetif>().AddUnicastAddress(mMeshLocal64);
Get<Notifier>().Signal(kEventThreadMeshLocalAddrChanged);
}
if (IsChild() && !IsFullThreadDevice())
{
ScheduleChildUpdateRequest();
}
}
if (aEvents.ContainsAny(kEventIp6MulticastSubscribed | kEventIp6MulticastUnsubscribed))
{
// When multicast subscription changes, SED always notifies its parent as it depends on its
// parent for indirect transmission. Since Thread 1.2, MED MAY also notify its parent of 1.2
// or higher version as it could depend on its parent to perform Multicast Listener Report.
if (IsChild() && !IsFullThreadDevice() &&
(!IsRxOnWhenIdle()
#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
|| !GetParent().IsThreadVersion1p1()
#endif
))
{
ScheduleChildUpdateRequest();
}
}
if (aEvents.Contains(kEventThreadNetdataChanged))
{
#if OPENTHREAD_FTD
if (IsFullThreadDevice())
{
Get<MleRouter>().HandleNetworkDataUpdateRouter();
}
else
#endif
{
if (!aEvents.Contains(kEventThreadRoleChanged))
{
ScheduleChildUpdateRequest();
}
}
#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
Get<BackboneRouter::Leader>().Update();
#endif
#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
UpdateServiceAlocs();
#endif
#if OPENTHREAD_CONFIG_DHCP6_SERVER_ENABLE
IgnoreError(Get<Dhcp6::Server>().UpdateService());
#endif
#if OPENTHREAD_CONFIG_NEIGHBOR_DISCOVERY_AGENT_ENABLE
Get<NeighborDiscovery::Agent>().UpdateService();
#endif
#if OPENTHREAD_CONFIG_DHCP6_CLIENT_ENABLE
Get<Dhcp6::Client>().UpdateAddresses();
#endif
}
if (aEvents.ContainsAny(kEventThreadRoleChanged | kEventThreadKeySeqCounterChanged))
{
// Store the settings on a key seq change, or when role changes and device
// is attached (i.e., skip `Store()` on role change to detached).
if (aEvents.Contains(kEventThreadKeySeqCounterChanged) || IsAttached())
{
IgnoreError(Store());
}
}
#if OPENTHREAD_FTD
if (aEvents.Contains(kEventSecurityPolicyChanged))
{
Get<MleRouter>().HandleSecurityPolicyChanged();
}
#endif
exit:
return;
}
#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
Mle::ServiceAloc::ServiceAloc(void)
{
InitAsThreadOriginRealmLocalScope();
GetAddress().GetIid().SetToLocator(kNotInUse);
}
Mle::ServiceAloc *Mle::FindInServiceAlocs(uint16_t aAloc16)
{
// Search in `mServiceAlocs` for an entry matching `aAloc16`.
// Can be used with `aAloc16 = ServerAloc::kNotInUse` to find
// an unused entry in the array.
ServiceAloc *match = nullptr;
for (ServiceAloc &serviceAloc : mServiceAlocs)
{
if (serviceAloc.GetAloc16() == aAloc16)
{
match = &serviceAloc;
break;
}
}
return match;
}
void Mle::UpdateServiceAlocs(void)
{
NetworkData::Iterator iterator;
NetworkData::ServiceConfig service;
VerifyOrExit(!IsDisabled());
// First remove all ALOCs which are no longer in the Network
// Data to free up space in `mServiceAlocs` array.
for (ServiceAloc &serviceAloc : mServiceAlocs)
{
bool found = false;
if (!serviceAloc.IsInUse())
{
continue;
}
iterator = NetworkData::kIteratorInit;
while (Get<NetworkData::Leader>().GetNextService(iterator, GetRloc16(), service) == kErrorNone)
{
if (service.mServiceId == ServiceIdFromAloc(serviceAloc.GetAloc16()))
{
found = true;
break;
}
}
if (!found)
{
Get<ThreadNetif>().RemoveUnicastAddress(serviceAloc);
serviceAloc.MarkAsNotInUse();
}
}
// Now add any new ALOCs if there is space in `mServiceAlocs`.
iterator = NetworkData::kIteratorInit;
while (Get<NetworkData::Leader>().GetNextService(iterator, GetRloc16(), service) == kErrorNone)
{
uint16_t aloc16 = ServiceAlocFromId(service.mServiceId);
if (FindInServiceAlocs(aloc16) == nullptr)
{
// No matching ALOC in `mServiceAlocs`, so we try to add it.
ServiceAloc *newServiceAloc = FindInServiceAlocs(ServiceAloc::kNotInUse);
VerifyOrExit(newServiceAloc != nullptr);
newServiceAloc->SetAloc16(aloc16);
Get<ThreadNetif>().AddUnicastAddress(*newServiceAloc);
}
}
exit:
return;
}
#endif // OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
Error Mle::DetermineParentRequestType(ParentRequestType &aType) const
{
// This method determines the Parent Request type to use during an
// attach cycle based on `mAttachMode`, `mAttachCounter` and
// `mParentRequestCounter`. This method MUST be used while in
// `kAttachStateParentRequest` state.
//
// On success it returns `kErrorNone` and sets `aType`. It returns
// `kErrorNotFound` to indicate that device can now transition
// from `kAttachStateParentRequest` state (has already sent the
// required number of Parent Requests for this attach attempt
// cycle).
Error error = kErrorNone;
OT_ASSERT(mAttachState == kAttachStateParentRequest);
aType = kToRoutersAndReeds;
// If device is not yet attached, `mAttachCounter` will track the
// number of attach attempt cycles so far, starting from one for
// the first attempt. `mAttachCounter` will be zero if device is
// already attached. Examples of this situation include a leader or
// router trying to attach to a better partition, or a child trying
// to find a better parent.
if ((mAttachCounter <= 1) && (mAttachMode != kBetterParent))
{
VerifyOrExit(mParentRequestCounter <= kFirstAttachCycleTotalParentRequests, error = kErrorNotFound);
// During reattach to the same partition all the Parent
// Request are sent to Routers and REEDs.
if ((mAttachMode != kSamePartition) && (mParentRequestCounter <= kFirstAttachCycleNumParentRequestToRouters))
{
aType = kToRouters;
}
}
else
{
VerifyOrExit(mParentRequestCounter <= kNextAttachCycleTotalParentRequests, error = kErrorNotFound);
if (mParentRequestCounter <= kNextAttachCycleNumParentRequestToRouters)
{
aType = kToRouters;
}
}
exit:
return error;
}
bool Mle::HasAcceptableParentCandidate(void) const
{
bool hasAcceptableParent = false;
ParentRequestType parentReqType;
VerifyOrExit(mParentCandidate.IsStateParentResponse());
switch (mAttachState)
{
case kAttachStateAnnounce:
VerifyOrExit(!HasMoreChannelsToAnnounce());
break;
case kAttachStateParentRequest:
SuccessOrAssert(DetermineParentRequestType(parentReqType));
if (parentReqType == kToRouters)
{
// If we cannot find a parent with best link quality (3) when
// in Parent Request was sent to routers, we will keep the
// candidate and forward to REED stage to potentially find a
// better parent.
VerifyOrExit(mParentCandidate.GetTwoWayLinkQuality() == kLinkQuality3);
}
break;
default:
ExitNow();
}
if (IsChild())
{
// If already attached, accept the parent candidate if
// we are trying to attach to a better partition or if a
// Parent Response was also received from the current parent
// to which the device is attached. This ensures that the
// new parent candidate is compared with the current parent
// and that it is indeed preferred over the current one.
VerifyOrExit(mReceivedResponseFromParent || (mAttachMode == kBetterPartition));
}
hasAcceptableParent = true;
exit:
return hasAcceptableParent;
}
void Mle::HandleAttachTimer(void)
{
uint32_t delay = 0;
bool shouldAnnounce = true;
ParentRequestType type;
// First, check if we are waiting to receive parent responses and
// found an acceptable parent candidate.
if (HasAcceptableParentCandidate() && (SendChildIdRequest() == kErrorNone))
{
SetAttachState(kAttachStateChildIdRequest);
delay = kChildIdResponseTimeout;
ExitNow();
}
switch (mAttachState)
{
case kAttachStateIdle:
mAttachCounter = 0;
break;
case kAttachStateProcessAnnounce:
ProcessAnnounce();
break;
case kAttachStateStart:
LogNote("Attach attempt %d, %s %s", mAttachCounter, AttachModeToString(mAttachMode),
ReattachStateToString(mReattachState));
SetAttachState(kAttachStateParentRequest);
mParentCandidate.SetState(Neighbor::kStateInvalid);
mReceivedResponseFromParent = false;
mParentRequestCounter = 0;
Get<MeshForwarder>().SetRxOnWhenIdle(true);
OT_FALL_THROUGH;
case kAttachStateParentRequest:
mParentRequestCounter++;
if (DetermineParentRequestType(type) == kErrorNone)
{
SendParentRequest(type);
delay = (type == kToRouters) ? kParentRequestRouterTimeout : kParentRequestReedTimeout;
break;
}
shouldAnnounce = PrepareAnnounceState();
if (shouldAnnounce)
{
// We send an extra "Parent Request" as we switch to
// `kAttachStateAnnounce` and start sending Announce on
// all channels. This gives an additional chance to find
// a parent during this phase. Note that we can stay in
// `kAttachStateAnnounce` for multiple iterations, each
// time sending an Announce on a different channel
// (with `mAnnounceDelay` wait between them).
SetAttachState(kAttachStateAnnounce);
SendParentRequest(kToRoutersAndReeds);
mAnnounceChannel = Mac::ChannelMask::kChannelIteratorFirst;
delay = mAnnounceDelay;
break;
}
OT_FALL_THROUGH;
case kAttachStateAnnounce:
if (shouldAnnounce && (GetNextAnnounceChannel(mAnnounceChannel) == kErrorNone))
{
SendAnnounce(mAnnounceChannel, kOrphanAnnounce);
delay = mAnnounceDelay;
break;
}
OT_FALL_THROUGH;
case kAttachStateChildIdRequest:
SetAttachState(kAttachStateIdle);
mParentCandidate.Clear();
delay = Reattach();
break;
}
exit:
if (delay != 0)
{
mAttachTimer.Start(delay);
}
}
bool Mle::PrepareAnnounceState(void)
{
bool shouldAnnounce = false;
Mac::ChannelMask channelMask;
VerifyOrExit(!IsChild() && (mReattachState == kReattachStop) &&
(Get<MeshCoP::ActiveDatasetManager>().IsPartiallyComplete() || !IsFullThreadDevice()));
if (Get<MeshCoP::ActiveDatasetManager>().GetChannelMask(channelMask) != kErrorNone)
{
channelMask = Get<Mac::Mac>().GetSupportedChannelMask();
}
mAnnounceDelay = kAnnounceTimeout / (channelMask.GetNumberOfChannels() + 1);
mAnnounceDelay = Max(mAnnounceDelay, kMinAnnounceDelay);
shouldAnnounce = true;
exit:
return shouldAnnounce;
}
uint32_t Mle::Reattach(void)
{
uint32_t delay = 0;
if (mReattachState == kReattachActive)
{
if (Get<MeshCoP::PendingDatasetManager>().Restore() == kErrorNone)
{
IgnoreError(Get<MeshCoP::PendingDatasetManager>().ApplyConfiguration());
mReattachState = kReattachPending;
SetAttachState(kAttachStateStart);
delay = 1 + Random::NonCrypto::GetUint32InRange(0, kAttachStartJitter);
}
else
{
mReattachState = kReattachStop;
}
}
else if (mReattachState == kReattachPending)
{
mReattachState = kReattachStop;
IgnoreError(Get<MeshCoP::ActiveDatasetManager>().Restore());
}
VerifyOrExit(mReattachState == kReattachStop);
switch (mAttachMode)
{
case kAnyPartition:
case kBetterParent:
if (!IsChild())
{
if (mAlternatePanId != Mac::kPanIdBroadcast)
{
IgnoreError(Get<Mac::Mac>().SetPanChannel(mAlternateChannel));
Get<Mac::Mac>().SetPanId(mAlternatePanId);
mAlternatePanId = Mac::kPanIdBroadcast;
IgnoreError(BecomeDetached());
}
#if OPENTHREAD_FTD
else if (IsFullThreadDevice() && Get<MleRouter>().BecomeLeader() == kErrorNone)
{
// do nothing
}
#endif
else
{
IgnoreError(BecomeDetached());
}
}
else if (!IsRxOnWhenIdle())
{
// return to sleepy operation
Get<DataPollSender>().SetAttachMode(false);
Get<MeshForwarder>().SetRxOnWhenIdle(false);
}
break;
case kSamePartition:
case kDowngradeToReed:
Attach(kAnyPartition);
break;
case kBetterPartition:
break;
}
exit:
return delay;
}
void Mle::HandleDelayedResponseTimer(void)
{
TimeMilli now = TimerMilli::GetNow();
TimeMilli nextSendTime = now.GetDistantFuture();
for (Message &message : mDelayedResponses)
{
DelayedResponseMetadata metadata;
metadata.ReadFrom(message);
if (now < metadata.mSendTime)
{
nextSendTime = Min(nextSendTime, metadata.mSendTime);
}
else
{
mDelayedResponses.Dequeue(message);
SendDelayedResponse(static_cast<TxMessage &>(message), metadata);
}
}
if (nextSendTime < now.GetDistantFuture())
{
mDelayedResponseTimer.FireAt(nextSendTime);
}
}
void Mle::SendDelayedResponse(TxMessage &aMessage, const DelayedResponseMetadata &aMetadata)
{
Error error = kErrorNone;
aMetadata.RemoveFrom(aMessage);
if (aMessage.GetSubType() == Message::kSubTypeMleDataRequest)
{
SuccessOrExit(error = aMessage.AppendActiveTimestampTlv());
SuccessOrExit(error = aMessage.AppendPendingTimestampTlv());
}
SuccessOrExit(error = aMessage.SendTo(aMetadata.mDestination));
Log(kMessageSend, kTypeGenericDelayed, aMetadata.mDestination);
if (!IsRxOnWhenIdle())
{
// Start fast poll mode, assuming enqueued msg is MLE Data Request.
// Note: Finer-grade check may be required when deciding whether or
// not to enter fast poll mode for other type of delayed message.
Get<DataPollSender>().SendFastPolls(DataPollSender::kDefaultFastPolls);
}
exit:
if (error != kErrorNone)
{
// do not use `FreeMessageOnError()` to avoid null check on nonnull pointer
aMessage.Free();
}
}
void Mle::RemoveDelayedDataResponseMessage(void)
{
RemoveDelayedMessage(Message::kSubTypeMleDataResponse, kTypeDataResponse, nullptr);
}
void Mle::RemoveDelayedDataRequestMessage(const Ip6::Address &aDestination)
{
RemoveDelayedMessage(Message::kSubTypeMleDataRequest, kTypeDataRequest, &aDestination);
}
void Mle::RemoveDelayedMessage(Message::SubType aSubType, MessageType aMessageType, const Ip6::Address *aDestination)
{
for (Message &message : mDelayedResponses)
{
DelayedResponseMetadata metadata;
metadata.ReadFrom(message);
if ((message.GetSubType() == aSubType) &&
((aDestination == nullptr) || (metadata.mDestination == *aDestination)))
{
mDelayedResponses.DequeueAndFree(message);
Log(kMessageRemoveDelayed, aMessageType, metadata.mDestination);
}
}
}
void Mle::SendParentRequest(ParentRequestType aType)
{
Error error = kErrorNone;
TxMessage *message;
uint8_t scanMask = 0;
Ip6::Address destination;
mParentRequestChallenge.GenerateRandom();
switch (aType)
{
case kToRouters:
scanMask = ScanMaskTlv::kRouterFlag;
break;
case kToRoutersAndReeds:
scanMask = ScanMaskTlv::kRouterFlag | ScanMaskTlv::kEndDeviceFlag;
break;
}
VerifyOrExit((message = NewMleMessage(kCommandParentRequest)) != nullptr, error = kErrorNoBufs);
SuccessOrExit(error = message->AppendModeTlv(mDeviceMode));
SuccessOrExit(error = message->AppendChallengeTlv(mParentRequestChallenge));
SuccessOrExit(error = message->AppendScanMaskTlv(scanMask));
SuccessOrExit(error = message->AppendVersionTlv());
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
SuccessOrExit(error = message->AppendTimeRequestTlv());
#endif
destination.SetToLinkLocalAllRoutersMulticast();
SuccessOrExit(error = message->SendTo(destination));
switch (aType)
{
case kToRouters:
Log(kMessageSend, kTypeParentRequestToRouters, destination);
break;
case kToRoutersAndReeds:
Log(kMessageSend, kTypeParentRequestToRoutersReeds, destination);
break;
}
exit:
FreeMessageOnError(message, error);
}
void Mle::RequestShorterChildIdRequest(void)
{
if (mAttachState == kAttachStateChildIdRequest)
{
mAddressRegistrationMode = kAppendMeshLocalOnly;
IgnoreError(SendChildIdRequest());
}
}
void Mle::HandleChildIdRequestTxDone(Message &aMessage)
{
if (aMessage.GetTxSuccess() && !IsRxOnWhenIdle())
{
Get<DataPollSender>().SetAttachMode(true);
Get<MeshForwarder>().SetRxOnWhenIdle(false);
}
if (aMessage.IsLinkSecurityEnabled())
{
// If the Child ID Request requires fragmentation and therefore
// link layer security, the frame transmission will be aborted.
// When the message is being freed, we signal to MLE to prepare a
// shorter Child ID Request message (by only including mesh-local
// address in the Address Registration TLV).
LogInfo("Requesting shorter `Child ID Request`");
RequestShorterChildIdRequest();
}
}
Error Mle::SendChildIdRequest(void)
{
static const uint8_t kTlvs[] = {Tlv::kAddress16, Tlv::kNetworkData, Tlv::kRoute};
Error error = kErrorNone;
uint8_t tlvsLen = sizeof(kTlvs);
TxMessage *message = nullptr;
Ip6::Address destination;
if (mParent.GetExtAddress() == mParentCandidate.GetExtAddress())
{
if (IsChild())
{
LogInfo("Already attached to candidate parent");
ExitNow(error = kErrorAlready);
}
else
{
// Invalidate stale parent state.
//
// Parent state is not normally invalidated after becoming a Router/Leader (see #1875). When trying to
// attach to a better partition, invalidating old parent state (especially when in kStateRestored) ensures
// that FindNeighbor() returns mParentCandidate when processing the Child ID Response.
mParent.SetState(Neighbor::kStateInvalid);
}
}
VerifyOrExit((message = NewMleMessage(kCommandChildIdRequest)) != nullptr, error = kErrorNoBufs);
SuccessOrExit(error = message->AppendResponseTlv(mParentCandidate.mRxChallenge));
SuccessOrExit(error = message->AppendLinkFrameCounterTlv());
SuccessOrExit(error = message->AppendMleFrameCounterTlv());
SuccessOrExit(error = message->AppendModeTlv(mDeviceMode));
SuccessOrExit(error = message->AppendTimeoutTlv(mTimeout));
SuccessOrExit(error = message->AppendVersionTlv());
SuccessOrExit(error = message->AppendSupervisionIntervalTlv(Get<SupervisionListener>().GetInterval()));
if (!IsFullThreadDevice())
{
SuccessOrExit(error = message->AppendAddressRegistrationTlv(mAddressRegistrationMode));
// no need to request the last Route64 TLV for MTD
tlvsLen -= 1;
}
SuccessOrExit(error = message->AppendTlvRequestTlv(kTlvs, tlvsLen));
SuccessOrExit(error = message->AppendActiveTimestampTlv());
SuccessOrExit(error = message->AppendPendingTimestampTlv());
mParentCandidate.SetState(Neighbor::kStateValid);
destination.SetToLinkLocalAddress(mParentCandidate.GetExtAddress());
SuccessOrExit(error = message->SendTo(destination));
Log(kMessageSend,
(mAddressRegistrationMode == kAppendMeshLocalOnly) ? kTypeChildIdRequestShort : kTypeChildIdRequest,
destination);
exit:
FreeMessageOnError(message, error);
return error;
}
Error Mle::SendDataRequest(const Ip6::Address &aDestination)
{
return SendDataRequestAfterDelay(aDestination, /* aDelay */ 0);
}
Error Mle::SendDataRequestAfterDelay(const Ip6::Address &aDestination, uint16_t aDelay)
{
static const uint8_t kTlvs[] = {Tlv::kNetworkData, Tlv::kRoute};
// Based on `mRequestRouteTlv` include both Network Data and Route
// TLVs or only Network Data TLV.
return SendDataRequest(aDestination, kTlvs, mRequestRouteTlv ? 2 : 1, aDelay);
}
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE
Error Mle::SendDataRequestForLinkMetricsReport(const Ip6::Address &aDestination,
const LinkMetrics::Initiator::QueryInfo &aQueryInfo)
{
static const uint8_t kTlvs[] = {Tlv::kLinkMetricsReport};
return SendDataRequest(aDestination, kTlvs, sizeof(kTlvs), /* aDelay */ 0, &aQueryInfo);
}
Error Mle::SendDataRequest(const Ip6::Address &aDestination,
const uint8_t *aTlvs,
uint8_t aTlvsLength,
uint16_t aDelay,
const LinkMetrics::Initiator::QueryInfo *aQueryInfo)
#else
Error Mle::SendDataRequest(const Ip6::Address &aDestination, const uint8_t *aTlvs, uint8_t aTlvsLength, uint16_t aDelay)
#endif
{
Error error = kErrorNone;
TxMessage *message;
RemoveDelayedDataRequestMessage(aDestination);
VerifyOrExit((message = NewMleMessage(kCommandDataRequest)) != nullptr, error = kErrorNoBufs);
SuccessOrExit(error = message->AppendTlvRequestTlv(aTlvs, aTlvsLength));
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE
if (aQueryInfo != nullptr)
{
SuccessOrExit(error = Get<LinkMetrics::Initiator>().AppendLinkMetricsQueryTlv(*message, *aQueryInfo));
}
#endif
if (aDelay)
{
SuccessOrExit(error = message->SendAfterDelay(aDestination, aDelay));
Log(kMessageDelay, kTypeDataRequest, aDestination);
}
else
{
SuccessOrExit(error = message->AppendActiveTimestampTlv());
SuccessOrExit(error = message->AppendPendingTimestampTlv());
SuccessOrExit(error = message->SendTo(aDestination));
Log(kMessageSend, kTypeDataRequest, aDestination);
if (!IsRxOnWhenIdle())
{
Get<DataPollSender>().SendFastPolls(DataPollSender::kDefaultFastPolls);
}
}
exit:
FreeMessageOnError(message, error);
if (IsChild() && !IsRxOnWhenIdle())
{
mDataRequestState = kDataRequestActive;
if (mChildUpdateRequestState == kChildUpdateRequestNone)
{
ScheduleMessageTransmissionTimer();
}
}
return error;
}
void Mle::ScheduleMessageTransmissionTimer(void)
{
uint32_t interval = 0;
#if OPENTHREAD_FTD
if (mRole == kRoleDetached && mLinkRequestAttempts > 0)
{
ExitNow(interval = Random::NonCrypto::GetUint32InRange(kMulticastRetxDelayMin, kMulticastRetxDelayMax));
}
#endif
switch (mChildUpdateRequestState)
{
case kChildUpdateRequestNone:
break;
case kChildUpdateRequestPending:
ExitNow(interval = kChildUpdateRequestPendingDelay);
case kChildUpdateRequestActive:
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
if (Get<Mac::Mac>().IsCslEnabled())
{
ExitNow(interval = Get<Mac::Mac>().GetCslPeriodInMsec() + kUnicastRetxDelay);
}
else
#endif
{
ExitNow(interval = kUnicastRetxDelay);
}
}
switch (mDataRequestState)
{
case kDataRequestNone:
break;
case kDataRequestActive:
ExitNow(interval = kUnicastRetxDelay);
}
if (IsChild() && IsRxOnWhenIdle())
{
interval = Time::SecToMsec(mTimeout) - kUnicastRetxDelay * kMaxChildKeepAliveAttempts;
}
exit:
if (interval != 0)
{
mMessageTransmissionTimer.Start(interval);
}
else
{
mMessageTransmissionTimer.Stop();
}
}
void Mle::HandleMessageTransmissionTimer(void)
{
// The `mMessageTransmissionTimer` is used for:
//
// - Delaying kEvent notification triggered "Child Update Request" transmission (to allow aggregation),
// - Retransmission of "Child Update Request",
// - Retransmission of "Data Request" on a child,
// - Sending periodic keep-alive "Child Update Request" messages on a non-sleepy (rx-on) child.
// - Retransmission of "Link Request" after router reset
#if OPENTHREAD_FTD
// Retransmit multicast link request if no response has been received
// and maximum transmission limit has not been reached.
if (mRole == kRoleDetached && mLinkRequestAttempts > 0)
{
IgnoreError(Get<MleRouter>().SendLinkRequest(nullptr));
mLinkRequestAttempts--;
ScheduleMessageTransmissionTimer();
ExitNow();
}
#endif
switch (mChildUpdateRequestState)
{
case kChildUpdateRequestNone:
if (mDataRequestState == kDataRequestActive)
{
Ip6::Address destination;
VerifyOrExit(mDataRequestAttempts < kMaxChildKeepAliveAttempts, IgnoreError(BecomeDetached()));
destination.SetToLinkLocalAddress(mParent.GetExtAddress());
if (SendDataRequest(destination) == kErrorNone)
{
mDataRequestAttempts++;
}
ExitNow();
}
// Keep-alive "Child Update Request" only on a non-sleepy child
VerifyOrExit(IsChild() && IsRxOnWhenIdle());
break;
case kChildUpdateRequestPending:
if (Get<Notifier>().IsPending())
{
// Here intentionally delay another kChildUpdateRequestPendingDelay
// cycle to ensure we only send a Child Update Request after we
// know there are no more pending changes.
ScheduleMessageTransmissionTimer();
ExitNow();
}
mChildUpdateAttempts = 0;
break;
case kChildUpdateRequestActive:
break;
}
VerifyOrExit(mChildUpdateAttempts < kMaxChildKeepAliveAttempts, IgnoreError(BecomeDetached()));
if (SendChildUpdateRequest() == kErrorNone)
{
mChildUpdateAttempts++;
}
exit:
return;
}
Error Mle::SendChildUpdateRequest(void) { return SendChildUpdateRequest(kNormalChildUpdateRequest); }
Error Mle::SendChildUpdateRequest(ChildUpdateRequestMode aMode)
{
Error error = kErrorNone;
Ip6::Address destination;
TxMessage *message = nullptr;
AddressRegistrationMode addrRegMode = kAppendAllAddresses;
if (!mParent.IsStateValidOrRestoring())
{
LogWarn("No valid parent when sending Child Update Request");
IgnoreError(BecomeDetached());
ExitNow();
}
mChildUpdateRequestState = kChildUpdateRequestActive;
ScheduleMessageTransmissionTimer();
VerifyOrExit((message = NewMleMessage(kCommandChildUpdateRequest)) != nullptr, error = kErrorNoBufs);
SuccessOrExit(error = message->AppendModeTlv(mDeviceMode));
if ((aMode == kAppendChallengeTlv) || IsDetached())
{
mParentRequestChallenge.GenerateRandom();
SuccessOrExit(error = message->AppendChallengeTlv(mParentRequestChallenge));
}
switch (mRole)
{
case kRoleDetached:
addrRegMode = kAppendMeshLocalOnly;
break;
case kRoleChild:
SuccessOrExit(error = message->AppendSourceAddressTlv());
SuccessOrExit(error = message->AppendLeaderDataTlv());
SuccessOrExit(error = message->AppendTimeoutTlv((aMode == kAppendZeroTimeout) ? 0 : mTimeout));
SuccessOrExit(error = message->AppendSupervisionIntervalTlv(Get<SupervisionListener>().GetInterval()));
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
if (Get<Mac::Mac>().IsCslEnabled())
{
SuccessOrExit(error = message->AppendCslChannelTlv());
SuccessOrExit(error = message->AppendCslTimeoutTlv());
}
#endif
break;
case kRoleDisabled:
case kRoleRouter:
case kRoleLeader:
OT_ASSERT(false);
}
if (!IsFullThreadDevice())
{
SuccessOrExit(error = message->AppendAddressRegistrationTlv(addrRegMode));
}
destination.SetToLinkLocalAddress(mParent.GetExtAddress());
SuccessOrExit(error = message->SendTo(destination));
Log(kMessageSend, kTypeChildUpdateRequestOfParent, destination);
if (!IsRxOnWhenIdle())
{
Get<MeshForwarder>().SetRxOnWhenIdle(false);
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
Get<DataPollSender>().SetAttachMode(!Get<Mac::Mac>().IsCslEnabled());
#else
Get<DataPollSender>().SetAttachMode(true);
#endif
}
else
{
Get<MeshForwarder>().SetRxOnWhenIdle(true);
}
exit:
FreeMessageOnError(message, error);
return error;
}
Error Mle::SendChildUpdateResponse(const TlvList &aTlvList, const RxChallenge &aChallenge)
{
Error error = kErrorNone;
Ip6::Address destination;
TxMessage *message;
bool checkAddress = false;
VerifyOrExit((message = NewMleMessage(kCommandChildUpdateResponse)) != nullptr, error = kErrorNoBufs);
SuccessOrExit(error = message->AppendSourceAddressTlv());
SuccessOrExit(error = message->AppendLeaderDataTlv());
for (uint8_t tlvType : aTlvList)
{
switch (tlvType)
{
case Tlv::kTimeout:
SuccessOrExit(error = message->AppendTimeoutTlv(mTimeout));
break;
case Tlv::kStatus:
SuccessOrExit(error = message->AppendStatusTlv(StatusTlv::kError));
break;
case Tlv::kAddressRegistration:
if (!IsFullThreadDevice())
{
// We only register the mesh-local address in the "Child
// Update Response" message and if there are additional
// addresses to register we follow up with a "Child Update
// Request".
SuccessOrExit(error = message->AppendAddressRegistrationTlv(kAppendMeshLocalOnly));
checkAddress = true;
}
break;
case Tlv::kResponse:
SuccessOrExit(error = message->AppendResponseTlv(aChallenge));
break;
case Tlv::kLinkFrameCounter:
SuccessOrExit(error = message->AppendLinkFrameCounterTlv());
break;
case Tlv::kMleFrameCounter:
SuccessOrExit(error = message->AppendMleFrameCounterTlv());
break;
case Tlv::kSupervisionInterval:
SuccessOrExit(error = message->AppendSupervisionIntervalTlv(Get<SupervisionListener>().GetInterval()));
break;
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
case Tlv::kCslTimeout:
if (Get<Mac::Mac>().IsCslEnabled())
{
SuccessOrExit(error = message->AppendCslTimeoutTlv());
}
break;
#endif
}
}
destination.SetToLinkLocalAddress(mParent.GetExtAddress());
SuccessOrExit(error = message->SendTo(destination));
Log(kMessageSend, kTypeChildUpdateResponseOfParent, destination);
if (checkAddress && HasUnregisteredAddress())
{
IgnoreError(SendChildUpdateRequest());
}
exit:
FreeMessageOnError(message, error);
return error;
}
void Mle::SendAnnounce(uint8_t aChannel, AnnounceMode aMode)
{
Ip6::Address destination;
destination.SetToLinkLocalAllNodesMulticast();
SendAnnounce(aChannel, destination, aMode);
}
void Mle::SendAnnounce(uint8_t aChannel, const Ip6::Address &aDestination, AnnounceMode aMode)
{
Error error = kErrorNone;
ChannelTlv channelTlv;
MeshCoP::Timestamp activeTimestamp;
TxMessage *message = nullptr;
VerifyOrExit(Get<Mac::Mac>().GetSupportedChannelMask().ContainsChannel(aChannel), error = kErrorInvalidArgs);
VerifyOrExit((message = NewMleMessage(kCommandAnnounce)) != nullptr, error = kErrorNoBufs);
message->SetLinkSecurityEnabled(true);
message->SetChannel(aChannel);
channelTlv.Init();
channelTlv.SetChannelPage(0);
channelTlv.SetChannel(Get<Mac::Mac>().GetPanChannel());
SuccessOrExit(error = channelTlv.AppendTo(*message));
switch (aMode)
{
case kOrphanAnnounce:
activeTimestamp.Clear();
activeTimestamp.SetAuthoritative(true);
SuccessOrExit(error = Tlv::Append<ActiveTimestampTlv>(*message, activeTimestamp));
break;
case kNormalAnnounce:
SuccessOrExit(error = message->AppendActiveTimestampTlv());
break;
}
SuccessOrExit(error = Tlv::Append<PanIdTlv>(*message, Get<Mac::Mac>().GetPanId()));
SuccessOrExit(error = message->SendTo(aDestination));
LogInfo("Send Announce on channel %d", aChannel);
exit:
FreeMessageOnError(message, error);
}
Error Mle::GetNextAnnounceChannel(uint8_t &aChannel) const
{
// This method gets the next channel to send announce on after
// `aChannel`. Returns `kErrorNotFound` if no more channel in the
// channel mask after `aChannel`.
Mac::ChannelMask channelMask;
if (Get<MeshCoP::ActiveDatasetManager>().GetChannelMask(channelMask) != kErrorNone)
{
channelMask = Get<Mac::Mac>().GetSupportedChannelMask();
}
return channelMask.GetNextChannel(aChannel);
}
bool Mle::HasMoreChannelsToAnnounce(void) const
{
uint8_t channel = mAnnounceChannel;
return GetNextAnnounceChannel(channel) == kErrorNone;
}
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
Error Mle::SendLinkMetricsManagementResponse(const Ip6::Address &aDestination, LinkMetrics::Status aStatus)
{
Error error = kErrorNone;
TxMessage *message;
Tlv tlv;
ot::Tlv statusSubTlv;
VerifyOrExit((message = NewMleMessage(kCommandLinkMetricsManagementResponse)) != nullptr, error = kErrorNoBufs);
tlv.SetType(Tlv::kLinkMetricsManagement);
statusSubTlv.SetType(LinkMetrics::SubTlv::kStatus);
statusSubTlv.SetLength(sizeof(aStatus));
tlv.SetLength(statusSubTlv.GetSize());
SuccessOrExit(error = message->Append(tlv));
SuccessOrExit(error = message->Append(statusSubTlv));
SuccessOrExit(error = message->Append(aStatus));
SuccessOrExit(error = message->SendTo(aDestination));
exit:
FreeMessageOnError(message, error);
return error;
}
#endif
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE
Error Mle::SendLinkProbe(const Ip6::Address &aDestination, uint8_t aSeriesId, uint8_t *aBuf, uint8_t aLength)
{
Error error = kErrorNone;
TxMessage *message;
Tlv tlv;
VerifyOrExit((message = NewMleMessage(kCommandLinkProbe)) != nullptr, error = kErrorNoBufs);
tlv.SetType(Tlv::kLinkProbe);
tlv.SetLength(sizeof(aSeriesId) + aLength);
SuccessOrExit(error = message->Append(tlv));
SuccessOrExit(error = message->Append(aSeriesId));
SuccessOrExit(error = message->AppendBytes(aBuf, aLength));
SuccessOrExit(error = message->SendTo(aDestination));
exit:
FreeMessageOnError(message, error);
return error;
}
#endif
Error Mle::ProcessMessageSecurity(Crypto::AesCcm::Mode aMode,
Message &aMessage,
const Ip6::MessageInfo &aMessageInfo,
uint16_t aCmdOffset,
const SecurityHeader &aHeader)
{
// This method performs MLE message security. Based on `aMode` it
// can be used to encrypt and append tag to `aMessage` or to
// decrypt and validate the tag in a received `aMessage` (which is
// then removed from `aMessage`).
//
// `aCmdOffset` in both cases specifies the offset in `aMessage`
// to the start of MLE payload (i.e., the command field).
//
// When decrypting, possible errors are:
// `kErrorNone` decrypted and verified tag, tag is also removed.
// `kErrorParse` message does not contain the tag
// `kErrorSecurity` message tag is invalid.
//
// When encrypting, possible errors are:
// `kErrorNone` message encrypted and tag appended to message.
// `kErrorNoBufs` could not grow the message to append the tag.
Error error = kErrorNone;
Crypto::AesCcm aesCcm;
uint8_t nonce[Crypto::AesCcm::kNonceSize];
uint8_t tag[kMleSecurityTagSize];
Mac::ExtAddress extAddress;
uint32_t keySequence;
uint16_t payloadLength = aMessage.GetLength() - aCmdOffset;
const Ip6::Address *senderAddress = &aMessageInfo.GetSockAddr();
const Ip6::Address *receiverAddress = &aMessageInfo.GetPeerAddr();
switch (aMode)
{
case Crypto::AesCcm::kEncrypt:
// Use the initialized values for `senderAddress`,
// `receiverAddress` and `payloadLength`
break;
case Crypto::AesCcm::kDecrypt:
senderAddress = &aMessageInfo.GetPeerAddr();
receiverAddress = &aMessageInfo.GetSockAddr();
// Ensure message contains command field (uint8_t) and
// tag. Then exclude the tag from payload to decrypt.
VerifyOrExit(aCmdOffset + sizeof(uint8_t) + kMleSecurityTagSize <= aMessage.GetLength(), error = kErrorParse);
payloadLength -= kMleSecurityTagSize;
break;
}
senderAddress->GetIid().ConvertToExtAddress(extAddress);
Crypto::AesCcm::GenerateNonce(extAddress, aHeader.GetFrameCounter(), Mac::Frame::kSecurityEncMic32, nonce);
keySequence = aHeader.GetKeyId();
aesCcm.SetKey(keySequence == Get<KeyManager>().GetCurrentKeySequence()
? Get<KeyManager>().GetCurrentMleKey()
: Get<KeyManager>().GetTemporaryMleKey(keySequence));
aesCcm.Init(sizeof(Ip6::Address) + sizeof(Ip6::Address) + sizeof(SecurityHeader), payloadLength,
kMleSecurityTagSize, nonce, sizeof(nonce));
aesCcm.Header(*senderAddress);
aesCcm.Header(*receiverAddress);
aesCcm.Header(aHeader);
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
if (aMode == Crypto::AesCcm::kDecrypt)
{
// Skip decrypting the message under fuzz build mode
IgnoreError(aMessage.SetLength(aMessage.GetLength() - kMleSecurityTagSize));
ExitNow();
}
#endif
aesCcm.Payload(aMessage, aCmdOffset, payloadLength, aMode);
aesCcm.Finalize(tag);
if (aMode == Crypto::AesCcm::kEncrypt)
{
SuccessOrExit(error = aMessage.Append(tag));
}
else
{
VerifyOrExit(aMessage.Compare(aMessage.GetLength() - kMleSecurityTagSize, tag), error = kErrorSecurity);
IgnoreError(aMessage.SetLength(aMessage.GetLength() - kMleSecurityTagSize));
}
exit:
return error;
}
void Mle::HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo)
{
static_cast<Mle *>(aContext)->HandleUdpReceive(AsCoreType(aMessage), AsCoreType(aMessageInfo));
}
void Mle::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
Error error = kErrorNone;
RxInfo rxInfo(aMessage, aMessageInfo);
uint8_t securitySuite;
SecurityHeader header;
uint32_t keySequence;
uint32_t frameCounter;
Mac::ExtAddress extAddr;
uint8_t command;
Neighbor *neighbor;
#if OPENTHREAD_FTD
bool isNeighborRxOnly = false;
#endif
LogDebg("Receive MLE message");
VerifyOrExit(aMessageInfo.GetLinkInfo() != nullptr);
VerifyOrExit(aMessageInfo.GetHopLimit() == kMleHopLimit, error = kErrorParse);
SuccessOrExit(error = aMessage.Read(aMessage.GetOffset(), securitySuite));
aMessage.MoveOffset(sizeof(securitySuite));
if (securitySuite == kNoSecurity)
{
SuccessOrExit(error = aMessage.Read(aMessage.GetOffset(), command));
aMessage.MoveOffset(sizeof(command));
switch (command)
{
#if OPENTHREAD_FTD
case kCommandDiscoveryRequest:
Get<MleRouter>().HandleDiscoveryRequest(rxInfo);
break;
#endif
case kCommandDiscoveryResponse:
Get<DiscoverScanner>().HandleDiscoveryResponse(rxInfo);
break;
default:
break;
}
ExitNow();
}
VerifyOrExit(!IsDisabled(), error = kErrorInvalidState);
VerifyOrExit(securitySuite == k154Security, error = kErrorParse);
SuccessOrExit(error = aMessage.Read(aMessage.GetOffset(), header));
aMessage.MoveOffset(sizeof(header));
VerifyOrExit(header.IsSecurityControlValid(), error = kErrorParse);
keySequence = header.GetKeyId();
frameCounter = header.GetFrameCounter();
SuccessOrExit(
error = ProcessMessageSecurity(Crypto::AesCcm::kDecrypt, aMessage, aMessageInfo, aMessage.GetOffset(), header));
IgnoreError(aMessage.Read(aMessage.GetOffset(), command));
aMessage.MoveOffset(sizeof(command));
aMessageInfo.GetPeerAddr().GetIid().ConvertToExtAddress(extAddr);
neighbor = (command == kCommandChildIdResponse) ? mNeighborTable.FindParent(extAddr)
: mNeighborTable.FindNeighbor(extAddr);
#if OPENTHREAD_FTD
if (neighbor == nullptr)
{
// As an FED, we may have rx-only neighbors. We find and set
// `neighbor` to perform security processing (frame counter
// and key sequence checks) for messages from such neighbors.
neighbor = mNeighborTable.FindRxOnlyNeighborRouter(extAddr);
isNeighborRxOnly = true;
}
#endif
if (neighbor != nullptr && neighbor->IsStateValid())
{
if (keySequence == neighbor->GetKeySequence())
{
#if OPENTHREAD_CONFIG_MULTI_RADIO
// Only when counter is exactly one off, we allow it to be
// used for updating radio link info (by `RadioSelector`)
// before message is dropped as a duplicate. This handles
// the common case where a broadcast MLE message (such as
// Link Advertisement) is received over multiple radio
// links.
if ((frameCounter + 1) == neighbor->GetMleFrameCounter())
{
OT_ASSERT(aMessage.IsRadioTypeSet());
Get<RadioSelector>().UpdateOnReceive(*neighbor, aMessage.GetRadioType(), /* IsDuplicate */ true);
// We intentionally exit without setting the error to
// skip logging "Failed to process UDP" at the exit
// label. Note that in multi-radio mode, receiving
// duplicate MLE message (with one-off counter) would
// be common and ok for broadcast MLE messages (e.g.
// MLE Link Advertisements).
ExitNow();
}
#endif
VerifyOrExit(frameCounter >= neighbor->GetMleFrameCounter(), error = kErrorDuplicated);
}
else
{
VerifyOrExit(keySequence > neighbor->GetKeySequence(), error = kErrorDuplicated);
neighbor->SetKeySequence(keySequence);
neighbor->GetLinkFrameCounters().Reset();
neighbor->SetLinkAckFrameCounter(0);
}
neighbor->SetMleFrameCounter(frameCounter + 1);
}
#if OPENTHREAD_CONFIG_MULTI_RADIO
if (neighbor != nullptr)
{
OT_ASSERT(aMessage.IsRadioTypeSet());
Get<RadioSelector>().UpdateOnReceive(*neighbor, aMessage.GetRadioType(), /* IsDuplicate */ false);
}
#endif
#if OPENTHREAD_FTD
if (isNeighborRxOnly)
{
// Clear the `neighbor` if it is a rx-only one before calling
// `Handle{Msg}()`, except for a subset of MLE messages such
// as MLE Advertisement. This ensures that, as an FED, we are
// selective about which messages to process from rx-only
// neighbors.
switch (command)
{
case kCommandAdvertisement:
case kCommandLinkRequest:
case kCommandLinkAccept:
case kCommandLinkAcceptAndRequest:
break;
default:
neighbor = nullptr;
break;
}
}
#endif
rxInfo.mKeySequence = keySequence;
rxInfo.mFrameCounter = frameCounter;
rxInfo.mNeighbor = neighbor;
switch (command)
{
case kCommandAdvertisement:
HandleAdvertisement(rxInfo);
break;
case kCommandDataResponse:
HandleDataResponse(rxInfo);
break;
case kCommandParentResponse:
HandleParentResponse(rxInfo);
break;
case kCommandChildIdResponse:
HandleChildIdResponse(rxInfo);
break;
case kCommandAnnounce:
HandleAnnounce(rxInfo);
break;
case kCommandChildUpdateRequest:
#if OPENTHREAD_FTD
if (IsRouterOrLeader())
{
Get<MleRouter>().HandleChildUpdateRequest(rxInfo);
}
else
#endif
{
HandleChildUpdateRequest(rxInfo);
}
break;
case kCommandChildUpdateResponse:
#if OPENTHREAD_FTD
if (IsRouterOrLeader())
{
Get<MleRouter>().HandleChildUpdateResponse(rxInfo);
}
else
#endif
{
HandleChildUpdateResponse(rxInfo);
}
break;
#if OPENTHREAD_FTD
case kCommandLinkRequest:
Get<MleRouter>().HandleLinkRequest(rxInfo);
break;
case kCommandLinkAccept:
Get<MleRouter>().HandleLinkAccept(rxInfo);
break;
case kCommandLinkAcceptAndRequest:
Get<MleRouter>().HandleLinkAcceptAndRequest(rxInfo);
break;
case kCommandDataRequest:
Get<MleRouter>().HandleDataRequest(rxInfo);
break;
case kCommandParentRequest:
Get<MleRouter>().HandleParentRequest(rxInfo);
break;
case kCommandChildIdRequest:
Get<MleRouter>().HandleChildIdRequest(rxInfo);
break;
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
case kCommandTimeSync:
Get<MleRouter>().HandleTimeSync(rxInfo);
break;
#endif
#endif // OPENTHREAD_FTD
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
case kCommandLinkMetricsManagementRequest:
HandleLinkMetricsManagementRequest(rxInfo);
break;
#endif
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE
case kCommandLinkMetricsManagementResponse:
HandleLinkMetricsManagementResponse(rxInfo);
break;
#endif
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
case kCommandLinkProbe:
HandleLinkProbe(rxInfo);
break;
#endif
default:
ExitNow(error = kErrorDrop);
}
ProcessKeySequence(rxInfo);
#if OPENTHREAD_CONFIG_MULTI_RADIO
// If we could not find a neighbor matching the MAC address of the
// received MLE messages, or if the neighbor is now invalid, we
// check again after the message is handled with a relaxed neighbor
// state filer. The processing of the received MLE message may
// create a new neighbor or change the neighbor table (e.g.,
// receiving a "Parent Request" from a new child, or processing a
// "Link Request" from a previous child which is being promoted to a
// router).
if ((neighbor == nullptr) || neighbor->IsStateInvalid())
{
neighbor = Get<NeighborTable>().FindNeighbor(extAddr, Neighbor::kInStateAnyExceptInvalid);
if (neighbor != nullptr)
{
Get<RadioSelector>().UpdateOnReceive(*neighbor, aMessage.GetRadioType(), /* aIsDuplicate */ false);
}
}
#endif
exit:
// We skip logging failures for broadcast MLE messages since it
// can be common to receive such messages from adjacent Thread
// networks.
if (!aMessageInfo.GetSockAddr().IsMulticast() || !aMessageInfo.GetThreadLinkInfo()->IsDstPanIdBroadcast())
{
LogProcessError(kTypeGenericUdp, error);
}
}
void Mle::ProcessKeySequence(RxInfo &aRxInfo)
{
// In case key sequence is larger, we determine whether to adopt it
// or not. The `Handle{MleMsg}()` methods set the `rxInfo.mClass`
// based on the message command type and the included TLVs. If
// there is any error during parsing of the message the `mClass`
// remains as its default value of `RxInfo::kUnknown`. Message
// classes are determined based on this:
//
// Authoritative : Larger key seq MUST be adopted.
// Peer : If from a known neighbor
// If difference is 1, adopt
// Otherwise don't adopt and try to re-sync with
// neighbor.
// Otherwise larger key seq MUST NOT be adopted.
VerifyOrExit(aRxInfo.mKeySequence > Get<KeyManager>().GetCurrentKeySequence());
switch (aRxInfo.mClass)
{
case RxInfo::kAuthoritativeMessage:
Get<KeyManager>().SetCurrentKeySequence(aRxInfo.mKeySequence);
break;
case RxInfo::kPeerMessage:
if ((aRxInfo.mNeighbor != nullptr) && aRxInfo.mNeighbor->IsStateValid())
{
if (aRxInfo.mKeySequence - Get<KeyManager>().GetCurrentKeySequence() == 1)
{
Get<KeyManager>().SetCurrentKeySequence(aRxInfo.mKeySequence);
}
else
{
LogInfo("Large key seq jump in peer class msg from 0x%04x ", aRxInfo.mNeighbor->GetRloc16());
ReestablishLinkWithNeighbor(*aRxInfo.mNeighbor);
}
}
break;
case RxInfo::kUnknown:
break;
}
exit:
return;
}
void Mle::ReestablishLinkWithNeighbor(Neighbor &aNeighbor)
{
VerifyOrExit(IsAttached() && aNeighbor.IsStateValid());
if (IsChild() && (&aNeighbor == &mParent))
{
IgnoreError(SendChildUpdateRequest(kAppendChallengeTlv));
ExitNow();
}
#if OPENTHREAD_FTD
VerifyOrExit(IsFullThreadDevice());
if (IsActiveRouter(aNeighbor.GetRloc16()))
{
IgnoreError(Get<MleRouter>().SendLinkRequest(&aNeighbor));
}
else if (Get<ChildTable>().Contains(aNeighbor))
{
Child &child = static_cast<Child &>(aNeighbor);
child.SetState(Child::kStateChildUpdateRequest);
IgnoreError(Get<MleRouter>().SendChildUpdateRequest(child));
}
#endif
exit:
return;
}
void Mle::HandleAdvertisement(RxInfo &aRxInfo)
{
Error error = kErrorNone;
uint16_t sourceAddress;
LeaderData leaderData;
uint16_t delay;
VerifyOrExit(IsAttached());
SuccessOrExit(error = Tlv::Find<SourceAddressTlv>(aRxInfo.mMessage, sourceAddress));
Log(kMessageReceive, kTypeAdvertisement, aRxInfo.mMessageInfo.GetPeerAddr(), sourceAddress);
SuccessOrExit(error = aRxInfo.mMessage.ReadLeaderDataTlv(leaderData));
#if OPENTHREAD_FTD
if (IsFullThreadDevice())
{
SuccessOrExit(error = Get<MleRouter>().HandleAdvertisement(aRxInfo, sourceAddress, leaderData));
}
#endif
if (IsChild())
{
VerifyOrExit(aRxInfo.mNeighbor == &mParent);
if (mParent.GetRloc16() != sourceAddress)
{
// Remove stale parent.
IgnoreError(BecomeDetached());
ExitNow();
}
if ((leaderData.GetPartitionId() != mLeaderData.GetPartitionId()) ||
(leaderData.GetLeaderRouterId() != GetLeaderId()))
{
SetLeaderData(leaderData.GetPartitionId(), leaderData.GetWeighting(), leaderData.GetLeaderRouterId());
#if OPENTHREAD_FTD
SuccessOrExit(error = Get<MleRouter>().ReadAndProcessRouteTlvOnFed(aRxInfo, mParent.GetRouterId()));
#endif
mRetrieveNewNetworkData = true;
}
mParent.SetLastHeard(TimerMilli::GetNow());
}
else // Device is router or leader
{
VerifyOrExit(aRxInfo.IsNeighborStateValid());
}
if (mRetrieveNewNetworkData || IsNetworkDataNewer(leaderData))
{
delay = Random::NonCrypto::GetUint16InRange(0, kMleMaxResponseDelay);
IgnoreError(SendDataRequestAfterDelay(aRxInfo.mMessageInfo.GetPeerAddr(), delay));
}
aRxInfo.mClass = RxInfo::kPeerMessage;
exit:
LogProcessError(kTypeAdvertisement, error);
}
void Mle::HandleDataResponse(RxInfo &aRxInfo)
{
Error error;
Log(kMessageReceive, kTypeDataResponse, aRxInfo.mMessageInfo.GetPeerAddr());
VerifyOrExit(aRxInfo.IsNeighborStateValid(), error = kErrorDrop);
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE
{
uint16_t offset;
uint16_t length;
if (Tlv::FindTlvValueOffset(aRxInfo.mMessage, Tlv::kLinkMetricsReport, offset, length) == kErrorNone)
{
Get<LinkMetrics::Initiator>().HandleReport(aRxInfo.mMessage, offset, length,
aRxInfo.mMessageInfo.GetPeerAddr());
}
}
#endif
#if OPENTHREAD_FTD
SuccessOrExit(error = Get<MleRouter>().ReadAndProcessRouteTlvOnFed(aRxInfo, mParent.GetRouterId()));
#endif
error = HandleLeaderData(aRxInfo);
if (mDataRequestState == kDataRequestNone && !IsRxOnWhenIdle())
{
// Here simply stops fast data poll request by Mle Data Request.
// Note that in some cases fast data poll may continue after below stop operation until
// running out the specified number. E.g. other component also trigger fast poll, and
// is waiting for response; or the corner case where multiple Mle Data Request attempts
// happened due to the retransmission mechanism.
Get<DataPollSender>().StopFastPolls();
}
SuccessOrExit(error);
aRxInfo.mClass = RxInfo::kPeerMessage;
exit:
LogProcessError(kTypeDataResponse, error);
}
bool Mle::IsNetworkDataNewer(const LeaderData &aLeaderData)
{
return SerialNumber::IsGreater(aLeaderData.GetDataVersion(GetNetworkDataType()),
Get<NetworkData::Leader>().GetVersion(GetNetworkDataType()));
}
Error Mle::HandleLeaderData(RxInfo &aRxInfo)
{
Error error = kErrorNone;
LeaderData leaderData;
MeshCoP::Timestamp activeTimestamp;
MeshCoP::Timestamp pendingTimestamp;
const MeshCoP::Timestamp *timestamp;
bool hasActiveTimestamp = false;
bool hasPendingTimestamp = false;
uint16_t networkDataOffset;
uint16_t networkDataLength;
uint16_t activeDatasetOffset = 0;
uint16_t activeDatasetLength = 0;
uint16_t pendingDatasetOffset = 0;
uint16_t pendingDatasetLength = 0;
bool dataRequest = false;
// Leader Data
SuccessOrExit(error = aRxInfo.mMessage.ReadLeaderDataTlv(leaderData));
if ((leaderData.GetPartitionId() != mLeaderData.GetPartitionId()) ||
(leaderData.GetWeighting() != mLeaderData.GetWeighting()) || (leaderData.GetLeaderRouterId() != GetLeaderId()))
{
if (IsChild())
{
SetLeaderData(leaderData.GetPartitionId(), leaderData.GetWeighting(), leaderData.GetLeaderRouterId());
mRetrieveNewNetworkData = true;
}
else
{
ExitNow(error = kErrorDrop);
}
}
else if (!mRetrieveNewNetworkData)
{
VerifyOrExit(IsNetworkDataNewer(leaderData));
}
// Active Timestamp
switch (Tlv::Find<ActiveTimestampTlv>(aRxInfo.mMessage, activeTimestamp))
{
case kErrorNone:
hasActiveTimestamp = true;
timestamp = Get<MeshCoP::ActiveDatasetManager>().GetTimestamp();
// if received timestamp does not match the local value and message does not contain the dataset,
// send MLE Data Request
if (!IsLeader() && (MeshCoP::Timestamp::Compare(&activeTimestamp, timestamp) != 0) &&
(Tlv::FindTlvValueOffset(aRxInfo.mMessage, Tlv::kActiveDataset, activeDatasetOffset, activeDatasetLength) !=
kErrorNone))
{
ExitNow(dataRequest = true);
}
break;
case kErrorNotFound:
break;
default:
ExitNow(error = kErrorParse);
}
// Pending Timestamp
switch (Tlv::Find<PendingTimestampTlv>(aRxInfo.mMessage, pendingTimestamp))
{
case kErrorNone:
hasPendingTimestamp = true;
timestamp = Get<MeshCoP::PendingDatasetManager>().GetTimestamp();
// if received timestamp does not match the local value and message does not contain the dataset,
// send MLE Data Request
if (!IsLeader() && (MeshCoP::Timestamp::Compare(&pendingTimestamp, timestamp) != 0) &&
(Tlv::FindTlvValueOffset(aRxInfo.mMessage, Tlv::kPendingDataset, pendingDatasetOffset,
pendingDatasetLength) != kErrorNone))
{
ExitNow(dataRequest = true);
}
break;
case kErrorNotFound:
break;
default:
ExitNow(error = kErrorParse);
}
if (Tlv::FindTlvValueOffset(aRxInfo.mMessage, Tlv::kNetworkData, networkDataOffset, networkDataLength) ==
kErrorNone)
{
error = Get<NetworkData::Leader>().SetNetworkData(
leaderData.GetDataVersion(NetworkData::kFullSet), leaderData.GetDataVersion(NetworkData::kStableSubset),
GetNetworkDataType(), aRxInfo.mMessage, networkDataOffset, networkDataLength);
SuccessOrExit(error);
}
else
{
ExitNow(dataRequest = true);
}
#if OPENTHREAD_FTD
if (IsLeader())
{
Get<NetworkData::Leader>().IncrementVersionAndStableVersion();
}
else
#endif
{
// Active Dataset
if (hasActiveTimestamp)
{
if (activeDatasetOffset > 0)
{
IgnoreError(Get<MeshCoP::ActiveDatasetManager>().Save(activeTimestamp, aRxInfo.mMessage,
activeDatasetOffset, activeDatasetLength));
}
}
// Pending Dataset
if (hasPendingTimestamp)
{
if (pendingDatasetOffset > 0)
{
IgnoreError(Get<MeshCoP::PendingDatasetManager>().Save(pendingTimestamp, aRxInfo.mMessage,
pendingDatasetOffset, pendingDatasetLength));
}
}
}
mRetrieveNewNetworkData = false;
exit:
if (dataRequest)
{
uint16_t delay;
if (aRxInfo.mMessageInfo.GetSockAddr().IsMulticast())
{
delay = Random::NonCrypto::GetUint16InRange(0, kMleMaxResponseDelay);
}
else
{
// This method may have been called from an MLE request
// handler. We add a minimum delay here so that the MLE
// response is enqueued before the MLE Data Request.
delay = 10;
}
IgnoreError(SendDataRequestAfterDelay(aRxInfo.mMessageInfo.GetPeerAddr(), delay));
}
else if (error == kErrorNone)
{
mDataRequestAttempts = 0;
mDataRequestState = kDataRequestNone;
// Here the `mMessageTransmissionTimer` is intentionally not canceled
// so that when it fires from its callback a "Child Update" is sent
// if the device is a rx-on child. This way, even when the timer is
// reused for retransmission of "Data Request" messages, it is ensured
// that keep-alive "Child Update Request" messages are send within the
// child's timeout.
}
return error;
}
bool Mle::IsBetterParent(uint16_t aRloc16,
LinkQuality aLinkQuality,
uint8_t aLinkMargin,
const ConnectivityTlv &aConnectivityTlv,
uint16_t aVersion,
const Mac::CslAccuracy &aCslAccuracy)
{
int rval;
LinkQuality candidateTwoWayLinkQuality = mParentCandidate.GetTwoWayLinkQuality();
// Mesh Impacting Criteria
rval = ThreeWayCompare(aLinkQuality, candidateTwoWayLinkQuality);
VerifyOrExit(rval == 0);
rval = ThreeWayCompare(IsActiveRouter(aRloc16), IsActiveRouter(mParentCandidate.GetRloc16()));
VerifyOrExit(rval == 0);
rval = ThreeWayCompare(aConnectivityTlv.GetParentPriority(), mParentCandidate.mPriority);
VerifyOrExit(rval == 0);
// Prefer the parent with highest quality links (Link Quality 3 field in Connectivity TLV) to neighbors
rval = ThreeWayCompare(aConnectivityTlv.GetLinkQuality3(), mParentCandidate.mLinkQuality3);
VerifyOrExit(rval == 0);
// Thread 1.2 Specification 4.5.2.1.2 Child Impacting Criteria
rval = ThreeWayCompare(aVersion, mParentCandidate.GetVersion());
VerifyOrExit(rval == 0);
rval = ThreeWayCompare(aConnectivityTlv.GetSedBufferSize(), mParentCandidate.mSedBufferSize);
VerifyOrExit(rval == 0);
rval = ThreeWayCompare(aConnectivityTlv.GetSedDatagramCount(), mParentCandidate.mSedDatagramCount);
VerifyOrExit(rval == 0);
// Extra rules
rval = ThreeWayCompare(aConnectivityTlv.GetLinkQuality2(), mParentCandidate.mLinkQuality2);
VerifyOrExit(rval == 0);
rval = ThreeWayCompare(aConnectivityTlv.GetLinkQuality1(), mParentCandidate.mLinkQuality1);
VerifyOrExit(rval == 0);
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
// CSL metric
if (!IsRxOnWhenIdle())
{
uint64_t cslMetric = CalcParentCslMetric(aCslAccuracy);
uint64_t candidateCslMetric = CalcParentCslMetric(mParentCandidate.GetCslAccuracy());
// Smaller metric is better.
rval = ThreeWayCompare(candidateCslMetric, cslMetric);
VerifyOrExit(rval == 0);
}
#else
OT_UNUSED_VARIABLE(aCslAccuracy);
#endif
rval = ThreeWayCompare(aLinkMargin, mParentCandidate.mLinkMargin);
exit:
return (rval > 0);
}
void Mle::HandleParentResponse(RxInfo &aRxInfo)
{
Error error = kErrorNone;
int8_t rss = aRxInfo.mMessageInfo.GetThreadLinkInfo()->GetRss();
RxChallenge response;
uint16_t version;
uint16_t sourceAddress;
LeaderData leaderData;
uint8_t linkMarginFromTlv;
uint8_t linkMargin;
LinkQuality linkQuality;
ConnectivityTlv connectivityTlv;
uint32_t linkFrameCounter;
uint32_t mleFrameCounter;
Mac::ExtAddress extAddress;
Mac::CslAccuracy cslAccuracy;
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
TimeParameterTlv timeParameterTlv;
#endif
// Source Address
SuccessOrExit(error = Tlv::Find<SourceAddressTlv>(aRxInfo.mMessage, sourceAddress));
Log(kMessageReceive, kTypeParentResponse, aRxInfo.mMessageInfo.GetPeerAddr(), sourceAddress);
// Version
SuccessOrExit(error = Tlv::Find<VersionTlv>(aRxInfo.mMessage, version));
VerifyOrExit(version >= kThreadVersion1p1, error = kErrorParse);
// Response
SuccessOrExit(error = aRxInfo.mMessage.ReadResponseTlv(response));
VerifyOrExit(response == mParentRequestChallenge, error = kErrorParse);
aRxInfo.mMessageInfo.GetPeerAddr().GetIid().ConvertToExtAddress(extAddress);
if (IsChild() && mParent.GetExtAddress() == extAddress)
{
mReceivedResponseFromParent = true;
}
// Leader Data
SuccessOrExit(error = aRxInfo.mMessage.ReadLeaderDataTlv(leaderData));
// Link Margin
SuccessOrExit(error = Tlv::Find<LinkMarginTlv>(aRxInfo.mMessage, linkMarginFromTlv));
linkMargin = Min(Get<Mac::Mac>().ComputeLinkMargin(rss), linkMarginFromTlv);
linkQuality = LinkQualityForLinkMargin(linkMargin);
// Connectivity
SuccessOrExit(error = Tlv::FindTlv(aRxInfo.mMessage, connectivityTlv));
VerifyOrExit(connectivityTlv.IsValid(), error = kErrorParse);
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
// CSL Accuracy
switch (aRxInfo.mMessage.ReadCslClockAccuracyTlv(cslAccuracy))
{
case kErrorNone:
break;
case kErrorNotFound:
cslAccuracy.Init(); // Use worst-case values if TLV is not found
break;
default:
ExitNow(error = kErrorParse);
}
#else
cslAccuracy.Init();
#endif
#if OPENTHREAD_CONFIG_MLE_PARENT_RESPONSE_CALLBACK_API_ENABLE
if (mParentResponseCallback.IsSet())
{
otThreadParentResponseInfo parentinfo;
parentinfo.mExtAddr = extAddress;
parentinfo.mRloc16 = sourceAddress;
parentinfo.mRssi = rss;
parentinfo.mPriority = connectivityTlv.GetParentPriority();
parentinfo.mLinkQuality3 = connectivityTlv.GetLinkQuality3();
parentinfo.mLinkQuality2 = connectivityTlv.GetLinkQuality2();
parentinfo.mLinkQuality1 = connectivityTlv.GetLinkQuality1();
parentinfo.mIsAttached = IsAttached();
mParentResponseCallback.Invoke(&parentinfo);
}
#endif
aRxInfo.mClass = RxInfo::kAuthoritativeMessage;
#if OPENTHREAD_FTD
if (IsFullThreadDevice() && !IsDetached())
{
bool isPartitionIdSame = (leaderData.GetPartitionId() == mLeaderData.GetPartitionId());
bool isIdSequenceSame = (connectivityTlv.GetIdSequence() == Get<RouterTable>().GetRouterIdSequence());
bool isIdSequenceGreater =
SerialNumber::IsGreater(connectivityTlv.GetIdSequence(), Get<RouterTable>().GetRouterIdSequence());
switch (mAttachMode)
{
case kAnyPartition:
case kBetterParent:
VerifyOrExit(!isPartitionIdSame || isIdSequenceGreater);
break;
case kSamePartition:
VerifyOrExit(isPartitionIdSame && isIdSequenceGreater);
break;
case kDowngradeToReed:
VerifyOrExit(isPartitionIdSame && (isIdSequenceSame || isIdSequenceGreater));
break;
case kBetterPartition:
VerifyOrExit(!isPartitionIdSame);
VerifyOrExit(MleRouter::ComparePartitions(connectivityTlv.GetActiveRouters() <= 1, leaderData,
Get<MleRouter>().IsSingleton(), mLeaderData) > 0);
break;
}
}
#endif
// Continue to process the "ParentResponse" if it is from current
// parent candidate to update the challenge and frame counters.
if (mParentCandidate.IsStateParentResponse() && (mParentCandidate.GetExtAddress() != extAddress))
{
// if already have a candidate parent, only seek a better parent
int compare = 0;
#if OPENTHREAD_FTD
if (IsFullThreadDevice())
{
compare = MleRouter::ComparePartitions(connectivityTlv.GetActiveRouters() <= 1, leaderData,
mParentCandidate.mIsSingleton, mParentCandidate.mLeaderData);
}
// only consider partitions that are the same or better
VerifyOrExit(compare >= 0);
#endif
// only consider better parents if the partitions are the same
if (compare == 0)
{
VerifyOrExit(IsBetterParent(sourceAddress, linkQuality, linkMargin, connectivityTlv, version, cslAccuracy));
}
}
// Link/MLE Frame Counters
SuccessOrExit(error = aRxInfo.mMessage.ReadFrameCounterTlvs(linkFrameCounter, mleFrameCounter));
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
// Time Parameter
if (Tlv::FindTlv(aRxInfo.mMessage, timeParameterTlv) == kErrorNone)
{
VerifyOrExit(timeParameterTlv.IsValid());
Get<TimeSync>().SetTimeSyncPeriod(timeParameterTlv.GetTimeSyncPeriod());
Get<TimeSync>().SetXtalThreshold(timeParameterTlv.GetXtalThreshold());
}
#if OPENTHREAD_CONFIG_TIME_SYNC_REQUIRED
else
{
// If the time sync feature is required, don't choose the parent which doesn't support it.
ExitNow();
}
#endif // OPENTHREAD_CONFIG_TIME_SYNC_REQUIRED
#endif // OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
// Challenge
SuccessOrExit(error = aRxInfo.mMessage.ReadChallengeTlv(mParentCandidate.mRxChallenge));
InitNeighbor(mParentCandidate, aRxInfo);
mParentCandidate.SetRloc16(sourceAddress);
mParentCandidate.GetLinkFrameCounters().SetAll(linkFrameCounter);
mParentCandidate.SetLinkAckFrameCounter(linkFrameCounter);
mParentCandidate.SetMleFrameCounter(mleFrameCounter);
mParentCandidate.SetVersion(version);
mParentCandidate.SetDeviceMode(DeviceMode(DeviceMode::kModeFullThreadDevice | DeviceMode::kModeRxOnWhenIdle |
DeviceMode::kModeFullNetworkData));
mParentCandidate.SetLinkQualityOut(LinkQualityForLinkMargin(linkMarginFromTlv));
mParentCandidate.SetState(Neighbor::kStateParentResponse);
mParentCandidate.SetKeySequence(aRxInfo.mKeySequence);
mParentCandidate.SetLeaderCost(connectivityTlv.GetLeaderCost());
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
mParentCandidate.SetCslAccuracy(cslAccuracy);
#endif
mParentCandidate.mPriority = connectivityTlv.GetParentPriority();
mParentCandidate.mLinkQuality3 = connectivityTlv.GetLinkQuality3();
mParentCandidate.mLinkQuality2 = connectivityTlv.GetLinkQuality2();
mParentCandidate.mLinkQuality1 = connectivityTlv.GetLinkQuality1();
mParentCandidate.mSedBufferSize = connectivityTlv.GetSedBufferSize();
mParentCandidate.mSedDatagramCount = connectivityTlv.GetSedDatagramCount();
mParentCandidate.mLeaderData = leaderData;
mParentCandidate.mIsSingleton = connectivityTlv.GetActiveRouters() <= 1;
mParentCandidate.mLinkMargin = linkMargin;
exit:
LogProcessError(kTypeParentResponse, error);
}
void Mle::HandleChildIdResponse(RxInfo &aRxInfo)
{
Error error = kErrorNone;
LeaderData leaderData;
uint16_t sourceAddress;
uint16_t shortAddress;
MeshCoP::Timestamp timestamp;
uint16_t networkDataOffset;
uint16_t networkDataLength;
uint16_t offset;
uint16_t length;
// Source Address
SuccessOrExit(error = Tlv::Find<SourceAddressTlv>(aRxInfo.mMessage, sourceAddress));
Log(kMessageReceive, kTypeChildIdResponse, aRxInfo.mMessageInfo.GetPeerAddr(), sourceAddress);
VerifyOrExit(aRxInfo.IsNeighborStateValid(), error = kErrorSecurity);
VerifyOrExit(mAttachState == kAttachStateChildIdRequest);
// ShortAddress
SuccessOrExit(error = Tlv::Find<Address16Tlv>(aRxInfo.mMessage, shortAddress));
VerifyOrExit(RouterIdMatch(sourceAddress, shortAddress), error = kErrorRejected);
// Leader Data
SuccessOrExit(error = aRxInfo.mMessage.ReadLeaderDataTlv(leaderData));
// Network Data
SuccessOrExit(
error = Tlv::FindTlvValueOffset(aRxInfo.mMessage, Tlv::kNetworkData, networkDataOffset, networkDataLength));
// Active Timestamp
switch (Tlv::Find<ActiveTimestampTlv>(aRxInfo.mMessage, timestamp))
{
case kErrorNone:
// Active Dataset
if (Tlv::FindTlvValueOffset(aRxInfo.mMessage, Tlv::kActiveDataset, offset, length) == kErrorNone)
{
SuccessOrExit(error =
Get<MeshCoP::ActiveDatasetManager>().Save(timestamp, aRxInfo.mMessage, offset, length));
}
break;
case kErrorNotFound:
break;
default:
ExitNow(error = kErrorParse);
}
// clear Pending Dataset if device succeed to reattach using stored Pending Dataset
if (mReattachState == kReattachPending)
{
Get<MeshCoP::PendingDatasetManager>().Clear();
}
// Pending Timestamp
switch (Tlv::Find<PendingTimestampTlv>(aRxInfo.mMessage, timestamp))
{
case kErrorNone:
// Pending Dataset
if (Tlv::FindTlvValueOffset(aRxInfo.mMessage, Tlv::kPendingDataset, offset, length) == kErrorNone)
{
IgnoreError(Get<MeshCoP::PendingDatasetManager>().Save(timestamp, aRxInfo.mMessage, offset, length));
}
break;
case kErrorNotFound:
Get<MeshCoP::PendingDatasetManager>().ClearNetwork();
break;
default:
ExitNow(error = kErrorParse);
}
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
// Sync to Thread network time
if (aRxInfo.mMessage.GetTimeSyncSeq() != OT_TIME_SYNC_INVALID_SEQ)
{
Get<TimeSync>().HandleTimeSyncMessage(aRxInfo.mMessage);
}
#endif
// Parent Attach Success
SetStateDetached();
SetLeaderData(leaderData.GetPartitionId(), leaderData.GetWeighting(), leaderData.GetLeaderRouterId());
#if OPENTHREAD_FTD
SuccessOrExit(error = Get<MleRouter>().ReadAndProcessRouteTlvOnFed(aRxInfo, RouterIdFromRloc16(sourceAddress)));
#endif
mParentCandidate.CopyTo(mParent);
mParentCandidate.Clear();
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
Get<Mac::Mac>().SetCslParentAccuracy(mParent.GetCslAccuracy());
#endif
mParent.SetRloc16(sourceAddress);
IgnoreError(Get<NetworkData::Leader>().SetNetworkData(
leaderData.GetDataVersion(NetworkData::kFullSet), leaderData.GetDataVersion(NetworkData::kStableSubset),
GetNetworkDataType(), aRxInfo.mMessage, networkDataOffset, networkDataLength));
SetStateChild(shortAddress);
if (!IsRxOnWhenIdle())
{
Get<DataPollSender>().SetAttachMode(false);
Get<MeshForwarder>().SetRxOnWhenIdle(false);
}
else
{
Get<MeshForwarder>().SetRxOnWhenIdle(true);
}
aRxInfo.mClass = RxInfo::kPeerMessage;
exit:
LogProcessError(kTypeChildIdResponse, error);
}
void Mle::HandleChildUpdateRequest(RxInfo &aRxInfo)
{
Error error = kErrorNone;
uint16_t sourceAddress;
RxChallenge challenge;
TlvList requestedTlvList;
TlvList tlvList;
// Source Address
SuccessOrExit(error = Tlv::Find<SourceAddressTlv>(aRxInfo.mMessage, sourceAddress));
Log(kMessageReceive, kTypeChildUpdateRequestOfParent, aRxInfo.mMessageInfo.GetPeerAddr(), sourceAddress);
// Challenge
switch (aRxInfo.mMessage.ReadChallengeTlv(challenge))
{
case kErrorNone:
tlvList.Add(Tlv::kResponse);
tlvList.Add(Tlv::kMleFrameCounter);
tlvList.Add(Tlv::kLinkFrameCounter);
break;
case kErrorNotFound:
challenge.Clear();
break;
default:
ExitNow(error = kErrorParse);
}
if (aRxInfo.mNeighbor == &mParent)
{
uint8_t status;
switch (Tlv::Find<StatusTlv>(aRxInfo.mMessage, status))
{
case kErrorNone:
VerifyOrExit(status != StatusTlv::kError, IgnoreError(BecomeDetached()));
break;
case kErrorNotFound:
break;
default:
ExitNow(error = kErrorParse);
}
if (mParent.GetRloc16() != sourceAddress)
{
IgnoreError(BecomeDetached());
ExitNow();
}
// Leader Data, Network Data, Active Timestamp, Pending Timestamp
SuccessOrExit(error = HandleLeaderData(aRxInfo));
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
{
Mac::CslAccuracy cslAccuracy;
if (aRxInfo.mMessage.ReadCslClockAccuracyTlv(cslAccuracy) == kErrorNone)
{
// MUST include CSL timeout TLV when request includes CSL accuracy
tlvList.Add(Tlv::kCslTimeout);
}
}
#endif
}
else
{
// this device is not a child of the Child Update Request source
tlvList.Add(Tlv::kStatus);
}
// TLV Request
switch (aRxInfo.mMessage.ReadTlvRequestTlv(requestedTlvList))
{
case kErrorNone:
tlvList.AddElementsFrom(requestedTlvList);
break;
case kErrorNotFound:
break;
default:
ExitNow(error = kErrorParse);
}
aRxInfo.mClass = RxInfo::kPeerMessage;
ProcessKeySequence(aRxInfo);
#if OPENTHREAD_CONFIG_MULTI_RADIO
if ((aRxInfo.mNeighbor != nullptr) && !challenge.IsEmpty())
{
aRxInfo.mNeighbor->ClearLastRxFragmentTag();
}
#endif
SuccessOrExit(error = SendChildUpdateResponse(tlvList, challenge));
exit:
LogProcessError(kTypeChildUpdateRequestOfParent, error);
}
void Mle::HandleChildUpdateResponse(RxInfo &aRxInfo)
{
Error error = kErrorNone;
uint8_t status;
uint8_t mode;
RxChallenge response;
uint32_t linkFrameCounter;
uint32_t mleFrameCounter;
uint16_t sourceAddress;
uint32_t timeout;
Log(kMessageReceive, kTypeChildUpdateResponseOfParent, aRxInfo.mMessageInfo.GetPeerAddr());
switch (aRxInfo.mMessage.ReadResponseTlv(response))
{
case kErrorNone:
break;
case kErrorNotFound:
response.Clear();
break;
default:
ExitNow(error = kErrorParse);
}
switch (mRole)
{
case kRoleDetached:
VerifyOrExit(response == mParentRequestChallenge, error = kErrorSecurity);
break;
case kRoleChild:
VerifyOrExit((aRxInfo.mNeighbor == &mParent) && mParent.IsStateValid(), error = kErrorSecurity);
break;
default:
OT_ASSERT(false);
}
// Status
if (Tlv::Find<StatusTlv>(aRxInfo.mMessage, status) == kErrorNone)
{
IgnoreError(BecomeDetached());
ExitNow();
}
// Mode
SuccessOrExit(error = Tlv::Find<ModeTlv>(aRxInfo.mMessage, mode));
VerifyOrExit(DeviceMode(mode) == mDeviceMode, error = kErrorDrop);
switch (mRole)
{
case kRoleDetached:
SuccessOrExit(error = aRxInfo.mMessage.ReadFrameCounterTlvs(linkFrameCounter, mleFrameCounter));
mParent.GetLinkFrameCounters().SetAll(linkFrameCounter);
mParent.SetLinkAckFrameCounter(linkFrameCounter);
mParent.SetMleFrameCounter(mleFrameCounter);
mParent.SetState(Neighbor::kStateValid);
SetStateChild(GetRloc16());
mRetrieveNewNetworkData = true;
#if OPENTHREAD_FTD
if (IsFullThreadDevice())
{
mRequestRouteTlv = true;
}
#endif
OT_FALL_THROUGH;
case kRoleChild:
// Source Address
SuccessOrExit(error = Tlv::Find<SourceAddressTlv>(aRxInfo.mMessage, sourceAddress));
if (RouterIdFromRloc16(sourceAddress) != RouterIdFromRloc16(GetRloc16()))
{
IgnoreError(BecomeDetached());
ExitNow();
}
// Leader Data, Network Data, Active Timestamp, Pending Timestamp
SuccessOrExit(error = HandleLeaderData(aRxInfo));
// Timeout optional
switch (Tlv::Find<TimeoutTlv>(aRxInfo.mMessage, timeout))
{
case kErrorNone:
if (timeout == 0 && IsDetachingGracefully())
{
Stop();
}
else
{
mTimeout = timeout;
}
break;
case kErrorNotFound:
break;
default:
ExitNow(error = kErrorParse);
}
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
{
Mac::CslAccuracy cslAccuracy;
// CSL Accuracy
switch (aRxInfo.mMessage.ReadCslClockAccuracyTlv(cslAccuracy))
{
case kErrorNone:
Get<Mac::Mac>().SetCslParentAccuracy(cslAccuracy);
break;
case kErrorNotFound:
break;
default:
ExitNow(error = kErrorParse);
}
}
#endif
if (!IsRxOnWhenIdle())
{
Get<DataPollSender>().SetAttachMode(false);
Get<MeshForwarder>().SetRxOnWhenIdle(false);
}
else
{
Get<MeshForwarder>().SetRxOnWhenIdle(true);
}
break;
default:
OT_ASSERT(false);
}
aRxInfo.mClass = response.IsEmpty() ? RxInfo::kPeerMessage : RxInfo::kAuthoritativeMessage;
exit:
if (error == kErrorNone)
{
if (mChildUpdateRequestState == kChildUpdateRequestActive)
{
mChildUpdateAttempts = 0;
mChildUpdateRequestState = kChildUpdateRequestNone;
ScheduleMessageTransmissionTimer();
}
}
LogProcessError(kTypeChildUpdateResponseOfParent, error);
}
void Mle::HandleAnnounce(RxInfo &aRxInfo)
{
Error error = kErrorNone;
ChannelTlv channelTlv;
MeshCoP::Timestamp timestamp;
const MeshCoP::Timestamp *localTimestamp;
uint8_t channel;
uint16_t panId;
bool isFromOrphan;
bool channelAndPanIdMatch;
int timestampCompare;
Log(kMessageReceive, kTypeAnnounce, aRxInfo.mMessageInfo.GetPeerAddr());
SuccessOrExit(error = Tlv::FindTlv(aRxInfo.mMessage, channelTlv));
VerifyOrExit(channelTlv.IsValid(), error = kErrorParse);
channel = static_cast<uint8_t>(channelTlv.GetChannel());
SuccessOrExit(error = Tlv::Find<ActiveTimestampTlv>(aRxInfo.mMessage, timestamp));
SuccessOrExit(error = Tlv::Find<PanIdTlv>(aRxInfo.mMessage, panId));
aRxInfo.mClass = RxInfo::kPeerMessage;
localTimestamp = Get<MeshCoP::ActiveDatasetManager>().GetTimestamp();
isFromOrphan = timestamp.IsOrphanTimestamp();
timestampCompare = MeshCoP::Timestamp::Compare(&timestamp, localTimestamp);
channelAndPanIdMatch = (channel == Get<Mac::Mac>().GetPanChannel()) && (panId == Get<Mac::Mac>().GetPanId());
if (isFromOrphan || (timestampCompare < 0))
{
if (isFromOrphan)
{
VerifyOrExit(!channelAndPanIdMatch);
}
SendAnnounce(channel);
#if OPENTHREAD_CONFIG_MLE_SEND_UNICAST_ANNOUNCE_RESPONSE
SendAnnounce(channel, aRxInfo.mMessageInfo.GetPeerAddr());
#endif
}
else if (timestampCompare > 0)
{
// No action is required if device is detached, and current
// channel and pan-id match the values from the received MLE
// Announce message.
if (IsDetached())
{
VerifyOrExit(!channelAndPanIdMatch);
}
if (mAttachState == kAttachStateProcessAnnounce)
{
VerifyOrExit(mAlternateTimestamp < timestamp.GetSeconds());
}
mAlternateTimestamp = timestamp.GetSeconds();
mAlternateChannel = channel;
mAlternatePanId = panId;
SetAttachState(kAttachStateProcessAnnounce);
mAttachTimer.Start(kAnnounceProcessTimeout);
mAttachCounter = 0;
LogNote("Delay processing Announce - channel %d, panid 0x%02x", channel, panId);
}
else
{
// Timestamps are equal.
#if OPENTHREAD_CONFIG_ANNOUNCE_SENDER_ENABLE
// Notify `AnnounceSender` of the received Announce
// message so it can update its state to determine
// whether to send Announce or not.
Get<AnnounceSender>().UpdateOnReceivedAnnounce();
#endif
}
exit:
LogProcessError(kTypeAnnounce, error);
}
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
void Mle::HandleLinkMetricsManagementRequest(RxInfo &aRxInfo)
{
Error error = kErrorNone;
LinkMetrics::Status status;
Log(kMessageReceive, kTypeLinkMetricsManagementRequest, aRxInfo.mMessageInfo.GetPeerAddr());
VerifyOrExit(aRxInfo.IsNeighborStateValid(), error = kErrorInvalidState);
SuccessOrExit(
error = Get<LinkMetrics::Subject>().HandleManagementRequest(aRxInfo.mMessage, *aRxInfo.mNeighbor, status));
error = SendLinkMetricsManagementResponse(aRxInfo.mMessageInfo.GetPeerAddr(), status);
aRxInfo.mClass = RxInfo::kPeerMessage;
exit:
LogProcessError(kTypeLinkMetricsManagementRequest, error);
}
#endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE
void Mle::HandleLinkMetricsManagementResponse(RxInfo &aRxInfo)
{
Error error = kErrorNone;
Log(kMessageReceive, kTypeLinkMetricsManagementResponse, aRxInfo.mMessageInfo.GetPeerAddr());
VerifyOrExit(aRxInfo.IsNeighborStateValid(), error = kErrorInvalidState);
error =
Get<LinkMetrics::Initiator>().HandleManagementResponse(aRxInfo.mMessage, aRxInfo.mMessageInfo.GetPeerAddr());
aRxInfo.mClass = RxInfo::kPeerMessage;
exit:
LogProcessError(kTypeLinkMetricsManagementResponse, error);
}
#endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
void Mle::HandleLinkProbe(RxInfo &aRxInfo)
{
Error error = kErrorNone;
uint8_t seriesId;
Log(kMessageReceive, kTypeLinkProbe, aRxInfo.mMessageInfo.GetPeerAddr());
VerifyOrExit(aRxInfo.IsNeighborStateValid(), error = kErrorInvalidState);
SuccessOrExit(error = Get<LinkMetrics::Subject>().HandleLinkProbe(aRxInfo.mMessage, seriesId));
aRxInfo.mNeighbor->AggregateLinkMetrics(seriesId, LinkMetrics::SeriesInfo::kSeriesTypeLinkProbe,
aRxInfo.mMessage.GetAverageLqi(), aRxInfo.mMessage.GetAverageRss());
aRxInfo.mClass = RxInfo::kPeerMessage;
exit:
LogProcessError(kTypeLinkProbe, error);
}
#endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
void Mle::ProcessAnnounce(void)
{
uint8_t newChannel = mAlternateChannel;
uint16_t newPanId = mAlternatePanId;
OT_ASSERT(mAttachState == kAttachStateProcessAnnounce);
LogNote("Processing Announce - channel %d, panid 0x%02x", newChannel, newPanId);
Stop(kKeepNetworkDatasets);
// Save the current/previous channel and pan-id
mAlternateChannel = Get<Mac::Mac>().GetPanChannel();
mAlternatePanId = Get<Mac::Mac>().GetPanId();
mAlternateTimestamp = 0;
IgnoreError(Get<Mac::Mac>().SetPanChannel(newChannel));
Get<Mac::Mac>().SetPanId(newPanId);
IgnoreError(Start(kAnnounceAttach));
}
uint16_t Mle::GetNextHop(uint16_t aDestination) const
{
OT_UNUSED_VARIABLE(aDestination);
return (mParent.IsStateValid()) ? mParent.GetRloc16() : static_cast<uint16_t>(Mac::kShortAddrInvalid);
}
Error Mle::GetParentInfo(Router::Info &aParentInfo) const
{
Error error = kErrorNone;
// Skip the check for reference device since it needs to get the
// original parent's info even after role change.
#if !OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
VerifyOrExit(IsChild(), error = kErrorInvalidState);
#endif
aParentInfo.SetFrom(mParent);
ExitNow();
exit:
return error;
}
bool Mle::IsRoutingLocator(const Ip6::Address &aAddress) const
{
return IsMeshLocalAddress(aAddress) && aAddress.GetIid().IsRoutingLocator();
}
bool Mle::IsAnycastLocator(const Ip6::Address &aAddress) const
{
return IsMeshLocalAddress(aAddress) && aAddress.GetIid().IsAnycastLocator();
}
bool Mle::IsMeshLocalAddress(const Ip6::Address &aAddress) const
{
return (aAddress.GetPrefix() == GetMeshLocalPrefix());
}
Error Mle::CheckReachability(uint16_t aMeshDest, const Ip6::Header &aIp6Header)
{
Error error;
if ((aMeshDest != GetRloc16()) || Get<ThreadNetif>().HasUnicastAddress(aIp6Header.GetDestination()))
{
error = kErrorNone;
}
else
{
error = kErrorNoRoute;
}
return error;
}
#if OPENTHREAD_CONFIG_MLE_INFORM_PREVIOUS_PARENT_ON_REATTACH
void Mle::InformPreviousParent(void)
{
Error error = kErrorNone;
Message *message = nullptr;
Ip6::MessageInfo messageInfo;
VerifyOrExit((message = Get<Ip6::Ip6>().NewMessage(0)) != nullptr, error = kErrorNoBufs);
SuccessOrExit(error = message->SetLength(0));
messageInfo.SetSockAddr(GetMeshLocal64());
messageInfo.SetPeerAddr(GetMeshLocal16());
messageInfo.GetPeerAddr().GetIid().SetLocator(mPreviousParentRloc);
SuccessOrExit(error = Get<Ip6::Ip6>().SendDatagram(*message, messageInfo, Ip6::kProtoNone));
LogNote("Sending message to inform previous parent 0x%04x", mPreviousParentRloc);
exit:
if (error != kErrorNone)
{
LogWarn("Failed to inform previous parent: %s", ErrorToString(error));
FreeMessage(message);
}
}
#endif // OPENTHREAD_CONFIG_MLE_INFORM_PREVIOUS_PARENT_ON_REATTACH
#if OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE
void Mle::ParentSearch::HandleTimer(void)
{
int8_t parentRss;
LogInfo("PeriodicParentSearch: %s interval passed", mIsInBackoff ? "Backoff" : "Check");
if (mBackoffWasCanceled)
{
// Backoff can be canceled if the device switches to a new parent.
// from `UpdateParentSearchState()`. We want to limit this to happen
// only once within a backoff interval.
if (TimerMilli::GetNow() - mBackoffCancelTime >= kBackoffInterval)
{
mBackoffWasCanceled = false;
LogInfo("PeriodicParentSearch: Backoff cancellation is allowed on parent switch");
}
}
mIsInBackoff = false;
VerifyOrExit(Get<Mle>().IsChild());
parentRss = Get<Mle>().GetParent().GetLinkInfo().GetAverageRss();
LogInfo("PeriodicParentSearch: Parent RSS %d", parentRss);
VerifyOrExit(parentRss != Radio::kInvalidRssi);
if (parentRss < kRssThreshold)
{
LogInfo("PeriodicParentSearch: Parent RSS less than %d, searching for new parents", kRssThreshold);
mIsInBackoff = true;
Get<Mle>().Attach(kBetterParent);
}
exit:
StartTimer();
}
void Mle::ParentSearch::StartTimer(void)
{
uint32_t interval;
interval = Random::NonCrypto::GetUint32InRange(0, kJitterInterval);
if (mIsInBackoff)
{
interval += kBackoffInterval;
}
else
{
interval += kCheckInterval;
}
mTimer.Start(interval);
LogInfo("PeriodicParentSearch: (Re)starting timer for %s interval", mIsInBackoff ? "backoff" : "check");
}
void Mle::ParentSearch::UpdateState(void)
{
#if OPENTHREAD_CONFIG_MLE_INFORM_PREVIOUS_PARENT_ON_REATTACH
// If we are in middle of backoff and backoff was not canceled
// recently and we recently detached from a previous parent,
// then we check if the new parent is different from the previous
// one, and if so, we cancel the backoff mode and also remember
// the backoff cancel time. This way the canceling of backoff
// is allowed only once within a backoff window.
//
// The reason behind the canceling of the backoff is to handle
// the scenario where a previous parent is not available for a
// short duration (e.g., it is going through a software update)
// and the child switches to a less desirable parent. With this
// model the child will check for other parents sooner and have
// the chance to switch back to the original (and possibly
// preferred) parent more quickly.
if (mIsInBackoff && !mBackoffWasCanceled && mRecentlyDetached)
{
if ((Get<Mle>().mPreviousParentRloc != Mac::kShortAddrInvalid) &&
(Get<Mle>().mPreviousParentRloc != Get<Mle>().mParent.GetRloc16()))
{
mIsInBackoff = false;
mBackoffWasCanceled = true;
mBackoffCancelTime = TimerMilli::GetNow();
LogInfo("PeriodicParentSearch: Canceling backoff on switching to a new parent");
}
}
#endif // OPENTHREAD_CONFIG_MLE_INFORM_PREVIOUS_PARENT_ON_REATTACH
mRecentlyDetached = false;
if (!mIsInBackoff)
{
StartTimer();
}
}
#endif // OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE
#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
void Mle::Log(MessageAction aAction, MessageType aType, const Ip6::Address &aAddress)
{
Log(aAction, aType, aAddress, Mac::kShortAddrInvalid);
}
void Mle::Log(MessageAction aAction, MessageType aType, const Ip6::Address &aAddress, uint16_t aRloc)
{
enum : uint8_t
{
kRlocStringSize = 17,
};
String<kRlocStringSize> rlocString;
if (aRloc != Mac::kShortAddrInvalid)
{
rlocString.Append(",0x%04x", aRloc);
}
LogInfo("%s %s%s (%s%s)", MessageActionToString(aAction), MessageTypeToString(aType),
MessageTypeActionToSuffixString(aType, aAction), aAddress.ToString().AsCString(), rlocString.AsCString());
}
#endif
#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_WARN)
void Mle::LogProcessError(MessageType aType, Error aError) { LogError(kMessageReceive, aType, aError); }
void Mle::LogSendError(MessageType aType, Error aError) { LogError(kMessageSend, aType, aError); }
void Mle::LogError(MessageAction aAction, MessageType aType, Error aError)
{
if (aError != kErrorNone)
{
if (aAction == kMessageReceive && (aError == kErrorDrop || aError == kErrorNoRoute))
{
LogInfo("Failed to %s %s%s: %s", "process", MessageTypeToString(aType),
MessageTypeActionToSuffixString(aType, aAction), ErrorToString(aError));
}
else
{
LogWarn("Failed to %s %s%s: %s", aAction == kMessageSend ? "send" : "process", MessageTypeToString(aType),
MessageTypeActionToSuffixString(aType, aAction), ErrorToString(aError));
}
}
}
const char *Mle::MessageActionToString(MessageAction aAction)
{
static const char *const kMessageActionStrings[] = {
"Send", // (0) kMessageSend
"Receive", // (1) kMessageReceive
"Delay", // (2) kMessageDelay
"Remove Delayed", // (3) kMessageRemoveDelayed
};
static_assert(kMessageSend == 0, "kMessageSend value is incorrect");
static_assert(kMessageReceive == 1, "kMessageReceive value is incorrect");
static_assert(kMessageDelay == 2, "kMessageDelay value is incorrect");
static_assert(kMessageRemoveDelayed == 3, "kMessageRemoveDelayed value is incorrect");
return kMessageActionStrings[aAction];
}
const char *Mle::MessageTypeToString(MessageType aType)
{
static const char *const kMessageTypeStrings[] = {
"Advertisement", // (0) kTypeAdvertisement
"Announce", // (1) kTypeAnnounce
"Child ID Request", // (2) kTypeChildIdRequest
"Child ID Request", // (3) kTypeChildIdRequestShort
"Child ID Response", // (4) kTypeChildIdResponse
"Child Update Request", // (5) kTypeChildUpdateRequestOfParent
"Child Update Response", // (6) kTypeChildUpdateResponseOfParent
"Data Request", // (7) kTypeDataRequest
"Data Response", // (8) kTypeDataResponse
"Discovery Request", // (9) kTypeDiscoveryRequest
"Discovery Response", // (10) kTypeDiscoveryResponse
"delayed message", // (11) kTypeGenericDelayed
"UDP", // (12) kTypeGenericUdp
"Parent Request", // (13) kTypeParentRequestToRouters
"Parent Request", // (14) kTypeParentRequestToRoutersReeds
"Parent Response", // (15) kTypeParentResponse
#if OPENTHREAD_FTD
"Address Release", // (16) kTypeAddressRelease
"Address Release Reply", // (17) kTypeAddressReleaseReply
"Address Reply", // (18) kTypeAddressReply
"Address Solicit", // (19) kTypeAddressSolicit
"Child Update Request", // (20) kTypeChildUpdateRequestOfChild
"Child Update Response", // (21) kTypeChildUpdateResponseOfChild
"Child Update Response", // (22) kTypeChildUpdateResponseOfUnknownChild
"Link Accept", // (23) kTypeLinkAccept
"Link Accept and Request", // (24) kTypeLinkAcceptAndRequest
"Link Reject", // (25) kTypeLinkReject
"Link Request", // (26) kTypeLinkRequest
"Parent Request", // (27) kTypeParentRequest
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
"Time Sync", // (28) kTypeTimeSync
#endif
#endif
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE || OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
"Link Metrics Management Request", // (29) kTypeLinkMetricsManagementRequest
"Link Metrics Management Response", // (30) kTypeLinkMetricsManagementResponse
"Link Probe", // (31) kTypeLinkProbe
#endif
};
static_assert(kTypeAdvertisement == 0, "kTypeAdvertisement value is incorrect");
static_assert(kTypeAnnounce == 1, "kTypeAnnounce value is incorrect");
static_assert(kTypeChildIdRequest == 2, "kTypeChildIdRequest value is incorrect");
static_assert(kTypeChildIdRequestShort == 3, "kTypeChildIdRequestShort value is incorrect");
static_assert(kTypeChildIdResponse == 4, "kTypeChildIdResponse value is incorrect");
static_assert(kTypeChildUpdateRequestOfParent == 5, "kTypeChildUpdateRequestOfParent value is incorrect");
static_assert(kTypeChildUpdateResponseOfParent == 6, "kTypeChildUpdateResponseOfParent value is incorrect");
static_assert(kTypeDataRequest == 7, "kTypeDataRequest value is incorrect");
static_assert(kTypeDataResponse == 8, "kTypeDataResponse value is incorrect");
static_assert(kTypeDiscoveryRequest == 9, "kTypeDiscoveryRequest value is incorrect");
static_assert(kTypeDiscoveryResponse == 10, "kTypeDiscoveryResponse value is incorrect");
static_assert(kTypeGenericDelayed == 11, "kTypeGenericDelayed value is incorrect");
static_assert(kTypeGenericUdp == 12, "kTypeGenericUdp value is incorrect");
static_assert(kTypeParentRequestToRouters == 13, "kTypeParentRequestToRouters value is incorrect");
static_assert(kTypeParentRequestToRoutersReeds == 14, "kTypeParentRequestToRoutersReeds value is incorrect");
static_assert(kTypeParentResponse == 15, "kTypeParentResponse value is incorrect");
#if OPENTHREAD_FTD
static_assert(kTypeAddressRelease == 16, "kTypeAddressRelease value is incorrect");
static_assert(kTypeAddressReleaseReply == 17, "kTypeAddressReleaseReply value is incorrect");
static_assert(kTypeAddressReply == 18, "kTypeAddressReply value is incorrect");
static_assert(kTypeAddressSolicit == 19, "kTypeAddressSolicit value is incorrect");
static_assert(kTypeChildUpdateRequestOfChild == 20, "kTypeChildUpdateRequestOfChild value is incorrect");
static_assert(kTypeChildUpdateResponseOfChild == 21, "kTypeChildUpdateResponseOfChild value is incorrect");
static_assert(kTypeChildUpdateResponseOfUnknownChild == 22, "kTypeChildUpdateResponseOfUnknownChild is incorrect");
static_assert(kTypeLinkAccept == 23, "kTypeLinkAccept value is incorrect");
static_assert(kTypeLinkAcceptAndRequest == 24, "kTypeLinkAcceptAndRequest value is incorrect");
static_assert(kTypeLinkReject == 25, "kTypeLinkReject value is incorrect");
static_assert(kTypeLinkRequest == 26, "kTypeLinkRequest value is incorrect");
static_assert(kTypeParentRequest == 27, "kTypeParentRequest value is incorrect");
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
static_assert(kTypeTimeSync == 28, "kTypeTimeSync value is incorrect");
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE || OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
static_assert(kTypeLinkMetricsManagementRequest == 29, "kTypeLinkMetricsManagementRequest value is incorrect)");
static_assert(kTypeLinkMetricsManagementResponse == 30, "kTypeLinkMetricsManagementResponse value is incorrect)");
static_assert(kTypeLinkProbe == 31, "kTypeLinkProbe value is incorrect)");
#endif
#else // OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE || OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
static_assert(kTypeLinkMetricsManagementRequest == 28, "kTypeLinkMetricsManagementRequest value is incorrect)");
static_assert(kTypeLinkMetricsManagementResponse == 29, "kTypeLinkMetricsManagementResponse value is incorrect)");
static_assert(kTypeLinkProbe == 30, "kTypeLinkProbe value is incorrect)");
#endif
#endif // OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
#else // OPENTHREAD_FTD
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE || OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
static_assert(kTypeLinkMetricsManagementRequest == 16, "kTypeLinkMetricsManagementRequest value is incorrect)");
static_assert(kTypeLinkMetricsManagementResponse == 17, "kTypeLinkMetricsManagementResponse value is incorrect)");
static_assert(kTypeLinkProbe == 18, "kTypeLinkProbe value is incorrect)");
#endif
#endif // OPENTHREAD_FTD
return kMessageTypeStrings[aType];
}
const char *Mle::MessageTypeActionToSuffixString(MessageType aType, MessageAction aAction)
{
const char *str = "";
switch (aType)
{
case kTypeChildIdRequestShort:
str = " - short";
break;
case kTypeChildUpdateRequestOfParent:
case kTypeChildUpdateResponseOfParent:
str = (aAction == kMessageReceive) ? " from parent" : " to parent";
break;
case kTypeParentRequestToRouters:
str = " to routers";
break;
case kTypeParentRequestToRoutersReeds:
str = " to routers and REEDs";
break;
#if OPENTHREAD_FTD
case kTypeChildUpdateRequestOfChild:
case kTypeChildUpdateResponseOfChild:
str = (aAction == kMessageReceive) ? " from child" : " to child";
break;
case kTypeChildUpdateResponseOfUnknownChild:
str = (aAction == kMessageReceive) ? " from unknown child" : " to child";
break;
#endif // OPENTHREAD_FTD
default:
break;
}
return str;
}
#endif // #if OT_SHOULD_LOG_AT( OT_LOG_LEVEL_WARN)
// LCOV_EXCL_START
#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_NOTE)
const char *Mle::AttachModeToString(AttachMode aMode)
{
static const char *const kAttachModeStrings[] = {
"AnyPartition", // (0) kAnyPartition
"SamePartition", // (1) kSamePartition
"BetterPartition", // (2) kBetterPartition
"DowngradeToReed", // (3) kDowngradeToReed
"BetterParent", // (4) kBetterParent
};
static_assert(kAnyPartition == 0, "kAnyPartition value is incorrect");
static_assert(kSamePartition == 1, "kSamePartition value is incorrect");
static_assert(kBetterPartition == 2, "kBetterPartition value is incorrect");
static_assert(kDowngradeToReed == 3, "kDowngradeToReed value is incorrect");
static_assert(kBetterParent == 4, "kBetterParent value is incorrect");
return kAttachModeStrings[aMode];
}
const char *Mle::AttachStateToString(AttachState aState)
{
static const char *const kAttachStateStrings[] = {
"Idle", // (0) kAttachStateIdle
"ProcessAnnounce", // (1) kAttachStateProcessAnnounce
"Start", // (2) kAttachStateStart
"ParentReq", // (3) kAttachStateParent
"Announce", // (4) kAttachStateAnnounce
"ChildIdReq", // (5) kAttachStateChildIdRequest
};
static_assert(kAttachStateIdle == 0, "kAttachStateIdle value is incorrect");
static_assert(kAttachStateProcessAnnounce == 1, "kAttachStateProcessAnnounce value is incorrect");
static_assert(kAttachStateStart == 2, "kAttachStateStart value is incorrect");
static_assert(kAttachStateParentRequest == 3, "kAttachStateParentRequest value is incorrect");
static_assert(kAttachStateAnnounce == 4, "kAttachStateAnnounce value is incorrect");
static_assert(kAttachStateChildIdRequest == 5, "kAttachStateChildIdRequest value is incorrect");
return kAttachStateStrings[aState];
}
const char *Mle::ReattachStateToString(ReattachState aState)
{
static const char *const kReattachStateStrings[] = {
"", // (0) kReattachStop
"reattaching", // (1) kReattachStart
"reattaching with Active Dataset", // (2) kReattachActive
"reattaching with Pending Dataset", // (3) kReattachPending
};
static_assert(kReattachStop == 0, "kReattachStop value is incorrect");
static_assert(kReattachStart == 1, "kReattachStart value is incorrect");
static_assert(kReattachActive == 2, "kReattachActive value is incorrect");
static_assert(kReattachPending == 3, "kReattachPending value is incorrect");
return kReattachStateStrings[aState];
}
#endif // OT_SHOULD_LOG_AT( OT_LOG_LEVEL_NOTE)
// LCOV_EXCL_STOP
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE
Error Mle::SendLinkMetricsManagementRequest(const Ip6::Address &aDestination, const ot::Tlv &aSubTlv)
{
Error error = kErrorNone;
TxMessage *message = NewMleMessage(kCommandLinkMetricsManagementRequest);
Tlv tlv;
VerifyOrExit(message != nullptr, error = kErrorNoBufs);
tlv.SetType(Tlv::kLinkMetricsManagement);
tlv.SetLength(static_cast<uint8_t>(aSubTlv.GetSize()));
SuccessOrExit(error = message->Append(tlv));
SuccessOrExit(error = aSubTlv.AppendTo(*message));
error = message->SendTo(aDestination);
exit:
FreeMessageOnError(message, error);
return error;
}
#endif
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
uint64_t Mle::CalcParentCslMetric(const Mac::CslAccuracy &aCslAccuracy) const
{
// This function calculates the overall time that device will operate
// on battery by summing sequence of "ON quants" over a period of time.
static constexpr uint64_t usInSecond = 1000000;
uint64_t cslPeriodUs = kMinCslPeriod * kUsPerTenSymbols;
uint64_t cslTimeoutUs = GetCslTimeout() * usInSecond;
uint64_t k = cslTimeoutUs / cslPeriodUs;
return k * (k + 1) * cslPeriodUs / usInSecond * aCslAccuracy.GetClockAccuracy() +
aCslAccuracy.GetUncertaintyInMicrosec() * k;
}
#endif
Error Mle::DetachGracefully(otDetachGracefullyCallback aCallback, void *aContext)
{
Error error = kErrorNone;
uint32_t timeout = kDetachGracefullyTimeout;
VerifyOrExit(!IsDetachingGracefully(), error = kErrorBusy);
OT_ASSERT(!mDetachGracefullyCallback.IsSet());
mDetachGracefullyCallback.Set(aCallback, aContext);
#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
Get<BorderRouter::RoutingManager>().RequestStop();
#endif
switch (mRole)
{
case kRoleLeader:
break;
case kRoleRouter:
#if OPENTHREAD_FTD
Get<MleRouter>().SendAddressRelease();
#endif
break;
case kRoleChild:
IgnoreError(SendChildUpdateRequest(kAppendZeroTimeout));
break;
case kRoleDisabled:
case kRoleDetached:
// If device is already detached or disabled, we start the timer
// with zero duration to stop and invoke the callback when the
// timer fires, so the operation finishes immediately and
// asynchronously.
timeout = 0;
break;
}
mDetachGracefullyTimer.Start(timeout);
exit:
return error;
}
void Mle::HandleDetachGracefullyTimer(void) { Stop(); }
//---------------------------------------------------------------------------------------------------------------------
// TlvList
void Mle::TlvList::Add(uint8_t aTlvType)
{
VerifyOrExit(!Contains(aTlvType));
if (PushBack(aTlvType) != kErrorNone)
{
LogWarn("Failed to include TLV %d", aTlvType);
}
exit:
return;
}
void Mle::TlvList::AddElementsFrom(const TlvList &aTlvList)
{
for (uint8_t tlvType : aTlvList)
{
Add(tlvType);
}
}
//---------------------------------------------------------------------------------------------------------------------
// DelayedResponseMetadata
void Mle::DelayedResponseMetadata::ReadFrom(const Message &aMessage)
{
uint16_t length = aMessage.GetLength();
OT_ASSERT(length >= sizeof(*this));
IgnoreError(aMessage.Read(length - sizeof(*this), *this));
}
void Mle::DelayedResponseMetadata::RemoveFrom(Message &aMessage) const
{
SuccessOrAssert(aMessage.SetLength(aMessage.GetLength() - sizeof(*this)));
}
//---------------------------------------------------------------------------------------------------------------------
// TxMessage
Mle::TxMessage *Mle::NewMleMessage(Command aCommand)
{
Error error = kErrorNone;
TxMessage *message;
Message::Settings settings(Message::kNoLinkSecurity, Message::kPriorityNet);
Message::SubType subType;
uint8_t securitySuite;
message = static_cast<TxMessage *>(mSocket.NewMessage(0, settings));
VerifyOrExit(message != nullptr, error = kErrorNoBufs);
securitySuite = k154Security;
subType = Message::kSubTypeMleGeneral;
switch (aCommand)
{
case kCommandAnnounce:
subType = Message::kSubTypeMleAnnounce;
break;
case kCommandDiscoveryRequest:
subType = Message::kSubTypeMleDiscoverRequest;
securitySuite = kNoSecurity;
break;
case kCommandDiscoveryResponse:
subType = Message::kSubTypeMleDiscoverResponse;
securitySuite = kNoSecurity;
break;
case kCommandChildUpdateRequest:
subType = Message::kSubTypeMleChildUpdateRequest;
break;
case kCommandDataResponse:
subType = Message::kSubTypeMleDataResponse;
break;
case kCommandChildIdRequest:
subType = Message::kSubTypeMleChildIdRequest;
break;
case kCommandDataRequest:
subType = Message::kSubTypeMleDataRequest;
break;
default:
break;
}
message->SetSubType(subType);
SuccessOrExit(error = message->Append(securitySuite));
if (securitySuite == k154Security)
{
SecurityHeader securityHeader;
// The other fields in security header are updated in the
// message in `TxMessage::SendTo()` before message is sent.
securityHeader.InitSecurityControl();
SuccessOrExit(error = message->Append(securityHeader));
}
error = message->Append<uint8_t>(aCommand);
exit:
FreeAndNullMessageOnError(message, error);
return message;
}
Error Mle::TxMessage::AppendSourceAddressTlv(void)
{
return Tlv::Append<SourceAddressTlv>(*this, Get<Mle>().GetRloc16());
}
Error Mle::TxMessage::AppendStatusTlv(StatusTlv::Status aStatus) { return Tlv::Append<StatusTlv>(*this, aStatus); }
Error Mle::TxMessage::AppendModeTlv(DeviceMode aMode) { return Tlv::Append<ModeTlv>(*this, aMode.Get()); }
Error Mle::TxMessage::AppendTimeoutTlv(uint32_t aTimeout) { return Tlv::Append<TimeoutTlv>(*this, aTimeout); }
Error Mle::TxMessage::AppendChallengeTlv(const TxChallenge &aChallenge)
{
return Tlv::Append<ChallengeTlv>(*this, &aChallenge, sizeof(aChallenge));
}
Error Mle::TxMessage::AppendResponseTlv(const RxChallenge &aResponse)
{
return Tlv::Append<ResponseTlv>(*this, aResponse.GetBytes(), aResponse.GetLength());
}
Error Mle::TxMessage::AppendLinkFrameCounterTlv(void)
{
uint32_t counter;
// When including Link-layer Frame Counter TLV in an MLE message
// the value is set to the maximum MAC frame counter on all
// supported radio links. All radio links must also start using
// the same counter value as the value included in the TLV.
counter = Get<KeyManager>().GetMaximumMacFrameCounter();
#if OPENTHREAD_CONFIG_MULTI_RADIO
Get<KeyManager>().SetAllMacFrameCounters(counter, /* aSetIfLarger */ true);
#endif
return Tlv::Append<LinkFrameCounterTlv>(*this, counter);
}
Error Mle::TxMessage::AppendMleFrameCounterTlv(void)
{
return Tlv::Append<MleFrameCounterTlv>(*this, Get<KeyManager>().GetMleFrameCounter());
}
Error Mle::TxMessage::AppendAddress16Tlv(uint16_t aRloc16) { return Tlv::Append<Address16Tlv>(*this, aRloc16); }
Error Mle::TxMessage::AppendLeaderDataTlv(void)
{
LeaderDataTlv leaderDataTlv;
Get<Mle>().mLeaderData.SetDataVersion(Get<NetworkData::Leader>().GetVersion(NetworkData::kFullSet));
Get<Mle>().mLeaderData.SetStableDataVersion(Get<NetworkData::Leader>().GetVersion(NetworkData::kStableSubset));
leaderDataTlv.Init();
leaderDataTlv.Set(Get<Mle>().mLeaderData);
return leaderDataTlv.AppendTo(*this);
}
Error Mle::TxMessage::AppendNetworkDataTlv(NetworkData::Type aType)
{
Error error = kErrorNone;
uint8_t networkData[NetworkData::NetworkData::kMaxSize];
uint8_t length;
VerifyOrExit(!Get<Mle>().mRetrieveNewNetworkData, error = kErrorInvalidState);
length = sizeof(networkData);
IgnoreError(Get<NetworkData::Leader>().CopyNetworkData(aType, networkData, length));
error = Tlv::Append<NetworkDataTlv>(*this, networkData, length);
exit:
return error;
}
Error Mle::TxMessage::AppendTlvRequestTlv(const uint8_t *aTlvs, uint8_t aTlvsLength)
{
return Tlv::Append<TlvRequestTlv>(*this, aTlvs, aTlvsLength);
}
Error Mle::TxMessage::AppendScanMaskTlv(uint8_t aScanMask) { return Tlv::Append<ScanMaskTlv>(*this, aScanMask); }
Error Mle::TxMessage::AppendLinkMarginTlv(uint8_t aLinkMargin)
{
return Tlv::Append<LinkMarginTlv>(*this, aLinkMargin);
}
Error Mle::TxMessage::AppendVersionTlv(void) { return Tlv::Append<VersionTlv>(*this, kThreadVersion); }
Error Mle::TxMessage::AppendAddressRegistrationTlv(AddressRegistrationMode aMode)
{
Error error = kErrorNone;
Tlv tlv;
Lowpan::Context context;
uint8_t counter = 0;
uint16_t startOffset = GetLength();
tlv.SetType(Tlv::kAddressRegistration);
SuccessOrExit(error = Append(tlv));
// Prioritize ML-EID
SuccessOrExit(error = AppendCompressedAddressEntry(kMeshLocalPrefixContextId, Get<Mle>().GetMeshLocal64()));
// Continue to append the other addresses if not `kAppendMeshLocalOnly` mode
VerifyOrExit(aMode != kAppendMeshLocalOnly);
counter++;
#if OPENTHREAD_CONFIG_DUA_ENABLE
if (Get<ThreadNetif>().HasUnicastAddress(Get<DuaManager>().GetDomainUnicastAddress()) &&
(Get<NetworkData::Leader>().GetContext(Get<DuaManager>().GetDomainUnicastAddress(), context) == kErrorNone))
{
// Prioritize DUA, compressed entry
SuccessOrExit(
error = AppendCompressedAddressEntry(context.mContextId, Get<DuaManager>().GetDomainUnicastAddress()));
counter++;
}
#endif
for (const Ip6::Netif::UnicastAddress &addr : Get<ThreadNetif>().GetUnicastAddresses())
{
if (addr.GetAddress().IsLinkLocal() || Get<Mle>().IsRoutingLocator(addr.GetAddress()) ||
Get<Mle>().IsAnycastLocator(addr.GetAddress()) || addr.GetAddress() == Get<Mle>().GetMeshLocal64())
{
continue;
}
#if OPENTHREAD_CONFIG_DUA_ENABLE
if (addr.GetAddress() == Get<DuaManager>().GetDomainUnicastAddress())
{
continue;
}
#endif
if (Get<NetworkData::Leader>().GetContext(addr.GetAddress(), context) == kErrorNone)
{
SuccessOrExit(error = AppendCompressedAddressEntry(context.mContextId, addr.GetAddress()));
}
else
{
SuccessOrExit(error = AppendAddressEntry(addr.GetAddress()));
}
counter++;
// only continue to append if there is available entry.
VerifyOrExit(counter < kMaxIpAddressesToRegister);
}
// Append external multicast addresses. For sleepy end device,
// register all external multicast addresses with the parent for
// indirect transmission. Since Thread 1.2, non-sleepy MED should
// also register external multicast addresses of scope larger than
// realm with a 1.2 or higher parent.
if (!Get<Mle>().IsRxOnWhenIdle()
#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
|| !Get<Mle>().GetParent().IsThreadVersion1p1()
#endif
)
{
for (const Ip6::Netif::MulticastAddress &addr : Get<ThreadNetif>().IterateExternalMulticastAddresses())
{
#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
// For Thread 1.2 MED, skip multicast address with scope not
// larger than realm local when registering.
if (Get<Mle>().IsRxOnWhenIdle() && !addr.GetAddress().IsMulticastLargerThanRealmLocal())
{
continue;
}
#endif
SuccessOrExit(error = AppendAddressEntry(addr.GetAddress()));
counter++;
// only continue to append if there is available entry.
VerifyOrExit(counter < kMaxIpAddressesToRegister);
}
}
exit:
if (error == kErrorNone)
{
tlv.SetLength(static_cast<uint8_t>(GetLength() - startOffset - sizeof(Tlv)));
Write(startOffset, tlv);
}
return error;
}
Error Mle::TxMessage::AppendCompressedAddressEntry(uint8_t aContextId, const Ip6::Address &aAddress)
{
// Append an IPv6 address entry in an Address Registration TLV
// using compressed format (context ID with IID).
Error error;
SuccessOrExit(error = Append<uint8_t>(AddressRegistrationTlv::ControlByteFor(aContextId)));
error = Append(aAddress.GetIid());
exit:
return error;
}
Error Mle::TxMessage::AppendAddressEntry(const Ip6::Address &aAddress)
{
// Append an IPv6 address entry in an Address Registration TLV
// using uncompressed format
Error error;
uint8_t controlByte = AddressRegistrationTlv::kControlByteUncompressed;
SuccessOrExit(error = Append(controlByte));
error = Append(aAddress);
exit:
return error;
}
Error Mle::TxMessage::AppendSupervisionIntervalTlv(uint16_t aInterval)
{
return Tlv::Append<SupervisionIntervalTlv>(*this, aInterval);
}
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
Error Mle::TxMessage::AppendTimeRequestTlv(void)
{
// `TimeRequestTlv` has no value.
return Tlv::Append<TimeRequestTlv>(*this, nullptr, 0);
}
Error Mle::TxMessage::AppendTimeParameterTlv(void)
{
TimeParameterTlv tlv;
tlv.Init();
tlv.SetTimeSyncPeriod(Get<TimeSync>().GetTimeSyncPeriod());
tlv.SetXtalThreshold(Get<TimeSync>().GetXtalThreshold());
return tlv.AppendTo(*this);
}
Error Mle::TxMessage::AppendXtalAccuracyTlv(void)
{
return Tlv::Append<XtalAccuracyTlv>(*this, otPlatTimeGetXtalAccuracy());
}
#endif // OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
Error Mle::TxMessage::AppendActiveTimestampTlv(void)
{
Error error = kErrorNone;
const MeshCoP::Timestamp *timestamp = Get<MeshCoP::ActiveDatasetManager>().GetTimestamp();
VerifyOrExit(timestamp != nullptr);
error = Tlv::Append<ActiveTimestampTlv>(*this, *timestamp);
exit:
return error;
}
Error Mle::TxMessage::AppendPendingTimestampTlv(void)
{
Error error = kErrorNone;
const MeshCoP::Timestamp *timestamp = Get<MeshCoP::PendingDatasetManager>().GetTimestamp();
VerifyOrExit(timestamp != nullptr && timestamp->GetSeconds() != 0);
error = Tlv::Append<PendingTimestampTlv>(*this, *timestamp);
exit:
return error;
}
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
Error Mle::TxMessage::AppendCslChannelTlv(void)
{
CslChannelTlv cslChannel;
// CSL channel value of zero indicates that the CSL channel is not
// specified. We can use this value in the TLV as well.
cslChannel.Init();
cslChannel.SetChannelPage(0);
cslChannel.SetChannel(Get<Mac::Mac>().GetCslChannel());
return Append(cslChannel);
}
Error Mle::TxMessage::AppendCslTimeoutTlv(void)
{
uint32_t timeout = Get<Mle>().GetCslTimeout();
if (timeout == 0)
{
timeout = Get<Mle>().GetTimeout();
}
return Tlv::Append<CslTimeoutTlv>(*this, timeout);
}
#endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
Error Mle::TxMessage::AppendCslClockAccuracyTlv(void)
{
CslClockAccuracyTlv cslClockAccuracyTlv;
cslClockAccuracyTlv.Init();
cslClockAccuracyTlv.SetCslClockAccuracy(Get<Radio>().GetCslAccuracy());
cslClockAccuracyTlv.SetCslUncertainty(Get<Radio>().GetCslUncertainty());
return Append(cslClockAccuracyTlv);
}
#endif
Error Mle::TxMessage::SendTo(const Ip6::Address &aDestination)
{
Error error = kErrorNone;
uint16_t offset = 0;
uint8_t securitySuite;
Ip6::MessageInfo messageInfo;
messageInfo.SetPeerAddr(aDestination);
messageInfo.SetSockAddr(Get<Mle>().mLinkLocal64.GetAddress());
messageInfo.SetPeerPort(kUdpPort);
messageInfo.SetHopLimit(kMleHopLimit);
IgnoreError(Read(offset, securitySuite));
offset += sizeof(securitySuite);
if (securitySuite == k154Security)
{
SecurityHeader header;
// Update the fields in the security header
IgnoreError(Read(offset, header));
header.SetFrameCounter(Get<KeyManager>().GetMleFrameCounter());
header.SetKeyId(Get<KeyManager>().GetCurrentKeySequence());
Write(offset, header);
offset += sizeof(SecurityHeader);
SuccessOrExit(
error = Get<Mle>().ProcessMessageSecurity(Crypto::AesCcm::kEncrypt, *this, messageInfo, offset, header));
Get<KeyManager>().IncrementMleFrameCounter();
}
SuccessOrExit(error = Get<Mle>().mSocket.SendTo(*this, messageInfo));
exit:
return error;
}
Error Mle::TxMessage::SendAfterDelay(const Ip6::Address &aDestination, uint16_t aDelay)
{
Error error = kErrorNone;
DelayedResponseMetadata metadata;
metadata.mSendTime = TimerMilli::GetNow() + aDelay;
metadata.mDestination = aDestination;
SuccessOrExit(error = metadata.AppendTo(*this));
Get<Mle>().mDelayedResponses.Enqueue(*this);
Get<Mle>().mDelayedResponseTimer.FireAtIfEarlier(metadata.mSendTime);
exit:
return error;
}
#if OPENTHREAD_FTD
Error Mle::TxMessage::AppendConnectivityTlv(void)
{
ConnectivityTlv tlv;
tlv.Init();
Get<MleRouter>().FillConnectivityTlv(tlv);
return tlv.AppendTo(*this);
}
Error Mle::TxMessage::AppendAddressRegistrationTlv(Child &aChild)
{
Error error;
Tlv tlv;
Lowpan::Context context;
uint16_t startOffset = GetLength();
tlv.SetType(Tlv::kAddressRegistration);
SuccessOrExit(error = Append(tlv));
for (const Ip6::Address &address : aChild.IterateIp6Addresses())
{
if (address.IsMulticast() || Get<NetworkData::Leader>().GetContext(address, context) != kErrorNone)
{
SuccessOrExit(error = AppendAddressEntry(address));
}
else if (context.mContextId != kMeshLocalPrefixContextId)
{
SuccessOrExit(error = AppendCompressedAddressEntry(context.mContextId, address));
}
else
{
continue;
}
}
tlv.SetLength(static_cast<uint8_t>(GetLength() - startOffset - sizeof(Tlv)));
Write(startOffset, tlv);
exit:
return error;
}
Error Mle::TxMessage::AppendRouteTlv(Neighbor *aNeighbor)
{
RouteTlv tlv;
tlv.Init();
Get<RouterTable>().FillRouteTlv(tlv, aNeighbor);
return tlv.AppendTo(*this);
}
Error Mle::TxMessage::AppendActiveDatasetTlv(void)
{
return Get<MeshCoP::ActiveDatasetManager>().AppendMleDatasetTlv(*this);
}
Error Mle::TxMessage::AppendPendingDatasetTlv(void)
{
return Get<MeshCoP::PendingDatasetManager>().AppendMleDatasetTlv(*this);
}
#endif // OPENTHREAD_FTD
//---------------------------------------------------------------------------------------------------------------------
// RxMessage
Error Mle::RxMessage::ReadChallengeOrResponse(uint8_t aTlvType, RxChallenge &aRxChallenge) const
{
Error error;
uint16_t offset;
uint16_t length;
SuccessOrExit(error = Tlv::FindTlvValueOffset(*this, aTlvType, offset, length));
error = aRxChallenge.ReadFrom(*this, offset, length);
exit:
return error;
}
Error Mle::RxMessage::ReadChallengeTlv(RxChallenge &aChallenge) const
{
return ReadChallengeOrResponse(Tlv::kChallenge, aChallenge);
}
Error Mle::RxMessage::ReadResponseTlv(RxChallenge &aResponse) const
{
return ReadChallengeOrResponse(Tlv::kResponse, aResponse);
}
Error Mle::RxMessage::ReadFrameCounterTlvs(uint32_t &aLinkFrameCounter, uint32_t &aMleFrameCounter) const
{
Error error;
SuccessOrExit(error = Tlv::Find<LinkFrameCounterTlv>(*this, aLinkFrameCounter));
switch (Tlv::Find<MleFrameCounterTlv>(*this, aMleFrameCounter))
{
case kErrorNone:
break;
case kErrorNotFound:
aMleFrameCounter = aLinkFrameCounter;
break;
default:
error = kErrorParse;
break;
}
exit:
return error;
}
Error Mle::RxMessage::ReadLeaderDataTlv(LeaderData &aLeaderData) const
{
Error error;
LeaderDataTlv leaderDataTlv;
SuccessOrExit(error = Tlv::FindTlv(*this, leaderDataTlv));
VerifyOrExit(leaderDataTlv.IsValid(), error = kErrorParse);
leaderDataTlv.Get(aLeaderData);
exit:
return error;
}
Error Mle::RxMessage::ReadTlvRequestTlv(TlvList &aTlvList) const
{
Error error;
uint16_t offset;
uint16_t length;
SuccessOrExit(error = Tlv::FindTlvValueOffset(*this, Tlv::kTlvRequest, offset, length));
if (length > aTlvList.GetMaxSize())
{
length = aTlvList.GetMaxSize();
}
ReadBytes(offset, aTlvList.GetArrayBuffer(), length);
aTlvList.SetLength(static_cast<uint8_t>(length));
exit:
return error;
}
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
Error Mle::RxMessage::ReadCslClockAccuracyTlv(Mac::CslAccuracy &aCslAccuracy) const
{
Error error;
CslClockAccuracyTlv clockAccuracyTlv;
SuccessOrExit(error = Tlv::FindTlv(*this, clockAccuracyTlv));
VerifyOrExit(clockAccuracyTlv.IsValid(), error = kErrorParse);
aCslAccuracy.SetClockAccuracy(clockAccuracyTlv.GetCslClockAccuracy());
aCslAccuracy.SetUncertainty(clockAccuracyTlv.GetCslUncertainty());
exit:
return error;
}
#endif
#if OPENTHREAD_FTD
Error Mle::RxMessage::ReadRouteTlv(RouteTlv &aRouteTlv) const
{
Error error;
SuccessOrExit(error = Tlv::FindTlv(*this, aRouteTlv));
VerifyOrExit(aRouteTlv.IsValid(), error = kErrorParse);
exit:
return error;
}
#endif
//---------------------------------------------------------------------------------------------------------------------
// ParentCandidate
void Mle::ParentCandidate::Clear(void)
{
Instance &instance = GetInstance();
memset(reinterpret_cast<void *>(this), 0, sizeof(ParentCandidate));
Init(instance);
}
void Mle::ParentCandidate::CopyTo(Parent &aParent) const
{
// We use an intermediate pointer to copy `ParentCandidate`
// to silence code checker's warning about object slicing
// (assigning a sub-class to base class instance).
const Parent *candidateAsParent = this;
aParent = *candidateAsParent;
}
} // namespace Mle
} // namespace ot