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/steady_timer.hpp>
29 
30 #ifndef SDBUSPLUS_DISABLE_BOOST_COROUTINES
31 #include <boost/asio/spawn.hpp>
32 #endif
33 #include <boost/callable_traits.hpp>
34 #include <sdbusplus/asio/detail/async_send_handler.hpp>
35 #include <sdbusplus/message.hpp>
36 #include <sdbusplus/utility/read_into_tuple.hpp>
37 #include <sdbusplus/utility/type_traits.hpp>
38 
39 #include <chrono>
40 #include <string>
41 #include <tuple>
42 
43 namespace sdbusplus
44 {
45 
46 namespace asio
47 {
48 
49 /// Root D-Bus IO object
50 /**
51  * A connection to a bus, through which messages may be sent or received.
52  */
53 class connection : public sdbusplus::bus_t
54 {
55   public:
56     // default to system bus
connection(boost::asio::io_context & io)57     connection(boost::asio::io_context& io) :
58         sdbusplus::bus_t(sdbusplus::bus::new_default()), io_(io),
59         socket(io_.get_executor(), get_fd()), timer(io_.get_executor())
60     {
61         read_immediate();
62     }
connection(boost::asio::io_context & io,sd_bus * bus)63     connection(boost::asio::io_context& io, sd_bus* bus) :
64         sdbusplus::bus_t(bus), io_(io), socket(io_.get_executor(), get_fd()),
65         timer(io_.get_executor())
66     {
67         read_immediate();
68     }
~connection()69     ~connection()
70     {
71         // The FD will be closed by the socket object, so assign null to the
72         // sd_bus object to avoid a double close()  Ignore return codes here,
73         // because there's nothing we can do about errors
74         socket.release();
75     }
76 
77     /** @brief Perform an asynchronous send of a message, executing the handler
78      *         upon return and return
79      *
80      *  @param[in] m - A message ready to send
81      *  @param[in] token - The completion token to execute upon completion;
82      *  @param[in] timeout - The timeout in microseconds
83      *
84      */
85     template <typename CompletionToken>
async_send(message_t & m,CompletionToken && token,uint64_t timeout=0)86     inline auto async_send(message_t& m, CompletionToken&& token,
87                            uint64_t timeout = 0)
88     {
89 #ifdef SDBUSPLUS_DISABLE_BOOST_COROUTINES
90         constexpr bool is_yield = false;
91 #else
92         constexpr bool is_yield =
93             std::is_same_v<CompletionToken, boost::asio::yield_context>;
94 #endif
95         using return_t = std::conditional_t<is_yield, message_t, message_t&>;
96         using callback_t = void(boost::system::error_code, return_t);
97         return boost::asio::async_initiate<CompletionToken, callback_t>(
98             detail::async_send_handler(get(), m, timeout), token);
99     }
100 
101     /** @brief Perform an asynchronous method call, with input parameter packing
102      *         and return value unpacking.
103      *
104      *  @param[in] handler - A function object that is to be called as a
105      *                       continuation for the async dbus method call. The
106      *                       arguments to parse on the return are deduced from
107      *                       the handler's signature and then passed in along
108      *                       with an error code and optional message_t
109      *  @param[in] service - The service to call.
110      *  @param[in] objpath - The object's path for the call.
111      *  @param[in] interf - The object's interface to call.
112      *  @param[in] method - The object's method to call.
113      *  @param[in] timeout - The timeout for the method call in usec (0 results
114      *                       in using the default value).
115      *  @param[in] a - Optional parameters for the method call.
116      *
117      */
118     template <typename MessageHandler, typename... InputArgs>
async_method_call_timed(MessageHandler && handler,const std::string & service,const std::string & objpath,const std::string & interf,const std::string & method,uint64_t timeout,const InputArgs &...a)119     void async_method_call_timed(
120         MessageHandler&& handler, const std::string& service,
121         const std::string& objpath, const std::string& interf,
122         const std::string& method, uint64_t timeout, const InputArgs&... a)
123     {
124         using FunctionTuple = boost::callable_traits::args_t<MessageHandler>;
125         using FunctionTupleType = utility::decay_tuple_t<FunctionTuple>;
126         constexpr bool returnWithMsg = []() {
127             if constexpr ((std::tuple_size_v<FunctionTupleType>) > 1)
128             {
129                 return std::is_same_v<
130                     std::tuple_element_t<1, FunctionTupleType>,
131                     sdbusplus::message_t>;
132             }
133             return false;
134         }();
135         using UnpackType = utility::strip_first_n_args_t<returnWithMsg ? 2 : 1,
136                                                          FunctionTupleType>;
137         auto applyHandler =
138             [handler = std::forward<MessageHandler>(
139                  handler)](boost::system::error_code ec, message_t& r) mutable {
140                 UnpackType responseData;
141                 if (!ec)
142                 {
143                     try
144                     {
145                         utility::read_into_tuple(responseData, r);
146                     }
147                     catch (const std::exception&)
148                     {
149                         // Set error code if not already set
150                         ec = boost::system::errc::make_error_code(
151                             boost::system::errc::invalid_argument);
152                     }
153                 }
154                 // Note.  Callback is called whether or not the unpack was
155                 // successful to allow the user to implement their own handling
156                 if constexpr (returnWithMsg)
157                 {
158                     auto response = std::tuple_cat(std::make_tuple(ec),
159                                                    std::forward_as_tuple(r),
160                                                    std::move(responseData));
161                     std::apply(handler, response);
162                 }
163                 else
164                 {
165                     auto response = std::tuple_cat(std::make_tuple(ec),
166                                                    std::move(responseData));
167                     std::apply(handler, response);
168                 }
169             };
170         message_t m;
171         boost::system::error_code ec;
172         try
173         {
174             m = new_method_call(service.c_str(), objpath.c_str(),
175                                 interf.c_str(), method.c_str());
176             m.append(a...);
177         }
178         catch (const exception::SdBusError& e)
179         {
180             ec = boost::system::errc::make_error_code(
181                 static_cast<boost::system::errc::errc_t>(e.get_errno()));
182             applyHandler(ec, m);
183             return;
184         }
185         async_send(m, std::forward<decltype(applyHandler)>(applyHandler),
186                    timeout);
187     }
188 
189     /** @brief Perform an asynchronous method call, with input parameter packing
190      *         and return value unpacking. Uses the default timeout value.
191      *
192      *  @param[in] handler - A function object that is to be called as a
193      *                       continuation for the async dbus method call. The
194      *                       arguments to parse on the return are deduced from
195      *                       the handler's signature and then passed in along
196      *                       with an error code and optional message_t
197      *  @param[in] service - The service to call.
198      *  @param[in] objpath - The object's path for the call.
199      *  @param[in] interf - The object's interface to call.
200      *  @param[in] method - The object's method to call.
201      *  @param[in] a - Optional parameters for the method call.
202      *
203      */
204     template <typename MessageHandler, typename... InputArgs>
async_method_call(MessageHandler && handler,const std::string & service,const std::string & objpath,const std::string & interf,const std::string & method,const InputArgs &...a)205     void async_method_call(MessageHandler&& handler, const std::string& service,
206                            const std::string& objpath,
207                            const std::string& interf, const std::string& method,
208                            const InputArgs&... a)
209     {
210         async_method_call_timed(std::forward<MessageHandler>(handler), service,
211                                 objpath, interf, method, 0, a...);
212     }
213 
214 #ifndef SDBUSPLUS_DISABLE_BOOST_COROUTINES
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>
yield_method_call(boost::asio::yield_context yield,boost::system::error_code & ec,const std::string & service,const std::string & objpath,const std::string & interf,const std::string & method,const InputArgs &...a)229     auto yield_method_call(
230         boost::asio::yield_context yield, boost::system::error_code& ec,
231         const std::string& service, const std::string& objpath,
232         const std::string& interf, const std::string& method,
233         const InputArgs&... a)
234     {
235         message_t m;
236         try
237         {
238             m = new_method_call(service.c_str(), objpath.c_str(),
239                                 interf.c_str(), method.c_str());
240             m.append(a...);
241         }
242         catch (const exception::SdBusError& e)
243         {
244             ec = boost::system::errc::make_error_code(
245                 static_cast<boost::system::errc::errc_t>(e.get_errno()));
246         }
247         message_t r;
248         if (!ec)
249         {
250             r = async_send(m, yield[ec]);
251         }
252         if constexpr (sizeof...(RetTypes) == 0)
253         {
254             // void return
255             return;
256         }
257         else if constexpr (sizeof...(RetTypes) == 1)
258         {
259             if constexpr (std::is_same_v<utility::first_type_t<RetTypes...>,
260                                          void>)
261             {
262                 return;
263             }
264             else
265             {
266                 // single item return
267                 utility::first_type_t<RetTypes...> responseData{};
268                 // before attempting to read, check ec and bail on error
269                 if (ec)
270                 {
271                     return responseData;
272                 }
273                 try
274                 {
275                     r.read(responseData);
276                 }
277                 catch (const std::exception&)
278                 {
279                     ec = boost::system::errc::make_error_code(
280                         boost::system::errc::invalid_argument);
281                     // responseData will be default-constructed...
282                 }
283                 return responseData;
284             }
285         }
286         else
287         {
288             // tuple of things to return
289             std::tuple<RetTypes...> responseData{};
290             // before attempting to read, check ec and bail on error
291             if (ec)
292             {
293                 return responseData;
294             }
295             try
296             {
297                 responseData = r.unpack<RetTypes...>();
298             }
299             catch (const std::exception&)
300             {
301                 ec = boost::system::errc::make_error_code(
302                     boost::system::errc::invalid_argument);
303                 // responseData will be default-constructed...
304             }
305             return responseData;
306         }
307     }
308 #endif
get_io_context()309     boost::asio::io_context& get_io_context()
310     {
311         return io_;
312     }
313 
314   private:
315     boost::asio::io_context& io_;
316     boost::asio::posix::stream_descriptor socket;
317     boost::asio::steady_timer timer;
318 
process()319     void process()
320     {
321         if (process_discard())
322         {
323             read_immediate();
324         }
325         else
326         {
327             read_wait();
328         }
329     }
330 
on_fd_event(const boost::system::error_code & ec)331     void on_fd_event(const boost::system::error_code& ec)
332     {
333         // This is expected if the timer expired before an fd event was
334         // available
335         if (ec == boost::asio::error::operation_aborted)
336         {
337             return;
338         }
339         timer.cancel();
340         if (ec)
341         {
342             return;
343         }
344         process();
345     }
346 
on_timer_event(const boost::system::error_code & ec)347     void on_timer_event(const boost::system::error_code& ec)
348     {
349         if (ec == boost::asio::error::operation_aborted)
350         {
351             // This is expected if the fd was available before the timer expired
352             return;
353         }
354         if (ec)
355         {
356             return;
357         }
358         // Abort existing operations on the socket
359         socket.cancel();
360         process();
361     }
362 
read_wait()363     void read_wait()
364     {
365         int fd = get_fd();
366         if (fd < 0)
367         {
368             return;
369         }
370         if (fd != socket.native_handle())
371         {
372             socket.release();
373             socket.assign(fd);
374         }
375         int events = get_events();
376         if (events < 0)
377         {
378             return;
379         }
380         if (events & POLLIN)
381         {
382             socket.async_wait(boost::asio::posix::stream_descriptor::wait_read,
383                               std::bind_front(&connection::on_fd_event, this));
384         }
385         if (events & POLLOUT)
386         {
387             socket.async_wait(boost::asio::posix::stream_descriptor::wait_write,
388                               std::bind_front(&connection::on_fd_event, this));
389         }
390         if (events & POLLERR)
391         {
392             socket.async_wait(boost::asio::posix::stream_descriptor::wait_error,
393                               std::bind_front(&connection::on_fd_event, this));
394         }
395 
396         uint64_t timeout = 0;
397         int timeret = get_timeout(&timeout);
398         if (timeret < 0)
399         {
400             return;
401         }
402         using clock = std::chrono::steady_clock;
403 
404         using SdDuration = std::chrono::duration<uint64_t, std::micro>;
405         SdDuration sdTimeout(timeout);
406         // sd-bus always returns a 64 bit timeout regardless of architecture,
407         // and per the documentation routinely returns UINT64_MAX
408         if (sdTimeout > clock::duration::max())
409         {
410             // No need to start the timer if the expiration is longer than
411             // underlying timer can run.
412             return;
413         }
414         auto nativeTimeout = std::chrono::floor<clock::duration>(sdTimeout);
415         timer.expires_at(clock::time_point(nativeTimeout));
416         timer.async_wait(std::bind_front(&connection::on_timer_event, this));
417     }
read_immediate()418     void read_immediate()
419     {
420         boost::asio::post(io_, std::bind_front(&connection::process, this));
421     }
422 };
423 
424 } // namespace asio
425 
426 } // namespace sdbusplus
427