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                    uint64_t timeout = 0)
89     {
90         boost::asio::async_completion<
91             MessageHandler, void(boost::system::error_code, message::message)>
92             init(handler);
93         detail::async_send_handler<typename boost::asio::async_result<
94             MessageHandler, void(boost::system::error_code,
95                                  message::message)>::completion_handler_type>(
96             std::move(init.completion_handler))(get(), m, timeout);
97         return init.result.get();
98     }
99 
100     /** @brief Perform an asynchronous method call, with input parameter packing
101      *         and return value unpacking.
102      *
103      *  @param[in] handler - A function object that is to be called as a
104      *                       continuation for the async dbus method call. The
105      *                       arguments to parse on the return are deduced from
106      *                       the handler's signature and then passed in along
107      *                       with an error code and optional message::message
108      *  @param[in] service - The service to call.
109      *  @param[in] objpath - The object's path for the call.
110      *  @param[in] interf - The object's interface to call.
111      *  @param[in] method - The object's method to call.
112      *  @param[in] timeout - The timeout for the method call in usec (0 results
113      *                       in using the default value).
114      *  @param[in] a... - Optional parameters for the method call.
115      *
116      *  @return immediate return of the internal handler registration. The
117      *          result of the actual asynchronous call will get unpacked from
118      *          the message and passed into the handler when the call is
119      *          complete.
120      */
121     template <typename MessageHandler, typename... InputArgs>
122     void async_method_call_timed(MessageHandler&& handler,
123                                  const std::string& service,
124                                  const std::string& objpath,
125                                  const std::string& interf,
126                                  const std::string& method, uint64_t timeout,
127                                  const InputArgs&... a)
128     {
129         using FunctionTuple = boost::callable_traits::args_t<MessageHandler>;
130         using FunctionTupleType =
131             typename utility::decay_tuple<FunctionTuple>::type;
132         constexpr bool returnWithMsg = []() {
133             if constexpr (std::tuple_size_v<FunctionTupleType> > 1)
134             {
135                 return std::is_same_v<
136                     std::tuple_element_t<1, FunctionTupleType>,
137                     sdbusplus::message::message>;
138             }
139             return false;
140         }();
141         using UnpackType =
142             typename utility::strip_first_n_args<returnWithMsg ? 2 : 1,
143                                                  FunctionTupleType>::type;
144         auto applyHandler = [handler = std::forward<MessageHandler>(handler)](
145                                 boost::system::error_code ec,
146                                 message::message& r) mutable {
147             UnpackType responseData;
148             if (!ec)
149             {
150                 try
151                 {
152                     utility::read_into_tuple(responseData, r);
153                 }
154                 catch (const std::exception& e)
155                 {
156                     // Set error code if not already set
157                     ec = boost::system::errc::make_error_code(
158                         boost::system::errc::invalid_argument);
159                 }
160             }
161             // Note.  Callback is called whether or not the unpack was
162             // successful to allow the user to implement their own handling
163             if constexpr (returnWithMsg)
164             {
165                 auto response = std::tuple_cat(std::make_tuple(ec),
166                                                std::forward_as_tuple(r),
167                                                std::move(responseData));
168                 std::apply(handler, response);
169             }
170             else
171             {
172                 auto response = std::tuple_cat(std::make_tuple(ec),
173                                                std::move(responseData));
174                 std::apply(handler, response);
175             }
176         };
177         message::message m;
178         boost::system::error_code ec;
179         try
180         {
181             m = new_method_call(service.c_str(), objpath.c_str(),
182                                 interf.c_str(), method.c_str());
183             m.append(a...);
184         }
185         catch (const exception::SdBusError& e)
186         {
187             ec = boost::system::errc::make_error_code(
188                 static_cast<boost::system::errc::errc_t>(e.get_errno()));
189             applyHandler(ec, m);
190             return;
191         }
192         async_send(m, std::forward<decltype(applyHandler)>(applyHandler),
193                    timeout);
194     }
195 
196     /** @brief Perform an asynchronous method call, with input parameter packing
197      *         and return value unpacking. Uses the default timeout value.
198      *
199      *  @param[in] handler - A function object that is to be called as a
200      *                       continuation for the async dbus method call. The
201      *                       arguments to parse on the return are deduced from
202      *                       the handler's signature and then passed in along
203      *                       with an error code and optional message::message
204      *  @param[in] service - The service to call.
205      *  @param[in] objpath - The object's path for the call.
206      *  @param[in] interf - The object's interface to call.
207      *  @param[in] method - The object's method to call.
208      *  @param[in] a... - Optional parameters for the method call.
209      *
210      *  @return immediate return of the internal handler registration. The
211      *          result of the actual asynchronous call will get unpacked from
212      *          the message and passed into the handler when the call is
213      *          complete.
214      */
215     template <typename MessageHandler, typename... InputArgs>
216     void async_method_call(MessageHandler&& handler, const std::string& service,
217                            const std::string& objpath,
218                            const std::string& interf, const std::string& method,
219                            const InputArgs&... a)
220     {
221         async_method_call_timed(std::forward<MessageHandler>(handler), service,
222                                 objpath, interf, method, 0, a...);
223     }
224 
225     /** @brief Perform a yielding asynchronous method call, with input
226      *         parameter packing and return value unpacking
227      *
228      *  @param[in] yield - A yield context to async block upon.
229      *  @param[in] ec - an error code that will be set for any errors
230      *  @param[in] service - The service to call.
231      *  @param[in] objpath - The object's path for the call.
232      *  @param[in] interf - The object's interface to call.
233      *  @param[in] method - The object's method to call.
234      *  @param[in] a... - Optional parameters for the method call.
235      *
236      *  @return Unpacked value of RetType
237      */
238     template <typename... RetTypes, typename... InputArgs>
239     auto yield_method_call(boost::asio::yield_context yield,
240                            boost::system::error_code& ec,
241                            const std::string& service,
242                            const std::string& objpath,
243                            const std::string& interf, const std::string& method,
244                            const InputArgs&... a)
245     {
246         message::message m;
247         try
248         {
249             m = new_method_call(service.c_str(), objpath.c_str(),
250                                 interf.c_str(), method.c_str());
251             m.append(a...);
252         }
253         catch (const exception::SdBusError& e)
254         {
255             ec = boost::system::errc::make_error_code(
256                 static_cast<boost::system::errc::errc_t>(e.get_errno()));
257         }
258         message::message r;
259         if (!ec)
260         {
261             r = async_send(m, yield[ec]);
262         }
263         if constexpr (sizeof...(RetTypes) == 0)
264         {
265             // void return
266             return;
267         }
268         else if constexpr (sizeof...(RetTypes) == 1)
269         {
270             if constexpr (std::is_same<utility::first_type<RetTypes...>,
271                                        void>::value)
272             {
273                 return;
274             }
275             else
276             {
277                 // single item return
278                 utility::first_type<RetTypes...> responseData{};
279                 // before attempting to read, check ec and bail on error
280                 if (ec)
281                 {
282                     return responseData;
283                 }
284                 try
285                 {
286                     r.read(responseData);
287                 }
288                 catch (const std::exception& e)
289                 {
290                     ec = boost::system::errc::make_error_code(
291                         boost::system::errc::invalid_argument);
292                     // responseData will be default-constructed...
293                 }
294                 return responseData;
295             }
296         }
297         else
298         {
299             // tuple of things to return
300             std::tuple<RetTypes...> responseData{};
301             // before attempting to read, check ec and bail on error
302             if (ec)
303             {
304                 return responseData;
305             }
306             try
307             {
308                 r.read(responseData);
309             }
310             catch (const std::exception& e)
311             {
312                 ec = boost::system::errc::make_error_code(
313                     boost::system::errc::invalid_argument);
314                 // responseData will be default-constructed...
315             }
316             return responseData;
317         }
318     }
319 
320     boost::asio::io_context& get_io_context()
321     {
322         return io_;
323     }
324 
325   private:
326     boost::asio::io_context& io_;
327     boost::asio::posix::stream_descriptor socket;
328 
329     void read_wait()
330     {
331         socket.async_read_some(
332             boost::asio::null_buffers(),
333             [&](const boost::system::error_code& /*ec*/, std::size_t) {
334                 if (process_discard())
335                 {
336                     read_immediate();
337                 }
338                 else
339                 {
340                     read_wait();
341                 }
342             });
343     }
344     void read_immediate()
345     {
346         boost::asio::post(io_, [&] {
347             if (process_discard())
348             {
349                 read_immediate();
350             }
351             else
352             {
353                 read_wait();
354             }
355         });
356     }
357 };
358 
359 } // namespace asio
360 
361 } // namespace sdbusplus
362