diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d0828e4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,42 @@ +# c++ +*.o +*.lo +*.la +m4/ +build/ +.deps/ +.dirstamp +.libs/ +test/libsdbus-c++_unittests +test/libsdbus-c++_integrationtests +test/run-test-on-device.sh + +#eclipse +.cproject +.settings +*.log + +#autotools +sdbus-cpp.pc +*Makefile +*Makefile.in +aclocal.m4 +arm-poky-linux-gnueabi-libtool +autom4te.cache +configure +config.h +config.status +config.guess +config.h.in +config.sub +compile +depcomp +install-sh +ltmain.sh +missing +stamp-h1 +test-driver +.autotools +.gdbinit +sdbus-cpp_gdb_arm-poky-linux-gnueabi.launch +sdbus-c++.pc diff --git a/.project b/.project new file mode 100644 index 0000000..0abe63f --- /dev/null +++ b/.project @@ -0,0 +1,35 @@ + + + sdbus-cpp + + + + + + org.eclipse.cdt.autotools.core.genmakebuilderV2 + + + + + org.eclipse.cdt.managedbuilder.core.genmakebuilder + clean,full,incremental, + + + + + org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder + full,incremental, + + + + + + org.eclipse.cdt.core.cnature + org.eclipse.cdt.core.ccnature + org.eclipse.cdt.managedbuilder.core.managedBuildNature + org.eclipse.cdt.managedbuilder.core.ScannerConfigNature + org.yocto.sdk.ide.YoctoSDKNature + org.eclipse.cdt.autotools.core.autotoolsNatureV2 + org.yocto.sdk.ide.YoctoSDKAutotoolsNature + + diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..90d2a75 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,5 @@ +The library: + Stanislav Angelovic (stanislav.angelovic[at]kistler.com) + +Stub generator: + Lukas Durfina (lukas.durfina[at]kistler.com) diff --git a/LICENSE b/COPYING similarity index 100% rename from LICENSE rename to COPYING diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..e69de29 diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..5635a77 --- /dev/null +++ b/INSTALL @@ -0,0 +1,6 @@ +Building: + $ ./autogen.sh ${CONFIGURE_FLAGS} + $ make + +Installing: + $ sudo make install diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..6b7e4c3 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,22 @@ +EXTRA_DIST = autogen.sh +SUBDIRS = include src test + +DISTCHECK_CONFIGURE_FLAGS= + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = sdbus-c++.pc + +CLEANFILES = *~ *.lo *.la + +MOSTLYCLEANFILES = *.o + +DISTCLEANFILES = \ + *libtool* \ + aclocal.m4 \ + compile config.* configure \ + depcomp install-sh \ + ltmain.sh \ + Makefile Makefile.in \ + missing \ + stamp-h1 \ + sdbus-c++.pc diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..eb2d119 --- /dev/null +++ b/NEWS @@ -0,0 +1,4 @@ +sdbus-c++: D-Bus IPC C++ binding library based on sd-bus + +v0.2.3: + * sdbus-c++ v0.2.3 released to the public! diff --git a/README b/README new file mode 100644 index 0000000..96dc92f --- /dev/null +++ b/README @@ -0,0 +1 @@ +See README.md diff --git a/README.md b/README.md index d0660dd..aa82b49 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,44 @@ -# sdbus-cpp -D-Bus IPC C++ binding library build on top of sd-bus +sdbus-c++ +========= + +sdbus-c++ is a C++ API library for D-Bus IPC, based on sd-bus implementation. + +Building and installing the library +----------------------------------- + +```bash +$ ./autogen.sh ${CONFIGURE_FLAGS} +$ make +$ sudo make install +``` + +Use `--disable-tests` flag when configuring to disable building unit and integration tests for the library. + +Dependencies +------------ + +* `C++17` - the library uses C++17 `std::uncaught_exceptions()` feature. When building sdbus-c++ manually, make sure you use a compiler that supports that feature. +* `libsystemd` - systemd library containing sd-bus implementation. Systemd v236 at least is needed for sdbus-c++ to compile. +* `googletest` - google unit testing framework, only necessary when building tests + +Licensing +--------- + +The library is distributed under LGPLv2.1+ license. + +References/documentation +------------------------ + +* [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) +* [Tutorial: Using sdbus-c++](doc/using-sdbus-c++.md) + +Contributing +------------ + +Contributions that increase the library quality, functionality, or fix issues are very welcome. To introduce a change, please submit a merge request with a description. + +Contact +------- + +stanislav.angelovic[at]kistler.com diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..7fd81e2 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,10 @@ +#! /bin/sh +[ -e config.cache ] && rm -f config.cache + +libtoolize --automake +aclocal ${OECORE_ACLOCAL_OPTS} +autoconf +autoheader +automake -a +./configure $@ +exit diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..4459a99 --- /dev/null +++ b/configure.ac @@ -0,0 +1,62 @@ +AC_PREREQ(2.61) + +# package version number (not shared library version) +# odd micro numbers indicate in-progress development +# even micro numbers indicate released versions +m4_define(sdbus_cpp_version_major, 0) +m4_define(sdbus_cpp_version_minor, 2) +m4_define(sdbus_cpp_version_micro, 3) + +m4_define([sdbus_cpp_version], + [sdbus_cpp_version_major.sdbus_cpp_version_minor.sdbus_cpp_version_micro]) +m4_define([sdbus_cpp_api_version], + [sdbus_cpp_version_major.sdbus_cpp_version_minor]) + +AC_INIT(libsdbus-c++, sdbus_cpp_version) +AM_INIT_AUTOMAKE +AC_CONFIG_HEADERS(config.h) + +# Checks for programs. +AC_PROG_LIBTOOL +AC_PROG_CXX +AC_PROG_CC +AC_PROG_INSTALL + +# enable pkg-config +PKG_PROG_PKG_CONFIG + +PKG_CHECK_MODULES(SYSTEMD, [libsystemd >= 0.10.1],, + AC_MSG_ERROR([You need the libsystemd library (version 0.10.1 or better)] + [https://www.freedesktop.org/wiki/Software/systemd/]) +) + +# Checks for library functions. +#AC_CHECK_FUNCS([memset]) + +AC_SUBST(libsdbus_cpp_CFLAGS) +AC_SUBST(libsdbus_cpp_LIBS) + +AC_ARG_ENABLE([tests], + [AS_HELP_STRING([--enable-tests], + [build and install tests @<:@default=yes@:>@])], + [], + [enable_tests=yes]) +AM_CONDITIONAL([ENABLE_TESTS], [test x$enable_tests = xyes]) + +#icondir=${datadir}/icons/hicolor/32x32/apps +#AC_SUBST(icondir) + +AC_OUTPUT([ +Makefile +include/Makefile +src/Makefile +test/Makefile +sdbus-c++.pc +]) + +echo "" +echo " sdbus-cpp $VERSION" +echo " =====================" +echo "" +echo " To build the project, run \"make\"" +echo "" diff --git a/doc/using-sdbus-c++.md b/doc/using-sdbus-c++.md new file mode 100644 index 0000000..7db7d26 --- /dev/null +++ b/doc/using-sdbus-c++.md @@ -0,0 +1,727 @@ +Using sdbus-c++ library +======================= + +**Table of contents** + +1. [Introduction](#introduction) +2. [Integrating sdbus-c++ into your project](#integrating-sdbus-c-into-your-project) +3. [Header files and namespaces](#header-files-and-namespaces) +4. [Error signalling and propagation](#error-signalling-and-propagation) +5. [Multiple layers of sdbus-c++ API](#multiple-layers-of-sdbus-c-api) +6. [An example: Number concatenator](#an-example-number-concatenator) +7. [Implementing the Concatenator example using basic sdbus-c++ API layer](#implementing-the-concatenator-example-using-basic-sdbus-c-api-layer) +8. [Implementing the Concatenator example using convenience sdbus-c++ API layer](#implementing-the-concatenator-example-using-convenience-sdbus-c-api-layer) +9. [Implementing the Concatenator example using sdbus-c++-generated stubs](#implementing-the-concatenator-example-using-sdbus-c-generated-stubs) +10. [Using D-Bus properties](#using-d-bus-properties) +11. [Conclusion](#conclusion) + +Introduction +------------ + +sdbus-c++ is a C++ wrapper library built on top of [sd-bus](http://0pointer.net/blog/the-new-sd-bus-api-of-systemd.html), a lightweight D-Bus client +library implemented within systemd project. It provides D-Bus functionality on a higher level of abstraction, trying to employ C++ type system +to shift as much work as possible from the developer to the compiler. + +sdbus-c++ does not cover the entire sd-bus API, but provides tools for implementing the most common functionality - RPC +method calls, signals and properties. There is room for additions and improvements, as needed and when needed. + +Integrating sdbus-c++ into your project +--------------------------------------- + +The library build system is based on Autotools. The library supports `pkg-config`, so integrating it into your autotools project +is a two step process. + +1. Add `PKG_CHECK_MODULES` macro into your `configure.ac`: +```bash +PKG_CHECK_MODULES(SDBUSCPP, [sdbus-c++ >= 0.1],, + AC_MSG_ERROR([You need the sdbus-c++ library (version 0.1 or better)] + [http://www.kistler.com/]) +) +``` + +2. Update `*_CFLAGS` and `*_LDFLAGS` in Makefiles of modules that use *sdbus-c++*, for example like this: +```bash +AM_CXXFLAGS = -std=c++17 -pedantic -W -Wall @SDBUSCPP_CFLAGS@ ... +AM_LDFLAGS = @SDBUSCPP_LIBS@ ... +``` + +Note: sdbus-c++ library depends on C++17, since it uses C++17 `std::uncaught_exceptions()` feature. When building sdbus-c++ manually, make sure you use a compiler that supports that feature. To use the library, make sure you have a C++ standard library that supports the feature. The feature is supported by e.g. gcc >= 6, and clang >= 3.7. + +Header files and namespaces +--------------------------- + +All sdbus-c++ header files reside in the `sdbus-c++` subdirectory within the standard include directory. Users can either include +individual header files, like so: + +```cpp +#include +#include +``` + +or just include the global header file that pulls in everything: + +```cpp +#include +``` + +All public types and functions of sdbus-c++ reside in the `sdbus` namespace. + +Error signalling and propagation +-------------------------------- + +`sdbus::Error` exception is used to signal errors in sdbus-c++. There are two types of errors: + * D-Bus related errors, like call timeouts, failed socket allocation, etc. + * user errors, i.e. errors signalled and propagated from remote methods back to the caller. + +The exception object carries the error name and error message with it. + +Multiple layers of sdbus-c++ API +------------------------------- + +sdbus-c++ API comes in two layers: + * the basic layer, which is almost pure wrapper layer on top of sd-bus, using mechanisms that are native to C++, + * the convenience layer, building on top of the basic layer, which aims at providing shorter, safer, and more expressive way of writing the + client code. + +sdbus-c++ also ships with a stub generator tool that converts D-Bus IDL in XML format into stub code for the adaptor as well as proxy part. +Hierarchically, these stubs provide yet another layer of convenience (the "stubs layer"), making it possible for D-Bus RPC calls to look like +native C++ calls on a local object. + +An example: Number concatenator +------------------------------- + +Let's have an object `/org/sdbuscpp/concatenator` that implements the `org.sdbuscpp.concatenator` interface. The interface exposes the following: +* a `concatenate` method that takes a collection of integers and a separator string and returns a string that is the concatenation of all + integers from the collection using given separator, +* a `concatenated` signal that is emitted at the end of each successful concatenation. + +In the following sections, we will elaborate on the ways of implementing such an object on both the server and the client side. + +Implementing the Concatenator example using basic sdbus-c++ API layer +--------------------------------------------------------------------- + +In the basic API layer, we already have abstractions for D-Bus connections, objects and object proxies, with which we can interact via +interfaces. However, we still work with the concept of messages. To issue a method call for example, we have to go through several steps: +we have to create a method call message first, serialize method arguments into the message, and send the message at last. We get the reply +message (if applicable) in return, so we have to deserialize the return values from it manually. + +Overloaded versions of C++ insertion/extraction operators are used for serialization/deserialization. That makes the client code much simpler. + +### Server side + +```c++ +#include +#include +#include + +// Yeah, global variable is ugly, but this is just an example and we want to access +// the concatenator instance from within the concatenate method handler to be able +// to emit signals. +sdbus::IObject* g_concatenator{}; + +void concatenate(sdbus::Message& msg, sdbus::Message& reply) +{ + // Deserialize the collection of numbers from the message + std::vector numbers; + msg >> numbers; + + // Deserialize separator from the message + std::string separator; + msg >> separator; + + // Return error if there are no numbers in the collection + if (numbers.empty()) + throw sdbus::Error("org.sdbuscpp.Concatenator.Error", "No numbers provided"); + + std::string result; + for (auto number : numbers) + { + result += (result.empty() ? std::string() : separator) + std::to_string(number); + } + + // Serialize resulting string to the reply + reply << result; + + // Emit 'concatenated' signal + const char* interfaceName = "org.sdbuscpp.Concatenator"; + auto signalMsg = g_concatenator->createSignal(interfaceName, "concatenated"); + signalMsg << result; + g_concatenator->emitSignal(signalMsg); +} + +int main(int argc, char *argv[]) +{ + // Create D-Bus connection and requests name on it. + const char* serviceName = "org.sdbuscpp.concatenator"; + auto connection = sdbus::createConnection(serviceName); + + // Create concatenator D-Bus object. + const char* objectPath = "/org/sdbuscpp/concatenator"; + auto concatenator = sdbus::createObject(*connection, objectPath); + + g_concatenator = concatenator.get(); + + // Register D-Bus methods and signals on the concatenator object, and exports the object. + const char* interfaceName = "org.sdbuscpp.Concatenator"; + concatenator->registerMethod(interfaceName, "concatenate", "ais", "s", &concatenate); + concatenator->registerSignal(interfaceName, "concatenated", "s"); + concatenator->finishRegistration(); + + // Run the loop on the connection. + connection->enterProcessingLoop(); +} +``` + +### Client side + +```c++ +#include +#include +#include +#include +#include + +void onConcatenated(sdbus::Message& signalMsg) +{ + std::string concatenatedString; + msg >> concatenatedString; + + std::cout << "Received signal with concatenated string " << concatenatedString << std::endl; +} + +int main(int argc, char *argv[]) +{ + // Create proxy object for the concatenator object on the server side + const char* destinationName = "org.sdbuscpp.concatenator"; + const char* objectPath = "/org/sdbuscpp/concatenator"; + auto concatenatorProxy = sdbus::createObjectProxy(destinationName, objectPath); + + // Let's subscribe for the 'concatenated' signals + const char* interfaceName = "org.sdbuscpp.Concatenator"; + concatenatorProxy->registerSignalHandler(interfaceName, "concatenated", &onConcatenated); + concatenatorProxy->finishRegistration(); + + std::vector numbers = {1, 2, 3}; + std::string separator = ":"; + + // Invoke concatenate on given interface of the object + { + auto method = concatenatorProxy->createMethodCall(interfaceName, "concatenate"); + method << numbers << separator; + auto reply = concatenatorProxy->callMethod(method); + std::string result; + reply >> result; + assert(result == "1:2:3"); + } + + // Invoke concatenate again, this time with no numbers and we shall get an error + { + auto method = concatenatorProxy->createMethodCall(interfaceName, "concatenate"); + method << std::vector() << separator; + try + { + auto reply = concatenatorProxy->callMethod(method); + assert(false); + } + catch(const sdbus::Error& e) + { + std::cerr << "Got concatenate error " << e.getName() << " with message " << e.getMessage() << std::endl; + } + } + + // Give sufficient time to receive 'concatenated' signal from the first concatenate invocation + sleep(1); + + return 0; +} +``` + +The object proxy is created without explicitly providing a D-Bus connection as an argument in its factory function. In that case, the proxy +will create its own connection and listen to signals on it in a separate thread. That means the `onConcatenated` method is invoked always +in the context of a thread different from the main thread. + +Implementing the Concatenator example using convenience sdbus-c++ API layer +--------------------------------------------------------------------------- + +One of the major sdbus-c++ design goals is to make the sdbus-c++ API easy to use correctly, and hard to use incorrectly. + +The convenience API layer abstracts the concept of underlying D-Bus messages away completely. It abstracts away D-Bus signatures. And it tries +to provide an interface that uses small, focused functions, with one or zero parameters, to form a chained function statement that reads like +a sentence to a human reading the code. To achieve that, sdbus-c++ utilizes the power of the C++ type system, so many issues are resolved at +compile time, and the run-time performance cost compared to the basic layer is close to zero. + +Thus, in the end of the day, the code written using the convenience API is: +- more expressive, +- closer to the abstraction level of the problem being solved, +- shorter, +- almost as fast (if not equally fast) as one written using the basic API layer. + +Rather than *how*, the code written using this layer expresses *what* it does. Let's look at code samples to see if you agree :) + +### Server side + +```c++ +#include +#include +#include + +// Yeah, global variable is ugly, but this is just an example and we want to access +// the concatenator instance from within the concatenate method handler to be able +// to emit signals. +sdbus::IObject* g_concatenator{}; + +std::string concatenate(const std::vector numbers, const std::string& separator) +{ + // Return error if there are no numbers in the collection + if (numbers.empty()) + throw sdbus::Error("org.sdbuscpp.Concatenator.Error", "No numbers provided"); + + std::string result; + for (auto number : numbers) + { + result += (result.empty() ? std::string() : separator) + std::to_string(number); + } + + // Emit 'concatenated' signal + const char* interfaceName = "org.sdbuscpp.Concatenator"; + g_concatenator->emitSignal("concatenated").onInterface(interfaceName).withArguments(result); + + return result; +} + +int main(int argc, char *argv[]) +{ + // Create D-Bus connection and requests name on it. + const char* serviceName = "org.sdbuscpp.concatenator"; + auto connection = sdbus::createConnection(serviceName); + + // Create concatenator D-Bus object. + const char* objectPath = "/org/sdbuscpp/concatenator"; + auto concatenator = sdbus::createObject(*connection, objectPath); + + g_concatenator = concatenator.get(); + + // Register D-Bus methods and signals on the concatenator object, and exports the object. + const char* interfaceName = "org.sdbuscpp.Concatenator"; + concatenator->registerMethod("concatenate").onInterface(interfaceName).implementedAs(&concatenate); + concatenator->registerSignal("concatenated").onInterface(interfaceName).withParameters(); + concatenator->finishRegistration(); + + // Run the loop on the connection. + connection->enterProcessingLoop(); +} +``` + +### Client side + +```c++ +#include +#include +#include +#include +#include + +void onConcatenated(const std::string& concatenatedString) +{ + std::cout << "Received signal with concatenated string " << concatenatedString << std::endl; +} + +int main(int argc, char *argv[]) +{ + // Create proxy object for the concatenator object on the server side + const char* destinationName = "org.sdbuscpp.concatenator"; + const char* objectPath = "/org/sdbuscpp/concatenator"; + auto concatenatorProxy = sdbus::createObjectProxy(destinationName, objectPath); + + // Let's subscribe for the 'concatenated' signals + const char* interfaceName = "org.sdbuscpp.Concatenator"; + concatenatorProxy->uponSignal("concatenated").onInterface(interfaceName).call([this](const std::string& str){ onConcatenated(str); }); + concatenatorProxy->finishRegistration(); + + std::vector numbers = {1, 2, 3}; + std::string separator = ":"; + + // Invoke concatenate on given interface of the object + { + std::string concatenatedString; + concatenatorProxy->callMethod("concatenate").onInterface(interfaceName).withArguments(numbers, separator).storeResultsTo(concatenatedString); + assert(concatenatedString == "1:2:3"); + } + + // Invoke concatenate again, this time with no numbers and we shall get an error + { + try + { + concatenatorProxy->callMethod("concatenate").onInterface(interfaceName).withArguments(std::vector(), separator); + assert(false); + } + catch(const sdbus::Error& e) + { + std::cerr << "Got concatenate error " << e.getName() << " with message " << e.getMessage() << std::endl; + } + } + + // Give sufficient time to receive 'concatenated' signal from the first concatenate invocation + sleep(1); + + return 0; +} +``` + +Several lines of code have shrunk into one-liners when registering/calling methods or signals. D-Bus signatures and the serialization/deserialization +of arguments from the messages is generated at compile time, by introspecting signatures of provided callbacks or deducing types of provided arguments. + +Implementing the Concatenator example using sdbus-c++-generated stubs +--------------------------------------------------------------------- + +sdbus-c++ ships with the native stub generator tool called sdbuscpp-xml2cpp. The tool is very similar to dbusxx-xml2cpp tool that comes from +dbus-c++ project. + +The generator tool takes D-Bus XML IDL description of D-Bus interfaces on its input, and can be instructed to generate one or both of these: +an adaptor header file for use at server side, and a proxy header file for use at client side. Like this: + +```bash +sdbuscpp-xml2cpp database-bindings.xml --adaptor=database-server-glue.h --proxy=database-client-glue.h +``` + +The adaptor header file contains classes that can be used to implement described interfaces. The proxy header file contains classes that can be used +to make calls to remote objects. + +### XML description of the Concatenator interface + +As an example, let's look at an XML description of our Concatenator's interfaces. + +```xml + + + + + + + + + + + + + + +``` + +After running this through the stubs generator, we get the stub code that is described in the following two subsections. + +### concatenator-server-glue.h + +There is specific class for each interface in the XML IDL file. The class is de facto an interface which shall be implemented by inheriting from it. +The class' constructor takes care of registering all methods, signals and properties. For each D-Bus method there is a pure virtual member function. +These pure virtual functions must be implemented in the child class. For each signal, there is a public function member that emits this signal. + +```cpp +/* + * This file was automatically generated by sdbuscpp-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__concatenator_server_glue_h__adaptor__H__ +#define __sdbuscpp__concatenator_server_glue_h__adaptor__H__ + +#include +#include +#include + +namespace org { +namespace sdbuscpp { + +class Concatenator_adaptor +{ +public: + static constexpr const char* interfaceName = "org.sdbuscpp.Concatenator"; + +protected: + Concatenator_adaptor(sdbus::IObject& object) + : object_(object) + { + object_.registerMethod("concatenate").onInterface(interfaceName).implementedAs([this](const std::vector& numbers, const std::string& separator){ return this->concatenate(numbers, separator); }); + object_.registerSignal("concatenated").onInterface(interfaceName).withParameters(); + } + +public: + void concatenated(const std::string& concatenatedString) + { + object_.emitSignal("concatenated").onInterface(interfaceName).withArguments(concatenatedString); + } + +private: + virtual std::string concatenate(const std::vector& numbers, const std::string& separator) = 0; + +private: + sdbus::IObject& object_; +}; + +}} // namespaces + +#endif +``` + +### concatenator-client-glue.h + +Analogously to the adaptor classes described above, there is specific class for each interface in the XML IDL file. The class is de facto a proxy +to the concrete interface of a remote object. For each D-Bus signal there is a pure virtual member function whose body must be provided in a child +class. For each method, there is a public function member that calls the method remotely. + +```cpp +/* + * This file was automatically generated by sdbuscpp-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__concatenator_client_glue_h__proxy__H__ +#define __sdbuscpp__concatenator_client_glue_h__proxy__H__ + +#include +#include +#include + +namespace org { +namespace sdbuscpp { + +class Concatenator_proxy +{ +public: + static constexpr const char* interfaceName = "org.sdbuscpp.Concatenator"; + +protected: + Concatenator_proxy(sdbus::IObjectProxy& object) + : object_(object) + { + object_.uponSignal("concatenated").onInterface(interfaceName).call([this](const std::string& concatenatedString){ this->onConcatenated(concatenatedString); }); + } + + virtual void onConcatenated(const std::string& concatenatedString) = 0; + +public: + std::string concatenate(const std::vector& numbers, const std::string& separator) + { + std::string result; + object_.callMethod("concatenate").onInterface(interfaceName).withArguments(numbers, separator).storeResultsTo(result); + return result; + } + +private: + sdbus::IObjectProxy& object_; +}; + +}} // namespaces + +#endif +``` + +### Providing server implementation based on generated adaptors + +To implement a D-Bus object that implements all its D-Bus interfaces, we shall create a class representing the object that inherits from all +corresponding `*_adaptor` classes and implements all pure virtual member functions. Specifically, the object class shall inherit from the +`Interfaces` template class, the template arguments of which are individual adaptor classes. The `Interfaces` is just a convenience class that +hides a few boiler-plate details. For example, in its constructor, it creates an `Object` instance, it takes care of proper initialization of +all adaptor superclasses, and exports the object finally. + +```cpp +#include +#include "concatenator-server-glue.h" + +class Concatenator : public sdbus::Interfaces +{ +public: + Concatenator(sdbus::IConnection& connection, std::string objectPath) + : sdbus::Interfaces(connection, std::move(objectPath)) + { + } + +protected: + std::string concatenate(const std::vector& numbers, const std::string& separator) override + { + // Return error if there are no numbers in the collection + if (numbers.empty()) + throw sdbus::Error("org.sdbuscpp.Concatenator.Error", "No numbers provided"); + + // Concatenate the numbers + std::string result; + for (auto number : numbers) + { + result += (result.empty() ? std::string() : separator) + std::to_string(number); + } + + // Emit the 'concatenated' signal with the resulting string + concatenated(result); + + // Return the resulting string + return result; + } +}; +``` + +That's it. We now have an implementation of a D-Bus object implementing `org.sdbuscpp.Concatenator` interface. Let's now create a service +publishing the object. + +```cpp +#include "Concatenator.h" + +int main(int argc, char *argv[]) +{ + // Create D-Bus connection and requests name on it. + const char* serviceName = "org.sdbuscpp.concatenator"; + auto connection = sdbus::createConnection(serviceName); + + // Create concatenator D-Bus object. + const char* objectPath = "/org/sdbuscpp/concatenator"; + Concatenator concatenator(*connection, objectPath); + + // Run the loop on the connection. + connection->enterProcessingLoop(); +} +``` + +It's that simple! + +### Providing client implementation based on generated proxies + +To implement a proxy for a remote D-Bus object, we shall create a class representing the object proxy that inherits from all corresponding +`*_proxy` classes and -- if applicable -- implements all pure virtual member functions. Specifically, the object proxy class shall inherit +from the `ProxyInterfaces` template class. As its template arguments we shall provide all proxy classes. The `ProxyInterfaces` is just a +convenience class that hides a few boiler-plate details. For example, in its constructor, it creates an `ObjectProxy` instance, and it takes +care of proper initialization of all proxy superclasses. + +```cpp +#include +#include "concatenator-client-glue.h" + +class ConcatenatorProxy : public sdbus::ProxyInterfaces +{ +public: + ConcatenatorProxy(std::string destination, std::string objectPath) + : sdbus::ProxyInterfaces(std::move(destination), std::move(objectPath)) + { + } + +protected: + void onConcatenated(const std::string& concatenatedString) override + { + std::cout << "Received signal with concatenated string " << concatenatedString << std::endl; + } +}; +``` + +Now let's use this proxy to make remote calls and listen to signals in a real application. + +```cpp +#include "ConcatenatorProxy.h" + +int main(int argc, char *argv[]) +{ + // Create proxy object for the concatenator object on the server side + const char* destinationName = "org.sdbuscpp.concatenator"; + const char* objectPath = "/org/sdbuscpp/concatenator"; + ConcatenatorProxy concatenatorProxy(destinationName, objectPath); + + std::vector numbers = {1, 2, 3}; + std::string separator = ":"; + + // Invoke concatenate with some numbers + auto concatenatedString = concatenatorProxy.concatenate(numbers, separator); + assert(concatenatedString == "1:2:3"); + + // Invoke concatenate again, this time with no numbers and we shall get an error + try + { + auto concatenatedString = concatenatorProxy.concatenate(std::vector(), separator); + assert(false); + } + catch(const sdbus::Error& e) + { + std::cerr << "Got concatenate error " << e.getName() << " with message " << e.getMessage() << std::endl; + } + + // Give sufficient time to receive 'concatenated' signal from the first concatenate invocation + sleep(1); + + return 0; +} +``` + +Using D-Bus properties +---------------------- + +Defining and working with D-Bus properties using XML description is quite easy. + +### Defining a property in the XML + +A property element has no arg child element. It just has the attributes name, type and access, which are all mandatory. The access attribute allows the values ‘readwrite’, ‘read’, and ‘write’. + +An example of a read-write property `status`: + +```xml + + + + + + + + + +``` + +### Generated stubs + +This is how generated adaptor and proxy classes would look like with the read-write `status` property. The adaptor: + +```cpp +class PropertyProvider_adaptor +{ + /*...*/ + +public: + PropertyProvider_adaptor(sdbus::IObject& object) + : object_(object) + { + object_.registerProperty("status").onInterface(INTERFACE_NAME).withGetter([this](){ return this->status(); }).withSetter([this](const uint32_t& value){ this->status(value); }); + } + +private: + // property getter + virtual uint32_t status() = 0; + // property setter + virtual void status(const uint32_t& value) = 0; + + /*...*/ +}; +#endif +``` + +The proxy: + +```cpp +class PropertyProvider_proxy +{ + /*...*/ + +public: + // getting the property value + uint32_t status() + { + return object_.getProperty("status").onInterface(INTERFACE_NAME); + } + + // setting the property value + void status(const uint32_t& value) + { + object_.setProperty("status").onInterface(INTERFACE_NAME).toValue(value); + } + + /*...*/ +}; +``` + +When implementing the adaptor, we simply need to provide the body for `status` getter and setter method by overriding them. Then in the proxy, we just call them. + +Conclusion +---------- + +There is no conclusion. Happy journeys by D-Bus with sdbus-c++! diff --git a/include/Makefile.am b/include/Makefile.am new file mode 100644 index 0000000..cc92c51 --- /dev/null +++ b/include/Makefile.am @@ -0,0 +1,23 @@ + +# Distribution +# Headers will be installed to $(includedir)/sdbus-c++ directory + +HEADER_DIR = sdbus-c++ +libsdbuscppdir = $(includedir)/$(HEADER_DIR) +libsdbuscpp_HEADERS = \ + $(HEADER_DIR)/ConvenienceClasses.h \ + $(HEADER_DIR)/ConvenienceClasses.inl \ + $(HEADER_DIR)/Error.h \ + $(HEADER_DIR)/IConnection.h \ + $(HEADER_DIR)/Interfaces.h \ + $(HEADER_DIR)/Introspection.h \ + $(HEADER_DIR)/IObject.h \ + $(HEADER_DIR)/IObjectProxy.h \ + $(HEADER_DIR)/Message.h \ + $(HEADER_DIR)/Types.h \ + $(HEADER_DIR)/TypeTraits.h \ + $(HEADER_DIR)/sdbus-c++.h + +EXTRA_DIST = ($libsdbuscpp_HEADERS) + +DISTCLEANFILES = Makefile Makefile.in diff --git a/include/sdbus-c++/ConvenienceClasses.h b/include/sdbus-c++/ConvenienceClasses.h new file mode 100644 index 0000000..25fd742 --- /dev/null +++ b/include/sdbus-c++/ConvenienceClasses.h @@ -0,0 +1,169 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file ConvenienceClasses.h + * + * Created on: Jan 19, 2017 + * Project: sdbus-c++ + * Description: High-level D-Bus IPC C++ library based on sd-bus + * + * This file is part of sdbus-c++. + * + * sdbus-c++ is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * sdbus-c++ is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with sdbus-c++. If not, see . + */ + +#ifndef SDBUS_CXX_CONVENIENCECLASSES_H_ +#define SDBUS_CXX_CONVENIENCECLASSES_H_ + +#include +#include + +// Forward declarations +namespace sdbus { + class IObject; + class IObjectProxy; + class Variant; +} + +namespace sdbus { + + class MethodRegistrator + { + public: + MethodRegistrator(IObject& object, const std::string& methodName); + MethodRegistrator& onInterface(const std::string& interfaceName); + template void implementedAs(_Function&& callback); + + private: + IObject& object_; + const std::string& methodName_; + std::string interfaceName_; + }; + + class SignalRegistrator + { + public: + SignalRegistrator(IObject& object, const std::string& signalName); + SignalRegistrator(SignalRegistrator&& other) = default; + SignalRegistrator& operator=(SignalRegistrator&& other) = default; + ~SignalRegistrator() noexcept(false); + SignalRegistrator& onInterface(std::string interfaceName); + template void withParameters(); + + private: + IObject& object_; + const std::string& signalName_; + std::string interfaceName_; + std::string signalSignature_; + 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& operator=(PropertyRegistrator&& other) = default; + ~PropertyRegistrator() noexcept(false); + PropertyRegistrator& onInterface(const std::string& interfaceName); + template PropertyRegistrator& withGetter(_Function&& callback); + template PropertyRegistrator& withSetter(_Function&& callback); + + private: + IObject& object_; + const std::string& propertyName_; + std::string interfaceName_; + std::string propertySignature_; + property_get_callback getter_; + property_set_callback setter_; + int exceptions_{}; // Number of active exceptions when PropertyRegistrator is constructed + }; + + class SignalEmitter + { + public: + SignalEmitter(IObject& object, const std::string& signalName); + SignalEmitter(SignalEmitter&& other) = default; + SignalEmitter& operator=(SignalEmitter&& other) = default; + ~SignalEmitter() noexcept(false); + SignalEmitter& onInterface(const std::string& interfaceName); + template void withArguments(_Args&&... args); + + private: + IObject& object_; + const std::string& signalName_; + Message signal_; + int exceptions_{}; // Number of active exceptions when SignalEmitter is constructed + }; + + class MethodInvoker + { + public: + MethodInvoker(IObjectProxy& objectProxy, const std::string& methodName); + MethodInvoker(MethodInvoker&& other) = default; + MethodInvoker& operator=(MethodInvoker&& other) = default; + ~MethodInvoker() noexcept(false); + + MethodInvoker& onInterface(const std::string& interfaceName); + template MethodInvoker& withArguments(_Args&&... args); + template void storeResultsTo(_Args&... args); + + private: + IObjectProxy& objectProxy_; + const std::string& methodName_; + Message method_; + int exceptions_{}; // Number of active exceptions when MethodInvoker is constructed + bool methodCalled_{}; + }; + + class SignalSubscriber + { + public: + SignalSubscriber(IObjectProxy& objectProxy, const std::string& signalName); + SignalSubscriber& onInterface(const std::string& interfaceName); + template void call(_Function&& callback); + + private: + IObjectProxy& objectProxy_; + std::string signalName_; + std::string interfaceName_; + }; + + class PropertyGetter + { + public: + PropertyGetter(IObjectProxy& objectProxy, const std::string& propertyName); + sdbus::Variant onInterface(const std::string& interfaceName); + + private: + IObjectProxy& objectProxy_; + std::string propertyName_; + }; + + class PropertySetter + { + public: + PropertySetter(IObjectProxy& objectProxy, const std::string& propertyName); + PropertySetter& onInterface(const std::string& interfaceName); + template void toValue(const _Value& value); + + private: + IObjectProxy& objectProxy_; + const std::string& propertyName_; + std::string interfaceName_; + }; + +} + +#endif /* SDBUS_CXX_CONVENIENCECLASSES_H_ */ diff --git a/include/sdbus-c++/ConvenienceClasses.inl b/include/sdbus-c++/ConvenienceClasses.inl new file mode 100644 index 0000000..84b6876 --- /dev/null +++ b/include/sdbus-c++/ConvenienceClasses.inl @@ -0,0 +1,401 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file ConvenienceClasses.inl + * + * Created on: Dec 19, 2016 + * 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 . + */ + +#ifndef SDBUS_CPP_CONVENIENCECLASSES_INL_ +#define SDBUS_CPP_CONVENIENCECLASSES_INL_ + +#include +#include +#include +#include +#include +#include +#include +#include +/*#include */ + +namespace sdbus { + + inline MethodRegistrator::MethodRegistrator(IObject& object, const std::string& methodName) + : object_(object) + , methodName_(methodName) + { + } + + inline MethodRegistrator& MethodRegistrator::onInterface(const std::string& interfaceName) + { + interfaceName_ = interfaceName; + + return *this; + } + + template + inline void MethodRegistrator::implementedAs(_Function&& callback) + { + SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when registering a DBus method", EINVAL); + + object_.registerMethod( interfaceName_ + , methodName_ + , signature_of_function_input_arguments<_Function>::str() + , signature_of_function_output_arguments<_Function>::str() + , [callback = std::forward<_Function>(callback)](Message& msg, Message& reply) + { + // 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 + msg >> inputArgs; + + // Invoke callback with input arguments from the tuple. + // For callbacks returning a non-void value, `apply' also returns that value. + // For callbacks returning void, `apply' returns an empty tuple. + auto ret = apply(callback, inputArgs); // We don't yet have C++17's std::apply :-( + + // The return value is stored to the reply message. + // In case of void functions, ret is an empty tuple and thus nothing is stored. + reply << ret; + }); + } + + + // Moved into the library to isolate from C++17 dependency + /* + inline SignalRegistrator::SignalRegistrator(IObject& object, std::string signalName) + : object_(object) + , signalName_(std::move(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; + + if (interfaceName_.empty()) + throw sdbus::Exception("DBus interface not specified when registering a DBus signal"); + + // registerSignal() can throw. But as the SignalRegistrator shall always be used as an unnamed, + // temporary object, i.e. not as a stack-allocated object, the double-exception situation + // shall never happen. I.e. it should not happen that this destructor is directly called + // in the stack-unwinding process of another flying exception (which would lead to immediate + // termination). It can be called indirectly in the destructor of another object, but that's + // fine and safe provided that the caller catches exceptions thrown from here. + // Therefore, we can allow registerSignal() to throw even if we are in the destructor. + // Bottomline is, to be on the safe side, the caller must take care of catching and reacting + // to the exception thrown from here if the caller is a destructor itself. + object_.registerSignal(interfaceName_, signalName_, signalSignature_); + } + */ + + inline SignalRegistrator& SignalRegistrator::onInterface(std::string interfaceName) + { + interfaceName_ = std::move(interfaceName); + + return *this; + } + + template + inline void SignalRegistrator::withParameters() + { + signalSignature_ = signature_of_function_input_arguments::str(); + } + + + // Moved into the library to isolate from C++17 dependency + /* + inline PropertyRegistrator::PropertyRegistrator(IObject& object, std::string propertyName) + : object_(object) + , propertyName_(std::move(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; + + SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when registering a DBus property", EINVAL); + + // registerProperty() can throw. But as the PropertyRegistrator shall always be used as an unnamed, + // temporary object, i.e. not as a stack-allocated object, the double-exception situation + // shall never happen. I.e. it should not happen that this destructor is directly called + // in the stack-unwinding process of another flying exception (which would lead to immediate + // termination). It can be called indirectly in the destructor of another object, but that's + // fine and safe provided that the caller catches exceptions thrown from here. + // Therefore, we can allow registerProperty() to throw even if we are in the destructor. + // Bottomline is, to be on the safe side, the caller must take care of catching and reacting + // to the exception thrown from here if the caller is a destructor itself. + object_.registerProperty( std::move(interfaceName_) + , std::move(propertyName_) + , std::move(propertySignature_) + , std::move(getter_) + , std::move(setter_) ); + } + */ + + inline PropertyRegistrator& PropertyRegistrator::onInterface(const std::string& interfaceName) + { + interfaceName_ = interfaceName; + + return *this; + } + + template + 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>::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)](Message& msg) + { + // Get the propety value and serialize it into the message + msg << callback(); + }; + + return *this; + } + + template + 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>::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)](Message& msg) + { + // Default-construct property value + using property_type = function_argument_t<_Function, 0>; + std::decay_t property; + + // Deserialize property value from the message + msg >> property; + + // Invoke setter with the value + callback(property); + }; + + return *this; + } + + + // Moved into the library to isolate from C++17 dependency + /* + inline SignalEmitter::SignalEmitter(IObject& object, const std::string& signalName) + : object_(object) + , signalName_(signalName) + , exceptions_(std::uncaught_exceptions()) + { + } + + inline SignalEmitter::~SignalEmitter() noexcept(false) // since C++11, destructors must + { // explicitly be allowed to throw + // Don't emit the signal if SignalEmitter threw an exception in one of its methods + if (std::uncaught_exceptions() != exceptions_) + return; + + if (!signal_.isValid()) + throw sdbus::Exception("DBus interface not specified when emitting a DBus signal"); + + // emitSignal() can throw. But as the SignalEmitter shall always be used as an unnamed, + // temporary object, i.e. not as a stack-allocated object, the double-exception situation + // shall never happen. I.e. it should not happen that this destructor is directly called + // in the stack-unwinding process of another flying exception (which would lead to immediate + // termination). It can be called indirectly in the destructor of another object, but that's + // fine and safe provided that the caller catches exceptions thrown from here. + // Therefore, we can allow emitSignal() to throw even if we are in the destructor. + // Bottomline is, to be on the safe side, the caller must take care of catching and reacting + // to the exception thrown from here if the caller is a destructor itself. + object_.emitSignal(signal_); + } + */ + + inline SignalEmitter& SignalEmitter::onInterface(const std::string& interfaceName) + { + signal_ = object_.createSignal(interfaceName, signalName_); + + return *this; + } + + template + inline void SignalEmitter::withArguments(_Args&&... args) + { + SDBUS_THROW_ERROR_IF(!signal_.isValid(), "DBus interface not specified when emitting a DBus signal", EINVAL); + + detail::serialize_pack(signal_, std::forward<_Args>(args)...); + } + + + // Moved into the library to isolate from C++17 dependency + /* + inline MethodInvoker::MethodInvoker(IObjectProxy& objectProxy, const std::string& methodName) + : objectProxy_(objectProxy) + , methodName_(methodName) + , exceptions_(std::uncaught_exceptions()) + { + } + + inline MethodInvoker::~MethodInvoker() noexcept(false) // since C++11, destructors must + { // explicitly be allowed to throw + // Don't call the method if it has been called already or if MethodInvoker + // threw an exception in one of its methods + if (methodCalled_ || std::uncaught_exceptions() != exceptions_) + return; + + if (!method_.isValid()) + throw sdbus::Exception("DBus interface not specified when calling a DBus method"); + + // callMethod() can throw. But as the MethodInvoker shall always be used as an unnamed, + // temporary object, i.e. not as a stack-allocated object, the double-exception situation + // shall never happen. I.e. it should not happen that this destructor is directly called + // in the stack-unwinding process of another flying exception (which would lead to immediate + // termination). It can be called indirectly in the destructor of another object, but that's + // fine and safe provided that the caller catches exceptions thrown from here. + // Therefore, we can allow callMethod() to throw even if we are in the destructor. + // Bottomline is, to be on the safe side, the caller must take care of catching and reacting + // to the exception thrown from here if the caller is a destructor itself. + objectProxy_.callMethod(method_); + } + */ + + inline MethodInvoker& MethodInvoker::onInterface(const std::string& interfaceName) + { + method_ = objectProxy_.createMethodCall(interfaceName, methodName_); + + return *this; + } + + template + inline MethodInvoker& MethodInvoker::withArguments(_Args&&... args) + { + SDBUS_THROW_ERROR_IF(!method_.isValid(), "DBus interface not specified when calling a DBus method", EINVAL); + + detail::serialize_pack(method_, std::forward<_Args>(args)...); + + return *this; + } + + template + inline void MethodInvoker::storeResultsTo(_Args&... args) + { + SDBUS_THROW_ERROR_IF(!method_.isValid(), "DBus interface not specified when calling a DBus method", EINVAL); + + auto reply = objectProxy_.callMethod(method_); + methodCalled_ = true; + + detail::deserialize_pack(reply, args...); + } + + + inline SignalSubscriber::SignalSubscriber(IObjectProxy& objectProxy, const std::string& signalName) + : objectProxy_(objectProxy) + , signalName_(signalName) + { + } + + inline SignalSubscriber& SignalSubscriber::onInterface(const std::string& interfaceName) + { + interfaceName_ = interfaceName; + + return *this; + } + + template + inline void SignalSubscriber::call(_Function&& callback) + { + SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when subscribing to a signal", EINVAL); + + objectProxy_.registerSignalHandler( interfaceName_ + , signalName_ + , [callback = std::forward<_Function>(callback)](Message& signal) + { + // Create a tuple of callback input arguments' types, which will be used + // as a storage for the argument values deserialized from the signal message. + tuple_of_function_input_arg_types_t<_Function> signalArgs; + + // Deserialize input arguments from the signal message into the tuple + signal >> signalArgs; + + // Invoke callback with input arguments from the tuple. + apply(callback, signalArgs); // We don't yet have C++17's std::apply :-( + }); + } + + + inline PropertyGetter::PropertyGetter(IObjectProxy& objectProxy, const std::string& propertyName) + : objectProxy_(objectProxy) + , propertyName_(propertyName) + { + } + + inline sdbus::Variant PropertyGetter::onInterface(const std::string& interfaceName) + { + sdbus::Variant var; + objectProxy_ + .callMethod("Get") + .onInterface("org.freedesktop.DBus.Properties") + .withArguments(interfaceName, propertyName_) + .storeResultsTo(var); + return var; + } + + + inline PropertySetter::PropertySetter(IObjectProxy& objectProxy, const std::string& propertyName) + : objectProxy_(objectProxy) + , propertyName_(propertyName) + { + } + + inline PropertySetter& PropertySetter::onInterface(const std::string& interfaceName) + { + interfaceName_ = interfaceName; + + return *this; + } + + template + inline void PropertySetter::toValue(const _Value& value) + { + SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when setting a property", EINVAL); + + objectProxy_ + .callMethod("Set") + .onInterface("org.freedesktop.DBus.Properties") + .withArguments(interfaceName_, propertyName_, sdbus::Variant{value}); + } + +} + +#endif /* SDBUS_CPP_CONVENIENCECLASSES_INL_ */ diff --git a/include/sdbus-c++/Error.h b/include/sdbus-c++/Error.h new file mode 100755 index 0000000..efdcd41 --- /dev/null +++ b/include/sdbus-c++/Error.h @@ -0,0 +1,76 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file ConvenienceClasses.h + * + * Created on: Nov 8, 2016 + * 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 . + */ + +#ifndef SDBUS_CXX_ERROR_H_ +#define SDBUS_CXX_ERROR_H_ + +#include + +namespace sdbus { + + /********************************************//** + * @class Error + * + * Represents a common sdbus-c++ exception. + * + ***********************************************/ + class Error + : public std::runtime_error + { + public: + Error(const std::string& name, const std::string& message) + : std::runtime_error("[" + name + "] " + message) + , name_(name) + , message_(message) + { + } + + const std::string& getName() const + { + return name_; + } + + const std::string& getMessage() const + { + return message_; + } + + private: + std::string name_; + std::string message_; + }; + + sdbus::Error createError(int errNo, const std::string& customMsg); +} + +#define SDBUS_THROW_ERROR(_MSG, _ERRNO) \ + throw sdbus::createError((_ERRNO), (_MSG)) \ + /**/ + +#define SDBUS_THROW_ERROR_IF(_COND, _MSG, _ERRNO) \ + if (_COND) SDBUS_THROW_ERROR((_MSG), (_ERRNO)) \ + /**/ + +#endif /* SDBUS_CXX_ERROR_H_ */ diff --git a/include/sdbus-c++/IConnection.h b/include/sdbus-c++/IConnection.h new file mode 100644 index 0000000..de5d3a6 --- /dev/null +++ b/include/sdbus-c++/IConnection.h @@ -0,0 +1,157 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file IConnection.h + * + * Created on: Nov 8, 2016 + * 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 . + */ + +#ifndef SDBUS_CXX_ICONNECTION_H_ +#define SDBUS_CXX_ICONNECTION_H_ + +//#include +#include +#include + +namespace sdbus { + + /********************************************//** + * @class IConnection + * + * An interface to D-Bus bus connection. Incorporates implementation + * of both synchronous and asynchronous processing loop. + * + * All methods throw @sdbus::Error in case of failure. The class is + * thread-aware, but not thread-safe. + * + ***********************************************/ + class IConnection + { + public: + /*! + * @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 Enters the D-Bus processing loop + * + * The incoming D-Bus messages are processed in the loop. The method + * blocks indefinitely, until unblocked via @leaveProcessingLoop. + * + * @throws sdbus::Error in case of failure + */ + virtual void enterProcessingLoop() = 0; + + /*! + * @brief Enters the D-Bus processing loop in a separate thread + * + * The same as @enterProcessingLoop, except that it doesn't block + * because it runs the loop in a separate thread managed internally. + */ + virtual void enterProcessingLoopAsync() = 0; + + /*! + * @brief Leaves the D-Bus processing loop + * + * Ends the previously started processing loop. + * + * @throws sdbus::Error in case of failure + */ + virtual void leaveProcessingLoop() = 0; + + inline virtual ~IConnection() = 0; + }; + + IConnection::~IConnection() {} + + /*! + * @brief Creates/opens D-Bus system connection + * + * @return Connection instance + * + * @throws sdbus::Error in case of failure + */ + std::unique_ptr createConnection(); + + /*! + * @brief Creates/opens D-Bus system 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 + */ + std::unique_ptr createConnection(const std::string& name); + + /*! + * @brief Creates/opens D-Bus system connection + * + * @return Connection instance + * + * @throws sdbus::Error in case of failure + */ + std::unique_ptr createSystemBusConnection(); + + /*! + * @brief Creates/opens D-Bus system 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 + */ + std::unique_ptr createSystemBusConnection(const std::string& name); + + /*! + * @brief Creates/opens D-Bus session connection + * + * @return Connection instance + * + * @throws sdbus::Error in case of failure + */ + std::unique_ptr createSessionBusConnection(); + + /*! + * @brief Creates/opens D-Bus session 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 + */ + std::unique_ptr createSessionBusConnection(const std::string& name); + +} + +#endif /* SDBUS_CXX_ICONNECTION_H_ */ diff --git a/include/sdbus-c++/IObject.h b/include/sdbus-c++/IObject.h new file mode 100755 index 0000000..75ccd7c --- /dev/null +++ b/include/sdbus-c++/IObject.h @@ -0,0 +1,279 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file IObject.h + * + * Created on: Nov 8, 2016 + * 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 . + */ + +#ifndef SDBUS_CXX_IOBJECT_H_ +#define SDBUS_CXX_IOBJECT_H_ + +#include +#include +#include +#include +#include + +// Forward declarations +namespace sdbus { + class Message; + class IConnection; +} + +namespace sdbus { + + /********************************************//** + * @class IObject + * + * An interface to D-Bus object. Provides API for registration + * of methods, signals, properties, and for emitting signals. + * + * All methods throw @c sdbus::Error in case of failure. The class is + * thread-aware, but not thread-safe. + * + ***********************************************/ + class IObject + { + public: + /*! + * @brief Registers method that the object will provide on D-Bus + * + * @param[in] interfaceName Name of an interface that the method will belong to + * @param[in] methodName Name of the method + * @param[in] inputSignature D-Bus signature of method input parameters + * @param[in] outputSignature D-Bus signature of method output parameters + * @param[in] methodCallback Callback that implements the body of the method + * + * @throws sdbus::Error in case of failure + */ + virtual void registerMethod( const std::string& interfaceName + , const std::string& methodName + , const std::string& inputSignature + , const std::string& outputSignature + , method_callback methodCallback ) = 0; + + /*! + * @brief Registers signal that the object will emit 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] signature D-Bus signature of signal parameters + * + * @throws sdbus::Error in case of failure + */ + virtual void registerSignal( const std::string& interfaceName + , const std::string& signalName + , const std::string& signature ) = 0; + + /*! + * @brief Registers read-only 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 + * + * @throws sdbus::Error in case of failure + */ + virtual void registerProperty( const std::string& interfaceName + , const std::string& propertyName + , const std::string& signature + , property_get_callback getCallback ) = 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 + * + * @throws sdbus::Error in case of failure + */ + virtual void registerProperty( const std::string& interfaceName + , const std::string& propertyName + , const std::string& signature + , property_get_callback getCallback + , property_set_callback setCallback ) = 0; + + /*! + * @brief Finishes the registration and exports object API on D-Bus + * + * The method exports all up to now registered methods, signals and properties on D-Bus. + * Must be called only once, after all methods, signals and properties have been registered. + * + * @throws sdbus::Error in case of failure + */ + virtual void finishRegistration() = 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 Message createSignal(const std::string& interfaceName, const std::string& signalName) = 0; + + /*! + * @brief Emits signal on D-Bus + * + * @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::Message& message) = 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 + */ + 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>(); + * @endcode + * + * @throws sdbus::Error in case of failure + */ + 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 + */ + PropertyRegistrator registerProperty(const std::string& propertyName); + + /*! + * @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 + */ + SignalEmitter emitSignal(const std::string& signalName); + + virtual ~IObject() = 0; + }; + + inline MethodRegistrator IObject::registerMethod(const std::string& methodName) + { + return MethodRegistrator(*this, methodName); + } + + inline SignalRegistrator IObject::registerSignal(const std::string& signalName) + { + return SignalRegistrator(*this, std::move(signalName)); + } + + inline PropertyRegistrator IObject::registerProperty(const std::string& propertyName) + { + return PropertyRegistrator(*this, std::move(propertyName)); + } + + inline SignalEmitter IObject::emitSignal(const std::string& signalName) + { + return SignalEmitter(*this, signalName); + } + + inline IObject::~IObject() {} + + /*! + * @brief Creates instance representing a D-Bus object + * + * @param[in] connection D-Bus connection to be used by the object + * @param[in] objectPath Path of the D-Bus object + * @return Pointer to the object representation instance + * + * The provided connection will be used by the object to export methods, + * issue signals and provide properties. + * + * Code example: + * @code + * auto proxy = sdbus::createObject(connection, "/com/kistler/foo"); + * @endcode + */ + std::unique_ptr createObject(sdbus::IConnection& connection, std::string objectPath); + +} + +#include + +#endif /* SDBUS_CXX_IOBJECT_H_ */ diff --git a/include/sdbus-c++/IObjectProxy.h b/include/sdbus-c++/IObjectProxy.h new file mode 100755 index 0000000..27104ef --- /dev/null +++ b/include/sdbus-c++/IObjectProxy.h @@ -0,0 +1,249 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file IObjectProxy.h + * + * Created on: Nov 8, 2016 + * 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 . + */ + +#ifndef SDBUS_CXX_IOBJECTPROXY_H_ +#define SDBUS_CXX_IOBJECTPROXY_H_ + +#include +#include +#include +#include + +// Forward declarations +namespace sdbus { + class Message; + class IConnection; +} + +namespace sdbus { + + /********************************************//** + * @class IObjectProxy + * + * An interface to D-Bus object proxy. Provides API for calling + * methods, getting/setting properties, and for registering to signals. + * + * All methods throw @c sdbus::Error in case of failure. The class is + * thread-aware, but not thread-safe. + * + ***********************************************/ + class IObjectProxy + { + public: + /*! + * @brief Creates a method call message + * + * @param[in] interfaceName Name of an interface that the method is defined under + * @param[in] methodName Name of the method + * @return A method call message message + * + * Serialize method arguments into the returned message and invoke the method by passing + * the message with serialized arguments to the @c callMethod function. + * Alternatively, use higher-level API @c callMethod(const std::string& methodName) defined below. + * + * @throws sdbus::Error in case of failure + */ + virtual Message createMethodCall(const std::string& interfaceName, const std::string& methodName) = 0; + + /*! + * @brief Calls method on the proxied D-Bus object + * + * @param[in] message Message representing a method call + * + * Note: To avoid messing with messages, use higher-level API defined below. + * + * @throws sdbus::Error in case of failure + */ + virtual Message callMethod(const sdbus::Message& message) = 0; + + /*! + * @brief Registers a handler for the desired signal emitted by the proxied D-Bus object + * + * @param[in] interfaceName Name of an interface that the signal belongs to + * @param[in] signalName Name of the signal + * @param[in] signalHandler Callback that implements the body of the signal handler + * + * @throws sdbus::Error in case of failure + */ + virtual void registerSignalHandler( const std::string& interfaceName + , const std::string& signalName + , signal_handler signalHandler ) = 0; + + /*! + * @brief Finishes the registration of signal handlers + * + * The method physically subscribes to the desired signals. + * Must be called only once, after all signals have been registered already. + * + * @throws sdbus::Error in case of failure + */ + virtual void finishRegistration() = 0; + + /*! + * @brief Calls method on the proxied D-Bus object + * + * @param[in] methodName Name of the method + * @return A helper object for convenient invocation of the method + * + * This is a high-level, convenience way of calling 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 provided native arguments + * and return values. + * + * Example of use: + * @code + * int result, a = ..., b = ...; + * object_.callMethod("multiply").onInterface(INTERFACE_NAME).withArguments(a, b).storeResultsTo(result); + * @endcode + * + * @throws sdbus::Error in case of failure + */ + MethodInvoker callMethod(const std::string& methodName); + + /*! + * @brief Registers signal handler for a given signal of the proxied D-Bus object + * + * @param[in] signalName Name of the signal + * @return A helper object for convenient registration of the signal handler + * + * This is a high-level, convenience way of registering to 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 parameters + * of the provided native signal callback. + * + * Example of use: + * @code + * object_.uponSignal("fooSignal").onInterface("com.kistler.foo").call([this](int arg1, double arg2){ this->onFooSignal(arg1, arg2); }); + * @endcode + * + * @throws sdbus::Error in case of failure + */ + SignalSubscriber uponSignal(const std::string& signalName); + + /*! + * @brief Gets value of a property of the proxied D-Bus object + * + * @param[in] propertyName Name of the property + * @return A helper object for convenient getting of property value + * + * This is a high-level, convenience way of reading D-Bus property values that abstracts + * from the D-Bus message concept. sdbus::Variant is returned which shall then be converted + * to the real property type (implicit conversion is supported). + * + * Example of use: + * @code + * int state = object.getProperty("state").onInterface("com.kistler.foo"); + * @endcode + * + * @throws sdbus::Error in case of failure + */ + PropertyGetter getProperty(const std::string& propertyName); + + /*! + * @brief Sets value of a property of the proxied D-Bus object + * + * @param[in] propertyName Name of the property + * @return A helper object for convenient setting of property value + * + * This is a high-level, convenience way of writing D-Bus property values that abstracts + * from the D-Bus message concept. + * + * Example of use: + * @code + * int state = ...; + * object_.setProperty("state").onInterface("com.kistler.foo").toValue(state); + * @endcode + * + * @throws sdbus::Error in case of failure + */ + PropertySetter setProperty(const std::string& propertyName); + + virtual ~IObjectProxy() = 0; + }; + + inline MethodInvoker IObjectProxy::callMethod(const std::string& methodName) + { + return MethodInvoker(*this, methodName); + } + + inline SignalSubscriber IObjectProxy::uponSignal(const std::string& signalName) + { + return SignalSubscriber(*this, signalName); + } + + inline PropertyGetter IObjectProxy::getProperty(const std::string& propertyName) + { + return PropertyGetter(*this, propertyName); + } + + inline PropertySetter IObjectProxy::setProperty(const std::string& propertyName) + { + return PropertySetter(*this, propertyName); + } + + inline IObjectProxy::~IObjectProxy() {} + + /*! + * @brief Creates object proxy 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 + * @return Pointer to the object proxy instance + * + * The provided connection will be used by the proxy to issue calls against the object, + * and signals, if any, will be subscribed to on this connection. + * + * Code example: + * @code + * auto proxy = sdbus::createObjectProxy(connection, "com.kistler.foo", "/com/kistler/foo"); + * @endcode + */ + std::unique_ptr createObjectProxy( sdbus::IConnection& connection + , std::string destination + , std::string objectPath ); + + /*! + * @brief Creates object proxy instance that uses its own D-Bus connection + * + * @param[in] destination Bus name that provides a D-Bus object + * @param[in] objectPath Path of the D-Bus object + * @return Pointer to the object proxy instance + * + * This factory overload creates a proxy that manages its own D-Bus connection(s). + * + * Code example: + * @code + * auto proxy = sdbus::createObjectProxy(connection, "com.kistler.foo", "/com/kistler/foo"); + * @endcode + */ + std::unique_ptr createObjectProxy( std::string destination + , std::string objectPath ); + +} + +#include + +#endif /* SDBUS_CXX_IOBJECTPROXY_H_ */ diff --git a/include/sdbus-c++/Interfaces.h b/include/sdbus-c++/Interfaces.h new file mode 100644 index 0000000..8aa0da5 --- /dev/null +++ b/include/sdbus-c++/Interfaces.h @@ -0,0 +1,120 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file Interfaces.h + * + * Created on: Nov 8, 2016 + * 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 . + */ + +#ifndef SDBUS_CXX_INTERFACES_H_ +#define SDBUS_CXX_INTERFACES_H_ + +#include +#include +#include +#include +#include + +// Forward declarations +namespace sdbus { + class IConnection; +} + +namespace sdbus { + + template + class ObjectHolder + { + protected: + ObjectHolder(std::unique_ptr<_Object>&& object) + : object_(std::move(object)) + { + } + + const _Object& getObject() const + { + assert(object_ != nullptr); + return *object_; + } + + _Object& getObject() + { + assert(object_ != nullptr); + return *object_; + } + + private: + std::unique_ptr<_Object> object_; + }; + + /********************************************//** + * @class Interfaces + * + * A helper template class that a user class representing a D-Bus object + * should inherit from, providing as template arguments the adaptor + * classes representing D-Bus interfaces that the object implements. + * + ***********************************************/ + template + class Interfaces + : private ObjectHolder + , public _Interfaces... + { + public: + Interfaces(IConnection& connection, std::string objectPath) + : ObjectHolder(createObject(connection, std::move(objectPath))) + , _Interfaces(getObject())... + { + getObject().finishRegistration(); + } + }; + + /********************************************//** + * @class Interfaces + * + * A helper template class that a user class representing a proxy of a + * D-Bus object should inherit from, providing as template arguments the proxy + * classes representing object D-Bus interfaces that the object implements. + * + ***********************************************/ + template + class ProxyInterfaces + : private ObjectHolder + , public _Interfaces... + { + public: + ProxyInterfaces(std::string destination, std::string objectPath) + : ObjectHolder(createObjectProxy(std::move(destination), std::move(objectPath))) + , _Interfaces(getObject())... + { + getObject().finishRegistration(); + } + + ProxyInterfaces(IConnection& connection, std::string destination, std::string objectPath) + : ObjectHolder(createObjectProxy(connection, std::move(destination), std::move(objectPath))) + , _Interfaces(getObject())... + { + getObject().finishRegistration(); + } + }; + +} + +#endif /* SDBUS_CXX_INTERFACES_H_ */ diff --git a/include/sdbus-c++/Introspection.h b/include/sdbus-c++/Introspection.h new file mode 100644 index 0000000..3fa297d --- /dev/null +++ b/include/sdbus-c++/Introspection.h @@ -0,0 +1,84 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file Introspection.h + * + * Created on: Dec 13, 2016 + * 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 . + */ + +#ifndef SDBUS_CXX_INTROSPECTION_H_ +#define SDBUS_CXX_INTROSPECTION_H_ + +#include +#include +#include + +namespace sdbus { + + // Proxy for introspection + class introspectable_proxy + { + static constexpr const char* interfaceName = "org.freedesktop.DBus.Introspectable"; + + protected: + introspectable_proxy(sdbus::IObjectProxy& object) + : object_(object) + { + } + + public: + std::string Introspect() + { + std::string xml; + object_.callMethod("Introspect").onInterface(interfaceName).storeResultsTo(xml); + return xml; + } + + private: + sdbus::IObjectProxy& object_; + }; + + // Adaptor is not necessary if we want to rely on sdbus-provided introspection +// class introspectable_adaptor +// { +// static constexpr const char* interfaceName = "org.freedesktop.DBus.Introspectable"; +// +// protected: +// introspectable_adaptor(sdbus::IObject& object) +// : object_(object) +// { +// object_.registerMethod("Introspect").onInterface(interfaceName).implementedAs([this](){ return object_.introspect(); }); +// } +// +// public: +// std::string introspect() +// { +// std::string xml; +// object_.callMethod("Introspect").onInterface(interfaceName).storeResultsTo(xml); +// return xml; +// } +// +// private: +// sdbus::IObject& object_; +// }; + +} + +#endif /* SDBUS_CXX_INTROSPECTION_H_ */ diff --git a/include/sdbus-c++/Message.h b/include/sdbus-c++/Message.h new file mode 100644 index 0000000..a4d630d --- /dev/null +++ b/include/sdbus-c++/Message.h @@ -0,0 +1,330 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file Message.h + * + * Created on: Nov 9, 2016 + * 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 . + */ + +#ifndef SDBUS_CXX_MESSAGE_H_ +#define SDBUS_CXX_MESSAGE_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// Forward declarations +namespace sdbus { + class Variant; + class ObjectPath; + class Signature; + template class Struct; +} + +namespace sdbus { + + /********************************************//** + * @class Message + * + * Message represents a D-Bus message, which can be either method call message, + * method reply message, signal message, or a plain message serving as a storage + * for serialized data. + * + * Serialization and deserialization functions are provided for types supported + * by D-Bus. + * + * You don't need to work with this class directly if you use high-level APIs + * of @c IObject and @c IObjectProxy. + * + ***********************************************/ + class Message + { + public: + enum class Type + { + ePlainMessage + , eMethodCall + , eMethodReply + , eSignal + }; + + Message() = default; + Message(void *msg, Type type = Type::ePlainMessage) noexcept; + 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<<(int16_t item); + Message& operator<<(int32_t item); + Message& operator<<(int64_t item); + Message& operator<<(uint8_t item); + Message& operator<<(uint16_t item); + Message& operator<<(uint32_t item); + Message& operator<<(uint64_t item); + Message& operator<<(double item); + Message& operator<<(const char *item); + Message& operator<<(const std::string &item); + Message& operator<<(const Variant &item); + Message& operator<<(const ObjectPath &item); + Message& operator<<(const Signature &item); + + Message& operator>>(bool& item); + Message& operator>>(int16_t& item); + Message& operator>>(int32_t& item); + Message& operator>>(int64_t& item); + Message& operator>>(uint8_t& item); + Message& operator>>(uint16_t& item); + Message& operator>>(uint32_t& item); + Message& operator>>(uint64_t& item); + Message& operator>>(double& item); + Message& operator>>(char*& item); + Message& operator>>(std::string &item); + Message& operator>>(Variant &item); + Message& operator>>(ObjectPath &item); + Message& operator>>(Signature &item); + + Message& openContainer(const std::string& signature); + Message& closeContainer(); + Message& openDictEntry(const std::string& signature); + Message& closeDictEntry(); + Message& openVariant(const std::string& signature); + Message& closeVariant(); + Message& openStruct(const std::string& signature); + Message& closeStruct(); + + Message& enterContainer(const std::string& signature); + Message& exitContainer(); + Message& enterDictEntry(const std::string& signature); + Message& exitDictEntry(); + Message& enterVariant(const std::string& signature); + Message& exitVariant(); + Message& enterStruct(const std::string& signature); + Message& exitStruct(); + + operator bool() const; + void clearFlags(); + + std::string getInterfaceName() const; + std::string getMemberName() const; + void peekType(std::string& type, std::string& contents) const; + bool isValid() const; + bool isEmpty() const; + Type getType() const; + + void copyTo(Message& destination, bool complete) const; + void seal(); + void rewind(bool complete); + + Message createReply() const; + Message send() const; + + private: + void* msg_{}; + Type type_{Type::ePlainMessage}; + mutable bool ok_{true}; + }; + + template + inline Message& operator<<(Message& msg, const std::vector<_Element>& items) + { + msg.openContainer(signature_of<_Element>::str()); + + for (const auto& item : items) + msg << item; + + msg.closeContainer(); + + return msg; + } + + template + inline Message& operator<<(Message& msg, const std::map<_Key, _Value>& items) + { + const std::string dictEntrySignature = signature_of<_Key>::str() + signature_of<_Value>::str(); + const std::string arraySignature = "{" + dictEntrySignature + "}"; + + msg.openContainer(arraySignature); + + for (const auto& item : items) + { + msg.openDictEntry(dictEntrySignature); + msg << item.first; + msg << item.second; + msg.closeDictEntry(); + } + + msg.closeContainer(); + + return msg; + } + + namespace detail + { + template + void serialize_pack(Message& msg, _Args&&... args) + { + // Use initializer_list because it guarantees left to right order, and can be empty + using _ = std::initializer_list; + // We are not interested in the list itself, but in the side effects + (void)_{(void(msg << std::forward<_Args>(args)), 0)...}; + } + + template + void serialize_tuple( Message& msg + , const _Tuple& t + , std::index_sequence<_Is...>) + { + serialize_pack(msg, std::get<_Is>(t)...); + } + } + + template + inline Message& operator<<(Message& msg, const Struct<_ValueTypes...>& item) + { + auto structSignature = signature_of>::str(); + assert(structSignature.size() > 2); + // Remove opening and closing parenthesis from the struct signature to get contents signature + auto structContentSignature = structSignature.substr(1, structSignature.size()-2); + + msg.openStruct(structContentSignature); + detail::serialize_tuple(msg, item, std::index_sequence_for<_ValueTypes...>{}); + msg.closeStruct(); + + return msg; + } + + template + inline Message& operator<<(Message& msg, const std::tuple<_ValueTypes...>& item) + { + detail::serialize_tuple(msg, item, std::index_sequence_for<_ValueTypes...>{}); + return msg; + } + + + template + inline Message& operator>>(Message& msg, std::vector<_Element>& items) + { + if(!msg.enterContainer(signature_of<_Element>::str())) + return msg; + + while (true) + { + _Element elem; + if (msg >> elem) + items.emplace_back(std::move(elem)); + else + break; + } + + msg.clearFlags(); + + msg.exitContainer(); + + return msg; + } + + template + inline Message& operator>>(Message& msg, std::map<_Key, _Value>& items) + { + const std::string dictEntrySignature = signature_of<_Key>::str() + signature_of<_Value>::str(); + const std::string arraySignature = "{" + dictEntrySignature + "}"; + + if (!msg.enterContainer(arraySignature)) + return msg; + + while (true) + { + if (!msg.enterDictEntry(dictEntrySignature)) + break; + + _Key key; + _Value value; + msg >> key >> value; + + items.emplace(std::move(key), std::move(value)); + + msg.exitDictEntry(); + } + + msg.clearFlags(); + + msg.exitContainer(); + + return msg; + } + + namespace detail + { + template + void deserialize_pack(Message& msg, _Args&... args) + { + // Use initializer_list because it guarantees left to right order, and can be empty + using _ = std::initializer_list; + // We are not interested in the list itself, but in the side effects + (void)_{(void(msg >> args), 0)...}; + } + + template + void deserialize_tuple( Message& msg + , _Tuple& t + , std::index_sequence<_Is...> ) + { + deserialize_pack(msg, std::get<_Is>(t)...); + } + } + + template + inline Message& operator>>(Message& msg, Struct<_ValueTypes...>& item) + { + auto structSignature = signature_of>::str(); + // Remove opening and closing parenthesis from the struct signature to get contents signature + auto structContentSignature = structSignature.substr(1, structSignature.size()-2); + + if (!msg.enterStruct(structContentSignature)) + return msg; + + detail::deserialize_tuple(msg, item, std::index_sequence_for<_ValueTypes...>{}); + + msg.exitStruct(); + + return msg; + } + + template + inline Message& operator>>(Message& msg, std::tuple<_ValueTypes...>& item) + { + detail::deserialize_tuple(msg, item, std::index_sequence_for<_ValueTypes...>{}); + return msg; + } + +} + +#endif /* SDBUS_CXX_MESSAGE_H_ */ diff --git a/include/sdbus-c++/TypeTraits.h b/include/sdbus-c++/TypeTraits.h new file mode 100644 index 0000000..b71dc50 --- /dev/null +++ b/include/sdbus-c++/TypeTraits.h @@ -0,0 +1,441 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file TypeTraits.h + * + * Created on: Nov 9, 2016 + * 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 . + */ + +#ifndef SDBUS_CXX_TYPETRAITS_H_ +#define SDBUS_CXX_TYPETRAITS_H_ + +#include +#include +#include +#include +#include +#include + +// Forward declarations +namespace sdbus { + class Variant; + template class Struct; + class ObjectPath; + class Signature; + class Message; +} + +namespace sdbus { + + using method_callback = std::function; + using signal_handler = std::function; + using property_set_callback = std::function; + using property_get_callback = std::function; + + // Primary template + template + struct signature_of + { + static const std::string str() + { + // sizeof(_T) < 0 is here to make compiler not being able to figure out + // the assertion expression before the template instantiation takes place. + static_assert(sizeof(_T) < 0, "Unknown DBus type"); + return ""; + } + }; + + template <> + struct signature_of + { + static const std::string str() + { + return ""; + } + }; + + template <> + struct signature_of + { + static const std::string str() + { + return "b"; + } + }; + + template <> + struct signature_of + { + static const std::string str() + { + return "y"; + } + }; + + template <> + struct signature_of + { + static const std::string str() + { + return "n"; + } + }; + + template <> + struct signature_of + { + static const std::string str() + { + return "q"; + } + }; + + template <> + struct signature_of + { + static const std::string str() + { + return "i"; + } + }; + + template <> + struct signature_of + { + static const std::string str() + { + return "u"; + } + }; + + template <> + struct signature_of + { + static const std::string str() + { + return "x"; + } + }; + + template <> + struct signature_of + { + static const std::string str() + { + return "t"; + } + }; + + template <> + struct signature_of + { + static const std::string str() + { + return "d"; + } + }; + + template <> + struct signature_of + { + static const std::string str() + { + return "s"; + } + }; + + template <> + struct signature_of + { + static const std::string str() + { + return "s"; + } + }; + + template + struct signature_of + { + static const std::string str() + { + return "s"; + } + }; + + template + struct signature_of + { + static const std::string str() + { + return "s"; + } + }; + + template <> + struct signature_of + { + static const std::string str() + { + return "s"; + } + }; + + template + struct signature_of> + { + static const std::string str() + { + std::initializer_list signatures{signature_of<_ValueTypes>::str()...}; + std::string signature; + signature += "("; + for (const auto& item : signatures) + signature += item; + signature += ")"; + return signature; + } + }; + + template <> + struct signature_of + { + static const std::string str() + { + return "v"; + } + }; + + template <> + struct signature_of + { + static const std::string str() + { + return "o"; + } + }; + + template <> + struct signature_of + { + static const std::string str() + { + return "g"; + } + }; + + template + struct signature_of> + { + static const std::string str() + { + return "a" + signature_of<_Element>::str(); + } + }; + + template + struct signature_of> + { + static const std::string str() + { + return "a{" + signature_of<_Key>::str() + signature_of<_Value>::str() + "}"; + } + }; + + template + struct function_traits + : public function_traits + {}; + + template + struct function_traits + : public function_traits<_Type> + {}; + + template + struct function_traits<_Type&> + : public function_traits<_Type> + {}; + + // Function traits implementation inspired by (c) kennytm, + // https://github.com/kennytm/utils/blob/master/traits.hpp + template + struct function_traits<_ReturnType(_Args...)> + { + typedef _ReturnType result_type; + typedef std::tuple<_Args...> arguments_type; + + typedef _ReturnType function_type(_Args...); + + static constexpr std::size_t arity = sizeof...(_Args); + + template + struct arg + { + typedef std::tuple_element_t<_Idx, std::tuple<_Args...>> type; + }; + + template + using arg_t = typename arg<_Idx>::type; + }; + + template + struct function_traits<_ReturnType(*)(_Args...)> + : public function_traits<_ReturnType(_Args...)> + {}; + + template + struct function_traits<_ReturnType(_ClassType::*)(_Args...)> + : public function_traits<_ReturnType(_Args...)> + { + typedef _ClassType& owner_type; + }; + + template + struct function_traits<_ReturnType(_ClassType::*)(_Args...) const> + : public function_traits<_ReturnType(_Args...)> + { + typedef const _ClassType& owner_type; + }; + + template + struct function_traits<_ReturnType(_ClassType::*)(_Args...) volatile> + : public function_traits<_ReturnType(_Args...)> + { + typedef volatile _ClassType& owner_type; + }; + + template + struct function_traits<_ReturnType(_ClassType::*)(_Args...) const volatile> + : public function_traits<_ReturnType(_Args...)> + { + typedef const volatile _ClassType& owner_type; + }; + + template + struct function_traits> + : public function_traits + {}; + + template + using function_argument_t = typename function_traits<_FunctionType>::template arg_t<_Idx>; + + template + using function_result_t = typename function_traits<_FunctionType>::result_type; + + template + struct aggregate_signature + { + static const std::string str() + { + return signature_of>::str(); + } + }; + + template + struct aggregate_signature> + { + static const std::string str() + { + std::initializer_list signatures{signature_of>::str()...}; + std::string signature; + for (const auto& item : signatures) + signature += item; + return signature; + } + }; + + // Get a tuple of function input argument types from function signature. + // But first, convert provided function signature to the standardized form `out(in...)'. + template + struct tuple_of_function_input_arg_types + : public tuple_of_function_input_arg_types::function_type> + {}; + + // Get a tuple of function input argument types from function signature. + // Function signature is expected in the standardized form `out(in...)'. + template + struct tuple_of_function_input_arg_types<_ReturnType(_Args...)> + { + // Arguments may be cv-qualified and may be references, so we have to strip cv and references + // with decay_t in order to get real 'naked' types. + // Example: for a function with signature void(const int i, const std::vector v, double d) + // the `type' will be `std::tuple, double>'. + typedef std::tuple...> type; + }; + + template + using tuple_of_function_input_arg_types_t = typename tuple_of_function_input_arg_types<_Function>::type; + + template + struct signature_of_function_input_arguments + { + static const std::string str() + { + return aggregate_signature>::str(); + } + }; + + template + struct signature_of_function_output_arguments + { + static const std::string str() + { + return aggregate_signature>::str(); + } + }; + + namespace detail + { + // Version of apply_impl for functions returning non-void values. + // In this case just forward function return value. + template + constexpr decltype(auto) apply_impl( _Function&& f + , _Tuple&& t + , std::index_sequence<_I...> + , std::enable_if_t>::value>* = nullptr) + { + return std::forward<_Function>(f)(std::get<_I>(std::forward<_Tuple>(t))...); + } + + // Version of apply_impl for functions returning void. + // In this case, to have uniform code on the caller side, return empty tuple, our synonym for `void'. + template + constexpr decltype(auto) apply_impl( _Function&& f + , _Tuple&& t + , std::index_sequence<_I...> + , std::enable_if_t>::value>* = nullptr) + { + std::forward<_Function>(f)(std::get<_I>(std::forward<_Tuple>(t))...); + return std::tuple<>{}; + } + } + + // Convert tuple `t' of values into a list of arguments + // and invoke function `f' with those arguments. + template + constexpr decltype(auto) apply(_Function&& f, _Tuple&& t) + { + return detail::apply_impl( std::forward<_Function>(f) + , std::forward<_Tuple>(t) + , std::make_index_sequence>::value>{} ); + } + +} + +#endif /* SDBUS_CXX_TYPETRAITS_H_ */ diff --git a/include/sdbus-c++/Types.h b/include/sdbus-c++/Types.h new file mode 100644 index 0000000..fc63dd8 --- /dev/null +++ b/include/sdbus-c++/Types.h @@ -0,0 +1,137 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file Types.h + * + * Created on: Nov 23, 2016 + * 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 . + */ + +#ifndef SDBUS_CXX_TYPES_H_ +#define SDBUS_CXX_TYPES_H_ + +#include +#include +#include +#include +#include +#include +#include + +namespace sdbus { + + /********************************************//** + * @class Variant + * + * Variant can hold value of any D-Bus-supported type. + * + ***********************************************/ + class Variant + { + public: + Variant(); + + template + Variant(const _ValueType& value) + : Variant() + { + msg_.openVariant(signature_of<_ValueType>::str()); + msg_ << value; + msg_.closeVariant(); + msg_.seal(); + } + + template + _ValueType get() const + { + _ValueType val; + msg_.rewind(false); + msg_.enterVariant(signature_of<_ValueType>::str()); + msg_ >> val; + msg_.exitVariant(); + return val; + } + + template + operator _ValueType() const + { + return get<_ValueType>(); + } + + template + bool containsValueOfType() const + { + return signature_of<_Type>::str() == peekValueType(); + } + + bool isEmpty() const; + + void serializeTo(Message& msg) const; + void deserializeFrom(Message& msg); + std::string peekValueType() const; + + private: + mutable Message msg_{}; + }; + + template + class Struct + : public std::tuple<_ValueTypes...> + { + public: + using std::tuple<_ValueTypes...>::tuple; + + template + auto& get() + { + return std::get<_I>(*this); + } + + template + const auto& get() const + { + return std::get<_I>(*this); + } + }; + + template + constexpr Struct...> + make_struct(_Elements&&... args) + { + typedef Struct...> result_type; + return result_type(std::forward<_Elements>(args)...); + } + + class ObjectPath : public std::string + { + public: + using std::string::string; + using std::string::operator=; + }; + + class Signature : public std::string + { + public: + using std::string::string; + using std::string::operator=; + }; + +} + +#endif /* SDBUS_CXX_TYPES_H_ */ diff --git a/include/sdbus-c++/sdbus-c++.h b/include/sdbus-c++/sdbus-c++.h new file mode 100644 index 0000000..e17b848 --- /dev/null +++ b/include/sdbus-c++/sdbus-c++.h @@ -0,0 +1,34 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file sdbus-c++.h + * + * Created on: Jan 19, 2017 + * Project: sdbus-c++ + * Description: High-level D-Bus IPC C++ library based on sd-bus + * + * This file is part of sdbus-c++. + * + * sdbus-c++ is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * sdbus-c++ is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with sdbus-c++. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/sdbus-c++.pc.in b/sdbus-c++.pc.in new file mode 100644 index 0000000..4e99276 --- /dev/null +++ b/sdbus-c++.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: @PACKAGE@ +Description: C++ bindings library for sd-bus +Requires: libsystemd +Version: @VERSION@ +Libs: -L${libdir} -lsdbus-c++ +Cflags: -I${includedir} diff --git a/src/Connection.cpp b/src/Connection.cpp new file mode 100755 index 0000000..509d6ef --- /dev/null +++ b/src/Connection.cpp @@ -0,0 +1,272 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file Connection.cpp + * + * Created on: Nov 8, 2016 + * 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 . + */ + +#include "Connection.h" +#include +#include +#include "ScopeGuard.h" +#include +#include +#include +#include + +namespace { + std::map busTypeToFactory + { + {sdbus::internal::Connection::BusType::eSystem, &sd_bus_open_system}, + {sdbus::internal::Connection::BusType::eSession, &sd_bus_open_user} + }; +} + +namespace sdbus { namespace internal { + +Connection::Connection(Connection::BusType type) + : busType_(type) +{ + sd_bus* bus{}; + auto r = busTypeToFactory[busType_](&bus); + if (r < 0) + SDBUS_THROW_ERROR("Failed to open system bus", -r); + + bus_.reset(bus); + + // Process all requests that are part of the initial handshake, + // like processing the Hello message response, authentication etc., + // to avoid connection authentication timeout in dbus daemon. + r = sd_bus_flush(bus_.get()); + if (r < 0) + SDBUS_THROW_ERROR("Failed to flush system bus on opening", -r); + + r = eventfd(0, EFD_SEMAPHORE); + SDBUS_THROW_ERROR_IF(r < 0, "Failed to create event object", -errno); + runFd_ = r; +} + +Connection::~Connection() +{ + leaveProcessingLoop(); + close(runFd_); +} + +void Connection::requestName(const std::string& name) +{ + auto r = sd_bus_request_name(bus_.get(), name.c_str(), 0); + if (r < 0) + SDBUS_THROW_ERROR("Failed to request bus name", -r); +} + +void Connection::releaseName(const std::string& name) +{ + auto r = sd_bus_release_name(bus_.get(), name.c_str()); + if (r < 0) + SDBUS_THROW_ERROR("Failed to release bus name", -r); +} + +void Connection::enterProcessingLoop() +{ + int semaphoreFd = runFd_; + short int semaphoreEvents = POLLIN; + + while (true) + { + /* Process requests */ + int r = sd_bus_process(bus_.get(), nullptr); + SDBUS_THROW_ERROR_IF(r < 0, "Failed to process bus requests", -r); + if (r > 0) /* we processed a request, try to process another one, right-away */ + continue; + + r = sd_bus_get_fd(bus_.get()); + SDBUS_THROW_ERROR_IF(r < 0, "Failed to get bus descriptor", -r); + auto sdbusFd = r; + + r = sd_bus_get_events(bus_.get()); + SDBUS_THROW_ERROR_IF(r < 0, "Failed to get bus descriptor", -r); + short int sdbusEvents = r; + + struct pollfd fds[] = {{sdbusFd, sdbusEvents, 0}, {semaphoreFd, semaphoreEvents, 0}}; + + /* Wait for the next request to process */ + uint64_t usec; + sd_bus_get_timeout(bus_.get(), &usec); + + auto fdsCount = sizeof(fds)/sizeof(fds[0]); + r = poll(fds, fdsCount, usec == (uint64_t) -1 ? -1 : (usec+999)/1000); + SDBUS_THROW_ERROR_IF(r < 0, "Failed to wait on the bus", -errno); + + if (fds[1].revents & POLLIN) + break; + } +} + +void Connection::enterProcessingLoopAsync() +{ + asyncLoopThread_ = std::thread([this](){ enterProcessingLoop(); }); +} + +void Connection::leaveProcessingLoop() +{ + assert(runFd_ >= 0); + uint64_t value = 1; + write(runFd_, &value, sizeof(value)); + + if (asyncLoopThread_.joinable()) + asyncLoopThread_.join(); +} + +void* Connection::addObjectVTable( const std::string& objectPath + , const std::string& interfaceName + , const void* vtable + , void* userData ) +{ + sd_bus_slot *slot{}; + auto r = sd_bus_add_object_vtable( bus_.get() + , &slot + , objectPath.c_str() + , interfaceName.c_str() + , static_cast(vtable) + , userData ); + if (r < 0) + SDBUS_THROW_ERROR("Failed to register object vtable", -r); + + return slot; +} + +void Connection::removeObjectVTable(void* vtableHandle) +{ + sd_bus_slot_unref((sd_bus_slot *)vtableHandle); +} + +sdbus::Message Connection::createMethodCall( const std::string& destination + , const std::string& objectPath + , const std::string& interfaceName + , const std::string& methodName ) const +{ + sd_bus_message *sdbusMsg{}; + SCOPE_EXIT{ sd_bus_message_unref(sdbusMsg); }; // Returned message will become an owner of sdbusMsg + auto r = sd_bus_message_new_method_call( bus_.get() + , &sdbusMsg + , destination.c_str() + , objectPath.c_str() + , interfaceName.c_str() + , methodName.c_str() ); + if (r < 0) + SDBUS_THROW_ERROR("Failed to create method call", -r); + + return Message(sdbusMsg, Message::Type::eMethodCall); +} + +sdbus::Message Connection::createSignal( const std::string& objectPath + , const std::string& interfaceName + , const std::string& signalName ) const +{ + sd_bus_message *sdbusSignal{}; + SCOPE_EXIT{ sd_bus_message_unref(sdbusSignal); }; // Returned message will become an owner of sdbusSignal + auto r = sd_bus_message_new_signal( bus_.get() + , &sdbusSignal + , objectPath.c_str() + , interfaceName.c_str() + , signalName.c_str() ); + if (r < 0) + SDBUS_THROW_ERROR("Failed to create signal", -r); + + return Message(sdbusSignal, Message::Type::eSignal); +} + +void* Connection::registerSignalHandler( const std::string& objectPath + , const std::string& interfaceName + , const std::string& signalName + , sd_bus_message_handler_t callback + , void* userData ) +{ + sd_bus_slot *slot{}; + auto filter = composeSignalMatchFilter(objectPath, interfaceName, signalName); + auto r = sd_bus_add_match(bus_.get(), &slot, filter.c_str(), callback, userData); + if (r < 0) + SDBUS_THROW_ERROR("Failed to register signal handler", -r); + + return slot; +} + +void Connection::unregisterSignalHandler(void* handlerCookie) +{ + sd_bus_slot_unref((sd_bus_slot *)handlerCookie); +} + +std::unique_ptr Connection::clone() const +{ + return std::make_unique(busType_); +} + +std::string Connection::composeSignalMatchFilter( const std::string& objectPath + , const std::string& interfaceName + , const std::string& signalName ) +{ + std::string filter; + filter += "type='signal',"; + filter += "interface='" + interfaceName + "',"; + filter += "member='" + signalName + "',"; + filter += "path='" + objectPath + "'"; + return filter; +} + +}} + +namespace sdbus { + +std::unique_ptr createConnection() +{ + return createSystemBusConnection(); +} + +std::unique_ptr createConnection(const std::string& name) +{ + return createSystemBusConnection(name); +} + +std::unique_ptr createSystemBusConnection() +{ + return std::make_unique(sdbus::internal::Connection::BusType::eSystem); +} + +std::unique_ptr createSystemBusConnection(const std::string& name) +{ + auto conn = createSystemBusConnection(); + conn->requestName(name); + return conn; +} + +std::unique_ptr createSessionBusConnection() +{ + return std::make_unique(sdbus::internal::Connection::BusType::eSession); +} + +std::unique_ptr createSessionBusConnection(const std::string& name) +{ + auto conn = createSessionBusConnection(); + conn->requestName(name); + return conn; +} + +} diff --git a/src/Connection.h b/src/Connection.h new file mode 100755 index 0000000..874760e --- /dev/null +++ b/src/Connection.h @@ -0,0 +1,98 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file Connection.h + * + * Created on: Nov 8, 2016 + * 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 . + */ + +#ifndef SDBUS_CXX_INTERNAL_CONNECTION_H_ +#define SDBUS_CXX_INTERNAL_CONNECTION_H_ + +#include +#include +#include +#include +#include + +#include "IConnection.h" + +namespace sdbus { namespace internal { + + class Connection + : public sdbus::IConnection // External, public interface + , public sdbus::internal::IConnection // Internal, private interface + { + public: + enum class BusType + { + eSystem, + eSession + }; + + Connection(BusType type); + ~Connection(); + + void requestName(const std::string& name) override; + void releaseName(const std::string& name) override; + void enterProcessingLoop() override; + void enterProcessingLoopAsync() override; + void leaveProcessingLoop() override; + + void* addObjectVTable( const std::string& objectPath + , const std::string& interfaceName + , const void* vtable + , void* userData ) override; + void removeObjectVTable(void* vtableHandle) override; + + sdbus::Message createMethodCall( const std::string& destination + , const std::string& objectPath + , const std::string& interfaceName + , const std::string& methodName ) const override; + sdbus::Message createSignal( const std::string& objectPath + , const std::string& interfaceName + , const std::string& signalName ) const override; + + void* registerSignalHandler( const std::string& objectPath + , const std::string& interfaceName + , const std::string& signalName + , sd_bus_message_handler_t callback + , void* userData ) override; + void unregisterSignalHandler(void* handlerCookie) override; + + std::unique_ptr clone() const override; + + private: + static std::string composeSignalMatchFilter( const std::string& objectPath + , const std::string& interfaceName + , const std::string& signalName ); + + private: + std::unique_ptr bus_{nullptr, &sd_bus_flush_close_unref}; + std::thread asyncLoopThread_; + std::atomic runFd_{-1}; + BusType busType_; + + static constexpr const uint64_t POLL_TIMEOUT_USEC = 500000; + }; + +}} + +#endif /* SDBUS_CXX_INTERNAL_CONNECTION_H_ */ diff --git a/src/ConvenienceClasses.cpp b/src/ConvenienceClasses.cpp new file mode 100644 index 0000000..e5b8788 --- /dev/null +++ b/src/ConvenienceClasses.cpp @@ -0,0 +1,150 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file ConvenienceClasses.cpp + * + * Created on: Jan 19, 2017 + * Project: sdbus-c++ + * Description: High-level D-Bus IPC C++ library based on sd-bus + * + * This file is part of sdbus-c++. + * + * sdbus-c++ is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * sdbus-c++ is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with sdbus-c++. If not, see . + */ + +#include +#include +#include +#include +#include + +namespace sdbus { + +SignalRegistrator::SignalRegistrator(IObject& object, const std::string& signalName) + : object_(object) + , signalName_(signalName) + , exceptions_(std::uncaught_exceptions()) // Needs C++17 +{ +} + +SignalRegistrator::~SignalRegistrator() noexcept(false) // since C++11, destructors must +{ // explicitly be allowed to throw + // Don't register the signal if SignalRegistrator threw an exception in one of its methods + if (std::uncaught_exceptions() != exceptions_) + return; + + SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when registering a DBus signal", EINVAL); + + // registerSignal() can throw. But as the SignalRegistrator shall always be used as an unnamed, + // temporary object, i.e. not as a stack-allocated object, the double-exception situation + // shall never happen. I.e. it should not happen that this destructor is directly called + // in the stack-unwinding process of another flying exception (which would lead to immediate + // termination). It can be called indirectly in the destructor of another object, but that's + // fine and safe provided that the caller catches exceptions thrown from here. + // Therefore, we can allow registerSignal() to throw even if we are in the destructor. + // Bottomline is, to be on the safe side, the caller must take care of catching and reacting + // to the exception thrown from here if the caller is a destructor itself. + object_.registerSignal(interfaceName_, signalName_, signalSignature_); +} + + +PropertyRegistrator::PropertyRegistrator(IObject& object, const std::string& propertyName) + : object_(object) + , propertyName_(propertyName) + , exceptions_(std::uncaught_exceptions()) +{ +} + +PropertyRegistrator::~PropertyRegistrator() noexcept(false) // since C++11, destructors must +{ // explicitly be allowed to throw + // Don't register the property if PropertyRegistrator threw an exception in one of its methods + if (std::uncaught_exceptions() != exceptions_) + return; + + SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when registering a DBus property", EINVAL); + + // registerProperty() can throw. But as the PropertyRegistrator shall always be used as an unnamed, + // temporary object, i.e. not as a stack-allocated object, the double-exception situation + // shall never happen. I.e. it should not happen that this destructor is directly called + // in the stack-unwinding process of another flying exception (which would lead to immediate + // termination). It can be called indirectly in the destructor of another object, but that's + // fine and safe provided that the caller catches exceptions thrown from here. + // Therefore, we can allow registerProperty() to throw even if we are in the destructor. + // Bottomline is, to be on the safe side, the caller must take care of catching and reacting + // to the exception thrown from here if the caller is a destructor itself. + object_.registerProperty( std::move(interfaceName_) + , std::move(propertyName_) + , std::move(propertySignature_) + , std::move(getter_) + , std::move(setter_) ); +} + + +SignalEmitter::SignalEmitter(IObject& object, const std::string& signalName) + : object_(object) + , signalName_(signalName) + , exceptions_(std::uncaught_exceptions()) // Needs C++17 +{ +} + +SignalEmitter::~SignalEmitter() noexcept(false) // since C++11, destructors must +{ // explicitly be allowed to throw + // Don't emit the signal if SignalEmitter threw an exception in one of its methods + if (std::uncaught_exceptions() != exceptions_) + return; + + SDBUS_THROW_ERROR_IF(!signal_.isValid(), "DBus interface not specified when emitting a DBus signal", EINVAL); + + // emitSignal() can throw. But as the SignalEmitter shall always be used as an unnamed, + // temporary object, i.e. not as a stack-allocated object, the double-exception situation + // shall never happen. I.e. it should not happen that this destructor is directly called + // in the stack-unwinding process of another flying exception (which would lead to immediate + // termination). It can be called indirectly in the destructor of another object, but that's + // fine and safe provided that the caller catches exceptions thrown from here. + // Therefore, we can allow emitSignal() to throw even if we are in the destructor. + // Bottomline is, to be on the safe side, the caller must take care of catching and reacting + // to the exception thrown from here if the caller is a destructor itself. + object_.emitSignal(signal_); +} + + +MethodInvoker::MethodInvoker(IObjectProxy& objectProxy, const std::string& methodName) + : objectProxy_(objectProxy) + , methodName_(methodName) + , exceptions_(std::uncaught_exceptions()) // Needs C++17 +{ +} + +MethodInvoker::~MethodInvoker() noexcept(false) // since C++11, destructors must +{ // explicitly be allowed to throw + // Don't call the method if it has been called already or if MethodInvoker + // threw an exception in one of its methods + if (methodCalled_ || std::uncaught_exceptions() != exceptions_) + return; + + SDBUS_THROW_ERROR_IF(!method_.isValid(), "DBus interface not specified when calling a DBus method", EINVAL); + + // callMethod() can throw. But as the MethodInvoker shall always be used as an unnamed, + // temporary object, i.e. not as a stack-allocated object, the double-exception situation + // shall never happen. I.e. it should not happen that this destructor is directly called + // in the stack-unwinding process of another flying exception (which would lead to immediate + // termination). It can be called indirectly in the destructor of another object, but that's + // fine and safe provided that the caller catches exceptions thrown from here. + // Therefore, we can allow callMethod() to throw even if we are in the destructor. + // Bottomline is, to be on the safe side, the caller must take care of catching and reacting + // to the exception thrown from here if the caller is a destructor itself. + objectProxy_.callMethod(method_); +} + +} diff --git a/src/Error.cpp b/src/Error.cpp new file mode 100644 index 0000000..1f1a7a7 --- /dev/null +++ b/src/Error.cpp @@ -0,0 +1,40 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file Error.cpp + * + * Created on: Nov 8, 2016 + * 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 . + */ + +#include +#include + +namespace sdbus +{ + sdbus::Error createError(int errNo, const std::string& customMsg) + { + sd_bus_error sdbusError = SD_BUS_ERROR_NULL; + sd_bus_error_set_errno(&sdbusError, errNo); + std::string name(sdbusError.name); + std::string message(customMsg + " (" + sdbusError.message + ")"); + sd_bus_error_free(&sdbusError); + return sdbus::Error(name, message); + } +} diff --git a/src/IConnection.h b/src/IConnection.h new file mode 100755 index 0000000..f7183fb --- /dev/null +++ b/src/IConnection.h @@ -0,0 +1,77 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file IConnection.h + * + * Created on: Nov 9, 2016 + * 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 . + */ + +#ifndef SDBUS_CXX_INTERNAL_ICONNECTION_H_ +#define SDBUS_CXX_INTERNAL_ICONNECTION_H_ + +#include +#include +#include + +// Forward declaration +namespace sdbus { + class Message; +} + +namespace sdbus { +namespace internal { + + class IConnection + { + public: + virtual void* addObjectVTable( const std::string& objectPath + , const std::string& interfaceName + , const void* vtable + , void* userData ) = 0; + virtual void removeObjectVTable(void* vtableHandle) = 0; + + virtual sdbus::Message createMethodCall( const std::string& destination + , const std::string& objectPath + , const std::string& interfaceName + , const std::string& methodName ) const = 0; + + virtual sdbus::Message createSignal( const std::string& objectPath + , const std::string& interfaceName + , const std::string& signalName ) const = 0; + + virtual void* registerSignalHandler( const std::string& objectPath + , const std::string& interfaceName + , const std::string& signalName + , sd_bus_message_handler_t callback + , void* userData ) = 0; + virtual void unregisterSignalHandler(void* handlerCookie) = 0; + + virtual void enterProcessingLoopAsync() = 0; + virtual void leaveProcessingLoop() = 0; + + virtual std::unique_ptr clone() const = 0; + + virtual ~IConnection() = default; + }; + +} +} + +#endif /* SDBUS_CXX_INTERNAL_ICONNECTION_H_ */ diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..810449c --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,26 @@ + +lib_LTLIBRARIES = libsdbus-c++.la + +libsdbus_c___la_SOURCES = \ + Connection.cpp \ + ConvenienceClasses.cpp \ + Message.cpp \ + Object.cpp \ + ObjectProxy.cpp \ + Types.cpp \ + Error.cpp \ + VTableUtils.c + +libsdbus_c___la_LIBADD = @SYSTEMD_LIBS@ + +# Setting per-file flags +AM_CPPFLAGS = -I$(top_srcdir)/include +AM_CXXFLAGS = @libsdbus_cpp_CFLAGS@ @SYSTEMD_CFLAGS@ -std=c++17 -pipe -pedantic -W -Wall +AM_LDFLAGS = @libsdbus_cpp_LIBS@ @SYSTEMD_LIBS@ + +libsdbus_c___la_LDFLAGS = -version-info 0:0:0 + +# Cleaning +CLEANFILES = *~ *.lo *.la +MOSTLYCLEANFILES = *.o +DISTCLEANFILES = Makefile Makefile.in diff --git a/src/Message.cpp b/src/Message.cpp new file mode 100755 index 0000000..335b9c6 --- /dev/null +++ b/src/Message.cpp @@ -0,0 +1,676 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file Message.cpp + * + * Created on: Nov 9, 2016 + * 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 . + */ + +#include +#include +#include +#include "MessageUtils.h" +#include "ScopeGuard.h" +#include +#include + +namespace sdbus { /*namespace internal {*/ + +Message::Message(void *msg, Type type) noexcept + : msg_(msg) + , type_(type) +{ + assert(msg_ != nullptr); + sd_bus_message_ref((sd_bus_message*)msg_); +} + +Message::Message(const Message& other) noexcept +{ + *this = other; +} + +Message& Message::operator=(const Message& other) noexcept +{ + msg_ = other.msg_; + type_ = other.type_; + ok_ = other.ok_; + + sd_bus_message_ref((sd_bus_message*)msg_); + + return *this; +} + +Message::Message(Message&& other) noexcept +{ + *this = std::move(other); +} + +Message& Message::operator=(Message&& other) noexcept +{ + msg_ = other.msg_; + other.msg_ = nullptr; + type_ = other.type_; + other.type_ = {}; + ok_ = other.ok_; + other.ok_ = true; + + return *this; +} + +Message::~Message() +{ + if (msg_) + sd_bus_message_unref((sd_bus_message*)msg_); +} + +Message& Message::operator<<(bool item) +{ + int intItem = item; + + auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_BOOLEAN, &intItem); + if (r < 0) + SDBUS_THROW_ERROR("Failed to serialize a bool value", -r); + + return *this; +} + +Message& Message::operator<<(int16_t item) +{ + auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_INT16, &item); + if (r < 0) + SDBUS_THROW_ERROR("Failed to serialize a int16_t value", -r); + + return *this; +} + +Message& Message::operator<<(int32_t item) +{ + auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_INT32, &item); + if (r < 0) + SDBUS_THROW_ERROR("Failed to serialize a int32_t value", -r); + + return *this; +} + +Message& Message::operator<<(int64_t item) +{ + auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_INT64, &item); + if (r < 0) + SDBUS_THROW_ERROR("Failed to serialize a int64_t value", -r); + + return *this; +} + +Message& Message::operator<<(uint8_t item) +{ + auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_BYTE, &item); + if (r < 0) + SDBUS_THROW_ERROR("Failed to serialize a byte value", -r); + + return *this; +} + +Message& Message::operator<<(uint16_t item) +{ + auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_UINT16, &item); + if (r < 0) + SDBUS_THROW_ERROR("Failed to serialize a uint16_t value", -r); + + return *this; +} + +Message& Message::operator<<(uint32_t item) +{ + auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_UINT32, &item); + if (r < 0) + SDBUS_THROW_ERROR("Failed to serialize a uint32_t value", -r); + + return *this; +} + +Message& Message::operator<<(uint64_t item) +{ + auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_UINT64, &item); + if (r < 0) + SDBUS_THROW_ERROR("Failed to serialize a uint64_t value", -r); + + return *this; +} + +Message& Message::operator<<(double item) +{ + auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_DOUBLE, &item); + if (r < 0) + SDBUS_THROW_ERROR("Failed to serialize a double value", -r); + + return *this; +} + +Message& Message::operator<<(const char* item) +{ + auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_STRING, item); + if (r < 0) + SDBUS_THROW_ERROR("Failed to serialize a C-string value", -r); + + return *this; +} + +Message& Message::operator<<(const std::string& item) +{ + auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_STRING, item.c_str()); + if (r < 0) + SDBUS_THROW_ERROR("Failed to serialize a string value", -r); + + return *this; +} + +Message& Message::operator<<(const Variant &item) +{ + item.serializeTo(*this); + + return *this; +} + +Message& Message::operator<<(const ObjectPath &item) +{ + auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_OBJECT_PATH, item.c_str()); + if (r < 0) + SDBUS_THROW_ERROR("Failed to serialize an ObjectPath value", -r); + + return *this; +} + +Message& Message::operator<<(const Signature &item) +{ + auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_SIGNATURE, item.c_str()); + if (r < 0) + SDBUS_THROW_ERROR("Failed to serialize an Signature value", -r); + + return *this; +} + + +Message& Message::operator>>(bool& item) +{ + int intItem; + auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_BOOLEAN, &intItem); + if (r == 0) + ok_ = false; + else if (r < 0) + SDBUS_THROW_ERROR("Failed to deserialize a bool value", -r); + + item = static_cast(intItem); + + return *this; +} + +Message& Message::operator>>(int16_t& item) +{ + auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_INT16, &item); + if (r == 0) + ok_ = false; + else if (r < 0) + SDBUS_THROW_ERROR("Failed to deserialize a int16_t value", -r); + + return *this; +} + +Message& Message::operator>>(int32_t& item) +{ + auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_INT32, &item); + if (r == 0) + ok_ = false; + else if (r < 0) + SDBUS_THROW_ERROR("Failed to deserialize a int32_t value", -r); + + return *this; +} + +Message& Message::operator>>(int64_t& item) +{ + auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_INT64, &item); + if (r == 0) + ok_ = false; + else if (r < 0) + SDBUS_THROW_ERROR("Failed to deserialize a bool value", -r); + + return *this; +} + +Message& Message::operator>>(uint8_t& item) +{ + auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_BYTE, &item); + if (r == 0) + ok_ = false; + else if (r < 0) + SDBUS_THROW_ERROR("Failed to deserialize a byte value", -r); + + return *this; +} + +Message& Message::operator>>(uint16_t& item) +{ + auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_UINT16, &item); + if (r == 0) + ok_ = false; + else if (r < 0) + SDBUS_THROW_ERROR("Failed to deserialize a uint16_t value", -r); + + return *this; +} + +Message& Message::operator>>(uint32_t& item) +{ + auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_UINT32, &item); + if (r == 0) + ok_ = false; + else if (r < 0) + SDBUS_THROW_ERROR("Failed to deserialize a uint32_t value", -r); + + return *this; +} + +Message& Message::operator>>(uint64_t& item) +{ + auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_UINT64, &item); + if (r == 0) + ok_ = false; + else if (r < 0) + SDBUS_THROW_ERROR("Failed to deserialize a uint64_t value", -r); + + return *this; +} + +Message& Message::operator>>(double& item) +{ + auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_DOUBLE, &item); + if (r == 0) + ok_ = false; + else if (r < 0) + SDBUS_THROW_ERROR("Failed to deserialize a double value", -r); + + return *this; +} + +Message& Message::operator>>(char*& item) +{ + auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_STRING, &item); + if (r == 0) + ok_ = false; + else if (r < 0) + SDBUS_THROW_ERROR("Failed to deserialize a string value", -r); + + return *this; +} + +Message& Message::operator>>(std::string& item) +{ + char* str{}; + (*this) >> str; + + if (str != nullptr) + item = str; + + return *this; +} + +Message& Message::operator>>(Variant &item) +{ + item.deserializeFrom(*this); + + // Empty variant is normally prohibited. Users cannot send empty variants. + // Therefore in this context an empty variant means that we are at the end + // of deserializing a container, and thus we shall set ok_ flag to false. + if (item.isEmpty()) + ok_ = false; + + return *this; +} + +Message& Message::operator>>(ObjectPath &item) +{ + char* str{}; + auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_OBJECT_PATH, &str); + if (r == 0) + ok_ = false; + else if (r < 0) + SDBUS_THROW_ERROR("Failed to deserialize an ObjectPath value", -r); + + if (str != nullptr) + item = str; + + return *this; +} + +Message& Message::operator>>(Signature &item) +{ + char* str{}; + auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_SIGNATURE, &str); + if (r == 0) + ok_ = false; + else if (r < 0) + SDBUS_THROW_ERROR("Failed to deserialize a Signature value", -r); + + if (str != nullptr) + item = str; + + return *this; +} + + +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()); + if (r < 0) + SDBUS_THROW_ERROR("Failed to open a container", -r); + + return *this; +} + +Message& Message::closeContainer() +{ + auto r = sd_bus_message_close_container((sd_bus_message*)msg_); + if (r < 0) + SDBUS_THROW_ERROR("Failed to close a container", -r); + + return *this; +} + +Message& Message::openDictEntry(const std::string& signature) +{ + auto r = sd_bus_message_open_container((sd_bus_message*)msg_, SD_BUS_TYPE_DICT_ENTRY, signature.c_str()); + if (r < 0) + SDBUS_THROW_ERROR("Failed to open a dictionary entry", -r); + + return *this; +} + +Message& Message::closeDictEntry() +{ + auto r = sd_bus_message_close_container((sd_bus_message*)msg_); + if (r < 0) + SDBUS_THROW_ERROR("Failed to close a dictionary entry", -r); + + return *this; +} + +Message& Message::openVariant(const std::string& signature) +{ + auto r = sd_bus_message_open_container((sd_bus_message*)msg_, SD_BUS_TYPE_VARIANT, signature.c_str()); + if (r < 0) + SDBUS_THROW_ERROR("Failed to open a variant", -r); + + return *this; +} + +Message& Message::closeVariant() +{ + auto r = sd_bus_message_close_container((sd_bus_message*)msg_); + if (r < 0) + SDBUS_THROW_ERROR("Failed to close a variant", -r); + + return *this; +} + +Message& Message::openStruct(const std::string& signature) +{ + auto r = sd_bus_message_open_container((sd_bus_message*)msg_, SD_BUS_TYPE_STRUCT, signature.c_str()); + if (r < 0) + SDBUS_THROW_ERROR("Failed to open a struct", -r); + + return *this; +} + +Message& Message::closeStruct() +{ + auto r = sd_bus_message_close_container((sd_bus_message*)msg_); + if (r < 0) + SDBUS_THROW_ERROR("Failed to close a struct", -r); + + return *this; +} + + +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()); + if (r == 0) + ok_ = false; + else if (r < 0) + SDBUS_THROW_ERROR("Failed to enter a container", -r); + + return *this; +} + +Message& Message::exitContainer() +{ + auto r = sd_bus_message_exit_container((sd_bus_message*)msg_); + if (r < 0) + SDBUS_THROW_ERROR("Failed to exit a container", -r); + + return *this; +} + +Message& Message::enterDictEntry(const std::string& signature) +{ + auto r = sd_bus_message_enter_container((sd_bus_message*)msg_, SD_BUS_TYPE_DICT_ENTRY, signature.c_str()); + if (r == 0) + ok_ = false; + else if (r < 0) + SDBUS_THROW_ERROR("Failed to enter a dictionary entry", -r); + + return *this; +} + +Message& Message::exitDictEntry() +{ + auto r = sd_bus_message_exit_container((sd_bus_message*)msg_); + if (r < 0) + SDBUS_THROW_ERROR("Failed to exit a dictionary entry", -r); + + return *this; +} + +Message& Message::enterVariant(const std::string& signature) +{ + auto r = sd_bus_message_enter_container((sd_bus_message*)msg_, SD_BUS_TYPE_VARIANT, signature.c_str()); + if (r == 0) + ok_ = false; + else if (r < 0) + SDBUS_THROW_ERROR("Failed to enter a variant", -r); + + return *this; +} + +Message& Message::exitVariant() +{ + auto r = sd_bus_message_exit_container((sd_bus_message*)msg_); + if (r < 0) + SDBUS_THROW_ERROR("Failed to exit a variant", -r); + + return *this; +} + +Message& Message::enterStruct(const std::string& signature) +{ + auto r = sd_bus_message_enter_container((sd_bus_message*)msg_, SD_BUS_TYPE_STRUCT, signature.c_str()); + if (r == 0) + ok_ = false; + else if (r < 0) + SDBUS_THROW_ERROR("Failed to enter a struct", -r); + + return *this; +} + +Message& Message::exitStruct() +{ + auto r = sd_bus_message_exit_container((sd_bus_message*)msg_); + if (r < 0) + SDBUS_THROW_ERROR("Failed to exit a struct", -r); + + return *this; +} + + +Message::operator bool() const +{ + return ok_; +} + +void Message::clearFlags() +{ + ok_ = true; +} + +void Message::copyTo(Message& destination, bool complete) const +{ + auto r = sd_bus_message_copy((sd_bus_message*)destination.msg_, (sd_bus_message*)msg_, complete); + if (r < 0) + SDBUS_THROW_ERROR("Failed to copy the message", -r); +} + +void Message::seal() +{ + const auto messageCookie = 1; + const auto sealTimeout = 0; + auto r = sd_bus_message_seal((sd_bus_message*)msg_, messageCookie, sealTimeout); + if (r < 0) + SDBUS_THROW_ERROR("Failed to seal the message", -r); +} + +void Message::rewind(bool complete) +{ + auto r = sd_bus_message_rewind((sd_bus_message*)msg_, complete); + if (r < 0) + SDBUS_THROW_ERROR("Failed to rewind the message", -r); +} + +Message Message::send() const +{ + if (type_ == Type::eMethodCall) + { + sd_bus_message* sdbusReply{}; + SCOPE_EXIT{ sd_bus_message_unref(sdbusReply); }; // Returned message will become an owner of sdbusReply + sd_bus_error sdbusError = SD_BUS_ERROR_NULL; + SCOPE_EXIT{ sd_bus_error_free(&sdbusError); }; + + auto r = sd_bus_call(nullptr, (sd_bus_message*)msg_, 0, &sdbusError, &sdbusReply); + + if (sd_bus_error_is_set(&sdbusError)) + { + throw sdbus::Error(sdbusError.name, sdbusError.message); + } + + SDBUS_THROW_ERROR_IF(r < 0, "Failed to call method", -r); + + return Message(sdbusReply); + } + else if (type_ == Type::eMethodReply) + { + auto r = sd_bus_send(nullptr, (sd_bus_message*)msg_, nullptr); + if (r < 0) + SDBUS_THROW_ERROR("Failed to send reply", -r); + + return Message(); + } + else if (type_ == Type::eSignal) + { + auto r = sd_bus_send(nullptr, (sd_bus_message*)msg_, nullptr); + if (r < 0) + SDBUS_THROW_ERROR("Failed to emit signal", -r); + + return Message(); + } + else + { + assert(false); + return Message(); + } +} + +Message Message::createReply() const +{ + sd_bus_message *sdbusReply{}; + SCOPE_EXIT{ sd_bus_message_unref(sdbusReply); }; // Returned message will become an owner of sdbusReply + auto r = sd_bus_message_new_method_return((sd_bus_message*)msg_, &sdbusReply); + if (r < 0) + SDBUS_THROW_ERROR("Failed to create method reply", -r); + + assert(sdbusReply != nullptr); + + return Message(sdbusReply, Type::eMethodReply); +} + +std::string Message::getInterfaceName() const +{ + return sd_bus_message_get_interface((sd_bus_message*)msg_); +} + +std::string Message::getMemberName() const +{ + return sd_bus_message_get_member((sd_bus_message*)msg_); +} + +void Message::peekType(std::string& type, std::string& contents) const +{ + char typeSig; + const char* contentsSig; + auto r = sd_bus_message_peek_type((sd_bus_message*)msg_, &typeSig, &contentsSig); + if (r < 0) + SDBUS_THROW_ERROR("Failed to peek message type", -r); + type = typeSig; + contents = contentsSig; +} + +bool Message::isValid() const +{ + return msg_ != nullptr; +} + +bool Message::isEmpty() const +{ + return sd_bus_message_is_empty((sd_bus_message*)msg_); +} + +Message::Type Message::getType() const +{ + return type_; +} + +Message createPlainMessage() +{ + int r; + + sd_bus* bus{}; + SCOPE_EXIT{ sd_bus_unref(bus); }; // sdbusMsg will hold reference to the bus + r = sd_bus_default_system(&bus); + if (r < 0) + SDBUS_THROW_ERROR("Failed to get default system bus", -r); + + sd_bus_message* sdbusMsg{}; + SCOPE_EXIT{ sd_bus_message_unref(sdbusMsg); }; // Returned message will become an owner of sdbusMsg + r = sd_bus_message_new(bus, &sdbusMsg, _SD_BUS_MESSAGE_TYPE_INVALID); + if (r < 0) + SDBUS_THROW_ERROR("Failed to create a new message", -r); + + return Message(sdbusMsg, Message::Type::ePlainMessage); +} + +/*}*/} diff --git a/src/MessageUtils.h b/src/MessageUtils.h new file mode 100644 index 0000000..d4cc361 --- /dev/null +++ b/src/MessageUtils.h @@ -0,0 +1,36 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file MessageUtils.h + * + * Created on: Dec 5, 2016 + * 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 . + */ + +#ifndef SDBUS_CXX_INTERNAL_MESSAGEUTILS_H_ +#define SDBUS_CXX_INTERNAL_MESSAGEUTILS_H_ + +#include + +namespace sdbus +{ + Message createPlainMessage(); +} + +#endif /* SDBUS_CXX_INTERNAL_MESSAGEUTILS_H_ */ diff --git a/src/Object.cpp b/src/Object.cpp new file mode 100755 index 0000000..48a8967 --- /dev/null +++ b/src/Object.cpp @@ -0,0 +1,261 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file Object.cpp + * + * Created on: Nov 8, 2016 + * 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 . + */ + +#include "Object.h" +#include +#include +#include +#include "IConnection.h" +#include "VTableUtils.h" +#include +#include +#include + +namespace sdbus { namespace internal { + +Object::Object(sdbus::internal::IConnection& connection, std::string objectPath) + : connection_(connection), objectPath_(std::move(objectPath)) +{ +} + +void Object::registerMethod( const std::string& interfaceName + , const std::string& methodName + , const std::string& inputSignature + , const std::string& outputSignature + , method_callback methodCallback ) +{ + SDBUS_THROW_ERROR_IF(!methodCallback, "Invalid method callback provided", EINVAL); + + auto& interface = interfaces_[interfaceName]; + + InterfaceData::MethodData methodData{inputSignature, outputSignature, std::move(methodCallback)}; + auto inserted = interface.methods_.emplace(methodName, std::move(methodData)).second; + + SDBUS_THROW_ERROR_IF(!inserted, "Failed to register method: method already exists", EINVAL); +} + +void Object::registerSignal( const std::string& interfaceName + , const std::string& signalName + , const std::string& signature ) +{ + auto& interface = interfaces_[interfaceName]; + + InterfaceData::SignalData signalData{signature}; + auto inserted = interface.signals_.emplace(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 + , const std::string& propertyName + , const std::string& signature + , property_get_callback getCallback ) +{ + registerProperty( interfaceName + , propertyName + , signature + , getCallback + , property_set_callback{} ); +} + +void Object::registerProperty( const std::string& interfaceName + , const std::string& propertyName + , const std::string& signature + , property_get_callback getCallback + , property_set_callback setCallback ) +{ + SDBUS_THROW_ERROR_IF(!getCallback && !setCallback, "Invalid property callbacks provided", EINVAL); + + auto& interface = interfaces_[interfaceName]; + + InterfaceData::PropertyData propertyData{signature, std::move(getCallback), std::move(setCallback)}; + auto inserted = interface.properties_.emplace(propertyName, std::move(propertyData)).second; + + SDBUS_THROW_ERROR_IF(!inserted, "Failed to register property: property already exists", EINVAL); +} + +void Object::finishRegistration() +{ + for (auto& item : interfaces_) + { + const auto& interfaceName = item.first; + auto& interfaceData = item.second; + + auto& vtable = interfaceData.vtable_; + assert(vtable.empty()); + + vtable.push_back(createVTableStartItem()); + for (const auto& item : interfaceData.methods_) + { + const auto& methodName = item.first; + const auto& methodData = item.second; + + vtable.push_back(createVTableMethodItem( methodName.c_str() + , methodData.inputArgs_.c_str() + , methodData.outputArgs_.c_str() + , &Object::sdbus_method_callback )); + } + for (const auto& item : interfaceData.signals_) + { + const auto& signalName = item.first; + const auto& signalData = item.second; + + vtable.push_back(createVTableSignalItem( signalName.c_str() + , signalData.signature_.c_str() )); + } + for (const auto& item : interfaceData.properties_) + { + const auto& propertyName = item.first; + const auto& propertyData = item.second; + + if (!propertyData.setCallback_) + vtable.push_back(createVTablePropertyItem( propertyName.c_str() + , propertyData.signature_.c_str() + , &Object::sdbus_property_get_callback )); + else + vtable.push_back(createVTableWritablePropertyItem( propertyName.c_str() + , propertyData.signature_.c_str() + , &Object::sdbus_property_get_callback + , &Object::sdbus_property_set_callback )); + } + vtable.push_back(createVTableEndItem()); + + // Tell, don't ask + auto slot = (sd_bus_slot*) connection_.addObjectVTable(objectPath_, interfaceName, &vtable[0], this); + interfaceData.slot_.reset(slot); + interfaceData.slot_.get_deleter() = [this](void *slot){ connection_.removeObjectVTable(slot); }; + } +} + +sdbus::Message Object::createSignal(const std::string& interfaceName, const std::string& signalName) +{ + // Tell, don't ask + return connection_.createSignal(objectPath_, interfaceName, signalName); +} + +void Object::emitSignal(const sdbus::Message& message) +{ + message.send(); +} + +int Object::sdbus_method_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError) +{ + Message message(sdbusMessage, Message::Type::eMethodCall); + + auto* object = static_cast(userData); + // Note: The lookup can be optimized by using sorted vectors instead of associative containers + auto& callback = object->interfaces_[message.getInterfaceName()].methods_[message.getMemberName()].callback_; + assert(callback); + + auto reply = message.createReply(); + + try + { + callback(message, reply); + } + catch (const sdbus::Error& e) + { + sd_bus_error_set(retError, e.getName().c_str(), e.getMessage().c_str()); + return 1; + } + + reply.send(); + + return 1; +} + +int Object::sdbus_property_get_callback( sd_bus */*bus*/ + , const char */*objectPath*/ + , const char *interface + , const char *property + , sd_bus_message *sdbusReply + , void *userData + , sd_bus_error *retError ) +{ + Message reply(sdbusReply, Message::Type::ePlainMessage); + + auto* object = static_cast(userData); + // Note: The lookup can be optimized by using sorted vectors instead of associative containers + auto& callback = object->interfaces_[interface].properties_[property].getCallback_; + // Getter can be empty - the case of "write-only" property + if (!callback) + { + sd_bus_error_set(retError, "org.freedesktop.DBus.Error.Failed", "Cannot read property as it is write-only"); + return 1; + } + + try + { + callback(reply); + } + catch (const sdbus::Error& e) + { + sd_bus_error_set(retError, e.getName().c_str(), e.getMessage().c_str()); + } + + return 1; +} + +int Object::sdbus_property_set_callback( sd_bus */*bus*/ + , const char */*objectPath*/ + , const char *interface + , const char *property + , sd_bus_message *sdbusValue + , void *userData + , sd_bus_error *retError ) +{ + Message value(sdbusValue, Message::Type::ePlainMessage); + + auto* object = static_cast(userData); + // Note: The lookup can be optimized by using sorted vectors instead of associative containers + auto& callback = object->interfaces_[interface].properties_[property].setCallback_; + assert(callback); + + try + { + callback(value); + } + catch (const sdbus::Error& e) + { + sd_bus_error_set(retError, e.getName().c_str(), e.getMessage().c_str()); + return 1; + } + + return 1; +} + +}} + +namespace sdbus { + +std::unique_ptr createObject(sdbus::IConnection& connection, std::string objectPath) +{ + auto* sdbusConnection = dynamic_cast(&connection); + SDBUS_THROW_ERROR_IF(!sdbusConnection, "Connection is not a real sdbus-c++ connection", EINVAL); + + return std::make_unique(*sdbusConnection, std::move(objectPath)); +} + +} diff --git a/src/Object.h b/src/Object.h new file mode 100644 index 0000000..d301967 --- /dev/null +++ b/src/Object.h @@ -0,0 +1,129 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file Object.h + * + * Created on: Nov 8, 2016 + * 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 . + */ + +#ifndef SDBUS_CXX_INTERNAL_OBJECT_H_ +#define SDBUS_CXX_INTERNAL_OBJECT_H_ + +#include +#include "IConnection.h" +#include +#include +#include +#include +#include +#include + +namespace sdbus { +namespace internal { + + class Object + : public IObject + { + public: + Object(sdbus::internal::IConnection& connection, std::string objectPath); + + void registerMethod( const std::string& interfaceName + , const std::string& methodName + , const std::string& inputSignature + , const std::string& outputSignature + , method_callback methodCallback ) override; + + void registerSignal( const std::string& interfaceName + , const std::string& signalName + , const std::string& signature ) override; + + void registerProperty( const std::string& interfaceName + , const std::string& propertyName + , const std::string& signature + , property_get_callback getCallback ) override; + + void registerProperty( const std::string& interfaceName + , const std::string& propertyName + , const std::string& signature + , property_get_callback getCallback + , property_set_callback setCallback ) override; + + void finishRegistration() override; + + sdbus::Message createSignal(const std::string& interfaceName, const std::string& signalName) override; + void emitSignal(const sdbus::Message& message) override; + + private: + static int sdbus_method_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError); + static int sdbus_property_get_callback( sd_bus *bus + , const char *objectPath + , const char *interface + , const char *property + , sd_bus_message *sdbusReply + , void *userData + , sd_bus_error *retError ); + static int sdbus_property_set_callback( sd_bus *bus + , const char *objectPath + , const char *interface + , const char *property + , sd_bus_message *sdbusValue + , void *userData + , sd_bus_error *retError ); + + private: + sdbus::internal::IConnection& connection_; + std::string objectPath_; + + using InterfaceName = std::string; + struct InterfaceData + { + using MethodName = std::string; + struct MethodData + { + std::string inputArgs_; + std::string outputArgs_; + method_callback callback_; + }; + std::map methods_; + using SignalName = std::string; + struct SignalData + { + std::string signature_; + }; + std::map signals_; + using PropertyName = std::string; + struct PropertyData + { + std::string signature_; + property_get_callback getCallback_; + property_set_callback setCallback_; + }; + std::map properties_; + std::vector vtable_; + + std::unique_ptr> slot_; + }; + std::map interfaces_; + }; + +} +} + +#endif /* SDBUS_CXX_INTERNAL_OBJECT_H_ */ diff --git a/src/ObjectProxy.cpp b/src/ObjectProxy.cpp new file mode 100755 index 0000000..a88018a --- /dev/null +++ b/src/ObjectProxy.cpp @@ -0,0 +1,182 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file ObjectProxy.cpp + * + * Created on: Nov 8, 2016 + * 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 . + */ + +#include "ObjectProxy.h" +#include +#include +#include +#include "IConnection.h" +#include +#include + +namespace sdbus { namespace internal { + +ObjectProxy::ObjectProxy(sdbus::internal::IConnection& connection, std::string destination, std::string objectPath) + : connection_(&connection, [](sdbus::internal::IConnection *){ /* Intentionally left empty */ }) + , ownConnection_(false) + , destination_(std::move(destination)) + , objectPath_(std::move(objectPath)) +{ +} + +ObjectProxy::ObjectProxy( std::unique_ptr&& connection + , std::string destination + , std::string objectPath ) + : connection_(std::move(connection)) + , ownConnection_(true) + , destination_(std::move(destination)) + , objectPath_(std::move(objectPath)) +{ +} + +ObjectProxy::~ObjectProxy() +{ + // If the dedicated connection for signals is used, we have to stop the processing loop + // upon this connection prior to unregistering signal slots in the interfaces_ container, + // otherwise we might have a race condition of two threads working upon one connection. + if (signalConnection_ != nullptr) + signalConnection_->leaveProcessingLoop(); +} + +Message ObjectProxy::createMethodCall(const std::string& interfaceName, const std::string& methodName) +{ + // Tell, don't ask + return connection_->createMethodCall(destination_, objectPath_, interfaceName, methodName); +} + +Message ObjectProxy::callMethod(const Message& message) +{ + return message.send(); +} + +void ObjectProxy::registerSignalHandler( const std::string& interfaceName + , const std::string& signalName + , signal_handler signalHandler ) +{ + SDBUS_THROW_ERROR_IF(!signalHandler, "Invalid signal handler provided", EINVAL); + + auto& interface = interfaces_[interfaceName]; + + InterfaceData::SignalData signalData{std::move(signalHandler), nullptr}; + auto insertionResult = interface.signals_.emplace(signalName, std::move(signalData)); + + auto inserted = insertionResult.second; + SDBUS_THROW_ERROR_IF(!inserted, "Failed to register signal handler: handler already exists", EINVAL); +} + +void ObjectProxy::finishRegistration() +{ + bool hasSignals = listensToSignals(); + + if (hasSignals && ownConnection_) + { + // Let's use dedicated signalConnection_ for signals, + // which will then be used by the processing loop thread. + signalConnection_ = connection_->clone(); + registerSignalHandlers(*signalConnection_); + signalConnection_->enterProcessingLoopAsync(); + } + else if (hasSignals) + { + // Let's used connection provided from the outside. + registerSignalHandlers(*connection_); + } +} + +bool ObjectProxy::listensToSignals() const +{ + for (auto& interfaceItem : interfaces_) + if (!interfaceItem.second.signals_.empty()) + return true; + + return false; +} + +void ObjectProxy::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& slot = signalItem.second.slot_; + auto* rawSlotPtr = connection.registerSignalHandler( objectPath_ + , interfaceName + , signalName + , &ObjectProxy::sdbus_signal_callback + , this ); + slot.reset(rawSlotPtr); + slot.get_deleter() = [&connection](void *slot){ connection.unregisterSignalHandler(slot); }; + } + } +} + +int ObjectProxy::sdbus_signal_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error */*retError*/) +{ + Message message(sdbusMessage, Message::Type::eSignal); + + auto* object = static_cast(userData); + // Note: The lookup can be optimized by using sorted vectors instead of associative containers + auto& callback = object->interfaces_[message.getInterfaceName()].signals_[message.getMemberName()].callback_; + assert(callback); + + callback(message); + + return 1; +} + +}} + +namespace sdbus { + +std::unique_ptr createObjectProxy( IConnection& connection + , std::string destination + , std::string objectPath ) +{ + auto* sdbusConnection = dynamic_cast(&connection); + SDBUS_THROW_ERROR_IF(!sdbusConnection, "Connection is not a real sdbus-c++ connection", EINVAL); + + return std::make_unique( *sdbusConnection + , std::move(destination) + , std::move(objectPath) ); +} + +std::unique_ptr createObjectProxy( std::string destination + , std::string objectPath ) +{ + auto connection = sdbus::createConnection(); + + auto sdbusConnection = std::unique_ptr(dynamic_cast(connection.release())); + assert(sdbusConnection != nullptr); + + return std::make_unique( std::move(sdbusConnection) + , std::move(destination) + , std::move(objectPath) ); +} + +} diff --git a/src/ObjectProxy.h b/src/ObjectProxy.h new file mode 100755 index 0000000..b80beac --- /dev/null +++ b/src/ObjectProxy.h @@ -0,0 +1,93 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file ObjectProxy.h + * + * Created on: Nov 8, 2016 + * 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 . + */ + +#ifndef SDBUS_CXX_INTERNAL_OBJECTPROXY_H_ +#define SDBUS_CXX_INTERNAL_OBJECTPROXY_H_ + +#include +#include +#include +#include +#include + +// Forward declarations +namespace sdbus { namespace internal { + class IConnection; +}} + +namespace sdbus { +namespace internal { + + class ObjectProxy + : public IObjectProxy + { + public: + ObjectProxy( sdbus::internal::IConnection& connection + , std::string destination + , std::string objectPath ); + ObjectProxy( std::unique_ptr&& connection + , std::string destination + , std::string objectPath ); + ~ObjectProxy(); + + Message createMethodCall(const std::string& interfaceName, const std::string& methodName) override; + Message callMethod(const Message& message) override; + + void registerSignalHandler( const std::string& interfaceName + , const std::string& signalName + , signal_handler signalHandler ) override; + void finishRegistration() override; + + private: + bool listensToSignals() const; + void registerSignalHandlers(sdbus::internal::IConnection& connection); + static int sdbus_signal_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError); + + private: + std::unique_ptr< sdbus::internal::IConnection + , std::function + > connection_; + bool ownConnection_{}; + std::unique_ptr signalConnection_; + std::string destination_; + std::string objectPath_; + + using InterfaceName = std::string; + struct InterfaceData + { + using SignalName = std::string; + struct SignalData + { + signal_handler callback_; + std::unique_ptr> slot_; + }; + std::map signals_; + }; + std::map interfaces_; + }; + +}} + +#endif /* SDBUS_CXX_INTERNAL_OBJECTPROXY_H_ */ diff --git a/src/ScopeGuard.h b/src/ScopeGuard.h new file mode 100644 index 0000000..a211481 --- /dev/null +++ b/src/ScopeGuard.h @@ -0,0 +1,131 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file ScopeGuard.h + * + * Created on: Apr 29, 2015 + * 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 . + */ + +#ifndef SDBUS_CPP_INTERNAL_SCOPEGUARD_H_ +#define SDBUS_CPP_INTERNAL_SCOPEGUARD_H_ + +#include + +// Straightforward, modern, easy-to-use RAII utility to perform work on scope exit in an exception-safe manner. +// +// The utility helps providing basic exception safety guarantee by ensuring that the resources are always +// released in face of an exception and released or kept when exiting the scope normally. +// +// Use SCOPE_EXIT if you'd like to perform an (mostly clean-up) operation when the scope ends, either due +// to an exception or because it just ends normally. +// Use SCOPE_EXIT_NAMED if you'd like to conditionally deactivate given scope-exit operation. This is useful +// if, for example, we want the operation to be executed only in face of an exception. +// +// Example usage (maybe a bit contrived): +// SqlDb* g_pDb = nullptr; +// void fnc() { +// auto* pDb = open_database(...); // Resource to be released when scope exits due to whatever reason +// SCOPE_EXIT{ close_database(pDb); }; // Executes body when exiting the scope due to whatever reason +// g_pDb = open_database(...); // Resource to be released only in face of an exception +// SCOPE_EXIT_NAMED(releaseGlobalDbOnException) // Executes body when exiting the scope in face of an exception +// { +// close_database(g_pDb); +// g_pDb = nullptr; +// }; +// //... do operations (that may or may not possibly throw) on the database here +// releaseGlobalDbOnException.dismiss(); // Don't release global DB on normal scope exit +// return; // exiting scope normally +// } + +#define SCOPE_EXIT \ + auto ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE) \ + = ::skybase::utils::detail::ScopeGuardOnExit() + [&]() \ + /**/ + +#define SCOPE_EXIT_NAMED(NAME) \ + auto NAME \ + = ::skybase::utils::detail::ScopeGuardOnExit() + [&]() \ + /**/ + +namespace skybase { +namespace utils { + + template + class ScopeGuard + { + _Fun fnc_; + bool active_; + + public: + ScopeGuard(_Fun f) + : fnc_(std::move(f)) + , active_(true) + { + } + ~ScopeGuard() + { + if (active_) + fnc_(); + } + void dismiss() + { + active_ = false; + } + ScopeGuard() = delete; + ScopeGuard(const ScopeGuard&) = delete; + ScopeGuard& operator=(const ScopeGuard&) = delete; + ScopeGuard(ScopeGuard&& rhs) + : fnc_(std::move(rhs.fnc_)) + , active_(rhs.active_) + { + rhs.dismiss(); + } + }; + + namespace detail + { + enum class ScopeGuardOnExit + { + }; + + // Helper function to auto-deduce type of the callable entity + template + ScopeGuard<_Fun> operator+(ScopeGuardOnExit, _Fun&& fnc) + { + return ScopeGuard<_Fun>(std::forward<_Fun>(fnc)); + } + } + +}} + +#define CONCATENATE_IMPL(s1, s2) s1##s2 +#define CONCATENATE(s1, s2) CONCATENATE_IMPL(s1, s2) + +#ifdef __COUNTER__ +#define ANONYMOUS_VARIABLE(str) \ + CONCATENATE(str, __COUNTER__) \ + /**/ +#else +#define ANONYMOUS_VARIABLE(str) \ + CONCATENATE(str, __LINE__) \ + /**/ +#endif + +#endif /* SDBUS_CPP_INTERNAL_SCOPEGUARD_H_ */ diff --git a/src/Types.cpp b/src/Types.cpp new file mode 100644 index 0000000..71cfa38 --- /dev/null +++ b/src/Types.cpp @@ -0,0 +1,65 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file Types.cpp + * + * Created on: Nov 30, 2016 + * 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 . + */ + +#include +#include +#include "MessageUtils.h" +#include +#include + +namespace sdbus { /*namespace internal {*/ + +Variant::Variant() + : msg_(createPlainMessage()) +{ +} + +void Variant::serializeTo(Message& msg) const +{ + SDBUS_THROW_ERROR_IF(isEmpty(), "Empty variant is not allowed", EINVAL); + msg_.rewind(true); + msg_.copyTo(msg, true); +} + +void Variant::deserializeFrom(Message& msg) +{ + msg.copyTo(msg_, false); + msg_.seal(); +} + +std::string Variant::peekValueType() const +{ + std::string type; + std::string contents; + msg_.peekType(type, contents); + return contents; +} + +bool Variant::isEmpty() const +{ + return msg_.isEmpty(); +} + +} diff --git a/src/VTableUtils.c b/src/VTableUtils.c new file mode 100644 index 0000000..8a7974d --- /dev/null +++ b/src/VTableUtils.c @@ -0,0 +1,72 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file VTableUtils.c + * + * Created on: Nov 8, 2016 + * 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 . + */ + +#include "VTableUtils.h" +#include + +sd_bus_vtable createVTableStartItem() +{ + struct sd_bus_vtable vtableStart = SD_BUS_VTABLE_START(0); + return vtableStart; +} + +sd_bus_vtable createVTableMethodItem( const char *member + , const char *signature + , const char *result + , sd_bus_message_handler_t handler ) +{ + struct sd_bus_vtable vtableItem = SD_BUS_METHOD(member, signature, result, handler, SD_BUS_VTABLE_UNPRIVILEGED); + return vtableItem; +} + +sd_bus_vtable createVTableSignalItem( const char *member + , const char *signature ) +{ + struct sd_bus_vtable vtableItem = SD_BUS_SIGNAL(member, signature, 0); + return vtableItem; +} + +sd_bus_vtable createVTablePropertyItem( const char *member + , const char *signature + , sd_bus_property_get_t getter ) +{ + struct sd_bus_vtable vtableItem = SD_BUS_PROPERTY(member, signature, getter, 0, 0); + return vtableItem; +} + +sd_bus_vtable createVTableWritablePropertyItem( const char *member + , const char *signature + , sd_bus_property_get_t getter + , sd_bus_property_set_t setter ) +{ + struct sd_bus_vtable vtableItem = SD_BUS_WRITABLE_PROPERTY(member, signature, getter, setter, 0, 0); + return vtableItem; +} + +sd_bus_vtable createVTableEndItem() +{ + struct sd_bus_vtable vtableEnd = SD_BUS_VTABLE_END; + return vtableEnd; +} diff --git a/src/VTableUtils.h b/src/VTableUtils.h new file mode 100644 index 0000000..67af0ad --- /dev/null +++ b/src/VTableUtils.h @@ -0,0 +1,55 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file VTableUtils.h + * + * Created on: Nov 8, 2016 + * 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 . + */ + +#ifndef SDBUS_CXX_INTERNAL_VTABLEUTILS_H_ +#define SDBUS_CXX_INTERNAL_VTABLEUTILS_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +sd_bus_vtable createVTableStartItem(); +sd_bus_vtable createVTableMethodItem( const char *member + , const char *signature + , const char *result + , sd_bus_message_handler_t handler ); +sd_bus_vtable createVTableSignalItem( const char *member + , const char *signature ); +sd_bus_vtable createVTablePropertyItem( const char *member + , const char *signature + , sd_bus_property_get_t getter ); +sd_bus_vtable createVTableWritablePropertyItem( const char *member + , const char *signature + , sd_bus_property_get_t getter + , sd_bus_property_set_t setter ); +sd_bus_vtable createVTableEndItem(); + +#ifdef __cplusplus +} +#endif + +#endif /* SDBUS_CXX_INTERNAL_VTABLEUTILS_H_ */ diff --git a/stub-generator/AdaptorGenerator.cpp b/stub-generator/AdaptorGenerator.cpp new file mode 100644 index 0000000..f50acad --- /dev/null +++ b/stub-generator/AdaptorGenerator.cpp @@ -0,0 +1,244 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file AdaptorGenerator.cpp + * + * Created on: Feb 1, 2017 + * Project: sdbus-c++ + * Description: High-level D-Bus IPC C++ library based on sd-bus + * + * This file is part of sdbus-c++. + * + * sdbus-c++ is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * sdbus-c++ is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with sdbus-c++. If not, see . + */ + +// Own +#include "generator_utils.h" +#include "AdaptorGenerator.h" + +// STL +#include +#include +#include +#include +#include + +using std::endl; + +using sdbuscpp::xml::Document; +using sdbuscpp::xml::Node; +using sdbuscpp::xml::Nodes; + +/** + * Generate adaptor code - server glue + */ +int AdaptorGenerator::transformXmlToFileImpl(const Document& doc, const char* filename) const +{ + Node &root = *(doc.root); + Nodes interfaces = root["interface"]; + + std::ostringstream code; + code << createHeader(filename, StubType::ADAPTOR); + + for (const auto& interface : interfaces) + { + code << processInterface(*interface); + } + + code << "#endif" << endl; + + return writeToFile(filename, code.str()); +} + + +std::string AdaptorGenerator::processInterface(Node& interface) const +{ + std::string ifaceName = interface.get("name"); + std::cout << "Generating adaptor code for interface " << ifaceName << endl; + + unsigned int namespacesCount = 0; + std::string namespacesStr; + std::tie(namespacesCount, namespacesStr) = generateNamespaces(ifaceName); + + std::ostringstream body; + body << namespacesStr; + + std::string className = ifaceName.substr(ifaceName.find_last_of(".") + 1) + + "_adaptor"; + + body << "class " << className << endl + << "{" << endl + << "public:" << endl + << tab << "static constexpr const char* interfaceName = \"" << ifaceName << "\";" << endl << endl + << "protected:" << endl + << tab << className << "(sdbus::IObject& object)" << endl + << tab << tab << ": object_(object)" << endl; + + Nodes methods = interface["method"]; + Nodes signals = interface["signal"]; + Nodes properties = interface["property"]; + + std::string methodRegistration, methodDeclaration; + std::tie(methodRegistration, methodDeclaration) = processMethods(methods); + + std::string signalRegistration, signalMethods; + std::tie(signalRegistration, signalMethods) = processSignals(signals); + + std::string propertyRegistration, propertyAccessorDeclaration; + std::tie(propertyRegistration, propertyAccessorDeclaration) = processProperties(properties); + + body << tab << "{" << endl + << methodRegistration + << signalRegistration + << propertyRegistration + << tab << "}" << endl << endl; + + if (!signalMethods.empty()) + { + body << "public:" << endl << signalMethods; + } + + if (!methodDeclaration.empty()) + { + body << "private:" << endl << methodDeclaration << endl; + } + + if (!propertyAccessorDeclaration.empty()) + { + body << "private:" << endl << propertyAccessorDeclaration << endl; + } + + body << "private:" << endl + << tab << "sdbus::IObject& object_;" << endl + << "};" << endl << endl + << std::string(namespacesCount, '}') << " // namespaces" << endl << endl; + + return body.str(); +} + + +std::tuple AdaptorGenerator::processMethods(const Nodes& methods) const +{ + std::ostringstream registrationSS, declarationSS; + + for (const auto& method : methods) + { + auto methodName = method->get("name"); + + Nodes args = (*method)["arg"]; + Nodes inArgs = args.select("direction" , "in"); + Nodes outArgs = args.select("direction" , "out"); + + std::string argStr, argTypeStr; + std::tie(argStr, argTypeStr, std::ignore) = argsToNamesAndTypes(inArgs); + + registrationSS << tab << tab << "object_.registerMethod(\"" + << methodName << "\")" + << ".onInterface(interfaceName)" + << ".implementedAs(" + << "[this](" + << argTypeStr + << "){ return this->" << methodName << "(" + << argStr << "); });" << endl; + + declarationSS << tab + << "virtual " << outArgsToType(outArgs) << " " << methodName + << "(" << argTypeStr << ") = 0;" << endl; + } + + return std::make_tuple(registrationSS.str(), declarationSS.str()); +} + + +std::tuple AdaptorGenerator::processSignals(const Nodes& signals) const +{ + std::ostringstream signalRegistrationSS, signalMethodSS; + + for (const auto& signal : signals) + { + auto name = signal->get("name"); + Nodes args = (*signal)["arg"]; + + std::string argStr, argTypeStr, typeStr;; + std::tie(argStr, argTypeStr, typeStr) = argsToNamesAndTypes(args); + + signalRegistrationSS << tab << tab + << "object_.registerSignal(\"" << name << "\")" + ".onInterface(interfaceName)"; + + if (args.size() > 0) + { + signalRegistrationSS << ".withParameters<" << typeStr << ">()"; + } + + signalRegistrationSS << ";" << endl; + + signalMethodSS << tab << "void " << name << "(" << argTypeStr << ")" << endl + << tab << "{" << endl + << tab << tab << "object_.emitSignal(\"" << name << "\")" + ".onInterface(interfaceName)"; + + if (!argStr.empty()) + { + signalMethodSS << ".withArguments(" << argStr << ")"; + } + + signalMethodSS << ";" << endl + << tab << "}" << endl << endl; + } + + return std::make_tuple(signalRegistrationSS.str(), signalMethodSS.str()); +} + + +std::tuple AdaptorGenerator::processProperties(const Nodes& properties) const +{ + std::ostringstream registrationSS, declarationSS; + + for (const auto& property : properties) + { + auto propertyName = property->get("name"); + auto propertyAccess = property->get("access"); + auto propertySignature = property->get("type"); + + auto propertyType = signature_to_type(propertySignature); + auto propertyArg = std::string("value"); + auto propertyTypeArg = std::string("const ") + propertyType + "& " + propertyArg; + + registrationSS << tab << tab << "object_.registerProperty(\"" + << propertyName << "\")" + << ".onInterface(interfaceName)"; + + if (propertyAccess == "read" || propertyAccess == "readwrite") + { + registrationSS << ".withGetter([this](){ return this->" << propertyName << "(); })"; + } + + if (propertyAccess == "readwrite" || propertyAccess == "write") + { + registrationSS + << ".withSetter([this](" << propertyTypeArg << ")" + "{ this->" << propertyName << "(" << propertyArg << "); })"; + } + + registrationSS << ";" << endl; + + if (propertyAccess == "read" || propertyAccess == "readwrite") + declarationSS << tab << "virtual " << propertyType << " " << propertyName << "() = 0;" << endl; + if (propertyAccess == "readwrite" || propertyAccess == "write") + declarationSS << tab << "virtual void " << propertyName << "(" << propertyTypeArg << ") = 0;" << endl; + } + + return std::make_tuple(registrationSS.str(), declarationSS.str()); +} diff --git a/stub-generator/AdaptorGenerator.h b/stub-generator/AdaptorGenerator.h new file mode 100644 index 0000000..6c17a76 --- /dev/null +++ b/stub-generator/AdaptorGenerator.h @@ -0,0 +1,80 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file AdaptorGenerator.h + * + * Created on: Feb 1, 2017 + * Project: sdbus-c++ + * Description: High-level D-Bus IPC C++ library based on sd-bus + * + * This file is part of sdbus-c++. + * + * sdbus-c++ is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * sdbus-c++ is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with sdbus-c++. If not, see . + */ + +#ifndef __SDBUSCPP_TOOLS_ADAPTOR_GENERATOR_H +#define __SDBUSCPP_TOOLS_ADAPTOR_GENERATOR_H + +// Own headers +#include "xml.h" +#include "BaseGenerator.h" + +// STL +#include + +class AdaptorGenerator : public BaseGenerator +{ +protected: + /** + * Transform xml to adaptor code + * @param doc + * @param filename + * @return 0 if ok + */ + int transformXmlToFileImpl(const sdbuscpp::xml::Document& doc, const char* filename) const override; + +private: + /** + * Generate source code for interface + * @param interface + * @return source code + */ + std::string processInterface(sdbuscpp::xml::Node& interface) const; + + /** + * Generate source code for methods + * @param methods + * @return tuple: registration of methods, declaration of abstract methods + */ + std::tuple processMethods(const sdbuscpp::xml::Nodes& methods) const; + + /** + * Generate source code for signals + * @param signals + * @return tuple: registration of signals, definition of signal methods + */ + std::tuple processSignals(const sdbuscpp::xml::Nodes& signals) const; + + /** + * Generate source code for properties + * @param properties + * @return tuple: registration of properties, declaration of property accessor virtual methods + */ + std::tuple processProperties(const sdbuscpp::xml::Nodes& properties) const; + +}; + + + +#endif //__SDBUSCPP_TOOLS_ADAPTOR_GENERATOR_H diff --git a/stub-generator/BaseGenerator.cpp b/stub-generator/BaseGenerator.cpp new file mode 100644 index 0000000..2d3db2f --- /dev/null +++ b/stub-generator/BaseGenerator.cpp @@ -0,0 +1,158 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file BaseGenerator.cpp + * + * Created on: Feb 1, 2017 + * Project: sdbus-c++ + * Description: High-level D-Bus IPC C++ library based on sd-bus + * + * This file is part of sdbus-c++. + * + * sdbus-c++ is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * sdbus-c++ is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with sdbus-c++. If not, see . + */ + +// Own +#include "generator_utils.h" +#include "BaseGenerator.h" + +// STL +#include +#include +#include +#include +#include +#include + + +using std::endl; + +using sdbuscpp::xml::Document; +using sdbuscpp::xml::Node; +using sdbuscpp::xml::Nodes; + + +int BaseGenerator::transformXmlToFile(const Document& doc, const char* filename) const +{ + return transformXmlToFileImpl(doc, filename); +} + + +int BaseGenerator::writeToFile(const char* filename, const std::string& data) const +{ + std::ofstream file(filename); + if (file.bad()) + { + std::cerr << "Unable to write file " << filename << endl; + return 1; + } + + file << data; + file.close(); + return 0; +} + +std::string BaseGenerator::createHeader(const char* filename, const StubType& stubType) const +{ + std::ostringstream head; + head << getHeaderComment(); + + std::string specialization = stubType == StubType::ADAPTOR ? "adaptor" : "proxy"; + + std::string cond_comp{"__sdbuscpp__" + underscorize(filename) + + "__" + specialization + "__H__"}; + + head << "#ifndef " << cond_comp << endl + << "#define " << cond_comp << endl << endl; + + head << "#include " << endl + << "#include " << endl + << "#include " << endl + << endl; + + return head.str(); +} + +std::tuple BaseGenerator::generateNamespaces(const std::string& ifaceName) const +{ + std::stringstream ss{ifaceName}; + std::ostringstream body; + unsigned count{0}; + + // prints the namespaces X and Y defined with + while (ss.str().find('.', ss.tellg()) != std::string::npos) + { + std::string nspace; + getline(ss, nspace, '.'); + body << "namespace " << nspace << " {" << endl; + ++count; + } + body << endl; + + return std::make_tuple(count, body.str()); +} + + +std::tuple BaseGenerator::argsToNamesAndTypes(const Nodes& args) const +{ + std::ostringstream argSS, argTypeSS, typeSS; + + bool firstArg{true}; + for (const auto& arg : args) + { + if (firstArg) firstArg = false; else { argSS << ", "; argTypeSS << ", "; typeSS << ", "; } + + auto argName = arg->get("name"); + auto type = signature_to_type(arg->get("type")); + argSS << argName; + argTypeSS << "const " << type << "& " << argName; + typeSS << type; + } + + return std::make_tuple(argSS.str(), argTypeSS.str(), typeSS.str()); +} + +/** + * + */ +std::string BaseGenerator::outArgsToType(const Nodes& args) const +{ + std::ostringstream retTypeSS; + + if (args.size() == 0) + { + retTypeSS << "void"; + } + else if (args.size() == 1) + { + const auto& arg = *args.begin(); + retTypeSS << signature_to_type(arg->get("type")); + } + else if (args.size() >= 2) + { + retTypeSS << "std::tuple<"; + + bool firstArg = true; + for (const auto& arg : args) + { + if (firstArg) firstArg = false; else retTypeSS << ", "; + retTypeSS << signature_to_type(arg->get("type")); + } + + retTypeSS << ">"; + } + + return retTypeSS.str(); +} + diff --git a/stub-generator/BaseGenerator.h b/stub-generator/BaseGenerator.h new file mode 100644 index 0000000..9f42b71 --- /dev/null +++ b/stub-generator/BaseGenerator.h @@ -0,0 +1,103 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file BaseGenerator.h + * + * Created on: Feb 1, 2017 + * Project: sdbus-c++ + * Description: High-level D-Bus IPC C++ library based on sd-bus + * + * This file is part of sdbus-c++. + * + * sdbus-c++ is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * sdbus-c++ is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with sdbus-c++. If not, see . + */ + +#ifndef __SDBUSCPP_TOOLS_BASE_GENERATOR_H +#define __SDBUSCPP_TOOLS_BASE_GENERATOR_H + +// Own headers +#include "xml.h" + +// STL +#include +#include + +class BaseGenerator +{ + +public: + int transformXmlToFile(const sdbuscpp::xml::Document& doc, const char* filename) const; + +protected: + enum class StubType + { + ADAPTOR, + PROXY + }; + + constexpr static const char *tab = " "; + + virtual ~BaseGenerator() {} + + /** + * Implementation of public function that is provided by inherited class + * @param doc + * @param filename + * @return + */ + virtual int transformXmlToFileImpl(const sdbuscpp::xml::Document& doc, const char* filename) const = 0; + + + /** + * Write data to file + * @param filename Written file + * @param data Data to write + * @return 0 if ok + */ + int writeToFile(const char* filename, const std::string& data) const; + + /** + * Crete header of file - include guard, includes + * @param filename + * @param stubType + * @return + */ + std::string createHeader(const char* filename, const StubType& stubType) const; + + /** + * Namespaces according to the interface name + * @param ifaceName + * @return tuple: count of namespaces, string with code + */ + std::tuple generateNamespaces(const std::string& ifaceName) const; + + /** + * Transform arguments into source code + * @param args + * @return tuple: argument names, argument types and names, argument types + */ + std::tuple argsToNamesAndTypes(const sdbuscpp::xml::Nodes& args) const; + + /** + * Output arguments to return type + * @param args + * @return return type + */ + std::string outArgsToType(const sdbuscpp::xml::Nodes& args) const; + +}; + + + +#endif //__SDBUSCPP_TOOLS_BASE_GENERATOR_H diff --git a/stub-generator/CMakeLists.txt b/stub-generator/CMakeLists.txt new file mode 100644 index 0000000..e68f252 --- /dev/null +++ b/stub-generator/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required (VERSION 3.3) + +add_definitions(-std=c++14) + +project (sdbuscpp-xml2cpp) + +add_executable(${PROJECT_NAME} xml2cpp.cpp xml.cpp generator_utils.cpp BaseGenerator.cpp AdaptorGenerator.cpp ProxyGenerator.cpp) + +find_package (EXPAT REQUIRED) + +target_link_libraries (${PROJECT_NAME} ${EXPAT_LIBRARIES}) + +install(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME} DESTINATION bin) diff --git a/stub-generator/ProxyGenerator.cpp b/stub-generator/ProxyGenerator.cpp new file mode 100644 index 0000000..6c6ce0e --- /dev/null +++ b/stub-generator/ProxyGenerator.cpp @@ -0,0 +1,224 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file ProxyGenerator.cpp + * + * Created on: Feb 1, 2017 + * Project: sdbus-c++ + * Description: High-level D-Bus IPC C++ library based on sd-bus + * + * This file is part of sdbus-c++. + * + * sdbus-c++ is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * sdbus-c++ is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with sdbus-c++. If not, see . + */ + +// Own +#include "generator_utils.h" +#include "ProxyGenerator.h" + +// STL +#include +#include +#include +#include + + +using std::endl; + +using sdbuscpp::xml::Document; +using sdbuscpp::xml::Node; +using sdbuscpp::xml::Nodes; + +/** + * Generate proxy code - client glue + */ +int ProxyGenerator::transformXmlToFileImpl(const Document &doc, const char *filename) const +{ + Node &root = *(doc.root); + Nodes interfaces = root["interface"]; + + std::ostringstream code; + code << createHeader(filename, StubType::PROXY); + + for (const auto& interface : interfaces) + { + code << processInterface(*interface); + } + + code << "#endif" << endl; + + return writeToFile(filename, code.str()); +} + + +std::string ProxyGenerator::processInterface(Node& interface) const +{ + std::string ifaceName = interface.get("name"); + std::cout << "Generating proxy code for interface " << ifaceName << endl; + + unsigned int namespacesCount = 0; + std::string namespacesStr; + std::tie(namespacesCount, namespacesStr) = generateNamespaces(ifaceName); + + std::ostringstream body; + body << namespacesStr; + + std::string className = ifaceName.substr(ifaceName.find_last_of(".") + 1) + + "_proxy"; + + body << "class " << className << endl + << "{" << endl + << "public:" << endl + << tab << "static constexpr const char* interfaceName = \"" << ifaceName << "\";" << endl << endl + << "protected:" << endl + << tab << className << "(sdbus::IObjectProxy& object)" << endl + << tab << tab << ": object_(object)" << endl; + + Nodes methods = interface["method"]; + Nodes signals = interface["signal"]; + Nodes properties = interface["property"]; + + std::string registration, declaration; + std::tie(registration, declaration) = processSignals(signals); + + body << tab << "{" << endl + << registration + << tab << "}" << endl << endl + << declaration << endl; + + std::string methodDefinitions = processMethods(methods); + if (!methodDefinitions.empty()) + { + body << "public:" << endl << methodDefinitions; + } + + std::string propertyDefinitions = processProperties(properties); + if (!propertyDefinitions.empty()) + { + body << "public:" << endl << propertyDefinitions; + } + + body << "private:" << endl + << tab << "sdbus::IObjectProxy& object_;" << endl + << "};" << endl << endl + << std::string(namespacesCount, '}') << " // namespaces" << endl << endl; + + return body.str(); +} + +std::string ProxyGenerator::processMethods(const Nodes& methods) const +{ + std::ostringstream methodSS; + for (const auto& method : methods) + { + auto name = method->get("name"); + Nodes args = (*method)["arg"]; + Nodes inArgs = args.select("direction" , "in"); + Nodes outArgs = args.select("direction" , "out"); + + auto retType = outArgsToType(outArgs); + std::string argStr, argTypeStr; + std::tie(argStr, argTypeStr, std::ignore) = argsToNamesAndTypes(inArgs); + + methodSS << tab << retType << " " << name << "(" << argTypeStr << ")" << endl + << tab << "{" << endl; + + if (outArgs.size() > 0) + { + methodSS << tab << tab << retType << " result;" << endl; + } + + methodSS << tab << tab << "object_.callMethod(\"" << name << "\")" + ".onInterface(interfaceName)"; + + if (inArgs.size() > 0) + { + methodSS << ".withArguments(" << argStr << ")"; + } + + if (outArgs.size() > 0) + { + methodSS << ".storeResultsTo(result);" << endl + << tab << tab << "return result"; + } + + methodSS << ";" << endl << tab << "}" << endl << endl; + } + + return methodSS.str(); +} + + +std::tuple ProxyGenerator::processSignals(const Nodes& signals) const +{ + std::ostringstream registrationSS, declarationSS; + + for (const auto& signal : signals) + { + auto name = signal->get("name"); + Nodes args = (*signal)["arg"]; + + auto nameBigFirst = name; + nameBigFirst[0] = islower(nameBigFirst[0]) ? nameBigFirst[0] + 'A' - 'a' : nameBigFirst[0]; + + std::string argStr, argTypeStr; + std::tie(argStr, argTypeStr, std::ignore) = argsToNamesAndTypes(args); + + registrationSS << tab << tab << "object_" + ".uponSignal(\"" << name << "\")" + ".onInterface(interfaceName)" + ".call([this](" << argTypeStr << ")" + "{ this->on" << nameBigFirst << "(" << argStr << "); });" << endl; + + declarationSS << tab << "virtual void on" << nameBigFirst << "(" << argTypeStr << ") = 0;" << endl; + } + + return std::make_tuple(registrationSS.str(), declarationSS.str()); +} + +std::string ProxyGenerator::processProperties(const Nodes& properties) const +{ + std::ostringstream propertySS; + for (const auto& property : properties) + { + auto propertyName = property->get("name"); + auto propertyAccess = property->get("access"); + auto propertySignature = property->get("type"); + + auto propertyType = signature_to_type(propertySignature); + auto propertyArg = std::string("value"); + auto propertyTypeArg = std::string("const ") + propertyType + "& " + propertyArg; + + if (propertyAccess == "read" || propertyAccess == "readwrite") + { + propertySS << tab << propertyType << " " << propertyName << "()" << endl + << tab << "{" << endl; + propertySS << tab << tab << "return object_.getProperty(\"" << propertyName << "\")" + ".onInterface(interfaceName)"; + propertySS << ";" << endl << tab << "}" << endl << endl; + } + + if (propertyAccess == "readwrite" || propertyAccess == "write") + { + propertySS << tab << "void " << propertyName << "(" << propertyTypeArg << ")" << endl + << tab << "{" << endl; + propertySS << tab << tab << "object_.setProperty(\"" << propertyName << "\")" + ".onInterface(interfaceName)" + ".toValue(" << propertyArg << ")"; + propertySS << ";" << endl << tab << "}" << endl << endl; + } + } + + return propertySS.str(); +} diff --git a/stub-generator/ProxyGenerator.h b/stub-generator/ProxyGenerator.h new file mode 100644 index 0000000..7743f61 --- /dev/null +++ b/stub-generator/ProxyGenerator.h @@ -0,0 +1,81 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file ProxyGenerator.h + * + * Created on: Feb 1, 2017 + * Project: sdbus-c++ + * Description: High-level D-Bus IPC C++ library based on sd-bus + * + * This file is part of sdbus-c++. + * + * sdbus-c++ is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * sdbus-c++ is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with sdbus-c++. If not, see . + */ + +#ifndef __SDBUSCPP_TOOLS_PROXY_GENERATOR_H +#define __SDBUSCPP_TOOLS_PROXY_GENERATOR_H + +// Own headers +#include "xml.h" +#include "BaseGenerator.h" + +// STL +#include + +class ProxyGenerator : public BaseGenerator +{ +protected: + /** + * Transform xml to proxy code + * @param doc + * @param filename + * @return 0 if ok + */ + int transformXmlToFileImpl(const sdbuscpp::xml::Document& doc, const char* filename) const override; + +private: + + /** + * Generate source code for interface + * @param interface + * @return source code + */ + std::string processInterface(sdbuscpp::xml::Node& interface) const; + + /** + * Generate method calls + * @param methods + * @return source code + */ + std::string processMethods(const sdbuscpp::xml::Nodes& methods) const; + + /** + * Generate code for handling signals + * @param signals + * @return tuple: registration, declaration of virtual methods + */ + std::tuple processSignals(const sdbuscpp::xml::Nodes& signals) const; + + /** + * Generate calls for properties + * @param properties + * @return source code + */ + std::string processProperties(const sdbuscpp::xml::Nodes& properties) const; + +}; + + + +#endif //__SDBUSCPP_TOOLS_PROXY_GENERATOR_H diff --git a/stub-generator/generator_utils.cpp b/stub-generator/generator_utils.cpp new file mode 100644 index 0000000..27eccb5 --- /dev/null +++ b/stub-generator/generator_utils.cpp @@ -0,0 +1,143 @@ +/** + * Inspired by: http://dbus-cplusplus.sourceforge.net/ + */ + +#include +#include +#include + +#include "generator_utils.h" + + +std::string underscorize(const std::string& str) +{ + std::string res = str; + for (unsigned int i = 0; i < res.length(); ++i) + { + if (!isalpha(res[i]) && !isdigit(res[i])) + { + res[i] = '_'; + } + } + return res; +} + +std::string stub_name(const std::string& name) +{ + return "_" + underscorize(name) + "_stub"; +} + +const char *atomic_type_to_string(char t) +{ + static std::map atos + { + { 'y', "uint8_t" }, + { 'b', "bool" }, + { 'n', "int16_t" }, + { 'q', "uint16_t" }, + { 'i', "int32_t" }, + { 'u', "uint32_t" }, + { 'x', "int64_t" }, + { 't', "uint64_t" }, + { 'd', "double" }, + { 's', "std::string" }, + { 'o', "sdbus::ObjectPath" }, + { 'g', "sdbus::Signature" }, + { 'v', "sdbus::Variant" }, + { '\0', "" } + }; + + if (atos.count(t)) + { + return atos[t]; + } + + return nullptr; +} + +static void _parse_signature(const std::string &signature, std::string &type, unsigned int &i, bool only_once = false) +{ + for (; i < signature.length(); ++i) + { + switch (signature[i]) + { + case 'a': + { + switch (signature[++i]) + { + case '{': + { + type += "std::map<"; + ++i; + _parse_signature(signature, type, i); + type += ">"; + + break; + } + case '(': + { + type += "std::vector +#include +#include + +const char *atomic_type_to_string(char t); + +std::string stub_name(const std::string& name); + +std::string signature_to_type(const std::string& signature); + +std::string underscorize(const std::string& str); + +constexpr const char* getHeaderComment() noexcept { return "\n/*\n * This file was automatically generated by sdbuscpp-xml2cpp; DO NOT EDIT!\n */\n\n"; } + +#endif //__SDBUSCPP_TOOLS_GENERATOR_UTILS_H diff --git a/stub-generator/xml.cpp b/stub-generator/xml.cpp new file mode 100644 index 0000000..3b1977c --- /dev/null +++ b/stub-generator/xml.cpp @@ -0,0 +1,309 @@ +/** + * Inspired by: http://dbus-cplusplus.sourceforge.net/ + */ + +#include "xml.h" + +#include + +using namespace sdbuscpp::xml; + +std::istream &operator >> (std::istream& in, Document& doc) +{ + std::stringbuf xmlbuf; + in.get(xmlbuf, '\0'); + doc.from_xml(xmlbuf.str()); + + return in; +} + +std::ostream &operator << (std::ostream& out, const Document& doc) +{ + return out << doc.to_xml(); +} + + +Error::Error(const char *error, int line, int column) +{ + std::ostringstream estream; + + estream << "line " << line << ", column " << column << ": " << error; + + m_error = estream.str(); +} + +Node::Node(const char *n, const char **a) +: name(n) +{ + if (a) + { + for (int i = 0; a[i]; i += 2) + { + m_attrs[a[i]] = a[i + 1]; + } + } +} + +Nodes Nodes::operator[](const std::string& key) const +{ + Nodes result; + + for (auto it = begin(), endIt = end(); it != endIt; ++it) + { + Nodes part = (**it)[key]; + + result.insert(result.end(), part.begin(), part.end()); + } + return result; +} + +Nodes Nodes::select(const std::string& attr, const std::string& value) const +{ + Nodes result; + + for (auto it = begin(), itEnd = end(); it != itEnd; ++it) + { + if ((*it)->get(attr) == value) + { + result.insert(result.end(), *it); + } + } + return result; +} + +Nodes Node::operator[](const std::string& key) +{ + Nodes result; + + if (key.length() == 0) + { + return result; + } + + for (auto it = children.begin(), itEnd = children.end(); it != itEnd; ++it) + { + if (it->name == key) + { + result.push_back(&(*it)); + } + } + return result; +} + +std::string Node::get(const std::string& attribute) const +{ + const auto it = m_attrs.find(attribute); + if (it != m_attrs.end()) + { + return it->second; + } + + return ""; +} + +void Node::set(const std::string& attribute, std::string value) +{ + if (value.length()) + { + m_attrs[attribute] = value; + } + else + { + m_attrs.erase(value); + } +} + +std::string Node::to_xml() const +{ + std::string xml; + int depth = 0; + + _raw_xml(xml, depth); + + return xml; +} + +void Node::_raw_xml(std::string& xml, int& depth) const +{ + xml.append(depth * 2, ' '); + xml.append("<" + name); + + for (auto it = m_attrs.begin(), itEnd = m_attrs.end(); it != itEnd; ++it) + { + xml.append(" " + it->first + "=\"" + it->second + "\""); + } + + if (cdata.length() == 0 && children.size() == 0) + { + xml.append("/>\n"); + } + else + { + xml.append(">"); + + if (cdata.length()) + { + xml.append(cdata); + } + + if (children.size()) + { + xml.append("\n"); + ++depth; + + for (auto it = children.begin(), itEnd = children.end(); it != itEnd; ++it) + { + it->_raw_xml(xml, depth); + } + + --depth; + xml.append(depth * 2, ' '); + } + + xml.append("\n"); + } +} + +Document::Document() : + root(nullptr), + m_depth(0) +{ +} + +Document::Document(const std::string &xml) : + root(nullptr), + m_depth(0) +{ + from_xml(xml); +} + +Document::~Document() +{ + delete root; +} + +struct Document::Expat +{ + static void start_doctype_decl_handler( + void *data, const XML_Char *name, const XML_Char *sysid, const XML_Char *pubid, int has_internal_subset + ); + static void end_doctype_decl_handler(void *data); + static void start_element_handler(void *data, const XML_Char *name, const XML_Char **atts); + static void character_data_handler(void *data, const XML_Char *chars, int len); + static void end_element_handler(void *data, const XML_Char *name); +}; + +void Document::from_xml(const std::string& xml) +{ + m_depth = 0; + if (root) + { + delete root; + } + root = nullptr; + + XML_Parser parser = XML_ParserCreate("UTF-8"); + + XML_SetUserData(parser, this); + + XML_SetDoctypeDeclHandler( + parser, + Document::Expat::start_doctype_decl_handler, + Document::Expat::end_doctype_decl_handler + ); + + XML_SetElementHandler( + parser, + Document::Expat::start_element_handler, + Document::Expat::end_element_handler + ); + + XML_SetCharacterDataHandler( + parser, + Document::Expat::character_data_handler + ); + + XML_Status status = XML_Parse(parser, xml.c_str(), xml.length(), true); + + if (status == XML_STATUS_ERROR) + { + const char* error = XML_ErrorString(XML_GetErrorCode(parser)); + int line = XML_GetCurrentLineNumber(parser); + int column = XML_GetCurrentColumnNumber(parser); + + XML_ParserFree(parser); + + throw Error(error, line, column); + } + else + { + XML_ParserFree(parser); + } +} + +std::string Document::to_xml() const +{ + return root->to_xml(); +} + +void Document::Expat::start_doctype_decl_handler( + void* data, + const XML_Char* name, + const XML_Char* sysid, + const XML_Char *pubid, + int has_internal_subset) +{ +} + +void Document::Expat::end_doctype_decl_handler(void* data) +{ +} + +void Document::Expat::start_element_handler(void* data, const XML_Char* name, const XML_Char** atts) +{ + Document* doc = static_cast(data); + + if (!doc->root) + { + doc->root = new Node(name, atts); + } + else + { + Node::Children* cld = &(doc->root->children); + + for (int i = 1; i < doc->m_depth; ++i) + { + cld = &(cld->back().children); + } + cld->push_back(Node(name, atts)); + + } + doc->m_depth++; +} + +void Document::Expat::character_data_handler(void* data, const XML_Char* chars, int len) +{ + Document* doc = static_cast(data); + + Node* nod = doc->root; + + for (int i = 1; i < doc->m_depth; ++i) + { + nod = &(nod->children.back()); + } + + int x = 0, y = len - 1; + + while (isspace(chars[y]) && y > 0) --y; + while (isspace(chars[x]) && x < y) ++x; + + nod->cdata = std::string(chars, x, y + 1); +} + +void Document::Expat::end_element_handler(void* data, const XML_Char* name) +{ + Document* doc = static_cast(data); + + doc->m_depth--; +} + diff --git a/stub-generator/xml.h b/stub-generator/xml.h new file mode 100644 index 0000000..0183033 --- /dev/null +++ b/stub-generator/xml.h @@ -0,0 +1,113 @@ +/** + * Inspired by: http://dbus-cplusplus.sourceforge.net/ + */ + +#ifndef __SDBUSCPP_TOOLS_XML_H +#define __SDBUSCPP_TOOLS_XML_H + + +#include +#include +#include +#include +#include +#include +#include + +namespace sdbuscpp +{ +namespace xml +{ + +class Error : public std::exception +{ +public: + + Error(const char *error, int line, int column); + + ~Error() {} + + const char *what() const noexcept { return m_error.c_str(); } + +private: + std::string m_error; +}; + + +class Node; + +class Nodes : public std::vector +{ +public: + Nodes operator[](const std::string &key) const; + Nodes select(const std::string &attr, const std::string &value) const; +}; + +class Node +{ +public: + + using Attributes = std::map; + using Children = std::vector; + + std::string name; + std::string cdata; + Children children; + + Node(std::string n, Attributes a) : + name(n), + m_attrs(a) + {} + + Node(const char* n, const char** a = nullptr); + + Nodes operator[](const std::string& key); + + std::string get(const std::string& attribute) const; + + void set(const std::string &attribute, std::string value); + + std::string to_xml() const; + + Node& add(Node child) + { + children.push_back(child); + return children.back(); + } + +private: + + void _raw_xml(std::string& xml, int& depth) const; + + Attributes m_attrs; +}; + +class Document +{ +public: + struct Expat; + + Node* root; + + Document(); + ~Document(); + + Document(const std::string& xml); + + void from_xml(const std::string& xml); + + std::string to_xml() const; + +private: + + int m_depth; +}; + +} // namespace xml + +} // namespace sdbuscpp + +std::istream &operator >> (std::istream &, sdbuscpp::xml::Document &); +std::ostream &operator << (std::ostream &, sdbuscpp::xml::Document &); + +#endif//__SDBUSCPP_TOOLS_XML_H diff --git a/stub-generator/xml2cpp.cpp b/stub-generator/xml2cpp.cpp new file mode 100644 index 0000000..69ec61c --- /dev/null +++ b/stub-generator/xml2cpp.cpp @@ -0,0 +1,202 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file xml2cpp.cpp + * + * Created on: Feb 1, 2017 + * Project: sdbus-c++ + * Description: High-level D-Bus IPC C++ library based on sd-bus + * + * This file is part of sdbus-c++. + * + * sdbus-c++ is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * sdbus-c++ is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with sdbus-c++. If not, see . + */ + +// Own +#include "xml.h" +#include "AdaptorGenerator.h" +#include "ProxyGenerator.h" + +// STL +#include +#include +#include +#include +#include +#include +#include + +using std::endl; +using namespace sdbuscpp; + +void usage(std::ostream& output, const char* programName) +{ + output << "Usage: " << programName << " [OPTION]... [FILE]" << endl << + "Creates C++ stubs for DBus API for adaptor and/or client" << endl << + endl << + "Available options:" << endl << + " --proxy=FILE Generate header file FILE with proxy class (client)" << endl << + " --adaptor=FILE Generate header file FILE with stub class (server)" << endl << + " -h, --help " << endl << + " --verbose Explain what is being done" << endl << + endl << + "The stub generator takes an XML file describing DBus interface and creates" << endl << + "C++ header files to be used by C++ code wanting to cumminicate through that" << endl << + "interface. Clients of the interface (those making the calls) need header" << endl << + "created with the --proxy option as this header forwards the calls via DBus" << endl << + "to provider of the service and the returns the result to the caller. Server" << endl << + "implementing the service should derive from interface classes in header" << endl << + "generated for --adaptor option and implement their methods." << endl << + endl << + "When FILE is not specified, standard input is read. Exit status is 0 when" << endl << + "no error was encountered and all requested headers were sucessfully generated." << endl << + "Otherwise 1 is returned." << endl; +} + + +int main(int argc, char **argv) +{ + const char* programName = argv[0]; + argv++; + argc--; + + const char* proxy = nullptr; + const char* adaptor = nullptr; + const char* xmlFile = nullptr; + bool verbose = false; + + while (argc > 0) + { + if (!strncmp(*argv, "--proxy=", 8)) + { + if (proxy != nullptr) + { + std::cerr << "Multiple occurrencies of --proxy is not allowed" << endl; + usage(std::cerr, programName); + return 1; + } + proxy = *argv + 8; + } + else if (!strncmp(*argv, "--adaptor=", 10) || !strncmp(*argv, "--adapter=", 10)) + { + if (adaptor != nullptr) + { + std::cerr << "Multiple occurrencies of --adaptor is not allowed" << endl; + usage(std::cerr, programName); + return 1; + } + adaptor = *argv + 10; + } + else if (!strcmp(*argv, "--help") || !strcmp(*argv, "-h")) + { + usage(std::cout, programName); + return 0; + } + else if (!strcmp(*argv, "--verbose")) + { + verbose = true; + } + else if (**argv == '-') + { + std::cerr << "Unknown option " << *argv << endl; + usage(std::cerr, programName); + return 1; + } + else + { + if (xmlFile != nullptr) + { + std::cerr << "More than one input file specified: " << *argv << endl; + usage(std::cerr, programName); + return 1; + } + xmlFile = *argv; + } + + argc--; + argv++; + } + + if (!proxy && !adaptor) + { + std::cerr << "Either --proxy or --adapter need to be specified" << endl; + usage(std::cerr, programName); + return 1; + } + + xml::Document doc; + + try + { + if (xmlFile != nullptr) + { + if (verbose) + { + std::cerr << "Reading DBus interface from " << xmlFile << endl; + } + + std::ifstream input(xmlFile); + + if (input.bad()) + { + std::cerr << "Unable to open file " << xmlFile << endl; + return 1; + } + + input >> doc; + } + else + { + if (verbose) + { + std::cerr << "Reading DBus interface from standard input" << endl; + } + + std::cin >> doc; + } + } + catch (const xml::Error& e) + { + std::cerr << "Parsing error: " << e.what() << endl; + return 1; + } + + if (!doc.root) + { + std::cerr << "Empty document" << endl; + return 1; + } + + if (proxy) + { + if (verbose) + { + std::cerr << "Generating proxy header " << proxy << endl; + } + ProxyGenerator pg; + pg.transformXmlToFile(doc, proxy); + } + + if (adaptor) + { + if (verbose) + { + std::cerr << "Generating adaptor header " << adaptor << endl; + } + AdaptorGenerator ag; + ag.transformXmlToFile(doc, adaptor); + } + + return 0; +} diff --git a/test/Makefile.am b/test/Makefile.am new file mode 100644 index 0000000..a060920 --- /dev/null +++ b/test/Makefile.am @@ -0,0 +1,68 @@ +# Defines how to build and install libsdbus-c++ tests + +AUTOMAKE_OPTIONS = subdir-objects + +# Target dirs for test binaries, scripts and files +testbindir = /opt/test/bin +dbusconfdir = $(sysconfdir)/dbus-1/system.d + +# ENABLE_TESTS is defined by configure when user enables tests during configuration +if ENABLE_TESTS +testbin_PROGRAMS = libsdbus-c++_unittests libsdbus-c++_integrationtests +dbusconf_DATA = integrationtests/files/libsdbus-cpp-test.conf +endif + +# Setting per-file flags +AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_srcdir)/src +AM_CXXFLAGS = @libsdbus_cpp_CFLAGS@ @SYSTEMD_CFLAGS@ -W -Wall -Werror -pedantic -pipe -std=c++14 +AM_LDFLAGS = @libsdbus_cpp_LIBS@ @SYSTEMD_LIBS@ + +CLEANFILES = *~ *.lo *.la +MOSTLYCLEANFILES = *.o + +TESTS = + +# Configuration for libsdbus-c++_unittests +libsdbus_c___unittests_SOURCES = \ +unittests/libsdbus-c++_unittests.cpp \ +unittests/TypeTraits_test.cpp \ +unittests/Types_test.cpp \ +unittests/Message_test.cpp + +libsdbus_c___unittests_LDFLAGS = -L$(top_builddir)/src + +libsdbus_c___unittests_LDADD = \ + -lsdbus-c++ \ + @libsdbus_cpp_LIBS@ \ + @SYSTEMD_LIBS@ \ + -lgmock + +TESTS += libsdbus-c++_unittests + +# Configuration for libsdbus-c++_integrationtests +libsdbus_c___integrationtests_SOURCES = \ +integrationtests/libsdbus-c++_integrationtests.cpp \ +integrationtests/Connection_test.cpp \ +integrationtests/AdaptorAndProxy_test.cpp + +libsdbus_c___integrationtests_LDFLAGS = -L$(top_builddir)/src + +libsdbus_c___integrationtests_LDADD = \ + -lsdbus-c++ \ + @libsdbus_cpp_LIBS@ \ + @SYSTEMD_LIBS@ \ + -lgmock \ + -lpthread + +TESTS += libsdbus-c++_integrationtests + +check_PROGRAMS = libsdbus-c++_unittests libsdbus-c++_integrationtests + +DISTCLEANFILES = Makefile Makefile.in + +# Post-build action: executing tests from the IDE +if ENABLE_TESTS +all-local: libsdbus-c++_unittests libsdbus-c++_integrationtests + if [ "${UNIT_TESTS_RUNNER}" ]; then "${UNIT_TESTS_RUNNER}" --deviceip="${TEST_DEVICE_IP}" --testbin=.libs/libsdbus-c++_unittests; fi; exit 0 + if [ "${UNIT_TESTS_RUNNER}" ]; then "${UNIT_TESTS_RUNNER}" --deviceip="${TEST_DEVICE_IP}" --testbin=.libs/libsdbus-c++_integrationtests; fi; exit 0 +endif diff --git a/test/integrationtests/AdaptorAndProxy_test.cpp b/test/integrationtests/AdaptorAndProxy_test.cpp new file mode 100644 index 0000000..3e914a1 --- /dev/null +++ b/test/integrationtests/AdaptorAndProxy_test.cpp @@ -0,0 +1,289 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file AdaptorAndProxy_test.cpp + * + * Created on: Jan 2, 2017 + * Project: sdbus-c++ + * Description: High-level D-Bus IPC C++ library based on sd-bus + * + * This file is part of sdbus-c++. + * + * sdbus-c++ is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * sdbus-c++ is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with sdbus-c++. If not, see . + */ + +// Own +#include "Connection.h" + +#include "TestingAdaptor.h" +#include "TestingProxy.h" + +// sdbus +#include "sdbus-c++/sdbus-c++.h" + +// gmock +#include +#include + +// STL +#include +#include +#include + +using ::testing::Eq; +using ::testing::Gt; + +namespace +{ + +class AdaptorAndProxyFixture : public ::testing::Test +{ +public: + static void SetUpTestCase() + { + m_connection.requestName(INTERFACE_NAME); + m_connection.enterProcessingLoopAsync(); + } + + static void TearDownTestCase() + { + m_connection.leaveProcessingLoop(); + m_connection.releaseName(INTERFACE_NAME); + } + +private: + void SetUp() override + { + m_adaptor = std::make_unique(m_connection); + m_proxy = std::make_unique(INTERFACE_NAME, OBJECT_PATH); + usleep(50000); // Give time for the proxy to start listening to signals + } + + void TearDown() override + { + m_proxy.reset(); + m_adaptor.reset(); + } + +public: + static sdbus::internal::Connection m_connection; + + std::unique_ptr m_adaptor; + std::unique_ptr m_proxy; +}; + +sdbus::internal::Connection AdaptorAndProxyFixture::m_connection{sdbus::internal::Connection::BusType::eSystem}; + +} + +/*-------------------------------------*/ +/* -- TEST CASES -- */ +/*-------------------------------------*/ + +TEST(AdaptorAndProxy, CanBeConstructedSuccesfully) +{ + auto connection = sdbus::createConnection(); + connection->requestName(INTERFACE_NAME); + + ASSERT_NO_THROW(TestingAdaptor adaptor(*connection)); + ASSERT_NO_THROW(TestingProxy proxy(INTERFACE_NAME, OBJECT_PATH)); + + connection->releaseName(INTERFACE_NAME); +} + +// Methods + +using SdbusTestObject = AdaptorAndProxyFixture; + +TEST_F(SdbusTestObject, CallsEmptyMethodSuccesfully) +{ + ASSERT_NO_THROW(m_proxy->noArgNoReturn()); +} + +TEST_F(SdbusTestObject, CallsMethodsWithBaseTypesSuccesfully) +{ + auto resInt = m_proxy->getInt(); + ASSERT_THAT(resInt, Eq(INT32_VALUE)); + + auto multiplyRes = m_proxy->multiply(INT64_VALUE, DOUBLE_VALUE); + ASSERT_THAT(multiplyRes, Eq(INT64_VALUE * DOUBLE_VALUE)); +} + +TEST_F(SdbusTestObject, CallsMethodsWithTuplesSuccesfully) +{ + auto resTuple = m_proxy->getTuple(); + ASSERT_THAT(std::get<0>(resTuple), Eq(UINT32_VALUE)); + ASSERT_THAT(std::get<1>(resTuple), Eq(STRING_VALUE)); +} + +TEST_F(SdbusTestObject, CallsMethodsWithStructSuccesfully) +{ + sdbus::Struct> a{}; + auto vectorRes = m_proxy->getInts16FromStruct(a); + ASSERT_THAT(vectorRes, Eq(std::vector{0})); // because second item is by default initialized to 0 + + + sdbus::Struct> b{ + UINT8_VALUE, INT16_VALUE, DOUBLE_VALUE, STRING_VALUE, {INT16_VALUE, -INT16_VALUE} + }; + vectorRes = m_proxy->getInts16FromStruct(b); + ASSERT_THAT(vectorRes, Eq(std::vector{INT16_VALUE, INT16_VALUE, -INT16_VALUE})); +} + +TEST_F(SdbusTestObject, CallsMethodWithVariantSuccesfully) +{ + sdbus::Variant v{DOUBLE_VALUE}; + auto variantRes = m_proxy->processVariant(v); + ASSERT_THAT(variantRes.get(), Eq(static_cast(DOUBLE_VALUE))); +} + +TEST_F(SdbusTestObject, CallsMethodWithStructVariantsAndGetMapSuccesfully) +{ + std::vector x{-2, 0, 2}; + sdbus::Struct y{false, true}; + auto mapOfVariants = m_proxy->getMapOfVariants(x, y); + decltype(mapOfVariants) res{{-2, false}, {0, false}, {2, true}}; + + ASSERT_THAT(mapOfVariants[-2].get(), Eq(res[-2].get())); + ASSERT_THAT(mapOfVariants[0].get(), Eq(res[0].get())); + ASSERT_THAT(mapOfVariants[2].get(), Eq(res[2].get())); +} + +TEST_F(SdbusTestObject, CallsMethodWithStructInStructSuccesfully) +{ + auto val = m_proxy->getStructInStruct(); + ASSERT_THAT(val.get<0>(), Eq(STRING_VALUE)); + ASSERT_THAT(std::get<0>(std::get<1>(val))[INT32_VALUE], Eq(INT32_VALUE)); +} + +TEST_F(SdbusTestObject, CallsMethodWithTwoStructsSuccesfully) +{ + auto val = m_proxy->sumStructItems({1, 2}, {3, 4}); + ASSERT_THAT(val, Eq(1 + 2 + 3 + 4)); +} + +TEST_F(SdbusTestObject, CallsMethodWithTwoVectorsSuccesfully) +{ + auto val = m_proxy->sumVectorItems({1, 7}, {2, 3}); + ASSERT_THAT(val, Eq(1 + 7 + 2 + 3)); +} + +TEST_F(SdbusTestObject, CallsMethodWithSignatureSuccesfully) +{ + auto resSignature = m_proxy->getSignature(); + ASSERT_THAT(resSignature, Eq(SIGNATURE_VALUE)); +} + +TEST_F(SdbusTestObject, CallsMethodWithObjectPathSuccesfully) +{ + auto resObjectPath = m_proxy->getObjectPath(); + ASSERT_THAT(resObjectPath, Eq(OBJECT_PATH_VALUE)); +} + +TEST_F(SdbusTestObject, CallsMethodWithComplexTypeSuccesfully) +{ + auto resComplex = m_proxy->getComplex(); + ASSERT_THAT(resComplex.count(0), Eq(1)); +} + +TEST_F(SdbusTestObject, FailsCallingNonexistentMethod) +{ + ASSERT_THROW(m_proxy->callNonexistentMethod(), sdbus::Error); +} + +TEST_F(SdbusTestObject, FailsCallingMethodOnNonexistentInterface) +{ + ASSERT_THROW(m_proxy->callMethodOnNonexistentInterface(), sdbus::Error); +} + +TEST_F(SdbusTestObject, FailsCallingMethodOnNonexistentDestination) +{ + TestingProxy proxy("wrongDestination", OBJECT_PATH); + ASSERT_THROW(proxy.getInt(), sdbus::Error); +} + +TEST_F(SdbusTestObject, FailsCallingMethodOnNonexistentObject) +{ + TestingProxy proxy(INTERFACE_NAME, "/wrong/path"); + ASSERT_THROW(proxy.getInt(), sdbus::Error); +} + +// Signals + +TEST_F(SdbusTestObject, EmitsSimpleSignalSuccesfully) +{ + auto count = m_proxy->getSimpleCallCount(); + m_adaptor->simpleSignal(); + usleep(10000); + + ASSERT_THAT(m_proxy->getSimpleCallCount(), Eq(count + 1)); +} + +TEST_F(SdbusTestObject, EmitsSignalWithMapSuccesfully) +{ + m_adaptor->signalWithMap({{0, "zero"}, {1, "one"}}); + usleep(10000); + + auto map = m_proxy->getMap(); + ASSERT_THAT(map[0], Eq("zero")); + ASSERT_THAT(map[1], Eq("one")); +} + +TEST_F(SdbusTestObject, EmitsSignalWithVariantSuccesfully) +{ + double d = 3.14; + m_adaptor->signalWithVariant(3.14); + usleep(10000); + + ASSERT_THAT(m_proxy->getVariantValue(), d); +} + +TEST_F(SdbusTestObject, EmitsSignalWithoutRegistrationSuccesfully) +{ + m_adaptor->signalWithoutRegistration({"platform", {"av"}}); + usleep(10000); + + auto signature = m_proxy->getSignatureFromSignal(); + ASSERT_THAT(signature["platform"], Eq("av")); +} + +TEST_F(SdbusTestObject, failsEmitingSignalOnNonexistentInterface) +{ + ASSERT_THROW(m_adaptor->emitSignalOnNonexistentInterface(), sdbus::Error); +} + +// Properties + +TEST_F(SdbusTestObject, ReadsReadPropertySuccesfully) +{ + ASSERT_THAT(m_proxy->state(), Eq(STRING_VALUE)); +} + +TEST_F(SdbusTestObject, WritesAndReadsReadWritePropertySuccesfully) +{ + auto x = 42; + ASSERT_NO_THROW(m_proxy->action(x)); + ASSERT_THAT(m_proxy->action(), Eq(x)); +} + +TEST_F(SdbusTestObject, WritesToWritePropertySuccesfully) +{ + auto x = true; + ASSERT_NO_THROW(m_proxy->blocking(x)); +} + +TEST_F(SdbusTestObject, CannotReadFromWriteProperty) +{ + ASSERT_THROW(m_proxy->blocking(), sdbus::Error); +} diff --git a/test/integrationtests/Connection_test.cpp b/test/integrationtests/Connection_test.cpp new file mode 100644 index 0000000..b8d3c44 --- /dev/null +++ b/test/integrationtests/Connection_test.cpp @@ -0,0 +1,91 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file Connection_test.cpp + * + * Created on: Jan 2, 2017 + * Project: sdbus-c++ + * Description: High-level D-Bus IPC C++ library based on sd-bus + * + * This file is part of sdbus-c++. + * + * sdbus-c++ is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * sdbus-c++ is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with sdbus-c++. If not, see . + */ + +// Own +#include "defs.h" + +// sdbus +#include +#include + +// gmock +#include +#include + +// STL +#include + +using ::testing::Eq; + + +/*-------------------------------------*/ +/* -- TEST CASES -- */ +/*-------------------------------------*/ + +TEST(Connection, CanBeDefaultConstructed) +{ + ASSERT_NO_THROW(sdbus::createConnection()); +} + +TEST(Connection, CanRequestRegisteredDbusName) +{ + auto connection = sdbus::createConnection(); + + ASSERT_NO_THROW(connection->requestName(INTERFACE_NAME)); + connection->releaseName(INTERFACE_NAME); +} + +TEST(Connection, CannotRequestNonregisteredDbusName) +{ + auto connection = sdbus::createConnection(); + ASSERT_THROW(connection->requestName("some_random_not_supported_dbus_name"), sdbus::Error); +} + +TEST(Connection, CanReleasedRequestedName) +{ + auto connection = sdbus::createConnection(); + + connection->requestName(INTERFACE_NAME); + ASSERT_NO_THROW(connection->releaseName(INTERFACE_NAME)); +} + +TEST(Connection, CannotReleaseNonrequestedName) +{ + auto connection = sdbus::createConnection(); + ASSERT_THROW(connection->releaseName("some_random_nonrequested_name"), sdbus::Error); +} + +TEST(Connection, CanEnterAndLeaveProcessingLoop) +{ + auto connection = sdbus::createConnection(); + connection->requestName(INTERFACE_NAME); + + std::thread t([&](){ connection->enterProcessingLoop(); }); + connection->leaveProcessingLoop(); + + t.join(); + + connection->releaseName(INTERFACE_NAME); +} diff --git a/test/integrationtests/TestingAdaptor.h b/test/integrationtests/TestingAdaptor.h new file mode 100644 index 0000000..0254d0f --- /dev/null +++ b/test/integrationtests/TestingAdaptor.h @@ -0,0 +1,146 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file TestingAdaptor.h + * + * Created on: Jan 2, 2017 + * Project: sdbus-c++ + * Description: High-level D-Bus IPC C++ library based on sd-bus + * + * This file is part of sdbus-c++. + * + * sdbus-c++ is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * sdbus-c++ is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with sdbus-c++. If not, see . + */ + +#ifndef SDBUS_CPP_INTEGRATIONTESTS_TESTINGADAPTOR_H_ +#define SDBUS_CPP_INTEGRATIONTESTS_TESTINGADAPTOR_H_ + +#include "adaptor-glue.h" + +class TestingAdaptor : public sdbus::Interfaces +{ +public: + TestingAdaptor(sdbus::IConnection& connection) : + sdbus::Interfaces<::testing_adaptor>(connection, OBJECT_PATH) { } + + virtual ~TestingAdaptor() { } + +protected: + + void noArgNoReturn() const { } + + int32_t getInt() const { return INT32_VALUE; } + + std::tuple getTuple() const { return std::make_tuple(UINT32_VALUE, STRING_VALUE); } + + double multiply(const int64_t& a, const double& b) const { return a * b; } + + std::vector getInts16FromStruct(const sdbus::Struct>& x) const + { + std::vector res{x.get<1>()}; + auto y = std::get>(x); + res.insert(res.end(), y.begin(), y.end()); + return res; + } + + sdbus::Variant processVariant(sdbus::Variant& v) + { + sdbus::Variant res = static_cast(v.get()); + return res; + } + + std::map getMapOfVariants(const std::vector& x, const sdbus::Struct& y) const + { + std::map res; + for (auto item : x) + { + res[item] = (item <= 0) ? std::get<0>(y) : std::get<1>(y); + } + return res; + } + + sdbus::Struct>> getStructInStruct() const + { + sdbus::Struct>> x{STRING_VALUE, {{{INT32_VALUE, INT32_VALUE}}}}; + return x; + } + + int32_t sumStructItems(const sdbus::Struct& a, const sdbus::Struct& b) + { + int32_t res{0}; + res += std::get<0>(a) + std::get<1>(a); + res += std::get<0>(b) + std::get<1>(b); + return res; + } + + uint32_t sumVectorItems(const std::vector& a, const std::vector& b) + { + uint32_t res{0}; + for (auto x : a) + { + res += x; + } + for (auto x : b) + { + res += x; + } + return res; + } + + sdbus::Signature getSignature() const { return SIGNATURE_VALUE; } + sdbus::ObjectPath getObjectPath() const { return OBJECT_PATH_VALUE; } + + ComplexType getComplex() const + { + return { // map + { + 0, // uint_64_t + { // struct + { // map + { + 'a', // uint8_t + { // vector + { // struct + "/object/path", // object path + false, + 3.14, + { // map + {0, "zero"} + } + } + } + } + }, + "a{t(a{ya(obva{is})}gs)}", // signature + "" + } + } + }; + } + + std::string state() { return STRING_VALUE; } + uint32_t action() { return m_action; } + void action(const uint32_t& value) { m_action = value; } + bool blocking() { return m_blocking; } + void blocking(const bool& value) { m_blocking = value; } + +private: + uint32_t m_action; + bool m_blocking; + +}; + + + +#endif /* INTEGRATIONTESTS_TESTINGADAPTOR_H_ */ diff --git a/test/integrationtests/TestingProxy.h b/test/integrationtests/TestingProxy.h new file mode 100644 index 0000000..130c04e --- /dev/null +++ b/test/integrationtests/TestingProxy.h @@ -0,0 +1,66 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file TestingProxy.h + * + * Created on: Jan 2, 2017 + * Project: sdbus-c++ + * Description: High-level D-Bus IPC C++ library based on sd-bus + * + * This file is part of sdbus-c++. + * + * sdbus-c++ is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * sdbus-c++ is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with sdbus-c++. If not, see . + */ + +#ifndef SDBUS_CPP_INTEGRATIONTESTS_TESTINGPROXY_H_ +#define SDBUS_CPP_INTEGRATIONTESTS_TESTINGPROXY_H_ + +#include "proxy-glue.h" + +class TestingProxy : public sdbus::ProxyInterfaces<::testing_proxy> +{ +public: + using sdbus::ProxyInterfaces<::testing_proxy>::ProxyInterfaces; + + int getSimpleCallCount() const { return m_simpleCallCounter; } + std::map getMap() const { return m_map; } + double getVariantValue() const { return m_variantValue; } + std::map getSignatureFromSignal() const { return m_signature; } + +protected: + void onSimpleSignal() override { ++m_simpleCallCounter; } + + void onSignalWithMap(const std::map& m) override { m_map = m; } + + void onSignalWithVariant(const sdbus::Variant& v) override + { + m_variantValue = v.get(); + } + + void onSignalWithoutRegistration(const sdbus::Struct>& s) override + { + m_signature[std::get<0>(s)] = static_cast(std::get<0>(std::get<1>(s))); + } + +private: + int m_simpleCallCounter{}; + std::map m_map; + double m_variantValue; + std::map m_signature; + +}; + + + +#endif /* SDBUS_CPP_INTEGRATIONTESTS_TESTINGPROXY_H_ */ diff --git a/test/integrationtests/adaptor-glue.h b/test/integrationtests/adaptor-glue.h new file mode 100644 index 0000000..b23d367 --- /dev/null +++ b/test/integrationtests/adaptor-glue.h @@ -0,0 +1,156 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file adaptor-glue.h + * + * Created on: Jan 2, 2017 + * Project: sdbus-c++ + * Description: High-level D-Bus IPC C++ library based on sd-bus + * + * This file is part of sdbus-c++. + * + * sdbus-c++ is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * sdbus-c++ is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with sdbus-c++. If not, see . + */ + +#ifndef SDBUS_CPP_INTEGRATIONTESTS_ADAPTOR_GLUE_H_ +#define SDBUS_CPP_INTEGRATIONTESTS_ADAPTOR_GLUE_H_ + +#include "defs.h" + +// sdbus +#include "sdbus-c++/sdbus-c++.h" + +using ComplexType = std::map< + uint64_t, + sdbus::Struct< + std::map< + uint8_t, + std::vector< + sdbus::Struct< + sdbus::ObjectPath, + bool, + sdbus::Variant, + std::map + > + > + >, + sdbus::Signature, + std::string // char* leads to type and memory issues, std::string is best choice + > + >; + +class testing_adaptor +{ + +protected: + testing_adaptor(sdbus::IObject& object) : + object_(object) + { + object_.registerMethod("noArgNoReturn").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->noArgNoReturn(); }); + object_.registerMethod("getInt").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->getInt(); }); + object_.registerMethod("getTuple").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->getTuple(); }); + + object_.registerMethod("multiply").onInterface(INTERFACE_NAME).implementedAs([this](const int64_t& a, const double& b){ return this->multiply(a, b); }); + object_.registerMethod("getInts16FromStruct").onInterface(INTERFACE_NAME).implementedAs([this]( + const sdbus::Struct>& x){ return this->getInts16FromStruct(x); }); + + object_.registerMethod("processVariant").onInterface(INTERFACE_NAME).implementedAs([this](sdbus::Variant& v){ return this->processVariant(v); }); + + object_.registerMethod("getMapOfVariants").onInterface(INTERFACE_NAME).implementedAs([this]( + const std::vector& x, const sdbus::Struct& y){ return this->getMapOfVariants(x ,y); }); + + object_.registerMethod("getStructInStruct").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->getStructInStruct(); }); + + object_.registerMethod("sumStructItems").onInterface(INTERFACE_NAME).implementedAs([this]( + const sdbus::Struct& a, const sdbus::Struct& b){ + return this->sumStructItems(a, b); + }); + + object_.registerMethod("sumVectorItems").onInterface(INTERFACE_NAME).implementedAs([this]( + const std::vector& a, const std::vector& b){ + return this->sumVectorItems(a, b); + }); + + object_.registerMethod("getSignature").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->getSignature(); }); + object_.registerMethod("getObjectPath").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->getObjectPath(); }); + + object_.registerMethod("getComplex").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->getComplex(); }); + + // registration of signals is optional, it is useful because of introspection + object_.registerSignal("simpleSignal").onInterface(INTERFACE_NAME); + object_.registerSignal("signalWithMap").onInterface(INTERFACE_NAME).withParameters>(); + object_.registerSignal("signalWithVariant").onInterface(INTERFACE_NAME).withParameters(); + + object_.registerProperty("state").onInterface(INTERFACE_NAME).withGetter([this](){ return this->state(); }); + object_.registerProperty("action").onInterface(INTERFACE_NAME).withGetter([this](){ return this->action(); }).withSetter([this](const uint32_t& value){ this->action(value); }); + object_.registerProperty("blocking").onInterface(INTERFACE_NAME)./*withGetter([this](){ return this->blocking(); }).*/withSetter([this](const bool& value){ this->blocking(value); }); + + } + +public: + void simpleSignal() + { + object_.emitSignal("simpleSignal").onInterface(INTERFACE_NAME); + } + + void signalWithMap(const std::map& map) + { + object_.emitSignal("signalWithMap").onInterface(INTERFACE_NAME).withArguments(map); + } + + void signalWithVariant(const sdbus::Variant& v) + { + object_.emitSignal("signalWithVariant").onInterface(INTERFACE_NAME).withArguments(v); + } + + void signalWithoutRegistration(const sdbus::Struct>& s) + { + object_.emitSignal("signalWithoutRegistration").onInterface(INTERFACE_NAME).withArguments(s); + } + + void emitSignalOnNonexistentInterface() + { + object_.emitSignal("simpleSignal").onInterface("interfaceThatDoesNotExists"); + } + +private: + sdbus::IObject& object_; + +protected: + + virtual void noArgNoReturn() const = 0; + virtual int32_t getInt() const = 0; + virtual std::tuple getTuple() const = 0; + virtual double multiply(const int64_t& a, const double& b) const = 0; + virtual std::vector getInts16FromStruct(const sdbus::Struct>& x) const = 0; + virtual sdbus::Variant processVariant(sdbus::Variant& v) = 0; + virtual std::map getMapOfVariants(const std::vector& x, const sdbus::Struct& y) const = 0; + virtual sdbus::Struct>> getStructInStruct() const = 0; + virtual int32_t sumStructItems(const sdbus::Struct& a, const sdbus::Struct& b) = 0; + virtual uint32_t sumVectorItems(const std::vector& a, const std::vector& b) = 0; + virtual sdbus::Signature getSignature() const = 0; + virtual sdbus::ObjectPath getObjectPath() const = 0; + virtual ComplexType getComplex() const = 0; + + virtual std::string state() = 0; + virtual uint32_t action() = 0; + virtual void action(const uint32_t& value) = 0; + virtual bool blocking() = 0; + virtual void blocking(const bool& value) = 0; + +}; + + + +#endif /* SDBUS_CPP_INTEGRATIONTESTS_ADAPTOR_GLUE_H_ */ diff --git a/test/integrationtests/defs.h b/test/integrationtests/defs.h new file mode 100644 index 0000000..a3aa5f5 --- /dev/null +++ b/test/integrationtests/defs.h @@ -0,0 +1,46 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file defs.h + * + * Created on: Jan 2, 2017 + * Project: sdbus-c++ + * Description: High-level D-Bus IPC C++ library based on sd-bus + * + * This file is part of sdbus-c++. + * + * sdbus-c++ is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * sdbus-c++ is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with sdbus-c++. If not, see . + */ + +#ifndef SDBUS_CPP_INTEGRATIONTESTS_DEFS_H_ +#define SDBUS_CPP_INTEGRATIONTESTS_DEFS_H_ + +#include "sdbus-c++/Types.h" + +const std::string INTERFACE_NAME{"com.kistler.testsdbuscpp"}; +const std::string OBJECT_PATH{"/"}; + +constexpr const uint8_t UINT8_VALUE{1}; +constexpr const int16_t INT16_VALUE{21}; +constexpr const uint32_t UINT32_VALUE{42}; +constexpr const int32_t INT32_VALUE{-42}; +constexpr const int32_t INT64_VALUE{-1024}; + +const std::string STRING_VALUE{"sdbus-c++-testing"}; +const sdbus::Signature SIGNATURE_VALUE{"a{is}"}; +const sdbus::ObjectPath OBJECT_PATH_VALUE{"/"}; + +constexpr const double DOUBLE_VALUE{3.24L}; + +#endif /* SDBUS_CPP_INTEGRATIONTESTS_DEFS_H_ */ diff --git a/test/integrationtests/files/libsdbus-cpp-test.conf b/test/integrationtests/files/libsdbus-cpp-test.conf new file mode 100644 index 0000000..7876e3e --- /dev/null +++ b/test/integrationtests/files/libsdbus-cpp-test.conf @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + diff --git a/test/integrationtests/libsdbus-c++_integrationtests.cpp b/test/integrationtests/libsdbus-c++_integrationtests.cpp new file mode 100644 index 0000000..2f373ea --- /dev/null +++ b/test/integrationtests/libsdbus-c++_integrationtests.cpp @@ -0,0 +1,33 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file libsdbus-c++_integrationtests.cpp + * + * Created on: Jan 2, 2017 + * Project: sdbus-c++ + * Description: High-level D-Bus IPC C++ library based on sd-bus + * + * This file is part of sdbus-c++. + * + * sdbus-c++ is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * sdbus-c++ is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with sdbus-c++. If not, see . + */ + +#include "gmock/gmock.h" + +int main(int argc, char **argv) +{ + ::testing::InitGoogleMock(&argc, argv); + + return RUN_ALL_TESTS(); +} diff --git a/test/integrationtests/proxy-glue.h b/test/integrationtests/proxy-glue.h new file mode 100644 index 0000000..172a72d --- /dev/null +++ b/test/integrationtests/proxy-glue.h @@ -0,0 +1,190 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file proxy-glue.h + * + * Created on: Jan 2, 2017 + * Project: sdbus-c++ + * Description: High-level D-Bus IPC C++ library based on sd-bus + * + * This file is part of sdbus-c++. + * + * sdbus-c++ is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * sdbus-c++ is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with sdbus-c++. If not, see . + */ + +#ifndef SDBUS_CPP_INTEGRATIONTESTS_PROXY_GLUE_H_ +#define SDBUS_CPP_INTEGRATIONTESTS_PROXY_GLUE_H_ + +#include "defs.h" + +// sdbus +#include "sdbus-c++/sdbus-c++.h" + +class testing_proxy +{ +protected: + testing_proxy(sdbus::IObjectProxy& object) : + object_(object) + { + object_.uponSignal("simpleSignal").onInterface(INTERFACE_NAME).call([this](){ this->onSimpleSignal(); }); + object_.uponSignal("signalWithMap").onInterface(INTERFACE_NAME).call([this](const std::map& map){ this->onSignalWithMap(map); }); + object_.uponSignal("signalWithVariant").onInterface(INTERFACE_NAME).call([this](const sdbus::Variant& v){ this->onSignalWithVariant(v); }); + + object_.uponSignal("signalWithoutRegistration").onInterface(INTERFACE_NAME).call([this](const sdbus::Struct>& s) + { this->onSignalWithoutRegistration(s); }); + } + + + virtual void onSimpleSignal() = 0; + virtual void onSignalWithMap(const std::map& map) = 0; + virtual void onSignalWithVariant(const sdbus::Variant& v) = 0; + virtual void onSignalWithoutRegistration(const sdbus::Struct>& s) = 0; + +public: + void noArgNoReturn() + { + object_.callMethod("noArgNoReturn").onInterface(INTERFACE_NAME); + } + + int32_t getInt() + { + int32_t result; + object_.callMethod("getInt").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + std::tuple getTuple() + { + std::tuple result; + object_.callMethod("getTuple").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + double multiply(const int64_t& a, const double& b) + { + double result; + object_.callMethod("multiply").onInterface(INTERFACE_NAME).withArguments(a, b).storeResultsTo(result); + return result; + } + + std::vector getInts16FromStruct(const sdbus::Struct>& x) + { + std::vector result; + object_.callMethod("getInts16FromStruct").onInterface(INTERFACE_NAME).withArguments(x).storeResultsTo(result); + return result; + } + + sdbus::Variant processVariant(const sdbus::Variant& v) + { + sdbus::Variant result; + object_.callMethod("processVariant").onInterface(INTERFACE_NAME).withArguments(v).storeResultsTo(result); + return result; + } + + std::map getMapOfVariants(const std::vector& x, const sdbus::Struct& y) + { + std::map result; + object_.callMethod("getMapOfVariants").onInterface(INTERFACE_NAME).withArguments(x, y).storeResultsTo(result); + return result; + } + + sdbus::Struct>> getStructInStruct() + { + sdbus::Struct>> result; + object_.callMethod("getStructInStruct").onInterface(INTERFACE_NAME).withArguments().storeResultsTo(result); + return result; + } + + int32_t sumStructItems(const sdbus::Struct& a, const sdbus::Struct& b) + { + int32_t result; + object_.callMethod("sumStructItems").onInterface(INTERFACE_NAME).withArguments(a, b).storeResultsTo(result); + return result; + } + + uint32_t sumVectorItems(const std::vector& a, const std::vector& b) + { + uint32_t result; + object_.callMethod("sumVectorItems").onInterface(INTERFACE_NAME).withArguments(a, b).storeResultsTo(result); + return result; + } + + sdbus::Signature getSignature() + { + sdbus::Signature result; + object_.callMethod("getSignature").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + sdbus::ObjectPath getObjectPath() + { + sdbus::ObjectPath result; + object_.callMethod("getObjectPath").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + ComplexType getComplex() + { + ComplexType result; + object_.callMethod("getComplex").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + int32_t callNonexistentMethod() + { + int32_t result; + object_.callMethod("callNonexistentMethod").onInterface(INTERFACE_NAME).storeResultsTo(result); + return result; + } + + int32_t callMethodOnNonexistentInterface() + { + int32_t result; + object_.callMethod("someMethod").onInterface("interfaceThatDoesNotExist").storeResultsTo(result); + return result; + } + + std::string state() + { + return object_.getProperty("state").onInterface(INTERFACE_NAME); + } + + uint32_t action() + { + return object_.getProperty("action").onInterface(INTERFACE_NAME); + } + + void action(const uint32_t& value) + { + object_.setProperty("action").onInterface(INTERFACE_NAME).toValue(value); + } + + bool blocking() + { + return object_.getProperty("blocking").onInterface(INTERFACE_NAME); + } + + void blocking(const bool& value) + { + object_.setProperty("blocking").onInterface(INTERFACE_NAME).toValue(value); + } + + +private: + sdbus::IObjectProxy& object_; + +}; + + +#endif /* SDBUS_CPP_INTEGRATIONTESTS_PROXY_GLUE_H_ */ diff --git a/test/unittests/Message_test.cpp b/test/unittests/Message_test.cpp new file mode 100644 index 0000000..ca57da5 --- /dev/null +++ b/test/unittests/Message_test.cpp @@ -0,0 +1,230 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file Message_test.cpp + * + * Created on: Dec 3, 2016 + * 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 . + */ + +#include +#include "MessageUtils.h" +#include +#include +#include + +using ::testing::Eq; +using ::testing::DoubleEq; +using namespace std::string_literals; + +namespace +{ + std::string deserializeString(sdbus::Message& msg) + { + std::string str; + msg >> str; + return str; + } +} + +/*-------------------------------------*/ +/* -- TEST CASES -- */ +/*-------------------------------------*/ + +TEST(AMessage, CanBeDefaultConstructed) +{ + ASSERT_NO_THROW(sdbus::Message()); +} + +TEST(AMessage, IsInvalidAfterDefaultConstructed) +{ + sdbus::Message msg; + + ASSERT_FALSE(msg.isValid()); +} + +TEST(AMessage, IsValidWhenConstructedAsRealMessage) +{ + sdbus::Message msg{sdbus::createPlainMessage()}; + ASSERT_TRUE(msg.isValid()); +} + +TEST(AMessage, CreatesShallowCopyWhenCopyConstructed) +{ + sdbus::Message msg{sdbus::createPlainMessage()}; + msg << "I am a string"s; + msg.seal(); + + sdbus::Message msgCopy = msg; + + std::string str; + msgCopy >> str; + + ASSERT_THAT(str, Eq("I am a string")); + ASSERT_THROW(msgCopy >> str, sdbus::Error); +} + +TEST(AMessage, CreatesDeepCopyWhenEplicitlyCopied) +{ + sdbus::Message msg{sdbus::createPlainMessage()}; + msg << "I am a string"s; + msg.seal(); + + sdbus::Message msgCopy{sdbus::createPlainMessage()}; + msg.copyTo(msgCopy, true); + msgCopy.seal(); // Seal to be able to read from it subsequently + msg.rewind(true); // Rewind to the beginning after copying + + ASSERT_THAT(deserializeString(msg), Eq("I am a string")); + ASSERT_THAT(deserializeString(msgCopy), Eq("I am a string")); +} + +TEST(AMessage, IsEmptyWhenContainsNoValue) +{ + sdbus::Message msg{sdbus::createPlainMessage()}; + + ASSERT_TRUE(msg.isEmpty()); +} + +TEST(AMessage, IsNotEmptyWhenContainsAValue) +{ + sdbus::Message msg{sdbus::createPlainMessage()}; + msg << "I am a string"s; + + ASSERT_FALSE(msg.isEmpty()); +} + +TEST(AMessage, ReturnsItsTypeWhenAsked) +{ + sdbus::Message msg{sdbus::createPlainMessage()}; + + ASSERT_THAT(msg.getType(), Eq(sdbus::Message::Type::ePlainMessage)); +} + +TEST(AMessage, CanCarryASimpleInteger) +{ + sdbus::Message msg{sdbus::createPlainMessage()}; + + int dataWritten = 5; + + msg << dataWritten; + msg.seal(); + + int dataRead; + msg >> dataRead; + + ASSERT_THAT(dataRead, Eq(dataWritten)); +} + +TEST(AMessage, CanCarryAVariant) +{ + sdbus::Message msg{sdbus::createPlainMessage()}; + + auto dataWritten = sdbus::Variant((double)3.14); + + msg << dataWritten; + msg.seal(); + + sdbus::Variant dataRead; + msg >> dataRead; + + ASSERT_THAT(dataRead.get(), Eq(dataWritten.get())); +} + +TEST(AMessage, CanCarryACollectionOfEmbeddedVariants) +{ + sdbus::Message msg{sdbus::createPlainMessage()}; + + auto value = std::vector{"hello"s, (double)3.14}; + auto dataWritten = sdbus::Variant{value}; + + msg << dataWritten; + msg.seal(); + + sdbus::Variant dataRead; + msg >> dataRead; + + ASSERT_THAT(dataRead.get()[0].get(), Eq(value[0].get())); + ASSERT_THAT(dataRead.get()[1].get(), Eq(value[1].get())); +} + +TEST(AMessage, CanCarryAnArray) +{ + sdbus::Message msg{sdbus::createPlainMessage()}; + + std::vector dataWritten{3545342, 43643532, 324325}; + + msg << dataWritten; + msg.seal(); + + std::vector dataRead; + msg >> dataRead; + + ASSERT_THAT(dataRead, Eq(dataWritten)); +} + +TEST(AMessage, CanCarryADictionary) +{ + sdbus::Message msg{sdbus::createPlainMessage()}; + + std::map dataWritten{{1, "one"}, {2, "two"}}; + + msg << dataWritten; + msg.seal(); + + std::map dataRead; + msg >> dataRead; + + ASSERT_THAT(dataRead, Eq(dataWritten)); +} + +TEST(AMessage, CanCarryAComplexType) +{ + sdbus::Message msg{sdbus::createPlainMessage()}; + + using ComplexType = std::map< + uint64_t, + sdbus::Struct< + std::map< + uint8_t, + std::vector< + sdbus::Struct< + sdbus::ObjectPath, + bool, + int16_t, + /*sdbus::Variant,*/ + std::map + > + > + >, + sdbus::Signature, + double + > + >; + + ComplexType dataWritten = { {1, {{{5, {{"/some/object", true, 45, {{6, "hello"}, {7, "world"}}}}}}, "av", 3.14}}}; + + msg << dataWritten; + msg.seal(); + + ComplexType dataRead; + msg >> dataRead; + + ASSERT_THAT(dataRead, Eq(dataWritten)); +} diff --git a/test/unittests/TypeTraits_test.cpp b/test/unittests/TypeTraits_test.cpp new file mode 100644 index 0000000..a811f51 --- /dev/null +++ b/test/unittests/TypeTraits_test.cpp @@ -0,0 +1,129 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file TypeTraits_test.cpp + * + * Created on: Nov 27, 2016 + * 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 . + */ + +#include +#include +#include +#include +#include + +using ::testing::Eq; + +namespace +{ + // --- + // FIXTURE DEFINITION FOR TYPED TESTS + // ---- + + template + class Type2DBusTypeSignatureConversion + : public ::testing::Test + { + protected: + const std::string dbusTypeSignature_{getDBusTypeSignature()}; + private: + static std::string getDBusTypeSignature(); + }; + +#define TYPE(...) \ + template <> \ + std::string Type2DBusTypeSignatureConversion<__VA_ARGS__>::getDBusTypeSignature() \ + /**/ +#define HAS_DBUS_TYPE_SIGNATURE(_SIG) \ + { \ + return (_SIG); \ + } \ + /**/ + + TYPE(bool)HAS_DBUS_TYPE_SIGNATURE("b") + TYPE(uint8_t)HAS_DBUS_TYPE_SIGNATURE("y") + TYPE(int16_t)HAS_DBUS_TYPE_SIGNATURE("n") + TYPE(uint16_t)HAS_DBUS_TYPE_SIGNATURE("q") + TYPE(int32_t)HAS_DBUS_TYPE_SIGNATURE("i") + TYPE(uint32_t)HAS_DBUS_TYPE_SIGNATURE("u") + TYPE(int64_t)HAS_DBUS_TYPE_SIGNATURE("x") + TYPE(uint64_t)HAS_DBUS_TYPE_SIGNATURE("t") + TYPE(double)HAS_DBUS_TYPE_SIGNATURE("d") + TYPE(const char*)HAS_DBUS_TYPE_SIGNATURE("s") + TYPE(std::string)HAS_DBUS_TYPE_SIGNATURE("s") + TYPE(sdbus::ObjectPath)HAS_DBUS_TYPE_SIGNATURE("o") + TYPE(sdbus::Signature)HAS_DBUS_TYPE_SIGNATURE("g") + TYPE(sdbus::Variant)HAS_DBUS_TYPE_SIGNATURE("v") + TYPE(sdbus::Struct)HAS_DBUS_TYPE_SIGNATURE("(b)") + TYPE(sdbus::Struct)HAS_DBUS_TYPE_SIGNATURE("(qdsv)") + TYPE(std::vector)HAS_DBUS_TYPE_SIGNATURE("an") + TYPE(std::map)HAS_DBUS_TYPE_SIGNATURE("a{ix}") + using ComplexType = std::map< + uint64_t, + sdbus::Struct< + std::map< + uint8_t, + std::vector< + sdbus::Struct< + sdbus::ObjectPath, + bool, + sdbus::Variant, + std::map + > + > + >, + sdbus::Signature, + const char* + > + >; + TYPE(ComplexType)HAS_DBUS_TYPE_SIGNATURE("a{t(a{ya(obva{is})}gs)}") + + typedef ::testing::Types< bool + , uint8_t + , int16_t + , uint16_t + , int32_t + , uint32_t + , int64_t + , uint64_t + , double + , const char* + , std::string + , sdbus::ObjectPath + , sdbus::Signature + , sdbus::Variant + , sdbus::Struct + , sdbus::Struct + , std::vector + , std::map + , ComplexType + > DBusSupportedTypes; + + TYPED_TEST_CASE(Type2DBusTypeSignatureConversion, DBusSupportedTypes); +} + +/*-------------------------------------*/ +/* -- TEST CASES -- */ +/*-------------------------------------*/ + +TYPED_TEST(Type2DBusTypeSignatureConversion, ConvertsTypeToProperDBusSignature) +{ + ASSERT_THAT(sdbus::signature_of::str(), Eq(this->dbusTypeSignature_)); +} diff --git a/test/unittests/Types_test.cpp b/test/unittests/Types_test.cpp new file mode 100644 index 0000000..c37642b --- /dev/null +++ b/test/unittests/Types_test.cpp @@ -0,0 +1,205 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file Types_test.cpp + * + * Created on: Dec 3, 2016 + * 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 . + */ + +#include +#include "MessageUtils.h" +#include +#include +#include + +using ::testing::Eq; +using namespace std::string_literals; + +namespace +{ + constexpr const uint64_t ANY_UINT64 = 84578348354; + constexpr const double ANY_DOUBLE = 3.14; +} + +/*-------------------------------------*/ +/* -- TEST CASES -- */ +/*-------------------------------------*/ + +TEST(AVariant, CanBeDefaultConstructed) +{ + ASSERT_NO_THROW(sdbus::Variant()); +} + +TEST(AVariant, ContainsNoValueAfterDefaultConstructed) +{ + sdbus::Variant v; + + ASSERT_TRUE(v.isEmpty()); +} + +TEST(AVariant, CanBeConstructedFromASimpleValue) +{ + int value = 5; + + ASSERT_NO_THROW(sdbus::Variant{value}); +} + +TEST(AVariant, CanBeConstructedFromAComplexValue) +{ + using ComplexType = std::map>>; + ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{sdbus::make_struct("hello", ANY_DOUBLE), sdbus::make_struct("world", ANY_DOUBLE)}} }; + + ASSERT_NO_THROW(sdbus::Variant(value)); +} + +TEST(AVariant, CanBeCopied) +{ + auto value = "hello"s; + sdbus::Variant variant(value); + + auto variantCopy1{variant}; + auto variantCopy2 = variantCopy1; + + ASSERT_THAT(variantCopy1.get(), Eq(value)); + ASSERT_THAT(variantCopy2.get(), Eq(value)); +} + +TEST(AVariant, IsNotEmptyWhenContainsAValue) +{ + sdbus::Variant v("hello"); + + ASSERT_FALSE(v.isEmpty()); +} + +TEST(ASimpleVariant, ReturnsTheSimpleValueWhenAsked) +{ + int value = 5; + + sdbus::Variant variant(value); + + ASSERT_THAT(variant.get(), Eq(value)); +} + +TEST(AComplexVariant, ReturnsTheComplexValueWhenAsked) +{ + using ComplexType = std::map>>; + ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{sdbus::make_struct("hello", ANY_DOUBLE), sdbus::make_struct("world", ANY_DOUBLE)}} }; + + sdbus::Variant variant(value); + + ASSERT_THAT(variant.get(), Eq(value)); +} + +TEST(AVariant, HasConceptuallyNonmutableGetMethodWhichCanBeCalledXTimes) +{ + std::string value{"I am a string"}; + sdbus::Variant variant(value); + + ASSERT_THAT(variant.get(), Eq(value)); + ASSERT_THAT(variant.get(), Eq(value)); + ASSERT_THAT(variant.get(), Eq(value)); +} + +TEST(AVariant, ReturnsTrueWhenAskedIfItContainsTheTypeItReallyContains) +{ + using ComplexType = std::map>>; + ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{sdbus::make_struct("hello", ANY_DOUBLE), sdbus::make_struct("world", ANY_DOUBLE)}} }; + + sdbus::Variant variant(value); + + ASSERT_TRUE(variant.containsValueOfType()); +} + +TEST(ASimpleVariant, ReturnsFalseWhenAskedIfItContainsTypeItDoesntReallyContain) +{ + int value = 5; + + sdbus::Variant variant(value); + + ASSERT_FALSE(variant.containsValueOfType()); +} + +TEST(AVariant, CanContainOtherEmbeddedVariants) +{ + using TypeWithVariants = std::vector>; + TypeWithVariants value; + value.emplace_back(sdbus::make_struct(sdbus::Variant("a string"), ANY_DOUBLE)); + value.emplace_back(sdbus::make_struct(sdbus::Variant(ANY_UINT64), ANY_DOUBLE)); + + sdbus::Variant variant(value); + + ASSERT_TRUE(variant.containsValueOfType()); +} + +TEST(ANonEmptyVariant, SerializesSuccessfullyToAMessage) +{ + sdbus::Variant variant("a string"); + + sdbus::Message msg = sdbus::createPlainMessage(); + + ASSERT_NO_THROW(variant.serializeTo(msg)); +} + +TEST(AnEmptyVariant, ThrowsWhenBeingSerializedToAMessage) +{ + sdbus::Variant variant; + + sdbus::Message msg = sdbus::createPlainMessage(); + + ASSERT_THROW(variant.serializeTo(msg), sdbus::Error); +} + +TEST(ANonEmptyVariant, SerializesToAndDeserializesFromAMessageSuccessfully) +{ + using ComplexType = std::map>>; + ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{sdbus::make_struct("hello", ANY_DOUBLE), sdbus::make_struct("world", ANY_DOUBLE)}} }; + sdbus::Variant variant(value); + + sdbus::Message msg = sdbus::createPlainMessage(); + variant.serializeTo(msg); + msg.seal(); + sdbus::Variant variant2; + variant2.deserializeFrom(msg); + + ASSERT_THAT(variant2.get(), Eq(value)); +} + +TEST(CopiesOfVariant, SerializeToAndDeserializeFromMessageSuccessfully) +{ + using ComplexType = std::map>>; + ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{sdbus::make_struct("hello", ANY_DOUBLE), sdbus::make_struct("world", ANY_DOUBLE)}} }; + sdbus::Variant variant(value); + auto variantCopy1{variant}; + auto variantCopy2 = variant; + + sdbus::Message msg = sdbus::createPlainMessage(); + variant.serializeTo(msg); + variantCopy1.serializeTo(msg); + variantCopy2.serializeTo(msg); + msg.seal(); + sdbus::Variant receivedVariant1, receivedVariant2, receivedVariant3; + receivedVariant1.deserializeFrom(msg); + receivedVariant2.deserializeFrom(msg); + receivedVariant3.deserializeFrom(msg); + + ASSERT_THAT(receivedVariant1.get(), Eq(value)); + ASSERT_THAT(receivedVariant2.get(), Eq(value)); + ASSERT_THAT(receivedVariant3.get(), Eq(value)); +} diff --git a/test/unittests/libsdbus-c++_unittests.cpp b/test/unittests/libsdbus-c++_unittests.cpp new file mode 100644 index 0000000..2214537 --- /dev/null +++ b/test/unittests/libsdbus-c++_unittests.cpp @@ -0,0 +1,33 @@ +/** + * (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file libsdbus-c++_unittests.cpp + * + * Created on: Nov 27, 2016 + * 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 . + */ + +#include "gmock/gmock.h" + +int main(int argc, char **argv) +{ + ::testing::InitGoogleMock(&argc, argv); + + return RUN_ALL_TESTS(); +}