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