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 try 150 { 151 testValue = conn->yield_method_call<int>( 152 yield[ec], "xyz.openbmc_project.asio-test", 153 "/xyz/openbmc_project/test", "xyz.openbmc_project.test", 154 "TestYieldFunction", int(41)); 155 } 156 catch (sdbusplus::exception::SdBusError& e) 157 { 158 std::cout << "oops: " << e.what() << "\n"; 159 } 160 if (!ec && testValue == 42) 161 { 162 std::cout 163 << "yielding call to TestYieldFunction serialized via yield OK!\n"; 164 } 165 else 166 { 167 std::cout << "ec = " << ec << ": " << testValue << "\n"; 168 } 169 } 170 171 int server() 172 { 173 // setup connection to dbus 174 boost::asio::io_context io; 175 auto conn = std::make_shared<sdbusplus::asio::connection>(io); 176 177 // test object server 178 conn->request_name("xyz.openbmc_project.asio-test"); 179 auto server = sdbusplus::asio::object_server(conn); 180 std::shared_ptr<sdbusplus::asio::dbus_interface> iface = 181 server.add_interface("/xyz/openbmc_project/test", 182 "xyz.openbmc_project.test"); 183 // test generic properties 184 iface->register_property("int", 33, 185 sdbusplus::asio::PropertyPermission::readWrite); 186 std::vector<std::string> myStringVec = {"some", "test", "data"}; 187 std::vector<std::string> myStringVec2 = {"more", "test", "data"}; 188 189 iface->register_property("myStringVec", myStringVec, 190 sdbusplus::asio::PropertyPermission::readWrite); 191 iface->register_property("myStringVec2", myStringVec2); 192 193 // test properties with specialized callbacks 194 iface->register_property("lessThan50", 23, 195 // custom set 196 [](const int& req, int& propertyValue) { 197 if (req >= 50) 198 { 199 return -EINVAL; 200 } 201 propertyValue = req; 202 return 1; // success 203 }); 204 iface->register_property( 205 "TrailTime", std::string("foo"), 206 // custom set 207 [](const std::string& req, std::string& propertyValue) { 208 propertyValue = req; 209 return 1; // success 210 }, 211 // custom get 212 [](const std::string& property) { 213 auto now = std::chrono::system_clock::now(); 214 auto timePoint = std::chrono::system_clock::to_time_t(now); 215 return property + std::ctime(&timePoint); 216 }); 217 218 // test method creation 219 iface->register_method("TestMethod", [](const int32_t& callCount) { 220 return std::make_tuple(callCount, 221 "success: " + std::to_string(callCount)); 222 }); 223 224 iface->register_method("TestFunction", foo); 225 226 // fooYield has boost::asio::yield_context as first argument 227 // so will be executed in coroutine context if called 228 iface->register_method("TestYieldFunction", 229 [conn](boost::asio::yield_context yield, int val) { 230 return fooYield(yield, conn, val); 231 }); 232 233 iface->register_method("TestMethodWithMessage", methodWithMessage); 234 235 iface->register_method("VoidFunctionReturnsInt", voidBar); 236 237 iface->register_method("execute", ipmiInterface); 238 239 iface->initialize(); 240 241 io.run(); 242 243 return 0; 244 } 245 246 int client() 247 { 248 using GetSubTreeType = std::vector<std::pair< 249 std::string, 250 std::vector<std::pair<std::string, std::vector<std::string>>>>>; 251 using message = sdbusplus::message::message; 252 253 // setup connection to dbus 254 boost::asio::io_context io; 255 auto conn = std::make_shared<sdbusplus::asio::connection>(io); 256 257 int ready = 0; 258 while (!ready) 259 { 260 auto readyMsg = conn->new_method_call( 261 "xyz.openbmc_project.asio-test", "/xyz/openbmc_project/test", 262 "xyz.openbmc_project.test", "VoidFunctionReturnsInt"); 263 try 264 { 265 message intMsg = conn->call(readyMsg); 266 intMsg.read(ready); 267 } 268 catch (sdbusplus::exception::SdBusError& e) 269 { 270 ready = 0; 271 // pause to give the server a chance to start up 272 usleep(10000); 273 } 274 } 275 276 // test async method call and async send 277 auto mesg = 278 conn->new_method_call("xyz.openbmc_project.ObjectMapper", 279 "/xyz/openbmc_project/object_mapper", 280 "xyz.openbmc_project.ObjectMapper", "GetSubTree"); 281 282 static const auto depth = 2; 283 static const std::vector<std::string> interfaces = { 284 "xyz.openbmc_project.Sensor.Value"}; 285 mesg.append("/xyz/openbmc_project/Sensors", depth, interfaces); 286 287 conn->async_send(mesg, [](boost::system::error_code ec, message& ret) { 288 std::cout << "async_send callback\n"; 289 if (ec || ret.is_method_error()) 290 { 291 std::cerr << "error with async_send\n"; 292 return; 293 } 294 GetSubTreeType data; 295 ret.read(data); 296 for (auto& item : data) 297 { 298 std::cout << item.first << "\n"; 299 } 300 }); 301 302 conn->async_method_call( 303 [](boost::system::error_code ec, GetSubTreeType& subtree) { 304 std::cout << "async_method_call callback\n"; 305 if (ec) 306 { 307 std::cerr << "error with async_method_call\n"; 308 return; 309 } 310 for (auto& item : subtree) 311 { 312 std::cout << item.first << "\n"; 313 } 314 }, 315 "xyz.openbmc_project.ObjectMapper", 316 "/xyz/openbmc_project/object_mapper", 317 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 318 "/org/openbmc/control", 2, std::vector<std::string>()); 319 320 // sd_events work too using the default event loop 321 phosphor::Timer t1([]() { std::cerr << "*** tock ***\n"; }); 322 t1.start(std::chrono::microseconds(1000000)); 323 phosphor::Timer t2([]() { std::cerr << "*** tick ***\n"; }); 324 t2.start(std::chrono::microseconds(500000), true); 325 // add the sd_event wrapper to the io object 326 sdbusplus::asio::sd_event_wrapper sdEvents(io); 327 328 // set up a client to make an async call to the server 329 // using coroutines (userspace cooperative multitasking) 330 boost::asio::spawn(io, [conn](boost::asio::yield_context yield) { 331 do_start_async_method_call_one(conn, yield); 332 }); 333 boost::asio::spawn(io, [conn](boost::asio::yield_context yield) { 334 do_start_async_ipmi_call(conn, yield); 335 }); 336 boost::asio::spawn(io, [conn](boost::asio::yield_context yield) { 337 do_start_async_to_yield(conn, yield); 338 }); 339 340 conn->async_method_call( 341 [](boost::system::error_code ec, int32_t testValue) { 342 if (ec) 343 { 344 std::cerr << "TestYieldFunction returned error with " 345 "async_method_call (ec = " 346 << ec << ")\n"; 347 return; 348 } 349 std::cout << "TestYieldFunction return " << testValue << "\n"; 350 }, 351 "xyz.openbmc_project.asio-test", "/xyz/openbmc_project/test", 352 "xyz.openbmc_project.test", "TestYieldFunction", int32_t(41)); 353 io.run(); 354 355 return 0; 356 } 357 358 int main(int argc, const char* argv[]) 359 { 360 if (argc == 1) 361 { 362 int pid = fork(); 363 if (pid == 0) 364 { 365 return client(); 366 } 367 else if (pid > 0) 368 { 369 return server(); 370 } 371 return pid; 372 } 373 if (std::string(argv[1]) == "--server") 374 { 375 return server(); 376 } 377 if (std::string(argv[1]) == "--client") 378 { 379 return client(); 380 } 381 std::cout << "usage: " << argv[0] << " [--server | --client]\n"; 382 return -1; 383 } 384