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