diff --git a/include/sdbus-c++/Message.h b/include/sdbus-c++/Message.h index 742d4e33..91f613cf 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::unordered_map<_Key, _Value, _Hash, _KeyEqual, _Allocator>& items) + { + serializeDictionary(items); return *this; } - template - inline Message& Message::operator<<(const std::map<_Key, _Value>& items) + template + inline void Message::serializeDictionary(const _Dictionary& items) { - const std::string dictEntrySignature = signature_of<_Key>::str() + signature_of<_Value>::str(); + 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) + { + deserializeArray(items); + + return *this; + } + + template + inline Message& Message::operator>>(std::array<_Element, _Size>& items) + { + 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<_Element>::is_trivial_dbus_type && !std::is_same_v<_Element, bool>) + if constexpr (signature_of::is_trivial_dbus_type && !std::is_same_v) { - 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))); + deserializeArrayFast(items); } else { - if(!enterContainer(signature_of<_Element>::str())) - return *this; + 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(); - while (true) - { - _Element elem; - if (*this >> elem) - items.emplace_back(std::move(elem)); - else - break; - } + exitContainer(); + } - clearFlags(); + template + void Message::deserializeArraySlow(std::vector<_Element, _Allocator>& items) + { + if(!enterContainer(signature_of<_Element>::str())) + return; - exitContainer(); + 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::map<_Key, _Value>& items) + template + inline Message& Message::operator>>(std::unordered_map<_Key, _Value, _Hash, _KeyEqual, _Allocator>& items) { - const std::string dictEntrySignature = signature_of<_Key>::str() + signature_of<_Value>::str(); + 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 f3225692..8cece897 100644 --- a/include/sdbus-c++/TypeTraits.h +++ b/include/sdbus-c++/TypeTraits.h @@ -105,6 +105,11 @@ namespace sdbus { } }; + template + struct signature_of + : public signature_of<_T> + {}; + template <> struct signature_of { diff --git a/src/Message.cpp b/src/Message.cpp index 2b2f7351..3373fe03 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/unittests/Message_test.cpp b/tests/unittests/Message_test.cpp index b4ae32f5..0aa2525a 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();