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 /** @brief Perform a yielding asynchronous method call, with input 216 * parameter packing and return value unpacking 217 * 218 * @param[in] yield - A yield context to async block upon. 219 * @param[in] ec - an error code that will be set for any errors 220 * @param[in] service - The service to call. 221 * @param[in] objpath - The object's path for the call. 222 * @param[in] interf - The object's interface to call. 223 * @param[in] method - The object's method to call. 224 * @param[in] a - Optional parameters for the method call. 225 * 226 * @return Unpacked value of RetType 227 */ 228 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)229 auto yield_method_call( 230 boost::asio::yield_context yield, boost::system::error_code& ec, 231 const std::string& service, const std::string& objpath, 232 const std::string& interf, const std::string& method, 233 const InputArgs&... a) 234 { 235 message_t m; 236 try 237 { 238 m = new_method_call(service.c_str(), objpath.c_str(), 239 interf.c_str(), method.c_str()); 240 m.append(a...); 241 } 242 catch (const exception::SdBusError& e) 243 { 244 ec = boost::system::errc::make_error_code( 245 static_cast<boost::system::errc::errc_t>(e.get_errno())); 246 } 247 message_t r; 248 if (!ec) 249 { 250 r = async_send(m, yield[ec]); 251 } 252 if constexpr (sizeof...(RetTypes) == 0) 253 { 254 // void return 255 return; 256 } 257 else if constexpr (sizeof...(RetTypes) == 1) 258 { 259 if constexpr (std::is_same_v<utility::first_type_t<RetTypes...>, 260 void>) 261 { 262 return; 263 } 264 else 265 { 266 // single item return 267 utility::first_type_t<RetTypes...> responseData{}; 268 // before attempting to read, check ec and bail on error 269 if (ec) 270 { 271 return responseData; 272 } 273 try 274 { 275 r.read(responseData); 276 } 277 catch (const std::exception&) 278 { 279 ec = boost::system::errc::make_error_code( 280 boost::system::errc::invalid_argument); 281 // responseData will be default-constructed... 282 } 283 return responseData; 284 } 285 } 286 else 287 { 288 // tuple of things to return 289 std::tuple<RetTypes...> responseData{}; 290 // before attempting to read, check ec and bail on error 291 if (ec) 292 { 293 return responseData; 294 } 295 try 296 { 297 responseData = r.unpack<RetTypes...>(); 298 } 299 catch (const std::exception&) 300 { 301 ec = boost::system::errc::make_error_code( 302 boost::system::errc::invalid_argument); 303 // responseData will be default-constructed... 304 } 305 return responseData; 306 } 307 } 308 #endif get_io_context()309 boost::asio::io_context& get_io_context() 310 { 311 return io_; 312 } 313 314 private: 315 boost::asio::io_context& io_; 316 boost::asio::posix::stream_descriptor socket; 317 boost::asio::steady_timer timer; 318 process()319 void process() 320 { 321 if (process_discard()) 322 { 323 read_immediate(); 324 } 325 else 326 { 327 read_wait(); 328 } 329 } 330 on_fd_event(const boost::system::error_code & ec)331 void on_fd_event(const boost::system::error_code& ec) 332 { 333 // This is expected if the timer expired before an fd event was 334 // available 335 if (ec == boost::asio::error::operation_aborted) 336 { 337 return; 338 } 339 timer.cancel(); 340 if (ec) 341 { 342 return; 343 } 344 process(); 345 } 346 on_timer_event(const boost::system::error_code & ec)347 void on_timer_event(const boost::system::error_code& ec) 348 { 349 if (ec == boost::asio::error::operation_aborted) 350 { 351 // This is expected if the fd was available before the timer expired 352 return; 353 } 354 if (ec) 355 { 356 return; 357 } 358 // Abort existing operations on the socket 359 socket.cancel(); 360 process(); 361 } 362 read_wait()363 void read_wait() 364 { 365 int fd = get_fd(); 366 if (fd < 0) 367 { 368 return; 369 } 370 if (fd != socket.native_handle()) 371 { 372 socket.release(); 373 socket.assign(fd); 374 } 375 int events = get_events(); 376 if (events < 0) 377 { 378 return; 379 } 380 if (events & POLLIN) 381 { 382 socket.async_wait(boost::asio::posix::stream_descriptor::wait_read, 383 std::bind_front(&connection::on_fd_event, this)); 384 } 385 if (events & POLLOUT) 386 { 387 socket.async_wait(boost::asio::posix::stream_descriptor::wait_write, 388 std::bind_front(&connection::on_fd_event, this)); 389 } 390 if (events & POLLERR) 391 { 392 socket.async_wait(boost::asio::posix::stream_descriptor::wait_error, 393 std::bind_front(&connection::on_fd_event, this)); 394 } 395 396 uint64_t timeout = 0; 397 int timeret = get_timeout(&timeout); 398 if (timeret < 0) 399 { 400 return; 401 } 402 using clock = std::chrono::steady_clock; 403 404 using SdDuration = std::chrono::duration<uint64_t, std::micro>; 405 SdDuration sdTimeout(timeout); 406 // sd-bus always returns a 64 bit timeout regardless of architecture, 407 // and per the documentation routinely returns UINT64_MAX 408 if (sdTimeout > clock::duration::max()) 409 { 410 // No need to start the timer if the expiration is longer than 411 // underlying timer can run. 412 return; 413 } 414 auto nativeTimeout = std::chrono::floor<clock::duration>(sdTimeout); 415 timer.expires_at(clock::time_point(nativeTimeout)); 416 timer.async_wait(std::bind_front(&connection::on_timer_event, this)); 417 } read_immediate()418 void read_immediate() 419 { 420 boost::asio::post(io_, std::bind_front(&connection::process, this)); 421 } 422 }; 423 424 } // namespace asio 425 426 } // namespace sdbusplus 427