blob: bf6d60f44050a6243f94c903ae3a7ab91bbec94f [file] [log] [blame]
/*
* 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<ThreadNetif>().IsUp(), error = kErrorInvalidState);
VerifyOrExit(mState == kStateIdle, error = kErrorBusy);
mEnableFiltering = aEnableFiltering;
if (mEnableFiltering)
{
if (aFilterIndexes == nullptr)
{
Mac::ExtAddress extAddress;
Get<Radio>().GetIeeeEui64(extAddress);
MeshCoP::ComputeJoinerId(extAddress, extAddress);
MeshCoP::SteeringData::CalculateHashBitIndexes(extAddress, mFilterIndexes);
}
else
{
mFilterIndexes = *aFilterIndexes;
}
}
mHandler = aCallback;
mHandlerContext = aContext;
mShouldRestorePanId = false;
mScanChannels = Get<Mac::Mac>().GetSupportedChannelMask();
if (!aScanChannels.IsEmpty())
{
mScanChannels.Intersect(aScanChannels);
}
VerifyOrExit((message = Get<Mle>().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<uint8_t>(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<Mac::Mac>().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<Mac::Mac>().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<Mac::Mac>().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<MeshForwarder>().PauseMessageTransmissions();
mTimer.Start(kDefaultScanDuration);
break;
case kStateScanDone:
HandleDiscoverComplete();
break;
}
}
void DiscoverScanner::HandleDiscoverComplete(void)
{
switch (mState)
{
case kStateIdle:
break;
case kStateScanning:
mTimer.Stop();
Get<MeshForwarder>().ResumeMessageTransmissions();
OT_FALL_THROUGH;
case kStateScanDone:
Get<Mac::Mac>().ClearTemporaryChannel();
if (mShouldRestorePanId)
{
Get<Mac::Mac>().SetPanId(Mac::kPanIdBroadcast);
mShouldRestorePanId = false;
}
mState = kStateIdle;
if (mHandler)
{
mHandler(nullptr, mHandlerContext);
}
break;
}
}
void DiscoverScanner::HandleTimer(Timer &aTimer)
{
aTimer.Get<DiscoverScanner>().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<MeshForwarder>().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<MeshCoP::ExtendedPanIdTlv>(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<MeshCoP::JoinerUdpPortTlv>(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