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