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