From 8c2abba2f8c52d536dbe3e5e92a1301a0de22d8f Mon Sep 17 00:00:00 2001 From: Li Cao Date: Sat, 2 Sep 2023 23:14:24 +0800 Subject: [PATCH] [link-metrics] implement link metrics manager (#9375) This commit implements a new module LinkMetricsManager, which utilizes the Link Metrics feature to get the Link Metrics data from neighboring devices. The commit also adds a few tests to the module: - Unit Test: tests/unit/test_link_metrics_manager.cpp, will be run in `unit-tests` in `unit.yml`. - Expect Test: tests/scripts/expect/v1_2-linkmetricsmgr.exp, will be run in `expects` in `simulation-1.2.yml`. - Simulation Test: tests/scripts/thread-cert/v1_2_LowPower_test_link_metrics_manager.py, will be run in `packet-verification-low-power` in `simulation-1.2.yml`. --- etc/cmake/options.cmake | 1 + .../config/ot-core-config-check-size-br.h | 1 + .../config/ot-core-config-check-size-ftd.h | 1 + .../config/ot-core-config-check-size-mtd.h | 1 + include/openthread/instance.h | 2 +- include/openthread/link_metrics.h | 25 ++ script/test | 4 + src/cli/README.md | 36 ++ src/cli/cli.cpp | 69 ++++ src/core/BUILD.gn | 3 + src/core/CMakeLists.txt | 1 + src/core/api/link_metrics_api.cpp | 21 +- src/core/common/instance.cpp | 3 + src/core/common/instance.hpp | 9 + src/core/common/notifier.cpp | 3 + src/core/config/link_metrics_manager.h | 58 +++ src/core/openthread-core-config.h | 1 + src/core/thread/mle.hpp | 2 + src/core/utils/link_metrics_manager.cpp | 377 ++++++++++++++++++ src/core/utils/link_metrics_manager.hpp | 223 +++++++++++ tests/scripts/expect/v1_2-linkmetricsmgr.exp | 60 +++ tests/scripts/thread-cert/node.py | 6 + ...v1_2_LowPower_test_link_metrics_manager.py | 271 +++++++++++++ tests/unit/CMakeLists.txt | 21 + tests/unit/test_link_metrics_manager.cpp | 222 +++++++++++ 25 files changed, 1419 insertions(+), 2 deletions(-) create mode 100644 src/core/config/link_metrics_manager.h create mode 100644 src/core/utils/link_metrics_manager.cpp create mode 100644 src/core/utils/link_metrics_manager.hpp create mode 100755 tests/scripts/expect/v1_2-linkmetricsmgr.exp create mode 100755 tests/scripts/thread-cert/v1_2_LowPower_test_link_metrics_manager.py create mode 100644 tests/unit/test_link_metrics_manager.cpp diff --git a/etc/cmake/options.cmake b/etc/cmake/options.cmake index d2c6bfb32..a03af2013 100644 --- a/etc/cmake/options.cmake +++ b/etc/cmake/options.cmake @@ -207,6 +207,7 @@ ot_option(OT_IP6_FRAGM OPENTHREAD_CONFIG_IP6_FRAGMENTATION_ENABLE "ipv6 fragment ot_option(OT_JAM_DETECTION OPENTHREAD_CONFIG_JAM_DETECTION_ENABLE "jam detection") ot_option(OT_JOINER OPENTHREAD_CONFIG_JOINER_ENABLE "joiner") ot_option(OT_LINK_METRICS_INITIATOR OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE "link metrics initiator") +ot_option(OT_LINK_METRICS_MANAGER OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE "link metrics manager") ot_option(OT_LINK_METRICS_SUBJECT OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE "link metrics subject") ot_option(OT_LINK_RAW OPENTHREAD_CONFIG_LINK_RAW_ENABLE "link raw service") ot_option(OT_LOG_LEVEL_DYNAMIC OPENTHREAD_CONFIG_LOG_LEVEL_DYNAMIC_ENABLE "dynamic log level control") diff --git a/examples/config/ot-core-config-check-size-br.h b/examples/config/ot-core-config-check-size-br.h index a86f26f23..8310019f7 100644 --- a/examples/config/ot-core-config-check-size-br.h +++ b/examples/config/ot-core-config-check-size-br.h @@ -66,6 +66,7 @@ #define OPENTHREAD_CONFIG_IP6_SLAAC_ENABLE 1 #define OPENTHREAD_CONFIG_JAM_DETECTION_ENABLE 1 #define OPENTHREAD_CONFIG_JOINER_ENABLE 1 +#define OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE 1 #define OPENTHREAD_CONFIG_LINK_RAW_ENABLE 1 #define OPENTHREAD_CONFIG_LOG_LEVEL OT_LOG_LEVEL_INFO #define OPENTHREAD_CONFIG_LOG_LEVEL_DYNAMIC_ENABLE 1 diff --git a/examples/config/ot-core-config-check-size-ftd.h b/examples/config/ot-core-config-check-size-ftd.h index 12a83754d..63e7ad484 100644 --- a/examples/config/ot-core-config-check-size-ftd.h +++ b/examples/config/ot-core-config-check-size-ftd.h @@ -66,6 +66,7 @@ #define OPENTHREAD_CONFIG_IP6_SLAAC_ENABLE 1 #define OPENTHREAD_CONFIG_JAM_DETECTION_ENABLE 1 #define OPENTHREAD_CONFIG_JOINER_ENABLE 1 +#define OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE 1 #define OPENTHREAD_CONFIG_LINK_RAW_ENABLE 1 #define OPENTHREAD_CONFIG_LOG_LEVEL OT_LOG_LEVEL_INFO #define OPENTHREAD_CONFIG_LOG_LEVEL_DYNAMIC_ENABLE 0 diff --git a/examples/config/ot-core-config-check-size-mtd.h b/examples/config/ot-core-config-check-size-mtd.h index baf958ab1..88b2c1198 100644 --- a/examples/config/ot-core-config-check-size-mtd.h +++ b/examples/config/ot-core-config-check-size-mtd.h @@ -66,6 +66,7 @@ #define OPENTHREAD_CONFIG_IP6_SLAAC_ENABLE 1 #define OPENTHREAD_CONFIG_JAM_DETECTION_ENABLE 0 #define OPENTHREAD_CONFIG_JOINER_ENABLE 1 +#define OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE 0 #define OPENTHREAD_CONFIG_LINK_RAW_ENABLE 1 #define OPENTHREAD_CONFIG_LOG_LEVEL OT_LOG_LEVEL_INFO #define OPENTHREAD_CONFIG_LOG_LEVEL_DYNAMIC_ENABLE 0 diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 5b6bb1e44..5ecea91f9 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 (355) +#define OPENTHREAD_API_VERSION (356) /** * @addtogroup api-instance diff --git a/include/openthread/link_metrics.h b/include/openthread/link_metrics.h index b97805757..3fbe42d66 100644 --- a/include/openthread/link_metrics.h +++ b/include/openthread/link_metrics.h @@ -249,6 +249,31 @@ otError otLinkMetricsSendLinkProbe(otInstance *aInstance, uint8_t aSeriesId, uint8_t aLength); +/** + * Enable or disable Link Metrics Manager. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aEnable A boolean indicating to enable or disable. + * + */ +void otLinkMetricsManagerSetEnabled(otInstance *aInstance, bool aEnable); + +/** + * Get Link Metrics data of a neighbor by its extended address. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aExtAddress A pointer to the Mac extended address of the Probing Subject. + * @param[out] aLinkMetricsValues A pointer to the Link Metrics values of the subject. + * + * @retval OT_ERROR_NONE Successfully got the Link Metrics data. + * @retval OT_ERROR_INVALID_ARGS The arguments are invalid. + * @retval OT_ERROR_NOT_FOUND No neighbor with the given extended address is found. + * + */ +otError otLinkMetricsManagerGetMetricsValueByExtAddr(otInstance *aInstance, + const otExtAddress *aExtAddress, + otLinkMetricsValues *aLinkMetricsValues); + /** * @} * diff --git a/script/test b/script/test index 17cf0478e..3e53df1b2 100755 --- a/script/test +++ b/script/test @@ -129,6 +129,7 @@ build_simulation() options+=("-DOT_CSL_RECEIVER=ON") options+=("-DOT_LINK_METRICS_INITIATOR=ON") options+=("-DOT_LINK_METRICS_SUBJECT=ON") + options+=("-DOT_LINK_METRICS_MANAGER=ON") fi if [[ ${ot_extra_options[*]+x} ]]; then @@ -162,6 +163,9 @@ build_posix() if [[ ${version} != "1.1" ]]; then options+=("-DOT_DUA=ON") options+=("-DOT_MLR=ON") + options+=("-DOT_LINK_METRICS_INITIATOR=ON") + options+=("-DOT_LINK_METRICS_SUBJECT=ON") + options+=("-DOT_LINK_METRICS_MANAGER=ON") fi if [[ ${FULL_LOGS} == 1 ]]; then diff --git a/src/cli/README.md b/src/cli/README.md index 8b2a5005e..2f44e3dc8 100644 --- a/src/cli/README.md +++ b/src/cli/README.md @@ -66,6 +66,7 @@ Done - [leaderdata](#leaderdata) - [leaderweight](#leaderweight) - [linkmetrics](#linkmetrics-mgmt-ipaddr-enhanced-ack-clear) +- [linkmetricsmgr](#linkmetricsmgr-disable) - [locate](#locate) - [log](#log-filename-filename) - [mac](#mac-retries-direct) @@ -1877,6 +1878,41 @@ Done - RSSI: -18 (dBm) (Exponential Moving Average) ``` +### linkmetricsmgr disable + +Disable the Link Metrics Manager. + +`OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE` is required. + +```bash +> linkmetricsmgr disable +Done +``` + +### linkmetricsmgr enable + +Enable the Link Metrics Manager. + +`OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE` is required. + +```bash +> linkmetricsmgr enable +Done +``` + +### linkmetricsmgr show + +Display the Link Metrics data of all subjects. The subjects are identified by its extended address. + +`OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE` is required. + +```bash + +> linkmetricsmgr show +ExtAddr:827aa7f7f63e1234, LinkMargin:80, Rssi:-20 +Done +``` + ### locate Gets the current state (`In Progress` or `Idle`) of anycast locator. diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 4bbb776f0..2565ec191 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -3863,6 +3863,72 @@ exit: return error; } +#if OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE +template <> otError Interpreter::Process(Arg aArgs[]) +{ + otError error = OT_ERROR_NONE; + + VerifyOrExit(!aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + + /** + * @cli linkmetricsmgr (enable,disable) + * @code + * linkmetricmgr enable + * Done + * @endcode + * @code + * linkmetricmgr disable + * Done + * @endcode + * @cparam linkmetricsmgr @ca{enable|disable} + * @par api_copy + * #otLinkMetricsManagerSetEnabled + * + */ + if (ProcessEnableDisable(aArgs, otLinkMetricsManagerSetEnabled) == OT_ERROR_NONE) + { + } + /** + * @cli linkmetricsmgr show + * @code + * linkmetricsmgr show + * ExtAddr:827aa7f7f63e1234, LinkMargin:80, Rssi:-20 + * Done + * @endcode + * @par api_copy + * #otLinkMetricsManagerGetMetricsValueByExtAddr + * + */ + else if (aArgs[0] == "show") + { + otNeighborInfoIterator iterator = OT_NEIGHBOR_INFO_ITERATOR_INIT; + otNeighborInfo neighborInfo; + + while (otThreadGetNextNeighborInfo(GetInstancePtr(), &iterator, &neighborInfo) == OT_ERROR_NONE) + { + otLinkMetricsValues linkMetricsValues; + + if (otLinkMetricsManagerGetMetricsValueByExtAddr(GetInstancePtr(), &neighborInfo.mExtAddress, + &linkMetricsValues) != OT_ERROR_NONE) + { + continue; + } + + OutputFormat("ExtAddr:"); + OutputExtAddress(neighborInfo.mExtAddress); + OutputLine(", LinkMargin:%u, Rssi:%d", linkMetricsValues.mLinkMarginValue, linkMetricsValues.mRssiValue); + } + } + else + { + error = OT_ERROR_INVALID_COMMAND; + } + +exit: + return error; +} +#endif // OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE + otError Interpreter::ParseLinkMetricsFlags(otLinkMetrics &aLinkMetrics, const Arg &aFlags) { otError error = OT_ERROR_NONE; @@ -8352,6 +8418,9 @@ otError Interpreter::ProcessCommand(Arg aArgs[]) #endif #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE CmdEntry("linkmetrics"), +#if OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE + CmdEntry("linkmetricsmgr"), +#endif #endif #if OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE CmdEntry("locate"), diff --git a/src/core/BUILD.gn b/src/core/BUILD.gn index eb07520ce..d44f410fd 100644 --- a/src/core/BUILD.gn +++ b/src/core/BUILD.gn @@ -706,6 +706,8 @@ openthread_core_files = [ "utils/history_tracker.hpp", "utils/jam_detector.cpp", "utils/jam_detector.hpp", + "utils/link_metrics_manager.cpp", + "utils/link_metrics_manager.hpp", "utils/mesh_diag.cpp", "utils/mesh_diag.hpp", "utils/otns.cpp", @@ -792,6 +794,7 @@ source_set("libopenthread_core_config") { "config/history_tracker.h", "config/ip6.h", "config/joiner.h", + "config/link_metrics_manager.h", "config/link_quality.h", "config/link_raw.h", "config/logging.h", diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index e6b86f005..c8e4079ab 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -244,6 +244,7 @@ set(COMMON_SOURCES utils/heap.cpp utils/history_tracker.cpp utils/jam_detector.cpp + utils/link_metrics_manager.cpp utils/mesh_diag.cpp utils/otns.cpp utils/parse_cmdline.cpp diff --git a/src/core/api/link_metrics_api.cpp b/src/core/api/link_metrics_api.cpp index 548876d27..8e678d921 100644 --- a/src/core/api/link_metrics_api.cpp +++ b/src/core/api/link_metrics_api.cpp @@ -54,7 +54,6 @@ otError otLinkMetricsQuery(otInstance *aInstance, AsCoreTypePtr(aLinkMetricsFlags)); } -#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE otError otLinkMetricsConfigForwardTrackingSeries(otInstance *aInstance, const otIp6Address *aDestination, uint8_t aSeriesId, @@ -98,6 +97,26 @@ otError otLinkMetricsSendLinkProbe(otInstance *aInstance, return initiator.SendLinkProbe(AsCoreType(aDestination), aSeriesId, aLength); } + +#if OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE +void otLinkMetricsManagerSetEnabled(otInstance *aInstance, bool aEnable) +{ + AsCoreType(aInstance).Get().SetEnabled(aEnable); +} + +otError otLinkMetricsManagerGetMetricsValueByExtAddr(otInstance *aInstance, + const otExtAddress *aExtAddress, + otLinkMetricsValues *aLinkMetricsValues) +{ + otError error = OT_ERROR_NONE; + + VerifyOrExit(aExtAddress != nullptr && aLinkMetricsValues != nullptr, error = OT_ERROR_INVALID_ARGS); + + error = AsCoreType(aInstance).Get().GetLinkMetricsValueByExtAddr( + AsCoreType(aExtAddress), AsCoreType(aLinkMetricsValues)); +exit: + return error; +} #endif #endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE diff --git a/src/core/common/instance.cpp b/src/core/common/instance.cpp index 10d545d66..6dcc3e892 100644 --- a/src/core/common/instance.cpp +++ b/src/core/common/instance.cpp @@ -216,6 +216,9 @@ Instance::Instance(void) #if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE , mHistoryTracker(*this) #endif +#if OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE + , mLinkMetricsManager(*this) +#endif #if (OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE || OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE) && OPENTHREAD_FTD , mDatasetUpdater(*this) #endif diff --git a/src/core/common/instance.hpp b/src/core/common/instance.hpp index 0a262bab0..d213c692e 100644 --- a/src/core/common/instance.hpp +++ b/src/core/common/instance.hpp @@ -127,6 +127,7 @@ #include "utils/heap.hpp" #include "utils/history_tracker.hpp" #include "utils/jam_detector.hpp" +#include "utils/link_metrics_manager.hpp" #include "utils/mesh_diag.hpp" #include "utils/ping_sender.hpp" #include "utils/slaac_address.hpp" @@ -610,6 +611,10 @@ private: Utils::HistoryTracker mHistoryTracker; #endif +#if OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE + Utils::LinkMetricsManager mLinkMetricsManager; +#endif + #if (OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE || OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE) && OPENTHREAD_FTD MeshCoP::DatasetUpdater mDatasetUpdater; #endif @@ -901,6 +906,10 @@ template <> inline Utils::MeshDiag &Instance::Get(void) { return mMeshDiag; } template <> inline Utils::HistoryTracker &Instance::Get(void) { return mHistoryTracker; } #endif +#if OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE +template <> inline Utils::LinkMetricsManager &Instance::Get(void) { return mLinkMetricsManager; } +#endif + #if (OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE || OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE) && OPENTHREAD_FTD template <> inline MeshCoP::DatasetUpdater &Instance::Get(void) { return mDatasetUpdater; } #endif diff --git a/src/core/common/notifier.cpp b/src/core/common/notifier.cpp index 7f7646f1a..f25e71eb5 100644 --- a/src/core/common/notifier.cpp +++ b/src/core/common/notifier.cpp @@ -186,6 +186,9 @@ void Notifier::EmitEvents(void) // being published (if needed). Get().HandleNotifierEvents(events); #endif +#if OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE + Get().HandleNotifierEvents(events); +#endif for (ExternalCallback &callback : mExternalCallbacks) { diff --git a/src/core/config/link_metrics_manager.h b/src/core/config/link_metrics_manager.h new file mode 100644 index 000000000..96fed1101 --- /dev/null +++ b/src/core/config/link_metrics_manager.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2023, 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 compile-time configurations for Link Metrics Manager. + * + */ + +#ifndef CONFIG_LINK_METRICS_MANAGER_H_ +#define CONFIG_LINK_METRICS_MANAGER_H_ + +/** + * @def OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE + * + * Define as 1 to enable Link Metrics Manager support. + * + */ +#ifndef OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE +#define OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE 0 +#endif + +/** + * @def OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ON_BY_DEFAULT + * + * Define as 1 to make Link Metrics Manager function on by default. + * + */ +#ifndef OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ON_BY_DEFAULT +#define OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ON_BY_DEFAULT 0 +#endif + +#endif // CONFIG_LINK_METRICS_MANAGER_H_ diff --git a/src/core/openthread-core-config.h b/src/core/openthread-core-config.h index 82692bf8a..ea238fb1f 100644 --- a/src/core/openthread-core-config.h +++ b/src/core/openthread-core-config.h @@ -92,6 +92,7 @@ #include "config/history_tracker.h" #include "config/ip6.h" #include "config/joiner.h" +#include "config/link_metrics_manager.h" #include "config/link_quality.h" #include "config/link_raw.h" #include "config/logging.h" diff --git a/src/core/thread/mle.hpp b/src/core/thread/mle.hpp index 5e21a8dff..b33f40df1 100644 --- a/src/core/thread/mle.hpp +++ b/src/core/thread/mle.hpp @@ -75,6 +75,7 @@ namespace ot { */ class SupervisionListener; +class UnitTester; /** * @namespace ot::Mle @@ -111,6 +112,7 @@ class Mle : public InstanceLocator, private NonCopyable #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE friend class ot::LinkMetrics::Initiator; #endif + friend class ot::UnitTester; public: /** diff --git a/src/core/utils/link_metrics_manager.cpp b/src/core/utils/link_metrics_manager.cpp new file mode 100644 index 000000000..bbdac1b5d --- /dev/null +++ b/src/core/utils/link_metrics_manager.cpp @@ -0,0 +1,377 @@ +/* + * Copyright (c) 2023, 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_manager.hpp" + +#if OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE + +#include "common/as_core_type.hpp" +#include "common/error.hpp" +#include "common/locator_getters.hpp" +#include "common/log.hpp" +#include "common/notifier.hpp" +#include "thread/mle.hpp" +#include "thread/neighbor_table.hpp" + +namespace ot { +namespace Utils { + +RegisterLogModule("LinkMetricsMgr"); + +/** + * @addtogroup utils-link-metrics-manager + * + * @brief + * This module includes definitions for Link Metrics Manager. + * + * @{ + */ + +LinkMetricsManager::LinkMetricsManager(Instance &aInstance) + : InstanceLocator(aInstance) + , mTimer(aInstance) + , mEnabled(OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ON_BY_DEFAULT) +{ +} + +void LinkMetricsManager::SetEnabled(bool aEnable) +{ + VerifyOrExit(mEnabled != aEnable); + mEnabled = aEnable; + + if (mEnabled) + { + Start(); + } + else + { + Stop(); + } + +exit: + return; +} + +Error LinkMetricsManager::GetLinkMetricsValueByExtAddr(const Mac::ExtAddress &aExtAddress, + LinkMetrics::MetricsValues &aMetricsValues) +{ + Error error = kErrorNone; + Subject *subject; + + subject = mSubjectList.FindMatching(aExtAddress); + VerifyOrExit(subject != nullptr, error = kErrorNotFound); + + aMetricsValues.mLinkMarginValue = subject->mData.mLinkMargin; + aMetricsValues.mRssiValue = subject->mData.mRssi; + +exit: + return error; +} + +void LinkMetricsManager::Start(void) +{ + LinkMetrics::Initiator &initiator = Get(); + + VerifyOrExit(mEnabled && Get().IsAttached()); + + initiator.SetMgmtResponseCallback(HandleMgmtResponse, this); + initiator.SetEnhAckProbingCallback(HandleEnhAckIe, this); + + mTimer.Start(kTimeBeforeStartMilliSec); +exit: + return; +} + +void LinkMetricsManager::Stop(void) +{ + LinkMetrics::Initiator &initiator = Get(); + + mTimer.Stop(); + + initiator.SetMgmtResponseCallback(nullptr, nullptr); + initiator.SetEnhAckProbingCallback(nullptr, nullptr); + + UnregisterAllSubjects(); + ReleaseAllSubjects(); +} + +void LinkMetricsManager::Update(void) +{ + UpdateSubjects(); + UpdateLinkMetricsStates(); +} + +// This method updates the Link Metrics Subject in the subject list. It adds new neighbors to the list. +void LinkMetricsManager::UpdateSubjects(void) +{ + Neighbor::Info neighborInfo; + otNeighborInfoIterator iterator = OT_NEIGHBOR_INFO_ITERATOR_INIT; + + while (Get().GetNextNeighborInfo(iterator, neighborInfo) == kErrorNone) + { + // if not in the subject list, allocate and add + if (!mSubjectList.ContainsMatching(AsCoreType(&neighborInfo.mExtAddress))) + { + Subject *subject = mPool.Allocate(); + + VerifyOrExit(subject != nullptr); + + subject->Clear(); + subject->mExtAddress = AsCoreType(&neighborInfo.mExtAddress); + IgnoreError(mSubjectList.Add(*subject)); + } + } + +exit: + return; +} + +// This method updates the state and take corresponding actions for all subjects and removes stale subjects. +void LinkMetricsManager::UpdateLinkMetricsStates(void) +{ + LinkedList staleSubjects; + + mSubjectList.RemoveAllMatching(*this, staleSubjects); + + while (!staleSubjects.IsEmpty()) + { + Subject *subject = staleSubjects.Pop(); + + mPool.Free(*subject); + } +} + +void LinkMetricsManager::UnregisterAllSubjects(void) +{ + for (Subject &subject : mSubjectList) + { + IgnoreError(subject.UnregisterEap(GetInstance())); + } +} + +void LinkMetricsManager::ReleaseAllSubjects(void) +{ + while (!mSubjectList.IsEmpty()) + { + Subject *subject = mSubjectList.Pop(); + + mPool.Free(*subject); + } +} + +void LinkMetricsManager::HandleNotifierEvents(Events aEvents) +{ + if (aEvents.Contains(kEventThreadRoleChanged)) + { + if (Get().IsAttached()) + { + Start(); + } + else + { + Stop(); + } + } +} + +void LinkMetricsManager::HandleTimer(void) +{ + if (Get().IsAttached()) + { + Update(); + mTimer.Start(kStateUpdateIntervalMilliSec); + } +} + +void LinkMetricsManager::HandleMgmtResponse(const otIp6Address *aAddress, otLinkMetricsStatus aStatus, void *aContext) +{ + static_cast(aContext)->HandleMgmtResponse(aAddress, aStatus); +} + +void LinkMetricsManager::HandleMgmtResponse(const otIp6Address *aAddress, otLinkMetricsStatus aStatus) +{ + Mac::ExtAddress extAddress; + Subject *subject; + Neighbor *neighbor; + + AsCoreType(aAddress).GetIid().ConvertToExtAddress(extAddress); + neighbor = Get().FindNeighbor(extAddress); + VerifyOrExit(neighbor != nullptr); + + subject = mSubjectList.FindMatching(extAddress); + VerifyOrExit(subject != nullptr); + + switch (MapEnum(aStatus)) + { + case LinkMetrics::Status::kStatusSuccess: + subject->mState = SubjectState::kActive; + break; + default: + subject->mState = SubjectState::kNotConfigured; + break; + } + +exit: + return; +} + +void LinkMetricsManager::HandleEnhAckIe(otShortAddress aShortAddress, + const otExtAddress *aExtAddress, + const otLinkMetricsValues *aMetricsValues, + void *aContext) +{ + static_cast(aContext)->HandleEnhAckIe(aShortAddress, aExtAddress, aMetricsValues); +} + +void LinkMetricsManager::HandleEnhAckIe(otShortAddress aShortAddress, + const otExtAddress *aExtAddress, + const otLinkMetricsValues *aMetricsValues) +{ + OT_UNUSED_VARIABLE(aShortAddress); + + Error error = kErrorNone; + Subject *subject = mSubjectList.FindMatching(AsCoreType(aExtAddress)); + + VerifyOrExit(subject != nullptr, error = kErrorNotFound); + + VerifyOrExit(subject->mState == SubjectState::kActive || subject->mState == SubjectState::kRenewing); + subject->mLastUpdateTime = TimerMilli::GetNow(); + + VerifyOrExit(aMetricsValues->mMetrics.mRssi && aMetricsValues->mMetrics.mLinkMargin, error = kErrorInvalidArgs); + + subject->mData.mRssi = aMetricsValues->mRssiValue; + subject->mData.mLinkMargin = aMetricsValues->mLinkMarginValue; + +exit: + if (error == kErrorInvalidArgs) + { + LogWarn("Metrics received are unexpected!"); + } +} + +// This special Match method is used for "iterating over list while removing some items" +bool LinkMetricsManager::Subject::Matches(const LinkMetricsManager &aLinkMetricsMgr) +{ + Error error = UpdateState(aLinkMetricsMgr.GetInstance()); + + return error == kErrorUnknownNeighbor || error == kErrorNotCapable; +} + +Error LinkMetricsManager::Subject::ConfigureEap(Instance &aInstance) +{ + Error error = kErrorNone; + Neighbor *neighbor = aInstance.Get().FindNeighbor(mExtAddress); + Ip6::Address destination; + LinkMetrics::EnhAckFlags enhAckFlags = LinkMetrics::kEnhAckRegister; + LinkMetrics::Metrics metricsFlags; + + VerifyOrExit(neighbor != nullptr, error = kErrorUnknownNeighbor); + destination.SetToLinkLocalAddress(neighbor->GetExtAddress()); + + metricsFlags.Clear(); + metricsFlags.mLinkMargin = 1; + metricsFlags.mRssi = 1; + error = + aInstance.Get().SendMgmtRequestEnhAckProbing(destination, enhAckFlags, &metricsFlags); + +exit: + if (error == kErrorNone) + { + mState = (mState == SubjectState::kActive) ? SubjectState::kRenewing : SubjectState::kConfiguring; + mAttempts++; + } + return error; +} + +Error LinkMetricsManager::Subject::UnregisterEap(Instance &aInstance) +{ + Error error = kErrorNone; + Neighbor *neighbor = aInstance.Get().FindNeighbor(mExtAddress); + Ip6::Address destination; + LinkMetrics::EnhAckFlags enhAckFlags = LinkMetrics::kEnhAckClear; + + VerifyOrExit(neighbor != nullptr, error = kErrorUnknownNeighbor); + destination.SetToLinkLocalAddress(neighbor->GetExtAddress()); + + error = aInstance.Get().SendMgmtRequestEnhAckProbing(destination, enhAckFlags, nullptr); +exit: + return error; +} + +Error LinkMetricsManager::Subject::UpdateState(Instance &aInstance) +{ + bool shouldConfigure = false; + uint32_t pastTimeMs; + Error error = kErrorNone; + + switch (mState) + { + case kNotConfigured: + case kConfiguring: + case kRenewing: + if (mAttempts >= kConfigureLinkMetricsMaxAttempts) + { + mState = kNotSupported; + } + else + { + shouldConfigure = true; + } + break; + case kActive: + pastTimeMs = TimerMilli::GetNow() - mLastUpdateTime; + if (pastTimeMs >= kStateUpdateIntervalMilliSec) + { + shouldConfigure = true; + } + break; + case kNotSupported: + ExitNow(error = kErrorNotCapable); + break; + default: + break; + } + + if (shouldConfigure) + { + error = ConfigureEap(aInstance); + } + +exit: + return error; +} + +/** + * @} + * + */ + +} // namespace Utils +} // namespace ot + +#endif // OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE diff --git a/src/core/utils/link_metrics_manager.hpp b/src/core/utils/link_metrics_manager.hpp new file mode 100644 index 000000000..9ab9ab331 --- /dev/null +++ b/src/core/utils/link_metrics_manager.hpp @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2023, 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. + */ + +#ifndef LINK_METRICS_MANAGER_HPP_ +#define LINK_METRICS_MANAGER_HPP_ + +#include "openthread-core-config.h" + +#if OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE + +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE == 0 +#error \ + "OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE can only be used when OPENTHREAD_CONFIG_LINK_METRICS_INITIATOR_ENABLE is true" +#endif + +#include + +#include "common/clearable.hpp" +#include "common/linked_list.hpp" +#include "common/locator.hpp" +#include "common/non_copyable.hpp" +#include "common/notifier.hpp" +#include "common/pool.hpp" +#include "common/time.hpp" +#include "common/timer.hpp" +#include "mac/mac_types.hpp" +#include "thread/link_metrics_types.hpp" + +namespace ot { +class UnitTester; +namespace Utils { + +/** + * @addtogroup utils-link-metrics-manager + * + * @brief + * This module includes definitions for Link Metrics Manager. + * + * @{ + */ + +/** + * + * Link Metrics Manager feature utilizes the Enhanced-ACK Based + * Probing (abbreviated as "EAP" below) to get the Link Metrics + * data of neighboring devices. It is a user of the Link Metrics + * feature. + * + * The feature works as follow: + * - Start/Stop + * The switch `OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ON_BY_DEFAULT` + * controls enabling/disabling this feature by default. The feature + * will only start to work after it joins a Thread network. + * + * A CLI interface is provided to enable/disable this feature. + * + * Once enabled, it will regularly check current neighbors (all + * devices in the neighbor table, including 'Child' and 'Router') + * and configure the probing with them if haven't done that. + * If disabled, it will clear the configuration with its subjects + * and the local data. + * + * - Maintenance + * The manager will regularly check the status of each subject. If + * it finds that the link metrics data for one subject hasn't been + * updated for `kStateUpdateIntervalMilliSec`, it will configure + * EAP with the subject again. + * The manager may find that some subject (neighbor) no longer + * exist when trying to configure EAP. It will remove the stale + * subject then. + * + * - Show data + * An OT API is provided to get the link metrics data of any + * subject (neighbor) by its extended address. In production, this + * data may be fetched by some other means like RPC. + * + */ + +class LinkMetricsManager : public InstanceLocator, private NonCopyable +{ + friend class ot::Notifier; + friend class ot::UnitTester; + + struct LinkMetricsData + { + uint8_t mLqi; ///< Link Quality Indicator. Value range: [0, 255]. + int8_t mRssi; ///< Receive Signal Strength Indicator. Value range: [-128, 0]. + uint8_t mLinkMargin; ///< Link Margin. The relative signal strength is recorded as + ///< db above the local noise floor. Value range: [0, 130]. + }; + + enum SubjectState : uint8_t + { + kNotConfigured = 0, + kConfiguring, + kActive, + kRenewing, + kNotSupported, + }; + + struct Subject : LinkedListEntry, Clearable + { + Mac::ExtAddress mExtAddress; ///< Use the extended address to identify the neighbor. + SubjectState mState; ///< Current State of the Subject + uint8_t mAttempts; ///< The count of attempt that has been made to + ///< configure EAP + TimeMilli mLastUpdateTime; ///< The time `mData` was updated last time + LinkMetricsData mData; + + Subject *mNext; + + bool Matches(const Mac::ExtAddress &aExtAddress) const { return mExtAddress == aExtAddress; } + bool Matches(const LinkMetricsManager &aLinkMetricsMgr); + + Error ConfigureEap(Instance &aInstance); + Error UnregisterEap(Instance &aInstance); + Error UpdateState(Instance &aInstance); + }; + +public: + /** + * Initializes a `LinkMetricsManager` object. + * + * @param[in] aInstance A reference to the OpenThread instance. + * + */ + explicit LinkMetricsManager(Instance &aInstance); + + /** + * Enable/Disable the LinkMetricsManager feature. + * + * @param[in] aEnable A boolean to indicate enable or disable. + * + */ + void SetEnabled(bool aEnable); + + /** + * Get Link Metrics data of subject by the extended address. + * + * @param[in] aExtAddress A reference to the extended address of the subject. + * @param[out] aMetricsValues A reference to the MetricsValues object to place the result. + * + * @retval kErrorNone Successfully got the metrics value. + * @retval kErrorInvalidArgs The arguments are invalid. + * @retval kNotFound No neighbor with the given extended address is found. + * + */ + Error GetLinkMetricsValueByExtAddr(const Mac::ExtAddress &aExtAddress, LinkMetrics::MetricsValues &aMetricsValues); + +private: + static constexpr uint16_t kTimeBeforeStartMilliSec = 5000; + static constexpr uint32_t kStateUpdateIntervalMilliSec = 150000; + static constexpr uint8_t kConfigureLinkMetricsMaxAttempts = 3; +#if OPENTHREAD_FTD + static constexpr uint8_t kMaximumSubjectToTrack = 128; +#elif OPENTHREAD_MTD + static constexpr uint8_t kMaximumSubjectToTrack = 1; +#endif + + void Start(void); + void Stop(void); + void Update(void); + void UpdateSubjects(void); + void UpdateLinkMetricsStates(void); + void UnregisterAllSubjects(void); + void ReleaseAllSubjects(void); + + void HandleNotifierEvents(Events aEvents); + void HandleTimer(void); + static void HandleMgmtResponse(const otIp6Address *aAddress, otLinkMetricsStatus aStatus, void *aContext); + void HandleMgmtResponse(const otIp6Address *aAddress, otLinkMetricsStatus aStatus); + static void HandleEnhAckIe(otShortAddress aShortAddress, + const otExtAddress *aExtAddress, + const otLinkMetricsValues *aMetricsValues, + void *aContext); + void HandleEnhAckIe(otShortAddress aShortAddress, + const otExtAddress *aExtAddress, + const otLinkMetricsValues *aMetricsValues); + + using LinkMetricsMgrTimer = TimerMilliIn; + + Pool mPool; + LinkedList mSubjectList; + LinkMetricsMgrTimer mTimer; + bool mEnabled; +}; + +/** + * @} + * + */ + +} // namespace Utils +} // namespace ot + +#endif // OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE + +#endif // LINK_METRICS_MANAGER_HPP_ diff --git a/tests/scripts/expect/v1_2-linkmetricsmgr.exp b/tests/scripts/expect/v1_2-linkmetricsmgr.exp new file mode 100755 index 000000000..4746e893e --- /dev/null +++ b/tests/scripts/expect/v1_2-linkmetricsmgr.exp @@ -0,0 +1,60 @@ +#!/usr/bin/expect -f +# +# Copyright (c) 2023, 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. +# + +source "tests/scripts/expect/_common.exp" +source "tests/scripts/expect/_multinode.exp" + +setup_two_nodes + +switch_node 1 +send "linkmetricsmgr enable\n" +expect_line "Done" + +sleep 10 + +switch_node 2 +set addr [get_ipaddr mleid] + +switch_node 1 +send "ping $addr\n" +expect "16 bytes from $addr: icmp_seq=1" +send "linkmetricsmgr show\n" +expect -re {ExtAddr:([0-9a-f]){16}, LinkMargin:\d+, Rssi:\-?\d+} +expect "Done" + +sleep 5 + +send "linkmetricsmgr disable\n" +expect_line "Done" +send "linkmetricsmgr show\n" +expect_line "Done" +send "linkmetricsmgr xxx\n" +expect_line "InvalidCommand" + +dispose_all diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index 81e86576a..7b794ca23 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -3257,6 +3257,12 @@ class NodeImpl: self.send_command(cmd) self._expect_done() + def link_metrics_mgr_set_enabled(self, enable: bool): + op_str = "enable" if enable else "disable" + cmd = f'linkmetricsmgr {op_str}' + 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_LowPower_test_link_metrics_manager.py b/tests/scripts/thread-cert/v1_2_LowPower_test_link_metrics_manager.py new file mode 100755 index 000000000..2b7977c4a --- /dev/null +++ b/tests/scripts/thread-cert/v1_2_LowPower_test_link_metrics_manager.py @@ -0,0 +1,271 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2023, 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 mle import LinkMetricsSubTlvType, TlvType +from pktverify import consts +from pktverify.null_field import nullField +from pktverify.packet_verifier import PacketVerifier + +import config +import thread_cert + +LEADER = 1 +ROUTER = 2 +CHILD = 3 +""" +Acronyms: +- EAP - Enhanced-ACK Based Probing + +Test Process: +1. Initiate a leader and a router at the beginning. +2. Enable Link Metrics Manager on leader. +3. Wait a few seconds. + - At this moment, leader should have configured EAP successfully at router. +4. Instruct the leader ping the router. + - Enhanced ACK sent by router should have Thread IE containing Link Metrics data. +5. Add a child into the network. The child should attach to the leader. +6. Wait 150 seconds. + - At this moment, leader should have configured EAP successfully at the child. +7. Instruct both the router and the child ping the leader. + - Enhanced ACK sent by router and child should have Thread IE containing Link Metrics data. +8. Shutdown the child. +9. Wait 300 seconds. + - The leader should not send a Link Management Request to the child. + - The leader should have sent a Link Management Request to the router and get a response. +10. Disable Link Metrics Manager on leader. + - The leader shonld send a Link Management Request to the router to unregister EAP. +""" + + +class LowPower_test_LinkMetricsManager(thread_cert.TestCase): + TOPOLOGY = { + LEADER: { + 'version': '1.2', + 'name': 'LEADER', + 'mode': 'rdn', + 'allowlist': [ROUTER, CHILD], + }, + ROUTER: { + 'version': '1.2', + 'name': 'ROUTER', + 'mode': 'rdn', + 'allowlist': [LEADER], + }, + CHILD: { + 'version': '1.2', + 'name': 'CHILD', + 'mode': 'r', + 'allowlist': [LEADER], + } + } + """All nodes are created with default configurations""" + + def test(self): + self.nodes[LEADER].start() + self.simulator.go(config.LEADER_STARTUP_DELAY) + self.assertEqual(self.nodes[LEADER].get_state(), 'leader') + + self.nodes[ROUTER].start() + self.simulator.go(config.ROUTER_STARTUP_DELAY) + self.assertEqual(self.nodes[ROUTER].get_state(), 'router') + + leader_addr = self.nodes[LEADER].get_ip6_address(ADDRESS_TYPE.LINK_LOCAL) + router_addr = self.nodes[ROUTER].get_ip6_address(ADDRESS_TYPE.LINK_LOCAL) + + # Step 2 - Enable Link Metrics Manager on leader. + self.nodes[LEADER].link_metrics_mgr_set_enabled(True) + + self.simulator.go(10) + + # Step 4 - Instruct the leader ping the router. + self.nodes[LEADER].ping(router_addr) + + # Step 5 - Add a child into the network. + self.nodes[CHILD].start() + self.simulator.go(10) + self.assertEqual(self.nodes[CHILD].get_state(), 'child') + child_addr = self.nodes[CHILD].get_ip6_address(ADDRESS_TYPE.LINK_LOCAL) + + # Step 6 - Wait the leader to configure EAP at the child. + self.simulator.go(150) + + # Step 7 - Instruct both the router and the child ping the leader. + self.nodes[LEADER].ping(router_addr) + self.nodes[LEADER].ping(child_addr) + + # Step 8 - Shutdown the child. + self.nodes[CHILD].stop() + + # Step 9 - Wait the leader to refresh EAP. + self.simulator.go(300) + + # Step 10 - Disable Link Metrics Manager on leader. + self.nodes[LEADER].link_metrics_mgr_set_enabled(False) + + def verify(self, pv): + pkts = pv.pkts + pv.summary.show() + LEADER = pv.vars['LEADER'] + ROUTER = pv.vars['ROUTER'] + CHILD = pv.vars['CHILD'] + + # Step 3 - Leader enables IEEE 802.15.4-2015 Enhanced ACK based Probing by sending a Link Metrics Management + # Request to the Router + # 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(LEADER) \ + .filter_wpan_dst64(ROUTER) \ + .filter_mle_cmd(consts.MLE_LINK_METRICS_MANAGEMENT_REQUEST) \ + .filter(lambda p: consts.LM_ENHANCED_ACK_CONFIGURATION_SUB_TLV in p.mle.tlv.link_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 3 - The Router MUST send a Link Metrics Management Response to Leader containing the following TLVs: + # - MLE LInk Metrics Management TLV + # -- Link Metrics Status Sub-TLV = 0 (Success) + pkts.filter_wpan_src64(ROUTER) \ + .filter_wpan_dst64(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 4 - The Enhanced Ack sent by both Router should contain Thread specific IE + pkt = pkts.filter_wpan_src64(LEADER) \ + .filter_wpan_dst64(ROUTER) \ + .filter_ping_request() \ + .must_next() + ack_seq_no = pkt.wpan.seq_no + 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 6 - Leader enables IEEE 802.15.4-2015 Enhanced ACK based Probing by sending a Link Metrics Management + # Request to the Child + # 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(LEADER) \ + .filter_wpan_dst64(CHILD) \ + .filter_mle_cmd(consts.MLE_LINK_METRICS_MANAGEMENT_REQUEST) \ + .filter(lambda p: consts.LM_ENHANCED_ACK_CONFIGURATION_SUB_TLV in p.mle.tlv.link_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 6 - The Child MUST send a Link Metrics Management Response to Leader containing the following TLVs: + # - MLE LInk Metrics Management TLV + # -- Link Metrics Status Sub-TLV = 0 (Success) + pkts.filter_wpan_src64(CHILD) \ + .filter_wpan_dst64(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 7 - The Enhanced Ack sent by both Router and Child should contain Thread specific IE + pkt1 = pkts.filter_wpan_src64(LEADER) \ + .filter_wpan_dst64(ROUTER) \ + .filter_ping_request() \ + .must_next() + ack_seq_no = pkt1.wpan.seq_no + 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() + + pkt2 = pkts.filter_wpan_src64(LEADER) \ + .filter_wpan_dst64(CHILD) \ + .filter_ping_request() \ + .must_next() + ack_seq_no = pkt2.wpan.seq_no + 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 9 - Leader refreshes IEEE 802.15.4-2015 Enhanced ACK based Probing by sending a Link Metrics Management + # Request to the Router + # 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(LEADER) \ + .filter_wpan_dst64(ROUTER) \ + .filter_mle_cmd(consts.MLE_LINK_METRICS_MANAGEMENT_REQUEST) \ + .filter(lambda p: consts.LM_ENHANCED_ACK_CONFIGURATION_SUB_TLV in p.mle.tlv.link_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 9 - The Router MUST send a Link Metrics Management Response to Leader containing the following TLVs: + # - MLE LInk Metrics Management TLV + # -- Link Metrics Status Sub-TLV = 0 (Success) + pkts.filter_wpan_src64(ROUTER) \ + .filter_wpan_dst64(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 10 - Leader unregisters IEEE 802.15.4-2015 Enhanced ACK based Probing by sending a Link Metrics Management + # Request to the Router + # MLE Link Metrics Management TLV Payload: + # - Enhanced ACK Configuration Sub-TLV + # -- Enh-ACK Flags = 0 (unregister a configuration) + pkts.filter_wpan_src64(LEADER) \ + .filter_wpan_dst64(ROUTER) \ + .filter_mle_cmd(consts.MLE_LINK_METRICS_MANAGEMENT_REQUEST) \ + .filter(lambda p: consts.LM_ENHANCED_ACK_CONFIGURATION_SUB_TLV in p.mle.tlv.link_sub_tlv) \ + .filter(lambda p: p.mle.tlv.link_enh_ack_flags == consts.LINK_METRICS_ENH_ACK_PROBING_CLEAR) \ + .must_next() + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 51f4b0f6a..19465a96f 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -538,6 +538,27 @@ target_link_libraries(ot-test-ip-address add_test(NAME ot-test-ip-address COMMAND ot-test-ip-address) +add_executable(ot-test-link-metrics-manager + test_link_metrics_manager.cpp +) + +target_include_directories(ot-test-link-metrics-manager + PRIVATE + ${COMMON_INCLUDES} +) + +target_compile_options(ot-test-link-metrics-manager + PRIVATE + ${COMMON_COMPILE_OPTIONS} +) + +target_link_libraries(ot-test-link-metrics-manager + PRIVATE + ${COMMON_LIBS} +) + +add_test(NAME ot-test-link-metrics-manager COMMAND ot-test-link-metrics-manager) + add_executable(ot-test-link-quality test_link_quality.cpp ) diff --git a/tests/unit/test_link_metrics_manager.cpp b/tests/unit/test_link_metrics_manager.cpp new file mode 100644 index 000000000..4125680be --- /dev/null +++ b/tests/unit/test_link_metrics_manager.cpp @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2023, 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 "test_platform.h" + +#include + +#include "test_util.h" +#include "common/array.hpp" +#include "common/code_utils.hpp" +#include "common/instance.hpp" +#include "mac/mac_types.hpp" +#include "thread/child_table.hpp" + +namespace ot { + +static ot::Instance *sInstance; + +#if OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE + +using namespace Utils; + +static uint32_t sNow = 10000; + +extern "C" { + +uint32_t otPlatAlarmMilliGetNow(void) { return sNow; } + +} // extern "C" + +struct TestChild +{ + Child::State mState; + otExtAddress mExtAddress; + uint16_t mThreadVersion; +}; + +class UnitTester +{ +public: + static void TestLinkMetricsManager(void); + +private: + static void SetTestLinkMetricsValues(otLinkMetricsValues &aLinkMetricsValues, + uint8_t aLinkMarginValue, + int8_t aRssiValue); + + static const TestChild mTestChildList[]; +}; + +const TestChild UnitTester::mTestChildList[] = { + { + Child::kStateValid, + {{0x10, 0x20, 0x03, 0x15, 0x10, 0x00, 0x60, 0x16}}, + ot::kThreadVersion1p2, + }, + { + Child::kStateValid, + {{0x10, 0x20, 0x03, 0x15, 0x10, 0x00, 0x60, 0x17}}, + ot::kThreadVersion1p2, + }, + { + Child::kStateParentRequest, + {{0x10, 0x20, 0x03, 0x15, 0x10, 0x00, 0x60, 0x18}}, + ot::kThreadVersion1p2, + }, +}; + +void UnitTester::SetTestLinkMetricsValues(otLinkMetricsValues &aLinkMetricsValues, + uint8_t aLinkMarginValue, + int8_t aRssiValue) +{ + aLinkMetricsValues.mMetrics.mLinkMargin = true; + aLinkMetricsValues.mMetrics.mRssi = true; + + aLinkMetricsValues.mLinkMarginValue = aLinkMarginValue; + aLinkMetricsValues.mRssiValue = aRssiValue; +} + +void UnitTester::TestLinkMetricsManager(void) +{ + ChildTable *childTable; + const uint16_t testListLength = GetArrayLength(mTestChildList); + Ip6::Address linkLocalAddr; + LinkMetricsManager::Subject *subject1 = nullptr; + LinkMetricsManager::Subject *subject2 = nullptr; + LinkMetricsManager *linkMetricsMgr = nullptr; + otLinkMetricsValues linkMetricsValues; + otShortAddress anyShortAddress = 0x1234; + + sInstance = testInitInstance(); + VerifyOrQuit(sInstance != nullptr); + + childTable = &sInstance->Get(); + + sInstance->Get().SetRole(Mle::kRoleRouter); + // Add the child entries from test list + for (uint16_t i = 0; i < testListLength; i++) + { + Child *child; + + child = childTable->GetNewChild(); + VerifyOrQuit(child != nullptr, "GetNewChild() failed"); + + child->SetState(mTestChildList[i].mState); + child->SetExtAddress((AsCoreType(&mTestChildList[i].mExtAddress))); + child->SetVersion(mTestChildList[i].mThreadVersion); + } + + linkMetricsMgr = &sInstance->Get(); + linkMetricsMgr->SetEnabled(true); + // Update the subjects for the first time. + linkMetricsMgr->UpdateSubjects(); + + // Expect there are 2 subjects in NotConfigured state. + { + uint16_t index = 0; + for (LinkMetricsManager::Subject &subject : linkMetricsMgr->mSubjectList) + { + VerifyOrQuit(subject.mExtAddress == AsCoreType(&mTestChildList[1 - index].mExtAddress)); + VerifyOrQuit(subject.mState == LinkMetricsManager::SubjectState::kNotConfigured); + index++; + } + } + subject1 = linkMetricsMgr->mSubjectList.FindMatching(AsCoreType(&mTestChildList[0].mExtAddress)); + subject2 = linkMetricsMgr->mSubjectList.FindMatching(AsCoreType(&mTestChildList[1].mExtAddress)); + VerifyOrQuit(subject1 != nullptr); + VerifyOrQuit(subject2 != nullptr); + + // Update the state of the subjects + linkMetricsMgr->UpdateLinkMetricsStates(); + // Expect the 2 subjects are in Configuring state. + for (LinkMetricsManager::Subject &subject : linkMetricsMgr->mSubjectList) + { + VerifyOrQuit(subject.mState == LinkMetricsManager::SubjectState::kConfiguring); + } + + // subject1 received a response with a success status code + linkLocalAddr.SetToLinkLocalAddress(AsCoreType(&mTestChildList[0].mExtAddress)); + linkMetricsMgr->HandleMgmtResponse(&linkLocalAddr, MapEnum(LinkMetrics::Status::kStatusSuccess)); + VerifyOrQuit(subject1->mState == LinkMetricsManager::SubjectState::kActive); + + // subject1 received Enhanced ACK IE and updated the link metrics data + { + constexpr uint8_t kTestLinkMargin = 100; + constexpr int8_t kTestRssi = -30; + SetTestLinkMetricsValues(linkMetricsValues, kTestLinkMargin, kTestRssi); + linkMetricsMgr->HandleEnhAckIe(anyShortAddress, &mTestChildList[0].mExtAddress, &linkMetricsValues); + VerifyOrQuit(subject1->mData.mLinkMargin == kTestLinkMargin); + VerifyOrQuit(subject1->mData.mRssi == kTestRssi); + } + + // subject2 didn't receive any responses after a few attempts and marked as UnSupported. + for (uint8_t i = 0; i < LinkMetricsManager::kConfigureLinkMetricsMaxAttempts; i++) + { + linkMetricsMgr->Update(); + } + VerifyOrQuit(subject2->mState == LinkMetricsManager::SubjectState::kNotSupported); + + // neighbor2 is removed and subject2 should also be removed. + Child *child2 = childTable->FindChild(subject2->mExtAddress, Child::kInStateValid); + child2->SetState(Child::kStateInvalid); + linkMetricsMgr->Update(); + subject2 = linkMetricsMgr->mSubjectList.FindMatching(AsCoreType(&mTestChildList[1].mExtAddress)); + VerifyOrQuit(subject2 == nullptr); + + // subject1 still existed + subject1 = linkMetricsMgr->mSubjectList.FindMatching(AsCoreType(&mTestChildList[0].mExtAddress)); + VerifyOrQuit(subject1 != nullptr); + + // Make subject1 renew + sNow += LinkMetricsManager::kStateUpdateIntervalMilliSec + 1; + linkMetricsMgr->Update(); + VerifyOrQuit(subject1->mState == LinkMetricsManager::SubjectState::kRenewing); + + // subject1 got Enh-ACK IE when in kRenewing state + sNow += 1; + linkMetricsMgr->HandleEnhAckIe(anyShortAddress, &mTestChildList[0].mExtAddress, &linkMetricsValues); + VerifyOrQuit(subject1->mLastUpdateTime == TimeMilli(sNow)); + + // subject1 got response and become active again + linkMetricsMgr->HandleMgmtResponse(&linkLocalAddr, MapEnum(LinkMetrics::Status::kStatusSuccess)); + VerifyOrQuit(subject1->mState == LinkMetricsManager::SubjectState::kActive); +} + +#endif // OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE + +} // namespace ot + +int main(void) +{ +#if OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE + ot::UnitTester::TestLinkMetricsManager(); +#endif + printf("\nAll tests passed.\n"); + return 0; +}