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