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