From 01dc893e1fd739efc83f93942eada9c66a10a98c Mon Sep 17 00:00:00 2001 From: Teis Johansen Date: Fri, 15 Mar 2019 14:39:34 +0100 Subject: [PATCH] Multi-interface services Sometimes is it necessary to support multiple interfaces on a single D-Bus object. This commit extends the `Skeleton` class template to allow multiple interfaces. It can similarly be useful to simply rely on the built-in support for various FreeDesktop interfaces, so the `Skeleton<>` class template should allow you have _no_ interfaces. --- include/simppl/clientside.h | 18 +- include/simppl/detail/interposer.h | 37 +++ include/simppl/detail/util.h | 19 +- include/simppl/interface.h | 8 +- include/simppl/serverside.h | 28 +- include/simppl/skeleton.h | 42 ++- include/simppl/skeletonbase.h | 83 ++++- include/simppl/stub.h | 23 +- include/simppl/stubbase.h | 8 +- src/clientside.cpp | 4 +- src/serverside.cpp | 14 +- src/skeletonbase.cpp | 506 +++++++++++++++++------------ src/stubbase.cpp | 10 +- src/util.cpp | 87 +++-- tests/CMakeLists.txt | 2 + tests/multi_interface.cpp | 137 ++++++++ tests/no_interface.cpp | 101 ++++++ 17 files changed, 824 insertions(+), 303 deletions(-) create mode 100644 include/simppl/detail/interposer.h create mode 100644 tests/multi_interface.cpp create mode 100644 tests/no_interface.cpp diff --git a/include/simppl/clientside.h b/include/simppl/clientside.h index 9ffc6db..b8972e4 100644 --- a/include/simppl/clientside.h +++ b/include/simppl/clientside.h @@ -46,7 +46,7 @@ struct ClientSignalBase eval_(this, iter); } - ClientSignalBase(const char* name, StubBase* iface); + ClientSignalBase(const char* name, StubBase* iface, int iface_id); const char* name() const { @@ -74,8 +74,8 @@ struct ClientSignal : ClientSignalBase typedef std::function::param_type...)> function_type; - ClientSignal(const char* name, StubBase* iface) - : ClientSignalBase(name, iface) + ClientSignal(const char* name, StubBase* iface, int iface_id) + : ClientSignalBase(name, iface, iface_id) { eval_ = __eval; } @@ -123,7 +123,7 @@ struct ClientPropertyBase typedef void (*eval_type)(ClientPropertyBase*, DBusMessageIter&); - ClientPropertyBase(const char* name, StubBase* iface); + ClientPropertyBase(const char* name, StubBase* iface, int iface_id); void eval(DBusMessageIter& iter) { @@ -156,8 +156,8 @@ struct ClientPropertyWritableMixin : ClientPropertyBase typedef detail::CallbackHolder holder_type; - ClientPropertyWritableMixin(const char* name, StubBase* iface) - : ClientPropertyBase(name, iface) + ClientPropertyWritableMixin(const char* name, StubBase* iface, int iface_id) + : ClientPropertyBase(name, iface, iface_id) { // NOOP } @@ -197,8 +197,8 @@ struct ClientProperty typedef detail::PropertyCallbackHolder, data_type> holder_type; - ClientProperty(const char* name, StubBase* iface) - : base_type(name, iface) + ClientProperty(const char* name, StubBase* iface, int iface_id) + : base_type(name, iface, iface_id) { this->eval_ = __eval; } @@ -307,7 +307,7 @@ struct ClientMethod static_assert(!is_oneway || (is_oneway && std::is_same::value), "oneway check"); - ClientMethod(const char* method_name, StubBase* parent) + ClientMethod(const char* method_name, StubBase* parent, int iface_id) : method_name_(method_name) , parent_(parent) { diff --git a/include/simppl/detail/interposer.h b/include/simppl/detail/interposer.h new file mode 100644 index 0000000..097aaad --- /dev/null +++ b/include/simppl/detail/interposer.h @@ -0,0 +1,37 @@ +#ifndef SIMPPL_DETAIL_INTERPOSER_H +#define SIMPPL_DETAIL_INTERPOSER_H + +namespace simppl +{ + +namespace dbus +{ + +namespace detail +{ + + +template class Method, + template class Signal, + template class Property, + template class, template class, template class, typename> class I, + template class, template class, template class, typename> class... Is> +struct Interposer : I> {}; + +template class Method, + template class Signal, + template class Property, + template class, template class, template class, typename> class I> +struct Interposer<0, AncestorT, Method, Signal, Property, I> : I<0, AncestorT, Method, Signal, Property, AncestorT> {}; + + +} // detail + +} // dbus + +} // simppl + +#endif // SIMPPL_DETAIL_INTERPOSER_H diff --git a/include/simppl/detail/util.h b/include/simppl/detail/util.h index 545d540..410b069 100644 --- a/include/simppl/detail/util.h +++ b/include/simppl/detail/util.h @@ -1,6 +1,8 @@ #ifndef SIMPPL_DETAIL_UTIL_H #define SIMPPL_DETAIL_UTIL_H +#include +#include namespace simppl { @@ -25,10 +27,21 @@ char* create_objectpath(const char* iface, const char* role); char* create_busname(const char* iface, const char* role); /** - * @return dbus compatible interface name from mangled c++ name. Must be - * deleted via delete[]. + * @return dbus compatible interface names from mangled c++ name. */ -char* extract_interface(const char* mangled_iface); +std::vector extract_interfaces(std::size_t iface_count, const char* mangled_iface_list); + +/** + * @return dbus compatible interface name from mangled c++ name. + */ +std::string make_interface_name(const char* begin, const char* end); + +/** + * @return a pointer to the N'th interface name given a pointer to the template + * args of interface N-1 or `nullptr` if there are no more interface names. + */ +const char* find_next_interface(const char* template_args); + } // namespace detail diff --git a/include/simppl/interface.h b/include/simppl/interface.h index 1b1ae1a..6aef2ce 100644 --- a/include/simppl/interface.h +++ b/include/simppl/interface.h @@ -8,14 +8,16 @@ #define INTERFACE(iface) \ - template class Method, \ + template class Method, \ template class Signal, \ template class Property, \ typename BaseT> \ - struct iface : public BaseT + struct iface : BaseT #define INIT(what) \ - what(# what, this) + what(# what, this, InterfaceID) #endif // SIMPPL_INTERFACE_H diff --git a/include/simppl/serverside.h b/include/simppl/serverside.h index 2ed44db..b3868e9 100644 --- a/include/simppl/serverside.h +++ b/include/simppl/serverside.h @@ -52,7 +52,7 @@ struct ServerMethodBase protected: - ServerMethodBase(const char* name, SkeletonBase* iface); + ServerMethodBase(const char* name, SkeletonBase* iface, int iface_id); ~ServerMethodBase(); @@ -65,7 +65,7 @@ struct ServerMethodBase struct ServerSignalBase { - ServerSignalBase(const char* name, SkeletonBase* iface); + ServerSignalBase(const char* name, SkeletonBase* iface, int iface_id); #if SIMPPL_HAVE_INTROSPECTION virtual void introspect(std::ostream& os) const = 0; @@ -77,6 +77,7 @@ struct ServerSignalBase ~ServerSignalBase() = default; const char* name_; + int iface_id_; SkeletonBase* parent_; }; @@ -84,15 +85,15 @@ struct ServerSignalBase template struct ServerSignal : ServerSignalBase { - ServerSignal(const char* name, SkeletonBase* iface) - : ServerSignalBase(name, iface) + ServerSignal(const char* name, SkeletonBase* iface, int iface_id) + : ServerSignalBase(name, iface, iface_id) { // NOOP } void notify(typename CallTraits::param_type... args) { - parent_->send_signal(this->name_, [&](DBusMessageIter& iter){ + parent_->send_signal(this->name_, this->iface_id_, [&](DBusMessageIter& iter){ encode(iter, args...); }); } @@ -132,8 +133,8 @@ struct ServerMethod : ServerMethodBase inline - ServerMethod(const char* name, SkeletonBase* iface) - : ServerMethodBase(name, iface) + ServerMethod(const char* name, SkeletonBase* iface, int iface_id) + : ServerMethodBase(name, iface, iface_id) { eval_ = __eval; } @@ -197,7 +198,7 @@ struct ServerPropertyBase typedef void (*eval_type)(ServerPropertyBase*, DBusMessage*); typedef void (*eval_set_type)(ServerPropertyBase*, DBusMessageIter&); - ServerPropertyBase(const char* name, SkeletonBase* iface); + ServerPropertyBase(const char* name, SkeletonBase* iface, int iface_id); void eval(DBusMessage* msg) { @@ -215,6 +216,7 @@ struct ServerPropertyBase ServerPropertyBase* next_; ///< list hook const char* name_; + int iface_id_; SkeletonBase* parent_; protected: @@ -231,8 +233,8 @@ struct BaseProperty : ServerPropertyBase { typedef std::function cb_type; - BaseProperty(const char* name, SkeletonBase* iface) - : ServerPropertyBase(name, iface) + BaseProperty(const char* name, SkeletonBase* iface, int iface_id) + : ServerPropertyBase(name, iface, iface_id) , t_() { eval_ = __eval; @@ -257,7 +259,7 @@ struct BaseProperty : ServerPropertyBase void notify(const DataT& data) { - this->parent_->send_property_change(this->name_, [this, data](DBusMessageIter& iter){ + this->parent_->send_property_change(this->name_, this->iface_id_, [this, data](DBusMessageIter& iter){ detail::PropertyCodec::encode(iter, data); }); } @@ -314,8 +316,8 @@ struct ServerWritableMixin template struct ServerProperty : BaseProperty, std::conditional, ServerNoopMixin>::type { - ServerProperty(const char* name, SkeletonBase* iface) - : BaseProperty(name, iface) + ServerProperty(const char* name, SkeletonBase* iface, int iface_id) + : BaseProperty(name, iface, iface_id) { if (Flags & ReadWrite) this->eval_set_ = __eval_set; diff --git a/include/simppl/skeleton.h b/include/simppl/skeleton.h index b7e3574..881482c 100644 --- a/include/simppl/skeleton.h +++ b/include/simppl/skeleton.h @@ -3,11 +3,12 @@ #include -#include #include "simppl/skeletonbase.h" #include "simppl/dispatcher.h" #include "simppl/serverside.h" +#include "simppl/typelist.h" +#include "simppl/detail/interposer.h" namespace simppl @@ -20,28 +21,32 @@ namespace dbus void dispatcher_add_skeleton(Dispatcher&, StubBase&); -template class, +template class, - template class, typename> class IfaceT> -struct Skeleton : IfaceT + template class, + template class, + typename> class... Is> +struct Skeleton : detail::Interposer, ServerMethod, ServerSignal, ServerProperty, Is...> { friend struct Dispatcher; - typedef IfaceT interface_type; + using interface_list = make_typelist...>; + + static constexpr std::size_t iface_count = sizeof...(Is); inline Skeleton(Dispatcher& disp, const char* role) - : interface_type() { - this->init(abi::__cxa_demangle(typeid(interface_type).name(), 0, 0, 0), role); + static_assert(iface_count == 1, "Generating bus and object names from a role only works with a single interface"); + this->init(typeid(interface_list).name(), role); dispatcher_add_skeleton(disp, *this); } inline - Skeleton(Dispatcher& disp, const char* busname, const char* objectpath) - : interface_type() + Skeleton(Dispatcher& disp, std::string busname, std::string objectpath) { - this->init(abi::__cxa_demangle(typeid(interface_type).name(), 0, 0, 0), busname, objectpath); + this->init(iface_count, typeid(interface_list).name(), std::move(busname), std::move(objectpath)); dispatcher_add_skeleton(disp, *this); } @@ -51,6 +56,23 @@ struct Skeleton : IfaceT +struct Skeleton<> : detail::SizedSkeletonBase<0> +{ + static constexpr std::size_t iface_count = 0; + + Skeleton(Dispatcher& disp, std::string busname, std::string objectpath) + { + this->init(std::move(busname), std::move(objectpath)); + dispatcher_add_skeleton(disp, *this); + } + +protected: + ServerRequestDescriptor current_request_; +}; + + } // namespace dbus } // namespace simppl diff --git a/include/simppl/skeletonbase.h b/include/simppl/skeletonbase.h index 5b875db..22ab2ca 100644 --- a/include/simppl/skeletonbase.h +++ b/include/simppl/skeletonbase.h @@ -1,7 +1,8 @@ #ifndef SIMPPL_SKELETONBASE_H #define SIMPPL_SKELETONBASE_H -#include +#include +#include #include @@ -26,6 +27,8 @@ struct ServerSignalBase; struct SkeletonBase { + using size_type = std::vector::size_type; + SkeletonBase(const SkeletonBase&) = delete; SkeletonBase& operator=(const SkeletonBase&) = delete; @@ -36,7 +39,7 @@ struct SkeletonBase static DBusHandlerResult method_handler(DBusConnection *connection, DBusMessage *message, void *user_data); - SkeletonBase(); + SkeletonBase(std::size_t iface_count); virtual ~SkeletonBase(); @@ -60,53 +63,99 @@ struct SkeletonBase const ServerRequestDescriptor& current_request() const; inline - const char* iface() const + const std::string& iface(size_type iface_id) const { - return iface_; + // `ifaces_[0]` has the highest ID (e.g. N-1). + return ifaces_[ifaces_.size() - iface_id - 1]; } inline const char* objectpath() const { - return objectpath_; + return objectpath_.c_str(); } inline const char* busname() const { - return busname_; + return busname_.c_str(); } - void send_property_change(const char* prop, std::function&& f); - - void send_signal(const char* signame, std::function&& f); + void send_property_change(const char* prop, int iface_id, std::function&& f); + void send_signal(const char* signame, int iface_id, std::function&& f); protected: + static constexpr int invalid_iface_id = -1; - void init(char* iface, const char* role); - void init(char* iface, const char* busname, const char* objectpath); + void init(const char* mangled_iface_list, const char* role); + void init(size_type iface_count, const char* mangled_iface_list, std::string busname, std::string objectpath); + void init(std::string busname, std::string objectpath); DBusHandlerResult handle_request(DBusMessage* msg); +#ifdef SIMPPL_HAVE_INTROSPECTION + DBusHandlerResult handle_introspect_request(DBusMessage* msg); +#endif + DBusHandlerResult handle_property_request(DBusMessage* msg); + DBusHandlerResult handle_property_get_request(DBusMessage* msg, ServerPropertyBase& property); + DBusHandlerResult handle_property_set_request(DBusMessage* msg, ServerPropertyBase& property, DBusMessageIter& iter); + DBusHandlerResult handle_interface_request(DBusMessage* msg, ServerMethodBase& method); + DBusHandlerResult handle_error(DBusMessage* msg, const char* dbus_error); + + void introspect_interface(std::ostream& os, size_type index) const; + bool has_any_properties() const; + ServerPropertyBase* find_property(int iface_id, const char* name) const; + ServerMethodBase* find_method(int iface_id, const char* name) const; + int find_interface(const char* name) const; + + inline + int find_interface(const std::string& name) const + { + return find_interface(name.c_str()); + } + + inline + ServerPropertyBase* find_property(int iface_id, const std::string& name) const + { + return find_property(iface_id, name.c_str()); + } + + inline + ServerMethodBase* find_method(int iface_id, const std::string& name) const + { + return find_method(iface_id, name.c_str()); + } /// return a session pointer and destruction function if adequate ///virtual std::tuple clientAttached(); - char* iface_; - char* busname_; - char* objectpath_; + std::vector ifaces_; + std::string busname_; + std::string objectpath_; Dispatcher* disp_; ServerRequestDescriptor current_request_; // linked list heads - ServerMethodBase* methods_; - ServerPropertyBase* properties_; + std::vector method_heads_; + std::vector property_heads_; #if SIMPPL_HAVE_INTROSPECTION - ServerSignalBase* signals_; + std::vector signal_heads_; #endif }; + +namespace detail +{ + +template +struct SizedSkeletonBase : SkeletonBase { + SizedSkeletonBase() : SkeletonBase(N) {} +}; + +} // namespace detail + + } // namespace dbus } // namespace simppl diff --git a/include/simppl/stub.h b/include/simppl/stub.h index 2fa6799..2ca0bcf 100644 --- a/include/simppl/stub.h +++ b/include/simppl/stub.h @@ -8,6 +8,7 @@ #include "simppl/stubbase.h" #include "simppl/clientside.h" +#include "simppl/typelist.h" #include "simppl/detail/constants.h" @@ -22,34 +23,36 @@ namespace dbus // forward decl void dispatcher_add_stub(Dispatcher&, StubBase&); - -template class, +// Client stubs do not require multiple interface support - you can simply +// create multiple stubs with different interfaces. +template class, template class, - template class, typename> class IfaceT> -struct Stub : IfaceT + template class, + typename> class IfaceT> +struct Stub : IfaceT<0, StubBase, ClientMethod, ClientSignal, ClientProperty, StubBase> { friend struct Dispatcher; private: - typedef IfaceT interface_type; + using interface_list = make_typelist>; public: inline - Stub(Dispatcher& disp, const char* role) - : interface_type() + Stub(Dispatcher& disp, const char* role) { - this->init(abi::__cxa_demangle(typeid(interface_type).name(), 0, 0, 0), role); + this->init(abi::__cxa_demangle(typeid(interface_list).name(), 0, 0, 0), role); dispatcher_add_stub(disp, *this); } inline Stub(Dispatcher& disp, const char* busname, const char* objectpath) - : interface_type() { - this->init(abi::__cxa_demangle(typeid(interface_type).name(), 0, 0, 0), busname, objectpath); + this->init(abi::__cxa_demangle(typeid(interface_list).name(), 0, 0, 0), busname, objectpath); dispatcher_add_stub(disp, *this); } }; diff --git a/include/simppl/stubbase.h b/include/simppl/stubbase.h index def299c..e4bf642 100644 --- a/include/simppl/stubbase.h +++ b/include/simppl/stubbase.h @@ -3,6 +3,8 @@ #include +#include +#include #include @@ -53,9 +55,9 @@ struct StubBase StubBase(); inline - const char* iface() const + const char* iface(std::size_t index = 0) const { - return iface_; + return ifaces_[index].c_str(); } inline @@ -113,7 +115,7 @@ struct StubBase PendingCall set_property_async(const char* Name, std::function&& f); - char* iface_; + std::vector ifaces_; char* objectpath_; std::string busname_; ConnectionState conn_state_; diff --git a/src/clientside.cpp b/src/clientside.cpp index 549d1c0..3717745 100644 --- a/src/clientside.cpp +++ b/src/clientside.cpp @@ -8,7 +8,7 @@ namespace dbus { -ClientSignalBase::ClientSignalBase(const char* name, StubBase* stub) +ClientSignalBase::ClientSignalBase(const char* name, StubBase* stub, int) : stub_(stub) , name_(name) , next_(nullptr) @@ -17,7 +17,7 @@ ClientSignalBase::ClientSignalBase(const char* name, StubBase* stub) } -ClientPropertyBase::ClientPropertyBase(const char* name, StubBase* stub) +ClientPropertyBase::ClientPropertyBase(const char* name, StubBase* stub, int) : name_(name) , stub_(stub) , next_(nullptr) diff --git a/src/serverside.cpp b/src/serverside.cpp index 504a706..bfbe4f6 100644 --- a/src/serverside.cpp +++ b/src/serverside.cpp @@ -12,10 +12,10 @@ namespace dbus { -ServerMethodBase::ServerMethodBase(const char* name, SkeletonBase* iface) +ServerMethodBase::ServerMethodBase(const char* name, SkeletonBase* iface, int iface_id) : name_(name) { - auto& methods = iface->methods_; + auto& methods = iface->method_heads_[iface_id]; this->next_ = methods; methods = this; } @@ -44,22 +44,24 @@ const char* ServerMethodBase::get_signature() const #endif -ServerPropertyBase::ServerPropertyBase(const char* name, SkeletonBase* iface) +ServerPropertyBase::ServerPropertyBase(const char* name, SkeletonBase* iface, int iface_id) : name_(name) + , iface_id_(iface_id) , parent_(iface) { - auto& properties = iface->properties_; + auto& properties = iface->property_heads_[iface_id]; this->next_ = properties; properties = this; } -ServerSignalBase::ServerSignalBase(const char* name, SkeletonBase* iface) +ServerSignalBase::ServerSignalBase(const char* name, SkeletonBase* iface, int iface_id) : name_(name) + , iface_id_(iface_id) , parent_(iface) { #if SIMPPL_HAVE_INTROSPECTION - auto& signals = iface->signals_; + auto& signals = iface->signal_heads_[iface_id]; this->next_ = signals; signals = this; #endif diff --git a/src/skeletonbase.cpp b/src/skeletonbase.cpp index 2c23e1c..3da2259 100644 --- a/src/skeletonbase.cpp +++ b/src/skeletonbase.cpp @@ -11,7 +11,11 @@ #include "simppl/detail/util.h" +#include + +#include #include +#include namespace simppl @@ -20,6 +24,20 @@ namespace simppl namespace dbus { +namespace detail +{ + +struct FreeDeleter { + template + void operator()(T* o) { + ::free(o); + } +}; + +using DemangledNamePtr = std::unique_ptr; + +} // namespace detail + /*static*/ DBusHandlerResult SkeletonBase::method_handler(DBusConnection* connection, DBusMessage* msg, void *user_data) @@ -29,59 +47,55 @@ DBusHandlerResult SkeletonBase::method_handler(DBusConnection* connection, DBusM } -SkeletonBase::SkeletonBase() - : iface_(nullptr) - , busname_(nullptr) - , objectpath_(nullptr) - , disp_(nullptr) - , methods_(nullptr) - , properties_(nullptr) +SkeletonBase::SkeletonBase(std::size_t iface_count) + : ifaces_(iface_count, std::string()) + , disp_(nullptr) + , method_heads_(iface_count, nullptr) + , property_heads_(iface_count, nullptr) #if SIMPPL_HAVE_INTROSPECTION - , signals_(nullptr) + , signal_heads_(iface_count, nullptr) #endif -{ - // NOOP -} +{} SkeletonBase::~SkeletonBase() { if (disp_) disp_->remove_server(*this); - - delete[] iface_; - delete[] busname_; - delete[] objectpath_; } -void SkeletonBase::init(char* iface, const char* role) +void SkeletonBase::init(const char* mangled_iface_list, const char* role) { assert(role); - iface_ = detail::extract_interface(iface); - - objectpath_ = detail::create_objectpath(iface_, role); - busname_ = detail::create_busname(iface_, role); + detail::DemangledNamePtr iface(abi::__cxa_demangle(mangled_iface_list, 0, 0, 0)); + ifaces_ = detail::extract_interfaces(1, iface.get()); - free(iface); + objectpath_ = detail::create_objectpath(ifaces_[0].c_str(), role); + busname_ = detail::create_busname(ifaces_[0].c_str(), role); } -void SkeletonBase::init(char* iface, const char* busname, const char* objectpath) +void SkeletonBase::init(size_type iface_count, const char* mangled_iface_list, std::string busname, std::string objectpath) { - assert(busname); - assert(objectpath); + assert(busname.length() > 0); + assert(objectpath.length() > 0); - iface_ = detail::extract_interface(iface); + detail::DemangledNamePtr iface(abi::__cxa_demangle(mangled_iface_list, 0, 0, 0)); + ifaces_ = detail::extract_interfaces(iface_count, iface.get()); + busname_ = std::move(busname); + objectpath_ = std::move(objectpath); +} - busname_ = new char[strlen(busname) + 1]; - strcpy(busname_, busname); - objectpath_ = new char[strlen(objectpath)+1]; - strcpy(objectpath_, objectpath); +void SkeletonBase::init(std::string busname, std::string objectpath) +{ + assert(busname.length() > 0); + assert(objectpath.length() > 0); - free(iface); + busname_ = std::move(busname); + objectpath_ = std::move(objectpath); } @@ -176,54 +190,66 @@ const ServerRequestDescriptor& SkeletonBase::current_request() const DBusHandlerResult SkeletonBase::handle_request(DBusMessage* msg) { - const char* method = dbus_message_get_member(msg); - const char* interface = dbus_message_get_interface(msg); + const char* method_name = dbus_message_get_member(msg); + const char* interface_name = dbus_message_get_interface(msg); + +#ifdef SIMPPL_HAVE_INTROSPECTION + if (!strcmp(interface_name, "org.freedesktop.DBus.Introspectable")) + { + return handle_introspect_request(msg); + } +#endif - if (!strcmp(interface, "org.freedesktop.DBus.Introspectable")) - { -#if SIMPPL_HAVE_INTROSPECTION - if (!strcmp(method, "Introspect")) - { - std::ostringstream oss; - - oss << "\n" - "\n" - " \n"; - - auto pm = this->methods_; - while(pm) - { - pm->introspect(oss); - pm = pm->next_; - } - - auto pa = this->properties_; - while(pa) - { - pa->introspect(oss); - pa = pa->next_; - } - - auto ps = this->signals_; - while(ps) - { - ps->introspect(oss); - ps = ps->next_; - } - - // introspectable - oss << " \n" - " \n" - " \n" - " \n" - " \n" - " \n"; - - // attributes - if (this->properties_) - { - oss << - " \n" + if (!strcmp(interface_name, "org.freedesktop.DBus.Properties")) + { + return handle_property_request(msg); + } + + int iface_id = find_interface(interface_name); + if (iface_id == invalid_iface_id) + { + return handle_error(msg, DBUS_ERROR_UNKNOWN_INTERFACE); + } + + ServerMethodBase* method = find_method(iface_id, method_name); + if (!method) + { + return handle_error(msg, DBUS_ERROR_UNKNOWN_METHOD); + } + + return handle_interface_request(msg, *method); +} + + +#ifdef SIMPPL_HAVE_INTROSPECTION +DBusHandlerResult SkeletonBase::handle_introspect_request(DBusMessage* msg) +{ + const char* method = dbus_message_get_member(msg); + + if (strcmp(method, "Introspect") != 0) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + std::ostringstream oss; + + oss << "\n" + "\n"; + + for (size_type i = 0; i < ifaces_.size(); ++i) + { + introspect_interface(oss, i); + } + + // introspectable + oss << " \n" + " \n" + " \n" + " \n" + " \n"; + + // properties + if (has_any_properties()) + { + oss << " \n" " \n" " \n" " \n" @@ -240,137 +266,219 @@ DBusHandlerResult SkeletonBase::handle_request(DBusMessage* msg) " \n" " \n" " \n"; - } + } - oss << "\n"; + oss << "\n"; - DBusMessage* reply = dbus_message_new_method_return(msg); + DBusMessage* reply = dbus_message_new_method_return(msg); - DBusMessageIter iter; - dbus_message_iter_init_append(reply, &iter); + DBusMessageIter iter; + dbus_message_iter_init_append(reply, &iter); - encode(iter, oss.str()); + encode(iter, oss.str()); - dbus_connection_send(disp_->conn_, reply, nullptr); + dbus_connection_send(disp_->conn_, reply, nullptr); + return DBUS_HANDLER_RESULT_HANDLED; +} +#endif // defined(SIMPPL_HAVE_INTROSPECTION) + + +DBusHandlerResult SkeletonBase::handle_property_request(DBusMessage* msg) +{ + DBusMessageIter iter; + std::string interface_name; + std::string property_name; + + try + { + dbus_message_iter_init(msg, &iter); + decode(iter, interface_name, property_name); + } + catch(DecoderError&) + { + return handle_error(msg, DBUS_ERROR_INVALID_ARGS); + } + + int iface_id = find_interface(interface_name); + if (iface_id == invalid_iface_id) + { + return handle_error(msg, DBUS_ERROR_UNKNOWN_INTERFACE); + } + + ServerPropertyBase* property = find_property(iface_id, property_name); + if (!property) + { + return handle_error(msg, DBUS_ERROR_UNKNOWN_PROPERTY); + } + + const char* method = dbus_message_get_member(msg); + if (!strcmp(method, "Get")) + { + return handle_property_get_request(msg, *property); + } + else if(!strcmp(method, "Set")) + { + return handle_property_set_request(msg, *property, iter); + } + else + { + return handle_error(msg, DBUS_ERROR_UNKNOWN_METHOD); + } +} + + +DBusHandlerResult SkeletonBase::handle_property_get_request(DBusMessage* msg, ServerPropertyBase& property) +{ + message_ptr_t response = make_message(dbus_message_new_method_return(msg)); + property.eval(response.get()); + dbus_connection_send(disp_->conn_, response.get(), nullptr); + return DBUS_HANDLER_RESULT_HANDLED; +} + + +DBusHandlerResult SkeletonBase::handle_property_set_request(DBusMessage* msg, ServerPropertyBase& property, DBusMessageIter& iter) +{ + message_ptr_t response = make_message(nullptr); + try + { + property.evalSet(iter); + response = make_message(dbus_message_new_method_return(msg)); + } + catch(simppl::dbus::Error& err) + { + response = err.make_reply_for(*msg); + } + catch(...) + { + simppl::dbus::Error err("simppl.dbus.UnhandledException"); + response = err.make_reply_for(*msg); + } + + dbus_connection_send(disp_->conn_, response.get(), nullptr); + return DBUS_HANDLER_RESULT_HANDLED; +} + + +DBusHandlerResult SkeletonBase::handle_interface_request(DBusMessage* msg, ServerMethodBase& method) +{ + current_request_.set(&method, msg); + + try + { + method.eval(msg); + } + catch(DecoderError&) + { + // don't use `handle_error` as we may need to clear the current request + simppl::dbus::Error err(DBUS_ERROR_INVALID_ARGS); + auto r = err.make_reply_for(*msg); + dbus_connection_send(disp_->conn_, r.get(), nullptr); + } + catch(...) + { + // don't use `handle_error` as we may need to clear the current request + simppl::dbus::Error e("simppl.dbus.UnhandledException"); + auto r = e.make_reply_for(*msg); + dbus_connection_send(disp_->conn_, r.get(), nullptr); + } + + // current_request_ is only valid if no response handler was called + if (current_request_) + current_request_.clear(); + + return DBUS_HANDLER_RESULT_HANDLED; +} + + +DBusHandlerResult SkeletonBase::handle_error(DBusMessage* msg, const char* dbus_error) +{ + simppl::dbus::Error err(dbus_error); + auto r = err.make_reply_for(*msg); + dbus_connection_send(disp_->conn_, r.get(), nullptr); + return DBUS_HANDLER_RESULT_HANDLED; +} + +#if SIMPPL_HAVE_INTROSPECTION +void SkeletonBase::introspect_interface(std::ostream& os, size_type index) const +{ + os << " \n"; + + for (auto pm = method_heads_[index]; pm; pm = pm->next_) + { + pm->introspect(os); + } + + for (auto pp = this->property_heads_[index]; pp; pp = pp->next_) + { + pp->introspect(os); + } + + for (auto ps = this->signal_heads_[index]; ps; ps = ps->next_) + { + ps->introspect(os); + } + + os << " \n"; +} +#endif // + +bool SkeletonBase::has_any_properties() const +{ + const auto first = property_heads_.begin(); + const auto last = property_heads_.end(); + return std::find_if(first, last, [](ServerPropertyBase* ptr) { return ptr != nullptr; }) != last; +} - return DBUS_HANDLER_RESULT_HANDLED; - } -#endif // #if SIMPPL_HAVE_INTROSPECTION - } - else if (!strcmp(interface, "org.freedesktop.DBus.Properties")) - { - if (!strcmp(method, "Get") || !strcmp(method, "Set")) - { - auto p = this->properties_; - - std::string interface; - std::string attribute; - - try - { - DBusMessageIter iter; - dbus_message_iter_init(msg, &iter); - - decode(iter, interface, attribute); - - while(p) - { - if (attribute == p->name_) - { - if (method[0] == 'G') - { - message_ptr_t response = make_message(dbus_message_new_method_return(msg)); - p->eval(response.get()); - - dbus_connection_send(disp_->conn_, response.get(), nullptr); - } - else - { - message_ptr_t response = make_message(nullptr); - - try - { - p->evalSet(iter); - - response = make_message(dbus_message_new_method_return(msg)); - } - catch(simppl::dbus::Error& err) - { - response = err.make_reply_for(*msg); - } - catch(...) - { - simppl::dbus::Error e("simppl.dbus.UnhandledException"); - response = e.make_reply_for(*msg); - } - - dbus_connection_send(disp_->conn_, response.get(), nullptr); - } - - return DBUS_HANDLER_RESULT_HANDLED; - } - - p = p->next_; - } - - std::cerr << "attribute '" << attribute << "' unknown" << std::endl; - } - catch(DecoderError&) - { - simppl::dbus::Error err(DBUS_ERROR_INVALID_ARGS); - auto r = err.make_reply_for(*msg); - dbus_connection_send(disp_->conn_, r.get(), nullptr); - - return DBUS_HANDLER_RESULT_HANDLED; - } - } - } - else - { - auto pm = this->methods_; - while(pm) - { - if (!strcmp(method, pm->name_)) - { - current_request_.set(pm, msg); - - try - { - pm->eval(msg); - } - catch(DecoderError&) - { - simppl::dbus::Error err(DBUS_ERROR_INVALID_ARGS); - auto r = err.make_reply_for(*msg); - dbus_connection_send(disp_->conn_, r.get(), nullptr); - } - catch(...) - { - simppl::dbus::Error e("simppl.dbus.UnhandledException"); - auto r = e.make_reply_for(*msg); - dbus_connection_send(disp_->conn_, r.get(), nullptr); - } - - // current_request_ is only valid if no response handler was called - if (current_request_) - current_request_.clear(); - - return DBUS_HANDLER_RESULT_HANDLED; - } - - pm = pm->next_; - } - - // std::cerr << "method '" << method << "' unknown" << std::endl; - } - return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +int SkeletonBase::find_interface(const char* name) const +{ + for (size_type i = 0; i < ifaces_.size(); ++i) + { + // use strcmp() because ifaces_[i] contains trailing zeros which causes + // operator== to fail. + if (!strcmp(ifaces_[i].c_str(), name)) + { + // `ifaces_[0]` has the highest ID (e.g. N-1), so we need to + // "reverse" the order. + return static_cast(ifaces_.size() - i - 1); + } + } + + return invalid_iface_id; +} + + +ServerPropertyBase* SkeletonBase::find_property(int iface_id, const char* name) const +{ + for (auto pp = property_heads_[iface_id]; pp; pp = pp->next_) + { + if (!strcmp(pp->name_, name)) + { + return pp; + } + } + + return nullptr; +} + + +ServerMethodBase* SkeletonBase::find_method(int iface_id, const char* name) const +{ + for (auto pm = method_heads_[iface_id]; pm; pm = pm->next_) + { + if (!strcmp(pm->name_, name)) + { + return pm; + } + } + + return nullptr; } -void SkeletonBase::send_signal(const char* signame, std::function&& f) +void SkeletonBase::send_signal(const char* signame, int iface_id, std::function&& f) { - message_ptr_t msg = make_message(dbus_message_new_signal(objectpath(), iface(), signame)); + message_ptr_t msg = make_message(dbus_message_new_signal(objectpath(), iface(iface_id).c_str(), signame)); DBusMessageIter iter; dbus_message_iter_init_append(msg.get(), &iter); @@ -381,7 +489,7 @@ void SkeletonBase::send_signal(const char* signame, std::function&& f) +void SkeletonBase::send_property_change(const char* prop, int iface_id, std::function&& f) { static std::vector invalid; @@ -390,7 +498,7 @@ void SkeletonBase::send_property_change(const char* prop, std::functionremove_client(*this); - delete[] iface_; delete[] objectpath_; } @@ -47,7 +45,7 @@ void StubBase::init(char* iface, const char* busname, const char* objectpath) assert(busname); assert(objectpath); - iface_ = detail::extract_interface(iface); + ifaces_ = detail::extract_interfaces(1, iface); objectpath_ = new char[strlen(objectpath)+1]; strcpy(objectpath_, objectpath); @@ -62,9 +60,9 @@ void StubBase::init(char* iface, const char* role) { assert(role); - iface_ = detail::extract_interface(iface); + ifaces_ = detail::extract_interfaces(1, iface); - objectpath_ = detail::create_objectpath(iface_, role); + objectpath_ = detail::create_objectpath(this->iface(), role); busname_.reserve(strlen(this->iface()) + 1 + strlen(role)); busname_ = this->iface(); diff --git a/src/util.cpp b/src/util.cpp index 21be61c..55eca2d 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -45,38 +45,81 @@ char* create_busname(const char* iface, const char* role) return busname; } - -char* extract_interface(const char* mangled_iface) +std::vector extract_interfaces(std::size_t iface_count, const char* mangled_iface_list) { - assert(mangled_iface); + assert(mangled_iface_list); + assert(strcmp(mangled_iface_list, "simppl::make_typelist<") > 0); + + // The `mangled_iface` string will be on the form: + // + // simppl::make_typelist< + // ns1::interface1, + // ns2::interface2 + // > + // + // We are just interested in the "ns1::interface1" part. + std::vector interfaces; + interfaces.reserve(iface_count); + + // skip the "simppl::make_typelist<" part + const char* begin = mangled_iface_list + strlen("simppl::make_typelist<"); + + do { + const char* end = strchr(begin, '<'); + interfaces.push_back(make_interface_name(begin, end)); + begin = find_next_interface(end); + } while (begin); + + return interfaces; +} - size_t len = strstr(mangled_iface, "<") - mangled_iface; +std::string make_interface_name(const char* src, const char* end) +{ + assert(end > src); + + std::string name(end - src, '\0'); + for (auto dst = name.begin(); src != end; ++dst, ++src) { + if (*src == ':') { + *dst = '.'; + ++src; + } else { + *dst = *src; + } + } + return name; +} - char* rc = new char[len+1]; +const char* find_next_interface(const char* s) { + assert(s && "input cannot be null"); + assert(*s == '<' && "expected '<'"); - strncpy(rc, mangled_iface, len); - rc[len] = '\0'; + ++s; + int level = 1; - // remove '::' separation in favour of '.' separation - char *readp = rc, *writep = rc; - while(*readp) - { - if (*readp == ':') - { - *writep++ = '.'; - readp += 2; + for (; s && level > 0; ++s) { + if (*s == '<') { + ++level; + } else if (*s == '>') { + --level; } - else - *writep++ = *readp++; } - // terminate - *writep = '\0'; - - return rc; + assert(level == 0 && "unbalanced angle brackets"); + for (; isspace(*s); ++s) + ; + + if (*s == ',') { + for (++s; isspace(*s); ++s) + ; + return s; + } else if (*s == '>') { + return nullptr; + } else { + assert(false && "expected ',' or '>'"); + return nullptr; + } } - } // namespace detail } // namespace dbus diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2098638..507354a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -18,6 +18,8 @@ add_executable(unittests variant.cpp buffer.cpp serialization.cpp + multi_interface.cpp + no_interface.cpp ) # This is so you can do 'make test' to see all your tests run, instead of diff --git a/tests/multi_interface.cpp b/tests/multi_interface.cpp new file mode 100644 index 0000000..9db00e3 --- /dev/null +++ b/tests/multi_interface.cpp @@ -0,0 +1,137 @@ +#include + +#include "simppl/stub.h" +#include "simppl/skeleton.h" +#include "simppl/dispatcher.h" +#include "simppl/interface.h" + +#include +#include + + +using simppl::dbus::in; +using simppl::dbus::out; + + +namespace test +{ + + +INTERFACE(Adder) +{ + Method, in, out> Add; + + Adder() + : INIT(Add) + {} +}; + + +INTERFACE(Multiplier) +{ + Method, in, out> Multiply; + + Multiplier() + : INIT(Multiply) + {} +}; + + +} // namespace test + + +namespace +{ + + +class MathServer { +public: + MathServer() + : disp_("bus:session") + , math_(disp_) + { + worker_ = std::thread([this] { + disp_.run(); + }); + } + + ~MathServer() { + disp_.stop(); + worker_.join(); + } + +private: + struct MathService : simppl::dbus::Skeleton + { + MathService(simppl::dbus::Dispatcher& d) + : simppl::dbus::Skeleton(d, "simppl.test", "/") + { + Add >> [this] (int a, int b) { + respond_with(Add(a + b)); + }; + + Multiply >> [this] (int a, int b) { + respond_with(Multiply(a * b)); + }; + } + }; + + simppl::dbus::Dispatcher disp_; + MathService math_; + std::thread worker_; +}; + + +} // anonymous namespace + + +TEST(MultiInterface, adder_interface) +{ + struct AdderClient : simppl::dbus::Stub + { + AdderClient(simppl::dbus::Dispatcher& d) + : simppl::dbus::Stub(d, "simppl.test", "/") + { + connected >> [this](simppl::dbus::ConnectionState s) { + EXPECT_EQ(simppl::dbus::ConnectionState::Connected, s); + + Add.async(1, 1) >> [&](simppl::dbus::CallState state, int result) { + EXPECT_TRUE(static_cast(state)); + EXPECT_EQ(result, 2); + disp().stop(); + }; + }; + } + }; + + MathServer server; + simppl::dbus::Dispatcher disp("bus:session"); + AdderClient client(disp); + disp.run(); +} + + +TEST(MultiInterface, multiplier_interface) +{ + struct MultiplierClient : simppl::dbus::Stub + { + MultiplierClient(simppl::dbus::Dispatcher& d) + : simppl::dbus::Stub(d, "simppl.test", "/") + { + connected >> [this](simppl::dbus::ConnectionState s) { + EXPECT_EQ(simppl::dbus::ConnectionState::Connected, s); + + Multiply.async(1, 1) >> [&](simppl::dbus::CallState state, int result) { + EXPECT_TRUE(static_cast(state)); + EXPECT_EQ(result, 1); + disp().stop(); + }; + }; + } + }; + + MathServer server; + simppl::dbus::Dispatcher disp("bus:session"); + MultiplierClient client(disp); + disp.run(); +} diff --git a/tests/no_interface.cpp b/tests/no_interface.cpp new file mode 100644 index 0000000..ee44a7a --- /dev/null +++ b/tests/no_interface.cpp @@ -0,0 +1,101 @@ +#include + +#include "simppl/stub.h" +#include "simppl/skeleton.h" +#include "simppl/dispatcher.h" +#include "simppl/interface.h" +#include "simppl/string.h" + +#include +#include + + +using simppl::dbus::out; + + +namespace org +{ + +namespace freedesktop +{ + +namespace DBus +{ + +INTERFACE(Introspectable) +{ + Method> Introspect; + + Introspectable() + : INIT(Introspect) + {} +}; + +} // namespace DBus + +} // namespace freedesktop + +} // namespace org + + +namespace +{ + + +class NoInterfaceServer { +public: + NoInterfaceServer() + : disp_("bus:session") + , empty_(disp_, "simppl.test", "/") + { + worker_ = std::thread([this] { + disp_.run(); + }); + } + + ~NoInterfaceServer() { + disp_.stop(); + worker_.join(); + } + +private: + simppl::dbus::Dispatcher disp_; + simppl::dbus::Skeleton<> empty_; + std::thread worker_; +}; + + +} // anonymous namespace + + +TEST(NoInterface, introspect) +{ + struct IntrospectClient : simppl::dbus::Stub + { + IntrospectClient(simppl::dbus::Dispatcher& d) + : simppl::dbus::Stub(d, "simppl.test", "/") + { + connected >> [this](simppl::dbus::ConnectionState s) { + EXPECT_EQ(simppl::dbus::ConnectionState::Connected, s); + + Introspect.async() >> [&](simppl::dbus::CallState state, std::string result) { + EXPECT_TRUE(static_cast(state)); + EXPECT_EQ(result, "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"); + disp().stop(); + }; + }; + } + }; + + NoInterfaceServer server; + simppl::dbus::Dispatcher disp("bus:session"); + IntrospectClient client(disp); + disp.run(); +}