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