/* * 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 "mac/mac.hpp" #include "thread/link_metrics_tlvs.hpp" #include "thread/neighbor_table.hpp" namespace ot { namespace LinkMetrics { RegisterLogModule("LinkMetrics"); using ot::Encoding::BigEndian::HostSwap32; void SeriesInfo::Init(uint8_t aSeriesId, const SeriesFlags &aSeriesFlags, const Metrics &aMetrics) { mSeriesId = aSeriesId; mSeriesFlags = aSeriesFlags; mMetrics = aMetrics; mRssAverager.Clear(); mLqiAverager.Clear(); mPduCount = 0; } void SeriesInfo::AggregateLinkMetrics(uint8_t aFrameType, uint8_t aLqi, int8_t aRss) { if (IsFrameTypeMatch(aFrameType)) { mPduCount++; mLqiAverager.Add(aLqi); IgnoreError(mRssAverager.Add(aRss)); } } bool SeriesInfo::IsFrameTypeMatch(uint8_t aFrameType) const { bool match = false; switch (aFrameType) { case kSeriesTypeLinkProbe: VerifyOrExit(!mSeriesFlags.IsMacDataFlagSet()); // Ignore this when Mac Data is accounted match = mSeriesFlags.IsLinkProbeFlagSet(); break; case Mac::Frame::kFcfFrameData: match = mSeriesFlags.IsMacDataFlagSet(); break; case Mac::Frame::kFcfFrameMacCmd: match = mSeriesFlags.IsMacDataRequestFlagSet(); break; case Mac::Frame::kFcfFrameAck: match = mSeriesFlags.IsMacAckFlagSet(); break; default: break; } exit: return match; } LinkMetrics::LinkMetrics(Instance &aInstance) : InstanceLocator(aInstance) , mReportCallback(nullptr) , mReportCallbackContext(nullptr) , mMgmtResponseCallback(nullptr) , mMgmtResponseCallbackContext(nullptr) , mEnhAckProbingIeReportCallback(nullptr) , mEnhAckProbingIeReportCallbackContext(nullptr) { } Error LinkMetrics::Query(const Ip6::Address &aDestination, uint8_t aSeriesId, const Metrics *aMetrics) { Error error; TypeIdFlags typeIdFlags[kMaxTypeIdFlags]; uint8_t typeIdFlagsCount = 0; Neighbor * neighbor = GetNeighborFromLinkLocalAddr(aDestination); VerifyOrExit(neighbor != nullptr, error = kErrorUnknownNeighbor); VerifyOrExit(neighbor->IsThreadVersion1p2OrHigher(), error = kErrorNotCapable); if (aMetrics != nullptr) { typeIdFlagsCount = TypeIdFlagsFromMetrics(typeIdFlags, *aMetrics); } if (aSeriesId != 0) { VerifyOrExit(typeIdFlagsCount == 0, error = kErrorInvalidArgs); } error = SendLinkMetricsQuery(aDestination, aSeriesId, typeIdFlags, typeIdFlagsCount); exit: return error; } #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE Error LinkMetrics::SendMgmtRequestForwardTrackingSeries(const Ip6::Address & aDestination, uint8_t aSeriesId, const SeriesFlags::Info &aSeriesFlags, const Metrics * aMetrics) { Error error = kErrorNone; uint8_t subTlvs[sizeof(Tlv) + sizeof(uint8_t) * 2 + sizeof(TypeIdFlags) * kMaxTypeIdFlags]; Tlv * fwdProbingSubTlv = reinterpret_cast(subTlvs); SeriesFlags *seriesFlags = reinterpret_cast(subTlvs + sizeof(Tlv) + sizeof(aSeriesId)); uint8_t typeIdFlagsOffset = sizeof(Tlv) + sizeof(uint8_t) * 2; uint8_t typeIdFlagsCount = 0; Neighbor * neighbor = GetNeighborFromLinkLocalAddr(aDestination); VerifyOrExit(neighbor != nullptr, error = kErrorUnknownNeighbor); VerifyOrExit(neighbor->IsThreadVersion1p2OrHigher(), error = kErrorNotCapable); // Directly transform `aMetrics` into TypeIdFlags and put them into `subTlvs` if (aMetrics != nullptr) { typeIdFlagsCount = TypeIdFlagsFromMetrics(reinterpret_cast(subTlvs + typeIdFlagsOffset), *aMetrics); } VerifyOrExit(aSeriesId > kQueryIdSingleProbe, error = kErrorInvalidArgs); fwdProbingSubTlv->SetType(SubTlv::kFwdProbingReg); // SeriesId + SeriesFlags + typeIdFlagsCount * TypeIdFlags fwdProbingSubTlv->SetLength(sizeof(uint8_t) * 2 + sizeof(TypeIdFlags) * typeIdFlagsCount); memcpy(subTlvs + sizeof(Tlv), &aSeriesId, sizeof(aSeriesId)); seriesFlags->SetFrom(aSeriesFlags); error = Get().SendLinkMetricsManagementRequest(aDestination, subTlvs, fwdProbingSubTlv->GetSize()); exit: LogDebg("SendMgmtRequestForwardTrackingSeries, error:%s, Series ID:%u", ErrorToString(error), aSeriesId); return error; } Error LinkMetrics::SendMgmtRequestEnhAckProbing(const Ip6::Address &aDestination, const EnhAckFlags aEnhAckFlags, const Metrics * aMetrics) { Error error = kErrorNone; EnhAckConfigSubTlv enhAckConfigSubTlv; Mac::Address macAddress; Neighbor * neighbor = GetNeighborFromLinkLocalAddr(aDestination); VerifyOrExit(neighbor != nullptr, error = kErrorUnknownNeighbor); VerifyOrExit(neighbor->IsThreadVersion1p2OrHigher(), error = kErrorNotCapable); if (aEnhAckFlags == kEnhAckClear) { VerifyOrExit(aMetrics == nullptr, error = kErrorInvalidArgs); } enhAckConfigSubTlv.SetEnhAckFlags(aEnhAckFlags); if (aMetrics != nullptr) { enhAckConfigSubTlv.SetTypeIdFlags(*aMetrics); } error = Get().SendLinkMetricsManagementRequest( aDestination, reinterpret_cast(&enhAckConfigSubTlv), enhAckConfigSubTlv.GetSize()); if (aMetrics != nullptr) { neighbor->SetEnhAckProbingMetrics(*aMetrics); } else { Metrics metrics; metrics.Clear(); neighbor->SetEnhAckProbingMetrics(metrics); } exit: return error; } Error LinkMetrics::SendLinkProbe(const Ip6::Address &aDestination, uint8_t aSeriesId, uint8_t aLength) { Error error = kErrorNone; uint8_t buf[kLinkProbeMaxLen]; Neighbor *neighbor = GetNeighborFromLinkLocalAddr(aDestination); VerifyOrExit(neighbor != nullptr, error = kErrorUnknownNeighbor); VerifyOrExit(neighbor->IsThreadVersion1p2OrHigher(), error = kErrorNotCapable); VerifyOrExit(aLength <= LinkMetrics::kLinkProbeMaxLen && aSeriesId != kQueryIdSingleProbe && aSeriesId != kSeriesIdAllSeries, error = kErrorInvalidArgs); error = Get().SendLinkProbe(aDestination, aSeriesId, buf, aLength); exit: LogDebg("SendLinkProbe, error:%s, Series ID:%u", ErrorToString(error), aSeriesId); return error; } #endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE Error LinkMetrics::AppendReport(Message &aMessage, const Message &aRequestMessage, Neighbor &aNeighbor) { Error error = kErrorNone; Tlv tlv; uint8_t queryId; bool hasQueryId = false; uint8_t length = 0; uint16_t startOffset = aMessage.GetLength(); uint16_t offset; uint16_t endOffset; MetricsValues values; values.Clear(); SuccessOrExit(error = Tlv::FindTlvValueOffset(aRequestMessage, Mle::Tlv::Type::kLinkMetricsQuery, offset, endOffset)); // `endOffset` is used to store tlv length here endOffset = offset + endOffset; while (offset < endOffset) { SuccessOrExit(error = aRequestMessage.Read(offset, tlv)); switch (tlv.GetType()) { case SubTlv::kQueryId: SuccessOrExit(error = Tlv::Read(aRequestMessage, offset, queryId)); hasQueryId = true; break; case SubTlv::kQueryOptions: SuccessOrExit(error = ReadTypeIdFlagsFromMessage(aRequestMessage, offset + sizeof(tlv), static_cast(offset + tlv.GetSize()), values.GetMetrics())); break; default: break; } offset += tlv.GetSize(); } VerifyOrExit(hasQueryId, error = kErrorParse); // Link Metrics Report TLV tlv.SetType(Mle::Tlv::kLinkMetricsReport); SuccessOrExit(error = aMessage.Append(tlv)); if (queryId == kQueryIdSingleProbe) { values.mPduCountValue = HostSwap32(aRequestMessage.GetPsduCount()); values.mLqiValue = aRequestMessage.GetAverageLqi(); // Linearly scale Link Margin from [0, 130] to [0, 255] values.mLinkMarginValue = LinkQualityInfo::ConvertRssToLinkMargin(Get().GetNoiseFloor(), aRequestMessage.GetAverageRss()) * 255 / 130; // Linearly scale rss from [-130, 0] to [0, 255] values.mRssiValue = (aRequestMessage.GetAverageRss() + 130) * 255 / 130; SuccessOrExit(error = AppendReportSubTlvToMessage(aMessage, length, values)); } else { SeriesInfo *seriesInfo = aNeighbor.GetForwardTrackingSeriesInfo(queryId); if (seriesInfo == nullptr) { SuccessOrExit(error = AppendStatusSubTlvToMessage(aMessage, length, kStatusSeriesIdNotRecognized)); } else if (seriesInfo->GetPduCount() == 0) { SuccessOrExit(error = AppendStatusSubTlvToMessage(aMessage, length, kStatusNoMatchingFramesReceived)); } else { values.SetMetrics(seriesInfo->GetLinkMetrics()); values.mPduCountValue = HostSwap32(seriesInfo->GetPduCount()); values.mLqiValue = seriesInfo->GetAverageLqi(); // Linearly scale Link Margin from [0, 130] to [0, 255] values.mLinkMarginValue = LinkQualityInfo::ConvertRssToLinkMargin(Get().GetNoiseFloor(), seriesInfo->GetAverageRss()) * 255 / 130; // Linearly scale RSSI from [-130, 0] to [0, 255] values.mRssiValue = (seriesInfo->GetAverageRss() + 130) * 255 / 130; SuccessOrExit(error = AppendReportSubTlvToMessage(aMessage, length, values)); } } tlv.SetLength(length); aMessage.Write(startOffset, tlv); exit: LogDebg("AppendReport, error:%s", ErrorToString(error)); return error; } Error LinkMetrics::HandleManagementRequest(const Message &aMessage, Neighbor &aNeighbor, Status &aStatus) { Error error = kErrorNone; Tlv tlv; uint8_t seriesId; SeriesFlags seriesFlags; EnhAckFlags enhAckFlags; Metrics metrics; bool hasForwardProbingRegistrationTlv = false; bool hasEnhAckProbingTlv = false; uint16_t offset; uint16_t length; uint16_t index = 0; SuccessOrExit(error = Tlv::FindTlvValueOffset(aMessage, Mle::Tlv::Type::kLinkMetricsManagement, offset, length)); while (index < length) { uint16_t pos = offset + index; SuccessOrExit(aMessage.Read(pos, tlv)); pos += sizeof(tlv); switch (tlv.GetType()) { case SubTlv::kFwdProbingReg: VerifyOrExit(!hasForwardProbingRegistrationTlv && !hasEnhAckProbingTlv, error = kErrorParse); VerifyOrExit(tlv.GetLength() >= sizeof(seriesId) + sizeof(seriesFlags), error = kErrorParse); SuccessOrExit(aMessage.Read(pos, seriesId)); pos += sizeof(seriesId); SuccessOrExit(aMessage.Read(pos, seriesFlags)); pos += sizeof(seriesFlags); SuccessOrExit(error = ReadTypeIdFlagsFromMessage( aMessage, pos, static_cast(offset + index + tlv.GetSize()), metrics)); hasForwardProbingRegistrationTlv = true; break; case SubTlv::kEnhAckConfig: VerifyOrExit(!hasForwardProbingRegistrationTlv && !hasEnhAckProbingTlv, error = kErrorParse); VerifyOrExit(tlv.GetLength() >= sizeof(EnhAckFlags), error = kErrorParse); SuccessOrExit(aMessage.Read(pos, enhAckFlags)); pos += sizeof(enhAckFlags); SuccessOrExit(error = ReadTypeIdFlagsFromMessage( aMessage, pos, static_cast(offset + index + tlv.GetSize()), metrics)); hasEnhAckProbingTlv = true; break; default: break; } index += tlv.GetSize(); } if (hasForwardProbingRegistrationTlv) { aStatus = ConfigureForwardTrackingSeries(seriesId, seriesFlags, metrics, aNeighbor); } else if (hasEnhAckProbingTlv) { aStatus = ConfigureEnhAckProbing(enhAckFlags, metrics, aNeighbor); } exit: return error; } #endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE Error LinkMetrics::HandleManagementResponse(const Message &aMessage, const Ip6::Address &aAddress) { Error error = kErrorNone; Tlv tlv; uint16_t offset; uint16_t length; uint16_t index = 0; Status status; bool hasStatus = false; VerifyOrExit(mMgmtResponseCallback != nullptr); SuccessOrExit(error = Tlv::FindTlvValueOffset(aMessage, Mle::Tlv::Type::kLinkMetricsManagement, offset, length)); while (index < length) { SuccessOrExit(aMessage.Read(offset + index, tlv)); switch (tlv.GetType()) { case SubTlv::kStatus: VerifyOrExit(!hasStatus, error = kErrorParse); VerifyOrExit(tlv.GetLength() == sizeof(status), error = kErrorParse); SuccessOrExit(aMessage.Read(offset + index + sizeof(tlv), status)); hasStatus = true; break; default: break; } index += tlv.GetSize(); } VerifyOrExit(hasStatus, error = kErrorParse); mMgmtResponseCallback(&aAddress, status, mMgmtResponseCallbackContext); exit: return error; } void LinkMetrics::HandleReport(const Message & aMessage, uint16_t aOffset, uint16_t aLength, const Ip6::Address &aAddress) { Error error = kErrorNone; MetricsValues values; uint8_t rawValue; uint16_t pos = aOffset; uint16_t endPos = aOffset + aLength; Tlv tlv; TypeIdFlags typeIdFlags; bool hasStatus = false; bool hasReport = false; Status status; OT_UNUSED_VARIABLE(error); VerifyOrExit(mReportCallback != nullptr); values.Clear(); while (pos < endPos) { SuccessOrExit(aMessage.Read(pos, tlv)); VerifyOrExit(tlv.GetType() == SubTlv::kReport); pos += sizeof(Tlv); VerifyOrExit(pos + tlv.GetLength() <= endPos, error = kErrorParse); switch (tlv.GetType()) { case SubTlv::kStatus: // There should be either: one Status TLV or some Report-Sub TLVs VerifyOrExit(!hasStatus && !hasReport, error = kErrorDrop); VerifyOrExit(tlv.GetLength() == sizeof(status), error = kErrorParse); SuccessOrExit(aMessage.Read(pos, status)); hasStatus = true; pos += sizeof(status); break; case SubTlv::kReport: // There shouldn't be any Report-Sub TLV when there's a Status TLV VerifyOrExit(!hasStatus, error = kErrorDrop); VerifyOrExit(tlv.GetLength() > sizeof(typeIdFlags), error = kErrorParse); SuccessOrExit(aMessage.Read(pos, typeIdFlags)); if (typeIdFlags.IsExtendedFlagSet()) { pos += tlv.GetLength(); // Skip the whole sub-TLV if `E` flag is set continue; } hasReport = true; pos += sizeof(TypeIdFlags); switch (typeIdFlags.GetRawValue()) { case TypeIdFlags::kPdu: values.GetMetrics().mPduCount = true; SuccessOrExit(aMessage.Read(pos, values.mPduCountValue)); values.mPduCountValue = HostSwap32(values.mPduCountValue); pos += sizeof(uint32_t); LogDebg(" - PDU Counter: %d (Count/Summation)", values.mPduCountValue); break; case TypeIdFlags::kLqi: values.GetMetrics().mLqi = true; SuccessOrExit(aMessage.Read(pos, values.mLqiValue)); pos += sizeof(uint8_t); LogDebg(" - LQI: %d (Exponential Moving Average)", values.mLqiValue); break; case TypeIdFlags::kLinkMargin: values.GetMetrics().mLinkMargin = true; SuccessOrExit(aMessage.Read(pos, rawValue)); // Reverse operation for linear scale, map from [0, 255] to [0, 130] values.mLinkMarginValue = rawValue * 130 / 255; pos += sizeof(uint8_t); LogDebg(" - Margin: %d (dB) (Exponential Moving Average)", values.mLinkMarginValue); break; case TypeIdFlags::kRssi: values.GetMetrics().mRssi = true; SuccessOrExit(aMessage.Read(pos, rawValue)); // Reverse operation for linear scale, map from [0, 255] to [-130, 0] values.mRssiValue = rawValue * 130 / 255 - 130; pos += sizeof(uint8_t); LogDebg(" - RSSI: %d (dBm) (Exponential Moving Average)", values.mRssiValue); break; default: break; } break; } } if (hasStatus) { mReportCallback(&aAddress, nullptr, status, mReportCallbackContext); } else if (hasReport) { mReportCallback(&aAddress, &values, OT_LINK_METRICS_STATUS_SUCCESS, mReportCallbackContext); } exit: LogDebg("HandleReport, error:%s", ErrorToString(error)); return; } Error LinkMetrics::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; } void LinkMetrics::SetReportCallback(ReportCallback aCallback, void *aContext) { mReportCallback = aCallback; mReportCallbackContext = aContext; } void LinkMetrics::SetMgmtResponseCallback(MgmtResponseCallback aCallback, void *aContext) { mMgmtResponseCallback = aCallback; mMgmtResponseCallbackContext = aContext; } void LinkMetrics::SetEnhAckProbingCallback(EnhAckProbingIeReportCallback aCallback, void *aContext) { mEnhAckProbingIeReportCallback = aCallback; mEnhAckProbingIeReportCallbackContext = aContext; } void LinkMetrics::ProcessEnhAckIeData(const uint8_t *aData, uint8_t aLength, const Neighbor &aNeighbor) { MetricsValues values; uint8_t idx = 0; VerifyOrExit(mEnhAckProbingIeReportCallback != nullptr); values.SetMetrics(aNeighbor.GetEnhAckProbingMetrics()); if (values.GetMetrics().mLqi && idx < aLength) { values.mLqiValue = aData[idx++]; } if (values.GetMetrics().mLinkMargin && idx < aLength) { // Reverse operation for linear scale, map from [0, 255] to [0, 130] values.mLinkMarginValue = aData[idx++] * 130 / 255; } if (values.GetMetrics().mRssi && idx < aLength) { // Reverse operation for linear scale, map from [0, 255] to [-130, 0] values.mRssiValue = aData[idx++] * 130 / 255 - 130; } mEnhAckProbingIeReportCallback(aNeighbor.GetRloc16(), &aNeighbor.GetExtAddress(), &values, mEnhAckProbingIeReportCallbackContext); exit: return; } Error LinkMetrics::SendLinkMetricsQuery(const Ip6::Address &aDestination, uint8_t aSeriesId, const TypeIdFlags * aTypeIdFlags, uint8_t aTypeIdFlagsCount) { // LinkMetricsQuery Tlv + LinkMetricsQueryId sub-TLV (value-length: 1 byte) + // LinkMetricsQueryOptions sub-TLV (value-length: `kMaxTypeIdFlags` bytes) constexpr uint16_t kBufferSize = sizeof(Tlv) * 3 + sizeof(uint8_t) + sizeof(TypeIdFlags) * kMaxTypeIdFlags; Error error = kErrorNone; QueryOptionsSubTlv queryOptionsTlv; uint8_t length = 0; static const uint8_t tlvs[] = {Mle::Tlv::kLinkMetricsReport}; uint8_t buf[kBufferSize]; Tlv * tlv = reinterpret_cast(buf); Tlv subTlv; // Link Metrics Query TLV tlv->SetType(Mle::Tlv::kLinkMetricsQuery); length += sizeof(Tlv); // Link Metrics Query ID sub-TLV subTlv.SetType(SubTlv::kQueryId); subTlv.SetLength(sizeof(uint8_t)); memcpy(buf + length, &subTlv, sizeof(subTlv)); length += sizeof(subTlv); memcpy(buf + length, &aSeriesId, sizeof(aSeriesId)); length += sizeof(aSeriesId); // Link Metrics Query Options sub-TLV if (aTypeIdFlagsCount > 0) { queryOptionsTlv.Init(); queryOptionsTlv.SetLength(aTypeIdFlagsCount * sizeof(TypeIdFlags)); memcpy(buf + length, &queryOptionsTlv, sizeof(queryOptionsTlv)); length += sizeof(queryOptionsTlv); memcpy(buf + length, aTypeIdFlags, queryOptionsTlv.GetLength()); length += queryOptionsTlv.GetLength(); } // Set Length for Link Metrics Report TLV tlv->SetLength(length - sizeof(Tlv)); SuccessOrExit(error = Get().SendDataRequest(aDestination, tlvs, sizeof(tlvs), 0, buf, length)); exit: return error; } Status LinkMetrics::ConfigureForwardTrackingSeries(uint8_t aSeriesId, const SeriesFlags &aSeriesFlags, const Metrics & aMetrics, Neighbor & aNeighbor) { Status status = kStatusSuccess; VerifyOrExit(0 < aSeriesId, status = kStatusOtherError); if (aSeriesFlags.GetRawValue() == 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, aSeriesFlags, aMetrics); aNeighbor.AddForwardTrackingSeriesInfo(*seriesInfo); } exit: return status; } #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE Status LinkMetrics::ConfigureEnhAckProbing(EnhAckFlags 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().ConfigureEnhAckProbing(aMetrics, aNeighbor.GetRloc16(), aNeighbor.GetExtAddress()); } else if (aEnhAckFlags == kEnhAckClear) { VerifyOrExit(!aMetrics.mLqi && !aMetrics.mLinkMargin && !aMetrics.mRssi, status = kStatusOtherError); error = Get().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 Neighbor *LinkMetrics::GetNeighborFromLinkLocalAddr(const Ip6::Address &aDestination) { Neighbor * neighbor = nullptr; Mac::Address macAddress; VerifyOrExit(aDestination.IsLinkLocal()); aDestination.GetIid().ConvertToMacAddress(macAddress); neighbor = Get().FindNeighbor(macAddress); exit: return neighbor; } Error LinkMetrics::ReadTypeIdFlagsFromMessage(const Message &aMessage, uint8_t aStartPos, uint8_t aEndPos, Metrics & aMetrics) { Error error = kErrorNone; memset(&aMetrics, 0, sizeof(aMetrics)); for (uint16_t pos = aStartPos; pos < aEndPos; pos += sizeof(TypeIdFlags)) { TypeIdFlags typeIdFlags; SuccessOrExit(aMessage.Read(pos, typeIdFlags)); switch (typeIdFlags.GetRawValue()) { case TypeIdFlags::kPdu: VerifyOrExit(!aMetrics.mPduCount, error = kErrorParse); aMetrics.mPduCount = true; break; case TypeIdFlags::kLqi: VerifyOrExit(!aMetrics.mLqi, error = kErrorParse); aMetrics.mLqi = true; break; case TypeIdFlags::kLinkMargin: VerifyOrExit(!aMetrics.mLinkMargin, error = kErrorParse); aMetrics.mLinkMargin = true; break; case TypeIdFlags::kRssi: VerifyOrExit(!aMetrics.mRssi, error = kErrorParse); aMetrics.mRssi = true; break; default: if (typeIdFlags.IsExtendedFlagSet()) { pos += sizeof(uint8_t); // Skip the additional second flags byte. } else { aMetrics.mReserved = true; } break; } } exit: return error; } Error LinkMetrics::AppendReportSubTlvToMessage(Message &aMessage, uint8_t &aLength, const MetricsValues &aValues) { Error error = kErrorNone; ReportSubTlv metric; aLength = 0; // Link Metrics Report sub-TLVs if (aValues.mMetrics.mPduCount) { metric.Init(); metric.SetMetricsTypeId(TypeIdFlags(TypeIdFlags::kPdu)); metric.SetMetricsValue32(aValues.mPduCountValue); SuccessOrExit(error = aMessage.AppendBytes(&metric, metric.GetSize())); aLength += metric.GetSize(); } if (aValues.mMetrics.mLqi) { metric.Init(); metric.SetMetricsTypeId(TypeIdFlags(TypeIdFlags::kLqi)); metric.SetMetricsValue8(aValues.mLqiValue); SuccessOrExit(error = aMessage.AppendBytes(&metric, metric.GetSize())); aLength += metric.GetSize(); } if (aValues.mMetrics.mLinkMargin) { metric.Init(); metric.SetMetricsTypeId(TypeIdFlags(TypeIdFlags::kLinkMargin)); metric.SetMetricsValue8(aValues.mLinkMarginValue); SuccessOrExit(error = aMessage.AppendBytes(&metric, metric.GetSize())); aLength += metric.GetSize(); } if (aValues.mMetrics.mRssi) { metric.Init(); metric.SetMetricsTypeId(TypeIdFlags(TypeIdFlags::kRssi)); metric.SetMetricsValue8(aValues.mRssiValue); SuccessOrExit(error = aMessage.AppendBytes(&metric, metric.GetSize())); aLength += metric.GetSize(); } exit: return error; } Error LinkMetrics::AppendStatusSubTlvToMessage(Message &aMessage, uint8_t &aLength, Status aStatus) { Error error = kErrorNone; Tlv statusTlv; statusTlv.SetType(SubTlv::kStatus); statusTlv.SetLength(sizeof(uint8_t)); SuccessOrExit(error = aMessage.AppendBytes(&statusTlv, sizeof(statusTlv))); SuccessOrExit(error = aMessage.AppendBytes(&aStatus, sizeof(aStatus))); aLength += statusTlv.GetSize(); exit: return error; } } // namespace LinkMetrics } // namespace ot #endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE || OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE