Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 27 additions & 7 deletions include/zenoh/api/ext/serialization.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ class Serializer : public Owned<::ze_owned_serializer_t> {
template <class T>
void serialize(const T& value, ZResult* err = nullptr);

/// @brief Write a sequence length without serializing its elements.
/// The elements can then be written individually with serialize(). The caller must write exactly length elements.
/// @param length number of elements in the sequence.
/// @param err if not null, the result code will be written to this location, otherwise ZException exception
/// will be thrown in case of error (or the process will abort if exceptions are disabled).
void serialize_sequence_length(size_t length, ZResult* err = nullptr) {
__ZENOH_RESULT_CHECK(::ze_serializer_serialize_sequence_length(interop::as_loaned_c_ptr(*this), length), err,
"Failed to serialize sequence length");
}

/// @brief Finalize serialization and return the underlying `Bytes` object.
/// @return underlying `Bytes` object.
Bytes finish() && {
Expand Down Expand Up @@ -87,6 +97,20 @@ class Deserializer : public Copyable<::ze_deserializer_t> {
template <class T>
T deserialize(zenoh::ZResult* err = nullptr);

/// @brief Read a sequence length without allocating or deserializing its elements.
/// The elements can then be read individually with deserialize(), after checking an application-defined limit.
/// This reads only the length prefix and does not check that all elements are present.
/// @param err if not null, the result code will be written to this location, otherwise ZException exception
/// will be thrown in case of error (or the process will abort if exceptions are disabled).
/// @return The sequence length, or zero on failure when err is provided. Check err to distinguish failure
/// from an empty sequence.
size_t deserialize_sequence_length(zenoh::ZResult* err = nullptr) {
size_t length = 0;
auto result = ::ze_deserializer_deserialize_sequence_length(interop::as_copyable_c_ptr(*this), &length);
__ZENOH_RESULT_CHECK(result, err, "Deserialization failure:: Failed to read sequence length");
return result == Z_OK ? length : 0;
}

/// @brief Checks if deserializer has parsed all the data.
/// @return `true` if there is no more data to parse, `false` otherwise.
bool is_done() const { return ::ze_deserializer_is_done(&this->_0); }
Expand Down Expand Up @@ -183,8 +207,7 @@ bool __zenoh_serialize_with_serializer(zenoh::ext::Serializer& serializer, const
template <class It>
bool __serialize_sequence_with_serializer(zenoh::ext::Serializer& serializer, It begin, It end, size_t n,
ZResult* err) {
__ZENOH_RESULT_CHECK(::ze_serializer_serialize_sequence_length(zenoh::interop::as_loaned_c_ptr(serializer), n), err,
"Failed to serialize sequence length");
serializer.serialize_sequence_length(n, err);
if (err != nullptr && *err != Z_OK) {
return false;
}
Expand Down Expand Up @@ -301,11 +324,8 @@ bool __zenoh_deserialize_with_deserializer(zenoh::ext::Deserializer& deserialize
deserialize_with_deserializer(deserializer, value.second, err);
}

#define _ZENOH_DESERIALIZE_SEQUENCE_BEGIN \
size_t len; \
__ZENOH_RESULT_CHECK( \
::ze_deserializer_deserialize_sequence_length(zenoh::interop::as_copyable_c_ptr(deserializer), &len), err, \
"Deserialization failure:: Failed to read sequence length"); \
#define _ZENOH_DESERIALIZE_SEQUENCE_BEGIN \
size_t len = deserializer.deserialize_sequence_length(err); \
if (err != nullptr && *err != Z_OK) return false;

#define _ZENOH_DESERIALIZE_SEQUENCE_END return (err == nullptr || *err == Z_OK);
Expand Down
83 changes: 83 additions & 0 deletions tests/universal/serialization.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,86 @@ void serialize_container() {
assert(zenoh_test_serialization(m2));
}

void serialize_sequence_length_test() {
const std::vector<std::string> values = {"first", "second"};
for (bool report_error : {false, true}) {
ext::Serializer serializer;
ZResult err = Z_EINVAL;
serializer.serialize_sequence_length(values.size(), report_error ? &err : nullptr);
if (report_error) assert(err == Z_OK);
for (const auto& value : values) serializer.serialize(value);
serializer.serialize(uint16_t(500));
auto bytes = std::move(serializer).finish();
assert(bytes.as_vector() == ext::serialize(std::make_tuple(values, uint16_t(500))).as_vector());
ext::Deserializer deserializer(bytes);
assert(deserializer.deserialize<std::vector<std::string>>() == values);
assert(deserializer.deserialize<uint16_t>() == 500);
assert(deserializer.is_done());
}

ext::Serializer empty_serializer;
empty_serializer.serialize_sequence_length(0);
assert(std::move(empty_serializer).finish().as_vector() == ext::serialize(std::vector<uint8_t>{}).as_vector());

// Writing a length alone must not add element data.
ext::Serializer length_only_serializer;
length_only_serializer.serialize_sequence_length(1048576);
const std::vector<uint8_t> expected_prefix = {0x80, 0x80, 0x40};
assert(std::move(length_only_serializer).finish().as_vector() == expected_prefix);
}

void deserialize_sequence_length_test() {
const std::vector<std::string> values = {"first", "second"};
auto bytes = ext::serialize(std::make_tuple(values, uint16_t(500)));
ext::Deserializer deserializer(bytes);
ZResult err = Z_EINVAL;
assert(deserializer.deserialize_sequence_length(&err) == values.size());
assert(err == Z_OK);
assert(!deserializer.is_done());
for (const auto& value : values) {
assert(deserializer.deserialize<std::string>() == value);
}
// Reading the sequence length must leave both its elements and the following value available.
assert(deserializer.deserialize<uint16_t>() == 500);
assert(deserializer.is_done());

auto empty = ext::serialize(std::vector<uint8_t>{});
ext::Deserializer empty_deserializer(empty);
assert(empty_deserializer.deserialize_sequence_length(&err) == 0);
assert(err == Z_OK);
assert(empty_deserializer.is_done());

// A length-only prefix advertising 1,048,576 elements can be read and rejected
// against a limit before allocating a container or attempting to read any elements.
Bytes length_only(std::vector<uint8_t>{0x80, 0x80, 0x40});
ext::Deserializer bounded_deserializer(length_only);
const auto length = bounded_deserializer.deserialize_sequence_length();
assert(length == 1048576);
assert(bounded_deserializer.is_done());
}

void deserialize_sequence_length_error_test() {
// Missing and truncated length prefixes fail in both error-reporting modes.
for (const auto& wire : {std::vector<uint8_t>{}, std::vector<uint8_t>{0x81}}) {
Bytes bytes(wire);
ext::Deserializer deserializer(bytes);
ZResult err = Z_OK;
assert(deserializer.deserialize_sequence_length(&err) == 0);
assert(err != Z_OK);
#ifdef __cpp_exceptions
ext::Deserializer throwing_deserializer(bytes);
bool caught = false;
try {
throwing_deserializer.deserialize_sequence_length();
} catch (const ZException& exception) {
caught = true;
assert(exception.e == err);
}
assert(caught);
#endif
}
}

struct CustomStruct {
std::vector<double> vd;
int32_t i;
Expand Down Expand Up @@ -124,6 +204,9 @@ int main(int argc, char** argv) {
serialize_primitive();
serialize_tuple();
serialize_container();
serialize_sequence_length_test();
deserialize_sequence_length_test();
deserialize_sequence_length_error_test();
serialize_custom();
binary_format_test();
}
Loading