1 /*
2 * Copyright (c) 2020, 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 /**
30 * @file
31 * This file includes definitions for Thread Link Metrics.
32 */
33
34 #include "link_metrics.hpp"
35
36 #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE || OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
37
38 #include "common/code_utils.hpp"
39 #include "common/instance.hpp"
40 #include "common/locator_getters.hpp"
41 #include "common/logging.hpp"
42 #include "mac/mac.hpp"
43 #include "thread/link_metrics_tlvs.hpp"
44 #include "thread/neighbor_table.hpp"
45
46 namespace ot {
47 namespace LinkMetrics {
48
Init(uint8_t aSeriesId,const SeriesFlags & aSeriesFlags,const Metrics & aMetrics)49 void SeriesInfo::Init(uint8_t aSeriesId, const SeriesFlags &aSeriesFlags, const Metrics &aMetrics)
50 {
51 mSeriesId = aSeriesId;
52 mSeriesFlags = aSeriesFlags;
53 mMetrics = aMetrics;
54 mRssAverager.Clear();
55 mLqiAverager.Clear();
56 mPduCount = 0;
57 }
58
AggregateLinkMetrics(uint8_t aFrameType,uint8_t aLqi,int8_t aRss)59 void SeriesInfo::AggregateLinkMetrics(uint8_t aFrameType, uint8_t aLqi, int8_t aRss)
60 {
61 if (IsFrameTypeMatch(aFrameType))
62 {
63 mPduCount++;
64 mLqiAverager.Add(aLqi);
65 IgnoreError(mRssAverager.Add(aRss));
66 }
67 }
68
IsFrameTypeMatch(uint8_t aFrameType) const69 bool SeriesInfo::IsFrameTypeMatch(uint8_t aFrameType) const
70 {
71 bool match = false;
72
73 switch (aFrameType)
74 {
75 case kSeriesTypeLinkProbe:
76 VerifyOrExit(!mSeriesFlags.IsMacDataFlagSet()); // Ignore this when Mac Data is accounted
77 match = mSeriesFlags.IsLinkProbeFlagSet();
78 break;
79 case Mac::Frame::kFcfFrameData:
80 match = mSeriesFlags.IsMacDataFlagSet();
81 break;
82 case Mac::Frame::kFcfFrameMacCmd:
83 match = mSeriesFlags.IsMacDataRequestFlagSet();
84 break;
85 case Mac::Frame::kFcfFrameAck:
86 match = mSeriesFlags.IsMacAckFlagSet();
87 break;
88 default:
89 break;
90 }
91
92 exit:
93 return match;
94 }
95
LinkMetrics(Instance & aInstance)96 LinkMetrics::LinkMetrics(Instance &aInstance)
97 : InstanceLocator(aInstance)
98 , mReportCallback(nullptr)
99 , mReportCallbackContext(nullptr)
100 , mMgmtResponseCallback(nullptr)
101 , mMgmtResponseCallbackContext(nullptr)
102 , mEnhAckProbingIeReportCallback(nullptr)
103 , mEnhAckProbingIeReportCallbackContext(nullptr)
104 {
105 }
106
Query(const Ip6::Address & aDestination,uint8_t aSeriesId,const Metrics * aMetrics)107 Error LinkMetrics::Query(const Ip6::Address &aDestination, uint8_t aSeriesId, const Metrics *aMetrics)
108 {
109 Error error;
110 TypeIdFlags typeIdFlags[kMaxTypeIdFlags];
111 uint8_t typeIdFlagsCount = 0;
112 Neighbor * neighbor = GetNeighborFromLinkLocalAddr(aDestination);
113
114 VerifyOrExit(neighbor != nullptr, error = kErrorUnknownNeighbor);
115 VerifyOrExit(neighbor->IsThreadVersion1p2(), error = kErrorNotCapable);
116
117 if (aMetrics != nullptr)
118 {
119 typeIdFlagsCount = TypeIdFlagsFromMetrics(typeIdFlags, *aMetrics);
120 }
121
122 if (aSeriesId != 0)
123 {
124 VerifyOrExit(typeIdFlagsCount == 0, error = kErrorInvalidArgs);
125 }
126
127 error = SendLinkMetricsQuery(aDestination, aSeriesId, typeIdFlags, typeIdFlagsCount);
128
129 exit:
130 return error;
131 }
132
133 #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE
SendMgmtRequestForwardTrackingSeries(const Ip6::Address & aDestination,uint8_t aSeriesId,const SeriesFlags::Info & aSeriesFlags,const Metrics * aMetrics)134 Error LinkMetrics::SendMgmtRequestForwardTrackingSeries(const Ip6::Address & aDestination,
135 uint8_t aSeriesId,
136 const SeriesFlags::Info &aSeriesFlags,
137 const Metrics * aMetrics)
138 {
139 Error error = kErrorNone;
140 uint8_t subTlvs[sizeof(Tlv) + sizeof(uint8_t) * 2 + sizeof(TypeIdFlags) * kMaxTypeIdFlags];
141 Tlv * fwdProbingSubTlv = reinterpret_cast<Tlv *>(subTlvs);
142 SeriesFlags *seriesFlags = reinterpret_cast<SeriesFlags *>(subTlvs + sizeof(Tlv) + sizeof(aSeriesId));
143 uint8_t typeIdFlagsOffset = sizeof(Tlv) + sizeof(uint8_t) * 2;
144 uint8_t typeIdFlagsCount = 0;
145 Neighbor * neighbor = GetNeighborFromLinkLocalAddr(aDestination);
146
147 VerifyOrExit(neighbor != nullptr, error = kErrorUnknownNeighbor);
148 VerifyOrExit(neighbor->IsThreadVersion1p2(), error = kErrorNotCapable);
149
150 // Directly transform `aMetrics` into TypeIdFlags and put them into `subTlvs`
151 if (aMetrics != nullptr)
152 {
153 typeIdFlagsCount =
154 TypeIdFlagsFromMetrics(reinterpret_cast<TypeIdFlags *>(subTlvs + typeIdFlagsOffset), *aMetrics);
155 }
156
157 VerifyOrExit(aSeriesId > kQueryIdSingleProbe, error = kErrorInvalidArgs);
158
159 fwdProbingSubTlv->SetType(SubTlv::kFwdProbingReg);
160
161 // SeriesId + SeriesFlags + typeIdFlagsCount * TypeIdFlags
162 fwdProbingSubTlv->SetLength(sizeof(uint8_t) * 2 + sizeof(TypeIdFlags) * typeIdFlagsCount);
163
164 memcpy(subTlvs + sizeof(Tlv), &aSeriesId, sizeof(aSeriesId));
165
166 seriesFlags->SetFrom(aSeriesFlags);
167
168 error = Get<Mle::MleRouter>().SendLinkMetricsManagementRequest(aDestination, subTlvs, fwdProbingSubTlv->GetSize());
169
170 exit:
171 otLogDebgMle("SendMgmtRequestForwardTrackingSeries, error:%s, Series ID:%u", ErrorToString(error), aSeriesId);
172 return error;
173 }
174
SendMgmtRequestEnhAckProbing(const Ip6::Address & aDestination,const EnhAckFlags aEnhAckFlags,const Metrics * aMetrics)175 Error LinkMetrics::SendMgmtRequestEnhAckProbing(const Ip6::Address &aDestination,
176 const EnhAckFlags aEnhAckFlags,
177 const Metrics * aMetrics)
178 {
179 Error error = kErrorNone;
180 EnhAckConfigSubTlv enhAckConfigSubTlv;
181 Mac::Address macAddress;
182 Neighbor * neighbor = GetNeighborFromLinkLocalAddr(aDestination);
183
184 VerifyOrExit(neighbor != nullptr, error = kErrorUnknownNeighbor);
185 VerifyOrExit(neighbor->IsThreadVersion1p2(), error = kErrorNotCapable);
186
187 if (aEnhAckFlags == kEnhAckClear)
188 {
189 VerifyOrExit(aMetrics == nullptr, error = kErrorInvalidArgs);
190 }
191
192 enhAckConfigSubTlv.SetEnhAckFlags(aEnhAckFlags);
193
194 if (aMetrics != nullptr)
195 {
196 enhAckConfigSubTlv.SetTypeIdFlags(*aMetrics);
197 }
198
199 error = Get<Mle::MleRouter>().SendLinkMetricsManagementRequest(
200 aDestination, reinterpret_cast<const uint8_t *>(&enhAckConfigSubTlv), enhAckConfigSubTlv.GetSize());
201
202 if (aMetrics != nullptr)
203 {
204 neighbor->SetEnhAckProbingMetrics(*aMetrics);
205 }
206 else
207 {
208 Metrics metrics;
209
210 metrics.Clear();
211 neighbor->SetEnhAckProbingMetrics(metrics);
212 }
213
214 exit:
215 return error;
216 }
217
SendLinkProbe(const Ip6::Address & aDestination,uint8_t aSeriesId,uint8_t aLength)218 Error LinkMetrics::SendLinkProbe(const Ip6::Address &aDestination, uint8_t aSeriesId, uint8_t aLength)
219 {
220 Error error = kErrorNone;
221 uint8_t buf[kLinkProbeMaxLen];
222 Neighbor *neighbor = GetNeighborFromLinkLocalAddr(aDestination);
223
224 VerifyOrExit(neighbor != nullptr, error = kErrorUnknownNeighbor);
225 VerifyOrExit(neighbor->IsThreadVersion1p2(), error = kErrorNotCapable);
226
227 VerifyOrExit(aLength <= LinkMetrics::kLinkProbeMaxLen && aSeriesId != kQueryIdSingleProbe &&
228 aSeriesId != kSeriesIdAllSeries,
229 error = kErrorInvalidArgs);
230
231 error = Get<Mle::MleRouter>().SendLinkProbe(aDestination, aSeriesId, buf, aLength);
232 exit:
233 otLogDebgMle("SendLinkProbe, error:%s, Series ID:%u", ErrorToString(error), aSeriesId);
234 return error;
235 }
236 #endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE
237
238 #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
AppendReport(Message & aMessage,const Message & aRequestMessage,Neighbor & aNeighbor)239 Error LinkMetrics::AppendReport(Message &aMessage, const Message &aRequestMessage, Neighbor &aNeighbor)
240 {
241 Error error = kErrorNone;
242 Tlv tlv;
243 uint8_t queryId;
244 bool hasQueryId = false;
245 uint8_t length = 0;
246 uint16_t startOffset = aMessage.GetLength();
247 uint16_t offset;
248 uint16_t endOffset;
249 MetricsValues values;
250
251 values.Clear();
252
253 SuccessOrExit(error = Tlv::FindTlvValueOffset(aRequestMessage, Mle::Tlv::Type::kLinkMetricsQuery, offset,
254 endOffset)); // `endOffset` is used to store tlv length here
255
256 endOffset = offset + endOffset;
257
258 while (offset < endOffset)
259 {
260 SuccessOrExit(error = aRequestMessage.Read(offset, tlv));
261
262 switch (tlv.GetType())
263 {
264 case SubTlv::kQueryId:
265 SuccessOrExit(error = Tlv::Read<QueryIdSubTlv>(aRequestMessage, offset, queryId));
266 hasQueryId = true;
267 break;
268
269 case SubTlv::kQueryOptions:
270 SuccessOrExit(error = ReadTypeIdFlagsFromMessage(aRequestMessage, offset + sizeof(tlv),
271 static_cast<uint16_t>(offset + tlv.GetSize()),
272 values.GetMetrics()));
273 break;
274
275 default:
276 break;
277 }
278
279 offset += tlv.GetSize();
280 }
281
282 VerifyOrExit(hasQueryId, error = kErrorParse);
283
284 // Link Metrics Report TLV
285 tlv.SetType(Mle::Tlv::kLinkMetricsReport);
286 SuccessOrExit(error = aMessage.Append(tlv));
287
288 if (queryId == kQueryIdSingleProbe)
289 {
290 values.mPduCountValue = aRequestMessage.GetPsduCount();
291 values.mLqiValue = aRequestMessage.GetAverageLqi();
292 // Linearly scale Link Margin from [0, 130] to [0, 255]
293 values.mLinkMarginValue =
294 LinkQualityInfo::ConvertRssToLinkMargin(Get<Mac::Mac>().GetNoiseFloor(), aRequestMessage.GetAverageRss()) *
295 255 / 130;
296 // Linearly scale rss from [-130, 0] to [0, 255]
297 values.mRssiValue = (aRequestMessage.GetAverageRss() + 130) * 255 / 130;
298
299 SuccessOrExit(error = AppendReportSubTlvToMessage(aMessage, length, values));
300 }
301 else
302 {
303 SeriesInfo *seriesInfo = aNeighbor.GetForwardTrackingSeriesInfo(queryId);
304
305 if (seriesInfo == nullptr)
306 {
307 SuccessOrExit(error = AppendStatusSubTlvToMessage(aMessage, length, kStatusSeriesIdNotRecognized));
308 }
309 else if (seriesInfo->GetPduCount() == 0)
310 {
311 SuccessOrExit(error = AppendStatusSubTlvToMessage(aMessage, length, kStatusNoMatchingFramesReceived));
312 }
313 else
314 {
315 values.SetMetrics(seriesInfo->GetLinkMetrics());
316 values.mPduCountValue = seriesInfo->GetPduCount();
317 values.mLqiValue = seriesInfo->GetAverageLqi();
318 // Linearly scale Link Margin from [0, 130] to [0, 255]
319 values.mLinkMarginValue =
320 LinkQualityInfo::ConvertRssToLinkMargin(Get<Mac::Mac>().GetNoiseFloor(), seriesInfo->GetAverageRss()) *
321 255 / 130;
322 // Linearly scale RSSI from [-130, 0] to [0, 255]
323 values.mRssiValue = (seriesInfo->GetAverageRss() + 130) * 255 / 130;
324 SuccessOrExit(error = AppendReportSubTlvToMessage(aMessage, length, values));
325 }
326 }
327
328 tlv.SetLength(length);
329 aMessage.Write(startOffset, tlv);
330
331 exit:
332 otLogDebgMle("AppendReport, error:%s", ErrorToString(error));
333 return error;
334 }
335
HandleManagementRequest(const Message & aMessage,Neighbor & aNeighbor,Status & aStatus)336 Error LinkMetrics::HandleManagementRequest(const Message &aMessage, Neighbor &aNeighbor, Status &aStatus)
337 {
338 Error error = kErrorNone;
339 Tlv tlv;
340 uint8_t seriesId;
341 SeriesFlags seriesFlags;
342 EnhAckFlags enhAckFlags;
343 Metrics metrics;
344 bool hasForwardProbingRegistrationTlv = false;
345 bool hasEnhAckProbingTlv = false;
346 uint16_t offset;
347 uint16_t length;
348 uint16_t index = 0;
349
350 SuccessOrExit(error = Tlv::FindTlvValueOffset(aMessage, Mle::Tlv::Type::kLinkMetricsManagement, offset, length));
351
352 while (index < length)
353 {
354 uint16_t pos = offset + index;
355
356 SuccessOrExit(aMessage.Read(pos, tlv));
357
358 pos += sizeof(tlv);
359
360 switch (tlv.GetType())
361 {
362 case SubTlv::kFwdProbingReg:
363 VerifyOrExit(!hasForwardProbingRegistrationTlv && !hasEnhAckProbingTlv, error = kErrorParse);
364 VerifyOrExit(tlv.GetLength() >= sizeof(seriesId) + sizeof(seriesFlags), error = kErrorParse);
365 SuccessOrExit(aMessage.Read(pos, seriesId));
366 pos += sizeof(seriesId);
367 SuccessOrExit(aMessage.Read(pos, seriesFlags));
368 pos += sizeof(seriesFlags);
369 SuccessOrExit(error = ReadTypeIdFlagsFromMessage(
370 aMessage, pos, static_cast<uint16_t>(offset + index + tlv.GetSize()), metrics));
371 hasForwardProbingRegistrationTlv = true;
372 break;
373
374 case SubTlv::kEnhAckConfig:
375 VerifyOrExit(!hasForwardProbingRegistrationTlv && !hasEnhAckProbingTlv, error = kErrorParse);
376 VerifyOrExit(tlv.GetLength() >= sizeof(EnhAckFlags), error = kErrorParse);
377 SuccessOrExit(aMessage.Read(pos, enhAckFlags));
378 pos += sizeof(enhAckFlags);
379 SuccessOrExit(error = ReadTypeIdFlagsFromMessage(
380 aMessage, pos, static_cast<uint16_t>(offset + index + tlv.GetSize()), metrics));
381 hasEnhAckProbingTlv = true;
382 break;
383
384 default:
385 break;
386 }
387
388 index += tlv.GetSize();
389 }
390
391 if (hasForwardProbingRegistrationTlv)
392 {
393 aStatus = ConfigureForwardTrackingSeries(seriesId, seriesFlags, metrics, aNeighbor);
394 }
395 else if (hasEnhAckProbingTlv)
396 {
397 aStatus = ConfigureEnhAckProbing(enhAckFlags, metrics, aNeighbor);
398 }
399
400 exit:
401 return error;
402 }
403 #endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
404
HandleManagementResponse(const Message & aMessage,const Ip6::Address & aAddress)405 Error LinkMetrics::HandleManagementResponse(const Message &aMessage, const Ip6::Address &aAddress)
406 {
407 Error error = kErrorNone;
408 Tlv tlv;
409 uint16_t offset;
410 uint16_t length;
411 uint16_t index = 0;
412 Status status;
413 bool hasStatus = false;
414
415 VerifyOrExit(mMgmtResponseCallback != nullptr);
416
417 SuccessOrExit(error = Tlv::FindTlvValueOffset(aMessage, Mle::Tlv::Type::kLinkMetricsManagement, offset, length));
418
419 while (index < length)
420 {
421 SuccessOrExit(aMessage.Read(offset + index, tlv));
422
423 switch (tlv.GetType())
424 {
425 case SubTlv::kStatus:
426 VerifyOrExit(!hasStatus, error = kErrorParse);
427 VerifyOrExit(tlv.GetLength() == sizeof(status), error = kErrorParse);
428 SuccessOrExit(aMessage.Read(offset + index + sizeof(tlv), status));
429 hasStatus = true;
430 break;
431
432 default:
433 break;
434 }
435
436 index += tlv.GetSize();
437 }
438
439 VerifyOrExit(hasStatus, error = kErrorParse);
440
441 mMgmtResponseCallback(&aAddress, status, mMgmtResponseCallbackContext);
442
443 exit:
444 return error;
445 }
446
HandleReport(const Message & aMessage,uint16_t aOffset,uint16_t aLength,const Ip6::Address & aAddress)447 void LinkMetrics::HandleReport(const Message & aMessage,
448 uint16_t aOffset,
449 uint16_t aLength,
450 const Ip6::Address &aAddress)
451 {
452 Error error = kErrorNone;
453 MetricsValues values;
454 uint8_t rawValue;
455 uint16_t pos = aOffset;
456 uint16_t endPos = aOffset + aLength;
457 Tlv tlv;
458 TypeIdFlags typeIdFlags;
459 bool hasStatus = false;
460 bool hasReport = false;
461 Status status;
462
463 OT_UNUSED_VARIABLE(error);
464
465 VerifyOrExit(mReportCallback != nullptr);
466
467 values.Clear();
468
469 while (pos < endPos)
470 {
471 SuccessOrExit(aMessage.Read(pos, tlv));
472 VerifyOrExit(tlv.GetType() == SubTlv::kReport);
473 pos += sizeof(Tlv);
474
475 VerifyOrExit(pos + tlv.GetLength() <= endPos, error = kErrorParse);
476
477 switch (tlv.GetType())
478 {
479 case SubTlv::kStatus:
480 // There should be either: one Status TLV or some Report-Sub TLVs
481 VerifyOrExit(!hasStatus && !hasReport, error = kErrorDrop);
482 VerifyOrExit(tlv.GetLength() == sizeof(status), error = kErrorParse);
483 SuccessOrExit(aMessage.Read(pos, status));
484 hasStatus = true;
485 pos += sizeof(status);
486 break;
487
488 case SubTlv::kReport:
489 // There shouldn't be any Report-Sub TLV when there's a Status TLV
490 VerifyOrExit(!hasStatus, error = kErrorDrop);
491 VerifyOrExit(tlv.GetLength() > sizeof(typeIdFlags), error = kErrorParse);
492 SuccessOrExit(aMessage.Read(pos, typeIdFlags));
493
494 if (typeIdFlags.IsExtendedFlagSet())
495 {
496 pos += tlv.GetLength(); // Skip the whole sub-TLV if `E` flag is set
497 continue;
498 }
499
500 hasReport = true;
501 pos += sizeof(TypeIdFlags);
502
503 switch (typeIdFlags.GetRawValue())
504 {
505 case TypeIdFlags::kPdu:
506 values.GetMetrics().mPduCount = true;
507 SuccessOrExit(aMessage.Read(pos, values.mPduCountValue));
508 pos += sizeof(uint32_t);
509 otLogDebgMle(" - PDU Counter: %d (Count/Summation)", values.mPduCountValue);
510 break;
511
512 case TypeIdFlags::kLqi:
513 values.GetMetrics().mLqi = true;
514 SuccessOrExit(aMessage.Read(pos, values.mLqiValue));
515 pos += sizeof(uint8_t);
516 otLogDebgMle(" - LQI: %d (Exponential Moving Average)", values.mLqiValue);
517 break;
518
519 case TypeIdFlags::kLinkMargin:
520 values.GetMetrics().mLinkMargin = true;
521 SuccessOrExit(aMessage.Read(pos, rawValue));
522 // Reverse operation for linear scale, map from [0, 255] to [0, 130]
523 values.mLinkMarginValue = rawValue * 130 / 255;
524 pos += sizeof(uint8_t);
525 otLogDebgMle(" - Margin: %d (dB) (Exponential Moving Average)", values.mLinkMarginValue);
526 break;
527
528 case TypeIdFlags::kRssi:
529 values.GetMetrics().mRssi = true;
530 SuccessOrExit(aMessage.Read(pos, rawValue));
531 // Reverse operation for linear scale, map from [0, 255] to [-130, 0]
532 values.mRssiValue = rawValue * 130 / 255 - 130;
533 pos += sizeof(uint8_t);
534 otLogDebgMle(" - RSSI: %d (dBm) (Exponential Moving Average)", values.mRssiValue);
535 break;
536
537 default:
538 break;
539 }
540 break;
541 }
542 }
543
544 if (hasStatus)
545 {
546 mReportCallback(&aAddress, nullptr, status, mReportCallbackContext);
547 }
548 else if (hasReport)
549 {
550 mReportCallback(&aAddress, &values, OT_LINK_METRICS_STATUS_SUCCESS, mReportCallbackContext);
551 }
552
553 exit:
554 otLogDebgMle("HandleReport, error:%s", ErrorToString(error));
555 return;
556 }
557
HandleLinkProbe(const Message & aMessage,uint8_t & aSeriesId)558 Error LinkMetrics::HandleLinkProbe(const Message &aMessage, uint8_t &aSeriesId)
559 {
560 Error error = kErrorNone;
561 uint16_t offset;
562 uint16_t length;
563
564 SuccessOrExit(error = Tlv::FindTlvValueOffset(aMessage, Mle::Tlv::Type::kLinkProbe, offset, length));
565 VerifyOrExit(length >= sizeof(aSeriesId), error = kErrorParse);
566 error = aMessage.Read(offset, aSeriesId);
567
568 exit:
569 return error;
570 }
571
SetReportCallback(ReportCallback aCallback,void * aContext)572 void LinkMetrics::SetReportCallback(ReportCallback aCallback, void *aContext)
573 {
574 mReportCallback = aCallback;
575 mReportCallbackContext = aContext;
576 }
577
SetMgmtResponseCallback(MgmtResponseCallback aCallback,void * aContext)578 void LinkMetrics::SetMgmtResponseCallback(MgmtResponseCallback aCallback, void *aContext)
579 {
580 mMgmtResponseCallback = aCallback;
581 mMgmtResponseCallbackContext = aContext;
582 }
583
SetEnhAckProbingCallback(EnhAckProbingIeReportCallback aCallback,void * aContext)584 void LinkMetrics::SetEnhAckProbingCallback(EnhAckProbingIeReportCallback aCallback, void *aContext)
585 {
586 mEnhAckProbingIeReportCallback = aCallback;
587 mEnhAckProbingIeReportCallbackContext = aContext;
588 }
589
ProcessEnhAckIeData(const uint8_t * aData,uint8_t aLength,const Neighbor & aNeighbor)590 void LinkMetrics::ProcessEnhAckIeData(const uint8_t *aData, uint8_t aLength, const Neighbor &aNeighbor)
591 {
592 MetricsValues values;
593 uint8_t idx = 0;
594
595 VerifyOrExit(mEnhAckProbingIeReportCallback != nullptr);
596
597 values.SetMetrics(aNeighbor.GetEnhAckProbingMetrics());
598
599 if (values.GetMetrics().mLqi && idx < aLength)
600 {
601 values.mLqiValue = aData[idx++];
602 }
603 if (values.GetMetrics().mLinkMargin && idx < aLength)
604 {
605 values.mLinkMarginValue = aData[idx++];
606 }
607 if (values.GetMetrics().mRssi && idx < aLength)
608 {
609 values.mRssiValue = aData[idx++];
610 }
611
612 mEnhAckProbingIeReportCallback(aNeighbor.GetRloc16(), &aNeighbor.GetExtAddress(), &values,
613 mEnhAckProbingIeReportCallbackContext);
614
615 exit:
616 return;
617 }
618
SendLinkMetricsQuery(const Ip6::Address & aDestination,uint8_t aSeriesId,const TypeIdFlags * aTypeIdFlags,uint8_t aTypeIdFlagsCount)619 Error LinkMetrics::SendLinkMetricsQuery(const Ip6::Address &aDestination,
620 uint8_t aSeriesId,
621 const TypeIdFlags * aTypeIdFlags,
622 uint8_t aTypeIdFlagsCount)
623 {
624 // LinkMetricsQuery Tlv + LinkMetricsQueryId sub-TLV (value-length: 1 byte) +
625 // LinkMetricsQueryOptions sub-TLV (value-length: `kMaxTypeIdFlags` bytes)
626 constexpr uint16_t kBufferSize = sizeof(Tlv) * 3 + sizeof(uint8_t) + sizeof(TypeIdFlags) * kMaxTypeIdFlags;
627
628 Error error = kErrorNone;
629 QueryOptionsSubTlv queryOptionsTlv;
630 uint8_t length = 0;
631 static const uint8_t tlvs[] = {Mle::Tlv::kLinkMetricsReport};
632 uint8_t buf[kBufferSize];
633 Tlv * tlv = reinterpret_cast<Tlv *>(buf);
634 Tlv subTlv;
635
636 // Link Metrics Query TLV
637 tlv->SetType(Mle::Tlv::kLinkMetricsQuery);
638 length += sizeof(Tlv);
639
640 // Link Metrics Query ID sub-TLV
641 subTlv.SetType(SubTlv::kQueryId);
642 subTlv.SetLength(sizeof(uint8_t));
643 memcpy(buf + length, &subTlv, sizeof(subTlv));
644 length += sizeof(subTlv);
645 memcpy(buf + length, &aSeriesId, sizeof(aSeriesId));
646 length += sizeof(aSeriesId);
647
648 // Link Metrics Query Options sub-TLV
649 if (aTypeIdFlagsCount > 0)
650 {
651 queryOptionsTlv.Init();
652 queryOptionsTlv.SetLength(aTypeIdFlagsCount * sizeof(TypeIdFlags));
653
654 memcpy(buf + length, &queryOptionsTlv, sizeof(queryOptionsTlv));
655 length += sizeof(queryOptionsTlv);
656 memcpy(buf + length, aTypeIdFlags, queryOptionsTlv.GetLength());
657 length += queryOptionsTlv.GetLength();
658 }
659
660 // Set Length for Link Metrics Report TLV
661 tlv->SetLength(length - sizeof(Tlv));
662
663 SuccessOrExit(error = Get<Mle::MleRouter>().SendDataRequest(aDestination, tlvs, sizeof(tlvs), 0, buf, length));
664
665 exit:
666 return error;
667 }
668
ConfigureForwardTrackingSeries(uint8_t aSeriesId,const SeriesFlags & aSeriesFlags,const Metrics & aMetrics,Neighbor & aNeighbor)669 Status LinkMetrics::ConfigureForwardTrackingSeries(uint8_t aSeriesId,
670 const SeriesFlags &aSeriesFlags,
671 const Metrics & aMetrics,
672 Neighbor & aNeighbor)
673 {
674 Status status = kStatusSuccess;
675
676 VerifyOrExit(0 < aSeriesId, status = kStatusOtherError);
677 if (aSeriesFlags.GetRawValue() == 0) // Remove the series
678 {
679 if (aSeriesId == kSeriesIdAllSeries) // Remove all
680 {
681 aNeighbor.RemoveAllForwardTrackingSeriesInfo();
682 }
683 else
684 {
685 SeriesInfo *seriesInfo = aNeighbor.RemoveForwardTrackingSeriesInfo(aSeriesId);
686 VerifyOrExit(seriesInfo != nullptr, status = kStatusSeriesIdNotRecognized);
687 mSeriesInfoPool.Free(*seriesInfo);
688 }
689 }
690 else // Add a new series
691 {
692 SeriesInfo *seriesInfo = aNeighbor.GetForwardTrackingSeriesInfo(aSeriesId);
693 VerifyOrExit(seriesInfo == nullptr, status = kStatusSeriesIdAlreadyRegistered);
694 seriesInfo = mSeriesInfoPool.Allocate();
695 VerifyOrExit(seriesInfo != nullptr, status = kStatusCannotSupportNewSeries);
696
697 seriesInfo->Init(aSeriesId, aSeriesFlags, aMetrics);
698
699 aNeighbor.AddForwardTrackingSeriesInfo(*seriesInfo);
700 }
701
702 exit:
703 return status;
704 }
705
706 #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
ConfigureEnhAckProbing(EnhAckFlags aEnhAckFlags,const Metrics & aMetrics,Neighbor & aNeighbor)707 Status LinkMetrics::ConfigureEnhAckProbing(EnhAckFlags aEnhAckFlags, const Metrics &aMetrics, Neighbor &aNeighbor)
708 {
709 Status status = kStatusSuccess;
710 Error error = kErrorNone;
711
712 VerifyOrExit(!aMetrics.mReserved, status = kStatusOtherError);
713
714 if (aEnhAckFlags == kEnhAckRegister)
715 {
716 VerifyOrExit(!aMetrics.mPduCount, status = kStatusOtherError);
717 VerifyOrExit(aMetrics.mLqi || aMetrics.mLinkMargin || aMetrics.mRssi, status = kStatusOtherError);
718 VerifyOrExit(!(aMetrics.mLqi && aMetrics.mLinkMargin && aMetrics.mRssi), status = kStatusOtherError);
719
720 error = Get<Radio>().ConfigureEnhAckProbing(aMetrics, aNeighbor.GetRloc16(), aNeighbor.GetExtAddress());
721 }
722 else if (aEnhAckFlags == kEnhAckClear)
723 {
724 VerifyOrExit(!aMetrics.mLqi && !aMetrics.mLinkMargin && !aMetrics.mRssi, status = kStatusOtherError);
725 error = Get<Radio>().ConfigureEnhAckProbing(aMetrics, aNeighbor.GetRloc16(), aNeighbor.GetExtAddress());
726 }
727 else
728 {
729 status = kStatusOtherError;
730 }
731
732 VerifyOrExit(error == kErrorNone, status = kStatusOtherError);
733
734 exit:
735 return status;
736 }
737 #endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
738
GetNeighborFromLinkLocalAddr(const Ip6::Address & aDestination)739 Neighbor *LinkMetrics::GetNeighborFromLinkLocalAddr(const Ip6::Address &aDestination)
740 {
741 Neighbor * neighbor = nullptr;
742 Mac::Address macAddress;
743
744 VerifyOrExit(aDestination.IsLinkLocal());
745 aDestination.GetIid().ConvertToMacAddress(macAddress);
746 neighbor = Get<NeighborTable>().FindNeighbor(macAddress);
747
748 exit:
749 return neighbor;
750 }
751
ReadTypeIdFlagsFromMessage(const Message & aMessage,uint8_t aStartPos,uint8_t aEndPos,Metrics & aMetrics)752 Error LinkMetrics::ReadTypeIdFlagsFromMessage(const Message &aMessage,
753 uint8_t aStartPos,
754 uint8_t aEndPos,
755 Metrics & aMetrics)
756 {
757 Error error = kErrorNone;
758
759 memset(&aMetrics, 0, sizeof(aMetrics));
760
761 for (uint16_t pos = aStartPos; pos < aEndPos; pos += sizeof(TypeIdFlags))
762 {
763 TypeIdFlags typeIdFlags;
764
765 SuccessOrExit(aMessage.Read(pos, typeIdFlags));
766
767 switch (typeIdFlags.GetRawValue())
768 {
769 case TypeIdFlags::kPdu:
770 VerifyOrExit(!aMetrics.mPduCount, error = kErrorParse);
771 aMetrics.mPduCount = true;
772 break;
773
774 case TypeIdFlags::kLqi:
775 VerifyOrExit(!aMetrics.mLqi, error = kErrorParse);
776 aMetrics.mLqi = true;
777 break;
778
779 case TypeIdFlags::kLinkMargin:
780 VerifyOrExit(!aMetrics.mLinkMargin, error = kErrorParse);
781 aMetrics.mLinkMargin = true;
782 break;
783
784 case TypeIdFlags::kRssi:
785 VerifyOrExit(!aMetrics.mRssi, error = kErrorParse);
786 aMetrics.mRssi = true;
787 break;
788
789 default:
790 if (typeIdFlags.IsExtendedFlagSet())
791 {
792 pos += sizeof(uint8_t); // Skip the additional second flags byte.
793 }
794 else
795 {
796 aMetrics.mReserved = true;
797 }
798 break;
799 }
800 }
801
802 exit:
803 return error;
804 }
805
AppendReportSubTlvToMessage(Message & aMessage,uint8_t & aLength,const MetricsValues & aValues)806 Error LinkMetrics::AppendReportSubTlvToMessage(Message &aMessage, uint8_t &aLength, const MetricsValues &aValues)
807 {
808 Error error = kErrorNone;
809 ReportSubTlv metric;
810
811 aLength = 0;
812
813 // Link Metrics Report sub-TLVs
814 if (aValues.mMetrics.mPduCount)
815 {
816 metric.Init();
817 metric.SetMetricsTypeId(TypeIdFlags(TypeIdFlags::kPdu));
818 metric.SetMetricsValue32(aValues.mPduCountValue);
819 SuccessOrExit(error = aMessage.AppendBytes(&metric, metric.GetSize()));
820 aLength += metric.GetSize();
821 }
822
823 if (aValues.mMetrics.mLqi)
824 {
825 metric.Init();
826 metric.SetMetricsTypeId(TypeIdFlags(TypeIdFlags::kLqi));
827 metric.SetMetricsValue8(aValues.mLqiValue);
828 SuccessOrExit(error = aMessage.AppendBytes(&metric, metric.GetSize()));
829 aLength += metric.GetSize();
830 }
831
832 if (aValues.mMetrics.mLinkMargin)
833 {
834 metric.Init();
835 metric.SetMetricsTypeId(TypeIdFlags(TypeIdFlags::kLinkMargin));
836 metric.SetMetricsValue8(aValues.mLinkMarginValue);
837 SuccessOrExit(error = aMessage.AppendBytes(&metric, metric.GetSize()));
838 aLength += metric.GetSize();
839 }
840
841 if (aValues.mMetrics.mRssi)
842 {
843 metric.Init();
844 metric.SetMetricsTypeId(TypeIdFlags(TypeIdFlags::kRssi));
845 metric.SetMetricsValue8(aValues.mRssiValue);
846 SuccessOrExit(error = aMessage.AppendBytes(&metric, metric.GetSize()));
847 aLength += metric.GetSize();
848 }
849
850 exit:
851 return error;
852 }
853
AppendStatusSubTlvToMessage(Message & aMessage,uint8_t & aLength,Status aStatus)854 Error LinkMetrics::AppendStatusSubTlvToMessage(Message &aMessage, uint8_t &aLength, Status aStatus)
855 {
856 Error error = kErrorNone;
857 Tlv statusTlv;
858
859 statusTlv.SetType(SubTlv::kStatus);
860 statusTlv.SetLength(sizeof(uint8_t));
861 SuccessOrExit(error = aMessage.AppendBytes(&statusTlv, sizeof(statusTlv)));
862 SuccessOrExit(error = aMessage.AppendBytes(&aStatus, sizeof(aStatus)));
863 aLength += statusTlv.GetSize();
864
865 exit:
866 return error;
867 }
868
869 } // namespace LinkMetrics
870 } // namespace ot
871
872 #endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE || OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
873