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