blob: c4265f60559414b415f1d8612bcd218af60149c6 [file] [log] [blame]
/*
* 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->IsThreadVersion1p2(), 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<Tlv *>(subTlvs);
SeriesFlags *seriesFlags = reinterpret_cast<SeriesFlags *>(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->IsThreadVersion1p2(), error = kErrorNotCapable);
// Directly transform `aMetrics` into TypeIdFlags and put them into `subTlvs`
if (aMetrics != nullptr)
{
typeIdFlagsCount =
TypeIdFlagsFromMetrics(reinterpret_cast<TypeIdFlags *>(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<Mle::MleRouter>().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->IsThreadVersion1p2(), error = kErrorNotCapable);
if (aEnhAckFlags == kEnhAckClear)
{
VerifyOrExit(aMetrics == nullptr, error = kErrorInvalidArgs);
}
enhAckConfigSubTlv.SetEnhAckFlags(aEnhAckFlags);
if (aMetrics != nullptr)
{
enhAckConfigSubTlv.SetTypeIdFlags(*aMetrics);
}
error = Get<Mle::MleRouter>().SendLinkMetricsManagementRequest(
aDestination, reinterpret_cast<const uint8_t *>(&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->IsThreadVersion1p2(), error = kErrorNotCapable);
VerifyOrExit(aLength <= LinkMetrics::kLinkProbeMaxLen && aSeriesId != kQueryIdSingleProbe &&
aSeriesId != kSeriesIdAllSeries,
error = kErrorInvalidArgs);
error = Get<Mle::MleRouter>().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<QueryIdSubTlv>(aRequestMessage, offset, queryId));
hasQueryId = true;
break;
case SubTlv::kQueryOptions:
SuccessOrExit(error = ReadTypeIdFlagsFromMessage(aRequestMessage, offset + sizeof(tlv),
static_cast<uint16_t>(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<Mac::Mac>().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<Mac::Mac>().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<uint16_t>(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<uint16_t>(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<Tlv *>(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<Mle::MleRouter>().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<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
Neighbor *LinkMetrics::GetNeighborFromLinkLocalAddr(const Ip6::Address &aDestination)
{
Neighbor * neighbor = nullptr;
Mac::Address macAddress;
VerifyOrExit(aDestination.IsLinkLocal());
aDestination.GetIid().ConvertToMacAddress(macAddress);
neighbor = Get<NeighborTable>().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