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