xref: /openbmc/boost-dbus/include/dbus/properties.hpp (revision a04118e8e5023cb992407b9561484eb8eceee376)
1377e76abSEd Tanous #include <dbus/connection.hpp>
2377e76abSEd Tanous #include <dbus/filter.hpp>
3377e76abSEd Tanous #include <dbus/match.hpp>
4377e76abSEd Tanous #include <functional>
5377e76abSEd Tanous #include <tuple>
6377e76abSEd Tanous #include <type_traits>
7377e76abSEd Tanous #include <boost/algorithm/string/predicate.hpp>
8377e76abSEd Tanous #include <boost/container/flat_map.hpp>
9377e76abSEd Tanous #include <boost/container/flat_set.hpp>
10377e76abSEd Tanous 
11377e76abSEd Tanous namespace dbus {
12377e76abSEd Tanous struct DbusArgument {
13377e76abSEd Tanous   DbusArgument(const std::string& direction, const std::string& name,
14377e76abSEd Tanous                const std::string& type)
15377e76abSEd Tanous       : direction(direction), name(name), type(type){};
16377e76abSEd Tanous   std::string direction;
17377e76abSEd Tanous   std::string name;
18377e76abSEd Tanous   std::string type;
19377e76abSEd Tanous };
20377e76abSEd Tanous 
21377e76abSEd Tanous class DbusMethod {
22377e76abSEd Tanous  public:
23e3b0bf5bSEd Tanous   DbusMethod(const std::string& name, std::shared_ptr<dbus::connection>& conn)
24377e76abSEd Tanous       : name(name), conn(conn){};
25377e76abSEd Tanous   virtual void call(dbus::message& m){};
26377e76abSEd Tanous   virtual std::vector<DbusArgument> get_args() { return {}; };
27377e76abSEd Tanous   std::string name;
28377e76abSEd Tanous   std::shared_ptr<dbus::connection> conn;
29377e76abSEd Tanous };
30377e76abSEd Tanous 
31377e76abSEd Tanous enum class UpdateType { VALUE_CHANGE_ONLY, FORCE };
32377e76abSEd Tanous 
33377e76abSEd Tanous // primary template.
34377e76abSEd Tanous template <class T>
35377e76abSEd Tanous struct function_traits : function_traits<decltype(&T::operator())> {};
36377e76abSEd Tanous 
37377e76abSEd Tanous // partial specialization for function type
38377e76abSEd Tanous template <class R, class... Args>
39377e76abSEd Tanous struct function_traits<R(Args...)> {
40377e76abSEd Tanous   using result_type = R;
41377e76abSEd Tanous   using argument_types = std::tuple<Args...>;
42377e76abSEd Tanous   using decayed_arg_types = std::tuple<typename std::decay<Args>::type...>;
43377e76abSEd Tanous };
44377e76abSEd Tanous 
45377e76abSEd Tanous // partial specialization for function pointer
46377e76abSEd Tanous template <class R, class... Args>
47377e76abSEd Tanous struct function_traits<R (*)(Args...)> {
48377e76abSEd Tanous   using result_type = R;
49377e76abSEd Tanous   using argument_types = std::tuple<Args...>;
50377e76abSEd Tanous   using decayed_arg_types = std::tuple<typename std::decay<Args>::type...>;
51377e76abSEd Tanous };
52377e76abSEd Tanous 
53377e76abSEd Tanous // partial specialization for std::function
54377e76abSEd Tanous template <class R, class... Args>
55377e76abSEd Tanous struct function_traits<std::function<R(Args...)>> {
56377e76abSEd Tanous   using result_type = R;
57377e76abSEd Tanous   using argument_types = std::tuple<Args...>;
58377e76abSEd Tanous   using decayed_arg_types = std::tuple<typename std::decay<Args>::type...>;
59377e76abSEd Tanous };
60377e76abSEd Tanous 
61377e76abSEd Tanous // partial specialization for pointer-to-member-function (i.e., operator()'s)
62377e76abSEd Tanous template <class T, class R, class... Args>
63377e76abSEd Tanous struct function_traits<R (T::*)(Args...)> {
64377e76abSEd Tanous   using result_type = R;
65377e76abSEd Tanous   using argument_types = std::tuple<Args...>;
66377e76abSEd Tanous   using decayed_arg_types = std::tuple<typename std::decay<Args>::type...>;
67377e76abSEd Tanous };
68377e76abSEd Tanous 
69377e76abSEd Tanous template <class T, class R, class... Args>
70377e76abSEd Tanous struct function_traits<R (T::*)(Args...) const> {
71377e76abSEd Tanous   using result_type = R;
72377e76abSEd Tanous   using argument_types = std::tuple<Args...>;
73377e76abSEd Tanous   using decayed_arg_types = std::tuple<typename std::decay<Args>::type...>;
74377e76abSEd Tanous };
75377e76abSEd Tanous 
76377e76abSEd Tanous template <class F, size_t... Is>
77377e76abSEd Tanous constexpr auto index_apply_impl(F f, std::index_sequence<Is...>) {
78377e76abSEd Tanous   return f(std::integral_constant<size_t, Is>{}...);
79377e76abSEd Tanous }
80377e76abSEd Tanous 
81377e76abSEd Tanous template <size_t N, class F>
82377e76abSEd Tanous constexpr auto index_apply(F f) {
83377e76abSEd Tanous   return index_apply_impl(f, std::make_index_sequence<N>{});
84377e76abSEd Tanous }
85377e76abSEd Tanous 
86377e76abSEd Tanous template <class Tuple, class F>
87377e76abSEd Tanous constexpr auto apply(Tuple& t, F f) {
88377e76abSEd Tanous   return index_apply<std::tuple_size<Tuple>{}>(
89377e76abSEd Tanous       [&](auto... Is) { return f(std::get<Is>(t)...); });
90377e76abSEd Tanous }
91377e76abSEd Tanous 
92377e76abSEd Tanous template <class Tuple>
93*a04118e8SEd Tanous constexpr bool unpack_into_tuple(Tuple& t, dbus::message& m) {
94*a04118e8SEd Tanous   return index_apply<std::tuple_size<Tuple>{}>(
95*a04118e8SEd Tanous       [&](auto... Is) { return m.unpack(std::get<Is>(t)...); });
96377e76abSEd Tanous }
97377e76abSEd Tanous 
98377e76abSEd Tanous // Specialization for empty tuples.  No need to unpack if no arguments
99*a04118e8SEd Tanous constexpr bool unpack_into_tuple(std::tuple<>& t, dbus::message& m) {
100*a04118e8SEd Tanous   return true;
101*a04118e8SEd Tanous }
102377e76abSEd Tanous 
103e3b0bf5bSEd Tanous template <typename... Args>
104*a04118e8SEd Tanous constexpr bool pack_tuple_into_msg(std::tuple<Args...>& t, dbus::message& m) {
105*a04118e8SEd Tanous   return index_apply<std::tuple_size<std::tuple<Args...>>{}>(
106*a04118e8SEd Tanous       [&](auto... Is) { return m.pack(std::get<Is>(t)...); });
107377e76abSEd Tanous }
108377e76abSEd Tanous 
109377e76abSEd Tanous // Specialization for empty tuples.  No need to pack if no arguments
110*a04118e8SEd Tanous constexpr bool pack_tuple_into_msg(std::tuple<>& t, dbus::message& m) {
111*a04118e8SEd Tanous   return true;
112*a04118e8SEd Tanous }
113377e76abSEd Tanous 
114*a04118e8SEd Tanous // Specialization for single types.  Used when callbacks simply return one value
115e3b0bf5bSEd Tanous template <typename Element>
116*a04118e8SEd Tanous constexpr bool pack_tuple_into_msg(Element& t, dbus::message& m) {
117*a04118e8SEd Tanous   return m.pack(t);
118e3b0bf5bSEd Tanous }
119e3b0bf5bSEd Tanous 
120377e76abSEd Tanous // Base case for when I == the size of the tuple args.  Does nothing, as we
121377e76abSEd Tanous // should be done
122377e76abSEd Tanous template <std::size_t I = 0, typename... Tp>
123377e76abSEd Tanous inline typename std::enable_if<I == sizeof...(Tp), void>::type arg_types(
124e3b0bf5bSEd Tanous     bool in, std::tuple<Tp...>& t, std::vector<DbusArgument>& v,
125e3b0bf5bSEd Tanous     const std::vector<std::string>* arg_names = nullptr) {}
126377e76abSEd Tanous 
127377e76abSEd Tanous // Case for when I < the size of tuple args.  Unpacks the tuple type into the
128377e76abSEd Tanous // dbusargument object and names it appropriately.
129377e76abSEd Tanous template <std::size_t I = 0, typename... Tp>
130377e76abSEd Tanous     inline typename std::enable_if <
131e3b0bf5bSEd Tanous     I<sizeof...(Tp), void>::type arg_types(
132e3b0bf5bSEd Tanous         bool in, std::tuple<Tp...>& t, std::vector<DbusArgument>& v,
133e3b0bf5bSEd Tanous         const std::vector<std::string>* arg_names = nullptr) {
134377e76abSEd Tanous   typedef typename std::tuple_element<I, std::tuple<Tp...>>::type element_type;
135377e76abSEd Tanous   auto constexpr sig = element_signature<element_type>::code;
136e3b0bf5bSEd Tanous   std::string name;
137e3b0bf5bSEd Tanous   std::string direction;
138e3b0bf5bSEd Tanous   if (arg_names == nullptr || arg_names->size() <= I) {
139377e76abSEd Tanous     if (in) {
140e3b0bf5bSEd Tanous       name = "arg_" + std::to_string(I);
141377e76abSEd Tanous     } else {
142e3b0bf5bSEd Tanous       name = "out_" + std::to_string(I);
143377e76abSEd Tanous     }
144e3b0bf5bSEd Tanous   } else {
145e3b0bf5bSEd Tanous     name = (*arg_names)[I];
146e3b0bf5bSEd Tanous   }
147e3b0bf5bSEd Tanous   v.emplace_back(in ? "in" : "out", name, &sig[0]);
148e3b0bf5bSEd Tanous 
149e3b0bf5bSEd Tanous   arg_types<I + 1, Tp...>(in, t, v, arg_names);
150e3b0bf5bSEd Tanous }
151e3b0bf5bSEd Tanous 
152e3b0bf5bSEd Tanous // Special case for handling raw arguments returned from handlers.  Because they
153e3b0bf5bSEd Tanous // don't get stored in a tuple, special handling is neccesary
154e3b0bf5bSEd Tanous template <typename Element>
155e3b0bf5bSEd Tanous void arg_types(bool in, Element& t, std::vector<DbusArgument>& v,
156e3b0bf5bSEd Tanous                const std::vector<std::string>* arg_names = nullptr) {
157e3b0bf5bSEd Tanous   auto constexpr sig = element_signature<Element>::code;
158e3b0bf5bSEd Tanous   std::string name;
159e3b0bf5bSEd Tanous   if (arg_names == nullptr || arg_names->size() < 1) {
160e3b0bf5bSEd Tanous     name.assign("arg_0");
161e3b0bf5bSEd Tanous   } else {
162e3b0bf5bSEd Tanous     name = (*arg_names)[0];
163e3b0bf5bSEd Tanous   }
164e3b0bf5bSEd Tanous 
165e3b0bf5bSEd Tanous   v.emplace_back(in ? "in" : "out", name, &sig[0]);
166377e76abSEd Tanous }
167377e76abSEd Tanous 
168377e76abSEd Tanous template <typename Handler>
169377e76abSEd Tanous class LambdaDbusMethod : public DbusMethod {
170377e76abSEd Tanous  public:
171377e76abSEd Tanous   typedef function_traits<Handler> traits;
172377e76abSEd Tanous   typedef typename traits::decayed_arg_types InputTupleType;
173e3b0bf5bSEd Tanous   typedef typename traits::result_type ResultType;
174e3b0bf5bSEd Tanous   LambdaDbusMethod(const std::string name,
175e3b0bf5bSEd Tanous                    std::shared_ptr<dbus::connection>& conn, Handler h)
176e3b0bf5bSEd Tanous       : DbusMethod(name, conn), h(std::move(h)) {
177e3b0bf5bSEd Tanous     InputTupleType t;
178e3b0bf5bSEd Tanous     arg_types(true, t, args);
179e3b0bf5bSEd Tanous 
180e3b0bf5bSEd Tanous     ResultType o;
181e3b0bf5bSEd Tanous     arg_types(false, o, args);
182e3b0bf5bSEd Tanous   }
183e3b0bf5bSEd Tanous 
184e3b0bf5bSEd Tanous   LambdaDbusMethod(const std::string& name,
185e3b0bf5bSEd Tanous                    const std::vector<std::string>& input_arg_names,
186e3b0bf5bSEd Tanous                    const std::vector<std::string>& output_arg_names,
187e3b0bf5bSEd Tanous                    std::shared_ptr<dbus::connection>& conn, Handler h)
188e3b0bf5bSEd Tanous       : DbusMethod(name, conn), h(std::move(h)) {
189e3b0bf5bSEd Tanous     InputTupleType t;
190e3b0bf5bSEd Tanous     arg_types(true, t, args, &input_arg_names);
191e3b0bf5bSEd Tanous 
192e3b0bf5bSEd Tanous     ResultType o;
193e3b0bf5bSEd Tanous     arg_types(false, o, args, &output_arg_names);
194e3b0bf5bSEd Tanous   }
195377e76abSEd Tanous   void call(dbus::message& m) override {
196377e76abSEd Tanous     InputTupleType input_args;
197*a04118e8SEd Tanous     if (unpack_into_tuple(input_args, m) == false) {
198*a04118e8SEd Tanous       auto err = dbus::message::new_error(m, DBUS_ERROR_INVALID_ARGS, "");
199*a04118e8SEd Tanous       conn->send(err, std::chrono::seconds(0));
200*a04118e8SEd Tanous       return;
201*a04118e8SEd Tanous     }
202*a04118e8SEd Tanous     try {
203e3b0bf5bSEd Tanous       ResultType r = apply(input_args, h);
204377e76abSEd Tanous       auto ret = dbus::message::new_return(m);
205*a04118e8SEd Tanous       if (pack_tuple_into_msg(r, ret) == false) {
206*a04118e8SEd Tanous         auto err = dbus::message::new_error(
207*a04118e8SEd Tanous             m, DBUS_ERROR_FAILED, "Handler had issue when packing response");
208*a04118e8SEd Tanous         conn->send(err, std::chrono::seconds(0));
209*a04118e8SEd Tanous         return;
210*a04118e8SEd Tanous       }
211377e76abSEd Tanous       conn->send(ret, std::chrono::seconds(0));
212*a04118e8SEd Tanous     } catch (...) {
213*a04118e8SEd Tanous       auto err = dbus::message::new_error(
214*a04118e8SEd Tanous           m, DBUS_ERROR_FAILED,
215*a04118e8SEd Tanous           "Handler threw exception while handling request.");
216*a04118e8SEd Tanous       conn->send(err, std::chrono::seconds(0));
217*a04118e8SEd Tanous       return;
218*a04118e8SEd Tanous     }
219377e76abSEd Tanous   };
220377e76abSEd Tanous 
221e3b0bf5bSEd Tanous   std::vector<DbusArgument> get_args() override { return args; };
222377e76abSEd Tanous   Handler h;
223e3b0bf5bSEd Tanous   std::vector<DbusArgument> args;
224377e76abSEd Tanous };
225377e76abSEd Tanous 
226377e76abSEd Tanous class DbusSignal {
227377e76abSEd Tanous  public:
228e3b0bf5bSEd Tanous   DbusSignal(){};
229e3b0bf5bSEd Tanous   virtual std::vector<DbusArgument> get_args() { return {}; }
230e3b0bf5bSEd Tanous };
231377e76abSEd Tanous 
232e3b0bf5bSEd Tanous template <typename... Args>
233e3b0bf5bSEd Tanous class DbusTemplateSignal : public DbusSignal {
234e3b0bf5bSEd Tanous  public:
235e3b0bf5bSEd Tanous   DbusTemplateSignal(const std::string& name, const std::string& object_name,
236e3b0bf5bSEd Tanous                      const std::string& interface_name,
237e3b0bf5bSEd Tanous                      const std::vector<std::string>& names,
238e3b0bf5bSEd Tanous                      std::shared_ptr<dbus::connection>& conn)
239e3b0bf5bSEd Tanous       : DbusSignal(),
240e3b0bf5bSEd Tanous         name(name),
241e3b0bf5bSEd Tanous         object_name(object_name),
242e3b0bf5bSEd Tanous         interface_name(interface_name),
243e3b0bf5bSEd Tanous         conn(conn) {
244e3b0bf5bSEd Tanous     std::tuple<Args...> tu;
245e3b0bf5bSEd Tanous     arg_types(true, tu, args, &names);
246e3b0bf5bSEd Tanous   };
247e3b0bf5bSEd Tanous 
248e3b0bf5bSEd Tanous   void send(Args&...) {
249e3b0bf5bSEd Tanous     dbus::endpoint endpoint("", object_name, interface_name);
250e3b0bf5bSEd Tanous     auto m = dbus::message::new_signal(endpoint, name);
251e3b0bf5bSEd Tanous     conn->send(m, std::chrono::seconds(0));
252e3b0bf5bSEd Tanous   }
253e3b0bf5bSEd Tanous 
254e3b0bf5bSEd Tanous   std::vector<DbusArgument> get_args() override { return args; };
255e3b0bf5bSEd Tanous 
256e3b0bf5bSEd Tanous   std::vector<DbusArgument> args;
257e3b0bf5bSEd Tanous   std::string name;
258e3b0bf5bSEd Tanous   std::string object_name;
259e3b0bf5bSEd Tanous   std::string interface_name;
260e3b0bf5bSEd Tanous   std::shared_ptr<dbus::connection> conn;
261377e76abSEd Tanous };
262377e76abSEd Tanous 
263377e76abSEd Tanous class DbusInterface {
264377e76abSEd Tanous  public:
265377e76abSEd Tanous   DbusInterface(std::string interface_name,
266377e76abSEd Tanous                 std::shared_ptr<dbus::connection>& conn)
267377e76abSEd Tanous       : interface_name(std::move(interface_name)), conn(conn) {}
268377e76abSEd Tanous   virtual boost::container::flat_map<std::string, std::shared_ptr<DbusSignal>>
269377e76abSEd Tanous   get_signals() {
270377e76abSEd Tanous     return dbus_signals;
271377e76abSEd Tanous   };
272377e76abSEd Tanous   virtual boost::container::flat_map<std::string, std::shared_ptr<DbusMethod>>
273377e76abSEd Tanous   get_methods() {
274377e76abSEd Tanous     return dbus_methods;
275377e76abSEd Tanous   };
276377e76abSEd Tanous   virtual std::string get_interface_name() { return interface_name; };
277377e76abSEd Tanous   virtual const boost::container::flat_map<std::string, dbus_variant>
278377e76abSEd Tanous   get_properties_map() {
279377e76abSEd Tanous     return properties_map;
280377e76abSEd Tanous   };
281377e76abSEd Tanous 
282377e76abSEd Tanous   template <typename VALUE_TYPE>
283377e76abSEd Tanous   void set_property(const std::string& property_name, const VALUE_TYPE value,
284377e76abSEd Tanous                     UpdateType update_mode = UpdateType::VALUE_CHANGE_ONLY) {
285377e76abSEd Tanous     // Construct a change vector of length 1.  if this set_properties is ever
286377e76abSEd Tanous     // templated for any type, we could probably swap with with a
287377e76abSEd Tanous     // std::array<pair, 1>
288377e76abSEd Tanous     std::vector<std::pair<std::string, dbus_variant>> v;
289377e76abSEd Tanous     v.emplace_back(property_name, value);
290377e76abSEd Tanous     set_properties(v, update_mode);
291377e76abSEd Tanous   }
292377e76abSEd Tanous 
293377e76abSEd Tanous   void set_properties(
294377e76abSEd Tanous       const std::vector<std::pair<std::string, dbus_variant>>& v,
295377e76abSEd Tanous       const UpdateType update_mode = UpdateType::VALUE_CHANGE_ONLY) {
296377e76abSEd Tanous     // TODO(ed) generalize this interface for all "map like" types, basically
297377e76abSEd Tanous     // anything that will return a const iterator of std::pair<string,
298377e76abSEd Tanous     // variant>
299377e76abSEd Tanous     std::vector<std::pair<std::string, dbus_variant>> updates;
300377e76abSEd Tanous     updates.reserve(v.size());
301377e76abSEd Tanous 
302377e76abSEd Tanous     if (update_mode == UpdateType::FORCE) {
303377e76abSEd Tanous       updates = v;
304377e76abSEd Tanous     } else {
305377e76abSEd Tanous       for (auto& property : v) {
306377e76abSEd Tanous         auto property_map_it = properties_map.find(property.first);
307377e76abSEd Tanous         if (property_map_it != properties_map.end()) {
308377e76abSEd Tanous           // Property exists in map
309377e76abSEd Tanous           if (property_map_it->second != property.second) {
310377e76abSEd Tanous             properties_map[property.first] = property.second;
311377e76abSEd Tanous             // if value has changed since last set
312377e76abSEd Tanous             updates.emplace_back(*property_map_it);
313377e76abSEd Tanous           }
314377e76abSEd Tanous         } else {
315377e76abSEd Tanous           // property doesn't exist, must be new
316377e76abSEd Tanous           properties_map[property.first] = property.second;
317377e76abSEd Tanous           updates.emplace_back(property.first, property.second);
318377e76abSEd Tanous         }
319377e76abSEd Tanous       }
320377e76abSEd Tanous     }
321377e76abSEd Tanous 
322377e76abSEd Tanous     const static dbus::endpoint endpoint("org.freedesktop.DBus", object_name,
323377e76abSEd Tanous                                          "org.freedesktop.DBus.Properties");
324377e76abSEd Tanous 
325377e76abSEd Tanous     auto m = dbus::message::new_signal(endpoint, "PropertiesChanged");
326377e76abSEd Tanous 
327377e76abSEd Tanous     static const std::vector<std::string> empty;
328377e76abSEd Tanous     m.pack(get_interface_name(), updates, empty);
329377e76abSEd Tanous     // TODO(ed) make sure this doesn't block
330377e76abSEd Tanous     conn->async_send(
331377e76abSEd Tanous         m, [](const boost::system::error_code ec, dbus::message r) {});
332377e76abSEd Tanous   }
333377e76abSEd Tanous 
334e3b0bf5bSEd Tanous   void register_method(std::shared_ptr<DbusMethod> method) {
335377e76abSEd Tanous     dbus_methods.emplace(method->name, method);
336377e76abSEd Tanous   }
337377e76abSEd Tanous 
338377e76abSEd Tanous   template <typename Handler>
339e3b0bf5bSEd Tanous   void register_method(const std::string& name, Handler method) {
340377e76abSEd Tanous     dbus_methods.emplace(name,
341377e76abSEd Tanous                          new LambdaDbusMethod<Handler>(name, conn, method));
342377e76abSEd Tanous   }
343377e76abSEd Tanous 
344e3b0bf5bSEd Tanous   template <typename Handler>
345e3b0bf5bSEd Tanous   void register_method(const std::string& name,
346e3b0bf5bSEd Tanous                        const std::vector<std::string>& input_arg_names,
347e3b0bf5bSEd Tanous                        const std::vector<std::string>& output_arg_names,
348e3b0bf5bSEd Tanous                        Handler method) {
349e3b0bf5bSEd Tanous     dbus_methods.emplace(
350e3b0bf5bSEd Tanous         name, new LambdaDbusMethod<Handler>(name, input_arg_names,
351e3b0bf5bSEd Tanous                                             output_arg_names, conn, method));
352e3b0bf5bSEd Tanous   }
353e3b0bf5bSEd Tanous 
354e3b0bf5bSEd Tanous   template <typename... Args>
355e3b0bf5bSEd Tanous   std::shared_ptr<DbusSignal> register_signal(
356e3b0bf5bSEd Tanous       const std::string& name, const std::vector<std::string> arg_names) {
357e3b0bf5bSEd Tanous     auto it = dbus_signals.emplace(
358e3b0bf5bSEd Tanous         name, new DbusTemplateSignal<Args...>(name, object_name, interface_name,
359e3b0bf5bSEd Tanous                                               arg_names, conn));
360e3b0bf5bSEd Tanous     return it.first->second;
361e3b0bf5bSEd Tanous   }
362e3b0bf5bSEd Tanous 
363377e76abSEd Tanous   void call(dbus::message& m) {
364377e76abSEd Tanous     std::string method_name = m.get_member();
365377e76abSEd Tanous     auto method = dbus_methods.find(method_name);
366377e76abSEd Tanous     if (method != dbus_methods.end()) {
367377e76abSEd Tanous       method->second->call(m);
368377e76abSEd Tanous     }  // TODO(ed) send something when method doesn't exist?
369377e76abSEd Tanous   }
370377e76abSEd Tanous 
371377e76abSEd Tanous   std::string object_name;
372377e76abSEd Tanous   std::string interface_name;
373377e76abSEd Tanous   boost::container::flat_map<std::string, std::shared_ptr<DbusMethod>>
374377e76abSEd Tanous       dbus_methods;
375377e76abSEd Tanous   boost::container::flat_map<std::string, std::shared_ptr<DbusSignal>>
376377e76abSEd Tanous       dbus_signals;
377377e76abSEd Tanous   boost::container::flat_map<std::string, dbus_variant> properties_map;
378377e76abSEd Tanous   std::shared_ptr<dbus::connection> conn;
379377e76abSEd Tanous };
380377e76abSEd Tanous 
381377e76abSEd Tanous class DbusObject {
382377e76abSEd Tanous  public:
383377e76abSEd Tanous   DbusObject(std::shared_ptr<dbus::connection> conn, std::string object_name)
384377e76abSEd Tanous       : object_name(std::move(object_name)), conn(conn) {
385377e76abSEd Tanous     properties_iface = add_interface("org.freedesktop.DBus.Properties");
386377e76abSEd Tanous 
387377e76abSEd Tanous     properties_iface->register_method(
388e3b0bf5bSEd Tanous         "Get", {"interface_name", "properties_name"}, {"value"},
389e3b0bf5bSEd Tanous         [&](const std::string& interface_name,
390377e76abSEd Tanous             const std::string& property_name) {
391377e76abSEd Tanous           auto interface_it = interfaces.find(interface_name);
392377e76abSEd Tanous           if (interface_it == interfaces.end()) {
393377e76abSEd Tanous             // Interface not found error
394377e76abSEd Tanous             throw std::runtime_error("interface not found");
395377e76abSEd Tanous           } else {
396377e76abSEd Tanous             auto& properties_map = interface_it->second->get_properties_map();
397377e76abSEd Tanous             auto property = properties_map.find(property_name);
398377e76abSEd Tanous             if (property == properties_map.end()) {
399377e76abSEd Tanous               // TODO(ed) property not found error
400377e76abSEd Tanous               throw std::runtime_error("property not found");
401377e76abSEd Tanous             } else {
402377e76abSEd Tanous               return std::tuple<dbus_variant>(property->second);
403377e76abSEd Tanous             }
404377e76abSEd Tanous           }
405377e76abSEd Tanous         });
406377e76abSEd Tanous 
407e3b0bf5bSEd Tanous     properties_iface->register_method(
408e3b0bf5bSEd Tanous         "GetAll", {"interface_name"}, {"properties"},
409e3b0bf5bSEd Tanous         [&](const std::string& interface_name) {
410377e76abSEd Tanous           auto interface_it = interfaces.find(interface_name);
411377e76abSEd Tanous           if (interface_it == interfaces.end()) {
412377e76abSEd Tanous             // Interface not found error
413377e76abSEd Tanous             throw std::runtime_error("interface not found");
414377e76abSEd Tanous           } else {
415377e76abSEd Tanous             std::vector<std::pair<std::string, dbus_variant>> v;
416377e76abSEd Tanous             for (auto& element : properties_iface->get_properties_map()) {
417377e76abSEd Tanous               v.emplace_back(element.first, element.second);
418377e76abSEd Tanous             }
419e3b0bf5bSEd Tanous             return std::tuple<
420e3b0bf5bSEd Tanous                 std::vector<std::pair<std::string, dbus_variant>>>(v);
421377e76abSEd Tanous           }
422377e76abSEd Tanous         });
423377e76abSEd Tanous     properties_iface->register_method(
424e3b0bf5bSEd Tanous         "Set", {"interface_name", "properties_name", "value"}, {},
425377e76abSEd Tanous         [&](const std::string& interface_name, const std::string& property_name,
426377e76abSEd Tanous             const dbus_variant& value) {
427377e76abSEd Tanous           auto interface_it = interfaces.find(interface_name);
428377e76abSEd Tanous           if (interface_it == interfaces.end()) {
429377e76abSEd Tanous             // Interface not found error
430377e76abSEd Tanous             throw std::runtime_error("interface not found");
431377e76abSEd Tanous           } else {
432377e76abSEd Tanous             // Todo, the set propery (signular) interface should support
433*a04118e8SEd Tanous             // handing a variant.  The below is expensive
434377e76abSEd Tanous             std::vector<std::pair<std::string, dbus_variant>> v;
435377e76abSEd Tanous             v.emplace_back(property_name, value);
436377e76abSEd Tanous             interface_it->second->set_properties(v);
437377e76abSEd Tanous             return std::tuple<>();
438377e76abSEd Tanous           }
439377e76abSEd Tanous         });
440e3b0bf5bSEd Tanous 
441e3b0bf5bSEd Tanous     properties_iface->register_signal<
442e3b0bf5bSEd Tanous         std::string, std::vector<std::pair<std::string, dbus_variant>>,
443e3b0bf5bSEd Tanous         std::vector<std::string>>(
444e3b0bf5bSEd Tanous         "PropertiesChanged",
445e3b0bf5bSEd Tanous         {"interface_name", "changed_properties", "invalidated_properties"});
446377e76abSEd Tanous   }
447377e76abSEd Tanous 
448377e76abSEd Tanous   std::shared_ptr<DbusInterface> add_interface(const std::string& name) {
449377e76abSEd Tanous     auto x = std::make_shared<DbusInterface>(name, conn);
450377e76abSEd Tanous     register_interface(x);
451377e76abSEd Tanous     return x;
452377e76abSEd Tanous   }
453377e76abSEd Tanous 
454377e76abSEd Tanous   void register_interface(std::shared_ptr<DbusInterface>& interface) {
455377e76abSEd Tanous     interfaces[interface->get_interface_name()] = interface;
456377e76abSEd Tanous     interface->object_name = object_name;
457377e76abSEd Tanous     const static dbus::endpoint endpoint("", object_name,
458377e76abSEd Tanous                                          "org.freedesktop.DBus.ObjectManager");
459377e76abSEd Tanous 
460e3b0bf5bSEd Tanous     auto m = message::new_signal(endpoint, "InterfacesAdded");
461377e76abSEd Tanous     typedef std::vector<std::pair<std::string, dbus_variant>> properties_dict;
462377e76abSEd Tanous     std::vector<std::pair<std::string, properties_dict>> sig;
463377e76abSEd Tanous     sig.emplace_back(interface->get_interface_name(), properties_dict());
464377e76abSEd Tanous     auto& prop_dict = sig.back().second;
465377e76abSEd Tanous     for (auto& property : interface->get_properties_map()) {
466377e76abSEd Tanous       prop_dict.emplace_back(property);
467377e76abSEd Tanous     }
468377e76abSEd Tanous 
469377e76abSEd Tanous     m.pack(object_name, sig);
470e3b0bf5bSEd Tanous     // TODO(ed)
471e3b0bf5bSEd Tanous     // conn->send(m, std::chrono::seconds(0));
472377e76abSEd Tanous   }
473377e76abSEd Tanous 
474377e76abSEd Tanous   auto get_interfaces() { return interfaces; }
475377e76abSEd Tanous 
476377e76abSEd Tanous   void call(dbus::message& m) {
477377e76abSEd Tanous     auto interface = interfaces.find(m.get_interface());
478377e76abSEd Tanous     if (interface != interfaces.end()) {
479377e76abSEd Tanous       interface->second->call(m);
480377e76abSEd Tanous     }  // TODO(ed) send something when interface doesn't exist?
481377e76abSEd Tanous   }
482377e76abSEd Tanous 
483377e76abSEd Tanous   std::string object_name;
484377e76abSEd Tanous   std::shared_ptr<dbus::connection> conn;
485377e76abSEd Tanous 
486377e76abSEd Tanous   // dbus::filter properties_filter;
487377e76abSEd Tanous   std::shared_ptr<DbusInterface> properties_iface;
488377e76abSEd Tanous 
489e3b0bf5bSEd Tanous   std::shared_ptr<DbusInterface> object_manager_iface;
490e3b0bf5bSEd Tanous 
491377e76abSEd Tanous   std::function<void(boost::system::error_code, message)> callback;
492377e76abSEd Tanous   boost::container::flat_map<std::string, std::shared_ptr<DbusInterface>>
493377e76abSEd Tanous       interfaces;
494377e76abSEd Tanous };
495377e76abSEd Tanous 
496377e76abSEd Tanous class DbusObjectServer {
497377e76abSEd Tanous  public:
498377e76abSEd Tanous   DbusObjectServer(std::shared_ptr<dbus::connection>& conn) : conn(conn) {
499377e76abSEd Tanous     introspect_filter =
500377e76abSEd Tanous         std::make_unique<dbus::filter>(conn, [](dbus::message m) {
501377e76abSEd Tanous           if (m.get_type() != "method_call") {
502377e76abSEd Tanous             return false;
503377e76abSEd Tanous           }
504377e76abSEd Tanous           if (m.get_interface() != "org.freedesktop.DBus.Introspectable") {
505377e76abSEd Tanous             return false;
506377e76abSEd Tanous           }
507377e76abSEd Tanous           if (m.get_member() != "Introspect") {
508377e76abSEd Tanous             return false;
509377e76abSEd Tanous           };
510377e76abSEd Tanous           return true;
511377e76abSEd Tanous         });
512377e76abSEd Tanous 
513377e76abSEd Tanous     introspect_filter->async_dispatch(
514377e76abSEd Tanous         [&](const boost::system::error_code ec, dbus::message m) {
515377e76abSEd Tanous           on_introspect(ec, m);
516377e76abSEd Tanous         });
517377e76abSEd Tanous 
518377e76abSEd Tanous     object_manager_filter =
519377e76abSEd Tanous         std::make_unique<dbus::filter>(conn, [](dbus::message m) {
520377e76abSEd Tanous 
521377e76abSEd Tanous           if (m.get_type() != "method_call") {
522377e76abSEd Tanous             return false;
523377e76abSEd Tanous           }
524377e76abSEd Tanous           if (m.get_interface() != "org.freedesktop.DBus.ObjectManager") {
525377e76abSEd Tanous             return false;
526377e76abSEd Tanous           }
527377e76abSEd Tanous           if (m.get_member() != "GetManagedObjects") {
528377e76abSEd Tanous             return false;
529377e76abSEd Tanous           };
530377e76abSEd Tanous           return true;
531377e76abSEd Tanous         });
532377e76abSEd Tanous 
533377e76abSEd Tanous     object_manager_filter->async_dispatch(
534377e76abSEd Tanous         [&](const boost::system::error_code ec, dbus::message m) {
535377e76abSEd Tanous           on_get_managed_objects(ec, m);
536377e76abSEd Tanous         });
537377e76abSEd Tanous 
538377e76abSEd Tanous     method_filter = std::make_unique<dbus::filter>(conn, [](dbus::message m) {
539377e76abSEd Tanous 
540377e76abSEd Tanous       if (m.get_type() != "method_call") {
541377e76abSEd Tanous         return false;
542377e76abSEd Tanous       }
543377e76abSEd Tanous       return true;
544377e76abSEd Tanous     });
545377e76abSEd Tanous 
546377e76abSEd Tanous     method_filter->async_dispatch(
547377e76abSEd Tanous         [&](const boost::system::error_code ec, dbus::message m) {
548377e76abSEd Tanous           on_method_call(ec, m);
549377e76abSEd Tanous         });
550377e76abSEd Tanous   };
551377e76abSEd Tanous 
552377e76abSEd Tanous   std::shared_ptr<dbus::connection>& get_connection() { return conn; }
553377e76abSEd Tanous   void on_introspect(const boost::system::error_code ec, dbus::message m) {
554377e76abSEd Tanous     auto xml = get_xml_for_path(m.get_path());
555e3b0bf5bSEd Tanous     std::cout << "path: " << m.get_path() << "\n" << xml << "\n";
556377e76abSEd Tanous     auto ret = dbus::message::new_return(m);
557377e76abSEd Tanous     ret.pack(xml);
558377e76abSEd Tanous     conn->async_send(
559377e76abSEd Tanous         ret, [](const boost::system::error_code ec, dbus::message r) {});
560377e76abSEd Tanous 
561377e76abSEd Tanous     introspect_filter->async_dispatch(
562377e76abSEd Tanous         [&](const boost::system::error_code ec, dbus::message m) {
563377e76abSEd Tanous           on_introspect(ec, m);
564377e76abSEd Tanous         });
565377e76abSEd Tanous   }
566377e76abSEd Tanous 
567377e76abSEd Tanous   void on_method_call(const boost::system::error_code ec, dbus::message m) {
568377e76abSEd Tanous     std::cout << "on method call\n";
569377e76abSEd Tanous     if (ec) {
570377e76abSEd Tanous       std::cerr << "on_method_call error: " << ec << "\n";
571377e76abSEd Tanous     } else {
572377e76abSEd Tanous       auto path = m.get_path();
573377e76abSEd Tanous       // TODO(ed) objects should be a map
574377e76abSEd Tanous       for (auto& object : objects) {
575377e76abSEd Tanous         if (object->object_name == path) {
576377e76abSEd Tanous           object->call(m);
577377e76abSEd Tanous           break;
578377e76abSEd Tanous         }
579377e76abSEd Tanous       }
580377e76abSEd Tanous     }
581377e76abSEd Tanous     method_filter->async_dispatch(
582377e76abSEd Tanous         [&](const boost::system::error_code ec, dbus::message m) {
583377e76abSEd Tanous           on_method_call(ec, m);
584377e76abSEd Tanous         });
585377e76abSEd Tanous   }
586377e76abSEd Tanous 
587377e76abSEd Tanous   void on_get_managed_objects(const boost::system::error_code ec,
588377e76abSEd Tanous                               dbus::message m) {
589377e76abSEd Tanous     typedef std::vector<std::pair<std::string, dbus::dbus_variant>>
590377e76abSEd Tanous         properties_dict;
591377e76abSEd Tanous 
592377e76abSEd Tanous     typedef std::vector<std::pair<std::string, properties_dict>>
593377e76abSEd Tanous         interfaces_dict;
594377e76abSEd Tanous 
595377e76abSEd Tanous     std::vector<std::pair<std::string, interfaces_dict>> dict;
596377e76abSEd Tanous 
597377e76abSEd Tanous     for (auto& object : objects) {
598377e76abSEd Tanous       interfaces_dict i;
599377e76abSEd Tanous       for (auto& interface : object->get_interfaces()) {
600377e76abSEd Tanous         properties_dict p;
601377e76abSEd Tanous 
602377e76abSEd Tanous         for (auto& property : interface.second->get_properties_map()) {
603377e76abSEd Tanous           p.push_back(property);
604377e76abSEd Tanous         }
605377e76abSEd Tanous 
606377e76abSEd Tanous         i.emplace_back(interface.second->get_interface_name(), std::move(p));
607377e76abSEd Tanous       }
608377e76abSEd Tanous       dict.emplace_back(object->object_name, std::move(i));
609377e76abSEd Tanous     }
610377e76abSEd Tanous     auto ret = dbus::message::new_return(m);
611377e76abSEd Tanous     ret.pack(dict);
612377e76abSEd Tanous     conn->async_send(
613377e76abSEd Tanous         ret, [](const boost::system::error_code ec, dbus::message r) {});
614377e76abSEd Tanous 
615377e76abSEd Tanous     object_manager_filter->async_dispatch(
616377e76abSEd Tanous         [&](const boost::system::error_code ec, dbus::message m) {
617377e76abSEd Tanous           on_get_managed_objects(ec, m);
618377e76abSEd Tanous         });
619377e76abSEd Tanous   }
620377e76abSEd Tanous 
621377e76abSEd Tanous   std::shared_ptr<DbusObject> add_object(const std::string& name) {
622377e76abSEd Tanous     auto x = std::make_shared<DbusObject>(conn, name);
623377e76abSEd Tanous     register_object(x);
624377e76abSEd Tanous     return x;
625377e76abSEd Tanous   }
626377e76abSEd Tanous 
627377e76abSEd Tanous   void register_object(std::shared_ptr<DbusObject> object) {
628377e76abSEd Tanous     objects.emplace_back(object);
629377e76abSEd Tanous   }
630377e76abSEd Tanous 
631377e76abSEd Tanous   std::string get_xml_for_path(const std::string& path) {
632377e76abSEd Tanous     std::string newpath(path);
633377e76abSEd Tanous 
634377e76abSEd Tanous     if (newpath == "/") {
635377e76abSEd Tanous       newpath.assign("");
636377e76abSEd Tanous     }
637377e76abSEd Tanous 
638377e76abSEd Tanous     boost::container::flat_set<std::string> node_names;
639377e76abSEd Tanous     std::string xml(
640377e76abSEd Tanous         "<!DOCTYPE node PUBLIC "
641377e76abSEd Tanous         "\"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\" "
642377e76abSEd Tanous         "\"http://www.freedesktop.org/standards/dbus/1.0/"
643377e76abSEd Tanous         "introspect.dtd\">\n<node>");
644377e76abSEd Tanous     for (auto& object : objects) {
645377e76abSEd Tanous       std::string& object_name = object->object_name;
646377e76abSEd Tanous       // exact match
647377e76abSEd Tanous       if (object->object_name == newpath) {
648377e76abSEd Tanous         xml +=
649377e76abSEd Tanous             "  <interface name=\"org.freedesktop.DBus.Peer\">"
650377e76abSEd Tanous             "    <method name=\"Ping\"/>"
651377e76abSEd Tanous             "    <method name=\"GetMachineId\">"
652377e76abSEd Tanous             "      <arg type=\"s\" name=\"machine_uuid\" direction=\"out\"/>"
653377e76abSEd Tanous             "    </method>"
654377e76abSEd Tanous             "  </interface>";
655377e76abSEd Tanous 
656377e76abSEd Tanous         xml +=
657377e76abSEd Tanous             "  <interface name=\"org.freedesktop.DBus.ObjectManager\">"
658377e76abSEd Tanous             "    <method name=\"GetManagedObjects\">"
659377e76abSEd Tanous             "      <arg type=\"a{oa{sa{sv}}}\" "
660377e76abSEd Tanous             "           name=\"object_paths_interfaces_and_properties\" "
661377e76abSEd Tanous             "           direction=\"out\"/>"
662377e76abSEd Tanous             "    </method>"
663377e76abSEd Tanous             "    <signal name=\"InterfacesAdded\">"
664377e76abSEd Tanous             "      <arg type=\"o\" name=\"object_path\"/>"
665377e76abSEd Tanous             "      <arg type=\"a{sa{sv}}\" "
666377e76abSEd Tanous             "name=\"interfaces_and_properties\"/>"
667377e76abSEd Tanous             "    </signal>"
668377e76abSEd Tanous             "    <signal name=\"InterfacesRemoved\">"
669377e76abSEd Tanous             "      <arg type=\"o\" name=\"object_path\"/>"
670377e76abSEd Tanous             "      <arg type=\"as\" name=\"interfaces\"/>"
671377e76abSEd Tanous             "    </signal>"
672377e76abSEd Tanous             "  </interface>";
673377e76abSEd Tanous 
674e3b0bf5bSEd Tanous         xml +=
675e3b0bf5bSEd Tanous             "<interface name=\"org.freedesktop.DBus.Introspectable\">"
676e3b0bf5bSEd Tanous             "    <method name=\"Introspect\">"
677e3b0bf5bSEd Tanous             "        <arg type=\"s\" name=\"xml_data\" direction=\"out\"/>"
678e3b0bf5bSEd Tanous             "    </method>"
679e3b0bf5bSEd Tanous             "</interface>";
680e3b0bf5bSEd Tanous 
681377e76abSEd Tanous         for (auto& interface_pair : object->interfaces) {
682377e76abSEd Tanous           xml += "<interface name=\"";
683377e76abSEd Tanous           xml += interface_pair.first;
684377e76abSEd Tanous           xml += "\">";
685377e76abSEd Tanous           for (auto& method : interface_pair.second->get_methods()) {
686377e76abSEd Tanous             xml += "<method name=\"";
687377e76abSEd Tanous             xml += method.first;
688377e76abSEd Tanous             xml += "\">";
689377e76abSEd Tanous             for (auto& arg : method.second->get_args()) {
690377e76abSEd Tanous               xml += "<arg name=\"";
691377e76abSEd Tanous               xml += arg.name;
692377e76abSEd Tanous               xml += "\" type=\"";
693377e76abSEd Tanous               xml += arg.type;
694377e76abSEd Tanous               xml += "\" direction=\"";
695377e76abSEd Tanous               xml += arg.direction;
696377e76abSEd Tanous               xml += "\"/>";
697377e76abSEd Tanous             }
698377e76abSEd Tanous             xml += "</method>";
699377e76abSEd Tanous           }
700377e76abSEd Tanous 
701377e76abSEd Tanous           for (auto& signal : interface_pair.second->get_signals()) {
702377e76abSEd Tanous             xml += "<signal name=\"";
703377e76abSEd Tanous             xml += signal.first;
704377e76abSEd Tanous             xml += "\">";
705377e76abSEd Tanous             for (auto& arg : signal.second->get_args()) {
706377e76abSEd Tanous               xml += "<arg name=\"";
707377e76abSEd Tanous               xml += arg.name;
708377e76abSEd Tanous               xml += "\" type=\"";
709377e76abSEd Tanous               xml += arg.type;
710377e76abSEd Tanous               xml += "\"/>";
711377e76abSEd Tanous             }
712377e76abSEd Tanous 
713377e76abSEd Tanous             xml += "</signal>";
714377e76abSEd Tanous           }
715377e76abSEd Tanous 
716377e76abSEd Tanous           for (auto& property : interface_pair.second->get_properties_map()) {
717377e76abSEd Tanous             xml += "<property name=\"";
718377e76abSEd Tanous             xml += property.first;
719377e76abSEd Tanous             xml += "\" type=\"";
720377e76abSEd Tanous 
721377e76abSEd Tanous             std::string type = std::string(boost::apply_visitor(
722377e76abSEd Tanous                 [&](auto val) {
723377e76abSEd Tanous                   static const auto constexpr sig =
724377e76abSEd Tanous                       element_signature<decltype(val)>::code;
725377e76abSEd Tanous                   return &sig[0];
726377e76abSEd Tanous                 },
727377e76abSEd Tanous                 property.second));
728377e76abSEd Tanous             xml += type;
729e3b0bf5bSEd Tanous             xml += "\" access=\"";
730377e76abSEd Tanous             // TODO direction can be readwrite, read, or write.  Need to
731377e76abSEd Tanous             // make this configurable
732377e76abSEd Tanous             xml += "readwrite";
733377e76abSEd Tanous             xml += "\"/>";
734377e76abSEd Tanous           }
735377e76abSEd Tanous           xml += "</interface>";
736377e76abSEd Tanous         }
737377e76abSEd Tanous       } else if (boost::starts_with(object_name, newpath)) {
738377e76abSEd Tanous         auto slash_index = object_name.find("/", newpath.size() + 1);
739377e76abSEd Tanous         auto subnode = object_name.substr(newpath.size() + 1,
740377e76abSEd Tanous                                           slash_index - newpath.size() - 1);
741377e76abSEd Tanous         if (node_names.find(subnode) == node_names.end()) {
742377e76abSEd Tanous           node_names.insert(subnode);
743377e76abSEd Tanous           xml += "<node name=\"";
744377e76abSEd Tanous           xml += subnode;
745377e76abSEd Tanous           xml += "\">";
746377e76abSEd Tanous           xml += "</node>";
747377e76abSEd Tanous         }
748377e76abSEd Tanous       }
749377e76abSEd Tanous     }
750377e76abSEd Tanous     xml += "</node>";
751377e76abSEd Tanous     return xml;
752377e76abSEd Tanous   }
753377e76abSEd Tanous 
754377e76abSEd Tanous  private:
755377e76abSEd Tanous   std::shared_ptr<dbus::connection> conn;
756377e76abSEd Tanous   std::vector<std::shared_ptr<DbusObject>> objects;
757377e76abSEd Tanous   std::unique_ptr<dbus::filter> introspect_filter;
758377e76abSEd Tanous   std::unique_ptr<dbus::filter> object_manager_filter;
759377e76abSEd Tanous   std::unique_ptr<dbus::filter> method_filter;
760377e76abSEd Tanous };
761377e76abSEd Tanous }
762