From 2b3b56def70d7f7f8ec2c7ea93a3d9517f18aba2 Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Wed, 8 Apr 2026 14:13:09 -0700 Subject: [PATCH] [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. --- src/core/thread/mle_ftd.cpp | 73 +++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 28 deletions(-) diff --git a/src/core/thread/mle_ftd.cpp b/src/core/thread/mle_ftd.cpp index 7a48bfc6e..9d1c70534 100644 --- a/src/core/thread/mle_ftd.cpp +++ b/src/core/thread/mle_ftd.cpp @@ -1533,8 +1533,6 @@ exit: void Mle::HandleTimeTick(void) { - bool roleTransitionTimeoutExpired = false; - VerifyOrExit(IsFullThreadDevice(), Get().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); } //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -