| /* |
| * Copyright (c) 2020, The OpenThread Authors. |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. Neither the name of the copyright holder nor the |
| * names of its contributors may be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
| * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| * POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| /** |
| * @file |
| * This file includes definitions for Thread Link Metrics. |
| */ |
| |
| #include "link_metrics.hpp" |
| |
| #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE || OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE |
| |
| #include "common/code_utils.hpp" |
| #include "common/encoding.hpp" |
| #include "common/instance.hpp" |
| #include "common/locator_getters.hpp" |
| #include "common/log.hpp" |
| #include "common/num_utils.hpp" |
| #include "common/numeric_limits.hpp" |
| #include "mac/mac.hpp" |
| #include "thread/link_metrics_tlvs.hpp" |
| #include "thread/neighbor_table.hpp" |
| |
| namespace ot { |
| namespace LinkMetrics { |
| |
| RegisterLogModule("LinkMetrics"); |
| |
| static constexpr uint8_t kQueryIdSingleProbe = 0; // This query ID represents Single Probe. |
| static constexpr uint8_t kSeriesIdAllSeries = 255; // This series ID represents all series. |
| |
| // Constants for scaling Link Margin and RSSI to raw value |
| static constexpr uint8_t kMaxLinkMargin = 130; |
| static constexpr int32_t kMinRssi = -130; |
| static constexpr int32_t kMaxRssi = 0; |
| |
| #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE |
| |
| Initiator::Initiator(Instance &aInstance) |
| : InstanceLocator(aInstance) |
| { |
| } |
| |
| Error Initiator::Query(const Ip6::Address &aDestination, uint8_t aSeriesId, const Metrics *aMetrics) |
| { |
| Error error; |
| Neighbor *neighbor; |
| QueryInfo info; |
| |
| SuccessOrExit(error = FindNeighbor(aDestination, neighbor)); |
| |
| info.Clear(); |
| info.mSeriesId = aSeriesId; |
| |
| if (aMetrics != nullptr) |
| { |
| info.mTypeIdCount = aMetrics->ConvertToTypeIds(info.mTypeIds); |
| } |
| |
| if (aSeriesId != 0) |
| { |
| VerifyOrExit(info.mTypeIdCount == 0, error = kErrorInvalidArgs); |
| } |
| |
| error = Get<Mle::Mle>().SendDataRequestForLinkMetricsReport(aDestination, info); |
| |
| exit: |
| return error; |
| } |
| |
| Error Initiator::AppendLinkMetricsQueryTlv(Message &aMessage, const QueryInfo &aInfo) |
| { |
| Error error = kErrorNone; |
| Tlv tlv; |
| |
| // The MLE Link Metrics Query TLV has two sub-TLVs: |
| // - Query ID sub-TLV with series ID as value. |
| // - Query Options sub-TLV with Type IDs as value. |
| |
| tlv.SetType(Mle::Tlv::kLinkMetricsQuery); |
| tlv.SetLength(sizeof(Tlv) + sizeof(uint8_t) + ((aInfo.mTypeIdCount == 0) ? 0 : (sizeof(Tlv) + aInfo.mTypeIdCount))); |
| |
| SuccessOrExit(error = aMessage.Append(tlv)); |
| |
| SuccessOrExit(error = Tlv::Append<QueryIdSubTlv>(aMessage, aInfo.mSeriesId)); |
| |
| if (aInfo.mTypeIdCount != 0) |
| { |
| QueryOptionsSubTlv queryOptionsTlv; |
| |
| queryOptionsTlv.Init(); |
| queryOptionsTlv.SetLength(aInfo.mTypeIdCount); |
| SuccessOrExit(error = aMessage.Append(queryOptionsTlv)); |
| SuccessOrExit(error = aMessage.AppendBytes(aInfo.mTypeIds, aInfo.mTypeIdCount)); |
| } |
| |
| exit: |
| return error; |
| } |
| |
| void Initiator::HandleReport(const Message &aMessage, uint16_t aOffset, uint16_t aLength, const Ip6::Address &aAddress) |
| { |
| Error error = kErrorNone; |
| uint16_t offset = aOffset; |
| uint16_t endOffset = aOffset + aLength; |
| bool hasStatus = false; |
| bool hasReport = false; |
| Tlv tlv; |
| ReportSubTlv reportTlv; |
| MetricsValues values; |
| uint8_t status; |
| uint8_t typeId; |
| |
| OT_UNUSED_VARIABLE(error); |
| |
| VerifyOrExit(mReportCallback.IsSet()); |
| |
| values.Clear(); |
| |
| while (offset < endOffset) |
| { |
| SuccessOrExit(error = aMessage.Read(offset, tlv)); |
| |
| VerifyOrExit(offset + sizeof(Tlv) + tlv.GetLength() <= endOffset, error = kErrorParse); |
| |
| // The report must contain either: |
| // - One or more Report Sub-TLVs (in case of success), or |
| // - A single Status Sub-TLV (in case of failure). |
| |
| switch (tlv.GetType()) |
| { |
| case StatusSubTlv::kType: |
| VerifyOrExit(!hasStatus && !hasReport, error = kErrorDrop); |
| SuccessOrExit(error = Tlv::Read<StatusSubTlv>(aMessage, offset, status)); |
| hasStatus = true; |
| break; |
| |
| case ReportSubTlv::kType: |
| VerifyOrExit(!hasStatus, error = kErrorDrop); |
| |
| // Read the report sub-TLV assuming minimum length |
| SuccessOrExit(error = aMessage.Read(offset, &reportTlv, sizeof(Tlv) + ReportSubTlv::kMinLength)); |
| VerifyOrExit(reportTlv.IsValid(), error = kErrorParse); |
| hasReport = true; |
| |
| typeId = reportTlv.GetMetricsTypeId(); |
| |
| if (TypeId::IsExtended(typeId)) |
| { |
| // Skip the sub-TLV if `E` flag is set. |
| break; |
| } |
| |
| if (TypeId::GetValueLength(typeId) > sizeof(uint8_t)) |
| { |
| // If Type ID indicates metric value has 4 bytes length, we |
| // read the full `reportTlv`. |
| SuccessOrExit(error = aMessage.Read(offset, reportTlv)); |
| } |
| |
| switch (typeId) |
| { |
| case TypeId::kPdu: |
| values.mMetrics.mPduCount = true; |
| values.mPduCountValue = reportTlv.GetMetricsValue32(); |
| LogDebg(" - PDU Counter: %lu (Count/Summation)", ToUlong(values.mPduCountValue)); |
| break; |
| |
| case TypeId::kLqi: |
| values.mMetrics.mLqi = true; |
| values.mLqiValue = reportTlv.GetMetricsValue8(); |
| LogDebg(" - LQI: %u (Exponential Moving Average)", values.mLqiValue); |
| break; |
| |
| case TypeId::kLinkMargin: |
| values.mMetrics.mLinkMargin = true; |
| values.mLinkMarginValue = ScaleRawValueToLinkMargin(reportTlv.GetMetricsValue8()); |
| LogDebg(" - Margin: %u (dB) (Exponential Moving Average)", values.mLinkMarginValue); |
| break; |
| |
| case TypeId::kRssi: |
| values.mMetrics.mRssi = true; |
| values.mRssiValue = ScaleRawValueToRssi(reportTlv.GetMetricsValue8()); |
| LogDebg(" - RSSI: %u (dBm) (Exponential Moving Average)", values.mRssiValue); |
| break; |
| } |
| |
| break; |
| } |
| |
| offset += sizeof(Tlv) + tlv.GetLength(); |
| } |
| |
| VerifyOrExit(hasStatus || hasReport); |
| |
| mReportCallback.Invoke(&aAddress, hasStatus ? nullptr : &values, |
| hasStatus ? MapEnum(static_cast<Status>(status)) : MapEnum(kStatusSuccess)); |
| |
| exit: |
| LogDebg("HandleReport, error:%s", ErrorToString(error)); |
| } |
| |
| Error Initiator::SendMgmtRequestForwardTrackingSeries(const Ip6::Address &aDestination, |
| uint8_t aSeriesId, |
| const SeriesFlags &aSeriesFlags, |
| const Metrics *aMetrics) |
| { |
| Error error; |
| Neighbor *neighbor; |
| uint8_t typeIdCount = 0; |
| FwdProbingRegSubTlv fwdProbingSubTlv; |
| |
| SuccessOrExit(error = FindNeighbor(aDestination, neighbor)); |
| |
| VerifyOrExit(aSeriesId > kQueryIdSingleProbe, error = kErrorInvalidArgs); |
| |
| fwdProbingSubTlv.Init(); |
| fwdProbingSubTlv.SetSeriesId(aSeriesId); |
| fwdProbingSubTlv.SetSeriesFlagsMask(aSeriesFlags.ConvertToMask()); |
| |
| if (aMetrics != nullptr) |
| { |
| typeIdCount = aMetrics->ConvertToTypeIds(fwdProbingSubTlv.GetTypeIds()); |
| } |
| |
| fwdProbingSubTlv.SetLength(sizeof(aSeriesId) + sizeof(uint8_t) + typeIdCount); |
| |
| error = Get<Mle::Mle>().SendLinkMetricsManagementRequest(aDestination, fwdProbingSubTlv); |
| |
| exit: |
| LogDebg("SendMgmtRequestForwardTrackingSeries, error:%s, Series ID:%u", ErrorToString(error), aSeriesId); |
| return error; |
| } |
| |
| Error Initiator::SendMgmtRequestEnhAckProbing(const Ip6::Address &aDestination, |
| EnhAckFlags aEnhAckFlags, |
| const Metrics *aMetrics) |
| { |
| Error error; |
| Neighbor *neighbor; |
| uint8_t typeIdCount = 0; |
| EnhAckConfigSubTlv enhAckConfigSubTlv; |
| |
| SuccessOrExit(error = FindNeighbor(aDestination, neighbor)); |
| |
| if (aEnhAckFlags == kEnhAckClear) |
| { |
| VerifyOrExit(aMetrics == nullptr, error = kErrorInvalidArgs); |
| } |
| |
| enhAckConfigSubTlv.Init(); |
| enhAckConfigSubTlv.SetEnhAckFlags(aEnhAckFlags); |
| |
| if (aMetrics != nullptr) |
| { |
| typeIdCount = aMetrics->ConvertToTypeIds(enhAckConfigSubTlv.GetTypeIds()); |
| } |
| |
| enhAckConfigSubTlv.SetLength(EnhAckConfigSubTlv::kMinLength + typeIdCount); |
| |
| error = Get<Mle::Mle>().SendLinkMetricsManagementRequest(aDestination, enhAckConfigSubTlv); |
| |
| if (aMetrics != nullptr) |
| { |
| neighbor->SetEnhAckProbingMetrics(*aMetrics); |
| } |
| else |
| { |
| Metrics metrics; |
| |
| metrics.Clear(); |
| neighbor->SetEnhAckProbingMetrics(metrics); |
| } |
| |
| exit: |
| return error; |
| } |
| |
| Error Initiator::HandleManagementResponse(const Message &aMessage, const Ip6::Address &aAddress) |
| { |
| Error error = kErrorNone; |
| uint16_t offset; |
| uint16_t endOffset; |
| uint8_t status; |
| bool hasStatus = false; |
| |
| VerifyOrExit(mMgmtResponseCallback.IsSet()); |
| |
| SuccessOrExit( |
| error = Tlv::FindTlvValueStartEndOffsets(aMessage, Mle::Tlv::Type::kLinkMetricsManagement, offset, endOffset)); |
| |
| while (offset < endOffset) |
| { |
| Tlv tlv; |
| |
| SuccessOrExit(error = aMessage.Read(offset, tlv)); |
| |
| switch (tlv.GetType()) |
| { |
| case StatusSubTlv::kType: |
| VerifyOrExit(!hasStatus, error = kErrorParse); |
| SuccessOrExit(error = Tlv::Read<StatusSubTlv>(aMessage, offset, status)); |
| hasStatus = true; |
| break; |
| |
| default: |
| break; |
| } |
| |
| offset += sizeof(Tlv) + tlv.GetLength(); |
| } |
| |
| VerifyOrExit(hasStatus, error = kErrorParse); |
| |
| mMgmtResponseCallback.Invoke(&aAddress, MapEnum(static_cast<Status>(status))); |
| |
| exit: |
| return error; |
| } |
| |
| Error Initiator::SendLinkProbe(const Ip6::Address &aDestination, uint8_t aSeriesId, uint8_t aLength) |
| { |
| Error error; |
| uint8_t buf[kLinkProbeMaxLen]; |
| Neighbor *neighbor; |
| |
| SuccessOrExit(error = FindNeighbor(aDestination, neighbor)); |
| |
| VerifyOrExit(aLength <= kLinkProbeMaxLen && aSeriesId != kQueryIdSingleProbe && aSeriesId != kSeriesIdAllSeries, |
| error = kErrorInvalidArgs); |
| |
| error = Get<Mle::Mle>().SendLinkProbe(aDestination, aSeriesId, buf, aLength); |
| exit: |
| LogDebg("SendLinkProbe, error:%s, Series ID:%u", ErrorToString(error), aSeriesId); |
| return error; |
| } |
| |
| void Initiator::ProcessEnhAckIeData(const uint8_t *aData, uint8_t aLength, const Neighbor &aNeighbor) |
| { |
| MetricsValues values; |
| uint8_t idx = 0; |
| |
| VerifyOrExit(mEnhAckProbingIeReportCallback.IsSet()); |
| |
| values.SetMetrics(aNeighbor.GetEnhAckProbingMetrics()); |
| |
| if (values.GetMetrics().mLqi && idx < aLength) |
| { |
| values.mLqiValue = aData[idx++]; |
| } |
| if (values.GetMetrics().mLinkMargin && idx < aLength) |
| { |
| values.mLinkMarginValue = ScaleRawValueToLinkMargin(aData[idx++]); |
| } |
| if (values.GetMetrics().mRssi && idx < aLength) |
| { |
| values.mRssiValue = ScaleRawValueToRssi(aData[idx++]); |
| } |
| |
| mEnhAckProbingIeReportCallback.Invoke(aNeighbor.GetRloc16(), &aNeighbor.GetExtAddress(), &values); |
| |
| exit: |
| return; |
| } |
| |
| Error Initiator::FindNeighbor(const Ip6::Address &aDestination, Neighbor *&aNeighbor) |
| { |
| Error error = kErrorUnknownNeighbor; |
| Mac::Address macAddress; |
| |
| aNeighbor = nullptr; |
| |
| VerifyOrExit(aDestination.IsLinkLocal()); |
| aDestination.GetIid().ConvertToMacAddress(macAddress); |
| |
| aNeighbor = Get<NeighborTable>().FindNeighbor(macAddress); |
| VerifyOrExit(aNeighbor != nullptr); |
| |
| VerifyOrExit(aNeighbor->GetVersion() >= kThreadVersion1p2, error = kErrorNotCapable); |
| error = kErrorNone; |
| |
| exit: |
| return error; |
| } |
| |
| #endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE |
| |
| #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE |
| Subject::Subject(Instance &aInstance) |
| : InstanceLocator(aInstance) |
| { |
| } |
| |
| Error Subject::AppendReport(Message &aMessage, const Message &aRequestMessage, Neighbor &aNeighbor) |
| { |
| Error error = kErrorNone; |
| Tlv tlv; |
| uint8_t queryId; |
| bool hasQueryId = false; |
| uint16_t length; |
| uint16_t offset; |
| uint16_t endOffset; |
| MetricsValues values; |
| |
| values.Clear(); |
| |
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| // Parse MLE Link Metrics Query TLV and its sub-TLVs from |
| // `aRequestMessage`. |
| |
| SuccessOrExit(error = Tlv::FindTlvValueStartEndOffsets(aRequestMessage, Mle::Tlv::Type::kLinkMetricsQuery, offset, |
| endOffset)); |
| |
| while (offset < endOffset) |
| { |
| SuccessOrExit(error = aRequestMessage.Read(offset, tlv)); |
| |
| switch (tlv.GetType()) |
| { |
| case SubTlv::kQueryId: |
| SuccessOrExit(error = Tlv::Read<QueryIdSubTlv>(aRequestMessage, offset, queryId)); |
| hasQueryId = true; |
| break; |
| |
| case SubTlv::kQueryOptions: |
| SuccessOrExit(error = ReadTypeIdsFromMessage(aRequestMessage, offset + sizeof(tlv), |
| static_cast<uint16_t>(offset + tlv.GetSize()), |
| values.GetMetrics())); |
| break; |
| |
| default: |
| break; |
| } |
| |
| offset += static_cast<uint16_t>(tlv.GetSize()); |
| } |
| |
| VerifyOrExit(hasQueryId, error = kErrorParse); |
| |
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| // Append MLE Link Metrics Report TLV and its sub-TLVs to |
| // `aMessage`. |
| |
| offset = aMessage.GetLength(); |
| tlv.SetType(Mle::Tlv::kLinkMetricsReport); |
| SuccessOrExit(error = aMessage.Append(tlv)); |
| |
| if (queryId == kQueryIdSingleProbe) |
| { |
| values.mPduCountValue = aRequestMessage.GetPsduCount(); |
| values.mLqiValue = aRequestMessage.GetAverageLqi(); |
| values.mLinkMarginValue = Get<Mac::Mac>().ComputeLinkMargin(aRequestMessage.GetAverageRss()); |
| values.mRssiValue = aRequestMessage.GetAverageRss(); |
| SuccessOrExit(error = AppendReportSubTlvToMessage(aMessage, values)); |
| } |
| else |
| { |
| SeriesInfo *seriesInfo = aNeighbor.GetForwardTrackingSeriesInfo(queryId); |
| |
| if (seriesInfo == nullptr) |
| { |
| SuccessOrExit(error = Tlv::Append<StatusSubTlv>(aMessage, kStatusSeriesIdNotRecognized)); |
| } |
| else if (seriesInfo->GetPduCount() == 0) |
| { |
| SuccessOrExit(error = Tlv::Append<StatusSubTlv>(aMessage, kStatusNoMatchingFramesReceived)); |
| } |
| else |
| { |
| values.SetMetrics(seriesInfo->GetLinkMetrics()); |
| values.mPduCountValue = seriesInfo->GetPduCount(); |
| values.mLqiValue = seriesInfo->GetAverageLqi(); |
| values.mLinkMarginValue = Get<Mac::Mac>().ComputeLinkMargin(seriesInfo->GetAverageRss()); |
| values.mRssiValue = seriesInfo->GetAverageRss(); |
| SuccessOrExit(error = AppendReportSubTlvToMessage(aMessage, values)); |
| } |
| } |
| |
| // Update the TLV length in message. |
| length = aMessage.GetLength() - offset - sizeof(Tlv); |
| tlv.SetLength(static_cast<uint8_t>(length)); |
| aMessage.Write(offset, tlv); |
| |
| exit: |
| LogDebg("AppendReport, error:%s", ErrorToString(error)); |
| return error; |
| } |
| |
| Error Subject::HandleManagementRequest(const Message &aMessage, Neighbor &aNeighbor, Status &aStatus) |
| { |
| Error error = kErrorNone; |
| uint16_t offset; |
| uint16_t endOffset; |
| uint16_t tlvEndOffset; |
| FwdProbingRegSubTlv fwdProbingSubTlv; |
| EnhAckConfigSubTlv enhAckConfigSubTlv; |
| Metrics metrics; |
| |
| SuccessOrExit( |
| error = Tlv::FindTlvValueStartEndOffsets(aMessage, Mle::Tlv::Type::kLinkMetricsManagement, offset, endOffset)); |
| |
| // Set sub-TLV lengths to zero to indicate that we have |
| // not yet seen them in the message. |
| fwdProbingSubTlv.SetLength(0); |
| enhAckConfigSubTlv.SetLength(0); |
| |
| for (; offset < endOffset; offset = tlvEndOffset) |
| { |
| Tlv tlv; |
| uint16_t minTlvSize; |
| Tlv *subTlv; |
| |
| SuccessOrExit(error = aMessage.Read(offset, tlv)); |
| |
| VerifyOrExit(offset + tlv.GetSize() <= endOffset, error = kErrorParse); |
| tlvEndOffset = static_cast<uint16_t>(offset + tlv.GetSize()); |
| |
| switch (tlv.GetType()) |
| { |
| case SubTlv::kFwdProbingReg: |
| subTlv = &fwdProbingSubTlv; |
| minTlvSize = sizeof(Tlv) + FwdProbingRegSubTlv::kMinLength; |
| break; |
| |
| case SubTlv::kEnhAckConfig: |
| subTlv = &enhAckConfigSubTlv; |
| minTlvSize = sizeof(Tlv) + EnhAckConfigSubTlv::kMinLength; |
| break; |
| |
| default: |
| continue; |
| } |
| |
| // Ensure message contains only one sub-TLV. |
| VerifyOrExit(fwdProbingSubTlv.GetLength() == 0, error = kErrorParse); |
| VerifyOrExit(enhAckConfigSubTlv.GetLength() == 0, error = kErrorParse); |
| |
| VerifyOrExit(tlv.GetSize() >= minTlvSize, error = kErrorParse); |
| |
| // Read `subTlv` with its `minTlvSize`, followed by the Type IDs. |
| SuccessOrExit(error = aMessage.Read(offset, subTlv, minTlvSize)); |
| SuccessOrExit(error = ReadTypeIdsFromMessage(aMessage, offset + minTlvSize, tlvEndOffset, metrics)); |
| } |
| |
| if (fwdProbingSubTlv.GetLength() != 0) |
| { |
| aStatus = ConfigureForwardTrackingSeries(fwdProbingSubTlv.GetSeriesId(), fwdProbingSubTlv.GetSeriesFlagsMask(), |
| metrics, aNeighbor); |
| } |
| |
| if (enhAckConfigSubTlv.GetLength() != 0) |
| { |
| aStatus = ConfigureEnhAckProbing(enhAckConfigSubTlv.GetEnhAckFlags(), metrics, aNeighbor); |
| } |
| |
| exit: |
| return error; |
| } |
| |
| Error Subject::HandleLinkProbe(const Message &aMessage, uint8_t &aSeriesId) |
| { |
| Error error = kErrorNone; |
| uint16_t offset; |
| uint16_t length; |
| |
| SuccessOrExit(error = Tlv::FindTlvValueOffset(aMessage, Mle::Tlv::Type::kLinkProbe, offset, length)); |
| VerifyOrExit(length >= sizeof(aSeriesId), error = kErrorParse); |
| error = aMessage.Read(offset, aSeriesId); |
| |
| exit: |
| return error; |
| } |
| |
| Error Subject::AppendReportSubTlvToMessage(Message &aMessage, const MetricsValues &aValues) |
| { |
| Error error = kErrorNone; |
| ReportSubTlv reportTlv; |
| |
| reportTlv.Init(); |
| |
| if (aValues.mMetrics.mPduCount) |
| { |
| reportTlv.SetMetricsTypeId(TypeId::kPdu); |
| reportTlv.SetMetricsValue32(aValues.mPduCountValue); |
| SuccessOrExit(error = reportTlv.AppendTo(aMessage)); |
| } |
| |
| if (aValues.mMetrics.mLqi) |
| { |
| reportTlv.SetMetricsTypeId(TypeId::kLqi); |
| reportTlv.SetMetricsValue8(aValues.mLqiValue); |
| SuccessOrExit(error = reportTlv.AppendTo(aMessage)); |
| } |
| |
| if (aValues.mMetrics.mLinkMargin) |
| { |
| reportTlv.SetMetricsTypeId(TypeId::kLinkMargin); |
| reportTlv.SetMetricsValue8(ScaleLinkMarginToRawValue(aValues.mLinkMarginValue)); |
| SuccessOrExit(error = reportTlv.AppendTo(aMessage)); |
| } |
| |
| if (aValues.mMetrics.mRssi) |
| { |
| reportTlv.SetMetricsTypeId(TypeId::kRssi); |
| reportTlv.SetMetricsValue8(ScaleRssiToRawValue(aValues.mRssiValue)); |
| SuccessOrExit(error = reportTlv.AppendTo(aMessage)); |
| } |
| |
| exit: |
| return error; |
| } |
| |
| void Subject::Free(SeriesInfo &aSeriesInfo) { mSeriesInfoPool.Free(aSeriesInfo); } |
| |
| Error Subject::ReadTypeIdsFromMessage(const Message &aMessage, |
| uint16_t aStartOffset, |
| uint16_t aEndOffset, |
| Metrics &aMetrics) |
| { |
| Error error = kErrorNone; |
| |
| aMetrics.Clear(); |
| |
| for (uint16_t offset = aStartOffset; offset < aEndOffset; offset++) |
| { |
| uint8_t typeId; |
| |
| SuccessOrExit(aMessage.Read(offset, typeId)); |
| |
| switch (typeId) |
| { |
| case TypeId::kPdu: |
| VerifyOrExit(!aMetrics.mPduCount, error = kErrorParse); |
| aMetrics.mPduCount = true; |
| break; |
| |
| case TypeId::kLqi: |
| VerifyOrExit(!aMetrics.mLqi, error = kErrorParse); |
| aMetrics.mLqi = true; |
| break; |
| |
| case TypeId::kLinkMargin: |
| VerifyOrExit(!aMetrics.mLinkMargin, error = kErrorParse); |
| aMetrics.mLinkMargin = true; |
| break; |
| |
| case TypeId::kRssi: |
| VerifyOrExit(!aMetrics.mRssi, error = kErrorParse); |
| aMetrics.mRssi = true; |
| break; |
| |
| default: |
| if (TypeId::IsExtended(typeId)) |
| { |
| offset += sizeof(uint8_t); // Skip the additional second byte. |
| } |
| else |
| { |
| aMetrics.mReserved = true; |
| } |
| break; |
| } |
| } |
| |
| exit: |
| return error; |
| } |
| |
| Status Subject::ConfigureForwardTrackingSeries(uint8_t aSeriesId, |
| uint8_t aSeriesFlagsMask, |
| const Metrics &aMetrics, |
| Neighbor &aNeighbor) |
| { |
| Status status = kStatusSuccess; |
| |
| VerifyOrExit(0 < aSeriesId, status = kStatusOtherError); |
| |
| if (aSeriesFlagsMask == 0) // Remove the series |
| { |
| if (aSeriesId == kSeriesIdAllSeries) // Remove all |
| { |
| aNeighbor.RemoveAllForwardTrackingSeriesInfo(); |
| } |
| else |
| { |
| SeriesInfo *seriesInfo = aNeighbor.RemoveForwardTrackingSeriesInfo(aSeriesId); |
| VerifyOrExit(seriesInfo != nullptr, status = kStatusSeriesIdNotRecognized); |
| mSeriesInfoPool.Free(*seriesInfo); |
| } |
| } |
| else // Add a new series |
| { |
| SeriesInfo *seriesInfo = aNeighbor.GetForwardTrackingSeriesInfo(aSeriesId); |
| VerifyOrExit(seriesInfo == nullptr, status = kStatusSeriesIdAlreadyRegistered); |
| seriesInfo = mSeriesInfoPool.Allocate(); |
| VerifyOrExit(seriesInfo != nullptr, status = kStatusCannotSupportNewSeries); |
| |
| seriesInfo->Init(aSeriesId, aSeriesFlagsMask, aMetrics); |
| |
| aNeighbor.AddForwardTrackingSeriesInfo(*seriesInfo); |
| } |
| |
| exit: |
| return status; |
| } |
| |
| Status Subject::ConfigureEnhAckProbing(uint8_t aEnhAckFlags, const Metrics &aMetrics, Neighbor &aNeighbor) |
| { |
| Status status = kStatusSuccess; |
| Error error = kErrorNone; |
| |
| VerifyOrExit(!aMetrics.mReserved, status = kStatusOtherError); |
| |
| if (aEnhAckFlags == kEnhAckRegister) |
| { |
| VerifyOrExit(!aMetrics.mPduCount, status = kStatusOtherError); |
| VerifyOrExit(aMetrics.mLqi || aMetrics.mLinkMargin || aMetrics.mRssi, status = kStatusOtherError); |
| VerifyOrExit(!(aMetrics.mLqi && aMetrics.mLinkMargin && aMetrics.mRssi), status = kStatusOtherError); |
| |
| error = Get<Radio>().ConfigureEnhAckProbing(aMetrics, aNeighbor.GetRloc16(), aNeighbor.GetExtAddress()); |
| } |
| else if (aEnhAckFlags == kEnhAckClear) |
| { |
| VerifyOrExit(!aMetrics.mLqi && !aMetrics.mLinkMargin && !aMetrics.mRssi, status = kStatusOtherError); |
| error = Get<Radio>().ConfigureEnhAckProbing(aMetrics, aNeighbor.GetRloc16(), aNeighbor.GetExtAddress()); |
| } |
| else |
| { |
| status = kStatusOtherError; |
| } |
| |
| VerifyOrExit(error == kErrorNone, status = kStatusOtherError); |
| |
| exit: |
| return status; |
| } |
| |
| #endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE |
| |
| uint8_t ScaleLinkMarginToRawValue(uint8_t aLinkMargin) |
| { |
| // Linearly scale Link Margin from [0, 130] to [0, 255]. |
| // `kMaxLinkMargin = 130`. |
| |
| uint16_t value; |
| |
| value = Min(aLinkMargin, kMaxLinkMargin); |
| value = value * NumericLimits<uint8_t>::kMax; |
| value = DivideAndRoundToClosest<uint16_t>(value, kMaxLinkMargin); |
| |
| return static_cast<uint8_t>(value); |
| } |
| |
| uint8_t ScaleRawValueToLinkMargin(uint8_t aRawValue) |
| { |
| // Scale back raw value of [0, 255] to Link Margin from [0, 130]. |
| |
| uint16_t value = aRawValue; |
| |
| value = value * kMaxLinkMargin; |
| value = DivideAndRoundToClosest<uint16_t>(value, NumericLimits<uint8_t>::kMax); |
| return static_cast<uint8_t>(value); |
| } |
| |
| uint8_t ScaleRssiToRawValue(int8_t aRssi) |
| { |
| // Linearly scale RSSI from [-130, 0] to [0, 255]. |
| // `kMinRssi = -130`, `kMaxRssi = 0`. |
| |
| int32_t value = aRssi; |
| |
| value = Clamp(value, kMinRssi, kMaxRssi) - kMinRssi; |
| value = value * NumericLimits<uint8_t>::kMax; |
| value = DivideAndRoundToClosest<int32_t>(value, kMaxRssi - kMinRssi); |
| |
| return static_cast<uint8_t>(value); |
| } |
| |
| int8_t ScaleRawValueToRssi(uint8_t aRawValue) |
| { |
| int32_t value = aRawValue; |
| |
| value = value * (kMaxRssi - kMinRssi); |
| value = DivideAndRoundToClosest<int32_t>(value, NumericLimits<uint8_t>::kMax); |
| value += kMinRssi; |
| |
| return ClampToInt8(value); |
| } |
| |
| } // namespace LinkMetrics |
| } // namespace ot |
| |
| #endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE || OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE |