forked from Kistler-Group/sdbus-cpp
Compare commits
25 Commits
Author | SHA1 | Date | |
---|---|---|---|
10977c6137 | |||
1e455b8ef3 | |||
75709e31f1 | |||
245db893b8 | |||
477c5dd714 | |||
b25534013f | |||
68b5eac9e9 | |||
4310a3bd17 | |||
f41d9bc395 | |||
5121d46eed | |||
121ed1a975 | |||
cc495811f9 | |||
839bc13625 | |||
5fe0f503ca | |||
d50a15b2a2 | |||
3a76e9c120 | |||
304b69dd8b | |||
099bc857ad | |||
c139110112 | |||
e7155c5506 | |||
0f7de608ac | |||
c6d4d2710f | |||
0440dcb15b | |||
e30ce194ab | |||
8dea11bac6 |
@ -4,7 +4,7 @@
|
||||
|
||||
cmake_minimum_required(VERSION 3.6)
|
||||
|
||||
project(sdbus-c++ VERSION 0.7.2 LANGUAGES C CXX)
|
||||
project(sdbus-c++ VERSION 0.7.8 LANGUAGES C CXX)
|
||||
|
||||
include(GNUInstallDirs) # Installation directories for `install` command and pkgconfig file
|
||||
|
||||
@ -37,7 +37,6 @@ set(SDBUSCPP_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include/${SDBUSCPP_INCLUDE_
|
||||
|
||||
set(SDBUSCPP_CPP_SRCS
|
||||
${SDBUSCPP_SOURCE_DIR}/Connection.cpp
|
||||
${SDBUSCPP_SOURCE_DIR}/ConvenienceApiClasses.cpp
|
||||
${SDBUSCPP_SOURCE_DIR}/Error.cpp
|
||||
${SDBUSCPP_SOURCE_DIR}/Message.cpp
|
||||
${SDBUSCPP_SOURCE_DIR}/Object.cpp
|
||||
@ -98,7 +97,7 @@ option(BUILD_SHARED_LIBS "Build shared libraries (.so) instead of static ones (.
|
||||
|
||||
# Having an object target allows unit tests to reuse already built sources without re-building
|
||||
add_library(sdbus-c++-objlib OBJECT ${SDBUSCPP_SRCS})
|
||||
target_compile_definitions(sdbus-c++-objlib PRIVATE BUILDLIB=1)
|
||||
target_compile_definitions(sdbus-c++-objlib PRIVATE BUILD_LIB=1 LIBSYSTEMD_VERSION=${SYSTEMD_VERSION})
|
||||
target_include_directories(sdbus-c++-objlib PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>
|
||||
$<BUILD_INTERFACE:${SYSTEMD_INCLUDE_DIRS}>)
|
||||
|
24
ChangeLog
24
ChangeLog
@ -114,3 +114,27 @@ v0.7.1
|
||||
|
||||
v0.7.2
|
||||
- Rewrite UnixFd implementation from plain UnixFd struct to full-ownership-semantics UnixFd class
|
||||
|
||||
v0.7.3
|
||||
- Add ability to integrate with external event loops
|
||||
- Add getSenderName() method to Message
|
||||
- Skip GetMachineId integration test case when /etc/machine-id is not available
|
||||
|
||||
v0.7.4
|
||||
- Add support for custom timeout of D-Bus method calls
|
||||
- Add support for opening a connection to a remote system bus using ssh
|
||||
- Internal refactoring: Use tag dispatching to construct various types of Connection
|
||||
|
||||
v0.7.5
|
||||
- [[Breaking ABI change]] No more hiding from C++17: Move API code containing C++17 uncaught_exceptions calls from within library to public API
|
||||
- Add a method to retrieve the unique name of a connection
|
||||
|
||||
v0.7.6
|
||||
- Fixes of clang-8 errors and warnings
|
||||
|
||||
v0.7.7
|
||||
- Fix race condition of polling on D-Bus fd from two threads (event loop thread and sync D-Bus call thread)
|
||||
- Little ordering fix in stress tests
|
||||
|
||||
v0.7.8
|
||||
- Switch from thread_local to global bus instance that is used to create Variant instances (thread_local caused issues with Variant in very special inter-thread situations)
|
||||
|
@ -50,6 +50,8 @@ set(SYSTEMD_INCLUDE_DIRS ${SOURCE_DIR}/src)
|
||||
ExternalProject_Get_property(LibsystemdBuildProject BINARY_DIR)
|
||||
set(SYSTEMD_LIBRARY_DIRS ${BINARY_DIR})
|
||||
|
||||
set(SYSTEMD_VERSION ${LIBSYSTEMD_VERSION})
|
||||
|
||||
add_library(Systemd::Libsystemd STATIC IMPORTED)
|
||||
set_target_properties(Systemd::Libsystemd PROPERTIES IMPORTED_LOCATION ${SYSTEMD_LIBRARY_DIRS}/libsystemd.a)
|
||||
set(SYSTEMD_LIBRARIES Systemd::Libsystemd ${CAP_LIBRARIES} ${GLIBC_RT_LIBRARY} ${MOUNT_LIBRARIES})
|
||||
|
@ -89,23 +89,17 @@ You may additionally set the `LIBSYSTEMD_VERSION` configuration flag to fine-tun
|
||||
Distributing sdbus-c++
|
||||
----------------------
|
||||
|
||||
sdbus-c++ recipes for Yocto are available. Contributors willing to help with bringing sdbus-c++ to popular package systems are welcome.
|
||||
|
||||
### Yocto
|
||||
|
||||
There are sdbus-c++ recipes for already released Yocto versions (for versions Sumo and newer) available in the meta-oe layer of meta-openembedded fork at Kistler-Group:
|
||||
There are Yocto recipes for sdbus-c++ available in the [`meta-oe`](https://github.com/openembedded/meta-openembedded/tree/master/meta-oe/recipes-core/sdbus-c%2B%2B) layer of the `meta-openembedded` project. There are two recipes:
|
||||
|
||||
* [sdbus-c++ recipes for Yocto Sumo](https://github.com/Kistler-Group/meta-openembedded/tree/sumo-with-sdbus-c%2B%2B/meta-oe/recipes-core/sdbus-c%2B%2B)
|
||||
* [sdbus-c++ recipes for Yocto Thud](https://github.com/Kistler-Group/meta-openembedded/tree/thud-with-sdbus-c%2B%2B/meta-oe/recipes-core/sdbus-c%2B%2B)
|
||||
* [sdbus-c++ recipes for Yocto Warrior](https://github.com/Kistler-Group/meta-openembedded/tree/warrior-with-sdbus-c%2B%2B/meta-oe/recipes-core/sdbus-c%2B%2B)
|
||||
|
||||
Also, there is currently a pull request pending that pushes there recipes upstream -- to the OpenEmbedded project, so they will be officially available for the upcoming Yocto release.
|
||||
|
||||
There are two recipes:
|
||||
|
||||
* One for sdbus-c++ itself. It detects whether systemd feature is turned on in the poky linux configuration. If so, it simply depends on systemd and makes use of libsystemd shared library available in the target system. Otherwise it automatically downloads and builds libsystemd static library and makes it an opaque part of sdbus-c++ shared library. The recipe also supports ptest.
|
||||
* One for sdbus-c++ itself. It detects whether systemd feature is turned on in the poky linux configuration. If so, it simply depends on systemd and makes use of libsystemd shared library available in the target system. Otherwise it automatically downloads and builds libsystemd static library and links it into the sdbus-c++ shared library. The recipe also supports ptest.
|
||||
* One for sdbus-c++ native tools, namely sdbus-c++ code generator to generate C++ adaptor and proxy binding classes.
|
||||
|
||||
Tip: If you get `ERROR: Program or command 'getent' not found or not executable` when building sdbus-c++ in Yocto, please make sure you've added `getent` to `HOSTTOOLS`. For example, you can add `HOSTTOOLS_NONFATAL += "getent"` into your local.conf file.
|
||||
|
||||
Contributors willing to help with bringing sdbus-c++ to other popular package systems are welcome.
|
||||
|
||||
Header files and namespaces
|
||||
---------------------------
|
||||
|
||||
@ -155,8 +149,7 @@ The following diagram illustrates the major entities in sdbus-c++.
|
||||
|
||||
`Message` class represents a message, which is the fundamental DBus concept. There are three distinctive types of message that are derived from the `Message` class:
|
||||
|
||||
* `MethodCall` (with serialized parameters),
|
||||
* `AsyncMethodCall` (with serialized parameters),
|
||||
* `MethodCall` (be it synchronous or asynchronous method call, with serialized parameters),
|
||||
* `MethodReply` (with serialized return values),
|
||||
* `Signal` (with serialized parameters),
|
||||
* `PropertySetCall` (with serialized parameter value to be set)
|
||||
@ -165,13 +158,17 @@ The following diagram illustrates the major entities in sdbus-c++.
|
||||
|
||||
### Thread safety in sdbus-c++
|
||||
|
||||
sdbus-c++ is thread-aware by design. But, in general, it's not thread-safe. At least not in all places. There are situations where sdbus-c++ provides and guarantees API-level thread safety by design. It is safe to do these operations from multiple threads at the same time:
|
||||
sdbus-c++ is completely thread-aware by design. Though sdbus-c++ is not thread-safe in general, there are situations where sdbus-c++ provides and guarantees API-level thread safety by design. It is safe to do these operations (operations within the bullet points, not across them) from multiple threads at the same time:
|
||||
|
||||
* Making and destroying `Object`s and `Proxy`s, even on a shared connection that is already running an event loop. Under *making* here is meant a complete atomic sequence of construction, registration of method/signal/property callbacks and export of the `Object`/`Proxy` so it is ready to issue/receive messages. This sequence must be done in one thread.
|
||||
* Making or destroying distinct `Object`/`Proxy` instances simultaneously (even on a shared connection that is running an event loop already, see below). Under *making* here is meant a complete sequence of construction, registration of method/signal/property callbacks and export of the `Object`/`Proxy` so it is ready to issue/receive messages. This sequence must be completely done within the context of one thread.
|
||||
* Creating and sending asynchronous method replies on an `Object` instance.
|
||||
* Creating and emitting signals on an `Object` instance.
|
||||
* Creating and sending method calls (both synchronously and asynchronously) on an `Proxy` instance. (But it's generally better that our threads use their own exclusive instances of proxy, to minimize shared state and contention.)
|
||||
|
||||
sdbus-c++ is designed such that all the above operations are thread-safe also on a connection that is running an event loop (usually in a separate thread) at that time. It's an internal thread safety. For example, a signal arrives and is processed by sdbus-c++ even loop at an appropriate `Proxy` instance, while the user is going to destroy that instance in their application thread. The user cannot explicitly control these situations (or they could, but that would be very limiting and cubersome on the API level).
|
||||
|
||||
However, other combinations, that the user invokes explicitly from within more threads are NOT thread-safe in sdbus-c++ by design, and the user should make sure by their design that these cases never occur. For example, destroying an `Object` instance in one thread while emitting a signal on it in another thread is not thread-safe. In this specific case, the user should make sure in their application that all threads stop working with a specific instance before a thread proceeds with deleting that instance.
|
||||
|
||||
Multiple layers of sdbus-c++ API
|
||||
-------------------------------
|
||||
|
||||
|
@ -32,6 +32,8 @@
|
||||
#include <sdbus-c++/Flags.h>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
|
||||
// Forward declarations
|
||||
namespace sdbus {
|
||||
@ -48,10 +50,9 @@ 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);
|
||||
MethodRegistrator& onInterface(std::string interfaceName);
|
||||
template <typename _Function>
|
||||
std::enable_if_t<!is_async_method_v<_Function>, MethodRegistrator&> implementedAs(_Function&& callback);
|
||||
template <typename _Function>
|
||||
@ -76,7 +77,6 @@ namespace sdbus {
|
||||
public:
|
||||
SignalRegistrator(IObject& object, const std::string& signalName);
|
||||
SignalRegistrator(SignalRegistrator&& other) = default;
|
||||
SignalRegistrator& operator=(SignalRegistrator&& other) = default;
|
||||
~SignalRegistrator() noexcept(false);
|
||||
|
||||
SignalRegistrator& onInterface(std::string interfaceName);
|
||||
@ -97,10 +97,9 @@ namespace sdbus {
|
||||
public:
|
||||
PropertyRegistrator(IObject& object, const std::string& propertyName);
|
||||
PropertyRegistrator(PropertyRegistrator&& other) = default;
|
||||
PropertyRegistrator& operator=(PropertyRegistrator&& other) = default;
|
||||
~PropertyRegistrator() noexcept(false);
|
||||
|
||||
PropertyRegistrator& onInterface(const std::string& interfaceName);
|
||||
PropertyRegistrator& onInterface(std::string interfaceName);
|
||||
template <typename _Function> PropertyRegistrator& withGetter(_Function&& callback);
|
||||
template <typename _Function> PropertyRegistrator& withSetter(_Function&& callback);
|
||||
PropertyRegistrator& markAsDeprecated();
|
||||
@ -123,7 +122,6 @@ namespace sdbus {
|
||||
public:
|
||||
InterfaceFlagsSetter(IObject& object, const std::string& interfaceName);
|
||||
InterfaceFlagsSetter(InterfaceFlagsSetter&& other) = default;
|
||||
InterfaceFlagsSetter& operator=(InterfaceFlagsSetter&& other) = default;
|
||||
~InterfaceFlagsSetter() noexcept(false);
|
||||
|
||||
InterfaceFlagsSetter& markAsDeprecated();
|
||||
@ -143,7 +141,6 @@ namespace sdbus {
|
||||
public:
|
||||
SignalEmitter(IObject& object, const std::string& signalName);
|
||||
SignalEmitter(SignalEmitter&& other) = default;
|
||||
SignalEmitter& operator=(SignalEmitter&& other) = default;
|
||||
~SignalEmitter() noexcept(false);
|
||||
SignalEmitter& onInterface(const std::string& interfaceName);
|
||||
template <typename... _Args> void withArguments(_Args&&... args);
|
||||
@ -160,10 +157,12 @@ namespace sdbus {
|
||||
public:
|
||||
MethodInvoker(IProxy& proxy, const std::string& methodName);
|
||||
MethodInvoker(MethodInvoker&& other) = default;
|
||||
MethodInvoker& operator=(MethodInvoker&& other) = default;
|
||||
~MethodInvoker() noexcept(false);
|
||||
|
||||
MethodInvoker& onInterface(const std::string& interfaceName);
|
||||
MethodInvoker& withTimeout(uint64_t usec);
|
||||
template <typename _Rep, typename _Period>
|
||||
MethodInvoker& withTimeout(const std::chrono::duration<_Rep, _Period>& timeout);
|
||||
template <typename... _Args> MethodInvoker& withArguments(_Args&&... args);
|
||||
template <typename... _Args> void storeResultsTo(_Args&... args);
|
||||
|
||||
@ -172,6 +171,7 @@ namespace sdbus {
|
||||
private:
|
||||
IProxy& proxy_;
|
||||
const std::string& methodName_;
|
||||
uint64_t timeout_{};
|
||||
MethodCall method_;
|
||||
int exceptions_{}; // Number of active exceptions when MethodInvoker is constructed
|
||||
bool methodCalled_{};
|
||||
@ -182,25 +182,29 @@ namespace sdbus {
|
||||
public:
|
||||
AsyncMethodInvoker(IProxy& proxy, const std::string& methodName);
|
||||
AsyncMethodInvoker& onInterface(const std::string& interfaceName);
|
||||
AsyncMethodInvoker& withTimeout(uint64_t usec);
|
||||
template <typename _Rep, typename _Period>
|
||||
AsyncMethodInvoker& withTimeout(const std::chrono::duration<_Rep, _Period>& timeout);
|
||||
template <typename... _Args> AsyncMethodInvoker& withArguments(_Args&&... args);
|
||||
template <typename _Function> void uponReplyInvoke(_Function&& callback);
|
||||
|
||||
private:
|
||||
IProxy& proxy_;
|
||||
const std::string& methodName_;
|
||||
AsyncMethodCall method_;
|
||||
uint64_t timeout_{};
|
||||
MethodCall method_;
|
||||
};
|
||||
|
||||
class SignalSubscriber
|
||||
{
|
||||
public:
|
||||
SignalSubscriber(IProxy& proxy, const std::string& signalName);
|
||||
SignalSubscriber& onInterface(const std::string& interfaceName);
|
||||
SignalSubscriber& onInterface(std::string interfaceName);
|
||||
template <typename _Function> void call(_Function&& callback);
|
||||
|
||||
private:
|
||||
IProxy& proxy_;
|
||||
std::string signalName_;
|
||||
const std::string& signalName_;
|
||||
std::string interfaceName_;
|
||||
};
|
||||
|
||||
@ -212,14 +216,14 @@ namespace sdbus {
|
||||
|
||||
private:
|
||||
IProxy& proxy_;
|
||||
std::string propertyName_;
|
||||
const std::string& propertyName_;
|
||||
};
|
||||
|
||||
class PropertySetter
|
||||
{
|
||||
public:
|
||||
PropertySetter(IProxy& proxy, const std::string& propertyName);
|
||||
PropertySetter& onInterface(const std::string& interfaceName);
|
||||
PropertySetter& onInterface(std::string interfaceName);
|
||||
template <typename _Value> void toValue(const _Value& value);
|
||||
void toValue(const sdbus::Variant& value);
|
||||
|
||||
|
@ -36,16 +36,19 @@
|
||||
#include <sdbus-c++/Error.h>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
/*#include <exception>*/
|
||||
#include <exception>
|
||||
#include <cassert>
|
||||
|
||||
namespace sdbus {
|
||||
|
||||
// Moved into the library to isolate from C++17 dependency
|
||||
/*
|
||||
/*** ----------------- ***/
|
||||
/*** MethodRegistrator ***/
|
||||
/*** ----------------- ***/
|
||||
|
||||
inline MethodRegistrator::MethodRegistrator(IObject& object, const std::string& methodName)
|
||||
: object_(object)
|
||||
, methodName_(methodName)
|
||||
, exceptions_(std::uncaught_exceptions()) // Needs C++17
|
||||
, exceptions_(std::uncaught_exceptions())
|
||||
{
|
||||
}
|
||||
|
||||
@ -55,8 +58,8 @@ namespace sdbus {
|
||||
if (std::uncaught_exceptions() != exceptions_)
|
||||
return;
|
||||
|
||||
SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when registering a DBus method", EINVAL);
|
||||
SDBUS_THROW_ERROR_IF(!methodCallback_, "Method handler not specified when registering a DBus method", EINVAL);
|
||||
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
|
||||
assert(methodCallback_); // implementedAs() must be placed/called prior to this function
|
||||
|
||||
// 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
|
||||
@ -69,11 +72,10 @@ namespace sdbus {
|
||||
// to the exception thrown from here if the caller is a destructor itself.
|
||||
object_.registerMethod(interfaceName_, methodName_, inputSignature_, outputSignature_, std::move(methodCallback_), flags_);
|
||||
}
|
||||
*/
|
||||
|
||||
inline MethodRegistrator& MethodRegistrator::onInterface(const std::string& interfaceName)
|
||||
inline MethodRegistrator& MethodRegistrator::onInterface(std::string interfaceName)
|
||||
{
|
||||
interfaceName_ = interfaceName;
|
||||
interfaceName_ = std::move(interfaceName);
|
||||
|
||||
return *this;
|
||||
}
|
||||
@ -149,12 +151,13 @@ namespace sdbus {
|
||||
return *this;
|
||||
}
|
||||
|
||||
/*** ----------------- ***/
|
||||
/*** SignalRegistrator ***/
|
||||
/*** ----------------- ***/
|
||||
|
||||
// Moved into the library to isolate from C++17 dependency
|
||||
/*
|
||||
inline SignalRegistrator::SignalRegistrator(IObject& object, std::string signalName)
|
||||
inline SignalRegistrator::SignalRegistrator(IObject& object, const std::string& signalName)
|
||||
: object_(object)
|
||||
, signalName_(std::move(signalName))
|
||||
, signalName_(signalName)
|
||||
, exceptions_(std::uncaught_exceptions())
|
||||
{
|
||||
}
|
||||
@ -165,8 +168,7 @@ namespace sdbus {
|
||||
if (std::uncaught_exceptions() != exceptions_)
|
||||
return;
|
||||
|
||||
if (interfaceName_.empty())
|
||||
throw sdbus::Exception("DBus interface not specified when registering a DBus signal");
|
||||
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
|
||||
|
||||
// registerSignal() can throw. But as the SignalRegistrator shall always be used as an unnamed,
|
||||
// temporary object, i.e. not as a stack-allocated object, the double-exception situation
|
||||
@ -177,9 +179,8 @@ namespace sdbus {
|
||||
// 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_);
|
||||
}
|
||||
*/
|
||||
|
||||
inline SignalRegistrator& SignalRegistrator::onInterface(std::string interfaceName)
|
||||
{
|
||||
@ -203,12 +204,13 @@ namespace sdbus {
|
||||
return *this;
|
||||
}
|
||||
|
||||
/*** ------------------- ***/
|
||||
/*** PropertyRegistrator ***/
|
||||
/*** ------------------- ***/
|
||||
|
||||
// Moved into the library to isolate from C++17 dependency
|
||||
/*
|
||||
inline PropertyRegistrator::PropertyRegistrator(IObject& object, std::string propertyName)
|
||||
inline PropertyRegistrator::PropertyRegistrator(IObject& object, const std::string& propertyName)
|
||||
: object_(object)
|
||||
, propertyName_(std::move(propertyName))
|
||||
, propertyName_(propertyName)
|
||||
, exceptions_(std::uncaught_exceptions())
|
||||
{
|
||||
}
|
||||
@ -219,7 +221,7 @@ namespace sdbus {
|
||||
if (std::uncaught_exceptions() != exceptions_)
|
||||
return;
|
||||
|
||||
SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when registering a DBus property", EINVAL);
|
||||
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
|
||||
|
||||
// registerProperty() can throw. But as the PropertyRegistrator shall always be used as an unnamed,
|
||||
// temporary object, i.e. not as a stack-allocated object, the double-exception situation
|
||||
@ -230,17 +232,17 @@ namespace sdbus {
|
||||
// Therefore, we can allow registerProperty() 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_.registerProperty( std::move(interfaceName_)
|
||||
, std::move(propertyName_)
|
||||
, std::move(propertySignature_)
|
||||
object_.registerProperty( interfaceName_
|
||||
, propertyName_
|
||||
, propertySignature_
|
||||
, std::move(getter_)
|
||||
, std::move(setter_) );
|
||||
, std::move(setter_)
|
||||
, flags_ );
|
||||
}
|
||||
*/
|
||||
|
||||
inline PropertyRegistrator& PropertyRegistrator::onInterface(const std::string& interfaceName)
|
||||
inline PropertyRegistrator& PropertyRegistrator::onInterface(std::string interfaceName)
|
||||
{
|
||||
interfaceName_ = interfaceName;
|
||||
interfaceName_ = std::move(interfaceName);
|
||||
|
||||
return *this;
|
||||
}
|
||||
@ -309,9 +311,10 @@ namespace sdbus {
|
||||
return *this;
|
||||
}
|
||||
|
||||
/*** -------------------- ***/
|
||||
/*** InterfaceFlagsSetter ***/
|
||||
/*** -------------------- ***/
|
||||
|
||||
// Moved into the library to isolate from C++17 dependency
|
||||
/*
|
||||
inline InterfaceFlagsSetter::InterfaceFlagsSetter(IObject& object, const std::string& interfaceName)
|
||||
: object_(object)
|
||||
, interfaceName_(interfaceName)
|
||||
@ -325,8 +328,6 @@ namespace sdbus {
|
||||
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
|
||||
@ -336,10 +337,8 @@ namespace sdbus {
|
||||
// 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_) );
|
||||
object_.setInterfaceFlags(interfaceName_, std::move(flags_));
|
||||
}
|
||||
*/
|
||||
|
||||
inline InterfaceFlagsSetter& InterfaceFlagsSetter::markAsDeprecated()
|
||||
{
|
||||
@ -369,9 +368,10 @@ namespace sdbus {
|
||||
return *this;
|
||||
}
|
||||
|
||||
/*** ------------- ***/
|
||||
/*** SignalEmitter ***/
|
||||
/*** ------------- ***/
|
||||
|
||||
// Moved into the library to isolate from C++17 dependency
|
||||
/*
|
||||
inline SignalEmitter::SignalEmitter(IObject& object, const std::string& signalName)
|
||||
: object_(object)
|
||||
, signalName_(signalName)
|
||||
@ -385,9 +385,6 @@ namespace sdbus {
|
||||
if (std::uncaught_exceptions() != exceptions_)
|
||||
return;
|
||||
|
||||
if (!signal_.isValid())
|
||||
throw sdbus::Exception("DBus interface not specified when emitting a DBus signal");
|
||||
|
||||
// emitSignal() can throw. But as the SignalEmitter 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
|
||||
@ -399,7 +396,6 @@ namespace sdbus {
|
||||
// to the exception thrown from here if the caller is a destructor itself.
|
||||
object_.emitSignal(signal_);
|
||||
}
|
||||
*/
|
||||
|
||||
inline SignalEmitter& SignalEmitter::onInterface(const std::string& interfaceName)
|
||||
{
|
||||
@ -411,15 +407,16 @@ namespace sdbus {
|
||||
template <typename... _Args>
|
||||
inline void SignalEmitter::withArguments(_Args&&... args)
|
||||
{
|
||||
SDBUS_THROW_ERROR_IF(!signal_.isValid(), "DBus interface not specified when emitting a DBus signal", EINVAL);
|
||||
assert(signal_.isValid()); // onInterface() must be placed/called prior to withArguments()
|
||||
|
||||
detail::serialize_pack(signal_, std::forward<_Args>(args)...);
|
||||
}
|
||||
|
||||
/*** ------------- ***/
|
||||
/*** MethodInvoker ***/
|
||||
/*** ------------- ***/
|
||||
|
||||
// Moved into the library to isolate from C++17 dependency
|
||||
/*
|
||||
inline MethodInvoker::MethodInvoker(IProxy& proxyObject, const std::string& methodName)
|
||||
inline MethodInvoker::MethodInvoker(IProxy& proxy, const std::string& methodName)
|
||||
: proxy_(proxy)
|
||||
, methodName_(methodName)
|
||||
, exceptions_(std::uncaught_exceptions())
|
||||
@ -433,9 +430,6 @@ namespace sdbus {
|
||||
if (methodCalled_ || std::uncaught_exceptions() != exceptions_)
|
||||
return;
|
||||
|
||||
if (!method_.isValid())
|
||||
throw sdbus::Exception("DBus interface not specified when calling a DBus method");
|
||||
|
||||
// callMethod() can throw. But as the MethodInvoker 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
|
||||
@ -445,9 +439,8 @@ namespace sdbus {
|
||||
// Therefore, we can allow callMethod() 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.
|
||||
proxy_.callMethod(method_);
|
||||
proxy_.callMethod(method_, timeout_);
|
||||
}
|
||||
*/
|
||||
|
||||
inline MethodInvoker& MethodInvoker::onInterface(const std::string& interfaceName)
|
||||
{
|
||||
@ -456,10 +449,24 @@ namespace sdbus {
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline MethodInvoker& MethodInvoker::withTimeout(uint64_t usec)
|
||||
{
|
||||
timeout_ = usec;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename _Rep, typename _Period>
|
||||
inline MethodInvoker& MethodInvoker::withTimeout(const std::chrono::duration<_Rep, _Period>& timeout)
|
||||
{
|
||||
auto microsecs = std::chrono::duration_cast<std::chrono::microseconds>(timeout);
|
||||
return withTimeout(microsecs.count());
|
||||
}
|
||||
|
||||
template <typename... _Args>
|
||||
inline MethodInvoker& MethodInvoker::withArguments(_Args&&... args)
|
||||
{
|
||||
SDBUS_THROW_ERROR_IF(!method_.isValid(), "DBus interface not specified when calling a DBus method", EINVAL);
|
||||
assert(method_.isValid()); // onInterface() must be placed/called prior to this function
|
||||
|
||||
detail::serialize_pack(method_, std::forward<_Args>(args)...);
|
||||
|
||||
@ -469,9 +476,9 @@ namespace sdbus {
|
||||
template <typename... _Args>
|
||||
inline void MethodInvoker::storeResultsTo(_Args&... args)
|
||||
{
|
||||
SDBUS_THROW_ERROR_IF(!method_.isValid(), "DBus interface not specified when calling a DBus method", EINVAL);
|
||||
assert(method_.isValid()); // onInterface() must be placed/called prior to this function
|
||||
|
||||
auto reply = proxy_.callMethod(method_);
|
||||
auto reply = proxy_.callMethod(method_, timeout_);
|
||||
methodCalled_ = true;
|
||||
|
||||
detail::deserialize_pack(reply, args...);
|
||||
@ -479,11 +486,14 @@ namespace sdbus {
|
||||
|
||||
inline void MethodInvoker::dontExpectReply()
|
||||
{
|
||||
SDBUS_THROW_ERROR_IF(!method_.isValid(), "DBus interface not specified when calling a DBus method", EINVAL);
|
||||
assert(method_.isValid()); // onInterface() must be placed/called prior to this function
|
||||
|
||||
method_.dontExpectReply();
|
||||
}
|
||||
|
||||
/*** ------------------ ***/
|
||||
/*** AsyncMethodInvoker ***/
|
||||
/*** ------------------ ***/
|
||||
|
||||
inline AsyncMethodInvoker::AsyncMethodInvoker(IProxy& proxy, const std::string& methodName)
|
||||
: proxy_(proxy)
|
||||
@ -493,15 +503,29 @@ namespace sdbus {
|
||||
|
||||
inline AsyncMethodInvoker& AsyncMethodInvoker::onInterface(const std::string& interfaceName)
|
||||
{
|
||||
method_ = proxy_.createAsyncMethodCall(interfaceName, methodName_);
|
||||
method_ = proxy_.createMethodCall(interfaceName, methodName_);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline AsyncMethodInvoker& AsyncMethodInvoker::withTimeout(uint64_t usec)
|
||||
{
|
||||
timeout_ = usec;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename _Rep, typename _Period>
|
||||
inline AsyncMethodInvoker& AsyncMethodInvoker::withTimeout(const std::chrono::duration<_Rep, _Period>& timeout)
|
||||
{
|
||||
auto microsecs = std::chrono::duration_cast<std::chrono::microseconds>(timeout);
|
||||
return withTimeout(microsecs.count());
|
||||
}
|
||||
|
||||
template <typename... _Args>
|
||||
inline AsyncMethodInvoker& AsyncMethodInvoker::withArguments(_Args&&... args)
|
||||
{
|
||||
SDBUS_THROW_ERROR_IF(!method_.isValid(), "DBus interface not specified when calling a DBus method", EINVAL);
|
||||
assert(method_.isValid()); // onInterface() must be placed/called prior to this function
|
||||
|
||||
detail::serialize_pack(method_, std::forward<_Args>(args)...);
|
||||
|
||||
@ -511,9 +535,9 @@ namespace sdbus {
|
||||
template <typename _Function>
|
||||
void AsyncMethodInvoker::uponReplyInvoke(_Function&& callback)
|
||||
{
|
||||
SDBUS_THROW_ERROR_IF(!method_.isValid(), "DBus interface not specified when calling a DBus method", EINVAL);
|
||||
assert(method_.isValid()); // onInterface() must be placed/called prior to this function
|
||||
|
||||
proxy_.callMethod(method_, [callback = std::forward<_Function>(callback)](MethodReply& reply, const Error* error)
|
||||
auto asyncReplyHandler = [callback = std::forward<_Function>(callback)](MethodReply& reply, const Error* error)
|
||||
{
|
||||
// Create a tuple of callback input arguments' types, which will be used
|
||||
// as a storage for the argument values deserialized from the message.
|
||||
@ -525,9 +549,14 @@ namespace sdbus {
|
||||
|
||||
// Invoke callback with input arguments from the tuple.
|
||||
sdbus::apply(callback, error, args); // TODO: Use std::apply when switching to full C++17 support
|
||||
});
|
||||
};
|
||||
|
||||
proxy_.callMethod(method_, std::move(asyncReplyHandler), timeout_);
|
||||
}
|
||||
|
||||
/*** ---------------- ***/
|
||||
/*** SignalSubscriber ***/
|
||||
/*** ---------------- ***/
|
||||
|
||||
inline SignalSubscriber::SignalSubscriber(IProxy& proxy, const std::string& signalName)
|
||||
: proxy_(proxy)
|
||||
@ -535,9 +564,9 @@ namespace sdbus {
|
||||
{
|
||||
}
|
||||
|
||||
inline SignalSubscriber& SignalSubscriber::onInterface(const std::string& interfaceName)
|
||||
inline SignalSubscriber& SignalSubscriber::onInterface(std::string interfaceName)
|
||||
{
|
||||
interfaceName_ = interfaceName;
|
||||
interfaceName_ = std::move(interfaceName);
|
||||
|
||||
return *this;
|
||||
}
|
||||
@ -545,7 +574,7 @@ namespace sdbus {
|
||||
template <typename _Function>
|
||||
inline void SignalSubscriber::call(_Function&& callback)
|
||||
{
|
||||
SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when subscribing to a signal", EINVAL);
|
||||
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
|
||||
|
||||
proxy_.registerSignalHandler( interfaceName_
|
||||
, signalName_
|
||||
@ -563,6 +592,9 @@ namespace sdbus {
|
||||
});
|
||||
}
|
||||
|
||||
/*** -------------- ***/
|
||||
/*** PropertyGetter ***/
|
||||
/*** -------------- ***/
|
||||
|
||||
inline PropertyGetter::PropertyGetter(IProxy& proxy, const std::string& propertyName)
|
||||
: proxy_(proxy)
|
||||
@ -581,6 +613,9 @@ namespace sdbus {
|
||||
return var;
|
||||
}
|
||||
|
||||
/*** -------------- ***/
|
||||
/*** PropertySetter ***/
|
||||
/*** -------------- ***/
|
||||
|
||||
inline PropertySetter::PropertySetter(IProxy& proxy, const std::string& propertyName)
|
||||
: proxy_(proxy)
|
||||
@ -588,9 +623,9 @@ namespace sdbus {
|
||||
{
|
||||
}
|
||||
|
||||
inline PropertySetter& PropertySetter::onInterface(const std::string& interfaceName)
|
||||
inline PropertySetter& PropertySetter::onInterface(std::string interfaceName)
|
||||
{
|
||||
interfaceName_ = interfaceName;
|
||||
interfaceName_ = std::move(interfaceName);
|
||||
|
||||
return *this;
|
||||
}
|
||||
@ -603,7 +638,7 @@ namespace sdbus {
|
||||
|
||||
inline void PropertySetter::toValue(const sdbus::Variant& value)
|
||||
{
|
||||
SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when setting a property", EINVAL);
|
||||
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
|
||||
|
||||
proxy_
|
||||
.callMethod("Set")
|
||||
|
@ -27,7 +27,9 @@
|
||||
#ifndef SDBUS_CXX_ERROR_H_
|
||||
#define SDBUS_CXX_ERROR_H_
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace sdbus {
|
||||
|
||||
|
@ -27,9 +27,10 @@
|
||||
#ifndef SDBUS_CXX_ICONNECTION_H_
|
||||
#define SDBUS_CXX_ICONNECTION_H_
|
||||
|
||||
//#include <cstdint>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
|
||||
namespace sdbus {
|
||||
|
||||
@ -46,6 +47,13 @@ namespace sdbus {
|
||||
class IConnection
|
||||
{
|
||||
public:
|
||||
struct PollData
|
||||
{
|
||||
int fd;
|
||||
short int events;
|
||||
uint64_t timeout_usec;
|
||||
};
|
||||
|
||||
virtual ~IConnection() = default;
|
||||
|
||||
/*!
|
||||
@ -66,6 +74,13 @@ namespace sdbus {
|
||||
*/
|
||||
virtual void releaseName(const std::string& name) = 0;
|
||||
|
||||
/*!
|
||||
* @brief Retrieve the unique name of a connection. E.g. ":1.xx"
|
||||
*
|
||||
* @throws sdbus::Error in case of failure
|
||||
*/
|
||||
virtual std::string getUniqueName() const = 0;
|
||||
|
||||
/*!
|
||||
* @brief Enters the D-Bus processing loop
|
||||
*
|
||||
@ -103,8 +118,81 @@ namespace sdbus {
|
||||
* @throws sdbus::Error in case of failure
|
||||
*/
|
||||
virtual void addObjectManager(const std::string& objectPath) = 0;
|
||||
|
||||
/*!
|
||||
* @brief Returns parameters you can pass to poll
|
||||
*
|
||||
* To integrate sdbus with your app's own custom event handling system
|
||||
* (without the requirement of an extra thread), you can use this
|
||||
* method to query which file descriptors, poll events and timeouts you
|
||||
* should add to your app's poll call in your main event loop. If these
|
||||
* file descriptors signal, then you should call processPendingRequest
|
||||
* to process the event. This means that all of sdbus's callbacks will
|
||||
* arrive on your app's main event thread (opposed to on a thread created
|
||||
* by sdbus-c++). If you are unsure what this all means then use
|
||||
* enterProcessingLoop() or enterProcessingLoopAsync() instead.
|
||||
*
|
||||
* To integrate sdbus-c++ into a gtk app, pass the file descriptor returned
|
||||
* by this method to g_main_context_add_poll.
|
||||
*
|
||||
* @throws sdbus::Error in case of failure
|
||||
*/
|
||||
virtual PollData getProcessLoopPollData() const = 0;
|
||||
|
||||
/*!
|
||||
* @brief Process a pending request
|
||||
*
|
||||
* Processes a single dbus event. All of sdbus-c++'s callbacks will be called
|
||||
* from within this method. This method should ONLY be used in conjuction
|
||||
* with getProcessLoopPollData(). enterProcessingLoop() and
|
||||
* enterProcessingLoopAsync() will call this method for you, so there is no
|
||||
* need to call it when using these. If you are unsure what this all means then
|
||||
* don't use this method.
|
||||
*
|
||||
* @returns true if an event was processed
|
||||
* @throws sdbus::Error in case of failure
|
||||
*/
|
||||
virtual bool processPendingRequest() = 0;
|
||||
|
||||
/*!
|
||||
* @brief Sets general method call timeout
|
||||
*
|
||||
* @param[in] timeout Timeout value in microseconds
|
||||
*
|
||||
* General method call timeout is used for all method calls upon this connection.
|
||||
* Method call-specific timeout overrides this general setting.
|
||||
*
|
||||
* Supported by libsystemd>=v240.
|
||||
*
|
||||
* @throws sdbus::Error in case of failure
|
||||
*/
|
||||
virtual void setMethodCallTimeout(uint64_t timeout) = 0;
|
||||
|
||||
/*!
|
||||
* @copydoc IConnection::setMethodCallTimeout(uint64_t)
|
||||
*/
|
||||
template <typename _Rep, typename _Period>
|
||||
void setMethodCallTimeout(const std::chrono::duration<_Rep, _Period>& timeout);
|
||||
|
||||
/*!
|
||||
* @brief Gets general method call timeout
|
||||
*
|
||||
* @return Timeout value in microseconds
|
||||
*
|
||||
* Supported by libsystemd>=v240.
|
||||
*
|
||||
* @throws sdbus::Error in case of failure
|
||||
*/
|
||||
virtual uint64_t getMethodCallTimeout() const = 0;
|
||||
};
|
||||
|
||||
template <typename _Rep, typename _Period>
|
||||
inline void IConnection::setMethodCallTimeout(const std::chrono::duration<_Rep, _Period>& timeout)
|
||||
{
|
||||
auto microsecs = std::chrono::duration_cast<std::chrono::microseconds>(timeout);
|
||||
return setMethodCallTimeout(microsecs.count());
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Creates/opens D-Bus system connection
|
||||
*
|
||||
@ -162,6 +250,15 @@ namespace sdbus {
|
||||
*/
|
||||
std::unique_ptr<sdbus::IConnection> createSessionBusConnection(const std::string& name);
|
||||
|
||||
/*!
|
||||
* @brief Creates/opens D-Bus system connection on a remote host using ssh
|
||||
*
|
||||
* @param[in] host Name of the host to connect
|
||||
* @return Connection instance
|
||||
*
|
||||
* @throws sdbus::Error in case of failure
|
||||
*/
|
||||
std::unique_ptr<sdbus::IConnection> createRemoteSystemBusConnection(const std::string& host);
|
||||
}
|
||||
|
||||
#endif /* SDBUS_CXX_ICONNECTION_H_ */
|
||||
|
@ -387,6 +387,8 @@ namespace sdbus {
|
||||
SignalEmitter emitSignal(const std::string& signalName);
|
||||
};
|
||||
|
||||
// Out-of-line member definitions
|
||||
|
||||
inline MethodRegistrator IObject::registerMethod(const std::string& methodName)
|
||||
{
|
||||
return MethodRegistrator(*this, methodName);
|
||||
|
@ -31,11 +31,11 @@
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <chrono>
|
||||
|
||||
// Forward declarations
|
||||
namespace sdbus {
|
||||
class MethodCall;
|
||||
class AsyncMethodCall;
|
||||
class MethodReply;
|
||||
class IConnection;
|
||||
}
|
||||
@ -76,25 +76,11 @@ namespace sdbus {
|
||||
*/
|
||||
virtual MethodCall createMethodCall(const std::string& interfaceName, const std::string& methodName) = 0;
|
||||
|
||||
/*!
|
||||
* @brief Creates an asynchronous method call message
|
||||
*
|
||||
* @param[in] interfaceName Name of an interface that provides a given method
|
||||
* @param[in] methodName Name of the method
|
||||
* @return A method call message
|
||||
*
|
||||
* Serialize method arguments into the returned message and invoke the method by passing
|
||||
* the message with serialized arguments to the @c callMethod function.
|
||||
* Alternatively, use higher-level API @c callMethodAsync(const std::string& methodName) defined below.
|
||||
*
|
||||
* @throws sdbus::Error in case of failure
|
||||
*/
|
||||
virtual AsyncMethodCall createAsyncMethodCall(const std::string& interfaceName, const std::string& methodName) = 0;
|
||||
|
||||
/*!
|
||||
* @brief Calls method on the proxied D-Bus object
|
||||
*
|
||||
* @param[in] message Message representing a method call
|
||||
* @param[in] timeout Timeout for dbus call in microseconds
|
||||
* @return A method reply message
|
||||
*
|
||||
* Normally, the call is blocking, i.e. it waits for the remote method to finish with either
|
||||
@ -108,13 +94,20 @@ namespace sdbus {
|
||||
*
|
||||
* @throws sdbus::Error in case of failure
|
||||
*/
|
||||
virtual MethodReply callMethod(const MethodCall& message) = 0;
|
||||
virtual MethodReply callMethod(const MethodCall& message, uint64_t timeout = 0) = 0;
|
||||
|
||||
/*!
|
||||
* @copydoc IProxy::callMethod(const MethodCall&,uint64_t)
|
||||
*/
|
||||
template <typename _Rep, typename _Period>
|
||||
MethodReply callMethod(const MethodCall& message, const std::chrono::duration<_Rep, _Period>& timeout);
|
||||
|
||||
/*!
|
||||
* @brief Calls method on the proxied D-Bus object asynchronously
|
||||
*
|
||||
* @param[in] message Message representing an async method call
|
||||
* @param[in] asyncReplyCallback Handler for the async reply
|
||||
* @param[in] timeout Timeout for dbus call in microseconds
|
||||
*
|
||||
* The call is non-blocking. It doesn't wait for the reply. Once the reply arrives,
|
||||
* the provided async reply handler will get invoked from the context of the connection
|
||||
@ -124,7 +117,13 @@ namespace sdbus {
|
||||
*
|
||||
* @throws sdbus::Error in case of failure
|
||||
*/
|
||||
virtual void callMethod(const AsyncMethodCall& message, async_reply_handler asyncReplyCallback) = 0;
|
||||
virtual void callMethod(const MethodCall& message, async_reply_handler asyncReplyCallback, uint64_t timeout = 0) = 0;
|
||||
|
||||
/*!
|
||||
* @copydoc IProxy::callMethod(const MethodCall&,async_reply_handler,uint64_t)
|
||||
*/
|
||||
template <typename _Rep, typename _Period>
|
||||
void callMethod(const MethodCall& message, async_reply_handler asyncReplyCallback, const std::chrono::duration<_Rep, _Period>& timeout);
|
||||
|
||||
/*!
|
||||
* @brief Registers a handler for the desired signal emitted by the proxied D-Bus object
|
||||
@ -264,6 +263,22 @@ namespace sdbus {
|
||||
PropertySetter setProperty(const std::string& propertyName);
|
||||
};
|
||||
|
||||
// Out-of-line member definitions
|
||||
|
||||
template <typename _Rep, typename _Period>
|
||||
inline MethodReply IProxy::callMethod(const MethodCall& message, const std::chrono::duration<_Rep, _Period>& timeout)
|
||||
{
|
||||
auto microsecs = std::chrono::duration_cast<std::chrono::microseconds>(timeout);
|
||||
return callMethod(message, microsecs.count());
|
||||
}
|
||||
|
||||
template <typename _Rep, typename _Period>
|
||||
inline void IProxy::callMethod(const MethodCall& message, async_reply_handler asyncReplyCallback, const std::chrono::duration<_Rep, _Period>& timeout)
|
||||
{
|
||||
auto microsecs = std::chrono::duration_cast<std::chrono::microseconds>(timeout);
|
||||
callMethod(message, std::move(asyncReplyCallback), microsecs.count());
|
||||
}
|
||||
|
||||
inline MethodInvoker IProxy::callMethod(const std::string& methodName)
|
||||
{
|
||||
return MethodInvoker(*this, methodName);
|
||||
|
@ -133,6 +133,7 @@ namespace sdbus {
|
||||
|
||||
std::string getInterfaceName() const;
|
||||
std::string getMemberName() const;
|
||||
std::string getSender() const;
|
||||
void peekType(std::string& type, std::string& contents) const;
|
||||
bool isValid() const;
|
||||
bool isEmpty() const;
|
||||
@ -164,35 +165,36 @@ namespace sdbus {
|
||||
mutable bool ok_{true};
|
||||
};
|
||||
|
||||
struct dont_request_slot_t { explicit dont_request_slot_t() = default; };
|
||||
#ifdef __cpp_inline_variables
|
||||
inline constexpr dont_request_slot_t dont_request_slot{};
|
||||
#else
|
||||
constexpr dont_request_slot_t dont_request_slot{};
|
||||
#endif
|
||||
|
||||
class MethodCall : public Message
|
||||
{
|
||||
using Message::Message;
|
||||
friend Factory;
|
||||
|
||||
public:
|
||||
MethodCall() = default;
|
||||
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 AsyncMethodCall : public Message
|
||||
{
|
||||
using Message::Message;
|
||||
friend Factory;
|
||||
|
||||
public:
|
||||
using Slot = std::unique_ptr<void, std::function<void(void*)>>;
|
||||
|
||||
AsyncMethodCall() = default;
|
||||
explicit AsyncMethodCall(MethodCall&& call) noexcept;
|
||||
Slot send(void* callback, void* userData) const;
|
||||
MethodCall() = default;
|
||||
|
||||
MethodReply send(uint64_t timeout) const;
|
||||
void send(void* callback, void* userData, uint64_t timeout, dont_request_slot_t) const;
|
||||
[[nodiscard]] Slot send(void* callback, void* userData, uint64_t timeout) const;
|
||||
|
||||
MethodReply createReply() const;
|
||||
MethodReply createErrorReply(const sdbus::Error& error) const;
|
||||
|
||||
void dontExpectReply();
|
||||
bool doesntExpectReply() const;
|
||||
|
||||
private:
|
||||
MethodReply sendWithReply(uint64_t timeout = 0) const;
|
||||
MethodReply sendWithNoReply() const;
|
||||
};
|
||||
|
||||
class MethodReply : public Message
|
||||
|
@ -35,39 +35,33 @@
|
||||
#include <poll.h>
|
||||
#include <sys/eventfd.h>
|
||||
|
||||
namespace {
|
||||
|
||||
std::vector</*const */char*> to_strv(const std::vector<std::string>& strings)
|
||||
{
|
||||
std::vector</*const */char*> strv;
|
||||
for (auto& str : strings)
|
||||
strv.push_back(const_cast<char*>(str.c_str()));
|
||||
strv.push_back(nullptr);
|
||||
return strv;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace sdbus { namespace internal {
|
||||
|
||||
Connection::Connection(Connection::BusType type, std::unique_ptr<ISdBus>&& interface)
|
||||
Connection::Connection(std::unique_ptr<ISdBus>&& interface, const BusFactory& busFactory)
|
||||
: iface_(std::move(interface))
|
||||
, busType_(type)
|
||||
, bus_(openBus(busFactory))
|
||||
{
|
||||
assert(iface_ != nullptr);
|
||||
}
|
||||
|
||||
auto bus = openBus(busType_);
|
||||
bus_.reset(bus);
|
||||
Connection::Connection(std::unique_ptr<ISdBus>&& interface, system_bus_t)
|
||||
: Connection(std::move(interface), [this](sd_bus** bus){ return iface_->sd_bus_open_system(bus); })
|
||||
{
|
||||
}
|
||||
|
||||
finishHandshake(bus);
|
||||
Connection::Connection(std::unique_ptr<ISdBus>&& interface, session_bus_t)
|
||||
: Connection(std::move(interface), [this](sd_bus** bus){ return iface_->sd_bus_open_user(bus); })
|
||||
{
|
||||
}
|
||||
|
||||
loopExitFd_ = createProcessingLoopExitDescriptor();
|
||||
Connection::Connection(std::unique_ptr<ISdBus>&& interface, remote_system_bus_t, const std::string& host)
|
||||
: Connection(std::move(interface), [this, &host](sd_bus** bus){ return iface_->sd_bus_open_system_remote(bus, host.c_str()); })
|
||||
{
|
||||
}
|
||||
|
||||
Connection::~Connection()
|
||||
{
|
||||
leaveProcessingLoop();
|
||||
closeProcessingLoopExitDescriptor(loopExitFd_);
|
||||
}
|
||||
|
||||
void Connection::requestName(const std::string& name)
|
||||
@ -82,8 +76,20 @@ void Connection::releaseName(const std::string& name)
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to release bus name", -r);
|
||||
}
|
||||
|
||||
std::string Connection::getUniqueName() const
|
||||
{
|
||||
const char* unique = nullptr;
|
||||
auto r = iface_->sd_bus_get_unique_name(bus_.get(), &unique);
|
||||
SDBUS_THROW_ERROR_IF(r < 0 || unique == nullptr, "Failed to get unique bus name", -r);
|
||||
return unique;
|
||||
}
|
||||
|
||||
void Connection::enterProcessingLoop()
|
||||
{
|
||||
loopThreadId_ = std::this_thread::get_id();
|
||||
|
||||
std::lock_guard guard(loopMutex_);
|
||||
|
||||
while (true)
|
||||
{
|
||||
auto processed = processPendingRequest();
|
||||
@ -94,6 +100,8 @@ void Connection::enterProcessingLoop()
|
||||
if (!success)
|
||||
break; // Exit processing loop
|
||||
}
|
||||
|
||||
loopThreadId_ = std::thread::id{};
|
||||
}
|
||||
|
||||
void Connection::enterProcessingLoopAsync()
|
||||
@ -108,6 +116,15 @@ void Connection::leaveProcessingLoop()
|
||||
joinWithProcessingLoop();
|
||||
}
|
||||
|
||||
sdbus::IConnection::PollData Connection::getProcessLoopPollData() const
|
||||
{
|
||||
ISdBus::PollData pollData;
|
||||
auto r = iface_->sd_bus_get_poll_data(bus_.get(), &pollData);
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get bus poll data", -r);
|
||||
|
||||
return {pollData.fd, pollData.events, pollData.timeout_usec};
|
||||
}
|
||||
|
||||
const ISdBus& Connection::getSdBusInterface() const
|
||||
{
|
||||
return *iface_.get();
|
||||
@ -136,6 +153,24 @@ SlotPtr Connection::addObjectManager(const std::string& objectPath, void* /*dumm
|
||||
return {slot, [this](void *slot){ iface_->sd_bus_slot_unref((sd_bus_slot*)slot); }};
|
||||
}
|
||||
|
||||
void Connection::setMethodCallTimeout(uint64_t timeout)
|
||||
{
|
||||
auto r = iface_->sd_bus_set_method_call_timeout(bus_.get(), timeout);
|
||||
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to set method call timeout", -r);
|
||||
}
|
||||
|
||||
uint64_t Connection::getMethodCallTimeout() const
|
||||
{
|
||||
uint64_t timeout;
|
||||
|
||||
auto r = iface_->sd_bus_get_method_call_timeout(bus_.get(), &timeout);
|
||||
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get method call timeout", -r);
|
||||
|
||||
return timeout;
|
||||
}
|
||||
|
||||
SlotPtr Connection::addObjectVTable( const std::string& objectPath
|
||||
, const std::string& interfaceName
|
||||
, const sd_bus_vtable* vtable
|
||||
@ -155,6 +190,17 @@ SlotPtr Connection::addObjectVTable( const std::string& objectPath
|
||||
return {slot, [this](void *slot){ iface_->sd_bus_slot_unref((sd_bus_slot*)slot); }};
|
||||
}
|
||||
|
||||
PlainMessage Connection::createPlainMessage() const
|
||||
{
|
||||
sd_bus_message* sdbusMsg{};
|
||||
|
||||
auto r = iface_->sd_bus_message_new(bus_.get(), &sdbusMsg, _SD_BUS_MESSAGE_TYPE_INVALID);
|
||||
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to create a plain message", -r);
|
||||
|
||||
return Message::Factory::create<PlainMessage>(sdbusMsg, iface_.get(), adopt_message);
|
||||
}
|
||||
|
||||
MethodCall Connection::createMethodCall( const std::string& destination
|
||||
, const std::string& objectPath
|
||||
, const std::string& interfaceName
|
||||
@ -178,17 +224,17 @@ Signal Connection::createSignal( const std::string& objectPath
|
||||
, const std::string& interfaceName
|
||||
, const std::string& signalName ) const
|
||||
{
|
||||
sd_bus_message *sdbusSignal{};
|
||||
sd_bus_message *sdbusMsg{};
|
||||
|
||||
auto r = iface_->sd_bus_message_new_signal( bus_.get()
|
||||
, &sdbusSignal
|
||||
, &sdbusMsg
|
||||
, objectPath.c_str()
|
||||
, interfaceName.c_str()
|
||||
, signalName.c_str() );
|
||||
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to create signal", -r);
|
||||
|
||||
return Message::Factory::create<Signal>(sdbusSignal, iface_.get(), adopt_message);
|
||||
return Message::Factory::create<Signal>(sdbusMsg, iface_.get(), adopt_message);
|
||||
}
|
||||
|
||||
void Connection::emitPropertiesChangedSignal( const std::string& objectPath
|
||||
@ -259,21 +305,44 @@ SlotPtr Connection::registerSignalHandler( const std::string& objectPath
|
||||
return {slot, [this](void *slot){ iface_->sd_bus_slot_unref((sd_bus_slot*)slot); }};
|
||||
}
|
||||
|
||||
sd_bus* Connection::openBus(Connection::BusType type)
|
||||
MethodReply Connection::tryCallMethodSynchronously(const MethodCall& message, uint64_t timeout)
|
||||
{
|
||||
auto loopThreadId = loopThreadId_.load(std::memory_order_relaxed);
|
||||
|
||||
// Is the loop not yet on? => Go make synchronous call
|
||||
while (loopThreadId == std::thread::id{})
|
||||
{
|
||||
// Did the loop begin in the meantime? Or try_lock() failed spuriously?
|
||||
if (!loopMutex_.try_lock())
|
||||
{
|
||||
loopThreadId = loopThreadId_.load(std::memory_order_relaxed);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Synchronous D-Bus call
|
||||
std::lock_guard guard(loopMutex_, std::adopt_lock);
|
||||
return message.send(timeout);
|
||||
}
|
||||
|
||||
// Is the loop on and we are in the same thread? => Go for synchronous call
|
||||
if (loopThreadId == std::this_thread::get_id())
|
||||
{
|
||||
assert(!loopMutex_.try_lock());
|
||||
return message.send(timeout);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Connection::BusPtr Connection::openBus(const BusFactory& busFactory)
|
||||
{
|
||||
sd_bus* bus{};
|
||||
int r = 0;
|
||||
if (type == BusType::eSystem)
|
||||
r = iface_->sd_bus_open_system(&bus);
|
||||
else if (type == BusType::eSession)
|
||||
r = iface_->sd_bus_open_user(&bus);
|
||||
else
|
||||
assert(false);
|
||||
|
||||
int r = busFactory(&bus);
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to open bus", -r);
|
||||
assert(bus != nullptr);
|
||||
|
||||
return bus;
|
||||
BusPtr busPtr{bus, [this](sd_bus* bus){ return iface_->sd_bus_flush_close_unref(bus); }};
|
||||
finishHandshake(busPtr.get());
|
||||
return busPtr;
|
||||
}
|
||||
|
||||
void Connection::finishHandshake(sd_bus* bus)
|
||||
@ -289,33 +358,19 @@ void Connection::finishHandshake(sd_bus* bus)
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to flush bus on opening", -r);
|
||||
}
|
||||
|
||||
int Connection::createProcessingLoopExitDescriptor()
|
||||
{
|
||||
auto r = eventfd(0, EFD_SEMAPHORE | EFD_CLOEXEC | EFD_NONBLOCK);
|
||||
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to create event object", -errno);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
void Connection::closeProcessingLoopExitDescriptor(int fd)
|
||||
{
|
||||
close(fd);
|
||||
}
|
||||
|
||||
void Connection::notifyProcessingLoopToExit()
|
||||
{
|
||||
assert(loopExitFd_ >= 0);
|
||||
assert(loopExitFd_.fd >= 0);
|
||||
|
||||
uint64_t value = 1;
|
||||
auto r = write(loopExitFd_, &value, sizeof(value));
|
||||
auto r = write(loopExitFd_.fd, &value, sizeof(value));
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to notify processing loop", -errno);
|
||||
}
|
||||
|
||||
void Connection::clearExitNotification()
|
||||
{
|
||||
uint64_t value{};
|
||||
auto r = read(loopExitFd_, &value, sizeof(value));
|
||||
auto r = read(loopExitFd_.fd, &value, sizeof(value));
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to read from the event descriptor", -errno);
|
||||
}
|
||||
|
||||
@ -328,11 +383,9 @@ void Connection::joinWithProcessingLoop()
|
||||
bool Connection::processPendingRequest()
|
||||
{
|
||||
auto bus = bus_.get();
|
||||
|
||||
assert(bus != nullptr);
|
||||
|
||||
int r = iface_->sd_bus_process(bus, nullptr);
|
||||
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to process bus requests", -r);
|
||||
|
||||
return r > 0;
|
||||
@ -343,17 +396,14 @@ bool Connection::waitForNextRequest()
|
||||
auto bus = bus_.get();
|
||||
|
||||
assert(bus != nullptr);
|
||||
assert(loopExitFd_ != 0);
|
||||
assert(loopExitFd_.fd != 0);
|
||||
|
||||
ISdBus::PollData sdbusPollData;
|
||||
auto r = iface_->sd_bus_get_poll_data(bus, &sdbusPollData);
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get bus poll data", -r);
|
||||
|
||||
struct pollfd fds[] = {{sdbusPollData.fd, sdbusPollData.events, 0}, {loopExitFd_, POLLIN, 0}};
|
||||
auto sdbusPollData = getProcessLoopPollData();
|
||||
struct pollfd fds[] = {{sdbusPollData.fd, sdbusPollData.events, 0}, {loopExitFd_.fd, POLLIN, 0}};
|
||||
auto fdsCount = sizeof(fds)/sizeof(fds[0]);
|
||||
|
||||
auto timeout = sdbusPollData.timeout_usec == (uint64_t) -1 ? (uint64_t)-1 : (sdbusPollData.timeout_usec+999)/1000;
|
||||
r = poll(fds, fdsCount, timeout);
|
||||
auto r = poll(fds, fdsCount, timeout);
|
||||
|
||||
if (r < 0 && errno == EINTR)
|
||||
return true; // Try again
|
||||
@ -383,10 +433,41 @@ std::string Connection::composeSignalMatchFilter( const std::string& objectPath
|
||||
return filter;
|
||||
}
|
||||
|
||||
std::vector</*const */char*> Connection::to_strv(const std::vector<std::string>& strings)
|
||||
{
|
||||
std::vector</*const */char*> strv;
|
||||
for (auto& str : strings)
|
||||
strv.push_back(const_cast<char*>(str.c_str()));
|
||||
strv.push_back(nullptr);
|
||||
return strv;
|
||||
}
|
||||
|
||||
Connection::LoopExitEventFd::LoopExitEventFd()
|
||||
{
|
||||
fd = eventfd(0, EFD_SEMAPHORE | EFD_CLOEXEC | EFD_NONBLOCK);
|
||||
SDBUS_THROW_ERROR_IF(fd < 0, "Failed to create event object", -errno);
|
||||
}
|
||||
|
||||
Connection::LoopExitEventFd::~LoopExitEventFd()
|
||||
{
|
||||
assert(fd >= 0);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
}}
|
||||
|
||||
namespace sdbus {
|
||||
|
||||
namespace internal {
|
||||
std::unique_ptr<sdbus::internal::IConnection> createConnection()
|
||||
{
|
||||
auto connection = sdbus::createConnection();
|
||||
SCOPE_EXIT{ connection.release(); };
|
||||
auto connectionInternal = dynamic_cast<sdbus::internal::IConnection*>(connection.get());
|
||||
return std::unique_ptr<sdbus::internal::IConnection>(connectionInternal);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<sdbus::IConnection> createConnection()
|
||||
{
|
||||
return createSystemBusConnection();
|
||||
@ -401,8 +482,8 @@ std::unique_ptr<sdbus::IConnection> createSystemBusConnection()
|
||||
{
|
||||
auto interface = std::make_unique<sdbus::internal::SdBus>();
|
||||
assert(interface != nullptr);
|
||||
return std::make_unique<sdbus::internal::Connection>( sdbus::internal::Connection::BusType::eSystem
|
||||
, std::move(interface));
|
||||
constexpr sdbus::internal::Connection::system_bus_t system_bus;
|
||||
return std::make_unique<sdbus::internal::Connection>(std::move(interface), system_bus);
|
||||
}
|
||||
|
||||
std::unique_ptr<sdbus::IConnection> createSystemBusConnection(const std::string& name)
|
||||
@ -416,8 +497,8 @@ std::unique_ptr<sdbus::IConnection> createSessionBusConnection()
|
||||
{
|
||||
auto interface = std::make_unique<sdbus::internal::SdBus>();
|
||||
assert(interface != nullptr);
|
||||
return std::make_unique<sdbus::internal::Connection>( sdbus::internal::Connection::BusType::eSession
|
||||
, std::move(interface));
|
||||
constexpr sdbus::internal::Connection::session_bus_t session_bus;
|
||||
return std::make_unique<sdbus::internal::Connection>(std::move(interface), session_bus);
|
||||
}
|
||||
|
||||
std::unique_ptr<sdbus::IConnection> createSessionBusConnection(const std::string& name)
|
||||
@ -427,4 +508,12 @@ std::unique_ptr<sdbus::IConnection> createSessionBusConnection(const std::string
|
||||
return conn;
|
||||
}
|
||||
|
||||
std::unique_ptr<sdbus::IConnection> createRemoteSystemBusConnection(const std::string& host)
|
||||
{
|
||||
auto interface = std::make_unique<sdbus::internal::SdBus>();
|
||||
assert(interface != nullptr);
|
||||
constexpr sdbus::internal::Connection::remote_system_bus_t remote_system_bus;
|
||||
return std::make_unique<sdbus::internal::Connection>(std::move(interface), remote_system_bus, host);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -35,6 +35,10 @@
|
||||
#include <systemd/sd-bus.h>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
|
||||
namespace sdbus { namespace internal {
|
||||
|
||||
@ -43,24 +47,31 @@ namespace sdbus { namespace internal {
|
||||
, public sdbus::internal::IConnection // Internal, private interface
|
||||
{
|
||||
public:
|
||||
enum class BusType
|
||||
{
|
||||
eSystem,
|
||||
eSession
|
||||
};
|
||||
// Bus type tags
|
||||
struct system_bus_t{};
|
||||
struct session_bus_t{};
|
||||
struct remote_system_bus_t{};
|
||||
|
||||
Connection(BusType type, std::unique_ptr<ISdBus>&& interface);
|
||||
Connection(std::unique_ptr<ISdBus>&& interface, system_bus_t);
|
||||
Connection(std::unique_ptr<ISdBus>&& interface, session_bus_t);
|
||||
Connection(std::unique_ptr<ISdBus>&& interface, remote_system_bus_t, const std::string& host);
|
||||
~Connection() override;
|
||||
|
||||
void requestName(const std::string& name) override;
|
||||
void releaseName(const std::string& name) override;
|
||||
std::string getUniqueName() const override;
|
||||
void enterProcessingLoop() override;
|
||||
void enterProcessingLoopAsync() override;
|
||||
void leaveProcessingLoop() override;
|
||||
sdbus::IConnection::PollData getProcessLoopPollData() const override;
|
||||
bool processPendingRequest() override;
|
||||
|
||||
void addObjectManager(const std::string& objectPath) override;
|
||||
SlotPtr addObjectManager(const std::string& objectPath, void* /*dummy*/) override;
|
||||
|
||||
void setMethodCallTimeout(uint64_t timeout) override;
|
||||
uint64_t getMethodCallTimeout() const override;
|
||||
|
||||
const ISdBus& getSdBusInterface() const override;
|
||||
ISdBus& getSdBusInterface() override;
|
||||
|
||||
@ -69,11 +80,11 @@ namespace sdbus { namespace internal {
|
||||
, const sd_bus_vtable* vtable
|
||||
, void* userData ) override;
|
||||
|
||||
PlainMessage createPlainMessage() const override;
|
||||
MethodCall createMethodCall( const std::string& destination
|
||||
, const std::string& objectPath
|
||||
, const std::string& interfaceName
|
||||
, const std::string& methodName ) const override;
|
||||
|
||||
Signal createSignal( const std::string& objectPath
|
||||
, const std::string& interfaceName
|
||||
, const std::string& signalName ) const override;
|
||||
@ -94,12 +105,15 @@ namespace sdbus { namespace internal {
|
||||
, sd_bus_message_handler_t callback
|
||||
, void* userData ) override;
|
||||
|
||||
MethodReply tryCallMethodSynchronously(const MethodCall& message, uint64_t timeout) override;
|
||||
|
||||
private:
|
||||
sd_bus* openBus(Connection::BusType type);
|
||||
using BusFactory = std::function<int(sd_bus**)>;
|
||||
using BusPtr = std::unique_ptr<sd_bus, std::function<sd_bus*(sd_bus*)>>;
|
||||
Connection(std::unique_ptr<ISdBus>&& interface, const BusFactory& busFactory);
|
||||
|
||||
BusPtr openBus(const std::function<int(sd_bus**)>& busFactory);
|
||||
void finishHandshake(sd_bus* bus);
|
||||
static int createProcessingLoopExitDescriptor();
|
||||
static void closeProcessingLoopExitDescriptor(int fd);
|
||||
bool processPendingRequest();
|
||||
bool waitForNextRequest();
|
||||
static std::string composeSignalMatchFilter( const std::string& objectPath
|
||||
, const std::string& interfaceName
|
||||
@ -107,17 +121,22 @@ namespace sdbus { namespace internal {
|
||||
void notifyProcessingLoopToExit();
|
||||
void clearExitNotification();
|
||||
void joinWithProcessingLoop();
|
||||
static std::vector</*const */char*> to_strv(const std::vector<std::string>& strings);
|
||||
|
||||
struct LoopExitEventFd
|
||||
{
|
||||
LoopExitEventFd();
|
||||
~LoopExitEventFd();
|
||||
int fd;
|
||||
};
|
||||
|
||||
private:
|
||||
std::unique_ptr<ISdBus> iface_;
|
||||
std::unique_ptr<sd_bus, std::function<sd_bus*(sd_bus*)>> bus_ {nullptr, [this](sd_bus* bus)
|
||||
{
|
||||
return iface_->sd_bus_flush_close_unref(bus);
|
||||
}};
|
||||
BusType busType_;
|
||||
|
||||
BusPtr bus_;
|
||||
std::thread asyncLoopThread_;
|
||||
int loopExitFd_{-1};
|
||||
std::atomic<std::thread::id> loopThreadId_;
|
||||
std::mutex loopMutex_;
|
||||
LoopExitEventFd loopExitFd_;
|
||||
};
|
||||
|
||||
}}
|
||||
|
@ -1,209 +0,0 @@
|
||||
/**
|
||||
* (C) 2016 - 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
|
||||
* (C) 2016 - 2019 Stanislav Angelovic <angelovic.s@gmail.com>
|
||||
*
|
||||
* @file ConvenienceApiClasses.cpp
|
||||
*
|
||||
* Created on: Jan 19, 2017
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "sdbus-c++/ConvenienceApiClasses.h"
|
||||
#include "sdbus-c++/IObject.h"
|
||||
#include "sdbus-c++/IProxy.h"
|
||||
#include <string>
|
||||
#include <exception>
|
||||
|
||||
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);
|
||||
SDBUS_THROW_ERROR_IF(!methodCallback_, "Method handler 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.
|
||||
object_.registerMethod(interfaceName_, methodName_, inputSignature_, outputSignature_, std::move(methodCallback_), flags_);
|
||||
}
|
||||
|
||||
SignalRegistrator::SignalRegistrator(IObject& object, const std::string& signalName)
|
||||
: object_(object)
|
||||
, signalName_(signalName)
|
||||
, exceptions_(std::uncaught_exceptions()) // Needs C++17
|
||||
{
|
||||
}
|
||||
|
||||
SignalRegistrator::~SignalRegistrator() noexcept(false) // since C++11, destructors must
|
||||
{ // explicitly be allowed to throw
|
||||
// Don't register the signal if SignalRegistrator 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 signal", EINVAL);
|
||||
|
||||
// registerSignal() can throw. But as the SignalRegistrator 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 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_, flags_);
|
||||
}
|
||||
|
||||
|
||||
PropertyRegistrator::PropertyRegistrator(IObject& object, const std::string& propertyName)
|
||||
: object_(object)
|
||||
, propertyName_(propertyName)
|
||||
, exceptions_(std::uncaught_exceptions())
|
||||
{
|
||||
}
|
||||
|
||||
PropertyRegistrator::~PropertyRegistrator() noexcept(false) // since C++11, destructors must
|
||||
{ // explicitly be allowed to throw
|
||||
// Don't register the property if PropertyRegistrator 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 property", EINVAL);
|
||||
|
||||
// registerProperty() can throw. But as the PropertyRegistrator 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 registerProperty() 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_.registerProperty( std::move(interfaceName_)
|
||||
, std::move(propertyName_)
|
||||
, std::move(propertySignature_)
|
||||
, std::move(getter_)
|
||||
, 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_) );
|
||||
}
|
||||
|
||||
|
||||
SignalEmitter::SignalEmitter(IObject& object, const std::string& signalName)
|
||||
: object_(object)
|
||||
, signalName_(signalName)
|
||||
, exceptions_(std::uncaught_exceptions()) // Needs C++17
|
||||
{
|
||||
}
|
||||
|
||||
SignalEmitter::~SignalEmitter() noexcept(false) // since C++11, destructors must
|
||||
{ // explicitly be allowed to throw
|
||||
// Don't emit the signal if SignalEmitter threw an exception in one of its methods
|
||||
if (std::uncaught_exceptions() != exceptions_)
|
||||
return;
|
||||
|
||||
SDBUS_THROW_ERROR_IF(!signal_.isValid(), "DBus interface not specified when emitting a DBus signal", EINVAL);
|
||||
|
||||
// emitSignal() can throw. But as the SignalEmitter 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 emitSignal() 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_.emitSignal(signal_);
|
||||
}
|
||||
|
||||
|
||||
MethodInvoker::MethodInvoker(IProxy& proxy, const std::string& methodName)
|
||||
: proxy_(proxy)
|
||||
, methodName_(methodName)
|
||||
, exceptions_(std::uncaught_exceptions()) // Needs C++17
|
||||
{
|
||||
}
|
||||
|
||||
MethodInvoker::~MethodInvoker() noexcept(false) // since C++11, destructors must
|
||||
{ // explicitly be allowed to throw
|
||||
// Don't call the method if it has been called already or if MethodInvoker
|
||||
// threw an exception in one of its methods
|
||||
if (methodCalled_ || std::uncaught_exceptions() != exceptions_)
|
||||
return;
|
||||
|
||||
SDBUS_THROW_ERROR_IF(!method_.isValid(), "DBus interface not specified when calling a DBus method", EINVAL);
|
||||
|
||||
// callMethod() can throw. But as the MethodInvoker 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 callMethod() 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.
|
||||
proxy_.callMethod(method_);
|
||||
}
|
||||
|
||||
}
|
@ -36,9 +36,9 @@
|
||||
// Forward declaration
|
||||
namespace sdbus {
|
||||
class MethodCall;
|
||||
class AsyncMethodCall;
|
||||
class MethodReply;
|
||||
class Signal;
|
||||
class PlainMessage;
|
||||
namespace internal {
|
||||
class ISdBus;
|
||||
}
|
||||
@ -62,11 +62,11 @@ namespace internal {
|
||||
, const sd_bus_vtable* vtable
|
||||
, void* userData ) = 0;
|
||||
|
||||
virtual PlainMessage createPlainMessage() const = 0;
|
||||
virtual MethodCall createMethodCall( const std::string& destination
|
||||
, const std::string& objectPath
|
||||
, const std::string& interfaceName
|
||||
, const std::string& methodName ) const = 0;
|
||||
|
||||
virtual Signal createSignal( const std::string& objectPath
|
||||
, const std::string& interfaceName
|
||||
, const std::string& signalName ) const = 0;
|
||||
@ -91,8 +91,12 @@ namespace internal {
|
||||
|
||||
virtual void enterProcessingLoopAsync() = 0;
|
||||
virtual void leaveProcessingLoop() = 0;
|
||||
|
||||
virtual MethodReply tryCallMethodSynchronously(const MethodCall& message, uint64_t timeout) = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<sdbus::internal::IConnection> createConnection();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
10
src/ISdBus.h
10
src/ISdBus.h
@ -42,6 +42,8 @@ namespace sdbus { namespace internal {
|
||||
uint64_t timeout_usec;
|
||||
};
|
||||
|
||||
virtual ~ISdBus() = default;
|
||||
|
||||
virtual sd_bus_message* sd_bus_message_ref(sd_bus_message *m) = 0;
|
||||
virtual sd_bus_message* sd_bus_message_unref(sd_bus_message *m) = 0;
|
||||
|
||||
@ -49,11 +51,15 @@ namespace sdbus { namespace internal {
|
||||
virtual int sd_bus_call(sd_bus *bus, sd_bus_message *m, uint64_t usec, sd_bus_error *ret_error, sd_bus_message **reply) = 0;
|
||||
virtual int sd_bus_call_async(sd_bus *bus, sd_bus_slot **slot, sd_bus_message *m, sd_bus_message_handler_t callback, void *userdata, uint64_t usec) = 0;
|
||||
|
||||
virtual int sd_bus_message_new(sd_bus *bus, sd_bus_message **m, uint8_t type) = 0;
|
||||
virtual int sd_bus_message_new_method_call(sd_bus *bus, sd_bus_message **m, const char *destination, const char *path, const char *interface, const char *member) = 0;
|
||||
virtual int sd_bus_message_new_signal(sd_bus *bus, sd_bus_message **m, const char *path, const char *interface, const char *member) = 0;
|
||||
virtual int sd_bus_message_new_method_return(sd_bus_message *call, sd_bus_message **m) = 0;
|
||||
virtual int sd_bus_message_new_method_error(sd_bus_message *call, sd_bus_message **m, const sd_bus_error *e) = 0;
|
||||
|
||||
virtual int sd_bus_set_method_call_timeout(sd_bus *bus, uint64_t usec) = 0;
|
||||
virtual int sd_bus_get_method_call_timeout(sd_bus *bus, uint64_t *ret) = 0;
|
||||
|
||||
virtual int sd_bus_emit_properties_changed_strv(sd_bus *bus, const char *path, const char *interface, char **names) = 0;
|
||||
virtual int sd_bus_emit_object_added(sd_bus *bus, const char *path) = 0;
|
||||
virtual int sd_bus_emit_object_removed(sd_bus *bus, const char *path) = 0;
|
||||
@ -62,8 +68,10 @@ namespace sdbus { namespace internal {
|
||||
|
||||
virtual int sd_bus_open_user(sd_bus **ret) = 0;
|
||||
virtual int sd_bus_open_system(sd_bus **ret) = 0;
|
||||
virtual int sd_bus_open_system_remote(sd_bus **ret, const char* host) = 0;
|
||||
virtual int sd_bus_request_name(sd_bus *bus, const char *name, uint64_t flags) = 0;
|
||||
virtual int sd_bus_release_name(sd_bus *bus, const char *name) = 0;
|
||||
virtual int sd_bus_get_unique_name(sd_bus *bus, const char **name) = 0;
|
||||
virtual int sd_bus_add_object_vtable(sd_bus *bus, sd_bus_slot **slot, const char *path, const char *interface, const sd_bus_vtable *vtable, void *userdata) = 0;
|
||||
virtual int sd_bus_add_object_manager(sd_bus *bus, sd_bus_slot **slot, const char *path) = 0;
|
||||
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;
|
||||
@ -74,8 +82,6 @@ namespace sdbus { namespace internal {
|
||||
|
||||
virtual int sd_bus_flush(sd_bus *bus) = 0;
|
||||
virtual sd_bus *sd_bus_flush_close_unref(sd_bus *bus) = 0;
|
||||
|
||||
virtual ~ISdBus() = default;
|
||||
};
|
||||
|
||||
}}
|
||||
|
@ -28,7 +28,8 @@
|
||||
#include <sdbus-c++/Types.h>
|
||||
#include <sdbus-c++/Error.h>
|
||||
#include "MessageUtils.h"
|
||||
#include "SdBus.h"
|
||||
#include "ISdBus.h"
|
||||
#include "IConnection.h"
|
||||
#include "ScopeGuard.h"
|
||||
#include <systemd/sd-bus.h>
|
||||
#include <cassert>
|
||||
@ -590,6 +591,11 @@ std::string Message::getMemberName() const
|
||||
return sd_bus_message_get_member((sd_bus_message*)msg_);
|
||||
}
|
||||
|
||||
std::string Message::getSender() const
|
||||
{
|
||||
return sd_bus_message_get_sender((sd_bus_message*)msg_);
|
||||
}
|
||||
|
||||
void Message::peekType(std::string& type, std::string& contents) const
|
||||
{
|
||||
char typeSig;
|
||||
@ -623,21 +629,21 @@ bool MethodCall::doesntExpectReply() const
|
||||
return r == 0;
|
||||
}
|
||||
|
||||
MethodReply MethodCall::send() const
|
||||
MethodReply MethodCall::send(uint64_t timeout) const
|
||||
{
|
||||
if (!doesntExpectReply())
|
||||
return sendWithReply();
|
||||
return sendWithReply(timeout);
|
||||
else
|
||||
return sendWithNoReply();
|
||||
}
|
||||
|
||||
MethodReply MethodCall::sendWithReply() const
|
||||
MethodReply MethodCall::sendWithReply(uint64_t timeout) const
|
||||
{
|
||||
sd_bus_error sdbusError = SD_BUS_ERROR_NULL;
|
||||
SCOPE_EXIT{ sd_bus_error_free(&sdbusError); };
|
||||
|
||||
sd_bus_message* sdbusReply{};
|
||||
auto r = sdbus_->sd_bus_call(nullptr, (sd_bus_message*)msg_, 0, &sdbusError, &sdbusReply);
|
||||
auto r = sdbus_->sd_bus_call(nullptr, (sd_bus_message*)msg_, timeout, &sdbusError, &sdbusReply);
|
||||
|
||||
if (sd_bus_error_is_set(&sdbusError))
|
||||
throw sdbus::Error(sdbusError.name, sdbusError.message);
|
||||
@ -655,6 +661,22 @@ MethodReply MethodCall::sendWithNoReply() const
|
||||
return Factory::create<MethodReply>(); // No reply
|
||||
}
|
||||
|
||||
void MethodCall::send(void* callback, void* userData, uint64_t timeout, dont_request_slot_t) const
|
||||
{
|
||||
auto r = sdbus_->sd_bus_call_async(nullptr, nullptr, (sd_bus_message*)msg_, (sd_bus_message_handler_t)callback, userData, timeout);
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to call method", -r);
|
||||
}
|
||||
|
||||
MethodCall::Slot MethodCall::send(void* callback, void* userData, uint64_t timeout) const
|
||||
{
|
||||
sd_bus_slot* slot;
|
||||
|
||||
auto r = sdbus_->sd_bus_call_async(nullptr, &slot, (sd_bus_message*)msg_, (sd_bus_message_handler_t)callback, userData, timeout);
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to call method asynchronously", -r);
|
||||
|
||||
return Slot{slot, [sdbus_ = sdbus_](void *slot){ sdbus_->sd_bus_slot_unref((sd_bus_slot*)slot); }};
|
||||
}
|
||||
|
||||
MethodReply MethodCall::createReply() const
|
||||
{
|
||||
sd_bus_message* sdbusReply{};
|
||||
@ -677,21 +699,6 @@ MethodReply MethodCall::createErrorReply(const Error& error) const
|
||||
return Factory::create<MethodReply>(sdbusErrorReply, sdbus_, adopt_message);
|
||||
}
|
||||
|
||||
AsyncMethodCall::AsyncMethodCall(MethodCall&& call) noexcept
|
||||
: Message(std::move(call))
|
||||
{
|
||||
}
|
||||
|
||||
AsyncMethodCall::Slot AsyncMethodCall::send(void* callback, void* userData) const
|
||||
{
|
||||
sd_bus_slot* slot;
|
||||
|
||||
auto r = sdbus_->sd_bus_call_async(nullptr, &slot, (sd_bus_message*)msg_, (sd_bus_message_handler_t)callback, userData, 0);
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to call method asynchronously", -r);
|
||||
|
||||
return Slot{slot, [sdbus_ = sdbus_](void *slot){ sdbus_->sd_bus_slot_unref((sd_bus_slot*)slot); }};
|
||||
}
|
||||
|
||||
void MethodReply::send() const
|
||||
{
|
||||
auto r = sdbus_->sd_bus_send(nullptr, (sd_bus_message*)msg_, nullptr);
|
||||
@ -706,42 +713,8 @@ void Signal::send() const
|
||||
|
||||
PlainMessage createPlainMessage()
|
||||
{
|
||||
int r;
|
||||
|
||||
// All references to the bus (like created messages) must not outlive this thread (because messages refer to sdbus
|
||||
// which is thread-local, and because BusReferenceKeeper below destroys the bus at thread exit).
|
||||
// A more flexible solution would be that the caller would already provide an ISdBus reference as a parameter.
|
||||
// Variant is one of the callers. This means Variant could no more be created in a stand-alone way, but
|
||||
// through a factory of some existing facility (Object, Proxy, Connection...).
|
||||
// TODO: Consider this alternative of creating Variant, it may live next to the current one. This function would
|
||||
// get IConnection* parameter and IConnection would provide createPlainMessage factory (just like it already
|
||||
// provides e.g. createMethodCall). If this parameter were null, the current mechanism would be used.
|
||||
|
||||
thread_local internal::SdBus sdbus;
|
||||
|
||||
sd_bus* bus{};
|
||||
SCOPE_EXIT{ sd_bus_unref(bus); };
|
||||
r = sd_bus_default_system(&bus);
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get default system bus", -r);
|
||||
|
||||
thread_local struct BusReferenceKeeper
|
||||
{
|
||||
explicit BusReferenceKeeper(sd_bus* bus) : bus_(sd_bus_ref(bus)) { sd_bus_flush(bus_); }
|
||||
~BusReferenceKeeper() { sd_bus_flush_close_unref(bus_); }
|
||||
sd_bus* bus_{};
|
||||
} busReferenceKeeper{bus};
|
||||
|
||||
// Shelved here as handy thing for potential future tracing purposes:
|
||||
//#include <unistd.h>
|
||||
//#include <sys/syscall.h>
|
||||
//#define gettid() syscall(SYS_gettid)
|
||||
//printf("createPlainMessage: sd_bus*=[%p], n_ref=[%d], TID=[%d]\n", bus, *(unsigned*)bus, gettid());
|
||||
|
||||
sd_bus_message* sdbusMsg{};
|
||||
r = sd_bus_message_new(bus, &sdbusMsg, _SD_BUS_MESSAGE_TYPE_INVALID);
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to create a new message", -r);
|
||||
|
||||
return Message::Factory::create<PlainMessage>(sdbusMsg, &sdbus, adopt_message);
|
||||
static auto connection = internal::createConnection();
|
||||
return connection->createPlainMessage();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -125,6 +125,7 @@ void Object::finishRegistration()
|
||||
void Object::unregister()
|
||||
{
|
||||
interfaces_.clear();
|
||||
removeObjectManager();
|
||||
}
|
||||
|
||||
sdbus::Signal Object::createSignal(const std::string& interfaceName, const std::string& signalName)
|
||||
@ -134,6 +135,8 @@ sdbus::Signal Object::createSignal(const std::string& interfaceName, const std::
|
||||
|
||||
void Object::emitSignal(const sdbus::Signal& message)
|
||||
{
|
||||
SDBUS_THROW_ERROR_IF(!message.isValid(), "Invalid signal message provided", EINVAL);
|
||||
|
||||
message.send();
|
||||
}
|
||||
|
||||
|
@ -64,26 +64,90 @@ MethodCall Proxy::createMethodCall(const std::string& interfaceName, const std::
|
||||
return connection_->createMethodCall(destination_, objectPath_, interfaceName, methodName);
|
||||
}
|
||||
|
||||
AsyncMethodCall Proxy::createAsyncMethodCall(const std::string& interfaceName, const std::string& methodName)
|
||||
MethodReply Proxy::callMethod(const MethodCall& message, uint64_t timeout)
|
||||
{
|
||||
return AsyncMethodCall{Proxy::createMethodCall(interfaceName, methodName)};
|
||||
// Sending method call synchronously is the only operation that blocks, waiting for the method
|
||||
// reply message among the incoming message on the sd-bus connection socket. But typically there
|
||||
// already is somebody that generally handles incoming D-Bus messages -- the connection event loop
|
||||
// running typically in its own thread. We have to avoid polling on socket from several threads.
|
||||
// So we have to branch here: either we are within the context of the event loop thread, then we
|
||||
// can send the message simply via sd_bus_call, which blocks. Or we are in another thread, then
|
||||
// we can perform the send operation of the method call message from here (because that is thread-
|
||||
// safe like other sd-bus API accesses), but the incoming reply we have to get through the event
|
||||
// loop thread, because this is be the only rightful listener on the sd-bus connection socket.
|
||||
// So, technically, we use async means to wait here for reply received by the event loop thread.
|
||||
|
||||
SDBUS_THROW_ERROR_IF(!message.isValid(), "Invalid method call message provided", EINVAL);
|
||||
|
||||
// If we don't need to wait for any reply, we can send the message now irrespective of the context
|
||||
if (message.doesntExpectReply())
|
||||
return message.send(timeout);
|
||||
|
||||
// If we are in the context of event loop thread, we can send the D-Bus call synchronously
|
||||
// and wait blockingly for the reply, because we are the exclusive listeners on the socket
|
||||
auto reply = connection_->tryCallMethodSynchronously(message, timeout);
|
||||
if (reply.isValid())
|
||||
return reply;
|
||||
|
||||
// Otherwise we send the call asynchronously and do blocking wait for the reply from the event loop thread
|
||||
return sendMethodCallMessageAndWaitForReply(message, timeout);
|
||||
}
|
||||
|
||||
MethodReply Proxy::callMethod(const MethodCall& message)
|
||||
void Proxy::callMethod(const MethodCall& message, async_reply_handler asyncReplyCallback, uint64_t timeout)
|
||||
{
|
||||
return message.send();
|
||||
}
|
||||
SDBUS_THROW_ERROR_IF(!message.isValid(), "Invalid async method call message provided", EINVAL);
|
||||
|
||||
void Proxy::callMethod(const AsyncMethodCall& message, async_reply_handler asyncReplyCallback)
|
||||
{
|
||||
auto callback = (void*)&Proxy::sdbus_async_reply_handler;
|
||||
auto callData = std::make_unique<AsyncCalls::CallData>(AsyncCalls::CallData{*this, std::move(asyncReplyCallback), {}});
|
||||
|
||||
callData->slot = message.send(callback, callData.get());
|
||||
callData->slot = message.send(callback, callData.get(), timeout);
|
||||
|
||||
pendingAsyncCalls_.addCall(callData->slot.get(), std::move(callData));
|
||||
}
|
||||
|
||||
MethodReply Proxy::sendMethodCallMessageAndWaitForReply(const MethodCall& message, uint64_t timeout)
|
||||
{
|
||||
/*thread_local*/ SyncCallReplyData syncCallReplyData;
|
||||
|
||||
async_reply_handler asyncReplyCallback = [&syncCallReplyData](MethodReply& reply, const Error* error)
|
||||
{
|
||||
syncCallReplyData.sendMethodReplyToWaitingThread(reply, error);
|
||||
};
|
||||
auto callback = (void*)&Proxy::sdbus_async_reply_handler;
|
||||
AsyncCalls::CallData callData{*this, std::move(asyncReplyCallback), {}};
|
||||
|
||||
message.send(callback, &callData, timeout, dont_request_slot);
|
||||
|
||||
return syncCallReplyData.waitForMethodReply();
|
||||
}
|
||||
|
||||
void Proxy::SyncCallReplyData::sendMethodReplyToWaitingThread(MethodReply& reply, const Error* error)
|
||||
{
|
||||
SCOPE_EXIT{ cond_.notify_one(); };
|
||||
std::unique_lock<std::mutex> lock{mutex_};
|
||||
SCOPE_EXIT{ arrived_ = true; };
|
||||
|
||||
//error_ = nullptr; // Necessary if SyncCallReplyData instance is thread_local
|
||||
|
||||
if (error == nullptr)
|
||||
reply_ = std::move(reply);
|
||||
else
|
||||
error_ = std::make_unique<Error>(*error);
|
||||
}
|
||||
|
||||
MethodReply Proxy::SyncCallReplyData::waitForMethodReply()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock{mutex_};
|
||||
cond_.wait(lock, [this](){ return arrived_; });
|
||||
|
||||
//arrived_ = false; // Necessary if SyncCallReplyData instance is thread_local
|
||||
|
||||
if (error_)
|
||||
throw *error_;
|
||||
|
||||
return std::move(reply_);
|
||||
}
|
||||
|
||||
void Proxy::registerSignalHandler( const std::string& interfaceName
|
||||
, const std::string& signalName
|
||||
, signal_handler signalHandler )
|
||||
@ -118,7 +182,7 @@ void Proxy::registerSignalHandlers(sdbus::internal::IConnection& connection)
|
||||
slot = connection.registerSignalHandler( objectPath_
|
||||
, interfaceName
|
||||
, signalName
|
||||
, &Proxy::sdbus_signal_callback
|
||||
, &Proxy::sdbus_signal_handler
|
||||
, this );
|
||||
}
|
||||
}
|
||||
@ -137,7 +201,13 @@ int Proxy::sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userDat
|
||||
assert(asyncCallData->callback);
|
||||
auto& proxy = asyncCallData->proxy;
|
||||
|
||||
SCOPE_EXIT{ proxy.pendingAsyncCalls_.removeCall(asyncCallData->slot.get()); };
|
||||
SCOPE_EXIT
|
||||
{
|
||||
// Slot may be null if we're doing blocking synchronous call implemented by means of asynchronous call,
|
||||
// because in that case the call data is still alive on the stack, we don't need to manage it separately.
|
||||
if (asyncCallData->slot)
|
||||
proxy.pendingAsyncCalls_.removeCall(asyncCallData->slot.get());
|
||||
};
|
||||
|
||||
auto message = Message::Factory::create<MethodReply>(sdbusMessage, &proxy.connection_->getSdBusInterface());
|
||||
|
||||
@ -155,7 +225,7 @@ int Proxy::sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userDat
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Proxy::sdbus_signal_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error */*retError*/)
|
||||
int Proxy::sdbus_signal_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error */*retError*/)
|
||||
{
|
||||
auto* proxy = static_cast<Proxy*>(userData);
|
||||
assert(proxy != nullptr);
|
||||
|
25
src/Proxy.h
25
src/Proxy.h
@ -35,6 +35,7 @@
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
|
||||
namespace sdbus {
|
||||
namespace internal {
|
||||
@ -51,9 +52,8 @@ namespace internal {
|
||||
, std::string objectPath );
|
||||
|
||||
MethodCall createMethodCall(const std::string& interfaceName, const std::string& methodName) override;
|
||||
AsyncMethodCall createAsyncMethodCall(const std::string& interfaceName, const std::string& methodName) override;
|
||||
MethodReply callMethod(const MethodCall& message) override;
|
||||
void callMethod(const AsyncMethodCall& message, async_reply_handler asyncReplyCallback) override;
|
||||
MethodReply callMethod(const MethodCall& message, uint64_t timeout) override;
|
||||
void callMethod(const MethodCall& message, async_reply_handler asyncReplyCallback, uint64_t timeout) override;
|
||||
|
||||
void registerSignalHandler( const std::string& interfaceName
|
||||
, const std::string& signalName
|
||||
@ -62,9 +62,24 @@ namespace internal {
|
||||
void unregister() override;
|
||||
|
||||
private:
|
||||
class SyncCallReplyData
|
||||
{
|
||||
public:
|
||||
void sendMethodReplyToWaitingThread(MethodReply& reply, const Error* error);
|
||||
MethodReply waitForMethodReply();
|
||||
|
||||
private:
|
||||
std::mutex mutex_;
|
||||
std::condition_variable cond_;
|
||||
bool arrived_{};
|
||||
MethodReply reply_;
|
||||
std::unique_ptr<Error> error_;
|
||||
};
|
||||
|
||||
MethodReply sendMethodCallMessageAndWaitForReply(const MethodCall& message, uint64_t timeout);
|
||||
void registerSignalHandlers(sdbus::internal::IConnection& connection);
|
||||
static int sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError);
|
||||
static int sdbus_signal_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError);
|
||||
static int sdbus_signal_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError);
|
||||
|
||||
private:
|
||||
std::unique_ptr< sdbus::internal::IConnection
|
||||
@ -97,7 +112,7 @@ namespace internal {
|
||||
{
|
||||
Proxy& proxy;
|
||||
async_reply_handler callback;
|
||||
AsyncMethodCall::Slot slot;
|
||||
MethodCall::Slot slot;
|
||||
};
|
||||
|
||||
~AsyncCalls()
|
||||
|
@ -26,6 +26,7 @@
|
||||
*/
|
||||
|
||||
#include "SdBus.h"
|
||||
#include <sdbus-c++/Error.h>
|
||||
|
||||
namespace sdbus { namespace internal {
|
||||
|
||||
@ -64,6 +65,13 @@ int SdBus::sd_bus_call_async(sd_bus *bus, sd_bus_slot **slot, sd_bus_message *m,
|
||||
return ::sd_bus_call_async(bus, slot, m, callback, userdata, usec);
|
||||
}
|
||||
|
||||
int SdBus::sd_bus_message_new(sd_bus *bus, sd_bus_message **m, uint8_t type)
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
|
||||
|
||||
return ::sd_bus_message_new(bus, m, type);
|
||||
}
|
||||
|
||||
int SdBus::sd_bus_message_new_method_call(sd_bus *bus, sd_bus_message **m, const char *destination, const char *path, const char *interface, const char *member)
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
|
||||
@ -92,6 +100,32 @@ int SdBus::sd_bus_message_new_method_error(sd_bus_message *call, sd_bus_message
|
||||
return ::sd_bus_message_new_method_error(call, m, e);
|
||||
}
|
||||
|
||||
int SdBus::sd_bus_set_method_call_timeout(sd_bus *bus, uint64_t usec)
|
||||
{
|
||||
#if LIBSYSTEMD_VERSION>=240
|
||||
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
|
||||
|
||||
return ::sd_bus_set_method_call_timeout(bus, usec);
|
||||
#else
|
||||
(void)bus;
|
||||
(void)usec;
|
||||
throw sdbus::Error(SD_BUS_ERROR_NOT_SUPPORTED, "Setting general method call timeout not supported by underlying version of libsystemd");
|
||||
#endif
|
||||
}
|
||||
|
||||
int SdBus::sd_bus_get_method_call_timeout(sd_bus *bus, uint64_t *ret)
|
||||
{
|
||||
#if LIBSYSTEMD_VERSION>=240
|
||||
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
|
||||
|
||||
return ::sd_bus_get_method_call_timeout(bus, ret);
|
||||
#else
|
||||
(void)bus;
|
||||
(void)ret;
|
||||
throw sdbus::Error(SD_BUS_ERROR_NOT_SUPPORTED, "Getting general method call timeout not supported by underlying version of libsystemd");
|
||||
#endif
|
||||
}
|
||||
|
||||
int SdBus::sd_bus_emit_properties_changed_strv(sd_bus *bus, const char *path, const char *interface, char **names)
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
|
||||
@ -137,6 +171,11 @@ int SdBus::sd_bus_open_system(sd_bus **ret)
|
||||
return ::sd_bus_open_system(ret);
|
||||
}
|
||||
|
||||
int SdBus::sd_bus_open_system_remote(sd_bus **ret, const char *host)
|
||||
{
|
||||
return ::sd_bus_open_system_remote(ret, host);
|
||||
}
|
||||
|
||||
int SdBus::sd_bus_request_name(sd_bus *bus, const char *name, uint64_t flags)
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
|
||||
@ -151,6 +190,12 @@ int SdBus::sd_bus_release_name(sd_bus *bus, const char *name)
|
||||
return ::sd_bus_release_name(bus, name);
|
||||
}
|
||||
|
||||
int SdBus::sd_bus_get_unique_name(sd_bus *bus, const char **name)
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
|
||||
return ::sd_bus_get_unique_name(bus, name);
|
||||
}
|
||||
|
||||
int SdBus::sd_bus_add_object_vtable(sd_bus *bus, sd_bus_slot **slot, const char *path, const char *interface, const sd_bus_vtable *vtable, void *userdata)
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
|
||||
|
@ -43,11 +43,15 @@ public:
|
||||
virtual int sd_bus_call(sd_bus *bus, sd_bus_message *m, uint64_t usec, sd_bus_error *ret_error, sd_bus_message **reply) override;
|
||||
virtual int sd_bus_call_async(sd_bus *bus, sd_bus_slot **slot, sd_bus_message *m, sd_bus_message_handler_t callback, void *userdata, uint64_t usec) override;
|
||||
|
||||
virtual int sd_bus_message_new(sd_bus *bus, sd_bus_message **m, uint8_t type) override;
|
||||
virtual int sd_bus_message_new_method_call(sd_bus *bus, sd_bus_message **m, const char *destination, const char *path, const char *interface, const char *member) override;
|
||||
virtual int sd_bus_message_new_signal(sd_bus *bus, sd_bus_message **m, const char *path, const char *interface, const char *member) override;
|
||||
virtual int sd_bus_message_new_method_return(sd_bus_message *call, sd_bus_message **m) override;
|
||||
virtual int sd_bus_message_new_method_error(sd_bus_message *call, sd_bus_message **m, const sd_bus_error *e) override;
|
||||
|
||||
virtual int sd_bus_set_method_call_timeout(sd_bus *bus, uint64_t usec) override;
|
||||
virtual int sd_bus_get_method_call_timeout(sd_bus *bus, uint64_t *ret) override;
|
||||
|
||||
virtual int sd_bus_emit_properties_changed_strv(sd_bus *bus, const char *path, const char *interface, char **names) override;
|
||||
virtual int sd_bus_emit_object_added(sd_bus *bus, const char *path) override;
|
||||
virtual int sd_bus_emit_object_removed(sd_bus *bus, const char *path) override;
|
||||
@ -56,8 +60,10 @@ public:
|
||||
|
||||
virtual int sd_bus_open_user(sd_bus **ret) override;
|
||||
virtual int sd_bus_open_system(sd_bus **ret) override;
|
||||
virtual int sd_bus_open_system_remote(sd_bus **ret, const char* hsot) override;
|
||||
virtual int sd_bus_request_name(sd_bus *bus, const char *name, uint64_t flags) override;
|
||||
virtual int sd_bus_release_name(sd_bus *bus, const char *name) override;
|
||||
virtual int sd_bus_get_unique_name(sd_bus *bus, const char **name) override;
|
||||
virtual int sd_bus_add_object_vtable(sd_bus *bus, sd_bus_slot **slot, const char *path, const char *interface, const sd_bus_vtable *vtable, void *userdata) override;
|
||||
virtual int sd_bus_add_object_manager(sd_bus *bus, sd_bus_slot **slot, const char *path) override;
|
||||
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;
|
||||
|
@ -85,12 +85,14 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR})
|
||||
#----------------------------------
|
||||
|
||||
add_executable(sdbus-c++-unit-tests ${UNITTESTS_SRCS} $<TARGET_OBJECTS:sdbus-c++-objlib>)
|
||||
target_compile_definitions(sdbus-c++-unit-tests PRIVATE LIBSYSTEMD_VERSION=${SYSTEMD_VERSION})
|
||||
target_include_directories(sdbus-c++-unit-tests PRIVATE ${SYSTEMD_INCLUDE_DIRS}
|
||||
${CMAKE_SOURCE_DIR}/src
|
||||
${CMAKE_SOURCE_DIR}/include)
|
||||
target_link_libraries(sdbus-c++-unit-tests ${SYSTEMD_LIBRARIES} gmock gmock_main)
|
||||
|
||||
add_executable(sdbus-c++-integration-tests ${INTEGRATIONTESTS_SRCS})
|
||||
target_compile_definitions(sdbus-c++-integration-tests PRIVATE LIBSYSTEMD_VERSION=${SYSTEMD_VERSION})
|
||||
target_link_libraries(sdbus-c++-integration-tests sdbus-c++ gmock gmock_main)
|
||||
|
||||
# Manual performance and stress tests
|
||||
|
@ -43,9 +43,12 @@
|
||||
#include <fstream>
|
||||
#include <future>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
using ::testing::Eq;
|
||||
using ::testing::DoubleEq;
|
||||
using ::testing::Gt;
|
||||
using ::testing::AnyOf;
|
||||
using ::testing::ElementsAre;
|
||||
using ::testing::SizeIs;
|
||||
using namespace std::chrono_literals;
|
||||
@ -104,6 +107,7 @@ public:
|
||||
};
|
||||
|
||||
std::unique_ptr<sdbus::IConnection> AdaptorAndProxyFixture::s_connection = sdbus::createSystemBusConnection();
|
||||
|
||||
}
|
||||
|
||||
/*-------------------------------------*/
|
||||
@ -230,6 +234,60 @@ TEST_F(SdbusTestObject, CallsMultiplyMethodWithNoReplyFlag)
|
||||
ASSERT_THAT(m_adaptor->m_multiplyResult, Eq(INT64_VALUE * DOUBLE_VALUE));
|
||||
}
|
||||
|
||||
TEST_F(SdbusTestObject, CallsMethodWithCustomTimeoutSuccessfully)
|
||||
{
|
||||
auto res = m_proxy->doOperationWith500msTimeout(20); // The operation will take 20ms, but the timeout is 500ms, so we are fine
|
||||
ASSERT_THAT(res, Eq(20));
|
||||
}
|
||||
|
||||
TEST_F(SdbusTestObject, ThrowsTimeoutErrorWhenMethodTimesOut)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_proxy->doOperationWith500msTimeout(1000); // The operation will take 1s, but the timeout is 500ms, so we should time out
|
||||
FAIL() << "Expected sdbus::Error exception";
|
||||
}
|
||||
catch (const sdbus::Error& e)
|
||||
{
|
||||
ASSERT_THAT(e.getName(), AnyOf("org.freedesktop.DBus.Error.Timeout", "org.freedesktop.DBus.Error.NoReply"));
|
||||
ASSERT_THAT(e.getMessage(), AnyOf("Connection timed out", "Method call timed out"));
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
FAIL() << "Expected sdbus::Error exception";
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(SdbusTestObject, ThrowsTimeoutErrorWhenClientSideAsyncMethodTimesOut)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::promise<uint32_t> promise;
|
||||
auto future = promise.get_future();
|
||||
m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t res, const sdbus::Error* err)
|
||||
{
|
||||
if (err == nullptr)
|
||||
promise.set_value(res);
|
||||
else
|
||||
promise.set_exception(std::make_exception_ptr(*err));
|
||||
});
|
||||
|
||||
m_proxy->doOperationClientSideAsyncWith500msTimeout(1000); // The operation will take 1s, but the timeout is 500ms, so we should time out
|
||||
future.get(), Eq(100);
|
||||
|
||||
FAIL() << "Expected sdbus::Error exception";
|
||||
}
|
||||
catch (const sdbus::Error& e)
|
||||
{
|
||||
ASSERT_THAT(e.getName(), AnyOf("org.freedesktop.DBus.Error.Timeout", "org.freedesktop.DBus.Error.NoReply"));
|
||||
ASSERT_THAT(e.getMessage(), AnyOf("Connection timed out", "Method call timed out"));
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
FAIL() << "Expected sdbus::Error exception";
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(SdbusTestObject, CallsMethodThatThrowsError)
|
||||
{
|
||||
try
|
||||
@ -366,6 +424,28 @@ TEST_F(SdbusTestObject, FailsCallingMethodOnNonexistentObject)
|
||||
ASSERT_THROW(proxy.getInt(), sdbus::Error);
|
||||
}
|
||||
|
||||
TEST_F(SdbusTestObject, ReceivesTwoSignalsWhileMakingMethodCall)
|
||||
{
|
||||
m_proxy->emitTwoSimpleSignals();
|
||||
|
||||
ASSERT_TRUE(waitUntil(m_proxy->m_gotSimpleSignal));
|
||||
ASSERT_TRUE(waitUntil(m_proxy->m_gotSignalWithMap));
|
||||
}
|
||||
|
||||
#if LIBSYSTEMD_VERSION>=240
|
||||
TEST_F(SdbusTestObject, CanSetGeneralMethodTimeoutWithLibsystemdVersionGreaterThan239)
|
||||
{
|
||||
s_connection->setMethodCallTimeout(5000000);
|
||||
ASSERT_THAT(s_connection->getMethodCallTimeout(), Eq(5000000));
|
||||
}
|
||||
#else
|
||||
TEST_F(SdbusTestObject, CannotSetGeneralMethodTimeoutWithLibsystemdVersionLessThan240)
|
||||
{
|
||||
ASSERT_THROW(s_connection->setMethodCallTimeout(5000000), sdbus::Error);
|
||||
ASSERT_THROW(s_connection->getMethodCallTimeout(), sdbus::Error);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Signals
|
||||
|
||||
TEST_F(SdbusTestObject, EmitsSimpleSignalSuccesfully)
|
||||
@ -431,6 +511,13 @@ TEST_F(SdbusTestObject, PingsViaPeerInterface)
|
||||
|
||||
TEST_F(SdbusTestObject, AnswersMachineUuidViaPeerInterface)
|
||||
{
|
||||
// If /etc/machine-id does not exist in your system (which is very likely because you have
|
||||
// a non-systemd Linux), org.freedesktop.DBus.Peer.GetMachineId() will not work. To solve
|
||||
// this, you can create /etc/machine-id yourself as symlink to /var/lib/dbus/machine-id,
|
||||
// and then org.freedesktop.DBus.Peer.GetMachineId() will start to work.
|
||||
if (::access("/etc/machine-id", F_OK) == -1)
|
||||
GTEST_SKIP() << "/etc/machine-id file does not exist, GetMachineId() will not work";
|
||||
|
||||
ASSERT_NO_THROW(m_proxy->GetMachineId());
|
||||
}
|
||||
|
||||
|
@ -197,6 +197,13 @@ protected:
|
||||
throw sdbus::createError(1, "A test error occurred");
|
||||
}
|
||||
|
||||
|
||||
void emitTwoSimpleSignals() override
|
||||
{
|
||||
emitSimpleSignal();
|
||||
emitSignalWithMap({});
|
||||
}
|
||||
|
||||
std::string state()
|
||||
{
|
||||
return m_state;
|
||||
|
@ -108,6 +108,7 @@ protected:
|
||||
|
||||
//private:
|
||||
public: // for tests
|
||||
int m_SimpleSignals = 0;
|
||||
std::atomic<bool> m_gotSimpleSignal{false};
|
||||
std::atomic<bool> m_gotSignalWithMap{false};
|
||||
std::map<int32_t, std::string> m_mapFromSignal;
|
||||
@ -122,6 +123,4 @@ public: // for tests
|
||||
std::function<void(const sdbus::ObjectPath&, const std::vector<std::string>&)> m_onInterfacesRemovedHandler;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif /* SDBUS_CPP_INTEGRATIONTESTS_TESTINGPROXY_H_ */
|
||||
|
@ -106,6 +106,8 @@ protected:
|
||||
|
||||
object_.registerMethod("doPrivilegedStuff").onInterface(INTERFACE_NAME).implementedAs([](){}).markAsPrivileged();
|
||||
|
||||
object_.registerMethod("emitTwoSimpleSignals").onInterface(INTERFACE_NAME).implementedAs([this](){ this->emitTwoSimpleSignals(); });
|
||||
|
||||
// registration of signals is optional, it is useful because of introspection
|
||||
object_.registerSignal("simpleSignal").onInterface(INTERFACE_NAME).markAsDeprecated();
|
||||
object_.registerSignal("signalWithMap").onInterface(INTERFACE_NAME).withParameters<std::map<int32_t, std::string>>();
|
||||
@ -168,6 +170,7 @@ protected:
|
||||
virtual sdbus::UnixFd getUnixFd() const = 0;
|
||||
virtual ComplexType getComplex() const = 0;
|
||||
virtual void throwError() const = 0;
|
||||
virtual void emitTwoSimpleSignals() = 0;
|
||||
|
||||
virtual std::string state() = 0;
|
||||
virtual uint32_t action() = 0;
|
||||
@ -240,6 +243,8 @@ R"delimiter(<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspectio
|
||||
<method name="doPrivilegedStuff">
|
||||
<annotation name="org.freedesktop.systemd1.Privileged" value="true"/>
|
||||
</method>
|
||||
<method name="emitTwoSimpleSignals">
|
||||
</method>
|
||||
<method name="getComplex">
|
||||
<arg type="a{t(a{ya(obva{is})}gs)}" direction="out"/>
|
||||
<annotation name="org.freedesktop.DBus.Deprecated" value="true"/>
|
||||
|
@ -56,6 +56,11 @@ protected:
|
||||
virtual void onDoOperationReply(uint32_t returnValue, const sdbus::Error* error) = 0;
|
||||
|
||||
public:
|
||||
void emitTwoSimpleSignals()
|
||||
{
|
||||
object_.callMethod("emitTwoSimpleSignals").onInterface(INTERFACE_NAME);
|
||||
}
|
||||
|
||||
void noArgNoReturn()
|
||||
{
|
||||
object_.callMethod("noArgNoReturn").onInterface(INTERFACE_NAME);
|
||||
@ -136,6 +141,14 @@ public:
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t doOperationWith500msTimeout(uint32_t param)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
uint32_t result;
|
||||
object_.callMethod("doOperation").onInterface(INTERFACE_NAME).withTimeout(500000us).withArguments(param).storeResultsTo(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t doOperationAsync(uint32_t param)
|
||||
{
|
||||
uint32_t result;
|
||||
@ -164,6 +177,19 @@ public:
|
||||
});
|
||||
}
|
||||
|
||||
void doOperationClientSideAsyncWith500msTimeout(uint32_t param)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
object_.callMethodAsync("doOperation")
|
||||
.onInterface(INTERFACE_NAME)
|
||||
.withTimeout(500000us)
|
||||
.withArguments(param)
|
||||
.uponReplyInvoke([this](const sdbus::Error* error, uint32_t returnValue)
|
||||
{
|
||||
this->onDoOperationReply(returnValue, error);
|
||||
});
|
||||
}
|
||||
|
||||
sdbus::Signature getSignature()
|
||||
{
|
||||
sdbus::Signature result;
|
||||
|
@ -37,6 +37,8 @@
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
uint64_t totalDuration = 0;
|
||||
|
||||
class PerftestProxy : public sdbus::ProxyInterfaces<org::sdbuscpp::perftests_proxy>
|
||||
{
|
||||
public:
|
||||
@ -66,7 +68,9 @@ protected:
|
||||
else if (counter == m_msgCount)
|
||||
{
|
||||
auto stopTime = std::chrono::steady_clock::now();
|
||||
std::cout << "Received " << m_msgCount << " signals in: " << std::chrono::duration_cast<std::chrono::milliseconds>(stopTime - startTime).count() << " ms" << std::endl;
|
||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(stopTime - startTime).count();
|
||||
totalDuration += duration;
|
||||
std::cout << "Received " << m_msgCount << " signals in: " << duration << " ms" << std::endl;
|
||||
counter = 0;
|
||||
}
|
||||
}
|
||||
@ -114,6 +118,9 @@ int main(int /*argc*/, char */*argv*/[])
|
||||
std::this_thread::sleep_for(1000ms);
|
||||
}
|
||||
|
||||
std::cout << "AVERAGE: " << (totalDuration/repetitions) << " ms" << std::endl;
|
||||
totalDuration = 0;
|
||||
|
||||
msgSize = 1000;
|
||||
std::cout << std::endl << "** Measuring signals of size " << msgSize << " bytes (" << repetitions << " repetitions)..." << std::endl << std::endl;
|
||||
client.m_msgCount = msgCount; client.m_msgSize = msgSize;
|
||||
@ -124,6 +131,9 @@ int main(int /*argc*/, char */*argv*/[])
|
||||
std::this_thread::sleep_for(1000ms);
|
||||
}
|
||||
|
||||
std::cout << "AVERAGE: " << (totalDuration/repetitions) << " ms" << std::endl;
|
||||
totalDuration = 0;
|
||||
|
||||
msgSize = 20;
|
||||
std::cout << std::endl << "** Measuring method calls of size " << msgSize << " bytes (" << repetitions << " repetitions)..." << std::endl << std::endl;
|
||||
for (unsigned int r = 0; r < repetitions; ++r)
|
||||
@ -140,11 +150,16 @@ int main(int /*argc*/, char */*argv*/[])
|
||||
assert(result.size() == msgSize);
|
||||
}
|
||||
auto stopTime = std::chrono::steady_clock::now();
|
||||
std::cout << "Called " << msgCount << " methods in: " << std::chrono::duration_cast<std::chrono::milliseconds>(stopTime - startTime).count() << " ms" << std::endl;
|
||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(stopTime - startTime).count();
|
||||
totalDuration += duration;
|
||||
std::cout << "Called " << msgCount << " methods in: " << duration << " ms" << std::endl;
|
||||
|
||||
std::this_thread::sleep_for(1000ms);
|
||||
}
|
||||
|
||||
std::cout << "AVERAGE: " << (totalDuration/repetitions) << " ms" << std::endl;
|
||||
totalDuration = 0;
|
||||
|
||||
msgSize = 1000;
|
||||
std::cout << std::endl << "** Measuring method calls of size " << msgSize << " bytes (" << repetitions << " repetitions)..." << std::endl << std::endl;
|
||||
for (unsigned int r = 0; r < repetitions; ++r)
|
||||
@ -161,10 +176,15 @@ int main(int /*argc*/, char */*argv*/[])
|
||||
assert(result.size() == msgSize);
|
||||
}
|
||||
auto stopTime = std::chrono::steady_clock::now();
|
||||
std::cout << "Called " << msgCount << " methods in: " << std::chrono::duration_cast<std::chrono::milliseconds>(stopTime - startTime).count() << " ms" << std::endl;
|
||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(stopTime - startTime).count();
|
||||
totalDuration += duration;
|
||||
std::cout << "Called " << msgCount << " methods in: " << duration << " ms" << std::endl;
|
||||
|
||||
std::this_thread::sleep_for(1000ms);
|
||||
}
|
||||
|
||||
std::cout << "AVERAGE: " << (totalDuration/repetitions) << " ms" << std::endl;
|
||||
totalDuration = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -154,12 +154,12 @@ public:
|
||||
|
||||
~FahrenheitThermometerAdaptor()
|
||||
{
|
||||
unregisterAdaptor();
|
||||
|
||||
exit_ = true;
|
||||
cond_.notify_all();
|
||||
for (auto& worker : workers_)
|
||||
worker.join();
|
||||
|
||||
unregisterAdaptor();
|
||||
}
|
||||
|
||||
protected:
|
||||
@ -264,12 +264,12 @@ public:
|
||||
|
||||
~ConcatenatorAdaptor()
|
||||
{
|
||||
unregisterAdaptor();
|
||||
|
||||
exit_ = true;
|
||||
cond_.notify_all();
|
||||
for (auto& worker : workers_)
|
||||
worker.join();
|
||||
|
||||
unregisterAdaptor();
|
||||
}
|
||||
|
||||
protected:
|
||||
|
@ -35,17 +35,18 @@ using ::testing::DoAll;
|
||||
using ::testing::SetArgPointee;
|
||||
using ::testing::Return;
|
||||
using ::testing::NiceMock;
|
||||
|
||||
using BusType = sdbus::internal::Connection::BusType;
|
||||
|
||||
using sdbus::internal::Connection;
|
||||
constexpr sdbus::internal::Connection::system_bus_t system_bus;
|
||||
constexpr sdbus::internal::Connection::session_bus_t session_bus;
|
||||
constexpr sdbus::internal::Connection::remote_system_bus_t remote_system_bus;
|
||||
|
||||
class ConnectionCreationTest : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
ConnectionCreationTest() = default;
|
||||
|
||||
std::unique_ptr<NiceMock<SdBusMock>> mock_ { std::make_unique<NiceMock<SdBusMock>>() };
|
||||
sd_bus* STUB_ { reinterpret_cast<sd_bus*>(1) };
|
||||
std::unique_ptr<NiceMock<SdBusMock>> sdBusIntfMock_ = std::make_unique<NiceMock<SdBusMock>>();
|
||||
sd_bus* fakeBusPtr_ = reinterpret_cast<sd_bus*>(1);
|
||||
};
|
||||
|
||||
using ASystemBusConnection = ConnectionCreationTest;
|
||||
@ -53,99 +54,119 @@ using ASessionBusConnection = ConnectionCreationTest;
|
||||
|
||||
TEST_F(ASystemBusConnection, OpensAndFlushesBusWhenCreated)
|
||||
{
|
||||
EXPECT_CALL(*mock_, sd_bus_open_system(_)).WillOnce(DoAll(SetArgPointee<0>(STUB_), Return(1)));
|
||||
EXPECT_CALL(*mock_, sd_bus_flush(_)).Times(1);
|
||||
sdbus::internal::Connection(BusType::eSystem, std::move(mock_));
|
||||
EXPECT_CALL(*sdBusIntfMock_, sd_bus_open_system(_)).WillOnce(DoAll(SetArgPointee<0>(fakeBusPtr_), Return(1)));
|
||||
EXPECT_CALL(*sdBusIntfMock_, sd_bus_flush(_)).Times(1);
|
||||
Connection(std::move(sdBusIntfMock_), system_bus);
|
||||
}
|
||||
|
||||
TEST_F(ASessionBusConnection, OpensAndFlushesBusWhenCreated)
|
||||
{
|
||||
EXPECT_CALL(*mock_, sd_bus_open_user(_)).WillOnce(DoAll(SetArgPointee<0>(STUB_), Return(1)));
|
||||
EXPECT_CALL(*mock_, sd_bus_flush(_)).Times(1);
|
||||
sdbus::internal::Connection(BusType::eSession, std::move(mock_));
|
||||
EXPECT_CALL(*sdBusIntfMock_, sd_bus_open_user(_)).WillOnce(DoAll(SetArgPointee<0>(fakeBusPtr_), Return(1)));
|
||||
EXPECT_CALL(*sdBusIntfMock_, sd_bus_flush(_)).Times(1);
|
||||
Connection(std::move(sdBusIntfMock_), session_bus);
|
||||
}
|
||||
|
||||
TEST_F(ASystemBusConnection, ClosesAndUnrefsBusWhenDestructed)
|
||||
{
|
||||
ON_CALL(*mock_, sd_bus_open_user(_)).WillByDefault(DoAll(SetArgPointee<0>(STUB_), Return(1)));
|
||||
EXPECT_CALL(*mock_, sd_bus_flush_close_unref(_)).Times(1);
|
||||
sdbus::internal::Connection(BusType::eSession, std::move(mock_));
|
||||
ON_CALL(*sdBusIntfMock_, sd_bus_open_user(_)).WillByDefault(DoAll(SetArgPointee<0>(fakeBusPtr_), Return(1)));
|
||||
EXPECT_CALL(*sdBusIntfMock_, sd_bus_flush_close_unref(_)).Times(1);
|
||||
Connection(std::move(sdBusIntfMock_), session_bus);
|
||||
}
|
||||
|
||||
TEST_F(ASessionBusConnection, ClosesAndUnrefsBusWhenDestructed)
|
||||
{
|
||||
ON_CALL(*mock_, sd_bus_open_user(_)).WillByDefault(DoAll(SetArgPointee<0>(STUB_), Return(1)));
|
||||
EXPECT_CALL(*mock_, sd_bus_flush_close_unref(_)).Times(1);
|
||||
sdbus::internal::Connection(BusType::eSession, std::move(mock_));
|
||||
ON_CALL(*sdBusIntfMock_, sd_bus_open_user(_)).WillByDefault(DoAll(SetArgPointee<0>(fakeBusPtr_), Return(1)));
|
||||
EXPECT_CALL(*sdBusIntfMock_, sd_bus_flush_close_unref(_)).Times(1);
|
||||
Connection(std::move(sdBusIntfMock_), session_bus);
|
||||
}
|
||||
|
||||
TEST_F(ASystemBusConnection, ThrowsErrorWhenOpeningTheBusFailsDuringConstruction)
|
||||
{
|
||||
ON_CALL(*mock_, sd_bus_open_system(_)).WillByDefault(DoAll(SetArgPointee<0>(STUB_), Return(-1)));
|
||||
ASSERT_THROW(sdbus::internal::Connection(BusType::eSystem, std::move(mock_)), sdbus::Error);
|
||||
ON_CALL(*sdBusIntfMock_, sd_bus_open_system(_)).WillByDefault(DoAll(SetArgPointee<0>(fakeBusPtr_), Return(-1)));
|
||||
ASSERT_THROW(Connection(std::move(sdBusIntfMock_), system_bus), sdbus::Error);
|
||||
}
|
||||
|
||||
TEST_F(ASessionBusConnection, ThrowsErrorWhenOpeningTheBusFailsDuringConstruction)
|
||||
{
|
||||
ON_CALL(*mock_, sd_bus_open_user(_)).WillByDefault(DoAll(SetArgPointee<0>(STUB_), Return(-1)));
|
||||
ASSERT_THROW(sdbus::internal::Connection(BusType::eSession, std::move(mock_)), sdbus::Error);
|
||||
ON_CALL(*sdBusIntfMock_, sd_bus_open_user(_)).WillByDefault(DoAll(SetArgPointee<0>(fakeBusPtr_), Return(-1)));
|
||||
ASSERT_THROW(Connection(std::move(sdBusIntfMock_), session_bus), sdbus::Error);
|
||||
}
|
||||
|
||||
TEST_F(ASystemBusConnection, ThrowsErrorWhenFlushingTheBusFailsDuringConstruction)
|
||||
{
|
||||
ON_CALL(*mock_, sd_bus_open_system(_)).WillByDefault(DoAll(SetArgPointee<0>(STUB_), Return(1)));
|
||||
ON_CALL(*mock_, sd_bus_flush(_)).WillByDefault(Return(-1));
|
||||
ASSERT_THROW(sdbus::internal::Connection(BusType::eSystem, std::move(mock_)), sdbus::Error);
|
||||
ON_CALL(*sdBusIntfMock_, sd_bus_open_system(_)).WillByDefault(DoAll(SetArgPointee<0>(fakeBusPtr_), Return(1)));
|
||||
ON_CALL(*sdBusIntfMock_, sd_bus_flush(_)).WillByDefault(Return(-1));
|
||||
ASSERT_THROW(Connection(std::move(sdBusIntfMock_), system_bus), sdbus::Error);
|
||||
}
|
||||
|
||||
TEST_F(ASessionBusConnection, ThrowsErrorWhenFlushingTheBusFailsDuringConstruction)
|
||||
{
|
||||
ON_CALL(*mock_, sd_bus_open_user(_)).WillByDefault(DoAll(SetArgPointee<0>(STUB_), Return(1)));
|
||||
ON_CALL(*mock_, sd_bus_flush(_)).WillByDefault(Return(-1));
|
||||
ASSERT_THROW(sdbus::internal::Connection(BusType::eSession, std::move(mock_)), sdbus::Error);
|
||||
ON_CALL(*sdBusIntfMock_, sd_bus_open_user(_)).WillByDefault(DoAll(SetArgPointee<0>(fakeBusPtr_), Return(1)));
|
||||
ON_CALL(*sdBusIntfMock_, sd_bus_flush(_)).WillByDefault(Return(-1));
|
||||
ASSERT_THROW(Connection(std::move(sdBusIntfMock_), session_bus), sdbus::Error);
|
||||
}
|
||||
|
||||
class ConnectionRequestTest : public ::testing::TestWithParam<BusType>
|
||||
namespace
|
||||
{
|
||||
template <typename _BusTypeTag>
|
||||
class AConnectionNameRequest : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
ConnectionRequestTest() = default;
|
||||
void setUpBusOpenExpectation();
|
||||
std::unique_ptr<Connection> makeConnection();
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
switch (GetParam())
|
||||
{
|
||||
case BusType::eSystem:
|
||||
EXPECT_CALL(*mock_, sd_bus_open_system(_)).WillOnce(DoAll(SetArgPointee<0>(STUB_), Return(1)));
|
||||
break;
|
||||
case BusType::eSession:
|
||||
EXPECT_CALL(*mock_, sd_bus_open_user(_)).WillOnce(DoAll(SetArgPointee<0>(STUB_), Return(1)));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ON_CALL(*mock_, sd_bus_flush(_)).WillByDefault(Return(1));
|
||||
ON_CALL(*mock_, sd_bus_flush_close_unref(_)).WillByDefault(Return(STUB_));
|
||||
setUpBusOpenExpectation();
|
||||
ON_CALL(*sdBusIntfMock_, sd_bus_flush(_)).WillByDefault(Return(1));
|
||||
ON_CALL(*sdBusIntfMock_, sd_bus_flush_close_unref(_)).WillByDefault(Return(fakeBusPtr_));
|
||||
con_ = makeConnection();
|
||||
}
|
||||
|
||||
std::unique_ptr<NiceMock<SdBusMock>> mock_ { std::make_unique<NiceMock<SdBusMock>>() };
|
||||
sd_bus* STUB_ { reinterpret_cast<sd_bus*>(1) };
|
||||
NiceMock<SdBusMock>* sdBusIntfMock_ = new NiceMock<SdBusMock>(); // con_ below will assume ownership
|
||||
sd_bus* fakeBusPtr_ = reinterpret_cast<sd_bus*>(1);
|
||||
std::unique_ptr<Connection> con_;
|
||||
};
|
||||
|
||||
using AConnectionNameRequest = ConnectionRequestTest;
|
||||
|
||||
TEST_P(AConnectionNameRequest, DoesNotThrowOnSuccess)
|
||||
template<> void AConnectionNameRequest<Connection::system_bus_t>::setUpBusOpenExpectation()
|
||||
{
|
||||
EXPECT_CALL(*mock_, sd_bus_request_name(_, _, _)).WillOnce(Return(1));
|
||||
sdbus::internal::Connection(GetParam(), std::move(mock_)).requestName("");
|
||||
EXPECT_CALL(*sdBusIntfMock_, sd_bus_open_system(_)).WillOnce(DoAll(SetArgPointee<0>(fakeBusPtr_), Return(1)));
|
||||
}
|
||||
template<> void AConnectionNameRequest<Connection::session_bus_t>::setUpBusOpenExpectation()
|
||||
{
|
||||
EXPECT_CALL(*sdBusIntfMock_, sd_bus_open_user(_)).WillOnce(DoAll(SetArgPointee<0>(fakeBusPtr_), Return(1)));
|
||||
}
|
||||
template<> void AConnectionNameRequest<Connection::remote_system_bus_t>::setUpBusOpenExpectation()
|
||||
{
|
||||
EXPECT_CALL(*sdBusIntfMock_, sd_bus_open_system_remote(_, _)).WillOnce(DoAll(SetArgPointee<0>(fakeBusPtr_), Return(1)));
|
||||
}
|
||||
template <typename _BusTypeTag>
|
||||
std::unique_ptr<Connection> AConnectionNameRequest<_BusTypeTag>::makeConnection()
|
||||
{
|
||||
return std::make_unique<Connection>(std::unique_ptr<NiceMock<SdBusMock>>(sdBusIntfMock_), _BusTypeTag{});
|
||||
}
|
||||
template<> std::unique_ptr<Connection> AConnectionNameRequest<Connection::remote_system_bus_t>::makeConnection()
|
||||
{
|
||||
return std::make_unique<Connection>(std::unique_ptr<NiceMock<SdBusMock>>(sdBusIntfMock_), remote_system_bus, "some host");
|
||||
}
|
||||
|
||||
TEST_P(AConnectionNameRequest, ThrowsOnFail)
|
||||
{
|
||||
EXPECT_CALL(*mock_, sd_bus_request_name(_, _, _)).WillOnce(Return(-1));
|
||||
typedef ::testing::Types< Connection::system_bus_t
|
||||
, Connection::session_bus_t
|
||||
, Connection::remote_system_bus_t
|
||||
> BusTypeTags;
|
||||
|
||||
sdbus::internal::Connection conn_(GetParam(), std::move(mock_));
|
||||
ASSERT_THROW(conn_.requestName(""), sdbus::Error);
|
||||
TYPED_TEST_SUITE(AConnectionNameRequest, BusTypeTags);
|
||||
}
|
||||
|
||||
// INSTANTIATE_TEST_SUITE_P is defined in googletest master, but not in googletest v1.8.1 that we are using now
|
||||
INSTANTIATE_TEST_CASE_P(Request, AConnectionNameRequest, ::testing::Values(BusType::eSystem, BusType::eSession));
|
||||
//INSTANTIATE_TEST_SUITE_P(Request, AConnectionNameRequest, ::testing::Values(BusType::eSystem, BusType::eSession))
|
||||
TYPED_TEST(AConnectionNameRequest, DoesNotThrowOnSuccess)
|
||||
{
|
||||
EXPECT_CALL(*this->sdBusIntfMock_, sd_bus_request_name(_, _, _)).WillOnce(Return(1));
|
||||
this->con_->requestName("org.sdbuscpp.somename");
|
||||
}
|
||||
|
||||
TYPED_TEST(AConnectionNameRequest, ThrowsOnFail)
|
||||
{
|
||||
EXPECT_CALL(*this->sdBusIntfMock_, sd_bus_request_name(_, _, _)).WillOnce(Return(-1));
|
||||
|
||||
ASSERT_THROW(this->con_->requestName("org.sdbuscpp.somename"), sdbus::Error);
|
||||
}
|
||||
|
@ -121,7 +121,7 @@ namespace
|
||||
, ComplexType
|
||||
> DBusSupportedTypes;
|
||||
|
||||
TYPED_TEST_CASE(Type2DBusTypeSignatureConversion, DBusSupportedTypes);
|
||||
TYPED_TEST_SUITE(Type2DBusTypeSignatureConversion, DBusSupportedTypes);
|
||||
}
|
||||
|
||||
/*-------------------------------------*/
|
||||
|
@ -67,7 +67,7 @@ TEST(AVariant, CanBeConstructedFromASimpleValue)
|
||||
TEST(AVariant, CanBeConstructedFromAComplexValue)
|
||||
{
|
||||
using ComplexType = std::map<uint64_t, std::vector<sdbus::Struct<std::string, double>>>;
|
||||
ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{sdbus::make_struct("hello", ANY_DOUBLE), sdbus::make_struct("world", ANY_DOUBLE)}} };
|
||||
ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{sdbus::make_struct("hello"s, ANY_DOUBLE), sdbus::make_struct("world"s, ANY_DOUBLE)}} };
|
||||
|
||||
ASSERT_NO_THROW(sdbus::Variant(value));
|
||||
}
|
||||
@ -103,7 +103,7 @@ TEST(ASimpleVariant, ReturnsTheSimpleValueWhenAsked)
|
||||
TEST(AComplexVariant, ReturnsTheComplexValueWhenAsked)
|
||||
{
|
||||
using ComplexType = std::map<uint64_t, std::vector<sdbus::Struct<std::string, double>>>;
|
||||
ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{sdbus::make_struct("hello", ANY_DOUBLE), sdbus::make_struct("world", ANY_DOUBLE)}} };
|
||||
ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{sdbus::make_struct("hello"s, ANY_DOUBLE), sdbus::make_struct("world"s, ANY_DOUBLE)}} };
|
||||
|
||||
sdbus::Variant variant(value);
|
||||
|
||||
@ -123,7 +123,7 @@ TEST(AVariant, HasConceptuallyNonmutableGetMethodWhichCanBeCalledXTimes)
|
||||
TEST(AVariant, ReturnsTrueWhenAskedIfItContainsTheTypeItReallyContains)
|
||||
{
|
||||
using ComplexType = std::map<uint64_t, std::vector<sdbus::Struct<std::string, double>>>;
|
||||
ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{sdbus::make_struct("hello", ANY_DOUBLE), sdbus::make_struct("world", ANY_DOUBLE)}} };
|
||||
ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{sdbus::make_struct("hello"s, ANY_DOUBLE), sdbus::make_struct("world"s, ANY_DOUBLE)}} };
|
||||
|
||||
sdbus::Variant variant(value);
|
||||
|
||||
@ -172,7 +172,7 @@ TEST(AnEmptyVariant, ThrowsWhenBeingSerializedToAMessage)
|
||||
TEST(ANonEmptyVariant, SerializesToAndDeserializesFromAMessageSuccessfully)
|
||||
{
|
||||
using ComplexType = std::map<uint64_t, std::vector<sdbus::Struct<std::string, double>>>;
|
||||
ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{sdbus::make_struct("hello", ANY_DOUBLE), sdbus::make_struct("world", ANY_DOUBLE)}} };
|
||||
ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{sdbus::make_struct("hello"s, ANY_DOUBLE), sdbus::make_struct("world"s, ANY_DOUBLE)}} };
|
||||
sdbus::Variant variant(value);
|
||||
|
||||
auto msg = sdbus::createPlainMessage();
|
||||
@ -187,7 +187,7 @@ TEST(ANonEmptyVariant, SerializesToAndDeserializesFromAMessageSuccessfully)
|
||||
TEST(CopiesOfVariant, SerializeToAndDeserializeFromMessageSuccessfully)
|
||||
{
|
||||
using ComplexType = std::map<uint64_t, std::vector<sdbus::Struct<std::string, double>>>;
|
||||
ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{sdbus::make_struct("hello", ANY_DOUBLE), sdbus::make_struct("world", ANY_DOUBLE)}} };
|
||||
ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{sdbus::make_struct("hello"s, ANY_DOUBLE), sdbus::make_struct("world"s, ANY_DOUBLE)}} };
|
||||
sdbus::Variant variant(value);
|
||||
auto variantCopy1{variant};
|
||||
auto variantCopy2 = variant;
|
||||
|
@ -42,11 +42,15 @@ public:
|
||||
MOCK_METHOD5(sd_bus_call, int(sd_bus *bus, sd_bus_message *m, uint64_t usec, sd_bus_error *ret_error, sd_bus_message **reply));
|
||||
MOCK_METHOD6(sd_bus_call_async, int(sd_bus *bus, sd_bus_slot **slot, sd_bus_message *m, sd_bus_message_handler_t callback, void *userdata, uint64_t usec));
|
||||
|
||||
MOCK_METHOD3(sd_bus_message_new, int(sd_bus *bus, sd_bus_message **m, uint8_t type));
|
||||
MOCK_METHOD6(sd_bus_message_new_method_call, int(sd_bus *bus, sd_bus_message **m, const char *destination, const char *path, const char *interface, const char *member));
|
||||
MOCK_METHOD5(sd_bus_message_new_signal, int(sd_bus *bus, sd_bus_message **m, const char *path, const char *interface, const char *member));
|
||||
MOCK_METHOD2(sd_bus_message_new_method_return, int(sd_bus_message *call, sd_bus_message **m));
|
||||
MOCK_METHOD3(sd_bus_message_new_method_error, int(sd_bus_message *call, sd_bus_message **m, const sd_bus_error *e));
|
||||
|
||||
MOCK_METHOD2(sd_bus_set_method_call_timeout, int(sd_bus *bus, uint64_t usec));
|
||||
MOCK_METHOD2(sd_bus_get_method_call_timeout, int(sd_bus *bus, uint64_t *ret));
|
||||
|
||||
MOCK_METHOD4(sd_bus_emit_properties_changed_strv, int(sd_bus *bus, const char *path, const char *interface, char **names));
|
||||
MOCK_METHOD2(sd_bus_emit_object_added, int(sd_bus *bus, const char *path));
|
||||
MOCK_METHOD2(sd_bus_emit_object_removed, int(sd_bus *bus, const char *path));
|
||||
@ -55,8 +59,10 @@ public:
|
||||
|
||||
MOCK_METHOD1(sd_bus_open_user, int(sd_bus **ret));
|
||||
MOCK_METHOD1(sd_bus_open_system, int(sd_bus **ret));
|
||||
MOCK_METHOD2(sd_bus_open_system_remote, int(sd_bus **ret, const char *host));
|
||||
MOCK_METHOD3(sd_bus_request_name, int(sd_bus *bus, const char *name, uint64_t flags));
|
||||
MOCK_METHOD2(sd_bus_release_name, int(sd_bus *bus, const char *name));
|
||||
MOCK_METHOD2(sd_bus_get_unique_name, int(sd_bus *bus, const char **name));
|
||||
MOCK_METHOD6(sd_bus_add_object_vtable, int(sd_bus *bus, sd_bus_slot **slot, const char *path, const char *interface, const sd_bus_vtable *vtable, void *userdata));
|
||||
MOCK_METHOD3(sd_bus_add_object_manager, int(sd_bus *bus, sd_bus_slot **slot, const char *path));
|
||||
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));
|
||||
|
@ -193,7 +193,7 @@ std::tuple<std::string, std::string> AdaptorGenerator::processMethods(const Node
|
||||
if (annotationValue == "true")
|
||||
annotationRegistration += ".markAsPrivileged()";
|
||||
}
|
||||
else
|
||||
else if (annotationName != "org.freedesktop.DBus.Method.Timeout") // Whatever else...
|
||||
{
|
||||
std::cerr << "Node: " << methodName << ": "
|
||||
<< "Option '" << annotationName << "' not allowed or supported in this context! Option ignored..." << std::endl;
|
||||
|
@ -34,7 +34,6 @@
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
|
||||
|
||||
using std::endl;
|
||||
|
||||
using sdbuscpp::xml::Document;
|
||||
@ -142,6 +141,8 @@ std::tuple<std::string, std::string> ProxyGenerator::processMethods(const Nodes&
|
||||
|
||||
bool dontExpectReply{false};
|
||||
bool async{false};
|
||||
std::string timeoutValue;
|
||||
|
||||
Nodes annotations = (*method)["annotation"];
|
||||
for (const auto& annotation : annotations)
|
||||
{
|
||||
@ -150,6 +151,8 @@ std::tuple<std::string, std::string> ProxyGenerator::processMethods(const Nodes&
|
||||
else if (annotation->get("name") == "org.freedesktop.DBus.Method.Async"
|
||||
&& (annotation->get("value") == "client" || annotation->get("value") == "clientserver"))
|
||||
async = true;
|
||||
if (annotation->get("name") == "org.freedesktop.DBus.Method.Timeout")
|
||||
timeoutValue = annotation->get("value");
|
||||
}
|
||||
if (dontExpectReply && outArgs.size() > 0)
|
||||
{
|
||||
@ -157,6 +160,12 @@ std::tuple<std::string, std::string> ProxyGenerator::processMethods(const Nodes&
|
||||
std::cerr << "Option 'org.freedesktop.DBus.Method.NoReply' not allowed for methods with 'out' variables! Option ignored..." << std::endl;
|
||||
dontExpectReply = false;
|
||||
}
|
||||
if (!timeoutValue.empty() && dontExpectReply)
|
||||
{
|
||||
std::cerr << "Function: " << name << ": ";
|
||||
std::cerr << "Option 'org.freedesktop.DBus.Method.Timeout' not allowed for 'NoReply' methods! Option ignored..." << std::endl;
|
||||
timeoutValue.clear();
|
||||
}
|
||||
|
||||
auto retType = outArgsToType(outArgs);
|
||||
std::string inArgStr, inArgTypeStr;
|
||||
@ -167,6 +176,11 @@ std::tuple<std::string, std::string> ProxyGenerator::processMethods(const Nodes&
|
||||
definitionSS << tab << (async ? "void" : retType) << " " << name << "(" << inArgTypeStr << ")" << endl
|
||||
<< tab << "{" << endl;
|
||||
|
||||
if (!timeoutValue.empty())
|
||||
{
|
||||
definitionSS << tab << tab << "using namespace std::chrono_literals;" << endl;
|
||||
}
|
||||
|
||||
if (outArgs.size() > 0 && !async)
|
||||
{
|
||||
definitionSS << tab << tab << retType << " result;" << endl;
|
||||
@ -175,6 +189,11 @@ std::tuple<std::string, std::string> ProxyGenerator::processMethods(const Nodes&
|
||||
definitionSS << tab << tab << "proxy_.callMethod" << (async ? "Async" : "") << "(\"" << name << "\")"
|
||||
".onInterface(INTERFACE_NAME)";
|
||||
|
||||
if (!timeoutValue.empty())
|
||||
{
|
||||
definitionSS << ".withTimeout(" << timeoutValue << "us)";
|
||||
}
|
||||
|
||||
if (inArgs.size() > 0)
|
||||
{
|
||||
definitionSS << ".withArguments(" << inArgStr << ")";
|
||||
|
Reference in New Issue
Block a user