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 "instance/instance.hpp"
37
38 namespace ot {
39 namespace Mle {
40
DiscoverScanner(Instance & aInstance)41 DiscoverScanner::DiscoverScanner(Instance &aInstance)
42 : InstanceLocator(aInstance)
43 , mScanDoneTask(aInstance)
44 , mTimer(aInstance)
45 , mFilterIndexes()
46 , mState(kStateIdle)
47 , mScanChannel(0)
48 , mAdvDataLength(0)
49 , mEnableFiltering(false)
50 , mShouldRestorePanId(false)
51 {
52 }
53
Discover(const Mac::ChannelMask & aScanChannels,uint16_t aPanId,bool aJoiner,bool aEnableFiltering,const FilterIndexes * aFilterIndexes,Handler aCallback,void * aContext)54 Error DiscoverScanner::Discover(const Mac::ChannelMask &aScanChannels,
55 uint16_t aPanId,
56 bool aJoiner,
57 bool aEnableFiltering,
58 const FilterIndexes *aFilterIndexes,
59 Handler aCallback,
60 void *aContext)
61 {
62 Error error = kErrorNone;
63 Mle::TxMessage *message = nullptr;
64 Tlv tlv;
65 Ip6::Address destination;
66 MeshCoP::DiscoveryRequestTlv discoveryRequest;
67 MeshCoP::JoinerAdvertisementTlv joinerAdvertisement;
68
69 VerifyOrExit(Get<ThreadNetif>().IsUp(), error = kErrorInvalidState);
70
71 VerifyOrExit(mState == kStateIdle, error = kErrorBusy);
72
73 mEnableFiltering = aEnableFiltering;
74
75 if (mEnableFiltering)
76 {
77 if (aFilterIndexes == nullptr)
78 {
79 Mac::ExtAddress extAddress;
80
81 Get<Radio>().GetIeeeEui64(extAddress);
82 MeshCoP::ComputeJoinerId(extAddress, extAddress);
83 MeshCoP::SteeringData::CalculateHashBitIndexes(extAddress, mFilterIndexes);
84 }
85 else
86 {
87 mFilterIndexes = *aFilterIndexes;
88 }
89 }
90
91 mCallback.Set(aCallback, aContext);
92 mShouldRestorePanId = false;
93 mScanChannels = Get<Mac::Mac>().GetSupportedChannelMask();
94
95 if (!aScanChannels.IsEmpty())
96 {
97 mScanChannels.Intersect(aScanChannels);
98 }
99
100 VerifyOrExit((message = Get<Mle>().NewMleMessage(kCommandDiscoveryRequest)) != nullptr, error = kErrorNoBufs);
101 message->SetPanId(aPanId);
102
103 // Prepare sub-TLV MeshCoP Discovery Request.
104 discoveryRequest.Init();
105 discoveryRequest.SetVersion(kThreadVersion);
106 discoveryRequest.SetJoiner(aJoiner);
107
108 if (mAdvDataLength != 0)
109 {
110 // Prepare sub-TLV MeshCoP Joiner Advertisement.
111 joinerAdvertisement.Init();
112 joinerAdvertisement.SetOui(mOui);
113 joinerAdvertisement.SetAdvData(mAdvData, mAdvDataLength);
114 }
115
116 // Append Discovery TLV with one or two sub-TLVs.
117 tlv.SetType(Tlv::kDiscovery);
118 tlv.SetLength(
119 static_cast<uint8_t>(discoveryRequest.GetSize() + ((mAdvDataLength != 0) ? joinerAdvertisement.GetSize() : 0)));
120
121 SuccessOrExit(error = message->Append(tlv));
122 SuccessOrExit(error = discoveryRequest.AppendTo(*message));
123
124 if (mAdvDataLength != 0)
125 {
126 SuccessOrExit(error = joinerAdvertisement.AppendTo(*message));
127 }
128
129 destination.SetToLinkLocalAllRoutersMulticast();
130
131 SuccessOrExit(error = message->SendTo(destination));
132
133 if ((aPanId == Mac::kPanIdBroadcast) && (Get<Mac::Mac>().GetPanId() == Mac::kPanIdBroadcast))
134 {
135 // In case a specific PAN ID of a Thread Network to be
136 // discovered is not known, Discovery Request messages MUST
137 // have the Destination PAN ID in the IEEE 802.15.4 MAC
138 // header set to be the Broadcast PAN ID (0xffff) and the
139 // Source PAN ID set to a randomly generated value.
140
141 Get<Mac::Mac>().SetPanId(Mac::GenerateRandomPanId());
142 mShouldRestorePanId = true;
143 }
144
145 mScanChannel = Mac::ChannelMask::kChannelIteratorFirst;
146 mState = (mScanChannels.GetNextChannel(mScanChannel) == kErrorNone) ? kStateScanning : kStateScanDone;
147
148 // For rx-off-when-idle device, temporarily enable receiver during discovery procedure.
149 if (!Get<Mle>().IsDisabled() && !Get<Mle>().IsRxOnWhenIdle())
150 {
151 Get<MeshForwarder>().SetRxOnWhenIdle(true);
152 }
153
154 Mle::Log(Mle::kMessageSend, Mle::kTypeDiscoveryRequest, destination);
155
156 exit:
157 FreeMessageOnError(message, error);
158 return error;
159 }
160
SetJoinerAdvertisement(uint32_t aOui,const uint8_t * aAdvData,uint8_t aAdvDataLength)161 Error DiscoverScanner::SetJoinerAdvertisement(uint32_t aOui, const uint8_t *aAdvData, uint8_t aAdvDataLength)
162 {
163 Error error = kErrorNone;
164
165 VerifyOrExit((aAdvData != nullptr) && (aAdvDataLength != 0) &&
166 (aAdvDataLength <= MeshCoP::JoinerAdvertisementTlv::kAdvDataMaxLength) && (aOui <= kMaxOui),
167 error = kErrorInvalidArgs);
168
169 mOui = aOui;
170 mAdvDataLength = aAdvDataLength;
171
172 memcpy(mAdvData, aAdvData, aAdvDataLength);
173
174 exit:
175 return error;
176 }
177
PrepareDiscoveryRequestFrame(Mac::TxFrame & aFrame)178 Mac::TxFrame *DiscoverScanner::PrepareDiscoveryRequestFrame(Mac::TxFrame &aFrame)
179 {
180 Mac::TxFrame *frame = &aFrame;
181
182 switch (mState)
183 {
184 case kStateIdle:
185 case kStateScanDone:
186 // If scan is finished (no more channels to scan), abort the
187 // Discovery Request frame tx. The handler callback is invoked &
188 // state is cleared from `HandleDiscoveryRequestFrameTxDone()`.
189 frame = nullptr;
190 break;
191
192 case kStateScanning:
193 frame->SetChannel(mScanChannel);
194 IgnoreError(Get<Mac::Mac>().SetTemporaryChannel(mScanChannel));
195 break;
196 }
197
198 return frame;
199 }
200
HandleDiscoveryRequestFrameTxDone(Message & aMessage,Error aError)201 void DiscoverScanner::HandleDiscoveryRequestFrameTxDone(Message &aMessage, Error aError)
202 {
203 switch (mState)
204 {
205 case kStateIdle:
206 break;
207
208 case kStateScanning:
209 if ((aError == kErrorNone) || (aError == kErrorChannelAccessFailure))
210 {
211 // Mark the Discovery Request message for direct tx to ensure it
212 // is not dequeued and freed by `MeshForwarder` and is ready for
213 // the next scan channel. Also pause message tx on `MeshForwarder`
214 // while listening to receive Discovery Responses.
215 aMessage.SetDirectTransmission();
216 aMessage.SetTimestampToNow();
217 Get<MeshForwarder>().PauseMessageTransmissions();
218 mTimer.Start(kDefaultScanDuration);
219 break;
220 }
221
222 // If we encounter other error failures (e.g., `kErrorDrop` due
223 // to queue management dropping the message or if message being
224 // evicted), `aMessage` may be immediately freed. This prevents
225 // us from reusing it to request a scan on the next scan channel.
226 // As a result, we stop the scan operation in such cases.
227
228 mState = kStateScanDone;
229
230 OT_FALL_THROUGH;
231
232 case kStateScanDone:
233 HandleDiscoverComplete();
234 break;
235 }
236 }
237
HandleDiscoverComplete(void)238 void DiscoverScanner::HandleDiscoverComplete(void)
239 {
240 // Restore Data Polling or CSL for rx-off-when-idle device.
241 if (!Get<Mle>().IsDisabled() && !Get<Mle>().IsRxOnWhenIdle())
242 {
243 Get<MeshForwarder>().SetRxOnWhenIdle(false);
244 }
245
246 switch (mState)
247 {
248 case kStateIdle:
249 break;
250
251 case kStateScanning:
252 mTimer.Stop();
253 Get<MeshForwarder>().ResumeMessageTransmissions();
254
255 OT_FALL_THROUGH;
256
257 case kStateScanDone:
258 Get<Mac::Mac>().ClearTemporaryChannel();
259
260 if (mShouldRestorePanId)
261 {
262 Get<Mac::Mac>().SetPanId(Mac::kPanIdBroadcast);
263 mShouldRestorePanId = false;
264 }
265
266 // Post the tasklet to change `mState` and invoke handler
267 // callback. This allows users to safely call OT APIs from
268 // the callback.
269 mScanDoneTask.Post();
270 break;
271 }
272 }
273
HandleScanDoneTask(void)274 void DiscoverScanner::HandleScanDoneTask(void)
275 {
276 mState = kStateIdle;
277 mCallback.InvokeIfSet(nullptr);
278 }
279
HandleTimer(void)280 void DiscoverScanner::HandleTimer(void)
281 {
282 VerifyOrExit(mState == kStateScanning);
283
284 // Move to next scan channel and resume message transmissions on
285 // `MeshForwarder` so that the queued MLE Discovery Request message
286 // is prepared again for the next scan channel. If no more channel
287 // to scan, change the state to `kStateScanDone` which ensures the
288 // frame tx is aborted from `PrepareDiscoveryRequestFrame()` and
289 // then wraps up the scan (invoking handler callback).
290
291 if (mScanChannels.GetNextChannel(mScanChannel) != kErrorNone)
292 {
293 mState = kStateScanDone;
294 }
295
296 Get<MeshForwarder>().ResumeMessageTransmissions();
297
298 exit:
299 return;
300 }
301
HandleDiscoveryResponse(Mle::RxInfo & aRxInfo) const302 void DiscoverScanner::HandleDiscoveryResponse(Mle::RxInfo &aRxInfo) const
303 {
304 Error error = kErrorNone;
305 MeshCoP::DiscoveryResponseTlv discoveryResponse;
306 ScanResult result;
307 OffsetRange offsetRange;
308 Tlv::ParsedInfo tlvInfo;
309 bool didCheckSteeringData = false;
310
311 Mle::Log(Mle::kMessageReceive, Mle::kTypeDiscoveryResponse, aRxInfo.mMessageInfo.GetPeerAddr());
312
313 VerifyOrExit(mState == kStateScanning, error = kErrorDrop);
314
315 // Find MLE Discovery TLV
316 SuccessOrExit(error = Tlv::FindTlvValueOffsetRange(aRxInfo.mMessage, Tlv::kDiscovery, offsetRange));
317
318 ClearAllBytes(result);
319 result.mDiscover = true;
320 result.mPanId = aRxInfo.mMessage.GetPanId();
321 result.mChannel = aRxInfo.mMessage.GetChannel();
322 result.mRssi = aRxInfo.mMessage.GetAverageRss();
323 result.mLqi = aRxInfo.mMessage.GetAverageLqi();
324
325 aRxInfo.mMessageInfo.GetPeerAddr().GetIid().ConvertToExtAddress(AsCoreType(&result.mExtAddress));
326
327 for (; !offsetRange.IsEmpty(); offsetRange.AdvanceOffset(tlvInfo.GetSize()))
328 {
329 SuccessOrExit(error = tlvInfo.ParseFrom(aRxInfo.mMessage, offsetRange));
330
331 if (tlvInfo.mIsExtended)
332 {
333 continue;
334 }
335
336 switch (tlvInfo.mType)
337 {
338 case MeshCoP::Tlv::kDiscoveryResponse:
339 SuccessOrExit(error = aRxInfo.mMessage.Read(offsetRange, discoveryResponse));
340 VerifyOrExit(discoveryResponse.IsValid(), error = kErrorParse);
341 result.mVersion = discoveryResponse.GetVersion();
342 result.mIsNative = discoveryResponse.IsNativeCommissioner();
343 break;
344
345 case MeshCoP::Tlv::kExtendedPanId:
346 SuccessOrExit(error = Tlv::Read<MeshCoP::ExtendedPanIdTlv>(aRxInfo.mMessage, offsetRange.GetOffset(),
347 AsCoreType(&result.mExtendedPanId)));
348 break;
349
350 case MeshCoP::Tlv::kNetworkName:
351 SuccessOrExit(error = Tlv::Read<MeshCoP::NetworkNameTlv>(aRxInfo.mMessage, offsetRange.GetOffset(),
352 result.mNetworkName.m8));
353 break;
354
355 case MeshCoP::Tlv::kSteeringData:
356 if (!tlvInfo.mValueOffsetRange.IsEmpty())
357 {
358 MeshCoP::SteeringData &steeringData = AsCoreType(&result.mSteeringData);
359 OffsetRange valueOffsetRange = tlvInfo.mValueOffsetRange;
360
361 valueOffsetRange.ShrinkLength(MeshCoP::SteeringData::kMaxLength);
362 steeringData.Init(static_cast<uint8_t>(valueOffsetRange.GetLength()));
363 aRxInfo.mMessage.ReadBytes(valueOffsetRange, steeringData.GetData());
364
365 if (mEnableFiltering)
366 {
367 VerifyOrExit(steeringData.Contains(mFilterIndexes));
368 }
369
370 didCheckSteeringData = true;
371 }
372 break;
373
374 case MeshCoP::Tlv::kJoinerUdpPort:
375 SuccessOrExit(error = Tlv::Read<MeshCoP::JoinerUdpPortTlv>(aRxInfo.mMessage, offsetRange.GetOffset(),
376 result.mJoinerUdpPort));
377 break;
378
379 default:
380 break;
381 }
382 }
383
384 VerifyOrExit(!mEnableFiltering || didCheckSteeringData);
385
386 mCallback.InvokeIfSet(&result);
387
388 exit:
389 Mle::LogProcessError(Mle::kTypeDiscoveryResponse, error);
390 }
391
392 } // namespace Mle
393 } // namespace ot
394