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