1 /*
2  *  Copyright (c) 2016-2020, The OpenThread Authors.
3  *  All rights reserved.
4  *
5  *  Redistribution and use in source and binary forms, with or without
6  *  modification, are permitted provided that the following conditions are met:
7  *  1. Redistributions of source code must retain the above copyright
8  *     notice, this list of conditions and the following disclaimer.
9  *  2. Redistributions in binary form must reproduce the above copyright
10  *     notice, this list of conditions and the following disclaimer in the
11  *     documentation and/or other materials provided with the distribution.
12  *  3. Neither the name of the copyright holder nor the
13  *     names of its contributors may be used to endorse or promote products
14  *     derived from this software without specific prior written permission.
15  *
16  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20  *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  *  POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 /**
30  * @file
31  *   This file implements MLE Discover Scan process.
32  */
33 
34 #include "discover_scanner.hpp"
35 
36 #include "common/as_core_type.hpp"
37 #include "common/code_utils.hpp"
38 #include "common/instance.hpp"
39 #include "common/locator_getters.hpp"
40 #include "thread/mesh_forwarder.hpp"
41 #include "thread/mle.hpp"
42 #include "thread/mle_router.hpp"
43 #include "thread/version.hpp"
44 
45 namespace ot {
46 namespace Mle {
47 
DiscoverScanner(Instance & aInstance)48 DiscoverScanner::DiscoverScanner(Instance &aInstance)
49     : InstanceLocator(aInstance)
50     , mScanDoneTask(aInstance)
51     , mTimer(aInstance)
52     , mFilterIndexes()
53     , mState(kStateIdle)
54     , mScanChannel(0)
55     , mAdvDataLength(0)
56     , mEnableFiltering(false)
57     , mShouldRestorePanId(false)
58 {
59 }
60 
Discover(const Mac::ChannelMask & aScanChannels,uint16_t aPanId,bool aJoiner,bool aEnableFiltering,const FilterIndexes * aFilterIndexes,Handler aCallback,void * aContext)61 Error DiscoverScanner::Discover(const Mac::ChannelMask &aScanChannels,
62                                 uint16_t                aPanId,
63                                 bool                    aJoiner,
64                                 bool                    aEnableFiltering,
65                                 const FilterIndexes    *aFilterIndexes,
66                                 Handler                 aCallback,
67                                 void                   *aContext)
68 {
69     Error                           error   = kErrorNone;
70     Mle::TxMessage                 *message = nullptr;
71     Tlv                             tlv;
72     Ip6::Address                    destination;
73     MeshCoP::DiscoveryRequestTlv    discoveryRequest;
74     MeshCoP::JoinerAdvertisementTlv joinerAdvertisement;
75 
76     VerifyOrExit(Get<ThreadNetif>().IsUp(), error = kErrorInvalidState);
77 
78     VerifyOrExit(mState == kStateIdle, error = kErrorBusy);
79 
80     mEnableFiltering = aEnableFiltering;
81 
82     if (mEnableFiltering)
83     {
84         if (aFilterIndexes == nullptr)
85         {
86             Mac::ExtAddress extAddress;
87 
88             Get<Radio>().GetIeeeEui64(extAddress);
89             MeshCoP::ComputeJoinerId(extAddress, extAddress);
90             MeshCoP::SteeringData::CalculateHashBitIndexes(extAddress, mFilterIndexes);
91         }
92         else
93         {
94             mFilterIndexes = *aFilterIndexes;
95         }
96     }
97 
98     mCallback.Set(aCallback, aContext);
99     mShouldRestorePanId = false;
100     mScanChannels       = Get<Mac::Mac>().GetSupportedChannelMask();
101 
102     if (!aScanChannels.IsEmpty())
103     {
104         mScanChannels.Intersect(aScanChannels);
105     }
106 
107     VerifyOrExit((message = Get<Mle>().NewMleMessage(Mle::kCommandDiscoveryRequest)) != nullptr, error = kErrorNoBufs);
108     message->SetPanId(aPanId);
109 
110     // Prepare sub-TLV MeshCoP Discovery Request.
111     discoveryRequest.Init();
112     discoveryRequest.SetVersion(kThreadVersion);
113     discoveryRequest.SetJoiner(aJoiner);
114 
115     if (mAdvDataLength != 0)
116     {
117         // Prepare sub-TLV MeshCoP Joiner Advertisement.
118         joinerAdvertisement.Init();
119         joinerAdvertisement.SetOui(mOui);
120         joinerAdvertisement.SetAdvData(mAdvData, mAdvDataLength);
121     }
122 
123     // Append Discovery TLV with one or two sub-TLVs.
124     tlv.SetType(Tlv::kDiscovery);
125     tlv.SetLength(
126         static_cast<uint8_t>(discoveryRequest.GetSize() + ((mAdvDataLength != 0) ? joinerAdvertisement.GetSize() : 0)));
127 
128     SuccessOrExit(error = message->Append(tlv));
129     SuccessOrExit(error = discoveryRequest.AppendTo(*message));
130 
131     if (mAdvDataLength != 0)
132     {
133         SuccessOrExit(error = joinerAdvertisement.AppendTo(*message));
134     }
135 
136     destination.SetToLinkLocalAllRoutersMulticast();
137 
138     SuccessOrExit(error = message->SendTo(destination));
139 
140     if ((aPanId == Mac::kPanIdBroadcast) && (Get<Mac::Mac>().GetPanId() == Mac::kPanIdBroadcast))
141     {
142         // In case a specific PAN ID of a Thread Network to be
143         // discovered is not known, Discovery Request messages MUST
144         // have the Destination PAN ID in the IEEE 802.15.4 MAC
145         // header set to be the Broadcast PAN ID (0xffff) and the
146         // Source PAN ID set to a randomly generated value.
147 
148         Get<Mac::Mac>().SetPanId(Mac::GenerateRandomPanId());
149         mShouldRestorePanId = true;
150     }
151 
152     mScanChannel = Mac::ChannelMask::kChannelIteratorFirst;
153     mState       = (mScanChannels.GetNextChannel(mScanChannel) == kErrorNone) ? kStateScanning : kStateScanDone;
154 
155     // For rx-off-when-idle device, temporarily enable receiver during discovery procedure.
156     if (!Get<Mle>().IsDisabled() && !Get<Mle>().IsRxOnWhenIdle())
157     {
158         Get<MeshForwarder>().SetRxOnWhenIdle(true);
159     }
160 
161     Mle::Log(Mle::kMessageSend, Mle::kTypeDiscoveryRequest, destination);
162 
163 exit:
164     FreeMessageOnError(message, error);
165     return error;
166 }
167 
SetJoinerAdvertisement(uint32_t aOui,const uint8_t * aAdvData,uint8_t aAdvDataLength)168 Error DiscoverScanner::SetJoinerAdvertisement(uint32_t aOui, const uint8_t *aAdvData, uint8_t aAdvDataLength)
169 {
170     Error error = kErrorNone;
171 
172     VerifyOrExit((aAdvData != nullptr) && (aAdvDataLength != 0) &&
173                      (aAdvDataLength <= MeshCoP::JoinerAdvertisementTlv::kAdvDataMaxLength) && (aOui <= kMaxOui),
174                  error = kErrorInvalidArgs);
175 
176     mOui           = aOui;
177     mAdvDataLength = aAdvDataLength;
178 
179     memcpy(mAdvData, aAdvData, aAdvDataLength);
180 
181 exit:
182     return error;
183 }
184 
PrepareDiscoveryRequestFrame(Mac::TxFrame & aFrame)185 Mac::TxFrame *DiscoverScanner::PrepareDiscoveryRequestFrame(Mac::TxFrame &aFrame)
186 {
187     Mac::TxFrame *frame = &aFrame;
188 
189     switch (mState)
190     {
191     case kStateIdle:
192     case kStateScanDone:
193         // If scan is finished (no more channels to scan), abort the
194         // Discovery Request frame tx. The handler callback is invoked &
195         // state is cleared from `HandleDiscoveryRequestFrameTxDone()`.
196         frame = nullptr;
197         break;
198 
199     case kStateScanning:
200         frame->SetChannel(mScanChannel);
201         IgnoreError(Get<Mac::Mac>().SetTemporaryChannel(mScanChannel));
202         break;
203     }
204 
205     return frame;
206 }
207 
HandleDiscoveryRequestFrameTxDone(Message & aMessage)208 void DiscoverScanner::HandleDiscoveryRequestFrameTxDone(Message &aMessage)
209 {
210     switch (mState)
211     {
212     case kStateIdle:
213         break;
214 
215     case kStateScanning:
216         // Mark the Discovery Request message for direct tx to ensure it
217         // is not dequeued and freed by `MeshForwarder` and is ready for
218         // the next scan channel. Also pause message tx on `MeshForwarder`
219         // while listening to receive Discovery Responses.
220         aMessage.SetDirectTransmission();
221         aMessage.SetTimestampToNow();
222         Get<MeshForwarder>().PauseMessageTransmissions();
223         mTimer.Start(kDefaultScanDuration);
224         break;
225 
226     case kStateScanDone:
227         HandleDiscoverComplete();
228         break;
229     }
230 }
231 
HandleDiscoverComplete(void)232 void DiscoverScanner::HandleDiscoverComplete(void)
233 {
234     // Restore Data Polling or CSL for rx-off-when-idle device.
235     if (!Get<Mle>().IsDisabled() && !Get<Mle>().IsRxOnWhenIdle())
236     {
237         Get<MeshForwarder>().SetRxOnWhenIdle(false);
238     }
239 
240     switch (mState)
241     {
242     case kStateIdle:
243         break;
244 
245     case kStateScanning:
246         mTimer.Stop();
247         Get<MeshForwarder>().ResumeMessageTransmissions();
248 
249         OT_FALL_THROUGH;
250 
251     case kStateScanDone:
252         Get<Mac::Mac>().ClearTemporaryChannel();
253 
254         if (mShouldRestorePanId)
255         {
256             Get<Mac::Mac>().SetPanId(Mac::kPanIdBroadcast);
257             mShouldRestorePanId = false;
258         }
259 
260         // Post the tasklet to change `mState` and invoke handler
261         // callback. This allows users to safely call OT APIs from
262         // the callback.
263         mScanDoneTask.Post();
264         break;
265     }
266 }
267 
HandleScanDoneTask(void)268 void DiscoverScanner::HandleScanDoneTask(void)
269 {
270     mState = kStateIdle;
271     mCallback.InvokeIfSet(nullptr);
272 }
273 
HandleTimer(void)274 void DiscoverScanner::HandleTimer(void)
275 {
276     VerifyOrExit(mState == kStateScanning);
277 
278     // Move to next scan channel and resume message transmissions on
279     // `MeshForwarder` so that the queued MLE Discovery Request message
280     // is prepared again for the next scan channel. If no more channel
281     // to scan, change the state to `kStateScanDone` which ensures the
282     // frame tx is aborted  from `PrepareDiscoveryRequestFrame()` and
283     // then wraps up the scan (invoking handler callback).
284 
285     if (mScanChannels.GetNextChannel(mScanChannel) != kErrorNone)
286     {
287         mState = kStateScanDone;
288     }
289 
290     Get<MeshForwarder>().ResumeMessageTransmissions();
291 
292 exit:
293     return;
294 }
295 
HandleDiscoveryResponse(Mle::RxInfo & aRxInfo) const296 void DiscoverScanner::HandleDiscoveryResponse(Mle::RxInfo &aRxInfo) const
297 {
298     Error                         error    = kErrorNone;
299     const ThreadLinkInfo         *linkInfo = aRxInfo.mMessageInfo.GetThreadLinkInfo();
300     MeshCoP::Tlv                  meshcopTlv;
301     MeshCoP::DiscoveryResponseTlv discoveryResponse;
302     MeshCoP::NetworkNameTlv       networkName;
303     ScanResult                    result;
304     uint16_t                      offset;
305     uint16_t                      end;
306     bool                          didCheckSteeringData = false;
307 
308     Mle::Log(Mle::kMessageReceive, Mle::kTypeDiscoveryResponse, aRxInfo.mMessageInfo.GetPeerAddr());
309 
310     VerifyOrExit(mState == kStateScanning, error = kErrorDrop);
311 
312     // Find MLE Discovery TLV
313     SuccessOrExit(error = Tlv::FindTlvValueStartEndOffsets(aRxInfo.mMessage, Tlv::kDiscovery, offset, end));
314 
315     memset(&result, 0, sizeof(result));
316     result.mDiscover = true;
317     result.mPanId    = linkInfo->mPanId;
318     result.mChannel  = linkInfo->mChannel;
319     result.mRssi     = linkInfo->mRss;
320     result.mLqi      = linkInfo->mLqi;
321 
322     aRxInfo.mMessageInfo.GetPeerAddr().GetIid().ConvertToExtAddress(AsCoreType(&result.mExtAddress));
323 
324     // Process MeshCoP TLVs
325     while (offset < end)
326     {
327         IgnoreError(aRxInfo.mMessage.Read(offset, meshcopTlv));
328 
329         switch (meshcopTlv.GetType())
330         {
331         case MeshCoP::Tlv::kDiscoveryResponse:
332             IgnoreError(aRxInfo.mMessage.Read(offset, discoveryResponse));
333             VerifyOrExit(discoveryResponse.IsValid(), error = kErrorParse);
334             result.mVersion  = discoveryResponse.GetVersion();
335             result.mIsNative = discoveryResponse.IsNativeCommissioner();
336             break;
337 
338         case MeshCoP::Tlv::kExtendedPanId:
339             SuccessOrExit(error = Tlv::Read<MeshCoP::ExtendedPanIdTlv>(aRxInfo.mMessage, offset,
340                                                                        AsCoreType(&result.mExtendedPanId)));
341             break;
342 
343         case MeshCoP::Tlv::kNetworkName:
344             IgnoreError(aRxInfo.mMessage.Read(offset, networkName));
345             if (networkName.IsValid())
346             {
347                 IgnoreError(AsCoreType(&result.mNetworkName).Set(networkName.GetNetworkName()));
348             }
349             break;
350 
351         case MeshCoP::Tlv::kSteeringData:
352             if (meshcopTlv.GetLength() > 0)
353             {
354                 MeshCoP::SteeringData &steeringData = AsCoreType(&result.mSteeringData);
355                 uint8_t                dataLength   = MeshCoP::SteeringData::kMaxLength;
356 
357                 if (meshcopTlv.GetLength() < dataLength)
358                 {
359                     dataLength = meshcopTlv.GetLength();
360                 }
361 
362                 steeringData.Init(dataLength);
363 
364                 SuccessOrExit(error = Tlv::ReadTlvValue(aRxInfo.mMessage, offset, steeringData.GetData(), dataLength));
365 
366                 if (mEnableFiltering)
367                 {
368                     VerifyOrExit(steeringData.Contains(mFilterIndexes));
369                 }
370 
371                 didCheckSteeringData = true;
372             }
373             break;
374 
375         case MeshCoP::Tlv::kJoinerUdpPort:
376             SuccessOrExit(error =
377                               Tlv::Read<MeshCoP::JoinerUdpPortTlv>(aRxInfo.mMessage, offset, result.mJoinerUdpPort));
378             break;
379 
380         default:
381             break;
382         }
383 
384         offset += sizeof(meshcopTlv) + meshcopTlv.GetLength();
385     }
386 
387     VerifyOrExit(!mEnableFiltering || didCheckSteeringData);
388 
389     mCallback.InvokeIfSet(&result);
390 
391 exit:
392     Mle::LogProcessError(Mle::kTypeDiscoveryResponse, error);
393 }
394 
395 } // namespace Mle
396 } // namespace ot
397