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/locator_getters.hpp"
39 #include "instance/instance.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,Error aError)208 void DiscoverScanner::HandleDiscoveryRequestFrameTxDone(Message &aMessage, Error aError)
209 {
210 switch (mState)
211 {
212 case kStateIdle:
213 break;
214
215 case kStateScanning:
216 if ((aError == kErrorNone) || (aError == kErrorChannelAccessFailure))
217 {
218 // Mark the Discovery Request message for direct tx to ensure it
219 // is not dequeued and freed by `MeshForwarder` and is ready for
220 // the next scan channel. Also pause message tx on `MeshForwarder`
221 // while listening to receive Discovery Responses.
222 aMessage.SetDirectTransmission();
223 aMessage.SetTimestampToNow();
224 Get<MeshForwarder>().PauseMessageTransmissions();
225 mTimer.Start(kDefaultScanDuration);
226 break;
227 }
228
229 // If we encounter other error failures (e.g., `kErrorDrop` due
230 // to queue management dropping the message or if message being
231 // evicted), `aMessage` may be immediately freed. This prevents
232 // us from reusing it to request a scan on the next scan channel.
233 // As a result, we stop the scan operation in such cases.
234
235 mState = kStateScanDone;
236
237 OT_FALL_THROUGH;
238
239 case kStateScanDone:
240 HandleDiscoverComplete();
241 break;
242 }
243 }
244
HandleDiscoverComplete(void)245 void DiscoverScanner::HandleDiscoverComplete(void)
246 {
247 // Restore Data Polling or CSL for rx-off-when-idle device.
248 if (!Get<Mle>().IsDisabled() && !Get<Mle>().IsRxOnWhenIdle())
249 {
250 Get<MeshForwarder>().SetRxOnWhenIdle(false);
251 }
252
253 switch (mState)
254 {
255 case kStateIdle:
256 break;
257
258 case kStateScanning:
259 mTimer.Stop();
260 Get<MeshForwarder>().ResumeMessageTransmissions();
261
262 OT_FALL_THROUGH;
263
264 case kStateScanDone:
265 Get<Mac::Mac>().ClearTemporaryChannel();
266
267 if (mShouldRestorePanId)
268 {
269 Get<Mac::Mac>().SetPanId(Mac::kPanIdBroadcast);
270 mShouldRestorePanId = false;
271 }
272
273 // Post the tasklet to change `mState` and invoke handler
274 // callback. This allows users to safely call OT APIs from
275 // the callback.
276 mScanDoneTask.Post();
277 break;
278 }
279 }
280
HandleScanDoneTask(void)281 void DiscoverScanner::HandleScanDoneTask(void)
282 {
283 mState = kStateIdle;
284 mCallback.InvokeIfSet(nullptr);
285 }
286
HandleTimer(void)287 void DiscoverScanner::HandleTimer(void)
288 {
289 VerifyOrExit(mState == kStateScanning);
290
291 // Move to next scan channel and resume message transmissions on
292 // `MeshForwarder` so that the queued MLE Discovery Request message
293 // is prepared again for the next scan channel. If no more channel
294 // to scan, change the state to `kStateScanDone` which ensures the
295 // frame tx is aborted from `PrepareDiscoveryRequestFrame()` and
296 // then wraps up the scan (invoking handler callback).
297
298 if (mScanChannels.GetNextChannel(mScanChannel) != kErrorNone)
299 {
300 mState = kStateScanDone;
301 }
302
303 Get<MeshForwarder>().ResumeMessageTransmissions();
304
305 exit:
306 return;
307 }
308
HandleDiscoveryResponse(Mle::RxInfo & aRxInfo) const309 void DiscoverScanner::HandleDiscoveryResponse(Mle::RxInfo &aRxInfo) const
310 {
311 Error error = kErrorNone;
312 MeshCoP::Tlv meshcopTlv;
313 MeshCoP::DiscoveryResponseTlv discoveryResponse;
314 MeshCoP::NetworkNameTlv networkName;
315 ScanResult result;
316 uint16_t offset;
317 uint16_t end;
318 bool didCheckSteeringData = false;
319
320 Mle::Log(Mle::kMessageReceive, Mle::kTypeDiscoveryResponse, aRxInfo.mMessageInfo.GetPeerAddr());
321
322 VerifyOrExit(mState == kStateScanning, error = kErrorDrop);
323
324 // Find MLE Discovery TLV
325 SuccessOrExit(error = Tlv::FindTlvValueStartEndOffsets(aRxInfo.mMessage, Tlv::kDiscovery, offset, end));
326
327 ClearAllBytes(result);
328 result.mDiscover = true;
329 result.mPanId = aRxInfo.mMessage.GetPanId();
330 result.mChannel = aRxInfo.mMessage.GetChannel();
331 result.mRssi = aRxInfo.mMessage.GetAverageRss();
332 result.mLqi = aRxInfo.mMessage.GetAverageLqi();
333
334 aRxInfo.mMessageInfo.GetPeerAddr().GetIid().ConvertToExtAddress(AsCoreType(&result.mExtAddress));
335
336 // Process MeshCoP TLVs
337 while (offset < end)
338 {
339 IgnoreError(aRxInfo.mMessage.Read(offset, meshcopTlv));
340
341 switch (meshcopTlv.GetType())
342 {
343 case MeshCoP::Tlv::kDiscoveryResponse:
344 IgnoreError(aRxInfo.mMessage.Read(offset, discoveryResponse));
345 VerifyOrExit(discoveryResponse.IsValid(), error = kErrorParse);
346 result.mVersion = discoveryResponse.GetVersion();
347 result.mIsNative = discoveryResponse.IsNativeCommissioner();
348 break;
349
350 case MeshCoP::Tlv::kExtendedPanId:
351 SuccessOrExit(error = Tlv::Read<MeshCoP::ExtendedPanIdTlv>(aRxInfo.mMessage, offset,
352 AsCoreType(&result.mExtendedPanId)));
353 break;
354
355 case MeshCoP::Tlv::kNetworkName:
356 IgnoreError(aRxInfo.mMessage.Read(offset, networkName));
357 if (networkName.IsValid())
358 {
359 IgnoreError(AsCoreType(&result.mNetworkName).Set(networkName.GetNetworkName()));
360 }
361 break;
362
363 case MeshCoP::Tlv::kSteeringData:
364 if (meshcopTlv.GetLength() > 0)
365 {
366 MeshCoP::SteeringData &steeringData = AsCoreType(&result.mSteeringData);
367 uint8_t dataLength = MeshCoP::SteeringData::kMaxLength;
368
369 if (meshcopTlv.GetLength() < dataLength)
370 {
371 dataLength = meshcopTlv.GetLength();
372 }
373
374 steeringData.Init(dataLength);
375
376 SuccessOrExit(error = Tlv::ReadTlvValue(aRxInfo.mMessage, offset, steeringData.GetData(), dataLength));
377
378 if (mEnableFiltering)
379 {
380 VerifyOrExit(steeringData.Contains(mFilterIndexes));
381 }
382
383 didCheckSteeringData = true;
384 }
385 break;
386
387 case MeshCoP::Tlv::kJoinerUdpPort:
388 SuccessOrExit(error =
389 Tlv::Read<MeshCoP::JoinerUdpPortTlv>(aRxInfo.mMessage, offset, result.mJoinerUdpPort));
390 break;
391
392 default:
393 break;
394 }
395
396 offset += sizeof(meshcopTlv) + meshcopTlv.GetLength();
397 }
398
399 VerifyOrExit(!mEnableFiltering || didCheckSteeringData);
400
401 mCallback.InvokeIfSet(&result);
402
403 exit:
404 Mle::LogProcessError(Mle::kTypeDiscoveryResponse, error);
405 }
406
407 } // namespace Mle
408 } // namespace ot
409