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