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