Add boost asio async connection

Add an async connection and example of usage. This
connection inherits the bus object and allows async
method calls using boost asio. Most of these concepts
are from boost-dbus.

Change-Id: I33b5349d543c9ff4b6ee1ce15346c709c052e1ae
Tested: Compiled and ran asio-example on bmc.
Signed-off-by: James Feist <james.feist@linux.intel.com>
diff --git a/Makefile.am b/Makefile.am
index 0129c2b..94a88f6 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -5,6 +5,8 @@
 nobase_include_HEADERS = \
 	mapbox/recursive_wrapper.hpp \
 	mapbox/variant.hpp \
+	sdbusplus/asio/connection.hpp \
+	sdbusplus/asio/detail/async_send_handler.hpp \
 	sdbusplus/bus.hpp \
 	sdbusplus/bus/match.hpp \
 	sdbusplus/sdbus.hpp \
@@ -22,6 +24,7 @@
 	sdbusplus/server/object.hpp \
 	sdbusplus/slot.hpp \
 	sdbusplus/utility/container_traits.hpp \
+	sdbusplus/utility/read_into_tuple.hpp \
 	sdbusplus/utility/tuple_to_array.hpp \
 	sdbusplus/utility/type_traits.hpp \
 	sdbusplus/vtable.hpp
diff --git a/configure.ac b/configure.ac
index c3d8554..753d04d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -133,6 +133,10 @@
     AC_SUBST([OESDK_TESTCASE_FLAGS], [$testcase_flags])
 )
 
+AC_ARG_ENABLE([boost],
+    AS_HELP_STRING([--enable-boost], [Enable building with boost.]))
+AM_CONDITIONAL(BOOST, [test "x$enable_boost" = "xyes"])
+
 # Create configured output
 AC_CONFIG_FILES([Makefile test/Makefile tools/Makefile tools/setup.py])
 AC_CONFIG_FILES([example/Makefile])
diff --git a/example/Makefile.am b/example/Makefile.am
index 2cf8941..86ab50a 100644
--- a/example/Makefile.am
+++ b/example/Makefile.am
@@ -1,5 +1,26 @@
 noinst_PROGRAMS = calculator-server list-users
 
+if BOOST
+noinst_PROGRAMS += asio-example
+asio_example_SOURCES = asio-example.cpp
+asio_example_CXXFLAGS = \
+    $(SYSTEMD_CFLAGS) \
+    $(PTHREAD_CFLAGS) \
+    $(BOOST_CPPFLAGS) \
+    -DBOOST_ALL_NO_LIB \
+    -DBOOST_SYSTEM_NO_DEPRECATED \
+    -DBOOST_ERROR_CODE_HEADER_ONLY \
+    -I$(top_srcdir)
+
+asio_example_LDADD = \
+    $(SYSTEMD_LIBS) \
+    $(PTHREAD_LIBS) \
+    ../libsdbusplus.la
+
+asio_example_LDFLAGS = \
+    $(BOOST_LDFLAGS)
+endif
+
 calculator_server_generated_files = \
 	net/poettering/Calculator/server.hpp \
 	net/poettering/Calculator/server.cpp \
