xref: /openbmc/sdbusplus/include/sdbusplus/asio/connection.hpp (revision 7f664254a4365612b82db2fd8fb82798686e777b)
1 /*
2 // Copyright (c) 2018 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16 #pragma once
17 
18 #ifndef BOOST_COROUTINES_NO_DEPRECATION_WARNING
19 // users should define this if they directly include boost/asio/spawn.hpp,
20 // but by defining it here, warnings won't cause problems with a compile
21 #define BOOST_COROUTINES_NO_DEPRECATION_WARNING
22 #endif
23 
24 #include <boost/asio.hpp>
25 #include <boost/asio/spawn.hpp>
26 #include <boost/callable_traits.hpp>
27 #include <sdbusplus/asio/detail/async_send_handler.hpp>
28 #include <sdbusplus/message.hpp>
29 #include <sdbusplus/utility/read_into_tuple.hpp>
30 #include <sdbusplus/utility/type_traits.hpp>
31 
32 #include <chrono>
33 #include <string>
34 #include <tuple>
35 
36 namespace sdbusplus
37 {
38 
39 namespace asio
40 {
41 
42 /// Root D-Bus IO object
43 /**
44  * A connection to a bus, through which messages may be sent or received.
45  */
46 class connection : public sdbusplus::bus::bus
47 {
48   public:
49     // default to system bus
50     connection(boost::asio::io_context& io) :
51         sdbusplus::bus::bus(sdbusplus::bus::new_default()), io_(io), socket(io_)
52     {
53         socket.assign(get_fd());
54         read_wait();
55     }
56     connection(boost::asio::io_context& io, sd_bus* bus) :
57         sdbusplus::bus::bus(bus), io_(io), socket(io_)
58     {
59         socket.assign(get_fd());
60         read_wait();
61     }
62     ~connection()
63     {
64         // The FD will be closed by the socket object, so assign null to the
65         // sd_bus object to avoid a double close()  Ignore return codes here,
66         // because there's nothing we can do about errors
67         socket.release();
68     }
69 
70     /** @brief Perform an asynchronous send of a message, executing the handler
71      *         upon return and return
72      *
73      *  @param[in] m - A message ready to send
74      *  @param[in] handler - handler to execute upon completion; this may be an
75      *                       asio::yield_context to execute asynchronously as a
76      *                       coroutine
77      *
78      *  @return If a yield context is passed as the handler, the return type is
79      *          a message. If a function object is passed in as the handler,
80      *          the return type is the result of the handler registration,
81      *          while the resulting message will get passed into the handler.
82      */
83     template <typename MessageHandler>
84     inline BOOST_ASIO_INITFN_RESULT_TYPE(MessageHandler,
85                                          void(boost::system::error_code,
86                                               message::message&))
87         async_send(message::message& m, MessageHandler&& handler)
88     {
89         boost::asio::async_completion<
90             MessageHandler, void(boost::system::error_code, message::message)>
91             init(handler);
92         detail::async_send_handler<typename boost::asio::async_result<
93             MessageHandler, void(boost::system::error_code,
94                                  message::message)>::completion_handler_type>(
95             std::move(init.completion_handler))(get(), m);
96         return init.result.get();
97     }
98 
99     /** @brief Perform an asynchronous method call, with input parameter packing
100      *         and return value unpacking
101      *
102      *  @param[in] handler - A function object that is to be called as a
103      *                       continuation for the async dbus method call. The
104      *                       arguments to parse on the return are deduced from
105      *                       the handler's signature and then passed in along
106      *                       with an error code and optional message::message
107      *  @param[in] service - The service to call.
108      *  @param[in] objpath - The object's path for the call.
109      *  @param[in] interf - The object's interface to call.
110      *  @param[in] method - The object's method to call.
111      *  @param[in] a... - Optional parameters for the method call.
112      *
113      *  @return immediate return of the internal handler registration. The
114      *          result of the actual asynchronous call will get unpacked from
115      *          the message and passed into the handler when the call is
116      *          complete.
117      */
118     template <typename MessageHandler, typename... InputArgs>
119     void async_method_call(MessageHandler&& handler, const std::string& service,
120                            const std::string& objpath,
121                            const std::string& interf, const std::string& method,
122                            const InputArgs&... a)
123     {
124         using FunctionTuple = boost::callable_traits::args_t<MessageHandler>;
125         using FunctionTupleType =
126             typename utility::decay_tuple<FunctionTuple>::type;
127         constexpr bool returnWithMsg = []() {
128             if constexpr (std::tuple_size_v<FunctionTupleType> > 1)
129             {
130                 return std::is_same_v<
131                     std::tuple_element_t<1, FunctionTupleType>,
132                     sdbusplus::message::message>;
133             }
134             return false;
135         }();
136         using UnpackType =
137             typename utility::strip_first_n_args<returnWithMsg ? 2 : 1,
138                                                  FunctionTupleType>::type;
139         auto applyHandler = [handler = std::forward<MessageHandler>(handler)](
140                                 boost::system::error_code ec,
141                                 message::message& r) mutable {
142             UnpackType responseData;
143             if (!ec)
144             {
145                 try
146                 {
147                     utility::read_into_tuple(responseData, r);
148                 }
149                 catch (const std::exception& e)
150                 {
151                     // Set error code if not already set
152                     ec = boost::system::errc::make_error_code(
153                         boost::system::errc::invalid_argument);
154                 }
155             }
156             // Note.  Callback is called whether or not the unpack was
157             // successful to allow the user to implement their own handling
158             if constexpr (returnWithMsg)
159             {
160                 auto response = std::tuple_cat(std::make_tuple(ec),
161                                                std::forward_as_tuple(r),
162                                                std::move(responseData));
163                 std::apply(handler, response);
164             }
165             else
166             {
167                 auto response = std::tuple_cat(std::make_tuple(ec),
168                                                std::move(responseData));
169                 std::apply(handler, response);
170             }
171         };
172         message::message m;
173         boost::system::error_code ec;
174         try
175         {
176             m = new_method_call(service.c_str(), objpath.c_str(),
177                                 interf.c_str(), method.c_str());
178             m.append(a...);
179         }
180         catch (const exception::SdBusError& e)
181         {
182             ec = boost::system::errc::make_error_code(
183                 static_cast<boost::system::errc::errc_t>(e.get_errno()));
184             applyHandler(ec, m);
185             return;
186         }
187         async_send(m, std::forward<decltype(applyHandler)>(applyHandler));
188     }
189 
190     /** @brief Perform a yielding asynchronous method call, with input
191      *         parameter packing and return value unpacking
192      *
193      *  @param[in] yield - A yield context to async block upon.
194      *  @param[in] ec - an error code that will be set for any errors
195      *  @param[in] service - The service to call.
196      *  @param[in] objpath - The object's path for the call.
197      *  @param[in] interf - The object's interface to call.
198      *  @param[in] method - The object's method to call.
199      *  @param[in] a... - Optional parameters for the method call.
200      *
201      *  @return Unpacked value of RetType
202      */
203     template <typename... RetTypes, typename... InputArgs>
204     auto yield_method_call(boost::asio::yield_context yield,
205                            boost::system::error_code& ec,
206                            const std::string& service,
207                            const std::string& objpath,
208                            const std::string& interf, const std::string& method,
209                            const InputArgs&... a)
210     {
211         message::message m;
212         try
213         {
214             m = new_method_call(service.c_str(), objpath.c_str(),
215                                 interf.c_str(), method.c_str());
216             m.append(a...);
217         }
218         catch (const exception::SdBusError& e)
219         {
220             ec = boost::system::errc::make_error_code(
221                 static_cast<boost::system::errc::errc_t>(e.get_errno()));
222         }
223         message::message r;
224         if (!ec)
225         {
226             r = async_send(m, yield[ec]);
227         }
228         if constexpr (sizeof...(RetTypes) == 0)
229         {
230             // void return
231             return;
232         }
233         else if constexpr (sizeof...(RetTypes) == 1)
234         {
235             if constexpr (std::is_same<utility::first_type<RetTypes...>,
236                                        void>::value)
237             {
238                 return;
239             }
240             else
241             {
242                 // single item return
243                 utility::first_type<RetTypes...> responseData{};
244                 // before attempting to read, check ec and bail on error
245                 if (ec)
246                 {
247                     return responseData;
248                 }
249                 try
250                 {
251                     r.read(responseData);
252                 }
253                 catch (const std::exception& e)
254                 {
255                     ec = boost::system::errc::make_error_code(
256                         boost::system::errc::invalid_argument);
257                     // responseData will be default-constructed...
258                 }
259                 return responseData;
260             }
261         }
262         else
263         {
264             // tuple of things to return
265             std::tuple<RetTypes...> responseData{};
266             // before attempting to read, check ec and bail on error
267             if (ec)
268             {
269                 return responseData;
270             }
271             try
272             {
273                 r.read(responseData);
274             }
275             catch (const std::exception& e)
276             {
277                 ec = boost::system::errc::make_error_code(
278                     boost::system::errc::invalid_argument);
279                 // responseData will be default-constructed...
280             }
281             return responseData;
282         }
283     }
284 
285     boost::asio::io_context& get_io_context()
286     {
287         return io_;
288     }
289 
290   private:
291     boost::asio::io_context& io_;
292     boost::asio::posix::stream_descriptor socket;
293 
294     void read_wait()
295     {
296         socket.async_read_some(
297             boost::asio::null_buffers(),
298             [&](const boost::system::error_code& /*ec*/, std::size_t) {
299                 if (process_discard())
300                 {
301                     read_immediate();
302                 }
303                 else
304                 {
305                     read_wait();
306                 }
307             });
308     }
309     void read_immediate()
310     {
311         boost::asio::post(io_, [&] {
312             if (process_discard())
313             {
314                 read_immediate();
315             }
316             else
317             {
318                 read_wait();
319             }
320         });
321     }
322 };
323 
324 } // namespace asio
325 
326 } // namespace sdbusplus
327