[mle] separate role transition and leader age checks (#12853)

This commit updates `Mle::HandleTimeTick()` to separate the processing
of role transitions and the checking of the leader's age into two
distinct `switch` statements.

Previously, these two checks were combined in a single `switch`
statement with complex fall-through logic. This structure contained
two issues:

1. For a device in the `kRoleChild` state, if the role transition
   timeout expired, the code would execute `ExitNow()`. This
   unintentionally skipped the leader age check and the rest of the
   operations in `Mle::HandleTimeTick()`, such as updating the
   `ChildTable` and `RouterTable`.
2. A non-router-eligible child would incorrectly fall through and
   perform the leader age check. The new logic adds an explicit check
   using `IsRouterEligible()` to ensure only router-eligible children
   monitor the leader's age.

By separating the logic into two blocks, the code is simplified and
we avoid the brittle fall-through behavior and ensure that all time
tick operations are consistently executed regardless of the device's
role or role transition state.
This commit is contained in:
Abtin Keshavarzian
2026-04-08 16:13:09 -05:00
committed by GitHub
parent 2a120a9ddf
commit 2b3b56def7
+45 -28
View File
@@ -1533,8 +1533,6 @@ exit:
void Mle::HandleTimeTick(void)
{
bool roleTransitionTimeoutExpired = false;
VerifyOrExit(IsFullThreadDevice(), Get<TimeTicker>().UnregisterReceiver(TimeTicker::kMle));
if (mPreviousPartitionIdTimeout > 0)
@@ -1555,16 +1553,18 @@ void Mle::HandleTimeTick(void)
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Role transitions
roleTransitionTimeoutExpired = mRouterRoleTransition.HandleTimeTick();
switch (mRole)
if (mRouterRoleTransition.HandleTimeTick())
{
case kRoleDetached:
break;
// `mRouterRoleTransition.HandleTimeTick()` returns `true`
// if role transition timeout expires.
case kRoleChild:
if (roleTransitionTimeoutExpired)
switch (mRole)
{
case kRoleDisabled:
case kRoleDetached:
break;
case kRoleChild:
if (mRouterTable.GetActiveRouterCount() < mRouterUpgradeThreshold && HasNeighborWithGoodLinkQuality())
{
IgnoreError(BecomeRouter(kReasonTooFewRouters));
@@ -1581,7 +1581,42 @@ void Mle::HandleTimeTick(void)
mAdvertiseTrickleTimer.Start(TrickleTimer::kModePlainTimer, kReedAdvIntervalMin, kReedAdvIntervalMax);
}
ExitNow();
break;
case kRoleRouter:
if (mRouterTable.GetActiveRouterCount() > mRouterDowngradeThreshold)
{
LogNote("Downgrade to REED");
mAttacher.Attach(kDowngradeToReed);
}
OT_FALL_THROUGH;
case kRoleLeader:
if (!IsRouterEligible())
{
LogInfo("No longer router eligible");
IgnoreError(BecomeDetached());
}
break;
}
}
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Check Leader's age
switch (mRole)
{
case kRoleDisabled:
case kRoleDetached:
case kRoleLeader:
break;
case kRoleChild:
if (!IsRouterEligible())
{
break;
}
OT_FALL_THROUGH;
@@ -1595,25 +1630,7 @@ void Mle::HandleTimeTick(void)
mAttacher.Attach(kSamePartition);
}
if (roleTransitionTimeoutExpired && mRouterTable.GetActiveRouterCount() > mRouterDowngradeThreshold)
{
LogNote("Downgrade to REED");
mAttacher.Attach(kDowngradeToReed);
}
OT_FALL_THROUGH;
case kRoleLeader:
if (roleTransitionTimeoutExpired && !IsRouterEligible())
{
LogInfo("No longer router eligible");
IgnoreError(BecomeDetached());
}
break;
case kRoleDisabled:
OT_ASSERT(false);
}
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -