std::tuple support for append/read

Change-Id: If914032fa4f655af509ac58a5e0057968f71aa32
Signed-off-by: Patrick Williams <patrick@stwcx.xyz>
diff --git a/sdbusplus/message/append.hpp b/sdbusplus/message/append.hpp
index da8dc1a..2db790b 100644
--- a/sdbusplus/message/append.hpp
+++ b/sdbusplus/message/append.hpp
@@ -56,6 +56,9 @@
     // 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 {};
 
 /** @struct append_single
  *  @brief Utility to append a single C++ element into a sd_bus_message.
@@ -159,6 +162,32 @@
     }
 };
 
+/** @brief Specialization of append_single for std::tuples. */
+template <typename ...Args> struct append_single<std::tuple<Args...>>
+{
+    template<typename S, std::size_t... I>
+    static void _op(sd_bus_message* m, S&& s,
+                    std::integer_sequence<std::size_t, I...>)
+    {
+        sdbusplus::message::append(m, std::get<I>(s)...);
+    }
+
+    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<Args...>(),
+                std::make_tuple('\0') /* null terminator for C-string */));
+
+        sd_bus_message_open_container(
+                m, SD_BUS_TYPE_STRUCT, dbusType.data());
+        _op(m, std::forward<S>(s),
+            std::make_index_sequence<sizeof...(Args)>());
+        sd_bus_message_close_container(m);
+
+    }
+};
+
 /** @brief Append a tuple of content into the sd_bus_message.
  *
  *  @tparam Tuple - The tuple type to append.
diff --git a/sdbusplus/message/read.hpp b/sdbusplus/message/read.hpp
index 6291409..47e2392 100644
--- a/sdbusplus/message/read.hpp
+++ b/sdbusplus/message/read.hpp
@@ -56,6 +56,9 @@
     // 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 {};
 
 /** @struct read_single
  *  @brief Utility to read a single C++ element from a sd_bus_message.
@@ -178,6 +181,31 @@
     }
 };
 
+/** @brief Specialization of read_single for std::tuples. */
+template <typename ...Args> struct read_single<std::tuple<Args...>>
+{
+    template<typename S, std::size_t... I>
+    static void _op(sd_bus_message* m, S&& s,
+                    std::integer_sequence<std::size_t, I...>)
+    {
+        sdbusplus::message::read(m, std::get<I>(s)...);
+    }
+
+    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<Args...>(),
+                std::make_tuple('\0') /* null terminator for C-string */));
+
+        sd_bus_message_enter_container(
+                m, SD_BUS_TYPE_STRUCT, dbusType.data());
+        _op(m, std::forward<S>(s),
+            std::make_index_sequence<sizeof...(Args)>());
+        sd_bus_message_exit_container(m);
+
+    }
+};
 
 /** @brief Read a tuple of content from the sd_bus_message.
  *
diff --git a/sdbusplus/message/types.hpp b/sdbusplus/message/types.hpp
index 3e39aca..1876fa2 100644
--- a/sdbusplus/message/types.hpp
+++ b/sdbusplus/message/types.hpp
@@ -161,6 +161,14 @@
         type_id<typename std::map<T1,T2>::value_type>::value);
 };
 
+template <typename ...Args> struct type_id<std::tuple<Args...>>
+{
+    static constexpr auto value = std::tuple_cat(
+        tuple_type_id<SD_BUS_TYPE_STRUCT_BEGIN>::value,
+        type_id<type_id_downcast_t<Args>>::value...,
+        tuple_type_id<SD_BUS_TYPE_STRUCT_END>::value);
+};
+
 template <typename T> constexpr auto& type_id_single()
 {
     static_assert(!std::is_base_of<undefined_type_id, type_id<T>>::value,
diff --git a/test/message/append.cpp b/test/message/append.cpp
index 4734815..03ab781 100644
--- a/test/message/append.cpp
+++ b/test/message/append.cpp
@@ -293,6 +293,45 @@
         b.call_noreply(m);
     }
 
+    // Test tuple.
+    {
+        auto m = newMethodCall__test(b);
+        std::tuple<int, double, std::string> a{ 3, 4.1, "asdf" };
+        m.append(1, a, 2);
+        verifyTypeString = "i(ids)i";
+
+        struct verify
+        {
+            static void op(sd_bus_message* m)
+            {
+                int32_t a = 0;
+                double b = 0;
+                const char* c = nullptr;
+
+                sd_bus_message_read(m, "i", &a);
+                assert(a == 1);
+
+                auto rc = sd_bus_message_enter_container(m,
+                                                         SD_BUS_TYPE_STRUCT,
+                                                         "ids");
+                assert(0 <= rc);
+
+                sd_bus_message_read(m, "ids", &a, &b, &c);
+                assert(a == 3);
+                assert(b == 4.1);
+                assert(0 == strcmp(c, "asdf"));
+
+                sd_bus_message_exit_container(m);
+
+                sd_bus_message_read(m, "i", &a);
+                assert(a == 2);
+            }
+        };
+        verifyCallback = &verify::op;
+
+        b.call_noreply(m);
+    }
+
     // Shutdown server.
     {
         auto m = b.new_method_call(SERVICE, "/", INTERFACE, QUIT_METHOD);
diff --git a/test/message/read.cpp b/test/message/read.cpp
index f1dfc77..25ccdb5 100644
--- a/test/message/read.cpp
+++ b/test/message/read.cpp
@@ -247,7 +247,30 @@
         b.call_noreply(m);
     }
 
+    // Test tuple.
+    {
+        auto m = newMethodCall__test(b);
+        std::tuple<int, double, std::string> a{ 3, 4.1, "asdf" };
+        m.append(1, a, 2);
+        verifyTypeString = "i(ids)i";
 
+        struct verify
+        {
+            static void op(sdbusplus::message::message& m)
+            {
+                int32_t a = 0, b = 0;
+                std::tuple<int, double, std::string> c{};
+
+                m.read(a, c, b);
+                assert(a == 1);
+                assert(b == 2);
+                assert(c == std::make_tuple(3, 4.1, "asdf"s));
+            }
+        };
+        verifyCallback = &verify::op;
+
+        b.call_noreply(m);
+    }
 
     // Shutdown server.
     {