diff --git a/example/asio-example.cpp b/example/asio-example.cpp
new file mode 100644
index 0000000..7dde720
--- /dev/null
+++ b/example/asio-example.cpp
@@ -0,0 +1,65 @@
+#include <iostream>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/server.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <boost/asio.hpp>
+
+int main()
+{
+    using GetSubTreeType = std::vector<std::pair<
+        std::string,
+        std::vector<std::pair<std::string, std::vector<std::string>>>>>;
+    using message = sdbusplus::message::message;
+    // setup connection to dbus
+    boost::asio::io_service io;
+    auto conn = std::make_shared<sdbusplus::asio::connection>(io);
+
+    // test async method call and async send
+    auto mesg =
+        conn->new_method_call("xyz.openbmc_project.ObjectMapper",
+                              "/xyz/openbmc_project/object_mapper",
+                              "xyz.openbmc_project.ObjectMapper", "GetSubTree");
+
+    static const auto depth = 2;
+    static const std::vector<std::string> interfaces = {
+        "xyz.openbmc_project.Sensor.Value"};
+    mesg.append("/xyz/openbmc_project/Sensors", depth, interfaces);
+
+    conn->async_send(mesg, [](boost::system::error_code ec, message& ret) {
+        std::cout << "async_send callback\n";
+        if (ec || ret.is_method_error())
+        {
+            std::cerr << "error with async_send\n";
+            return;
+        }
+        GetSubTreeType data;
+        ret.read(data);
+        for (auto& item : data)
+        {
+            std::cout << item.first << "\n";
+        }
+    });
+
+    conn->async_method_call(
+        [](boost::system::error_code ec, GetSubTreeType& subtree) {
+            std::cout << "async_method_call callback\n";
+            if (ec)
+            {
+                std::cerr << "error with async_method_call\n";
+                return;
+            }
+            for (auto& item : subtree)
+            {
+                std::cout << item.first << "\n";
+            }
+
+        },
+        "xyz.openbmc_project.ObjectMapper",
+        "/xyz/openbmc_project/object_mapper",
+        "xyz.openbmc_project.ObjectMapper", "GetSubTree",
+        "/org/openbmc/control", 2, std::vector<std::string>());
+
+    io.run();
+
+    return 0;
+}
diff --git a/sdbusplus/asio/connection.hpp b/sdbusplus/asio/connection.hpp
new file mode 100644
index 0000000..e306113
--- /dev/null
+++ b/sdbusplus/asio/connection.hpp
@@ -0,0 +1,127 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+#pragma once
+
+#include <sdbusplus/message.hpp>
+#include <sdbusplus/utility/read_into_tuple.hpp>
+#include <sdbusplus/utility/type_traits.hpp>
+#include <sdbusplus/asio/detail/async_send_handler.hpp>
+#include <chrono>
+#include <string>
+#include <experimental/tuple>
+#include <boost/asio.hpp>
+#include <boost/callable_traits.hpp>
+
+namespace sdbusplus
+{
+
+namespace asio
+{
+
+/// Root D-Bus IO object
+/**
+ * A connection to a bus, through which messages may be sent or received.
+ */
+class connection : public sdbusplus::bus::bus
+{
+  public:
+    // default to system bus
+    connection(boost::asio::io_service& io) :
+        sdbusplus::bus::bus(sdbusplus::bus::new_system()), io_(io), socket(io_)
+    {
+        socket.assign(get_fd());
+        do_read();
+    }
+    connection(boost::asio::io_service& io, sd_bus* bus) :
+        sdbusplus::bus::bus(bus), io_(io), socket(io_)
+    {
+        socket.assign(get_fd());
+        do_read();
+    }
+    ~connection()
+    {
+        // The FD will be closed by the socket object, so assign null to the
+        // sd_bus object to avoid a double close()  Ignore return codes here,
+        // because there's nothing we can do about errors
+        socket.release();
+    }
+
+    template <typename MessageHandler>
+    inline BOOST_ASIO_INITFN_RESULT_TYPE(MessageHandler,
+                                         void(boost::system::error_code,
+                                              message::message))
+        async_send(message::message& m, MessageHandler&& handler)
+    {
+        boost::asio::async_completion<
+            MessageHandler, void(boost::system::error_code, message::message)>
+            init(handler);
+        detail::async_send_handler<typename boost::asio::handler_type<
+            MessageHandler,
+            void(boost::system::error_code, message::message)>::type>(
+            std::move(init.completion_handler))(get(), m);
+        return init.result.get();
+    }
+
+    template <typename MessageHandler, typename... InputArgs>
+    auto async_method_call(MessageHandler handler, const std::string& service,
+                           const std::string& objpath,
+                           const std::string& interf, const std::string& method,
+                           const InputArgs&... a)
+    {
+        message::message m = new_method_call(service.c_str(), objpath.c_str(),
+                                             interf.c_str(), method.c_str());
+        m.append(a...);
+        return async_send(m, [handler](boost::system::error_code ec,
+                                       message::message& r) {
+            using FunctionTuple =
+                boost::callable_traits::args_t<MessageHandler>;
+            using UnpackType = typename utility::strip_first_arg<
+                typename utility::decay_tuple<FunctionTuple>::type>::type;
+            UnpackType responseData;
+            if (!ec)
+            {
+                if (!utility::read_into_tuple(responseData, r))
+                {
+                    // Set error code if not already set
+                    ec = boost::system::errc::make_error_code(
+                        boost::system::errc::invalid_argument);
+                }
+            }
+            // Note.  Callback is called whether or not the unpack was
+            // sucessful to allow the user to implement their own handling
+            auto response = std::tuple_cat(std::make_tuple(ec), responseData);
+            std::experimental::apply(handler, response);
+        });
+    }
+
+  private:
+    boost::asio::io_service& io_;
+    boost::asio::posix::stream_descriptor socket;
+
+    void do_read(void)
+    {
+        socket.async_read_some(
+            boost::asio::null_buffers(),
+            [&](const boost::system::error_code& ec, std::size_t) {
+                process_discard();
+                do_read();
+            });
+    }
+};
+
+} // namespace asio
+
+} // namespace sdbusplus
diff --git a/sdbusplus/asio/detail/async_send_handler.hpp b/sdbusplus/asio/detail/async_send_handler.hpp
new file mode 100644
index 0000000..e9e036d
--- /dev/null
+++ b/sdbusplus/asio/detail/async_send_handler.hpp
@@ -0,0 +1,70 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+#pragma once
+
+#include <sdbusplus/message.hpp>
+#include <sdbusplus/bus.hpp>
+#include <systemd/sd-bus.h>
+#include <boost/asio.hpp>
+
+namespace sdbusplus
+{
+namespace asio
+{
+namespace detail
+{
+template <typename Handler> struct async_send_handler
+{
+    Handler handler_;
+    async_send_handler(Handler&& handler) : handler_(std::move(handler))
+    {
+    }
+    async_send_handler(Handler& handler) : handler_(handler)
+    {
+    }
+    void operator()(sd_bus* conn, message::message& mesg)
+    {
+        async_send_handler* context = new async_send_handler(std::move(*this));
+        // 0 is the default timeout
+        int ec =
+            sd_bus_call_async(conn, NULL, mesg.get(), &callback, context, 0);
+        if (ec < 0)
+        {
+            auto err =
+                make_error_code(static_cast<boost::system::errc::errc_t>(ec));
+            context->handler_(err, mesg);
+            delete context;
+        }
+    }
+    static int callback(sd_bus_message* mesg, void* userdata,
+                        sd_bus_error* error)
+    {
+        if (userdata == nullptr || mesg == nullptr)
+        {
+            return -1;
+        }
+        std::unique_ptr<async_send_handler> context(
+            static_cast<async_send_handler*>(userdata));
+        message::message message(mesg);
+        auto ec = make_error_code(
+            static_cast<boost::system::errc::errc_t>(message.get_errno()));
+        context->handler_(ec, message);
+        return 1;
+    }
+};
+} // namespace detail
+} // namespace asio
+} // namespace sdbusplus
\ No newline at end of file
diff --git a/sdbusplus/bus.hpp.in b/sdbusplus/bus.hpp.in
index 1f13a5e..638a515 100644
--- a/sdbusplus/bus.hpp.in
+++ b/sdbusplus/bus.hpp.in
@@ -282,6 +282,10 @@
         return std::string(unique);
     }
 
