From 6e1a6ff1905c8a058caa87d55a2e151cec2b05ee Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Tue, 15 Jul 2025 14:09:40 -0700 Subject: [PATCH] [mle] add `Mle::Detacher` class to manage graceful detach process (#11723) This commit introduces a new `Mle::Detacher` class to encapsulate all state and logic for the graceful detach process. By managing its own internal state, timer, and completion callback, the `Detacher` class centralizes the detach logic, improving code clarity and maintainability. --- src/core/thread/mle.cpp | 96 ++++++++++++++++++++++++++++------------- src/core/thread/mle.hpp | 38 ++++++++++++++-- 2 files changed, 101 insertions(+), 33 deletions(-) diff --git a/src/core/thread/mle.cpp b/src/core/thread/mle.cpp index 1e3d05760..eeaba81f7 100644 --- a/src/core/thread/mle.cpp +++ b/src/core/thread/mle.cpp @@ -52,7 +52,6 @@ Mle::Mle(Instance &aInstance) , mRequestRouteTlv(false) , mHasRestored(false) , mReceivedResponseFromParent(false) - , mDetachingGracefully(false) , mInitiallyAttachedAsSleepy(false) , mRole(kRoleDisabled) , mLastSavedRole(kRoleDisabled) @@ -75,6 +74,7 @@ Mle::Mle(Instance &aInstance) , mNeighborTable(aInstance) , mDelayedSender(aInstance) , mSocket(aInstance, *this) + , mDetacher(aInstance) , mRetxTracker(aInstance) , mAnnounceHandler(aInstance) #if OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE @@ -273,11 +273,7 @@ void Mle::Stop(StopMode aMode) SetRole(kRoleDisabled); exit: - if (mDetachingGracefully) - { - mDetachingGracefully = false; - mDetachGracefullyCallback.InvokeAndClearIfSet(); - } + mDetacher.HandleStop(); } Error Mle::RestorePrevRole(void) @@ -1465,12 +1461,6 @@ void Mle::HandleAttachTimer(void) bool shouldAnnounce = true; ParentRequestType type; - if (mDetachingGracefully) - { - Stop(); - ExitNow(); - } - #if OPENTHREAD_FTD if (IsDetached() && mRouterRoleRestorer.IsActive()) { @@ -3519,14 +3509,8 @@ void Mle::HandleChildUpdateResponseOnChild(RxInfo &aRxInfo) switch (Tlv::Find(aRxInfo.mMessage, timeout)) { case kErrorNone: - if (timeout == 0 && mDetachingGracefully) - { - Stop(); - } - else - { - SetTimeout(timeout, kDoNotSendChildUpdateToParent); - } + SuccessOrExit(mDetacher.HandleChildUpdateResponse(timeout)); + SetTimeout(timeout, kDoNotSendChildUpdateToParent); break; case kErrorNotFound: break; @@ -4264,32 +4248,32 @@ exit: } #endif // OPENTHREAD_CONFIG_WAKEUP_COORDINATOR_ENABLE -Error Mle::DetachGracefully(DetachCallback aCallback, void *aContext) +Error Mle::Detacher::Detach(DetachCallback aCallback, void *aContext) { Error error = kErrorNone; - uint32_t timeout = kDetachGracefullyTimeout; + uint32_t timeout = kTimeout; - VerifyOrExit(!mDetachingGracefully, error = kErrorBusy); + VerifyOrExit(mState == kIdle, error = kErrorBusy); - mDetachGracefullyCallback.Set(aCallback, aContext); + mCallback.Set(aCallback, aContext); #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE Get().RequestStop(); #endif - switch (mRole) + switch (Get().GetRole()) { case kRoleLeader: break; case kRoleRouter: #if OPENTHREAD_FTD - SendAddressRelease(); + Get().SendAddressRelease(); #endif break; case kRoleChild: - IgnoreError(SendChildUpdateRequestToParent(kAppendZeroTimeout)); + IgnoreError(Get().SendChildUpdateRequestToParent(kAppendZeroTimeout)); break; case kRoleDisabled: @@ -4302,8 +4286,8 @@ Error Mle::DetachGracefully(DetachCallback aCallback, void *aContext) break; } - mDetachingGracefully = true; - mAttachTimer.Start(timeout); + mState = kDetaching; + mTimer.Start(timeout); exit: return error; @@ -5382,6 +5366,60 @@ void Mle::ParentCandidate::CopyTo(Parent &aParent) const aParent = *candidateAsParent; } +//--------------------------------------------------------------------------------------------------------------------- +// Detacher + +Mle::Detacher::Detacher(Instance &aInstance) + : InstanceLocator(aInstance) + , mState(kIdle) + , mTimer(aInstance) +{ +} + +void Mle::Detacher::HandleTimer(void) +{ + if (mState == kDetaching) + { + Get().Stop(); + } +} + +Error Mle::Detacher::HandleChildUpdateResponse(uint32_t aTimeout) +{ + // Called while processing a "Child Update Response" from parent + // + // To gracefully detach as a child, we send a "Child Update + // Request" with zero timeout value to the parent. Receiving + // a "Child Update Response" confirms the parent has received the + // request, allowing the device to then stop its MLE operations. + + // Returns `kErrorDetached` to signal that MLE is stopped and the + // incoming response should be ignored. Returns `kErrorNone` if + // the response should be processed (i.e., not detaching or if + // the conditions for MLE stop are not yet met). + + Error error = kErrorNone; + + VerifyOrExit(mState == kDetaching); + VerifyOrExit(aTimeout == 0); + Get().Stop(); + error = kErrorDetached; + +exit: + return error; +} + +void Mle::Detacher::HandleStop(void) +{ + // Called upon `Mle::Stop()` after role change. + + if (mState == kDetaching) + { + mState = kIdle; + mCallback.InvokeAndClearIfSet(); + } +} + //--------------------------------------------------------------------------------------------------------------------- // RetxTracker::RetryInfo diff --git a/src/core/thread/mle.hpp b/src/core/thread/mle.hpp index 17d995367..89bf41cfc 100644 --- a/src/core/thread/mle.hpp +++ b/src/core/thread/mle.hpp @@ -216,7 +216,7 @@ public: * @retval kErrorNone Successfully started detaching. * @retval kErrorBusy Detaching is already in progress. */ - Error DetachGracefully(DetachCallback aCallback, void *aContext); + Error DetachGracefully(DetachCallback aCallback, void *aContext) { return mDetacher.Detach(aCallback, aContext); } /** * Indicates whether or not the Thread device is attached to a Thread network. @@ -1182,7 +1182,6 @@ private: static constexpr uint32_t kMaxLinkAcceptDelay = 1000; // Max delay to tx Link Accept for multicast Req static constexpr uint32_t kChildIdRequestTimeout = 5000; // Max delay to rx a Child ID Req after Parent Res static constexpr uint32_t kLinkRequestTimeout = 2000; // Max delay to rx a Link Accept - static constexpr uint32_t kDetachGracefullyTimeout = 1000; // Timeout for graceful detach static constexpr uint32_t kUnicastRetxDelay = 1000; // Base delay for MLE unicast retx static constexpr uint32_t kMulticastRetxDelay = 5000; // Base delay for MLE multicast retx static constexpr uint32_t kMulticastRetxDelayMin = kMulticastRetxDelay * 9 / 10; // 0.9 * base delay @@ -1721,6 +1720,38 @@ private: //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + void HandleDetacherTimer(void) { mDetacher.HandleTimer(); } + + class Detacher : public InstanceLocator + { + // Manages graceful detach process. + + public: + explicit Detacher(Instance &aInstance); + + Error Detach(DetachCallback aCallback, void *aContext); + void HandleTimer(void); + Error HandleChildUpdateResponse(uint32_t aTimeout); + void HandleStop(void); + + private: + static constexpr uint32_t kTimeout = 1000; + + enum State : uint8_t + { + kIdle, + kDetaching, + }; + + using DetachTimer = TimerMilliIn; + + State mState; + Callback mCallback; + DetachTimer mTimer; + }; + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + void HandleRetxTrackerTimer(void) { mRetxTracker.HandleTimer(); } class RetxTracker : public InstanceLocator @@ -2171,7 +2202,6 @@ private: bool mRequestRouteTlv : 1; bool mHasRestored : 1; bool mReceivedResponseFromParent : 1; - bool mDetachingGracefully : 1; bool mInitiallyAttachedAsSleepy : 1; DeviceRole mRole; @@ -2204,6 +2234,7 @@ private: ParentCandidate mParentCandidate; MleSocket mSocket; Counters mCounters; + Detacher mDetacher; RetxTracker mRetxTracker; AnnounceHandler mAnnounceHandler; #if OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE @@ -2212,7 +2243,6 @@ private: #if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE ServiceAloc mServiceAlocs[kMaxServiceAlocs]; #endif - Callback mDetachGracefullyCallback; #if OPENTHREAD_CONFIG_MLE_PARENT_RESPONSE_CALLBACK_API_ENABLE Callback mParentResponseCallback; #endif