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