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