diff --git a/src/Connection.cpp b/src/Connection.cpp index 1a558a3..d235ed1 100644 --- a/src/Connection.cpp +++ b/src/Connection.cpp @@ -70,6 +70,13 @@ Connection::Connection(std::unique_ptr&& interface, remote_system_bus_t, { } +Connection::Connection(std::unique_ptr&& interface, pseudo_bus_t) + : iface_(std::move(interface)) + , bus_(openPseudoBus()) +{ + assert(iface_ != nullptr); +} + Connection::~Connection() { Connection::leaveEventLoop(); @@ -394,6 +401,23 @@ Connection::BusPtr Connection::openBus(const BusFactory& busFactory) return busPtr; } +Connection::BusPtr Connection::openPseudoBus() +{ + sd_bus* bus{}; + + int r = iface_->sd_bus_new(&bus); + SDBUS_THROW_ERROR_IF(r < 0, "Failed to open pseudo bus", -r); + + (void)iface_->sd_bus_start(bus); + // It is expected that sd_bus_start has failed here, returning -EINVAL, due to having + // not set a bus address, but it will leave the bus in an OPENING state, which enables + // us to create plain D-Bus messages as a local data storage (for Variant, for example), + // without dependency on real IPC communication with the D-Bus broker daemon. + SDBUS_THROW_ERROR_IF(r < 0 && r != -EINVAL, "Failed to start pseudo bus", -r); + + return {bus, [this](sd_bus* bus){ return iface_->sd_bus_close_unref(bus); }}; +} + void Connection::finishHandshake(sd_bus* bus) { // Process all requests that are part of the initial handshake, @@ -573,6 +597,12 @@ std::unique_ptr createConnection() return std::unique_ptr(connectionInternal); } +std::unique_ptr createPseudoConnection() +{ + auto interface = std::make_unique(); + return std::make_unique(std::move(interface), Connection::pseudo_bus); +} + } // namespace sdbus::internal namespace sdbus { diff --git a/src/Connection.h b/src/Connection.h index 32fe0b4..445c915 100644 --- a/src/Connection.h +++ b/src/Connection.h @@ -57,12 +57,15 @@ namespace sdbus::internal { inline static constexpr custom_session_bus_t custom_session_bus{}; struct remote_system_bus_t{}; inline static constexpr remote_system_bus_t remote_system_bus{}; + struct pseudo_bus_t{}; // A bus connection that is not really established with D-Bus daemon + inline static constexpr pseudo_bus_t pseudo_bus{}; Connection(std::unique_ptr&& interface, default_bus_t); Connection(std::unique_ptr&& interface, system_bus_t); Connection(std::unique_ptr&& interface, session_bus_t); Connection(std::unique_ptr&& interface, custom_session_bus_t, const std::string& address); Connection(std::unique_ptr&& interface, remote_system_bus_t, const std::string& host); + Connection(std::unique_ptr&& interface, pseudo_bus_t); ~Connection() override; void requestName(const std::string& name) override; @@ -126,6 +129,7 @@ namespace sdbus::internal { Connection(std::unique_ptr&& interface, const BusFactory& busFactory); BusPtr openBus(const std::function& busFactory); + BusPtr openPseudoBus(); void finishHandshake(sd_bus* bus); bool waitForNextRequest(); static std::string composeSignalMatchFilter( const std::string &sender diff --git a/src/IConnection.h b/src/IConnection.h index 20bb196..74b7832 100644 --- a/src/IConnection.h +++ b/src/IConnection.h @@ -95,6 +95,7 @@ namespace sdbus::internal { }; [[nodiscard]] std::unique_ptr createConnection(); + [[nodiscard]] std::unique_ptr createPseudoConnection(); } diff --git a/src/ISdBus.h b/src/ISdBus.h index 10322c4..5fdd352 100644 --- a/src/ISdBus.h +++ b/src/ISdBus.h @@ -79,11 +79,15 @@ namespace sdbus::internal { virtual int sd_bus_add_match(sd_bus *bus, sd_bus_slot **slot, const char *match, sd_bus_message_handler_t callback, void *userdata) = 0; virtual sd_bus_slot* sd_bus_slot_unref(sd_bus_slot *slot) = 0; + virtual int sd_bus_new(sd_bus **ret) = 0; + virtual int sd_bus_start(sd_bus *bus) = 0; + virtual int sd_bus_process(sd_bus *bus, sd_bus_message **r) = 0; virtual int sd_bus_get_poll_data(sd_bus *bus, PollData* data) = 0; virtual int sd_bus_flush(sd_bus *bus) = 0; virtual sd_bus *sd_bus_flush_close_unref(sd_bus *bus) = 0; + virtual sd_bus *sd_bus_close_unref(sd_bus *bus) = 0; virtual int sd_bus_message_set_destination(sd_bus_message *m, const char *destination) = 0; diff --git a/src/Message.cpp b/src/Message.cpp index 7a9e359..696df64 100644 --- a/src/Message.cpp +++ b/src/Message.cpp @@ -864,9 +864,13 @@ void Signal::setDestination(const std::string& destination) PlainMessage createPlainMessage() { - static auto connection = internal::createConnection(); + //static auto connection = internal::createConnection(); + // Let's create a pseudo connection -- one that does not really connect to the real bus. + // This is a bit of a hack, but it enables use to work with D-Bus message locally without + // the need of D-Bus daemon. This is especially useful in unit tests of both sdbus-c++ and client code. + // Additionally, it's light-weight and fast solution. + static auto connection = internal::createPseudoConnection(); return connection->createPlainMessage(); } - } diff --git a/src/SdBus.cpp b/src/SdBus.cpp index 6dfecaa..0fec56b 100644 --- a/src/SdBus.cpp +++ b/src/SdBus.cpp @@ -261,6 +261,16 @@ sd_bus_slot* SdBus::sd_bus_slot_unref(sd_bus_slot *slot) return ::sd_bus_slot_unref(slot); } +int SdBus::sd_bus_new(sd_bus **ret) +{ + return ::sd_bus_new(ret); +} + +int SdBus::sd_bus_start(sd_bus *bus) +{ + return ::sd_bus_start(bus); +} + int SdBus::sd_bus_process(sd_bus *bus, sd_bus_message **r) { std::lock_guard lock(sdbusMutex_); @@ -297,6 +307,16 @@ sd_bus* SdBus::sd_bus_flush_close_unref(sd_bus *bus) return ::sd_bus_flush_close_unref(bus); } +sd_bus* SdBus::sd_bus_close_unref(sd_bus *bus) +{ +#if LIBSYSTEMD_VERSION>=241 + return ::sd_bus_close_unref(bus); +#else + ::sd_bus_close(bus); + return ::sd_bus_unref(bus); +#endif +} + int SdBus::sd_bus_message_set_destination(sd_bus_message *m, const char *destination) { std::lock_guard lock(sdbusMutex_); diff --git a/src/SdBus.h b/src/SdBus.h index 050496c..364d127 100644 --- a/src/SdBus.h +++ b/src/SdBus.h @@ -71,11 +71,15 @@ public: virtual int sd_bus_add_match(sd_bus *bus, sd_bus_slot **slot, const char *match, sd_bus_message_handler_t callback, void *userdata) override; virtual sd_bus_slot* sd_bus_slot_unref(sd_bus_slot *slot) override; + virtual int sd_bus_new(sd_bus **ret) override; + virtual int sd_bus_start(sd_bus *bus) override; + virtual int sd_bus_process(sd_bus *bus, sd_bus_message **r) override; virtual int sd_bus_get_poll_data(sd_bus *bus, PollData* data) override; virtual int sd_bus_flush(sd_bus *bus) override; virtual sd_bus *sd_bus_flush_close_unref(sd_bus *bus) override; + virtual sd_bus *sd_bus_close_unref(sd_bus *bus) override; virtual int sd_bus_message_set_destination(sd_bus_message *m, const char *destination) override; diff --git a/tests/unittests/Connection_test.cpp b/tests/unittests/Connection_test.cpp index 7e55ee1..f1c4db1 100644 --- a/tests/unittests/Connection_test.cpp +++ b/tests/unittests/Connection_test.cpp @@ -173,6 +173,12 @@ template<> void AConnectionNameRequest::setUpBu { EXPECT_CALL(*sdBusIntfMock_, sd_bus_open_system_remote(_, _)).WillOnce(DoAll(SetArgPointee<0>(fakeBusPtr_), Return(1))); } +template<> void AConnectionNameRequest::setUpBusOpenExpectation() +{ + EXPECT_CALL(*sdBusIntfMock_, sd_bus_new(_)).WillOnce(DoAll(SetArgPointee<0>(fakeBusPtr_), Return(1))); + // `sd_bus_start` for pseudo connection shall return an error value, remember this is a fake connection... + EXPECT_CALL(*sdBusIntfMock_, sd_bus_start(fakeBusPtr_)).WillOnce(Return(-EINVAL)); +} template std::unique_ptr AConnectionNameRequest<_BusTypeTag>::makeConnection() { @@ -192,6 +198,7 @@ typedef ::testing::Types< Connection::default_bus_t , Connection::session_bus_t , Connection::custom_session_bus_t , Connection::remote_system_bus_t + , Connection::pseudo_bus_t > BusTypeTags; TYPED_TEST_SUITE(AConnectionNameRequest, BusTypeTags); diff --git a/tests/unittests/mocks/SdBusMock.h b/tests/unittests/mocks/SdBusMock.h index d89aa64..9e74df0 100644 --- a/tests/unittests/mocks/SdBusMock.h +++ b/tests/unittests/mocks/SdBusMock.h @@ -70,11 +70,15 @@ public: MOCK_METHOD5(sd_bus_add_match, int(sd_bus *bus, sd_bus_slot **slot, const char *match, sd_bus_message_handler_t callback, void *userdata)); MOCK_METHOD1(sd_bus_slot_unref, sd_bus_slot*(sd_bus_slot *slot)); + MOCK_METHOD1(sd_bus_new, int(sd_bus **ret)); + MOCK_METHOD1(sd_bus_start, int(sd_bus *bus)); + MOCK_METHOD2(sd_bus_process, int(sd_bus *bus, sd_bus_message **r)); MOCK_METHOD2(sd_bus_get_poll_data, int(sd_bus *bus, PollData* data)); MOCK_METHOD1(sd_bus_flush, int(sd_bus *bus)); MOCK_METHOD1(sd_bus_flush_close_unref, sd_bus *(sd_bus *bus)); + MOCK_METHOD1(sd_bus_close_unref, sd_bus *(sd_bus *bus)); MOCK_METHOD2(sd_bus_message_set_destination, int(sd_bus_message *m, const char *destination));