std::map support for append/read

Change-Id: I3b5510b8ba400cf4d3f936f01708cb17aa009e62
Signed-off-by: Patrick Williams <patrick@stwcx.xyz>
diff --git a/sdbusplus/message/append.hpp b/sdbusplus/message/append.hpp
index 2fda860..da8dc1a 100644
--- a/sdbusplus/message/append.hpp
+++ b/sdbusplus/message/append.hpp
@@ -50,6 +50,12 @@
     // std::vector needs a loop.
 template<typename T>
 struct can_append_multiple<std::vector<T>> : std::false_type {};
+    // std::pair needs to be broken down into components.
+template<typename T1, typename T2>
+struct can_append_multiple<std::pair<T1,T2>> : std::false_type {};
+    // std::map needs a loop.
+template<typename T1, typename T2>
+struct can_append_multiple<std::map<T1,T2>> : std::false_type {};
 
 /** @struct append_single
  *  @brief Utility to append a single C++ element into a sd_bus_message.
@@ -121,6 +127,38 @@
     }
 };
 
+/** @brief Specialization of append_single for std::pairs. */
+template <typename T1, typename T2> struct append_single<std::pair<T1, T2>>
+{
+    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<T1>(),
+                               types::type_id<T2>()));
+
+        sd_bus_message_open_container(
+                m, SD_BUS_TYPE_DICT_ENTRY, dbusType.data());
+        sdbusplus::message::append(m, s.first, s.second);
+        sd_bus_message_close_container(m);
+    }
+};
+
+/** @brief Specialization of append_single for std::maps. */
+template <typename T1, typename T2> struct append_single<std::map<T1, T2>>
+{
+    template<typename S>
+    static void op(sd_bus_message* m, S&& s)
+    {
+        constexpr auto dbusType = utility::tuple_to_array(
+                types::type_id<typename std::map<T1, T2>::value_type>());
+
+        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 9b28421..6291409 100644
--- a/sdbusplus/message/read.hpp
+++ b/sdbusplus/message/read.hpp
@@ -50,6 +50,12 @@
     // std::vector needs a loop.
 template<typename T>
 struct can_read_multiple<std::vector<T>> : std::false_type {};
+    // std::pair needs to be broken down into components.
+template<typename T1, typename T2>
+struct can_read_multiple<std::pair<T1,T2>> : std::false_type {};
+    // std::map needs a loop.
+template<typename T1, typename T2>
+struct can_read_multiple<std::map<T1,T2>> : std::false_type {};
 
 /** @struct read_single
  *  @brief Utility to read a single C++ element from a sd_bus_message.
@@ -131,6 +137,48 @@
     }
 };
 
+/** @brief Specialization of read_single for std::pairs. */
+template <typename T1, typename T2> struct read_single<std::pair<T1, T2>>
+{
+    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<T1>(),
+                               types::type_id<T2>()));
+
+        sd_bus_message_enter_container(
+                m, SD_BUS_TYPE_DICT_ENTRY, dbusType.data());
+        sdbusplus::message::read(m, s.first, s.second);
+        sd_bus_message_exit_container(m);
+    }
+};
+
+/** @brief Specialization of read_single for std::maps. */
+template <typename T1, typename T2> struct read_single<std::map<T1, T2>>
+{
+    template<typename S>
+    static void op(sd_bus_message* m, S&& s)
+    {
+        s.clear();
+
+        constexpr auto dbusType = utility::tuple_to_array(
+                types::type_id<typename std::map<T1, T2>::value_type>());
+
+        sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, dbusType.data());
+
+        while(!sd_bus_message_at_end(m, false))
+        {
+            std::pair<std::remove_const_t<T1>, std::remove_const_t<T2>> p{};
+            sdbusplus::message::read(m, p);
+            s.insert(std::move(p));
+        }
+
+        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 403b4f1..3e39aca 100644
--- a/sdbusplus/message/types.hpp
+++ b/sdbusplus/message/types.hpp
@@ -3,6 +3,7 @@
 #include <tuple>
 #include <string>
 #include <vector>
+#include <map>
 #include <systemd/sd-bus.h>
 
 #include <sdbusplus/utility/type_traits.hpp>
@@ -31,6 +32,12 @@
  *  the cost of hard-coded type string constants.
  */
 template <typename ...Args> constexpr auto type_id();
+/** @fn type_id_nonull()
+ *  @brief A non-null-terminated version of type_id.
+ *
+ *  This is useful when type-ids may need to be concatenated.
+ */
+template <typename ...Args> constexpr auto type_id_nonull();
 
 namespace details
 {
@@ -138,6 +145,22 @@
         type_id<type_id_downcast_t<T>>::value);
 };
 
