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