diff --git a/CMakeLists.txt b/CMakeLists.txt index 41d2d0d..ebcf988 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,7 @@ set(SDBUSCPP_CPP_SRCS ${SDBUSCPP_SOURCE_DIR}/Object.cpp ${SDBUSCPP_SOURCE_DIR}/ObjectProxy.cpp ${SDBUSCPP_SOURCE_DIR}/Types.cpp + ${SDBUSCPP_SOURCE_DIR}/Flags.cpp ${SDBUSCPP_SOURCE_DIR}/VTableUtils.c) set(SDBUSCPP_HDR_SRCS @@ -56,7 +57,8 @@ set(SDBUSCPP_PUBLIC_HDRS ${SDBUSCPP_INCLUDE_DIR}/MethodResult.h ${SDBUSCPP_INCLUDE_DIR}/sdbus-c++.h ${SDBUSCPP_INCLUDE_DIR}/Types.h - ${SDBUSCPP_INCLUDE_DIR}/TypeTraits.h) + ${SDBUSCPP_INCLUDE_DIR}/TypeTraits.h + ${SDBUSCPP_INCLUDE_DIR}/Flags.h) set(SDBUSCPP_SRCS ${SDBUSCPP_CPP_SRCS} ${SDBUSCPP_HDR_SRCS} ${SDBUSCPP_PUBLIC_HDRS}) diff --git a/include/sdbus-c++/ConvenienceClasses.h b/include/sdbus-c++/ConvenienceClasses.h index 8b15f52..5c86e28 100644 --- a/include/sdbus-c++/ConvenienceClasses.h +++ b/include/sdbus-c++/ConvenienceClasses.h @@ -28,6 +28,7 @@ #include #include +#include #include #include @@ -44,16 +45,29 @@ namespace sdbus { { public: MethodRegistrator(IObject& object, const std::string& methodName); + MethodRegistrator(MethodRegistrator&& other) = default; + MethodRegistrator& operator=(MethodRegistrator&& other) = default; + ~MethodRegistrator() noexcept(false); + MethodRegistrator& onInterface(const std::string& interfaceName); template - std::enable_if_t> implementedAs(_Function&& callback); + std::enable_if_t, MethodRegistrator&> implementedAs(_Function&& callback); template - std::enable_if_t> implementedAs(_Function&& callback); + std::enable_if_t, MethodRegistrator&> implementedAs(_Function&& callback); + MethodRegistrator& markAsDeprecated(); + MethodRegistrator& markAsPrivileged(); + MethodRegistrator& withNoReply(); private: IObject& object_; const std::string& methodName_; std::string interfaceName_; + std::string inputSignature_; + std::string outputSignature_; + method_callback syncCallback_; + async_method_callback asyncCallback_; + Flags flags_; + int exceptions_{}; // Number of active exceptions when SignalRegistrator is constructed }; class SignalRegistrator @@ -63,14 +77,17 @@ namespace sdbus { SignalRegistrator(SignalRegistrator&& other) = default; SignalRegistrator& operator=(SignalRegistrator&& other) = default; ~SignalRegistrator() noexcept(false); + SignalRegistrator& onInterface(std::string interfaceName); - template void withParameters(); + template SignalRegistrator& withParameters(); + SignalRegistrator& markAsDeprecated(); private: IObject& object_; const std::string& signalName_; std::string interfaceName_; std::string signalSignature_; + Flags flags_; int exceptions_{}; // Number of active exceptions when SignalRegistrator is constructed }; @@ -81,9 +98,13 @@ namespace sdbus { PropertyRegistrator(PropertyRegistrator&& other) = default; PropertyRegistrator& operator=(PropertyRegistrator&& other) = default; ~PropertyRegistrator() noexcept(false); + PropertyRegistrator& onInterface(const std::string& interfaceName); template PropertyRegistrator& withGetter(_Function&& callback); template PropertyRegistrator& withSetter(_Function&& callback); + PropertyRegistrator& markAsDeprecated(); + PropertyRegistrator& markAsPrivileged(); + PropertyRegistrator& withUpdateBehavior(Flags::PropertyUpdateBehaviorFlags behavior); private: IObject& object_; @@ -92,9 +113,30 @@ namespace sdbus { std::string propertySignature_; property_get_callback getter_; property_set_callback setter_; + Flags flags_; int exceptions_{}; // Number of active exceptions when PropertyRegistrator is constructed }; + class InterfaceFlagsSetter + { + public: + InterfaceFlagsSetter(IObject& object, const std::string& interfaceName); + InterfaceFlagsSetter(InterfaceFlagsSetter&& other) = default; + InterfaceFlagsSetter& operator=(InterfaceFlagsSetter&& other) = default; + ~InterfaceFlagsSetter() noexcept(false); + + InterfaceFlagsSetter& markAsDeprecated(); + InterfaceFlagsSetter& markAsPrivileged(); + InterfaceFlagsSetter& withNoReplyMethods(); + InterfaceFlagsSetter& withPropertyUpdateBehavior(Flags::PropertyUpdateBehaviorFlags behavior); + + private: + IObject& object_; + const std::string& interfaceName_; + Flags flags_; + int exceptions_{}; // Number of active exceptions when InterfaceFlagsSetter is constructed + }; + class SignalEmitter { public: @@ -123,6 +165,7 @@ namespace sdbus { MethodInvoker& onInterface(const std::string& interfaceName); template MethodInvoker& withArguments(_Args&&... args); template void storeResultsTo(_Args&... args); + void dontExpectReply(); private: IObjectProxy& objectProxy_; diff --git a/include/sdbus-c++/ConvenienceClasses.inl b/include/sdbus-c++/ConvenienceClasses.inl index 50b6d9e..9cfbca9 100755 --- a/include/sdbus-c++/ConvenienceClasses.inl +++ b/include/sdbus-c++/ConvenienceClasses.inl @@ -39,12 +39,41 @@ namespace sdbus { + // Moved into the library to isolate from C++17 dependency + /* inline MethodRegistrator::MethodRegistrator(IObject& object, const std::string& methodName) : object_(object) , methodName_(methodName) + , exceptions_(std::uncaught_exceptions()) // Needs C++17 { } + inline MethodRegistrator::~MethodRegistrator() noexcept(false) // since C++11, destructors must + { // explicitly be allowed to throw + // Don't register the method if MethodRegistrator threw an exception in one of its methods + if (std::uncaught_exceptions() != exceptions_) + return; + + SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when registering a DBus method", EINVAL); + + // registerMethod() can throw. But as the MethodRegistrator shall always be used as an unnamed, + // temporary object, i.e. not as a stack-allocated object, the double-exception situation + // shall never happen. I.e. it should not happen that this destructor is directly called + // in the stack-unwinding process of another flying exception (which would lead to immediate + // termination). It can be called indirectly in the destructor of another object, but that's + // fine and safe provided that the caller catches exceptions thrown from here. + // Therefore, we can allow registerMethod() to throw even if we are in the destructor. + // Bottomline is, to be on the safe side, the caller must take care of catching and reacting + // to the exception thrown from here if the caller is a destructor itself. + if (syncCallback_) + object_.registerMethod(interfaceName_, methodName_, inputSignature_, outputSignature_, std::move(syncCallback_)); + else if(asyncCallback_) + object_.registerMethod(interfaceName_, methodName_, inputSignature_, outputSignature_, std::move(asyncCallback_)); + else + SDBUS_THROW_ERROR("Method handler not specified when registering a DBus method", EINVAL); + } + */ + inline MethodRegistrator& MethodRegistrator::onInterface(const std::string& interfaceName) { interfaceName_ = interfaceName; @@ -53,15 +82,11 @@ namespace sdbus { } template - inline std::enable_if_t> MethodRegistrator::implementedAs(_Function&& callback) + inline std::enable_if_t, MethodRegistrator&> MethodRegistrator::implementedAs(_Function&& callback) { - SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when registering a DBus method", EINVAL); - - object_.registerMethod( interfaceName_ - , methodName_ - , signature_of_function_input_arguments<_Function>::str() - , signature_of_function_output_arguments<_Function>::str() - , [callback = std::forward<_Function>(callback)](MethodCall& msg, MethodReply& reply) + inputSignature_ = signature_of_function_input_arguments<_Function>::str(); + outputSignature_ = signature_of_function_output_arguments<_Function>::str(); + syncCallback_ = [callback = std::forward<_Function>(callback)](MethodCall& msg, MethodReply& reply) { // Create a tuple of callback input arguments' types, which will be used // as a storage for the argument values deserialized from the message. @@ -78,19 +103,17 @@ namespace sdbus { // The return value is stored to the reply message. // In case of void functions, ret is an empty tuple and thus nothing is stored. reply << ret; - }); + }; + + return *this; } template - inline std::enable_if_t> MethodRegistrator::implementedAs(_Function&& callback) + inline std::enable_if_t, MethodRegistrator&> MethodRegistrator::implementedAs(_Function&& callback) { - SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when registering a DBus method", EINVAL); - - object_.registerMethod( interfaceName_ - , methodName_ - , signature_of_function_input_arguments<_Function>::str() - , signature_of_function_output_arguments<_Function>::str() //signature_of>::str() // because last argument contains output types - , [callback = std::forward<_Function>(callback)](MethodCall& msg, MethodResult result) + inputSignature_ = signature_of_function_input_arguments<_Function>::str(); + outputSignature_ = signature_of_function_output_arguments<_Function>::str(); + asyncCallback_ = [callback = std::forward<_Function>(callback)](MethodCall& msg, MethodResult result) { // Create a tuple of callback input arguments' types, which will be used // as a storage for the argument values deserialized from the message. @@ -102,9 +125,33 @@ namespace sdbus { // Invoke callback with input arguments from the tuple. sdbus::apply(callback, std::move(result), inputArgs); // TODO: Use std::apply when switching to full C++17 support - }); + }; + + return *this; } + inline MethodRegistrator& MethodRegistrator::markAsDeprecated() + { + flags_.set(Flags::DEPRECATED); + + return *this; + } + + inline MethodRegistrator& MethodRegistrator::markAsPrivileged() + { + flags_.set(Flags::PRIVILEGED); + + return *this; + } + + inline MethodRegistrator& MethodRegistrator::withNoReply() + { + flags_.set(Flags::METHOD_NO_REPLY); + + return *this; + } + + // Moved into the library to isolate from C++17 dependency /* inline SignalRegistrator::SignalRegistrator(IObject& object, std::string signalName) @@ -144,9 +191,18 @@ namespace sdbus { } template - inline void SignalRegistrator::withParameters() + inline SignalRegistrator& SignalRegistrator::withParameters() { signalSignature_ = signature_of_function_input_arguments::str(); + + return *this; + } + + inline SignalRegistrator& SignalRegistrator::markAsDeprecated() + { + flags_.set(Flags::DEPRECATED); + + return *this; } @@ -234,6 +290,87 @@ namespace sdbus { return *this; } + inline PropertyRegistrator& PropertyRegistrator::markAsDeprecated() + { + flags_.set(Flags::DEPRECATED); + + return *this; + } + + inline PropertyRegistrator& PropertyRegistrator::markAsPrivileged() + { + flags_.set(Flags::PRIVILEGED); + + return *this; + } + + inline PropertyRegistrator& PropertyRegistrator::withUpdateBehavior(Flags::PropertyUpdateBehaviorFlags behavior) + { + flags_.set(behavior); + + return *this; + } + + + // Moved into the library to isolate from C++17 dependency + /* + inline InterfaceFlagsSetter::InterfaceFlagsSetter(IObject& object, const std::string& interfaceName) + : object_(object) + , interfaceName_(interfaceName) + , exceptions_(std::uncaught_exceptions()) + { + } + + inline InterfaceFlagsSetter::~InterfaceFlagsSetter() noexcept(false) // since C++11, destructors must + { // explicitly be allowed to throw + // Don't set any flags if InterfaceFlagsSetter threw an exception in one of its methods + if (std::uncaught_exceptions() != exceptions_) + return; + + SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when setting its flags", EINVAL); + + // setInterfaceFlags() can throw. But as the InterfaceFlagsSetter shall always be used as an unnamed, + // temporary object, i.e. not as a stack-allocated object, the double-exception situation + // shall never happen. I.e. it should not happen that this destructor is directly called + // in the stack-unwinding process of another flying exception (which would lead to immediate + // termination). It can be called indirectly in the destructor of another object, but that's + // fine and safe provided that the caller catches exceptions thrown from here. + // Therefore, we can allow setInterfaceFlags() to throw even if we are in the destructor. + // Bottomline is, to be on the safe side, the caller must take care of catching and reacting + // to the exception thrown from here if the caller is a destructor itself. + object_.setInterfaceFlags( std::move(interfaceName_) + , std::move(flags_) ); + } + */ + + inline InterfaceFlagsSetter& InterfaceFlagsSetter::markAsDeprecated() + { + flags_.set(Flags::DEPRECATED); + + return *this; + } + + inline InterfaceFlagsSetter& InterfaceFlagsSetter::markAsPrivileged() + { + flags_.set(Flags::PRIVILEGED); + + return *this; + } + + inline InterfaceFlagsSetter& InterfaceFlagsSetter::withNoReplyMethods() + { + flags_.set(Flags::METHOD_NO_REPLY); + + return *this; + } + + inline InterfaceFlagsSetter& InterfaceFlagsSetter::withPropertyUpdateBehavior(Flags::PropertyUpdateBehaviorFlags behavior) + { + flags_.set(behavior); + + return *this; + } + // Moved into the library to isolate from C++17 dependency /* @@ -342,6 +479,13 @@ namespace sdbus { detail::deserialize_pack(reply, args...); } + inline void MethodInvoker::dontExpectReply() + { + SDBUS_THROW_ERROR_IF(!method_.isValid(), "DBus interface not specified when calling a DBus method", EINVAL); + + method_.dontExpectReply(); + } + inline SignalSubscriber::SignalSubscriber(IObjectProxy& objectProxy, const std::string& signalName) : objectProxy_(objectProxy) diff --git a/include/sdbus-c++/Flags.h b/include/sdbus-c++/Flags.h new file mode 100644 index 0000000..df6fb0c --- /dev/null +++ b/include/sdbus-c++/Flags.h @@ -0,0 +1,98 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file Flags.h + * + * Created on: Dec 31, 2018 + * Project: sdbus-c++ + * Description: High-level D-Bus IPC C++ library based on sd-bus + * + * This file is part of sdbus-c++. + * + * sdbus-c++ is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * sdbus-c++ is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with sdbus-c++. If not, see . + */ + +#ifndef SDBUS_CXX_FLAGS_H_ +#define SDBUS_CXX_FLAGS_H_ + +#include +#include + +namespace sdbus { + + // D-Bus interface, method, signal or property flags + class Flags + { + public: + enum GeneralFlags : uint8_t + { DEPRECATED = 0 + , METHOD_NO_REPLY = 1 + , PRIVILEGED = 2 + }; + + enum PropertyUpdateBehaviorFlags : uint8_t + { EMITS_CHANGE_SIGNAL = 3 + , EMITS_INVALIDATION_SIGNAL = 4 + , EMITS_NO_SIGNAL = 5 + , CONST_PROPERTY_VALUE = 6 + }; + + enum : uint8_t + { FLAG_COUNT = 7 + }; + + Flags() + { + // EMITS_CHANGE_SIGNAL is on by default + flags_.set(EMITS_CHANGE_SIGNAL, true); + } + + void set(GeneralFlags flag, bool value = true) + { + flags_.set(flag, value); + } + + void set(PropertyUpdateBehaviorFlags flag, bool value = true) + { + flags_.set(EMITS_CHANGE_SIGNAL, false); + flags_.set(EMITS_INVALIDATION_SIGNAL, false); + flags_.set(EMITS_NO_SIGNAL, false); + flags_.set(CONST_PROPERTY_VALUE, false); + + flags_.set(flag, value); + } + + bool test(GeneralFlags flag) const + { + return flags_.test(flag); + } + + bool test(PropertyUpdateBehaviorFlags flag) const + { + return flags_.test(flag); + } + + uint64_t toSdBusInterfaceFlags() const; + uint64_t toSdBusMethodFlags() const; + uint64_t toSdBusSignalFlags() const; + uint64_t toSdBusPropertyFlags() const; + uint64_t toSdBusWritablePropertyFlags() const; + + private: + std::bitset flags_; + }; + +} + +#endif /* SDBUS_CXX_FLAGS_H_ */ diff --git a/include/sdbus-c++/IObject.h b/include/sdbus-c++/IObject.h index 0ef0d3a..ff18242 100755 --- a/include/sdbus-c++/IObject.h +++ b/include/sdbus-c++/IObject.h @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -61,6 +62,7 @@ namespace sdbus { * @param[in] inputSignature D-Bus signature of method input parameters * @param[in] outputSignature D-Bus signature of method output parameters * @param[in] methodCallback Callback that implements the body of the method + * @param[in] noReply If true, the method isn't expected to send reply * * @throws sdbus::Error in case of failure */ @@ -68,7 +70,8 @@ namespace sdbus { , const std::string& methodName , const std::string& inputSignature , const std::string& outputSignature - , method_callback methodCallback ) = 0; + , method_callback methodCallback + , Flags flags = {} ) = 0; /*! * @brief Registers method that the object will provide on D-Bus @@ -78,6 +81,7 @@ namespace sdbus { * @param[in] inputSignature D-Bus signature of method input parameters * @param[in] outputSignature D-Bus signature of method output parameters * @param[in] asyncMethodCallback Callback that implements the body of the method + * @param[in] noReply If true, the method isn't expected to send reply * * This overload register a method callback that will have a freedom to execute * its body in asynchronous contexts, and send the results from those contexts. @@ -90,7 +94,8 @@ namespace sdbus { , const std::string& methodName , const std::string& inputSignature , const std::string& outputSignature - , async_method_callback asyncMethodCallback ) = 0; + , async_method_callback asyncMethodCallback + , Flags flags = {} ) = 0; /*! * @brief Registers signal that the object will emit on D-Bus @@ -103,7 +108,8 @@ namespace sdbus { */ virtual void registerSignal( const std::string& interfaceName , const std::string& signalName - , const std::string& signature ) = 0; + , const std::string& signature + , Flags flags = {} ) = 0; /*! * @brief Registers read-only property that the object will provide on D-Bus @@ -118,7 +124,8 @@ namespace sdbus { virtual void registerProperty( const std::string& interfaceName , const std::string& propertyName , const std::string& signature - , property_get_callback getCallback ) = 0; + , property_get_callback getCallback + , Flags flags = {} ) = 0; /*! * @brief Registers read/write property that the object will provide on D-Bus @@ -135,7 +142,18 @@ namespace sdbus { , const std::string& propertyName , const std::string& signature , property_get_callback getCallback - , property_set_callback setCallback ) = 0; + , property_set_callback setCallback + , Flags flags = {} ) = 0; + + /*! + * @brief Sets flags for a given interface + * + * @param[in] interfaceName Name of an interface whose flags will be set + * @param[in] flags Flags to be set + * + * @throws sdbus::Error in case of failure + */ + virtual void setInterfaceFlags(const std::string& interfaceName, Flags flags) = 0; /*! * @brief Finishes the registration and exports object API on D-Bus @@ -231,6 +249,23 @@ namespace sdbus { */ PropertyRegistrator registerProperty(const std::string& propertyName); + /*! + * @brief Sets flags (annotations) for a given interface + * + * @param[in] interfaceName Name of an interface whose flags will be set + * @return A helper object for convenient setting of Interface flags + * + * This is a high-level, convenience alternative to the other setInterfaceFlags overload. + * + * Example of use: + * @code + * object_.setInterfaceFlags("com.kistler.foo").markAsDeprecated().withPropertyUpdateBehavior(sdbus::Flags::EMITS_NO_SIGNAL); + * @endcode + * + * @throws sdbus::Error in case of failure + */ + InterfaceFlagsSetter setInterfaceFlags(const std::string& interfaceName); + /*! * @brief Emits signal on D-Bus * @@ -270,6 +305,11 @@ namespace sdbus { return PropertyRegistrator(*this, std::move(propertyName)); } + inline InterfaceFlagsSetter IObject::setInterfaceFlags(const std::string& interfaceName) + { + return InterfaceFlagsSetter(*this, std::move(interfaceName)); + } + inline SignalEmitter IObject::emitSignal(const std::string& signalName) { return SignalEmitter(*this, signalName); diff --git a/include/sdbus-c++/IObjectProxy.h b/include/sdbus-c++/IObjectProxy.h index 81464df..b09d8dd 100755 --- a/include/sdbus-c++/IObjectProxy.h +++ b/include/sdbus-c++/IObjectProxy.h @@ -72,6 +72,14 @@ namespace sdbus { * @brief Calls method on the proxied D-Bus object * * @param[in] message Message representing a method call + * @return A method reply message + * + * Normally, the call is blocking, i.e. it waits for the remote method to finish with either + * a return value or an error. + * + * If the method call argument is set to not expect reply, the call will not wait for the remote + * method to finish, i.e. the call will be non-blocking, and the function will return an empty, + * invalid MethodReply object (representing void). * * Note: To avoid messing with messages, use higher-level API defined below. * diff --git a/include/sdbus-c++/Interfaces.h b/include/sdbus-c++/Interfaces.h index 39ff9f4..45b33bd 100644 --- a/include/sdbus-c++/Interfaces.h +++ b/include/sdbus-c++/Interfaces.h @@ -120,7 +120,6 @@ namespace sdbus { { getObject().finishRegistration(); } - }; } diff --git a/include/sdbus-c++/Message.h b/include/sdbus-c++/Message.h index 97bb038..e253d4b 100644 --- a/include/sdbus-c++/Message.h +++ b/include/sdbus-c++/Message.h @@ -155,6 +155,12 @@ namespace sdbus { MethodReply send() const; MethodReply createReply() const; MethodReply createErrorReply(const sdbus::Error& error) const; + void dontExpectReply(); + bool doesntExpectReply() const; + + private: + MethodReply sendWithReply() const; + MethodReply sendWithNoReply() const; }; class MethodReply : public Message diff --git a/src/ConvenienceClasses.cpp b/src/ConvenienceClasses.cpp index e5b8788..d843d20 100644 --- a/src/ConvenienceClasses.cpp +++ b/src/ConvenienceClasses.cpp @@ -31,6 +31,38 @@ namespace sdbus { +MethodRegistrator::MethodRegistrator(IObject& object, const std::string& methodName) + : object_(object) + , methodName_(methodName) + , exceptions_(std::uncaught_exceptions()) // Needs C++17 +{ +} + +MethodRegistrator::~MethodRegistrator() noexcept(false) // since C++11, destructors must +{ // explicitly be allowed to throw + // Don't register the method if MethodRegistrator threw an exception in one of its methods + if (std::uncaught_exceptions() != exceptions_) + return; + + SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when registering a DBus method", EINVAL); + + // registerMethod() can throw. But as the MethodRegistrator shall always be used as an unnamed, + // temporary object, i.e. not as a stack-allocated object, the double-exception situation + // shall never happen. I.e. it should not happen that this destructor is directly called + // in the stack-unwinding process of another flying exception (which would lead to immediate + // termination). It can be called indirectly in the destructor of another object, but that's + // fine and safe provided that the caller catches exceptions thrown from here. + // Therefore, we can allow registerMethod() to throw even if we are in the destructor. + // Bottomline is, to be on the safe side, the caller must take care of catching and reacting + // to the exception thrown from here if the caller is a destructor itself. + if (syncCallback_) + object_.registerMethod(interfaceName_, methodName_, inputSignature_, outputSignature_, std::move(syncCallback_), flags_); + else if(asyncCallback_) + object_.registerMethod(interfaceName_, methodName_, inputSignature_, outputSignature_, std::move(asyncCallback_), flags_); + else + SDBUS_THROW_ERROR("Method handler not specified when registering a DBus method", EINVAL); +} + SignalRegistrator::SignalRegistrator(IObject& object, const std::string& signalName) : object_(object) , signalName_(signalName) @@ -55,7 +87,7 @@ SignalRegistrator::~SignalRegistrator() noexcept(false) // since C++11, destruct // Therefore, we can allow registerSignal() to throw even if we are in the destructor. // Bottomline is, to be on the safe side, the caller must take care of catching and reacting // to the exception thrown from here if the caller is a destructor itself. - object_.registerSignal(interfaceName_, signalName_, signalSignature_); + object_.registerSignal(interfaceName_, signalName_, signalSignature_, flags_); } @@ -87,7 +119,37 @@ PropertyRegistrator::~PropertyRegistrator() noexcept(false) // since C++11, dest , std::move(propertyName_) , std::move(propertySignature_) , std::move(getter_) - , std::move(setter_) ); + , std::move(setter_) + , flags_ ); +} + + +InterfaceFlagsSetter::InterfaceFlagsSetter(IObject& object, const std::string& interfaceName) + : object_(object) + , interfaceName_(interfaceName) + , exceptions_(std::uncaught_exceptions()) +{ +} + +InterfaceFlagsSetter::~InterfaceFlagsSetter() noexcept(false) // since C++11, destructors must +{ // explicitly be allowed to throw + // Don't set any flags if InterfaceFlagsSetter threw an exception in one of its methods + if (std::uncaught_exceptions() != exceptions_) + return; + + SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when setting its flags", EINVAL); + + // setInterfaceFlags() can throw. But as the InterfaceFlagsSetter shall always be used as an unnamed, + // temporary object, i.e. not as a stack-allocated object, the double-exception situation + // shall never happen. I.e. it should not happen that this destructor is directly called + // in the stack-unwinding process of another flying exception (which would lead to immediate + // termination). It can be called indirectly in the destructor of another object, but that's + // fine and safe provided that the caller catches exceptions thrown from here. + // Therefore, we can allow setInterfaceFlags() to throw even if we are in the destructor. + // Bottomline is, to be on the safe side, the caller must take care of catching and reacting + // to the exception thrown from here if the caller is a destructor itself. + object_.setInterfaceFlags( std::move(interfaceName_) + , std::move(flags_) ); } diff --git a/src/Flags.cpp b/src/Flags.cpp new file mode 100644 index 0000000..1c86fbd --- /dev/null +++ b/src/Flags.cpp @@ -0,0 +1,111 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file Flags.cpp + * + * Created on: Dec 31, 2018 + * Project: sdbus-c++ + * Description: High-level D-Bus IPC C++ library based on sd-bus + * + * This file is part of sdbus-c++. + * + * sdbus-c++ is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * sdbus-c++ is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with sdbus-c++. If not, see . + */ + +#include +#include + +namespace sdbus +{ + uint64_t Flags::toSdBusInterfaceFlags() const + { + uint64_t sdbusFlags{}; + + using namespace sdbus; + if (flags_.test(Flags::DEPRECATED)) + sdbusFlags |= SD_BUS_VTABLE_DEPRECATED; + if (!flags_.test(Flags::PRIVILEGED)) + sdbusFlags |= SD_BUS_VTABLE_UNPRIVILEGED; + + if (flags_.test(Flags::EMITS_CHANGE_SIGNAL)) + sdbusFlags |= SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE; + else if (flags_.test(Flags::EMITS_INVALIDATION_SIGNAL)) + sdbusFlags |= SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION; + else if (flags_.test(Flags::CONST_PROPERTY_VALUE)) + sdbusFlags |= SD_BUS_VTABLE_PROPERTY_CONST; + else if (flags_.test(Flags::EMITS_NO_SIGNAL)) + sdbusFlags |= 0; + + return sdbusFlags; + } + + uint64_t Flags::toSdBusMethodFlags() const + { + uint64_t sdbusFlags{}; + + using namespace sdbus; + if (flags_.test(Flags::DEPRECATED)) + sdbusFlags |= SD_BUS_VTABLE_DEPRECATED; + if (!flags_.test(Flags::PRIVILEGED)) + sdbusFlags |= SD_BUS_VTABLE_UNPRIVILEGED; + if (flags_.test(Flags::METHOD_NO_REPLY)) + sdbusFlags |= SD_BUS_VTABLE_METHOD_NO_REPLY; + + return sdbusFlags; + } + + uint64_t Flags::toSdBusSignalFlags() const + { + uint64_t sdbusFlags{}; + + using namespace sdbus; + if (flags_.test(Flags::DEPRECATED)) + sdbusFlags |= SD_BUS_VTABLE_DEPRECATED; + + return sdbusFlags; + } + + uint64_t Flags::toSdBusPropertyFlags() const + { + uint64_t sdbusFlags{}; + + using namespace sdbus; + if (flags_.test(Flags::DEPRECATED)) + sdbusFlags |= SD_BUS_VTABLE_DEPRECATED; + //if (!flags_.test(Flags::PRIVILEGED)) + // sdbusFlags |= SD_BUS_VTABLE_UNPRIVILEGED; + + if (flags_.test(Flags::EMITS_CHANGE_SIGNAL)) + sdbusFlags |= SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE; + else if (flags_.test(Flags::EMITS_INVALIDATION_SIGNAL)) + sdbusFlags |= SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION; + else if (flags_.test(Flags::CONST_PROPERTY_VALUE)) + sdbusFlags |= SD_BUS_VTABLE_PROPERTY_CONST; + else if (flags_.test(Flags::EMITS_NO_SIGNAL)) + sdbusFlags |= 0; + + return sdbusFlags; + } + + uint64_t Flags::toSdBusWritablePropertyFlags() const + { + auto sdbusFlags = toSdBusPropertyFlags(); + + using namespace sdbus; + if (!flags_.test(Flags::PRIVILEGED)) + sdbusFlags |= SD_BUS_VTABLE_UNPRIVILEGED; + + return sdbusFlags; + } +} diff --git a/src/Message.cpp b/src/Message.cpp index 6aefc8c..f624f96 100755 --- a/src/Message.cpp +++ b/src/Message.cpp @@ -571,7 +571,28 @@ void* Message::getMsg() const return msg_; } +void MethodCall::dontExpectReply() +{ + auto r = sd_bus_message_set_expect_reply((sd_bus_message*)getMsg(), 0); + SDBUS_THROW_ERROR_IF(r < 0, "Failed to set the dont-expect-reply flag", -r); +} + +bool MethodCall::doesntExpectReply() const +{ + auto r = sd_bus_message_get_expect_reply((sd_bus_message*)getMsg()); + SDBUS_THROW_ERROR_IF(r < 0, "Failed to get the dont-expect-reply flag", -r); + return r > 0 ? false : true; +} + MethodReply MethodCall::send() const +{ + if (!doesntExpectReply()) + return sendWithReply(); + else + return sendWithNoReply(); +} + +MethodReply MethodCall::sendWithReply() const { sd_bus_message* sdbusReply{}; SCOPE_EXIT{ sd_bus_message_unref(sdbusReply); }; // Returned message will become an owner of sdbusReply @@ -590,6 +611,13 @@ MethodReply MethodCall::send() const return MethodReply(sdbusReply); } +MethodReply MethodCall::sendWithNoReply() const +{ + auto r = sd_bus_send(nullptr, (sd_bus_message*)getMsg(), nullptr); + SDBUS_THROW_ERROR_IF(r < 0, "Failed to call method with no reply", -r); + return MethodReply{}; // No reply +} + MethodReply MethodCall::createReply() const { sd_bus_message *sdbusReply{}; diff --git a/src/Object.cpp b/src/Object.cpp index b35dd5d..bc83cd9 100755 --- a/src/Object.cpp +++ b/src/Object.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include "IConnection.h" #include "VTableUtils.h" #include @@ -45,7 +46,8 @@ void Object::registerMethod( const std::string& interfaceName , const std::string& methodName , const std::string& inputSignature , const std::string& outputSignature - , method_callback methodCallback ) + , method_callback methodCallback + , Flags flags ) { SDBUS_THROW_ERROR_IF(!methodCallback, "Invalid method callback provided", EINVAL); @@ -57,7 +59,7 @@ void Object::registerMethod( const std::string& interfaceName }; auto& interface = interfaces_[interfaceName]; - InterfaceData::MethodData methodData{inputSignature, outputSignature, std::move(syncCallback)}; + InterfaceData::MethodData methodData{inputSignature, outputSignature, std::move(syncCallback), flags}; auto inserted = interface.methods_.emplace(methodName, std::move(methodData)).second; SDBUS_THROW_ERROR_IF(!inserted, "Failed to register method: method already exists", EINVAL); @@ -67,7 +69,8 @@ void Object::registerMethod( const std::string& interfaceName , const std::string& methodName , const std::string& inputSignature , const std::string& outputSignature - , async_method_callback asyncMethodCallback ) + , async_method_callback asyncMethodCallback + , Flags flags ) { SDBUS_THROW_ERROR_IF(!asyncMethodCallback, "Invalid method callback provided", EINVAL); @@ -78,7 +81,7 @@ void Object::registerMethod( const std::string& interfaceName }; auto& interface = interfaces_[interfaceName]; - InterfaceData::MethodData methodData{inputSignature, outputSignature, std::move(asyncCallback)}; + InterfaceData::MethodData methodData{inputSignature, outputSignature, std::move(asyncCallback), flags}; auto inserted = interface.methods_.emplace(methodName, std::move(methodData)).second; SDBUS_THROW_ERROR_IF(!inserted, "Failed to register method: method already exists", EINVAL); @@ -86,11 +89,12 @@ void Object::registerMethod( const std::string& interfaceName void Object::registerSignal( const std::string& interfaceName , const std::string& signalName - , const std::string& signature ) + , const std::string& signature + , Flags flags ) { auto& interface = interfaces_[interfaceName]; - InterfaceData::SignalData signalData{signature}; + InterfaceData::SignalData signalData{signature, flags}; auto inserted = interface.signals_.emplace(signalName, std::move(signalData)).second; SDBUS_THROW_ERROR_IF(!inserted, "Failed to register signal: signal already exists", EINVAL); @@ -99,31 +103,40 @@ void Object::registerSignal( const std::string& interfaceName void Object::registerProperty( const std::string& interfaceName , const std::string& propertyName , const std::string& signature - , property_get_callback getCallback ) + , property_get_callback getCallback + , Flags flags ) { registerProperty( interfaceName , propertyName , signature , getCallback - , property_set_callback{} ); + , property_set_callback{} + , flags ); } void Object::registerProperty( const std::string& interfaceName , const std::string& propertyName , const std::string& signature , property_get_callback getCallback - , property_set_callback setCallback ) + , property_set_callback setCallback + , Flags flags ) { SDBUS_THROW_ERROR_IF(!getCallback && !setCallback, "Invalid property callbacks provided", EINVAL); auto& interface = interfaces_[interfaceName]; - InterfaceData::PropertyData propertyData{signature, std::move(getCallback), std::move(setCallback)}; + InterfaceData::PropertyData propertyData{signature, std::move(getCallback), std::move(setCallback), flags}; auto inserted = interface.properties_.emplace(propertyName, std::move(propertyData)).second; SDBUS_THROW_ERROR_IF(!inserted, "Failed to register property: property already exists", EINVAL); } +void Object::setInterfaceFlags(const std::string& interfaceName, Flags flags) +{ + auto& interface = interfaces_[interfaceName]; + interface.flags_ = flags; +} + void Object::finishRegistration() { for (auto& item : interfaces_) @@ -159,7 +172,7 @@ const std::vector& Object::createInterfaceVTable(InterfaceData& i auto& vtable = interfaceData.vtable_; assert(vtable.empty()); - vtable.push_back(createVTableStartItem()); + vtable.push_back(createVTableStartItem(interfaceData.flags_.toSdBusInterfaceFlags())); registerMethodsToVTable(interfaceData, vtable); registerSignalsToVTable(interfaceData, vtable); registerPropertiesToVTable(interfaceData, vtable); @@ -178,7 +191,8 @@ void Object::registerMethodsToVTable(const InterfaceData& interfaceData, std::ve vtable.push_back(createVTableMethodItem( methodName.c_str() , methodData.inputArgs_.c_str() , methodData.outputArgs_.c_str() - , &Object::sdbus_method_callback )); + , &Object::sdbus_method_callback + , methodData.flags_.toSdBusMethodFlags() )); } } @@ -190,7 +204,8 @@ void Object::registerSignalsToVTable(const InterfaceData& interfaceData, std::ve const auto& signalData = item.second; vtable.push_back(createVTableSignalItem( signalName.c_str() - , signalData.signature_.c_str() )); + , signalData.signature_.c_str() + , signalData.flags_.toSdBusSignalFlags() )); } } @@ -204,12 +219,14 @@ void Object::registerPropertiesToVTable(const InterfaceData& interfaceData, std: if (!propertyData.setCallback_) vtable.push_back(createVTablePropertyItem( propertyName.c_str() , propertyData.signature_.c_str() - , &Object::sdbus_property_get_callback )); + , &Object::sdbus_property_get_callback + , propertyData.flags_.toSdBusPropertyFlags() )); else vtable.push_back(createVTableWritablePropertyItem( propertyName.c_str() , propertyData.signature_.c_str() , &Object::sdbus_property_get_callback - , &Object::sdbus_property_set_callback )); + , &Object::sdbus_property_set_callback + , propertyData.flags_.toSdBusWritablePropertyFlags() )); } } diff --git a/src/Object.h b/src/Object.h index f3e3a3a..d35c8e2 100644 --- a/src/Object.h +++ b/src/Object.h @@ -49,28 +49,35 @@ namespace internal { , const std::string& methodName , const std::string& inputSignature , const std::string& outputSignature - , method_callback methodCallback ) override; + , method_callback methodCallback + , Flags flags ) override; void registerMethod( const std::string& interfaceName , const std::string& methodName , const std::string& inputSignature , const std::string& outputSignature - , async_method_callback asyncMethodCallback ) override; + , async_method_callback asyncMethodCallback + , Flags flags ) override; void registerSignal( const std::string& interfaceName , const std::string& signalName - , const std::string& signature ) override; - - void registerProperty( const std::string& interfaceName - , const std::string& propertyName - , const std::string& signature - , property_get_callback getCallback ) override; + , const std::string& signature + , Flags flags ) override; void registerProperty( const std::string& interfaceName , const std::string& propertyName , const std::string& signature , property_get_callback getCallback - , property_set_callback setCallback ) override; + , Flags flags ) override; + + void registerProperty( const std::string& interfaceName + , const std::string& propertyName + , const std::string& signature + , property_get_callback getCallback + , property_set_callback setCallback + , Flags flags ) override; + + void setInterfaceFlags(const std::string& interfaceName, Flags flags) override; void finishRegistration() override; @@ -89,12 +96,14 @@ namespace internal { std::string inputArgs_; std::string outputArgs_; std::function callback_; + Flags flags_; }; std::map methods_; using SignalName = std::string; struct SignalData { std::string signature_; + Flags flags_; }; std::map signals_; using PropertyName = std::string; @@ -103,9 +112,11 @@ namespace internal { std::string signature_; property_get_callback getCallback_; property_set_callback setCallback_; + Flags flags_; }; std::map properties_; std::vector vtable_; + Flags flags_; std::unique_ptr> slot_; }; diff --git a/src/VTableUtils.c b/src/VTableUtils.c index 8a7974d..d375970 100644 --- a/src/VTableUtils.c +++ b/src/VTableUtils.c @@ -26,42 +26,46 @@ #include "VTableUtils.h" #include -sd_bus_vtable createVTableStartItem() +sd_bus_vtable createVTableStartItem(uint64_t flags) { - struct sd_bus_vtable vtableStart = SD_BUS_VTABLE_START(0); + struct sd_bus_vtable vtableStart = SD_BUS_VTABLE_START(flags); return vtableStart; } sd_bus_vtable createVTableMethodItem( const char *member , const char *signature , const char *result - , sd_bus_message_handler_t handler ) + , sd_bus_message_handler_t handler + , uint64_t flags ) { - struct sd_bus_vtable vtableItem = SD_BUS_METHOD(member, signature, result, handler, SD_BUS_VTABLE_UNPRIVILEGED); + struct sd_bus_vtable vtableItem = SD_BUS_METHOD(member, signature, result, handler, flags); return vtableItem; } sd_bus_vtable createVTableSignalItem( const char *member - , const char *signature ) + , const char *signature + , uint64_t flags ) { - struct sd_bus_vtable vtableItem = SD_BUS_SIGNAL(member, signature, 0); + struct sd_bus_vtable vtableItem = SD_BUS_SIGNAL(member, signature, flags); return vtableItem; } sd_bus_vtable createVTablePropertyItem( const char *member , const char *signature - , sd_bus_property_get_t getter ) + , sd_bus_property_get_t getter + , uint64_t flags ) { - struct sd_bus_vtable vtableItem = SD_BUS_PROPERTY(member, signature, getter, 0, 0); + struct sd_bus_vtable vtableItem = SD_BUS_PROPERTY(member, signature, getter, 0, flags); return vtableItem; } sd_bus_vtable createVTableWritablePropertyItem( const char *member , const char *signature , sd_bus_property_get_t getter - , sd_bus_property_set_t setter ) + , sd_bus_property_set_t setter + , uint64_t flags ) { - struct sd_bus_vtable vtableItem = SD_BUS_WRITABLE_PROPERTY(member, signature, getter, setter, 0, 0); + struct sd_bus_vtable vtableItem = SD_BUS_WRITABLE_PROPERTY(member, signature, getter, setter, 0, flags); return vtableItem; } diff --git a/src/VTableUtils.h b/src/VTableUtils.h index 67af0ad..b6a0038 100644 --- a/src/VTableUtils.h +++ b/src/VTableUtils.h @@ -27,25 +27,30 @@ #define SDBUS_CXX_INTERNAL_VTABLEUTILS_H_ #include +#include #ifdef __cplusplus extern "C" { #endif -sd_bus_vtable createVTableStartItem(); +sd_bus_vtable createVTableStartItem(uint64_t flags); sd_bus_vtable createVTableMethodItem( const char *member , const char *signature , const char *result - , sd_bus_message_handler_t handler ); + , sd_bus_message_handler_t handler + , uint64_t flags ); sd_bus_vtable createVTableSignalItem( const char *member - , const char *signature ); + , const char *signature + , uint64_t flags ); sd_bus_vtable createVTablePropertyItem( const char *member , const char *signature - , sd_bus_property_get_t getter ); + , sd_bus_property_get_t getter + , uint64_t flags ); sd_bus_vtable createVTableWritablePropertyItem( const char *member , const char *signature , sd_bus_property_get_t getter - , sd_bus_property_set_t setter ); + , sd_bus_property_set_t setter + , uint64_t flags ); sd_bus_vtable createVTableEndItem(); #ifdef __cplusplus diff --git a/stub-generator/AdaptorGenerator.cpp b/stub-generator/AdaptorGenerator.cpp index 92e3e93..5730451 100644 --- a/stub-generator/AdaptorGenerator.cpp +++ b/stub-generator/AdaptorGenerator.cpp @@ -89,6 +89,30 @@ std::string AdaptorGenerator::processInterface(Node& interface) const Nodes signals = interface["signal"]; Nodes properties = interface["property"]; + auto annotations = getAnnotations(interface); + std::string annotationRegistration; + for (const auto& annotation : annotations) + { + const auto& annotationName = annotation.first; + const auto& annotationValue = annotation.second; + + if (annotationName == "org.freedesktop.DBus.Deprecated" && annotationValue == "true") + annotationRegistration += ".markAsDeprecated()"; + else if (annotationName == "org.freedesktop.systemd1.Privileged" && annotationValue == "true") + annotationRegistration += ".markAsPrivileged()"; + else if (annotationName == "org.freedesktop.DBus.Property.EmitsChangedSignal") + annotationRegistration += ".withPropertyUpdateBehavior(" + propertyAnnotationToFlag(annotationValue) + ")"; + else + std::cerr << "Node: " << ifaceName << ": " + << "Option '" << annotationName << "' not allowed or supported in this context! Option ignored..." << std::endl; + } + if(!annotationRegistration.empty()) + { + std::stringstream str; + str << tab << tab << "object_.setInterfaceFlags(interfaceName)" << annotationRegistration << ";" << endl; + annotationRegistration = str.str(); + } + std::string methodRegistration, methodDeclaration; std::tie(methodRegistration, methodDeclaration) = processMethods(methods); @@ -99,10 +123,11 @@ std::string AdaptorGenerator::processInterface(Node& interface) const std::tie(propertyRegistration, propertyAccessorDeclaration) = processProperties(properties); body << tab << "{" << endl - << methodRegistration - << signalRegistration - << propertyRegistration - << tab << "}" << endl << endl; + << annotationRegistration + << methodRegistration + << signalRegistration + << propertyRegistration + << tab << "}" << endl << endl; if (!signalMethods.empty()) { @@ -136,17 +161,25 @@ std::tuple AdaptorGenerator::processMethods(const Node { auto methodName = method->get("name"); + auto annotations = getAnnotations(*method); bool async{false}; - Nodes annotations = (*method)["annotation"]; - + std::string annotationRegistration; for (const auto& annotation : annotations) { - if (annotation->get("name") == "org.freedesktop.DBus.Method.Async" - && (annotation->get("value") == "server" || annotation->get("value") == "clientserver")) - { + const auto& annotationName = annotation.first; + const auto& annotationValue = annotation.second; + + if (annotationName == "org.freedesktop.DBus.Deprecated" && annotationValue == "true") + annotationRegistration += ".markAsDeprecated()"; + else if (annotationName == "org.freedesktop.DBus.Method.NoReply" && annotationValue == "true") + annotationRegistration += ".withNoReply()"; + else if (annotationName == "org.freedesktop.DBus.Method.Async" && (annotationValue == "server" || annotationValue == "clientserver")) async = true; - break; - } + else if (annotationName == "org.freedesktop.systemd1.Privileged" && annotationValue == "true") + annotationRegistration += ".markAsPrivileged()"; + else + std::cerr << "Node: " << methodName << ": " + << "Option '" << annotationName << "' not allowed or supported in this context! Option ignored..." << std::endl; } Nodes args = (*method)["arg"]; @@ -167,7 +200,8 @@ std::tuple AdaptorGenerator::processMethods(const Node << argTypeStr << "){ " << (async ? "" : "return ") << "this->" << methodName << "(" << (async ? "std::move(result)"s + (argTypeStr.empty() ? "" : ", ") : "") - << argStr << "); });" << endl; + << argStr << "); })" + << annotationRegistration << ";" << endl; declarationSS << tab << "virtual " @@ -190,6 +224,21 @@ std::tuple AdaptorGenerator::processSignals(const Node for (const auto& signal : signals) { auto name = signal->get("name"); + + auto annotations = getAnnotations(*signal); + std::string annotationRegistration; + for (const auto& annotation : annotations) + { + const auto& annotationName = annotation.first; + const auto& annotationValue = annotation.second; + + if (annotationName == "org.freedesktop.DBus.Deprecated" && annotationValue == "true") + annotationRegistration += ".markAsDeprecated()"; + else + std::cerr << "Node: " << name << ": " + << "Option '" << annotationName << "' not allowed or supported in this context! Option ignored..." << std::endl; + } + Nodes args = (*signal)["arg"]; std::string argStr, argTypeStr, typeStr;; @@ -204,6 +253,7 @@ std::tuple AdaptorGenerator::processSignals(const Node signalRegistrationSS << ".withParameters<" << typeStr << ">()"; } + signalRegistrationSS << annotationRegistration; signalRegistrationSS << ";" << endl; signalMethodSS << tab << "void " << name << "(" << argTypeStr << ")" << endl @@ -238,6 +288,24 @@ std::tuple AdaptorGenerator::processProperties(const N auto propertyArg = std::string("value"); auto propertyTypeArg = std::string("const ") + propertyType + "& " + propertyArg; + auto annotations = getAnnotations(*property); + std::string annotationRegistration; + for (const auto& annotation : annotations) + { + const auto& annotationName = annotation.first; + const auto& annotationValue = annotation.second; + + if (annotationName == "org.freedesktop.DBus.Deprecated" && annotationValue == "true") + annotationRegistration += ".markAsDeprecated()"; + else if (annotationName == "org.freedesktop.DBus.Property.EmitsChangedSignal") + annotationRegistration += ".withUpdateBehavior(" + propertyAnnotationToFlag(annotationValue) + ")"; + else if (annotationName == "org.freedesktop.systemd1.Privileged" && annotationValue == "true") + annotationRegistration += ".markAsPrivileged()"; + else + std::cerr << "Node: " << propertyName << ": " + << "Option '" << annotationName << "' not allowed or supported in this context! Option ignored..." << std::endl; + } + registrationSS << tab << tab << "object_.registerProperty(\"" << propertyName << "\")" << ".onInterface(interfaceName)"; @@ -254,6 +322,7 @@ std::tuple AdaptorGenerator::processProperties(const N "{ this->" << propertyName << "(" << propertyArg << "); })"; } + registrationSS << annotationRegistration; registrationSS << ";" << endl; if (propertyAccess == "read" || propertyAccess == "readwrite") @@ -264,3 +333,25 @@ std::tuple AdaptorGenerator::processProperties(const N return std::make_tuple(registrationSS.str(), declarationSS.str()); } + +std::map AdaptorGenerator::getAnnotations( sdbuscpp::xml::Node& node) const +{ + std::map result; + + Nodes annotations = (node)["annotation"]; + for (const auto& annotation : annotations) + { + result[annotation->get("name")] = annotation->get("value"); + } + + return result; +} + +std::string AdaptorGenerator::propertyAnnotationToFlag(const std::string& annotationValue) const +{ + return annotationValue == "true" ? "sdbus::Flags::EMITS_CHANGE_SIGNAL" + : annotationValue == "invalidates" ? "sdbus::Flags::EMITS_INVALIDATION_SIGNAL" + : annotationValue == "const" ? "sdbus::Flags::CONST_PROPERTY_VALUE" + : annotationValue == "false" ? "sdbus::Flags::EMITS_NO_SIGNAL" + : "EMITS_CHANGE_SIGNAL"; // Default +} diff --git a/stub-generator/AdaptorGenerator.h b/stub-generator/AdaptorGenerator.h index 6c17a76..02fd2c2 100644 --- a/stub-generator/AdaptorGenerator.h +++ b/stub-generator/AdaptorGenerator.h @@ -32,6 +32,7 @@ // STL #include +#include class AdaptorGenerator : public BaseGenerator { @@ -73,6 +74,19 @@ private: */ std::tuple processProperties(const sdbuscpp::xml::Nodes& properties) const; + /** + * Get annotations listed for a given node + * @param node + * @return map of annotation names to their values + */ + std::map getAnnotations(sdbuscpp::xml::Node& node) const; + + /** + * Get flag for property update behavior annotation value + * @param annotationValue + * @return flag + */ + std::string propertyAnnotationToFlag(const std::string& annotationValue) const; }; diff --git a/stub-generator/CMakeLists.txt b/stub-generator/CMakeLists.txt index e0e5b88..4e84a7f 100644 --- a/stub-generator/CMakeLists.txt +++ b/stub-generator/CMakeLists.txt @@ -18,7 +18,18 @@ find_package(EXPAT REQUIRED) # SOURCE FILES CONFIGURATION #------------------------------- -set(SDBUSCPP_XML2CPP_SRCS xml2cpp.cpp xml.cpp generator_utils.cpp BaseGenerator.cpp AdaptorGenerator.cpp ProxyGenerator.cpp) +set(SDBUSCPP_XML2CPP_SRCS + xml2cpp.cpp + xml.h + xml.cpp + generator_utils.h + generator_utils.cpp + BaseGenerator.h + BaseGenerator.cpp + AdaptorGenerator.h + AdaptorGenerator.cpp + ProxyGenerator.h + ProxyGenerator.cpp) #------------------------------- # GENERAL COMPILER CONFIGURATION diff --git a/stub-generator/ProxyGenerator.cpp b/stub-generator/ProxyGenerator.cpp index 6c6ce0e..3923486 100644 --- a/stub-generator/ProxyGenerator.cpp +++ b/stub-generator/ProxyGenerator.cpp @@ -127,6 +127,24 @@ std::string ProxyGenerator::processMethods(const Nodes& methods) const Nodes inArgs = args.select("direction" , "in"); Nodes outArgs = args.select("direction" , "out"); + bool dontExpectReply{false}; + Nodes annotations = (*method)["annotation"]; + for (const auto& annotation : annotations) + { + if (annotation->get("name") == "org.freedesktop.DBus.Method.NoReply" + && annotation->get("value") == "true") + { + dontExpectReply = true; + break; + } + } + if (dontExpectReply && outArgs.size() > 0) + { + std::cerr << "Function: " << name << ": "; + std::cerr << "Option 'org.freedesktop.DBus.Method.NoReply' not allowed for methods with 'out' variables! Option ignored..." << std::endl; + dontExpectReply = false; + } + auto retType = outArgsToType(outArgs); std::string argStr, argTypeStr; std::tie(argStr, argTypeStr, std::ignore) = argsToNamesAndTypes(inArgs); @@ -152,6 +170,10 @@ std::string ProxyGenerator::processMethods(const Nodes& methods) const methodSS << ".storeResultsTo(result);" << endl << tab << tab << "return result"; } + else if (dontExpectReply) + { + methodSS << ".dontExpectReply()"; + } methodSS << ";" << endl << tab << "}" << endl << endl; } @@ -159,7 +181,6 @@ std::string ProxyGenerator::processMethods(const Nodes& methods) const return methodSS.str(); } - std::tuple ProxyGenerator::processSignals(const Nodes& signals) const { std::ostringstream registrationSS, declarationSS; diff --git a/test/integrationtests/AdaptorAndProxy_test.cpp b/test/integrationtests/AdaptorAndProxy_test.cpp index 0ccd5ce..1132a5b 100644 --- a/test/integrationtests/AdaptorAndProxy_test.cpp +++ b/test/integrationtests/AdaptorAndProxy_test.cpp @@ -41,6 +41,7 @@ #include #include #include +#include using ::testing::Eq; using ::testing::Gt; @@ -200,6 +201,51 @@ TEST_F(SdbusTestObject, CallsMethodWithComplexTypeSuccesfully) ASSERT_THAT(resComplex.count(0), Eq(1)); } +TEST_F(SdbusTestObject, CallsMultiplyMethodWithNoReplyFlag) +{ + m_proxy->multiplyWithNoReply(INT64_VALUE, DOUBLE_VALUE); + + for (auto i = 0; i < 100; ++i) + { + if (m_adaptor->wasMultiplyCalled()) + break; + std::this_thread::sleep_for(10ms); + } + ASSERT_TRUE(m_adaptor->wasMultiplyCalled()); + ASSERT_THAT(m_adaptor->getMultiplyResult(), Eq(INT64_VALUE * DOUBLE_VALUE)); +} + +TEST_F(SdbusTestObject, CallsMethodThatThrowsError) +{ + try + { + m_proxy->throwError(); + FAIL() << "Expected sdbus::Error exception"; + } + catch (const sdbus::Error& e) + { + ASSERT_THAT(e.getName(), Eq("org.freedesktop.DBus.Error.AccessDenied")); + ASSERT_THAT(e.getMessage(), Eq("A test error occurred (Operation not permitted)")); + } + catch(...) + { + FAIL() << "Expected sdbus::Error exception"; + } +} + +TEST_F(SdbusTestObject, CallsErrorThrowingMethodWithDontExpectReplySet) +{ + ASSERT_NO_THROW(m_proxy->throwErrorWithNoReply()); + + for (auto i = 0; i < 100; ++i) + { + if (m_adaptor->wasThrowErrorCalled()) + break; + std::this_thread::sleep_for(10ms); + } + ASSERT_TRUE(m_adaptor->wasThrowErrorCalled()); +} + TEST_F(SdbusTestObject, DoesServerSideAsynchoronousMethodInParallel) { // Yeah, this is kinda timing-dependent test, but times should be safe... @@ -227,7 +273,6 @@ TEST_F(SdbusTestObject, DoesServerSideAsynchoronousMethodInParallel) TEST_F(SdbusTestObject, HandlesCorrectlyABulkOfParallelServerSideAsyncMethods) { - std::mutex mtx; std::atomic resultCount{}; std::atomic invoke{}; std::atomic startedCount{}; @@ -331,7 +376,7 @@ TEST_F(SdbusTestObject, ReadsReadPropertySuccesfully) TEST_F(SdbusTestObject, WritesAndReadsReadWritePropertySuccesfully) { - auto x = 42; + uint32_t x = 42; ASSERT_NO_THROW(m_proxy->action(x)); ASSERT_THAT(m_proxy->action(), Eq(x)); } @@ -346,3 +391,8 @@ TEST_F(SdbusTestObject, CannotReadFromWriteProperty) { ASSERT_THROW(m_proxy->blocking(), sdbus::Error); } + +TEST_F(SdbusTestObject, AnswersXmlApiDescriptionOnIntrospection) +{ + ASSERT_THAT(m_proxy->Introspect(), Eq(testing_adaptor::expectedXmlApiDescription)); +} diff --git a/test/integrationtests/TestingAdaptor.h b/test/integrationtests/TestingAdaptor.h index 163ca14..e8b8e59 100644 --- a/test/integrationtests/TestingAdaptor.h +++ b/test/integrationtests/TestingAdaptor.h @@ -29,6 +29,7 @@ #include "adaptor-glue.h" #include #include +#include class TestingAdaptor : public sdbus::Interfaces { @@ -38,6 +39,10 @@ public: virtual ~TestingAdaptor() { } + bool wasMultiplyCalled() const { return m_multiplyCalled; } + double getMultiplyResult() const { return m_multiplyResult; } + bool wasThrowErrorCalled() const { return m_throwErrorCalled; } + protected: void noArgNoReturn() const { } @@ -48,6 +53,12 @@ protected: double multiply(const int64_t& a, const double& b) const { return a * b; } + void multiplyWithNoReply(const int64_t& a, const double& b) const + { + m_multiplyResult = a * b; + m_multiplyCalled = true; + } + std::vector getInts16FromStruct(const sdbus::Struct>& x) const { std::vector res{x.get<1>()}; @@ -154,6 +165,12 @@ protected: }; } + void throwError() const + { + m_throwErrorCalled = true; + throw sdbus::createError(1, "A test error occurred"); + } + std::string state() { return STRING_VALUE; } uint32_t action() { return m_action; } void action(const uint32_t& value) { m_action = value; } @@ -164,6 +181,10 @@ private: uint32_t m_action; bool m_blocking; + // For dont-expect-reply method call verifications + mutable std::atomic m_multiplyCalled{}; + mutable double m_multiplyResult{}; + mutable std::atomic m_throwErrorCalled{}; }; diff --git a/test/integrationtests/TestingProxy.h b/test/integrationtests/TestingProxy.h index 130c04e..8e619b2 100644 --- a/test/integrationtests/TestingProxy.h +++ b/test/integrationtests/TestingProxy.h @@ -28,10 +28,10 @@ #include "proxy-glue.h" -class TestingProxy : public sdbus::ProxyInterfaces<::testing_proxy> +class TestingProxy : public sdbus::ProxyInterfaces<::testing_proxy, sdbus::introspectable_proxy> { public: - using sdbus::ProxyInterfaces<::testing_proxy>::ProxyInterfaces; + using sdbus::ProxyInterfaces<::testing_proxy, sdbus::introspectable_proxy>::ProxyInterfaces; int getSimpleCallCount() const { return m_simpleCallCounter; } std::map getMap() const { return m_map; } diff --git a/test/integrationtests/adaptor-glue.h b/test/integrationtests/adaptor-glue.h index 641cc81..54b43c0 100644 --- a/test/integrationtests/adaptor-glue.h +++ b/test/integrationtests/adaptor-glue.h @@ -57,11 +57,14 @@ protected: testing_adaptor(sdbus::IObject& object) : object_(object) { + object_.setInterfaceFlags(INTERFACE_NAME).markAsDeprecated().withPropertyUpdateBehavior(sdbus::Flags::EMITS_NO_SIGNAL); + object_.registerMethod("noArgNoReturn").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->noArgNoReturn(); }); object_.registerMethod("getInt").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->getInt(); }); object_.registerMethod("getTuple").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->getTuple(); }); object_.registerMethod("multiply").onInterface(INTERFACE_NAME).implementedAs([this](const int64_t& a, const double& b){ return this->multiply(a, b); }); + object_.registerMethod("multiplyWithNoReply").onInterface(INTERFACE_NAME).implementedAs([this](const int64_t& a, const double& b){ this->multiplyWithNoReply(a, b); }).markAsDeprecated().withNoReply(); object_.registerMethod("getInts16FromStruct").onInterface(INTERFACE_NAME).implementedAs([this]( const sdbus::Struct>& x){ return this->getInts16FromStruct(x); }); @@ -95,15 +98,20 @@ protected: object_.registerMethod("getSignature").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->getSignature(); }); object_.registerMethod("getObjectPath").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->getObjectPath(); }); - object_.registerMethod("getComplex").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->getComplex(); }); + object_.registerMethod("getComplex").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->getComplex(); }).markAsDeprecated(); + + object_.registerMethod("throwError").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->throwError(); }); + object_.registerMethod("throwErrorWithNoReply").onInterface(INTERFACE_NAME).implementedAs([this](){ this->throwError(); }).withNoReply(); + + object_.registerMethod("doPrivilegedStuff").onInterface(INTERFACE_NAME).implementedAs([](){}).markAsPrivileged(); // registration of signals is optional, it is useful because of introspection - object_.registerSignal("simpleSignal").onInterface(INTERFACE_NAME); + object_.registerSignal("simpleSignal").onInterface(INTERFACE_NAME).markAsDeprecated(); object_.registerSignal("signalWithMap").onInterface(INTERFACE_NAME).withParameters>(); object_.registerSignal("signalWithVariant").onInterface(INTERFACE_NAME).withParameters(); - object_.registerProperty("state").onInterface(INTERFACE_NAME).withGetter([this](){ return this->state(); }); - object_.registerProperty("action").onInterface(INTERFACE_NAME).withGetter([this](){ return this->action(); }).withSetter([this](const uint32_t& value){ this->action(value); }); + object_.registerProperty("state").onInterface(INTERFACE_NAME).withGetter([this](){ return this->state(); }).markAsDeprecated().withUpdateBehavior(sdbus::Flags::CONST_PROPERTY_VALUE); + object_.registerProperty("action").onInterface(INTERFACE_NAME).withGetter([this](){ return this->action(); }).withSetter([this](const uint32_t& value){ this->action(value); }).withUpdateBehavior(sdbus::Flags::EMITS_NO_SIGNAL); object_.registerProperty("blocking").onInterface(INTERFACE_NAME)./*withGetter([this](){ return this->blocking(); }).*/withSetter([this](const bool& value){ this->blocking(value); }); } @@ -143,6 +151,7 @@ protected: virtual int32_t getInt() const = 0; virtual std::tuple getTuple() const = 0; virtual double multiply(const int64_t& a, const double& b) const = 0; + virtual void multiplyWithNoReply(const int64_t& a, const double& b) const = 0; virtual std::vector getInts16FromStruct(const sdbus::Struct>& x) const = 0; virtual sdbus::Variant processVariant(sdbus::Variant& v) = 0; virtual std::map getMapOfVariants(const std::vector& x, const sdbus::Struct& y) const = 0; @@ -154,6 +163,7 @@ protected: virtual sdbus::Signature getSignature() const = 0; virtual sdbus::ObjectPath getObjectPath() const = 0; virtual ComplexType getComplex() const = 0; + virtual void throwError() const = 0; virtual std::string state() = 0; virtual uint32_t action() = 0; @@ -161,6 +171,139 @@ protected: virtual bool blocking() = 0; virtual void blocking(const bool& value) = 0; +public: // For testing purposes + static constexpr char expectedXmlApiDescription[] = +R"delimiter( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +)delimiter"; + }; diff --git a/test/integrationtests/proxy-glue.h b/test/integrationtests/proxy-glue.h index 8db4d23..20cf68e 100644 --- a/test/integrationtests/proxy-glue.h +++ b/test/integrationtests/proxy-glue.h @@ -45,7 +45,6 @@ protected: { this->onSignalWithoutRegistration(s); }); } - virtual void onSimpleSignal() = 0; virtual void onSignalWithMap(const std::map& map) = 0; virtual void onSignalWithVariant(const sdbus::Variant& v) = 0; @@ -78,6 +77,11 @@ public: return result; } + void multiplyWithNoReply(const int64_t& a, const double& b) + { + object_.callMethod("multiplyWithNoReply").onInterface(INTERFACE_NAME).withArguments(a, b).dontExpectReply(); + } + std::vector getInts16FromStruct(const sdbus::Struct>& x) { std::vector result; @@ -155,6 +159,16 @@ public: return result; } + void throwError() + { + object_.callMethod("throwError").onInterface(INTERFACE_NAME); + } + + void throwErrorWithNoReply() + { + object_.callMethod("throwErrorWithNoReply").onInterface(INTERFACE_NAME).dontExpectReply(); + } + int32_t callNonexistentMethod() { int32_t result;