1 #include <unistd.h>
2 #include <dbus/connection.hpp>
3 #include <dbus/endpoint.hpp>
4 #include <dbus/filter.hpp>
5 #include <dbus/match.hpp>
6 #include <dbus/message.hpp>
7 #include <dbus/properties.hpp>
8 #include <functional>
9 #include <vector>
10 #include <gmock/gmock.h>
11 #include <gtest/gtest.h>
12 
13 static const std::string dbus_boilerplate(
14     "<!DOCTYPE node PUBLIC "
15     "\"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\" "
16     "\"http://www.freedesktop.org/standards/dbus/1.0/"
17     "introspect.dtd\">\n");
18 
19 TEST(DbusPropertiesInterface, EmptyObjectServer) {
20   boost::asio::io_service io;
21   auto system_bus = std::make_shared<dbus::connection>(io, dbus::bus::system);
22 
23   // Set up the object server, and send some test events
24   dbus::DbusObjectServer foo(system_bus);
25 
26   EXPECT_EQ(foo.get_xml_for_path("/"), dbus_boilerplate + "<node></node>");
27   EXPECT_EQ(foo.get_xml_for_path(""), dbus_boilerplate + "<node></node>");
28 }
29 
30 TEST(DbusPropertiesInterface, BasicObjectServer) {
31   boost::asio::io_service io;
32   auto system_bus = std::make_shared<dbus::connection>(io, dbus::bus::system);
33 
34   // Set up the object server, and send some test events
35   dbus::DbusObjectServer foo(system_bus);
36 
37   foo.register_object(std::make_shared<dbus::DbusObject>(
38       system_bus, "/org/freedesktop/NetworkManager"));
39 
40   EXPECT_EQ(foo.get_xml_for_path("/"), dbus_boilerplate +
41                                            "<node><node "
42                                            "name=\"org\"></node></node>");
43   EXPECT_EQ(foo.get_xml_for_path(""), dbus_boilerplate +
44                                           "<node><node "
45                                           "name=\"org\"></node></node>");
46 
47   EXPECT_EQ(foo.get_xml_for_path("/org"),
48             dbus_boilerplate +
49                 "<node><node "
50                 "name=\"freedesktop\"></node></node>");
51   EXPECT_EQ(foo.get_xml_for_path("/org/freedesktop"),
52             dbus_boilerplate +
53                 "<node><node "
54                 "name=\"NetworkManager\"></node></node>");
55   // TODO(Ed) turn this test back on once the signal interface stabilizes
56   /*EXPECT_EQ(foo.get_xml_for_path("/org/freedesktop/NetworkManager"),
57             dbus_boilerplate + "<node></node>");*/
58 }
59 
60 TEST(DbusPropertiesInterface, SharedNodeObjectServer) {
61   boost::asio::io_service io;
62   auto system_bus = std::make_shared<dbus::connection>(io, dbus::bus::system);
63 
64   // Set up the object server, and send some test events
65   dbus::DbusObjectServer foo(system_bus);
66 
67   foo.register_object(
68       std::make_shared<dbus::DbusObject>(system_bus, "/org/freedesktop/test1"));
69   foo.register_object(
70       std::make_shared<dbus::DbusObject>(system_bus, "/org/freedesktop/test2"));
71 
72   EXPECT_EQ(foo.get_xml_for_path("/"), dbus_boilerplate +
73                                            "<node><node "
74                                            "name=\"org\"></node></node>");
75   EXPECT_EQ(foo.get_xml_for_path(""), dbus_boilerplate +
76                                           "<node><node "
77                                           "name=\"org\"></node></node>");
78 
79   EXPECT_EQ(foo.get_xml_for_path("/org"),
80             dbus_boilerplate +
81                 "<node><node "
82                 "name=\"freedesktop\"></node></node>");
83   EXPECT_EQ(foo.get_xml_for_path("/org/freedesktop"),
84             dbus_boilerplate +
85                 "<node><node "
86                 "name=\"test1\"></node><node name=\"test2\"></node></node>");
87   // TODO(Ed) turn this test back on once the signal interface stabilizes
88   /*
89   EXPECT_EQ(foo.get_xml_for_path("/org/freedesktop/test1"),
90             dbus_boilerplate + "<node></node>");
91   EXPECT_EQ(foo.get_xml_for_path("/org/freedesktop/test2"),
92             dbus_boilerplate + "<node></node>");
93             */
94 }
95 
96 TEST(LambdaDbusMethodTest, Basic) {
97   bool lambda_called = false;
98   auto lambda = [&](int32_t x) {
99     EXPECT_EQ(x, 18);
100     lambda_called = true;
101     return std::make_tuple<int64_t, int32_t>(4L, 2);
102   };
103   boost::asio::io_service io;
104   auto bus = std::make_shared<dbus::connection>(io, dbus::bus::session);
105   auto dbus_method =
106       dbus::LambdaDbusMethod<decltype(lambda)>("foo", bus, lambda);
107 
108   dbus::message m =
109       dbus::message::new_call(dbus::endpoint("org.freedesktop.Avahi", "/",
110                                              "org.freedesktop.Avahi.Server"),
111                               "GetHostName");
112   m.pack(static_cast<int32_t>(18));
113   // Small thing that the dbus library normally does for us, but because we're
114   // bypassing it, we need to fill it in as if it was done
115   m.set_serial(585);
116   dbus_method.call(m);
117   EXPECT_EQ(lambda_called, true);
118 }
119 
120 TEST(DbusPropertiesInterface, ObjectServer) {
121   boost::asio::io_service io;
122   auto bus = std::make_shared<dbus::connection>(io, dbus::bus::session);
123 
124   // Set up the object server, and send some test objects
125   dbus::DbusObjectServer foo(bus);
126 
127   foo.register_object(
128       std::make_shared<dbus::DbusObject>(bus, "/org/freedesktop/test1"));
129   foo.register_object(
130       std::make_shared<dbus::DbusObject>(bus, "/org/freedesktop/test2"));
131   int completion_count(0);
132 
133   std::array<std::string, 4> paths_to_test(
134       {{"/", "/org", "/org/freedesktop", "/org/freedesktop/test1"}});
135 
136   for (auto& path : paths_to_test) {
137     dbus::endpoint test_daemon(bus->get_unique_name(), path,
138                                "org.freedesktop.DBus.Introspectable");
139     dbus::message m = dbus::message::new_call(test_daemon, "Introspect");
140     completion_count++;
141     bus->async_send(
142         m, [&](const boost::system::error_code ec, dbus::message r) {
143           if (ec) {
144             std::string error;
145             r.unpack(error);
146             FAIL() << ec << error;
147           } else {
148             std::string xml;
149             r.unpack(xml);
150             EXPECT_EQ(r.get_type(), "method_return");
151             if (path == "/") {
152               EXPECT_EQ(xml, dbus_boilerplate +
153                                  "<node><node "
154                                  "name=\"org\"></node></node>");
155             } else if (path == "/org") {
156               EXPECT_EQ(xml, dbus_boilerplate +
157                                  "<node><node "
158                                  "name=\"freedesktop\"></node></node>");
159             } else if (path == "/org/freedesktop") {
160               EXPECT_EQ(xml, dbus_boilerplate +
161                                  "<node><node "
162                                  "name=\"test1\"></node><node "
163                                  "name=\"test2\"></node></node>");
164             } else if (path == "/org/freedesktop/test1") {
165             } else {
166               FAIL() << "Unknown path: " << path;
167             }
168           }
169           completion_count--;
170           if (completion_count == 0) {
171             io.stop();
172           }
173         });
174   }
175   io.run();
176 }
177 
178 TEST(DbusPropertiesInterface, EmptyMethodServer) {
179   boost::asio::io_service io;
180   auto bus = std::make_shared<dbus::connection>(io, dbus::bus::session);
181 
182   // Set up the object server, and send some test objects
183   dbus::DbusObjectServer foo(bus);
184   foo.register_object(
185       std::make_shared<dbus::DbusObject>(bus, "/org/freedesktop/test1"));
186 
187   dbus::endpoint test_daemon(bus->get_unique_name(), "/org/freedesktop/test1",
188                              "org.freedesktop.DBus.Introspectable");
189   dbus::message m = dbus::message::new_call(test_daemon, "Introspect");
190 
191   bus->async_send(m, [&](const boost::system::error_code ec, dbus::message r) {
192     if (ec) {
193       std::string error;
194       r.unpack(error);
195       FAIL() << ec << error;
196     } else {
197       std::cout << r;
198       std::string xml;
199       r.unpack(xml);
200       EXPECT_EQ(r.get_type(), "method_return");
201       // TODO(ed) turn back on when method interface stabilizes
202       // EXPECT_EQ(xml, dbus_boilerplate + "<node></node>");
203     }
204 
205     io.stop();
206 
207   });
208 
209   io.run();
210 }
211 
212 std::tuple<int> test_method(uint32_t x) {
213   std::cout << "method called.\n";
214   return std::make_tuple<int>(42);
215 }
216 
217 class TestClass {
218  public:
219   static std::tuple<int> test_method(uint32_t x) {
220     std::cout << "method called.\n";
221     return std::make_tuple<int>(42);
222   }
223 };
224 
225 TEST(DbusPropertiesInterface, MethodServer) {
226   boost::asio::io_service io;
227   auto bus = std::make_shared<dbus::connection>(io, dbus::bus::session);
228 
229   // Set up the object server, and send some test objects
230   dbus::DbusObjectServer foo(bus);
231   auto object =
232       std::make_shared<dbus::DbusObject>(bus, "/org/freedesktop/test1");
233   foo.register_object(object);
234 
235   auto iface = std::make_shared<dbus::DbusInterface>(
236       "org.freedesktop.My.Interface", bus);
237   object->register_interface(iface);
238 
239   // Test multiple ways to register methods
240   // Basic lambda
241   iface->register_method("MyMethodLambda", [](uint32_t x) {
242 
243     std::cout << "method called.  Got:" << x << "\n";
244     return std::make_tuple<int>(42);
245   });
246 
247   // std::function
248   std::function<typename std::tuple<int>(uint32_t)> my_function =
249       [](uint32_t x) {
250 
251         std::cout << "method called.  Got:" << x << "\n";
252         return std::make_tuple<int>(42);
253       };
254 
255   iface->register_method("MyMethodStdFunction", my_function);
256 
257   // Function pointer
258   iface->register_method("MyMethodFunctionPointer", &test_method);
259 
260   // Class function pointer
261   TestClass t;
262   iface->register_method("MyClassFunctionPointer", t.test_method);
263 
264   // const class function pointer
265   const TestClass t2;
266   iface->register_method("MyClassFunctionPointer", t2.test_method);
267 
268   iface->register_method("VoidMethod", []() {
269 
270     std::cout << "method called.\n";
271     return (uint32_t)42;
272   });
273 
274   iface->register_method("VoidMethod", []() {
275 
276     std::cout << "method called.\n";
277     return std::make_tuple<int>(42);
278   });
279 
280   // Test multiple ways to register methods
281   // Basic lambda
282   iface->register_method("MyMethodLambda", {"x"}, {"return_value_name"},
283                          [](uint32_t x) {
284                            std::cout << "method called.  Got:" << x << "\n";
285                            return 42;
286                          });
287 
288   dbus::endpoint test_daemon(bus->get_unique_name(), "/org/freedesktop/test1",
289                              "org.freedesktop.DBus.Introspectable");
290   dbus::message m = dbus::message::new_call(test_daemon, "Introspect");
291 
292   bus->async_send(m, [&](const boost::system::error_code ec, dbus::message r) {
293     if (ec) {
294       std::string error;
295       r.unpack(error);
296 
297       FAIL() << ec << error;
298     } else {
299       std::string xml;
300       r.unpack(xml);
301       EXPECT_EQ(r.get_type(), "method_return");
302       // todo(ED)
303       /*
304       EXPECT_EQ(xml, dbus_boilerplate +
305                          "<node><interface "
306                          "name=\"MyInterface\"><method "
307                          "name=\"MyMethod\"></method></interface></node>");
308                          */
309     }
310     io.stop();
311   });
312 
313   io.run();
314 }
315 
316 TEST(DbusPropertiesInterface, PropertiesInterface) {
317   boost::asio::io_service io;
318   auto bus = std::make_shared<dbus::connection>(io, dbus::bus::session);
319 
320   // Set up the object server, and send some test objects
321   dbus::DbusObjectServer foo(bus);
322   auto object =
323       std::make_shared<dbus::DbusObject>(bus, "/org/freedesktop/test1");
324   foo.register_object(object);
325 
326   auto iface = std::make_shared<dbus::DbusInterface>(
327       "org.freedesktop.My.Interface", bus);
328   object->register_interface(iface);
329 
330   iface->set_property("foo", (uint32_t)26);
331 
332   dbus::endpoint get_dbus_properties(bus->get_unique_name(),
333                                      "/org/freedesktop/test1",
334                                      "org.freedesktop.DBus.Properties", "Get");
335   size_t outstanding_async_calls = 0;
336 
337   outstanding_async_calls++;
338   bus->async_method_call(
339       [&](const boost::system::error_code ec, dbus::dbus_variant value) {
340         outstanding_async_calls--;
341         if (ec) {
342           FAIL() << ec;
343         } else {
344           EXPECT_EQ(boost::get<uint32_t>(value), 26);
345         }
346         if (outstanding_async_calls == 0) {
347           io.stop();
348         }
349       },
350       get_dbus_properties, "org.freedesktop.My.Interface", "foo");
351 
352   dbus::endpoint getall_dbus_properties(
353       bus->get_unique_name(), "/org/freedesktop/test1",
354       "org.freedesktop.DBus.Properties", "GetAll");
355 
356   outstanding_async_calls++;
357   bus->async_method_call(
358       [&](const boost::system::error_code ec,
359           std::vector<std::pair<std::string, dbus::dbus_variant>> value) {
360         outstanding_async_calls--;
361         if (ec) {
362           FAIL() << ec;
363         } else {
364           EXPECT_EQ(value.size(), 1);
365           EXPECT_EQ(value[0].first, "foo");
366           EXPECT_EQ(value[0].second, dbus::dbus_variant((uint32_t)26));
367         }
368         if (outstanding_async_calls == 0) {
369           io.stop();
370         }
371       },
372       getall_dbus_properties, "org.freedesktop.My.Interface");
373 
374   io.run();
375 }
376