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