std::vector support for append/read

Change-Id: I1820ee2213feb37fab10a491b1099032fd48c18d
Signed-off-by: Patrick Williams <patrick@stwcx.xyz>
diff --git a/sdbusplus/message/append.hpp b/sdbusplus/message/append.hpp
index 7ce429b..2fda860 100644
--- a/sdbusplus/message/append.hpp
+++ b/sdbusplus/message/append.hpp
@@ -47,6 +47,9 @@
 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 {};
+    // std::vector needs a loop.
+template<typename T>
+struct can_append_multiple<std::vector<T>> : std::false_type {};
 
 /** @struct append_single
  *  @brief Utility to append a single C++ element into a sd_bus_message.
@@ -104,6 +107,20 @@
     }
 };
 
+/** @brief Specialization of append_single for std::vectors. */
+template <typename T> struct append_single<std::vector<T>>
+{
+    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());
+        for(auto& i : s) { sdbusplus::message::append(m, i); }
+        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 ea7ccd1..9b28421 100644
--- a/sdbusplus/message/read.hpp
+++ b/sdbusplus/message/read.hpp
@@ -47,6 +47,9 @@
 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 {};
+    // std::vector needs a loop.
+template<typename T>
+struct can_read_multiple<std::vector<T>> : std::false_type {};
 
 /** @struct read_single
  *  @brief Utility to read a single C++ element from a sd_bus_message.
@@ -106,6 +109,28 @@
     }
 };
 
+/** @brief Specialization of read_single for std::vectors. */
+template <typename T> struct read_single<std::vector<T>>
+{
+    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());
+
+        while(!sd_bus_message_at_end(m, false))
+        {
+            std::remove_const_t<T> t{};
+            sdbusplus::message::read(m, t);
+            s.push_back(std::move(t));
+        }
+
+        sd_bus_message_exit_container(m);
+    }
+};
+
 /** @brief Read a tuple of content from the sd_bus_message.
  *
  *  @tparam Tuple - The tuple type to read.
diff --git a/sdbusplus/message/types.hpp b/sdbusplus/message/types.hpp
index cd0fff2..026c3cf 100644
--- a/sdbusplus/message/types.hpp
+++ b/sdbusplus/message/types.hpp
@@ -34,6 +34,24 @@
 namespace details
 {
 
+/** @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
+ *  duplication in type_id template specializations.
+ *
+ *  1. Remove references.
+ *  2. Remove 'const' and 'volatile'.
+ *  3. Convert 'char[N]' to 'char*'.
+ */
+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>>>;
+};
+
+template <typename T> using type_id_downcast_t =
+        typename type_id_downcast<T>::type;
+
 /** @struct undefined_type_id
  *  @brief Special type indicating no dbus-type_id is defined for a C++ type.
  */
@@ -112,6 +130,13 @@
 template <> struct type_id<char*> : tuple_type_id<'s'> {};
 template <> struct type_id<std::string> : tuple_type_id<'s'> {};
 
+template <typename T> struct type_id<std::vector<T>>
+{
+    static constexpr auto value = std::tuple_cat(
+        tuple_type_id<'a'>::value,
+        type_id<type_id_downcast_t<T>>::value);
+};
+
 template <typename T> constexpr auto& type_id_single()
 {
     static_assert(!std::is_base_of<undefined_type_id, type_id<T>>::value,
@@ -125,24 +150,6 @@
         type_id_single<Args>()...);
 }
 
-/** @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
- *  duplication in type_id template specializations.
- *
- *  1. Remove references.
- *  2. Remove 'const' and 'vector'.
- *  3. Convert 'char[N]' to 'char*'.
- */
-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>>>;
-};
-
-template <typename T> using type_id_downcast_t =
-        typename type_id_downcast<T>::type;
-
 } // namespace details
 
 template <typename ...Args> constexpr auto type_id()
diff --git a/test/message/append.cpp b/test/message/append.cpp
index 4413910..87d8fc5 100644
--- a/test/message/append.cpp
+++ b/test/message/append.cpp
@@ -192,6 +192,48 @@
         b.call_noreply(m);
     }
 
+    // Test vector.
+    {
+        auto m = newMethodCall__test(b);
+        std::vector<std::string> 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);
+    }
+
     // 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 48c445e..1021175 100644
--- a/test/message/read.cpp
+++ b/test/message/read.cpp
@@ -193,6 +193,33 @@
         b.call_noreply(m);
     }
 
+    // Test vector.
+    {
+        auto m = newMethodCall__test(b);
+        std::vector<std::string> s{ "1", "2", "3"};
+        m.append(1, s, 2);
+        verifyTypeString = "iasi";
+
+        struct verify
+        {
+            static void op(sdbusplus::message::message& m)
+            {
+                int32_t a = 0;
+                std::vector<std::string> s;
+                m.read(a, s);
+                assert(a == 1);
+                assert(s[0] == "1");
+                assert(s[1] == "2");
+                assert(s[2] == "3");
+                decltype(s) s2 = { "1" , "2" , "3" };
+                assert(s == s2);
+            }
+        };
+        verifyCallback = &verify::op;
+
+        b.call_noreply(m);
+    }
+
     // Shutdown server.
     {
         auto m = b.new_method_call(SERVICE, "/", INTERFACE, QUIT_METHOD);