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