From b8335e0772e9e3557aef693e8fef02ab297a3c26 Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Tue, 21 Mar 2023 22:51:49 -0700 Subject: [PATCH] [mle] add `otDeviceProperties` to calculate local leader weight (#8670) This commit adds `otDeviceProperties` which represents a set of device properties which are used to calculate and set the local Leader Weight on the device. The device property set contains a `otPowerSupplyConfig` enum value specifying the device power supply: - Battery powered. - Externally powered (mains-powered). - Stable external power with a battery backup or UPS. - Unstable external power (e.g., a light bulb powered via a switch). It also indicates whether or not device is a border router, supports CCM, and specifies a Leader Weight adjustment value. The `otDeviceProperties` can be set through the newly added OT public API `otThreadSetDeviceProperties()`. Its default value (upon OT stack start) can also be configured using OT configs: - Newly added `OPENTHREAD_CONFIG_DEVICE_POWER_SUPPLY` specifies the default power supply config to use. This config can also be set using the newly added CMake option `OT_POWER_SUPPLY`. - Existing `OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE` will indicate if device is acting as a BR. This commit also adds CLI command `deviceprops` for getting/setting the device config. It also adds a unit test to validate the Leader Weight calculation algorithm from a given device config. --- etc/cmake/options.cmake | 17 +++ include/openthread/instance.h | 2 +- include/openthread/thread_ftd.h | 57 ++++++++++ src/cli/README.md | 39 +++++++ src/cli/cli.cpp | 103 ++++++++++++++++++ src/core/api/thread_ftd_api.cpp | 10 ++ src/core/config/misc.h | 13 +++ src/core/config/mle.h | 12 +++ src/core/thread/mle_router.cpp | 9 +- src/core/thread/mle_router.hpp | 21 ++++ src/core/thread/mle_types.cpp | 76 +++++++++++++ src/core/thread/mle_types.hpp | 69 +++++++++++- tests/unit/CMakeLists.txt | 21 ++++ tests/unit/test_mle.cpp | 185 ++++++++++++++++++++++++++++++++ 14 files changed, 631 insertions(+), 3 deletions(-) create mode 100644 tests/unit/test_mle.cpp diff --git a/etc/cmake/options.cmake b/etc/cmake/options.cmake index 7ed2d73bd..7bbe4ebc8 100644 --- a/etc/cmake/options.cmake +++ b/etc/cmake/options.cmake @@ -157,6 +157,23 @@ if(OT_FULL_LOGS) target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_LOG_PREPEND_LEVEL=1") endif() +set(OT_POWER_SUPPLY "" CACHE STRING "set the device power supply config") +set(OT_POWER_SUPPLY_VALUES + "" + "BATTERY" + "EXTERNAL" + "EXTERNAL_STABLE" + "EXTERNAL_UNSTABLE" +) +set_property(CACHE OT_POWER_SUPPLY PROPERTY STRINGS ${OT_POWER_SUPPLY_VALUES}) +string(COMPARE EQUAL "${OT_POWER_SUPPLY}" "" is_empty) +if (is_empty) + message(STATUS "OT_POWER_SUPPLY=\"\"") +else() + message(STATUS "OT_POWER_SUPPLY=${OT_POWER_SUPPLY} --> OPENTHREAD_CONFIG_DEVICE_POWER_SUPPLY=OT_POWER_SUPPLY_${OT_POWER_SUPPLY}") + target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_DEVICE_POWER_SUPPLY=OT_POWER_SUPPLY_${OT_POWER_SUPPLY}") +endif() + set(OT_MLE_MAX_CHILDREN "" CACHE STRING "set maximum number of children") if(OT_MLE_MAX_CHILDREN MATCHES "^[0-9]+$") message(STATUS "OT_MLE_MAX_CHILDREN=${OT_MLE_MAX_CHILDREN}") diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 9a93e48ec..6202f12bc 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 (302) +#define OPENTHREAD_API_VERSION (303) /** * @addtogroup api-instance diff --git a/include/openthread/thread_ftd.h b/include/openthread/thread_ftd.h index 37cd9fc50..0778a0a19 100644 --- a/include/openthread/thread_ftd.h +++ b/include/openthread/thread_ftd.h @@ -196,6 +196,59 @@ otError otThreadSetRouterEligible(otInstance *aInstance, bool aEligible); */ otError otThreadSetPreferredRouterId(otInstance *aInstance, uint8_t aRouterId); +/** + * This enumeration represents the power supply property on a device. + * + * This is used as a property in `otDeviceProperties` to calculate the leader weight. + * + */ +typedef enum +{ + OT_POWER_SUPPLY_BATTERY = 0, ///< Battery powered. + OT_POWER_SUPPLY_EXTERNAL = 1, ///< Externally powered (mains-powered). + OT_POWER_SUPPLY_EXTERNAL_STABLE = 2, ///< Stable external power with a battery backup or UPS. + OT_POWER_SUPPLY_EXTERNAL_UNSTABLE = 3, ///< Potentially unstable ext power (e.g. light bulb powered via a switch). +} otPowerSupply; + +/** + * This structure represents the device properties which are used for calculating the local leader weight on a + * device. + * + * The parameters are set based on device's capability, whether acting as border router, its power supply config, etc. + * + * `mIsUnstable` indicates operational stability of device and is determined via a vendor specific mechanism. It can + * include the following cases: + * - Device internally detects that it loses external power supply more often than usual. What is usual is + * determined by the vendor. + * - Device internally detects that it reboots more often than usual. What is usual is determined by the vendor. + * + */ +typedef struct otDeviceProperties +{ + otPowerSupply mPowerSupply; ///< Power supply config. + bool mIsBorderRouter : 1; ///< Whether device is a border router. + bool mSupportsCcm : 1; ///< Whether device supports CCM (can act as a CCM border router). + bool mIsUnstable : 1; ///< Operational stability of device (vendor specific). + int8_t mLeaderWeightAdjustment; ///< Weight adjustment. Should be -16 to +16 (clamped otherwise). +} otDeviceProperties; + +/** + * Get the current device properties. + * + * @returns The device properties `otDeviceProperties`. + * + */ +const otDeviceProperties *otThreadGetDeviceProperties(otInstance *aInstance); + +/** + * Set the device properties which are then used to determine and set the Leader Weight. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aDeviceProperties The device properties. + * + */ +void otThreadSetDeviceProperties(otInstance *aInstance, const otDeviceProperties *aDeviceProperties); + /** * Gets the Thread Leader Weight used when operating in the Leader role. * @@ -204,6 +257,7 @@ otError otThreadSetPreferredRouterId(otInstance *aInstance, uint8_t aRouterId); * @returns The Thread Leader Weight value. * * @sa otThreadSetLeaderWeight + * @sa otThreadSetDeviceProperties * */ uint8_t otThreadGetLocalLeaderWeight(otInstance *aInstance); @@ -211,6 +265,9 @@ uint8_t otThreadGetLocalLeaderWeight(otInstance *aInstance); /** * Sets the Thread Leader Weight used when operating in the Leader role. * + * This function directly sets the Leader Weight to the new value, replacing its previous value (which may have been + * determined from the current `otDeviceProperties`). + * * @param[in] aInstance A pointer to an OpenThread instance. * @param[in] aWeight The Thread Leader Weight value. * diff --git a/src/cli/README.md b/src/cli/README.md index f3483c1af..6d8369fd2 100644 --- a/src/cli/README.md +++ b/src/cli/README.md @@ -41,6 +41,7 @@ Done - [csl](#csl) - [dataset](README_DATASET.md) - [delaytimermin](#delaytimermin) +- [deviceprops](#deviceprops) - [diag](#diag) - [discover](#discover-channel) - [dns](#dns-config) @@ -1056,6 +1057,44 @@ Set the minimal delay timer (in seconds). Done ``` +### deviceprops + +Get the current device properties. + +```bash +> deviceprops +PowerSupply : external +IsBorderRouter : yes +SupportsCcm : no +IsUnstable : no +WeightAdjustment : 0 +Done +``` + +### deviceprops \ \ \ \ \ + +Set the device properties which are then used to determine and set the Leader Weight. + +- power-supply: `battery`, `external`, `external-stable`, or `external-unstable`. +- weight-adjustment: Valid range is from -16 to +16. Clamped if not within the range. + +```bash +> deviceprops battery 0 0 0 -5 +Done + +> deviceprops +PowerSupply : battery +IsBorderRouter : no +SupportsCcm : no +IsUnstable : no +WeightAdjustment : -5 +Done + +> leaderweight +51 +Done +``` + ### discover \[channel\] Perform an MLE Discovery operation. diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 7054b9d33..57e6d66ad 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -4290,6 +4290,106 @@ template <> otError Interpreter::Process(Arg aArgs[]) */ return ProcessGetSet(aArgs, otThreadGetLocalLeaderWeight, otThreadSetLocalLeaderWeight); } + +template <> otError Interpreter::Process(Arg aArgs[]) +{ + static const char *const kPowerSupplyStrings[4] = { + "battery", // (0) OT_POWER_SUPPLY_BATTERY + "external", // (1) OT_POWER_SUPPLY_EXTERNAL + "external-stable", // (2) OT_POWER_SUPPLY_EXTERNAL_STABLE + "external-unstable", // (3) OT_POWER_SUPPLY_EXTERNAL_UNSTABLE + }; + + static_assert(0 == OT_POWER_SUPPLY_BATTERY, "OT_POWER_SUPPLY_BATTERY value is incorrect"); + static_assert(1 == OT_POWER_SUPPLY_EXTERNAL, "OT_POWER_SUPPLY_EXTERNAL value is incorrect"); + static_assert(2 == OT_POWER_SUPPLY_EXTERNAL_STABLE, "OT_POWER_SUPPLY_EXTERNAL_STABLE value is incorrect"); + static_assert(3 == OT_POWER_SUPPLY_EXTERNAL_UNSTABLE, "OT_POWER_SUPPLY_EXTERNAL_UNSTABLE value is incorrect"); + + otError error = OT_ERROR_NONE; + + /** + * @cli deviceprops + * @code + * deviceprops + * PowerSupply : external + * IsBorderRouter : yes + * SupportsCcm : no + * IsUnstable : no + * WeightAdjustment : 0 + * Done + * @endcode + * @par api_copy + * #otThreadGetDeviceProperties + */ + if (aArgs[0].IsEmpty()) + { + const otDeviceProperties *props = otThreadGetDeviceProperties(GetInstancePtr()); + + OutputLine("PowerSupply : %s", Stringify(props->mPowerSupply, kPowerSupplyStrings)); + OutputLine("IsBorderRouter : %s", props->mIsBorderRouter ? "yes" : "no"); + OutputLine("SupportsCcm : %s", props->mSupportsCcm ? "yes" : "no"); + OutputLine("IsUnstable : %s", props->mIsUnstable ? "yes" : "no"); + OutputLine("WeightAdjustment : %d", props->mLeaderWeightAdjustment); + } + /** + * @cli deviceprops (set) + * @code + * deviceprops battery 0 0 0 -5 + * Done + * @endcode + * @code + * deviceprops + * PowerSupply : battery + * IsBorderRouter : no + * SupportsCcm : no + * IsUnstable : no + * WeightAdjustment : -5 + * Done + * @endcode + * @cparam deviceprops @ca{powerSupply} @ca{isBr} @ca{supportsCcm} @ca{isUnstable} @ca{weightAdjustment} + * `powerSupply`: should be 'battery', 'external', 'external-stable', 'external-unstable'. + * @par + * Sets the device properties. + * @csa{leaderweight} + * @csa{leaderweight (set)} + * @sa #otThreadSetDeviceProperties + */ + else + { + otDeviceProperties props; + bool value; + uint8_t index; + + for (index = 0; index < OT_ARRAY_LENGTH(kPowerSupplyStrings); index++) + { + if (aArgs[0] == kPowerSupplyStrings[index]) + { + props.mPowerSupply = static_cast(index); + break; + } + } + + VerifyOrExit(index < OT_ARRAY_LENGTH(kPowerSupplyStrings), error = OT_ERROR_INVALID_ARGS); + + SuccessOrExit(error = aArgs[1].ParseAsBool(value)); + props.mIsBorderRouter = value; + + SuccessOrExit(error = aArgs[2].ParseAsBool(value)); + props.mSupportsCcm = value; + + SuccessOrExit(error = aArgs[3].ParseAsBool(value)); + props.mIsUnstable = value; + + SuccessOrExit(error = aArgs[4].ParseAsInt8(props.mLeaderWeightAdjustment)); + + VerifyOrExit(aArgs[5].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + otThreadSetDeviceProperties(GetInstancePtr(), &props); + } + +exit: + return error; +} + #endif // OPENTHREAD_FTD #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE @@ -8053,6 +8153,9 @@ otError Interpreter::ProcessCommand(Arg aArgs[]) #endif CmdEntry("detach"), #endif // OPENTHREAD_FTD || OPENTHREAD_MTD +#if OPENTHREAD_FTD + CmdEntry("deviceprops"), +#endif #if OPENTHREAD_CONFIG_DIAG_ENABLE CmdEntry("diag"), #endif diff --git a/src/core/api/thread_ftd_api.cpp b/src/core/api/thread_ftd_api.cpp index 2c90cb08e..96fb8903a 100644 --- a/src/core/api/thread_ftd_api.cpp +++ b/src/core/api/thread_ftd_api.cpp @@ -79,6 +79,16 @@ otError otThreadSetPreferredRouterId(otInstance *aInstance, uint8_t aRouterId) return AsCoreType(aInstance).Get().SetPreferredRouterId(aRouterId); } +const otDeviceProperties *otThreadGetDeviceProperties(otInstance *aInstance) +{ + return &AsCoreType(aInstance).Get().GetDeviceProperties(); +} + +void otThreadSetDeviceProperties(otInstance *aInstance, const otDeviceProperties *aDeviceProperties) +{ + AsCoreType(aInstance).Get().SetDeviceProperties(AsCoreType(aDeviceProperties)); +} + uint8_t otThreadGetLocalLeaderWeight(otInstance *aInstance) { return AsCoreType(aInstance).Get().GetLeaderWeight(); diff --git a/src/core/config/misc.h b/src/core/config/misc.h index 69496ea46..918531cc2 100644 --- a/src/core/config/misc.h +++ b/src/core/config/misc.h @@ -77,6 +77,19 @@ #define OPENTHREAD_CONFIG_STACK_VERSION_MINOR 1 #endif +/** + * @def OPENTHREAD_CONFIG_DEVICE_POWER_SUPPLY + * + * Specifies the default device power supply config. This config MUST use values from `otPowerSupply` enumeration. + * + * Device manufacturer can use this config to set the power supply config used by the device. This is then used as part + * of default `otDeviceProperties` to determine the Leader Weight used by the device. + * + */ +#ifndef OPENTHREAD_CONFIG_DEVICE_POWER_SUPPLY +#define OPENTHREAD_CONFIG_DEVICE_POWER_SUPPLY OT_POWER_SUPPLY_EXTERNAL +#endif + /** * @def OPENTHREAD_CONFIG_ECDSA_ENABLE * diff --git a/src/core/config/mle.h b/src/core/config/mle.h index 872e1a7fe..431b7e797 100644 --- a/src/core/config/mle.h +++ b/src/core/config/mle.h @@ -88,6 +88,18 @@ #define OPENTHREAD_CONFIG_MLE_IP_ADDRS_TO_REGISTER (OPENTHREAD_CONFIG_MLE_IP_ADDRS_PER_CHILD) #endif +/** + * @def OPENTHREAD_CONFIG_MLE_DEFAULT_LEADER_WEIGHT_ADJUSTMENT + * + * Specifies the default value for `mLeaderWeightAdjustment` in `otDeviceProperties`. MUST be from -16 up to +16. + * + * This value is used to adjust the calculated Leader Weight from `otDeviceProperties`. + * + */ +#ifndef OPENTHREAD_CONFIG_MLE_DEFAULT_LEADER_WEIGHT_ADJUSTMENT +#define OPENTHREAD_CONFIG_MLE_DEFAULT_LEADER_WEIGHT_ADJUSTMENT 0 +#endif + /** * @def OPENTHREAD_CONFIG_MLE_STEERING_DATA_SET_OOB_ENABLE * diff --git a/src/core/thread/mle_router.cpp b/src/core/thread/mle_router.cpp index 360dec831..c5fc52764 100644 --- a/src/core/thread/mle_router.cpp +++ b/src/core/thread/mle_router.cpp @@ -70,7 +70,6 @@ MleRouter::MleRouter(Instance &aInstance) , mNetworkIdTimeout(kNetworkIdTimeout) , mRouterUpgradeThreshold(kRouterUpgradeThreshold) , mRouterDowngradeThreshold(kRouterDowngradeThreshold) - , mLeaderWeight(kLeaderWeight) #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE , mPreferredLeaderPartitionId(0) , mCcmEnabled(false) @@ -95,6 +94,7 @@ MleRouter::MleRouter(Instance &aInstance) #endif { mDeviceMode.Set(mDeviceMode.Get() | DeviceMode::kModeFullThreadDevice | DeviceMode::kModeFullNetworkData); + mLeaderWeight = mDeviceProperties.CalculateLeaderWeight(); SetRouterId(kInvalidRouterId); @@ -183,6 +183,13 @@ exit: return error; } +void MleRouter::SetDeviceProperties(const DeviceProperties &aDeviceProperties) +{ + mDeviceProperties = aDeviceProperties; + mDeviceProperties.ClampWeightAdjustment(); + SetLeaderWeight(mDeviceProperties.CalculateLeaderWeight()); +} + Error MleRouter::BecomeRouter(ThreadStatusTlv::Status aStatus) { Error error = kErrorNone; diff --git a/src/core/thread/mle_router.hpp b/src/core/thread/mle_router.hpp index d1ac1ae2e..423550bc7 100644 --- a/src/core/thread/mle_router.hpp +++ b/src/core/thread/mle_router.hpp @@ -143,6 +143,22 @@ public: */ Error BecomeLeader(void); + /** + * This method gets the device properties which are used to determine the Leader Weight. + * + * @returns The current device properties. + * + */ + const DeviceProperties &GetDeviceProperties(void) const { return mDeviceProperties; } + + /** + * This method sets the device properties which are then used to determine and set the Leader Weight. + * + * @param[in] aDeviceProperties The device properties. + * + */ + void SetDeviceProperties(const DeviceProperties &aDeviceProperties); + /** * This method returns the Leader Weighting value for this Thread interface. * @@ -154,6 +170,9 @@ public: /** * This method sets the Leader Weighting value for this Thread interface. * + * This method directly sets the Leader Weight to the new value replacing its previous value (which may have been + * determined from a previous call to `SetDeviceProperties()`). + * * @param[in] aWeight The Leader Weighting value. * */ @@ -636,6 +655,8 @@ private: TrickleTimer mAdvertiseTrickleTimer; + DeviceProperties mDeviceProperties; + ChildTable mChildTable; RouterTable mRouterTable; diff --git a/src/core/thread/mle_types.cpp b/src/core/thread/mle_types.cpp index 0714d3ad7..ca76165cf 100644 --- a/src/core/thread/mle_types.cpp +++ b/src/core/thread/mle_types.cpp @@ -39,6 +39,9 @@ namespace ot { namespace Mle { +//--------------------------------------------------------------------------------------------------------------------- +// DeviceMode + void DeviceMode::Get(ModeConfig &aModeConfig) const { aModeConfig.mRxOnWhenIdle = IsRxOnWhenIdle(); @@ -64,6 +67,77 @@ DeviceMode::InfoString DeviceMode::ToString(void) const return string; } +//--------------------------------------------------------------------------------------------------------------------- +// DeviceProperties + +#if OPENTHREAD_FTD + +DeviceProperties::DeviceProperties(void) +{ + Clear(); + + mPowerSupply = OPENTHREAD_CONFIG_DEVICE_POWER_SUPPLY; + mLeaderWeightAdjustment = kDefaultAdjustment; +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + mIsBorderRouter = true; +#endif +} + +void DeviceProperties::ClampWeightAdjustment(void) +{ + mLeaderWeightAdjustment = Clamp(mLeaderWeightAdjustment, kMinAdjustment, kMaxAdjustment); +} + +uint8_t DeviceProperties::CalculateLeaderWeight(void) const +{ + static const int8_t kPowerSupplyIncs[] = { + kPowerBatteryInc, // (0) kPowerSupplyBattery + kPowerExternalInc, // (1) kPowerSupplyExternal + kPowerExternalStableInc, // (2) kPowerSupplyExternalStable + kPowerExternalUnstableInc, // (3) kPowerSupplyExternalUnstable + }; + + static_assert(0 == kPowerSupplyBattery, "kPowerSupplyBattery value is incorrect"); + static_assert(1 == kPowerSupplyExternal, "kPowerSupplyExternal value is incorrect"); + static_assert(2 == kPowerSupplyExternalStable, "kPowerSupplyExternalStable value is incorrect"); + static_assert(3 == kPowerSupplyExternalUnstable, "kPowerSupplyExternalUnstable value is incorrect"); + + uint8_t weight = kBaseWeight; + PowerSupply powerSupply = MapEnum(mPowerSupply); + + if (mIsBorderRouter) + { + weight += (mSupportsCcm ? kCcmBorderRouterInc : kBorderRouterInc); + } + + if (powerSupply < GetArrayLength(kPowerSupplyIncs)) + { + weight += kPowerSupplyIncs[powerSupply]; + } + + if (mIsUnstable) + { + switch (powerSupply) + { + case kPowerSupplyBattery: + case kPowerSupplyExternalUnstable: + break; + + default: + weight += kIsUnstableInc; + } + } + + weight += mLeaderWeightAdjustment; + + return weight; +} + +#endif // OPENTHREAD_FTD + +//--------------------------------------------------------------------------------------------------------------------- +// RouterIdSet + uint8_t RouterIdSet::GetNumberOfAllocatedIds(void) const { uint8_t count = 0; @@ -76,6 +150,8 @@ uint8_t RouterIdSet::GetNumberOfAllocatedIds(void) const return count; } +//--------------------------------------------------------------------------------------------------------------------- + const char *RoleToString(DeviceRole aRole) { static const char *const kRoleStrings[] = { diff --git a/src/core/thread/mle_types.hpp b/src/core/thread/mle_types.hpp index 33e84bf18..b4543970d 100644 --- a/src/core/thread/mle_types.hpp +++ b/src/core/thread/mle_types.hpp @@ -41,6 +41,9 @@ #include #include +#if OPENTHREAD_FTD +#include +#endif #include "common/as_core_type.hpp" #include "common/clearable.hpp" @@ -181,7 +184,6 @@ constexpr uint32_t kMaxLeaderToRouterTimeout = 90; ///< (in sec) constexpr uint32_t kReedAdvertiseInterval = 570; ///< (in sec) constexpr uint32_t kReedAdvertiseJitter = 60; ///< (in sec) -constexpr uint8_t kLeaderWeight = 64; ///< Default leader weight constexpr uint32_t kMleEndDeviceTimeout = OPENTHREAD_CONFIG_MLE_CHILD_TIMEOUT_DEFAULT; ///< (in sec) constexpr uint8_t kMeshLocalPrefixContextId = 0; ///< 0 is reserved for Mesh Local Prefix @@ -435,6 +437,67 @@ private: uint8_t mMode; }; +#if OPENTHREAD_FTD +/** + * This class represents device properties. + * + * The device properties are used for calculating the local leader weight on the device. + * + */ +class DeviceProperties : public otDeviceProperties, public Clearable +{ +public: + /** + * This enumeration represents the device's power supply property. + * + */ + enum PowerSupply : uint8_t + { + kPowerSupplyBattery = OT_POWER_SUPPLY_BATTERY, ///< Battery powered. + kPowerSupplyExternal = OT_POWER_SUPPLY_EXTERNAL, ///< External powered. + kPowerSupplyExternalStable = OT_POWER_SUPPLY_EXTERNAL_STABLE, ///< Stable external power with backup. + kPowerSupplyExternalUnstable = OT_POWER_SUPPLY_EXTERNAL_UNSTABLE, ///< Unstable external power. + }; + + /** + * This constructor initializes `DeviceProperties` with default values. + * + */ + DeviceProperties(void); + + /** + * This method clamps the `mLeaderWeightAdjustment` value to the valid range. + * + */ + void ClampWeightAdjustment(void); + + /** + * This method calculates the leader weight based on the device properties. + * + * @returns The calculated leader weight. + * + */ + uint8_t CalculateLeaderWeight(void) const; + +private: + static constexpr int8_t kDefaultAdjustment = OPENTHREAD_CONFIG_MLE_DEFAULT_LEADER_WEIGHT_ADJUSTMENT; + static constexpr uint8_t kBaseWeight = 64; + static constexpr int8_t kBorderRouterInc = +1; + static constexpr int8_t kCcmBorderRouterInc = +8; + static constexpr int8_t kIsUnstableInc = -4; + static constexpr int8_t kPowerBatteryInc = -8; + static constexpr int8_t kPowerExternalInc = 0; + static constexpr int8_t kPowerExternalStableInc = +4; + static constexpr int8_t kPowerExternalUnstableInc = -4; + static constexpr int8_t kMinAdjustment = -16; + static constexpr int8_t kMaxAdjustment = +16; + + static_assert(kDefaultAdjustment >= kMinAdjustment, "Invalid default weight adjustment"); + static_assert(kDefaultAdjustment <= kMaxAdjustment, "Invalid default weight adjustment"); +}; + +#endif // OPENTHREAD_FTD + /** * This class represents the Thread Leader Data. * @@ -693,6 +756,10 @@ const char *RoleToString(DeviceRole aRole); DefineCoreType(otLeaderData, Mle::LeaderData); DefineMapEnum(otDeviceRole, Mle::DeviceRole); +#if OPENTHREAD_FTD +DefineCoreType(otDeviceProperties, Mle::DeviceProperties); +DefineMapEnum(otPowerSupply, Mle::DeviceProperties::PowerSupply); +#endif } // namespace ot diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 80c22e8a7..aeda3cc73 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -665,6 +665,27 @@ target_link_libraries(ot-test-message-queue add_test(NAME ot-test-message-queue COMMAND ot-test-message-queue) +add_executable(ot-test-mle + test_mle.cpp +) + +target_include_directories(ot-test-mle + PRIVATE + ${COMMON_INCLUDES} +) + +target_compile_options(ot-test-mle + PRIVATE + ${COMMON_COMPILE_OPTIONS} +) + +target_link_libraries(ot-test-mle + PRIVATE + ${COMMON_LIBS} +) + +add_test(NAME ot-test-mle COMMAND ot-test-mle) + add_executable(ot-test-multicast-listeners-table test_multicast_listeners_table.cpp ) diff --git a/tests/unit/test_mle.cpp b/tests/unit/test_mle.cpp new file mode 100644 index 000000000..e7e1748e8 --- /dev/null +++ b/tests/unit/test_mle.cpp @@ -0,0 +1,185 @@ +/* + * 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 + +#include "test_platform.h" +#include "test_util.hpp" + +#include "common/num_utils.hpp" +#include "thread/mle_types.hpp" + +namespace ot { + +#if OPENTHREAD_FTD +void TestDefaultDeviceProperties(void) +{ + Instance *instance; + const otDeviceProperties *props; + uint8_t weight; + + instance = static_cast(testInitInstance()); + VerifyOrQuit(instance != nullptr); + + props = otThreadGetDeviceProperties(instance); + + VerifyOrQuit(props->mPowerSupply == OPENTHREAD_CONFIG_DEVICE_POWER_SUPPLY); + VerifyOrQuit(!props->mSupportsCcm); + VerifyOrQuit(!props->mIsUnstable); + VerifyOrQuit(props->mLeaderWeightAdjustment == OPENTHREAD_CONFIG_MLE_DEFAULT_LEADER_WEIGHT_ADJUSTMENT); +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + VerifyOrQuit(props->mIsBorderRouter); +#else + VerifyOrQuit(!props->mIsBorderRouter); +#endif + + weight = 64; + + switch (props->mPowerSupply) + { + case OT_POWER_SUPPLY_BATTERY: + weight -= 8; + break; + case OT_POWER_SUPPLY_EXTERNAL: + break; + case OT_POWER_SUPPLY_EXTERNAL_STABLE: + weight += 4; + break; + case OT_POWER_SUPPLY_EXTERNAL_UNSTABLE: + weight -= 4; + break; + } + + weight += props->mIsBorderRouter ? 1 : 0; + + VerifyOrQuit(otThreadGetLocalLeaderWeight(instance) == weight); + + printf("TestDefaultDeviceProperties passed\n"); +} + +void CompareDevicePropertiess(const otDeviceProperties &aFirst, const otDeviceProperties &aSecond) +{ + static constexpr int8_t kMinAdjustment = -16; + static constexpr int8_t kMaxAdjustment = +16; + + VerifyOrQuit(aFirst.mPowerSupply == aSecond.mPowerSupply); + VerifyOrQuit(aFirst.mIsBorderRouter == aSecond.mIsBorderRouter); + VerifyOrQuit(aFirst.mSupportsCcm == aSecond.mSupportsCcm); + VerifyOrQuit(aFirst.mIsUnstable == aSecond.mIsUnstable); + VerifyOrQuit(Clamp(aFirst.mLeaderWeightAdjustment, kMinAdjustment, kMaxAdjustment) == + Clamp(aSecond.mLeaderWeightAdjustment, kMinAdjustment, kMaxAdjustment)); +} + +void TestLeaderWeightCalculation(void) +{ + struct TestCase + { + otDeviceProperties mDeviceProperties; + uint8_t mExpectedLeaderWeight; + }; + + static const TestCase kTestCases[] = { + {{OT_POWER_SUPPLY_BATTERY, false, false, false, 0}, 56}, + {{OT_POWER_SUPPLY_EXTERNAL, false, false, false, 0}, 64}, + {{OT_POWER_SUPPLY_EXTERNAL_STABLE, false, false, false, 0}, 68}, + {{OT_POWER_SUPPLY_EXTERNAL_UNSTABLE, false, false, false, 0}, 60}, + + {{OT_POWER_SUPPLY_BATTERY, true, false, false, 0}, 57}, + {{OT_POWER_SUPPLY_EXTERNAL, true, false, false, 0}, 65}, + {{OT_POWER_SUPPLY_EXTERNAL_STABLE, true, false, false, 0}, 69}, + {{OT_POWER_SUPPLY_EXTERNAL_UNSTABLE, true, false, false, 0}, 61}, + + {{OT_POWER_SUPPLY_BATTERY, true, true, false, 0}, 64}, + {{OT_POWER_SUPPLY_EXTERNAL, true, true, false, 0}, 72}, + {{OT_POWER_SUPPLY_EXTERNAL_STABLE, true, true, false, 0}, 76}, + {{OT_POWER_SUPPLY_EXTERNAL_UNSTABLE, true, true, false, 0}, 68}, + + // Check when `mIsUnstable` is set. + {{OT_POWER_SUPPLY_BATTERY, false, false, true, 0}, 56}, + {{OT_POWER_SUPPLY_EXTERNAL, false, false, true, 0}, 60}, + {{OT_POWER_SUPPLY_EXTERNAL_STABLE, false, false, true, 0}, 64}, + {{OT_POWER_SUPPLY_EXTERNAL_UNSTABLE, false, false, true, 0}, 60}, + + {{OT_POWER_SUPPLY_BATTERY, true, false, true, 0}, 57}, + {{OT_POWER_SUPPLY_EXTERNAL, true, false, true, 0}, 61}, + {{OT_POWER_SUPPLY_EXTERNAL_STABLE, true, false, true, 0}, 65}, + {{OT_POWER_SUPPLY_EXTERNAL_UNSTABLE, true, false, true, 0}, 61}, + + // Include non-zero `mLeaderWeightAdjustment`. + {{OT_POWER_SUPPLY_BATTERY, true, false, false, 10}, 67}, + {{OT_POWER_SUPPLY_EXTERNAL, true, false, false, 10}, 75}, + {{OT_POWER_SUPPLY_EXTERNAL_STABLE, true, false, false, 10}, 79}, + {{OT_POWER_SUPPLY_EXTERNAL_UNSTABLE, true, false, false, 10}, 71}, + + {{OT_POWER_SUPPLY_BATTERY, false, false, false, -10}, 46}, + {{OT_POWER_SUPPLY_EXTERNAL, false, false, false, -10}, 54}, + {{OT_POWER_SUPPLY_EXTERNAL_STABLE, false, false, false, -10}, 58}, + {{OT_POWER_SUPPLY_EXTERNAL_UNSTABLE, false, false, false, -10}, 50}, + + // Use `mLeaderWeightAdjustment` larger than valid range + // Make sure it clamps to -16 and +16. + {{OT_POWER_SUPPLY_BATTERY, false, false, false, 20}, 72}, + {{OT_POWER_SUPPLY_EXTERNAL, false, false, false, 20}, 80}, + {{OT_POWER_SUPPLY_EXTERNAL_STABLE, false, false, false, 20}, 84}, + {{OT_POWER_SUPPLY_EXTERNAL_UNSTABLE, false, false, false, 20}, 76}, + + {{OT_POWER_SUPPLY_BATTERY, true, false, false, -20}, 41}, + {{OT_POWER_SUPPLY_EXTERNAL, true, false, false, -20}, 49}, + {{OT_POWER_SUPPLY_EXTERNAL_STABLE, true, false, false, -20}, 53}, + {{OT_POWER_SUPPLY_EXTERNAL_UNSTABLE, true, false, false, -20}, 45}, + }; + + Instance *instance; + + instance = static_cast(testInitInstance()); + VerifyOrQuit(instance != nullptr); + + for (const TestCase &testCase : kTestCases) + { + otThreadSetDeviceProperties(instance, &testCase.mDeviceProperties); + CompareDevicePropertiess(testCase.mDeviceProperties, *otThreadGetDeviceProperties(instance)); + VerifyOrQuit(otThreadGetLocalLeaderWeight(instance) == testCase.mExpectedLeaderWeight); + } + + printf("TestLeaderWeightCalculation passed\n"); +} + +#endif // OPENTHREAD_FTD + +} // namespace ot + +int main(void) +{ +#if OPENTHREAD_FTD + ot::TestDefaultDeviceProperties(); + ot::TestLeaderWeightCalculation(); +#endif + + printf("All tests passed\n"); + return 0; +}