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
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 @@
     /** @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 @@
         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;
+}