xref: /openbmc/sdbusplus/include/sdbusplus/asio/connection.hpp (revision 4076259525c9301efe02ebfa321713067f51c528)
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/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(const boost::system::error_code & ec,message_t & r,MessageHandler && handler)110     static void unpack(const 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         FunctionTupleType responseData;
116         if (ec)
117         {
118             std::get<0>(responseData) = ec;
119         }
120         else
121         {
122             try
123             {
124                 auto unpack = utility::make_dbus_args_tuple(responseData);
125                 std::apply([&r](auto&&... x) { (r.read(x), ...); }, unpack);
126             }
127             catch (const std::exception&)
128             {
129                 // Set error code if not already set
130                 std::get<0>(responseData) =
131                     boost::system::errc::make_error_code(
132                         boost::system::errc::invalid_argument);
133             }
134         }
135         std::apply(handler, responseData);
136     }
137 
138     /** @brief Perform an asynchronous method call, with input parameter packing
139      *         and return value unpacking.
140      *
141      *  @param[in] handler - A function object that is to be called as a
142      *                       continuation for the async dbus method call. The
143      *                       arguments to parse on the return are deduced from
144      *                       the handler's signature and then passed in along
145      *                       with an error code and optional message_t
146      *  @param[in] service - The service to call.
147      *  @param[in] objpath - The object's path for the call.
148      *  @param[in] interf - The object's interface to call.
149      *  @param[in] method - The object's method to call.
150      *  @param[in] timeout - The timeout for the method call in usec (0 results
151      *                       in using the default value).
152      *  @param[in] a - Optional parameters for the method call.
153      *
154      */
155     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)156     void async_method_call_timed(
157         MessageHandler&& handler, const std::string& service,
158         const std::string& objpath, const std::string& interf,
159         const std::string& method, uint64_t timeout, const InputArgs&... a)
160     {
161         using callback_t = std::move_only_function<void(
162             boost::system::error_code, message_t&)>;
163         callback_t applyHandler =
164             [handler = std::forward<MessageHandler>(
165                  handler)](boost::system::error_code ec, message_t& r) mutable {
166                 unpack(ec, r, std::move(handler));
167             };
168         message_t m;
169         boost::system::error_code ec;
170         try
171         {
172             m = new_method_call(service.c_str(), objpath.c_str(),
173                                 interf.c_str(), method.c_str());
174             m.append(a...);
175         }
176         catch (const exception::SdBusError& e)
177         {
178             ec = boost::system::errc::make_error_code(
179                 static_cast<boost::system::errc::errc_t>(e.get_errno()));
180             applyHandler(ec, m);
181             return;
182         }
183         async_send(m, std::move(applyHandler), timeout);
184     }
185 
186     /** @brief Perform an asynchronous method call, with input parameter packing
187      *         and return value unpacking. Uses the default timeout value.
188      *
189      *  @param[in] handler - A function object that is to be called as a
190      *                       continuation for the async dbus method call. The
191      *                       arguments to parse on the return are deduced from
192      *                       the handler's signature and then passed in along
193      *                       with an error code and optional message_t
194      *  @param[in] service - The service to call.
195      *  @param[in] objpath - The object's path for the call.
196      *  @param[in] interf - The object's interface to call.
197      *  @param[in] method - The object's method to call.
198      *  @param[in] a - Optional parameters for the method call.
199      *
200      */
201     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)202     void async_method_call(MessageHandler&& handler, const std::string& service,
203                            const std::string& objpath,
204                            const std::string& interf, const std::string& method,
205                            InputArgs&&... a)
206     {
207         async_method_call_timed(std::forward<MessageHandler>(handler), service,
208                                 objpath, interf, method, 0,
209                                 std::forward<InputArgs>(a)...);
210     }
211 
212 #ifndef SDBUSPLUS_DISABLE_BOOST_COROUTINES
213     template <typename... RetTypes>
default_ret_types()214     auto default_ret_types()
215     {
216         if constexpr (sizeof...(RetTypes) == 0)
217         {
218             return;
219         }
220         else if constexpr (sizeof...(RetTypes) == 1 &&
221                            std::is_void_v<std::tuple_element_t<
222                                0, std::tuple<RetTypes...>>>)
223         {
224             return;
225         }
226         else if constexpr (sizeof...(RetTypes) == 1)
227         {
228             return std::tuple_element_t<0, std::tuple<RetTypes...>>{};
229         }
230         else
231         {
232             return std::tuple<RetTypes...>{};
233         }
234     }
235     /** @brief Perform a yielding asynchronous method call, with input
236      *         parameter packing and return value unpacking
237      *
238      *  @param[in] yield - A yield context to async block upon.
239      *  @param[in] ec - an error code that will be set for any errors
240      *  @param[in] service - The service to call.
241      *  @param[in] objpath - The object's path for the call.
242      *  @param[in] interf - The object's interface to call.
243      *  @param[in] method - The object's method to call.
244      *  @param[in] a - Optional parameters for the method call.
245      *
246      *  @return Unpacked value of RetType
247      */
248     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)249     auto yield_method_call(
250         boost::asio::yield_context yield, boost::system::error_code& ec,
251         const std::string& service, const std::string& objpath,
252         const std::string& interf, const std::string& method,
253         const InputArgs&... a)
254     {
255         message_t m;
256         try
257         {
258             m = new_method_call(service.c_str(), objpath.c_str(),
259                                 interf.c_str(), method.c_str());
260             m.append(a...);
261         }
262         catch (const exception::SdBusError& e)
263         {
264             ec = boost::system::errc::make_error_code(
265                 static_cast<boost::system::errc::errc_t>(e.get_errno()));
266         }
267         if (!ec)
268         {
269             message_t r;
270             r = async_send_yield(m, yield[ec]);
271             try
272             {
273                 return r.unpack<RetTypes...>();
274             }
275             catch (const std::exception&)
276             {
277                 ec = boost::system::errc::make_error_code(
278                     boost::system::errc::invalid_argument);
279             }
280         }
281         return default_ret_types<RetTypes...>();
282     }
283 #endif
get_io_context()284     boost::asio::io_context& get_io_context()
285     {
286         return io_;
287     }
288 
289   private:
290     boost::asio::io_context& io_;
291     boost::asio::posix::stream_descriptor socket;
292     boost::asio::steady_timer timer;
293 
process()294     void process()
295     {
296         if (process_discard())
297         {
298             read_immediate();
299         }
300         else
301         {
302             read_wait();
303         }
304     }
305 
on_fd_event(const boost::system::error_code & ec)306     void on_fd_event(const boost::system::error_code& ec)
307     {
308         // This is expected if the timer expired before an fd event was
309         // available
310         if (ec == boost::asio::error::operation_aborted)
311         {
312             return;
313         }
314         timer.cancel();
315         if (ec)
316         {
317             return;
318         }
319         process();
320     }
321 
on_timer_event(const boost::system::error_code & ec)322     void on_timer_event(const boost::system::error_code& ec)
323     {
324         if (ec == boost::asio::error::operation_aborted)
325         {
326             // This is expected if the fd was available before the timer expired
327             return;
328         }
329         if (ec)
330         {
331             return;
332         }
333         // Abort existing operations on the socket
334         socket.cancel();
335         process();
336     }
337 
read_wait()338     void read_wait()
339     {
340         int fd = get_fd();
341         if (fd < 0)
342         {
343             return;
344         }
345         if (fd != socket.native_handle())
346         {
347             socket.release();
348             socket.assign(fd);
349         }
350         int events = get_events();
351         if (events < 0)
352         {
353             return;
354         }
355         if (events & POLLIN)
356         {
357             socket.async_wait(boost::asio::posix::stream_descriptor::wait_read,
358                               std::bind_front(&connection::on_fd_event, this));
359         }
360         if (events & POLLOUT)
361         {
362             socket.async_wait(boost::asio::posix::stream_descriptor::wait_write,
363                               std::bind_front(&connection::on_fd_event, this));
364         }
365         if (events & POLLERR)
366         {
367             socket.async_wait(boost::asio::posix::stream_descriptor::wait_error,
368                               std::bind_front(&connection::on_fd_event, this));
369         }
370 
371         uint64_t timeout = 0;
372         int timeret = get_timeout(&timeout);
373         if (timeret < 0)
374         {
375             return;
376         }
377         using clock = std::chrono::steady_clock;
378 
379         using SdDuration = std::chrono::duration<uint64_t, std::micro>;
380         SdDuration sdTimeout(timeout);
381         // sd-bus always returns a 64 bit timeout regardless of architecture,
382         // and per the documentation routinely returns UINT64_MAX
383         if (sdTimeout > clock::duration::max())
384         {
385             // No need to start the timer if the expiration is longer than
386             // underlying timer can run.
387             return;
388         }
389         auto nativeTimeout = std::chrono::floor<clock::duration>(sdTimeout);
390         timer.expires_at(clock::time_point(nativeTimeout));
391         timer.async_wait(std::bind_front(&connection::on_timer_event, this));
392     }
read_immediate()393     void read_immediate()
394     {
395         boost::asio::post(io_, std::bind_front(&connection::process, this));
396     }
397 };
398 
399 } // namespace asio
400 
401 } // namespace sdbusplus
402