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