+    auto get_fd(){
+        return sd_bus_get_fd(_bus.get());
+    }
+
     /** @brief Attach the bus with a sd-event event loop object.
      *
      *  @param[in] event - sd_event object.
@@ -400,7 +404,7 @@
     template <class... Args> friend struct server::object::object;
     friend struct match::match;
 
-  private:
+  protected:
     busp_t get()
     {
         return _bus.get();
diff --git a/sdbusplus/message.hpp b/sdbusplus/message.hpp
index 6b58099..d6b5aa7 100644
--- a/sdbusplus/message.hpp
+++ b/sdbusplus/message.hpp
@@ -193,6 +193,15 @@
         return _intf->sd_bus_message_is_method_error(_msg.get(), nullptr);
     }
 
+    /** @brief Get the errno from the message.
+     *
+     *  @return The errno of the message.
+     */
+    int get_errno()
+    {
+        return sd_bus_message_get_errno(_msg.get());
+    }
+
     /** @brief Get the transaction cookie of a message.
      *
      * @return The transaction cookie of a message.
@@ -254,12 +263,13 @@
 
     friend struct sdbusplus::bus::bus;
 
-  private:
     /** @brief Get a pointer to the owned 'msgp_t'. */
     msgp_t get()
     {
         return _msg.get();
     }
