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