1 #include <boost/asio/io_context.hpp> 2 #include <boost/asio/spawn.hpp> 3 #include <sdbusplus/asio/connection.hpp> 4 #include <sdbusplus/asio/object_server.hpp> 5 #include <sdbusplus/asio/sd_event.hpp> 6 #include <sdbusplus/bus.hpp> 7 #include <sdbusplus/exception.hpp> 8 #include <sdbusplus/server.hpp> 9 #include <sdbusplus/timer.hpp> 10 11 #include <chrono> 12 #include <ctime> 13 #include <iostream> 14 #include <variant> 15 16 using variant = std::variant<int, std::string>; 17 18 int foo(int test) 19 { 20 std::cout << "foo(" << test << ") -> " << (test + 1) << "\n"; 21 return ++test; 22 } 23 24 // called from coroutine context, can make yielding dbus calls 25 int fooYield(boost::asio::yield_context yield, 26 std::shared_ptr<sdbusplus::asio::connection> conn, int test) 27 { 28 // fetch the real value from testFunction 29 boost::system::error_code ec; 30 std::cout << "fooYield(yield, " << test << ")...\n"; 31 int testCount = conn->yield_method_call<int>( 32 yield, ec, "xyz.openbmc_project.asio-test", "/xyz/openbmc_project/test", 33 "xyz.openbmc_project.test", "TestFunction", test); 34 if (ec || testCount != (test + 1)) 35 { 36 std::cout << "call to foo failed: ec = " << ec << '\n'; 37 return -1; 38 } 39 std::cout << "yielding call to foo OK! (-> " << testCount << ")\n"; 40 return testCount; 41 } 42 43 int methodWithMessage(sdbusplus::message_t& /*m*/, int test) 44 { 45 std::cout << "methodWithMessage(m, " << test << ") -> " << (test + 1) 46 << "\n"; 47 return ++test; 48 } 49 50 int voidBar(void) 51 { 52 std::cout << "voidBar() -> 42\n"; 53 return 42; 54 } 55 56 void do_start_async_method_call_one( 57 std::shared_ptr<sdbusplus::asio::connection> conn, 58 boost::asio::yield_context yield) 59 { 60 boost::system::error_code ec; 61 variant testValue; 62 conn->yield_method_call<>(yield, ec, "xyz.openbmc_project.asio-test", 63 "/xyz/openbmc_project/test", 64 "org.freedesktop.DBus.Properties", "Set", 65 "xyz.openbmc_project.test", "int", variant(24)); 66 testValue = conn->yield_method_call<variant>( 67 yield, ec, "xyz.openbmc_project.asio-test", "/xyz/openbmc_project/test", 68 "org.freedesktop.DBus.Properties", "Get", "xyz.openbmc_project.test", 69 "int"); 70 if (!ec && std::get<int>(testValue) == 24) 71 { 72 std::cout << "async call to Properties.Get serialized via yield OK!\n"; 73 } 74 else 75 { 76 std::cout << "ec = " << ec << ": " << std::get<int>(testValue) << "\n"; 77 } 78 conn->yield_method_call<void>( 79 yield, ec, "xyz.openbmc_project.asio-test", "/xyz/openbmc_project/test", 80 "org.freedesktop.DBus.Properties", "Set", "xyz.openbmc_project.test", 81 "int", variant(42)); 82 testValue = conn->yield_method_call<variant>( 83 yield, ec, "xyz.openbmc_project.asio-test", "/xyz/openbmc_project/test", 84 "org.freedesktop.DBus.Properties", "Get", "xyz.openbmc_project.test", 85 "int"); 86 if (!ec && std::get<int>(testValue) == 42) 87 { 88 std::cout << "async call to Properties.Get serialized via yield OK!\n"; 89 } 90 else 91 { 92 std::cout << "ec = " << ec << ": " << std::get<int>(testValue) << "\n"; 93 } 94 } 95 96 void do_start_async_ipmi_call(std::shared_ptr<sdbusplus::asio::connection> conn, 97 boost::asio::yield_context yield) 98 { 99 auto method = conn->new_method_call("xyz.openbmc_project.asio-test", 100 "/xyz/openbmc_project/test", 101 "xyz.openbmc_project.test", "execute"); 102 constexpr uint8_t netFn = 6; 103 constexpr uint8_t lun = 0; 104 constexpr uint8_t cmd = 1; 105 std::map<std::string, variant> options = {{"username", variant("admin")}, 106 {"privilege", variant(4)}}; 107 std::vector<uint8_t> commandData = {4, 3, 2, 1}; 108 method.append(netFn, lun, cmd, commandData, options); 109 boost::system::error_code ec; 110 sdbusplus::message_t reply = conn->async_send(method, yield[ec]); 111 std::tuple<uint8_t, uint8_t, uint8_t, uint8_t, std::vector<uint8_t>> 112 tupleOut; 113 try 114 { 115 reply.read(tupleOut); 116 } 117 catch (const sdbusplus::exception::SdBusError& e) 118 { 119 std::cerr << "failed to unpack; sig is " << reply.get_signature() 120 << "\n"; 121 } 122 auto& [rnetFn, rlun, rcmd, cc, responseData] = tupleOut; 123 std::vector<uint8_t> expRsp = {1, 2, 3, 4}; 124 if (rnetFn == uint8_t(netFn + 1) && rlun == lun && rcmd == cmd && cc == 0 && 125 responseData == expRsp) 126 { 127 std::cerr << "ipmi call returns OK!\n"; 128 } 129 else 130 { 131 std::cerr << "ipmi call returns unexpected response\n"; 132 } 133 } 134 135 auto ipmiInterface(boost::asio::yield_context /*yield*/, uint8_t netFn, 136 uint8_t lun, uint8_t cmd, std::vector<uint8_t>& /*data*/, 137 const std::map<std::string, variant>& /*options*/) 138 { 139 std::vector<uint8_t> reply = {1, 2, 3, 4}; 140 uint8_t cc = 0; 141 std::cerr << "ipmiInterface:execute(" << int(netFn) << int(cmd) << ")\n"; 142 return std::make_tuple(uint8_t(netFn + 1), lun, cmd, cc, reply); 143 } 144 145 void do_start_async_to_yield(std::shared_ptr<sdbusplus::asio::connection> conn, 146 boost::asio::yield_context yield) 147 { 148 boost::system::error_code ec; 149 int testValue = 0; 150 testValue = conn->yield_method_call<int>( 151 yield, ec, "xyz.openbmc_project.asio-test", "/xyz/openbmc_project/test", 152 "xyz.openbmc_project.test", "TestYieldFunction", int(41)); 153 154 if (!ec && testValue == 42) 155 { 156 std::cout 157 << "yielding call to TestYieldFunction serialized via yield OK!\n"; 158 } 159 else 160 { 161 std::cout << "ec = " << ec << ": " << testValue << "\n"; 162 } 163 164 ec.clear(); 165 auto badValue = conn->yield_method_call<std::string>( 166 yield, ec, "xyz.openbmc_project.asio-test", "/xyz/openbmc_project/test", 167 "xyz.openbmc_project.test", "TestYieldFunction", int(41)); 168 169 if (!ec) 170 { 171 std::cout 172 << "yielding call to TestYieldFunction returned the wrong type\n"; 173 } 174 else 175 { 176 std::cout << "TestYieldFunction expected error: " << ec << "\n"; 177 } 178 179 ec.clear(); 180 auto unUsedValue = conn->yield_method_call<std::string>( 181 yield, ec, "xyz.openbmc_project.asio-test", "/xyz/openbmc_project/test", 182 "xyz.openbmc_project.test", "TestYieldFunctionNotExits", int(41)); 183 184 if (!ec) 185 { 186 std::cout << "TestYieldFunctionNotExists returned unexpectedly\n"; 187 } 188 else 189 { 190 std::cout << "TestYieldFunctionNotExits expected error: " << ec << "\n"; 191 } 192 } 193 194 int server() 195 { 196 // setup connection to dbus 197 boost::asio::io_context io; 198 auto conn = std::make_shared<sdbusplus::asio::connection>(io); 199 200 // test object server 201 conn->request_name("xyz.openbmc_project.asio-test"); 202 auto server = sdbusplus::asio::object_server(conn); 203 std::shared_ptr<sdbusplus::asio::dbus_interface> iface = 204 server.add_interface("/xyz/openbmc_project/test", 205 "xyz.openbmc_project.test"); 206 // test generic properties 207 iface->register_property("int", 33, 208 sdbusplus::asio::PropertyPermission::readWrite); 209 std::vector<std::string> myStringVec = {"some", "test", "data"}; 210 std::vector<std::string> myStringVec2 = {"more", "test", "data"}; 211 212 iface->register_property("myStringVec", myStringVec, 213 sdbusplus::asio::PropertyPermission::readWrite); 214 iface->register_property("myStringVec2", myStringVec2); 215 216 // test properties with specialized callbacks 217 iface->register_property("lessThan50", 23, 218 // custom set 219 [](const int& req, int& propertyValue) { 220 if (req >= 50) 221 { 222 return false; 223 } 224 propertyValue = req; 225 return true; 226 }); 227 iface->register_property( 228 "TrailTime", std::string("foo"), 229 // custom set 230 [](const std::string& req, std::string& propertyValue) { 231 propertyValue = req; 232 return true; 233 }, 234 // custom get 235 [](const std::string& property) { 236 auto now = std::chrono::system_clock::now(); 237 auto timePoint = std::chrono::system_clock::to_time_t(now); 238 return property + std::ctime(&timePoint); 239 }); 240 241 // test method creation 242 iface->register_method("TestMethod", [](const int32_t& callCount) { 243 return std::make_tuple(callCount, 244 "success: " + std::to_string(callCount)); 245 }); 246 247 iface->register_method("TestFunction", foo); 248 249 // fooYield has boost::asio::yield_context as first argument 250 // so will be executed in coroutine context if called 251 iface->register_method("TestYieldFunction", 252 [conn](boost::asio::yield_context yield, int val) { 253 return fooYield(yield, conn, val); 254 }); 255 256 iface->register_method("TestMethodWithMessage", methodWithMessage); 257 258 iface->register_method("VoidFunctionReturnsInt", voidBar); 259 260 iface->register_method("execute", ipmiInterface); 261 262 iface->initialize(); 263 264 io.run(); 265 266 return 0; 267 } 268 269 int client() 270 { 271 using GetSubTreeType = std::vector<std::pair< 272 std::string, 273 std::vector<std::pair<std::string, std::vector<std::string>>>>>; 274 using message = sdbusplus::message_t; 275 276 // setup connection to dbus 277 boost::asio::io_context io; 278 auto conn = std::make_shared<sdbusplus::asio::connection>(io); 279 280 int ready = 0; 281 while (!ready) 282 { 283 auto readyMsg = conn->new_method_call( 284 "xyz.openbmc_project.asio-test", "/xyz/openbmc_project/test", 285 "xyz.openbmc_project.test", "VoidFunctionReturnsInt"); 286 try 287 { 288 message intMsg = conn->call(readyMsg); 289 intMsg.read(ready); 290 } 291 catch (const sdbusplus::exception::SdBusError& e) 292 { 293 ready = 0; 294 // pause to give the server a chance to start up 295 usleep(10000); 296 } 297 } 298 299 // test async method call and async send 300 auto mesg = conn->new_method_call("xyz.openbmc_project.ObjectMapper", 301 "/xyz/openbmc_project/object_mapper", 302 "xyz.openbmc_project.ObjectMapper", 303 "GetSubTree"); 304 305 int32_t depth = 2; 306 constexpr std::array<std::string_view, 1> interfaces{ 307 "xyz.openbmc_project.Sensor.Value"}; 308 mesg.append("/xyz/openbmc_project/Sensors", depth, interfaces); 309 310 conn->async_send(mesg, [](boost::system::error_code ec, message& ret) { 311 std::cout << "async_send callback\n"; 312 if (ec || ret.is_method_error()) 313 { 314 std::cerr << "error with async_send\n"; 315 return; 316 } 317 GetSubTreeType data; 318 ret.read(data); 319 for (auto& item : data) 320 { 321 std::cout << item.first << "\n"; 322 } 323 }); 324 325 conn->async_method_call( 326 [](boost::system::error_code ec, GetSubTreeType& subtree) { 327 std::cout << "async_method_call callback\n"; 328 if (ec) 329 { 330 std::cerr << "error with async_method_call\n"; 331 return; 332 } 333 for (auto& item : subtree) 334 { 335 std::cout << item.first << "\n"; 336 } 337 }, 338 "xyz.openbmc_project.ObjectMapper", 339 "/xyz/openbmc_project/object_mapper", 340 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 341 "/org/openbmc/control", 2, std::vector<std::string>()); 342 343 std::string nonConstCapture = "lalalala"; 344 conn->async_method_call( 345 [nonConstCapture = std::move(nonConstCapture)]( 346 boost::system::error_code ec, 347 const std::vector<std::string>& /*things*/) mutable { 348 std::cout << "async_method_call callback\n"; 349 nonConstCapture += " stuff"; 350 if (ec) 351 { 352 std::cerr << "async_method_call expected failure: " << ec 353 << "\n"; 354 } 355 else 356 { 357 std::cerr << "async_method_call should have failed!\n"; 358 } 359 }, 360 "xyz.openbmc_project.ObjectMapper", 361 "/xyz/openbmc_project/object_mapper", 362 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 363 "/xyz/openbmc_project/sensors", depth, interfaces); 364 365 // sd_events work too using the default event loop 366 phosphor::Timer t1([]() { std::cerr << "*** tock ***\n"; }); 367 t1.start(std::chrono::microseconds(1000000)); 368 phosphor::Timer t2([]() { std::cerr << "*** tick ***\n"; }); 369 t2.start(std::chrono::microseconds(500000), true); 370 // add the sd_event wrapper to the io object 371 sdbusplus::asio::sd_event_wrapper sdEvents(io); 372 373 // set up a client to make an async call to the server 374 // using coroutines (userspace cooperative multitasking) 375 boost::asio::spawn(io, [conn](boost::asio::yield_context yield) { 376 do_start_async_method_call_one(conn, yield); 377 }); 378 boost::asio::spawn(io, [conn](boost::asio::yield_context yield) { 379 do_start_async_ipmi_call(conn, yield); 380 }); 381 boost::asio::spawn(io, [conn](boost::asio::yield_context yield) { 382 do_start_async_to_yield(conn, yield); 383 }); 384 385 conn->async_method_call( 386 [](boost::system::error_code ec, int32_t testValue) { 387 if (ec) 388 { 389 std::cerr << "TestYieldFunction returned error with " 390 "async_method_call (ec = " 391 << ec << ")\n"; 392 return; 393 } 394 std::cout << "TestYieldFunction return " << testValue << "\n"; 395 }, 396 "xyz.openbmc_project.asio-test", "/xyz/openbmc_project/test", 397 "xyz.openbmc_project.test", "TestYieldFunction", int32_t(41)); 398 io.run(); 399 400 return 0; 401 } 402 403 int main(int argc, const char* argv[]) 404 { 405 if (argc == 1) 406 { 407 int pid = fork(); 408 if (pid == 0) 409 { 410 return client(); 411 } 412 else if (pid > 0) 413 { 414 return server(); 415 } 416 return pid; 417 } 418 if (std::string(argv[1]) == "--server") 419 { 420 return server(); 421 } 422 if (std::string(argv[1]) == "--client") 423 { 424 return client(); 425 } 426 std::cout << "usage: " << argv[0] << " [--server | --client]\n"; 427 return -1; 428 } 429