/* * Copyright (c) 2016-2020, 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 Discover Scan process. */ #include "discover_scanner.hpp" #include "common/as_core_type.hpp" #include "common/code_utils.hpp" #include "common/instance.hpp" #include "common/locator_getters.hpp" #include "thread/mesh_forwarder.hpp" #include "thread/mle.hpp" #include "thread/mle_router.hpp" namespace ot { namespace Mle { DiscoverScanner::DiscoverScanner(Instance &aInstance) : InstanceLocator(aInstance) , mHandler(nullptr) , mHandlerContext(nullptr) , mTimer(aInstance, DiscoverScanner::HandleTimer) , mFilterIndexes() , mState(kStateIdle) , mScanChannel(0) , mAdvDataLength(0) , mEnableFiltering(false) , mShouldRestorePanId(false) { } Error DiscoverScanner::Discover(const Mac::ChannelMask &aScanChannels, uint16_t aPanId, bool aJoiner, bool aEnableFiltering, const FilterIndexes * aFilterIndexes, Handler aCallback, void * aContext) { Error error = kErrorNone; Mle::TxMessage * message = nullptr; Tlv tlv; Ip6::Address destination; MeshCoP::DiscoveryRequestTlv discoveryRequest; MeshCoP::JoinerAdvertisementTlv joinerAdvertisement; VerifyOrExit(Get().IsUp(), error = kErrorInvalidState); VerifyOrExit(mState == kStateIdle, error = kErrorBusy); mEnableFiltering = aEnableFiltering; if (mEnableFiltering) { if (aFilterIndexes == nullptr) { Mac::ExtAddress extAddress; Get().GetIeeeEui64(extAddress); MeshCoP::ComputeJoinerId(extAddress, extAddress); MeshCoP::SteeringData::CalculateHashBitIndexes(extAddress, mFilterIndexes); } else { mFilterIndexes = *aFilterIndexes; } } mHandler = aCallback; mHandlerContext = aContext; mShouldRestorePanId = false; mScanChannels = Get().GetSupportedChannelMask(); if (!aScanChannels.IsEmpty()) { mScanChannels.Intersect(aScanChannels); } VerifyOrExit((message = Get().NewMleMessage(Mle::kCommandDiscoveryRequest)) != nullptr, error = kErrorNoBufs); message->SetPanId(aPanId); // Prepare sub-TLV MeshCoP Discovery Request. discoveryRequest.Init(); discoveryRequest.SetVersion(kThreadVersion); discoveryRequest.SetJoiner(aJoiner); if (mAdvDataLength != 0) { // Prepare sub-TLV MeshCoP Joiner Advertisement. joinerAdvertisement.Init(); joinerAdvertisement.SetOui(mOui); joinerAdvertisement.SetAdvData(mAdvData, mAdvDataLength); } // Append Discovery TLV with one or two sub-TLVs. tlv.SetType(Tlv::kDiscovery); tlv.SetLength( static_cast(discoveryRequest.GetSize() + ((mAdvDataLength != 0) ? joinerAdvertisement.GetSize() : 0))); SuccessOrExit(error = message->Append(tlv)); SuccessOrExit(error = discoveryRequest.AppendTo(*message)); if (mAdvDataLength != 0) { SuccessOrExit(error = joinerAdvertisement.AppendTo(*message)); } destination.SetToLinkLocalAllRoutersMulticast(); SuccessOrExit(error = message->SendTo(destination)); if ((aPanId == Mac::kPanIdBroadcast) && (Get().GetPanId() == Mac::kPanIdBroadcast)) { // In case a specific PAN ID of a Thread Network to be // discovered is not known, Discovery Request messages MUST // have the Destination PAN ID in the IEEE 802.15.4 MAC // header set to be the Broadcast PAN ID (0xffff) and the // Source PAN ID set to a randomly generated value. Get().SetPanId(Mac::GenerateRandomPanId()); mShouldRestorePanId = true; } mScanChannel = Mac::ChannelMask::kChannelIteratorFirst; mState = (mScanChannels.GetNextChannel(mScanChannel) == kErrorNone) ? kStateScanning : kStateScanDone; Mle::Log(Mle::kMessageSend, Mle::kTypeDiscoveryRequest, destination); exit: FreeMessageOnError(message, error); return error; } Error DiscoverScanner::SetJoinerAdvertisement(uint32_t aOui, const uint8_t *aAdvData, uint8_t aAdvDataLength) { Error error = kErrorNone; VerifyOrExit((aAdvData != nullptr) && (aAdvDataLength != 0) && (aAdvDataLength <= MeshCoP::JoinerAdvertisementTlv::kAdvDataMaxLength) && (aOui <= kMaxOui), error = kErrorInvalidArgs); mOui = aOui; mAdvDataLength = aAdvDataLength; memcpy(mAdvData, aAdvData, aAdvDataLength); exit: return error; } Mac::TxFrame *DiscoverScanner::PrepareDiscoveryRequestFrame(Mac::TxFrame &aFrame) { Mac::TxFrame *frame = &aFrame; switch (mState) { case kStateIdle: case kStateScanDone: // If scan is finished (no more channels to scan), abort the // Discovery Request frame tx. The handler callback is invoked & // state is cleared from `HandleDiscoveryRequestFrameTxDone()`. frame = nullptr; break; case kStateScanning: frame->SetChannel(mScanChannel); IgnoreError(Get().SetTemporaryChannel(mScanChannel)); break; } return frame; } void DiscoverScanner::HandleDiscoveryRequestFrameTxDone(Message &aMessage) { switch (mState) { case kStateIdle: break; case kStateScanning: // Mark the Discovery Request message for direct tx to ensure it // is not dequeued and freed by `MeshForwarder` and is ready for // the next scan channel. Also pause message tx on `MeshForwarder` // while listening to receive Discovery Responses. aMessage.SetDirectTransmission(); aMessage.SetTimestampToNow(); Get().PauseMessageTransmissions(); mTimer.Start(kDefaultScanDuration); break; case kStateScanDone: HandleDiscoverComplete(); break; } } void DiscoverScanner::HandleDiscoverComplete(void) { switch (mState) { case kStateIdle: break; case kStateScanning: mTimer.Stop(); Get().ResumeMessageTransmissions(); OT_FALL_THROUGH; case kStateScanDone: Get().ClearTemporaryChannel(); if (mShouldRestorePanId) { Get().SetPanId(Mac::kPanIdBroadcast); mShouldRestorePanId = false; } mState = kStateIdle; if (mHandler) { mHandler(nullptr, mHandlerContext); } break; } } void DiscoverScanner::HandleTimer(Timer &aTimer) { aTimer.Get().HandleTimer(); } void DiscoverScanner::HandleTimer(void) { VerifyOrExit(mState == kStateScanning); // Move to next scan channel and resume message transmissions on // `MeshForwarder` so that the queued MLE Discovery Request message // is prepared again for the next scan channel. If no more channel // to scan, change the state to `kStateScanDone` which ensures the // frame tx is aborted from `PrepareDiscoveryRequestFrame()` and // then wraps up the scan (invoking handler callback). if (mScanChannels.GetNextChannel(mScanChannel) != kErrorNone) { mState = kStateScanDone; } Get().ResumeMessageTransmissions(); exit: return; } void DiscoverScanner::HandleDiscoveryResponse(Mle::RxInfo &aRxInfo) const { Error error = kErrorNone; const ThreadLinkInfo * linkInfo = aRxInfo.mMessageInfo.GetThreadLinkInfo(); Tlv tlv; MeshCoP::Tlv meshcopTlv; MeshCoP::DiscoveryResponseTlv discoveryResponse; MeshCoP::NetworkNameTlv networkName; ScanResult result; uint16_t offset; uint16_t end; bool didCheckSteeringData = false; Mle::Log(Mle::kMessageReceive, Mle::kTypeDiscoveryResponse, aRxInfo.mMessageInfo.GetPeerAddr()); VerifyOrExit(mState == kStateScanning, error = kErrorDrop); // Find MLE Discovery TLV VerifyOrExit(Tlv::FindTlvOffset(aRxInfo.mMessage, Tlv::kDiscovery, offset) == kErrorNone, error = kErrorParse); IgnoreError(aRxInfo.mMessage.Read(offset, tlv)); offset += sizeof(tlv); end = offset + tlv.GetLength(); memset(&result, 0, sizeof(result)); result.mDiscover = true; result.mPanId = linkInfo->mPanId; result.mChannel = linkInfo->mChannel; result.mRssi = linkInfo->mRss; result.mLqi = linkInfo->mLqi; aRxInfo.mMessageInfo.GetPeerAddr().GetIid().ConvertToExtAddress(AsCoreType(&result.mExtAddress)); // Process MeshCoP TLVs while (offset < end) { IgnoreError(aRxInfo.mMessage.Read(offset, meshcopTlv)); switch (meshcopTlv.GetType()) { case MeshCoP::Tlv::kDiscoveryResponse: IgnoreError(aRxInfo.mMessage.Read(offset, discoveryResponse)); VerifyOrExit(discoveryResponse.IsValid(), error = kErrorParse); result.mVersion = discoveryResponse.GetVersion(); result.mIsNative = discoveryResponse.IsNativeCommissioner(); break; case MeshCoP::Tlv::kExtendedPanId: SuccessOrExit(error = Tlv::Read(aRxInfo.mMessage, offset, AsCoreType(&result.mExtendedPanId))); break; case MeshCoP::Tlv::kNetworkName: IgnoreError(aRxInfo.mMessage.Read(offset, networkName)); if (networkName.IsValid()) { IgnoreError(AsCoreType(&result.mNetworkName).Set(networkName.GetNetworkName())); } break; case MeshCoP::Tlv::kSteeringData: if (meshcopTlv.GetLength() > 0) { MeshCoP::SteeringData &steeringData = AsCoreType(&result.mSteeringData); uint8_t dataLength = MeshCoP::SteeringData::kMaxLength; if (meshcopTlv.GetLength() < dataLength) { dataLength = meshcopTlv.GetLength(); } steeringData.Init(dataLength); SuccessOrExit(error = Tlv::ReadTlv(aRxInfo.mMessage, offset, steeringData.GetData(), dataLength)); if (mEnableFiltering) { VerifyOrExit(steeringData.Contains(mFilterIndexes)); } didCheckSteeringData = true; } break; case MeshCoP::Tlv::kJoinerUdpPort: SuccessOrExit(error = Tlv::Read(aRxInfo.mMessage, offset, result.mJoinerUdpPort)); break; default: break; } offset += sizeof(meshcopTlv) + meshcopTlv.GetLength(); } VerifyOrExit(!mEnableFiltering || didCheckSteeringData); if (mHandler) { mHandler(&result, mHandlerContext); } exit: Mle::LogProcessError(Mle::kTypeDiscoveryResponse, error); } } // namespace Mle } // namespace ot