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