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