#include #include #include #include #include #include #include #include #include namespace dbus { struct DbusArgument { DbusArgument(const std::string& direction, const std::string& name, const std::string& type) : direction(direction), name(name), type(type){}; std::string direction; std::string name; std::string type; }; class DbusMethod { public: DbusMethod(const std::string& name, std::shared_ptr& conn) : name(name), conn(conn){}; virtual void call(dbus::message& m){}; virtual std::vector get_args() { return {}; }; std::string name; std::shared_ptr conn; }; enum class UpdateType { VALUE_CHANGE_ONLY, FORCE }; // primary template. template struct function_traits : function_traits {}; // partial specialization for function type template struct function_traits { using result_type = R; using argument_types = std::tuple; using decayed_arg_types = std::tuple::type...>; }; // partial specialization for function pointer template struct function_traits { using result_type = R; using argument_types = std::tuple; using decayed_arg_types = std::tuple::type...>; }; // partial specialization for std::function template struct function_traits> { using result_type = R; using argument_types = std::tuple; using decayed_arg_types = std::tuple::type...>; }; // partial specialization for pointer-to-member-function (i.e., operator()'s) template struct function_traits { using result_type = R; using argument_types = std::tuple; using decayed_arg_types = std::tuple::type...>; }; template struct function_traits { using result_type = R; using argument_types = std::tuple; using decayed_arg_types = std::tuple::type...>; }; template constexpr auto index_apply_impl(F f, std::index_sequence) { return f(std::integral_constant{}...); } template constexpr auto index_apply(F f) { return index_apply_impl(f, std::make_index_sequence{}); } template constexpr auto apply(Tuple& t, F f) { return index_apply{}>( [&](auto... Is) { return f(std::get(t)...); }); } template constexpr bool unpack_into_tuple(Tuple& t, dbus::message& m) { return index_apply{}>( [&](auto... Is) { return m.unpack(std::get(t)...); }); } // Specialization for empty tuples. No need to unpack if no arguments constexpr bool unpack_into_tuple(std::tuple<>& t, dbus::message& m) { return true; } template constexpr bool pack_tuple_into_msg(std::tuple& t, dbus::message& m) { return index_apply>{}>( [&](auto... Is) { return m.pack(std::get(t)...); }); } // Specialization for empty tuples. No need to pack if no arguments constexpr bool pack_tuple_into_msg(std::tuple<>& t, dbus::message& m) { return true; } // Specialization for single types. Used when callbacks simply return one value template constexpr bool pack_tuple_into_msg(Element& t, dbus::message& m) { return m.pack(t); } // Base case for when I == the size of the tuple args. Does nothing, as we // should be done template inline typename std::enable_if::type arg_types( bool in, std::tuple& t, std::vector& v, const std::vector* arg_names = nullptr) {} // Case for when I < the size of tuple args. Unpacks the tuple type into the // dbusargument object and names it appropriately. template inline typename std::enable_if < I::type arg_types( bool in, std::tuple& t, std::vector& v, const std::vector* arg_names = nullptr) { typedef typename std::tuple_element>::type element_type; auto constexpr sig = element_signature::code; std::string name; std::string direction; if (arg_names == nullptr || arg_names->size() <= I) { if (in) { name = "arg_" + std::to_string(I); } else { name = "out_" + std::to_string(I); } } else { name = (*arg_names)[I]; } v.emplace_back(in ? "in" : "out", name, &sig[0]); arg_types(in, t, v, arg_names); } // Special case for handling raw arguments returned from handlers. Because they // don't get stored in a tuple, special handling is neccesary template void arg_types(bool in, Element& t, std::vector& v, const std::vector* arg_names = nullptr) { auto constexpr sig = element_signature::code; std::string name; if (arg_names == nullptr || arg_names->size() < 1) { name.assign("arg_0"); } else { name = (*arg_names)[0]; } v.emplace_back(in ? "in" : "out", name, &sig[0]); } template class LambdaDbusMethod : public DbusMethod { public: typedef function_traits traits; typedef typename traits::decayed_arg_types InputTupleType; typedef typename traits::result_type ResultType; LambdaDbusMethod(const std::string name, std::shared_ptr& conn, Handler h) : DbusMethod(name, conn), h(std::move(h)) { InputTupleType t; arg_types(true, t, args); ResultType o; arg_types(false, o, args); } LambdaDbusMethod(const std::string& name, const std::vector& input_arg_names, const std::vector& output_arg_names, std::shared_ptr& conn, Handler h) : DbusMethod(name, conn), h(std::move(h)) { InputTupleType t; arg_types(true, t, args, &input_arg_names); ResultType o; arg_types(false, o, args, &output_arg_names); } void call(dbus::message& m) override { InputTupleType input_args; if (unpack_into_tuple(input_args, m) == false) { auto err = dbus::message::new_error(m, DBUS_ERROR_INVALID_ARGS, ""); conn->send(err, std::chrono::seconds(0)); return; } try { ResultType r = apply(input_args, h); auto ret = dbus::message::new_return(m); if (pack_tuple_into_msg(r, ret) == false) { auto err = dbus::message::new_error( m, DBUS_ERROR_FAILED, "Handler had issue when packing response"); conn->send(err, std::chrono::seconds(0)); return; } conn->send(ret, std::chrono::seconds(0)); } catch (...) { auto err = dbus::message::new_error( m, DBUS_ERROR_FAILED, "Handler threw exception while handling request."); conn->send(err, std::chrono::seconds(0)); return; } }; std::vector get_args() override { return args; }; Handler h; std::vector args; }; class DbusSignal { public: DbusSignal(){}; virtual std::vector get_args() { return {}; } }; template class DbusTemplateSignal : public DbusSignal { public: DbusTemplateSignal(const std::string& name, const std::string& object_name, const std::string& interface_name, const std::vector& names, std::shared_ptr& conn) : DbusSignal(), name(name), object_name(object_name), interface_name(interface_name), conn(conn) { std::tuple tu; arg_types(true, tu, args, &names); }; void send(Args&...) { dbus::endpoint endpoint("", object_name, interface_name); auto m = dbus::message::new_signal(endpoint, name); conn->send(m, std::chrono::seconds(0)); } std::vector get_args() override { return args; }; std::vector args; std::string name; std::string object_name; std::string interface_name; std::shared_ptr conn; }; class DbusInterface { public: DbusInterface(std::string interface_name, std::shared_ptr& conn) : interface_name(std::move(interface_name)), conn(conn) {} virtual boost::container::flat_map> get_signals() { return dbus_signals; }; virtual boost::container::flat_map> get_methods() { return dbus_methods; }; virtual std::string get_interface_name() { return interface_name; }; virtual const boost::container::flat_map get_properties_map() { return properties_map; }; template void set_property(const std::string& property_name, const VALUE_TYPE value, UpdateType update_mode = UpdateType::VALUE_CHANGE_ONLY) { // Construct a change vector of length 1. if this set_properties is ever // templated for any type, we could probably swap with with a // std::array std::vector> v; v.emplace_back(property_name, value); set_properties(v, update_mode); } void set_properties( const std::vector>& v, const UpdateType update_mode = UpdateType::VALUE_CHANGE_ONLY) { // TODO(ed) generalize this interface for all "map like" types, basically // anything that will return a const iterator of std::pair std::vector> updates; updates.reserve(v.size()); if (update_mode == UpdateType::FORCE) { updates = v; } else { for (auto& property : v) { auto property_map_it = properties_map.find(property.first); if (property_map_it != properties_map.end()) { // Property exists in map if (property_map_it->second != property.second) { properties_map[property.first] = property.second; // if value has changed since last set updates.emplace_back(*property_map_it); } } else { // property doesn't exist, must be new properties_map[property.first] = property.second; updates.emplace_back(property.first, property.second); } } } const static dbus::endpoint endpoint("org.freedesktop.DBus", object_name, "org.freedesktop.DBus.Properties"); auto m = dbus::message::new_signal(endpoint, "PropertiesChanged"); static const std::vector empty; m.pack(get_interface_name(), updates, empty); // TODO(ed) make sure this doesn't block conn->async_send( m, [](const boost::system::error_code ec, dbus::message r) {}); } void register_method(std::shared_ptr method) { dbus_methods.emplace(method->name, method); } template void register_method(const std::string& name, Handler method) { dbus_methods.emplace(name, new LambdaDbusMethod(name, conn, method)); } template void register_method(const std::string& name, const std::vector& input_arg_names, const std::vector& output_arg_names, Handler method) { dbus_methods.emplace( name, new LambdaDbusMethod(name, input_arg_names, output_arg_names, conn, method)); } template std::shared_ptr register_signal( const std::string& name, const std::vector arg_names) { auto it = dbus_signals.emplace( name, new DbusTemplateSignal(name, object_name, interface_name, arg_names, conn)); return it.first->second; } void call(dbus::message& m) { std::string method_name = m.get_member(); auto method = dbus_methods.find(method_name); if (method != dbus_methods.end()) { method->second->call(m); } // TODO(ed) send something when method doesn't exist? } std::string object_name; std::string interface_name; boost::container::flat_map> dbus_methods; boost::container::flat_map> dbus_signals; boost::container::flat_map properties_map; std::shared_ptr conn; }; class DbusObject { public: DbusObject(std::shared_ptr conn, std::string object_name) : object_name(std::move(object_name)), conn(conn) { properties_iface = add_interface("org.freedesktop.DBus.Properties"); properties_iface->register_method( "Get", {"interface_name", "properties_name"}, {"value"}, [&](const std::string& interface_name, const std::string& property_name) { auto interface_it = interfaces.find(interface_name); if (interface_it == interfaces.end()) { // Interface not found error throw std::runtime_error("interface not found"); } else { auto& properties_map = interface_it->second->get_properties_map(); auto property = properties_map.find(property_name); if (property == properties_map.end()) { // TODO(ed) property not found error throw std::runtime_error("property not found"); } else { return std::tuple(property->second); } } }); properties_iface->register_method( "GetAll", {"interface_name"}, {"properties"}, [&](const std::string& interface_name) { auto interface_it = interfaces.find(interface_name); if (interface_it == interfaces.end()) { // Interface not found error throw std::runtime_error("interface not found"); } else { std::vector> v; for (auto& element : properties_iface->get_properties_map()) { v.emplace_back(element.first, element.second); } return std::tuple< std::vector>>(v); } }); properties_iface->register_method( "Set", {"interface_name", "properties_name", "value"}, {}, [&](const std::string& interface_name, const std::string& property_name, const dbus_variant& value) { auto interface_it = interfaces.find(interface_name); if (interface_it == interfaces.end()) { // Interface not found error throw std::runtime_error("interface not found"); } else { // Todo, the set propery (signular) interface should support // handing a variant. The below is expensive std::vector> v; v.emplace_back(property_name, value); interface_it->second->set_properties(v); return std::tuple<>(); } }); properties_iface->register_signal< std::string, std::vector>, std::vector>( "PropertiesChanged", {"interface_name", "changed_properties", "invalidated_properties"}); } std::shared_ptr add_interface(const std::string& name) { auto x = std::make_shared(name, conn); register_interface(x); return x; } void register_interface(std::shared_ptr& interface) { interfaces[interface->get_interface_name()] = interface; interface->object_name = object_name; const static dbus::endpoint endpoint("", object_name, "org.freedesktop.DBus.ObjectManager"); auto m = message::new_signal(endpoint, "InterfacesAdded"); typedef std::vector> properties_dict; std::vector> sig; sig.emplace_back(interface->get_interface_name(), properties_dict()); auto& prop_dict = sig.back().second; for (auto& property : interface->get_properties_map()) { prop_dict.emplace_back(property); } m.pack(object_name, sig); // TODO(ed) // conn->send(m, std::chrono::seconds(0)); } auto get_interfaces() { return interfaces; } void call(dbus::message& m) { auto interface = interfaces.find(m.get_interface()); if (interface != interfaces.end()) { interface->second->call(m); } // TODO(ed) send something when interface doesn't exist? } std::string object_name; std::shared_ptr conn; // dbus::filter properties_filter; std::shared_ptr properties_iface; std::shared_ptr object_manager_iface; std::function callback; boost::container::flat_map> interfaces; }; class DbusObjectServer { public: DbusObjectServer(std::shared_ptr& conn) : conn(conn) { introspect_filter = std::make_unique(conn, [](dbus::message m) { if (m.get_type() != "method_call") { return false; } if (m.get_interface() != "org.freedesktop.DBus.Introspectable") { return false; } if (m.get_member() != "Introspect") { return false; }; return true; }); introspect_filter->async_dispatch( [&](const boost::system::error_code ec, dbus::message m) { on_introspect(ec, m); }); object_manager_filter = std::make_unique(conn, [](dbus::message m) { if (m.get_type() != "method_call") { return false; } if (m.get_interface() != "org.freedesktop.DBus.ObjectManager") { return false; } if (m.get_member() != "GetManagedObjects") { return false; }; return true; }); object_manager_filter->async_dispatch( [&](const boost::system::error_code ec, dbus::message m) { on_get_managed_objects(ec, m); }); method_filter = std::make_unique(conn, [](dbus::message m) { if (m.get_type() != "method_call") { return false; } return true; }); method_filter->async_dispatch( [&](const boost::system::error_code ec, dbus::message m) { on_method_call(ec, m); }); }; std::shared_ptr& get_connection() { return conn; } void on_introspect(const boost::system::error_code ec, dbus::message m) { auto xml = get_xml_for_path(m.get_path()); std::cout << "path: " << m.get_path() << "\n" << xml << "\n"; auto ret = dbus::message::new_return(m); ret.pack(xml); conn->async_send( ret, [](const boost::system::error_code ec, dbus::message r) {}); introspect_filter->async_dispatch( [&](const boost::system::error_code ec, dbus::message m) { on_introspect(ec, m); }); } void on_method_call(const boost::system::error_code ec, dbus::message m) { std::cout << "on method call\n"; if (ec) { std::cerr << "on_method_call error: " << ec << "\n"; } else { auto path = m.get_path(); // TODO(ed) objects should be a map for (auto& object : objects) { if (object->object_name == path) { object->call(m); break; } } } method_filter->async_dispatch( [&](const boost::system::error_code ec, dbus::message m) { on_method_call(ec, m); }); } void on_get_managed_objects(const boost::system::error_code ec, dbus::message m) { typedef std::vector> properties_dict; typedef std::vector> interfaces_dict; std::vector> dict; for (auto& object : objects) { interfaces_dict i; for (auto& interface : object->get_interfaces()) { properties_dict p; for (auto& property : interface.second->get_properties_map()) { p.push_back(property); } i.emplace_back(interface.second->get_interface_name(), std::move(p)); } dict.emplace_back(object->object_name, std::move(i)); } auto ret = dbus::message::new_return(m); ret.pack(dict); conn->async_send( ret, [](const boost::system::error_code ec, dbus::message r) {}); object_manager_filter->async_dispatch( [&](const boost::system::error_code ec, dbus::message m) { on_get_managed_objects(ec, m); }); } std::shared_ptr add_object(const std::string& name) { auto x = std::make_shared(conn, name); register_object(x); return x; } void register_object(std::shared_ptr object) { objects.emplace_back(object); } std::string get_xml_for_path(const std::string& path) { std::string newpath(path); if (newpath == "/") { newpath.assign(""); } boost::container::flat_set node_names; std::string xml( "\n"); for (auto& object : objects) { std::string& object_name = object->object_name; // exact match if (object->object_name == newpath) { xml += " " " " " " " " " " " "; xml += " " " " " " " " " " " " " " " " " " " " " " " " " "; xml += "" " " " " " " ""; for (auto& interface_pair : object->interfaces) { xml += ""; for (auto& method : interface_pair.second->get_methods()) { xml += ""; for (auto& arg : method.second->get_args()) { xml += ""; } xml += ""; } for (auto& signal : interface_pair.second->get_signals()) { xml += ""; for (auto& arg : signal.second->get_args()) { xml += ""; } xml += ""; } for (auto& property : interface_pair.second->get_properties_map()) { xml += "::code; return &sig[0]; }, property.second)); xml += type; xml += "\" access=\""; // TODO direction can be readwrite, read, or write. Need to // make this configurable xml += "readwrite"; xml += "\"/>"; } xml += ""; } } else if (boost::starts_with(object_name, newpath)) { auto slash_index = object_name.find("/", newpath.size() + 1); auto subnode = object_name.substr(newpath.size() + 1, slash_index - newpath.size() - 1); if (node_names.find(subnode) == node_names.end()) { node_names.insert(subnode); xml += ""; xml += ""; } } } xml += ""; return xml; } private: std::shared_ptr conn; std::vector> objects; std::unique_ptr introspect_filter; std::unique_ptr object_manager_filter; std::unique_ptr method_filter; }; }