diff --git a/src/core/thread/mle.cpp b/src/core/thread/mle.cpp index 8770714a1..57138bce9 100644 --- a/src/core/thread/mle.cpp +++ b/src/core/thread/mle.cpp @@ -2231,6 +2231,7 @@ void Mle::HandleChildUpdateRequestOnChild(RxInfo &aRxInfo) // message (`canTrustMessage` will be `false`). VerifyOrExit(mPrevRoleRestorer.IsRestoringChildRole()); + mPrevRoleRestorer.HandleChildUpdateRequest(aRxInfo); } SuccessOrExit(error = Tlv::Find(aRxInfo.mMessage, sourceAddress)); @@ -4203,6 +4204,8 @@ Mle::PrevRoleRestorer::PrevRoleRestorer(Instance &aInstance) : InstanceLocator(aInstance) , mState(kIdle) , mAttempts(0) + , mUseIncreasingTimeout(false) + , mCurTimeout(0) , mTimer(aInstance) { } @@ -4242,9 +4245,38 @@ Error Mle::PrevRoleRestorer::Start(void) VerifyOrExit(Get().mLastSavedRole == kRoleChild); VerifyOrExit(Get().mParent.IsStateValidOrRestoring()); + + // Try to restore the previous child role by sending up to + // `kChildUpdateAttempts` Child Update Requests to the former + // parent. + // + // A non-sleepy child uses an increasing timeout (starting at + // `kChildUpdateStartTimeout` and doubling) to give a potentially + // restarting parent more time to recover. The total wait time is + // 29 seconds (4+8+16+1). A sleepy child uses a fixed short + // timeout of one seconds(`kChildUpdateMinTimeout`). + // + // Receiving an MLE message from the parent triggers a switch to + // the short timeout and guarantees at least two more attempts + // (`kExtraChildUpdatesAfterRxFromParent`). + SetState(kRestoringChildRole); + GenerateRandomChallenge(); - mAttempts = kMaxChildUpdatesToRestoreRole; + + mAttempts = kChildUpdateAttempts; + + if (!Get().IsRxOnWhenIdle()) + { + mUseIncreasingTimeout = false; + mCurTimeout = kChildUpdateMinTimeout; + } + else + { + mUseIncreasingTimeout = true; + mCurTimeout = kChildUpdateStartTimeout; + } + mTimer.Start(Get().GenerateRandomDelay(kMaxStartDelay)); error = kErrorNone; @@ -4305,12 +4337,48 @@ exit: void Mle::PrevRoleRestorer::SendChildUpdate(void) { - mTimer.Start(Random::NonCrypto::AddJitter(kChildUpdateRetxDelay, kRetxJitter)); + if (mAttempts == 0) + { + mTimer.Start(kChildUpdateMinTimeout); + } + else + { + mTimer.Start(Random::NonCrypto::AddJitter(mCurTimeout, kChildUpdateRetxJitter)); + + if (mUseIncreasingTimeout) + { + mCurTimeout += mCurTimeout; + } + } LogDebg("Sending Child Update Request to restore child role, remaining attempts: %u", mAttempts); IgnoreError(Get().SendChildUpdateRequestToParent(kToRestoreChildRole)); } +void Mle::PrevRoleRestorer::CheckIfMessageIsFromParent(RxInfo &aRxInfo) +{ + VerifyOrExit(IsRestoringChildRole()); + VerifyOrExit(aRxInfo.mNeighbor == &Get().GetParent()); + + VerifyOrExit(mUseIncreasingTimeout); + + LogInfo("Received msg from former parent, speeding up child role restoration"); + + mUseIncreasingTimeout = false; + mCurTimeout = kChildUpdateMinTimeout; + + if (mAttempts <= kExtraChildUpdatesAfterRxFromParent) + { + mAttempts = kExtraChildUpdatesAfterRxFromParent; + LogInfo("Allow extra Child Update attempts %u", mAttempts); + } + + mTimer.FireAtIfEarlier(TimerMilli::GetNow() + Random::NonCrypto::AddJitter(mCurTimeout, kChildUpdateRetxJitter)); + +exit: + return; +} + #if OPENTHREAD_FTD void Mle::PrevRoleRestorer::DetermineMaxLinkRequestAttempts(void) diff --git a/src/core/thread/mle.hpp b/src/core/thread/mle.hpp index 12febc1f8..72732701d 100644 --- a/src/core/thread/mle.hpp +++ b/src/core/thread/mle.hpp @@ -1843,15 +1843,20 @@ private: bool IsRestoringChildRole(void) const { return mState == kRestoringChildRole; } bool IsRestoringRouterOrLeaderRole(void) const { return mState == kRestoringRouterOrLeaderRole; } void HandleTimer(void); + void HandleChildUpdateRequest(RxInfo &aRxInfo) { CheckIfMessageIsFromParent(aRxInfo); } void GenerateRandomChallenge(void) { mChallenge.GenerateRandom(); } const TxChallenge &GetChallenge(void) const { return mChallenge; } 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; + static constexpr uint32_t kMaxStartDelay = 25; // in msec + + // Restoring Child Role (sending "child update request"). + static constexpr uint8_t kChildUpdateAttempts = 4; + static constexpr uint8_t kExtraChildUpdatesAfterRxFromParent = 2; + static constexpr uint16_t kChildUpdateMinTimeout = 1000; // in ms + static constexpr uint16_t kChildUpdateStartTimeout = 4000; // in ms + static constexpr uint16_t kChildUpdateRetxJitter = 25; // in ms enum State : uint8_t { @@ -1862,6 +1867,7 @@ private: void SetState(State aState); void SendChildUpdate(void); + void CheckIfMessageIsFromParent(RxInfo &aRxInfo); #if OPENTHREAD_FTD void DetermineMaxLinkRequestAttempts(void); void SendMulticastLinkRequest(void); @@ -1870,7 +1876,9 @@ private: using DelayTimer = TimerMilliIn; State mState; - uint8_t mAttempts; + uint8_t mAttempts : 7; + bool mUseIncreasingTimeout : 1; + uint16_t mCurTimeout; DelayTimer mTimer; TxChallenge mChallenge; };