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