diff --git a/CMakeLists.txt b/CMakeLists.txt index 720f334..79b84bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,20 +3,19 @@ # or an add_subdirectory. # cmake_minimum_required(VERSION 3.5) -project(simppl VERSION 0.3.0) +project(simppl VERSION 0.4.0) # Configuration specific to being the main project if(PROJECT_NAME STREQUAL CMAKE_PROJECT_NAME) set(CMAKE_VERBOSE_MAKEFILE ON) set(SIMPPL_NOT_SUBPROJECT ON) - - include(CTest) endif() # Reference the FindDBus module until an upstream one is available list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake) -set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD 17) + if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) endif() @@ -24,13 +23,19 @@ endif() # enable introspection by default option(HAVE_INTROSPECTION "Have introspection" ON) include(CMakeDependentOption) + cmake_dependent_option(SIMPPL_BUILD_TESTS "Build tests" ON "SIMPPL_NOT_SUBPROJECT" OFF ) + cmake_dependent_option(SIMPPL_BUILD_EXAMPLES "Build examples" ON "SIMPPL_NOT_SUBPROJECT" OFF ) +if(SIMPPL_BUILD_TESTS) + include(CTest) +endif() + # enable threading set(CMAKE_THREAD_PREFER_PTHREAD TRUE) set(THREADS_PREFER_PTHREAD_FLAG TRUE) @@ -39,8 +44,8 @@ find_package(Threads REQUIRED) find_package(DBus REQUIRED) find_package(Boost) + add_library(simppl SHARED - src/callstate.cpp src/dispatcher.cpp src/error.cpp src/serverresponseholder.cpp @@ -58,6 +63,8 @@ add_library(simppl SHARED src/clientside.cpp src/serialization.cpp src/bool.cpp + src/holders.cpp + src/properties.cpp ) # Provide a namespaced alias add_library(Simppl::simppl ALIAS simppl) @@ -74,14 +81,16 @@ target_include_directories(simppl $ ) -target_compile_options(simppl PRIVATE -Werror -Wall) +target_compile_options(simppl PRIVATE -Werror -Wall -Wno-error=deprecated-declarations) target_link_libraries(simppl PUBLIC DBUS::DBUS ${CMAKE_THREAD_LIBS_INIT} ) # Build a set of examples -add_subdirectory(examples) +if(SIMPPL_BUILD_EXAMPLES) + add_subdirectory(examples) +endif() if(SIMPPL_BUILD_TESTS AND BUILD_TESTING) add_subdirectory(tests) @@ -106,6 +115,9 @@ if(SIMPPL_NOT_SUBPROJECT) NAMESPACE Simppl:: DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/simppl ) + install(DIRECTORY include/simppl + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + ) configure_package_config_file(cmake/SimpplConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/SimpplConfig.cmake diff --git a/README.md b/README.md index b516c9e..454841f 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Development libraries of libdbus-1 are also needed for a successful build. ## Compilers -I only developed on GCC >=4.9. The code uses features of upto C++14 and +I only developed on GCC >=4.9. The code uses features of upto C++14 and g++ specific demangling functions for class symbols. Therefore, I do not expect the code to work on any other compiler family without appropriate code adaptions. @@ -122,7 +122,7 @@ provided under This mapping is done automatically by simppl, for servers this is currently fix. But clients may connect to any bus/objectpath layout in order to connect -any DBus service. This also means that only one interface can be provided +any DBus service. This also means that only one interface can be provided by a distinct objectpath, at least other than the properties interface needed for providing service properties. But we will ignore signals and properties for now and continue with our EchoService. Let's instantiate the @@ -160,7 +160,7 @@ Let's provide an implementation for the echo method: ``` Now, simppl will receive the echo request, unmarshall the argument and -forward the request to the provided lambda function (of course, you may +forward the request to the provided lambda function (of course, you may also bind any other function via std::bind). But there is still no response yet. Let's send a response back to the client: @@ -188,7 +188,7 @@ That's simppl, isn't it? Setup the eventloop and the server is finished: { simppl::dbus::Dispatcher disp("bus::session"); MyEcho instance(disp); - + disp.run(); return EXIT_SUCCESS; @@ -259,7 +259,7 @@ Event loop driven clients always get callbacks called from the dbus runtime when any event occurs. The initial event for a client is the connected event which will be emitted when the server registers itself on the bus (remember the busname). After being connected, the client may start an -asynchronous method like in the example above. The method response callbacks +asynchronous method like in the example above. The method response callbacks can be any function object fullfilling the response signature, the preferred way nowadays in a C++ lambda. The main program is as simple as in the blocking example: @@ -271,7 +271,7 @@ blocking example: simppl::dbus::Dispatcher disp("bus:session"); MyEchoClient client(disp); - + disp.run(); return EXIT_SUCCESS; @@ -327,7 +327,7 @@ the structure, i.e. the structure can be used as any simple data type: simppl::dbus::Dispatcher disp("bus:session"); simppl::dbus::Stub teststub(disp, "test"); - + Data d({ 42, "Hallo", ... }); int i_ret; @@ -346,9 +346,9 @@ this blocking call? Isn't that cool? The signal in the example above is only sensefully usable in an event loop driven client. There is a slight difference between the properties concepts of simppl and dbus for now: the changed notification -will not be part of the Properties interface so only get and set may -currently be used to access properties of non-simppl services. But this is -nothing that cannot be changed in future. See how a client will typically +will not be part of the Properties interface so only get and set may +currently be used to access properties of non-simppl services. But this is +nothing that cannot be changed in future. See how a client will typically register for update notifications in order to receive changes on the properties on server side. See the clients connected callback: @@ -367,7 +367,7 @@ on server side. See the clients connected callback: { ... }; - + data.attach() >> [](const Data& d) { // first callback is due to the attach! @@ -385,9 +385,9 @@ on server side. See the clients connected callback: ``` Properties on server side can be either implemented by providing a -callback function to be called whenever the property is requested by a +callback function to be called whenever the property is requested by a client or the property value can be stored within the server instance. -Therefore, you have to decide and initialize the property within the +Therefore, you have to decide and initialize the property within the server's constructor. Let's see the difference: ```c++ @@ -396,22 +396,97 @@ server's constructor. Let's see the difference: MyComplexServer(simppl::dbus::Dispatcher& disp) : simppl::dbus::Skeleton(disp, "test") { - // either using the callback version... + // either using the callback version... data.on_read([](){ return Data({ 42, "Hallo", ... }); }); - + // ... or keep a copy in the server instance data = Data({ 42, "Hallo", ... }); } }; ``` - + The notification of property changes is either done by calling the properties -notify(...) method in case the property is initialized for callback access -or by just assigning a new value to the stored property instance. +notify(...) method in case the property is initialized for callback access +or by just assigning a new value to the stored property instance. The version of property access to be used in your server is completely -transparent for the client and depends on your use-case. +transparent for the client and depends on your use-case. + +Simppl properties also support the GetAll interface. See unittest for an example. + +User-defined exceptions may also be transferred. This feature is currently +restricted to method calls. To define an error you have to use the boost +fusion binding: + +```c++ + namespace test + { + + class MyError : simppl::dbus::Error + { + public: + + // needed for client side code generation + MyError() = default; + + // used to throw error on server side + MyError(int rc) + : simppl::dbus::Error("My.Error") // make a DBus appropriate error name + , result(rc) + { + // NOOP + } + + int result; + }; + + } // namespace + + BOOST_FUSION_ADAPT_STRUCT( + test::MyError, + (int, result) + ) +``` + +In the interface definition the exception class has to be added: + +```c++ + namespace test + { + + INTERFACE(HelloService) + { + Method, _throw> hello; + + // constructor + HelloService() + : INIT(hello) + { + } + }; + + } // namespace +``` + +You may now throw the error in a server's method callback: + +```c++ + class MyHello : simppl::dbus::Skeleton + { + MyHello(simppl::dbus::Dispatcher& disp) + : simppl::dbus::Skeleton(disp, "myHello") + { + hello >> [](const std::string& hello_string) + { + if (hello_string == "idiot") + respond_with(MyError(EINVAL)); + + respond_with(hello()); + }; + } + }; +``` This was a short introduction to simppl/dbus. I hope you will like developing client/server applications with the means of C++ and without diff --git a/cmake/FindDBus.cmake b/cmake/FindDBus.cmake index 4a40992..ddb917b 100644 --- a/cmake/FindDBus.cmake +++ b/cmake/FindDBus.cmake @@ -52,7 +52,7 @@ find_path(DBUS_ARCH_INCLUDE_DIR ${PC_DBUS_INCLUDE_DIRS} ${_DBUS_LIBRARY_DIR} ${DBUS_INCLUDE_DIR} - PATH_SUFFIXES include + PATH_SUFFIXES include dbus-1.0/include ) include(FindPackageHandleStandardArgs) diff --git a/cmake/GoogleTest-CMakeLists.txt.in b/cmake/GoogleTest-CMakeLists.txt.in index 105c872..a214558 100644 --- a/cmake/GoogleTest-CMakeLists.txt.in +++ b/cmake/GoogleTest-CMakeLists.txt.in @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.5.0) +cmake_minimum_required(VERSION 3.2) project(googletest-download NONE) diff --git a/examples/echo/main.cpp b/examples/echo/main.cpp index 40423b8..dfeb60d 100644 --- a/examples/echo/main.cpp +++ b/examples/echo/main.cpp @@ -23,7 +23,7 @@ class MyEchoClient : public simppl::dbus::Stub { if (st == simppl::dbus::ConnectionState::Connected) { - echo.async("Hello World!") >> [this](const simppl::dbus::CallState st, const std::string& echo_string) + echo.async("Hello World!") >> [this](const simppl::dbus::CallState& st, const std::string& echo_string) { if (st) { @@ -31,7 +31,7 @@ class MyEchoClient : public simppl::dbus::Stub } else std::cout << "Got error: " << st.what() << std::endl; - + disp().stop(); }; } @@ -56,7 +56,7 @@ class MyEcho : public simppl::dbus::Skeleton int main(int argc, char** argv) { simppl::dbus::Dispatcher disp("bus:session"); - + if (argc == 1) { MyEchoClient client(disp); @@ -73,6 +73,6 @@ int main(int argc, char** argv) MyEchoClient client(disp); disp.run(); } - + return EXIT_SUCCESS; } diff --git a/include/simppl/callstate.h b/include/simppl/callstate.h index 0b19e48..c3d8260 100644 --- a/include/simppl/callstate.h +++ b/include/simppl/callstate.h @@ -6,6 +6,8 @@ #include "simppl/error.h" +#include "simppl/detail/error.h" + struct DBusMessage; @@ -16,41 +18,29 @@ namespace simppl namespace dbus { -struct CallState +// forward decl +namespace detail { + template struct CallbackHolder; + template struct PropertyCallbackHolder; +} + + +template +struct TCallState { - explicit inline - CallState(uint32_t serial) - : ex_() - , serial_(serial) - { - // NOOP - } + template friend struct detail::CallbackHolder; + template friend struct detail::PropertyCallbackHolder; + template friend struct ClientProperty; + friend struct StubBase; - /// @param ex The function takes ownership of the exception. - explicit inline - CallState(Error* ex) - : ex_(ex) - , serial_(SIMPPL_INVALID_SERIAL) - { - // NOOP - } - CallState(CallState&& st) + TCallState(TCallState&& st) : ex_(st.ex_.release()) , serial_(st.serial_) { // NOOP } - CallState(DBusMessage&); - - // FIXME why is this necessary, we are not copyable by design?! - CallState(const CallState& st) - : ex_() - , serial_(st.serial_) - { - ex_.reset(const_cast(st).ex_.release()); - } explicit inline operator bool() const @@ -72,20 +62,55 @@ struct CallState } inline - const Error& exception() const + const ErrorT& exception() const { return *ex_; } - void throw_exception() const; - private: - std::unique_ptr ex_; + explicit inline + TCallState(uint32_t serial) + : ex_() + , serial_(serial) + { + // NOOP + } + + + /// @param ex The function takes ownership of the exception. + explicit inline + TCallState(ErrorT* ex) + : ex_(ex) + , serial_(SIMPPL_INVALID_SERIAL) + { + // NOOP + } + + + TCallState(DBusMessage& msg) + : ex_() + , serial_(SIMPPL_INVALID_SERIAL) + { + if (dbus_message_get_type(&msg) == DBUS_MESSAGE_TYPE_ERROR) + { + ex_.reset(new ErrorT); + detail::ErrorFactory::init(*ex_, msg); + } + else + serial_ = dbus_message_get_reply_serial(&msg); + } + + + std::unique_ptr ex_; uint32_t serial_; }; + +typedef TCallState CallState; + + } // namespace dbus } // namespace simppl diff --git a/include/simppl/clientside.h b/include/simppl/clientside.h index b8972e4..c95a665 100644 --- a/include/simppl/clientside.h +++ b/include/simppl/clientside.h @@ -120,12 +120,12 @@ struct ClientPropertyBase { friend struct StubBase; - typedef void (*eval_type)(ClientPropertyBase*, DBusMessageIter&); + typedef void (*eval_type)(ClientPropertyBase*, DBusMessageIter*); ClientPropertyBase(const char* name, StubBase* iface, int iface_id); - void eval(DBusMessageIter& iter) + void eval(DBusMessageIter* iter) { eval_(this, iter); } @@ -142,8 +142,6 @@ struct ClientPropertyBase StubBase* stub_; eval_type eval_; - - ClientPropertyBase* next_; }; @@ -152,8 +150,8 @@ struct ClientPropertyWritableMixin : ClientPropertyBase { typedef DataT data_type; typedef typename CallTraits::param_type arg_type; - typedef std::function function_type; - typedef detail::CallbackHolder holder_type; + typedef std::function function_type; + typedef detail::CallbackHolder holder_type; ClientPropertyWritableMixin(const char* name, StubBase* iface, int iface_id) @@ -192,9 +190,9 @@ struct ClientProperty typedef typename std::conditional<(Flags & ReadWrite), ClientPropertyWritableMixin, DataT>, ClientPropertyBase>::type base_type; typedef DataT data_type; typedef typename CallTraits::param_type arg_type; - typedef std::function function_type; + typedef std::function function_type; typedef ClientSignal signal_type; - typedef detail::PropertyCallbackHolder, data_type> holder_type; + typedef detail::PropertyCallbackHolder, data_type> holder_type; ClientProperty(const char* name, StubBase* iface, int iface_id) @@ -212,7 +210,6 @@ struct ClientProperty /// only call this after the server is connected. ClientProperty& attach(); - // TODO implement GetAll DataT get(); detail::InterimCallbackHolder get_async() @@ -232,15 +229,21 @@ struct ClientProperty private: static - void __eval(ClientPropertyBase* obj, DBusMessageIter& iter) + void __eval(ClientPropertyBase* obj, DBusMessageIter* iter) { ClientProperty* that = (ClientProperty*)obj; - data_type d; - detail::PropertyCodec::decode(iter, d); - if (that->f_) - that->f_(CallState(42), d); + { + if (iter) + { + data_type d; + detail::PropertyCodec::decode(*iter, d); + that->f_(CallState(42), d); + } + else + that->f_(CallState(new Error("simppl.dbus.Invalid")), data_type()); + } } @@ -267,11 +270,11 @@ DataT ClientProperty::get() template ClientProperty& ClientProperty::attach() { - this->stub_->attach_property(*this); + this->stub_->attach_property(this); dbus_pending_call_set_notify(this->stub_->get_property_async(this->name_).pending(), &holder_type::pending_notify, - new holder_type([this](CallState cs, const arg_type& val){ + new holder_type([this](const CallState& cs, const arg_type& val){ if (f_) f_(cs, val); }), @@ -284,22 +287,48 @@ ClientProperty& ClientProperty::attach() // -------------------------------------------------------------------------------- +struct ClientMethodBase +{ + typedef void(*throw_func_type)(DBusMessage&); + + ClientMethodBase(const char* method_name, StubBase* parent) + : method_name_(method_name) + , parent_(parent) + { + // NOOP + } + + void _throw(DBusMessage& msg) + { + return throw_(msg); + } + +// FIXME friend or public protected: + + throw_func_type throw_; + + const char* method_name_; + StubBase* parent_; +}; + + template -struct ClientMethod +struct ClientMethod : ClientMethodBase { typedef detail::generate_argument_type args_type_generator; typedef detail::generate_return_type return_type_generator; enum { - valid = AllOf::type, detail::InOutOrOneway>::value, + valid = AllOf::type, detail::InOutThrowOrOneway>::value, is_oneway = detail::is_oneway_request::value }; typedef typename detail::canonify::type args_type; typedef typename detail::canonify::type return_type; - typedef typename detail::generate_callback_function::type callback_type; - typedef detail::CallbackHolder holder_type; + typedef typename detail::get_exception_type::type exception_type; + typedef typename detail::generate_callback_function::type callback_type; + typedef detail::CallbackHolder holder_type; // correct typesafe serializer typedef typename detail::generate_serializer::type serializer_type; @@ -308,10 +337,9 @@ struct ClientMethod ClientMethod(const char* method_name, StubBase* parent, int iface_id) - : method_name_(method_name) - , parent_(parent) + : ClientMethodBase(method_name, parent) { - // NOOP + throw_ = __throw; } @@ -325,7 +353,7 @@ struct ClientMethod static_assert(std::is_convertible>::type, args_type>::value, "args mismatch"); - auto msg = parent_->send_request_and_block(method_name_, [&](DBusMessageIter& s){ + auto msg = parent_->send_request_and_block(this, [&](DBusMessageIter& s){ serializer_type::eval(s, t...); }, is_oneway); @@ -341,7 +369,7 @@ struct ClientMethod static_assert(std::is_convertible::type...>>::type, args_type>::value, "args mismatch"); - return detail::InterimCallbackHolder(parent_->send_request(method_name_, [&](DBusMessageIter& s){ + return detail::InterimCallbackHolder(parent_->send_request(this, [&](DBusMessageIter& s){ serializer_type::eval(s, t...); }, false)); } @@ -357,11 +385,16 @@ struct ClientMethod return *this; } - private: - const char* method_name_; - StubBase* parent_; + static + void __throw(DBusMessage& msg) + { + exception_type err; + detail::ErrorFactory::init(err, msg); + + throw err; + } }; diff --git a/include/simppl/detail/callinterface.h b/include/simppl/detail/callinterface.h index 084a599..bb675a9 100644 --- a/include/simppl/detail/callinterface.h +++ b/include/simppl/detail/callinterface.h @@ -29,9 +29,9 @@ struct FunctionCaller FunctionCaller::template eval_intern(f, tuple); } - template + template static inline - void eval_cs(FunctorT& f, const CallState& cs, const TupleT& tuple) + void eval_cs(FunctorT& f, const TCallState& cs, const TupleT& tuple) { FunctionCaller::template eval_intern_cs(f, cs, tuple); } @@ -43,9 +43,9 @@ struct FunctionCaller FunctionCaller::value ? -1 : N+1, TupleT>::template eval_intern(f, tuple, t..., std::get(tuple)); } - template + template static inline - void eval_intern_cs(FunctorT& f, const CallState& cs, const TupleT& tuple, const T&... t) + void eval_intern_cs(FunctorT& f, const TCallState& cs, const TupleT& tuple, const T&... t) { FunctionCaller::value ? -1 : N+1, TupleT>::template eval_intern_cs(f, cs, tuple, t..., std::get(tuple)); } @@ -61,9 +61,9 @@ struct FunctionCaller<-1, TupleT> f(t...); } - template + template static inline - void eval_intern_cs(FunctorT& f, const CallState& cs, const TupleT& /*tuple*/, const T&... t) + void eval_intern_cs(FunctorT& f, const TCallState& cs, const TupleT& /*tuple*/, const T&... t) { f(cs, t...); } @@ -72,9 +72,9 @@ struct FunctionCaller<-1, TupleT> template<> struct FunctionCaller<0, std::tuple<>> { - template + template static inline - void eval_cs(FunctorT& f, const CallState& cs, const std::tuple<>& tuple) + void eval_cs(FunctorT& f, const TCallState& cs, const std::tuple<>& tuple) { f(cs); } @@ -85,7 +85,7 @@ template struct DeserializeAndCall : simppl::NonInstantiable { template - static + static void eval(DBusMessageIter& iter, FunctorT& f) { std::tuple tuple; @@ -94,9 +94,9 @@ struct DeserializeAndCall : simppl::NonInstantiable FunctionCaller<0, std::tuple>::template eval(f, tuple); } - template + template static - void evalResponse(DBusMessageIter& iter, FunctorT& f, const simppl::dbus::CallState& cs) + void evalResponse(DBusMessageIter& iter, FunctorT& f, const simppl::dbus::TCallState& cs) { std::tuple tuple; @@ -121,9 +121,9 @@ struct DeserializeAndCall> : simppl::NonInstantiable FunctionCaller<0, std::tuple>::template eval(f, tuple); } - template - static - void evalResponse(DBusMessageIter& iter, FunctorT& f, const simppl::dbus::CallState& cs) + template + static + void evalResponse(DBusMessageIter& iter, FunctorT& f, const simppl::dbus::TCallState& cs) { std::tuple tuple; @@ -144,9 +144,9 @@ struct DeserializeAndCall0 : simppl::NonInstantiable f(); } - template + template static inline - void evalResponse(DBusMessageIter& /*iter*/, FunctorT& f, const simppl::dbus::CallState& cs) + void evalResponse(DBusMessageIter& /*iter*/, FunctorT& f, const simppl::dbus::TCallState& cs) { f(cs); } diff --git a/include/simppl/detail/deserialize_and_return.h b/include/simppl/detail/deserialize_and_return.h index 9b6806f..ea312ed 100644 --- a/include/simppl/detail/deserialize_and_return.h +++ b/include/simppl/detail/deserialize_and_return.h @@ -4,27 +4,27 @@ namespace simppl { - + namespace dbus { - + namespace detail { -template +template struct deserialize_and_return { - static + static ReturnT eval(DBusMessage* msg) { ReturnT rc; - + DBusMessageIter iter; dbus_message_iter_init(msg, &iter); - + decode(iter, rc); - - return rc; + + return rc; } }; @@ -42,24 +42,25 @@ struct deserialize_and_return // FIXME problem when return type is a single tuple instead of a parameter list // then the flattened return would be problematic! -template +template struct deserialize_and_return> { typedef std::tuple return_type; - static + static return_type eval(DBusMessage* msg) { return_type rc; - + DBusMessageIter iter; dbus_message_iter_init(msg, &iter); - + Codec::decode_flattened(iter, rc); - - return rc; + + return rc; } }; - + + } } diff --git a/include/simppl/detail/error.h b/include/simppl/detail/error.h new file mode 100644 index 0000000..a26e433 --- /dev/null +++ b/include/simppl/detail/error.h @@ -0,0 +1,112 @@ +#ifndef SIMPPL_DETAIL_ERROR_H +#define SIMPPL_DETAIL_ERROR_H + + +#include "simppl/types.h" +#include "simppl/string.h" + +#include "util.h" + +#include + + +namespace simppl +{ + +namespace dbus +{ + +namespace detail +{ + + +// FIXME get rid of redefinion +struct FreeDeleter { + template + void operator()(T* o) { + ::free(o); + } +}; + + +template +struct ErrorFactory +{ + /// server side + static + message_ptr_t reply(DBusMessage& msg, const Error& e) + { + std::unique_ptr name; + + if (!e.name()) + name.reset(make_error_name(abi::__cxa_demangle(typeid(ExceptionT).name(), nullptr, 0, nullptr))); + + message_ptr_t rmsg = e.make_reply_for(msg, name.get()); + + // encode arguments + DBusMessageIter iter; + dbus_message_iter_init_append(rmsg.get(), &iter); + + const ExceptionT* exp = dynamic_cast(&e); + if (exp) + encode(iter, *exp); + + return rmsg; + } + + + /// client side + static + void init(ExceptionT& err, DBusMessage& msg) + { + DBusMessageIter iter; + dbus_message_iter_init(&msg, &iter); + + std::string text; + decode(iter, text); + + // set default members + err.set_members(dbus_message_get_error_name(&msg), text.c_str(), dbus_message_get_reply_serial(&msg)); + + // any other unexpected dbus error, e.g. exception during method body + if (dbus_message_iter_get_arg_type(&iter) != 0) + { + // and now the rest + decode(iter, err); + } + } +}; + +template<> +struct ErrorFactory +{ + static + message_ptr_t reply(DBusMessage& msg, const Error& e) + { + message_ptr_t rmsg = e.make_reply_for(msg); + return rmsg; + } + + + static + void init(Error& err, DBusMessage& msg) + { + DBusMessageIter iter; + dbus_message_iter_init(&msg, &iter); + + std::string text; + decode(iter, text); + + err.set_members(dbus_message_get_error_name(&msg), text.c_str(), dbus_message_get_reply_serial(&msg)); + } +}; + + +} // detail + +} // dbus + +} // simppl + + +#endif // SIMPPL_DETAIL_ERROR_H diff --git a/include/simppl/detail/holders.h b/include/simppl/detail/holders.h index 3ec41f7..1b43bba 100644 --- a/include/simppl/detail/holders.h +++ b/include/simppl/detail/holders.h @@ -2,6 +2,8 @@ #define SIMPPL_DETAIL_HOLDERS_H +#include + #include "callinterface.h" @@ -11,6 +13,10 @@ namespace simppl namespace dbus { +// forward decl +struct StubBase; + + namespace detail { @@ -19,12 +25,6 @@ struct InterimCallbackHolder { typedef HolderT holder_type; - InterimCallbackHolder(const InterimCallbackHolder& rhs) - : pc_(std::move(rhs.pc_)) - { - // NOOP - } - InterimCallbackHolder& operator=(const InterimCallbackHolder&) = delete; explicit inline @@ -33,12 +33,28 @@ struct InterimCallbackHolder { // NOOP } - + PendingCall pc_; }; -template +struct InterimGetAllPropertiesCallbackHolder +{ + InterimGetAllPropertiesCallbackHolder& operator=(const InterimGetAllPropertiesCallbackHolder&) = delete; + + InterimGetAllPropertiesCallbackHolder(const PendingCall& pc, StubBase& stub) + : pc_(std::move(pc)) + , stub_(stub) + { + // NOOP + } + + PendingCall pc_; + StubBase& stub_; +}; + + +template struct CallbackHolder { CallbackHolder(const CallbackHolder&) = delete; @@ -51,7 +67,7 @@ struct CallbackHolder { // NOOP } - + static inline void _delete(void* p) { @@ -67,11 +83,11 @@ struct CallbackHolder auto that = (CallbackHolder*)data; assert(that->f_); - CallState cs(*msg); - + TCallState cs(*msg); + DBusMessageIter iter; dbus_message_iter_init(msg.get(), &iter); - + GetCaller::type::template evalResponse(iter, that->f_, cs); } @@ -114,10 +130,10 @@ struct PropertyCallbackHolder DBusMessageIter iter; dbus_message_iter_init(msg.get(), &iter); - simppl::Variant v; + std::variant v; decode(iter, v); - that->f_(cs, *v.template get()); + that->f_(cs, std::get(v)); } else that->f_(cs, DataT()); @@ -126,6 +142,26 @@ struct PropertyCallbackHolder FuncT f_; }; + +struct GetAllPropertiesHolder +{ + GetAllPropertiesHolder(const GetAllPropertiesHolder&) = delete; + GetAllPropertiesHolder& operator=(const GetAllPropertiesHolder&) = delete; + + + GetAllPropertiesHolder(std::function f, StubBase& stub); + + static + void _delete(void* p); + + static + void pending_notify(DBusPendingCall* pc, void* data); + + std::function f_; + StubBase& stub_; +}; + + } // detail } // dbus diff --git a/include/simppl/detail/parameter_deduction.h b/include/simppl/detail/parameter_deduction.h index f0e472f..e5a88cc 100644 --- a/include/simppl/detail/parameter_deduction.h +++ b/include/simppl/detail/parameter_deduction.h @@ -43,6 +43,18 @@ struct is_out> enum { value = true }; }; +template +struct is_throw +{ + enum { value = false }; +}; + +template +struct is_throw> +{ + enum { value = true }; +}; + // --------------------------------------------------------------------- // flatten typelist into std::tuple @@ -201,7 +213,7 @@ struct add_const_for_pointer { typedef T type; }; - + template struct apply_ { @@ -248,16 +260,16 @@ struct generate_argument_type { typedef typename filter::list_type list_type; typedef typename transform::type const_list_type; - + typedef typename make_tuple_from_list>::type type; typedef typename make_tuple_from_list>::type const_type; }; -template +template struct generate_callback_function { typedef typename filter::list_type list_type; - typedef typename make_function_from_list>::type type; + typedef typename make_function_from_list&)>>::type type; }; template @@ -303,6 +315,27 @@ struct is_oneway_request }; +template +struct has_exception +{ + typedef typename filter::list_type list_type; + enum { value = Size::value == 0 }; +}; + + +template +struct get_exception_type +{ + typedef typename filter::list_type list_type; + enum { size = Size::value }; + + static_assert(size <= 1, "multiple throw clauses encountered"); + static_assert(size == 0 || std::is_base_of::type>::value, "not a valid exception type"); + static_assert(size == 0 || (size && !is_oneway_request::value), "oneway with exception not possible"); + + typedef typename std::conditional::value == 0, Error, typename TypeAt<0, list_type>::type>::type type; +}; + // --- introspection stuff --------------------------------------------- @@ -381,6 +414,15 @@ struct IntrospectionHelper<::simppl::dbus::oneway> } }; +template +struct IntrospectionHelper<::simppl::dbus::_throw> +{ + static inline void eval(std::ostream& os, int i) + { + // NOOP + } +}; + struct Introspector { diff --git a/include/simppl/detail/util.h b/include/simppl/detail/util.h index 410b069..1bebb71 100644 --- a/include/simppl/detail/util.h +++ b/include/simppl/detail/util.h @@ -42,6 +42,10 @@ std::string make_interface_name(const char* begin, const char* end); */ const char* find_next_interface(const char* template_args); +/** + * Make dbus compatible name from Exception type. + */ +char* make_error_name(char*); } // namespace detail diff --git a/include/simppl/detail/validation.h b/include/simppl/detail/validation.h index 9b61489..43fa3be 100644 --- a/include/simppl/detail/validation.h +++ b/include/simppl/detail/validation.h @@ -12,13 +12,13 @@ namespace detail { -struct InOutOrOneway +struct InOutThrowOrOneway { template struct apply_ { - static_assert(is_in::value || is_out::value || std::is_same::value, "neither in, out nor oneway parameter"); - enum { value = is_in::value || is_out::value || std::is_same::value }; + static_assert(is_in::value || is_out::value || std::is_same::value || is_throw::value, "neither in, out nor oneway parameter and no throw directive"); + enum { value = is_in::value || is_out::value || std::is_same::value || is_throw::value }; }; }; diff --git a/include/simppl/dispatcher.h b/include/simppl/dispatcher.h index f1e5b60..43d594a 100644 --- a/include/simppl/dispatcher.h +++ b/include/simppl/dispatcher.h @@ -7,6 +7,7 @@ #include +#include "simppl/callstate.h" #include "simppl/connectionstate.h" #include "simppl/detail/constants.h" @@ -21,7 +22,6 @@ namespace dbus struct StubBase; struct SkeletonBase; struct ClientSignalBase; -struct CallState; void enable_threads(); diff --git a/include/simppl/error.h b/include/simppl/error.h index 025d330..9357ccf 100644 --- a/include/simppl/error.h +++ b/include/simppl/error.h @@ -17,24 +17,33 @@ namespace simppl namespace dbus { +// forward decl +namespace detail { + template struct ErrorFactory; +} + struct Error : public std::exception { - friend struct CallState; - friend struct SkeletonBase; - - + template friend struct detail::ErrorFactory; + template friend struct TCallState; + template friend struct ClientMethod; + friend struct StubBase; + + // no copies + Error(const Error&) = delete; Error& operator=(const Error&) = delete; + /** + * @param name must be a valid dbus name, i.e. . + */ Error(const char* name, const char* msg = nullptr, uint32_t serial = SIMPPL_INVALID_SERIAL); - Error(const Error& rhs); + // allow move + Error(Error&& rhs); ~Error(); - message_ptr_t make_reply_for(DBusMessage& req) const; - void _throw(); - const char* what() const throw(); const char* name() const; @@ -46,9 +55,15 @@ struct Error : public std::exception return serial_; } -private: - static std::unique_ptr from_message(DBusMessage& msg); +protected: + + message_ptr_t make_reply_for(DBusMessage& req, const char* classname = nullptr) const; + + void set_members(const char* name, const char* msg, uint32_t serial); + + Error(); + char* name_and_message_; char* message_; @@ -56,6 +71,7 @@ struct Error : public std::exception uint32_t serial_; }; + class RuntimeError : public std::runtime_error { public: @@ -81,7 +97,9 @@ class RuntimeError : public std::runtime_error return error_.message; } + private: + static std::string format_what(const char* action, const ::DBusError& error) { std::ostringstream ss; @@ -90,6 +108,7 @@ class RuntimeError : public std::runtime_error } private: + ::DBusError error_; }; diff --git a/include/simppl/filedescriptor.h b/include/simppl/filedescriptor.h index 38b4e76..9ac2175 100644 --- a/include/simppl/filedescriptor.h +++ b/include/simppl/filedescriptor.h @@ -4,6 +4,9 @@ #include "simppl/serialization.h" +#include +#include + namespace simppl { @@ -21,9 +24,18 @@ class FileDescriptor FileDescriptor(); + /** + * RAII behaviour: destroys the fd by calling close on object destruction. + */ explicit FileDescriptor(int fd); + /** + * Non-RAII behaviour: keeps fd open on object destruction. + */ + explicit + FileDescriptor(std::reference_wrapper fd); + ~FileDescriptor(); FileDescriptor(const FileDescriptor& rhs); @@ -38,6 +50,7 @@ class FileDescriptor private: int fd_; + int (*destructor_)(int); }; diff --git a/include/simppl/objectpath.h b/include/simppl/objectpath.h index ef74cb6..8fd5b3f 100644 --- a/include/simppl/objectpath.h +++ b/include/simppl/objectpath.h @@ -69,13 +69,7 @@ struct ObjectPath { return !(lhs < rhs); } - - inline - bool operator!=(const ObjectPath& rhs) const - { - return path != rhs.path; - } - + std::string path; }; diff --git a/include/simppl/parameter_deduction.h b/include/simppl/parameter_deduction.h index 4b25fc4..af244ac 100644 --- a/include/simppl/parameter_deduction.h +++ b/include/simppl/parameter_deduction.h @@ -29,6 +29,13 @@ struct out typedef T real_type; }; +// marker +template +struct _throw +{ + typedef T real_type; +}; + } // namespace dbus diff --git a/include/simppl/property.h b/include/simppl/property.h index 5e84c76..e8bd894 100644 --- a/include/simppl/property.h +++ b/include/simppl/property.h @@ -8,7 +8,7 @@ namespace simppl { - + namespace dbus { @@ -67,9 +67,9 @@ struct PropertyCodec { detail::VariantSerializer(iter).operator()(t); } - - - static + + + static void decode(DBusMessageIter& iter, T& t) { DBusMessageIter _iter; @@ -78,7 +78,7 @@ struct PropertyCodec Codec::decode(_iter, t); dbus_message_iter_next(&iter); - } + } }; diff --git a/include/simppl/serverside.h b/include/simppl/serverside.h index 9e73778..ac10205 100644 --- a/include/simppl/serverside.h +++ b/include/simppl/serverside.h @@ -43,6 +43,7 @@ namespace detail struct ServerMethodBase { typedef void(*eval_type)(ServerMethodBase*, DBusMessage*); + typedef message_ptr_t(*throw_type)(ServerMethodBase*, DBusMessage&, const Error&); typedef void(*sig_type)(std::ostream&); void eval(DBusMessage* msg) @@ -50,6 +51,11 @@ struct ServerMethodBase eval_(this, msg); } + message_ptr_t _throw(DBusMessage& msg, const Error& err) + { + return throw_(this, msg, err); + } + #if SIMPPL_HAVE_INTROSPECTION virtual void introspect(std::ostream& os) const = 0; @@ -65,6 +71,7 @@ struct ServerMethodBase ~ServerMethodBase(); eval_type eval_; + throw_type throw_; }; @@ -127,16 +134,18 @@ struct ServerMethod : ServerMethodBase typedef detail::generate_return_type return_type_generator; enum { - valid = AllOf::type, detail::InOutOrOneway>::value, - is_oneway = detail::is_oneway_request::value + valid = AllOf::type, detail::InOutThrowOrOneway>::value, + is_oneway = detail::is_oneway_request::value, }; - typedef typename detail::canonify::type args_type; - typedef typename detail::canonify::type return_type; + typedef typename detail::canonify::type args_type; + typedef typename detail::canonify::type return_type; - typedef typename detail::generate_server_callback_function::type callback_type; + typedef typename detail::generate_server_callback_function::type callback_type; typedef typename detail::generate_serializer::type serializer_type; + typedef typename detail::get_exception_type::type exception_type; + static_assert(!is_oneway || (is_oneway && std::is_same::value), "oneway check"); @@ -145,6 +154,7 @@ struct ServerMethod : ServerMethodBase : ServerMethodBase(name, iface, iface_id) { eval_ = __eval; + throw_ = __throw; } @@ -182,6 +192,14 @@ struct ServerMethod : ServerMethodBase } + template + static + message_ptr_t __throw(ServerMethodBase* obj, DBusMessage& req, const Error& err) + { + return detail::ErrorFactory::reply(req, err); + } + + template detail::ServerResponseHolder __impl(std::true_type, const T&... t) { @@ -203,14 +221,14 @@ struct ServerMethod : ServerMethodBase struct ServerPropertyBase { - typedef void (*eval_type)(ServerPropertyBase*, DBusMessage*); + typedef void (*eval_type)(ServerPropertyBase*, DBusMessageIter*); typedef void (*eval_set_type)(ServerPropertyBase*, DBusMessageIter&); ServerPropertyBase(const char* name, SkeletonBase* iface, int iface_id); - void eval(DBusMessage* msg) + void eval(DBusMessageIter* iter) { - eval_(this, msg); + return eval_(this, iter); } void evalSet(DBusMessageIter& iter) @@ -252,24 +270,24 @@ struct BaseProperty : ServerPropertyBase const DataT& value() const { - const DataT* t = t_.template get(); - assert(t); - return *t; + return std::get(t_); } static - void __eval(ServerPropertyBase* obj, DBusMessage* response) + void __eval(ServerPropertyBase* obj, DBusMessageIter* iter) { - DBusMessageIter iter; - dbus_message_iter_init_append(response, &iter); - auto that = ((BaseProperty*)obj); - + // missing initialization? assert(!that->t_.empty()); - detail::PropertyCodec::encode(iter, that->t_.template get() ? (*that->t_.template get())() : *that->t_.template get()); + + detail::PropertyCodec::encode(*iter, std::get_if(&that->t_) ? (std::get(that->t_))() : std::get(that->t_)); } + /** + * Shall be used with properties implementing on_read callback in order + * to send property changes. Not suitable with value holding properties. + */ void notify(const DataT& data) { this->parent_->send_property_change(this->name_, this->iface_id_, [this, data](DBusMessageIter& iter){ @@ -277,6 +295,15 @@ struct BaseProperty : ServerPropertyBase }); } + /** + * Send an invalidation message, shall only be used in conjunction with the notify message and + * the on_read callback. Currently, value holding properties cannot be invalidated. + */ + void invalidate() + { + this->parent_->send_property_invalidate(this->name_, this->iface_id_); + } + /// set callback for each read void on_read(cb_type cb) { @@ -285,7 +312,7 @@ struct BaseProperty : ServerPropertyBase protected: - Variant t_; + std::variant t_; }; @@ -348,7 +375,7 @@ namespace detail static void eval(BaseProperty& p, const DataT& d) { - if (!p.t_.template get() || PropertyComparator::compare(p.value(), d)) + if (!std::get_if(&p.t_) || PropertyComparator::compare(p.value(), d)) { p.t_ = d; p.notify(d); diff --git a/include/simppl/skeletonbase.h b/include/simppl/skeletonbase.h index 8a059a2..4b35db0 100644 --- a/include/simppl/skeletonbase.h +++ b/include/simppl/skeletonbase.h @@ -82,6 +82,8 @@ struct SkeletonBase } void send_property_change(const char* prop, int iface_id, std::function&& f); + void send_property_invalidate(const char* prop, int iface_id); + void send_signal(const char* signame, int iface_id, std::function&& f); protected: @@ -100,10 +102,11 @@ struct SkeletonBase 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); + DBusHandlerResult handle_property_getall_request(DBusMessage* msg, int iface_id); #if SIMPPL_HAVE_INTROSPECTION void introspect_interface(std::ostream& os, size_type index) const; -#endif +#endif 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; diff --git a/include/simppl/stubbase.h b/include/simppl/stubbase.h index e4bf642..600f55e 100644 --- a/include/simppl/stubbase.h +++ b/include/simppl/stubbase.h @@ -10,7 +10,9 @@ #include "simppl/callstate.h" #include "simppl/pendingcall.h" + #include "simppl/detail/constants.h" +#include "simppl/detail/holders.h" #include "simppl/connectionstate.h" @@ -25,10 +27,36 @@ namespace dbus struct Dispatcher; struct ClientSignalBase; struct ClientPropertyBase; +struct StubBase; +struct ClientMethodBase; + + +// TODO move away from here +namespace detail +{ +struct GetAllProperties +{ + typedef detail::InterimGetAllPropertiesCallbackHolder getall_properties_holder_type; + + GetAllProperties(simppl::dbus::StubBase& stub); + + GetAllProperties& operator[](int flags); + + void operator()(); + + getall_properties_holder_type + async(); + + simppl::dbus::StubBase& stub_; +}; + +} struct StubBase { + typedef detail::GetAllProperties::getall_properties_holder_type getall_properties_holder_type; + template friend struct ClientSignal; template friend struct ClientMethod; template friend struct ClientProperty; @@ -36,6 +64,8 @@ struct StubBase friend struct Dispatcher; friend struct ClientPropertyBase; + friend struct detail::GetAllPropertiesHolder; + friend struct detail::GetAllProperties; StubBase(const StubBase&) = delete; StubBase& operator=(const StubBase&) = delete; @@ -82,6 +112,20 @@ struct StubBase return busname_; } + /** + * Implementation of org.freedesktop.DBus.Properties.GetAll(). Blocking call. + * + * Before calling this method all Properties callbacks shall be installed. You may install + * only some of the property callbacks. If a callback is not registered the property will + * just omitted. + * + * The asynchronous return will arrive as soon as call the property callbacks are evaluated. + * + * Issues a call like this: + * dbus-send --print-reply --dest=test.Properties.s /test/Properties/s org.freedesktop.DBus.Properties.GetAll string:test.Properties + */ + detail::GetAllProperties get_all_properties; + protected: @@ -91,15 +135,15 @@ struct StubBase void cleanup(); - PendingCall send_request(const char* method_name, std::function&& f, bool is_oneway); + PendingCall send_request(ClientMethodBase* method, std::function&& f, bool is_oneway); - message_ptr_t send_request_and_block(const char* method_name, std::function&& f, bool is_oneway); + message_ptr_t send_request_and_block(ClientMethodBase* method, std::function&& f, bool is_oneway); void register_signal(ClientSignalBase& sigbase); void unregister_signal(ClientSignalBase& sigbase); - void attach_property(ClientPropertyBase& prop); - void detach_property(ClientPropertyBase& prop); + void attach_property(ClientPropertyBase* prop); + void detach_property(ClientPropertyBase* prop); /** * Blocking call. @@ -113,8 +157,24 @@ struct StubBase */ void set_property(const char* Name, std::function&& f); + /** + * Just register the property within the stub. + */ + void add_property(ClientPropertyBase* property); + PendingCall set_property_async(const char* Name, std::function&& f); + /** + * Second part of get_all_properties_async. Once the callback arrives + * the property callbacks have to be called. + * + * @param __throw blocking call throws exception + */ + simppl::dbus::CallState get_all_properties_handle_response(DBusMessage& response, bool __throw); + + void get_all_properties_request(); + getall_properties_holder_type get_all_properties_request_async(); + std::vector ifaces_; char* objectpath_; std::string busname_; @@ -122,8 +182,10 @@ struct StubBase Dispatcher* disp_; - ClientSignalBase* signals_; ///< attached signals - ClientPropertyBase* properties_; ///< attached properties + ClientSignalBase* signals_; ///< attached signals + + std::vector> properties_; ///< all properties TODO maybe take another container... + int attached_properties_; ///< attach counter }; } // namespace dbus @@ -131,4 +193,15 @@ struct StubBase } // namespace simppl +inline +void operator>>(simppl::dbus::detail::InterimGetAllPropertiesCallbackHolder&& r, const std::function& f) +{ + dbus_pending_call_set_notify(r.pc_.pending(), + &simppl::dbus::detail::GetAllPropertiesHolder::pending_notify, + new simppl::dbus::detail::GetAllPropertiesHolder(f, r.stub_), + &simppl::dbus::detail::GetAllPropertiesHolder::_delete); +} + + #endif // SIMPPL_STUBBASE_H + diff --git a/include/simppl/typelist.h b/include/simppl/typelist.h index dd62bf3..29a485d 100644 --- a/include/simppl/typelist.h +++ b/include/simppl/typelist.h @@ -37,6 +37,12 @@ struct Size > enum { value = 1 }; }; +template<> +struct Size > +{ + enum { value = 0 }; +}; + // -------------------------------popfrontN ------------------------------------ template diff --git a/include/simppl/variant.h b/include/simppl/variant.h index b8d2af3..77e86e3 100644 --- a/include/simppl/variant.h +++ b/include/simppl/variant.h @@ -5,6 +5,7 @@ #include "simppl/typelist.h" #include "simppl/serialization.h" +#include #include #include #include @@ -13,369 +14,6 @@ namespace simppl { -struct AlignFunc -{ - template - struct apply_ - { - enum { value = std::alignment_of::value }; - }; -}; - - -struct SizeFunc -{ - template - struct apply_ - { - enum { value = sizeof(T) }; - }; -}; - - -// FIXME the algorithm should look a little bit like max_element and return an index of the -// maximum element as given by the Functor -template -struct Max; - -template -struct Max, FuncT> -{ - typedef typename FuncT::template apply_ first_type__; - enum { value1__ = first_type__::value }; - enum { value2__ = Max::value }; - - enum { value = (int)value1__ > (int)value2__ ? (int)value1__ : (int)value2__ }; -}; - - -template -struct Max, FuncT> -{ - typedef typename FuncT::template apply_ type__; - enum { value = type__::value }; -}; - - -// ---------------------------------------------------------------------------------------------- - - -template -struct Variant -{ - // FIXME make sure not to be able to add the same type multiple times - - typedef typename make_typelist::type typelist_type; - - enum { size = Max::value }; - enum { alignment = Max::value }; - - static_assert(sizeof...(T) <= 7, "currently only 7 variant entries supported"); - - /*private*/ enum { unset = -1 }; - - inline - Variant() - : idx_(unset) - { - // NOOP - } - - inline - ~Variant() - { - try_destroy(); - } - - template - inline - Variant(const _T& t) // FIXME use calltraits here - : idx_(Find<_T, typelist_type>::value) - { - static_assert(Size::value >= 0, "variant with size 0 invalid"); - static_assert(Find<_T, typelist_type>::value >= 0, "given_type_is_not_element_of_variant_maybe_use_explicit_cast"); - ::new(&data_) _T(t); - } - - - // TODO implement inplace factories and assignment operator - - Variant(const Variant& rhs); - Variant& operator=(const Variant& rhs); - - // NO INLINE, TOO LONG - template - Variant& operator=(const _T& t) // FIXME use calltraits here - { - static_assert(Find<_T, typelist_type>::value >= 0, "given_type_is_not_element_of_variant_maybe_use_explicit_cast"); - - if (idx_ == Find<_T, typelist_type>::value) - { - *get<_T>() = t; - } - else - { - try_destroy(); - idx_ = Find<_T, typelist_type>::value; - ::new(&data_) _T(t); - } - - return *this; - } - - template - inline - _T* const get() - { - static_assert(Find<_T, typelist_type>::value >= 0, "given_type_is_not_element_of_variant_maybe_use_explicit_cast"); - - if (Find<_T, typelist_type>::value != idx_) - return 0; - - return (_T*)(&data_); - } - - template - inline - const _T* const get() const - { - static_assert(Find<_T, typelist_type>::value >= 0, "given_type_is_not_element_of_variant_maybe_use_explicit_cast"); - - if (Find<_T, typelist_type>::value != idx_) - return 0; - - return (_T*)(&data_); - } - -// private - - template - static inline - void variant_destroy(void* t) - { - typedef typename RelaxedTypeAt::type _T; - ((_T*)t)->~_T(); - } - - - bool empty() const - { - return idx_ == unset; - } - - - // NON INLINE - too long!!! - // FIXME write with recursive function instead - void try_destroy() - { - // Beware that the direction of types in the typelist is in reverse order!!! - typedef void(*func_type)(void*); - static func_type funcs[] = { - &variant_destroy<0>, - &variant_destroy<1>, - &variant_destroy<2>, - &variant_destroy<3>, - &variant_destroy<4>, - &variant_destroy<5>, - &variant_destroy<6>, - &variant_destroy<7> - // append if necessary - }; - if (idx_ >= 0 && idx_ < Size::value) - funcs[(int)idx_](&data_); - } - - // with an ordinary union only simple data types could be stored in here - typename std::aligned_storage::type data_; - int8_t idx_; -}; - - -template -struct StaticVisitor -{ - typedef ReturnT return_type; -}; - - -template -struct Callfunc -{ - template - static inline - typename VisitorT::return_type eval(VisitorT& visitor, VariantT& variant) - { - return visitor(*variant.template get()); - } -}; - -template<> -struct Callfunc -{ - template - static inline - typename VisitorT::return_type eval(VisitorT&, VariantT&) - { - throw; - } -}; - - -template -typename VisitorT::return_type static_visit(VisitorT& visitor, VariantT& variant) -{ - // FIXME recursive iterate - switch(variant.idx_) - { - // FIXME case -1: - case 0: - return Callfunc::type>::eval(visitor, variant); - - case 1: - return Callfunc::type>::eval(visitor, variant); - - case 2: - return Callfunc::type>::eval(visitor, variant); - - case 3: - return Callfunc::type>::eval(visitor, variant); - - case 4: - return Callfunc::type>::eval(visitor, variant); - - case 5: - return Callfunc::type>::eval(visitor, variant); - - case 6: - return Callfunc::type>::eval(visitor, variant); - - case 7: - return Callfunc::type>::eval(visitor, variant); - - default: - //std::cerr << "Hey, ugly!" << std::endl; - throw; - } -} - - -// FIXME make visitor a first class object -template -typename VisitorT::return_type static_visit(VisitorT& visitor, const VariantT& variant) -{ - // FIXME subsitute switch with static function table - // FIXME recursive iterate - switch(variant.idx_) - { - case 0: - return Callfunc::type>::eval(visitor, variant); - - case 1: - return Callfunc::type>::eval(visitor, variant); - - case 2: - return Callfunc::type>::eval(visitor, variant); - - case 3: - return Callfunc::type>::eval(visitor, variant); - - case 4: - return Callfunc::type>::eval(visitor, variant); - - case 5: - return Callfunc::type>::eval(visitor, variant); - - case 6: - return Callfunc::type>::eval(visitor, variant); - - case 7: - return Callfunc::type>::eval(visitor, variant); - - default: - //std::cerr << "Hey, ugly!" << std::endl; - throw; - } -} - - -namespace detail -{ - template - struct ConstructionVisitor : StaticVisitor<> - { - ConstructionVisitor(VariantT& v) - : v_(v) - { - // NOOP - } - - template - void operator()(const T& t) - { - ::new(&v_.data_) T(t); - } - - VariantT& v_; - }; - - - template - struct AssignmentVisitor : StaticVisitor<> - { - AssignmentVisitor(VariantT& v) - : v_(v) - { - // NOOP - } - - template - void operator()(const T& t) - { - *v_.template get() = t; - } - - VariantT& v_; - }; -} - - -template -Variant::Variant(const Variant& rhs) - : idx_(rhs.idx_) -{ - if (idx_ != unset) - { - detail::ConstructionVisitor> v(*this); - static_visit(v, rhs); - } -} - - -template -Variant& Variant::operator=(const Variant& rhs) -{ - if (this != &rhs) - { - if (idx_ != rhs.idx_) - { - // need to call copy constructor - try_destroy(); - - idx_ = rhs.idx_; - detail::ConstructionVisitor> v(*this); - static_visit(v, rhs); - } - else - { - detail::AssignmentVisitor> v(*this); - static_visit(v, rhs); - } - } - - return *this; -} - - namespace dbus { @@ -383,7 +21,7 @@ namespace detail { -struct VariantSerializer : StaticVisitor<> +struct VariantSerializer { inline VariantSerializer(DBusMessageIter& iter) @@ -415,7 +53,7 @@ struct VariantDeserializer if (!strcmp(buf.str().c_str(), sig)) { v = T1(); - Codec::decode(iter, *v.template get()); + Codec::decode(iter, std::get(v)); return true; } @@ -438,7 +76,7 @@ struct VariantDeserializer if (!strcmp(buf.str().c_str(), sig)) { v = T(); - Codec::decode(iter, *v.template get()); + Codec::decode(iter, std::get(v)); return true; } @@ -450,25 +88,25 @@ struct VariantDeserializer template -bool try_deserialize(DBusMessageIter& iter, Variant& v, const char* sig); +bool try_deserialize(DBusMessageIter& iter, std::variant& v, const char* sig); } // namespace detail template -struct Codec> +struct Codec> { static - void encode(DBusMessageIter& iter, const Variant& v) + void encode(DBusMessageIter& iter, const std::variant& v) { detail::VariantSerializer vs(iter); - static_visit(vs, const_cast&>(v)); // TODO need const visitor + std::visit(vs, const_cast&>(v)); // TODO need const visitor } static - void decode(DBusMessageIter& orig, Variant& v) + void decode(DBusMessageIter& orig, std::variant& v) { DBusMessageIter iter; simppl_dbus_message_iter_recurse(&orig, &iter, DBUS_TYPE_VARIANT); @@ -491,7 +129,7 @@ struct Codec> template -bool detail::try_deserialize(DBusMessageIter& iter, Variant& v, const char* sig) +bool detail::try_deserialize(DBusMessageIter& iter, std::variant& v, const char* sig) { return VariantDeserializer::eval(iter, v, sig); } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..538ad02 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,23 @@ +add_library(simppl SHARED + dispatcher.cpp + error.cpp + serverresponseholder.cpp + skeletonbase.cpp + serverside.cpp + stubbase.cpp + timeout.cpp + util.cpp + serverrequestdescriptor.cpp + pendingcall.cpp + string.cpp + wstring.cpp + objectpath.cpp + filedescriptor.cpp + clientside.cpp + serialization.cpp + bool.cpp +) + +target_link_libraries(simppl dbus-1) + +install(TARGETS simppl DESTINATION lib) diff --git a/src/callstate.cpp b/src/callstate.cpp deleted file mode 100644 index 43aca7f..0000000 --- a/src/callstate.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "simppl/callstate.h" - -#include - -#include -#include - - -namespace simppl -{ - -namespace dbus -{ - - -CallState::CallState(DBusMessage& msg) - : ex_() - , serial_(SIMPPL_INVALID_SERIAL) -{ - if (dbus_message_get_type(&msg) == DBUS_MESSAGE_TYPE_ERROR) - { - ex_ = Error::from_message(msg); - } - else - serial_ = dbus_message_get_reply_serial(&msg); -} - - -void CallState::throw_exception() const -{ - assert(ex_); - ex_->_throw(); -} - - -} // namespace dbus - -} // namespace simppl diff --git a/src/clientside.cpp b/src/clientside.cpp index 3717745..e04dc02 100644 --- a/src/clientside.cpp +++ b/src/clientside.cpp @@ -3,15 +3,14 @@ namespace simppl { - + namespace dbus { - + ClientSignalBase::ClientSignalBase(const char* name, StubBase* stub, int) : stub_(stub) , name_(name) - , next_(nullptr) { // NOOP } @@ -20,16 +19,15 @@ ClientSignalBase::ClientSignalBase(const char* name, StubBase* stub, int) ClientPropertyBase::ClientPropertyBase(const char* name, StubBase* stub, int) : name_(name) , stub_(stub) - , next_(nullptr) { - // NOOP + stub_->add_property(this); } /// only call this after the server is connected. void ClientPropertyBase::detach() { - stub_->detach_property(*this); + stub_->detach_property(this); } diff --git a/src/dispatcher.cpp b/src/dispatcher.cpp index 34ca0b1..b932f1f 100644 --- a/src/dispatcher.cpp +++ b/src/dispatcher.cpp @@ -497,10 +497,10 @@ Dispatcher::~Dispatcher() void Dispatcher::notify_clients(const std::string& busname, ConnectionState state) { - auto range = d->stubs_.equal_range(busname); + std::for_each(d->stubs_.begin(), d->stubs_.end(), [busname, state](auto& entry){ - std::for_each(range.first, range.second, [state](auto& entry){ - entry.second->connection_state_changed(state); + if (busname == entry.second->busname()) + entry.second->connection_state_changed(state); }); } @@ -595,6 +595,8 @@ void Dispatcher::unregister_signal_match(const std::string& match_string) DBusError err; dbus_error_init(&err); + d->signal_matches_.erase(iter); + dbus_bus_remove_match(conn_, match_string.c_str(), &err); if (dbus_error_is_set(&err)) throw RuntimeError("dbus_bus_remove_match", std::move(err)); @@ -658,27 +660,18 @@ DBusHandlerResult Dispatcher::try_handle_signal(DBusMessage* msg) // ordinary signals... - // here we expect that pathname is the same as busname, just with / instead of . - char originator[256]; - strncpy(originator, dbus_message_get_path(msg)+1, sizeof(originator)); - originator[sizeof(originator)-1] = '\0'; + bool handled = false; + auto range = d->stubs_.equal_range(dbus_message_get_path(msg)); - char* p = originator; - while(*++p) + for (auto iter = range.first; iter != range.second; ++iter) { - if (*p == '/') - *p = '.'; + iter->second->try_handle_signal(msg); + handled = true; } - auto range = d->stubs_.equal_range(originator); - if (range.first != range.second) - { - std::for_each(range.first, range.second, [msg](auto& entry){ - entry.second->try_handle_signal(msg); - }); - + if (handled) return DBUS_HANDLER_RESULT_HANDLED; - } + // else // nobody interested in signal - go on with other filters } @@ -703,8 +696,7 @@ void Dispatcher::add_client(StubBase& clnt) { clnt.disp_ = this; - //std::cout << "Adding stub: " << clnt.busname() << std::endl; - d->stubs_.insert(std::make_pair(clnt.busname(), &clnt)); + d->stubs_.insert(std::make_pair(clnt.objectpath(), &clnt)); // send connected request from event loop auto iter = d->busnames_.find(clnt.busname()); diff --git a/src/error.cpp b/src/error.cpp index 9bdcdf4..1824d14 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -11,67 +11,37 @@ namespace simppl namespace dbus { - - -/*static*/ -std::unique_ptr Error::from_message(DBusMessage& msg) -{ - DBusMessageIter iter; - dbus_message_iter_init(&msg, &iter); - - std::string text; - decode(iter, text); - - return std::unique_ptr(new Error(dbus_message_get_error_name(&msg), text.c_str(), dbus_message_get_reply_serial(&msg))); -} - Error::Error(const char* name, const char* msg, uint32_t serial) - : serial_(serial) + : name_and_message_(nullptr) + , message_(nullptr) + , serial_(SIMPPL_INVALID_SERIAL) { assert(name); - // TODO check name for valid dbus name (.) - - size_t capacity = strlen(name) + 1; - - if (msg && strlen(msg)) - capacity += strlen(msg)+1; - - name_and_message_ = new char[capacity]; - strcpy(name_and_message_, name); - - if (msg && strlen(msg)) - { - message_ = name_and_message_ + strlen(name) + 1; - strcpy(message_, msg); - } - else - message_ = name_and_message_ + strlen(name_and_message_); + // TODO check name for valid dbus name (.) + set_members(name, msg, serial); } -Error::Error(const Error& rhs) - : serial_(rhs.serial_) +Error::Error() + : name_and_message_(nullptr) + , message_(nullptr) + , serial_(SIMPPL_INVALID_SERIAL) { - size_t capacity = strlen(rhs.name_and_message_) + strlen(rhs.message_) + 2; - - name_and_message_ = new char[capacity]; - memset(name_and_message_, 0, capacity); - - message_ = name_and_message_ + (rhs.message_ - rhs.name_and_message_); - - char* p_to = name_and_message_; - char* p_from = rhs.name_and_message_; + // NOOP +} - while(*p_from) - *p_to++ = *p_from++; - p_to = message_; - p_from = rhs.message_; +Error::Error(Error&& rhs) +{ + name_and_message_ = rhs.name_and_message_; + message_ = rhs.message_; + serial_ = rhs.serial_; - while(*p_from) - *p_to++ = *p_from++; + rhs.name_and_message_ = nullptr; + rhs.message_ = nullptr; + rhs.serial_ = 0; // FIXME INVALID } @@ -84,15 +54,35 @@ Error::~Error() } -message_ptr_t Error::make_reply_for(DBusMessage& req) const +void Error::set_members(const char* name, const char* msg, uint32_t serial) { - return make_message(dbus_message_new_error(&req, name(), message())); + if (name_and_message_) + delete[] name_and_message_; + + serial_ = serial; + + size_t capacity = strlen(name) + 1; + + if (msg && strlen(msg)) + capacity += strlen(msg)+1; + + name_and_message_ = new char[capacity]; + + strcpy(name_and_message_, name); + + if (msg && strlen(msg)) + { + message_ = name_and_message_ + strlen(name) + 1; + strcpy(message_, msg); + } + else + message_ = name_and_message_ + strlen(name_and_message_); } -void Error::_throw() +message_ptr_t Error::make_reply_for(DBusMessage& req, const char* class_name) const { - throw Error(*this); + return make_message(dbus_message_new_error(&req, class_name ? class_name : name(), message())); } @@ -110,7 +100,7 @@ const char* Error::name() const const char* Error::message() const { - return message_; + return message_ ? message_ : ""; } diff --git a/src/filedescriptor.cpp b/src/filedescriptor.cpp index c693db4..cfef731 100644 --- a/src/filedescriptor.cpp +++ b/src/filedescriptor.cpp @@ -2,18 +2,30 @@ #include -#include + +namespace { + + int noop(int) + { + // NOOP + return 0; + } +} + + +// --------------------------------------------------------------------- namespace simppl { - + namespace dbus { FileDescriptor::FileDescriptor() : fd_(-1) + , destructor_(&close) { // NOOP } @@ -21,23 +33,38 @@ FileDescriptor::FileDescriptor() FileDescriptor::FileDescriptor(int fd) : fd_(fd) + , destructor_(&close) +{ + assert(fd >= 0); +} + + +FileDescriptor::FileDescriptor(std::reference_wrapper fd) + : fd_(fd) + , destructor_(&noop) { assert(fd >= 0); } - + FileDescriptor::~FileDescriptor() { if (fd_ != -1) - close(fd_); + (*destructor_)(fd_); } - + FileDescriptor::FileDescriptor(const FileDescriptor& rhs) : fd_(-1) + , destructor_(rhs.destructor_) { if (rhs.fd_ != -1) - fd_ = dup(rhs.fd_); + { + if (destructor_ == &noop) + fd_ = rhs.fd_; + else + fd_ = dup(rhs.fd_); + } } @@ -46,22 +73,28 @@ FileDescriptor& FileDescriptor::operator=(const FileDescriptor& rhs) if (this != &rhs) { if (fd_ != -1) - close(fd_); - + (*destructor_)(fd_); + if (rhs.fd_ != -1) { - fd_ = dup(rhs.fd_); + if (rhs.destructor_ == &noop) + fd_ = rhs.fd_; + else + fd_ = dup(rhs.fd_); } else fd_ = -1; + + destructor_ = rhs.destructor_; } - + return *this; } FileDescriptor::FileDescriptor(FileDescriptor&& rhs) : fd_(rhs.fd_) + , destructor_(rhs.destructor_) { rhs.fd_ = -1; } @@ -70,13 +103,13 @@ FileDescriptor::FileDescriptor(FileDescriptor&& rhs) FileDescriptor& FileDescriptor::operator=(int fd) { if (fd_ != -1) - close(fd_); - + (*destructor_)(fd_); + fd_ = fd; - + if (fd_ < 0) fd_ = -1; - + return *this; } @@ -84,11 +117,13 @@ FileDescriptor& FileDescriptor::operator=(int fd) FileDescriptor& FileDescriptor::operator=(FileDescriptor&& rhs) { if (fd_ != -1) - close(fd_); - + (*destructor_)(fd_); + fd_ = rhs.fd_; rhs.fd_ = -1; - + + destructor_ = rhs.destructor_; + return *this; } @@ -103,12 +138,12 @@ int FileDescriptor::release() { int fd = fd_; fd_ = -1; - + return fd; } -/*static*/ +/*static*/ void FileDescriptorCodec::encode(DBusMessageIter& iter, const FileDescriptor& fd) { int _fd = fd.native_handle(); @@ -116,7 +151,7 @@ void FileDescriptorCodec::encode(DBusMessageIter& iter, const FileDescriptor& fd } -/*static*/ +/*static*/ void FileDescriptorCodec::decode(DBusMessageIter& iter, FileDescriptor& fd) { int _fd; @@ -125,13 +160,13 @@ void FileDescriptorCodec::decode(DBusMessageIter& iter, FileDescriptor& fd) } -/*static*/ +/*static*/ std::ostream& FileDescriptorCodec::make_type_signature(std::ostream& os) { return os << DBUS_TYPE_UNIX_FD_AS_STRING; } - - + + } // namespace dbus } // namespace simppl diff --git a/src/holders.cpp b/src/holders.cpp new file mode 100644 index 0000000..93a29fd --- /dev/null +++ b/src/holders.cpp @@ -0,0 +1,33 @@ +#include "simppl/pendingcall.h" +#include "simppl/stub.h" + +#include "simppl/detail/holders.h" + + +simppl::dbus::detail::GetAllPropertiesHolder::GetAllPropertiesHolder(std::function f, StubBase& stub) + : f_(f) + , stub_(stub) +{ + // NOOP +} + + +/*static*/ +void simppl::dbus::detail::GetAllPropertiesHolder::_delete(void* p) +{ + auto that = (GetAllPropertiesHolder*)p; + delete that; +} + + +/*static*/ +void simppl::dbus::detail::GetAllPropertiesHolder::pending_notify(DBusPendingCall* pc, void* data) +{ + auto msg = simppl::dbus::make_message(dbus_pending_call_steal_reply(pc)); + + auto that = (GetAllPropertiesHolder*)data; + assert(that->f_); + + auto cs = that->stub_.get_all_properties_handle_response(*msg, false); + that->f_(cs); +} diff --git a/src/properties.cpp b/src/properties.cpp new file mode 100644 index 0000000..e4bbee1 --- /dev/null +++ b/src/properties.cpp @@ -0,0 +1,31 @@ +#include "simppl/stubbase.h" +#include "simppl/timeout.h" + + +simppl::dbus::detail::GetAllProperties::GetAllProperties(simppl::dbus::StubBase& stub) + : stub_(stub) +{ + // NOOP +} + + +simppl::dbus::detail::GetAllProperties& simppl::dbus::detail::GetAllProperties::operator[](int flags) +{ + if (flags & (1<<0)) + detail::request_specific_timeout = timeout.timeout_; + + return *this; +} + + +void simppl::dbus::detail::GetAllProperties::operator()() +{ + stub_.get_all_properties_request(); +} + + +simppl::dbus::detail::GetAllProperties::getall_properties_holder_type +simppl::dbus::detail::GetAllProperties::async() +{ + return stub_.get_all_properties_request_async(); +} diff --git a/src/skeletonbase.cpp b/src/skeletonbase.cpp index fa1a56f..7b42538 100644 --- a/src/skeletonbase.cpp +++ b/src/skeletonbase.cpp @@ -4,6 +4,7 @@ #include "simppl/interface.h" #include "simppl/string.h" #include "simppl/vector.h" +#include "simppl/map.h" #define SIMPPL_SKELETONBASE_CPP #include "simppl/serverside.h" @@ -27,13 +28,6 @@ namespace dbus namespace detail { -struct FreeDeleter { - template - void operator()(T* o) { - ::free(o); - } -}; - using DemangledNamePtr = std::unique_ptr; } // namespace detail @@ -162,7 +156,7 @@ void SkeletonBase::respond_with(const Error& err) assert(current_request_); //assert(current_request_.requestor_->hasResponse()); - message_ptr_t rmsg = err.make_reply_for(*current_request().msg_); + message_ptr_t rmsg = current_request_.requestor_->_throw(*current_request_.msg_, err); dbus_connection_send(disp_->conn_, rmsg.get(), nullptr); current_request_.clear(); // only respond once!!! @@ -174,7 +168,7 @@ void SkeletonBase::respond_on(ServerRequestDescriptor& req, const Error& err) assert(req); //assert(req.requestor_->hasResponse()); - message_ptr_t rmsg = err.make_reply_for(*req.msg_); + message_ptr_t rmsg = req.requestor_->_throw(*req.msg_, err); dbus_connection_send(disp_->conn_, rmsg.get(), nullptr); req.clear(); @@ -195,27 +189,24 @@ DBusHandlerResult SkeletonBase::handle_request(DBusMessage* msg) #if SIMPPL_HAVE_INTROSPECTION if (!strcmp(interface_name, "org.freedesktop.DBus.Introspectable")) - { return handle_introspect_request(msg); - } #endif if (!strcmp(interface_name, "org.freedesktop.DBus.Properties")) { + if (!has_any_properties()) + return handle_error(msg, DBUS_ERROR_UNKNOWN_INTERFACE); + 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); } @@ -260,6 +251,10 @@ DBusHandlerResult SkeletonBase::handle_introspect_request(DBusMessage* msg) " \n" " \n" " \n" + " \n" + " \n" + " \n" + " \n" " \n" " \n" " \n" @@ -300,46 +295,89 @@ DBusHandlerResult SkeletonBase::handle_introspect_request(DBusMessage* msg) #endif // defined(SIMPPL_HAVE_INTROSPECTION) +DBusHandlerResult SkeletonBase::handle_property_getall_request(DBusMessage* msg, int iface_id) +{ + message_ptr_t response = make_message(dbus_message_new_method_return(msg)); + + DBusMessageIter iter; + DBusMessageIter _iter; + DBusMessageIter __iter; + + dbus_message_iter_init_append(response.get(), &iter); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &_iter); + + for (auto pp = property_heads_[iface_id]; pp; pp = pp->next_) + { + dbus_message_iter_open_container(&_iter, DBUS_TYPE_DICT_ENTRY, nullptr, &__iter); + + encode(__iter, pp->name_); + pp->eval(&__iter); + + dbus_message_iter_close_container(&_iter, &__iter); + } + + dbus_message_iter_close_container(&iter, &_iter); + + dbus_connection_send(disp_->conn_, response.get(), nullptr); + return DBUS_HANDLER_RESULT_HANDLED; +} + + DBusHandlerResult SkeletonBase::handle_property_request(DBusMessage* msg) { DBusMessageIter iter; std::string interface_name; - std::string property_name; + int iface_id; + + const char* method = dbus_message_get_member(msg); try { dbus_message_iter_init(msg, &iter); - decode(iter, interface_name, property_name); + decode(iter, interface_name); } catch(DecoderError&) { return handle_error(msg, DBUS_ERROR_INVALID_ARGS); } - int iface_id = find_interface(interface_name); + 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")) + if(!strcmp(method, "GetAll")) { - return handle_property_get_request(msg, *property); - } - else if(!strcmp(method, "Set")) - { - return handle_property_set_request(msg, *property, iter); + return handle_property_getall_request(msg, iface_id); } else { - return handle_error(msg, DBUS_ERROR_UNKNOWN_METHOD); + std::string property_name; + + try + { + decode(iter, property_name); + } + catch(DecoderError&) + { + return handle_error(msg, DBUS_ERROR_INVALID_ARGS); + } + + ServerPropertyBase* property = find_property(iface_id, property_name); + + if (!property) + return handle_error(msg, DBUS_ERROR_UNKNOWN_PROPERTY); + + 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); } } @@ -347,7 +385,24 @@ DBusHandlerResult SkeletonBase::handle_property_request(DBusMessage* msg) 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()); + DBusMessageIter iter; + + dbus_message_iter_init_append(response.get(), &iter); + + try + { + property.eval(&iter); + } + catch(const simppl::dbus::Error& err) + { + response = detail::ErrorFactory::reply(*msg, err); + } + catch(...) + { + simppl::dbus::Error e("simppl.dbus.UnhandledException"); + response = detail::ErrorFactory::reply(*msg, e); + } + dbus_connection_send(disp_->conn_, response.get(), nullptr); return DBUS_HANDLER_RESULT_HANDLED; } @@ -356,19 +411,20 @@ DBusHandlerResult SkeletonBase::handle_property_get_request(DBusMessage* msg, Se 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) + catch(const simppl::dbus::Error& err) { - response = err.make_reply_for(*msg); + response = detail::ErrorFactory::reply(*msg, err); } catch(...) { - simppl::dbus::Error err("simppl.dbus.UnhandledException"); - response = err.make_reply_for(*msg); + simppl::dbus::Error e("simppl.dbus.UnhandledException"); + response = detail::ErrorFactory::reply(*msg, e); } dbus_connection_send(disp_->conn_, response.get(), nullptr); @@ -388,14 +444,16 @@ DBusHandlerResult SkeletonBase::handle_interface_request(DBusMessage* msg, Serve { // 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); + auto r = detail::ErrorFactory::reply(*msg, err); + 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); + auto r = detail::ErrorFactory::reply(*msg, e); + dbus_connection_send(disp_->conn_, r.get(), nullptr); } @@ -410,11 +468,14 @@ DBusHandlerResult SkeletonBase::handle_interface_request(DBusMessage* msg, Serve DBusHandlerResult SkeletonBase::handle_error(DBusMessage* msg, const char* dbus_error) { simppl::dbus::Error err(dbus_error); - auto r = err.make_reply_for(*msg); + auto r = detail::ErrorFactory::reply(*msg, err); + 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 { @@ -506,6 +567,30 @@ void SkeletonBase::send_signal(const char* signame, int iface_id, std::function< } +void SkeletonBase::send_property_invalidate(const char* prop, int iface_id) +{ + // dummy map for changed properties, is empty -> type doesn't matter + static std::map m; + + message_ptr_t msg = make_message(dbus_message_new_signal(objectpath(), "org.freedesktop.DBus.Properties", "PropertiesChanged")); + + DBusMessageIter iter; + dbus_message_iter_init_append(msg.get(), &iter); + + encode(iter, iface(iface_id)); + encode(iter, m); + + // the invalidated property as the only element in a vector + DBusMessageIter inv_iter; + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &inv_iter); + encode(inv_iter, prop); + + dbus_message_iter_close_container(&iter, &inv_iter); + + dbus_connection_send(disp_->conn_, msg.get(), nullptr); +} + + void SkeletonBase::send_property_change(const char* prop, int iface_id, std::function&& f) { static std::vector invalid; diff --git a/src/stubbase.cpp b/src/stubbase.cpp index d61dabb..69fa8d8 100644 --- a/src/stubbase.cpp +++ b/src/stubbase.cpp @@ -1,4 +1,5 @@ #include "simppl/stub.h" +#include #include "simppl/dispatcher.h" #include "simppl/string.h" @@ -11,6 +12,36 @@ #include +namespace { + +struct TimeoutRAIIHelper +{ + TimeoutRAIIHelper(simppl::dbus::Dispatcher& disp) + : timeout_(disp.request_timeout()) + { + if (simppl::dbus::detail::request_specific_timeout.count() > 0) + timeout_ = simppl::dbus::detail::request_specific_timeout.count(); + } + + ~TimeoutRAIIHelper() + { + simppl::dbus::detail::request_specific_timeout = std::chrono::milliseconds(0); + } + + operator int() + { + return timeout_; + } + + int timeout_; +}; + +} // namespace + + +// --------------------------------------------------------------------- + + using namespace std::literals::chrono_literals; @@ -21,11 +52,12 @@ namespace dbus { StubBase::StubBase() - : objectpath_(nullptr) + : get_all_properties(*this) + , objectpath_(nullptr) , conn_state_(ConnectionState::Disconnected) , disp_(nullptr) , signals_(nullptr) - , properties_(nullptr) + , attached_properties_(0) { // NOOP } @@ -73,6 +105,12 @@ void StubBase::init(char* iface, const char* role) } +void StubBase::add_property(ClientPropertyBase* property) +{ + properties_.push_back(std::make_pair(property, false)); +} + + DBusConnection* StubBase::conn() { return disp().conn_; @@ -86,24 +124,110 @@ Dispatcher& StubBase::disp() } -PendingCall StubBase::send_request(const char* method_name, std::function&& f, bool is_oneway) +void StubBase::get_all_properties_request() { - message_ptr_t msg = make_message(dbus_message_new_method_call(busname().c_str(), objectpath(), iface(), method_name)); + message_ptr_t msg = make_message(dbus_message_new_method_call(busname().c_str(), objectpath(), "org.freedesktop.DBus.Properties", "GetAll")); DBusPendingCall* pending = nullptr; DBusMessageIter iter; dbus_message_iter_init_append(msg.get(), &iter); - - f(iter); - if (!is_oneway) + encode(iter, iface()); + + dbus_connection_send_with_reply(conn(), msg.get(), &pending, TimeoutRAIIHelper(disp())); + + dbus_pending_call_block(pending); + + msg.reset(dbus_pending_call_steal_reply(pending)); + dbus_pending_call_unref(pending); + + auto cs = get_all_properties_handle_response(*msg, true); + + // unused + (void)cs; +} + + +simppl::dbus::CallState StubBase::get_all_properties_handle_response(DBusMessage& response, bool __throw) +{ + DBusMessageIter iter; + dbus_message_iter_init(&response, &iter); + + // blocking version -> throw exception if error occurred + if (__throw) + { + if (dbus_message_get_type(&response) == DBUS_MESSAGE_TYPE_ERROR) + { + simppl::dbus::Error err; + detail::ErrorFactory::init(err, response); + + throw err; + } + } + + CallState cs(response); + + if (cs) { - int timeout = disp().request_timeout(); + // open array container + DBusMessageIter _iter; + dbus_message_iter_recurse(&iter, &_iter); + + while(dbus_message_iter_get_arg_type(&_iter) != 0) + { + // open dict container + DBusMessageIter __iter; + dbus_message_iter_recurse(&_iter, &__iter); + + std::string propname; + decode(__iter, propname); - if (detail::request_specific_timeout.count() > 0) - timeout = detail::request_specific_timeout.count(); + // get property by name + auto propiter = std::find_if(properties_.begin(), properties_.end(), [propname](auto& pair){ return propname == pair.first->name_; }); - dbus_connection_send_with_reply(disp().conn_, msg.get(), &pending, timeout); + // get value and call + if (propiter != properties_.end()) + propiter->first->eval(&__iter); + + dbus_message_iter_next(&_iter); + } + } + + return cs; +} + + +StubBase::getall_properties_holder_type StubBase::get_all_properties_request_async() +{ + message_ptr_t msg = make_message(dbus_message_new_method_call(busname().c_str(), objectpath(), "org.freedesktop.DBus.Properties", "GetAll")); + DBusPendingCall* pending = nullptr; + + { + DBusMessageIter iter; + dbus_message_iter_init_append(msg.get(), &iter); + + encode(iter, iface()); + } + + dbus_connection_send_with_reply(conn(), msg.get(), &pending, TimeoutRAIIHelper(disp())); + + return getall_properties_holder_type(PendingCall(dbus_message_get_serial(msg.get()), pending), *this); +} + + +PendingCall StubBase::send_request(ClientMethodBase* method, std::function&& f, bool is_oneway) +{ + message_ptr_t msg = make_message(dbus_message_new_method_call(busname().c_str(), objectpath(), iface(), method->method_name_)); + DBusPendingCall* pending = nullptr; + + DBusMessageIter iter; + dbus_message_iter_init_append(msg.get(), &iter); + + f(iter); + + if (!is_oneway) + { + dbus_connection_send_with_reply(disp().conn_, msg.get(), &pending, TimeoutRAIIHelper(disp())); } else { @@ -114,43 +238,32 @@ PendingCall StubBase::send_request(const char* method_name, std::function&& f, bool is_oneway) +message_ptr_t StubBase::send_request_and_block(ClientMethodBase* method, std::function&& f, bool is_oneway) { - message_ptr_t msg = make_message(dbus_message_new_method_call(busname().c_str(), objectpath(), iface(), method_name)); + message_ptr_t msg = make_message(dbus_message_new_method_call(busname().c_str(), objectpath(), iface(), method->method_name_)); DBusPendingCall* pending = nullptr; message_ptr_t rc(nullptr, &dbus_message_unref); DBusMessageIter iter; dbus_message_iter_init_append(msg.get(), &iter); - + f(iter); if (!is_oneway) { - int timeout = disp().request_timeout(); - - if (detail::request_specific_timeout.count() > 0) - timeout = detail::request_specific_timeout.count(); - - dbus_connection_send_with_reply(disp().conn_, msg.get(), &pending, timeout); - - detail::request_specific_timeout = std::chrono::milliseconds(0); + dbus_connection_send_with_reply(disp().conn_, msg.get(), &pending, TimeoutRAIIHelper(disp())); dbus_pending_call_block(pending); rc = make_message(dbus_pending_call_steal_reply(pending)); dbus_pending_call_unref(pending); - CallState cs(*rc); - - if (!cs) - cs.throw_exception(); + if (dbus_message_get_type(rc.get()) == DBUS_MESSAGE_TYPE_ERROR) + method->_throw(*rc); } else { @@ -180,17 +293,17 @@ void StubBase::connection_state_changed(ConnectionState state) void StubBase::register_signal(ClientSignalBase& sigbase) { assert(disp_); - + auto sig = signals_; while(sig) { // already attached? if (&sigbase == sig) return; - + sig = sig->next_; } - + disp_->register_signal(*this, sigbase); sigbase.next_ = signals_; @@ -204,14 +317,14 @@ void StubBase::unregister_signal(ClientSignalBase& sigbase) ClientSignalBase* last = nullptr; auto sig = signals_; - + while(sig) { // found... if (&sigbase == sig) { disp_->unregister_signal(*this, sigbase); - + // remove from list if (last) { @@ -219,70 +332,47 @@ void StubBase::unregister_signal(ClientSignalBase& sigbase) } else signals_ = sig->next_; - + sigbase.next_ = nullptr; break; } - + last = sig; sig = sig->next_; } } -void StubBase::attach_property(ClientPropertyBase& prop) +void StubBase::attach_property(ClientPropertyBase* prop) { assert(disp_); - - if (!properties_) - disp_->register_properties(*this); - auto p = properties_; - while(p) + auto propiter = std::find_if(properties_.begin(), properties_.end(), [prop](auto& pair){ return prop == pair.first; }); + + if (propiter->second == false) { - // already attached? - if (&prop == p) - return; - - p = p->next_; + propiter->second = true; + + if (++attached_properties_ == 1) + disp_->register_properties(*this); } - - prop.next_ = properties_; - properties_ = ∝ } -void StubBase::detach_property(ClientPropertyBase& prop) +void StubBase::detach_property(ClientPropertyBase* prop) { assert(disp_); - ClientPropertyBase* last = nullptr; - auto p = properties_; - - while(p) + + auto propiter = std::find_if(properties_.begin(), properties_.end(), [prop](auto& pair){ return prop == pair.first; }); + + if (propiter->second) { - // found... - if (&prop == p) - { - // remove from list - if (last) - { - last->next_ = p->next_; - } - else - properties_ = p->next_; - - prop.next_ = nullptr; - break; - } - - last = p; - p = p->next_; + propiter->second = false; + + if (--attached_properties_ == 0) + disp_->unregister_properties(*this); } - - // empty? - if (!properties_) - disp_->unregister_properties(*this); } @@ -295,14 +385,14 @@ void StubBase::cleanup() disp_->unregister_signal(*this, *sig); sig = sig->next_; } - + signals_ = nullptr; // cleanup property registration - if (properties_) + if (attached_properties_) disp_->unregister_properties(*this); - properties_ = nullptr; + attached_properties_ = 0; disp_ = nullptr; } @@ -315,10 +405,11 @@ PendingCall StubBase::get_property_async(const char* name) DBusMessageIter iter; dbus_message_iter_init_append(msg.get(), &iter); - + encode(iter, iface(), name); - dbus_connection_send_with_reply(conn(), msg.get(), &pending, DBUS_TIMEOUT_USE_DEFAULT); + // TODO request specific timeout handling here + dbus_connection_send_with_reply(conn(), msg.get(), &pending, disp().request_timeout()); return PendingCall(dbus_message_get_serial(msg.get()), pending); } @@ -327,19 +418,30 @@ PendingCall StubBase::get_property_async(const char* name) message_ptr_t StubBase::get_property(const char* name) { message_ptr_t msg = make_message(dbus_message_new_method_call(busname().c_str(), objectpath(), "org.freedesktop.DBus.Properties", "Get")); - DBusPendingCall* pending = nullptr; DBusMessageIter iter; dbus_message_iter_init_append(msg.get(), &iter); encode(iter, iface(), name); - dbus_connection_send_with_reply(conn(), msg.get(), &pending, DBUS_TIMEOUT_USE_DEFAULT); + DBusError err; + dbus_error_init(&err); + + // TODO request specific timeout handling here + DBusMessage* reply = dbus_connection_send_with_reply_and_block(conn(), msg.get(), disp().request_timeout(), &err); + + // drop original message + msg.reset(reply); - dbus_pending_call_block(pending); + // check for reponse + if (dbus_error_is_set(&err)) + { + // TODO make static function to throw from DBusError + Error ex(err.name, err.message); - msg.reset(dbus_pending_call_steal_reply(pending)); - dbus_pending_call_unref(pending); + dbus_error_free(&err); + throw ex; + } return msg; } @@ -351,7 +453,7 @@ void StubBase::set_property(const char* name, std::functionname_) - { - p->eval(item_iterator); - break; - } - - p = p->next_; - } - + auto propiter = std::find_if(properties_.begin(), properties_.end(), [&property_name](auto& pair){ return property_name == pair.first->name_; }); + + if (propiter != properties_.end() && propiter->second) + propiter->first->eval(&item_iterator); + + // advance to next element + dbus_message_iter_next(&iter); + } + + // check for invalidated properties + dbus_message_iter_next(&it); + dbus_message_iter_recurse(&it, &iter); + + while(dbus_message_iter_get_arg_type(&iter) != 0) + { + std::string property_name; + decode(iter, property_name); + + auto propiter = std::find_if(properties_.begin(), properties_.end(), [&property_name](auto& pair){ return property_name == pair.first->name_; }); + + if (propiter != properties_.end() && propiter->second) + propiter->first->eval(nullptr); + // advance to next element dbus_message_iter_next(&iter); } @@ -436,18 +551,18 @@ void StubBase::try_handle_signal(DBusMessage* msg) else { auto sig = signals_; - + while(sig) { if (!strcmp(sig->name_, dbus_message_get_member(msg))) { DBusMessageIter iter; dbus_message_iter_init(msg, &iter); - + sig->eval(iter); break; } - + sig = sig->next_; } } diff --git a/src/util.cpp b/src/util.cpp index b9a2423..ff68fb9 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -121,6 +121,29 @@ const char* find_next_interface(const char* s) { } } + +char* make_error_name(char* str) +{ + char* to = str; + char* from = str; + + while(*from) + { + if (*from == ':') + { + *to++ = '.'; + from += 2; + } + else + *to++ = *from++; + } + + *to = '\0'; + + return str; +} + + } // namespace detail } // namespace dbus diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b7d7f1d..bc27978 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -34,7 +34,7 @@ add_executable(unittests no_interface.cpp utils.cpp ) -if(BOOST_FOUND) +if(Boost_FOUND) target_compile_definitions(unittests PRIVATE SIMPPL_HAVE_BOOST_FUSION=1) endif() target_link_libraries(unittests gtest gtest_main simppl rt) @@ -49,11 +49,11 @@ if(DBUS_RUN_SESSION) endif() -# not a test, just an introspection example +# not a test, just an introspection example add_executable(introserver introserver.cpp ) -if(BOOST_FOUND) +if(Boost_FOUND) target_compile_definitions(introserver PRIVATE SIMPPL_HAVE_BOOST_FUSION=1) endif() target_link_libraries(introserver simppl rt) diff --git a/tests/async_server.cpp b/tests/async_server.cpp index 05a740a..3fbff6f 100644 --- a/tests/async_server.cpp +++ b/tests/async_server.cpp @@ -48,13 +48,13 @@ struct Client : simppl::dbus::Stub { connected >> [this](simppl::dbus::ConnectionState s){ EXPECT_EQ(simppl::dbus::ConnectionState::Connected, s); - - add.async(42, 777) >> [this](simppl::dbus::CallState s, double d){ + + add.async(42, 777) >> [this](const simppl::dbus::CallState& s, double d){ EXPECT_TRUE((bool)s); oneway(42); }; - - echo.async(42, 3.1415) >> [this](simppl::dbus::CallState s, int i, double d){ + + echo.async(42, 3.1415) >> [this](const simppl::dbus::CallState& s, int i, double d){ EXPECT_TRUE((bool)s); haveEcho_ = true; }; @@ -75,13 +75,13 @@ struct ShutdownClient : simppl::dbus::Stub if (s == simppl::dbus::ConnectionState::Connected) { - add.async(42, 777) >> [this](simppl::dbus::CallState s, double d){ + add.async(42, 777) >> [this](const simppl::dbus::CallState& s, double d){ EXPECT_FALSE((bool)s); EXPECT_STREQ(s.exception().name(), "org.freedesktop.DBus.Error.NoReply"); - + disp().stop(); }; - + oneway(42); } @@ -103,11 +103,11 @@ struct Server : simppl::dbus::Skeleton oneway >> [this](int){ disp().stop(); }; - + add >> [this](int i, double d){ req_ = defer_response(); }; - + echo >> [this](int i, double d){ respond_on(req_, add(d)); respond_with(echo(i, d)); diff --git a/tests/errors.cpp b/tests/errors.cpp index f7ca298..6bedf40 100644 --- a/tests/errors.cpp +++ b/tests/errors.cpp @@ -3,6 +3,7 @@ #include "simppl/stub.h" #include "simppl/skeleton.h" #include "simppl/interface.h" +#include "simppl/struct.h" #include @@ -11,11 +12,42 @@ using namespace std::literals::chrono_literals; using simppl::dbus::in; using simppl::dbus::out; +using simppl::dbus::_throw; namespace test { +/** + * Due to the virtual function table provided by the base std::exception + * is it not possible to use the make_serializer here. + * + * But with boost::fusion everything works ok. + * + * @note that if mixing simppl code with raw DBus calls you must make sure + * that when using the exceptions feature, the dbus message must contain + * a message in addition to the error name, otherwise deserialization would + * yield an error. + */ +struct MyException : simppl::dbus::Error +{ + /// class needs a default constructor which is used during deserialization. + /// If such an exception instance is returned on the server, the name + /// will be set via C++ RTTI. + MyException() = default; + + /// This is the instance that can be thrown in user code. + MyException(int return_code) + : simppl::dbus::Error("My.Exception") + , rc(return_code) + { + // NOOP + } + + int rc; +}; + + INTERFACE(Errors) { Method stop; @@ -23,11 +55,22 @@ INTERFACE(Errors) Method<> hello; Method, out> hello1; +#if SIMPPL_HAVE_BOOST_FUSION + Method<_throw> hello2; + Method<_throw> hello3; + Method<_throw> hello4; +#endif + inline Errors() : INIT(stop) , INIT(hello) , INIT(hello1) +#if SIMPPL_HAVE_BOOST_FUSION + , INIT(hello2) + , INIT(hello3) + , INIT(hello4) +#endif { // NOOP } @@ -35,6 +78,16 @@ INTERFACE(Errors) } + +#if SIMPPL_HAVE_BOOST_FUSION +// must be outside of namespace +BOOST_FUSION_ADAPT_STRUCT( + test::MyException, + (int, rc) +) +#endif + + using namespace test; @@ -48,17 +101,31 @@ struct Client : simppl::dbus::Stub { connected >> [this](simppl::dbus::ConnectionState s){ EXPECT_EQ(simppl::dbus::ConnectionState::Connected, s); - - hello.async() >> [this](simppl::dbus::CallState state){ + + hello.async() >> [this](const simppl::dbus::CallState& state){ EXPECT_FALSE((bool)state); EXPECT_STREQ(state.exception().name(), "shit.happens"); - hello1.async(42) >> [this](simppl::dbus::CallState state, int){ - EXPECT_FALSE((bool)state); - EXPECT_STREQ(state.exception().name(), "also.shit"); - - disp().stop(); +#if SIMPPL_HAVE_BOOST_FUSION + hello2.async() >> [this](const simppl::dbus::TCallState& state){ + EXPECT_FALSE((bool)state); + EXPECT_STREQ(state.exception().name(), "My.Exception"); + EXPECT_EQ(42, state.exception().rc); + + hello3.async() >> [this](const simppl::dbus::TCallState& state){ + EXPECT_FALSE((bool)state); + EXPECT_STREQ(state.exception().name(), "simppl.dbus.UnhandledException"); +#endif + hello1.async(42) >> [this](const simppl::dbus::CallState& state, int){ + EXPECT_FALSE((bool)state); + EXPECT_STREQ(state.exception().name(), "also.shit"); + + disp().stop(); + }; +#if SIMPPL_HAVE_BOOST_FUSION + }; }; +#endif }; }; } @@ -70,17 +137,31 @@ struct Server : simppl::dbus::Skeleton Server(simppl::dbus::Dispatcher& d, const char* rolename) : simppl::dbus::Skeleton(d, rolename) { - stop >> [this](){ - disp().stop(); + stop >> [this](){ + disp().stop(); }; - + hello >> [this](){ respond_with(simppl::dbus::Error("shit.happens")); }; - + hello1 >> [this](int){ respond_with(simppl::dbus::Error("also.shit")); }; + +#if SIMPPL_HAVE_BOOST_FUSION + hello2 >> [this](){ + respond_with(MyException(42)); + }; + + hello3 >> [this](){ + throw std::runtime_error("Ooops"); + }; + + hello4 >> [this](){ + respond_with(MyException()); // will produce an DBus error test.MyException + }; +#endif } }; @@ -150,6 +231,78 @@ TEST(Errors, blocking) ASSERT_FALSE(true); } +#if SIMPPL_HAVE_BOOST_FUSION + try + { + stub.hello2(); + + // never reach + ASSERT_FALSE(true); + } + catch(const test::MyException& e) + { + EXPECT_STREQ(e.name(), "My.Exception"); + EXPECT_EQ(42, e.rc); + } + catch(const simppl::dbus::Error& e) + { + // does not reach the default exception handler + ASSERT_FALSE(true); + } + catch(...) + { + ASSERT_FALSE(true); + } + + try + { + stub.hello3(); + + // never reach + ASSERT_FALSE(true); + } + catch(const test::MyException& e) + { + EXPECT_STREQ(e.name(), "simppl.dbus.UnhandledException"); + // e.rc is not set + } + catch(const simppl::dbus::Error& e) + { + // does not reach the default exception handler + ASSERT_FALSE(true); + } + catch(...) + { + ASSERT_FALSE(true); + } + + try + { + stub.hello4(); + + // never reach + ASSERT_FALSE(true); + } + catch(const test::MyException& e) + { + EXPECT_STREQ(e.name(), "test.MyException"); + EXPECT_EQ(0, e.rc); + } + catch(const simppl::dbus::RuntimeError& e) + { + ASSERT_FALSE(true); + } + catch(const simppl::dbus::Error& e) + { + // does not reach the default exception handler + ASSERT_FALSE(true); + } + catch(...) + { + ASSERT_FALSE(true); + } +#endif + stub.stop(); t.join(); } diff --git a/tests/introserver.cpp b/tests/introserver.cpp index ede510d..7d5aebe 100644 --- a/tests/introserver.cpp +++ b/tests/introserver.cpp @@ -72,7 +72,7 @@ struct ComboEntry struct Menu { - typedef simppl::Variant entry_type; + typedef std::variant entry_type; typedef std::map> menu_entries_type; diff --git a/tests/multi_interface.cpp b/tests/multi_interface.cpp index 9db00e3..4ee4d44 100644 --- a/tests/multi_interface.cpp +++ b/tests/multi_interface.cpp @@ -95,7 +95,7 @@ TEST(MultiInterface, adder_interface) connected >> [this](simppl::dbus::ConnectionState s) { EXPECT_EQ(simppl::dbus::ConnectionState::Connected, s); - Add.async(1, 1) >> [&](simppl::dbus::CallState state, int result) { + Add.async(1, 1) >> [&](const simppl::dbus::CallState& state, int result) { EXPECT_TRUE(static_cast(state)); EXPECT_EQ(result, 2); disp().stop(); @@ -121,7 +121,7 @@ TEST(MultiInterface, multiplier_interface) connected >> [this](simppl::dbus::ConnectionState s) { EXPECT_EQ(simppl::dbus::ConnectionState::Connected, s); - Multiply.async(1, 1) >> [&](simppl::dbus::CallState state, int result) { + Multiply.async(1, 1) >> [&](const simppl::dbus::CallState& state, int result) { EXPECT_TRUE(static_cast(state)); EXPECT_EQ(result, 1); disp().stop(); diff --git a/tests/no_interface.cpp b/tests/no_interface.cpp index fa39222..d6227c3 100644 --- a/tests/no_interface.cpp +++ b/tests/no_interface.cpp @@ -79,7 +79,7 @@ TEST(NoInterface, introspect) connected >> [this](simppl::dbus::ConnectionState s) { EXPECT_EQ(simppl::dbus::ConnectionState::Connected, s); - Introspect.async() >> [&](simppl::dbus::CallState state, std::string result) { + Introspect.async() >> [&](const simppl::dbus::CallState& state, std::string result) { EXPECT_TRUE(static_cast(state)); EXPECT_EQ(result, "\n" "\n" diff --git a/tests/properties.cpp b/tests/properties.cpp index 7e804ac..8056810 100644 --- a/tests/properties.cpp +++ b/tests/properties.cpp @@ -35,6 +35,7 @@ INTERFACE(Properties) Property data; Property> props; + Property str_prop; Signal mayShutdown; @@ -44,12 +45,26 @@ INTERFACE(Properties) , INIT(shutdown) , INIT(data) , INIT(props) + , INIT(str_prop) , INIT(mayShutdown) { // NOOP } }; + +INTERFACE(NoProperties) +{ + Method shutdown; + + inline + NoProperties() + : INIT(shutdown) + { + // NOOP + } +}; + } using namespace test; @@ -66,7 +81,7 @@ struct Client : simppl::dbus::Stub connected >> [this](simppl::dbus::ConnectionState s){ EXPECT_EQ(simppl::dbus::ConnectionState::Connected, s); - props.attach() >> [this](simppl::dbus::CallState, const std::map& props){ + props.attach() >> [this](const simppl::dbus::CallState&, const std::map& props){ ++callback_count_; if (callback_count_ == 1) // the property Get(...) from the attach @@ -113,7 +128,7 @@ struct GetterClient : simppl::dbus::Stub EXPECT_EQ(simppl::dbus::ConnectionState::Connected, s); // never use operator= here, it's blocking! - data.get_async() >> [this](simppl::dbus::CallState cs, int i){ + data.get_async() >> [this](const simppl::dbus::CallState& cs, int i){ EXPECT_TRUE((bool)cs); EXPECT_EQ(i, 4711); @@ -124,6 +139,26 @@ struct GetterClient : simppl::dbus::Stub }; +struct GetterErrorClient : simppl::dbus::Stub +{ + GetterErrorClient(simppl::dbus::Dispatcher& d) + : simppl::dbus::Stub(d, "s") + { + connected >> [this](simppl::dbus::ConnectionState s){ + EXPECT_EQ(simppl::dbus::ConnectionState::Connected, s); + + // never use operator= here, it's blocking! + data.get_async() >> [this](const simppl::dbus::CallState& cs, int i){ + EXPECT_FALSE((bool)cs); + EXPECT_STREQ(cs.exception().what(), "Not.Available"); + + disp().stop(); + }; + }; + } +}; + + struct MultiClient : simppl::dbus::Stub { MultiClient(simppl::dbus::Dispatcher& d, bool attach) @@ -135,7 +170,7 @@ struct MultiClient : simppl::dbus::Stub if (attach_) { - props.attach() >> [this](simppl::dbus::CallState, const std::map& props){ + props.attach() >> [this](const simppl::dbus::CallState&, const std::map& props){ ++callback_count_; if (callback_count_ == 1) @@ -172,7 +207,7 @@ struct SetterClient : simppl::dbus::Stub connected >> [this](simppl::dbus::ConnectionState s){ EXPECT_EQ(simppl::dbus::ConnectionState::Connected, s); - data.attach() >> [this](simppl::dbus::CallState, int i){ + data.attach() >> [this](const simppl::dbus::CallState&, int i){ ++callback_count_; if (callback_count_ == 1) // the property Get(...) from the attach @@ -186,7 +221,7 @@ struct SetterClient : simppl::dbus::Stub }; // never use operator= here, it's blocking! - data.set_async(5555) >> [this](simppl::dbus::CallState cs){ + data.set_async(5555) >> [this](const simppl::dbus::CallState& cs){ EXPECT_TRUE((bool)cs); disp().stop(); @@ -206,22 +241,22 @@ struct InvalidSetterClient : simppl::dbus::Stub connected >> [this](simppl::dbus::ConnectionState s){ EXPECT_EQ(simppl::dbus::ConnectionState::Connected, s); - data.attach() >> [this](simppl::dbus::CallState, int) { + data.attach() >> [this](const simppl::dbus::CallState&, int) { ++callback_count_; }; // never use operator= here, it's blocking! - data.set_async(-1) >> [this](simppl::dbus::CallState cs){ + data.set_async(-1) >> [this](const simppl::dbus::CallState& cs){ EXPECT_FALSE((bool)cs); EXPECT_STREQ(cs.exception().what(), "simppl.dbus.UnhandledException"); }; - data.set_async(1) >> [this](simppl::dbus::CallState cs){ + data.set_async(1) >> [this](const simppl::dbus::CallState& cs){ EXPECT_FALSE((bool)cs); EXPECT_STREQ(cs.exception().what(), "Invalid.Argument"); }; - data.set_async(2) >> [this](simppl::dbus::CallState cs){ + data.set_async(2) >> [this](const simppl::dbus::CallState& cs){ EXPECT_TRUE((bool)cs); EXPECT_EQ(2, callback_count_); // one from attach, one from data change @@ -259,12 +294,25 @@ struct Server : simppl::dbus::Skeleton // initialize properties data = 4711; props = { { One, "One" }, { Two, "Two" } }; + str_prop = "Hallo Welt"; } int calls_ = 0; }; +struct NoPropertiesServer : simppl::dbus::Skeleton +{ + NoPropertiesServer(simppl::dbus::Dispatcher& d, const char* rolename) + : simppl::dbus::Skeleton(d, rolename) + { + shutdown >> [this](){ + disp().stop(); + }; + } +}; + + struct InvalidSetterServer : simppl::dbus::Skeleton { InvalidSetterServer(simppl::dbus::Dispatcher& d, const char* rolename) @@ -300,11 +348,11 @@ struct NonCachingTestClient : simppl::dbus::Stub EXPECT_EQ(simppl::dbus::ConnectionState::Connected, s); // never use operator= here, it's blocking! - data.get_async() >> [this](simppl::dbus::CallState cs, int i){ + data.get_async() >> [this](const simppl::dbus::CallState& cs, int i){ EXPECT_TRUE((bool)cs); EXPECT_EQ(i, 4711); - data.attach() >> [this](simppl::dbus::CallState cs, int i){ + data.attach() >> [this](const simppl::dbus::CallState& cs, int i){ EXPECT_TRUE((bool)cs); if (i == 4711) @@ -320,6 +368,100 @@ struct NonCachingTestClient : simppl::dbus::Stub }; +struct GetAllClient : simppl::dbus::Stub +{ + GetAllClient(simppl::dbus::Dispatcher& d) + : simppl::dbus::Stub(d, "s") + { + connected >> [this](simppl::dbus::ConnectionState s){ + EXPECT_EQ(simppl::dbus::ConnectionState::Connected, s); + + // you only get the properties with a callback set + // so first connect the desired properties + data >> [this](const simppl::dbus::CallState& cs, int i) { + EXPECT_TRUE((bool)cs); + EXPECT_EQ(i, 4711); + + ++count_; + }; + + str_prop >> [this](const simppl::dbus::CallState& cs, const std::string& str) { + EXPECT_TRUE((bool)cs); + EXPECT_EQ(str, "Hallo Welt"); + + ++count_; + }; + + // now call - callbacks will be called in background just before + // this callback gets called... + get_all_properties.async() >> [this](const simppl::dbus::CallState& cs){ + EXPECT_TRUE((bool)cs); + + // the other two callbacks are already evaluated... + EXPECT_EQ(2, count_); + + // ok, test finished + disp().stop(); + }; + }; + } + + int count_ = 0; +}; + + +struct InvalidatedPropertyClient : simppl::dbus::Stub +{ + InvalidatedPropertyClient(simppl::dbus::Dispatcher& d) + : simppl::dbus::Stub(d, "s") + { + connected >> [this](simppl::dbus::ConnectionState s){ + EXPECT_EQ(simppl::dbus::ConnectionState::Connected, s); + + data.attach() >> [this](const simppl::dbus::CallState& cs, int) { + ++calls_; + EXPECT_FALSE((bool)cs); + EXPECT_STREQ(cs.what(), "simppl.dbus.Invalid"); + + shutdown(); + }; + }; + } + + int calls_ = 0; +}; + + +struct InvalidatedPropertyServer : simppl::dbus::Skeleton +{ + InvalidatedPropertyServer(simppl::dbus::Dispatcher& d) + : simppl::dbus::Skeleton(d, "s") + , countdown_(3) + { + // initialize attributes callbacks + data.on_read([](){ + + throw simppl::dbus::Error("simppl.dbus.Invalid"); + + // complete lambda with return value + return 42; + }); + + // just send message + shutdown >> [this](){ + if(--countdown_ > 0) + { + data.invalidate(); // sending error simppl.dbus.Invalid + } + else + disp().stop(); + }; + } + + int countdown_; +}; + + struct NonCachingPropertyServer : simppl::dbus::Skeleton { NonCachingPropertyServer(simppl::dbus::Dispatcher& d, const char* rolename) @@ -342,9 +484,75 @@ struct NonCachingPropertyServer : simppl::dbus::Skeleton }; +struct ErrorThrowingPropertyServer : simppl::dbus::Skeleton +{ + ErrorThrowingPropertyServer(simppl::dbus::Dispatcher& d, const char* rolename) + : simppl::dbus::Skeleton(d, rolename) + { + // initialize attributes callbacks + data.on_read([](){ + throw simppl::dbus::Error("Not.Available"); + + // complete lambda with return value + return 42; + }); + + // stop thread + shutdown >> [this](){ + disp().stop(); + }; + } +}; + + } // anonymous namespace +TEST(Properties, blocking_get_error) +{ + simppl::dbus::Dispatcher d("bus:session"); + + std::thread t([](){ + + simppl::dbus::Dispatcher d("bus:session"); + ErrorThrowingPropertyServer s(d, "s"); + d.run(); + }); + + // wait for server to get ready + std::this_thread::sleep_for(200ms); + + simppl::dbus::Stub c(d, "s"); + + try + { + int val = c.data.get(); + (void)val; + + // never arrive here! + EXPECT_FALSE(true); + } + catch(simppl::dbus::Error& err) + { + EXPECT_STREQ("Not.Available", err.name()); + } + + c.shutdown(); // stop server + t.join(); +} + + +TEST(Properties, get_async_error) +{ + simppl::dbus::Dispatcher d("bus:session"); + + ErrorThrowingPropertyServer s(d, "s"); + GetterErrorClient c(d); + + d.run(); +} + + TEST(Properties, attr) { simppl::dbus::Dispatcher d("bus:session"); @@ -405,7 +613,7 @@ TEST(Properties, blocking_set) } -TEST(Properties, blocking_get) +TEST(Properties, get_blocking) { simppl::dbus::Dispatcher d("bus:session"); @@ -425,6 +633,84 @@ TEST(Properties, blocking_get) } +TEST(Properties, getall_blocking) +{ + simppl::dbus::Dispatcher d("bus:session"); + + std::thread t(blockrunner); + + // wait for server to get ready + std::this_thread::sleep_for(200ms); + + simppl::dbus::Stub c(d, "s"); + + int ival = 0; + std::map mval; + std::string sval; + + // first connect all properties + c.data >> [&ival](const simppl::dbus::CallState&, int i) { ival = i; }; + c.props >> [&mval](const simppl::dbus::CallState&, const std::map& val) { mval = val; }; + c.str_prop >> [&sval](const simppl::dbus::CallState&, const std::string& str) { sval = str; }; + + // now call - callbacks will be called in background + c.get_all_properties(); + + c.get_all_properties[42](); + + EXPECT_EQ(ival, 4711); + EXPECT_EQ(sval, "Hallo Welt"); + + c.shutdown(); // stop server + t.join(); +} + + +TEST(Properties, getall_async) +{ + simppl::dbus::Dispatcher d("bus:session"); + + Server s(d, "s"); + GetAllClient c(d); + + d.run(); +} + + +TEST(Properties, getall_no_properties_blocking) +{ + simppl::dbus::Dispatcher d("bus:session"); + + std::thread t([](){ + simppl::dbus::Dispatcher d("bus:session"); + NoPropertiesServer s(d, "s"); + + d.run(); + }); + + // wait for server to get ready + std::this_thread::sleep_for(200ms); + + simppl::dbus::Stub c(d, "s"); + + try + { + // now call - no properties -> exception + c.get_all_properties(); + + // never reached + EXPECT_TRUE(false); + } + catch(simppl::dbus::Error& err) + { + EXPECT_NE(nullptr, strstr(err.what(), "UnknownInterface")); + } + + c.shutdown(); // stop server + t.join(); +} + + TEST(Properties, set) { simppl::dbus::Dispatcher d("bus:session"); @@ -467,3 +753,15 @@ TEST(Properties, non_caching) d.run(); } + + +TEST(Properties, invalidate) +{ + simppl::dbus::Dispatcher d("bus:session"); + + InvalidatedPropertyServer s(d); + InvalidatedPropertyClient c(d); + + d.run(); + EXPECT_EQ(c.calls_, 3); +} diff --git a/tests/simple.cpp b/tests/simple.cpp index 7ef9edb..c12edc2 100644 --- a/tests/simple.cpp +++ b/tests/simple.cpp @@ -117,14 +117,14 @@ struct Client : simppl::dbus::Stub connected >> [this](simppl::dbus::ConnectionState s){ EXPECT_EQ(simppl::dbus::ConnectionState::Connected, s); - this->hello.async() >> [this](simppl::dbus::CallState state){ + this->hello.async() >> [this](const simppl::dbus::CallState& state){ EXPECT_TRUE((bool)state); this->oneway(42); const wchar_t* wct = L"Hello world"; - this->echo_wchart.async(wct) >> [this](simppl::dbus::CallState state, wchar_t* p){ + this->echo_wchart.async(wct) >> [this](const simppl::dbus::CallState& state, wchar_t* p){ EXPECT_TRUE((bool)state); EXPECT_EQ(0, wcscmp(p, L"Hello world")); @@ -150,7 +150,7 @@ struct CancelClient : simppl::dbus::Stub if (s == simppl::dbus::ConnectionState::Connected) // FIXME add bool cast operator { - simppl::dbus::PendingCall p = this->hello_wait_for_some_time.async() >> [this](simppl::dbus::CallState state){ + simppl::dbus::PendingCall p = this->hello_wait_for_some_time.async() >> [this](const simppl::dbus::CallState& state){ // never called!!! EXPECT_TRUE(false); @@ -207,14 +207,14 @@ struct PropertyClient : simppl::dbus::Stub EXPECT_EQ(simppl::dbus::ConnectionState::Connected, s); // like for signals, attributes must be attached when the client is connected - this->data.attach() >> [this](simppl::dbus::CallState state, int new_value) + this->data.attach() >> [this](const simppl::dbus::CallState& state, int new_value) { this->attributeChanged(state, new_value); }; }; } - void attributeChanged(simppl::dbus::CallState state, int new_value) + void attributeChanged(const simppl::dbus::CallState& state, int new_value) { EXPECT_TRUE((bool)state); diff --git a/tests/timeout.cpp b/tests/timeout.cpp index 32a2e28..70cc4e8 100644 --- a/tests/timeout.cpp +++ b/tests/timeout.cpp @@ -51,8 +51,8 @@ struct Client : simppl::dbus::Stub if (s == simppl::dbus::ConnectionState::Connected) { start_ = std::chrono::steady_clock::now(); - - eval.async(42) >> [this](simppl::dbus::CallState state, double){ + + eval.async(42) >> [this](const simppl::dbus::CallState& state, double){ EXPECT_FALSE((bool)state); EXPECT_STREQ(state.exception().name(), "org.freedesktop.DBus.Error.NoReply"); @@ -86,7 +86,7 @@ struct DisconnectClient : simppl::dbus::Stub if (s == simppl::dbus::ConnectionState::Connected) { - eval.async(777) >> [this](simppl::dbus::CallState state, double){ + eval.async(777) >> [this](const simppl::dbus::CallState& state, double){ EXPECT_FALSE((bool)state); EXPECT_STREQ(state.exception().name(), "org.freedesktop.DBus.Error.Timeout"); @@ -156,7 +156,7 @@ struct Server : simppl::dbus::Skeleton else (void)defer_response(); }; - + oneway >> [this](int i){ // generate timeout on client side std::this_thread::sleep_for(1s); @@ -201,7 +201,7 @@ void runServer() TEST(Timeout, method) { simppl::dbus::enable_threads(); - + std::thread serverthread(&runServer); simppl::dbus::Dispatcher d; @@ -255,7 +255,7 @@ TEST(Timeout, request_specific) // default timeout d.set_request_timeout(500ms); - + auto start = std::chrono::steady_clock::now(); try diff --git a/tests/variant.cpp b/tests/variant.cpp index 17b3361..fc8f2ec 100644 --- a/tests/variant.cpp +++ b/tests/variant.cpp @@ -28,7 +28,7 @@ namespace test INTERFACE(VServer) { - Method>>> getData; + Method>>> getData; VServer() : INIT(getData) @@ -70,7 +70,7 @@ namespace { : simppl::dbus::Stub(d, "role") { connected >> [this](simppl::dbus::ConnectionState s){ - getData.async() >> [this](simppl::dbus::CallState state, const std::map>& mapping){ + getData.async() >> [this](const simppl::dbus::CallState& state, const std::map>& mapping){ EXPECT_EQ(3u, mapping.size()); auto hello = mapping.find("Hello"); @@ -81,9 +81,9 @@ namespace { EXPECT_NE(mapping.end(), world); EXPECT_NE(mapping.end(), toll); - EXPECT_EQ(42, *hello->second.get()); - EXPECT_EQ(4711, *world->second.get()); - EXPECT_EQ(std::string("Show"), *toll->second.get()); + EXPECT_EQ(42, std::get(hello->second)); + EXPECT_EQ(4711, std::get(world->second)); + EXPECT_EQ(std::string("Show"), std::get(toll->second)); disp().stop(); }; @@ -97,7 +97,7 @@ namespace { : simppl::dbus::Skeleton(d, "role") { getData >> [this](){ - std::map> mapping; + std::map> mapping; mapping["Hello"] = 42; mapping["World"] = 4711; mapping["Tolle"] = std::string("Show"); @@ -109,62 +109,6 @@ namespace { } -TEST(Variant, basic) -{ - constructs = 0; - destructs = 0; - - simppl::Variant v; - - v = 42; - EXPECT_EQ(42, *v.get()); - - v = std::string("Hallo Welt"); - EXPECT_EQ(std::string("Hallo Welt"), *v.get()); - - v = TestHelper(); - - v = 43; - EXPECT_EQ(43, *v.get()); - - EXPECT_EQ(2, constructs); - EXPECT_EQ(2, destructs); -} - - -TEST(Variant, map) -{ - simppl::Variant > v; - - std::map m { - { 1, "Hallo" }, - { 2, "Welt" } - }; - - v = m; - EXPECT_EQ(2u, (v.get>()->size())); - - int i=0; - for(auto& e : *v.get>()) - { - if (i == 0) - { - EXPECT_EQ(1, e.first); - EXPECT_EQ(std::string("Hallo"), e.second); - } - else if (i == 1) - { - EXPECT_EQ(2, e.first); - EXPECT_EQ(std::string("Welt"), e.second); - } - - ++i; - } - - EXPECT_EQ(2, i); -} - - TEST(Variant, method) { simppl::dbus::Dispatcher d("bus:session");