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