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