+template <typename T1, typename T2> struct type_id<std::pair<T1, T2>>
+{
+    static constexpr auto value = std::tuple_cat(
+        tuple_type_id<SD_BUS_TYPE_DICT_ENTRY_BEGIN>::value,
+        type_id<type_id_downcast_t<T1>>::value,
+        type_id<type_id_downcast_t<T2>>::value,
+        tuple_type_id<SD_BUS_TYPE_DICT_ENTRY_END>::value);
+};
+
+template <typename T1, typename T2> struct type_id<std::map<T1, T2>>
+{
+    static constexpr auto value = std::tuple_cat(
+        tuple_type_id<SD_BUS_TYPE_ARRAY>::value,
+        type_id<typename std::map<T1,T2>::value_type>::value);
+};
+
 template <typename T> constexpr auto& type_id_single()
 {
     static_assert(!std::is_base_of<undefined_type_id, type_id<T>>::value,
@@ -160,6 +183,11 @@
         std::make_tuple('\0') /* null terminator for C-string */ );
 }
 
+template <typename ...Args> constexpr auto type_id_nonull()
+{
+    return details::type_id_multiple<details::type_id_downcast_t<Args>...>();
+}
+
 } // namespace types
 
 } // namespace message
diff --git a/test/message/append.cpp b/test/message/append.cpp
index 87d8fc5..4734815 100644
--- a/test/message/append.cpp
+++ b/test/message/append.cpp
@@ -234,6 +234,65 @@
         b.call_noreply(m);
     }
 
+    // Test map.
+    {
+        auto m = newMethodCall__test(b);
+        std::map<std::string, int> s = { { "asdf", 3 }, { "jkl;", 4 } };
+        m.append(1, s, 2);
+        verifyTypeString = "ia{si}i";
+
+        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,
+                                                         "{si}");
+                assert(0 <= rc);
+
+                rc = sd_bus_message_enter_container(m,
+                                                    SD_BUS_TYPE_DICT_ENTRY,
+                                                    "si");
+                assert(0 <= rc);
+
+                const char* s = nullptr;
+                sd_bus_message_read_basic(m, 's', &s);
+                assert(0 == strcmp("asdf", s));
+                sd_bus_message_read_basic(m, 'i', &a);
+                assert(a == 3);
+
+                assert(1 == sd_bus_message_at_end(m, false));
+                sd_bus_message_exit_container(m);
+
+                rc = sd_bus_message_enter_container(m,
+                                                    SD_BUS_TYPE_DICT_ENTRY,
+                                                    "si");
+                assert(0 <= rc);
+
+                sd_bus_message_read_basic(m, 's', &s);
+                assert(0 == strcmp("jkl;", s));
+                sd_bus_message_read_basic(m, 'i', &a);
+                assert(a == 4);
+
+                assert(1 == sd_bus_message_at_end(m, false));
+                sd_bus_message_exit_container(m);
+
+                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 1021175..f1dfc77 100644
--- a/test/message/read.cpp
+++ b/test/message/read.cpp
@@ -220,6 +220,35 @@
         b.call_noreply(m);
     }
 
+    // Test map.
+    {
+        auto m = newMethodCall__test(b);
+        std::map<std::string, int> s = { { "asdf", 3 }, { "jkl;", 4 } };
+        m.append(1, s, 2);
+        verifyTypeString = "ia{si}i";
+
+        struct verify
+        {
+            static void op(sdbusplus::message::message& m)
+            {
+                int32_t a = 0, b = 0;
+                std::map<std::string, int> s{};
+
+                m.read(a, s, b);
+                assert(a == 1);
+                assert(s.size() == 2);
+                assert(s["asdf"] == 3);
+                assert(s["jkl;"] == 4);
+                assert(b == 2);
+            }
+        };
+        verifyCallback = &verify::op;
+
+        b.call_noreply(m);
+    }
+
+
+
     // Shutdown server.
     {
         auto m = b.new_method_call(SERVICE, "/", INTERFACE, QUIT_METHOD);