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