diff options
author | Ed Tanous <ed.tanous@intel.com> | 2018-02-21 12:22:54 -0800 |
---|---|---|
committer | Brad Bishop <bradleyb@fuzziesquirrel.com> | 2018-03-07 20:26:19 -0500 |
commit | 28dc36d509ba8f77ffb5726cc7d5f0184f8054b1 (patch) | |
tree | def9262fb06256e5f91d8af1d5825b6418216a07 | |
parent | f9c02dbd87d952286b80a5397b4702fbc80a8aec (diff) | |
download | sdbusplus-28dc36d509ba8f77ffb5726cc7d5f0184f8054b1.tar.gz sdbusplus-28dc36d509ba8f77ffb5726cc7d5f0184f8054b1.zip |
Allow reading and appending of more complex types
This commit makes sdbusplus compatible with most containers that meet
a few requirements. This includes:
std::unordered_map
std::array
std::set
boost::flat_set
boost::flat_map
Read requires a container to support emplace or emplace_back methods.
Append requires a container to suport a const iterator
Tested: The top level OpenBMC compiles properly, and the sdbusplus
unit tests compile and pass, and unit tests have been updated with a
few new types to ensure we see any breakages.
Signed-off-by: Ed Tanous <ed.tanous@intel.com>
Change-Id: I5eb1cf7dc07bacc7aca62d87844794223ad4de80
Signed-off-by: Brad Bishop <bradleyb@fuzziesquirrel.com>
-rw-r--r-- | Makefile.am | 1 | ||||
-rw-r--r-- | sdbusplus/message/append.hpp | 48 | ||||
-rw-r--r-- | sdbusplus/message/read.hpp | 75 | ||||
-rw-r--r-- | sdbusplus/message/types.hpp | 54 | ||||
-rw-r--r-- | sdbusplus/utility/container_traits.hpp | 81 | ||||
-rw-r--r-- | test/message/append.cpp | 135 | ||||
-rw-r--r-- | test/message/read.cpp | 56 |
7 files changed, 369 insertions, 81 deletions
diff --git a/Makefile.am b/Makefile.am index 037640c..8c96b3e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -17,6 +17,7 @@ nobase_include_HEADERS = \ sdbusplus/server/manager.hpp \ sdbusplus/server/object.hpp \ sdbusplus/slot.hpp \ + sdbusplus/utility/container_traits.hpp \ sdbusplus/utility/tuple_to_array.hpp \ sdbusplus/utility/type_traits.hpp \ sdbusplus/vtable.hpp diff --git a/sdbusplus/message/append.hpp b/sdbusplus/message/append.hpp index 85bb602..e81f813 100644 --- a/sdbusplus/message/append.hpp +++ b/sdbusplus/message/append.hpp @@ -1,10 +1,12 @@ #pragma once #include <tuple> + +#include <systemd/sd-bus.h> #include <sdbusplus/message/types.hpp> -#include <sdbusplus/utility/type_traits.hpp> +#include <sdbusplus/utility/container_traits.hpp> #include <sdbusplus/utility/tuple_to_array.hpp> -#include <systemd/sd-bus.h> +#include <sdbusplus/utility/type_traits.hpp> namespace sdbusplus { @@ -44,7 +46,8 @@ namespace details * User-defined types are expected to inherit from std::false_type. * */ -template <typename T> struct can_append_multiple : std::true_type +template <typename T, typename Enable = void> +struct can_append_multiple : std::true_type { }; // std::string needs a c_str() call. @@ -63,9 +66,11 @@ template <> struct can_append_multiple<signature> : std::false_type template <> struct can_append_multiple<bool> : std::false_type { }; -// std::vector needs a loop. +// std::vector/map/unordered_map/set need loops template <typename T> -struct can_append_multiple<std::vector<T>> : std::false_type +struct can_append_multiple< + T, typename std::enable_if<utility::has_const_iterator<T>::value>::type> + : std::false_type { }; // std::pair needs to be broken down into components. @@ -73,11 +78,6 @@ template <typename T1, typename T2> struct can_append_multiple<std::pair<T1, T2>> : std::false_type { }; -// std::map needs a loop. -template <typename T1, typename T2> -struct can_append_multiple<std::map<T1, T2>> : std::false_type -{ -}; // std::tuple needs to be broken down into components. template <typename... Args> struct can_append_multiple<std::tuple<Args...>> : std::false_type @@ -97,7 +97,7 @@ struct can_append_multiple<variant<Args...>> : std::false_type * * @tparam S - Type of element to append. */ -template <typename S> struct append_single +template <typename S, typename Enable = void> struct append_single { // Downcast template <typename T> using Td = types::details::type_id_downcast_t<T>; @@ -186,14 +186,17 @@ template <> struct append_single<bool> } }; -/** @brief Specialization of append_single for std::vectors. */ -template <typename T> struct append_single<std::vector<T>> +/** @brief Specialization of append_single for containers (ie vector, array, + * set, map, ect) */ +template <typename T> +struct append_single<T, std::enable_if_t<utility::has_const_iterator<T>::value>> { template <typename S> static void op(sd_bus_message* m, S&& s) { constexpr auto dbusType = utility::tuple_to_array(types::type_id<T>()); - sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, dbusType.data()); + sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, + dbusType.data() + 1); for (auto& i : s) { sdbusplus::message::append(m, i); @@ -217,23 +220,6 @@ template <typename T1, typename T2> struct append_single<std::pair<T1, T2>> } }; -/** @brief Specialization of append_single for std::maps. */ -template <typename T1, typename T2> struct append_single<std::map<T1, T2>> -{ - template <typename S> static void op(sd_bus_message* m, S&& s) - { - constexpr auto dbusType = utility::tuple_to_array( - types::type_id<typename std::map<T1, T2>::value_type>()); - - sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, dbusType.data()); - for (auto& i : s) - { - sdbusplus::message::append(m, i); - } - sd_bus_message_close_container(m); - } -}; - /** @brief Specialization of append_single for std::tuples. */ template <typename... Args> struct append_single<std::tuple<Args...>> { diff --git a/sdbusplus/message/read.hpp b/sdbusplus/message/read.hpp index df4ebc6..9f28c48 100644 --- a/sdbusplus/message/read.hpp +++ b/sdbusplus/message/read.hpp @@ -44,7 +44,8 @@ namespace details * User-defined types are expected to inherit from std::false_type. * */ -template <typename T> struct can_read_multiple : std::true_type +template <typename T, typename Enable = void> +struct can_read_multiple : std::true_type { }; // std::string needs a char* conversion. @@ -63,20 +64,23 @@ template <> struct can_read_multiple<signature> : std::false_type template <> struct can_read_multiple<bool> : std::false_type { }; -// std::vector needs a loop. -template <typename T> struct can_read_multiple<std::vector<T>> : std::false_type + +// std::vector/map/unordered_vector/set need loops +template <typename T> +struct can_read_multiple< + T, + typename std::enable_if<utility::has_emplace_method<T>::value || + utility::has_emplace_back_method<T>::value>::type> + : std::false_type { }; + // std::pair needs to be broken down into components. template <typename T1, typename T2> struct can_read_multiple<std::pair<T1, T2>> : std::false_type { }; -// std::map needs a loop. -template <typename T1, typename T2> -struct can_read_multiple<std::map<T1, T2>> : std::false_type -{ -}; + // std::tuple needs to be broken down into components. template <typename... Args> struct can_read_multiple<std::tuple<Args...>> : std::false_type @@ -96,7 +100,7 @@ struct can_read_multiple<variant<Args...>> : std::false_type * * @tparam S - Type of element to read. */ -template <typename S> struct read_single +template <typename S, typename Enable = void> struct read_single { // Downcast template <typename T> using Td = types::details::type_id_downcast_t<T>; @@ -169,60 +173,59 @@ template <> struct read_single<bool> }; /** @brief Specialization of read_single for std::vectors. */ -template <typename T> struct read_single<std::vector<T>> +template <typename T> +struct read_single<T, + std::enable_if_t<utility::has_emplace_back_method<T>::value>> { template <typename S> static void op(sd_bus_message* m, S&& s) { - s.clear(); - constexpr auto dbusType = utility::tuple_to_array(types::type_id<T>()); - sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, dbusType.data()); + sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, + dbusType.data() + 1); while (!sd_bus_message_at_end(m, false)) { - std::remove_const_t<T> t{}; + types::details::type_id_downcast_t<typename T::value_type> t; sdbusplus::message::read(m, t); - s.push_back(std::move(t)); + s.emplace_back(std::move(t)); } sd_bus_message_exit_container(m); } }; -/** @brief Specialization of read_single for std::pairs. */ -template <typename T1, typename T2> struct read_single<std::pair<T1, T2>> +/** @brief Specialization of read_single for std::map. */ +template <typename T> +struct read_single<T, std::enable_if_t<utility::has_emplace_method<T>::value>> { template <typename S> static void op(sd_bus_message* m, S&& s) { - constexpr auto dbusType = utility::tuple_to_array( - std::tuple_cat(types::type_id_nonull<T1>(), types::type_id<T2>())); + constexpr auto dbusType = utility::tuple_to_array(types::type_id<T>()); + sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, + dbusType.data() + 1); + + while (!sd_bus_message_at_end(m, false)) + { + types::details::type_id_downcast_t<typename T::value_type> t; + sdbusplus::message::read(m, t); + s.emplace(std::move(t)); + } - sd_bus_message_enter_container(m, SD_BUS_TYPE_DICT_ENTRY, - dbusType.data()); - sdbusplus::message::read(m, s.first, s.second); sd_bus_message_exit_container(m); } }; -/** @brief Specialization of read_single for std::maps. */ -template <typename T1, typename T2> struct read_single<std::map<T1, T2>> +/** @brief Specialization of read_single for std::pairs. */ +template <typename T1, typename T2> struct read_single<std::pair<T1, T2>> { template <typename S> static void op(sd_bus_message* m, S&& s) { - s.clear(); - constexpr auto dbusType = utility::tuple_to_array( - types::type_id<typename std::map<T1, T2>::value_type>()); - - sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, dbusType.data()); - - while (!sd_bus_message_at_end(m, false)) - { - std::pair<std::remove_const_t<T1>, std::remove_const_t<T2>> p{}; - sdbusplus::message::read(m, p); - s.insert(std::move(p)); - } + std::tuple_cat(types::type_id_nonull<T1>(), types::type_id<T2>())); + sd_bus_message_enter_container(m, SD_BUS_TYPE_DICT_ENTRY, + dbusType.data()); + sdbusplus::message::read(m, s.first, s.second); sd_bus_message_exit_container(m); } }; diff --git a/sdbusplus/message/types.hpp b/sdbusplus/message/types.hpp index 6a30dd6..5d3be09 100644 --- a/sdbusplus/message/types.hpp +++ b/sdbusplus/message/types.hpp @@ -7,6 +7,7 @@ #include <mapbox/variant.hpp> #include <systemd/sd-bus.h> +#include <sdbusplus/utility/container_traits.hpp> #include <sdbusplus/utility/type_traits.hpp> #include <sdbusplus/message/native_types.hpp> @@ -48,6 +49,35 @@ template <typename... Args> constexpr auto type_id_nonull(); namespace details { +/** @brief Downcast type submembers. + * + * This allows std::tuple and std::pair members to be downcast to their + * non-const, nonref versions of themselves to limit duplication in template + * specializations + * + * 1. Remove references. + * 2. Remove 'const' and 'volatile'. + * 3. Convert 'char[N]' to 'char*'. + */ +template <typename T> struct downcast_members +{ + using type = T; +}; +template <typename... Args> struct downcast_members<std::pair<Args...>> +{ + using type = std::pair<utility::array_to_ptr_t< + char, std::remove_cv_t<std::remove_reference_t<Args>>>...>; +}; + +template <typename... Args> struct downcast_members<std::tuple<Args...>> +{ + using type = std::tuple<utility::array_to_ptr_t< + char, std::remove_cv_t<std::remove_reference_t<Args>>>...>; +}; + +template <typename T> +using downcast_members_t = typename downcast_members<T>::type; + /** @brief Convert some C++ types to others for 'type_id' conversion purposes. * * Similar C++ types have the same dbus type-id, so 'downcast' those to limit @@ -59,8 +89,8 @@ namespace details */ template <typename T> struct type_id_downcast { - using type = typename utility::array_to_ptr_t< - char, std::remove_cv_t<std::remove_reference_t<T>>>; + using type = utility::array_to_ptr_t< + char, downcast_members_t<std::remove_cv_t<std::remove_reference_t<T>>>>; }; template <typename T> @@ -127,7 +157,8 @@ template <typename T, typename... Args> constexpr auto type_id_multiple(); * Struct must have a 'value' tuple containing the dbus type. The default * value is an empty tuple, which is used to indicate an unsupported type. */ -template <typename T> struct type_id : public undefined_type_id +template <typename T, typename Enable = void> +struct type_id : public undefined_type_id { }; // Specializations for built-in types. @@ -176,11 +207,13 @@ template <> struct type_id<signature> : tuple_type_id<SD_BUS_TYPE_SIGNATURE> { }; -template <typename T> struct type_id<std::vector<T>> +template <typename T> +struct type_id<T, std::enable_if_t<utility::has_const_iterator<T>::value>> + : std::false_type { - static constexpr auto value = - std::tuple_cat(tuple_type_id<SD_BUS_TYPE_ARRAY>::value, - type_id<type_id_downcast_t<T>>::value); + static constexpr auto value = std::tuple_cat( + tuple_type_id<SD_BUS_TYPE_ARRAY>::value, + type_id<type_id_downcast_t<typename T::value_type>>::value); }; template <typename T1, typename T2> struct type_id<std::pair<T1, T2>> @@ -192,13 +225,6 @@ template <typename T1, typename T2> struct type_id<std::pair<T1, T2>> tuple_type_id<SD_BUS_TYPE_DICT_ENTRY_END>::value); }; -template <typename T1, typename T2> struct type_id<std::map<T1, T2>> -{ - static constexpr auto value = - std::tuple_cat(tuple_type_id<SD_BUS_TYPE_ARRAY>::value, - type_id<typename std::map<T1, T2>::value_type>::value); -}; - template <typename... Args> struct type_id<std::tuple<Args...>> { static constexpr auto value = diff --git a/sdbusplus/utility/container_traits.hpp b/sdbusplus/utility/container_traits.hpp new file mode 100644 index 0000000..e778d17 --- /dev/null +++ b/sdbusplus/utility/container_traits.hpp @@ -0,0 +1,81 @@ +#pragma once + +namespace sdbusplus +{ +namespace utility +{ + +/** has_const_iterator - Determine if type has const iterator + * + * @tparam T - Type to be tested. + * + * @value A value as to whether or not the type supports iteration + */ +template <typename T> struct has_const_iterator +{ + private: + typedef char yes; + typedef struct + { + char array[2]; + } no; + + template <typename C> + static constexpr yes test(typename C::const_iterator*); + template <typename C> static constexpr no test(...); + + public: + static constexpr bool value = sizeof(test<T>(0)) == sizeof(yes); +}; + +/** has_emplace_method - Determine if type has a method template named emplace + * + * @tparam T - Type to be tested. + * + * @value A value as to whether or not the type has an emplace method + */ +template <typename T> struct has_emplace_method +{ + private: + struct dummy + { + }; + + template <typename C, typename P> + static constexpr auto test(P* p) + -> decltype(std::declval<C>().emplace(*p), std::true_type()); + + template <typename, typename> static std::false_type test(...); + + public: + static constexpr bool value = + std::is_same<std::true_type, decltype(test<T, dummy>(nullptr))>::value; +}; + +/** has_emplace_method - Determine if type has a method template named + * emplace_back + * + * @tparam T - Type to be tested. + * + * @value A value as to whether or not the type has an emplace_back method + */ +template <typename T> struct has_emplace_back_method +{ + private: + struct dummy + { + }; + + template <typename C, typename P> + static constexpr auto test(P* p) + -> decltype(std::declval<C>().emplace_back(*p), std::true_type()); + + template <typename, typename> static std::false_type test(...); + + public: + static constexpr bool value = + std::is_same<std::true_type, decltype(test<T, dummy>(nullptr))>::value; +}; + +} // namespace utility +} // namespace sdbusplus diff --git a/test/message/append.cpp b/test/message/append.cpp index 7782f02..c56402a 100644 --- a/test/message/append.cpp +++ b/test/message/append.cpp @@ -2,6 +2,8 @@ #include <cassert> #include <sdbusplus/message.hpp> #include <sdbusplus/bus.hpp> +#include <unordered_map> +#include <set> // Global to share the dbus type string between client and server. static std::string verifyTypeString; @@ -384,6 +386,139 @@ void runTests() b.call_noreply(m); } + // Test unordered_map. + { + auto m = newMethodCall__test(b); + std::unordered_map<std::string, int> s = {{"asdf", 3}, {"jkl;", 4}}; + m.append(1, s, 2); + verifyTypeString = "ia{si}i"; + + struct verify + { + static void op(sd_bus_message* m) + { + int32_t a = 0; + sd_bus_message_read(m, "i", &a); + assert(a == 1); + + auto rc = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, + "{si}"); + assert(0 <= rc); + + rc = sd_bus_message_enter_container(m, SD_BUS_TYPE_DICT_ENTRY, + "si"); + assert(0 <= rc); + + const char* s = nullptr; + sd_bus_message_read_basic(m, 's', &s); + sd_bus_message_read_basic(m, 'i', &a); + assert((0 == strcmp("asdf", s) && a == 3) || + (a = 4 && 0 == strcmp("jkl;", s))); + + assert(1 == sd_bus_message_at_end(m, false)); + sd_bus_message_exit_container(m); + + rc = sd_bus_message_enter_container(m, SD_BUS_TYPE_DICT_ENTRY, + "si"); + assert(0 <= rc); + + sd_bus_message_read_basic(m, 's', &s); + sd_bus_message_read_basic(m, 'i', &a); + assert((0 == strcmp("asdf", s) && a == 3) || + (a = 4 && 0 == strcmp("jkl;", s))); + + assert(1 == sd_bus_message_at_end(m, false)); + sd_bus_message_exit_container(m); + + assert(1 == sd_bus_message_at_end(m, false)); + sd_bus_message_exit_container(m); + + sd_bus_message_read(m, "i", &a); + assert(a == 2); + } + }; + verifyCallback = &verify::op; + + b.call_noreply(m); + } + + // Test set. + { + auto m = newMethodCall__test(b); + std::set<std::string> s = {{"asdf"}, {"jkl;"}}; + m.append(1, s, 2); + verifyTypeString = "iasi"; + + struct verify + { + static void op(sd_bus_message* m) + { + int32_t a = 0; + sd_bus_message_read(m, "i", &a); + assert(a == 1); + + auto rc = + sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "s"); + assert(0 <= rc); + + const char* s = nullptr; + sd_bus_message_read_basic(m, 's', &s); + assert(0 == strcmp("asdf", s)); + + sd_bus_message_read_basic(m, 's', &s); + assert(0 == strcmp("jkl;", s)); + + assert(1 == sd_bus_message_at_end(m, false)); + sd_bus_message_exit_container(m); + + sd_bus_message_read(m, "i", &a); + assert(a == 2); + } + }; + verifyCallback = &verify::op; + + b.call_noreply(m); + } + + // Test array. + { + auto m = newMethodCall__test(b); + std::array<std::string, 3> s{"1", "2", "3"}; + m.append(1, s, 2); + verifyTypeString = "iasi"; + + struct verify + { + static void op(sd_bus_message* m) + { + int32_t a = 0; + sd_bus_message_read(m, "i", &a); + assert(a == 1); + + auto rc = + sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "s"); + assert(0 <= rc); + + const char* s = nullptr; + sd_bus_message_read_basic(m, 's', &s); + assert(0 == strcmp("1", s)); + sd_bus_message_read_basic(m, 's', &s); + assert(0 == strcmp("2", s)); + sd_bus_message_read_basic(m, 's', &s); + assert(0 == strcmp("3", s)); + assert(1 == sd_bus_message_at_end(m, false)); + + sd_bus_message_exit_container(m); + + sd_bus_message_read(m, "i", &a); + assert(a == 2); + } + }; + verifyCallback = &verify::op; + + b.call_noreply(m); + } + // Test tuple. { auto m = newMethodCall__test(b); diff --git a/test/message/read.cpp b/test/message/read.cpp index 8c2c4ea..cd31b61 100644 --- a/test/message/read.cpp +++ b/test/message/read.cpp @@ -2,6 +2,8 @@ #include <cassert> #include <sdbusplus/message.hpp> #include <sdbusplus/bus.hpp> +#include <unordered_map> +#include <set> // Global to share the dbus type string between client and server. static std::string verifyTypeString; @@ -304,6 +306,60 @@ void runTests() b.call_noreply(m); } + // Test unordered_map. + { + auto m = newMethodCall__test(b); + std::unordered_map<std::string, int> s = {{"asdf", 3}, {"jkl;", 4}}; + m.append(1, s, 2); + verifyTypeString = "ia{si}i"; + + struct verify + { + static void op(sdbusplus::message::message& m) + { + int32_t a = 0, b = 0; + std::unordered_map<std::string, int> s{}; + + m.read(a, s, b); + assert(a == 1); + assert(s.size() == 2); + assert(s["asdf"] == 3); + assert(s["jkl;"] == 4); + assert(b == 2); + } + }; + verifyCallback = &verify::op; + + b.call_noreply(m); + } + + // Test set. + { + auto m = newMethodCall__test(b); + std::set<std::string> s = {{"asdf"}, {"jkl;"}}; + m.append(1, s, 2); + verifyTypeString = "iasi"; + + struct verify + { + static void op(sdbusplus::message::message& m) + { + int32_t a = 0, b = 0; + std::set<std::string> s{}; + + m.read(a, s, b); + assert(a == 1); + assert(s.size() == 2); + assert(s.find("asdf") != s.end()); + assert(s.find("jkl;") != s.end()); + assert(b == 2); + } + }; + verifyCallback = &verify::op; + + b.call_noreply(m); + } + // Test tuple. { auto m = newMethodCall__test(b); |