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/spawn.hpp> 29 #include <boost/callable_traits.hpp> 30 #include <sdbusplus/asio/detail/async_send_handler.hpp> 31 #include <sdbusplus/message.hpp> 32 #include <sdbusplus/utility/read_into_tuple.hpp> 33 #include <sdbusplus/utility/type_traits.hpp> 34 35 #include <chrono> 36 #include <string> 37 #include <tuple> 38 39 namespace sdbusplus 40 { 41 42 namespace asio 43 { 44 45 /// Root D-Bus IO object 46 /** 47 * A connection to a bus, through which messages may be sent or received. 48 */ 49 class connection : public sdbusplus::bus_t 50 { 51 public: 52 // default to system bus 53 connection(boost::asio::io_context& io) : 54 sdbusplus::bus_t(sdbusplus::bus::new_default()), io_(io), socket(io_) 55 { 56 socket.assign(get_fd()); 57 read_wait(); 58 } 59 connection(boost::asio::io_context& io, sd_bus* bus) : 60 sdbusplus::bus_t(bus), io_(io), socket(io_) 61 { 62 socket.assign(get_fd()); 63 read_wait(); 64 } 65 ~connection() 66 { 67 // The FD will be closed by the socket object, so assign null to the 68 // sd_bus object to avoid a double close() Ignore return codes here, 69 // because there's nothing we can do about errors 70 socket.release(); 71 } 72 73 /** @brief Perform an asynchronous send of a message, executing the handler 74 * upon return and return 75 * 76 * @param[in] m - A message ready to send 77 * @param[in] token- The completion token to execute upon completion; 78 * 79 */ 80 template <typename CompletionToken> 81 inline auto async_send(message_t& m, CompletionToken&& token, 82 uint64_t timeout = 0) 83 { 84 constexpr bool is_yield = 85 std::is_same_v<CompletionToken, boost::asio::yield_context>; 86 using return_t = std::conditional_t<is_yield, message_t, message_t&>; 87 using callback_t = void(boost::system::error_code, return_t); 88 return boost::asio::async_initiate<CompletionToken, callback_t>( 89 detail::async_send_handler(get(), m, timeout), token); 90 } 91 92 /** @brief Perform an asynchronous method call, with input parameter packing 93 * and return value unpacking. 94 * 95 * @param[in] handler - A function object that is to be called as a 96 * continuation for the async dbus method call. The 97 * arguments to parse on the return are deduced from 98 * the handler's signature and then passed in along 99 * with an error code and optional message_t 100 * @param[in] service - The service to call. 101 * @param[in] objpath - The object's path for the call. 102 * @param[in] interf - The object's interface to call. 103 * @param[in] method - The object's method to call. 104 * @param[in] timeout - The timeout for the method call in usec (0 results 105 * in using the default value). 106 * @param[in] a... - Optional parameters for the method call. 107 * 108 * @return immediate return of the internal handler registration. The 109 * result of the actual asynchronous call will get unpacked from 110 * the message and passed into the handler when the call is 111 * complete. 112 */ 113 template <typename MessageHandler, typename... InputArgs> 114 void async_method_call_timed(MessageHandler&& handler, 115 const std::string& service, 116 const std::string& objpath, 117 const std::string& interf, 118 const std::string& method, uint64_t timeout, 119 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 = [handler = std::forward<MessageHandler>(handler)]( 135 boost::system::error_code ec, 136 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& e) 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 * @return immediate return of the internal handler registration. The 201 * result of the actual asynchronous call will get unpacked from 202 * the message and passed into the handler when the call is 203 * complete. 204 */ 205 template <typename MessageHandler, typename... InputArgs> 206 void async_method_call(MessageHandler&& handler, const std::string& service, 207 const std::string& objpath, 208 const std::string& interf, const std::string& method, 209 const InputArgs&... a) 210 { 211 async_method_call_timed(std::forward<MessageHandler>(handler), service, 212 objpath, interf, method, 0, a...); 213 } 214 215 /** @brief Perform a yielding asynchronous method call, with input 216 * parameter packing and return value unpacking 217 * 218 * @param[in] yield - A yield context to async block upon. 219 * @param[in] ec - an error code that will be set for any errors 220 * @param[in] service - The service to call. 221 * @param[in] objpath - The object's path for the call. 222 * @param[in] interf - The object's interface to call. 223 * @param[in] method - The object's method to call. 224 * @param[in] a... - Optional parameters for the method call. 225 * 226 * @return Unpacked value of RetType 227 */ 228 template <typename... RetTypes, typename... InputArgs> 229 auto yield_method_call(boost::asio::yield_context yield, 230 boost::system::error_code& ec, 231 const std::string& service, 232 const std::string& objpath, 233 const std::string& interf, const std::string& method, 234 const InputArgs&... a) 235 { 236 message_t m; 237 try 238 { 239 m = new_method_call(service.c_str(), objpath.c_str(), 240 interf.c_str(), method.c_str()); 241 m.append(a...); 242 } 243 catch (const exception::SdBusError& e) 244 { 245 ec = boost::system::errc::make_error_code( 246 static_cast<boost::system::errc::errc_t>(e.get_errno())); 247 } 248 message_t r; 249 if (!ec) 250 { 251 r = async_send(m, yield[ec]); 252 } 253 if constexpr (sizeof...(RetTypes) == 0) 254 { 255 // void return 256 return; 257 } 258 else if constexpr (sizeof...(RetTypes) == 1) 259 { 260 if constexpr (std::is_same_v<utility::first_type_t<RetTypes...>, 261 void>) 262 { 263 return; 264 } 265 else 266 { 267 // single item return 268 utility::first_type_t<RetTypes...> responseData{}; 269 // before attempting to read, check ec and bail on error 270 if (ec) 271 { 272 return responseData; 273 } 274 try 275 { 276 r.read(responseData); 277 } 278 catch (const std::exception& e) 279 { 280 ec = boost::system::errc::make_error_code( 281 boost::system::errc::invalid_argument); 282 // responseData will be default-constructed... 283 } 284 return responseData; 285 } 286 } 287 else 288 { 289 // tuple of things to return 290 std::tuple<RetTypes...> responseData{}; 291 // before attempting to read, check ec and bail on error 292 if (ec) 293 { 294 return responseData; 295 } 296 try 297 { 298 r.read(responseData); 299 } 300 catch (const std::exception& e) 301 { 302 ec = boost::system::errc::make_error_code( 303 boost::system::errc::invalid_argument); 304 // responseData will be default-constructed... 305 } 306 return responseData; 307 } 308 } 309 310 boost::asio::io_context& get_io_context() 311 { 312 return io_; 313 } 314 315 private: 316 boost::asio::io_context& io_; 317 boost::asio::posix::stream_descriptor socket; 318 319 void read_wait() 320 { 321 socket.async_read_some( 322 boost::asio::null_buffers(), 323 [&](const boost::system::error_code& ec, std::size_t) { 324 if (ec) 325 { 326 return; 327 } 328 if (process_discard()) 329 { 330 read_immediate(); 331 } 332 else 333 { 334 read_wait(); 335 } 336 }); 337 } 338 void read_immediate() 339 { 340 boost::asio::post(io_, [&] { 341 if (process_discard()) 342 { 343 read_immediate(); 344 } 345 else 346 { 347 read_wait(); 348 } 349 }); 350 } 351 }; 352 353 } // namespace asio 354 355 } // namespace sdbusplus 356