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