1 /*
2  *  Copyright (c) 2023, 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 #include "link_metrics_manager.hpp"
30 
31 #if OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE
32 
33 #include "common/as_core_type.hpp"
34 #include "common/error.hpp"
35 #include "common/locator_getters.hpp"
36 #include "common/log.hpp"
37 #include "common/notifier.hpp"
38 #include "thread/mle.hpp"
39 #include "thread/neighbor_table.hpp"
40 
41 namespace ot {
42 namespace Utils {
43 
44 RegisterLogModule("LinkMetricsMgr");
45 
46 /**
47  * @addtogroup utils-link-metrics-manager
48  *
49  * @brief
50  *   This module includes definitions for Link Metrics Manager.
51  *
52  * @{
53  */
54 
LinkMetricsManager(Instance & aInstance)55 LinkMetricsManager::LinkMetricsManager(Instance &aInstance)
56     : InstanceLocator(aInstance)
57     , mTimer(aInstance)
58     , mEnabled(OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ON_BY_DEFAULT)
59 {
60 }
61 
SetEnabled(bool aEnable)62 void LinkMetricsManager::SetEnabled(bool aEnable)
63 {
64     VerifyOrExit(mEnabled != aEnable);
65     mEnabled = aEnable;
66 
67     if (mEnabled)
68     {
69         Start();
70     }
71     else
72     {
73         Stop();
74     }
75 
76 exit:
77     return;
78 }
79 
GetLinkMetricsValueByExtAddr(const Mac::ExtAddress & aExtAddress,LinkMetrics::MetricsValues & aMetricsValues)80 Error LinkMetricsManager::GetLinkMetricsValueByExtAddr(const Mac::ExtAddress      &aExtAddress,
81                                                        LinkMetrics::MetricsValues &aMetricsValues)
82 {
83     Error    error = kErrorNone;
84     Subject *subject;
85 
86     subject = mSubjectList.FindMatching(aExtAddress);
87     VerifyOrExit(subject != nullptr, error = kErrorNotFound);
88     VerifyOrExit(subject->mState == kActive || subject->mState == kRenewing, error = kErrorInvalidState);
89 
90     aMetricsValues.mLinkMarginValue = subject->mData.mLinkMargin;
91     aMetricsValues.mRssiValue       = subject->mData.mRssi;
92 
93 exit:
94     return error;
95 }
96 
Start(void)97 void LinkMetricsManager::Start(void)
98 {
99     LinkMetrics::Initiator &initiator = Get<LinkMetrics::Initiator>();
100 
101     VerifyOrExit(mEnabled && Get<Mle::Mle>().IsAttached());
102 
103     initiator.SetMgmtResponseCallback(HandleMgmtResponse, this);
104     initiator.SetEnhAckProbingCallback(HandleEnhAckIe, this);
105 
106     mTimer.Start(kTimeBeforeStartMilliSec);
107 exit:
108     return;
109 }
110 
Stop(void)111 void LinkMetricsManager::Stop(void)
112 {
113     LinkMetrics::Initiator &initiator = Get<LinkMetrics::Initiator>();
114 
115     mTimer.Stop();
116 
117     initiator.SetMgmtResponseCallback(nullptr, nullptr);
118     initiator.SetEnhAckProbingCallback(nullptr, nullptr);
119 
120     UnregisterAllSubjects();
121     ReleaseAllSubjects();
122 }
123 
Update(void)124 void LinkMetricsManager::Update(void)
125 {
126     UpdateSubjects();
127     UpdateLinkMetricsStates();
128 }
129 
130 // This method updates the Link Metrics Subject in the subject list. It adds new neighbors to the list.
UpdateSubjects(void)131 void LinkMetricsManager::UpdateSubjects(void)
132 {
133     Neighbor::Info         neighborInfo;
134     otNeighborInfoIterator iterator = OT_NEIGHBOR_INFO_ITERATOR_INIT;
135 
136     while (Get<NeighborTable>().GetNextNeighborInfo(iterator, neighborInfo) == kErrorNone)
137     {
138         // if not in the subject list, allocate and add
139         if (!mSubjectList.ContainsMatching(AsCoreType(&neighborInfo.mExtAddress)))
140         {
141             Subject *subject = mPool.Allocate();
142 
143             VerifyOrExit(subject != nullptr);
144 
145             subject->Clear();
146             subject->mExtAddress = AsCoreType(&neighborInfo.mExtAddress);
147             IgnoreError(mSubjectList.Add(*subject));
148         }
149     }
150 
151 exit:
152     return;
153 }
154 
155 // This method updates the state and take corresponding actions for all subjects and removes stale subjects.
UpdateLinkMetricsStates(void)156 void LinkMetricsManager::UpdateLinkMetricsStates(void)
157 {
158     LinkedList<Subject> staleSubjects;
159 
160     mSubjectList.RemoveAllMatching(*this, staleSubjects);
161 
162     while (!staleSubjects.IsEmpty())
163     {
164         Subject *subject = staleSubjects.Pop();
165 
166         mPool.Free(*subject);
167     }
168 }
169 
UnregisterAllSubjects(void)170 void LinkMetricsManager::UnregisterAllSubjects(void)
171 {
172     for (Subject &subject : mSubjectList)
173     {
174         IgnoreError(subject.UnregisterEap(GetInstance()));
175     }
176 }
177 
ReleaseAllSubjects(void)178 void LinkMetricsManager::ReleaseAllSubjects(void)
179 {
180     while (!mSubjectList.IsEmpty())
181     {
182         Subject *subject = mSubjectList.Pop();
183 
184         mPool.Free(*subject);
185     }
186 }
187 
HandleNotifierEvents(Events aEvents)188 void LinkMetricsManager::HandleNotifierEvents(Events aEvents)
189 {
190     if (aEvents.Contains(kEventThreadRoleChanged))
191     {
192         if (Get<Mle::Mle>().IsAttached())
193         {
194             Start();
195         }
196         else
197         {
198             Stop();
199         }
200     }
201 }
202 
HandleTimer(void)203 void LinkMetricsManager::HandleTimer(void)
204 {
205     if (Get<Mle::Mle>().IsAttached())
206     {
207         Update();
208         mTimer.Start(kStateUpdateIntervalMilliSec);
209     }
210 }
211 
HandleMgmtResponse(const otIp6Address * aAddress,otLinkMetricsStatus aStatus,void * aContext)212 void LinkMetricsManager::HandleMgmtResponse(const otIp6Address *aAddress, otLinkMetricsStatus aStatus, void *aContext)
213 {
214     static_cast<LinkMetricsManager *>(aContext)->HandleMgmtResponse(aAddress, aStatus);
215 }
216 
HandleMgmtResponse(const otIp6Address * aAddress,otLinkMetricsStatus aStatus)217 void LinkMetricsManager::HandleMgmtResponse(const otIp6Address *aAddress, otLinkMetricsStatus aStatus)
218 {
219     Mac::ExtAddress extAddress;
220     Subject        *subject;
221     Neighbor       *neighbor;
222 
223     AsCoreType(aAddress).GetIid().ConvertToExtAddress(extAddress);
224     neighbor = Get<NeighborTable>().FindNeighbor(extAddress);
225     VerifyOrExit(neighbor != nullptr);
226 
227     subject = mSubjectList.FindMatching(extAddress);
228     VerifyOrExit(subject != nullptr);
229 
230     switch (MapEnum(aStatus))
231     {
232     case LinkMetrics::Status::kStatusSuccess:
233         subject->mState = SubjectState::kActive;
234         break;
235     default:
236         subject->mState = SubjectState::kNotConfigured;
237         break;
238     }
239 
240 exit:
241     return;
242 }
243 
HandleEnhAckIe(otShortAddress aShortAddress,const otExtAddress * aExtAddress,const otLinkMetricsValues * aMetricsValues,void * aContext)244 void LinkMetricsManager::HandleEnhAckIe(otShortAddress             aShortAddress,
245                                         const otExtAddress        *aExtAddress,
246                                         const otLinkMetricsValues *aMetricsValues,
247                                         void                      *aContext)
248 {
249     static_cast<LinkMetricsManager *>(aContext)->HandleEnhAckIe(aShortAddress, aExtAddress, aMetricsValues);
250 }
251 
HandleEnhAckIe(otShortAddress aShortAddress,const otExtAddress * aExtAddress,const otLinkMetricsValues * aMetricsValues)252 void LinkMetricsManager::HandleEnhAckIe(otShortAddress             aShortAddress,
253                                         const otExtAddress        *aExtAddress,
254                                         const otLinkMetricsValues *aMetricsValues)
255 {
256     OT_UNUSED_VARIABLE(aShortAddress);
257 
258     Error    error   = kErrorNone;
259     Subject *subject = mSubjectList.FindMatching(AsCoreType(aExtAddress));
260 
261     VerifyOrExit(subject != nullptr, error = kErrorNotFound);
262 
263     VerifyOrExit(subject->mState == SubjectState::kActive || subject->mState == SubjectState::kRenewing);
264     subject->mLastUpdateTime = TimerMilli::GetNow();
265 
266     VerifyOrExit(aMetricsValues->mMetrics.mRssi && aMetricsValues->mMetrics.mLinkMargin, error = kErrorInvalidArgs);
267 
268     subject->mData.mRssi       = aMetricsValues->mRssiValue;
269     subject->mData.mLinkMargin = aMetricsValues->mLinkMarginValue;
270 
271 exit:
272     if (error == kErrorInvalidArgs)
273     {
274         LogWarn("Metrics received are unexpected!");
275     }
276 }
277 
278 // This special Match method is used for "iterating over list while removing some items"
Matches(const LinkMetricsManager & aLinkMetricsMgr)279 bool LinkMetricsManager::Subject::Matches(const LinkMetricsManager &aLinkMetricsMgr)
280 {
281     Error error = UpdateState(aLinkMetricsMgr.GetInstance());
282 
283     return error == kErrorUnknownNeighbor || error == kErrorNotCapable;
284 }
285 
ConfigureEap(Instance & aInstance)286 Error LinkMetricsManager::Subject::ConfigureEap(Instance &aInstance)
287 {
288     Error                    error    = kErrorNone;
289     Neighbor                *neighbor = aInstance.Get<NeighborTable>().FindNeighbor(mExtAddress);
290     Ip6::Address             destination;
291     LinkMetrics::EnhAckFlags enhAckFlags = LinkMetrics::kEnhAckRegister;
292     LinkMetrics::Metrics     metricsFlags;
293 
294     VerifyOrExit(neighbor != nullptr, error = kErrorUnknownNeighbor);
295     destination.SetToLinkLocalAddress(neighbor->GetExtAddress());
296 
297     metricsFlags.Clear();
298     metricsFlags.mLinkMargin = 1;
299     metricsFlags.mRssi       = 1;
300     error =
301         aInstance.Get<LinkMetrics::Initiator>().SendMgmtRequestEnhAckProbing(destination, enhAckFlags, &metricsFlags);
302 
303 exit:
304     if (error == kErrorNone)
305     {
306         mState = (mState == SubjectState::kActive) ? SubjectState::kRenewing : SubjectState::kConfiguring;
307         mAttempts++;
308     }
309     return error;
310 }
311 
UnregisterEap(Instance & aInstance)312 Error LinkMetricsManager::Subject::UnregisterEap(Instance &aInstance)
313 {
314     Error                    error    = kErrorNone;
315     Neighbor                *neighbor = aInstance.Get<NeighborTable>().FindNeighbor(mExtAddress);
316     Ip6::Address             destination;
317     LinkMetrics::EnhAckFlags enhAckFlags = LinkMetrics::kEnhAckClear;
318 
319     VerifyOrExit(neighbor != nullptr, error = kErrorUnknownNeighbor);
320     destination.SetToLinkLocalAddress(neighbor->GetExtAddress());
321 
322     error = aInstance.Get<LinkMetrics::Initiator>().SendMgmtRequestEnhAckProbing(destination, enhAckFlags, nullptr);
323 exit:
324     return error;
325 }
326 
UpdateState(Instance & aInstance)327 Error LinkMetricsManager::Subject::UpdateState(Instance &aInstance)
328 {
329     bool     shouldConfigure = false;
330     uint32_t pastTimeMs;
331     Error    error = kErrorNone;
332 
333     switch (mState)
334     {
335     case kNotConfigured:
336     case kConfiguring:
337     case kRenewing:
338         if (mAttempts >= kConfigureLinkMetricsMaxAttempts)
339         {
340             mState = kNotSupported;
341         }
342         else
343         {
344             shouldConfigure = true;
345         }
346         break;
347     case kActive:
348         pastTimeMs = TimerMilli::GetNow() - mLastUpdateTime;
349         if (pastTimeMs >= kStateUpdateIntervalMilliSec)
350         {
351             shouldConfigure = true;
352         }
353         break;
354     case kNotSupported:
355         ExitNow(error = kErrorNotCapable);
356         break;
357     default:
358         break;
359     }
360 
361     if (shouldConfigure)
362     {
363         error = ConfigureEap(aInstance);
364     }
365 
366 exit:
367     return error;
368 }
369 
370 /**
371  * @}
372  *
373  */
374 
375 } // namespace Utils
376 } // namespace ot
377 
378 #endif // OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE
379