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