• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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