+
+  private:
     sdbusplus::SdBusInterface* _intf;
     details::msg _msg;
 };
diff --git a/sdbusplus/utility/read_into_tuple.hpp b/sdbusplus/utility/read_into_tuple.hpp
new file mode 100644
index 0000000..9628e11
--- /dev/null
+++ b/sdbusplus/utility/read_into_tuple.hpp
@@ -0,0 +1,51 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+
+#pragma once
+#include <tuple>
+#include <sdbusplus/message.hpp>
+
+namespace sdbusplus
+{
+namespace utility
+{
+
+namespace detail
+{
+template <class F, size_t... Is>
+constexpr auto index_apply_impl(F f, std::index_sequence<Is...>)
+{
+    return f(std::integral_constant<size_t, Is>{}...);
+}
+template <size_t N, class F> constexpr auto index_apply(F f)
+{
+    return index_apply_impl(f, std::make_index_sequence<N>{});
+}
+} // namespace detail
+template <class Tuple>
+constexpr bool read_into_tuple(Tuple& t, message::message& m)
+{
+    return detail::index_apply<std::tuple_size<Tuple>{}>([&](auto... Is) {
+        if (m.is_method_error())
+        {
+            return false;
+        }
+        m.read(std::get<Is>(t)...);
+        return true;
+    });
+}
+} // namespace utility
+} // namespace sdbusplus
diff --git a/sdbusplus/utility/type_traits.hpp b/sdbusplus/utility/type_traits.hpp
index cad5168..3629d09 100644
--- a/sdbusplus/utility/type_traits.hpp
+++ b/sdbusplus/utility/type_traits.hpp
@@ -1,6 +1,7 @@
 #pragma once
 
 #include <type_traits>
+#include <tuple>
 
 namespace sdbusplus
 {
@@ -20,6 +21,28 @@
                        std::add_pointer_t<std::remove_extent_t<T>>, T>,
     T>;
 
+// Small helper class for stripping off the error code from the function
+// argument definitions so unpack can be called appropriately
+template <typename T> struct strip_first_arg
+{
+};
+
+template <typename FirstArg, typename... Rest>
+struct strip_first_arg<std::tuple<FirstArg, Rest...>>
+{
+    using type = std::tuple<Rest...>;
+};
+
+// helper class to remove const and reference from types
+template <typename T> struct decay_tuple
+{
+};
+
+template <typename... Args> struct decay_tuple<std::tuple<Args...>>
+{
+    using type = std::tuple<typename std::decay<Args>::type...>;
+};
+
 } // namespace utility
 
 } // namespace sdbusplus