From dc94758e847ca372eaf2ef76094c6b873e6a77fe Mon Sep 17 00:00:00 2001 From: Li Cao Date: Sat, 26 Sep 2020 00:50:09 +0800 Subject: [PATCH] [low-power] implement link metrics - single probe (#5481) This commit is the first part in link metrics. It implements single probe function. An overview of major changes: - Add link metrics apis which allow apps to send a single probe and set report callback. - Add cli commands for calling link metrics api and related README doc. - Add a new module link_metrics to implement the query process and handle the report. - Add related tlv types. - Add simple script test to run single probe process. --- Android.mk | 2 + etc/cmake/options.cmake | 5 + etc/gn/openthread.gni | 3 + examples/common-switches.mk | 4 + include/Makefile.am | 1 + include/openthread/link_metrics.h | 127 ++++++ script/test | 1 + src/cli/README.md | 22 + src/cli/cli.cpp | 106 +++++ src/cli/cli.hpp | 15 + src/core/BUILD.gn | 8 + src/core/CMakeLists.txt | 2 + src/core/Makefile.am | 4 + src/core/api/link_metrics_api.cpp | 60 +++ src/core/common/instance.hpp | 11 + src/core/common/message.cpp | 3 + src/core/common/message.hpp | 32 ++ src/core/config/mle.h | 10 + .../config/openthread-core-config-check.h | 6 + src/core/thread/link_metrics.cpp | 385 ++++++++++++++++++ src/core/thread/link_metrics.hpp | 163 ++++++++ src/core/thread/link_metrics_tlvs.hpp | 337 +++++++++++++++ src/core/thread/link_quality.cpp | 19 + src/core/thread/link_quality.hpp | 49 +++ src/core/thread/mesh_forwarder.cpp | 3 + src/core/thread/mle.cpp | 24 +- src/core/thread/mle.hpp | 18 +- src/core/thread/mle_router.cpp | 14 +- src/core/thread/mle_router.hpp | 6 +- src/core/thread/mle_tlvs.hpp | 3 + src/core/thread/thread_netif.cpp | 3 + src/core/thread/thread_netif.hpp | 4 + tests/scripts/thread-cert/config.py | 2 + tests/scripts/thread-cert/mle.py | 28 ++ tests/scripts/thread-cert/node.py | 5 + .../thread-cert/v1_2_test_single_probe.py | 115 ++++++ 36 files changed, 1591 insertions(+), 9 deletions(-) create mode 100644 include/openthread/link_metrics.h create mode 100644 src/core/api/link_metrics_api.cpp create mode 100644 src/core/thread/link_metrics.cpp create mode 100644 src/core/thread/link_metrics.hpp create mode 100644 src/core/thread/link_metrics_tlvs.hpp create mode 100755 tests/scripts/thread-cert/v1_2_test_single_probe.py diff --git a/Android.mk b/Android.mk index 1dcec863f..50b829907 100644 --- a/Android.mk +++ b/Android.mk @@ -164,6 +164,7 @@ LOCAL_SRC_FILES := \ src/core/api/jam_detection_api.cpp \ src/core/api/joiner_api.cpp \ src/core/api/link_api.cpp \ + src/core/api/link_metrics_api.cpp \ src/core/api/link_raw_api.cpp \ src/core/api/logging_api.cpp \ src/core/api/message_api.cpp \ @@ -255,6 +256,7 @@ LOCAL_SRC_FILES := \ src/core/thread/energy_scan_server.cpp \ src/core/thread/indirect_sender.cpp \ src/core/thread/key_manager.cpp \ + src/core/thread/link_metrics.cpp \ src/core/thread/link_quality.cpp \ src/core/thread/lowpan.cpp \ src/core/thread/mesh_forwarder.cpp \ diff --git a/etc/cmake/options.cmake b/etc/cmake/options.cmake index c97ae4256..b03bdc68f 100644 --- a/etc/cmake/options.cmake +++ b/etc/cmake/options.cmake @@ -177,6 +177,11 @@ if(OT_LINK_RAW) target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_LINK_RAW_ENABLE=1") endif() +option(OT_LINK_METRICS "enable link metrics") +if (OT_LINK_METRICS) + target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE=1") +endif() + option(OT_LOG_LEVEL_DYNAMIC "enable dynamic log level control") if(OT_LOG_LEVEL_DYNAMIC) target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_LOG_LEVEL_DYNAMIC_ENABLE=1") diff --git a/etc/gn/openthread.gni b/etc/gn/openthread.gni index 084aa61cb..68ede5dc8 100644 --- a/etc/gn/openthread.gni +++ b/etc/gn/openthread.gni @@ -150,6 +150,9 @@ if (openthread_enable_core_config_args) { # Enable legacy network support openthread_config_legacy_enable = false + # Enable link metrics + openthread_config_link_metrics_enable = false + # Enable link raw service openthread_config_link_raw_enable = false diff --git a/examples/common-switches.mk b/examples/common-switches.mk index e53557b1b..0649a61e0 100644 --- a/examples/common-switches.mk +++ b/examples/common-switches.mk @@ -202,6 +202,10 @@ ifeq ($(LINK_RAW),1) COMMONCFLAGS += -DOPENTHREAD_CONFIG_LINK_RAW_ENABLE=1 endif +ifeq ($(LINK_METRICS),1) +COMMONCFLAGS += -DOPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE=1 +endif + ifneq ($(LOG_OUTPUT),) COMMONCFLAGS += -DOPENTHREAD_CONFIG_LOG_OUTPUT=OPENTHREAD_CONFIG_LOG_OUTPUT_$(LOG_OUTPUT) endif diff --git a/include/Makefile.am b/include/Makefile.am index ebba9a7f1..3347fee22 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -60,6 +60,7 @@ openthread_headers = \ openthread/jam_detection.h \ openthread/joiner.h \ openthread/link.h \ + openthread/link_metrics.h \ openthread/link_raw.h \ openthread/logging.h \ openthread/message.h \ diff --git a/include/openthread/link_metrics.h b/include/openthread/link_metrics.h new file mode 100644 index 000000000..f750847f6 --- /dev/null +++ b/include/openthread/link_metrics.h @@ -0,0 +1,127 @@ +/* + * 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 OpenThread Link Metrics API. + */ + +#ifndef LINK_METRICS_H_ +#define LINK_METRICS_H_ + +#include "openthread-core-config.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @addtogroup linkmetrics Link Metrics + * + * @brief + * This module includes functions that control Link Metrics protocol. + * + * @{ + * + */ + +/* + * 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. + * + */ +typedef struct otLinkMetricsValues +{ + otLinkMetrics mMetrics; ///< Specifies which metrics values are present/included. + + uint32_t mPduCountValue; ///< The value of Pdu Count. + uint8_t mLqiValue; ///< The value LQI. + uint8_t mLinkMarginValue; ///< The value of Link Margin. + int8_t mRssiValue; ///< The value of Rssi. +} otLinkMetricsValues; + +/** + * This function pointer is called when a Link Metrics report is received. + * + * @param[in] aSource A pointer to the source address. + * @param[in] aMetricsValues A pointer to the Link Metrics values (the query result). + * @param[in] aContext A pointer to application-specific context. + * + */ +typedef void (*otLinkMetricsReportCallback)(const otIp6Address * aSource, + const otLinkMetricsValues *aMetricsValues, + void * aContext); + +/** + * This function sends an MLE Data Request to query Link Metrics. + * + * It could be either Single Probe or Forward Tracking Series. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aDestination A pointer to the destination address. + * @param[in] aSeriesId The Series ID to query about, 0 for Single Probe. + * @param[in] aLinkMetricsFlags A pointer to flags specifying what metrics to query. + * @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. + * + */ +otError otLinkMetricsQuery(otInstance * aInstance, + const otIp6Address * aDestination, + uint8_t aSeriesId, + const otLinkMetrics * aLinkMetricsFlags, + otLinkMetricsReportCallback aCallback, + void * aCallbackContext); + +/** + * @} + * + */ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // LINK_METRICS_H_ diff --git a/script/test b/script/test index 3f4979a79..90d2bcd47 100755 --- a/script/test +++ b/script/test @@ -67,6 +67,7 @@ build_simulation() if [[ ${version} == "1.2" ]]; then options+=("-DOT_CSL_RECEIVER=ON") + options+=("-DOT_LINK_METRICS=ON") fi if [[ ${ot_extra_options[*]+x} ]]; then diff --git a/src/cli/README.md b/src/cli/README.md index 3fe78a8fa..a4919081e 100644 --- a/src/cli/README.md +++ b/src/cli/README.md @@ -57,6 +57,7 @@ Done - [leaderdata](#leaderdata) - [leaderpartitionid](#leaderpartitionid) - [leaderweight](#leaderweight) +- [linkmetrics](#linkmetrics-query-ipaddr-single-pqmr) - [linkquality](#linkquality-extaddr) - [log](#log-filename-filename) - [mac](#mac-retries-direct) @@ -1066,6 +1067,27 @@ Set the Thread Leader Weight. Done ``` +### linkmetrics query \ single [pqmr] + +Perform a Link Metrics query (Single Probe). + +- ipaddr: Peer address. +- pqmr: This specifies what metrics to query. +- p: Layer 2 Number of PDUs received. +- q: Layer 2 LQI. +- m: Link Margin. +- r: RSSI. + +```bash +> linkmetrics query fe80:0:0:0:3092:f334:1455:1ad2 single qmr +Done +> Received Link Metrics Report from: fe80:0:0:0:3092:f334:1455:1ad2 + + - LQI: 76 (Exponential Moving Average) + - Margin: 82 (dB) (Exponential Moving Average) + - RSSI: -18 (dBm) (Exponential Moving Average) +``` + ### linkquality \ Get the link quality on the link to a given extended address. diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 2990721cf..1fca0abb8 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -75,6 +75,9 @@ #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE #include #endif +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE +#include +#endif #endif #include "cli_dataset.hpp" @@ -1899,6 +1902,109 @@ exit: } #endif // OPENTHREAD_FTD +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE +void Interpreter::HandleLinkMetricsReport(const otIp6Address * aAddress, + const otLinkMetricsValues *aMetricsValues, + void * aContext) +{ + static_cast(aContext)->HandleLinkMetricsReport(aAddress, aMetricsValues); +} + +void Interpreter::HandleLinkMetricsReport(const otIp6Address *aAddress, const otLinkMetricsValues *aMetricsValues) +{ + const char kLinkMetricsTypeCount[] = "(Count/Summation)"; + const char kLinkMetricsTypeAverage[] = "(Exponential Moving Average)"; + + OutputFormat("Received Link Metrics Report from: "); + OutputIp6Address(*aAddress); + OutputLine(""); + + 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); + } +} + +otError Interpreter::ProcessLinkMetrics(uint8_t aArgsLength, char *aArgs[]) +{ + otError error = OT_ERROR_INVALID_COMMAND; + + VerifyOrExit(aArgsLength >= 1, OT_NOOP); + + if (strcmp(aArgs[0], "query") == 0) + { + error = ProcessLinkMetricsQuery(aArgsLength - 1, aArgs + 1); + } + +exit: + return error; +} + +otError Interpreter::ProcessLinkMetricsQuery(uint8_t aArgsLength, char *aArgs[]) +{ + otError error = OT_ERROR_INVALID_ARGS; + otIp6Address address; + otLinkMetrics linkMetrics; + long seriesId = 0; + + VerifyOrExit(aArgsLength >= 2, OT_NOOP); + + SuccessOrExit(error = otIp6AddressFromString(aArgs[0], &address)); + + memset(&linkMetrics, 0, sizeof(otLinkMetrics)); + + if (strcmp(aArgs[1], "single") == 0) + { + VerifyOrExit(aArgsLength == 3, OT_NOOP); + for (char *arg = aArgs[2]; *arg != '\0'; arg++) + { + switch (*arg) + { + case 'p': + linkMetrics.mPduCount = 1; + break; + + case 'q': + linkMetrics.mLqi = 1; + break; + + case 'm': + linkMetrics.mLinkMargin = 1; + break; + + case 'r': + linkMetrics.mRssi = 1; + break; + + default: + ExitNow(error = OT_ERROR_INVALID_ARGS); + } + } + error = otLinkMetricsQuery(mInstance, &address, static_cast(seriesId), &linkMetrics, + &Interpreter::HandleLinkMetricsReport, this); + } + +exit: + return error; +} + +#endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + #if OPENTHREAD_FTD otError Interpreter::ProcessPskc(uint8_t aArgsLength, char *aArgs[]) { diff --git a/src/cli/cli.hpp b/src/cli/cli.hpp index 41328f6a1..ff7b545fa 100644 --- a/src/cli/cli.hpp +++ b/src/cli/cli.hpp @@ -370,6 +370,10 @@ private: otError ProcessLeaderWeight(uint8_t aArgsLength, char *aArgs[]); #endif otError ProcessMasterKey(uint8_t aArgsLength, char *aArgs[]); +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + otError ProcessLinkMetrics(uint8_t aArgsLength, char *aArgs[]); + otError ProcessLinkMetricsQuery(uint8_t aArgsLength, char *aArgs[]); +#endif #if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE otError ProcessMlr(uint8_t aArgsLength, char *aArgs[]); @@ -530,6 +534,14 @@ private: #if OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE void HandleSntpResponse(uint64_t aTime, otError aResult); #endif +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + static void HandleLinkMetricsReport(const otIp6Address * aAddress, + const otLinkMetricsValues *aMetricsValues, + void * aContext); + + void HandleLinkMetricsReport(const otIp6Address *aAddress, const otLinkMetricsValues *aMetricsValues); +#endif + static Interpreter &GetOwner(OwnerLocator &aOwnerLocator); static void HandleDiscoveryRequest(const otThreadDiscoveryRequestInfo *aInfo, void *aContext) @@ -626,6 +638,9 @@ private: #if OPENTHREAD_FTD {"leaderpartitionid", &Interpreter::ProcessLeaderPartitionId}, {"leaderweight", &Interpreter::ProcessLeaderWeight}, +#endif +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + {"linkmetrics", &Interpreter::ProcessLinkMetrics}, #endif {"log", &Interpreter::ProcessLog}, {"mac", &Interpreter::ProcessMac}, diff --git a/src/core/BUILD.gn b/src/core/BUILD.gn index 584aa3377..965eac595 100644 --- a/src/core/BUILD.gn +++ b/src/core/BUILD.gn @@ -160,6 +160,10 @@ if (openthread_enable_core_config_args) { defines += [ "OPENTHREAD_CONFIG_LEGACY_ENABLE=1" ] } + if (openthread_config_link_metrics_enable) { + defines += [ "OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE=1" ] + } + if (openthread_config_link_raw_enable) { defines += [ "OPENTHREAD_CONFIG_LINK_RAW_ENABLE=1" ] } @@ -305,6 +309,7 @@ openthread_core_files = [ "api/jam_detection_api.cpp", "api/joiner_api.cpp", "api/link_api.cpp", + "api/link_metrics_api.cpp", "api/link_raw_api.cpp", "api/logging_api.cpp", "api/message_api.cpp", @@ -500,6 +505,9 @@ openthread_core_files = [ "thread/indirect_sender_frame_context.hpp", "thread/key_manager.cpp", "thread/key_manager.hpp", + "thread/link_metrics.cpp", + "thread/link_metrics.hpp", + "thread/link_metrics_tlvs.hpp", "thread/link_quality.cpp", "thread/link_quality.hpp", "thread/lowpan.cpp", diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index fe4ba3d5f..aff9e1f42 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -88,6 +88,7 @@ set(COMMON_SOURCES api/jam_detection_api.cpp api/joiner_api.cpp api/link_api.cpp + api/link_metrics_api.cpp api/link_raw_api.cpp api/logging_api.cpp api/message_api.cpp @@ -184,6 +185,7 @@ set(COMMON_SOURCES thread/energy_scan_server.cpp thread/indirect_sender.cpp thread/key_manager.cpp + thread/link_metrics.cpp thread/link_quality.cpp thread/lowpan.cpp thread/mesh_forwarder.cpp diff --git a/src/core/Makefile.am b/src/core/Makefile.am index 1fec81360..eabfd6103 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -130,6 +130,7 @@ SOURCES_COMMON = \ api/jam_detection_api.cpp \ api/joiner_api.cpp \ api/link_api.cpp \ + api/link_metrics_api.cpp \ api/link_raw_api.cpp \ api/logging_api.cpp \ api/message_api.cpp \ @@ -226,6 +227,7 @@ SOURCES_COMMON = \ thread/energy_scan_server.cpp \ thread/indirect_sender.cpp \ thread/key_manager.cpp \ + thread/link_metrics.cpp \ thread/link_quality.cpp \ thread/lowpan.cpp \ thread/mesh_forwarder.cpp \ @@ -451,6 +453,8 @@ HEADERS_COMMON = \ thread/indirect_sender.hpp \ thread/indirect_sender_frame_context.hpp \ thread/key_manager.hpp \ + thread/link_metrics.hpp \ + thread/link_metrics_tlvs.hpp \ thread/link_quality.hpp \ thread/lowpan.hpp \ thread/mesh_forwarder.hpp \ diff --git a/src/core/api/link_metrics_api.cpp b/src/core/api/link_metrics_api.cpp new file mode 100644 index 000000000..80769f200 --- /dev/null +++ b/src/core/api/link_metrics_api.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2020, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file implements the OpenThread Link Metrics API. + */ + +#include "openthread-core-config.h" + +#include + +#include "common/instance.hpp" +#include "net/ip6_address.hpp" + +using namespace ot; + +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + +otError otLinkMetricsQuery(otInstance * aInstance, + const otIp6Address * aDestination, + uint8_t aSeriesId, + const otLinkMetrics * aLinkMetricsFlags, + otLinkMetricsReportCallback aCallback, + void * aCallbackContext) +{ + OT_ASSERT(aDestination != nullptr && aLinkMetricsFlags != nullptr); + + static_cast(aInstance)->Get().SetLinkMetricsReportCallback(aCallback, aCallbackContext); + + return static_cast(aInstance)->Get().LinkMetricsQuery( + static_cast(*aDestination), aSeriesId, *aLinkMetricsFlags); +} + +#endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE diff --git a/src/core/common/instance.hpp b/src/core/common/instance.hpp index eeca05f19..38def2502 100644 --- a/src/core/common/instance.hpp +++ b/src/core/common/instance.hpp @@ -86,6 +86,10 @@ #include "backbone_router/bbr_local.hpp" #endif +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE +#include "thread/link_metrics.hpp" +#endif + #endif // (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) #endif // OPENTHREAD_FTD || OPENTHREAD_MTD @@ -789,6 +793,13 @@ template <> inline DuaManager &Instance::Get(void) } #endif +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE +template <> inline LinkMetrics &Instance::Get(void) +{ + return mThreadNetif.mLinkMetrics; +} +#endif + #endif // (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) #if OPENTHREAD_CONFIG_OTNS_ENABLE diff --git a/src/core/common/message.cpp b/src/core/common/message.cpp index e6357a786..46232e62b 100644 --- a/src/core/common/message.cpp +++ b/src/core/common/message.cpp @@ -620,6 +620,9 @@ void Message::SetLinkInfo(const ThreadLinkInfo &aLinkInfo) SetLinkSecurityEnabled(aLinkInfo.mLinkSecurity); SetPanId(aLinkInfo.mPanId); AddRss(aLinkInfo.mRss); +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + AddLqi(aLinkInfo.mLqi); +#endif #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE SetTimeSyncSeq(aLinkInfo.mTimeSyncSeq); SetNetworkTimeOffset(aLinkInfo.mNetworkTimeOffset); diff --git a/src/core/common/message.hpp b/src/core/common/message.hpp index 923bf6122..c1c6e81d3 100644 --- a/src/core/common/message.hpp +++ b/src/core/common/message.hpp @@ -151,6 +151,9 @@ struct MessageMetadata uint16_t mLength; ///< Number of bytes within the message. uint16_t mOffset; ///< A byte offset within the message. RssAverager mRssAverager; ///< The averager maintaining the received signal strength (RSS) average. +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + LqiAverager mLqiAverager; ///< The averager maintaining the Link quality indicator (LQI) average. +#endif ChildMask mChildMask; ///< A ChildMask to indicate which sleepy children need to receive this. uint16_t mMeshDest; ///< Used for unicast non-link-local messages. @@ -883,6 +886,35 @@ public: */ const RssAverager &GetRssAverager(void) const { return GetMetadata().mRssAverager; } +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + /** + * This method updates the average LQI (Link Quality Indicator) associated with the message. + * + * The given LQI value would be added to the average. Note that a message can be composed of multiple 802.15.4 + * frame fragments each received with a different signal strength. + * + * @param[in] aLQI A new LQI value (has no unit) to be added to average. + * + */ + void AddLqi(uint8_t aLqi) { GetMetadata().mLqiAverager.Add(aLqi); } + + /** + * This method returns the average LQI (Link Quality Indicator) associated with the message. + * + * @returns The current average LQI value (in dBm) or OT_RADIO_LQI_NONE if no average is available. + * + */ + uint8_t GetAverageLqi(void) const { return GetMetadata().mLqiAverager.GetAverage(); } + + /** + * This mehod returns the count of frames counted so far. + * + * @retruns The count of frames that have been counted. + * + */ + uint8_t GetPsduCount(void) const { return GetMetadata().mLqiAverager.GetCount(); } +#endif + /** * This method sets the message's link info properties (PAN ID, link security, RSS) from a given `ThreadLinkInfo`. * diff --git a/src/core/config/mle.h b/src/core/config/mle.h index 2ec38d50d..4c5b5432d 100644 --- a/src/core/config/mle.h +++ b/src/core/config/mle.h @@ -240,4 +240,14 @@ #define OPENTHREAD_CONFIG_MLE_INFORM_PREVIOUS_PARENT_ON_REATTACH 0 #endif +/** + * @def OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + * + * Define as 1 to enable Link Metrics feature. + * + */ +#ifndef OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE +#define OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE 0 +#endif + #endif // CONFIG_MLE_H_ diff --git a/src/core/config/openthread-core-config-check.h b/src/core/config/openthread-core-config-check.h index 5b537aaaf..351856c22 100644 --- a/src/core/config/openthread-core-config-check.h +++ b/src/core/config/openthread-core-config-check.h @@ -523,6 +523,12 @@ #endif #endif +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE +#if (OPENTHREAD_CONFIG_THREAD_VERSION < OT_THREAD_VERSION_1_2) +#error "Thread 1.2 or higher version is required for OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE" +#endif +#endif + #if OPENTHREAD_CONFIG_DUA_ENABLE && (OPENTHREAD_CONFIG_THREAD_VERSION < OT_THREAD_VERSION_1_2) #error "Thread 1.2 or higher version is required for OPENTHREAD_CONFIG_DUA_ENABLE" #endif diff --git a/src/core/thread/link_metrics.cpp b/src/core/thread/link_metrics.cpp new file mode 100644 index 000000000..0b1948905 --- /dev/null +++ b/src/core/thread/link_metrics.cpp @@ -0,0 +1,385 @@ +/* + * Copyright (c) 2020, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file includes definitions for Thread Link Metrics. + */ + +#include "link_metrics.hpp" + +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + +#include "common/code_utils.hpp" +#include "common/instance.hpp" +#include "common/locator-getters.hpp" +#include "common/logging.hpp" + +#include "link_metrics_tlvs.hpp" + +namespace ot { + +LinkMetrics::LinkMetrics(Instance &aInstance) + : InstanceLocator(aInstance) + , mLinkMetricsReportCallback(nullptr) + , mLinkMetricsReportCallbackContext(nullptr) +{ +} + +otError LinkMetrics::LinkMetricsQuery(const Ip6::Address & aDestination, + uint8_t aSeriesId, + const otLinkMetrics &aLinkMetricsFlags) +{ + otError error; + LinkMetricsTypeIdFlags typeIdFlags[kMaxTypeIdFlags]; + uint8_t typeIdFlagsCount = TypeIdFlagsFromLinkMetricsFlags(typeIdFlags, aLinkMetricsFlags); + + error = SendLinkMetricsQuery(aDestination, aSeriesId, typeIdFlags, typeIdFlagsCount); + + return error; +} + +otError LinkMetrics::AppendLinkMetricsReport(Message &aMessage, const Message &aRequestMessage) +{ + otError error = OT_ERROR_NONE; + Tlv tlv; + uint8_t queryId; + bool hasQueryId = false; + uint8_t length = 0; + uint16_t startOffset = aMessage.GetLength(); + uint16_t offset; + uint16_t endOffset; + otLinkMetrics linkMetrics; + + memset(&linkMetrics, 0, sizeof(linkMetrics)); + + SuccessOrExit(error = Tlv::FindTlvValueOffset(aRequestMessage, Mle::Tlv::Type::kLinkMetricsQuery, offset, + endOffset)); // `endOffset` is used to store tlv length here + + endOffset = offset + endOffset; + + while (offset < endOffset) + { + VerifyOrExit(aRequestMessage.Read(offset, sizeof(tlv), &tlv) == sizeof(tlv), error = OT_ERROR_PARSE); + + switch (tlv.GetType()) + { + case kLinkMetricsQueryId: + SuccessOrExit(error = Tlv::ReadUint8Tlv(aRequestMessage, offset, queryId)); + hasQueryId = true; + break; + + case kLinkMetricsQueryOptions: + for (uint16_t index = offset + sizeof(tlv), endIndex = static_cast(offset + tlv.GetSize()); + index < endIndex; index += sizeof(LinkMetricsTypeIdFlags)) + { + LinkMetricsTypeIdFlags typeIdFlags; + + VerifyOrExit(aRequestMessage.Read(index, sizeof(typeIdFlags), &typeIdFlags) == sizeof(typeIdFlags), + error = OT_ERROR_PARSE); + + switch (typeIdFlags.GetRawValue()) + { + case kTypeIdFlagPdu: + VerifyOrExit(!linkMetrics.mPduCount, error = OT_ERROR_PARSE); + linkMetrics.mPduCount = true; + break; + + case kTypeIdFlagLqi: + VerifyOrExit(!linkMetrics.mLqi, error = OT_ERROR_PARSE); + linkMetrics.mLqi = true; + break; + + case kTypeIdFlagLinkMargin: + VerifyOrExit(!linkMetrics.mLinkMargin, error = OT_ERROR_PARSE); + linkMetrics.mLinkMargin = true; + break; + + case kTypeIdFlagRssi: + VerifyOrExit(!linkMetrics.mRssi, error = OT_ERROR_PARSE); + linkMetrics.mRssi = true; + break; + + default: + if (typeIdFlags.IsExtendedFlagSet()) + { + index += sizeof(uint8_t); // Skip the additional second flags byte. + } + break; + } + } + break; + + default: + break; + } + + offset += tlv.GetSize(); + } + + VerifyOrExit(hasQueryId, error = OT_ERROR_PARSE); + + // Link Metrics Report TLV + tlv.SetType(Mle::Tlv::kLinkMetricsReport); + SuccessOrExit(error = aMessage.Append(&tlv, sizeof(tlv))); + + if (queryId == 0) + { + SuccessOrExit(error = AppendSingleProbeLinkMetricsReport(aMessage, length, linkMetrics, aRequestMessage)); + } + else + { + ExitNow(error = OT_ERROR_NOT_IMPLEMENTED); + } + + tlv.SetLength(length); + aMessage.Write(startOffset, sizeof(tlv), &tlv); + +exit: + otLogDebgMle("AppendLinkMetricsReport, error:%s", otThreadErrorToString(error)); + return error; +} + +void LinkMetrics::HandleLinkMetricsReport(const Message & aMessage, + uint16_t aOffset, + uint16_t aLength, + const Ip6::Address &aAddress) +{ + otLinkMetricsValues metricsValues; + uint8_t metricsRawValue; + uint16_t pos = aOffset; + uint16_t endPos = aOffset + aLength; + Tlv tlv; + LinkMetricsTypeIdFlags typeIdFlags; + + VerifyOrExit(mLinkMetricsReportCallback != nullptr, OT_NOOP); + + memset(&metricsValues, 0, sizeof(metricsValues)); + + otLogDebgMle("Received Link Metrics Report"); + + while (pos < endPos) + { + VerifyOrExit(aMessage.Read(pos, sizeof(Tlv), &tlv) == sizeof(Tlv), OT_NOOP); + VerifyOrExit(tlv.GetType() == kLinkMetricsReportSub, OT_NOOP); + pos += sizeof(Tlv); + VerifyOrExit(pos + tlv.GetLength() <= endPos, OT_NOOP); + + aMessage.Read(pos, sizeof(LinkMetricsTypeIdFlags), &typeIdFlags); + if (typeIdFlags.IsExtendedFlagSet()) + { + pos += tlv.GetLength(); // Skip the whole sub-TLV if `E` flag is set + continue; + } + pos += sizeof(LinkMetricsTypeIdFlags); + + switch (typeIdFlags.GetRawValue()) + { + case kTypeIdFlagPdu: + metricsValues.mMetrics.mPduCount = true; + aMessage.Read(pos, sizeof(uint32_t), &metricsValues.mPduCountValue); + pos += sizeof(uint32_t); + otLogDebgMle(" - PDU Counter: %d (Count/Summation)", metricsValues.mPduCountValue); + break; + + case kTypeIdFlagLqi: + metricsValues.mMetrics.mLqi = true; + aMessage.Read(pos, sizeof(uint8_t), &metricsValues.mLqiValue); + pos += sizeof(uint8_t); + otLogDebgMle(" - LQI: %d (Exponential Moving Average)", metricsValues.mLqiValue); + break; + + case kTypeIdFlagLinkMargin: + metricsValues.mMetrics.mLinkMargin = true; + aMessage.Read(pos, sizeof(uint8_t), &metricsRawValue); + metricsValues.mLinkMarginValue = + metricsRawValue * 130 / 255; // Reverse operation for linear scale, map from [0, 255] to [0, 130] + pos += sizeof(uint8_t); + otLogDebgMle(" - Margin: %d (dB) (Exponential Moving Average)", metricsValues.mLinkMarginValue); + break; + + case kTypeIdFlagRssi: + metricsValues.mMetrics.mRssi = true; + aMessage.Read(pos, sizeof(uint8_t), &metricsRawValue); + metricsValues.mRssiValue = + metricsRawValue * 130 / 255 - 130; // Reverse operation for linear scale, map from [0, 255] to [-130, 0] + pos += sizeof(uint8_t); + otLogDebgMle(" - RSSI: %d (dBm) (Exponential Moving Average)", metricsValues.mRssiValue); + break; + + default: + break; + } + } + + mLinkMetricsReportCallback(&aAddress, &metricsValues, mLinkMetricsReportCallbackContext); + +exit: + return; +} + +void LinkMetrics::SetLinkMetricsReportCallback(otLinkMetricsReportCallback aCallback, void *aCallbackContext) +{ + mLinkMetricsReportCallback = aCallback; + mLinkMetricsReportCallbackContext = aCallbackContext; +} + +otError LinkMetrics::SendLinkMetricsQuery(const Ip6::Address & aDestination, + uint8_t aSeriesId, + const LinkMetricsTypeIdFlags *aTypeIdFlags, + uint8_t aTypeIdFlagsCount) +{ + otError error = OT_ERROR_NONE; + LinkMetricsQueryOptionsTlv linkMetricsQueryOptionsTlv; + uint8_t length = 0; + static const uint8_t tlvs[] = {Mle::Tlv::kLinkMetricsReport}; + uint8_t buf[sizeof(Tlv) * 3 + sizeof(uint8_t) + + sizeof(LinkMetricsTypeIdFlags) * + kMaxTypeIdFlags]; // LinkMetricsQuery Tlv + LinkMetricsQueryId sub-TLV (value-length: 1 byte) + + // LinkMetricsQueryOptions sub-TLV (value-length: `kMaxTypeIdFlags` bytes) + Tlv *tlv = reinterpret_cast(buf); + Tlv subTlv; + + // Link Metrics Query TLV + tlv->SetType(Mle::Tlv::kLinkMetricsQuery); + length += sizeof(Tlv); + + // Link Metrics Query ID sub-TLV + subTlv.SetType(kLinkMetricsQueryId); + subTlv.SetLength(sizeof(uint8_t)); + memcpy(buf + length, &subTlv, sizeof(subTlv)); + length += sizeof(subTlv); + memcpy(buf + length, &aSeriesId, sizeof(aSeriesId)); + length += sizeof(aSeriesId); + + // Link Metrics Query Options sub-TLV + if (aTypeIdFlagsCount > 0) + { + linkMetricsQueryOptionsTlv.Init(); + linkMetricsQueryOptionsTlv.SetLength(aTypeIdFlagsCount * sizeof(LinkMetricsTypeIdFlags)); + + memcpy(buf + length, &linkMetricsQueryOptionsTlv, sizeof(linkMetricsQueryOptionsTlv)); + length += sizeof(linkMetricsQueryOptionsTlv); + memcpy(buf + length, aTypeIdFlags, linkMetricsQueryOptionsTlv.GetLength()); + length += linkMetricsQueryOptionsTlv.GetLength(); + } + + // Set Length for Link Metrics Report TLV + tlv->SetLength(length - sizeof(Tlv)); + + SuccessOrExit(error = Get().SendDataRequest(aDestination, tlvs, sizeof(tlvs), 0, buf, length)); + +exit: + return error; +} + +otError LinkMetrics::AppendSingleProbeLinkMetricsReport(Message & aMessage, + uint8_t & aLength, + const otLinkMetrics &aLinkMetrics, + const Message & aRequestMessage) +{ + otError error = OT_ERROR_NONE; + LinkMetricsReportSubTlv metric; + + aLength = 0; + + // Link Metrics Report sub-TLVs + if (aLinkMetrics.mPduCount) + { + metric.Init(); + metric.SetMetricsTypeId(kTypeIdFlagPdu); + metric.SetMetricsValue32(aRequestMessage.GetPsduCount()); + SuccessOrExit(error = aMessage.Append(&metric, metric.GetSize())); + aLength += metric.GetSize(); + } + + if (aLinkMetrics.mLqi) + { + metric.Init(); + metric.SetMetricsTypeId(kTypeIdFlagLqi); + metric.SetMetricsValue8(aRequestMessage.GetAverageLqi()); // IEEE 802.15.4 LQI is in scale 0-255 + SuccessOrExit(error = aMessage.Append(&metric, metric.GetSize())); + aLength += metric.GetSize(); + } + + if (aLinkMetrics.mLinkMargin) + { + metric.Init(); + metric.SetMetricsTypeId(kTypeIdFlagLinkMargin); + metric.SetMetricsValue8( + LinkQualityInfo::ConvertRssToLinkMargin(Get().GetNoiseFloor(), aRequestMessage.GetAverageRss()) * + 255 / 130); // Linear scale Link Margin from [0, 130] to [0, 255] + SuccessOrExit(error = aMessage.Append(&metric, metric.GetSize())); + aLength += metric.GetSize(); + } + + if (aLinkMetrics.mRssi) + { + metric.Init(); + metric.SetMetricsTypeId(kTypeIdFlagRssi); + metric.SetMetricsValue8((aRequestMessage.GetAverageRss() + 130) * 255 / + 130); // Linear scale rss from [-130, 0] to [0, 255] + SuccessOrExit(error = aMessage.Append(&metric, metric.GetSize())); + aLength += metric.GetSize(); + } + +exit: + return error; +} + +uint8_t LinkMetrics::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); + } + + return count; +} + +} // namespace ot + +#endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE diff --git a/src/core/thread/link_metrics.hpp b/src/core/thread/link_metrics.hpp new file mode 100644 index 000000000..515292feb --- /dev/null +++ b/src/core/thread/link_metrics.hpp @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2020, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file includes definitions for Thread Link Metrics query and management. + */ + +#ifndef LINK_METRICS_HPP_ +#define LINK_METRICS_HPP_ + +#include "openthread-core-config.h" + +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + +#include + +#include "common/code_utils.hpp" +#include "common/locator.hpp" + +#include "link_metrics_tlvs.hpp" +#include "topology.hpp" + +namespace ot { + +/** + * @addtogroup core-link-metrics + * + * @brief + * This module includes definitions for Thread Link Metrics query and management. + * + * @{ + */ + +class LinkMetrics : public InstanceLocator +{ +public: + /** + * This constructor initializes an instance of the LinkMetrics class. + * + * @param[in] aInstance A reference to the OpenThread interface. + * + */ + explicit LinkMetrics(Instance &aInstance); + + /** + * This function sends an MLE Data Request containing Link Metrics Query TLV to query Link Metrics data. + * + * It could be either a Single Probe or a Forward Tracking Series. + * + * @param[in] aDestination A reference to the IPv6 address of the destination. + * @param[in] aSeriesId The Series ID to query, 0 for single probe. + * @param[in] aLinkMetricsFlags A reference 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. + * + */ + otError LinkMetricsQuery(const Ip6::Address & aDestination, + uint8_t aSeriesId, + const otLinkMetrics &aLinkMetricsFlags); + + /** + * This method appends a Link Metrics Report to a message according to the Link Metrics query. + * + * @param[out] aMessage A reference to the message to append report. + * @param[in] aRequestMessage A reference to the message of the Data Request. + * + * @retval OT_ERROR_NONE Successfully appended the Thread Discovery TLV. + * @retval OT_ERROR_PARSE Cannot parse query sub TLV successfully. + * @retval OT_ERROR_INVALID_ARGS QueryId is invalid or any Type ID is invalid. + * + */ + otError AppendLinkMetricsReport(Message &aMessage, const Message &aRequestMessage); + + /** + * This method handles the received Link Metrics report contained in @p aMessage. + * + * @param[in] aMessage A reference to the message. + * @param[in] aOffset The offset in bytes where the metrics report sub-TLVs start. + * @param[in] aLength The length of the metrics report sub-TLVs in bytes. + * @param[in] aAddress A reference to the source address of the message. + * + */ + void HandleLinkMetricsReport(const Message & aMessage, + uint16_t aOffset, + uint16_t aLength, + const Ip6::Address &aAddress); + + /** + * This method registers a callback to handle Link Metrics report received. + * + * @param[in] aCallback A pointer to a function that is called when link probing report is received + * @param[in] aCallbackContext A pointer to application-specific context. + * + */ + void SetLinkMetricsReportCallback(otLinkMetricsReportCallback aCallback, void *aCallbackContext); + +private: + enum + { + kMaxTypeIdFlags = 4, + + kTypeIdFlagPdu = + 0x40, ///< 0x0_1_000_000 -> 0x40 ==> L bit set, type = 0 (count/summation), metric-enum = 0 (PDU rxed) + kTypeIdFlagLqi = 0x09, ///< 0x0_0_001_001 -> 0x00 ==> L bit not set, type = 1 (exp ave), metric-enum = 1 (LQI) + kTypeIdFlagLinkMargin = + 0x0a, ///< 0x0_0_001_010 -> 0x00 ==> L bit not set, type = 1 (exp ave), metric-enum = 2 (Link Margin) + kTypeIdFlagRssi = 0x0b, ///< 0x0_0_001_011 -> 0x00 ==> L bit not set, type = 1 (exp ave), metric-enum = 3 (RSSI) + }; + + otLinkMetricsReportCallback mLinkMetricsReportCallback; + void * mLinkMetricsReportCallbackContext; + + otError SendLinkMetricsQuery(const Ip6::Address & aDestination, + uint8_t aSeriesId, + const LinkMetricsTypeIdFlags *aTypeIdFlags, + uint8_t aTypeIdFlagsCount); + + otError AppendSingleProbeLinkMetricsReport(Message & aMessage, + uint8_t & aLength, + const otLinkMetrics &aLinkMetrics, + const Message & aRequestMessage); + + static uint8_t TypeIdFlagsFromLinkMetricsFlags(LinkMetricsTypeIdFlags *aTypeIdFlags, + const otLinkMetrics & aLinkMetricsFlags); +}; + +/** + * @} + */ + +} // namespace ot + +#endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + +#endif // LINK_METRICS_HPP diff --git a/src/core/thread/link_metrics_tlvs.hpp b/src/core/thread/link_metrics_tlvs.hpp new file mode 100644 index 000000000..8cefa14f7 --- /dev/null +++ b/src/core/thread/link_metrics_tlvs.hpp @@ -0,0 +1,337 @@ +/* + * Copyright (c) 2020, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file includes definitions for generating and processing Link Metrics TLVs. + * + */ + +#ifndef LINK_METRICS_TLVS_HPP_ +#define LINK_METRICS_TLVS_HPP_ + +#include + +#include "common/encoding.hpp" +#include "common/message.hpp" +#include "common/tlvs.hpp" + +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + +namespace ot { + +/** + * Link Metrics parameters + * + */ +enum +{ + kLinkMetricsMaxTypeIdFlags = 4, ///< Max count of Link Metrics Type ID Flags. +}; + +/** + * Link Metrics Sub-TLV types + * + */ +enum Type +{ + kLinkMetricsReportSub = 0, ///< Link Metrics Report Sub-TLV + kLinkMetricsQueryId = 1, ///< Link Metrics Query ID Sub-TLV + kLinkMetricsQueryOptions = 2, ///< Link Metrics Query Options Sub-TLV + kForwardProbingRegistration = 3, ///< Forward Probing Registration Sub-TLV + kReverseProbingRegistration = 4, ///< Reverse Probing Registration Sub-TLV + kLinkMetricsStatus = 5, ///< Link Metrics Status Sub-TLV + kSeriesTrackingCapabilities = 6, ///< Series Tracking Capabilities Sub-TLV + kEnhancedACKConfiguration = 7, ///< Enhanced ACK Configuration Sub-TLV +}; + +/** + * This class implements Link Metrics Type Id Flags generation and parsing. + * + */ +OT_TOOL_PACKED_BEGIN +class LinkMetricsTypeIdFlags +{ +public: + /** + * Default constructor. + * + */ + LinkMetricsTypeIdFlags(void) {} + + /** + * Constructor for implicit cast from `uint8_t` to `LinkMetricsTypeIdFlags`. + * + */ + LinkMetricsTypeIdFlags(uint8_t typeIdFlags) + : mTypeIdFlags(typeIdFlags) + { + } + + /** + * This method init the Type Id value + * + */ + void Init(void) { mTypeIdFlags = 0; } + + /** + * This method clears the Extended flag. + * + */ + void ClearExtendedFlag(void) { mTypeIdFlags &= ~kExtendedFlag; } + + /** + * This method sets the Extended flag, indicating an additional second flags byte after the current 1-byte flags. + * MUST NOT set in Thread 1.2.1. + * + */ + void SetExtendedFlag(void) { mTypeIdFlags |= kExtendedFlag; } + + /** + * This method indicates whether or not the Extended flag is set. + * + * @retval true The Extended flag is set. + * @retval false The Extended flag is not set. + * + */ + bool IsExtendedFlagSet(void) const { return (mTypeIdFlags & kExtendedFlag) != 0; } + + /** + * This method clears value length flag. + * + */ + void ClearLengthFlag(void) { mTypeIdFlags &= ~kLengthFlag; } + + /** + * This method sets the value length flag. + * + */ + void SetLengthFlag(void) { mTypeIdFlags |= kLengthFlag; } + + /** + * This method indicates whether or not the value length flag is set. + * + * @retval true The value length flag is set, extended value length (4 bytes) + * @retval false The value length flag is not set, short value length (1 byte) + * + */ + bool IsLengthFlagSet(void) const { return (mTypeIdFlags & kLengthFlag) != 0; } + + /** + * This method sets the Type/Average Enum. + * + * @param[in] aTypeEnum Type/Average Enum. + * + */ + void SetTypeEnum(uint8_t aTypeEnum) + { + mTypeIdFlags = (mTypeIdFlags & ~kTypeEnumMask) | ((aTypeEnum << kTypeEnumOffset) & kTypeEnumMask); + } + + /** + * This method returns the Type/Average Enum. + * + * @returns The Type/Average Enum. + * + */ + uint8_t GetTypeEnum(void) const { return (mTypeIdFlags & kTypeEnumMask) >> kTypeEnumOffset; } + + /** + * This method sets the Metric Enum. + * + * @param[in] aMetricEnum Metric Enum. + * + */ + void SetMetricEnum(uint8_t aMetricEnum) + { + mTypeIdFlags = (mTypeIdFlags & ~kMetricEnumMask) | ((aMetricEnum << kMetricEnumOffset) & kMetricEnumMask); + } + + /** + * This method returns the Metric Enum. + * + * @returns The Metric Enum. + * + */ + uint8_t GetMetricEnum(void) const { return (mTypeIdFlags & kMetricEnumMask) >> kMetricEnumOffset; } + + /** + * This method returns the raw value of the entire TypeIdFlags. + * + * @returns The raw value of TypeIdFlags. + * + */ + uint8_t GetRawValue(void) const { return mTypeIdFlags; } + + /** + * This method sets the raw value of the entire TypeIdFlags. + * + * @param[in] aTypeIdFlags The value of entire TypeIdFlags. + * + */ + void SetRawValue(uint8_t aTypeIdFlags) { mTypeIdFlags = aTypeIdFlags; } + +private: + enum + { + kLengthFlag = 1 << 6, + kExtendedFlag = 1 << 7, + kTypeEnumOffset = 3, + kTypeEnumMask = 7 << kTypeEnumOffset, + kMetricEnumOffset = 0, + kMetricEnumMask = 7 << kMetricEnumOffset, + }; + + uint8_t mTypeIdFlags; +} OT_TOOL_PACKED_END; + +/** + * This class implements Link Metrics Report Sub-TLV generation and parsing. + * + */ +OT_TOOL_PACKED_BEGIN +class LinkMetricsReportSubTlv : public Tlv +{ +public: + /** + * This method initializes the TLV. + * + */ + void Init(void) + { + SetType(kLinkMetricsReportSub); + SetLength(sizeof(*this) - sizeof(Tlv)); + } + + /** + * This method indicates whether or not the TLV appears to be well-formed. + * + * @retval true The TLV appears to be well-formed. + * @retval false The TLV does not appear to be well-formed. + * + */ + bool IsValid(void) const { return GetLength() >= sizeof(LinkMetricsTypeIdFlags) + sizeof(uint8_t); } + + /** + * This method returns the Link Metrics Type ID. + * + * @returns The Link Metrics Type ID. + * + */ + LinkMetricsTypeIdFlags GetMetricsTypeId(void) const { return mMetricsTypeId; } + + /** + * This method sets the Link Metrics Type ID. + * + * @param[in] aMetricsTypeID The Link Metrics Type ID to set. + * + */ + void SetMetricsTypeId(LinkMetricsTypeIdFlags aMetricsTypeId) + { + mMetricsTypeId = aMetricsTypeId; + if (!aMetricsTypeId.IsLengthFlagSet()) + { + SetLength(sizeof(*this) - sizeof(Tlv) - sizeof(uint32_t) + sizeof(uint8_t)); // The value is 1 byte long + } + } + + /** + * This method returns the metric value in 8 bits. + * + * @returns The metric value. + * + */ + uint8_t GetMetricsValue8(void) const { return mMetricsValue.m8; } + + /** + * This method returns the metric value in 32 bits. + * + * @returns The metric value. + * + */ + uint32_t GetMetricsValue32(void) const { return mMetricsValue.m32; } + + /** + * This method sets the metric value (8 bits). + * + * @param[in] aMetricsValue Metrics value. + * + */ + void SetMetricsValue8(uint8_t aMetricsValue) { mMetricsValue.m8 = aMetricsValue; } + + /** + * This method sets the metric value (32 bits). + * + * @param[in] aMetricsValue Metrics value. + * + */ + void SetMetricsValue32(uint32_t aMetricsValue) { mMetricsValue.m32 = aMetricsValue; } + +private: + LinkMetricsTypeIdFlags mMetricsTypeId; + union + { + uint8_t m8; + uint32_t m32; + } mMetricsValue; +} OT_TOOL_PACKED_END; + +/** + * This class implements Link Metrics Query Options Sub-TLV generation and parsing. + * + */ +OT_TOOL_PACKED_BEGIN +class LinkMetricsQueryOptionsTlv : public Tlv +{ +public: + /** + * This method initializes the TLV. + * + */ + void Init(void) + { + SetType(kLinkMetricsQueryOptions); + SetLength(0); + } + + /** + * This method indicates whether or not the TLV appears to be well-formed. + * + * @retval TRUE If the TLV appears to be well-formed. + * @retval FALSE If the TLV does not appear to be well-formed. + * + */ + bool IsValid(void) const { return GetLength() >= sizeof(LinkMetricsTypeIdFlags); } + +} OT_TOOL_PACKED_END; + +} // namespace ot + +#endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + +#endif // LINK_METRICS_TLVS_HPP_ diff --git a/src/core/thread/link_quality.cpp b/src/core/thread/link_quality.cpp index ca1632381..15ee2e182 100644 --- a/src/core/thread/link_quality.cpp +++ b/src/core/thread/link_quality.cpp @@ -121,6 +121,25 @@ exit: return string; } +void LqiAverager::Reset(void) +{ + mCount = 0; + mAverage = 0; +} + +void LqiAverager::Add(uint8_t aLqi) +{ + uint8_t count; + + if (mCount < UINT8_MAX) + { + mCount++; + } + count = OT_MIN((1 << kCoeffBitShift), mCount); + + mAverage = static_cast(((mAverage * (count - 1)) + aLqi) / count); +} + void LinkQualityInfo::Clear(void) { mRssAverager.Reset(); diff --git a/src/core/thread/link_quality.hpp b/src/core/thread/link_quality.hpp index 1f9f022f6..5f47983a8 100644 --- a/src/core/thread/link_quality.hpp +++ b/src/core/thread/link_quality.hpp @@ -211,6 +211,55 @@ private: uint16_t mCount : 5; // Number of RSS values added to averager so far (limited to 2^kCoeffBitShift-1). }; +/** + * This class implements a Link Quality Indicator (LQI) averager. + * + * It maintains the exponential moving average value of LQI. + * + */ +class LqiAverager +{ +public: + /** + * This method resets the averager and clears the average value. + * + */ + void Reset(void); + + /** + * This method adds a link quality indicator (LQI) value to the average. + * + * @param[in] aLqi Link Quality Indicator value to be added to the average. + * + */ + void Add(uint8_t aLqi); + + /** + * This method returns the current average link quality value maintained by the averager. + * + * @returns The current average value. + * + */ + uint8_t GetAverage(void) const { return mAverage; } + + /** + * This method returns the count of frames calculated so far. + * + * @returns The count of frames calculated. + * + */ + uint8_t GetCount(void) const { return mCount; } + +private: + enum + { + kCoeffBitShift = 3, ///< Coefficient used for exponentially weighted filter (1 << kCoeffBitShift). + }; + + uint8_t mAverage; ///< The average link quality indicator value. + uint8_t mCount; ///< Number of LQI values added to averager so far. +}; + /** * This class encapsulates/stores all relevant information about quality of a link, including average received signal * strength (RSS), last RSS, link margin, and link quality. diff --git a/src/core/thread/mesh_forwarder.cpp b/src/core/thread/mesh_forwarder.cpp index 1593ff413..5e0ffb33a 100644 --- a/src/core/thread/mesh_forwarder.cpp +++ b/src/core/thread/mesh_forwarder.cpp @@ -1042,6 +1042,9 @@ void MeshForwarder::HandleFragment(const uint8_t * aFrame, message->Write(message->GetOffset(), aFrameLength, aFrame); message->MoveOffset(aFrameLength); message->AddRss(aLinkInfo.GetRss()); +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + message->AddLqi(aLinkInfo.GetLqi()); +#endif message->SetTimeout(kReassemblyTimeout); } diff --git a/src/core/thread/mle.cpp b/src/core/thread/mle.cpp index 37fd598a2..261a623c2 100644 --- a/src/core/thread/mle.cpp +++ b/src/core/thread/mle.cpp @@ -2035,8 +2035,13 @@ exit: otError Mle::SendDataRequest(const Ip6::Address &aDestination, const uint8_t * aTlvs, uint8_t aTlvsLength, - uint16_t aDelay) + uint16_t aDelay, + const uint8_t * aExtraTlvs, + uint8_t aExtraTlvsLength) { + OT_UNUSED_VARIABLE(aExtraTlvs); + OT_UNUSED_VARIABLE(aExtraTlvsLength); + otError error = OT_ERROR_NONE; Message *message; @@ -2046,6 +2051,11 @@ otError Mle::SendDataRequest(const Ip6::Address &aDestination, SuccessOrExit(error = AppendActiveTimestamp(*message)); SuccessOrExit(error = AppendPendingTimestamp(*message)); + if (aExtraTlvs != nullptr && aExtraTlvsLength > 0) + { + SuccessOrExit(error = message->Append(aExtraTlvs, aExtraTlvsLength)); + } + if (aDelay) { SuccessOrExit(error = AddDelayedResponse(*message, aDestination, aDelay)); @@ -2801,11 +2811,23 @@ exit: void Mle::HandleDataResponse(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo, const Neighbor *aNeighbor) { otError error; +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + uint16_t metricsReportValueOffset; + uint16_t length; +#endif Log(kMessageReceive, kTypeDataResponse, aMessageInfo.GetPeerAddr()); VerifyOrExit(aNeighbor && aNeighbor->IsStateValid(), error = OT_ERROR_SECURITY); +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + if (Tlv::FindTlvValueOffset(aMessage, Tlv::kLinkMetricsReport, metricsReportValueOffset, length) == OT_ERROR_NONE) + { + Get().HandleLinkMetricsReport(aMessage, metricsReportValueOffset, length, + aMessageInfo.GetPeerAddr()); + } +#endif + error = HandleLeaderData(aMessage, aMessageInfo); if (mDataRequestState == kDataRequestNone && !IsRxOnWhenIdle()) diff --git a/src/core/thread/mle.hpp b/src/core/thread/mle.hpp index c2f40e093..239a6520d 100644 --- a/src/core/thread/mle.hpp +++ b/src/core/thread/mle.hpp @@ -43,6 +43,7 @@ #include "meshcop/joiner_router.hpp" #include "meshcop/meshcop.hpp" #include "net/udp6.hpp" +#include "thread/link_metrics.hpp" #include "thread/mle_tlvs.hpp" #include "thread/mle_types.hpp" #include "thread/neighbor_table.hpp" @@ -92,6 +93,9 @@ class Mle : public InstanceLocator { friend class DiscoverScanner; friend class ot::Notifier; +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + friend class ot::LinkMetrics; +#endif public: /** @@ -1288,10 +1292,12 @@ protected: /** * This method generates an MLE Data Request message. * - * @param[in] aDestination A reference to the IPv6 address of the destination. - * @param[in] aTlvs A pointer to requested TLV types. - * @param[in] aTlvsLength The number of TLV types in @p aTlvs. - * @param[in] aDelay Delay in milliseconds before the Data Request message is sent. + * @param[in] aDestination A reference to the IPv6 address of the destination. + * @param[in] aTlvs A pointer to requested TLV types. + * @param[in] aTlvsLength The number of TLV types in @p aTlvs. + * @param[in] aDelay Delay in milliseconds before the Data Request message is sent. + * @param[in] aExtraTlvs A pointer to extra TLVs. + * @param[in] aExtraTlvsLength Length of extra TLVs. * * @retval OT_ERROR_NONE Successfully generated an MLE Data Request message. * @retval OT_ERROR_NO_BUFS Insufficient buffers to generate the MLE Data Request message. @@ -1300,7 +1306,9 @@ protected: otError SendDataRequest(const Ip6::Address &aDestination, const uint8_t * aTlvs, uint8_t aTlvsLength, - uint16_t aDelay); + uint16_t aDelay, + const uint8_t * aExtraTlvs = nullptr, + uint8_t aExtraTlvsLength = 0); /** * This method generates an MLE Child Update Request message. diff --git a/src/core/thread/mle_router.cpp b/src/core/thread/mle_router.cpp index c01d1a814..4726c8ada 100644 --- a/src/core/thread/mle_router.cpp +++ b/src/core/thread/mle_router.cpp @@ -2723,7 +2723,7 @@ void MleRouter::HandleDataRequest(const Message & aMessage, tlvs[numTlvs++] = Tlv::kPendingDataset; } - SendDataResponse(aMessageInfo.GetPeerAddr(), tlvs, numTlvs, 0); + SendDataResponse(aMessageInfo.GetPeerAddr(), tlvs, numTlvs, 0, &aMessage); exit: LogProcessError(kTypeDataRequest, error); @@ -3210,8 +3210,11 @@ exit: void MleRouter::SendDataResponse(const Ip6::Address &aDestination, const uint8_t * aTlvs, uint8_t aTlvsLength, - uint16_t aDelay) + uint16_t aDelay, + const Message * aRequestMessage) { + OT_UNUSED_VARIABLE(aRequestMessage); + otError error = OT_ERROR_NONE; Message * message = nullptr; Neighbor *neighbor; @@ -3248,6 +3251,13 @@ void MleRouter::SendDataResponse(const Ip6::Address &aDestination, case Tlv::kPendingDataset: SuccessOrExit(error = AppendPendingDataset(*message)); break; + +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + case Tlv::kLinkMetricsReport: + OT_ASSERT(aRequestMessage != nullptr); + SuccessOrExit(error = Get().AppendLinkMetricsReport(*message, *aRequestMessage)); + break; +#endif } } diff --git a/src/core/thread/mle_router.hpp b/src/core/thread/mle_router.hpp index 93347cd8a..33c712076 100644 --- a/src/core/thread/mle_router.hpp +++ b/src/core/thread/mle_router.hpp @@ -615,7 +615,11 @@ private: const uint8_t * aTlvs, uint8_t aTlvsLength, const Challenge & aChallenge); - void SendDataResponse(const Ip6::Address &aDestination, const uint8_t *aTlvs, uint8_t aTlvsLength, uint16_t aDelay); + void SendDataResponse(const Ip6::Address &aDestination, + const uint8_t * aTlvs, + uint8_t aTlvsLength, + uint16_t aDelay, + const Message * aRequestMessage = nullptr); otError SendDiscoveryResponse(const Ip6::Address &aDestination, uint16_t aPanId); void SetStateRouter(uint16_t aRloc16); diff --git a/src/core/thread/mle_tlvs.hpp b/src/core/thread/mle_tlvs.hpp index 4bdb5e233..30f8d3565 100644 --- a/src/core/thread/mle_tlvs.hpp +++ b/src/core/thread/mle_tlvs.hpp @@ -41,6 +41,7 @@ #include "common/tlvs.hpp" #include "meshcop/timestamp.hpp" #include "net/ip6_address.hpp" +#include "thread/link_metrics_tlvs.hpp" #include "thread/mle_types.hpp" namespace ot { @@ -103,6 +104,8 @@ public: kDiscovery = 26, ///< Thread Discovery TLV kCslChannel = 80, ///< CSL Channel TLV kCslTimeout = 85, ///< CSL Timeout TLV + kLinkMetricsQuery = 87, ///< Link Metrics Query TLV + kLinkMetricsReport = 89, ///< Link Metrics Report TLV /** * Applicable/Required only when time synchronization service diff --git a/src/core/thread/thread_netif.cpp b/src/core/thread/thread_netif.cpp index 63cd652d8..05165e84b 100644 --- a/src/core/thread/thread_netif.cpp +++ b/src/core/thread/thread_netif.cpp @@ -126,6 +126,9 @@ ThreadNetif::ThreadNetif(Instance &aInstance) #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE , mTimeSync(aInstance) #endif +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + , mLinkMetrics(aInstance) +#endif { } diff --git a/src/core/thread/thread_netif.hpp b/src/core/thread/thread_netif.hpp index 23b2507cb..40f8cd0fa 100644 --- a/src/core/thread/thread_netif.hpp +++ b/src/core/thread/thread_netif.hpp @@ -84,6 +84,7 @@ #include "thread/discover_scanner.hpp" #include "thread/energy_scan_server.hpp" #include "thread/key_manager.hpp" +#include "thread/link_metrics.hpp" #include "thread/mesh_forwarder.hpp" #include "thread/mle.hpp" #include "thread/mle_router.hpp" @@ -266,6 +267,9 @@ private: #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE TimeSync mTimeSync; #endif +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + LinkMetrics mLinkMetrics; +#endif }; /** diff --git a/tests/scripts/thread-cert/config.py b/tests/scripts/thread-cert/config.py index b74ef545b..ed720b2b4 100644 --- a/tests/scripts/thread-cert/config.py +++ b/tests/scripts/thread-cert/config.py @@ -257,6 +257,8 @@ def create_default_mle_tlvs_factories(): mle.TlvType.TIME_REQUEST: mle.TimeRequestFactory(), mle.TlvType.TIME_PARAMETER: mle.TimeParameterFactory(), mle.TlvType.THREAD_DISCOVERY: create_default_mle_tlv_thread_discovery_factory(), + mle.TlvType.LINK_METRICS_QUERY: mle.LinkMetricsQueryFactory(), + mle.TlvType.LINK_METRICS_REPORT: mle.LinkMetricsReportFactory(), } diff --git a/tests/scripts/thread-cert/mle.py b/tests/scripts/thread-cert/mle.py index df6aaf101..16981b805 100644 --- a/tests/scripts/thread-cert/mle.py +++ b/tests/scripts/thread-cert/mle.py @@ -89,6 +89,8 @@ class TlvType(IntEnum): THREAD_DISCOVERY = 26 CSL_CHANNEL = 80 CSL_SYNCHRONIZED_TIMEOUT = 85 + LINK_METRICS_QUERY = 87 + LINK_METRICS_REPORT = 89 TIME_REQUEST = 252 TIME_PARAMETER = 253 @@ -1102,6 +1104,32 @@ class TimeParameterFactory: return TimeParameter() +class LinkMetricsQuery: + # TODO: Not implemented yet + + def __init__(self): + print("LinkMetricsQuery is not implemented yet.") + + +class LinkMetricsQueryFactory: + + def parse(self, data, message_info): + return LinkMetricsQuery() + + +class LinkMetricsReport: + # TODO: Not implemented yet + + def __init__(self): + print("LinkMetricsReport is not implemented yet.") + + +class LinkMetricsReportFactory: + + def parse(self, data, message_info): + return LinkMetricsReport() + + class MleCommand(object): def __init__(self, _type, tlvs): diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index 78b391392..c6a19ff1a 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -1866,6 +1866,11 @@ class NodeImpl: return router_table + def link_metrics_query_single_probe(self, dst_addr: str, linkmetrics_flags: str): + cmd = 'linkmetrics query %s single %s' % (dst_addr, linkmetrics_flags) + self.send_command(cmd) + self._expect('Done') + def send_address_notification(self, dst: str, target: str, mliid: str): cmd = f'fake /a/an {dst} {target} {mliid}' self.send_command(cmd) diff --git a/tests/scripts/thread-cert/v1_2_test_single_probe.py b/tests/scripts/thread-cert/v1_2_test_single_probe.py new file mode 100755 index 000000000..8a1139854 --- /dev/null +++ b/tests/scripts/thread-cert/v1_2_test_single_probe.py @@ -0,0 +1,115 @@ +#!/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 + +import mle +import thread_cert + +LEADER = 1 +SSED_1 = 2 + +CSL_PERIOD = 500 * 6.25 # 500ms +CSL_TIMEOUT = 30 # 30s + + +class SSED_SingleProbe(thread_cert.TestCase): + TOPOLOGY = { + LEADER: { + 'version': '1.2', + }, + SSED_1: { + 'version': '1.2', + 'mode': 's', + }, + } + """All nodes are created with default configurations""" + + def test(self): + + self.nodes[SSED_1].set_csl_period(CSL_PERIOD) + self.nodes[SSED_1].set_csl_timeout(CSL_TIMEOUT) + + self.nodes[LEADER].start() + self.simulator.go(5) + self.assertEqual(self.nodes[LEADER].get_state(), 'leader') + + self.nodes[SSED_1].start() + self.simulator.go(7) + self.assertEqual(self.nodes[SSED_1].get_state(), 'child') + + leader_addr = self.nodes[LEADER].get_linklocal() + + leader_messages = self.simulator.get_messages_sent_by(LEADER) + + # SSED_1 sends a Single Probe Link Metrics for L2 PDU count using MLE Data Request + self.nodes[SSED_1].link_metrics_query_single_probe(leader_addr, 'p') + self.simulator.go(5) + + leader_messages = self.simulator.get_messages_sent_by(LEADER) + msg = leader_messages.next_mle_message(mle.CommandType.DATA_RESPONSE) + msg.assertMleMessageContainsTlv(mle.LinkMetricsReport) + + # SSED_1 sends a Single Probe Link Metrics for L2 LQI using MLE Data Request + self.nodes[SSED_1].link_metrics_query_single_probe(leader_addr, 'q') + self.simulator.go(5) + + leader_messages = self.simulator.get_messages_sent_by(LEADER) + msg = leader_messages.next_mle_message(mle.CommandType.DATA_RESPONSE) + msg.assertMleMessageContainsTlv(mle.LinkMetricsReport) + + # SSED_1 sends a Single Probe Link Metrics for Link Margin using MLE Data Request + self.nodes[SSED_1].link_metrics_query_single_probe(leader_addr, 'm') + self.simulator.go(5) + + leader_messages = self.simulator.get_messages_sent_by(LEADER) + msg = leader_messages.next_mle_message(mle.CommandType.DATA_RESPONSE) + msg.assertMleMessageContainsTlv(mle.LinkMetricsReport) + + # SSED_1 sends a Single Probe Link Metrics for Link Margin using MLE Data Request + self.nodes[SSED_1].link_metrics_query_single_probe(leader_addr, 'r') + self.simulator.go(5) + + leader_messages = self.simulator.get_messages_sent_by(LEADER) + msg = leader_messages.next_mle_message(mle.CommandType.DATA_RESPONSE) + msg.assertMleMessageContainsTlv(mle.LinkMetricsReport) + + # SSED_1 sends a Single Probe Link Metrics for all metrics using MLE Data Request + self.nodes[SSED_1].link_metrics_query_single_probe(leader_addr, 'pqmr') + self.simulator.go(5) + + leader_messages = self.simulator.get_messages_sent_by(LEADER) + msg = leader_messages.next_mle_message(mle.CommandType.DATA_RESPONSE) + msg.assertMleMessageContainsTlv(mle.LinkMetricsReport) + + self.assertTrue(self.nodes[LEADER].ping(self.nodes[SSED_1].get_rloc())) + + +if __name__ == '__main__': + unittest.main()