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