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 #ifndef SDBUSPLUS_DISABLE_BOOST_COROUTINES 29 #include <boost/asio/spawn.hpp> 30 #endif 31 #include <boost/callable_traits.hpp> 32 #include <sdbusplus/asio/detail/async_send_handler.hpp> 33 #include <sdbusplus/message.hpp> 34 #include <sdbusplus/utility/read_into_tuple.hpp> 35 #include <sdbusplus/utility/type_traits.hpp> 36 37 #include <chrono> 38 #include <string> 39 #include <tuple> 40 41 namespace sdbusplus 42 { 43 44 namespace asio 45 { 46 47 /// Root D-Bus IO object 48 /** 49 * A connection to a bus, through which messages may be sent or received. 50 */ 51 class connection : public sdbusplus::bus_t 52 { 53 public: 54 // default to system bus 55 connection(boost::asio::io_context& io) : 56 sdbusplus::bus_t(sdbusplus::bus::new_default()), io_(io), 57 socket(io_.get_executor(), get_fd()) 58 { 59 read_immediate(); 60 } 61 connection(boost::asio::io_context& io, sd_bus* bus) : 62 sdbusplus::bus_t(bus), io_(io), socket(io_.get_executor(), get_fd()) 63 { 64 read_immediate(); 65 } 66 ~connection() 67 { 68 // The FD will be closed by the socket object, so assign null to the 69 // sd_bus object to avoid a double close() Ignore return codes here, 70 // because there's nothing we can do about errors 71 socket.release(); 72 } 73 74 /** @brief Perform an asynchronous send of a message, executing the handler 75 * upon return and return 76 * 77 * @param[in] m - A message ready to send 78 * @param[in] token - The completion token to execute upon completion; 79 * @param[in] timeout - The timeout in microseconds 80 * 81 */ 82 template <typename CompletionToken> 83 inline auto async_send(message_t& m, CompletionToken&& token, 84 uint64_t timeout = 0) 85 { 86 #ifdef SDBUSPLUS_DISABLE_BOOST_COROUTINES 87 constexpr bool is_yield = false; 88 #else 89 constexpr bool is_yield = 90 std::is_same_v<CompletionToken, boost::asio::yield_context>; 91 #endif 92 using return_t = std::conditional_t<is_yield, message_t, message_t&>; 93 using callback_t = void(boost::system::error_code, return_t); 94 return boost::asio::async_initiate<CompletionToken, callback_t>( 95 detail::async_send_handler(get(), m, timeout), token); 96 } 97 98 /** @brief Perform an asynchronous method call, with input parameter packing 99 * and return value unpacking. 100 * 101 * @param[in] handler - A function object that is to be called as a 102 * continuation for the async dbus method call. The 103 * arguments to parse on the return are deduced from 104 * the handler's signature and then passed in along 105 * with an error code and optional message_t 106 * @param[in] service - The service to call. 107 * @param[in] objpath - The object's path for the call. 108 * @param[in] interf - The object's interface to call. 109 * @param[in] method - The object's method to call. 110 * @param[in] timeout - The timeout for the method call in usec (0 results 111 * in using the default value). 112 * @param[in] a - Optional parameters for the method call. 113 * 114 */ 115 template <typename MessageHandler, typename... InputArgs> 116 void async_method_call_timed( 117 MessageHandler&& handler, const std::string& service, 118 const std::string& objpath, const std::string& interf, 119 const std::string& method, uint64_t timeout, const InputArgs&... a) 120 { 121 using FunctionTuple = boost::callable_traits::args_t<MessageHandler>; 122 using FunctionTupleType = utility::decay_tuple_t<FunctionTuple>; 123 constexpr bool returnWithMsg = []() { 124 if constexpr ((std::tuple_size_v<FunctionTupleType>) > 1) 125 { 126 return std::is_same_v< 127 std::tuple_element_t<1, FunctionTupleType>, 128 sdbusplus::message_t>; 129 } 130 return false; 131 }(); 132 using UnpackType = utility::strip_first_n_args_t<returnWithMsg ? 2 : 1, 133 FunctionTupleType>; 134 auto applyHandler = 135 [handler = std::forward<MessageHandler>( 136 handler)](boost::system::error_code ec, message_t& r) mutable { 137 UnpackType responseData; 138 if (!ec) 139 { 140 try 141 { 142 utility::read_into_tuple(responseData, r); 143 } 144 catch (const std::exception&) 145 { 146 // Set error code if not already set 147 ec = boost::system::errc::make_error_code( 148 boost::system::errc::invalid_argument); 149 } 150 } 151 // Note. Callback is called whether or not the unpack was 152 // successful to allow the user to implement their own handling 153 if constexpr (returnWithMsg) 154 { 155 auto response = std::tuple_cat(std::make_tuple(ec), 156 std::forward_as_tuple(r), 157 std::move(responseData)); 158 std::apply(handler, response); 159 } 160 else 161 { 162 auto response = std::tuple_cat(std::make_tuple(ec), 163 std::move(responseData)); 164 std::apply(handler, response); 165 } 166 }; 167 message_t m; 168 boost::system::error_code ec; 169 try 170 { 171 m = new_method_call(service.c_str(), objpath.c_str(), 172 interf.c_str(), method.c_str()); 173 m.append(a...); 174 } 175 catch (const exception::SdBusError& e) 176 { 177 ec = boost::system::errc::make_error_code( 178 static_cast<boost::system::errc::errc_t>(e.get_errno())); 179 applyHandler(ec, m); 180 return; 181 } 182 async_send(m, std::forward<decltype(applyHandler)>(applyHandler), 183 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> 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 const InputArgs&... a) 206 { 207 async_method_call_timed(std::forward<MessageHandler>(handler), service, 208 objpath, interf, method, 0, a...); 209 } 210 211 #ifndef SDBUSPLUS_DISABLE_BOOST_COROUTINES 212 /** @brief Perform a yielding asynchronous method call, with input 213 * parameter packing and return value unpacking 214 * 215 * @param[in] yield - A yield context to async block upon. 216 * @param[in] ec - an error code that will be set for any errors 217 * @param[in] service - The service to call. 218 * @param[in] objpath - The object's path for the call. 219 * @param[in] interf - The object's interface to call. 220 * @param[in] method - The object's method to call. 221 * @param[in] a - Optional parameters for the method call. 222 * 223 * @return Unpacked value of RetType 224 */ 225 template <typename... RetTypes, typename... InputArgs> 226 auto yield_method_call( 227 boost::asio::yield_context yield, boost::system::error_code& ec, 228 const std::string& service, const std::string& objpath, 229 const std::string& interf, const std::string& method, 230 const InputArgs&... a) 231 { 232 message_t m; 233 try 234 { 235 m = new_method_call(service.c_str(), objpath.c_str(), 236 interf.c_str(), method.c_str()); 237 m.append(a...); 238 } 239 catch (const exception::SdBusError& e) 240 { 241 ec = boost::system::errc::make_error_code( 242 static_cast<boost::system::errc::errc_t>(e.get_errno())); 243 } 244 message_t r; 245 if (!ec) 246 { 247 r = async_send(m, yield[ec]); 248 } 249 if constexpr (sizeof...(RetTypes) == 0) 250 { 251 // void return 252 return; 253 } 254 else if constexpr (sizeof...(RetTypes) == 1) 255 { 256 if constexpr (std::is_same_v<utility::first_type_t<RetTypes...>, 257 void>) 258 { 259 return; 260 } 261 else 262 { 263 // single item return 264 utility::first_type_t<RetTypes...> responseData{}; 265 // before attempting to read, check ec and bail on error 266 if (ec) 267 { 268 return responseData; 269 } 270 try 271 { 272 r.read(responseData); 273 } 274 catch (const std::exception&) 275 { 276 ec = boost::system::errc::make_error_code( 277 boost::system::errc::invalid_argument); 278 // responseData will be default-constructed... 279 } 280 return responseData; 281 } 282 } 283 else 284 { 285 // tuple of things to return 286 std::tuple<RetTypes...> responseData{}; 287 // before attempting to read, check ec and bail on error 288 if (ec) 289 { 290 return responseData; 291 } 292 try 293 { 294 r.read(responseData); 295 } 296 catch (const std::exception&) 297 { 298 ec = boost::system::errc::make_error_code( 299 boost::system::errc::invalid_argument); 300 // responseData will be default-constructed... 301 } 302 return responseData; 303 } 304 } 305 #endif 306 boost::asio::io_context& get_io_context() 307 { 308 return io_; 309 } 310 311 private: 312 boost::asio::io_context& io_; 313 boost::asio::posix::stream_descriptor socket; 314 315 void read_wait() 316 { 317 socket.async_read_some( 318 boost::asio::null_buffers(), 319 [&](const boost::system::error_code& ec, std::size_t) { 320 if (ec) 321 { 322 return; 323 } 324 if (process_discard()) 325 { 326 read_immediate(); 327 } 328 else 329 { 330 read_wait(); 331 } 332 }); 333 } 334 void read_immediate() 335 { 336 boost::asio::post(io_, [&] { 337 if (process_discard()) 338 { 339 read_immediate(); 340 } 341 else 342 { 343 read_wait(); 344 } 345 }); 346 } 347 }; 348 349 } // namespace asio 350 351 } // namespace sdbusplus 352