Compare commits

...

116 Commits

Author SHA1 Message Date
b7d85f936d chore: release version v2.0.0 2024-04-24 21:00:02 +02:00
798eaf8626 refactor: remove floating_slot_t tag and use return_slot_t instead (#439)
Make all the API consistent by using return_slot_t-based overloads for returning the slots to clients, and overloads without that tag for "floating" slots. floating_slot_t tag was previously added for API backwards compatibility reasons, but now is a (counter-)duplicate to the return_slot_t.
2024-04-24 20:20:29 +02:00
2bc9d3ebb3 refactor: object manager API (#438)
This makes the signature of addObjectManager() function overloads consistent with common API design of the library.
2024-04-24 20:20:29 +02:00
26c6da11be refactor: reorganize public API of main abstract classes (#437) 2024-04-24 20:20:29 +02:00
0c75ad2b71 docs: fix generated class examples in the tutorial (#436) 2024-04-24 20:20:29 +02:00
84a6fbbf86 fix: disable move in generated adaptor and proxy classes (#435)
Moving adaptor or proxy instances changes their `this` pointer. But `this` is captured by value in closures used by those instances, and this remains unchanged on move, leading to accessing an invalid instance when a lambda expression executes. Supporting move semantics would require unsubscribing/unregistering vtable, handlers, etc. and re-subscribing and re-registering all that, which is too complicated and may have side effects. Hence it has been decided that these classes are not moveable. One may use an indirection with e.g. `std::unique_ptr` to get move semantics.
2024-04-24 20:20:29 +02:00
83ece48ab0 feat: add Slot-returning overloads of async method calls (#433) 2024-04-24 20:20:29 +02:00
310161207a refactor: improve and make more efficient some Message API (#432) 2024-04-24 20:20:29 +02:00
ef552ec089 chore: update years in header comments 2024-04-24 20:20:29 +02:00
7894cda577 perf: provide also const char* overloads for convenience functions 2024-04-24 20:20:29 +02:00
d41a176c2a refactor: use string_view in Property convenience classes 2024-04-24 20:20:29 +02:00
f15c260750 feat: add support for string_view as D-Bus type 2024-04-24 20:20:29 +02:00
d856969051 refactor: enable compile-time D-Bus signature generation (#425)
Since sdbus-c++ knows types of D-Bus call/signal/property input/output parameters at compile time, why not assemble the D-Bus signature strings at compile time, too, instead of assembling them at run time as std::string with likely cost of (unnecessary) heap allocations...

std::array is well supported constexpr type in C++20, so we harness it to build the D-Bus signature string and store it into the binary at compile time.
2024-04-24 20:20:29 +02:00
32a97f214f fix: make Variant conversion operator explicit (#428)
Having explicit conversion operator is a good practice according to the C++ core guidelines, as it makes the code safer and better follows the principle of least astonishment. Also, it is consistent with the standard library style, where wrappers like std::variant, std::any, std::optional... also do not provide an implicit conversion to the underlying type. Last but not least, it paves the way for the upcoming std::variant <-> sdbus::Variant implicit conversions without surprising behavior in some edge cases.
2024-04-24 20:20:29 +02:00
42f0bd07c0 refactor: add strong types to public API (#414)
This introduces strong types for `std::string`-based D-Bus types. This facilitates safer, less error-prone and more expressive API.

What previously was `auto proxy = createProxy("org.sdbuscpp.concatenator", "/org/sdbuscpp/concatenator");` is now written like `auto proxy = createProxy(ServiceName{"org.sdbuscpp.concatenator"}, ObjectPath{"/org/sdbuscpp/concatenator"});`.

These types are:
  * `ObjectPath` type for the object path (the type has been around already but now is also used consistently in sdbus-c++ API for object path strings),
  * `InterfaceName` type for D-Bus interface names,
  * `BusName` (and its aliases `ServiceName` and `ConnectionName`) type for bus/service/connection names,
  * `MemberName` (and its aliases `MethodName`, `SignalName` and `PropertyName`) type for D-Bus method, signal and property names,
  * `Signature` type for the D-Bus signature (the type has been around already but now is also used consistently in sdbus-c++ API for signature strings),
  * `Error::Name` type for D-Bus error names.
2024-04-24 20:20:29 +02:00
fe21ee9656 fix(tests): fix intermittently failing integration tests (#412) 2024-04-24 20:20:29 +02:00
0dc0c87cc9 chore: use C++20 standard (#410)
As of now sdbus-c++ supports C++20 but does not require it, and the used C++20 features are conditionally compiled depending on whether they are available or not.
2024-04-24 20:20:29 +02:00
29e94c3b68 fix(cmake): rename forgotten CMake variables 2024-04-24 20:20:29 +02:00
c433d26176 chore(cmake): implement more flexible searching for sd-bus libs (#409)
A new CMake configuration variable SDBUSCPP_SDBUS_LIB has been introduced that enables clients to specify which sd-bus implementation library shall be used by sdbus-c++.
2024-04-24 20:20:29 +02:00
abc4d755f7 fix: conditional use and tests of span (#411) 2024-04-24 20:20:29 +02:00
4c35e98668 fix: pass correctly underlying lib to intg tests 2024-04-24 20:20:29 +02:00
87500ad9ad docs: add v2 migration notes to the tutorial (#407) 2024-04-24 20:20:29 +02:00
9a5616a87a refactor: rename connection creation methods (#406)
This PR makes things around connection factories a little more consistent and more intuitive:

    * createConnection() has been removed. One shall call more expressive createSystemConnection() instead to get a connection to the system bus.
    * createDefaultBusConnection() has been renamed to createBusConnection(), so as not to be confused with libsystemd's default_bus, which is a different thing (a reusable thread-local bus).

Proxies still by default call createBusConnection() to get a connection when the connection is not provided explicitly by the caller, but now createBusConnection() does a different thing, so now the proxies connect to either session bus or system bus depending on the context (as opposed to always to system bus like before).

The integration tests were modified to use createBusConnection().
2024-04-24 20:20:29 +02:00
4a90f80933 chore: update ChangeLog for v2.0.0 (#405) 2024-04-24 20:20:29 +02:00
d177d2e365 chore: reorder some API functions (#404)
In sdbus-c++ v1, new virtual functions (e.g. overloads of existing virtual functions) were always placed at the end of the class to keep backwards ABI compatibility. Now, with v2, these functions are reordered and functions forming a logical group are together.
2024-04-24 20:20:29 +02:00
c205178fc0 chore: rename COMPONENTs in CMake (#402) 2024-04-24 20:20:29 +02:00
280fcfb067 refactor: remove deprecated API stuff (#403) 2024-04-24 20:20:29 +02:00
0074c79e7f refactor: rename and re-organize CMake options (#401)
This improves usability of sdbus-c++ in downstream CMake projects. CMake options now have `SDBUSCPP_` prefix so potential conflicts with downstream options/variables are minimized. Also, all configurable options and variables are placed in a single place in the root CMake file.
2024-04-24 20:20:29 +02:00
3de2812a60 refactor: use optional for passing potential call errors (#396)
This switches from a raw pointer to std::optional type to pass prospective call errors to the client (using std::optional was not possible years back when sdbus-c++ was based on C++14). This makes the API a little clearer, safer, idiomatically more expressive, and removes potential confusion associated with raw pointers (like ownership, lifetime questions, etc.).
2024-04-24 20:20:29 +02:00
914d133d10 refactor: add nodiscard attribute for more functions 2024-04-24 20:20:29 +02:00
4721060d76 refactor: use correct header include order 2024-04-24 20:20:29 +02:00
1c72f1ce52 refactor: add Async suffix to async callMethod functions (#394)
This makes naming consistent, so all asynchronously working functions have Async suffix
2024-04-24 20:20:29 +02:00
b65461c404 refactor: add nodiscard attribute for some functions 2024-04-24 20:20:29 +02:00
24776f2047 chore: remove legacy comments 2024-04-24 20:20:29 +02:00
f1b9226491 refactor: remove deprecated dont_request_slot_t tag 2024-04-24 20:20:29 +02:00
dc0c2562b8 refactor: rename request_slot tag to return_slot 2024-04-24 20:20:29 +02:00
84932a6985 refactor: improve Proxy signal subscription (#389)
This makes D-Bus proxy signal registration more flexible, more dynamic, and less error-prone since no `finishRegistration()` call is needed. A proxy can register to a signal at any time during its lifetime, and can unregister freely by simply destroying the associated slot.
2024-04-24 20:20:29 +02:00
bdf313bc60 refactor: improve Object vtable registration (#388)
This improves the D-Bus object API registration/unregistration by making it more flexible, more dynamic, closer to sd-bus API design but still on high abstraction level, and -- most importantly -- less error-prone since no `finishRegistration()` call is needed anymore.
2024-04-24 20:20:29 +02:00
e76d38c317 fix: add missing header to ScopeGuard.h 2024-04-24 20:20:29 +02:00
c92f5722c7 refactor: use sd_bus_match_signal() for signal registration (#372)
sd_bus_match_signal() is more convenient than more general sd_bus_add_match() for signal registration, and requires less code on our side. Plus, systemd of at least v238 is required for sdbus-c++ v2, and this version already ships with sd_bus_match_signal().
2024-04-24 20:20:29 +02:00
20a13eeafb refactor: make Variant constructor explicit (#370)
This makes the library more robust and prone to user's errors when the user writes an extension for their custom type. In case they forget to implement a serialization function for that type and yet insert an object of that type into sdbus::Message, the current behavior is that, surprisingly, the library masks the error as it resolves the call to the Variant overload, because Variant provides an implicit template converting constructor, so the library tries to construct first the Variant object from the object of custom type, and then inserting into the message that Variant object. Variant constructor serializes the underlying object into its internal message object, which resolves to the same message insertion overload, creating an infinite recursion and ultimately the stack overflow. This is undesired and plain wrong. Marking this Variant converting constructor solves these problems, plus in overall it makes the code a little safer and more verbose. With explicit Variant constructor, when the user forgets to implement a serialization function for their type, the call of such function will fail with an expressive compilation error, and will produce no undesired, surprising results.
2024-04-24 20:20:29 +02:00
4fd09d4e81 fix(tests): enable skipped test for Clang and FreeBSD (#369)
The test now works with Clang, libc++ and -O2 optimization, since the underlying implementation has been completely re-designed and doesn't suffer from that problem anymore.
2024-04-24 20:20:29 +02:00
35fd3ff5ce refactor: simplify async call state flag (#368)
Since synchronous D-Bus calls are simplified in v2.0 and no more implemented in terms of an async call, the issue reported in #362 is no more relevant, and we can return to the simple boolean flag for indicating a finished call.
2024-04-24 20:20:29 +02:00
6f35f00fcf refactor: let callbacks take message objects by value (#367)
Signatures of callbacks async_reply_handler, signal_handler, message_handler and property_set_callback were modified to take input message objects by value, as opposed to non-const ref.

The callee assumes ownership of the message. This API is more idiomatic, more expressive, cleaner and safer. Move semantics is used to pass messages to the callback handlers. In some cases, this also improves performance.
2024-04-24 20:20:29 +02:00
9412940d1e refactor: use sd-bus API to get current message 2024-04-24 20:20:29 +02:00
c6afa26541 fix: update cmake version for library to 2.0.0
This will also install the library as `libsdbus-c++.so.2.0.0`, which
represents the actual version of this library
2024-04-24 20:20:29 +02:00
b4d036c503 fix: prevent installing a time event, when there's no need for a timeout
See #324 for discussion
2024-04-24 20:20:29 +02:00
0bfda9ab92 chore: require cmake v3.14 and use FetchContent_MakeAvailable 2024-04-24 20:20:29 +02:00
64337e545d test: support new googletest git tags 2024-04-24 20:20:29 +02:00
788168eded ci: build googletest manually on older ubuntus 2024-04-24 20:20:29 +02:00
b9aa770f58 feat: introduce sd-event integration 2024-04-24 20:20:29 +02:00
2efb6909b5 chore: enable actions on release/v2.0 branch 2024-04-24 20:20:29 +02:00
3e20fc639e refactor: simplify async D-Bus connection handling 2024-04-24 20:20:29 +02:00
7450515d0b chore: release version v1.6.0 2024-04-24 20:04:34 +02:00
c769637f3b chore: update years in header comments 2024-04-16 22:48:34 +02:00
334fcb8833 feat: add std::variant constructor and conversion operator to sdbus::Variant (#429)
Signed-off-by: Anthony Brandon <anthony@amarulasolutions.com>
2024-04-04 20:16:27 +02:00
b9088cc801 test: add integration tests for std::variant (#427)
This also introduces `always_false` technique instead of `sizeof` trick for unsupported D-Bus type representation static assert. This one is more expressive and leads to more specific, more revealing compiler error messages.
2024-04-02 16:44:45 +02:00
a73eb9b8c1 feat: add support for std::variant as D-Bus variant representation (#415)
Signed-off-by: Anthony Brandon <anthony@amarulasolutions.com>
Co-authored-by: Stanislav Angelovič <stanislav.angelovic@protonmail.com>
2024-04-01 13:23:08 +02:00
700a011b80 fix: add missing #include <exception> (#421) 2024-03-25 20:16:23 +01:00
8a9cfc1f19 feat: support enums in D-Bus serialization and signatures (#416) 2024-03-07 17:49:05 +01:00
30d9f1d462 chore: release v1.5.0 2024-02-26 08:29:35 +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
0eda855745 chore: version 1.3.0 2023-08-20 11:45:44 +02:00
2a992ca84d fix: remove explicit from Variant ctor to avoid potential breaking client code 2023-08-20 11:17:10 +02:00
3717e63c64 feat: support std::future-based async methods in codegen tool (#353) 2023-08-19 20:57:32 +02:00
8113bf88ad chore(cmake): add support for libelogind (#352)
* CMakeLists.txt: Fallback to elogind when libsystemd could not be
found.  Set LIBSYSTEMD variable.
* pkgconfig/sdbus-c++.pc.in (Description): Parameterize with above
LIBSYSTEMD variable.

Co-authored-by: Sven Eden <sven.eden@prydeworx.com>
2023-08-18 12:08:37 +02:00
3e84b254e9 refactor: improve type extensibility and its safety (#348) 2023-08-09 12:39:16 +02:00
8728653359 test: add tests for type extensibility (#347) 2023-08-09 12:14:33 +02:00
6620a447d1 docs: add tutorial on extending sdbus-c++ types (#346) 2023-08-09 12:13:47 +02:00
24a3d83c3f chore: fix file permissions 2023-08-04 13:30:34 +02:00
dcd9d46b9c style: remove trailing whitespace (#345)
* style: restore correct 644 file permission

* style: remove trailing whitespace
2023-08-04 13:26:45 +02:00
605fbe48c0 fix: moving instead of copying std::string argument 2023-08-03 14:25:33 +02:00
fb61420bf0 feat: support serialization of array, span and unordered_map (#342)
* feat: support serialization of array, span and unordered_map

* fix some spelling mistakes

* docs: update table of valid c++ types

---------

Co-authored-by: Marcel Hellwig <github@cookiesoft.de>
2023-08-03 13:55:37 +02:00
0a2bda9c67 feat: make Struct tuple-like class (#343) 2023-08-03 13:00:01 +02:00
f6e597a583 fix: add cast to void to silence compiler warning in case of empty parameter list 2023-08-03 12:59:48 +02:00
29c877a89a perf: optimize serialization of arrays of trivial D-Bus types (#340)
* Improve performance of std::vector<> per Issue #339.

* refactor: improve performance of vector serialization

---------

Co-authored-by: Plinio Andrade <plinio.andrade@oracle.com>
Co-authored-by: Stanislav Angelovič <stanislav.angelovic@protonmail.com>
2023-07-27 18:31:49 +02:00
98f4929337 fix: pseudo-connection static lifetime issue 2023-07-26 08:57:21 +02:00
8d0d9b0d40 chore(ci): remove ubuntu-18.04 container image 2023-07-26 08:46:00 +02:00
c39bc637b8 fix: race condition in async Proxy::callMethod
Signed-off-by: Anthony Brandon <anthony@amarulasolutions.com>
2023-02-07 22:23:35 +01:00
737f04abc7 feat: add support for std::future-based async calls 2023-02-07 12:31:31 +01:00
3a56113422 fix: fix namespace name in ScopeGuard 2023-01-21 01:34:34 +01:00
f332f46087 fix: use correct runtime component in CMake file 2023-01-17 09:58:21 +01:00
c9e157e3e1 fix: flush long messages after sending 2023-01-05 15:12:39 +01:00
8ca3fdd5ce chore: update issue templates 2023-01-04 22:05:56 +01:00
55c306ce05 docs: strip absolute paths from doxygen documentation 2023-01-04 21:51:28 +01:00
6c5e72326c refactor(ci): use newer github actions and ubuntu v22 image 2023-01-04 21:51:13 +01:00
8bbeeeb4ce fix: integration tests in release mode 2023-01-03 16:39:54 +01:00
7a09e9bcc8 chore: install namelink to dev component 2023-01-03 15:24:17 +01:00
c812d03bc7 fix: integration tests for libsystemd v251 2023-01-03 15:20:30 +01:00
e7d4e07926 fix: broken D-Bus specs link in README (#297)
- D-Bus specification broken url link is corrected

Signed-off-by: Bhavith C <bhavithc.acharya@gmail.com>

Signed-off-by: Bhavith C <bhavithc.acharya@gmail.com>
2023-01-02 15:31:08 +01:00
031f4687ca fix: compilation warnings 2022-09-21 15:37:57 +02:00
aeae79003a refactor: support move semantics in generated adaptor and proxy classes 2022-09-20 17:05:59 +02:00
74d849d933 feat: add support for proxy with no event loop thread 2022-09-05 17:25:37 +02:00
f336811fc7 docs: add section on D-Bus types to C++ types mapping (#285)
* Added D-Bus type C++ type table

* docs: do corrections and rewordings in the proposed chapter

Co-authored-by: Stanislav Angelovič <stanislav.angelovic@protonmail.com>
2022-09-05 13:51:05 +02:00
100 changed files with 8645 additions and 4068 deletions

23
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,23 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Real (buggy) behavior**
A clear and concise description of what really happened in contrast to your expectation.
**Additional context**
Add any other context about the problem here, like version of sdbus-c++ library, version of systemd used, OS used.

View File

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@ -4,7 +4,9 @@ on:
push: push:
branches: [ master ] branches: [ master ]
pull_request: pull_request:
branches: [ master ] branches:
- master
- release/v2.0
jobs: jobs:
build: build:
@ -12,15 +14,21 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [ubuntu-18.04, ubuntu-20.04] os: [ubuntu-20.04, ubuntu-22.04]
compiler: [g++, clang] compiler: [g++]
build: [shared-libsystemd] build: [shared-libsystemd]
include: include:
- os: ubuntu-20.04 - os: ubuntu-22.04
compiler: clang
build: shared-libsystemd
- os: ubuntu-22.04
compiler: g++ compiler: g++
build: embedded-static-libsystemd build: embedded-static-libsystemd
- os: ubuntu-22.04
compiler: clang
build: embedded-static-libsystemd
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: install-libsystemd-toolchain - name: install-libsystemd-toolchain
if: matrix.build == 'embedded-static-libsystemd' if: matrix.build == 'embedded-static-libsystemd'
run: | run: |
@ -39,44 +47,80 @@ jobs:
sudo update-alternatives --install /usr/bin/cc cc /usr/bin/clang 10 sudo update-alternatives --install /usr/bin/cc cc /usr/bin/clang 10
sudo update-alternatives --remove-all c++ sudo update-alternatives --remove-all c++
sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++ 10 sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++ 10
# We need to use libc++ with Clang because there is a bug in libstdc++ chrono headers that Clang has issues with
echo "SDBUSCPP_EXTRA_CXX_FLAGS=-stdlib=libc++" >> $GITHUB_ENV
# We don't install googletest but we let it be built within sdbus-c++ builds below, since it needs to be built against libc++ for Clang jobs to pass
# - name: install-googletest
# if: matrix.os == 'ubuntu-22.04'
# run: |
# sudo apt-get install -y libgmock-dev
# - name: install-googletest
# if: matrix.os == 'ubuntu-20.04' # On older ubuntus the libgmock-dev package is either unavailable or has faulty pkg-config file, so we build & install manually
# run: |
# git clone https://github.com/google/googletest.git
# cd googletest
# mkdir build
# cd build
# cmake .. -DCMAKE_CXX_FLAGS="$SDBUSCPP_EXTRA_CXX_FLAGS"
# cmake --build . -j4
# sudo cmake --build . --target install
- name: configure-debug - name: configure-debug
if: matrix.build == 'shared-libsystemd' && matrix.os == 'ubuntu-18.04'
run: |
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="-O0 -g -W -Wextra -Wall -Wnon-virtual-dtor -Werror" -DBUILD_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-20.04' if: matrix.build == 'shared-libsystemd' && matrix.os == 'ubuntu-20.04'
run: | run: |
mkdir build mkdir build
cd build cd build
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_CXX_FLAGS="-O3 -DNDEBUG -W -Wextra -Wall -Wnon-virtual-dtor -Werror" -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 $SDBUSCPP_EXTRA_CXX_FLAGS" -DCMAKE_VERBOSE_MAKEFILE=ON -DSDBUSCPP_INSTALL=ON -DSDBUSCPP_BUILD_TESTS=ON -DSDBUSCPP_BUILD_PERF_TESTS=ON -DSDBUSCPP_BUILD_STRESS_TESTS=ON -DSDBUSCPP_BUILD_CODEGEN=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_INSTALL_PREFIX:PATH=/usr -DCMAKE_CXX_FLAGS="-O3 -DNDEBUG -W -Wextra -Wall -Wnon-virtual-dtor -Werror $SDBUSCPP_EXTRA_CXX_FLAGS" -DCMAKE_VERBOSE_MAKEFILE=ON -DSDBUSCPP_INSTALL=ON -DSDBUSCPP_BUILD_TESTS=ON -DSDBUSCPP_BUILD_PERF_TESTS=ON -DSDBUSCPP_BUILD_STRESS_TESTS=ON -DSDBUSCPP_BUILD_CODEGEN=ON -DSDBUSCPP_GOOGLETEST_VERSION=1.14.0 ..
- name: configure-with-embedded-libsystemd - name: configure-with-embedded-libsystemd
if: matrix.build == 'embedded-static-libsystemd' if: matrix.build == 'embedded-static-libsystemd'
run: | run: |
mkdir build mkdir build
cd build cd build
cmake -DCMAKE_BUILD_TYPE=Release -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_CXX_FLAGS="$SDBUSCPP_EXTRA_CXX_FLAGS" -DCMAKE_VERBOSE_MAKEFILE=ON -DSDBUSCPP_INSTALL=ON -DSDBUSCPP_BUILD_TESTS=ON -DSDBUSCPP_BUILD_PERF_TESTS=ON -DSDBUSCPP_BUILD_STRESS_TESTS=ON -DSDBUSCPP_BUILD_CODEGEN=ON -DSDBUSCPP_BUILD_LIBSYSTEMD=ON -DSDBUSCPP_LIBSYSTEMD_VERSION=252 -DSDBUSCPP_GOOGLETEST_VERSION=1.14.0 ..
- name: make - name: make
run: | run: |
cd build cd build
cmake --build . -j2 cmake --build . -j4
- name: verify - name: verify
run: | run: |
cd build cd build
sudo cmake --build . --target install sudo cmake --build . --target install
ctest ctest --output-on-failure
- name: pack - name: pack
if: matrix.build == 'shared-libsystemd' && matrix.os == 'ubuntu-20.04' if: matrix.build == 'shared-libsystemd' && matrix.os == 'ubuntu-20.04'
run: | run: |
cd build cd build
cpack -G DEB cpack -G DEB
- name: 'Upload Artifact' - name: 'Upload Artifact'
if: matrix.build == 'shared-libsystemd' && matrix.os == 'ubuntu-20.04' && matrix.compiler == 'g++' if: matrix.build == 'shared-libsystemd' && matrix.os == 'ubuntu-22.04' && matrix.compiler == 'g++'
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v3
with: with:
name: "debian-packages-${{ matrix.os }}-${{ matrix.compiler }}" name: "debian-packages-${{ matrix.os }}-${{ matrix.compiler }}"
path: | path: |
build/sdbus-c++*.deb build/sdbus-c++*.deb
build/sdbus-c++*.ddeb build/sdbus-c++*.ddeb
retention-days: 10 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 -DSDBUSCPP_INSTALL=ON -DSDBUSCPP_BUILD_CODEGEN=ON -DSDBUSCPP_BUILD_TESTS=ON -DSDBUSCPP_BUILD_PERF_TESTS=ON -DSDBUSCPP_BUILD_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

@ -2,35 +2,128 @@
# PROJECT INFORMATION # PROJECT INFORMATION
#------------------------------- #-------------------------------
cmake_minimum_required(VERSION 3.13) cmake_minimum_required(VERSION 3.14)
project(sdbus-c++ VERSION 1.2.0 LANGUAGES C CXX) project(sdbus-c++ VERSION 2.0.0 LANGUAGES CXX C)
include(GNUInstallDirs) # Installation directories for `install` command and pkgconfig file include(GNUInstallDirs) # Installation directories for `install` command and pkgconfig file
# -------------------------------
# CONFIGURATION OPTIONS
# -------------------------------
option(SDBUSCPP_BUILD_LIBSYSTEMD "Fetch & build libsystemd static library and make it part of libsdbus-c++, instead of searching for it in the system" OFF)
if(NOT SDBUSCPP_BUILD_LIBSYSTEMD)
set(SDBUSCPP_SDBUS_LIB "default" CACHE STRING "sd-bus implementation library to search for and use (default, systemd, elogind, or basu)")
set_property(CACHE SDBUSCPP_SDBUS_LIB PROPERTY STRINGS default systemd elogind basu)
else()
set(SDBUSCPP_LIBSYSTEMD_VERSION "242" CACHE STRING "libsystemd version (>=239) to build and incorporate into libsdbus-c++")
set(SDBUSCPP_LIBSYSTEMD_EXTRA_CONFIG_OPTS "" CACHE STRING "Additional configuration options to be passed as-is to libsystemd build system")
endif()
option(SDBUSCPP_INSTALL "Enable installation of sdbus-c++ (downstream projects embedding sdbus-c++ may want to turn this OFF)" ON)
option(SDBUSCPP_BUILD_TESTS "Build tests" OFF)
if (SDBUSCPP_BUILD_TESTS)
option(SDBUSCPP_BUILD_PERF_TESTS "Build also sdbus-c++ performance tests" OFF)
option(SDBUSCPP_BUILD_STRESS_TESTS "Build also sdbus-c++ stress tests" OFF)
set(SDBUSCPP_TESTS_INSTALL_PATH "tests/${PROJECT_NAME}" CACHE STRING "Specifies where the test binaries will be installed")
set(SDBUSCPP_GOOGLETEST_VERSION 1.10.0 CACHE STRING "Version of gmock library to use")
set(SDBUSCPP_GOOGLETEST_GIT_REPO "https://github.com/google/googletest.git" CACHE STRING "A git repo to clone and build googletest from if gmock is not found in the system")
endif()
option(SDBUSCPP_BUILD_CODEGEN "Build generator tool for C++ native bindings" OFF)
option(SDBUSCPP_BUILD_EXAMPLES "Build example programs" OFF)
option(SDBUSCPP_BUILD_DOCS "Build documentation for sdbus-c++" ON)
if(SDBUSCPP_BUILD_DOCS)
option(SDBUSCPP_BUILD_DOXYGEN_DOCS "Build doxygen documentation for sdbus-c++ API" OFF)
endif()
#option(SDBUSCPP_CLANG_TIDY "Co-compile with clang-tidy static analyzer" OFF)
#option(SDBUSCPP_COVERAGE "Build sdbus-c++ with code coverage instrumentation" OFF)
# 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()
message(STATUS "")
message(STATUS " *******************************************")
message(STATUS " * SDBUS-C++ CONFIGURATION *")
message(STATUS " *******************************************")
message(STATUS "")
message(STATUS " SDBUSCPP_BUILD_LIBSYSTEMD: ${SDBUSCPP_BUILD_LIBSYSTEMD}")
if(SDBUSCPP_BUILD_LIBSYSTEMD)
message(STATUS " SDBUSCPP_LIBSYSTEMD_VERSION: ${SDBUSCPP_LIBSYSTEMD_VERSION}")
message(STATUS " SDBUSCPP_LIBSYSTEMD_EXTRA_CONFIG_OPTS: ${SDBUSCPP_LIBSYSTEMD_EXTRA_CONFIG_OPTS}")
endif()
message(STATUS " SDBUSCPP_INSTALL: ${SDBUSCPP_INSTALL}")
message(STATUS " SDBUSCPP_BUILD_TESTS: ${SDBUSCPP_BUILD_TESTS}")
if(SDBUSCPP_BUILD_TESTS)
message(STATUS " SDBUSCPP_BUILD_PERF_TESTS: ${SDBUSCPP_BUILD_PERF_TESTS}")
message(STATUS " SDBUSCPP_BUILD_STRESS_TESTS: ${SDBUSCPP_BUILD_STRESS_TESTS}")
message(STATUS " SDBUSCPP_TESTS_INSTALL_PATH: ${SDBUSCPP_TESTS_INSTALL_PATH}")
message(STATUS " SDBUSCPP_GOOGLETEST_VERSION: ${SDBUSCPP_GOOGLETEST_VERSION}")
message(STATUS " SDBUSCPP_GOOGLETEST_GIT_REPO: ${SDBUSCPP_GOOGLETEST_GIT_REPO}")
endif()
message(STATUS " SDBUSCPP_BUILD_CODEGEN: ${SDBUSCPP_BUILD_CODEGEN}")
message(STATUS " SDBUSCPP_BUILD_EXAMPLES: ${SDBUSCPP_BUILD_EXAMPLES}")
message(STATUS " SDBUSCPP_BUILD_DOCS: ${SDBUSCPP_BUILD_DOCS}")
if(SDBUSCPP_BUILD_DOCS)
message(STATUS " SDBUSCPP_BUILD_DOXYGEN_DOCS: ${SDBUSCPP_BUILD_DOXYGEN_DOCS}")
endif()
message(STATUS " BUILD_SHARED_LIBS: ${BUILD_SHARED_LIBS}")
message(STATUS "")
#------------------------------- #-------------------------------
# PERFORMING CHECKS & PREPARING THE DEPENDENCIES # PERFORMING CHECKS & PREPARING THE DEPENDENCIES
#------------------------------- #-------------------------------
option(BUILD_LIBSYSTEMD "Build libsystemd static library and incorporate it into libsdbus-c++" OFF) if(SDBUSCPP_BUILD_LIBSYSTEMD)
# Build static libsystemd library as an external project
include(cmake/LibsystemdExternalProject.cmake)
set(SDBUS_IMPL "systemd")
set(SDBUS_LIB "libsystemd")
else()
# Search for sd-bus implementations in the system as per user's configuration
set(SDBUS_LIBS ${SDBUSCPP_SDBUS_LIB})
if(SDBUSCPP_SDBUS_LIB STREQUAL "default")
set(SDBUS_LIBS systemd elogind basu) # This is the default search order
endif()
if(NOT BUILD_LIBSYSTEMD)
find_package(PkgConfig REQUIRED) find_package(PkgConfig REQUIRED)
pkg_check_modules(Systemd IMPORTED_TARGET GLOBAL libsystemd>=236) foreach(LIB ${SDBUS_LIBS})
if(LIB STREQUAL "systemd")
pkg_check_modules(Systemd IMPORTED_TARGET GLOBAL libsystemd>=238)
if(TARGET PkgConfig::Systemd)
set(SDBUS_IMPL "systemd")
set(SDBUS_LIB "libsystemd")
break()
endif()
elseif(LIB STREQUAL "elogind")
pkg_check_modules(Systemd IMPORTED_TARGET GLOBAL libelogind>=238)
if(TARGET PkgConfig::Systemd)
set(SDBUS_IMPL "elogind")
set(SDBUS_LIB "libelogind")
string(REPLACE "." ";" VERSION_LIST ${Systemd_VERSION})
list(GET VERSION_LIST 0 Systemd_VERSION)
break()
endif()
elseif(LIB STREQUAL "basu")
pkg_check_modules(Systemd IMPORTED_TARGET GLOBAL basu)
if(TARGET PkgConfig::Systemd)
set(SDBUS_IMPL "basu")
set(SDBUS_LIB "basu")
set(Systemd_VERSION "240") # https://git.sr.ht/~emersion/basu/commit/d4d185d29a26
break()
endif()
else()
message(FATAL_ERROR "Unrecognized sd-bus implementation library ${LIB}")
endif()
endforeach()
if(NOT TARGET PkgConfig::Systemd) if(NOT TARGET PkgConfig::Systemd)
message(FATAL_ERROR "libsystemd of version at least 236 is required, but was not found " message(FATAL_ERROR "None of ${SDBUS_LIBS} libraries implementing sd-bus was found (Are their dev packages installed?)")
"(if you have systemd in your OS, you may want to install package containing pkgconfig "
" files for libsystemd library. On Ubuntu, that is libsystemd-dev. "
" Alternatively, you may turn BUILD_LIBSYSTEMD on for sdbus-c++ to download, build "
"and incorporate libsystemd as embedded library within sdbus-c++)")
endif() endif()
add_library(Systemd::Libsystemd ALIAS PkgConfig::Systemd) add_library(Systemd::Libsystemd ALIAS PkgConfig::Systemd)
string(REGEX MATCHALL "([0-9]+)" SYSTEMD_VERSION_LIST "${Systemd_VERSION}") string(REGEX MATCHALL "([0-9]+)" SYSTEMD_VERSION_LIST "${Systemd_VERSION}")
list(GET SYSTEMD_VERSION_LIST 0 LIBSYSTEMD_VERSION) list(GET SYSTEMD_VERSION_LIST 0 SDBUSCPP_LIBSYSTEMD_VERSION)
message(STATUS "Building with libsystemd v${LIBSYSTEMD_VERSION}") message(STATUS "Building with libsystemd v${SDBUSCPP_LIBSYSTEMD_VERSION}")
else()
# Build static libsystemd library as an external project
include(cmake/LibsystemdExternalProject.cmake)
endif() endif()
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
@ -69,6 +162,8 @@ set(SDBUSCPP_HDR_SRCS
set(SDBUSCPP_PUBLIC_HDRS set(SDBUSCPP_PUBLIC_HDRS
${SDBUSCPP_INCLUDE_DIR}/ConvenienceApiClasses.h ${SDBUSCPP_INCLUDE_DIR}/ConvenienceApiClasses.h
${SDBUSCPP_INCLUDE_DIR}/ConvenienceApiClasses.inl ${SDBUSCPP_INCLUDE_DIR}/ConvenienceApiClasses.inl
${SDBUSCPP_INCLUDE_DIR}/VTableItems.h
${SDBUSCPP_INCLUDE_DIR}/VTableItems.inl
${SDBUSCPP_INCLUDE_DIR}/Error.h ${SDBUSCPP_INCLUDE_DIR}/Error.h
${SDBUSCPP_INCLUDE_DIR}/IConnection.h ${SDBUSCPP_INCLUDE_DIR}/IConnection.h
${SDBUSCPP_INCLUDE_DIR}/AdaptorInterfaces.h ${SDBUSCPP_INCLUDE_DIR}/AdaptorInterfaces.h
@ -89,7 +184,7 @@ set(SDBUSCPP_SRCS ${SDBUSCPP_CPP_SRCS} ${SDBUSCPP_HDR_SRCS} ${SDBUSCPP_PUBLIC_HD
# GENERAL COMPILER CONFIGURATION # GENERAL COMPILER CONFIGURATION
#------------------------------- #-------------------------------
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 20)
#---------------------------------- #----------------------------------
# LIBRARY BUILD INFORMATION # LIBRARY BUILD INFORMATION
@ -98,20 +193,19 @@ set(CMAKE_CXX_STANDARD 17)
set(SDBUSCPP_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}") set(SDBUSCPP_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}")
set(SDBUSCPP_VERSION "${PROJECT_VERSION}") set(SDBUSCPP_VERSION "${PROJECT_VERSION}")
# We promote BUILD_SHARED_LIBS flags to (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 # Having an object target allows unit tests to reuse already built sources without re-building
add_library(sdbus-c++-objlib OBJECT ${SDBUSCPP_SRCS}) 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=${SDBUSCPP_LIBSYSTEMD_VERSION}
SDBUS_${SDBUS_IMPL}
SDBUS_HEADER=<${SDBUS_IMPL}/sd-bus.h>)
target_include_directories(sdbus-c++-objlib PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> target_include_directories(sdbus-c++-objlib PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>) $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>)
if(DEFINED BUILD_SHARED_LIBS) if(BUILD_SHARED_LIBS)
set_target_properties(sdbus-c++-objlib PROPERTIES POSITION_INDEPENDENT_CODE ${BUILD_SHARED_LIBS}) set_target_properties(sdbus-c++-objlib PROPERTIES POSITION_INDEPENDENT_CODE ON)
endif() endif()
if(BUILD_LIBSYSTEMD) if(SDBUSCPP_BUILD_LIBSYSTEMD)
add_dependencies(sdbus-c++-objlib LibsystemdBuildProject) add_dependencies(sdbus-c++-objlib LibsystemdBuildProject)
endif() endif()
target_link_libraries(sdbus-c++-objlib target_link_libraries(sdbus-c++-objlib
@ -129,28 +223,11 @@ set_target_properties(sdbus-c++
OUTPUT_NAME "sdbus-c++") OUTPUT_NAME "sdbus-c++")
target_link_libraries(sdbus-c++ PRIVATE sdbus-c++-objlib) target_link_libraries(sdbus-c++ PRIVATE sdbus-c++-objlib)
#----------------------------------
# INSTALLATION
#----------------------------------
set(EXPORT_SET sdbus-c++)
if(NOT BUILD_SHARED_LIBS)
list(APPEND EXPORT_SET "sdbus-c++-objlib")
endif()
install(TARGETS ${EXPORT_SET}
EXPORT sdbus-c++-targets
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT runtime
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT dev
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${SDBUSCPP_INCLUDE_SUBDIR} COMPONENT dev)
#---------------------------------- #----------------------------------
# TESTS # TESTS
#---------------------------------- #----------------------------------
option(BUILD_TESTS "Build and install tests (default OFF)" OFF) if(SDBUSCPP_BUILD_TESTS)
if(BUILD_TESTS)
message(STATUS "Building with tests") message(STATUS "Building with tests")
enable_testing() enable_testing()
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/tests") add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/tests")
@ -160,9 +237,7 @@ endif()
# UTILS # UTILS
#---------------------------------- #----------------------------------
option(BUILD_CODE_GEN "Build and install interface stub code generator (default OFF)" OFF) if(SDBUSCPP_BUILD_CODEGEN)
if(BUILD_CODE_GEN)
message(STATUS "Building with code generator tool") message(STATUS "Building with code generator tool")
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/tools") add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/tools")
endif() endif()
@ -171,9 +246,7 @@ endif()
# EXAMPLES # EXAMPLES
#---------------------------------- #----------------------------------
option(BUILD_EXAMPLES "Build example programs (default OFF)" OFF) if(SDBUSCPP_BUILD_EXAMPLES)
if(BUILD_EXAMPLES)
message(STATUS "Building with examples") message(STATUS "Building with examples")
add_subdirectory(examples) add_subdirectory(examples)
endif() endif()
@ -182,13 +255,9 @@ endif()
# DOCUMENTATION # DOCUMENTATION
#---------------------------------- #----------------------------------
option(BUILD_DOC "Build documentation for sdbus-c++" ON) if(SDBUSCPP_BUILD_DOCS)
if(BUILD_DOC)
message(STATUS "Building with documentation") message(STATUS "Building with documentation")
option(BUILD_DOXYGEN_DOC "Build doxygen documentation for sdbus-c++ API" OFF)
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/docs") add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/docs")
install(FILES README README.md NEWS COPYING ChangeLog AUTHORS DESTINATION ${CMAKE_INSTALL_DOCDIR} COMPONENT doc)
endif() endif()
#---------------------------------- #----------------------------------
@ -197,38 +266,58 @@ endif()
include(CMakePackageConfigHelpers) include(CMakePackageConfigHelpers)
install(EXPORT sdbus-c++-targets
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/sdbus-c++
NAMESPACE SDBusCpp::
COMPONENT dev)
configure_package_config_file(cmake/sdbus-c++-config.cmake.in cmake/sdbus-c++-config.cmake configure_package_config_file(cmake/sdbus-c++-config.cmake.in cmake/sdbus-c++-config.cmake
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/sdbus-c++) INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/sdbus-c++)
write_basic_package_version_file(cmake/sdbus-c++-config-version.cmake COMPATIBILITY SameMajorVersion) write_basic_package_version_file(cmake/sdbus-c++-config-version.cmake COMPATIBILITY SameMajorVersion)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/cmake/sdbus-c++-config.cmake
${CMAKE_CURRENT_BINARY_DIR}/cmake/sdbus-c++-config-version.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/sdbus-c++
COMPONENT dev)
if(BUILD_SHARED_LIBS AND (BUILD_LIBSYSTEMD OR Systemd_LINK_LIBRARIES MATCHES "/libsystemd\.a(;|$)")) if(BUILD_SHARED_LIBS AND (SDBUSCPP_BUILD_LIBSYSTEMD OR Systemd_LINK_LIBRARIES MATCHES "/libsystemd\.a(;|$)"))
set(PKGCONFIG_REQS ".private") set(PKGCONFIG_REQS ".private")
else() else()
set(PKGCONFIG_REQS "") set(PKGCONFIG_REQS "")
endif() endif()
set(PKGCONFIG_DEPS ${SDBUS_LIB})
configure_file(pkgconfig/sdbus-c++.pc.in pkgconfig/sdbus-c++.pc @ONLY) 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)
#---------------------------------- #----------------------------------
# CPack # INSTALLATION
#---------------------------------- #----------------------------------
if(SDBUSCPP_INSTALL)
set(EXPORT_SET sdbus-c++)
if(NOT BUILD_SHARED_LIBS)
list(APPEND EXPORT_SET "sdbus-c++-objlib")
endif()
install(TARGETS ${EXPORT_SET}
EXPORT sdbus-c++-targets
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT sdbus-c++-runtime
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT sdbus-c++-runtime NAMELINK_COMPONENT sdbus-c++-dev
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT sdbus-c++-dev
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${SDBUSCPP_INCLUDE_SUBDIR} COMPONENT sdbus-c++-dev)
install(EXPORT sdbus-c++-targets
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/sdbus-c++
NAMESPACE SDBusCpp::
COMPONENT sdbus-c++-dev)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/cmake/sdbus-c++-config.cmake
${CMAKE_CURRENT_BINARY_DIR}/cmake/sdbus-c++-config-version.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/sdbus-c++
COMPONENT sdbus-c++-dev)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/pkgconfig/sdbus-c++.pc
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig COMPONENT sdbus-c++-dev)
if(SDBUSCPP_BUILD_DOCS)
install(FILES README README.md NEWS COPYING ChangeLog AUTHORS DESTINATION ${CMAKE_INSTALL_DOCDIR} COMPONENT sdbus-c++-doc)
endif()
endif()
#----------------------------------
# CPACK
#----------------------------------
set(CPACK_PACKAGE_VENDOR "Kistler") set(CPACK_PACKAGE_VENDOR "Kistler")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "high-level C++ D-Bus library") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "High-level C++ D-Bus library")
set(CPACK_PACKAGE_CONTACT "info@kistler.com") set(CPACK_PACKAGE_CONTACT "info@kistler.com")
set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md") set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
set(CPACK_COMPONENTS_ALL runtime dev doc) set(CPACK_COMPONENTS_ALL runtime dev doc)
set(CPACK_COMPONENT_DEV_DEPENDS "runtime") set(CPACK_COMPONENT_DEV_DEPENDS "runtime")
# specific for DEB generator # specific for DEB generator
set(CPACK_DEB_COMPONENT_INSTALL ON) set(CPACK_DEB_COMPONENT_INSTALL ON)
set(CPACK_DEBIAN_RUNTIME_DEBUGINFO_PACKAGE ON) set(CPACK_DEBIAN_RUNTIME_DEBUGINFO_PACKAGE ON)

View File

@ -4,7 +4,7 @@ As an additional permission to the GNU Lesser General Public License version
2.1, the object code form of a "work that uses the Library" may incorporate 2.1, the object code form of a "work that uses the Library" may incorporate
material from a header file that is part of the Library. You may distribute material from a header file that is part of the Library. You may distribute
such object code under terms of your choice, provided that: such object code under terms of your choice, provided that:
(i) the header files of the Library have not been modified; and (i) the header files of the Library have not been modified; and
(ii) the incorporated material is limited to numerical parameters, data (ii) the incorporated material is limited to numerical parameters, data
structure layouts, accessors, macros, inline functions and structure layouts, accessors, macros, inline functions and
templates; and templates; and

View File

@ -216,3 +216,78 @@ v1.2.0
- Add printer for std::chrono in googletest v1.11.0 - Add printer for std::chrono in googletest v1.11.0
- Fix potential undefined behavior in creation of sdbus::Error - Fix potential undefined behavior in creation of sdbus::Error
- Additional little fixes and improvements in code, build system, and documentation - Additional little fixes and improvements in code, build system, and documentation
v1.3.0
- Add support for light-weight proxies (proxies without own event loop threads)
- Extend documentation with explicit mapping between D-Bus and corresponding C++ types
- Support move semantics in generated adaptor and proxy classes
- Adaptations for libsystemd v251
- Fix for proper complete sending of long D-Bus messages by explicitly flushing them
- Add support for std::future-based async calls
- Fix race condition in async Proxy::callMethod
- Fix pseudo-connection static lifetime issue with Phoenix pattern
- Speed up performance of of serialization of arrays of trivial D-Bus types
- Make sdbus::Struct a tuple-like class, so it's usable wherever std::tuple is
- Add support for std::array, std::span and std::unordered_map as additional C++ types for D-Bus array types
- 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
v1.5.0
- Improve handling of exceptions from callback handlers
- Add support for async registration of matches
- Correctly add libsystemd dependency to pkgconfi
- Fix request name signal handling issue
- Add INSTALL_TESTS CMake option
- Minor UnixFd cleanups
- Additional little fixes and updates in code, build system, CI, and documentation
v1.6.0
- Add support for enums in D-Bus serialization and signatures
- Add support for std::variant as an alternative C++ type for D-Bus Variant
- Add support for implicit conversions between std::variant and sdbus::Variant
- Fix missing includes
v2.0.0
- A new major version with revamped API, bringing numerous new features, simplifications, fixes and breaking changes improving not only API consistency, safety and expressiveness, but also performance
- Add section 'Migrating to sdbus-c++ v2' to the 'Using sdbus-c++' document providing the complete list of breaking API/ABI/behavior changes and migration notes
- Switch to C++20 standard (but the API is backwards-compatible with C++17, skipping C++20 features in such a case)
- Add strong types to public API
- Add support for string_view as a D-Bus type representation
- Enable compile-time D-Bus signature generation
- Redesign Object vtable registration API
- Improve Proxy signal subscription API
- Refactor object manager API for consistency
- Introduce native sd-event integration
- Let callbacks take message objects by value
- Simplify async D-Bus connection handling
- Simplify async call state flag
- Make Variant constructor explicit
- Use optional for passing potential call errors
- Rename and re-organize CMake options
- Rename connection creation methods
- Have all async function names finish with *Async for consistency
- Implement more flexible searching for sd-bus libs
- Add new `SDBUSCPP_SDBUS_LIB` CMake configuration variable determining which sd-bus library shall be picked
- Make Variant conversion operator explicit
- Use sd-bus API to get the current message
- Use sd_bus_match_signal() for signal registration
- Provide also const char* overloads for convenience functions
- Disable move in generated adaptor and proxy classes
- Improve and make more efficient some Message API
- Add Slot-returning overloads of async method calls
- Remove floating_slot_t tag and use return_slot_t instead
- Add [[nodiscard]] to many relevant API functions
- Add proper fix for timeout handling
- Fix for external event loops in which the event loop thread ID was not correctly initialized (now fixed and simplified by not needing the thread ID anymore)
- Remove deprecated stuff
- Add `SDBUSCPP_` prefix to CMake configuration variables to avoid conflicts with downstream projects
- Require systemd of at least v238
- Many other fixes and updates in code, tests, build system, CI, and documentation

View File

@ -26,11 +26,11 @@ $ sudo cmake --build . --target install
### CMake configuration flags for sdbus-c++ ### CMake configuration flags for sdbus-c++
* `BUILD_CODE_GEN` [boolean] * `SDBUSCPP_BUILD_CODEGEN` [boolean]
Option for building the stub code generator `sdbus-c++-xml2cpp` for generating the adaptor and proxy interfaces out of the D-Bus IDL XML description. Default value: `OFF`. Use `-DBUILD_CODE_GEN=ON` flag to turn on building the code gen. Option for building the stub code generator `sdbus-c++-xml2cpp` for generating the adaptor and proxy interfaces out of the D-Bus IDL XML description. Default value: `OFF`. Use `-DSDBUSCPP_BUILD_CODEGEN=ON` flag to turn on building the code gen.
* `BUILD_DOC` [boolean] * `SDBUSCPP_BUILD_DOCS` [boolean]
Option for including sdbus-c++ documentation files and tutorials. Default value: `ON`. With this option turned on, you may also enable/disable the following option: Option for including sdbus-c++ documentation files and tutorials. Default value: `ON`. With this option turned on, you may also enable/disable the following option:
@ -38,31 +38,41 @@ $ sudo cmake --build . --target install
Option for building Doxygen documentation of sdbus-c++ API. If enabled, the documentation must still be built explicitly through `cmake --build . --target doc`. Default value: `OFF`. Use `-DBUILD_DOXYGEN_DOC=OFF` to disable searching for Doxygen and building Doxygen documentation of sdbus-c++ API. Option for building Doxygen documentation of sdbus-c++ API. If enabled, the documentation must still be built explicitly through `cmake --build . --target doc`. Default value: `OFF`. Use `-DBUILD_DOXYGEN_DOC=OFF` to disable searching for Doxygen and building Doxygen documentation of sdbus-c++ API.
* `BUILD_TESTS` [boolean] * `SDBUSCPP_BUILD_TESTS` [boolean]
Option for building sdbus-c++ unit and integration tests, invokable by `cmake --build . --target test` (Note: before invoking `cmake --build . --target test`, make sure you copy `tests/integrationtests/files/org.sdbuscpp.integrationtests.conf` file to `/etc/dbus-1/system.d` directory). That incorporates downloading and building static libraries of Google Test. Default value: `OFF`. Use `-DBUILD_TESTS=ON` to enable building the tests. With this option turned on, you may also enable/disable the following options: Option for building sdbus-c++ unit and integration tests, invokable by `cmake --build . --target test` (Note: before invoking `cmake --build . --target test`, make sure you copy `tests/integrationtests/files/org.sdbuscpp.integrationtests.conf` file to `/etc/dbus-1/system.d` directory). That incorporates downloading and building static libraries of Google Test. Default value: `OFF`. Use `-DBUILD_TESTS=ON` to enable building the tests. With this option turned on, you may also enable/disable the following options:
* `ENABLE_PERF_TESTS` [boolean] * `SDBUSCPP_BUILD_PERF_TESTS` [boolean]
Option for building sdbus-c++ performance tests. Default value: `OFF`. Option for building sdbus-c++ performance tests. Default value: `OFF`.
* `ENABLE_STRESS_TESTS` [boolean] * `SDBUSCPP_BUILD_STRESS_TESTS` [boolean]
Option for building sdbus-c++ stress tests. Default value: `OFF`. Option for building sdbus-c++ stress tests. Default value: `OFF`.
* `TESTS_INSTALL_PATH` [string] * `SDBUSCPP_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] * `SDBUSCPP_BUILD_LIBSYSTEMD` [boolean]
Option for building libsystemd dependency library automatically when sdbus-c++ is built, and making libsystemd an integral part of sdbus-c++ library. Default value: `OFF`. Might be very helpful in non-systemd environments where libsystemd shared library is unavailable (see [Solving libsystemd dependency](docs/using-sdbus-c++.md#solving-libsystemd-dependency) for more information). With this option turned on, you may also provide the following configuration flag: Option for building libsystemd as a sd-bus implementation when sdbus-c++ is built, and making libsystemd an integral part of sdbus-c++ library. Default value: `OFF`, which means that the sd-bus implementation library (`libsystemd`, `libelogind`, or `basu`) will be searched via `pkg-config` in the system.
* `LIBSYSTEMD_VERSION` [string] This option may be very helpful in environments where sd-bus implementation library is unavailable (see [Solving sd-bus dependency](docs/using-sdbus-c++.md#solving-sd-bus-dependency) for more information).
With this option turned off, you may provide the following additional configuration flag:
* `SDBUSCPP_SDBUS_LIB` [string]
Defines which sd-bus implementation library to search for and use. Allowed values: `default`, `systemd`, `elogind`, `basu`. Default value: `default`, which means that sdbus-c++ will try to find any of `systemd`, `elogind`, `basu` in the order as listed here.
With this option turned on, you may provide the following additional configuration flag:
* `SDBUSCPP_LIBSYSTEMD_VERSION` [string]
Defines version of systemd to be downloaded, built and integrated into sdbus-c++. Default value: `242`. Defines version of systemd to be downloaded, built and integrated into sdbus-c++. Default value: `242`.
* `LIBSYSTEMD_EXTRA_CONFIG_OPTS` [string] * `SDBUSCPP_LIBSYSTEMD_EXTRA_CONFIG_OPTS` [string]
Additional options to be passed as-is to the libsystemd build system (meson for systemd v242) in its configure step. Can be used for passing e.g. toolchain file path in case of cross builds. Default value: empty. Additional options to be passed as-is to the libsystemd build system (meson for systemd v242) in its configure step. Can be used for passing e.g. toolchain file path in case of cross builds. Default value: empty.
@ -74,7 +84,7 @@ $ sudo cmake --build . --target install
This is a global CMake flag, promoted in sdbus-c++ project to a CMake option. Use this to control whether sdbus-c++ is built as either a shared or static library. Default value: `ON`. This is a global CMake flag, promoted in sdbus-c++ project to a CMake option. Use this to control whether sdbus-c++ is built as either a shared or static library. Default value: `ON`.
* `BUILD_EXAMPLES` [boolean] * `SDBUSCPP_BUILD_EXAMPLES` [boolean]
Build example programs which are located in the _example_ directory. Examples are not installed. Default value: `OFF` Build example programs which are located in the _example_ directory. Examples are not installed. Default value: `OFF`
@ -82,10 +92,10 @@ Dependencies
------------ ------------
* `C++17` - the library uses C++17 features. * `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 you're missing any of those sd-bus implementations, don't worry, see [Solving sd-bus dependency](docs/using-sdbus-c++.md#solving-sd-bus-dependency) for more information.)
* `googletest` - google unit testing framework, only necessary when building tests, will be downloaded and built automatically. * `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. * `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). * `expat` - necessary when building the xml2cpp binding code generator (`SDBUSCPP_BUILD_CODEGEN` option is `ON`).
Licensing Licensing
--------- ---------
@ -97,7 +107,7 @@ References/documentation
* [Using sdbus-c++](docs/using-sdbus-c++.md) - *the* main, comprehensive tutorial on sdbus-c++ * [Using sdbus-c++](docs/using-sdbus-c++.md) - *the* main, comprehensive tutorial on sdbus-c++
* [Systemd and dbus configuration](docs/systemd-dbus-config.md) * [Systemd and dbus configuration](docs/systemd-dbus-config.md)
* [D-Bus Specification](https://dbus.freedesktop.org/docs/dbus-specification.html) * [D-Bus Specification](https://dbus.freedesktop.org/doc/dbus-specification.html)
* [sd-bus Overview](http://0pointer.net/blog/the-new-sd-bus-api-of-systemd.html) * [sd-bus Overview](http://0pointer.net/blog/the-new-sd-bus-api-of-systemd.html)
Contributing Contributing

View File

@ -18,9 +18,6 @@ if (NOT CAP_FOUND)
find_library(CAP_LIBRARIES cap) # Compat with Ubuntu 14.04 which ships libcap w/o .pc file find_library(CAP_LIBRARIES cap) # Compat with Ubuntu 14.04 which ships libcap w/o .pc file
endif() endif()
set(LIBSYSTEMD_VERSION "242" CACHE STRING "libsystemd version (>=239) to build and incorporate into libsdbus-c++")
set(LIBSYSTEMD_EXTRA_CONFIG_OPTS "" CACHE STRING "Additional configuration options to be passed as-is to libsystemd build system")
if(NOT CMAKE_BUILD_TYPE) if(NOT CMAKE_BUILD_TYPE)
set(LIBSYSTEMD_BUILD_TYPE "plain") set(LIBSYSTEMD_BUILD_TYPE "plain")
elseif(CMAKE_BUILD_TYPE STREQUAL "Debug") elseif(CMAKE_BUILD_TYPE STREQUAL "Debug")
@ -29,24 +26,24 @@ else()
set(LIBSYSTEMD_BUILD_TYPE "release") set(LIBSYSTEMD_BUILD_TYPE "release")
endif() endif()
if(LIBSYSTEMD_VERSION LESS "239") if(SDBUSCPP_LIBSYSTEMD_VERSION LESS "239")
message(FATAL_ERROR "Only libsystemd version >=239 can be built as static part of sdbus-c++") message(FATAL_ERROR "Only libsystemd version >=239 can be built as static part of sdbus-c++")
endif() endif()
if(LIBSYSTEMD_VERSION GREATER "240") if(SDBUSCPP_LIBSYSTEMD_VERSION GREATER "240")
set(BUILD_VERSION_H ${NINJA} -C <BINARY_DIR> version.h) set(BUILD_VERSION_H ${NINJA} -C <BINARY_DIR> version.h)
endif() endif()
message(STATUS "Building with embedded libsystemd v${LIBSYSTEMD_VERSION}") message(STATUS "Building with embedded libsystemd v${SDBUSCPP_LIBSYSTEMD_VERSION}")
include(ExternalProject) include(ExternalProject)
ExternalProject_Add(LibsystemdBuildProject ExternalProject_Add(LibsystemdBuildProject
PREFIX libsystemd-v${LIBSYSTEMD_VERSION} PREFIX libsystemd-v${SDBUSCPP_LIBSYSTEMD_VERSION}
GIT_REPOSITORY https://github.com/systemd/systemd-stable.git GIT_REPOSITORY https://github.com/systemd/systemd-stable.git
GIT_TAG v${LIBSYSTEMD_VERSION}-stable GIT_TAG v${SDBUSCPP_LIBSYSTEMD_VERSION}-stable
GIT_SHALLOW 1 GIT_SHALLOW 1
UPDATE_COMMAND "" UPDATE_COMMAND ""
CONFIGURE_COMMAND ${CMAKE_COMMAND} -E remove <BINARY_DIR>/* CONFIGURE_COMMAND ${CMAKE_COMMAND} -E remove <BINARY_DIR>/*
COMMAND ${MESON} --prefix=<INSTALL_DIR> --buildtype=${LIBSYSTEMD_BUILD_TYPE} -Dstatic-libsystemd=pic -Dselinux=false <SOURCE_DIR> <BINARY_DIR> ${LIBSYSTEMD_EXTRA_CONFIG_OPTS} COMMAND ${MESON} --prefix=<INSTALL_DIR> --buildtype=${LIBSYSTEMD_BUILD_TYPE} -Drootprefix=<INSTALL_DIR> -Dstatic-libsystemd=pic -Dselinux=false <SOURCE_DIR> <BINARY_DIR> ${SDBUSCPP_LIBSYSTEMD_EXTRA_CONFIG_OPTS}
BUILD_COMMAND ${BUILD_VERSION_H} BUILD_COMMAND ${BUILD_VERSION_H}
COMMAND ${NINJA} -C <BINARY_DIR> libsystemd.a COMMAND ${NINJA} -C <BINARY_DIR> libsystemd.a
BUILD_ALWAYS 0 BUILD_ALWAYS 0

View File

@ -15,15 +15,19 @@ if(BUILD_DOXYGEN_DOC)
# workaround bug https://github.com/doxygen/doxygen/pull/6787 # workaround bug https://github.com/doxygen/doxygen/pull/6787
add_custom_command(TARGET doc POST_BUILD add_custom_command(TARGET doc POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_LIST_DIR}/sdbus-c++-class-diagram.png ${CMAKE_CURRENT_BINARY_DIR}/html/.) COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_LIST_DIR}/sdbus-c++-class-diagram.png ${CMAKE_CURRENT_BINARY_DIR}/html/.)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html DESTINATION ${CMAKE_INSTALL_DOCDIR} OPTIONAL COMPONENT doc)
else() else()
message(WARNING "Documentation enabled, but Doxygen cannot be found") message(WARNING "Documentation enabled, but Doxygen cannot be found")
endif() endif()
endif() endif()
install(FILES sdbus-c++-class-diagram.png if(SDBUSCPP_INSTALL)
sdbus-c++-class-diagram.uml install(FILES sdbus-c++-class-diagram.png
systemd-dbus-config.md sdbus-c++-class-diagram.uml
using-sdbus-c++.md systemd-dbus-config.md
DESTINATION ${CMAKE_INSTALL_DOCDIR} COMPONENT doc) using-sdbus-c++.md
DESTINATION ${CMAKE_INSTALL_DOCDIR} COMPONENT sdbus-c++-doc)
if (BUILD_DOXYGEN_DOC AND DOXYGEN_FOUND)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html DESTINATION ${CMAKE_INSTALL_DOCDIR} OPTIONAL COMPONENT sdbus-c++-doc)
endif()
endif()

View File

@ -162,7 +162,7 @@ FULL_PATH_NAMES = YES
# will be relative from the directory where doxygen is started. # will be relative from the directory where doxygen is started.
# This tag requires that the tag FULL_PATH_NAMES is set to YES. # This tag requires that the tag FULL_PATH_NAMES is set to YES.
STRIP_FROM_PATH = STRIP_FROM_PATH = @PROJECT_SOURCE_DIR@ @PROJECT_BINARY_DIR@
# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
# path mentioned in the documentation of a class, which tells the reader which # path mentioned in the documentation of a class, which tells the reader which

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,12 @@
# Building examples
add_executable(obj-manager-server org.freedesktop.DBus.ObjectManager/obj-manager-server.cpp) add_executable(obj-manager-server org.freedesktop.DBus.ObjectManager/obj-manager-server.cpp)
target_link_libraries(obj-manager-server sdbus-c++) target_link_libraries(obj-manager-server sdbus-c++)
add_executable(obj-manager-client org.freedesktop.DBus.ObjectManager/obj-manager-client.cpp) add_executable(obj-manager-client org.freedesktop.DBus.ObjectManager/obj-manager-client.cpp)
target_link_libraries(obj-manager-client sdbus-c++) target_link_libraries(obj-manager-client sdbus-c++)
if(SDBUSCPP_INSTALL)
install(TARGETS obj-manager-server DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT sdbus-c++-examples)
install(TARGETS obj-manager-client DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT sdbus-c++-examples)
endif()

View File

@ -21,28 +21,37 @@ public:
protected: protected:
Planet1_proxy(sdbus::IProxy& proxy) Planet1_proxy(sdbus::IProxy& proxy)
: proxy_(proxy) : m_proxy(proxy)
{ {
} }
Planet1_proxy(const Planet1_proxy&) = delete;
Planet1_proxy& operator=(const Planet1_proxy&) = delete;
Planet1_proxy(Planet1_proxy&&) = delete;
Planet1_proxy& operator=(Planet1_proxy&&) = delete;
~Planet1_proxy() = default; ~Planet1_proxy() = default;
void registerProxy()
{
}
public: public:
uint64_t GetPopulation() uint64_t GetPopulation()
{ {
uint64_t result; uint64_t result;
proxy_.callMethod("GetPopulation").onInterface(INTERFACE_NAME).storeResultsTo(result); m_proxy.callMethod("GetPopulation").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result; return result;
} }
public: public:
std::string Name() std::string Name()
{ {
return proxy_.getProperty("Name").onInterface(INTERFACE_NAME); return m_proxy.getProperty("Name").onInterface(INTERFACE_NAME).get<std::string>();
} }
private: private:
sdbus::IProxy& proxy_; sdbus::IProxy& m_proxy;
}; };
}}} // namespaces }}} // namespaces

View File

@ -21,14 +21,24 @@ public:
protected: protected:
Planet1_adaptor(sdbus::IObject& object) Planet1_adaptor(sdbus::IObject& object)
: object_(object) : m_object(object)
{ {
object_.registerMethod("GetPopulation").onInterface(INTERFACE_NAME).withOutputParamNames("population").implementedAs([this](){ return this->GetPopulation(); });
object_.registerProperty("Name").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Name(); });
} }
Planet1_adaptor(const Planet1_adaptor&) = delete;
Planet1_adaptor& operator=(const Planet1_adaptor&) = delete;
Planet1_adaptor(Planet1_adaptor&&) = delete;
Planet1_adaptor& operator=(Planet1_adaptor&&) = delete;
~Planet1_adaptor() = default; ~Planet1_adaptor() = default;
void registerAdaptor()
{
m_object.addVTable( sdbus::registerMethod("GetPopulation").withOutputParamNames("population").implementedAs([this](){ return this->GetPopulation(); })
, sdbus::registerProperty("Name").withGetter([this](){ return this->Name(); })
).forInterface(INTERFACE_NAME);
}
private: private:
virtual uint64_t GetPopulation() = 0; virtual uint64_t GetPopulation() = 0;
@ -36,7 +46,7 @@ private:
virtual std::string Name() = 0; virtual std::string Name() = 0;
private: private:
sdbus::IObject& object_; sdbus::IObject& m_object;
}; };
}}} // namespaces }}} // namespaces

View File

@ -17,7 +17,7 @@
class PlanetProxy final : public sdbus::ProxyInterfaces< org::sdbuscpp::ExampleManager::Planet1_proxy > class PlanetProxy final : public sdbus::ProxyInterfaces< org::sdbuscpp::ExampleManager::Planet1_proxy >
{ {
public: public:
PlanetProxy(sdbus::IConnection& connection, std::string destination, std::string path) PlanetProxy(sdbus::IConnection& connection, sdbus::ServiceName destination, sdbus::ObjectPath path)
: ProxyInterfaces(connection, std::move(destination), std::move(path)) : ProxyInterfaces(connection, std::move(destination), std::move(path))
{ {
registerProxy(); registerProxy();
@ -29,13 +29,13 @@ public:
} }
}; };
class ManagerProxy final : public sdbus::ProxyInterfaces< sdbus::ObjectManager_proxy > class ManagerProxy final : public sdbus::ProxyInterfaces<sdbus::ObjectManager_proxy>
{ {
public: public:
ManagerProxy(sdbus::IConnection& connection, const std::string& destination, std::string path) ManagerProxy(sdbus::IConnection& connection, sdbus::ServiceName destination, sdbus::ObjectPath path)
: ProxyInterfaces(connection, destination, std::move(path)) : ProxyInterfaces(connection, destination, std::move(path))
, m_connection(connection) , m_connection(connection)
, m_destination(destination) , m_destination(destination)
{ {
registerProxy(); registerProxy();
} }
@ -47,8 +47,7 @@ public:
void handleExistingObjects() void handleExistingObjects()
{ {
std::map<sdbus::ObjectPath, std::map<std::string, std::map<std::string, sdbus::Variant>>> objectsInterfacesAndProperties; auto objectsInterfacesAndProperties = GetManagedObjects();
objectsInterfacesAndProperties = GetManagedObjects();
for (const auto& [object, interfacesAndProperties] : objectsInterfacesAndProperties) { for (const auto& [object, interfacesAndProperties] : objectsInterfacesAndProperties) {
onInterfacesAdded(object, interfacesAndProperties); onInterfacesAdded(object, interfacesAndProperties);
} }
@ -56,7 +55,7 @@ public:
private: private:
void onInterfacesAdded( const sdbus::ObjectPath& objectPath void onInterfacesAdded( const sdbus::ObjectPath& objectPath
, const std::map<std::string, std::map<std::string, sdbus::Variant>>& interfacesAndProperties) override , const std::map<sdbus::InterfaceName, std::map<sdbus::PropertyName, sdbus::Variant>>& interfacesAndProperties) override
{ {
std::cout << objectPath << " added:\t"; std::cout << objectPath << " added:\t";
for (const auto& [interface, _] : interfacesAndProperties) { for (const auto& [interface, _] : interfacesAndProperties) {
@ -65,20 +64,20 @@ private:
std::cout << std::endl; std::cout << std::endl;
// Parse and print some more info // Parse and print some more info
auto planetInterface = interfacesAndProperties.find(org::sdbuscpp::ExampleManager::Planet1_proxy::INTERFACE_NAME); auto planetInterface = interfacesAndProperties.find(sdbus::InterfaceName{org::sdbuscpp::ExampleManager::Planet1_proxy::INTERFACE_NAME});
if (planetInterface == interfacesAndProperties.end()) { if (planetInterface == interfacesAndProperties.end()) {
return; return;
} }
const auto& properties = planetInterface->second; const auto& properties = planetInterface->second;
// get a property which was passed as part of the signal. // get a property which was passed as part of the signal.
const auto& name = properties.at("Name").get<std::string>(); const auto& name = properties.at(sdbus::PropertyName{"Name"}).get<std::string>();
// or create a proxy instance to the newly added object. // or create a proxy instance to the newly added object.
PlanetProxy planet(m_connection, m_destination, objectPath); PlanetProxy planet(m_connection, m_destination, objectPath);
std::cout << name << " has a population of " << planet.GetPopulation() << ".\n" << std::endl; std::cout << name << " has a population of " << planet.GetPopulation() << ".\n" << std::endl;
} }
void onInterfacesRemoved( const sdbus::ObjectPath& objectPath void onInterfacesRemoved( const sdbus::ObjectPath& objectPath
, const std::vector<std::string>& interfaces) override , const std::vector<sdbus::InterfaceName>& interfaces) override
{ {
std::cout << objectPath << " removed:\t"; std::cout << objectPath << " removed:\t";
for (const auto& interface : interfaces) { for (const auto& interface : interfaces) {
@ -88,14 +87,16 @@ private:
} }
sdbus::IConnection& m_connection; sdbus::IConnection& m_connection;
std::string m_destination; sdbus::ServiceName m_destination;
}; };
int main() int main()
{ {
auto connection = sdbus::createSessionBusConnection(); auto connection = sdbus::createSessionBusConnection();
auto managerProxy = std::make_unique<ManagerProxy>(*connection, "org.sdbuscpp.examplemanager", "/org/sdbuscpp/examplemanager"); sdbus::ServiceName destination{"org.sdbuscpp.examplemanager"};
sdbus::ObjectPath objectPath{"/org/sdbuscpp/examplemanager"};
auto managerProxy = std::make_unique<ManagerProxy>(*connection, std::move(destination), std::move(objectPath));
try { try {
managerProxy->handleExistingObjects(); managerProxy->handleExistingObjects();
} }
@ -107,4 +108,4 @@ int main()
connection->enterEventLoop(); connection->enterEventLoop();
return 0; return 0;
} }

View File

@ -19,11 +19,13 @@
#include <thread> #include <thread>
#include <chrono> #include <chrono>
class ManagerAdaptor : public sdbus::AdaptorInterfaces< sdbus::ObjectManager_adaptor > using sdbus::ObjectPath;
class ManagerAdaptor : public sdbus::AdaptorInterfaces<sdbus::ObjectManager_adaptor>
{ {
public: public:
ManagerAdaptor(sdbus::IConnection& connection, std::string path) ManagerAdaptor(sdbus::IConnection& connection, sdbus::ObjectPath path)
: AdaptorInterfaces(connection, std::move(path)) : AdaptorInterfaces(connection, std::move(path))
{ {
registerAdaptor(); registerAdaptor();
} }
@ -34,23 +36,23 @@ public:
} }
}; };
class PlanetAdaptor final : public sdbus::AdaptorInterfaces< org::sdbuscpp::ExampleManager::Planet1_adaptor, class PlanetAdaptor final : public sdbus::AdaptorInterfaces< org::sdbuscpp::ExampleManager::Planet1_adaptor
sdbus::ManagedObject_adaptor, , sdbus::ManagedObject_adaptor
sdbus::Properties_adaptor > , sdbus::Properties_adaptor >
{ {
public: public:
PlanetAdaptor(sdbus::IConnection& connection, std::string path, std::string name, uint64_t poulation) PlanetAdaptor(sdbus::IConnection& connection, sdbus::ObjectPath path, std::string name, uint64_t population)
: AdaptorInterfaces(connection, std::move(path)) : AdaptorInterfaces(connection, std::move(path))
, m_name(std::move(name)) , m_name(std::move(name))
, m_population(poulation) , m_population(population)
{ {
registerAdaptor(); registerAdaptor();
emitInterfacesAddedSignal({org::sdbuscpp::ExampleManager::Planet1_adaptor::INTERFACE_NAME}); emitInterfacesAddedSignal({sdbus::InterfaceName{org::sdbuscpp::ExampleManager::Planet1_adaptor::INTERFACE_NAME}});
} }
~PlanetAdaptor() ~PlanetAdaptor()
{ {
emitInterfacesRemovedSignal({org::sdbuscpp::ExampleManager::Planet1_adaptor::INTERFACE_NAME}); emitInterfacesRemovedSignal({sdbus::InterfaceName{org::sdbuscpp::ExampleManager::Planet1_adaptor::INTERFACE_NAME}});
unregisterAdaptor(); unregisterAdaptor();
} }
@ -82,18 +84,19 @@ void printCountDown(const std::string& message, int seconds)
int main() int main()
{ {
auto connection = sdbus::createSessionBusConnection(); auto connection = sdbus::createSessionBusConnection();
connection->requestName("org.sdbuscpp.examplemanager"); sdbus::ServiceName serviceName{"org.sdbuscpp.examplemanager"};
connection->requestName(serviceName);
connection->enterEventLoopAsync(); connection->enterEventLoopAsync();
auto manager = std::make_unique<ManagerAdaptor>(*connection, "/org/sdbuscpp/examplemanager"); auto manager = std::make_unique<ManagerAdaptor>(*connection, ObjectPath{"/org/sdbuscpp/examplemanager"});
while (true) while (true)
{ {
printCountDown("Creating PlanetAdaptor in ", 5); printCountDown("Creating PlanetAdaptor in ", 5);
auto earth = std::make_unique<PlanetAdaptor>(*connection, "/org/sdbuscpp/examplemanager/Planet1/Earth", "Earth", 7'874'965'825); auto earth = std::make_unique<PlanetAdaptor>(*connection, ObjectPath{"/org/sdbuscpp/examplemanager/Planet1/Earth"}, "Earth", 7'874'965'825);
printCountDown("Creating PlanetAdaptor in ", 5); printCountDown("Creating PlanetAdaptor in ", 5);
auto trantor = std::make_unique<PlanetAdaptor>(*connection, "/org/sdbuscpp/examplemanager/Planet1/Trantor", "Trantor", 40'000'000'000); auto trantor = std::make_unique<PlanetAdaptor>(*connection, ObjectPath{"/org/sdbuscpp/examplemanager/Planet1/Trantor"}, "Trantor", 40'000'000'000);
printCountDown("Creating PlanetAdaptor in ", 5); printCountDown("Creating PlanetAdaptor in ", 5);
auto laconia = std::make_unique<PlanetAdaptor>(*connection, "/org/sdbuscpp/examplemanager/Planet1/Laconia", "Laconia", 231'721); auto laconia = std::make_unique<PlanetAdaptor>(*connection, ObjectPath{"/org/sdbuscpp/examplemanager/Planet1/Laconia"}, "Laconia", 231'721);
printCountDown("Removing PlanetAdaptor in ", 5); printCountDown("Removing PlanetAdaptor in ", 5);
earth.reset(); earth.reset();
printCountDown("Removing PlanetAdaptor in ", 5); printCountDown("Removing PlanetAdaptor in ", 5);
@ -102,7 +105,7 @@ int main()
laconia.reset(); laconia.reset();
} }
connection->releaseName("org.sdbuscpp.examplemanager"); connection->releaseName(serviceName);
connection->leaveEventLoop(); connection->leaveEventLoop();
return 0; return 0;
} }

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file AdaptorInterfaces.h * @file AdaptorInterfaces.h
* *
@ -82,9 +82,10 @@ namespace sdbus {
* adaptor-side interface classes representing interfaces (with methods, signals and properties) * adaptor-side interface classes representing interfaces (with methods, signals and properties)
* of the D-Bus object. * of the D-Bus object.
* *
* In the final adaptor class inherited from AdaptorInterfaces, it is necessary to finish * In the final adaptor class inherited from AdaptorInterfaces, one needs to make sure:
* adaptor registration in class constructor (finishRegistration();`), and, conversely, * 1. to call `registerAdaptor();` in the class constructor, and, conversely,
* unregister the adaptor in class destructor (`unregister();`). * 2. to call `unregisterAdaptor();` in the class destructor,
* so that the object API vtable is registered and unregistered at the proper time.
* *
***********************************************/ ***********************************************/
template <typename... _Interfaces> template <typename... _Interfaces>
@ -101,22 +102,22 @@ namespace sdbus {
* *
* For more information, consult @ref createObject(sdbus::IConnection&,std::string) * For more information, consult @ref createObject(sdbus::IConnection&,std::string)
*/ */
AdaptorInterfaces(IConnection& connection, std::string objectPath) AdaptorInterfaces(IConnection& connection, ObjectPath objectPath)
: ObjectHolder(createObject(connection, std::move(objectPath))) : ObjectHolder(createObject(connection, std::move(objectPath)))
, _Interfaces(getObject())... , _Interfaces(getObject())...
{ {
} }
/*! /*!
* @brief Finishes adaptor API registration and publishes the adaptor on the bus * @brief Adds object vtable (i.e. D-Bus API) definitions for all interfaces it implements
* *
* This function must be called in the constructor of the final adaptor class that implements AdaptorInterfaces. * This function must be called in the constructor of the final adaptor class that implements AdaptorInterfaces.
* *
* For more information, see underlying @ref IObject::finishRegistration() * See also @ref IObject::addVTable()
*/ */
void registerAdaptor() void registerAdaptor()
{ {
getObject().finishRegistration(); (_Interfaces::registerAdaptor(), ...);
} }
/*! /*!
@ -132,15 +133,17 @@ namespace sdbus {
} }
/*! /*!
* @brief Returns object path of the underlying DBus object * @brief Returns reference to the underlying IObject instance
*/ */
const std::string& getObjectPath() const using ObjectHolder::getObject;
{
return getObject().getObjectPath();
}
protected: protected:
using base_type = AdaptorInterfaces; using base_type = AdaptorInterfaces;
AdaptorInterfaces(const AdaptorInterfaces&) = delete;
AdaptorInterfaces& operator=(const AdaptorInterfaces&) = delete;
AdaptorInterfaces(AdaptorInterfaces&&) = delete;
AdaptorInterfaces& operator=(AdaptorInterfaces&&) = delete;
~AdaptorInterfaces() = default; ~AdaptorInterfaces() = default;
}; };

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file ConvenienceApiClasses.h * @file ConvenienceApiClasses.h
* *
@ -29,133 +29,63 @@
#include <sdbus-c++/Message.h> #include <sdbus-c++/Message.h>
#include <sdbus-c++/TypeTraits.h> #include <sdbus-c++/TypeTraits.h>
#include <sdbus-c++/Flags.h> #include <sdbus-c++/Types.h>
#include <string> #include <sdbus-c++/VTableItems.h>
#include <vector>
#include <type_traits>
#include <chrono> #include <chrono>
#include <cstdint> #include <cstdint>
#include <future>
#include <map>
#include <string>
#include <string_view>
#include <vector>
// Forward declarations // Forward declarations
namespace sdbus { namespace sdbus {
class IObject; class IObject;
class IProxy; class IProxy;
class Variant;
class Error; class Error;
class PendingAsyncCall; class PendingAsyncCall;
} }
namespace sdbus { namespace sdbus {
class MethodRegistrator class VTableAdder
{ {
public: public:
MethodRegistrator(IObject& object, const std::string& methodName); void forInterface(InterfaceName interfaceName);
MethodRegistrator(MethodRegistrator&& other) = default; void forInterface(std::string interfaceName);
~MethodRegistrator() noexcept(false); [[nodiscard]] Slot forInterface(InterfaceName interfaceName, return_slot_t);
[[nodiscard]] Slot forInterface(std::string interfaceName, return_slot_t);
MethodRegistrator& onInterface(std::string interfaceName); private:
template <typename _Function> MethodRegistrator& implementedAs(_Function&& callback); friend IObject;
MethodRegistrator& withInputParamNames(std::vector<std::string> paramNames); VTableAdder(IObject& object, std::vector<VTableItem> vtable);
template <typename... _String> MethodRegistrator& withInputParamNames(_String... paramNames);
MethodRegistrator& withOutputParamNames(std::vector<std::string> paramNames);
template <typename... _String> MethodRegistrator& withOutputParamNames(_String... paramNames);
MethodRegistrator& markAsDeprecated();
MethodRegistrator& markAsPrivileged();
MethodRegistrator& withNoReply();
private: private:
IObject& object_; IObject& object_;
const std::string& methodName_; std::vector<VTableItem> vtable_;
std::string interfaceName_;
std::string inputSignature_;
std::vector<std::string> inputParamNames_;
std::string outputSignature_;
std::vector<std::string> outputParamNames_;
method_callback methodCallback_;
Flags flags_;
int exceptions_{}; // Number of active exceptions when SignalRegistrator is constructed
};
class SignalRegistrator
{
public:
SignalRegistrator(IObject& object, const std::string& signalName);
SignalRegistrator(SignalRegistrator&& other) = default;
~SignalRegistrator() noexcept(false);
SignalRegistrator& onInterface(std::string interfaceName);
template <typename... _Args> SignalRegistrator& withParameters();
template <typename... _Args> SignalRegistrator& withParameters(std::vector<std::string> paramNames);
template <typename... _Args, typename... _String> SignalRegistrator& withParameters(_String... paramNames);
SignalRegistrator& markAsDeprecated();
private:
IObject& object_;
const std::string& signalName_;
std::string interfaceName_;
std::string signalSignature_;
std::vector<std::string> paramNames_;
Flags flags_;
int exceptions_{}; // Number of active exceptions when SignalRegistrator is constructed
};
class PropertyRegistrator
{
public:
PropertyRegistrator(IObject& object, const std::string& propertyName);
PropertyRegistrator(PropertyRegistrator&& other) = default;
~PropertyRegistrator() noexcept(false);
PropertyRegistrator& onInterface(std::string interfaceName);
template <typename _Function> PropertyRegistrator& withGetter(_Function&& callback);
template <typename _Function> PropertyRegistrator& withSetter(_Function&& callback);
PropertyRegistrator& markAsDeprecated();
PropertyRegistrator& markAsPrivileged();
PropertyRegistrator& withUpdateBehavior(Flags::PropertyUpdateBehaviorFlags behavior);
private:
IObject& object_;
const std::string& propertyName_;
std::string interfaceName_;
std::string propertySignature_;
property_get_callback getter_;
property_set_callback setter_;
Flags flags_;
int exceptions_{}; // Number of active exceptions when PropertyRegistrator is constructed
};
class InterfaceFlagsSetter
{
public:
InterfaceFlagsSetter(IObject& object, const std::string& interfaceName);
InterfaceFlagsSetter(InterfaceFlagsSetter&& other) = default;
~InterfaceFlagsSetter() noexcept(false);
InterfaceFlagsSetter& markAsDeprecated();
InterfaceFlagsSetter& markAsPrivileged();
InterfaceFlagsSetter& withNoReplyMethods();
InterfaceFlagsSetter& withPropertyUpdateBehavior(Flags::PropertyUpdateBehaviorFlags behavior);
private:
IObject& object_;
const std::string& interfaceName_;
Flags flags_;
int exceptions_{}; // Number of active exceptions when InterfaceFlagsSetter is constructed
}; };
class SignalEmitter class SignalEmitter
{ {
public: public:
SignalEmitter(IObject& object, const std::string& signalName); SignalEmitter& onInterface(const InterfaceName& interfaceName);
SignalEmitter& onInterface(const std::string& interfaceName);
SignalEmitter& onInterface(const char* interfaceName);
template <typename... _Args> void withArguments(_Args&&... args);
SignalEmitter(SignalEmitter&& other) = default; SignalEmitter(SignalEmitter&& other) = default;
~SignalEmitter() noexcept(false); ~SignalEmitter() noexcept(false);
SignalEmitter& onInterface(const std::string& interfaceName);
template <typename... _Args> void withArguments(_Args&&... args); private:
friend IObject;
SignalEmitter(IObject& object, const SignalName& signalName);
SignalEmitter(IObject& object, const char* signalName);
private: private:
IObject& object_; IObject& object_;
const std::string& signalName_; const char* signalName_;
Signal signal_; Signal signal_;
int exceptions_{}; // Number of active exceptions when SignalEmitter is constructed int exceptions_{}; // Number of active exceptions when SignalEmitter is constructed
}; };
@ -163,22 +93,27 @@ namespace sdbus {
class MethodInvoker class MethodInvoker
{ {
public: public:
MethodInvoker(IProxy& proxy, const std::string& methodName); MethodInvoker& onInterface(const InterfaceName& interfaceName);
MethodInvoker(MethodInvoker&& other) = default;
~MethodInvoker() noexcept(false);
MethodInvoker& onInterface(const std::string& interfaceName); MethodInvoker& onInterface(const std::string& interfaceName);
MethodInvoker& onInterface(const char* interfaceName);
MethodInvoker& withTimeout(uint64_t usec); MethodInvoker& withTimeout(uint64_t usec);
template <typename _Rep, typename _Period> template <typename _Rep, typename _Period>
MethodInvoker& withTimeout(const std::chrono::duration<_Rep, _Period>& timeout); MethodInvoker& withTimeout(const std::chrono::duration<_Rep, _Period>& timeout);
template <typename... _Args> MethodInvoker& withArguments(_Args&&... args); template <typename... _Args> MethodInvoker& withArguments(_Args&&... args);
template <typename... _Args> void storeResultsTo(_Args&... args); template <typename... _Args> void storeResultsTo(_Args&... args);
void dontExpectReply(); void dontExpectReply();
MethodInvoker(MethodInvoker&& other) = default;
~MethodInvoker() noexcept(false);
private:
friend IProxy;
MethodInvoker(IProxy& proxy, const MethodName& methodName);
MethodInvoker(IProxy& proxy, const char* methodName);
private: private:
IProxy& proxy_; IProxy& proxy_;
const std::string& methodName_; const char* methodName_;
uint64_t timeout_{}; uint64_t timeout_{};
MethodCall method_; MethodCall method_;
int exceptions_{}; // Number of active exceptions when MethodInvoker is constructed int exceptions_{}; // Number of active exceptions when MethodInvoker is constructed
@ -188,17 +123,29 @@ namespace sdbus {
class AsyncMethodInvoker class AsyncMethodInvoker
{ {
public: public:
AsyncMethodInvoker(IProxy& proxy, const std::string& methodName); AsyncMethodInvoker& onInterface(const InterfaceName& interfaceName);
AsyncMethodInvoker& onInterface(const std::string& interfaceName); AsyncMethodInvoker& onInterface(const std::string& interfaceName);
AsyncMethodInvoker& onInterface(const char* interfaceName);
AsyncMethodInvoker& withTimeout(uint64_t usec); AsyncMethodInvoker& withTimeout(uint64_t usec);
template <typename _Rep, typename _Period> template <typename _Rep, typename _Period>
AsyncMethodInvoker& withTimeout(const std::chrono::duration<_Rep, _Period>& timeout); AsyncMethodInvoker& withTimeout(const std::chrono::duration<_Rep, _Period>& timeout);
template <typename... _Args> AsyncMethodInvoker& withArguments(_Args&&... args); template <typename... _Args> AsyncMethodInvoker& withArguments(_Args&&... args);
template <typename _Function> PendingAsyncCall uponReplyInvoke(_Function&& callback); template <typename _Function> PendingAsyncCall uponReplyInvoke(_Function&& callback);
template <typename _Function> [[nodiscard]] Slot uponReplyInvoke(_Function&& callback, return_slot_t);
// Returned future will be std::future<void> for no (void) D-Bus method return value
// or std::future<T> for single D-Bus method return value
// or std::future<std::tuple<...>> for multiple method return values
template <typename... _Args> std::future<future_return_t<_Args...>> getResultAsFuture();
private:
friend IProxy;
AsyncMethodInvoker(IProxy& proxy, const MethodName& methodName);
AsyncMethodInvoker(IProxy& proxy, const char* methodName);
template <typename _Function> async_reply_handler makeAsyncReplyHandler(_Function&& callback);
private: private:
IProxy& proxy_; IProxy& proxy_;
const std::string& methodName_; const char* methodName_;
uint64_t timeout_{}; uint64_t timeout_{};
MethodCall method_; MethodCall method_;
}; };
@ -206,52 +153,138 @@ namespace sdbus {
class SignalSubscriber class SignalSubscriber
{ {
public: public:
SignalSubscriber(IProxy& proxy, const std::string& signalName); SignalSubscriber& onInterface(const InterfaceName& interfaceName);
SignalSubscriber& onInterface(std::string interfaceName); SignalSubscriber& onInterface(const std::string& interfaceName);
SignalSubscriber& onInterface(const char* interfaceName);
template <typename _Function> void call(_Function&& callback); template <typename _Function> void call(_Function&& callback);
template <typename _Function> [[nodiscard]] Slot call(_Function&& callback, return_slot_t);
private:
friend IProxy;
SignalSubscriber(IProxy& proxy, const SignalName& signalName);
SignalSubscriber(IProxy& proxy, const char* signalName);
template <typename _Function> signal_handler makeSignalHandler(_Function&& callback);
private: private:
IProxy& proxy_; IProxy& proxy_;
const std::string& signalName_; const char* signalName_;
std::string interfaceName_; const char* interfaceName_{};
};
class SignalUnsubscriber
{
public:
SignalUnsubscriber(IProxy& proxy, const std::string& signalName);
void onInterface(std::string interfaceName);
private:
IProxy& proxy_;
const std::string& signalName_;
}; };
class PropertyGetter class PropertyGetter
{ {
public: public:
PropertyGetter(IProxy& proxy, const std::string& propertyName); Variant onInterface(std::string_view interfaceName);
sdbus::Variant onInterface(const std::string& interfaceName);
private:
friend IProxy;
PropertyGetter(IProxy& proxy, std::string_view propertyName);
static constexpr const char* DBUS_PROPERTIES_INTERFACE_NAME = "org.freedesktop.DBus.Properties";
private: private:
IProxy& proxy_; IProxy& proxy_;
const std::string& propertyName_; std::string_view propertyName_;
};
class AsyncPropertyGetter
{
public:
AsyncPropertyGetter& onInterface(std::string_view interfaceName);
template <typename _Function> PendingAsyncCall uponReplyInvoke(_Function&& callback);
template <typename _Function> [[nodiscard]] Slot uponReplyInvoke(_Function&& callback, return_slot_t);
std::future<Variant> getResultAsFuture();
private:
friend IProxy;
AsyncPropertyGetter(IProxy& proxy, std::string_view propertyName);
static constexpr const char* DBUS_PROPERTIES_INTERFACE_NAME = "org.freedesktop.DBus.Properties";
private:
IProxy& proxy_;
std::string_view propertyName_;
std::string_view interfaceName_;
}; };
class PropertySetter class PropertySetter
{ {
public: public:
PropertySetter(IProxy& proxy, const std::string& propertyName); PropertySetter& onInterface(std::string_view interfaceName);
PropertySetter& onInterface(std::string interfaceName);
template <typename _Value> void toValue(const _Value& value); 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:
friend IProxy;
PropertySetter(IProxy& proxy, std::string_view propertyName);
static constexpr const char* DBUS_PROPERTIES_INTERFACE_NAME = "org.freedesktop.DBus.Properties";
private: private:
IProxy& proxy_; IProxy& proxy_;
const std::string& propertyName_; std::string_view propertyName_;
std::string interfaceName_; std::string_view interfaceName_;
}; };
} class AsyncPropertySetter
{
public:
AsyncPropertySetter& onInterface(std::string_view interfaceName);
template <typename _Value> AsyncPropertySetter& toValue(_Value&& value);
AsyncPropertySetter& toValue(Variant value);
template <typename _Function> PendingAsyncCall uponReplyInvoke(_Function&& callback);
template <typename _Function> [[nodiscard]] Slot uponReplyInvoke(_Function&& callback, return_slot_t);
std::future<void> getResultAsFuture();
private:
friend IProxy;
AsyncPropertySetter(IProxy& proxy, std::string_view propertyName);
static constexpr const char* DBUS_PROPERTIES_INTERFACE_NAME = "org.freedesktop.DBus.Properties";
private:
IProxy& proxy_;
std::string_view propertyName_;
std::string_view interfaceName_;
Variant value_;
};
class AllPropertiesGetter
{
public:
std::map<PropertyName, Variant> onInterface(std::string_view interfaceName);
private:
friend IProxy;
AllPropertiesGetter(IProxy& proxy);
static constexpr const char* DBUS_PROPERTIES_INTERFACE_NAME = "org.freedesktop.DBus.Properties";
private:
IProxy& proxy_;
};
class AsyncAllPropertiesGetter
{
public:
AsyncAllPropertiesGetter& onInterface(std::string_view interfaceName);
template <typename _Function> PendingAsyncCall uponReplyInvoke(_Function&& callback);
template <typename _Function> [[nodiscard]] Slot uponReplyInvoke(_Function&& callback, return_slot_t);
std::future<std::map<PropertyName, Variant>> getResultAsFuture();
private:
friend IProxy;
AsyncAllPropertiesGetter(IProxy& proxy);
static constexpr const char* DBUS_PROPERTIES_INTERFACE_NAME = "org.freedesktop.DBus.Properties";
private:
IProxy& proxy_;
std::string_view interfaceName_;
};
} // namespace sdbus
#endif /* SDBUS_CXX_CONVENIENCEAPICLASSES_H_ */ #endif /* SDBUS_CXX_CONVENIENCEAPICLASSES_H_ */

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file ConvenienceApiClasses.inl * @file ConvenienceApiClasses.inl
* *
@ -27,395 +27,62 @@
#ifndef SDBUS_CPP_CONVENIENCEAPICLASSES_INL_ #ifndef SDBUS_CPP_CONVENIENCEAPICLASSES_INL_
#define SDBUS_CPP_CONVENIENCEAPICLASSES_INL_ #define SDBUS_CPP_CONVENIENCEAPICLASSES_INL_
#include <sdbus-c++/Error.h>
#include <sdbus-c++/IObject.h> #include <sdbus-c++/IObject.h>
#include <sdbus-c++/IProxy.h> #include <sdbus-c++/IProxy.h>
#include <sdbus-c++/Message.h> #include <sdbus-c++/Message.h>
#include <sdbus-c++/MethodResult.h> #include <sdbus-c++/MethodResult.h>
#include <sdbus-c++/Types.h>
#include <sdbus-c++/TypeTraits.h> #include <sdbus-c++/TypeTraits.h>
#include <sdbus-c++/Error.h> #include <sdbus-c++/Types.h>
#include <cassert>
#include <exception>
#include <string> #include <string>
#include <tuple> #include <tuple>
#include <exception> #include <type_traits>
#include <cassert>
namespace sdbus { namespace sdbus {
/*** ----------------- ***/ /*** ------------- ***/
/*** MethodRegistrator ***/ /*** VTableAdder ***/
/*** ----------------- ***/ /*** ------------- ***/
inline MethodRegistrator::MethodRegistrator(IObject& object, const std::string& methodName) inline VTableAdder::VTableAdder(IObject& object, std::vector<VTableItem> vtable)
: object_(object) : object_(object)
, methodName_(methodName) , vtable_(std::move(vtable))
, exceptions_(std::uncaught_exceptions())
{ {
} }
inline MethodRegistrator::~MethodRegistrator() noexcept(false) // since C++11, destructors must inline void VTableAdder::forInterface(InterfaceName interfaceName)
{ // explicitly be allowed to throw
// Don't register the method if MethodRegistrator threw an exception in one of its methods
if (std::uncaught_exceptions() != exceptions_)
return;
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
assert(methodCallback_); // implementedAs() must be placed/called prior to this function
// registerMethod() can throw. But as the MethodRegistrator shall always be used as an unnamed,
// temporary object, i.e. not as a stack-allocated object, the double-exception situation
// shall never happen. I.e. it should not happen that this destructor is directly called
// in the stack-unwinding process of another flying exception (which would lead to immediate
// termination). It can be called indirectly in the destructor of another object, but that's
// fine and safe provided that the caller catches exceptions thrown from here.
// Therefore, we can allow registerMethod() to throw even if we are in the destructor.
// Bottomline is, to be on the safe side, the caller must take care of catching and reacting
// to the exception thrown from here if the caller is a destructor itself.
object_.registerMethod( interfaceName_
, std::move(methodName_)
, std::move(inputSignature_)
, std::move(inputParamNames_)
, std::move(outputSignature_)
, std::move(outputParamNames_)
, std::move(methodCallback_)
, std::move(flags_));
}
inline MethodRegistrator& MethodRegistrator::onInterface(std::string interfaceName)
{ {
interfaceName_ = std::move(interfaceName); object_.addVTable(std::move(interfaceName), std::move(vtable_));
return *this;
} }
template <typename _Function> inline void VTableAdder::forInterface(std::string interfaceName)
MethodRegistrator& MethodRegistrator::implementedAs(_Function&& callback)
{ {
inputSignature_ = signature_of_function_input_arguments<_Function>::str(); forInterface(InterfaceName{std::move(interfaceName)});
outputSignature_ = signature_of_function_output_arguments<_Function>::str();
methodCallback_ = [callback = std::forward<_Function>(callback)](MethodCall call)
{
// Create a tuple of callback input arguments' types, which will be used
// as a storage for the argument values deserialized from the message.
tuple_of_function_input_arg_types_t<_Function> inputArgs;
// Deserialize input arguments from the message into the tuple.
call >> inputArgs;
if constexpr (!is_async_method_v<_Function>)
{
// Invoke callback with input arguments from the tuple.
auto ret = sdbus::apply(callback, inputArgs);
// Store output arguments to the reply message and send it back.
auto reply = call.createReply();
reply << ret;
reply.send();
}
else
{
// Invoke callback with input arguments from the tuple and with result object to be set later
using AsyncResult = typename function_traits<_Function>::async_result_t;
sdbus::apply(callback, AsyncResult{std::move(call)}, std::move(inputArgs));
}
};
return *this;
} }
inline MethodRegistrator& MethodRegistrator::withInputParamNames(std::vector<std::string> paramNames) [[nodiscard]] inline Slot VTableAdder::forInterface(InterfaceName interfaceName, return_slot_t)
{ {
inputParamNames_ = std::move(paramNames); return object_.addVTable(std::move(interfaceName), std::move(vtable_), return_slot);
return *this;
} }
template <typename... _String> [[nodiscard]] inline Slot VTableAdder::forInterface(std::string interfaceName, return_slot_t)
inline MethodRegistrator& MethodRegistrator::withInputParamNames(_String... paramNames)
{ {
static_assert(std::conjunction_v<std::is_convertible<_String, std::string>...>, "Parameter names must be (convertible to) strings"); return forInterface(InterfaceName{std::move(interfaceName)}, return_slot);
return withInputParamNames({paramNames...});
}
inline MethodRegistrator& MethodRegistrator::withOutputParamNames(std::vector<std::string> paramNames)
{
outputParamNames_ = std::move(paramNames);
return *this;
}
template <typename... _String>
inline MethodRegistrator& MethodRegistrator::withOutputParamNames(_String... paramNames)
{
static_assert(std::conjunction_v<std::is_convertible<_String, std::string>...>, "Parameter names must be (convertible to) strings");
return withOutputParamNames({paramNames...});
}
inline MethodRegistrator& MethodRegistrator::markAsDeprecated()
{
flags_.set(Flags::DEPRECATED);
return *this;
}
inline MethodRegistrator& MethodRegistrator::markAsPrivileged()
{
flags_.set(Flags::PRIVILEGED);
return *this;
}
inline MethodRegistrator& MethodRegistrator::withNoReply()
{
flags_.set(Flags::METHOD_NO_REPLY);
return *this;
}
/*** ----------------- ***/
/*** SignalRegistrator ***/
/*** ----------------- ***/
inline SignalRegistrator::SignalRegistrator(IObject& object, const std::string& signalName)
: object_(object)
, signalName_(signalName)
, exceptions_(std::uncaught_exceptions())
{
}
inline SignalRegistrator::~SignalRegistrator() noexcept(false) // since C++11, destructors must
{ // explicitly be allowed to throw
// Don't register the signal if SignalRegistrator threw an exception in one of its methods
if (std::uncaught_exceptions() != exceptions_)
return;
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
// registerSignal() can throw. But as the SignalRegistrator shall always be used as an unnamed,
// temporary object, i.e. not as a stack-allocated object, the double-exception situation
// shall never happen. I.e. it should not happen that this destructor is directly called
// in the stack-unwinding process of another flying exception (which would lead to immediate
// termination). It can be called indirectly in the destructor of another object, but that's
// fine and safe provided that the caller catches exceptions thrown from here.
// Therefore, we can allow registerSignal() to throw even if we are in the destructor.
// Bottomline is, to be on the safe side, the caller must take care of catching and reacting
// to the exception thrown from here if the caller is a destructor itself.
object_.registerSignal( interfaceName_
, std::move(signalName_)
, std::move(signalSignature_)
, std::move(paramNames_)
, std::move(flags_) );
}
inline SignalRegistrator& SignalRegistrator::onInterface(std::string interfaceName)
{
interfaceName_ = std::move(interfaceName);
return *this;
}
template <typename... _Args>
inline SignalRegistrator& SignalRegistrator::withParameters()
{
signalSignature_ = signature_of_function_input_arguments<void(_Args...)>::str();
return *this;
}
template <typename... _Args>
inline SignalRegistrator& SignalRegistrator::withParameters(std::vector<std::string> paramNames)
{
paramNames_ = std::move(paramNames);
return withParameters<_Args...>();
}
template <typename... _Args, typename... _String>
inline SignalRegistrator& SignalRegistrator::withParameters(_String... paramNames)
{
static_assert(std::conjunction_v<std::is_convertible<_String, std::string>...>, "Parameter names must be (convertible to) strings");
static_assert(sizeof...(_Args) == sizeof...(_String), "Numbers of signal parameters and their names don't match");
return withParameters<_Args...>({paramNames...});
}
inline SignalRegistrator& SignalRegistrator::markAsDeprecated()
{
flags_.set(Flags::DEPRECATED);
return *this;
}
/*** ------------------- ***/
/*** PropertyRegistrator ***/
/*** ------------------- ***/
inline PropertyRegistrator::PropertyRegistrator(IObject& object, const std::string& propertyName)
: object_(object)
, propertyName_(propertyName)
, exceptions_(std::uncaught_exceptions())
{
}
inline PropertyRegistrator::~PropertyRegistrator() noexcept(false) // since C++11, destructors must
{ // explicitly be allowed to throw
// Don't register the property if PropertyRegistrator threw an exception in one of its methods
if (std::uncaught_exceptions() != exceptions_)
return;
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
// registerProperty() can throw. But as the PropertyRegistrator shall always be used as an unnamed,
// temporary object, i.e. not as a stack-allocated object, the double-exception situation
// shall never happen. I.e. it should not happen that this destructor is directly called
// in the stack-unwinding process of another flying exception (which would lead to immediate
// termination). It can be called indirectly in the destructor of another object, but that's
// fine and safe provided that the caller catches exceptions thrown from here.
// Therefore, we can allow registerProperty() to throw even if we are in the destructor.
// Bottomline is, to be on the safe side, the caller must take care of catching and reacting
// to the exception thrown from here if the caller is a destructor itself.
object_.registerProperty( interfaceName_
, propertyName_
, propertySignature_
, std::move(getter_)
, std::move(setter_)
, flags_ );
}
inline PropertyRegistrator& PropertyRegistrator::onInterface(std::string interfaceName)
{
interfaceName_ = std::move(interfaceName);
return *this;
}
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(!std::is_void<function_result_t<_Function>>::value, "Property getter function must return property value");
if (propertySignature_.empty())
propertySignature_ = signature_of_function_output_arguments<_Function>::str();
getter_ = [callback = std::forward<_Function>(callback)](PropertyGetReply& reply)
{
// Get the propety value and serialize it into the pre-constructed reply message
reply << callback();
};
return *this;
}
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(std::is_void<function_result_t<_Function>>::value, "Property setter function must not return any value");
if (propertySignature_.empty())
propertySignature_ = signature_of_function_input_arguments<_Function>::str();
setter_ = [callback = std::forward<_Function>(callback)](PropertySetCall& call)
{
// Default-construct property value
using property_type = function_argument_t<_Function, 0>;
std::decay_t<property_type> property;
// Deserialize property value from the incoming call message
call >> property;
// Invoke setter with the value
callback(property);
};
return *this;
}
inline PropertyRegistrator& PropertyRegistrator::markAsDeprecated()
{
flags_.set(Flags::DEPRECATED);
return *this;
}
inline PropertyRegistrator& PropertyRegistrator::markAsPrivileged()
{
flags_.set(Flags::PRIVILEGED);
return *this;
}
inline PropertyRegistrator& PropertyRegistrator::withUpdateBehavior(Flags::PropertyUpdateBehaviorFlags behavior)
{
flags_.set(behavior);
return *this;
}
/*** -------------------- ***/
/*** InterfaceFlagsSetter ***/
/*** -------------------- ***/
inline InterfaceFlagsSetter::InterfaceFlagsSetter(IObject& object, const std::string& interfaceName)
: object_(object)
, interfaceName_(interfaceName)
, exceptions_(std::uncaught_exceptions())
{
}
inline InterfaceFlagsSetter::~InterfaceFlagsSetter() noexcept(false) // since C++11, destructors must
{ // explicitly be allowed to throw
// Don't set any flags if InterfaceFlagsSetter threw an exception in one of its methods
if (std::uncaught_exceptions() != exceptions_)
return;
// setInterfaceFlags() can throw. But as the InterfaceFlagsSetter shall always be used as an unnamed,
// temporary object, i.e. not as a stack-allocated object, the double-exception situation
// shall never happen. I.e. it should not happen that this destructor is directly called
// in the stack-unwinding process of another flying exception (which would lead to immediate
// termination). It can be called indirectly in the destructor of another object, but that's
// fine and safe provided that the caller catches exceptions thrown from here.
// Therefore, we can allow setInterfaceFlags() to throw even if we are in the destructor.
// Bottomline is, to be on the safe side, the caller must take care of catching and reacting
// to the exception thrown from here if the caller is a destructor itself.
object_.setInterfaceFlags(interfaceName_, std::move(flags_));
}
inline InterfaceFlagsSetter& InterfaceFlagsSetter::markAsDeprecated()
{
flags_.set(Flags::DEPRECATED);
return *this;
}
inline InterfaceFlagsSetter& InterfaceFlagsSetter::markAsPrivileged()
{
flags_.set(Flags::PRIVILEGED);
return *this;
}
inline InterfaceFlagsSetter& InterfaceFlagsSetter::withNoReplyMethods()
{
flags_.set(Flags::METHOD_NO_REPLY);
return *this;
}
inline InterfaceFlagsSetter& InterfaceFlagsSetter::withPropertyUpdateBehavior(Flags::PropertyUpdateBehaviorFlags behavior)
{
flags_.set(behavior);
return *this;
} }
/*** ------------- ***/ /*** ------------- ***/
/*** SignalEmitter ***/ /*** SignalEmitter ***/
/*** ------------- ***/ /*** ------------- ***/
inline SignalEmitter::SignalEmitter(IObject& object, const std::string& signalName) inline SignalEmitter::SignalEmitter(IObject& object, const SignalName& signalName)
: SignalEmitter(object, signalName.c_str())
{
}
inline SignalEmitter::SignalEmitter(IObject& object, const char* signalName)
: object_(object) : object_(object)
, signalName_(signalName) , signalName_(signalName)
, exceptions_(std::uncaught_exceptions()) , exceptions_(std::uncaught_exceptions())
@ -440,7 +107,17 @@ namespace sdbus {
object_.emitSignal(signal_); object_.emitSignal(signal_);
} }
inline SignalEmitter& SignalEmitter::onInterface(const InterfaceName& interfaceName)
{
return onInterface(interfaceName.c_str());
}
inline SignalEmitter& SignalEmitter::onInterface(const std::string& interfaceName) inline SignalEmitter& SignalEmitter::onInterface(const std::string& interfaceName)
{
return onInterface(interfaceName.c_str());
}
inline SignalEmitter& SignalEmitter::onInterface(const char* interfaceName)
{ {
signal_ = object_.createSignal(interfaceName, signalName_); signal_ = object_.createSignal(interfaceName, signalName_);
@ -459,7 +136,12 @@ namespace sdbus {
/*** MethodInvoker ***/ /*** MethodInvoker ***/
/*** ------------- ***/ /*** ------------- ***/
inline MethodInvoker::MethodInvoker(IProxy& proxy, const std::string& methodName) inline MethodInvoker::MethodInvoker(IProxy& proxy, const MethodName& methodName)
: MethodInvoker(proxy, methodName.c_str())
{
}
inline MethodInvoker::MethodInvoker(IProxy& proxy, const char* methodName)
: proxy_(proxy) : proxy_(proxy)
, methodName_(methodName) , methodName_(methodName)
, exceptions_(std::uncaught_exceptions()) , exceptions_(std::uncaught_exceptions())
@ -485,7 +167,17 @@ namespace sdbus {
proxy_.callMethod(method_, timeout_); proxy_.callMethod(method_, timeout_);
} }
inline MethodInvoker& MethodInvoker::onInterface(const InterfaceName& interfaceName)
{
return onInterface(interfaceName.c_str());
}
inline MethodInvoker& MethodInvoker::onInterface(const std::string& interfaceName) inline MethodInvoker& MethodInvoker::onInterface(const std::string& interfaceName)
{
return onInterface(interfaceName.c_str());
}
inline MethodInvoker& MethodInvoker::onInterface(const char* interfaceName)
{ {
method_ = proxy_.createMethodCall(interfaceName, methodName_); method_ = proxy_.createMethodCall(interfaceName, methodName_);
@ -538,13 +230,28 @@ namespace sdbus {
/*** AsyncMethodInvoker ***/ /*** AsyncMethodInvoker ***/
/*** ------------------ ***/ /*** ------------------ ***/
inline AsyncMethodInvoker::AsyncMethodInvoker(IProxy& proxy, const std::string& methodName) inline AsyncMethodInvoker::AsyncMethodInvoker(IProxy& proxy, const MethodName& methodName)
: AsyncMethodInvoker(proxy, methodName.c_str())
{
}
inline AsyncMethodInvoker::AsyncMethodInvoker(IProxy& proxy, const char* methodName)
: proxy_(proxy) : proxy_(proxy)
, methodName_(methodName) , methodName_(methodName)
{ {
} }
inline AsyncMethodInvoker& AsyncMethodInvoker::onInterface(const InterfaceName& interfaceName)
{
return onInterface(interfaceName.c_str());
}
inline AsyncMethodInvoker& AsyncMethodInvoker::onInterface(const std::string& interfaceName) inline AsyncMethodInvoker& AsyncMethodInvoker::onInterface(const std::string& interfaceName)
{
return onInterface(interfaceName.c_str());
}
inline AsyncMethodInvoker& AsyncMethodInvoker::onInterface(const char* interfaceName)
{ {
method_ = proxy_.createMethodCall(interfaceName, methodName_); method_ = proxy_.createMethodCall(interfaceName, methodName_);
@ -580,14 +287,31 @@ namespace sdbus {
{ {
assert(method_.isValid()); // onInterface() must be placed/called prior to this function assert(method_.isValid()); // onInterface() must be placed/called prior to this function
auto asyncReplyHandler = [callback = std::forward<_Function>(callback)](MethodReply& reply, const Error* error) return proxy_.callMethodAsync(method_, makeAsyncReplyHandler(std::forward<_Function>(callback)), timeout_);
}
template <typename _Function>
[[nodiscard]] Slot AsyncMethodInvoker::uponReplyInvoke(_Function&& callback, return_slot_t)
{
assert(method_.isValid()); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync( method_
, makeAsyncReplyHandler(std::forward<_Function>(callback))
, timeout_
, return_slot );
}
template <typename _Function>
inline async_reply_handler AsyncMethodInvoker::makeAsyncReplyHandler(_Function&& callback)
{
return [callback = std::forward<_Function>(callback)](MethodReply reply, std::optional<Error> error)
{ {
// Create a tuple of callback input arguments' types, which will be used // Create a tuple of callback input arguments' types, which will be used
// as a storage for the argument values deserialized from the message. // as a storage for the argument values deserialized from the message.
tuple_of_function_input_arg_types_t<_Function> args; tuple_of_function_input_arg_types_t<_Function> args;
// Deserialize input arguments from the message into the tuple (if no error occurred). // Deserialize input arguments from the message into the tuple (if no error occurred).
if (error == nullptr) if (!error)
{ {
try try
{ {
@ -595,32 +319,67 @@ namespace sdbus {
} }
catch (const Error& e) catch (const Error& e)
{ {
// Catch message unpack exceptions and pass them to the callback // Pass message deserialization exceptions to the client via callback error parameter,
// in the expected manner to avoid propagating them up the call // instead of propagating them up the message loop call stack.
// stack to the event loop. sdbus::apply(callback, e, args);
sdbus::apply(callback, &e, args);
return; return;
} }
} }
// Invoke callback with input arguments from the tuple. // Invoke callback with input arguments from the tuple.
sdbus::apply(callback, error, args); sdbus::apply(callback, std::move(error), args);
}; };
}
return proxy_.callMethod(method_, std::move(asyncReplyHandler), timeout_); template <typename... _Args>
std::future<future_return_t<_Args...>> AsyncMethodInvoker::getResultAsFuture()
{
auto promise = std::make_shared<std::promise<future_return_t<_Args...>>>();
auto future = promise->get_future();
uponReplyInvoke([promise = std::move(promise)](std::optional<Error> error, _Args... args)
{
if (!error)
if constexpr (!std::is_void_v<future_return_t<_Args...>>)
promise->set_value({std::move(args)...});
else
promise->set_value();
else
promise->set_exception(std::make_exception_ptr(*std::move(error)));
});
// Will be std::future<void> for no D-Bus method return value
// or std::future<T> for single D-Bus method return value
// or std::future<std::tuple<...>> for multiple method return values
return future;
} }
/*** ---------------- ***/ /*** ---------------- ***/
/*** SignalSubscriber ***/ /*** SignalSubscriber ***/
/*** ---------------- ***/ /*** ---------------- ***/
inline SignalSubscriber::SignalSubscriber(IProxy& proxy, const std::string& signalName) inline SignalSubscriber::SignalSubscriber(IProxy& proxy, const SignalName& signalName)
: SignalSubscriber(proxy, signalName.c_str())
{
}
inline SignalSubscriber::SignalSubscriber(IProxy& proxy, const char* signalName)
: proxy_(proxy) : proxy_(proxy)
, signalName_(signalName) , signalName_(signalName)
{ {
} }
inline SignalSubscriber& SignalSubscriber::onInterface(std::string interfaceName) inline SignalSubscriber& SignalSubscriber::onInterface(const InterfaceName& interfaceName)
{
return onInterface(interfaceName.c_str());
}
inline SignalSubscriber& SignalSubscriber::onInterface(const std::string& interfaceName)
{
return onInterface(interfaceName.c_str());
}
inline SignalSubscriber& SignalSubscriber::onInterface(const char* interfaceName)
{ {
interfaceName_ = std::move(interfaceName); interfaceName_ = std::move(interfaceName);
@ -630,20 +389,37 @@ namespace sdbus {
template <typename _Function> template <typename _Function>
inline void SignalSubscriber::call(_Function&& callback) inline void SignalSubscriber::call(_Function&& callback)
{ {
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function
proxy_.registerSignalHandler( interfaceName_ proxy_.registerSignalHandler( interfaceName_
, signalName_ , signalName_
, [callback = std::forward<_Function>(callback)](Signal& signal) , makeSignalHandler(std::forward<_Function>(callback)) );
}
template <typename _Function>
[[nodiscard]] inline Slot SignalSubscriber::call(_Function&& callback, return_slot_t)
{
assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function
return proxy_.registerSignalHandler( interfaceName_
, signalName_
, makeSignalHandler(std::forward<_Function>(callback))
, return_slot );
}
template <typename _Function>
inline signal_handler SignalSubscriber::makeSignalHandler(_Function&& callback)
{
return [callback = std::forward<_Function>(callback)](Signal signal)
{ {
// Create a tuple of callback input arguments' types, which will be used // Create a tuple of callback input arguments' types, which will be used
// as a storage for the argument values deserialized from the signal message. // as a storage for the argument values deserialized from the signal message.
tuple_of_function_input_arg_types_t<_Function> signalArgs; tuple_of_function_input_arg_types_t<_Function> signalArgs;
// The signal handler can take pure signal parameters only, or an additional `const Error*` as its first // The signal handler can take pure signal parameters only, or an additional `std::optional<Error>` as its first
// parameter. In the former case, if the deserialization fails (e.g. due to signature mismatch), // parameter. In the former case, if the deserialization fails (e.g. due to signature mismatch),
// the failure is ignored (and signal simply dropped). In the latter case, the deserialization failure // the failure is ignored (and signal simply dropped). In the latter case, the deserialization failure
// will be communicated as a non-zero Error pointer to the client's signal handler. // will be communicated to the client's signal handler as a valid Error object inside the std::optional parameter.
if constexpr (has_error_param_v<_Function>) if constexpr (has_error_param_v<_Function>)
{ {
// Deserialize input arguments from the signal message into the tuple // Deserialize input arguments from the signal message into the tuple
@ -653,12 +429,14 @@ namespace sdbus {
} }
catch (const sdbus::Error& e) 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,
sdbus::apply(callback, &e, signalArgs); // 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. // Invoke callback with no error and input arguments from the tuple.
sdbus::apply(callback, nullptr, signalArgs); sdbus::apply(callback, {}, signalArgs);
} }
else else
{ {
@ -668,56 +446,95 @@ namespace sdbus {
// Invoke callback with input arguments from the tuple. // Invoke callback with input arguments from the tuple.
sdbus::apply(callback, signalArgs); sdbus::apply(callback, signalArgs);
} }
}); };
}
/*** ------------------ ***/
/*** SignalUnsubscriber ***/
/*** ------------------ ***/
inline SignalUnsubscriber::SignalUnsubscriber(IProxy& proxy, const std::string& signalName)
: proxy_(proxy)
, signalName_(signalName)
{
}
inline void SignalUnsubscriber::onInterface(std::string interfaceName)
{
proxy_.unregisterSignalHandler(interfaceName, signalName_);
} }
/*** -------------- ***/ /*** -------------- ***/
/*** PropertyGetter ***/ /*** PropertyGetter ***/
/*** -------------- ***/ /*** -------------- ***/
inline PropertyGetter::PropertyGetter(IProxy& proxy, const std::string& propertyName) inline PropertyGetter::PropertyGetter(IProxy& proxy, std::string_view propertyName)
: proxy_(proxy) : proxy_(proxy)
, propertyName_(propertyName) , propertyName_(std::move(propertyName))
{ {
} }
inline sdbus::Variant PropertyGetter::onInterface(const std::string& interfaceName) inline Variant PropertyGetter::onInterface(std::string_view interfaceName)
{ {
sdbus::Variant var; Variant var;
proxy_ proxy_.callMethod("Get")
.callMethod("Get") .onInterface(DBUS_PROPERTIES_INTERFACE_NAME)
.onInterface("org.freedesktop.DBus.Properties") .withArguments(interfaceName, propertyName_)
.withArguments(interfaceName, propertyName_) .storeResultsTo(var);
.storeResultsTo(var);
return var; return var;
} }
/*** ------------------- ***/
/*** AsyncPropertyGetter ***/
/*** ------------------- ***/
inline AsyncPropertyGetter::AsyncPropertyGetter(IProxy& proxy, std::string_view propertyName)
: proxy_(proxy)
, propertyName_(std::move(propertyName))
{
}
inline AsyncPropertyGetter& AsyncPropertyGetter::onInterface(std::string_view interfaceName)
{
interfaceName_ = std::move(interfaceName);
return *this;
}
template <typename _Function>
PendingAsyncCall AsyncPropertyGetter::uponReplyInvoke(_Function&& callback)
{
static_assert( std::is_invocable_r_v<void, _Function, std::optional<Error>, Variant>
, "Property get callback function must accept std::optional<Error> and property value as Variant" );
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync("Get")
.onInterface(DBUS_PROPERTIES_INTERFACE_NAME)
.withArguments(interfaceName_, propertyName_)
.uponReplyInvoke(std::forward<_Function>(callback));
}
template <typename _Function>
[[nodiscard]] Slot AsyncPropertyGetter::uponReplyInvoke(_Function&& callback, return_slot_t)
{
static_assert( std::is_invocable_r_v<void, _Function, std::optional<Error>, Variant>
, "Property get callback function must accept std::optional<Error> and property value as Variant" );
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync("Get")
.onInterface(DBUS_PROPERTIES_INTERFACE_NAME)
.withArguments(interfaceName_, propertyName_)
.uponReplyInvoke(std::forward<_Function>(callback), return_slot);
}
inline std::future<Variant> AsyncPropertyGetter::getResultAsFuture()
{
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync("Get")
.onInterface(DBUS_PROPERTIES_INTERFACE_NAME)
.withArguments(interfaceName_, propertyName_)
.getResultAsFuture<Variant>();
}
/*** -------------- ***/ /*** -------------- ***/
/*** PropertySetter ***/ /*** PropertySetter ***/
/*** -------------- ***/ /*** -------------- ***/
inline PropertySetter::PropertySetter(IProxy& proxy, const std::string& propertyName) inline PropertySetter::PropertySetter(IProxy& proxy, std::string_view propertyName)
: proxy_(proxy) : proxy_(proxy)
, propertyName_(propertyName) , propertyName_(std::move(propertyName))
{ {
} }
inline PropertySetter& PropertySetter::onInterface(std::string interfaceName) inline PropertySetter& PropertySetter::onInterface(std::string_view interfaceName)
{ {
interfaceName_ = std::move(interfaceName); interfaceName_ = std::move(interfaceName);
@ -727,19 +544,175 @@ namespace sdbus {
template <typename _Value> template <typename _Value>
inline void PropertySetter::toValue(const _Value& 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)
{
PropertySetter::toValue(Variant{value}, dont_expect_reply);
}
inline void PropertySetter::toValue(const Variant& value)
{ {
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
proxy_ proxy_.callMethod("Set")
.callMethod("Set") .onInterface(DBUS_PROPERTIES_INTERFACE_NAME)
.onInterface("org.freedesktop.DBus.Properties") .withArguments(interfaceName_, propertyName_, value);
.withArguments(interfaceName_, propertyName_, value);
} }
} inline void PropertySetter::toValue(const Variant& value, dont_expect_reply_t)
{
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
proxy_.callMethod("Set")
.onInterface(DBUS_PROPERTIES_INTERFACE_NAME)
.withArguments(interfaceName_, propertyName_, value)
.dontExpectReply();
}
/*** ------------------- ***/
/*** AsyncPropertySetter ***/
/*** ------------------- ***/
inline AsyncPropertySetter::AsyncPropertySetter(IProxy& proxy, std::string_view propertyName)
: proxy_(proxy)
, propertyName_(propertyName)
{
}
inline AsyncPropertySetter& AsyncPropertySetter::onInterface(std::string_view interfaceName)
{
interfaceName_ = std::move(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, std::optional<Error>>
, "Property set callback function must accept std::optional<Error> only" );
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync("Set")
.onInterface(DBUS_PROPERTIES_INTERFACE_NAME)
.withArguments(interfaceName_, propertyName_, std::move(value_))
.uponReplyInvoke(std::forward<_Function>(callback));
}
template <typename _Function>
[[nodiscard]] Slot AsyncPropertySetter::uponReplyInvoke(_Function&& callback, return_slot_t)
{
static_assert( std::is_invocable_r_v<void, _Function, std::optional<Error>>
, "Property set callback function must accept std::optional<Error> only" );
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync("Set")
.onInterface(DBUS_PROPERTIES_INTERFACE_NAME)
.withArguments(interfaceName_, propertyName_, std::move(value_))
.uponReplyInvoke(std::forward<_Function>(callback), return_slot);
}
inline std::future<void> AsyncPropertySetter::getResultAsFuture()
{
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync("Set")
.onInterface(DBUS_PROPERTIES_INTERFACE_NAME)
.withArguments(interfaceName_, propertyName_, std::move(value_))
.getResultAsFuture<>();
}
/*** ------------------- ***/
/*** AllPropertiesGetter ***/
/*** ------------------- ***/
inline AllPropertiesGetter::AllPropertiesGetter(IProxy& proxy)
: proxy_(proxy)
{
}
inline std::map<PropertyName, Variant> AllPropertiesGetter::onInterface(std::string_view interfaceName)
{
std::map<PropertyName, Variant> props;
proxy_.callMethod("GetAll")
.onInterface(DBUS_PROPERTIES_INTERFACE_NAME)
.withArguments(std::move(interfaceName))
.storeResultsTo(props);
return props;
}
/*** ------------------------ ***/
/*** AsyncAllPropertiesGetter ***/
/*** ------------------------ ***/
inline AsyncAllPropertiesGetter::AsyncAllPropertiesGetter(IProxy& proxy)
: proxy_(proxy)
{
}
inline AsyncAllPropertiesGetter& AsyncAllPropertiesGetter::onInterface(std::string_view interfaceName)
{
interfaceName_ = std::move(interfaceName);
return *this;
}
template <typename _Function>
PendingAsyncCall AsyncAllPropertiesGetter::uponReplyInvoke(_Function&& callback)
{
static_assert( std::is_invocable_r_v<void, _Function, std::optional<Error>, std::map<PropertyName, Variant>>
, "All properties get callback function must accept std::optional<Error> and a map of property names to their values" );
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync("GetAll")
.onInterface(DBUS_PROPERTIES_INTERFACE_NAME)
.withArguments(interfaceName_)
.uponReplyInvoke(std::forward<_Function>(callback));
}
template <typename _Function>
[[nodiscard]] Slot AsyncAllPropertiesGetter::uponReplyInvoke(_Function&& callback, return_slot_t)
{
static_assert( std::is_invocable_r_v<void, _Function, std::optional<Error>, std::map<PropertyName, Variant>>
, "All properties get callback function must accept std::optional<Error> and a map of property names to their values" );
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync("GetAll")
.onInterface(DBUS_PROPERTIES_INTERFACE_NAME)
.withArguments(interfaceName_)
.uponReplyInvoke(std::forward<_Function>(callback), return_slot);
}
inline std::future<std::map<PropertyName, Variant>> AsyncAllPropertiesGetter::getResultAsFuture()
{
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync("GetAll")
.onInterface(DBUS_PROPERTIES_INTERFACE_NAME)
.withArguments(interfaceName_)
.getResultAsFuture<std::map<PropertyName, Variant>>();
}
} // namespace sdbus
#endif /* SDBUS_CPP_CONVENIENCEAPICLASSES_INL_ */ #endif /* SDBUS_CPP_CONVENIENCEAPICLASSES_INL_ */

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file Error.h * @file Error.h
* *
@ -43,39 +43,56 @@ namespace sdbus {
: public std::runtime_error : public std::runtime_error
{ {
public: public:
explicit Error(const std::string& name, const char* message = nullptr) // Strong type representing the D-Bus error name
: Error(name, std::string(message ? message : "")) class Name : public std::string
{
public:
Name() = default;
explicit Name(std::string value)
: std::string(std::move(value))
{}
explicit Name(const char* value)
: std::string(value)
{}
using std::string::operator=;
};
explicit Error(Name name, const char* message = nullptr)
: Error(std::move(name), std::string(message ? message : ""))
{ {
} }
Error(const std::string& name, const std::string& message) Error(Name name, std::string message)
: std::runtime_error("[" + name + "] " + message) : std::runtime_error("[" + name + "] " + message)
, name_(name) , name_(std::move(name))
, message_(message) , message_(std::move(message))
{ {
} }
const std::string& getName() const [[nodiscard]] const Name& getName() const
{ {
return name_; return name_;
} }
const std::string& getMessage() const [[nodiscard]] const std::string& getMessage() const
{ {
return message_; return message_;
} }
bool isValid() const [[nodiscard]] bool isValid() const
{ {
return !getName().empty(); return !getName().empty();
} }
private: private:
std::string name_; Name name_;
std::string message_; std::string message_;
}; };
sdbus::Error createError(int errNo, const std::string& customMsg); Error createError(int errNo, std::string customMsg);
inline const Error::Name SDBUSCPP_ERROR_NAME{"org.sdbuscpp.Error"};
} }
#define SDBUS_THROW_ERROR(_MSG, _ERRNO) \ #define SDBUS_THROW_ERROR(_MSG, _ERRNO) \

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file Flags.h * @file Flags.h
* *
@ -74,26 +74,26 @@ namespace sdbus {
flags_.set(flag, value); flags_.set(flag, value);
} }
bool test(GeneralFlags flag) const [[nodiscard]] bool test(GeneralFlags flag) const
{ {
return flags_.test(flag); return flags_.test(flag);
} }
bool test(PropertyUpdateBehaviorFlags flag) const [[nodiscard]] bool test(PropertyUpdateBehaviorFlags flag) const
{ {
return flags_.test(flag); return flags_.test(flag);
} }
uint64_t toSdBusInterfaceFlags() const; [[nodiscard]] uint64_t toSdBusInterfaceFlags() const;
uint64_t toSdBusMethodFlags() const; [[nodiscard]] uint64_t toSdBusMethodFlags() const;
uint64_t toSdBusSignalFlags() const; [[nodiscard]] uint64_t toSdBusSignalFlags() const;
uint64_t toSdBusPropertyFlags() const; [[nodiscard]] uint64_t toSdBusPropertyFlags() const;
uint64_t toSdBusWritablePropertyFlags() const; [[nodiscard]] uint64_t toSdBusWritablePropertyFlags() const;
private: private:
std::bitset<FLAG_COUNT> flags_; std::bitset<FLAG_COUNT> flags_;
}; };
} }
#endif /* SDBUS_CXX_FLAGS_H_ */ #endif /* SDBUS_CXX_FLAGS_H_ */

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file IConnection.h * @file IConnection.h
* *
@ -28,11 +28,22 @@
#define SDBUS_CXX_ICONNECTION_H_ #define SDBUS_CXX_ICONNECTION_H_
#include <sdbus-c++/TypeTraits.h> #include <sdbus-c++/TypeTraits.h>
#include <string>
#include <memory>
#include <chrono> #include <chrono>
#include <cstdint> #include <cstdint>
#include <memory>
#include <optional> #include <optional>
#include <string>
// Forward declarations
struct sd_bus;
struct sd_event;
namespace sdbus {
class Message;
class ObjectPath;
class BusName;
using ServiceName = BusName;
}
namespace sdbus { namespace sdbus {
@ -49,90 +60,10 @@ namespace sdbus {
class IConnection class IConnection
{ {
public: public:
/*! struct PollData;
* Poll Data for external event loop implementations.
*
* To integrate sdbus with your app's own custom event handling system
* you can use this method to query which file descriptors, poll events
* and timeouts you should add to your app's poll(2), or select(2)
* call in your main event loop.
*
* If you are unsure what this all means then use
* enterEventLoop() or enterEventLoopAsync() instead.
*
* See: getEventLoopPollData()
*/
struct PollData
{
/*!
* The read fd to be monitored by the event loop.
*/
int fd;
/*!
* The events to use for poll(2) alongside fd.
*/
short int events;
/*!
* Absolute timeout value in micro seconds and based of CLOCK_MONOTONIC.
*/
uint64_t timeout_usec;
/*!
* Get the event poll timeout.
*
* The timeout is an absolute value based of CLOCK_MONOTONIC.
*
* @return a duration since the CLOCK_MONOTONIC epoch started.
*/
[[nodiscard]] std::chrono::microseconds getAbsoluteTimeout() const
{
return std::chrono::microseconds(timeout_usec);
}
/*!
* Get the timeout as relative value from now
*
* @return std::nullopt if the timeout is indefinite. A duration otherwise.
*/
[[nodiscard]] std::optional<std::chrono::microseconds> getRelativeTimeout() const;
/*!
* Get a converted, relative timeout which can be passed as argument 'timeout' to poll(2)
*
* @return -1 if the timeout is indefinite. 0 if the poll(2) shouldn't block. An integer in milli
* seconds otherwise.
*/
[[nodiscard]] int getPollTimeout() const;
};
virtual ~IConnection() = default; virtual ~IConnection() = default;
/*!
* @brief Requests D-Bus name on the connection
*
* @param[in] name Name to request
*
* @throws sdbus::Error in case of failure
*/
virtual void requestName(const std::string& name) = 0;
/*!
* @brief Releases D-Bus name on the connection
*
* @param[in] name Name to release
*
* @throws sdbus::Error in case of failure
*/
virtual void releaseName(const std::string& name) = 0;
/*!
* @brief Retrieve the unique name of a connection. E.g. ":1.xx"
*
* @throws sdbus::Error in case of failure
*/
virtual std::string getUniqueName() const = 0;
/*! /*!
* @brief Enters I/O event loop on this bus connection * @brief Enters I/O event loop on this bus connection
* *
@ -161,61 +92,103 @@ namespace sdbus {
virtual void leaveEventLoop() = 0; virtual void leaveEventLoop() = 0;
/*! /*!
* @brief Adds an ObjectManager at the specified D-Bus object path * @brief Attaches the bus connection to an sd-event event loop
* *
* Creates an ObjectManager interface at the specified object path on * @param[in] event sd-event event loop object
* the connection. This is a convenient way to interrogate a connection * @param[in] priority Specified priority
* to see what objects it has.
*
* This call creates a floating registration. The ObjectManager will
* be there for the object path until the connection is destroyed.
*
* Another, recommended way to add object managers is directly through
* IObject API.
* *
* @throws sdbus::Error in case of failure * @throws sdbus::Error in case of failure
*
* See `man sd_bus_attach_event'.
*/ */
[[deprecated("Use one of other addObjectManager overloads")]] virtual void addObjectManager(const std::string& objectPath) = 0; virtual void attachSdEventLoop(sd_event *event, int priority = 0) = 0;
/*! /*!
* @brief Returns fd, I/O events and timeout data you can pass to poll * @brief Detaches the bus connection from an sd-event event loop
*
* To integrate sdbus with your app's own custom event handling system
* (without the requirement of an extra thread), you can use this
* method to query which file descriptors, poll events and timeouts you
* should add to your app's poll call in your main event loop. If these
* file descriptors signal, then you should call processPendingRequest
* to process the event. This means that all of sdbus's callbacks will
* arrive on your app's main event thread (opposed to on a thread created
* by sdbus-c++). If you are unsure what this all means then use
* enterEventLoop() or enterEventLoopAsync() instead.
*
* To integrate sdbus-c++ into a gtk app, pass the file descriptor returned
* by this method to g_main_context_add_poll.
* *
* @throws sdbus::Error in case of failure * @throws sdbus::Error in case of failure
*/ */
virtual PollData getEventLoopPollData() const = 0; virtual void detachSdEventLoop() = 0;
/*! /*!
* @brief Process a pending request * @brief Gets current sd-event event loop for the bus connection
* *
* @returns true if an event was processed, false if poll should be called * @return Pointer to event loop object if attached, nullptr otherwise
*/
virtual sd_event *getSdEventLoop() = 0;
/*!
* @brief Returns fd's, I/O events and timeout data to be used in an external event loop
* *
* Processes a single dbus event. All of sdbus-c++'s callbacks will be called * This function is useful to hook up a bus connection object with an
* from within this method. This method should ONLY be used in conjuction * external (like GMainLoop, boost::asio, etc.) or manual event loop
* with getEventLoopPollData(). * involving poll() or a similar I/O polling call.
* This method returns true if an I/O message was processed. This you can try *
* to call this method again before going to poll on I/O events. The method * Before **each** invocation of the I/O polling call, this function
* returns false if no operations were pending, and the caller should then * should be invoked. Returned PollData::fd file descriptor should
* poll for I/O events before calling this method again. * be polled for the events indicated by PollData::events, and the I/O
* enterEventLoop() and enterEventLoopAsync() will call this method for you, * call should block for that up to the returned PollData::timeout.
* so there is no need to call it when using these. If you are unsure what *
* this all means then don't use this method. * Additionally, returned PollData::eventFd should be polled for POLLIN
* events.
*
* After each I/O polling call the bus connection needs to process
* incoming or outgoing data, by invoking processPendingEvent().
*
* Note that the returned timeout should be considered only a maximum
* sleeping time. It is permissible (and even expected) that shorter
* timeouts are used by the calling program, in case other event sources
* are polled in the same event loop. Note that the returned time-value
* is absolute, based of CLOCK_MONOTONIC and specified in microseconds.
* Use PollData::getPollTimeout() to have the timeout value converted
* in a form that can be passed to poll(2).
*
* The bus connection conveniently integrates sd-event event loop.
* To attach the bus connection to an sd-event event loop, use
* attachSdEventLoop() function.
* *
* @throws sdbus::Error in case of failure * @throws sdbus::Error in case of failure
*/ */
virtual bool processPendingRequest() = 0; [[nodiscard]] virtual PollData getEventLoopPollData() const = 0;
/*!
* @brief Processes a pending event
*
* @returns True if an event was processed, false if no operations were pending
*
* This function drives the D-Bus connection. It processes pending I/O events.
* Queued outgoing messages (or parts thereof) are sent out. Queued incoming
* messages are dispatched to registered callbacks. Timeouts are recalculated.
*
* It returns false when no operations were pending and true if a message was
* processed. When false is returned the caller should synchronously poll for
* I/O events before calling into processPendingEvent() again.
* Don't forget to call getEventLoopPollData() each time before the next poll.
*
* You don't need to directly call this method or getEventLoopPollData() method
* when using convenient, internal bus connection event loops through
* enterEventLoop() or enterEventLoopAsync() calls, or when the bus is
* connected to an sd-event event loop through attachSdEventLoop().
* It is invoked automatically when necessary.
*
* @throws sdbus::Error in case of failure
*/
virtual bool processPendingEvent() = 0;
/*!
* @brief Provides access to the currently processed D-Bus message
*
* This method provides access to the currently processed incoming D-Bus message.
* "Currently processed" means that the registered callback handler(s) for that message
* are being invoked. This method is meant to be called from within a callback handler
* (e.g. from a D-Bus signal handler, or async method reply handler, etc.). In such a case it is
* guaranteed to return a valid D-Bus message instance for which the handler is called.
* If called from other contexts/threads, it may return a valid or invalid message, depending
* on whether a message was processed or not at the time of the call.
*
* @return Currently processed D-Bus message
*/
[[nodiscard]] virtual Message getCurrentlyProcessedMessage() const = 0;
/*! /*!
* @brief Sets general method call timeout * @brief Sets general method call timeout
@ -246,10 +219,11 @@ namespace sdbus {
* *
* @throws sdbus::Error in case of failure * @throws sdbus::Error in case of failure
*/ */
virtual uint64_t getMethodCallTimeout() const = 0; [[nodiscard]] virtual uint64_t getMethodCallTimeout() const = 0;
/*! /*!
* @brief Adds an ObjectManager at the specified D-Bus object path * @brief Adds an ObjectManager at the specified D-Bus object path
* @param[in] objectPath Object path at which the ObjectManager interface shall be installed
* *
* Creates an ObjectManager interface at the specified object path on * Creates an ObjectManager interface at the specified object path on
* the connection. This is a convenient way to interrogate a connection * the connection. This is a convenient way to interrogate a connection
@ -258,81 +232,198 @@ namespace sdbus {
* This call creates a floating registration. The ObjectManager will * This call creates a floating registration. The ObjectManager will
* be there for the object path until the connection is destroyed. * be there for the object path until the connection is destroyed.
* *
* Another, recommended way to add object managers is directly through * Another, recommended way to add object managers is directly through IObject API.
* IObject API.
* *
* @throws sdbus::Error in case of failure * @throws sdbus::Error in case of failure
*/ */
virtual void addObjectManager(const std::string& objectPath, floating_slot_t) = 0; virtual void addObjectManager(const ObjectPath& objectPath) = 0;
/*! /*!
* @brief Adds a match rule for incoming message dispatching * @brief Adds an ObjectManager at the specified D-Bus object path
* @param[in] objectPath Object path at which the ObjectManager interface shall be installed
* @return Slot handle owning the registration
*
* Creates an ObjectManager interface at the specified object path on
* the connection. This is a convenient way to interrogate a connection
* to see what objects it has.
*
* This call returns an owning slot. The lifetime of the ObjectManager
* interface is bound to the lifetime of the returned slot instance.
*
* Another, recommended way to add object managers is directly through IObject API.
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] virtual Slot addObjectManager(const ObjectPath& objectPath, return_slot_t) = 0;
/*!
* @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] 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 match rule for messages received on the specified bus connection.
* The syntax of the match rule expression passed in match is described in the D-Bus specification.
* The specified handler function callback is called for each incoming message matching the specified
* expression. The match is installed synchronously when connected to a bus broker, i.e. the call
* sends a control message requesting the match to be added to the broker and waits until the broker
* confirms the match has been installed successfully.
*
* 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.
*
* For more information, consult `man sd_bus_add_match`.
*
* @throws sdbus::Error in case of failure
*/
virtual void addMatch(const std::string& match, message_handler callback) = 0;
/*!
* @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 processing an inbound D-Bus message matching the rule
* @return RAII-style slot handle representing the ownership of the subscription * @return RAII-style slot handle representing the ownership of the subscription
* *
* The method installs a match rule for messages received on the specified bus connection. * The method installs a match rule for messages received on the specified bus connection.
* The syntax of the match rule expression passed in match is described in the D-Bus specification. * The syntax of the match rule expression passed in match is described in the D-Bus specification.
* The specified handler function callback is called for each incoming message matching the specified * The specified handler function callback is called for each incoming message matching the specified
* expression. The match is installed synchronously when connected to a bus broker, i.e. the call * expression. The match is installed synchronously when connected to a bus broker, i.e. the call
* sends a control message requested the match to be added to the broker and waits until the broker * sends a control message requesting the match to be added to the broker and waits until the broker
* confirms the match has been installed successfully. * confirms the match has been installed successfully.
* *
* Simply let go of the slot instance to uninstall the match rule from the bus connection. The slot * The lifetime of the match rule is bound to the lifetime of the returned slot instance. Destroying
* must not outlive the connection for the slot is associated with it. * the slot instance implies uninstalling of the match rule from the bus connection.
* *
* For more information, consult `man sd_bus_add_match`. * For more information, consult `man sd_bus_add_match`.
* *
* @throws sdbus::Error in case of failure * @throws sdbus::Error in case of failure
*/ */
[[nodiscard]] virtual Slot addMatch(const std::string& match, message_handler callback) = 0; [[nodiscard]] virtual Slot addMatch(const std::string& match, message_handler callback, return_slot_t) = 0;
/*! /*!
* @brief Adds a floating match rule for incoming message dispatching * @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] 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
* @param[in] Floating slot tag * @param[in] installCallback Callback handler to be called upon processing an inbound D-Bus message matching the rule
*
* 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.
* *
* The method installs a floating match rule for messages received on the specified bus connection. * 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 * 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. * is bound to the lifetime of the bus connection.
* *
* Refer to the @c addMatch(const std::string& match, message_handler callback) documentation for more * For more information, consult `man sd_bus_add_match_async`.
* information.
* *
* @throws sdbus::Error in case of failure * @throws sdbus::Error in case of failure
*/ */
virtual void addMatch(const std::string& match, message_handler callback, floating_slot_t) = 0; virtual void addMatchAsync(const std::string& match, message_handler callback, message_handler installCallback) = 0;
/*! /*!
* @copydoc IConnection::enterEventLoop() * @brief Asynchronously installs a match rule for messages received on this bus connection
* *
* @deprecated This function has been replaced by enterEventLoop() * @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.
*
* The lifetime of the match rule is bound to the lifetime of the returned slot instance. Destroying
* the slot instance implies the uninstalling of the match rule from the bus connection.
*
* For more information, consult `man sd_bus_add_match_async`.
*
* @throws sdbus::Error in case of failure
*/ */
[[deprecated("This function has been replaced by enterEventLoop()")]] void enterProcessingLoop(); [[nodiscard]] virtual Slot addMatchAsync( const std::string& match
, message_handler callback
, message_handler installCallback
, return_slot_t ) = 0;
/*! /*!
* @copydoc IConnection::enterProcessingLoopAsync() * @brief Retrieves the unique name of a connection. E.g. ":1.xx"
* *
* @deprecated This function has been replaced by enterEventLoopAsync() * @throws sdbus::Error in case of failure
*/ */
[[deprecated("This function has been replaced by enterEventLoopAsync()")]] void enterProcessingLoopAsync(); [[nodiscard]] virtual BusName getUniqueName() const = 0;
/*! /*!
* @copydoc IConnection::leaveProcessingLoop() * @brief Requests a well-known D-Bus service name on a bus
* *
* @deprecated This function has been replaced by leaveEventLoop() * @param[in] name Name to request
*
* @throws sdbus::Error in case of failure
*/ */
[[deprecated("This function has been replaced by leaveEventLoop()")]] void leaveProcessingLoop(); virtual void requestName(const ServiceName& name) = 0;
/*! /*!
* @copydoc IConnection::getProcessLoopPollData() * @brief Releases an acquired well-known D-Bus service name on a bus
* *
* @deprecated This function has been replaced by getEventLoopPollData() * @param[in] name Name to release
*
* @throws sdbus::Error in case of failure
*/ */
[[deprecated("This function has been replaced by getEventLoopPollData()")]] PollData getProcessLoopPollData() const; virtual void releaseName(const ServiceName& name) = 0;
/*!
* @struct PollData
*
* Carries poll data needed for integration with external event loop implementations.
*
* See getEventLoopPollData() for more info.
*/
struct PollData
{
/*!
* The read fd to be monitored by the event loop.
*/
int fd;
/*!
* The events to use for poll(2) alongside fd.
*/
short int events;
/*!
* Absolute timeout value in microseconds, based of CLOCK_MONOTONIC.
*
* Call getPollTimeout() to get timeout recalculated to relative timeout that can be passed to poll(2).
*/
std::chrono::microseconds timeout;
/*!
* An additional event fd to be monitored by the event loop for POLLIN events.
*/
int eventFd;
/*!
* Returns the timeout as relative value from now.
*
* Returned value is std::chrono::microseconds::max() if the timeout is indefinite.
*
* @return Relative timeout as a time duration
*/
[[nodiscard]] std::chrono::microseconds getRelativeTimeout() const;
/*!
* Returns relative timeout in the form which can be passed as argument 'timeout' to poll(2)
*
* @return -1 if the timeout is indefinite. 0 if the poll(2) shouldn't block.
* An integer in milliseconds otherwise.
*/
[[nodiscard]] int getPollTimeout() const;
};
}; };
template <typename _Rep, typename _Period> template <typename _Rep, typename _Period>
@ -342,45 +433,6 @@ namespace sdbus {
return setMethodCallTimeout(microsecs.count()); return setMethodCallTimeout(microsecs.count());
} }
inline void IConnection::enterProcessingLoop()
{
enterEventLoop();
}
inline void IConnection::enterProcessingLoopAsync()
{
enterEventLoopAsync();
}
inline void IConnection::leaveProcessingLoop()
{
leaveEventLoop();
}
inline IConnection::PollData IConnection::getProcessLoopPollData() const
{
return getEventLoopPollData();
}
/*!
* @brief Creates/opens D-Bus system bus connection
*
* @return Connection instance
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] std::unique_ptr<sdbus::IConnection> createConnection();
/*!
* @brief Creates/opens D-Bus system bus connection with a name
*
* @param[in] name Name to request on the connection after its opening
* @return Connection instance
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] std::unique_ptr<sdbus::IConnection> createConnection(const std::string& name);
/*! /*!
* @brief Creates/opens D-Bus session bus connection when in a user context, and a system bus connection, otherwise. * @brief Creates/opens D-Bus session bus connection when in a user context, and a system bus connection, otherwise.
* *
@ -388,7 +440,7 @@ namespace sdbus {
* *
* @throws sdbus::Error in case of failure * @throws sdbus::Error in case of failure
*/ */
[[nodiscard]] std::unique_ptr<sdbus::IConnection> createDefaultBusConnection(); [[nodiscard]] std::unique_ptr<sdbus::IConnection> createBusConnection();
/*! /*!
* @brief Creates/opens D-Bus session bus connection with a name when in a user context, and a system bus connection with a name, otherwise. * @brief Creates/opens D-Bus session bus connection with a name when in a user context, and a system bus connection with a name, otherwise.
@ -398,7 +450,7 @@ namespace sdbus {
* *
* @throws sdbus::Error in case of failure * @throws sdbus::Error in case of failure
*/ */
[[nodiscard]] std::unique_ptr<sdbus::IConnection> createDefaultBusConnection(const std::string& name); [[nodiscard]] std::unique_ptr<sdbus::IConnection> createBusConnection(const ServiceName& name);
/*! /*!
* @brief Creates/opens D-Bus system bus connection * @brief Creates/opens D-Bus system bus connection
@ -417,7 +469,7 @@ namespace sdbus {
* *
* @throws sdbus::Error in case of failure * @throws sdbus::Error in case of failure
*/ */
[[nodiscard]] std::unique_ptr<sdbus::IConnection> createSystemBusConnection(const std::string& name); [[nodiscard]] std::unique_ptr<sdbus::IConnection> createSystemBusConnection(const ServiceName& name);
/*! /*!
* @brief Creates/opens D-Bus session bus connection * @brief Creates/opens D-Bus session bus connection
@ -436,7 +488,7 @@ namespace sdbus {
* *
* @throws sdbus::Error in case of failure * @throws sdbus::Error in case of failure
*/ */
[[nodiscard]] std::unique_ptr<sdbus::IConnection> createSessionBusConnection(const std::string& name); [[nodiscard]] std::unique_ptr<sdbus::IConnection> createSessionBusConnection(const ServiceName& name);
/*! /*!
* @brief Creates/opens D-Bus session bus connection at a custom address * @brief Creates/opens D-Bus session bus connection at a custom address
@ -459,6 +511,74 @@ namespace sdbus {
* @throws sdbus::Error in case of failure * @throws sdbus::Error in case of failure
*/ */
[[nodiscard]] std::unique_ptr<sdbus::IConnection> createRemoteSystemBusConnection(const std::string& host); [[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_ */ #endif /* SDBUS_CXX_ICONNECTION_H_ */

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file IObject.h * @file IObject.h
* *
@ -28,17 +28,20 @@
#define SDBUS_CXX_IOBJECT_H_ #define SDBUS_CXX_IOBJECT_H_
#include <sdbus-c++/ConvenienceApiClasses.h> #include <sdbus-c++/ConvenienceApiClasses.h>
#include <sdbus-c++/TypeTraits.h>
#include <sdbus-c++/Flags.h> #include <sdbus-c++/Flags.h>
#include <sdbus-c++/TypeTraits.h>
#include <sdbus-c++/VTableItems.h>
#include <functional> #include <functional>
#include <string>
#include <memory> #include <memory>
#include <string>
#include <vector> #include <vector>
// Forward declarations // Forward declarations
namespace sdbus { namespace sdbus {
class Signal; class Signal;
class IConnection; class IConnection;
class ObjectPath;
} }
namespace sdbus { namespace sdbus {
@ -58,186 +61,108 @@ namespace sdbus {
***********************************************/ ***********************************************/
class IObject class IObject
{ {
public: public: // High-level, convenience API
virtual ~IObject() = default; virtual ~IObject() = default;
/*! /*!
* @brief Registers method that the object will provide on D-Bus * @brief Adds a declaration of methods, properties and signals of the object at a given interface
* *
* @param[in] interfaceName Name of an interface that the method will belong to * @param[in] vtable Individual instances of VTable item structures stored in a vector
* @param[in] methodName Name of the method * @return VTableAdder high-level helper class
* @param[in] inputSignature D-Bus signature of method input parameters *
* @param[in] outputSignature D-Bus signature of method output parameters * This method is used to declare attributes for the object under the given interface.
* @param[in] methodCallback Callback that implements the body of the method * Parameter `vtable' represents a vtable definition that may contain method declarations
* @param[in] flags D-Bus method flags (privileged, deprecated, or no reply) * (using MethodVTableItem struct), property declarations (using PropertyVTableItem
* struct), signal declarations (using SignalVTableItem struct), or global interface
* flags (using InterfaceFlagsVTableItem struct).
*
* An interface can have any number of vtables attached to it.
*
* Consult manual pages for the underlying `sd_bus_add_object_vtable` function for more information.
*
* The method can be called at any time during object's lifetime.
*
* When called like `addVTable(vtable).forInterface(interface)`, then an internal registration
* slot is created for that vtable and its lifetime is tied to the lifetime of the Object instance.
* When called like `addVTable(items...).forInterface(interface, sdbus::return_slot)`, then an internal
* registration slot is created for the vtable and is returned to the caller. Keeping the slot means
* keep the registration "alive". Destroying the slot means that the vtable is not needed anymore,
* and the vtable gets removed from the object. This allows for "dynamic" object API where vtables
* can be added or removed by the user at runtime.
*
* The function provides strong exception guarantee. The state of the object remains
* unmodified in face of an exception.
* *
* @throws sdbus::Error in case of failure * @throws sdbus::Error in case of failure
*/ */
virtual void registerMethod( const std::string& interfaceName [[nodiscard]] VTableAdder addVTable(std::vector<VTableItem> vtable);
, std::string methodName
, std::string inputSignature
, std::string outputSignature
, method_callback methodCallback
, Flags flags = {} ) = 0;
/*! /*!
* @brief Registers method that the object will provide on D-Bus * @brief Adds a declaration of methods, properties and signals of the object at a given interface
* *
* @param[in] interfaceName Name of an interface that the method will belong to * @param[in] items Individual instances of VTable item structures
* @param[in] methodName Name of the method * @return VTableAdder high-level helper class
* @param[in] inputSignature D-Bus signature of method input parameters
* @param[in] inputNames Names of input parameters
* @param[in] outputSignature D-Bus signature of method output parameters
* @param[in] outputNames Names of output parameters
* @param[in] methodCallback Callback that implements the body of the method
* @param[in] flags D-Bus method flags (privileged, deprecated, or no reply)
* *
* Provided names of input and output parameters will be included in the introspection * This method is used to declare attributes for the object under the given interface.
* description (given that at least version 242 of underlying libsystemd library is * Parameter pack contains vtable definition that may contain method declarations
* used; otherwise, names of parameters are ignored). This usually helps better describe * (using MethodVTableItem struct), property declarations (using PropertyVTableItem
* the API to the introspector. * struct), signal declarations (using SignalVTableItem struct), or global interface
* flags (using InterfaceFlagsVTableItem struct).
*
* An interface can have any number of vtables attached to it.
*
* Consult manual pages for the underlying `sd_bus_add_object_vtable` function for more information.
*
* The method can be called at any time during object's lifetime.
*
* When called like `addVTable(items...).forInterface(interface)`, then an internal registration
* slot is created for that vtable and its lifetime is tied to the lifetime of the Object instance.
* When called like `addVTable(items...).forInterface(interface, sdbus::return_slot)`, then an internal
* registration slot is created for the vtable and is returned to the caller. Keeping the slot means
* keep the registration "alive". Destroying the slot means that the vtable is not needed anymore,
* and the vtable gets removed from the object. This allows for "dynamic" object API where vtables
* can be added or removed by the user at runtime.
*
* The function provides strong exception guarantee. The state of the object remains
* unmodified in face of an exception.
* *
* @throws sdbus::Error in case of failure * @throws sdbus::Error in case of failure
*/ */
virtual void registerMethod( const std::string& interfaceName template < typename... VTableItems
, std::string methodName , typename = std::enable_if_t<(is_one_of_variants_types<VTableItem, std::decay_t<VTableItems>> && ...)> >
, std::string inputSignature [[nodiscard]] VTableAdder addVTable(VTableItems&&... items);
, const std::vector<std::string>& inputNames
, std::string outputSignature
, const std::vector<std::string>& outputNames
, method_callback methodCallback
, Flags flags = {} ) = 0;
/*! /*!
* @brief Registers signal that the object will emit on D-Bus * @brief Emits signal on D-Bus
* *
* @param[in] interfaceName Name of an interface that the signal will fall under
* @param[in] signalName Name of the signal * @param[in] signalName Name of the signal
* @param[in] signature D-Bus signature of signal parameters * @return A helper object for convenient emission of signals
* @param[in] flags D-Bus signal flags (deprecated) *
* This is a high-level, convenience way of emitting D-Bus signals that abstracts
* from the D-Bus message concept. Signal arguments are automatically serialized
* in a message and D-Bus signatures automatically deduced from the provided native arguments.
*
* Example of use:
* @code
* int arg1 = ...;
* double arg2 = ...;
* SignalName fooSignal{"fooSignal"};
* object_.emitSignal(fooSignal).onInterface("com.kistler.foo").withArguments(arg1, arg2);
* @endcode
* *
* @throws sdbus::Error in case of failure * @throws sdbus::Error in case of failure
*/ */
virtual void registerSignal( const std::string& interfaceName [[nodiscard]] SignalEmitter emitSignal(const SignalName& signalName);
, std::string signalName
, std::string signature
, Flags flags = {} ) = 0;
/*! /*!
* @brief Registers signal that the object will emit on D-Bus * @copydoc IObject::emitSignal(const SignalName&)
*
* @param[in] interfaceName Name of an interface that the signal will fall under
* @param[in] signalName Name of the signal
* @param[in] signature D-Bus signature of signal parameters
* @param[in] paramNames Names of parameters of the signal
* @param[in] flags D-Bus signal flags (deprecated)
*
* Provided names of signal output parameters will be included in the introspection
* description (given that at least version 242 of underlying libsystemd library is
* used; otherwise, names of parameters are ignored). This usually helps better describe
* the API to the introspector.
*
* @throws sdbus::Error in case of failure
*/ */
virtual void registerSignal( const std::string& interfaceName [[nodiscard]] SignalEmitter emitSignal(const std::string& signalName);
, std::string signalName
, std::string signature
, const std::vector<std::string>& paramNames
, Flags flags = {} ) = 0;
/*! /*!
* @brief Registers read-only property that the object will provide on D-Bus * @copydoc IObject::emitSignal(const SignalName&)
*
* @param[in] interfaceName Name of an interface that the property will fall under
* @param[in] propertyName Name of the property
* @param[in] signature D-Bus signature of property parameters
* @param[in] getCallback Callback that implements the body of the property getter
* @param[in] flags D-Bus property flags (deprecated, property update behavior)
*
* @throws sdbus::Error in case of failure
*/ */
virtual void registerProperty( const std::string& interfaceName [[nodiscard]] SignalEmitter emitSignal(const char* signalName);
, std::string propertyName
, std::string signature
, property_get_callback getCallback
, Flags flags = {} ) = 0;
/*!
* @brief Registers read/write property that the object will provide on D-Bus
*
* @param[in] interfaceName Name of an interface that the property will fall under
* @param[in] propertyName Name of the property
* @param[in] signature D-Bus signature of property parameters
* @param[in] getCallback Callback that implements the body of the property getter
* @param[in] setCallback Callback that implements the body of the property setter
* @param[in] flags D-Bus property flags (deprecated, property update behavior)
*
* @throws sdbus::Error in case of failure
*/
virtual void registerProperty( const std::string& interfaceName
, std::string propertyName
, std::string signature
, property_get_callback getCallback
, property_set_callback setCallback
, Flags flags = {} ) = 0;
/*!
* @brief Sets flags for a given interface
*
* @param[in] interfaceName Name of an interface whose flags will be set
* @param[in] flags Flags to be set
*
* @throws sdbus::Error in case of failure
*/
virtual void setInterfaceFlags(const std::string& interfaceName, Flags flags) = 0;
/*!
* @brief Finishes object API registration and publishes the object on the bus
*
* The method exports all up to now registered methods, signals and properties on D-Bus.
* Must be called after all methods, signals and properties have been registered.
*
* @throws sdbus::Error in case of failure
*/
virtual void finishRegistration() = 0;
/*!
* @brief Unregisters object's API and removes object from the bus
*
* This method unregisters the object, its interfaces, methods, signals and properties
* from the bus. Unregistration is done automatically also in object's destructor. This
* method makes sense if, in the process of object removal, we need to make sure that
* callbacks are unregistered explicitly before the final destruction of the object instance.
*
* @throws sdbus::Error in case of failure
*/
virtual void unregister() = 0;
/*!
* @brief Creates a signal message
*
* @param[in] interfaceName Name of an interface that the signal belongs under
* @param[in] signalName Name of the signal
* @return A signal message
*
* Serialize signal arguments into the returned message and emit the signal by passing
* the message with serialized arguments to the @c emitSignal function.
* Alternatively, use higher-level API @c emitSignal(const std::string& signalName) defined below.
*
* @throws sdbus::Error in case of failure
*/
virtual Signal createSignal(const std::string& interfaceName, const std::string& signalName) = 0;
/*!
* @brief Emits signal for this object path
*
* @param[in] message Signal message to be sent out
*
* Note: To avoid messing with messages, use higher-level API defined below.
*
* @throws sdbus::Error in case of failure
*/
virtual void emitSignal(const sdbus::Signal& message) = 0;
/*! /*!
* @brief Emits PropertyChanged signal for specified properties under a given interface of this object path * @brief Emits PropertyChanged signal for specified properties under a given interface of this object path
@ -247,7 +172,12 @@ namespace sdbus {
* *
* @throws sdbus::Error in case of failure * @throws sdbus::Error in case of failure
*/ */
virtual void emitPropertiesChangedSignal(const std::string& interfaceName, const std::vector<std::string>& propNames) = 0; virtual void emitPropertiesChangedSignal(const InterfaceName& interfaceName, const std::vector<PropertyName>& propNames) = 0;
/*!
* @copydoc IObject::emitPropertiesChangedSignal(const InterfaceName&,const std::vector<PropertyName>&)
*/
virtual void emitPropertiesChangedSignal(const char* interfaceName, const std::vector<PropertyName>& propNames) = 0;
/*! /*!
* @brief Emits PropertyChanged signal for all properties on a given interface of this object path * @brief Emits PropertyChanged signal for all properties on a given interface of this object path
@ -256,7 +186,12 @@ namespace sdbus {
* *
* @throws sdbus::Error in case of failure * @throws sdbus::Error in case of failure
*/ */
virtual void emitPropertiesChangedSignal(const std::string& interfaceName) = 0; virtual void emitPropertiesChangedSignal(const InterfaceName& interfaceName) = 0;
/*!
* @copydoc IObject::emitPropertiesChangedSignal(const InterfaceName&)
*/
virtual void emitPropertiesChangedSignal(const char* interfaceName) = 0;
/*! /*!
* @brief Emits InterfacesAdded signal on this object path * @brief Emits InterfacesAdded signal on this object path
@ -276,14 +211,12 @@ namespace sdbus {
* @brief Emits InterfacesAdded signal on this object path * @brief Emits InterfacesAdded signal on this object path
* *
* This emits an InterfacesAdded signal on this object path with explicitly provided list * This emits an InterfacesAdded signal on this object path with explicitly provided list
* of registered interfaces. As sdbus-c++ does currently not supported adding/removing * of registered interfaces. Since v2.0, sdbus-c++ supports dynamically addable/removable
* interfaces of an existing object at run time (an object has a fixed set of interfaces * object interfaces and their vtables, so this method now makes more sense.
* registered by the time of invoking finishRegistration()), emitInterfacesAddedSignal(void)
* is probably what you are looking for.
* *
* @throws sdbus::Error in case of failure * @throws sdbus::Error in case of failure
*/ */
virtual void emitInterfacesAddedSignal(const std::vector<std::string>& interfaces) = 0; virtual void emitInterfacesAddedSignal(const std::vector<InterfaceName>& interfaces) = 0;
/*! /*!
* @brief Emits InterfacesRemoved signal on this object path * @brief Emits InterfacesRemoved signal on this object path
@ -300,14 +233,12 @@ namespace sdbus {
* @brief Emits InterfacesRemoved signal on this object path * @brief Emits InterfacesRemoved signal on this object path
* *
* This emits an InterfacesRemoved signal on the given path with explicitly provided list * This emits an InterfacesRemoved signal on the given path with explicitly provided list
* of registered interfaces. As sdbus-c++ does currently not supported adding/removing * of registered interfaces. Since v2.0, sdbus-c++ supports dynamically addable/removable
* interfaces of an existing object at run time (an object has a fixed set of interfaces * object interfaces and their vtables, so this method now makes more sense.
* registered by the time of invoking finishRegistration()), emitInterfacesRemovedSignal(void)
* is probably what you are looking for.
* *
* @throws sdbus::Error in case of failure * @throws sdbus::Error in case of failure
*/ */
virtual void emitInterfacesRemovedSignal(const std::vector<std::string>& interfaces) = 0; virtual void emitInterfacesRemovedSignal(const std::vector<InterfaceName>& interfaces) = 0;
/*! /*!
* @brief Adds an ObjectManager interface at the path of this D-Bus object * @brief Adds an ObjectManager interface at the path of this D-Bus object
@ -316,175 +247,215 @@ namespace sdbus {
* the connection. This is a convenient way to interrogate a connection * the connection. This is a convenient way to interrogate a connection
* to see what objects it has. * to see what objects it has.
* *
* This call creates a so-called floating registration. This means that
* the ObjectManager interface stays there for the lifetime of the object.
*
* @throws sdbus::Error in case of failure * @throws sdbus::Error in case of failure
*/ */
virtual void addObjectManager() = 0; virtual void addObjectManager() = 0;
/*! /*!
* @brief Removes an ObjectManager interface from the path of this D-Bus object * @brief Adds an ObjectManager interface at the path of this D-Bus object
*
* @return Slot handle owning the registration
*
* Creates an ObjectManager interface at the specified object path on
* the connection. This is a convenient way to interrogate a connection
* to see what objects it has.
*
* The lifetime of the ObjectManager interface is bound to the lifetime
* of the returned slot instance.
* *
* @throws sdbus::Error in case of failure * @throws sdbus::Error in case of failure
*/ */
virtual void removeObjectManager() = 0; [[nodiscard]] virtual Slot addObjectManager(return_slot_t) = 0;
/*!
* @brief Tests whether ObjectManager interface is added at the path of this D-Bus object
* @return True if ObjectManager interface is there, false otherwise
*/
virtual bool hasObjectManager() const = 0;
/*! /*!
* @brief Provides D-Bus connection used by the object * @brief Provides D-Bus connection used by the object
* *
* @return Reference to the D-Bus connection * @return Reference to the D-Bus connection
*/ */
virtual sdbus::IConnection& getConnection() const = 0; [[nodiscard]] virtual sdbus::IConnection& getConnection() const = 0;
/*!
* @brief Registers method that the object will provide on D-Bus
*
* @param[in] methodName Name of the method
* @return A helper object for convenient registration of the method
*
* This is a high-level, convenience way of registering D-Bus methods that abstracts
* from the D-Bus message concept. Method arguments/return value are automatically (de)serialized
* in a message and D-Bus signatures automatically deduced from the parameters and return type
* of the provided native method implementation callback.
*
* Example of use:
* @code
* object.registerMethod("doFoo").onInterface("com.kistler.foo").implementedAs([this](int value){ return this->doFoo(value); });
* @endcode
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] MethodRegistrator registerMethod(const std::string& methodName);
/*!
* @brief Registers signal that the object will provide on D-Bus
*
* @param[in] signalName Name of the signal
* @return A helper object for convenient registration of the signal
*
* This is a high-level, convenience way of registering D-Bus signals that abstracts
* from the D-Bus message concept. Signal arguments are automatically (de)serialized
* in a message and D-Bus signatures automatically deduced from the provided native parameters.
*
* Example of use:
* @code
* object.registerSignal("paramChange").onInterface("com.kistler.foo").withParameters<std::map<int32_t, std::string>>();
* @endcode
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] SignalRegistrator registerSignal(const std::string& signalName);
/*!
* @brief Registers property that the object will provide on D-Bus
*
* @param[in] propertyName Name of the property
* @return A helper object for convenient registration of the property
*
* This is a high-level, convenience way of registering D-Bus properties that abstracts
* from the D-Bus message concept. Property arguments are automatically (de)serialized
* in a message and D-Bus signatures automatically deduced from the provided native callbacks.
*
* Example of use:
* @code
* object_.registerProperty("state").onInterface("com.kistler.foo").withGetter([this](){ return this->state(); });
* @endcode
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] PropertyRegistrator registerProperty(const std::string& propertyName);
/*!
* @brief Sets flags (annotations) for a given interface
*
* @param[in] interfaceName Name of an interface whose flags will be set
* @return A helper object for convenient setting of Interface flags
*
* This is a high-level, convenience alternative to the other setInterfaceFlags overload.
*
* Example of use:
* @code
* object_.setInterfaceFlags("com.kistler.foo").markAsDeprecated().withPropertyUpdateBehavior(sdbus::Flags::EMITS_NO_SIGNAL);
* @endcode
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] InterfaceFlagsSetter setInterfaceFlags(const std::string& interfaceName);
/*!
* @brief Emits signal on D-Bus
*
* @param[in] signalName Name of the signal
* @return A helper object for convenient emission of signals
*
* This is a high-level, convenience way of emitting D-Bus signals that abstracts
* from the D-Bus message concept. Signal arguments are automatically serialized
* in a message and D-Bus signatures automatically deduced from the provided native arguments.
*
* Example of use:
* @code
* int arg1 = ...;
* double arg2 = ...;
* object_.emitSignal("fooSignal").onInterface("com.kistler.foo").withArguments(arg1, arg2);
* @endcode
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] SignalEmitter emitSignal(const std::string& signalName);
/*! /*!
* @brief Returns object path of the underlying DBus object * @brief Returns object path of the underlying DBus object
*/ */
virtual const std::string& getObjectPath() const = 0; [[nodiscard]] virtual const ObjectPath& getObjectPath() const = 0;
/*! /*!
* @brief Provides currently processed D-Bus message * @brief Provides access to the currently processed D-Bus message
* *
* This method provides immutable access to the currently processed incoming D-Bus message. * This method provides access to the currently processed incoming D-Bus message.
* "Currently processed" means that the registered callback handler(s) for that message * "Currently processed" means that the registered callback handler(s) for that message
* are being invoked. This method is meant to be called from within a callback handler * are being invoked. This method is meant to be called from within a callback handler
* (e.g. D-Bus method implementation handler). In such a case it is guaranteed to return * (e.g. from a D-Bus signal handler, or async method reply handler, etc.). In such a case it is
* a valid pointer to the D-Bus message for which the handler is called. If called from other * guaranteed to return a valid D-Bus message instance for which the handler is called.
* contexts/threads, it may return a nonzero pointer or a nullptr, depending on whether a message * If called from other contexts/threads, it may return a valid or invalid message, depending
* was processed at the time of call or not, but the value is nondereferencable, since the pointed-to * on whether a message was processed or not at the time of the call.
* message may have gone in the meantime.
* *
* @return A pointer to the currently processed D-Bus message * @return Currently processed D-Bus message
*/ */
virtual const Message* getCurrentlyProcessedMessage() const = 0; [[nodiscard]] virtual Message getCurrentlyProcessedMessage() const = 0;
/*!
* @brief Unregisters object's API and removes object from the bus
*
* This method unregisters the object, its interfaces, methods, signals and properties
* from the bus. Unregistration is done automatically also in object's destructor. This
* method makes sense if, in the process of object removal, we need to make sure that
* callbacks are unregistered explicitly before the final destruction of the object instance.
*
* @throws sdbus::Error in case of failure
*/
virtual void unregister() = 0;
public: // Lower-level, message-based API
/*!
* @brief Adds a declaration of methods, properties and signals of the object at a given interface
*
* @param[in] interfaceName Name of an interface the the vtable is registered for
* @param[in] items Individual instances of VTable item structures
*
* This method is used to declare attributes for the object under the given interface.
* Parameter `items' represents a vtable definition that may contain method declarations
* (using MethodVTableItem struct), property declarations (using PropertyVTableItem
* struct), signal declarations (using SignalVTableItem struct), or global interface
* flags (using InterfaceFlagsVTableItem struct).
*
* An interface can have any number of vtables attached to it.
*
* Consult manual pages for the underlying `sd_bus_add_object_vtable` function for more information.
*
* The method can be called at any time during object's lifetime. For each vtable an internal
* registration slot is created and its lifetime is tied to the lifetime of the Object instance.
*
* The function provides strong exception guarantee. The state of the object remains
* unmodified in face of an exception.
*
* @throws sdbus::Error in case of failure
*/
template < typename... VTableItems
, typename = std::enable_if_t<(is_one_of_variants_types<VTableItem, std::decay_t<VTableItems>> && ...)> >
void addVTable(InterfaceName interfaceName, VTableItems&&... items);
/*!
* @brief Adds a declaration of methods, properties and signals of the object at a given interface
*
* @param[in] interfaceName Name of an interface the the vtable is registered for
* @param[in] vtable A list of individual descriptions in the form of VTable item instances
*
* This method is used to declare attributes for the object under the given interface.
* The `vtable' parameter may contain method declarations (using MethodVTableItem struct),
* property declarations (using PropertyVTableItem struct), signal declarations (using
* SignalVTableItem struct), or global interface flags (using InterfaceFlagsVTableItem struct).
*
* An interface can have any number of vtables attached to it.
*
* Consult manual pages for the underlying `sd_bus_add_object_vtable` function for more information.
*
* The method can be called at any time during object's lifetime. For each vtable an internal
* registration slot is created and its lifetime is tied to the lifetime of the Object instance.
*
* The function provides strong exception guarantee. The state of the object remains
* unmodified in face of an exception.
*
* @throws sdbus::Error in case of failure
*/
virtual void addVTable(InterfaceName interfaceName, std::vector<VTableItem> vtable) = 0;
/*!
* @brief Adds a declaration of methods, properties and signals of the object at a given interface
*
* @param[in] interfaceName Name of an interface the the vtable is registered for
* @param[in] vtable A list of individual descriptions in the form of VTable item instances
*
* This method is used to declare attributes for the object under the given interface.
* The `vtable' parameter may contain method declarations (using MethodVTableItem struct),
* property declarations (using PropertyVTableItem struct), signal declarations (using
* SignalVTableItem struct), or global interface flags (using InterfaceFlagsVTableItem struct).
*
* An interface can have any number of vtables attached to it.
*
* Consult manual pages for the underlying `sd_bus_add_object_vtable` function for more information.
*
* The method can be called at any time during object's lifetime. For each vtable an internal
* registration slot is created and is returned to the caller. The returned slot should be destroyed
* when the vtable is not needed anymore. This allows for "dynamic" object API where vtables
* can be added or removed by the user at runtime.
*
* The function provides strong exception guarantee. The state of the object remains
* unmodified in face of an exception.
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] virtual Slot addVTable(InterfaceName interfaceName, std::vector<VTableItem> vtable, return_slot_t) = 0;
/*!
* @brief Creates a signal message
*
* @param[in] interfaceName Name of an interface that the signal belongs under
* @param[in] signalName Name of the signal
* @return A signal message
*
* Serialize signal arguments into the returned message and emit the signal by passing
* the message with serialized arguments to the @c emitSignal function.
* Alternatively, use higher-level API @c emitSignal(const std::string& signalName) defined below.
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] virtual Signal createSignal(const InterfaceName& interfaceName, const SignalName& signalName) = 0;
/*!
* @brief Emits signal for this object path
*
* @param[in] message Signal message to be sent out
*
* Note: To avoid messing with messages, use higher-level API defined below.
*
* @throws sdbus::Error in case of failure
*/
virtual void emitSignal(const sdbus::Signal& message) = 0;
protected: // Internal API for efficiency reasons used by high-level API helper classes
friend SignalEmitter;
[[nodiscard]] virtual Signal createSignal(const char* interfaceName, const char* signalName) = 0;
}; };
// Out-of-line member definitions // Out-of-line member definitions
inline MethodRegistrator IObject::registerMethod(const std::string& methodName) inline SignalEmitter IObject::emitSignal(const SignalName& signalName)
{ {
return MethodRegistrator(*this, methodName); return SignalEmitter(*this, signalName);
}
inline SignalRegistrator IObject::registerSignal(const std::string& signalName)
{
return SignalRegistrator(*this, signalName);
}
inline PropertyRegistrator IObject::registerProperty(const std::string& propertyName)
{
return PropertyRegistrator(*this, propertyName);
}
inline InterfaceFlagsSetter IObject::setInterfaceFlags(const std::string& interfaceName)
{
return InterfaceFlagsSetter(*this, interfaceName);
} }
inline SignalEmitter IObject::emitSignal(const std::string& signalName) inline SignalEmitter IObject::emitSignal(const std::string& signalName)
{
return SignalEmitter(*this, signalName.c_str());
}
inline SignalEmitter IObject::emitSignal(const char* signalName)
{ {
return SignalEmitter(*this, signalName); return SignalEmitter(*this, signalName);
} }
template <typename... VTableItems, typename>
void IObject::addVTable(InterfaceName interfaceName, VTableItems&&... items)
{
addVTable(std::move(interfaceName), {std::forward<VTableItems>(items)...});
}
template <typename... VTableItems, typename>
VTableAdder IObject::addVTable(VTableItems&&... items)
{
return addVTable(std::vector<VTableItem>{std::forward<VTableItems>(items)...});
}
inline VTableAdder IObject::addVTable(std::vector<VTableItem> vtable)
{
return VTableAdder(*this, std::move(vtable));
}
/*! /*!
* @brief Creates instance representing a D-Bus object * @brief Creates instance representing a D-Bus object
* *
@ -503,10 +474,11 @@ namespace sdbus {
* auto proxy = sdbus::createObject(connection, "/com/kistler/foo"); * auto proxy = sdbus::createObject(connection, "/com/kistler/foo");
* @endcode * @endcode
*/ */
[[nodiscard]] std::unique_ptr<sdbus::IObject> createObject(sdbus::IConnection& connection, std::string objectPath); [[nodiscard]] std::unique_ptr<sdbus::IObject> createObject(sdbus::IConnection& connection, ObjectPath objectPath);
} }
#include <sdbus-c++/ConvenienceApiClasses.inl> #include <sdbus-c++/ConvenienceApiClasses.inl>
#include <sdbus-c++/VTableItems.inl>
#endif /* SDBUS_CXX_IOBJECT_H_ */ #endif /* SDBUS_CXX_IOBJECT_H_ */

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file Message.h * @file Message.h
* *
@ -27,16 +27,27 @@
#ifndef SDBUS_CXX_MESSAGE_H_ #ifndef SDBUS_CXX_MESSAGE_H_
#define SDBUS_CXX_MESSAGE_H_ #define SDBUS_CXX_MESSAGE_H_
#include <sdbus-c++/TypeTraits.h>
#include <sdbus-c++/Error.h> #include <sdbus-c++/Error.h>
#include <string> #include <sdbus-c++/TypeTraits.h>
#include <vector>
#include <map> #include <algorithm>
#include <utility> #include <array>
#include <cstdint>
#include <cassert> #include <cassert>
#include <cstdint>
#include <cstring>
#include <functional> #include <functional>
#include <map>
#ifdef __has_include
# if __has_include(<span>)
# include <span>
# endif
#endif
#include <string>
#include <sys/types.h> #include <sys/types.h>
#include <unordered_map>
#include <utility>
#include <variant>
#include <vector>
// Forward declarations // Forward declarations
namespace sdbus { namespace sdbus {
@ -48,7 +59,6 @@ namespace sdbus {
class MethodReply; class MethodReply;
namespace internal { namespace internal {
class ISdBus; class ISdBus;
class IConnection;
} }
} }
@ -63,13 +73,19 @@ namespace sdbus {
* Serialization and deserialization functions are provided for types supported * Serialization and deserialization functions are provided for types supported
* by D-Bus. * by D-Bus.
* *
* You don't need to work with this class directly if you use high-level APIs * You mostly don't need to work with this class directly if you use high-level
* of @c IObject and @c IProxy. * APIs of @c IObject and @c IProxy.
* *
***********************************************/ ***********************************************/
class [[nodiscard]] Message class [[nodiscard]] Message
{ {
public: public:
Message(const Message&) noexcept;
Message& operator=(const Message&) noexcept;
Message(Message&& other) noexcept;
Message& operator=(Message&& other) noexcept;
~Message();
Message& operator<<(bool item); Message& operator<<(bool item);
Message& operator<<(int16_t item); Message& operator<<(int16_t item);
Message& operator<<(int32_t item); Message& operator<<(int32_t item);
@ -81,10 +97,31 @@ namespace sdbus {
Message& operator<<(double item); Message& operator<<(double item);
Message& operator<<(const char *item); Message& operator<<(const char *item);
Message& operator<<(const std::string &item); Message& operator<<(const std::string &item);
Message& operator<<(std::string_view item);
Message& operator<<(const Variant &item); Message& operator<<(const Variant &item);
template <typename ...Elements>
Message& operator<<(const std::variant<Elements...>& value);
Message& operator<<(const ObjectPath &item); Message& operator<<(const ObjectPath &item);
Message& operator<<(const Signature &item); Message& operator<<(const Signature &item);
Message& operator<<(const UnixFd &item); Message& operator<<(const UnixFd &item);
template <typename _Element, typename _Allocator>
Message& operator<<(const std::vector<_Element, _Allocator>& items);
template <typename _Element, std::size_t _Size>
Message& operator<<(const std::array<_Element, _Size>& items);
#ifdef __cpp_lib_span
template <typename _Element, std::size_t _Extent>
Message& operator<<(const std::span<_Element, _Extent>& items);
#endif
template <typename _Enum, typename = std::enable_if_t<std::is_enum_v<_Enum>>>
Message& operator<<(const _Enum& item);
template <typename _Key, typename _Value, typename _Compare, typename _Allocator>
Message& operator<<(const std::map<_Key, _Value, _Compare, _Allocator>& items);
template <typename _Key, typename _Value, typename _Hash, typename _KeyEqual, typename _Allocator>
Message& operator<<(const std::unordered_map<_Key, _Value, _Hash, _KeyEqual, _Allocator>& items);
template <typename... _ValueTypes>
Message& operator<<(const Struct<_ValueTypes...>& item);
template <typename... _ValueTypes>
Message& operator<<(const std::tuple<_ValueTypes...>& item);
Message& operator>>(bool& item); Message& operator>>(bool& item);
Message& operator>>(int16_t& item); Message& operator>>(int16_t& item);
@ -98,39 +135,80 @@ namespace sdbus {
Message& operator>>(char*& item); Message& operator>>(char*& item);
Message& operator>>(std::string &item); Message& operator>>(std::string &item);
Message& operator>>(Variant &item); Message& operator>>(Variant &item);
template <typename ...Elements>
Message& operator>>(std::variant<Elements...>& value);
Message& operator>>(ObjectPath &item); Message& operator>>(ObjectPath &item);
Message& operator>>(Signature &item); Message& operator>>(Signature &item);
Message& operator>>(UnixFd &item); Message& operator>>(UnixFd &item);
template <typename _Element, typename _Allocator>
Message& operator>>(std::vector<_Element, _Allocator>& items);
template <typename _Element, std::size_t _Size>
Message& operator>>(std::array<_Element, _Size>& items);
#ifdef __cpp_lib_span
template <typename _Element, std::size_t _Extent>
Message& operator>>(std::span<_Element, _Extent>& items);
#endif
template <typename _Enum, typename = std::enable_if_t<std::is_enum_v<_Enum>>>
Message& operator>>(_Enum& item);
template <typename _Key, typename _Value, typename _Compare, typename _Allocator>
Message& operator>>(std::map<_Key, _Value, _Compare, _Allocator>& items);
template <typename _Key, typename _Value, typename _Hash, typename _KeyEqual, typename _Allocator>
Message& operator>>(std::unordered_map<_Key, _Value, _Hash, _KeyEqual, _Allocator>& items);
template <typename... _ValueTypes>
Message& operator>>(Struct<_ValueTypes...>& item);
template <typename... _ValueTypes>
Message& operator>>(std::tuple<_ValueTypes...>& item);
Message& openContainer(const std::string& signature); template <typename _ElementType>
Message& openContainer();
Message& openContainer(const char* signature);
Message& closeContainer(); Message& closeContainer();
Message& openDictEntry(const std::string& signature); template <typename _KeyType, typename _ValueType>
Message& openDictEntry();
Message& openDictEntry(const char* signature);
Message& closeDictEntry(); Message& closeDictEntry();
Message& openVariant(const std::string& signature); template <typename _ValueType>
Message& openVariant();
Message& openVariant(const char* signature);
Message& closeVariant(); Message& closeVariant();
Message& openStruct(const std::string& signature); template <typename... _ValueTypes>
Message& openStruct();
Message& openStruct(const char* signature);
Message& closeStruct(); Message& closeStruct();
Message& enterContainer(const std::string& signature); template <typename _ElementType>
Message& enterContainer();
Message& enterContainer(const char* signature);
Message& exitContainer(); Message& exitContainer();
Message& enterDictEntry(const std::string& signature); template <typename _KeyType, typename _ValueType>
Message& enterDictEntry();
Message& enterDictEntry(const char* signature);
Message& exitDictEntry(); Message& exitDictEntry();
Message& enterVariant(const std::string& signature); template <typename _ValueType>
Message& enterVariant();
Message& enterVariant(const char* signature);
Message& exitVariant(); Message& exitVariant();
Message& enterStruct(const std::string& signature); template <typename... _ValueTypes>
Message& enterStruct();
Message& enterStruct(const char* signature);
Message& exitStruct(); Message& exitStruct();
Message& appendArray(char type, const void *ptr, size_t size);
Message& readArray(char type, const void **ptr, size_t *size);
explicit operator bool() const; explicit operator bool() const;
void clearFlags(); void clearFlags();
std::string getInterfaceName() const; const char* getInterfaceName() const;
std::string getMemberName() const; const char* getMemberName() const;
std::string getSender() const; const char* getSender() const;
std::string getPath() const; const char* getPath() const;
std::string getDestination() const; const char* getDestination() const;
void peekType(std::string& type, std::string& contents) const; // TODO: short docs in whole Message API
std::pair<char, const char*> peekType() const;
bool isValid() const; bool isValid() const;
bool isEmpty() const; bool isEmpty() const;
bool isAtEnd(bool complete) const;
void copyTo(Message& destination, bool complete) const; void copyTo(Message& destination, bool complete) const;
void seal(); void seal();
@ -146,19 +224,31 @@ namespace sdbus {
class Factory; class Factory;
private:
template <typename _Array>
void serializeArray(const _Array& items);
template <typename _Array>
void deserializeArray(_Array& items);
template <typename _Array>
void deserializeArrayFast(_Array& items);
template <typename _Element, typename _Allocator>
void deserializeArrayFast(std::vector<_Element, _Allocator>& items);
template <typename _Array>
void deserializeArraySlow(_Array& items);
template <typename _Element, typename _Allocator>
void deserializeArraySlow(std::vector<_Element, _Allocator>& items);
template <typename _Dictionary>
void serializeDictionary(const _Dictionary& items);
template <typename _Dictionary>
void deserializeDictionary(_Dictionary& items);
protected: protected:
Message() = default; Message() = default;
explicit Message(internal::ISdBus* sdbus) noexcept; explicit Message(internal::ISdBus* sdbus) noexcept;
Message(void *msg, internal::ISdBus* sdbus) noexcept; Message(void *msg, internal::ISdBus* sdbus) noexcept;
Message(void *msg, internal::ISdBus* sdbus, adopt_message_t) noexcept; Message(void *msg, internal::ISdBus* sdbus, adopt_message_t) noexcept;
Message(const Message&) noexcept;
Message& operator=(const Message&) noexcept;
Message(Message&& other) noexcept;
Message& operator=(Message&& other) noexcept;
~Message();
friend Factory; friend Factory;
protected: protected:
@ -176,9 +266,7 @@ namespace sdbus {
MethodCall() = default; MethodCall() = default;
MethodReply send(uint64_t timeout) const; MethodReply send(uint64_t timeout) const;
[[deprecated("Use send overload with floating_slot instead")]] void send(void* callback, void* userData, uint64_t timeout, dont_request_slot_t) const; [[nodiscard]] Slot send(void* callback, void* userData, uint64_t timeout, return_slot_t) const;
void send(void* callback, void* userData, uint64_t timeout, floating_slot_t) const;
[[nodiscard]] Slot send(void* callback, void* userData, uint64_t timeout) const;
MethodReply createReply() const; MethodReply createReply() const;
MethodReply createErrorReply(const sdbus::Error& error) const; MethodReply createErrorReply(const sdbus::Error& error) const;
@ -187,12 +275,11 @@ namespace sdbus {
bool doesntExpectReply() const; bool doesntExpectReply() const;
protected: protected:
MethodCall(void *msg, internal::ISdBus* sdbus, const internal::IConnection* connection, adopt_message_t) noexcept; MethodCall(void *msg, internal::ISdBus* sdbus, adopt_message_t) noexcept;
private: private:
MethodReply sendWithReply(uint64_t timeout = 0) const; MethodReply sendWithReply(uint64_t timeout = 0) const;
MethodReply sendWithNoReply() const; MethodReply sendWithNoReply() const;
const internal::IConnection* connection_{};
}; };
class MethodReply : public Message class MethodReply : public Message
@ -213,6 +300,7 @@ namespace sdbus {
public: public:
Signal() = default; Signal() = default;
void setDestination(const std::string& destination); void setDestination(const std::string& destination);
void setDestination(const char* destination);
void send() const; void send() const;
}; };
@ -244,38 +332,107 @@ namespace sdbus {
PlainMessage() = default; PlainMessage() = default;
}; };
template <typename _Element> template <typename ...Elements>
inline Message& operator<<(Message& msg, const std::vector<_Element>& items) inline Message& Message::operator<<(const std::variant<Elements...>& value)
{ {
msg.openContainer(signature_of<_Element>::str()); std::visit([this](const auto& inner)
{
openVariant<decltype(inner)>();
*this << inner;
closeVariant();
}, value);
for (const auto& item : items) return *this;
msg << item;
msg.closeContainer();
return msg;
} }
template <typename _Key, typename _Value> template <typename _Element, typename _Allocator>
inline Message& operator<<(Message& msg, const std::map<_Key, _Value>& items) inline Message& Message::operator<<(const std::vector<_Element, _Allocator>& items)
{ {
const std::string dictEntrySignature = signature_of<_Key>::str() + signature_of<_Value>::str(); serializeArray(items);
const std::string arraySignature = "{" + dictEntrySignature + "}";
msg.openContainer(arraySignature); return *this;
}
template <typename _Element, std::size_t _Size>
inline Message& Message::operator<<(const std::array<_Element, _Size>& items)
{
serializeArray(items);
return *this;
}
#ifdef __cpp_lib_span
template <typename _Element, std::size_t _Extent>
inline Message& Message::operator<<(const std::span<_Element, _Extent>& items)
{
serializeArray(items);
return *this;
}
#endif
template <typename _Enum, typename>
inline Message& Message::operator<<(const _Enum &item)
{
return operator<<(static_cast<std::underlying_type_t<_Enum>>(item));
}
template <typename _Array>
inline void Message::serializeArray(const _Array& items)
{
using ElementType = typename _Array::value_type;
// Use faster, one-step serialization of contiguous array of elements of trivial D-Bus types except bool,
// otherwise use step-by-step serialization of individual elements.
if constexpr (signature_of<ElementType>::is_trivial_dbus_type && !std::is_same_v<ElementType, bool>)
{
constexpr auto signature = as_null_terminated(signature_of_v<ElementType>);
appendArray(*signature.data(), items.data(), items.size() * sizeof(ElementType));
}
else
{
openContainer<ElementType>();
for (const auto& item : items)
*this << item;
closeContainer();
}
}
template <typename _Key, typename _Value, typename _Compare, typename _Allocator>
inline Message& Message::operator<<(const std::map<_Key, _Value, _Compare, _Allocator>& items)
{
serializeDictionary(items);
return *this;
}
template <typename _Key, typename _Value, typename _Hash, typename _KeyEqual, typename _Allocator>
inline Message& Message::operator<<(const std::unordered_map<_Key, _Value, _Hash, _KeyEqual, _Allocator>& items)
{
serializeDictionary(items);
return *this;
}
template <typename _Dictionary>
inline void Message::serializeDictionary(const _Dictionary& items)
{
using KeyType = typename _Dictionary::key_type;
using MappedType = typename _Dictionary::mapped_type;
openContainer<DictEntry<KeyType, MappedType>>();
for (const auto& item : items) for (const auto& item : items)
{ {
msg.openDictEntry(dictEntrySignature); openDictEntry<KeyType, MappedType>();
msg << item.first; *this << item.first;
msg << item.second; *this << item.second;
msg.closeDictEntry(); closeDictEntry();
} }
msg.closeContainer(); closeContainer();
return msg;
} }
namespace detail namespace detail
@ -296,78 +453,212 @@ namespace sdbus {
} }
template <typename... _ValueTypes> template <typename... _ValueTypes>
inline Message& operator<<(Message& msg, const Struct<_ValueTypes...>& item) inline Message& Message::operator<<(const Struct<_ValueTypes...>& item)
{ {
auto structSignature = signature_of<Struct<_ValueTypes...>>::str(); openStruct<_ValueTypes...>();
assert(structSignature.size() > 2); detail::serialize_tuple(*this, item, std::index_sequence_for<_ValueTypes...>{});
// Remove opening and closing parenthesis from the struct signature to get contents signature closeStruct();
auto structContentSignature = structSignature.substr(1, structSignature.size()-2);
msg.openStruct(structContentSignature); return *this;
detail::serialize_tuple(msg, item, std::index_sequence_for<_ValueTypes...>{});
msg.closeStruct();
return msg;
} }
template <typename... _ValueTypes> template <typename... _ValueTypes>
inline Message& operator<<(Message& msg, const std::tuple<_ValueTypes...>& item) inline Message& Message::operator<<(const std::tuple<_ValueTypes...>& item)
{ {
detail::serialize_tuple(msg, item, std::index_sequence_for<_ValueTypes...>{}); detail::serialize_tuple(*this, item, std::index_sequence_for<_ValueTypes...>{});
return msg; return *this;
} }
namespace detail
template <typename _Element>
inline Message& operator>>(Message& msg, std::vector<_Element>& items)
{ {
if(!msg.enterContainer(signature_of<_Element>::str())) template <typename _Element, typename... _Elements>
return msg; bool deserialize_variant(Message& msg, std::variant<_Elements...>& value, const char* signature)
{
constexpr auto elemSignature = as_null_terminated(sdbus::signature_of_v<_Element>);
if (std::strcmp(signature, elemSignature.data()) != 0)
return false;
_Element temp;
msg.enterVariant(signature);
msg >> temp;
msg.exitVariant();
value = std::move(temp);
return true;
}
}
template <typename... Elements>
inline Message& Message::operator>>(std::variant<Elements...>& value)
{
auto [type, contents] = peekType();
bool result = (detail::deserialize_variant<Elements>(*this, value, contents) || ...);
SDBUS_THROW_ERROR_IF(!result, "Failed to deserialize variant: signature did not match any of the variant types", EINVAL);
return *this;
}
template <typename _Element, typename _Allocator>
inline Message& Message::operator>>(std::vector<_Element, _Allocator>& items)
{
deserializeArray(items);
return *this;
}
template <typename _Element, std::size_t _Size>
inline Message& Message::operator>>(std::array<_Element, _Size>& items)
{
deserializeArray(items);
return *this;
}
#ifdef __cpp_lib_span
template <typename _Element, std::size_t _Extent>
inline Message& Message::operator>>(std::span<_Element, _Extent>& items)
{
deserializeArray(items);
return *this;
}
#endif
template <typename _Enum, typename>
inline Message& Message::operator>>(_Enum& item)
{
std::underlying_type_t<_Enum> val;
*this >> val;
item = static_cast<_Enum>(val);
return *this;
}
template <typename _Array>
inline void Message::deserializeArray(_Array& items)
{
using ElementType = typename _Array::value_type;
// Use faster, one-step deserialization of contiguous array of elements of trivial D-Bus types except bool,
// otherwise use step-by-step deserialization of individual elements.
if constexpr (signature_of<ElementType>::is_trivial_dbus_type && !std::is_same_v<ElementType, bool>)
{
deserializeArrayFast(items);
}
else
{
deserializeArraySlow(items);
}
}
template <typename _Array>
inline void Message::deserializeArrayFast(_Array& items)
{
using ElementType = typename _Array::value_type;
size_t arraySize{};
const ElementType* arrayPtr{};
constexpr auto signature = as_null_terminated(sdbus::signature_of_v<ElementType>);
readArray(*signature.data(), (const void**)&arrayPtr, &arraySize);
size_t elementsInMsg = arraySize / sizeof(ElementType);
bool notEnoughSpace = items.size() < elementsInMsg;
SDBUS_THROW_ERROR_IF(notEnoughSpace, "Failed to deserialize array: not enough space in destination sequence", EINVAL);
std::copy_n(arrayPtr, elementsInMsg, items.begin());
}
template <typename _Element, typename _Allocator>
void Message::deserializeArrayFast(std::vector<_Element, _Allocator>& items)
{
size_t arraySize{};
const _Element* arrayPtr{};
constexpr auto signature = as_null_terminated(sdbus::signature_of_v<_Element>);
readArray(*signature.data(), (const void**)&arrayPtr, &arraySize);
items.insert(items.end(), arrayPtr, arrayPtr + (arraySize / sizeof(_Element)));
}
template <typename _Array>
inline void Message::deserializeArraySlow(_Array& items)
{
using ElementType = typename _Array::value_type;
if(!enterContainer<ElementType>())
return;
for (auto& elem : items)
if (!(*this >> elem))
break; // Keep the rest in the destination sequence untouched
SDBUS_THROW_ERROR_IF(!isAtEnd(false), "Failed to deserialize array: not enough space in destination sequence", EINVAL);
clearFlags();
exitContainer();
}
template <typename _Element, typename _Allocator>
void Message::deserializeArraySlow(std::vector<_Element, _Allocator>& items)
{
if(!enterContainer<_Element>())
return;
while (true) while (true)
{ {
_Element elem; _Element elem;
if (msg >> elem) if (*this >> elem)
items.emplace_back(std::move(elem)); items.emplace_back(std::move(elem));
else else
break; break;
} }
msg.clearFlags(); clearFlags();
msg.exitContainer(); exitContainer();
return msg;
} }
template <typename _Key, typename _Value> template <typename _Key, typename _Value, typename _Compare, typename _Allocator>
inline Message& operator>>(Message& msg, std::map<_Key, _Value>& items) inline Message& Message::operator>>(std::map<_Key, _Value, _Compare, _Allocator>& items)
{ {
const std::string dictEntrySignature = signature_of<_Key>::str() + signature_of<_Value>::str(); deserializeDictionary(items);
const std::string arraySignature = "{" + dictEntrySignature + "}";
if (!msg.enterContainer(arraySignature)) return *this;
return msg; }
template <typename _Key, typename _Value, typename _Hash, typename _KeyEqual, typename _Allocator>
inline Message& Message::operator>>(std::unordered_map<_Key, _Value, _Hash, _KeyEqual, _Allocator>& items)
{
deserializeDictionary(items);
return *this;
}
template <typename _Dictionary>
inline void Message::deserializeDictionary(_Dictionary& items)
{
using KeyType = typename _Dictionary::key_type;
using MappedType = typename _Dictionary::mapped_type;
if (!enterContainer<DictEntry<KeyType, MappedType>>())
return;
while (true) while (true)
{ {
if (!msg.enterDictEntry(dictEntrySignature)) if (!enterDictEntry<KeyType, MappedType>())
break; break;
_Key key; KeyType key;
_Value value; MappedType value;
msg >> key >> value; *this >> key >> value;
items.emplace(std::move(key), std::move(value)); items.emplace(std::move(key), std::move(value));
msg.exitDictEntry(); exitDictEntry();
} }
msg.clearFlags(); clearFlags();
msg.exitContainer(); exitContainer();
return msg;
} }
namespace detail namespace detail
@ -388,27 +679,79 @@ namespace sdbus {
} }
template <typename... _ValueTypes> template <typename... _ValueTypes>
inline Message& operator>>(Message& msg, Struct<_ValueTypes...>& item) inline Message& Message::operator>>(Struct<_ValueTypes...>& item)
{ {
auto structSignature = signature_of<Struct<_ValueTypes...>>::str(); if (!enterStruct<_ValueTypes...>())
// Remove opening and closing parenthesis from the struct signature to get contents signature return *this;
auto structContentSignature = structSignature.substr(1, structSignature.size()-2);
if (!msg.enterStruct(structContentSignature)) detail::deserialize_tuple(*this, item, std::index_sequence_for<_ValueTypes...>{});
return msg;
detail::deserialize_tuple(msg, item, std::index_sequence_for<_ValueTypes...>{}); exitStruct();
msg.exitStruct(); return *this;
return msg;
} }
template <typename... _ValueTypes> template <typename... _ValueTypes>
inline Message& operator>>(Message& msg, std::tuple<_ValueTypes...>& item) inline Message& Message::operator>>(std::tuple<_ValueTypes...>& item)
{ {
detail::deserialize_tuple(msg, item, std::index_sequence_for<_ValueTypes...>{}); detail::deserialize_tuple(*this, item, std::index_sequence_for<_ValueTypes...>{});
return msg; return *this;
}
template <typename _ElementType>
inline Message& Message::openContainer()
{
constexpr auto signature = as_null_terminated(signature_of_v<_ElementType>);
return openContainer(signature.data());
}
template <typename _KeyType, typename _ValueType>
inline Message& Message::openDictEntry()
{
constexpr auto signature = as_null_terminated(signature_of_v<std::tuple<_KeyType, _ValueType>>);
return openDictEntry(signature.data());
}
template <typename _ValueType>
inline Message& Message::openVariant()
{
constexpr auto signature = as_null_terminated(signature_of_v<_ValueType>);
return openVariant(signature.data());
}
template <typename... _ValueTypes>
inline Message& Message::openStruct()
{
constexpr auto signature = as_null_terminated(signature_of_v<std::tuple<_ValueTypes...>>);
return openStruct(signature.data());
}
template <typename _ElementType>
inline Message& Message::enterContainer()
{
constexpr auto signature = as_null_terminated(signature_of_v<_ElementType>);
return enterContainer(signature.data());
}
template <typename _KeyType, typename _ValueType>
inline Message& Message::enterDictEntry()
{
constexpr auto signature = as_null_terminated(signature_of_v<std::tuple<_KeyType, _ValueType>>);
return enterDictEntry(signature.data());
}
template <typename _ValueType>
inline Message& Message::enterVariant()
{
constexpr auto signature = as_null_terminated(signature_of_v<_ValueType>);
return enterVariant(signature.data());
}
template <typename... _ValueTypes>
inline Message& Message::enterStruct()
{
constexpr auto signature = as_null_terminated(signature_of_v<std::tuple<_ValueTypes...>>);
return enterStruct(signature.data());
} }
} }

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file MethodResult.h * @file MethodResult.h
* *
@ -28,9 +28,10 @@
#define SDBUS_CXX_METHODRESULT_H_ #define SDBUS_CXX_METHODRESULT_H_
#include <sdbus-c++/Message.h> #include <sdbus-c++/Message.h>
#include <cassert> #include <cassert>
// Forward declaration // Forward declarations
namespace sdbus { namespace sdbus {
class Error; class Error;
} }
@ -76,7 +77,7 @@ namespace sdbus {
{ {
assert(call_.isValid()); assert(call_.isValid());
auto reply = call_.createReply(); auto reply = call_.createReply();
(reply << ... << results); (void)(reply << ... << results);
reply.send(); reply.send();
} }

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file ProxyInterfaces.h * @file ProxyInterfaces.h
* *
@ -83,9 +83,10 @@ namespace sdbus {
* methods. So the _Interfaces template parameter is a list of sdbus-c++-xml2cpp-generated * methods. So the _Interfaces template parameter is a list of sdbus-c++-xml2cpp-generated
* proxy-side interface classes representing interfaces of the corresponding remote D-Bus object. * proxy-side interface classes representing interfaces of the corresponding remote D-Bus object.
* *
* In the final proxy class inherited from ProxyInterfaces, it is necessary to finish proxy * In the final adaptor class inherited from ProxyInterfaces, one needs to make sure:
* registration in class constructor (`finishRegistration();`), and, conversely, unregister * 1. to call `registerProxy();` in the class constructor, and, conversely,
* the proxy in class destructor (`unregister();`). * 2. to call `unregisterProxy();` in the class destructor,
* so that the signals are subscribed to and unsubscribed from at a proper time.
* *
***********************************************/ ***********************************************/
template <typename... _Interfaces> template <typename... _Interfaces>
@ -103,12 +104,27 @@ namespace sdbus {
* This constructor overload creates a proxy that manages its own D-Bus connection(s). * This constructor overload creates a proxy that manages its own D-Bus connection(s).
* For more information on its behavior, consult @ref createProxy(std::string,std::string) * For more information on its behavior, consult @ref createProxy(std::string,std::string)
*/ */
ProxyInterfaces(std::string destination, std::string objectPath) ProxyInterfaces(ServiceName destination, ObjectPath objectPath)
: ProxyObjectHolder(createProxy(std::move(destination), std::move(objectPath))) : ProxyObjectHolder(createProxy(std::move(destination), std::move(objectPath)))
, _Interfaces(getProxy())... , _Interfaces(getProxy())...
{ {
} }
/*!
* @brief Creates native-like proxy object instance
*
* @param[in] destination Bus name that provides a D-Bus object
* @param[in] objectPath Path of the D-Bus object
*
* This constructor overload creates a proxy that manages its own D-Bus connection(s).
* For more information on its behavior, consult @ref createProxy(std::string,std::string,sdbus::dont_run_event_loop_thread_t)
*/
ProxyInterfaces(ServiceName destination, ObjectPath objectPath, dont_run_event_loop_thread_t)
: ProxyObjectHolder(createProxy(std::move(destination), std::move(objectPath), dont_run_event_loop_thread))
, _Interfaces(getProxy())...
{
}
/*! /*!
* @brief Creates native-like proxy object instance * @brief Creates native-like proxy object instance
* *
@ -119,7 +135,7 @@ namespace sdbus {
* The proxy created this way just references a D-Bus connection owned and managed by the user. * The proxy created this way just references a D-Bus connection owned and managed by the user.
* For more information on its behavior, consult @ref createProxy(IConnection&,std::string,std::string) * For more information on its behavior, consult @ref createProxy(IConnection&,std::string,std::string)
*/ */
ProxyInterfaces(IConnection& connection, std::string destination, std::string objectPath) ProxyInterfaces(IConnection& connection, ServiceName destination, ObjectPath objectPath)
: ProxyObjectHolder(createProxy(connection, std::move(destination), std::move(objectPath))) : ProxyObjectHolder(createProxy(connection, std::move(destination), std::move(objectPath)))
, _Interfaces(getProxy())... , _Interfaces(getProxy())...
{ {
@ -135,22 +151,38 @@ namespace sdbus {
* The proxy created this way becomes an owner of the connection. * The proxy created this way becomes an owner of the connection.
* For more information on its behavior, consult @ref createProxy(std::unique_ptr<sdbus::IConnection>&&,std::string,std::string) * For more information on its behavior, consult @ref createProxy(std::unique_ptr<sdbus::IConnection>&&,std::string,std::string)
*/ */
ProxyInterfaces(std::unique_ptr<sdbus::IConnection>&& connection, std::string destination, std::string objectPath) ProxyInterfaces(std::unique_ptr<sdbus::IConnection>&& connection, ServiceName destination, ObjectPath objectPath)
: ProxyObjectHolder(createProxy(std::move(connection), std::move(destination), std::move(objectPath))) : ProxyObjectHolder(createProxy(std::move(connection), std::move(destination), std::move(objectPath)))
, _Interfaces(getProxy())... , _Interfaces(getProxy())...
{ {
} }
/*! /*!
* @brief Finishes proxy registration and makes the proxy ready for use * @brief Creates native-like proxy object instance
*
* @param[in] connection D-Bus connection to be used by the proxy object
* @param[in] destination Bus name that provides a D-Bus object
* @param[in] objectPath Path of the D-Bus object
*
* The proxy created this way becomes an owner of the connection.
* For more information on its behavior, consult @ref createProxy(std::unique_ptr<sdbus::IConnection>&&,std::string,std::string,sdbus::dont_run_event_loop_thread_t)
*/
ProxyInterfaces(std::unique_ptr<sdbus::IConnection>&& connection, ServiceName destination, ObjectPath objectPath, dont_run_event_loop_thread_t)
: ProxyObjectHolder(createProxy(std::move(connection), std::move(destination), std::move(objectPath), dont_run_event_loop_thread))
, _Interfaces(getProxy())...
{
}
/*!
* @brief Registers handlers for D-Bus signals of the remote object
* *
* This function must be called in the constructor of the final proxy class that implements ProxyInterfaces. * This function must be called in the constructor of the final proxy class that implements ProxyInterfaces.
* *
* For more information, see underlying @ref IProxy::finishRegistration() * See also @ref IProxy::registerSignalHandler()
*/ */
void registerProxy() void registerProxy()
{ {
getProxy().finishRegistration(); (_Interfaces::registerProxy(), ...);
} }
/*! /*!
@ -166,15 +198,17 @@ namespace sdbus {
} }
/*! /*!
* @brief Returns object path of the underlying DBus object * @brief Returns reference to the underlying IProxy instance
*/ */
const std::string& getObjectPath() const using ProxyObjectHolder::getProxy;
{
return getProxy().getObjectPath();
}
protected: protected:
using base_type = ProxyInterfaces; using base_type = ProxyInterfaces;
ProxyInterfaces(const ProxyInterfaces&) = delete;
ProxyInterfaces& operator=(const ProxyInterfaces&) = delete;
ProxyInterfaces(ProxyInterfaces&&) = delete;
ProxyInterfaces& operator=(ProxyInterfaces&&) = delete;
~ProxyInterfaces() = default; ~ProxyInterfaces() = default;
}; };

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file StandardInterfaces.h * @file StandardInterfaces.h
* *
@ -31,6 +31,7 @@
#include <sdbus-c++/IProxy.h> #include <sdbus-c++/IProxy.h>
#include <sdbus-c++/Types.h> #include <sdbus-c++/Types.h>
#include <string> #include <string>
#include <string_view>
#include <map> #include <map>
#include <vector> #include <vector>
@ -39,151 +40,312 @@ namespace sdbus {
// Proxy for peer // Proxy for peer
class Peer_proxy class Peer_proxy
{ {
static constexpr const char* INTERFACE_NAME = "org.freedesktop.DBus.Peer"; static inline const char* INTERFACE_NAME = "org.freedesktop.DBus.Peer";
protected: protected:
Peer_proxy(sdbus::IProxy& proxy) Peer_proxy(sdbus::IProxy& proxy)
: proxy_(proxy) : m_proxy(proxy)
{ {
} }
Peer_proxy(const Peer_proxy&) = delete;
Peer_proxy& operator=(const Peer_proxy&) = delete;
Peer_proxy(Peer_proxy&&) = delete;
Peer_proxy& operator=(Peer_proxy&&) = delete;
~Peer_proxy() = default; ~Peer_proxy() = default;
void registerProxy()
{
}
public: public:
void Ping() void Ping()
{ {
proxy_.callMethod("Ping").onInterface(INTERFACE_NAME); m_proxy.callMethod("Ping").onInterface(INTERFACE_NAME);
} }
std::string GetMachineId() std::string GetMachineId()
{ {
std::string machineUUID; std::string machineUUID;
proxy_.callMethod("GetMachineId").onInterface(INTERFACE_NAME).storeResultsTo(machineUUID); m_proxy.callMethod("GetMachineId").onInterface(INTERFACE_NAME).storeResultsTo(machineUUID);
return machineUUID; return machineUUID;
} }
private: private:
sdbus::IProxy& proxy_; sdbus::IProxy& m_proxy;
}; };
// Proxy for introspection // Proxy for introspection
class Introspectable_proxy class Introspectable_proxy
{ {
static constexpr const char* INTERFACE_NAME = "org.freedesktop.DBus.Introspectable"; static inline const char* INTERFACE_NAME = "org.freedesktop.DBus.Introspectable";
protected: protected:
Introspectable_proxy(sdbus::IProxy& proxy) Introspectable_proxy(sdbus::IProxy& proxy)
: proxy_(proxy) : m_proxy(proxy)
{ {
} }
Introspectable_proxy(const Introspectable_proxy&) = delete;
Introspectable_proxy& operator=(const Introspectable_proxy&) = delete;
Introspectable_proxy(Introspectable_proxy&&) = delete;
Introspectable_proxy& operator=(Introspectable_proxy&&) = delete;
~Introspectable_proxy() = default; ~Introspectable_proxy() = default;
void registerProxy()
{
}
public: public:
std::string Introspect() std::string Introspect()
{ {
std::string xml; std::string xml;
proxy_.callMethod("Introspect").onInterface(INTERFACE_NAME).storeResultsTo(xml); m_proxy.callMethod("Introspect").onInterface(INTERFACE_NAME).storeResultsTo(xml);
return xml; return xml;
} }
private: private:
sdbus::IProxy& proxy_; sdbus::IProxy& m_proxy;
}; };
// Proxy for properties // Proxy for properties
class Properties_proxy class Properties_proxy
{ {
static constexpr const char* INTERFACE_NAME = "org.freedesktop.DBus.Properties"; static inline const char* INTERFACE_NAME = "org.freedesktop.DBus.Properties";
protected: protected:
Properties_proxy(sdbus::IProxy& proxy) Properties_proxy(sdbus::IProxy& proxy)
: proxy_(proxy) : m_proxy(proxy)
{ {
proxy_ }
Properties_proxy(const Properties_proxy&) = delete;
Properties_proxy& operator=(const Properties_proxy&) = delete;
Properties_proxy(Properties_proxy&&) = delete;
Properties_proxy& operator=(Properties_proxy&&) = delete;
~Properties_proxy() = default;
void registerProxy()
{
m_proxy
.uponSignal("PropertiesChanged") .uponSignal("PropertiesChanged")
.onInterface(INTERFACE_NAME) .onInterface(INTERFACE_NAME)
.call([this]( const std::string& interfaceName .call([this]( const InterfaceName& interfaceName
, const std::map<std::string, sdbus::Variant>& changedProperties , const std::map<PropertyName, sdbus::Variant>& changedProperties
, const std::vector<std::string>& invalidatedProperties ) , const std::vector<PropertyName>& invalidatedProperties )
{ {
this->onPropertiesChanged(interfaceName, changedProperties, invalidatedProperties); this->onPropertiesChanged(interfaceName, changedProperties, invalidatedProperties);
}); });
} }
~Properties_proxy() = default; virtual void onPropertiesChanged( const InterfaceName& interfaceName
, const std::map<PropertyName, sdbus::Variant>& changedProperties
virtual void onPropertiesChanged( const std::string& interfaceName , const std::vector<PropertyName>& invalidatedProperties ) = 0;
, const std::map<std::string, sdbus::Variant>& changedProperties
, const std::vector<std::string>& invalidatedProperties ) = 0;
public: public:
sdbus::Variant Get(const std::string& interfaceName, const std::string& propertyName) sdbus::Variant Get(const InterfaceName& interfaceName, const PropertyName& propertyName)
{ {
return proxy_.getProperty(propertyName).onInterface(interfaceName); return m_proxy.getProperty(propertyName).onInterface(interfaceName);
} }
void Set(const std::string& interfaceName, const std::string& propertyName, const sdbus::Variant& value) sdbus::Variant Get(std::string_view interfaceName, std::string_view propertyName)
{ {
proxy_.setProperty(propertyName).onInterface(interfaceName).toValue(value); return m_proxy.getProperty(propertyName).onInterface(interfaceName);
} }
std::map<std::string, sdbus::Variant> GetAll(const std::string& interfaceName) template <typename _Function>
PendingAsyncCall GetAsync(const InterfaceName& interfaceName, const PropertyName& propertyName, _Function&& callback)
{ {
std::map<std::string, sdbus::Variant> props; return m_proxy.getPropertyAsync(propertyName).onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback));
proxy_.callMethod("GetAll").onInterface(INTERFACE_NAME).withArguments(interfaceName).storeResultsTo(props); }
return props;
template <typename _Function>
[[nodiscard]] Slot GetAsync(const InterfaceName& interfaceName, const PropertyName& propertyName, _Function&& callback, return_slot_t)
{
return m_proxy.getPropertyAsync(propertyName).onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback), return_slot);
}
template <typename _Function>
PendingAsyncCall GetAsync(std::string_view interfaceName, std::string_view propertyName, _Function&& callback)
{
return m_proxy.getPropertyAsync(propertyName).onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback));
}
template <typename _Function>
[[nodiscard]] Slot GetAsync(std::string_view interfaceName, std::string_view propertyName, _Function&& callback, return_slot_t)
{
return m_proxy.getPropertyAsync(propertyName).onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback), return_slot);
}
std::future<sdbus::Variant> GetAsync(const InterfaceName& interfaceName, const PropertyName& propertyName, with_future_t)
{
return m_proxy.getPropertyAsync(propertyName).onInterface(interfaceName).getResultAsFuture();
}
std::future<sdbus::Variant> GetAsync(std::string_view interfaceName, std::string_view propertyName, with_future_t)
{
return m_proxy.getPropertyAsync(propertyName).onInterface(interfaceName).getResultAsFuture();
}
void Set(const InterfaceName& interfaceName, const PropertyName& propertyName, const sdbus::Variant& value)
{
m_proxy.setProperty(propertyName).onInterface(interfaceName).toValue(value);
}
void Set(std::string_view interfaceName, const std::string_view propertyName, const sdbus::Variant& value)
{
m_proxy.setProperty(propertyName).onInterface(interfaceName).toValue(value);
}
void Set(const InterfaceName& interfaceName, const PropertyName& propertyName, const sdbus::Variant& value, dont_expect_reply_t)
{
m_proxy.setProperty(propertyName).onInterface(interfaceName).toValue(value, dont_expect_reply);
}
void Set(std::string_view interfaceName, const std::string_view propertyName, const sdbus::Variant& value, dont_expect_reply_t)
{
m_proxy.setProperty(propertyName).onInterface(interfaceName).toValue(value, dont_expect_reply);
}
template <typename _Function>
PendingAsyncCall SetAsync(const InterfaceName& interfaceName, const PropertyName& propertyName, const sdbus::Variant& value, _Function&& callback)
{
return m_proxy.setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).uponReplyInvoke(std::forward<_Function>(callback));
}
template <typename _Function>
PendingAsyncCall SetAsync(const InterfaceName& interfaceName, const PropertyName& propertyName, const sdbus::Variant& value, _Function&& callback, return_slot_t)
{
return m_proxy.setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).uponReplyInvoke(std::forward<_Function>(callback), return_slot);
}
template <typename _Function>
PendingAsyncCall SetAsync(std::string_view interfaceName, std::string_view propertyName, const sdbus::Variant& value, _Function&& callback)
{
return m_proxy.setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).uponReplyInvoke(std::forward<_Function>(callback));
}
template <typename _Function>
PendingAsyncCall SetAsync(std::string_view interfaceName, std::string_view propertyName, const sdbus::Variant& value, _Function&& callback, return_slot_t)
{
return m_proxy.setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).uponReplyInvoke(std::forward<_Function>(callback), return_slot);
}
std::future<void> SetAsync(const InterfaceName& interfaceName, const PropertyName& propertyName, const sdbus::Variant& value, with_future_t)
{
return m_proxy.setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).getResultAsFuture();
}
std::future<void> SetAsync(std::string_view interfaceName, std::string_view propertyName, const sdbus::Variant& value, with_future_t)
{
return m_proxy.setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).getResultAsFuture();
}
std::map<PropertyName, sdbus::Variant> GetAll(const InterfaceName& interfaceName)
{
return m_proxy.getAllProperties().onInterface(interfaceName);
}
std::map<PropertyName, sdbus::Variant> GetAll(std::string_view interfaceName)
{
return m_proxy.getAllProperties().onInterface(interfaceName);
}
template <typename _Function>
PendingAsyncCall GetAllAsync(const InterfaceName& interfaceName, _Function&& callback)
{
return m_proxy.getAllPropertiesAsync().onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback));
}
template <typename _Function>
PendingAsyncCall GetAllAsync(const InterfaceName& interfaceName, _Function&& callback, return_slot_t)
{
return m_proxy.getAllPropertiesAsync().onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback), return_slot);
}
template <typename _Function>
PendingAsyncCall GetAllAsync(std::string_view interfaceName, _Function&& callback)
{
return m_proxy.getAllPropertiesAsync().onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback));
}
template <typename _Function>
PendingAsyncCall GetAllAsync(std::string_view interfaceName, _Function&& callback, return_slot_t)
{
return m_proxy.getAllPropertiesAsync().onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback), return_slot);
}
std::future<std::map<PropertyName, sdbus::Variant>> GetAllAsync(const InterfaceName& interfaceName, with_future_t)
{
return m_proxy.getAllPropertiesAsync().onInterface(interfaceName).getResultAsFuture();
}
std::future<std::map<PropertyName, sdbus::Variant>> GetAllAsync(std::string_view interfaceName, with_future_t)
{
return m_proxy.getAllPropertiesAsync().onInterface(interfaceName).getResultAsFuture();
} }
private: private:
sdbus::IProxy& proxy_; sdbus::IProxy& m_proxy;
}; };
// Proxy for object manager // Proxy for object manager
class ObjectManager_proxy class ObjectManager_proxy
{ {
static constexpr const char* INTERFACE_NAME = "org.freedesktop.DBus.ObjectManager"; static inline const char* INTERFACE_NAME = "org.freedesktop.DBus.ObjectManager";
protected: protected:
ObjectManager_proxy(sdbus::IProxy& proxy) ObjectManager_proxy(sdbus::IProxy& proxy)
: proxy_(proxy) : m_proxy(proxy)
{ {
proxy_ }
ObjectManager_proxy(const ObjectManager_proxy&) = delete;
ObjectManager_proxy& operator=(const ObjectManager_proxy&) = delete;
ObjectManager_proxy(ObjectManager_proxy&&) = delete;
ObjectManager_proxy& operator=(ObjectManager_proxy&&) = delete;
~ObjectManager_proxy() = default;
void registerProxy()
{
m_proxy
.uponSignal("InterfacesAdded") .uponSignal("InterfacesAdded")
.onInterface(INTERFACE_NAME) .onInterface(INTERFACE_NAME)
.call([this]( const sdbus::ObjectPath& objectPath .call([this]( const sdbus::ObjectPath& objectPath
, const std::map<std::string, std::map<std::string, sdbus::Variant>>& interfacesAndProperties ) , const std::map<sdbus::InterfaceName, std::map<PropertyName, sdbus::Variant>>& interfacesAndProperties )
{ {
this->onInterfacesAdded(objectPath, interfacesAndProperties); this->onInterfacesAdded(objectPath, interfacesAndProperties);
}); });
proxy_ m_proxy
.uponSignal("InterfacesRemoved") .uponSignal("InterfacesRemoved")
.onInterface(INTERFACE_NAME) .onInterface(INTERFACE_NAME)
.call([this]( const sdbus::ObjectPath& objectPath .call([this]( const sdbus::ObjectPath& objectPath
, const std::vector<std::string>& interfaces ) , const std::vector<sdbus::InterfaceName>& interfaces )
{ {
this->onInterfacesRemoved(objectPath, interfaces); this->onInterfacesRemoved(objectPath, interfaces);
}); });
} }
~ObjectManager_proxy() = default;
virtual void onInterfacesAdded( const sdbus::ObjectPath& objectPath virtual void onInterfacesAdded( const sdbus::ObjectPath& objectPath
, const std::map<std::string, std::map<std::string, sdbus::Variant>>& interfacesAndProperties) = 0; , const std::map<sdbus::InterfaceName, std::map<PropertyName, sdbus::Variant>>& interfacesAndProperties) = 0;
virtual void onInterfacesRemoved( const sdbus::ObjectPath& objectPath virtual void onInterfacesRemoved( const sdbus::ObjectPath& objectPath
, const std::vector<std::string>& interfaces) = 0; , const std::vector<sdbus::InterfaceName>& interfaces) = 0;
public: public:
std::map<sdbus::ObjectPath, std::map<std::string, std::map<std::string, sdbus::Variant>>> GetManagedObjects() std::map<sdbus::ObjectPath, std::map<sdbus::InterfaceName, std::map<PropertyName, sdbus::Variant>>> GetManagedObjects()
{ {
std::map<sdbus::ObjectPath, std::map<std::string, std::map<std::string, sdbus::Variant>>> objectsInterfacesAndProperties; std::map<sdbus::ObjectPath, std::map<sdbus::InterfaceName, std::map<PropertyName, sdbus::Variant>>> objectsInterfacesAndProperties;
proxy_.callMethod("GetManagedObjects").onInterface(INTERFACE_NAME).storeResultsTo(objectsInterfacesAndProperties); m_proxy.callMethod("GetManagedObjects").onInterface(INTERFACE_NAME).storeResultsTo(objectsInterfacesAndProperties);
return objectsInterfacesAndProperties; return objectsInterfacesAndProperties;
} }
private: private:
sdbus::IProxy& proxy_; sdbus::IProxy& m_proxy;
}; };
// Adaptors for the above-listed standard D-Bus interfaces are not necessary because the functionality // Adaptors for the above-listed standard D-Bus interfaces are not necessary because the functionality
@ -193,29 +355,47 @@ namespace sdbus {
// Adaptor for properties // Adaptor for properties
class Properties_adaptor class Properties_adaptor
{ {
static constexpr const char* INTERFACE_NAME = "org.freedesktop.DBus.Properties"; static inline const char* INTERFACE_NAME = "org.freedesktop.DBus.Properties";
protected: protected:
Properties_adaptor(sdbus::IObject& object) Properties_adaptor(sdbus::IObject& object) : m_object(object)
: object_(object)
{ {
} }
Properties_adaptor(const Properties_adaptor&) = delete;
Properties_adaptor& operator=(const Properties_adaptor&) = delete;
Properties_adaptor(Properties_adaptor&&) = delete;
Properties_adaptor& operator=(Properties_adaptor&&) = delete;
~Properties_adaptor() = default; ~Properties_adaptor() = default;
public: void registerAdaptor()
void emitPropertiesChangedSignal(const std::string& interfaceName, const std::vector<std::string>& properties)
{ {
object_.emitPropertiesChangedSignal(interfaceName, properties);
} }
void emitPropertiesChangedSignal(const std::string& interfaceName) public:
void emitPropertiesChangedSignal(const InterfaceName& interfaceName, const std::vector<PropertyName>& properties)
{ {
object_.emitPropertiesChangedSignal(interfaceName); m_object.emitPropertiesChangedSignal(interfaceName, properties);
}
void emitPropertiesChangedSignal(const char* interfaceName, const std::vector<PropertyName>& properties)
{
m_object.emitPropertiesChangedSignal(interfaceName, properties);
}
void emitPropertiesChangedSignal(const InterfaceName& interfaceName)
{
m_object.emitPropertiesChangedSignal(interfaceName);
}
void emitPropertiesChangedSignal(const char* interfaceName)
{
m_object.emitPropertiesChangedSignal(interfaceName);
} }
private: private:
sdbus::IObject& object_; sdbus::IObject& m_object;
}; };
/*! /*!
@ -230,19 +410,27 @@ namespace sdbus {
*/ */
class ObjectManager_adaptor class ObjectManager_adaptor
{ {
static constexpr const char* INTERFACE_NAME = "org.freedesktop.DBus.ObjectManager"; static inline const char* INTERFACE_NAME = "org.freedesktop.DBus.ObjectManager";
protected: protected:
explicit ObjectManager_adaptor(sdbus::IObject& object) explicit ObjectManager_adaptor(sdbus::IObject& object) : m_object(object)
: object_(object)
{ {
object_.addObjectManager();
} }
ObjectManager_adaptor(const ObjectManager_adaptor&) = delete;
ObjectManager_adaptor& operator=(const ObjectManager_adaptor&) = delete;
ObjectManager_adaptor(ObjectManager_adaptor&&) = delete;
ObjectManager_adaptor& operator=(ObjectManager_adaptor&&) = delete;
~ObjectManager_adaptor() = default; ~ObjectManager_adaptor() = default;
void registerAdaptor()
{
m_object.addObjectManager();
}
private: private:
sdbus::IObject& object_; sdbus::IObject& m_object;
}; };
/*! /*!
@ -259,12 +447,22 @@ namespace sdbus {
class ManagedObject_adaptor class ManagedObject_adaptor
{ {
protected: protected:
explicit ManagedObject_adaptor(sdbus::IObject& object) : object_(object) explicit ManagedObject_adaptor(sdbus::IObject& object)
: m_object(object)
{ {
} }
ManagedObject_adaptor(const ManagedObject_adaptor&) = delete;
ManagedObject_adaptor& operator=(const ManagedObject_adaptor&) = delete;
ManagedObject_adaptor(ManagedObject_adaptor&&) = delete;
ManagedObject_adaptor& operator=(ManagedObject_adaptor&&) = delete;
~ManagedObject_adaptor() = default; ~ManagedObject_adaptor() = default;
void registerAdaptor()
{
}
public: public:
/*! /*!
* @brief Emits InterfacesAdded signal for this object path * @brief Emits InterfacesAdded signal for this object path
@ -273,7 +471,7 @@ namespace sdbus {
*/ */
void emitInterfacesAddedSignal() void emitInterfacesAddedSignal()
{ {
object_.emitInterfacesAddedSignal(); m_object.emitInterfacesAddedSignal();
} }
/*! /*!
@ -281,9 +479,9 @@ namespace sdbus {
* *
* See IObject::emitInterfacesAddedSignal(). * See IObject::emitInterfacesAddedSignal().
*/ */
void emitInterfacesAddedSignal(const std::vector<std::string>& interfaces) void emitInterfacesAddedSignal(const std::vector<sdbus::InterfaceName>& interfaces)
{ {
object_.emitInterfacesAddedSignal(interfaces); m_object.emitInterfacesAddedSignal(interfaces);
} }
/*! /*!
@ -293,7 +491,7 @@ namespace sdbus {
*/ */
void emitInterfacesRemovedSignal() void emitInterfacesRemovedSignal()
{ {
object_.emitInterfacesRemovedSignal(); m_object.emitInterfacesRemovedSignal();
} }
/*! /*!
@ -301,13 +499,13 @@ namespace sdbus {
* *
* See IObject::emitInterfacesRemovedSignal(). * See IObject::emitInterfacesRemovedSignal().
*/ */
void emitInterfacesRemovedSignal(const std::vector<std::string>& interfaces) void emitInterfacesRemovedSignal(const std::vector<InterfaceName>& interfaces)
{ {
object_.emitInterfacesRemovedSignal(interfaces); m_object.emitInterfacesRemovedSignal(interfaces);
} }
private: private:
sdbus::IObject& object_; sdbus::IObject& m_object;
}; };
} }

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file TypeTraits.h * @file TypeTraits.h
* *
@ -27,14 +27,27 @@
#ifndef SDBUS_CXX_TYPETRAITS_H_ #ifndef SDBUS_CXX_TYPETRAITS_H_
#define SDBUS_CXX_TYPETRAITS_H_ #define SDBUS_CXX_TYPETRAITS_H_
#include <type_traits> #include <sdbus-c++/Error.h>
#include <string>
#include <vector> #include <array>
#include <map>
#include <cstdint> #include <cstdint>
#include <functional> #include <functional>
#include <map>
#include <memory> #include <memory>
#include <optional>
#ifdef __has_include
# if __has_include(<span>)
# include <span>
# endif
#endif
#include <string>
#include <string_view>
#include <tuple> #include <tuple>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include <variant>
#include <vector>
// Forward declarations // Forward declarations
namespace sdbus { namespace sdbus {
@ -43,6 +56,10 @@ namespace sdbus {
class ObjectPath; class ObjectPath;
class Signature; class Signature;
class UnixFd; class UnixFd;
template<typename _T1, typename _T2> using DictEntry = std::pair<_T1, _T2>;
class BusName;
class InterfaceName;
class MemberName;
class MethodCall; class MethodCall;
class MethodReply; class MethodReply;
class Signal; class Signal;
@ -51,314 +68,323 @@ namespace sdbus {
class PropertyGetReply; class PropertyGetReply;
template <typename... _Results> class Result; template <typename... _Results> class Result;
class Error; class Error;
template <typename _T, typename _Enable = void> struct signature_of;
} }
namespace sdbus { namespace sdbus {
// Callbacks from sdbus-c++ // Callbacks from sdbus-c++
using method_callback = std::function<void(MethodCall msg)>; using method_callback = std::function<void(MethodCall msg)>;
using async_reply_handler = std::function<void(MethodReply& reply, const Error* error)>; using async_reply_handler = std::function<void(MethodReply reply, std::optional<Error> error)>;
using signal_handler = std::function<void(Signal& signal)>; using signal_handler = std::function<void(Signal signal)>;
using message_handler = std::function<void(Message& msg)>; using message_handler = std::function<void(Message msg)>;
using property_set_callback = std::function<void(PropertySetCall& msg)>; using property_set_callback = std::function<void(PropertySetCall msg)>;
using property_get_callback = std::function<void(PropertyGetReply& reply)>; using property_get_callback = std::function<void(PropertyGetReply& reply)>;
// Type-erased RAII-style handle to callbacks/subscriptions registered to sdbus-c++ // Type-erased RAII-style handle to callbacks/subscriptions registered to sdbus-c++
using Slot = std::unique_ptr<void, std::function<void(void*)>>; using Slot = std::unique_ptr<void, std::function<void(void*)>>;
// Tag specifying that an owning slot handle shall be returned from the function // Tag specifying that an owning handle (so-called slot) of the logical resource shall be provided to the client
struct request_slot_t { explicit request_slot_t() = default; }; struct return_slot_t { explicit return_slot_t() = default; };
inline constexpr request_slot_t request_slot{}; inline constexpr return_slot_t return_slot{};
// Tag specifying that the library shall own the slot resulting from the call of the function (so-called floating slot) // Tag specifying that the library shall own the slot resulting from the call of the function (so-called floating slot)
struct floating_slot_t { explicit floating_slot_t() = default; }; struct floating_slot_t { explicit floating_slot_t() = default; };
inline constexpr floating_slot_t floating_slot{}; inline constexpr floating_slot_t floating_slot{};
// Deprecated name for the above -- a floating slot
struct dont_request_slot_t { explicit dont_request_slot_t() = default; };
[[deprecated("Replaced by floating_slot")]] inline constexpr dont_request_slot_t dont_request_slot{};
// Tag denoting the assumption that the caller has already obtained message ownership // Tag denoting the assumption that the caller has already obtained message ownership
struct adopt_message_t { explicit adopt_message_t() = default; }; struct adopt_message_t { explicit adopt_message_t() = default; };
inline constexpr adopt_message_t adopt_message{}; inline constexpr adopt_message_t adopt_message{};
// Tag denoting the assumption that the caller has already obtained fd ownership // Tag denoting the assumption that the caller has already obtained fd ownership
struct adopt_fd_t { explicit adopt_fd_t() = default; }; struct adopt_fd_t { explicit adopt_fd_t() = default; };
inline constexpr adopt_fd_t adopt_fd{}; inline constexpr adopt_fd_t adopt_fd{};
// Tag specifying that the proxy shall not run an event loop thread on its D-Bus connection.
// Such proxies are typically created to carry out a simple synchronous D-Bus call(s) and then are destroyed.
struct dont_run_event_loop_thread_t { explicit dont_run_event_loop_thread_t() = default; };
inline constexpr dont_run_event_loop_thread_t dont_run_event_loop_thread{};
// 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{};
// Helper for static assert
template <class... _T> constexpr bool always_false = false;
// Helper operator+ for concatenation of `std::array`s
template <typename _T, std::size_t _N1, std::size_t _N2>
constexpr std::array<_T, _N1 + _N2> operator+(std::array<_T, _N1> lhs, std::array<_T, _N2> rhs);
// Template specializations for getting D-Bus signatures from C++ types // Template specializations for getting D-Bus signatures from C++ types
template <typename _T> template <typename _T>
constexpr auto signature_of_v = signature_of<_T>::value;
template <typename _T, typename _Enable>
struct signature_of struct signature_of
{ {
static constexpr bool is_valid = false; static constexpr bool is_valid = false;
static constexpr bool is_trivial_dbus_type = false;
static const std::string str() static constexpr void* value = []
{ {
// sizeof(_T) < 0 is here to make compiler not being able to figure out // See using-sdbus-c++.md, section "Extending sdbus-c++ type system",
// the assertion expression before the template instantiation takes place. // on how to teach sdbus-c++ about your custom types
static_assert(sizeof(_T) < 0, "Unknown DBus type"); static_assert(always_false<_T>, "Unsupported D-Bus type (specialize `signature_of` for your custom types)");
return ""; };
}
}; };
template <typename _T>
struct signature_of<const _T> : signature_of<_T>
{};
template <typename _T>
struct signature_of<volatile _T> : signature_of<_T>
{};
template <typename _T>
struct signature_of<_T&> : signature_of<_T>
{};
template <> template <>
struct signature_of<void> struct signature_of<void>
{ {
static constexpr std::array<char, 0> value{};
static constexpr bool is_valid = true; static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = false;
static const std::string str()
{
return "";
}
}; };
template <> template <>
struct signature_of<bool> struct signature_of<bool>
{ {
static constexpr std::array value{'b'};
static constexpr bool is_valid = true; static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = true;
static const std::string str()
{
return "b";
}
}; };
template <> template <>
struct signature_of<uint8_t> struct signature_of<uint8_t>
{ {
static constexpr std::array value{'y'};
static constexpr bool is_valid = true; static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = true;
static const std::string str()
{
return "y";
}
}; };
template <> template <>
struct signature_of<int16_t> struct signature_of<int16_t>
{ {
static constexpr std::array value{'n'};
static constexpr bool is_valid = true; static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = true;
static const std::string str()
{
return "n";
}
}; };
template <> template <>
struct signature_of<uint16_t> struct signature_of<uint16_t>
{ {
static constexpr std::array value{'q'};
static constexpr bool is_valid = true; static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = true;
static const std::string str()
{
return "q";
}
}; };
template <> template <>
struct signature_of<int32_t> struct signature_of<int32_t>
{ {
static constexpr std::array value{'i'};
static constexpr bool is_valid = true; static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = true;
static const std::string str()
{
return "i";
}
}; };
template <> template <>
struct signature_of<uint32_t> struct signature_of<uint32_t>
{ {
static constexpr std::array value{'u'};
static constexpr bool is_valid = true; static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = true;
static const std::string str()
{
return "u";
}
}; };
template <> template <>
struct signature_of<int64_t> struct signature_of<int64_t>
{ {
static constexpr std::array value{'x'};
static constexpr bool is_valid = true; static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = true;
static const std::string str()
{
return "x";
}
}; };
template <> template <>
struct signature_of<uint64_t> struct signature_of<uint64_t>
{ {
static constexpr std::array value{'t'};
static constexpr bool is_valid = true; static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = true;
static const std::string str()
{
return "t";
}
}; };
template <> template <>
struct signature_of<double> struct signature_of<double>
{ {
static constexpr std::array value{'d'};
static constexpr bool is_valid = true; static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = true;
static const std::string str()
{
return "d";
}
};
template <>
struct signature_of<char*>
{
static constexpr bool is_valid = true;
static const std::string str()
{
return "s";
}
};
template <>
struct signature_of<const char*>
{
static constexpr bool is_valid = true;
static const std::string str()
{
return "s";
}
};
template <std::size_t _N>
struct signature_of<char[_N]>
{
static constexpr bool is_valid = true;
static const std::string str()
{
return "s";
}
};
template <std::size_t _N>
struct signature_of<const char[_N]>
{
static constexpr bool is_valid = true;
static const std::string str()
{
return "s";
}
}; };
template <> template <>
struct signature_of<std::string> struct signature_of<std::string>
{ {
static constexpr std::array value{'s'};
static constexpr bool is_valid = true; static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = false;
static const std::string str()
{
return "s";
}
}; };
template <>
struct signature_of<std::string_view> : signature_of<std::string>
{};
template <>
struct signature_of<char*> : signature_of<std::string>
{};
template <>
struct signature_of<const char*> : signature_of<std::string>
{};
template <std::size_t _N>
struct signature_of<char[_N]> : signature_of<std::string>
{};
template <std::size_t _N>
struct signature_of<const char[_N]> : signature_of<std::string>
{};
template <>
struct signature_of<BusName> : signature_of<std::string>
{};
template <>
struct signature_of<InterfaceName> : signature_of<std::string>
{};
template <>
struct signature_of<MemberName> : signature_of<std::string>
{};
template <typename... _ValueTypes> template <typename... _ValueTypes>
struct signature_of<Struct<_ValueTypes...>> struct signature_of<Struct<_ValueTypes...>>
{ {
static constexpr std::array contents = (signature_of_v<_ValueTypes> + ...);
static constexpr std::array value = std::array{'('} + contents + std::array{')'};
static constexpr bool is_valid = true; static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = false;
static const std::string str()
{
std::string signature;
signature += "(";
(signature += ... += signature_of<_ValueTypes>::str());
signature += ")";
return signature;
}
}; };
template <> template <>
struct signature_of<Variant> struct signature_of<Variant>
{ {
static constexpr std::array value{'v'};
static constexpr bool is_valid = true; static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = false;
static const std::string str()
{
return "v";
}
}; };
template <typename... Elements>
struct signature_of<std::variant<Elements...>> : signature_of<Variant>
{};
template <> template <>
struct signature_of<ObjectPath> struct signature_of<ObjectPath>
{ {
static constexpr std::array value{'o'};
static constexpr bool is_valid = true; static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = false;
static const std::string str()
{
return "o";
}
}; };
template <> template <>
struct signature_of<Signature> struct signature_of<Signature>
{ {
static constexpr std::array value{'g'};
static constexpr bool is_valid = true; static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = false;
static const std::string str()
{
return "g";
}
}; };
template <> template <>
struct signature_of<UnixFd> struct signature_of<UnixFd>
{ {
static constexpr std::array value{'h'};
static constexpr bool is_valid = true; static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = false;
static const std::string str()
{
return "h";
}
}; };
template <typename _Element> template <typename _T1, typename _T2>
struct signature_of<std::vector<_Element>> struct signature_of<DictEntry<_T1, _T2>>
{ {
static constexpr std::array value = std::array{'{'} + signature_of_v<std::tuple<_T1, _T2>> + std::array{'}'};
static constexpr bool is_valid = true; static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = false;
static const std::string str()
{
return "a" + signature_of<_Element>::str();
}
}; };
template <typename _Key, typename _Value> template <typename _Element, typename _Allocator>
struct signature_of<std::map<_Key, _Value>> struct signature_of<std::vector<_Element, _Allocator>>
{ {
static constexpr std::array value = std::array{'a'} + signature_of_v<_Element>;
static constexpr bool is_valid = true; static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = false;
static const std::string str()
{
return "a{" + signature_of<_Key>::str() + signature_of<_Value>::str() + "}";
}
}; };
template <typename _Element, std::size_t _Size>
struct signature_of<std::array<_Element, _Size>> : signature_of<std::vector<_Element>>
{
};
#ifdef __cpp_lib_span
template <typename _Element, std::size_t _Extent>
struct signature_of<std::span<_Element, _Extent>> : signature_of<std::vector<_Element>>
{
};
#endif
template <typename _Enum>
struct signature_of<_Enum, typename std::enable_if_t<std::is_enum_v<_Enum>>>
: public signature_of<std::underlying_type_t<_Enum>>
{};
template <typename _Key, typename _Value, typename _Compare, typename _Allocator>
struct signature_of<std::map<_Key, _Value, _Compare, _Allocator>>
{
static constexpr std::array contents = signature_of_v<std::tuple<_Key, _Value>>;
static constexpr std::array dict_entry = std::array{'{'} + contents + std::array{'}'};
static constexpr std::array value = std::array{'a'} + dict_entry;
static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = false;
};
template <typename _Key, typename _Value, typename _Hash, typename _KeyEqual, typename _Allocator>
struct signature_of<std::unordered_map<_Key, _Value, _Hash, _KeyEqual, _Allocator>>
: signature_of<std::map<_Key, _Value>>
{
};
template <typename... _Types>
struct signature_of<std::tuple<_Types...>> // A simple concatenation of signatures of _Types
{
static constexpr std::array value = (std::array<char, 0>{} + ... + signature_of_v<_Types>);
static constexpr bool is_valid = false;
static constexpr bool is_trivial_dbus_type = false;
};
// To simplify conversions of arrays to C strings
template <typename _T, std::size_t _N>
constexpr auto as_null_terminated(std::array<_T, _N> arr)
{
return arr + std::array<_T, 1>{0};
}
// Function traits implementation inspired by (c) kennytm, // Function traits implementation inspired by (c) kennytm,
// https://github.com/kennytm/utils/blob/master/traits.hpp // https://github.com/kennytm/utils/blob/master/traits.hpp
template <typename _Type> template <typename _Type>
struct function_traits struct function_traits : function_traits<decltype(&_Type::operator())>
: public function_traits<decltype(&_Type::operator())>
{}; {};
template <typename _Type> template <typename _Type>
struct function_traits<const _Type> struct function_traits<const _Type> : function_traits<_Type>
: public function_traits<_Type>
{}; {};
template <typename _Type> template <typename _Type>
struct function_traits<_Type&> struct function_traits<_Type&> : function_traits<_Type>
: public function_traits<_Type>
{}; {};
template <typename _ReturnType, typename... _Args> template <typename _ReturnType, typename... _Args>
@ -398,72 +424,62 @@ namespace sdbus {
}; };
template <typename _ReturnType, typename... _Args> template <typename _ReturnType, typename... _Args>
struct function_traits<_ReturnType(_Args...)> struct function_traits<_ReturnType(_Args...)> : function_traits_base<_ReturnType, _Args...>
: public function_traits_base<_ReturnType, _Args...>
{ {
static constexpr bool is_async = false; static constexpr bool is_async = false;
static constexpr bool has_error_param = false; static constexpr bool has_error_param = false;
}; };
template <typename... _Args> template <typename... _Args>
struct function_traits<void(const Error*, _Args...)> struct function_traits<void(std::optional<Error>, _Args...)> : function_traits_base<void, _Args...>
: public function_traits_base<void, _Args...>
{ {
static constexpr bool has_error_param = true; static constexpr bool has_error_param = true;
}; };
template <typename... _Args, typename... _Results> template <typename... _Args, typename... _Results>
struct function_traits<void(Result<_Results...>, _Args...)> struct function_traits<void(Result<_Results...>, _Args...)> : function_traits_base<std::tuple<_Results...>, _Args...>
: public function_traits_base<std::tuple<_Results...>, _Args...>
{ {
static constexpr bool is_async = true; static constexpr bool is_async = true;
using async_result_t = Result<_Results...>; using async_result_t = Result<_Results...>;
}; };
template <typename... _Args, typename... _Results> template <typename... _Args, typename... _Results>
struct function_traits<void(Result<_Results...>&&, _Args...)> struct function_traits<void(Result<_Results...>&&, _Args...)> : function_traits_base<std::tuple<_Results...>, _Args...>
: public function_traits_base<std::tuple<_Results...>, _Args...>
{ {
static constexpr bool is_async = true; static constexpr bool is_async = true;
using async_result_t = Result<_Results...>; using async_result_t = Result<_Results...>;
}; };
template <typename _ReturnType, typename... _Args> template <typename _ReturnType, typename... _Args>
struct function_traits<_ReturnType(*)(_Args...)> struct function_traits<_ReturnType(*)(_Args...)> : function_traits<_ReturnType(_Args...)>
: public function_traits<_ReturnType(_Args...)>
{}; {};
template <typename _ClassType, typename _ReturnType, typename... _Args> template <typename _ClassType, typename _ReturnType, typename... _Args>
struct function_traits<_ReturnType(_ClassType::*)(_Args...)> struct function_traits<_ReturnType(_ClassType::*)(_Args...)> : function_traits<_ReturnType(_Args...)>
: public function_traits<_ReturnType(_Args...)>
{ {
typedef _ClassType& owner_type; typedef _ClassType& owner_type;
}; };
template <typename _ClassType, typename _ReturnType, typename... _Args> template <typename _ClassType, typename _ReturnType, typename... _Args>
struct function_traits<_ReturnType(_ClassType::*)(_Args...) const> struct function_traits<_ReturnType(_ClassType::*)(_Args...) const> : function_traits<_ReturnType(_Args...)>
: public function_traits<_ReturnType(_Args...)>
{ {
typedef const _ClassType& owner_type; typedef const _ClassType& owner_type;
}; };
template <typename _ClassType, typename _ReturnType, typename... _Args> template <typename _ClassType, typename _ReturnType, typename... _Args>
struct function_traits<_ReturnType(_ClassType::*)(_Args...) volatile> struct function_traits<_ReturnType(_ClassType::*)(_Args...) volatile> : function_traits<_ReturnType(_Args...)>
: public function_traits<_ReturnType(_Args...)>
{ {
typedef volatile _ClassType& owner_type; typedef volatile _ClassType& owner_type;
}; };
template <typename _ClassType, typename _ReturnType, typename... _Args> template <typename _ClassType, typename _ReturnType, typename... _Args>
struct function_traits<_ReturnType(_ClassType::*)(_Args...) const volatile> struct function_traits<_ReturnType(_ClassType::*)(_Args...) const volatile> : function_traits<_ReturnType(_Args...)>
: public function_traits<_ReturnType(_Args...)>
{ {
typedef const volatile _ClassType& owner_type; typedef const volatile _ClassType& owner_type;
}; };
template <typename FunctionType> template <typename FunctionType>
struct function_traits<std::function<FunctionType>> struct function_traits<std::function<FunctionType>> : function_traits<FunctionType>
: public function_traits<FunctionType>
{}; {};
template <class _Function> template <class _Function>
@ -502,44 +518,59 @@ namespace sdbus {
template <typename _Function> template <typename _Function>
using tuple_of_function_output_arg_types_t = typename tuple_of_function_output_arg_types<_Function>::type; using tuple_of_function_output_arg_types_t = typename tuple_of_function_output_arg_types<_Function>::type;
template <typename _Type> template <typename _Function>
struct aggregate_signature struct signature_of_function_input_arguments : signature_of<tuple_of_function_input_arg_types_t<_Function>>
{ {
static const std::string str() static std::string value_as_string()
{ {
return signature_of<std::decay_t<_Type>>::str(); constexpr auto signature = as_null_terminated(signature_of_v<tuple_of_function_input_arg_types_t<_Function>>);
} return signature.data();
};
template <typename... _Types>
struct aggregate_signature<std::tuple<_Types...>>
{
static const std::string str()
{
std::string signature;
(void)(signature += ... += signature_of<std::decay_t<_Types>>::str());
return signature;
} }
}; };
template <typename _Function> template <typename _Function>
struct signature_of_function_input_arguments inline auto signature_of_function_input_arguments_v = signature_of_function_input_arguments<_Function>::value_as_string();
template <typename _Function>
struct signature_of_function_output_arguments : signature_of<tuple_of_function_output_arg_types_t<_Function>>
{ {
static const std::string str() static std::string value_as_string()
{ {
return aggregate_signature<tuple_of_function_input_arg_types_t<_Function>>::str(); constexpr auto signature = as_null_terminated(signature_of_v<tuple_of_function_output_arg_types_t<_Function>>);
return signature.data();
} }
}; };
template <typename _Function> template <typename _Function>
struct signature_of_function_output_arguments inline auto signature_of_function_output_arguments_v = signature_of_function_output_arguments<_Function>::value_as_string();
// std::future stuff for return values of async calls
template <typename... _Args> struct future_return
{ {
static const std::string str() typedef std::tuple<_Args...> type;
{
return aggregate_signature<tuple_of_function_output_arg_types_t<_Function>>::str();
}
}; };
template <> struct future_return<>
{
typedef void type;
};
template <typename _Type> struct future_return<_Type>
{
typedef _Type type;
};
template <typename... _Args>
using future_return_t = typename future_return<_Args...>::type;
// Credit: Piotr Skotnicki (https://stackoverflow.com/a/57639506)
template <typename, typename>
constexpr bool is_one_of_variants_types = false;
template <typename... _VariantTypes, typename _QueriedType>
constexpr bool is_one_of_variants_types<std::variant<_VariantTypes...>, _QueriedType>
= (std::is_same_v<_QueriedType, _VariantTypes> || ...);
namespace detail namespace detail
{ {
template <class _Function, class _Tuple, typename... _Args, std::size_t... _I> template <class _Function, class _Tuple, typename... _Args, std::size_t... _I>
@ -552,12 +583,12 @@ namespace sdbus {
} }
template <class _Function, class _Tuple, std::size_t... _I> template <class _Function, class _Tuple, std::size_t... _I>
constexpr decltype(auto) apply_impl( _Function&& f decltype(auto) apply_impl( _Function&& f
, const Error* e , std::optional<Error> e
, _Tuple&& t , _Tuple&& t
, std::index_sequence<_I...> ) , std::index_sequence<_I...> )
{ {
return std::forward<_Function>(f)(e, std::get<_I>(std::forward<_Tuple>(t))...); return std::forward<_Function>(f)(std::move(e), std::get<_I>(std::forward<_Tuple>(t))...);
} }
// For non-void returning functions, apply_impl simply returns function return value (a tuple of values). // For non-void returning functions, apply_impl simply returns function return value (a tuple of values).
@ -598,13 +629,33 @@ namespace sdbus {
// Convert tuple `t' of values into a list of arguments // Convert tuple `t' of values into a list of arguments
// and invoke function `f' with those arguments. // and invoke function `f' with those arguments.
template <class _Function, class _Tuple> template <class _Function, class _Tuple>
constexpr decltype(auto) apply(_Function&& f, const Error* e, _Tuple&& t) decltype(auto) apply(_Function&& f, std::optional<Error> e, _Tuple&& t)
{ {
return detail::apply_impl( std::forward<_Function>(f) return detail::apply_impl( std::forward<_Function>(f)
, e , std::move(e)
, std::forward<_Tuple>(t) , std::forward<_Tuple>(t)
, std::make_index_sequence<std::tuple_size<std::decay_t<_Tuple>>::value>{} ); , std::make_index_sequence<std::tuple_size<std::decay_t<_Tuple>>::value>{} );
} }
// Convenient concatenation of arrays
template <typename _T, std::size_t _N1, std::size_t _N2>
constexpr std::array<_T, _N1 + _N2> operator+(std::array<_T, _N1> lhs, std::array<_T, _N2> rhs)
{
std::array<_T, _N1 + _N2> result{};
std::size_t index = 0;
for (auto& el : lhs) {
result[index] = std::move(el);
++index;
}
for (auto& el : rhs) {
result[index] = std::move(el);
++index;
}
return result;
}
} }
#endif /* SDBUS_CXX_TYPETRAITS_H_ */ #endif /* SDBUS_CXX_TYPETRAITS_H_ */

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file Types.h * @file Types.h
* *
@ -29,12 +29,14 @@
#include <sdbus-c++/Message.h> #include <sdbus-c++/Message.h>
#include <sdbus-c++/TypeTraits.h> #include <sdbus-c++/TypeTraits.h>
#include <cstring>
#include <memory>
#include <string> #include <string>
#include <tuple>
#include <type_traits> #include <type_traits>
#include <typeinfo> #include <typeinfo>
#include <memory> #include <utility>
#include <tuple>
#include <unistd.h>
namespace sdbus { namespace sdbus {
@ -42,7 +44,7 @@ namespace sdbus {
* @class Variant * @class Variant
* *
* Variant can hold value of any D-Bus-supported type. * Variant can hold value of any D-Bus-supported type.
* *
* Note: Even though thread-aware, Variant objects are not thread-safe. * Note: Even though thread-aware, Variant objects are not thread-safe.
* Some const methods are conceptually const, but not physically const, * Some const methods are conceptually const, but not physically const,
* thus are not thread-safe. This is by design: normally, clients * thus are not thread-safe. This is by design: normally, clients
@ -56,21 +58,29 @@ namespace sdbus {
Variant(); Variant();
template <typename _ValueType> template <typename _ValueType>
Variant(const _ValueType& value) explicit Variant(const _ValueType& value) : Variant()
: Variant()
{ {
msg_.openVariant(signature_of<_ValueType>::str()); msg_.openVariant<_ValueType>();
msg_ << value; msg_ << value;
msg_.closeVariant(); msg_.closeVariant();
msg_.seal(); msg_.seal();
} }
template <typename... _Elements>
Variant(const std::variant<_Elements...>& value)
: Variant()
{
msg_ << value;
msg_.seal();
}
template <typename _ValueType> template <typename _ValueType>
_ValueType get() const _ValueType get() const
{ {
_ValueType val; _ValueType val;
msg_.rewind(false); msg_.rewind(false);
msg_.enterVariant(signature_of<_ValueType>::str());
msg_.enterVariant<_ValueType>();
msg_ >> val; msg_ >> val;
msg_.exitVariant(); msg_.exitVariant();
return val; return val;
@ -78,22 +88,32 @@ namespace sdbus {
// Only allow conversion operator for true D-Bus type representations in C++ // Only allow conversion operator for true D-Bus type representations in C++
template <typename _ValueType, typename = std::enable_if_t<signature_of<_ValueType>::is_valid>> template <typename _ValueType, typename = std::enable_if_t<signature_of<_ValueType>::is_valid>>
operator _ValueType() const explicit operator _ValueType() const
{ {
return get<_ValueType>(); return get<_ValueType>();
} }
template <typename... _Elements>
operator std::variant<_Elements...>() const
{
std::variant<_Elements...> result;
msg_.rewind(false);
msg_ >> result;
return result;
}
template <typename _Type> template <typename _Type>
bool containsValueOfType() const bool containsValueOfType() const
{ {
return signature_of<_Type>::str() == peekValueType(); constexpr auto signature = as_null_terminated(signature_of_v<_Type>);
return std::strcmp(signature.data(), peekValueType()) == 0;
} }
bool isEmpty() const; bool isEmpty() const;
void serializeTo(Message& msg) const; void serializeTo(Message& msg) const;
void deserializeFrom(Message& msg); void deserializeFrom(Message& msg);
std::string peekValueType() const; const char* peekValueType() const;
private: private:
mutable PlainMessage msg_{}; mutable PlainMessage msg_{};
@ -104,6 +124,10 @@ namespace sdbus {
* *
* Representation of struct D-Bus type * Representation of struct D-Bus type
* *
* Struct implements tuple protocol, i.e. it's a tuple-like class.
* It can be used with std::get<>(), std::tuple_element,
* std::tuple_size and in structured bindings.
*
***********************************************/ ***********************************************/
template <typename... _ValueTypes> template <typename... _ValueTypes>
class Struct class Struct
@ -112,15 +136,12 @@ namespace sdbus {
public: public:
using std::tuple<_ValueTypes...>::tuple; using std::tuple<_ValueTypes...>::tuple;
// Disable constructor if an older then 7.1.0 version of GCC is used
#if !((defined(__GNUC__) || defined(__GNUG__)) && !defined(__clang__) && !(__GNUC__ > 7 || (__GNUC__ == 7 && (__GNUC_MINOR__ > 1 || (__GNUC_MINOR__ == 1 && __GNUC_PATCHLEVEL__ > 0)))))
Struct() = default; Struct() = default;
explicit Struct(const std::tuple<_ValueTypes...>& t) explicit Struct(const std::tuple<_ValueTypes...>& t)
: std::tuple<_ValueTypes...>(t) : std::tuple<_ValueTypes...>(t)
{ {
} }
#endif
template <std::size_t _I> template <std::size_t _I>
auto& get() auto& get()
@ -135,6 +156,9 @@ namespace sdbus {
} }
}; };
template <typename... _Elements>
Struct(_Elements...) -> Struct<_Elements...>;
template<typename... _Elements> template<typename... _Elements>
constexpr Struct<std::decay_t<_Elements>...> constexpr Struct<std::decay_t<_Elements>...>
make_struct(_Elements&&... args) make_struct(_Elements&&... args)
@ -146,42 +170,107 @@ namespace sdbus {
/********************************************//** /********************************************//**
* @class ObjectPath * @class ObjectPath
* *
* Representation of object path D-Bus type * Strong type representing the D-Bus object path
* *
***********************************************/ ***********************************************/
class ObjectPath : public std::string class ObjectPath : public std::string
{ {
public: public:
using std::string::string; ObjectPath() = default;
ObjectPath() = default; // Fixes gcc 6.3 error (default c-tor is not imported in above using declaration) explicit ObjectPath(std::string value)
ObjectPath(const ObjectPath&) = default; // Fixes gcc 8.3 error (deleted copy constructor) : std::string(std::move(value))
ObjectPath(ObjectPath&&) = default; // Enable move - user-declared copy ctor prevents implicit creation
ObjectPath& operator = (const ObjectPath&) = default; // Fixes gcc 8.3 error (deleted copy assignment)
ObjectPath& operator = (ObjectPath&&) = default; // Enable move - user-declared copy assign prevents implicit creation
ObjectPath(std::string path)
: std::string(std::move(path))
{} {}
explicit ObjectPath(const char* value)
: std::string(value)
{}
using std::string::operator=; using std::string::operator=;
}; };
/********************************************//**
* @class BusName
*
* Strong type representing the D-Bus bus/service/connection name
*
***********************************************/
class BusName : public std::string
{
public:
BusName() = default;
explicit BusName(std::string value)
: std::string(std::move(value))
{}
explicit BusName(const char* value)
: std::string(value)
{}
using std::string::operator=;
};
using ServiceName = BusName;
using ConnectionName = BusName;
/********************************************//**
* @class InterfaceName
*
* Strong type representing the D-Bus interface name
*
***********************************************/
class InterfaceName : public std::string
{
public:
InterfaceName() = default;
explicit InterfaceName(std::string value)
: std::string(std::move(value))
{}
explicit InterfaceName(const char* value)
: std::string(value)
{}
using std::string::operator=;
};
/********************************************//**
* @class MemberName
*
* Strong type representing the D-Bus member name
*
***********************************************/
class MemberName : public std::string
{
public:
MemberName() = default;
explicit MemberName(std::string value)
: std::string(std::move(value))
{}
explicit MemberName(const char* value)
: std::string(value)
{}
using std::string::operator=;
};
using MethodName = MemberName;
using SignalName = MemberName;
using PropertyName = MemberName;
/********************************************//** /********************************************//**
* @class Signature * @class Signature
* *
* Representation of Signature D-Bus type * Strong type representing the D-Bus object path
* *
***********************************************/ ***********************************************/
class Signature : public std::string class Signature : public std::string
{ {
public: public:
using std::string::string; Signature() = default;
Signature() = default; // Fixes gcc 6.3 error (default c-tor is not imported in above using declaration) explicit Signature(std::string value)
Signature(const Signature&) = default; // Fixes gcc 8.3 error (deleted copy constructor) : std::string(std::move(value))
Signature(Signature&&) = default; // Enable move - user-declared copy ctor prevents implicit creation
Signature& operator = (const Signature&) = default; // Fixes gcc 8.3 error (deleted copy assignment)
Signature& operator = (Signature&&) = default; // Enable move - user-declared copy assign prevents implicit creation
Signature(std::string path)
: std::string(std::move(path))
{} {}
explicit Signature(const char* value)
: std::string(value)
{}
using std::string::operator=; using std::string::operator=;
}; };
@ -202,7 +291,7 @@ namespace sdbus {
UnixFd() = default; UnixFd() = default;
explicit UnixFd(int fd) explicit UnixFd(int fd)
: fd_(::dup(fd)) : fd_(checkedDup(fd))
{ {
} }
@ -218,8 +307,12 @@ namespace sdbus {
UnixFd& operator=(const UnixFd& other) UnixFd& operator=(const UnixFd& other)
{ {
if (this == &other)
{
return *this;
}
close(); close();
fd_ = ::dup(other.fd_); fd_ = checkedDup(other.fd_);
return *this; return *this;
} }
@ -230,9 +323,12 @@ namespace sdbus {
UnixFd& operator=(UnixFd&& other) UnixFd& operator=(UnixFd&& other)
{ {
if (this == &other)
{
return *this;
}
close(); close();
fd_ = other.fd_; fd_ = std::exchange(other.fd_, -1);
other.fd_ = -1;
return *this; return *this;
} }
@ -241,7 +337,7 @@ namespace sdbus {
close(); close();
} }
int get() const [[nodiscard]] int get() const
{ {
return fd_; return fd_;
} }
@ -258,26 +354,45 @@ namespace sdbus {
int release() int release()
{ {
auto fd = fd_; return std::exchange(fd_, -1);
fd_ = -1;
return fd;
} }
bool isValid() const [[nodiscard]] bool isValid() const
{ {
return fd_ >= 0; return fd_ >= 0;
} }
private: private:
void close() /// Closes file descriptor, but does not set it to -1.
{ void close();
if (fd_ >= 0)
::close(fd_); /// Returns negative argument unchanged.
} /// Otherwise, call ::dup and throw on failure.
static int checkedDup(int fd);
int fd_ = -1; int fd_ = -1;
}; };
/********************************************//**
* @typedef DictEntry
*
* DictEntry is implemented as std::pair, a standard
* value_type in STL(-like) associative containers.
*
***********************************************/
template<typename _T1, typename _T2>
using DictEntry = std::pair<_T1, _T2>;
} }
template <size_t _I, typename... _ValueTypes>
struct std::tuple_element<_I, sdbus::Struct<_ValueTypes...>>
: std::tuple_element<_I, std::tuple<_ValueTypes...>>
{};
template <typename... _ValueTypes>
struct std::tuple_size<sdbus::Struct<_ValueTypes...>>
: std::tuple_size<std::tuple<_ValueTypes...>>
{};
#endif /* SDBUS_CXX_TYPES_H_ */ #endif /* SDBUS_CXX_TYPES_H_ */

View File

@ -0,0 +1,112 @@
/**
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file VTableItems.h
*
* Created on: Dec 14, 2023
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_VTABLEITEMS_H_
#define SDBUS_CXX_VTABLEITEMS_H_
#include <sdbus-c++/Flags.h>
#include <sdbus-c++/Types.h>
#include <sdbus-c++/TypeTraits.h>
#include <string>
#include <variant>
#include <vector>
namespace sdbus {
struct MethodVTableItem
{
template <typename _Function> MethodVTableItem& implementedAs(_Function&& callback);
MethodVTableItem& withInputParamNames(std::vector<std::string> names);
template <typename... _String> MethodVTableItem& withInputParamNames(_String... names);
MethodVTableItem& withOutputParamNames(std::vector<std::string> names);
template <typename... _String> MethodVTableItem& withOutputParamNames(_String... names);
MethodVTableItem& markAsDeprecated();
MethodVTableItem& markAsPrivileged();
MethodVTableItem& withNoReply();
MethodName name;
Signature inputSignature;
std::vector<std::string> inputParamNames;
Signature outputSignature;
std::vector<std::string> outputParamNames;
method_callback callbackHandler;
Flags flags;
};
MethodVTableItem registerMethod(MethodName methodName);
MethodVTableItem registerMethod(std::string methodName);
struct SignalVTableItem
{
template <typename... _Args> SignalVTableItem& withParameters();
template <typename... _Args> SignalVTableItem& withParameters(std::vector<std::string> names);
template <typename... _Args, typename... _String> SignalVTableItem& withParameters(_String... names);
SignalVTableItem& markAsDeprecated();
SignalName name;
Signature signature;
std::vector<std::string> paramNames;
Flags flags;
};
SignalVTableItem registerSignal(SignalName signalName);
SignalVTableItem registerSignal(std::string signalName);
struct PropertyVTableItem
{
template <typename _Function> PropertyVTableItem& withGetter(_Function&& callback);
template <typename _Function> PropertyVTableItem& withSetter(_Function&& callback);
PropertyVTableItem& markAsDeprecated();
PropertyVTableItem& markAsPrivileged();
PropertyVTableItem& withUpdateBehavior(Flags::PropertyUpdateBehaviorFlags behavior);
PropertyName name;
Signature signature;
property_get_callback getter;
property_set_callback setter;
Flags flags;
};
PropertyVTableItem registerProperty(PropertyName propertyName);
PropertyVTableItem registerProperty(std::string propertyName);
struct InterfaceFlagsVTableItem
{
InterfaceFlagsVTableItem& markAsDeprecated();
InterfaceFlagsVTableItem& markAsPrivileged();
InterfaceFlagsVTableItem& withNoReplyMethods();
InterfaceFlagsVTableItem& withPropertyUpdateBehavior(Flags::PropertyUpdateBehaviorFlags behavior);
Flags flags;
};
InterfaceFlagsVTableItem setInterfaceFlags();
using VTableItem = std::variant<MethodVTableItem, SignalVTableItem, PropertyVTableItem, InterfaceFlagsVTableItem>;
} // namespace sdbus
#endif /* SDBUS_CXX_VTABLEITEMS_H_ */

View File

@ -0,0 +1,301 @@
/**
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file VTableItems.inl
*
* Created on: Dec 14, 2023
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CPP_VTABLEITEMS_INL_
#define SDBUS_CPP_VTABLEITEMS_INL_
#include <sdbus-c++/Error.h>
#include <sdbus-c++/TypeTraits.h>
#include <string>
#include <type_traits>
#include <vector>
namespace sdbus {
/*** -------------------- ***/
/*** Method VTable Item ***/
/*** -------------------- ***/
template <typename _Function>
MethodVTableItem& MethodVTableItem::implementedAs(_Function&& callback)
{
inputSignature = signature_of_function_input_arguments_v<_Function>;
outputSignature = signature_of_function_output_arguments_v<_Function>;
callbackHandler = [callback = std::forward<_Function>(callback)](MethodCall call)
{
// Create a tuple of callback input arguments' types, which will be used
// as a storage for the argument values deserialized from the message.
tuple_of_function_input_arg_types_t<_Function> inputArgs;
// Deserialize input arguments from the message into the tuple.
call >> inputArgs;
if constexpr (!is_async_method_v<_Function>)
{
// Invoke callback with input arguments from the tuple.
auto ret = sdbus::apply(callback, inputArgs);
// Store output arguments to the reply message and send it back.
auto reply = call.createReply();
reply << ret;
reply.send();
}
else
{
// Invoke callback with input arguments from the tuple and with result object to be set later
using AsyncResult = typename function_traits<_Function>::async_result_t;
sdbus::apply(callback, AsyncResult{std::move(call)}, std::move(inputArgs));
}
};
return *this;
}
inline MethodVTableItem& MethodVTableItem::withInputParamNames(std::vector<std::string> names)
{
inputParamNames = std::move(names);
return *this;
}
template <typename... _String>
inline MethodVTableItem& MethodVTableItem::withInputParamNames(_String... names)
{
static_assert(std::conjunction_v<std::is_convertible<_String, std::string>...>, "Parameter names must be (convertible to) strings");
return withInputParamNames({names...});
}
inline MethodVTableItem& MethodVTableItem::withOutputParamNames(std::vector<std::string> names)
{
outputParamNames = std::move(names);
return *this;
}
template <typename... _String>
inline MethodVTableItem& MethodVTableItem::withOutputParamNames(_String... names)
{
static_assert(std::conjunction_v<std::is_convertible<_String, std::string>...>, "Parameter names must be (convertible to) strings");
return withOutputParamNames({names...});
}
inline MethodVTableItem& MethodVTableItem::markAsDeprecated()
{
flags.set(Flags::DEPRECATED);
return *this;
}
inline MethodVTableItem& MethodVTableItem::markAsPrivileged()
{
flags.set(Flags::PRIVILEGED);
return *this;
}
inline MethodVTableItem& MethodVTableItem::withNoReply()
{
flags.set(Flags::METHOD_NO_REPLY);
return *this;
}
inline MethodVTableItem registerMethod(MethodName methodName)
{
return {std::move(methodName), {}, {}, {}, {}, {}, {}};
}
inline MethodVTableItem registerMethod(std::string methodName)
{
return registerMethod(MethodName{std::move(methodName)});
}
/*** -------------------- ***/
/*** Signal VTable Item ***/
/*** -------------------- ***/
template <typename... _Args>
inline SignalVTableItem& SignalVTableItem::withParameters()
{
signature = signature_of_function_input_arguments_v<void(_Args...)>;
return *this;
}
template <typename... _Args>
inline SignalVTableItem& SignalVTableItem::withParameters(std::vector<std::string> names)
{
paramNames = std::move(names);
return withParameters<_Args...>();
}
template <typename... _Args, typename... _String>
inline SignalVTableItem& SignalVTableItem::withParameters(_String... names)
{
static_assert(std::conjunction_v<std::is_convertible<_String, std::string>...>, "Parameter names must be (convertible to) strings");
static_assert(sizeof...(_Args) == sizeof...(_String), "Numbers of signal parameters and their names don't match");
return withParameters<_Args...>({names...});
}
inline SignalVTableItem& SignalVTableItem::markAsDeprecated()
{
flags.set(Flags::DEPRECATED);
return *this;
}
inline SignalVTableItem registerSignal(SignalName signalName)
{
return {std::move(signalName), {}, {}, {}};
}
inline SignalVTableItem registerSignal(std::string signalName)
{
return registerSignal(SignalName{std::move(signalName)});
}
/*** -------------------- ***/
/*** Property VTable Item ***/
/*** -------------------- ***/
template <typename _Function>
inline PropertyVTableItem& PropertyVTableItem::withGetter(_Function&& callback)
{
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 (signature.empty())
signature = signature_of_function_output_arguments_v<_Function>;
getter = [callback = std::forward<_Function>(callback)](PropertyGetReply& reply)
{
// Get the propety value and serialize it into the pre-constructed reply message
reply << callback();
};
return *this;
}
template <typename _Function>
inline PropertyVTableItem& PropertyVTableItem::withSetter(_Function&& callback)
{
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 (signature.empty())
signature = signature_of_function_input_arguments_v<_Function>;
setter = [callback = std::forward<_Function>(callback)](PropertySetCall call)
{
// Default-construct property value
using property_type = function_argument_t<_Function, 0>;
std::decay_t<property_type> property;
// Deserialize property value from the incoming call message
call >> property;
// Invoke setter with the value
callback(property);
};
return *this;
}
inline PropertyVTableItem& PropertyVTableItem::markAsDeprecated()
{
flags.set(Flags::DEPRECATED);
return *this;
}
inline PropertyVTableItem& PropertyVTableItem::markAsPrivileged()
{
flags.set(Flags::PRIVILEGED);
return *this;
}
inline PropertyVTableItem& PropertyVTableItem::withUpdateBehavior(Flags::PropertyUpdateBehaviorFlags behavior)
{
flags.set(behavior);
return *this;
}
inline PropertyVTableItem registerProperty(PropertyName propertyName)
{
return {std::move(propertyName), {}, {}, {}, {}};
}
inline PropertyVTableItem registerProperty(std::string propertyName)
{
return registerProperty(PropertyName{std::move(propertyName)});
}
/*** --------------------------- ***/
/*** Interface Flags VTable Item ***/
/*** --------------------------- ***/
inline InterfaceFlagsVTableItem& InterfaceFlagsVTableItem::markAsDeprecated()
{
flags.set(Flags::DEPRECATED);
return *this;
}
inline InterfaceFlagsVTableItem& InterfaceFlagsVTableItem::markAsPrivileged()
{
flags.set(Flags::PRIVILEGED);
return *this;
}
inline InterfaceFlagsVTableItem& InterfaceFlagsVTableItem::withNoReplyMethods()
{
flags.set(Flags::METHOD_NO_REPLY);
return *this;
}
inline InterfaceFlagsVTableItem& InterfaceFlagsVTableItem::withPropertyUpdateBehavior(Flags::PropertyUpdateBehaviorFlags behavior)
{
flags.set(behavior);
return *this;
}
inline InterfaceFlagsVTableItem setInterfaceFlags()
{
return {};
}
} // namespace sdbus
#endif /* SDBUS_CPP_VTABLEITEMS_INL_ */

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file sdbus-c++.h * @file sdbus-c++.h
* *

View File

@ -5,7 +5,7 @@ includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@
Name: @PROJECT_NAME@ Name: @PROJECT_NAME@
Description: C++ library on top of sd-bus, a systemd D-Bus library 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@ Version: @SDBUSCPP_VERSION@
Libs: -L${libdir} -l@PROJECT_NAME@ Libs: -L${libdir} -l@PROJECT_NAME@
Cflags: -I${includedir} Cflags: -I${includedir}

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file Connection.h * @file Connection.h
* *
@ -27,18 +27,32 @@
#ifndef SDBUS_CXX_INTERNAL_CONNECTION_H_ #ifndef SDBUS_CXX_INTERNAL_CONNECTION_H_
#define SDBUS_CXX_INTERNAL_CONNECTION_H_ #define SDBUS_CXX_INTERNAL_CONNECTION_H_
#include <sdbus-c++/IConnection.h> #include "sdbus-c++/IConnection.h"
#include <sdbus-c++/Message.h>
#include "sdbus-c++/Message.h"
#include "IConnection.h" #include "IConnection.h"
#include "ScopeGuard.h"
#include "ISdBus.h" #include "ISdBus.h"
#include <systemd/sd-bus.h> #include "ScopeGuard.h"
#include <memory> #include <memory>
#include <thread>
#include <string> #include <string>
#include SDBUS_HEADER
#include <thread>
#include <vector> #include <vector>
#include <atomic>
#include <mutex> // Forward declarations
struct sd_event_source;
namespace sdbus {
class ObjectPath;
class InterfaceName;
class BusName;
using ServiceName = BusName;
class MemberName;
using MethodName = MemberName;
using SignalName = MemberName;
using PropertyName = MemberName;
}
namespace sdbus::internal { namespace sdbus::internal {
@ -57,6 +71,12 @@ namespace sdbus::internal {
inline static constexpr custom_session_bus_t custom_session_bus{}; inline static constexpr custom_session_bus_t custom_session_bus{};
struct remote_system_bus_t{}; struct remote_system_bus_t{};
inline static constexpr remote_system_bus_t remote_system_bus{}; 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 struct pseudo_bus_t{}; // A bus connection that is not really established with D-Bus daemon
inline static constexpr pseudo_bus_t pseudo_bus{}; inline static constexpr pseudo_bus_t pseudo_bus{};
@ -65,63 +85,89 @@ namespace sdbus::internal {
Connection(std::unique_ptr<ISdBus>&& interface, session_bus_t); 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, 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, 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(std::unique_ptr<ISdBus>&& interface, pseudo_bus_t);
~Connection() override; ~Connection() override;
void requestName(const std::string& name) override; void requestName(const ServiceName & name) override;
void releaseName(const std::string& name) override; void releaseName(const ServiceName& name) override;
std::string getUniqueName() const override; [[nodiscard]] BusName getUniqueName() const override;
void enterEventLoop() override; void enterEventLoop() override;
void enterEventLoopAsync() override; void enterEventLoopAsync() override;
void leaveEventLoop() override; void leaveEventLoop() override;
PollData getEventLoopPollData() const override; [[nodiscard]] PollData getEventLoopPollData() const override;
bool processPendingRequest() override; bool processPendingEvent() override;
Message getCurrentlyProcessedMessage() const override;
void addObjectManager(const std::string& objectPath) override; void addObjectManager(const ObjectPath& objectPath) override;
void addObjectManager(const std::string& objectPath, floating_slot_t) override; Slot addObjectManager(const ObjectPath& objectPath, return_slot_t) override;
Slot addObjectManager(const std::string& objectPath, request_slot_t) override;
void setMethodCallTimeout(uint64_t timeout) override; void setMethodCallTimeout(uint64_t timeout) override;
uint64_t getMethodCallTimeout() const override; [[nodiscard]] uint64_t getMethodCallTimeout() const override;
[[nodiscard]] Slot addMatch(const std::string& match, message_handler callback) override; void addMatch(const std::string& match, message_handler callback) override;
void addMatch(const std::string& match, message_handler callback, floating_slot_t) override; [[nodiscard]] Slot addMatch(const std::string& match, message_handler callback, return_slot_t) override;
void addMatchAsync(const std::string& match, message_handler callback, message_handler installCallback) override;
[[nodiscard]] Slot addMatchAsync( const std::string& match
, message_handler callback
, message_handler installCallback
, return_slot_t ) override;
const ISdBus& getSdBusInterface() const override; void attachSdEventLoop(sd_event *event, int priority) override;
ISdBus& getSdBusInterface() override; void detachSdEventLoop() override;
sd_event *getSdEventLoop() override;
Slot addObjectVTable( const std::string& objectPath [[nodiscard]] const ISdBus& getSdBusInterface() const override;
, const std::string& interfaceName [[nodiscard]] ISdBus& getSdBusInterface() override;
Slot addObjectVTable( const ObjectPath& objectPath
, const InterfaceName& interfaceName
, const sd_bus_vtable* vtable , const sd_bus_vtable* vtable
, void* userData ) override; , void* userData
, return_slot_t ) override;
PlainMessage createPlainMessage() const override; [[nodiscard]] PlainMessage createPlainMessage() const override;
MethodCall createMethodCall( const std::string& destination [[nodiscard]] MethodCall createMethodCall( const ServiceName& destination
, const std::string& objectPath , const ObjectPath& objectPath
, const std::string& interfaceName , const InterfaceName& interfaceName
, const std::string& methodName ) const override; , const MethodName& methodName ) const override;
Signal createSignal( const std::string& objectPath [[nodiscard]] MethodCall createMethodCall( const char* destination
, const std::string& interfaceName , const char* objectPath
, const std::string& signalName ) const override; , const char* interfaceName
, const char* methodName ) const override;
[[nodiscard]] Signal createSignal( const ObjectPath& objectPath
, const InterfaceName& interfaceName
, const SignalName& signalName ) const override;
[[nodiscard]] Signal createSignal( const char* objectPath
, const char* interfaceName
, const char* signalName ) const override;
void emitPropertiesChangedSignal( const std::string& objectPath MethodReply callMethod(const MethodCall& message, uint64_t timeout) override;
, const std::string& interfaceName Slot callMethod(const MethodCall& message, void* callback, void* userData, uint64_t timeout, return_slot_t) override;
, const std::vector<std::string>& propNames ) override;
void emitInterfacesAddedSignal(const std::string& objectPath) override;
void emitInterfacesAddedSignal( const std::string& objectPath
, const std::vector<std::string>& interfaces ) override;
void emitInterfacesRemovedSignal(const std::string& objectPath) override;
void emitInterfacesRemovedSignal( const std::string& objectPath
, const std::vector<std::string>& interfaces ) override;
Slot registerSignalHandler( const std::string& sender void emitPropertiesChangedSignal( const ObjectPath& objectPath
, const std::string& objectPath , const InterfaceName& interfaceName
, const std::string& interfaceName , const std::vector<PropertyName>& propNames ) override;
, const std::string& signalName void emitPropertiesChangedSignal( const char* objectPath
, const char* interfaceName
, const std::vector<PropertyName>& propNames ) override;
void emitInterfacesAddedSignal(const ObjectPath& objectPath) override;
void emitInterfacesAddedSignal( const ObjectPath& objectPath
, const std::vector<InterfaceName>& interfaces ) override;
void emitInterfacesRemovedSignal(const ObjectPath& objectPath) override;
void emitInterfacesRemovedSignal( const ObjectPath& objectPath
, const std::vector<InterfaceName>& interfaces ) override;
Slot registerSignalHandler( const char* sender
, const char* objectPath
, const char* interfaceName
, const char* signalName
, sd_bus_message_handler_t callback , sd_bus_message_handler_t callback
, void* userData ) override; , void* userData
, return_slot_t ) override;
MethodReply tryCallMethodSynchronously(const MethodCall& message, uint64_t timeout) override;
private: private:
using BusFactory = std::function<int(sd_bus**)>; using BusFactory = std::function<int(sd_bus**)>;
@ -131,44 +177,70 @@ namespace sdbus::internal {
BusPtr openBus(const std::function<int(sd_bus**)>& busFactory); BusPtr openBus(const std::function<int(sd_bus**)>& busFactory);
BusPtr openPseudoBus(); BusPtr openPseudoBus();
void finishHandshake(sd_bus* bus); void finishHandshake(sd_bus* bus);
bool waitForNextRequest(); bool waitForNextEvent();
static std::string composeSignalMatchFilter( const std::string &sender
, const std::string &objectPath [[nodiscard]] bool arePendingMessagesInReadQueue() const;
, const std::string &interfaceName
, const std::string &signalName); void notifyEventLoopToExit();
void notifyEventLoop(int fd) const; void notifyEventLoopToWakeUpFromPoll();
void notifyEventLoopToExit() const; void wakeUpEventLoopIfMessagesInQueue();
void clearEventLoopNotification(int fd) const; void joinWithEventLoop();
void notifyEventLoopNewTimeout() const override;
template <typename StringBasedType>
static std::vector</*const */char*> to_strv(const std::vector<StringBasedType>& 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: private:
void joinWithEventLoop(); #ifndef SDBUS_basu // sd_event integration is not supported if instead of libsystemd we are based on basu
static std::vector</*const */char*> to_strv(const std::vector<std::string>& strings); Slot createSdEventSlot(sd_event *event);
Slot createSdTimeEventSourceSlot(sd_event *event, int priority);
Slot createSdIoEventSourceSlot(sd_event *event, int fd, int priority);
Slot createSdInternalEventSourceSlot(sd_event *event, int fd, int priority);
static void deleteSdEventSource(sd_event_source *s);
static int onSdTimerEvent(sd_event_source *s, uint64_t usec, void *userdata);
static int onSdIoEvent(sd_event_source *s, int fd, uint32_t revents, void *userdata);
static int onSdInternalEvent(sd_event_source *s, int fd, uint32_t revents, void *userdata);
static int onSdEventPrepare(sd_event_source *s, void *userdata);
#endif
struct EventFd struct EventFd
{ {
EventFd(); EventFd();
~EventFd(); ~EventFd();
void notify();
bool clear();
int fd{-1}; int fd{-1};
}; };
struct MatchInfo struct MatchInfo
{ {
message_handler callback; message_handler callback;
message_handler installCallback;
Connection& connection; Connection& connection;
sd_bus_slot *slot; Slot slot;
};
// sd-event integration
struct SdEvent
{
Slot sdEvent;
Slot sdTimeEventSource;
Slot sdIoEventSource;
Slot sdInternalEventSource;
}; };
private: private:
std::unique_ptr<ISdBus> iface_; std::unique_ptr<ISdBus> sdbus_;
BusPtr bus_; BusPtr bus_;
std::thread asyncLoopThread_; std::thread asyncLoopThread_;
std::atomic<std::thread::id> loopThreadId_; EventFd loopExitFd_; // To wake up event loop I/O polling to exit
std::mutex loopMutex_; EventFd eventFd_; // To wake up event loop I/O polling to re-enter poll with fresh PollData values
EventFd loopExitFd_;
EventFd eventFd_;
std::atomic<uint64_t> activeTimeout_{};
std::vector<Slot> floatingMatchRules_; std::vector<Slot> floatingMatchRules_;
std::unique_ptr<SdEvent> sdEvent_; // Integration of systemd sd-event event loop implementation
}; };
} }

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file Error.cpp * @file Error.cpp
* *
@ -24,20 +24,26 @@
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>. * along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <sdbus-c++/Error.h> #include "sdbus-c++/Error.h"
#include <systemd/sd-bus.h>
#include "ScopeGuard.h" #include "ScopeGuard.h"
#include SDBUS_HEADER
namespace sdbus namespace sdbus
{ {
sdbus::Error createError(int errNo, const std::string& customMsg) sdbus::Error createError(int errNo, std::string customMsg)
{ {
sd_bus_error sdbusError = SD_BUS_ERROR_NULL; sd_bus_error sdbusError = SD_BUS_ERROR_NULL;
sd_bus_error_set_errno(&sdbusError, errNo); sd_bus_error_set_errno(&sdbusError, errNo);
SCOPE_EXIT{ sd_bus_error_free(&sdbusError); }; SCOPE_EXIT{ sd_bus_error_free(&sdbusError); };
std::string name(sdbusError.name); Error::Name name(sdbusError.name);
std::string message(customMsg + " (" + sdbusError.message + ")"); std::string message(std::move(customMsg));
return sdbus::Error(name, message); message.append(" (");
message.append(sdbusError.message);
message.append(")");
return Error(std::move(name), std::move(message));
} }
} } // namespace sdbus

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file Flags.cpp * @file Flags.cpp
* *
@ -25,7 +25,7 @@
*/ */
#include <sdbus-c++/Flags.h> #include <sdbus-c++/Flags.h>
#include <systemd/sd-bus.h> #include SDBUS_HEADER
namespace sdbus namespace sdbus
{ {
@ -47,7 +47,7 @@ namespace sdbus
sdbusFlags |= SD_BUS_VTABLE_PROPERTY_CONST; sdbusFlags |= SD_BUS_VTABLE_PROPERTY_CONST;
else if (flags_.test(Flags::EMITS_NO_SIGNAL)) else if (flags_.test(Flags::EMITS_NO_SIGNAL))
sdbusFlags |= 0; sdbusFlags |= 0;
return sdbusFlags; return sdbusFlags;
} }

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file IConnection.h * @file IConnection.h
* *
@ -27,19 +27,30 @@
#ifndef SDBUS_CXX_INTERNAL_ICONNECTION_H_ #ifndef SDBUS_CXX_INTERNAL_ICONNECTION_H_
#define SDBUS_CXX_INTERNAL_ICONNECTION_H_ #define SDBUS_CXX_INTERNAL_ICONNECTION_H_
#include <sdbus-c++/IConnection.h> #include "sdbus-c++/IConnection.h"
#include <systemd/sd-bus.h>
#include <string> #include "sdbus-c++/TypeTraits.h"
#include <memory>
#include <functional> #include <functional>
#include <memory>
#include <string>
#include SDBUS_HEADER
#include <vector> #include <vector>
// Forward declaration // Forward declarations
namespace sdbus { namespace sdbus {
class MethodCall; class MethodCall;
class MethodReply; class MethodReply;
class Signal; class Signal;
class PlainMessage; class PlainMessage;
class ObjectPath;
class InterfaceName;
class BusName;
using ServiceName = BusName;
class MemberName;
using MethodName = MemberName;
using SignalName = MemberName;
using PropertyName = MemberName;
namespace internal { namespace internal {
class ISdBus; class ISdBus;
} }
@ -53,48 +64,60 @@ namespace sdbus::internal {
public: public:
~IConnection() override = default; ~IConnection() override = default;
virtual const ISdBus& getSdBusInterface() const = 0; [[nodiscard]] virtual const ISdBus& getSdBusInterface() const = 0;
virtual ISdBus& getSdBusInterface() = 0; [[nodiscard]] virtual ISdBus& getSdBusInterface() = 0;
[[nodiscard]] virtual Slot addObjectVTable( const std::string& objectPath [[nodiscard]] virtual Slot addObjectVTable( const ObjectPath& objectPath
, const std::string& interfaceName , const InterfaceName& interfaceName
, const sd_bus_vtable* vtable , const sd_bus_vtable* vtable
, void* userData ) = 0; , void* userData
, return_slot_t ) = 0;
virtual PlainMessage createPlainMessage() const = 0; [[nodiscard]] virtual PlainMessage createPlainMessage() const = 0;
virtual MethodCall createMethodCall( const std::string& destination [[nodiscard]] virtual MethodCall createMethodCall( const ServiceName& destination
, const std::string& objectPath , const ObjectPath& objectPath
, const std::string& interfaceName , const InterfaceName& interfaceName
, const std::string& methodName ) const = 0; , const MethodName& methodName ) const = 0;
virtual Signal createSignal( const std::string& objectPath [[nodiscard]] virtual MethodCall createMethodCall( const char* destination
, const std::string& interfaceName , const char* objectPath
, const std::string& signalName ) const = 0; , const char* interfaceName
, const char* methodName ) const = 0;
[[nodiscard]] virtual Signal createSignal( const ObjectPath& objectPath
, const InterfaceName& interfaceName
, const SignalName& signalName ) const = 0;
[[nodiscard]] virtual Signal createSignal( const char* objectPath
, const char* interfaceName
, const char* signalName ) const = 0;
virtual void emitPropertiesChangedSignal( const std::string& objectPath virtual MethodReply callMethod(const MethodCall& message, uint64_t timeout) = 0;
, const std::string& interfaceName [[nodiscard]] virtual Slot callMethod( const MethodCall& message
, const std::vector<std::string>& propNames ) = 0; , void* callback
virtual void emitInterfacesAddedSignal(const std::string& objectPath) = 0; , void* userData
virtual void emitInterfacesAddedSignal( const std::string& objectPath , uint64_t timeout
, const std::vector<std::string>& interfaces ) = 0; , return_slot_t ) = 0;
virtual void emitInterfacesRemovedSignal(const std::string& objectPath) = 0;
virtual void emitInterfacesRemovedSignal( const std::string& objectPath
, const std::vector<std::string>& interfaces ) = 0;
using sdbus::IConnection::addObjectManager; virtual void emitPropertiesChangedSignal( const ObjectPath& objectPath
[[nodiscard]] virtual Slot addObjectManager(const std::string& objectPath, request_slot_t) = 0; , const InterfaceName& interfaceName
, const std::vector<PropertyName>& propNames ) = 0;
virtual void emitPropertiesChangedSignal( const char* objectPath
, const char* interfaceName
, const std::vector<PropertyName>& propNames ) = 0;
virtual void emitInterfacesAddedSignal(const ObjectPath& objectPath) = 0;
virtual void emitInterfacesAddedSignal( const ObjectPath& objectPath
, const std::vector<InterfaceName>& interfaces ) = 0;
virtual void emitInterfacesRemovedSignal(const ObjectPath& objectPath) = 0;
virtual void emitInterfacesRemovedSignal( const ObjectPath& objectPath
, const std::vector<InterfaceName>& interfaces ) = 0;
[[nodiscard]] virtual Slot registerSignalHandler( const std::string& sender [[nodiscard]] virtual Slot registerSignalHandler( const char* sender
, const std::string& objectPath , const char* objectPath
, const std::string& interfaceName , const char* interfaceName
, const std::string& signalName , const char* signalName
, sd_bus_message_handler_t callback , sd_bus_message_handler_t callback
, void* userData ) = 0; , void* userData
, return_slot_t ) = 0;
virtual void notifyEventLoopNewTimeout() const = 0;
virtual MethodReply tryCallMethodSynchronously(const MethodCall& message, uint64_t timeout) = 0;
}; };
[[nodiscard]] std::unique_ptr<sdbus::internal::IConnection> createConnection();
[[nodiscard]] std::unique_ptr<sdbus::internal::IConnection> createPseudoConnection(); [[nodiscard]] std::unique_ptr<sdbus::internal::IConnection> createPseudoConnection();
} }

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file ISdBus.h * @file ISdBus.h
* @author Ardazishvili Roman (ardazishvili.roman@yandex.ru) * @author Ardazishvili Roman (ardazishvili.roman@yandex.ru)
@ -28,7 +28,7 @@
#ifndef SDBUS_CXX_ISDBUS_H #ifndef SDBUS_CXX_ISDBUS_H
#define SDBUS_CXX_ISDBUS_H #define SDBUS_CXX_ISDBUS_H
#include <systemd/sd-bus.h> #include SDBUS_HEADER
namespace sdbus::internal { namespace sdbus::internal {
@ -71,20 +71,26 @@ namespace sdbus::internal {
virtual int sd_bus_open_user(sd_bus **ret) = 0; 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_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_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_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_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_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_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_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(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 int sd_bus_match_signal(sd_bus *bus, sd_bus_slot **ret, const char *sender, const char *path, const char *interface, const char *member, sd_bus_message_handler_t callback, void *userdata) = 0;
virtual sd_bus_slot* sd_bus_slot_unref(sd_bus_slot *slot) = 0; virtual sd_bus_slot* sd_bus_slot_unref(sd_bus_slot *slot) = 0;
virtual int sd_bus_new(sd_bus **ret) = 0; virtual int sd_bus_new(sd_bus **ret) = 0;
virtual int sd_bus_start(sd_bus *bus) = 0; virtual int sd_bus_start(sd_bus *bus) = 0;
virtual int sd_bus_process(sd_bus *bus, sd_bus_message **r) = 0; virtual int sd_bus_process(sd_bus *bus, sd_bus_message **r) = 0;
virtual sd_bus_message* sd_bus_get_current_message(sd_bus *bus) = 0;
virtual int sd_bus_get_poll_data(sd_bus *bus, PollData* data) = 0; virtual int sd_bus_get_poll_data(sd_bus *bus, PollData* data) = 0;
virtual int sd_bus_get_n_queued_read(sd_bus *bus, uint64_t *ret) = 0;
virtual int sd_bus_flush(sd_bus *bus) = 0; virtual int sd_bus_flush(sd_bus *bus) = 0;
virtual sd_bus *sd_bus_flush_close_unref(sd_bus *bus) = 0; virtual sd_bus *sd_bus_flush_close_unref(sd_bus *bus) = 0;
virtual sd_bus *sd_bus_close_unref(sd_bus *bus) = 0; virtual sd_bus *sd_bus_close_unref(sd_bus *bus) = 0;

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file Message.cpp * @file Message.cpp
* *
@ -24,15 +24,19 @@
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>. * along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <sdbus-c++/Message.h> #include "sdbus-c++/Message.h"
#include <sdbus-c++/Types.h>
#include <sdbus-c++/Error.h> #include "sdbus-c++/Error.h"
#include "MessageUtils.h" #include "sdbus-c++/Types.h"
#include "ISdBus.h"
#include "IConnection.h" #include "IConnection.h"
#include "ISdBus.h"
#include "MessageUtils.h"
#include "ScopeGuard.h" #include "ScopeGuard.h"
#include <systemd/sd-bus.h>
#include <cassert> #include <cassert>
#include <cstring>
#include SDBUS_HEADER
namespace sdbus { namespace sdbus {
@ -194,6 +198,17 @@ Message& Message::operator<<(const std::string& item)
return *this; return *this;
} }
Message& Message::operator<<(std::string_view item)
{
char* destPtr{};
auto r = sd_bus_message_append_string_space((sd_bus_message*)msg_, item.length(), &destPtr);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to serialize a string_view value", -r);
std::memcpy(destPtr, item.data(), item.length());
return *this;
}
Message& Message::operator<<(const Variant &item) Message& Message::operator<<(const Variant &item)
{ {
item.serializeTo(*this); item.serializeTo(*this);
@ -226,6 +241,13 @@ Message& Message::operator<<(const UnixFd &item)
return *this; return *this;
} }
Message& Message::appendArray(char type, const void *ptr, size_t size)
{
auto r = sd_bus_message_append_array((sd_bus_message*)msg_, type, ptr, size);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to serialize an array", -r);
return *this;
}
Message& Message::operator>>(bool& item) Message& Message::operator>>(bool& item)
{ {
@ -318,6 +340,17 @@ Message& Message::operator>>(uint64_t& item)
return *this; return *this;
} }
Message& Message::readArray(char type, const void **ptr, size_t *size)
{
auto r = sd_bus_message_read_array((sd_bus_message*)msg_, type, ptr, size);
if (r == 0)
ok_ = false;
SDBUS_THROW_ERROR_IF(r < 0, "Failed to deserialize an array", -r);
return *this;
}
Message& Message::operator>>(double& item) Message& Message::operator>>(double& item)
{ {
auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_DOUBLE, &item); auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_DOUBLE, &item);
@ -408,10 +441,9 @@ Message& Message::operator>>(UnixFd &item)
return *this; return *this;
} }
Message& Message::openContainer(const char* signature)
Message& Message::openContainer(const std::string& signature)
{ {
auto r = sd_bus_message_open_container((sd_bus_message*)msg_, SD_BUS_TYPE_ARRAY, signature.c_str()); auto r = sd_bus_message_open_container((sd_bus_message*)msg_, SD_BUS_TYPE_ARRAY, signature);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to open a container", -r); SDBUS_THROW_ERROR_IF(r < 0, "Failed to open a container", -r);
return *this; return *this;
@ -425,9 +457,9 @@ Message& Message::closeContainer()
return *this; return *this;
} }
Message& Message::openDictEntry(const std::string& signature) Message& Message::openDictEntry(const char* signature)
{ {
auto r = sd_bus_message_open_container((sd_bus_message*)msg_, SD_BUS_TYPE_DICT_ENTRY, signature.c_str()); auto r = sd_bus_message_open_container((sd_bus_message*)msg_, SD_BUS_TYPE_DICT_ENTRY, signature);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to open a dictionary entry", -r); SDBUS_THROW_ERROR_IF(r < 0, "Failed to open a dictionary entry", -r);
return *this; return *this;
@ -441,9 +473,9 @@ Message& Message::closeDictEntry()
return *this; return *this;
} }
Message& Message::openVariant(const std::string& signature) Message& Message::openVariant(const char* signature)
{ {
auto r = sd_bus_message_open_container((sd_bus_message*)msg_, SD_BUS_TYPE_VARIANT, signature.c_str()); auto r = sd_bus_message_open_container((sd_bus_message*)msg_, SD_BUS_TYPE_VARIANT, signature);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to open a variant", -r); SDBUS_THROW_ERROR_IF(r < 0, "Failed to open a variant", -r);
return *this; return *this;
@ -457,9 +489,9 @@ Message& Message::closeVariant()
return *this; return *this;
} }
Message& Message::openStruct(const std::string& signature) Message& Message::openStruct(const char* signature)
{ {
auto r = sd_bus_message_open_container((sd_bus_message*)msg_, SD_BUS_TYPE_STRUCT, signature.c_str()); auto r = sd_bus_message_open_container((sd_bus_message*)msg_, SD_BUS_TYPE_STRUCT, signature);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to open a struct", -r); SDBUS_THROW_ERROR_IF(r < 0, "Failed to open a struct", -r);
return *this; return *this;
@ -473,10 +505,9 @@ Message& Message::closeStruct()
return *this; return *this;
} }
Message& Message::enterContainer(const char* signature)
Message& Message::enterContainer(const std::string& signature)
{ {
auto r = sd_bus_message_enter_container((sd_bus_message*)msg_, SD_BUS_TYPE_ARRAY, signature.c_str()); auto r = sd_bus_message_enter_container((sd_bus_message*)msg_, SD_BUS_TYPE_ARRAY, signature);
if (r == 0) if (r == 0)
ok_ = false; ok_ = false;
@ -493,9 +524,9 @@ Message& Message::exitContainer()
return *this; return *this;
} }
Message& Message::enterDictEntry(const std::string& signature) Message& Message::enterDictEntry(const char* signature)
{ {
auto r = sd_bus_message_enter_container((sd_bus_message*)msg_, SD_BUS_TYPE_DICT_ENTRY, signature.c_str()); auto r = sd_bus_message_enter_container((sd_bus_message*)msg_, SD_BUS_TYPE_DICT_ENTRY, signature);
if (r == 0) if (r == 0)
ok_ = false; ok_ = false;
@ -512,9 +543,9 @@ Message& Message::exitDictEntry()
return *this; return *this;
} }
Message& Message::enterVariant(const std::string& signature) Message& Message::enterVariant(const char* signature)
{ {
auto r = sd_bus_message_enter_container((sd_bus_message*)msg_, SD_BUS_TYPE_VARIANT, signature.c_str()); auto r = sd_bus_message_enter_container((sd_bus_message*)msg_, SD_BUS_TYPE_VARIANT, signature);
if (r == 0) if (r == 0)
ok_ = false; ok_ = false;
@ -531,9 +562,9 @@ Message& Message::exitVariant()
return *this; return *this;
} }
Message& Message::enterStruct(const std::string& signature) Message& Message::enterStruct(const char* signature)
{ {
auto r = sd_bus_message_enter_container((sd_bus_message*)msg_, SD_BUS_TYPE_STRUCT, signature.c_str()); auto r = sd_bus_message_enter_container((sd_bus_message*)msg_, SD_BUS_TYPE_STRUCT, signature);
if (r == 0) if (r == 0)
ok_ = false; ok_ = false;
@ -581,43 +612,38 @@ void Message::rewind(bool complete)
SDBUS_THROW_ERROR_IF(r < 0, "Failed to rewind the message", -r); SDBUS_THROW_ERROR_IF(r < 0, "Failed to rewind the message", -r);
} }
std::string Message::getInterfaceName() const const char* Message::getInterfaceName() const
{ {
auto interface = sd_bus_message_get_interface((sd_bus_message*)msg_); return sd_bus_message_get_interface((sd_bus_message*)msg_);
return interface != nullptr ? interface : "";
} }
std::string Message::getMemberName() const const char* Message::getMemberName() const
{ {
auto member = sd_bus_message_get_member((sd_bus_message*)msg_); return sd_bus_message_get_member((sd_bus_message*)msg_);
return member != nullptr ? member : "";
} }
std::string Message::getSender() const const char* Message::getSender() const
{ {
return sd_bus_message_get_sender((sd_bus_message*)msg_); return sd_bus_message_get_sender((sd_bus_message*)msg_);
} }
std::string Message::getPath() const const char* Message::getPath() const
{ {
auto path = sd_bus_message_get_path((sd_bus_message*)msg_); return sd_bus_message_get_path((sd_bus_message*)msg_);
return path != nullptr ? path : "";
} }
std::string Message::getDestination() const const char* Message::getDestination() const
{ {
auto destination = sd_bus_message_get_destination((sd_bus_message*)msg_); return sd_bus_message_get_destination((sd_bus_message*)msg_);
return destination != nullptr ? destination : "";
} }
void Message::peekType(std::string& type, std::string& contents) const std::pair<char, const char*> Message::peekType() const
{ {
char typeSig; char typeSignature{};
const char* contentsSig; const char* contentsSignature{};
auto r = sd_bus_message_peek_type((sd_bus_message*)msg_, &typeSig, &contentsSig); auto r = sd_bus_message_peek_type((sd_bus_message*)msg_, &typeSignature, &contentsSignature);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to peek message type", -r); SDBUS_THROW_ERROR_IF(r < 0, "Failed to peek message type", -r);
type = typeSig; return {typeSignature, contentsSignature};
contents = contentsSig ? contentsSig : "";
} }
bool Message::isValid() const bool Message::isValid() const
@ -630,6 +656,11 @@ bool Message::isEmpty() const
return sd_bus_message_is_empty((sd_bus_message*)msg_) != 0; return sd_bus_message_is_empty((sd_bus_message*)msg_) != 0;
} }
bool Message::isAtEnd(bool complete) const
{
return sd_bus_message_at_end((sd_bus_message*)msg_, complete) > 0;
}
pid_t Message::getCredsPid() const pid_t Message::getCredsPid() const
{ {
uint64_t mask = SD_BUS_CREDS_PID | SD_BUS_CREDS_AUGMENT; uint64_t mask = SD_BUS_CREDS_PID | SD_BUS_CREDS_AUGMENT;
@ -740,12 +771,9 @@ std::string Message::getSELinuxContext() const
MethodCall::MethodCall( void *msg MethodCall::MethodCall( void *msg
, internal::ISdBus *sdbus , internal::ISdBus *sdbus
, const internal::IConnection *connection
, adopt_message_t) noexcept , adopt_message_t) noexcept
: Message(msg, sdbus, adopt_message) : Message(msg, sdbus, adopt_message)
, connection_(connection)
{ {
assert(connection_ != nullptr);
} }
void MethodCall::dontExpectReply() void MethodCall::dontExpectReply()
@ -778,7 +806,7 @@ MethodReply MethodCall::sendWithReply(uint64_t timeout) const
auto r = sdbus_->sd_bus_call(nullptr, (sd_bus_message*)msg_, timeout, &sdbusError, &sdbusReply); auto r = sdbus_->sd_bus_call(nullptr, (sd_bus_message*)msg_, timeout, &sdbusError, &sdbusReply);
if (sd_bus_error_is_set(&sdbusError)) if (sd_bus_error_is_set(&sdbusError))
throw sdbus::Error(sdbusError.name, sdbusError.message); throw Error(Error::Name{sdbusError.name}, sdbusError.message);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to call method", -r); SDBUS_THROW_ERROR_IF(r < 0, "Failed to call method", -r);
@ -793,32 +821,13 @@ MethodReply MethodCall::sendWithNoReply() const
return Factory::create<MethodReply>(); // No reply return Factory::create<MethodReply>(); // No reply
} }
void MethodCall::send(void* callback, void* userData, uint64_t timeout, dont_request_slot_t) const Slot MethodCall::send(void* callback, void* userData, uint64_t timeout, return_slot_t) const
{
MethodCall::send(callback, userData, timeout, floating_slot);
}
void MethodCall::send(void* callback, void* userData, uint64_t timeout, floating_slot_t) const
{
auto r = sdbus_->sd_bus_call_async(nullptr, nullptr, (sd_bus_message*)msg_, (sd_bus_message_handler_t)callback, userData, timeout);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to call method", -r);
// Force event loop to re-enter polling with the async call timeout if that is less than the one used in current poll
SDBUS_THROW_ERROR_IF(connection_ == nullptr, "Invalid use of MethodCall API", ENOTSUP);
connection_->notifyEventLoopNewTimeout();
}
Slot MethodCall::send(void* callback, void* userData, uint64_t timeout) const
{ {
sd_bus_slot* slot; sd_bus_slot* slot;
auto r = sdbus_->sd_bus_call_async(nullptr, &slot, (sd_bus_message*)msg_, (sd_bus_message_handler_t)callback, userData, timeout); auto r = sdbus_->sd_bus_call_async(nullptr, &slot, (sd_bus_message*)msg_, (sd_bus_message_handler_t)callback, userData, timeout);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to call method asynchronously", -r); SDBUS_THROW_ERROR_IF(r < 0, "Failed to call method asynchronously", -r);
// Force event loop to re-enter polling with the async call timeout if that is less than the one used in current poll
SDBUS_THROW_ERROR_IF(connection_ == nullptr, "Invalid use of MethodCall API", ENOTSUP);
connection_->notifyEventLoopNewTimeout();
return {slot, [sdbus_ = sdbus_](void *slot){ sdbus_->sd_bus_slot_unref((sd_bus_slot*)slot); }}; return {slot, [sdbus_ = sdbus_](void *slot){ sdbus_->sd_bus_slot_unref((sd_bus_slot*)slot); }};
} }
@ -858,19 +867,70 @@ void Signal::send() const
void Signal::setDestination(const std::string& destination) void Signal::setDestination(const std::string& destination)
{ {
auto r = sdbus_->sd_bus_message_set_destination((sd_bus_message*)msg_, destination.c_str()); return setDestination(destination.c_str());
}
void Signal::setDestination(const char* destination)
{
auto r = sdbus_->sd_bus_message_set_destination((sd_bus_message*)msg_, destination);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to set signal destination", -r); SDBUS_THROW_ERROR_IF(r < 0, "Failed to set signal destination", -r);
} }
namespace {
// Pseudo-connection lifetime handling. In standard cases, we could do with simply function-local static pseudo
// connection instance below. However, it may happen that client's sdbus-c++ objects outlive this static connection
// instance (because they are used in global application objects that were created before this connection, and thus
// are destroyed later). This by itself sounds like a smell in client's application design, but it is downright bad
// in sdbus-c++ because it has no control over when client's dependent statics get destroyed. A "Phoenix" pattern
// (see Modern C++ Design - Generic Programming and Design Patterns Applied, by Andrei Alexandrescu) is applied to fix
// this by re-creating the connection again in such cases and keeping it alive until the next exit handler is invoked.
// Please note that the solution is NOT thread-safe.
// Another common solution is global sdbus-c++ startup/shutdown functions, but that would be an intrusive change.
#ifdef __cpp_constinit
constinit static bool pseudoConnectionDestroyed{};
#else
static bool pseudoConnectionDestroyed{};
#endif
std::unique_ptr<sdbus::internal::IConnection, void(*)(sdbus::internal::IConnection*)> createPseudoConnection()
{
auto deleter = [](sdbus::internal::IConnection* con)
{
delete con;
pseudoConnectionDestroyed = true;
};
return {internal::createPseudoConnection().release(), std::move(deleter)};
}
sdbus::internal::IConnection& getPseudoConnectionInstance()
{
static auto connection = createPseudoConnection();
if (pseudoConnectionDestroyed)
{
connection = createPseudoConnection(); // Phoenix rising from the ashes
atexit([](){ connection.~unique_ptr(); }); // We have to manually take care of deleting the phoenix
pseudoConnectionDestroyed = false;
}
assert(connection != nullptr);
return *connection;
}
}
PlainMessage createPlainMessage() PlainMessage createPlainMessage()
{ {
//static auto connection = internal::createConnection();
// Let's create a pseudo connection -- one that does not really connect to the real bus. // Let's create a pseudo connection -- one that does not really connect to the real bus.
// This is a bit of a hack, but it enables use to work with D-Bus message locally without // This is a bit of a hack, but it enables use to work with D-Bus message locally without
// the need of D-Bus daemon. This is especially useful in unit tests of both sdbus-c++ and client code. // the need of D-Bus daemon. This is especially useful in unit tests of both sdbus-c++ and client code.
// Additionally, it's light-weight and fast solution. // Additionally, it's light-weight and fast solution.
static auto connection = internal::createPseudoConnection(); auto& connection = getPseudoConnectionInstance();
return connection->createPlainMessage(); return connection.createPlainMessage();
} }
} }

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file MessageUtils.h * @file MessageUtils.h
* *
@ -57,12 +57,6 @@ namespace sdbus
{ {
return _Msg{msg, sdbus, adopt_message}; return _Msg{msg, sdbus, adopt_message};
} }
template<typename _Msg>
static _Msg create(void *msg, internal::ISdBus* sdbus, const internal::IConnection* connection, adopt_message_t)
{
return _Msg{msg, sdbus, connection, adopt_message};
}
}; };
PlainMessage createPlainMessage(); PlainMessage createPlainMessage();

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file Object.cpp * @file Object.cpp
* *
@ -25,159 +25,74 @@
*/ */
#include "Object.h" #include "Object.h"
#include "MessageUtils.h"
#include <sdbus-c++/IConnection.h> #include "sdbus-c++/Error.h"
#include <sdbus-c++/Message.h> #include "sdbus-c++/Flags.h"
#include <sdbus-c++/Error.h> #include "sdbus-c++/IConnection.h"
#include <sdbus-c++/MethodResult.h> #include "sdbus-c++/Message.h"
#include <sdbus-c++/Flags.h>
#include "ScopeGuard.h"
#include "IConnection.h" #include "IConnection.h"
#include "MessageUtils.h"
#include "ScopeGuard.h"
#include "Utils.h" #include "Utils.h"
#include "VTableUtils.h" #include "VTableUtils.h"
#include <systemd/sd-bus.h>
#include <utility>
#include <cassert> #include <cassert>
#include SDBUS_HEADER
#include <utility>
namespace sdbus::internal { namespace sdbus::internal {
Object::Object(sdbus::internal::IConnection& connection, std::string objectPath) Object::Object(sdbus::internal::IConnection& connection, ObjectPath objectPath)
: connection_(connection), objectPath_(std::move(objectPath)) : connection_(connection), objectPath_(std::move(objectPath))
{ {
SDBUS_CHECK_OBJECT_PATH(objectPath_); SDBUS_CHECK_OBJECT_PATH(objectPath_.c_str());
} }
void Object::registerMethod( const std::string& interfaceName void Object::addVTable(InterfaceName interfaceName, std::vector<VTableItem> vtable)
, std::string methodName
, std::string inputSignature
, std::string outputSignature
, method_callback methodCallback
, Flags flags )
{ {
registerMethod( interfaceName auto slot = Object::addVTable(std::move(interfaceName), std::move(vtable), return_slot);
, std::move(methodName)
, std::move(inputSignature) vtables_.push_back(std::move(slot));
, {}
, std::move(outputSignature)
, {}
, std::move(methodCallback)
, std::move(flags) );
} }
void Object::registerMethod( const std::string& interfaceName Slot Object::addVTable(InterfaceName interfaceName, std::vector<VTableItem> vtable, return_slot_t)
, std::string methodName
, std::string inputSignature
, const std::vector<std::string>& inputNames
, std::string outputSignature
, const std::vector<std::string>& outputNames
, method_callback methodCallback
, Flags flags )
{ {
SDBUS_CHECK_INTERFACE_NAME(interfaceName); SDBUS_CHECK_INTERFACE_NAME(interfaceName.c_str());
SDBUS_CHECK_MEMBER_NAME(methodName);
SDBUS_THROW_ERROR_IF(!methodCallback, "Invalid method callback provided", EINVAL);
auto& interface = getInterface(interfaceName); // 1st pass -- create vtable structure for internal sdbus-c++ purposes
InterfaceData::MethodData methodData{ std::move(inputSignature) auto internalVTable = std::make_unique<VTable>(createInternalVTable(std::move(interfaceName), std::move(vtable)));
, std::move(outputSignature)
, paramNamesToString(inputNames) + paramNamesToString(outputNames)
, std::move(methodCallback)
, std::move(flags) };
auto inserted = interface.methods.emplace(std::move(methodName), std::move(methodData)).second;
SDBUS_THROW_ERROR_IF(!inserted, "Failed to register method: method already exists", EINVAL); // 2nd pass -- from internal sdbus-c++ vtable, create vtable structure in format expected by underlying sd-bus library
} internalVTable->sdbusVTable = createInternalSdBusVTable(*internalVTable);
void Object::registerSignal( const std::string& interfaceName // 3rd step -- register the vtable with sd-bus
, std::string signalName internalVTable->slot = connection_.addObjectVTable( objectPath_
, std::string signature , internalVTable->interfaceName
, Flags flags ) , &internalVTable->sdbusVTable[0]
{ , internalVTable.get()
registerSignal(interfaceName, std::move(signalName), std::move(signature), {}, std::move(flags)); , return_slot );
}
void Object::registerSignal( const std::string& interfaceName // Return vtable wrapped in a Slot object
, std::string signalName return {internalVTable.release(), [](void *ptr){ delete static_cast<VTable*>(ptr); }};
, std::string signature
, const std::vector<std::string>& paramNames
, Flags flags )
{
SDBUS_CHECK_INTERFACE_NAME(interfaceName);
SDBUS_CHECK_MEMBER_NAME(signalName);
auto& interface = getInterface(interfaceName);
InterfaceData::SignalData signalData{std::move(signature), paramNamesToString(paramNames), std::move(flags)};
auto inserted = interface.signals.emplace(std::move(signalName), std::move(signalData)).second;
SDBUS_THROW_ERROR_IF(!inserted, "Failed to register signal: signal already exists", EINVAL);
}
void Object::registerProperty( const std::string& interfaceName
, std::string propertyName
, std::string signature
, property_get_callback getCallback
, Flags flags )
{
registerProperty( interfaceName
, std::move(propertyName)
, std::move(signature)
, std::move(getCallback)
, {}
, std::move(flags) );
}
void Object::registerProperty( const std::string& interfaceName
, std::string propertyName
, std::string signature
, property_get_callback getCallback
, property_set_callback setCallback
, Flags flags )
{
SDBUS_CHECK_INTERFACE_NAME(interfaceName);
SDBUS_CHECK_MEMBER_NAME(propertyName);
SDBUS_THROW_ERROR_IF(!getCallback && !setCallback, "Invalid property callbacks provided", EINVAL);
auto& interface = getInterface(interfaceName);
InterfaceData::PropertyData propertyData{ std::move(signature)
, std::move(getCallback)
, std::move(setCallback)
, std::move(flags) };
auto inserted = interface.properties.emplace(std::move(propertyName), std::move(propertyData)).second;
SDBUS_THROW_ERROR_IF(!inserted, "Failed to register property: property already exists", EINVAL);
}
void Object::setInterfaceFlags(const std::string& interfaceName, Flags flags)
{
auto& interface = getInterface(interfaceName);
interface.flags = flags;
}
void Object::finishRegistration()
{
for (auto& item : interfaces_)
{
const auto& interfaceName = item.first;
auto& interfaceData = item.second;
const auto& vtable = createInterfaceVTable(interfaceData);
activateInterfaceVTable(interfaceName, interfaceData, vtable);
}
} }
void Object::unregister() void Object::unregister()
{ {
interfaces_.clear(); vtables_.clear();
removeObjectManager(); objectManagerSlot_.reset();
} }
sdbus::Signal Object::createSignal(const std::string& interfaceName, const std::string& signalName) sdbus::Signal Object::createSignal(const InterfaceName& interfaceName, const SignalName& signalName)
{ {
return connection_.createSignal(objectPath_, interfaceName, signalName); return connection_.createSignal(objectPath_, interfaceName, signalName);
} }
sdbus::Signal Object::createSignal(const char* interfaceName, const char* signalName)
{
return connection_.createSignal(objectPath_.c_str(), interfaceName, signalName);
}
void Object::emitSignal(const sdbus::Signal& message) void Object::emitSignal(const sdbus::Signal& message)
{ {
SDBUS_THROW_ERROR_IF(!message.isValid(), "Invalid signal message provided", EINVAL); SDBUS_THROW_ERROR_IF(!message.isValid(), "Invalid signal message provided", EINVAL);
@ -185,12 +100,22 @@ void Object::emitSignal(const sdbus::Signal& message)
message.send(); message.send();
} }
void Object::emitPropertiesChangedSignal(const std::string& interfaceName, const std::vector<std::string>& propNames) void Object::emitPropertiesChangedSignal(const InterfaceName& interfaceName, const std::vector<PropertyName>& propNames)
{ {
connection_.emitPropertiesChangedSignal(objectPath_, interfaceName, propNames); connection_.emitPropertiesChangedSignal(objectPath_, interfaceName, propNames);
} }
void Object::emitPropertiesChangedSignal(const std::string& interfaceName) void Object::emitPropertiesChangedSignal(const char* interfaceName, const std::vector<PropertyName>& propNames)
{
connection_.emitPropertiesChangedSignal(objectPath_.c_str(), interfaceName, propNames);
}
void Object::emitPropertiesChangedSignal(const InterfaceName& interfaceName)
{
Object::emitPropertiesChangedSignal(interfaceName, {});
}
void Object::emitPropertiesChangedSignal(const char* interfaceName)
{ {
Object::emitPropertiesChangedSignal(interfaceName, {}); Object::emitPropertiesChangedSignal(interfaceName, {});
} }
@ -200,7 +125,7 @@ void Object::emitInterfacesAddedSignal()
connection_.emitInterfacesAddedSignal(objectPath_); connection_.emitInterfacesAddedSignal(objectPath_);
} }
void Object::emitInterfacesAddedSignal(const std::vector<std::string>& interfaces) void Object::emitInterfacesAddedSignal(const std::vector<InterfaceName>& interfaces)
{ {
connection_.emitInterfacesAddedSignal(objectPath_, interfaces); connection_.emitInterfacesAddedSignal(objectPath_, interfaces);
} }
@ -210,24 +135,19 @@ void Object::emitInterfacesRemovedSignal()
connection_.emitInterfacesRemovedSignal(objectPath_); connection_.emitInterfacesRemovedSignal(objectPath_);
} }
void Object::emitInterfacesRemovedSignal(const std::vector<std::string>& interfaces) void Object::emitInterfacesRemovedSignal(const std::vector<InterfaceName>& interfaces)
{ {
connection_.emitInterfacesRemovedSignal(objectPath_, interfaces); connection_.emitInterfacesRemovedSignal(objectPath_, interfaces);
} }
void Object::addObjectManager() void Object::addObjectManager()
{ {
objectManagerSlot_ = connection_.addObjectManager(objectPath_, request_slot); objectManagerSlot_ = connection_.addObjectManager(objectPath_, return_slot);
} }
void Object::removeObjectManager() Slot Object::addObjectManager(return_slot_t)
{ {
objectManagerSlot_.reset(); return connection_.addObjectManager(objectPath_, return_slot);
}
bool Object::hasObjectManager() const
{
return objectManagerSlot_ != nullptr;
} }
sdbus::IConnection& Object::getConnection() const sdbus::IConnection& Object::getConnection() const
@ -235,91 +155,161 @@ sdbus::IConnection& Object::getConnection() const
return connection_; return connection_;
} }
const std::string& Object::getObjectPath() const const ObjectPath& Object::getObjectPath() const
{ {
return objectPath_; return objectPath_;
} }
const Message* Object::getCurrentlyProcessedMessage() const Message Object::getCurrentlyProcessedMessage() const
{ {
return m_CurrentlyProcessedMessage.load(std::memory_order_relaxed); return connection_.getCurrentlyProcessedMessage();
} }
Object::InterfaceData& Object::getInterface(const std::string& interfaceName) Object::VTable Object::createInternalVTable(InterfaceName interfaceName, std::vector<VTableItem> vtable)
{ {
return interfaces_.emplace(interfaceName, *this).first->second; VTable internalVTable;
}
const std::vector<sd_bus_vtable>& Object::createInterfaceVTable(InterfaceData& interfaceData) internalVTable.interfaceName = std::move(interfaceName);
{
auto& vtable = interfaceData.vtable;
assert(vtable.empty());
vtable.push_back(createVTableStartItem(interfaceData.flags.toSdBusInterfaceFlags())); for (auto& vtableItem : vtable)
registerMethodsToVTable(interfaceData, vtable);
registerSignalsToVTable(interfaceData, vtable);
registerPropertiesToVTable(interfaceData, vtable);
vtable.push_back(createVTableEndItem());
return vtable;
}
void Object::registerMethodsToVTable(const InterfaceData& interfaceData, std::vector<sd_bus_vtable>& vtable)
{
for (const auto& item : interfaceData.methods)
{ {
const auto& methodName = item.first; std::visit( overload{ [&](InterfaceFlagsVTableItem&& interfaceFlags){ writeInterfaceFlagsToVTable(std::move(interfaceFlags), internalVTable); }
const auto& methodData = item.second; , [&](MethodVTableItem&& method){ writeMethodRecordToVTable(std::move(method), internalVTable); }
, [&](SignalVTableItem&& signal){ writeSignalRecordToVTable(std::move(signal), internalVTable); }
vtable.push_back(createVTableMethodItem( methodName.c_str() , [&](PropertyVTableItem&& property){ writePropertyRecordToVTable(std::move(property), internalVTable); } }
, methodData.inputArgs.c_str() , std::move(vtableItem) );
, methodData.outputArgs.c_str()
, methodData.paramNames.c_str()
, &Object::sdbus_method_callback
, methodData.flags.toSdBusMethodFlags() ));
} }
// Sort arrays so we can do fast searching for an item in sd-bus callback handlers
std::sort(internalVTable.methods.begin(), internalVTable.methods.end(), [](const auto& a, const auto& b){ return a.name < b.name; });
std::sort(internalVTable.signals.begin(), internalVTable.signals.end(), [](const auto& a, const auto& b){ return a.name < b.name; });
std::sort(internalVTable.properties.begin(), internalVTable.properties.end(), [](const auto& a, const auto& b){ return a.name < b.name; });
internalVTable.object = this;
return internalVTable;
} }
void Object::registerSignalsToVTable(const InterfaceData& interfaceData, std::vector<sd_bus_vtable>& vtable) void Object::writeInterfaceFlagsToVTable(InterfaceFlagsVTableItem flags, VTable& vtable)
{ {
for (const auto& item : interfaceData.signals) vtable.interfaceFlags = std::move(flags.flags);
}
void Object::writeMethodRecordToVTable(MethodVTableItem method, VTable& vtable)
{
SDBUS_CHECK_MEMBER_NAME(method.name.c_str());
SDBUS_THROW_ERROR_IF(!method.callbackHandler, "Invalid method callback provided", EINVAL);
vtable.methods.push_back({ std::move(method.name)
, std::move(method.inputSignature)
, std::move(method.outputSignature)
, paramNamesToString(method.inputParamNames) + paramNamesToString(method.outputParamNames)
, std::move(method.callbackHandler)
, std::move(method.flags) });
}
void Object::writeSignalRecordToVTable(SignalVTableItem signal, VTable& vtable)
{
SDBUS_CHECK_MEMBER_NAME(signal.name.c_str());
vtable.signals.push_back({ std::move(signal.name)
, std::move(signal.signature)
, paramNamesToString(signal.paramNames)
, std::move(signal.flags) });
}
void Object::writePropertyRecordToVTable(PropertyVTableItem property, VTable& vtable)
{
SDBUS_CHECK_MEMBER_NAME(property.name.c_str());
SDBUS_THROW_ERROR_IF(!property.getter && !property.setter, "Invalid property callbacks provided", EINVAL);
vtable.properties.push_back({ std::move(property.name)
, std::move(property.signature)
, std::move(property.getter)
, std::move(property.setter)
, std::move(property.flags) });
}
std::vector<sd_bus_vtable> Object::createInternalSdBusVTable(const VTable& vtable)
{
std::vector<sd_bus_vtable> sdbusVTable;
startSdBusVTable(vtable.interfaceFlags, sdbusVTable);
for (const auto& methodItem : vtable.methods)
writeMethodRecordToSdBusVTable(methodItem, sdbusVTable);
for (const auto& signalItem : vtable.signals)
writeSignalRecordToSdBusVTable(signalItem, sdbusVTable);
for (const auto& propertyItem : vtable.properties)
writePropertyRecordToSdBusVTable(propertyItem, sdbusVTable);
finalizeSdBusVTable(sdbusVTable);
return sdbusVTable;
}
void Object::startSdBusVTable(const Flags& interfaceFlags, std::vector<sd_bus_vtable>& vtable)
{
auto vtableItem = createSdBusVTableStartItem(interfaceFlags.toSdBusInterfaceFlags());
vtable.push_back(std::move(vtableItem));
}
void Object::writeMethodRecordToSdBusVTable(const VTable::MethodItem& method, std::vector<sd_bus_vtable>& vtable)
{
auto vtableItem = createSdBusVTableMethodItem( method.name.c_str()
, method.inputSignature.c_str()
, method.outputSignature.c_str()
, method.paramNames.c_str()
, &Object::sdbus_method_callback
, method.flags.toSdBusMethodFlags() );
vtable.push_back(std::move(vtableItem));
}
void Object::writeSignalRecordToSdBusVTable(const VTable::SignalItem& signal, std::vector<sd_bus_vtable>& vtable)
{
auto vtableItem = createSdBusVTableSignalItem( signal.name.c_str()
, signal.signature.c_str()
, signal.paramNames.c_str()
, signal.flags.toSdBusSignalFlags() );
vtable.push_back(std::move(vtableItem));
}
void Object::writePropertyRecordToSdBusVTable(const VTable::PropertyItem& property, std::vector<sd_bus_vtable>& vtable)
{
auto vtableItem = !property.setCallback
? createSdBusVTableReadOnlyPropertyItem( property.name.c_str()
, property.signature.c_str()
, &Object::sdbus_property_get_callback
, property.flags.toSdBusPropertyFlags() )
: createSdBusVTableWritablePropertyItem( property.name.c_str()
, property.signature.c_str()
, &Object::sdbus_property_get_callback
, &Object::sdbus_property_set_callback
, property.flags.toSdBusWritablePropertyFlags() );
vtable.push_back(std::move(vtableItem));
}
void Object::finalizeSdBusVTable(std::vector<sd_bus_vtable>& vtable)
{
vtable.push_back(createSdBusVTableEndItem());
}
const Object::VTable::MethodItem* Object::findMethod(const VTable& vtable, std::string_view methodName)
{
auto it = std::lower_bound(vtable.methods.begin(), vtable.methods.end(), methodName, [](const auto& methodItem, const auto& methodName)
{ {
const auto& signalName = item.first; return methodItem.name < methodName;
const auto& signalData = item.second; });
vtable.push_back(createVTableSignalItem( signalName.c_str() return it != vtable.methods.end() && it->name == methodName ? &*it : nullptr;
, signalData.signature.c_str()
, signalData.paramNames.c_str()
, signalData.flags.toSdBusSignalFlags() ));
}
} }
void Object::registerPropertiesToVTable(const InterfaceData& interfaceData, std::vector<sd_bus_vtable>& vtable) const Object::VTable::PropertyItem* Object::findProperty(const VTable& vtable, std::string_view propertyName)
{ {
for (const auto& item : interfaceData.properties) auto it = std::lower_bound(vtable.properties.begin(), vtable.properties.end(), propertyName, [](const auto& propertyItem, const auto& propertyName)
{ {
const auto& propertyName = item.first; return propertyItem.name < propertyName;
const auto& propertyData = item.second; });
if (!propertyData.setCallback) return it != vtable.properties.end() && it->name == propertyName ? &*it : nullptr;
vtable.push_back(createVTablePropertyItem( propertyName.c_str()
, propertyData.signature.c_str()
, &Object::sdbus_property_get_callback
, propertyData.flags.toSdBusPropertyFlags() ));
else
vtable.push_back(createVTableWritablePropertyItem( propertyName.c_str()
, propertyData.signature.c_str()
, &Object::sdbus_property_get_callback
, &Object::sdbus_property_set_callback
, propertyData.flags.toSdBusWritablePropertyFlags() ));
}
}
void Object::activateInterfaceVTable( const std::string& interfaceName
, InterfaceData& interfaceData
, const std::vector<sd_bus_vtable>& vtable )
{
interfaceData.slot = connection_.addObjectVTable(objectPath_, interfaceName, &vtable[0], &interfaceData);
} }
std::string Object::paramNamesToString(const std::vector<std::string>& paramNames) std::string Object::paramNamesToString(const std::vector<std::string>& paramNames)
@ -332,31 +322,19 @@ std::string Object::paramNamesToString(const std::vector<std::string>& paramName
int Object::sdbus_method_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError) int Object::sdbus_method_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError)
{ {
auto* interfaceData = static_cast<InterfaceData*>(userData); auto* vtable = static_cast<VTable*>(userData);
assert(interfaceData != nullptr); assert(vtable != nullptr);
auto& object = interfaceData->object; assert(vtable->object != nullptr);
auto message = Message::Factory::create<MethodCall>(sdbusMessage, &object.connection_.getSdBusInterface()); auto message = Message::Factory::create<MethodCall>(sdbusMessage, &vtable->object->connection_.getSdBusInterface());
object.m_CurrentlyProcessedMessage.store(&message, std::memory_order_relaxed); const auto* methodItem = findMethod(*vtable, message.getMemberName());
SCOPE_EXIT assert(methodItem != nullptr);
{ assert(methodItem->callback);
object.m_CurrentlyProcessedMessage.store(nullptr, std::memory_order_relaxed);
};
auto& callback = interfaceData->methods[message.getMemberName()].callback; auto ok = invokeHandlerAndCatchErrors([&](){ methodItem->callback(std::move(message)); }, retError);
assert(callback);
try return ok ? 1 : -1;
{
callback(message);
}
catch (const Error& e)
{
sd_bus_error_set(retError, e.getName().c_str(), e.getMessage().c_str());
}
return 1;
} }
int Object::sdbus_property_get_callback( sd_bus */*bus*/ int Object::sdbus_property_get_callback( sd_bus */*bus*/
@ -367,30 +345,25 @@ int Object::sdbus_property_get_callback( sd_bus */*bus*/
, void *userData , void *userData
, sd_bus_error *retError ) , sd_bus_error *retError )
{ {
auto* interfaceData = static_cast<InterfaceData*>(userData); auto* vtable = static_cast<VTable*>(userData);
assert(interfaceData != nullptr); assert(vtable != nullptr);
auto& object = interfaceData->object; assert(vtable->object != nullptr);
auto& callback = interfaceData->properties[property].getCallback; const auto* propertyItem = findProperty(*vtable, property);
// Getter can be empty - the case of "write-only" property assert(propertyItem != nullptr);
if (!callback)
// Getter may be empty - the case of "write-only" property
if (!propertyItem->getCallback)
{ {
sd_bus_error_set(retError, "org.freedesktop.DBus.Error.Failed", "Cannot read property as it is write-only"); sd_bus_error_set(retError, "org.freedesktop.DBus.Error.Failed", "Cannot read property as it is write-only");
return 1; return 1;
} }
auto reply = Message::Factory::create<PropertyGetReply>(sdbusReply, &object.connection_.getSdBusInterface()); auto reply = Message::Factory::create<PropertyGetReply>(sdbusReply, &vtable->object->connection_.getSdBusInterface());
try auto ok = invokeHandlerAndCatchErrors([&](){ propertyItem->getCallback(reply); }, retError);
{
callback(reply);
}
catch (const Error& e)
{
sd_bus_error_set(retError, e.getName().c_str(), e.getMessage().c_str());
}
return 1; return ok ? 1 : -1;
} }
int Object::sdbus_property_set_callback( sd_bus */*bus*/ int Object::sdbus_property_set_callback( sd_bus */*bus*/
@ -401,38 +374,26 @@ int Object::sdbus_property_set_callback( sd_bus */*bus*/
, void *userData , void *userData
, sd_bus_error *retError ) , sd_bus_error *retError )
{ {
auto* interfaceData = static_cast<InterfaceData*>(userData); auto* vtable = static_cast<VTable*>(userData);
assert(interfaceData != nullptr); assert(vtable != nullptr);
auto& object = interfaceData->object; assert(vtable->object != nullptr);
auto& callback = interfaceData->properties[property].setCallback; const auto* propertyItem = findProperty(*vtable, property);
assert(callback); assert(propertyItem != nullptr);
assert(propertyItem->setCallback);
auto value = Message::Factory::create<PropertySetCall>(sdbusValue, &object.connection_.getSdBusInterface()); auto value = Message::Factory::create<PropertySetCall>(sdbusValue, &vtable->object->connection_.getSdBusInterface());
object.m_CurrentlyProcessedMessage.store(&value, std::memory_order_relaxed); auto ok = invokeHandlerAndCatchErrors([&](){ propertyItem->setCallback(std::move(value)); }, retError);
SCOPE_EXIT
{
object.m_CurrentlyProcessedMessage.store(nullptr, std::memory_order_relaxed);
};
try return ok ? 1 : -1;
{
callback(value);
}
catch (const Error& e)
{
sd_bus_error_set(retError, e.getName().c_str(), e.getMessage().c_str());
}
return 1;
} }
} }
namespace sdbus { namespace sdbus {
std::unique_ptr<sdbus::IObject> createObject(sdbus::IConnection& connection, std::string objectPath) std::unique_ptr<sdbus::IObject> createObject(sdbus::IConnection& connection, ObjectPath objectPath)
{ {
auto* sdbusConnection = dynamic_cast<sdbus::internal::IConnection*>(&connection); auto* sdbusConnection = dynamic_cast<sdbus::internal::IConnection*>(&connection);
SDBUS_THROW_ERROR_IF(!sdbusConnection, "Connection is not a real sdbus-c++ connection", EINVAL); SDBUS_THROW_ERROR_IF(!sdbusConnection, "Connection is not a real sdbus-c++ connection", EINVAL);

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file Object.h * @file Object.h
* *
@ -27,16 +27,19 @@
#ifndef SDBUS_CXX_INTERNAL_OBJECT_H_ #ifndef SDBUS_CXX_INTERNAL_OBJECT_H_
#define SDBUS_CXX_INTERNAL_OBJECT_H_ #define SDBUS_CXX_INTERNAL_OBJECT_H_
#include <sdbus-c++/IObject.h> #include "sdbus-c++/IObject.h"
#include "IConnection.h" #include "IConnection.h"
#include <systemd/sd-bus.h> #include "sdbus-c++/Types.h"
#include <string>
#include <map>
#include <vector>
#include <functional>
#include <memory>
#include <atomic>
#include <cassert> #include <cassert>
#include <functional>
#include <list>
#include <memory>
#include <string>
#include <string_view>
#include SDBUS_HEADER
#include <vector>
namespace sdbus::internal { namespace sdbus::internal {
@ -44,117 +47,100 @@ namespace sdbus::internal {
: public IObject : public IObject
{ {
public: public:
Object(sdbus::internal::IConnection& connection, std::string objectPath); Object(sdbus::internal::IConnection& connection, ObjectPath objectPath);
void registerMethod( const std::string& interfaceName void addVTable(InterfaceName interfaceName, std::vector<VTableItem> vtable) override;
, std::string methodName Slot addVTable(InterfaceName interfaceName, std::vector<VTableItem> vtable, return_slot_t) override;
, std::string inputSignature
, std::string outputSignature
, method_callback methodCallback
, Flags flags ) override;
void registerMethod( const std::string& interfaceName
, std::string methodName
, std::string inputSignature
, const std::vector<std::string>& inputNames
, std::string outputSignature
, const std::vector<std::string>& outputNames
, method_callback methodCallback
, Flags flags ) override;
void registerSignal( const std::string& interfaceName
, std::string signalName
, std::string signature
, Flags flags ) override;
void registerSignal( const std::string& interfaceName
, std::string signalName
, std::string signature
, const std::vector<std::string>& paramNames
, Flags flags ) override;
void registerProperty( const std::string& interfaceName
, std::string propertyName
, std::string signature
, property_get_callback getCallback
, Flags flags ) override;
void registerProperty( const std::string& interfaceName
, std::string propertyName
, std::string signature
, property_get_callback getCallback
, property_set_callback setCallback
, Flags flags ) override;
void setInterfaceFlags(const std::string& interfaceName, Flags flags) override;
void finishRegistration() override;
void unregister() override; void unregister() override;
sdbus::Signal createSignal(const std::string& interfaceName, const std::string& signalName) override; sdbus::Signal createSignal(const InterfaceName& interfaceName, const SignalName& signalName) override;
sdbus::Signal createSignal(const char* interfaceName, const char* signalName) override;
void emitSignal(const sdbus::Signal& message) override; void emitSignal(const sdbus::Signal& message) override;
void emitPropertiesChangedSignal(const std::string& interfaceName, const std::vector<std::string>& propNames) override; void emitPropertiesChangedSignal(const InterfaceName& interfaceName, const std::vector<PropertyName>& propNames) override;
void emitPropertiesChangedSignal(const std::string& interfaceName) override; void emitPropertiesChangedSignal(const char* interfaceName, const std::vector<PropertyName>& propNames) override;
void emitPropertiesChangedSignal(const InterfaceName& interfaceName) override;
void emitPropertiesChangedSignal(const char* interfaceName) override;
void emitInterfacesAddedSignal() override; void emitInterfacesAddedSignal() override;
void emitInterfacesAddedSignal(const std::vector<std::string>& interfaces) override; void emitInterfacesAddedSignal(const std::vector<InterfaceName>& interfaces) override;
void emitInterfacesRemovedSignal() override; void emitInterfacesRemovedSignal() override;
void emitInterfacesRemovedSignal(const std::vector<std::string>& interfaces) override; void emitInterfacesRemovedSignal(const std::vector<InterfaceName>& interfaces) override;
void addObjectManager() override; void addObjectManager() override;
void removeObjectManager() override; [[nodiscard]] Slot addObjectManager(return_slot_t) override;
bool hasObjectManager() const override;
sdbus::IConnection& getConnection() const override; [[nodiscard]] sdbus::IConnection& getConnection() const override;
const std::string& getObjectPath() const override; [[nodiscard]] const ObjectPath& getObjectPath() const override;
const Message* getCurrentlyProcessedMessage() const override; [[nodiscard]] Message getCurrentlyProcessedMessage() const override;
private: private:
using InterfaceName = std::string; // A vtable record comprising methods, signals, properties, flags.
struct InterfaceData // Once created, it cannot be modified. Only new vtables records can be added.
// An interface can have any number of vtables attached to it, not only one.
struct VTable
{ {
InterfaceData(Object& object) : object(object) {} InterfaceName interfaceName;
Flags interfaceFlags;
using MethodName = std::string; struct MethodItem
struct MethodData
{ {
const std::string inputArgs; MethodName name;
const std::string outputArgs; Signature inputSignature;
const std::string paramNames; Signature outputSignature;
std::string paramNames;
method_callback callback; method_callback callback;
Flags flags; Flags flags;
}; };
std::map<MethodName, MethodData> methods; // Array of method records sorted by method name
using SignalName = std::string; std::vector<MethodItem> methods;
struct SignalData
struct SignalItem
{ {
const std::string signature; SignalName name;
const std::string paramNames; Signature signature;
std::string paramNames;
Flags flags; Flags flags;
}; };
std::map<SignalName, SignalData> signals; // Array of signal records sorted by signal name
using PropertyName = std::string; std::vector<SignalItem> signals;
struct PropertyData
struct PropertyItem
{ {
const std::string signature; PropertyName name;
Signature signature;
property_get_callback getCallback; property_get_callback getCallback;
property_set_callback setCallback; property_set_callback setCallback;
Flags flags; Flags flags;
}; };
std::map<PropertyName, PropertyData> properties; // Array of signal records sorted by signal name
std::vector<sd_bus_vtable> vtable; std::vector<PropertyItem> properties;
Flags flags;
Object& object; // VTable structure in format required by sd-bus API
std::vector<sd_bus_vtable> sdbusVTable;
// Back-reference to the owning object from sd-bus callback handlers
Object* object{};
// This is intentionally the last member, because it must be destructed first, // This is intentionally the last member, because it must be destructed first,
// releasing callbacks above before the callbacks themselves are destructed. // releasing callbacks above before the callbacks themselves are destructed.
Slot slot; Slot slot;
}; };
InterfaceData& getInterface(const std::string& interfaceName); VTable createInternalVTable(InterfaceName interfaceName, std::vector<VTableItem> vtable);
static const std::vector<sd_bus_vtable>& createInterfaceVTable(InterfaceData& interfaceData); void writeInterfaceFlagsToVTable(InterfaceFlagsVTableItem flags, VTable& vtable);
static void registerMethodsToVTable(const InterfaceData& interfaceData, std::vector<sd_bus_vtable>& vtable); void writeMethodRecordToVTable(MethodVTableItem method, VTable& vtable);
static void registerSignalsToVTable(const InterfaceData& interfaceData, std::vector<sd_bus_vtable>& vtable); void writeSignalRecordToVTable(SignalVTableItem signal, VTable& vtable);
static void registerPropertiesToVTable(const InterfaceData& interfaceData, std::vector<sd_bus_vtable>& vtable); void writePropertyRecordToVTable(PropertyVTableItem property, VTable& vtable);
void activateInterfaceVTable( const std::string& interfaceName
, InterfaceData& interfaceData std::vector<sd_bus_vtable> createInternalSdBusVTable(const VTable& vtable);
, const std::vector<sd_bus_vtable>& vtable ); static void startSdBusVTable(const Flags& interfaceFlags, std::vector<sd_bus_vtable>& vtable);
static void writeMethodRecordToSdBusVTable(const VTable::MethodItem& method, std::vector<sd_bus_vtable>& vtable);
static void writeSignalRecordToSdBusVTable(const VTable::SignalItem& signal, std::vector<sd_bus_vtable>& vtable);
static void writePropertyRecordToSdBusVTable(const VTable::PropertyItem& property, std::vector<sd_bus_vtable>& vtable);
static void finalizeSdBusVTable(std::vector<sd_bus_vtable>& vtable);
static const VTable::MethodItem* findMethod(const VTable& vtable, std::string_view methodName);
static const VTable::PropertyItem* findProperty(const VTable& vtable, std::string_view propertyName);
static std::string paramNamesToString(const std::vector<std::string>& paramNames); static std::string paramNamesToString(const std::vector<std::string>& paramNames);
static int sdbus_method_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError); static int sdbus_method_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError);
@ -175,10 +161,9 @@ namespace sdbus::internal {
private: private:
sdbus::internal::IConnection& connection_; sdbus::internal::IConnection& connection_;
std::string objectPath_; ObjectPath objectPath_;
std::map<InterfaceName, InterfaceData> interfaces_; std::vector<Slot> vtables_;
Slot objectManagerSlot_; Slot objectManagerSlot_;
std::atomic<const Message*> m_CurrentlyProcessedMessage{nullptr};
}; };
} }

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file Proxy.cpp * @file Proxy.cpp
* *
@ -25,196 +25,208 @@
*/ */
#include "Proxy.h" #include "Proxy.h"
#include "sdbus-c++/Error.h"
#include "sdbus-c++/IConnection.h"
#include "sdbus-c++/Message.h"
#include "IConnection.h" #include "IConnection.h"
#include "MessageUtils.h" #include "MessageUtils.h"
#include "Utils.h"
#include "sdbus-c++/Message.h"
#include "sdbus-c++/IConnection.h"
#include "sdbus-c++/Error.h"
#include "ScopeGuard.h" #include "ScopeGuard.h"
#include <systemd/sd-bus.h> #include "Utils.h"
#include <cassert> #include <cassert>
#include <chrono> #include <cstring>
#include SDBUS_HEADER
#include <utility> #include <utility>
namespace sdbus::internal { namespace sdbus::internal {
Proxy::Proxy(sdbus::internal::IConnection& connection, std::string destination, std::string objectPath) Proxy::Proxy(sdbus::internal::IConnection& connection, ServiceName destination, ObjectPath objectPath)
: connection_(&connection, [](sdbus::internal::IConnection *){ /* Intentionally left empty */ }) : connection_(&connection, [](sdbus::internal::IConnection *){ /* Intentionally left empty */ })
, destination_(std::move(destination)) , destination_(std::move(destination))
, objectPath_(std::move(objectPath)) , objectPath_(std::move(objectPath))
{ {
SDBUS_CHECK_SERVICE_NAME(destination_); SDBUS_CHECK_SERVICE_NAME(destination_.c_str());
SDBUS_CHECK_OBJECT_PATH(objectPath_); SDBUS_CHECK_OBJECT_PATH(objectPath_.c_str());
// The connection is not ours only, it is owned and managed by the user and we just reference // The connection is not ours only, it is owned and managed by the user and we just reference
// it here, so we expect the client to manage the event loop upon this connection themselves. // it here, so we expect the client to manage the event loop upon this connection themselves.
} }
Proxy::Proxy( std::unique_ptr<sdbus::internal::IConnection>&& connection Proxy::Proxy( std::unique_ptr<sdbus::internal::IConnection>&& connection
, std::string destination , ServiceName destination
, std::string objectPath ) , ObjectPath objectPath )
: connection_(std::move(connection)) : connection_(std::move(connection))
, destination_(std::move(destination)) , destination_(std::move(destination))
, objectPath_(std::move(objectPath)) , objectPath_(std::move(objectPath))
{ {
SDBUS_CHECK_SERVICE_NAME(destination_); SDBUS_CHECK_SERVICE_NAME(destination_.c_str());
SDBUS_CHECK_OBJECT_PATH(objectPath_); SDBUS_CHECK_OBJECT_PATH(objectPath_.c_str());
// The connection is ours only, i.e. it's us who has to manage the event loop upon this connection, // The connection is ours only, i.e. it's us who has to manage the event loop upon this connection,
// in order that we get and process signals, async call replies, and other messages from D-Bus. // in order that we get and process signals, async call replies, and other messages from D-Bus.
connection_->enterEventLoopAsync(); connection_->enterEventLoopAsync();
} }
MethodCall Proxy::createMethodCall(const std::string& interfaceName, const std::string& methodName) Proxy::Proxy( std::unique_ptr<sdbus::internal::IConnection>&& connection
, ServiceName destination
, ObjectPath objectPath
, dont_run_event_loop_thread_t )
: connection_(std::move(connection))
, destination_(std::move(destination))
, objectPath_(std::move(objectPath))
{
SDBUS_CHECK_SERVICE_NAME(destination_.c_str());
SDBUS_CHECK_OBJECT_PATH(objectPath_.c_str());
// Even though the connection is ours only, we don't start an event loop thread.
// This proxy is meant to be created, used for simple synchronous D-Bus call(s) and then dismissed.
}
MethodCall Proxy::createMethodCall(const InterfaceName& interfaceName, const MethodName& methodName)
{ {
return connection_->createMethodCall(destination_, objectPath_, interfaceName, methodName); return connection_->createMethodCall(destination_, objectPath_, interfaceName, methodName);
} }
MethodReply Proxy::callMethod(const MethodCall& message, uint64_t timeout) MethodCall Proxy::createMethodCall(const char* interfaceName, const char* methodName)
{ {
// Sending method call synchronously is the only operation that blocks, waiting for the method return connection_->createMethodCall(destination_.c_str(), objectPath_.c_str(), interfaceName, methodName);
// reply message among the incoming messages on the sd-bus connection socket. But typically there
// already is somebody that generally handles incoming D-Bus messages -- the connection event loop
// running typically in its own thread. We have to avoid polling on socket from several threads.
// So we have to branch here: either we are within the context of the event loop thread, then we
// can send the message simply via sd_bus_call, which blocks. Or we are in another thread, then
// we can perform the send operation of the method call message from here (because that is thread-
// safe like other sd-bus API accesses), but the incoming reply we have to get through the event
// loop thread, because this is the only rightful listener on the sd-bus connection socket.
// So, technically, we use async means to wait here for reply received by the event loop thread.
SDBUS_THROW_ERROR_IF(!message.isValid(), "Invalid method call message provided", EINVAL);
// If we don't need to wait for any reply, we can send the message now irrespective of the context
if (message.doesntExpectReply())
return message.send(timeout);
// If we are in the context of event loop thread, we can send the D-Bus call synchronously
// and wait blockingly for the reply, because we are the exclusive listeners on the socket
auto reply = connection_->tryCallMethodSynchronously(message, timeout);
if (reply.isValid())
return reply;
// Otherwise we send the call asynchronously and do blocking wait for the reply from the event loop thread
return sendMethodCallMessageAndWaitForReply(message, timeout);
} }
PendingAsyncCall Proxy::callMethod(const MethodCall& message, async_reply_handler asyncReplyCallback, uint64_t timeout) MethodReply Proxy::callMethod(const MethodCall& message)
{
return Proxy::callMethod(message, /*timeout*/ 0);
}
MethodReply Proxy::callMethod(const MethodCall& message, uint64_t timeout)
{
SDBUS_THROW_ERROR_IF(!message.isValid(), "Invalid method call message provided", EINVAL);
return connection_->callMethod(message, timeout);
}
PendingAsyncCall Proxy::callMethodAsync(const MethodCall& message, async_reply_handler asyncReplyCallback)
{
return Proxy::callMethodAsync(message, std::move(asyncReplyCallback), /*timeout*/ 0);
}
Slot Proxy::callMethodAsync(const MethodCall& message, async_reply_handler asyncReplyCallback, return_slot_t)
{
return Proxy::callMethodAsync(message, std::move(asyncReplyCallback), /*timeout*/ 0, return_slot);
}
PendingAsyncCall Proxy::callMethodAsync(const MethodCall& message, async_reply_handler asyncReplyCallback, uint64_t timeout)
{ {
SDBUS_THROW_ERROR_IF(!message.isValid(), "Invalid async method call message provided", EINVAL); SDBUS_THROW_ERROR_IF(!message.isValid(), "Invalid async method call message provided", EINVAL);
auto callback = (void*)&Proxy::sdbus_async_reply_handler; auto asyncCallInfo = std::make_shared<AsyncCallInfo>(AsyncCallInfo{ .callback = std::move(asyncReplyCallback)
auto callData = std::make_shared<AsyncCalls::CallData>(AsyncCalls::CallData{*this, std::move(asyncReplyCallback), {}}); , .proxy = *this
auto weakData = std::weak_ptr<AsyncCalls::CallData>{callData}; , .floating = false });
callData->slot = message.send(callback, callData.get(), timeout); asyncCallInfo->slot = connection_->callMethod( message
, (void*)&Proxy::sdbus_async_reply_handler
, asyncCallInfo.get()
, timeout
, return_slot );
auto slotPtr = callData->slot.get(); auto asyncCallInfoWeakPtr = std::weak_ptr{asyncCallInfo};
pendingAsyncCalls_.addCall(slotPtr, std::move(callData));
return {weakData}; floatingAsyncCallSlots_.push_back(std::move(asyncCallInfo));
return {asyncCallInfoWeakPtr};
} }
MethodReply Proxy::sendMethodCallMessageAndWaitForReply(const MethodCall& message, uint64_t timeout) Slot Proxy::callMethodAsync(const MethodCall& message, async_reply_handler asyncReplyCallback, uint64_t timeout, return_slot_t)
{ {
/*thread_local*/ SyncCallReplyData syncCallReplyData; SDBUS_THROW_ERROR_IF(!message.isValid(), "Invalid async method call message provided", EINVAL);
async_reply_handler asyncReplyCallback = [&syncCallReplyData](MethodReply& reply, const Error* error) auto asyncCallInfo = std::make_unique<AsyncCallInfo>(AsyncCallInfo{ .callback = std::move(asyncReplyCallback)
, .proxy = *this
, .floating = true });
asyncCallInfo->slot = connection_->callMethod( message
, (void*)&Proxy::sdbus_async_reply_handler
, asyncCallInfo.get()
, timeout
, return_slot );
return {asyncCallInfo.release(), [](void *ptr){ delete static_cast<AsyncCallInfo*>(ptr); }};
}
std::future<MethodReply> Proxy::callMethodAsync(const MethodCall& message, with_future_t)
{
return Proxy::callMethodAsync(message, {}, with_future);
}
std::future<MethodReply> Proxy::callMethodAsync(const MethodCall& message, uint64_t timeout, with_future_t)
{
auto promise = std::make_shared<std::promise<MethodReply>>();
auto future = promise->get_future();
async_reply_handler asyncReplyCallback = [promise = std::move(promise)](MethodReply reply, std::optional<Error> error) noexcept
{ {
syncCallReplyData.sendMethodReplyToWaitingThread(reply, error); if (!error)
promise->set_value(std::move(reply));
else
promise->set_exception(std::make_exception_ptr(*std::move(error)));
}; };
auto callback = (void*)&Proxy::sdbus_async_reply_handler;
AsyncCalls::CallData callData{*this, std::move(asyncReplyCallback), {}};
message.send(callback, &callData, timeout, floating_slot); (void)Proxy::callMethodAsync(message, std::move(asyncReplyCallback), timeout);
return syncCallReplyData.waitForMethodReply(); return future;
} }
void Proxy::SyncCallReplyData::sendMethodReplyToWaitingThread(MethodReply& reply, const Error* error) void Proxy::registerSignalHandler( const InterfaceName& interfaceName
{ , const SignalName& signalName
std::unique_lock lock{mutex_};
SCOPE_EXIT{ cond_.notify_one(); }; // This must happen before unlocking the mutex to avoid potential data race on spurious wakeup in the waiting thread
SCOPE_EXIT{ arrived_ = true; };
//error_ = nullptr; // Necessary if SyncCallReplyData instance is thread_local
if (error == nullptr)
reply_ = std::move(reply);
else
error_ = std::make_unique<Error>(*error);
}
MethodReply Proxy::SyncCallReplyData::waitForMethodReply()
{
std::unique_lock lock{mutex_};
cond_.wait(lock, [this](){ return arrived_; });
//arrived_ = false; // Necessary if SyncCallReplyData instance is thread_local
if (error_)
throw *error_;
return std::move(reply_);
}
void Proxy::registerSignalHandler( const std::string& interfaceName
, const std::string& signalName
, signal_handler signalHandler ) , signal_handler signalHandler )
{
Proxy::registerSignalHandler(interfaceName.c_str(), signalName.c_str(), std::move(signalHandler));
}
void Proxy::registerSignalHandler( const char* interfaceName
, const char* signalName
, signal_handler signalHandler )
{
auto slot = Proxy::registerSignalHandler(interfaceName, signalName, std::move(signalHandler), return_slot);
floatingSignalSlots_.push_back(std::move(slot));
}
Slot Proxy::registerSignalHandler( const InterfaceName& interfaceName
, const SignalName& signalName
, signal_handler signalHandler
, return_slot_t )
{
return Proxy::registerSignalHandler(interfaceName.c_str(), signalName.c_str(), std::move(signalHandler), return_slot);
}
Slot Proxy::registerSignalHandler( const char* interfaceName
, const char* signalName
, signal_handler signalHandler
, return_slot_t )
{ {
SDBUS_CHECK_INTERFACE_NAME(interfaceName); SDBUS_CHECK_INTERFACE_NAME(interfaceName);
SDBUS_CHECK_MEMBER_NAME(signalName); SDBUS_CHECK_MEMBER_NAME(signalName);
SDBUS_THROW_ERROR_IF(!signalHandler, "Invalid signal handler provided", EINVAL); SDBUS_THROW_ERROR_IF(!signalHandler, "Invalid signal handler provided", EINVAL);
auto& interface = interfaces_[interfaceName]; auto signalInfo = std::make_unique<SignalInfo>(SignalInfo{std::move(signalHandler), *this, {}});
auto signalData = std::make_unique<InterfaceData::SignalData>(*this, std::move(signalHandler), nullptr); signalInfo->slot = connection_->registerSignalHandler( destination_.c_str()
auto insertionResult = interface.signals_.emplace(signalName, std::move(signalData)); , objectPath_.c_str()
, interfaceName
, signalName
, &Proxy::sdbus_signal_handler
, signalInfo.get()
, return_slot );
auto inserted = insertionResult.second; return {signalInfo.release(), [](void *ptr){ delete static_cast<SignalInfo*>(ptr); }};
SDBUS_THROW_ERROR_IF(!inserted, "Failed to register signal handler: handler already exists", EINVAL);
}
void Proxy::unregisterSignalHandler( const std::string& interfaceName
, const std::string& signalName )
{
auto it = interfaces_.find(interfaceName);
if (it != interfaces_.end())
it->second.signals_.erase(signalName);
}
void Proxy::finishRegistration()
{
registerSignalHandlers(*connection_);
}
void Proxy::registerSignalHandlers(sdbus::internal::IConnection& connection)
{
for (auto& interfaceItem : interfaces_)
{
const auto& interfaceName = interfaceItem.first;
auto& signalsOnInterface = interfaceItem.second.signals_;
for (auto& signalItem : signalsOnInterface)
{
const auto& signalName = signalItem.first;
auto* signalData = signalItem.second.get();
signalData->slot = connection.registerSignalHandler( destination_
, objectPath_
, interfaceName
, signalName
, &Proxy::sdbus_signal_handler
, signalData);
}
}
} }
void Proxy::unregister() void Proxy::unregister()
{ {
pendingAsyncCalls_.clear(); floatingAsyncCallSlots_.clear();
interfaces_.clear(); floatingSignalSlots_.clear();
} }
sdbus::IConnection& Proxy::getConnection() const sdbus::IConnection& Proxy::getConnection() const
@ -222,104 +234,122 @@ sdbus::IConnection& Proxy::getConnection() const
return *connection_; return *connection_;
} }
const std::string& Proxy::getObjectPath() const const ObjectPath& Proxy::getObjectPath() const
{ {
return objectPath_; return objectPath_;
} }
const Message* Proxy::getCurrentlyProcessedMessage() const Message Proxy::getCurrentlyProcessedMessage() const
{ {
return m_CurrentlyProcessedMessage.load(std::memory_order_relaxed); return connection_->getCurrentlyProcessedMessage();
} }
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); auto* asyncCallInfo = static_cast<AsyncCallInfo*>(userData);
assert(asyncCallData != nullptr); assert(asyncCallInfo != nullptr);
assert(asyncCallData->callback); assert(asyncCallInfo->callback);
auto& proxy = asyncCallData->proxy; auto& proxy = asyncCallInfo->proxy;
auto slot = asyncCallData->slot.get();
// We are removing the CallData item at the complete scope exit, after the callback has been invoked. // 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) // 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. // is the synchronization point between callback invocation and Proxy::unregister.
SCOPE_EXIT 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.floatingAsyncCallSlots_.erase(asyncCallInfo);
if (slot)
proxy.pendingAsyncCalls_.removeCall(slot);
}; };
auto message = Message::Factory::create<MethodReply>(sdbusMessage, &proxy.connection_->getSdBusInterface()); auto message = Message::Factory::create<MethodReply>(sdbusMessage, &proxy.connection_->getSdBusInterface());
proxy.m_CurrentlyProcessedMessage.store(&message, std::memory_order_relaxed); auto ok = invokeHandlerAndCatchErrors([&]
SCOPE_EXIT
{
proxy.m_CurrentlyProcessedMessage.store(nullptr, std::memory_order_relaxed);
};
try
{ {
const auto* error = sd_bus_message_get_error(sdbusMessage); const auto* error = sd_bus_message_get_error(sdbusMessage);
if (error == nullptr) if (error == nullptr)
{ {
asyncCallData->callback(message, nullptr); asyncCallInfo->callback(std::move(message), {});
} }
else else
{ {
Error exception(error->name, error->message); Error exception(Error::Name{error->name}, error->message);
asyncCallData->callback(message, &exception); asyncCallInfo->callback(std::move(message), std::move(exception));
} }
} }, retError);
catch (const Error&)
{
// Intentionally left blank -- sdbus-c++ exceptions shall not bubble up to the underlying C sd-bus library
}
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); auto* signalInfo = static_cast<SignalInfo*>(userData);
assert(signalData != nullptr); assert(signalInfo != nullptr);
assert(signalData->callback); assert(signalInfo->callback);
auto message = Message::Factory::create<Signal>(sdbusMessage, &signalData->proxy.connection_->getSdBusInterface()); // TODO: Hide Message factory invocation under Connection API (tell, don't ask principle), then we can remove getSdBusInterface()
auto message = Message::Factory::create<Signal>(sdbusMessage, &signalInfo->proxy.connection_->getSdBusInterface());
signalData->proxy.m_CurrentlyProcessedMessage.store(&message, std::memory_order_relaxed); auto ok = invokeHandlerAndCatchErrors([&](){ signalInfo->callback(std::move(message)); }, retError);
SCOPE_EXIT
return ok ? 0 : -1;
}
Proxy::FloatingAsyncCallSlots::~FloatingAsyncCallSlots()
{
clear();
}
void Proxy::FloatingAsyncCallSlots::push_back(std::shared_ptr<AsyncCallInfo> asyncCallInfo)
{
std::lock_guard lock(mutex_);
if (!asyncCallInfo->finished) // The call may have finished in the meantime
slots_.emplace_back(std::move(asyncCallInfo));
}
void Proxy::FloatingAsyncCallSlots::erase(AsyncCallInfo* info)
{
std::unique_lock lock(mutex_);
info->finished = true;
auto it = std::find_if(slots_.begin(), slots_.end(), [info](auto const& entry){ return entry.get() == info; });
if (it != slots_.end())
{ {
signalData->proxy.m_CurrentlyProcessedMessage.store(nullptr, std::memory_order_relaxed); auto callInfo = std::move(*it);
}; slots_.erase(it);
lock.unlock();
try // Releasing call slot pointer acquires global sd-bus mutex. We have to perform the release
{ // out of the `mutex_' critical section here, because if the `removeCall` is called by some
signalData->callback(message); // thread and at the same time Proxy's async reply handler (which already holds global sd-bus
} // mutex) is in progress in a different thread, we get double-mutex deadlock.
catch (const Error&)
{
// Intentionally left blank -- sdbus-c++ exceptions shall not bubble up to the underlying C sd-bus library
} }
}
return 0; void Proxy::FloatingAsyncCallSlots::clear()
{
std::unique_lock lock(mutex_);
auto asyncCallSlots = std::move(slots_);
slots_ = {};
lock.unlock();
// Releasing call slot pointer acquires global sd-bus mutex. We have to perform the release
// out of the `mutex_' critical section here, because if the `clear` is called by some thread
// and at the same time Proxy's async reply handler (which already holds global sd-bus
// mutex) is in progress in a different thread, we get double-mutex deadlock.
} }
} }
namespace sdbus { namespace sdbus {
PendingAsyncCall::PendingAsyncCall(std::weak_ptr<void> callData) PendingAsyncCall::PendingAsyncCall(std::weak_ptr<void> callInfo)
: callData_(std::move(callData)) : callInfo_(std::move(callInfo))
{ {
} }
void PendingAsyncCall::cancel() void PendingAsyncCall::cancel()
{ {
if (auto ptr = callData_.lock(); ptr != nullptr) if (auto ptr = callInfo_.lock(); ptr != nullptr)
{ {
auto* callData = static_cast<internal::Proxy::AsyncCalls::CallData*>(ptr.get()); auto* asyncCallInfo = static_cast<internal::Proxy::AsyncCallInfo*>(ptr.get());
callData->proxy.pendingAsyncCalls_.removeCall(callData->slot.get()); asyncCallInfo->proxy.floatingAsyncCallSlots_.erase(asyncCallInfo);
// At this point, the callData item is being deleted, leading to the release of the // At this point, the callData item is being deleted, leading to the release of the
// sd-bus slot pointer. This release locks the global sd-bus mutex. If the async // sd-bus slot pointer. This release locks the global sd-bus mutex. If the async
@ -330,7 +360,7 @@ void PendingAsyncCall::cancel()
bool PendingAsyncCall::isPending() const bool PendingAsyncCall::isPending() const
{ {
return !callData_.expired(); return !callInfo_.expired();
} }
} }
@ -338,8 +368,8 @@ bool PendingAsyncCall::isPending() const
namespace sdbus { namespace sdbus {
std::unique_ptr<sdbus::IProxy> createProxy( IConnection& connection std::unique_ptr<sdbus::IProxy> createProxy( IConnection& connection
, std::string destination , ServiceName destination
, std::string objectPath ) , ObjectPath objectPath )
{ {
auto* sdbusConnection = dynamic_cast<sdbus::internal::IConnection*>(&connection); auto* sdbusConnection = dynamic_cast<sdbus::internal::IConnection*>(&connection);
SDBUS_THROW_ERROR_IF(!sdbusConnection, "Connection is not a real sdbus-c++ connection", EINVAL); SDBUS_THROW_ERROR_IF(!sdbusConnection, "Connection is not a real sdbus-c++ connection", EINVAL);
@ -350,8 +380,8 @@ std::unique_ptr<sdbus::IProxy> createProxy( IConnection& connection
} }
std::unique_ptr<sdbus::IProxy> createProxy( std::unique_ptr<IConnection>&& connection std::unique_ptr<sdbus::IProxy> createProxy( std::unique_ptr<IConnection>&& connection
, std::string destination , ServiceName destination
, std::string objectPath ) , ObjectPath objectPath )
{ {
auto* sdbusConnection = dynamic_cast<sdbus::internal::IConnection*>(connection.get()); auto* sdbusConnection = dynamic_cast<sdbus::internal::IConnection*>(connection.get());
SDBUS_THROW_ERROR_IF(!sdbusConnection, "Connection is not a real sdbus-c++ connection", EINVAL); SDBUS_THROW_ERROR_IF(!sdbusConnection, "Connection is not a real sdbus-c++ connection", EINVAL);
@ -363,10 +393,26 @@ std::unique_ptr<sdbus::IProxy> createProxy( std::unique_ptr<IConnection>&& conne
, std::move(objectPath) ); , std::move(objectPath) );
} }
std::unique_ptr<sdbus::IProxy> createProxy( std::string destination std::unique_ptr<sdbus::IProxy> createProxy( std::unique_ptr<IConnection>&& connection
, std::string objectPath ) , ServiceName destination
, ObjectPath objectPath
, dont_run_event_loop_thread_t )
{ {
auto connection = sdbus::createConnection(); auto* sdbusConnection = dynamic_cast<sdbus::internal::IConnection*>(connection.get());
SDBUS_THROW_ERROR_IF(!sdbusConnection, "Connection is not a real sdbus-c++ connection", EINVAL);
connection.release();
return std::make_unique<sdbus::internal::Proxy>( std::unique_ptr<sdbus::internal::IConnection>(sdbusConnection)
, std::move(destination)
, std::move(objectPath)
, dont_run_event_loop_thread );
}
std::unique_ptr<sdbus::IProxy> createProxy( ServiceName destination
, ObjectPath objectPath )
{
auto connection = sdbus::createBusConnection();
auto sdbusConnection = std::unique_ptr<sdbus::internal::IConnection>(dynamic_cast<sdbus::internal::IConnection*>(connection.release())); auto sdbusConnection = std::unique_ptr<sdbus::internal::IConnection>(dynamic_cast<sdbus::internal::IConnection*>(connection.release()));
assert(sdbusConnection != nullptr); assert(sdbusConnection != nullptr);
@ -376,4 +422,19 @@ std::unique_ptr<sdbus::IProxy> createProxy( std::string destination
, std::move(objectPath) ); , std::move(objectPath) );
} }
std::unique_ptr<sdbus::IProxy> createProxy( ServiceName destination
, ObjectPath objectPath
, dont_run_event_loop_thread_t )
{
auto connection = sdbus::createBusConnection();
auto sdbusConnection = std::unique_ptr<sdbus::internal::IConnection>(dynamic_cast<sdbus::internal::IConnection*>(connection.release()));
assert(sdbusConnection != nullptr);
return std::make_unique<sdbus::internal::Proxy>( std::move(sdbusConnection)
, std::move(destination)
, std::move(objectPath)
, dont_run_event_loop_thread );
}
} }

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file Proxy.h * @file Proxy.h
* *
@ -27,16 +27,17 @@
#ifndef SDBUS_CXX_INTERNAL_PROXY_H_ #ifndef SDBUS_CXX_INTERNAL_PROXY_H_
#define SDBUS_CXX_INTERNAL_PROXY_H_ #define SDBUS_CXX_INTERNAL_PROXY_H_
#include <sdbus-c++/IProxy.h> #include "sdbus-c++/IProxy.h"
#include "IConnection.h" #include "IConnection.h"
#include <systemd/sd-bus.h> #include "sdbus-c++/Types.h"
#include <string>
#include <deque>
#include <memory> #include <memory>
#include <map>
#include <unordered_map>
#include <mutex> #include <mutex>
#include <atomic> #include <string>
#include <condition_variable> #include SDBUS_HEADER
#include <vector>
namespace sdbus::internal { namespace sdbus::internal {
@ -45,48 +46,57 @@ namespace sdbus::internal {
{ {
public: public:
Proxy( sdbus::internal::IConnection& connection Proxy( sdbus::internal::IConnection& connection
, std::string destination , ServiceName destination
, std::string objectPath ); , ObjectPath objectPath );
Proxy( std::unique_ptr<sdbus::internal::IConnection>&& connection Proxy( std::unique_ptr<sdbus::internal::IConnection>&& connection
, std::string destination , ServiceName destination
, std::string objectPath ); , ObjectPath objectPath );
Proxy( std::unique_ptr<sdbus::internal::IConnection>&& connection
, ServiceName destination
, ObjectPath objectPath
, dont_run_event_loop_thread_t );
MethodCall createMethodCall(const std::string& interfaceName, const std::string& methodName) override; MethodCall createMethodCall(const InterfaceName& interfaceName, const MethodName& methodName) override;
MethodCall createMethodCall(const char* interfaceName, const char* methodName) override;
MethodReply callMethod(const MethodCall& message) override;
MethodReply callMethod(const MethodCall& message, uint64_t timeout) override; MethodReply callMethod(const MethodCall& message, uint64_t timeout) override;
PendingAsyncCall callMethod(const MethodCall& message, async_reply_handler asyncReplyCallback, uint64_t timeout) override; PendingAsyncCall callMethodAsync(const MethodCall& message, async_reply_handler asyncReplyCallback) override;
Slot callMethodAsync( const MethodCall& message
, async_reply_handler asyncReplyCallback
, return_slot_t ) override;
PendingAsyncCall callMethodAsync( const MethodCall& message
, async_reply_handler asyncReplyCallback
, uint64_t timeout ) override;
Slot callMethodAsync( const MethodCall& message
, async_reply_handler asyncReplyCallback
, uint64_t timeout
, return_slot_t ) override;
std::future<MethodReply> callMethodAsync(const MethodCall& message, with_future_t) override;
std::future<MethodReply> callMethodAsync(const MethodCall& message, uint64_t timeout, with_future_t) override;
void registerSignalHandler( const std::string& interfaceName void registerSignalHandler( const InterfaceName& interfaceName
, const std::string& signalName , const SignalName& signalName
, signal_handler signalHandler ) override; , signal_handler signalHandler ) override;
void unregisterSignalHandler( const std::string& interfaceName void registerSignalHandler( const char* interfaceName
, const std::string& signalName ) override; , const char* signalName
, signal_handler signalHandler ) override;
void finishRegistration() override; Slot registerSignalHandler( const InterfaceName& interfaceName
, const SignalName& signalName
, signal_handler signalHandler
, return_slot_t ) override;
Slot registerSignalHandler( const char* interfaceName
, const char* signalName
, signal_handler signalHandler
, return_slot_t ) override;
void unregister() override; void unregister() override;
sdbus::IConnection& getConnection() const override; [[nodiscard]] sdbus::IConnection& getConnection() const override;
const std::string& getObjectPath() const override; [[nodiscard]] const ObjectPath& getObjectPath() const override;
const Message* getCurrentlyProcessedMessage() const override; [[nodiscard]] Message getCurrentlyProcessedMessage() const override;
private: private:
class SyncCallReplyData
{
public:
void sendMethodReplyToWaitingThread(MethodReply& reply, const Error* error);
MethodReply waitForMethodReply();
private:
std::mutex mutex_;
std::condition_variable cond_;
bool arrived_{};
MethodReply reply_;
std::unique_ptr<Error> error_;
};
MethodReply sendMethodCallMessageAndWaitForReply(const MethodCall& message, uint64_t timeout);
void registerSignalHandlers(sdbus::internal::IConnection& connection);
static int sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError);
static int sdbus_signal_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError); static int sdbus_signal_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError);
static int sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError);
private: private:
friend PendingAsyncCall; friend PendingAsyncCall;
@ -94,92 +104,42 @@ namespace sdbus::internal {
std::unique_ptr< sdbus::internal::IConnection std::unique_ptr< sdbus::internal::IConnection
, std::function<void(sdbus::internal::IConnection*)> , std::function<void(sdbus::internal::IConnection*)>
> connection_; > connection_;
std::string destination_; ServiceName destination_;
std::string objectPath_; ObjectPath objectPath_;
using InterfaceName = std::string; std::vector<Slot> floatingSignalSlots_;
struct InterfaceData
struct SignalInfo
{ {
using SignalName = std::string; signal_handler callback;
struct SignalData Proxy& proxy;
{ Slot slot;
SignalData(Proxy& proxy, signal_handler callback, Slot slot)
: proxy(proxy)
, callback(std::move(callback))
, slot(std::move(slot))
{}
Proxy& proxy;
signal_handler callback;
// slot must be listed after callback to ensure that slot is destructed first.
// Destructing the slot will sd_bus_slot_unref() the callback.
// Only after sd_bus_slot_unref(), we can safely delete the callback. The bus mutex (SdBus::sdbusMutex_)
// ensures that sd_bus_slot_unref() and the callback execute sequentially.
Slot slot;
};
std::map<SignalName, std::unique_ptr<SignalData>> signals_;
}; };
std::map<InterfaceName, InterfaceData> interfaces_;
// We need to keep track of pending async calls. When the proxy is being destructed, we must struct AsyncCallInfo
// remove all slots of these pending calls, otherwise in case when the connection outlives {
// the proxy, we might get async reply handlers invoked for pending async calls after the proxy async_reply_handler callback;
// has been destroyed, which is a free ticket into the realm of undefined behavior. Proxy& proxy;
class AsyncCalls Slot slot{};
bool finished{false};
bool floating;
};
// Container keeping track of pending async calls
class FloatingAsyncCallSlots
{ {
public: public:
struct CallData ~FloatingAsyncCallSlots();
{ void push_back(std::shared_ptr<AsyncCallInfo> asyncCallInfo);
Proxy& proxy; void erase(AsyncCallInfo* info);
async_reply_handler callback; void clear();
Slot slot;
};
~AsyncCalls()
{
clear();
}
bool addCall(void* slot, std::shared_ptr<CallData> asyncCallData)
{
std::lock_guard lock(mutex_);
return calls_.emplace(slot, std::move(asyncCallData)).second;
}
void removeCall(void* slot)
{
std::unique_lock lock(mutex_);
if (auto it = calls_.find(slot); it != calls_.end())
{
auto callData = std::move(it->second);
calls_.erase(it);
lock.unlock();
// Releasing call slot pointer acquires global sd-bus mutex. We have to perform the release
// out of the `mutex_' critical section here, because if the `removeCall` is called by some
// thread and at the same time Proxy's async reply handler (which already holds global sd-bus
// mutex) is in progress in a different thread, we get double-mutex deadlock.
}
}
void clear()
{
std::unique_lock lock(mutex_);
auto asyncCallSlots = std::move(calls_);
calls_ = {};
lock.unlock();
// Releasing call slot pointer acquires global sd-bus mutex. We have to perform the release
// out of the `mutex_' critical section here, because if the `clear` is called by some thread
// and at the same time Proxy's async reply handler (which already holds global sd-bus
// mutex) is in progress in a different thread, we get double-mutex deadlock.
}
private: private:
std::mutex mutex_; std::mutex mutex_;
std::unordered_map<void*, std::shared_ptr<CallData>> calls_; std::deque<std::shared_ptr<AsyncCallInfo>> slots_;
} pendingAsyncCalls_; };
std::atomic<const Message*> m_CurrentlyProcessedMessage{nullptr}; FloatingAsyncCallSlots floatingAsyncCallSlots_;
}; };
} }

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file ScopeGuard.h * @file ScopeGuard.h
* *
@ -27,6 +27,7 @@
#ifndef SDBUS_CPP_INTERNAL_SCOPEGUARD_H_ #ifndef SDBUS_CPP_INTERNAL_SCOPEGUARD_H_
#define SDBUS_CPP_INTERNAL_SCOPEGUARD_H_ #define SDBUS_CPP_INTERNAL_SCOPEGUARD_H_
#include <exception>
#include <utility> #include <utility>
// Straightforward, modern, easy-to-use RAII utility to perform work on scope exit in an exception-safe manner. // Straightforward, modern, easy-to-use RAII utility to perform work on scope exit in an exception-safe manner.
@ -55,78 +56,115 @@
// return; // exiting scope normally // return; // exiting scope normally
// } // }
#define SCOPE_EXIT \ #define SCOPE_EXIT \
auto ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE) \ auto ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE) \
= ::skybase::utils::detail::ScopeGuardOnExit() + [&]() \ = ::sdbus::internal::ScopeGuardOnExitTag{} + [&]() \
/**/
#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{} + [&]() \
/**/ /**/
#define SCOPE_EXIT_NAMED(NAME) \ namespace sdbus::internal {
auto NAME \
= ::skybase::utils::detail::ScopeGuardOnExit() + [&]() \
/**/
namespace skybase { struct ScopeGuardOnExitTag
namespace utils { {
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> template <class _Fun, typename _Tag>
class ScopeGuard class ScopeGuard
{ {
_Fun fnc_;
bool active_;
public: public:
ScopeGuard(_Fun f) ScopeGuard(_Fun f) : fnc_(std::move(f))
: fnc_(std::move(f))
, active_(true)
{ {
} }
~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_) rhs.dismiss();
fnc_();
} }
void dismiss() void dismiss()
{ {
active_ = false; active_ = false;
} }
ScopeGuard() = delete;
ScopeGuard(const ScopeGuard&) = delete; ~ScopeGuard()
ScopeGuard& operator=(const ScopeGuard&) = delete;
ScopeGuard(ScopeGuard&& rhs)
: fnc_(std::move(rhs.fnc_))
, active_(rhs.active_)
{ {
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));
}
}
#define CONCATENATE_IMPL(s1, s2) s1##s2 #define CONCATENATE_IMPL(s1, s2) s1##s2
#define CONCATENATE(s1, s2) CONCATENATE_IMPL(s1, s2) #define CONCATENATE(s1, s2) CONCATENATE_IMPL(s1, s2)
#ifdef __COUNTER__ #ifdef __COUNTER__
#define ANONYMOUS_VARIABLE(str) \ #define ANONYMOUS_VARIABLE(str) CONCATENATE(str, __COUNTER__)
CONCATENATE(str, __COUNTER__) \
/**/
#else #else
#define ANONYMOUS_VARIABLE(str) \ #define ANONYMOUS_VARIABLE(str) CONCATENATE(str, __LINE__)
CONCATENATE(str, __LINE__) \
/**/
#endif #endif
#endif /* SDBUS_CPP_INTERNAL_SCOPEGUARD_H_ */ #endif /* SDBUS_CPP_INTERNAL_SCOPEGUARD_H_ */

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file SdBus.cpp * @file SdBus.cpp
* @author Ardazishvili Roman (ardazishvili.roman@yandex.ru) * @author Ardazishvili Roman (ardazishvili.roman@yandex.ru)
@ -48,7 +48,16 @@ int SdBus::sd_bus_send(sd_bus *bus, sd_bus_message *m, uint64_t *cookie)
{ {
std::lock_guard lock(sdbusMutex_); std::lock_guard lock(sdbusMutex_);
return ::sd_bus_send(bus, m, cookie); auto r = ::sd_bus_send(bus, m, cookie);
if (r < 0)
return r;
// Make sure long messages are not only stored in outgoing queues but also really sent out
// TODO: This is a workaround. We should not block here until everything is physically sent out.
// Refactor: if sd_bus_get_n_queued_write() > 0 then wake up event loop through event fd
::sd_bus_flush(bus != nullptr ? bus : ::sd_bus_message_get_bus(m));
return r;
} }
int SdBus::sd_bus_call(sd_bus *bus, sd_bus_message *m, uint64_t usec, sd_bus_error *ret_error, sd_bus_message **reply) int SdBus::sd_bus_call(sd_bus *bus, sd_bus_message *m, uint64_t usec, sd_bus_error *ret_error, sd_bus_message **reply)
@ -62,7 +71,16 @@ int SdBus::sd_bus_call_async(sd_bus *bus, sd_bus_slot **slot, sd_bus_message *m,
{ {
std::lock_guard lock(sdbusMutex_); std::lock_guard lock(sdbusMutex_);
return ::sd_bus_call_async(bus, slot, m, callback, userdata, usec); auto r = ::sd_bus_call_async(bus, slot, m, callback, userdata, usec);
if (r < 0)
return r;
// Make sure long messages are not only stored in outgoing queues but also really sent out
// TODO: This is a workaround. We should not block here until everything is physically sent out.
// Refactor: if sd_bus_get_n_queued_write() > 0 then wake up event loop through event fd
::sd_bus_flush(bus != nullptr ? bus : ::sd_bus_message_get_bus(m));
return r;
} }
int SdBus::sd_bus_message_new(sd_bus *bus, sd_bus_message **m, uint8_t type) int SdBus::sd_bus_message_new(sd_bus *bus, sd_bus_message **m, uint8_t type)
@ -180,26 +198,98 @@ int SdBus::sd_bus_open_user_with_address(sd_bus **ret, const char* address)
{ {
sd_bus* bus = nullptr; sd_bus* bus = nullptr;
int r = sd_bus_new(&bus); int r = ::sd_bus_new(&bus);
if (r < 0) if (r < 0)
return r; return r;
r = sd_bus_set_address(bus, address); r = ::sd_bus_set_address(bus, address);
if (r < 0) if (r < 0)
return r; return r;
r = sd_bus_set_bus_client(bus, true); r = ::sd_bus_set_bus_client(bus, true);
if (r < 0) if (r < 0)
return r; return r;
// Copying behavior from // Copying behavior from
// https://github.com/systemd/systemd/blob/fee6441601c979165ebcbb35472036439f8dad5f/src/libsystemd/sd-bus/sd-bus.c#L1381 // https://github.com/systemd/systemd/blob/fee6441601c979165ebcbb35472036439f8dad5f/src/libsystemd/sd-bus/sd-bus.c#L1381
// Here, we make the bus as trusted // Here, we make the bus as trusted
r = sd_bus_set_trusted(bus, true); r = ::sd_bus_set_trusted(bus, true);
if (r < 0) if (r < 0)
return r; 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) if (r < 0)
return r; return r;
@ -210,7 +300,14 @@ 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) int SdBus::sd_bus_open_system_remote(sd_bus **ret, const char *host)
{ {
#ifndef SDBUS_basu
return ::sd_bus_open_system_remote(ret, host); return ::sd_bus_open_system_remote(ret, host);
#else
(void)ret;
(void)host;
// https://git.sr.ht/~emersion/basu/commit/01d33b244eb6
return -EOPNOTSUPP;
#endif
} }
int SdBus::sd_bus_request_name(sd_bus *bus, const char *name, uint64_t flags) int SdBus::sd_bus_request_name(sd_bus *bus, const char *name, uint64_t flags)
@ -251,7 +348,21 @@ int SdBus::sd_bus_add_match(sd_bus *bus, sd_bus_slot **slot, const char *match,
{ {
std::lock_guard lock(sdbusMutex_); 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);
}
int SdBus::sd_bus_match_signal(sd_bus *bus, sd_bus_slot **ret, const char *sender, const char *path, const char *interface, const char *member, sd_bus_message_handler_t callback, void *userdata)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_match_signal(bus, ret, sender, path, interface, member, callback, userdata);
} }
sd_bus_slot* SdBus::sd_bus_slot_unref(sd_bus_slot *slot) sd_bus_slot* SdBus::sd_bus_slot_unref(sd_bus_slot *slot)
@ -278,6 +389,11 @@ int SdBus::sd_bus_process(sd_bus *bus, sd_bus_message **r)
return ::sd_bus_process(bus, r); return ::sd_bus_process(bus, r);
} }
sd_bus_message* SdBus::sd_bus_get_current_message(sd_bus *bus)
{
return ::sd_bus_get_current_message(bus);
}
int SdBus::sd_bus_get_poll_data(sd_bus *bus, PollData* data) int SdBus::sd_bus_get_poll_data(sd_bus *bus, PollData* data)
{ {
std::lock_guard lock(sdbusMutex_); std::lock_guard lock(sdbusMutex_);
@ -297,6 +413,13 @@ int SdBus::sd_bus_get_poll_data(sd_bus *bus, PollData* data)
return r; return r;
} }
int SdBus::sd_bus_get_n_queued_read(sd_bus *bus, uint64_t *ret)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_get_n_queued_read(bus, ret);
}
int SdBus::sd_bus_flush(sd_bus *bus) int SdBus::sd_bus_flush(sd_bus *bus)
{ {
return ::sd_bus_flush(bus); return ::sd_bus_flush(bus);

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file SdBus.h * @file SdBus.h
* @author Ardazishvili Roman (ardazishvili.roman@yandex.ru) * @author Ardazishvili Roman (ardazishvili.roman@yandex.ru)
@ -63,20 +63,26 @@ public:
virtual int sd_bus_open_user(sd_bus **ret) override; 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_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_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_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_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_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_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_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(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 int sd_bus_match_signal(sd_bus *bus, sd_bus_slot **ret, const char *sender, const char *path, const char *interface, const char *member, sd_bus_message_handler_t callback, void *userdata) override;
virtual sd_bus_slot* sd_bus_slot_unref(sd_bus_slot *slot) override; virtual sd_bus_slot* sd_bus_slot_unref(sd_bus_slot *slot) override;
virtual int sd_bus_new(sd_bus **ret) override; virtual int sd_bus_new(sd_bus **ret) override;
virtual int sd_bus_start(sd_bus *bus) override; virtual int sd_bus_start(sd_bus *bus) override;
virtual int sd_bus_process(sd_bus *bus, sd_bus_message **r) override; virtual int sd_bus_process(sd_bus *bus, sd_bus_message **r) override;
virtual sd_bus_message* sd_bus_get_current_message(sd_bus *bus) override;
virtual int sd_bus_get_poll_data(sd_bus *bus, PollData* data) override; virtual int sd_bus_get_poll_data(sd_bus *bus, PollData* data) override;
virtual int sd_bus_get_n_queued_read(sd_bus *bus, uint64_t *ret) override;
virtual int sd_bus_flush(sd_bus *bus) override; virtual int sd_bus_flush(sd_bus *bus) override;
virtual sd_bus *sd_bus_flush_close_unref(sd_bus *bus) override; virtual sd_bus *sd_bus_flush_close_unref(sd_bus *bus) override;
virtual sd_bus *sd_bus_close_unref(sd_bus *bus) override; virtual sd_bus *sd_bus_close_unref(sd_bus *bus) override;

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file Types.cpp * @file Types.cpp
* *
@ -24,11 +24,16 @@
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>. * along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <sdbus-c++/Types.h> #include "sdbus-c++/Types.h"
#include <sdbus-c++/Error.h>
#include "sdbus-c++/Error.h"
#include "MessageUtils.h" #include "MessageUtils.h"
#include <systemd/sd-bus.h>
#include <cassert> #include <cerrno>
#include <system_error>
#include SDBUS_HEADER
#include <unistd.h>
namespace sdbus { namespace sdbus {
@ -50,12 +55,10 @@ void Variant::deserializeFrom(Message& msg)
msg_.seal(); msg_.seal();
} }
std::string Variant::peekValueType() const const char* Variant::peekValueType() const
{ {
msg_.rewind(false); msg_.rewind(false);
std::string type; auto [type, contents] = msg_.peekType();
std::string contents;
msg_.peekType(type, contents);
return contents; return contents;
} }
@ -64,4 +67,27 @@ bool Variant::isEmpty() const
return msg_.isEmpty(); return msg_.isEmpty();
} }
void UnixFd::close() // NOLINT(readability-make-member-function-const)
{
if (fd_ >= 0)
{
::close(fd_);
}
} }
int UnixFd::checkedDup(int fd)
{
if (fd < 0)
{
return fd;
}
int ret = ::dup(fd); // NOLINT(android-cloexec-dup) // TODO: verify
if (ret < 0)
{
throw std::system_error(errno, std::generic_category(), "dup failed");
}
return ret;
}
} // namespace sdbus

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file Utils.h * @file Utils.h
* *
@ -28,20 +28,20 @@
#define SDBUS_CXX_INTERNAL_UTILS_H_ #define SDBUS_CXX_INTERNAL_UTILS_H_
#include <sdbus-c++/Error.h> #include <sdbus-c++/Error.h>
#include <systemd/sd-bus.h> #include SDBUS_HEADER
#if LIBSYSTEMD_VERSION>=246 #if LIBSYSTEMD_VERSION>=246
#define SDBUS_CHECK_OBJECT_PATH(_PATH) \ #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) \ SDBUS_THROW_ERROR_IF(!sd_bus_object_path_is_valid(_PATH), std::string("Invalid object path '") + _PATH + "' provided", EINVAL) \
/**/ /**/
#define SDBUS_CHECK_INTERFACE_NAME(_NAME) \ #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) \ SDBUS_THROW_ERROR_IF(!sd_bus_interface_name_is_valid(_NAME), std::string("Invalid interface name '") + _NAME + "' provided", EINVAL) \
/**/ /**/
#define SDBUS_CHECK_SERVICE_NAME(_NAME) \ #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) \ SDBUS_THROW_ERROR_IF(*_NAME && !sd_bus_service_name_is_valid(_NAME), std::string("Invalid service name '") + _NAME + "' provided", EINVAL) \
/**/ /**/
#define SDBUS_CHECK_MEMBER_NAME(_NAME) \ #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) \ SDBUS_THROW_ERROR_IF(!sd_bus_member_name_is_valid(_NAME), std::string("Invalid member name '") + _NAME + "' provided", EINVAL) \
/**/ /**/
#else #else
#define SDBUS_CHECK_OBJECT_PATH(_PATH) #define SDBUS_CHECK_OBJECT_PATH(_PATH)
@ -50,4 +50,49 @@
#define SDBUS_CHECK_MEMBER_NAME(_NAME) #define SDBUS_CHECK_MEMBER_NAME(_NAME)
#endif #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.c_str(), e.what());
return false;
}
catch (...)
{
sd_bus_error_set(retError, SDBUSCPP_ERROR_NAME.c_str(), "Unknown error occurred");
return false;
}
return true;
}
// Returns time since epoch based of POSIX CLOCK_MONOTONIC,
// so we use the very same clock as underlying sd-bus library.
[[nodiscard]] inline auto now()
{
struct timespec ts{};
auto r = clock_gettime(CLOCK_MONOTONIC, &ts);
SDBUS_THROW_ERROR_IF(r < 0, "clock_gettime failed: ", -errno);
return std::chrono::nanoseconds(ts.tv_nsec) + std::chrono::seconds(ts.tv_sec);
}
// 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_ */ #endif /* SDBUS_CXX_INTERNAL_UTILS_H_ */

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file VTableUtils.c * @file VTableUtils.c
* *
@ -25,20 +25,20 @@
*/ */
#include "VTableUtils.h" #include "VTableUtils.h"
#include <systemd/sd-bus.h> #include SDBUS_HEADER
sd_bus_vtable createVTableStartItem(uint64_t flags) sd_bus_vtable createSdBusVTableStartItem(uint64_t flags)
{ {
struct sd_bus_vtable vtableStart = SD_BUS_VTABLE_START(flags); struct sd_bus_vtable vtableStart = SD_BUS_VTABLE_START(flags);
return vtableStart; return vtableStart;
} }
sd_bus_vtable createVTableMethodItem( const char *member sd_bus_vtable createSdBusVTableMethodItem( const char *member
, const char *signature , const char *signature
, const char *result , const char *result
, const char *paramNames , const char *paramNames
, sd_bus_message_handler_t handler , sd_bus_message_handler_t handler
, uint64_t flags ) , uint64_t flags )
{ {
#if LIBSYSTEMD_VERSION>=242 #if LIBSYSTEMD_VERSION>=242
// We have to expand macro SD_BUS_METHOD_WITH_NAMES manually here, because the macro expects literal char strings // We have to expand macro SD_BUS_METHOD_WITH_NAMES manually here, because the macro expects literal char strings
@ -65,10 +65,10 @@ sd_bus_vtable createVTableMethodItem( const char *member
return vtableItem; return vtableItem;
} }
sd_bus_vtable createVTableSignalItem( const char *member sd_bus_vtable createSdBusVTableSignalItem( const char *member
, const char *signature , const char *signature
, const char *outnames , const char *outnames
, uint64_t flags ) , uint64_t flags )
{ {
#if LIBSYSTEMD_VERSION>=242 #if LIBSYSTEMD_VERSION>=242
struct sd_bus_vtable vtableItem = SD_BUS_SIGNAL_WITH_NAMES(member, signature, outnames, flags); struct sd_bus_vtable vtableItem = SD_BUS_SIGNAL_WITH_NAMES(member, signature, outnames, flags);
@ -79,26 +79,26 @@ sd_bus_vtable createVTableSignalItem( const char *member
return vtableItem; return vtableItem;
} }
sd_bus_vtable createVTablePropertyItem( const char *member sd_bus_vtable createSdBusVTableReadOnlyPropertyItem( const char *member
, const char *signature , const char *signature
, sd_bus_property_get_t getter , sd_bus_property_get_t getter
, uint64_t flags ) , uint64_t flags )
{ {
struct sd_bus_vtable vtableItem = SD_BUS_PROPERTY(member, signature, getter, 0, flags); struct sd_bus_vtable vtableItem = SD_BUS_PROPERTY(member, signature, getter, 0, flags);
return vtableItem; return vtableItem;
} }
sd_bus_vtable createVTableWritablePropertyItem( const char *member sd_bus_vtable createSdBusVTableWritablePropertyItem( const char *member
, const char *signature , const char *signature
, sd_bus_property_get_t getter , sd_bus_property_get_t getter
, sd_bus_property_set_t setter , sd_bus_property_set_t setter
, uint64_t flags ) , uint64_t flags )
{ {
struct sd_bus_vtable vtableItem = SD_BUS_WRITABLE_PROPERTY(member, signature, getter, setter, 0, flags); struct sd_bus_vtable vtableItem = SD_BUS_WRITABLE_PROPERTY(member, signature, getter, setter, 0, flags);
return vtableItem; return vtableItem;
} }
sd_bus_vtable createVTableEndItem() sd_bus_vtable createSdBusVTableEndItem()
{ {
struct sd_bus_vtable vtableEnd = SD_BUS_VTABLE_END; struct sd_bus_vtable vtableEnd = SD_BUS_VTABLE_END;
return vtableEnd; return vtableEnd;

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file VTableUtils.h * @file VTableUtils.h
* *
@ -27,34 +27,34 @@
#ifndef SDBUS_CXX_INTERNAL_VTABLEUTILS_H_ #ifndef SDBUS_CXX_INTERNAL_VTABLEUTILS_H_
#define SDBUS_CXX_INTERNAL_VTABLEUTILS_H_ #define SDBUS_CXX_INTERNAL_VTABLEUTILS_H_
#include <systemd/sd-bus.h>
#include <stdbool.h> #include <stdbool.h>
#include SDBUS_HEADER
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
sd_bus_vtable createVTableStartItem(uint64_t flags); sd_bus_vtable createSdBusVTableStartItem(uint64_t flags);
sd_bus_vtable createVTableMethodItem( const char *member sd_bus_vtable createSdBusVTableMethodItem( const char *member
, const char *signature , const char *signature
, const char *result , const char *result
, const char *paramNames , const char *paramNames
, sd_bus_message_handler_t handler , sd_bus_message_handler_t handler
, uint64_t flags ); , uint64_t flags );
sd_bus_vtable createVTableSignalItem( const char *member sd_bus_vtable createSdBusVTableSignalItem( const char *member
, const char *signature , const char *signature
, const char *outnames , const char *outnames
, uint64_t flags ); , uint64_t flags );
sd_bus_vtable createVTablePropertyItem( const char *member sd_bus_vtable createSdBusVTableReadOnlyPropertyItem( const char *member
, const char *signature , const char *signature
, sd_bus_property_get_t getter , sd_bus_property_get_t getter
, uint64_t flags ); , uint64_t flags );
sd_bus_vtable createVTableWritablePropertyItem( const char *member sd_bus_vtable createSdBusVTableWritablePropertyItem( const char *member
, const char *signature , const char *signature
, sd_bus_property_get_t getter , sd_bus_property_get_t getter
, sd_bus_property_set_t setter , sd_bus_property_set_t setter
, uint64_t flags ); , uint64_t flags );
sd_bus_vtable createVTableEndItem(); sd_bus_vtable createSdBusVTableEndItem();
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -2,15 +2,12 @@
# DOWNLOAD AND BUILD OF GOOGLETEST # DOWNLOAD AND BUILD OF GOOGLETEST
#------------------------------- #-------------------------------
set(GOOGLETEST_VERSION 1.10.0 CACHE STRING "Version of gmock to use") find_package(GTest ${SDBUSCPP_GOOGLETEST_VERSION} CONFIG)
set(GOOGLETEST_GIT_REPO "https://github.com/google/googletest.git" CACHE STRING "A git repo to clone and build googletest from if gmock is not found in the system")
find_package(GTest ${GOOGLETEST_VERSION} CONFIG)
if (NOT TARGET GTest::gmock) if (NOT TARGET GTest::gmock)
# Try pkg-config if GTest was not found through CMake config # Try pkg-config if GTest was not found through CMake config
find_package(PkgConfig) find_package(PkgConfig)
if (PkgConfig_FOUND) if (PkgConfig_FOUND)
pkg_check_modules(GMock IMPORTED_TARGET GLOBAL gmock>=${GOOGLETEST_VERSION}) pkg_check_modules(GMock IMPORTED_TARGET GLOBAL gmock>=${SDBUSCPP_GOOGLETEST_VERSION})
if(TARGET PkgConfig::GMock) if(TARGET PkgConfig::GMock)
add_library(GTest::gmock ALIAS PkgConfig::GMock) add_library(GTest::gmock ALIAS PkgConfig::GMock)
endif() endif()
@ -19,26 +16,26 @@ if (NOT TARGET GTest::gmock)
if (NOT TARGET GTest::gmock) if (NOT TARGET GTest::gmock)
include(FetchContent) include(FetchContent)
message("Fetching googletest v${GOOGLETEST_VERSION}...") if (SDBUSCPP_GOOGLETEST_VERSION VERSION_GREATER_EQUAL 1.13.0)
set(GOOGLETEST_TAG "v${SDBUSCPP_GOOGLETEST_VERSION}")
else()
set(GOOGLETEST_TAG "release-${SDBUSCPP_GOOGLETEST_VERSION}")
endif()
message("Manually fetching & building googletest ${GOOGLETEST_TAG}...")
FetchContent_Declare(googletest FetchContent_Declare(googletest
GIT_REPOSITORY ${GOOGLETEST_GIT_REPO} GIT_REPOSITORY ${SDBUSCPP_GOOGLETEST_GIT_REPO}
GIT_TAG release-${GOOGLETEST_VERSION} GIT_TAG ${GOOGLETEST_TAG}
GIT_SHALLOW 1 GIT_SHALLOW 1
UPDATE_COMMAND "") UPDATE_COMMAND "")
#FetchContent_MakeAvailable(googletest) # Not available in CMake 3.13 :-( Let's do it manually: set(gtest_force_shared_crt ON CACHE INTERNAL "" FORCE)
FetchContent_GetProperties(googletest) set(INSTALL_GTEST OFF CACHE INTERNAL "" FORCE)
if(NOT googletest_POPULATED) set(BUILD_SHARED_LIBS_BAK ${BUILD_SHARED_LIBS})
FetchContent_Populate(googletest) set(BUILD_SHARED_LIBS OFF)
set(gtest_force_shared_crt ON CACHE INTERNAL "" FORCE) FetchContent_MakeAvailable(googletest)
set(BUILD_GMOCK ON CACHE INTERNAL "" FORCE) set(BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS_BAK})
set(INSTALL_GTEST OFF CACHE INTERNAL "" FORCE) add_library(GTest::gmock ALIAS gmock)
set(BUILD_SHARED_LIBS_BAK ${BUILD_SHARED_LIBS})
set(BUILD_SHARED_LIBS OFF)
add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR})
set(BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS_BAK})
add_library(GTest::gmock ALIAS gmock)
endif()
endif() endif()
endif() endif()
@ -50,6 +47,7 @@ set(UNITTESTS_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/unittests)
set(UNITTESTS_SRCS set(UNITTESTS_SRCS
${UNITTESTS_SOURCE_DIR}/sdbus-c++-unit-tests.cpp ${UNITTESTS_SOURCE_DIR}/sdbus-c++-unit-tests.cpp
${UNITTESTS_SOURCE_DIR}/Message_test.cpp ${UNITTESTS_SOURCE_DIR}/Message_test.cpp
${UNITTESTS_SOURCE_DIR}/PollData_test.cpp
${UNITTESTS_SOURCE_DIR}/Types_test.cpp ${UNITTESTS_SOURCE_DIR}/Types_test.cpp
${UNITTESTS_SOURCE_DIR}/TypeTraits_test.cpp ${UNITTESTS_SOURCE_DIR}/TypeTraits_test.cpp
${UNITTESTS_SOURCE_DIR}/Connection_test.cpp ${UNITTESTS_SOURCE_DIR}/Connection_test.cpp
@ -104,22 +102,29 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR})
#---------------------------------- #----------------------------------
add_executable(sdbus-c++-unit-tests ${UNITTESTS_SRCS}) 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=${SDBUSCPP_LIBSYSTEMD_VERSION}
SDBUS_${SDBUS_IMPL}
SDBUS_HEADER=<${SDBUS_IMPL}/sd-bus.h>)
target_link_libraries(sdbus-c++-unit-tests sdbus-c++-objlib GTest::gmock) target_link_libraries(sdbus-c++-unit-tests sdbus-c++-objlib GTest::gmock)
add_executable(sdbus-c++-integration-tests ${INTEGRATIONTESTS_SRCS}) add_executable(sdbus-c++-integration-tests ${INTEGRATIONTESTS_SRCS})
target_compile_definitions(sdbus-c++-integration-tests PRIVATE LIBSYSTEMD_VERSION=${LIBSYSTEMD_VERSION}) target_compile_definitions(sdbus-c++-integration-tests PRIVATE
target_link_libraries(sdbus-c++-integration-tests sdbus-c++ GTest::gmock) LIBSYSTEMD_VERSION=${SDBUSCPP_LIBSYSTEMD_VERSION}
SDBUS_${SDBUS_IMPL})
if(NOT SDBUS_IMPL STREQUAL "basu")
# Systemd::Libsystemd is included because integration tests use sd-event. Otherwise sdbus-c++ encapsulates and hides libsystemd.
target_link_libraries(sdbus-c++-integration-tests sdbus-c++ Systemd::Libsystemd GTest::gmock)
else()
# sd-event implementation is not part of basu, so its integration tests will be skipped
target_link_libraries(sdbus-c++-integration-tests sdbus-c++ GTest::gmock)
endif()
# Manual performance and stress tests if(SDBUSCPP_BUILD_PERF_TESTS OR SDBUSCPP_BUILD_STRESS_TESTS)
option(ENABLE_PERF_TESTS "Build and install manual performance tests (default OFF)" OFF)
option(ENABLE_STRESS_TESTS "Build and install manual stress tests (default OFF)" OFF)
if(ENABLE_PERF_TESTS OR ENABLE_STRESS_TESTS)
set(THREADS_PREFER_PTHREAD_FLAG ON) set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
if(ENABLE_PERF_TESTS) if(SDBUSCPP_BUILD_PERF_TESTS)
message(STATUS "Building with performance tests") message(STATUS "Building with performance tests")
add_executable(sdbus-c++-perf-tests-client ${STRESSTESTS_CLIENT_SRCS}) add_executable(sdbus-c++-perf-tests-client ${STRESSTESTS_CLIENT_SRCS})
target_link_libraries(sdbus-c++-perf-tests-client sdbus-c++ Threads::Threads) target_link_libraries(sdbus-c++-perf-tests-client sdbus-c++ Threads::Threads)
@ -127,7 +132,7 @@ if(ENABLE_PERF_TESTS OR ENABLE_STRESS_TESTS)
target_link_libraries(sdbus-c++-perf-tests-server sdbus-c++ Threads::Threads) target_link_libraries(sdbus-c++-perf-tests-server sdbus-c++ Threads::Threads)
endif() endif()
if(ENABLE_STRESS_TESTS) if(SDBUSCPP_BUILD_STRESS_TESTS)
message(STATUS "Building with stress tests") message(STATUS "Building with stress tests")
add_executable(sdbus-c++-stress-tests ${STRESSTESTS_SRCS}) add_executable(sdbus-c++-stress-tests ${STRESSTESTS_SRCS})
target_link_libraries(sdbus-c++-stress-tests sdbus-c++ Threads::Threads) target_link_libraries(sdbus-c++-stress-tests sdbus-c++ Threads::Threads)
@ -138,21 +143,26 @@ endif()
# INSTALLATION # INSTALLATION
#---------------------------------- #----------------------------------
set(TESTS_INSTALL_PATH "/opt/test/bin" CACHE STRING "Specifies where the test binaries will be installed") if(SDBUSCPP_INSTALL)
include(GNUInstallDirs)
install(TARGETS sdbus-c++-unit-tests DESTINATION ${TESTS_INSTALL_PATH} COMPONENT test) install(TARGETS sdbus-c++-unit-tests DESTINATION ${SDBUSCPP_TESTS_INSTALL_PATH} COMPONENT sdbus-c++-test)
install(TARGETS sdbus-c++-integration-tests DESTINATION ${TESTS_INSTALL_PATH} COMPONENT test) install(TARGETS sdbus-c++-integration-tests DESTINATION ${SDBUSCPP_TESTS_INSTALL_PATH} COMPONENT sdbus-c++-test)
install(FILES ${INTEGRATIONTESTS_SOURCE_DIR}/files/org.sdbuscpp.integrationtests.conf DESTINATION /etc/dbus-1/system.d COMPONENT test) install(FILES ${INTEGRATIONTESTS_SOURCE_DIR}/files/org.sdbuscpp.integrationtests.conf
DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/dbus-1/system.d
if(ENABLE_PERF_TESTS) COMPONENT sdbus-c++-test)
install(TARGETS sdbus-c++-perf-tests-client DESTINATION ${TESTS_INSTALL_PATH} COMPONENT test) if(SDBUSCPP_BUILD_PERF_TESTS)
install(TARGETS sdbus-c++-perf-tests-server DESTINATION ${TESTS_INSTALL_PATH} COMPONENT test) install(TARGETS sdbus-c++-perf-tests-client DESTINATION ${SDBUSCPP_TESTS_INSTALL_PATH} COMPONENT sdbus-c++-test)
install(FILES ${PERFTESTS_SOURCE_DIR}/files/org.sdbuscpp.perftests.conf DESTINATION /etc/dbus-1/system.d COMPONENT test) install(TARGETS sdbus-c++-perf-tests-server DESTINATION ${SDBUSCPP_TESTS_INSTALL_PATH} COMPONENT sdbus-c++-test)
endif() install(FILES ${PERFTESTS_SOURCE_DIR}/files/org.sdbuscpp.perftests.conf
DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/dbus-1/system.d
if(ENABLE_STRESS_TESTS) COMPONENT sdbus-c++-test)
install(TARGETS sdbus-c++-stress-tests DESTINATION ${TESTS_INSTALL_PATH} COMPONENT test) endif()
install(FILES ${STRESSTESTS_SOURCE_DIR}/files/org.sdbuscpp.stresstests.conf DESTINATION /etc/dbus-1/system.d COMPONENT test) if(SDBUSCPP_BUILD_STRESS_TESTS)
install(TARGETS sdbus-c++-stress-tests DESTINATION ${SDBUSCPP_TESTS_INSTALL_PATH} COMPONENT sdbus-c++-test)
install(FILES ${STRESSTESTS_SOURCE_DIR}/files/org.sdbuscpp.stresstests.conf
DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/dbus-1/system.d
COMPONENT sdbus-c++-test)
endif()
endif() endif()
#---------------------------------- #----------------------------------
@ -162,4 +172,4 @@ endif()
if(NOT CMAKE_CROSSCOMPILING) if(NOT CMAKE_CROSSCOMPILING)
add_test(NAME sdbus-c++-unit-tests COMMAND sdbus-c++-unit-tests) add_test(NAME sdbus-c++-unit-tests COMMAND sdbus-c++-unit-tests)
add_test(NAME sdbus-c++-integration-tests COMMAND sdbus-c++-integration-tests) add_test(NAME sdbus-c++-integration-tests COMMAND sdbus-c++-integration-tests)
endif() endif()

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file DBusAsyncMethodsTests.cpp * @file DBusAsyncMethodsTests.cpp
* *
@ -49,29 +49,27 @@ using ::testing::SizeIs;
using namespace std::chrono_literals; using namespace std::chrono_literals;
using namespace sdbus::test; using namespace sdbus::test;
using SdbusTestObject = TestFixture;
/*-------------------------------------*/ /*-------------------------------------*/
/* -- TEST CASES -- */ /* -- TEST CASES -- */
/*-------------------------------------*/ /*-------------------------------------*/
TEST_F(SdbusTestObject, ThrowsTimeoutErrorWhenClientSideAsyncMethodTimesOut) TYPED_TEST(AsyncSdbusTestObject, ThrowsTimeoutErrorWhenClientSideAsyncMethodTimesOut)
{ {
std::chrono::time_point<std::chrono::steady_clock> start; std::chrono::time_point<std::chrono::steady_clock> start;
try try
{ {
std::promise<uint32_t> promise; std::promise<uint32_t> promise;
auto future = promise.get_future(); auto future = promise.get_future();
m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t res, const sdbus::Error* err) this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t res, std::optional<sdbus::Error> err)
{ {
if (err == nullptr) if (!err)
promise.set_value(res); promise.set_value(res);
else else
promise.set_exception(std::make_exception_ptr(*err)); promise.set_exception(std::make_exception_ptr(*std::move(err)));
}); });
start = std::chrono::steady_clock::now(); start = std::chrono::steady_clock::now();
m_proxy->doOperationClientSideAsyncWithTimeout(1us, 1000); // The operation will take 1s, but the timeout is 500ms, so we should time out this->m_proxy->doOperationClientSideAsyncWithTimeout(1us, (1s).count()); // The operation will take 1s, but the timeout is 1us, so we should time out
future.get(); future.get();
FAIL() << "Expected sdbus::Error exception"; FAIL() << "Expected sdbus::Error exception";
@ -89,7 +87,7 @@ TEST_F(SdbusTestObject, ThrowsTimeoutErrorWhenClientSideAsyncMethodTimesOut)
} }
} }
TEST_F(SdbusTestObject, RunsServerSideAsynchoronousMethodAsynchronously) TYPED_TEST(AsyncSdbusTestObject, RunsServerSideAsynchoronousMethodAsynchronously)
{ {
// Yeah, this is kinda timing-dependent test, but times should be safe... // Yeah, this is kinda timing-dependent test, but times should be safe...
std::mutex mtx; std::mutex mtx;
@ -98,7 +96,7 @@ TEST_F(SdbusTestObject, RunsServerSideAsynchoronousMethodAsynchronously)
std::atomic<int> startedCount{}; std::atomic<int> startedCount{};
auto call = [&](uint32_t param) auto call = [&](uint32_t param)
{ {
TestProxy proxy{BUS_NAME, OBJECT_PATH}; TestProxy proxy{SERVICE_NAME, OBJECT_PATH};
++startedCount; ++startedCount;
while (!invoke) ; while (!invoke) ;
auto result = proxy.doOperationAsync(param); auto result = proxy.doOperationAsync(param);
@ -114,14 +112,14 @@ TEST_F(SdbusTestObject, RunsServerSideAsynchoronousMethodAsynchronously)
ASSERT_THAT(results, ElementsAre(500, 1000, 1500)); ASSERT_THAT(results, ElementsAre(500, 1000, 1500));
} }
TEST_F(SdbusTestObject, HandlesCorrectlyABulkOfParallelServerSideAsyncMethods) TYPED_TEST(AsyncSdbusTestObject, HandlesCorrectlyABulkOfParallelServerSideAsyncMethods)
{ {
std::atomic<size_t> resultCount{}; std::atomic<size_t> resultCount{};
std::atomic<bool> invoke{}; std::atomic<bool> invoke{};
std::atomic<int> startedCount{}; std::atomic<int> startedCount{};
auto call = [&]() auto call = [&]()
{ {
TestProxy proxy{BUS_NAME, OBJECT_PATH}; TestProxy proxy{SERVICE_NAME, OBJECT_PATH};
++startedCount; ++startedCount;
while (!invoke) ; while (!invoke) ;
@ -144,97 +142,136 @@ TEST_F(SdbusTestObject, HandlesCorrectlyABulkOfParallelServerSideAsyncMethods)
ASSERT_THAT(resultCount, Eq(1500)); ASSERT_THAT(resultCount, Eq(1500));
} }
TEST_F(SdbusTestObject, InvokesMethodAsynchronouslyOnClientSide) TYPED_TEST(AsyncSdbusTestObject, InvokesMethodAsynchronouslyOnClientSide)
{ {
std::promise<uint32_t> promise; std::promise<uint32_t> promise;
auto future = promise.get_future(); auto future = promise.get_future();
m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t res, const sdbus::Error* err) this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t res, std::optional<sdbus::Error> err)
{ {
if (err == nullptr) if (!err)
promise.set_value(res); promise.set_value(res);
else else
promise.set_exception(std::make_exception_ptr(*err)); promise.set_exception(std::make_exception_ptr(std::move(err)));
}); });
m_proxy->doOperationClientSideAsync(100); this->m_proxy->doOperationClientSideAsync(100);
ASSERT_THAT(future.get(), Eq(100)); ASSERT_THAT(future.get(), Eq(100));
} }
TEST_F(SdbusTestObject, AnswersThatAsyncCallIsPendingIfItIsInProgress) TYPED_TEST(AsyncSdbusTestObject, InvokesMethodAsynchronouslyOnClientSideWithFuture)
{ {
m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t /*res*/, const sdbus::Error* /*err*/){}); auto future = this->m_proxy->doOperationClientSideAsync(100, sdbus::with_future);
auto call = m_proxy->doOperationClientSideAsync(100); ASSERT_THAT(future.get(), Eq(100));
}
TYPED_TEST(AsyncSdbusTestObject, InvokesMethodAsynchronouslyOnClientSideWithFutureOnBasicAPILevel)
{
auto future = this->m_proxy->doOperationClientSideAsyncOnBasicAPILevel(100);
auto methodReply = future.get();
uint32_t returnValue{};
methodReply >> returnValue;
ASSERT_THAT(returnValue, Eq(100));
}
TYPED_TEST(AsyncSdbusTestObject, AnswersThatAsyncCallIsPendingIfItIsInProgress)
{
this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t /*res*/, std::optional<sdbus::Error> /*err*/){});
auto call = this->m_proxy->doOperationClientSideAsync(100);
ASSERT_TRUE(call.isPending()); ASSERT_TRUE(call.isPending());
} }
TEST_F(SdbusTestObject, CancelsPendingAsyncCallOnClientSide) TYPED_TEST(AsyncSdbusTestObject, CancelsPendingAsyncCallOnClientSide)
{ {
std::promise<uint32_t> promise; std::promise<uint32_t> promise;
auto future = promise.get_future(); auto future = promise.get_future();
m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t /*res*/, const sdbus::Error* /*err*/){ promise.set_value(1); }); this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t /*res*/, std::optional<sdbus::Error> /*err*/){ promise.set_value(1); });
auto call = m_proxy->doOperationClientSideAsync(100); auto call = this->m_proxy->doOperationClientSideAsync(100);
call.cancel(); call.cancel();
ASSERT_THAT(future.wait_for(300ms), Eq(std::future_status::timeout)); ASSERT_THAT(future.wait_for(300ms), Eq(std::future_status::timeout));
} }
TEST_F(SdbusTestObject, AnswersThatAsyncCallIsNotPendingAfterItHasBeenCancelled) TYPED_TEST(AsyncSdbusTestObject, CancelsPendingAsyncCallOnClientSideByDestroyingOwningSlot)
{ {
std::promise<uint32_t> promise; std::promise<uint32_t> promise;
auto future = promise.get_future(); auto future = promise.get_future();
m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t /*res*/, const sdbus::Error* /*err*/){ promise.set_value(1); }); this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t /*res*/, std::optional<sdbus::Error> /*err*/){ promise.set_value(1); });
auto call = m_proxy->doOperationClientSideAsync(100);
{
auto slot = this->m_proxy->doOperationClientSideAsync(100, sdbus::return_slot);
// Now the slot is destroyed, cancelling the async call
}
ASSERT_THAT(future.wait_for(300ms), Eq(std::future_status::timeout));
}
TYPED_TEST(AsyncSdbusTestObject, AnswersThatAsyncCallIsNotPendingAfterItHasBeenCancelled)
{
std::promise<uint32_t> promise;
auto future = promise.get_future();
this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t /*res*/, std::optional<sdbus::Error> /*err*/){ promise.set_value(1); });
auto call = this->m_proxy->doOperationClientSideAsync(100);
call.cancel(); call.cancel();
ASSERT_FALSE(call.isPending()); ASSERT_FALSE(call.isPending());
} }
TEST_F(SdbusTestObject, AnswersThatAsyncCallIsNotPendingAfterItHasBeenCompleted) TYPED_TEST(AsyncSdbusTestObject, AnswersThatAsyncCallIsNotPendingAfterItHasBeenCompleted)
{ {
std::promise<uint32_t> promise; std::promise<uint32_t> promise;
auto future = promise.get_future(); auto future = promise.get_future();
m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t /*res*/, const sdbus::Error* /*err*/){ promise.set_value(1); }); this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t /*res*/, std::optional<sdbus::Error> /*err*/){ promise.set_value(1); });
auto call = m_proxy->doOperationClientSideAsync(0); auto call = this->m_proxy->doOperationClientSideAsync(0);
(void) future.get(); // Wait for the call to finish (void) future.get(); // Wait for the call to finish
ASSERT_TRUE(waitUntil([&call](){ return !call.isPending(); })); ASSERT_TRUE(waitUntil([&call](){ return !call.isPending(); }));
} }
TEST_F(SdbusTestObject, AnswersThatDefaultConstructedAsyncCallIsNotPending) TYPED_TEST(AsyncSdbusTestObject, AnswersThatDefaultConstructedAsyncCallIsNotPending)
{ {
sdbus::PendingAsyncCall call; sdbus::PendingAsyncCall call;
ASSERT_FALSE(call.isPending()); ASSERT_FALSE(call.isPending());
} }
TEST_F(SdbusTestObject, SupportsAsyncCallCopyAssignment) TYPED_TEST(AsyncSdbusTestObject, SupportsAsyncCallCopyAssignment)
{ {
sdbus::PendingAsyncCall call; sdbus::PendingAsyncCall call;
call = m_proxy->doOperationClientSideAsync(100); call = this->m_proxy->doOperationClientSideAsync(100);
ASSERT_TRUE(call.isPending()); ASSERT_TRUE(call.isPending());
} }
TEST_F(SdbusTestObject, InvokesErroneousMethodAsynchronouslyOnClientSide) TYPED_TEST(AsyncSdbusTestObject, ReturnsNonnullErrorWhenAsynchronousMethodCallFails)
{ {
std::promise<uint32_t> promise; std::promise<uint32_t> promise;
auto future = promise.get_future(); auto future = promise.get_future();
m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t res, const sdbus::Error* err) this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t res, std::optional<sdbus::Error> err)
{ {
if (err == nullptr) if (!err)
promise.set_value(res); promise.set_value(res);
else else
promise.set_exception(std::make_exception_ptr(*err)); promise.set_exception(std::make_exception_ptr(*std::move(err)));
}); });
m_proxy->doErroneousOperationClientSideAsync(); this->m_proxy->doErroneousOperationClientSideAsync();
ASSERT_THROW(future.get(), sdbus::Error);
}
TYPED_TEST(AsyncSdbusTestObject, ThrowsErrorWhenClientSideAsynchronousMethodCallWithFutureFails)
{
auto future = this->m_proxy->doErroneousOperationClientSideAsync(sdbus::with_future);
ASSERT_THROW(future.get(), sdbus::Error); ASSERT_THROW(future.get(), sdbus::Error);
} }

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file DBusConnectionTests.cpp * @file DBusConnectionTests.cpp
* *
@ -41,7 +41,6 @@
using ::testing::Eq; using ::testing::Eq;
using namespace sdbus::test; using namespace sdbus::test;
using namespace std::chrono_literals;
/*-------------------------------------*/ /*-------------------------------------*/
/* -- TEST CASES -- */ /* -- TEST CASES -- */
@ -49,85 +48,49 @@ using namespace std::chrono_literals;
TEST(Connection, CanBeDefaultConstructed) TEST(Connection, CanBeDefaultConstructed)
{ {
ASSERT_NO_THROW(auto con = sdbus::createConnection()); ASSERT_NO_THROW(auto con = sdbus::createBusConnection());
} }
TEST(Connection, CanRequestRegisteredDbusName) TEST(Connection, CanRequestName)
{ {
auto connection = sdbus::createConnection(); auto connection = sdbus::createBusConnection();
ASSERT_NO_THROW(connection->requestName(BUS_NAME)) // In case of system bus connection, requesting may throw as we need to allow that first through a config file in /etc/dbus-1/system.d
ASSERT_NO_THROW(connection->requestName(SERVICE_NAME))
<< "Perhaps you've forgotten to copy `org.sdbuscpp.integrationtests.conf` file to `/etc/dbus-1/system.d` directory before running the tests?"; << "Perhaps you've forgotten to copy `org.sdbuscpp.integrationtests.conf` file to `/etc/dbus-1/system.d` directory before running the tests?";
} }
TEST(Connection, CannotRequestNonregisteredDbusName) TEST(SystemBusConnection, CannotRequestNonregisteredDbusName)
{ {
auto connection = sdbus::createConnection(); auto connection = sdbus::createSystemBusConnection();
ASSERT_THROW(connection->requestName("some.random.not.supported.dbus.name"), sdbus::Error); sdbus::ServiceName notSupportedBusName{"some.random.not.supported.dbus.name"};
ASSERT_THROW(connection->requestName(notSupportedBusName), sdbus::Error);
} }
TEST(Connection, CanReleasedRequestedName) TEST(Connection, CanReleaseRequestedName)
{ {
auto connection = sdbus::createConnection(); auto connection = sdbus::createBusConnection();
connection->requestName(SERVICE_NAME);
connection->requestName(BUS_NAME); ASSERT_NO_THROW(connection->releaseName(SERVICE_NAME));
ASSERT_NO_THROW(connection->releaseName(BUS_NAME));
} }
TEST(Connection, CannotReleaseNonrequestedName) TEST(Connection, CannotReleaseNonrequestedName)
{ {
auto connection = sdbus::createConnection(); auto connection = sdbus::createBusConnection();
ASSERT_THROW(connection->releaseName("some.random.nonrequested.name"), sdbus::Error); sdbus::ServiceName notAcquiredBusName{"some.random.unacquired.name"};
ASSERT_THROW(connection->releaseName(notAcquiredBusName), sdbus::Error);
} }
TEST(Connection, CanEnterAndLeaveEventLoop) TEST(Connection, CanEnterAndLeaveInternalEventLoop)
{ {
auto connection = sdbus::createConnection(); auto connection = sdbus::createBusConnection();
connection->requestName(BUS_NAME); connection->requestName(SERVICE_NAME);
std::thread t([&](){ connection->enterEventLoop(); }); std::thread t([&](){ connection->enterEventLoop(); });
connection->leaveEventLoop(); connection->leaveEventLoop();
t.join(); t.join();
} }
TEST(Connection, PollDataGetZeroTimeout)
{
sdbus::IConnection::PollData pd{};
pd.timeout_usec = 0;
ASSERT_TRUE(pd.getRelativeTimeout().has_value());
EXPECT_THAT(pd.getRelativeTimeout().value(), Eq(std::chrono::microseconds::zero()));
EXPECT_THAT(pd.getPollTimeout(), Eq(0));
}
TEST(Connection, PollDataGetInfiniteTimeout)
{
sdbus::IConnection::PollData pd{};
pd.timeout_usec = UINT64_MAX;
ASSERT_FALSE(pd.getRelativeTimeout().has_value());
EXPECT_THAT(pd.getPollTimeout(), Eq(-1));
}
TEST(Connection, PollDataGetZeroRelativeTimeoutForPast)
{
sdbus::IConnection::PollData pd{};
auto past = std::chrono::steady_clock::now() - 10s;
pd.timeout_usec = std::chrono::duration_cast<std::chrono::microseconds>(past.time_since_epoch()).count();
ASSERT_TRUE(pd.getRelativeTimeout().has_value());
EXPECT_THAT(pd.getRelativeTimeout().value(), Eq(0us));
EXPECT_THAT(pd.getPollTimeout(), Eq(0));
}
TEST(Connection, PollDataGetRelativeTimeoutInTolerance)
{
sdbus::IConnection::PollData pd{};
constexpr auto TIMEOUT = 1s;
constexpr auto TOLERANCE = 100ms;
auto future = std::chrono::steady_clock::now() + TIMEOUT;
pd.timeout_usec = std::chrono::duration_cast<std::chrono::microseconds>(future.time_since_epoch()).count();
ASSERT_TRUE(pd.getRelativeTimeout().has_value());
EXPECT_GE(pd.getRelativeTimeout().value(), TIMEOUT - TOLERANCE);
EXPECT_LE(pd.getRelativeTimeout().value(), TIMEOUT + TOLERANCE);
EXPECT_GE(pd.getPollTimeout(), 900);
EXPECT_LE(pd.getPollTimeout(), 1100);
}

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file DBusGeneralTests.cpp * @file DBusGeneralTests.cpp
* *
@ -38,13 +38,15 @@
#include <fstream> #include <fstream>
#include <future> #include <future>
#include <unistd.h> #include <unistd.h>
#include <variant>
using ::testing::ElementsAre; using ::testing::ElementsAre;
using ::testing::Eq; using ::testing::Eq;
using namespace std::chrono_literals; using namespace std::chrono_literals;
using namespace sdbus::test; using namespace sdbus::test;
using namespace std::string_view_literals;
using AConnection = TestFixture; using ADirectConnection = TestFixtureWithDirectConnection;
/*-------------------------------------*/ /*-------------------------------------*/
/* -- TEST CASES -- */ /* -- TEST CASES -- */
@ -52,81 +54,131 @@ using AConnection = TestFixture;
TEST(AdaptorAndProxy, CanBeConstructedSuccesfully) TEST(AdaptorAndProxy, CanBeConstructedSuccesfully)
{ {
auto connection = sdbus::createConnection(); auto connection = sdbus::createBusConnection();
connection->requestName(BUS_NAME); connection->requestName(SERVICE_NAME);
ASSERT_NO_THROW(TestAdaptor adaptor(*connection, OBJECT_PATH)); ASSERT_NO_THROW(TestAdaptor adaptor(*connection, OBJECT_PATH));
ASSERT_NO_THROW(TestProxy proxy(BUS_NAME, OBJECT_PATH)); ASSERT_NO_THROW(TestProxy proxy(SERVICE_NAME, OBJECT_PATH));
connection->releaseName(SERVICE_NAME);
} }
TEST_F(AConnection, WillCallCallbackHandlerForIncomingMessageMatchingMatchRule) TEST(AProxy, DoesNotSupportMoveSemantics)
{ {
auto matchRule = "sender='" + BUS_NAME + "',path='" + OBJECT_PATH + "'"; static_assert(!std::is_move_constructible_v<DummyTestProxy>);
static_assert(!std::is_move_assignable_v<DummyTestProxy>);
}
TEST(AnAdaptor, DoesNotSupportMoveSemantics)
{
static_assert(!std::is_move_constructible_v<DummyTestAdaptor>);
static_assert(!std::is_move_assignable_v<DummyTestAdaptor>);
}
TYPED_TEST(AConnection, WillCallCallbackHandlerForIncomingMessageMatchingMatchRule)
{
auto matchRule = "sender='" + SERVICE_NAME + "',path='" + OBJECT_PATH + "'";
std::atomic<bool> matchingMessageReceived{false}; std::atomic<bool> matchingMessageReceived{false};
auto slot = s_proxyConnection->addMatch(matchRule, [&](sdbus::Message& msg) auto slot = this->s_proxyConnection->addMatch(matchRule, [&](sdbus::Message msg)
{ {
if(msg.getPath() == OBJECT_PATH) if(msg.getPath() == OBJECT_PATH)
matchingMessageReceived = true; matchingMessageReceived = true;
}); }, sdbus::return_slot);
m_adaptor->emitSimpleSignal(); this->m_adaptor->emitSimpleSignal();
ASSERT_TRUE(waitUntil(matchingMessageReceived)); ASSERT_TRUE(waitUntil(matchingMessageReceived));
} }
TEST_F(AConnection, WillUnsubscribeMatchRuleWhenClientDestroysTheAssociatedSlot) TYPED_TEST(AConnection, CanInstallMatchRuleAsynchronously)
{ {
auto matchRule = "sender='" + BUS_NAME + "',path='" + OBJECT_PATH + "'"; auto matchRule = "sender='" + SERVICE_NAME + "',path='" + OBJECT_PATH + "'";
std::atomic<bool> matchingMessageReceived{false}; std::atomic<bool> matchingMessageReceived{false};
auto slot = s_proxyConnection->addMatch(matchRule, [&](sdbus::Message& msg) std::atomic<bool> matchRuleInstalled{false};
auto slot = this->s_proxyConnection->addMatchAsync( matchRule
, [&](sdbus::Message msg)
{
if(msg.getPath() == OBJECT_PATH)
matchingMessageReceived = true;
}
, [&](sdbus::Message /*msg*/)
{
matchRuleInstalled = true;
}
, sdbus::return_slot );
EXPECT_TRUE(waitUntil(matchRuleInstalled));
this->m_adaptor->emitSimpleSignal();
ASSERT_TRUE(waitUntil(matchingMessageReceived));
}
TYPED_TEST(AConnection, WillUnsubscribeMatchRuleWhenClientDestroysTheAssociatedSlot)
{
auto matchRule = "sender='" + SERVICE_NAME + "',path='" + OBJECT_PATH + "'";
std::atomic<bool> matchingMessageReceived{false};
auto slot = this->s_proxyConnection->addMatch(matchRule, [&](sdbus::Message msg)
{ {
if(msg.getPath() == OBJECT_PATH) if(msg.getPath() == OBJECT_PATH)
matchingMessageReceived = true; matchingMessageReceived = true;
}); }, sdbus::return_slot);
slot.reset(); slot.reset();
m_adaptor->emitSimpleSignal(); this->m_adaptor->emitSimpleSignal();
ASSERT_FALSE(waitUntil(matchingMessageReceived, 2s)); ASSERT_FALSE(waitUntil(matchingMessageReceived, 1s));
} }
TEST_F(AConnection, CanAddFloatingMatchRule) TYPED_TEST(AConnection, CanAddFloatingMatchRule)
{ {
auto matchRule = "sender='" + BUS_NAME + "',path='" + OBJECT_PATH + "'"; auto matchRule = "sender='" + SERVICE_NAME + "',path='" + OBJECT_PATH + "'";
std::atomic<bool> matchingMessageReceived{false}; std::atomic<bool> matchingMessageReceived{false};
auto con = sdbus::createSystemBusConnection(); auto con = sdbus::createBusConnection();
con->enterEventLoopAsync(); con->enterEventLoopAsync();
auto callback = [&](sdbus::Message& msg) auto callback = [&](sdbus::Message msg)
{ {
if(msg.getPath() == OBJECT_PATH) if(msg.getPath() == OBJECT_PATH)
matchingMessageReceived = true; matchingMessageReceived = true;
}; };
con->addMatch(matchRule, std::move(callback), sdbus::floating_slot); con->addMatch(matchRule, std::move(callback));
m_adaptor->emitSimpleSignal(); this->m_adaptor->emitSimpleSignal();
assert(waitUntil(matchingMessageReceived, 2s)); [[maybe_unused]] auto gotMessage = waitUntil(matchingMessageReceived, 2s);
assert(gotMessage);
matchingMessageReceived = false; matchingMessageReceived = false;
con.reset(); con.reset();
m_adaptor->emitSimpleSignal(); this->m_adaptor->emitSimpleSignal();
ASSERT_FALSE(waitUntil(matchingMessageReceived, 2s)); ASSERT_FALSE(waitUntil(matchingMessageReceived, 1s));
} }
TEST_F(AConnection, WillNotPassToMatchCallbackMessagesThatDoNotMatchTheRule) TYPED_TEST(AConnection, WillNotPassToMatchCallbackMessagesThatDoNotMatchTheRule)
{ {
auto matchRule = "type='signal',interface='" + INTERFACE_NAME + "',member='simpleSignal'"; auto matchRule = "type='signal',interface='" + INTERFACE_NAME + "',member='simpleSignal'";
std::atomic<size_t> numberOfMatchingMessages{}; std::atomic<size_t> numberOfMatchingMessages{};
auto slot = s_proxyConnection->addMatch(matchRule, [&](sdbus::Message& msg) auto slot = this->s_proxyConnection->addMatch(matchRule, [&](sdbus::Message msg)
{ {
if(msg.getMemberName() == "simpleSignal") if(msg.getMemberName() == "simpleSignal"sv)
numberOfMatchingMessages++; numberOfMatchingMessages++;
}); }, sdbus::return_slot);
auto adaptor2 = std::make_unique<TestAdaptor>(*s_adaptorConnection, OBJECT_PATH_2); auto adaptor2 = std::make_unique<TestAdaptor>(*this->s_adaptorConnection, OBJECT_PATH_2);
m_adaptor->emitSignalWithMap({}); this->m_adaptor->emitSignalWithMap({});
adaptor2->emitSimpleSignal(); adaptor2->emitSimpleSignal();
m_adaptor->emitSimpleSignal(); this->m_adaptor->emitSimpleSignal();
ASSERT_TRUE(waitUntil([&](){ return numberOfMatchingMessages == 2; })); ASSERT_TRUE(waitUntil([&](){ return numberOfMatchingMessages == 2; }));
ASSERT_FALSE(waitUntil([&](){ return numberOfMatchingMessages > 2; }, 1s)); 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

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file DBusMethodsTests.cpp * @file DBusMethodsTests.cpp
* *
@ -50,135 +50,141 @@ using ::testing::NotNull;
using namespace std::chrono_literals; using namespace std::chrono_literals;
using namespace sdbus::test; using namespace sdbus::test;
using SdbusTestObject = TestFixture;
/*-------------------------------------*/ /*-------------------------------------*/
/* -- TEST CASES -- */ /* -- TEST CASES -- */
/*-------------------------------------*/ /*-------------------------------------*/
TEST_F(SdbusTestObject, CallsEmptyMethodSuccesfully) TYPED_TEST(SdbusTestObject, CallsEmptyMethodSuccesfully)
{ {
ASSERT_NO_THROW(m_proxy->noArgNoReturn()); ASSERT_NO_THROW(this->m_proxy->noArgNoReturn());
} }
TEST_F(SdbusTestObject, CallsMethodsWithBaseTypesSuccesfully) TYPED_TEST(SdbusTestObject, CallsMethodsWithBaseTypesSuccesfully)
{ {
auto resInt = m_proxy->getInt(); auto resInt = this->m_proxy->getInt();
ASSERT_THAT(resInt, Eq(INT32_VALUE)); ASSERT_THAT(resInt, Eq(INT32_VALUE));
auto multiplyRes = m_proxy->multiply(INT64_VALUE, DOUBLE_VALUE); auto multiplyRes = this->m_proxy->multiply(INT64_VALUE, DOUBLE_VALUE);
ASSERT_THAT(multiplyRes, Eq(INT64_VALUE * DOUBLE_VALUE)); ASSERT_THAT(multiplyRes, Eq(INT64_VALUE * DOUBLE_VALUE));
} }
TEST_F(SdbusTestObject, CallsMethodsWithTuplesSuccesfully) TYPED_TEST(SdbusTestObject, CallsMethodsWithTuplesSuccesfully)
{ {
auto resTuple = m_proxy->getTuple(); auto resTuple = this->m_proxy->getTuple();
ASSERT_THAT(std::get<0>(resTuple), Eq(UINT32_VALUE)); ASSERT_THAT(std::get<0>(resTuple), Eq(UINT32_VALUE));
ASSERT_THAT(std::get<1>(resTuple), Eq(STRING_VALUE)); ASSERT_THAT(std::get<1>(resTuple), Eq(STRING_VALUE));
} }
TEST_F(SdbusTestObject, CallsMethodsWithStructSuccesfully) TYPED_TEST(SdbusTestObject, CallsMethodsWithStructSuccesfully)
{ {
sdbus::Struct<uint8_t, int16_t, double, std::string, std::vector<int16_t>> a{}; sdbus::Struct<uint8_t, int16_t, double, std::string, std::vector<int16_t>> a{};
auto vectorRes = m_proxy->getInts16FromStruct(a); auto vectorRes = this->m_proxy->getInts16FromStruct(a);
ASSERT_THAT(vectorRes, Eq(std::vector<int16_t>{0})); // because second item is by default initialized to 0 ASSERT_THAT(vectorRes, Eq(std::vector<int16_t>{0})); // because second item is by default initialized to 0
sdbus::Struct<uint8_t, int16_t, double, std::string, std::vector<int16_t>> b{ sdbus::Struct<uint8_t, int16_t, double, std::string, std::vector<int16_t>> b{
UINT8_VALUE, INT16_VALUE, DOUBLE_VALUE, STRING_VALUE, {INT16_VALUE, -INT16_VALUE} UINT8_VALUE, INT16_VALUE, DOUBLE_VALUE, STRING_VALUE, {INT16_VALUE, -INT16_VALUE}
}; };
vectorRes = m_proxy->getInts16FromStruct(b); vectorRes = this->m_proxy->getInts16FromStruct(b);
ASSERT_THAT(vectorRes, Eq(std::vector<int16_t>{INT16_VALUE, INT16_VALUE, -INT16_VALUE})); ASSERT_THAT(vectorRes, Eq(std::vector<int16_t>{INT16_VALUE, INT16_VALUE, -INT16_VALUE}));
} }
TEST_F(SdbusTestObject, CallsMethodWithVariantSuccesfully) TYPED_TEST(SdbusTestObject, CallsMethodWithVariantSuccesfully)
{ {
sdbus::Variant v{DOUBLE_VALUE}; sdbus::Variant v{DOUBLE_VALUE};
auto variantRes = m_proxy->processVariant(v); sdbus::Variant variantRes = this->m_proxy->processVariant(v);
ASSERT_THAT(variantRes.get<int32_t>(), Eq(static_cast<int32_t>(DOUBLE_VALUE))); ASSERT_THAT(variantRes.get<int32_t>(), Eq(static_cast<int32_t>(DOUBLE_VALUE)));
} }
TEST_F(SdbusTestObject, CallsMethodWithStructVariantsAndGetMapSuccesfully) TYPED_TEST(SdbusTestObject, CallsMethodWithStdVariantSuccesfully)
{
std::variant<int32_t, double, std::string> v{DOUBLE_VALUE};
auto variantRes = this->m_proxy->processVariant(v);
ASSERT_THAT(std::get<int32_t>(variantRes), Eq(static_cast<int32_t>(DOUBLE_VALUE)));
}
TYPED_TEST(SdbusTestObject, CallsMethodWithStructVariantsAndGetMapSuccesfully)
{ {
std::vector<int32_t> x{-2, 0, 2}; std::vector<int32_t> x{-2, 0, 2};
sdbus::Struct<sdbus::Variant, sdbus::Variant> y{false, true}; sdbus::Struct<sdbus::Variant, sdbus::Variant> y{false, true};
auto mapOfVariants = m_proxy->getMapOfVariants(x, y); std::map<int32_t, sdbus::Variant> mapOfVariants = this->m_proxy->getMapOfVariants(x, y);
decltype(mapOfVariants) res{{-2, false}, {0, false}, {2, true}}; decltype(mapOfVariants) res{ {-2, sdbus::Variant{false}}
, {0, sdbus::Variant{false}}
, {2, sdbus::Variant{true}}};
ASSERT_THAT(mapOfVariants[-2].get<bool>(), Eq(res[-2].get<bool>())); ASSERT_THAT(mapOfVariants[-2].get<bool>(), Eq(res[-2].get<bool>()));
ASSERT_THAT(mapOfVariants[0].get<bool>(), Eq(res[0].get<bool>())); ASSERT_THAT(mapOfVariants[0].get<bool>(), Eq(res[0].get<bool>()));
ASSERT_THAT(mapOfVariants[2].get<bool>(), Eq(res[2].get<bool>())); ASSERT_THAT(mapOfVariants[2].get<bool>(), Eq(res[2].get<bool>()));
} }
TEST_F(SdbusTestObject, CallsMethodWithStructInStructSuccesfully) TYPED_TEST(SdbusTestObject, CallsMethodWithStructInStructSuccesfully)
{ {
auto val = m_proxy->getStructInStruct(); auto val = this->m_proxy->getStructInStruct();
ASSERT_THAT(val.get<0>(), Eq(STRING_VALUE)); ASSERT_THAT(val.template get<0>(), Eq(STRING_VALUE));
ASSERT_THAT(std::get<0>(std::get<1>(val))[INT32_VALUE], Eq(INT32_VALUE)); ASSERT_THAT(std::get<0>(std::get<1>(val))[INT32_VALUE], Eq(INT32_VALUE));
} }
TEST_F(SdbusTestObject, CallsMethodWithTwoStructsSuccesfully) TYPED_TEST(SdbusTestObject, CallsMethodWithTwoStructsSuccesfully)
{ {
auto val = m_proxy->sumStructItems({1, 2}, {3, 4}); auto val = this->m_proxy->sumStructItems({1, 2}, {3, 4});
ASSERT_THAT(val, Eq(1 + 2 + 3 + 4)); ASSERT_THAT(val, Eq(1 + 2 + 3 + 4));
} }
TEST_F(SdbusTestObject, CallsMethodWithTwoVectorsSuccesfully) TYPED_TEST(SdbusTestObject, CallsMethodWithTwoVectorsSuccesfully)
{ {
auto val = m_proxy->sumVectorItems({1, 7}, {2, 3}); auto val = this->m_proxy->sumArrayItems({1, 7}, {2, 3, 4});
ASSERT_THAT(val, Eq(1 + 7 + 2 + 3)); ASSERT_THAT(val, Eq(1 + 7 + 2 + 3 + 4));
} }
TEST_F(SdbusTestObject, CallsMethodWithSignatureSuccesfully) TYPED_TEST(SdbusTestObject, CallsMethodWithSignatureSuccesfully)
{ {
auto resSignature = m_proxy->getSignature(); auto resSignature = this->m_proxy->getSignature();
ASSERT_THAT(resSignature, Eq(SIGNATURE_VALUE)); ASSERT_THAT(resSignature, Eq(SIGNATURE_VALUE));
} }
TEST_F(SdbusTestObject, CallsMethodWithObjectPathSuccesfully) TYPED_TEST(SdbusTestObject, CallsMethodWithObjectPathSuccesfully)
{ {
auto resObjectPath = m_proxy->getObjPath(); auto resObjectPath = this->m_proxy->getObjPath();
ASSERT_THAT(resObjectPath, Eq(OBJECT_PATH_VALUE)); ASSERT_THAT(resObjectPath, Eq(OBJECT_PATH_VALUE));
} }
TEST_F(SdbusTestObject, CallsMethodWithUnixFdSuccesfully) TYPED_TEST(SdbusTestObject, CallsMethodWithUnixFdSuccesfully)
{ {
auto resUnixFd = m_proxy->getUnixFd(); auto resUnixFd = this->m_proxy->getUnixFd();
ASSERT_THAT(resUnixFd.get(), Gt(UNIX_FD_VALUE)); ASSERT_THAT(resUnixFd.get(), Gt(UNIX_FD_VALUE));
} }
TEST_F(SdbusTestObject, CallsMethodWithComplexTypeSuccesfully) TYPED_TEST(SdbusTestObject, CallsMethodWithComplexTypeSuccesfully)
{ {
auto resComplex = m_proxy->getComplex(); auto resComplex = this->m_proxy->getComplex();
ASSERT_THAT(resComplex.count(0), Eq(1)); ASSERT_THAT(resComplex.count(0), Eq(1));
} }
TEST_F(SdbusTestObject, CallsMultiplyMethodWithNoReplyFlag) TYPED_TEST(SdbusTestObject, CallsMultiplyMethodWithNoReplyFlag)
{ {
m_proxy->multiplyWithNoReply(INT64_VALUE, DOUBLE_VALUE); this->m_proxy->multiplyWithNoReply(INT64_VALUE, DOUBLE_VALUE);
ASSERT_TRUE(waitUntil(m_adaptor->m_wasMultiplyCalled)); ASSERT_TRUE(waitUntil(this->m_adaptor->m_wasMultiplyCalled));
ASSERT_THAT(m_adaptor->m_multiplyResult, Eq(INT64_VALUE * DOUBLE_VALUE)); ASSERT_THAT(this->m_adaptor->m_multiplyResult, Eq(INT64_VALUE * DOUBLE_VALUE));
} }
TEST_F(SdbusTestObject, CallsMethodWithCustomTimeoutSuccessfully) TYPED_TEST(SdbusTestObject, CallsMethodWithCustomTimeoutSuccessfully)
{ {
auto res = m_proxy->doOperationWithTimeout(500ms, 20); // The operation will take 20ms, but the timeout is 500ms, so we are fine auto res = this->m_proxy->doOperationWithTimeout(500ms, (20ms).count()); // The operation will take 20ms, but the timeout is 500ms, so we are fine
ASSERT_THAT(res, Eq(20)); ASSERT_THAT(res, Eq(20));
} }
TEST_F(SdbusTestObject, ThrowsTimeoutErrorWhenMethodTimesOut) TYPED_TEST(SdbusTestObject, ThrowsTimeoutErrorWhenMethodTimesOut)
{ {
auto start = std::chrono::steady_clock::now(); auto start = std::chrono::steady_clock::now();
try try
{ {
m_proxy->doOperationWithTimeout(1us, 1000); // The operation will take 1s, but the timeout is 1us, so we should time out this->m_proxy->doOperationWithTimeout(1us, (1s).count()); // The operation will take 1s, but the timeout is 1us, so we should time out
FAIL() << "Expected sdbus::Error exception"; FAIL() << "Expected sdbus::Error exception";
} }
catch (const sdbus::Error& e) catch (const sdbus::Error& e)
{ {
ASSERT_THAT(e.getName(), AnyOf("org.freedesktop.DBus.Error.Timeout", "org.freedesktop.DBus.Error.NoReply")); 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; auto measuredTimeout = std::chrono::steady_clock::now() - start;
ASSERT_THAT(measuredTimeout, Le(50ms)); ASSERT_THAT(measuredTimeout, Le(50ms));
} }
@ -188,11 +194,11 @@ TEST_F(SdbusTestObject, ThrowsTimeoutErrorWhenMethodTimesOut)
} }
} }
TEST_F(SdbusTestObject, CallsMethodThatThrowsError) TYPED_TEST(SdbusTestObject, CallsMethodThatThrowsError)
{ {
try try
{ {
m_proxy->throwError(); this->m_proxy->throwError();
FAIL() << "Expected sdbus::Error exception"; FAIL() << "Expected sdbus::Error exception";
} }
catch (const sdbus::Error& e) catch (const sdbus::Error& e)
@ -206,69 +212,111 @@ TEST_F(SdbusTestObject, CallsMethodThatThrowsError)
} }
} }
TEST_F(SdbusTestObject, CallsErrorThrowingMethodWithDontExpectReplySet) TYPED_TEST(SdbusTestObject, CallsErrorThrowingMethodWithDontExpectReplySet)
{ {
ASSERT_NO_THROW(m_proxy->throwErrorWithNoReply()); ASSERT_NO_THROW(this->m_proxy->throwErrorWithNoReply());
ASSERT_TRUE(waitUntil(m_adaptor->m_wasThrowErrorCalled)); ASSERT_TRUE(waitUntil(this->m_adaptor->m_wasThrowErrorCalled));
} }
TEST_F(SdbusTestObject, FailsCallingNonexistentMethod) TYPED_TEST(SdbusTestObject, FailsCallingNonexistentMethod)
{ {
ASSERT_THROW(m_proxy->callNonexistentMethod(), sdbus::Error); ASSERT_THROW(this->m_proxy->callNonexistentMethod(), sdbus::Error);
} }
TEST_F(SdbusTestObject, FailsCallingMethodOnNonexistentInterface) TYPED_TEST(SdbusTestObject, FailsCallingMethodOnNonexistentInterface)
{ {
ASSERT_THROW(m_proxy->callMethodOnNonexistentInterface(), sdbus::Error); ASSERT_THROW(this->m_proxy->callMethodOnNonexistentInterface(), sdbus::Error);
} }
TEST_F(SdbusTestObject, FailsCallingMethodOnNonexistentDestination) TYPED_TEST(SdbusTestObject, FailsCallingMethodOnNonexistentDestination)
{ {
TestProxy proxy("sdbuscpp.destination.that.does.not.exist", OBJECT_PATH); TestProxy proxy(sdbus::ServiceName{"sdbuscpp.destination.that.does.not.exist"}, OBJECT_PATH);
ASSERT_THROW(proxy.getInt(), sdbus::Error); ASSERT_THROW(proxy.getInt(), sdbus::Error);
} }
TEST_F(SdbusTestObject, FailsCallingMethodOnNonexistentObject) TYPED_TEST(SdbusTestObject, FailsCallingMethodOnNonexistentObject)
{ {
TestProxy proxy(BUS_NAME, "/sdbuscpp/path/that/does/not/exist"); TestProxy proxy(SERVICE_NAME, sdbus::ObjectPath{"/sdbuscpp/path/that/does/not/exist"});
ASSERT_THROW(proxy.getInt(), sdbus::Error); ASSERT_THROW(proxy.getInt(), sdbus::Error);
} }
TEST_F(SdbusTestObject, CanReceiveSignalWhileMakingMethodCall) TYPED_TEST(SdbusTestObject, CanReceiveSignalWhileMakingMethodCall)
{ {
m_proxy->emitTwoSimpleSignals(); this->m_proxy->emitTwoSimpleSignals();
ASSERT_TRUE(waitUntil(m_proxy->m_gotSimpleSignal)); EXPECT_TRUE(waitUntil(this->m_proxy->m_gotSimpleSignal));
ASSERT_TRUE(waitUntil(m_proxy->m_gotSignalWithMap)); EXPECT_TRUE(waitUntil(this->m_proxy->m_gotSignalWithMap));
} }
TEST_F(SdbusTestObject, CanAccessAssociatedMethodCallMessageInMethodCallHandler) TYPED_TEST(SdbusTestObject, CanAccessAssociatedMethodCallMessageInMethodCallHandler)
{ {
m_proxy->doOperation(10); // This will save pointer to method call message on server side this->m_proxy->doOperation(10); // This will save pointer to method call message on server side
ASSERT_THAT(m_adaptor->m_methodCallMsg, NotNull()); ASSERT_THAT(this->m_adaptor->m_methodCallMsg, NotNull());
ASSERT_THAT(m_adaptor->m_methodCallMemberName, Eq("doOperation")); ASSERT_THAT(this->m_adaptor->m_methodName, Eq("doOperation"));
} }
TEST_F(SdbusTestObject, CanAccessAssociatedMethodCallMessageInAsyncMethodCallHandler) TYPED_TEST(SdbusTestObject, CanAccessAssociatedMethodCallMessageInAsyncMethodCallHandler)
{ {
m_proxy->doOperationAsync(10); // This will save pointer to method call message on server side this->m_proxy->doOperationAsync(10); // This will save pointer to method call message on server side
ASSERT_THAT(m_adaptor->m_methodCallMsg, NotNull()); ASSERT_THAT(this->m_adaptor->m_methodCallMsg, NotNull());
ASSERT_THAT(m_adaptor->m_methodCallMemberName, Eq("doOperationAsync")); ASSERT_THAT(this->m_adaptor->m_methodName, Eq("doOperationAsync"));
} }
#if LIBSYSTEMD_VERSION>=240 #if LIBSYSTEMD_VERSION>=240
TEST_F(SdbusTestObject, CanSetGeneralMethodTimeoutWithLibsystemdVersionGreaterThan239) TYPED_TEST(SdbusTestObject, CanSetGeneralMethodTimeoutWithLibsystemdVersionGreaterThan239)
{ {
s_adaptorConnection->setMethodCallTimeout(5000000); this->s_adaptorConnection->setMethodCallTimeout(5000000);
ASSERT_THAT(s_adaptorConnection->getMethodCallTimeout(), Eq(5000000)); ASSERT_THAT(this->s_adaptorConnection->getMethodCallTimeout(), Eq(5000000));
} }
#else #else
TEST_F(SdbusTestObject, CannotSetGeneralMethodTimeoutWithLibsystemdVersionLessThan240) TYPED_TEST(SdbusTestObject, CannotSetGeneralMethodTimeoutWithLibsystemdVersionLessThan240)
{ {
ASSERT_THROW(s_adaptorConnection->setMethodCallTimeout(5000000), sdbus::Error); ASSERT_THROW(this->s_adaptorConnection->setMethodCallTimeout(5000000), sdbus::Error);
ASSERT_THROW(s_adaptorConnection->getMethodCallTimeout(), sdbus::Error); ASSERT_THROW(this->s_adaptorConnection->getMethodCallTimeout(), sdbus::Error);
} }
#endif #endif
TYPED_TEST(SdbusTestObject, CanCallMethodSynchronouslyWithoutAnEventLoopThread)
{
auto proxy = std::make_unique<TestProxy>(SERVICE_NAME, OBJECT_PATH, sdbus::dont_run_event_loop_thread);
auto multiplyRes = proxy->multiply(INT64_VALUE, DOUBLE_VALUE);
ASSERT_THAT(multiplyRes, Eq(INT64_VALUE * DOUBLE_VALUE));
}
TYPED_TEST(SdbusTestObject, CanRegisterAdditionalVTableDynamicallyAtAnyTime)
{
auto& object = this->m_adaptor->getObject();
sdbus::InterfaceName interfaceName{"org.sdbuscpp.integrationtests2"};
auto vtableSlot = object.addVTable( interfaceName
, { sdbus::registerMethod("add").implementedAs([](const int64_t& a, const double& b){ return a + b; })
, sdbus::registerMethod("subtract").implementedAs([](const int& a, const int& b){ return a - b; }) }
, sdbus::return_slot );
// The new remote vtable is registered as long as we keep vtableSlot, so remote method calls now should pass
auto proxy = sdbus::createProxy(SERVICE_NAME, OBJECT_PATH, sdbus::dont_run_event_loop_thread);
int result{};
proxy->callMethod("subtract").onInterface(interfaceName).withArguments(10, 2).storeResultsTo(result);
ASSERT_THAT(result, Eq(8));
}
TYPED_TEST(SdbusTestObject, CanUnregisterAdditionallyRegisteredVTableAtAnyTime)
{
auto& object = this->m_adaptor->getObject();
sdbus::InterfaceName interfaceName{"org.sdbuscpp.integrationtests2"};
auto vtableSlot = object.addVTable( interfaceName
, { sdbus::registerMethod("add").implementedAs([](const int64_t& a, const double& b){ return a + b; })
, sdbus::registerMethod("subtract").implementedAs([](const int& a, const int& b){ return a - b; }) }
, sdbus::return_slot );
vtableSlot.reset(); // Letting the slot go means letting go the associated vtable registration
// No such remote D-Bus method under given interface exists anymore...
auto proxy = sdbus::createProxy(SERVICE_NAME, OBJECT_PATH, sdbus::dont_run_event_loop_thread);
ASSERT_THROW(proxy->callMethod("subtract").onInterface(interfaceName).withArguments(10, 2), sdbus::Error);
}

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file DBusPropertiesTests.cpp * @file DBusPropertiesTests.cpp
* *
@ -51,35 +51,33 @@ using ::testing::IsEmpty;
using namespace std::chrono_literals; using namespace std::chrono_literals;
using namespace sdbus::test; using namespace sdbus::test;
using SdbusTestObject = TestFixture;
/*-------------------------------------*/ /*-------------------------------------*/
/* -- TEST CASES -- */ /* -- TEST CASES -- */
/*-------------------------------------*/ /*-------------------------------------*/
TEST_F(SdbusTestObject, ReadsReadOnlyPropertySuccesfully) TYPED_TEST(SdbusTestObject, ReadsReadOnlyPropertySuccesfully)
{ {
ASSERT_THAT(m_proxy->state(), Eq(DEFAULT_STATE_VALUE)); ASSERT_THAT(this->m_proxy->state(), Eq(DEFAULT_STATE_VALUE));
} }
TEST_F(SdbusTestObject, FailsWritingToReadOnlyProperty) TYPED_TEST(SdbusTestObject, FailsWritingToReadOnlyProperty)
{ {
ASSERT_THROW(m_proxy->setStateProperty("new_value"), sdbus::Error); ASSERT_THROW(this->m_proxy->setStateProperty("new_value"), sdbus::Error);
} }
TEST_F(SdbusTestObject, WritesAndReadsReadWritePropertySuccesfully) TYPED_TEST(SdbusTestObject, WritesAndReadsReadWritePropertySuccesfully)
{ {
uint32_t newActionValue = 5678; uint32_t newActionValue = 5678;
m_proxy->action(newActionValue); this->m_proxy->action(newActionValue);
ASSERT_THAT(m_proxy->action(), Eq(newActionValue)); ASSERT_THAT(this->m_proxy->action(), Eq(newActionValue));
} }
TEST_F(SdbusTestObject, CanAccessAssociatedPropertySetMessageInPropertySetHandler) TYPED_TEST(SdbusTestObject, CanAccessAssociatedPropertySetMessageInPropertySetHandler)
{ {
m_proxy->blocking(true); // This will save pointer to property get message on server side this->m_proxy->blocking(true); // This will save pointer to property get message on server side
ASSERT_THAT(m_adaptor->m_propertySetMsg, NotNull()); ASSERT_THAT(this->m_adaptor->m_propertySetMsg, NotNull());
ASSERT_THAT(m_adaptor->m_propertySetSender, Not(IsEmpty())); ASSERT_THAT(this->m_adaptor->m_propertySetSender, Not(IsEmpty()));
} }

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file AdaptorAndProxy_test.cpp * @file AdaptorAndProxy_test.cpp
* *
@ -44,114 +44,124 @@ using ::testing::NotNull;
using namespace std::chrono_literals; using namespace std::chrono_literals;
using namespace sdbus::test; using namespace sdbus::test;
using SdbusTestObject = TestFixture;
/*-------------------------------------*/ /*-------------------------------------*/
/* -- TEST CASES -- */ /* -- TEST CASES -- */
/*-------------------------------------*/ /*-------------------------------------*/
TEST_F(SdbusTestObject, EmitsSimpleSignalSuccesfully) TYPED_TEST(SdbusTestObject, EmitsSimpleSignalSuccesfully)
{ {
m_adaptor->emitSimpleSignal(); this->m_adaptor->emitSimpleSignal();
ASSERT_TRUE(waitUntil(m_proxy->m_gotSimpleSignal)); ASSERT_TRUE(waitUntil(this->m_proxy->m_gotSimpleSignal));
} }
TEST_F(SdbusTestObject, EmitsSimpleSignalToMultipleProxiesSuccesfully) TYPED_TEST(SdbusTestObject, EmitsSimpleSignalToMultipleProxiesSuccesfully)
{ {
auto proxy1 = std::make_unique<TestProxy>(*s_adaptorConnection, BUS_NAME, OBJECT_PATH); auto proxy1 = std::make_unique<TestProxy>(*this->s_adaptorConnection, SERVICE_NAME, OBJECT_PATH);
auto proxy2 = std::make_unique<TestProxy>(*s_adaptorConnection, BUS_NAME, OBJECT_PATH); auto proxy2 = std::make_unique<TestProxy>(*this->s_adaptorConnection, SERVICE_NAME, OBJECT_PATH);
m_adaptor->emitSimpleSignal(); this->m_adaptor->emitSimpleSignal();
ASSERT_TRUE(waitUntil(m_proxy->m_gotSimpleSignal)); ASSERT_TRUE(waitUntil(this->m_proxy->m_gotSimpleSignal));
ASSERT_TRUE(waitUntil(proxy1->m_gotSimpleSignal)); ASSERT_TRUE(waitUntil(proxy1->m_gotSimpleSignal));
ASSERT_TRUE(waitUntil(proxy2->m_gotSimpleSignal)); ASSERT_TRUE(waitUntil(proxy2->m_gotSimpleSignal));
} }
TEST_F(SdbusTestObject, ProxyDoesNotReceiveSignalFromOtherBusName) TYPED_TEST(SdbusTestObject, ProxyDoesNotReceiveSignalFromOtherBusName)
{ {
auto otherBusName = BUS_NAME + "2"; sdbus::ServiceName otherBusName{SERVICE_NAME + "2"};
auto connection2 = sdbus::createConnection(otherBusName); auto connection2 = sdbus::createBusConnection(otherBusName);
auto adaptor2 = std::make_unique<TestAdaptor>(*connection2, OBJECT_PATH); auto adaptor2 = std::make_unique<TestAdaptor>(*connection2, OBJECT_PATH);
adaptor2->emitSimpleSignal(); adaptor2->emitSimpleSignal();
ASSERT_FALSE(waitUntil(m_proxy->m_gotSimpleSignal, 2s)); ASSERT_FALSE(waitUntil(this->m_proxy->m_gotSimpleSignal, 1s));
} }
TEST_F(SdbusTestObject, EmitsSignalWithMapSuccesfully) TYPED_TEST(SdbusTestObject, EmitsSignalWithMapSuccesfully)
{ {
m_adaptor->emitSignalWithMap({{0, "zero"}, {1, "one"}}); this->m_adaptor->emitSignalWithMap({{0, "zero"}, {1, "one"}});
ASSERT_TRUE(waitUntil(m_proxy->m_gotSignalWithMap)); ASSERT_TRUE(waitUntil(this->m_proxy->m_gotSignalWithMap));
ASSERT_THAT(m_proxy->m_mapFromSignal[0], Eq("zero")); ASSERT_THAT(this->m_proxy->m_mapFromSignal[0], Eq("zero"));
ASSERT_THAT(m_proxy->m_mapFromSignal[1], Eq("one")); ASSERT_THAT(this->m_proxy->m_mapFromSignal[1], Eq("one"));
} }
TEST_F(SdbusTestObject, EmitsSignalWithVariantSuccesfully) TYPED_TEST(SdbusTestObject, EmitsSignalWithLargeMapSuccesfully)
{
std::map<int32_t, std::string> largeMap;
for (int32_t i = 0; i < 20'000; ++i)
largeMap.emplace(i, "This is string nr. " + std::to_string(i+1));
this->m_adaptor->emitSignalWithMap(largeMap);
ASSERT_TRUE(waitUntil(this->m_proxy->m_gotSignalWithMap));
ASSERT_THAT(this->m_proxy->m_mapFromSignal[0], Eq("This is string nr. 1"));
ASSERT_THAT(this->m_proxy->m_mapFromSignal[1], Eq("This is string nr. 2"));
}
TYPED_TEST(SdbusTestObject, EmitsSignalWithVariantSuccesfully)
{ {
double d = 3.14; double d = 3.14;
m_adaptor->emitSignalWithVariant(d); this->m_adaptor->emitSignalWithVariant(sdbus::Variant{d});
ASSERT_TRUE(waitUntil(m_proxy->m_gotSignalWithVariant)); ASSERT_TRUE(waitUntil(this->m_proxy->m_gotSignalWithVariant));
ASSERT_THAT(m_proxy->m_variantFromSignal, DoubleEq(d)); ASSERT_THAT(this->m_proxy->m_variantFromSignal, DoubleEq(d));
} }
TEST_F(SdbusTestObject, EmitsSignalWithoutRegistrationSuccesfully) TYPED_TEST(SdbusTestObject, EmitsSignalWithoutRegistrationSuccesfully)
{ {
m_adaptor->emitSignalWithoutRegistration({"platform", {"av"}}); this->m_adaptor->emitSignalWithoutRegistration({"platform", sdbus::Signature{"av"}});
ASSERT_TRUE(waitUntil(m_proxy->m_gotSignalWithSignature)); ASSERT_TRUE(waitUntil(this->m_proxy->m_gotSignalWithSignature));
ASSERT_THAT(m_proxy->m_signatureFromSignal["platform"], Eq("av")); ASSERT_THAT(this->m_proxy->m_signatureFromSignal["platform"], Eq(sdbus::Signature{"av"}));
} }
TEST_F(SdbusTestObject, CanAccessAssociatedSignalMessageInSignalHandler) TYPED_TEST(SdbusTestObject, CanAccessAssociatedSignalMessageInSignalHandler)
{ {
m_adaptor->emitSimpleSignal(); this->m_adaptor->emitSimpleSignal();
waitUntil(m_proxy->m_gotSimpleSignal); waitUntil(this->m_proxy->m_gotSimpleSignal);
ASSERT_THAT(m_proxy->m_signalMsg, NotNull()); ASSERT_THAT(this->m_proxy->m_signalMsg, NotNull());
ASSERT_THAT(m_proxy->m_signalMemberName, Eq("simpleSignal")); ASSERT_THAT(this->m_proxy->m_signalName, Eq(std::string("simpleSignal")));
} }
TEST_F(SdbusTestObject, UnregistersSignalHandler) TYPED_TEST(SdbusTestObject, UnregistersSignalHandler)
{ {
ASSERT_NO_THROW(m_proxy->unregisterSimpleSignalHandler()); ASSERT_NO_THROW(this->m_proxy->unregisterSimpleSignalHandler());
m_adaptor->emitSimpleSignal(); this->m_adaptor->emitSimpleSignal();
ASSERT_FALSE(waitUntil(m_proxy->m_gotSimpleSignal, 2s)); ASSERT_FALSE(waitUntil(this->m_proxy->m_gotSimpleSignal, 1s));
} }
TEST_F(SdbusTestObject, UnregistersSignalHandlerForSomeProxies) TYPED_TEST(SdbusTestObject, UnregistersSignalHandlerForSomeProxies)
{ {
auto proxy1 = std::make_unique<TestProxy>(*s_adaptorConnection, BUS_NAME, OBJECT_PATH); auto proxy1 = std::make_unique<TestProxy>(*this->s_adaptorConnection, SERVICE_NAME, OBJECT_PATH);
auto proxy2 = std::make_unique<TestProxy>(*s_adaptorConnection, BUS_NAME, OBJECT_PATH); auto proxy2 = std::make_unique<TestProxy>(*this->s_adaptorConnection, SERVICE_NAME, OBJECT_PATH);
ASSERT_NO_THROW(m_proxy->unregisterSimpleSignalHandler()); ASSERT_NO_THROW(this->m_proxy->unregisterSimpleSignalHandler());
m_adaptor->emitSimpleSignal(); this->m_adaptor->emitSimpleSignal();
ASSERT_TRUE(waitUntil(proxy1->m_gotSimpleSignal)); ASSERT_TRUE(waitUntil(proxy1->m_gotSimpleSignal));
ASSERT_TRUE(waitUntil(proxy2->m_gotSimpleSignal)); ASSERT_TRUE(waitUntil(proxy2->m_gotSimpleSignal));
ASSERT_FALSE(waitUntil(m_proxy->m_gotSimpleSignal, 2s)); ASSERT_FALSE(waitUntil(this->m_proxy->m_gotSimpleSignal, 1s));
} }
TEST_F(SdbusTestObject, ReRegistersSignalHandler) TYPED_TEST(SdbusTestObject, ReRegistersSignalHandler)
{ {
// unregister simple-signal handler // unregister simple-signal handler
ASSERT_NO_THROW(m_proxy->unregisterSimpleSignalHandler()); ASSERT_NO_THROW(this->m_proxy->unregisterSimpleSignalHandler());
m_adaptor->emitSimpleSignal(); this->m_adaptor->emitSimpleSignal();
ASSERT_FALSE(waitUntil(m_proxy->m_gotSimpleSignal, 2s)); ASSERT_FALSE(waitUntil(this->m_proxy->m_gotSimpleSignal, 1s));
// re-register simple-signal handler // re-register simple-signal handler
ASSERT_NO_THROW(m_proxy->reRegisterSimpleSignalHandler()); ASSERT_NO_THROW(this->m_proxy->reRegisterSimpleSignalHandler());
m_adaptor->emitSimpleSignal(); this->m_adaptor->emitSimpleSignal();
ASSERT_TRUE(waitUntil(m_proxy->m_gotSimpleSignal)); ASSERT_TRUE(waitUntil(this->m_proxy->m_gotSimpleSignal));
} }

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file DBusStandardInterfacesTests.cpp * @file DBusStandardInterfacesTests.cpp
* *
@ -48,126 +48,204 @@ using ::testing::SizeIs;
using namespace std::chrono_literals; using namespace std::chrono_literals;
using namespace sdbus::test; using namespace sdbus::test;
using SdbusTestObject = TestFixture;
/*-------------------------------------*/ /*-------------------------------------*/
/* -- TEST CASES -- */ /* -- TEST CASES -- */
/*-------------------------------------*/ /*-------------------------------------*/
TEST_F(SdbusTestObject, PingsViaPeerInterface) TYPED_TEST(SdbusTestObject, PingsViaPeerInterface)
{ {
ASSERT_NO_THROW(m_proxy->Ping()); ASSERT_NO_THROW(this->m_proxy->Ping());
} }
TEST_F(SdbusTestObject, AnswersMachineUuidViaPeerInterface) TYPED_TEST(SdbusTestObject, AnswersMachineUuidViaPeerInterface)
{ {
// If /etc/machine-id does not exist in your system (which is very likely because you have if (::access("/etc/machine-id", F_OK) == -1 &&
// a non-systemd Linux), org.freedesktop.DBus.Peer.GetMachineId() will not work. To solve ::access("/var/lib/dbus/machine-id", F_OK) == -1)
// this, you can create /etc/machine-id yourself as symlink to /var/lib/dbus/machine-id, GTEST_SKIP() << "/etc/machine-id and /var/lib/dbus/machine-id files do not exist, GetMachineId() will not work";
// and then org.freedesktop.DBus.Peer.GetMachineId() will start to work.
if (::access("/etc/machine-id", F_OK) == -1)
GTEST_SKIP() << "/etc/machine-id file does not exist, GetMachineId() will not work";
ASSERT_NO_THROW(m_proxy->GetMachineId()); ASSERT_NO_THROW(this->m_proxy->GetMachineId());
} }
// TODO: Adjust expected xml and uncomment this test // TODO: Adjust expected xml and uncomment this test
//TEST_F(SdbusTestObject, AnswersXmlApiDescriptionViaIntrospectableInterface) //TYPED_TEST(SdbusTestObject, AnswersXmlApiDescriptionViaIntrospectableInterface)
//{ //{
// ASSERT_THAT(m_proxy->Introspect(), Eq(m_adaptor->getExpectedXmlApiDescription())); // ASSERT_THAT(this->m_proxy->Introspect(), Eq(this->m_adaptor->getExpectedXmlApiDescription()));
//} //}
TEST_F(SdbusTestObject, GetsPropertyViaPropertiesInterface) TYPED_TEST(SdbusTestObject, GetsPropertyViaPropertiesInterface)
{ {
ASSERT_THAT(m_proxy->Get(INTERFACE_NAME, "state").get<std::string>(), Eq(DEFAULT_STATE_VALUE)); ASSERT_THAT(this->m_proxy->Get(INTERFACE_NAME, "state").template get<std::string>(), Eq(DEFAULT_STATE_VALUE));
} }
TEST_F(SdbusTestObject, SetsPropertyViaPropertiesInterface) TYPED_TEST(SdbusTestObject, GetsPropertyAsynchronouslyViaPropertiesInterface)
{
std::promise<std::string> promise;
auto future = promise.get_future();
this->m_proxy->GetAsync(INTERFACE_NAME, "state", [&](std::optional<sdbus::Error> err, sdbus::Variant value)
{
if (!err)
promise.set_value(value.get<std::string>());
else
promise.set_exception(std::make_exception_ptr(*std::move(err)));
});
ASSERT_THAT(future.get(), Eq(DEFAULT_STATE_VALUE));
}
TYPED_TEST(SdbusTestObject, GetsPropertyAsynchronouslyViaPropertiesInterfaceWithFuture)
{
auto future = this->m_proxy->GetAsync(INTERFACE_NAME, "state", sdbus::with_future);
ASSERT_THAT(future.get().template get<std::string>(), Eq(DEFAULT_STATE_VALUE));
}
TYPED_TEST(SdbusTestObject, SetsPropertyViaPropertiesInterface)
{ {
uint32_t newActionValue = 2345; uint32_t newActionValue = 2345;
m_proxy->Set(INTERFACE_NAME, "action", newActionValue); this->m_proxy->Set(INTERFACE_NAME, "action", sdbus::Variant{newActionValue});
ASSERT_THAT(m_proxy->action(), Eq(newActionValue)); ASSERT_THAT(this->m_proxy->action(), Eq(newActionValue));
} }
TEST_F(SdbusTestObject, GetsAllPropertiesViaPropertiesInterface) TYPED_TEST(SdbusTestObject, SetsPropertyAsynchronouslyViaPropertiesInterface)
{ {
const auto properties = m_proxy->GetAll(INTERFACE_NAME); uint32_t newActionValue = 2346;
std::promise<void> promise;
auto future = promise.get_future();
this->m_proxy->SetAsync(INTERFACE_NAME, "action", sdbus::Variant{newActionValue}, [&](std::optional<sdbus::Error> err)
{
if (!err)
promise.set_value();
else
promise.set_exception(std::make_exception_ptr(*std::move(err)));
});
ASSERT_NO_THROW(future.get());
ASSERT_THAT(this->m_proxy->action(), Eq(newActionValue));
}
TYPED_TEST(SdbusTestObject, SetsPropertyAsynchronouslyViaPropertiesInterfaceWithFuture)
{
uint32_t newActionValue = 2347;
auto future = this->m_proxy->SetAsync(INTERFACE_NAME, "action", sdbus::Variant{newActionValue}, sdbus::with_future);
ASSERT_NO_THROW(future.get());
ASSERT_THAT(this->m_proxy->action(), Eq(newActionValue));
}
TYPED_TEST(SdbusTestObject, GetsAllPropertiesViaPropertiesInterface)
{
const auto properties = this->m_proxy->GetAll(INTERFACE_NAME);
ASSERT_THAT(properties, SizeIs(3)); ASSERT_THAT(properties, SizeIs(3));
EXPECT_THAT(properties.at("state").get<std::string>(), Eq(DEFAULT_STATE_VALUE)); EXPECT_THAT(properties.at(STATE_PROPERTY).template get<std::string>(), Eq(DEFAULT_STATE_VALUE));
EXPECT_THAT(properties.at("action").get<uint32_t>(), Eq(DEFAULT_ACTION_VALUE)); EXPECT_THAT(properties.at(ACTION_PROPERTY).template get<uint32_t>(), Eq(DEFAULT_ACTION_VALUE));
EXPECT_THAT(properties.at("blocking").get<bool>(), Eq(DEFAULT_BLOCKING_VALUE)); EXPECT_THAT(properties.at(BLOCKING_PROPERTY).template get<bool>(), Eq(DEFAULT_BLOCKING_VALUE));
} }
TEST_F(SdbusTestObject, EmitsPropertyChangedSignalForSelectedProperties) TYPED_TEST(SdbusTestObject, GetsAllPropertiesAsynchronouslyViaPropertiesInterface)
{
std::promise<std::map<sdbus::PropertyName, sdbus::Variant>> promise;
auto future = promise.get_future();
this->m_proxy->GetAllAsync(INTERFACE_NAME, [&](std::optional<sdbus::Error> err, std::map<sdbus::PropertyName, sdbus::Variant> value)
{
if (!err)
promise.set_value(std::move(value));
else
promise.set_exception(std::make_exception_ptr(*std::move(err)));
});
const auto properties = future.get();
ASSERT_THAT(properties, SizeIs(3));
EXPECT_THAT(properties.at(STATE_PROPERTY).get<std::string>(), Eq(DEFAULT_STATE_VALUE));
EXPECT_THAT(properties.at(ACTION_PROPERTY).get<uint32_t>(), Eq(DEFAULT_ACTION_VALUE));
EXPECT_THAT(properties.at(BLOCKING_PROPERTY).get<bool>(), Eq(DEFAULT_BLOCKING_VALUE));
}
TYPED_TEST(SdbusTestObject, GetsAllPropertiesAsynchronouslyViaPropertiesInterfaceWithFuture)
{
auto future = this->m_proxy->GetAllAsync(INTERFACE_NAME, sdbus::with_future);
auto properties = future.get();
ASSERT_THAT(properties, SizeIs(3));
EXPECT_THAT(properties.at(STATE_PROPERTY).template get<std::string>(), Eq(DEFAULT_STATE_VALUE));
EXPECT_THAT(properties.at(ACTION_PROPERTY).template get<uint32_t>(), Eq(DEFAULT_ACTION_VALUE));
EXPECT_THAT(properties.at(BLOCKING_PROPERTY).template get<bool>(), Eq(DEFAULT_BLOCKING_VALUE));
}
TYPED_TEST(SdbusTestObject, EmitsPropertyChangedSignalForSelectedProperties)
{ {
std::atomic<bool> signalReceived{false}; std::atomic<bool> signalReceived{false};
m_proxy->m_onPropertiesChangedHandler = [&signalReceived]( const std::string& interfaceName this->m_proxy->m_onPropertiesChangedHandler = [&signalReceived]( const sdbus::InterfaceName& interfaceName
, const std::map<std::string, sdbus::Variant>& changedProperties , const std::map<sdbus::PropertyName, sdbus::Variant>& changedProperties
, const std::vector<std::string>& /*invalidatedProperties*/ ) , const std::vector<sdbus::PropertyName>& /*invalidatedProperties*/ )
{ {
EXPECT_THAT(interfaceName, Eq(INTERFACE_NAME)); EXPECT_THAT(interfaceName, Eq(INTERFACE_NAME));
EXPECT_THAT(changedProperties, SizeIs(1)); EXPECT_THAT(changedProperties, SizeIs(1));
EXPECT_THAT(changedProperties.at("blocking").get<bool>(), Eq(!DEFAULT_BLOCKING_VALUE)); EXPECT_THAT(changedProperties.at(BLOCKING_PROPERTY).get<bool>(), Eq(!DEFAULT_BLOCKING_VALUE));
signalReceived = true; signalReceived = true;
}; };
m_proxy->blocking(!DEFAULT_BLOCKING_VALUE); this->m_proxy->blocking(!DEFAULT_BLOCKING_VALUE);
m_proxy->action(DEFAULT_ACTION_VALUE*2); this->m_proxy->action(DEFAULT_ACTION_VALUE*2);
m_adaptor->emitPropertiesChangedSignal(INTERFACE_NAME, {"blocking"}); this->m_adaptor->emitPropertiesChangedSignal(INTERFACE_NAME, {BLOCKING_PROPERTY});
ASSERT_TRUE(waitUntil(signalReceived)); ASSERT_TRUE(waitUntil(signalReceived));
} }
TEST_F(SdbusTestObject, EmitsPropertyChangedSignalForAllProperties) TYPED_TEST(SdbusTestObject, EmitsPropertyChangedSignalForAllProperties)
{ {
std::atomic<bool> signalReceived{false}; std::atomic<bool> signalReceived{false};
m_proxy->m_onPropertiesChangedHandler = [&signalReceived]( const std::string& interfaceName this->m_proxy->m_onPropertiesChangedHandler = [&signalReceived]( const sdbus::InterfaceName& interfaceName
, const std::map<std::string, sdbus::Variant>& changedProperties , const std::map<sdbus::PropertyName, sdbus::Variant>& changedProperties
, const std::vector<std::string>& invalidatedProperties ) , const std::vector<sdbus::PropertyName>& invalidatedProperties )
{ {
EXPECT_THAT(interfaceName, Eq(INTERFACE_NAME)); EXPECT_THAT(interfaceName, Eq(INTERFACE_NAME));
EXPECT_THAT(changedProperties, SizeIs(1)); EXPECT_THAT(changedProperties, SizeIs(1));
EXPECT_THAT(changedProperties.at("blocking").get<bool>(), Eq(DEFAULT_BLOCKING_VALUE)); EXPECT_THAT(changedProperties.at(BLOCKING_PROPERTY).get<bool>(), Eq(DEFAULT_BLOCKING_VALUE));
ASSERT_THAT(invalidatedProperties, SizeIs(1)); ASSERT_THAT(invalidatedProperties, SizeIs(1));
EXPECT_THAT(invalidatedProperties[0], Eq("action")); EXPECT_THAT(invalidatedProperties[0], Eq("action"));
signalReceived = true; signalReceived = true;
}; };
m_adaptor->emitPropertiesChangedSignal(INTERFACE_NAME); this->m_adaptor->emitPropertiesChangedSignal(INTERFACE_NAME);
ASSERT_TRUE(waitUntil(signalReceived)); ASSERT_TRUE(waitUntil(signalReceived));
} }
TEST_F(SdbusTestObject, GetsZeroManagedObjectsIfHasNoSubPathObjects) TYPED_TEST(SdbusTestObject, GetsZeroManagedObjectsIfHasNoSubPathObjects)
{ {
m_adaptor.reset(); this->m_adaptor.reset();
const auto objectsInterfacesAndProperties = m_objectManagerProxy->GetManagedObjects(); const auto objectsInterfacesAndProperties = this->m_objectManagerProxy->GetManagedObjects();
ASSERT_THAT(objectsInterfacesAndProperties, SizeIs(0)); ASSERT_THAT(objectsInterfacesAndProperties, SizeIs(0));
} }
TEST_F(SdbusTestObject, GetsManagedObjectsSuccessfully) TYPED_TEST(SdbusTestObject, GetsManagedObjectsSuccessfully)
{ {
auto adaptor2 = std::make_unique<TestAdaptor>(*s_adaptorConnection, OBJECT_PATH_2); auto adaptor2 = std::make_unique<TestAdaptor>(*this->s_adaptorConnection, OBJECT_PATH_2);
const auto objectsInterfacesAndProperties = m_objectManagerProxy->GetManagedObjects(); const auto objectsInterfacesAndProperties = this->m_objectManagerProxy->GetManagedObjects();
ASSERT_THAT(objectsInterfacesAndProperties, SizeIs(2)); ASSERT_THAT(objectsInterfacesAndProperties, SizeIs(2));
EXPECT_THAT(objectsInterfacesAndProperties.at(OBJECT_PATH) EXPECT_THAT(objectsInterfacesAndProperties.at(OBJECT_PATH)
.at(org::sdbuscpp::integrationtests_adaptor::INTERFACE_NAME) .at(sdbus::InterfaceName{org::sdbuscpp::integrationtests_adaptor::INTERFACE_NAME})
.at("action").get<uint32_t>(), Eq(DEFAULT_ACTION_VALUE)); .at(ACTION_PROPERTY).template get<uint32_t>(), Eq(DEFAULT_ACTION_VALUE));
EXPECT_THAT(objectsInterfacesAndProperties.at(OBJECT_PATH_2) EXPECT_THAT(objectsInterfacesAndProperties.at(OBJECT_PATH_2)
.at(org::sdbuscpp::integrationtests_adaptor::INTERFACE_NAME) .at(sdbus::InterfaceName{org::sdbuscpp::integrationtests_adaptor::INTERFACE_NAME})
.at("action").get<uint32_t>(), Eq(DEFAULT_ACTION_VALUE)); .at(ACTION_PROPERTY).template get<uint32_t>(), Eq(DEFAULT_ACTION_VALUE));
} }
TEST_F(SdbusTestObject, EmitsInterfacesAddedSignalForSelectedObjectInterfaces) TYPED_TEST(SdbusTestObject, EmitsInterfacesAddedSignalForSelectedObjectInterfaces)
{ {
std::atomic<bool> signalReceived{false}; std::atomic<bool> signalReceived{false};
m_objectManagerProxy->m_onInterfacesAddedHandler = [&signalReceived]( const sdbus::ObjectPath& objectPath this->m_objectManagerProxy->m_onInterfacesAddedHandler = [&signalReceived]( const sdbus::ObjectPath& objectPath
, const std::map<std::string, std::map<std::string, sdbus::Variant>>& interfacesAndProperties ) , const std::map<sdbus::InterfaceName, std::map<sdbus::PropertyName, sdbus::Variant>>& interfacesAndProperties )
{ {
EXPECT_THAT(objectPath, Eq(OBJECT_PATH)); EXPECT_THAT(objectPath, Eq(OBJECT_PATH));
EXPECT_THAT(interfacesAndProperties, SizeIs(1)); EXPECT_THAT(interfacesAndProperties, SizeIs(1));
@ -175,60 +253,66 @@ TEST_F(SdbusTestObject, EmitsInterfacesAddedSignalForSelectedObjectInterfaces)
#if LIBSYSTEMD_VERSION<=244 #if LIBSYSTEMD_VERSION<=244
// Up to sd-bus v244, all properties are added to the list, i.e. `state', `action', and `blocking' in this case. // Up to sd-bus v244, all properties are added to the list, i.e. `state', `action', and `blocking' in this case.
EXPECT_THAT(interfacesAndProperties.at(INTERFACE_NAME), SizeIs(3)); EXPECT_THAT(interfacesAndProperties.at(INTERFACE_NAME), SizeIs(3));
EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count("state")); EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count(STATE_PROPERTY));
EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count("action")); EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count(ACTION_PROPERTY));
EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count("blocking")); EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count(BLOCKING_PROPERTY));
#else #else
// Since v245 sd-bus does not add to the InterfacesAdded signal message the values of properties marked only // Since v245 sd-bus does not add to the InterfacesAdded signal message the values of properties marked only
// for invalidation on change, which makes the behavior consistent with the PropertiesChangedSignal. // for invalidation on change, which makes the behavior consistent with the PropertiesChangedSignal.
// So in this specific instance, `action' property is no more added to the list. // So in this specific instance, `action' property is no more added to the list.
EXPECT_THAT(interfacesAndProperties.at(INTERFACE_NAME), SizeIs(2)); EXPECT_THAT(interfacesAndProperties.at(INTERFACE_NAME), SizeIs(2));
EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count("state")); EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count(STATE_PROPERTY));
EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count("blocking")); EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count(BLOCKING_PROPERTY));
#endif #endif
signalReceived = true; signalReceived = true;
}; };
m_adaptor->emitInterfacesAddedSignal({INTERFACE_NAME}); this->m_adaptor->emitInterfacesAddedSignal({INTERFACE_NAME});
ASSERT_TRUE(waitUntil(signalReceived)); ASSERT_TRUE(waitUntil(signalReceived));
} }
TEST_F(SdbusTestObject, EmitsInterfacesAddedSignalForAllObjectInterfaces) TYPED_TEST(SdbusTestObject, EmitsInterfacesAddedSignalForAllObjectInterfaces)
{ {
std::atomic<bool> signalReceived{false}; std::atomic<bool> signalReceived{false};
m_objectManagerProxy->m_onInterfacesAddedHandler = [&signalReceived]( const sdbus::ObjectPath& objectPath this->m_objectManagerProxy->m_onInterfacesAddedHandler = [&signalReceived]( const sdbus::ObjectPath& objectPath
, const std::map<std::string, std::map<std::string, sdbus::Variant>>& interfacesAndProperties ) , const std::map<sdbus::InterfaceName, std::map<sdbus::PropertyName, sdbus::Variant>>& interfacesAndProperties )
{ {
EXPECT_THAT(objectPath, Eq(OBJECT_PATH)); EXPECT_THAT(objectPath, Eq(OBJECT_PATH));
#if LIBSYSTEMD_VERSION<=250
EXPECT_THAT(interfacesAndProperties, SizeIs(5)); // INTERFACE_NAME + 4 standard interfaces EXPECT_THAT(interfacesAndProperties, SizeIs(5)); // INTERFACE_NAME + 4 standard interfaces
#else
// Since systemd v251, ObjectManager standard interface is not listed among the interfaces
// if the object does not have object manager functionality explicitly enabled.
EXPECT_THAT(interfacesAndProperties, SizeIs(4)); // INTERFACE_NAME + 3 standard interfaces
#endif
#if LIBSYSTEMD_VERSION<=244 #if LIBSYSTEMD_VERSION<=244
// Up to sd-bus v244, all properties are added to the list, i.e. `state', `action', and `blocking' in this case. // Up to sd-bus v244, all properties are added to the list, i.e. `state', `action', and `blocking' in this case.
EXPECT_THAT(interfacesAndProperties.at(INTERFACE_NAME), SizeIs(3)); EXPECT_THAT(interfacesAndProperties.at(INTERFACE_NAME), SizeIs(3));
EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count("state")); EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count(STATE_PROPERTY));
EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count("action")); EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count(ACTION_PROPERTY));
EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count("blocking")); EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count(BLOCKING_PROPERTY));
#else #else
// Since v245 sd-bus does not add to the InterfacesAdded signal message the values of properties marked only // Since v245 sd-bus does not add to the InterfacesAdded signal message the values of properties marked only
// for invalidation on change, which makes the behavior consistent with the PropertiesChangedSignal. // for invalidation on change, which makes the behavior consistent with the PropertiesChangedSignal.
// So in this specific instance, `action' property is no more added to the list. // So in this specific instance, `action' property is no more added to the list.
EXPECT_THAT(interfacesAndProperties.at(INTERFACE_NAME), SizeIs(2)); EXPECT_THAT(interfacesAndProperties.at(INTERFACE_NAME), SizeIs(2));
EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count("state")); EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count(STATE_PROPERTY));
EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count("blocking")); EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count(BLOCKING_PROPERTY));
#endif #endif
signalReceived = true; signalReceived = true;
}; };
m_adaptor->emitInterfacesAddedSignal(); this->m_adaptor->emitInterfacesAddedSignal();
ASSERT_TRUE(waitUntil(signalReceived)); ASSERT_TRUE(waitUntil(signalReceived));
} }
TEST_F(SdbusTestObject, EmitsInterfacesRemovedSignalForSelectedObjectInterfaces) TYPED_TEST(SdbusTestObject, EmitsInterfacesRemovedSignalForSelectedObjectInterfaces)
{ {
std::atomic<bool> signalReceived{false}; std::atomic<bool> signalReceived{false};
m_objectManagerProxy->m_onInterfacesRemovedHandler = [&signalReceived]( const sdbus::ObjectPath& objectPath this->m_objectManagerProxy->m_onInterfacesRemovedHandler = [&signalReceived]( const sdbus::ObjectPath& objectPath
, const std::vector<std::string>& interfaces ) , const std::vector<sdbus::InterfaceName>& interfaces )
{ {
EXPECT_THAT(objectPath, Eq(OBJECT_PATH)); EXPECT_THAT(objectPath, Eq(OBJECT_PATH));
ASSERT_THAT(interfaces, SizeIs(1)); ASSERT_THAT(interfaces, SizeIs(1));
@ -236,23 +320,29 @@ TEST_F(SdbusTestObject, EmitsInterfacesRemovedSignalForSelectedObjectInterfaces)
signalReceived = true; signalReceived = true;
}; };
m_adaptor->emitInterfacesRemovedSignal({INTERFACE_NAME}); this->m_adaptor->emitInterfacesRemovedSignal({INTERFACE_NAME});
ASSERT_TRUE(waitUntil(signalReceived)); ASSERT_TRUE(waitUntil(signalReceived));
} }
TEST_F(SdbusTestObject, EmitsInterfacesRemovedSignalForAllObjectInterfaces) TYPED_TEST(SdbusTestObject, EmitsInterfacesRemovedSignalForAllObjectInterfaces)
{ {
std::atomic<bool> signalReceived{false}; std::atomic<bool> signalReceived{false};
m_objectManagerProxy->m_onInterfacesRemovedHandler = [&signalReceived]( const sdbus::ObjectPath& objectPath this->m_objectManagerProxy->m_onInterfacesRemovedHandler = [&signalReceived]( const sdbus::ObjectPath& objectPath
, const std::vector<std::string>& interfaces ) , const std::vector<sdbus::InterfaceName>& interfaces )
{ {
EXPECT_THAT(objectPath, Eq(OBJECT_PATH)); EXPECT_THAT(objectPath, Eq(OBJECT_PATH));
#if LIBSYSTEMD_VERSION<=250
ASSERT_THAT(interfaces, SizeIs(5)); // INTERFACE_NAME + 4 standard interfaces ASSERT_THAT(interfaces, SizeIs(5)); // INTERFACE_NAME + 4 standard interfaces
#else
// Since systemd v251, ObjectManager standard interface is not listed among the interfaces
// if the object does not have object manager functionality explicitly enabled.
ASSERT_THAT(interfaces, SizeIs(4)); // INTERFACE_NAME + 3 standard interfaces
#endif
signalReceived = true; signalReceived = true;
}; };
m_adaptor->emitInterfacesRemovedSignal(); this->m_adaptor->emitInterfacesRemovedSignal();
ASSERT_TRUE(waitUntil(signalReceived)); ASSERT_TRUE(waitUntil(signalReceived));
} }

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file Defs.h * @file Defs.h
* *
@ -30,14 +30,20 @@
#include "sdbus-c++/Types.h" #include "sdbus-c++/Types.h"
#include <chrono> #include <chrono>
#include <ostream> #include <ostream>
#include <filesystem>
namespace sdbus { namespace test { namespace sdbus { namespace test {
const std::string INTERFACE_NAME{"org.sdbuscpp.integrationtests"}; const InterfaceName INTERFACE_NAME{"org.sdbuscpp.integrationtests"};
const std::string BUS_NAME = INTERFACE_NAME; const ServiceName SERVICE_NAME{"org.sdbuscpp.integrationtests"};
const std::string MANAGER_PATH {"/org/sdbuscpp/integrationtests"}; const ServiceName EMPTY_DESTINATION;
const std::string OBJECT_PATH {"/org/sdbuscpp/integrationtests/ObjectA1"}; const ObjectPath MANAGER_PATH {"/org/sdbuscpp/integrationtests"};
const std::string OBJECT_PATH_2{"/org/sdbuscpp/integrationtests/ObjectB1"}; const ObjectPath OBJECT_PATH {"/org/sdbuscpp/integrationtests/ObjectA1"};
const ObjectPath OBJECT_PATH_2{"/org/sdbuscpp/integrationtests/ObjectB1"};
const PropertyName STATE_PROPERTY{"state"};
const PropertyName ACTION_PROPERTY{"action"};
const PropertyName BLOCKING_PROPERTY{"blocking"};
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 uint8_t UINT8_VALUE{1};
constexpr const int16_t INT16_VALUE{21}; constexpr const int16_t INT16_VALUE{21};
@ -46,8 +52,8 @@ constexpr const int32_t INT32_VALUE{-42};
constexpr const int32_t INT64_VALUE{-1024}; constexpr const int32_t INT64_VALUE{-1024};
const std::string STRING_VALUE{"sdbus-c++-testing"}; const std::string STRING_VALUE{"sdbus-c++-testing"};
const sdbus::Signature SIGNATURE_VALUE{"a{is}"}; const Signature SIGNATURE_VALUE{"a{is}"};
const sdbus::ObjectPath OBJECT_PATH_VALUE{"/"}; const ObjectPath OBJECT_PATH_VALUE{"/"};
const int UNIX_FD_VALUE = 0; const int UNIX_FD_VALUE = 0;
const std::string DEFAULT_STATE_VALUE{"default-state-value"}; const std::string DEFAULT_STATE_VALUE{"default-state-value"};

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file TestAdaptor.cpp * @file TestAdaptor.cpp
* *
@ -30,9 +30,9 @@
#include <atomic> #include <atomic>
namespace sdbus { namespace test { namespace sdbus { namespace test {
TestAdaptor::TestAdaptor(sdbus::IConnection& connection, const std::string& path) : TestAdaptor::TestAdaptor(sdbus::IConnection& connection, sdbus::ObjectPath path) :
AdaptorInterfaces(connection, path) AdaptorInterfaces(connection, std::move(path))
{ {
registerAdaptor(); registerAdaptor();
} }
@ -75,9 +75,9 @@ std::vector<int16_t> TestAdaptor::getInts16FromStruct(const sdbus::Struct<uint8_
return res; return res;
} }
sdbus::Variant TestAdaptor::processVariant(const sdbus::Variant& v) sdbus::Variant TestAdaptor::processVariant(const std::variant<int32_t, double, std::string>& v)
{ {
sdbus::Variant res = static_cast<int32_t>(v.get<double>()); sdbus::Variant res{static_cast<int32_t>(std::get<double>(v))};
return res; return res;
} }
@ -93,7 +93,7 @@ std::map<int32_t, sdbus::Variant> TestAdaptor::getMapOfVariants(const std::vecto
sdbus::Struct<std::string, sdbus::Struct<std::map<int32_t, int32_t>>> TestAdaptor::getStructInStruct() sdbus::Struct<std::string, sdbus::Struct<std::map<int32_t, int32_t>>> TestAdaptor::getStructInStruct()
{ {
return sdbus::make_struct(STRING_VALUE, sdbus::make_struct(std::map<int32_t, int32_t>{{INT32_VALUE, INT32_VALUE}})); return sdbus::Struct{STRING_VALUE, sdbus::Struct{std::map<int32_t, int32_t>{{INT32_VALUE, INT32_VALUE}}}};
} }
int32_t TestAdaptor::sumStructItems(const sdbus::Struct<uint8_t, uint16_t>& a, const sdbus::Struct<int32_t, int64_t>& b) int32_t TestAdaptor::sumStructItems(const sdbus::Struct<uint8_t, uint16_t>& a, const sdbus::Struct<int32_t, int64_t>& b)
@ -104,7 +104,7 @@ int32_t TestAdaptor::sumStructItems(const sdbus::Struct<uint8_t, uint16_t>& a, c
return res; return res;
} }
uint32_t TestAdaptor::sumVectorItems(const std::vector<uint16_t>& a, const std::vector<uint64_t>& b) uint32_t TestAdaptor::sumArrayItems(const std::vector<uint16_t>& a, const std::array<uint64_t, 3>& b)
{ {
uint32_t res{0}; uint32_t res{0};
for (auto x : a) for (auto x : a)
@ -122,16 +122,16 @@ uint32_t TestAdaptor::doOperation(const uint32_t& param)
{ {
std::this_thread::sleep_for(std::chrono::milliseconds(param)); std::this_thread::sleep_for(std::chrono::milliseconds(param));
m_methodCallMsg = getObject().getCurrentlyProcessedMessage(); m_methodCallMsg = std::make_unique<const Message>(getObject().getCurrentlyProcessedMessage());
m_methodCallMemberName = m_methodCallMsg->getMemberName(); m_methodName = m_methodCallMsg->getMemberName();
return param; return param;
} }
void TestAdaptor::doOperationAsync(sdbus::Result<uint32_t>&& result, uint32_t param) void TestAdaptor::doOperationAsync(sdbus::Result<uint32_t>&& result, uint32_t param)
{ {
m_methodCallMsg = getObject().getCurrentlyProcessedMessage(); m_methodCallMsg = std::make_unique<const Message>(getObject().getCurrentlyProcessedMessage());
m_methodCallMemberName = m_methodCallMsg->getMemberName(); m_methodName = m_methodCallMsg->getMemberName();
if (param == 0) if (param == 0)
{ {
@ -162,9 +162,9 @@ sdbus::UnixFd TestAdaptor::getUnixFd()
return sdbus::UnixFd{UNIX_FD_VALUE}; return sdbus::UnixFd{UNIX_FD_VALUE};
} }
std::map<uint64_t, sdbus::Struct<std::map<uint8_t, std::vector<sdbus::Struct<sdbus::ObjectPath, bool, sdbus::Variant, std::map<int32_t, std::string>>>>, sdbus::Signature, std::string>> TestAdaptor::getComplex() std::unordered_map<uint64_t, sdbus::Struct<std::map<uint8_t, std::vector<sdbus::Struct<sdbus::ObjectPath, bool, sdbus::Variant, std::map<int32_t, std::string>>>>, sdbus::Signature, std::string>> TestAdaptor::getComplex()
{ {
return { // map return { // unordered_map
{ {
0, // uint_64_t 0, // uint_64_t
{ // struct { // struct
@ -173,7 +173,7 @@ std::map<uint64_t, sdbus::Struct<std::map<uint8_t, std::vector<sdbus::Struct<sdb
23, // uint8_t 23, // uint8_t
{ // vector { // vector
{ // struct { // struct
"/object/path", // object path sdbus::ObjectPath{"/object/path"}, // object path
false, false,
Variant{3.14}, Variant{3.14},
{ // map { // map
@ -183,7 +183,7 @@ std::map<uint64_t, sdbus::Struct<std::map<uint8_t, std::vector<sdbus::Struct<sdb
} }
} }
}, },
"a{t(a{ya(obva{is})}gs)}", // signature sdbus::Signature{"a{t(a{ya(obva{is})}gs)}"}, // signature
std::string{} std::string{}
} }
} }
@ -234,7 +234,7 @@ bool TestAdaptor::blocking()
void TestAdaptor::blocking(const bool& value) void TestAdaptor::blocking(const bool& value)
{ {
m_propertySetMsg = getObject().getCurrentlyProcessedMessage(); m_propertySetMsg = std::make_unique<const Message>(getObject().getCurrentlyProcessedMessage());
m_propertySetSender = m_propertySetMsg->getSender(); m_propertySetSender = m_propertySetMsg->getSender();
m_blocking = value; m_blocking = value;
@ -390,7 +390,7 @@ R"delimiter(
<arg type="(ix)" direction="in"/> <arg type="(ix)" direction="in"/>
<arg type="i" direction="out"/> <arg type="i" direction="out"/>
</method> </method>
<method name="sumVectorItems"> <method name="sumArrayItems">
<arg type="aq" direction="in"/> <arg type="aq" direction="in"/>
<arg type="at" direction="in"/> <arg type="at" direction="in"/>
<arg type="u" direction="out"/> <arg type="u" direction="out"/>

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file TestAdaptor.h * @file TestAdaptor.h
* *
@ -33,13 +33,14 @@
#include <chrono> #include <chrono>
#include <atomic> #include <atomic>
#include <utility> #include <utility>
#include <memory>
namespace sdbus { namespace test { namespace sdbus { namespace test {
class ObjectManagerTestAdaptor final : public sdbus::AdaptorInterfaces< sdbus::ObjectManager_adaptor > class ObjectManagerTestAdaptor final : public sdbus::AdaptorInterfaces< sdbus::ObjectManager_adaptor >
{ {
public: public:
ObjectManagerTestAdaptor(sdbus::IConnection& connection, std::string path) : ObjectManagerTestAdaptor(sdbus::IConnection& connection, sdbus::ObjectPath path) :
AdaptorInterfaces(connection, std::move(path)) AdaptorInterfaces(connection, std::move(path))
{ {
registerAdaptor(); registerAdaptor();
@ -56,7 +57,7 @@ class TestAdaptor final : public sdbus::AdaptorInterfaces< org::sdbuscpp::integr
, sdbus::ManagedObject_adaptor > , sdbus::ManagedObject_adaptor >
{ {
public: public:
TestAdaptor(sdbus::IConnection& connection, const std::string& path); TestAdaptor(sdbus::IConnection& connection, sdbus::ObjectPath path);
~TestAdaptor(); ~TestAdaptor();
protected: protected:
@ -66,17 +67,17 @@ protected:
double multiply(const int64_t& a, const double& b) override; double multiply(const int64_t& a, const double& b) override;
void multiplyWithNoReply(const int64_t& a, const double& b) override; void multiplyWithNoReply(const int64_t& a, const double& b) override;
std::vector<int16_t> getInts16FromStruct(const sdbus::Struct<uint8_t, int16_t, double, std::string, std::vector<int16_t>>& arg0) override; std::vector<int16_t> getInts16FromStruct(const sdbus::Struct<uint8_t, int16_t, double, std::string, std::vector<int16_t>>& arg0) override;
sdbus::Variant processVariant(const sdbus::Variant& variant) override; sdbus::Variant processVariant(const std::variant<int32_t, double, std::string>& variant) override;
std::map<int32_t, sdbus::Variant> getMapOfVariants(const std::vector<int32_t>& x, const sdbus::Struct<sdbus::Variant, sdbus::Variant>& y) override; std::map<int32_t, sdbus::Variant> getMapOfVariants(const std::vector<int32_t>& x, const sdbus::Struct<sdbus::Variant, sdbus::Variant>& y) override;
sdbus::Struct<std::string, sdbus::Struct<std::map<int32_t, int32_t>>> getStructInStruct() override; sdbus::Struct<std::string, sdbus::Struct<std::map<int32_t, int32_t>>> getStructInStruct() override;
int32_t sumStructItems(const sdbus::Struct<uint8_t, uint16_t>& arg0, const sdbus::Struct<int32_t, int64_t>& arg1) override; int32_t sumStructItems(const sdbus::Struct<uint8_t, uint16_t>& arg0, const sdbus::Struct<int32_t, int64_t>& arg1) override;
uint32_t sumVectorItems(const std::vector<uint16_t>& arg0, const std::vector<uint64_t>& arg1) override; uint32_t sumArrayItems(const std::vector<uint16_t>& arg0, const std::array<uint64_t, 3>& arg1) override;
uint32_t doOperation(const uint32_t& arg0) override; uint32_t doOperation(const uint32_t& arg0) override;
void doOperationAsync(sdbus::Result<uint32_t>&& result, uint32_t arg0) override; void doOperationAsync(sdbus::Result<uint32_t>&& result, uint32_t arg0) override;
sdbus::Signature getSignature() override; sdbus::Signature getSignature() override;
sdbus::ObjectPath getObjPath() override; sdbus::ObjectPath getObjPath() override;
sdbus::UnixFd getUnixFd() override; sdbus::UnixFd getUnixFd() override;
std::map<uint64_t, sdbus::Struct<std::map<uint8_t, std::vector<sdbus::Struct<sdbus::ObjectPath, bool, sdbus::Variant, std::map<int32_t, std::string>>>>, sdbus::Signature, std::string>> getComplex() override; std::unordered_map<uint64_t, sdbus::Struct<std::map<uint8_t, std::vector<sdbus::Struct<sdbus::ObjectPath, bool, sdbus::Variant, std::map<int32_t, std::string>>>>, sdbus::Signature, std::string>> getComplex() override;
void throwError() override; void throwError() override;
void throwErrorWithNoReply() override; void throwErrorWithNoReply() override;
void doPrivilegedStuff() override; void doPrivilegedStuff() override;
@ -103,12 +104,51 @@ public: // for tests
mutable double m_multiplyResult{}; mutable double m_multiplyResult{};
mutable std::atomic<bool> m_wasThrowErrorCalled{false}; mutable std::atomic<bool> m_wasThrowErrorCalled{false};
const Message* m_methodCallMsg{}; std::unique_ptr<const Message> m_methodCallMsg;
std::string m_methodCallMemberName; MethodName m_methodName;
const Message* m_propertySetMsg{}; std::unique_ptr<const Message> m_propertySetMsg;
std::string m_propertySetSender; std::string m_propertySetSender;
}; };
class DummyTestAdaptor final : public sdbus::AdaptorInterfaces< org::sdbuscpp::integrationtests_adaptor
, sdbus::Properties_adaptor
, sdbus::ManagedObject_adaptor >
{
public:
DummyTestAdaptor(sdbus::IConnection& connection, sdbus::ObjectPath path)
: AdaptorInterfaces(connection, std::move(path))
{}
protected:
void noArgNoReturn() override {}
int32_t getInt() override { return {}; }
std::tuple<uint32_t, std::string> getTuple() override { return {}; }
double multiply(const int64_t&, const double&) override { return {}; }
void multiplyWithNoReply(const int64_t&, const double&) override {}
std::vector<int16_t> getInts16FromStruct(const sdbus::Struct<uint8_t, int16_t, double, std::string, std::vector<int16_t>>&) override { return {}; }
sdbus::Variant processVariant(const std::variant<int32_t, double, std::string>&) override { return {}; }
std::map<int32_t, sdbus::Variant> getMapOfVariants(const std::vector<int32_t>&, const sdbus::Struct<sdbus::Variant, sdbus::Variant>&) override { return {}; }
sdbus::Struct<std::string, sdbus::Struct<std::map<int32_t, int32_t>>> getStructInStruct() override { return {}; }
int32_t sumStructItems(const sdbus::Struct<uint8_t, uint16_t>&, const sdbus::Struct<int32_t, int64_t>&) override { return {}; }
uint32_t sumArrayItems(const std::vector<uint16_t>&, const std::array<uint64_t, 3>&) override { return {}; }
uint32_t doOperation(const uint32_t&) override { return {}; }
void doOperationAsync(sdbus::Result<uint32_t>&&, uint32_t) override {}
sdbus::Signature getSignature() override { return {}; }
sdbus::ObjectPath getObjPath() override { return {}; }
sdbus::UnixFd getUnixFd() override { return {}; }
std::unordered_map<uint64_t, sdbus::Struct<std::map<uint8_t, std::vector<sdbus::Struct<sdbus::ObjectPath, bool, sdbus::Variant, std::map<int32_t, std::string>>>>, sdbus::Signature, std::string>> getComplex() override { return {}; }
void throwError() override {}
void throwErrorWithNoReply() override {}
void doPrivilegedStuff() override {}
void emitTwoSimpleSignals() override {}
uint32_t action() override { return {}; }
void action(const uint32_t&) override {}
bool blocking() override { return {}; }
void blocking(const bool&) override {}
std::string state() override { return {}; }
};
}} }}
#endif /* INTEGRATIONTESTS_TESTADAPTOR_H_ */ #endif /* INTEGRATIONTESTS_TESTADAPTOR_H_ */

View File

@ -1,8 +1,8 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file TestAdaptor.cpp * @file TestFixture.cpp
* *
* Created on: May 23, 2020 * Created on: May 23, 2020
* Project: sdbus-c++ * Project: sdbus-c++
@ -27,8 +27,18 @@
#include "TestFixture.h" #include "TestFixture.h"
namespace sdbus { namespace test { namespace sdbus { namespace test {
std::unique_ptr<sdbus::IConnection> TestFixture::s_adaptorConnection = sdbus::createSystemBusConnection(); std::unique_ptr<sdbus::IConnection> BaseTestFixture::s_adaptorConnection = sdbus::createBusConnection();
std::unique_ptr<sdbus::IConnection> TestFixture::s_proxyConnection = sdbus::createSystemBusConnection(); std::unique_ptr<sdbus::IConnection> BaseTestFixture::s_proxyConnection = sdbus::createBusConnection();
#ifndef SDBUS_basu // sd_event integration is not supported in basu-based sdbus-c++
std::thread TestFixture<SdEventLoop>::s_adaptorEventLoopThread{};
std::thread TestFixture<SdEventLoop>::s_proxyEventLoopThread{};
sd_event *TestFixture<SdEventLoop>::s_adaptorSdEvent{};
sd_event *TestFixture<SdEventLoop>::s_proxySdEvent{};
int TestFixture<SdEventLoop>::s_eventExitFd{-1};
#endif // SDBUS_basu
}} }}

View File

@ -1,8 +1,8 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file TestAdaptor.h * @file TestFixture.h
* *
* Created on: Jan 2, 2017 * Created on: Jan 2, 2017
* Project: sdbus-c++ * Project: sdbus-c++
@ -33,59 +33,41 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <gmock/gmock.h> #include <gmock/gmock.h>
#ifndef SDBUS_basu // sd_event integration is not supported in basu-based sdbus-c++
#include <systemd/sd-event.h>
#endif // SDBUS_basu
#include <sys/eventfd.h>
#include <thread> #include <thread>
#include <chrono> #include <chrono>
#include <atomic> #include <atomic>
#include <chrono> #include <chrono>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
namespace sdbus { namespace test { namespace sdbus { namespace test {
class TestFixture : public ::testing::Test class BaseTestFixture : public ::testing::Test
{ {
public: public:
static void SetUpTestCase() static void SetUpTestCase()
{ {
s_proxyConnection->enterEventLoopAsync(); s_adaptorConnection->requestName(SERVICE_NAME);
s_adaptorConnection->requestName(BUS_NAME);
s_adaptorConnection->enterEventLoopAsync();
std::this_thread::sleep_for(std::chrono::milliseconds(50)); // Give time for the proxy connection to start listening to signals
} }
static void TearDownTestCase() static void TearDownTestCase()
{ {
s_adaptorConnection->releaseName(BUS_NAME); s_adaptorConnection->releaseName(SERVICE_NAME);
s_adaptorConnection->leaveEventLoop();
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: private:
void SetUp() override void SetUp() override
{ {
m_objectManagerProxy = std::make_unique<ObjectManagerTestProxy>(*s_proxyConnection, BUS_NAME, MANAGER_PATH); m_objectManagerProxy = std::make_unique<ObjectManagerTestProxy>(*s_proxyConnection, SERVICE_NAME, MANAGER_PATH);
m_proxy = std::make_unique<TestProxy>(*s_proxyConnection, BUS_NAME, OBJECT_PATH); m_proxy = std::make_unique<TestProxy>(*s_proxyConnection, SERVICE_NAME, OBJECT_PATH);
m_objectManagerAdaptor = std::make_unique<ObjectManagerTestAdaptor>(*s_adaptorConnection, MANAGER_PATH); m_objectManagerAdaptor = std::make_unique<ObjectManagerTestAdaptor>(*s_adaptorConnection, MANAGER_PATH);
m_adaptor = std::make_unique<TestAdaptor>(*s_adaptorConnection, OBJECT_PATH); m_adaptor = std::make_unique<TestAdaptor>(*s_adaptorConnection, OBJECT_PATH);
@ -106,6 +88,203 @@ public:
std::unique_ptr<TestProxy> m_proxy; std::unique_ptr<TestProxy> m_proxy;
}; };
struct SdBusCppLoop{};
struct SdEventLoop{};
template <typename _EventLoop>
class TestFixture : public BaseTestFixture{};
// Fixture working upon internal sdbus-c++ event loop
template <>
class TestFixture<SdBusCppLoop> : public BaseTestFixture
{
public:
static void SetUpTestCase()
{
BaseTestFixture::SetUpTestCase();
s_proxyConnection->enterEventLoopAsync();
s_adaptorConnection->enterEventLoopAsync();
std::this_thread::sleep_for(std::chrono::milliseconds(50)); // Give time for the proxy connection to start listening to signals
}
static void TearDownTestCase()
{
BaseTestFixture::TearDownTestCase();
s_adaptorConnection->leaveEventLoop();
s_proxyConnection->leaveEventLoop();
}
};
#ifndef SDBUS_basu // sd_event integration is not supported in basu-based sdbus-c++
// Fixture working upon attached external sd-event loop
template <>
class TestFixture<SdEventLoop> : public BaseTestFixture
{
public:
static void SetUpTestCase()
{
sd_event_new(&s_adaptorSdEvent);
sd_event_new(&s_proxySdEvent);
s_adaptorConnection->attachSdEventLoop(s_adaptorSdEvent);
s_proxyConnection->attachSdEventLoop(s_proxySdEvent);
s_eventExitFd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
auto exitHandler = [](sd_event_source *s, auto...){ return sd_event_exit(sd_event_source_get_event(s), 0); };
sd_event_add_io(s_adaptorSdEvent, nullptr, s_eventExitFd, EPOLLIN, exitHandler, nullptr);
sd_event_add_io(s_proxySdEvent, nullptr, s_eventExitFd, EPOLLIN, exitHandler, nullptr);
s_adaptorEventLoopThread = std::thread([]()
{
sd_event_loop(s_adaptorSdEvent);
});
s_proxyEventLoopThread = std::thread([]()
{
sd_event_loop(s_proxySdEvent);
});
BaseTestFixture::SetUpTestCase();
std::this_thread::sleep_for(std::chrono::milliseconds(50)); // Give time for the proxy connection to start listening to signals
}
static void TearDownTestCase()
{
(void)eventfd_write(s_eventExitFd, 1);
s_adaptorEventLoopThread.join();
s_proxyEventLoopThread.join();
sd_event_unref(s_adaptorSdEvent);
sd_event_unref(s_proxySdEvent);
close(s_eventExitFd);
BaseTestFixture::TearDownTestCase();
}
private:
static std::thread s_adaptorEventLoopThread;
static std::thread s_proxyEventLoopThread;
static sd_event *s_adaptorSdEvent;
static sd_event *s_proxySdEvent;
static int s_eventExitFd;
};
typedef ::testing::Types<SdBusCppLoop, SdEventLoop> EventLoopTags;
#else // SDBUS_basu
typedef ::testing::Types<SdBusCppLoop> EventLoopTags;
#endif // SDBUS_basu
TYPED_TEST_SUITE(TestFixture, EventLoopTags);
template <typename _EventLoop>
using SdbusTestObject = TestFixture<_EventLoop>;
TYPED_TEST_SUITE(SdbusTestObject, EventLoopTags);
template <typename _EventLoop>
using AsyncSdbusTestObject = TestFixture<_EventLoop>;
TYPED_TEST_SUITE(AsyncSdbusTestObject, EventLoopTags);
template <typename _EventLoop>
using AConnection = TestFixture<_EventLoop>;
TYPED_TEST_SUITE(AConnection, EventLoopTags);
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_ */ #endif /* SDBUS_CPP_INTEGRATIONTESTS_TESTFIXTURE_H_ */

View File

@ -1,8 +1,8 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file TestAdaptor.cpp * @file TestProxy.cpp
* *
* Created on: May 23, 2020 * Created on: May 23, 2020
* Project: sdbus-c++ * Project: sdbus-c++
@ -30,8 +30,8 @@
#include <atomic> #include <atomic>
namespace sdbus { namespace test { namespace sdbus { namespace test {
TestProxy::TestProxy(std::string destination, std::string objectPath) TestProxy::TestProxy(ServiceName destination, ObjectPath objectPath)
: ProxyInterfaces(std::move(destination), std::move(objectPath)) : ProxyInterfaces(std::move(destination), std::move(objectPath))
{ {
getProxy().uponSignal("signalWithoutRegistration").onInterface(sdbus::test::INTERFACE_NAME).call([this](const sdbus::Struct<std::string, sdbus::Struct<sdbus::Signature>>& s){ this->onSignalWithoutRegistration(s); }); getProxy().uponSignal("signalWithoutRegistration").onInterface(sdbus::test::INTERFACE_NAME).call([this](const sdbus::Struct<std::string, sdbus::Struct<sdbus::Signature>>& s){ this->onSignalWithoutRegistration(s); });
@ -39,7 +39,14 @@ TestProxy::TestProxy(std::string destination, std::string objectPath)
registerProxy(); registerProxy();
} }
TestProxy::TestProxy(sdbus::IConnection& connection, std::string destination, std::string objectPath) TestProxy::TestProxy(ServiceName destination, ObjectPath objectPath, dont_run_event_loop_thread_t)
: ProxyInterfaces(std::move(destination), std::move(objectPath), dont_run_event_loop_thread)
{
// It doesn't make sense to register any signals here since proxy upon a D-Bus connection with no event loop thread
// will not receive any incoming messages except replies to synchronous D-Bus calls.
}
TestProxy::TestProxy(sdbus::IConnection& connection, ServiceName destination, ObjectPath objectPath)
: ProxyInterfaces(connection, std::move(destination), std::move(objectPath)) : ProxyInterfaces(connection, std::move(destination), std::move(objectPath))
{ {
getProxy().uponSignal("signalWithoutRegistration").onInterface(sdbus::test::INTERFACE_NAME).call([this](const sdbus::Struct<std::string, sdbus::Struct<sdbus::Signature>>& s){ this->onSignalWithoutRegistration(s); }); getProxy().uponSignal("signalWithoutRegistration").onInterface(sdbus::test::INTERFACE_NAME).call([this](const sdbus::Struct<std::string, sdbus::Struct<sdbus::Signature>>& s){ this->onSignalWithoutRegistration(s); });
@ -54,8 +61,8 @@ TestProxy::~TestProxy()
void TestProxy::onSimpleSignal() void TestProxy::onSimpleSignal()
{ {
m_signalMsg = getProxy().getCurrentlyProcessedMessage(); m_signalMsg = std::make_unique<sdbus::Message>(getProxy().getCurrentlyProcessedMessage());
m_signalMemberName = m_signalMsg->getMemberName(); m_signalName = m_signalMsg->getMemberName();
m_gotSimpleSignal = true; m_gotSimpleSignal = true;
} }
@ -74,25 +81,26 @@ void TestProxy::onSignalWithVariant(const sdbus::Variant& aVariant)
void TestProxy::onSignalWithoutRegistration(const sdbus::Struct<std::string, sdbus::Struct<sdbus::Signature>>& s) void TestProxy::onSignalWithoutRegistration(const sdbus::Struct<std::string, sdbus::Struct<sdbus::Signature>>& s)
{ {
// Static cast to std::string is a workaround for gcc 11.4 false positive warning (which later gcc versions nor Clang emit)
m_signatureFromSignal[std::get<0>(s)] = static_cast<std::string>(std::get<0>(std::get<1>(s))); m_signatureFromSignal[std::get<0>(s)] = static_cast<std::string>(std::get<0>(std::get<1>(s)));
m_gotSignalWithSignature = true; m_gotSignalWithSignature = true;
} }
void TestProxy::onDoOperationReply(uint32_t returnValue, const sdbus::Error* error) void TestProxy::onDoOperationReply(uint32_t returnValue, std::optional<sdbus::Error> error)
{ {
if (m_DoOperationClientSideAsyncReplyHandler) if (m_DoOperationClientSideAsyncReplyHandler)
m_DoOperationClientSideAsyncReplyHandler(returnValue, error); m_DoOperationClientSideAsyncReplyHandler(returnValue, error);
} }
void TestProxy::onPropertiesChanged( const std::string& interfaceName void TestProxy::onPropertiesChanged( const sdbus::InterfaceName& interfaceName
, const std::map<std::string, sdbus::Variant>& changedProperties , const std::map<PropertyName, sdbus::Variant>& changedProperties
, const std::vector<std::string>& invalidatedProperties ) , const std::vector<PropertyName>& invalidatedProperties )
{ {
if (m_onPropertiesChangedHandler) if (m_onPropertiesChangedHandler)
m_onPropertiesChangedHandler(interfaceName, changedProperties, invalidatedProperties); m_onPropertiesChangedHandler(interfaceName, changedProperties, invalidatedProperties);
} }
void TestProxy::installDoOperationClientSideAsyncReplyHandler(std::function<void(uint32_t res, const sdbus::Error* err)> handler) void TestProxy::installDoOperationClientSideAsyncReplyHandler(std::function<void(uint32_t res, std::optional<sdbus::Error> err)> handler)
{ {
m_DoOperationClientSideAsyncReplyHandler = std::move(handler); m_DoOperationClientSideAsyncReplyHandler = std::move(handler);
} }
@ -110,22 +118,56 @@ sdbus::PendingAsyncCall TestProxy::doOperationClientSideAsync(uint32_t param)
return getProxy().callMethodAsync("doOperation") return getProxy().callMethodAsync("doOperation")
.onInterface(sdbus::test::INTERFACE_NAME) .onInterface(sdbus::test::INTERFACE_NAME)
.withArguments(param) .withArguments(param)
.uponReplyInvoke([this](const sdbus::Error* error, uint32_t returnValue) .uponReplyInvoke([this](std::optional<sdbus::Error> error, uint32_t returnValue)
{ {
this->onDoOperationReply(returnValue, error); this->onDoOperationReply(returnValue, std::move(error));
}); });
} }
Slot TestProxy::doOperationClientSideAsync(uint32_t param, sdbus::return_slot_t)
{
return getProxy().callMethodAsync("doOperation")
.onInterface(sdbus::test::INTERFACE_NAME)
.withArguments(param)
.uponReplyInvoke([this](std::optional<sdbus::Error> error, uint32_t returnValue)
{
this->onDoOperationReply(returnValue, std::move(error));
}, sdbus::return_slot);
}
std::future<uint32_t> TestProxy::doOperationClientSideAsync(uint32_t param, with_future_t)
{
return getProxy().callMethodAsync("doOperation")
.onInterface(sdbus::test::INTERFACE_NAME)
.withArguments(param)
.getResultAsFuture<uint32_t>();
}
std::future<MethodReply> TestProxy::doOperationClientSideAsyncOnBasicAPILevel(uint32_t param)
{
auto methodCall = getProxy().createMethodCall(sdbus::test::INTERFACE_NAME, sdbus::MethodName{"doOperation"});
methodCall << param;
return getProxy().callMethodAsync(methodCall, sdbus::with_future);
}
void TestProxy::doErroneousOperationClientSideAsync() void TestProxy::doErroneousOperationClientSideAsync()
{ {
getProxy().callMethodAsync("throwError") getProxy().callMethodAsync("throwError")
.onInterface(sdbus::test::INTERFACE_NAME) .onInterface(sdbus::test::INTERFACE_NAME)
.uponReplyInvoke([this](const sdbus::Error* error) .uponReplyInvoke([this](std::optional<sdbus::Error> error)
{ {
this->onDoOperationReply(0, error); this->onDoOperationReply(0, std::move(error));
}); });
} }
std::future<void> TestProxy::doErroneousOperationClientSideAsync(with_future_t)
{
return getProxy().callMethodAsync("throwError")
.onInterface(sdbus::test::INTERFACE_NAME)
.getResultAsFuture<>();
}
void TestProxy::doOperationClientSideAsyncWithTimeout(const std::chrono::microseconds &timeout, uint32_t param) void TestProxy::doOperationClientSideAsyncWithTimeout(const std::chrono::microseconds &timeout, uint32_t param)
{ {
using namespace std::chrono_literals; using namespace std::chrono_literals;
@ -133,9 +175,9 @@ void TestProxy::doOperationClientSideAsyncWithTimeout(const std::chrono::microse
.onInterface(sdbus::test::INTERFACE_NAME) .onInterface(sdbus::test::INTERFACE_NAME)
.withTimeout(timeout) .withTimeout(timeout)
.withArguments(param) .withArguments(param)
.uponReplyInvoke([this](const sdbus::Error* error, uint32_t returnValue) .uponReplyInvoke([this](std::optional<sdbus::Error> error, uint32_t returnValue)
{ {
this->onDoOperationReply(returnValue, error); this->onDoOperationReply(returnValue, std::move(error));
}); });
} }
@ -148,8 +190,9 @@ int32_t TestProxy::callNonexistentMethod()
int32_t TestProxy::callMethodOnNonexistentInterface() int32_t TestProxy::callMethodOnNonexistentInterface()
{ {
sdbus::InterfaceName nonexistentInterfaceName{"sdbuscpp.interface.that.does.not.exist"};
int32_t result; int32_t result;
getProxy().callMethod("someMethod").onInterface("sdbuscpp.interface.that.does.not.exist").storeResultsTo(result); getProxy().callMethod("someMethod").onInterface(nonexistentInterfaceName).storeResultsTo(result);
return result; return result;
} }

View File

@ -1,8 +1,8 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file TestAdaptor.h * @file TestProxy.h
* *
* Created on: Jan 2, 2017 * Created on: Jan 2, 2017
* Project: sdbus-c++ * Project: sdbus-c++
@ -32,13 +32,15 @@
#include <thread> #include <thread>
#include <chrono> #include <chrono>
#include <atomic> #include <atomic>
#include <future>
#include <memory>
namespace sdbus { namespace test { namespace sdbus { namespace test {
class ObjectManagerTestProxy final : public sdbus::ProxyInterfaces< sdbus::ObjectManager_proxy > class ObjectManagerTestProxy final : public sdbus::ProxyInterfaces< sdbus::ObjectManager_proxy >
{ {
public: public:
ObjectManagerTestProxy(sdbus::IConnection& connection, std::string destination, std::string objectPath) ObjectManagerTestProxy(sdbus::IConnection& connection, ServiceName destination, ObjectPath objectPath)
: ProxyInterfaces(connection, std::move(destination), std::move(objectPath)) : ProxyInterfaces(connection, std::move(destination), std::move(objectPath))
{ {
registerProxy(); registerProxy();
@ -49,21 +51,21 @@ public:
unregisterProxy(); unregisterProxy();
} }
protected: protected:
void onInterfacesAdded(const sdbus::ObjectPath& objectPath, const std::map<std::string, std::map<std::string, sdbus::Variant>>& interfacesAndProperties) override void onInterfacesAdded(const sdbus::ObjectPath& objectPath, const std::map<sdbus::InterfaceName, std::map<PropertyName, sdbus::Variant>>& interfacesAndProperties) override
{ {
if (m_onInterfacesAddedHandler) if (m_onInterfacesAddedHandler)
m_onInterfacesAddedHandler(objectPath, interfacesAndProperties); m_onInterfacesAddedHandler(objectPath, interfacesAndProperties);
} }
void onInterfacesRemoved(const sdbus::ObjectPath& objectPath, const std::vector<std::string>& interfaces) override void onInterfacesRemoved(const sdbus::ObjectPath& objectPath, const std::vector<sdbus::InterfaceName>& interfaces) override
{ {
if (m_onInterfacesRemovedHandler) if (m_onInterfacesRemovedHandler)
m_onInterfacesRemovedHandler(objectPath, interfaces); m_onInterfacesRemovedHandler(objectPath, interfaces);
} }
public: // for tests public: // for tests
std::function<void(const sdbus::ObjectPath&, const std::map<std::string, std::map<std::string, sdbus::Variant>>&)> m_onInterfacesAddedHandler; std::function<void(const sdbus::ObjectPath&, const std::map<sdbus::InterfaceName, std::map<PropertyName, sdbus::Variant>>&)> m_onInterfacesAddedHandler;
std::function<void(const sdbus::ObjectPath&, const std::vector<std::string>&)> m_onInterfacesRemovedHandler; std::function<void(const sdbus::ObjectPath&, const std::vector<sdbus::InterfaceName>&)> m_onInterfacesRemovedHandler;
}; };
class TestProxy final : public sdbus::ProxyInterfaces< org::sdbuscpp::integrationtests_proxy class TestProxy final : public sdbus::ProxyInterfaces< org::sdbuscpp::integrationtests_proxy
@ -72,8 +74,9 @@ class TestProxy final : public sdbus::ProxyInterfaces< org::sdbuscpp::integratio
, sdbus::Properties_proxy > , sdbus::Properties_proxy >
{ {
public: public:
TestProxy(std::string destination, std::string objectPath); TestProxy(ServiceName destination, ObjectPath objectPath);
TestProxy(sdbus::IConnection& connection, std::string destination, std::string objectPath); TestProxy(ServiceName destination, ObjectPath objectPath, dont_run_event_loop_thread_t);
TestProxy(sdbus::IConnection& connection, ServiceName destination, ObjectPath objectPath);
~TestProxy(); ~TestProxy();
protected: protected:
@ -82,17 +85,21 @@ protected:
void onSignalWithVariant(const sdbus::Variant& aVariant) override; void onSignalWithVariant(const sdbus::Variant& aVariant) override;
void onSignalWithoutRegistration(const sdbus::Struct<std::string, sdbus::Struct<sdbus::Signature>>& s); void onSignalWithoutRegistration(const sdbus::Struct<std::string, sdbus::Struct<sdbus::Signature>>& s);
void onDoOperationReply(uint32_t returnValue, const sdbus::Error* error); void onDoOperationReply(uint32_t returnValue, std::optional<sdbus::Error> error);
// Signals of standard D-Bus interfaces // Signals of standard D-Bus interfaces
void onPropertiesChanged( const std::string& interfaceName void onPropertiesChanged( const sdbus::InterfaceName& interfaceName
, const std::map<std::string, sdbus::Variant>& changedProperties , const std::map<PropertyName, sdbus::Variant>& changedProperties
, const std::vector<std::string>& invalidatedProperties ) override; , const std::vector<PropertyName>& invalidatedProperties ) override;
public: public:
void installDoOperationClientSideAsyncReplyHandler(std::function<void(uint32_t res, const sdbus::Error* err)> handler); void installDoOperationClientSideAsyncReplyHandler(std::function<void(uint32_t res, std::optional<sdbus::Error> err)> handler);
uint32_t doOperationWithTimeout(const std::chrono::microseconds &timeout, uint32_t param); uint32_t doOperationWithTimeout(const std::chrono::microseconds &timeout, uint32_t param);
sdbus::PendingAsyncCall doOperationClientSideAsync(uint32_t param); sdbus::PendingAsyncCall doOperationClientSideAsync(uint32_t param);
[[nodiscard]] sdbus::Slot doOperationClientSideAsync(uint32_t param, sdbus::return_slot_t);
std::future<uint32_t> doOperationClientSideAsync(uint32_t param, with_future_t);
std::future<MethodReply> doOperationClientSideAsyncOnBasicAPILevel(uint32_t param);
std::future<void> doErroneousOperationClientSideAsync(with_future_t);
void doErroneousOperationClientSideAsync(); void doErroneousOperationClientSideAsync();
void doOperationClientSideAsyncWithTimeout(const std::chrono::microseconds &timeout, uint32_t param); void doOperationClientSideAsyncWithTimeout(const std::chrono::microseconds &timeout, uint32_t param);
int32_t callNonexistentMethod(); int32_t callNonexistentMethod();
@ -108,13 +115,36 @@ public: // for tests
std::atomic<bool> m_gotSignalWithVariant{false}; std::atomic<bool> m_gotSignalWithVariant{false};
double m_variantFromSignal; double m_variantFromSignal;
std::atomic<bool> m_gotSignalWithSignature{false}; std::atomic<bool> m_gotSignalWithSignature{false};
std::map<std::string, std::string> m_signatureFromSignal; std::map<std::string, Signature> m_signatureFromSignal;
std::function<void(uint32_t res, const sdbus::Error* err)> m_DoOperationClientSideAsyncReplyHandler; std::function<void(uint32_t res, std::optional<sdbus::Error> err)> m_DoOperationClientSideAsyncReplyHandler;
std::function<void(const std::string&, const std::map<std::string, sdbus::Variant>&, const std::vector<std::string>&)> m_onPropertiesChangedHandler; std::function<void(const sdbus::InterfaceName&, const std::map<PropertyName, sdbus::Variant>&, const std::vector<PropertyName>&)> m_onPropertiesChangedHandler;
const Message* m_signalMsg{}; std::unique_ptr<const Message> m_signalMsg;
std::string m_signalMemberName; SignalName m_signalName;
};
class DummyTestProxy final : public sdbus::ProxyInterfaces< org::sdbuscpp::integrationtests_proxy
, sdbus::Peer_proxy
, sdbus::Introspectable_proxy
, sdbus::Properties_proxy >
{
public:
DummyTestProxy(ServiceName destination, ObjectPath objectPath)
: ProxyInterfaces(destination, objectPath)
{
}
protected:
void onSimpleSignal() override {}
void onSignalWithMap(const std::map<int32_t, std::string>&) override {}
void onSignalWithVariant(const sdbus::Variant&) override {}
void onSignalWithoutRegistration(const sdbus::Struct<std::string, sdbus::Struct<sdbus::Signature>>&) {}
void onDoOperationReply(uint32_t, std::optional<sdbus::Error>) {}
// Signals of standard D-Bus interfaces
void onPropertiesChanged(const InterfaceName&, const std::map<PropertyName, sdbus::Variant>&, const std::vector<PropertyName>&) override {}
}; };
}} }}

View File

@ -20,54 +20,64 @@ public:
protected: protected:
integrationtests_adaptor(sdbus::IObject& object) integrationtests_adaptor(sdbus::IObject& object)
: object_(object) : m_object(object)
{ {
object_.setInterfaceFlags(INTERFACE_NAME).markAsDeprecated().withPropertyUpdateBehavior(sdbus::Flags::EMITS_NO_SIGNAL);
object_.registerMethod("noArgNoReturn").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->noArgNoReturn(); });
object_.registerMethod("getInt").onInterface(INTERFACE_NAME).withOutputParamNames("anInt").implementedAs([this](){ return this->getInt(); });
object_.registerMethod("getTuple").onInterface(INTERFACE_NAME).withOutputParamNames("arg0", "arg1").implementedAs([this](){ return this->getTuple(); });
object_.registerMethod("multiply").onInterface(INTERFACE_NAME).withInputParamNames("a", "b").withOutputParamNames("result").implementedAs([this](const int64_t& a, const double& b){ return this->multiply(a, b); });
object_.registerMethod("multiplyWithNoReply").onInterface(INTERFACE_NAME).withInputParamNames("a", "b").implementedAs([this](const int64_t& a, const double& b){ return this->multiplyWithNoReply(a, b); }).markAsDeprecated().withNoReply();
object_.registerMethod("getInts16FromStruct").onInterface(INTERFACE_NAME).withInputParamNames("arg0").withOutputParamNames("arg0").implementedAs([this](const sdbus::Struct<uint8_t, int16_t, double, std::string, std::vector<int16_t>>& arg0){ return this->getInts16FromStruct(arg0); });
object_.registerMethod("processVariant").onInterface(INTERFACE_NAME).withInputParamNames("variant").withOutputParamNames("result").implementedAs([this](const sdbus::Variant& variant){ return this->processVariant(variant); });
object_.registerMethod("getMapOfVariants").onInterface(INTERFACE_NAME).withInputParamNames("x", "y").withOutputParamNames("aMapOfVariants").implementedAs([this](const std::vector<int32_t>& x, const sdbus::Struct<sdbus::Variant, sdbus::Variant>& y){ return this->getMapOfVariants(x, y); });
object_.registerMethod("getStructInStruct").onInterface(INTERFACE_NAME).withOutputParamNames("aMapOfVariants").implementedAs([this](){ return this->getStructInStruct(); });
object_.registerMethod("sumStructItems").onInterface(INTERFACE_NAME).withInputParamNames("arg0", "arg1").withOutputParamNames("arg0").implementedAs([this](const sdbus::Struct<uint8_t, uint16_t>& arg0, const sdbus::Struct<int32_t, int64_t>& arg1){ return this->sumStructItems(arg0, arg1); });
object_.registerMethod("sumVectorItems").onInterface(INTERFACE_NAME).withInputParamNames("arg0", "arg1").withOutputParamNames("arg0").implementedAs([this](const std::vector<uint16_t>& arg0, const std::vector<uint64_t>& arg1){ return this->sumVectorItems(arg0, arg1); });
object_.registerMethod("doOperation").onInterface(INTERFACE_NAME).withInputParamNames("arg0").withOutputParamNames("arg0").implementedAs([this](const uint32_t& arg0){ return this->doOperation(arg0); });
object_.registerMethod("doOperationAsync").onInterface(INTERFACE_NAME).withInputParamNames("arg0").withOutputParamNames("arg0").implementedAs([this](sdbus::Result<uint32_t>&& result, uint32_t arg0){ this->doOperationAsync(std::move(result), std::move(arg0)); });
object_.registerMethod("getSignature").onInterface(INTERFACE_NAME).withOutputParamNames("arg0").implementedAs([this](){ return this->getSignature(); });
object_.registerMethod("getObjPath").onInterface(INTERFACE_NAME).withOutputParamNames("arg0").implementedAs([this](){ return this->getObjPath(); });
object_.registerMethod("getUnixFd").onInterface(INTERFACE_NAME).withOutputParamNames("arg0").implementedAs([this](){ return this->getUnixFd(); });
object_.registerMethod("getComplex").onInterface(INTERFACE_NAME).withOutputParamNames("arg0").implementedAs([this](){ return this->getComplex(); }).markAsDeprecated();
object_.registerMethod("throwError").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->throwError(); });
object_.registerMethod("throwErrorWithNoReply").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->throwErrorWithNoReply(); }).withNoReply();
object_.registerMethod("doPrivilegedStuff").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->doPrivilegedStuff(); }).markAsPrivileged();
object_.registerMethod("emitTwoSimpleSignals").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->emitTwoSimpleSignals(); });
object_.registerSignal("simpleSignal").onInterface(INTERFACE_NAME).markAsDeprecated();
object_.registerSignal("signalWithMap").onInterface(INTERFACE_NAME).withParameters<std::map<int32_t, std::string>>("aMap");
object_.registerSignal("signalWithVariant").onInterface(INTERFACE_NAME).withParameters<sdbus::Variant>("aVariant");
object_.registerProperty("action").onInterface(INTERFACE_NAME).withGetter([this](){ return this->action(); }).withSetter([this](const uint32_t& value){ this->action(value); }).withUpdateBehavior(sdbus::Flags::EMITS_INVALIDATION_SIGNAL);
object_.registerProperty("blocking").onInterface(INTERFACE_NAME).withGetter([this](){ return this->blocking(); }).withSetter([this](const bool& value){ this->blocking(value); });
object_.registerProperty("state").onInterface(INTERFACE_NAME).withGetter([this](){ return this->state(); }).markAsDeprecated().withUpdateBehavior(sdbus::Flags::CONST_PROPERTY_VALUE);
} }
integrationtests_adaptor(const integrationtests_adaptor&) = delete;
integrationtests_adaptor& operator=(const integrationtests_adaptor&) = delete;
integrationtests_adaptor(integrationtests_adaptor&&) = delete;
integrationtests_adaptor& operator=(integrationtests_adaptor&&) = delete;
~integrationtests_adaptor() = default; ~integrationtests_adaptor() = default;
void registerAdaptor()
{
m_object.addVTable( sdbus::setInterfaceFlags().markAsDeprecated().withPropertyUpdateBehavior(sdbus::Flags::EMITS_NO_SIGNAL)
, sdbus::registerMethod("noArgNoReturn").implementedAs([this](){ return this->noArgNoReturn(); })
, sdbus::registerMethod("getInt").withOutputParamNames("anInt").implementedAs([this](){ return this->getInt(); })
, sdbus::registerMethod("getTuple").withOutputParamNames("arg0", "arg1").implementedAs([this](){ return this->getTuple(); })
, sdbus::registerMethod("multiply").withInputParamNames("a", "b").withOutputParamNames("result").implementedAs([this](const int64_t& a, const double& b){ return this->multiply(a, b); })
, sdbus::registerMethod("multiplyWithNoReply").withInputParamNames("a", "b").implementedAs([this](const int64_t& a, const double& b){ return this->multiplyWithNoReply(a, b); }).markAsDeprecated().withNoReply()
, sdbus::registerMethod("getInts16FromStruct").withInputParamNames("arg0").withOutputParamNames("arg0").implementedAs([this](const sdbus::Struct<uint8_t, int16_t, double, std::string, std::vector<int16_t>>& arg0){ return this->getInts16FromStruct(arg0); })
, sdbus::registerMethod("processVariant").withInputParamNames("variant").withOutputParamNames("result").implementedAs([this](const std::variant<int32_t, double, std::string>& variant){ return this->processVariant(variant); })
, sdbus::registerMethod("getMapOfVariants").withInputParamNames("x", "y").withOutputParamNames("aMapOfVariants").implementedAs([this](const std::vector<int32_t>& x, const sdbus::Struct<sdbus::Variant, sdbus::Variant>& y){ return this->getMapOfVariants(x, y); })
, sdbus::registerMethod("getStructInStruct").withOutputParamNames("aMapOfVariants").implementedAs([this](){ return this->getStructInStruct(); })
, sdbus::registerMethod("sumStructItems").withInputParamNames("arg0", "arg1").withOutputParamNames("arg0").implementedAs([this](const sdbus::Struct<uint8_t, uint16_t>& arg0, const sdbus::Struct<int32_t, int64_t>& arg1){ return this->sumStructItems(arg0, arg1); })
, sdbus::registerMethod("sumArrayItems").withInputParamNames("arg0", "arg1").withOutputParamNames("arg0").implementedAs([this](const std::vector<uint16_t>& arg0, const std::array<uint64_t, 3>& arg1){ return this->sumArrayItems(arg0, arg1); })
, sdbus::registerMethod("doOperation").withInputParamNames("arg0").withOutputParamNames("arg0").implementedAs([this](const uint32_t& arg0){ return this->doOperation(arg0); })
, sdbus::registerMethod("doOperationAsync").withInputParamNames("arg0").withOutputParamNames("arg0").implementedAs([this](sdbus::Result<uint32_t>&& result, uint32_t arg0){ this->doOperationAsync(std::move(result), std::move(arg0)); })
, sdbus::registerMethod("getSignature").withOutputParamNames("arg0").implementedAs([this](){ return this->getSignature(); })
, sdbus::registerMethod("getObjPath").withOutputParamNames("arg0").implementedAs([this](){ return this->getObjPath(); })
, sdbus::registerMethod("getUnixFd").withOutputParamNames("arg0").implementedAs([this](){ return this->getUnixFd(); })
, sdbus::registerMethod("getComplex").withOutputParamNames("arg0").implementedAs([this](){ return this->getComplex(); }).markAsDeprecated()
, sdbus::registerMethod("throwError").implementedAs([this](){ return this->throwError(); })
, sdbus::registerMethod("throwErrorWithNoReply").implementedAs([this](){ return this->throwErrorWithNoReply(); }).withNoReply()
, sdbus::registerMethod("doPrivilegedStuff").implementedAs([this](){ return this->doPrivilegedStuff(); }).markAsPrivileged()
, sdbus::registerMethod("emitTwoSimpleSignals").implementedAs([this](){ return this->emitTwoSimpleSignals(); })
, sdbus::registerSignal("simpleSignal").markAsDeprecated()
, sdbus::registerSignal("signalWithMap").withParameters<std::map<int32_t, std::string>>("aMap")
, sdbus::registerSignal("signalWithVariant").withParameters<sdbus::Variant>("aVariant")
, sdbus::registerProperty("action").withGetter([this](){ return this->action(); }).withSetter([this](const uint32_t& value){ this->action(value); }).withUpdateBehavior(sdbus::Flags::EMITS_INVALIDATION_SIGNAL)
, sdbus::registerProperty("blocking").withGetter([this](){ return this->blocking(); }).withSetter([this](const bool& value){ this->blocking(value); })
, sdbus::registerProperty("state").withGetter([this](){ return this->state(); }).markAsDeprecated().withUpdateBehavior(sdbus::Flags::CONST_PROPERTY_VALUE)
).forInterface(INTERFACE_NAME);
}
public: public:
void emitSimpleSignal() void emitSimpleSignal()
{ {
object_.emitSignal("simpleSignal").onInterface(INTERFACE_NAME); m_object.emitSignal("simpleSignal").onInterface(INTERFACE_NAME);
} }
void emitSignalWithMap(const std::map<int32_t, std::string>& aMap) void emitSignalWithMap(const std::map<int32_t, std::string>& aMap)
{ {
object_.emitSignal("signalWithMap").onInterface(INTERFACE_NAME).withArguments(aMap); m_object.emitSignal("signalWithMap").onInterface(INTERFACE_NAME).withArguments(aMap);
} }
void emitSignalWithVariant(const sdbus::Variant& aVariant) void emitSignalWithVariant(const sdbus::Variant& aVariant)
{ {
object_.emitSignal("signalWithVariant").onInterface(INTERFACE_NAME).withArguments(aVariant); m_object.emitSignal("signalWithVariant").onInterface(INTERFACE_NAME).withArguments(aVariant);
} }
private: private:
@ -77,17 +87,17 @@ private:
virtual double multiply(const int64_t& a, const double& b) = 0; virtual double multiply(const int64_t& a, const double& b) = 0;
virtual void multiplyWithNoReply(const int64_t& a, const double& b) = 0; virtual void multiplyWithNoReply(const int64_t& a, const double& b) = 0;
virtual std::vector<int16_t> getInts16FromStruct(const sdbus::Struct<uint8_t, int16_t, double, std::string, std::vector<int16_t>>& arg0) = 0; virtual std::vector<int16_t> getInts16FromStruct(const sdbus::Struct<uint8_t, int16_t, double, std::string, std::vector<int16_t>>& arg0) = 0;
virtual sdbus::Variant processVariant(const sdbus::Variant& variant) = 0; virtual sdbus::Variant processVariant(const std::variant<int32_t, double, std::string>& variant) = 0;
virtual std::map<int32_t, sdbus::Variant> getMapOfVariants(const std::vector<int32_t>& x, const sdbus::Struct<sdbus::Variant, sdbus::Variant>& y) = 0; virtual std::map<int32_t, sdbus::Variant> getMapOfVariants(const std::vector<int32_t>& x, const sdbus::Struct<sdbus::Variant, sdbus::Variant>& y) = 0;
virtual sdbus::Struct<std::string, sdbus::Struct<std::map<int32_t, int32_t>>> getStructInStruct() = 0; virtual sdbus::Struct<std::string, sdbus::Struct<std::map<int32_t, int32_t>>> getStructInStruct() = 0;
virtual int32_t sumStructItems(const sdbus::Struct<uint8_t, uint16_t>& arg0, const sdbus::Struct<int32_t, int64_t>& arg1) = 0; virtual int32_t sumStructItems(const sdbus::Struct<uint8_t, uint16_t>& arg0, const sdbus::Struct<int32_t, int64_t>& arg1) = 0;
virtual uint32_t sumVectorItems(const std::vector<uint16_t>& arg0, const std::vector<uint64_t>& arg1) = 0; virtual uint32_t sumArrayItems(const std::vector<uint16_t>& arg0, const std::array<uint64_t, 3>& arg1) = 0;
virtual uint32_t doOperation(const uint32_t& arg0) = 0; virtual uint32_t doOperation(const uint32_t& arg0) = 0;
virtual void doOperationAsync(sdbus::Result<uint32_t>&& result, uint32_t arg0) = 0; virtual void doOperationAsync(sdbus::Result<uint32_t>&& result, uint32_t arg0) = 0;
virtual sdbus::Signature getSignature() = 0; virtual sdbus::Signature getSignature() = 0;
virtual sdbus::ObjectPath getObjPath() = 0; virtual sdbus::ObjectPath getObjPath() = 0;
virtual sdbus::UnixFd getUnixFd() = 0; virtual sdbus::UnixFd getUnixFd() = 0;
virtual std::map<uint64_t, sdbus::Struct<std::map<uint8_t, std::vector<sdbus::Struct<sdbus::ObjectPath, bool, sdbus::Variant, std::map<int32_t, std::string>>>>, sdbus::Signature, std::string>> getComplex() = 0; virtual std::unordered_map<uint64_t, sdbus::Struct<std::map<uint8_t, std::vector<sdbus::Struct<sdbus::ObjectPath, bool, sdbus::Variant, std::map<int32_t, std::string>>>>, sdbus::Signature, std::string>> getComplex() = 0;
virtual void throwError() = 0; virtual void throwError() = 0;
virtual void throwErrorWithNoReply() = 0; virtual void throwErrorWithNoReply() = 0;
virtual void doPrivilegedStuff() = 0; virtual void doPrivilegedStuff() = 0;
@ -101,7 +111,7 @@ private:
virtual std::string state() = 0; virtual std::string state() = 0;
private: private:
sdbus::IObject& object_; sdbus::IObject& m_object;
}; };
}} // namespaces }} // namespaces

View File

@ -20,15 +20,24 @@ public:
protected: protected:
integrationtests_proxy(sdbus::IProxy& proxy) integrationtests_proxy(sdbus::IProxy& proxy)
: proxy_(proxy) : m_proxy(proxy)
{ {
proxy_.uponSignal("simpleSignal").onInterface(INTERFACE_NAME).call([this](){ this->onSimpleSignal(); });
proxy_.uponSignal("signalWithMap").onInterface(INTERFACE_NAME).call([this](const std::map<int32_t, std::string>& aMap){ this->onSignalWithMap(aMap); });
proxy_.uponSignal("signalWithVariant").onInterface(INTERFACE_NAME).call([this](const sdbus::Variant& aVariant){ this->onSignalWithVariant(aVariant); });
} }
integrationtests_proxy(const integrationtests_proxy&) = delete;
integrationtests_proxy& operator=(const integrationtests_proxy&) = delete;
integrationtests_proxy(integrationtests_proxy&&) = delete;
integrationtests_proxy& operator=(integrationtests_proxy&&) = delete;
~integrationtests_proxy() = default; ~integrationtests_proxy() = default;
void registerProxy()
{
simpleSignalSlot_ = m_proxy.uponSignal("simpleSignal").onInterface(INTERFACE_NAME).call([this](){ this->onSimpleSignal(); }, sdbus::return_slot);
m_proxy.uponSignal("signalWithMap").onInterface(INTERFACE_NAME).call([this](const std::map<int32_t, std::string>& aMap){ this->onSignalWithMap(aMap); });
m_proxy.uponSignal("signalWithVariant").onInterface(INTERFACE_NAME).call([this](const sdbus::Variant& aVariant){ this->onSignalWithVariant(aVariant); });
}
virtual void onSimpleSignal() = 0; virtual void onSimpleSignal() = 0;
virtual void onSignalWithMap(const std::map<int32_t, std::string>& aMap) = 0; virtual void onSignalWithMap(const std::map<int32_t, std::string>& aMap) = 0;
virtual void onSignalWithVariant(const sdbus::Variant& aVariant) = 0; virtual void onSignalWithVariant(const sdbus::Variant& aVariant) = 0;
@ -36,178 +45,185 @@ protected:
public: public:
void noArgNoReturn() void noArgNoReturn()
{ {
proxy_.callMethod("noArgNoReturn").onInterface(INTERFACE_NAME); m_proxy.callMethod("noArgNoReturn").onInterface(INTERFACE_NAME);
} }
int32_t getInt() int32_t getInt()
{ {
int32_t result; int32_t result;
proxy_.callMethod("getInt").onInterface(INTERFACE_NAME).storeResultsTo(result); m_proxy.callMethod("getInt").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result; return result;
} }
std::tuple<uint32_t, std::string> getTuple() std::tuple<uint32_t, std::string> getTuple()
{ {
std::tuple<uint32_t, std::string> result; std::tuple<uint32_t, std::string> result;
proxy_.callMethod("getTuple").onInterface(INTERFACE_NAME).storeResultsTo(result); m_proxy.callMethod("getTuple").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result; return result;
} }
double multiply(const int64_t& a, const double& b) double multiply(const int64_t& a, const double& b)
{ {
double result; double result;
proxy_.callMethod("multiply").onInterface(INTERFACE_NAME).withArguments(a, b).storeResultsTo(result); m_proxy.callMethod("multiply").onInterface(INTERFACE_NAME).withArguments(a, b).storeResultsTo(result);
return result; return result;
} }
void multiplyWithNoReply(const int64_t& a, const double& b) void multiplyWithNoReply(const int64_t& a, const double& b)
{ {
proxy_.callMethod("multiplyWithNoReply").onInterface(INTERFACE_NAME).withArguments(a, b).dontExpectReply(); m_proxy.callMethod("multiplyWithNoReply").onInterface(INTERFACE_NAME).withArguments(a, b).dontExpectReply();
} }
std::vector<int16_t> getInts16FromStruct(const sdbus::Struct<uint8_t, int16_t, double, std::string, std::vector<int16_t>>& arg0) std::vector<int16_t> getInts16FromStruct(const sdbus::Struct<uint8_t, int16_t, double, std::string, std::vector<int16_t>>& arg0)
{ {
std::vector<int16_t> result; std::vector<int16_t> result;
proxy_.callMethod("getInts16FromStruct").onInterface(INTERFACE_NAME).withArguments(arg0).storeResultsTo(result); m_proxy.callMethod("getInts16FromStruct").onInterface(INTERFACE_NAME).withArguments(arg0).storeResultsTo(result);
return result; return result;
} }
sdbus::Variant processVariant(const sdbus::Variant& variant) sdbus::Variant processVariant(const sdbus::Variant& variant)
{ {
sdbus::Variant result; sdbus::Variant result;
proxy_.callMethod("processVariant").onInterface(INTERFACE_NAME).withArguments(variant).storeResultsTo(result); m_proxy.callMethod("processVariant").onInterface(INTERFACE_NAME).withArguments(variant).storeResultsTo(result);
return result;
}
std::variant<int32_t, double, std::string> processVariant(const std::variant<int32_t, double, std::string>& variant)
{
std::variant<int32_t, double, std::string> result;
m_proxy.callMethod("processVariant").onInterface(INTERFACE_NAME).withArguments(variant).storeResultsTo(result);
return result; return result;
} }
std::map<int32_t, sdbus::Variant> getMapOfVariants(const std::vector<int32_t>& x, const sdbus::Struct<sdbus::Variant, sdbus::Variant>& y) std::map<int32_t, sdbus::Variant> getMapOfVariants(const std::vector<int32_t>& x, const sdbus::Struct<sdbus::Variant, sdbus::Variant>& y)
{ {
std::map<int32_t, sdbus::Variant> result; std::map<int32_t, sdbus::Variant> result;
proxy_.callMethod("getMapOfVariants").onInterface(INTERFACE_NAME).withArguments(x, y).storeResultsTo(result); m_proxy.callMethod("getMapOfVariants").onInterface(INTERFACE_NAME).withArguments(x, y).storeResultsTo(result);
return result; return result;
} }
sdbus::Struct<std::string, sdbus::Struct<std::map<int32_t, int32_t>>> getStructInStruct() sdbus::Struct<std::string, sdbus::Struct<std::map<int32_t, int32_t>>> getStructInStruct()
{ {
sdbus::Struct<std::string, sdbus::Struct<std::map<int32_t, int32_t>>> result; sdbus::Struct<std::string, sdbus::Struct<std::map<int32_t, int32_t>>> result;
proxy_.callMethod("getStructInStruct").onInterface(INTERFACE_NAME).storeResultsTo(result); m_proxy.callMethod("getStructInStruct").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result; return result;
} }
int32_t sumStructItems(const sdbus::Struct<uint8_t, uint16_t>& arg0, const sdbus::Struct<int32_t, int64_t>& arg1) int32_t sumStructItems(const sdbus::Struct<uint8_t, uint16_t>& arg0, const sdbus::Struct<int32_t, int64_t>& arg1)
{ {
int32_t result; int32_t result;
proxy_.callMethod("sumStructItems").onInterface(INTERFACE_NAME).withArguments(arg0, arg1).storeResultsTo(result); m_proxy.callMethod("sumStructItems").onInterface(INTERFACE_NAME).withArguments(arg0, arg1).storeResultsTo(result);
return result; return result;
} }
uint32_t sumVectorItems(const std::vector<uint16_t>& arg0, const std::vector<uint64_t>& arg1) uint32_t sumArrayItems(const std::vector<uint16_t>& arg0, const std::array<uint64_t, 3>& arg1)
{ {
uint32_t result; uint32_t result;
proxy_.callMethod("sumVectorItems").onInterface(INTERFACE_NAME).withArguments(arg0, arg1).storeResultsTo(result); m_proxy.callMethod("sumArrayItems").onInterface(INTERFACE_NAME).withArguments(arg0, arg1).storeResultsTo(result);
return result; return result;
} }
uint32_t doOperation(const uint32_t& arg0) uint32_t doOperation(const uint32_t& arg0)
{ {
uint32_t result; uint32_t result;
proxy_.callMethod("doOperation").onInterface(INTERFACE_NAME).withArguments(arg0).storeResultsTo(result); m_proxy.callMethod("doOperation").onInterface(INTERFACE_NAME).withArguments(arg0).storeResultsTo(result);
return result; return result;
} }
uint32_t doOperationAsync(const uint32_t& arg0) uint32_t doOperationAsync(const uint32_t& arg0)
{ {
uint32_t result; uint32_t result;
proxy_.callMethod("doOperationAsync").onInterface(INTERFACE_NAME).withArguments(arg0).storeResultsTo(result); m_proxy.callMethod("doOperationAsync").onInterface(INTERFACE_NAME).withArguments(arg0).storeResultsTo(result);
return result; return result;
} }
sdbus::Signature getSignature() sdbus::Signature getSignature()
{ {
sdbus::Signature result; sdbus::Signature result;
proxy_.callMethod("getSignature").onInterface(INTERFACE_NAME).storeResultsTo(result); m_proxy.callMethod("getSignature").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result; return result;
} }
sdbus::ObjectPath getObjPath() sdbus::ObjectPath getObjPath()
{ {
sdbus::ObjectPath result; sdbus::ObjectPath result;
proxy_.callMethod("getObjPath").onInterface(INTERFACE_NAME).storeResultsTo(result); m_proxy.callMethod("getObjPath").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result; return result;
} }
sdbus::UnixFd getUnixFd() sdbus::UnixFd getUnixFd()
{ {
sdbus::UnixFd result; sdbus::UnixFd result;
proxy_.callMethod("getUnixFd").onInterface(INTERFACE_NAME).storeResultsTo(result); m_proxy.callMethod("getUnixFd").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result; return result;
} }
std::map<uint64_t, sdbus::Struct<std::map<uint8_t, std::vector<sdbus::Struct<sdbus::ObjectPath, bool, sdbus::Variant, std::map<int32_t, std::string>>>>, sdbus::Signature, std::string>> getComplex() std::map<uint64_t, sdbus::Struct<std::map<uint8_t, std::vector<sdbus::Struct<sdbus::ObjectPath, bool, sdbus::Variant, std::unordered_map<int32_t, std::string>>>>, sdbus::Signature, std::string>> getComplex()
{ {
std::map<uint64_t, sdbus::Struct<std::map<uint8_t, std::vector<sdbus::Struct<sdbus::ObjectPath, bool, sdbus::Variant, std::map<int32_t, std::string>>>>, sdbus::Signature, std::string>> result; std::map<uint64_t, sdbus::Struct<std::map<uint8_t, std::vector<sdbus::Struct<sdbus::ObjectPath, bool, sdbus::Variant, std::unordered_map<int32_t, std::string>>>>, sdbus::Signature, std::string>> result;
proxy_.callMethod("getComplex").onInterface(INTERFACE_NAME).storeResultsTo(result); m_proxy.callMethod("getComplex").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result; return result;
} }
void throwError() void throwError()
{ {
proxy_.callMethod("throwError").onInterface(INTERFACE_NAME); m_proxy.callMethod("throwError").onInterface(INTERFACE_NAME);
} }
void throwErrorWithNoReply() void throwErrorWithNoReply()
{ {
proxy_.callMethod("throwErrorWithNoReply").onInterface(INTERFACE_NAME).dontExpectReply(); m_proxy.callMethod("throwErrorWithNoReply").onInterface(INTERFACE_NAME).dontExpectReply();
} }
void doPrivilegedStuff() void doPrivilegedStuff()
{ {
proxy_.callMethod("doPrivilegedStuff").onInterface(INTERFACE_NAME); m_proxy.callMethod("doPrivilegedStuff").onInterface(INTERFACE_NAME);
} }
void emitTwoSimpleSignals() void emitTwoSimpleSignals()
{ {
proxy_.callMethod("emitTwoSimpleSignals").onInterface(INTERFACE_NAME); m_proxy.callMethod("emitTwoSimpleSignals").onInterface(INTERFACE_NAME);
} }
void unregisterSimpleSignalHandler() void unregisterSimpleSignalHandler()
{ {
proxy_.muteSignal("simpleSignal").onInterface(INTERFACE_NAME); simpleSignalSlot_.reset();
} }
void reRegisterSimpleSignalHandler() void reRegisterSimpleSignalHandler()
{ {
proxy_.uponSignal("simpleSignal").onInterface(INTERFACE_NAME).call([this](){ this->onSimpleSignal(); }); simpleSignalSlot_ = m_proxy.uponSignal("simpleSignal").onInterface(INTERFACE_NAME).call([this](){ this->onSimpleSignal(); }, sdbus::return_slot);
proxy_.finishRegistration();
} }
public: public:
uint32_t action() uint32_t action()
{ {
return proxy_.getProperty("action").onInterface(INTERFACE_NAME); return m_proxy.getProperty("action").onInterface(INTERFACE_NAME).get<uint32_t>();
} }
void action(const uint32_t& value) void action(const uint32_t& value)
{ {
proxy_.setProperty("action").onInterface(INTERFACE_NAME).toValue(value); m_proxy.setProperty("action").onInterface(INTERFACE_NAME).toValue(value);
} }
bool blocking() bool blocking()
{ {
return proxy_.getProperty("blocking").onInterface(INTERFACE_NAME); return m_proxy.getProperty("blocking").onInterface(INTERFACE_NAME).get<bool>();
} }
void blocking(const bool& value) void blocking(const bool& value)
{ {
proxy_.setProperty("blocking").onInterface(INTERFACE_NAME).toValue(value); m_proxy.setProperty("blocking").onInterface(INTERFACE_NAME).toValue(value);
} }
std::string state() std::string state()
{ {
return proxy_.getProperty("state").onInterface(INTERFACE_NAME); return m_proxy.getProperty("state").onInterface(INTERFACE_NAME).get<std::string>();
} }
private: private:
sdbus::IProxy& proxy_; sdbus::IProxy& m_proxy;
sdbus::Slot simpleSignalSlot_;
}; };
}} // namespaces }} // namespaces

View File

@ -45,7 +45,7 @@
<arg type="(ix)" direction="in" /> <arg type="(ix)" direction="in" />
<arg type="i" direction="out" /> <arg type="i" direction="out" />
</method> </method>
<method name="sumVectorItems"> <method name="sumArrayItems">
<arg type="aq" direction="in" /> <arg type="aq" direction="in" />
<arg type="at" direction="in" /> <arg type="at" direction="in" />
<arg type="u" direction="out" /> <arg type="u" direction="out" />
@ -82,7 +82,7 @@
</method> </method>
<method name="emitTwoSimpleSignals"> <method name="emitTwoSimpleSignals">
</method> </method>
<signal name="simpleSignal"> <signal name="simpleSignal">
<annotation name="org.freedesktop.DBus.Deprecated" value="true" /> <annotation name="org.freedesktop.DBus.Deprecated" value="true" />
</signal> </signal>
@ -102,4 +102,4 @@
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/> <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
</property> </property>
</interface> </interface>
</node> </node>

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file sdbus-c++-integration-tests.cpp * @file sdbus-c++-integration-tests.cpp
* *

View File

@ -1,5 +1,6 @@
/** /**
* (C) 2019 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file client.cpp * @file client.cpp
* *
@ -42,7 +43,7 @@ uint64_t totalDuration = 0;
class PerftestProxy final : public sdbus::ProxyInterfaces<org::sdbuscpp::perftests_proxy> class PerftestProxy final : public sdbus::ProxyInterfaces<org::sdbuscpp::perftests_proxy>
{ {
public: public:
PerftestProxy(std::string destination, std::string objectPath) PerftestProxy(sdbus::ServiceName destination, sdbus::ObjectPath objectPath)
: ProxyInterfaces(std::move(destination), std::move(objectPath)) : ProxyInterfaces(std::move(destination), std::move(objectPath))
{ {
registerProxy(); registerProxy();
@ -100,9 +101,9 @@ std::string createRandomString(size_t length)
//----------------------------------------- //-----------------------------------------
int main(int /*argc*/, char */*argv*/[]) int main(int /*argc*/, char */*argv*/[])
{ {
const char* destinationName = "org.sdbuscpp.perftests"; sdbus::ServiceName destination{"org.sdbuscpp.perftests"};
const char* objectPath = "/org/sdbuscpp/perftests"; sdbus::ObjectPath objectPath{"/org/sdbuscpp/perftests"};
PerftestProxy client(destinationName, objectPath); PerftestProxy client(std::move(destination), std::move(objectPath));
const unsigned int repetitions{20}; const unsigned int repetitions{20};
unsigned int msgCount = 1000; unsigned int msgCount = 1000;

View File

@ -20,19 +20,29 @@ public:
protected: protected:
perftests_adaptor(sdbus::IObject& object) perftests_adaptor(sdbus::IObject& object)
: object_(object) : m_object(object)
{ {
object_.registerMethod("sendDataSignals").onInterface(INTERFACE_NAME).withInputParamNames("numberOfSignals", "signalMsgSize").implementedAs([this](const uint32_t& numberOfSignals, const uint32_t& signalMsgSize){ return this->sendDataSignals(numberOfSignals, signalMsgSize); });
object_.registerMethod("concatenateTwoStrings").onInterface(INTERFACE_NAME).withInputParamNames("string1", "string2").withOutputParamNames("result").implementedAs([this](const std::string& string1, const std::string& string2){ return this->concatenateTwoStrings(string1, string2); });
object_.registerSignal("dataSignal").onInterface(INTERFACE_NAME).withParameters<std::string>("data");
} }
perftests_adaptor(const perftests_adaptor&) = delete;
perftests_adaptor& operator=(const perftests_adaptor&) = delete;
perftests_adaptor(perftests_adaptor&&) = delete;
perftests_adaptor& operator=(perftests_adaptor&&) = delete;
~perftests_adaptor() = default; ~perftests_adaptor() = default;
void registerAdaptor()
{
m_object.addVTable( sdbus::registerMethod("sendDataSignals").withInputParamNames("numberOfSignals", "signalMsgSize").implementedAs([this](const uint32_t& numberOfSignals, const uint32_t& signalMsgSize){ return this->sendDataSignals(numberOfSignals, signalMsgSize); })
, sdbus::registerMethod("concatenateTwoStrings").withInputParamNames("string1", "string2").withOutputParamNames("result").implementedAs([this](const std::string& string1, const std::string& string2){ return this->concatenateTwoStrings(string1, string2); })
, sdbus::registerSignal("dataSignal").withParameters<std::string>("data")
).forInterface(INTERFACE_NAME);
}
public: public:
void emitDataSignal(const std::string& data) void emitDataSignal(const std::string& data)
{ {
object_.emitSignal("dataSignal").onInterface(INTERFACE_NAME).withArguments(data); m_object.emitSignal("dataSignal").onInterface(INTERFACE_NAME).withArguments(data);
} }
private: private:
@ -40,7 +50,7 @@ private:
virtual std::string concatenateTwoStrings(const std::string& string1, const std::string& string2) = 0; virtual std::string concatenateTwoStrings(const std::string& string1, const std::string& string2) = 0;
private: private:
sdbus::IObject& object_; sdbus::IObject& m_object;
}; };
}} // namespaces }} // namespaces

View File

@ -20,30 +20,39 @@ public:
protected: protected:
perftests_proxy(sdbus::IProxy& proxy) perftests_proxy(sdbus::IProxy& proxy)
: proxy_(proxy) : m_proxy(proxy)
{ {
proxy_.uponSignal("dataSignal").onInterface(INTERFACE_NAME).call([this](const std::string& data){ this->onDataSignal(data); });
} }
perftests_proxy(const perftests_proxy&) = delete;
perftests_proxy& operator=(const perftests_proxy&) = delete;
perftests_proxy(perftests_proxy&&) = delete;
perftests_proxy& operator=(perftests_proxy&&) = delete;
~perftests_proxy() = default; ~perftests_proxy() = default;
void registerProxy()
{
m_proxy.uponSignal("dataSignal").onInterface(INTERFACE_NAME).call([this](const std::string& data){ this->onDataSignal(data); });
}
virtual void onDataSignal(const std::string& data) = 0; virtual void onDataSignal(const std::string& data) = 0;
public: public:
void sendDataSignals(const uint32_t& numberOfSignals, const uint32_t& signalMsgSize) void sendDataSignals(const uint32_t& numberOfSignals, const uint32_t& signalMsgSize)
{ {
proxy_.callMethod("sendDataSignals").onInterface(INTERFACE_NAME).withArguments(numberOfSignals, signalMsgSize); m_proxy.callMethod("sendDataSignals").onInterface(INTERFACE_NAME).withArguments(numberOfSignals, signalMsgSize);
} }
std::string concatenateTwoStrings(const std::string& string1, const std::string& string2) std::string concatenateTwoStrings(const std::string& string1, const std::string& string2)
{ {
std::string result; std::string result;
proxy_.callMethod("concatenateTwoStrings").onInterface(INTERFACE_NAME).withArguments(string1, string2).storeResultsTo(result); m_proxy.callMethod("concatenateTwoStrings").onInterface(INTERFACE_NAME).withArguments(string1, string2).storeResultsTo(result);
return result; return result;
} }
private: private:
sdbus::IProxy& proxy_; sdbus::IProxy& m_proxy;
}; };
}} // namespaces }} // namespaces

View File

@ -1,5 +1,6 @@
/** /**
* (C) 2019 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file server.cpp * @file server.cpp
* *
@ -39,7 +40,7 @@ std::string createRandomString(size_t length);
class PerftestAdaptor final : public sdbus::AdaptorInterfaces<org::sdbuscpp::perftests_adaptor> class PerftestAdaptor final : public sdbus::AdaptorInterfaces<org::sdbuscpp::perftests_adaptor>
{ {
public: public:
PerftestAdaptor(sdbus::IConnection& connection, std::string objectPath) PerftestAdaptor(sdbus::IConnection& connection, sdbus::ObjectPath objectPath)
: AdaptorInterfaces(connection, std::move(objectPath)) : AdaptorInterfaces(connection, std::move(objectPath))
{ {
registerAdaptor(); registerAdaptor();
@ -91,11 +92,11 @@ std::string createRandomString(size_t length)
//----------------------------------------- //-----------------------------------------
int main(int /*argc*/, char */*argv*/[]) int main(int /*argc*/, char */*argv*/[])
{ {
const char* serviceName = "org.sdbuscpp.perftests"; sdbus::ServiceName serviceName{"org.sdbuscpp.perftests"};
auto connection = sdbus::createSystemBusConnection(serviceName); auto connection = sdbus::createSystemBusConnection(serviceName);
const char* objectPath = "/org/sdbuscpp/perftests"; sdbus::ObjectPath objectPath{"/org/sdbuscpp/perftests"};
PerftestAdaptor server(*connection, objectPath); PerftestAdaptor server(*connection, std::move(objectPath));
connection->enterEventLoop(); connection->enterEventLoop();
} }

View File

@ -22,18 +22,27 @@ public:
protected: protected:
thermometer_adaptor(sdbus::IObject& object) thermometer_adaptor(sdbus::IObject& object)
: object_(object) : m_object(object)
{ {
object_.registerMethod("getCurrentTemperature").onInterface(INTERFACE_NAME).withOutputParamNames("result").implementedAs([this](){ return this->getCurrentTemperature(); });
} }
thermometer_adaptor(const thermometer_adaptor&) = delete;
thermometer_adaptor& operator=(const thermometer_adaptor&) = delete;
thermometer_adaptor(thermometer_adaptor&&) = delete;
thermometer_adaptor& operator=(thermometer_adaptor&&) = delete;
~thermometer_adaptor() = default; ~thermometer_adaptor() = default;
void registerAdaptor()
{
m_object.addVTable(sdbus::registerMethod("getCurrentTemperature").withOutputParamNames("result").implementedAs([this](){ return this->getCurrentTemperature(); })).forInterface(INTERFACE_NAME);
}
private: private:
virtual uint32_t getCurrentTemperature() = 0; virtual uint32_t getCurrentTemperature() = 0;
private: private:
sdbus::IObject& object_; sdbus::IObject& m_object;
}; };
}}}} // namespaces }}}} // namespaces

View File

@ -22,22 +22,31 @@ public:
protected: protected:
thermometer_proxy(sdbus::IProxy& proxy) thermometer_proxy(sdbus::IProxy& proxy)
: proxy_(proxy) : m_proxy(proxy)
{ {
} }
thermometer_proxy(const thermometer_proxy&) = delete;
thermometer_proxy& operator=(const thermometer_proxy&) = delete;
thermometer_proxy(thermometer_proxy&&) = delete;
thermometer_proxy& operator=(thermometer_proxy&&) = delete;
~thermometer_proxy() = default; ~thermometer_proxy() = default;
void registerProxy()
{
}
public: public:
uint32_t getCurrentTemperature() uint32_t getCurrentTemperature()
{ {
uint32_t result; uint32_t result;
proxy_.callMethod("getCurrentTemperature").onInterface(INTERFACE_NAME).storeResultsTo(result); m_proxy.callMethod("getCurrentTemperature").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result; return result;
} }
private: private:
sdbus::IProxy& proxy_; sdbus::IProxy& m_proxy;
}; };
}}}} // namespaces }}}} // namespaces

View File

@ -21,25 +21,35 @@ public:
protected: protected:
concatenator_adaptor(sdbus::IObject& object) concatenator_adaptor(sdbus::IObject& object)
: object_(object) : m_object(object)
{ {
object_.registerMethod("concatenate").onInterface(INTERFACE_NAME).withInputParamNames("params").withOutputParamNames("result").implementedAs([this](sdbus::Result<std::string>&& result, std::map<std::string, sdbus::Variant> params){ this->concatenate(std::move(result), std::move(params)); });
object_.registerSignal("concatenatedSignal").onInterface(INTERFACE_NAME).withParameters<std::string>("concatenatedString");
} }
concatenator_adaptor(const concatenator_adaptor&) = delete;
concatenator_adaptor& operator=(const concatenator_adaptor&) = delete;
concatenator_adaptor(concatenator_adaptor&&) = delete;
concatenator_adaptor& operator=(concatenator_adaptor&&) = delete;
~concatenator_adaptor() = default; ~concatenator_adaptor() = default;
void registerAdaptor()
{
m_object.addVTable( sdbus::registerMethod("concatenate").withInputParamNames("params").withOutputParamNames("result").implementedAs([this](sdbus::Result<std::string>&& result, std::map<std::string, sdbus::Variant> params){ this->concatenate(std::move(result), std::move(params)); })
, sdbus::registerSignal("concatenatedSignal").withParameters<std::string>("concatenatedString")
).forInterface(INTERFACE_NAME);
}
public: public:
void emitConcatenatedSignal(const std::string& concatenatedString) void emitConcatenatedSignal(const std::string& concatenatedString)
{ {
object_.emitSignal("concatenatedSignal").onInterface(INTERFACE_NAME).withArguments(concatenatedString); m_object.emitSignal("concatenatedSignal").onInterface(INTERFACE_NAME).withArguments(concatenatedString);
} }
private: private:
virtual void concatenate(sdbus::Result<std::string>&& result, std::map<std::string, sdbus::Variant> params) = 0; virtual void concatenate(sdbus::Result<std::string>&& result, std::map<std::string, sdbus::Variant> params) = 0;
private: private:
sdbus::IObject& object_; sdbus::IObject& m_object;
}; };
}}} // namespaces }}} // namespaces

View File

@ -21,25 +21,34 @@ public:
protected: protected:
concatenator_proxy(sdbus::IProxy& proxy) concatenator_proxy(sdbus::IProxy& proxy)
: proxy_(proxy) : m_proxy(proxy)
{ {
proxy_.uponSignal("concatenatedSignal").onInterface(INTERFACE_NAME).call([this](const std::string& concatenatedString){ this->onConcatenatedSignal(concatenatedString); });
} }
concatenator_proxy(const concatenator_proxy&) = delete;
concatenator_proxy& operator=(const concatenator_proxy&) = delete;
concatenator_proxy(concatenator_proxy&&) = delete;
concatenator_proxy& operator=(concatenator_proxy&&) = delete;
~concatenator_proxy() = default; ~concatenator_proxy() = default;
void registerProxy()
{
m_proxy.uponSignal("concatenatedSignal").onInterface(INTERFACE_NAME).call([this](const std::string& concatenatedString){ this->onConcatenatedSignal(concatenatedString); });
}
virtual void onConcatenatedSignal(const std::string& concatenatedString) = 0; virtual void onConcatenatedSignal(const std::string& concatenatedString) = 0;
virtual void onConcatenateReply(const std::string& result, const sdbus::Error* error) = 0; virtual void onConcatenateReply(const std::string& result, std::optional<sdbus::Error> error) = 0;
public: public:
sdbus::PendingAsyncCall concatenate(const std::map<std::string, sdbus::Variant>& params) sdbus::PendingAsyncCall concatenate(const std::map<std::string, sdbus::Variant>& params)
{ {
return proxy_.callMethodAsync("concatenate").onInterface(INTERFACE_NAME).withArguments(params).uponReplyInvoke([this](const sdbus::Error* error, const std::string& result){ this->onConcatenateReply(result, error); }); return m_proxy.callMethodAsync("concatenate").onInterface(INTERFACE_NAME).withArguments(params).uponReplyInvoke([this](std::optional<sdbus::Error> error, const std::string& result){ this->onConcatenateReply(result, std::move(error)); });
} }
private: private:
sdbus::IProxy& proxy_; sdbus::IProxy& m_proxy;
}; };
}}} // namespaces }}} // namespaces

View File

@ -22,18 +22,27 @@ public:
protected: protected:
thermometer_adaptor(sdbus::IObject& object) thermometer_adaptor(sdbus::IObject& object)
: object_(object) : m_object(object)
{ {
object_.registerMethod("getCurrentTemperature").onInterface(INTERFACE_NAME).withOutputParamNames("result").implementedAs([this](){ return this->getCurrentTemperature(); });
} }
thermometer_adaptor(const thermometer_adaptor&) = delete;
thermometer_adaptor& operator=(const thermometer_adaptor&) = delete;
thermometer_adaptor(thermometer_adaptor&&) = delete;
thermometer_adaptor& operator=(thermometer_adaptor&&) = delete;
~thermometer_adaptor() = default; ~thermometer_adaptor() = default;
void registerAdaptor()
{
m_object.addVTable(sdbus::registerMethod("getCurrentTemperature").withOutputParamNames("result").implementedAs([this](){ return this->getCurrentTemperature(); })).forInterface(INTERFACE_NAME);
}
private: private:
virtual uint32_t getCurrentTemperature() = 0; virtual uint32_t getCurrentTemperature() = 0;
private: private:
sdbus::IObject& object_; sdbus::IObject& m_object;
}; };
}}}} // namespaces }}}} // namespaces
@ -51,20 +60,30 @@ public:
protected: protected:
factory_adaptor(sdbus::IObject& object) factory_adaptor(sdbus::IObject& object)
: object_(object) : m_object(object)
{ {
object_.registerMethod("createDelegateObject").onInterface(INTERFACE_NAME).withOutputParamNames("delegate").implementedAs([this](sdbus::Result<sdbus::ObjectPath>&& result){ this->createDelegateObject(std::move(result)); });
object_.registerMethod("destroyDelegateObject").onInterface(INTERFACE_NAME).withInputParamNames("delegate").implementedAs([this](sdbus::Result<>&& result, sdbus::ObjectPath delegate){ this->destroyDelegateObject(std::move(result), std::move(delegate)); }).withNoReply();
} }
factory_adaptor(const factory_adaptor&) = delete;
factory_adaptor& operator=(const factory_adaptor&) = delete;
factory_adaptor(factory_adaptor&&) = delete;
factory_adaptor& operator=(factory_adaptor&&) = delete;
~factory_adaptor() = default; ~factory_adaptor() = default;
void registerAdaptor()
{
m_object.addVTable( sdbus::registerMethod("createDelegateObject").withOutputParamNames("delegate").implementedAs([this](sdbus::Result<sdbus::ObjectPath>&& result){ this->createDelegateObject(std::move(result)); })
, sdbus::registerMethod("destroyDelegateObject").withInputParamNames("delegate").implementedAs([this](sdbus::Result<>&& result, sdbus::ObjectPath delegate){ this->destroyDelegateObject(std::move(result), std::move(delegate)); }).withNoReply()
).forInterface(INTERFACE_NAME);
}
private: private:
virtual void createDelegateObject(sdbus::Result<sdbus::ObjectPath>&& result) = 0; virtual void createDelegateObject(sdbus::Result<sdbus::ObjectPath>&& result) = 0;
virtual void destroyDelegateObject(sdbus::Result<>&& result, sdbus::ObjectPath delegate) = 0; virtual void destroyDelegateObject(sdbus::Result<>&& result, sdbus::ObjectPath delegate) = 0;
private: private:
sdbus::IObject& object_; sdbus::IObject& m_object;
}; };
}}}}} // namespaces }}}}} // namespaces

View File

@ -22,22 +22,31 @@ public:
protected: protected:
thermometer_proxy(sdbus::IProxy& proxy) thermometer_proxy(sdbus::IProxy& proxy)
: proxy_(proxy) : m_proxy(proxy)
{ {
} }
thermometer_proxy(const thermometer_proxy&) = delete;
thermometer_proxy& operator=(const thermometer_proxy&) = delete;
thermometer_proxy(thermometer_proxy&&) = delete;
thermometer_proxy& operator=(thermometer_proxy&&) = delete;
~thermometer_proxy() = default; ~thermometer_proxy() = default;
void registerProxy()
{
}
public: public:
uint32_t getCurrentTemperature() uint32_t getCurrentTemperature()
{ {
uint32_t result; uint32_t result;
proxy_.callMethod("getCurrentTemperature").onInterface(INTERFACE_NAME).storeResultsTo(result); m_proxy.callMethod("getCurrentTemperature").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result; return result;
} }
private: private:
sdbus::IProxy& proxy_; sdbus::IProxy& m_proxy;
}; };
}}}} // namespaces }}}} // namespaces
@ -55,27 +64,36 @@ public:
protected: protected:
factory_proxy(sdbus::IProxy& proxy) factory_proxy(sdbus::IProxy& proxy)
: proxy_(proxy) : m_proxy(proxy)
{ {
} }
factory_proxy(const factory_proxy&) = delete;
factory_proxy& operator=(const factory_proxy&) = delete;
factory_proxy(factory_proxy&&) = delete;
factory_proxy& operator=(factory_proxy&&) = delete;
~factory_proxy() = default; ~factory_proxy() = default;
void registerProxy()
{
}
public: public:
sdbus::ObjectPath createDelegateObject() sdbus::ObjectPath createDelegateObject()
{ {
sdbus::ObjectPath result; sdbus::ObjectPath result;
proxy_.callMethod("createDelegateObject").onInterface(INTERFACE_NAME).storeResultsTo(result); m_proxy.callMethod("createDelegateObject").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result; return result;
} }
void destroyDelegateObject(const sdbus::ObjectPath& delegate) void destroyDelegateObject(const sdbus::ObjectPath& delegate)
{ {
proxy_.callMethod("destroyDelegateObject").onInterface(INTERFACE_NAME).withArguments(delegate).dontExpectReply(); m_proxy.callMethod("destroyDelegateObject").onInterface(INTERFACE_NAME).withArguments(delegate).dontExpectReply();
} }
private: private:
sdbus::IProxy& proxy_; sdbus::IProxy& m_proxy;
}; };
}}}}} // namespaces }}}}} // namespaces

View File

@ -12,7 +12,7 @@
<allow send_destination="org.sdbuscpp.stresstests.service1"/> <allow send_destination="org.sdbuscpp.stresstests.service1"/>
<allow send_interface="org.sdbuscpp.stresstests.service1"/> <allow send_interface="org.sdbuscpp.stresstests.service1"/>
</policy> </policy>
<policy context="default"> <policy context="default">
<allow own="org.sdbuscpp.stresstests.service2"/> <allow own="org.sdbuscpp.stresstests.service2"/>
<allow send_destination="org.sdbuscpp.stresstests.service2"/> <allow send_destination="org.sdbuscpp.stresstests.service2"/>

View File

@ -1,5 +1,6 @@
/** /**
* (C) 2019 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file sdbus-c++-stress-tests.cpp * @file sdbus-c++-stress-tests.cpp
* *
@ -45,18 +46,17 @@
#include <queue> #include <queue>
using namespace std::chrono_literals; using namespace std::chrono_literals;
using namespace std::string_literals;
#define SERVICE_1_BUS_NAME "org.sdbuscpp.stresstests.service1"s const sdbus::ServiceName SERVICE_1_BUS_NAME{"org.sdbuscpp.stresstests.service1"};
#define SERVICE_2_BUS_NAME "org.sdbuscpp.stresstests.service2"s const sdbus::ServiceName SERVICE_2_BUS_NAME{"org.sdbuscpp.stresstests.service2"};
#define CELSIUS_THERMOMETER_OBJECT_PATH "/org/sdbuscpp/stresstests/celsius/thermometer"s const sdbus::ObjectPath CELSIUS_THERMOMETER_OBJECT_PATH{"/org/sdbuscpp/stresstests/celsius/thermometer"};
#define FAHRENHEIT_THERMOMETER_OBJECT_PATH "/org/sdbuscpp/stresstests/fahrenheit/thermometer"s const sdbus::ObjectPath FAHRENHEIT_THERMOMETER_OBJECT_PATH{"/org/sdbuscpp/stresstests/fahrenheit/thermometer"};
#define CONCATENATOR_OBJECT_PATH "/org/sdbuscpp/stresstests/concatenator"s const sdbus::ObjectPath CONCATENATOR_OBJECT_PATH{"/org/sdbuscpp/stresstests/concatenator"};
class CelsiusThermometerAdaptor final : public sdbus::AdaptorInterfaces<org::sdbuscpp::stresstests::celsius::thermometer_adaptor> class CelsiusThermometerAdaptor final : public sdbus::AdaptorInterfaces<org::sdbuscpp::stresstests::celsius::thermometer_adaptor>
{ {
public: public:
CelsiusThermometerAdaptor(sdbus::IConnection& connection, std::string objectPath) CelsiusThermometerAdaptor(sdbus::IConnection& connection, sdbus::ObjectPath objectPath)
: AdaptorInterfaces(connection, std::move(objectPath)) : AdaptorInterfaces(connection, std::move(objectPath))
{ {
registerAdaptor(); registerAdaptor();
@ -80,7 +80,7 @@ private:
class CelsiusThermometerProxy : public sdbus::ProxyInterfaces<org::sdbuscpp::stresstests::celsius::thermometer_proxy> class CelsiusThermometerProxy : public sdbus::ProxyInterfaces<org::sdbuscpp::stresstests::celsius::thermometer_proxy>
{ {
public: public:
CelsiusThermometerProxy(sdbus::IConnection& connection, std::string destination, std::string objectPath) CelsiusThermometerProxy(sdbus::IConnection& connection, sdbus::ServiceName destination, sdbus::ObjectPath objectPath)
: ProxyInterfaces(connection, std::move(destination), std::move(objectPath)) : ProxyInterfaces(connection, std::move(destination), std::move(objectPath))
{ {
registerProxy(); registerProxy();
@ -96,7 +96,7 @@ class FahrenheitThermometerAdaptor final : public sdbus::AdaptorInterfaces< org:
, org::sdbuscpp::stresstests::fahrenheit::thermometer::factory_adaptor > , org::sdbuscpp::stresstests::fahrenheit::thermometer::factory_adaptor >
{ {
public: public:
FahrenheitThermometerAdaptor(sdbus::IConnection& connection, std::string objectPath, bool isDelegate) FahrenheitThermometerAdaptor(sdbus::IConnection& connection, sdbus::ObjectPath objectPath, bool isDelegate)
: AdaptorInterfaces(connection, std::move(objectPath)) : AdaptorInterfaces(connection, std::move(objectPath))
, celsiusProxy_(connection, SERVICE_2_BUS_NAME, CELSIUS_THERMOMETER_OBJECT_PATH) , celsiusProxy_(connection, SERVICE_2_BUS_NAME, CELSIUS_THERMOMETER_OBJECT_PATH)
{ {
@ -127,7 +127,7 @@ public:
{ {
// Create new delegate object // Create new delegate object
auto& connection = getObject().getConnection(); auto& connection = getObject().getConnection();
sdbus::ObjectPath newObjectPath = FAHRENHEIT_THERMOMETER_OBJECT_PATH + "/" + std::to_string(request.objectNr); sdbus::ObjectPath newObjectPath{FAHRENHEIT_THERMOMETER_OBJECT_PATH + "/" + std::to_string(request.objectNr)};
// Here we are testing dynamic creation of a D-Bus object in an async way // Here we are testing dynamic creation of a D-Bus object in an async way
auto adaptor = std::make_unique<FahrenheitThermometerAdaptor>(connection, newObjectPath, true); auto adaptor = std::make_unique<FahrenheitThermometerAdaptor>(connection, newObjectPath, true);
@ -175,7 +175,7 @@ protected:
objectCounter++; objectCounter++;
std::unique_lock<std::mutex> lock(mutex_); std::unique_lock<std::mutex> lock(mutex_);
requests_.push(WorkItem{objectCounter, std::string{}, std::move(result)}); requests_.push(WorkItem{objectCounter, {}, std::move(result)});
lock.unlock(); lock.unlock();
cond_.notify_one(); cond_.notify_one();
} }
@ -190,7 +190,7 @@ protected:
private: private:
CelsiusThermometerProxy celsiusProxy_; CelsiusThermometerProxy celsiusProxy_;
std::map<std::string, std::unique_ptr<FahrenheitThermometerAdaptor>> children_; std::map<sdbus::ObjectPath, std::unique_ptr<FahrenheitThermometerAdaptor>> children_;
std::mutex childrenMutex_; std::mutex childrenMutex_;
struct WorkItem struct WorkItem
@ -210,7 +210,7 @@ class FahrenheitThermometerProxy : public sdbus::ProxyInterfaces< org::sdbuscpp:
, org::sdbuscpp::stresstests::fahrenheit::thermometer::factory_proxy > , org::sdbuscpp::stresstests::fahrenheit::thermometer::factory_proxy >
{ {
public: public:
FahrenheitThermometerProxy(sdbus::IConnection& connection, std::string destination, std::string objectPath) FahrenheitThermometerProxy(sdbus::IConnection& connection, sdbus::ServiceName destination, sdbus::ObjectPath objectPath)
: ProxyInterfaces(connection, std::move(destination), std::move(objectPath)) : ProxyInterfaces(connection, std::move(destination), std::move(objectPath))
{ {
registerProxy(); registerProxy();
@ -225,7 +225,7 @@ public:
class ConcatenatorAdaptor final : public sdbus::AdaptorInterfaces<org::sdbuscpp::stresstests::concatenator_adaptor> class ConcatenatorAdaptor final : public sdbus::AdaptorInterfaces<org::sdbuscpp::stresstests::concatenator_adaptor>
{ {
public: public:
ConcatenatorAdaptor(sdbus::IConnection& connection, std::string objectPath) ConcatenatorAdaptor(sdbus::IConnection& connection, sdbus::ObjectPath objectPath)
: AdaptorInterfaces(connection, std::move(objectPath)) : AdaptorInterfaces(connection, std::move(objectPath))
{ {
unsigned int workers = std::thread::hardware_concurrency(); unsigned int workers = std::thread::hardware_concurrency();
@ -297,7 +297,7 @@ private:
class ConcatenatorProxy final : public sdbus::ProxyInterfaces<org::sdbuscpp::stresstests::concatenator_proxy> class ConcatenatorProxy final : public sdbus::ProxyInterfaces<org::sdbuscpp::stresstests::concatenator_proxy>
{ {
public: public:
ConcatenatorProxy(sdbus::IConnection& connection, std::string destination, std::string objectPath) ConcatenatorProxy(sdbus::IConnection& connection, sdbus::ServiceName destination, sdbus::ObjectPath objectPath)
: ProxyInterfaces(connection, std::move(destination), std::move(objectPath)) : ProxyInterfaces(connection, std::move(destination), std::move(objectPath))
{ {
registerProxy(); registerProxy();
@ -309,9 +309,9 @@ public:
} }
private: private:
virtual void onConcatenateReply(const std::string& result, [[maybe_unused]] const sdbus::Error* error) override virtual void onConcatenateReply(const std::string& result, [[maybe_unused]] std::optional<sdbus::Error> error) override
{ {
assert(error == nullptr); assert(error == std::nullopt);
std::stringstream str(result); std::stringstream str(result);
std::string aString; std::string aString;
@ -427,8 +427,8 @@ int main(int argc, char *argv[])
while (!stopClients) while (!stopClients)
{ {
std::map<std::string, sdbus::Variant> param; std::map<std::string, sdbus::Variant> param;
param["key1"] = "sdbus-c++-stress-tests"; param["key1"] = sdbus::Variant{"sdbus-c++-stress-tests"};
param["key2"] = ++localCounter; param["key2"] = sdbus::Variant{++localCounter};
concatenator.concatenate(param); concatenator.concatenate(param);
@ -509,6 +509,6 @@ int main(int argc, char *argv[])
exitLogger = true; exitLogger = true;
loggerThread.join(); loggerThread.join();
return 0; return 0;
} }

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file Connection_test.cpp * @file Connection_test.cpp
* @author Ardazishvili Roman (ardazishvili.roman@yandex.ru) * @author Ardazishvili Roman (ardazishvili.roman@yandex.ru)
@ -26,6 +26,7 @@
*/ */
#include "Connection.h" #include "Connection.h"
#include "sdbus-c++/Types.h"
#include "unittests/mocks/SdBusMock.h" #include "unittests/mocks/SdBusMock.h"
#include <gtest/gtest.h> #include <gtest/gtest.h>
@ -207,12 +208,16 @@ TYPED_TEST_SUITE(AConnectionNameRequest, BusTypeTags);
TYPED_TEST(AConnectionNameRequest, DoesNotThrowOnSuccess) TYPED_TEST(AConnectionNameRequest, DoesNotThrowOnSuccess)
{ {
EXPECT_CALL(*this->sdBusIntfMock_, sd_bus_request_name(_, _, _)).WillOnce(Return(1)); EXPECT_CALL(*this->sdBusIntfMock_, sd_bus_request_name(_, _, _)).WillOnce(Return(1));
this->con_->requestName("org.sdbuscpp.somename"); sdbus::ConnectionName name{"org.sdbuscpp.somename"};
this->con_->requestName(name);
} }
TYPED_TEST(AConnectionNameRequest, ThrowsOnFail) TYPED_TEST(AConnectionNameRequest, ThrowsOnFail)
{ {
sdbus::ConnectionName name{"org.sdbuscpp.somename"};
EXPECT_CALL(*this->sdBusIntfMock_, sd_bus_request_name(_, _, _)).WillOnce(Return(-1)); EXPECT_CALL(*this->sdBusIntfMock_, sd_bus_request_name(_, _, _)).WillOnce(Return(-1));
ASSERT_THROW(this->con_->requestName("org.sdbuscpp.somename"), sdbus::Error); ASSERT_THROW(this->con_->requestName(name), sdbus::Error);
} }

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file Message_test.cpp * @file Message_test.cpp
* *
@ -29,10 +29,13 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <gmock/gmock.h> #include <gmock/gmock.h>
#include <cstdint> #include <cstdint>
#include <list>
using ::testing::Eq; using ::testing::Eq;
using ::testing::StrEq;
using ::testing::Gt; using ::testing::Gt;
using ::testing::DoubleEq; using ::testing::DoubleEq;
using ::testing::IsNull;
using namespace std::string_literals; using namespace std::string_literals;
namespace namespace
@ -45,6 +48,82 @@ namespace
} }
} }
namespace sdbus {
template <typename _ElementType>
sdbus::Message& operator<<(sdbus::Message& msg, const std::list<_ElementType>& items)
{
msg.openContainer<_ElementType>();
for (const auto& item : items)
msg << item;
msg.closeContainer();
return msg;
}
template <typename _ElementType>
sdbus::Message& operator>>(sdbus::Message& msg, std::list<_ElementType>& items)
{
if(!msg.enterContainer<_ElementType>())
return msg;
while (true)
{
_ElementType elem;
if (msg >> elem)
items.emplace_back(std::move(elem));
else
break;
}
msg.clearFlags();
msg.exitContainer();
return msg;
}
}
template <typename _Element, typename _Allocator>
struct sdbus::signature_of<std::list<_Element, _Allocator>>
: sdbus::signature_of<std::vector<_Element, _Allocator>>
{};
namespace my {
struct Struct
{
int i;
std::string s;
std::list<double> l;
};
bool operator==(const Struct& lhs, const Struct& rhs)
{
return lhs.i == rhs.i && lhs.s == rhs.s && lhs.l == rhs.l;
}
sdbus::Message& operator<<(sdbus::Message& msg, const Struct& items)
{
return msg << sdbus::Struct{std::forward_as_tuple(items.i, items.s, items.l)};
}
sdbus::Message& operator>>(sdbus::Message& msg, Struct& items)
{
sdbus::Struct s{std::forward_as_tuple(items.i, items.s, items.l)};
return msg >> s;
}
}
template <>
struct sdbus::signature_of<my::Struct>
: sdbus::signature_of<sdbus::Struct<int, std::string, std::list<double>>>
{};
/*-------------------------------------*/ /*-------------------------------------*/
/* -- TEST CASES -- */ /* -- TEST CASES -- */
/*-------------------------------------*/ /*-------------------------------------*/
@ -116,7 +195,7 @@ TEST(AMessage, CanCarryASimpleInteger)
{ {
auto msg = sdbus::createPlainMessage(); auto msg = sdbus::createPlainMessage();
int dataWritten = 5; const int dataWritten = 5;
msg << dataWritten; msg << dataWritten;
msg.seal(); msg.seal();
@ -127,11 +206,26 @@ TEST(AMessage, CanCarryASimpleInteger)
ASSERT_THAT(dataRead, Eq(dataWritten)); ASSERT_THAT(dataRead, Eq(dataWritten));
} }
TEST(AMessage, CanCarryAStringAsAStringView)
{
auto msg = sdbus::createPlainMessage();
const std::string_view dataWritten = "Hello";
msg << dataWritten;
msg.seal();
std::string dataRead;
msg >> dataRead;
ASSERT_THAT(dataRead, Eq(dataWritten));
}
TEST(AMessage, CanCarryAUnixFd) TEST(AMessage, CanCarryAUnixFd)
{ {
auto msg = sdbus::createPlainMessage(); auto msg = sdbus::createPlainMessage();
sdbus::UnixFd dataWritten{0}; const sdbus::UnixFd dataWritten{0};
msg << dataWritten; msg << dataWritten;
msg.seal(); msg.seal();
@ -146,7 +240,7 @@ TEST(AMessage, CanCarryAVariant)
{ {
auto msg = sdbus::createPlainMessage(); auto msg = sdbus::createPlainMessage();
auto dataWritten = sdbus::Variant((double)3.14); const auto dataWritten = sdbus::Variant((double)3.14);
msg << dataWritten; msg << dataWritten;
msg.seal(); msg.seal();
@ -161,8 +255,8 @@ TEST(AMessage, CanCarryACollectionOfEmbeddedVariants)
{ {
auto msg = sdbus::createPlainMessage(); auto msg = sdbus::createPlainMessage();
auto value = std::vector<sdbus::Variant>{"hello"s, (double)3.14}; std::vector<sdbus::Variant> value{sdbus::Variant{"hello"s}, sdbus::Variant{(double)3.14}};
auto dataWritten = sdbus::Variant{value}; const auto dataWritten = sdbus::Variant{value};
msg << dataWritten; msg << dataWritten;
msg.seal(); msg.seal();
@ -174,11 +268,11 @@ TEST(AMessage, CanCarryACollectionOfEmbeddedVariants)
ASSERT_THAT(dataRead.get<decltype(value)>()[1].get<double>(), Eq(value[1].get<double>())); ASSERT_THAT(dataRead.get<decltype(value)>()[1].get<double>(), Eq(value[1].get<double>()));
} }
TEST(AMessage, CanCarryAnArray) TEST(AMessage, CanCarryDBusArrayOfTrivialTypesGivenAsStdVector)
{ {
auto msg = sdbus::createPlainMessage(); auto msg = sdbus::createPlainMessage();
std::vector<int64_t> dataWritten{3545342, 43643532, 324325}; const std::vector<int64_t> dataWritten{3545342, 43643532, 324325};
msg << dataWritten; msg << dataWritten;
msg.seal(); msg.seal();
@ -189,6 +283,134 @@ TEST(AMessage, CanCarryAnArray)
ASSERT_THAT(dataRead, Eq(dataWritten)); ASSERT_THAT(dataRead, Eq(dataWritten));
} }
TEST(AMessage, CanCarryDBusArrayOfNontrivialTypesGivenAsStdVector)
{
auto msg = sdbus::createPlainMessage();
const std::vector dataWritten{sdbus::Signature{"s"}, sdbus::Signature{"u"}, sdbus::Signature{"b"}};
msg << dataWritten;
msg.seal();
std::vector<sdbus::Signature> dataRead;
msg >> dataRead;
ASSERT_THAT(dataRead, Eq(dataWritten));
}
TEST(AMessage, CanCarryDBusArrayOfTrivialTypesGivenAsStdArray)
{
auto msg = sdbus::createPlainMessage();
const std::array<int, 3> dataWritten{3545342, 43643532, 324325};
msg << dataWritten;
msg.seal();
std::array<int, 3> dataRead;
msg >> dataRead;
ASSERT_THAT(dataRead, Eq(dataWritten));
}
TEST(AMessage, CanCarryDBusArrayOfNontrivialTypesGivenAsStdArray)
{
auto msg = sdbus::createPlainMessage();
const std::array dataWritten{sdbus::Signature{"s"}, sdbus::Signature{"u"}, sdbus::Signature{"b"}};
msg << dataWritten;
msg.seal();
std::array<sdbus::Signature, 3> dataRead;
msg >> dataRead;
ASSERT_THAT(dataRead, Eq(dataWritten));
}
#ifdef __cpp_lib_span
TEST(AMessage, CanCarryDBusArrayOfTrivialTypesGivenAsStdSpan)
{
auto msg = sdbus::createPlainMessage();
const std::array<int, 3> sourceArray{3545342, 43643532, 324325};
const std::span dataWritten{sourceArray};
msg << dataWritten;
msg.seal();
std::array<int, 3> destinationArray;
std::span dataRead{destinationArray};
msg >> dataRead;
ASSERT_THAT(std::vector(dataRead.begin(), dataRead.end()), Eq(std::vector(dataWritten.begin(), dataWritten.end())));
}
TEST(AMessage, CanCarryDBusArrayOfNontrivialTypesGivenAsStdSpan)
{
auto msg = sdbus::createPlainMessage();
const std::array sourceArray{sdbus::Signature{"s"}, sdbus::Signature{"u"}, sdbus::Signature{"b"}};
const std::span dataWritten{sourceArray};
msg << dataWritten;
msg.seal();
std::array<sdbus::Signature, 3> destinationArray;
std::span dataRead{destinationArray};
msg >> dataRead;
ASSERT_THAT(std::vector(dataRead.begin(), dataRead.end()), Eq(std::vector(dataWritten.begin(), dataWritten.end())));
}
#endif
TEST(AMessage, CanCarryAnEnumValue)
{
auto msg = sdbus::createPlainMessage();
enum class EnumA : int16_t {X = 5} aWritten{EnumA::X};
enum EnumB {Y = 11} bWritten{EnumB::Y};
msg << aWritten << bWritten;
msg.seal();
EnumA aRead{};
EnumB bRead{};
msg >> aRead >> bRead;
ASSERT_THAT(aRead, Eq(aWritten));
ASSERT_THAT(bRead, Eq(bWritten));
}
TEST(AMessage, ThrowsWhenDestinationStdArrayIsTooSmallDuringDeserialization)
{
auto msg = sdbus::createPlainMessage();
const std::vector<int> dataWritten{3545342, 43643532, 324325, 89789, 15343};
msg << dataWritten;
msg.seal();
std::array<int, 3> dataRead;
ASSERT_THROW(msg >> dataRead, sdbus::Error);
}
#ifdef __cpp_lib_span
TEST(AMessage, ThrowsWhenDestinationStdSpanIsTooSmallDuringDeserialization)
{
auto msg = sdbus::createPlainMessage();
const std::array<int, 3> dataWritten{3545342, 43643532, 324325};
msg << dataWritten;
msg.seal();
std::array<int, 2> destinationArray;
std::span dataRead{destinationArray};
ASSERT_THROW(msg >> dataRead, sdbus::Error);
}
#endif
TEST(AMessage, CanCarryADictionary) TEST(AMessage, CanCarryADictionary)
{ {
auto msg = sdbus::createPlainMessage(); auto msg = sdbus::createPlainMessage();
@ -228,7 +450,7 @@ TEST(AMessage, CanCarryAComplexType)
> >
>; >;
ComplexType dataWritten = { {1, {{{5, {{"/some/object", true, 45, {{6, "hello"}, {7, "world"}}}}}}, "av", 3.14}}}; ComplexType dataWritten = { {1, {{{5, {{sdbus::ObjectPath{"/some/object"}, true, 45, {{6, "hello"}, {7, "world"}}}}}}, sdbus::Signature{"av"}, 3.14}}};
msg << dataWritten; msg << dataWritten;
msg.seal(); msg.seal();
@ -245,11 +467,10 @@ TEST(AMessage, CanPeekASimpleType)
msg << 123; msg << 123;
msg.seal(); msg.seal();
std::string type; auto [type, contents] = msg.peekType();
std::string contents;
msg.peekType(type, contents); ASSERT_THAT(type, Eq('i'));
ASSERT_THAT(type, "i"); ASSERT_THAT(contents, IsNull());
ASSERT_THAT(contents, "");
} }
TEST(AMessage, CanPeekContainerContents) TEST(AMessage, CanPeekContainerContents)
@ -258,9 +479,76 @@ TEST(AMessage, CanPeekContainerContents)
msg << std::map<int, std::string>{{1, "one"}, {2, "two"}}; msg << std::map<int, std::string>{{1, "one"}, {2, "two"}};
msg.seal(); msg.seal();
std::string type; auto [type, contents] = msg.peekType();
std::string contents;
msg.peekType(type, contents); ASSERT_THAT(type, Eq('a'));
ASSERT_THAT(type, "a"); ASSERT_THAT(contents, StrEq("{is}"));
ASSERT_THAT(contents, "{is}");
} }
TEST(AMessage, CanCarryDBusArrayGivenAsCustomType)
{
auto msg = sdbus::createPlainMessage();
const std::list<int64_t> dataWritten{3545342, 43643532, 324325};
//custom::MyType t;
msg << dataWritten;
// msg << t;
msg.seal();
std::list<int64_t> dataRead;
msg >> dataRead;
ASSERT_THAT(dataRead, Eq(dataWritten));
}
TEST(AMessage, CanCarryDBusStructGivenAsCustomType)
{
auto msg = sdbus::createPlainMessage();
const my::Struct dataWritten{3545342, "hello"s, {3.14, 2.4568546}};
msg << dataWritten;
msg.seal();
my::Struct dataRead;
msg >> dataRead;
ASSERT_THAT(dataRead, Eq(dataWritten));
}
class AMessage : public ::testing::TestWithParam<std::variant<int32_t, std::string, my::Struct>>
{
};
TEST_P(AMessage, CanCarryDBusVariantGivenAsStdVariant)
{
auto msg = sdbus::createPlainMessage();
const std::variant<int32_t, std::string, my::Struct> dataWritten{GetParam()};
msg << dataWritten;
msg.seal();
std::variant<int32_t, std::string, my::Struct> dataRead;
msg >> dataRead;
ASSERT_THAT(dataRead, Eq(dataWritten));
}
TEST_P(AMessage, ThrowsWhenDestinationStdVariantHasWrongTypeDuringDeserialization)
{
auto msg = sdbus::createPlainMessage();
const std::variant<int32_t, std::string, my::Struct> dataWritten{GetParam()};
msg << dataWritten;
msg.seal();
std::variant<std::vector<bool>> dataRead;
ASSERT_THROW(msg >> dataRead, sdbus::Error);
}
INSTANTIATE_TEST_SUITE_P( StringIntStruct
, AMessage
, ::testing::Values("hello"s, 1, my::Struct{}));

View File

@ -0,0 +1,125 @@
/**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file PollData_test.cpp
*
* Created on: Jan 19, 2023
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include <sdbus-c++/IConnection.h>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <chrono>
using ::testing::Eq;
using ::testing::Ge;
using ::testing::Le;
using ::testing::AllOf;
using namespace std::string_literals;
using namespace std::chrono_literals;
/*-------------------------------------*/
/* -- TEST CASES -- */
/*-------------------------------------*/
TEST(PollData, ReturnsZeroRelativeTimeoutForZeroAbsoluteTimeout)
{
sdbus::IConnection::PollData pd;
pd.timeout = std::chrono::microseconds::zero();
auto relativeTimeout = pd.getRelativeTimeout();
EXPECT_THAT(relativeTimeout, Eq(std::chrono::microseconds::zero()));
}
TEST(PollData, ReturnsZeroPollTimeoutForZeroAbsoluteTimeout)
{
sdbus::IConnection::PollData pd;
pd.timeout = std::chrono::microseconds::zero();
auto pollTimeout = pd.getPollTimeout();
EXPECT_THAT(pollTimeout, Eq(0));
}
TEST(PollData, ReturnsInfiniteRelativeTimeoutForInfiniteAbsoluteTimeout)
{
sdbus::IConnection::PollData pd;
pd.timeout = std::chrono::microseconds::max();
auto relativeTimeout = pd.getRelativeTimeout();
EXPECT_THAT(relativeTimeout, Eq(std::chrono::microseconds::max()));
}
TEST(PollData, ReturnsNegativePollTimeoutForInfiniteAbsoluteTimeout)
{
sdbus::IConnection::PollData pd;
pd.timeout = std::chrono::microseconds::max();
auto pollTimeout = pd.getPollTimeout();
EXPECT_THAT(pollTimeout, Eq(-1));
}
TEST(PollData, ReturnsZeroRelativeTimeoutForPastAbsoluteTimeout)
{
sdbus::IConnection::PollData pd;
auto past = std::chrono::steady_clock::now() - 10s;
pd.timeout = std::chrono::duration_cast<std::chrono::microseconds>(past.time_since_epoch());
auto relativeTimeout = pd.getRelativeTimeout();
EXPECT_THAT(relativeTimeout, Eq(0us));
}
TEST(PollData, ReturnsZeroPollTimeoutForPastAbsoluteTimeout)
{
sdbus::IConnection::PollData pd;
auto past = std::chrono::steady_clock::now() - 10s;
pd.timeout = std::chrono::duration_cast<std::chrono::microseconds>(past.time_since_epoch());
auto pollTimeout = pd.getPollTimeout();
EXPECT_THAT(pollTimeout, Eq(0));
}
TEST(PollData, ReturnsCorrectRelativeTimeoutForFutureAbsoluteTimeout)
{
sdbus::IConnection::PollData pd;
auto future = std::chrono::steady_clock::now() + 1s;
pd.timeout = std::chrono::duration_cast<std::chrono::microseconds>(future.time_since_epoch());
auto relativeTimeout = pd.getRelativeTimeout();
EXPECT_THAT(relativeTimeout, AllOf(Ge(900ms), Le(1100ms)));
}
TEST(PollData, ReturnsCorrectPollTimeoutForFutureAbsoluteTimeout)
{
sdbus::IConnection::PollData pd;
auto future = std::chrono::steady_clock::now() + 1s;
pd.timeout = std::chrono::duration_cast<std::chrono::microseconds>(future.time_since_epoch());
auto pollTimeout = pd.getPollTimeout();
EXPECT_THAT(pollTimeout, AllOf(Ge(900), Le(1100)));
}

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file TypeTraits_test.cpp * @file TypeTraits_test.cpp
* *
@ -49,6 +49,21 @@ namespace
static std::string getDBusTypeSignature(); static std::string getDBusTypeSignature();
}; };
enum class SomeEnumClass : uint8_t
{
A, B, C
};
enum struct SomeEnumStruct : int64_t
{
A, B, C
};
enum SomeClassicEnum
{
A, B, C
};
#define TYPE(...) \ #define TYPE(...) \
template <> \ template <> \
std::string Type2DBusTypeSignatureConversion<__VA_ARGS__>::getDBusTypeSignature() \ std::string Type2DBusTypeSignatureConversion<__VA_ARGS__>::getDBusTypeSignature() \
@ -70,14 +85,27 @@ namespace
TYPE(double)HAS_DBUS_TYPE_SIGNATURE("d") TYPE(double)HAS_DBUS_TYPE_SIGNATURE("d")
TYPE(const char*)HAS_DBUS_TYPE_SIGNATURE("s") TYPE(const char*)HAS_DBUS_TYPE_SIGNATURE("s")
TYPE(std::string)HAS_DBUS_TYPE_SIGNATURE("s") TYPE(std::string)HAS_DBUS_TYPE_SIGNATURE("s")
TYPE(std::string_view)HAS_DBUS_TYPE_SIGNATURE("s")
TYPE(sdbus::BusName)HAS_DBUS_TYPE_SIGNATURE("s")
TYPE(sdbus::InterfaceName)HAS_DBUS_TYPE_SIGNATURE("s")
TYPE(sdbus::MemberName)HAS_DBUS_TYPE_SIGNATURE("s")
TYPE(sdbus::ObjectPath)HAS_DBUS_TYPE_SIGNATURE("o") TYPE(sdbus::ObjectPath)HAS_DBUS_TYPE_SIGNATURE("o")
TYPE(sdbus::Signature)HAS_DBUS_TYPE_SIGNATURE("g") TYPE(sdbus::Signature)HAS_DBUS_TYPE_SIGNATURE("g")
TYPE(sdbus::Variant)HAS_DBUS_TYPE_SIGNATURE("v") TYPE(sdbus::Variant)HAS_DBUS_TYPE_SIGNATURE("v")
TYPE(std::variant<int16_t, std::string>)HAS_DBUS_TYPE_SIGNATURE("v")
TYPE(sdbus::UnixFd)HAS_DBUS_TYPE_SIGNATURE("h") TYPE(sdbus::UnixFd)HAS_DBUS_TYPE_SIGNATURE("h")
TYPE(sdbus::Struct<bool>)HAS_DBUS_TYPE_SIGNATURE("(b)") TYPE(sdbus::Struct<bool>)HAS_DBUS_TYPE_SIGNATURE("(b)")
TYPE(sdbus::Struct<uint16_t, double, std::string, sdbus::Variant>)HAS_DBUS_TYPE_SIGNATURE("(qdsv)") TYPE(sdbus::Struct<uint16_t, double, std::string, sdbus::Variant>)HAS_DBUS_TYPE_SIGNATURE("(qdsv)")
TYPE(std::vector<int16_t>)HAS_DBUS_TYPE_SIGNATURE("an") TYPE(std::vector<int16_t>)HAS_DBUS_TYPE_SIGNATURE("an")
TYPE(std::array<int16_t, 3>)HAS_DBUS_TYPE_SIGNATURE("an")
#ifdef __cpp_lib_span
TYPE(std::span<int16_t>)HAS_DBUS_TYPE_SIGNATURE("an")
#endif
TYPE(SomeEnumClass)HAS_DBUS_TYPE_SIGNATURE("y")
TYPE(SomeEnumStruct)HAS_DBUS_TYPE_SIGNATURE("x")
TYPE(SomeClassicEnum)HAS_DBUS_TYPE_SIGNATURE("u")
TYPE(std::map<int32_t, int64_t>)HAS_DBUS_TYPE_SIGNATURE("a{ix}") TYPE(std::map<int32_t, int64_t>)HAS_DBUS_TYPE_SIGNATURE("a{ix}")
TYPE(std::unordered_map<int32_t, int64_t>)HAS_DBUS_TYPE_SIGNATURE("a{ix}")
using ComplexType = std::map< using ComplexType = std::map<
uint64_t, uint64_t,
sdbus::Struct< sdbus::Struct<
@ -86,18 +114,20 @@ namespace
std::vector< std::vector<
sdbus::Struct< sdbus::Struct<
sdbus::ObjectPath, sdbus::ObjectPath,
std::array<int16_t, 3>,
bool, bool,
sdbus::Variant, sdbus::Variant,
std::map<int, std::string> std::unordered_map<int, std::string>
> >
> >
>, >,
sdbus::Signature, sdbus::Signature,
sdbus::UnixFd, sdbus::UnixFd,
const char* const char*,
std::string_view
> >
>; >;
TYPE(ComplexType)HAS_DBUS_TYPE_SIGNATURE("a{t(a{ya(obva{is})}ghs)}") TYPE(ComplexType)HAS_DBUS_TYPE_SIGNATURE("a{t(a{ya(oanbva{is})}ghss)}")
typedef ::testing::Types< bool typedef ::testing::Types< bool
, uint8_t , uint8_t
@ -110,14 +140,27 @@ namespace
, double , double
, const char* , const char*
, std::string , std::string
, std::string_view
, sdbus::BusName
, sdbus::InterfaceName
, sdbus::MemberName
, sdbus::ObjectPath , sdbus::ObjectPath
, sdbus::Signature , sdbus::Signature
, sdbus::Variant , sdbus::Variant
, std::variant<int16_t, std::string>
, sdbus::UnixFd , sdbus::UnixFd
, sdbus::Struct<bool> , sdbus::Struct<bool>
, sdbus::Struct<uint16_t, double, std::string, sdbus::Variant> , sdbus::Struct<uint16_t, double, std::string, sdbus::Variant>
, std::vector<int16_t> , std::vector<int16_t>
, std::array<int16_t, 3>
#ifdef __cpp_lib_span
, std::span<int16_t>
#endif
, SomeEnumClass
, SomeEnumStruct
, SomeClassicEnum
, std::map<int32_t, int64_t> , std::map<int32_t, int64_t>
, std::unordered_map<int32_t, int64_t>
, ComplexType , ComplexType
> DBusSupportedTypes; > DBusSupportedTypes;
@ -130,7 +173,8 @@ namespace
TYPED_TEST(Type2DBusTypeSignatureConversion, ConvertsTypeToProperDBusSignature) TYPED_TEST(Type2DBusTypeSignatureConversion, ConvertsTypeToProperDBusSignature)
{ {
ASSERT_THAT(sdbus::signature_of<TypeParam>::str(), Eq(this->dbusTypeSignature_)); constexpr auto signature = sdbus::as_null_terminated(sdbus::signature_of_v<TypeParam>);
ASSERT_THAT(signature.data(), Eq(this->dbusTypeSignature_));
} }
TEST(FreeFunctionTypeTraits, DetectsTraitsOfTrivialSignatureFunction) TEST(FreeFunctionTypeTraits, DetectsTraitsOfTrivialSignatureFunction)

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file Types_test.cpp * @file Types_test.cpp
* *
@ -67,11 +67,24 @@ TEST(AVariant, CanBeConstructedFromASimpleValue)
TEST(AVariant, CanBeConstructedFromAComplexValue) TEST(AVariant, CanBeConstructedFromAComplexValue)
{ {
using ComplexType = std::map<uint64_t, std::vector<sdbus::Struct<std::string, double>>>; using ComplexType = std::map<uint64_t, std::vector<sdbus::Struct<std::string, double>>>;
ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{sdbus::make_struct("hello"s, ANY_DOUBLE), sdbus::make_struct("world"s, ANY_DOUBLE)}} }; ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{{"hello"s, ANY_DOUBLE}, {"world"s, ANY_DOUBLE}}} };
ASSERT_NO_THROW(sdbus::Variant{value}); ASSERT_NO_THROW(sdbus::Variant{value});
} }
TEST(AVariant, CanBeConstructedFromAnStdVariant)
{
using ComplexType = std::vector<sdbus::Struct<std::string, double>>;
using StdVariantType = std::variant<std::string, uint64_t, ComplexType>;
ComplexType value{{"hello"s, ANY_DOUBLE}, {"world"s, ANY_DOUBLE}};
StdVariantType stdVariant{value};
sdbus::Variant sdbusVariant{stdVariant};
ASSERT_TRUE(sdbusVariant.containsValueOfType<ComplexType>());
ASSERT_THAT(sdbusVariant.get<ComplexType>(), Eq(value));
}
TEST(AVariant, CanBeCopied) TEST(AVariant, CanBeCopied)
{ {
auto value = "hello"s; auto value = "hello"s;
@ -103,7 +116,7 @@ TEST(ASimpleVariant, ReturnsTheSimpleValueWhenAsked)
TEST(AComplexVariant, ReturnsTheComplexValueWhenAsked) TEST(AComplexVariant, ReturnsTheComplexValueWhenAsked)
{ {
using ComplexType = std::map<uint64_t, std::vector<sdbus::Struct<std::string, double>>>; using ComplexType = std::map<uint64_t, std::vector<sdbus::Struct<std::string, double>>>;
ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{sdbus::make_struct("hello"s, ANY_DOUBLE), sdbus::make_struct("world"s, ANY_DOUBLE)}} }; ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{{"hello"s, ANY_DOUBLE}, {"world"s, ANY_DOUBLE}}} };
sdbus::Variant variant(value); sdbus::Variant variant(value);
@ -123,13 +136,37 @@ TEST(AVariant, HasConceptuallyNonmutableGetMethodWhichCanBeCalledXTimes)
TEST(AVariant, ReturnsTrueWhenAskedIfItContainsTheTypeItReallyContains) TEST(AVariant, ReturnsTrueWhenAskedIfItContainsTheTypeItReallyContains)
{ {
using ComplexType = std::map<uint64_t, std::vector<sdbus::Struct<std::string, double>>>; using ComplexType = std::map<uint64_t, std::vector<sdbus::Struct<std::string, double>>>;
ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{sdbus::make_struct("hello"s, ANY_DOUBLE), sdbus::make_struct("world"s, ANY_DOUBLE)}} }; ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{{"hello"s, ANY_DOUBLE}, {"world"s, ANY_DOUBLE}}} };
sdbus::Variant variant(value); sdbus::Variant variant(value);
ASSERT_TRUE(variant.containsValueOfType<ComplexType>()); ASSERT_TRUE(variant.containsValueOfType<ComplexType>());
} }
TEST(AVariant, CanBeConvertedIntoAnStdVariant)
{
using ComplexType = std::vector<sdbus::Struct<std::string, double>>;
using StdVariantType = std::variant<std::string, uint64_t, ComplexType>;
ComplexType value{{"hello"s, ANY_DOUBLE}, {"world"s, ANY_DOUBLE}};
sdbus::Variant sdbusVariant{value};
StdVariantType stdVariant{sdbusVariant};
ASSERT_TRUE(std::holds_alternative<ComplexType>(stdVariant));
ASSERT_THAT(std::get<ComplexType>(stdVariant), Eq(value));
}
TEST(AVariant, IsImplicitlyInterchangeableWithStdVariant)
{
using ComplexType = std::vector<sdbus::Struct<std::string, double>>;
using StdVariantType = std::variant<std::string, uint64_t, ComplexType>;
ComplexType value{{"hello"s, ANY_DOUBLE}, {"world"s, ANY_DOUBLE}};
StdVariantType stdVariant{value};
auto stdVariantCopy = [](const sdbus::Variant &v) -> StdVariantType { return v; }(stdVariant);
ASSERT_THAT(stdVariantCopy, Eq(stdVariant));
}
TEST(ASimpleVariant, ReturnsFalseWhenAskedIfItContainsTypeItDoesntReallyContain) TEST(ASimpleVariant, ReturnsFalseWhenAskedIfItContainsTypeItDoesntReallyContain)
{ {
int value = 5; int value = 5;
@ -143,8 +180,8 @@ TEST(AVariant, CanContainOtherEmbeddedVariants)
{ {
using TypeWithVariants = std::vector<sdbus::Struct<sdbus::Variant, double>>; using TypeWithVariants = std::vector<sdbus::Struct<sdbus::Variant, double>>;
TypeWithVariants value; TypeWithVariants value;
value.emplace_back(sdbus::make_struct(sdbus::Variant("a string"), ANY_DOUBLE)); value.push_back({sdbus::Variant("a string"), ANY_DOUBLE});
value.emplace_back(sdbus::make_struct(sdbus::Variant(ANY_UINT64), ANY_DOUBLE)); value.push_back({sdbus::Variant(ANY_UINT64), ANY_DOUBLE});
sdbus::Variant variant(value); sdbus::Variant variant(value);
@ -172,7 +209,7 @@ TEST(AnEmptyVariant, ThrowsWhenBeingSerializedToAMessage)
TEST(ANonEmptyVariant, SerializesToAndDeserializesFromAMessageSuccessfully) TEST(ANonEmptyVariant, SerializesToAndDeserializesFromAMessageSuccessfully)
{ {
using ComplexType = std::map<uint64_t, std::vector<sdbus::Struct<std::string, double>>>; using ComplexType = std::map<uint64_t, std::vector<sdbus::Struct<std::string, double>>>;
ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{sdbus::make_struct("hello"s, ANY_DOUBLE), sdbus::make_struct("world"s, ANY_DOUBLE)}} }; ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{{"hello"s, ANY_DOUBLE}, {"world"s, ANY_DOUBLE}}} };
sdbus::Variant variant(value); sdbus::Variant variant(value);
auto msg = sdbus::createPlainMessage(); auto msg = sdbus::createPlainMessage();
@ -187,7 +224,7 @@ TEST(ANonEmptyVariant, SerializesToAndDeserializesFromAMessageSuccessfully)
TEST(CopiesOfVariant, SerializeToAndDeserializeFromMessageSuccessfully) TEST(CopiesOfVariant, SerializeToAndDeserializeFromMessageSuccessfully)
{ {
using ComplexType = std::map<uint64_t, std::vector<sdbus::Struct<std::string, double>>>; using ComplexType = std::map<uint64_t, std::vector<sdbus::Struct<std::string, double>>>;
ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{sdbus::make_struct("hello"s, ANY_DOUBLE), sdbus::make_struct("world"s, ANY_DOUBLE)}} }; ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{{"hello"s, ANY_DOUBLE}, {"world"s, ANY_DOUBLE}}} };
sdbus::Variant variant(value); sdbus::Variant variant(value);
auto variantCopy1{variant}; auto variantCopy1{variant};
auto variantCopy2 = variant; auto variantCopy2 = variant;
@ -207,15 +244,41 @@ TEST(CopiesOfVariant, SerializeToAndDeserializeFromMessageSuccessfully)
ASSERT_THAT(receivedVariant3.get<decltype(value)>(), Eq(value)); ASSERT_THAT(receivedVariant3.get<decltype(value)>(), Eq(value));
} }
TEST(AStruct, CreatesStructFromTuple) TEST(AStruct, CanBeCreatedFromStdTuple)
{ {
std::tuple<int32_t, std::string> value{1234, "abcd"}; std::tuple<int32_t, std::string> value{1234, "abcd"};
sdbus::Struct<int32_t, std::string> valueStruct{value}; sdbus::Struct valueStruct{value};
ASSERT_THAT(valueStruct.get<0>(), Eq(std::get<0>(value)));
ASSERT_THAT(valueStruct.get<1>(), Eq(std::get<1>(value)));
}
TEST(AStruct, CanProvideItsDataThroughStdGet)
{
std::tuple<int32_t, std::string> value{1234, "abcd"};
sdbus::Struct valueStruct{value};
ASSERT_THAT(std::get<0>(valueStruct), Eq(std::get<0>(value))); ASSERT_THAT(std::get<0>(valueStruct), Eq(std::get<0>(value)));
ASSERT_THAT(std::get<1>(valueStruct), Eq(std::get<1>(value))); ASSERT_THAT(std::get<1>(valueStruct), Eq(std::get<1>(value)));
} }
TEST(AStruct, CanBeUsedLikeStdTupleType)
{
using StructType = sdbus::Struct<int, std::string, bool>;
static_assert(std::tuple_size_v<StructType> == 3);
static_assert(std::is_same_v<std::tuple_element_t<1, StructType>, std::string>);
}
TEST(AStruct, CanBeUsedInStructuredBinding)
{
sdbus::Struct valueStruct(1234, "abcd", true);
auto [first, second, third] = valueStruct;
ASSERT_THAT(std::tie(first, second, third), Eq(std::tuple{1234, "abcd", true}));
}
TEST(AnObjectPath, CanBeConstructedFromCString) TEST(AnObjectPath, CanBeConstructedFromCString)
{ {
const char* aPath = "/some/path"; const char* aPath = "/some/path";
@ -259,7 +322,6 @@ TEST(ASignature, CanBeMovedLikeAStdString)
sdbus::Signature oSignature{aSignature}; sdbus::Signature oSignature{aSignature};
ASSERT_THAT(sdbus::Signature{std::move(oSignature)}, Eq(sdbus::Signature(std::move(aSignature)))); ASSERT_THAT(sdbus::Signature{std::move(oSignature)}, Eq(sdbus::Signature(std::move(aSignature))));
ASSERT_THAT(std::string(oSignature), Eq(aSignature));
} }
TEST(AUnixFd, DuplicatesAndOwnsFdUponStandardConstruction) TEST(AUnixFd, DuplicatesAndOwnsFdUponStandardConstruction)
@ -365,17 +427,17 @@ TEST(AUnixFd, TakesOverNewFdAndClosesOriginalFdOnAdoptingReset)
TEST(AnError, CanBeConstructedFromANameAndAMessage) TEST(AnError, CanBeConstructedFromANameAndAMessage)
{ {
auto error = sdbus::Error("name", "message"); auto error = sdbus::Error(sdbus::Error::Name{"org.sdbuscpp.error"}, "message");
EXPECT_THAT(error.getName(), Eq<std::string>("name")); EXPECT_THAT(error.getName(), Eq<std::string>("org.sdbuscpp.error"));
EXPECT_THAT(error.getMessage(), Eq<std::string>("message")); EXPECT_THAT(error.getMessage(), Eq<std::string>("message"));
} }
TEST(AnError, CanBeConstructedFromANameOnly) TEST(AnError, CanBeConstructedFromANameOnly)
{ {
auto error1 = sdbus::Error("name"); auto error1 = sdbus::Error(sdbus::Error::Name{"org.sdbuscpp.error"});
auto error2 = sdbus::Error("name", nullptr); auto error2 = sdbus::Error(sdbus::Error::Name{"org.sdbuscpp.error"}, nullptr);
EXPECT_THAT(error1.getName(), Eq<std::string>("name")); EXPECT_THAT(error1.getName(), Eq<std::string>("org.sdbuscpp.error"));
EXPECT_THAT(error2.getName(), Eq<std::string>("name")); EXPECT_THAT(error2.getName(), Eq<std::string>("org.sdbuscpp.error"));
EXPECT_THAT(error1.getMessage(), Eq<std::string>("")); EXPECT_THAT(error1.getMessage(), Eq<std::string>(""));
EXPECT_THAT(error2.getMessage(), Eq<std::string>("")); EXPECT_THAT(error2.getMessage(), Eq<std::string>(""));

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file SdBusMock.h * @file SdBusMock.h
* @author Ardazishvili Roman (ardazishvili.roman@yandex.ru) * @author Ardazishvili Roman (ardazishvili.roman@yandex.ru)
@ -62,20 +62,26 @@ public:
MOCK_METHOD1(sd_bus_open_user, int(sd_bus **ret)); 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_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_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_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_release_name, int(sd_bus *bus, const char *name));
MOCK_METHOD2(sd_bus_get_unique_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_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_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_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_METHOD8(sd_bus_match_signal, int(sd_bus *bus, sd_bus_slot **ret, const char *sender, const char *path, const char *interface, const char *member, sd_bus_message_handler_t callback, void *userdata));
MOCK_METHOD1(sd_bus_slot_unref, sd_bus_slot*(sd_bus_slot *slot)); MOCK_METHOD1(sd_bus_slot_unref, sd_bus_slot*(sd_bus_slot *slot));
MOCK_METHOD1(sd_bus_new, int(sd_bus **ret)); MOCK_METHOD1(sd_bus_new, int(sd_bus **ret));
MOCK_METHOD1(sd_bus_start, int(sd_bus *bus)); MOCK_METHOD1(sd_bus_start, int(sd_bus *bus));
MOCK_METHOD2(sd_bus_process, int(sd_bus *bus, sd_bus_message **r)); MOCK_METHOD2(sd_bus_process, int(sd_bus *bus, sd_bus_message **r));
MOCK_METHOD1(sd_bus_get_current_message, sd_bus_message*(sd_bus *bus));
MOCK_METHOD2(sd_bus_get_poll_data, int(sd_bus *bus, PollData* data)); MOCK_METHOD2(sd_bus_get_poll_data, int(sd_bus *bus, PollData* data));
MOCK_METHOD2(sd_bus_get_n_queued_read, int(sd_bus *bus, uint64_t *ret));
MOCK_METHOD1(sd_bus_flush, int(sd_bus *bus)); MOCK_METHOD1(sd_bus_flush, int(sd_bus *bus));
MOCK_METHOD1(sd_bus_flush_close_unref, sd_bus *(sd_bus *bus)); MOCK_METHOD1(sd_bus_flush_close_unref, sd_bus *(sd_bus *bus));
MOCK_METHOD1(sd_bus_close_unref, sd_bus *(sd_bus *bus)); MOCK_METHOD1(sd_bus_close_unref, sd_bus *(sd_bus *bus));

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file sdbus-c++-unit-tests.cpp * @file sdbus-c++-unit-tests.cpp
* *

View File

@ -4,7 +4,7 @@
cmake_minimum_required(VERSION 3.5) cmake_minimum_required(VERSION 3.5)
project(sdbus-c++-tools VERSION 1.2.0) project(sdbus-c++-tools VERSION 2.0.0)
include(GNUInstallDirs) include(GNUInstallDirs)
@ -45,31 +45,38 @@ add_executable(sdbus-c++-xml2cpp ${SDBUSCPP_XML2CPP_SRCS})
target_link_libraries (sdbus-c++-xml2cpp ${EXPAT_LIBRARIES}) target_link_libraries (sdbus-c++-xml2cpp ${EXPAT_LIBRARIES})
target_include_directories(sdbus-c++-xml2cpp PRIVATE ${EXPAT_INCLUDE_DIRS}) target_include_directories(sdbus-c++-xml2cpp PRIVATE ${EXPAT_INCLUDE_DIRS})
#----------------------------------
# INSTALLATION
#----------------------------------
install(TARGETS sdbus-c++-xml2cpp EXPORT sdbus-c++-tools-targets DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT dev)
#---------------------------------- #----------------------------------
# CMAKE CONFIG & PACKAGE CONFIG # CMAKE CONFIG & PACKAGE CONFIG
#---------------------------------- #----------------------------------
include(CMakePackageConfigHelpers) include(CMakePackageConfigHelpers)
install(EXPORT sdbus-c++-tools-targets
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/sdbus-c++-tools
NAMESPACE SDBusCpp::
COMPONENT dev)
configure_package_config_file(cmake/sdbus-c++-tools-config.cmake.in cmake/sdbus-c++-tools-config.cmake configure_package_config_file(cmake/sdbus-c++-tools-config.cmake.in cmake/sdbus-c++-tools-config.cmake
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/sdbus-c++) INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/sdbus-c++)
write_basic_package_version_file(cmake/sdbus-c++-tools-config-version.cmake COMPATIBILITY SameMajorVersion) write_basic_package_version_file(cmake/sdbus-c++-tools-config-version.cmake COMPATIBILITY SameMajorVersion)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/cmake/sdbus-c++-tools-config.cmake
${CMAKE_CURRENT_BINARY_DIR}/cmake/sdbus-c++-tools-config-version.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/sdbus-c++-tools
COMPONENT dev)
configure_file(pkgconfig/sdbus-c++-tools.pc.in pkgconfig/sdbus-c++-tools.pc @ONLY) configure_file(pkgconfig/sdbus-c++-tools.pc.in pkgconfig/sdbus-c++-tools.pc @ONLY)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/pkgconfig/sdbus-c++-tools.pc
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig COMPONENT dev) #----------------------------------
# INSTALLATION
#----------------------------------
if(NOT DEFINED SDBUSCPP_INSTALL)
set(SDBUSCPP_INSTALL ON)
endif()
if (SDBUSCPP_INSTALL)
install(TARGETS sdbus-c++-xml2cpp
EXPORT sdbus-c++-tools-targets
DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT sdbus-c++-dev)
install(EXPORT sdbus-c++-tools-targets
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/sdbus-c++-tools
NAMESPACE SDBusCpp::
COMPONENT sdbus-c++-dev)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/cmake/sdbus-c++-tools-config.cmake
${CMAKE_CURRENT_BINARY_DIR}/cmake/sdbus-c++-tools-config-version.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/sdbus-c++-tools
COMPONENT sdbus-c++-dev)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/pkgconfig/sdbus-c++-tools.pc
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig COMPONENT sdbus-c++-dev)
endif()

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file AdaptorGenerator.cpp * @file AdaptorGenerator.cpp
* *
@ -85,7 +85,17 @@ std::string AdaptorGenerator::processInterface(Node& interface) const
<< tab << "static constexpr const char* INTERFACE_NAME = \"" << ifaceName << "\";" << endl << endl << tab << "static constexpr const char* INTERFACE_NAME = \"" << ifaceName << "\";" << endl << endl
<< "protected:" << endl << "protected:" << endl
<< tab << className << "(sdbus::IObject& object)" << endl << tab << className << "(sdbus::IObject& object)" << endl
<< tab << tab << ": object_(object)" << endl; << tab << tab << ": m_object(object)" << endl
<< tab << "{" << endl
<< tab << "}" << endl << endl;
// Rule of Five
body << tab << className << "(const " << className << "&) = delete;" << endl;
body << tab << className << "& operator=(const " << className << "&) = delete;" << endl;
body << tab << className << "(" << className << "&&) = delete;" << endl;
body << tab << className << "& operator=(" << className << "&&) = delete;" << endl << endl;
body << tab << "~" << className << "() = default;" << endl << endl;
Nodes methods = interface["method"]; Nodes methods = interface["method"];
Nodes signals = interface["signal"]; Nodes signals = interface["signal"];
@ -111,28 +121,29 @@ std::string AdaptorGenerator::processInterface(Node& interface) const
if(!annotationRegistration.empty()) if(!annotationRegistration.empty())
{ {
std::stringstream str; std::stringstream str;
str << tab << tab << "object_.setInterfaceFlags(INTERFACE_NAME)" << annotationRegistration << ";" << endl; str << "sdbus::setInterfaceFlags()" << annotationRegistration << ";";
annotationRegistration = str.str(); annotationRegistration = str.str();
} }
std::string methodRegistration, methodDeclaration; std::vector<std::string> methodRegistrations;
std::tie(methodRegistration, methodDeclaration) = processMethods(methods); std::string methodDeclaration;
std::tie(methodRegistrations, methodDeclaration) = processMethods(methods);
std::string signalRegistration, signalMethods; std::vector<std::string> signalRegistrations;
std::tie(signalRegistration, signalMethods) = processSignals(signals); std::string signalMethods;
std::tie(signalRegistrations, signalMethods) = processSignals(signals);
std::string propertyRegistration, propertyAccessorDeclaration; std::vector<std::string> propertyRegistrations;
std::tie(propertyRegistration, propertyAccessorDeclaration) = processProperties(properties); std::string propertyAccessorDeclaration;
std::tie(propertyRegistrations, propertyAccessorDeclaration) = processProperties(properties);
body << tab << "{" << endl auto vtableRegistration = createVTableRegistration(annotationRegistration, methodRegistrations, signalRegistrations, propertyRegistrations);
<< annotationRegistration
<< methodRegistration body << tab << "void registerAdaptor()" << endl
<< signalRegistration << tab << "{" << endl
<< propertyRegistration << vtableRegistration << endl
<< tab << "}" << endl << endl; << tab << "}" << endl << endl;
body << tab << "~" << className << "() = default;" << endl << endl;
if (!signalMethods.empty()) if (!signalMethods.empty())
{ {
body << "public:" << endl << signalMethods; body << "public:" << endl << signalMethods;
@ -149,7 +160,7 @@ std::string AdaptorGenerator::processInterface(Node& interface) const
} }
body << "private:" << endl body << "private:" << endl
<< tab << "sdbus::IObject& object_;" << endl << tab << "sdbus::IObject& m_object;" << endl
<< "};" << endl << endl << "};" << endl << endl
<< std::string(namespacesCount, '}') << " // namespaces" << endl << endl; << std::string(namespacesCount, '}') << " // namespaces" << endl << endl;
@ -157,12 +168,16 @@ std::string AdaptorGenerator::processInterface(Node& interface) const
} }
std::tuple<std::string, std::string> AdaptorGenerator::processMethods(const Nodes& methods) const std::tuple<std::vector<std::string>, std::string> AdaptorGenerator::processMethods(const Nodes& methods) const
{ {
std::ostringstream registrationSS, declarationSS; std::ostringstream declarationSS;
std::vector<std::string> methodRegistrations;
for (const auto& method : methods) for (const auto& method : methods)
{ {
std::ostringstream registrationSS;
auto methodName = method->get("name"); auto methodName = method->get("name");
auto methodNameSafe = mangle_name(methodName); auto methodNameSafe = mangle_name(methodName);
@ -186,7 +201,7 @@ std::tuple<std::string, std::string> AdaptorGenerator::processMethods(const Node
} }
else if (annotationName == "org.freedesktop.DBus.Method.Async") else if (annotationName == "org.freedesktop.DBus.Method.Async")
{ {
if (annotationValue == "server" || annotationValue == "clientserver") if (annotationValue == "server" || annotationValue == "clientserver" || annotationValue == "client-server")
async = true; async = true;
} }
else if (annotationName == "org.freedesktop.systemd1.Privileged") else if (annotationName == "org.freedesktop.systemd1.Privileged")
@ -211,9 +226,8 @@ std::tuple<std::string, std::string> AdaptorGenerator::processMethods(const Node
using namespace std::string_literals; using namespace std::string_literals;
registrationSS << tab << tab << "object_.registerMethod(\"" registrationSS << "sdbus::registerMethod(\""
<< methodName << "\")" << methodName << "\")"
<< ".onInterface(INTERFACE_NAME)"
<< (!argStringsStr.empty() ? (".withInputParamNames(" + argStringsStr + ")") : "") << (!argStringsStr.empty() ? (".withInputParamNames(" + argStringsStr + ")") : "")
<< (!outArgStringsStr.empty() ? (".withOutputParamNames(" + outArgStringsStr + ")") : "") << (!outArgStringsStr.empty() ? (".withOutputParamNames(" + outArgStringsStr + ")") : "")
<< ".implementedAs(" << ".implementedAs("
@ -223,7 +237,9 @@ std::tuple<std::string, std::string> AdaptorGenerator::processMethods(const Node
<< "){ " << (async ? "" : "return ") << "this->" << methodNameSafe << "(" << "){ " << (async ? "" : "return ") << "this->" << methodNameSafe << "("
<< (async ? "std::move(result)"s + (argTypeStr.empty() ? "" : ", ") : "") << (async ? "std::move(result)"s + (argTypeStr.empty() ? "" : ", ") : "")
<< argStr << "); })" << argStr << "); })"
<< annotationRegistration << ";" << endl; << annotationRegistration;
methodRegistrations.push_back(registrationSS.str());
declarationSS << tab declarationSS << tab
<< "virtual " << "virtual "
@ -235,16 +251,20 @@ std::tuple<std::string, std::string> AdaptorGenerator::processMethods(const Node
<< ") = 0;" << endl; << ") = 0;" << endl;
} }
return std::make_tuple(registrationSS.str(), declarationSS.str()); return std::make_tuple(methodRegistrations, declarationSS.str());
} }
std::tuple<std::string, std::string> AdaptorGenerator::processSignals(const Nodes& signals) const std::tuple<std::vector<std::string>, std::string> AdaptorGenerator::processSignals(const Nodes& signals) const
{ {
std::ostringstream signalRegistrationSS, signalMethodSS; std::ostringstream signalMethodSS;
std::vector<std::string> signalRegistrations;
for (const auto& signal : signals) for (const auto& signal : signals)
{ {
std::stringstream signalRegistrationSS;
auto name = signal->get("name"); auto name = signal->get("name");
auto annotations = getAnnotations(*signal); auto annotations = getAnnotations(*signal);
@ -266,9 +286,7 @@ std::tuple<std::string, std::string> AdaptorGenerator::processSignals(const Node
std::string argStr, argTypeStr, typeStr, argStringsStr; std::string argStr, argTypeStr, typeStr, argStringsStr;
std::tie(argStr, argTypeStr, typeStr, argStringsStr) = argsToNamesAndTypes(args); std::tie(argStr, argTypeStr, typeStr, argStringsStr) = argsToNamesAndTypes(args);
signalRegistrationSS << tab << tab signalRegistrationSS << "sdbus::registerSignal(\"" << name << "\")";
<< "object_.registerSignal(\"" << name << "\")"
".onInterface(INTERFACE_NAME)";
if (args.size() > 0) if (args.size() > 0)
{ {
@ -276,7 +294,8 @@ std::tuple<std::string, std::string> AdaptorGenerator::processSignals(const Node
} }
signalRegistrationSS << annotationRegistration; signalRegistrationSS << annotationRegistration;
signalRegistrationSS << ";" << endl;
signalRegistrations.push_back(signalRegistrationSS.str());
auto nameWithCapFirstLetter = name; auto nameWithCapFirstLetter = name;
nameWithCapFirstLetter[0] = std::toupper(nameWithCapFirstLetter[0]); nameWithCapFirstLetter[0] = std::toupper(nameWithCapFirstLetter[0]);
@ -284,7 +303,7 @@ std::tuple<std::string, std::string> AdaptorGenerator::processSignals(const Node
signalMethodSS << tab << "void emit" << nameWithCapFirstLetter << "(" << argTypeStr << ")" << endl signalMethodSS << tab << "void emit" << nameWithCapFirstLetter << "(" << argTypeStr << ")" << endl
<< tab << "{" << endl << tab << "{" << endl
<< tab << tab << "object_.emitSignal(\"" << name << "\")" << tab << tab << "m_object.emitSignal(\"" << name << "\")"
".onInterface(INTERFACE_NAME)"; ".onInterface(INTERFACE_NAME)";
if (!argStr.empty()) if (!argStr.empty())
@ -296,16 +315,20 @@ std::tuple<std::string, std::string> AdaptorGenerator::processSignals(const Node
<< tab << "}" << endl << endl; << tab << "}" << endl << endl;
} }
return std::make_tuple(signalRegistrationSS.str(), signalMethodSS.str()); return std::make_tuple(signalRegistrations, signalMethodSS.str());
} }
std::tuple<std::string, std::string> AdaptorGenerator::processProperties(const Nodes& properties) const std::tuple<std::vector<std::string>, std::string> AdaptorGenerator::processProperties(const Nodes& properties) const
{ {
std::ostringstream registrationSS, declarationSS; std::ostringstream declarationSS;
std::vector<std::string> propertyRegistrations;
for (const auto& property : properties) for (const auto& property : properties)
{ {
std::ostringstream registrationSS;
auto propertyName = property->get("name"); auto propertyName = property->get("name");
auto propertyNameSafe = mangle_name(propertyName); auto propertyNameSafe = mangle_name(propertyName);
auto propertyAccess = property->get("access"); auto propertyAccess = property->get("access");
@ -333,9 +356,8 @@ std::tuple<std::string, std::string> AdaptorGenerator::processProperties(const N
<< "Option '" << annotationName << "' not allowed or supported in this context! Option ignored..." << std::endl; << "Option '" << annotationName << "' not allowed or supported in this context! Option ignored..." << std::endl;
} }
registrationSS << tab << tab << "object_.registerProperty(\"" registrationSS << "sdbus::registerProperty(\""
<< propertyName << "\")" << propertyName << "\")";
<< ".onInterface(INTERFACE_NAME)";
if (propertyAccess == "read" || propertyAccess == "readwrite") if (propertyAccess == "read" || propertyAccess == "readwrite")
{ {
@ -350,7 +372,8 @@ std::tuple<std::string, std::string> AdaptorGenerator::processProperties(const N
} }
registrationSS << annotationRegistration; registrationSS << annotationRegistration;
registrationSS << ";" << endl;
propertyRegistrations.push_back(registrationSS.str());
if (propertyAccess == "read" || propertyAccess == "readwrite") if (propertyAccess == "read" || propertyAccess == "readwrite")
declarationSS << tab << "virtual " << propertyType << " " << propertyNameSafe << "() = 0;" << endl; declarationSS << tab << "virtual " << propertyType << " " << propertyNameSafe << "() = 0;" << endl;
@ -358,7 +381,38 @@ std::tuple<std::string, std::string> AdaptorGenerator::processProperties(const N
declarationSS << tab << "virtual void " << propertyNameSafe << "(" << propertyTypeArg << ") = 0;" << endl; declarationSS << tab << "virtual void " << propertyNameSafe << "(" << propertyTypeArg << ") = 0;" << endl;
} }
return std::make_tuple(registrationSS.str(), declarationSS.str()); return std::make_tuple(propertyRegistrations, declarationSS.str());
}
std::string AdaptorGenerator::createVTableRegistration(const std::string& annotationRegistration,
const std::vector<std::string>& methodRegistrations,
const std::vector<std::string>& signalRegistrations,
const std::vector<std::string>& propertyRegistrations) const
{
std::vector<std::string> allRegistrations;
if (!annotationRegistration.empty())
allRegistrations.push_back(annotationRegistration);
allRegistrations.insert(allRegistrations.end(), methodRegistrations.begin(), methodRegistrations.end());
allRegistrations.insert(allRegistrations.end(), signalRegistrations.begin(), signalRegistrations.end());
allRegistrations.insert(allRegistrations.end(), propertyRegistrations.begin(), propertyRegistrations.end());
if (allRegistrations.empty())
return {};
std::ostringstream registrationSS;
if (allRegistrations.size() == 1)
{
registrationSS << tab << tab << "m_object.addVTable(" << allRegistrations[0] << ").forInterface(INTERFACE_NAME);";
}
else
{
registrationSS << tab << tab << "m_object.addVTable( " << allRegistrations[0] << endl;
for (size_t i = 1; i < allRegistrations.size(); ++i)
registrationSS << tab << tab << " , " << allRegistrations[i] << endl;
registrationSS << tab << tab << " ).forInterface(INTERFACE_NAME);";
}
return registrationSS.str();
} }
std::map<std::string, std::string> AdaptorGenerator::getAnnotations( sdbuscpp::xml::Node& node) const std::map<std::string, std::string> AdaptorGenerator::getAnnotations( sdbuscpp::xml::Node& node) const

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file AdaptorGenerator.h * @file AdaptorGenerator.h
* *
@ -59,21 +59,26 @@ private:
* @param methods * @param methods
* @return tuple: registration of methods, declaration of abstract methods * @return tuple: registration of methods, declaration of abstract methods
*/ */
std::tuple<std::string, std::string> processMethods(const sdbuscpp::xml::Nodes& methods) const; std::tuple<std::vector<std::string>, std::string> processMethods(const sdbuscpp::xml::Nodes& methods) const;
/** /**
* Generate source code for signals * Generate source code for signals
* @param signals * @param signals
* @return tuple: registration of signals, definition of signal methods * @return tuple: registration of signals, definition of signal methods
*/ */
std::tuple<std::string, std::string> processSignals(const sdbuscpp::xml::Nodes& signals) const; std::tuple<std::vector<std::string>, std::string> processSignals(const sdbuscpp::xml::Nodes& signals) const;
/** /**
* Generate source code for properties * Generate source code for properties
* @param properties * @param properties
* @return tuple: registration of properties, declaration of property accessor virtual methods * @return tuple: registration of properties, declaration of property accessor virtual methods
*/ */
std::tuple<std::string, std::string> processProperties(const sdbuscpp::xml::Nodes& properties) const; std::tuple<std::vector<std::string>, std::string> processProperties(const sdbuscpp::xml::Nodes& properties) const;
std::string createVTableRegistration(const std::string& annotationRegistration,
const std::vector<std::string>& methodRegistrations,
const std::vector<std::string>& signalRegistrations,
const std::vector<std::string>& propertyRegistrations) const;
/** /**
* Get annotations listed for a given node * Get annotations listed for a given node

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file BaseGenerator.cpp * @file BaseGenerator.cpp
* *

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file BaseGenerator.h * @file BaseGenerator.h
* *

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file ProxyGenerator.cpp * @file ProxyGenerator.cpp
* *
@ -84,7 +84,17 @@ std::string ProxyGenerator::processInterface(Node& interface) const
<< tab << "static constexpr const char* INTERFACE_NAME = \"" << ifaceName << "\";" << endl << endl << tab << "static constexpr const char* INTERFACE_NAME = \"" << ifaceName << "\";" << endl << endl
<< "protected:" << endl << "protected:" << endl
<< tab << className << "(sdbus::IProxy& proxy)" << endl << tab << className << "(sdbus::IProxy& proxy)" << endl
<< tab << tab << ": proxy_(proxy)" << endl; << tab << tab << ": m_proxy(proxy)" << endl
<< tab << "{" << endl
<< tab << "}" << endl << endl;
// Rule of Five
body << tab << className << "(const " << className << "&) = delete;" << endl;
body << tab << className << "& operator=(const " << className << "&) = delete;" << endl;
body << tab << className << "(" << className << "&&) = delete;" << endl;
body << tab << className << "& operator=(" << className << "&&) = delete;" << endl << endl;
body << tab << "~" << className << "() = default;" << endl << endl;
Nodes methods = interface["method"]; Nodes methods = interface["method"];
Nodes signals = interface["signal"]; Nodes signals = interface["signal"];
@ -93,21 +103,26 @@ std::string ProxyGenerator::processInterface(Node& interface) const
std::string registration, declaration; std::string registration, declaration;
std::tie(registration, declaration) = processSignals(signals); std::tie(registration, declaration) = processSignals(signals);
body << tab << "{" << endl body << tab << "void registerProxy()" << endl
<< registration << tab << "{" << endl
<< tab << "}" << endl << endl; << registration
<< tab << "}" << endl << endl;
body << tab << "~" << className << "() = default;" << endl << endl;
if (!declaration.empty()) if (!declaration.empty())
body << declaration << endl; body << declaration << endl;
std::string methodDefinitions, asyncDeclarations; std::string methodDefinitions, asyncDeclarationsMethods;
std::tie(methodDefinitions, asyncDeclarations) = processMethods(methods); 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()) if (!methodDefinitions.empty())
@ -115,14 +130,13 @@ std::string ProxyGenerator::processInterface(Node& interface) const
body << "public:" << endl << methodDefinitions; body << "public:" << endl << methodDefinitions;
} }
std::string propertyDefinitions = processProperties(properties);
if (!propertyDefinitions.empty()) if (!propertyDefinitions.empty())
{ {
body << "public:" << endl << propertyDefinitions; body << "public:" << endl << propertyDefinitions;
} }
body << "private:" << endl body << "private:" << endl
<< tab << "sdbus::IProxy& proxy_;" << endl << tab << "sdbus::IProxy& m_proxy;" << endl
<< "};" << endl << endl << "};" << endl << endl
<< std::string(namespacesCount, '}') << " // namespaces" << endl << endl; << std::string(namespacesCount, '}') << " // namespaces" << endl << endl;
@ -145,19 +159,30 @@ std::tuple<std::string, std::string> ProxyGenerator::processMethods(const Nodes&
bool dontExpectReply{false}; bool dontExpectReply{false};
bool async{false}; bool async{false};
bool future{false}; // Async methods implemented by means of either std::future or callbacks
std::string timeoutValue; std::string timeoutValue;
std::smatch smTimeout; std::smatch smTimeout;
Nodes annotations = (*method)["annotation"]; Nodes annotations = (*method)["annotation"];
for (const auto& annotation : annotations) for (const auto& annotation : annotations)
{ {
if (annotation->get("name") == "org.freedesktop.DBus.Method.NoReply" && annotation->get("value") == "true") const auto annotationName = annotation->get("name");
const auto annotationValue = annotation->get("value");
if (annotationName == "org.freedesktop.DBus.Method.NoReply" && annotationValue == "true")
dontExpectReply = true; dontExpectReply = true;
else if (annotation->get("name") == "org.freedesktop.DBus.Method.Async" else
&& (annotation->get("value") == "client" || annotation->get("value") == "clientserver")) {
async = true; if (annotationName == "org.freedesktop.DBus.Method.Async"
if (annotation->get("name") == "org.freedesktop.DBus.Method.Timeout") && (annotationValue == "client" || annotationValue == "clientserver" || annotationValue == "client-server"))
timeoutValue = annotation->get("value"); async = true;
else if (annotationName == "org.freedesktop.DBus.Method.Async.ClientImpl" && annotationValue == "callback")
future = false;
else if (annotationName == "org.freedesktop.DBus.Method.Async.ClientImpl" && (annotationValue == "future" || annotationValue == "std::future"))
future = true;
}
if (annotationName == "org.freedesktop.DBus.Method.Timeout")
timeoutValue = annotationValue;
} }
if (dontExpectReply && outArgs.size() > 0) if (dontExpectReply && outArgs.size() > 0)
{ {
@ -180,12 +205,13 @@ std::tuple<std::string, std::string> ProxyGenerator::processMethods(const Nodes&
} }
auto retType = outArgsToType(outArgs); auto retType = outArgsToType(outArgs);
auto retTypeBare = outArgsToType(outArgs, true);
std::string inArgStr, inArgTypeStr; std::string inArgStr, inArgTypeStr;
std::tie(inArgStr, inArgTypeStr, std::ignore, std::ignore) = argsToNamesAndTypes(inArgs); std::tie(inArgStr, inArgTypeStr, std::ignore, std::ignore) = argsToNamesAndTypes(inArgs);
std::string outArgStr, outArgTypeStr; std::string outArgStr, outArgTypeStr;
std::tie(outArgStr, outArgTypeStr, std::ignore, std::ignore) = argsToNamesAndTypes(outArgs); std::tie(outArgStr, outArgTypeStr, std::ignore, std::ignore) = argsToNamesAndTypes(outArgs);
const std::string realRetType = (async && !dontExpectReply ? "sdbus::PendingAsyncCall" : async ? "void" : retType); const std::string realRetType = (async && !dontExpectReply ? (future ? "std::future<" + retType + ">" : "sdbus::PendingAsyncCall") : async ? "void" : retType);
definitionSS << tab << realRetType << " " << nameSafe << "(" << inArgTypeStr << ")" << endl definitionSS << tab << realRetType << " " << nameSafe << "(" << inArgTypeStr << ")" << endl
<< tab << "{" << endl; << tab << "{" << endl;
@ -200,7 +226,7 @@ std::tuple<std::string, std::string> ProxyGenerator::processMethods(const Nodes&
} }
definitionSS << tab << tab << (async && !dontExpectReply ? "return " : "") definitionSS << tab << tab << (async && !dontExpectReply ? "return " : "")
<< "proxy_.callMethod" << (async ? "Async" : "") << "(\"" << name << "\").onInterface(INTERFACE_NAME)"; << "m_proxy.callMethod" << (async ? "Async" : "") << "(\"" << name << "\").onInterface(INTERFACE_NAME)";
if (!timeoutValue.empty()) if (!timeoutValue.empty())
{ {
@ -219,11 +245,18 @@ std::tuple<std::string, std::string> ProxyGenerator::processMethods(const Nodes&
auto nameBigFirst = name; auto nameBigFirst = name;
nameBigFirst[0] = islower(nameBigFirst[0]) ? nameBigFirst[0] + 'A' - 'a' : nameBigFirst[0]; nameBigFirst[0] = islower(nameBigFirst[0]) ? nameBigFirst[0] + 'A' - 'a' : nameBigFirst[0];
definitionSS << ".uponReplyInvoke([this](const sdbus::Error* error" << (outArgTypeStr.empty() ? "" : ", ") << outArgTypeStr << ")" if (future) // Async methods implemented through future
"{ this->on" << nameBigFirst << "Reply(" << outArgStr << (outArgStr.empty() ? "" : ", ") << "error); })"; {
definitionSS << ".getResultAsFuture<" << retTypeBare << ">()";
}
else // Async methods implemented through callbacks
{
definitionSS << ".uponReplyInvoke([this](std::optional<sdbus::Error> error" << (outArgTypeStr.empty() ? "" : ", ") << outArgTypeStr << ")"
"{ this->on" << nameBigFirst << "Reply(" << outArgStr << (outArgStr.empty() ? "" : ", ") << "std::move(error)); })";
asyncDeclarationSS << tab << "virtual void on" << nameBigFirst << "Reply(" asyncDeclarationSS << tab << "virtual void on" << nameBigFirst << "Reply("
<< outArgTypeStr << (outArgTypeStr.empty() ? "" : ", ") << "const sdbus::Error* error) = 0;" << endl; << outArgTypeStr << (outArgTypeStr.empty() ? "" : ", ") << "std::optional<sdbus::Error> error) = 0;" << endl;
}
} }
else if (outArgs.size() > 0) else if (outArgs.size() > 0)
{ {
@ -256,7 +289,7 @@ std::tuple<std::string, std::string> ProxyGenerator::processSignals(const Nodes&
std::string argStr, argTypeStr; std::string argStr, argTypeStr;
std::tie(argStr, argTypeStr, std::ignore, std::ignore) = argsToNamesAndTypes(args); std::tie(argStr, argTypeStr, std::ignore, std::ignore) = argsToNamesAndTypes(args);
registrationSS << tab << tab << "proxy_" registrationSS << tab << tab << "m_proxy"
".uponSignal(\"" << name << "\")" ".uponSignal(\"" << name << "\")"
".onInterface(INTERFACE_NAME)" ".onInterface(INTERFACE_NAME)"
".call([this](" << argTypeStr << ")" ".call([this](" << argTypeStr << ")"
@ -268,9 +301,9 @@ std::tuple<std::string, std::string> ProxyGenerator::processSignals(const Nodes&
return std::make_tuple(registrationSS.str(), declarationSS.str()); 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) for (const auto& property : properties)
{ {
auto propertyName = property->get("name"); auto propertyName = property->get("name");
@ -282,25 +315,97 @@ std::string ProxyGenerator::processProperties(const Nodes& properties) const
auto propertyArg = std::string("value"); auto propertyArg = std::string("value");
auto propertyTypeArg = std::string("const ") + propertyType + "& " + propertyArg; 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") 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; << tab << "{" << endl;
propertySS << tab << tab << "return proxy_.getProperty(\"" << propertyName << "\")" propertySS << tab << tab << "return m_proxy.getProperty" << (asyncGet ? "Async" : "") << "(\"" << propertyName << "\")"
".onInterface(INTERFACE_NAME)"; ".onInterface(INTERFACE_NAME)";
if (!asyncGet)
{
propertySS << ".get<" << realRetType << ">()";
}
else
{
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](std::optional<sdbus::Error> error, const sdbus::Variant& value)"
"{ this->on" << nameBigFirst << "PropertyGetReply(value.get<" << propertyType << ">(), std::move(error)); })";
asyncDeclarationSS << tab << "virtual void on" << nameBigFirst << "PropertyGetReply("
<< "const " << propertyType << "& value, std::optional<sdbus::Error> error) = 0;" << endl;
}
}
propertySS << ";" << endl << tab << "}" << endl << endl; propertySS << ";" << endl << tab << "}" << endl << endl;
} }
if (propertyAccess == "readwrite" || propertyAccess == "write") if (propertyAccess == "readwrite" || propertyAccess == "write")
{ {
propertySS << tab << "void " << propertyNameSafe << "(" << propertyTypeArg << ")" << endl const std::string realRetType = (asyncSet ? (futureSet ? "std::future<void>" : "sdbus::PendingAsyncCall") : "void");
<< tab << "{" << endl;
propertySS << tab << tab << "proxy_.setProperty(\"" << propertyName << "\")" propertySS << tab << realRetType << " " << propertyNameSafe << "(" << propertyTypeArg << ")" << endl
<< tab << "{" << endl;
propertySS << tab << tab << (asyncSet ? "return " : "") << "m_proxy.setProperty" << (asyncSet ? "Async" : "")
<< "(\"" << propertyName << "\")"
".onInterface(INTERFACE_NAME)" ".onInterface(INTERFACE_NAME)"
".toValue(" << propertyArg << ")"; ".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](std::optional<sdbus::Error> error)"
"{ this->on" << nameBigFirst << "PropertySetReply(std::move(error)); })";
asyncDeclarationSS << tab << "virtual void on" << nameBigFirst << "PropertySetReply("
<< "std::optional<sdbus::Error> error) = 0;" << endl;
}
}
propertySS << ";" << endl << tab << "}" << endl << endl; propertySS << ";" << endl << tab << "}" << endl << endl;
} }
} }
return propertySS.str(); return std::make_tuple(propertySS.str(), asyncDeclarationSS.str());
} }

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file ProxyGenerator.h * @file ProxyGenerator.h
* *
@ -73,7 +73,7 @@ private:
* @param properties * @param properties
* @return source code * @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;
}; };

View File

@ -87,7 +87,7 @@ static void _parse_signature(const std::string &signature, std::string &type, un
} }
case '\0': case '\0':
{ {
std::cerr << std::cerr <<
"Invalid array definition. Type is missing after '" << signature "Invalid array definition. Type is missing after '" << signature
<< "'." << "'."
<< std::endl; << std::endl;

View File

@ -1,6 +1,6 @@
/** /**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com> * (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
* *
* @file xml2cpp.cpp * @file xml2cpp.cpp
* *