Add message append functionality
Adds C++ binding to sd_bus_message_append* class functions. Uses
type deduction to identify all of the parameters passed to the
append function and, at compile-time, creates the appropriate dbus
type string.
Example:
sdbusplus::message::append(m, "asdf"s);
Is equal to:
sd_bus_message_append_basic(m, 's', ("asdf"s).c_str());
Rather than use a naive sequence of ..._append_basic calls, the
implementation will attempt to group multiple basic types into a
single sd_bus_message_append call.
Change-Id: I1341a0299573c61588fe225c07174b743cb3a282
Signed-off-by: Patrick Williams <patrick@stwcx.xyz>
diff --git a/sdbusplus/message/append.hpp b/sdbusplus/message/append.hpp
new file mode 100644
index 0000000..7ce429b
--- /dev/null
+++ b/sdbusplus/message/append.hpp
@@ -0,0 +1,260 @@
+#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>
+
+namespace sdbusplus
+{
+
+namespace message
+{
+
+/** @brief Append data into an sdbus message.
+ *
+ * (This is an empty no-op function that is useful in some cases for
+ * variadic template reasons.)
+ */
+inline void append(sd_bus_message* m) {};
+/** @brief Append data into an sdbus message.
+ *
+ * @param[in] msg - The message to append to.
+ * @tparam Args - C++ types of arguments to append to message.
+ * @param[in] args - values to append to message.
+ *
+ * This function will, at compile-time, deduce the DBus types of the passed
+ * C++ values and call the sd_bus_message_append functions with the
+ * appropriate type parameters. It may also do conversions, where needed,
+ * to convert C++ types into C representations (eg. string, vector).
+ */
+template <typename ...Args> void append(sd_bus_message* m, Args&&... args);
+
+namespace details
+{
+
+/** @struct can_append_multiple
+ * @brief Utility to identify C++ types that may not be grouped into a
+ * single sd_bus_message_append call and instead need special
+ * handling.
+ *
+ * @tparam T - Type for identification.
+ *
+ * User-defined types are expected to inherit from std::false_type.
+ *
+ */
+template<typename T> struct can_append_multiple : std::true_type {};
+ // std::string needs a c_str() call.
+template<> struct can_append_multiple<std::string> : std::false_type {};
+
+/** @struct append_single
+ * @brief Utility to append a single C++ element into a sd_bus_message.
+ *
+ * User-defined types are expected to specialize this template in order to
+ * get their functionality.
+ *
+ * @tparam S - Type of element to append.
+ */
+template<typename S> struct append_single
+{
+ // Downcast
+ template<typename T>
+ using Td = types::details::type_id_downcast_t<T>;
+
+ /** @brief Do the operation to append element.
+ *
+ * @tparam T - Type of element to append.
+ *
+ * Template parameters T (function) and S (class) are different
+ * to allow the function to be utilized for many varients of S:
+ * S&, S&&, const S&, volatile S&, etc. The type_id_downcast is used
+ * to ensure T and S are equivalent. For 'char*', this also allows
+ * use for 'char[N]' types.
+ *
+ * @param[in] m - sd_bus_message to append into.
+ * @param[in] t - The item to append.
+ */
+ template<typename T,
+ typename = std::enable_if_t<std::is_same<S, Td<T>>::value>>
+ static void op(sd_bus_message* m, T&& t)
+ {
+ // For this default implementation, we need to ensure that only
+ // basic types are used.
+ static_assert(std::is_fundamental<Td<T>>::value ||
+ std::is_convertible<Td<T>, const char*>::value,
+ "Non-basic types are not allowed.");
+
+ constexpr auto dbusType = std::get<0>(types::type_id<T>());
+ sd_bus_message_append_basic(m, dbusType, &t);
+ }
+};
+
+template<typename T> using append_single_t =
+ append_single<types::details::type_id_downcast_t<T>>;
+
+/** @brief Specialization of append_single for std::strings. */
+template <> struct append_single<std::string>
+{
+ template<typename T>
+ static void op(sd_bus_message* m, T&& s)
+ {
+ constexpr auto dbusType = std::get<0>(types::type_id<T>());
+ sd_bus_message_append_basic(m, dbusType, s.c_str());
+ }
+};
+
+/** @brief Append a tuple of content into the sd_bus_message.
+ *
+ * @tparam Tuple - The tuple type to append.
+ * @param[in] t - The tuple value to append.
+ * @tparam I - The indexes of the tuple type Tuple.
+ * @param[in] [unamed] - unused index_sequence for type deduction of I.
+ */
+template <typename Tuple, size_t... I>
+void append_tuple(sd_bus_message* m, Tuple&& t, std::index_sequence<I...>)
+{
+ auto dbusTypes = utility::tuple_to_array(
+ types::type_id<decltype(std::get<I>(t))...>());
+
+ sd_bus_message_append(m, dbusTypes.data(), std::get<I>(t)...);
+}
+
+/** @brief Append a tuple of 2 or more entries into the sd_bus_message.
+ *
+ * @tparam Tuple - The tuple type to append.
+ * @param[in] t - The tuple to append.
+ *
+ * A tuple of 2 or more entries can be added as a set with
+ * sd_bus_message_append.
+ */
+template <typename Tuple> std::enable_if_t<2 <= std::tuple_size<Tuple>::value>
+append_tuple(sd_bus_message* m, Tuple&& t)
+{
+ append_tuple(m, std::move(t),
+ std::make_index_sequence<std::tuple_size<Tuple>::value>());
+}
+
+/** @brief Append a tuple of exactly 1 entry into the sd_bus_message.
+ *
+ * @tparam Tuple - The tuple type to append.
+ * @param[in] t - The tuple to append.
+ *
+ * A tuple of 1 entry can be added with sd_bus_message_append_basic.
+ *
+ * Note: Some 1-entry tuples may need special handling due to
+ * can_append_multiple::value == false.
+ */
+template <typename Tuple> std::enable_if_t<1 == std::tuple_size<Tuple>::value>
+append_tuple(sd_bus_message* m, Tuple&& t)
+{
+ using itemType = decltype(std::get<0>(t));
+ append_single_t<itemType>::op(m, std::forward<itemType>(std::get<0>(t)));
+}
+
+/** @brief Append a tuple of 0 entries - no-op.
+ *
+ * This a no-op function that is useful due to variadic templates.
+ */
+template <typename Tuple> std::enable_if_t<0 == std::tuple_size<Tuple>::value>
+inline append_tuple(sd_bus_message* m, Tuple&& t) {}
+
+/** @brief Group a sequence of C++ types for appending into an sd_bus_message.
+ * @tparam Tuple - A tuple of previously analyzed types.
+ * @tparam Arg - The argument to analyze for grouping.
+ *
+ * Specialization for when can_append_multiple<Arg> is true.
+ */
+template <typename Tuple, typename Arg> std::enable_if_t<
+ can_append_multiple<types::details::type_id_downcast_t<Arg>>::value>
+append_grouping(sd_bus_message* m, Tuple&& t, Arg&& arg);
+/** @brief Group a sequence of C++ types for appending into an sd_bus_message.
+ * @tparam Tuple - A tuple of previously analyzed types.
+ * @tparam Arg - The argument to analyze for grouping.
+ *
+ * Specialization for when can_append_multiple<Arg> is false.
+ */
+template <typename Tuple, typename Arg> std::enable_if_t<
+ !can_append_multiple<types::details::type_id_downcast_t<Arg>>::value>
+append_grouping(sd_bus_message* m, Tuple&& t, Arg&& arg);
+/** @brief Group a sequence of C++ types for appending into an sd_bus_message.
+ * @tparam Tuple - A tuple of previously analyzed types.
+ * @tparam Arg - The argument to analyze for grouping.
+ * @tparam Rest - The remaining arguments left to analyze.
+ *
+ * Specialization for when can_append_multiple<Arg> is true.
+ */
+template <typename Tuple, typename Arg, typename ...Rest> std::enable_if_t<
+ can_append_multiple<types::details::type_id_downcast_t<Arg>>::value>
+append_grouping(sd_bus_message* m, Tuple&& t, Arg&& arg, Rest&&... rest);
+/** @brief Group a sequence of C++ types for appending into an sd_bus_message.
+ * @tparam Tuple - A tuple of previously analyzed types.
+ * @tparam Arg - The argument to analyze for grouping.
+ * @tparam Rest - The remaining arguments left to analyze.
+ *
+ * Specialization for when can_append_multiple<Arg> is false.
+ */
+template <typename Tuple, typename Arg, typename ...Rest> std::enable_if_t<
+ !can_append_multiple<types::details::type_id_downcast_t<Arg>>::value>
+append_grouping(sd_bus_message* m, Tuple&& t, Arg&& arg, Rest&&... rest);
+
+template <typename Tuple, typename Arg> std::enable_if_t<
+ can_append_multiple<types::details::type_id_downcast_t<Arg>>::value>
+append_grouping(sd_bus_message* m, Tuple&& t, Arg&& arg)
+{
+ // Last element of a sequence and can_append_multiple, so add it to
+ // the tuple and call append_tuple.
+
+ append_tuple(m, std::tuple_cat(std::forward<Tuple>(t),
+ std::forward_as_tuple(
+ std::forward<Arg>(arg))));
+}
+
+template <typename Tuple, typename Arg> std::enable_if_t<
+ !can_append_multiple<types::details::type_id_downcast_t<Arg>>::value>
+append_grouping(sd_bus_message* m, Tuple&& t, Arg&& arg)
+{
+ // Last element of a sequence but !can_append_multiple, so call
+ // append_tuple on the previous elements and separately this single
+ // element.
+
+ append_tuple(m, std::forward<Tuple>(t));
+ append_tuple(m, std::forward_as_tuple(std::forward<Arg>(arg)));
+}
+
+template <typename Tuple, typename Arg, typename ...Rest> std::enable_if_t<
+ can_append_multiple<types::details::type_id_downcast_t<Arg>>::value>
+append_grouping(sd_bus_message* m, Tuple&& t, Arg&& arg, Rest&&... rest)
+{
+ // Not the last element of a sequence and can_append_multiple, so add it
+ // to the tuple and keep grouping.
+
+ append_grouping(m, std::tuple_cat(std::forward<Tuple>(t),
+ std::forward_as_tuple(
+ std::forward<Arg>(arg))),
+ std::forward<Rest>(rest)...);
+}
+
+template <typename Tuple, typename Arg, typename ...Rest> std::enable_if_t<
+ !can_append_multiple<types::details::type_id_downcast_t<Arg>>::value>
+append_grouping(sd_bus_message* m, Tuple&& t, Arg&& arg, Rest&&... rest)
+{
+ // Not the last element of a sequence but !can_append_multiple, so call
+ // append_tuple on the previous elements and separately this single
+ // element and then group the remaining elements.
+
+ append_tuple(m, std::forward<Tuple>(t));
+ append_tuple(m, std::forward_as_tuple(std::forward<Arg>(arg)));
+ append_grouping(m, std::make_tuple(), std::forward<Rest>(rest)...);
+}
+
+} // namespace details
+
+template <typename ...Args> void append(sd_bus_message* m, Args&&... args)
+{
+ details::append_grouping(m, std::make_tuple(), std::forward<Args>(args)...);
+}
+
+} // namespace message
+
+} // namespace sdbusplus
diff --git a/test/message/append.cpp b/test/message/append.cpp
new file mode 100644
index 0000000..1b0f70b
--- /dev/null
+++ b/test/message/append.cpp
@@ -0,0 +1,140 @@
+#include <iostream>
+#include <sdbusplus/message/append.hpp>
+#include <cassert>
+
+// Global to share the dbus type string between client and server.
+static std::string verifyTypeString;
+
+static constexpr auto SERVICE = "sdbusplus.test";
+static constexpr auto INTERFACE = SERVICE;
+static constexpr auto TEST_METHOD = "test";
+static constexpr auto QUIT_METHOD = "quit";
+
+// Open up the sdbus and claim SERVICE name.
+void serverInit(sd_bus** b)
+{
+ assert(0 <= sd_bus_open(b));
+ assert(0 <= sd_bus_request_name(*b, SERVICE, 0));
+}
+
+// Thread to run the dbus server.
+void* server(void* b)
+{
+ auto bus = reinterpret_cast<sd_bus*>(b);
+
+ while(1)
+ {
+ // Wait for messages.
+ sd_bus_message *m = nullptr;
+ if (0 == sd_bus_process(bus, &m))
+ {
+ sd_bus_wait(bus, 0);
+ continue;
+ }
+
+ if(!m)
+ {
+ continue;
+ }
+
+ if (sd_bus_message_is_method_call(m, INTERFACE, TEST_METHOD))
+ {
+ // Verify the message type matches what the test expects.
+ // TODO: It would be nice to verify content here as well.
+ assert(verifyTypeString == sd_bus_message_get_signature(m, true));
+ // Reply to client.
+ sd_bus_reply_method_return(m, nullptr);
+ }
+ else if (sd_bus_message_is_method_call(m, INTERFACE, QUIT_METHOD))
+ {
+ // Reply and exit.
+ sd_bus_reply_method_return(m, nullptr);
+ break;
+ }
+ }
+}
+
+void newMethodCall__test(sd_bus* b, sd_bus_message** m)
+{
+ // Allocate a method-call message for INTERFACE,TEST_METHOD.
+ assert(0 <= sd_bus_message_new_method_call(b, m, SERVICE, "/", INTERFACE,
+ TEST_METHOD));
+ sd_bus_message_set_expect_reply(*m, true);
+}
+
+void runTests()
+{
+ using namespace std::literals;
+
+ sd_bus_message* m = nullptr;
+ sd_bus* b = nullptr;
+
+ // Connect to dbus.
+ assert(0 <= sd_bus_open(&b));
+
+ // Test r-value int.
+ {
+ newMethodCall__test(b, &m);
+ sdbusplus::message::append(m, 1);
+ verifyTypeString = "i";
+ sd_bus_call(b, m, 0, nullptr, nullptr);
+ }
+
+ // Test l-value int.
+ {
+ newMethodCall__test(b, &m);
+ int a = 1;
+ sdbusplus::message::append(m, a, a);
+ verifyTypeString = "ii";
+ sd_bus_call(b, m, 0, nullptr, nullptr);
+ }
+
+ // Test multiple ints.
+ {
+ newMethodCall__test(b, &m);
+ sdbusplus::message::append(m, 1, 2, 3, 4, 5);
+ verifyTypeString = "iiiii";
+ sd_bus_call(b, m, 0, nullptr, nullptr);
+ }
+
+ // Test r-value string.
+ {
+ newMethodCall__test(b, &m);
+ sdbusplus::message::append(m, "asdf"s);
+ verifyTypeString = "s";
+ sd_bus_call(b, m, 0, nullptr, nullptr);
+ }
+
+ // Test multiple strings, various forms.
+ {
+ newMethodCall__test(b, &m);
+ auto str = "jkl;"s;
+ auto str2 = "JKL:"s;
+ sdbusplus::message::append(m, 1, "asdf", "ASDF"s, str,
+ std::move(str2), 5);
+ verifyTypeString = "issssi";
+ sd_bus_call(b, m, 0, nullptr, nullptr);
+ }
+
+ // Shutdown server.
+ sd_bus_call_method(b, SERVICE, "/", INTERFACE, QUIT_METHOD,
+ nullptr, nullptr, nullptr);
+}
+
+int main()
+{
+ // Initialize and start server thread.
+ pthread_t t;
+ {
+ sd_bus* b;
+ serverInit(&b);
+ pthread_create(&t, NULL, server, b);
+ }
+
+ runTests();
+
+ // Wait for server thread to exit.
+ pthread_join(t, NULL);
+
+ return 0;
+}