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