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>
diff --git a/Makefile.am b/Makefile.am
index 037640c..8c96b3e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -17,6 +17,7 @@
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 <sdbusplus/message/types.hpp>
-#include <sdbusplus/utility/type_traits.hpp>
-#include <sdbusplus/utility/tuple_to_array.hpp>
+
#include <systemd/sd-bus.h>
+#include <sdbusplus/message/types.hpp>
+#include <sdbusplus/utility/container_traits.hpp>
+#include <sdbusplus/utility/tuple_to_array.hpp>
+#include <sdbusplus/utility/type_traits.hpp>
namespace sdbusplus
{
@@ -44,7 +46,8 @@
* 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<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 @@
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 @@
*
* @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 @@
}
};
-/** @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 @@
}
};
-/** @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 @@
* 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<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 @@
*
* @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,20 +173,42 @@
};
/** @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::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(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_exit_container(m);
@@ -204,29 +230,6 @@
}
};
-/** @brief Specialization of read_single for std::maps. */
-template <typename T1, typename T2> struct read_single<std::map<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));
- }
-
- sd_bus_message_exit_container(m);
- }
-};
-
/** @brief Specialization of read_single for std::tuples. */
template <typename... Args> struct read_single<std::tuple<Args...>>
{
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 @@
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 @@
*/
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 @@
* 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 <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 @@
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 @@
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 @@
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);