feat: add support for match rules

This commit is contained in:
Stanislav Angelovic
2022-06-17 23:04:07 +02:00
committed by Stanislav Angelovič
parent d864e1dfa4
commit 5ec6027d5f
7 changed files with 168 additions and 2 deletions

View File

@ -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
----------

41
include/sdbus-c++/IConnection.h Normal file → Executable file
View File

@ -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()
*

2
include/sdbus-c++/TypeTraits.h Normal file → Executable file
View File

@ -46,6 +46,7 @@ namespace sdbus {
class MethodCall;
class MethodReply;
class Signal;
class Message;
class PropertySetCall;
class PropertyGetReply;
template <typename... _Results> class Result;
@ -58,6 +59,7 @@ namespace sdbus {
using method_callback = std::function<void(MethodCall msg)>;
using async_reply_handler = std::function<void(MethodReply& reply, const Error* error)>;
using signal_handler = std::function<void(Signal& signal)>;
using message_handler = std::function<void(Message& msg)>;
using property_set_callback = std::function<void(PropertySetCall& msg)>;
using property_get_callback = std::function<void(PropertyGetReply& reply)>;

28
src/Connection.cpp Normal file → Executable file
View File

@ -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>(MatchInfo{std::move(callback), *this, {}});
auto messageHandler = [](sd_bus_message *sdbusMessage, void *userData, sd_bus_error */*retError*/) -> int
{
auto* matchInfo = static_cast<MatchInfo*>(userData);
auto message = Message::Factory::create<PlainMessage>(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<MatchInfo*>(ptr);
iface_->sd_bus_slot_unref(matchInfo->slot);
std::default_delete<MatchInfo>{}(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

11
src/Connection.h Normal file → Executable file
View File

@ -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<ISdBus> iface_;
BusPtr bus_;
@ -147,6 +157,7 @@ namespace sdbus::internal {
EventFd loopExitFd_;
EventFd eventFd_;
std::atomic<uint64_t> activeTimeout_{};
std::vector<Slot> floatingMatchRules_;
};
}

View File

@ -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*/)

View File

@ -26,6 +26,7 @@
#include "TestAdaptor.h"
#include "TestProxy.h"
#include "TestFixture.h"
#include "sdbus-c++/sdbus-c++.h"
#include <gtest/gtest.h>
@ -38,8 +39,13 @@
#include <future>
#include <unistd.h>
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<bool> 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<bool> 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<bool> 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<size_t> numberOfMatchingMessages{};
auto slot = s_proxyConnection->addMatch(matchRule, [&](sdbus::Message& msg)
{
if(msg.getMemberName() == "simpleSignal")
numberOfMatchingMessages++;
});
auto adaptor2 = std::make_unique<TestAdaptor>(*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));
}