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