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/async_result.hpp>
25 #include <boost/asio/io_context.hpp>
26 #include <boost/asio/posix/stream_descriptor.hpp>
27 #include <boost/asio/post.hpp>
28 #include <boost/asio/spawn.hpp>
29 #include <boost/callable_traits.hpp>
30 #include <sdbusplus/asio/detail/async_send_handler.hpp>
31 #include <sdbusplus/message.hpp>
32 #include <sdbusplus/utility/read_into_tuple.hpp>
33 #include <sdbusplus/utility/type_traits.hpp>
34 
35 #include <chrono>
36 #include <string>
37 #include <tuple>
38 
39 namespace sdbusplus
40 {
41 
42 namespace asio
43 {
44 
45 /// Root D-Bus IO object
46 /**
47  * A connection to a bus, through which messages may be sent or received.
48  */
49 class connection : public sdbusplus::bus::bus
50 {
51   public:
52     // default to system bus
53     connection(boost::asio::io_context& io) :
54         sdbusplus::bus::bus(sdbusplus::bus::new_default()), io_(io), socket(io_)
55     {
56         socket.assign(get_fd());
57         read_wait();
58     }
59     connection(boost::asio::io_context& io, sd_bus* bus) :
60         sdbusplus::bus::bus(bus), io_(io), socket(io_)
61     {
62         socket.assign(get_fd());
63         read_wait();
64     }
65     ~connection()
66     {
67         // The FD will be closed by the socket object, so assign null to the
68         // sd_bus object to avoid a double close()  Ignore return codes here,
69         // because there's nothing we can do about errors
70         socket.release();
71     }
72 
73     /** @brief Perform an asynchronous send of a message, executing the handler
74      *         upon return and return
75      *
76      *  @param[in] m - A message ready to send
77      *  @param[in] handler - handler to execute upon completion; this may be an
78      *                       asio::yield_context to execute asynchronously as a
79      *                       coroutine
80      *
81      *  @return If a yield context is passed as the handler, the return type is
82      *          a message. If a function object is passed in as the handler,
83      *          the return type is the result of the handler registration,
84      *          while the resulting message will get passed into the handler.
85      */
86     template <typename MessageHandler>
87     inline BOOST_ASIO_INITFN_RESULT_TYPE(MessageHandler,
88                                          void(boost::system::error_code,
89                                               message::message&))
90         async_send(message::message& m, MessageHandler&& handler,
91                    uint64_t timeout = 0)
92     {
93         boost::asio::async_completion<
94             MessageHandler, void(boost::system::error_code, message::message)>
95             init(handler);
96         detail::async_send_handler<typename boost::asio::async_result<
97             MessageHandler, void(boost::system::error_code,
98                                  message::message)>::completion_handler_type>(
99             std::move(init.completion_handler))(get(), m, timeout);
100         return init.result.get();
101     }
102 
103     /** @brief Perform an asynchronous method call, with input parameter packing
104      *         and return value unpacking.
105      *
106      *  @param[in] handler - A function object that is to be called as a
107      *                       continuation for the async dbus method call. The
108      *                       arguments to parse on the return are deduced from
109      *                       the handler's signature and then passed in along
110      *                       with an error code and optional message::message
111      *  @param[in] service - The service to call.
112      *  @param[in] objpath - The object's path for the call.
113      *  @param[in] interf - The object's interface to call.
114      *  @param[in] method - The object's method to call.
115      *  @param[in] timeout - The timeout for the method call in usec (0 results
116      *                       in using the default value).
117      *  @param[in] a... - Optional parameters for the method call.
118      *
119      *  @return immediate return of the internal handler registration. The
120      *          result of the actual asynchronous call will get unpacked from
121      *          the message and passed into the handler when the call is
122      *          complete.
123      */
124     template <typename MessageHandler, typename... InputArgs>
125     void async_method_call_timed(MessageHandler&& handler,
126                                  const std::string& service,
127                                  const std::string& objpath,
128                                  const std::string& interf,
129                                  const std::string& method, uint64_t timeout,
130                                  const InputArgs&... a)
131     {
132         using FunctionTuple = boost::callable_traits::args_t<MessageHandler>;
133         using FunctionTupleType = utility::decay_tuple_t<FunctionTuple>;
134         constexpr bool returnWithMsg = []() {
135             if constexpr ((std::tuple_size_v<FunctionTupleType>) > 1)
136             {
137                 return std::is_same_v<
138                     std::tuple_element_t<1, FunctionTupleType>,
139                     sdbusplus::message::message>;
140             }
141             return false;
142         }();
143         using UnpackType = utility::strip_first_n_args_t<returnWithMsg ? 2 : 1,
144                                                          FunctionTupleType>;
145         auto applyHandler = [handler = std::forward<MessageHandler>(handler)](
146                                 boost::system::error_code ec,
147                                 message::message& r) mutable {
148             UnpackType responseData;
149             if (!ec)
150             {
151                 try
152                 {
153                     utility::read_into_tuple(responseData, r);
154                 }
155                 catch (const std::exception& e)
156                 {
157                     // Set error code if not already set
158                     ec = boost::system::errc::make_error_code(
159                         boost::system::errc::invalid_argument);
160                 }
161             }
162             // Note.  Callback is called whether or not the unpack was
163             // successful to allow the user to implement their own handling
164             if constexpr (returnWithMsg)
165             {
166                 auto response = std::tuple_cat(std::make_tuple(ec),
167                                                std::forward_as_tuple(r),
168                                                std::move(responseData));
169                 std::apply(handler, response);
170             }
171             else
172             {
173                 auto response = std::tuple_cat(std::make_tuple(ec),
174                                                std::move(responseData));
175                 std::apply(handler, response);
176             }
177         };
178         message::message m;
179         boost::system::error_code ec;
180         try
181         {
182             m = new_method_call(service.c_str(), objpath.c_str(),
183                                 interf.c_str(), method.c_str());
184             m.append(a...);
185         }
186         catch (const exception::SdBusError& e)
187         {
188             ec = boost::system::errc::make_error_code(
189                 static_cast<boost::system::errc::errc_t>(e.get_errno()));
190             applyHandler(ec, m);
191             return;
192         }
193         async_send(m, std::forward<decltype(applyHandler)>(applyHandler),
194                    timeout);
195     }
196 
197     /** @brief Perform an asynchronous method call, with input parameter packing
198      *         and return value unpacking. Uses the default timeout value.
199      *
200      *  @param[in] handler - A function object that is to be called as a
201      *                       continuation for the async dbus method call. The
202      *                       arguments to parse on the return are deduced from
203      *                       the handler's signature and then passed in along
204      *                       with an error code and optional message::message
205      *  @param[in] service - The service to call.
206      *  @param[in] objpath - The object's path for the call.
207      *  @param[in] interf - The object's interface to call.
208      *  @param[in] method - The object's method to call.
209      *  @param[in] a... - Optional parameters for the method call.
210      *
211      *  @return immediate return of the internal handler registration. The
212      *          result of the actual asynchronous call will get unpacked from
213      *          the message and passed into the handler when the call is
214      *          complete.
215      */
216     template <typename MessageHandler, typename... InputArgs>
217     void async_method_call(MessageHandler&& handler, const std::string& service,
218                            const std::string& objpath,
219                            const std::string& interf, const std::string& method,
220                            const InputArgs&... a)
221     {
222         async_method_call_timed(std::forward<MessageHandler>(handler), service,
223                                 objpath, interf, method, 0, a...);
224     }
225 
226     /** @brief Perform a yielding asynchronous method call, with input
227      *         parameter packing and return value unpacking
228      *
229      *  @param[in] yield - A yield context to async block upon.
230      *  @param[in] ec - an error code that will be set for any errors
231      *  @param[in] service - The service to call.
232      *  @param[in] objpath - The object's path for the call.
233      *  @param[in] interf - The object's interface to call.
234      *  @param[in] method - The object's method to call.
235      *  @param[in] a... - Optional parameters for the method call.
236      *
237      *  @return Unpacked value of RetType
238      */
239     template <typename... RetTypes, typename... InputArgs>
240     auto yield_method_call(boost::asio::yield_context yield,
241                            boost::system::error_code& ec,
242                            const std::string& service,
243                            const std::string& objpath,
244                            const std::string& interf, const std::string& method,
245                            const InputArgs&... a)
246     {
247         message::message m;
248         try
249         {
250             m = new_method_call(service.c_str(), objpath.c_str(),
251                                 interf.c_str(), method.c_str());
252             m.append(a...);
253         }
254         catch (const exception::SdBusError& e)
255         {
256             ec = boost::system::errc::make_error_code(
257                 static_cast<boost::system::errc::errc_t>(e.get_errno()));
258         }
259         message::message r;
260         if (!ec)
261         {
262             r = async_send(m, yield[ec]);
263         }
264         if constexpr (sizeof...(RetTypes) == 0)
265         {
266             // void return
267             return;
268         }
269         else if constexpr (sizeof...(RetTypes) == 1)
270         {
271             if constexpr (std::is_same_v<utility::first_type_t<RetTypes...>,
272                                          void>)
273             {
274                 return;
275             }
276             else
277             {
278                 // single item return
279                 utility::first_type_t<RetTypes...> responseData{};
280                 // before attempting to read, check ec and bail on error
281                 if (ec)
282                 {
283                     return responseData;
284                 }
285                 try
286                 {
287                     r.read(responseData);
288                 }
289                 catch (const std::exception& e)
290                 {
291                     ec = boost::system::errc::make_error_code(
292                         boost::system::errc::invalid_argument);
293                     // responseData will be default-constructed...
294                 }
295                 return responseData;
296             }
297         }
298         else
299         {
300             // tuple of things to return
301             std::tuple<RetTypes...> responseData{};
302             // before attempting to read, check ec and bail on error
303             if (ec)
304             {
305                 return responseData;
306             }
307             try
308             {
309                 r.read(responseData);
310             }
311             catch (const std::exception& e)
312             {
313                 ec = boost::system::errc::make_error_code(
314                     boost::system::errc::invalid_argument);
315                 // responseData will be default-constructed...
316             }
317             return responseData;
318         }
319     }
320 
321     boost::asio::io_context& get_io_context()
322     {
323         return io_;
324     }
325 
326   private:
327     boost::asio::io_context& io_;
328     boost::asio::posix::stream_descriptor socket;
329 
330     void read_wait()
331     {
332         socket.async_read_some(
333             boost::asio::null_buffers(),
334             [&](const boost::system::error_code& ec, std::size_t) {
335                 if (ec)
336                 {
337                     return;
338                 }
339                 if (process_discard())
340                 {
341                     read_immediate();
342                 }
343                 else
344                 {
345                     read_wait();
346                 }
347             });
348     }
349     void read_immediate()
350     {
351         boost::asio::post(io_, [&] {
352             if (process_discard())
353             {
354                 read_immediate();
355             }
356             else
357             {
358                 read_wait();
359             }
360         });
361     }
362 };
363 
364 } // namespace asio
365 
366 } // namespace sdbusplus
367