From e8f3ec0fd3356d0c52b1a6f444e63b5c86abbbdb Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Thu, 23 Mar 2023 18:47:57 -0700 Subject: [PATCH] [common] add `Preference` helper methods for 2-bit preference value (#8895) This commit introduces a common `Preference` class which defines constants and helper methods (e.g., to convert between an `int8_t` preference and its 2-bit unsigned representation). This is used for `RoutePreference` in Network Data entries and also for Parent Priority in `ConnectivityTlv`. --- src/core/BUILD.gn | 2 + src/core/CMakeLists.txt | 1 + src/core/Makefile.am | 2 + src/core/common/preference.cpp | 57 +++++++++++ src/core/common/preference.hpp | 135 +++++++++++++++++++++++++ src/core/thread/mle_tlvs.cpp | 31 +----- src/core/thread/mle_tlvs.hpp | 5 +- src/core/thread/network_data_types.cpp | 19 ---- src/core/thread/network_data_types.hpp | 34 ++----- tests/unit/test_serial_number.cpp | 62 ++++++++++++ 10 files changed, 271 insertions(+), 77 deletions(-) create mode 100644 src/core/common/preference.cpp create mode 100644 src/core/common/preference.hpp diff --git a/src/core/BUILD.gn b/src/core/BUILD.gn index 7b2b30e63..f968aee6a 100644 --- a/src/core/BUILD.gn +++ b/src/core/BUILD.gn @@ -427,6 +427,8 @@ openthread_core_files = [ "common/owned_ptr.hpp", "common/owning_list.hpp", "common/pool.hpp", + "common/preference.cpp", + "common/preference.hpp", "common/ptr_wrapper.hpp", "common/random.cpp", "common/random.hpp", diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 09818c239..eb1279f8a 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -109,6 +109,7 @@ set(COMMON_SOURCES common/log.cpp common/message.cpp common/notifier.cpp + common/preference.cpp common/random.cpp common/settings.cpp common/string.cpp diff --git a/src/core/Makefile.am b/src/core/Makefile.am index 776ca2095..169902e73 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -199,6 +199,7 @@ SOURCES_COMMON = \ common/log.cpp \ common/message.cpp \ common/notifier.cpp \ + common/preference.cpp \ common/random.cpp \ common/settings.cpp \ common/string.cpp \ @@ -472,6 +473,7 @@ HEADERS_COMMON = \ common/owned_ptr.hpp \ common/owning_list.hpp \ common/pool.hpp \ + common/preference.hpp \ common/ptr_wrapper.hpp \ common/random.hpp \ common/retain_ptr.hpp \ diff --git a/src/core/common/preference.cpp b/src/core/common/preference.cpp new file mode 100644 index 000000000..44aeb522f --- /dev/null +++ b/src/core/common/preference.cpp @@ -0,0 +1,57 @@ +/* + * 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 implements methods for a signed preference value and its 2-bit unsigned representation. + * + */ + +#include "preference.hpp" + +namespace ot { + +uint8_t Preference::To2BitUint(int8_t aPrf) { return (aPrf == 0) ? k2BitMedium : ((aPrf > 0) ? k2BitHigh : k2BitLow); } + +int8_t Preference::From2BitUint(uint8_t a2BitUint) +{ + static const int8_t kPreferences[] = { + /* 0 (00) -> */ kMedium, + /* 1 (01) -> */ kHigh, + /* 2 (10) -> */ kMedium, // Per RFC-4191, the reserved value (10) MUST be treated as (00) + /* 3 (11) -> */ kLow, + }; + + return kPreferences[a2BitUint & k2BitMask]; +} + +bool Preference::IsValid(int8_t aPrf) { return (aPrf == kHigh) || (aPrf == kMedium) || (aPrf == kLow); } + +const char *Preference::ToString(int8_t aPrf) { return (aPrf == 0) ? "medium" : ((aPrf > 0) ? "high" : "low"); } + +} // namespace ot diff --git a/src/core/common/preference.hpp b/src/core/common/preference.hpp new file mode 100644 index 000000000..a4e2651a6 --- /dev/null +++ b/src/core/common/preference.hpp @@ -0,0 +1,135 @@ +/* + * 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 definitions for a signed preference value and its 2-bit unsigned representation used by + * Route Preference in RFC-4191. + */ + +#ifndef PREFERENCE_HPP_ +#define PREFERENCE_HPP_ + +#include "openthread-core-config.h" + +#include "common/error.hpp" + +namespace ot { + +/** + * This class provides constants and static methods to convert between `int8_t` preference and its 2-bit unsigned + * representation. + * + */ +class Preference +{ +public: + static constexpr int8_t kHigh = 1; ///< High preference. + static constexpr int8_t kMedium = 0; ///< Medium preference. + static constexpr int8_t kLow = -1; ///< Low preference. + + /** + * This static method converts a signed preference value to its corresponding 2-bit `uint8_t` value. + * + * A positive @p aPrf is mapped to "High Preference", a negative @p aPrf is mapped to "Low Preference", and + * zero @p aPrf is mapped to "Medium Preference". + * + * @param[in] aPrf The preference to convert to `uint8_t`. + * + * @returns The 2-bit unsigned value representing @p aPrf. + * + */ + static uint8_t To2BitUint(int8_t aPrf); + + /** + * This static method converts a 2-bit `uint8_t` value to a signed preference value `kHigh`, `kMedium`, and `kLow`. + * + * Only the first two bits (LSB) of @p a2BitUint are used and the rest of the bits are ignored. + * + * - `0b01` (or 1) is mapped to `kHigh`. + * - `0b00` (or 0) is mapped to `kMedium`. + * - `0b11` (or 3) is mapped to `kLow`. + * - `0b10` (or 2) is reserved for future and is also mapped to `kMedium` (this complies with RFC-4191 where + * the reserved value `0b10` MUST be treated as `0b00` for Route Preference). + * + * @param[in] a2BitUint The 2-bit unsigned value to convert from. Only two LSB bits are used and the reset are + * ignored. + * + * @returns The signed preference `kHigh`, `kMedium`, or `kLow` corresponding to @p a2BitUint. + * + */ + static int8_t From2BitUint(uint8_t a2BitUint); + + /** + * This static method indicates whether a given `int8_t` preference value is valid, i.e., whether it has of the + * three values `kHigh`, `kMediumm`, or `kLow`. + * + * @param[in] aPrf The signed preference value to check. + * + * @retval TRUE if @p aPrf is valid. + * @retval FALSE if @p aPrf is not valid + * + */ + static bool IsValid(int8_t aPrf); + + /** + * This static method indicates whether a given 2-bit `uint8_t` preference value is valid. + * + * @param[in] a2BitUint The 2-bit unsigned value to convert from. Only two LSB bits are used and the reset are + * ignored. + * + * @retval TRUE if the first 2 bits of @p a2BitUint are `0b00`, `0b01`, or `0b11`. + * @retval FALSE if the first 2 bits of @p a2BitUint are `0b01`. + * + */ + static bool Is2BitUintValid(uint8_t a2BitUint) { return ((a2BitUint & k2BitMask) != k2BitReserved); } + + /** + * This static method converts a given preference to a human-readable string. + * + * @param[in] aPrf The preference to convert. + * + * @returns The string representation of @p aPrf. + * + */ + static const char *ToString(int8_t aPrf); + + Preference(void) = delete; + +private: + static constexpr uint8_t k2BitMask = 3; + + static constexpr uint8_t k2BitHigh = 1; // 0b01 + static constexpr uint8_t k2BitMedium = 0; // 0b00 + static constexpr uint8_t k2BitLow = 3; // 0b11 + static constexpr uint8_t k2BitReserved = 2; // 0b10 +}; + +} // namespace ot + +#endif // PREFERENCE_HPP_ diff --git a/src/core/thread/mle_tlvs.cpp b/src/core/thread/mle_tlvs.cpp index de7268bce..1908d61d4 100644 --- a/src/core/thread/mle_tlvs.cpp +++ b/src/core/thread/mle_tlvs.cpp @@ -86,39 +86,12 @@ void ConnectivityTlv::IncrementLinkQuality(LinkQuality aLinkQuality) int8_t ConnectivityTlv::GetParentPriority(void) const { - int8_t priority = kParentPriorityUnspecified; - - switch (mFlags & kFlagsParentPriorityMask) - { - case kFlagsPriorityHigh: - priority = kParentPriorityHigh; - break; - case kFlagsPriorityMedium: - priority = kParentPriorityMedium; - break; - case kFlagsPriorityLow: - priority = kParentPriorityLow; - default: - break; - } - - return priority; + return Preference::From2BitUint(mFlags >> kFlagsParentPriorityOffset); } void ConnectivityTlv::SetParentPriority(int8_t aParentPriority) { - if (aParentPriority > 0) - { - mFlags = kFlagsPriorityHigh; - } - else if (aParentPriority < 0) - { - mFlags = kFlagsPriorityLow; - } - else - { - mFlags = kFlagsPriorityMedium; - } + mFlags = static_cast(Preference::To2BitUint(aParentPriority) << kFlagsParentPriorityOffset); } } // namespace Mle diff --git a/src/core/thread/mle_tlvs.hpp b/src/core/thread/mle_tlvs.hpp index 1cab08437..81b529f7d 100644 --- a/src/core/thread/mle_tlvs.hpp +++ b/src/core/thread/mle_tlvs.hpp @@ -38,6 +38,7 @@ #include "common/encoding.hpp" #include "common/message.hpp" +#include "common/preference.hpp" #include "common/tlvs.hpp" #include "meshcop/timestamp.hpp" #include "net/ip6_address.hpp" @@ -947,10 +948,6 @@ private: static constexpr uint8_t kFlagsParentPriorityOffset = 6; static constexpr uint8_t kFlagsParentPriorityMask = (3 << kFlagsParentPriorityOffset); - static constexpr uint8_t kFlagsPriorityHigh = 1 << kFlagsParentPriorityOffset; // (01) 1 - static constexpr uint8_t kFlagsPriorityMedium = 0 << kFlagsParentPriorityOffset; // (00) 0 - static constexpr uint8_t kFlagsPriorityLow = 3 << kFlagsParentPriorityOffset; // (11) -1 - uint8_t mFlags; uint8_t mLinkQuality3; uint8_t mLinkQuality2; diff --git a/src/core/thread/network_data_types.cpp b/src/core/thread/network_data_types.cpp index 724bf0a59..24440cf53 100644 --- a/src/core/thread/network_data_types.cpp +++ b/src/core/thread/network_data_types.cpp @@ -39,25 +39,6 @@ namespace ot { namespace NetworkData { -const char *RoutePreferenceToString(RoutePreference aPreference) -{ - const char *str = "low"; - - switch (aPreference) - { - case kRoutePreferenceHigh: - str = "high"; - break; - case kRoutePreferenceMedium: - str = "med"; - break; - case kRoutePreferenceLow: - break; - } - - return str; -} - #if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE static bool IsPrefixValid(Instance &aInstance, const Ip6::Prefix &aPrefix) diff --git a/src/core/thread/network_data_types.hpp b/src/core/thread/network_data_types.hpp index d9a3c1069..642b398e6 100644 --- a/src/core/thread/network_data_types.hpp +++ b/src/core/thread/network_data_types.hpp @@ -43,6 +43,7 @@ #include "common/data.hpp" #include "common/debug.hpp" #include "common/equatable.hpp" +#include "common/preference.hpp" #include "net/ip6_address.hpp" namespace ot { @@ -90,6 +91,10 @@ enum RoutePreference : int8_t kRoutePreferenceHigh = OT_ROUTE_PREFERENCE_HIGH, ///< High route preference. }; +static_assert(kRoutePreferenceHigh == Preference::kHigh, "kRoutePreferenceHigh is not valid"); +static_assert(kRoutePreferenceMedium == Preference::kMedium, "kRoutePreferenceMedium is not valid"); +static_assert(kRoutePreferenceLow == Preference::kLow, "kRoutePreferenceLow is not valid"); + /** * This enumeration represents the border router RLOC role filter used when searching for border routers in the Network * Data. @@ -112,10 +117,7 @@ enum RoleFilter : uint8_t * @retval FALSE if @p aPref is not valid * */ -inline bool IsRoutePreferenceValid(int8_t aPref) -{ - return (aPref == kRoutePreferenceLow) || (aPref == kRoutePreferenceMedium) || (aPref == kRoutePreferenceHigh); -} +inline bool IsRoutePreferenceValid(int8_t aPref) { return Preference::IsValid(aPref); } /** * This function coverts a route preference to a 2-bit unsigned value. @@ -127,16 +129,7 @@ inline bool IsRoutePreferenceValid(int8_t aPref) * @returns The 2-bit unsigned value representing @p aPref. * */ -inline uint8_t RoutePreferenceToValue(int8_t aPref) -{ - constexpr uint8_t kHigh = 1; // 01 - constexpr uint8_t kMedium = 0; // 00 - constexpr uint8_t kLow = 3; // 11 - - OT_ASSERT(IsRoutePreferenceValid(aPref)); - - return (aPref == 0) ? kMedium : ((aPref > 0) ? kHigh : kLow); -} +inline uint8_t RoutePreferenceToValue(int8_t aPref) { return Preference::To2BitUint(aPref); } /** * This function coverts a 2-bit unsigned value to a route preference. @@ -149,16 +142,7 @@ inline uint8_t RoutePreferenceToValue(int8_t aPref) */ inline RoutePreference RoutePreferenceFromValue(uint8_t aValue) { - constexpr uint8_t kMask = 3; // First two bits. - - static const RoutePreference kRoutePreferences[] = { - /* 0 (00) -> */ kRoutePreferenceMedium, - /* 1 (01) -> */ kRoutePreferenceHigh, - /* 2 (10) -> */ kRoutePreferenceMedium, // Per RFC-4191, the reserved value (10) MUST be treated as (00) - /* 3 (11) -> */ kRoutePreferenceLow, - }; - - return kRoutePreferences[aValue & kMask]; + return static_cast(Preference::From2BitUint(aValue)); } /** @@ -169,7 +153,7 @@ inline RoutePreference RoutePreferenceFromValue(uint8_t aValue) * @returns The string representation of @p aPreference. * */ -const char *RoutePreferenceToString(RoutePreference aPreference); +inline const char *RoutePreferenceToString(RoutePreference aPreference) { return Preference::ToString(aPreference); } /** * This class represents an On-mesh Prefix (Border Router) configuration. diff --git a/tests/unit/test_serial_number.cpp b/tests/unit/test_serial_number.cpp index 727c1ea61..df5f11851 100644 --- a/tests/unit/test_serial_number.cpp +++ b/tests/unit/test_serial_number.cpp @@ -34,6 +34,7 @@ #include "common/code_utils.hpp" #include "common/num_utils.hpp" #include "common/numeric_limits.hpp" +#include "common/preference.hpp" #include "common/serial_number.hpp" namespace ot { @@ -152,6 +153,66 @@ void TestNumUtils(void) printf("TestNumUtils() passed\n"); } +void TestPreference(void) +{ + VerifyOrQuit(Preference::kHigh == 1); + VerifyOrQuit(Preference::kMedium == 0); + VerifyOrQuit(Preference::kLow == -1); + + // To2BitUint() + VerifyOrQuit(Preference::To2BitUint(Preference::kHigh) == 0x1); + VerifyOrQuit(Preference::To2BitUint(Preference::kMedium) == 0x0); + VerifyOrQuit(Preference::To2BitUint(Preference::kLow) == 0x3); + VerifyOrQuit(Preference::To2BitUint(2) == 0x1); + VerifyOrQuit(Preference::To2BitUint(-2) == 0x3); + VerifyOrQuit(Preference::To2BitUint(127) == 0x1); + VerifyOrQuit(Preference::To2BitUint(-128) == 0x3); + + // From2BitUint() + VerifyOrQuit(Preference::From2BitUint(0x1) == Preference::kHigh); + VerifyOrQuit(Preference::From2BitUint(0x0) == Preference::kMedium); + VerifyOrQuit(Preference::From2BitUint(0x3) == Preference::kLow); + VerifyOrQuit(Preference::From2BitUint(0x2) == Preference::kMedium); + + VerifyOrQuit(Preference::From2BitUint(0x1 | 4) == Preference::kHigh); + VerifyOrQuit(Preference::From2BitUint(0x0 | 4) == Preference::kMedium); + VerifyOrQuit(Preference::From2BitUint(0x3 | 4) == Preference::kLow); + VerifyOrQuit(Preference::From2BitUint(0x2 | 4) == Preference::kMedium); + + VerifyOrQuit(Preference::From2BitUint(0x1 | 0xfc) == Preference::kHigh); + VerifyOrQuit(Preference::From2BitUint(0x0 | 0xfc) == Preference::kMedium); + VerifyOrQuit(Preference::From2BitUint(0x3 | 0xfc) == Preference::kLow); + VerifyOrQuit(Preference::From2BitUint(0x2 | 0xfc) == Preference::kMedium); + + // IsValid() + VerifyOrQuit(Preference::IsValid(Preference::kHigh)); + VerifyOrQuit(Preference::IsValid(Preference::kMedium)); + VerifyOrQuit(Preference::IsValid(Preference::kLow)); + + VerifyOrQuit(!Preference::IsValid(2)); + VerifyOrQuit(!Preference::IsValid(-2)); + VerifyOrQuit(!Preference::IsValid(127)); + VerifyOrQuit(!Preference::IsValid(-128)); + + // Is2BitUintValid + VerifyOrQuit(Preference::Is2BitUintValid(0x1)); + VerifyOrQuit(Preference::Is2BitUintValid(0x0)); + VerifyOrQuit(Preference::Is2BitUintValid(0x3)); + VerifyOrQuit(!Preference::Is2BitUintValid(0x2)); + + VerifyOrQuit(Preference::Is2BitUintValid(0x1 | 4)); + VerifyOrQuit(Preference::Is2BitUintValid(0x0 | 4)); + VerifyOrQuit(Preference::Is2BitUintValid(0x3 | 4)); + VerifyOrQuit(!Preference::Is2BitUintValid(0x2 | 4)); + + VerifyOrQuit(Preference::Is2BitUintValid(0x1 | 0xfc)); + VerifyOrQuit(Preference::Is2BitUintValid(0x0 | 0xfc)); + VerifyOrQuit(Preference::Is2BitUintValid(0x3 | 0xfc)); + VerifyOrQuit(!Preference::Is2BitUintValid(0x2 | 0xfc)); + + printf("TestPreference() passed\n"); +} + } // namespace ot int main(void) @@ -161,6 +222,7 @@ int main(void) ot::TestSerialNumber("uint32_t"); ot::TestSerialNumber("uint64_t"); ot::TestNumUtils(); + ot::TestPreference(); printf("\nAll tests passed.\n"); return 0; }