1 /*
2  *  Copyright (c) 2019, 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 includes implementation of radio selector (for multi radio links).
32  */
33 
34 #include "radio_selector.hpp"
35 
36 #if OPENTHREAD_CONFIG_MULTI_RADIO
37 
38 #include "common/code_utils.hpp"
39 #include "common/instance.hpp"
40 #include "common/locator_getters.hpp"
41 #include "common/log.hpp"
42 #include "common/random.hpp"
43 
44 namespace ot {
45 
46 RegisterLogModule("RadioSelector");
47 
48 // This array defines the order in which different radio link types are
49 // selected for message tx (direct message).
50 const Mac::RadioType RadioSelector::sRadioSelectionOrder[Mac::kNumRadioTypes] = {
51 #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
52     Mac::kRadioTypeTrel,
53 #endif
54 #if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
55     Mac::kRadioTypeIeee802154,
56 #endif
57 };
58 
RadioSelector(Instance & aInstance)59 RadioSelector::RadioSelector(Instance &aInstance)
60     : InstanceLocator(aInstance)
61 {
62 }
63 
PopulateMultiRadioInfo(MultiRadioInfo & aInfo)64 void RadioSelector::NeighborInfo::PopulateMultiRadioInfo(MultiRadioInfo &aInfo)
65 {
66     memset(&aInfo, 0, sizeof(MultiRadioInfo));
67 
68 #if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
69     if (GetSupportedRadioTypes().Contains(Mac::kRadioTypeIeee802154))
70     {
71         aInfo.mSupportsIeee802154         = true;
72         aInfo.mIeee802154Info.mPreference = GetRadioPreference(Mac::kRadioTypeIeee802154);
73     }
74 #endif
75 
76 #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
77     if (GetSupportedRadioTypes().Contains(Mac::kRadioTypeTrel))
78     {
79         aInfo.mSupportsTrelUdp6         = true;
80         aInfo.mTrelUdp6Info.mPreference = GetRadioPreference(Mac::kRadioTypeTrel);
81     }
82 #endif
83 }
84 
UpdatePreference(Neighbor & aNeighbor,Mac::RadioType aRadioType,int16_t aDifference)85 LogLevel RadioSelector::UpdatePreference(Neighbor &aNeighbor, Mac::RadioType aRadioType, int16_t aDifference)
86 {
87     uint8_t old        = aNeighbor.GetRadioPreference(aRadioType);
88     int16_t preference = static_cast<int16_t>(old);
89 
90     preference += aDifference;
91 
92     if (preference > kMaxPreference)
93     {
94         preference = kMaxPreference;
95     }
96 
97     if (preference < kMinPreference)
98     {
99         preference = kMinPreference;
100     }
101 
102     aNeighbor.SetRadioPreference(aRadioType, static_cast<uint8_t>(preference));
103 
104     // We check whether the update to the preference value caused it
105     // to cross the threshold `kHighPreference`. Based on this we
106     // return a suggested log level. If there is cross, suggest info
107     // log level, otherwise debug log level.
108 
109     return ((old >= kHighPreference) != (preference >= kHighPreference)) ? kLogLevelInfo : kLogLevelDebg;
110 }
111 
UpdateOnReceive(Neighbor & aNeighbor,Mac::RadioType aRadioType,bool aIsDuplicate)112 void RadioSelector::UpdateOnReceive(Neighbor &aNeighbor, Mac::RadioType aRadioType, bool aIsDuplicate)
113 {
114     LogLevel logLevel = kLogLevelInfo;
115 
116     if (aNeighbor.GetSupportedRadioTypes().Contains(aRadioType))
117     {
118         logLevel = UpdatePreference(aNeighbor, aRadioType,
119                                     aIsDuplicate ? kPreferenceChangeOnRxDuplicate : kPreferenceChangeOnRx);
120 
121         Log(logLevel, aIsDuplicate ? "UpdateOnDupRx" : "UpdateOnRx", aRadioType, aNeighbor);
122     }
123     else
124     {
125         aNeighbor.AddSupportedRadioType(aRadioType);
126         aNeighbor.SetRadioPreference(aRadioType, kInitPreference);
127 
128         Log(logLevel, "NewRadio(OnRx)", aRadioType, aNeighbor);
129     }
130 }
131 
UpdateOnSendDone(Mac::TxFrame & aFrame,Error aTxError)132 void RadioSelector::UpdateOnSendDone(Mac::TxFrame &aFrame, Error aTxError)
133 {
134     LogLevel       logLevel  = kLogLevelInfo;
135     Mac::RadioType radioType = aFrame.GetRadioType();
136     Mac::Address   macDest;
137     Neighbor      *neighbor;
138 
139 #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
140     if (radioType == Mac::kRadioTypeTrel)
141     {
142         // TREL radio link uses deferred ack model. We ignore
143         // `SendDone` event from `Mac` layer with success status and
144         // wait for deferred ack callback.
145         VerifyOrExit(aTxError != kErrorNone);
146     }
147 #endif
148 
149     VerifyOrExit(aFrame.GetAckRequest());
150 
151     IgnoreError(aFrame.GetDstAddr(macDest));
152     neighbor = Get<NeighborTable>().FindNeighbor(macDest, Neighbor::kInStateAnyExceptInvalid);
153     VerifyOrExit(neighbor != nullptr);
154 
155     if (neighbor->GetSupportedRadioTypes().Contains(radioType))
156     {
157         logLevel = UpdatePreference(
158             *neighbor, radioType, (aTxError == kErrorNone) ? kPreferenceChangeOnTxSuccess : kPreferenceChangeOnTxError);
159 
160         Log(logLevel, (aTxError == kErrorNone) ? "UpdateOnTxSucc" : "UpdateOnTxErr", radioType, *neighbor);
161     }
162     else
163     {
164         VerifyOrExit(aTxError == kErrorNone);
165         neighbor->AddSupportedRadioType(radioType);
166         neighbor->SetRadioPreference(radioType, kInitPreference);
167 
168         Log(logLevel, "NewRadio(OnTx)", radioType, *neighbor);
169     }
170 
171 exit:
172     return;
173 }
174 
175 #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
UpdateOnDeferredAck(Neighbor & aNeighbor,Error aTxError,bool & aAllowNeighborRemove)176 void RadioSelector::UpdateOnDeferredAck(Neighbor &aNeighbor, Error aTxError, bool &aAllowNeighborRemove)
177 {
178     LogLevel logLevel = kLogLevelInfo;
179 
180     aAllowNeighborRemove = true;
181 
182     if (aNeighbor.GetSupportedRadioTypes().Contains(Mac::kRadioTypeTrel))
183     {
184         logLevel = UpdatePreference(aNeighbor, Mac::kRadioTypeTrel,
185                                     (aTxError == kErrorNone) ? kPreferenceChangeOnDeferredAckSuccess
186                                                              : kPreferenceChangeOnDeferredAckTimeout);
187 
188         Log(logLevel, (aTxError == kErrorNone) ? "UpdateOnDefAckSucc" : "UpdateOnDefAckFail", Mac::kRadioTypeTrel,
189             aNeighbor);
190 
191         // In case of deferred ack timeout, we check if the neighbor
192         // has any other radio link (with high preference) for future
193         // tx. If it it does, we set `aAllowNeighborRemove` to `false`
194         // to ensure neighbor is not removed yet.
195 
196         VerifyOrExit(aTxError != kErrorNone);
197 
198         for (Mac::RadioType radio : sRadioSelectionOrder)
199         {
200             if ((radio != Mac::kRadioTypeTrel) && aNeighbor.GetSupportedRadioTypes().Contains(radio) &&
201                 aNeighbor.GetRadioPreference(radio) >= kHighPreference)
202             {
203                 aAllowNeighborRemove = false;
204                 ExitNow();
205             }
206         }
207     }
208     else
209     {
210         VerifyOrExit(aTxError == kErrorNone);
211         aNeighbor.AddSupportedRadioType(Mac::kRadioTypeTrel);
212         aNeighbor.SetRadioPreference(Mac::kRadioTypeTrel, kInitPreference);
213 
214         Log(logLevel, "NewRadio(OnDefAckSucc)", Mac::kRadioTypeTrel, aNeighbor);
215     }
216 
217 exit:
218     return;
219 }
220 #endif // OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
221 
Select(Mac::RadioTypes aRadioOptions,const Neighbor & aNeighbor)222 Mac::RadioType RadioSelector::Select(Mac::RadioTypes aRadioOptions, const Neighbor &aNeighbor)
223 {
224     Mac::RadioType selectedRadio      = sRadioSelectionOrder[0];
225     uint8_t        selectedPreference = 0;
226     bool           found              = false;
227 
228     // Select the first radio links with preference higher than
229     // threshold `kHighPreference`. The radio links are checked in the
230     // order defined by the `sRadioSelectionOrder` array. If no radio
231     // link has preference higher then threshold, select the one with
232     // highest preference.
233 
234     for (Mac::RadioType radio : sRadioSelectionOrder)
235     {
236         if (aRadioOptions.Contains(radio))
237         {
238             uint8_t preference = aNeighbor.GetRadioPreference(radio);
239 
240             if (preference >= kHighPreference)
241             {
242                 selectedRadio = radio;
243                 break;
244             }
245 
246             if (!found || (selectedPreference < preference))
247             {
248                 found              = true;
249                 selectedRadio      = radio;
250                 selectedPreference = preference;
251             }
252         }
253     }
254 
255     return selectedRadio;
256 }
257 
SelectRadio(Message & aMessage,const Mac::Address & aMacDest,Mac::TxFrames & aTxFrames)258 Mac::TxFrame &RadioSelector::SelectRadio(Message &aMessage, const Mac::Address &aMacDest, Mac::TxFrames &aTxFrames)
259 {
260     Neighbor       *neighbor;
261     Mac::RadioType  selectedRadio;
262     Mac::RadioTypes selections;
263 
264     if (aMacDest.IsBroadcast() || aMacDest.IsNone())
265     {
266         aMessage.ClearRadioType();
267         ExitNow(selections.AddAll());
268     }
269 
270     // If the radio type is already set when message was created we
271     // use the selected radio type. (e.g., MLE Discovery Response
272     // selects the radio link from which MLE Discovery Request is
273     // received.
274 
275     if (aMessage.IsRadioTypeSet())
276     {
277         ExitNow(selections.Add(aMessage.GetRadioType()));
278     }
279 
280     neighbor = Get<NeighborTable>().FindNeighbor(aMacDest, Neighbor::kInStateAnyExceptInvalid);
281 
282     if ((neighbor == nullptr) || neighbor->GetSupportedRadioTypes().IsEmpty())
283     {
284         // If we do not have a corresponding neighbor or do not yet
285         // know the supported radio types, we try sending on all radio
286         // links in parallel. As an example, such a situation can
287         // happen when recovering a non-sleepy child (sending MLE
288         // Child Update Request to it) after device itself was reset.
289 
290         aMessage.ClearRadioType();
291         ExitNow(selections.AddAll());
292     }
293 
294     selectedRadio = Select(neighbor->GetSupportedRadioTypes(), *neighbor);
295     selections.Add(selectedRadio);
296 
297     Log(kLogLevelDebg, "SelectRadio", selectedRadio, *neighbor);
298 
299     aMessage.SetRadioType(selectedRadio);
300 
301     // We (probabilistically) decide whether to probe on another radio
302     // link for the current frame tx. When probing we allow the same
303     // frame to be sent in parallel over multiple radio links but only
304     // care about the tx outcome (ack status) on the main selected
305     // radio link. This is done by setting the "required radio types"
306     // (`SetRequiredRadioTypes()`) to match the main selection on
307     // `aTxFrames`. We allow probe on TREL radio link if it is not
308     // currently usable (thus not selected) but is/was supported by
309     // the neighbor (i.e., we did rx/tx on TREL link from/to this
310     // neighbor in the past). The probe process helps detect whether
311     // the TREL link is usable again allowing us to switch over
312     // faster.
313 
314 #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
315     if (!selections.Contains(Mac::kRadioTypeTrel) && neighbor->GetSupportedRadioTypes().Contains(Mac::kRadioTypeTrel) &&
316         (Random::NonCrypto::GetUint8InRange(0, 100) < kTrelProbeProbability))
317     {
318         aTxFrames.SetRequiredRadioTypes(selections);
319         selections.Add(Mac::kRadioTypeTrel);
320 
321         Log(kLogLevelDebg, "Probe", Mac::kRadioTypeTrel, *neighbor);
322     }
323 #endif
324 
325 exit:
326     return aTxFrames.GetTxFrame(selections);
327 }
328 
SelectPollFrameRadio(const Neighbor & aParent)329 Mac::RadioType RadioSelector::SelectPollFrameRadio(const Neighbor &aParent)
330 {
331     // This array defines the order in which different radio link types
332     // are selected for data poll frame tx.
333     static const Mac::RadioType selectionOrder[Mac::kNumRadioTypes] = {
334 #if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
335         Mac::kRadioTypeIeee802154,
336 #endif
337 #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
338         Mac::kRadioTypeTrel,
339 #endif
340     };
341 
342     Mac::RadioType selection = selectionOrder[0];
343 
344     for (Mac::RadioType radio : selectionOrder)
345     {
346         if (aParent.GetSupportedRadioTypes().Contains(radio))
347         {
348             selection = radio;
349             break;
350         }
351     }
352 
353     return selection;
354 }
355 
356 // LCOV_EXCL_START
357 
358 #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
359 
Log(LogLevel aLogLevel,const char * aActionText,Mac::RadioType aRadioType,const Neighbor & aNeighbor)360 void RadioSelector::Log(LogLevel        aLogLevel,
361                         const char     *aActionText,
362                         Mac::RadioType  aRadioType,
363                         const Neighbor &aNeighbor)
364 {
365     String<kRadioPreferenceStringSize> preferenceString;
366     bool                               isFirstEntry = true;
367 
368     VerifyOrExit(Instance::GetLogLevel() >= aLogLevel);
369 
370     for (Mac::RadioType radio : sRadioSelectionOrder)
371     {
372         if (aNeighbor.GetSupportedRadioTypes().Contains(radio))
373         {
374             preferenceString.Append("%s%s:%d", isFirstEntry ? "" : " ", RadioTypeToString(radio),
375                                     aNeighbor.GetRadioPreference(radio));
376             isFirstEntry = false;
377         }
378     }
379 
380     LogAt(aLogLevel, "RadioSelector: %s %s - neighbor:[%s rloc16:0x%04x radio-pref:{%s} state:%s]", aActionText,
381           RadioTypeToString(aRadioType), aNeighbor.GetExtAddress().ToString().AsCString(), aNeighbor.GetRloc16(),
382           preferenceString.AsCString(), Neighbor::StateToString(aNeighbor.GetState()));
383 
384 exit:
385     return;
386 }
387 
388 #else // #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
389 
Log(LogLevel,const char *,Mac::RadioType,const Neighbor &)390 void RadioSelector::Log(LogLevel, const char *, Mac::RadioType, const Neighbor &) {}
391 
392 #endif // #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
393 
394 // LCOV_EXCL_STOP
395 
396 } // namespace ot
397 
398 #endif // #if OPENTHREAD_CONFIG_MULTI_RADIO
399