diff options
| author | Patrick Williams <patrick@stwcx.xyz> | 2016-09-08 15:03:56 -0500 |
|---|---|---|
| committer | Patrick Williams <patrick@stwcx.xyz> | 2016-09-12 21:54:52 -0500 |
| commit | 4fe85a30e7b2c7cdbc08c28971a8f2f7b8c4de85 (patch) | |
| tree | 50778bc705a8b92f8286a1cba29f77152f3dc502 | |
| parent | 13b97c527d0df89a03c4efbe7644e25aa4f4efbd (diff) | |
| download | sdbusplus-4fe85a30e7b2c7cdbc08c28971a8f2f7b8c4de85.tar.gz sdbusplus-4fe85a30e7b2c7cdbc08c28971a8f2f7b8c4de85.zip | |
Add message read functionality
C++ bindings for sd_bus_message_read* functions. Similar compile-time
type deduction as the message::append interfaces.
Change-Id: I88639bedb9703266f7282642ce261c28b736adfc
| -rw-r--r-- | sdbusplus/message.hpp | 36 | ||||
| -rw-r--r-- | sdbusplus/message/read.hpp | 262 | ||||
| -rw-r--r-- | test/message/read.cpp | 219 |
3 files changed, 517 insertions, 0 deletions
diff --git a/sdbusplus/message.hpp b/sdbusplus/message.hpp index acaa04d..1d60c81 100644 --- a/sdbusplus/message.hpp +++ b/sdbusplus/message.hpp @@ -3,6 +3,7 @@ #include <memory> #include <systemd/sd-bus.h> #include <sdbusplus/message/append.hpp> +#include <sdbusplus/message/read.hpp> namespace sdbusplus { @@ -62,6 +63,10 @@ struct message /** @brief Release ownership of the stored msg-pointer. */ msgp_t release() { return _msg.release(); } + /** @brief Check if message contains a real pointer. (non-nullptr). */ + explicit operator bool() const { return bool(_msg); } + + /** @brief Perform sd_bus_message_append, with automatic type deduction. * * @tparam ...Args - Type of items to append to message. @@ -72,6 +77,37 @@ struct message sdbusplus::message::append(_msg.get(), std::forward<Args>(args)...); } + /** @brief Perform sd_bus_message_read, with automatic type deduction. + * + * @tparam ...Args - Type of items to read from message. + * @param[out] args - Items to read from message. + */ + template <typename ...Args> void read(Args&&... args) + { + sdbusplus::message::read(_msg.get(), std::forward<Args>(args)...); + } + + /** @brief Get the signature of a message. + * + * @return A [weak] pointer to the signature of the message. + */ + const char* get_signature() + { + return sd_bus_message_get_signature(_msg.get(), true); + } + + /** @brief Check if message is a method call for an interface/method. + * + * @param[in] interface - The interface to match. + * @param[in] method - The method to match. + * + * @return True - if message is a method call for interface/method. + */ + bool is_method_call(const char* interface, const char* method) + { + return sd_bus_message_is_method_call(_msg.get(), interface, method); + } + friend struct sdbusplus::bus::bus; private: diff --git a/sdbusplus/message/read.hpp b/sdbusplus/message/read.hpp new file mode 100644 index 0000000..ea7ccd1 --- /dev/null +++ b/sdbusplus/message/read.hpp @@ -0,0 +1,262 @@ +#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 Read data from an sdbus message. + * + * (This is an empty no-op function that is useful in some cases for + * variadic template reasons.) + */ +inline void read(sd_bus_message* m) {}; +/** @brief Read data from an sdbus message. + * + * @param[in] msg - The message to read from. + * @tparam Args - C++ types of arguments to read from message. + * @param[out] args - References to place contents read from message. + * + * This function will, at compile-time, deduce the DBus types of the passed + * C++ values and call the sd_bus_message_read 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 read(sd_bus_message* m, Args&&... args); + +namespace details +{ + +/** @struct can_read_multiple + * @brief Utility to identify C++ types that may not be grouped into a + * single sd_bus_message_read 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_read_multiple : std::true_type {}; + // std::string needs a c_str() call. +template<> struct can_read_multiple<std::string> : std::false_type {}; + +/** @struct read_single + * @brief Utility to read a single C++ element from 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 read. + */ +template<typename S> struct read_single +{ + // Downcast + template<typename T> + using Td = types::details::type_id_downcast_t<T>; + + /** @brief Do the operation to read element. + * + * @tparam T - Type of element to read. + * + * 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 read from. + * @param[out] t - The reference to read item into. + */ + 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_read_basic(m, dbusType, &t); + } +}; + +template<typename T> using read_single_t = + read_single<types::details::type_id_downcast_t<T>>; + +/** @brief Specialization of read_single for std::strings. */ +template <> struct read_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>()); + const char* str = nullptr; + sd_bus_message_read_basic(m, dbusType, &str); + s = str; + } +}; + +/** @brief Read a tuple of content from the sd_bus_message. + * + * @tparam Tuple - The tuple type to read. + * @param[out] t - The tuple references to read. + * @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 read_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_read(m, dbusTypes.data(), &std::get<I>(t)...); +} + +/** @brief Read a tuple of 2 or more entries from the sd_bus_message. + * + * @tparam Tuple - The tuple type to read. + * @param[out] t - The tuple to read into. + * + * A tuple of 2 or more entries can be read as a set with + * sd_bus_message_read. + */ +template <typename Tuple> std::enable_if_t<2 <= std::tuple_size<Tuple>::value> +read_tuple(sd_bus_message* m, Tuple&& t) +{ + read_tuple(m, std::move(t), + std::make_index_sequence<std::tuple_size<Tuple>::value>()); +} + +/** @brief Read a tuple of exactly 1 entry from the sd_bus_message. + * + * @tparam Tuple - The tuple type to read. + * @param[out] t - The tuple to read into. + * + * A tuple of 1 entry can be read with sd_bus_message_read_basic. + * + * Note: Some 1-entry tuples may need special handling due to + * can_read_multiple::value == false. + */ +template <typename Tuple> std::enable_if_t<1 == std::tuple_size<Tuple>::value> +read_tuple(sd_bus_message* m, Tuple&& t) +{ + using itemType = decltype(std::get<0>(t)); + read_single_t<itemType>::op(m, std::forward<itemType>(std::get<0>(t))); +} + +/** @brief Read 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 read_tuple(sd_bus_message* m, Tuple&& t) {} + +/** @brief Group a sequence of C++ types for reading from an sd_bus_message. + * @tparam Tuple - A tuple of previously analyzed types. + * @tparam Arg - The argument to analyze for grouping. + * + * Specialization for when can_read_multiple<Arg> is true. + */ +template <typename Tuple, typename Arg> std::enable_if_t< + can_read_multiple<types::details::type_id_downcast_t<Arg>>::value> +read_grouping(sd_bus_message* m, Tuple&& t, Arg&& arg); +/** @brief Group a sequence of C++ types for reading from an sd_bus_message. + * @tparam Tuple - A tuple of previously analyzed types. + * @tparam Arg - The argument to analyze for grouping. + * + * Specialization for when can_read_multiple<Arg> is false. + */ +template <typename Tuple, typename Arg> std::enable_if_t< + !can_read_multiple<types::details::type_id_downcast_t<Arg>>::value> +read_grouping(sd_bus_message* m, Tuple&& t, Arg&& arg); +/** @brief Group a sequence of C++ types for reading from 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_read_multiple<Arg> is true. + */ +template <typename Tuple, typename Arg, typename ...Rest> std::enable_if_t< + can_read_multiple<types::details::type_id_downcast_t<Arg>>::value> +read_grouping(sd_bus_message* m, Tuple&& t, Arg&& arg, Rest&&... rest); +/** @brief Group a sequence of C++ types for reading from 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_read_multiple<Arg> is false. + */ +template <typename Tuple, typename Arg, typename ...Rest> std::enable_if_t< + !can_read_multiple<types::details::type_id_downcast_t<Arg>>::value> +read_grouping(sd_bus_message* m, Tuple&& t, Arg&& arg, Rest&&... rest); + +template <typename Tuple, typename Arg> std::enable_if_t< + can_read_multiple<types::details::type_id_downcast_t<Arg>>::value> +read_grouping(sd_bus_message* m, Tuple&& t, Arg&& arg) +{ + // Last element of a sequence and can_read_multiple, so add it to + // the tuple and call read_tuple. + + read_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_read_multiple<types::details::type_id_downcast_t<Arg>>::value> +read_grouping(sd_bus_message* m, Tuple&& t, Arg&& arg) +{ + // Last element of a sequence but !can_read_multiple, so call + // read_tuple on the previous elements and separately this single + // element. + + read_tuple(m, std::forward<Tuple>(t)); + read_tuple(m, std::forward_as_tuple(std::forward<Arg>(arg))); +} + +template <typename Tuple, typename Arg, typename ...Rest> std::enable_if_t< + can_read_multiple<types::details::type_id_downcast_t<Arg>>::value> +read_grouping(sd_bus_message* m, Tuple&& t, Arg&& arg, Rest&&... rest) +{ + // Not the last element of a sequence and can_read_multiple, so add it + // to the tuple and keep grouping. + + read_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_read_multiple<types::details::type_id_downcast_t<Arg>>::value> +read_grouping(sd_bus_message* m, Tuple&& t, Arg&& arg, Rest&&... rest) +{ + // Not the last element of a sequence but !can_read_multiple, so call + // read_tuple on the previous elements and separately this single + // element and then group the remaining elements. + + read_tuple(m, std::forward<Tuple>(t)); + read_tuple(m, std::forward_as_tuple(std::forward<Arg>(arg))); + read_grouping(m, std::make_tuple(), std::forward<Rest>(rest)...); +} + +} // namespace details + +template <typename ...Args> void read(sd_bus_message* m, Args&&... args) +{ + details::read_grouping(m, std::make_tuple(), std::forward<Args>(args)...); +} + +} // namespace message + +} // namespace sdbusplus diff --git a/test/message/read.cpp b/test/message/read.cpp new file mode 100644 index 0000000..48c445e --- /dev/null +++ b/test/message/read.cpp @@ -0,0 +1,219 @@ +#include <iostream> +#include <cassert> +#include <sdbusplus/message.hpp> +#include <sdbusplus/bus.hpp> + +// Global to share the dbus type string between client and server. +static std::string verifyTypeString; + +using verifyCallback_t = void(*)(sdbusplus::message::message&); +verifyCallback_t verifyCallback = nullptr; + +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. +auto serverInit() +{ + auto b = sdbusplus::bus::new_default(); + b.request_name(SERVICE); + + return std::move(b); +} + +// Thread to run the dbus server. +void* server(void* b) +{ + auto bus = sdbusplus::bus::bus(reinterpret_cast<sdbusplus::bus::busp_t>(b)); + + while(1) + { + // Wait for messages. + auto m = bus.process(); + + if(!m) + { + bus.wait(); + continue; + } + + if (m.is_method_call(INTERFACE, TEST_METHOD)) + { + // Verify the message type matches what the test expects. + assert(verifyTypeString == m.get_signature()); + + if (verifyCallback) + { + + verifyCallback(m); + verifyCallback = nullptr; + } + else + { + std::cout << "Warning: No verification for " + << verifyTypeString << std::endl; + } + // Reply to client. + sd_bus_reply_method_return(m.release(), nullptr); + } + else if (m.is_method_call(INTERFACE, QUIT_METHOD)) + { + // Reply and exit. + sd_bus_reply_method_return(m.release(), nullptr); + break; + } + } +} + +auto newMethodCall__test(sdbusplus::bus::bus& b) +{ + // Allocate a method-call message for INTERFACE,TEST_METHOD. + return b.new_method_call(SERVICE, "/", INTERFACE, TEST_METHOD); +} + +void runTests() +{ + using namespace std::literals; + + auto b = sdbusplus::bus::new_default(); + + // Test r-value int. + { + auto m = newMethodCall__test(b); + m.append(1); + verifyTypeString = "i"; + + struct verify + { + static void op(sdbusplus::message::message& m) + { + int32_t i = 0; + m.read(i); + assert(i == 1); + } + }; + verifyCallback = &verify::op; + + b.call_noreply(m); + } + // Test l-value int. + { + auto m = newMethodCall__test(b); + int a = 1; + m.append(a, a); + verifyTypeString = "ii"; + + struct verify + { + static void op(sdbusplus::message::message& m) + { + int32_t a = 0, b = 0; + m.read(a, b); + assert(a == 1); + assert(b == 1); + } + }; + verifyCallback = &verify::op; + + b.call_noreply(m); + } + + // Test multiple ints. + { + auto m = newMethodCall__test(b); + m.append(1, 2, 3, 4, 5); + verifyTypeString = "iiiii"; + + struct verify + { + static void op(sdbusplus::message::message& m) + { + int32_t a = 0, b = 0, c = 0, d = 0, e = 0; + m.read(a,b,c,d,e); + assert(a == 1); + assert(b == 2); + assert(c == 3); + assert(d == 4); + assert(e == 5); + } + }; + verifyCallback = &verify::op; + + b.call_noreply(m); + } + + // Test r-value string. + { + auto m = newMethodCall__test(b); + m.append("asdf"s); + verifyTypeString = "s"; + + struct verify + { + static void op(sdbusplus::message::message& m) + { + const char* s = nullptr; + m.read(s); + assert(0 == strcmp("asdf", s)); + } + }; + verifyCallback = &verify::op; + + b.call_noreply(m); + } + + // Test multiple strings, various forms. + { + auto m = newMethodCall__test(b); + auto str = "jkl;"s; + auto str2 = "JKL:"s; + m.append(1, "asdf", "ASDF"s, str, + std::move(str2), 5); + verifyTypeString = "issssi"; + + struct verify + { + static void op(sdbusplus::message::message& m) + { + int32_t a = 0, b = 0; + std::string s0, s1, s2, s3; + m.read(a, s0, s1, s2, s3, b); + assert(a == 1); + assert(b == 5); + assert(s0 == "asdf"s); + assert(s1 == "ASDF"s); + assert(s2 == "jkl;"s); + assert(s3 == "JKL:"); + } + }; + verifyCallback = &verify::op; + + b.call_noreply(m); + } + + // Shutdown server. + { + auto m = b.new_method_call(SERVICE, "/", INTERFACE, QUIT_METHOD); + b.call_noreply(m); + } +} + +int main() +{ + + // Initialize and start server thread. + pthread_t t; + { + auto b = serverInit(); + pthread_create(&t, NULL, server, b.release()); + } + + runTests(); + + // Wait for server thread to exit. + pthread_join(t, NULL); + + return 0; +} |

