[mle] consolidate role transition management in RoleTransitioner (#12983)

This commit introduces the `RoleTransitioner` class (renamed from
`RouterRoleTransition`) to centralize the management of router role
eligibility, thresholds, and transitions.

The following state and logic are moved from the `Mle` class into
the `RoleTransitioner`:
- Router role eligibility and allowance state (`mRouterEligible`,
  `mRouterRoleAllowed`).
- Upgrade and downgrade thresholds.
- Downgrade blocking state (`mDowngradeBlocked`).
- Transition decision logic (`DecideWhetherToUpgrade()`,
  `DecideWhetherToDowngrade()`).
- The transition jitter timer and its management.

By consolidating these responsibilities, the complexity of the main
`Mle` class is reduced, and the role transition process is more
explicitly managed within its own sub-component.
This commit is contained in:
Abtin Keshavarzian
2026-05-05 07:42:08 -07:00
committed by GitHub
parent bdea2ae98c
commit 4384c66e7b
3 changed files with 215 additions and 178 deletions
+10 -16
View File
@@ -80,18 +80,9 @@ Mle::Mle(Instance &aInstance)
, mWedAttachTimer(aInstance)
#endif
#if OPENTHREAD_FTD
, mRouterEligible(true)
, mRouterRoleAllowed(true)
, mBlockDowngrade(false)
, mAddressSolicitPending(false)
, mAddressSolicitRejected(false)
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
, mCcmEnabled(false)
, mThreadVersionCheckEnabled(true)
#endif
, mNetworkIdTimeout(kNetworkIdTimeout)
, mRouterUpgradeThreshold(kRouterUpgradeThreshold)
, mRouterDowngradeThreshold(kRouterDowngradeThreshold)
, mPreviousPartitionRouterIdSequence(0)
, mPreviousPartitionIdTimeout(0)
, mChildRouterLinks(kChildRouterLinks)
@@ -108,6 +99,7 @@ Mle::Mle(Instance &aInstance)
, mAdvertiseTrickleTimer(aInstance, Mle::HandleAdvertiseTrickleTimer)
, mChildTable(aInstance)
, mRouterTable(aInstance)
, mRoleTransitioner(aInstance)
#endif // OPENTHREAD_FTD
#if OPENTHREAD_CONFIG_P2P_ENABLE
, mP2p(aInstance)
@@ -455,7 +447,7 @@ void Mle::Restore(void)
mHasRestored = true;
#if OPENTHREAD_FTD
UpdateRouterRoleAllowed(kReasonMleInit);
mRoleTransitioner.UpdateRouterRoleAllowed(RoleTransitioner::kReasonMleInit);
#endif
exit:
@@ -606,7 +598,7 @@ void Mle::SetStateDetached(void)
Get<MeshForwarder>().SetRxOnWhenIdle(true);
Get<Mac::Mac>().SetBeaconEnabled(false);
#if OPENTHREAD_FTD
mBlockDowngrade = false;
mRoleTransitioner.SetDowngradeBlocked(false);
ClearAlternateRloc16();
HandleDetachStart();
#endif
@@ -727,7 +719,7 @@ Error Mle::SetDeviceMode(DeviceMode aDeviceMode)
ClearAlternateRloc16();
}
UpdateRouterRoleAllowed(kReasonDeviceModeChanged);
mRoleTransitioner.UpdateRouterRoleAllowed(RoleTransitioner::kReasonDeviceModeChanged);
#endif
if (IsAttached())
@@ -1052,21 +1044,23 @@ void Mle::HandleNotifierEvents(Events aEvents)
#if OPENTHREAD_FTD
if (aEvents.Contains(kEventSecurityPolicyChanged))
{
UpdateRouterRoleAllowed(kReasonSecurityPolicyChanged);
mRoleTransitioner.UpdateRouterRoleAllowed(RoleTransitioner::kReasonSecurityPolicyChanged);
}
if (mBlockDowngrade && aEvents.Contains(kEventThreadChildRemoved))
if (mRoleTransitioner.IsDowngradeBlocked() && aEvents.Contains(kEventThreadChildRemoved))
{
mBlockDowngrade = false;
bool shouldBlock = false;
for (const Child &child : Get<ChildTable>().Iterate(Child::kInStateValid))
{
if (child.IsBlockingParentDowngrade())
{
mBlockDowngrade = true;
shouldBlock = true;
break;
}
}
mRoleTransitioner.SetDowngradeBlocked(shouldBlock);
}
#endif
+64 -46
View File
@@ -771,7 +771,7 @@ public:
* @retval kErrorNone Successfully set the router-eligible configuration.
* @retval kErrorNotCapable The device is not capable of becoming a router.
*/
Error SetRouterEligible(bool aEligible);
Error SetRouterEligible(bool aEligible) { return mRoleTransitioner.SetRouterEligible(aEligible); }
/**
* Indicates whether the router role is currently allowed.
@@ -782,7 +782,7 @@ public:
* @retval TRUE If the router role is allowed.
* @retval FALSE If the router role is not allowed.
*/
bool IsRouterRoleAllowed(void) const { return mRouterRoleAllowed; }
bool IsRouterRoleAllowed(void) const { return mRoleTransitioner.IsRouterRoleAllowed(); }
/**
* Indicates whether a node is the only router on the network.
@@ -928,14 +928,14 @@ public:
*
* @returns The ROUTER_SELECTION_JITTER value in seconds.
*/
uint8_t GetRouterSelectionJitter(void) const { return mRouterRoleTransition.GetJitter(); }
uint8_t GetRouterSelectionJitter(void) const { return mRoleTransitioner.GetJitter(); }
/**
* Sets the ROUTER_SELECTION_JITTER value.
*
* @param[in] aRouterJitter The router selection jitter value (in seconds).
*/
void SetRouterSelectionJitter(uint8_t aRouterJitter) { mRouterRoleTransition.SetJitter(aRouterJitter); }
void SetRouterSelectionJitter(uint8_t aRouterJitter) { mRoleTransitioner.SetJitter(aRouterJitter); }
/**
* Indicates whether or not router role transition (upgrade from REED or downgrade to REED) is pending.
@@ -943,7 +943,7 @@ public:
* @retval TRUE Router role transition is pending.
* @retval FALSE Router role transition is not pending
*/
bool IsRouterRoleTransitionPending(void) const { return mRouterRoleTransition.IsPending(); }
bool IsRouterRoleTransitionPending(void) const { return mRoleTransitioner.IsTransitionPending(); }
/**
* Returns the current timeout delay in seconds till router role transition (upgrade from REED or downgrade to
@@ -951,35 +951,35 @@ public:
*
* @returns The timeout in seconds till router role transition, or zero if not pending role transition.
*/
uint8_t GetRouterRoleTransitionTimeout(void) const { return mRouterRoleTransition.GetTimeout(); }
uint8_t GetRouterRoleTransitionTimeout(void) const { return mRoleTransitioner.GetTimeout(); }
/**
* Returns the ROUTER_UPGRADE_THRESHOLD value.
*
* @returns The ROUTER_UPGRADE_THRESHOLD value.
*/
uint8_t GetRouterUpgradeThreshold(void) const { return mRouterUpgradeThreshold; }
uint8_t GetRouterUpgradeThreshold(void) const { return mRoleTransitioner.GetUpgradeThreshold(); }
/**
* Sets the ROUTER_UPGRADE_THRESHOLD value.
*
* @param[in] aThreshold The ROUTER_UPGRADE_THRESHOLD value.
*/
void SetRouterUpgradeThreshold(uint8_t aThreshold) { mRouterUpgradeThreshold = aThreshold; }
void SetRouterUpgradeThreshold(uint8_t aThreshold) { mRoleTransitioner.SetUpgradeThreshold(aThreshold); }
/**
* Returns the ROUTER_DOWNGRADE_THRESHOLD value.
*
* @returns The ROUTER_DOWNGRADE_THRESHOLD value.
*/
uint8_t GetRouterDowngradeThreshold(void) const { return mRouterDowngradeThreshold; }
uint8_t GetRouterDowngradeThreshold(void) const { return mRoleTransitioner.GetDowngradeThreshold(); }
/**
* Sets the ROUTER_DOWNGRADE_THRESHOLD value.
*
* @param[in] aThreshold The ROUTER_DOWNGRADE_THRESHOLD value.
*/
void SetRouterDowngradeThreshold(uint8_t aThreshold) { mRouterDowngradeThreshold = aThreshold; }
void SetRouterDowngradeThreshold(uint8_t aThreshold) { mRoleTransitioner.SetDowngradeThreshold(aThreshold); }
/**
* Indicates whether or not downgrading from router role to REED is blocked.
@@ -997,7 +997,7 @@ public:
* @retval TRUE The device is blocked from downgrading.
* @retval FALSE The device is not blocked from downgrading.
*/
bool IsDowngradeBlocked(void) const { return mBlockDowngrade; }
bool IsDowngradeBlocked(void) const { return mRoleTransitioner.IsDowngradeBlocked(); }
/**
* Returns the MLE_CHILD_ROUTER_LINKS value.
@@ -1022,7 +1022,7 @@ public:
* @retval TRUE If the REED is going to become a Router soon.
* @retval FALSE If the REED is not going to become a Router soon.
*/
bool WillBecomeRouterSoon(void) const;
bool WillBecomeRouterSoon(void) const { return mRoleTransitioner.WillBecomeRouterSoon(); }
/**
* Removes a link to a neighbor.
@@ -1179,14 +1179,14 @@ public:
*
* @param[in] aEnabled TRUE if the device was commissioned using CCM, FALSE otherwise.
*/
void SetCcmEnabled(bool aEnabled);
void SetCcmEnabled(bool aEnabled) { mRoleTransitioner.SetCcmEnabled(aEnabled); }
/**
* Sets whether the Security Policy TLV version-threshold for routing (VR field) is enabled.
*
* @param[in] aEnabled TRUE to enable Security Policy TLV version-threshold for routing, FALSE otherwise.
*/
void SetThreadVersionCheckEnabled(bool aEnabled);
void SetThreadVersionCheckEnabled(bool aEnabled) { mRoleTransitioner.SetThreadVersionCheckEnabled(aEnabled); }
/**
* Gets the current Interval Max value used by Advertisement trickle timer.
@@ -1473,16 +1473,6 @@ private:
kAddrSolicitUnrecognizedReason = 6,
};
#if OPENTHREAD_FTD
enum UpdateRouterRoleAllowedReason : uint8_t // Used in `UpdateRouterRoleAllowed()`
{
kReasonMleInit,
kReasonDeviceModeChanged,
kReasonConfigParameterChanged,
kReasonSecurityPolicyChanged,
};
#endif
enum MessageAction : uint8_t
{
kMessageSend,
@@ -2216,26 +2206,65 @@ private:
#if OPENTHREAD_FTD
class RouterRoleTransition
class RoleTransitioner : public InstanceLocator
{
public:
RouterRoleTransition(void);
// Manages the router role upgrade/downgrade transitions
bool IsPending(void) const { return (mTimeout != 0); }
public:
enum UpdateRouterRoleAllowedReason : uint8_t // Used in `UpdateRouterRoleAllowed()`
{
kReasonMleInit,
kReasonDeviceModeChanged,
kReasonConfigParameterChanged,
kReasonSecurityPolicyChanged,
};
explicit RoleTransitioner(Instance &aInstance);
bool IsRouterRoleAllowed(void) const { return mRouterRoleAllowed; }
void UpdateRouterRoleAllowed(UpdateRouterRoleAllowedReason aReason);
Error SetRouterEligible(bool aEligible);
uint8_t GetJitter(void) const { return mJitter; }
void SetJitter(uint8_t aJitter) { mJitter = aJitter; }
uint8_t GetUpgradeThreshold(void) const { return mUpgradeThreshold; }
void SetUpgradeThreshold(uint8_t aThreshold) { mUpgradeThreshold = aThreshold; }
uint8_t GetDowngradeThreshold(void) const { return mDowngradeThreshold; }
void SetDowngradeThreshold(uint8_t aThreshold) { mDowngradeThreshold = aThreshold; }
bool IsDowngradeBlocked(void) const { return mDowngradeBlocked; }
void SetDowngradeBlocked(bool aBlocked) { mDowngradeBlocked = aBlocked; }
bool IsTransitionPending(void) const { return (mTimeout != 0); }
bool WillBecomeRouterSoon(void) const;
void StartTimeout(void);
void StopTimeout(void) { mTimeout = 0; }
void IncreaseTimeout(uint8_t aIncrement) { mTimeout += aIncrement; }
uint8_t GetTimeout(void) const { return mTimeout; }
bool HandleTimeTick(void);
uint8_t GetJitter(void) const { return mJitter; }
void SetJitter(uint8_t aJitter) { mJitter = aJitter; }
bool IsRouterCountBelowUpgradeThreshold(void) const;
void HandleTimeTick(void);
void DecideWhetherToUpgrade(void);
void DecideWhetherToDowngrade(uint8_t aNeighborId, const RouteTlv &aRouteTlv);
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
void SetCcmEnabled(bool aEnabled);
void SetThreadVersionCheckEnabled(bool aEnabled);
#endif
private:
bool DetermineIfRouterRoleAllowed(void) const;
bool NeighborHasComparableConnectivity(uint8_t aNeighborId, const RouteTlv &aRouteTlv) const;
bool mRouterEligible : 1;
bool mRouterRoleAllowed : 1;
bool mDowngradeBlocked : 1;
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
bool mCcmEnabled : 1;
bool mThreadVersionCheckEnabled : 1;
#endif
uint8_t mTimeout;
uint8_t mJitter;
uint8_t mUpgradeThreshold;
uint8_t mDowngradeThreshold;
};
#endif
#endif // OPENTHREAD_FTD
//------------------------------------------------------------------------------------------------------------------
#if OPENTHREAD_CONFIG_P2P_ENABLE
@@ -2419,8 +2448,6 @@ private:
void ClearAlternateRloc16(void);
uint8_t SelectLeaderId(void) const;
uint32_t SelectPartitionId(void) const;
bool DetermineIfRouterRoleAllowed(void) const;
void UpdateRouterRoleAllowed(UpdateRouterRoleAllowedReason aReason);
void DetermineConnectivity(Connectivity &aConnectivity) const;
void HandleDetachStart(void);
void HandleChildStart(void);
@@ -2468,8 +2495,6 @@ private:
void SetChildStateToValid(Child &aChild);
bool HasChildren(void);
void RemoveChildren(void);
bool ShouldDowngrade(uint8_t aNeighborId, const RouteTlv &aRouteTlv) const;
bool NeighborHasComparableConnectivity(const RouteTlv &aRouteTlv, uint8_t aNeighborId) const;
void HandleAdvertiseTrickleTimer(void);
void HandleTimeTick(void);
void HandleRouterTableEvent(RouterTable::Events aEvents);
@@ -2548,15 +2573,8 @@ private:
#if OPENTHREAD_FTD
bool mRouterEligible : 1;
bool mRouterRoleAllowed : 1;
bool mBlockDowngrade : 1;
bool mAddressSolicitPending : 1;
bool mAddressSolicitRejected : 1;
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
bool mCcmEnabled : 1;
bool mThreadVersionCheckEnabled : 1;
#endif
bool mAddressSolicitPending : 1;
bool mAddressSolicitRejected : 1;
uint8_t mRouterId;
uint8_t mPreviousRouterId;
uint8_t mNetworkIdTimeout;
@@ -2579,7 +2597,7 @@ private:
TrickleTimer mAdvertiseTrickleTimer;
ChildTable mChildTable;
RouterTable mRouterTable;
RouterRoleTransition mRouterRoleTransition;
RoleTransitioner mRoleTransitioner;
Ip6::Netif::UnicastAddress mLeaderAloc;
#if OPENTHREAD_CONFIG_MLE_DEVICE_PROPERTY_LEADER_WEIGHT_ENABLE
DeviceProperties mDeviceProperties;
+141 -116
View File
@@ -76,12 +76,12 @@ void Mle::HandlePartitionChange(void)
mRouterTable.Clear();
}
bool Mle::DetermineIfRouterRoleAllowed(void) const
bool Mle::RoleTransitioner::DetermineIfRouterRoleAllowed(void) const
{
bool allowed = false;
const SecurityPolicy &secPolicy = Get<KeyManager>().GetSecurityPolicy();
VerifyOrExit(mRouterEligible && IsFullThreadDevice());
VerifyOrExit(mRouterEligible && Get<Mle>().IsFullThreadDevice());
#if OPENTHREAD_CONFIG_THREAD_VERSION == OT_THREAD_VERSION_1_1
VerifyOrExit(secPolicy.mRoutersEnabled);
@@ -116,14 +116,14 @@ exit:
return allowed;
}
void Mle::UpdateRouterRoleAllowed(UpdateRouterRoleAllowedReason aReason)
void Mle::RoleTransitioner::UpdateRouterRoleAllowed(UpdateRouterRoleAllowedReason aReason)
{
bool allowed = DetermineIfRouterRoleAllowed();
VerifyOrExit(allowed != mRouterRoleAllowed);
mRouterRoleAllowed = allowed;
if (IsAttached())
if (Get<Mle>().IsAttached())
{
Get<Mac::Mac>().SetBeaconEnabled(mRouterRoleAllowed);
}
@@ -131,12 +131,12 @@ void Mle::UpdateRouterRoleAllowed(UpdateRouterRoleAllowedReason aReason)
// Take action based on the current role, the new `mRouterRoleAllowed`,
// and the reason for the change.
if (IsChild() && mRouterRoleAllowed && (aReason == kReasonConfigParameterChanged))
if (Get<Mle>().IsChild() && mRouterRoleAllowed && (aReason == kReasonConfigParameterChanged))
{
mRouterRoleTransition.StartTimeout();
StartTimeout();
}
if (IsRouterOrLeader())
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
@@ -157,16 +157,16 @@ void Mle::UpdateRouterRoleAllowed(UpdateRouterRoleAllowedReason aReason)
break;
case kReasonConfigParameterChanged:
IgnoreError(BecomeDetached());
IgnoreError(Get<Mle>().BecomeDetached());
break;
case kReasonSecurityPolicyChanged:
VerifyOrExit(!mRouterRoleTransition.IsPending());
mRouterRoleTransition.StartTimeout();
VerifyOrExit(!IsTransitionPending());
StartTimeout();
if (IsLeader())
if (Get<Mle>().IsLeader())
{
mRouterRoleTransition.IncreaseTimeout(kLeaderDowngradeExtraDelay);
IncreaseTimeout(kLeaderDowngradeExtraDelay);
}
break;
}
@@ -176,11 +176,11 @@ exit:
return;
}
Error Mle::SetRouterEligible(bool aEligible)
Error Mle::RoleTransitioner::SetRouterEligible(bool aEligible)
{
Error error = kErrorNone;
if (!IsFullThreadDevice())
if (!Get<Mle>().IsFullThreadDevice())
{
VerifyOrExit(!aEligible, error = kErrorNotCapable);
}
@@ -195,13 +195,13 @@ exit:
}
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
void Mle::SetCcmEnabled(bool aEnabled)
void Mle::RoleTransitioner::SetCcmEnabled(bool aEnabled)
{
mCcmEnabled = aEnabled;
UpdateRouterRoleAllowed(kReasonConfigParameterChanged);
}
void Mle::SetThreadVersionCheckEnabled(bool aEnabled)
void Mle::RoleTransitioner::SetThreadVersionCheckEnabled(bool aEnabled)
{
mThreadVersionCheckEnabled = aEnabled;
UpdateRouterRoleAllowed(kReasonConfigParameterChanged);
@@ -241,7 +241,7 @@ Error Mle::BecomeRouter(RouterUpgradeReason aReason)
LogInfo("Attempt to become router, reason:%s", RouterUpgradeReasonToString(aReason));
Get<MeshForwarder>().SetRxOnWhenIdle(true);
mRouterRoleTransition.StopTimeout();
mRoleTransitioner.StopTimeout();
error = SendAddressSolicit(aReason);
@@ -341,7 +341,7 @@ void Mle::HandleChildStart(void)
{
mAddressSolicitRejected = false;
mRouterRoleTransition.StartTimeout();
mRoleTransitioner.StartTimeout();
StopLeader();
Get<TimeTicker>().RegisterReceiver(TimeTicker::kMle);
@@ -409,7 +409,7 @@ void Mle::HandleChildStart(void)
exit:
if (mRouterTable.GetActiveRouterCount() >= mRouterUpgradeThreshold &&
if (!mRoleTransitioner.IsRouterCountBelowUpgradeThreshold() &&
(!IsRouterIdValid(mPreviousRouterId) || !HasChildren()))
{
SetRouterId(kInvalidRouterId);
@@ -515,7 +515,7 @@ void Mle::HandleRouterTableEvent(RouterTable::Events aEvents)
if (aEvents & RouterTable::kEventRouterAdded)
{
mBlockDowngrade = false;
mRoleTransitioner.SetDowngradeBlocked(false);
}
if (IsRouterOrLeader() && mAdvertiseTrickleTimer.IsRunning())
@@ -1305,12 +1305,9 @@ Error Mle::HandleAdvertisementOnFtd(RxInfo &aRxInfo, uint16_t aSourceAddress, co
ExitNow(error = kErrorDetached);
}
if (!mRouterRoleTransition.IsPending() && (mRouterTable.GetActiveRouterCount() < mRouterUpgradeThreshold))
{
mRouterRoleTransition.StartTimeout();
}
mRouterTable.UpdateRouterOnFtdChild(routeTlv, routerId);
mRoleTransitioner.DecideWhetherToUpgrade();
}
else
{
@@ -1331,12 +1328,12 @@ Error Mle::HandleAdvertisementOnFtd(RxInfo &aRxInfo, uint16_t aSourceAddress, co
}
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Update routers as a router or leader.
// Inform `RoleTransitioner` to decide whether we need to downgrade
if (IsRouter() && ShouldDowngrade(routerId, routeTlv))
{
mRouterRoleTransition.StartTimeout();
}
mRoleTransitioner.DecideWhetherToDowngrade(routerId, routeTlv);
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Update routers as a router or leader.
router = mRouterTable.FindRouterById(routerId);
VerifyOrExit(router != nullptr);
@@ -1589,55 +1586,7 @@ void Mle::HandleTimeTick(void)
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Role transitions
if (mRouterRoleTransition.HandleTimeTick())
{
// `mRouterRoleTransition.HandleTimeTick()` returns `true`
// if role transition timeout expires.
switch (mRole)
{
case kRoleDisabled:
case kRoleDetached:
break;
case kRoleChild:
if (mRouterTable.GetActiveRouterCount() < mRouterUpgradeThreshold && HasNeighborWithGoodLinkQuality())
{
IgnoreError(BecomeRouter(kReasonTooFewRouters));
}
else
{
mAnnounceHandler.HandleRouterRoleTransitionAttemptDone();
}
if (!mAdvertiseTrickleTimer.IsRunning())
{
SendMulticastAdvertisement();
mAdvertiseTrickleTimer.Start(TrickleTimer::kModePlainTimer, kReedAdvIntervalMin, kReedAdvIntervalMax);
}
break;
case kRoleRouter:
if (mRouterTable.GetActiveRouterCount() > mRouterDowngradeThreshold)
{
LogNote("Downgrade to REED");
mAttacher.Attach(kDowngradeToReed);
}
OT_FALL_THROUGH;
case kRoleLeader:
if (!IsRouterRoleAllowed())
{
LogInfo("Router role no longer allowed");
IgnoreError(BecomeDetached());
}
break;
}
}
mRoleTransitioner.HandleTimeTick();
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Check Leader's age
@@ -3485,13 +3434,13 @@ void Mle::HandleAddressSolicitResponse(Coap::Msg *aMsg, Error aResult)
// router from downgrading back to a REED to ensure this
// child remains connected.
//
// The `mBlockDowngrade` is cleared in various situations:
// 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);
mBlockDowngrade = true;
mRoleTransitioner.SetDowngradeBlocked(true);
}
exit:
@@ -3508,18 +3457,18 @@ exit:
return error;
}
bool Mle::WillBecomeRouterSoon(void) const
bool Mle::RoleTransitioner::WillBecomeRouterSoon(void) const
{
static constexpr uint8_t kMaxDelay = 10;
bool willBecomeRouter = false;
VerifyOrExit(IsRouterRoleAllowed() && IsChild());
VerifyOrExit(!mAddressSolicitRejected);
VerifyOrExit(IsRouterRoleAllowed() && Get<Mle>().IsChild());
VerifyOrExit(!Get<Mle>().mAddressSolicitRejected);
if (!mAddressSolicitPending)
if (!Get<Mle>().mAddressSolicitPending)
{
VerifyOrExit(mRouterRoleTransition.IsPending() && mRouterRoleTransition.GetTimeout() <= kMaxDelay);
VerifyOrExit(IsTransitionPending() && GetTimeout() <= kMaxDelay);
}
willBecomeRouter = true;
@@ -3597,7 +3546,7 @@ void Mle::ProcessAddressSolicit(AddrSolicitInfo &aInfo)
switch (aInfo.mReason)
{
case kReasonTooFewRouters:
VerifyOrExit(mRouterTable.GetActiveRouterCount() < mRouterUpgradeThreshold);
VerifyOrExit(mRoleTransitioner.IsRouterCountBelowUpgradeThreshold());
break;
case kReasonHaveChildIdRequest:
@@ -3605,7 +3554,7 @@ void Mle::ProcessAddressSolicit(AddrSolicitInfo &aInfo)
break;
case kReasonBorderRouterRequest:
if ((mRouterTable.GetActiveRouterCount() >= mRouterUpgradeThreshold) &&
if (!mRoleTransitioner.IsRouterCountBelowUpgradeThreshold() &&
(Get<NetworkData::Leader>().CountBorderRouters(NetworkData::kRouterRoleOnly) >=
kRouterUpgradeBorderRouterRequestThreshold))
{
@@ -3781,30 +3730,29 @@ void Mle::FillConnectivityTlvValue(ConnectivityTlvValue &aTlvValue) const
aTlvValue.InitFrom(connectivity);
}
bool Mle::ShouldDowngrade(uint8_t aNeighborId, const RouteTlv &aRouteTlv) const
void Mle::RoleTransitioner::DecideWhetherToDowngrade(uint8_t aNeighborId, const RouteTlv &aRouteTlv)
{
// 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 `aRouteTlv`.
bool shouldDowngrade = false;
uint8_t activeRouterCount = mRouterTable.GetActiveRouterCount();
uint8_t activeRouterCount = Get<RouterTable>().GetActiveRouterCount();
uint8_t count;
VerifyOrExit(IsRouter());
VerifyOrExit(mRouterTable.IsAllocated(aNeighborId));
VerifyOrExit(!mBlockDowngrade);
VerifyOrExit(Get<Mle>().IsRouter());
VerifyOrExit(Get<RouterTable>().IsAllocated(aNeighborId));
VerifyOrExit(!mDowngradeBlocked);
VerifyOrExit(!mRouterRoleTransition.IsPending());
VerifyOrExit(!IsTransitionPending());
VerifyOrExit(activeRouterCount > mRouterDowngradeThreshold);
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 : mRouterTable)
for (const Router &router : Get<RouterTable>())
{
if (!router.IsStateValid() || (router.GetTwoWayLinkQuality() < kLinkQuality2))
{
@@ -3823,28 +3771,29 @@ bool Mle::ShouldDowngrade(uint8_t aNeighborId, const RouteTlv &aRouteTlv) const
// Check that we have fewer children than three times the number
// of excess routers (defined as the difference between number of
// active routers and `mRouterDowngradeThreshold`).
// active routers and `mDowngradeThreshold`).
count = activeRouterCount - mRouterDowngradeThreshold;
VerifyOrExit(mChildTable.GetNumChildren(Child::kInStateValid) < count * 3);
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(aRouteTlv, aNeighborId));
VerifyOrExit(NeighborHasComparableConnectivity(aNeighborId, aRouteTlv));
#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
shouldDowngrade = true;
// All conditions are satisfied to start downgrade.
StartTimeout();
exit:
return shouldDowngrade;
return;
}
bool Mle::NeighborHasComparableConnectivity(const RouteTlv &aRouteTlv, uint8_t aNeighborId) const
bool Mle::RoleTransitioner::NeighborHasComparableConnectivity(uint8_t aNeighborId, const RouteTlv &aRouteTlv) const
{
// Check whether the neighboring router with Router ID `aNeighborId`
// (along with its `aRouteTlv`) has as good or better-quality links
@@ -3860,12 +3809,12 @@ bool Mle::NeighborHasComparableConnectivity(const RouteTlv &aRouteTlv, uint8_t a
LinkQuality localLinkQuality;
LinkQuality peerLinkQuality;
if ((routerId == mRouterId) || (routerId == aNeighborId))
if ((routerId == Get<Mle>().mRouterId) || (routerId == aNeighborId))
{
continue;
}
router = mRouterTable.FindRouterById(routerId);
router = Get<RouterTable>().FindRouterById(routerId);
if ((router == nullptr) || !router->IsStateValid())
{
@@ -4017,26 +3966,102 @@ const char *Mle::RouterUpgradeReasonToString(uint8_t aReason)
#endif // OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
//----------------------------------------------------------------------------------------------------------------------
// RouterRoleTransition
// RoleTransitioner
Mle::RouterRoleTransition::RouterRoleTransition(void)
: mTimeout(0)
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::RouterRoleTransition::StartTimeout(void) { mTimeout = 1 + Random::NonCrypto::GetUint8InRange(0, mJitter); }
void Mle::RoleTransitioner::StartTimeout(void) { mTimeout = 1 + Random::NonCrypto::GetUint8InRange(0, mJitter); }
bool Mle::RouterRoleTransition::HandleTimeTick(void)
bool Mle::RoleTransitioner::IsRouterCountBelowUpgradeThreshold(void) const
{
bool expired = false;
return Get<RouterTable>().GetActiveRouterCount() < mUpgradeThreshold;
}
VerifyOrExit(mTimeout > 0);
mTimeout--;
expired = (mTimeout == 0);
void Mle::RoleTransitioner::DecideWhetherToUpgrade(void)
{
VerifyOrExit(Get<Mle>().IsChild());
VerifyOrExit(IsRouterRoleAllowed());
VerifyOrExit(!IsTransitionPending());
VerifyOrExit(IsRouterCountBelowUpgradeThreshold());
StartTimeout();
exit:
return expired;
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