blob: fd3bd6c539ba4064b130f8e21c576a2509f8b699 [file] [log] [blame]
/*
* Copyright (c) 2016, The OpenThread Authors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @file
* This file implements MLE functionality required for the Thread Child, Router and Leader roles.
*/
#include "mle.hpp"
#include <openthread/platform/radio.h>
#include <openthread/platform/time.h>
#include "common/code_utils.hpp"
#include "common/debug.hpp"
#include "common/encoding.hpp"
#include "common/instance.hpp"
#include "common/locator-getters.hpp"
#include "common/logging.hpp"
#include "common/random.hpp"
#include "common/settings.hpp"
#include "crypto/aes_ccm.hpp"
#include "meshcop/meshcop.hpp"
#include "meshcop/meshcop_tlvs.hpp"
#include "net/netif.hpp"
#include "net/udp6.hpp"
#include "thread/address_resolver.hpp"
#include "thread/key_manager.hpp"
#include "thread/mle_router.hpp"
#include "thread/thread_netif.hpp"
#include "thread/time_sync_service.hpp"
using ot::Encoding::BigEndian::HostSwap16;
namespace ot {
namespace Mle {
Mle::Mle(Instance &aInstance)
: InstanceLocator(aInstance)
, mRetrieveNewNetworkData(false)
, mRole(OT_DEVICE_ROLE_DISABLED)
, mDeviceMode(DeviceMode::kModeRxOnWhenIdle | DeviceMode::kModeSecureDataRequest)
, mAttachState(kAttachStateIdle)
, mReattachState(kReattachStop)
, mAttachCounter(0)
, mAnnounceDelay(kAnnounceTimeout)
, mAttachTimer(aInstance, &Mle::HandleAttachTimer, this)
, mDelayedResponseTimer(aInstance, &Mle::HandleDelayedResponseTimer, this)
, mMessageTransmissionTimer(aInstance, &Mle::HandleMessageTransmissionTimer, this)
, mParentLeaderCost(0)
, mParentRequestMode(kAttachAny)
, mParentPriority(0)
, mParentLinkQuality3(0)
, mParentLinkQuality2(0)
, mParentLinkQuality1(0)
, mChildUpdateAttempts(0)
, mChildUpdateRequestState(kChildUpdateRequestNone)
, mDataRequestAttempts(0)
, mDataRequestState(kDataRequestNone)
, mAddressRegistrationMode(kAppendAllAddresses)
, mParentLinkMargin(0)
, mParentIsSingleton(false)
, mReceivedResponseFromParent(false)
, mSocket(aInstance.Get<Ip6::Udp>())
, mTimeout(kMleEndDeviceTimeout)
, mDiscoverHandler(NULL)
, mDiscoverContext(NULL)
, mDiscoverCcittIndex(0)
, mDiscoverAnsiIndex(0)
, mDiscoverInProgress(false)
, mDiscoverEnableFiltering(false)
#if OPENTHREAD_CONFIG_MLE_INFORM_PREVIOUS_PARENT_ON_REATTACH
, mPreviousParentRloc(Mac::kShortAddrInvalid)
#endif
#if OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE
, mParentSearchIsInBackoff(false)
, mParentSearchBackoffWasCanceled(false)
, mParentSearchRecentlyDetached(false)
, mParentSearchBackoffCancelTime(0)
, mParentSearchTimer(aInstance, &Mle::HandleParentSearchTimer, this)
#endif
, mAnnounceChannel(0)
, mAlternateChannel(0)
, mAlternatePanId(Mac::kPanIdBroadcast)
, mAlternateTimestamp(0)
, mNotifierCallback(aInstance, &Mle::HandleStateChanged, this)
, mParentResponseCb(NULL)
, mParentResponseCbContext(NULL)
{
otMeshLocalPrefix meshLocalPrefix;
mLeaderData.Clear();
mParentLeaderData.Clear();
mParent.Clear();
memset(&mChildIdRequest, 0, sizeof(mChildIdRequest));
mParentCandidate.Clear();
ResetCounters();
// link-local 64
mLinkLocal64.Clear();
mLinkLocal64.GetAddress().mFields.m16[0] = HostSwap16(0xfe80);
mLinkLocal64.GetAddress().SetIid(Get<Mac::Mac>().GetExtAddress());
mLinkLocal64.mPrefixLength = 64;
mLinkLocal64.mPreferred = true;
mLinkLocal64.mValid = true;
// Leader Aloc
mLeaderAloc.Clear();
mLeaderAloc.mPrefixLength = 64;
mLeaderAloc.mPreferred = true;
mLeaderAloc.mValid = true;
mLeaderAloc.mScopeOverride = Ip6::Address::kRealmLocalScope;
mLeaderAloc.mScopeOverrideValid = true;
#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
// Service Alocs
for (size_t i = 0; i < OT_ARRAY_LENGTH(mServiceAlocs); i++)
{
mServiceAlocs[i].Clear();
mServiceAlocs[i].mPrefixLength = 64;
mServiceAlocs[i].mPreferred = true;
mServiceAlocs[i].mValid = true;
mServiceAlocs[i].mScopeOverride = Ip6::Address::kRealmLocalScope;
mServiceAlocs[i].mScopeOverrideValid = true;
mServiceAlocs[i].GetAddress().mFields.m16[7] = HostSwap16(Mac::kShortAddrInvalid);
}
#endif
// initialize Mesh Local Prefix
meshLocalPrefix.m8[0] = 0xfd;
memcpy(&meshLocalPrefix.m8[1], Get<Mac::Mac>().GetExtendedPanId().m8, 5);
meshLocalPrefix.m8[6] = 0x00;
meshLocalPrefix.m8[7] = 0x00;
// mesh-local 64
mMeshLocal64.Clear();
Random::NonCrypto::FillBuffer(mMeshLocal64.GetAddress().mFields.m8 + OT_IP6_PREFIX_SIZE,
OT_IP6_ADDRESS_SIZE - OT_IP6_PREFIX_SIZE);
mMeshLocal64.mPrefixLength = 64;
mMeshLocal64.mPreferred = true;
mMeshLocal64.mValid = true;
mMeshLocal64.mScopeOverride = Ip6::Address::kRealmLocalScope;
mMeshLocal64.mScopeOverrideValid = true;
// mesh-local 16
mMeshLocal16.Clear();
mMeshLocal16.GetAddress().mFields.m16[4] = HostSwap16(0x0000);
mMeshLocal16.GetAddress().mFields.m16[5] = HostSwap16(0x00ff);
mMeshLocal16.GetAddress().mFields.m16[6] = HostSwap16(0xfe00);
mMeshLocal16.mPrefixLength = 64;
mMeshLocal16.mPreferred = true;
mMeshLocal16.mValid = true;
mMeshLocal16.mScopeOverride = Ip6::Address::kRealmLocalScope;
mMeshLocal16.mScopeOverrideValid = true;
mMeshLocal16.mRloc = true;
// Store RLOC address reference in MPL module.
Get<Ip6::Mpl>().SetMatchingAddress(mMeshLocal16.GetAddress());
// link-local all thread nodes
mLinkLocalAllThreadNodes.Clear();
mLinkLocalAllThreadNodes.GetAddress().mFields.m16[0] = HostSwap16(0xff32);
mLinkLocalAllThreadNodes.GetAddress().mFields.m16[6] = HostSwap16(0x0000);
mLinkLocalAllThreadNodes.GetAddress().mFields.m16[7] = HostSwap16(0x0001);
// realm-local all thread nodes
mRealmLocalAllThreadNodes.Clear();
mRealmLocalAllThreadNodes.GetAddress().mFields.m16[0] = HostSwap16(0xff33);
mRealmLocalAllThreadNodes.GetAddress().mFields.m16[6] = HostSwap16(0x0000);
mRealmLocalAllThreadNodes.GetAddress().mFields.m16[7] = HostSwap16(0x0001);
SetMeshLocalPrefix(meshLocalPrefix);
// `SetMeshLocalPrefix()` also adds the Mesh-Local EID and subscribes
// to the Link- and Realm-Local All Thread Nodes multicast addresses.
}
otError Mle::Enable(void)
{
otError error = OT_ERROR_NONE;
Ip6::SockAddr sockaddr;
UpdateLinkLocalAddress();
sockaddr.mPort = kUdpPort;
SuccessOrExit(error = mSocket.Open(&Mle::HandleUdpReceive, this));
SuccessOrExit(error = mSocket.Bind(sockaddr));
#if OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE
StartParentSearchTimer();
#endif
exit:
return error;
}
otError Mle::Disable(void)
{
otError error = OT_ERROR_NONE;
Stop(false);
SuccessOrExit(error = mSocket.Close());
SuccessOrExit(error = Get<ThreadNetif>().RemoveUnicastAddress(mLinkLocal64));
exit:
return error;
}
otError Mle::Start(bool aAnnounceAttach)
{
otError error = OT_ERROR_NONE;
// cannot bring up the interface if IEEE 802.15.4 promiscuous mode is enabled
VerifyOrExit(!Get<Radio>().GetPromiscuous(), error = OT_ERROR_INVALID_STATE);
VerifyOrExit(Get<ThreadNetif>().IsUp(), error = OT_ERROR_INVALID_STATE);
if (Get<Mac::Mac>().GetPanId() == Mac::kPanIdBroadcast)
{
// if PAN ID is not configured, pick a random one to start
uint16_t panid;
do
{
panid = Random::NonCrypto::GetUint16();
} while (panid == Mac::kPanIdBroadcast);
Get<Mac::Mac>().SetPanId(panid);
}
SetStateDetached();
ApplyMeshLocalPrefix();
SetRloc16(GetRloc16());
mAttachCounter = 0;
Get<KeyManager>().Start();
if (!aAnnounceAttach)
{
mReattachState = kReattachStart;
}
if (aAnnounceAttach || (GetRloc16() == Mac::kShortAddrInvalid))
{
BecomeChild(kAttachAny);
}
else if (IsActiveRouter(GetRloc16()))
{
if (Get<MleRouter>().BecomeRouter(ThreadStatusTlv::kTooFewRouters) != OT_ERROR_NONE)
{
BecomeChild(kAttachAny);
}
}
else
{
mChildUpdateAttempts = 0;
SendChildUpdateRequest();
}
exit:
return error;
}
void Mle::Stop(bool aClearNetworkDatasets)
{
if (aClearNetworkDatasets)
{
Get<MeshCoP::ActiveDataset>().HandleDetach();
Get<MeshCoP::PendingDataset>().HandleDetach();
}
VerifyOrExit(mRole != OT_DEVICE_ROLE_DISABLED);
Get<KeyManager>().Stop();
SetStateDetached();
Get<ThreadNetif>().UnsubscribeMulticast(mRealmLocalAllThreadNodes);
Get<ThreadNetif>().UnsubscribeMulticast(mLinkLocalAllThreadNodes);
Get<ThreadNetif>().RemoveUnicastAddress(mMeshLocal16);
Get<ThreadNetif>().RemoveUnicastAddress(mMeshLocal64);
SetRole(OT_DEVICE_ROLE_DISABLED);
exit:
return;
}
void Mle::SetRole(otDeviceRole aRole)
{
otDeviceRole oldRole = mRole;
SuccessOrExit(Get<Notifier>().Update(mRole, aRole, OT_CHANGED_THREAD_ROLE));
otLogNoteMle("Role %s -> %s", RoleToString(oldRole), RoleToString(mRole));
switch (mRole)
{
case OT_DEVICE_ROLE_DISABLED:
mCounters.mDisabledRole++;
break;
case OT_DEVICE_ROLE_DETACHED:
mCounters.mDetachedRole++;
break;
case OT_DEVICE_ROLE_CHILD:
mCounters.mChildRole++;
break;
case OT_DEVICE_ROLE_ROUTER:
mCounters.mRouterRole++;
break;
case OT_DEVICE_ROLE_LEADER:
mCounters.mLeaderRole++;
break;
}
#if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE
if (IsAttached())
{
SuccessOrExit(Get<MeshCoP::BorderAgent>().Start());
}
else
{
SuccessOrExit(Get<MeshCoP::BorderAgent>().Stop());
}
#endif
exit:
OT_UNUSED_VARIABLE(oldRole);
}
void Mle::SetAttachState(AttachState aState)
{
VerifyOrExit(aState != mAttachState);
otLogInfoMle("AttachState %s -> %s", AttachStateToString(mAttachState), AttachStateToString(aState));
mAttachState = aState;
exit:
return;
}
otError Mle::Restore(void)
{
otError error = OT_ERROR_NONE;
Settings::NetworkInfo networkInfo;
Settings::ParentInfo parentInfo;
Get<MeshCoP::ActiveDataset>().Restore();
Get<MeshCoP::PendingDataset>().Restore();
SuccessOrExit(error = Get<Settings>().ReadNetworkInfo(networkInfo));
Get<KeyManager>().SetCurrentKeySequence(networkInfo.mKeySequence);
Get<KeyManager>().SetMleFrameCounter(networkInfo.mMleFrameCounter);
Get<KeyManager>().SetMacFrameCounter(networkInfo.mMacFrameCounter);
mDeviceMode.Set(networkInfo.mDeviceMode);
switch (networkInfo.mRole)
{
case OT_DEVICE_ROLE_CHILD:
case OT_DEVICE_ROLE_ROUTER:
case OT_DEVICE_ROLE_LEADER:
break;
default:
ExitNow();
}
Get<Mac::Mac>().SetShortAddress(networkInfo.mRloc16);
Get<Mac::Mac>().SetExtAddress(networkInfo.mExtAddress);
memcpy(&mMeshLocal64.GetAddress().mFields.m8[OT_IP6_PREFIX_SIZE], networkInfo.mMlIid,
OT_IP6_ADDRESS_SIZE - OT_IP6_PREFIX_SIZE);
if (networkInfo.mRloc16 == Mac::kShortAddrInvalid)
{
ExitNow();
}
if (!IsActiveRouter(networkInfo.mRloc16))
{
error = Get<Settings>().ReadParentInfo(parentInfo);
if (error != OT_ERROR_NONE)
{
// If the restored RLOC16 corresponds to an end-device, it
// is expected that the `ParentInfo` settings to be valid
// as well. The device can still recover from such an invalid
// setting by skipping the re-attach ("Child Update Request"
// exchange) and going through the full attach process.
otLogWarnMle("Invalid settings - no saved parent info with valid end-device RLOC16 0x%04x",
networkInfo.mRloc16);
ExitNow();
}
mParent.Clear();
mParent.SetExtAddress(*static_cast<Mac::ExtAddress *>(&parentInfo.mExtAddress));
mParent.SetDeviceMode(DeviceMode(DeviceMode::kModeFullThreadDevice | DeviceMode::kModeRxOnWhenIdle |
DeviceMode::kModeFullNetworkData | DeviceMode::kModeSecureDataRequest));
mParent.SetRloc16(Rloc16FromRouterId(RouterIdFromRloc16(networkInfo.mRloc16)));
mParent.SetState(Neighbor::kStateRestored);
#if OPENTHREAD_CONFIG_MLE_INFORM_PREVIOUS_PARENT_ON_REATTACH
mPreviousParentRloc = mParent.GetRloc16();
#endif
}
else
{
Get<MleRouter>().SetRouterId(RouterIdFromRloc16(GetRloc16()));
Get<MleRouter>().SetPreviousPartitionId(networkInfo.mPreviousPartitionId);
Get<MleRouter>().RestoreChildren();
}
exit:
return error;
}
otError Mle::Store(void)
{
otError error = OT_ERROR_NONE;
Settings::NetworkInfo networkInfo;
networkInfo.Clear();
if (IsAttached())
{
// Only update network information while we are attached to
// avoid losing/overwriting previous information when a reboot
// occurs after a message is sent but before attaching.
networkInfo.mRole = mRole;
networkInfo.mRloc16 = GetRloc16();
networkInfo.mPreviousPartitionId = mLeaderData.GetPartitionId();
networkInfo.mExtAddress = Get<Mac::Mac>().GetExtAddress();
memcpy(networkInfo.mMlIid, &mMeshLocal64.GetAddress().mFields.m8[OT_IP6_PREFIX_SIZE], OT_IP6_IID_SIZE);
if (mRole == OT_DEVICE_ROLE_CHILD)
{
Settings::ParentInfo parentInfo;
parentInfo.Clear();
parentInfo.mExtAddress = mParent.GetExtAddress();
SuccessOrExit(error = Get<Settings>().SaveParentInfo(parentInfo));
}
}
else
{
// When not attached, read out any previous saved `NetworkInfo`.
// If there is none, it indicates that device was never attached
// before. In that case, no need to save any info (note that on
// a device reset the MLE/MAC frame counters would reset but
// device also starts with a new randomly generated extended
// address. If there is a previously saved `NetworkInfo`, we
// just update the key sequence and MAC and MLE frame counters.
SuccessOrExit(Get<Settings>().ReadNetworkInfo(networkInfo));
}
networkInfo.mKeySequence = Get<KeyManager>().GetCurrentKeySequence();
networkInfo.mMleFrameCounter = Get<KeyManager>().GetMleFrameCounter() + OPENTHREAD_CONFIG_STORE_FRAME_COUNTER_AHEAD;
networkInfo.mMacFrameCounter = Get<KeyManager>().GetMacFrameCounter() + OPENTHREAD_CONFIG_STORE_FRAME_COUNTER_AHEAD;
networkInfo.mDeviceMode = mDeviceMode.Get();
SuccessOrExit(error = Get<Settings>().SaveNetworkInfo(networkInfo));
Get<KeyManager>().SetStoredMleFrameCounter(networkInfo.mMleFrameCounter);
Get<KeyManager>().SetStoredMacFrameCounter(networkInfo.mMacFrameCounter);
otLogDebgMle("Store Network Information");
exit:
return error;
}
otError Mle::Discover(const Mac::ChannelMask &aScanChannels,
uint16_t aPanId,
bool aJoiner,
bool aEnableFiltering,
DiscoverHandler aCallback,
void * aContext)
{
otError error = OT_ERROR_NONE;
Message * message = NULL;
Ip6::Address destination;
Tlv tlv;
MeshCoP::DiscoveryRequestTlv discoveryRequest;
uint16_t startOffset;
VerifyOrExit(!mDiscoverInProgress, error = OT_ERROR_BUSY);
mDiscoverEnableFiltering = aEnableFiltering;
if (mDiscoverEnableFiltering)
{
Mac::ExtAddress extAddress;
Crc16 ccitt(Crc16::kCcitt);
Crc16 ansi(Crc16::kAnsi);
Get<Radio>().GetIeeeEui64(extAddress);
MeshCoP::ComputeJoinerId(extAddress, extAddress);
// Compute bloom filter (for steering data)
for (size_t i = 0; i < sizeof(extAddress.m8); i++)
{
ccitt.Update(extAddress.m8[i]);
ansi.Update(extAddress.m8[i]);
}
mDiscoverCcittIndex = ccitt.Get();
mDiscoverAnsiIndex = ansi.Get();
}
mDiscoverHandler = aCallback;
mDiscoverContext = aContext;
Get<MeshForwarder>().SetDiscoverParameters(aScanChannels);
VerifyOrExit((message = NewMleMessage()) != NULL, error = OT_ERROR_NO_BUFS);
message->SetSubType(Message::kSubTypeMleDiscoverRequest);
message->SetPanId(aPanId);
SuccessOrExit(error = AppendHeader(*message, Header::kCommandDiscoveryRequest));
// Discovery TLV
tlv.SetType(Tlv::kDiscovery);
SuccessOrExit(error = message->Append(&tlv, sizeof(tlv)));
startOffset = message->GetLength();
// Discovery Request TLV
discoveryRequest.Init();
discoveryRequest.SetVersion(kThreadVersion);
discoveryRequest.SetJoiner(aJoiner);
SuccessOrExit(error = discoveryRequest.AppendTo(*message));
tlv.SetLength(static_cast<uint8_t>(message->GetLength() - startOffset));
message->Write(startOffset - sizeof(tlv), sizeof(tlv), &tlv);
destination.Clear();
destination.mFields.m16[0] = HostSwap16(0xff02);
destination.mFields.m16[7] = HostSwap16(0x0002);
SuccessOrExit(error = SendMessage(*message, destination));
mDiscoverInProgress = true;
LogMleMessage("Send Discovery Request", destination);
exit:
if (error != OT_ERROR_NONE && message != NULL)
{
message->Free();
}
return error;
}
void Mle::HandleDiscoverComplete(void)
{
mDiscoverInProgress = false;
mDiscoverEnableFiltering = false;
mDiscoverHandler(NULL, mDiscoverContext);
}
otError Mle::BecomeDetached(void)
{
otError error = OT_ERROR_NONE;
VerifyOrExit(mRole != OT_DEVICE_ROLE_DISABLED, error = OT_ERROR_INVALID_STATE);
// In case role is already detached and attach state is `kAttachStateStart`
// (i.e., waiting to start an attach attempt), there is no need to make any
// changes.
VerifyOrExit(mRole != OT_DEVICE_ROLE_DETACHED || mAttachState != kAttachStateStart);
// not in reattach stage after reset
if (mReattachState == kReattachStop)
{
Get<MeshCoP::PendingDataset>().HandleDetach();
}
#if OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE
mParentSearchRecentlyDetached = true;
#endif
SetStateDetached();
mParent.SetState(Neighbor::kStateInvalid);
SetRloc16(Mac::kShortAddrInvalid);
BecomeChild(kAttachAny);
exit:
return error;
}
otError Mle::BecomeChild(AttachMode aMode)
{
otError error = OT_ERROR_NONE;
VerifyOrExit(mRole != OT_DEVICE_ROLE_DISABLED, error = OT_ERROR_INVALID_STATE);
VerifyOrExit(!IsAttaching(), error = OT_ERROR_BUSY);
if (mReattachState == kReattachStart)
{
if (Get<MeshCoP::ActiveDataset>().Restore() == OT_ERROR_NONE)
{
mReattachState = kReattachActive;
}
else
{
mReattachState = kReattachStop;
}
}
ResetParentCandidate();
SetAttachState(kAttachStateStart);
mParentRequestMode = aMode;
if (aMode != kAttachBetter)
{
if (IsFullThreadDevice())
{
Get<MleRouter>().StopAdvertiseTimer();
}
}
else
{
mCounters.mBetterPartitionAttachAttempts++;
}
mAttachTimer.Start(GetAttachStartDelay());
if (mRole == OT_DEVICE_ROLE_DETACHED)
{
mAttachCounter++;
if (mAttachCounter == 0)
{
mAttachCounter--;
}
mCounters.mAttachAttempts++;
if (!IsRxOnWhenIdle())
{
Get<Mac::Mac>().SetRxOnWhenIdle(false);
}
}
exit:
return error;
}
uint32_t Mle::GetAttachStartDelay(void) const
{
uint32_t delay = 1;
uint32_t jitter;
VerifyOrExit(mRole == OT_DEVICE_ROLE_DETACHED);
if (mAttachCounter == 0)
{
delay = 1 + Random::NonCrypto::GetUint32InRange(0, kParentRequestRouterTimeout);
ExitNow();
}
#if OPENTHREAD_CONFIG_MLE_ATTACH_BACKOFF_ENABLE
else
{
uint16_t counter = mAttachCounter - 1;
const uint32_t ratio = kAttachBackoffMaxInterval / kAttachBackoffMinInterval;
if ((counter < sizeof(ratio) * CHAR_BIT) && ((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;
}
otLogNoteMle("Attach attempt %d unsuccessful, will try again in %u.%03u seconds", mAttachCounter, delay / 1000,
delay % 1000);
exit:
return delay;
}
bool Mle::IsAttached(void) const
{
return (mRole == OT_DEVICE_ROLE_CHILD || mRole == OT_DEVICE_ROLE_ROUTER || mRole == OT_DEVICE_ROLE_LEADER);
}
void Mle::SetStateDetached(void)
{
if (mRole == OT_DEVICE_ROLE_LEADER)
{
Get<ThreadNetif>().RemoveUnicastAddress(mLeaderAloc);
}
SetRole(OT_DEVICE_ROLE_DETACHED);
SetAttachState(kAttachStateIdle);
mAttachTimer.Stop();
mMessageTransmissionTimer.Stop();
mChildUpdateRequestState = kChildUpdateRequestNone;
mChildUpdateAttempts = 0;
mDataRequestState = kDataRequestNone;
mDataRequestAttempts = 0;
Get<MeshForwarder>().SetRxOnWhenIdle(true);
Get<Mac::Mac>().SetBeaconEnabled(false);
Get<MleRouter>().HandleDetachStart();
Get<Ip6::Ip6>().SetForwardingEnabled(false);
Get<Ip6::Mpl>().SetTimerExpirations(0);
}
void Mle::SetStateChild(uint16_t aRloc16)
{
if (mRole == OT_DEVICE_ROLE_LEADER)
{
Get<ThreadNetif>().RemoveUnicastAddress(mLeaderAloc);
}
SetRloc16(aRloc16);
SetRole(OT_DEVICE_ROLE_CHILD);
SetAttachState(kAttachStateIdle);
mAttachTimer.Stop();
mAttachCounter = 0;
mReattachState = kReattachStop;
mChildUpdateAttempts = 0;
mDataRequestAttempts = 0;
Get<Mac::Mac>().SetBeaconEnabled(false);
ScheduleMessageTransmissionTimer();
if (IsFullThreadDevice())
{
Get<MleRouter>().HandleChildStart(mParentRequestMode);
}
#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE || OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
Get<NetworkData::Local>().ClearResubmitDelayTimer();
#endif
Get<Ip6::Ip6>().SetForwardingEnabled(false);
Get<Ip6::Mpl>().SetTimerExpirations(kMplChildDataMessageTimerExpirations);
// send announce after attached if needed
InformPreviousChannel();
#if OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE
UpdateParentSearchState();
#endif
#if OPENTHREAD_CONFIG_MLE_INFORM_PREVIOUS_PARENT_ON_REATTACH
InformPreviousParent();
mPreviousParentRloc = mParent.GetRloc16();
#endif
}
void Mle::InformPreviousChannel(void)
{
VerifyOrExit(mAlternatePanId != Mac::kPanIdBroadcast);
VerifyOrExit(mRole == OT_DEVICE_ROLE_CHILD || mRole == OT_DEVICE_ROLE_ROUTER);
if (!IsFullThreadDevice() || mRole == OT_DEVICE_ROLE_ROUTER ||
Get<MleRouter>().GetRouterSelectionJitterTimeout() == 0)
{
mAlternatePanId = Mac::kPanIdBroadcast;
Get<AnnounceBeginServer>().SendAnnounce(1 << mAlternateChannel);
}
exit:
return;
}
void Mle::SetTimeout(uint32_t aTimeout)
{
VerifyOrExit(mTimeout != aTimeout);
if (aTimeout < kMinTimeout)
{
aTimeout = kMinTimeout;
}
mTimeout = aTimeout;
Get<DataPollSender>().RecalculatePollPeriod();
if (mRole == OT_DEVICE_ROLE_CHILD)
{
SendChildUpdateRequest();
}
exit:
return;
}
otError Mle::SetDeviceMode(DeviceMode aDeviceMode)
{
otError error = OT_ERROR_NONE;
DeviceMode oldMode = mDeviceMode;
VerifyOrExit(aDeviceMode.IsValid(), error = OT_ERROR_INVALID_ARGS);
VerifyOrExit(mDeviceMode != aDeviceMode);
mDeviceMode = aDeviceMode;
otLogNoteMle("Mode 0x%02x -> 0x%02x [%s]", oldMode.Get(), mDeviceMode.Get(), mDeviceMode.ToString().AsCString());
Store();
switch (mRole)
{
case OT_DEVICE_ROLE_DISABLED:
break;
case OT_DEVICE_ROLE_DETACHED:
mAttachCounter = 0;
SetStateDetached();
BecomeChild(kAttachAny);
break;
case OT_DEVICE_ROLE_CHILD:
SetStateChild(GetRloc16());
SendChildUpdateRequest();
break;
case OT_DEVICE_ROLE_ROUTER:
case OT_DEVICE_ROLE_LEADER:
if (oldMode.IsFullThreadDevice() && !mDeviceMode.IsFullThreadDevice())
{
BecomeDetached();
}
break;
}
exit:
return error;
}
void Mle::UpdateLinkLocalAddress(void)
{
Get<ThreadNetif>().RemoveUnicastAddress(mLinkLocal64);
mLinkLocal64.GetAddress().SetIid(Get<Mac::Mac>().GetExtAddress());
Get<ThreadNetif>().AddUnicastAddress(mLinkLocal64);
Get<Notifier>().Signal(OT_CHANGED_THREAD_LL_ADDR);
}
void Mle::SetMeshLocalPrefix(const otMeshLocalPrefix &aMeshLocalPrefix)
{
if (memcmp(mMeshLocal64.GetAddress().mFields.m8, aMeshLocalPrefix.m8, sizeof(aMeshLocalPrefix)) == 0)
{
Get<Notifier>().SignalIfFirst(OT_CHANGED_THREAD_ML_ADDR);
ExitNow();
}
if (Get<ThreadNetif>().IsUp())
{
Get<ThreadNetif>().RemoveUnicastAddress(mLeaderAloc);
// We must remove the old addresses before adding the new ones.
Get<ThreadNetif>().RemoveUnicastAddress(mMeshLocal64);
Get<ThreadNetif>().RemoveUnicastAddress(mMeshLocal16);
Get<ThreadNetif>().UnsubscribeMulticast(mLinkLocalAllThreadNodes);
Get<ThreadNetif>().UnsubscribeMulticast(mRealmLocalAllThreadNodes);
}
memcpy(mMeshLocal64.GetAddress().mFields.m8, aMeshLocalPrefix.m8, sizeof(aMeshLocalPrefix));
memcpy(mMeshLocal16.GetAddress().mFields.m8, aMeshLocalPrefix.m8, sizeof(aMeshLocalPrefix));
memcpy(mLeaderAloc.GetAddress().mFields.m8, aMeshLocalPrefix.m8, sizeof(aMeshLocalPrefix));
// Just keep mesh local prefix if network interface is down
VerifyOrExit(Get<ThreadNetif>().IsUp());
ApplyMeshLocalPrefix();
exit:
return;
}
void Mle::ApplyMeshLocalPrefix(void)
{
#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
for (uint8_t i = 0; i < OT_ARRAY_LENGTH(mServiceAlocs); i++)
{
if (HostSwap16(mServiceAlocs[i].GetAddress().mFields.m16[7]) != Mac::kShortAddrInvalid)
{
Get<ThreadNetif>().RemoveUnicastAddress(mServiceAlocs[i]);
memcpy(mServiceAlocs[i].GetAddress().mFields.m8, mMeshLocal64.GetAddress().mFields.m8, 8);
Get<ThreadNetif>().AddUnicastAddress(mServiceAlocs[i]);
}
}
#endif
mLinkLocalAllThreadNodes.GetAddress().mFields.m8[3] = 64;
memcpy(mLinkLocalAllThreadNodes.GetAddress().mFields.m8 + 4, mMeshLocal64.GetAddress().mFields.m8, 8);
mRealmLocalAllThreadNodes.GetAddress().mFields.m8[3] = 64;
memcpy(mRealmLocalAllThreadNodes.GetAddress().mFields.m8 + 4, mMeshLocal64.GetAddress().mFields.m8, 8);
VerifyOrExit(mRole != OT_DEVICE_ROLE_DISABLED);
// Add the addresses back into the table.
Get<ThreadNetif>().AddUnicastAddress(mMeshLocal64);
Get<ThreadNetif>().SubscribeMulticast(mLinkLocalAllThreadNodes);
Get<ThreadNetif>().SubscribeMulticast(mRealmLocalAllThreadNodes);
if (IsAttached())
{
Get<ThreadNetif>().AddUnicastAddress(mMeshLocal16);
}
// update Leader ALOC
if (mRole == OT_DEVICE_ROLE_LEADER)
{
Get<ThreadNetif>().AddUnicastAddress(mLeaderAloc);
}
exit:
// Changing the prefix also causes the mesh local address to be different.
Get<Notifier>().Signal(OT_CHANGED_THREAD_ML_ADDR);
}
uint16_t Mle::GetRloc16(void) const
{
return Get<Mac::Mac>().GetShortAddress();
}
void Mle::SetRloc16(uint16_t aRloc16)
{
uint16_t oldRloc16 = GetRloc16();
if (aRloc16 != oldRloc16)
{
otLogNoteMle("RLOC16 %04x -> %04x", oldRloc16, aRloc16);
}
Get<ThreadNetif>().RemoveUnicastAddress(mMeshLocal16);
Get<Mac::Mac>().SetShortAddress(aRloc16);
Get<Ip6::Mpl>().SetSeedId(aRloc16);
if (aRloc16 != Mac::kShortAddrInvalid)
{
// mesh-local 16
mMeshLocal16.GetAddress().mFields.m16[7] = HostSwap16(aRloc16);
Get<ThreadNetif>().AddUnicastAddress(mMeshLocal16);
#if OPENTHREAD_FTD
Get<AddressResolver>().RestartAddressQueries();
#endif
}
}
void Mle::SetLeaderData(uint32_t aPartitionId, uint8_t aWeighting, uint8_t aLeaderRouterId)
{
if (mLeaderData.GetPartitionId() != aPartitionId)
{
Get<MleRouter>().HandlePartitionChange();
Get<Notifier>().Signal(OT_CHANGED_THREAD_PARTITION_ID);
mCounters.mPartitionIdChanges++;
}
else
{
Get<Notifier>().SignalIfFirst(OT_CHANGED_THREAD_PARTITION_ID);
}
mLeaderData.SetPartitionId(aPartitionId);
mLeaderData.SetWeighting(aWeighting);
mLeaderData.SetLeaderRouterId(aLeaderRouterId);
}
otError Mle::GetLeaderAddress(Ip6::Address &aAddress) const
{
otError error = OT_ERROR_NONE;
VerifyOrExit(GetRloc16() != Mac::kShortAddrInvalid, error = OT_ERROR_DETACHED);
memcpy(&aAddress, &mMeshLocal16.GetAddress(), 8);
aAddress.mFields.m16[4] = HostSwap16(0x0000);
aAddress.mFields.m16[5] = HostSwap16(0x00ff);
aAddress.mFields.m16[6] = HostSwap16(0xfe00);
aAddress.mFields.m16[7] = HostSwap16(Rloc16FromRouterId(mLeaderData.GetLeaderRouterId()));
exit:
return error;
}
otError Mle::GetAlocAddress(Ip6::Address &aAddress, uint16_t aAloc16) const
{
otError error = OT_ERROR_NONE;
VerifyOrExit(GetRloc16() != Mac::kShortAddrInvalid, error = OT_ERROR_DETACHED);
memcpy(&aAddress, &mMeshLocal16.GetAddress(), 14);
aAddress.mFields.m16[7] = HostSwap16(aAloc16);
exit:
return error;
}
otError Mle::GetServiceAloc(uint8_t aServiceId, Ip6::Address &aAddress) const
{
otError error = OT_ERROR_NONE;
VerifyOrExit(GetRloc16() != Mac::kShortAddrInvalid, error = OT_ERROR_DETACHED);
memcpy(&aAddress, &mMeshLocal16.GetAddress(), 8);
aAddress.mFields.m16[4] = HostSwap16(0x0000);
aAddress.mFields.m16[5] = HostSwap16(0x00ff);
aAddress.mFields.m16[6] = HostSwap16(0xfe00);
aAddress.mFields.m16[7] = HostSwap16(ServiceAlocFromId(aServiceId));
exit:
return error;
}
otError Mle::AddLeaderAloc(void)
{
otError error = OT_ERROR_NONE;
VerifyOrExit(mRole == OT_DEVICE_ROLE_LEADER, error = OT_ERROR_INVALID_STATE);
SuccessOrExit(error = GetLeaderAloc(mLeaderAloc.GetAddress()));
error = Get<ThreadNetif>().AddUnicastAddress(mLeaderAloc);
exit:
return error;
}
const LeaderDataTlv &Mle::GetLeaderDataTlv(void)
{
mLeaderData.SetDataVersion(Get<NetworkData::Leader>().GetVersion());
mLeaderData.SetStableDataVersion(Get<NetworkData::Leader>().GetStableVersion());
return mLeaderData;
}
otError Mle::GetLeaderData(otLeaderData &aLeaderData)
{
const LeaderDataTlv &leaderData(GetLeaderDataTlv());
otError error = OT_ERROR_NONE;
VerifyOrExit(mRole != OT_DEVICE_ROLE_DISABLED && mRole != OT_DEVICE_ROLE_DETACHED, error = OT_ERROR_DETACHED);
aLeaderData.mPartitionId = leaderData.GetPartitionId();
aLeaderData.mWeighting = leaderData.GetWeighting();
aLeaderData.mDataVersion = leaderData.GetDataVersion();
aLeaderData.mStableDataVersion = leaderData.GetStableDataVersion();
aLeaderData.mLeaderRouterId = leaderData.GetLeaderRouterId();
exit:
return error;
}
Message *Mle::NewMleMessage(void)
{
Message * message;
otMessageSettings settings = {false, static_cast<otMessagePriority>(kMleMessagePriority)};
message = mSocket.NewMessage(0, &settings);
VerifyOrExit(message != NULL);
message->SetSubType(Message::kSubTypeMleGeneral);
exit:
return message;
}
otError Mle::AppendHeader(Message &aMessage, Header::Command aCommand)
{
otError error = OT_ERROR_NONE;
Header header;
header.Init();
if (aCommand == Header::kCommandDiscoveryRequest || aCommand == Header::kCommandDiscoveryResponse)
{
header.SetSecuritySuite(Header::kNoSecurity);
}
else
{
header.SetKeyIdMode2();
}
header.SetCommand(aCommand);
SuccessOrExit(error = aMessage.Append(&header, header.GetLength()));
exit:
return error;
}
otError Mle::AppendSourceAddress(Message &aMessage)
{
SourceAddressTlv tlv;
tlv.Init();
tlv.SetRloc16(GetRloc16());
return tlv.AppendTo(aMessage);
}
otError Mle::AppendStatus(Message &aMessage, StatusTlv::Status aStatus)
{
StatusTlv tlv;
tlv.Init();
tlv.SetStatus(aStatus);
return tlv.AppendTo(aMessage);
}
otError Mle::AppendMode(Message &aMessage, DeviceMode aMode)
{
ModeTlv tlv;
tlv.Init();
tlv.SetMode(aMode);
return tlv.AppendTo(aMessage);
}
otError Mle::AppendTimeout(Message &aMessage, uint32_t aTimeout)
{
TimeoutTlv tlv;
tlv.Init();
tlv.SetTimeout(aTimeout);
return tlv.AppendTo(aMessage);
}
otError Mle::AppendChallenge(Message &aMessage, const uint8_t *aChallenge, uint8_t aChallengeLength)
{
otError error;
Tlv tlv;
tlv.SetType(Tlv::kChallenge);
tlv.SetLength(aChallengeLength);
SuccessOrExit(error = aMessage.Append(&tlv, sizeof(tlv)));
SuccessOrExit(error = aMessage.Append(aChallenge, aChallengeLength));
exit:
return error;
}
otError Mle::AppendResponse(Message &aMessage, const uint8_t *aResponse, uint8_t aResponseLength)
{
otError error;
Tlv tlv;
tlv.SetType(Tlv::kResponse);
tlv.SetLength(aResponseLength);
SuccessOrExit(error = aMessage.Append(&tlv, sizeof(tlv)));
SuccessOrExit(error = aMessage.Append(aResponse, aResponseLength));
exit:
return error;
}
otError Mle::AppendLinkFrameCounter(Message &aMessage)
{
LinkFrameCounterTlv tlv;
tlv.Init();
tlv.SetFrameCounter(Get<KeyManager>().GetMacFrameCounter());
return tlv.AppendTo(aMessage);
}
otError Mle::AppendMleFrameCounter(Message &aMessage)
{
MleFrameCounterTlv tlv;
tlv.Init();
tlv.SetFrameCounter(Get<KeyManager>().GetMleFrameCounter());
return tlv.AppendTo(aMessage);
}
otError Mle::AppendAddress16(Message &aMessage, uint16_t aRloc16)
{
Address16Tlv tlv;
tlv.Init();
tlv.SetRloc16(aRloc16);
return tlv.AppendTo(aMessage);
}
otError Mle::AppendLeaderData(Message &aMessage)
{
mLeaderData.Init();
mLeaderData.SetDataVersion(Get<NetworkData::Leader>().GetVersion());
mLeaderData.SetStableDataVersion(Get<NetworkData::Leader>().GetStableVersion());
return mLeaderData.AppendTo(aMessage);
}
void Mle::FillNetworkDataTlv(NetworkDataTlv &aTlv, bool aStableOnly)
{
uint8_t length = sizeof(NetworkDataTlv) - sizeof(Tlv); // sizeof( NetworkDataTlv::mNetworkData )
// Ignore result code, provided buffer must be enough
Get<NetworkData::Leader>().GetNetworkData(aStableOnly, aTlv.GetNetworkData(), length);
aTlv.SetLength(length);
}
otError Mle::AppendNetworkData(Message &aMessage, bool aStableOnly)
{
otError error = OT_ERROR_NONE;
NetworkDataTlv tlv;
VerifyOrExit(!mRetrieveNewNetworkData, error = OT_ERROR_INVALID_STATE);
tlv.Init();
FillNetworkDataTlv(tlv, aStableOnly);
error = tlv.AppendTo(aMessage);
exit:
return error;
}
otError Mle::AppendTlvRequest(Message &aMessage, const uint8_t *aTlvs, uint8_t aTlvsLength)
{
otError error;
Tlv tlv;
tlv.SetType(Tlv::kTlvRequest);
tlv.SetLength(aTlvsLength);
SuccessOrExit(error = aMessage.Append(&tlv, sizeof(tlv)));
SuccessOrExit(error = aMessage.Append(aTlvs, aTlvsLength));
exit:
return error;
}
otError Mle::AppendScanMask(Message &aMessage, uint8_t aScanMask)
{
ScanMaskTlv tlv;
tlv.Init();
tlv.SetMask(aScanMask);
return tlv.AppendTo(aMessage);
}
otError Mle::AppendLinkMargin(Message &aMessage, uint8_t aLinkMargin)
{
LinkMarginTlv tlv;
tlv.Init();
tlv.SetLinkMargin(aLinkMargin);
return tlv.AppendTo(aMessage);
}
otError Mle::AppendVersion(Message &aMessage)
{
VersionTlv tlv;
tlv.Init();
tlv.SetVersion(kThreadVersion);
return tlv.AppendTo(aMessage);
}
bool Mle::HasUnregisteredAddress(void)
{
bool retval = false;
// Checks whether there are any addresses in addition to the mesh-local
// address that need to be registered.
for (const Ip6::NetifUnicastAddress *addr = Get<ThreadNetif>().GetUnicastAddresses(); addr; addr = addr->GetNext())
{
if (!addr->GetAddress().IsLinkLocal() && !IsRoutingLocator(addr->GetAddress()) &&
!IsAnycastLocator(addr->GetAddress()) && addr->GetAddress() != GetMeshLocal64())
{
ExitNow(retval = true);
}
}
if (!IsRxOnWhenIdle())
{
uint8_t iterator = 0;
Ip6::Address address;
// For sleepy end-device, we register any external multicast
// addresses.
retval = (Get<ThreadNetif>().GetNextExternalMulticast(iterator, address) == OT_ERROR_NONE);
}
exit:
return retval;
}
otError Mle::AppendAddressRegistration(Message &aMessage, AddressRegistrationMode aMode)
{
otError error = OT_ERROR_NONE;
Tlv tlv;
AddressRegistrationEntry entry;
Lowpan::Context context;
uint8_t length = 0;
uint8_t counter = 0;
uint16_t startOffset = aMessage.GetLength();
tlv.SetType(Tlv::kAddressRegistration);
SuccessOrExit(error = aMessage.Append(&tlv, sizeof(tlv)));
// Prioritize ML-EID
entry.SetContextId(kMeshLocalPrefixContextId);
entry.SetIid(GetMeshLocal64().GetIid());
SuccessOrExit(error = aMessage.Append(&entry, entry.GetLength()));
length += entry.GetLength();
// Continue to append the other addresses if not `kAppendMeshLocalOnly` mode
VerifyOrExit(aMode != kAppendMeshLocalOnly);
counter++;
for (const Ip6::NetifUnicastAddress *addr = Get<ThreadNetif>().GetUnicastAddresses(); addr; addr = addr->GetNext())
{
if (addr->GetAddress().IsLinkLocal() || IsRoutingLocator(addr->GetAddress()) ||
IsAnycastLocator(addr->GetAddress()) || addr->GetAddress() == GetMeshLocal64())
{
continue;
}
if (Get<NetworkData::Leader>().GetContext(addr->GetAddress(), context) == OT_ERROR_NONE)
{
// compressed entry
entry.SetContextId(context.mContextId);
entry.SetIid(addr->GetAddress().GetIid());
}
else
{
// uncompressed entry
entry.SetUncompressed();
entry.SetIp6Address(addr->GetAddress());
}
SuccessOrExit(error = aMessage.Append(&entry, entry.GetLength()));
length += entry.GetLength();
counter++;
// only continue to append if there is available entry.
VerifyOrExit(counter < OPENTHREAD_CONFIG_MLE_IP_ADDRS_TO_REGISTER);
}
// For sleepy end device, register external multicast addresses to the parent for indirect transmission
if (!IsRxOnWhenIdle())
{
uint8_t iterator = 0;
Ip6::Address address;
// append external multicast address
while (Get<ThreadNetif>().GetNextExternalMulticast(iterator, address) == OT_ERROR_NONE)
{
entry.SetUncompressed();
entry.SetIp6Address(address);
SuccessOrExit(error = aMessage.Append(&entry, entry.GetLength()));
length += entry.GetLength();
counter++;
// only continue to append if there is available entry.
VerifyOrExit(counter < OPENTHREAD_CONFIG_MLE_IP_ADDRS_TO_REGISTER);
}
}
exit:
if (error == OT_ERROR_NONE && length > 0)
{
tlv.SetLength(length);
aMessage.Write(startOffset, sizeof(tlv), &tlv);
}
return error;
}
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
otError Mle::AppendTimeRequest(Message &aMessage)
{
TimeRequestTlv tlv;
tlv.Init();
return tlv.AppendTo(aMessage);
}
otError Mle::AppendTimeParameter(Message &aMessage)
{
TimeParameterTlv tlv;
tlv.Init();
tlv.SetTimeSyncPeriod(Get<TimeSync>().GetTimeSyncPeriod());
tlv.SetXtalThreshold(Get<TimeSync>().GetXtalThreshold());
return tlv.AppendTo(aMessage);
}
otError Mle::AppendXtalAccuracy(Message &aMessage)
{
XtalAccuracyTlv tlv;
tlv.Init();
tlv.SetXtalAccuracy(otPlatTimeGetXtalAccuracy());
return tlv.AppendTo(aMessage);
}
#endif // OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
otError Mle::AppendActiveTimestamp(Message &aMessage)
{
otError error;
ActiveTimestampTlv timestampTlv;
const MeshCoP::Timestamp *timestamp;
timestamp = Get<MeshCoP::ActiveDataset>().GetTimestamp();
VerifyOrExit(timestamp, error = OT_ERROR_NONE);
timestampTlv.Init();
*static_cast<MeshCoP::Timestamp *>(&timestampTlv) = *timestamp;
error = timestampTlv.AppendTo(aMessage);
exit:
return error;
}
otError Mle::AppendPendingTimestamp(Message &aMessage)
{
otError error;
PendingTimestampTlv timestampTlv;
const MeshCoP::Timestamp *timestamp;
timestamp = Get<MeshCoP::PendingDataset>().GetTimestamp();
VerifyOrExit(timestamp && timestamp->GetSeconds() != 0, error = OT_ERROR_NONE);
timestampTlv.Init();
*static_cast<MeshCoP::Timestamp *>(&timestampTlv) = *timestamp;
error = timestampTlv.AppendTo(aMessage);
exit:
return error;
}
void Mle::HandleStateChanged(Notifier::Callback &aCallback, otChangedFlags aFlags)
{
aCallback.GetOwner<Mle>().HandleStateChanged(aFlags);
}
void Mle::HandleStateChanged(otChangedFlags aFlags)
{
VerifyOrExit(mRole != OT_DEVICE_ROLE_DISABLED);
if (aFlags & OT_CHANGED_THREAD_ROLE)
{
if (mRole == OT_DEVICE_ROLE_CHILD && !IsFullThreadDevice() && mAddressRegistrationMode == kAppendMeshLocalOnly)
{
// If only mesh-local address was registered in the "Child
// ID Request" message, after device is attached, trigger a
// "Child Update Request" to register the remaining
// addresses.
mAddressRegistrationMode = kAppendAllAddresses;
mChildUpdateRequestState = kChildUpdateRequestPending;
ScheduleMessageTransmissionTimer();
}
}
if ((aFlags & (OT_CHANGED_IP6_ADDRESS_ADDED | OT_CHANGED_IP6_ADDRESS_REMOVED)) != 0)
{
if (!Get<ThreadNetif>().IsUnicastAddress(mMeshLocal64.GetAddress()))
{
// Mesh Local EID was removed, choose a new one and add it back
Random::NonCrypto::FillBuffer(mMeshLocal64.GetAddress().mFields.m8 + OT_IP6_PREFIX_SIZE,
OT_IP6_ADDRESS_SIZE - OT_IP6_PREFIX_SIZE);
Get<ThreadNetif>().AddUnicastAddress(mMeshLocal64);
Get<Notifier>().Signal(OT_CHANGED_THREAD_ML_ADDR);
}
if (mRole == OT_DEVICE_ROLE_CHILD && !IsFullThreadDevice())
{
mChildUpdateRequestState = kChildUpdateRequestPending;
ScheduleMessageTransmissionTimer();
}
}
if ((aFlags & (OT_CHANGED_IP6_MULTICAST_SUBSCRIBED | OT_CHANGED_IP6_MULTICAST_UNSUBSCRIBED)) != 0)
{
if (mRole == OT_DEVICE_ROLE_CHILD && !IsFullThreadDevice() && !IsRxOnWhenIdle())
{
mChildUpdateRequestState = kChildUpdateRequestPending;
ScheduleMessageTransmissionTimer();
}
}
if ((aFlags & OT_CHANGED_THREAD_NETDATA) != 0)
{
if (IsFullThreadDevice())
{
Get<MleRouter>().HandleNetworkDataUpdateRouter();
}
else if ((aFlags & OT_CHANGED_THREAD_ROLE) == 0)
{
mChildUpdateRequestState = kChildUpdateRequestPending;
ScheduleMessageTransmissionTimer();
}
#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE || OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
Get<NetworkData::Local>().SendServerDataNotification();
#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
this->UpdateServiceAlocs();
#endif
#endif
#if OPENTHREAD_CONFIG_DHCP6_SERVER_ENABLE
Get<Dhcp6::Dhcp6Server>().UpdateService();
#endif // OPENTHREAD_CONFIG_DHCP6_SERVER_ENABLE
#if OPENTHREAD_CONFIG_DHCP6_CLIENT_ENABLE
Get<Dhcp6::Dhcp6Client>().UpdateAddresses();
#endif // OPENTHREAD_CONFIG_DHCP6_CLIENT_ENABLE
}
if (aFlags & (OT_CHANGED_THREAD_ROLE | OT_CHANGED_THREAD_KEY_SEQUENCE_COUNTER))
{
// Store the settings on a key seq change, or when role changes and device
// is attached (i.e., skip `Store()` on role change to detached).
if ((aFlags & OT_CHANGED_THREAD_KEY_SEQUENCE_COUNTER) || IsAttached())
{
Store();
}
}
if (aFlags & OT_CHANGED_SECURITY_POLICY)
{
Get<Ip6::Filter>().AllowNativeCommissioner(
(Get<KeyManager>().GetSecurityPolicyFlags() & OT_SECURITY_POLICY_NATIVE_COMMISSIONING) != 0);
}
exit:
return;
}
#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
void Mle::UpdateServiceAlocs(void)
{
uint16_t rloc = GetRloc16();
uint16_t serviceAloc = 0;
uint8_t serviceId = 0;
int i = 0;
NetworkData::Iterator serviceIterator = NetworkData::kIteratorInit;
int serviceAlocsLength = OT_ARRAY_LENGTH(mServiceAlocs);
VerifyOrExit(mRole != OT_DEVICE_ROLE_DISABLED);
// First remove all alocs which are no longer necessary, to free up space in mServiceAlocs
for (i = 0; i < serviceAlocsLength; i++)
{
serviceAloc = HostSwap16(mServiceAlocs[i].GetAddress().mFields.m16[7]);
if ((serviceAloc != Mac::kShortAddrInvalid) &&
(!Get<NetworkData::Leader>().ContainsService(Mle::ServiceIdFromAloc(serviceAloc), rloc)))
{
Get<ThreadNetif>().RemoveUnicastAddress(mServiceAlocs[i]);
mServiceAlocs[i].GetAddress().mFields.m16[7] = HostSwap16(Mac::kShortAddrInvalid);
}
}
// Now add any missing service alocs which should be there, if there is enough space in mServiceAlocs
while (Get<NetworkData::Leader>().GetNextServiceId(serviceIterator, rloc, serviceId) == OT_ERROR_NONE)
{
for (i = 0; i < serviceAlocsLength; i++)
{
serviceAloc = HostSwap16(mServiceAlocs[i].GetAddress().mFields.m16[7]);
if ((serviceAloc != Mac::kShortAddrInvalid) && (Mle::ServiceIdFromAloc(serviceAloc) == serviceId))
{
break;
}
}
if (i >= serviceAlocsLength)
{
// Service Aloc is not there, but it should be. Lets add it into first empty space
for (i = 0; i < serviceAlocsLength; i++)
{
serviceAloc = HostSwap16(mServiceAlocs[i].GetAddress().mFields.m16[7]);
if (serviceAloc == Mac::kShortAddrInvalid)
{
SuccessOrExit(GetServiceAloc(serviceId, mServiceAlocs[i].GetAddress()));
Get<ThreadNetif>().AddUnicastAddress(mServiceAlocs[i]);
break;
}
}
}
}
exit:
return;
}
#endif
void Mle::HandleAttachTimer(Timer &aTimer)
{
aTimer.GetOwner<Mle>().HandleAttachTimer();
}
void Mle::HandleAttachTimer(void)
{
uint32_t delay = 0;
bool shouldAnnounce = true;
if (mAttachState == kAttachStateParentRequestRouter || mAttachState == kAttachStateParentRequestReed ||
mAttachState == kAttachStateAnnounce)
{
uint8_t linkQuality;
linkQuality = mParentCandidate.GetLinkInfo().GetLinkQuality();
if (linkQuality > mParentCandidate.GetLinkQualityOut())
{
linkQuality = mParentCandidate.GetLinkQualityOut();
}
// If already attached, accept the parent candidate if
// we are trying to attach to a better partition or if a
// Parent Response was also received from the current parent
// to which the device is attached. This ensures that the
// new parent candidate is compared with the current parent
// and that it is indeed preferred over the current one.
// If we are in kAttachStateParentRequestRouter and cannot
// find a parent with best link quality(3), we will keep
// the candidate and forward to REED stage to find a better
// parent.
if ((linkQuality == 3 || mAttachState != kAttachStateParentRequestRouter) &&
mParentCandidate.IsStateParentResponse() &&
(mRole != OT_DEVICE_ROLE_CHILD || mReceivedResponseFromParent || mParentRequestMode == kAttachBetter) &&
SendChildIdRequest() == OT_ERROR_NONE)
{
SetAttachState(kAttachStateChildIdRequest);
delay = kParentRequestReedTimeout;
ExitNow();
}
}
switch (mAttachState)
{
case kAttachStateIdle:
assert(false);
break;
case kAttachStateProcessAnnounce:
ProcessAnnounce();
break;
case kAttachStateStart:
if (mAttachCounter > 0)
{
otLogNoteMle("Attempt to attach - attempt %d, %s %s", mAttachCounter,
AttachModeToString(mParentRequestMode), ReattachStateToString(mReattachState));
}
else
{
otLogNoteMle("Attempt to attach - %s %s", AttachModeToString(mParentRequestMode),
ReattachStateToString(mReattachState));
}
SetAttachState(kAttachStateParentRequestRouter);
mParentCandidate.SetState(Neighbor::kStateInvalid);
mReceivedResponseFromParent = false;
Get<MeshForwarder>().SetRxOnWhenIdle(true);
// initial MLE Parent Request has both E and R flags set in Scan Mask TLV
// during reattach when losing connectivity.
if (mParentRequestMode == kAttachSame1 || mParentRequestMode == kAttachSame2)
{
SendParentRequest(kParentRequestTypeRoutersAndReeds);
delay = kParentRequestReedTimeout;
}
// initial MLE Parent Request has only R flag set in Scan Mask TLV for
// during initial attach or downgrade process
else
{
SendParentRequest(kParentRequestTypeRouters);
delay = kParentRequestRouterTimeout;
}
break;
case kAttachStateParentRequestRouter:
SetAttachState(kAttachStateParentRequestReed);
SendParentRequest(kParentRequestTypeRoutersAndReeds);
delay = kParentRequestReedTimeout;
break;
case kAttachStateParentRequestReed:
shouldAnnounce = PrepareAnnounceState();
if (shouldAnnounce)
{
SetAttachState(kAttachStateAnnounce);
SendParentRequest(kParentRequestTypeRoutersAndReeds);
mAnnounceChannel = Mac::ChannelMask::kChannelIteratorFirst;
delay = mAnnounceDelay;
break;
}
// fall through
case kAttachStateAnnounce:
if (shouldAnnounce)
{
if (SendOrphanAnnounce() == OT_ERROR_NONE)
{
delay = mAnnounceDelay;
break;
}
}
// fall through
case kAttachStateChildIdRequest:
SetAttachState(kAttachStateIdle);
ResetParentCandidate();
delay = Reattach();
break;
}
exit:
if (delay != 0)
{
mAttachTimer.Start(delay);
}
}
bool Mle::PrepareAnnounceState(void)
{
bool shouldAnnounce = false;
Mac::ChannelMask channelMask;
VerifyOrExit((mRole != OT_DEVICE_ROLE_CHILD) && (mReattachState == kReattachStop) &&
(Get<MeshCoP::ActiveDataset>().IsPartiallyComplete() || !IsFullThreadDevice()));
if (Get<MeshCoP::ActiveDataset>().GetChannelMask(channelMask) != OT_ERROR_NONE)
{
channelMask = Get<Mac::Mac>().GetSupportedChannelMask();
}
mAnnounceDelay = kAnnounceTimeout / (channelMask.GetNumberOfChannels() + 1);
if (mAnnounceDelay < kMinAnnounceDelay)
{
mAnnounceDelay = kMinAnnounceDelay;
}
shouldAnnounce = true;
exit:
return shouldAnnounce;
}
uint32_t Mle::Reattach(void)
{
uint32_t delay = 0;
if (mReattachState == kReattachActive)
{
if (Get<MeshCoP::PendingDataset>().Restore() == OT_ERROR_NONE)
{
Get<MeshCoP::PendingDataset>().ApplyConfiguration();
mReattachState = kReattachPending;
SetAttachState(kAttachStateStart);
delay = 1 + Random::NonCrypto::GetUint32InRange(0, kAttachStartJitter);
}
else
{
mReattachState = kReattachStop;
}
}
else if (mReattachState == kReattachPending)
{
mReattachState = kReattachStop;
Get<MeshCoP::ActiveDataset>().Restore();
}
VerifyOrExit(mReattachState == kReattachStop);
switch (mParentRequestMode)
{
case kAttachAny:
if (mRole != OT_DEVICE_ROLE_CHILD)
{
if (mAlternatePanId != Mac::kPanIdBroadcast)
{
Get<Mac::Mac>().SetPanChannel(mAlternateChannel);
Get<Mac::Mac>().SetPanId(mAlternatePanId);
mAlternatePanId = Mac::kPanIdBroadcast;
BecomeDetached();
}
else if (!IsFullThreadDevice())
{
BecomeDetached();
}
else if (Get<MleRouter>().BecomeLeader() != OT_ERROR_NONE)
{
BecomeDetached();
}
}
else if (!IsRxOnWhenIdle())
{
// return to sleepy operation
Get<DataPollSender>().SetAttachMode(false);
Get<MeshForwarder>().SetRxOnWhenIdle(false);
}
break;
case kAttachSame1:
BecomeChild(kAttachSame2);
break;
case kAttachSame2:
case kAttachSameDowngrade:
BecomeChild(kAttachAny);
break;
case kAttachBetter:
break;
}
exit:
return delay;
}
void Mle::HandleDelayedResponseTimer(Timer &aTimer)
{
aTimer.GetOwner<Mle>().HandleDelayedResponseTimer();
}
void Mle::HandleDelayedResponseTimer(void)
{
DelayedResponseHeader delayedResponse;
TimeMilli now = TimerMilli::GetNow();
TimeMilli nextSendTime = now.GetDistantFuture();
Message * message;
Message * nextMessage;
for (message = mDelayedResponses.GetHead(); message != NULL; message = nextMessage)
{
nextMessage = message->GetNext();
delayedResponse.ReadFrom(*message);
if (now < delayedResponse.GetSendTime())
{
if (nextSendTime > delayedResponse.GetSendTime())
{
nextSendTime = delayedResponse.GetSendTime();
}
}
else
{
mDelayedResponses.Dequeue(*message);
// Remove the DelayedResponseHeader from the message.
DelayedResponseHeader::RemoveFrom(*message);
// Send the message.
if (SendMessage(*message, delayedResponse.GetDestination()) == OT_ERROR_NONE)
{
LogMleMessage("Send delayed message", delayedResponse.GetDestination());
// Here enters fast poll mode, as for Rx-Off-when-idle device, the enqueued msg should
// be Mle Data Request.
// Note: Finer-grade check (e.g. message subtype) might be required when deciding whether
// or not enters fast poll mode fast poll mode if there are other type of delayed message
// for Rx-Off-when-idle device.
if (!IsRxOnWhenIdle())
{
Get<DataPollSender>().SendFastPolls(DataPollSender::kDefaultFastPolls);
}
}
else
{
message->Free();
}
}
}
if (nextSendTime < now.GetDistantFuture())
{
mDelayedResponseTimer.FireAt(nextSendTime);
}
}
void Mle::RemoveDelayedDataResponseMessage(void)
{
Message * message = mDelayedResponses.GetHead();
DelayedResponseHeader delayedResponse;
while (message != NULL)
{
delayedResponse.ReadFrom(*message);
if (message->GetSubType() == Message::kSubTypeMleDataResponse)
{
mDelayedResponses.Dequeue(*message);
message->Free();
LogMleMessage("Remove Delayed Data Response", delayedResponse.GetDestination());
// no more than one multicast MLE Data Response in Delayed Message Queue.
break;
}
message = message->GetNext();
}
}
otError Mle::SendParentRequest(ParentRequestType aType)
{
otError error = OT_ERROR_NONE;
Message * message;
uint8_t scanMask = 0;
Ip6::Address destination;
Random::Crypto::FillBuffer(mParentRequest.mChallenge, sizeof(mParentRequest.mChallenge));
switch (aType)
{
case kParentRequestTypeRouters:
scanMask = ScanMaskTlv::kRouterFlag;
break;
case kParentRequestTypeRoutersAndReeds:
scanMask = ScanMaskTlv::kRouterFlag | ScanMaskTlv::kEndDeviceFlag;
break;
}
VerifyOrExit((message = NewMleMessage()) != NULL, error = OT_ERROR_NO_BUFS);
SuccessOrExit(error = AppendHeader(*message, Header::kCommandParentRequest));
SuccessOrExit(error = AppendMode(*message, mDeviceMode));
SuccessOrExit(error = AppendChallenge(*message, mParentRequest.mChallenge, sizeof(mParentRequest.mChallenge)));
SuccessOrExit(error = AppendScanMask(*message, scanMask));
SuccessOrExit(error = AppendVersion(*message));
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
SuccessOrExit(error = AppendTimeRequest(*message));
#endif
destination.Clear();
destination.mFields.m16[0] = HostSwap16(0xff02);
destination.mFields.m16[7] = HostSwap16(0x0002);
SuccessOrExit(error = SendMessage(*message, destination));
switch (aType)
{
case kParentRequestTypeRouters:
LogMleMessage("Send Parent Request to routers", destination);
break;
case kParentRequestTypeRoutersAndReeds:
LogMleMessage("Send Parent Request to routers and REEDs", destination);
break;
}
exit:
if (error != OT_ERROR_NONE && message != NULL)
{
message->Free();
}
return error;
}
void Mle::RequestShorterChildIdRequest(void)
{
if (mAttachState == kAttachStateChildIdRequest)
{
mAddressRegistrationMode = kAppendMeshLocalOnly;
SendChildIdRequest();
}
}
otError Mle::SendChildIdRequest(void)
{
otError error = OT_ERROR_NONE;
uint8_t tlvs[] = {Tlv::kAddress16, Tlv::kNetworkData, Tlv::kRoute};
uint8_t tlvsLen = sizeof(tlvs);
Message * message = NULL;
Ip6::Address destination;
if (mParent.GetExtAddress() == mParentCandidate.GetExtAddress())
{
if (mRole == OT_DEVICE_ROLE_CHILD)
{
otLogInfoMle("Already attached to candidate parent");
ExitNow(error = OT_ERROR_ALREADY);
}
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 GetNeighbor() returns mParentCandidate when processing the Child ID Response.
mParent.SetState(Neighbor::kStateInvalid);
}
}
VerifyOrExit((message = NewMleMessage()) != NULL, error = OT_ERROR_NO_BUFS);
message->SetSubType(Message::kSubTypeMleChildIdRequest);
SuccessOrExit(error = AppendHeader(*message, Header::kCommandChildIdRequest));
SuccessOrExit(error = AppendResponse(*message, mChildIdRequest.mChallenge, mChildIdRequest.mChallengeLength));
SuccessOrExit(error = AppendLinkFrameCounter(*message));
SuccessOrExit(error = AppendMleFrameCounter(*message));
SuccessOrExit(error = AppendMode(*message, mDeviceMode));
SuccessOrExit(error = AppendTimeout(*message, mTimeout));
SuccessOrExit(error = AppendVersion(*message));
if (!IsFullThreadDevice())
{
SuccessOrExit(error = AppendAddressRegistration(*message, mAddressRegistrationMode));
// no need to request the last Route64 TLV for MTD
tlvsLen -= 1;
}
SuccessOrExit(error = AppendTlvRequest(*message, tlvs, tlvsLen));
SuccessOrExit(error = AppendActiveTimestamp(*message));
SuccessOrExit(error = AppendPendingTimestamp(*message));
mParentCandidate.SetState(Neighbor::kStateValid);
destination.Clear();
destination.mFields.m16[0] = HostSwap16(0xfe80);
destination.SetIid(mParentCandidate.GetExtAddress());
SuccessOrExit(error = SendMessage(*message, destination));
if (mAddressRegistrationMode == kAppendMeshLocalOnly)
{
LogMleMessage("Send Child ID Request - short", destination);
}
else
{
LogMleMessage("Send Child ID Request", destination);
}
if (!IsRxOnWhenIdle())
{
Get<DataPollSender>().SetAttachMode(true);
Get<MeshForwarder>().SetRxOnWhenIdle(false);
}
exit:
if (error != OT_ERROR_NONE && message != NULL)
{
message->Free();
}
return error;
}
otError Mle::SendDataRequest(const Ip6::Address &aDestination,
const uint8_t * aTlvs,
uint8_t aTlvsLength,
uint16_t aDelay)
{
otError error = OT_ERROR_NONE;
Message *message;
VerifyOrExit((message = NewMleMessage()) != NULL, error = OT_ERROR_NO_BUFS);
SuccessOrExit(error = AppendHeader(*message, Header::kCommandDataRequest));
SuccessOrExit(error = AppendTlvRequest(*message, aTlvs, aTlvsLength));
SuccessOrExit(error = AppendActiveTimestamp(*message));
SuccessOrExit(error = AppendPendingTimestamp(*message));
if (aDelay)
{
SuccessOrExit(error = AddDelayedResponse(*message, aDestination, aDelay));
LogMleMessage("Delay Data Request", aDestination);
}
else
{
SuccessOrExit(error = SendMessage(*message, aDestination));
LogMleMessage("Send Data Request", aDestination);
if (!IsRxOnWhenIdle())
{
Get<DataPollSender>().SendFastPolls(DataPollSender::kDefaultFastPolls);
}
}
exit:
if (error != OT_ERROR_NONE && message != NULL)
{
message->Free();
}
if ((mRole == OT_DEVICE_ROLE_CHILD) && !IsRxOnWhenIdle())
{
mDataRequestState = kDataRequestActive;
if (mChildUpdateRequestState == kChildUpdateRequestNone)
{
ScheduleMessageTransmissionTimer();
}
}
return error;
}
void Mle::ScheduleMessageTransmissionTimer(void)
{
uint32_t interval = 0;
switch (mChildUpdateRequestState)
{
case kChildUpdateRequestNone:
break;
case kChildUpdateRequestPending:
ExitNow(interval = kChildUpdateRequestPendingDelay);
case kChildUpdateRequestActive:
ExitNow(interval = kUnicastRetransmissionDelay);
}
switch (mDataRequestState)
{
case kDataRequestNone:
break;
case kDataRequestActive:
ExitNow(interval = kUnicastRetransmissionDelay);
}
if ((mRole == OT_DEVICE_ROLE_CHILD) && IsRxOnWhenIdle())
{
interval =
Time::SecToMsec(mTimeout) - static_cast<uint32_t>(kUnicastRetransmissionDelay) * kMaxChildKeepAliveAttempts;
}
exit:
if (interval != 0)
{
mMessageTransmissionTimer.Start(interval);
}
else
{
mMessageTransmissionTimer.Stop();
}
}
void Mle::HandleMessageTransmissionTimer(Timer &aTimer)
{
aTimer.GetOwner<Mle>().HandleMessageTransmissionTimer();
}
void Mle::HandleMessageTransmissionTimer(void)
{
// The `mMessageTransmissionTimer` is used for:
//
// - Delaying OT_CHANGED notification triggered "Child Update Request" transmission (to allow aggregation),
// - Retransmission of "Child Update Request",
// - Retransmission of "Data Request" on a child,
// - Sending periodic keep-alive "Child Update Request" messages on a non-sleepy (rx-on) child.
switch (mChildUpdateRequestState)
{
case kChildUpdateRequestNone:
if (mDataRequestState == kDataRequestActive)
{
static const uint8_t tlvs[] = {Tlv::kNetworkData};
Ip6::Address destination;
VerifyOrExit(mDataRequestAttempts < kMaxChildKeepAliveAttempts, BecomeDetached());
destination.Clear();
destination.mFields.m16[0] = HostSwap16(0xfe80);
destination.SetIid(mParent.GetExtAddress());
if (SendDataRequest(destination, tlvs, sizeof(tlvs), 0) == OT_ERROR_NONE)
{
mDataRequestAttempts++;
}
ExitNow();
}
// Keep-alive "Child Update Request" only on a non-sleepy child
VerifyOrExit((mRole == OT_DEVICE_ROLE_CHILD) && IsRxOnWhenIdle());
break;
case kChildUpdateRequestPending:
if (Get<Notifier>().IsPending())
{
// Here intentionally delay another kChildUpdateRequestPendingDelay
// cycle to ensure we only send a Child Update Request after we
// know there are no more pending changes.
ScheduleMessageTransmissionTimer();
ExitNow();
}
mChildUpdateAttempts = 0;
break;
case kChildUpdateRequestActive:
break;
}
VerifyOrExit(mChildUpdateAttempts < kMaxChildKeepAliveAttempts, BecomeDetached());
if (SendChildUpdateRequest() == OT_ERROR_NONE)
{
mChildUpdateAttempts++;
}
exit:
return;
}
otError Mle::SendChildUpdateRequest(void)
{
otError error = OT_ERROR_NONE;
Ip6::Address destination;
Message * message = NULL;
if (!mParent.IsStateValidOrRestoring())
{
otLogWarnMle("No valid parent when sending Child Update Request");
BecomeDetached();
ExitNow();
}
mChildUpdateRequestState = kChildUpdateRequestActive;
ScheduleMessageTransmissionTimer();
VerifyOrExit((message = NewMleMessage()) != NULL, error = OT_ERROR_NO_BUFS);
message->SetSubType(Message::kSubTypeMleChildUpdateRequest);
SuccessOrExit(error = AppendHeader(*message, Header::kCommandChildUpdateRequest));
SuccessOrExit(error = AppendMode(*message, mDeviceMode));
if (!IsFullThreadDevice())
{
SuccessOrExit(error = AppendAddressRegistration(*message));
}
switch (mRole)
{
case OT_DEVICE_ROLE_DETACHED:
Random::Crypto::FillBuffer(mParentRequest.mChallenge, sizeof(mParentRequest.mChallenge));
SuccessOrExit(error = AppendChallenge(*message, mParentRequest.mChallenge, sizeof(mParentRequest.mChallenge)));
break;
case OT_DEVICE_ROLE_CHILD:
SuccessOrExit(error = AppendSourceAddress(*message));
SuccessOrExit(error = AppendLeaderData(*message));
SuccessOrExit(error = AppendTimeout(*message, mTimeout));
break;
case OT_DEVICE_ROLE_DISABLED:
case OT_DEVICE_ROLE_ROUTER:
case OT_DEVICE_ROLE_LEADER:
assert(false);
break;
}
destination.Clear();
destination.mFields.m16[0] = HostSwap16(0xfe80);
destination.SetIid(mParent.GetExtAddress());
SuccessOrExit(error = SendMessage(*message, destination));
LogMleMessage("Send Child Update Request to parent", destination);
if (!IsRxOnWhenIdle())
{
Get<DataPollSender>().SetAttachMode(true);
Get<MeshForwarder>().SetRxOnWhenIdle(false);
}
else
{
Get<MeshForwarder>().SetRxOnWhenIdle(true);
}
exit:
if (error != OT_ERROR_NONE && message != NULL)
{
message->Free();
}
return error;
}
otError Mle::SendChildUpdateResponse(const uint8_t *aTlvs, uint8_t aNumTlvs, const ChallengeTlv &aChallenge)
{
otError error = OT_ERROR_NONE;
Ip6::Address destination;
Message * message;
bool checkAddress = false;
VerifyOrExit((message = NewMleMessage()) != NULL, error = OT_ERROR_NO_BUFS);
SuccessOrExit(error = AppendHeader(*message, Header::kCommandChildUpdateResponse));
SuccessOrExit(error = AppendSourceAddress(*message));
SuccessOrExit(error = AppendLeaderData(*message));
for (int i = 0; i < aNumTlvs; i++)
{
switch (aTlvs[i])
{
case Tlv::kTimeout:
SuccessOrExit(error = AppendTimeout(*message, mTimeout));
break;
case Tlv::kStatus:
SuccessOrExit(error = AppendStatus(*message, StatusTlv::kError));
break;
case Tlv::kAddressRegistration:
if (!IsFullThreadDevice())
{
// We only register the mesh-local address in the "Child
// Update Response" message and if there are additional
// addresses to register we follow up with a "Child Update
// Request".
SuccessOrExit(error = AppendAddressRegistration(*message, kAppendMeshLocalOnly));
checkAddress = true;
}
break;
case Tlv::kResponse:
SuccessOrExit(error = AppendResponse(*message, aChallenge.GetChallenge(), aChallenge.GetChallengeLength()));
break;
case Tlv::kLinkFrameCounter:
SuccessOrExit(error = AppendLinkFrameCounter(*message));
break;
case Tlv::kMleFrameCounter:
SuccessOrExit(error = AppendMleFrameCounter(*message));
break;
}
}
destination.Clear();
destination.mFields.m16[0] = HostSwap16(0xfe80);
destination.SetIid(mParent.GetExtAddress());
SuccessOrExit(error = SendMessage(*message, destination));
LogMleMessage("Send Child Update Response to parent", destination);
if (checkAddress && HasUnregisteredAddress())
{
SendChildUpdateRequest();
}
exit:
if (error != OT_ERROR_NONE && message != NULL)
{
message->Free();
}
return error;
}
otError Mle::SendAnnounce(uint8_t aChannel, bool aOrphanAnnounce)
{
Ip6::Address destination;
destination.Clear();
destination.mFields.m16[0] = HostSwap16(0xff02);
destination.mFields.m16[7] = HostSwap16(0x0001);
return SendAnnounce(aChannel, aOrphanAnnounce, destination);
}
otError Mle::SendAnnounce(uint8_t aChannel, bool aOrphanAnnounce, const Ip6::Address &aDestination)
{
otError error = OT_ERROR_NONE;
ChannelTlv channel;
PanIdTlv panid;
ActiveTimestampTlv activeTimestamp;
Message * message = NULL;
VerifyOrExit(Get<Mac::Mac>().GetSupportedChannelMask().ContainsChannel(aChannel), error = OT_ERROR_INVALID_ARGS);
VerifyOrExit((message = NewMleMessage()) != NULL, error = OT_ERROR_NO_BUFS);
message->SetLinkSecurityEnabled(true);
message->SetSubType(Message::kSubTypeMleAnnounce);
message->SetChannel(aChannel);
SuccessOrExit(error = AppendHeader(*message, Header::kCommandAnnounce));
channel.Init();
channel.SetChannel(Get<Mac::Mac>().GetPanChannel());
SuccessOrExit(error = channel.AppendTo(*message));
if (aOrphanAnnounce)
{
activeTimestamp.Init();
activeTimestamp.SetSeconds(0);
activeTimestamp.SetTicks(0);
activeTimestamp.SetAuthoritative(true);
SuccessOrExit(error = activeTimestamp.AppendTo(*message));
}
else
{
SuccessOrExit(error = AppendActiveTimestamp(*message));
}
panid.Init();
panid.SetPanId(Get<Mac::Mac>().GetPanId());
SuccessOrExit(error = panid.AppendTo(*message));
SuccessOrExit(error = SendMessage(*message, aDestination));
otLogInfoMle("Send Announce on channel %d", aChannel);
exit:
if (error != OT_ERROR_NONE && message != NULL)
{
message->Free();
}
return error;
}
otError Mle::SendOrphanAnnounce(void)
{
otError error;
Mac::ChannelMask channelMask;
if (Get<MeshCoP::ActiveDataset>().GetChannelMask(channelMask) != OT_ERROR_NONE)
{
channelMask = Get<Mac::Mac>().GetSupportedChannelMask();
}
SuccessOrExit(error = channelMask.GetNextChannel(mAnnounceChannel));
SendAnnounce(mAnnounceChannel, true);
exit:
return error;
}
otError Mle::SendMessage(Message &aMessage, const Ip6::Address &aDestination)
{
otError error = OT_ERROR_NONE;
Header header;
uint32_t keySequence;
uint8_t nonce[KeyManager::kNonceSize];
uint8_t tag[4];
uint8_t tagLength;
Crypto::AesCcm aesCcm;
uint8_t buf[64];
uint16_t length;
Ip6::MessageInfo messageInfo;
aMessage.Read(0, sizeof(header), &header);
if (header.GetSecuritySuite() == Header::k154Security)
{
header.SetFrameCounter(Get<KeyManager>().GetMleFrameCounter());
keySequence = Get<KeyManager>().GetCurrentKeySequence();
header.SetKeyId(keySequence);
aMessage.Write(0, header.GetLength(), &header);
KeyManager::GenerateNonce(Get<Mac::Mac>().GetExtAddress(), Get<KeyManager>().GetMleFrameCounter(),
Mac::Frame::kSecEncMic32, nonce);
aesCcm.SetKey(Get<KeyManager>().GetCurrentMleKey(), 16);
error = aesCcm.Init(16 + 16 + header.GetHeaderLength(), aMessage.GetLength() - (header.GetLength() - 1),
sizeof(tag), nonce, sizeof(nonce));
assert(error == OT_ERROR_NONE);
aesCcm.Header(&mLinkLocal64.GetAddress(), sizeof(mLinkLocal64.GetAddress()));
aesCcm.Header(&aDestination, sizeof(aDestination));
aesCcm.Header(header.GetBytes() + 1, header.GetHeaderLength());
aMessage.SetOffset(header.GetLength() - 1);
while (aMessage.GetOffset() < aMessage.GetLength())
{
length = aMessage.Read(aMessage.GetOffset(), sizeof(buf), buf);
aesCcm.Payload(buf, buf, length, true);
aMessage.Write(aMessage.GetOffset(), length, buf);
aMessage.MoveOffset(length);
}
tagLength = sizeof(tag);
aesCcm.Finalize(tag, &tagLength);
SuccessOrExit(error = aMessage.Append(tag, tagLength));
Get<KeyManager>().IncrementMleFrameCounter();
}
messageInfo.SetPeerAddr(aDestination);
messageInfo.SetSockAddr(mLinkLocal64.GetAddress());
messageInfo.SetPeerPort(kUdpPort);
messageInfo.SetHopLimit(kMleHopLimit);
SuccessOrExit(error = mSocket.SendTo(aMessage, messageInfo));
exit:
return error;
}
otError Mle::AddDelayedResponse(Message &aMessage, const Ip6::Address &aDestination, uint16_t aDelay)
{
otError error = OT_ERROR_NONE;
DelayedResponseHeader delayedResponse(TimerMilli::GetNow() + aDelay, aDestination);
SuccessOrExit(error = delayedResponse.AppendTo(aMessage));
mDelayedResponses.Enqueue(aMessage);
mDelayedResponseTimer.FireAtIfEarlier(delayedResponse.GetSendTime());
exit:
return error;
}
void Mle::HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo)
{
static_cast<Mle *>(aContext)->HandleUdpReceive(*static_cast<Message *>(aMessage),
*static_cast<const Ip6::MessageInfo *>(aMessageInfo));
}
void Mle::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
otError error = OT_ERROR_NONE;
Header header;
uint32_t keySequence;
const uint8_t * mleKey;
uint32_t frameCounter;
uint8_t messageTag[4];
uint8_t nonce[KeyManager::kNonceSize];
Mac::ExtAddress macAddr;
Crypto::AesCcm aesCcm;
uint16_t mleOffset;
uint8_t buf[64];
uint16_t length;
uint8_t tag[4];
uint8_t tagLength;
uint8_t command;
Neighbor * neighbor;
otLogDebgMle("Receive UDP message");
VerifyOrExit(aMessageInfo.GetLinkInfo() != NULL);
VerifyOrExit(aMessageInfo.GetHopLimit() == kMleHopLimit, error = OT_ERROR_PARSE);
length = aMessage.Read(aMessage.GetOffset(), sizeof(header), &header);
VerifyOrExit(header.IsValid() && header.GetLength() <= length, error = OT_ERROR_PARSE);
if (header.GetSecuritySuite() == Header::kNoSecurity)
{
aMessage.MoveOffset(header.GetLength());
switch (header.GetCommand())
{
case Header::kCommandDiscoveryRequest:
Get<MleRouter>().HandleDiscoveryRequest(aMessage, aMessageInfo);
break;
case Header::kCommandDiscoveryResponse:
HandleDiscoveryResponse(aMessage, aMessageInfo);
break;
default:
break;
}
ExitNow();
}
VerifyOrExit(mRole != OT_DEVICE_ROLE_DISABLED, error = OT_ERROR_INVALID_STATE);
VerifyOrExit(header.GetSecuritySuite() == Header::k154Security, error = OT_ERROR_PARSE);
keySequence = header.GetKeyId();
if (keySequence == Get<KeyManager>().GetCurrentKeySequence())
{
mleKey = Get<KeyManager>().GetCurrentMleKey();
}
else
{
mleKey = Get<KeyManager>().GetTemporaryMleKey(keySequence);
}
VerifyOrExit(aMessage.GetOffset() + header.GetLength() + sizeof(messageTag) <= aMessage.GetLength(),
error = OT_ERROR_PARSE);
aMessage.MoveOffset(header.GetLength() - 1);
aMessage.Read(aMessage.GetLength() - sizeof(messageTag), sizeof(messageTag), messageTag);
SuccessOrExit(error = aMessage.SetLength(aMessage.GetLength() - sizeof(messageTag)));
aMessageInfo.GetPeerAddr().ToExtAddress(macAddr);
frameCounter = header.GetFrameCounter();
KeyManager::GenerateNonce(macAddr, frameCounter, Mac::Frame::kSecEncMic32, nonce);
aesCcm.SetKey(mleKey, 16);
SuccessOrExit(error = aesCcm.Init(sizeof(aMessageInfo.GetPeerAddr()) + sizeof(aMessageInfo.GetSockAddr()) +
header.GetHeaderLength(),
aMessage.GetLength() - aMessage.GetOffset(), sizeof(messageTag), nonce,
sizeof(nonce)));
aesCcm.Header(&aMessageInfo.GetPeerAddr(), sizeof(aMessageInfo.GetPeerAddr()));
aesCcm.Header(&aMessageInfo.GetSockAddr(), sizeof(aMessageInfo.GetSockAddr()));
aesCcm.Header(header.GetBytes() + 1, header.GetHeaderLength());
mleOffset = aMessage.GetOffset();
while (aMessage.GetOffset() < aMessage.GetLength())
{
length = aMessage.Read(aMessage.GetOffset(), sizeof(buf), buf);
aesCcm.Payload(buf, buf, length, false);
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
aMessage.Write(aMessage.GetOffset(), length, buf);
#endif
aMessage.MoveOffset(length);
}
tagLength = sizeof(tag);
aesCcm.Finalize(tag, &tagLength);
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
VerifyOrExit(memcmp(messageTag, tag, sizeof(tag)) == 0, error = OT_ERROR_SECURITY);
#endif
if (keySequence > Get<KeyManager>().GetCurrentKeySequence())
{
Get<KeyManager>().SetCurrentKeySequence(keySequence);
}
aMessage.SetOffset(mleOffset);
aMessage.Read(aMessage.GetOffset(), sizeof(command), &command);
aMessage.MoveOffset(sizeof(command));
switch (mRole)
{
case OT_DEVICE_ROLE_DETACHED:
case OT_DEVICE_ROLE_CHILD:
neighbor = GetNeighbor(macAddr);
break;
case OT_DEVICE_ROLE_ROUTER:
case OT_DEVICE_ROLE_LEADER:
if (command == Header::kCommandChildIdResponse)
{
neighbor = GetNeighbor(macAddr);
}
else
{
neighbor = Get<MleRouter>().GetNeighbor(macAddr);
}
break;
default:
neighbor = NULL;
break;
}
if (neighbor != NULL && neighbor->IsStateValid())
{
if (keySequence == neighbor->GetKeySequence())
{
VerifyOrExit(frameCounter >= neighbor->GetMleFrameCounter(), error = OT_ERROR_DUPLICATED);
}
else
{
VerifyOrExit(keySequence > neighbor->GetKeySequence(), error = OT_ERROR_DUPLICATED);
neighbor->SetKeySequence(keySequence);
neighbor->SetLinkFrameCounter(0);
}
neighbor->SetMleFrameCounter(frameCounter + 1);
}
switch (command)
{
case Header::kCommandLinkRequest:
Get<MleRouter>().HandleLinkRequest(aMessage, aMessageInfo, neighbor);
break;
case Header::kCommandLinkAccept:
Get<MleRouter>().HandleLinkAccept(aMessage, aMessageInfo, keySequence, neighbor);
break;
case Header::kCommandLinkAcceptAndRequest:
Get<MleRouter>().HandleLinkAcceptAndRequest(aMessage, aMessageInfo, keySequence, neighbor);
break;
case Header::kCommandAdvertisement:
HandleAdvertisement(aMessage, aMessageInfo, neighbor);
break;
case Header::kCommandDataRequest:
Get<MleRouter>().HandleDataRequest(aMessage, aMessageInfo, neighbor);
break;
case Header::kCommandDataResponse:
HandleDataResponse(aMessage, aMessageInfo, neighbor);
break;
case Header::kCommandParentRequest:
Get<MleRouter>().HandleParentRequest(aMessage, aMessageInfo);
break;
case Header::kCommandParentResponse:
HandleParentResponse(aMessage, aMessageInfo, keySequence);
break;
case Header::kCommandChildIdRequest:
Get<MleRouter>().HandleChildIdRequest(aMessage, aMessageInfo, keySequence);
break;
case Header::kCommandChildIdResponse:
HandleChildIdResponse(aMessage, aMessageInfo, neighbor);
break;
case Header::kCommandChildUpdateRequest:
if (mRole == OT_DEVICE_ROLE_LEADER || mRole == OT_DEVICE_ROLE_ROUTER)
{
Get<MleRouter>().HandleChildUpdateRequest(aMessage, aMessageInfo, keySequence);
}
else
{
HandleChildUpdateRequest(aMessage, aMessageInfo, neighbor);
}
break;
case Header::kCommandChildUpdateResponse:
if (mRole == OT_DEVICE_ROLE_LEADER || mRole == OT_DEVICE_ROLE_ROUTER)
{
Get<MleRouter>().HandleChildUpdateResponse(aMessage, aMessageInfo, keySequence, neighbor);
}
else
{
HandleChildUpdateResponse(aMessage, aMessageInfo, neighbor);
}
break;
case Header::kCommandAnnounce:
HandleAnnounce(aMessage, aMessageInfo);
break;
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
case Header::kCommandTimeSync:
Get<MleRouter>().HandleTimeSync(aMessage, aMessageInfo, neighbor);
break;
#endif
}
exit:
if (error != OT_ERROR_NONE)
{
otLogNoteMle("Failed to process UDP: %s", otThreadErrorToString(error));
}
return;
}
otError Mle::HandleAdvertisement(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo, Neighbor *aNeighbor)
{
otError error = OT_ERROR_NONE;
SourceAddressTlv sourceAddress;
LeaderDataTlv leaderData;
RouteTlv route;
uint8_t tlvs[] = {Tlv::kNetworkData};
uint16_t delay;
// Source Address
SuccessOrExit(error = Tlv::GetTlv(aMessage, Tlv::kSourceAddress, sizeof(sourceAddress), sourceAddress));
VerifyOrExit(sourceAddress.IsValid(), error = OT_ERROR_PARSE);
LogMleMessage("Receive Advertisement", aMessageInfo.GetPeerAddr(), sourceAddress.GetRloc16());
// Leader Data
SuccessOrExit(error = Tlv::GetTlv(aMessage, Tlv::kLeaderData, sizeof(leaderData), leaderData));
VerifyOrExit(leaderData.IsValid(), error = OT_ERROR_PARSE);
if (mRole != OT_DEVICE_ROLE_DETACHED)
{
if (IsFullThreadDevice())
{
SuccessOrExit(error = Get<MleRouter>().HandleAdvertisement(aMessage, aMessageInfo, aNeighbor));
}
else if ((aNeighbor == &mParent) && (mParent.GetRloc16() != sourceAddress.GetRloc16()))
{
// Remove stale parent.
BecomeDetached();
}
}
switch (mRole)
{
case OT_DEVICE_ROLE_DISABLED:
case OT_DEVICE_ROLE_DETACHED:
ExitNow();
case OT_DEVICE_ROLE_CHILD:
VerifyOrExit(aNeighbor == &mParent);
if ((mParent.GetRloc16() == sourceAddress.GetRloc16()) &&
(leaderData.GetPartitionId() != mLeaderData.GetPartitionId() ||
leaderData.GetLeaderRouterId() != GetLeaderId()))
{
SetLeaderData(leaderData.GetPartitionId(), leaderData.GetWeighting(), leaderData.GetLeaderRouterId());
if (IsFullThreadDevice() && (Tlv::GetTlv(aMessage, Tlv::kRoute, sizeof(route), route) == OT_ERROR_NONE) &&
route.IsValid())
{
// Overwrite Route Data
Get<MleRouter>().ProcessRouteTlv(route);
}
mRetrieveNewNetworkData = true;
}
mParent.SetLastHeard(TimerMilli::GetNow());
break;
case OT_DEVICE_ROLE_ROUTER:
case OT_DEVICE_ROLE_LEADER:
VerifyOrExit(aNeighbor && aNeighbor->IsStateValid());
break;
}
if (mRetrieveNewNetworkData || IsNetworkDataNewer(leaderData))
{
delay = Random::NonCrypto::GetUint16InRange(0, kMleMaxResponseDelay);
SendDataRequest(aMessageInfo.GetPeerAddr(), tlvs, sizeof(tlvs), delay);
}
#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE || OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
else
{
Get<NetworkData::Local>().SendServerDataNotification();
}
#endif
exit:
if (error != OT_ERROR_NONE)
{
otLogWarnMle("Failed to process Advertisement: %s", otThreadErrorToString(error));
}
return error;
}
otError Mle::HandleDataResponse(const Message & aMessage,
const Ip6::MessageInfo &aMessageInfo,
const Neighbor * aNeighbor)
{
otError error;
LogMleMessage("Receive Data Response", aMessageInfo.GetPeerAddr());
VerifyOrExit(aNeighbor && aNeighbor->IsStateValid(), error = OT_ERROR_SECURITY);
error = HandleLeaderData(aMessage, aMessageInfo);
if (mDataRequestState == kDataRequestNone && !IsRxOnWhenIdle())
{
// Here simply stops fast data poll request by Mle Data Request.
// Note that in some cases fast data poll may continue after below stop operation until
// running out the specified number. E.g. other component also trigger fast poll, and
// is waiting for response; or the corner case where multiple Mle Data Request attempts
// happened due to the retransmission mechanism.
IgnoreReturnValue(Get<DataPollSender>().StopFastPolls());
}
exit:
if (error != OT_ERROR_NONE)
{
otLogWarnMle("Failed to process Data Response: %s", otThreadErrorToString(error));
}
return error;
}
bool Mle::IsNetworkDataNewer(const LeaderDataTlv &aLeaderData)
{
int8_t diff;
if (IsFullNetworkData())
{
diff = static_cast<int8_t>(aLeaderData.GetDataVersion() - Get<NetworkData::Leader>().GetVersion());
}
else
{
diff = static_cast<int8_t>(aLeaderData.GetStableDataVersion() - Get<NetworkData::Leader>().GetStableVersion());
}
return (diff > 0);
}
otError Mle::HandleLeaderData(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
otError error = OT_ERROR_NONE;
LeaderDataTlv leaderData;
ActiveTimestampTlv activeTimestamp;
PendingTimestampTlv pendingTimestamp;
uint16_t networkDataOffset = 0;
uint16_t activeDatasetOffset = 0;
uint16_t pendingDatasetOffset = 0;
bool dataRequest = false;
Tlv tlv;
// Leader Data
SuccessOrExit(error = Tlv::GetTlv(aMessage, Tlv::kLeaderData, sizeof(leaderData), leaderData));
VerifyOrExit(leaderData.IsValid(), error = OT_ERROR_PARSE);
if ((leaderData.GetPartitionId() != mLeaderData.GetPartitionId()) ||
(leaderData.GetWeighting() != mLeaderData.GetWeighting()) || (leaderData.GetLeaderRouterId() != GetLeaderId()))
{
if (mRole == OT_DEVICE_ROLE_CHILD)
{
SetLeaderData(leaderData.GetPartitionId(), leaderData.GetWeighting(), leaderData.GetLeaderRouterId());
mRetrieveNewNetworkData = true;
}
else
{
ExitNow(error = OT_ERROR_DROP);
}
}
else if (!mRetrieveNewNetworkData)
{
VerifyOrExit(IsNetworkDataNewer(leaderData));
}
// Active Timestamp
if (Tlv::GetTlv(aMessage, Tlv::kActiveTimestamp, sizeof(activeTimestamp), activeTimestamp) == OT_ERROR_NONE)
{
const MeshCoP::Timestamp *timestamp;
VerifyOrExit(activeTimestamp.IsValid(), error = OT_ERROR_PARSE);
timestamp = Get<MeshCoP::ActiveDataset>().GetTimestamp();
// if received timestamp does not match the local value and message does not contain the dataset,
// send MLE Data Request
if ((timestamp == NULL || timestamp->Compare(activeTimestamp) != 0) &&
(Tlv::GetOffset(aMessage, Tlv::kActiveDataset, activeDatasetOffset) != OT_ERROR_NONE))
{
ExitNow(dataRequest = true);
}
}
else
{
activeTimestamp.SetLength(0);
}
// Pending Timestamp
if (Tlv::GetTlv(aMessage, Tlv::kPendingTimestamp, sizeof(pendingTimestamp), pendingTimestamp) == OT_ERROR_NONE)
{
const MeshCoP::Timestamp *timestamp;
VerifyOrExit(pendingTimestamp.IsValid(), error = OT_ERROR_PARSE);
timestamp = Get<MeshCoP::PendingDataset>().GetTimestamp();
// if received timestamp does not match the local value and message does not contain the dataset,
// send MLE Data Request
if ((timestamp == NULL || timestamp->Compare(pendingTimestamp) != 0) &&
(Tlv::GetOffset(aMessage, Tlv::kPendingDataset, pendingDatasetOffset) != OT_ERROR_NONE))
{
ExitNow(dataRequest = true);
}
}
else
{
pendingTimestamp.SetLength(0);
}
if (Tlv::GetOffset(aMessage, Tlv::kNetworkData, networkDataOffset) == OT_ERROR_NONE)
{
error =
Get<NetworkData::Leader>().SetNetworkData(leaderData.GetDataVersion(), leaderData.GetStableDataVersion(),
!IsFullNetworkData(), aMessage, networkDataOffset);
SuccessOrExit(error);
}
else
{
ExitNow(dataRequest = true);
}
// Active Dataset
if (activeTimestamp.GetLength() > 0)
{
if (activeDatasetOffset > 0)
{
aMessage.Read(activeDatasetOffset, sizeof(tlv), &tlv);
Get<MeshCoP::ActiveDataset>().Save(activeTimestamp, aMessage, activeDatasetOffset + sizeof(tlv),
tlv.GetLength());
}
}
// Pending Dataset
if (pendingTimestamp.GetLength() > 0)
{
if (pendingDatasetOffset > 0)
{
aMessage.Read(pendingDatasetOffset, sizeof(tlv), &tlv);
Get<MeshCoP::PendingDataset>().Save(pendingTimestamp, aMessage, pendingDatasetOffset + sizeof(tlv),
tlv.GetLength());
}
}
mRetrieveNewNetworkData = false;
exit:
if (dataRequest)
{
static const uint8_t tlvs[] = {Tlv::kNetworkData};
uint16_t delay;
if (aMessageInfo.GetSockAddr().IsMulticast())
{
delay = Random::NonCrypto::GetUint16InRange(0, kMleMaxResponseDelay);
}
else
{
// This method may have been called from an MLE request
// handler. We add a minimum delay here so that the MLE
// response is enqueued before the MLE Data Request.
delay = 10;
}
SendDataRequest(aMessageInfo.GetPeerAddr(), tlvs, sizeof(tlvs), delay);
}
else if (error == OT_ERROR_NONE)
{
mDataRequestAttempts = 0;
mDataRequestState = kDataRequestNone;
// Here the `mMessageTransmissionTimer` is intentionally not canceled
// so that when it fires from its callback a "Child Update" is sent
// if the device is a rx-on child. This way, even when the timer is
// reused for retransmission of "Data Request" messages, it is ensured
// that keep-alive "Child Update Request" messages are send within the
// child's timeout.
}
return error;
}
bool Mle::IsBetterParent(uint16_t aRloc16, uint8_t aLinkQuality, uint8_t aLinkMargin, ConnectivityTlv &aConnectivityTlv)
{
bool rval = false;
uint8_t candidateLinkQualityIn = mParentCandidate.GetLinkInfo().GetLinkQuality();
uint8_t candidateTwoWayLinkQuality = (candidateLinkQualityIn < mParentCandidate.GetLinkQualityOut())
? candidateLinkQualityIn
: mParentCandidate.GetLinkQualityOut();
if (aLinkQuality != candidateTwoWayLinkQuality)
{
ExitNow(rval = (aLinkQuality > candidateTwoWayLinkQuality));
}
if (IsActiveRouter(aRloc16) != IsActiveRouter(mParentCandidate.GetRloc16()))
{
ExitNow(rval = IsActiveRouter(aRloc16));
}
if (aConnectivityTlv.GetParentPriority() != mParentPriority)
{
ExitNow(rval = (aConnectivityTlv.GetParentPriority() > mParentPriority));
}
if (aConnectivityTlv.GetLinkQuality3() != mParentLinkQuality3)
{
ExitNow(rval = (aConnectivityTlv.GetLinkQuality3() > mParentLinkQuality3));
}
if (aConnectivityTlv.GetLinkQuality2() != mParentLinkQuality2)
{
ExitNow(rval = (aConnectivityTlv.GetLinkQuality2() > mParentLinkQuality2));
}
if (aConnectivityTlv.GetLinkQuality1() != mParentLinkQuality1)
{
ExitNow(rval = (aConnectivityTlv.GetLinkQuality1() > mParentLinkQuality1));
}
rval = (aLinkMargin > mParentLinkMargin);
exit:
return rval;
}
void Mle::ResetParentCandidate(void)
{
mParentCandidate.Clear();
}
otError Mle::HandleParentResponse(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo, uint32_t aKeySequence)
{
otError error = OT_ERROR_NONE;
const otThreadLinkInfo *linkInfo = static_cast<const otThreadLinkInfo *>(aMessageInfo.GetLinkInfo());
ResponseTlv response;
SourceAddressTlv sourceAddress;
LeaderDataTlv leaderData;
LinkMarginTlv linkMarginTlv;
uint8_t linkMargin;
uint8_t linkQuality;
ConnectivityTlv connectivity;
LinkFrameCounterTlv linkFrameCounter;
MleFrameCounterTlv mleFrameCounter;
ChallengeTlv challenge;
Mac::ExtAddress extAddress;
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
TimeParameterTlv timeParameter;
#endif
// Source Address
SuccessOrExit(error = Tlv::GetTlv(aMessage, Tlv::kSourceAddress, sizeof(sourceAddress), sourceAddress));
VerifyOrExit(sourceAddress.IsValid(), error = OT_ERROR_PARSE);
LogMleMessage("Receive Parent Response", aMessageInfo.GetPeerAddr(), sourceAddress.GetRloc16());
// Response
SuccessOrExit(error = Tlv::GetTlv(aMessage, Tlv::kResponse, sizeof(response), response));
VerifyOrExit(response.IsValid() &&
memcmp(response.GetResponse(), mParentRequest.mChallenge, response.GetResponseLength()) == 0,
error = OT_ERROR_PARSE);
aMessageInfo.GetPeerAddr().ToExtAddress(extAddress);
if (mRole == OT_DEVICE_ROLE_CHILD && mParent.GetExtAddress() == extAddress)
{
mReceivedResponseFromParent = true;
}
// Leader Data
SuccessOrExit(error = Tlv::GetTlv(aMessage, Tlv::kLeaderData, sizeof(leaderData), leaderData));
VerifyOrExit(leaderData.IsValid(), error = OT_ERROR_PARSE);
// Link Quality
SuccessOrExit(error = Tlv::GetTlv(aMessage, Tlv::kLinkMargin, sizeof(linkMarginTlv), linkMarginTlv));
VerifyOrExit(linkMarginTlv.IsValid(), error = OT_ERROR_PARSE);
linkMargin = LinkQualityInfo::ConvertRssToLinkMargin(Get<Mac::Mac>().GetNoiseFloor(), linkInfo->mRss);
if (linkMargin > linkMarginTlv.GetLinkMargin())
{
linkMargin = linkMarginTlv.GetLinkMargin();
}
linkQuality = LinkQualityInfo::ConvertLinkMarginToLinkQuality(linkMargin);
// Connectivity
SuccessOrExit(error = Tlv::GetTlv(aMessage, Tlv::kConnectivity, sizeof(connectivity), connectivity));
VerifyOrExit(connectivity.IsValid(), error = OT_ERROR_PARSE);
// Share data with application, if requested.
if (mParentResponseCb)
{
otThreadParentResponseInfo parentinfo;
parentinfo.mExtAddr = extAddress;
parentinfo.mRloc16 = sourceAddress.GetRloc16();
parentinfo.mRssi = linkInfo->mRss;
parentinfo.mPriority = connectivity.GetParentPriority();
parentinfo.mLinkQuality3 = connectivity.GetLinkQuality3();
parentinfo.mLinkQuality2 = connectivity.GetLinkQuality2();
parentinfo.mLinkQuality1 = connectivity.GetLinkQuality1();
parentinfo.mIsAttached = IsAttached();
mParentResponseCb(&parentinfo, mParentResponseCbContext);
}
#if OPENTHREAD_FTD
if (IsFullThreadDevice() && (mRole != OT_DEVICE_ROLE_DETACHED))
{
int8_t diff = static_cast<int8_t>(connectivity.GetIdSequence() - Get<RouterTable>().GetRouterIdSequence());
switch (mParentRequestMode)
{
case kAttachAny:
VerifyOrExit(leaderData.GetPartitionId() != mLeaderData.GetPartitionId() || diff > 0);
break;
case kAttachSame1:
case kAttachSame2:
VerifyOrExit(leaderData.GetPartitionId() == mLeaderData.GetPartitionId());
VerifyOrExit(diff > 0);
break;
case kAttachSameDowngrade:
VerifyOrExit(leaderData.GetPartitionId() == mLeaderData.GetPartitionId());
VerifyOrExit(diff >= 0);
break;
case kAttachBetter:
VerifyOrExit(leaderData.GetPartitionId() != mLeaderData.GetPartitionId());
VerifyOrExit(MleRouter::ComparePartitions(connectivity.GetActiveRouters() <= 1, leaderData,
Get<MleRouter>().IsSingleton(), mLeaderData) > 0);
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 (IsFullThreadDevice())
{
compare = MleRouter::ComparePartitions(connectivity.GetActiveRouters() <= 1, leaderData, mParentIsSingleton,
mParentLeaderData);
}
// only consider partitions that are the same or better
VerifyOrExit(compare >= 0);
// only consider better parents if the partitions are the same
VerifyOrExit(compare != 0 || IsBetterParent(sourceAddress.GetRloc16(), linkQuality, linkMargin, connectivity));
}
// Link Frame Counter
SuccessOrExit(error = Tlv::GetTlv(aMessage, Tlv::kLinkFrameCounter, sizeof(linkFrameCounter), linkFrameCounter));
VerifyOrExit(linkFrameCounter.IsValid(), error = OT_ERROR_PARSE);
// Mle Frame Counter
if (Tlv::GetTlv(aMessage, Tlv::kMleFrameCounter, sizeof(mleFrameCounter), mleFrameCounter) == OT_ERROR_NONE)
{
VerifyOrExit(mleFrameCounter.IsValid());
}
else
{
mleFrameCounter.SetFrameCounter(linkFrameCounter.GetFrameCounter());
}
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
// Time Parameter
if (Tlv::GetTlv(aMessage, Tlv::kTimeParameter, sizeof(timeParameter), timeParameter) == OT_ERROR_NONE)
{
VerifyOrExit(timeParameter.IsValid());
Get<TimeSync>().SetTimeSyncPeriod(timeParameter.GetTimeSyncPeriod());
Get<TimeSync>().SetXtalThreshold(timeParameter.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 // OPENTHREAD_CONFIG_TIME_SYNC_REQUIRED
#endif // OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
// Challenge
SuccessOrExit(error = Tlv::GetTlv(aMessage, Tlv::kChallenge, sizeof(challenge), challenge));
VerifyOrExit(challenge.IsValid(), error = OT_ERROR_PARSE);
mChildIdRequest.mChallengeLength = challenge.GetChallengeLength();
memcpy(mChildIdRequest.mChallenge, challenge.GetChallenge(), mChildIdRequest.mChallengeLength);
mParentCandidate.SetExtAddress(extAddress);
mParentCandidate.SetRloc16(sourceAddress.GetRloc16());
mParentCandidate.SetLinkFrameCounter(linkFrameCounter.GetFrameCounter());
mParentCandidate.SetMleFrameCounter(mleFrameCounter.GetFrameCounter());
mParentCandidate.SetDeviceMode(DeviceMode(DeviceMode::kModeFullThreadDevice | DeviceMode::kModeRxOnWhenIdle |
DeviceMode::kModeFullNetworkData | DeviceMode::kModeSecureDataRequest));
mParentCandidate.GetLinkInfo().Clear();
mParentCandidate.GetLinkInfo().AddRss(Get<Mac::Mac>().GetNoiseFloor(), linkInfo->mRss);
mParentCandidate.ResetLinkFailures();
mParentCandidate.SetLinkQualityOut(LinkQualityInfo::ConvertLinkMarginToLinkQuality(linkMarginTlv.GetLinkMargin()));
mParentCandidate.SetState(Neighbor::kStateParentResponse);
mParentCandidate.SetKeySequence(aKeySequence);
mParentPriority = connectivity.GetParentPriority();
mParentLinkQuality3 = connectivity.GetLinkQuality3();
mParentLinkQuality2 = connectivity.GetLinkQuality2();
mParentLinkQuality1 = connectivity.GetLinkQuality1();
mParentLeaderCost = connectivity.GetLeaderCost();
mParentLeaderData = leaderData;
mParentIsSingleton = connectivity.GetActiveRouters() <= 1;
mParentLinkMargin = linkMargin;
exit:
if (error != OT_ERROR_NONE)
{
otLogWarnMle("Failed to process Parent Response: %s", otThreadErrorToString(error));
}
return error;
}
otError Mle::HandleChildIdResponse(const Message & aMessage,
const Ip6::MessageInfo &aMessageInfo,
const Neighbor * aNeighbor)
{
OT_UNUSED_VARIABLE(aMessageInfo);
otError error = OT_ERROR_NONE;
LeaderDataTlv leaderData;
SourceAddressTlv sourceAddress;
Address16Tlv shortAddress;
RouteTlv route;
ActiveTimestampTlv activeTimestamp;
PendingTimestampTlv pendingTimestamp;
Tlv tlv;
uint16_t networkDataOffset;
uint16_t offset;
// Source Address
SuccessOrExit(error = Tlv::GetTlv(aMessage, Tlv::kSourceAddress, sizeof(sourceAddress), sourceAddress));
VerifyOrExit(sourceAddress.IsValid(), error = OT_ERROR_PARSE);
LogMleMessage("Receive Child ID Response", aMessageInfo.GetPeerAddr(), sourceAddress.GetRloc16());
VerifyOrExit(aNeighbor && aNeighbor->IsStateValid(), error = OT_ERROR_SECURITY);
VerifyOrExit(mAttachState == kAttachStateChildIdRequest);
// Leader Data
SuccessOrExit(error = Tlv::GetTlv(aMessage, Tlv::kLeaderData, sizeof(leaderData), leaderData));
VerifyOrExit(leaderData.IsValid(), error = OT_ERROR_PARSE);
// ShortAddress
SuccessOrExit(error = Tlv::GetTlv(aMessage, Tlv::kAddress16, sizeof(shortAddress), shortAddress));
VerifyOrExit(shortAddress.IsValid(), error = OT_ERROR_PARSE);
// Network Data
error = Tlv::GetOffset(aMessage, Tlv::kNetworkData, networkDataOffset);
SuccessOrExit(error);
// Active Timestamp
if (Tlv::GetTlv(aMessage, Tlv::kActiveTimestamp, sizeof(activeTimestamp), activeTimestamp) == OT_ERROR_NONE)
{
VerifyOrExit(activeTimestamp.IsValid(), error = OT_ERROR_PARSE);
// Active Dataset
if (Tlv::GetOffset(aMessage, Tlv::kActiveDataset, offset) == OT_ERROR_NONE)
{
aMessage.Read(offset, sizeof(tlv), &tlv);
Get<MeshCoP::ActiveDataset>().Save(activeTimestamp, aMessage, offset + sizeof(tlv), tlv.GetLength());
}
}
// clear Pending Dataset if device succeed to reattach using stored Pending Dataset
if (mReattachState == kReattachPending)
{
Get<MeshCoP::PendingDataset>().Clear();
}
// Pending Timestamp
if (Tlv::GetTlv(aMessage, Tlv::kPendingTimestamp, sizeof(pendingTimestamp), pendingTimestamp) == OT_ERROR_NONE)
{
VerifyOrExit(pendingTimestamp.IsValid(), error = OT_ERROR_PARSE);
// Pending Dataset
if (Tlv::GetOffset(aMessage, Tlv::kPendingDataset, offset) == OT_ERROR_NONE)
{
aMessage.Read(offset, sizeof(tlv), &tlv);
Get<MeshCoP::PendingDataset>().Save(pendingTimestamp, aMessage, offset + sizeof(tlv), tlv.GetLength());
}
}
else
{
Get<MeshCoP::PendingDataset>().ClearNetwork();
}
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
// Sync to Thread network time
if (aMessage.GetTimeSyncSeq() != OT_TIME_SYNC_INVALID_SEQ)
{
Get<TimeSync>().HandleTimeSyncMessage(aMessage);
}
#endif
// Parent Attach Success
SetStateDetached();
SetLeaderData(leaderData.GetPartitionId(), leaderData.GetWeighting(), leaderData.GetLeaderRouterId());
if (!IsRxOnWhenIdle())
{
Get<DataPollSender>().SetAttachMode(false);
Get<MeshForwarder>().SetRxOnWhenIdle(false);
}
else
{
Get<MeshForwarder>().SetRxOnWhenIdle(true);
}
// Route
if ((Tlv::GetTlv(aMessage, Tlv::kRoute, sizeof(route), route) == OT_ERROR_NONE) && IsFullThreadDevice())
{
SuccessOrExit(error = Get<MleRouter>().ProcessRouteTlv(route));
}
mParent = mParentCandidate;
ResetParentCandidate();
mParent.SetRloc16(sourceAddress.GetRloc16());
Get<NetworkData::Leader>().SetNetworkData(leaderData.GetDataVersion(), leaderData.GetStableDataVersion(),
!IsFullNetworkData(), aMessage, networkDataOffset);
SetStateChild(shortAddress.GetRloc16());
exit:
if (error != OT_ERROR_NONE)
{
otLogWarnMle("Failed to process Child ID Response: %s", otThreadErrorToString(error));
}
return error;
}
otError Mle::HandleChildUpdateRequest(const Message & aMessage,
const Ip6::MessageInfo &aMessageInfo,
Neighbor * aNeighbor)
{
static const uint8_t kMaxResponseTlvs = 6;
otError error = OT_ERROR_NONE;
SourceAddressTlv sourceAddress;
ChallengeTlv challenge;
TlvRequestTlv tlvRequest;
uint8_t tlvs[kMaxResponseTlvs] = {};
uint8_t numTlvs = 0;
// Source Address
SuccessOrExit(error = Tlv::GetTlv(aMessage, Tlv::kSourceAddress, sizeof(sourceAddress), sourceAddress));
VerifyOrExit(sourceAddress.IsValid(), error = OT_ERROR_PARSE);
LogMleMessage("Receive Child Update Request from parent", aMessageInfo.GetPeerAddr(), sourceAddress.GetRloc16());
// Challenge
if (Tlv::GetTlv(aMessage, Tlv::kChallenge, sizeof(challenge), challenge) == OT_ERROR_NONE)
{
VerifyOrExit(challenge.IsValid(), error = OT_ERROR_PARSE);
tlvs[numTlvs++] = Tlv::kResponse;
tlvs[numTlvs++] = Tlv::kMleFrameCounter;
tlvs[numTlvs++] = Tlv::kLinkFrameCounter;
}
if (aNeighbor == &mParent)
{
StatusTlv status;
if (Tlv::GetTlv(aMessage, Tlv::kStatus, sizeof(status), status) == OT_ERROR_NONE)
{
VerifyOrExit(status.IsValid(), error = OT_ERROR_PARSE);
if (status.GetStatus() == StatusTlv::kError)
{
BecomeDetached();
ExitNow();
}
}
if (mParent.GetRloc16() != sourceAddress.GetRloc16())
{
BecomeDetached();
ExitNow();
}
// Leader Data, Network Data, Active Timestamp, Pending Timestamp
SuccessOrExit(error = HandleLeaderData(aMessage, aMessageInfo));
}
else
{
// this device is not a child of the Child Update Request source
tlvs[numTlvs++] = Tlv::kStatus;
}
// TLV Request
if (Tlv::GetTlv(aMessage, Tlv::kTlvRequest, sizeof(tlvRequest), tlvRequest) == OT_ERROR_NONE)
{
VerifyOrExit(tlvRequest.IsValid(), error = OT_ERROR_PARSE);
for (uint8_t i = 0; i < tlvRequest.GetLength(); i++)
{
if (numTlvs >= sizeof(tlvs))
{
otLogWarnMle("Failed to respond with TLVs: %d of %d", i, tlvRequest.GetLength());
break;
}
tlvs[numTlvs++] = tlvRequest.GetTlvs()[i];
}
}
SuccessOrExit(error = SendChildUpdateResponse(tlvs, numTlvs, challenge));
exit:
if (error != OT_ERROR_NONE)
{
otLogWarnMle("Failed to process Child Update Request from parent: %s", otThreadErrorToString(error));
}
return error;
}
otError Mle::HandleChildUpdateResponse(const Message & aMessage,
const Ip6::MessageInfo &aMessageInfo,
const Neighbor * aNeighbor)
{
otError error = OT_ERROR_NONE;
StatusTlv status;
ModeTlv mode;
ResponseTlv response;
LinkFrameCounterTlv linkFrameCounter;
MleFrameCounterTlv mleFrameCounter;
SourceAddressTlv sourceAddress;
TimeoutTlv timeout;
LogMleMessage("Receive Child Update Response from parent", aMessageInfo.GetPeerAddr());
switch (mRole)
{
case OT_DEVICE_ROLE_DETACHED:
VerifyOrExit(
(Tlv::GetTlv(aMessage, Tlv::kResponse, sizeof(response), response) == OT_ERROR_NONE) &&
(response.IsValid()) &&
(!memcmp(response.GetResponse(), mParentRequest.mChallenge, sizeof(mParentRequest.mChallenge))),
error = OT_ERROR_SECURITY);
break;
case OT_DEVICE_ROLE_CHILD:
VerifyOrExit((aNeighbor == &mParent) && mParent.IsStateValid(), error = OT_ERROR_SECURITY);
break;
default:
assert(false);
break;
}
// Status
if (Tlv::GetTlv(aMessage, Tlv::kStatus, sizeof(status), status) == OT_ERROR_NONE)
{
BecomeDetached();
ExitNow();
}
// Mode
SuccessOrExit(error = Tlv::GetTlv(aMessage, Tlv::kMode, sizeof(mode), mode));
VerifyOrExit(mode.IsValid(), error = OT_ERROR_PARSE);
VerifyOrExit(mode.GetMode() == mDeviceMode, error = OT_ERROR_DROP);
switch (mRole)
{
case OT_DEVICE_ROLE_DETACHED:
SuccessOrExit(error =
Tlv::GetTlv(aMessage, Tlv::kLinkFrameCounter, sizeof(linkFrameCounter), linkFrameCounter));
VerifyOrExit(linkFrameCounter.IsValid(), error = OT_ERROR_PARSE);
if (Tlv::GetTlv(aMessage, Tlv::kMleFrameCounter, sizeof(mleFrameCounter), mleFrameCounter) == OT_ERROR_NONE)
{
VerifyOrExit(mleFrameCounter.IsValid(), error = OT_ERROR_PARSE);
}
else
{
mleFrameCounter.SetFrameCounter(linkFrameCounter.GetFrameCounter());
}
mParent.SetLinkFrameCounter(linkFrameCounter.GetFrameCounter());
mParent.SetMleFrameCounter(mleFrameCounter.GetFrameCounter());
mParent.SetState(Neighbor::kStateValid);
SetStateChild(GetRloc16());
mRetrieveNewNetworkData = true;
// fall through
case OT_DEVICE_ROLE_CHILD:
// Source Address
SuccessOrExit(error = Tlv::GetTlv(aMessage, Tlv::kSourceAddress, sizeof(sourceAddress), sourceAddress));
VerifyOrExit(sourceAddress.IsValid(), error = OT_ERROR_PARSE);
if (RouterIdFromRloc16(sourceAddress.GetRloc16()) != RouterIdFromRloc16(GetRloc16()))
{
BecomeDetached();
ExitNow();
}
// Leader Data, Network Data, Active Timestamp, Pending Timestamp
SuccessOrExit(error = HandleLeaderData(aMessage, aMessageInfo));
// Timeout optional
if (Tlv::GetTlv(aMessage, Tlv::kTimeout, sizeof(timeout), timeout) == OT_ERROR_NONE)
{
VerifyOrExit(timeout.IsValid(), error = OT_ERROR_PARSE);
mTimeout = timeout.GetTimeout();
}
if (!IsRxOnWhenIdle())
{
Get<DataPollSender>().SetAttachMode(false);
Get<MeshForwarder>().SetRxOnWhenIdle(false);
}
else
{
Get<MeshForwarder>().SetRxOnWhenIdle(true);
}
break;
default:
assert(false);
break;
}
exit:
if (error == OT_ERROR_NONE)
{
if (mChildUpdateRequestState == kChildUpdateRequestActive)
{
mChildUpdateAttempts = 0;
mChildUpdateRequestState = kChildUpdateRequestNone;
ScheduleMessageTransmissionTimer();
}
}
else
{
otLogWarnMle("Failed to process Child Update Response: %s", otThreadErrorToString(error));
}
return error;
}
otError Mle::HandleAnnounce(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
OT_UNUSED_VARIABLE(aMessageInfo);
otError error = OT_ERROR_NONE;
ChannelTlv channelTlv;
ActiveTimestampTlv timestamp;
const MeshCoP::Timestamp *localTimestamp;
PanIdTlv panIdTlv;
uint8_t channel;
uint16_t panId;
LogMleMessage("Receive Announce", aMessageInfo.GetPeerAddr());
SuccessOrExit(error = Tlv::GetTlv(aMessage, Tlv::kChannel, sizeof(channelTlv), channelTlv));
VerifyOrExit(channelTlv.IsValid(), error = OT_ERROR_PARSE);
channel = static_cast<uint8_t>(channelTlv.GetChannel());
SuccessOrExit(error = Tlv::GetTlv(aMessage, Tlv::kActiveTimestamp, sizeof(timestamp), timestamp));
VerifyOrExit(timestamp.IsValid(), error = OT_ERROR_PARSE);
SuccessOrExit(error = Tlv::GetTlv(aMessage, Tlv::kPanId, sizeof(panIdTlv), panIdTlv));
VerifyOrExit(panIdTlv.IsValid(), error = OT_ERROR_PARSE);
panId = panIdTlv.GetPanId();
localTimestamp = Get<MeshCoP::ActiveDataset>().GetTimestamp();
if (localTimestamp == NULL || localTimestamp->Compare(timestamp) > 0)
{
// No action is required if device is detached, and current
// channel and pan-id match the values from the received MLE
// Announce message.
VerifyOrExit((mRole != OT_DEVICE_ROLE_DETACHED) || (Get<Mac::Mac>().GetPanChannel() != channel) ||
(Get<Mac::Mac>().GetPanId() != panId));
if (mAttachState == kAttachStateProcessAnnounce)
{
VerifyOrExit(mAlternateTimestamp < timestamp.GetSeconds());
}
mAlternateTimestamp = timestamp.GetSeconds();
mAlternateChannel = channel;
mAlternatePanId = panId;
SetAttachState(kAttachStateProcessAnnounce);
mAttachTimer.Start(kAnnounceProcessTimeout);
otLogNoteMle("Delay processing Announce - channel %d, panid 0x%02x", channel, panId);
}
else if (localTimestamp->Compare(timestamp) < 0)
{
SendAnnounce(channel, false);
#if OPENTHREAD_CONFIG_MLE_SEND_UNICAST_ANNOUNCE_RESPONSE
SendAnnounce(channel, false, aMessageInfo.GetPeerAddr());
#endif
}
else
{
// do nothing
// timestamps are equal: no behaviour specified by the Thread spec.
// If SendAnnounce is executed at this point, there exists a scenario where
// multiple devices keep sending MLE Announce messages to one another indefinitely.
}
exit:
if (error != OT_ERROR_NONE)
{
otLogWarnMle("Failed to process Announce: %s", otThreadErrorToString(error));
}
return error;
}
void Mle::ProcessAnnounce(void)
{
uint8_t newChannel = mAlternateChannel;
uint16_t newPanId = mAlternatePanId;
assert(mAttachState == kAttachStateProcessAnnounce);
otLogNoteMle("Processing Announce - channel %d, panid 0x%02x", newChannel, newPanId);
Stop(/* aClearNetworkDatasets */ false);
// Save the current/previous channel and pan-id
mAlternateChannel = Get<Mac::Mac>().GetPanChannel();
mAlternatePanId = Get<Mac::Mac>().GetPanId();
mAlternateTimestamp = 0;
Get<Mac::Mac>().SetPanChannel(newChannel);
Get<Mac::Mac>().SetPanId(newPanId);
Start(/* aAnnounceAttach */ true);
}
otError Mle::HandleDiscoveryResponse(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
otError error = OT_ERROR_NONE;
const otThreadLinkInfo * linkInfo = static_cast<const otThreadLinkInfo *>(aMessageInfo.GetLinkInfo());
Tlv tlv;
MeshCoP::Tlv meshcopTlv;
MeshCoP::DiscoveryResponseTlv discoveryResponse;
MeshCoP::ExtendedPanIdTlv extPanId;
MeshCoP::NetworkNameTlv networkName;
MeshCoP::SteeringDataTlv steeringData;
MeshCoP::JoinerUdpPortTlv JoinerUdpPort;
otActiveScanResult result;
uint16_t offset;
uint16_t end;
bool didCheckSteeringData = false;
LogMleMessage("Receive Discovery Response", aMessageInfo.GetPeerAddr());
VerifyOrExit(mDiscoverInProgress, error = OT_ERROR_DROP);
// find MLE Discovery TLV
VerifyOrExit(Tlv::GetOffset(aMessage, Tlv::kDiscovery, offset) == OT_ERROR_NONE, error = OT_ERROR_PARSE);
aMessage.Read(offset, sizeof(tlv), &tlv);
offset += sizeof(tlv);
end = offset + tlv.GetLength();
memset(&result, 0, sizeof(result));
result.mPanId = linkInfo->mPanId;
result.mChannel = linkInfo->mChannel;
result.mRssi = linkInfo->mRss;
result.mLqi = linkInfo->mLqi;
aMessageInfo.GetPeerAddr().ToExtAddress(*static_cast<Mac::ExtAddress *>(&result.mExtAddress));
// process MeshCoP TLVs
while (offset < end)
{
aMessage.Read(offset, sizeof(meshcopTlv), &meshcopTlv);
switch (meshcopTlv.GetType())
{
case MeshCoP::Tlv::kDiscoveryResponse:
aMessage.Read(offset, sizeof(discoveryResponse), &discoveryResponse);
VerifyOrExit(discoveryResponse.IsValid(), error = OT_ERROR_PARSE);
result.mVersion = discoveryResponse.GetVersion();
result.mIsNative = discoveryResponse.IsNativeCommissioner();
break;
case MeshCoP::Tlv::kExtendedPanId:
aMessage.Read(offset, sizeof(extPanId), &extPanId);
VerifyOrExit(extPanId.IsValid(), error = OT_ERROR_PARSE);
result.mExtendedPanId = extPanId.GetExtendedPanId();
break;
case MeshCoP::Tlv::kNetworkName:
aMessage.Read(offset, sizeof(networkName), &networkName);
static_cast<Mac::NetworkName &>(result.mNetworkName).Set(networkName.GetNetworkName());
break;
case MeshCoP::Tlv::kSteeringData:
aMessage.Read(offset, sizeof(steeringData), &steeringData);
VerifyOrExit(steeringData.IsValid(), error = OT_ERROR_PARSE);
if (mDiscoverEnableFiltering)
{
VerifyOrExit((steeringData.GetBit(mDiscoverCcittIndex % steeringData.GetNumBits()) &&
steeringData.GetBit(mDiscoverAnsiIndex % steeringData.GetNumBits())));
}
didCheckSteeringData = true;
result.mSteeringData.mLength = steeringData.GetSteeringDataLength();
memcpy(result.mSteeringData.m8, steeringData.GetValue(), result.mSteeringData.mLength);
break;
case MeshCoP::Tlv::kJoinerUdpPort:
aMessage.Read(offset, sizeof(JoinerUdpPort), &JoinerUdpPort);
VerifyOrExit(JoinerUdpPort.IsValid(), error = OT_ERROR_PARSE);
result.mJoinerUdpPort = JoinerUdpPort.GetUdpPort();
break;
default:
break;
}
offset += sizeof(meshcopTlv) + meshcopTlv.GetLength();
}
VerifyOrExit(!mDiscoverEnableFiltering || didCheckSteeringData);
mDiscoverHandler(&result, mDiscoverContext);
exit:
if (error != OT_ERROR_NONE)
{
otLogWarnMle("Failed to process Discovery Response: %s", otThreadErrorToString(error));
}
return error;
}
Neighbor *Mle::GetNeighbor(uint16_t aAddress)
{
Neighbor *rval = NULL;
if (mParent.IsStateValidOrRestoring() && (mParent.GetRloc16() == aAddress))
{
rval = &mParent;
}
else if (mParentCandidate.IsStateValid() && (mParentCandidate.GetRloc16() == aAddress))
{
rval = &mParentCandidate;
}
return rval;
}
Neighbor *Mle::GetNeighbor(const Mac::ExtAddress &aAddress)
{
Neighbor *rval = NULL;
if (mParent.IsStateValidOrRestoring() && (mParent.GetExtAddress() == aAddress))
{
rval = &mParent;
}
else if (mParentCandidate.IsStateValid() && (mParentCandidate.GetExtAddress() == aAddress))
{
rval = &mParentCandidate;
}
return rval;
}
Neighbor *Mle::GetNeighbor(const Mac::Address &aAddress)
{
Neighbor *neighbor = NULL;
switch (aAddress.GetType())
{
case Mac::Address::kTypeShort:
neighbor = GetNeighbor(aAddress.GetShort());
break;
case Mac::Address::kTypeExtended:
neighbor = GetNeighbor(aAddress.GetExtended());
break;
default:
break;
}
return neighbor;
}
uint16_t Mle::GetNextHop(uint16_t aDestination) const
{
OT_UNUSED_VARIABLE(aDestination);
return (mParent.IsStateValid()) ? mParent.GetRloc16() : static_cast<uint16_t>(Mac::kShortAddrInvalid);
}
bool Mle::IsRoutingLocator(const Ip6::Address &aAddress) const
{
return memcmp(&mMeshLocal16, &aAddress, kRlocPrefixLength) == 0 &&
aAddress.mFields.m8[14] < Ip6::Address::kAloc16Mask &&
(aAddress.mFields.m8[14] & Ip6::Address::kRloc16ReservedBitMask) == 0;
}
bool Mle::IsAnycastLocator(const Ip6::Address &aAddress) const
{
return memcmp(&mMeshLocal16, &aAddress, kRlocPrefixLength) == 0 &&
aAddress.mFields.m8[14] == Ip6::Address::kAloc16Mask;
}
bool Mle::IsMeshLocalAddress(const Ip6::Address &aAddress) const
{
return aAddress.PrefixMatch(GetMeshLocal16()) >= Ip6::Address::kMeshLocalPrefixLength;
}
otError Mle::CheckReachability(uint16_t aMeshSource, uint16_t aMeshDest, Ip6::Header &aIp6Header)
{
otError error = OT_ERROR_DROP;
Ip6::MessageInfo messageInfo;
if (aMeshDest != GetRloc16())
{
ExitNow(error = OT_ERROR_NONE);
}
if (Get<ThreadNetif>().IsUnicastAddress(aIp6Header.GetDestination()))
{
ExitNow(error = OT_ERROR_NONE);
}
messageInfo.GetPeerAddr() = GetMeshLocal16();
messageInfo.GetPeerAddr().mFields.m16[7] = HostSwap16(aMeshSource);
Get<Ip6::Icmp>().SendError(Ip6::IcmpHeader::kTypeDstUnreach, Ip6::IcmpHeader::kCodeDstUnreachNoRoute, messageInfo,
aIp6Header);
exit:
return error;
}
#if OPENTHREAD_CONFIG_MLE_INFORM_PREVIOUS_PARENT_ON_REATTACH
otError Mle::InformPreviousParent(void)
{
otError error = OT_ERROR_NONE;
Message * message = NULL;
Ip6::MessageInfo messageInfo;
VerifyOrExit((mPreviousParentRloc != Mac::kShortAddrInvalid) && (mPreviousParentRloc != mParent.GetRloc16()));
mCounters.mParentChanges++;
VerifyOrExit((message = Get<Ip6::Ip6>().NewMessage(0)) != NULL, error = OT_ERROR_NO_BUFS);
SuccessOrExit(error = message->SetLength(0));
messageInfo.SetSockAddr(GetMeshLocal64());
messageInfo.SetPeerAddr(GetMeshLocal16());
messageInfo.GetPeerAddr().mFields.m16[7] = HostSwap16(mPreviousParentRloc);
SuccessOrExit(error = Get<Ip6::Ip6>().SendDatagram(*message, messageInfo, Ip6::kProtoNone));
otLogNoteMle("Sending message to inform previous parent 0x%04x", mPreviousParentRloc);
exit:
if (error != OT_ERROR_NONE)
{
otLogWarnMle("Failed to inform previous parent: %s", otThreadErrorToString(error));
if (message != NULL)
{
message->Free();
}
}
return error;
}
#endif // OPENTHREAD_CONFIG_MLE_INFORM_PREVIOUS_PARENT_ON_REATTACH
#if OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE
void Mle::HandleParentSearchTimer(Timer &aTimer)
{
aTimer.GetOwner<Mle>().HandleParentSearchTimer();
}
void Mle::HandleParentSearchTimer(void)
{
int8_t parentRss;
otLogInfoMle("PeriodicParentSearch: %s interval passed", mParentSearchIsInBackoff ? "Backoff" : "Check");
if (mParentSearchBackoffWasCanceled)
{
// Backoff can be canceled if the device switches to a new parent.
// from `UpdateParentSearchState()`. We want to limit this to happen
// only once within a backoff interval.
if (TimerMilli::GetNow() - mParentSearchBackoffCancelTime >= kParentSearchBackoffInterval)
{
mParentSearchBackoffWasCanceled = false;
otLogInfoMle("PeriodicParentSearch: Backoff cancellation is allowed on parent switch");
}
}
mParentSearchIsInBackoff = false;
VerifyOrExit(mRole == OT_DEVICE_ROLE_CHILD);
parentRss = GetParent().GetLinkInfo().GetAverageRss();
otLogInfoMle("PeriodicParentSearch: Parent RSS %d", parentRss);
VerifyOrExit(parentRss != OT_RADIO_RSSI_INVALID);
if (parentRss < kParentSearchRssThreadhold)
{
otLogInfoMle("PeriodicParentSearch: Parent RSS less than %d, searching for new parents",
kParentSearchRssThreadhold);
mParentSearchIsInBackoff = true;
BecomeChild(kAttachAny);
}
exit:
StartParentSearchTimer();
}
void Mle::StartParentSearchTimer(void)
{
uint32_t interval;
interval = Random::NonCrypto::GetUint32InRange(0, kParentSearchJitterInterval);
if (mParentSearchIsInBackoff)
{
interval += kParentSearchBackoffInterval;
}
else
{
interval += kParentSearchCheckInterval;
}
mParentSearchTimer.Start(interval);
otLogInfoMle("PeriodicParentSearch: (Re)starting timer for %s interval",
mParentSearchIsInBackoff ? "backoff" : "check");
}
void Mle::UpdateParentSearchState(void)
{
#if OPENTHREAD_CONFIG_MLE_INFORM_PREVIOUS_PARENT_ON_REATTACH
// If we are in middle of backoff and backoff was not canceled
// recently and we recently detached from a previous parent,
// then we check if the new parent is different from the previous
// one, and if so, we cancel the backoff mode and also remember
// the backoff cancel time. This way the canceling of backoff
// is allowed only once within a backoff window.
//
// The reason behind the canceling of the backoff is to handle
// the scenario where a previous parent is not available for a
// short duration (e.g., it is going through a software update)
// and the child switches to a less desirable parent. With this
// model the child will check for other parents sooner and have
// the chance to switch back to the original (and possibly
// preferred) parent more quickly.
if (mParentSearchIsInBackoff && !mParentSearchBackoffWasCanceled && mParentSearchRecentlyDetached)
{
if ((mPreviousParentRloc != Mac::kShortAddrInvalid) && (mPreviousParentRloc != mParent.GetRloc16()))
{
mParentSearchIsInBackoff = false;
mParentSearchBackoffWasCanceled = true;
mParentSearchBackoffCancelTime = TimerMilli::GetNow();
otLogInfoMle("PeriodicParentSearch: Canceling backoff on switching to a new parent");
}
}
#endif // OPENTHREAD_CONFIG_MLE_INFORM_PREVIOUS_PARENT_ON_REATTACH
mParentSearchRecentlyDetached = false;
if (!mParentSearchIsInBackoff)
{
StartParentSearchTimer();
}
}
#endif // OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE
void Mle::LogMleMessage(const char *aLogString, const Ip6::Address &aAddress) const
{
OT_UNUSED_VARIABLE(aLogString);
OT_UNUSED_VARIABLE(aAddress);
otLogInfoMle("%s (%s)", aLogString, aAddress.ToString().AsCString());
}
void Mle::LogMleMessage(const char *aLogString, const Ip6::Address &aAddress, uint16_t aRloc) const
{
OT_UNUSED_VARIABLE(aLogString);
OT_UNUSED_VARIABLE(aAddress);
OT_UNUSED_VARIABLE(aRloc);
otLogInfoMle("%s (%s,0x%04x)", aLogString, aAddress.ToString().AsCString(), aRloc);
}
const char *Mle::RoleToString(otDeviceRole aRole)
{
const char *roleString = "Unknown";
switch (aRole)
{
case OT_DEVICE_ROLE_DISABLED:
roleString = "Disabled";
break;
case OT_DEVICE_ROLE_DETACHED:
roleString = "Detached";
break;
case OT_DEVICE_ROLE_CHILD:
roleString = "Child";
break;
case OT_DEVICE_ROLE_ROUTER:
roleString = "Router";
break;
case OT_DEVICE_ROLE_LEADER:
roleString = "Leader";
break;
}
return roleString;
}
// LCOV_EXCL_START
#if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_NOTE) && (OPENTHREAD_CONFIG_LOG_MLE == 1)
const char *Mle::AttachModeToString(AttachMode aMode)
{
const char *str = "unknown";
switch (aMode)
{
case kAttachAny:
str = "any-partition";
break;
case kAttachSame1:
str = "same-partition-try-1";
break;
case kAttachSame2:
str = "same-partition-try-2";
break;
case kAttachBetter:
str = "better-partition";
break;
case kAttachSameDowngrade:
str = "same-partition-downgrade";
break;
}
return str;
}
const char *Mle::AttachStateToString(AttachState aState)
{
const char *str = "Unknown";
switch (aState)
{
case kAttachStateIdle:
str = "Idle";
break;
case kAttachStateProcessAnnounce:
str = "ProcessAnnounce";
break;
case kAttachStateStart:
str = "Start";
break;
case kAttachStateParentRequestRouter:
str = "ParentReqRouters";
break;
case kAttachStateParentRequestReed:
str = "ParentReqReeds";
break;
case kAttachStateAnnounce:
str = "Announce";
break;
case kAttachStateChildIdRequest:
str = "ChildIdReq";
break;
};
return str;
}
const char *Mle::ReattachStateToString(ReattachState aState)
{
const char *str = "unknown";
switch (aState)
{
case kReattachStop:
str = "";
break;
case kReattachStart:
str = "reattaching";
break;
case kReattachActive:
str = "reattaching with Active Dataset";
break;
case kReattachPending:
str = "reattaching with Pending Dataset";
break;
}
return str;
}
#endif // (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_NOTE) && (OPENTHREAD_CONFIG_LOG_MLE == 1)
// LCOV_EXCL_STOP
void Mle::RegisterParentResponseStatsCallback(otThreadParentResponseCallback aCallback, void *aContext)
{
mParentResponseCb = aCallback;
mParentResponseCbContext = aContext;
}
} // namespace Mle
} // namespace ot