1 /*
2  *  Copyright (c) 2018, 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 Channel Manager.
32  *
33  */
34 
35 #include "channel_manager.hpp"
36 
37 #if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE && OPENTHREAD_FTD
38 
39 #include "common/code_utils.hpp"
40 #include "common/instance.hpp"
41 #include "common/locator_getters.hpp"
42 #include "common/logging.hpp"
43 #include "common/random.hpp"
44 #include "meshcop/dataset_updater.hpp"
45 #include "radio/radio.hpp"
46 
47 namespace ot {
48 namespace Utils {
49 
ChannelManager(Instance & aInstance)50 ChannelManager::ChannelManager(Instance &aInstance)
51     : InstanceLocator(aInstance)
52     , mSupportedChannelMask(0)
53     , mFavoredChannelMask(0)
54     , mDelay(kMinimumDelay)
55     , mChannel(0)
56     , mState(kStateIdle)
57     , mTimer(aInstance, ChannelManager::HandleTimer)
58     , mAutoSelectInterval(kDefaultAutoSelectInterval)
59     , mAutoSelectEnabled(false)
60     , mCcaFailureRateThreshold(kCcaFailureRateThreshold)
61 {
62 }
63 
RequestChannelChange(uint8_t aChannel)64 void ChannelManager::RequestChannelChange(uint8_t aChannel)
65 {
66     otLogInfoUtil("ChannelManager: Request to change to channel %d with delay %d sec", aChannel, mDelay);
67 
68     if (aChannel == Get<Mac::Mac>().GetPanChannel())
69     {
70         otLogInfoUtil("ChannelManager: Already operating on the requested channel %d", aChannel);
71         ExitNow();
72     }
73 
74     if (mState == kStateChangeInProgress)
75     {
76         VerifyOrExit(mChannel != aChannel);
77     }
78 
79     mState   = kStateChangeRequested;
80     mChannel = aChannel;
81 
82     mTimer.Start(1 + Random::NonCrypto::GetUint32InRange(0, kRequestStartJitterInterval));
83 
84     Get<Notifier>().Signal(kEventChannelManagerNewChannelChanged);
85 
86 exit:
87     return;
88 }
89 
SetDelay(uint16_t aDelay)90 Error ChannelManager::SetDelay(uint16_t aDelay)
91 {
92     Error error = kErrorNone;
93 
94     VerifyOrExit(aDelay >= kMinimumDelay, error = kErrorInvalidArgs);
95     mDelay = aDelay;
96 
97 exit:
98     return error;
99 }
100 
StartDatasetUpdate(void)101 void ChannelManager::StartDatasetUpdate(void)
102 {
103     MeshCoP::Dataset::Info dataset;
104 
105     dataset.Clear();
106     dataset.SetChannel(mChannel);
107     dataset.SetDelay(Time::SecToMsec(mDelay));
108 
109     switch (Get<MeshCoP::DatasetUpdater>().RequestUpdate(dataset, HandleDatasetUpdateDone, this))
110     {
111     case kErrorNone:
112         mState = kStateChangeInProgress;
113         // Wait for the `HandleDatasetUpdateDone()` callback.
114         break;
115 
116     case kErrorBusy:
117     case kErrorNoBufs:
118         mTimer.Start(kPendingDatasetTxRetryInterval);
119         break;
120 
121     case kErrorInvalidState:
122         otLogInfoUtil("ChannelManager: Request to change to channel %d failed. Device is disabled", mChannel);
123 
124         OT_FALL_THROUGH;
125 
126     default:
127         mState = kStateIdle;
128         StartAutoSelectTimer();
129         break;
130     }
131 }
132 
HandleDatasetUpdateDone(Error aError,void * aContext)133 void ChannelManager::HandleDatasetUpdateDone(Error aError, void *aContext)
134 {
135     static_cast<ChannelManager *>(aContext)->HandleDatasetUpdateDone(aError);
136 }
137 
HandleDatasetUpdateDone(Error aError)138 void ChannelManager::HandleDatasetUpdateDone(Error aError)
139 {
140     if (aError == kErrorNone)
141     {
142         otLogInfoUtil("ChannelManager: Channel changed to %d", mChannel);
143     }
144     else
145     {
146         otLogInfoUtil("ChannelManager: Canceling channel change to %d%s", mChannel,
147                       (aError == kErrorAlready) ? " since current ActiveDataset is more recent" : "");
148     }
149 
150     mState = kStateIdle;
151     StartAutoSelectTimer();
152 }
153 
HandleTimer(Timer & aTimer)154 void ChannelManager::HandleTimer(Timer &aTimer)
155 {
156     aTimer.Get<ChannelManager>().HandleTimer();
157 }
158 
HandleTimer(void)159 void ChannelManager::HandleTimer(void)
160 {
161     switch (mState)
162     {
163     case kStateIdle:
164         otLogInfoUtil("ChannelManager: Auto-triggered channel select");
165         IgnoreError(RequestChannelSelect(false));
166         StartAutoSelectTimer();
167         break;
168 
169     case kStateChangeRequested:
170         StartDatasetUpdate();
171         break;
172 
173     case kStateChangeInProgress:
174         break;
175     }
176 }
177 
178 #if OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE
179 
FindBetterChannel(uint8_t & aNewChannel,uint16_t & aOccupancy)180 Error ChannelManager::FindBetterChannel(uint8_t &aNewChannel, uint16_t &aOccupancy)
181 {
182     Error            error = kErrorNone;
183     Mac::ChannelMask favoredAndSupported;
184     Mac::ChannelMask favoredBest;
185     Mac::ChannelMask supportedBest;
186     uint16_t         favoredOccupancy;
187     uint16_t         supportedOccupancy;
188 
189     if (Get<ChannelMonitor>().GetSampleCount() <= kMinChannelMonitorSampleCount)
190     {
191         otLogInfoUtil("ChannelManager: Too few samples (%d <= %d) to select channel",
192                       Get<ChannelMonitor>().GetSampleCount(), kMinChannelMonitorSampleCount);
193         ExitNow(error = kErrorInvalidState);
194     }
195 
196     favoredAndSupported = mFavoredChannelMask;
197     favoredAndSupported.Intersect(mSupportedChannelMask);
198 
199     favoredBest   = Get<ChannelMonitor>().FindBestChannels(favoredAndSupported, favoredOccupancy);
200     supportedBest = Get<ChannelMonitor>().FindBestChannels(mSupportedChannelMask, supportedOccupancy);
201 
202     otLogInfoUtil("ChannelManager: Best favored %s, occupancy 0x%04x", favoredBest.ToString().AsCString(),
203                   favoredOccupancy);
204     otLogInfoUtil("ChannelManager: Best overall %s, occupancy 0x%04x", supportedBest.ToString().AsCString(),
205                   supportedOccupancy);
206 
207     // Prefer favored channels unless there is no favored channel,
208     // or the occupancy rate of the best favored channel is worse
209     // than the best overall by at least `kThresholdToSkipFavored`.
210 
211     if (favoredBest.IsEmpty() || ((favoredOccupancy >= kThresholdToSkipFavored) &&
212                                   (supportedOccupancy < favoredOccupancy - kThresholdToSkipFavored)))
213     {
214         if (!favoredBest.IsEmpty())
215         {
216             otLogInfoUtil("ChannelManager: Preferring an unfavored channel due to high occupancy rate diff");
217         }
218 
219         favoredBest      = supportedBest;
220         favoredOccupancy = supportedOccupancy;
221     }
222 
223     VerifyOrExit(!favoredBest.IsEmpty(), error = kErrorNotFound);
224 
225     aNewChannel = favoredBest.ChooseRandomChannel();
226     aOccupancy  = favoredOccupancy;
227 
228 exit:
229     return error;
230 }
231 
ShouldAttemptChannelChange(void)232 bool ChannelManager::ShouldAttemptChannelChange(void)
233 {
234     uint16_t ccaFailureRate = Get<Mac::Mac>().GetCcaFailureRate();
235     bool     shouldAttempt  = (ccaFailureRate >= mCcaFailureRateThreshold);
236 
237     otLogInfoUtil("ChannelManager: CCA-err-rate: 0x%04x %s 0x%04x, selecting channel: %s", ccaFailureRate,
238                   shouldAttempt ? ">=" : "<", mCcaFailureRateThreshold, shouldAttempt ? "yes" : "no");
239 
240     return shouldAttempt;
241 }
242 
RequestChannelSelect(bool aSkipQualityCheck)243 Error ChannelManager::RequestChannelSelect(bool aSkipQualityCheck)
244 {
245     Error    error = kErrorNone;
246     uint8_t  curChannel, newChannel;
247     uint16_t curOccupancy, newOccupancy;
248 
249     otLogInfoUtil("ChannelManager: Request to select channel (skip quality check: %s)",
250                   aSkipQualityCheck ? "yes" : "no");
251 
252     VerifyOrExit(!Get<Mle::Mle>().IsDisabled(), error = kErrorInvalidState);
253 
254     VerifyOrExit(aSkipQualityCheck || ShouldAttemptChannelChange());
255 
256     SuccessOrExit(error = FindBetterChannel(newChannel, newOccupancy));
257 
258     curChannel   = Get<Mac::Mac>().GetPanChannel();
259     curOccupancy = Get<ChannelMonitor>().GetChannelOccupancy(curChannel);
260 
261     if (newChannel == curChannel)
262     {
263         otLogInfoUtil("ChannelManager: Already on best possible channel %d", curChannel);
264         ExitNow();
265     }
266 
267     otLogInfoUtil("ChannelManager: Cur channel %d, occupancy 0x%04x - Best channel %d, occupancy 0x%04x", curChannel,
268                   curOccupancy, newChannel, newOccupancy);
269 
270     // Switch only if new channel's occupancy rate is better than current
271     // channel's occupancy rate by threshold `kThresholdToChangeChannel`.
272 
273     if ((newOccupancy >= curOccupancy) ||
274         (static_cast<uint16_t>(curOccupancy - newOccupancy) < kThresholdToChangeChannel))
275     {
276         otLogInfoUtil("ChannelManager: Occupancy rate diff too small to change channel");
277         ExitNow();
278     }
279 
280     RequestChannelChange(newChannel);
281 
282 exit:
283 
284     if (error != kErrorNone)
285     {
286         otLogInfoUtil("ChannelManager: Request to select better channel failed, error: %s", ErrorToString(error));
287     }
288 
289     return error;
290 }
291 #endif // OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE
292 
StartAutoSelectTimer(void)293 void ChannelManager::StartAutoSelectTimer(void)
294 {
295     VerifyOrExit(mState == kStateIdle);
296 
297     if (mAutoSelectEnabled)
298     {
299         mTimer.Start(Time::SecToMsec(mAutoSelectInterval));
300     }
301     else
302     {
303         mTimer.Stop();
304     }
305 
306 exit:
307     return;
308 }
309 
SetAutoChannelSelectionEnabled(bool aEnabled)310 void ChannelManager::SetAutoChannelSelectionEnabled(bool aEnabled)
311 {
312     if (aEnabled != mAutoSelectEnabled)
313     {
314         mAutoSelectEnabled = aEnabled;
315         IgnoreError(RequestChannelSelect(false));
316         StartAutoSelectTimer();
317     }
318 }
319 
SetAutoChannelSelectionInterval(uint32_t aInterval)320 Error ChannelManager::SetAutoChannelSelectionInterval(uint32_t aInterval)
321 {
322     Error    error        = kErrorNone;
323     uint32_t prevInterval = mAutoSelectInterval;
324 
325     VerifyOrExit((aInterval != 0) && (aInterval <= Time::MsecToSec(Timer::kMaxDelay)), error = kErrorInvalidArgs);
326 
327     mAutoSelectInterval = aInterval;
328 
329     if (mAutoSelectEnabled && (mState == kStateIdle) && mTimer.IsRunning() && (prevInterval != aInterval))
330     {
331         mTimer.StartAt(mTimer.GetFireTime() - Time::SecToMsec(prevInterval), Time::SecToMsec(aInterval));
332     }
333 
334 exit:
335     return error;
336 }
337 
SetSupportedChannels(uint32_t aChannelMask)338 void ChannelManager::SetSupportedChannels(uint32_t aChannelMask)
339 {
340     mSupportedChannelMask.SetMask(aChannelMask & Get<Mac::Mac>().GetSupportedChannelMask().GetMask());
341 
342     otLogInfoUtil("ChannelManager: Supported channels: %s", mSupportedChannelMask.ToString().AsCString());
343 }
344 
SetFavoredChannels(uint32_t aChannelMask)345 void ChannelManager::SetFavoredChannels(uint32_t aChannelMask)
346 {
347     mFavoredChannelMask.SetMask(aChannelMask & Get<Mac::Mac>().GetSupportedChannelMask().GetMask());
348 
349     otLogInfoUtil("ChannelManager: Favored channels: %s", mFavoredChannelMask.ToString().AsCString());
350 }
351 
SetCcaFailureRateThreshold(uint16_t aThreshold)352 void ChannelManager::SetCcaFailureRateThreshold(uint16_t aThreshold)
353 {
354     mCcaFailureRateThreshold = aThreshold;
355 
356     otLogInfoUtil("ChannelManager: CCA threshold: 0x%04x", mCcaFailureRateThreshold);
357 }
358 
359 } // namespace Utils
360 } // namespace ot
361 
362 #endif // #if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE
363