diff --git a/src/core/thread/mle.cpp b/src/core/thread/mle.cpp index d82fb568f..fd8656056 100644 --- a/src/core/thread/mle.cpp +++ b/src/core/thread/mle.cpp @@ -354,16 +354,6 @@ exit: return; } -void Mle::Attacher::SetState(State aState) -{ - VerifyOrExit(aState != mState); - LogInfo("AttachState %s -> %s", StateToString(mState), StateToString(aState)); - mState = aState; - -exit: - return; -} - void Mle::Restore(void) { Settings::NetworkInfo networkInfo; @@ -579,102 +569,6 @@ exit: return error; } -void Mle::Attacher::Attach(AttachMode aMode) -{ - VerifyOrExit(!Get().IsDisabled()); - - VerifyOrExit(!IsAttaching()); - - if (!Get().IsDetached()) - { - mAttachCounter = 0; - } - - mParentCandidate.Clear(); - SetState(kStateStart); - mMode = aMode; - - if (aMode != kBetterPartition) - { -#if OPENTHREAD_FTD - if (Get().IsFullThreadDevice()) - { - Get().StopAdvertiseTrickleTimer(); - } -#endif - } - else - { - Get().mCounters.mBetterPartitionAttachAttempts++; - } - - mTimer.Start(GetStartDelay()); - - if (Get().IsDetached()) - { - mAttachCounter++; - - if (mAttachCounter == 0) - { - mAttachCounter--; - } - - Get().mCounters.mAttachAttempts++; - - if (!Get().IsRxOnWhenIdle()) - { - Get().SetRxOnWhenIdle(false); - } - } - -exit: - return; -} - -uint32_t Mle::Attacher::GetStartDelay(void) const -{ - uint32_t delay = 1; - uint32_t jitter; - - VerifyOrExit(Get().IsDetached()); - - if (mAttachCounter == 0) - { - delay = Get().GenerateRandomDelay(kParentRequestRouterTimeout); - ExitNow(); - } -#if OPENTHREAD_CONFIG_MLE_ATTACH_BACKOFF_ENABLE - else - { - uint16_t counter = mAttachCounter - 1; - const uint32_t ratio = kAttachBackoffMaxInterval / kAttachBackoffMinInterval; - - if ((counter < BitSizeOf(ratio)) && ((1UL << counter) <= ratio)) - { - delay = kAttachBackoffMinInterval; - delay <<= counter; - } - else - { - delay = Random::NonCrypto::AddJitter(kAttachBackoffMaxInterval, kAttachBackoffJitter); - } - } -#endif // OPENTHREAD_CONFIG_MLE_ATTACH_BACKOFF_ENABLE - - jitter = Random::NonCrypto::GetUint32InRange(0, kAttachStartJitter); - - if (jitter + delay > delay) // check for overflow - { - delay += jitter; - } - - LogNote("Attach attempt %u unsuccessful, will try again in %lu.%03u seconds", mAttachCounter, ToUlong(delay / 1000), - static_cast(delay % 1000)); - -exit: - return delay; -} - bool Mle::IsAttached(void) const { return (IsChild() || IsRouter() || IsLeader()); } bool Mle::IsRouterOrLeader(void) const { return (IsRouter() || IsLeader()); } @@ -1177,496 +1071,6 @@ exit: return; } -Error Mle::Attacher::DetermineParentRequestType(ParentRequestType &aType) const -{ - // This method determines the Parent Request type to use during an - // attach cycle based on `mMode`, `mAttachCounter` and - // `mParentRequestCounter`. This method MUST be used while in - // `kAttachStateParentRequest` state. - // - // On success it returns `kErrorNone` and sets `aType`. It returns - // `kErrorNotFound` to indicate that device can now transition - // from `kAttachStateParentRequest` state (has already sent the - // required number of Parent Requests for this attach attempt - // cycle). - - Error error = kErrorNone; - - OT_ASSERT(mState == kStateParentRequest); - - if (mMode == kSelectedParent) - { - aType = kToSelectedRouter; - VerifyOrExit(mParentRequestCounter <= 1, error = kErrorNotFound); - ExitNow(); - } - - aType = kToRoutersAndReeds; - - // If device is not yet attached, `mAttachCounter` will track the - // number of attach attempt cycles so far, starting from one for - // the first attempt. `mAttachCounter` will be zero if device is - // already attached. Examples of this situation include a leader or - // router trying to attach to a better partition, or a child trying - // to find a better parent. - - if ((mAttachCounter <= 1) && (mMode != kBetterParent)) - { - VerifyOrExit(mParentRequestCounter <= kFirstAttachCycleTotalParentRequests, error = kErrorNotFound); - - // During reattach to the same partition all the Parent - // Request are sent to Routers and REEDs. - - if ((mMode != kSamePartition) && (mParentRequestCounter <= kFirstAttachCycleNumParentRequestToRouters)) - { - aType = kToRouters; - } - } - else - { - VerifyOrExit(mParentRequestCounter <= kNextAttachCycleTotalParentRequests, error = kErrorNotFound); - - if (mParentRequestCounter <= kNextAttachCycleNumParentRequestToRouters) - { - aType = kToRouters; - } - } - -exit: - return error; -} - -bool Mle::Attacher::HasAcceptableParentCandidate(void) const -{ - bool hasAcceptableParent = false; - ParentRequestType parentReqType; - - VerifyOrExit(mParentCandidate.IsStateParentResponse()); - - switch (mState) - { - case kStateAnnounce: - VerifyOrExit(!HasMoreChannelsToAnnounce()); - break; - - case kStateParentRequest: - SuccessOrAssert(DetermineParentRequestType(parentReqType)); - - if (parentReqType == kToRouters) - { - // If we cannot find a parent with best link quality (3) when - // in Parent Request was sent to routers, we will keep the - // candidate and forward to REED stage to potentially find a - // better parent. - VerifyOrExit(mParentCandidate.GetTwoWayLinkQuality() == kLinkQuality3); - } - - break; - - default: - ExitNow(); - } - - if (Get().IsChild()) - { - switch (mMode) - { - case kBetterPartition: - break; - - case kAnyPartition: - case kSamePartition: - case kDowngradeToReed: - case kBetterParent: - case kSelectedParent: - // Ensure that a Parent Response was received from the - // current parent to which the device is attached, so - // that the new parent candidate can be compared with the - // current parent and confirmed to be preferred. - VerifyOrExit(mReceivedResponseFromParent); - break; - } - } - - hasAcceptableParent = true; - -exit: - return hasAcceptableParent; -} - -void Mle::Attacher::HandleTimer(void) -{ - uint32_t delay = 0; - bool shouldAnnounce = true; - ParentRequestType type; - - // First, check if we are waiting to receive parent responses and - // found an acceptable parent candidate. - - if (HasAcceptableParentCandidate() && (SendChildIdRequest() == kErrorNone)) - { - SetState(kStateChildIdRequest); - delay = kChildIdResponseTimeout; - ExitNow(); - } - - switch (mState) - { - case kStateIdle: - mAttachCounter = 0; - break; - - case kStateStart: - LogNote("Attach attempt %d, %s %s", mAttachCounter, AttachModeToString(mMode), - ReattachModeToString(mReattachMode)); - - SetState(kStateParentRequest); - mParentCandidate.SetState(Neighbor::kStateInvalid); - mReceivedResponseFromParent = false; - mParentRequestCounter = 0; - Get().SetRxOnWhenIdle(true); - - OT_FALL_THROUGH; - - case kStateParentRequest: - mParentRequestCounter++; - if (DetermineParentRequestType(type) == kErrorNone) - { - SendParentRequest(type); - - switch (type) - { - case kToRouters: - case kToSelectedRouter: - delay = kParentRequestRouterTimeout; - break; - case kToRoutersAndReeds: - delay = kParentRequestReedTimeout; - break; - } - - break; - } - - shouldAnnounce = PrepareAnnounceState(); - - if (shouldAnnounce) - { - // We send an extra "Parent Request" as we switch to - // `kStateAnnounce` and start sending Announce on - // all channels. This gives an additional chance to find - // a parent during this phase. Note that we can stay in - // `kStateAnnounce` for multiple iterations, each - // time sending an Announce on a different channel - // (with `mAnnounceDelay` wait between them). - - SetState(kStateAnnounce); - SendParentRequest(kToRoutersAndReeds); - mAnnounceChannel = Mac::ChannelMask::kChannelIteratorFirst; - delay = mAnnounceDelay; - break; - } - - OT_FALL_THROUGH; - - case kStateAnnounce: - if (shouldAnnounce && (GetNextAnnounceChannel(mAnnounceChannel) == kErrorNone)) - { - Get().SendAnnounce(mAnnounceChannel, kOrphanAnnounce); - delay = mAnnounceDelay; - break; - } - - OT_FALL_THROUGH; - - case kStateChildIdRequest: - SetState(kStateIdle); - mParentCandidate.Clear(); - delay = Reattach(); - break; - } - -exit: - - if (delay != 0) - { - mTimer.Start(delay); - } -} - -bool Mle::Attacher::PrepareAnnounceState(void) -{ - bool shouldAnnounce = false; - Mac::ChannelMask channelMask; - - VerifyOrExit(!Get().IsChild() && (mReattachMode == kReattachModeStop) && - (Get().IsPartiallyComplete() || !Get().IsFullThreadDevice())); - - if (Get().GetChannelMask(channelMask) != kErrorNone) - { - channelMask = Get().GetSupportedChannelMask(); - } - - mAnnounceDelay = kAnnounceTimeout / (channelMask.GetNumberOfChannels() + 1); - mAnnounceDelay = Max(mAnnounceDelay, kMinAnnounceDelay); - shouldAnnounce = true; - -exit: - return shouldAnnounce; -} - -uint32_t Mle::Attacher::Reattach(void) -{ - uint32_t delay = 0; - - // First, check `mReattachMode`. If an attach attempt failed - // while using the Active Dataset, start a new attach cycle with - // the Pending Dataset (if available). If attaching with the - // Pending Dataset fails, switch back to the Active Dataset. - - switch (mReattachMode) - { - case kReattachModeActive: - if (Get().Restore() == kErrorNone) - { - IgnoreError(Get().ApplyConfiguration()); - mReattachMode = kReattachModePending; - SetState(kStateStart); - delay = Get().GenerateRandomDelay(kAttachStartJitter); - ExitNow(); - } - - mReattachMode = kReattachModeStop; - break; - - case kReattachModePending: - IgnoreError(Get().Restore()); - mReattachMode = kReattachModeStop; - break; - - case kReattachModeStop: - break; - } - - switch (mMode) - { - case kAnyPartition: - case kBetterParent: - case kSelectedParent: - if (Get().IsChild()) - { - // If already attached (e.g., trying to find a better - // parent or partition), and attach fails, we revert to - // sleepy operation if needed and stop the attach process. - - if (!Get().IsRxOnWhenIdle()) - { - Get().SetAttachMode(false); - Get().SetRxOnWhenIdle(false); - } - - ExitNow(); - } - - if (Get().mAnnounceHandler.IsAnnounceAttaching()) - { - Get().mAnnounceHandler.HandleAnnounceAttachFailure(); - IgnoreError(Get().BecomeDetached()); - ExitNow(); - } - -#if OPENTHREAD_FTD - if (Get().IsFullThreadDevice() && Get().BecomeLeader(kIgnoreLeaderWeight) == kErrorNone) - { - ExitNow(); - } -#endif - - IgnoreError(Get().BecomeDetached()); - break; - - case kSamePartition: - case kDowngradeToReed: - Attach(kAnyPartition); - break; - - case kBetterPartition: - break; - } - -exit: - return delay; -} - -void Mle::Attacher::SendParentRequest(ParentRequestType aType) -{ - Error error = kErrorNone; - TxMessage *message; - uint8_t scanMask = 0; - Ip6::Address destination; - - mParentRequestChallenge.GenerateRandom(); - - switch (aType) - { - case kToRouters: - case kToSelectedRouter: - scanMask = ScanMaskTlv::kRouterFlag; - break; - - case kToRoutersAndReeds: - scanMask = ScanMaskTlv::kRouterFlag | ScanMaskTlv::kEndDeviceFlag; - break; - } - - VerifyOrExit((message = Get().NewMleMessage(kCommandParentRequest)) != nullptr, error = kErrorNoBufs); - SuccessOrExit(error = message->AppendModeTlv(Get().mDeviceMode)); - SuccessOrExit(error = message->AppendChallengeTlv(mParentRequestChallenge)); - SuccessOrExit(error = message->AppendScanMaskTlv(scanMask)); - SuccessOrExit(error = message->AppendVersionTlv()); -#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE - SuccessOrExit(error = message->AppendTimeRequestTlv()); -#endif - -#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE - if (aType == kToSelectedRouter) - { - TxMessage *messageToCurParent = static_cast(message->Clone()); - - VerifyOrExit(messageToCurParent != nullptr, error = kErrorNoBufs); - - destination.SetToLinkLocalAddress(Get().mParent.GetExtAddress()); - error = messageToCurParent->SendTo(destination); - - if (error != kErrorNone) - { - messageToCurParent->Free(); - ExitNow(); - } - - Log(kMessageSend, kTypeParentRequestToRouters, destination); - - destination.SetToLinkLocalAddress(Get().mParentSearch.GetSelectedParent().GetExtAddress()); - } - else -#endif - { - destination.SetToLinkLocalAllRoutersMulticast(); - } - - SuccessOrExit(error = message->SendTo(destination)); - - switch (aType) - { - case kToRouters: - case kToSelectedRouter: - Log(kMessageSend, kTypeParentRequestToRouters, destination); - break; - - case kToRoutersAndReeds: - Log(kMessageSend, kTypeParentRequestToRoutersReeds, destination); - break; - } - -exit: - FreeMessageOnError(message, error); -} - -void Mle::Attacher::HandleChildIdRequestTxDone(const otMessage *aMessage, otError aError, void *aContext) -{ - OT_UNUSED_VARIABLE(aError); - - static_cast(aContext)->HandleChildIdRequestTxDone(AsCoreType(aMessage)); -} - -void Mle::Attacher::HandleChildIdRequestTxDone(const Message &aMessage) -{ - if (aMessage.GetTxSuccess() && !Get().IsRxOnWhenIdle()) - { - Get().SetAttachMode(true); - Get().SetRxOnWhenIdle(false); - } - - if (aMessage.IsLinkSecurityEnabled() && (mState == kStateChildIdRequest)) - { - // If the Child ID Request requires fragmentation and therefore - // link layer security, the frame transmission will be aborted. - // When the message is being freed, we signal to MLE to prepare a - // shorter Child ID Request message (by only including mesh-local - // address in the Address Registration TLV). - - LogInfo("Requesting shorter `Child ID Request`"); - - mAddressRegistrationMode = kAppendMeshLocalOnly; - IgnoreError(SendChildIdRequest()); - } -} - -Error Mle::Attacher::SendChildIdRequest(void) -{ - static const uint8_t kTlvs[] = {Tlv::kAddress16, Tlv::kNetworkData, Tlv::kRoute}; - - Error error = kErrorNone; - uint8_t tlvsLen = sizeof(kTlvs); - TxMessage *message = nullptr; - Ip6::Address destination; - - if (Get().mParent.GetExtAddress() == mParentCandidate.GetExtAddress()) - { - if (Get().IsChild()) - { - LogInfo("Already attached to candidate parent"); - ExitNow(error = kErrorAlready); - } - else - { - // Invalidate stale parent state. - // - // Parent state is not normally invalidated after becoming - // a Router/Leader (see #1875). When trying to attach to - // a better partition, invalidating old parent state - // (especially when in `kStateRestored`) ensures that - // `FindNeighbor()` returns `mParentCandidate` when - // processing the Child ID Response. - - Get().mParent.SetState(Neighbor::kStateInvalid); - } - } - - VerifyOrExit((message = Get().NewMleMessage(kCommandChildIdRequest)) != nullptr, error = kErrorNoBufs); - SuccessOrExit(error = message->AppendResponseTlv(mParentCandidate.mRxChallenge)); - SuccessOrExit(error = message->AppendLinkAndMleFrameCounterTlvs()); - SuccessOrExit(error = message->AppendModeTlv(Get().mDeviceMode)); - SuccessOrExit(error = message->AppendTimeoutTlv(Get().mTimeout)); - SuccessOrExit(error = message->AppendVersionTlv()); - SuccessOrExit(error = message->AppendSupervisionIntervalTlvIfSleepyChild()); - - if (!Get().IsFullThreadDevice()) - { - SuccessOrExit(error = message->AppendAddressRegistrationTlv(mAddressRegistrationMode)); - - // No need to request the last Route64 TLV for MTD - tlvsLen -= 1; - } - - SuccessOrExit(error = message->AppendTlvRequestTlv(kTlvs, tlvsLen)); - SuccessOrExit(error = message->AppendActiveAndPendingTimestampTlvs()); - - mParentCandidate.SetState(Neighbor::kStateValid); - - message->RegisterTxCallback(HandleChildIdRequestTxDone, this); - - destination.SetToLinkLocalAddress(mParentCandidate.GetExtAddress()); - SuccessOrExit(error = message->SendTo(destination)); - - Log(kMessageSend, - (mAddressRegistrationMode == kAppendMeshLocalOnly) ? kTypeChildIdRequestShort : kTypeChildIdRequest, - destination); -exit: - FreeMessageOnError(message, error); - return error; -} - Error Mle::SendDataRequestToParent(void) { Ip6::Address destination; @@ -1967,29 +1371,6 @@ exit: FreeMessageOnError(message, error); } -Error Mle::Attacher::GetNextAnnounceChannel(uint8_t &aChannel) const -{ - // This method gets the next channel to send announce on after - // `aChannel`. Returns `kErrorNotFound` if no more channel in the - // channel mask after `aChannel`. - - Mac::ChannelMask channelMask; - - if (Get().GetChannelMask(channelMask) != kErrorNone) - { - channelMask = Get().GetSupportedChannelMask(); - } - - return channelMask.GetNextChannel(aChannel); -} - -bool Mle::Attacher::HasMoreChannelsToAnnounce(void) const -{ - uint8_t channel = mAnnounceChannel; - - return GetNextAnnounceChannel(channel) == kErrorNone; -} - #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE Error Mle::SendLinkMetricsManagementResponse(const Ip6::Address &aDestination, LinkMetrics::Status aStatus) { @@ -2769,364 +2150,6 @@ exit: return error; } -bool Mle::Attacher::IsBetterParent(uint16_t aRloc16, - uint8_t aTwoWayLinkMargin, - const ConnectivityTlv &aConnectivityTlv, - uint16_t aVersion, - const Mac::CslAccuracy &aCslAccuracy) -{ - int rval; - - // Mesh Impacting Criteria - rval = ThreeWayCompare(LinkQualityForLinkMargin(aTwoWayLinkMargin), mParentCandidate.GetTwoWayLinkQuality()); - VerifyOrExit(rval == 0); - - rval = ThreeWayCompare(IsRouterRloc16(aRloc16), IsRouterRloc16(mParentCandidate.GetRloc16())); - VerifyOrExit(rval == 0); - - rval = ThreeWayCompare(aConnectivityTlv.GetParentPriority(), mParentCandidate.mPriority); - VerifyOrExit(rval == 0); - - // Prefer the parent with highest quality links (Link Quality 3 field in Connectivity TLV) to neighbors - rval = ThreeWayCompare(aConnectivityTlv.GetLinkQuality3(), mParentCandidate.mLinkQuality3); - VerifyOrExit(rval == 0); - - // Thread 1.2 Specification 4.5.2.1.2 Child Impacting Criteria - - rval = ThreeWayCompare(aVersion, mParentCandidate.GetVersion()); - VerifyOrExit(rval == 0); - - rval = ThreeWayCompare(aConnectivityTlv.GetSedBufferSize(), mParentCandidate.mSedBufferSize); - VerifyOrExit(rval == 0); - - rval = ThreeWayCompare(aConnectivityTlv.GetSedDatagramCount(), mParentCandidate.mSedDatagramCount); - VerifyOrExit(rval == 0); - - // Extra rules - rval = ThreeWayCompare(aConnectivityTlv.GetLinkQuality2(), mParentCandidate.mLinkQuality2); - VerifyOrExit(rval == 0); - - rval = ThreeWayCompare(aConnectivityTlv.GetLinkQuality1(), mParentCandidate.mLinkQuality1); - VerifyOrExit(rval == 0); - -#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE - // CSL metric - if (!Get().IsRxOnWhenIdle()) - { - uint64_t cslMetric = Get().CalcParentCslMetric(aCslAccuracy); - uint64_t candidateCslMetric = Get().CalcParentCslMetric(mParentCandidate.GetCslAccuracy()); - - // Smaller metric is better. - rval = ThreeWayCompare(candidateCslMetric, cslMetric); - VerifyOrExit(rval == 0); - } -#else - OT_UNUSED_VARIABLE(aCslAccuracy); -#endif - - rval = ThreeWayCompare(aTwoWayLinkMargin, mParentCandidate.mLinkMargin); - -exit: - return (rval > 0); -} - -void Mle::Attacher::HandleParentResponse(RxInfo &aRxInfo) -{ - Error error = kErrorNone; - int8_t rss = aRxInfo.mMessage.GetAverageRss(); - uint16_t version; - uint16_t sourceAddress; - LeaderData leaderData; - uint8_t linkMarginOut; - uint8_t twoWayLinkMargin; - ConnectivityTlv connectivityTlv; - uint32_t linkFrameCounter; - uint32_t mleFrameCounter; - Mac::ExtAddress extAddress; - Mac::CslAccuracy cslAccuracy; -#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE - TimeParameterTlv timeParameterTlv; -#endif - - SuccessOrExit(error = Tlv::Find(aRxInfo.mMessage, sourceAddress)); - - Log(kMessageReceive, kTypeParentResponse, aRxInfo.mMessageInfo.GetPeerAddr(), sourceAddress); - - SuccessOrExit(error = aRxInfo.mMessage.ReadVersionTlv(version)); - - SuccessOrExit(error = aRxInfo.mMessage.ReadAndMatchResponseTlvWith(mParentRequestChallenge)); - - extAddress.SetFromIid(aRxInfo.mMessageInfo.GetPeerAddr().GetIid()); - - if (Get().IsChild() && Get().mParent.GetExtAddress() == extAddress) - { - mReceivedResponseFromParent = true; - } - - SuccessOrExit(error = aRxInfo.mMessage.ReadLeaderDataTlv(leaderData)); - - SuccessOrExit(error = Tlv::Find(aRxInfo.mMessage, linkMarginOut)); - twoWayLinkMargin = Min(Get().ComputeLinkMargin(rss), linkMarginOut); - - SuccessOrExit(error = Tlv::FindTlv(aRxInfo.mMessage, connectivityTlv)); - VerifyOrExit(connectivityTlv.IsValid(), error = kErrorParse); - -#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE - switch (aRxInfo.mMessage.ReadCslClockAccuracyTlv(cslAccuracy)) - { - case kErrorNone: - break; - case kErrorNotFound: - cslAccuracy.Init(); // Use worst-case values if TLV is not found - break; - default: - ExitNow(error = kErrorParse); - } -#else - cslAccuracy.Init(); -#endif - -#if OPENTHREAD_CONFIG_MLE_PARENT_RESPONSE_CALLBACK_API_ENABLE - if (mParentResponseCallback.IsSet()) - { - otThreadParentResponseInfo parentinfo; - - parentinfo.mExtAddr = extAddress; - parentinfo.mRloc16 = sourceAddress; - parentinfo.mRssi = rss; - parentinfo.mPriority = connectivityTlv.GetParentPriority(); - parentinfo.mLinkQuality3 = connectivityTlv.GetLinkQuality3(); - parentinfo.mLinkQuality2 = connectivityTlv.GetLinkQuality2(); - parentinfo.mLinkQuality1 = connectivityTlv.GetLinkQuality1(); - parentinfo.mIsAttached = Get().IsAttached(); - - mParentResponseCallback.Invoke(&parentinfo); - } -#endif - - aRxInfo.mClass = RxInfo::kAuthoritativeMessage; - -#if OPENTHREAD_FTD - if (Get().IsFullThreadDevice() && !Get().IsDetached()) - { - bool isPartitionIdSame = (leaderData.GetPartitionId() == Get().mLeaderData.GetPartitionId()); - bool isIdSequenceSame = (connectivityTlv.GetIdSequence() == Get().GetRouterIdSequence()); - bool isIdSequenceGreater = - SerialNumber::IsGreater(connectivityTlv.GetIdSequence(), Get().GetRouterIdSequence()); - - switch (mMode) - { - case kAnyPartition: - VerifyOrExit(!isPartitionIdSame || isIdSequenceGreater); - break; - - case kSamePartition: - VerifyOrExit(isPartitionIdSame && isIdSequenceGreater); - break; - - case kDowngradeToReed: - VerifyOrExit(isPartitionIdSame && (isIdSequenceSame || isIdSequenceGreater)); - break; - - case kBetterPartition: - VerifyOrExit(!isPartitionIdSame); - - VerifyOrExit(ComparePartitions(connectivityTlv.IsSingleton(), leaderData, Get().IsSingleton(), - Get().mLeaderData) > 0); - break; - - case kBetterParent: - case kSelectedParent: - break; - } - } -#endif - - // Continue to process the "ParentResponse" if it is from current - // parent candidate to update the challenge and frame counters. - - if (mParentCandidate.IsStateParentResponse() && (mParentCandidate.GetExtAddress() != extAddress)) - { - // If already have a candidate parent, only seek a better parent - - int compare = 0; - -#if OPENTHREAD_FTD - if (Get().IsFullThreadDevice()) - { - compare = ComparePartitions(connectivityTlv.IsSingleton(), leaderData, mParentCandidate.mIsSingleton, - mParentCandidate.mLeaderData); - } - - // Only consider partitions that are the same or better - VerifyOrExit(compare >= 0); -#endif - - // Only consider better parents if the partitions are the same - if (compare == 0) - { - VerifyOrExit(IsBetterParent(sourceAddress, twoWayLinkMargin, connectivityTlv, version, cslAccuracy)); - } - } - - SuccessOrExit(error = aRxInfo.mMessage.ReadFrameCounterTlvs(linkFrameCounter, mleFrameCounter)); - -#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE - - if (Tlv::FindTlv(aRxInfo.mMessage, timeParameterTlv) == kErrorNone) - { - VerifyOrExit(timeParameterTlv.IsValid()); - - Get().SetTimeSyncPeriod(timeParameterTlv.GetTimeSyncPeriod()); - Get().SetXtalThreshold(timeParameterTlv.GetXtalThreshold()); - } - -#if OPENTHREAD_CONFIG_TIME_SYNC_REQUIRED - else - { - // If the time sync feature is required, don't choose the - // parent which doesn't support it. - ExitNow(); - } -#endif -#endif // OPENTHREAD_CONFIG_TIME_SYNC_ENABLE - - SuccessOrExit(error = aRxInfo.mMessage.ReadChallengeTlv(mParentCandidate.mRxChallenge)); - - Get().InitNeighbor(mParentCandidate, aRxInfo); - mParentCandidate.SetRloc16(sourceAddress); - mParentCandidate.GetLinkFrameCounters().SetAll(linkFrameCounter); - mParentCandidate.SetLinkAckFrameCounter(linkFrameCounter); - mParentCandidate.SetMleFrameCounter(mleFrameCounter); - mParentCandidate.SetVersion(version); - mParentCandidate.SetDeviceMode(DeviceMode(DeviceMode::kModeFullThreadDevice | DeviceMode::kModeRxOnWhenIdle | - DeviceMode::kModeFullNetworkData)); - mParentCandidate.SetLinkQualityOut(LinkQualityForLinkMargin(linkMarginOut)); - mParentCandidate.SetState(Neighbor::kStateParentResponse); - mParentCandidate.SetKeySequence(aRxInfo.mKeySequence); - mParentCandidate.SetLeaderCost(connectivityTlv.GetLeaderCost()); -#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE - mParentCandidate.SetCslAccuracy(cslAccuracy); -#endif - - mParentCandidate.mPriority = connectivityTlv.GetParentPriority(); - mParentCandidate.mLinkQuality3 = connectivityTlv.GetLinkQuality3(); - mParentCandidate.mLinkQuality2 = connectivityTlv.GetLinkQuality2(); - mParentCandidate.mLinkQuality1 = connectivityTlv.GetLinkQuality1(); - mParentCandidate.mSedBufferSize = connectivityTlv.GetSedBufferSize(); - mParentCandidate.mSedDatagramCount = connectivityTlv.GetSedDatagramCount(); - mParentCandidate.mLeaderData = leaderData; - mParentCandidate.mIsSingleton = connectivityTlv.IsSingleton(); - mParentCandidate.mLinkMargin = twoWayLinkMargin; - -exit: - LogProcessError(kTypeParentResponse, error); -} - -void Mle::Attacher::HandleChildIdResponse(RxInfo &aRxInfo) -{ - Error error = kErrorNone; - LeaderData leaderData; - uint16_t sourceAddress; - uint16_t shortAddress; - MeshCoP::Timestamp timestamp; - - SuccessOrExit(error = Tlv::Find(aRxInfo.mMessage, sourceAddress)); - - Log(kMessageReceive, kTypeChildIdResponse, aRxInfo.mMessageInfo.GetPeerAddr(), sourceAddress); - - VerifyOrExit(aRxInfo.IsNeighborStateValid(), error = kErrorSecurity); - - VerifyOrExit(mState == kStateChildIdRequest); - - SuccessOrExit(error = Tlv::Find(aRxInfo.mMessage, shortAddress)); - VerifyOrExit(RouterIdMatch(sourceAddress, shortAddress), error = kErrorRejected); - - SuccessOrExit(error = aRxInfo.mMessage.ReadLeaderDataTlv(leaderData)); - - VerifyOrExit(aRxInfo.mMessage.ContainsTlv(Tlv::kNetworkData)); - - switch (Tlv::Find(aRxInfo.mMessage, timestamp)) - { - case kErrorNone: - error = aRxInfo.mMessage.ReadAndSaveActiveDataset(timestamp); - error = (error == kErrorNotFound) ? kErrorNone : error; - SuccessOrExit(error); - break; - - case kErrorNotFound: - break; - - default: - ExitNow(error = kErrorParse); - } - - // Clear Pending Dataset if device succeed to reattach using stored Pending Dataset - if (mReattachMode == kReattachModePending) - { - Get().Clear(); - } - - switch (Tlv::Find(aRxInfo.mMessage, timestamp)) - { - case kErrorNone: - IgnoreError(aRxInfo.mMessage.ReadAndSavePendingDataset(timestamp)); - break; - - case kErrorNotFound: - Get().Clear(); - break; - - default: - ExitNow(error = kErrorParse); - } - -#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE - if (aRxInfo.mMessage.GetTimeSyncSeq() != OT_TIME_SYNC_INVALID_SEQ) - { - Get().HandleTimeSyncMessage(aRxInfo.mMessage); - } -#endif - - // Parent Attach Success - - Get().SetStateDetached(); - - Get().SetLeaderData(leaderData); - -#if OPENTHREAD_FTD - SuccessOrExit(error = Get().ReadAndProcessRouteTlvOnFtdChild(aRxInfo, RouterIdFromRloc16(sourceAddress))); -#endif - - mParentCandidate.CopyTo(Get().mParent); - mParentCandidate.Clear(); - -#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE - Get().SetCslParentAccuracy(Get().mParent.GetCslAccuracy()); -#endif - - Get().mParent.SetRloc16(sourceAddress); - - IgnoreError(aRxInfo.mMessage.ReadAndSetNetworkDataTlv(leaderData)); - - Get().SetStateChild(shortAddress); - - if (!Get().IsRxOnWhenIdle()) - { - Get().SetAttachMode(false); - Get().SetRxOnWhenIdle(false); - } - else - { - Get().SetRxOnWhenIdle(true); - } - - aRxInfo.mClass = RxInfo::kPeerMessage; - -exit: - LogProcessError(kTypeChildIdResponse, error); -} - void Mle::HandleChildUpdateRequest(RxInfo &aRxInfo) { VerifyOrExit(IsAttached()); @@ -3930,81 +2953,6 @@ const char *Mle::MessageTypeActionToSuffixString(MessageType aType, MessageActio #endif // #if OT_SHOULD_LOG_AT( OT_LOG_LEVEL_WARN) -// LCOV_EXCL_START - -#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_NOTE) - -const char *Mle::Attacher::AttachModeToString(AttachMode aMode) -{ - static const char *const kAttachModeStrings[] = { - "AnyPartition", // (0) kAnyPartition - "SamePartition", // (1) kSamePartition - "BetterPartition", // (2) kBetterPartition - "DowngradeToReed", // (3) kDowngradeToReed - "BetterParent", // (4) kBetterParent - "SelectedParent", // (5) kSelectedParent - }; - - struct EnumCheck - { - InitEnumValidatorCounter(); - ValidateNextEnum(kAnyPartition); - ValidateNextEnum(kSamePartition); - ValidateNextEnum(kBetterPartition); - ValidateNextEnum(kDowngradeToReed); - ValidateNextEnum(kBetterParent); - ValidateNextEnum(kSelectedParent); - }; - - return kAttachModeStrings[aMode]; -} - -const char *Mle::Attacher::StateToString(State aState) -{ - static const char *const kStateStrings[] = { - "Idle", // (0) kStateIdle - "Start", // (1) kStateStart - "ParentReq", // (2) kStateParent - "Announce", // (3) kStateAnnounce - "ChildIdReq", // (4) kStateChildIdRequest - }; - - struct EnumCheck - { - InitEnumValidatorCounter(); - ValidateNextEnum(kStateIdle); - ValidateNextEnum(kStateStart); - ValidateNextEnum(kStateParentRequest); - ValidateNextEnum(kStateAnnounce); - ValidateNextEnum(kStateChildIdRequest); - }; - - return kStateStrings[aState]; -} - -const char *Mle::Attacher::ReattachModeToString(ReattachMode aMode) -{ - static const char *const kReattachModeStrings[] = { - "", // (0) kReattachModeStop - "reattaching with Active Dataset", // (1) kReattachModeActive - "reattaching with Pending Dataset", // (2) kReattachModePending - }; - - struct EnumCheck - { - InitEnumValidatorCounter(); - ValidateNextEnum(kReattachModeStop); - ValidateNextEnum(kReattachModeActive); - ValidateNextEnum(kReattachModePending); - }; - - return kReattachModeStrings[aMode]; -} - -#endif // OT_SHOULD_LOG_AT( OT_LOG_LEVEL_NOTE) - -// LCOV_EXCL_STOP - #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE Error Mle::SendLinkMetricsManagementRequest(const Ip6::Address &aDestination, const ot::Tlv &aSubTlv) { @@ -5384,6 +4332,1062 @@ void Mle::Attacher::CancelAttachOnRoleChange(void) } } +void Mle::Attacher::SetState(State aState) +{ + VerifyOrExit(aState != mState); + LogInfo("AttachState %s -> %s", StateToString(mState), StateToString(aState)); + mState = aState; + +exit: + return; +} + +void Mle::Attacher::Attach(AttachMode aMode) +{ + VerifyOrExit(!Get().IsDisabled()); + + VerifyOrExit(!IsAttaching()); + + if (!Get().IsDetached()) + { + mAttachCounter = 0; + } + + mParentCandidate.Clear(); + SetState(kStateStart); + mMode = aMode; + + if (aMode != kBetterPartition) + { +#if OPENTHREAD_FTD + if (Get().IsFullThreadDevice()) + { + Get().StopAdvertiseTrickleTimer(); + } +#endif + } + else + { + Get().mCounters.mBetterPartitionAttachAttempts++; + } + + mTimer.Start(GetStartDelay()); + + if (Get().IsDetached()) + { + mAttachCounter++; + + if (mAttachCounter == 0) + { + mAttachCounter--; + } + + Get().mCounters.mAttachAttempts++; + + if (!Get().IsRxOnWhenIdle()) + { + Get().SetRxOnWhenIdle(false); + } + } + +exit: + return; +} + +uint32_t Mle::Attacher::GetStartDelay(void) const +{ + uint32_t delay = 1; + uint32_t jitter; + + VerifyOrExit(Get().IsDetached()); + + if (mAttachCounter == 0) + { + delay = Get().GenerateRandomDelay(kParentRequestRouterTimeout); + ExitNow(); + } +#if OPENTHREAD_CONFIG_MLE_ATTACH_BACKOFF_ENABLE + else + { + uint16_t counter = mAttachCounter - 1; + const uint32_t ratio = kAttachBackoffMaxInterval / kAttachBackoffMinInterval; + + if ((counter < BitSizeOf(ratio)) && ((1UL << counter) <= ratio)) + { + delay = kAttachBackoffMinInterval; + delay <<= counter; + } + else + { + delay = Random::NonCrypto::AddJitter(kAttachBackoffMaxInterval, kAttachBackoffJitter); + } + } +#endif // OPENTHREAD_CONFIG_MLE_ATTACH_BACKOFF_ENABLE + + jitter = Random::NonCrypto::GetUint32InRange(0, kAttachStartJitter); + + if (jitter + delay > delay) // check for overflow + { + delay += jitter; + } + + LogNote("Attach attempt %u unsuccessful, will try again in %lu.%03u seconds", mAttachCounter, ToUlong(delay / 1000), + static_cast(delay % 1000)); + +exit: + return delay; +} + +Error Mle::Attacher::DetermineParentRequestType(ParentRequestType &aType) const +{ + // This method determines the Parent Request type to use during an + // attach cycle based on `mMode`, `mAttachCounter` and + // `mParentRequestCounter`. This method MUST be used while in + // `kAttachStateParentRequest` state. + // + // On success it returns `kErrorNone` and sets `aType`. It returns + // `kErrorNotFound` to indicate that device can now transition + // from `kAttachStateParentRequest` state (has already sent the + // required number of Parent Requests for this attach attempt + // cycle). + + Error error = kErrorNone; + + OT_ASSERT(mState == kStateParentRequest); + + if (mMode == kSelectedParent) + { + aType = kToSelectedRouter; + VerifyOrExit(mParentRequestCounter <= 1, error = kErrorNotFound); + ExitNow(); + } + + aType = kToRoutersAndReeds; + + // If device is not yet attached, `mAttachCounter` will track the + // number of attach attempt cycles so far, starting from one for + // the first attempt. `mAttachCounter` will be zero if device is + // already attached. Examples of this situation include a leader or + // router trying to attach to a better partition, or a child trying + // to find a better parent. + + if ((mAttachCounter <= 1) && (mMode != kBetterParent)) + { + VerifyOrExit(mParentRequestCounter <= kFirstAttachCycleTotalParentRequests, error = kErrorNotFound); + + // During reattach to the same partition all the Parent + // Request are sent to Routers and REEDs. + + if ((mMode != kSamePartition) && (mParentRequestCounter <= kFirstAttachCycleNumParentRequestToRouters)) + { + aType = kToRouters; + } + } + else + { + VerifyOrExit(mParentRequestCounter <= kNextAttachCycleTotalParentRequests, error = kErrorNotFound); + + if (mParentRequestCounter <= kNextAttachCycleNumParentRequestToRouters) + { + aType = kToRouters; + } + } + +exit: + return error; +} + +bool Mle::Attacher::HasAcceptableParentCandidate(void) const +{ + bool hasAcceptableParent = false; + ParentRequestType parentReqType; + + VerifyOrExit(mParentCandidate.IsStateParentResponse()); + + switch (mState) + { + case kStateAnnounce: + VerifyOrExit(!HasMoreChannelsToAnnounce()); + break; + + case kStateParentRequest: + SuccessOrAssert(DetermineParentRequestType(parentReqType)); + + if (parentReqType == kToRouters) + { + // If we cannot find a parent with best link quality (3) when + // in Parent Request was sent to routers, we will keep the + // candidate and forward to REED stage to potentially find a + // better parent. + VerifyOrExit(mParentCandidate.GetTwoWayLinkQuality() == kLinkQuality3); + } + + break; + + default: + ExitNow(); + } + + if (Get().IsChild()) + { + switch (mMode) + { + case kBetterPartition: + break; + + case kAnyPartition: + case kSamePartition: + case kDowngradeToReed: + case kBetterParent: + case kSelectedParent: + // Ensure that a Parent Response was received from the + // current parent to which the device is attached, so + // that the new parent candidate can be compared with the + // current parent and confirmed to be preferred. + VerifyOrExit(mReceivedResponseFromParent); + break; + } + } + + hasAcceptableParent = true; + +exit: + return hasAcceptableParent; +} + +void Mle::Attacher::HandleTimer(void) +{ + uint32_t delay = 0; + bool shouldAnnounce = true; + ParentRequestType type; + + // First, check if we are waiting to receive parent responses and + // found an acceptable parent candidate. + + if (HasAcceptableParentCandidate() && (SendChildIdRequest() == kErrorNone)) + { + SetState(kStateChildIdRequest); + delay = kChildIdResponseTimeout; + ExitNow(); + } + + switch (mState) + { + case kStateIdle: + mAttachCounter = 0; + break; + + case kStateStart: + LogNote("Attach attempt %d, %s %s", mAttachCounter, AttachModeToString(mMode), + ReattachModeToString(mReattachMode)); + + SetState(kStateParentRequest); + mParentCandidate.SetState(Neighbor::kStateInvalid); + mReceivedResponseFromParent = false; + mParentRequestCounter = 0; + Get().SetRxOnWhenIdle(true); + + OT_FALL_THROUGH; + + case kStateParentRequest: + mParentRequestCounter++; + if (DetermineParentRequestType(type) == kErrorNone) + { + SendParentRequest(type); + + switch (type) + { + case kToRouters: + case kToSelectedRouter: + delay = kParentRequestRouterTimeout; + break; + case kToRoutersAndReeds: + delay = kParentRequestReedTimeout; + break; + } + + break; + } + + shouldAnnounce = PrepareAnnounceState(); + + if (shouldAnnounce) + { + // We send an extra "Parent Request" as we switch to + // `kStateAnnounce` and start sending Announce on + // all channels. This gives an additional chance to find + // a parent during this phase. Note that we can stay in + // `kStateAnnounce` for multiple iterations, each + // time sending an Announce on a different channel + // (with `mAnnounceDelay` wait between them). + + SetState(kStateAnnounce); + SendParentRequest(kToRoutersAndReeds); + mAnnounceChannel = Mac::ChannelMask::kChannelIteratorFirst; + delay = mAnnounceDelay; + break; + } + + OT_FALL_THROUGH; + + case kStateAnnounce: + if (shouldAnnounce && (GetNextAnnounceChannel(mAnnounceChannel) == kErrorNone)) + { + Get().SendAnnounce(mAnnounceChannel, kOrphanAnnounce); + delay = mAnnounceDelay; + break; + } + + OT_FALL_THROUGH; + + case kStateChildIdRequest: + SetState(kStateIdle); + mParentCandidate.Clear(); + delay = Reattach(); + break; + } + +exit: + + if (delay != 0) + { + mTimer.Start(delay); + } +} + +bool Mle::Attacher::PrepareAnnounceState(void) +{ + bool shouldAnnounce = false; + Mac::ChannelMask channelMask; + + VerifyOrExit(!Get().IsChild() && (mReattachMode == kReattachModeStop) && + (Get().IsPartiallyComplete() || !Get().IsFullThreadDevice())); + + if (Get().GetChannelMask(channelMask) != kErrorNone) + { + channelMask = Get().GetSupportedChannelMask(); + } + + mAnnounceDelay = kAnnounceTimeout / (channelMask.GetNumberOfChannels() + 1); + mAnnounceDelay = Max(mAnnounceDelay, kMinAnnounceDelay); + shouldAnnounce = true; + +exit: + return shouldAnnounce; +} + +uint32_t Mle::Attacher::Reattach(void) +{ + uint32_t delay = 0; + + // First, check `mReattachMode`. If an attach attempt failed + // while using the Active Dataset, start a new attach cycle with + // the Pending Dataset (if available). If attaching with the + // Pending Dataset fails, switch back to the Active Dataset. + + switch (mReattachMode) + { + case kReattachModeActive: + if (Get().Restore() == kErrorNone) + { + IgnoreError(Get().ApplyConfiguration()); + mReattachMode = kReattachModePending; + SetState(kStateStart); + delay = Get().GenerateRandomDelay(kAttachStartJitter); + ExitNow(); + } + + mReattachMode = kReattachModeStop; + break; + + case kReattachModePending: + IgnoreError(Get().Restore()); + mReattachMode = kReattachModeStop; + break; + + case kReattachModeStop: + break; + } + + switch (mMode) + { + case kAnyPartition: + case kBetterParent: + case kSelectedParent: + if (Get().IsChild()) + { + // If already attached (e.g., trying to find a better + // parent or partition), and attach fails, we revert to + // sleepy operation if needed and stop the attach process. + + if (!Get().IsRxOnWhenIdle()) + { + Get().SetAttachMode(false); + Get().SetRxOnWhenIdle(false); + } + + ExitNow(); + } + + if (Get().mAnnounceHandler.IsAnnounceAttaching()) + { + Get().mAnnounceHandler.HandleAnnounceAttachFailure(); + IgnoreError(Get().BecomeDetached()); + ExitNow(); + } + +#if OPENTHREAD_FTD + if (Get().IsFullThreadDevice() && Get().BecomeLeader(kIgnoreLeaderWeight) == kErrorNone) + { + ExitNow(); + } +#endif + + IgnoreError(Get().BecomeDetached()); + break; + + case kSamePartition: + case kDowngradeToReed: + Attach(kAnyPartition); + break; + + case kBetterPartition: + break; + } + +exit: + return delay; +} + +void Mle::Attacher::SendParentRequest(ParentRequestType aType) +{ + Error error = kErrorNone; + TxMessage *message; + uint8_t scanMask = 0; + Ip6::Address destination; + + mParentRequestChallenge.GenerateRandom(); + + switch (aType) + { + case kToRouters: + case kToSelectedRouter: + scanMask = ScanMaskTlv::kRouterFlag; + break; + + case kToRoutersAndReeds: + scanMask = ScanMaskTlv::kRouterFlag | ScanMaskTlv::kEndDeviceFlag; + break; + } + + VerifyOrExit((message = Get().NewMleMessage(kCommandParentRequest)) != nullptr, error = kErrorNoBufs); + SuccessOrExit(error = message->AppendModeTlv(Get().mDeviceMode)); + SuccessOrExit(error = message->AppendChallengeTlv(mParentRequestChallenge)); + SuccessOrExit(error = message->AppendScanMaskTlv(scanMask)); + SuccessOrExit(error = message->AppendVersionTlv()); +#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE + SuccessOrExit(error = message->AppendTimeRequestTlv()); +#endif + +#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE + if (aType == kToSelectedRouter) + { + TxMessage *messageToCurParent = static_cast(message->Clone()); + + VerifyOrExit(messageToCurParent != nullptr, error = kErrorNoBufs); + + destination.SetToLinkLocalAddress(Get().mParent.GetExtAddress()); + error = messageToCurParent->SendTo(destination); + + if (error != kErrorNone) + { + messageToCurParent->Free(); + ExitNow(); + } + + Log(kMessageSend, kTypeParentRequestToRouters, destination); + + destination.SetToLinkLocalAddress(Get().mParentSearch.GetSelectedParent().GetExtAddress()); + } + else +#endif + { + destination.SetToLinkLocalAllRoutersMulticast(); + } + + SuccessOrExit(error = message->SendTo(destination)); + + switch (aType) + { + case kToRouters: + case kToSelectedRouter: + Log(kMessageSend, kTypeParentRequestToRouters, destination); + break; + + case kToRoutersAndReeds: + Log(kMessageSend, kTypeParentRequestToRoutersReeds, destination); + break; + } + +exit: + FreeMessageOnError(message, error); +} + +void Mle::Attacher::HandleChildIdRequestTxDone(const otMessage *aMessage, otError aError, void *aContext) +{ + OT_UNUSED_VARIABLE(aError); + + static_cast(aContext)->HandleChildIdRequestTxDone(AsCoreType(aMessage)); +} + +void Mle::Attacher::HandleChildIdRequestTxDone(const Message &aMessage) +{ + if (aMessage.GetTxSuccess() && !Get().IsRxOnWhenIdle()) + { + Get().SetAttachMode(true); + Get().SetRxOnWhenIdle(false); + } + + if (aMessage.IsLinkSecurityEnabled() && (mState == kStateChildIdRequest)) + { + // If the Child ID Request requires fragmentation and therefore + // link layer security, the frame transmission will be aborted. + // When the message is being freed, we signal to MLE to prepare a + // shorter Child ID Request message (by only including mesh-local + // address in the Address Registration TLV). + + LogInfo("Requesting shorter `Child ID Request`"); + + mAddressRegistrationMode = kAppendMeshLocalOnly; + IgnoreError(SendChildIdRequest()); + } +} + +Error Mle::Attacher::SendChildIdRequest(void) +{ + static const uint8_t kTlvs[] = {Tlv::kAddress16, Tlv::kNetworkData, Tlv::kRoute}; + + Error error = kErrorNone; + uint8_t tlvsLen = sizeof(kTlvs); + TxMessage *message = nullptr; + Ip6::Address destination; + + if (Get().mParent.GetExtAddress() == mParentCandidate.GetExtAddress()) + { + if (Get().IsChild()) + { + LogInfo("Already attached to candidate parent"); + ExitNow(error = kErrorAlready); + } + else + { + // Invalidate stale parent state. + // + // Parent state is not normally invalidated after becoming + // a Router/Leader (see #1875). When trying to attach to + // a better partition, invalidating old parent state + // (especially when in `kStateRestored`) ensures that + // `FindNeighbor()` returns `mParentCandidate` when + // processing the Child ID Response. + + Get().mParent.SetState(Neighbor::kStateInvalid); + } + } + + VerifyOrExit((message = Get().NewMleMessage(kCommandChildIdRequest)) != nullptr, error = kErrorNoBufs); + SuccessOrExit(error = message->AppendResponseTlv(mParentCandidate.mRxChallenge)); + SuccessOrExit(error = message->AppendLinkAndMleFrameCounterTlvs()); + SuccessOrExit(error = message->AppendModeTlv(Get().mDeviceMode)); + SuccessOrExit(error = message->AppendTimeoutTlv(Get().mTimeout)); + SuccessOrExit(error = message->AppendVersionTlv()); + SuccessOrExit(error = message->AppendSupervisionIntervalTlvIfSleepyChild()); + + if (!Get().IsFullThreadDevice()) + { + SuccessOrExit(error = message->AppendAddressRegistrationTlv(mAddressRegistrationMode)); + + // No need to request the last Route64 TLV for MTD + tlvsLen -= 1; + } + + SuccessOrExit(error = message->AppendTlvRequestTlv(kTlvs, tlvsLen)); + SuccessOrExit(error = message->AppendActiveAndPendingTimestampTlvs()); + + mParentCandidate.SetState(Neighbor::kStateValid); + + message->RegisterTxCallback(HandleChildIdRequestTxDone, this); + + destination.SetToLinkLocalAddress(mParentCandidate.GetExtAddress()); + SuccessOrExit(error = message->SendTo(destination)); + + Log(kMessageSend, + (mAddressRegistrationMode == kAppendMeshLocalOnly) ? kTypeChildIdRequestShort : kTypeChildIdRequest, + destination); +exit: + FreeMessageOnError(message, error); + return error; +} + +Error Mle::Attacher::GetNextAnnounceChannel(uint8_t &aChannel) const +{ + // This method gets the next channel to send announce on after + // `aChannel`. Returns `kErrorNotFound` if no more channel in the + // channel mask after `aChannel`. + + Mac::ChannelMask channelMask; + + if (Get().GetChannelMask(channelMask) != kErrorNone) + { + channelMask = Get().GetSupportedChannelMask(); + } + + return channelMask.GetNextChannel(aChannel); +} + +bool Mle::Attacher::HasMoreChannelsToAnnounce(void) const +{ + uint8_t channel = mAnnounceChannel; + + return GetNextAnnounceChannel(channel) == kErrorNone; +} + +bool Mle::Attacher::IsBetterParent(uint16_t aRloc16, + uint8_t aTwoWayLinkMargin, + const ConnectivityTlv &aConnectivityTlv, + uint16_t aVersion, + const Mac::CslAccuracy &aCslAccuracy) +{ + int rval; + + // Mesh Impacting Criteria + rval = ThreeWayCompare(LinkQualityForLinkMargin(aTwoWayLinkMargin), mParentCandidate.GetTwoWayLinkQuality()); + VerifyOrExit(rval == 0); + + rval = ThreeWayCompare(IsRouterRloc16(aRloc16), IsRouterRloc16(mParentCandidate.GetRloc16())); + VerifyOrExit(rval == 0); + + rval = ThreeWayCompare(aConnectivityTlv.GetParentPriority(), mParentCandidate.mPriority); + VerifyOrExit(rval == 0); + + // Prefer the parent with highest quality links (Link Quality 3 field in Connectivity TLV) to neighbors + rval = ThreeWayCompare(aConnectivityTlv.GetLinkQuality3(), mParentCandidate.mLinkQuality3); + VerifyOrExit(rval == 0); + + // Thread 1.2 Specification 4.5.2.1.2 Child Impacting Criteria + + rval = ThreeWayCompare(aVersion, mParentCandidate.GetVersion()); + VerifyOrExit(rval == 0); + + rval = ThreeWayCompare(aConnectivityTlv.GetSedBufferSize(), mParentCandidate.mSedBufferSize); + VerifyOrExit(rval == 0); + + rval = ThreeWayCompare(aConnectivityTlv.GetSedDatagramCount(), mParentCandidate.mSedDatagramCount); + VerifyOrExit(rval == 0); + + // Extra rules + rval = ThreeWayCompare(aConnectivityTlv.GetLinkQuality2(), mParentCandidate.mLinkQuality2); + VerifyOrExit(rval == 0); + + rval = ThreeWayCompare(aConnectivityTlv.GetLinkQuality1(), mParentCandidate.mLinkQuality1); + VerifyOrExit(rval == 0); + +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + // CSL metric + if (!Get().IsRxOnWhenIdle()) + { + uint64_t cslMetric = Get().CalcParentCslMetric(aCslAccuracy); + uint64_t candidateCslMetric = Get().CalcParentCslMetric(mParentCandidate.GetCslAccuracy()); + + // Smaller metric is better. + rval = ThreeWayCompare(candidateCslMetric, cslMetric); + VerifyOrExit(rval == 0); + } +#else + OT_UNUSED_VARIABLE(aCslAccuracy); +#endif + + rval = ThreeWayCompare(aTwoWayLinkMargin, mParentCandidate.mLinkMargin); + +exit: + return (rval > 0); +} + +void Mle::Attacher::HandleParentResponse(RxInfo &aRxInfo) +{ + Error error = kErrorNone; + int8_t rss = aRxInfo.mMessage.GetAverageRss(); + uint16_t version; + uint16_t sourceAddress; + LeaderData leaderData; + uint8_t linkMarginOut; + uint8_t twoWayLinkMargin; + ConnectivityTlv connectivityTlv; + uint32_t linkFrameCounter; + uint32_t mleFrameCounter; + Mac::ExtAddress extAddress; + Mac::CslAccuracy cslAccuracy; +#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE + TimeParameterTlv timeParameterTlv; +#endif + + SuccessOrExit(error = Tlv::Find(aRxInfo.mMessage, sourceAddress)); + + Log(kMessageReceive, kTypeParentResponse, aRxInfo.mMessageInfo.GetPeerAddr(), sourceAddress); + + SuccessOrExit(error = aRxInfo.mMessage.ReadVersionTlv(version)); + + SuccessOrExit(error = aRxInfo.mMessage.ReadAndMatchResponseTlvWith(mParentRequestChallenge)); + + extAddress.SetFromIid(aRxInfo.mMessageInfo.GetPeerAddr().GetIid()); + + if (Get().IsChild() && Get().mParent.GetExtAddress() == extAddress) + { + mReceivedResponseFromParent = true; + } + + SuccessOrExit(error = aRxInfo.mMessage.ReadLeaderDataTlv(leaderData)); + + SuccessOrExit(error = Tlv::Find(aRxInfo.mMessage, linkMarginOut)); + twoWayLinkMargin = Min(Get().ComputeLinkMargin(rss), linkMarginOut); + + SuccessOrExit(error = Tlv::FindTlv(aRxInfo.mMessage, connectivityTlv)); + VerifyOrExit(connectivityTlv.IsValid(), error = kErrorParse); + +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + switch (aRxInfo.mMessage.ReadCslClockAccuracyTlv(cslAccuracy)) + { + case kErrorNone: + break; + case kErrorNotFound: + cslAccuracy.Init(); // Use worst-case values if TLV is not found + break; + default: + ExitNow(error = kErrorParse); + } +#else + cslAccuracy.Init(); +#endif + +#if OPENTHREAD_CONFIG_MLE_PARENT_RESPONSE_CALLBACK_API_ENABLE + if (mParentResponseCallback.IsSet()) + { + otThreadParentResponseInfo parentinfo; + + parentinfo.mExtAddr = extAddress; + parentinfo.mRloc16 = sourceAddress; + parentinfo.mRssi = rss; + parentinfo.mPriority = connectivityTlv.GetParentPriority(); + parentinfo.mLinkQuality3 = connectivityTlv.GetLinkQuality3(); + parentinfo.mLinkQuality2 = connectivityTlv.GetLinkQuality2(); + parentinfo.mLinkQuality1 = connectivityTlv.GetLinkQuality1(); + parentinfo.mIsAttached = Get().IsAttached(); + + mParentResponseCallback.Invoke(&parentinfo); + } +#endif + + aRxInfo.mClass = RxInfo::kAuthoritativeMessage; + +#if OPENTHREAD_FTD + if (Get().IsFullThreadDevice() && !Get().IsDetached()) + { + bool isPartitionIdSame = (leaderData.GetPartitionId() == Get().mLeaderData.GetPartitionId()); + bool isIdSequenceSame = (connectivityTlv.GetIdSequence() == Get().GetRouterIdSequence()); + bool isIdSequenceGreater = + SerialNumber::IsGreater(connectivityTlv.GetIdSequence(), Get().GetRouterIdSequence()); + + switch (mMode) + { + case kAnyPartition: + VerifyOrExit(!isPartitionIdSame || isIdSequenceGreater); + break; + + case kSamePartition: + VerifyOrExit(isPartitionIdSame && isIdSequenceGreater); + break; + + case kDowngradeToReed: + VerifyOrExit(isPartitionIdSame && (isIdSequenceSame || isIdSequenceGreater)); + break; + + case kBetterPartition: + VerifyOrExit(!isPartitionIdSame); + + VerifyOrExit(ComparePartitions(connectivityTlv.IsSingleton(), leaderData, Get().IsSingleton(), + Get().mLeaderData) > 0); + break; + + case kBetterParent: + case kSelectedParent: + break; + } + } +#endif + + // Continue to process the "ParentResponse" if it is from current + // parent candidate to update the challenge and frame counters. + + if (mParentCandidate.IsStateParentResponse() && (mParentCandidate.GetExtAddress() != extAddress)) + { + // If already have a candidate parent, only seek a better parent + + int compare = 0; + +#if OPENTHREAD_FTD + if (Get().IsFullThreadDevice()) + { + compare = ComparePartitions(connectivityTlv.IsSingleton(), leaderData, mParentCandidate.mIsSingleton, + mParentCandidate.mLeaderData); + } + + // Only consider partitions that are the same or better + VerifyOrExit(compare >= 0); +#endif + + // Only consider better parents if the partitions are the same + if (compare == 0) + { + VerifyOrExit(IsBetterParent(sourceAddress, twoWayLinkMargin, connectivityTlv, version, cslAccuracy)); + } + } + + SuccessOrExit(error = aRxInfo.mMessage.ReadFrameCounterTlvs(linkFrameCounter, mleFrameCounter)); + +#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE + + if (Tlv::FindTlv(aRxInfo.mMessage, timeParameterTlv) == kErrorNone) + { + VerifyOrExit(timeParameterTlv.IsValid()); + + Get().SetTimeSyncPeriod(timeParameterTlv.GetTimeSyncPeriod()); + Get().SetXtalThreshold(timeParameterTlv.GetXtalThreshold()); + } + +#if OPENTHREAD_CONFIG_TIME_SYNC_REQUIRED + else + { + // If the time sync feature is required, don't choose the + // parent which doesn't support it. + ExitNow(); + } +#endif +#endif // OPENTHREAD_CONFIG_TIME_SYNC_ENABLE + + SuccessOrExit(error = aRxInfo.mMessage.ReadChallengeTlv(mParentCandidate.mRxChallenge)); + + Get().InitNeighbor(mParentCandidate, aRxInfo); + mParentCandidate.SetRloc16(sourceAddress); + mParentCandidate.GetLinkFrameCounters().SetAll(linkFrameCounter); + mParentCandidate.SetLinkAckFrameCounter(linkFrameCounter); + mParentCandidate.SetMleFrameCounter(mleFrameCounter); + mParentCandidate.SetVersion(version); + mParentCandidate.SetDeviceMode(DeviceMode(DeviceMode::kModeFullThreadDevice | DeviceMode::kModeRxOnWhenIdle | + DeviceMode::kModeFullNetworkData)); + mParentCandidate.SetLinkQualityOut(LinkQualityForLinkMargin(linkMarginOut)); + mParentCandidate.SetState(Neighbor::kStateParentResponse); + mParentCandidate.SetKeySequence(aRxInfo.mKeySequence); + mParentCandidate.SetLeaderCost(connectivityTlv.GetLeaderCost()); +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + mParentCandidate.SetCslAccuracy(cslAccuracy); +#endif + + mParentCandidate.mPriority = connectivityTlv.GetParentPriority(); + mParentCandidate.mLinkQuality3 = connectivityTlv.GetLinkQuality3(); + mParentCandidate.mLinkQuality2 = connectivityTlv.GetLinkQuality2(); + mParentCandidate.mLinkQuality1 = connectivityTlv.GetLinkQuality1(); + mParentCandidate.mSedBufferSize = connectivityTlv.GetSedBufferSize(); + mParentCandidate.mSedDatagramCount = connectivityTlv.GetSedDatagramCount(); + mParentCandidate.mLeaderData = leaderData; + mParentCandidate.mIsSingleton = connectivityTlv.IsSingleton(); + mParentCandidate.mLinkMargin = twoWayLinkMargin; + +exit: + LogProcessError(kTypeParentResponse, error); +} + +void Mle::Attacher::HandleChildIdResponse(RxInfo &aRxInfo) +{ + Error error = kErrorNone; + LeaderData leaderData; + uint16_t sourceAddress; + uint16_t shortAddress; + MeshCoP::Timestamp timestamp; + + SuccessOrExit(error = Tlv::Find(aRxInfo.mMessage, sourceAddress)); + + Log(kMessageReceive, kTypeChildIdResponse, aRxInfo.mMessageInfo.GetPeerAddr(), sourceAddress); + + VerifyOrExit(aRxInfo.IsNeighborStateValid(), error = kErrorSecurity); + + VerifyOrExit(mState == kStateChildIdRequest); + + SuccessOrExit(error = Tlv::Find(aRxInfo.mMessage, shortAddress)); + VerifyOrExit(RouterIdMatch(sourceAddress, shortAddress), error = kErrorRejected); + + SuccessOrExit(error = aRxInfo.mMessage.ReadLeaderDataTlv(leaderData)); + + VerifyOrExit(aRxInfo.mMessage.ContainsTlv(Tlv::kNetworkData)); + + switch (Tlv::Find(aRxInfo.mMessage, timestamp)) + { + case kErrorNone: + error = aRxInfo.mMessage.ReadAndSaveActiveDataset(timestamp); + error = (error == kErrorNotFound) ? kErrorNone : error; + SuccessOrExit(error); + break; + + case kErrorNotFound: + break; + + default: + ExitNow(error = kErrorParse); + } + + // Clear Pending Dataset if device succeed to reattach using stored Pending Dataset + if (mReattachMode == kReattachModePending) + { + Get().Clear(); + } + + switch (Tlv::Find(aRxInfo.mMessage, timestamp)) + { + case kErrorNone: + IgnoreError(aRxInfo.mMessage.ReadAndSavePendingDataset(timestamp)); + break; + + case kErrorNotFound: + Get().Clear(); + break; + + default: + ExitNow(error = kErrorParse); + } + +#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE + if (aRxInfo.mMessage.GetTimeSyncSeq() != OT_TIME_SYNC_INVALID_SEQ) + { + Get().HandleTimeSyncMessage(aRxInfo.mMessage); + } +#endif + + // Parent Attach Success + + Get().SetStateDetached(); + + Get().SetLeaderData(leaderData); + +#if OPENTHREAD_FTD + SuccessOrExit(error = Get().ReadAndProcessRouteTlvOnFtdChild(aRxInfo, RouterIdFromRloc16(sourceAddress))); +#endif + + mParentCandidate.CopyTo(Get().mParent); + mParentCandidate.Clear(); + +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + Get().SetCslParentAccuracy(Get().mParent.GetCslAccuracy()); +#endif + + Get().mParent.SetRloc16(sourceAddress); + + IgnoreError(aRxInfo.mMessage.ReadAndSetNetworkDataTlv(leaderData)); + + Get().SetStateChild(shortAddress); + + if (!Get().IsRxOnWhenIdle()) + { + Get().SetAttachMode(false); + Get().SetRxOnWhenIdle(false); + } + else + { + Get().SetRxOnWhenIdle(true); + } + + aRxInfo.mClass = RxInfo::kPeerMessage; + +exit: + LogProcessError(kTypeChildIdResponse, error); +} + +// LCOV_EXCL_START + +#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO) + +const char *Mle::Attacher::StateToString(State aState) +{ + static const char *const kStateStrings[] = { + "Idle", // (0) kStateIdle + "Start", // (1) kStateStart + "ParentReq", // (2) kStateParent + "Announce", // (3) kStateAnnounce + "ChildIdReq", // (4) kStateChildIdRequest + }; + + struct EnumCheck + { + InitEnumValidatorCounter(); + ValidateNextEnum(kStateIdle); + ValidateNextEnum(kStateStart); + ValidateNextEnum(kStateParentRequest); + ValidateNextEnum(kStateAnnounce); + ValidateNextEnum(kStateChildIdRequest); + }; + + return kStateStrings[aState]; +} + +#endif // OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO) + +#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_NOTE) + +const char *Mle::Attacher::AttachModeToString(AttachMode aMode) +{ + static const char *const kAttachModeStrings[] = { + "AnyPartition", // (0) kAnyPartition + "SamePartition", // (1) kSamePartition + "BetterPartition", // (2) kBetterPartition + "DowngradeToReed", // (3) kDowngradeToReed + "BetterParent", // (4) kBetterParent + "SelectedParent", // (5) kSelectedParent + }; + + struct EnumCheck + { + InitEnumValidatorCounter(); + ValidateNextEnum(kAnyPartition); + ValidateNextEnum(kSamePartition); + ValidateNextEnum(kBetterPartition); + ValidateNextEnum(kDowngradeToReed); + ValidateNextEnum(kBetterParent); + ValidateNextEnum(kSelectedParent); + }; + + return kAttachModeStrings[aMode]; +} + +const char *Mle::Attacher::ReattachModeToString(ReattachMode aMode) +{ + static const char *const kReattachModeStrings[] = { + "", // (0) kReattachModeStop + "reattaching with Active Dataset", // (1) kReattachModeActive + "reattaching with Pending Dataset", // (2) kReattachModePending + }; + + struct EnumCheck + { + InitEnumValidatorCounter(); + ValidateNextEnum(kReattachModeStop); + ValidateNextEnum(kReattachModeActive); + ValidateNextEnum(kReattachModePending); + }; + + return kReattachModeStrings[aMode]; +} + +#endif // OT_SHOULD_LOG_AT( OT_LOG_LEVEL_NOTE) + +// LCOV_EXCL_STOP + //--------------------------------------------------------------------------------------------------------------------- // Detacher