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