• 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 "instance/instance.hpp"
39 
40 namespace ot {
41 namespace LinkMetrics {
42 
43 RegisterLogModule("LinkMetrics");
44 
45 static constexpr uint8_t kQueryIdSingleProbe = 0;   // This query ID represents Single Probe.
46 static constexpr uint8_t kSeriesIdAllSeries  = 255; // This series ID represents all series.
47 
48 // Constants for scaling Link Margin and RSSI to raw value
49 static constexpr uint8_t kMaxLinkMargin = 130;
50 static constexpr int32_t kMinRssi       = -130;
51 static constexpr int32_t kMaxRssi       = 0;
52 
53 #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE
54 
Initiator(Instance & aInstance)55 Initiator::Initiator(Instance &aInstance)
56     : InstanceLocator(aInstance)
57 {
58 }
59 
Query(const Ip6::Address & aDestination,uint8_t aSeriesId,const Metrics * aMetrics)60 Error Initiator::Query(const Ip6::Address &aDestination, uint8_t aSeriesId, const Metrics *aMetrics)
61 {
62     Error     error;
63     Neighbor *neighbor;
64     QueryInfo info;
65 
66     SuccessOrExit(error = FindNeighbor(aDestination, neighbor));
67 
68     info.Clear();
69     info.mSeriesId = aSeriesId;
70 
71     if (aMetrics != nullptr)
72     {
73         info.mTypeIdCount = aMetrics->ConvertToTypeIds(info.mTypeIds);
74     }
75 
76     if (aSeriesId != 0)
77     {
78         VerifyOrExit(info.mTypeIdCount == 0, error = kErrorInvalidArgs);
79     }
80 
81     error = Get<Mle::Mle>().SendDataRequestForLinkMetricsReport(aDestination, info);
82 
83 exit:
84     return error;
85 }
86 
AppendLinkMetricsQueryTlv(Message & aMessage,const QueryInfo & aInfo)87 Error Initiator::AppendLinkMetricsQueryTlv(Message &aMessage, const QueryInfo &aInfo)
88 {
89     Error error = kErrorNone;
90     Tlv   tlv;
91 
92     // The MLE Link Metrics Query TLV has two sub-TLVs:
93     // - Query ID sub-TLV with series ID as value.
94     // - Query Options sub-TLV with Type IDs as value.
95 
96     tlv.SetType(Mle::Tlv::kLinkMetricsQuery);
97     tlv.SetLength(sizeof(Tlv) + sizeof(uint8_t) + ((aInfo.mTypeIdCount == 0) ? 0 : (sizeof(Tlv) + aInfo.mTypeIdCount)));
98 
99     SuccessOrExit(error = aMessage.Append(tlv));
100 
101     SuccessOrExit(error = Tlv::Append<QueryIdSubTlv>(aMessage, aInfo.mSeriesId));
102 
103     if (aInfo.mTypeIdCount != 0)
104     {
105         QueryOptionsSubTlv queryOptionsTlv;
106 
107         queryOptionsTlv.Init();
108         queryOptionsTlv.SetLength(aInfo.mTypeIdCount);
109         SuccessOrExit(error = aMessage.Append(queryOptionsTlv));
110         SuccessOrExit(error = aMessage.AppendBytes(aInfo.mTypeIds, aInfo.mTypeIdCount));
111     }
112 
113 exit:
114     return error;
115 }
116 
HandleReport(const Message & aMessage,OffsetRange & aOffsetRange,const Ip6::Address & aAddress)117 void Initiator::HandleReport(const Message &aMessage, OffsetRange &aOffsetRange, const Ip6::Address &aAddress)
118 {
119     Error           error     = kErrorNone;
120     bool            hasStatus = false;
121     bool            hasReport = false;
122     Tlv::ParsedInfo tlvInfo;
123     ReportSubTlv    reportTlv;
124     MetricsValues   values;
125     uint8_t         status;
126     uint8_t         typeId;
127 
128     OT_UNUSED_VARIABLE(error);
129 
130     VerifyOrExit(mReportCallback.IsSet());
131 
132     values.Clear();
133 
134     for (; !aOffsetRange.IsEmpty(); aOffsetRange.AdvanceOffset(tlvInfo.GetSize()))
135     {
136         SuccessOrExit(error = tlvInfo.ParseFrom(aMessage, aOffsetRange));
137 
138         if (tlvInfo.mIsExtended)
139         {
140             continue;
141         }
142 
143         // The report must contain either:
144         // - One or more Report Sub-TLVs (in case of success), or
145         // - A single Status Sub-TLV (in case of failure).
146 
147         switch (tlvInfo.mType)
148         {
149         case StatusSubTlv::kType:
150             VerifyOrExit(!hasStatus && !hasReport, error = kErrorDrop);
151             SuccessOrExit(error = Tlv::Read<StatusSubTlv>(aMessage, aOffsetRange.GetOffset(), status));
152             hasStatus = true;
153             break;
154 
155         case ReportSubTlv::kType:
156             VerifyOrExit(!hasStatus, error = kErrorDrop);
157 
158             // Read the report sub-TLV assuming minimum length
159             SuccessOrExit(error = aMessage.Read(aOffsetRange, &reportTlv, sizeof(Tlv) + ReportSubTlv::kMinLength));
160             VerifyOrExit(reportTlv.IsValid(), error = kErrorParse);
161             hasReport = true;
162 
163             typeId = reportTlv.GetMetricsTypeId();
164 
165             if (TypeId::IsExtended(typeId))
166             {
167                 // Skip the sub-TLV if `E` flag is set.
168                 break;
169             }
170 
171             if (TypeId::GetValueLength(typeId) > sizeof(uint8_t))
172             {
173                 // If Type ID indicates metric value has 4 bytes length, we
174                 // read the full `reportTlv`.
175                 SuccessOrExit(error = aMessage.Read(aOffsetRange.GetOffset(), reportTlv));
176             }
177 
178             switch (typeId)
179             {
180             case TypeId::kPdu:
181                 values.mMetrics.mPduCount = true;
182                 values.mPduCountValue     = reportTlv.GetMetricsValue32();
183                 LogDebg(" - PDU Counter: %lu (Count/Summation)", ToUlong(values.mPduCountValue));
184                 break;
185 
186             case TypeId::kLqi:
187                 values.mMetrics.mLqi = true;
188                 values.mLqiValue     = reportTlv.GetMetricsValue8();
189                 LogDebg(" - LQI: %u (Exponential Moving Average)", values.mLqiValue);
190                 break;
191 
192             case TypeId::kLinkMargin:
193                 values.mMetrics.mLinkMargin = true;
194                 values.mLinkMarginValue     = ScaleRawValueToLinkMargin(reportTlv.GetMetricsValue8());
195                 LogDebg(" - Margin: %u (dB) (Exponential Moving Average)", values.mLinkMarginValue);
196                 break;
197 
198             case TypeId::kRssi:
199                 values.mMetrics.mRssi = true;
200                 values.mRssiValue     = ScaleRawValueToRssi(reportTlv.GetMetricsValue8());
201                 LogDebg(" - RSSI: %u (dBm) (Exponential Moving Average)", values.mRssiValue);
202                 break;
203             }
204 
205             break;
206         }
207     }
208 
209     VerifyOrExit(hasStatus || hasReport);
210 
211     mReportCallback.Invoke(&aAddress, hasStatus ? nullptr : &values,
212                            hasStatus ? MapEnum(static_cast<Status>(status)) : MapEnum(kStatusSuccess));
213 
214 exit:
215     LogDebg("HandleReport, error:%s", ErrorToString(error));
216 }
217 
SendMgmtRequestForwardTrackingSeries(const Ip6::Address & aDestination,uint8_t aSeriesId,const SeriesFlags & aSeriesFlags,const Metrics * aMetrics)218 Error Initiator::SendMgmtRequestForwardTrackingSeries(const Ip6::Address &aDestination,
219                                                       uint8_t             aSeriesId,
220                                                       const SeriesFlags  &aSeriesFlags,
221                                                       const Metrics      *aMetrics)
222 {
223     Error               error;
224     Neighbor           *neighbor;
225     uint8_t             typeIdCount = 0;
226     FwdProbingRegSubTlv fwdProbingSubTlv;
227 
228     SuccessOrExit(error = FindNeighbor(aDestination, neighbor));
229 
230     VerifyOrExit(aSeriesId > kQueryIdSingleProbe, error = kErrorInvalidArgs);
231 
232     fwdProbingSubTlv.Init();
233     fwdProbingSubTlv.SetSeriesId(aSeriesId);
234     fwdProbingSubTlv.SetSeriesFlagsMask(aSeriesFlags.ConvertToMask());
235 
236     if (aMetrics != nullptr)
237     {
238         typeIdCount = aMetrics->ConvertToTypeIds(fwdProbingSubTlv.GetTypeIds());
239     }
240 
241     fwdProbingSubTlv.SetLength(sizeof(aSeriesId) + sizeof(uint8_t) + typeIdCount);
242 
243     error = Get<Mle::Mle>().SendLinkMetricsManagementRequest(aDestination, fwdProbingSubTlv);
244 
245 exit:
246     LogDebg("SendMgmtRequestForwardTrackingSeries, error:%s, Series ID:%u", ErrorToString(error), aSeriesId);
247     return error;
248 }
249 
SendMgmtRequestEnhAckProbing(const Ip6::Address & aDestination,EnhAckFlags aEnhAckFlags,const Metrics * aMetrics)250 Error Initiator::SendMgmtRequestEnhAckProbing(const Ip6::Address &aDestination,
251                                               EnhAckFlags         aEnhAckFlags,
252                                               const Metrics      *aMetrics)
253 {
254     Error              error;
255     Neighbor          *neighbor;
256     uint8_t            typeIdCount = 0;
257     EnhAckConfigSubTlv enhAckConfigSubTlv;
258 
259     SuccessOrExit(error = FindNeighbor(aDestination, neighbor));
260 
261     if (aEnhAckFlags == kEnhAckClear)
262     {
263         VerifyOrExit(aMetrics == nullptr, error = kErrorInvalidArgs);
264     }
265 
266     enhAckConfigSubTlv.Init();
267     enhAckConfigSubTlv.SetEnhAckFlags(aEnhAckFlags);
268 
269     if (aMetrics != nullptr)
270     {
271         typeIdCount = aMetrics->ConvertToTypeIds(enhAckConfigSubTlv.GetTypeIds());
272     }
273 
274     enhAckConfigSubTlv.SetLength(EnhAckConfigSubTlv::kMinLength + typeIdCount);
275 
276     error = Get<Mle::Mle>().SendLinkMetricsManagementRequest(aDestination, enhAckConfigSubTlv);
277 
278     if (aMetrics != nullptr)
279     {
280         neighbor->SetEnhAckProbingMetrics(*aMetrics);
281     }
282     else
283     {
284         Metrics metrics;
285 
286         metrics.Clear();
287         neighbor->SetEnhAckProbingMetrics(metrics);
288     }
289 
290 exit:
291     return error;
292 }
293 
HandleManagementResponse(const Message & aMessage,const Ip6::Address & aAddress)294 Error Initiator::HandleManagementResponse(const Message &aMessage, const Ip6::Address &aAddress)
295 {
296     Error           error = kErrorNone;
297     OffsetRange     offsetRange;
298     Tlv::ParsedInfo tlvInfo;
299     uint8_t         status;
300     bool            hasStatus = false;
301 
302     VerifyOrExit(mMgmtResponseCallback.IsSet());
303 
304     SuccessOrExit(error = Tlv::FindTlvValueOffsetRange(aMessage, Mle::Tlv::Type::kLinkMetricsManagement, offsetRange));
305 
306     for (; !offsetRange.IsEmpty(); offsetRange.AdvanceOffset(tlvInfo.GetSize()))
307     {
308         SuccessOrExit(error = tlvInfo.ParseFrom(aMessage, offsetRange));
309 
310         if (tlvInfo.mIsExtended)
311         {
312             continue;
313         }
314 
315         switch (tlvInfo.mType)
316         {
317         case StatusSubTlv::kType:
318             VerifyOrExit(!hasStatus, error = kErrorParse);
319             SuccessOrExit(error = Tlv::Read<StatusSubTlv>(aMessage, offsetRange.GetOffset(), status));
320             hasStatus = true;
321             break;
322 
323         default:
324             break;
325         }
326     }
327 
328     VerifyOrExit(hasStatus, error = kErrorParse);
329 
330     mMgmtResponseCallback.Invoke(&aAddress, MapEnum(static_cast<Status>(status)));
331 
332 exit:
333     return error;
334 }
335 
SendLinkProbe(const Ip6::Address & aDestination,uint8_t aSeriesId,uint8_t aLength)336 Error Initiator::SendLinkProbe(const Ip6::Address &aDestination, uint8_t aSeriesId, uint8_t aLength)
337 {
338     Error     error;
339     uint8_t   buf[kLinkProbeMaxLen];
340     Neighbor *neighbor;
341 
342     SuccessOrExit(error = FindNeighbor(aDestination, neighbor));
343 
344     VerifyOrExit(aLength <= kLinkProbeMaxLen && aSeriesId != kQueryIdSingleProbe && aSeriesId != kSeriesIdAllSeries,
345                  error = kErrorInvalidArgs);
346 
347     error = Get<Mle::Mle>().SendLinkProbe(aDestination, aSeriesId, buf, aLength);
348 exit:
349     LogDebg("SendLinkProbe, error:%s, Series ID:%u", ErrorToString(error), aSeriesId);
350     return error;
351 }
352 
ProcessEnhAckIeData(const uint8_t * aData,uint8_t aLength,const Neighbor & aNeighbor)353 void Initiator::ProcessEnhAckIeData(const uint8_t *aData, uint8_t aLength, const Neighbor &aNeighbor)
354 {
355     MetricsValues values;
356     uint8_t       idx = 0;
357 
358     VerifyOrExit(mEnhAckProbingIeReportCallback.IsSet());
359 
360     values.SetMetrics(aNeighbor.GetEnhAckProbingMetrics());
361 
362     if (values.GetMetrics().mLqi && idx < aLength)
363     {
364         values.mLqiValue = aData[idx++];
365     }
366     if (values.GetMetrics().mLinkMargin && idx < aLength)
367     {
368         values.mLinkMarginValue = ScaleRawValueToLinkMargin(aData[idx++]);
369     }
370     if (values.GetMetrics().mRssi && idx < aLength)
371     {
372         values.mRssiValue = ScaleRawValueToRssi(aData[idx++]);
373     }
374 
375     mEnhAckProbingIeReportCallback.Invoke(aNeighbor.GetRloc16(), &aNeighbor.GetExtAddress(), &values);
376 
377 exit:
378     return;
379 }
380 
FindNeighbor(const Ip6::Address & aDestination,Neighbor * & aNeighbor)381 Error Initiator::FindNeighbor(const Ip6::Address &aDestination, Neighbor *&aNeighbor)
382 {
383     Error        error = kErrorUnknownNeighbor;
384     Mac::Address macAddress;
385 
386     aNeighbor = nullptr;
387 
388     VerifyOrExit(aDestination.IsLinkLocalUnicast());
389     aDestination.GetIid().ConvertToMacAddress(macAddress);
390 
391     aNeighbor = Get<NeighborTable>().FindNeighbor(macAddress);
392     VerifyOrExit(aNeighbor != nullptr);
393 
394     VerifyOrExit(aNeighbor->GetVersion() >= kThreadVersion1p2, error = kErrorNotCapable);
395     error = kErrorNone;
396 
397 exit:
398     return error;
399 }
400 
401 #endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE
402 
403 #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
Subject(Instance & aInstance)404 Subject::Subject(Instance &aInstance)
405     : InstanceLocator(aInstance)
406 {
407 }
408 
AppendReport(Message & aMessage,const Message & aRequestMessage,Neighbor & aNeighbor)409 Error Subject::AppendReport(Message &aMessage, const Message &aRequestMessage, Neighbor &aNeighbor)
410 {
411     Error           error = kErrorNone;
412     Tlv             tlv;
413     Tlv::ParsedInfo tlvInfo;
414     uint8_t         queryId;
415     bool            hasQueryId = false;
416     uint16_t        length;
417     uint16_t        offset;
418     OffsetRange     offsetRange;
419     MetricsValues   values;
420 
421     values.Clear();
422 
423     // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
424     // Parse MLE Link Metrics Query TLV and its sub-TLVs from
425     // `aRequestMessage`.
426 
427     SuccessOrExit(error =
428                       Tlv::FindTlvValueOffsetRange(aRequestMessage, Mle::Tlv::Type::kLinkMetricsQuery, offsetRange));
429 
430     for (; !offsetRange.IsEmpty(); offsetRange.AdvanceOffset(tlvInfo.GetSize()))
431     {
432         SuccessOrExit(error = tlvInfo.ParseFrom(aRequestMessage, offsetRange));
433 
434         if (tlvInfo.mIsExtended)
435         {
436             continue;
437         }
438 
439         switch (tlvInfo.mType)
440         {
441         case SubTlv::kQueryId:
442             SuccessOrExit(error =
443                               Tlv::Read<QueryIdSubTlv>(aRequestMessage, tlvInfo.mTlvOffsetRange.GetOffset(), queryId));
444             hasQueryId = true;
445             break;
446 
447         case SubTlv::kQueryOptions:
448             SuccessOrExit(error =
449                               ReadTypeIdsFromMessage(aRequestMessage, tlvInfo.mValueOffsetRange, values.GetMetrics()));
450             break;
451 
452         default:
453             break;
454         }
455     }
456 
457     VerifyOrExit(hasQueryId, error = kErrorParse);
458 
459     // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
460     // Append MLE Link Metrics Report TLV and its sub-TLVs to
461     // `aMessage`.
462 
463     offset = aMessage.GetLength();
464     tlv.SetType(Mle::Tlv::kLinkMetricsReport);
465     SuccessOrExit(error = aMessage.Append(tlv));
466 
467     if (queryId == kQueryIdSingleProbe)
468     {
469         values.mPduCountValue   = aRequestMessage.GetPsduCount();
470         values.mLqiValue        = aRequestMessage.GetAverageLqi();
471         values.mLinkMarginValue = Get<Mac::Mac>().ComputeLinkMargin(aRequestMessage.GetAverageRss());
472         values.mRssiValue       = aRequestMessage.GetAverageRss();
473         SuccessOrExit(error = AppendReportSubTlvToMessage(aMessage, values));
474     }
475     else
476     {
477         SeriesInfo *seriesInfo = aNeighbor.GetForwardTrackingSeriesInfo(queryId);
478 
479         if (seriesInfo == nullptr)
480         {
481             SuccessOrExit(error = Tlv::Append<StatusSubTlv>(aMessage, kStatusSeriesIdNotRecognized));
482         }
483         else if (seriesInfo->GetPduCount() == 0)
484         {
485             SuccessOrExit(error = Tlv::Append<StatusSubTlv>(aMessage, kStatusNoMatchingFramesReceived));
486         }
487         else
488         {
489             values.SetMetrics(seriesInfo->GetLinkMetrics());
490             values.mPduCountValue   = seriesInfo->GetPduCount();
491             values.mLqiValue        = seriesInfo->GetAverageLqi();
492             values.mLinkMarginValue = Get<Mac::Mac>().ComputeLinkMargin(seriesInfo->GetAverageRss());
493             values.mRssiValue       = seriesInfo->GetAverageRss();
494             SuccessOrExit(error = AppendReportSubTlvToMessage(aMessage, values));
495         }
496     }
497 
498     // Update the TLV length in message.
499     length = aMessage.GetLength() - offset - sizeof(Tlv);
500     tlv.SetLength(static_cast<uint8_t>(length));
501     aMessage.Write(offset, tlv);
502 
503 exit:
504     LogDebg("AppendReport, error:%s", ErrorToString(error));
505     return error;
506 }
507 
HandleManagementRequest(const Message & aMessage,Neighbor & aNeighbor,Status & aStatus)508 Error Subject::HandleManagementRequest(const Message &aMessage, Neighbor &aNeighbor, Status &aStatus)
509 {
510     Error               error = kErrorNone;
511     OffsetRange         offsetRange;
512     Tlv::ParsedInfo     tlvInfo;
513     FwdProbingRegSubTlv fwdProbingSubTlv;
514     EnhAckConfigSubTlv  enhAckConfigSubTlv;
515     Metrics             metrics;
516 
517     SuccessOrExit(error = Tlv::FindTlvValueOffsetRange(aMessage, Mle::Tlv::Type::kLinkMetricsManagement, offsetRange));
518 
519     // Set sub-TLV lengths to zero to indicate that we have
520     // not yet seen them in the message.
521     fwdProbingSubTlv.SetLength(0);
522     enhAckConfigSubTlv.SetLength(0);
523 
524     for (; !offsetRange.IsEmpty(); offsetRange.AdvanceOffset(tlvInfo.GetSize()))
525     {
526         uint16_t    minTlvSize;
527         Tlv        *subTlv;
528         OffsetRange tlvOffsetRange;
529 
530         SuccessOrExit(error = tlvInfo.ParseFrom(aMessage, offsetRange));
531 
532         if (tlvInfo.mIsExtended)
533         {
534             continue;
535         }
536 
537         tlvOffsetRange = tlvInfo.mTlvOffsetRange;
538 
539         switch (tlvInfo.mType)
540         {
541         case SubTlv::kFwdProbingReg:
542             subTlv     = &fwdProbingSubTlv;
543             minTlvSize = sizeof(Tlv) + FwdProbingRegSubTlv::kMinLength;
544             break;
545 
546         case SubTlv::kEnhAckConfig:
547             subTlv     = &enhAckConfigSubTlv;
548             minTlvSize = sizeof(Tlv) + EnhAckConfigSubTlv::kMinLength;
549             break;
550 
551         default:
552             continue;
553         }
554 
555         // Ensure message contains only one sub-TLV.
556         VerifyOrExit(fwdProbingSubTlv.GetLength() == 0, error = kErrorParse);
557         VerifyOrExit(enhAckConfigSubTlv.GetLength() == 0, error = kErrorParse);
558 
559         VerifyOrExit(tlvInfo.GetSize() >= minTlvSize, error = kErrorParse);
560 
561         // Read `subTlv` with its `minTlvSize`, followed by the Type IDs.
562         SuccessOrExit(error = aMessage.Read(tlvOffsetRange, subTlv, minTlvSize));
563 
564         tlvOffsetRange.AdvanceOffset(minTlvSize);
565         SuccessOrExit(error = ReadTypeIdsFromMessage(aMessage, tlvOffsetRange, metrics));
566     }
567 
568     if (fwdProbingSubTlv.GetLength() != 0)
569     {
570         aStatus = ConfigureForwardTrackingSeries(fwdProbingSubTlv.GetSeriesId(), fwdProbingSubTlv.GetSeriesFlagsMask(),
571                                                  metrics, aNeighbor);
572     }
573 
574     if (enhAckConfigSubTlv.GetLength() != 0)
575     {
576         aStatus = ConfigureEnhAckProbing(enhAckConfigSubTlv.GetEnhAckFlags(), metrics, aNeighbor);
577     }
578 
579 exit:
580     return error;
581 }
582 
HandleLinkProbe(const Message & aMessage,uint8_t & aSeriesId)583 Error Subject::HandleLinkProbe(const Message &aMessage, uint8_t &aSeriesId)
584 {
585     Error       error = kErrorNone;
586     OffsetRange offsetRange;
587 
588     SuccessOrExit(error = Tlv::FindTlvValueOffsetRange(aMessage, Mle::Tlv::Type::kLinkProbe, offsetRange));
589     error = aMessage.Read(offsetRange, aSeriesId);
590 
591 exit:
592     return error;
593 }
594 
AppendReportSubTlvToMessage(Message & aMessage,const MetricsValues & aValues)595 Error Subject::AppendReportSubTlvToMessage(Message &aMessage, const MetricsValues &aValues)
596 {
597     Error        error = kErrorNone;
598     ReportSubTlv reportTlv;
599 
600     reportTlv.Init();
601 
602     if (aValues.mMetrics.mPduCount)
603     {
604         reportTlv.SetMetricsTypeId(TypeId::kPdu);
605         reportTlv.SetMetricsValue32(aValues.mPduCountValue);
606         SuccessOrExit(error = reportTlv.AppendTo(aMessage));
607     }
608 
609     if (aValues.mMetrics.mLqi)
610     {
611         reportTlv.SetMetricsTypeId(TypeId::kLqi);
612         reportTlv.SetMetricsValue8(aValues.mLqiValue);
613         SuccessOrExit(error = reportTlv.AppendTo(aMessage));
614     }
615 
616     if (aValues.mMetrics.mLinkMargin)
617     {
618         reportTlv.SetMetricsTypeId(TypeId::kLinkMargin);
619         reportTlv.SetMetricsValue8(ScaleLinkMarginToRawValue(aValues.mLinkMarginValue));
620         SuccessOrExit(error = reportTlv.AppendTo(aMessage));
621     }
622 
623     if (aValues.mMetrics.mRssi)
624     {
625         reportTlv.SetMetricsTypeId(TypeId::kRssi);
626         reportTlv.SetMetricsValue8(ScaleRssiToRawValue(aValues.mRssiValue));
627         SuccessOrExit(error = reportTlv.AppendTo(aMessage));
628     }
629 
630 exit:
631     return error;
632 }
633 
Free(SeriesInfo & aSeriesInfo)634 void Subject::Free(SeriesInfo &aSeriesInfo) { mSeriesInfoPool.Free(aSeriesInfo); }
635 
ReadTypeIdsFromMessage(const Message & aMessage,const OffsetRange & aOffsetRange,Metrics & aMetrics)636 Error Subject::ReadTypeIdsFromMessage(const Message &aMessage, const OffsetRange &aOffsetRange, Metrics &aMetrics)
637 {
638     Error       error       = kErrorNone;
639     OffsetRange offsetRange = aOffsetRange;
640 
641     aMetrics.Clear();
642 
643     while (!offsetRange.IsEmpty())
644     {
645         uint8_t typeId;
646 
647         SuccessOrExit(aMessage.Read(offsetRange, typeId));
648 
649         switch (typeId)
650         {
651         case TypeId::kPdu:
652             VerifyOrExit(!aMetrics.mPduCount, error = kErrorParse);
653             aMetrics.mPduCount = true;
654             break;
655 
656         case TypeId::kLqi:
657             VerifyOrExit(!aMetrics.mLqi, error = kErrorParse);
658             aMetrics.mLqi = true;
659             break;
660 
661         case TypeId::kLinkMargin:
662             VerifyOrExit(!aMetrics.mLinkMargin, error = kErrorParse);
663             aMetrics.mLinkMargin = true;
664             break;
665 
666         case TypeId::kRssi:
667             VerifyOrExit(!aMetrics.mRssi, error = kErrorParse);
668             aMetrics.mRssi = true;
669             break;
670 
671         default:
672             if (TypeId::IsExtended(typeId))
673             {
674                 offsetRange.AdvanceOffset(sizeof(uint8_t)); // Skip the additional second byte.
675             }
676             else
677             {
678                 aMetrics.mReserved = true;
679             }
680             break;
681         }
682 
683         offsetRange.AdvanceOffset(sizeof(uint8_t));
684     }
685 
686 exit:
687     return error;
688 }
689 
ConfigureForwardTrackingSeries(uint8_t aSeriesId,uint8_t aSeriesFlagsMask,const Metrics & aMetrics,Neighbor & aNeighbor)690 Status Subject::ConfigureForwardTrackingSeries(uint8_t        aSeriesId,
691                                                uint8_t        aSeriesFlagsMask,
692                                                const Metrics &aMetrics,
693                                                Neighbor      &aNeighbor)
694 {
695     Status status = kStatusSuccess;
696 
697     VerifyOrExit(0 < aSeriesId, status = kStatusOtherError);
698 
699     if (aSeriesFlagsMask == 0) // Remove the series
700     {
701         if (aSeriesId == kSeriesIdAllSeries) // Remove all
702         {
703             aNeighbor.RemoveAllForwardTrackingSeriesInfo();
704         }
705         else
706         {
707             SeriesInfo *seriesInfo = aNeighbor.RemoveForwardTrackingSeriesInfo(aSeriesId);
708             VerifyOrExit(seriesInfo != nullptr, status = kStatusSeriesIdNotRecognized);
709             mSeriesInfoPool.Free(*seriesInfo);
710         }
711     }
712     else // Add a new series
713     {
714         SeriesInfo *seriesInfo = aNeighbor.GetForwardTrackingSeriesInfo(aSeriesId);
715         VerifyOrExit(seriesInfo == nullptr, status = kStatusSeriesIdAlreadyRegistered);
716         seriesInfo = mSeriesInfoPool.Allocate();
717         VerifyOrExit(seriesInfo != nullptr, status = kStatusCannotSupportNewSeries);
718 
719         seriesInfo->Init(aSeriesId, aSeriesFlagsMask, aMetrics);
720 
721         aNeighbor.AddForwardTrackingSeriesInfo(*seriesInfo);
722     }
723 
724 exit:
725     return status;
726 }
727 
ConfigureEnhAckProbing(uint8_t aEnhAckFlags,const Metrics & aMetrics,Neighbor & aNeighbor)728 Status Subject::ConfigureEnhAckProbing(uint8_t aEnhAckFlags, const Metrics &aMetrics, Neighbor &aNeighbor)
729 {
730     Status status = kStatusSuccess;
731     Error  error  = kErrorNone;
732 
733     VerifyOrExit(!aMetrics.mReserved, status = kStatusOtherError);
734 
735     if (aEnhAckFlags == kEnhAckRegister)
736     {
737         VerifyOrExit(!aMetrics.mPduCount, status = kStatusOtherError);
738         VerifyOrExit(aMetrics.mLqi || aMetrics.mLinkMargin || aMetrics.mRssi, status = kStatusOtherError);
739         VerifyOrExit(!(aMetrics.mLqi && aMetrics.mLinkMargin && aMetrics.mRssi), status = kStatusOtherError);
740 
741         error = Get<Radio>().ConfigureEnhAckProbing(aMetrics, aNeighbor.GetRloc16(), aNeighbor.GetExtAddress());
742     }
743     else if (aEnhAckFlags == kEnhAckClear)
744     {
745         VerifyOrExit(!aMetrics.mLqi && !aMetrics.mLinkMargin && !aMetrics.mRssi, status = kStatusOtherError);
746         error = Get<Radio>().ConfigureEnhAckProbing(aMetrics, aNeighbor.GetRloc16(), aNeighbor.GetExtAddress());
747     }
748     else
749     {
750         status = kStatusOtherError;
751     }
752 
753     VerifyOrExit(error == kErrorNone, status = kStatusOtherError);
754 
755 exit:
756     return status;
757 }
758 
759 #endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
760 
ScaleLinkMarginToRawValue(uint8_t aLinkMargin)761 uint8_t ScaleLinkMarginToRawValue(uint8_t aLinkMargin)
762 {
763     // Linearly scale Link Margin from [0, 130] to [0, 255].
764     // `kMaxLinkMargin = 130`.
765 
766     uint16_t value;
767 
768     value = Min(aLinkMargin, kMaxLinkMargin);
769     value = value * NumericLimits<uint8_t>::kMax;
770     value = DivideAndRoundToClosest<uint16_t>(value, kMaxLinkMargin);
771 
772     return static_cast<uint8_t>(value);
773 }
774 
ScaleRawValueToLinkMargin(uint8_t aRawValue)775 uint8_t ScaleRawValueToLinkMargin(uint8_t aRawValue)
776 {
777     // Scale back raw value of [0, 255] to Link Margin from [0, 130].
778 
779     uint16_t value = aRawValue;
780 
781     value = value * kMaxLinkMargin;
782     value = DivideAndRoundToClosest<uint16_t>(value, NumericLimits<uint8_t>::kMax);
783     return static_cast<uint8_t>(value);
784 }
785 
ScaleRssiToRawValue(int8_t aRssi)786 uint8_t ScaleRssiToRawValue(int8_t aRssi)
787 {
788     // Linearly scale RSSI from [-130, 0] to [0, 255].
789     // `kMinRssi = -130`, `kMaxRssi = 0`.
790 
791     int32_t value = aRssi;
792 
793     value = Clamp(value, kMinRssi, kMaxRssi) - kMinRssi;
794     value = value * NumericLimits<uint8_t>::kMax;
795     value = DivideAndRoundToClosest<int32_t>(value, kMaxRssi - kMinRssi);
796 
797     return static_cast<uint8_t>(value);
798 }
799 
ScaleRawValueToRssi(uint8_t aRawValue)800 int8_t ScaleRawValueToRssi(uint8_t aRawValue)
801 {
802     int32_t value = aRawValue;
803 
804     value = value * (kMaxRssi - kMinRssi);
805     value = DivideAndRoundToClosest<int32_t>(value, NumericLimits<uint8_t>::kMax);
806     value += kMinRssi;
807 
808     return ClampToInt8(value);
809 }
810 
811 } // namespace LinkMetrics
812 } // namespace ot
813 
814 #endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE || OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
815