From ae07fe27ff0d32b1f446695b300857a895b8cc97 Mon Sep 17 00:00:00 2001 From: Li Cao Date: Fri, 11 Dec 2020 15:31:41 +0800 Subject: [PATCH] [low-power] implement enhanced-ack based probing (#5780) This commit implements Enhanced-ACK Probing (on simulation platform). - Add a new public API otLinkMetricsConfigEnhAckProbing which sends Link Metrics Management Request to configure the probing. This is called on Probing Initiator side. - Add a new radio platform API otPlatRadioConfigureEnhAckProbing. This API should be called on Probing Subject side when handling the requests from Initiators. This API notifies the radio to start/stop aggregating link metrics info and include the data into Vendor IE in enhanced-ACK for the specific neighbor. As discussed in #5746, the code for doing such thing should be put in radio driver. - Add a util module util/link_metrics, which provides a group of APIs to implement Probing Subject side logic for Enh-ACK Probing. Any platform could use this module to implement the feature easily in radio driver. - Add new util APIs in util/mac_frame to generate Enh-ACK Probing IE (Vendor IE with Thread OUI and SubType = 0) and set value for this IE. - Update the implementation in simulation/radio.c to support Probing Subject side logic for Enh-ACK Probing, using APIs in util/link_metrics and util/mac_frame. - Add a test v1_2_LowPower_7_1_01_SingleProbeLinkMetricsWithEnhancedAcks.py for testing. Misc: - Add check for all the public Link Metrics APIs (Initiator side) to ensure that the Subject is a neighbor of the Initiator. If the address is not link-local or the neighbor is not found, an error UNKNOWN_NEIGHBOR would be returned. - Update PrepareEmptyFrame so that when it's called for reference device, its frame version would be set to 2015. --- examples/platforms/nrf528xx/src/radio.c | 12 + examples/platforms/simulation/radio.c | 80 +++- examples/platforms/utils/CMakeLists.txt | 1 + examples/platforms/utils/Makefile.am | 2 + examples/platforms/utils/link_metrics.cpp | 239 +++++++++++ examples/platforms/utils/link_metrics.h | 104 +++++ examples/platforms/utils/mac_frame.cpp | 44 ++ examples/platforms/utils/mac_frame.h | 37 ++ include/openthread/instance.h | 2 +- include/openthread/link_metrics.h | 92 ++++- include/openthread/platform/radio.h | 45 +- src/cli/README.md | 38 +- src/cli/cli.cpp | 106 ++++- src/cli/cli.hpp | 13 +- src/core/api/link_metrics_api.cpp | 20 + src/core/mac/mac.cpp | 33 ++ src/core/mac/mac.hpp | 3 + src/core/mac/mac_frame.cpp | 47 ++- src/core/mac/mac_frame.hpp | 59 ++- src/core/radio/radio.hpp | 30 ++ src/core/thread/link_metrics.cpp | 261 ++++++++---- src/core/thread/link_metrics.hpp | 95 +++-- src/core/thread/link_metrics_tlvs.hpp | 149 +++++++ src/core/thread/mesh_forwarder.cpp | 2 +- src/core/thread/mle.cpp | 10 +- src/core/thread/mle.hpp | 2 +- src/core/thread/topology.hpp | 50 ++- tests/scripts/thread-cert/node.py | 13 + tests/scripts/thread-cert/pktverify/consts.py | 15 + .../thread-cert/pktverify/layer_fields.py | 4 + .../thread-cert/pktverify/packet_filter.py | 34 ++ ..._SingleProbeLinkMetricsWithEnhancedAcks.py | 390 ++++++++++++++++++ tests/unit/test_platform.cpp | 21 + 33 files changed, 1877 insertions(+), 176 deletions(-) create mode 100644 examples/platforms/utils/link_metrics.cpp create mode 100644 examples/platforms/utils/link_metrics.h create mode 100755 tests/scripts/thread-cert/v1_2_LowPower_7_1_01_SingleProbeLinkMetricsWithEnhancedAcks.py diff --git a/examples/platforms/nrf528xx/src/radio.c b/examples/platforms/nrf528xx/src/radio.c index 2911f38ea..31c17d5c2 100644 --- a/examples/platforms/nrf528xx/src/radio.c +++ b/examples/platforms/nrf528xx/src/radio.c @@ -1287,3 +1287,15 @@ void otPlatRadioUpdateCslSampleTime(otInstance *aInstance, uint32_t aCslSampleTi sCslSampleTime = aCslSampleTime; } #endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE +otError otPlatRadioConfigureEnhAckProbing(otInstance * aInstance, + otLinkMetrics aLinkMetrics, + const otShortAddress aShortAddress, + const otExtAddress * aExtAddress) +{ + OT_UNUSED_VARIABLE(aInstance); + + return OT_ERROR_NOT_IMPLEMENTED; +} +#endif diff --git a/examples/platforms/simulation/radio.c b/examples/platforms/simulation/radio.c index fc9219de1..51182eb26 100644 --- a/examples/platforms/simulation/radio.c +++ b/examples/platforms/simulation/radio.c @@ -41,6 +41,7 @@ #include "common/code_utils.hpp" #include "utils/code_utils.h" +#include "utils/link_metrics.h" #include "utils/mac_frame.h" #include "utils/soft_source_match_table.h" @@ -92,6 +93,9 @@ static void radioTransmit(struct RadioMessage *aMessage, const struct otRadioFra static void radioSendMessage(otInstance *aInstance); static void radioSendAck(void); static void radioProcessFrame(otInstance *aInstance); +#if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 +static uint8_t generateAckIeData(uint8_t *aLinkMetricsIeData, uint8_t aLinkMetricsIeDataLen); +#endif static otRadioState sState = OT_RADIO_STATE_DISABLED; static struct RadioMessage sReceiveMessage; @@ -130,9 +134,8 @@ static uint8_t sAckIeDataLength = 0; #endif #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE -static const uint8_t sCslIeHeader[OT_IE_HEADER_SIZE] = {CSL_IE_HEADER_BYTES_LO, CSL_IE_HEADER_BYTES_HI}; -static uint32_t sCslSampleTime; -static uint32_t sCslPeriod; +static uint32_t sCslSampleTime; +static uint32_t sCslPeriod; #endif #if OPENTHREAD_CONFIG_PLATFORM_RADIO_COEX_ENABLE @@ -373,6 +376,10 @@ void platformRadioInit(void) { sChannelMaxTransmitPower[i] = OT_RADIO_POWER_INVALID; } + +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + otLinkMetricsInit(SIM_RECEIVE_SENSITIVITY); +#endif } #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE @@ -860,6 +867,26 @@ void radioSendAck(void) // Use enh-ack for 802.15.4-2015 frames if (otMacFrameIsVersion2015(&sReceiveFrame)) { + uint8_t linkMetricsDataLen = 0; + uint8_t *dataPtr = NULL; + +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + uint8_t linkMetricsData[OT_ENH_PROBING_IE_DATA_MAX_SIZE]; + otMacAddress macAddress; + + otEXPECT(otMacFrameGetSrcAddr(&sReceiveFrame, &macAddress) == OT_ERROR_NONE); + + linkMetricsDataLen = otLinkMetricsEnhAckGenData(&macAddress, sReceiveFrame.mInfo.mRxInfo.mLqi, + sReceiveFrame.mInfo.mRxInfo.mRssi, linkMetricsData); + + if (linkMetricsDataLen > 0) + { + dataPtr = linkMetricsData; + } +#endif + + sAckIeDataLength = generateAckIeData(dataPtr, linkMetricsDataLen); + otEXPECT(otMacFrameGenerateEnhAck(&sReceiveFrame, sReceiveFrame.mInfo.mRxInfo.mAckedWithFramePending, sAckIeData, sAckIeDataLength, &sAckFrame) == OT_ERROR_NONE); #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE @@ -892,7 +919,9 @@ exit: void radioProcessFrame(otInstance *aInstance) { - otError error = OT_ERROR_NONE; + otError error = OT_ERROR_NONE; + otMacAddress macAddress; + OT_UNUSED_VARIABLE(macAddress); sReceiveFrame.mInfo.mRxInfo.mRssi = -20; sReceiveFrame.mInfo.mRxInfo.mLqi = OT_RADIO_LQI_NONE; @@ -905,6 +934,10 @@ void radioProcessFrame(otInstance *aInstance) otEXPECT_ACTION(otMacFrameDoesAddrMatch(&sReceiveFrame, sPanid, sShortAddress, &sExtAddress), error = OT_ERROR_ABORT); +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + otEXPECT_ACTION(otMacFrameGetSrcAddr(&sReceiveFrame, &macAddress) == OT_ERROR_NONE, error = OT_ERROR_PARSE); +#endif + // generate acknowledgment if (otMacFrameIsAckRequested(&sReceiveFrame)) { @@ -1104,26 +1137,33 @@ uint64_t otPlatRadioGetNow(otInstance *aInstance) return otPlatTimeGet(); } -#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE -static otError updateIeData(otInstance *aInstance) - +#if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 +static uint8_t generateAckIeData(uint8_t *aLinkMetricsIeData, uint8_t aLinkMetricsIeDataLen) { - OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aLinkMetricsIeData); + OT_UNUSED_VARIABLE(aLinkMetricsIeDataLen); - otError error = OT_ERROR_NONE; uint8_t offset = 0; +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE if (sCslPeriod > 0) { - memcpy(sAckIeData, sCslIeHeader, OT_IE_HEADER_SIZE); - offset += OT_IE_HEADER_SIZE + OT_CSL_IE_SIZE; // reserve space for CSL IE + offset += otMacFrameGenerateCslIeTemplate(sAckIeData); } +#endif - sAckIeDataLength = offset; +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + if (aLinkMetricsIeData != NULL && aLinkMetricsIeDataLen > 0) + { + offset += otMacFrameGenerateEnhAckProbingIe(sAckIeData, aLinkMetricsIeData, aLinkMetricsIeDataLen); + } +#endif - return error; + return offset; } +#endif +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE otError otPlatRadioEnableCsl(otInstance *aInstance, uint32_t aCslPeriod, const otExtAddress *aExtAddr) { OT_UNUSED_VARIABLE(aInstance); @@ -1133,8 +1173,6 @@ otError otPlatRadioEnableCsl(otInstance *aInstance, uint32_t aCslPeriod, const o sCslPeriod = aCslPeriod; - error = updateIeData(aInstance); - return error; } @@ -1186,3 +1224,15 @@ otError otPlatRadioSetChannelMaxTransmitPower(otInstance *aInstance, uint8_t aCh exit: return error; } + +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE +otError otPlatRadioConfigureEnhAckProbing(otInstance * aInstance, + otLinkMetrics aLinkMetrics, + const otShortAddress aShortAddress, + const otExtAddress * aExtAddress) +{ + OT_UNUSED_VARIABLE(aInstance); + + return otLinkMetricsConfigureEnhAckProbing(aShortAddress, aExtAddress, aLinkMetrics); +} +#endif diff --git a/examples/platforms/utils/CMakeLists.txt b/examples/platforms/utils/CMakeLists.txt index 4712015e8..523e45116 100644 --- a/examples/platforms/utils/CMakeLists.txt +++ b/examples/platforms/utils/CMakeLists.txt @@ -28,6 +28,7 @@ add_library(openthread-platform-utils OBJECT debug_uart.c + link_metrics.cpp logging_rtt.c mac_frame.cpp otns_utils.cpp diff --git a/examples/platforms/utils/Makefile.am b/examples/platforms/utils/Makefile.am index 80967011c..a8cf2e46a 100644 --- a/examples/platforms/utils/Makefile.am +++ b/examples/platforms/utils/Makefile.am @@ -40,6 +40,8 @@ libopenthread_platform_utils_a_CPPFLAGS = \ libopenthread_platform_utils_a_SOURCES = \ code_utils.h \ debug_uart.c \ + link_metrics.cpp \ + link_metrics.h \ logging_rtt.c \ logging_rtt.h \ mac_frame.cpp \ diff --git a/examples/platforms/utils/link_metrics.cpp b/examples/platforms/utils/link_metrics.cpp new file mode 100644 index 000000000..f73f13188 --- /dev/null +++ b/examples/platforms/utils/link_metrics.cpp @@ -0,0 +1,239 @@ +/* + * 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. + */ + +#include "link_metrics.h" + +#include + +#include "common/clearable.hpp" +#include "common/linked_list.hpp" +#include "common/pool.hpp" +#include "thread/link_quality.hpp" + +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + +using namespace ot; + +static int8_t sNoiseFloor; ///< The noise floor used by Link Metrics. It should be set to the platform's + ///< noise floor (measured noise floor, receiver sensitivity or a constant). + +class LinkMetricsDataInfo : public LinkedListEntry, public Clearable +{ + friend class LinkedList; + friend class LinkedListEntry; + +public: + /** + * Construtor. + * + */ + LinkMetricsDataInfo(void) { Clear(); }; + + /** + * Set the information for this object. + * + * @param[in] aLinkMetrics Flags specifying what metrics to query. + * @param[in] aShortAddress Short Address of the Probing Initiator tracked by this object. + * @param[in] aExtAddress A reference to the Extended Address of the Probing Initiator tracked by this + * object. + * + */ + void Set(otLinkMetrics aLinkMetrics, otShortAddress aShortAddress, const otExtAddress &aExtAddress) + { + mLinkMetrics = aLinkMetrics; + mShortAddress = aShortAddress; + memcpy(mExtAddress.m8, aExtAddress.m8, sizeof(aExtAddress)); + } + + /** + * This method gets Link Metrics data stored in this object. + * + * TODO: Currently the order of Link Metircs data is fixed. Will update it to follow the order specified in TLV. + * + * @param[in] aLqi LQI value of the acknowledeged frame. + * @param[in] aRssi RSSI value of the acknowledged frame. + * @param[out] aData A pointer to the output buffer. @p aData MUST NOT be `nullptr`. The buffer must have + * at least 2 bytes (per spec 4.11.3.4.4.6). Otherwise the behavior would be undefined. + * + * @returns The number of bytes written. `0` on failure. + * + */ + uint8_t GetEnhAckData(uint8_t aLqi, int8_t aRssi, uint8_t *aData) const + { + enum + { + kEnhAckProbingDataMaxLen = 2, + }; + + uint8_t bytes = 0; + + VerifyOrExit(aData != nullptr); + + if (mLinkMetrics.mLqi) + { + aData[bytes++] = aLqi; + } + if (mLinkMetrics.mLinkMargin) + { + aData[bytes++] = static_cast(GetLinkMargin(aRssi) * 255 / + 130); // Linear scale Link Margin from [0, 130] to [0, 255] + } + if (bytes < kEnhAckProbingDataMaxLen && mLinkMetrics.mRssi) + { + aData[bytes++] = + static_cast((aRssi + 130) * 255 / 130); // Linear scale RSSI from [-130, 0] to [0, 255] + } + + exit: + return bytes; + } + + /** + * This method gets the metrics configured for the Enhanced-ACK Based Probing. + * + * @returns The metrics configured. + * + */ + otLinkMetrics GetLinkMetrics(void) const { return mLinkMetrics; } + +private: + uint8_t GetLinkMargin(int8_t aRssi) const { return LinkQualityInfo::ConvertRssToLinkMargin(sNoiseFloor, aRssi); } + + bool Matches(const otShortAddress &aShortAddress) const { return mShortAddress == aShortAddress; }; + + bool Matches(const otExtAddress &aExtAddress) const + { + return memcmp(&mExtAddress, &aExtAddress, sizeof(otExtAddress)) == 0; + }; + + LinkMetricsDataInfo *mNext; + + otLinkMetrics mLinkMetrics; + + otShortAddress mShortAddress; + otExtAddress mExtAddress; +}; + +enum +{ + kMaxEnhAckProbingInitiator = 10, +}; + +typedef Pool LinkMetricsDataInfoPool; + +typedef LinkedList LinkMetricsDataInfoList; + +static LinkMetricsDataInfoPool &GetLinkMetricsDataInfoPool(void) +{ + static LinkMetricsDataInfoPool sDataInfoPool; + return sDataInfoPool; +} + +static LinkMetricsDataInfoList &GetLinkMetricsDataInfoActiveList(void) +{ + static LinkMetricsDataInfoList sDataInfoActiveList; + return sDataInfoActiveList; +} + +static inline bool IsLinkMetricsClear(otLinkMetrics aLinkMetrics) +{ + return !aLinkMetrics.mPduCount && !aLinkMetrics.mLqi && !aLinkMetrics.mLinkMargin && !aLinkMetrics.mRssi; +} + +void otLinkMetricsInit(int8_t aNoiseFloor) +{ + sNoiseFloor = aNoiseFloor; +} + +otError otLinkMetricsConfigureEnhAckProbing(otShortAddress aShortAddress, + const otExtAddress *aExtAddress, + otLinkMetrics aLinkMetrics) +{ + otError error = OT_ERROR_NONE; + LinkMetricsDataInfo *dataInfo = nullptr; + + VerifyOrExit(aExtAddress != nullptr, error = OT_ERROR_INVALID_ARGS); + + if (IsLinkMetricsClear(aLinkMetrics)) ///< Remove the entry + { + dataInfo = GetLinkMetricsDataInfoActiveList().RemoveMatching(aShortAddress); + VerifyOrExit(dataInfo != nullptr, error = OT_ERROR_NOT_FOUND); + GetLinkMetricsDataInfoPool().Free(*dataInfo); + } + else + { + dataInfo = GetLinkMetricsDataInfoActiveList().FindMatching(aShortAddress); + + if (dataInfo == nullptr) + { + dataInfo = GetLinkMetricsDataInfoPool().Allocate(); + VerifyOrExit(dataInfo != nullptr, error = OT_ERROR_NO_BUFS); + dataInfo->Clear(); + GetLinkMetricsDataInfoActiveList().Push(*dataInfo); + } + + // Overwrite the previous configuration if it already existed. + dataInfo->Set(aLinkMetrics, aShortAddress, *aExtAddress); + } + +exit: + return error; +} + +LinkMetricsDataInfo *GetLinkMetricsInfoByMacAddress(const otMacAddress *aMacAddress) +{ + LinkMetricsDataInfo *dataInfo = nullptr; + + VerifyOrExit(aMacAddress != nullptr); + + if (aMacAddress->mType == OT_MAC_ADDRESS_TYPE_SHORT) + { + dataInfo = GetLinkMetricsDataInfoActiveList().FindMatching(aMacAddress->mAddress.mShortAddress); + } + else if (aMacAddress->mType == OT_MAC_ADDRESS_TYPE_EXTENDED) + { + dataInfo = GetLinkMetricsDataInfoActiveList().FindMatching(aMacAddress->mAddress.mExtAddress); + } + +exit: + return dataInfo; +} + +uint8_t otLinkMetricsEnhAckGenData(const otMacAddress *aMacAddress, uint8_t aLqi, int8_t aRssi, uint8_t *aData) +{ + uint8_t bytes = 0; + LinkMetricsDataInfo *dataInfo = GetLinkMetricsInfoByMacAddress(aMacAddress); + + VerifyOrExit(dataInfo != nullptr); + + bytes = dataInfo->GetEnhAckData(aLqi, aRssi, aData); + +exit: + return bytes; +} +#endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE diff --git a/examples/platforms/utils/link_metrics.h b/examples/platforms/utils/link_metrics.h new file mode 100644 index 000000000..1582b8705 --- /dev/null +++ b/examples/platforms/utils/link_metrics.h @@ -0,0 +1,104 @@ +/* + * 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 + * @brief + * This file defines the link metrics interface for OpenThread platform radio drivers. + * + * APIs defined in this module could be used by a platform to implement Enhanced-ACK Based Probing feature + * (Probing Subject side) in its radio driver. + * + */ + +#ifndef OPENTHREAD_UTILS_LINK_METRICS_H +#define OPENTHREAD_UTILS_LINK_METRICS_H + +#include + +#include "mac_frame.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * This method initializes the Link Metrics util module. + * + * @param[in] aNoiseFloor The noise floor used by Link Metrics. It should be set to the platform's + * noise floor (measured noise floor, receiver sensitivity or a constant). + * + */ +void otLinkMetricsInit(int8_t aNoiseFloor); + +/** + * This method sets/clears Enhanced-ACK Based Probing for a specific Initiator. + * + * This method can start/stop Enhanced-ACK Based Probing for a neighbor that has the address @p aShortAddress and + * @p aExtAddress. Once the Probing is started, the device would record the Link Metrics data of link layer frames + * sent from that neighbor and include the data into header IE in Enhanced-ACK sent to that neighbor. + * + * @param[in] aShortAddress The short address of the Initiator. + * @param[in] aExtAddress A pointer to the extended address of the Initiator. + * @param[in] aLinkMetrics Flags specifying what metrics to query (Pdu Count would be omitted). When + * @p aLinkMetrics is eqaul to `0`, this method clears the Initiator. + * + * @retval OT_ERROR_NONE Successfully configured the Enhanced-ACK Based Probing. + * @retval OT_ERROR_INVALID_ARGS @p aExtAddress is `nullptr`. + * @retval OT_ERROR_NOT_FOUND The Initiator indicated by @p aShortAddress is not found when trying to clear. + * @retval OT_ERROR_NO_BUFS No more Initiator can be supported. + * + */ +otError otLinkMetricsConfigureEnhAckProbing(otShortAddress aShortAddress, + const otExtAddress *aExtAddress, + otLinkMetrics aLinkMetrics); + +/** + * This method generates the Link Metrics data (assessed for the acknowledged frame) bytes that would be included in + * Vendor-Specific IE. + * + * This method first checks what Link Metrics are specified by the Initiator indicated by @p aMacAddress. And then + * write the values to @p aData. + * + * @param[in] aMacAddress The Mac address of the Initiator. + * @param[in] aLqi LQI value of the acknowledged frame. + * @param[in] aRssi RSSI value of the acknowledged frame. + * @param[out] aData A pointer to the buffer where the data would be written to. The caller should make + * sure that the size of the buffer is not less than the size of Link Metrics data + * configured before. + * + * @returns The size of data read. Would be `0` if the Initiator is not found or @p aData is invalid. + * + */ +uint8_t otLinkMetricsEnhAckGenData(const otMacAddress *aMacAddress, uint8_t aLqi, int8_t aRssi, uint8_t *aData); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // OPENTHREAD_UTILS_LINK_METRICS_H diff --git a/examples/platforms/utils/mac_frame.cpp b/examples/platforms/utils/mac_frame.cpp index dcaa6d119..24c0dea81 100644 --- a/examples/platforms/utils/mac_frame.cpp +++ b/examples/platforms/utils/mac_frame.cpp @@ -205,3 +205,47 @@ void otMacFrameSetFrameCounter(otRadioFrame *aFrame, uint32_t aFrameCounter) { static_cast(aFrame)->SetFrameCounter(aFrameCounter); } + +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE +uint8_t otMacFrameGenerateCslIeTemplate(uint8_t *aDest) +{ + assert(aDest != nullptr); + + reinterpret_cast(aDest)->SetId(Mac::Frame::kHeaderIeCsl); + reinterpret_cast(aDest)->SetLength(sizeof(Mac::CslIe)); + + return sizeof(Mac::HeaderIe) + sizeof(Mac::CslIe); +} +#endif + +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE +uint8_t otMacFrameGenerateEnhAckProbingIe(uint8_t *aDest, const uint8_t *aIeData, uint8_t aIeDataLength) +{ + uint8_t len = sizeof(Mac::VendorIeHeader) + aIeDataLength; + + assert(aDest != nullptr); + + reinterpret_cast(aDest)->SetId(Mac::Frame::kHeaderIeVendor); + reinterpret_cast(aDest)->SetLength(len); + + aDest += sizeof(Mac::HeaderIe); + + reinterpret_cast(aDest)->SetVendorOui(Mac::ThreadIe::kVendorOuiThreadCompanyId); + reinterpret_cast(aDest)->SetSubType(Mac::ThreadIe::kEnhAckProbingIe); + + if (aIeData != nullptr) + { + aDest += sizeof(Mac::VendorIeHeader); + memcpy(aDest, aIeData, aIeDataLength); + } + + return sizeof(Mac::HeaderIe) + len; +} + +void otMacFrameSetEnhAckProbingIe(otRadioFrame *aFrame, const uint8_t *aData, uint8_t aDataLen) +{ + assert(aFrame != nullptr && aData != nullptr); + + reinterpret_cast(aFrame)->SetEnhAckProbingIe(aData, aDataLen); +} +#endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE diff --git a/examples/platforms/utils/mac_frame.h b/examples/platforms/utils/mac_frame.h index edac95fe0..5aef72470 100644 --- a/examples/platforms/utils/mac_frame.h +++ b/examples/platforms/utils/mac_frame.h @@ -283,6 +283,43 @@ uint32_t otMacFrameGetFrameCounter(otRadioFrame *aFrame); */ void otMacFrameSetFrameCounter(otRadioFrame *aFrame, uint32_t aFrameCounter); +/** + * Write CSL IE to a buffer (without setting IE value). + * + * @param[out] aDest A pointer to the output buffer. + * + * @returns The total count of bytes (total length of CSL IE) written to the buffer. + * + */ +uint8_t otMacFrameGenerateCslIeTemplate(uint8_t *aDest); + +/** + * Write Enh-ACK Probing IE (Vendor IE with THREAD OUI) to a buffer. + * + * @p aIeData could be `NULL`. If @p aIeData is `NULL`, this method generates the IE with the data unset. This allows + * users to generate the pattern first and update value later. (For example, using `otMacFrameSetEnhAckProbingIe`) + * + * @param[out] aDest A pointer to the output buffer. + * @param[in] aIeData A pointer to the Link Metrics data. + * @param[in] aIeDataLength The length of Link Metrics data value. Should be `1` or `2`. (Per spec 4.11.3.4.4.6) + * + * @returns The total count of bytes (total length of the Vendor IE) written to the buffer. + * + */ +uint8_t otMacFrameGenerateEnhAckProbingIe(uint8_t *aDest, const uint8_t *aIeData, uint8_t aIeDataLength); + +/** + * Sets the data value of Enh-ACK Probing IE (Vendor IE with THREAD OUI) in a frame. + * + * If no Enh-ACK Probing IE is found in @p aFrame, nothing would be done. + * + * @param[in] aFrame The target frame that contains the IE. MUST NOT be `NULL`. + * @param[in] aData A pointer to the data value. MUST NOT be `NULL`. + * @param[in] aDataLen The length of @p aData. + * + */ +void otMacFrameSetEnhAckProbingIe(otRadioFrame *aFrame, const uint8_t *aData, uint8_t aDataLen); + #ifdef __cplusplus } // extern "C" #endif diff --git a/include/openthread/instance.h b/include/openthread/instance.h index a16ba7391..6de9df68c 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -53,7 +53,7 @@ extern "C" { * @note This number versions both OpenThread platform and user APIs. * */ -#define OPENTHREAD_API_VERSION (54) +#define OPENTHREAD_API_VERSION (55) /** * @addtogroup api-instance diff --git a/include/openthread/link_metrics.h b/include/openthread/link_metrics.h index 75f45da6b..32299fdca 100644 --- a/include/openthread/link_metrics.h +++ b/include/openthread/link_metrics.h @@ -39,6 +39,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { @@ -54,18 +55,6 @@ extern "C" { * */ -/** - * This structure represents what metrics are specified to query. - * - */ -typedef struct otLinkMetrics -{ - bool mPduCount : 1; - bool mLqi : 1; - bool mLinkMargin : 1; - bool mRssi : 1; -} otLinkMetrics; - /** * This structure represents the result (value) for a Link Metrics query. * @@ -92,11 +81,23 @@ typedef struct otLinkMetricsSeriesFlags bool mMacAck : 1; ///< MAC Ack. } otLinkMetricsSeriesFlags; +/** + * Enhanced-ACK Flags. + * + * These are used in Enhanced-ACK Based Probing to indicate whether to register or clear the probing. + * + */ +typedef enum otLinkMetricsEnhAckFlags +{ + OT_LINK_METRICS_ENH_ACK_CLEAR = 0, ///< Clear. + OT_LINK_METRICS_ENH_ACK_REGISTER = 1, ///< Register. +} otLinkMetricsEnhAckFlags; + /** * Link Metrics Status values. * */ -typedef enum otLinkMetricsStatus : uint8_t +typedef enum otLinkMetricsStatus { OT_LINK_METRICS_STATUS_SUCCESS = 0, OT_LINK_METRICS_STATUS_CANNOT_SUPPORT_NEW_SERIES = 1, @@ -129,6 +130,20 @@ typedef void (*otLinkMetricsReportCallback)(const otIp6Address * aSource, */ typedef void (*otLinkMetricsMgmtResponseCallback)(const otIp6Address *aSource, uint8_t aStatus, void *aContext); +/** + * This function pointer is called when Enh-ACK Probing IE is received. + * + * @param[in] aShortAddress The Mac short address of the Probing Subject. + * @param[in] aExtAddress A pointer to the Mac extended address of the Probing Subject. + * @param[in] aMetricsValues A pointer to the Link Metrics values obtained from the IE. + * @param[in] aContext A pointer to application-specific context. + * + */ +typedef void (*otLinkMetricsEnhAckProbingIeReportCallback)(otShortAddress aShortAddress, + const otExtAddress * aExtAddress, + const otLinkMetricsValues *aMetricsValues, + void * aContext); + /** * This function sends an MLE Data Request to query Link Metrics. * @@ -141,8 +156,10 @@ typedef void (*otLinkMetricsMgmtResponseCallback)(const otIp6Address *aSource, u * @param[in] aCallback A pointer to a function that is called when Link Metrics report is received. * @param[in] aCallbackContext A pointer to application-specific context. * - * @retval OT_ERROR_NONE Successfully sent a Link Metrics query message. - * @retval OT_ERROR_NO_BUFS Insufficient buffers to generate the MLE Data Request message. + * @retval OT_ERROR_NONE Successfully sent a Link Metrics query message. + * @retval OT_ERROR_NO_BUFS Insufficient buffers to generate the MLE Data Request message. + * @retval OT_ERROR_UNKNOWN_NEIGHBOR @p aDestination is not link-local or the neighbor is not found. + * @retval OT_ERROR_NOT_CAPABLE The neighbor is not a Thread 1.2 device and does not support Link Metrics. * */ otError otLinkMetricsQuery(otInstance * aInstance, @@ -165,9 +182,11 @@ otError otLinkMetricsQuery(otInstance * aInstance, * received. * @param[in] aCallbackContext A pointer to application-specific context. * - * @retval OT_ERROR_NONE Successfully sent a Link Metrics Management Request message. - * @retval OT_ERROR_NO_BUFS Insufficient buffers to generate the MLE Link Metrics Management Request message. - * @retval OT_ERROR_INVALID_ARGS @p aSeriesId is not within the valid range. + * @retval OT_ERROR_NONE Successfully sent a Link Metrics Management Request message. + * @retval OT_ERROR_NO_BUFS Insufficient buffers to generate the MLE Link Metrics Management Request message. + * @retval OT_ERROR_INVALID_ARGS @p aSeriesId is not within the valid range. + * @retval OT_ERROR_UNKNOWN_NEIGHBOR @p aDestination is not link-local or the neighbor is not found. + * @retval OT_ERROR_NOT_CAPABLE The neighbor is not a Thread 1.2 device and does not support Link Metrics. * */ otError otLinkMetricsConfigForwardTrackingSeries(otInstance * aInstance, @@ -178,6 +197,35 @@ otError otLinkMetricsConfigForwardTrackingSeries(otInstance * otLinkMetricsMgmtResponseCallback aCallback, void * aCallbackContext); +/** + * This function sends an MLE Link Metrics Management Request to configure/clear an Enhanced-ACK Based Probing. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aDestination A pointer to the destination address. + * @param[in] aEnhAckFlags Enh-ACK Flags to indicate whether to register or clear the probing. `0` to clear and + * `1` to register. Other values are reserved. + * @param[in] aLinkMetricsFlags A pointer to flags specifying what metrics to query. Should be `NULL` when + * `aEnhAckFlags` is `0`. + * @param[in] aCallback A pointer to a function that is called when an Enhanced Ack with Link Metrics is + * received. + * @param[in] aCallbackContext A pointer to application-specific context. + * + * @retval OT_ERROR_NONE Successfully sent a Link Metrics Management Request message. + * @retval OT_ERROR_NO_BUFS Insufficient buffers to generate the MLE Link Metrics Management Request message. + * @retval OT_ERROR_INVALID_ARGS @p aEnhAckFlags is not a valid value or @p aLinkMetricsFlags isn't correct. + * @retval OT_ERROR_UNKNOWN_NEIGHBOR @p aDestination is not link-local or the neighbor is not found. + * @retval OT_ERROR_NOT_CAPABLE The neighbor is not a Thread 1.2 device and does not support Link Metrics. + * + */ +otError otLinkMetricsConfigEnhAckProbing(otInstance * aInstance, + const otIp6Address * aDestination, + otLinkMetricsEnhAckFlags aEnhAckFlags, + const otLinkMetrics * aLinkMetricsFlags, + otLinkMetricsMgmtResponseCallback aCallback, + void * aCallbackContext, + otLinkMetricsEnhAckProbingIeReportCallback aEnhAckCallback, + void * aEnhAckCallbackContext); + /** * This function sends an MLE Link Probe message. * @@ -186,9 +234,11 @@ otError otLinkMetricsConfigForwardTrackingSeries(otInstance * * @param[in] aSeriesId The Series ID [1, 254] which the Probe message aims at. * @param[in] aLength The length of the data payload in Link Probe TLV, [0, 64] (per Thread 1.2 spec, 4.4.37). * - * @retval OT_ERROR_NONE Successfully sent a Link Probe message. - * @retval OT_ERROR_NO_BUFS Insufficient buffers to generate the MLE Link Probe message. - * @retval OT_ERROR_INVALID_ARGS @p aSeriesId or @p aLength is not within the valid range. + * @retval OT_ERROR_NONE Successfully sent a Link Probe message. + * @retval OT_ERROR_NO_BUFS Insufficient buffers to generate the MLE Link Probe message. + * @retval OT_ERROR_INVALID_ARGS @p aSeriesId or @p aLength is not within the valid range. + * @retval OT_ERROR_UNKNOWN_NEIGHBOR @p aDestination is not link-local or the neighbor is not found. + * @retval OT_ERROR_NOT_CAPABLE The neighbor is not a Thread 1.2 device and does not support Link Metrics. * */ otError otLinkMetricsSendLinkProbe(otInstance * aInstance, diff --git a/include/openthread/platform/radio.h b/include/openthread/platform/radio.h index 510152a48..f9e622f7e 100644 --- a/include/openthread/platform/radio.h +++ b/include/openthread/platform/radio.h @@ -151,9 +151,10 @@ typedef uint16_t otShortAddress; */ enum { - OT_IE_HEADER_SIZE = 2, ///< Size of IE header in bytes. - OT_CSL_IE_SIZE = 4, ///< Size of CSL IE content in bytes. - OT_ACK_IE_MAX_SIZE = 16, ///< Max length for header IE in ACK. + OT_IE_HEADER_SIZE = 2, ///< Size of IE header in bytes. + OT_CSL_IE_SIZE = 4, ///< Size of CSL IE content in bytes. + OT_ACK_IE_MAX_SIZE = 16, ///< Max length for header IE in ACK. + OT_ENH_PROBING_IE_DATA_MAX_SIZE = 2, ///< Max length of Link Metrics data in Vendor-Specific IE. }; #define CSL_IE_HEADER_BYTES_LO 0x04 ///< Fixed CSL IE header first byte @@ -323,6 +324,19 @@ typedef struct otRadioCoexMetrics bool mStopped; ///< Stats collection stopped due to saturation. } otRadioCoexMetrics; +/** + * This structure represents what metrics are specified to query. + * + */ +typedef struct otLinkMetrics +{ + bool mPduCount : 1; ///< Pdu count. + bool mLqi : 1; ///< Link Quality Indicator. + bool mLinkMargin : 1; ///< Link Margin. + bool mRssi : 1; ///< Received Signal Strength Indicator. + bool mReserved : 1; ///< Reserved, this is for reference device. +} otLinkMetrics; + /** * @} * @@ -964,6 +978,31 @@ void otPlatRadioUpdateCslSampleTime(otInstance *aInstance, uint32_t aCslSampleTi */ otError otPlatRadioSetChannelMaxTransmitPower(otInstance *aInstance, uint8_t aChannel, int8_t aMaxPower); +/** + * Enable/disable or update Enhanced-ACK Based Probing in radio for a specific Initiator. + * + * After Enhanced-ACK Based Probing is configured by a specific Probing Initiator, the Enhanced-ACK sent to that + * node should include Vendor-Specific IE containing Link Metrics data. This method informs the radio to start/stop to + * collect Link Metrics data and include Vendor-Specific IE that containing the data in Enhanced-ACK sent to that + * Probing Initiator. + * + * @param[in] aInstance The OpenThread instance structure. + * @param[in] aLinkMetrics This parameter specifies what metrics to query. Per spec 4.11.3.4.4.6, at most 2 metrics + * can be specified. The probing would be disabled if @p `aLinkMetrics` is bitwise 0. + * @param[in] aShortAddr The short address of the Probing Initiator. + * @param[in] aExtAddr The extended source address of the Probing Initiator. @p aExtAddr MUST NOT be `NULL`. + * + * @retval OT_ERROR_NONE Successfully configured the Enhanced-ACK Based Probing. + * @retval OT_ERROR_INVALID_ARGS @p aExtAddress is `NULL`. + * @retval OT_ERROR_NOT_FOUND The Initiator indicated by @p aShortAddress is not found when trying to clear. + * @retval OT_ERROR_NO_BUFS No more Initiator can be supported. + * + */ +otError otPlatRadioConfigureEnhAckProbing(otInstance * aInstance, + otLinkMetrics aLinkMetrics, + otShortAddress aShortAddress, + const otExtAddress *aExtAddress); + /** * @} * diff --git a/src/cli/README.md b/src/cli/README.md index 5edca1ab9..6969ddd23 100644 --- a/src/cli/README.md +++ b/src/cli/README.md @@ -58,7 +58,7 @@ Done - [keysequence](#keysequence-counter) - [leaderdata](#leaderdata) - [leaderweight](#leaderweight) -- [linkmetrics](#linkmetrics-mgmt-ipaddr-forward-seriesid-ldraxpqmr) +- [linkmetrics](#linkmetrics-mgmt-ipaddr-enhanced-ack-clear) - [linkquality](#linkquality-extaddr) - [log](#log-filename-filename) - [mac](#mac-retries-direct) @@ -1126,6 +1126,42 @@ Set the Thread Leader Weight. Done ``` +### linkmetrics mgmt \ enhanced-ack clear + +Send a Link Metrics Management Request to clear an Enhanced-ACK Based Probing. + +- ipaddr: Peer address (SHOULD be link local address of the neighboring device). + +```bash +> linkmetrics mgmt fe80:0:0:0:3092:f334:1455:1ad2 enhanced-ack clear +Done +> Received Link Metrics Management Response from: fe80:0:0:0:3092:f334:1455:1ad2 +Status: Success +``` + +### linkmetrics mgmt \ enhanced-ack register [qmr][r] + +Send a Link Metrics Management Request to register an Enhanced-ACK Based Probing. + +- ipaddr: Peer address. +- qmr: This specifies what metrics to query. At most two options are allowed to select (per spec 4.11.3.4.4.6). + - q: Layer 2 LQI. + - m: Link Margin. + - r: RSSI. +- r: This is optional and only used for reference devices. When this option is specified, Type/Average Enum of each Type Id Flags would be set to `reserved`. This is used to verify the Probing Subject correctly handles invalid Type Id Flags. This is only available when `OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE` is enabled. + +```bash +> linkmetrics mgmt fe80:0:0:0:3092:f334:1455:1ad2 enhanced-ack register qm +Done +> Received Link Metrics Management Response from: fe80:0:0:0:3092:f334:1455:1ad2 +Status: Success + +> linkmetrics mgmt fe80:0:0:0:3092:f334:1455:1ad2 enhanced-ack register qm r +Done +> Received Link Metrics Management Response from: fe80:0:0:0:3092:f334:1455:1ad2 +Status: Cannot support new series +``` + ### linkmetrics mgmt \ forward \ [ldraX][pqmr] Send a Link Metrics Management Request to configure a Forward Tracking Series. diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index f7bf7635e..658feb98b 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -1950,38 +1950,43 @@ void Interpreter::HandleLinkMetricsReport(const otIp6Address * aAddress, static_cast(aContext)->HandleLinkMetricsReport(aAddress, aMetricsValues, aStatus); } -void Interpreter::HandleLinkMetricsReport(const otIp6Address * aAddress, - const otLinkMetricsValues *aMetricsValues, - uint8_t aStatus) +void Interpreter::PrintLinkMetricsValue(const otLinkMetricsValues *aMetricsValues) { const char kLinkMetricsTypeCount[] = "(Count/Summation)"; const char kLinkMetricsTypeAverage[] = "(Exponential Moving Average)"; + if (aMetricsValues->mMetrics.mPduCount) + { + OutputLine(" - PDU Counter: %d %s", aMetricsValues->mPduCountValue, kLinkMetricsTypeCount); + } + + if (aMetricsValues->mMetrics.mLqi) + { + OutputLine(" - LQI: %d %s", aMetricsValues->mLqiValue, kLinkMetricsTypeAverage); + } + + if (aMetricsValues->mMetrics.mLinkMargin) + { + OutputLine(" - Margin: %d (dB) %s", aMetricsValues->mLinkMarginValue, kLinkMetricsTypeAverage); + } + + if (aMetricsValues->mMetrics.mRssi) + { + OutputLine(" - RSSI: %d (dBm) %s", aMetricsValues->mRssiValue, kLinkMetricsTypeAverage); + } +} + +void Interpreter::HandleLinkMetricsReport(const otIp6Address * aAddress, + const otLinkMetricsValues *aMetricsValues, + uint8_t aStatus) +{ OutputFormat("Received Link Metrics Report from: "); OutputIp6Address(*aAddress); OutputLine(""); if (aMetricsValues != nullptr) { - if (aMetricsValues->mMetrics.mPduCount) - { - OutputLine(" - PDU Counter: %d %s", aMetricsValues->mPduCountValue, kLinkMetricsTypeCount); - } - - if (aMetricsValues->mMetrics.mLqi) - { - OutputLine(" - LQI: %d %s", aMetricsValues->mLqiValue, kLinkMetricsTypeAverage); - } - - if (aMetricsValues->mMetrics.mLinkMargin) - { - OutputLine(" - Margin: %d (dB) %s", aMetricsValues->mLinkMarginValue, kLinkMetricsTypeAverage); - } - - if (aMetricsValues->mMetrics.mRssi) - { - OutputLine(" - RSSI: %d (dBm) %s", aMetricsValues->mRssiValue, kLinkMetricsTypeAverage); - } + PrintLinkMetricsValue(aMetricsValues); } else { @@ -2003,6 +2008,29 @@ void Interpreter::HandleLinkMetricsMgmtResponse(const otIp6Address *aAddress, ui OutputLine("Status: %s", LinkMetricsStatusToStr(aStatus)); } +void Interpreter::HandleLinkMetricsEnhAckProbingIe(otShortAddress aShortAddress, + const otExtAddress * aExtAddress, + const otLinkMetricsValues *aMetricsValues, + void * aContext) +{ + static_cast(aContext)->HandleLinkMetricsEnhAckProbingIe(aShortAddress, aExtAddress, aMetricsValues); +} + +void Interpreter::HandleLinkMetricsEnhAckProbingIe(otShortAddress aShortAddress, + const otExtAddress * aExtAddress, + const otLinkMetricsValues *aMetricsValues) +{ + OutputFormat("Received Link Metrics data in Enh Ack from neighbor, short address:0x%02x , extended address:", + aShortAddress); + OutputExtAddress(*aExtAddress); + OutputLine(""); + + if (aMetricsValues != nullptr) + { + PrintLinkMetricsValue(aMetricsValues); + } +} + const char *Interpreter::LinkMetricsStatusToStr(uint8_t aStatus) { uint8_t strIndex = 0; @@ -2193,6 +2221,40 @@ otError Interpreter::ProcessLinkMetricsMgmt(uint8_t aArgsLength, char *aArgs[]) clear ? nullptr : &linkMetrics, &Interpreter::HandleLinkMetricsMgmtResponse, this); } + else if (strcmp(aArgs[1], "enhanced-ack") == 0) + { + otLinkMetricsEnhAckFlags enhAckFlags; + otLinkMetrics linkMetrics; + otLinkMetrics * pLinkMetrics = &linkMetrics; + + VerifyOrExit(aArgsLength >= 3); + + if (strcmp(aArgs[2], "clear") == 0) + { + enhAckFlags = OT_LINK_METRICS_ENH_ACK_CLEAR; + pLinkMetrics = nullptr; + } + else if (strcmp(aArgs[2], "register") == 0) + { + enhAckFlags = OT_LINK_METRICS_ENH_ACK_REGISTER; + VerifyOrExit(aArgsLength >= 4); + SuccessOrExit(error = ParseLinkMetricsFlags(linkMetrics, aArgs[3])); +#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE + if (aArgsLength > 4 && strcmp(aArgs[4], "r") == 0) + { + linkMetrics.mReserved = true; + } +#endif + } + else + { + ExitNow(); + } + + error = otLinkMetricsConfigEnhAckProbing(mInstance, &address, enhAckFlags, pLinkMetrics, + &Interpreter::HandleLinkMetricsMgmtResponse, this, + &Interpreter::HandleLinkMetricsEnhAckProbingIe, this); + } exit: return error; diff --git a/src/cli/cli.hpp b/src/cli/cli.hpp index 978fa9dd7..35c7912f5 100644 --- a/src/cli/cli.hpp +++ b/src/cli/cli.hpp @@ -558,6 +558,8 @@ private: void HandleSntpResponse(uint64_t aTime, otError aResult); #endif #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + void PrintLinkMetricsValue(const otLinkMetricsValues *aMetricsValues); + static void HandleLinkMetricsReport(const otIp6Address * aAddress, const otLinkMetricsValues *aMetricsValues, uint8_t aStatus, @@ -571,8 +573,17 @@ private: void HandleLinkMetricsMgmtResponse(const otIp6Address *aAddress, uint8_t aStatus); + static void HandleLinkMetricsEnhAckProbingIe(otShortAddress aShortAddress, + const otExtAddress * aExtAddress, + const otLinkMetricsValues *aMetricsValues, + void * aContext); + + void HandleLinkMetricsEnhAckProbingIe(otShortAddress aShortAddress, + const otExtAddress * aExtAddress, + const otLinkMetricsValues *aMetricsValues); + const char *LinkMetricsStatusToStr(uint8_t aStatus); -#endif +#endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE static Interpreter &GetOwner(OwnerLocator &aOwnerLocator); diff --git a/src/core/api/link_metrics_api.cpp b/src/core/api/link_metrics_api.cpp index 62fd7994e..06180c245 100644 --- a/src/core/api/link_metrics_api.cpp +++ b/src/core/api/link_metrics_api.cpp @@ -74,6 +74,26 @@ otError otLinkMetricsConfigForwardTrackingSeries(otInstance * static_cast(*aDestination), aSeriesId, aSeriesFlags, aLinkMetricsFlags); } +otError otLinkMetricsConfigEnhAckProbing(otInstance * aInstance, + const otIp6Address * aDestination, + const otLinkMetricsEnhAckFlags aEnhAckFlags, + const otLinkMetrics * aLinkMetricsFlags, + otLinkMetricsMgmtResponseCallback aCallback, + void * aCallbackContext, + otLinkMetricsEnhAckProbingIeReportCallback aEnhAckCallback, + void * aEnhAckCallbackContext) +{ + OT_ASSERT(aDestination != nullptr); + + static_cast(aInstance)->Get().SetLinkMetricsMgmtResponseCallback(aCallback, + aCallbackContext); + static_cast(aInstance)->Get().SetLinkMetricsEnhAckProbingCallback(aEnhAckCallback, + aEnhAckCallbackContext); + + return static_cast(aInstance)->Get().SendMgmtRequestEnhAckProbing( + static_cast(*aDestination), aEnhAckFlags, aLinkMetricsFlags); +} + otError otLinkMetricsSendLinkProbe(otInstance * aInstance, const otIp6Address *aDestination, uint8_t aSeriesId, diff --git a/src/core/mac/mac.cpp b/src/core/mac/mac.cpp index 20cbfabec..9aec61e2c 100644 --- a/src/core/mac/mac.cpp +++ b/src/core/mac/mac.cpp @@ -1409,6 +1409,7 @@ void Mac::RecordFrameTransmitStatus(const TxFrame &aFrame, #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE neighbor->AggregateLinkMetrics(/* aSeriesId */ 0, aAckFrame->GetType(), aAckFrame->GetLqi(), aAckFrame->GetRssi()); + ProcessEnhAckProbing(*aAckFrame, *neighbor); #endif #if OPENTHREAD_FTD if (aAckFrame->GetVersion() == Frame::kFcfFrameVersion2015) @@ -2577,6 +2578,31 @@ exit: } #endif // !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE +void Mac::ProcessEnhAckProbing(const RxFrame &aFrame, const Neighbor &aNeighbor) +{ + enum + { + kEnhAckProbingIeMaxLen = 2, + }; + + const HeaderIe *enhAckProbingIe = + reinterpret_cast(aFrame.GetThreadIe(ThreadIe::kEnhAckProbingIe)); + const uint8_t *data = + reinterpret_cast(enhAckProbingIe) + sizeof(HeaderIe) + sizeof(VendorIeHeader); + uint8_t dataLen = 0; + + VerifyOrExit(enhAckProbingIe != nullptr); + + dataLen = enhAckProbingIe->GetLength() - sizeof(VendorIeHeader); + VerifyOrExit(dataLen <= kEnhAckProbingIeMaxLen); + + Get().ProcessEnhAckIeData(data, dataLen, aNeighbor); +exit: + return; +} +#endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + #if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT otError Mac::AppendHeaderIe(bool aIsTimeSync, TxFrame &aFrame) const { @@ -2668,6 +2694,13 @@ void Mac::UpdateFrameControlField(const Neighbor *aNeighbor, bool aIsTimeSync, u } else #endif +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + if (aNeighbor != nullptr && aNeighbor->IsEnhAckProbingActive()) + { + aFcf |= Frame::kFcfFrameVersion2015; ///< Set version to 2015 to fetch Link Metrics data in Enh-ACK. + } + else +#endif #endif { aFcf |= Frame::kFcfFrameVersion2006; diff --git a/src/core/mac/mac.hpp b/src/core/mac/mac.hpp index d0400cd4c..caa413b63 100644 --- a/src/core/mac/mac.hpp +++ b/src/core/mac/mac.hpp @@ -864,6 +864,9 @@ private: #if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE void ProcessCsl(const RxFrame &aFrame, const Address &aSrcAddr); +#endif +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + void ProcessEnhAckProbing(const RxFrame &aFrame, const Neighbor &aNeighbor); #endif static const char *OperationToString(Operation aOperation); diff --git a/src/core/mac/mac_frame.cpp b/src/core/mac/mac_frame.cpp index 84686a01a..bdbb03016 100644 --- a/src/core/mac/mac_frame.cpp +++ b/src/core/mac/mac_frame.cpp @@ -824,7 +824,6 @@ uint8_t Frame::FindPayloadIndex(void) const const HeaderIe *ie = reinterpret_cast(&mPsdu[index]); index += sizeof(HeaderIe); - VerifyOrExit(index + footerLength <= mLength, index = kInvalidIndex); index += ie->GetLength(); @@ -933,6 +932,41 @@ const uint8_t *Frame::GetHeaderIe(uint8_t aIeId) const exit: return header; } + +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE +const uint8_t *Frame::GetThreadIe(uint8_t aSubType) const +{ + uint8_t index = FindHeaderIeIndex(); + uint8_t payloadIndex = FindPayloadIndex(); + const uint8_t *header = nullptr; + + // `FindPayloadIndex()` verifies that Header IE(s) in frame (if present) + // are well-formed. + VerifyOrExit((index != kInvalidIndex) && (payloadIndex != kInvalidIndex)); + + while (index <= payloadIndex) + { + const HeaderIe *ie = reinterpret_cast(&mPsdu[index]); + + if (ie->GetId() == kHeaderIeVendor) + { + const VendorIeHeader *vendorIe = + reinterpret_cast(reinterpret_cast(ie) + sizeof(HeaderIe)); + if (vendorIe->GetVendorOui() == ThreadIe::kVendorOuiThreadCompanyId && vendorIe->GetSubType() == aSubType) + { + header = &mPsdu[index]; + ExitNow(); + } + } + + index += sizeof(HeaderIe) + ie->GetLength(); + } + +exit: + return header; +} +#endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + #endif // OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE @@ -951,6 +985,17 @@ exit: } #endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE +void Frame::SetEnhAckProbingIe(const uint8_t *aValue, uint8_t aLen) +{ + uint8_t *cur = GetThreadIe(ThreadIe::kEnhAckProbingIe); + + OT_ASSERT(cur != nullptr); + + memcpy(cur + sizeof(HeaderIe) + sizeof(VendorIeHeader), aValue, aLen); +} +#endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE const TimeIe *Frame::GetTimeIe(void) const { diff --git a/src/core/mac/mac_frame.hpp b/src/core/mac/mac_frame.hpp index 959cb67fb..c8e0672b9 100644 --- a/src/core/mac/mac_frame.hpp +++ b/src/core/mac/mac_frame.hpp @@ -135,7 +135,7 @@ private: } OT_TOOL_PACKED_END; -#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE +#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE || OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE /** * This class implements vendor specific Header IE generation and parsing. * @@ -186,6 +186,7 @@ private: uint8_t mSubType; } OT_TOOL_PACKED_END; +#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE /** * This class implements Time Header IE generation and parsing. * @@ -252,6 +253,24 @@ private: } OT_TOOL_PACKED_END; #endif // OPENTHREAD_CONFIG_TIME_SYNC_ENABLE +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE +class ThreadIe +{ +public: + enum : uint32_t + { + kVendorOuiThreadCompanyId = 0xeab89b, + }; + + enum SubType : uint8_t + { + kEnhAckProbingIe = 0x00, + }; +}; +#endif + +#endif // OPENTHREAD_CONFIG_TIME_SYNC_ENABLE || OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + /** * This class implements IEEE 802.15.4 MAC frame generation and parsing. * @@ -938,6 +957,33 @@ public: */ const uint8_t *GetHeaderIe(uint8_t aIeId) const; + /** + * This method returns a pointer to a specific Thread IE. + * + * A Thread IE is a vendor specific IE with Vendor OUI as `kVendorOuiThreadCompanyId`. + * + * @param[in] aSubType The sub type of the Thread IE. + * + * @returns A pointer to the Thread IE, nullptr if not found. + * + */ + uint8_t *GetThreadIe(uint8_t aSubType) + { + return const_cast(const_cast(this)->GetThreadIe(aSubType)); + } + + /** + * This method returns a pointer to a specific Thread IE. + * + * A Thread IE is a vendor specific IE with Vendor OUI as `kVendorOuiThreadCompanyId`. + * + * @param[in] aSubType The sub type of the Thread IE. + * + * @returns A pointer to the Thread IE, nullptr if not found. + * + */ + const uint8_t *GetThreadIe(uint8_t aSubType) const; + #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE /** * This method finds CSL IE in the frame and modify its content. @@ -949,6 +995,17 @@ public: void SetCslIe(uint16_t aCslPeriod, uint16_t aCslPhase); #endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + /** + * This method finds Enhanced ACK Probing (Vendor Specific) IE and set its value. + * + * @param[in] aValue A pointer to the value to set. + * @param[in] aLen The length of @p aValue. + * + */ + void SetEnhAckProbingIe(const uint8_t *aValue, uint8_t aLen); +#endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + #endif // OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT #if OPENTHREAD_CONFIG_MULTI_RADIO diff --git a/src/core/radio/radio.hpp b/src/core/radio/radio.hpp index 417e459cd..bdec57903 100644 --- a/src/core/radio/radio.hpp +++ b/src/core/radio/radio.hpp @@ -564,6 +564,36 @@ public: */ uint32_t GetPreferredChannelMask(void); +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + /** + * This method enables/disables or updates Enhanced-ACK Based Probing in radio for a specific Initiator. + * + * After Enhanced-ACK Based Probing is configured by a specific Probing Initiator, the Enhanced-ACK sent to that + * node should include Vendor-Specific IE containing Link Metrics data. This method informs the radio to + * starts/stops to collect Link Metrics data and include Vendor-Specific IE that containing the data + * in Enhanced-ACK sent to that Probing Initiator. + * + * @param[in] aInstance The OpenThread instance structure. + * @param[in] aDataLength Length of Link Metrics data in the Vendor-Specific IE. Per spec 4.11.3.4.4.6, + * @p aDataLength should only be 1 or 2. The probing would be disabled if `aDataLength` is + * `0`. + * @param[in] aShortAddr The short address of the the probing Initiator. + * @param[in] aExtAddr The extended source address of the probing Initiator. + * + * @retval OT_ERROR_NONE Successfully enable/disable or update Enhanced-ACK Based Probing for a specific + * Initiator. + * @retval OT_ERROR_INVALID_ARGS @p aDataLength or @p aExtAddr is not valid. + * @retval OT_ERROR_NOT_SUPPORTED Radio driver doesn't support Enhanced-ACK Probing. + * + */ + otError ConfigureEnhAckProbing(otLinkMetrics aLinkMetrics, + const Mac::ShortAddress &aShortAddress, + const Mac::ExtAddress & aExtAddress) + { + return otPlatRadioConfigureEnhAckProbing(GetInstancePtr(), aLinkMetrics, aShortAddress, &aExtAddress); + } +#endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + /** * This method checks if a given channel is valid as a CSL channel. * diff --git a/src/core/thread/link_metrics.cpp b/src/core/thread/link_metrics.cpp index 80d29d9e2..95f6bea0c 100644 --- a/src/core/thread/link_metrics.cpp +++ b/src/core/thread/link_metrics.cpp @@ -39,6 +39,7 @@ #include "common/instance.hpp" #include "common/locator-getters.hpp" #include "common/logging.hpp" +#include "thread/neighbor_table.hpp" #include "link_metrics_tlvs.hpp" @@ -118,6 +119,10 @@ otError LinkMetrics::LinkMetricsQuery(const Ip6::Address & aDestination, otError error; LinkMetricsTypeIdFlags typeIdFlags[kMaxTypeIdFlags]; uint8_t typeIdFlagsCount = 0; + Neighbor * neighbor = GetNeighborFromLinkLocalAddr(aDestination); + + VerifyOrExit(neighbor != nullptr, error = OT_ERROR_UNKNOWN_NEIGHBOR); + VerifyOrExit(neighbor->IsThreadVersion1p2(), error = OT_ERROR_NOT_CAPABLE); if (aLinkMetricsFlags != nullptr) { @@ -146,6 +151,10 @@ otError LinkMetrics::SendMgmtRequestForwardTrackingSeries(const Ip6::Address & 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 = OT_ERROR_UNKNOWN_NEIGHBOR); + VerifyOrExit(neighbor->IsThreadVersion1p2(), error = OT_ERROR_NOT_CAPABLE); // Directly transform `aLinkMetricsFlags` into LinkMetricsTypeIdFlags and put them into `subTlvs` if (aLinkMetricsFlags != nullptr) @@ -164,23 +173,7 @@ otError LinkMetrics::SendMgmtRequestForwardTrackingSeries(const Ip6::Address & memcpy(subTlvs + sizeof(Tlv), &aSeriesId, sizeof(aSeriesId)); - seriesFlags->Clear(); - if (aSeriesFlags.mLinkProbe) - { - seriesFlags->SetLinkProbeFlag(); - } - if (aSeriesFlags.mMacData) - { - seriesFlags->SetMacDataFlag(); - } - if (aSeriesFlags.mMacDataRequest) - { - seriesFlags->SetMacDataRequestFlag(); - } - if (aSeriesFlags.mMacAck) - { - seriesFlags->SetMacAckFlag(); - } + seriesFlags->SetFromOtSeriesFlags(aSeriesFlags); error = Get().SendLinkMetricsManagementRequest(aDestination, subTlvs, forwardProbingRegistrationSubTlv->GetSize()); @@ -191,10 +184,57 @@ exit: return error; } +otError LinkMetrics::SendMgmtRequestEnhAckProbing(const Ip6::Address & aDestination, + const otLinkMetricsEnhAckFlags aEnhAckFlags, + const otLinkMetrics * aLinkMetricsFlags) +{ + otError error = OT_ERROR_NONE; + EnhAckLinkMetricsConfigurationSubTlv enhAckLinkMetricsConfigurationSubTlv; + Mac::Address macAddress; + Neighbor * neighbor = GetNeighborFromLinkLocalAddr(aDestination); + + VerifyOrExit(neighbor != nullptr, error = OT_ERROR_UNKNOWN_NEIGHBOR); + VerifyOrExit(neighbor->IsThreadVersion1p2(), error = OT_ERROR_NOT_CAPABLE); + + if (aEnhAckFlags == OT_LINK_METRICS_ENH_ACK_CLEAR) + { + VerifyOrExit(aLinkMetricsFlags == nullptr, error = OT_ERROR_INVALID_ARGS); + } + + enhAckLinkMetricsConfigurationSubTlv.SetEnhAckFlags(aEnhAckFlags); + + if (aLinkMetricsFlags != nullptr) + { + enhAckLinkMetricsConfigurationSubTlv.SetTypeIdFlags(aLinkMetricsFlags); + } + + error = Get().SendLinkMetricsManagementRequest( + aDestination, reinterpret_cast(&enhAckLinkMetricsConfigurationSubTlv), + enhAckLinkMetricsConfigurationSubTlv.GetSize()); + + if (aLinkMetricsFlags != nullptr) + { + neighbor->SetEnhAckProbingMetrics(*aLinkMetricsFlags); + } + else + { + otLinkMetrics linkMetrics; + memset(&linkMetrics, 0, sizeof(linkMetrics)); + neighbor->SetEnhAckProbingMetrics(linkMetrics); + } + +exit: + return error; +} + otError LinkMetrics::SendLinkProbe(const Ip6::Address &aDestination, uint8_t aSeriesId, uint8_t aLength) { - otError error = OT_ERROR_NONE; - uint8_t buf[kLinkProbeMaxLen]; + otError error = OT_ERROR_NONE; + uint8_t buf[kLinkProbeMaxLen]; + Neighbor *neighbor = GetNeighborFromLinkLocalAddr(aDestination); + + VerifyOrExit(neighbor != nullptr, error = OT_ERROR_UNKNOWN_NEIGHBOR); + VerifyOrExit(neighbor->IsThreadVersion1p2(), error = OT_ERROR_NOT_CAPABLE); VerifyOrExit(aLength <= LinkMetrics::kLinkProbeMaxLen && aSeriesId != kQueryIdSingleProbe && aSeriesId != kSeriesIdAllSeries, @@ -272,13 +312,13 @@ otError LinkMetrics::AppendLinkMetricsReport(Message &aMessage, const Message &a LinkMetricsSeriesInfo *seriesInfo = aNeighbor.GetForwardTrackingSeriesInfo(queryId); if (seriesInfo == nullptr) { - SuccessOrExit( - error = AppendStatusSubTlvToMessage(aMessage, length, OT_LINK_METRICS_STATUS_SERIESID_NOT_RECOGNIZED)); + SuccessOrExit(error = + AppendStatusSubTlvToMessage(aMessage, length, kLinkMetricsStatusSeriesIdNotRecognized)); } else if (seriesInfo->GetPduCount() == 0) { - SuccessOrExit(error = AppendStatusSubTlvToMessage(aMessage, length, - OT_LINK_METRICS_STATUS_NO_MATCHING_FRAMES_RECEIVED)); + SuccessOrExit( + error = AppendStatusSubTlvToMessage(aMessage, length, kLinkMetricsStatusNoMatchingFramesReceived)); } else { @@ -303,19 +343,21 @@ exit: return error; } -otError LinkMetrics::HandleLinkMetricsManagementRequest(const Message & aMessage, - Neighbor & aNeighbor, - otLinkMetricsStatus &aStatus) +otError LinkMetrics::HandleLinkMetricsManagementRequest(const Message & aMessage, + Neighbor & aNeighbor, + LinkMetricsStatus &aStatus) { - otError error = OT_ERROR_NONE; - Tlv tlv; - uint8_t seriesId; - SeriesFlags seriesFlags; - otLinkMetrics linkMetrics; - bool hasForwardProbingRegistrationTlv = false; - uint16_t offset; - uint16_t length; - uint16_t index = 0; + otError error = OT_ERROR_NONE; + Tlv tlv; + uint8_t seriesId; + SeriesFlags seriesFlags; + LinkMetricsEnhAckFlags enhAckFlags; + otLinkMetrics linkMetrics; + 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)); @@ -329,7 +371,7 @@ otError LinkMetrics::HandleLinkMetricsManagementRequest(const Message & aMe switch (tlv.GetType()) { case kForwardProbingRegistration: - VerifyOrExit(!hasForwardProbingRegistrationTlv, error = OT_ERROR_PARSE); + VerifyOrExit(!hasForwardProbingRegistrationTlv && !hasEnhAckProbingTlv, error = OT_ERROR_PARSE); VerifyOrExit(tlv.GetLength() >= sizeof(seriesId) + sizeof(seriesFlags), error = OT_ERROR_PARSE); SuccessOrExit(aMessage.Read(pos, seriesId)); pos += sizeof(seriesId); @@ -340,6 +382,16 @@ otError LinkMetrics::HandleLinkMetricsManagementRequest(const Message & aMe hasForwardProbingRegistrationTlv = true; break; + case kEnhancedACKConfiguration: + VerifyOrExit(!hasForwardProbingRegistrationTlv && !hasEnhAckProbingTlv, error = OT_ERROR_PARSE); + VerifyOrExit(tlv.GetLength() >= sizeof(LinkMetricsEnhAckFlags), error = OT_ERROR_PARSE); + SuccessOrExit(aMessage.Read(pos, enhAckFlags)); + pos += sizeof(enhAckFlags); + SuccessOrExit(error = ReadTypeIdFlagsFromMessage( + aMessage, pos, static_cast(offset + index + tlv.GetSize()), linkMetrics)); + hasEnhAckProbingTlv = true; + break; + default: break; } @@ -351,6 +403,10 @@ otError LinkMetrics::HandleLinkMetricsManagementRequest(const Message & aMe { aStatus = ConfigureForwardTrackingSeries(seriesId, seriesFlags, linkMetrics, aNeighbor); } + else if (hasEnhAckProbingTlv) + { + aStatus = ConfigureEnhAckProbing(enhAckFlags, linkMetrics, aNeighbor); + } exit: return error; @@ -358,13 +414,13 @@ exit: otError LinkMetrics::HandleLinkMetricsManagementResponse(const Message &aMessage, const Ip6::Address &aAddress) { - otError error = OT_ERROR_NONE; - Tlv tlv; - uint16_t offset; - uint16_t length; - uint16_t index = 0; - otLinkMetricsStatus status; - bool hasStatus = false; + otError error = OT_ERROR_NONE; + Tlv tlv; + uint16_t offset; + uint16_t length; + uint16_t index = 0; + LinkMetricsStatus status; + bool hasStatus = false; VerifyOrExit(mLinkMetricsMgmtResponseCallback != nullptr); @@ -412,7 +468,7 @@ void LinkMetrics::HandleLinkMetricsReport(const Message & aMessage, LinkMetricsTypeIdFlags typeIdFlags; bool hasStatus = false; bool hasReport = false; - otLinkMetricsStatus status; + LinkMetricsStatus status; OT_UNUSED_VARIABLE(error); @@ -536,6 +592,42 @@ void LinkMetrics::SetLinkMetricsMgmtResponseCallback(otLinkMetricsMgmtResponseCa mLinkMetricsMgmtResponseCallbackContext = aCallbackContext; } +void LinkMetrics::SetLinkMetricsEnhAckProbingCallback(otLinkMetricsEnhAckProbingIeReportCallback aCallback, + void * aCallbackContext) +{ + mLinkMetricsEnhAckProbingIeReportCallback = aCallback; + mLinkMetricsEnhAckProbingIeReportCallbackContext = aCallbackContext; +} + +void LinkMetrics::ProcessEnhAckIeData(const uint8_t *aData, uint8_t aLen, const Neighbor &aNeighbor) +{ + otLinkMetricsValues linkMetricsValues; + uint8_t idx = 0; + + VerifyOrExit(mLinkMetricsEnhAckProbingIeReportCallback != nullptr); + + linkMetricsValues.mMetrics = aNeighbor.GetEnhAckProbingMetrics(); + + if (linkMetricsValues.mMetrics.mLqi && idx < aLen) + { + linkMetricsValues.mLqiValue = aData[idx++]; + } + if (linkMetricsValues.mMetrics.mLinkMargin && idx < aLen) + { + linkMetricsValues.mLinkMarginValue = aData[idx++]; + } + if (linkMetricsValues.mMetrics.mRssi && idx < aLen) + { + linkMetricsValues.mRssiValue = aData[idx++]; + } + + mLinkMetricsEnhAckProbingIeReportCallback(aNeighbor.GetRloc16(), &aNeighbor.GetExtAddress(), &linkMetricsValues, + mLinkMetricsEnhAckProbingIeReportCallbackContext); + +exit: + return; +} + otError LinkMetrics::SendLinkMetricsQuery(const Ip6::Address & aDestination, uint8_t aSeriesId, const LinkMetricsTypeIdFlags *aTypeIdFlags, @@ -585,14 +677,14 @@ exit: return error; } -otLinkMetricsStatus LinkMetrics::ConfigureForwardTrackingSeries(uint8_t aSeriesId, - const SeriesFlags & aSeriesFlags, - const otLinkMetrics &aLinkMetrics, - Neighbor & aNeighbor) +LinkMetrics::LinkMetricsStatus LinkMetrics::ConfigureForwardTrackingSeries(uint8_t aSeriesId, + const SeriesFlags & aSeriesFlags, + const otLinkMetrics &aLinkMetrics, + Neighbor & aNeighbor) { - otLinkMetricsStatus status = OT_LINK_METRICS_STATUS_SUCCESS; + LinkMetricsStatus status = kLinkMetricsStatusSuccess; - VerifyOrExit(0 < aSeriesId, status = OT_LINK_METRICS_STATUS_OTHER_ERROR); + VerifyOrExit(0 < aSeriesId, status = kLinkMetricsStatusOtherError); if (aSeriesFlags.GetRawValue() == 0) // Remove the series { if (aSeriesId == kSeriesIdAllSeries) // Remove all @@ -602,16 +694,16 @@ otLinkMetricsStatus LinkMetrics::ConfigureForwardTrackingSeries(uint8_t else { LinkMetricsSeriesInfo *seriesInfo = aNeighbor.RemoveForwardTrackingSeriesInfo(aSeriesId); - VerifyOrExit(seriesInfo != nullptr, status = OT_LINK_METRICS_STATUS_SERIESID_NOT_RECOGNIZED); + VerifyOrExit(seriesInfo != nullptr, status = kLinkMetricsStatusSeriesIdNotRecognized); mLinkMetricsSeriesInfoPool.Free(*seriesInfo); } } else // Add a new series { LinkMetricsSeriesInfo *seriesInfo = aNeighbor.GetForwardTrackingSeriesInfo(aSeriesId); - VerifyOrExit(seriesInfo == nullptr, status = OT_LINK_METRICS_STATUS_SERIESID_ALREADY_REGISTERED); + VerifyOrExit(seriesInfo == nullptr, status = kLinkMetricsStatusSeriesIdAlreadyRegistered); seriesInfo = mLinkMetricsSeriesInfoPool.Allocate(); - VerifyOrExit(seriesInfo != nullptr, status = OT_LINK_METRICS_STATUS_CANNOT_SUPPORT_NEW_SERIES); + VerifyOrExit(seriesInfo != nullptr, status = kLinkMetricsStatusCannotSupportNewSeries); seriesInfo->Init(aSeriesId, aSeriesFlags, aLinkMetrics); @@ -622,32 +714,53 @@ exit: return status; } -uint8_t LinkMetrics::TypeIdFlagsFromLinkMetricsFlags(LinkMetricsTypeIdFlags *aTypeIdFlags, - const otLinkMetrics & aLinkMetricsFlags) +LinkMetrics::LinkMetricsStatus LinkMetrics::ConfigureEnhAckProbing(LinkMetricsEnhAckFlags aEnhAckFlags, + const otLinkMetrics & aLinkMetrics, + Neighbor & aNeighbor) { - uint8_t count = 0; + LinkMetricsStatus status = kLinkMetricsStatusSuccess; + otError error = OT_ERROR_NONE; - if (aLinkMetricsFlags.mPduCount) + VerifyOrExit(!aLinkMetrics.mReserved, status = kLinkMetricsStatusCannotSupportNewSeries); + + if (aEnhAckFlags == kEnhAckRegister) { - aTypeIdFlags[count++].SetRawValue(kTypeIdFlagPdu); + VerifyOrExit(!aLinkMetrics.mPduCount, status = kLinkMetricsStatusOtherError); + VerifyOrExit(aLinkMetrics.mLqi || aLinkMetrics.mLinkMargin || aLinkMetrics.mRssi, + status = kLinkMetricsStatusOtherError); + VerifyOrExit(!(aLinkMetrics.mLqi && aLinkMetrics.mLinkMargin && aLinkMetrics.mRssi), + status = kLinkMetricsStatusOtherError); + + error = Get().ConfigureEnhAckProbing(aLinkMetrics, aNeighbor.GetRloc16(), aNeighbor.GetExtAddress()); + } + else if (aEnhAckFlags == kEnhAckClear) + { + VerifyOrExit(!aLinkMetrics.mLqi && !aLinkMetrics.mLinkMargin && !aLinkMetrics.mRssi, + status = kLinkMetricsStatusOtherError); + error = Get().ConfigureEnhAckProbing(aLinkMetrics, aNeighbor.GetRloc16(), aNeighbor.GetExtAddress()); + } + else + { + status = kLinkMetricsStatusOtherError; } - if (aLinkMetricsFlags.mLqi) - { - aTypeIdFlags[count++].SetRawValue(kTypeIdFlagLqi); - } + VerifyOrExit(error == OT_ERROR_NONE, status = kLinkMetricsStatusOtherError); - if (aLinkMetricsFlags.mLinkMargin) - { - aTypeIdFlags[count++].SetRawValue(kTypeIdFlagLinkMargin); - } +exit: + return status; +} - if (aLinkMetricsFlags.mRssi) - { - aTypeIdFlags[count++].SetRawValue(kTypeIdFlagRssi); - } +Neighbor *LinkMetrics::GetNeighborFromLinkLocalAddr(const Ip6::Address &aDestination) +{ + Neighbor * neighbor = nullptr; + Mac::Address macAddress; - return count; + VerifyOrExit(aDestination.IsLinkLocal()); + aDestination.GetIid().ConvertToMacAddress(macAddress); + neighbor = Get().FindNeighbor(macAddress); + +exit: + return neighbor; } otError LinkMetrics::ReadTypeIdFlagsFromMessage(const Message &aMessage, @@ -692,6 +805,10 @@ otError LinkMetrics::ReadTypeIdFlagsFromMessage(const Message &aMessage, { pos += sizeof(uint8_t); // Skip the additional second flags byte. } + else + { + aLinkMetrics.mReserved = true; + } break; } } @@ -750,7 +867,7 @@ exit: return error; } -otError LinkMetrics::AppendStatusSubTlvToMessage(Message &aMessage, uint8_t &aLength, otLinkMetricsStatus aStatus) +otError LinkMetrics::AppendStatusSubTlvToMessage(Message &aMessage, uint8_t &aLength, LinkMetricsStatus aStatus) { otError error = OT_ERROR_NONE; Tlv statusTlv; diff --git a/src/core/thread/link_metrics.hpp b/src/core/thread/link_metrics.hpp index ef0b66a82..e2d1c3731 100644 --- a/src/core/thread/link_metrics.hpp +++ b/src/core/thread/link_metrics.hpp @@ -167,6 +167,16 @@ class LinkMetrics : public InstanceLocator, private NonCopyable friend class Neighbor; public: + enum LinkMetricsStatus : uint8_t + { + kLinkMetricsStatusSuccess = OT_LINK_METRICS_STATUS_SUCCESS, + kLinkMetricsStatusCannotSupportNewSeries = OT_LINK_METRICS_STATUS_CANNOT_SUPPORT_NEW_SERIES, + kLinkMetricsStatusSeriesIdAlreadyRegistered = OT_LINK_METRICS_STATUS_SERIESID_ALREADY_REGISTERED, + kLinkMetricsStatusSeriesIdNotRecognized = OT_LINK_METRICS_STATUS_SERIESID_NOT_RECOGNIZED, + kLinkMetricsStatusNoMatchingFramesReceived = OT_LINK_METRICS_STATUS_NO_MATCHING_FRAMES_RECEIVED, + kLinkMetricsStatusOtherError = OT_LINK_METRICS_STATUS_OTHER_ERROR, + }; + /** * This constructor initializes an instance of the LinkMetrics class. * @@ -184,9 +194,10 @@ public: * @param[in] aSeriesId The Series ID to query, 0 for single probe. * @param[in] aLinkMetricsFlags A pointer to flags specifying what metrics to query. * - * @retval OT_ERROR_NONE Successfully sent a Link Metrics query message. - * @retval OT_ERROR_NO_BUFS Insufficient buffers to generate the MLE Data Request message. - * @retval OT_ERROR_INVALID_ARGS TypeIdFlags are not valid or exceed the count limit. + * @retval OT_ERROR_NONE Successfully sent a Link Metrics query message. + * @retval OT_ERROR_NO_BUFS Insufficient buffers to generate the MLE Data Request message. + * @retval OT_ERROR_INVALID_ARGS TypeIdFlags are not valid or exceed the count limit. + * @retval OT_ERROR_UNKNOWN_NEIGHBOR @p aDestination is not link-local or the neighbor is not found. * */ otError LinkMetricsQuery(const Ip6::Address & aDestination, @@ -202,9 +213,11 @@ public: * accounted. * @param[in] aLinkMetricsFlags A pointer to flags specifying what metrics to query. * - * @retval OT_ERROR_NONE Successfully sent a Link Metrics Management Request message. - * @retval OT_ERROR_NO_BUFS Insufficient buffers to generate the MLE Link Metrics Management Request message. - * @retval OT_ERROR_INVALID_ARGS @p aSeriesId is not within the valid range. + * @retval OT_ERROR_NONE Successfully sent a Link Metrics Management Request message. + * @retval OT_ERROR_NO_BUFS Insufficient buffers to generate the MLE Link Metrics Management Request + * message. + * @retval OT_ERROR_INVALID_ARGS @p aSeriesId is not within the valid range. + * @retval OT_ERROR_UNKNOWN_NEIGHBOR @p aDestination is not link-local or the neighbor is not found. * */ otError SendMgmtRequestForwardTrackingSeries(const Ip6::Address & aDestination, @@ -212,6 +225,26 @@ public: const otLinkMetricsSeriesFlags &aSeriesFlags, const otLinkMetrics * aLinkMetricsFlags); + /** + * This method sends an MLE Link Metrics Management Request to configure/clear a Enhanced-ACK Based Probing. + * + * @param[in] aDestination A reference to the IPv6 address of the destination. + * @param[in] aEnhAckFlags Enh-ACK Flags to indicate whether to register or clear the probing. `0` to clear + * and `1` to register. Other values are reserved. + * @param[in] aLinkMetricsFlags A pointer to flags specifying what metrics to query. Should be `NULL` when + * `aEnhAckFlags` is `0`. + * + * @retval OT_ERROR_NONE Successfully sent a Link Metrics Management Request message. + * @retval OT_ERROR_NO_BUFS Insufficient buffers to generate the MLE Link Metrics Management Request + * message. + * @retval OT_ERROR_INVALID_ARGS @p aEnhAckFlags is not a valid value or @p aLinkMetricsFlags isn't correct. + * @retval OT_ERROR_UNKNOWN_NEIGHBOR @p aDestination is not link-local or the neighbor is not found. + * + */ + otError SendMgmtRequestEnhAckProbing(const Ip6::Address & aDestination, + otLinkMetricsEnhAckFlags aEnhAckFlags, + const otLinkMetrics * aLinkMetricsFlags); + /** * This method sends an MLE Link Probe message. * @@ -219,9 +252,10 @@ public: * @param[in] aSeriesId The Series ID which the Probe message targets at. * @param[in] aLength The length of the data payload in Link Probe TLV, [0, 64]. * - * @retval OT_ERROR_NONE Successfully sent a Link Probe message. - * @retval OT_ERROR_NO_BUFS Insufficient buffers to generate the MLE Link Probe message. - * @retval OT_ERROR_INVALID_ARGS @p aSeriesId or @p aLength is not within the valid range. + * @retval OT_ERROR_NONE Successfully sent a Link Probe message. + * @retval OT_ERROR_NO_BUFS Insufficient buffers to generate the MLE Link Probe message. + * @retval OT_ERROR_INVALID_ARGS @p aSeriesId or @p aLength is not within the valid range. + * @retval OT_ERROR_UNKNOWN_NEIGHBOR @p aDestination is not link-local or the neighbor is not found. * */ otError SendLinkProbe(const Ip6::Address &aDestination, uint8_t aSeriesId, uint8_t aLength); @@ -251,9 +285,9 @@ public: * @retval OT_ERROR_PARSE Cannot parse sub-TLVs from @p aMessage successfully. * */ - otError HandleLinkMetricsManagementRequest(const Message & aMessage, - Neighbor & aNeighbor, - otLinkMetricsStatus &aStatus); + otError HandleLinkMetricsManagementRequest(const Message & aMessage, + Neighbor & aNeighbor, + LinkMetricsStatus &aStatus); /** * This method handles the received Link Metrics Management Response contained in @p aMessage. @@ -312,6 +346,11 @@ public: */ void SetLinkMetricsMgmtResponseCallback(otLinkMetricsMgmtResponseCallback aCallback, void *aCallbackContext); + void SetLinkMetricsEnhAckProbingCallback(otLinkMetricsEnhAckProbingIeReportCallback aCallback, + void * aCallbackContext); + + void ProcessEnhAckIeData(const uint8_t *aData, uint8_t aLen, const Neighbor &aNeighbor); + private: /** * TypeIdFlagPdu: 0x0_1_000_000 -> 0x40 ==> L bit set, type = 0 (count/summation), metric-enum = 0 (PDU rxed). @@ -324,11 +363,6 @@ private: { kMaxTypeIdFlags = 4, - kTypeIdFlagPdu = 0x40, - kTypeIdFlagLqi = 0x09, - kTypeIdFlagLinkMargin = 0x0a, - kTypeIdFlagRssi = 0x0b, - kMaxSeriesSupported = OPENTHREAD_CONFIG_MLE_LINK_METRICS_MAX_SERIES_SUPPORTED, ///< Max number of LinkMetricsSeriesInfo that could ///< be allocated by the pool. @@ -340,10 +374,12 @@ private: kLinkProbeMaxLen = 64, ///< Max length of data payload in Link Probe TLV. }; - otLinkMetricsReportCallback mLinkMetricsReportCallback; - void * mLinkMetricsReportCallbackContext; - otLinkMetricsMgmtResponseCallback mLinkMetricsMgmtResponseCallback; - void * mLinkMetricsMgmtResponseCallbackContext; + otLinkMetricsReportCallback mLinkMetricsReportCallback; + void * mLinkMetricsReportCallbackContext; + otLinkMetricsMgmtResponseCallback mLinkMetricsMgmtResponseCallback; + void * mLinkMetricsMgmtResponseCallbackContext; + otLinkMetricsEnhAckProbingIeReportCallback mLinkMetricsEnhAckProbingIeReportCallback; + void * mLinkMetricsEnhAckProbingIeReportCallbackContext; Pool mLinkMetricsSeriesInfoPool; @@ -352,13 +388,16 @@ private: const LinkMetricsTypeIdFlags *aTypeIdFlags, uint8_t aTypeIdFlagsCount); - otLinkMetricsStatus ConfigureForwardTrackingSeries(uint8_t aSeriesId, - const SeriesFlags & aSeriesFlags, - const otLinkMetrics &aLinkMetrics, - Neighbor & aNeighbor); + LinkMetricsStatus ConfigureForwardTrackingSeries(uint8_t aSeriesId, + const SeriesFlags & aSeriesFlags, + const otLinkMetrics &aLinkMetrics, + Neighbor & aNeighbor); - static uint8_t TypeIdFlagsFromLinkMetricsFlags(LinkMetricsTypeIdFlags *aTypeIdFlags, - const otLinkMetrics & aLinkMetricsFlags); + LinkMetricsStatus ConfigureEnhAckProbing(LinkMetricsEnhAckFlags aEnhAckFlags, + const otLinkMetrics & aLinkMetrics, + Neighbor & aNeighbor); + + Neighbor *GetNeighborFromLinkLocalAddr(const Ip6::Address &aDestination); static otError ReadTypeIdFlagsFromMessage(const Message &aMessage, uint8_t aStartPos, @@ -367,7 +406,7 @@ private: static otError AppendReportSubTlvToMessage(Message &aMessage, uint8_t &aLength, const otLinkMetricsValues &aValues); - static otError AppendStatusSubTlvToMessage(Message &aMessage, uint8_t &aLength, otLinkMetricsStatus aStatus); + static otError AppendStatusSubTlvToMessage(Message &aMessage, uint8_t &aLength, LinkMetricsStatus aStatus); }; /** diff --git a/src/core/thread/link_metrics_tlvs.hpp b/src/core/thread/link_metrics_tlvs.hpp index 822467833..9880f83f3 100644 --- a/src/core/thread/link_metrics_tlvs.hpp +++ b/src/core/thread/link_metrics_tlvs.hpp @@ -70,6 +70,18 @@ enum Type : uint8_t kEnhancedACKConfiguration = 7, ///< Enhanced ACK Configuration Sub-TLV }; +/** + * Valid values for Link Metrics Type Id Flags. + * + */ +enum +{ + kTypeIdFlagPdu = 0x40, + kTypeIdFlagLqi = 0x09, + kTypeIdFlagLinkMargin = 0x0a, + kTypeIdFlagRssi = 0x0b, +}; + /** * This class defines Link Metrics Query ID TLV constants and types. * @@ -83,6 +95,11 @@ typedef UintTlvInfo LinkMetricsQueryIdTlv; OT_TOOL_PACKED_BEGIN class LinkMetricsTypeIdFlags { public: + enum : uint8_t + { + kTypeEnumReserved = 2, + }; + /** * Default constructor. * @@ -355,6 +372,33 @@ public: */ SeriesFlags(const SeriesFlags &aSeriesFlags) { mSeriesFlags = aSeriesFlags.mSeriesFlags; } + /** + * This method sets the values of this object from a `otLinkMetricsSeriesFlags` object. + * + * @param[in] aSeriesFlags The `otLinkMetricsSeriesFlags` object. + * + */ + void SetFromOtSeriesFlags(const otLinkMetricsSeriesFlags aSeriesFlags) + { + Clear(); + if (aSeriesFlags.mLinkProbe) + { + SetLinkProbeFlag(); + } + if (aSeriesFlags.mMacData) + { + SetMacDataFlag(); + } + if (aSeriesFlags.mMacDataRequest) + { + SetMacDataRequestFlag(); + } + if (aSeriesFlags.mMacAck) + { + SetMacAckFlag(); + } + } + /** * This method clears the Link Probe flag. * @@ -469,6 +513,111 @@ private: uint8_t mSeriesFlags; } OT_TOOL_PACKED_END; +enum LinkMetricsEnhAckFlags : uint8_t +{ + kEnhAckClear = OT_LINK_METRICS_ENH_ACK_CLEAR, ///< Clear. + kEnhAckRegister = OT_LINK_METRICS_ENH_ACK_REGISTER, ///< Register. +}; + +static uint8_t TypeIdFlagsFromLinkMetricsFlags(LinkMetricsTypeIdFlags *aTypeIdFlags, + const otLinkMetrics & aLinkMetricsFlags) +{ + uint8_t count = 0; + + if (aLinkMetricsFlags.mPduCount) + { + aTypeIdFlags[count++].SetRawValue(kTypeIdFlagPdu); + } + + if (aLinkMetricsFlags.mLqi) + { + aTypeIdFlags[count++].SetRawValue(kTypeIdFlagLqi); + } + + if (aLinkMetricsFlags.mLinkMargin) + { + aTypeIdFlags[count++].SetRawValue(kTypeIdFlagLinkMargin); + } + + if (aLinkMetricsFlags.mRssi) + { + aTypeIdFlags[count++].SetRawValue(kTypeIdFlagRssi); + } + +#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE + if (aLinkMetricsFlags.mReserved) + { + for (uint8_t i = 0; i < count; i++) + { + aTypeIdFlags[i].SetTypeEnum(LinkMetricsTypeIdFlags::kTypeEnumReserved); + } + } +#endif + + return count; +} + +OT_TOOL_PACKED_BEGIN +class EnhAckLinkMetricsConfigurationSubTlv : public Tlv +{ +public: + /** + * Default constructor + * + */ + EnhAckLinkMetricsConfigurationSubTlv(void) { Init(); } + + /** + * This method initializes the TLV. + * + */ + void Init(void) + { + SetType(kEnhancedACKConfiguration); + SetLength(sizeof(LinkMetricsEnhAckFlags)); + } + + /** + * This method sets Enhanced ACK Flags. + * + * @param[in] aEnhAckFlags The value of Enhanced ACK Flags. + * + */ + void SetEnhAckFlags(otLinkMetricsEnhAckFlags aEnhAckFlags) + { + memcpy(mSubTlvs + kEnhAckFlagsOffset, &aEnhAckFlags, sizeof(aEnhAckFlags)); + } + + /** + * This method sets Type Id Flags. + * + * @param[in] aLinkMetricsFlags A pointer to a `otLinkMetrics` representing the Type Id Flags. + * + */ + void SetTypeIdFlags(const otLinkMetrics *aLinkMetricsFlags) + { + uint8_t typeIdFlagsCount; + + OT_ASSERT(aLinkMetricsFlags != nullptr); + typeIdFlagsCount = TypeIdFlagsFromLinkMetricsFlags( + reinterpret_cast(mSubTlvs + kTypeIdFlagsOffset), *aLinkMetricsFlags); + OT_ASSERT(typeIdFlagsCount <= kMaxTypeIdFlagsEnhAck); + + SetLength(sizeof(LinkMetricsEnhAckFlags) + sizeof(LinkMetricsTypeIdFlags) * typeIdFlagsCount); + } + +private: + enum + { + kMaxTypeIdFlagsEnhAck = 3, + + kEnhAckFlagsOffset = 0, + kTypeIdFlagsOffset = sizeof(LinkMetricsTypeIdFlags), + }; + + uint8_t mSubTlvs[sizeof(LinkMetricsEnhAckFlags) + sizeof(LinkMetricsTypeIdFlags) * kMaxTypeIdFlagsEnhAck]; +} OT_TOOL_PACKED_END; + } // namespace ot #endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE diff --git a/src/core/thread/mesh_forwarder.cpp b/src/core/thread/mesh_forwarder.cpp index 71a83ff82..a1339feef 100644 --- a/src/core/thread/mesh_forwarder.cpp +++ b/src/core/thread/mesh_forwarder.cpp @@ -173,7 +173,7 @@ void MeshForwarder::PrepareEmptyFrame(Mac::TxFrame &aFrame, const Mac::Address & fcf |= (aMacDest.IsShort()) ? Mac::Frame::kFcfDstAddrShort : Mac::Frame::kFcfDstAddrExt; fcf |= (macSource.IsShort()) ? Mac::Frame::kFcfSrcAddrShort : Mac::Frame::kFcfSrcAddrExt; - Get().UpdateFrameControlField(nullptr, false, fcf); + Get().UpdateFrameControlField(Get().FindNeighbor(aMacDest), false, fcf); aFrame.InitMacHeader(fcf, Mac::Frame::kKeyIdMode1 | Mac::Frame::kSecEncMic32); diff --git a/src/core/thread/mle.cpp b/src/core/thread/mle.cpp index 84bb9944d..feaa4d6a6 100644 --- a/src/core/thread/mle.cpp +++ b/src/core/thread/mle.cpp @@ -2454,7 +2454,7 @@ exit: } #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE -otError Mle::SendLinkMetricsManagementResponse(const Ip6::Address &aDestination, otLinkMetricsStatus aStatus) +otError Mle::SendLinkMetricsManagementResponse(const Ip6::Address &aDestination, LinkMetrics::LinkMetricsStatus aStatus) { otError error = OT_ERROR_NONE; Message *message; @@ -2498,7 +2498,7 @@ otError Mle::SendLinkProbe(const Ip6::Address &aDestination, uint8_t aSeriesId, exit: return error; } -#endif +#endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE otError Mle::SendMessage(Message &aMessage, const Ip6::Address &aDestination) { @@ -3823,8 +3823,8 @@ void Mle::HandleLinkMetricsManagementRequest(const Message & aMessage, const Ip6::MessageInfo &aMessageInfo, Neighbor * aNeighbor) { - otError error = OT_ERROR_NONE; - otLinkMetricsStatus status; + otError error = OT_ERROR_NONE; + LinkMetrics::LinkMetricsStatus status; Log(kMessageReceive, kTypeLinkMetricsManagementRequest, aMessageInfo.GetPeerAddr()); @@ -3867,7 +3867,7 @@ void Mle::HandleLinkProbe(const Message &aMessage, const Ip6::MessageInfo &aMess exit: LogProcessError(kTypeLinkProbe, error); } -#endif +#endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE void Mle::ProcessAnnounce(void) { diff --git a/src/core/thread/mle.hpp b/src/core/thread/mle.hpp index 2bec7b9d3..95b54741a 100644 --- a/src/core/thread/mle.hpp +++ b/src/core/thread/mle.hpp @@ -1746,7 +1746,7 @@ private: bool PrepareAnnounceState(void); void SendAnnounce(uint8_t aChannel, bool aOrphanAnnounce, const Ip6::Address &aDestination); #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE - otError SendLinkMetricsManagementResponse(const Ip6::Address &aDestination, otLinkMetricsStatus aStatus); + otError SendLinkMetricsManagementResponse(const Ip6::Address &aDestination, LinkMetrics::LinkMetricsStatus aStatus); #endif uint32_t Reattach(void); diff --git a/src/core/thread/topology.hpp b/src/core/thread/topology.hpp index 6394e13e0..dd066e599 100644 --- a/src/core/thread/topology.hpp +++ b/src/core/thread/topology.hpp @@ -558,6 +558,14 @@ public: */ bool IsThreadVersion1p1(void) const { return mState != kStateInvalid && mVersion == OT_THREAD_VERSION_1_1; } + /** + * This method indicates whether or not it is a valid Thread 1.2 neighbor. + * + * @returns TRUE if it is a valid Thread 1.2 neighbor, FALSE otherwise. + * + */ + bool IsThreadVersion1p2(void) const { return mState != kStateInvalid && mVersion == OT_THREAD_VERSION_1_2; } + /** * This method indicates whether Enhanced Keep-Alive is supported or not. * @@ -703,12 +711,43 @@ public: LinkMetricsSeriesInfo *RemoveForwardTrackingSeriesInfo(const uint8_t &aSeriesId); /** - * This method removes all the Series and return the data structures to the Pool + * This method removes all the Series and return the data structures to the Pool. * */ void RemoveAllForwardTrackingSeriesInfo(void); -#endif + /** + * This method gets the Enh-ACK Probing metrics (this `Neighbor` object is the Probing Subject). + * + * @returns Enh-ACK Probing metrics configured. + * + */ + const otLinkMetrics &GetEnhAckProbingMetrics(void) const { return mEnhAckProbingMetrics; } + + /** + * This method sets the Enh-ACK Probing metrics (this `Neighbor` object is the Probing Subject). + * + * @param[in] aEnhAckProbingMetrics The metrics value to set. + * + */ + void SetEnhAckProbingMetrics(const otLinkMetrics &aEnhAckProbingMetrics) + { + mEnhAckProbingMetrics = aEnhAckProbingMetrics; + } + + /** + * This method indicates if Enh-ACK Probing is configured and active for this `Neighbor` object. + * + * @retval TRUE Enh-ACK Probing is configured and active for this `Neighbor`. + * @retval FALSE Otherwise. + * + */ + bool IsEnhAckProbingActive(void) const + { + return (mEnhAckProbingMetrics.mLqi != 0) || (mEnhAckProbingMetrics.mLinkMargin != 0) || + (mEnhAckProbingMetrics.mRssi != 0); + } +#endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE /** * This method converts a given `State` to a human-readable string. @@ -766,7 +805,12 @@ private: LinkQualityInfo mLinkInfo; ///< Link quality info (contains average RSS, link margin and link quality) #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE LinkedList mLinkMetricsSeriesInfoList; ///< A list of Link Metrics Forward Tracking Series - ///< that is being tracked for this neighbor. + ///< that is being tracked for this neighbor. Note + ///< that this device is the Subject and this + ///< this neighbor is the Initiator. + otLinkMetrics mEnhAckProbingMetrics; ///< Metrics configured for Enh-ACK Based Probing at the Probing Subject + ///< (this neighbor). Note that this device is the Initiator and this neighbor + ///< is the Subject. #endif }; diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index ad7394f7b..abbd026ff 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -1936,6 +1936,19 @@ class NodeImpl: self.send_command(cmd) self._expect('Done') + def link_metrics_mgmt_req_enhanced_ack_based_probing(self, + dst_addr: str, + enable: bool, + metrics_flags: str, + ext_flags=''): + cmd = "linkmetrics mgmt %s enhanced-ack" % (dst_addr) + if enable: + cmd = cmd + (" register %s %s" % (metrics_flags, ext_flags)) + else: + cmd = cmd + " clear" + self.send_command(cmd) + self._expect('Done') + def link_metrics_mgmt_req_forward_tracking_series(self, dst_addr: str, series_id: int, series_flags: str, metrics_flags: str): cmd = "linkmetrics mgmt %s forward %d %s %s" % (dst_addr, series_id, series_flags, metrics_flags) diff --git a/tests/scripts/thread-cert/pktverify/consts.py b/tests/scripts/thread-cert/pktverify/consts.py index 04d447f5f..9d2d3f142 100644 --- a/tests/scripts/thread-cert/pktverify/consts.py +++ b/tests/scripts/thread-cert/pktverify/consts.py @@ -230,6 +230,10 @@ NWD_SERVICE_TLV = 4 NWD_SERVER_TLV = 5 NWD_COMMISSIONING_DATA_TLV = 6 +# Link Metrics TLVs +LM_FORWARD_PROBING_REGISTRATION_SUB_TLV = 3 +LM_ENHANCED_ACK_CONFIGURATION_SUB_TLV = 7 + # DUA related constants ADDRESS_QUERY_INITIAL_RETRY_DELAY = 15 @@ -312,6 +316,11 @@ TIMEOUT_REGISTER_MA = 5 MAC_FRAME_VERSION_2006 = 1 MAC_FRAME_VERSION_2015 = 2 +# 802.15.4 Frame Type +MAC_FRAME_TYPE_DATA = 0x1 +MAC_FRAME_TYPE_ACK = 0x2 +MAC_FRAME_TYPE_MAC_CMD = 0x3 + # CSL CSL_DEFAULT_PERIOD = 3125 # 0.5s, 3125 in units of ten symbols CSL_DEFAULT_PERIOD_IN_SECOND = 0.5 @@ -340,6 +349,12 @@ LINK_METRICS_METRIC_TYPE_ENUM_LQI = 1 LINK_METRICS_METRIC_TYPE_ENUM_LINK_MARGIN = 2 LINK_METRICS_METRIC_TYPE_ENUM_RSSI = 3 +LINK_METRICS_ENH_ACK_PROBING_CLEAR = 0 +LINK_METRICS_ENH_ACK_PROBING_REGISTER = 1 + +# THREAD_COMPANY_ID +THREAD_IEEE_802154_COMPANY_ID = 0xEAB89B + if __name__ == '__main__': from pktverify.addrs import Ipv6Addr diff --git a/tests/scripts/thread-cert/pktverify/layer_fields.py b/tests/scripts/thread-cert/pktverify/layer_fields.py index 2ab89a47f..2236c7375 100644 --- a/tests/scripts/thread-cert/pktverify/layer_fields.py +++ b/tests/scripts/thread-cert/pktverify/layer_fields.py @@ -275,6 +275,7 @@ _LAYER_FIELDS = { 'wpan.channel': _auto, 'wpan.header_ie.id': _list(_auto), 'wpan.header_ie.csl.period': _auto, + 'wpan.payload_ie.vendor.oui': _auto, # MLE 'mle.cmd': _auto, @@ -320,13 +321,16 @@ _LAYER_FIELDS = { 'mle.tlv.addr16': _auto, 'mle.tlv.channel': _auto, 'mle.tlv.addr_reg_iid': _list(_auto), + 'mle.tlv.link_enh_ack_flags': _auto, 'mle.tlv.link_forward_series': _list(_auto), + 'mle.tlv.link_requested_type_id_flags': _list(_hex), 'mle.tlv.link_sub_tlv': _auto, 'mle.tlv.link_status_sub_tlv': _auto, 'mle.tlv.query_id': _auto, 'mle.tlv.metric_type_id_flags.type': _list(_hex), 'mle.tlv.metric_type_id_flags.metric': _list(_hex), 'mle.tlv.metric_type_id_flags.l': _list(_hex), + 'mle.tlv.link_requested_type_id_flags': _bytes, # IP 'ip.version': _auto, diff --git a/tests/scripts/thread-cert/pktverify/packet_filter.py b/tests/scripts/thread-cert/pktverify/packet_filter.py index 4ec4d19ab..586f8f0b2 100644 --- a/tests/scripts/thread-cert/pktverify/packet_filter.py +++ b/tests/scripts/thread-cert/pktverify/packet_filter.py @@ -436,6 +436,34 @@ class PacketFilter(object): """ return self.filter(attrgetter('wpan'), **kwargs) + def filter_wpan_ack(self, **kwargs): + """ + Create a new PacketFilter for filter WPAN ACK packets. + + :param kwargs: Extra arguments for `filter`. + :return: The new PacketFilter to filter WPAN packets. + """ + return self.filter(lambda p: p.wpan.frame_type == consts.MAC_FRAME_TYPE_ACK, **kwargs) + + def filter_wpan_data(self, **kwargs): + """ + Create a new PacketFilter for filter WPAN data packets. + + :param kwargs: Extra arguments for `filter`. + :return: The new PacketFilter to filter WPAN packets. + """ + return self.filter(lambda p: p.wpan.frame_type == consts.MAC_FRAME_TYPE_DATA, **kwargs) + + def filter_wpan_seq(self, seq, **kwargs): + """ + Create a new PacketFilter for filter WPAN packets of a sequence number. + + :param seq: The sequence number to filter. + :param kwargs: Extra arguments for `filter`. + :return: The new PacketFilter to filter WPAN packets. + """ + return self.filter(lambda p: p.wpan.seq_no == seq, **kwargs) + def filter_wpan_version(self, version: int, **kwargs): """ Create a new PacketFilter for filter WPAN packets of a given version. @@ -476,6 +504,12 @@ class PacketFilter(object): def filter_dst16(self, rloc16: int, **kwargs): return self.filter(lambda p: p.lowpan.mesh.dest16 == rloc16 or p.wpan.dst16 == rloc16, **kwargs) + def filter_wpan_ie_present(self, **kwargs): + return self.filter(lambda p: p.wpan.ie_present == 1) + + def filter_wpan_ie_not_present(self, **kwargs): + return self.filter(lambda p: p.wpan.ie_present == 0) + def filter_ping_request(self, identifier=None, **kwargs): return self.filter( lambda p: p.icmpv6.is_ping_request and (identifier is None or p.icmpv6.echo.identifier == identifier), diff --git a/tests/scripts/thread-cert/v1_2_LowPower_7_1_01_SingleProbeLinkMetricsWithEnhancedAcks.py b/tests/scripts/thread-cert/v1_2_LowPower_7_1_01_SingleProbeLinkMetricsWithEnhancedAcks.py new file mode 100755 index 000000000..6166b2463 --- /dev/null +++ b/tests/scripts/thread-cert/v1_2_LowPower_7_1_01_SingleProbeLinkMetricsWithEnhancedAcks.py @@ -0,0 +1,390 @@ +#!/usr/bin/env python3 +# +# 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. +# + +import unittest + +from config import ADDRESS_TYPE +from pktverify import consts + +import thread_cert + +LEADER = 1 +SED_1 = 2 +SSED_1 = 3 + +POLL_PERIOD = 3000 # 3s + + +class LowPower_7_1_01(thread_cert.TestCase): + TOPOLOGY = { + LEADER: { + 'version': '1.2', + 'name': 'LEADER', + 'mode': 'rdn', + 'panid': 0xface, + 'allowlist': [SED_1, SSED_1], + }, + SED_1: { + 'version': '1.2', + 'name': 'SED_1', + 'mode': '-', + 'panid': 0xface, + 'allowlist': [LEADER], + }, + SSED_1: { + 'version': '1.2', + 'name': 'SSED_1', + 'mode': '-', + 'panid': 0xface, + 'allowlist': [LEADER], + } + } + """All nodes are created with default configurations""" + + def test(self): + self.nodes[LEADER].start() + self.simulator.go(5) + self.assertEqual(self.nodes[LEADER].get_state(), 'leader') + + self.nodes[SED_1].set_pollperiod(POLL_PERIOD) + self.nodes[SED_1].start() + self.simulator.go(5) + self.assertEqual(self.nodes[SED_1].get_state(), 'child') + + self.nodes[SSED_1].set_csl_period(consts.CSL_DEFAULT_PERIOD) + self.nodes[SSED_1].start() + self.simulator.go(5) + self.assertEqual(self.nodes[SSED_1].get_state(), 'child') + + leader_addr = self.nodes[LEADER].get_ip6_address(ADDRESS_TYPE.LINK_LOCAL) + + # Step 3 - Verify connectivity by instructing each device to sending an ICMPv6 Echo Request to the DUT + self.assertTrue(self.nodes[SED_1].ping(leader_addr, timeout=POLL_PERIOD)) + self.assertTrue(self.nodes[SSED_1].ping(leader_addr)) + self.simulator.go(5) + + # Step 4 - SED_1 enables IEEE 802.15.4-2015 Enhanced ACK based Probing by sending a Link Metrics Management + # Request to the DUT + # MLE Link Metrics Management TLV Payload: + # - Enhanced ACK Configuration Sub-TLV + # -- Enh-ACK Flags = 1 (register a configuration) + # -- Concatenation of Link Metric Type ID Flags = 0x00: + # --- Item1: (0)(0)(001)(010) = 0x0a + # ---- E = 0 + # ---- L = 0 + # ---- Type/Average Enum = 1 (Exponential Moving Avg) + # ---- Metrics Enum = 2 (Link Margin) + self.nodes[SED_1].link_metrics_mgmt_req_enhanced_ack_based_probing(leader_addr, True, 'm') + self.simulator.go(5) + + # Step 6 - SSED_1 enables IEEE 802.15.4-2015 Enhanced ACK based Probing by sending a Link Metrics Management + # Request to the DUT + # MLE Link Metrics Management TLV Payload: + # - Enhanced ACK Configuration Sub-TLV + # -- Enh-ACK Flags = 1 (register a configuration) + # -- Concatenation of Link Metric Type ID Flags = 0x00: + # --- Item1: (0)(0)(001)(010) = 0x0a + # --- Item2: (0)(0)(001)(011) = 0x0b + # ---- E = 0 + # ---- L = 0 + # ---- Type/Average Enum = 1 (Exponential Moving Avg) + # ---- Metrics Enum = 2 (Link Margin) + # ---- Metrics Enum = 3 (RSSI) + self.nodes[SSED_1].link_metrics_mgmt_req_enhanced_ack_based_probing(leader_addr, True, 'mr') + self.simulator.go(5) + + # Step 8 - SSED_1 sends an MLE Data Message with setting Frame Version subfield within the MAC header of the + # message to 0b10 + self.nodes[SSED_1].send_mac_emptydata() + self.simulator.go(5) + + # Step 10 - SED_1 sends an MLE Data Message with setting Frame Version subfield within the MAC header of the + # message # to 0b10 + self.nodes[SED_1].send_mac_emptydata() + self.simulator.go(5) + + # Step 12 - SSED_1 clears its Enhanced ACK link metrics configuration by # sending a Link Metrics Management + # Request to the DUT + # Enh-ACK Flags = 0 (clear enhanced ACK link metric config) + self.nodes[SSED_1].link_metrics_mgmt_req_enhanced_ack_based_probing(leader_addr, False, '') + self.simulator.go(5) + + # Step 14 - SSED_1 Sends a MLE Data Message with setting Frame Version subfield within the MAC header of the + # message to 0b10 + self.nodes[SSED_1].send_mac_emptydata() + self.simulator.go(5) + + # Step 16 - This step verifies that Enhanced ACKs cannot be enabled while requesting 3 metric types by + # instructing the device to send the following Link Metrics Management Request to the DUT + # MLE Link Metrics Management TLV Payload: + # - Enhanced ACK Configuration Sub-TLV + # -- Enh-ACK Flags = 1 (register a configuration) + # -- Concatenation of Link Metric Type ID Flags = 0x00: + # --- Item1: (0)(0)(001)(001) = 0x09 + # --- Item2: (0)(0)(001)(010) = 0x0a + # --- Item3: (0)(0)(001)(011) = 0x0b + # ---- E = 0 + # ---- L = 0 + # ---- Type/Average Enum = 1 (Exponential Moving Avg) + # ---- Metrics Enum = 1 (Layer 2 LQI) + # ---- Metrics Enum = 2 (Link Margin) + # ---- Metrics Enum = 3 (RSSI) + self.nodes[SSED_1].link_metrics_mgmt_req_enhanced_ack_based_probing(leader_addr, True, 'qmr') + self.simulator.go(5) + + # Step 18 - This step verifies that Enhanced ACKs cannot be enabled while requesting a reserved Type/Average + # enum of value 2 by instructing the device to send the following Link Metrics Management Request to the DUT + # MLE Link Metrics Management TLV Payload: + # - Enhanced ACK Configuration Sub-TLV + # -- Enh-ACK Flags = 1 (register a configuration) + # -- Concatenation of Link Metric Type ID Flags = 0x00: + # --- Item1: (0)(0)(010)(010) = 0x12 + # ---- E = 0 + # ---- L = 0 + # ---- Type/Average Enum = 2 (Reserved) + # ---- Metrics Enum = 2 (Link Margin) + self.nodes[SSED_1].link_metrics_mgmt_req_enhanced_ack_based_probing(leader_addr, True, 'm', 'r') + self.simulator.go(5) + + def verify(self, pv): + pkts = pv.pkts + pv.summary.show() + LEADER = pv.vars['LEADER'] + SED_1 = pv.vars['SED_1'] + SSED_1 = pv.vars['SSED_1'] + + # Step 3 - The DUT MUST send ICMPv6 Echo Responses to both SED1 & SSED1 + pkts.filter_wpan_src64(LEADER) \ + .filter_wpan_dst64(SED_1) \ + .filter_ping_reply() \ + .must_next() + pkts.filter_wpan_src64(LEADER) \ + .filter_wpan_dst64(SSED_1) \ + .filter_ping_reply() \ + .must_next() + + # Step 4 - SED_1 enables IEEE 802.15.4-2015 Enhanced ACK based Probing by sending a Link Metrics Management + # Request to the DUT + # MLE Link Metrics Management TLV Payload: + # - Enhanced ACK Configuration Sub-TLV + # -- Enh-ACK Flags = 1 (register a configuration) + # -- Concatenation of Link Metric Type ID Flags = 0x00: + # --- Item1: (0)(0)(001)(010) = 0x0a + # ---- E = 0 + # ---- L = 0 + # ---- Type/Average Enum = 1 (Exponential Moving Avg) + # ---- Metrics Enum = 2 (Link Margin) + pkts.filter_wpan_src64(SED_1) \ + .filter_wpan_dst64(LEADER) \ + .filter_mle_cmd(consts.MLE_LINK_METRICS_MANAGEMENT_REQUEST) \ + .filter(lambda p: p.mle.tlv.link_sub_tlv == consts.LM_ENHANCED_ACK_CONFIGURATION_SUB_TLV) \ + .filter(lambda p: p.mle.tlv.link_enh_ack_flags == consts.LINK_METRICS_ENH_ACK_PROBING_REGISTER) \ + .filter(lambda p: p.mle.tlv.link_requested_type_id_flags == '0a') \ + .must_next() + + # Step 5 - The DUT MUST send a Link Metrics Management Response to SED_1 containing the following TLVs: + # - MLE LInk Metrics Management TLV + # -- Link Metrics Status Sub-TLV = 0 (Success) + pkts.filter_wpan_src64(LEADER) \ + .filter_wpan_dst64(SED_1) \ + .filter_mle_cmd(consts.MLE_LINK_METRICS_MANAGEMENT_RESPONSE) \ + .filter(lambda p: p.mle.tlv.link_status_sub_tlv == consts.LINK_METRICS_STATUS_SUCCESS) \ + .must_next() + + # Step 6 - SSED_1 enables IEEE 802.15.4-2015 Enhanced ACK based Probing by sending a Link Metrics Management + # Request to the DUT + # MLE Link Metrics Management TLV Payload: + # - Enhanced ACK Configuration Sub-TLV + # -- Enh-ACK Flags = 1 (register a configuration) + # -- Concatenation of Link Metric Type ID Flags = 0x00: + # --- Item1: (0)(0)(001)(010) = 0x0a + # --- Item2: (0)(0)(001)(011) = 0x0b + # ---- E = 0 + # ---- L = 0 + # ---- Type/Average Enum = 1 (Exponential Moving Avg) + # ---- Metrics Enum = 2 (Link Margin) + # ---- Metrics Enum = 3 (RSSI) + pkts.filter_wpan_src64(SSED_1) \ + .filter_mle_cmd(consts.MLE_LINK_METRICS_MANAGEMENT_REQUEST) \ + .filter(lambda p: p.mle.tlv.link_sub_tlv == consts.LM_ENHANCED_ACK_CONFIGURATION_SUB_TLV) \ + .filter(lambda p: p.mle.tlv.link_enh_ack_flags == consts.LINK_METRICS_ENH_ACK_PROBING_REGISTER) \ + .filter(lambda p: p.mle.tlv.link_requested_type_id_flags == '0a0b') \ + .must_next() + + # Step 7 - The DUT MUST send a Link Metrics Management Response to SSED_1 + pkts.filter_wpan_src64(LEADER) \ + .filter_mle_cmd(consts.MLE_LINK_METRICS_MANAGEMENT_RESPONSE) \ + .filter(lambda p: p.mle.tlv.link_status_sub_tlv == consts.LINK_METRICS_STATUS_SUCCESS) \ + .must_next() + + # Step 8 - SSED_1 sends an MLE Data Message with setting Frame Version subfield within the MAC header of the + # message to 0b10 + pkt = pkts.filter_wpan_src64(SSED_1) \ + .filter_wpan_data() \ + .filter_wpan_version(consts.MAC_FRAME_VERSION_2015) \ + .must_next() + ack_seq_no = pkt.wpan.seq_no + + # Step 9 - The DUT MUST reply to SSED_1 with an Enhanced ACK containing the following: + # - Frame Control Field + # -- Security Enabled = True + # - Header IE + # -- Element ID = 0x00 + # --- Vendor CID = 0xEAB89B (Thread Group) + # --- Vendor Specific Information + # ---- 1st byte = 0 (Enhanced ACK Link Metrics) + # ---- 2nd byte ... Link Margin data + # ---- 3rd byte ... RSSI data + pkts.filter_wpan_ack() \ + .filter_wpan_seq(ack_seq_no) \ + .filter(lambda p: p.wpan.payload_ie.vendor.oui == consts.THREAD_IEEE_802154_COMPANY_ID) \ + .must_next() + + # Step 10 - SED_1 sends an MLE Data Message with setting Frame Version subfield within the MAC header of the + # message to 0b10 + pkt = pkts.filter_wpan_src64(SED_1) \ + .filter_wpan_data() \ + .filter_wpan_version(consts.MAC_FRAME_VERSION_2015) \ + .must_next() + ack_seq_no = pkt.wpan.seq_no + + # Step 11 - The DUT MUST reply to SED_1 with an Enhanced ACK containing the following: + # - Frame Control Field + # -- Security Enabled = True + # - Header IE + # -- Element ID = 0x00 + # --- Vendor CID = 0xEAB89B (Thread Group) + # --- Vendor Specific Information + # ---- 1st byte = 0 (Enhanced ACK Link Metrics) + # ---- 2nd byte ... Link Margin data + pkts.filter_wpan_ack() \ + .filter_wpan_seq(ack_seq_no) \ + .filter(lambda p: p.wpan.payload_ie.vendor.oui == consts.THREAD_IEEE_802154_COMPANY_ID) \ + .must_next() + + # Step 12 - SSED_1 clears enhanced ACK link metrics configuration by instructing it to send a Link Metrics + # Management Request to the DUT + # - MLE Link Metrics Management TLV Payload + # -- Enhanced ACK Configuration Sub-TLV + # -- Enh-ACK Flags = 0 (clear enhanced ACK link metric config) + pkts.filter_wpan_src64(SSED_1) \ + .filter_wpan_dst64(LEADER) \ + .filter_mle_cmd(consts.MLE_LINK_METRICS_MANAGEMENT_REQUEST) \ + .filter(lambda p: p.mle.tlv.link_sub_tlv == consts.LM_ENHANCED_ACK_CONFIGURATION_SUB_TLV) \ + .filter(lambda p: p.mle.tlv.link_enh_ack_flags == consts.LINK_METRICS_ENH_ACK_PROBING_CLEAR) \ + .must_next() + + # Step 13 - The DUT MUST send Link Metrics Management Response to SSED_1 containing the following: + # - MLE Link Metrics Management TLV + # -- Link Metrics Status Sub-TLV = 0 (Success) + pkts.filter_wpan_src64(LEADER) \ + .filter_wpan_dst64(SSED_1) \ + .filter_mle_cmd(consts.MLE_LINK_METRICS_MANAGEMENT_RESPONSE) \ + .filter(lambda p: p.mle.tlv.link_status_sub_tlv == consts.LINK_METRICS_STATUS_SUCCESS) \ + .must_next() + + # Step 14 - SSED_1 sends an MLE Data Message with setting Frame Version subfield within the MAC header of the + # message to 0b10 + pkt = pkts.filter_wpan_src64(SSED_1) \ + .filter_wpan_data() \ + .filter_wpan_version(consts.MAC_FRAME_VERSION_2015) \ + .must_next() + ack_seq_no = pkt.wpan.seq_no + + # Step 15 - The DUT MUST NOT include a Link Metrics Report in the ACK + pkts.filter_wpan_ack() \ + .filter_wpan_seq(ack_seq_no) \ + .filter_wpan_ie_not_present() \ + .must_next() + + # Step 16 - This step verifies that Enhanced ACKS cannot be enabled while requesting 3 metric types by + # instructing the device to send the following Link Metrics Management Request to the DUT: + # MLE Link Metrics Management TLV Payload: + # - Enhanced ACK Configuration Sub-TLV + # -- Enh-ACK Flags = 1 (register a configuration) + # -- Concatenation of Link Metric Type ID Flags = 0x00: + # --- Item1: (0)(0)(001)(001) = 0x09 + # --- Item2: (0)(0)(001)(010) = 0x0a + # --- Item3: (0)(0)(001)(011) = 0x0b + # ---- E = 0 + # ---- L = 0 + # ---- Type/Average Enum = 1 (Exponential Moving Avg) + # ---- Metrics Enum = 1 (Layer 2 LQI) + # ---- Metrics Enum = 2 (Link Margin) + # ---- Metrics Enum = 3 (RSSI) + pkts.filter_wpan_src64(SSED_1) \ + .filter_wpan_dst64(LEADER) \ + .filter_mle_cmd(consts.MLE_LINK_METRICS_MANAGEMENT_REQUEST) \ + .filter(lambda p: p.mle.tlv.link_sub_tlv == consts.LM_ENHANCED_ACK_CONFIGURATION_SUB_TLV) \ + .filter(lambda p: p.mle.tlv.link_enh_ack_flags == consts.LINK_METRICS_ENH_ACK_PROBING_REGISTER) \ + .filter(lambda p: p.mle.tlv.link_requested_type_id_flags == '090a0b') \ + .must_next() + + # Step 17 - Leader automatically responds to the invalid query from SSED_1 with a failure + # The DUT MUST send Link Metrics Management Response to SSED_1containing the following: + # - MLE Link Metrics Management TLV + # -- Link Metrics Status Sub-TLV = 254 (Failure) + pkts.filter_wpan_src64(LEADER) \ + .filter_wpan_dst64(SSED_1) \ + .filter_mle_cmd(consts.MLE_LINK_METRICS_MANAGEMENT_RESPONSE) \ + .filter(lambda p: p.mle.tlv.link_status_sub_tlv == consts.LINK_METRICS_STATUS_OTHER_ERROR) \ + .must_next() + + # Step 18 - This step verifies that Enhanced ACKs cannot be enabled while requesting a reserved Type/Average + # enum of value 2 by instructing the device to send the following Link Metrics Management Request to the DUT + # MLE Link Metrics Management TLV Payload: + # - Enhanced ACK Configuration Sub-TLV + # -- Enh-ACK Flags = 1 (register a configuration) + # -- Concatenation of Link Metric Type ID Flags = 0x00: + # --- Item1: (0)(0)(010)(010) = 0x12 + # ---- E = 0 + # ---- L = 0 + # ---- Type/Average Enum = 2 (Reserved) + # ---- Metrics Enum = 2 (Link Margin) + pkts.filter_wpan_src64(SSED_1) \ + .filter_wpan_dst64(LEADER) \ + .filter_mle_cmd(consts.MLE_LINK_METRICS_MANAGEMENT_REQUEST) \ + .filter(lambda p: p.mle.tlv.link_sub_tlv == consts.LM_ENHANCED_ACK_CONFIGURATION_SUB_TLV) \ + .filter(lambda p: p.mle.tlv.link_enh_ack_flags == consts.LINK_METRICS_ENH_ACK_PROBING_REGISTER) \ + .filter(lambda p: p.mle.tlv.link_requested_type_id_flags == '12') \ + .must_next() + + # Step 19 - Leader automatically responds to the invalid query from SSED_1 with a failure + # The DUT MUST send Link Metrics Management Response to SSED_1containing the following: + # - MLE Link Metrics Management TLV + # -- Link Metrics Status Sub-TLV = 1 (Failure) + pkts.filter_wpan_src64(LEADER) \ + .filter_wpan_dst64(SSED_1) \ + .filter_mle_cmd(consts.MLE_LINK_METRICS_MANAGEMENT_RESPONSE) \ + .filter(lambda p: p.mle.tlv.link_status_sub_tlv == consts.LINK_METRICS_ENH_ACK_PROBING_REGISTER) \ + .must_next() + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/unit/test_platform.cpp b/tests/unit/test_platform.cpp index 8e8a1c48e..f4392b5e6 100644 --- a/tests/unit/test_platform.cpp +++ b/tests/unit/test_platform.cpp @@ -653,4 +653,25 @@ otError otPlatTrelUdp6SetTestMode(otInstance *aInstance, bool aEnable) #endif // OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE +otError otPlatRadioConfigureEnhAckProbing(otInstance * aInstance, + otLinkMetrics aLinkMetrics, + const otShortAddress aShortAddress, + const otExtAddress * aExtAddress) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aLinkMetrics); + OT_UNUSED_VARIABLE(aShortAddress); + OT_UNUSED_VARIABLE(aExtAddress); + + return OT_ERROR_NONE; +} + +otLinkMetrics otPlatRadioGetEnhAckProbingMetrics(otInstance *aInstance, const otShortAddress aShortAddress) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aShortAddress); +} +#endif + } // extern "C"