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), socket(io_) 57 { 58 socket.assign(get_fd()); 59 read_wait(); 60 } 61 connection(boost::asio::io_context& io, sd_bus* bus) : 62 sdbusplus::bus_t(bus), io_(io), socket(io_) 63 { 64 socket.assign(get_fd()); 65 read_wait(); 66 } 67 ~connection() 68 { 69 // The FD will be closed by the socket object, so assign null to the 70 // sd_bus object to avoid a double close() Ignore return codes here, 71 // because there's nothing we can do about errors 72 socket.release(); 73 } 74 75 /** @brief Perform an asynchronous send of a message, executing the handler 76 * upon return and return 77 * 78 * @param[in] m - A message ready to send 79 * @param[in] token- The completion token to execute upon completion; 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 * @return immediate return of the internal handler registration. The 111 * result of the actual asynchronous call will get unpacked from 112 * the message and passed into the handler when the call is 113 * complete. 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 * @return immediate return of the internal handler registration. The 203 * result of the actual asynchronous call will get unpacked from 204 * the message and passed into the handler when the call is 205 * complete. 206 */ 207 template <typename MessageHandler, typename... InputArgs> 208 void async_method_call(MessageHandler&& handler, const std::string& service, 209 const std::string& objpath, 210 const std::string& interf, const std::string& method, 211 const InputArgs&... a) 212 { 213 async_method_call_timed(std::forward<MessageHandler>(handler), service, 214 objpath, interf, method, 0, a...); 215 } 216 217 #ifndef SDBUSPLUS_DISABLE_BOOST_COROUTINES 218 /** @brief Perform a yielding asynchronous method call, with input 219 * parameter packing and return value unpacking 220 * 221 * @param[in] yield - A yield context to async block upon. 222 * @param[in] ec - an error code that will be set for any errors 223 * @param[in] service - The service to call. 224 * @param[in] objpath - The object's path for the call. 225 * @param[in] interf - The object's interface to call. 226 * @param[in] method - The object's method to call. 227 * @param[in] a... - Optional parameters for the method call. 228 * 229 * @return Unpacked value of RetType 230 */ 231 template <typename... RetTypes, typename... InputArgs> 232 auto yield_method_call(boost::asio::yield_context yield, 233 boost::system::error_code& ec, 234 const std::string& service, 235 const std::string& objpath, 236 const std::string& interf, const std::string& method, 237 const InputArgs&... a) 238 { 239 message_t m; 240 try 241 { 242 m = new_method_call(service.c_str(), objpath.c_str(), 243 interf.c_str(), method.c_str()); 244 m.append(a...); 245 } 246 catch (const exception::SdBusError& e) 247 { 248 ec = boost::system::errc::make_error_code( 249 static_cast<boost::system::errc::errc_t>(e.get_errno())); 250 } 251 message_t r; 252 if (!ec) 253 { 254 r = async_send(m, yield[ec]); 255 } 256 if constexpr (sizeof...(RetTypes) == 0) 257 { 258 // void return 259 return; 260 } 261 else if constexpr (sizeof...(RetTypes) == 1) 262 { 263 if constexpr (std::is_same_v<utility::first_type_t<RetTypes...>, 264 void>) 265 { 266 return; 267 } 268 else 269 { 270 // single item return 271 utility::first_type_t<RetTypes...> responseData{}; 272 // before attempting to read, check ec and bail on error 273 if (ec) 274 { 275 return responseData; 276 } 277 try 278 { 279 r.read(responseData); 280 } 281 catch (const std::exception&) 282 { 283 ec = boost::system::errc::make_error_code( 284 boost::system::errc::invalid_argument); 285 // responseData will be default-constructed... 286 } 287 return responseData; 288 } 289 } 290 else 291 { 292 // tuple of things to return 293 std::tuple<RetTypes...> responseData{}; 294 // before attempting to read, check ec and bail on error 295 if (ec) 296 { 297 return responseData; 298 } 299 try 300 { 301 r.read(responseData); 302 } 303 catch (const std::exception&) 304 { 305 ec = boost::system::errc::make_error_code( 306 boost::system::errc::invalid_argument); 307 // responseData will be default-constructed... 308 } 309 return responseData; 310 } 311 } 312 #endif 313 boost::asio::io_context& get_io_context() 314 { 315 return io_; 316 } 317 318 private: 319 boost::asio::io_context& io_; 320 boost::asio::posix::stream_descriptor socket; 321 322 void read_wait() 323 { 324 socket.async_read_some( 325 boost::asio::null_buffers(), 326 [&](const boost::system::error_code& ec, std::size_t) { 327 if (ec) 328 { 329 return; 330 } 331 if (process_discard()) 332 { 333 read_immediate(); 334 } 335 else 336 { 337 read_wait(); 338 } 339 }); 340 } 341 void read_immediate() 342 { 343 boost::asio::post(io_, [&] { 344 if (process_discard()) 345 { 346 read_immediate(); 347 } 348 else 349 { 350 read_wait(); 351 } 352 }); 353 } 354 }; 355 356 } // namespace asio 357 358 } // namespace sdbusplus 359