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