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