1 #include <dbus/connection.hpp>
2 #include <dbus/filter.hpp>
3 #include <dbus/match.hpp>
4 #include <functional>
5 #include <tuple>
6 #include <type_traits>
7 #include <boost/algorithm/string/predicate.hpp>
8 #include <boost/container/flat_map.hpp>
9 #include <boost/container/flat_set.hpp>
10 
11 namespace dbus {
12 struct DbusArgument {
13   DbusArgument(const std::string& direction, const std::string& name,
14                const std::string& type)
15       : direction(direction), name(name), type(type){};
16   std::string direction;
17   std::string name;
18   std::string type;
19 };
20 
21 class DbusMethod {
22  public:
23   DbusMethod(const std::string& name, std::shared_ptr<dbus::connection>& conn)
24       : name(name), conn(conn){};
25   virtual void call(dbus::message& m){};
26   virtual std::vector<DbusArgument> get_args() { return {}; };
27   std::string name;
28   std::shared_ptr<dbus::connection> conn;
29 };
30 
31 enum class UpdateType { VALUE_CHANGE_ONLY, FORCE };
32 
33 // primary template.
34 template <class T>
35 struct function_traits : function_traits<decltype(&T::operator())> {};
36 
37 // partial specialization for function type
38 template <class R, class... Args>
39 struct function_traits<R(Args...)> {
40   using result_type = R;
41   using argument_types = std::tuple<Args...>;
42   using decayed_arg_types = std::tuple<typename std::decay<Args>::type...>;
43 };
44 
45 // partial specialization for function pointer
46 template <class R, class... Args>
47 struct function_traits<R (*)(Args...)> {
48   using result_type = R;
49   using argument_types = std::tuple<Args...>;
50   using decayed_arg_types = std::tuple<typename std::decay<Args>::type...>;
51 };
52 
53 // partial specialization for std::function
54 template <class R, class... Args>
55 struct function_traits<std::function<R(Args...)>> {
56   using result_type = R;
57   using argument_types = std::tuple<Args...>;
58   using decayed_arg_types = std::tuple<typename std::decay<Args>::type...>;
59 };
60 
61 // partial specialization for pointer-to-member-function (i.e., operator()'s)
62 template <class T, class R, class... Args>
63 struct function_traits<R (T::*)(Args...)> {
64   using result_type = R;
65   using argument_types = std::tuple<Args...>;
66   using decayed_arg_types = std::tuple<typename std::decay<Args>::type...>;
67 };
68 
69 template <class T, class R, class... Args>
70 struct function_traits<R (T::*)(Args...) const> {
71   using result_type = R;
72   using argument_types = std::tuple<Args...>;
73   using decayed_arg_types = std::tuple<typename std::decay<Args>::type...>;
74 };
75 
76 template <class F, size_t... Is>
77 constexpr auto index_apply_impl(F f, std::index_sequence<Is...>) {
78   return f(std::integral_constant<size_t, Is>{}...);
79 }
80 
81 template <size_t N, class F>
82 constexpr auto index_apply(F f) {
83   return index_apply_impl(f, std::make_index_sequence<N>{});
84 }
85 
86 template <class Tuple, class F>
87 constexpr auto apply(Tuple& t, F f) {
88   return index_apply<std::tuple_size<Tuple>{}>(
89       [&](auto... Is) { return f(std::get<Is>(t)...); });
90 }
91 
92 template <class Tuple>
93 constexpr void unpack_into_tuple(Tuple& t, dbus::message& m) {
94   index_apply<std::tuple_size<Tuple>{}>(
95       [&](auto... Is) { m.unpack(std::get<Is>(t)...); });
96 }
97 
98 // Specialization for empty tuples.  No need to unpack if no arguments
99 constexpr void unpack_into_tuple(std::tuple<>& t, dbus::message& m) {}
100 
101 template <typename... Args>
102 constexpr void pack_tuple_into_msg(std::tuple<Args...>& t, dbus::message& m) {
103   index_apply<std::tuple_size<std::tuple<Args...>>{}>(
104       [&](auto... Is) { m.pack(std::get<Is>(t)...); });
105 }
106 
107 // Specialization for empty tuples.  No need to pack if no arguments
108 constexpr void pack_tuple_into_msg(std::tuple<>& t, dbus::message& m) {}
109 
110 template <typename Element>
111 constexpr void pack_tuple_into_msg(Element& t, dbus::message& m) {
112   m.pack(t);
113 }
114 
115 // Base case for when I == the size of the tuple args.  Does nothing, as we
116 // should be done
117 template <std::size_t I = 0, typename... Tp>
118 inline typename std::enable_if<I == sizeof...(Tp), void>::type arg_types(
119     bool in, std::tuple<Tp...>& t, std::vector<DbusArgument>& v,
120     const std::vector<std::string>* arg_names = nullptr) {}
121 
122 // Case for when I < the size of tuple args.  Unpacks the tuple type into the
123 // dbusargument object and names it appropriately.
124 template <std::size_t I = 0, typename... Tp>
125     inline typename std::enable_if <
126     I<sizeof...(Tp), void>::type arg_types(
127         bool in, std::tuple<Tp...>& t, std::vector<DbusArgument>& v,
128         const std::vector<std::string>* arg_names = nullptr) {
129   typedef typename std::tuple_element<I, std::tuple<Tp...>>::type element_type;
130   auto constexpr sig = element_signature<element_type>::code;
131   std::string name;
132   std::string direction;
133   if (arg_names == nullptr || arg_names->size() <= I) {
134     if (in) {
135       name = "arg_" + std::to_string(I);
136     } else {
137       name = "out_" + std::to_string(I);
138     }
139   } else {
140     name = (*arg_names)[I];
141   }
142   v.emplace_back(in ? "in" : "out", name, &sig[0]);
143 
144   arg_types<I + 1, Tp...>(in, t, v, arg_names);
145 }
146 
147 // Special case for handling raw arguments returned from handlers.  Because they
148 // don't get stored in a tuple, special handling is neccesary
149 template <typename Element>
150 void arg_types(bool in, Element& t, std::vector<DbusArgument>& v,
151                const std::vector<std::string>* arg_names = nullptr) {
152   auto constexpr sig = element_signature<Element>::code;
153   std::string name;
154   if (arg_names == nullptr || arg_names->size() < 1) {
155     name.assign("arg_0");
156   } else {
157     name = (*arg_names)[0];
158   }
159 
160   v.emplace_back(in ? "in" : "out", name, &sig[0]);
161 }
162 
163 template <typename Handler>
164 class LambdaDbusMethod : public DbusMethod {
165  public:
166   typedef function_traits<Handler> traits;
167   typedef typename traits::decayed_arg_types InputTupleType;
168   typedef typename traits::result_type ResultType;
169   LambdaDbusMethod(const std::string name,
170                    std::shared_ptr<dbus::connection>& conn, Handler h)
171       : DbusMethod(name, conn), h(std::move(h)) {
172     InputTupleType t;
173     arg_types(true, t, args);
174 
175     ResultType o;
176     arg_types(false, o, args);
177   }
178 
179   LambdaDbusMethod(const std::string& name,
180                    const std::vector<std::string>& input_arg_names,
181                    const std::vector<std::string>& output_arg_names,
182                    std::shared_ptr<dbus::connection>& conn, Handler h)
183       : DbusMethod(name, conn), h(std::move(h)) {
184     InputTupleType t;
185     arg_types(true, t, args, &input_arg_names);
186 
187     ResultType o;
188     arg_types(false, o, args, &output_arg_names);
189   }
190   void call(dbus::message& m) override {
191     InputTupleType input_args;
192     unpack_into_tuple(input_args, m);
193     ResultType r = apply(input_args, h);
194     auto ret = dbus::message::new_return(m);
195     pack_tuple_into_msg(r, ret);
196     conn->send(ret, std::chrono::seconds(0));
197   };
198 
199   std::vector<DbusArgument> get_args() override { return args; };
200   Handler h;
201   std::vector<DbusArgument> args;
202 };
203 
204 class DbusSignal {
205  public:
206   DbusSignal(){};
207   virtual std::vector<DbusArgument> get_args() { return {}; }
208 };
209 
210 template <typename... Args>
211 class DbusTemplateSignal : public DbusSignal {
212  public:
213   DbusTemplateSignal(const std::string& name, const std::string& object_name,
214                      const std::string& interface_name,
215                      const std::vector<std::string>& names,
216                      std::shared_ptr<dbus::connection>& conn)
217       : DbusSignal(),
218         name(name),
219         object_name(object_name),
220         interface_name(interface_name),
221         conn(conn) {
222     std::tuple<Args...> tu;
223     arg_types(true, tu, args, &names);
224   };
225 
226   void send(Args&...) {
227     dbus::endpoint endpoint("", object_name, interface_name);
228     auto m = dbus::message::new_signal(endpoint, name);
229     conn->send(m, std::chrono::seconds(0));
230   }
231 
232   std::vector<DbusArgument> get_args() override { return args; };
233 
234   std::vector<DbusArgument> args;
235   std::string name;
236   std::string object_name;
237   std::string interface_name;
238   std::shared_ptr<dbus::connection> conn;
239 };
240 
241 class DbusInterface {
242  public:
243   DbusInterface(std::string interface_name,
244                 std::shared_ptr<dbus::connection>& conn)
245       : interface_name(std::move(interface_name)), conn(conn) {}
246   virtual boost::container::flat_map<std::string, std::shared_ptr<DbusSignal>>
247   get_signals() {
248     return dbus_signals;
249   };
250   virtual boost::container::flat_map<std::string, std::shared_ptr<DbusMethod>>
251   get_methods() {
252     return dbus_methods;
253   };
254   virtual std::string get_interface_name() { return interface_name; };
255   virtual const boost::container::flat_map<std::string, dbus_variant>
256   get_properties_map() {
257     return properties_map;
258   };
259 
260   template <typename VALUE_TYPE>
261   void set_property(const std::string& property_name, const VALUE_TYPE value,
262                     UpdateType update_mode = UpdateType::VALUE_CHANGE_ONLY) {
263     // Construct a change vector of length 1.  if this set_properties is ever
264     // templated for any type, we could probably swap with with a
265     // std::array<pair, 1>
266     std::vector<std::pair<std::string, dbus_variant>> v;
267     v.emplace_back(property_name, value);
268     set_properties(v, update_mode);
269   }
270 
271   void set_properties(
272       const std::vector<std::pair<std::string, dbus_variant>>& v,
273       const UpdateType update_mode = UpdateType::VALUE_CHANGE_ONLY) {
274     // TODO(ed) generalize this interface for all "map like" types, basically
275     // anything that will return a const iterator of std::pair<string,
276     // variant>
277     std::vector<std::pair<std::string, dbus_variant>> updates;
278     updates.reserve(v.size());
279 
280     if (update_mode == UpdateType::FORCE) {
281       updates = v;
282     } else {
283       for (auto& property : v) {
284         auto property_map_it = properties_map.find(property.first);
285         if (property_map_it != properties_map.end()) {
286           // Property exists in map
287           if (property_map_it->second != property.second) {
288             properties_map[property.first] = property.second;
289             // if value has changed since last set
290             updates.emplace_back(*property_map_it);
291           }
292         } else {
293           // property doesn't exist, must be new
294           properties_map[property.first] = property.second;
295           updates.emplace_back(property.first, property.second);
296         }
297       }
298     }
299 
300     const static dbus::endpoint endpoint("org.freedesktop.DBus", object_name,
301                                          "org.freedesktop.DBus.Properties");
302 
303     auto m = dbus::message::new_signal(endpoint, "PropertiesChanged");
304 
305     static const std::vector<std::string> empty;
306     m.pack(get_interface_name(), updates, empty);
307     // TODO(ed) make sure this doesn't block
308     conn->async_send(
309         m, [](const boost::system::error_code ec, dbus::message r) {});
310   }
311 
312   void register_method(std::shared_ptr<DbusMethod> method) {
313     dbus_methods.emplace(method->name, method);
314   }
315 
316   template <typename Handler>
317   void register_method(const std::string& name, Handler method) {
318     dbus_methods.emplace(name,
319                          new LambdaDbusMethod<Handler>(name, conn, method));
320   }
321 
322   template <typename Handler>
323   void register_method(const std::string& name,
324                        const std::vector<std::string>& input_arg_names,
325                        const std::vector<std::string>& output_arg_names,
326                        Handler method) {
327     dbus_methods.emplace(
328         name, new LambdaDbusMethod<Handler>(name, input_arg_names,
329                                             output_arg_names, conn, method));
330   }
331 
332   template <typename... Args>
333   std::shared_ptr<DbusSignal> register_signal(
334       const std::string& name, const std::vector<std::string> arg_names) {
335     auto it = dbus_signals.emplace(
336         name, new DbusTemplateSignal<Args...>(name, object_name, interface_name,
337                                               arg_names, conn));
338     return it.first->second;
339   }
340 
341   void call(dbus::message& m) {
342     std::string method_name = m.get_member();
343     auto method = dbus_methods.find(method_name);
344     if (method != dbus_methods.end()) {
345       method->second->call(m);
346     }  // TODO(ed) send something when method doesn't exist?
347   }
348 
349   std::string object_name;
350   std::string interface_name;
351   boost::container::flat_map<std::string, std::shared_ptr<DbusMethod>>
352       dbus_methods;
353   boost::container::flat_map<std::string, std::shared_ptr<DbusSignal>>
354       dbus_signals;
355   boost::container::flat_map<std::string, dbus_variant> properties_map;
356   std::shared_ptr<dbus::connection> conn;
357 };
358 
359 class DbusObject {
360  public:
361   DbusObject(std::shared_ptr<dbus::connection> conn, std::string object_name)
362       : object_name(std::move(object_name)), conn(conn) {
363     properties_iface = add_interface("org.freedesktop.DBus.Properties");
364 
365     properties_iface->register_method(
366         "Get", {"interface_name", "properties_name"}, {"value"},
367         [&](const std::string& interface_name,
368             const std::string& property_name) {
369           auto interface_it = interfaces.find(interface_name);
370           if (interface_it == interfaces.end()) {
371             // Interface not found error
372             throw std::runtime_error("interface not found");
373           } else {
374             auto& properties_map = interface_it->second->get_properties_map();
375             auto property = properties_map.find(property_name);
376             if (property == properties_map.end()) {
377               // TODO(ed) property not found error
378               throw std::runtime_error("property not found");
379             } else {
380               return std::tuple<dbus_variant>(property->second);
381             }
382           }
383         });
384 
385     properties_iface->register_method(
386         "GetAll", {"interface_name"}, {"properties"},
387         [&](const std::string& interface_name) {
388           auto interface_it = interfaces.find(interface_name);
389           if (interface_it == interfaces.end()) {
390             // Interface not found error
391             throw std::runtime_error("interface not found");
392           } else {
393             std::vector<std::pair<std::string, dbus_variant>> v;
394             for (auto& element : properties_iface->get_properties_map()) {
395               v.emplace_back(element.first, element.second);
396             }
397             return std::tuple<
398                 std::vector<std::pair<std::string, dbus_variant>>>(v);
399           }
400         });
401     properties_iface->register_method(
402         "Set", {"interface_name", "properties_name", "value"}, {},
403         [&](const std::string& interface_name, const std::string& property_name,
404             const dbus_variant& value) {
405           auto interface_it = interfaces.find(interface_name);
406           if (interface_it == interfaces.end()) {
407             // Interface not found error
408             throw std::runtime_error("interface not found");
409           } else {
410             // Todo, the set propery (signular) interface should support
411             // handing
412             // a variant.  THe below is expensive
413             std::vector<std::pair<std::string, dbus_variant>> v;
414             v.emplace_back(property_name, value);
415             interface_it->second->set_properties(v);
416             return std::tuple<>();
417           }
418         });
419 
420     properties_iface->register_signal<
421         std::string, std::vector<std::pair<std::string, dbus_variant>>,
422         std::vector<std::string>>(
423         "PropertiesChanged",
424         {"interface_name", "changed_properties", "invalidated_properties"});
425   }
426 
427   std::shared_ptr<DbusInterface> add_interface(const std::string& name) {
428     auto x = std::make_shared<DbusInterface>(name, conn);
429     register_interface(x);
430     return x;
431   }
432 
433   void register_interface(std::shared_ptr<DbusInterface>& interface) {
434     interfaces[interface->get_interface_name()] = interface;
435     interface->object_name = object_name;
436     const static dbus::endpoint endpoint("", object_name,
437                                          "org.freedesktop.DBus.ObjectManager");
438 
439     auto m = message::new_signal(endpoint, "InterfacesAdded");
440     typedef std::vector<std::pair<std::string, dbus_variant>> properties_dict;
441     std::vector<std::pair<std::string, properties_dict>> sig;
442     sig.emplace_back(interface->get_interface_name(), properties_dict());
443     auto& prop_dict = sig.back().second;
444     for (auto& property : interface->get_properties_map()) {
445       prop_dict.emplace_back(property);
446     }
447 
448     m.pack(object_name, sig);
449     // TODO(ed)
450     // conn->send(m, std::chrono::seconds(0));
451   }
452 
453   auto get_interfaces() { return interfaces; }
454 
455   void call(dbus::message& m) {
456     auto interface = interfaces.find(m.get_interface());
457     if (interface != interfaces.end()) {
458       interface->second->call(m);
459     }  // TODO(ed) send something when interface doesn't exist?
460   }
461 
462   std::string object_name;
463   std::shared_ptr<dbus::connection> conn;
464 
465   // dbus::filter properties_filter;
466   std::shared_ptr<DbusInterface> properties_iface;
467 
468   std::shared_ptr<DbusInterface> object_manager_iface;
469 
470   std::function<void(boost::system::error_code, message)> callback;
471   boost::container::flat_map<std::string, std::shared_ptr<DbusInterface>>
472       interfaces;
473 };
474 
475 class DbusObjectServer {
476  public:
477   DbusObjectServer(std::shared_ptr<dbus::connection>& conn) : conn(conn) {
478     introspect_filter =
479         std::make_unique<dbus::filter>(conn, [](dbus::message m) {
480           if (m.get_type() != "method_call") {
481             return false;
482           }
483           if (m.get_interface() != "org.freedesktop.DBus.Introspectable") {
484             return false;
485           }
486           if (m.get_member() != "Introspect") {
487             return false;
488           };
489           return true;
490         });
491 
492     introspect_filter->async_dispatch(
493         [&](const boost::system::error_code ec, dbus::message m) {
494           on_introspect(ec, m);
495         });
496 
497     object_manager_filter =
498         std::make_unique<dbus::filter>(conn, [](dbus::message m) {
499 
500           if (m.get_type() != "method_call") {
501             return false;
502           }
503           if (m.get_interface() != "org.freedesktop.DBus.ObjectManager") {
504             return false;
505           }
506           if (m.get_member() != "GetManagedObjects") {
507             return false;
508           };
509           return true;
510         });
511 
512     object_manager_filter->async_dispatch(
513         [&](const boost::system::error_code ec, dbus::message m) {
514           on_get_managed_objects(ec, m);
515         });
516 
517     method_filter = std::make_unique<dbus::filter>(conn, [](dbus::message m) {
518 
519       if (m.get_type() != "method_call") {
520         return false;
521       }
522       return true;
523     });
524 
525     method_filter->async_dispatch(
526         [&](const boost::system::error_code ec, dbus::message m) {
527           on_method_call(ec, m);
528         });
529   };
530 
531   std::shared_ptr<dbus::connection>& get_connection() { return conn; }
532   void on_introspect(const boost::system::error_code ec, dbus::message m) {
533     auto xml = get_xml_for_path(m.get_path());
534     std::cout << "path: " << m.get_path() << "\n" << xml << "\n";
535     auto ret = dbus::message::new_return(m);
536     ret.pack(xml);
537     conn->async_send(
538         ret, [](const boost::system::error_code ec, dbus::message r) {});
539 
540     introspect_filter->async_dispatch(
541         [&](const boost::system::error_code ec, dbus::message m) {
542           on_introspect(ec, m);
543         });
544   }
545 
546   void on_method_call(const boost::system::error_code ec, dbus::message m) {
547     std::cout << "on method call\n";
548     if (ec) {
549       std::cerr << "on_method_call error: " << ec << "\n";
550     } else {
551       auto path = m.get_path();
552       // TODO(ed) objects should be a map
553       for (auto& object : objects) {
554         if (object->object_name == path) {
555           object->call(m);
556           break;
557         }
558       }
559     }
560     method_filter->async_dispatch(
561         [&](const boost::system::error_code ec, dbus::message m) {
562           on_method_call(ec, m);
563         });
564   }
565 
566   void on_get_managed_objects(const boost::system::error_code ec,
567                               dbus::message m) {
568     typedef std::vector<std::pair<std::string, dbus::dbus_variant>>
569         properties_dict;
570 
571     typedef std::vector<std::pair<std::string, properties_dict>>
572         interfaces_dict;
573 
574     std::vector<std::pair<std::string, interfaces_dict>> dict;
575 
576     for (auto& object : objects) {
577       interfaces_dict i;
578       for (auto& interface : object->get_interfaces()) {
579         properties_dict p;
580 
581         for (auto& property : interface.second->get_properties_map()) {
582           p.push_back(property);
583         }
584 
585         i.emplace_back(interface.second->get_interface_name(), std::move(p));
586       }
587       dict.emplace_back(object->object_name, std::move(i));
588     }
589     auto ret = dbus::message::new_return(m);
590     ret.pack(dict);
591     conn->async_send(
592         ret, [](const boost::system::error_code ec, dbus::message r) {});
593 
594     object_manager_filter->async_dispatch(
595         [&](const boost::system::error_code ec, dbus::message m) {
596           on_get_managed_objects(ec, m);
597         });
598   }
599 
600   std::shared_ptr<DbusObject> add_object(const std::string& name) {
601     auto x = std::make_shared<DbusObject>(conn, name);
602     register_object(x);
603     return x;
604   }
605 
606   void register_object(std::shared_ptr<DbusObject> object) {
607     objects.emplace_back(object);
608   }
609 
610   std::string get_xml_for_path(const std::string& path) {
611     std::string newpath(path);
612 
613     if (newpath == "/") {
614       newpath.assign("");
615     }
616 
617     boost::container::flat_set<std::string> node_names;
618     std::string xml(
619         "<!DOCTYPE node PUBLIC "
620         "\"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\" "
621         "\"http://www.freedesktop.org/standards/dbus/1.0/"
622         "introspect.dtd\">\n<node>");
623     for (auto& object : objects) {
624       std::string& object_name = object->object_name;
625       // exact match
626       if (object->object_name == newpath) {
627         xml +=
628             "  <interface name=\"org.freedesktop.DBus.Peer\">"
629             "    <method name=\"Ping\"/>"
630             "    <method name=\"GetMachineId\">"
631             "      <arg type=\"s\" name=\"machine_uuid\" direction=\"out\"/>"
632             "    </method>"
633             "  </interface>";
634 
635         xml +=
636             "  <interface name=\"org.freedesktop.DBus.ObjectManager\">"
637             "    <method name=\"GetManagedObjects\">"
638             "      <arg type=\"a{oa{sa{sv}}}\" "
639             "           name=\"object_paths_interfaces_and_properties\" "
640             "           direction=\"out\"/>"
641             "    </method>"
642             "    <signal name=\"InterfacesAdded\">"
643             "      <arg type=\"o\" name=\"object_path\"/>"
644             "      <arg type=\"a{sa{sv}}\" "
645             "name=\"interfaces_and_properties\"/>"
646             "    </signal>"
647             "    <signal name=\"InterfacesRemoved\">"
648             "      <arg type=\"o\" name=\"object_path\"/>"
649             "      <arg type=\"as\" name=\"interfaces\"/>"
650             "    </signal>"
651             "  </interface>";
652 
653         xml +=
654             "<interface name=\"org.freedesktop.DBus.Introspectable\">"
655             "    <method name=\"Introspect\">"
656             "        <arg type=\"s\" name=\"xml_data\" direction=\"out\"/>"
657             "    </method>"
658             "</interface>";
659 
660         for (auto& interface_pair : object->interfaces) {
661           xml += "<interface name=\"";
662           xml += interface_pair.first;
663           xml += "\">";
664           for (auto& method : interface_pair.second->get_methods()) {
665             xml += "<method name=\"";
666             xml += method.first;
667             xml += "\">";
668             for (auto& arg : method.second->get_args()) {
669               xml += "<arg name=\"";
670               xml += arg.name;
671               xml += "\" type=\"";
672               xml += arg.type;
673               xml += "\" direction=\"";
674               xml += arg.direction;
675               xml += "\"/>";
676             }
677             xml += "</method>";
678           }
679 
680           for (auto& signal : interface_pair.second->get_signals()) {
681             xml += "<signal name=\"";
682             xml += signal.first;
683             xml += "\">";
684             for (auto& arg : signal.second->get_args()) {
685               xml += "<arg name=\"";
686               xml += arg.name;
687               xml += "\" type=\"";
688               xml += arg.type;
689               xml += "\"/>";
690             }
691 
692             xml += "</signal>";
693           }
694 
695           for (auto& property : interface_pair.second->get_properties_map()) {
696             xml += "<property name=\"";
697             xml += property.first;
698             xml += "\" type=\"";
699 
700             std::string type = std::string(boost::apply_visitor(
701                 [&](auto val) {
702                   static const auto constexpr sig =
703                       element_signature<decltype(val)>::code;
704                   return &sig[0];
705                 },
706                 property.second));
707             xml += type;
708             xml += "\" access=\"";
709             // TODO direction can be readwrite, read, or write.  Need to
710             // make this configurable
711             xml += "readwrite";
712             xml += "\"/>";
713           }
714           xml += "</interface>";
715         }
716       } else if (boost::starts_with(object_name, newpath)) {
717         auto slash_index = object_name.find("/", newpath.size() + 1);
718         auto subnode = object_name.substr(newpath.size() + 1,
719                                           slash_index - newpath.size() - 1);
720         if (node_names.find(subnode) == node_names.end()) {
721           node_names.insert(subnode);
722           xml += "<node name=\"";
723           xml += subnode;
724           xml += "\">";
725           xml += "</node>";
726         }
727       }
728     }
729     xml += "</node>";
730     return xml;
731   }
732 
733  private:
734   std::shared_ptr<dbus::connection> conn;
735   std::vector<std::shared_ptr<DbusObject>> objects;
736   std::unique_ptr<dbus::filter> introspect_filter;
737   std::unique_ptr<dbus::filter> object_manager_filter;
738   std::unique_ptr<dbus::filter> method_filter;
739 };
740 }
741