Files
openthread/src/core/thread/mle_ftd.cpp
T
Jonathan Hui ecd4c92465 [dua] completely remove DUA features and configurations (#13191)
This commit removes the OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE feature
and all associated code, tests, CLI commands, and harness references.

Changes:
- Removed OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE definition and all
  assert/preprocessor checks.
- Completely deleted dua_manager.cpp and dua_manager.hpp.
- Removed DUA registration notifying and request URI paths.
- Cleaned up all references to Domain Unicast Address (DUA) across
  child management, notifier, time ticker, and MLE.
- Removed DUA commands and logic from the CLI and Python cert tests
  (including packet verifier).
- Verified that the entire codebase compiles clean and all tests
  successfully pass using the Nexus test suite.
2026-06-03 12:29:18 -07:00

3970 lines
117 KiB
C++

/*
* Copyright (c) 2016, The OpenThread Authors. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @file
* This file implements MLE functionality required for the Thread Router and Leader roles.
*/
#include "mle.hpp"
#if OPENTHREAD_FTD
#include "instance/instance.hpp"
namespace ot {
namespace Mle {
RegisterLogModule("Mle");
void Mle::SetAlternateRloc16(uint16_t aRloc16)
{
VerifyOrExit(aRloc16 != Mac::kShortAddrInvalid);
LogInfo("Setting alternate RLOC16 0x%04x", aRloc16);
Get<Mac::Mac>().SetAlternateShortAddress(aRloc16);
mAlternateRloc16Timeout = kAlternateRloc16Timeout;
exit:
return;
}
void Mle::ClearAlternateRloc16(void)
{
VerifyOrExit(Get<Mac::Mac>().GetAlternateShortAddress() != Mac::kShortAddrInvalid);
LogInfo("Clearing alternate RLOC16");
Get<Mac::Mac>().SetAlternateShortAddress(Mac::kShortAddrInvalid);
exit:
mAlternateRloc16Timeout = 0;
}
void Mle::HandlePartitionChange(void)
{
mPreviousPartitionId = mLeaderData.GetPartitionId();
mPreviousPartitionRouterIdSequence = mRouterTable.GetRouterIdSequence();
mPreviousPartitionIdTimeout = GetNetworkIdTimeout();
Get<AddressResolver>().Clear();
IgnoreError(Get<Tmf::Agent>().AbortTransaction(HandleAddressSolicitResponse, this));
mRouterTable.Clear();
}
bool Mle::RoleTransitioner::DetermineIfRouterRoleAllowed(void) const
{
bool allowed = false;
const SecurityPolicy &secPolicy = Get<KeyManager>().GetSecurityPolicy();
VerifyOrExit(mRouterEligible && Get<Mle>().IsFullThreadDevice());
#if OPENTHREAD_CONFIG_THREAD_VERSION == OT_THREAD_VERSION_1_1
VerifyOrExit(secPolicy.mRoutersEnabled);
#else
if (secPolicy.mCommercialCommissioningEnabled)
{
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
VerifyOrExit(mCcmEnabled || secPolicy.mNonCcmRoutersEnabled);
#else
VerifyOrExit(secPolicy.mNonCcmRoutersEnabled);
#endif
}
if (!secPolicy.mRoutersEnabled)
{
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
VerifyOrExit(!mThreadVersionCheckEnabled ||
secPolicy.mVersionThresholdForRouting + SecurityPolicy::kVersionThresholdOffsetVersion <=
kThreadVersion);
#else
VerifyOrExit(secPolicy.mVersionThresholdForRouting + SecurityPolicy::kVersionThresholdOffsetVersion <=
kThreadVersion);
#endif
}
#endif // #if OPENTHREAD_CONFIG_THREAD_VERSION == OT_THREAD_VERSION_1_1
allowed = true;
exit:
return allowed;
}
void Mle::RoleTransitioner::UpdateRouterRoleAllowed(UpdateRouterRoleAllowedReason aReason)
{
bool allowed = DetermineIfRouterRoleAllowed();
VerifyOrExit(allowed != mRouterRoleAllowed);
mRouterRoleAllowed = allowed;
if (Get<Mle>().IsAttached())
{
Get<Mac::Mac>().SetBeaconEnabled(mRouterRoleAllowed);
}
// Take action based on the current role, the new `mRouterRoleAllowed`,
// and the reason for the change.
if (Get<Mle>().IsChild() && mRouterRoleAllowed && (aReason == kReasonConfigParameterChanged))
{
StartTimeout();
}
if (Get<Mle>().IsRouterOrLeader())
{
// If currently acting as router or leader, but the config or
// security policy changes such that the router role is no
// longer allowed, we take action based on the reason. If the
// change is due to a security policy update, we start a jitter
// timeout to downgrade (per Section 5.9.9 in the Thread spec),
// adding an extra delay if acting as leader. If this is
// triggered due to parameter change (`SetRouterEligible(false)`
// was called by the user), we take immediate action and become
// detached.
VerifyOrExit(!mRouterRoleAllowed);
switch (aReason)
{
case kReasonMleInit:
case kReasonDeviceModeChanged:
break;
case kReasonConfigParameterChanged:
IgnoreError(Get<Mle>().BecomeDetached());
break;
case kReasonSecurityPolicyChanged:
VerifyOrExit(!IsTransitionPending());
StartTimeout();
if (Get<Mle>().IsLeader())
{
IncreaseTimeout(kLeaderDowngradeExtraDelay);
}
break;
}
}
exit:
return;
}
Error Mle::RoleTransitioner::SetRouterEligible(bool aEligible)
{
Error error = kErrorNone;
if (!Get<Mle>().IsFullThreadDevice())
{
VerifyOrExit(!aEligible, error = kErrorNotCapable);
}
VerifyOrExit(aEligible != mRouterEligible);
mRouterEligible = aEligible;
UpdateRouterRoleAllowed(kReasonConfigParameterChanged);
exit:
return error;
}
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
void Mle::RoleTransitioner::SetCcmEnabled(bool aEnabled)
{
mCcmEnabled = aEnabled;
UpdateRouterRoleAllowed(kReasonConfigParameterChanged);
}
void Mle::RoleTransitioner::SetThreadVersionCheckEnabled(bool aEnabled)
{
mThreadVersionCheckEnabled = aEnabled;
UpdateRouterRoleAllowed(kReasonConfigParameterChanged);
}
#endif
#if OPENTHREAD_CONFIG_MLE_DEVICE_PROPERTY_LEADER_WEIGHT_ENABLE
void Mle::SetDeviceProperties(const DeviceProperties &aDeviceProperties)
{
mDeviceProperties = aDeviceProperties;
mDeviceProperties.ClampWeightAdjustment();
SetLeaderWeight(mDeviceProperties.CalculateLeaderWeight());
}
#endif
Error Mle::BecomeRouter(RouterUpgradeReason aReason)
{
Error error = kErrorNone;
switch (mRole)
{
case kRoleChild:
break;
case kRoleDisabled:
case kRoleDetached:
error = kErrorInvalidState;
OT_FALL_THROUGH;
case kRoleRouter:
case kRoleLeader:
ExitNow();
}
VerifyOrExit(IsRouterRoleAllowed(), error = kErrorNotCapable);
LogInfo("Attempt to become router, reason:%s", RouterUpgradeReasonToString(aReason));
Get<MeshForwarder>().SetRxOnWhenIdle(true);
mRoleTransitioner.StopTimeout();
error = SendAddressSolicit(aReason);
exit:
return error;
}
Error Mle::BecomeLeader(LeaderWeightCheck aMode)
{
Error error = kErrorNone;
Router *router;
uint8_t leaderId;
#if OPENTHREAD_CONFIG_OPERATIONAL_DATASET_AUTO_INIT
VerifyOrExit(!Get<MeshCoP::ActiveDatasetManager>().IsPartiallyComplete(), error = kErrorInvalidState);
#else
VerifyOrExit(Get<MeshCoP::ActiveDatasetManager>().IsComplete(), error = kErrorInvalidState);
#endif
VerifyOrExit(!IsDisabled(), error = kErrorInvalidState);
VerifyOrExit(!IsLeader(), error = kErrorNone);
VerifyOrExit(IsRouterRoleAllowed(), error = kErrorNotCapable);
if ((aMode == kCheckLeaderWeight) && IsAttached())
{
VerifyOrExit(mLeaderWeight > mLeaderData.GetWeighting(), error = kErrorNotCapable);
}
mRouterTable.Clear();
leaderId = SelectLeaderId();
SetLeaderData(SelectPartitionId(), mLeaderWeight, leaderId);
router = mRouterTable.Allocate(leaderId);
OT_ASSERT(router != nullptr);
SetRouterId(leaderId);
router->SetExtAddress(Get<Mac::Mac>().GetExtAddress());
Get<NetworkData::Leader>().Reset();
Get<MeshCoP::Leader>().SetEmptyCommissionerData();
SetStateLeader(Rloc16FromRouterId(leaderId), kStartingAsLeader);
exit:
return error;
}
uint8_t Mle::SelectLeaderId(void) const
{
uint8_t minId;
uint8_t maxId;
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
mRouterTable.GetRouterIdRange(minId, maxId);
#else
minId = 0;
maxId = kMaxRouterId;
#endif
return IsValueInRange(mPreviousRouterId, minId, maxId) ? mPreviousRouterId
: Random::NonCrypto::GenerateInClosedRange(minId, maxId);
}
uint32_t Mle::SelectPartitionId(void) const
{
uint32_t partitionId;
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
if (mPreferredLeaderPartitionId != 0)
{
partitionId = mPreferredLeaderPartitionId;
}
else
#endif
{
partitionId = Random::NonCrypto::Generate<uint32_t>();
}
return partitionId;
}
void Mle::StopLeader(void)
{
StopAdvertiseTrickleTimer();
Get<ThreadNetif>().UnsubscribeAllRoutersMulticast();
}
void Mle::HandleDetachStart(void)
{
mRouterTable.ClearNeighbors();
StopLeader();
Get<TimeTicker>().UnregisterReceiver(TimeTicker::kMle);
}
void Mle::HandleChildStart(void)
{
mAddressSolicitRejected = false;
mRoleTransitioner.StartTimeout();
StopLeader();
Get<TimeTicker>().RegisterReceiver(TimeTicker::kMle);
Get<Mac::Mac>().SetBeaconEnabled(IsRouterRoleAllowed());
Get<ThreadNetif>().SubscribeAllRoutersMulticast();
VerifyOrExit(IsRouterIdValid(mPreviousRouterId));
switch (mAttacher.GetAttachMode())
{
case kDowngradeToReed:
SendAddressRelease();
if (HasChildren())
{
RemoveChildren();
}
SetRouterId(kInvalidRouterId);
break;
case kSamePartition:
if (HasChildren())
{
IgnoreError(BecomeRouter(kReasonHaveChildIdRequest));
}
break;
case kAnyPartition:
case kBetterParent:
case kSelectedParent:
// If attach was initiated due to receiving an MLE Announce
// message, all rx-on-when-idle devices will immediately
// attempt to attach as well. This aligns with the Thread 1.1
// specification (Section 4.8.1):
//
// "If the received value is newer and the channel and/or PAN
// ID in the Announce message differ from those currently in
// use, the receiving device attempts to attach using the
// channel and PAN ID received from the Announce message."
//
// Since parent-child relationships are unlikely to persist in
// the new partition, we remove all children here. The
// decision to become router is determined based on the new
// partition's status.
if (mAnnounceHandler.IsAnnounceAttaching() && HasChildren())
{
RemoveChildren();
}
OT_FALL_THROUGH;
case kBetterPartition:
if (HasChildren() && mPreviousPartitionIdRouter != mLeaderData.GetPartitionId())
{
IgnoreError(BecomeRouter(kReasonParentPartitionChange));
}
break;
}
exit:
if (!mRoleTransitioner.IsRouterCountBelowUpgradeThreshold() &&
(!IsRouterIdValid(mPreviousRouterId) || !HasChildren()))
{
SetRouterId(kInvalidRouterId);
}
}
void Mle::SetStateRouter(uint16_t aRloc16)
{
// The `aStartMode` is ignored when used with `kRoleRouter`
SetStateRouterOrLeader(kRoleRouter, aRloc16, /* aStartMode */ kStartingAsLeader);
}
void Mle::SetStateLeader(uint16_t aRloc16, LeaderStartMode aStartMode)
{
SetStateRouterOrLeader(kRoleLeader, aRloc16, aStartMode);
}
void Mle::SetStateRouterOrLeader(DeviceRole aRole, uint16_t aRloc16, LeaderStartMode aStartMode)
{
if (aRole == kRoleLeader)
{
IgnoreError(Get<MeshCoP::ActiveDatasetManager>().Restore());
IgnoreError(Get<MeshCoP::PendingDatasetManager>().Restore());
}
SetRloc16(aRloc16);
SetRole(aRole);
mPrevRoleRestorer.Stop();
mAttacher.CancelAttachOnRoleChange();
mRetxTracker.Stop();
StopAdvertiseTrickleTimer();
ResetAdvertiseInterval();
Get<ThreadNetif>().SubscribeAllRoutersMulticast();
mPreviousPartitionIdRouter = mLeaderData.GetPartitionId();
Get<Mac::Mac>().SetBeaconEnabled(true);
Get<TimeTicker>().RegisterReceiver(TimeTicker::kMle);
// Avoid informing an old parent when attaching next as a child after becoming an active router
mPreviousParentRloc = kInvalidRloc16;
if (aRole == kRoleLeader)
{
GetLeaderAloc(mLeaderAloc.GetAddress());
Get<ThreadNetif>().AddUnicastAddress(mLeaderAloc);
Get<NetworkData::Leader>().Start(aStartMode);
Get<MeshCoP::ActiveDatasetManager>().StartLeader();
Get<MeshCoP::PendingDatasetManager>().StartLeader();
Get<AddressResolver>().Clear();
}
// Remove children that do not have a matching RLOC16
for (Child &child : Get<ChildTable>().Iterate(Child::kInStateValidOrRestoring))
{
if (RouterIdFromRloc16(child.GetRloc16()) != mRouterId)
{
RemoveNeighbor(child);
}
}
LogNote("Partition ID 0x%lx", ToUlong(mLeaderData.GetPartitionId()));
}
void Mle::HandleAdvertiseTrickleTimer(TrickleTimer &aTimer) { aTimer.Get<Mle>().HandleAdvertiseTrickleTimer(); }
void Mle::HandleAdvertiseTrickleTimer(void)
{
if (!IsRouterRoleAllowed())
{
mAdvertiseTrickleTimer.Stop();
}
else
{
SendMulticastAdvertisement();
}
}
void Mle::StopAdvertiseTrickleTimer(void) { mAdvertiseTrickleTimer.Stop(); }
uint32_t Mle::DetermineAdvertiseIntervalMax(void) const
{
uint32_t interval;
#if OPENTHREAD_CONFIG_MLE_LONG_ROUTES_ENABLE
interval = kAdvIntervalMaxLogRoutes;
#else
// Determine the interval based on the number of router neighbors
// with link quality 2 or higher.
interval = (Get<RouterTable>().GetNeighborCount(kLinkQuality2) + 1) * kAdvIntervalNeighborMultiplier;
interval = Clamp(interval, kAdvIntervalMaxLowerBound, kAdvIntervalMaxUpperBound);
#endif
return interval;
}
void Mle::HandleRouterTableEvent(RouterTable::Events aEvents)
{
// Callback from `RouterTable` when there is a change.
if (aEvents & RouterTable::kEventRouterAdded)
{
mRoleTransitioner.SetDowngradeBlocked(false);
}
if (IsRouterOrLeader() && mAdvertiseTrickleTimer.IsRunning())
{
mAdvertiseTrickleTimer.SetIntervalMax(DetermineAdvertiseIntervalMax());
}
}
void Mle::ResetAdvertiseInterval(void)
{
VerifyOrExit(IsRouterOrLeader());
if (!mAdvertiseTrickleTimer.IsRunning())
{
mAdvertiseTrickleTimer.Start(TrickleTimer::kModeTrickle, kAdvIntervalMin, DetermineAdvertiseIntervalMax());
}
mAdvertiseTrickleTimer.IndicateInconsistent();
exit:
return;
}
void Mle::SendMulticastAdvertisement(void)
{
VerifyOrExit(IsRouterRoleAllowed());
SendAdvertisement(Ip6::Address::GetLinkLocalAllNodesMulticast());
exit:
return;
}
void Mle::ScheduleUnicastAdvertisementTo(const Router &aRouter)
{
Ip6::Address destination;
destination.InitAsLinkLocalAddress(aRouter.GetExtAddress());
mDelayedSender.ScheduleAdvertisement(destination, GenerateRandomDelay(kMaxUnicastAdvertisementDelay));
}
void Mle::SendAdvertisement(const Ip6::Address &aDestination)
{
Error error = kErrorNone;
TxMessage *message = nullptr;
// Suppress MLE Advertisements when trying to attach to a better
// partition. Without this, a candidate parent might incorrectly
// interpret this advertisement (Source Address TLV containing an
// RLOC16 indicating device is acting as router) and reject the
// attaching device.
VerifyOrExit(!IsAttaching());
// Suppress MLE Advertisements when attempting to transition to
// router role. Advertisements as a REED while attaching to a new
// partition can cause existing children to detach
// unnecessarily.
VerifyOrExit(!mAddressSolicitPending);
VerifyOrExit((message = NewMleMessage(kCommandAdvertisement)) != nullptr, error = kErrorNoBufs);
SuccessOrExit(error = message->AppendSourceAddressAndLeaderDataTlvs());
switch (mRole)
{
case kRoleChild:
break;
case kRoleRouter:
case kRoleLeader:
SuccessOrExit(error = message->AppendRouteTlv());
break;
case kRoleDisabled:
case kRoleDetached:
OT_ASSERT(false);
}
SuccessOrExit(error = message->SendTo(aDestination));
Log(kMessageSend, kTypeAdvertisement, aDestination);
exit:
FreeMessageOnError(message, error);
LogSendError(kTypeAdvertisement, error);
}
void Mle::SendLinkRequest(Router *aRouter)
{
static const uint8_t kDetachedTlvs[] = {Tlv::kAddress16, Tlv::kRoute};
static const uint8_t kRouterTlvs[] = {Tlv::kLinkMargin};
static const uint8_t kValidNeighborTlvs[] = {Tlv::kLinkMargin, Tlv::kRoute};
Error error = kErrorNone;
TxMessage *message = nullptr;
Ip6::Address destination;
destination.Clear();
VerifyOrExit((message = NewMleMessage(kCommandLinkRequest)) != nullptr, error = kErrorNoBufs);
SuccessOrExit(error = message->AppendVersionTlv());
switch (mRole)
{
case kRoleDetached:
SuccessOrExit(error = message->AppendTlvRequestTlv(kDetachedTlvs));
break;
case kRoleChild:
SuccessOrExit(error = message->AppendSourceAddressAndLeaderDataTlvs());
break;
case kRoleRouter:
case kRoleLeader:
if (aRouter == nullptr || !aRouter->IsStateValid())
{
SuccessOrExit(error = message->AppendTlvRequestTlv(kRouterTlvs));
}
else
{
SuccessOrExit(error = message->AppendTlvRequestTlv(kValidNeighborTlvs));
}
SuccessOrExit(error = message->AppendSourceAddressAndLeaderDataTlvs());
break;
case kRoleDisabled:
OT_ASSERT(false);
}
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
SuccessOrExit(error = message->AppendTimeRequestTlv());
#endif
if (aRouter == nullptr)
{
mPrevRoleRestorer.GenerateRandomChallenge();
SuccessOrExit(error = message->AppendChallengeTlv(mPrevRoleRestorer.GetChallenge()));
destination = Ip6::Address::GetLinkLocalAllRoutersMulticast();
}
else
{
if (!aRouter->IsStateValid())
{
aRouter->GenerateChallenge();
SuccessOrExit(error = message->AppendChallengeTlv(aRouter->GetChallenge()));
}
else
{
TxChallenge challenge;
challenge.GenerateRandom();
SuccessOrExit(error = message->AppendChallengeTlv(challenge));
}
destination.InitAsLinkLocalAddress(aRouter->GetExtAddress());
aRouter->RestartLinkAcceptTimeout();
}
SuccessOrExit(error = message->SendTo(destination));
Log(kMessageSend, kTypeLinkRequest, destination);
exit:
FreeMessageOnError(message, error);
}
void Mle::HandleLinkRequest(RxInfo &aRxInfo)
{
Error error = kErrorNone;
Neighbor *neighbor = nullptr;
uint16_t version;
LeaderData leaderData;
LinkAcceptInfo info;
Log(kMessageReceive, kTypeLinkRequest, aRxInfo.mMessageInfo.GetPeerAddr());
VerifyOrExit(IsRouterOrLeader(), error = kErrorInvalidState);
VerifyOrExit(!IsAttaching(), error = kErrorInvalidState);
SuccessOrExit(error = aRxInfo.mMessage.ReadChallengeTlv(info.mRxChallenge));
SuccessOrExit(error = aRxInfo.mMessage.ReadVersionTlv(version));
switch (aRxInfo.mMessage.ReadLeaderDataTlv(leaderData))
{
case kErrorNone:
VerifyOrExit(leaderData.GetPartitionId() == mLeaderData.GetPartitionId(), error = kErrorInvalidState);
break;
case kErrorNotFound:
break;
default:
ExitNow(error = kErrorParse);
}
info.mExtAddress.SetFromIid(aRxInfo.mMessageInfo.GetPeerAddr().GetIid());
info.mLinkMargin = Get<Mac::Mac>().ComputeLinkMargin(aRxInfo.mMessage.GetAverageRss());
switch (Tlv::Find<SourceAddressTlv>(aRxInfo.mMessage, info.mRloc16))
{
case kErrorNone:
if (IsRouterRloc16(info.mRloc16))
{
Router *router = mRouterTable.FindRouterByRloc16(info.mRloc16);
VerifyOrExit(router != nullptr, error = kErrorParse);
if (!router->IsStateValid())
{
InitNeighbor(*router, aRxInfo);
router->SetState(Neighbor::kStateLinkRequest);
router->ClearLinkAcceptTimeout();
}
else
{
VerifyOrExit(router->GetExtAddress() == info.mExtAddress);
}
neighbor = router;
}
break;
case kErrorNotFound:
// A missing source address indicates that the router was
// recently reset.
VerifyOrExit(aRxInfo.IsNeighborStateValid(), error = kErrorDrop);
info.mRloc16 = aRxInfo.mNeighbor->GetRloc16();
VerifyOrExit(IsRouterRloc16(info.mRloc16), error = kErrorDrop);
neighbor = aRxInfo.mNeighbor;
break;
default:
ExitNow(error = kErrorParse);
}
switch (aRxInfo.mMessage.ReadTlvRequestTlv(info.mRequestedTlvList))
{
case kErrorNone:
case kErrorNotFound:
break;
default:
ExitNow(error = kErrorParse);
}
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
if (neighbor != nullptr)
{
neighbor->SetTimeSyncEnabled(Tlv::Find<TimeRequestTlv>(aRxInfo.mMessage, nullptr, 0) == kErrorNone);
}
#endif
#if OPENTHREAD_CONFIG_MULTI_RADIO
if (neighbor != nullptr)
{
neighbor->ClearLastRxFragmentTag();
}
#endif
aRxInfo.mClass = RxInfo::kPeerMessage;
ProcessKeySequence(aRxInfo);
if (aRxInfo.mMessageInfo.GetSockAddr().IsMulticast())
{
mDelayedSender.ScheduleLinkAccept(info, GenerateRandomDelay(kMaxLinkAcceptDelay));
}
else
{
error = SendLinkAccept(info);
}
exit:
LogProcessError(kTypeLinkRequest, error);
OT_UNUSED_VARIABLE(neighbor);
}
Error Mle::SendLinkAccept(const LinkAcceptInfo &aInfo)
{
static const uint8_t kRouterTlvs[] = {Tlv::kLinkMargin};
Error error = kErrorNone;
TxMessage *message = nullptr;
Command command = kCommandLinkAccept;
Router *router;
Ip6::Address destination;
VerifyOrExit(IsAttached());
router = mRouterTable.FindRouter(aInfo.mExtAddress);
if (router != nullptr)
{
if (router->IsStateLinkRequest())
{
command = kCommandLinkAcceptAndRequest;
router->SetLastHeard(TimerMilli::GetNow());
}
else
{
VerifyOrExit(router->IsStateValid());
}
}
VerifyOrExit((message = NewMleMessage(command)) != nullptr, error = kErrorNoBufs);
SuccessOrExit(error = message->AppendVersionTlv());
SuccessOrExit(error = message->AppendSourceAddressTlv());
SuccessOrExit(error = message->AppendResponseTlv(aInfo.mRxChallenge));
SuccessOrExit(error = message->AppendLinkAndMleFrameCounterTlvs());
SuccessOrExit(error = message->AppendLinkMarginTlv(aInfo.mLinkMargin));
if (router != nullptr)
{
SuccessOrExit(error = message->AppendLeaderDataTlv());
}
for (uint8_t tlvType : aInfo.mRequestedTlvList)
{
switch (tlvType)
{
case Tlv::kRoute:
SuccessOrExit(error = message->AppendCompactRouteTlv(aInfo.mRloc16));
break;
case Tlv::kAddress16:
VerifyOrExit(router != nullptr, error = kErrorDrop);
SuccessOrExit(error = message->AppendAddress16Tlv(router->GetRloc16()));
break;
case Tlv::kLinkMargin:
break;
default:
ExitNow(error = kErrorDrop);
}
}
if (command == kCommandLinkAcceptAndRequest)
{
router->GenerateChallenge();
SuccessOrExit(error = message->AppendChallengeTlv(router->GetChallenge()));
SuccessOrExit(error = message->AppendTlvRequestTlv(kRouterTlvs));
}
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
if (router != nullptr && router->IsTimeSyncEnabled())
{
message->SetTimeSync(true);
}
#endif
destination.InitAsLinkLocalAddress(aInfo.mExtAddress);
SuccessOrExit(error = message->SendTo(destination));
Log(kMessageSend, (command == kCommandLinkAccept) ? kTypeLinkAccept : kTypeLinkAcceptAndRequest, destination);
exit:
FreeMessageOnError(message, error);
return error;
}
void Mle::HandleLinkAccept(RxInfo &aRxInfo) { HandleLinkAcceptVariant(aRxInfo, kTypeLinkAccept); }
void Mle::HandleLinkAcceptAndRequest(RxInfo &aRxInfo) { HandleLinkAcceptVariant(aRxInfo, kTypeLinkAcceptAndRequest); }
void Mle::HandleLinkAcceptVariant(RxInfo &aRxInfo, MessageType aMessageType)
{
// Handles "Link Accept" or "Link Accept And Request".
Error error = kErrorNone;
Router *router;
Neighbor::State neighborState;
uint16_t version;
RxChallenge response;
uint16_t sourceAddress;
uint32_t linkFrameCounter;
uint32_t mleFrameCounter;
uint8_t routerId;
uint16_t address16;
RouteTlv::Data routeTlvData;
LeaderData leaderData;
uint8_t linkMargin;
bool shouldUpdateRoutes = false;
SuccessOrExit(error = Tlv::Find<SourceAddressTlv>(aRxInfo.mMessage, sourceAddress));
Log(kMessageReceive, aMessageType, aRxInfo.mMessageInfo.GetPeerAddr(), sourceAddress);
VerifyOrExit(IsRouterRloc16(sourceAddress), error = kErrorParse);
routerId = RouterIdFromRloc16(sourceAddress);
router = mRouterTable.FindRouterById(routerId);
neighborState = (router != nullptr) ? router->GetState() : Neighbor::kStateInvalid;
SuccessOrExit(error = aRxInfo.mMessage.ReadResponseTlv(response));
switch (neighborState)
{
case Neighbor::kStateLinkRequest:
VerifyOrExit(response == router->GetChallenge(), error = kErrorSecurity);
break;
case Neighbor::kStateInvalid:
VerifyOrExit(mPrevRoleRestorer.IsRestoringRouterOrLeaderRole(), error = kErrorSecurity);
VerifyOrExit(response == mPrevRoleRestorer.GetChallenge(), error = kErrorSecurity);
break;
case Neighbor::kStateValid:
break;
default:
ExitNow(error = kErrorSecurity);
}
// Remove stale neighbors
if (aRxInfo.mNeighbor && aRxInfo.mNeighbor->GetRloc16() != sourceAddress)
{
RemoveNeighbor(*aRxInfo.mNeighbor);
}
SuccessOrExit(error = aRxInfo.mMessage.ReadVersionTlv(version));
SuccessOrExit(error = aRxInfo.mMessage.ReadFrameCounterTlvs(linkFrameCounter, mleFrameCounter));
switch (Tlv::Find<LinkMarginTlv>(aRxInfo.mMessage, linkMargin))
{
case kErrorNone:
break;
case kErrorNotFound:
// The Link Margin TLV may be omitted after a reset. We wait
// for MLE Advertisements to establish the routing cost to
// the neighbor
VerifyOrExit(IsDetached(), error = kErrorNotFound);
linkMargin = 0;
break;
default:
ExitNow(error = kErrorParse);
}
switch (mRole)
{
case kRoleDetached:
SuccessOrExit(error = Tlv::Find<Address16Tlv>(aRxInfo.mMessage, address16));
VerifyOrExit(GetRloc16() == address16, error = kErrorDrop);
SuccessOrExit(error = aRxInfo.mMessage.ReadLeaderDataTlv(leaderData));
SetLeaderData(leaderData);
mRouterTable.Clear();
SuccessOrExit(error = aRxInfo.mMessage.ReadRouteTlv(routeTlvData));
SuccessOrExit(error = ProcessRouteTlv(routeTlvData, aRxInfo));
router = mRouterTable.FindRouterById(routerId);
VerifyOrExit(router != nullptr);
if (GetLeaderRloc16() == GetRloc16())
{
SetStateLeader(GetRloc16(), kRestoringLeaderRoleAfterReset);
}
else
{
SetStateRouter(GetRloc16());
}
mRetrieveNewNetworkData = true;
IgnoreError(SendDataRequest(aRxInfo.mMessageInfo.GetPeerAddr()));
shouldUpdateRoutes = true;
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
Get<TimeSync>().HandleTimeSyncMessage(aRxInfo.mMessage);
#endif
break;
case kRoleChild:
VerifyOrExit(router != nullptr);
break;
case kRoleRouter:
case kRoleLeader:
VerifyOrExit(router != nullptr);
SuccessOrExit(error = aRxInfo.mMessage.ReadLeaderDataTlv(leaderData));
VerifyOrExit(leaderData.GetPartitionId() == mLeaderData.GetPartitionId());
if (mRetrieveNewNetworkData ||
SerialNumber::IsGreater(leaderData.GetDataVersion(NetworkData::kFullSet),
Get<NetworkData::Leader>().GetVersion(NetworkData::kFullSet)))
{
IgnoreError(SendDataRequest(aRxInfo.mMessageInfo.GetPeerAddr()));
}
switch (aRxInfo.mMessage.ReadRouteTlv(routeTlvData))
{
case kErrorNone:
VerifyOrExit(routeTlvData.IsAllocated(routerId), error = kErrorParse);
if (mRouterTable.IsRouteTlvIdSequenceMoreRecent(routeTlvData))
{
SuccessOrExit(error = ProcessRouteTlv(routeTlvData, aRxInfo));
router = mRouterTable.FindRouterById(routerId);
OT_ASSERT(router != nullptr);
}
shouldUpdateRoutes = true;
break;
case kErrorNotFound:
break;
default:
ExitNow(error = kErrorParse);
}
if (routerId != mRouterId && !IsRouterIdValid(router->GetNextHop()))
{
ResetAdvertiseInterval();
}
break;
case kRoleDisabled:
OT_ASSERT(false);
}
InitNeighbor(*router, aRxInfo);
router->SetRloc16(sourceAddress);
router->GetLinkFrameCounters().SetAll(linkFrameCounter);
router->SetLinkAckFrameCounter(linkFrameCounter);
router->SetMleFrameCounter(mleFrameCounter);
router->SetVersion(version);
router->SetDeviceMode(DeviceMode(DeviceMode::kModeFullThreadDevice | DeviceMode::kModeRxOnWhenIdle |
DeviceMode::kModeFullNetworkData));
router->SetLinkQualityOut(LinkQualityForLinkMargin(linkMargin));
router->SetState(Neighbor::kStateValid);
router->SetKeySequence(aRxInfo.mKeySequence);
router->ClearLinkAcceptTimeout();
mNeighborTable.Signal(NeighborTable::kRouterAdded, *router);
mDelayedSender.RemoveScheduledLinkRequest(*router);
if (shouldUpdateRoutes)
{
mRouterTable.UpdateRoutes(routeTlvData, routerId);
}
aRxInfo.mClass = RxInfo::kAuthoritativeMessage;
ProcessKeySequence(aRxInfo);
if (aMessageType == kTypeLinkAcceptAndRequest)
{
LinkAcceptInfo info;
SuccessOrExit(error = aRxInfo.mMessage.ReadChallengeTlv(info.mRxChallenge));
switch (aRxInfo.mMessage.ReadTlvRequestTlv(info.mRequestedTlvList))
{
case kErrorNone:
case kErrorNotFound:
break;
default:
ExitNow(error = kErrorParse);
}
info.mExtAddress = router->GetExtAddress();
info.mRloc16 = router->GetRloc16();
info.mLinkMargin = Get<Mac::Mac>().ComputeLinkMargin(aRxInfo.mMessage.GetAverageRss());
SuccessOrExit(error = SendLinkAccept(info));
}
exit:
LogProcessError(aMessageType, error);
}
Error Mle::ProcessRouteTlv(const RouteTlv::Data &aRouteTlvData, RxInfo &aRxInfo)
{
// This method processes `aRouteTlvData` read from an MLE message.
//
// During processing of Route TLV, the entries in the router table
// may shuffle. This method ensures that the `aRxInfo.mNeighbor`
// (which indicates the neighbor from which the MLE message was
// received) is correctly updated to point to the same neighbor
// (in case `mNeighbor` was pointing to a router entry from the
// `RouterTable`).
Error error = kErrorNone;
uint16_t neighborRloc16 = kInvalidRloc16;
if ((aRxInfo.mNeighbor != nullptr) && Get<RouterTable>().Contains(*aRxInfo.mNeighbor))
{
neighborRloc16 = aRxInfo.mNeighbor->GetRloc16();
}
mRouterTable.UpdateRouterIdMask(aRouteTlvData);
if (IsAttached() && !mRouterTable.IsAllocated(RouterIdFromRloc16(GetRloc16())))
{
// Either we're a router and our own router ID has been removed from the router table,
// or we're a child and our parent's router ID has been removed from the router table.
// Detach immediately to minimize time where we're not connected.
IgnoreError(BecomeDetached());
error = kErrorNoRoute;
}
if (neighborRloc16 != kInvalidRloc16)
{
aRxInfo.mNeighbor = Get<NeighborTable>().FindNeighbor(neighborRloc16);
}
return error;
}
Error Mle::ReadAndProcessRouteTlvOnFtdChild(RxInfo &aRxInfo, uint8_t aParentId)
{
// This method reads and processes Route TLV from message on an
// FTD child if message contains one. It returns `kErrorNone`
// when successfully processed or if there is no Route TLV in
// the message.
//
// It MUST be used only when device is acting as a child and
// for a message received from device's current parent.
Error error = kErrorNone;
RouteTlv::Data routeTlvData;
VerifyOrExit(IsFullThreadDevice());
switch (aRxInfo.mMessage.ReadRouteTlv(routeTlvData))
{
case kErrorNone:
SuccessOrExit(error = ProcessRouteTlv(routeTlvData, aRxInfo));
mRouterTable.UpdateRouterOnFtdChild(routeTlvData, aParentId);
mRequestRouteTlv = false;
break;
case kErrorNotFound:
break;
default:
ExitNow(error = kErrorParse);
}
exit:
return error;
}
bool Mle::IsSingleton(void) const
{
bool isSingleton = true;
VerifyOrExit(IsAttached() && IsRouterRoleAllowed());
isSingleton = (mRouterTable.GetActiveRouterCount() <= 1);
exit:
return isSingleton;
}
int Mle::ComparePartitions(bool aSingletonA,
const LeaderData &aLeaderDataA,
bool aSingletonB,
const LeaderData &aLeaderDataB)
{
int rval = 0;
rval = ThreeWayCompare(aLeaderDataA.GetWeighting(), aLeaderDataB.GetWeighting());
VerifyOrExit(rval == 0);
// Not being a singleton is better.
rval = ThreeWayCompare(!aSingletonA, !aSingletonB);
VerifyOrExit(rval == 0);
rval = ThreeWayCompare(aLeaderDataA.GetPartitionId(), aLeaderDataB.GetPartitionId());
exit:
return rval;
}
Error Mle::HandleAdvertisementOnFtd(RxInfo &aRxInfo, uint16_t aSourceAddress, const LeaderData &aLeaderData)
{
// This method processes a received MLE Advertisement message on
// an FTD device. It is called from `Mle::HandleAdvertisement()`
// only when device is attached (in child, router, or leader roles)
// and `IsFullThreadDevice()`.
//
// - `aSourceAddress` is the read value from `SourceAddressTlv`.
// - `aLeaderData` is the read value from `LeaderDataTlv`.
Error error = kErrorNone;
uint8_t linkMargin = Get<Mac::Mac>().ComputeLinkMargin(aRxInfo.mMessage.GetAverageRss());
RouteTlv::Data routeTlvData;
bool hasRouteTlv = false;
Router *router;
uint8_t routerId;
uint32_t delay;
switch (aRxInfo.mMessage.ReadRouteTlv(routeTlvData))
{
case kErrorNone:
hasRouteTlv = true;
break;
case kErrorNotFound:
break;
default:
ExitNow(error = kErrorParse);
}
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Handle Partition ID mismatch
if (aLeaderData.GetPartitionId() != mLeaderData.GetPartitionId())
{
LogNote("Different partition (peer:%lu, local:%lu)", ToUlong(aLeaderData.GetPartitionId()),
ToUlong(mLeaderData.GetPartitionId()));
VerifyOrExit(linkMargin >= kPartitionMergeMinMargin, error = kErrorLinkMarginLow);
if (hasRouteTlv && (mPreviousPartitionIdTimeout > 0) && (aLeaderData.GetPartitionId() == mPreviousPartitionId))
{
VerifyOrExit(
SerialNumber::IsGreater(routeTlvData.GetRouterIdSequence(), mPreviousPartitionRouterIdSequence),
error = kErrorDrop);
}
if (IsChild() && (aRxInfo.mNeighbor == &mParent))
{
ExitNow();
}
if (hasRouteTlv &&
ComparePartitions(routeTlvData.IsSingleton(), aLeaderData, IsSingleton(), mLeaderData) > 0
#if OPENTHREAD_CONFIG_TIME_SYNC_REQUIRED
// Allow a better partition if it also enables time sync.
&& aRxInfo.mMessage.GetTimeSyncSeq() != OT_TIME_SYNC_INVALID_SEQ
#endif
)
{
mAttacher.Attach(kBetterPartition);
}
ExitNow(error = kErrorDrop);
}
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Handle Leader Router ID mismatch
if (aLeaderData.GetLeaderRouterId() != GetLeaderId())
{
VerifyOrExit(aRxInfo.IsNeighborStateValid());
if (!IsChild())
{
LogInfo("Leader ID mismatch");
IgnoreError(BecomeDetached());
error = kErrorDrop;
}
ExitNow();
}
VerifyOrExit(IsRouterRloc16(aSourceAddress) && hasRouteTlv);
routerId = RouterIdFromRloc16(aSourceAddress);
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
Get<TimeSync>().HandleTimeSyncMessage(aRxInfo.mMessage);
#endif
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Process `RouteTlv`
if (aRxInfo.IsNeighborStateValid() && mRouterTable.IsRouteTlvIdSequenceMoreRecent(routeTlvData))
{
SuccessOrExit(error = ProcessRouteTlv(routeTlvData, aRxInfo));
}
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Update routers as a child
if (IsChild())
{
if (aRxInfo.mNeighbor == &mParent)
{
// MLE Advertisement from parent
router = &mParent;
if (mParent.GetRloc16() != aSourceAddress)
{
IgnoreError(BecomeDetached());
ExitNow(error = kErrorDetached);
}
mRouterTable.UpdateRouterOnFtdChild(routeTlvData, routerId);
mRoleTransitioner.DecideWhetherToUpgrade();
}
else
{
// MLE Advertisement not from parent, but from some other neighboring router
router = mRouterTable.FindRouterById(routerId);
VerifyOrExit(router != nullptr);
EstablishRouterLinkOnFtdChild(*router, aRxInfo, linkMargin);
}
#if OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE
router->SetSelectableAsParent(true);
#endif
router->SetLastHeard(TimerMilli::GetNow());
ExitNow();
}
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Inform `RoleTransitioner` to decide whether we need to downgrade
mRoleTransitioner.DecideWhetherToDowngrade(routerId, routeTlvData);
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Update routers as a router or leader.
router = mRouterTable.FindRouterById(routerId);
VerifyOrExit(router != nullptr);
if (!router->IsStateValid() && aRxInfo.IsNeighborStateValid() && Get<ChildTable>().Contains(*aRxInfo.mNeighbor))
{
// The Adv is from a former child that is now acting as a router,
// we copy the info from child entry and update the RLOC16.
*static_cast<Neighbor *>(router) = *aRxInfo.mNeighbor;
router->SetRloc16(Rloc16FromRouterId(routerId));
router->SetDeviceMode(DeviceMode(DeviceMode::kModeFullThreadDevice | DeviceMode::kModeRxOnWhenIdle |
DeviceMode::kModeFullNetworkData));
mNeighborTable.Signal(NeighborTable::kRouterAdded, *router);
// Change the cache entries associated with the former child
// from using the old RLOC16 to its new RLOC16.
Get<AddressResolver>().ReplaceEntriesForRloc16(aRxInfo.mNeighbor->GetRloc16(), router->GetRloc16());
}
// Send unicast link request if no link to router and no
// unicast/multicast link request in progress
if (!router->IsStateValid() && !router->IsStateLinkRequest() && (linkMargin >= kLinkRequestMinMargin) &&
routeTlvData.IsAllocated(mRouterId))
{
InitNeighbor(*router, aRxInfo);
router->SetState(Neighbor::kStateLinkRequest);
router->ClearLinkAcceptTimeout();
delay = GenerateRandomDelay(kMaxLinkRequestDelayOnRouter);
mDelayedSender.ScheduleLinkRequest(*router, delay);
ExitNow(error = kErrorNoRoute);
}
router->SetLastHeard(TimerMilli::GetNow());
mRouterTable.UpdateRoutes(routeTlvData, routerId);
exit:
if (aRxInfo.mNeighbor && aRxInfo.mNeighbor->GetRloc16() != aSourceAddress)
{
RemoveNeighbor(*aRxInfo.mNeighbor);
}
return error;
}
void Mle::EstablishRouterLinkOnFtdChild(Router &aRouter, RxInfo &aRxInfo, uint8_t aLinkMargin)
{
// Decide on an FTD child whether to establish a link with a
// router upon receiving an advertisement from it.
uint8_t neighborCount;
uint32_t minDelay;
uint32_t maxDelay;
VerifyOrExit(!aRouter.IsStateValid() && !aRouter.IsStateLinkRequest());
// The first `mChildRouterLinks` are established quickly. After that,
// the "gradual router link establishment" mechanism is used, which
// allows `kExtraChildRouterLinks` additional router links to be
// established, but it is done slowly and over a longer span of time.
//
// Gradual router link establishment conditions:
// - The maximum `neighborCount` limit is not yet reached.
// - We see Link Quality 2 or better.
// - Always skipped in the first 5 minutes
// (`kWaitDurationAfterAttach`) after the device attaches.
// - The child randomly decides whether to perform/skip this (with a 5%
// probability, `kProbabilityPercentage`).
// - If the child decides to send Link Request, a longer random delay
// window is used, [1.5-10] seconds.
//
// Even in a dense network, if the advertisement is received by 500 FTD
// children with a 5% selection probability, on average, 25 nodes will
// try to send a Link Request, which will be randomly spread over a
// [1.5-10] second window.
//
// With a 5% probability, on average, it takes 20 trials (20 advertisement
// receptions for an FTD child to send a Link Request). Advertisements
// are, on average, ~32 seconds apart, so, on average, a child will try
// to establish a link in `20 * 32 = 640` seconds (~10 minutes).
neighborCount = mRouterTable.GetNeighborCount(kLinkQuality1);
if (neighborCount < mChildRouterLinks)
{
minDelay = kMinLinkRequestDelayOnChild;
maxDelay = kMaxLinkRequestDelayOnChild;
}
else
{
VerifyOrExit(neighborCount < mChildRouterLinks + GradualChildRouterLink::kExtraChildRouterLinks);
VerifyOrExit(LinkQualityForLinkMargin(aLinkMargin) >= kLinkQuality2);
VerifyOrExit(GetCurrentAttachDuration() > GradualChildRouterLink::kWaitDurationAfterAttach);
VerifyOrExit(Random::NonCrypto::GenerateUpToExcluding<uint8_t>(100) <
GradualChildRouterLink::kProbabilityPercentage);
minDelay = GradualChildRouterLink::kMinLinkRequestDelay;
maxDelay = GradualChildRouterLink::kMaxLinkRequestDelay;
}
InitNeighbor(aRouter, aRxInfo);
aRouter.SetState(Neighbor::kStateLinkRequest);
aRouter.ClearLinkAcceptTimeout();
mDelayedSender.ScheduleLinkRequest(aRouter, Random::NonCrypto::GenerateInClosedRange(minDelay, maxDelay));
exit:
return;
}
void Mle::HandleParentRequest(RxInfo &aRxInfo)
{
Error error = kErrorNone;
uint16_t version;
uint8_t scanMask;
Child *child;
DeviceMode mode;
uint32_t delay;
ParentResponseInfo info;
Log(kMessageReceive, kTypeParentRequest, aRxInfo.mMessageInfo.GetPeerAddr());
VerifyOrExit(IsRouterRoleAllowed());
VerifyOrExit(!IsDetached() && !IsAttaching());
VerifyOrExit(!mDetacher.IsDetaching());
VerifyOrExit(mRouterTable.GetLeaderAge() < mNetworkIdTimeout, error = kErrorDrop);
VerifyOrExit(mRouterTable.GetPathCostToLeader() < kMaxRouteCost, error = kErrorDrop);
info.mChildExtAddress.SetFromIid(aRxInfo.mMessageInfo.GetPeerAddr().GetIid());
SuccessOrExit(error = aRxInfo.mMessage.ReadVersionTlv(version));
SuccessOrExit(error = Tlv::Find<ScanMaskTlv>(aRxInfo.mMessage, scanMask));
switch (mRole)
{
case kRoleDisabled:
case kRoleDetached:
ExitNow();
case kRoleChild:
VerifyOrExit(ScanMaskTlv::IsEndDeviceFlagSet(scanMask));
VerifyOrExit(mRouterTable.GetActiveRouterCount() < kMaxRouters, error = kErrorDrop);
break;
case kRoleRouter:
case kRoleLeader:
VerifyOrExit(ScanMaskTlv::IsRouterFlagSet(scanMask));
break;
}
SuccessOrExit(error = aRxInfo.mMessage.ReadChallengeTlv(info.mRxChallenge));
child = mChildTable.FindChild(info.mChildExtAddress, Child::kInStateAnyExceptInvalid);
if (child == nullptr)
{
VerifyOrExit((child = mChildTable.GetNewChild()) != nullptr, error = kErrorNoBufs);
InitNeighbor(*child, aRxInfo);
child->SetState(Neighbor::kStateParentRequest);
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
child->SetTimeSyncEnabled(Tlv::Find<TimeRequestTlv>(aRxInfo.mMessage, nullptr, 0) == kErrorNone);
#endif
if (aRxInfo.mMessage.ReadModeTlv(mode) == kErrorNone)
{
child->SetDeviceMode(mode);
child->SetVersion(version);
}
}
else
{
uint32_t durSinceLastHeard = TimerMilli::GetNow() - child->GetLastHeard();
VerifyOrExit(durSinceLastHeard >= kParentRequestDuplicateTimeout, error = kErrorDuplicated);
}
if (!child->IsStateValidOrRestoring())
{
child->SetLastHeard(TimerMilli::GetNow());
child->SetTimeout(Time::MsecToSec(kChildIdRequestTimeout));
}
aRxInfo.mClass = RxInfo::kPeerMessage;
ProcessKeySequence(aRxInfo);
delay = GenerateRandomDelay(!ScanMaskTlv::IsEndDeviceFlagSet(scanMask) ? kParentResponseMaxDelayRouters
: kParentResponseMaxDelayAll);
mDelayedSender.ScheduleParentResponse(info, delay);
exit:
LogProcessError(kTypeParentRequest, error);
}
bool Mle::HasNeighborWithGoodLinkQuality(void) const
{
bool haveNeighbor = true;
uint8_t linkMargin;
linkMargin = Get<Mac::Mac>().ComputeLinkMargin(mParent.GetLinkInfo().GetLastRss());
if (linkMargin >= kLinkRequestMinMargin)
{
ExitNow();
}
for (const Router &router : Get<RouterTable>())
{
if (!router.IsStateValid())
{
continue;
}
linkMargin = Get<Mac::Mac>().ComputeLinkMargin(router.GetLinkInfo().GetLastRss());
if (linkMargin >= kLinkRequestMinMargin)
{
ExitNow();
}
}
haveNeighbor = false;
exit:
return haveNeighbor;
}
void Mle::HandleTimeTick(void)
{
VerifyOrExit(IsFullThreadDevice(), Get<TimeTicker>().UnregisterReceiver(TimeTicker::kMle));
if (mPreviousPartitionIdTimeout > 0)
{
mPreviousPartitionIdTimeout--;
}
if (mAlternateRloc16Timeout > 0)
{
mAlternateRloc16Timeout--;
if (mAlternateRloc16Timeout == 0)
{
ClearAlternateRloc16();
}
}
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Role transitions
mRoleTransitioner.HandleTimeTick();
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Check Leader's age
switch (mRole)
{
case kRoleDisabled:
case kRoleDetached:
case kRoleLeader:
break;
case kRoleChild:
if (!IsRouterRoleAllowed())
{
break;
}
OT_FALL_THROUGH;
case kRoleRouter:
LogDebg("Leader age %lu", ToUlong(mRouterTable.GetLeaderAge()));
if ((mRouterTable.GetActiveRouterCount() > 0) && (mRouterTable.GetLeaderAge() >= mNetworkIdTimeout))
{
LogInfo("Leader age timeout");
mAttacher.Attach(kSamePartition);
}
break;
}
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Update `ChildTable`
for (Child &child : Get<ChildTable>().Iterate(Child::kInStateAnyExceptInvalid))
{
uint32_t timeout = 0;
switch (child.GetState())
{
case Neighbor::kStateInvalid:
continue;
case Neighbor::kStateParentRequest:
case Neighbor::kStateChildIdRequest:
case Neighbor::kStateValid:
case Neighbor::kStateRestored:
case Neighbor::kStateChildUpdateRequest:
timeout = Time::SecToMsec(child.GetTimeout());
break;
case Neighbor::kStateParentResponse:
case Neighbor::kStateLinkRequest:
OT_ASSERT(false);
}
#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
if (child.IsCslSynchronized() &&
TimerMilli::GetNow() - child.GetCslLastHeard() >= Time::SecToMsec(child.GetCslTimeout()))
{
LogInfo("Child 0x%04x CSL synchronization expired", child.GetRloc16());
child.SetCslSynchronized(false);
Get<CslTxScheduler>().Update();
}
#endif
if (TimerMilli::GetNow() - child.GetLastHeard() >= timeout)
{
LogInfo("Child 0x%04x timeout expired", child.GetRloc16());
RemoveNeighbor(child);
}
else if (IsRouterOrLeader() && child.IsStateRestored())
{
IgnoreError(SendChildUpdateRequestToChild(child));
}
}
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Update `RouterTable`
for (Router &router : Get<RouterTable>())
{
uint32_t age;
if (router.GetRloc16() == GetRloc16())
{
router.SetLastHeard(TimerMilli::GetNow());
continue;
}
age = TimerMilli::GetNow() - router.GetLastHeard();
#if OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE
router.DecrementParentReselectTimeout();
if (age >= kMaxNeighborAge)
{
router.SetSelectableAsParent(false);
}
#endif
if (router.IsStateValid())
{
// Neighbor router age and link recovery
//
// If the device is an FTD child and has more than
// `mChildRouterLinks` neighbors, it uses a longer age,
// `kMaxNeighborAgeOnChild`, and removes the neighboring
// router upon expiration without trying to re-establish
// its link with it.
//
// Otherwise, if the device itself is a router, or it is an
// FTD child with `mChildRouterLinks` or fewer neighbors,
// it uses a shorter `kMaxNeighborAge`. Upon expiration, it
// tries to re-establish its link with the neighboring router.
if (IsChild() && (mRouterTable.GetNeighborCount(kLinkQuality1) > mChildRouterLinks))
{
if (age >= kMaxNeighborAgeOnChild)
{
LogInfo("No Adv from router 0x%04x - removing router", router.GetRloc16());
mDelayedSender.RemoveScheduledLinkRequest(router);
RemoveNeighbor(router);
continue;
}
}
else if (age >= kMaxNeighborAge)
{
// We send a Link Request every time tick (second), up to
// max attempts. Each transmission is randomly delayed
// (one-second window). After the last attempt, we wait for
// the "Link Accept" timeout (~3 seconds) before removing the
// neighboring router.
if (!mDelayedSender.HasAnyScheduledLinkRequest(router) && !router.IsWaitingForLinkAccept())
{
LogInfo("No Adv from router 0x%04x - sending Link Request", router.GetRloc16());
router.SetLinkRequestAttemptsToMax();
}
if (router.HasRemainingLinkRequestAttempts())
{
router.DecrementLinkRequestAttempts();
mDelayedSender.ScheduleLinkRequest(router, GenerateRandomDelay(kMaxLinkRequestDelayOnRouter));
}
}
}
if (router.IsStateLinkRequest() && !mDelayedSender.HasAnyScheduledLinkRequest(router) &&
!router.IsWaitingForLinkAccept())
{
LogInfo("Router 0x%04x - Failed to schedule/send Link Request", router.GetRloc16());
RemoveNeighbor(router);
continue;
}
if (router.IsWaitingForLinkAccept() && (router.DecrementLinkAcceptTimeout() == 0))
{
LogInfo("Router 0x%04x - Link Accept timeout expired", router.GetRloc16());
RemoveNeighbor(router);
continue;
}
if (IsLeader() && (mRouterTable.FindNextHopTowards(router) == nullptr) &&
(mRouterTable.GetLinkCost(router) >= kMaxRouteCost) && (age >= kMaxLeaderToRouterTimeout))
{
LogInfo("Router 0x%04x ID timeout expired (no route)", router.GetRloc16());
IgnoreError(mRouterTable.Release(router.GetRouterId()));
}
}
mRouterTable.HandleTimeTick();
SynchronizeChildNetworkData();
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
if (IsRouterOrLeader())
{
Get<TimeSync>().ProcessTimeSync();
}
#endif
exit:
return;
}
void Mle::SendParentResponse(const ParentResponseInfo &aInfo)
{
Error error = kErrorNone;
TxMessage *message = nullptr;
Child *child;
Ip6::Address destination;
child = mChildTable.FindChild(aInfo.mChildExtAddress, Child::kInStateAnyExceptInvalid);
VerifyOrExit(child != nullptr);
VerifyOrExit((message = NewMleMessage(kCommandParentResponse)) != nullptr, error = kErrorNoBufs);
message->SetDirectTransmission();
SuccessOrExit(error = message->AppendSourceAddressAndLeaderDataTlvs());
SuccessOrExit(error = message->AppendLinkAndMleFrameCounterTlvs());
SuccessOrExit(error = message->AppendResponseTlv(aInfo.mRxChallenge));
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
if (child->IsTimeSyncEnabled())
{
SuccessOrExit(error = message->AppendTimeParameterTlv());
}
#endif
#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
if (child->IsThreadVersionCslCapable())
{
SuccessOrExit(error = message->AppendCslClockAccuracyTlv());
}
#endif
child->GenerateChallenge();
SuccessOrExit(error = message->AppendChallengeTlv(child->GetChallenge()));
SuccessOrExit(error = message->AppendLinkMarginTlv(child->GetLinkInfo().GetLinkMargin()));
SuccessOrExit(error = message->AppendConnectivityTlv());
SuccessOrExit(error = message->AppendVersionTlv());
destination.InitAsLinkLocalAddress(aInfo.mChildExtAddress);
SuccessOrExit(error = message->SendTo(destination));
Log(kMessageSend, kTypeParentResponse, destination);
exit:
FreeMessageOnError(message, error);
LogSendError(kTypeParentResponse, error);
}
Error Mle::ProcessAddressRegistrationTlv(RxInfo &aRxInfo, Child &aChild)
{
Error error;
OffsetRange offsetRange;
uint8_t count = 0;
uint8_t storedCount = 0;
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
Child::Ip6AddressArray oldMlrRegisteredAddresses;
#endif
OT_UNUSED_VARIABLE(storedCount);
SuccessOrExit(error = Tlv::FindTlvValueOffsetRange(aRxInfo.mMessage, Tlv::kAddressRegistration, offsetRange));
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
aChild.GetAllMlrRegisteredAddresses(oldMlrRegisteredAddresses);
#endif
aChild.ClearIp6Addresses();
while (!offsetRange.IsEmpty())
{
uint8_t controlByte;
Ip6::Address address;
// Read out the control byte (first byte in entry)
SuccessOrExit(error = aRxInfo.mMessage.ReadAndAdvance(offsetRange, controlByte));
count++;
address.Clear();
if (AddressRegistrationTlv::IsEntryCompressed(controlByte))
{
// Compressed entry contains IID with the 64-bit prefix
// determined from 6LoWPAN context identifier (from
// the control byte).
uint8_t contextId = AddressRegistrationTlv::GetContextId(controlByte);
Lowpan::Context context;
IgnoreError(aRxInfo.mMessage.ReadAndAdvance(offsetRange, address.GetIid()));
Get<NetworkData::Leader>().FindContextForId(contextId, context);
if (!context.IsValid())
{
LogWarn("Failed to get context %u for compressed address from child 0x%04x", contextId,
aChild.GetRloc16());
continue;
}
address.SetPrefix(context.GetPrefix());
}
else
{
// Uncompressed entry contains the full IPv6 address.
IgnoreError(aRxInfo.mMessage.ReadAndAdvance(offsetRange, address));
}
error = aChild.AddIp6Address(address);
if (error == kErrorNone)
{
storedCount++;
LogInfo("Child 0x%04x IPv6 address[%u]=%s", aChild.GetRloc16(), storedCount,
address.ToString().AsCString());
}
else
{
LogWarnOnError(error, "add IPv6 address %s to child 0x%04x", address.ToString().AsCString(),
aChild.GetRloc16());
}
if (address.IsMulticast())
{
continue;
}
// We check if the same address is in-use by another child, if so
// remove it. This implements "last-in wins" duplicate address
// resolution policy.
//
// Duplicate addresses can occur if a previously attached child
// attaches to same parent again (after a reset, memory wipe) using
// a new random extended address before the old entry in the child
// table is timed out and then trying to register its globally unique
// IPv6 address as the new child.
for (Child &child : Get<ChildTable>().Iterate(Child::kInStateValidOrRestoring))
{
if (&child == &aChild)
{
continue;
}
IgnoreError(child.RemoveIp6Address(address));
}
// Clear EID-to-RLOC cache for the unicast address registered by the child.
Get<AddressResolver>().RemoveEntryForAddress(address);
}
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
Get<Mlr::Manager>().UpdateChildRegistrations(aChild, oldMlrRegisteredAddresses);
#endif
if (count == 0)
{
LogInfo("Child 0x%04x has no registered IPv6 address", aChild.GetRloc16());
}
else
{
LogInfo("Child 0x%04x has %u registered IPv6 address%s, %u address%s stored", aChild.GetRloc16(), count,
(count == 1) ? "" : "es", storedCount, (storedCount == 1) ? "" : "es");
}
error = kErrorNone;
exit:
return error;
}
bool Mle::IsMessageMleSubType(const Message &aMessage) { return aMessage.IsSubTypeMle(); }
bool Mle::IsMessageChildUpdateRequest(const Message &aMessage)
{
return aMessage.IsMleCommand(kCommandChildUpdateRequest);
}
void Mle::HandleChildIdRequest(RxInfo &aRxInfo)
{
Error error = kErrorNone;
Mac::ExtAddress extAddr;
uint16_t version;
uint32_t linkFrameCounter;
uint32_t mleFrameCounter;
DeviceMode mode;
uint32_t timeout;
TlvList tlvList;
MeshCoP::Timestamp timestamp;
Child *child;
Router *router;
uint16_t supervisionInterval;
Log(kMessageReceive, kTypeChildIdRequest, aRxInfo.mMessageInfo.GetPeerAddr());
VerifyOrExit(IsRouterRoleAllowed(), error = kErrorInvalidState);
VerifyOrExit(IsAttached(), error = kErrorInvalidState);
extAddr.SetFromIid(aRxInfo.mMessageInfo.GetPeerAddr().GetIid());
child = mChildTable.FindChild(extAddr, Child::kInStateAnyExceptInvalid);
VerifyOrExit(child != nullptr, error = kErrorAlready);
SuccessOrExit(error = aRxInfo.mMessage.ReadVersionTlv(version));
SuccessOrExit(error = aRxInfo.mMessage.ReadAndMatchResponseTlvWith(child->GetChallenge()));
Get<MeshForwarder>().RemoveMessagesForChild(*child, IsMessageMleSubType);
SuccessOrExit(error = aRxInfo.mMessage.ReadFrameCounterTlvs(linkFrameCounter, mleFrameCounter));
SuccessOrExit(error = aRxInfo.mMessage.ReadModeTlv(mode));
SuccessOrExit(error = Tlv::Find<TimeoutTlv>(aRxInfo.mMessage, timeout));
SuccessOrExit(error = aRxInfo.mMessage.ReadTlvRequestTlv(tlvList));
switch (Tlv::Find<SupervisionIntervalTlv>(aRxInfo.mMessage, supervisionInterval))
{
case kErrorNone:
tlvList.Add(Tlv::kSupervisionInterval);
break;
case kErrorNotFound:
supervisionInterval = (version <= kThreadVersion1p3) ? kChildSupervisionDefaultIntervalForOlderVersion : 0;
break;
default:
ExitNow(error = kErrorParse);
}
switch (Tlv::Find<ActiveTimestampTlv>(aRxInfo.mMessage, timestamp))
{
case kErrorNone:
if (timestamp == Get<MeshCoP::ActiveDatasetManager>().GetTimestamp())
{
break;
}
OT_FALL_THROUGH;
case kErrorNotFound:
tlvList.Add(Tlv::kActiveDataset);
break;
default:
ExitNow(error = kErrorParse);
}
switch (Tlv::Find<PendingTimestampTlv>(aRxInfo.mMessage, timestamp))
{
case kErrorNone:
if (timestamp == Get<MeshCoP::PendingDatasetManager>().GetTimestamp())
{
break;
}
OT_FALL_THROUGH;
case kErrorNotFound:
tlvList.Add(Tlv::kPendingDataset);
break;
default:
ExitNow(error = kErrorParse);
}
VerifyOrExit(tlvList.GetLength() <= Child::kMaxRequestTlvs, error = kErrorParse);
if (!mode.IsFullThreadDevice())
{
SuccessOrExit(error = ProcessAddressRegistrationTlv(aRxInfo, *child));
}
router = mRouterTable.FindRouter(extAddr);
if (router != nullptr)
{
RemoveNeighbor(*router);
}
if (!child->IsStateValid())
{
child->SetState(Neighbor::kStateChildIdRequest);
}
else
{
RemoveNeighbor(*child);
}
child->SetLastHeard(TimerMilli::GetNow());
child->GetLinkFrameCounters().SetAll(linkFrameCounter);
child->SetLinkAckFrameCounter(linkFrameCounter);
child->SetMleFrameCounter(mleFrameCounter);
child->SetKeySequence(aRxInfo.mKeySequence);
child->SetDeviceMode(mode);
child->SetVersion(version);
child->GetLinkInfo().AddRss(aRxInfo.mMessage.GetAverageRss());
child->SetTimeout(timeout);
child->SetSupervisionInterval(supervisionInterval);
#if OPENTHREAD_CONFIG_MULTI_RADIO
child->ClearLastRxFragmentTag();
#endif
child->SetNetworkDataVersion(mLeaderData.GetDataVersion(mode.GetNetworkDataType()));
// We already checked above that `tlvList` will fit in
// `child` entry (with `Child::kMaxRequestTlvs` TLVs).
child->ClearRequestTlvs();
for (uint8_t index = 0; index < tlvList.GetLength(); index++)
{
child->SetRequestTlv(index, tlvList[index]);
}
aRxInfo.mClass = RxInfo::kAuthoritativeMessage;
ProcessKeySequence(aRxInfo);
switch (mRole)
{
case kRoleChild:
child->SetState(Neighbor::kStateChildIdRequest);
IgnoreError(BecomeRouter(kReasonHaveChildIdRequest));
break;
case kRoleRouter:
case kRoleLeader:
SuccessOrExit(error = SendChildIdResponse(*child));
break;
case kRoleDisabled:
case kRoleDetached:
OT_ASSERT(false);
}
exit:
LogProcessError(kTypeChildIdRequest, error);
}
void Mle::HandleChildUpdateRequestOnParent(RxInfo &aRxInfo)
{
Error error = kErrorNone;
Mac::ExtAddress extAddr;
DeviceMode mode;
LeaderData leaderData;
uint32_t timeout;
uint16_t supervisionInterval;
Child *child;
DeviceMode oldMode;
TlvList requestedTlvList;
ChildUpdateResponseInfo info;
bool childDidChange = false;
Log(kMessageReceive, kTypeChildUpdateRequestOfChild, aRxInfo.mMessageInfo.GetPeerAddr());
VerifyOrExit(!mDetacher.IsDetaching());
info.mDestination = aRxInfo.mMessageInfo.GetPeerAddr();
SuccessOrExit(error = aRxInfo.mMessage.ReadModeTlv(mode));
switch (aRxInfo.mMessage.ReadChallengeTlv(info.mChallenge))
{
case kErrorNone:
info.mTlvList.Add(Tlv::kResponse);
break;
case kErrorNotFound:
info.mChallenge.Clear();
break;
default:
ExitNow(error = kErrorParse);
}
info.mTlvList.Add(Tlv::kSourceAddress);
extAddr.SetFromIid(aRxInfo.mMessageInfo.GetPeerAddr().GetIid());
child = mChildTable.FindChild(extAddr, Child::kInStateAnyExceptInvalid);
if (child == nullptr)
{
// For invalid non-sleepy child, send Child Update Response with
// Status TLV (error).
if (mode.IsRxOnWhenIdle())
{
IgnoreError(SendChildUpdateRejectResponse(info));
}
ExitNow();
}
// Ignore "Child Update Request" from a child that is present in the
// child table but it is not yet in valid state. For example, a
// child which is being restored (due to parent reset) or is in the
// middle of the attach process (in `kStateParentRequest` or
// `kStateChildIdRequest`).
VerifyOrExit(child->IsStateValid());
oldMode = child->GetDeviceMode();
child->SetDeviceMode(mode);
info.mTlvList.Add(Tlv::kMode);
info.mTlvList.Add(Tlv::kLinkMargin);
// Parent MUST include Leader Data TLV in Child Update Response
info.mTlvList.Add(Tlv::kLeaderData);
if (!info.mChallenge.IsEmpty())
{
info.mTlvList.Add(Tlv::kMleFrameCounter);
info.mTlvList.Add(Tlv::kLinkFrameCounter);
}
switch (ProcessAddressRegistrationTlv(aRxInfo, *child))
{
case kErrorNone:
info.mTlvList.Add(Tlv::kAddressRegistration);
break;
case kErrorNotFound:
break;
default:
ExitNow(error = kErrorParse);
}
switch (aRxInfo.mMessage.ReadLeaderDataTlv(leaderData))
{
case kErrorNone:
child->SetNetworkDataVersion(leaderData.GetDataVersion(child->GetNetworkDataType()));
break;
case kErrorNotFound:
break;
default:
ExitNow(error = kErrorParse);
}
switch (Tlv::Find<TimeoutTlv>(aRxInfo.mMessage, timeout))
{
case kErrorNone:
if (child->GetTimeout() != timeout)
{
child->SetTimeout(timeout);
childDidChange = true;
}
info.mTlvList.Add(Tlv::kTimeout);
break;
case kErrorNotFound:
break;
default:
ExitNow(error = kErrorParse);
}
switch (Tlv::Find<SupervisionIntervalTlv>(aRxInfo.mMessage, supervisionInterval))
{
case kErrorNone:
info.mTlvList.Add(Tlv::kSupervisionInterval);
break;
case kErrorNotFound:
supervisionInterval =
(child->GetVersion() <= kThreadVersion1p3) ? kChildSupervisionDefaultIntervalForOlderVersion : 0;
break;
default:
ExitNow(error = kErrorParse);
}
child->SetSupervisionInterval(supervisionInterval);
switch (aRxInfo.mMessage.ReadTlvRequestTlv(requestedTlvList))
{
case kErrorNone:
info.mTlvList.AddElementsFrom(requestedTlvList);
break;
case kErrorNotFound:
break;
default:
ExitNow(error = kErrorParse);
}
#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
if (child->IsCslSynchronized())
{
ChannelTlvValue cslChannelTlvValue;
uint32_t cslTimeout;
switch (Tlv::Find<CslTimeoutTlv>(aRxInfo.mMessage, cslTimeout))
{
case kErrorNone:
child->SetCslTimeout(cslTimeout);
// MUST include CSL accuracy TLV when request includes CSL timeout
info.mTlvList.Add(Tlv::kCslClockAccuracy);
break;
case kErrorNotFound:
break;
default:
ExitNow(error = kErrorNone);
}
if (Tlv::Find<CslChannelTlv>(aRxInfo.mMessage, cslChannelTlvValue) == kErrorNone)
{
// Special value of zero is used to indicate that
// CSL channel is not specified.
child->SetCslChannel(static_cast<uint8_t>(cslChannelTlvValue.GetChannel()));
}
}
#endif // OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
child->SetLastHeard(TimerMilli::GetNow());
if (oldMode != child->GetDeviceMode())
{
LogNote("Child 0x%04x mode change 0x%02x -> 0x%02x [%s]", child->GetRloc16(), oldMode.Get(),
child->GetDeviceMode().Get(), child->GetDeviceMode().ToString().AsCString());
childDidChange = true;
#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
if (child->IsRxOnWhenIdle())
{
// Clear CSL synchronization state
child->SetCslSynchronized(false);
}
#endif
// The `IndirectSender::HandleChildModeChange()` needs to happen
// after "Child Update" message is fully parsed to ensure that
// any registered IPv6 addresses included in the "Child Update"
// are added to the child.
Get<IndirectSender>().HandleChildModeChange(*child, oldMode);
}
if (childDidChange)
{
IgnoreError(mChildTable.StoreChild(*child));
}
#if OPENTHREAD_CONFIG_MULTI_RADIO
// We clear the fragment tag only if the "Child Update Request" is
// from a detached child trying to restore its link with its
// parent which is indicated by the presence of Challenge TLV in
// the message.
if (!info.mChallenge.IsEmpty())
{
child->ClearLastRxFragmentTag();
}
#endif
SendChildUpdateResponseToChild(child, info);
aRxInfo.mClass = RxInfo::kPeerMessage;
exit:
LogProcessError(kTypeChildUpdateRequestOfChild, error);
}
void Mle::HandleChildUpdateResponseOnParent(RxInfo &aRxInfo)
{
Error error = kErrorNone;
uint16_t sourceAddress;
uint32_t timeout;
RxChallenge response;
uint8_t status;
uint32_t linkFrameCounter;
uint32_t mleFrameCounter;
LeaderData leaderData;
Child *child;
if ((aRxInfo.mNeighbor == nullptr) || IsRouterRloc16(aRxInfo.mNeighbor->GetRloc16()) ||
!Get<ChildTable>().Contains(*aRxInfo.mNeighbor))
{
Log(kMessageReceive, kTypeChildUpdateResponseOfUnknownChild, aRxInfo.mMessageInfo.GetPeerAddr());
ExitNow(error = kErrorNotFound);
}
child = static_cast<Child *>(aRxInfo.mNeighbor);
switch (aRxInfo.mMessage.ReadResponseTlv(response))
{
case kErrorNone:
VerifyOrExit(response == child->GetChallenge(), error = kErrorSecurity);
break;
case kErrorNotFound:
VerifyOrExit(child->IsStateValid(), error = kErrorSecurity);
response.Clear();
break;
default:
ExitNow(error = kErrorNone);
}
Log(kMessageReceive, kTypeChildUpdateResponseOfChild, aRxInfo.mMessageInfo.GetPeerAddr(), child->GetRloc16());
switch (Tlv::Find<SourceAddressTlv>(aRxInfo.mMessage, sourceAddress))
{
case kErrorNone:
if (child->GetRloc16() != sourceAddress)
{
RemoveNeighbor(*child);
ExitNow();
}
break;
case kErrorNotFound:
break;
default:
ExitNow(error = kErrorParse);
}
switch (Tlv::Find<StatusTlv>(aRxInfo.mMessage, status))
{
case kErrorNone:
VerifyOrExit(status != kStatusError, RemoveNeighbor(*child));
break;
case kErrorNotFound:
break;
default:
ExitNow(error = kErrorParse);
}
switch (Tlv::Find<LinkFrameCounterTlv>(aRxInfo.mMessage, linkFrameCounter))
{
case kErrorNone:
child->GetLinkFrameCounters().SetAll(linkFrameCounter);
child->SetLinkAckFrameCounter(linkFrameCounter);
break;
case kErrorNotFound:
break;
default:
ExitNow(error = kErrorParse);
}
switch (Tlv::Find<MleFrameCounterTlv>(aRxInfo.mMessage, mleFrameCounter))
{
case kErrorNone:
child->SetMleFrameCounter(mleFrameCounter);
break;
case kErrorNotFound:
break;
default:
ExitNow(error = kErrorNone);
}
switch (Tlv::Find<TimeoutTlv>(aRxInfo.mMessage, timeout))
{
case kErrorNone:
child->SetTimeout(timeout);
break;
case kErrorNotFound:
break;
default:
ExitNow(error = kErrorParse);
}
{
uint16_t supervisionInterval;
switch (Tlv::Find<SupervisionIntervalTlv>(aRxInfo.mMessage, supervisionInterval))
{
case kErrorNone:
child->SetSupervisionInterval(supervisionInterval);
break;
case kErrorNotFound:
break;
default:
ExitNow(error = kErrorParse);
}
}
switch (ProcessAddressRegistrationTlv(aRxInfo, *child))
{
case kErrorNone:
case kErrorNotFound:
break;
default:
ExitNow(error = kErrorParse);
}
switch (aRxInfo.mMessage.ReadLeaderDataTlv(leaderData))
{
case kErrorNone:
child->SetNetworkDataVersion(leaderData.GetDataVersion(child->GetNetworkDataType()));
break;
case kErrorNotFound:
break;
default:
ExitNow(error = kErrorParse);
}
SetChildStateToValid(*child);
child->SetLastHeard(TimerMilli::GetNow());
child->SetKeySequence(aRxInfo.mKeySequence);
child->GetLinkInfo().AddRss(aRxInfo.mMessage.GetAverageRss());
aRxInfo.mClass = response.IsEmpty() ? RxInfo::kPeerMessage : RxInfo::kAuthoritativeMessage;
exit:
LogProcessError(kTypeChildUpdateResponseOfChild, error);
}
void Mle::HandleDataRequest(RxInfo &aRxInfo)
{
Error error = kErrorNone;
TlvList tlvList;
MeshCoP::Timestamp timestamp;
Log(kMessageReceive, kTypeDataRequest, aRxInfo.mMessageInfo.GetPeerAddr());
VerifyOrExit(aRxInfo.IsNeighborStateValid(), error = kErrorSecurity);
SuccessOrExit(error = aRxInfo.mMessage.ReadTlvRequestTlv(tlvList));
switch (Tlv::Find<ActiveTimestampTlv>(aRxInfo.mMessage, timestamp))
{
case kErrorNone:
if (timestamp == Get<MeshCoP::ActiveDatasetManager>().GetTimestamp())
{
break;
}
OT_FALL_THROUGH;
case kErrorNotFound:
tlvList.Add(Tlv::kActiveDataset);
break;
default:
ExitNow(error = kErrorParse);
}
switch (Tlv::Find<PendingTimestampTlv>(aRxInfo.mMessage, timestamp))
{
case kErrorNone:
if (timestamp == Get<MeshCoP::PendingDatasetManager>().GetTimestamp())
{
break;
}
OT_FALL_THROUGH;
case kErrorNotFound:
tlvList.Add(Tlv::kPendingDataset);
break;
default:
ExitNow(error = kErrorParse);
}
aRxInfo.mClass = RxInfo::kPeerMessage;
ProcessKeySequence(aRxInfo);
SendDataResponse(aRxInfo.mMessageInfo.GetPeerAddr(), tlvList, &aRxInfo.mMessage);
exit:
LogProcessError(kTypeDataRequest, error);
}
void Mle::HandleNetworkDataUpdateRouter(void)
{
VerifyOrExit(IsRouterOrLeader());
if (IsLeader())
{
SendMulticastDataResponse();
}
else
{
mDelayedSender.ScheduleMulticastDataResponse(GenerateRandomDelay(kUnsolicitedDataResponseJitter));
}
SynchronizeChildNetworkData();
exit:
return;
}
void Mle::SynchronizeChildNetworkData(void)
{
VerifyOrExit(IsRouterOrLeader());
for (Child &child : Get<ChildTable>().Iterate(Child::kInStateValid))
{
if (child.IsRxOnWhenIdle())
{
continue;
}
if (child.GetNetworkDataVersion() == Get<NetworkData::Leader>().GetVersion(child.GetNetworkDataType()))
{
continue;
}
SuccessOrExit(SendChildUpdateRequestToChild(child));
}
exit:
return;
}
#if OPENTHREAD_CONFIG_MLE_STEERING_DATA_SET_OOB_ENABLE
void Mle::SetSteeringData(const Mac::ExtAddress *aExtAddress)
{
Mac::ExtAddress nullExtAddr;
Mac::ExtAddress allowAnyExtAddr;
nullExtAddr.Clear();
allowAnyExtAddr.Fill(0xff);
if ((aExtAddress == nullptr) || (*aExtAddress == nullExtAddr))
{
mSteeringData.Clear();
}
else if (*aExtAddress == allowAnyExtAddr)
{
mSteeringData.SetToPermitAllJoiners();
}
else
{
Mac::ExtAddress joinerId;
IgnoreError(mSteeringData.Init(MeshCoP::SteeringData::kMaxLength));
MeshCoP::ComputeJoinerId(*aExtAddress, joinerId);
IgnoreError(mSteeringData.UpdateBloomFilter(joinerId));
}
}
#endif // OPENTHREAD_CONFIG_MLE_STEERING_DATA_SET_OOB_ENABLE
void Mle::HandleDiscoveryRequest(RxInfo &aRxInfo)
{
Error error = kErrorNone;
bool parsedDiscoveryRequestTlv = false;
Tlv::Info tlvInfo;
MeshCoP::DiscoveryRequestTlvValue discoveryRequestTlvValue;
MeshCoP::ExtendedPanId extPanId;
OffsetRange offsetRange;
DiscoveryResponseInfo responseInfo;
Log(kMessageReceive, kTypeDiscoveryRequest, aRxInfo.mMessageInfo.GetPeerAddr());
VerifyOrExit(IsRouterRoleAllowed(), error = kErrorInvalidState);
SuccessOrExit(error = Tlv::FindTlvValueOffsetRange(aRxInfo.mMessage, Tlv::kDiscovery, offsetRange));
for (; !offsetRange.IsEmpty(); offsetRange.AdvanceOffset(tlvInfo.GetSize()))
{
SuccessOrExit(error = tlvInfo.ParseFrom(aRxInfo.mMessage, offsetRange));
if (tlvInfo.IsExtended())
{
continue;
}
switch (tlvInfo.GetType())
{
case MeshCoP::Tlv::kDiscoveryRequest:
SuccessOrExit(error =
tlvInfo.Read<MeshCoP::DiscoveryRequestTlv>(aRxInfo.mMessage, discoveryRequestTlvValue));
parsedDiscoveryRequestTlv = true;
break;
case MeshCoP::Tlv::kExtendedPanId:
SuccessOrExit(error = tlvInfo.Read<MeshCoP::ExtendedPanIdTlv>(aRxInfo.mMessage, extPanId));
VerifyOrExit(Get<MeshCoP::NetworkIdentity>().GetExtPanId() != extPanId, error = kErrorDrop);
break;
default:
break;
}
}
if (parsedDiscoveryRequestTlv)
{
#if OPENTHREAD_CONFIG_MLE_DISCOVERY_SCAN_REQUEST_CALLBACK_ENABLE
if (mDiscoveryRequestCallback.IsSet())
{
DiscoveryRequestInfo info;
AsCoreType(&info.mExtAddress).SetFromIid(aRxInfo.mMessageInfo.GetPeerAddr().GetIid());
info.mVersion = discoveryRequestTlvValue.GetVersion();
info.mIsJoiner = discoveryRequestTlvValue.GetJoinerFlag();
mDiscoveryRequestCallback.Invoke(&info);
}
#endif
if (discoveryRequestTlvValue.GetJoinerFlag())
{
#if OPENTHREAD_CONFIG_MLE_STEERING_DATA_SET_OOB_ENABLE
if (!mSteeringData.IsEmpty())
{
}
else // if steering data is not set out of band, fall back to network data
#endif
{
VerifyOrExit(Get<NetworkData::Leader>().IsJoiningAllowed(), error = kErrorSecurity);
}
}
}
responseInfo.mPanId = aRxInfo.mMessage.GetPanId();
#if OPENTHREAD_CONFIG_MULTI_RADIO
// Send the MLE Discovery Response message on same radio link
// from which the "MLE Discover Request" message was received.
responseInfo.mRadioType = aRxInfo.mMessage.GetRadioType();
#endif
mDelayedSender.ScheduleDiscoveryResponse(aRxInfo.mMessageInfo.GetPeerAddr(), responseInfo,
GenerateRandomDelay(kDiscoveryMaxJitter));
exit:
LogProcessError(kTypeDiscoveryRequest, error);
}
Error Mle::SendDiscoveryResponse(const Ip6::Address &aDestination, const DiscoveryResponseInfo &aInfo)
{
Error error = kErrorNone;
TxMessage *message;
Tlv::Bookmark tlvBookmark;
MeshCoP::DiscoveryResponseTlvValue discoveryResponseTlvValue;
VerifyOrExit((message = NewMleMessage(kCommandDiscoveryResponse)) != nullptr, error = kErrorNoBufs);
message->SetDirectTransmission();
message->SetPanId(aInfo.mPanId);
#if OPENTHREAD_CONFIG_MULTI_RADIO
message->SetRadioType(aInfo.mRadioType);
#endif
SuccessOrExit(error = Tlv::StartTlv(*message, Tlv::kDiscovery, tlvBookmark));
discoveryResponseTlvValue.Clear();
discoveryResponseTlvValue.SetVersion(kThreadVersion);
#if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE
if (Get<KeyManager>().GetSecurityPolicy().mNativeCommissioningEnabled)
{
SuccessOrExit(error = Tlv::Append<MeshCoP::CommissionerUdpPortTlv>(
*message, Get<MeshCoP::BorderAgent::Manager>().GetUdpPort()));
discoveryResponseTlvValue.SetNativeCommissionerFlag();
}
#endif
if (Get<KeyManager>().GetSecurityPolicy().mCommercialCommissioningEnabled)
{
discoveryResponseTlvValue.SetCcmFlag();
}
SuccessOrExit(error = Tlv::Append<MeshCoP::DiscoveryResponseTlv>(*message, discoveryResponseTlvValue));
SuccessOrExit(error =
Tlv::Append<MeshCoP::ExtendedPanIdTlv>(*message, Get<MeshCoP::NetworkIdentity>().GetExtPanId()));
SuccessOrExit(error = Tlv::Append<MeshCoP::NetworkNameTlv>(
*message, Get<MeshCoP::NetworkIdentity>().GetNetworkName().GetAsCString()));
SuccessOrExit(error = message->AppendSteeringDataTlv());
SuccessOrExit(
error = Tlv::Append<MeshCoP::JoinerUdpPortTlv>(*message, Get<MeshCoP::JoinerRouter>().GetJoinerUdpPort()));
#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_4)
if (!Get<MeshCoP::NetworkIdentity>().IsDefaultDomainNameSet())
{
SuccessOrExit(error = Tlv::Append<MeshCoP::ThreadDomainNameTlv>(
*message, Get<MeshCoP::NetworkIdentity>().GetDomainName().GetAsCString()));
}
#endif
SuccessOrExit(error = Tlv::EndTlv(*message, tlvBookmark));
SuccessOrExit(error = message->SendTo(aDestination));
Log(kMessageSend, kTypeDiscoveryResponse, aDestination);
exit:
FreeMessageOnError(message, error);
LogProcessError(kTypeDiscoveryResponse, error);
return error;
}
Error Mle::SendChildIdResponse(Child &aChild)
{
Error error = kErrorNone;
Ip6::Address destination;
TxMessage *message;
VerifyOrExit((message = NewMleMessage(kCommandChildIdResponse)) != nullptr, error = kErrorNoBufs);
SuccessOrExit(error = message->AppendSourceAddressAndLeaderDataTlvs());
SuccessOrExit(error = message->AppendActiveAndPendingTimestampTlvs());
if ((aChild.GetRloc16() == 0) || !HasMatchingRouterIdWith(aChild.GetRloc16()))
{
aChild.SetRloc16(mChildTable.AllocateNewChildRloc16());
}
SuccessOrExit(error = message->AppendAddress16Tlv(aChild.GetRloc16()));
for (uint8_t i = 0; i < Child::kMaxRequestTlvs; i++)
{
switch (aChild.GetRequestTlv(i))
{
case Tlv::kNetworkData:
SuccessOrExit(error = message->AppendNetworkDataTlv(aChild.GetNetworkDataType()));
break;
case Tlv::kRoute:
SuccessOrExit(error = message->AppendRouteTlv());
break;
case Tlv::kActiveDataset:
SuccessOrExit(error = message->AppendActiveDatasetTlv());
break;
case Tlv::kPendingDataset:
SuccessOrExit(error = message->AppendPendingDatasetTlv());
break;
case Tlv::kSupervisionInterval:
SuccessOrExit(error = message->AppendSupervisionIntervalTlv(aChild.GetSupervisionInterval()));
break;
default:
break;
}
}
if (!aChild.IsFullThreadDevice())
{
SuccessOrExit(error = message->AppendAddressRegistrationTlv(aChild));
}
if (!aChild.IsRxOnWhenIdle())
{
Get<IndirectSender>().SetChildUseShortAddress(aChild, false);
}
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
if (aChild.IsTimeSyncEnabled())
{
message->SetTimeSync(true);
}
#endif
destination.InitAsLinkLocalAddress(aChild.GetExtAddress());
SuccessOrExit(error = message->SendTo(destination));
SetChildStateToValid(aChild);
Log(kMessageSend, kTypeChildIdResponse, destination, aChild.GetRloc16());
exit:
FreeMessageOnError(message, error);
return error;
}
Error Mle::SendChildUpdateRequestToChild(Child &aChild)
{
static const uint8_t kTlvs[] = {Tlv::kTimeout, Tlv::kAddressRegistration};
Error error = kErrorNone;
Ip6::Address destination;
TxMessage *message = nullptr;
if (!aChild.IsRxOnWhenIdle() && aChild.IsStateRestoring())
{
// No need to send the resync "Child Update Request"
// to the sleepy child if there is one already
// queued.
VerifyOrExit(!Get<IndirectSender>().HasQueuedMessageForSleepyChild(aChild, IsMessageChildUpdateRequest));
}
Get<MeshForwarder>().RemoveMessagesForChild(aChild, IsMessageChildUpdateRequest);
VerifyOrExit((message = NewMleMessage(kCommandChildUpdateRequest)) != nullptr, error = kErrorNoBufs);
SuccessOrExit(error = message->AppendSourceAddressAndLeaderDataTlvs());
SuccessOrExit(error = message->AppendNetworkDataTlv(aChild.GetNetworkDataType()));
SuccessOrExit(error = message->AppendActiveAndPendingTimestampTlvs());
if (aChild.IsStateValid())
{
SuccessOrExit(error = message->AppendLinkMarginTlv(aChild.GetLinkInfo().GetLinkMargin()));
}
else
{
SuccessOrExit(error = message->AppendTlvRequestTlv(kTlvs));
if (!aChild.IsStateRestored())
{
// A random challenge is generated and saved when `aChild`
// is first initialized in `kStateRestored`. We will use
// the saved challenge here. This prevents overwriting
// the saved challenge when the child is also detached
// and happens to send a "Parent Request" in the window
// where the parent transitions to the router/leader role
// and before the parent sends the "Child Update Request".
// This ensures that the same random challenge is
// included in both "Parent Response" and "Child Update
// Response," guaranteeing proper acceptance of the
// child's "Child ID request".
aChild.GenerateChallenge();
}
SuccessOrExit(error = message->AppendChallengeTlv(aChild.GetChallenge()));
}
destination.InitAsLinkLocalAddress(aChild.GetExtAddress());
SuccessOrExit(error = message->SendTo(destination));
if (aChild.IsRxOnWhenIdle())
{
// only try to send a single Child Update Request message to an rx-on-when-idle child
aChild.SetState(Child::kStateChildUpdateRequest);
}
Log(kMessageSend, kTypeChildUpdateRequestOfChild, destination, aChild.GetRloc16());
exit:
FreeMessageOnError(message, error);
return error;
}
void Mle::SendChildUpdateResponseToChild(Child *aChild, const ChildUpdateResponseInfo &aInfo)
{
Error error = kErrorNone;
TxMessage *message;
VerifyOrExit((message = NewMleMessage(kCommandChildUpdateResponse)) != nullptr, error = kErrorNoBufs);
for (uint8_t tlvType : aInfo.mTlvList)
{
// Add all TLV types that do not depend on `child`
switch (tlvType)
{
case Tlv::kStatus:
SuccessOrExit(error = message->AppendStatusTlv(kStatusError));
break;
case Tlv::kLeaderData:
SuccessOrExit(error = message->AppendLeaderDataTlv());
break;
case Tlv::kResponse:
SuccessOrExit(error = message->AppendResponseTlv(aInfo.mChallenge));
break;
case Tlv::kSourceAddress:
SuccessOrExit(error = message->AppendSourceAddressTlv());
break;
case Tlv::kMleFrameCounter:
SuccessOrExit(error = message->AppendMleFrameCounterTlv());
break;
case Tlv::kLinkFrameCounter:
SuccessOrExit(error = message->AppendLinkFrameCounterTlv());
break;
}
// Make sure `child` is not null before adding TLV types
// that can depend on it.
if (aChild == nullptr)
{
continue;
}
switch (tlvType)
{
case Tlv::kAddressRegistration:
SuccessOrExit(error = message->AppendAddressRegistrationTlv(*aChild));
break;
case Tlv::kMode:
SuccessOrExit(error = message->AppendModeTlv(aChild->GetDeviceMode()));
break;
case Tlv::kNetworkData:
SuccessOrExit(error = message->AppendNetworkDataTlv(aChild->GetNetworkDataType()));
SuccessOrExit(error = message->AppendActiveAndPendingTimestampTlvs());
break;
case Tlv::kTimeout:
SuccessOrExit(error = message->AppendTimeoutTlv(aChild->GetTimeout()));
break;
case Tlv::kLinkMargin:
SuccessOrExit(error = message->AppendLinkMarginTlv(aChild->GetLinkInfo().GetLinkMargin()));
break;
case Tlv::kSupervisionInterval:
SuccessOrExit(error = message->AppendSupervisionIntervalTlv(aChild->GetSupervisionInterval()));
break;
#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
case Tlv::kCslClockAccuracy:
if (!aChild->IsRxOnWhenIdle())
{
SuccessOrExit(error = message->AppendCslClockAccuracyTlv());
}
break;
#endif
}
}
SuccessOrExit(error = message->SendTo(aInfo.mDestination));
if (aChild == nullptr)
{
Log(kMessageSend, kTypeChildUpdateResponseOfChild, aInfo.mDestination);
}
else
{
Log(kMessageSend, kTypeChildUpdateResponseOfChild, aInfo.mDestination, aChild->GetRloc16());
}
exit:
FreeMessageOnError(message, error);
}
void Mle::SendMulticastDataResponse(void)
{
TlvList tlvList;
tlvList.Add(Tlv::kNetworkData);
SendDataResponse(Ip6::Address::GetLinkLocalAllNodesMulticast(), tlvList);
}
void Mle::SendDataResponse(const Ip6::Address &aDestination, const TlvList &aTlvList, const Message *aRequestMessage)
{
OT_UNUSED_VARIABLE(aRequestMessage);
Error error = kErrorNone;
TxMessage *message = nullptr;
Neighbor *neighbor;
VerifyOrExit(IsAttached());
if (mRetrieveNewNetworkData)
{
LogInfo("Suppressing Data Response - waiting for new network data");
ExitNow();
}
VerifyOrExit((message = NewMleMessage(kCommandDataResponse)) != nullptr, error = kErrorNoBufs);
SuccessOrExit(error = message->AppendSourceAddressAndLeaderDataTlvs());
SuccessOrExit(error = message->AppendActiveAndPendingTimestampTlvs());
for (uint8_t tlvType : aTlvList)
{
switch (tlvType)
{
case Tlv::kNetworkData:
neighbor = mNeighborTable.FindNeighbor(aDestination);
SuccessOrExit(error = message->AppendNetworkDataTlv((neighbor != nullptr) ? neighbor->GetNetworkDataType()
: NetworkData::kFullSet));
break;
case Tlv::kActiveDataset:
SuccessOrExit(error = message->AppendActiveDatasetTlv());
break;
case Tlv::kPendingDataset:
SuccessOrExit(error = message->AppendPendingDatasetTlv());
break;
case Tlv::kRoute:
SuccessOrExit(error = message->AppendRouteTlv());
break;
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
case Tlv::kLinkMetricsReport:
OT_ASSERT(aRequestMessage != nullptr);
neighbor = mNeighborTable.FindNeighbor(aDestination);
VerifyOrExit(neighbor != nullptr, error = kErrorInvalidState);
SuccessOrExit(error = Get<LinkMetrics::Subject>().AppendReport(*message, *aRequestMessage, *neighbor));
break;
#endif
}
}
SuccessOrExit(error = message->SendTo(aDestination));
Log(kMessageSend, kTypeDataResponse, aDestination);
exit:
FreeMessageOnError(message, error);
LogSendError(kTypeDataResponse, error);
}
void Mle::RemoveRouterLink(Router &aRouter)
{
switch (mRole)
{
case kRoleChild:
if (&aRouter == &mParent)
{
IgnoreError(BecomeDetached());
}
break;
case kRoleRouter:
case kRoleLeader:
mRouterTable.RemoveRouterLink(aRouter);
break;
default:
break;
}
}
void Mle::RemoveNeighbor(Neighbor &aNeighbor)
{
VerifyOrExit(!aNeighbor.IsStateInvalid());
if (&aNeighbor == &mParent)
{
if (IsChild())
{
IgnoreError(BecomeDetached());
}
}
else if (&aNeighbor == &GetParentCandidate())
{
mAttacher.ClearParentCandidate();
}
else if (IsChildRloc16(aNeighbor.GetRloc16()))
{
OT_ASSERT(mChildTable.Contains(aNeighbor));
if (aNeighbor.IsStateValidOrRestoring())
{
mNeighborTable.Signal(NeighborTable::kChildRemoved, aNeighbor);
}
Get<IndirectSender>().ClearAllMessagesForSleepyChild(static_cast<Child &>(aNeighbor));
if (aNeighbor.IsFullThreadDevice())
{
Get<AddressResolver>().RemoveEntriesForRloc16(aNeighbor.GetRloc16());
}
mChildTable.RemoveStoredChild(static_cast<Child &>(aNeighbor));
}
else if (aNeighbor.IsStateValid())
{
OT_ASSERT(mRouterTable.Contains(aNeighbor));
mNeighborTable.Signal(NeighborTable::kRouterRemoved, aNeighbor);
mRouterTable.RemoveRouterLink(static_cast<Router &>(aNeighbor));
}
aNeighbor.GetLinkInfo().Clear();
aNeighbor.SetState(Neighbor::kStateInvalid);
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
aNeighbor.RemoveAllForwardTrackingSeriesInfo();
#endif
exit:
return;
}
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
Error Mle::SetPreferredRouterId(uint8_t aRouterId)
{
Error error = kErrorNone;
VerifyOrExit(IsDetached() || IsDisabled(), error = kErrorInvalidState);
mPreviousRouterId = aRouterId;
exit:
return error;
}
#endif
void Mle::SetRouterId(uint8_t aRouterId)
{
mRouterId = aRouterId;
mPreviousRouterId = mRouterId;
}
Error Mle::SendAddressSolicit(RouterUpgradeReason aReason)
{
Error error = kErrorNone;
Coap::Message *message = nullptr;
Ip6::Address leaderRloc;
VerifyOrExit(!mAddressSolicitPending);
message = Get<Tmf::Agent>().AllocateAndInitPriorityConfirmablePostMessage(kUriAddressSolicit);
VerifyOrExit(message != nullptr, error = kErrorNoBufs);
SuccessOrExit(error = Tlv::Append<ThreadExtMacAddressTlv>(*message, Get<Mac::Mac>().GetExtAddress()));
if (IsRouterIdValid(mPreviousRouterId))
{
SuccessOrExit(error = Tlv::Append<ThreadRloc16Tlv>(*message, Rloc16FromRouterId(mPreviousRouterId)));
}
SuccessOrExit(error = Tlv::Append<ThreadStatusTlv>(*message, aReason));
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
SuccessOrExit(error = Tlv::Append<XtalAccuracyTlv>(*message, otPlatTimeGetXtalAccuracy()));
#endif
GetLeaderRloc(leaderRloc);
SuccessOrExit(error = Get<Tmf::Agent>().SendMessageTo(*message, leaderRloc, HandleAddressSolicitResponse, this));
mAddressSolicitPending = true;
Log(kMessageSend, kTypeAddressSolicit, leaderRloc);
exit:
FreeMessageOnError(message, error);
return error;
}
void Mle::SendAddressRelease(void)
{
Error error = kErrorNone;
Coap::Message *message;
Ip6::Address leaderRloc;
message = Get<Tmf::Agent>().AllocateAndInitPriorityConfirmablePostMessage(kUriAddressRelease);
VerifyOrExit(message != nullptr, error = kErrorNoBufs);
SuccessOrExit(error = Tlv::Append<ThreadRloc16Tlv>(*message, Rloc16FromRouterId(mRouterId)));
SuccessOrExit(error = Tlv::Append<ThreadExtMacAddressTlv>(*message, Get<Mac::Mac>().GetExtAddress()));
GetLeaderRloc(leaderRloc);
SuccessOrExit(error = Get<Tmf::Agent>().SendMessageTo(*message, leaderRloc));
Log(kMessageSend, kTypeAddressRelease, leaderRloc);
exit:
FreeMessageOnError(message, error);
LogSendError(kTypeAddressRelease, error);
}
void Mle::HandleAddressSolicitResponse(Coap::Msg *aMsg, Error aResult)
{
uint8_t status;
uint16_t rloc16;
RouterIdMask routerIdMask;
uint8_t routerId;
Router *router;
mAddressSolicitPending = false;
VerifyOrExit(aResult == kErrorNone && aMsg != nullptr);
VerifyOrExit(aMsg->GetCode() == Coap::kCodeChanged);
Log(kMessageReceive, kTypeAddressReply, aMsg->mMessageInfo.GetPeerAddr());
SuccessOrExit(Tlv::Find<ThreadStatusTlv>(aMsg->mMessage, status));
if (status != kAddrSolicitSuccess)
{
mAddressSolicitRejected = true;
if (IsRouterIdValid(mPreviousRouterId))
{
if (HasChildren())
{
RemoveChildren();
}
SetRouterId(kInvalidRouterId);
}
ExitNow();
}
SuccessOrExit(Tlv::Find<ThreadRloc16Tlv>(aMsg->mMessage, rloc16));
routerId = RouterIdFromRloc16(rloc16);
SuccessOrExit(Tlv::Find<ThreadRouterMaskTlv>(aMsg->mMessage, routerIdMask));
VerifyOrExit(routerIdMask.IsValid());
VerifyOrExit(routerIdMask.IsAllocated(GetLeaderId()));
SetAlternateRloc16(GetRloc16());
SetRouterId(routerId);
SetStateRouter(Rloc16FromRouterId(mRouterId));
// We keep the router table next hop and cost as what we had as a
// REED, i.e., our parent was the next hop towards all other
// routers and we tracked its cost towards them. As an FTD child,
// we may have established links with a subset of neighboring routers.
// We ensure to clear these links to avoid using them (since will
// be rejected by the neighbor).
mRouterTable.ClearNeighbors();
mRouterTable.UpdateRouterIdMask(routerIdMask);
router = mRouterTable.FindRouterById(routerId);
VerifyOrExit(router != nullptr);
router->SetExtAddress(Get<Mac::Mac>().GetExtAddress());
router->SetNextHopToInvalid();
// Ensure we have our parent as a neighboring router, copying the
// `mParent` entry.
router = mRouterTable.FindRouterById(mParent.GetRouterId());
VerifyOrExit(router != nullptr);
router->SetFrom(mParent);
router->SetState(Neighbor::kStateValid);
router->SetNextHopToInvalid();
// Ensure we have a next hop and cost towards leader.
if (mRouterTable.GetPathCostToLeader() >= kMaxRouteCost)
{
Router *leader = mRouterTable.GetLeader();
OT_ASSERT(leader != nullptr);
leader->SetNextHopAndCost(RouterIdFromRloc16(mParent.GetRloc16()), mParent.GetLeaderCost());
}
// We send a unicast Link Request to our former parent if its
// version is earlier than 1.3. This is to address a potential
// compatibility issue with some non-OpenThread stacks which may
// ignore MLE Advertisements from a former/existing child.
if (mParent.GetVersion() < kThreadVersion1p3)
{
SendLinkRequest(&mParent);
}
// We send an Advertisement to inform our former parent of our
// newly allocated Router ID. This will cause the parent to
// reset its advertisement trickle timer which can help speed
// up the dissemination of the new Router ID to other routers.
// This can also help with quicker link establishment with our
// former parent and other routers.
SendMulticastAdvertisement();
for (Child &child : Get<ChildTable>().Iterate(Child::kInStateChildIdRequest))
{
IgnoreError(SendChildIdResponse(child));
// The transition to the router role was triggered by a Child
// ID Request. This indicates that the child has no other
// parent option. We set the flags to prevent the parent
// router from downgrading back to a REED to ensure this
// child remains connected.
//
// The `DowngradeBlocked` is cleared in various situations:
// - From `SetStateDetached()` (e.g. partition change).
// - If a new router is added (new possible parent).
// - If all children blocking downgrade are disconnected.
child.SetBlockParentDowngrade(true);
mRoleTransitioner.SetDowngradeBlocked(true);
}
exit:
mAnnounceHandler.HandleRouterRoleTransitionAttemptDone();
}
Error Mle::SetChildRouterLinks(uint8_t aChildRouterLinks)
{
Error error = kErrorNone;
VerifyOrExit(IsDisabled(), error = kErrorInvalidState);
mChildRouterLinks = aChildRouterLinks;
exit:
return error;
}
bool Mle::RoleTransitioner::WillBecomeRouterSoon(void) const
{
static constexpr uint8_t kMaxDelay = 10;
bool willBecomeRouter = false;
VerifyOrExit(IsRouterRoleAllowed() && Get<Mle>().IsChild());
VerifyOrExit(!Get<Mle>().mAddressSolicitRejected);
if (!Get<Mle>().mAddressSolicitPending)
{
VerifyOrExit(IsTransitionPending() && GetTimeout() <= kMaxDelay);
}
willBecomeRouter = true;
exit:
return willBecomeRouter;
}
Error Mle::AddrSolicitInfo::ParseFrom(const Coap::Msg &aMsg)
{
// Parses a `kUriAddressSolicit` request message.
Error error;
VerifyOrExit(aMsg.IsConfirmable(), error = kErrorParse);
SuccessOrExit(error = Tlv::Find<ThreadExtMacAddressTlv>(aMsg.mMessage, mExtAddress));
SuccessOrExit(error = Tlv::Find<ThreadStatusTlv>(aMsg.mMessage, mReason));
switch (Tlv::Find<ThreadRloc16Tlv>(aMsg.mMessage, mRequestedRloc16))
{
case kErrorNone:
break;
case kErrorNotFound:
mRequestedRloc16 = kInvalidRloc16;
break;
default:
ExitNow(error = kErrorParse);
}
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
switch (Tlv::Find<XtalAccuracyTlv>(aMsg.mMessage, mXtalAccuracy))
{
case kErrorNone:
break;
case kErrorNotFound:
SetToUintMax(mXtalAccuracy);
break;
default:
ExitNow(error = kErrorParse);
}
#endif
exit:
return error;
}
void Mle::ProcessAddressSolicit(AddrSolicitInfo &aInfo)
{
// Processes a previously parsed Address Solicit request and
// determines whether to accept or reject the request.
aInfo.mResponse = kAddrSolicitNoAddressAvailable;
aInfo.mRouter = nullptr;
LogInfo("AddrSolicit Reason: %s", RouterUpgradeReasonToString(aInfo.mReason));
if (aInfo.mRequestedRloc16 != kInvalidRloc16)
{
LogInfo("AddrSolicit Requested ID: %u", RouterIdFromRloc16(aInfo.mRequestedRloc16));
}
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
VerifyOrExit(aInfo.mXtalAccuracy <= Get<TimeSync>().GetXtalThreshold());
#endif
aInfo.mRouter = mRouterTable.FindRouter(aInfo.mExtAddress);
if (aInfo.mRouter != nullptr)
{
aInfo.mResponse = kAddrSolicitSuccess;
ExitNow();
}
switch (aInfo.mReason)
{
case kReasonTooFewRouters:
VerifyOrExit(mRoleTransitioner.IsRouterCountBelowUpgradeThreshold());
break;
case kReasonHaveChildIdRequest:
case kReasonParentPartitionChange:
break;
case kReasonBorderRouterRequest:
if (!mRoleTransitioner.IsRouterCountBelowUpgradeThreshold() &&
(Get<NetworkData::Leader>().CountBorderRouters(NetworkData::kRouterRoleOnly) >=
kRouterUpgradeBorderRouterRequestThreshold))
{
ExitNow();
}
break;
default:
aInfo.mResponse = kAddrSolicitUnrecognizedReason;
ExitNow();
}
if (aInfo.mRequestedRloc16 != kInvalidRloc16)
{
aInfo.mRouter = mRouterTable.Allocate(RouterIdFromRloc16(aInfo.mRequestedRloc16));
}
if (aInfo.mRouter == nullptr)
{
aInfo.mRouter = mRouterTable.Allocate();
VerifyOrExit(aInfo.mRouter != nullptr);
}
aInfo.mRouter->SetExtAddress(aInfo.mExtAddress);
aInfo.mResponse = kAddrSolicitSuccess;
exit:
return;
}
template <> void Mle::HandleTmf<kUriAddressSolicit>(Coap::Msg &aMsg)
{
Coap::Message *response = nullptr;
AddrSolicitInfo info;
VerifyOrExit(IsLeader() && !IsAttaching());
Log(kMessageReceive, kTypeAddressSolicit, aMsg.mMessageInfo.GetPeerAddr());
SuccessOrExit(info.ParseFrom(aMsg));
ProcessAddressSolicit(info);
// Prepare and send response
response = Get<Tmf::Agent>().AllocateAndInitPriorityResponseFor(aMsg.mMessage);
VerifyOrExit(response != nullptr);
SuccessOrExit(Tlv::Append<ThreadStatusTlv>(*response, info.mResponse));
if (info.mRouter != nullptr)
{
RouterIdMask routerIdMask;
SuccessOrExit(Tlv::Append<ThreadRloc16Tlv>(*response, info.mRouter->GetRloc16()));
mRouterTable.GetRouterIdMask(routerIdMask);
SuccessOrExit(Tlv::Append<ThreadRouterMaskTlv>(*response, routerIdMask));
}
SuccessOrExit(Get<Tmf::Agent>().SendMessage(*response, aMsg.mMessageInfo));
response = nullptr;
Log(kMessageSend, kTypeAddressReply, aMsg.mMessageInfo.GetPeerAddr());
// If assigning a new RLOC16 (e.g., on promotion of a child to
// router role) we clear any address cache entries associated
// with the old RLOC16 unless the sender is a direct child. For
// direct children, we retain the cache entries to allow
// association with the promoted router's new RLOC16 upon
// receiving its Link Advertisement.
if ((info.mResponse == kAddrSolicitSuccess) && (info.mRouter != nullptr))
{
uint16_t oldRloc16;
VerifyOrExit(IsRoutingLocator(aMsg.mMessageInfo.GetPeerAddr()));
oldRloc16 = aMsg.mMessageInfo.GetPeerAddr().GetIid().GetLocator();
VerifyOrExit(oldRloc16 != info.mRouter->GetRloc16());
VerifyOrExit(!RouterIdMatch(oldRloc16, GetRloc16()));
Get<AddressResolver>().RemoveEntriesForRloc16(oldRloc16);
}
exit:
FreeMessage(response);
}
template <> void Mle::HandleTmf<kUriAddressRelease>(Coap::Msg &aMsg)
{
uint16_t rloc16;
Mac::ExtAddress extAddress;
uint8_t routerId;
Router *router;
VerifyOrExit(mRole == kRoleLeader);
VerifyOrExit(aMsg.IsConfirmable());
Log(kMessageReceive, kTypeAddressRelease, aMsg.mMessageInfo.GetPeerAddr());
SuccessOrExit(Tlv::Find<ThreadRloc16Tlv>(aMsg.mMessage, rloc16));
SuccessOrExit(Tlv::Find<ThreadExtMacAddressTlv>(aMsg.mMessage, extAddress));
routerId = RouterIdFromRloc16(rloc16);
router = mRouterTable.FindRouterById(routerId);
VerifyOrExit((router != nullptr) && (router->GetExtAddress() == extAddress));
IgnoreError(mRouterTable.Release(routerId));
SuccessOrExit(Get<Tmf::Agent>().SendAckResponse(aMsg));
Log(kMessageSend, kTypeAddressReleaseReply, aMsg.mMessageInfo.GetPeerAddr());
exit:
return;
}
void Mle::DetermineConnectivity(Connectivity &aConnectivity) const
{
aConnectivity.Clear();
aConnectivity.mParentPriority = GetAssignParentPriority();
if (aConnectivity.mParentPriority == kParentPriorityUnspecified)
{
uint16_t numChildren = mChildTable.GetNumChildren(Child::kInStateValid);
uint16_t maxAllowed = mChildTable.GetMaxChildrenAllowed();
if ((maxAllowed - numChildren) < (maxAllowed / 3))
{
aConnectivity.mParentPriority = kParentPriorityLow;
}
else
{
aConnectivity.mParentPriority = kParentPriorityMedium;
}
}
if (IsChild())
{
aConnectivity.IncrementNumForLinkQuality(mParent.GetLinkQualityIn());
}
for (const Router &router : Get<RouterTable>())
{
if (router.GetRloc16() == GetRloc16())
{
continue;
}
if (!router.IsStateValid())
{
continue;
}
aConnectivity.IncrementNumForLinkQuality(router.GetTwoWayLinkQuality());
}
aConnectivity.mLeaderCost = Min(mRouterTable.GetPathCostToLeader(), kMaxRouteCost);
aConnectivity.mIdSequence = mRouterTable.GetRouterIdSequence();
aConnectivity.mActiveRouters = mRouterTable.GetActiveRouterCount();
aConnectivity.mSedBufferSize = Connectivity::kDefaultSedBufferSize;
aConnectivity.mSedDatagramCount = Connectivity::kDefaultSedDatagramCount;
}
void Mle::FillConnectivityTlvValue(ConnectivityTlvValue &aTlvValue) const
{
Connectivity connectivity;
DetermineConnectivity(connectivity);
aTlvValue.InitFrom(connectivity);
}
void Mle::RoleTransitioner::DecideWhetherToDowngrade(uint8_t aNeighborId, const RouteTlv::Data &aRouteTlvData)
{
// Determine whether all conditions are satisfied for the router
// to downgrade after receiving info for a neighboring router
// with Router ID `aNeighborId` along with its `aRouteTlvData`.
uint8_t activeRouterCount = Get<RouterTable>().GetActiveRouterCount();
uint8_t count;
VerifyOrExit(Get<Mle>().IsRouter());
VerifyOrExit(Get<RouterTable>().IsAllocated(aNeighborId));
VerifyOrExit(!mDowngradeBlocked);
VerifyOrExit(!IsTransitionPending());
VerifyOrExit(activeRouterCount > mDowngradeThreshold);
// Check that we have at least `kMinDowngradeNeighbors`
// neighboring routers with two-way link quality of 2 or better.
count = 0;
for (const Router &router : Get<RouterTable>())
{
if (!router.IsStateValid() || (router.GetTwoWayLinkQuality() < kLinkQuality2))
{
continue;
}
count++;
if (count >= kMinDowngradeNeighbors)
{
break;
}
}
VerifyOrExit(count >= kMinDowngradeNeighbors);
// Check that we have fewer children than three times the number
// of excess routers (defined as the difference between number of
// active routers and `mDowngradeThreshold`).
count = activeRouterCount - mDowngradeThreshold;
VerifyOrExit(Get<ChildTable>().GetNumChildren(Child::kInStateValid) < count * 3);
// Check that the neighbor has as good or better-quality links to
// same routers.
VerifyOrExit(NeighborHasComparableConnectivity(aNeighborId, aRouteTlvData));
#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE && OPENTHREAD_CONFIG_BORDER_ROUTER_REQUEST_ROUTER_ROLE
// Check if we are eligible to be router due to being a BR.
VerifyOrExit(!Get<NetworkData::Notifier>().IsEligibleForRouterRoleUpgradeAsBorderRouter());
#endif
// All conditions are satisfied to start downgrade.
StartTimeout();
exit:
return;
}
bool Mle::RoleTransitioner::NeighborHasComparableConnectivity(uint8_t aNeighborId,
const RouteTlv::Data &aRouteTlvData) const
{
// Check whether the neighboring router with Router ID `aNeighborId`
// (along with its `aRouteTlvData`) has as good or better-quality
// links to all our neighboring routers which have a two-way link
// quality of two or better.
bool isComparable = true;
for (uint8_t routerId = 0; routerId <= kMaxRouterId; routerId++)
{
const Router *router;
LinkQuality localLinkQuality;
LinkQuality peerLinkQuality;
const RouteTlv::Data::Entry *entry;
if ((routerId == Get<Mle>().mRouterId) || (routerId == aNeighborId))
{
continue;
}
router = Get<RouterTable>().FindRouterById(routerId);
if ((router == nullptr) || !router->IsStateValid())
{
continue;
}
localLinkQuality = router->GetTwoWayLinkQuality();
if (localLinkQuality < kLinkQuality2)
{
continue;
}
// `router` is our neighbor with two-way link quality of
// at least two. Check that `aRouteTlvData` has as good or
// better-quality link to it as well.
entry = aRouteTlvData.GetEntries().FindMatching(routerId);
if (entry == nullptr)
{
ExitNow(isComparable = false);
}
peerLinkQuality = Min(entry->GetLinkQualityIn(), entry->GetLinkQualityOut());
if (peerLinkQuality < localLinkQuality)
{
ExitNow(isComparable = false);
}
}
exit:
return isComparable;
}
void Mle::SetChildStateToValid(Child &aChild)
{
VerifyOrExit(!aChild.IsStateValid());
aChild.SetState(Neighbor::kStateValid);
IgnoreError(mChildTable.StoreChild(aChild));
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
Get<Mlr::Manager>().UpdateChildRegistrations(aChild);
#endif
mNeighborTable.Signal(NeighborTable::kChildAdded, aChild);
exit:
return;
}
bool Mle::HasChildren(void) { return mChildTable.HasChildren(Child::kInStateValidOrAttaching); }
void Mle::RemoveChildren(void)
{
for (Child &child : Get<ChildTable>().Iterate(Child::kInStateValidOrRestoring))
{
RemoveNeighbor(child);
}
}
Error Mle::SetAssignParentPriority(int8_t aParentPriority)
{
Error error = kErrorNone;
VerifyOrExit(aParentPriority <= kParentPriorityHigh && aParentPriority >= kParentPriorityUnspecified,
error = kErrorInvalidArgs);
mParentPriority = aParentPriority;
exit:
return error;
}
Error Mle::GetMaxChildTimeout(uint32_t &aTimeout) const
{
Error error = kErrorNotFound;
aTimeout = 0;
VerifyOrExit(IsRouterOrLeader(), error = kErrorInvalidState);
for (Child &child : Get<ChildTable>().Iterate(Child::kInStateValid))
{
if (child.IsFullThreadDevice())
{
continue;
}
if (child.GetTimeout() > aTimeout)
{
aTimeout = child.GetTimeout();
}
error = kErrorNone;
}
exit:
return error;
}
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
Error Mle::SendTimeSync(void)
{
Error error = kErrorNone;
Ip6::Address destination;
TxMessage *message = nullptr;
VerifyOrExit((message = NewMleMessage(kCommandTimeSync)) != nullptr, error = kErrorNoBufs);
message->SetTimeSync(true);
destination = Ip6::Address::GetLinkLocalAllNodesMulticast();
SuccessOrExit(error = message->SendTo(destination));
Log(kMessageSend, kTypeTimeSync, destination);
exit:
FreeMessageOnError(message, error);
return error;
}
#endif // OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
const char *Mle::RouterUpgradeReasonToString(uint8_t aReason)
{
const char *str = "Unknown";
switch (aReason)
{
case kReasonTooFewRouters:
str = "TooFewRouters";
break;
case kReasonHaveChildIdRequest:
str = "HaveChildIdRequest";
break;
case kReasonParentPartitionChange:
str = "ParentPartitionChange";
break;
case kReasonBorderRouterRequest:
str = "BorderRouterRequest";
break;
}
return str;
}
#endif // OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
//----------------------------------------------------------------------------------------------------------------------
// RoleTransitioner
Mle::RoleTransitioner::RoleTransitioner(Instance &aInstance)
: InstanceLocator(aInstance)
, mRouterEligible(true)
, mRouterRoleAllowed(true)
, mDowngradeBlocked(false)
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
, mCcmEnabled(false)
, mThreadVersionCheckEnabled(true)
#endif
, mTimeout(0)
, mJitter(kRouterSelectionJitter)
, mUpgradeThreshold(kRouterUpgradeThreshold)
, mDowngradeThreshold(kRouterDowngradeThreshold)
{
}
void Mle::RoleTransitioner::StartTimeout(void) { mTimeout = 1 + Random::NonCrypto::GenerateUpToExcluding(mJitter); }
bool Mle::RoleTransitioner::IsRouterCountBelowUpgradeThreshold(void) const
{
return Get<RouterTable>().GetActiveRouterCount() < mUpgradeThreshold;
}
void Mle::RoleTransitioner::DecideWhetherToUpgrade(void)
{
VerifyOrExit(Get<Mle>().IsChild());
VerifyOrExit(IsRouterRoleAllowed());
VerifyOrExit(!IsTransitionPending());
VerifyOrExit(IsRouterCountBelowUpgradeThreshold());
StartTimeout();
exit:
return;
}
void Mle::RoleTransitioner::HandleTimeTick(void)
{
VerifyOrExit(mTimeout > 0);
mTimeout--;
VerifyOrExit(mTimeout == 0);
// Timeout expired
switch (Get<Mle>().mRole)
{
case kRoleDisabled:
case kRoleDetached:
break;
case kRoleChild:
if (IsRouterCountBelowUpgradeThreshold() && Get<Mle>().HasNeighborWithGoodLinkQuality())
{
IgnoreError(Get<Mle>().BecomeRouter(kReasonTooFewRouters));
}
else
{
Get<Mle>().mAnnounceHandler.HandleRouterRoleTransitionAttemptDone();
}
if (!Get<Mle>().mAdvertiseTrickleTimer.IsRunning())
{
Get<Mle>().SendMulticastAdvertisement();
Get<Mle>().mAdvertiseTrickleTimer.Start(TrickleTimer::kModePlainTimer, kReedAdvIntervalMin,
kReedAdvIntervalMax);
}
break;
case kRoleRouter:
if (Get<RouterTable>().GetActiveRouterCount() > mDowngradeThreshold)
{
LogNote("Downgrade to REED");
Get<Mle>().mAttacher.Attach(kDowngradeToReed);
}
OT_FALL_THROUGH;
case kRoleLeader:
if (!IsRouterRoleAllowed())
{
LogInfo("Router role no longer allowed");
IgnoreError(Get<Mle>().BecomeDetached());
}
break;
}
exit:
return;
}
} // namespace Mle
} // namespace ot
#endif // OPENTHREAD_FTD