From fb61420bf031bd6a9d7e0b0303b9620137949c3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Thu, 3 Aug 2023 13:55:37 +0200 Subject: [PATCH] feat: support serialization of array, span and unordered_map (#342) * feat: support serialization of array, span and unordered_map * fix some spelling mistakes * docs: update table of valid c++ types --------- Co-authored-by: Marcel Hellwig --- docs/using-sdbus-c++.md | 44 +-- include/sdbus-c++/Message.h | 300 ++++++++++++++---- include/sdbus-c++/TypeTraits.h | 56 +++- src/Message.cpp | 5 + tests/integrationtests/DBusMethodsTests.cpp | 4 +- tests/integrationtests/TestAdaptor.cpp | 8 +- tests/integrationtests/TestAdaptor.h | 8 +- .../integrationtests-adaptor.h | 6 +- .../integrationtests/integrationtests-proxy.h | 8 +- .../org.sdbuscpp.integrationtests.xml | 2 +- tests/unittests/Message_test.cpp | 122 ++++++- tests/unittests/TypeTraits_test.cpp | 15 +- 12 files changed, 468 insertions(+), 110 deletions(-) diff --git a/docs/using-sdbus-c++.md b/docs/using-sdbus-c++.md index 53568da..a7fade6 100644 --- a/docs/using-sdbus-c++.md +++ b/docs/using-sdbus-c++.md @@ -100,7 +100,7 @@ $ ninja libsystemd.so.0.26.0 # or another version number depending which system ### Building and distributing libsystemd as part of sdbus-c++ -sdbus-c++ provides `BUILD_LIBSYSTEMD` configuration option. When turned on, sdbus-c++ will automatically download and build libsystemd as a static library and make it an opaque part of sdbus-c++ shared library for you. This is the most convenient and effective approach to build, distribute and use sdbus-c++ as a self-contained, systemd-independent library in non-systemd enviroments. Just make sure your build machine has all dependencies needed by libsystemd build process. That includes, among others, `meson`, `ninja`, `git`, `gperf`, and -- primarily -- libraries and library headers for `libmount`, `libcap` and `librt` (part of glibc). Be sure to check out the systemd documentation for the Also, when distributing, make sure these dependency libraries are installed on the production machine. +sdbus-c++ provides `BUILD_LIBSYSTEMD` configuration option. When turned on, sdbus-c++ will automatically download and build libsystemd as a static library and make it an opaque part of sdbus-c++ shared library for you. This is the most convenient and effective approach to build, distribute and use sdbus-c++ as a self-contained, systemd-independent library in non-systemd environments. Just make sure your build machine has all dependencies needed by libsystemd build process. That includes, among others, `meson`, `ninja`, `git`, `gperf`, and -- primarily -- libraries and library headers for `libmount`, `libcap` and `librt` (part of glibc). Be sure to check out the systemd documentation for the Also, when distributing, make sure these dependency libraries are installed on the production machine. You may additionally set the `LIBSYSTEMD_VERSION` configuration flag to fine-tune the version of systemd to be taken in. (The default value is 242). @@ -203,7 +203,7 @@ sdbus-c++ is completely thread-aware by design. Though sdbus-c++ is not thread-s * Creating and emitting signals on an `Object` instance. * Creating and sending method calls (both synchronously and asynchronously) on an `Proxy` instance. (But it's generally better that our threads use their own exclusive instances of proxy, to minimize shared state and contention.) -sdbus-c++ is designed such that all the above operations are thread-safe also on a connection that is running an event loop (usually in a separate thread) at that time. It's an internal thread safety. For example, a signal arrives and is processed by sdbus-c++ even loop at an appropriate `Proxy` instance, while the user is going to destroy that instance in their application thread. The user cannot explicitly control these situations (or they could, but that would be very limiting and cubersome on the API level). +sdbus-c++ is designed such that all the above operations are thread-safe also on a connection that is running an event loop (usually in a separate thread) at that time. It's an internal thread safety. For example, a signal arrives and is processed by sdbus-c++ even loop at an appropriate `Proxy` instance, while the user is going to destroy that instance in their application thread. The user cannot explicitly control these situations (or they could, but that would be very limiting and cumbersome on the API level). However, other combinations, that the user invokes explicitly from within more threads are NOT thread-safe in sdbus-c++ by design, and the user should make sure by their design that these cases never occur. For example, destroying an `Object` instance in one thread while emitting a signal on it in another thread is not thread-safe. In this specific case, the user should make sure in their application that all threads stop working with a specific instance before a thread proceeds with deleting that instance. @@ -307,7 +307,7 @@ int main(int argc, char *argv[]) } ``` -We establish a D-Bus sytem connection and request `org.sdbuscpp.concatenator` D-Bus name on it. This name will be used by D-Bus clients to find the service. We then create an object with path `/org/sdbuscpp/concatenator` on this connection. We register interfaces, its methods, signals that the object provides, and, through `finishRegistration()`, export the object (i.e., make it visible to clients) on the bus. Then we need to make sure to run the event loop upon the connection, which handles all incoming, outgoing and other requests. +We establish a D-Bus system connection and request `org.sdbuscpp.concatenator` D-Bus name on it. This name will be used by D-Bus clients to find the service. We then create an object with path `/org/sdbuscpp/concatenator` on this connection. We register interfaces, its methods, signals that the object provides, and, through `finishRegistration()`, export the object (i.e., make it visible to clients) on the bus. Then we need to make sure to run the event loop upon the connection, which handles all incoming, outgoing and other requests. The callback for any D-Bus object method on this level is any callable of signature `void(sdbus::MethodCall call)`. The `call` parameter is the incoming method call message. We need to deserialize our method input arguments from it. Then we can invoke the logic of the method and get the results. Then for the given `call`, we create a `reply` message, pack results into it and send it back to the caller through `send()`. (If we had a void-returning method, we'd just send an empty `reply` back.) We also fire a signal with the results. To do this, we need to create a signal message via object's `createSignal()`, serialize the results into it, and then send it out to subscribers by invoking object's `emitSignal()`. @@ -587,7 +587,7 @@ Yes, there is -- we can access the corresponding D-Bus message in: * property set implementation callback handlers (server side), * signal callback handlers (client side). -Both `IObject` and `IProxy` provide the `getCurrentlyProcessedMessage()` method. This method is meant to be called from within a callback handler. It returns a pointer to the corresponding D-Bus message that caused invocation of the handler. The pointer is only valid (dereferencable) as long as the flow of execution does not leave the callback handler. When called from other contexts/threads, the pointer may be both zero or non-zero, and its dereferencing is undefined behavior. +Both `IObject` and `IProxy` provide the `getCurrentlyProcessedMessage()` method. This method is meant to be called from within a callback handler. It returns a pointer to the corresponding D-Bus message that caused invocation of the handler. The pointer is only valid (dereferenceable) as long as the flow of execution does not leave the callback handler. When called from other contexts/threads, the pointer may be both zero or non-zero, and its dereferencing is undefined behavior. An excerpt of the above example of concatenator modified to print out a name of the sender of method call: @@ -765,7 +765,7 @@ In our object class we need to: * Give an implementation to the D-Bus object's methods by overriding corresponding virtual functions, * call `registerAdaptor()` in the constructor, which makes the adaptor (the D-Bus object underneath it) available for remote calls, - * call `unregisterAdaptor()`, which, conversely, unregisters the adaptor from the bus. + * call `unregisterAdaptor()`, which, conversely, deregisters the adaptor from the bus. Calling `registerAdaptor()` and `unregisterAdaptor()` was not necessary in previous sdbus-c++ versions, as it was handled by the parent class. This was convenient, but suffered from a potential pure virtual function call issue. Only the class that implements virtual functions can do the registration, hence this slight inconvenience on user's shoulders. @@ -844,7 +844,7 @@ In our proxy class we need to: * Give an implementation to signal handlers and asynchronous method reply handlers (if any) by overriding corresponding virtual functions, * call `registerProxy()` in the constructor, which makes the proxy (the D-Bus proxy object underneath it) ready to receive signals and async call replies, - * call `unregisterProxy()`, which, conversely, unregisters the proxy from the bus. + * call `unregisterProxy()`, which, conversely, deregisters the proxy from the bus. Calling `registerProxy()` and `unregisterProxy()` was not necessary in previous versions of sdbus-c++, as it was handled by the parent class. This was convenient, but suffered from a potential pure virtual function call issue. Only the class that implements virtual functions can do the registration, hence this slight inconvenience on user's shoulders. @@ -1002,7 +1002,7 @@ Callbacks of async methods based on convenience sdbus-c++ API have slightly diff * The result holder is of type `Result&&`, where `Types...` is a list of method output argument types. * The result object must be the first physical parameter of the callback taken by r-value ref. `Result` class template is move-only. * The callback itself is physically a void-returning function. - * Method input arguments are taken by value rathern than by const ref, because we usually want to `std::move` them to the worker thread. Moving is usually a lot cheaper than copying, and it's idiomatic. For non-movable types, this falls back to copying. + * Method input arguments are taken by value rather than by const ref, because we usually want to `std::move` them to the worker thread. Moving is usually a lot cheaper than copying, and it's idiomatic. For non-movable types, this falls back to copying. So the concatenate callback signature would change from `std::string concatenate(const std::vector& numbers, const std::string& separator)` to `void concatenate(sdbus::Result&& result, std::vector numbers, std::string separator)`: @@ -1037,7 +1037,7 @@ void concatenate(sdbus::Result&& result, std::vector numbe The `Result` is a convenience class that represents a future method result, and it is where we write the results (`returnResults()`) or an error (`returnError()`) which we want to send back to the client. -Registraion (`implementedAs()`) doesn't change. Nothing else needs to change. +Registration (`implementedAs()`) doesn't change. Nothing else needs to change. ### Marking server-side async methods in the IDL @@ -1349,22 +1349,22 @@ Using D-Bus Types For many D-Bus interactions dealing with D-Bus types is necessary. For that, sdbus-c++ provides many predefined D-Bus types. The table below shows which C++ type corresponds to which D-Bus type. -| Category | Code | Code ASCII | Conventional Name | C++ Type | +| Category | Code | Code ASCII | Conventional Name | C++ Type | |---------------------|-------------|------------|--------------------|---------------------------------| | reserved | 0 | NUL | INVALID | - | -| fixed, basic | 121 | y | BYTE | `uint8_t` | -| fixed, basic | 98 | b | BOOLEAN | `bool` | -| fixed, basic | 110 | n | INT16 | `int16_t` | -| fixed, basic | 113 | q | UINT16 | `uint16_t` | -| fixed, basic | 105 | i | INT32 | `int32_t` | -| fixed, basic | 117 | u | UINT32 | `uint32_t` | -| fixed, basic | 120 | x | INT64 | `int64_t` | -| fixed, basic | 116 | t | UINT64 | `uint64_t` | -| fixed, basic | 100 | d | DOUBLE | `double` | -| string-like, basic | 115 | s | STRING | `const char*`, `std::string` | -| string-like, basic | 111 | o | OBJECT_PATH | `sdbus::ObjectPath` | -| string-like, basic | 103 | g | SIGNATURE | `sdbus::Signature` | -| container | 97 | a | ARRAY | `std::vector` (if used as an array followed by a single complete type T), or `std::map` (if used as an array of dict entries) | +| fixed, basic | 121 | y | BYTE | `uint8_t` | +| fixed, basic | 98 | b | BOOLEAN | `bool` | +| fixed, basic | 110 | n | INT16 | `int16_t` | +| fixed, basic | 113 | q | UINT16 | `uint16_t` | +| fixed, basic | 105 | i | INT32 | `int32_t` | +| fixed, basic | 117 | u | UINT32 | `uint32_t` | +| fixed, basic | 120 | x | INT64 | `int64_t` | +| fixed, basic | 116 | t | UINT64 | `uint64_t` | +| fixed, basic | 100 | d | DOUBLE | `double` | +| string-like, basic | 115 | s | STRING | `const char*`, `std::string` | +| string-like, basic | 111 | o | OBJECT_PATH | `sdbus::ObjectPath` | +| string-like, basic | 103 | g | SIGNATURE | `sdbus::Signature` | +| container | 97 | a | ARRAY | `std::vector`, `std::array`, `std::span` - if used as an array followed by a single complete type `T`
`std::map`, `std::unordered_map` - if used as an array of dict entries | | container | 114,40,41 | r() | STRUCT | `sdbus::Struct` variadic class template | | container | 118 | v | VARIANT | `sdbus::Variant` | | container | 101,123,125 | e{} | DICT_ENTRY | - | diff --git a/include/sdbus-c++/Message.h b/include/sdbus-c++/Message.h index 742d4e3..91f613c 100755 --- a/include/sdbus-c++/Message.h +++ b/include/sdbus-c++/Message.h @@ -31,7 +31,12 @@ #include #include #include +#include +#if __cplusplus >= 202002L +#include +#endif #include +#include #include #include #include @@ -85,10 +90,23 @@ namespace sdbus { Message& operator<<(const ObjectPath &item); Message& operator<<(const Signature &item); Message& operator<<(const UnixFd &item); - template Message& operator<<(const std::vector<_Element>& items); - template Message& operator<<(const std::map<_Key, _Value>& items); - template Message& operator<<(const Struct<_ValueTypes...>& item); - template Message& operator<<(const std::tuple<_ValueTypes...>& item); + + template + Message& operator<<(const std::vector<_Element, _Allocator>& items); + template + Message& operator<<(const std::array<_Element, _Size>& items); +#if __cplusplus >= 202002L + template + Message& operator<<(const std::span<_Element, _Extent>& items); +#endif + template + Message& operator<<(const std::map<_Key, _Value, _Compare, _Allocator>& items); + template + Message& operator<<(const std::unordered_map<_Key, _Value, _Hash, _KeyEqual, _Allocator>& items); + template + Message& operator<<(const Struct<_ValueTypes...>& item); + template + Message& operator<<(const std::tuple<_ValueTypes...>& item); Message& operator>>(bool& item); Message& operator>>(int16_t& item); @@ -105,10 +123,22 @@ namespace sdbus { Message& operator>>(ObjectPath &item); Message& operator>>(Signature &item); Message& operator>>(UnixFd &item); - template Message& operator>>(std::vector<_Element>& items); - template Message& operator>>(std::map<_Key, _Value>& items); - template Message& operator>>(Struct<_ValueTypes...>& item); - template Message& operator>>(std::tuple<_ValueTypes...>& item); + template + Message& operator>>(std::vector<_Element, _Allocator>& items); + template + Message& operator>>(std::array<_Element, _Size>& items); +#if __cplusplus >= 202002L + template + Message& operator>>(std::span<_Element, _Extent>& items); +#endif + template + Message& operator>>(std::map<_Key, _Value, _Compare, _Allocator>& items); + template + Message& operator>>(std::unordered_map<_Key, _Value, _Hash, _KeyEqual, _Allocator>& items); + template + Message& operator>>(Struct<_ValueTypes...>& item); + template + Message& operator>>(std::tuple<_ValueTypes...>& item); Message& openContainer(const std::string& signature); Message& closeContainer(); @@ -139,6 +169,7 @@ namespace sdbus { void peekType(std::string& type, std::string& contents) const; bool isValid() const; bool isEmpty() const; + bool isAtEnd(bool complete) const; void copyTo(Message& destination, bool complete) const; void seal(); @@ -155,9 +186,27 @@ namespace sdbus { class Factory; private: + template + void serializeArray(const _Array& items); + template + void deserializeArray(_Array& items); + template + void deserializeArrayFast(_Array& items); + template + void deserializeArrayFast(std::vector<_Element, _Allocator>& items); + template + void deserializeArraySlow(_Array& items); + template + void deserializeArraySlow(std::vector<_Element, _Allocator>& items); + void appendArray(char type, const void *ptr, size_t size); void readArray(char type, const void **ptr, size_t *size); + template + void serializeDictionary(const _Dictionary& items); + template + void deserializeDictionary(_Dictionary& items); + protected: Message() = default; explicit Message(internal::ISdBus* sdbus) noexcept; @@ -256,32 +305,77 @@ namespace sdbus { PlainMessage() = default; }; - template - inline Message& Message::operator<<(const std::vector<_Element>& items) + template + inline Message& Message::operator<<(const std::vector<_Element, _Allocator>& items) { + serializeArray(items); + + return *this; + } + + template + inline Message& Message::operator<<(const std::array<_Element, _Size>& items) + { + serializeArray(items); + + return *this; + } + +#if __cplusplus >= 202002L + template + inline Message& Message::operator<<(const std::span<_Element, _Extent>& items) + { + serializeArray(items); + + return *this; + } +#endif + + template + inline void Message::serializeArray(const _Array& items) + { + using ElementType = typename _Array::value_type; + // Use faster, one-step serialization of contiguous array of elements of trivial D-Bus types except bool, // otherwise use step-by-step serialization of individual elements. - if constexpr (signature_of<_Element>::is_trivial_dbus_type && !std::is_same_v<_Element, bool>) + if constexpr (signature_of::is_trivial_dbus_type && !std::is_same_v) { - appendArray(*signature_of<_Element>::str().c_str(), items.data(), items.size() * sizeof(_Element)); + appendArray(*signature_of::str().c_str(), items.data(), items.size() * sizeof(ElementType)); } else { - openContainer(signature_of<_Element>::str()); + openContainer(signature_of::str()); for (const auto& item : items) *this << item; closeContainer(); } + } + + template + inline Message& Message::operator<<(const std::map<_Key, _Value, _Compare, _Allocator>& items) + { + serializeDictionary(items); return *this; } - template - inline Message& Message::operator<<(const std::map<_Key, _Value>& items) + template + inline Message& Message::operator<<(const std::unordered_map<_Key, _Value, _Hash, _KeyEqual, _Allocator>& items) { - const std::string dictEntrySignature = signature_of<_Key>::str() + signature_of<_Value>::str(); + serializeDictionary(items); + + return *this; + } + + template + inline void Message::serializeDictionary(const _Dictionary& items) + { + using KeyType = typename _Dictionary::key_type; + using ValueType = typename _Dictionary::mapped_type; + + const std::string dictEntrySignature = signature_of::str() + signature_of::str(); const std::string arraySignature = "{" + dictEntrySignature + "}"; openContainer(arraySignature); @@ -295,8 +389,6 @@ namespace sdbus { } closeContainer(); - - return *this; } namespace detail @@ -322,7 +414,7 @@ namespace sdbus { 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); + auto structContentSignature = structSignature.substr(1, structSignature.size() - 2); openStruct(structContentSignature); detail::serialize_tuple(*this, item, std::index_sequence_for<_ValueTypes...>{}); @@ -338,58 +430,152 @@ namespace sdbus { return *this; } - template - inline Message& Message::operator>>(std::vector<_Element>& items) + template + inline Message& Message::operator>>(std::vector<_Element, _Allocator>& items) { - // Use faster, one-step deserialization of contiguous array of elements of trivial D-Bus types except bool, - // otherwise use step-by-step deserialization of individual elements. - if constexpr (signature_of<_Element>::is_trivial_dbus_type && !std::is_same_v<_Element, bool>) - { - size_t arraySize{}; - const _Element* arrayPtr{}; - - readArray(*signature_of<_Element>::str().c_str(), (const void**)&arrayPtr, &arraySize); - - items.insert(items.end(), arrayPtr, arrayPtr + (arraySize / sizeof(_Element))); - } - else - { - if(!enterContainer(signature_of<_Element>::str())) - return *this; - - while (true) - { - _Element elem; - if (*this >> elem) - items.emplace_back(std::move(elem)); - else - break; - } - - clearFlags(); - - exitContainer(); - } + deserializeArray(items); return *this; } - template - inline Message& Message::operator>>(std::map<_Key, _Value>& items) + template + inline Message& Message::operator>>(std::array<_Element, _Size>& items) { - const std::string dictEntrySignature = signature_of<_Key>::str() + signature_of<_Value>::str(); + deserializeArray(items); + + return *this; + } + +#if __cplusplus >= 202002L + template + inline Message& Message::operator>>(std::span<_Element, _Extent>& items) + { + deserializeArray(items); + + return *this; + } +#endif + + template + inline void Message::deserializeArray(_Array& items) + { + using ElementType = typename _Array::value_type; + + // Use faster, one-step deserialization of contiguous array of elements of trivial D-Bus types except bool, + // otherwise use step-by-step deserialization of individual elements. + if constexpr (signature_of::is_trivial_dbus_type && !std::is_same_v) + { + deserializeArrayFast(items); + } + else + { + deserializeArraySlow(items); + } + } + + template + inline void Message::deserializeArrayFast(_Array& items) + { + using ElementType = typename _Array::value_type; + + size_t arraySize{}; + const ElementType* arrayPtr{}; + + readArray(*signature_of::str().c_str(), (const void**)&arrayPtr, &arraySize); + + size_t elementsInMsg = arraySize / sizeof(ElementType); + bool notEnoughSpace = items.size() < elementsInMsg; + SDBUS_THROW_ERROR_IF(notEnoughSpace, "Failed to deserialize array: not enough space in destination sequence", EINVAL); + + std::copy_n(arrayPtr, elementsInMsg, items.begin()); + } + + template + void Message::deserializeArrayFast(std::vector<_Element, _Allocator>& items) + { + size_t arraySize{}; + const _Element* arrayPtr{}; + + readArray(*signature_of<_Element>::str().c_str(), (const void**)&arrayPtr, &arraySize); + + items.insert(items.end(), arrayPtr, arrayPtr + (arraySize / sizeof(_Element))); + } + + template + inline void Message::deserializeArraySlow(_Array& items) + { + using ElementType = typename _Array::value_type; + + if(!enterContainer(signature_of::str())) + return; + + for (auto& elem : items) + if (!(*this >> elem)) + break; // Keep the rest in the destination sequence untouched + + SDBUS_THROW_ERROR_IF(!isAtEnd(false), "Failed to deserialize array: not enough space in destination sequence", EINVAL); + + clearFlags(); + + exitContainer(); + } + + template + void Message::deserializeArraySlow(std::vector<_Element, _Allocator>& items) + { + if(!enterContainer(signature_of<_Element>::str())) + return; + + while (true) + { + _Element elem; + // TODO: Is there a way to find D-Bus message container size upfront? We could reserve space in vector upfront. + if (*this >> elem) + items.emplace_back(std::move(elem)); + else + break; + } + + clearFlags(); + + exitContainer(); + } + + template + inline Message& Message::operator>>(std::map<_Key, _Value, _Compare, _Allocator>& items) + { + deserializeDictionary(items); + + return *this; + } + + template + inline Message& Message::operator>>(std::unordered_map<_Key, _Value, _Hash, _KeyEqual, _Allocator>& items) + { + deserializeDictionary(items); + + return *this; + } + + template + inline void Message::deserializeDictionary(_Dictionary& items) + { + using KeyType = typename _Dictionary::key_type; + using ValueType = typename _Dictionary::mapped_type; + + const std::string dictEntrySignature = signature_of::str() + signature_of::str(); const std::string arraySignature = "{" + dictEntrySignature + "}"; if (!enterContainer(arraySignature)) - return *this; + return; while (true) { if (!enterDictEntry(dictEntrySignature)) break; - _Key key; - _Value value; + KeyType key; + ValueType value; *this >> key >> value; items.emplace(std::move(key), std::move(value)); @@ -400,8 +586,6 @@ namespace sdbus { clearFlags(); exitContainer(); - - return *this; } namespace detail diff --git a/include/sdbus-c++/TypeTraits.h b/include/sdbus-c++/TypeTraits.h index f322569..e501681 100644 --- a/include/sdbus-c++/TypeTraits.h +++ b/include/sdbus-c++/TypeTraits.h @@ -30,7 +30,12 @@ #include #include #include +#include +#if __cplusplus >= 202002L +#include +#endif #include +#include #include #include #include @@ -105,6 +110,11 @@ namespace sdbus { } }; + template + struct signature_of + : public signature_of<_T> + {}; + template <> struct signature_of { @@ -349,8 +359,8 @@ namespace sdbus { } }; - template - struct signature_of> + template + struct signature_of> { static constexpr bool is_valid = true; static constexpr bool is_trivial_dbus_type = false; @@ -361,8 +371,46 @@ namespace sdbus { } }; - template - struct signature_of> + template + struct signature_of> + { + static constexpr bool is_valid = true; + static constexpr bool is_trivial_dbus_type = false; + + static const std::string str() + { + return "a" + signature_of<_Element>::str(); + } + }; + +#if __cplusplus >= 202002L + template + struct signature_of> + { + static constexpr bool is_valid = true; + static constexpr bool is_trivial_dbus_type = false; + + static const std::string str() + { + return "a" + signature_of<_Element>::str(); + } + }; +#endif + + template + struct signature_of> + { + static constexpr bool is_valid = true; + static constexpr bool is_trivial_dbus_type = false; + + static const std::string str() + { + return "a{" + signature_of<_Key>::str() + signature_of<_Value>::str() + "}"; + } + }; + + template + struct signature_of> { static constexpr bool is_valid = true; static constexpr bool is_trivial_dbus_type = false; diff --git a/src/Message.cpp b/src/Message.cpp index 2b2f735..3373fe0 100755 --- a/src/Message.cpp +++ b/src/Message.cpp @@ -644,6 +644,11 @@ bool Message::isEmpty() const return sd_bus_message_is_empty((sd_bus_message*)msg_) != 0; } +bool Message::isAtEnd(bool complete) const +{ + return sd_bus_message_at_end((sd_bus_message*)msg_, complete) > 0; +} + pid_t Message::getCredsPid() const { uint64_t mask = SD_BUS_CREDS_PID | SD_BUS_CREDS_AUGMENT; diff --git a/tests/integrationtests/DBusMethodsTests.cpp b/tests/integrationtests/DBusMethodsTests.cpp index 77bb317..93ca28e 100644 --- a/tests/integrationtests/DBusMethodsTests.cpp +++ b/tests/integrationtests/DBusMethodsTests.cpp @@ -125,8 +125,8 @@ TEST_F(SdbusTestObject, CallsMethodWithTwoStructsSuccesfully) TEST_F(SdbusTestObject, CallsMethodWithTwoVectorsSuccesfully) { - auto val = m_proxy->sumVectorItems({1, 7}, {2, 3}); - ASSERT_THAT(val, Eq(1 + 7 + 2 + 3)); + auto val = m_proxy->sumArrayItems({1, 7}, {2, 3, 4}); + ASSERT_THAT(val, Eq(1 + 7 + 2 + 3 + 4)); } TEST_F(SdbusTestObject, CallsMethodWithSignatureSuccesfully) diff --git a/tests/integrationtests/TestAdaptor.cpp b/tests/integrationtests/TestAdaptor.cpp index cfbe83f..4157e06 100644 --- a/tests/integrationtests/TestAdaptor.cpp +++ b/tests/integrationtests/TestAdaptor.cpp @@ -104,7 +104,7 @@ int32_t TestAdaptor::sumStructItems(const sdbus::Struct& a, c return res; } -uint32_t TestAdaptor::sumVectorItems(const std::vector& a, const std::vector& b) +uint32_t TestAdaptor::sumArrayItems(const std::vector& a, const std::array& b) { uint32_t res{0}; for (auto x : a) @@ -162,9 +162,9 @@ sdbus::UnixFd TestAdaptor::getUnixFd() return sdbus::UnixFd{UNIX_FD_VALUE}; } -std::map>>>, sdbus::Signature, std::string>> TestAdaptor::getComplex() +std::unordered_map>>>, sdbus::Signature, std::string>> TestAdaptor::getComplex() { - return { // map + return { // unordered_map { 0, // uint_64_t { // struct @@ -390,7 +390,7 @@ R"delimiter( - + diff --git a/tests/integrationtests/TestAdaptor.h b/tests/integrationtests/TestAdaptor.h index 3c26f26..550adf8 100644 --- a/tests/integrationtests/TestAdaptor.h +++ b/tests/integrationtests/TestAdaptor.h @@ -70,13 +70,13 @@ protected: std::map getMapOfVariants(const std::vector& x, const sdbus::Struct& y) override; sdbus::Struct>> getStructInStruct() override; int32_t sumStructItems(const sdbus::Struct& arg0, const sdbus::Struct& arg1) override; - uint32_t sumVectorItems(const std::vector& arg0, const std::vector& arg1) override; + uint32_t sumArrayItems(const std::vector& arg0, const std::array& arg1) override; uint32_t doOperation(const uint32_t& arg0) override; void doOperationAsync(sdbus::Result&& result, uint32_t arg0) override; sdbus::Signature getSignature() override; sdbus::ObjectPath getObjPath() override; sdbus::UnixFd getUnixFd() override; - std::map>>>, sdbus::Signature, std::string>> getComplex() override; + std::unordered_map>>>, sdbus::Signature, std::string>> getComplex() override; void throwError() override; void throwErrorWithNoReply() override; void doPrivilegedStuff() override; @@ -127,13 +127,13 @@ protected: std::map getMapOfVariants(const std::vector&, const sdbus::Struct&) override { return {}; } sdbus::Struct>> getStructInStruct() override { return {}; } int32_t sumStructItems(const sdbus::Struct&, const sdbus::Struct&) override { return {}; } - uint32_t sumVectorItems(const std::vector&, const std::vector&) override { return {}; } + uint32_t sumArrayItems(const std::vector&, const std::array&) override { return {}; } uint32_t doOperation(const uint32_t&) override { return {}; } void doOperationAsync(sdbus::Result&&, uint32_t) override {} sdbus::Signature getSignature() override { return {}; } sdbus::ObjectPath getObjPath() override { return {}; } sdbus::UnixFd getUnixFd() override { return {}; } - std::map>>>, sdbus::Signature, std::string>> getComplex() override { return {}; } + std::unordered_map>>>, sdbus::Signature, std::string>> getComplex() override { return {}; } void throwError() override {} void throwErrorWithNoReply() override {} void doPrivilegedStuff() override {} diff --git a/tests/integrationtests/integrationtests-adaptor.h b/tests/integrationtests/integrationtests-adaptor.h index cbf7464..5a7ef76 100644 --- a/tests/integrationtests/integrationtests-adaptor.h +++ b/tests/integrationtests/integrationtests-adaptor.h @@ -33,7 +33,7 @@ protected: object_->registerMethod("getMapOfVariants").onInterface(INTERFACE_NAME).withInputParamNames("x", "y").withOutputParamNames("aMapOfVariants").implementedAs([this](const std::vector& x, const sdbus::Struct& y){ return this->getMapOfVariants(x, y); }); object_->registerMethod("getStructInStruct").onInterface(INTERFACE_NAME).withOutputParamNames("aMapOfVariants").implementedAs([this](){ return this->getStructInStruct(); }); object_->registerMethod("sumStructItems").onInterface(INTERFACE_NAME).withInputParamNames("arg0", "arg1").withOutputParamNames("arg0").implementedAs([this](const sdbus::Struct& arg0, const sdbus::Struct& arg1){ return this->sumStructItems(arg0, arg1); }); - object_->registerMethod("sumVectorItems").onInterface(INTERFACE_NAME).withInputParamNames("arg0", "arg1").withOutputParamNames("arg0").implementedAs([this](const std::vector& arg0, const std::vector& arg1){ return this->sumVectorItems(arg0, arg1); }); + object_->registerMethod("sumArrayItems").onInterface(INTERFACE_NAME).withInputParamNames("arg0", "arg1").withOutputParamNames("arg0").implementedAs([this](const std::vector& arg0, const std::array& arg1){ return this->sumArrayItems(arg0, arg1); }); object_->registerMethod("doOperation").onInterface(INTERFACE_NAME).withInputParamNames("arg0").withOutputParamNames("arg0").implementedAs([this](const uint32_t& arg0){ return this->doOperation(arg0); }); object_->registerMethod("doOperationAsync").onInterface(INTERFACE_NAME).withInputParamNames("arg0").withOutputParamNames("arg0").implementedAs([this](sdbus::Result&& result, uint32_t arg0){ this->doOperationAsync(std::move(result), std::move(arg0)); }); object_->registerMethod("getSignature").onInterface(INTERFACE_NAME).withOutputParamNames("arg0").implementedAs([this](){ return this->getSignature(); }); @@ -86,13 +86,13 @@ private: virtual std::map getMapOfVariants(const std::vector& x, const sdbus::Struct& y) = 0; virtual sdbus::Struct>> getStructInStruct() = 0; virtual int32_t sumStructItems(const sdbus::Struct& arg0, const sdbus::Struct& arg1) = 0; - virtual uint32_t sumVectorItems(const std::vector& arg0, const std::vector& arg1) = 0; + virtual uint32_t sumArrayItems(const std::vector& arg0, const std::array& arg1) = 0; virtual uint32_t doOperation(const uint32_t& arg0) = 0; virtual void doOperationAsync(sdbus::Result&& result, uint32_t arg0) = 0; virtual sdbus::Signature getSignature() = 0; virtual sdbus::ObjectPath getObjPath() = 0; virtual sdbus::UnixFd getUnixFd() = 0; - virtual std::map>>>, sdbus::Signature, std::string>> getComplex() = 0; + virtual std::unordered_map>>>, sdbus::Signature, std::string>> getComplex() = 0; virtual void throwError() = 0; virtual void throwErrorWithNoReply() = 0; virtual void doPrivilegedStuff() = 0; diff --git a/tests/integrationtests/integrationtests-proxy.h b/tests/integrationtests/integrationtests-proxy.h index 7fc9078..6704686 100644 --- a/tests/integrationtests/integrationtests-proxy.h +++ b/tests/integrationtests/integrationtests-proxy.h @@ -105,10 +105,10 @@ public: return result; } - uint32_t sumVectorItems(const std::vector& arg0, const std::vector& arg1) + uint32_t sumArrayItems(const std::vector& arg0, const std::array& arg1) { uint32_t result; - proxy_->callMethod("sumVectorItems").onInterface(INTERFACE_NAME).withArguments(arg0, arg1).storeResultsTo(result); + proxy_->callMethod("sumArrayItems").onInterface(INTERFACE_NAME).withArguments(arg0, arg1).storeResultsTo(result); return result; } @@ -147,9 +147,9 @@ public: return result; } - std::map>>>, sdbus::Signature, std::string>> getComplex() + std::map>>>, sdbus::Signature, std::string>> getComplex() { - std::map>>>, sdbus::Signature, std::string>> result; + std::map>>>, sdbus::Signature, std::string>> result; proxy_->callMethod("getComplex").onInterface(INTERFACE_NAME).storeResultsTo(result); return result; } diff --git a/tests/integrationtests/org.sdbuscpp.integrationtests.xml b/tests/integrationtests/org.sdbuscpp.integrationtests.xml index e658382..bec5aeb 100644 --- a/tests/integrationtests/org.sdbuscpp.integrationtests.xml +++ b/tests/integrationtests/org.sdbuscpp.integrationtests.xml @@ -45,7 +45,7 @@ - + diff --git a/tests/unittests/Message_test.cpp b/tests/unittests/Message_test.cpp index b4ae32f..0aa2525 100644 --- a/tests/unittests/Message_test.cpp +++ b/tests/unittests/Message_test.cpp @@ -116,7 +116,7 @@ TEST(AMessage, CanCarryASimpleInteger) { auto msg = sdbus::createPlainMessage(); - int dataWritten = 5; + const int dataWritten = 5; msg << dataWritten; msg.seal(); @@ -131,7 +131,7 @@ TEST(AMessage, CanCarryAUnixFd) { auto msg = sdbus::createPlainMessage(); - sdbus::UnixFd dataWritten{0}; + const sdbus::UnixFd dataWritten{0}; msg << dataWritten; msg.seal(); @@ -146,7 +146,7 @@ TEST(AMessage, CanCarryAVariant) { auto msg = sdbus::createPlainMessage(); - auto dataWritten = sdbus::Variant((double)3.14); + const auto dataWritten = sdbus::Variant((double)3.14); msg << dataWritten; msg.seal(); @@ -162,7 +162,7 @@ TEST(AMessage, CanCarryACollectionOfEmbeddedVariants) auto msg = sdbus::createPlainMessage(); auto value = std::vector{"hello"s, (double)3.14}; - auto dataWritten = sdbus::Variant{value}; + const auto dataWritten = sdbus::Variant{value}; msg << dataWritten; msg.seal(); @@ -174,11 +174,11 @@ TEST(AMessage, CanCarryACollectionOfEmbeddedVariants) ASSERT_THAT(dataRead.get()[1].get(), Eq(value[1].get())); } -TEST(AMessage, CanCarryAnArray) +TEST(AMessage, CanCarryDBusArrayOfTrivialTypesGivenAsStdVector) { auto msg = sdbus::createPlainMessage(); - std::vector dataWritten{3545342, 43643532, 324325}; + const std::vector dataWritten{3545342, 43643532, 324325}; msg << dataWritten; msg.seal(); @@ -189,6 +189,116 @@ TEST(AMessage, CanCarryAnArray) ASSERT_THAT(dataRead, Eq(dataWritten)); } +TEST(AMessage, CanCarryDBusArrayOfNontrivialTypesGivenAsStdVector) +{ + auto msg = sdbus::createPlainMessage(); + + const std::vector dataWritten{"s", "u", "b"}; + + msg << dataWritten; + msg.seal(); + + std::vector dataRead; + msg >> dataRead; + + ASSERT_THAT(dataRead, Eq(dataWritten)); +} + +TEST(AMessage, CanCarryDBusArrayOfTrivialTypesGivenAsStdArray) +{ + auto msg = sdbus::createPlainMessage(); + + const std::array dataWritten{3545342, 43643532, 324325}; + + msg << dataWritten; + msg.seal(); + + std::array dataRead; + msg >> dataRead; + + ASSERT_THAT(dataRead, Eq(dataWritten)); +} + +TEST(AMessage, CanCarryDBusArrayOfNontrivialTypesGivenAsStdArray) +{ + auto msg = sdbus::createPlainMessage(); + + const std::array dataWritten{"s", "u", "b"}; + + msg << dataWritten; + msg.seal(); + + std::array dataRead; + msg >> dataRead; + + ASSERT_THAT(dataRead, Eq(dataWritten)); +} + +#if __cplusplus >= 202002L +TEST(AMessage, CanCarryDBusArrayOfTrivialTypesGivenAsStdSpan) +{ + auto msg = sdbus::createPlainMessage(); + + const std::array sourceArray{3545342, 43643532, 324325}; + const std::span dataWritten{sourceArray}; + + msg << dataWritten; + msg.seal(); + + std::array destinationArray; + std::span dataRead{destinationArray}; + msg >> dataRead; + + ASSERT_THAT(std::vector(dataRead.begin(), dataRead.end()), Eq(std::vector(dataWritten.begin(), dataWritten.end()))); +} + +TEST(AMessage, CanCarryDBusArrayOfNontrivialTypesGivenAsStdSpan) +{ + auto msg = sdbus::createPlainMessage(); + + const std::array sourceArray{"s", "u", "b"}; + const std::span dataWritten{sourceArray}; + + msg << dataWritten; + msg.seal(); + + std::array destinationArray; + std::span dataRead{destinationArray}; + msg >> dataRead; + + ASSERT_THAT(std::vector(dataRead.begin(), dataRead.end()), Eq(std::vector(dataWritten.begin(), dataWritten.end()))); +} +#endif + +TEST(AMessage, ThrowsWhenDestinationStdArrayIsTooSmallDuringDeserialization) +{ + auto msg = sdbus::createPlainMessage(); + + const std::vector dataWritten{3545342, 43643532, 324325, 89789, 15343}; + + msg << dataWritten; + msg.seal(); + + std::array dataRead; + ASSERT_THROW(msg >> dataRead, sdbus::Error); +} + +#if __cplusplus >= 202002L +TEST(AMessage, ThrowsWhenDestinationStdSpanIsTooSmallDuringDeserialization) +{ + auto msg = sdbus::createPlainMessage(); + + const std::array dataWritten{3545342, 43643532, 324325}; + + msg << dataWritten; + msg.seal(); + + std::array destinationArray; + std::span dataRead{destinationArray}; + ASSERT_THROW(msg >> dataRead, sdbus::Error); +} +#endif + TEST(AMessage, CanCarryADictionary) { auto msg = sdbus::createPlainMessage(); diff --git a/tests/unittests/TypeTraits_test.cpp b/tests/unittests/TypeTraits_test.cpp index 825d9c1..169aa2d 100644 --- a/tests/unittests/TypeTraits_test.cpp +++ b/tests/unittests/TypeTraits_test.cpp @@ -77,7 +77,12 @@ namespace 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::array)HAS_DBUS_TYPE_SIGNATURE("an") +#if __cplusplus >= 202002L + TYPE(std::span)HAS_DBUS_TYPE_SIGNATURE("ao") +#endif TYPE(std::map)HAS_DBUS_TYPE_SIGNATURE("a{ix}") + TYPE(std::unordered_map)HAS_DBUS_TYPE_SIGNATURE("a{ix}") using ComplexType = std::map< uint64_t, sdbus::Struct< @@ -86,9 +91,10 @@ namespace std::vector< sdbus::Struct< sdbus::ObjectPath, + std::array, bool, sdbus::Variant, - std::map + std::unordered_map > > >, @@ -97,7 +103,7 @@ namespace const char* > >; - TYPE(ComplexType)HAS_DBUS_TYPE_SIGNATURE("a{t(a{ya(obva{is})}ghs)}") + TYPE(ComplexType)HAS_DBUS_TYPE_SIGNATURE("a{t(a{ya(oanbva{is})}ghs)}") typedef ::testing::Types< bool , uint8_t @@ -117,7 +123,12 @@ namespace , sdbus::Struct , sdbus::Struct , std::vector + , std::array +#if __cplusplus >= 202002L + , std::span +#endif , std::map + , std::unordered_map , ComplexType > DBusSupportedTypes;