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_wait(); 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_wait(); 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(MessageHandler&& handler, 117 const std::string& service, 118 const std::string& objpath, 119 const std::string& interf, 120 const std::string& method, uint64_t timeout, 121 const InputArgs&... a) 122 { 123 using FunctionTuple = boost::callable_traits::args_t<MessageHandler>; 124 using FunctionTupleType = utility::decay_tuple_t<FunctionTuple>; 125 constexpr bool returnWithMsg = []() { 126 if constexpr ((std::tuple_size_v<FunctionTupleType>) > 1) 127 { 128 return std::is_same_v< 129 std::tuple_element_t<1, FunctionTupleType>, 130 sdbusplus::message_t>; 131 } 132 return false; 133 }(); 134 using UnpackType = utility::strip_first_n_args_t<returnWithMsg ? 2 : 1, 135 FunctionTupleType>; 136 auto applyHandler = [handler = std::forward<MessageHandler>(handler)]( 137 boost::system::error_code ec, 138 message_t& r) mutable { 139 UnpackType responseData; 140 if (!ec) 141 { 142 try 143 { 144 utility::read_into_tuple(responseData, r); 145 } 146 catch (const std::exception&) 147 { 148 // Set error code if not already set 149 ec = boost::system::errc::make_error_code( 150 boost::system::errc::invalid_argument); 151 } 152 } 153 // Note. Callback is called whether or not the unpack was 154 // successful to allow the user to implement their own handling 155 if constexpr (returnWithMsg) 156 { 157 auto response = std::tuple_cat(std::make_tuple(ec), 158 std::forward_as_tuple(r), 159 std::move(responseData)); 160 std::apply(handler, response); 161 } 162 else 163 { 164 auto response = std::tuple_cat(std::make_tuple(ec), 165 std::move(responseData)); 166 std::apply(handler, response); 167 } 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::forward<decltype(applyHandler)>(applyHandler), 185 timeout); 186 } 187 188 /** @brief Perform an asynchronous method call, with input parameter packing 189 * and return value unpacking. Uses the default timeout value. 190 * 191 * @param[in] handler - A function object that is to be called as a 192 * continuation for the async dbus method call. The 193 * arguments to parse on the return are deduced from 194 * the handler's signature and then passed in along 195 * with an error code and optional message_t 196 * @param[in] service - The service to call. 197 * @param[in] objpath - The object's path for the call. 198 * @param[in] interf - The object's interface to call. 199 * @param[in] method - The object's method to call. 200 * @param[in] a - Optional parameters for the method call. 201 * 202 */ 203 template <typename MessageHandler, typename... InputArgs> 204 void async_method_call(MessageHandler&& handler, const std::string& service, 205 const std::string& objpath, 206 const std::string& interf, const std::string& method, 207 const InputArgs&... a) 208 { 209 async_method_call_timed(std::forward<MessageHandler>(handler), service, 210 objpath, interf, method, 0, a...); 211 } 212 213 #ifndef SDBUSPLUS_DISABLE_BOOST_COROUTINES 214 /** @brief Perform a yielding asynchronous method call, with input 215 * parameter packing and return value unpacking 216 * 217 * @param[in] yield - A yield context to async block upon. 218 * @param[in] ec - an error code that will be set for any errors 219 * @param[in] service - The service to call. 220 * @param[in] objpath - The object's path for the call. 221 * @param[in] interf - The object's interface to call. 222 * @param[in] method - The object's method to call. 223 * @param[in] a - Optional parameters for the method call. 224 * 225 * @return Unpacked value of RetType 226 */ 227 template <typename... RetTypes, typename... InputArgs> 228 auto yield_method_call(boost::asio::yield_context yield, 229 boost::system::error_code& ec, 230 const std::string& service, 231 const std::string& objpath, 232 const std::string& interf, const std::string& method, 233 const InputArgs&... a) 234 { 235 message_t m; 236 try 237 { 238 m = new_method_call(service.c_str(), objpath.c_str(), 239 interf.c_str(), method.c_str()); 240 m.append(a...); 241 } 242 catch (const exception::SdBusError& e) 243 { 244 ec = boost::system::errc::make_error_code( 245 static_cast<boost::system::errc::errc_t>(e.get_errno())); 246 } 247 message_t r; 248 if (!ec) 249 { 250 r = async_send(m, yield[ec]); 251 } 252 if constexpr (sizeof...(RetTypes) == 0) 253 { 254 // void return 255 return; 256 } 257 else if constexpr (sizeof...(RetTypes) == 1) 258 { 259 if constexpr (std::is_same_v<utility::first_type_t<RetTypes...>, 260 void>) 261 { 262 return; 263 } 264 else 265 { 266 // single item return 267 utility::first_type_t<RetTypes...> responseData{}; 268 // before attempting to read, check ec and bail on error 269 if (ec) 270 { 271 return responseData; 272 } 273 try 274 { 275 r.read(responseData); 276 } 277 catch (const std::exception&) 278 { 279 ec = boost::system::errc::make_error_code( 280 boost::system::errc::invalid_argument); 281 // responseData will be default-constructed... 282 } 283 return responseData; 284 } 285 } 286 else 287 { 288 // tuple of things to return 289 std::tuple<RetTypes...> responseData{}; 290 // before attempting to read, check ec and bail on error 291 if (ec) 292 { 293 return responseData; 294 } 295 try 296 { 297 r.read(responseData); 298 } 299 catch (const std::exception&) 300 { 301 ec = boost::system::errc::make_error_code( 302 boost::system::errc::invalid_argument); 303 // responseData will be default-constructed... 304 } 305 return responseData; 306 } 307 } 308 #endif 309 boost::asio::io_context& get_io_context() 310 { 311 return io_; 312 } 313 314 private: 315 boost::asio::io_context& io_; 316 boost::asio::posix::stream_descriptor socket; 317 318 void read_wait() 319 { 320 socket.async_read_some( 321 boost::asio::null_buffers(), 322 [&](const boost::system::error_code& ec, std::size_t) { 323 if (ec) 324 { 325 return; 326 } 327 if (process_discard()) 328 { 329 read_immediate(); 330 } 331 else 332 { 333 read_wait(); 334 } 335 }); 336 } 337 void read_immediate() 338 { 339 boost::asio::post(io_, [&] { 340 if (process_discard()) 341 { 342 read_immediate(); 343 } 344 else 345 { 346 read_wait(); 347 } 348 }); 349 } 350 }; 351 352 } // namespace asio 353 354 } // namespace sdbusplus 355