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