blob: 0152854f4b4909cb3dc5a1106ffe0d5c58bd2fc1 [file]
/*
* 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 "instance/instance.hpp"
#include "openthread/platform/toolchain.h"
#include "radio/ble_secure.hpp"
#include "utils/static_counter.hpp"
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)
, mInitiallyAttachedAsSleepy(false)
, mRole(kRoleDisabled)
, mLastSavedRole(kRoleDisabled)
, mDeviceMode(DeviceMode::kModeRxOnWhenIdle)
, mRloc16(kInvalidRloc16)
, mPreviousParentRloc(kInvalidRloc16)
, mStoreFrameCounterAhead(kDefaultStoreFrameCounterAhead)
, mTimeout(kDefaultChildTimeout)
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
, mCslTimeout(kDefaultCslTimeout)
#endif
, mNeighborTable(aInstance)
, mDelayedSender(aInstance)
, mSocket(aInstance, *this)
, mPrevRoleRestorer(aInstance)
, mAttacher(aInstance)
, mDetacher(aInstance)
, mRetxTracker(aInstance)
, mAnnounceHandler(aInstance)
#if OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE
, mParentSearch(aInstance)
#endif
#if OPENTHREAD_CONFIG_WAKEUP_COORDINATOR_ENABLE
, mWakeupTxScheduler(aInstance)
, mWedAttachState(kWedDetached)
, mWedAttachTimer(aInstance)
#endif
#if OPENTHREAD_FTD
, mRouterEligible(true)
, mAddressSolicitPending(false)
, mAddressSolicitRejected(false)
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
, mCcmEnabled(false)
, mThreadVersionCheckEnabled(true)
#endif
, mNetworkIdTimeout(kNetworkIdTimeout)
, mRouterUpgradeThreshold(kRouterUpgradeThreshold)
, mRouterDowngradeThreshold(kRouterDowngradeThreshold)
, mPreviousPartitionRouterIdSequence(0)
, mPreviousPartitionIdTimeout(0)
, mChildRouterLinks(kChildRouterLinks)
, mAlternateRloc16Timeout(0)
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
, mMaxChildIpAddresses(0)
#endif
, mParentPriority(kParentPriorityUnspecified)
, mPreviousPartitionIdRouter(0)
, mPreviousPartitionId(0)
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
, mPreferredLeaderPartitionId(0)
#endif
, mAdvertiseTrickleTimer(aInstance, Mle::HandleAdvertiseTrickleTimer)
, mChildTable(aInstance)
, mRouterTable(aInstance)
#endif // OPENTHREAD_FTD
#if OPENTHREAD_CONFIG_P2P_ENABLE
, mP2p(aInstance)
#endif
{
mParent.Init(aInstance);
mLeaderData.Clear();
mParent.Clear();
ResetCounters();
mLinkLocalAddress.InitAsThreadOrigin();
mLinkLocalAddress.GetAddress().SetToLinkLocalAddress(Get<Mac::Mac>().GetExtAddress());
mMeshLocalEid.InitAsThreadOriginMeshLocal();
mMeshLocalEid.GetAddress().GetIid().GenerateRandom();
mMeshLocalRloc.InitAsThreadOriginMeshLocal();
mMeshLocalRloc.GetAddress().GetIid().SetToLocator(0);
mMeshLocalRloc.mRloc = true;
mLinkLocalAllThreadNodes.InitAsThreadOrigin();
mLinkLocalAllThreadNodes.GetAddress().mFields.m16[0] = BigEndian::HostSwap16(0xff32);
mLinkLocalAllThreadNodes.GetAddress().mFields.m16[7] = BigEndian::HostSwap16(0x0001);
mRealmLocalAllThreadNodes.InitAsThreadOrigin();
mRealmLocalAllThreadNodes.GetAddress().mFields.m16[0] = BigEndian::HostSwap16(0xff33);
mRealmLocalAllThreadNodes.GetAddress().mFields.m16[7] = BigEndian::HostSwap16(0x0001);
mMeshLocalPrefix.Clear();
SetMeshLocalPrefix(AsCoreType(&kMeshLocalPrefixInit));
#if OPENTHREAD_FTD
mDeviceMode.Set(mDeviceMode.Get() | DeviceMode::kModeFullThreadDevice | DeviceMode::kModeFullNetworkData);
#if OPENTHREAD_CONFIG_MLE_DEVICE_PROPERTY_LEADER_WEIGHT_ENABLE
mLeaderWeight = mDeviceProperties.CalculateLeaderWeight();
#else
mLeaderWeight = kDefaultLeaderWeight;
#endif
mLeaderAloc.InitAsThreadOriginMeshLocal();
SetRouterId(kInvalidRouterId);
#if OPENTHREAD_CONFIG_MLE_STEERING_DATA_SET_OOB_ENABLE
mSteeringData.Clear();
#endif
#endif // OPENTHREAD_FTD
}
Error Mle::Enable(void)
{
Error error = kErrorNone;
UpdateLinkLocalAddress();
SuccessOrExit(error = mSocket.Open(Ip6::kNetifThreadInternal));
SuccessOrExit(error = mSocket.Bind(kUdpPort));
#if OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE
mParentSearch.SetEnabled(true);
#endif
exit:
return error;
}
void Mle::ScheduleChildUpdateRequest(void)
{
mDelayedSender.ScheduleChildUpdateRequestToParent(kChildUpdateRequestDelay);
}
Error Mle::Disable(void)
{
Error error = kErrorNone;
Stop(kKeepNetworkDatasets);
SuccessOrExit(error = mSocket.Close());
Get<ThreadNetif>().RemoveUnicastAddress(mLinkLocalAddress);
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();
Get<ThreadNetif>().AddUnicastAddress(mMeshLocalEid);
Get<ThreadNetif>().SubscribeMulticast(mLinkLocalAllThreadNodes);
Get<ThreadNetif>().SubscribeMulticast(mRealmLocalAllThreadNodes);
SetRloc16(GetRloc16());
Get<KeyManager>().Start();
mAttacher.Start(aMode);
exit:
return error;
}
void Mle::Stop(StopMode aMode)
{
if (aMode == kUpdateNetworkDatasets)
{
IgnoreError(Get<MeshCoP::ActiveDatasetManager>().Restore());
IgnoreError(Get<MeshCoP::PendingDatasetManager>().Restore());
}
VerifyOrExit(!IsDisabled());
mDelayedSender.Stop();
mPrevRoleRestorer.Stop();
mAnnounceHandler.Stop();
Get<KeyManager>().Stop();
SetStateDetached();
Get<ThreadNetif>().UnsubscribeMulticast(mRealmLocalAllThreadNodes);
Get<ThreadNetif>().UnsubscribeMulticast(mLinkLocalAllThreadNodes);
Get<ThreadNetif>().RemoveUnicastAddress(mMeshLocalRloc);
Get<ThreadNetif>().RemoveUnicastAddress(mMeshLocalEid);
SetRole(kRoleDisabled);
exit:
mDetacher.HandleStop();
}
const Counters &Mle::GetCounters(void)
{
UpdateRoleTimeCounters(mRole);
return mCounters;
}
void Mle::ResetCounters(void)
{
ClearAllBytes(mCounters);
mLastUpdatedTimestamp = Get<UptimeTracker>().GetUptime();
}
uint32_t Mle::GetCurrentAttachDuration(void) const
{
return IsAttached() ? Get<UptimeTracker>().GetUptimeInSeconds() - mLastAttachTime : 0;
}
void Mle::UpdateRoleTimeCounters(DeviceRole aRole)
{
UptimeMsec currentUptimeMsec = Get<UptimeTracker>().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;
}
}
void Mle::SetRole(DeviceRole aRole)
{
DeviceRole oldRole = mRole;
SuccessOrExit(Get<Notifier>().Update(mRole, aRole, kEventThreadRoleChanged));
LogNote("Role %s -> %s", RoleToString(oldRole), RoleToString(mRole));
if ((oldRole == kRoleDetached) && IsAttached())
{
mLastAttachTime = Get<UptimeTracker>().GetUptimeInSeconds();
}
UpdateRoleTimeCounters(oldRole);
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 OPENTHREAD_CONFIG_BLE_TCAT_ENABLE
IgnoreError(Get<Ble::BleSecure>().NotifyAdvertisementChanged());
#endif
// 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();
}
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
Get<Mac::Mac>().SetCslCapable(IsCslSupported() && !IsRxOnWhenIdle());
#endif
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(),
KeyManager::kForceUpdate | KeyManager::kGuardTimerUnchanged);
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);
mLastSavedRole = static_cast<DeviceRole>(networkInfo.GetRole());
switch (mLastSavedRole)
{
case kRoleChild:
case kRoleRouter:
case kRoleLeader:
break;
default:
ExitNow();
}
#if OPENTHREAD_MTD
if (IsChildRloc16(networkInfo.GetRloc16()))
#endif
{
Get<Mac::Mac>().SetShortAddress(networkInfo.GetRloc16());
mRloc16 = networkInfo.GetRloc16();
}
Get<Mac::Mac>().SetExtAddress(networkInfo.GetExtAddress());
mMeshLocalEid.GetAddress().SetIid(networkInfo.GetMeshLocalIid());
if (networkInfo.GetRloc16() == kInvalidRloc16)
{
ExitNow();
}
if (IsChildRloc16(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(ParentRloc16ForRloc16(networkInfo.GetRloc16()));
mParent.SetState(Neighbor::kStateRestored);
mPreviousParentRloc = mParent.GetRloc16();
}
#if OPENTHREAD_FTD
else
{
SetRouterId(RouterIdFromRloc16(GetRloc16()));
SetPreviousPartitionId(networkInfo.GetPreviousPartitionId());
Get<ChildTable>().Restore();
}
#endif
// Successfully restored the network information from
// non-volatile settings after boot.
mHasRestored = true;
exit:
return;
}
void Mle::Store(void)
{
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(mMeshLocalEid.GetAddress().GetIid());
networkInfo.SetVersion(kThreadVersion);
mLastSavedRole = mRole;
if (IsChild())
{
Settings::ParentInfo parentInfo;
parentInfo.Init();
parentInfo.SetExtAddress(mParent.GetExtAddress());
parentInfo.SetVersion(mParent.GetVersion());
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() + mStoreFrameCounterAhead);
networkInfo.SetMacFrameCounter(Get<KeyManager>().GetMaximumMacFrameCounter() + mStoreFrameCounterAhead);
networkInfo.SetDeviceMode(mDeviceMode.Get());
Get<Settings>().Save(networkInfo);
Get<KeyManager>().SetStoredMleFrameCounter(networkInfo.GetMleFrameCounter());
Get<KeyManager>().SetStoredMacFrameCounter(networkInfo.GetMacFrameCounter());
LogDebg("Store Network Information");
exit:
return;
}
Error Mle::BecomeDetached(void)
{
Error error = kErrorNone;
VerifyOrExit(!IsDisabled(), error = kErrorInvalidState);
if (IsDetached() && mAttacher.WillStartAttachSoon())
{
// Already detached and waiting to start an attach attempt, so
// there is not need to make any changes.
ExitNow();
}
// Not in reattach stage after reset
if (mAttacher.IsReattachWithDatasetDone())
{
IgnoreError(Get<MeshCoP::PendingDatasetManager>().Restore());
}
#if OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE
mParentSearch.SetRecentlyDetached();
#endif
SetStateDetached();
mParent.SetState(Neighbor::kStateInvalid);
SetRloc16(kInvalidRloc16);
mAttacher.Attach(kAnyPartition);
exit:
return error;
}
Error Mle::BecomeChild(void)
{
Error error = kErrorNone;
VerifyOrExit(!IsDisabled(), error = kErrorInvalidState);
VerifyOrExit(!IsAttaching(), error = kErrorBusy);
mAttacher.Attach(kAnyPartition);
exit:
return error;
}
Error Mle::SearchForBetterParent(void)
{
Error error = kErrorNone;
VerifyOrExit(IsChild(), error = kErrorInvalidState);
mAttacher.Attach(kBetterParent);
exit:
return error;
}
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 OPENTHREAD_FTD
if (IsLeader())
{
Get<ThreadNetif>().RemoveUnicastAddress(mLeaderAloc);
}
#endif
SetRole(kRoleDetached);
mAttacher.CancelAttachOnRoleChange();
mDelayedSender.RemoveScheduledChildUpdateRequestToParent();
mRetxTracker.Stop();
mInitiallyAttachedAsSleepy = false;
Get<MeshForwarder>().SetRxOnWhenIdle(true);
Get<Mac::Mac>().SetBeaconEnabled(false);
#if OPENTHREAD_FTD
ClearAlternateRloc16();
HandleDetachStart();
#endif
}
void Mle::SetStateChild(uint16_t aRloc16)
{
#if OPENTHREAD_FTD
if (IsLeader())
{
Get<ThreadNetif>().RemoveUnicastAddress(mLeaderAloc);
}
#endif
SetRloc16(aRloc16);
SetRole(kRoleChild);
mAttacher.CancelAttachOnRoleChange();
Get<Mac::Mac>().SetBeaconEnabled(false);
mRetxTracker.UpdateOnRoleChangeToChild();
mPrevRoleRestorer.Stop();
#if OPENTHREAD_FTD
if (IsFullThreadDevice())
{
HandleChildStart();
}
#endif
if (mAnnounceHandler.IsAnnounceAttaching())
{
mAnnounceHandler.HandleAnnounceAttachSuccess();
}
#if OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE
mParentSearch.UpdateState();
#endif
if ((mPreviousParentRloc != kInvalidRloc16) && (mPreviousParentRloc != mParent.GetRloc16()))
{
mCounters.mParentChanges++;
#if OPENTHREAD_CONFIG_MLE_INFORM_PREVIOUS_PARENT_ON_REATTACH
InformPreviousParent();
#endif
}
mPreviousParentRloc = mParent.GetRloc16();
}
uint32_t Mle::GenerateRandomDelay(uint32_t aMaxDelay) const
{
// Generates a random delay within `[1, aMaxDelay]` (inclusive).
return 1 + Random::NonCrypto::GetUint32InRange(0, aMaxDelay);
}
void Mle::SetTimeout(uint32_t aTimeout, TimeoutAction aAction)
{
// Determine `kMinTimeout` based on other parameters. `kMaxTimeout`
// is set (per spec) to minimum Delay Timer value for a Pending
// Operational Dataset when updating the Network Key which is 8
// hours.
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));
static constexpr uint32_t kMaxTimeout = (8 * Time::kOneHourInSec);
static_assert(kMinTimeout <= kMaxTimeout, "Min timeout MUST be less than Max timeout");
aTimeout = Clamp(aTimeout, kMinTimeout, kMaxTimeout);
VerifyOrExit(mTimeout != aTimeout);
mTimeout = aTimeout;
Get<DataPollSender>().RecalculatePollPeriod();
if (IsChild() && (aAction == kSendChildUpdateToParent))
{
IgnoreError(SendChildUpdateRequestToParent());
}
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<HistoryTracker::Local>().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());
Store();
#if OPENTHREAD_FTD
if (!aDeviceMode.IsFullThreadDevice())
{
ClearAlternateRloc16();
}
#endif
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)
{
mAttacher.ResetAttachCounter();
IgnoreError(BecomeDetached());
ExitNow();
}
}
if (IsDetached())
{
mAttacher.ResetAttachCounter();
SetStateDetached();
mAttacher.Attach(kAnyPartition);
}
else if (IsChild())
{
SetStateChild(GetRloc16());
IgnoreError(SendChildUpdateRequestToParent());
}
exit:
return error;
}
void Mle::UpdateLinkLocalAddress(void)
{
Get<ThreadNetif>().RemoveUnicastAddress(mLinkLocalAddress);
mLinkLocalAddress.GetAddress().GetIid().SetFromExtAddress(Get<Mac::Mac>().GetExtAddress());
Get<ThreadNetif>().AddUnicastAddress(mLinkLocalAddress);
Get<Notifier>().Signal(kEventThreadLinkLocalAddrChanged);
}
void Mle::SetMeshLocalPrefix(const Ip6::NetworkPrefix &aMeshLocalPrefix)
{
VerifyOrExit(mMeshLocalPrefix != aMeshLocalPrefix);
mMeshLocalPrefix = aMeshLocalPrefix;
// We ask `ThreadNetif` to apply the new mesh-local prefix which
// will then update all of its assigned unicast addresses that are
// marked as mesh-local, as well as all of the subscribed mesh-local
// prefix-based multicast addresses (such as link-local or
// realm-local All Thread Nodes addresses). It is important to call
// `ApplyNewMeshLocalPrefix()` first so that `ThreadNetif` can
// correctly signal the updates. It will first signal the removal
// of the previous address based on the old prefix, and then the
// addition of the new address with the new mesh-local prefix.
Get<ThreadNetif>().ApplyNewMeshLocalPrefix();
// Some of the addresses may already be updated from the
// `ApplyNewMeshLocalPrefix()` call, but we apply the new prefix to
// them in case they are not yet added to the `Netif`. This ensures
// that addresses are always updated and other modules can retrieve
// them using methods such as `GetMeshLocalRloc()`, `GetMeshLocalEid()`
// or `GetLinkLocalAllThreadNodesAddress()`, even if they have not
// yet been added to the `Netif`.
mMeshLocalEid.GetAddress().SetPrefix(mMeshLocalPrefix);
mMeshLocalRloc.GetAddress().SetPrefix(mMeshLocalPrefix);
mLinkLocalAllThreadNodes.GetAddress().SetMulticastNetworkPrefix(mMeshLocalPrefix);
mRealmLocalAllThreadNodes.GetAddress().SetMulticastNetworkPrefix(mMeshLocalPrefix);
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
Get<BackboneRouter::Local>().ApplyNewMeshLocalPrefix();
#endif
Get<Notifier>().Signal(kEventThreadMeshLocalAddrChanged);
exit:
return;
}
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
Error Mle::SetMeshLocalIid(const Ip6::InterfaceIdentifier &aMlIid)
{
Error error = kErrorNone;
VerifyOrExit(!Get<ThreadNetif>().HasUnicastAddress(mMeshLocalEid), error = kErrorInvalidState);
mMeshLocalEid.GetAddress().SetIid(aMlIid);
exit:
return error;
}
#endif
void Mle::SetRloc16(uint16_t aRloc16)
{
uint16_t oldRloc16 = GetRloc16();
if (aRloc16 != oldRloc16)
{
LogNote("RLOC16 %04x -> %04x", oldRloc16, aRloc16);
}
if (Get<ThreadNetif>().HasUnicastAddress(mMeshLocalRloc) &&
(mMeshLocalRloc.GetAddress().GetIid().GetLocator() != aRloc16))
{
Get<ThreadNetif>().RemoveUnicastAddress(mMeshLocalRloc);
Get<Tmf::Agent>().ClearRequests(mMeshLocalRloc.GetAddress());
}
Get<Mac::Mac>().SetShortAddress(aRloc16);
mRloc16 = aRloc16;
if (aRloc16 != kInvalidRloc16)
{
// We can always call `AddUnicastAddress(mMeshLocat16)` and if
// the address is already added, it will perform no action.
mMeshLocalRloc.GetAddress().GetIid().SetLocator(aRloc16);
Get<ThreadNetif>().AddUnicastAddress(mMeshLocalRloc);
#if OPENTHREAD_FTD
Get<AddressResolver>().RestartAddressQueries();
#endif
}
else
{
#if OPENTHREAD_FTD
ClearAlternateRloc16();
#endif
}
}
void Mle::SetLeaderData(const LeaderData &aLeaderData)
{
SetLeaderData(aLeaderData.GetPartitionId(), aLeaderData.GetWeighting(), aLeaderData.GetLeaderRouterId());
}
void Mle::SetLeaderData(uint32_t aPartitionId, uint8_t aWeighting, uint8_t aLeaderRouterId)
{
if (mLeaderData.GetPartitionId() != aPartitionId)
{
#if OPENTHREAD_FTD
HandlePartitionChange();
#endif
Get<Notifier>().Signal(kEventThreadPartitionIdChanged);
mCounters.mPartitionIdChanges++;
}
else
{
Get<Notifier>().SignalIfFirst(kEventThreadPartitionIdChanged);
}
mLeaderData.SetPartitionId(aPartitionId);
mLeaderData.SetWeighting(aWeighting);
mLeaderData.SetLeaderRouterId(aLeaderRouterId);
}
void Mle::GetLeaderRloc(Ip6::Address &aAddress) const
{
aAddress.SetToRoutingLocator(mMeshLocalPrefix, GetLeaderRloc16());
}
void Mle::GetLeaderAloc(Ip6::Address &aAddress) const
{
aAddress.SetToAnycastLocator(mMeshLocalPrefix, Aloc16::ForLeader());
}
void Mle::GetCommissionerAloc(uint16_t aSessionId, Ip6::Address &aAddress) const
{
aAddress.SetToAnycastLocator(mMeshLocalPrefix, Aloc16::FromCommissionerSessionId(aSessionId));
}
void Mle::GetServiceAloc(uint8_t aServiceId, Ip6::Address &aAddress) const
{
aAddress.SetToAnycastLocator(mMeshLocalPrefix, Aloc16::FromServiceId(aServiceId));
}
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().IsLinkLocalUnicast() && !IsRoutingLocator(addr.GetAddress()) &&
!IsAnycastLocator(addr.GetAddress()) && addr.GetAddress() != GetMeshLocalEid())
{
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;
}
bool Mle::IsCslSupported(void) const { return IsChild() && GetParent().IsThreadVersion1p2OrHigher(); }
#endif
void Mle::InitNeighbor(Neighbor &aNeighbor, const RxInfo &aRxInfo)
{
aNeighbor.GetExtAddress().SetFromIid(aRxInfo.mMessageInfo.GetPeerAddr().GetIid());
aNeighbor.GetLinkInfo().Clear();
aNeighbor.GetLinkInfo().AddRss(aRxInfo.mMessage.GetAverageRss());
aNeighbor.ResetLinkFailures();
aNeighbor.SetLastHeard(TimerMilli::GetNow());
}
void Mle::ScheduleChildUpdateRequestIfMtdChild(void)
{
if (IsChild() && !IsFullThreadDevice())
{
ScheduleChildUpdateRequest();
}
}
void Mle::HandleNotifierEvents(Events aEvents)
{
VerifyOrExit(!IsDisabled());
if (aEvents.ContainsAny(kEventIp6AddressAdded | kEventIp6AddressRemoved))
{
if (!Get<ThreadNetif>().HasUnicastAddress(mMeshLocalEid.GetAddress()))
{
mMeshLocalEid.GetAddress().GetIid().GenerateRandom();
Get<ThreadNetif>().AddUnicastAddress(mMeshLocalEid);
Get<Notifier>().Signal(kEventThreadMeshLocalAddrChanged);
}
ScheduleChildUpdateRequestIfMtdChild();
}
if (aEvents.ContainsAny(kEventIp6MulticastSubscribed | kEventIp6MulticastUnsubscribed) &&
ShouldRegisterMulticastAddrsWithParent())
{
ScheduleChildUpdateRequestIfMtdChild();
}
if (aEvents.Contains(kEventThreadNetdataChanged))
{
#if OPENTHREAD_FTD
if (IsFullThreadDevice())
{
HandleNetworkDataUpdateRouter();
}
else
#endif
{
if (!aEvents.Contains(kEventThreadRoleChanged))
{
ScheduleChildUpdateRequest();
}
}
}
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())
{
Store();
}
}
#if OPENTHREAD_FTD
if (aEvents.Contains(kEventSecurityPolicyChanged))
{
HandleSecurityPolicyChanged();
}
#endif
if (aEvents.Contains(kEventSupportedChannelMaskChanged))
{
Mac::ChannelMask channelMask = Get<Mac::Mac>().GetSupportedChannelMask();
if (!channelMask.ContainsChannel(Get<Mac::Mac>().GetPanChannel()) && (mRole != kRoleDisabled))
{
LogWarn("Channel %u is not in the supported channel mask %s, detach the network gracefully!",
Get<Mac::Mac>().GetPanChannel(), channelMask.ToString().AsCString());
IgnoreError(DetachGracefully(nullptr, nullptr));
}
}
exit:
return;
}
bool Mle::ShouldRegisterMulticastAddrsWithParent(void) const
{
// 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.
bool shouldRegister = !IsRxOnWhenIdle();
#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
shouldRegister |= !IsFullThreadDevice() && GetParent().IsThreadVersion1p2OrHigher();
#endif
return shouldRegister;
}
Error Mle::SendDataRequestToParent(void)
{
Ip6::Address destination;
destination.SetToLinkLocalAddress(mParent.GetExtAddress());
return SendDataRequest(destination);
}
Error Mle::SendDataRequest(const Ip6::Address &aDestination)
{
static const uint8_t kTlvs[] = {Tlv::kNetworkData, Tlv::kRoute};
Error error = kErrorNone;
VerifyOrExit(IsAttached());
// Based on `mRequestRouteTlv` include both Network Data and Route
// TLVs or only Network Data TLV.
error = SendDataRequest(aDestination, kTlvs, mRequestRouteTlv ? 2 : 1);
mRetxTracker.UpdateOnDataRequestTx();
exit:
return error;
}
#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), &aQueryInfo);
}
Error Mle::SendDataRequest(const Ip6::Address &aDestination,
const uint8_t *aTlvs,
uint8_t aTlvsLength,
const LinkMetrics::Initiator::QueryInfo *aQueryInfo)
#else
Error Mle::SendDataRequest(const Ip6::Address &aDestination, const uint8_t *aTlvs, uint8_t aTlvsLength)
#endif
{
Error error = kErrorNone;
TxMessage *message;
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
SuccessOrExit(error = message->AppendActiveAndPendingTimestampTlvs());
SuccessOrExit(error = message->SendTo(aDestination));
Log(kMessageSend, kTypeDataRequest, aDestination);
if (!IsRxOnWhenIdle())
{
Get<DataPollSender>().SendFastPolls(DataPollSender::kDefaultFastPolls);
}
exit:
FreeMessageOnError(message, error);
return error;
}
Error Mle::SendChildUpdateRequestToParent(void) { return SendChildUpdateRequestToParent(kNormalChildUpdateRequest); }
Error Mle::SendChildUpdateRequestToParent(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();
}
mDelayedSender.RemoveScheduledChildUpdateRequestToParent();
// Track Child Update retx, except when gracefully detaching
// i.e., sending Child Update with zero timeout, or restoring
// previous child role (re-establishing link with parent).
switch (aMode)
{
case kNormalChildUpdateRequest:
case kAppendChallengeTlv:
mRetxTracker.UpdateOnChildUpdateRequestTx();
break;
case kAppendZeroTimeout:
case kToRestoreChildRole:
break;
}
VerifyOrExit((message = NewMleMessage(kCommandChildUpdateRequest)) != nullptr, error = kErrorNoBufs);
SuccessOrExit(error = message->AppendModeTlv(mDeviceMode));
switch (aMode)
{
case kNormalChildUpdateRequest:
case kAppendZeroTimeout:
break;
case kAppendChallengeTlv:
mPrevRoleRestorer.GenerateRandomChallenge();
OT_FALL_THROUGH;
case kToRestoreChildRole:
// The challenge used for child role restoration is generated
// only once, specifically when the `mPrevRoleRestorer` state
// changes and the process starts. We reuse this single challenge
// for all "Child Update Request" retries. This prevents a new
// challenge from invalidating a potentially delayed, yet correct,
// response from the parent.
SuccessOrExit(error = message->AppendChallengeTlv(mPrevRoleRestorer.GetChallenge()));
break;
}
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->AppendSupervisionIntervalTlvIfSleepyChild());
#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, kTypeChildUpdateRequestAsChild, 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::SendChildUpdateRejectResponse(ChildUpdateResponseInfo &aInfo)
{
// Send a reject response which only includes a Source Address TLV,
// a Status TLV, and a Response TLV when request contained a
// Challenge TLV.
aInfo.mTlvList.Clear();
aInfo.mTlvList.Add(Tlv::kSourceAddress);
aInfo.mTlvList.Add(Tlv::kStatus);
if (!aInfo.mChallenge.IsEmpty())
{
aInfo.mTlvList.Add(Tlv::kResponse);
}
return SendChildUpdateResponse(aInfo);
}
Error Mle::SendChildUpdateResponse(const ChildUpdateResponseInfo &aInfo)
{
Error error = kErrorNone;
TxMessage *message;
bool checkAddress = false;
VerifyOrExit((message = NewMleMessage(kCommandChildUpdateResponse)) != nullptr, error = kErrorNoBufs);
for (uint8_t tlvType : aInfo.mTlvList)
{
switch (tlvType)
{
case Tlv::kSourceAddress:
SuccessOrExit(error = message->AppendSourceAddressTlv());
break;
case Tlv::kLeaderData:
SuccessOrExit(error = message->AppendLeaderDataTlv());
break;
case Tlv::kTimeout:
SuccessOrExit(error = message->AppendTimeoutTlv(mTimeout));
break;
case Tlv::kStatus:
SuccessOrExit(error = message->AppendStatusTlv(kStatusError));
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(aInfo.mChallenge));
break;
case Tlv::kLinkFrameCounter:
SuccessOrExit(error = message->AppendLinkFrameCounterTlv());
break;
case Tlv::kMleFrameCounter:
SuccessOrExit(error = message->AppendMleFrameCounterTlv());
break;
case Tlv::kSupervisionInterval:
SuccessOrExit(error = message->AppendSupervisionIntervalTlvIfSleepyChild());
break;
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
case Tlv::kCslTimeout:
if (Get<Mac::Mac>().IsCslEnabled())
{
SuccessOrExit(error = message->AppendCslTimeoutTlv());
}
break;
#endif
}
}
SuccessOrExit(error = message->SendTo(aInfo.mDestination));
Log(kMessageSend, kTypeChildUpdateResponseAsChild, aInfo.mDestination);
if (checkAddress && HasUnregisteredAddress())
{
IgnoreError(SendChildUpdateRequestToParent());
}
exit:
FreeMessageOnError(message, error);
return error;
}
void Mle::SendAnnounce(uint8_t aChannel, AnnounceMode aMode)
{
SendAnnounce(aChannel, Ip6::Address::GetLinkLocalAllNodesMulticast(), aMode);
}
void Mle::SendAnnounce(uint8_t aChannel, const Ip6::Address &aDestination, AnnounceMode aMode)
{
Error error = kErrorNone;
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);
SuccessOrExit(error = Tlv::Append<ChannelTlv>(*message, ChannelTlvValue(Get<Mac::Mac>().GetPanChannel())));
switch (aMode)
{
case kOrphanAnnounce:
activeTimestamp.SetToOrphanAnnounce();
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);
}
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
Error Mle::SendLinkMetricsManagementResponse(const Ip6::Address &aDestination, LinkMetrics::Status aStatus)
{
Error error = kErrorNone;
TxMessage *message;
Tlv::Bookmark tlvBookmark;
VerifyOrExit((message = NewMleMessage(kCommandLinkMetricsManagementResponse)) != nullptr, error = kErrorNoBufs);
SuccessOrExit(error = Tlv::StartTlv(*message, Tlv::kLinkMetricsManagement, tlvBookmark));
SuccessOrExit(error = Tlv::Append<LinkMetrics::StatusSubTlv>(*message, aStatus));
SuccessOrExit(error = Tlv::EndTlv(*message, tlvBookmark));
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::Bookmark tlvBookmark;
VerifyOrExit((message = NewMleMessage(kCommandLinkProbe)) != nullptr, error = kErrorNoBufs);
SuccessOrExit(error = Tlv::StartTlv(*message, Tlv::kLinkProbe, tlvBookmark));
SuccessOrExit(error = message->Append(aSeriesId));
SuccessOrExit(error = message->AppendBytes(aBuf, aLength));
SuccessOrExit(error = Tlv::EndTlv(*message, tlvBookmark));
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;
}
extAddress.SetFromIid(senderAddress->GetIid());
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
aMessage.RemoveFooter(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);
aMessage.RemoveFooter(kMleSecurityTagSize);
}
exit:
return error;
}
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(aMessage.GetOrigin() == Message::kOriginThreadNetif);
VerifyOrExit(aMessageInfo.GetPeerAddr().IsLinkLocalUnicast());
VerifyOrExit(aMessageInfo.GetSockAddr().IsLinkLocalUnicastOrMulticast());
VerifyOrExit(aMessageInfo.GetHopLimit() == kMleHopLimit, error = kErrorParse);
SuccessOrExit(error = aMessage.ReadAtAndAdvanceOffset(securitySuite));
if (securitySuite == kNoSecurity)
{
SuccessOrExit(error = aMessage.ReadAtAndAdvanceOffset(command));
switch (command)
{
#if OPENTHREAD_FTD
case kCommandDiscoveryRequest:
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.ReadAtAndAdvanceOffset(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.ReadAtAndAdvanceOffset(command));
extAddr.SetFromIid(aMessageInfo.GetPeerAddr().GetIid());
neighbor = (command == kCommandChildIdResponse) ? mNeighborTable.FindParent(extAddr)
: mNeighborTable.FindNeighbor(extAddr);
#if OPENTHREAD_FTD
if (neighbor == nullptr)
{
// As an FTD child, 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);
#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
CheckTrelPeerAddrOnSecureMleRx(aMessage);
#endif
// 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_RADIO_LINK_TREL_ENABLE
CheckTrelPeerAddrOnSecureMleRx(aMessage);
#endif
#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 FTD child,
// 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:
mAttacher.HandleParentResponse(rxInfo);
break;
case kCommandChildIdResponse:
mAttacher.HandleChildIdResponse(rxInfo);
break;
case kCommandAnnounce:
mAnnounceHandler.HandleAnnounce(rxInfo);
break;
case kCommandChildUpdateRequest:
HandleChildUpdateRequest(rxInfo);
break;
case kCommandChildUpdateResponse:
HandleChildUpdateResponse(rxInfo);
break;
#if OPENTHREAD_FTD
case kCommandLinkRequest:
HandleLinkRequest(rxInfo);
break;
case kCommandLinkAccept:
HandleLinkAccept(rxInfo);
break;
case kCommandLinkAcceptAndRequest:
HandleLinkAcceptAndRequest(rxInfo);
break;
case kCommandDataRequest:
HandleDataRequest(rxInfo);
break;
case kCommandParentRequest:
HandleParentRequest(rxInfo);
break;
case kCommandChildIdRequest:
HandleChildIdRequest(rxInfo);
break;
#endif // OPENTHREAD_FTD
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
case kCommandTimeSync:
HandleTimeSync(rxInfo);
break;
#endif
#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
#if OPENTHREAD_CONFIG_P2P_ENABLE
#if OPENTHREAD_CONFIG_WAKEUP_COORDINATOR_ENABLE
case kCommandP2pLinkRequest:
mP2p.HandleP2pLinkRequest(rxInfo);
break;
case kCommandP2pLinkAccept:
mP2p.HandleP2pLinkAccept(rxInfo);
break;
#endif
#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE
case kCommandP2pLinkAcceptAndRequest:
mP2p.HandleP2pLinkAcceptAndRequest(rxInfo);
break;
#endif
case kCommandP2pLinkTearDown:
mP2p.HandleP2pLinkTearDown(rxInfo);
break;
#endif // OPENTHREAD_CONFIG_P2P_ENABLE
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() || !aMessage.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.
bool isNextKeySeq;
KeyManager::KeySeqUpdateFlags flags = 0;
VerifyOrExit(aRxInfo.mKeySequence > Get<KeyManager>().GetCurrentKeySequence());
isNextKeySeq = (aRxInfo.mKeySequence - Get<KeyManager>().GetCurrentKeySequence() == 1);
switch (aRxInfo.mClass)
{
case RxInfo::kAuthoritativeMessage:
flags = KeyManager::kForceUpdate;
break;
case RxInfo::kPeerMessage:
VerifyOrExit(aRxInfo.IsNeighborStateValid());
if (!isNextKeySeq)
{
LogInfo("Large key seq jump in peer class msg from 0x%04x ", aRxInfo.mNeighbor->GetRloc16());
ReestablishLinkWithNeighbor(*aRxInfo.mNeighbor);
ExitNow();
}
flags = KeyManager::kApplySwitchGuard;
break;
case RxInfo::kUnknown:
ExitNow();
}
if (isNextKeySeq)
{
flags |= KeyManager::kResetGuardTimer;
}
Get<KeyManager>().SetCurrentKeySequence(aRxInfo.mKeySequence, flags);
exit:
return;
}
#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
void Mle::CheckTrelPeerAddrOnSecureMleRx(const Message &aMessage)
{
OT_UNUSED_VARIABLE(aMessage);
#if OPENTHREAD_CONFIG_MULTI_RADIO
if (aMessage.IsRadioTypeSet() && aMessage.GetRadioType() == Mac::kRadioTypeTrel)
#endif
{
Get<Trel::Link>().CheckPeerAddrOnRxSuccess(Trel::Link::kAllowPeerSockAddrUpdate);
}
}
#endif
void Mle::ReestablishLinkWithNeighbor(Neighbor &aNeighbor)
{
VerifyOrExit(IsAttached() && aNeighbor.IsStateValid());
if (IsChild() && (&aNeighbor == &mParent))
{
IgnoreError(SendChildUpdateRequestToParent(kAppendChallengeTlv));
ExitNow();
}
#if OPENTHREAD_FTD
VerifyOrExit(IsFullThreadDevice());
if (IsRouterRloc16(aNeighbor.GetRloc16()))
{
SendLinkRequest(static_cast<Router *>(&aNeighbor));
}
else if (Get<ChildTable>().Contains(aNeighbor))
{
Child &child = static_cast<Child &>(aNeighbor);
child.SetState(Child::kStateChildUpdateRequest);
IgnoreError(SendChildUpdateRequestToChild(child));
}
#endif
exit:
return;
}
void Mle::HandleAdvertisement(RxInfo &aRxInfo)
{
Error error = kErrorNone;
uint16_t sourceAddress;
LeaderData leaderData;
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 = HandleAdvertisementOnFtd(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);
#if OPENTHREAD_FTD
SuccessOrExit(error = ReadAndProcessRouteTlvOnFtdChild(aRxInfo, mParent.GetRouterId()));
#endif
mRetrieveNewNetworkData = true;
}
mParent.SetLastHeard(TimerMilli::GetNow());
}
else // Device is router or leader
{
VerifyOrExit(aRxInfo.IsNeighborStateValid());
}
if (mRetrieveNewNetworkData || IsNetworkDataNewer(leaderData))
{
mDelayedSender.ScheduleDataRequest(aRxInfo.mMessageInfo.GetPeerAddr(),
GenerateRandomDelay(kMleMaxResponseDelay));
}
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
{
OffsetRange offsetRange;
if (Tlv::FindTlvValueOffsetRange(aRxInfo.mMessage, Tlv::kLinkMetricsReport, offsetRange) == kErrorNone)
{
Get<LinkMetrics::Initiator>().HandleReport(aRxInfo.mMessage, offsetRange,
aRxInfo.mMessageInfo.GetPeerAddr());
}
}
#endif
#if OPENTHREAD_FTD
SuccessOrExit(error = ReadAndProcessRouteTlvOnFtdChild(aRxInfo, mParent.GetRouterId()));
#endif
error = HandleLeaderData(aRxInfo);
if (!mRetxTracker.IsWaitingForDataResponse() && !IsRxOnWhenIdle())
{
// Stop fast data poll request by MLE since we received
// the response.
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;
bool saveActiveDataset = false;
bool savePendingDataset = false;
bool dataRequest = false;
SuccessOrExit(error = aRxInfo.mMessage.ReadLeaderDataTlv(leaderData));
if ((leaderData.GetPartitionId() != mLeaderData.GetPartitionId()) ||
(leaderData.GetWeighting() != mLeaderData.GetWeighting()) || (leaderData.GetLeaderRouterId() != GetLeaderId()))
{
if (IsChild())
{
SetLeaderData(leaderData);
mRetrieveNewNetworkData = true;
}
else
{
ExitNow(error = kErrorDrop);
}
}
else if (!mRetrieveNewNetworkData)
{
VerifyOrExit(IsNetworkDataNewer(leaderData));
}
switch (Tlv::Find<ActiveTimestampTlv>(aRxInfo.mMessage, activeTimestamp))
{
case kErrorNone:
#if OPENTHREAD_FTD
if (IsLeader())
{
break;
}
#endif
if (activeTimestamp != Get<MeshCoP::ActiveDatasetManager>().GetTimestamp())
{
// Send an MLE Data Request if the received timestamp
// mismatches the local value and the message does not
// include the dataset.
VerifyOrExit(aRxInfo.mMessage.ContainsTlv(Tlv::kActiveDataset), dataRequest = true);
saveActiveDataset = true;
}
break;
case kErrorNotFound:
break;
default:
ExitNow(error = kErrorParse);
}
switch (Tlv::Find<PendingTimestampTlv>(aRxInfo.mMessage, pendingTimestamp))
{
case kErrorNone:
#if OPENTHREAD_FTD
if (IsLeader())
{
break;
}
#endif
if (pendingTimestamp != Get<MeshCoP::PendingDatasetManager>().GetTimestamp())
{
VerifyOrExit(aRxInfo.mMessage.ContainsTlv(Tlv::kPendingDataset), dataRequest = true);
savePendingDataset = true;
}
break;
case kErrorNotFound:
break;
default:
ExitNow(error = kErrorParse);
}
switch (error = aRxInfo.mMessage.ReadAndSetNetworkDataTlv(leaderData))
{
case kErrorNone:
break;
case kErrorNotFound:
dataRequest = true;
OT_FALL_THROUGH;
default:
ExitNow();
}
#if OPENTHREAD_FTD
if (IsLeader())
{
Get<NetworkData::Leader>().IncrementVersionAndStableVersion();
}
else
#endif
{
// We previously confirmed the message contains an
// Active or a Pending Dataset TLV before setting the
// corresponding `saveDataset` flag.
if (saveActiveDataset)
{
IgnoreError(aRxInfo.mMessage.ReadAndSaveActiveDataset(activeTimestamp));
}
if (savePendingDataset)
{
IgnoreError(aRxInfo.mMessage.ReadAndSavePendingDataset(pendingTimestamp));
}
}
mRetrieveNewNetworkData = false;
exit:
if (dataRequest)
{
uint32_t delay;
if (aRxInfo.mMessageInfo.GetSockAddr().IsMulticast())
{
delay = GenerateRandomDelay(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;
}
mDelayedSender.ScheduleDataRequest(aRxInfo.mMessageInfo.GetPeerAddr(), delay);
}
else if (error == kErrorNone)
{
mRetxTracker.UpdateOnDataResponseRx();
}
return error;
}
void Mle::HandleChildUpdateRequest(RxInfo &aRxInfo)
{
#if OPENTHREAD_FTD
if (IsRouterOrLeader())
{
HandleChildUpdateRequestOnParent(aRxInfo);
}
else
#endif
{
HandleChildUpdateRequestOnChild(aRxInfo);
}
}
void Mle::HandleChildUpdateRequestOnChild(RxInfo &aRxInfo)
{
Error error = kErrorNone;
bool canTrustMessage = aRxInfo.IsNeighborStateValid();
uint8_t linkMarginOut;
uint16_t sourceAddress;
ChildUpdateResponseInfo info;
TlvList requestedTlvList;
if (!IsAttached())
{
// If detached and trying to restore our role as a child, we
// allow processing of a received "Child Update Request"
// and send a response. But since we have not yet established
// trust with any device (including our former parent),
// we will not save any of the content (TLVs) from the
// message (`canTrustMessage` will be `false`).
VerifyOrExit(mPrevRoleRestorer.IsRestoringChildRole());
mPrevRoleRestorer.HandleChildUpdateRequest(aRxInfo);
}
SuccessOrExit(error = Tlv::Find<SourceAddressTlv>(aRxInfo.mMessage, sourceAddress));
Log(kMessageReceive, kTypeChildUpdateRequestAsChild, aRxInfo.mMessageInfo.GetPeerAddr(), sourceAddress);
info.mDestination = aRxInfo.mMessageInfo.GetPeerAddr();
info.mTlvList.Add(Tlv::kSourceAddress);
info.mTlvList.Add(Tlv::kLeaderData);
switch (aRxInfo.mMessage.ReadChallengeTlv(info.mChallenge))
{
case kErrorNone:
info.mTlvList.Add(Tlv::kResponse);
info.mTlvList.Add(Tlv::kMleFrameCounter);
info.mTlvList.Add(Tlv::kLinkFrameCounter);
break;
case kErrorNotFound:
info.mChallenge.Clear();
break;
default:
ExitNow(error = kErrorParse);
}
if (aRxInfo.mNeighbor == &mParent)
{
uint8_t status;
switch (Tlv::Find<StatusTlv>(aRxInfo.mMessage, status))
{
case kErrorNone:
VerifyOrExit(status != kStatusError, IgnoreError(BecomeDetached()));
break;
case kErrorNotFound:
break;
default:
ExitNow(error = kErrorParse);
}
if (mParent.GetRloc16() != sourceAddress)
{
IgnoreError(BecomeDetached());
ExitNow();
}
if (canTrustMessage)
{
SuccessOrExit(error = HandleLeaderData(aRxInfo));
switch (Tlv::Find<LinkMarginTlv>(aRxInfo.mMessage, linkMarginOut))
{
case kErrorNone:
mParent.SetLinkQualityOut(LinkQualityForLinkMargin(linkMarginOut));
break;
case kErrorNotFound:
break;
default:
ExitNow(error = kErrorParse);
}
}
#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
info.mTlvList.Add(Tlv::kCslTimeout);
}
}
#endif
switch (aRxInfo.mMessage.ReadTlvRequestTlv(requestedTlvList))
{
case kErrorNone:
info.mTlvList.AddElementsFrom(requestedTlvList);
break;
case kErrorNotFound:
break;
default:
ExitNow(error = kErrorParse);
}
}
else
{
// This device is not a child of the Child Update Request source.
error = SendChildUpdateRejectResponse(info);
ExitNow();
}
aRxInfo.mClass = RxInfo::kPeerMessage;
ProcessKeySequence(aRxInfo);
#if OPENTHREAD_CONFIG_MULTI_RADIO
if ((aRxInfo.mNeighbor != nullptr) && !info.mChallenge.IsEmpty())
{
aRxInfo.mNeighbor->ClearLastRxFragmentTag();
}
#endif
error = SendChildUpdateResponse(info);
exit:
LogProcessError(kTypeChildUpdateRequestAsChild, error);
}
void Mle::HandleChildUpdateResponse(RxInfo &aRxInfo)
{
#if OPENTHREAD_FTD
if (IsRouterOrLeader())
{
HandleChildUpdateResponseOnParent(aRxInfo);
}
else
#endif
{
HandleChildUpdateResponseOnChild(aRxInfo);
}
}
void Mle::HandleChildUpdateResponseOnChild(RxInfo &aRxInfo)
{
Error error = kErrorNone;
uint8_t status;
DeviceMode mode;
RxChallenge response;
uint32_t linkFrameCounter;
uint32_t mleFrameCounter;
uint16_t sourceAddress;
uint32_t timeout;
uint8_t linkMarginOut;
Log(kMessageReceive, kTypeChildUpdateResponseAsChild, 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(mPrevRoleRestorer.IsRestoringChildRole(), error = kErrorSecurity);
VerifyOrExit(response == mPrevRoleRestorer.GetChallenge(), error = kErrorSecurity);
break;
case kRoleChild:
VerifyOrExit((aRxInfo.mNeighbor == &mParent) && mParent.IsStateValid(), error = kErrorSecurity);
break;
default:
OT_ASSERT(false);
}
if (Tlv::Find<StatusTlv>(aRxInfo.mMessage, status) == kErrorNone)
{
IgnoreError(BecomeDetached());
ExitNow();
}
SuccessOrExit(error = aRxInfo.mMessage.ReadModeTlv(mode));
VerifyOrExit(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:
SuccessOrExit(error = Tlv::Find<SourceAddressTlv>(aRxInfo.mMessage, sourceAddress));
if (!HasMatchingRouterIdWith(sourceAddress))
{
IgnoreError(BecomeDetached());
ExitNow();
}
SuccessOrExit(error = HandleLeaderData(aRxInfo));
switch (Tlv::Find<TimeoutTlv>(aRxInfo.mMessage, timeout))
{
case kErrorNone:
SuccessOrExit(mDetacher.HandleChildUpdateResponse(timeout));
SetTimeout(timeout, kDoNotSendChildUpdateToParent);
break;
case kErrorNotFound:
break;
default:
ExitNow(error = kErrorParse);
}
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
{
Mac::CslAccuracy cslAccuracy;
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);
}
switch (Tlv::Find<LinkMarginTlv>(aRxInfo.mMessage, linkMarginOut))
{
case kErrorNone:
mParent.SetLinkQualityOut(LinkQualityForLinkMargin(linkMarginOut));
break;
case kErrorNotFound:
break;
default:
ExitNow(error = kErrorParse);
}
aRxInfo.mClass = response.IsEmpty() ? RxInfo::kPeerMessage : RxInfo::kAuthoritativeMessage;
exit:
if (error == kErrorNone)
{
mRetxTracker.UpdateOnChildUpdateResponseRx();
}
LogProcessError(kTypeChildUpdateResponseAsChild, 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
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
void Mle::HandleTimeSync(RxInfo &aRxInfo)
{
Log(kMessageReceive, kTypeTimeSync, aRxInfo.mMessageInfo.GetPeerAddr());
VerifyOrExit(aRxInfo.IsNeighborStateValid());
aRxInfo.mClass = RxInfo::kPeerMessage;
Get<TimeSync>().HandleTimeSyncMessage(aRxInfo.mMessage);
exit:
return;
}
#endif
#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
#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
uint16_t Mle::GetParentRloc16(void) const { return (mParent.IsStateValid() ? mParent.GetRloc16() : kInvalidRloc16); }
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() == mMeshLocalPrefix); }
#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()) != nullptr, error = kErrorNoBufs);
SuccessOrExit(error = message->SetLength(0));
messageInfo.SetSockAddr(GetMeshLocalEid());
messageInfo.GetPeerAddr().SetToRoutingLocator(mMeshLocalPrefix, mPreviousParentRloc);
SuccessOrExit(error = Get<Ip6::Ip6>().SendDatagram(*message, messageInfo, Ip6::kProtoNone));
LogNote("Sending message to inform previous parent 0x%04x", mPreviousParentRloc);
exit:
LogWarnOnError(error, "inform previous parent");
FreeMessageOnError(message, error);
}
#endif // OPENTHREAD_CONFIG_MLE_INFORM_PREVIOUS_PARENT_ON_REATTACH
#if OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE
void Mle::ParentSearch::SetEnabled(bool aEnabled)
{
VerifyOrExit(mEnabled != aEnabled);
mEnabled = aEnabled;
StartTimer();
exit:
return;
}
void Mle::ParentSearch::HandleTimer(void)
{
AttachMode attachMode;
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());
#if OPENTHREAD_FTD
if (Get<Mle>().IsFullThreadDevice())
{
SuccessOrExit(SelectBetterParent());
attachMode = kSelectedParent;
}
else
#endif
{
int8_t parentRss;
parentRss = Get<Mle>().GetParent().GetLinkInfo().GetAverageRss();
LogInfo("PeriodicParentSearch: Parent RSS %d", parentRss);
VerifyOrExit(parentRss != Radio::kInvalidRssi);
VerifyOrExit(parentRss < kRssThreshold);
LogInfo("PeriodicParentSearch: Parent RSS less than %d, searching for new parents", kRssThreshold);
mIsInBackoff = true;
attachMode = kBetterParent;
}
Get<Mle>().mCounters.mBetterParentAttachAttempts++;
Get<Mle>().mAttacher.Attach(attachMode);
exit:
StartTimer();
}
#if OPENTHREAD_FTD
Error Mle::ParentSearch::SelectBetterParent(void)
{
Error error = kErrorNone;
mSelectedParent = nullptr;
for (Router &router : Get<RouterTable>())
{
CompareAndUpdateSelectedParent(router);
}
VerifyOrExit(mSelectedParent != nullptr, error = kErrorNotFound);
mSelectedParent->RestartParentReselectTimeout();
LogInfo("PeriodicParentSearch: Selected router 0x%04x as parent with RSS %d", mSelectedParent->GetRloc16(),
mSelectedParent->GetLinkInfo().GetAverageRss());
exit:
return error;
}
void Mle::ParentSearch::CompareAndUpdateSelectedParent(Router &aRouter)
{
int8_t routerRss;
VerifyOrExit(aRouter.IsSelectableAsParent());
VerifyOrExit(aRouter.GetParentReselectTimeout() == 0);
VerifyOrExit(aRouter.GetRloc16() != Get<Mle>().GetParent().GetRloc16());
routerRss = aRouter.GetLinkInfo().GetAverageRss();
VerifyOrExit(routerRss != Radio::kInvalidRssi);
if (mSelectedParent == nullptr)
{
VerifyOrExit(routerRss >= Get<Mle>().GetParent().GetLinkInfo().GetAverageRss() + kRssMarginOverParent);
}
else
{
VerifyOrExit(routerRss > mSelectedParent->GetLinkInfo().GetAverageRss());
}
mSelectedParent = &aRouter;
exit:
return;
}
#endif // OPENTHREAD_FTD
void Mle::ParentSearch::StartTimer(void)
{
uint32_t interval;
if (!mEnabled)
{
mTimer.Stop();
ExitNow();
}
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");
exit:
return;
}
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 != kInvalidRloc16) &&
(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, kInvalidRloc16);
}
void Mle::Log(MessageAction aAction, MessageType aType, const Ip6::Address &aAddress, uint16_t aRloc)
{
static constexpr uint16_t kRlocStringSize = 17;
String<kRlocStringSize> rlocString;
if (aRloc != kInvalidRloc16)
{
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))
{
LogInfoOnError(aError, "%s %s%s", "process", MessageTypeToString(aType),
MessageTypeActionToSuffixString(aType, aAction));
}
else
{
LogWarnOnError(aError, "%s %s%s", aAction == kMessageSend ? "send" : "process", MessageTypeToString(aType),
MessageTypeActionToSuffixString(aType, aAction));
}
}
}
const char *Mle::MessageActionToString(MessageAction aAction)
{
#define MessageActionMapList(_) \
_(kMessageSend, "Send") \
_(kMessageReceive, "Receive") \
_(kMessageDelay, "Delay") \
_(kMessageRemoveDelayed, "Remove Delayed")
DefineEnumStringArray(MessageActionMapList);
return kStrings[aAction];
}
const char *Mle::MessageTypeToString(MessageType aType)
{
#define MessageTypeMapList(_) \
_(kTypeAdvertisement, "Advertisement") \
_(kTypeAnnounce, "Announce") \
_(kTypeChildIdRequest, "Child ID Request") \
_(kTypeChildIdRequestShort, "Child ID Request") \
_(kTypeChildIdResponse, "Child ID Response") \
_(kTypeChildUpdateRequestAsChild, "Child Update Request") \
_(kTypeChildUpdateResponseAsChild, "Child Update Response") \
_(kTypeDataRequest, "Data Request") \
_(kTypeDataResponse, "Data Response") \
_(kTypeDiscoveryRequest, "Discovery Request") \
_(kTypeDiscoveryResponse, "Discovery Response") \
_(kTypeGenericDelayed, "delayed message") \
_(kTypeGenericUdp, "UDP") \
_(kTypeParentRequestToRouters, "Parent Request") \
_(kTypeParentRequestToRoutersReeds, "Parent Request") \
_(kTypeParentResponse, "Parent Response") \
FtdMessageTypeMapList(_) LinkMetricsMessageTypeMapList(_) TimeSyncMessageTypeMapList(_) P2pMessageTypeMapList(_)
#if OPENTHREAD_FTD
#define FtdMessageTypeMapList(_) \
_(kTypeAddressRelease, "Address Release") \
_(kTypeAddressReleaseReply, "Address Release Reply") \
_(kTypeAddressReply, "Address Reply") \
_(kTypeAddressSolicit, "Address Solicit") \
_(kTypeChildUpdateRequestOfChild, "Child Update Request") \
_(kTypeChildUpdateResponseOfChild, "Child Update Response") \
_(kTypeChildUpdateResponseOfUnknownChild, "Child Update Response") \
_(kTypeLinkAccept, "Link Accept") \
_(kTypeLinkAcceptAndRequest, "Link Accept and Request") \
_(kTypeLinkReject, "Link Reject") \
_(kTypeLinkRequest, "Link Request") \
_(kTypeParentRequest, "Parent Request")
#else
#define FtdMessageTypeMapList(_)
#endif
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE || OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
#define LinkMetricsMessageTypeMapList(_) \
_(kTypeLinkMetricsManagementRequest, "Link Metrics Management Request") \
_(kTypeLinkMetricsManagementResponse, "Link Metrics Management Response") \
_(kTypeLinkProbe, "Link Probe")
#else
#define LinkMetricsMessageTypeMapList(_)
#endif
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
#define TimeSyncMessageTypeMapList(_) _(kTypeTimeSync, "Time Sync")
#else
#define TimeSyncMessageTypeMapList(_)
#endif
#if OPENTHREAD_CONFIG_P2P_ENABLE
#define P2pMessageTypeMapList(_) \
_(kTypeP2pLinkRequest, "P2P Link Request") \
_(kTypeP2pLinkAcceptAndRequest, "P2P Link Accept and Request") \
_(kTypeP2pLinkAccept, "P2P Link Accept") \
_(kTypeP2pLinkTearDown, "P2P Link Tear Down")
#else
#define P2pMessageTypeMapList(_)
#endif
DefineEnumStringArray(MessageTypeMapList);
return kStrings[aType];
}
const char *Mle::MessageTypeActionToSuffixString(MessageType aType, MessageAction aAction)
{
const char *str = "";
OT_UNUSED_VARIABLE(aAction); // Not currently used in non-FTD builds
switch (aType)
{
case kTypeChildIdRequestShort:
str = " - short";
break;
case kTypeChildUpdateRequestAsChild:
case kTypeChildUpdateResponseAsChild:
str = " as child";
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)
#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);
VerifyOrExit(message != nullptr, error = kErrorNoBufs);
SuccessOrExit(error = Tlv::AppendTlv(*message, Tlv::kLinkMetricsManagement, &aSubTlv,
static_cast<uint16_t>(aSubTlv.GetSize())));
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
#if OPENTHREAD_CONFIG_WAKEUP_COORDINATOR_ENABLE
void Mle::HandleWedAttachTimer(void)
{
switch (mWedAttachState)
{
case kWedAttaching:
// Connection timeout
if (!IsRxOnWhenIdle())
{
Get<MeshForwarder>().SetRxOnWhenIdle(false);
}
LogInfo("Connection window closed");
mWedAttachState = kWedDetached;
mWakeupCallback.InvokeAndClearIfSet(kErrorFailed);
break;
default:
break;
}
}
Error Mle::Wakeup(const Mac::ExtAddress &aWedAddress,
uint16_t aIntervalUs,
uint16_t aDurationMs,
WakeupCallback aCallback,
void *aCallbackContext)
{
Error error = kErrorNone;
Mac::WakeupRequest wakeupRequest;
VerifyOrExit((aIntervalUs > 0) && (aDurationMs > 0), error = kErrorInvalidArgs);
VerifyOrExit(aIntervalUs < aDurationMs * Time::kOneMsecInUsec, error = kErrorInvalidArgs);
VerifyOrExit(mWedAttachState == kWedDetached, error = kErrorInvalidState);
wakeupRequest.SetExtAddress(aWedAddress);
SuccessOrExit(error = mWakeupTxScheduler.WakeUp(wakeupRequest, aIntervalUs, aDurationMs));
mWedAttachState = kWedAttaching;
mWakeupCallback.Set(aCallback, aCallbackContext);
Get<MeshForwarder>().SetRxOnWhenIdle(true);
mWedAttachTimer.FireAt(mWakeupTxScheduler.GetTxEndTime() + mWakeupTxScheduler.GetConnectionWindowUs());
LogInfo("Connection window open");
exit:
return error;
}
#endif // OPENTHREAD_CONFIG_WAKEUP_COORDINATOR_ENABLE
#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE
void Mle::HandleWakeupFrame(const Mac::WakeupInfo &aWakeupInfo)
{
OT_UNUSED_VARIABLE(aWakeupInfo);
#if OPENTHREAD_CONFIG_P2P_ENABLE
mP2p.HandleP2pWakeup(aWakeupInfo);
#endif
}
#endif
//---------------------------------------------------------------------------------------------------------------------
// 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);
}
}
//---------------------------------------------------------------------------------------------------------------------
// DelayedSender
Mle::DelayedSender::DelayedSender(Instance &aInstance)
: InstanceLocator(aInstance)
, mTimer(aInstance)
{
}
void Mle::DelayedSender::Stop(void)
{
mTimer.Stop();
mSchedules.DequeueAndFreeAll();
}
void Mle::DelayedSender::ScheduleDataRequest(const Ip6::Address &aDestination, uint32_t aDelay)
{
VerifyOrExit(!HasMatchingSchedule(kTypeDataRequest, aDestination));
AddSchedule(kTypeDataRequest, aDestination, aDelay, nullptr, 0);
exit:
return;
}
void Mle::DelayedSender::ScheduleChildUpdateRequestToParent(uint32_t aDelay)
{
Ip6::Address destination;
destination.SetToLinkLocalAddress(Get<Mle>().mParent.GetExtAddress());
VerifyOrExit(!HasMatchingSchedule(kTypeChildUpdateRequestAsChild, destination));
AddSchedule(kTypeChildUpdateRequestAsChild, destination, aDelay, nullptr, 0);
exit:
return;
}
void Mle::DelayedSender::RemoveScheduledChildUpdateRequestToParent(void)
{
Ip6::Address destination;
destination.SetToLinkLocalAddress(Get<Mle>().mParent.GetExtAddress());
RemoveMatchingSchedules(kTypeChildUpdateRequestAsChild, destination);
}
#if OPENTHREAD_FTD
void Mle::DelayedSender::ScheduleParentResponse(const ParentResponseInfo &aInfo, uint32_t aDelay)
{
Ip6::Address destination;
destination.SetToLinkLocalAddress(aInfo.mChildExtAddress);
RemoveMatchingSchedules(kTypeParentResponse, destination);
AddSchedule(kTypeParentResponse, destination, aDelay, &aInfo, sizeof(aInfo));
}
void Mle::DelayedSender::RemoveScheduledParentResponses(void)
{
Ip6::Address destination;
// The unspecified address will clear all parent responses to any destination
destination.Clear();
RemoveMatchingSchedules(kTypeParentResponse, destination);
}
void Mle::DelayedSender::ScheduleAdvertisement(const Ip6::Address &aDestination, uint32_t aDelay)
{
VerifyOrExit(!HasMatchingSchedule(kTypeAdvertisement, aDestination));
AddSchedule(kTypeAdvertisement, aDestination, aDelay, nullptr, 0);
exit:
return;
}
void Mle::DelayedSender::ScheduleMulticastDataResponse(uint32_t aDelay)
{
Get<MeshForwarder>().RemoveDataResponseMessages();
RemoveMatchingSchedules(kTypeDataResponse, Ip6::Address::GetLinkLocalAllNodesMulticast());
AddSchedule(kTypeDataResponse, Ip6::Address::GetLinkLocalAllNodesMulticast(), aDelay, nullptr, 0);
}
void Mle::DelayedSender::ScheduleLinkRequest(const Router &aRouter, uint32_t aDelay)
{
Ip6::Address destination;
uint16_t routerRloc16;
destination.SetToLinkLocalAddress(aRouter.GetExtAddress());
VerifyOrExit(!HasMatchingSchedule(kTypeLinkRequest, destination));
routerRloc16 = aRouter.GetRloc16();
AddSchedule(kTypeLinkRequest, destination, aDelay, &routerRloc16, sizeof(uint16_t));
exit:
return;
}
void Mle::DelayedSender::RemoveScheduledLinkRequest(const Router &aRouter)
{
Ip6::Address destination;
destination.SetToLinkLocalAddress(aRouter.GetExtAddress());
RemoveMatchingSchedules(kTypeLinkRequest, destination);
}
bool Mle::DelayedSender::HasAnyScheduledLinkRequest(const Router &aRouter) const
{
Ip6::Address destination;
destination.SetToLinkLocalAddress(aRouter.GetExtAddress());
return HasMatchingSchedule(kTypeLinkRequest, destination);
}
void Mle::DelayedSender::ScheduleLinkAccept(const LinkAcceptInfo &aInfo, uint32_t aDelay)
{
Ip6::Address destination;
destination.SetToLinkLocalAddress(aInfo.mExtAddress);
RemoveMatchingSchedules(kTypeLinkAccept, destination);
AddSchedule(kTypeLinkAccept, destination, aDelay, &aInfo, sizeof(aInfo));
}
void Mle::DelayedSender::ScheduleDiscoveryResponse(const Ip6::Address &aDestination,
const DiscoveryResponseInfo &aInfo,
uint32_t aDelay)
{
AddSchedule(kTypeDiscoveryResponse, aDestination, aDelay, &aInfo, sizeof(aInfo));
}
#endif // OPENTHREAD_FTD
void Mle::DelayedSender::AddSchedule(MessageType aMessageType,
const Ip6::Address &aDestination,
uint32_t aDelay,
const void *aInfo,
uint16_t aInfoSize)
{
Schedule *schedule = Get<MessagePool>().Allocate(Message::kTypeOther);
Header header;
VerifyOrExit(schedule != nullptr);
header.mSendTime = TimerMilli::GetNow() + aDelay;
header.mDestination = aDestination;
header.mMessageType = aMessageType;
SuccessOrExit(schedule->Append(header));
if (aInfo != nullptr)
{
SuccessOrExit(schedule->AppendBytes(aInfo, aInfoSize));
}
mTimer.FireAtIfEarlier(header.mSendTime);
mSchedules.Enqueue(*schedule);
schedule = nullptr;
Log(kMessageDelay, aMessageType, aDestination);
exit:
FreeMessage(schedule);
}
void Mle::DelayedSender::HandleTimer(void)
{
NextFireTime nextSendTime;
MessageQueue schedulesToExecute;
for (Schedule &schedule : mSchedules)
{
Header header;
header.ReadFrom(schedule);
if (nextSendTime.GetNow() < header.mSendTime)
{
nextSendTime.UpdateIfEarlier(header.mSendTime);
}
else
{
mSchedules.Dequeue(schedule);
schedulesToExecute.Enqueue(schedule);
}
}
mTimer.FireAt(nextSendTime);
for (Schedule &schedule : schedulesToExecute)
{
Execute(schedule);
}
schedulesToExecute.DequeueAndFreeAll();
}
void Mle::DelayedSender::Execute(const Schedule &aSchedule)
{
Header header;
header.ReadFrom(aSchedule);
switch (header.mMessageType)
{
case kTypeDataRequest:
IgnoreError(Get<Mle>().SendDataRequest(header.mDestination));
break;
case kTypeChildUpdateRequestAsChild:
IgnoreError(Get<Mle>().SendChildUpdateRequestToParent());
break;
#if OPENTHREAD_FTD
case kTypeParentResponse:
{
ParentResponseInfo info;
IgnoreError(aSchedule.Read(sizeof(Header), info));
Get<Mle>().SendParentResponse(info);
break;
}
case kTypeAdvertisement:
Get<Mle>().SendAdvertisement(header.mDestination);
break;
case kTypeDataResponse:
Get<Mle>().SendMulticastDataResponse();
break;
case kTypeLinkAccept:
{
LinkAcceptInfo info;
IgnoreError(aSchedule.Read(sizeof(Header), info));
IgnoreError(Get<Mle>().SendLinkAccept(info));
break;
}
case kTypeLinkRequest:
{
uint16_t rloc16;
Router *router;
IgnoreError(aSchedule.Read(sizeof(Header), rloc16));
router = Get<RouterTable>().FindRouterByRloc16(rloc16);
if (router != nullptr)
{
Get<Mle>().SendLinkRequest(router);
}
break;
}
case kTypeDiscoveryResponse:
{
DiscoveryResponseInfo info;
IgnoreError(aSchedule.Read(sizeof(Header), info));
IgnoreError(Get<Mle>().SendDiscoveryResponse(header.mDestination, info));
break;
}
#endif // OPENTHREAD_FTD
default:
break;
}
}
bool Mle::DelayedSender::Match(const Schedule &aSchedule, MessageType aMessageType, const Ip6::Address &aDestination)
{
// If `aDestination` is `::` (the unspecified address), the
// address check is skipped, effectively accepting any
// destination address.
bool matches = false;
Header header;
header.ReadFrom(aSchedule);
VerifyOrExit(header.mMessageType == aMessageType);
if (!aDestination.IsUnspecified())
{
VerifyOrExit(header.mDestination == aDestination);
}
matches = true;
exit:
return matches;
}
bool Mle::DelayedSender::HasMatchingSchedule(MessageType aMessageType, const Ip6::Address &aDestination) const
{
bool hasMatching = false;
for (const Schedule &schedule : mSchedules)
{
if (Match(schedule, aMessageType, aDestination))
{
hasMatching = true;
break;
}
}
return hasMatching;
}
void Mle::DelayedSender::RemoveMatchingSchedules(MessageType aMessageType, const Ip6::Address &aDestination)
{
for (Schedule &schedule : mSchedules)
{
if (Match(schedule, aMessageType, aDestination))
{
LogRemove(schedule);
mSchedules.DequeueAndFree(schedule);
}
}
}
#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
void Mle::DelayedSender::LogRemove(const Schedule &aSchedule)
{
Header header;
header.ReadFrom(aSchedule);
Log(kMessageRemoveDelayed, header.mMessageType, header.mDestination);
}
#else
void Mle::DelayedSender::LogRemove(const Schedule &) {}
#endif
//---------------------------------------------------------------------------------------------------------------------
// TxMessage
Mle::TxMessage *Mle::NewMleMessage(Command aCommand)
{
Error error = kErrorNone;
TxMessage *message;
Message::Settings settings(kNoLinkSecurity, Message::kPriorityNet);
uint8_t securitySuite;
message = static_cast<TxMessage *>(mSocket.NewMessage(settings));
VerifyOrExit(message != nullptr, error = kErrorNoBufs);
securitySuite = k154Security;
if ((aCommand == kCommandDiscoveryRequest) || (aCommand == kCommandDiscoveryResponse))
{
securitySuite = kNoSecurity;
}
message->SetSubType(Message::kSubTypeMle);
message->SetMleCommand(aCommand);
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(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::AppendLinkAndMleFrameCounterTlvs(void)
{
Error error;
SuccessOrExit(error = AppendLinkFrameCounterTlv());
error = AppendMleFrameCounterTlv();
exit:
return error;
}
Error Mle::TxMessage::AppendAddress16Tlv(uint16_t aRloc16) { return Tlv::Append<Address16Tlv>(*this, aRloc16); }
Error Mle::TxMessage::AppendLeaderDataTlv(void)
{
Get<Mle>().mLeaderData.SetDataVersion(Get<NetworkData::Leader>().GetVersion(NetworkData::kFullSet));
Get<Mle>().mLeaderData.SetStableDataVersion(Get<NetworkData::Leader>().GetVersion(NetworkData::kStableSubset));
return Tlv::Append<LeaderDataTlv>(*this, LeaderDataTlvValue(Get<Mle>().mLeaderData));
}
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::Bookmark tlvBookmark;
uint8_t counter = 0;
SuccessOrExit(error = Tlv::StartTlv(*this, Tlv::kAddressRegistration, tlvBookmark));
// Prioritize ML-EID
SuccessOrExit(error = AppendAddressRegistrationEntry(Get<Mle>().GetMeshLocalEid()));
// 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()))
{
// Prioritize DUA, compressed entry
SuccessOrExit(error = AppendAddressRegistrationEntry(Get<DuaManager>().GetDomainUnicastAddress()));
counter++;
}
#endif
for (const Ip6::Netif::UnicastAddress &addr : Get<ThreadNetif>().GetUnicastAddresses())
{
if (addr.GetAddress().IsLoopback() || addr.GetAddress().IsLinkLocalUnicast() ||
Get<Mle>().IsRoutingLocator(addr.GetAddress()) || Get<Mle>().IsAnycastLocator(addr.GetAddress()) ||
addr.GetAddress() == Get<Mle>().GetMeshLocalEid())
{
continue;
}
#if OPENTHREAD_CONFIG_DUA_ENABLE
if (addr.GetAddress() == Get<DuaManager>().GetDomainUnicastAddress())
{
continue;
}
#endif
SuccessOrExit(error = AppendAddressRegistrationEntry(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>().ShouldRegisterMulticastAddrsWithParent())
{
for (const Ip6::Netif::MulticastAddress &addr : Get<ThreadNetif>().GetMulticastAddresses())
{
if (addr.GetOrigin() != Ip6::Netif::kOriginManual)
{
continue;
}
#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 = AppendAddressRegistrationEntry(addr.GetAddress()));
counter++;
// only continue to append if there is available entry.
VerifyOrExit(counter < kMaxIpAddressesToRegister);
}
}
exit:
if (error == kErrorNone)
{
error = Tlv::EndTlv(*this, tlvBookmark);
}
return error;
}
Error Mle::TxMessage::AppendAddressRegistrationEntry(const Ip6::Address &aAddress)
{
uint8_t ctlByte = AddressRegistrationTlv::kControlByteUncompressed;
Error error;
if (!aAddress.IsMulticast())
{
Lowpan::Context context;
Get<NetworkData::Leader>().FindContextForAddress(aAddress, context);
if (context.IsValid() && context.GetCompressFlag())
{
ctlByte = AddressRegistrationTlv::ControlByteFor(context.GetContextId());
}
}
SuccessOrExit(error = Append(ctlByte));
if (ctlByte == AddressRegistrationTlv::kControlByteUncompressed)
{
error = Append(aAddress);
}
else
{
error = Append(aAddress.GetIid());
}
exit:
return error;
}
Error Mle::TxMessage::AppendSupervisionIntervalTlvIfSleepyChild(void)
{
Error error = kErrorNone;
VerifyOrExit(!Get<Mle>().IsRxOnWhenIdle());
error = AppendSupervisionIntervalTlv(Get<SupervisionListener>().GetInterval());
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.IsValid());
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.IsValid());
error = Tlv::Append<PendingTimestampTlv>(*this, timestamp);
exit:
return error;
}
Error Mle::TxMessage::AppendActiveAndPendingTimestampTlvs(void)
{
Error error;
SuccessOrExit(error = AppendActiveTimestampTlv());
error = AppendPendingTimestampTlv();
exit:
return error;
}
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
Error Mle::TxMessage::AppendCslChannelTlv(void)
{
// CSL channel value of zero indicates that the CSL channel is not
// specified. We can use this value in the TLV as well.
return Tlv::Append<CslChannelTlv>(*this, ChannelTlvValue(Get<Mac::Mac>().GetCslChannel()));
}
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>().mLinkLocalAddress.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;
}
#if OPENTHREAD_FTD
Error Mle::TxMessage::AppendConnectivityTlv(void)
{
ConnectivityTlvValue tlvValue;
Get<Mle>().FillConnectivityTlvValue(tlvValue);
return Tlv::Append<ConnectivityTlv>(*this, &tlvValue, sizeof(tlvValue));
}
Error Mle::TxMessage::AppendAddressRegistrationTlv(Child &aChild)
{
Error error;
Tlv::Bookmark tlvBookmark;
SuccessOrExit(error = Tlv::StartTlv(*this, Tlv::kAddressRegistration, tlvBookmark));
// The parent must echo back all registered IPv6 addresses except
// for the ML-EID, which is excluded by `Child::GetIp6Addresses()`.
for (const Ip6::Address &address : aChild.GetIp6Addresses())
{
SuccessOrExit(error = AppendAddressRegistrationEntry(address));
}
error = Tlv::EndTlv(*this, tlvBookmark);
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 AppendDatasetTlv(MeshCoP::Dataset::kActive); }
Error Mle::TxMessage::AppendPendingDatasetTlv(void) { return AppendDatasetTlv(MeshCoP::Dataset::kPending); }
Error Mle::TxMessage::AppendDatasetTlv(MeshCoP::Dataset::Type aDatasetType)
{
Error error = kErrorNotFound;
Tlv::Type tlvType;
MeshCoP::Dataset dataset;
switch (aDatasetType)
{
case MeshCoP::Dataset::kActive:
error = Get<MeshCoP::ActiveDatasetManager>().Read(dataset);
tlvType = Tlv::kActiveDataset;
break;
case MeshCoP::Dataset::kPending:
error = Get<MeshCoP::PendingDatasetManager>().Read(dataset);
tlvType = Tlv::kPendingDataset;
break;
default:
OT_ASSERT(false);
}
if (error != kErrorNone)
{
// If there's no dataset, no need to append TLV. We'll treat it
// as success.
ExitNow(error = kErrorNone);
}
// Remove the Timestamp TLV from Dataset before appending to the
// message. The Timestamp is appended as its own MLE TLV to the
// message.
dataset.RemoveTimestamp(aDatasetType);
error = Tlv::AppendTlv(*this, tlvType, dataset.GetBytes(), dataset.GetLength());
exit:
return error;
}
Error Mle::TxMessage::AppendSteeringDataTlv(void)
{
Error error = kErrorNone;
MeshCoP::SteeringData steeringData;
#if OPENTHREAD_CONFIG_MLE_STEERING_DATA_SET_OOB_ENABLE
if (!Get<Mle>().mSteeringData.IsEmpty())
{
steeringData = Get<Mle>().mSteeringData;
}
else
#endif
{
SuccessOrExit(Get<NetworkData::Leader>().FindSteeringData(steeringData));
}
error = Tlv::Append<MeshCoP::SteeringDataTlv>(*this, steeringData.GetData(), steeringData.GetLength());
exit:
return error;
}
#endif // OPENTHREAD_FTD
//---------------------------------------------------------------------------------------------------------------------
// RxMessage
bool Mle::RxMessage::ContainsTlv(Tlv::Type aTlvType) const
{
OffsetRange offsetRange;
return Tlv::FindTlvValueOffsetRange(*this, aTlvType, offsetRange) == kErrorNone;
}
Error Mle::RxMessage::ReadModeTlv(DeviceMode &aMode) const
{
Error error;
uint8_t modeBitmask;
SuccessOrExit(error = Tlv::Find<ModeTlv>(*this, modeBitmask));
aMode.Set(modeBitmask);
exit:
return error;
}
Error Mle::RxMessage::ReadVersionTlv(uint16_t &aVersion) const
{
Error error;
SuccessOrExit(error = Tlv::Find<VersionTlv>(*this, aVersion));
VerifyOrExit(aVersion >= kThreadVersion1p1, error = kErrorParse);
exit:
return error;
}
Error Mle::RxMessage::ReadChallengeOrResponse(uint8_t aTlvType, RxChallenge &aRxChallenge) const
{
Error error;
OffsetRange offsetRange;
SuccessOrExit(error = Tlv::FindTlvValueOffsetRange(*this, aTlvType, offsetRange));
error = aRxChallenge.ReadFrom(*this, offsetRange);
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::ReadAndMatchResponseTlvWith(const TxChallenge &aChallenge) const
{
Error error;
RxChallenge response;
SuccessOrExit(error = ReadResponseTlv(response));
VerifyOrExit(response == aChallenge, error = kErrorSecurity);
exit:
return error;
}
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;
LeaderDataTlvValue tlvValue;
SuccessOrExit(error = Tlv::Find<LeaderDataTlv>(*this, tlvValue));
tlvValue.Get(aLeaderData);
exit:
return error;
}
Error Mle::RxMessage::ReadConnectivityTlv(Connectivity &aConnectivity) const
{
Error error;
ConnectivityTlvValue tlvValue;
OffsetRange offsetRange;
SuccessOrExit(error = Tlv::FindTlvValueOffsetRange(*this, ConnectivityTlv::kType, offsetRange));
SuccessOrExit(error = tlvValue.ParseFrom(*this, offsetRange));
tlvValue.GetConnectivity(aConnectivity);
exit:
return error;
}
Error Mle::RxMessage::ReadAndSetNetworkDataTlv(const LeaderData &aLeaderData) const
{
Error error;
OffsetRange offsetRange;
SuccessOrExit(error = Tlv::FindTlvValueOffsetRange(*this, Tlv::kNetworkData, offsetRange));
error = Get<NetworkData::Leader>().SetNetworkData(aLeaderData.GetDataVersion(NetworkData::kFullSet),
aLeaderData.GetDataVersion(NetworkData::kStableSubset),
Get<Mle>().GetNetworkDataType(), *this, offsetRange);
exit:
return error;
}
Error Mle::RxMessage::ReadAndSaveActiveDataset(const MeshCoP::Timestamp &aActiveTimestamp) const
{
return ReadAndSaveDataset(MeshCoP::Dataset::kActive, aActiveTimestamp);
}
Error Mle::RxMessage::ReadAndSavePendingDataset(const MeshCoP::Timestamp &aPendingTimestamp) const
{
return ReadAndSaveDataset(MeshCoP::Dataset::kPending, aPendingTimestamp);
}
Error Mle::RxMessage::ReadAndSaveDataset(MeshCoP::Dataset::Type aDatasetType,
const MeshCoP::Timestamp &aTimestamp) const
{
Error error = kErrorNone;
Tlv::Type tlvType = (aDatasetType == MeshCoP::Dataset::kActive) ? Tlv::kActiveDataset : Tlv::kPendingDataset;
MeshCoP::Dataset dataset;
OffsetRange offsetRange;
SuccessOrExit(error = Tlv::FindTlvValueOffsetRange(*this, tlvType, offsetRange));
SuccessOrExit(error = dataset.SetFrom(*this, offsetRange));
SuccessOrExit(error = dataset.ValidateTlvs());
SuccessOrExit(error = dataset.WriteTimestamp(aDatasetType, aTimestamp));
switch (aDatasetType)
{
case MeshCoP::Dataset::kActive:
error = Get<MeshCoP::ActiveDatasetManager>().Save(dataset);
break;
case MeshCoP::Dataset::kPending:
error = Get<MeshCoP::PendingDatasetManager>().Save(dataset);
break;
}
exit:
return error;
}
Error Mle::RxMessage::ReadTlvRequestTlv(TlvList &aTlvList) const
{
Error error;
OffsetRange offsetRange;
SuccessOrExit(error = Tlv::FindTlvValueOffsetRange(*this, Tlv::kTlvRequest, offsetRange));
offsetRange.ShrinkLength(aTlvList.GetMaxSize());
ReadBytes(offsetRange, aTlvList.GetArrayBuffer());
aTlvList.SetLength(static_cast<uint8_t>(offsetRange.GetLength()));
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();
ClearAllBytes(*this);
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;
}
//----------------------------------------------------------------------------------------------------------------------
// PrevRoleRestorer
Mle::PrevRoleRestorer::PrevRoleRestorer(Instance &aInstance)
: InstanceLocator(aInstance)
, mState(kIdle)
, mAttempts(0)
, mUseIncreasingTimeout(false)
, mCurTimeout(0)
, mTimer(aInstance)
{
}
Error Mle::PrevRoleRestorer::Start(void)
{
// Starts the process of restoring the device to its previously
// saved role after MLE start.
//
// Returns `kErrorNone` if the restoration process begins
// successfully. An error is returned if:
// - No previous role was saved.
// - The saved role info is inconsistent (e.g., RLOC16 indicates a
// router/leader, but `mLastSavedRole` doesn't match).
Error error = kErrorFailed;
mState = kIdle;
mAttempts = 0;
VerifyOrExit(Get<Mle>().IsDetached());
VerifyOrExit(Get<Mle>().GetRloc16() != kInvalidRloc16);
if (IsRouterRloc16(Get<Mle>().GetRloc16()))
{
#if OPENTHREAD_FTD
VerifyOrExit((Get<Mle>().mLastSavedRole == kRoleRouter) || (Get<Mle>().mLastSavedRole == kRoleLeader));
VerifyOrExit(Get<Mle>().IsRouterEligible());
Get<MeshForwarder>().SetRxOnWhenIdle(true);
SetState(kRestoringRouterOrLeaderRole);
DetermineMaxLinkRequestAttempts();
mTimer.Start(Get<Mle>().GenerateRandomDelay(kMaxStartDelay));
error = kErrorNone;
#endif
ExitNow();
}
VerifyOrExit(Get<Mle>().mLastSavedRole == kRoleChild);
VerifyOrExit(Get<Mle>().mParent.IsStateValidOrRestoring());
// Try to restore the previous child role by sending up to
// `kChildUpdateAttempts` Child Update Requests to the former
// parent.
//
// A non-sleepy child uses an increasing timeout (starting at
// `kChildUpdateStartTimeout` and doubling) to give a potentially
// restarting parent more time to recover. The total wait time is
// 29 seconds (4+8+16+1). A sleepy child uses a fixed short
// timeout of one seconds(`kChildUpdateMinTimeout`).
//
// Receiving an MLE message from the parent triggers a switch to
// the short timeout and guarantees at least two more attempts
// (`kExtraChildUpdatesAfterRxFromParent`).
SetState(kRestoringChildRole);
GenerateRandomChallenge();
mAttempts = kChildUpdateAttempts;
if (!Get<Mle>().IsRxOnWhenIdle())
{
mUseIncreasingTimeout = false;
mCurTimeout = kChildUpdateMinTimeout;
}
else
{
mUseIncreasingTimeout = true;
mCurTimeout = kChildUpdateStartTimeout;
}
mTimer.Start(Get<Mle>().GenerateRandomDelay(kMaxStartDelay));
error = kErrorNone;
exit:
return error;
}
void Mle::PrevRoleRestorer::Stop(void)
{
SetState(kIdle);
mTimer.Stop();
}
void Mle::PrevRoleRestorer::SetState(State aState)
{
mState = aState;
if (mState != kIdle)
{
LogInfo("Attempting to restore prev role: %s", RoleToString(Get<Mle>().mLastSavedRole));
}
}
void Mle::PrevRoleRestorer::HandleTimer(void)
{
VerifyOrExit(mState != kIdle);
if (!Get<Mle>().IsDetached())
{
Stop();
ExitNow();
}
if (mAttempts == 0)
{
LogInfo("Failed to restore prev role");
Stop();
IgnoreError(Get<Mle>().BecomeDetached());
ExitNow();
}
mAttempts--;
#if OPENTHREAD_FTD
if (mState == kRestoringRouterOrLeaderRole)
{
SendMulticastLinkRequest();
}
else
#endif
{
SendChildUpdate();
}
exit:
return;
}
void Mle::PrevRoleRestorer::SendChildUpdate(void)
{
if (mAttempts == 0)
{
mTimer.Start(kChildUpdateMinTimeout);
}
else
{
mTimer.Start(Random::NonCrypto::AddJitter(mCurTimeout, kChildUpdateRetxJitter));
if (mUseIncreasingTimeout)
{
mCurTimeout += mCurTimeout;
}
}
LogDebg("Sending Child Update Request to restore child role, remaining attempts: %u", mAttempts);
IgnoreError(Get<Mle>().SendChildUpdateRequestToParent(kToRestoreChildRole));
}
void Mle::PrevRoleRestorer::CheckIfMessageIsFromParent(RxInfo &aRxInfo)
{
VerifyOrExit(IsRestoringChildRole());
VerifyOrExit(aRxInfo.mNeighbor == &Get<Mle>().GetParent());
VerifyOrExit(mUseIncreasingTimeout);
LogInfo("Received msg from former parent, speeding up child role restoration");
mUseIncreasingTimeout = false;
mCurTimeout = kChildUpdateMinTimeout;
if (mAttempts <= kExtraChildUpdatesAfterRxFromParent)
{
mAttempts = kExtraChildUpdatesAfterRxFromParent;
LogInfo("Allow extra Child Update attempts %u", mAttempts);
}
mTimer.FireAtIfEarlier(TimerMilli::GetNow() + Random::NonCrypto::AddJitter(mCurTimeout, kChildUpdateRetxJitter));
exit:
return;
}
#if OPENTHREAD_FTD
void Mle::PrevRoleRestorer::DetermineMaxLinkRequestAttempts(void)
{
mAttempts = kMaxCriticalTxCount;
if ((Get<Mle>().mLastSavedRole == kRoleRouter) &&
(Get<Mle>().mChildTable.GetNumChildren(Child::kInStateValidOrRestoring) < kMinCriticalChildrenCount))
{
mAttempts = kMaxTxCount;
}
}
void Mle::PrevRoleRestorer::SendMulticastLinkRequest(void)
{
uint32_t delay;
delay = (mAttempts == 0) ? kLinkRequestTimeout
: Random::NonCrypto::GetUint32InRange(kMulticastRetxDelayMin, kMulticastRetxDelayMax);
mTimer.Start(delay);
LogDebg("Sending multicast Link Request to restore role, remaining attempts: %u", mAttempts);
Get<Mle>().SendLinkRequest(nullptr);
}
#endif // OPENTHREAD_FTD
//---------------------------------------------------------------------------------------------------------------------
// Attacher
Mle::Attacher::Attacher(Instance &aInstance)
: InstanceLocator(aInstance)
, mReceivedResponseFromParent(false)
, mState(kStateIdle)
, mMode(kAnyPartition)
, mReattachMode(kReattachModeStop)
, mAddressRegistrationMode(kAppendAllAddresses)
, mParentRequestCounter(0)
, mAnnounceChannel(0)
, mAttachCounter(0)
, mAnnounceDelay(kAnnounceTimeout)
, mTimer(aInstance)
{
mParentCandidate.Init(aInstance);
mParentCandidate.Clear();
}
void Mle::Attacher::Start(StartMode aMode)
{
mAttachCounter = 0;
switch (aMode)
{
case kNormalAttach:
mReattachMode =
(Get<MeshCoP::ActiveDatasetManager>().Restore() == kErrorNone) ? kReattachModeActive : kReattachModeStop;
if (Get<Mle>().mPrevRoleRestorer.Start() == kErrorNone)
{
ExitNow();
}
break;
case kAnnounceAttach:
break;
}
Attach(kAnyPartition);
exit:
return;
}
void Mle::Attacher::CancelAttachOnRoleChange(void)
{
SetState(kStateIdle);
mTimer.Stop();
if (Get<Mle>().IsChild())
{
mTimer.Start(kAttachBackoffDelayToResetCounter);
mReattachMode = kReattachModeStop;
if (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;
Get<Mle>().ScheduleChildUpdateRequestIfMtdChild();
}
}
else if (Get<Mle>().IsRouterOrLeader())
{
mAttachCounter = 0;
}
}
void Mle::Attacher::SetState(State aState)
{
VerifyOrExit(aState != mState);
LogInfo("AttachState %s -> %s", StateToString(mState), StateToString(aState));
mState = aState;
exit:
return;
}
void Mle::Attacher::Attach(AttachMode aMode)
{
VerifyOrExit(!Get<Mle>().IsDisabled());
VerifyOrExit(!IsAttaching());
#if OPENTHREAD_FTD
Get<Mle>().RemoveScheduledParentResponses();
#endif
if (!Get<Mle>().IsDetached())
{
mAttachCounter = 0;
}
mParentCandidate.Clear();
SetState(kStateStart);
mMode = aMode;
if (aMode != kBetterPartition)
{
#if OPENTHREAD_FTD
if (Get<Mle>().IsFullThreadDevice())
{
Get<Mle>().StopAdvertiseTrickleTimer();
}
#endif
}
else
{
Get<Mle>().mCounters.mBetterPartitionAttachAttempts++;
}
mTimer.Start(GetStartDelay());
if (Get<Mle>().IsDetached())
{
mAttachCounter++;
if (mAttachCounter == 0)
{
mAttachCounter--;
}
Get<Mle>().mCounters.mAttachAttempts++;
if (!Get<Mle>().IsRxOnWhenIdle())
{
Get<Mac::Mac>().SetRxOnWhenIdle(false);
}
}
exit:
return;
}
uint32_t Mle::Attacher::GetStartDelay(void) const
{
uint32_t delay = 1;
uint32_t jitter;
VerifyOrExit(Get<Mle>().IsDetached());
if (mAttachCounter == 0)
{
delay = Get<Mle>().GenerateRandomDelay(kParentRequestRouterTimeout);
ExitNow();
}
#if OPENTHREAD_CONFIG_MLE_ATTACH_BACKOFF_ENABLE
else
{
uint16_t counter = mAttachCounter - 1;
const uint32_t ratio = kAttachBackoffMaxInterval / kAttachBackoffMinInterval;
if ((counter < BitSizeOf(ratio)) && ((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;
}
Error Mle::Attacher::DetermineParentRequestType(ParentRequestType &aType) const
{
// This method determines the Parent Request type to use during an
// attach cycle based on `mMode`, `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(mState == kStateParentRequest);
if (mMode == kSelectedParent)
{
aType = kToSelectedRouter;
VerifyOrExit(mParentRequestCounter <= 1, error = kErrorNotFound);
ExitNow();
}
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) && (mMode != kBetterParent))
{
VerifyOrExit(mParentRequestCounter <= kFirstAttachCycleTotalParentRequests, error = kErrorNotFound);
// During reattach to the same partition all the Parent
// Request are sent to Routers and REEDs.
if ((mMode != kSamePartition) && (mParentRequestCounter <= kFirstAttachCycleNumParentRequestToRouters))
{
aType = kToRouters;
}
}
else
{
VerifyOrExit(mParentRequestCounter <= kNextAttachCycleTotalParentRequests, error = kErrorNotFound);
if (mParentRequestCounter <= kNextAttachCycleNumParentRequestToRouters)
{
aType = kToRouters;
}
}
exit:
return error;
}
bool Mle::Attacher::HasAcceptableParentCandidate(void) const
{
bool hasAcceptableParent = false;
ParentRequestType parentReqType;
VerifyOrExit(mParentCandidate.IsStateParentResponse());
switch (mState)
{
case kStateAnnounce:
VerifyOrExit(!HasMoreChannelsToAnnounce());
break;
case kStateParentRequest:
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 (Get<Mle>().IsChild())
{
switch (mMode)
{
case kBetterPartition:
break;
case kAnyPartition:
case kSamePartition:
case kDowngradeToReed:
case kBetterParent:
case kSelectedParent:
// Ensure that a Parent Response was received from the
// current parent to which the device is attached, so
// that the new parent candidate can be compared with the
// current parent and confirmed to be preferred.
VerifyOrExit(mReceivedResponseFromParent);
break;
}
}
hasAcceptableParent = true;
exit:
return hasAcceptableParent;
}
void Mle::Attacher::HandleTimer(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))
{
SetState(kStateChildIdRequest);
delay = kChildIdResponseTimeout;
ExitNow();
}
switch (mState)
{
case kStateIdle:
mAttachCounter = 0;
break;
case kStateStart:
LogNote("Attach attempt %d, %s %s", mAttachCounter, AttachModeToString(mMode),
ReattachModeToString(mReattachMode));
SetState(kStateParentRequest);
mParentCandidate.SetState(Neighbor::kStateInvalid);
mReceivedResponseFromParent = false;
mParentRequestCounter = 0;
Get<MeshForwarder>().SetRxOnWhenIdle(true);
OT_FALL_THROUGH;
case kStateParentRequest:
mParentRequestCounter++;
if (DetermineParentRequestType(type) == kErrorNone)
{
SendParentRequest(type);
switch (type)
{
case kToRouters:
case kToSelectedRouter:
delay = kParentRequestRouterTimeout;
break;
case kToRoutersAndReeds:
delay = kParentRequestReedTimeout;
break;
}
break;
}
shouldAnnounce = PrepareAnnounceState();
if (shouldAnnounce)
{
// We send an extra "Parent Request" as we switch to
// `kStateAnnounce` 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
// `kStateAnnounce` for multiple iterations, each
// time sending an Announce on a different channel
// (with `mAnnounceDelay` wait between them).
SetState(kStateAnnounce);
SendParentRequest(kToRoutersAndReeds);
mAnnounceChannel = Mac::ChannelMask::kChannelIteratorFirst;
delay = mAnnounceDelay;
break;
}
OT_FALL_THROUGH;
case kStateAnnounce:
if (shouldAnnounce && (GetNextAnnounceChannel(mAnnounceChannel) == kErrorNone))
{
Get<Mle>().SendAnnounce(mAnnounceChannel, kOrphanAnnounce);
delay = mAnnounceDelay;
break;
}
OT_FALL_THROUGH;
case kStateChildIdRequest:
SetState(kStateIdle);
mParentCandidate.Clear();
delay = Reattach();
break;
}
exit:
if (delay != 0)
{
mTimer.Start(delay);
}
}
bool Mle::Attacher::PrepareAnnounceState(void)
{
bool shouldAnnounce = false;
Mac::ChannelMask channelMask;
VerifyOrExit(!Get<Mle>().IsChild() && (mReattachMode == kReattachModeStop) &&
(Get<MeshCoP::ActiveDatasetManager>().IsPartiallyComplete() || !Get<Mle>().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::Attacher::Reattach(void)
{
uint32_t delay = 0;
// First, check `mReattachMode`. If an attach attempt failed
// while using the Active Dataset, start a new attach cycle with
// the Pending Dataset (if available). If attaching with the
// Pending Dataset fails, switch back to the Active Dataset.
switch (mReattachMode)
{
case kReattachModeActive:
if (Get<MeshCoP::PendingDatasetManager>().Restore() == kErrorNone)
{
IgnoreError(Get<MeshCoP::PendingDatasetManager>().ApplyConfiguration());
mReattachMode = kReattachModePending;
SetState(kStateStart);
delay = Get<Mle>().GenerateRandomDelay(kAttachStartJitter);
ExitNow();
}
mReattachMode = kReattachModeStop;
break;
case kReattachModePending:
IgnoreError(Get<MeshCoP::ActiveDatasetManager>().Restore());
mReattachMode = kReattachModeStop;
break;
case kReattachModeStop:
break;
}
switch (mMode)
{
case kAnyPartition:
case kBetterParent:
case kSelectedParent:
if (Get<Mle>().IsChild())
{
// If already attached (e.g., trying to find a better
// parent or partition), and attach fails, we revert to
// sleepy operation if needed and stop the attach process.
if (!Get<Mle>().IsRxOnWhenIdle())
{
Get<DataPollSender>().SetAttachMode(false);
Get<MeshForwarder>().SetRxOnWhenIdle(false);
}
ExitNow();
}
if (Get<Mle>().mAnnounceHandler.IsAnnounceAttaching())
{
Get<Mle>().mAnnounceHandler.HandleAnnounceAttachFailure();
IgnoreError(Get<Mle>().BecomeDetached());
ExitNow();
}
#if OPENTHREAD_FTD
if (Get<Mle>().IsFullThreadDevice() && Get<Mle>().BecomeLeader(kIgnoreLeaderWeight) == kErrorNone)
{
ExitNow();
}
#endif
IgnoreError(Get<Mle>().BecomeDetached());
break;
case kSamePartition:
case kDowngradeToReed:
Attach(kAnyPartition);
break;
case kBetterPartition:
break;
}
exit:
return delay;
}
void Mle::Attacher::SendParentRequest(ParentRequestType aType)
{
Error error = kErrorNone;
TxMessage *message;
uint8_t scanMask = 0;
Ip6::Address destination;
mParentRequestChallenge.GenerateRandom();
switch (aType)
{
case kToRouters:
case kToSelectedRouter:
scanMask = ScanMaskTlv::kRouterFlag;
break;
case kToRoutersAndReeds:
scanMask = ScanMaskTlv::kRouterFlag | ScanMaskTlv::kEndDeviceFlag;
break;
}
VerifyOrExit((message = Get<Mle>().NewMleMessage(kCommandParentRequest)) != nullptr, error = kErrorNoBufs);
SuccessOrExit(error = message->AppendModeTlv(Get<Mle>().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
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE
if (aType == kToSelectedRouter)
{
TxMessage *messageToCurParent;
messageToCurParent = static_cast<TxMessage *>(Get<Mle>().mSocket.CloneMessage(*message));
VerifyOrExit(messageToCurParent != nullptr, error = kErrorNoBufs);
destination.SetToLinkLocalAddress(Get<Mle>().mParent.GetExtAddress());
error = messageToCurParent->SendTo(destination);
if (error != kErrorNone)
{
messageToCurParent->Free();
ExitNow();
}
Log(kMessageSend, kTypeParentRequestToRouters, destination);
destination.SetToLinkLocalAddress(Get<Mle>().mParentSearch.GetSelectedParent().GetExtAddress());
}
else
#endif
{
destination = Ip6::Address::GetLinkLocalAllRoutersMulticast();
}
SuccessOrExit(error = message->SendTo(destination));
switch (aType)
{
case kToRouters:
case kToSelectedRouter:
Log(kMessageSend, kTypeParentRequestToRouters, destination);
break;
case kToRoutersAndReeds:
Log(kMessageSend, kTypeParentRequestToRoutersReeds, destination);
break;
}
exit:
FreeMessageOnError(message, error);
}
void Mle::Attacher::HandleChildIdRequestTxDone(const otMessage *aMessage, otError aError, void *aContext)
{
OT_UNUSED_VARIABLE(aError);
static_cast<Attacher *>(aContext)->HandleChildIdRequestTxDone(AsCoreType(aMessage));
}
void Mle::Attacher::HandleChildIdRequestTxDone(const Message &aMessage)
{
if (aMessage.GetTxSuccess() && !Get<Mle>().IsRxOnWhenIdle())
{
Get<DataPollSender>().SetAttachMode(true);
Get<MeshForwarder>().SetRxOnWhenIdle(false);
}
if (aMessage.IsLinkSecurityEnabled() && (mState == kStateChildIdRequest))
{
// 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`");
mAddressRegistrationMode = kAppendMeshLocalOnly;
IgnoreError(SendChildIdRequest());
}
}
Error Mle::Attacher::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 (Get<Mle>().mParent.GetExtAddress() == mParentCandidate.GetExtAddress())
{
if (Get<Mle>().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.
Get<Mle>().mParent.SetState(Neighbor::kStateInvalid);
}
}
VerifyOrExit((message = Get<Mle>().NewMleMessage(kCommandChildIdRequest)) != nullptr, error = kErrorNoBufs);
SuccessOrExit(error = message->AppendResponseTlv(mParentCandidate.mRxChallenge));
SuccessOrExit(error = message->AppendLinkAndMleFrameCounterTlvs());
SuccessOrExit(error = message->AppendModeTlv(Get<Mle>().mDeviceMode));
SuccessOrExit(error = message->AppendTimeoutTlv(Get<Mle>().mTimeout));
SuccessOrExit(error = message->AppendVersionTlv());
SuccessOrExit(error = message->AppendSupervisionIntervalTlvIfSleepyChild());
if (!Get<Mle>().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->AppendActiveAndPendingTimestampTlvs());
mParentCandidate.SetState(Neighbor::kStateValid);
message->RegisterTxCallback(HandleChildIdRequestTxDone, this);
destination.SetToLinkLocalAddress(mParentCandidate.GetExtAddress());
SuccessOrExit(error = message->SendTo(destination));
Log(kMessageSend,
(mAddressRegistrationMode == kAppendMeshLocalOnly) ? kTypeChildIdRequestShort : kTypeChildIdRequest,
destination);
exit:
FreeMessageOnError(message, error);
return error;
}
Error Mle::Attacher::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::Attacher::HasMoreChannelsToAnnounce(void) const
{
uint8_t channel = mAnnounceChannel;
return GetNextAnnounceChannel(channel) == kErrorNone;
}
bool Mle::Attacher::IsBetterParent(uint16_t aRloc16,
uint8_t aTwoWayLinkMargin,
const Connectivity &aConnectivity,
uint16_t aVersion,
const Mac::CslAccuracy &aCslAccuracy)
{
int rval;
// Mesh Impacting Criteria
rval = ThreeWayCompare(LinkQualityForLinkMargin(aTwoWayLinkMargin), mParentCandidate.GetTwoWayLinkQuality());
VerifyOrExit(rval == 0);
rval = ThreeWayCompare(IsRouterRloc16(aRloc16), IsRouterRloc16(mParentCandidate.GetRloc16()));
VerifyOrExit(rval == 0);
rval = ThreeWayCompare(aConnectivity.GetParentPriority(), mParentCandidate.mConnectivity.GetParentPriority());
VerifyOrExit(rval == 0);
// Prefer the parent with highest quality links (Link Quality 3 field in Connectivity TLV) to neighbors
rval = ThreeWayCompare(aConnectivity.GetNumLinkQuality3(), mParentCandidate.mConnectivity.GetNumLinkQuality3());
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(aConnectivity.GetSedBufferSize(), mParentCandidate.mConnectivity.GetSedBufferSize());
VerifyOrExit(rval == 0);
rval = ThreeWayCompare(aConnectivity.GetSedDatagramCount(), mParentCandidate.mConnectivity.GetSedDatagramCount());
VerifyOrExit(rval == 0);
// Extra rules
rval = ThreeWayCompare(aConnectivity.GetNumLinkQuality2(), mParentCandidate.mConnectivity.GetNumLinkQuality2());
VerifyOrExit(rval == 0);
rval = ThreeWayCompare(aConnectivity.GetNumLinkQuality1(), mParentCandidate.mConnectivity.GetNumLinkQuality1());
VerifyOrExit(rval == 0);
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
// CSL metric
if (!Get<Mle>().IsRxOnWhenIdle())
{
uint64_t cslMetric = Get<Mle>().CalcParentCslMetric(aCslAccuracy);
uint64_t candidateCslMetric = Get<Mle>().CalcParentCslMetric(mParentCandidate.GetCslAccuracy());
// Smaller metric is better.
rval = ThreeWayCompare(candidateCslMetric, cslMetric);
VerifyOrExit(rval == 0);
}
#else
OT_UNUSED_VARIABLE(aCslAccuracy);
#endif
rval = ThreeWayCompare(aTwoWayLinkMargin, mParentCandidate.mLinkMargin);
exit:
return (rval > 0);
}
void Mle::Attacher::HandleParentResponse(RxInfo &aRxInfo)
{
Error error = kErrorNone;
int8_t rss = aRxInfo.mMessage.GetAverageRss();
uint16_t version;
uint16_t sourceAddress;
LeaderData leaderData;
uint8_t linkMarginOut;
uint8_t twoWayLinkMargin;
Connectivity connectivity;
uint32_t linkFrameCounter;
uint32_t mleFrameCounter;
Mac::ExtAddress extAddress;
Mac::CslAccuracy cslAccuracy;
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
TimeParameterTlv timeParameterTlv;
#endif
SuccessOrExit(error = Tlv::Find<SourceAddressTlv>(aRxInfo.mMessage, sourceAddress));
Log(kMessageReceive, kTypeParentResponse, aRxInfo.mMessageInfo.GetPeerAddr(), sourceAddress);
VerifyOrExit(mState != kStateChildIdRequest, error = kErrorInvalidState);
SuccessOrExit(error = aRxInfo.mMessage.ReadVersionTlv(version));
SuccessOrExit(error = aRxInfo.mMessage.ReadAndMatchResponseTlvWith(mParentRequestChallenge));
extAddress.SetFromIid(aRxInfo.mMessageInfo.GetPeerAddr().GetIid());
if (Get<Mle>().IsChild() && Get<Mle>().mParent.GetExtAddress() == extAddress)
{
mReceivedResponseFromParent = true;
}
SuccessOrExit(error = aRxInfo.mMessage.ReadLeaderDataTlv(leaderData));
SuccessOrExit(error = Tlv::Find<LinkMarginTlv>(aRxInfo.mMessage, linkMarginOut));
twoWayLinkMargin = Min(Get<Mac::Mac>().ComputeLinkMargin(rss), linkMarginOut);
SuccessOrExit(error = aRxInfo.mMessage.ReadConnectivityTlv(connectivity));
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
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 = connectivity.GetParentPriority();
parentinfo.mLinkQuality3 = connectivity.GetNumLinkQuality3();
parentinfo.mLinkQuality2 = connectivity.GetNumLinkQuality2();
parentinfo.mLinkQuality1 = connectivity.GetNumLinkQuality1();
parentinfo.mIsAttached = Get<Mle>().IsAttached();
mParentResponseCallback.Invoke(&parentinfo);
}
#endif
aRxInfo.mClass = RxInfo::kAuthoritativeMessage;
#if OPENTHREAD_FTD
if (Get<Mle>().IsFullThreadDevice() && !Get<Mle>().IsDetached())
{
bool isPartitionIdSame = (leaderData.GetPartitionId() == Get<Mle>().mLeaderData.GetPartitionId());
bool isIdSequenceSame = (connectivity.GetIdSequence() == Get<RouterTable>().GetRouterIdSequence());
bool isIdSequenceGreater =
SerialNumber::IsGreater(connectivity.GetIdSequence(), Get<RouterTable>().GetRouterIdSequence());
switch (mMode)
{
case kAnyPartition:
VerifyOrExit(!isPartitionIdSame || isIdSequenceGreater);
break;
case kSamePartition:
VerifyOrExit(isPartitionIdSame && isIdSequenceGreater);
break;
case kDowngradeToReed:
VerifyOrExit(isPartitionIdSame && (isIdSequenceSame || isIdSequenceGreater));
break;
case kBetterPartition:
VerifyOrExit(!isPartitionIdSame);
VerifyOrExit(ComparePartitions(connectivity.IsSingleton(), leaderData, Get<Mle>().IsSingleton(),
Get<Mle>().mLeaderData) > 0);
break;
case kBetterParent:
case kSelectedParent:
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 (Get<Mle>().IsFullThreadDevice())
{
compare = ComparePartitions(connectivity.IsSingleton(), leaderData,
mParentCandidate.mConnectivity.IsSingleton(), 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, twoWayLinkMargin, connectivity, version, cslAccuracy));
}
}
SuccessOrExit(error = aRxInfo.mMessage.ReadFrameCounterTlvs(linkFrameCounter, mleFrameCounter));
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
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
#endif // OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
SuccessOrExit(error = aRxInfo.mMessage.ReadChallengeTlv(mParentCandidate.mRxChallenge));
Get<Mle>().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(linkMarginOut));
mParentCandidate.SetState(Neighbor::kStateParentResponse);
mParentCandidate.SetKeySequence(aRxInfo.mKeySequence);
mParentCandidate.SetLeaderCost(connectivity.GetLeaderCost());
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
mParentCandidate.SetCslAccuracy(cslAccuracy);
#endif
mParentCandidate.mConnectivity = connectivity;
mParentCandidate.mLeaderData = leaderData;
mParentCandidate.mLinkMargin = twoWayLinkMargin;
exit:
LogProcessError(kTypeParentResponse, error);
}
void Mle::Attacher::HandleChildIdResponse(RxInfo &aRxInfo)
{
Error error = kErrorNone;
LeaderData leaderData;
uint16_t sourceAddress;
uint16_t shortAddress;
MeshCoP::Timestamp timestamp;
SuccessOrExit(error = Tlv::Find<SourceAddressTlv>(aRxInfo.mMessage, sourceAddress));
Log(kMessageReceive, kTypeChildIdResponse, aRxInfo.mMessageInfo.GetPeerAddr(), sourceAddress);
VerifyOrExit(aRxInfo.IsNeighborStateValid(), error = kErrorSecurity);
VerifyOrExit(mState == kStateChildIdRequest);
SuccessOrExit(error = Tlv::Find<Address16Tlv>(aRxInfo.mMessage, shortAddress));
VerifyOrExit(RouterIdMatch(sourceAddress, shortAddress), error = kErrorRejected);
SuccessOrExit(error = aRxInfo.mMessage.ReadLeaderDataTlv(leaderData));
VerifyOrExit(aRxInfo.mMessage.ContainsTlv(Tlv::kNetworkData));
switch (Tlv::Find<ActiveTimestampTlv>(aRxInfo.mMessage, timestamp))
{
case kErrorNone:
error = aRxInfo.mMessage.ReadAndSaveActiveDataset(timestamp);
error = (error == kErrorNotFound) ? kErrorNone : error;
SuccessOrExit(error);
break;
case kErrorNotFound:
break;
default:
ExitNow(error = kErrorParse);
}
// Clear Pending Dataset if device succeed to reattach using stored Pending Dataset
if (mReattachMode == kReattachModePending)
{
Get<MeshCoP::PendingDatasetManager>().Clear();
}
switch (Tlv::Find<PendingTimestampTlv>(aRxInfo.mMessage, timestamp))
{
case kErrorNone:
IgnoreError(aRxInfo.mMessage.ReadAndSavePendingDataset(timestamp));
break;
case kErrorNotFound:
Get<MeshCoP::PendingDatasetManager>().Clear();
break;
default:
ExitNow(error = kErrorParse);
}
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
if (aRxInfo.mMessage.GetTimeSyncSeq() != OT_TIME_SYNC_INVALID_SEQ)
{
Get<TimeSync>().HandleTimeSyncMessage(aRxInfo.mMessage);
}
#endif
// Parent Attach Success
Get<Mle>().SetStateDetached();
Get<Mle>().SetLeaderData(leaderData);
#if OPENTHREAD_FTD
SuccessOrExit(error = Get<Mle>().ReadAndProcessRouteTlvOnFtdChild(aRxInfo, RouterIdFromRloc16(sourceAddress)));
#endif
mParentCandidate.CopyTo(Get<Mle>().mParent);
mParentCandidate.Clear();
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
Get<Mac::Mac>().SetCslParentAccuracy(Get<Mle>().mParent.GetCslAccuracy());
#endif
Get<Mle>().mParent.SetRloc16(sourceAddress);
IgnoreError(aRxInfo.mMessage.ReadAndSetNetworkDataTlv(leaderData));
Get<Mle>().SetStateChild(shortAddress);
if (!Get<Mle>().IsRxOnWhenIdle())
{
Get<DataPollSender>().SetAttachMode(false);
Get<MeshForwarder>().SetRxOnWhenIdle(false);
}
else
{
Get<MeshForwarder>().SetRxOnWhenIdle(true);
}
aRxInfo.mClass = RxInfo::kPeerMessage;
exit:
LogProcessError(kTypeChildIdResponse, error);
}
// LCOV_EXCL_START
#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
const char *Mle::Attacher::StateToString(State aState)
{
#define AttacherStateMapList(_) \
_(kStateIdle, "Idle") \
_(kStateStart, "Start") \
_(kStateParentRequest, "ParentReq") \
_(kStateAnnounce, "Announce") \
_(kStateChildIdRequest, "ChildIdReq")
DefineEnumStringArray(AttacherStateMapList);
return kStrings[aState];
}
#endif // OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_NOTE)
const char *Mle::Attacher::AttachModeToString(AttachMode aMode)
{
#define AttachModeMapList(_) \
_(kAnyPartition, "AnyPartition") \
_(kSamePartition, "SamePartition") \
_(kBetterPartition, "BetterPartition") \
_(kDowngradeToReed, "DowngradeToReed") \
_(kBetterParent, "BetterParent") \
_(kSelectedParent, "SelectedParent")
DefineEnumStringArray(AttachModeMapList);
return kStrings[aMode];
}
const char *Mle::Attacher::ReattachModeToString(ReattachMode aMode)
{
#define ReattachModeMapList(_) \
_(kReattachModeStop, "") \
_(kReattachModeActive, "reattaching with Active Dataset") \
_(kReattachModePending, "reattaching with Pending Dataset")
DefineEnumStringArray(ReattachModeMapList);
return kStrings[aMode];
}
#endif // OT_SHOULD_LOG_AT( OT_LOG_LEVEL_NOTE)
// LCOV_EXCL_STOP
//---------------------------------------------------------------------------------------------------------------------
// Detacher
Mle::Detacher::Detacher(Instance &aInstance)
: InstanceLocator(aInstance)
, mState(kIdle)
, mTimer(aInstance)
{
}
Error Mle::Detacher::Detach(DetachCallback aCallback, void *aContext)
{
Error error = kErrorNone;
uint32_t timeout = kTimeout;
VerifyOrExit(mState == kIdle, error = kErrorBusy);
mCallback.Set(aCallback, aContext);
#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
Get<BorderRouter::RoutingManager>().RequestStop();
#endif
switch (Get<Mle>().GetRole())
{
case kRoleLeader:
break;
case kRoleRouter:
#if OPENTHREAD_FTD
Get<Mle>().SendAddressRelease();
#endif
break;
case kRoleChild:
IgnoreError(Get<Mle>().SendChildUpdateRequestToParent(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;
}
mState = kDetaching;
mTimer.Start(timeout);
exit:
return error;
}
void Mle::Detacher::HandleTimer(void)
{
if (mState == kDetaching)
{
Get<Mle>().Stop();
}
}
Error Mle::Detacher::HandleChildUpdateResponse(uint32_t aTimeout)
{
// Called while processing a "Child Update Response" from parent
//
// To gracefully detach as a child, we send a "Child Update
// Request" with zero timeout value to the parent. Receiving
// a "Child Update Response" confirms the parent has received the
// request, allowing the device to then stop its MLE operations.
// Returns `kErrorDetached` to signal that MLE is stopped and the
// incoming response should be ignored. Returns `kErrorNone` if
// the response should be processed (i.e., not detaching or if
// the conditions for MLE stop are not yet met).
Error error = kErrorNone;
VerifyOrExit(mState == kDetaching);
VerifyOrExit(aTimeout == 0);
Get<Mle>().Stop();
error = kErrorDetached;
exit:
return error;
}
void Mle::Detacher::HandleStop(void)
{
// Called upon `Mle::Stop()` after role change.
if (mState == kDetaching)
{
mState = kIdle;
mCallback.InvokeAndClearIfSet();
}
}
//---------------------------------------------------------------------------------------------------------------------
// RetxTracker::RetryInfo
void Mle::RetxTracker::RetryInfo::Reset(void)
{
mState = kIdle;
mAttempts = 0;
}
void Mle::RetxTracker::RetryInfo::IncrementAttempts(void)
{
if (mAttempts != NumericLimits<uint8_t>::kMax)
{
mAttempts++;
}
}
void Mle::RetxTracker::RetryInfo::SetNextTxTime(uint32_t aDelay, uint16_t aJitter)
{
mNextTxTime = TimerMilli::GetNow() + Random::NonCrypto::AddJitter(aDelay, aJitter);
}
bool Mle::RetxTracker::RetryInfo::ShouldSend(TimeMilli aNow) const
{
bool shouldSend = false;
switch (mState)
{
case kIdle:
break;
case kWaitingForResponse:
case kSendingKeepAlive:
shouldSend = (aNow >= mNextTxTime);
break;
}
return shouldSend;
}
void Mle::RetxTracker::RetryInfo::Schedule(TimerMilli &aTimer) const
{
switch (mState)
{
case kIdle:
break;
case kWaitingForResponse:
case kSendingKeepAlive:
aTimer.FireAtIfEarlier(mNextTxTime);
break;
}
}
Error Mle::RetxTracker::RetryInfo::DetachIfMaxAttemptsReached(Mle &aMle) const
{
Error error = kErrorNone;
if (mAttempts >= kMaxAttempts)
{
IgnoreError(aMle.BecomeDetached());
error = kErrorDetached;
}
return error;
}
//---------------------------------------------------------------------------------------------------------------------
// RetxTracker
Mle::RetxTracker::RetxTracker(Instance &aInstance)
: InstanceLocator(aInstance)
, mTimer(aInstance)
{
mChildUpdate.Reset();
mDataRequest.Reset();
}
void Mle::RetxTracker::Stop(void)
{
mTimer.Stop();
mChildUpdate.Reset();
mDataRequest.Reset();
}
void Mle::RetxTracker::UpdateOnRoleChangeToChild(void)
{
mChildUpdate.Reset();
mDataRequest.Reset();
DetermineKeepAliveChildUpdateTxTime();
ScheduleTimer();
}
void Mle::RetxTracker::DetermineKeepAliveChildUpdateTxTime(void)
{
// Keep-alive periodic Child Update is used on a rx-on child.
uint32_t interval;
VerifyOrExit(Get<Mle>().IsChild() && Get<Mle>().IsRxOnWhenIdle());
interval = Time::SecToMsec(Get<Mle>().mTimeout) - (kRetxDelay + kRetxJitter) * kMaxChildKeepAliveAttempts;
mChildUpdate.mState = kSendingKeepAlive;
mChildUpdate.SetNextTxTime(interval, kRetxJitter);
exit:
return;
}
void Mle::RetxTracker::UpdateOnChildUpdateRequestTx(void)
{
uint32_t interval = kRetxDelay;
mChildUpdate.IncrementAttempts();
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
if (Get<Mac::Mac>().IsCslEnabled())
{
interval += Get<Mac::Mac>().GetCslPeriodInMsec();
}
#endif
mChildUpdate.mState = kWaitingForResponse;
mChildUpdate.SetNextTxTime(interval, kRetxJitter);
ScheduleTimer();
}
void Mle::RetxTracker::UpdateOnChildUpdateResponseRx(void)
{
mChildUpdate.mAttempts = 0;
mChildUpdate.mState = kIdle;
DetermineKeepAliveChildUpdateTxTime();
// We hold off on retransmitting Data Requests while waiting for a
// Child Update Response. The Child Update Response is expected
// to contain the necessary data, which typically fulfills and
// clears any outstanding Data Request. If, however, a Data
// Request remains pending, we'll schedule its retransmission
// following a `kRetxDelay`.
if (mDataRequest.mState == kWaitingForResponse)
{
mDataRequest.SetNextTxTime(kRetxDelay, kRetxJitter);
}
ScheduleTimer();
}
void Mle::RetxTracker::UpdateOnDataRequestTx(void)
{
// Data Request retries are tracked only on a sleepy child.
if (Get<Mle>().IsChild() && !Get<Mle>().IsRxOnWhenIdle())
{
mDataRequest.IncrementAttempts();
mDataRequest.mState = kWaitingForResponse;
mDataRequest.SetNextTxTime(kRetxDelay, kRetxJitter);
}
else
{
mDataRequest.mState = kIdle;
mDataRequest.mAttempts = 0;
}
ScheduleTimer();
}
void Mle::RetxTracker::UpdateOnDataResponseRx(void)
{
mDataRequest.mState = kIdle;
mDataRequest.mAttempts = 0;
ScheduleTimer();
}
void Mle::RetxTracker::ScheduleTimer(void)
{
mTimer.Stop();
VerifyOrExit(!Get<Mle>().IsDisabled());
mChildUpdate.Schedule(mTimer);
// We defer sending Data Request while awaiting a Child Update
// Response.
if (mChildUpdate.mState != kWaitingForResponse)
{
mDataRequest.Schedule(mTimer);
}
exit:
return;
}
void Mle::RetxTracker::HandleTimer(void)
{
TimeMilli now = TimerMilli::GetNow();
VerifyOrExit(!Get<Mle>().IsDisabled());
if (mChildUpdate.ShouldSend(now))
{
SuccessOrExit(mChildUpdate.DetachIfMaxAttemptsReached(Get<Mle>()));
IgnoreError(Get<Mle>().SendChildUpdateRequestToParent());
ExitNow();
}
if (mDataRequest.ShouldSend(now))
{
SuccessOrExit(mDataRequest.DetachIfMaxAttemptsReached(Get<Mle>()));
IgnoreError(Get<Mle>().SendDataRequestToParent());
ExitNow();
}
ScheduleTimer();
exit:
return;
}
//---------------------------------------------------------------------------------------------------------------------
// AnnounceHanlder
Mle::AnnounceHandler::AnnounceHandler(Instance &aInstance)
: InstanceLocator(aInstance)
, mState(kStateIdle)
, mTimer(aInstance)
{
}
void Mle::AnnounceHandler::Stop(void)
{
mTimer.Stop();
mState = kStateIdle;
}
void Mle::AnnounceHandler::HandleAnnounce(RxInfo &aRxInfo)
{
enum Action : uint8_t
{
kIgnore,
kSendAnnouceBack,
kAnnounceAttachAfterDelay,
kSignalAnnounceSender,
};
Error error = kErrorNone;
Action action = kIgnore;
ChannelTlvValue channelTlvValue;
MeshCoP::Timestamp timestamp;
MeshCoP::Timestamp pendingActiveTimestamp;
uint8_t channel;
uint16_t panId;
bool isFromOrphan;
bool channelAndPanIdMatch;
int timestampCompare;
Log(kMessageReceive, kTypeAnnounce, aRxInfo.mMessageInfo.GetPeerAddr());
SuccessOrExit(error = Tlv::Find<ChannelTlv>(aRxInfo.mMessage, channelTlvValue));
channel = static_cast<uint8_t>(channelTlvValue.GetChannel());
SuccessOrExit(error = Tlv::Find<ActiveTimestampTlv>(aRxInfo.mMessage, timestamp));
SuccessOrExit(error = Tlv::Find<PanIdTlv>(aRxInfo.mMessage, panId));
aRxInfo.mClass = RxInfo::kPeerMessage;
isFromOrphan = timestamp.IsOrphanAnnounce();
timestampCompare = MeshCoP::Timestamp::Compare(timestamp, Get<MeshCoP::ActiveDatasetManager>().GetTimestamp());
channelAndPanIdMatch = (channel == Get<Mac::Mac>().GetPanChannel()) && (panId == Get<Mac::Mac>().GetPanId());
// Determine the action to perform.
if (isFromOrphan)
{
VerifyOrExit(!channelAndPanIdMatch);
action = kSendAnnouceBack;
}
else if (timestampCompare < 0)
{
// On an FTD which can become a router, we send an Announce
// back to help the sender learn and migrate to the newer
// Dataset. On a detached MTD, we process the Announce (wait
// for a short delay before trying to attach to the older
// Dataset). This is useful when an MTD child device has a
// newer Dataset but the routers it can hear are still on a
// previous, older Dataset. On an attached MTD, we ignore the
// stale Announce, since we cannot become a router to help the
// older device join the new Dataset, so sending an Announce
// back would be pointless.
#if OPENTHREAD_FTD
if (Get<Mle>().IsFullThreadDevice() && Get<Mle>().IsRouterEligible())
{
action = kSendAnnouceBack;
}
else
#endif
{
action = Get<Mle>().IsDetached() ? kAnnounceAttachAfterDelay : kIgnore;
}
}
else if (timestampCompare > 0)
{
action = kAnnounceAttachAfterDelay;
}
else // timestampCompare is zero
{
action = kSignalAnnounceSender;
}
switch (action)
{
case kIgnore:
break;
case kSendAnnouceBack:
Get<Mle>().SendAnnounce(channel);
#if OPENTHREAD_CONFIG_MLE_SEND_UNICAST_ANNOUNCE_RESPONSE
Get<Mle>().SendAnnounce(channel, aRxInfo.mMessageInfo.GetPeerAddr());
#endif
break;
case kAnnounceAttachAfterDelay:
// No action is required if device is detached, and current
// channel and pan-id match the values from the received MLE
// Announce message.
if (Get<Mle>().IsDetached())
{
VerifyOrExit(!channelAndPanIdMatch);
}
if (Get<MeshCoP::PendingDatasetManager>().ReadActiveTimestamp(pendingActiveTimestamp) == kErrorNone)
{
// Ignore the Announce and take no action, if a pending
// dataset exists with an equal or more recent timestamp,
// and it will be applied soon.
if (pendingActiveTimestamp >= timestamp)
{
uint32_t remainingDelay;
if ((Get<MeshCoP::PendingDatasetManager>().ReadRemainingDelay(remainingDelay) == kErrorNone) &&
(remainingDelay < kAnnounceBackoffForPendingDataset))
{
ExitNow();
}
}
}
if (mState == kStateToAnnounceAttach)
{
VerifyOrExit(mAlternateTimestamp < timestamp.GetSeconds());
}
mAlternateTimestamp = timestamp.GetSeconds();
mAlternateChannel = channel;
mAlternatePanId = panId;
mState = kStateToAnnounceAttach;
mTimer.Start(kAnnounceProcessTimeout);
LogNote("Delay processing Announce - channel %d, panid 0x%02x", channel, panId);
break;
case kSignalAnnounceSender:
#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
break;
}
exit:
LogProcessError(kTypeAnnounce, error);
}
void Mle::AnnounceHandler::StartAnnounceAttach(void)
{
uint8_t newChannel = mAlternateChannel;
uint16_t newPanId = mAlternatePanId;
VerifyOrExit(mState == kStateToAnnounceAttach);
LogNote("Starting Announce attach - channel %d, panid 0x%02x", newChannel, newPanId);
Get<Mle>().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);
mState = kStateAnnounceAttaching;
IgnoreError(Get<Mle>().Start(kAnnounceAttach));
exit:
return;
}
void Mle::AnnounceHandler::HandleAnnounceAttachSuccess(void)
{
// Clear state and send announce on previous channel.
VerifyOrExit(mState == kStateAnnounceAttaching);
mState = kStateToInformPreviousChannel;
#if OPENTHREAD_FTD
if (Get<Mle>().IsFullThreadDevice() && !Get<Mle>().IsRouter() && Get<Mle>().IsRouterRoleTransitionPending())
{
ExitNow();
}
#endif
InformPreviousChannel();
exit:
return;
}
void Mle::AnnounceHandler::InformPreviousChannel(void)
{
VerifyOrExit(mState == kStateToInformPreviousChannel);
mState = kStateIdle;
Get<AnnounceBeginServer>().SendAnnounce(1 << mAlternateChannel);
exit:
return;
}
void Mle::AnnounceHandler::HandleAnnounceAttachFailure(void)
{
VerifyOrExit(mState == kStateAnnounceAttaching);
mState = kStateIdle;
IgnoreError(Get<Mac::Mac>().SetPanChannel(mAlternateChannel));
Get<Mac::Mac>().SetPanId(mAlternatePanId);
exit:
return;
}
void Mle::AnnounceHandler::HandleTimer(void) { StartAnnounceAttach(); }
} // namespace Mle
} // namespace ot