Compare commits

...

24 Commits

Author SHA1 Message Date
e75337bd23 test 2024-01-08 08:00:39 +01:00
28921ad424 fix: request name signal handling issue (#392)
In case a signal arrives during the `RequestName` or `ReleaseName` D-Bus call (which is a synchronous call), the signal may not be processed immediately, which is a bug. This is solved now by waking up the event loop.
2023-12-30 17:37:00 +01:00
721f583db1 refactor: handle exceptions correctly also in match callback handlers (#386)
This is a follow-up to #375. It covers match callback handlers that were missed in #375.
2023-12-05 19:06:41 +01:00
47a84ab889 feat: implement Overload pattern in utils (#385) 2023-12-05 18:16:59 +01:00
d80483cdc0 feat: extend ScopeGuard with success and failure overloads (#384) 2023-12-05 18:16:45 +01:00
934d51fa8a chore: add INSTALL_TESTS CMake option (#382)
* Change default test installation path to tests/sdbus-c++ , while also respecting CMAKE_INSTALL_PREFIX
* Introduce INSTALL_TESTS CMake option, so BUILD_TESTS is now split into BUILD_TESTS just for building and INSTALL_TESTS for installing the test binaries

Per discussion in #358 (comment), the change in the default settings is fine a for a minor release.
2023-11-23 21:05:44 +01:00
fb9e4ae371 fix: correctly add libsystemd dependency to pkgconfig (#378)
* fix: correctly add libsystemd dependency to pkgconfig

* refactor: solve sd-bus dependencies uniformly

---------

Co-authored-by: Stanislav Angelovič <stanislav.angelovic@protonmail.com>
2023-11-20 14:41:59 +01:00
6e348f3910 refactor: minor UnixFd cleanups (#376)
* chore: Use std::exchange in UnixFd

This was suggested in code review for #376 .

* fix: Protect against UnixFd self-assignment

While self-assignment is rare, it is expected to be safe.  Add a check
to prevent putting the object in an invalid state.

* fix: Improve hygiene around dup system call

- Don't try to call dup on a negative value.
- Check dup return code and throw if it fails, rather than returning an
  empty UnixFd object.

* chore: Move UnixFd::close to Types.cpp

Minor convenience for applications: unistd.h doesn't have to be included
in the public header.

---------

Co-authored-by: David Reiss <dreiss@meta.com>
2023-11-18 18:11:00 +01:00
f50e4676fe fix: add missing algorithm header include (#380)
* https://gcc.gnu.org/gcc-14/porting_to.html

Using gcc 14 uncovers a missing include in Message.h

Signed-off-by: Alfred Wingate <parona@protonmail.com>
2023-11-16 22:26:42 +01:00
1aa30e3a20 chore(ci): run freebsd job on ubuntu 2023-11-10 16:01:33 +01:00
e2b3e98374 refactor: improve handling of exceptions from callback handlers (#375)
* Catch and process all exceptions (not just sdbus::Error) from callback handlers
* Unify handling of exceptions from all types of callbacks -- always set sd_bus_error and return a negative result number in case of exception

Although libsystemd logs (with DEBUG severity) all errors from such callback handlers (except method callback handler), it seems to be out of our control. One of handy sdbus-c++ features could be the ability for clients to install a log callback, which sdbus-c++ would call in case of exceptions flying from callback handlers. In case something doesn't work for clients (especially novices), they can first look into these logs.

This may be handy in common situations like ignored signals on client side because of the inadvertent mismatch between real signal signature and signal handler signature. Like here: #373. (Although in this specific case of signals, there is a solution with an additional const sdbus::Error* argument that would reveal such an error.)
2023-11-03 18:03:01 +01:00
9490b3351f feat: add support for async registration of matches (#374) 2023-11-03 17:57:52 +01:00
9da18aec25 chore: remove obsolete TODO comments 2023-10-31 15:54:49 +01:00
b7b454ba38 doc: add section on bus connection factories 2023-10-16 19:28:27 +02:00
f420b216aa docs: update documentation 2023-10-16 15:30:23 +02:00
b482cd6d08 chore: version 1.4.0 2023-10-10 19:26:21 +02:00
aac7e590ea docs: add recommendation on destroying direct D-Bus connections 2023-10-10 19:15:21 +02:00
0ad2553417 fix: use-after-return in synchronous calls (#362)
* fix: Use-after-return in synchronous calls

This bug was introduced by c39bc637b8 and can be reproduced by
configuring with

  cmake -S . -B build -DBUILD_TESTS=yes -DCMAKE_CXX_COMPILER=clang++ \
    -DCMAKE_CXX_FLAGS="-Wno-error=deprecated-copy -fno-omit-frame-pointer -fsanitize=address -fsanitize-address-use-after-scope"

and running `cmake --buid build && cmake --build build -t test` or
`build/tests/sdbus-c++-integration-tests --gtest_filter=SdbusTestObject.HandlesCorrectlyABulkOfParallelServerSideAsyncMethods`

The issue is that `sdbus_async_reply_handler` can call `removeCall`, which
writes to `data->finished`, but `data` can point to the stack of
`sendMethodCallMessageAndWaitForReply`, which can return as soon as
`asyncCallData->callback` is called.

As a fix, I restored some of the logic removed in c39bc637b8.
Specifically, in `sdbus_async_reply_handler`, I make a copy of some data
from `asyncCallData` (a new `state` field instead of `slot`), and in the
`SCOPE_GUARD`, I don't call `removeCall` if the call was actually
synchronous.

* refactor: use enum class instead of int

---------

Co-authored-by: David Reiss <dreiss@meta.com>
Co-authored-by: Stanislav Angelovič <stanislav.angelovic@protonmail.com>
2023-10-09 20:01:45 +02:00
621b3d0862 feat: enable creation of IConnection from sd_bus object (#363) 2023-10-09 15:46:39 +02:00
189fd23744 fix(tests): fix ETIMEDOUT message on FreeBSD (#365)
[ RUN      ] SdbusTestObject/0.ThrowsTimeoutErrorWhenMethodTimesOut
tests/integrationtests/DBusMethodsTests.cpp:181: Failure
Value of: e.getMessage()
Expected: (is equal to "Connection timed out") or (is equal to "Method call timed out")
  Actual: "Operation timed out"

[  FAILED  ] SdbusTestObject/0.ThrowsTimeoutErrorWhenMethodTimesOut, where TypeParam = sdbus::test::SdBusCppLoop (3 ms)
2023-10-04 15:48:51 +02:00
cfb71bd6cf feat: add support for direct connections (#350)
* feat: add support for direct connections

* refactor: simplify a bit, change comments, extend tests

* fix: compiler warning about unused variable

* docs: add section on direct connections to the tutorial

---------

Co-authored-by: Maksim Fedyarov <m.fedyarov@omp.ru>
Co-authored-by: Stanislav Angelovič <stanislav.angelovic@protonmail.com>
2023-09-25 20:12:34 +02:00
c437b4d508 fix: honor CMAKE_POSITION_INDEPENDENT_CODE when building (#361) 2023-09-25 08:53:24 +02:00
1e2d13a04a feat: add FreeBSD support (#358)
* chore: don't use systemd headers with elogind

In file included from src/VTableUtils.c:27:
src/VTableUtils.h:30:10: fatal error: 'systemd/sd-bus.h' file not found
 #include <systemd/sd-bus.h>
          ^~~~~~~~~~~~~~~~~~

* chore: add basu support

Similar to elogind but also supported on non-Linux.

* chore(tests): permit /var/lib/machine-id on non-systemd

https://github.com/elogind/elogind/commit/84fdc0fc61c1
https://git.sr.ht/~emersion/basu/commit/8324e6729231

* chore(ci): add simple freebsd job

Mainly to cover libc++ and basu.

* chore(ci): explicitly pass CMAKE_INSTALL_PREFIX

Some sdbus-cpp tests require configuring system bus. However, Linux
testing relies on writing outside of prefix in order to affect current
system bus instance instead of launching a dedicated one.

* chore(tests): respect CMAKE_INSTALL_PREFIX for system bus config

DBus isn't part of base system on BSDs, so may not use /etc for configs.
Also, testing installation failed as non-root:

$ cmake -DBUILD_TESTS=1 -DCMAKE_INSTALL_PREFIX=/tmp/sdbus-cpp_prefix -DTESTS_INSTALL_PATH=/tmp/sdbus-cpp_prefix/tests
$ cmake --build .
$ cmake --install .
[...]
CMake Error at tests/cmake_install.cmake:105 (file):
  file cannot create directory: /etc/dbus-1/system.d.  Maybe need
  administrative privileges.

* chore(tests): temporarily skip 1 test on FreeBSD to keep CI happy

* chore(ci): run tests in freebsd job
2023-09-18 11:35:23 +02:00
290078d6af feat: add support for async property get/set on client-side (#354)
* feat: add async property get/set convenience support classes

* feat: add no-reply and async overloads to Properties_proxy

* feat: add convenience functions for GetAll functionality

* test: add tests for new functionality

* add codegen IDL support and documentation
2023-09-14 10:54:57 +02:00
43 changed files with 1576 additions and 285 deletions

View File

@ -48,19 +48,19 @@ jobs:
run: |
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="-O0 -g -W -Wextra -Wall -Wnon-virtual-dtor -Werror" -DCMAKE_VERBOSE_MAKEFILE=ON -DBUILD_TESTS=ON -DENABLE_PERF_TESTS=ON -DENABLE_STRESS_TESTS=ON -DBUILD_CODE_GEN=ON ..
cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_CXX_FLAGS="-O0 -g -W -Wextra -Wall -Wnon-virtual-dtor -Werror" -DCMAKE_VERBOSE_MAKEFILE=ON -DBUILD_TESTS=ON -DINSTALL_TESTS=ON -DENABLE_PERF_TESTS=ON -DENABLE_STRESS_TESTS=ON -DBUILD_CODE_GEN=ON ..
- name: configure-release
if: matrix.build == 'shared-libsystemd' && matrix.os == 'ubuntu-22.04'
run: |
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_CXX_FLAGS="-O3 -DNDEBUG -W -Wextra -Wall -Wnon-virtual-dtor -Werror" -DCMAKE_VERBOSE_MAKEFILE=ON -DBUILD_TESTS=ON -DENABLE_PERF_TESTS=ON -DENABLE_STRESS_TESTS=ON -DBUILD_CODE_GEN=ON ..
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_CXX_FLAGS="-O3 -DNDEBUG -W -Wextra -Wall -Wnon-virtual-dtor -Werror" -DCMAKE_VERBOSE_MAKEFILE=ON -DBUILD_TESTS=ON -DINSTALL_TESTS=ON -DENABLE_PERF_TESTS=ON -DENABLE_STRESS_TESTS=ON -DBUILD_CODE_GEN=ON ..
- name: configure-with-embedded-libsystemd
if: matrix.build == 'embedded-static-libsystemd'
run: |
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_VERBOSE_MAKEFILE=ON -DBUILD_TESTS=ON -DENABLE_PERF_TESTS=ON -DENABLE_STRESS_TESTS=ON -DBUILD_CODE_GEN=ON -DBUILD_LIBSYSTEMD=ON -DLIBSYSTEMD_VERSION=244 ..
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_VERBOSE_MAKEFILE=ON -DBUILD_TESTS=ON -DINSTALL_TESTS=ON -DENABLE_PERF_TESTS=ON -DENABLE_STRESS_TESTS=ON -DBUILD_CODE_GEN=ON -DBUILD_LIBSYSTEMD=ON -DLIBSYSTEMD_VERSION=244 ..
- name: make
run: |
cd build
@ -84,3 +84,22 @@ jobs:
build/sdbus-c++*.deb
build/sdbus-c++*.ddeb
retention-days: 10
freebsd-build:
name: build (freebsd, clang/libc++, basu)
runs-on: ubuntu-22.04 # until https://github.com/actions/runner/issues/385
steps:
- uses: actions/checkout@v2
- name: Test in FreeBSD VM
uses: vmactions/freebsd-vm@v1
with:
copyback: false
usesh: true
prepare: |
pkg install -y cmake ninja pkgconf basu expat googletest
run: |
cmake -B _build -G Ninja -DBUILD_CODE_GEN=ON -DBUILD_TESTS=ON -DINSTALL_TESTS=ON -DENABLE_PERF_TESTS=ON -DENABLE_STRESS_TESTS=ON
cmake --build _build
cmake --install _build
pkg install -y dbus
service dbus onestart
ctest --output-on-failure --test-dir _build

View File

@ -4,7 +4,7 @@
cmake_minimum_required(VERSION 3.13)
project(sdbus-c++ VERSION 1.3.0 LANGUAGES C CXX)
project(sdbus-c++ VERSION 1.4.0 LANGUAGES C CXX)
include(GNUInstallDirs) # Installation directories for `install` command and pkgconfig file
@ -12,7 +12,8 @@ include(GNUInstallDirs) # Installation directories for `install` command and pkg
# PERFORMING CHECKS & PREPARING THE DEPENDENCIES
#-------------------------------
set(LIBSYSTEMD "libsystemd")
set(LIBSYSTEMD_IMPL "systemd")
set(LIBSYSTEMD_LIB "libsystemd")
option(BUILD_LIBSYSTEMD "Build libsystemd static library and incorporate it into libsdbus-c++" OFF)
@ -23,9 +24,17 @@ if(NOT BUILD_LIBSYSTEMD)
message(WARNING "libsystemd not found, checking for libelogind instead")
pkg_check_modules(Systemd IMPORTED_TARGET GLOBAL libelogind>=236)
if(TARGET PkgConfig::Systemd)
set(LIBSYSTEMD "libelogind")
set(LIBSYSTEMD_IMPL "elogind")
set(LIBSYSTEMD_LIB "libelogind")
string(REPLACE "." ";" VERSION_LIST ${Systemd_VERSION})
list(GET VERSION_LIST 0 Systemd_VERSION)
else()
message(WARNING "libelogind not found, checking for basu instead")
pkg_check_modules(Systemd IMPORTED_TARGET GLOBAL basu)
set(LIBSYSTEMD_IMPL "basu")
set(LIBSYSTEMD_LIB "basu")
# https://git.sr.ht/~emersion/basu/commit/d4d185d29a26
set(Systemd_VERSION "240")
endif()
endif()
if(NOT TARGET PkgConfig::Systemd)
@ -109,18 +118,22 @@ set(CMAKE_CXX_STANDARD 17)
set(SDBUSCPP_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}")
set(SDBUSCPP_VERSION "${PROJECT_VERSION}")
# We promote BUILD_SHARED_LIBS flags to (global) option only if we are the main project
# We promote the BUILD_SHARED_LIBS flag to a (global) option only if we are the main project
if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR)
option(BUILD_SHARED_LIBS "Build shared libraries (.so) instead of static ones (.a)" ON)
endif()
# 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 BUILD_LIB=1 LIBSYSTEMD_VERSION=${LIBSYSTEMD_VERSION})
target_compile_definitions(sdbus-c++-objlib PRIVATE
BUILD_LIB=1
LIBSYSTEMD_VERSION=${LIBSYSTEMD_VERSION}
SDBUS_${LIBSYSTEMD_IMPL}
SDBUS_HEADER=<${LIBSYSTEMD_IMPL}/sd-bus.h>)
target_include_directories(sdbus-c++-objlib PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>)
if(DEFINED BUILD_SHARED_LIBS)
set_target_properties(sdbus-c++-objlib PROPERTIES POSITION_INDEPENDENT_CODE ${BUILD_SHARED_LIBS})
if(BUILD_SHARED_LIBS)
set_target_properties(sdbus-c++-objlib PROPERTIES POSITION_INDEPENDENT_CODE ON)
endif()
if(BUILD_LIBSYSTEMD)
add_dependencies(sdbus-c++-objlib LibsystemdBuildProject)
@ -159,7 +172,7 @@ install(TARGETS ${EXPORT_SET}
# TESTS
#----------------------------------
option(BUILD_TESTS "Build and install tests (default OFF)" OFF)
option(BUILD_TESTS "Build tests (default OFF)" OFF)
if(BUILD_TESTS)
message(STATUS "Building with tests")
@ -208,6 +221,7 @@ endif()
include(CMakePackageConfigHelpers)
install(EXPORT sdbus-c++-targets
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/sdbus-c++
NAMESPACE SDBusCpp::
@ -226,6 +240,7 @@ if(BUILD_SHARED_LIBS AND (BUILD_LIBSYSTEMD OR Systemd_LINK_LIBRARIES MATCHES "/l
else()
set(PKGCONFIG_REQS "")
endif()
set(PKGCONFIG_DEPS ${LIBSYSTEMD_LIB})
configure_file(pkgconfig/sdbus-c++.pc.in pkgconfig/sdbus-c++.pc @ONLY)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/pkgconfig/sdbus-c++.pc
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig COMPONENT dev)

View File

@ -232,3 +232,10 @@ v1.3.0
- Add support for libelogind as an addition to libsystemd
- Add support for std::future-based async methods in codegen tool
- Additional little fixes and improvements in code, build system, CI, and documentation
v1.4.0
- Implement API for convenient asynchronous property get/set on the client-side
- Add support for FreeBSD systems (including support for basu implementation of sd-bus on non-systemd machines)
- Add support for direct, peer-to-peer connections
- Add option to create IConnection directly from an underlying sd_bus instance
- Some additional fixes

View File

@ -50,9 +50,13 @@ $ sudo cmake --build . --target install
Option for building sdbus-c++ stress tests. Default value: `OFF`.
* `INSTALL_TESTS` [boolean]
Option for installing tests that were built. Default value: `OFF`.
* `TESTS_INSTALL_PATH` [string]
Path where the test binaries shall get installed. Default value: `/opt/test/bin`.
Path where the test binaries shall get installed. Default value: `${CMAKE_INSTALL_PREFIX}/tests/sdbus-c++` (previously: `/opt/test/bin`).
* `BUILD_LIBSYSTEMD` [boolean]
@ -82,7 +86,7 @@ Dependencies
------------
* `C++17` - the library uses C++17 features.
* `libsystemd` - systemd library containing sd-bus implementation. This library is part of systemd. Systemd at least v236 is needed. (In case you have a non-systemd environment, don't worry, see [Solving libsystemd dependency](docs/using-sdbus-c++.md#solving-libsystemd-dependency) for more information.)
* `libsystemd`/`libelogind`/`basu` - libraries containing sd-bus implementation that sdbus-c++ is written around. In case of `libsystemd` and `libelogind`, version >= 236 is needed. (In case you have a non-systemd environment, don't worry, see [Solving libsystemd dependency](docs/using-sdbus-c++.md#solving-libsystemd-dependency) for more information.)
* `googletest` - google unit testing framework, only necessary when building tests, will be downloaded and built automatically.
* `pkgconfig` - required for sdbus-c++ to be able to find some dependency packages.
* `expat` - necessary when building xml2cpp code generator (`BUILD_CODE_GEN` option is ON).

View File

@ -21,7 +21,8 @@ Using sdbus-c++ library
16. [Standard D-Bus interfaces](#standard-d-bus-interfaces)
17. [Representing D-Bus Types in sdbus-c++](#representing-d-bus-types-in-sdbus-c)
18. [Support for match rules](#support-for-match-rules)
19. [Conclusion](#conclusion)
19. [Using direct (peer-to-peer) D-Bus connections](#using-direct-peer-to-peer-d-bus-connections)
20. [Conclusion](#conclusion)
Introduction
------------
@ -74,13 +75,19 @@ add_custom_command(
Solving libsystemd dependency
-----------------------------
sdbus-c++ depends on libsystemd, a C library that is part of [systemd](https://github.com/systemd/systemd) and that contains underlying sd-bus implementation.
sdbus-c++ depends on sd-bus API, which is implemented in libsystemd, a C library that is part of [systemd](https://github.com/systemd/systemd).
Minimum required libsystemd shared library version is 0.20.0 (which corresponds to minimum systemd version 236).
If your target Linux distribution is already based on systemd ecosystem of version 236 and higher, then there is no additional effort, just make sure you have corresponding systemd header files available (provided by `libsystemd-dev` package on Debian/Ubuntu, for example), and you may go on building sdbus-c++ seamlessly.
However, sdbus-c++ can perfectly be used in non-systemd environments as well. There are two ways to approach this:
However, sdbus-c++ can perfectly be used in non-systemd environments as well. If `libsystemd` is not found in the system when configuring sdbus-c++, then
1. sdbus-c++ will try to find `libelogind`, which is an extracted logind from original systemd containing sd-bus implementation. If not found, then
2. sdbus-c++ will try to find `basu`, which is just sd-bus implementation extracted from systemd.
On systems where neither of these is available, we can build sd-bus as a shared lib manually or we can (conveniently) instruct sdbus-c++ to build and integrate sd-bus into itself for us.
### Building and distributing libsystemd as a shared library yourself
@ -100,7 +107,7 @@ $ ninja libsystemd.so.0.26.0 # or another version number depending which system
### Building and distributing libsystemd as part of sdbus-c++
sdbus-c++ provides `BUILD_LIBSYSTEMD` configuration option. When turned on, sdbus-c++ will automatically download and build libsystemd as a static library and make it an opaque part of sdbus-c++ shared library for you. This is the most convenient and effective approach to build, distribute and use sdbus-c++ as a self-contained, systemd-independent library in non-systemd environments. Just make sure your build machine has all dependencies needed by libsystemd build process. That includes, among others, `meson`, `ninja`, `git`, `gperf`, and -- primarily -- libraries and library headers for `libmount`, `libcap` and `librt` (part of glibc). Be sure to check out the systemd documentation for the Also, when distributing, make sure these dependency libraries are installed on the production machine.
sdbus-c++ provides `BUILD_LIBSYSTEMD` configuration option. When turned on, sdbus-c++ will automatically download and build libsystemd as a static library and make it an opaque part of sdbus-c++ shared library for you. This is the most convenient and effective approach to build, distribute and use sdbus-c++ as a self-contained, systemd-independent library in non-systemd environments. Just make sure your build machine has all dependencies needed by libsystemd build process. That includes, among others, `meson`, `ninja`, `git`, `gperf`, and -- primarily -- libraries and library headers for `libmount`, `libcap` and `librt` (part of glibc). Also, when distributing, make sure these dependency libraries are installed on the production machine.
You may additionally set the `LIBSYSTEMD_VERSION` configuration flag to fine-tune the version of systemd to be taken in. (The default value is 242).
@ -387,6 +394,27 @@ Subsequently, we invoke two RPC calls to object's `concatenate()` method. We cre
Please note that we can create and destroy D-Bus object proxies dynamically, at any time during runtime, even when they share a common D-Bus connection and there is an active event loop upon the connection. So managing D-Bus object proxies' lifecycle (creating and destroying D-Bus object proxies) is completely thread-safe.
### Opening bus connections in sdbus-c++
There are several factory methods to create a bus connection object in sdbus-c++:
* `createConnection()` - opens a connection to the system bus
* `createConnection(const std::string& name)` - opens a connection with the given name to the system bus
* `createDefaultBusConnection()` - opens a connection to the session bus when in a user context, and a connection to the system bus, otherwise
* `createDefaultBusConnection(const std::string& name)` - opens a connection with the given name to the session bus when in a user context, and a connection with the given name to the system bus, otherwise
* `createSystemBusConnection()` - opens a connection to the system bus
* `createSystemBusConnection(const std::string& name)` - opens a connection with the given name to the system bus
* `createSessionBusConnection()` - opens a connection to the session bus
* `createSessionBusConnection(const std::string& name)` - opens a connection with the given name to the session bus
* `createSessionBusConnectionWithAddress(const std::string& address)` - opens a connection to the session bus at a custom address
* `createRemoteSystemBusConnection(const std::string& host)` - opens a connection to the system bus on a remote host using ssh
* `createDirectBusConnection(const std::string& address)` - opens direct D-Bus connection at a custom address (see [Using direct (peer-to-peer) D-Bus connections](#using-direct-peer-to-peer-d-bus-connections))
* `createDirectBusConnection(int fd)` - opens direct D-Bus connection at the given file descriptor (see [Using direct (peer-to-peer) D-Bus connections](#using-direct-peer-to-peer-d-bus-connections))
* `createServerBus(int fd)` - opens direct D-Bus connection at the given file descriptor as a server (see [Using direct (peer-to-peer) D-Bus connections](#using-direct-peer-to-peer-d-bus-connections))
* `createBusConnection(sd_bus *bus)` - creates a connection directly from the underlying sd_bus connection instance (which has been created and set up upfront directly through sd-bus API).
For more information, peek into [`IConnection.h`](/include/sdbus-c++/IConnection.h) where these functions are declared and documented.
### Working with D-Bus connections in sdbus-c++
The design of D-Bus connections in sdbus-c++ allows for certain flexibility and enables users to choose simplicity over scalability or scalability (at a finer granularity of user's choice) at the cost of slightly decreased simplicity.
@ -564,6 +592,7 @@ We recommend that sdbus-c++ users prefer the convenience API to the lower level,
> assert(e->getMessage() == "Failed to deserialize a int32 value");
> }
> ```
> Signature mismatch in signal handlers is probably the most common reason why signals are not received in the client, while we can see them on the bus with `dbus-monitor`. Use `const sdbus::Error*`-based callback variant and inspect the error to check if that's the cause of such problems.
> **_Tip_:** When registering a D-Bus object, we can additionally provide names of input and output parameters of its methods and names of parameters of its signals. When the object is introspected, these names are listed in the resulting introspection XML, which improves the description of object's interfaces:
> ```c++
@ -1256,9 +1285,67 @@ Annotate the element with `org.freedesktop.DBus.Method.Timeout` in order to spec
Using D-Bus properties
----------------------
sdbus-c++ provides functionality for convenient working with D-Bus properties, on both convenience and generated code API level.
### Convenience API
Let's say a remote D-Bus object provides property `status` of type `u` under interface `org.sdbuscpp.Concatenator`.
#### Reading a property
We read property value easily through `IProxy::getProperty()` method:
```c++
uint32_t status = proxy->getProperty("status").onInterface("org.sdbuscpp.Concatenator");
```
Getting a property in asynchronous manner is also possible, in both callback-based and future-based way, by calling `IProxy::getPropertyAsync()` method:
```c++
// Callback-based method:
auto callback = [](const sdbus::Error* err, sdbus::Variant value)
{
std::cout << "Got property value: " << value.get<uint32_t>() << std::endl;
};
uint32_t status = proxy->getPropertyAsync("status").onInterface("org.sdbuscpp.Concatenator").uponReplyInvoke(std::move(callback));
// Future-based method:
std::future<sdbus::Variant> statusFuture = object.getPropertyAsync("status").onInterface("org.sdbuscpp.Concatenator").getResultAsFuture();
...
std::cout << "Got property value: " << statusFuture.get().get<uint32_t>() << std::endl;
```
More information on `error` callback handler parameter, on behavior of `future` in erroneous situations, can be found in section [Asynchronous client-side methods](#asynchronous-client-side-methods).
#### Writing a property
Writing a property is equally simple, through `IProxy::setProperty()`:
```c++
uint32_t status = ...;
proxy->setProperty("status").onInterface("org.sdbuscpp.Concatenator").toValue(status);
```
Setting a property in asynchronous manner is also possible, in both callback-based and future-based way, by calling `IProxy::setPropertyAsync()` method:
```c++
// Callback-based method:
auto callback = [](const sdbus::Error* err) { /*... Error handling in case err is non-null...*/ };
uint32_t status = proxy->setPropertyAsync("status").onInterface("org.sdbuscpp.Concatenator").toValue(status).uponReplyInvoke(std::move(callback));
// Future-based method:
std::future<void> statusFuture = object.setPropertyAsync("status").onInterface("org.sdbuscpp.Concatenator").getResultAsFuture();
```
More information on `error` callback handler parameter, on behavior of `future` in erroneous situations, can be found in section [Asynchronous client-side methods](#asynchronous-client-side-methods).
#### Getting all properties
In a very analogous way, with both synchronous and asynchronous options, it's possible to read all properties of an object under given interface at once. `IProxy::getAllProperties()` is what you're looking for.
### Generated bindings API
Defining and working with D-Bus properties using XML description is quite easy.
### Defining a property in the IDL
#### Defining a property in the IDL
A property element has no arg child element. It just has the attributes name, type and access, which are all mandatory. The access attribute allows the values readwrite, read, and write.
@ -1276,7 +1363,9 @@ An example of a read-write property `status`:
</node>
```
### Generated C++ bindings
The property may also have annotations. In addition to standard annotations defined in D-Bus specification, there are sdbus-c++-specific ones, discussed further below.
#### Generated C++ bindings
This is how generated adaptor and proxy classes would look like with the read-write `status` property. The adaptor:
@ -1329,7 +1418,60 @@ public:
};
```
When implementing the adaptor, we simply need to provide the body for `status` getter and setter method by overriding them. Then in the proxy, we just call them.
When implementing the adaptor, we simply need to provide the body for the `status` getter and setter methods by overriding them. Then in the proxy, we just call them.
#### Client-side asynchronous properties
We can mark the property so that the generator generates either asynchronous variant of getter method, or asynchronous variant of setter method, or both. Annotations names are `org.freedesktop.DBus.Property.Get.Async`, or `org.freedesktop.DBus.Property.Set.Async`, respectively. Their values must be set to `client`.
In addition, we can choose through annotations `org.freedesktop.DBus.Property.Get.Async.ClientImpl`, or `org.freedesktop.DBus.Property.Set.Async.ClientImpl`, respectively, whether a callback-based or future-based variant will be generated. The concept is analogous to the one for asynchronous D-Bus methods described above in this document.
The callback-based method will generate a pure virtual function `On<PropertyName>Property[Get|Set]Reply()`, which must be overridden by the derived class.
For example, this description:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<node name="/org/sdbuscpp/propertyprovider">
<interface name="org.sdbuscpp.PropertyProvider">
<!--...-->
<property name="status" type="u" access="readwrite">
<annotation name="org.freedesktop.DBus.Property.Get.Async" value="client"/>
<annotation name="org.freedesktop.DBus.Property.Get.Async.ClientImpl" value="callback"/>
</property>
<!--...-->
</interface>
</node>
```
will get generated into this C++ code on client side:
```cpp
class PropertyProvider_proxy
{
/*...*/
virtual void onStatusPropertyGetReply(const uint32_t& value, const sdbus::Error* error) = 0;
public:
// getting the property value
sdbus::PendingAsyncCall status()
{
return object_->getPropertyAsync("status").onInterface(INTERFACE_NAME).uponReplyInvoke([this](const sdbus::Error* error, const sdbus::Variant& value){ this->onActionPropertyGetReply(value.get<uint32_t>(), error); });
}
// setting the property value
void status(const uint32_t& value)
{
object_->setProperty("status").onInterface(INTERFACE_NAME).toValue(value);
}
/*...*/
};
```
In addition to custom generated code for getting/setting properties, `org.freedesktop.DBus.Properties` standard D-Bus interface, implemented through pre-defined `sdbus::Properties_proxy` in `sdbus-c++/StandardInterfaces.h`, can also be used for reading/writing properties. See next section.
Standard D-Bus interfaces
-------------------------
@ -1398,10 +1540,10 @@ The above mapping between D-Bus and C++ types is what sdbus-c++ provides by defa
We need two things to do that:
* implement `sdbus::Message` insertion and extraction operators, so sdbus-c++ knows how to serialize/deserialize our custom type,
* implement `sdbus::Message` insertion (serialization) and extraction (deserialization) operators, so sdbus-c++ knows how to serialize/deserialize our custom type,
* specialize `sdbus::signature_of` template for our custom type, so sdbus-c++ knows the mapping to D-Bus type and other necessary information about our type.
Say, we would like to represent D-Bus arrays as `std::list`s in our application. Since sdbus-c++ comes with pre-defined support for `std::vector`s, `std::array`s and `std::span`s as D-Bus array representations, we have to provide an extension:
Say, we would like to represent D-Bus arrays as `std::list`s in our application. Since sdbus-c++ comes with pre-defined support for `std::vector`s, `std::array`s and `std::span`s as D-Bus array representations, we have to provide an extension. To implement message serialization and deserialization functions for `std::list`, we can simply copy the sdbus-c++ implementation of these functions for `std::vector`, and simply adjust for `std::list`. Then we provide `signature_of` specialization, again written on terms of one specialized for `std::vector`:
```c++
#include <list>
@ -1454,12 +1596,11 @@ template <typename _Element, typename _Allocator>
struct sdbus::signature_of<std::list<_Element, _Allocator>>
: sdbus::signature_of<std::vector<_Element, _Allocator>>
{};
};
```
Then we can simply use `std::list`s, serialize/deserialize them in a D-Bus message, in D-Bus method calls or return values... and they will be simply transmitted as D-Bus arrays.
As another example, say we have our custom type `my::Struct` which we'd like to use as a D-Bus structure representation (sdbus-c++ provides `sdbus::Struct` type for that, but we don't want to use it because using our custom type directly is more convenient). Again, we have to provide type traits and message serialization/deserialization functions for our custom type. We build our functions and specializations on top of `sdbus::Struct`, so we don't have to copy and write a lot of boiler-plate:
As another example, say we have our custom type `my::Struct` which we'd like to use as a D-Bus structure representation (sdbus-c++ provides `sdbus::Struct` type for that, but we don't want to use it because using our custom type directly is more convenient). Again, we have to provide type traits and message serialization/deserialization functions for our custom type. We build our functions and specializations on top of `sdbus::Struct`, so we don't have to copy and write a lot of boiler-plate. Serialization/deserialization functions can be placed in the same namespace as our custom type, and will be found thanks to the ADR lookup. The `signature_of` specialization must always be in either `sdbus` namespace or in a global namespace:
```c++
namespace my {
@ -1482,7 +1623,7 @@ namespace my {
sdbus::Struct s{std::forward_as_tuple(items.i, items.s, items.l)};
return msg >> s;
}
}
} // namespace my
template <>
struct sdbus::signature_of<my::Struct>
@ -1497,7 +1638,66 @@ Live examples of extending sdbus-c++ types can be found in [Message unit tests](
Support for match rules
-----------------------
`IConnection` class provides `addMatch` method that you can use to install match rules. An associated callback handler will be called upon an incoming message matching given match rule. There is support for both client-owned and floating (library-owned) match rules. Consult `IConnection` header or sdbus-c++ doxygen documentation for more information.
`IConnection` class provides `addMatch` and `addMatchAsync` family of methods that you can use to install match rules on that bus connection. An associated callback handler will be called when an incoming D-Bus message matches the given match rule. Clients can decide whether they own and control the match rule lifetime, or whether the match rule lifetime is bound the connection object lifetime (so-called floating match rule). Consult `IConnection` header or sdbus-c++ doxygen documentation for more information.
Using direct (peer-to-peer) D-Bus connections
---------------------------------------------
sdbus-c++ provides an API to establish a direct connection between two peers -- between a client and a server, without going via the D-Bus daemon. The methods of interest, which will create a D-Bus server bus, and a client connection to it, respectively, are:
* `sdbus::createServerBus()` creates and returns a new, custom bus object in server mode, out of provided file descriptor parameter.
* `sdbus::createDirectBusConnection()` opens and returns direct D-Bus connection at the provided custom address(es), or at the provided file descriptor.
Here is an example, extracted from the analogous test case in sdbus-c++ integration tests suite:
```c++
#include "Concatenator.h"
#include "ConcatenatorProxy.h"
#include <sdbus-c++/sdbus-c++.h>
#include <sys/types.h>
#include <sys/socket.h>
int main(int argc, char *argv[])
{
int fds[2];
socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
std::unique_ptr<sdbus::IConnection> serverConnection;
std::unique_ptr<sdbus::IConnection> clientConnection;
std::thread t([&]()
{
serverConnection = sdbus::createServerBus(fds[0]);
// This is necessary so that createDirectBusConnection() below does not block
serverConnection->enterEventLoopAsync();
});
clientConnection = sdbus::createDirectBusConnection(fds[1]);
clientConnection->enterEventLoopAsync();
t.join();
// We can now use connection objects in a familiar way, e.g. create adaptor and proxy objects on them, and exchange messages.
// Here, using Concatenator IDL-generated bindings example from chapters above:
const char* objectPath = "/org/sdbuscpp/concatenator";
Concatenator concatenator(*serverConnection, objectPath);
const char* emptyDestinationName = ""; // Destination may be empty in case of direct connections
ConcatenatorProxy concatenatorProxy(*clientConnection, emptyDestinationName, objectPath);
// Perform call of concatenate D-Bus method
std::vector<int> numbers = {1, 2, 3};
std::string separator = ":";
auto concatenatedString = concatenatorProxy.concatenate(numbers, separator);
assert(concatenatedString == "1:2:3");
// Explicitly stop working on socket fd's to avoid "Connection reset by peer" errors
clientConnection->leaveEventLoop();
serverConnection->leaveEventLoop();
}
```
> **_Note_:** The example above explicitly stops the event loops on both sides, before the connection objects are destroyed. This avoids potential `Connection reset by peer` errors caused when one side closes its socket while the other side is still working on the counterpart socket. This is a recommended workflow for closing direct D-Bus connections.
Conclusion
----------

View File

@ -29,6 +29,7 @@
#include <sdbus-c++/Message.h>
#include <sdbus-c++/TypeTraits.h>
#include <sdbus-c++/Types.h>
#include <sdbus-c++/Flags.h>
#include <string>
#include <vector>
@ -41,7 +42,6 @@
namespace sdbus {
class IObject;
class IProxy;
class Variant;
class Error;
class PendingAsyncCall;
}
@ -225,7 +225,7 @@ namespace sdbus {
{
public:
SignalUnsubscriber(IProxy& proxy, const std::string& signalName);
void onInterface(std::string interfaceName);
void onInterface(const std::string& interfaceName);
private:
IProxy& proxy_;
@ -236,25 +236,81 @@ namespace sdbus {
{
public:
PropertyGetter(IProxy& proxy, const std::string& propertyName);
sdbus::Variant onInterface(const std::string& interfaceName);
Variant onInterface(const std::string& interfaceName);
private:
IProxy& proxy_;
const std::string& propertyName_;
};
class AsyncPropertyGetter
{
public:
AsyncPropertyGetter(IProxy& proxy, const std::string& propertyName);
AsyncPropertyGetter& onInterface(const std::string& interfaceName);
template <typename _Function> PendingAsyncCall uponReplyInvoke(_Function&& callback);
std::future<Variant> getResultAsFuture();
private:
IProxy& proxy_;
const std::string& propertyName_;
const std::string* interfaceName_{};
};
class PropertySetter
{
public:
PropertySetter(IProxy& proxy, const std::string& propertyName);
PropertySetter& onInterface(std::string interfaceName);
PropertySetter& onInterface(const std::string& interfaceName);
template <typename _Value> void toValue(const _Value& value);
void toValue(const sdbus::Variant& value);
template <typename _Value> void toValue(const _Value& value, dont_expect_reply_t);
void toValue(const Variant& value);
void toValue(const Variant& value, dont_expect_reply_t);
private:
IProxy& proxy_;
const std::string& propertyName_;
std::string interfaceName_;
const std::string* interfaceName_{};
};
class AsyncPropertySetter
{
public:
AsyncPropertySetter(IProxy& proxy, const std::string& propertyName);
AsyncPropertySetter& onInterface(const std::string& interfaceName);
template <typename _Value> AsyncPropertySetter& toValue(_Value&& value);
AsyncPropertySetter& toValue(Variant value);
template <typename _Function> PendingAsyncCall uponReplyInvoke(_Function&& callback);
std::future<void> getResultAsFuture();
private:
IProxy& proxy_;
const std::string& propertyName_;
const std::string* interfaceName_{};
Variant value_;
};
class AllPropertiesGetter
{
public:
AllPropertiesGetter(IProxy& proxy);
std::map<std::string, Variant> onInterface(const std::string& interfaceName);
private:
IProxy& proxy_;
};
class AsyncAllPropertiesGetter
{
public:
AsyncAllPropertiesGetter(IProxy& proxy);
AsyncAllPropertiesGetter& onInterface(const std::string& interfaceName);
template <typename _Function> PendingAsyncCall uponReplyInvoke(_Function&& callback);
std::future<std::map<std::string, Variant>> getResultAsFuture();
private:
IProxy& proxy_;
const std::string* interfaceName_{};
};
}

View File

@ -293,7 +293,7 @@ namespace sdbus {
template <typename _Function>
inline PropertyRegistrator& PropertyRegistrator::withGetter(_Function&& callback)
{
static_assert(function_traits<_Function>::arity == 0, "Property getter function must not take any arguments");
static_assert(function_argument_count_v<_Function> == 0, "Property getter function must not take any arguments");
static_assert(!std::is_void<function_result_t<_Function>>::value, "Property getter function must return property value");
if (propertySignature_.empty())
@ -311,7 +311,7 @@ namespace sdbus {
template <typename _Function>
inline PropertyRegistrator& PropertyRegistrator::withSetter(_Function&& callback)
{
static_assert(function_traits<_Function>::arity == 1, "Property setter function must take one parameter - the property value");
static_assert(function_argument_count_v<_Function> == 1, "Property setter function must take one parameter - the property value");
static_assert(std::is_void<function_result_t<_Function>>::value, "Property setter function must not return any value");
if (propertySignature_.empty())
@ -595,9 +595,8 @@ namespace sdbus {
}
catch (const Error& e)
{
// Catch message unpack exceptions and pass them to the callback
// in the expected manner to avoid propagating them up the call
// stack to the event loop.
// Pass message deserialization exceptions to the client via callback error parameter,
// instead of propagating them up the message loop call stack.
sdbus::apply(callback, &e, args);
return;
}
@ -676,8 +675,10 @@ namespace sdbus {
}
catch (const sdbus::Error& e)
{
// Invoke callback with error argument and input arguments from the tuple.
// Pass message deserialization exceptions to the client via callback error parameter,
// instead of propagating them up the message loop call stack.
sdbus::apply(callback, &e, signalArgs);
return;
}
// Invoke callback with no error and input arguments from the tuple.
@ -704,7 +705,7 @@ namespace sdbus {
{
}
inline void SignalUnsubscriber::onInterface(std::string interfaceName)
inline void SignalUnsubscriber::onInterface(const std::string& interfaceName)
{
proxy_.unregisterSignalHandler(interfaceName, signalName_);
}
@ -719,17 +720,56 @@ namespace sdbus {
{
}
inline sdbus::Variant PropertyGetter::onInterface(const std::string& interfaceName)
inline Variant PropertyGetter::onInterface(const std::string& interfaceName)
{
sdbus::Variant var;
proxy_
.callMethod("Get")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(interfaceName, propertyName_)
.storeResultsTo(var);
Variant var;
proxy_.callMethod("Get")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(interfaceName, propertyName_)
.storeResultsTo(var);
return var;
}
/*** ------------------- ***/
/*** AsyncPropertyGetter ***/
/*** ------------------- ***/
inline AsyncPropertyGetter::AsyncPropertyGetter(IProxy& proxy, const std::string& propertyName)
: proxy_(proxy)
, propertyName_(propertyName)
{
}
inline AsyncPropertyGetter& AsyncPropertyGetter::onInterface(const std::string& interfaceName)
{
interfaceName_ = &interfaceName;
return *this;
}
template <typename _Function>
PendingAsyncCall AsyncPropertyGetter::uponReplyInvoke(_Function&& callback)
{
static_assert(std::is_invocable_r_v<void, _Function, const Error*, Variant>, "Property get callback function must accept Error* and property value as Variant");
assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync("Get")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(*interfaceName_, propertyName_)
.uponReplyInvoke(std::forward<_Function>(callback));
}
inline std::future<Variant> AsyncPropertyGetter::getResultAsFuture()
{
assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync("Get")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(*interfaceName_, propertyName_)
.getResultAsFuture<Variant>();
}
/*** -------------- ***/
/*** PropertySetter ***/
/*** -------------- ***/
@ -740,9 +780,9 @@ namespace sdbus {
{
}
inline PropertySetter& PropertySetter::onInterface(std::string interfaceName)
inline PropertySetter& PropertySetter::onInterface(const std::string& interfaceName)
{
interfaceName_ = std::move(interfaceName);
interfaceName_ = &interfaceName;
return *this;
}
@ -750,17 +790,144 @@ namespace sdbus {
template <typename _Value>
inline void PropertySetter::toValue(const _Value& value)
{
PropertySetter::toValue(sdbus::Variant{value});
PropertySetter::toValue(Variant{value});
}
inline void PropertySetter::toValue(const sdbus::Variant& value)
template <typename _Value>
inline void PropertySetter::toValue(const _Value& value, dont_expect_reply_t)
{
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
PropertySetter::toValue(Variant{value}, dont_expect_reply);
}
proxy_
.callMethod("Set")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(interfaceName_, propertyName_, value);
inline void PropertySetter::toValue(const Variant& value)
{
assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function
proxy_.callMethod("Set")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(*interfaceName_, propertyName_, value);
}
inline void PropertySetter::toValue(const Variant& value, dont_expect_reply_t)
{
assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function
proxy_.callMethod("Set")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(*interfaceName_, propertyName_, value)
.dontExpectReply();
}
/*** ------------------- ***/
/*** AsyncPropertySetter ***/
/*** ------------------- ***/
inline AsyncPropertySetter::AsyncPropertySetter(IProxy& proxy, const std::string& propertyName)
: proxy_(proxy)
, propertyName_(propertyName)
{
}
inline AsyncPropertySetter& AsyncPropertySetter::onInterface(const std::string& interfaceName)
{
interfaceName_ = &interfaceName;
return *this;
}
template <typename _Value>
inline AsyncPropertySetter& AsyncPropertySetter::toValue(_Value&& value)
{
return AsyncPropertySetter::toValue(Variant{std::forward<_Value>(value)});
}
inline AsyncPropertySetter& AsyncPropertySetter::toValue(Variant value)
{
value_ = std::move(value);
return *this;
}
template <typename _Function>
PendingAsyncCall AsyncPropertySetter::uponReplyInvoke(_Function&& callback)
{
static_assert(std::is_invocable_r_v<void, _Function, const Error*>, "Property set callback function must accept Error* only");
assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync("Set")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(*interfaceName_, propertyName_, std::move(value_))
.uponReplyInvoke(std::forward<_Function>(callback));
}
inline std::future<void> AsyncPropertySetter::getResultAsFuture()
{
assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync("Set")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(*interfaceName_, propertyName_, std::move(value_))
.getResultAsFuture<>();
}
/*** ------------------- ***/
/*** AllPropertiesGetter ***/
/*** ------------------- ***/
inline AllPropertiesGetter::AllPropertiesGetter(IProxy& proxy)
: proxy_(proxy)
{
}
inline std::map<std::string, Variant> AllPropertiesGetter::onInterface(const std::string& interfaceName)
{
std::map<std::string, Variant> props;
proxy_.callMethod("GetAll")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(interfaceName)
.storeResultsTo(props);
return props;
}
/*** ------------------------ ***/
/*** AsyncAllPropertiesGetter ***/
/*** ------------------------ ***/
inline AsyncAllPropertiesGetter::AsyncAllPropertiesGetter(IProxy& proxy)
: proxy_(proxy)
{
}
inline AsyncAllPropertiesGetter& AsyncAllPropertiesGetter::onInterface(const std::string& interfaceName)
{
interfaceName_ = &interfaceName;
return *this;
}
template <typename _Function>
PendingAsyncCall AsyncAllPropertiesGetter::uponReplyInvoke(_Function&& callback)
{
static_assert( std::is_invocable_r_v<void, _Function, const Error*, std::map<std::string, Variant>>
, "All properties get callback function must accept Error* and a map of property names to their values" );
assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync("GetAll")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(*interfaceName_)
.uponReplyInvoke(std::forward<_Function>(callback));
}
inline std::future<std::map<std::string, Variant>> AsyncAllPropertiesGetter::getResultAsFuture()
{
assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync("GetAll")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(*interfaceName_)
.getResultAsFuture<std::map<std::string, Variant>>();
}
}

View File

@ -76,6 +76,8 @@ namespace sdbus {
};
sdbus::Error createError(int errNo, const std::string& customMsg);
inline const char* SDBUSCPP_ERROR_NAME = "org.sdbuscpp.Error";
}
#define SDBUS_THROW_ERROR(_MSG, _ERRNO) \

View File

@ -34,6 +34,8 @@
#include <cstdint>
#include <optional>
struct sd_bus;
namespace sdbus {
/********************************************//**
@ -266,10 +268,10 @@ namespace sdbus {
virtual void addObjectManager(const std::string& objectPath, floating_slot_t) = 0;
/*!
* @brief Adds a match rule for incoming message dispatching
* @brief Installs a match rule for messages received on this bus connection
*
* @param[in] match Match expression to filter incoming D-Bus message
* @param[in] callback Callback handler to be called upon incoming D-Bus message matching the rule
* @param[in] callback Callback handler to be called upon processing an inbound D-Bus message matching the rule
* @return RAII-style slot handle representing the ownership of the subscription
*
* The method installs a match rule for messages received on the specified bus connection.
@ -289,10 +291,10 @@ namespace sdbus {
[[nodiscard]] virtual Slot addMatch(const std::string& match, message_handler callback) = 0;
/*!
* @brief Adds a floating match rule for incoming message dispatching
* @brief Installs a floating match rule for messages received on this bus connection
*
* @param[in] match Match expression to filter incoming D-Bus message
* @param[in] callback Callback handler to be called upon incoming D-Bus message matching the rule
* @param[in] callback Callback handler to be called upon processing an inbound D-Bus message matching the rule
*
* The method installs a floating match rule for messages received on the specified bus connection.
* Floating means that the bus connection object owns the match rule, i.e. lifetime of the match rule
@ -305,6 +307,45 @@ namespace sdbus {
*/
virtual void addMatch(const std::string& match, message_handler callback, floating_slot_t) = 0;
/*!
* @brief Asynchronously installs a match rule for messages received on this bus connection
*
* @param[in] match Match expression to filter incoming D-Bus message
* @param[in] callback Callback handler to be called upon processing an inbound D-Bus message matching the rule
* @param[in] installCallback Callback handler to be called upon processing an inbound D-Bus message matching the rule
* @return RAII-style slot handle representing the ownership of the subscription
*
* This method operates the same as `addMatch()` above, just that it installs the match rule asynchronously,
* in a non-blocking fashion. A request is sent to the broker, but the call does not wait for a response.
* The `installCallback' callable is called when the response is later received, with the response message
* from the broker as parameter. If it's an empty function object, a default implementation is used that
* terminates the bus connection should installing the match fail.
*
* Refer to the @c addMatch(const std::string& match, message_handler callback) documentation, and consult
* `man sd_bus_add_match`, for more information.
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] virtual Slot addMatchAsync(const std::string& match, message_handler callback, message_handler installCallback) = 0;
/*!
* @brief Asynchronously installs a floating match rule for messages received on this bus connection
*
* @param[in] match Match expression to filter incoming D-Bus message
* @param[in] callback Callback handler to be called upon processing an inbound D-Bus message matching the rule
* @param[in] installCallback Callback handler to be called upon processing an inbound D-Bus message matching the rule
*
* The method installs a floating match rule for messages received on the specified bus connection.
* Floating means that the bus connection object owns the match rule, i.e. lifetime of the match rule
* is bound to the lifetime of the bus connection.
*
* Refer to the @c addMatch(const std::string& match, message_handler callback, message_handler installCallback)
* documentation for more information.
*
* @throws sdbus::Error in case of failure
*/
virtual void addMatchAsync(const std::string& match, message_handler callback, message_handler installCallback, floating_slot_t) = 0;
/*!
* @copydoc IConnection::enterEventLoop()
*
@ -458,6 +499,74 @@ namespace sdbus {
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] std::unique_ptr<sdbus::IConnection> createRemoteSystemBusConnection(const std::string& host);
/*!
* @brief Opens direct D-Bus connection at a custom address
*
* @param[in] address ";"-separated list of addresses of bus brokers to try to connect to
* @return Connection instance
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] std::unique_ptr<sdbus::IConnection> createDirectBusConnection(const std::string& address);
/*!
* @brief Opens direct D-Bus connection at the given file descriptor
*
* @param[in] fd File descriptor used to communicate directly from/to a D-Bus server
* @return Connection instance
*
* The underlying sdbus-c++ connection instance takes over ownership of fd, so the caller can let it go.
* If, however, the call throws an exception, the ownership of fd remains with the caller.
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] std::unique_ptr<sdbus::IConnection> createDirectBusConnection(int fd);
/*!
* @brief Opens direct D-Bus connection at fd as a server
*
* @param[in] fd File descriptor to use for server DBus connection
* @return Server connection instance
*
* This creates a new, custom bus object in server mode. One can then call createDirectBusConnection()
* on client side to connect to this bus.
*
* The underlying sdbus-c++ connection instance takes over ownership of fd, so the caller can let it go.
* If, however, the call throws an exception, the ownership of fd remains with the caller.
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] std::unique_ptr<sdbus::IConnection> createServerBus(int fd);
/*!
* @brief Creates sdbus-c++ bus connection representation out of underlying sd_bus instance
*
* @param[in] bus File descriptor to use for server DBus connection
* @return Connection instance
*
* This functions is helpful in cases where clients need a custom, tweaked configuration of their
* bus object. Since sdbus-c++ does not provide C++ API for all bus connection configuration
* functions of the underlying sd-bus library, clients can use these sd-bus functions themselves
* to create and configure their sd_bus object, and create sdbus-c++ IConnection on top of it.
*
* The IConnection instance assumes unique ownership of the provided bus object. The bus object
* must have been started by the client before this call.
* The bus object will get flushed, closed, and unreffed when the IConnection instance is destroyed.
*
* @throws sdbus::Error in case of failure
*
* Code example:
* @code
* sd_bus* bus{};
* ::sd_bus_new(&bus);
* ::sd_bus_set_address(bus, address);
* ::sd_bus_set_anonymous(bus, true);
* ::sd_bus_start(bus);
* auto con = sdbus::createBusConnection(bus); // IConnection consumes sd_bus object
* @endcode
*/
[[nodiscard]] std::unique_ptr<sdbus::IConnection> createBusConnection(sd_bus *bus);
}
#endif /* SDBUS_CXX_ICONNECTION_H_ */

View File

@ -83,7 +83,7 @@ namespace sdbus {
virtual MethodCall createMethodCall(const std::string& interfaceName, const std::string& methodName) = 0;
/*!
* @brief Calls method on the proxied D-Bus object
* @brief Calls method on the D-Bus object
*
* @param[in] message Message representing a method call
* @param[in] timeout Timeout for dbus call in microseconds
@ -109,7 +109,7 @@ namespace sdbus {
MethodReply callMethod(const MethodCall& message, const std::chrono::duration<_Rep, _Period>& timeout);
/*!
* @brief Calls method on the proxied D-Bus object asynchronously
* @brief Calls method on the D-Bus object asynchronously
*
* @param[in] message Message representing an async method call
* @param[in] asyncReplyCallback Handler for the async reply
@ -133,7 +133,7 @@ namespace sdbus {
PendingAsyncCall 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
* @brief Registers a handler for the desired signal emitted by the D-Bus object
*
* @param[in] interfaceName Name of an interface that the signal belongs to
* @param[in] signalName Name of the signal
@ -178,7 +178,7 @@ namespace sdbus {
virtual void unregister() = 0;
/*!
* @brief Calls method on the proxied D-Bus object
* @brief Calls method on the D-Bus object
*
* @param[in] methodName Name of the method
* @return A helper object for convenient invocation of the method
@ -199,7 +199,7 @@ namespace sdbus {
[[nodiscard]] MethodInvoker callMethod(const std::string& methodName);
/*!
* @brief Calls method on the proxied D-Bus object asynchronously
* @brief Calls method on the D-Bus object asynchronously
*
* @param[in] methodName Name of the method
* @return A helper object for convenient asynchronous invocation of the method
@ -223,7 +223,7 @@ namespace sdbus {
[[nodiscard]] AsyncMethodInvoker callMethodAsync(const std::string& methodName);
/*!
* @brief Registers signal handler for a given signal of the proxied D-Bus object
* @brief Registers signal handler for a given signal of the D-Bus object
*
* @param[in] signalName Name of the signal
* @return A helper object for convenient registration of the signal handler
@ -243,7 +243,7 @@ namespace sdbus {
[[nodiscard]] SignalSubscriber uponSignal(const std::string& signalName);
/*!
* @brief Unregisters signal handler of a given signal of the proxied D-Bus object
* @brief Unregisters signal handler of a given signal of the D-Bus object
*
* @param[in] signalName Name of the signal
* @return A helper object for convenient unregistration of the signal handler
@ -260,7 +260,7 @@ namespace sdbus {
[[nodiscard]] SignalUnsubscriber muteSignal(const std::string& signalName);
/*!
* @brief Gets value of a property of the proxied D-Bus object
* @brief Gets value of a property of the D-Bus object
*
* @param[in] propertyName Name of the property
* @return A helper object for convenient getting of property value
@ -279,23 +279,101 @@ namespace sdbus {
[[nodiscard]] PropertyGetter getProperty(const std::string& propertyName);
/*!
* @brief Sets value of a property of the proxied D-Bus object
* @brief Gets value of a property of the D-Bus object asynchronously
*
* @param[in] propertyName Name of the property
* @return A helper object for convenient asynchronous getting of property value
*
* This is a high-level, convenience way of reading D-Bus property values that abstracts
* from the D-Bus message concept.
*
* Example of use:
* @code
* std::future<sdbus::Variant> state = object.getPropertyAsync("state").onInterface("com.kistler.foo").getResultAsFuture();
* auto callback = [](const sdbus::Error* err, const sdbus::Variant& value){ ... };
* object.getPropertyAsync("state").onInterface("com.kistler.foo").uponReplyInvoke(std::move(callback));
* @endcode
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] AsyncPropertyGetter getPropertyAsync(const std::string& propertyName);
/*!
* @brief Sets value of a property of the D-Bus object
*
* @param[in] propertyName Name of the property
* @return A helper object for convenient setting of property value
*
* This is a high-level, convenience way of writing D-Bus property values that abstracts
* from the D-Bus message concept.
* Setting property value with NoReply flag is also supported.
*
* Example of use:
* @code
* int state = ...;
* object_.setProperty("state").onInterface("com.kistler.foo").toValue(state);
* // Or we can just send the set message call without waiting for the reply
* object_.setProperty("state").onInterface("com.kistler.foo").toValue(state, dont_expect_reply);
* @endcode
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] PropertySetter setProperty(const std::string& propertyName);
/*!
* @brief Sets value of a property of the D-Bus object asynchronously
*
* @param[in] propertyName Name of the property
* @return A helper object for convenient asynchronous setting of property value
*
* This is a high-level, convenience way of writing D-Bus property values that abstracts
* from the D-Bus message concept.
*
* Example of use:
* @code
* int state = ...;
* object_.setProperty("state").onInterface("com.kistler.foo").toValue(state);
* // We can wait until the set operation finishes by waiting on the future
* std::future<void> res = object_.setPropertyAsync("state").onInterface("com.kistler.foo").toValue(state).getResultAsFuture();
* @endcode
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] PropertySetter setProperty(const std::string& propertyName);
[[nodiscard]] AsyncPropertySetter setPropertyAsync(const std::string& propertyName);
/*!
* @brief Gets values of all properties of the D-Bus object
*
* @return A helper object for convenient getting of properties' values
*
* This is a high-level, convenience way of reading D-Bus properties' values that abstracts
* from the D-Bus message concept.
*
* Example of use:
* @code
* auto props = object.getAllProperties().onInterface("com.kistler.foo");
* @endcode
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] AllPropertiesGetter getAllProperties();
/*!
* @brief Gets values of all properties of the D-Bus object asynchronously
*
* @return A helper object for convenient asynchronous getting of properties' values
*
* This is a high-level, convenience way of reading D-Bus properties' values that abstracts
* from the D-Bus message concept.
*
* Example of use:
* @code
* auto callback = [](const sdbus::Error* err, const std::map<std::string, Variant>>& properties){ ... };
* auto props = object.getAllPropertiesAsync().onInterface("com.kistler.foo").uponReplyInvoke(std::move(callback));
* @endcode
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] AsyncAllPropertiesGetter getAllPropertiesAsync();
/*!
* @brief Provides D-Bus connection used by the proxy
@ -326,7 +404,7 @@ namespace sdbus {
virtual const Message* getCurrentlyProcessedMessage() const = 0;
/*!
* @brief Calls method on the proxied D-Bus object asynchronously
* @brief Calls method on the D-Bus object asynchronously
*
* @param[in] message Message representing an async method call
* @param[in] asyncReplyCallback Handler for the async reply
@ -445,11 +523,31 @@ namespace sdbus {
return PropertyGetter(*this, propertyName);
}
inline AsyncPropertyGetter IProxy::getPropertyAsync(const std::string& propertyName)
{
return AsyncPropertyGetter(*this, propertyName);
}
inline PropertySetter IProxy::setProperty(const std::string& propertyName)
{
return PropertySetter(*this, propertyName);
}
inline AsyncPropertySetter IProxy::setPropertyAsync(const std::string& propertyName)
{
return AsyncPropertySetter(*this, propertyName);
}
inline AllPropertiesGetter IProxy::getAllProperties()
{
return AllPropertiesGetter(*this);
}
inline AsyncAllPropertiesGetter IProxy::getAllPropertiesAsync()
{
return AsyncAllPropertiesGetter(*this);
}
/*!
* @brief Creates a proxy object for a specific remote D-Bus object
*
@ -464,6 +562,9 @@ namespace sdbus {
* should make sure that an I/O event loop is running on that connection, so the proxy
* may receive incoming signals and asynchronous method replies.
*
* The destination parameter may be an empty string (useful e.g. in case of direct
* D-Bus connections to a custom server bus).
*
* Code example:
* @code
* auto proxy = sdbus::createProxy(connection, "com.kistler.foo", "/com/kistler/foo");
@ -487,6 +588,9 @@ namespace sdbus {
* upon that connection in a separate internal thread. Handlers for incoming signals and
* asynchronous method replies will be executed in the context of that thread.
*
* The destination parameter may be an empty string (useful e.g. in case of direct
* D-Bus connections to a custom server bus).
*
* Code example:
* @code
* auto proxy = sdbus::createProxy(std::move(connection), "com.kistler.foo", "/com/kistler/foo");
@ -505,10 +609,14 @@ namespace sdbus {
* @return Pointer to the object proxy instance
*
* The provided connection will be used by the proxy to issue calls against the object.
* The Object proxy becomes an exclusive owner of this connection, but will not start an event loop
* thread on this connection. This is cheap construction and is suitable for short-lived proxies
* created just to execute simple synchronous D-Bus calls and then destroyed. Such blocking request-reply
* calls will work without an event loop (but signals, async calls, etc. won't).
* The Object proxy becomes an exclusive owner of this connection, but will not start
* an event loop thread on this connection. This is cheap construction and is suitable
* for short-lived proxies created just to execute simple synchronous D-Bus calls and
* then destroyed. Such blocking request-reply calls will work without an event loop
* (but signals, async calls, etc. won't).
*
* The destination parameter may be an empty string (useful e.g. in case of direct
* D-Bus connections to a custom server bus).
*
* Code example:
* @code

View File

@ -42,6 +42,7 @@
#include <cassert>
#include <functional>
#include <sys/types.h>
#include <algorithm>
// Forward declarations
namespace sdbus {

View File

@ -138,16 +138,52 @@ namespace sdbus {
return proxy_->getProperty(propertyName).onInterface(interfaceName);
}
template <typename _Function>
PendingAsyncCall GetAsync(const std::string& interfaceName, const std::string& propertyName, _Function&& callback)
{
return proxy_->getPropertyAsync(propertyName).onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback));
}
std::future<sdbus::Variant> GetAsync(const std::string& interfaceName, const std::string& propertyName, with_future_t)
{
return proxy_->getPropertyAsync(propertyName).onInterface(interfaceName).getResultAsFuture();
}
void Set(const std::string& interfaceName, const std::string& propertyName, const sdbus::Variant& value)
{
proxy_->setProperty(propertyName).onInterface(interfaceName).toValue(value);
}
void Set(const std::string& interfaceName, const std::string& propertyName, const sdbus::Variant& value, dont_expect_reply_t)
{
proxy_->setProperty(propertyName).onInterface(interfaceName).toValue(value, dont_expect_reply);
}
template <typename _Function>
PendingAsyncCall SetAsync(const std::string& interfaceName, const std::string& propertyName, const sdbus::Variant& value, _Function&& callback)
{
return proxy_->setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).uponReplyInvoke(std::forward<_Function>(callback));
}
std::future<void> SetAsync(const std::string& interfaceName, const std::string& propertyName, const sdbus::Variant& value, with_future_t)
{
return proxy_->setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).getResultAsFuture();
}
std::map<std::string, sdbus::Variant> GetAll(const std::string& interfaceName)
{
std::map<std::string, sdbus::Variant> props;
proxy_->callMethod("GetAll").onInterface(INTERFACE_NAME).withArguments(interfaceName).storeResultsTo(props);
return props;
return proxy_->getAllProperties().onInterface(interfaceName);
}
template <typename _Function>
PendingAsyncCall GetAllAsync(const std::string& interfaceName, _Function&& callback)
{
return proxy_->getAllPropertiesAsync().onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback));
}
std::future<std::map<std::string, sdbus::Variant>> GetAllAsync(const std::string& interfaceName, with_future_t)
{
return proxy_->getAllPropertiesAsync().onInterface(interfaceName).getResultAsFuture();
}
private:

View File

@ -93,6 +93,9 @@ namespace sdbus {
// Tag denoting an asynchronous call that returns std::future as a handle
struct with_future_t { explicit with_future_t() = default; };
inline constexpr with_future_t with_future{};
// Tag denoting a call where the reply shouldn't be waited for
struct dont_expect_reply_t { explicit dont_expect_reply_t() = default; };
inline constexpr dont_expect_reply_t dont_expect_reply{};
// Template specializations for getting D-Bus signatures from C++ types
template <typename _T>

View File

@ -34,7 +34,7 @@
#include <typeinfo>
#include <memory>
#include <tuple>
#include <unistd.h>
#include <utility>
namespace sdbus {
@ -56,7 +56,7 @@ namespace sdbus {
Variant();
template <typename _ValueType>
/*explicit*/ Variant(const _ValueType& value) // TODO: Mark explicit in new major version so we don't break client code within v1
Variant(const _ValueType& value)
: Variant()
{
msg_.openVariant(signature_of<_ValueType>::str());
@ -209,7 +209,7 @@ namespace sdbus {
UnixFd() = default;
explicit UnixFd(int fd)
: fd_(::dup(fd))
: fd_(checkedDup(fd))
{
}
@ -225,8 +225,12 @@ namespace sdbus {
UnixFd& operator=(const UnixFd& other)
{
if (this == &other)
{
return *this;
}
close();
fd_ = ::dup(other.fd_);
fd_ = checkedDup(other.fd_);
return *this;
}
@ -237,9 +241,12 @@ namespace sdbus {
UnixFd& operator=(UnixFd&& other)
{
if (this == &other)
{
return *this;
}
close();
fd_ = other.fd_;
other.fd_ = -1;
fd_ = std::exchange(other.fd_, -1);
return *this;
}
@ -265,9 +272,7 @@ namespace sdbus {
int release()
{
auto fd = fd_;
fd_ = -1;
return fd;
return std::exchange(fd_, -1);
}
bool isValid() const
@ -276,11 +281,12 @@ namespace sdbus {
}
private:
void close()
{
if (fd_ >= 0)
::close(fd_);
}
/// Closes file descriptor, but does not set it to -1.
void close();
/// Returns negative argument unchanged.
/// Otherwise, call ::dup and throw on failure.
static int checkedDup(int fd);
int fd_ = -1;
};

View File

@ -5,7 +5,7 @@ includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@
Name: @PROJECT_NAME@
Description: C++ library on top of sd-bus, a systemd D-Bus library
Requires@PKGCONFIG_REQS@: @LIBSYSTEMD@
Requires@PKGCONFIG_REQS@: @PKGCONFIG_DEPS@
Version: @SDBUSCPP_VERSION@
Libs: -L${libdir} -l@PROJECT_NAME@
Cflags: -I${includedir}

View File

@ -31,7 +31,7 @@
#include <sdbus-c++/Message.h>
#include <sdbus-c++/Error.h>
#include "ScopeGuard.h"
#include <systemd/sd-bus.h>
#include SDBUS_HEADER
#include <unistd.h>
#include <poll.h>
#include <sys/eventfd.h>
@ -70,6 +70,26 @@ Connection::Connection(std::unique_ptr<ISdBus>&& interface, remote_system_bus_t,
{
}
Connection::Connection(std::unique_ptr<ISdBus>&& interface, private_bus_t, const std::string& address)
: Connection(std::move(interface), [&](sd_bus** bus) { return iface_->sd_bus_open_direct(bus, address.c_str()); })
{
}
Connection::Connection(std::unique_ptr<ISdBus>&& interface, private_bus_t, int fd)
: Connection(std::move(interface), [&](sd_bus** bus) { return iface_->sd_bus_open_direct(bus, fd); })
{
}
Connection::Connection(std::unique_ptr<ISdBus>&& interface, server_bus_t, int fd)
: Connection(std::move(interface), [&](sd_bus** bus) { return iface_->sd_bus_open_server(bus, fd); })
{
}
Connection::Connection(std::unique_ptr<ISdBus>&& interface, sdbus_bus_t, sd_bus *bus)
: Connection(std::move(interface), [&](sd_bus** b) { *b = bus; return 0; })
{
}
Connection::Connection(std::unique_ptr<ISdBus>&& interface, pseudo_bus_t)
: iface_(std::move(interface))
, bus_(openPseudoBus())
@ -88,12 +108,20 @@ void Connection::requestName(const std::string& name)
auto r = iface_->sd_bus_request_name(bus_.get(), name.c_str(), 0);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to request bus name", -r);
// In some cases we need to explicitly notify the event loop
// to process messages that may have arrived while executing the call.
notifyEventLoop(eventFd_.fd);
}
void Connection::releaseName(const std::string& name)
{
auto r = iface_->sd_bus_release_name(bus_.get(), name.c_str());
SDBUS_THROW_ERROR_IF(r < 0, "Failed to release bus name", -r);
// In some cases we need to explicitly notify the event loop
// to process messages that may have arrived while executing the call.
notifyEventLoop(eventFd_.fd);
}
std::string Connection::getUniqueName() const
@ -197,17 +225,11 @@ uint64_t Connection::getMethodCallTimeout() const
Slot Connection::addMatch(const std::string& match, message_handler callback)
{
auto matchInfo = std::make_unique<MatchInfo>(MatchInfo{std::move(callback), *this, {}});
SDBUS_THROW_ERROR_IF(!callback, "Invalid match callback handler provided", EINVAL);
auto messageHandler = [](sd_bus_message *sdbusMessage, void *userData, sd_bus_error */*retError*/) -> int
{
auto* matchInfo = static_cast<MatchInfo*>(userData);
auto message = Message::Factory::create<PlainMessage>(sdbusMessage, &matchInfo->connection.getSdBusInterface());
matchInfo->callback(message);
return 0;
};
auto matchInfo = std::make_unique<MatchInfo>(MatchInfo{std::move(callback), {}, *this, {}});
auto r = iface_->sd_bus_add_match(bus_.get(), &matchInfo->slot, match.c_str(), std::move(messageHandler), matchInfo.get());
auto r = iface_->sd_bus_add_match(bus_.get(), &matchInfo->slot, match.c_str(), &Connection::sdbus_match_callback, matchInfo.get());
SDBUS_THROW_ERROR_IF(r < 0, "Failed to add match", -r);
return {matchInfo.release(), [this](void *ptr)
@ -223,6 +245,34 @@ void Connection::addMatch(const std::string& match, message_handler callback, fl
floatingMatchRules_.push_back(addMatch(match, std::move(callback)));
}
Slot Connection::addMatchAsync(const std::string& match, message_handler callback, message_handler installCallback)
{
SDBUS_THROW_ERROR_IF(!callback, "Invalid match callback handler provided", EINVAL);
sd_bus_message_handler_t sdbusInstallCallback = installCallback ? &Connection::sdbus_match_install_callback : nullptr;
auto matchInfo = std::make_unique<MatchInfo>(MatchInfo{std::move(callback), std::move(installCallback), *this, {}});
auto r = iface_->sd_bus_add_match_async( bus_.get()
, &matchInfo->slot
, match.c_str()
, &Connection::sdbus_match_callback
, sdbusInstallCallback
, matchInfo.get());
SDBUS_THROW_ERROR_IF(r < 0, "Failed to add match", -r);
return {matchInfo.release(), [this](void *ptr)
{
auto* matchInfo = static_cast<MatchInfo*>(ptr);
iface_->sd_bus_slot_unref(matchInfo->slot);
std::default_delete<MatchInfo>{}(matchInfo);
}};
}
void Connection::addMatchAsync(const std::string& match, message_handler callback, message_handler installCallback, floating_slot_t)
{
floatingMatchRules_.push_back(addMatchAsync(match, std::move(callback), std::move(installCallback)));
}
Slot Connection::addObjectVTable( const std::string& objectPath
, const std::string& interfaceName
, const sd_bus_vtable* vtable
@ -262,7 +312,7 @@ MethodCall Connection::createMethodCall( const std::string& destination
auto r = iface_->sd_bus_message_new_method_call( bus_.get()
, &sdbusMsg
, destination.c_str()
, destination.empty() ? nullptr : destination.c_str()
, objectPath.c_str()
, interfaceName.c_str()
, methodName.c_str() );
@ -527,7 +577,8 @@ std::string Connection::composeSignalMatchFilter( const std::string &sender
std::string filter;
filter += "type='signal',";
filter += "sender='" + sender + "',";
if (!sender.empty())
filter += "sender='" + sender + "',";
filter += "interface='" + interfaceName + "',";
filter += "member='" + signalName + "',";
filter += "path='" + objectPath + "'";
@ -544,6 +595,22 @@ std::vector</*const */char*> Connection::to_strv(const std::vector<std::string>&
return strv;
}
int Connection::sdbus_match_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError)
{
auto* matchInfo = static_cast<MatchInfo*>(userData);
auto message = Message::Factory::create<PlainMessage>(sdbusMessage, &matchInfo->connection.getSdBusInterface());
auto ok = invokeHandlerAndCatchErrors([&](){ matchInfo->callback(message); }, retError);
return ok ? 0 : -1;
}
int Connection::sdbus_match_install_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError)
{
auto* matchInfo = static_cast<MatchInfo*>(userData);
auto message = Message::Factory::create<PlainMessage>(sdbusMessage, &matchInfo->connection.getSdBusInterface());
auto ok = invokeHandlerAndCatchErrors([&](){ matchInfo->installCallback(message); }, retError);
return ok ? 0 : -1;
}
Connection::EventFd::EventFd()
{
fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
@ -661,7 +728,6 @@ std::unique_ptr<sdbus::IConnection> createSessionBusConnection(const std::string
std::unique_ptr<sdbus::IConnection> createSessionBusConnectionWithAddress(const std::string &address)
{
auto interface = std::make_unique<sdbus::internal::SdBus>();
assert(interface != nullptr);
return std::make_unique<sdbus::internal::Connection>(std::move(interface), Connection::custom_session_bus, address);
}
@ -671,4 +737,30 @@ std::unique_ptr<sdbus::IConnection> createRemoteSystemBusConnection(const std::s
return std::make_unique<sdbus::internal::Connection>(std::move(interface), Connection::remote_system_bus, host);
}
std::unique_ptr<sdbus::IConnection> createDirectBusConnection(const std::string& address)
{
auto interface = std::make_unique<sdbus::internal::SdBus>();
return std::make_unique<sdbus::internal::Connection>(std::move(interface), Connection::private_bus, address);
}
std::unique_ptr<sdbus::IConnection> createDirectBusConnection(int fd)
{
auto interface = std::make_unique<sdbus::internal::SdBus>();
return std::make_unique<sdbus::internal::Connection>(std::move(interface), Connection::private_bus, fd);
}
std::unique_ptr<sdbus::IConnection> createServerBus(int fd)
{
auto interface = std::make_unique<sdbus::internal::SdBus>();
return std::make_unique<sdbus::internal::Connection>(std::move(interface), Connection::server_bus, fd);
}
std::unique_ptr<sdbus::IConnection> createBusConnection(sd_bus *bus)
{
SDBUS_THROW_ERROR_IF(bus == nullptr, "Invalid bus argument", EINVAL);
auto interface = std::make_unique<sdbus::internal::SdBus>();
return std::make_unique<sdbus::internal::Connection>(std::move(interface), Connection::sdbus_bus, bus);
}
} // namespace sdbus

View File

@ -32,7 +32,7 @@
#include "IConnection.h"
#include "ScopeGuard.h"
#include "ISdBus.h"
#include <systemd/sd-bus.h>
#include SDBUS_HEADER
#include <memory>
#include <thread>
#include <string>
@ -57,6 +57,12 @@ namespace sdbus::internal {
inline static constexpr custom_session_bus_t custom_session_bus{};
struct remote_system_bus_t{};
inline static constexpr remote_system_bus_t remote_system_bus{};
struct private_bus_t{};
inline static constexpr private_bus_t private_bus{};
struct server_bus_t{};
inline static constexpr server_bus_t server_bus{};
struct sdbus_bus_t{}; // A bus connection created directly from existing sd_bus instance
inline static constexpr sdbus_bus_t sdbus_bus{};
struct pseudo_bus_t{}; // A bus connection that is not really established with D-Bus daemon
inline static constexpr pseudo_bus_t pseudo_bus{};
@ -65,6 +71,10 @@ namespace sdbus::internal {
Connection(std::unique_ptr<ISdBus>&& interface, session_bus_t);
Connection(std::unique_ptr<ISdBus>&& interface, custom_session_bus_t, const std::string& address);
Connection(std::unique_ptr<ISdBus>&& interface, remote_system_bus_t, const std::string& host);
Connection(std::unique_ptr<ISdBus>&& interface, private_bus_t, const std::string& address);
Connection(std::unique_ptr<ISdBus>&& interface, private_bus_t, int fd);
Connection(std::unique_ptr<ISdBus>&& interface, server_bus_t, int fd);
Connection(std::unique_ptr<ISdBus>&& interface, sdbus_bus_t, sd_bus *bus);
Connection(std::unique_ptr<ISdBus>&& interface, pseudo_bus_t);
~Connection() override;
@ -86,6 +96,8 @@ namespace sdbus::internal {
[[nodiscard]] Slot addMatch(const std::string& match, message_handler callback) override;
void addMatch(const std::string& match, message_handler callback, floating_slot_t) override;
[[nodiscard]] Slot addMatchAsync(const std::string& match, message_handler callback, message_handler installCallback) override;
void addMatchAsync(const std::string& match, message_handler callback, message_handler installCallback, floating_slot_t) override;
const ISdBus& getSdBusInterface() const override;
ISdBus& getSdBusInterface() override;
@ -141,10 +153,13 @@ namespace sdbus::internal {
void clearEventLoopNotification(int fd) const;
void notifyEventLoopNewTimeout() const override;
private:
void joinWithEventLoop();
static std::vector</*const */char*> to_strv(const std::vector<std::string>& strings);
static int sdbus_match_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError);
static int sdbus_match_install_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError);
private:
struct EventFd
{
EventFd();
@ -155,6 +170,7 @@ namespace sdbus::internal {
struct MatchInfo
{
message_handler callback;
message_handler installCallback;
Connection& connection;
sd_bus_slot *slot;
};

View File

@ -25,7 +25,7 @@
*/
#include <sdbus-c++/Error.h>
#include <systemd/sd-bus.h>
#include SDBUS_HEADER
#include "ScopeGuard.h"
namespace sdbus

View File

@ -25,7 +25,7 @@
*/
#include <sdbus-c++/Flags.h>
#include <systemd/sd-bus.h>
#include SDBUS_HEADER
namespace sdbus
{

View File

@ -28,7 +28,7 @@
#define SDBUS_CXX_INTERNAL_ICONNECTION_H_
#include <sdbus-c++/IConnection.h>
#include <systemd/sd-bus.h>
#include SDBUS_HEADER
#include <string>
#include <memory>
#include <functional>

View File

@ -28,7 +28,7 @@
#ifndef SDBUS_CXX_ISDBUS_H
#define SDBUS_CXX_ISDBUS_H
#include <systemd/sd-bus.h>
#include SDBUS_HEADER
namespace sdbus::internal {
@ -71,12 +71,16 @@ namespace sdbus::internal {
virtual int sd_bus_open_user(sd_bus **ret) = 0;
virtual int sd_bus_open_user_with_address(sd_bus **ret, const char* address) = 0;
virtual int sd_bus_open_system_remote(sd_bus **ret, const char* host) = 0;
virtual int sd_bus_open_direct(sd_bus **ret, const char* address) = 0;
virtual int sd_bus_open_direct(sd_bus **ret, int fd) = 0;
virtual int sd_bus_open_server(sd_bus **ret, int fd) = 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;
virtual int sd_bus_add_match_async(sd_bus *bus, sd_bus_slot **slot, const char *match, sd_bus_message_handler_t callback, sd_bus_message_handler_t install_callback, void *userdata) = 0;
virtual sd_bus_slot* sd_bus_slot_unref(sd_bus_slot *slot) = 0;
virtual int sd_bus_new(sd_bus **ret) = 0;

View File

@ -31,7 +31,7 @@
#include "ISdBus.h"
#include "IConnection.h"
#include "ScopeGuard.h"
#include <systemd/sd-bus.h>
#include SDBUS_HEADER
#include <cassert>
namespace sdbus {

View File

@ -35,7 +35,7 @@
#include "IConnection.h"
#include "Utils.h"
#include "VTableUtils.h"
#include <systemd/sd-bus.h>
#include SDBUS_HEADER
#include <utility>
#include <cassert>
@ -347,16 +347,9 @@ int Object::sdbus_method_callback(sd_bus_message *sdbusMessage, void *userData,
auto& callback = interfaceData->methods[message.getMemberName()].callback;
assert(callback);
try
{
callback(message);
}
catch (const Error& e)
{
sd_bus_error_set(retError, e.getName().c_str(), e.getMessage().c_str());
}
auto ok = invokeHandlerAndCatchErrors([&](){ callback(message); }, retError);
return 1;
return ok ? 1 : -1;
}
int Object::sdbus_property_get_callback( sd_bus */*bus*/
@ -381,16 +374,9 @@ int Object::sdbus_property_get_callback( sd_bus */*bus*/
auto reply = Message::Factory::create<PropertyGetReply>(sdbusReply, &object.connection_.getSdBusInterface());
try
{
callback(reply);
}
catch (const Error& e)
{
sd_bus_error_set(retError, e.getName().c_str(), e.getMessage().c_str());
}
auto ok = invokeHandlerAndCatchErrors([&](){ callback(reply); }, retError);
return 1;
return ok ? 1 : -1;
}
int Object::sdbus_property_set_callback( sd_bus */*bus*/
@ -416,16 +402,9 @@ int Object::sdbus_property_set_callback( sd_bus */*bus*/
object.m_CurrentlyProcessedMessage.store(nullptr, std::memory_order_relaxed);
};
try
{
callback(value);
}
catch (const Error& e)
{
sd_bus_error_set(retError, e.getName().c_str(), e.getMessage().c_str());
}
auto ok = invokeHandlerAndCatchErrors([&](){ callback(value); }, retError);
return 1;
return ok ? 1 : -1;
}
}

View File

@ -29,7 +29,7 @@
#include <sdbus-c++/IObject.h>
#include "IConnection.h"
#include <systemd/sd-bus.h>
#include SDBUS_HEADER
#include <string>
#include <map>
#include <vector>

View File

@ -32,7 +32,7 @@
#include "sdbus-c++/IConnection.h"
#include "sdbus-c++/Error.h"
#include "ScopeGuard.h"
#include <systemd/sd-bus.h>
#include SDBUS_HEADER
#include <cassert>
#include <chrono>
#include <utility>
@ -120,7 +120,7 @@ PendingAsyncCall Proxy::callMethod(const MethodCall& message, async_reply_handle
SDBUS_THROW_ERROR_IF(!message.isValid(), "Invalid async method call message provided", EINVAL);
auto callback = (void*)&Proxy::sdbus_async_reply_handler;
auto callData = std::make_shared<AsyncCalls::CallData>(AsyncCalls::CallData{*this, std::move(asyncReplyCallback), {}});
auto callData = std::make_shared<AsyncCalls::CallData>(AsyncCalls::CallData{*this, std::move(asyncReplyCallback), {}, AsyncCalls::CallData::State::RUNNING});
auto weakData = std::weak_ptr<AsyncCalls::CallData>{callData};
callData->slot = message.send(callback, callData.get(), timeout);
@ -143,7 +143,7 @@ std::future<MethodReply> Proxy::callMethod(const MethodCall& message, uint64_t t
async_reply_handler asyncReplyCallback = [promise = std::move(promise)](MethodReply& reply, const Error* error) noexcept
{
if (error == nullptr)
promise->set_value(reply); // TODO: std::move? Can't move now because currently processed message. TODO: Refactor
promise->set_value(reply);
else
promise->set_exception(std::make_exception_ptr(*error));
};
@ -162,7 +162,7 @@ MethodReply Proxy::sendMethodCallMessageAndWaitForReply(const MethodCall& messag
syncCallReplyData.sendMethodReplyToWaitingThread(reply, error);
};
auto callback = (void*)&Proxy::sdbus_async_reply_handler;
AsyncCalls::CallData callData{*this, std::move(asyncReplyCallback), {}};
AsyncCalls::CallData callData{*this, std::move(asyncReplyCallback), {}, AsyncCalls::CallData::State::NOT_ASYNC};
message.send(callback, &callData, timeout, floating_slot);
@ -269,20 +269,22 @@ const Message* Proxy::getCurrentlyProcessedMessage() const
return m_CurrentlyProcessedMessage.load(std::memory_order_relaxed);
}
int Proxy::sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error */*retError*/)
int Proxy::sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError)
{
auto* asyncCallData = static_cast<AsyncCalls::CallData*>(userData);
assert(asyncCallData != nullptr);
assert(asyncCallData->callback);
auto& proxy = asyncCallData->proxy;
auto state = asyncCallData->state;
// We are removing the CallData item at the complete scope exit, after the callback has been invoked.
// We can't do it earlier (before callback invocation for example), because CallBack data (slot release)
// is the synchronization point between callback invocation and Proxy::unregister.
SCOPE_EXIT
{
// Remove call meta-data if it's a real async call (a sync call done in terms of async has slot == nullptr)
proxy.pendingAsyncCalls_.removeCall(asyncCallData);
// Remove call meta-data if it's a real async call (a sync call done in terms of async has STATE_NOT_ASYNC)
if (state != AsyncCalls::CallData::State::NOT_ASYNC)
proxy.pendingAsyncCalls_.removeCall(asyncCallData);
};
auto message = Message::Factory::create<MethodReply>(sdbusMessage, &proxy.connection_->getSdBusInterface());
@ -293,7 +295,7 @@ int Proxy::sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userDat
proxy.m_CurrentlyProcessedMessage.store(nullptr, std::memory_order_relaxed);
};
try
auto ok = invokeHandlerAndCatchErrors([&]
{
const auto* error = sd_bus_message_get_error(sdbusMessage);
if (error == nullptr)
@ -305,16 +307,12 @@ int Proxy::sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userDat
Error exception(error->name, error->message);
asyncCallData->callback(message, &exception);
}
}
catch (const Error&)
{
// Intentionally left blank -- sdbus-c++ exceptions shall not bubble up to the underlying C sd-bus library
}
}, retError);
return 0;
return ok ? 0 : -1;
}
int Proxy::sdbus_signal_handler(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* signalData = static_cast<InterfaceData::SignalData*>(userData);
assert(signalData != nullptr);
@ -328,16 +326,9 @@ int Proxy::sdbus_signal_handler(sd_bus_message *sdbusMessage, void *userData, sd
signalData->proxy.m_CurrentlyProcessedMessage.store(nullptr, std::memory_order_relaxed);
};
try
{
signalData->callback(message);
}
catch (const Error&)
{
// Intentionally left blank -- sdbus-c++ exceptions shall not bubble up to the underlying C sd-bus library
}
auto ok = invokeHandlerAndCatchErrors([&](){ signalData->callback(message); }, retError);
return 0;
return ok ? 0 : -1;
}
}

View File

@ -29,7 +29,7 @@
#include <sdbus-c++/IProxy.h>
#include "IConnection.h"
#include <systemd/sd-bus.h>
#include SDBUS_HEADER
#include <string>
#include <memory>
#include <map>
@ -135,10 +135,16 @@ namespace sdbus::internal {
public:
struct CallData
{
enum class State
{ NOT_ASYNC
, RUNNING
, FINISHED
};
Proxy& proxy;
async_reply_handler callback;
Slot slot;
bool finished { false };
State state;
};
~AsyncCalls()
@ -149,14 +155,14 @@ namespace sdbus::internal {
void addCall(std::shared_ptr<CallData> asyncCallData)
{
std::lock_guard lock(mutex_);
if (!asyncCallData->finished) // The call may have finished in the mean time
if (asyncCallData->state != CallData::State::FINISHED) // The call may have finished in the meantime
calls_.emplace_back(std::move(asyncCallData));
}
void removeCall(CallData* data)
{
std::unique_lock lock(mutex_);
data->finished = true;
data->state = CallData::State::FINISHED;
if (auto it = std::find_if(calls_.begin(), calls_.end(), [data](auto const& entry){ return entry.get() == data; }); it != calls_.end())
{
auto callData = std::move(*it);

View File

@ -55,62 +55,104 @@
// return; // exiting scope normally
// }
#define SCOPE_EXIT \
auto ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE) \
= ::sdbus::internal::detail::ScopeGuardOnExit() + [&]() \
#define SCOPE_EXIT \
auto ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE) \
= ::sdbus::internal::ScopeGuardOnExitTag{} + [&]() \
/**/
#define SCOPE_EXIT_NAMED(NAME) \
auto NAME \
= ::sdbus::internal::detail::ScopeGuardOnExit() + [&]() \
#define SCOPE_EXIT_NAMED(NAME) \
auto NAME \
= ::sdbus::internal::ScopeGuardOnExitTag{} + [&]() \
/**/
#define SCOPE_EXIT_SUCCESS \
auto ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE) \
= ::sdbus::internal::ScopeGuardOnExitSuccessTag{} + [&]() \
/**/
#define SCOPE_EXIT_SUCCESS_NAMED(NAME) \
auto NAME \
= ::sdbus::internal::ScopeGuardOnExitSuccessTag{} + [&]() \
/**/
#define SCOPE_EXIT_FAILURE \
auto ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE) \
= ::sdbus::internal::ScopeGuardOnExitFailureTag{} + [&]() \
/**/
#define SCOPE_EXIT_FAILURE_NAMED(NAME) \
auto NAME \
= ::sdbus::internal::ScopeGuardOnExitFailureTag{} + [&]() \
/**/
namespace sdbus::internal {
template <class _Fun>
struct ScopeGuardOnExitTag
{
static bool holds(int /*originalExceptions*/)
{
return true; // Always holds
}
};
struct ScopeGuardOnExitSuccessTag
{
static bool holds(int originalExceptions)
{
return originalExceptions == std::uncaught_exceptions(); // Only holds when no exception occurred within the scope
}
};
struct ScopeGuardOnExitFailureTag
{
static bool holds(int originalExceptions)
{
return originalExceptions != std::uncaught_exceptions(); // Only holds when an exception occurred within the scope
}
};
template <class _Fun, typename _Tag>
class ScopeGuard
{
_Fun fnc_;
bool active_;
public:
ScopeGuard(_Fun f)
: fnc_(std::move(f))
, active_(true)
ScopeGuard(_Fun f) : fnc_(std::move(f))
{
}
~ScopeGuard()
ScopeGuard() = delete;
ScopeGuard(const ScopeGuard&) = delete;
ScopeGuard& operator=(const ScopeGuard&) = delete;
ScopeGuard(ScopeGuard&& rhs) : fnc_(std::move(rhs.fnc_)), active_(rhs.active_), exceptions_(rhs.exceptions_)
{
if (active_)
fnc_();
rhs.dismiss();
}
void dismiss()
{
active_ = false;
}
ScopeGuard() = delete;
ScopeGuard(const ScopeGuard&) = delete;
ScopeGuard& operator=(const ScopeGuard&) = delete;
ScopeGuard(ScopeGuard&& rhs)
: fnc_(std::move(rhs.fnc_))
, active_(rhs.active_)
~ScopeGuard()
{
rhs.dismiss();
if (active_ && _Tag::holds(exceptions_))
fnc_();
}
private:
_Fun fnc_;
int exceptions_{std::uncaught_exceptions()};
bool active_{true};
};
namespace detail
template <typename _Fun>
ScopeGuard<_Fun, ScopeGuardOnExitTag> operator+(ScopeGuardOnExitTag, _Fun&& fnc)
{
enum class ScopeGuardOnExit
{
};
return ScopeGuard<_Fun, ScopeGuardOnExitTag>(std::forward<_Fun>(fnc));
}
// Helper function to auto-deduce type of the callable entity
template <typename _Fun>
ScopeGuard<_Fun> operator+(ScopeGuardOnExit, _Fun&& fnc)
{
return ScopeGuard<_Fun>(std::forward<_Fun>(fnc));
}
template <typename _Fun>
ScopeGuard<_Fun, ScopeGuardOnExitSuccessTag> operator+(ScopeGuardOnExitSuccessTag, _Fun&& fnc)
{
return ScopeGuard<_Fun, ScopeGuardOnExitSuccessTag>(std::forward<_Fun>(fnc));
}
template <typename _Fun>
ScopeGuard<_Fun, ScopeGuardOnExitFailureTag> operator+(ScopeGuardOnExitFailureTag, _Fun&& fnc)
{
return ScopeGuard<_Fun, ScopeGuardOnExitFailureTag>(std::forward<_Fun>(fnc));
}
}
@ -119,13 +161,9 @@ namespace sdbus::internal {
#define CONCATENATE(s1, s2) CONCATENATE_IMPL(s1, s2)
#ifdef __COUNTER__
#define ANONYMOUS_VARIABLE(str) \
CONCATENATE(str, __COUNTER__) \
/**/
#define ANONYMOUS_VARIABLE(str) CONCATENATE(str, __COUNTER__)
#else
#define ANONYMOUS_VARIABLE(str) \
CONCATENATE(str, __LINE__) \
/**/
#define ANONYMOUS_VARIABLE(str) CONCATENATE(str, __LINE__)
#endif
#endif /* SDBUS_CPP_INTERNAL_SCOPEGUARD_H_ */

View File

@ -194,26 +194,98 @@ int SdBus::sd_bus_open_user_with_address(sd_bus **ret, const char* address)
{
sd_bus* bus = nullptr;
int r = sd_bus_new(&bus);
int r = ::sd_bus_new(&bus);
if (r < 0)
return r;
r = sd_bus_set_address(bus, address);
r = ::sd_bus_set_address(bus, address);
if (r < 0)
return r;
r = sd_bus_set_bus_client(bus, true);
r = ::sd_bus_set_bus_client(bus, true);
if (r < 0)
return r;
// Copying behavior from
// https://github.com/systemd/systemd/blob/fee6441601c979165ebcbb35472036439f8dad5f/src/libsystemd/sd-bus/sd-bus.c#L1381
// Here, we make the bus as trusted
r = sd_bus_set_trusted(bus, true);
r = ::sd_bus_set_trusted(bus, true);
if (r < 0)
return r;
r = sd_bus_start(bus);
r = ::sd_bus_start(bus);
if (r < 0)
return r;
*ret = bus;
return 0;
}
int SdBus::sd_bus_open_direct(sd_bus **ret, const char* address)
{
sd_bus* bus = nullptr;
int r = ::sd_bus_new(&bus);
if (r < 0)
return r;
r = ::sd_bus_set_address(bus, address);
if (r < 0)
return r;
r = ::sd_bus_start(bus);
if (r < 0)
return r;
*ret = bus;
return 0;
}
int SdBus::sd_bus_open_direct(sd_bus **ret, int fd)
{
sd_bus* bus = nullptr;
int r = ::sd_bus_new(&bus);
if (r < 0)
return r;
r = ::sd_bus_set_fd(bus, fd, fd);
if (r < 0)
return r;
r = ::sd_bus_start(bus);
if (r < 0)
return r;
*ret = bus;
return 0;
}
int SdBus::sd_bus_open_server(sd_bus **ret, int fd)
{
sd_bus* bus = nullptr;
int r = ::sd_bus_new(&bus);
if (r < 0)
return r;
r = ::sd_bus_set_fd(bus, fd, fd);
if (r < 0)
return r;
sd_id128_t id;
r = ::sd_id128_randomize(&id);
if (r < 0)
return r;
r = ::sd_bus_set_server(bus, true, id);
if (r < 0)
return r;
r = ::sd_bus_start(bus);
if (r < 0)
return r;
@ -224,7 +296,12 @@ int SdBus::sd_bus_open_user_with_address(sd_bus **ret, const char* address)
int SdBus::sd_bus_open_system_remote(sd_bus **ret, const char *host)
{
#ifdef SDBUS_basu
// https://git.sr.ht/~emersion/basu/commit/01d33b244eb6
return -EOPNOTSUPP;
#else
return ::sd_bus_open_system_remote(ret, host);
#endif
}
int SdBus::sd_bus_request_name(sd_bus *bus, const char *name, uint64_t flags)
@ -265,7 +342,14 @@ int SdBus::sd_bus_add_match(sd_bus *bus, sd_bus_slot **slot, const char *match,
{
std::lock_guard lock(sdbusMutex_);
return :: sd_bus_add_match(bus, slot, match, callback, userdata);
return ::sd_bus_add_match(bus, slot, match, callback, userdata);
}
int SdBus::sd_bus_add_match_async(sd_bus *bus, sd_bus_slot **slot, const char *match, sd_bus_message_handler_t callback, sd_bus_message_handler_t install_callback, void *userdata)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_add_match_async(bus, slot, match, callback, install_callback, userdata);
}
sd_bus_slot* SdBus::sd_bus_slot_unref(sd_bus_slot *slot)

View File

@ -63,12 +63,16 @@ public:
virtual int sd_bus_open_user(sd_bus **ret) override;
virtual int sd_bus_open_user_with_address(sd_bus **ret, const char* address) override;
virtual int sd_bus_open_system_remote(sd_bus **ret, const char* hsot) override;
virtual int sd_bus_open_direct(sd_bus **ret, const char* address) override;
virtual int sd_bus_open_direct(sd_bus **ret, int fd) override;
virtual int sd_bus_open_server(sd_bus **ret, int fd) 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;
virtual int sd_bus_add_match_async(sd_bus *bus, sd_bus_slot **slot, const char *match, sd_bus_message_handler_t callback, sd_bus_message_handler_t install_callback, void *userdata) override;
virtual sd_bus_slot* sd_bus_slot_unref(sd_bus_slot *slot) override;
virtual int sd_bus_new(sd_bus **ret) override;

View File

@ -27,8 +27,11 @@
#include <sdbus-c++/Types.h>
#include <sdbus-c++/Error.h>
#include "MessageUtils.h"
#include <systemd/sd-bus.h>
#include SDBUS_HEADER
#include <cassert>
#include <cerrno>
#include <system_error>
#include <unistd.h>
namespace sdbus {
@ -64,4 +67,27 @@ bool Variant::isEmpty() const
return msg_.isEmpty();
}
void UnixFd::close()
{
if (fd_ >= 0)
{
::close(fd_);
}
}
int UnixFd::checkedDup(int fd)
{
if (fd < 0)
{
return fd;
}
int ret = ::dup(fd);
if (ret < 0)
{
throw std::system_error(errno, std::generic_category(), "dup failed");
}
return ret;
}
}

View File

@ -28,20 +28,20 @@
#define SDBUS_CXX_INTERNAL_UTILS_H_
#include <sdbus-c++/Error.h>
#include <systemd/sd-bus.h>
#include SDBUS_HEADER
#if LIBSYSTEMD_VERSION>=246
#define SDBUS_CHECK_OBJECT_PATH(_PATH) \
SDBUS_THROW_ERROR_IF(!sd_bus_object_path_is_valid(_PATH.c_str()), "Invalid object path '" + _PATH + "' provided", EINVAL) \
#define SDBUS_CHECK_OBJECT_PATH(_PATH) \
SDBUS_THROW_ERROR_IF(!sd_bus_object_path_is_valid(_PATH.c_str()), "Invalid object path '" + _PATH + "' provided", EINVAL) \
/**/
#define SDBUS_CHECK_INTERFACE_NAME(_NAME) \
SDBUS_THROW_ERROR_IF(!sd_bus_interface_name_is_valid(_NAME.c_str()), "Invalid interface name '" + _NAME + "' provided", EINVAL) \
#define SDBUS_CHECK_INTERFACE_NAME(_NAME) \
SDBUS_THROW_ERROR_IF(!sd_bus_interface_name_is_valid(_NAME.c_str()), "Invalid interface name '" + _NAME + "' provided", EINVAL) \
/**/
#define SDBUS_CHECK_SERVICE_NAME(_NAME) \
SDBUS_THROW_ERROR_IF(!sd_bus_service_name_is_valid(_NAME.c_str()), "Invalid service name '" + _NAME + "' provided", EINVAL) \
#define SDBUS_CHECK_SERVICE_NAME(_NAME) \
SDBUS_THROW_ERROR_IF(!_NAME.empty() && !sd_bus_service_name_is_valid(_NAME.c_str()), "Invalid service name '" + _NAME + "' provided", EINVAL) \
/**/
#define SDBUS_CHECK_MEMBER_NAME(_NAME) \
SDBUS_THROW_ERROR_IF(!sd_bus_member_name_is_valid(_NAME.c_str()), "Invalid member name '" + _NAME + "' provided", EINVAL) \
#define SDBUS_CHECK_MEMBER_NAME(_NAME) \
SDBUS_THROW_ERROR_IF(!sd_bus_member_name_is_valid(_NAME.c_str()), "Invalid member name '" + _NAME + "' provided", EINVAL) \
/**/
#else
#define SDBUS_CHECK_OBJECT_PATH(_PATH)
@ -50,4 +50,38 @@
#define SDBUS_CHECK_MEMBER_NAME(_NAME)
#endif
namespace sdbus::internal {
template <typename _Callable>
bool invokeHandlerAndCatchErrors(_Callable callable, sd_bus_error *retError)
{
try
{
callable();
}
catch (const Error& e)
{
sd_bus_error_set(retError, e.getName().c_str(), e.getMessage().c_str());
return false;
}
catch (const std::exception& e)
{
sd_bus_error_set(retError, SDBUSCPP_ERROR_NAME, e.what());
return false;
}
catch (...)
{
sd_bus_error_set(retError, SDBUSCPP_ERROR_NAME, "Unknown error occurred");
return false;
}
return true;
}
// Implementation of the overload pattern for variant visitation
template <class... Ts> struct overload : Ts... { using Ts::operator()...; };
template <class... Ts> overload(Ts...) -> overload<Ts...>;
}
#endif /* SDBUS_CXX_INTERNAL_UTILS_H_ */

View File

@ -25,7 +25,7 @@
*/
#include "VTableUtils.h"
#include <systemd/sd-bus.h>
#include SDBUS_HEADER
sd_bus_vtable createVTableStartItem(uint64_t flags)
{

View File

@ -27,7 +27,7 @@
#ifndef SDBUS_CXX_INTERNAL_VTABLEUTILS_H_
#define SDBUS_CXX_INTERNAL_VTABLEUTILS_H_
#include <systemd/sd-bus.h>
#include SDBUS_HEADER
#include <stdbool.h>
#ifdef __cplusplus

View File

@ -104,7 +104,9 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR})
#----------------------------------
add_executable(sdbus-c++-unit-tests ${UNITTESTS_SRCS})
target_compile_definitions(sdbus-c++-unit-tests PRIVATE LIBSYSTEMD_VERSION=${LIBSYSTEMD_VERSION})
target_compile_definitions(sdbus-c++-unit-tests PRIVATE
LIBSYSTEMD_VERSION=${LIBSYSTEMD_VERSION}
SDBUS_HEADER=<${LIBSYSTEMD_IMPL}/sd-bus.h>)
target_link_libraries(sdbus-c++-unit-tests sdbus-c++-objlib GTest::gmock)
add_executable(sdbus-c++-integration-tests ${INTEGRATIONTESTS_SRCS})
@ -138,21 +140,26 @@ endif()
# INSTALLATION
#----------------------------------
set(TESTS_INSTALL_PATH "/opt/test/bin" CACHE STRING "Specifies where the test binaries will be installed")
option(INSTALL_TESTS "Install tests (default OFF)" OFF)
install(TARGETS sdbus-c++-unit-tests DESTINATION ${TESTS_INSTALL_PATH} COMPONENT test)
install(TARGETS sdbus-c++-integration-tests DESTINATION ${TESTS_INSTALL_PATH} COMPONENT test)
install(FILES ${INTEGRATIONTESTS_SOURCE_DIR}/files/org.sdbuscpp.integrationtests.conf DESTINATION /etc/dbus-1/system.d COMPONENT test)
if(INSTALL_TESTS)
include(GNUInstallDirs)
set(TESTS_INSTALL_PATH "tests/${PROJECT_NAME}" CACHE STRING "Specifies where the test binaries will be installed")
if(ENABLE_PERF_TESTS)
install(TARGETS sdbus-c++-perf-tests-client DESTINATION ${TESTS_INSTALL_PATH} COMPONENT test)
install(TARGETS sdbus-c++-perf-tests-server DESTINATION ${TESTS_INSTALL_PATH} COMPONENT test)
install(FILES ${PERFTESTS_SOURCE_DIR}/files/org.sdbuscpp.perftests.conf DESTINATION /etc/dbus-1/system.d COMPONENT test)
endif()
install(TARGETS sdbus-c++-unit-tests DESTINATION ${TESTS_INSTALL_PATH} COMPONENT test)
install(TARGETS sdbus-c++-integration-tests DESTINATION ${TESTS_INSTALL_PATH} COMPONENT test)
install(FILES ${INTEGRATIONTESTS_SOURCE_DIR}/files/org.sdbuscpp.integrationtests.conf DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/dbus-1/system.d COMPONENT test)
if(ENABLE_STRESS_TESTS)
install(TARGETS sdbus-c++-stress-tests DESTINATION ${TESTS_INSTALL_PATH} COMPONENT test)
install(FILES ${STRESSTESTS_SOURCE_DIR}/files/org.sdbuscpp.stresstests.conf DESTINATION /etc/dbus-1/system.d COMPONENT test)
if(ENABLE_PERF_TESTS)
install(TARGETS sdbus-c++-perf-tests-client DESTINATION ${TESTS_INSTALL_PATH} COMPONENT test)
install(TARGETS sdbus-c++-perf-tests-server DESTINATION ${TESTS_INSTALL_PATH} COMPONENT test)
install(FILES ${PERFTESTS_SOURCE_DIR}/files/org.sdbuscpp.perftests.conf DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/dbus-1/system.d COMPONENT test)
endif()
if(ENABLE_STRESS_TESTS)
install(TARGETS sdbus-c++-stress-tests DESTINATION ${TESTS_INSTALL_PATH} COMPONENT test)
install(FILES ${STRESSTESTS_SOURCE_DIR}/files/org.sdbuscpp.stresstests.conf DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/dbus-1/system.d COMPONENT test)
endif()
endif()
#----------------------------------

View File

@ -46,6 +46,7 @@ using namespace std::chrono_literals;
using namespace sdbus::test;
using AConnection = TestFixture;
using ADirectConnection = TestFixtureWithDirectConnection;
/*-------------------------------------*/
/* -- TEST CASES -- */
@ -87,6 +88,29 @@ TEST_F(AConnection, WillCallCallbackHandlerForIncomingMessageMatchingMatchRule)
ASSERT_TRUE(waitUntil(matchingMessageReceived));
}
TEST_F(AConnection, CanInstallMatchRuleAsynchronously)
{
auto matchRule = "sender='" + BUS_NAME + "',path='" + OBJECT_PATH + "'";
std::atomic<bool> matchingMessageReceived{false};
std::atomic<bool> matchRuleInstalled{false};
auto slot = s_proxyConnection->addMatchAsync( matchRule
, [&](sdbus::Message& msg)
{
if(msg.getPath() == OBJECT_PATH)
matchingMessageReceived = true;
}
, [&](sdbus::Message& /*msg*/)
{
matchRuleInstalled = true;
} );
EXPECT_TRUE(waitUntil(matchRuleInstalled));
m_adaptor->emitSimpleSignal();
ASSERT_TRUE(waitUntil(matchingMessageReceived));
}
TEST_F(AConnection, WillUnsubscribeMatchRuleWhenClientDestroysTheAssociatedSlot)
{
auto matchRule = "sender='" + BUS_NAME + "',path='" + OBJECT_PATH + "'";
@ -144,3 +168,14 @@ TEST_F(AConnection, WillNotPassToMatchCallbackMessagesThatDoNotMatchTheRule)
ASSERT_TRUE(waitUntil([&](){ return numberOfMatchingMessages == 2; }));
ASSERT_FALSE(waitUntil([&](){ return numberOfMatchingMessages > 2; }, 1s));
}
// A simple direct connection test similar in nature to https://github.com/systemd/systemd/blob/main/src/libsystemd/sd-bus/test-bus-server.c
TEST_F(ADirectConnection, CanBeUsedBetweenClientAndServer)
{
auto val = m_proxy->sumArrayItems({1, 7}, {2, 3, 4});
m_adaptor->emitSimpleSignal();
// Make sure method call passes and emitted signal is received
ASSERT_THAT(val, Eq(1 + 7 + 2 + 3 + 4));
ASSERT_TRUE(waitUntil(m_proxy->m_gotSimpleSignal));
}

View File

@ -180,7 +180,7 @@ TEST_F(SdbusTestObject, ThrowsTimeoutErrorWhenMethodTimesOut)
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"));
ASSERT_THAT(e.getMessage(), AnyOf("Connection timed out", "Operation timed out", "Method call timed out"));
auto measuredTimeout = std::chrono::steady_clock::now() - start;
ASSERT_THAT(measuredTimeout, Le(50ms));
}
@ -277,6 +277,10 @@ TEST_F(SdbusTestObject, CannotSetGeneralMethodTimeoutWithLibsystemdVersionLessTh
TEST_F(SdbusTestObject, CanCallMethodSynchronouslyWithoutAnEventLoopThread)
{
#if defined(__clang__) && defined(__FreeBSD__)
GTEST_SKIP() << "https://github.com/Kistler-Group/sdbus-cpp/issues/359";
#endif
auto proxy = std::make_unique<TestProxy>(BUS_NAME, OBJECT_PATH, sdbus::dont_run_event_loop_thread);
auto multiplyRes = proxy->multiply(INT64_VALUE, DOUBLE_VALUE);

View File

@ -61,12 +61,9 @@ 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";
if (::access("/etc/machine-id", F_OK) == -1 &&
::access("/var/lib/dbus/machine-id", F_OK) == -1)
GTEST_SKIP() << "/etc/machine-id and /var/lib/dbus/machine-id files do not exist, GetMachineId() will not work";
ASSERT_NO_THROW(m_proxy->GetMachineId());
}
@ -82,6 +79,29 @@ TEST_F(SdbusTestObject, GetsPropertyViaPropertiesInterface)
ASSERT_THAT(m_proxy->Get(INTERFACE_NAME, "state").get<std::string>(), Eq(DEFAULT_STATE_VALUE));
}
TEST_F(SdbusTestObject, GetsPropertyAsynchronouslyViaPropertiesInterface)
{
std::promise<std::string> promise;
auto future = promise.get_future();
m_proxy->GetAsync(INTERFACE_NAME, "state", [&](const sdbus::Error* err, sdbus::Variant value)
{
if (err == nullptr)
promise.set_value(value.get<std::string>());
else
promise.set_exception(std::make_exception_ptr(*err));
});
ASSERT_THAT(future.get(), Eq(DEFAULT_STATE_VALUE));
}
TEST_F(SdbusTestObject, GetsPropertyAsynchronouslyViaPropertiesInterfaceWithFuture)
{
auto future = m_proxy->GetAsync(INTERFACE_NAME, "state", sdbus::with_future);
ASSERT_THAT(future.get().get<std::string>(), Eq(DEFAULT_STATE_VALUE));
}
TEST_F(SdbusTestObject, SetsPropertyViaPropertiesInterface)
{
uint32_t newActionValue = 2345;
@ -91,6 +111,34 @@ TEST_F(SdbusTestObject, SetsPropertyViaPropertiesInterface)
ASSERT_THAT(m_proxy->action(), Eq(newActionValue));
}
TEST_F(SdbusTestObject, SetsPropertyAsynchronouslyViaPropertiesInterface)
{
uint32_t newActionValue = 2346;
std::promise<void> promise;
auto future = promise.get_future();
m_proxy->SetAsync(INTERFACE_NAME, "action", sdbus::Variant{newActionValue}, [&](const sdbus::Error* err)
{
if (err == nullptr)
promise.set_value();
else
promise.set_exception(std::make_exception_ptr(*err));
});
ASSERT_NO_THROW(future.get());
ASSERT_THAT(m_proxy->action(), Eq(newActionValue));
}
TEST_F(SdbusTestObject, SetsPropertyAsynchronouslyViaPropertiesInterfaceWithFuture)
{
uint32_t newActionValue = 2347;
auto future = m_proxy->SetAsync(INTERFACE_NAME, "action", sdbus::Variant{newActionValue}, sdbus::with_future);
ASSERT_NO_THROW(future.get());
ASSERT_THAT(m_proxy->action(), Eq(newActionValue));
}
TEST_F(SdbusTestObject, GetsAllPropertiesViaPropertiesInterface)
{
const auto properties = m_proxy->GetAll(INTERFACE_NAME);
@ -101,6 +149,38 @@ TEST_F(SdbusTestObject, GetsAllPropertiesViaPropertiesInterface)
EXPECT_THAT(properties.at("blocking").get<bool>(), Eq(DEFAULT_BLOCKING_VALUE));
}
TEST_F(SdbusTestObject, GetsAllPropertiesAsynchronouslyViaPropertiesInterface)
{
std::promise<std::map<std::string, sdbus::Variant>> promise;
auto future = promise.get_future();
m_proxy->GetAllAsync(INTERFACE_NAME, [&](const sdbus::Error* err, std::map<std::string, sdbus::Variant> value)
{
if (err == nullptr)
promise.set_value(std::move(value));
else
promise.set_exception(std::make_exception_ptr(*err));
});
const auto properties = future.get();
ASSERT_THAT(properties, SizeIs(3));
EXPECT_THAT(properties.at("state").get<std::string>(), Eq(DEFAULT_STATE_VALUE));
EXPECT_THAT(properties.at("action").get<uint32_t>(), Eq(DEFAULT_ACTION_VALUE));
EXPECT_THAT(properties.at("blocking").get<bool>(), Eq(DEFAULT_BLOCKING_VALUE));
}
TEST_F(SdbusTestObject, GetsAllPropertiesAsynchronouslyViaPropertiesInterfaceWithFuture)
{
auto future = m_proxy->GetAllAsync(INTERFACE_NAME, sdbus::with_future);
auto properties = future.get();
ASSERT_THAT(properties, SizeIs(3));
EXPECT_THAT(properties.at("state").get<std::string>(), Eq(DEFAULT_STATE_VALUE));
EXPECT_THAT(properties.at("action").get<uint32_t>(), Eq(DEFAULT_ACTION_VALUE));
EXPECT_THAT(properties.at("blocking").get<bool>(), Eq(DEFAULT_BLOCKING_VALUE));
}
TEST_F(SdbusTestObject, EmitsPropertyChangedSignalForSelectedProperties)
{
std::atomic<bool> signalReceived{false};

View File

@ -30,14 +30,17 @@
#include "sdbus-c++/Types.h"
#include <chrono>
#include <ostream>
#include <filesystem>
namespace sdbus { namespace test {
const std::string INTERFACE_NAME{"org.sdbuscpp.integrationtests"};
const std::string BUS_NAME = INTERFACE_NAME;
const std::string EMPTY_DESTINATION;
const std::string MANAGER_PATH {"/org/sdbuscpp/integrationtests"};
const std::string OBJECT_PATH {"/org/sdbuscpp/integrationtests/ObjectA1"};
const std::string OBJECT_PATH_2{"/org/sdbuscpp/integrationtests/ObjectB1"};
const std::string DIRECT_CONNECTION_SOCKET_PATH{std::filesystem::temp_directory_path() / "sdbus-cpp-direct-connection-test"};
constexpr const uint8_t UINT8_VALUE{1};
constexpr const int16_t INT16_VALUE{21};

View File

@ -39,6 +39,11 @@
#include <atomic>
#include <chrono>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
namespace sdbus { namespace test {
class TestFixture : public ::testing::Test
@ -59,28 +64,6 @@ public:
s_proxyConnection->leaveEventLoop();
}
template <typename _Fnc>
static bool waitUntil(_Fnc&& fnc, std::chrono::milliseconds timeout = std::chrono::seconds(5))
{
using namespace std::chrono_literals;
std::chrono::milliseconds elapsed{};
std::chrono::milliseconds step{5ms};
do {
std::this_thread::sleep_for(step);
elapsed += step;
if (elapsed > timeout)
return false;
} while (!fnc());
return true;
}
static bool waitUntil(std::atomic<bool>& flag, std::chrono::milliseconds timeout = std::chrono::seconds(5))
{
return waitUntil([&flag]() -> bool { return flag; }, timeout);
}
private:
void SetUp() override
{
@ -106,6 +89,101 @@ public:
std::unique_ptr<TestProxy> m_proxy;
};
class TestFixtureWithDirectConnection : public ::testing::Test
{
private:
void SetUp() override
{
int sock = openUnixSocket();
createClientAndServerConnections(sock);
createAdaptorAndProxyObjects();
}
void TearDown() override
{
m_proxy.reset();
m_adaptor.reset();
m_proxyConnection->leaveEventLoop();
m_adaptorConnection->leaveEventLoop();
}
static int openUnixSocket()
{
int sock = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
assert(sock >= 0);
sockaddr_un sa;
memset(&sa, 0, sizeof(sa));
sa.sun_family = AF_UNIX;
snprintf(sa.sun_path, sizeof(sa.sun_path), "%s", DIRECT_CONNECTION_SOCKET_PATH.c_str());
unlink(DIRECT_CONNECTION_SOCKET_PATH.c_str());
umask(0000);
[[maybe_unused]] int r = bind(sock, (const sockaddr*) &sa, sizeof(sa.sun_path));
assert(r >= 0);
r = listen(sock, 5);
assert(r >= 0);
return sock;
}
void createClientAndServerConnections(int sock)
{
std::thread t([&]()
{
auto fd = accept4(sock, NULL, NULL, /*SOCK_NONBLOCK|*/SOCK_CLOEXEC);
m_adaptorConnection = sdbus::createServerBus(fd);
// This is necessary so that createDirectBusConnection() below does not block
m_adaptorConnection->enterEventLoopAsync();
});
m_proxyConnection = sdbus::createDirectBusConnection("unix:path=" + DIRECT_CONNECTION_SOCKET_PATH);
m_proxyConnection->enterEventLoopAsync();
t.join();
}
void createAdaptorAndProxyObjects()
{
assert(m_adaptorConnection != nullptr);
assert(m_proxyConnection != nullptr);
m_adaptor = std::make_unique<TestAdaptor>(*m_adaptorConnection, OBJECT_PATH);
// Destination parameter can be empty in case of direct connections
m_proxy = std::make_unique<TestProxy>(*m_proxyConnection, EMPTY_DESTINATION, OBJECT_PATH);
}
public:
std::unique_ptr<sdbus::IConnection> m_adaptorConnection;
std::unique_ptr<sdbus::IConnection> m_proxyConnection;
std::unique_ptr<TestAdaptor> m_adaptor;
std::unique_ptr<TestProxy> m_proxy;
};
template <typename _Fnc>
inline bool waitUntil(_Fnc&& fnc, std::chrono::milliseconds timeout = std::chrono::seconds(5))
{
using namespace std::chrono_literals;
std::chrono::milliseconds elapsed{};
std::chrono::milliseconds step{5ms};
do {
std::this_thread::sleep_for(step);
elapsed += step;
if (elapsed > timeout)
return false;
} while (!fnc());
return true;
}
inline bool waitUntil(std::atomic<bool>& flag, std::chrono::milliseconds timeout = std::chrono::seconds(5))
{
return waitUntil([&flag]() -> bool { return flag; }, timeout);
}
}}
#endif /* SDBUS_CPP_INTEGRATIONTESTS_TESTFIXTURE_H_ */

View File

@ -62,12 +62,16 @@ public:
MOCK_METHOD1(sd_bus_open_user, int(sd_bus **ret));
MOCK_METHOD2(sd_bus_open_user_with_address, int(sd_bus **ret, const char* address));
MOCK_METHOD2(sd_bus_open_system_remote, int(sd_bus **ret, const char *host));
MOCK_METHOD2(sd_bus_open_direct, int(sd_bus **ret, const char* address));
MOCK_METHOD2(sd_bus_open_direct, int(sd_bus **ret, int fd));
MOCK_METHOD2(sd_bus_open_server, int(sd_bus **ret, int fd));
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));
MOCK_METHOD6(sd_bus_add_match_async, int(sd_bus *bus, sd_bus_slot **slot, const char *match, sd_bus_message_handler_t callback, sd_bus_message_handler_t install_callback, void *userdata));
MOCK_METHOD1(sd_bus_slot_unref, sd_bus_slot*(sd_bus_slot *slot));
MOCK_METHOD1(sd_bus_new, int(sd_bus **ret));

View File

@ -4,7 +4,7 @@
cmake_minimum_required(VERSION 3.5)
project(sdbus-c++-tools VERSION 1.3.0)
project(sdbus-c++-tools VERSION 1.4.0)
include(GNUInstallDirs)

View File

@ -108,12 +108,18 @@ std::string ProxyGenerator::processInterface(Node& interface) const
if (!declaration.empty())
body << declaration << endl;
std::string methodDefinitions, asyncDeclarations;
std::tie(methodDefinitions, asyncDeclarations) = processMethods(methods);
std::string methodDefinitions, asyncDeclarationsMethods;
std::tie(methodDefinitions, asyncDeclarationsMethods) = processMethods(methods);
std::string propertyDefinitions, asyncDeclarationsProperties;
std::tie(propertyDefinitions, asyncDeclarationsProperties) = processProperties(properties);
if (!asyncDeclarations.empty())
if (!asyncDeclarationsMethods.empty())
{
body << asyncDeclarations << endl;
body << asyncDeclarationsMethods << endl;
}
if (!asyncDeclarationsProperties.empty())
{
body << asyncDeclarationsProperties << endl;
}
if (!methodDefinitions.empty())
@ -121,7 +127,6 @@ std::string ProxyGenerator::processInterface(Node& interface) const
body << "public:" << endl << methodDefinitions;
}
std::string propertyDefinitions = processProperties(properties);
if (!propertyDefinitions.empty())
{
body << "public:" << endl << propertyDefinitions;
@ -293,9 +298,9 @@ std::tuple<std::string, std::string> ProxyGenerator::processSignals(const Nodes&
return std::make_tuple(registrationSS.str(), declarationSS.str());
}
std::string ProxyGenerator::processProperties(const Nodes& properties) const
std::tuple<std::string, std::string> ProxyGenerator::processProperties(const Nodes& properties) const
{
std::ostringstream propertySS;
std::ostringstream propertySS, asyncDeclarationSS;
for (const auto& property : properties)
{
auto propertyName = property->get("name");
@ -307,25 +312,93 @@ std::string ProxyGenerator::processProperties(const Nodes& properties) const
auto propertyArg = std::string("value");
auto propertyTypeArg = std::string("const ") + propertyType + "& " + propertyArg;
bool asyncGet{false};
bool futureGet{false}; // Async property getter implemented by means of either std::future or callbacks
bool asyncSet{false};
bool futureSet{false}; // Async property setter implemented by means of either std::future or callbacks
Nodes annotations = (*property)["annotation"];
for (const auto& annotation : annotations)
{
const auto annotationName = annotation->get("name");
const auto annotationValue = annotation->get("value");
if (annotationName == "org.freedesktop.DBus.Property.Get.Async" && annotationValue == "client") // Server-side not supported (may be in the future)
asyncGet = true;
else if (annotationName == "org.freedesktop.DBus.Property.Get.Async.ClientImpl" && annotationValue == "callback")
futureGet = false;
else if (annotationName == "org.freedesktop.DBus.Property.Get.Async.ClientImpl" && (annotationValue == "future" || annotationValue == "std::future"))
futureGet = true;
else if (annotationName == "org.freedesktop.DBus.Property.Set.Async" && annotationValue == "client") // Server-side not supported (may be in the future)
asyncSet = true;
else if (annotationName == "org.freedesktop.DBus.Property.Set.Async.ClientImpl" && annotationValue == "callback")
futureSet = false;
else if (annotationName == "org.freedesktop.DBus.Property.Set.Async.ClientImpl" && (annotationValue == "future" || annotationValue == "std::future"))
futureSet = true;
}
if (propertyAccess == "read" || propertyAccess == "readwrite")
{
propertySS << tab << propertyType << " " << propertyNameSafe << "()" << endl
const std::string realRetType = (asyncGet ? (futureGet ? "std::future<sdbus::Variant>" : "sdbus::PendingAsyncCall") : propertyType);
propertySS << tab << realRetType << " " << propertyNameSafe << "()" << endl
<< tab << "{" << endl;
propertySS << tab << tab << "return proxy_->getProperty(\"" << propertyName << "\")"
propertySS << tab << tab << "return proxy_->getProperty" << (asyncGet ? "Async" : "") << "(\"" << propertyName << "\")"
".onInterface(INTERFACE_NAME)";
if (asyncGet)
{
auto nameBigFirst = propertyName;
nameBigFirst[0] = islower(nameBigFirst[0]) ? nameBigFirst[0] + 'A' - 'a' : nameBigFirst[0];
if (futureGet) // Async methods implemented through future
{
propertySS << ".getResultAsFuture()";
}
else // Async methods implemented through callbacks
{
propertySS << ".uponReplyInvoke([this](const sdbus::Error* error, const sdbus::Variant& value)"
"{ this->on" << nameBigFirst << "PropertyGetReply(value.get<" << propertyType << ">(), error); })";
asyncDeclarationSS << tab << "virtual void on" << nameBigFirst << "PropertyGetReply("
<< "const " << propertyType << "& value, const sdbus::Error* error) = 0;" << endl;
}
}
propertySS << ";" << endl << tab << "}" << endl << endl;
}
if (propertyAccess == "readwrite" || propertyAccess == "write")
{
propertySS << tab << "void " << propertyNameSafe << "(" << propertyTypeArg << ")" << endl
<< tab << "{" << endl;
propertySS << tab << tab << "proxy_->setProperty(\"" << propertyName << "\")"
const std::string realRetType = (asyncSet ? (futureSet ? "std::future<void>" : "sdbus::PendingAsyncCall") : "void");
propertySS << tab << realRetType << " " << propertyNameSafe << "(" << propertyTypeArg << ")" << endl
<< tab << "{" << endl;
propertySS << tab << tab << (asyncSet ? "return " : "") << "proxy_->setProperty" << (asyncSet ? "Async" : "")
<< "(\"" << propertyName << "\")"
".onInterface(INTERFACE_NAME)"
".toValue(" << propertyArg << ")";
if (asyncSet)
{
auto nameBigFirst = propertyName;
nameBigFirst[0] = islower(nameBigFirst[0]) ? nameBigFirst[0] + 'A' - 'a' : nameBigFirst[0];
if (futureSet) // Async methods implemented through future
{
propertySS << ".getResultAsFuture()";
}
else // Async methods implemented through callbacks
{
propertySS << ".uponReplyInvoke([this](const sdbus::Error* error)"
"{ this->on" << nameBigFirst << "PropertySetReply(error); })";
asyncDeclarationSS << tab << "virtual void on" << nameBigFirst << "PropertySetReply("
<< "const sdbus::Error* error) = 0;" << endl;
}
}
propertySS << ";" << endl << tab << "}" << endl << endl;
}
}
return propertySS.str();
return std::make_tuple(propertySS.str(), asyncDeclarationSS.str());
}

View File

@ -73,7 +73,7 @@ private:
* @param properties
* @return source code
*/
std::string processProperties(const sdbuscpp::xml::Nodes& properties) const;
std::tuple<std::string, std::string> processProperties(const sdbuscpp::xml::Nodes& properties) const;
};