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