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