[mle] introduce PrevRoleRestorer to manage role restoration (#11731)

This commit introduces `Mle::PrevRoleRestorer` to consolidate the
logic for restoring a device's previous role (child or router/leader)
after an MLE restart.

This new class replaces and encapsulates the functionality from the
now-removed `RouterRoleRestorer` class and `RestorePrevRole()` method.

`PrevRoleRestorer` manages its own timer and retransmission logic. It
handles sending Child Update Requests to restore a child role, or
multicast Link Requests to restore a router/leader role. It also adds
a small random delay before the first transmission attempt to avoid
synchronized transmissions when multiple devices restart at once.

This change simplifies the `Mle` class by centralizing all role
restoration logic into a single component, making future enhancements
to this process easier.
This commit is contained in:
Abtin Keshavarzian
2025-07-23 18:46:11 -07:00
committed by GitHub
parent 382c4c44c4
commit fcfee4ec7a
3 changed files with 242 additions and 174 deletions
+156 -54
View File
@@ -74,6 +74,7 @@ Mle::Mle(Instance &aInstance)
, mNeighborTable(aInstance)
, mDelayedSender(aInstance)
, mSocket(aInstance, *this)
, mPrevRoleRestorer(aInstance)
, mDetacher(aInstance)
, mRetxTracker(aInstance)
, mAnnounceHandler(aInstance)
@@ -114,7 +115,6 @@ Mle::Mle(Instance &aInstance)
, mAdvertiseTrickleTimer(aInstance, Mle::HandleAdvertiseTrickleTimer)
, mChildTable(aInstance)
, mRouterTable(aInstance)
, mRouterRoleRestorer(aInstance)
#endif // OPENTHREAD_FTD
{
mParent.Init(aInstance);
@@ -231,7 +231,7 @@ Error Mle::Start(StartMode aMode)
mReattachState =
(Get<MeshCoP::ActiveDatasetManager>().Restore() == kErrorNone) ? kReattachActive : kReattachStop;
if (RestorePrevRole() == kErrorNone)
if (mPrevRoleRestorer.Start() == kErrorNone)
{
ExitNow();
}
@@ -258,6 +258,7 @@ void Mle::Stop(StopMode aMode)
VerifyOrExit(!IsDisabled());
mDelayedSender.Stop();
mPrevRoleRestorer.Stop();
mAnnounceHandler.Stop();
Get<KeyManager>().Stop();
SetStateDetached();
@@ -266,54 +267,12 @@ void Mle::Stop(StopMode aMode)
Get<ThreadNetif>().RemoveUnicastAddress(mMeshLocalRloc);
Get<ThreadNetif>().RemoveUnicastAddress(mMeshLocalEid);
#if OPENTHREAD_FTD
mRouterRoleRestorer.Stop();
#endif
SetRole(kRoleDisabled);
exit:
mDetacher.HandleStop();
}
Error Mle::RestorePrevRole(void)
{
// Restores the device to its previously saved role after MLE
// start.
//
// Returns `kErrorNone` if the restoration process begins
// successfully. An error is returned if:
// - No previous role was saved.
// - The saved role info is inconsistent (e.g., RLOC16 indicates a
// router/leader, but `mLastSavedRole` doesn't match).
// - The last role is "Child," but no valid parent is available.
Error error = kErrorFailed;
VerifyOrExit(IsDetached());
VerifyOrExit(GetRloc16() != kInvalidRloc16);
if (IsRouterRloc16(GetRloc16()))
{
#if OPENTHREAD_FTD
VerifyOrExit(mLastSavedRole == kRoleRouter || mLastSavedRole == kRoleLeader);
VerifyOrExit(IsRouterEligible());
Get<MeshForwarder>().SetRxOnWhenIdle(true);
mRouterRoleRestorer.Start(mLastSavedRole);
error = kErrorNone;
#endif
ExitNow();
}
VerifyOrExit(mLastSavedRole == kRoleChild);
VerifyOrExit(mParent.IsStateValidOrRestoring());
IgnoreError(SendChildUpdateRequestToParent());
error = kErrorNone;
exit:
return error;
}
const Counters &Mle::GetCounters(void)
{
UpdateRoleTimeCounters(mRole);
@@ -789,6 +748,7 @@ void Mle::SetStateChild(uint16_t aRloc16)
mReattachState = kReattachStop;
Get<Mac::Mac>().SetBeaconEnabled(false);
mRetxTracker.UpdateOnRoleChangeToChild();
mPrevRoleRestorer.Stop();
#if OPENTHREAD_FTD
if (IsFullThreadDevice())
@@ -1471,14 +1431,6 @@ void Mle::HandleAttachTimer(void)
bool shouldAnnounce = true;
ParentRequestType type;
#if OPENTHREAD_FTD
if (IsDetached() && mRouterRoleRestorer.IsActive())
{
mRouterRoleRestorer.HandleTimer();
ExitNow();
}
#endif
// First, check if we are waiting to receive parent responses and
// found an acceptable parent candidate.
@@ -1938,11 +1890,19 @@ Error Mle::SendChildUpdateRequestToParent(ChildUpdateRequestMode aMode)
mDelayedSender.RemoveScheduledChildUpdateRequestToParent();
// Track Child Update retx, except when gracefully detaching
// i.e., sending Child Update with zero timeout.
// i.e., sending Child Update with zero timeout, or restoring
// previous child role (re-establishing link with parent).
if (aMode != kAppendZeroTimeout)
switch (aMode)
{
case kNormalChildUpdateRequest:
case kAppendChallengeTlv:
mRetxTracker.UpdateOnChildUpdateRequestTx();
break;
case kAppendZeroTimeout:
case kToRestoreChildRole:
break;
}
VerifyOrExit((message = NewMleMessage(kCommandChildUpdateRequest)) != nullptr, error = kErrorNoBufs);
@@ -5330,6 +5290,148 @@ void Mle::ParentCandidate::CopyTo(Parent &aParent) const
aParent = *candidateAsParent;
}
//----------------------------------------------------------------------------------------------------------------------
// PrevRoleRestorer
Mle::PrevRoleRestorer::PrevRoleRestorer(Instance &aInstance)
: InstanceLocator(aInstance)
, mState(kIdle)
, mAttempts(0)
, mTimer(aInstance)
{
}
Error Mle::PrevRoleRestorer::Start(void)
{
// Starts the process of restoring the device to its previously
// saved role after MLE start.
//
// Returns `kErrorNone` if the restoration process begins
// successfully. An error is returned if:
// - No previous role was saved.
// - The saved role info is inconsistent (e.g., RLOC16 indicates a
// router/leader, but `mLastSavedRole` doesn't match).
Error error = kErrorFailed;
mState = kIdle;
mAttempts = 0;
VerifyOrExit(Get<Mle>().IsDetached());
VerifyOrExit(Get<Mle>().GetRloc16() != kInvalidRloc16);
if (IsRouterRloc16(Get<Mle>().GetRloc16()))
{
#if OPENTHREAD_FTD
VerifyOrExit((Get<Mle>().mLastSavedRole == kRoleRouter) || (Get<Mle>().mLastSavedRole == kRoleLeader));
VerifyOrExit(Get<Mle>().IsRouterEligible());
Get<MeshForwarder>().SetRxOnWhenIdle(true);
SetState(kRestoringRouterOrLeaderRole);
DetermineMaxLinkRequestAttempts();
mTimer.Start(Get<Mle>().GenerateRandomDelay(kMaxStartDelay));
error = kErrorNone;
#endif
ExitNow();
}
VerifyOrExit(Get<Mle>().mLastSavedRole == kRoleChild);
VerifyOrExit(Get<Mle>().mParent.IsStateValidOrRestoring());
SetState(kRestoringChildRole);
mAttempts = kMaxChildUpdatesToRestoreRole;
mTimer.Start(Get<Mle>().GenerateRandomDelay(kMaxStartDelay));
error = kErrorNone;
exit:
return error;
}
void Mle::PrevRoleRestorer::Stop(void)
{
SetState(kIdle);
mTimer.Stop();
}
void Mle::PrevRoleRestorer::SetState(State aState)
{
mState = aState;
if (mState != kIdle)
{
LogInfo("Attempting to restore prev role: %s", RoleToString(Get<Mle>().mLastSavedRole));
}
}
void Mle::PrevRoleRestorer::HandleTimer(void)
{
VerifyOrExit(mState != kIdle);
if (!Get<Mle>().IsDetached())
{
Stop();
ExitNow();
}
if (mAttempts == 0)
{
LogInfo("Failed to restore prev role");
Stop();
IgnoreError(Get<Mle>().BecomeDetached());
ExitNow();
}
mAttempts--;
#if OPENTHREAD_FTD
if (mState == kRestoringRouterOrLeaderRole)
{
SendMulticastLinkRequest();
}
else
#endif
{
SendChildUpdate();
}
exit:
return;
}
void Mle::PrevRoleRestorer::SendChildUpdate(void)
{
mTimer.Start(Random::NonCrypto::AddJitter(kChildUpdateRetxDelay, kRetxJitter));
LogDebg("Sending Child Update Request to restore child role, remaining attempts: %u", mAttempts);
IgnoreError(Get<Mle>().SendChildUpdateRequestToParent(kToRestoreChildRole));
}
#if OPENTHREAD_FTD
void Mle::PrevRoleRestorer::DetermineMaxLinkRequestAttempts(void)
{
mAttempts = kMaxCriticalTxCount;
if ((Get<Mle>().mLastSavedRole == kRoleRouter) &&
(Get<Mle>().mChildTable.GetNumChildren(Child::kInStateValidOrRestoring) < kMinCriticalChildrenCount))
{
mAttempts = kMaxTxCount;
}
}
void Mle::PrevRoleRestorer::SendMulticastLinkRequest(void)
{
uint32_t delay;
delay = (mAttempts == 0) ? kLinkRequestTimeout
: Random::NonCrypto::GetUint32InRange(kMulticastRetxDelayMin, kMulticastRetxDelayMax);
mTimer.Start(delay);
LogDebg("Sending multicast Link Request to restore role, remaining attempts: %u", mAttempts);
Get<Mle>().SendLinkRequest(nullptr);
}
#endif // OPENTHREAD_FTD
//---------------------------------------------------------------------------------------------------------------------
// Detacher
+79 -39
View File
@@ -1356,6 +1356,7 @@ private:
kNormalChildUpdateRequest, // Normal Child Update Request.
kAppendChallengeTlv, // Append Challenge TLV to Child Update Request even if currently attached.
kAppendZeroTimeout, // Use zero timeout when appending Timeout TLV (used for graceful detach).
kToRestoreChildRole, // To restore previous child role (upon restart), re-establishing link with parent.
};
enum SecuritySuite : uint8_t
@@ -1717,6 +1718,71 @@ private:
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void HandleRoleRestorerTimer(void) { mPrevRoleRestorer.HandleTimer(); }
class PrevRoleRestorer : public InstanceLocator
{
// Attempts to restore the device's previously saved role
// (child/router/leader) after an MLE restart (e.g., after a
// device reboot).
//
// If the previous role was router/leader, it sends multicast
// Link Requests. If the role was child, it sends Child
// Update Requests to the parent. It manages message
// retransmissions and stops the restoration attempt if the
// maximum number of attempts is exhausted, setting the
// device to a detached state.
//
// To prevent synchronized transmissions when multiple devices
// reboot at once, it adds a random delay (up to 25 ms)
// before sending the first message.
public:
PrevRoleRestorer(Instance &aInstance);
Error Start(void);
void Stop(void);
bool IsRestoringChildRole(void) const { return mState == kRestoringChildRole; }
bool IsRestoringRouterOrLeaderRole(void) const { return mState == kRestoringRouterOrLeaderRole; }
void HandleTimer(void);
#if OPENTHREAD_FTD
void GenerateRandomChallenge(void) { mChallenge.GenerateRandom(); }
const TxChallenge &GetChallenge(void) const { return mChallenge; }
#endif
private:
static constexpr uint32_t kMaxStartDelay = 25;
static constexpr uint8_t kMaxChildUpdatesToRestoreRole = kMaxChildKeepAliveAttempts;
static constexpr uint32_t kChildUpdateRetxDelay = kUnicastRetxDelay; /// 1000 msec
static constexpr uint16_t kRetxJitter = 5;
enum State : uint8_t
{
kIdle,
kRestoringChildRole,
kRestoringRouterOrLeaderRole,
};
void SetState(State aState);
void SendChildUpdate(void);
#if OPENTHREAD_FTD
void DetermineMaxLinkRequestAttempts(void);
void SendMulticastLinkRequest(void);
#endif
using DelayTimer = TimerMilliIn<Mle, &Mle::HandleRoleRestorerTimer>;
State mState;
uint8_t mAttempts;
DelayTimer mTimer;
#if OPENTHREAD_FTD
TxChallenge mChallenge;
#endif
};
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void HandleDetacherTimer(void) { mDetacher.HandleTimer(); }
class Detacher : public InstanceLocator
@@ -1943,33 +2009,7 @@ private:
uint8_t mJitter;
};
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
class RouterRoleRestorer : public InstanceLocator
{
// Attempts to restore the router or leader role after an MLE
// restart(e.g., after a device reboot) by sending multicast
// Link Requests.
public:
RouterRoleRestorer(Instance &aInstance);
bool IsActive(void) const { return mAttempts > 0; }
void Start(DeviceRole aPreviousRole);
void Stop(void) { mAttempts = 0; }
void HandleTimer(void);
void GenerateRandomChallenge(void) { mChallenge.GenerateRandom(); }
const TxChallenge &GetChallenge(void) const { return mChallenge; }
private:
void SendMulticastLinkRequest(void);
uint8_t mAttempts;
TxChallenge mChallenge;
};
#endif // OPENTHREAD_FTD
#endif
//------------------------------------------------------------------------------------------------------------------
// Methods
@@ -2224,17 +2264,18 @@ private:
uint32_t mLastAttachTime;
uint64_t mLastUpdatedTimestamp;
LeaderData mLeaderData;
Parent mParent;
NeighborTable mNeighborTable;
DelayedSender mDelayedSender;
TxChallenge mParentRequestChallenge;
ParentCandidate mParentCandidate;
MleSocket mSocket;
Counters mCounters;
Detacher mDetacher;
RetxTracker mRetxTracker;
AnnounceHandler mAnnounceHandler;
LeaderData mLeaderData;
Parent mParent;
NeighborTable mNeighborTable;
DelayedSender mDelayedSender;
TxChallenge mParentRequestChallenge;
ParentCandidate mParentCandidate;
MleSocket mSocket;
Counters mCounters;
PrevRoleRestorer mPrevRoleRestorer;
Detacher mDetacher;
RetxTracker mRetxTracker;
AnnounceHandler mAnnounceHandler;
#if OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE
ParentSearch mParentSearch;
#endif
@@ -2293,7 +2334,6 @@ private:
TrickleTimer mAdvertiseTrickleTimer;
ChildTable mChildTable;
RouterTable mRouterTable;
RouterRoleRestorer mRouterRoleRestorer;
RouterRoleTransition mRouterRoleTransition;
Ip6::Netif::UnicastAddress mLeaderAloc;
#if OPENTHREAD_CONFIG_MLE_DEVICE_PROPERTY_LEADER_WEIGHT_ENABLE
+7 -81
View File
@@ -392,6 +392,8 @@ void Mle::SetStateRouterOrLeader(DeviceRole aRole, uint16_t aRloc16, LeaderStart
SetRole(aRole);
mPrevRoleRestorer.Stop();
SetAttachState(kAttachStateIdle);
mAttachCounter = 0;
mAttachTimer.Stop();
@@ -595,8 +597,8 @@ void Mle::SendLinkRequest(Router *aRouter)
if (aRouter == nullptr)
{
mRouterRoleRestorer.GenerateRandomChallenge();
SuccessOrExit(error = message->AppendChallengeTlv(mRouterRoleRestorer.GetChallenge()));
mPrevRoleRestorer.GenerateRandomChallenge();
SuccessOrExit(error = message->AppendChallengeTlv(mPrevRoleRestorer.GetChallenge()));
destination.SetToLinkLocalAllRoutersMulticast();
}
else
@@ -866,10 +868,9 @@ void Mle::HandleLinkAcceptVariant(RxInfo &aRxInfo, MessageType aMessageType)
break;
case Neighbor::kStateInvalid:
VerifyOrExit(mRouterRoleRestorer.IsActive() && (response == mRouterRoleRestorer.GetChallenge()),
error = kErrorSecurity);
OT_FALL_THROUGH;
VerifyOrExit(mPrevRoleRestorer.IsRestoringRouterOrLeaderRole(), error = kErrorSecurity);
VerifyOrExit(response == mPrevRoleRestorer.GetChallenge(), error = kErrorSecurity);
break;
case Neighbor::kStateValid:
break;
@@ -927,7 +928,6 @@ void Mle::HandleLinkAcceptVariant(RxInfo &aRxInfo, MessageType aMessageType)
SetStateRouter(GetRloc16());
}
mRouterRoleRestorer.Stop();
mRetrieveNewNetworkData = true;
IgnoreError(SendDataRequest(aRxInfo.mMessageInfo.GetPeerAddr()));
shouldUpdateRoutes = true;
@@ -3929,80 +3929,6 @@ exit:
return expired;
}
//----------------------------------------------------------------------------------------------------------------------
// RouterRoleRestorer
Mle::RouterRoleRestorer::RouterRoleRestorer(Instance &aInstance)
: InstanceLocator(aInstance)
, mAttempts(0)
{
}
void Mle::RouterRoleRestorer::Start(DeviceRole aPreviousRole)
{
// If the device was previously the leader or had more than
// `kMinCriticalChildrenCount` children, we use more link
// request attempts.
mAttempts = 0;
switch (aPreviousRole)
{
case kRoleRouter:
if (Get<Mle>().mChildTable.GetNumChildren(Child::kInStateValidOrRestoring) < kMinCriticalChildrenCount)
{
mAttempts = kMaxTxCount;
break;
}
OT_FALL_THROUGH;
case kRoleLeader:
mAttempts = kMaxCriticalTxCount;
break;
case kRoleChild:
case kRoleDetached:
case kRoleDisabled:
break;
}
SendMulticastLinkRequest();
}
void Mle::RouterRoleRestorer::HandleTimer(void)
{
if (mAttempts > 0)
{
mAttempts--;
}
SendMulticastLinkRequest();
}
void Mle::RouterRoleRestorer::SendMulticastLinkRequest(void)
{
uint32_t delay;
VerifyOrExit(Get<Mle>().IsDetached(), mAttempts = 0);
if (mAttempts == 0)
{
IgnoreError(Get<Mle>().BecomeDetached());
ExitNow();
}
Get<Mle>().SendLinkRequest(nullptr);
delay = (mAttempts == 1) ? kLinkRequestTimeout
: Random::NonCrypto::GetUint32InRange(kMulticastRetxDelayMin, kMulticastRetxDelayMax);
Get<Mle>().mAttachTimer.Start(delay);
exit:
return;
}
} // namespace Mle
} // namespace ot