diff --git a/docs/using-sdbus-c++.md b/docs/using-sdbus-c++.md index adf083c..4afb5b3 100644 --- a/docs/using-sdbus-c++.md +++ b/docs/using-sdbus-c++.md @@ -19,7 +19,8 @@ Using sdbus-c++ library 14. [Asynchronous client-side methods](#asynchronous-client-side-methods) 15. [Using D-Bus properties](#using-d-bus-properties) 16. [Standard D-Bus interfaces](#standard-d-bus-interfaces) -17. [Conclusion](#conclusion) +17. [Support for match rules](#support-for-match-rules) +18. [Conclusion](#conclusion) Introduction ------------ @@ -1279,6 +1280,11 @@ Note that signals of afore-mentioned standard D-Bus interfaces are not emitted b Working examples of using standard D-Bus interfaces can be found in [sdbus-c++ integration tests](/tests/integrationtests/DBusStandardInterfacesTests.cpp) or the [examples](/examples) directory. +Support for match rules +----------------------- + +`IConnection` class provides `addMatch` method that you can use to install match rules. An associated callback handler will be called upon an incoming message matching given match rule. There is support for both client-owned and floating (library-owned) match rules. Consult `IConnection` header or sdbus-c++ doxygen documentation for more information. + Conclusion ---------- diff --git a/include/sdbus-c++/IConnection.h b/include/sdbus-c++/IConnection.h old mode 100644 new mode 100755 index 0ae5fb9..a232ddc --- a/include/sdbus-c++/IConnection.h +++ b/include/sdbus-c++/IConnection.h @@ -265,6 +265,47 @@ namespace sdbus { */ virtual void addObjectManager(const std::string& objectPath, floating_slot_t) = 0; + /*! + * @brief Adds a match rule for incoming message dispatching + * + * @param[in] match Match expression to filter incoming D-Bus message + * @param[in] callback Callback handler to be called upon incoming D-Bus message matching the rule + * @return RAII-style slot handle representing the ownership of the subscription + * + * The method installs a match rule for messages received on the specified bus connection. + * The syntax of the match rule expression passed in match is described in the D-Bus specification. + * The specified handler function callback is called for each incoming message matching the specified + * expression. The match is installed synchronously when connected to a bus broker, i.e. the call + * sends a control message requested the match to be added to the broker and waits until the broker + * confirms the match has been installed successfully. + * + * Simply let go of the slot instance to uninstall the match rule from the bus connection. The slot + * must not outlive the connection for the slot is associated with it. + * + * For more information, consult `man sd_bus_add_match`. + * + * @throws sdbus::Error in case of failure + */ + [[nodiscard]] virtual Slot addMatch(const std::string& match, message_handler callback) = 0; + + /*! + * @brief Adds a floating match rule for incoming message dispatching + * + * @param[in] match Match expression to filter incoming D-Bus message + * @param[in] callback Callback handler to be called upon incoming D-Bus message matching the rule + * @param[in] Floating slot tag + * + * The method installs a floating match rule for messages received on the specified bus connection. + * Floating means that the bus connection object owns the match rule, i.e. lifetime of the match rule + * is bound to the lifetime of the bus connection. + * + * Refer to the @c addMatch(const std::string& match, message_handler callback) documentation for more + * information. + * + * @throws sdbus::Error in case of failure + */ + virtual void addMatch(const std::string& match, message_handler callback, floating_slot_t) = 0; + /*! * @copydoc IConnection::enterEventLoop() * diff --git a/include/sdbus-c++/TypeTraits.h b/include/sdbus-c++/TypeTraits.h old mode 100644 new mode 100755 index fe8f343..e11dc45 --- a/include/sdbus-c++/TypeTraits.h +++ b/include/sdbus-c++/TypeTraits.h @@ -46,6 +46,7 @@ namespace sdbus { class MethodCall; class MethodReply; class Signal; + class Message; class PropertySetCall; class PropertyGetReply; template class Result; @@ -58,6 +59,7 @@ namespace sdbus { using method_callback = std::function; using async_reply_handler = std::function; using signal_handler = std::function; + using message_handler = std::function; using property_set_callback = std::function; using property_get_callback = std::function; diff --git a/src/Connection.cpp b/src/Connection.cpp old mode 100644 new mode 100755 index f6f64c7..9f8cfd3 --- a/src/Connection.cpp +++ b/src/Connection.cpp @@ -183,6 +183,34 @@ uint64_t Connection::getMethodCallTimeout() const return timeout; } +Slot Connection::addMatch(const std::string& match, message_handler callback) +{ + auto matchInfo = std::make_unique(MatchInfo{std::move(callback), *this, {}}); + + auto messageHandler = [](sd_bus_message *sdbusMessage, void *userData, sd_bus_error */*retError*/) -> int + { + auto* matchInfo = static_cast(userData); + auto message = Message::Factory::create(sdbusMessage, &matchInfo->connection.getSdBusInterface()); + matchInfo->callback(message); + return 0; + }; + + auto r = iface_->sd_bus_add_match(bus_.get(), &matchInfo->slot, match.c_str(), std::move(messageHandler), matchInfo.get()); + SDBUS_THROW_ERROR_IF(r < 0, "Failed to add match", -r); + + return {matchInfo.release(), [this](void *ptr) + { + auto* matchInfo = static_cast(ptr); + iface_->sd_bus_slot_unref(matchInfo->slot); + std::default_delete{}(matchInfo); + }}; +} + +void Connection::addMatch(const std::string& match, message_handler callback, floating_slot_t) +{ + floatingMatchRules_.push_back(addMatch(match, std::move(callback))); +} + Slot Connection::addObjectVTable( const std::string& objectPath , const std::string& interfaceName , const sd_bus_vtable* vtable diff --git a/src/Connection.h b/src/Connection.h old mode 100644 new mode 100755 index 5d71def..bd78828 --- a/src/Connection.h +++ b/src/Connection.h @@ -74,6 +74,9 @@ namespace sdbus::internal { void setMethodCallTimeout(uint64_t timeout) override; uint64_t getMethodCallTimeout() const override; + [[nodiscard]] Slot addMatch(const std::string& match, message_handler callback) override; + void addMatch(const std::string& match, message_handler callback, floating_slot_t) override; + const ISdBus& getSdBusInterface() const override; ISdBus& getSdBusInterface() override; @@ -138,6 +141,13 @@ namespace sdbus::internal { int fd{-1}; }; + struct MatchInfo + { + message_handler callback; + Connection& connection; + sd_bus_slot *slot; + }; + private: std::unique_ptr iface_; BusPtr bus_; @@ -147,6 +157,7 @@ namespace sdbus::internal { EventFd loopExitFd_; EventFd eventFd_; std::atomic activeTimeout_{}; + std::vector floatingMatchRules_; }; } diff --git a/src/Proxy.cpp b/src/Proxy.cpp index 7fc5a80..7500e54 100644 --- a/src/Proxy.cpp +++ b/src/Proxy.cpp @@ -276,7 +276,7 @@ int Proxy::sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userDat // Intentionally left blank -- sdbus-c++ exceptions shall not bubble up to the underlying C sd-bus library } - return 1; + return 0; } int Proxy::sdbus_signal_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error */*retError*/) diff --git a/tests/integrationtests/DBusGeneralTests.cpp b/tests/integrationtests/DBusGeneralTests.cpp index 29880e4..3c1bb3f 100644 --- a/tests/integrationtests/DBusGeneralTests.cpp +++ b/tests/integrationtests/DBusGeneralTests.cpp @@ -26,6 +26,7 @@ #include "TestAdaptor.h" #include "TestProxy.h" +#include "TestFixture.h" #include "sdbus-c++/sdbus-c++.h" #include @@ -38,8 +39,13 @@ #include #include +using ::testing::ElementsAre; +using ::testing::Eq; +using namespace std::chrono_literals; using namespace sdbus::test; +using AConnection = TestFixture; + /*-------------------------------------*/ /* -- TEST CASES -- */ /*-------------------------------------*/ @@ -52,3 +58,75 @@ TEST(AdaptorAndProxy, CanBeConstructedSuccesfully) ASSERT_NO_THROW(TestAdaptor adaptor(*connection, OBJECT_PATH)); ASSERT_NO_THROW(TestProxy proxy(BUS_NAME, OBJECT_PATH)); } + +TEST_F(AConnection, WillCallCallbackHandlerForIncomingMessageMatchingMatchRule) +{ + auto matchRule = "sender='" + BUS_NAME + "',path='" + OBJECT_PATH + "'"; + std::atomic matchingMessageReceived{false}; + auto slot = s_proxyConnection->addMatch(matchRule, [&](sdbus::Message& msg) + { + if(msg.getPath() == OBJECT_PATH) + matchingMessageReceived = true; + }); + + m_adaptor->emitSimpleSignal(); + + ASSERT_TRUE(waitUntil(matchingMessageReceived)); +} + +TEST_F(AConnection, WillUnsubscribeMatchRuleWhenClientDestroysTheAssociatedSlot) +{ + auto matchRule = "sender='" + BUS_NAME + "',path='" + OBJECT_PATH + "'"; + std::atomic matchingMessageReceived{false}; + auto slot = s_proxyConnection->addMatch(matchRule, [&](sdbus::Message& msg) + { + if(msg.getPath() == OBJECT_PATH) + matchingMessageReceived = true; + }); + slot.reset(); + + m_adaptor->emitSimpleSignal(); + + ASSERT_FALSE(waitUntil(matchingMessageReceived, 2s)); +} + +TEST_F(AConnection, CanAddFloatingMatchRule) +{ + auto matchRule = "sender='" + BUS_NAME + "',path='" + OBJECT_PATH + "'"; + std::atomic matchingMessageReceived{false}; + auto con = sdbus::createSystemBusConnection(); + con->enterEventLoopAsync(); + auto callback = [&](sdbus::Message& msg) + { + if(msg.getPath() == OBJECT_PATH) + matchingMessageReceived = true; + }; + con->addMatch(matchRule, std::move(callback), sdbus::floating_slot); + m_adaptor->emitSimpleSignal(); + assert(waitUntil(matchingMessageReceived, 2s)); + matchingMessageReceived = false; + + con.reset(); + m_adaptor->emitSimpleSignal(); + + ASSERT_FALSE(waitUntil(matchingMessageReceived, 2s)); +} + +TEST_F(AConnection, WillNotPassToMatchCallbackMessagesThatDoNotMatchTheRule) +{ + auto matchRule = "type='signal',interface='" + INTERFACE_NAME + "',member='simpleSignal'"; + std::atomic numberOfMatchingMessages{}; + auto slot = s_proxyConnection->addMatch(matchRule, [&](sdbus::Message& msg) + { + if(msg.getMemberName() == "simpleSignal") + numberOfMatchingMessages++; + }); + auto adaptor2 = std::make_unique(*s_adaptorConnection, OBJECT_PATH_2); + + m_adaptor->emitSignalWithMap({}); + adaptor2->emitSimpleSignal(); + m_adaptor->emitSimpleSignal(); + + ASSERT_TRUE(waitUntil([&](){ return numberOfMatchingMessages == 2; })); + ASSERT_FALSE(waitUntil([&](){ return numberOfMatchingMessages > 2; }, 1s)); +}