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