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