From 2e95ec37667d8af879a51cf36f05375d1ec10559 Mon Sep 17 00:00:00 2001 From: delphi Date: Mon, 28 Feb 2022 23:12:57 +0300 Subject: [PATCH] Initial commit for (de)serialization This commit intoduces (de)serialization of primitive types. It also introduces an additional debug header - magic_enum - to be used in debugging return codes and pretty printing. I think, it should be deleted before this implementation goes to main branch. Signed-off-by: delphi Signed-off-by: delphi --- src/nunavut/lang/cpp/__init__.py | 14 + src/nunavut/lang/cpp/support/serialization.j2 | 113 +- .../lang/cpp/templates/_composite_type.j2 | 28 +- .../lang/cpp/templates/_definitions.j2 | 12 + .../lang/cpp/templates/deserialization.j2 | 146 +++ .../lang/cpp/templates/serialization.j2 | 237 ++++ verification/cpp/suite/magic_enum.hpp | 1023 +++++++++++++++++ verification/cpp/suite/test_bitarray.cpp | 240 +++- verification/cpp/suite/test_compiles.cpp | 2 +- verification/cpp/suite/test_helpers.hpp | 108 ++ verification/cpp/suite/test_serialization.cpp | 407 +++++++ 11 files changed, 2305 insertions(+), 25 deletions(-) create mode 100644 src/nunavut/lang/cpp/templates/_definitions.j2 create mode 100644 src/nunavut/lang/cpp/templates/deserialization.j2 create mode 100644 src/nunavut/lang/cpp/templates/serialization.j2 create mode 100644 verification/cpp/suite/magic_enum.hpp create mode 100644 verification/cpp/suite/test_helpers.hpp create mode 100644 verification/cpp/suite/test_serialization.cpp diff --git a/src/nunavut/lang/cpp/__init__.py b/src/nunavut/lang/cpp/__init__.py index 1807b0b8..bbbdfc08 100644 --- a/src/nunavut/lang/cpp/__init__.py +++ b/src/nunavut/lang/cpp/__init__.py @@ -9,6 +9,7 @@ """ import functools +import fractions import io import re import textwrap @@ -163,6 +164,19 @@ def filter_constant_value(language: Language, constant: pydsdl.Constant) -> str: return c_filter_literal(language, constant.value.native_value, constant.data_type, "static_cast<{type}>({value})") +@template_language_filter(__name__) +def filter_literal( + language: Language, + value: typing.Union[fractions.Fraction, bool, int], + ty: pydsdl.Any, + cast_format: str = "static_cast<{type}>({value})", +) -> str: + """ + Renders the specified value of the specified type as a literal. + """ + return c_filter_literal(language, value, ty, cast_format) + + def filter_to_standard_bit_length(t: pydsdl.PrimitiveType) -> int: """ Returns the nearest standard bit length of a type as an int. diff --git a/src/nunavut/lang/cpp/support/serialization.j2 b/src/nunavut/lang/cpp/support/serialization.j2 index 3089b856..e10599bb 100644 --- a/src/nunavut/lang/cpp/support/serialization.j2 +++ b/src/nunavut/lang/cpp/support/serialization.j2 @@ -140,6 +140,16 @@ public: return derived_bitspan(self.data_, self.offset_bits_ + bits); } + derived_bitspan subspan({{ typename_unsigned_bit_length }} bits_at=0) const noexcept { + auto& self = *static_cast(this); + const {{ typename_unsigned_bit_length }} offset_bits = self.offset_bits_ + bits_at; + const {{ typename_unsigned_length }} offset_bytes = offset_bits / 8U; + {{ assert('offset_bytes * 8U <= offset_bits') }} + const {{ typename_unsigned_length }} new_offset_bits = offset_bits - offset_bytes * 8U; + {{ assert('offset_bytes <= self.data_.size()') }} + return derived_bitspan({ self.data_.data() + offset_bytes, self.data_.size() - offset_bytes}, new_offset_bits); + } + void add_offset({{ typename_unsigned_bit_length }} bits) noexcept{ auto& self = *static_cast(this); self.offset_bits_ += bits; @@ -150,6 +160,19 @@ public: self.offset_bits_ = bits; } + {{ typename_unsigned_bit_length }} offset_misalignment({{ typename_unsigned_bit_length }} alignment_bits) const noexcept{ + auto& self = *static_cast(this); + return self.offset_bits_ % alignment_bits; + } + + bool offset_alings_to({{ typename_unsigned_bit_length }} alignment_bits) const noexcept{ + return offset_misalignment(alignment_bits) == 0U; + } + + bool offset_alings_to_byte() const noexcept{ + return offset_alings_to(8U); + } + {{ typename_unsigned_bit_length }} size() const noexcept{ auto& self = *static_cast(this); {{ typename_unsigned_bit_length }} bit_size = {# -#} @@ -160,27 +183,45 @@ public: return bit_size - self.offset_bits_; } - {{ typename_byte }}& aligned_ref({{ typename_unsigned_length }} plus_offset_bits=0U) noexcept { + {{ typename_unsigned_bit_length }} offset() const noexcept { + auto& self = *static_cast(this); + return self.offset_bits_; + } + + {{ typename_unsigned_bit_length }} offset_bytes() const noexcept { + auto& self = *static_cast(this); + const {{ typename_unsigned_length }} offset_bytes = (self.offset_bits_) / 8U; + return offset_bytes ; + } + + {{ typename_unsigned_bit_length }} offset_bytes_ceil() const noexcept { + auto& self = *static_cast(this); + const {{ typename_unsigned_length }} offset_bytes = ((self.offset_bits_ + 7U) / 8U); + return offset_bytes ; + } + + {{ typename_byte }}& aligned_ref({{ typename_unsigned_bit_length }} plus_offset_bits=0U) noexcept { auto& self = *static_cast(this); const {{ typename_unsigned_length }} offset_bytes = ((self.offset_bits_ + plus_offset_bits) / 8U); {{ assert('offset_bytes <= self.data_.size()') }} return self.data_[offset_bytes]; } - const {{ typename_byte }}& aligned_ref({{ typename_unsigned_length }} plus_offset_bits=0U) const noexcept { + const {{ typename_byte }}& aligned_ref({{ typename_unsigned_bit_length }} plus_offset_bits=0U) const noexcept { auto& self = *static_cast(this); const {{ typename_unsigned_length }} offset_bytes = ((self.offset_bits_ + plus_offset_bits) / 8U); {{ assert('offset_bytes <= self.data_.size()') }} return self.data_[offset_bytes]; } - {{ typename_byte }}* aligned_ptr({{ typename_unsigned_length }} plus_offset_bits=0U) noexcept { + {{ typename_byte }}* aligned_ptr({{ typename_unsigned_bit_length }} plus_offset_bits=0U) noexcept { return &aligned_ref(plus_offset_bits); } - const {{ typename_byte }}* aligned_ptr({{ typename_unsigned_length }} plus_offset_bits=0U) const noexcept { + const {{ typename_byte }}* aligned_ptr({{ typename_unsigned_bit_length }} plus_offset_bits=0U) const noexcept { return &aligned_ref(plus_offset_bits); } + }; } // namespace detail @@ -214,6 +255,11 @@ public: VoidResult setF32(const {{ typename_float_32 }} value); VoidResult setF64(const {{ typename_float_64 }} value); + + VoidResult setZeros() { return setZeros(size()); } + VoidResult setZeros({{ typename_unsigned_bit_length }} length); + + VoidResult padAndMoveToAlignment({{ typename_unsigned_bit_length }} length); }; struct const_bitspan: public detail::any_bitspan{ @@ -257,7 +303,10 @@ public: { {{ assert('length_mod < 8U') }} const uint8_t mask = static_cast((1U << length_mod) - 1U); - dst.data_[length_bytes] = (dst.data_[length_bytes] & static_cast<{{ typename_byte }}>(~mask)) | (data_[length_bytes] & mask); + //dst.data_[length_bytes] = (dst.data_[length_bytes] & static_cast<{{ typename_byte }}>(~mask)) | (data_[length_bytes] & mask); + dst.aligned_ref(length_bits) = {# -#} + (dst.aligned_ref(length_bits) & static_cast<{{ typename_byte }}>(~mask)) {# -#} + | (aligned_ref(length_bits) & mask); } } else @@ -292,7 +341,7 @@ public: const uint8_t in = static_cast(static_cast(data_[src_off / 8U] >> src_mod) << dst_mod) & 0xFFU; // NOSONAR // Intentional violation of MISRA: indexing on a pointer. // This simplifies the implementation greatly and avoids pointer arithmetics. - const uint8_t a = dst.data_[dst_off / 8U] & (static_cast(~mask)); // NOSONAR + const uint8_t a = dst.data_[dst_off / 8U] & (static_cast((~mask) & 0xFFU)); // NOSONAR const uint8_t b = in & mask; // Intentional violation of MISRA: indexing on a pointer. // This simplifies the implementation greatly and avoids pointer arithmetics. @@ -304,6 +353,12 @@ public: } } + template<{{ typename_unsigned_bit_length }} n_bits> + void align_offset_to(){ + static_assert((n_bits == 8) or (n_bits == 16) or (n_bits == 32) or (n_bits == 64), "Non-standard alignment!"); + offset_bits_ = (offset_bits_ + (n_bits - 1)) & ~(static_cast<{{ typename_unsigned_bit_length }}>(n_bits - 1)); + } + /// Calculate the number of bits to safely copy from/to a serialized buffer. /// Mind the units! By convention, buffer size is specified in bytes, but fragment length and offset are in bits. /// @@ -387,6 +442,39 @@ public: {{ typename_float_64 }} getF64(); }; +VoidResult bitspan::setZeros({{ typename_unsigned_bit_length }} length){ + if(length > size()){ + return -Error::SERIALIZATION_BUFFER_TOO_SMALL; + } + if(length == 0){ + return {}; + } + const {{ typename_unsigned_length }} offset_bytes = offset_bits_ / 8U; + const {{ typename_unsigned_bit_length }} offset_bits_mod = offset_bits_ % 8U; + const {{ typename_unsigned_bit_length }} length_bytes_ceil = (length + 7U) / 8U; + {{ assert('offset_bits_mod < 8U') }} + const auto first_byte_temp = data_[offset_bytes] & static_cast<{{ typename_byte }}>(0xFF >> (8U - offset_bits_mod)); + memset(&data_[offset_bytes], 0, length_bytes_ceil); + data_[offset_bytes] = static_cast<{{ typename_byte }}>(data_[offset_bytes] | first_byte_temp); + return {}; +} + +VoidResult bitspan::padAndMoveToAlignment({{ typename_unsigned_bit_length }} n_bits){ + const auto padding = static_cast(n_bits - offset_misalignment(n_bits)); + if (padding != n_bits) // Pad to n_bits bits. TODO: Eliminate redundant padding checks. + { + {{ assert('padding > 0') }} + auto ref_result = setZeros(padding); + if(not ref_result){ + return ref_result; + } + add_offset( padding); + {{ assert('offset_alings_to(n_bits)') }} + } + return {}; +} + + uint8_t const_bitspan::getU8(const uint8_t len_bits) const noexcept { {{ assert('data_.data() != nullptr') }} @@ -425,11 +513,12 @@ uint32_t const_bitspan::getU32(const uint8_t len_bits) const noexcept return val; {%- elif options.target_endianness in ('any', 'big') %} uint8_t tmp[sizeof(uint32_t)] = {0}; - copyTo(bitspan{ { &tmp[0], sizeof(tmp) } }, bits); - return static_cast(static_cast(tmp[0]) | - (static_cast(tmp[1]) << 8U) | - (static_cast(tmp[2]) << 16U) | - (static_cast(tmp[3]) << 24U)); + copyTo(bitspan{ { tmp, sizeof(tmp) } }, bits); + return static_cast( + (static_cast(tmp[0])) | + (static_cast(tmp[1]) << 8U) | + (static_cast(tmp[2]) << 16U) | + (static_cast(tmp[3]) << 24U)); {%- else %}{%- assert False %} {%- endif %} } @@ -445,7 +534,7 @@ uint64_t const_bitspan::getU64(const uint8_t len_bits) const noexcept return val; {%- elif options.target_endianness in ('any', 'big') %} uint8_t tmp[sizeof(uint64_t)] = {0}; - copyTo(bitspan{ { &tmp[0], sizeof(tmp) } }, bits); + copyTo(bitspan{ { tmp, sizeof(tmp) } }, bits); return static_cast(static_cast(tmp[0]) | (static_cast(tmp[1]) << 8U) | (static_cast(tmp[2]) << 16U) | diff --git a/src/nunavut/lang/cpp/templates/_composite_type.j2 b/src/nunavut/lang/cpp/templates/_composite_type.j2 index 0e2406cb..31cb694a 100644 --- a/src/nunavut/lang/cpp/templates/_composite_type.j2 +++ b/src/nunavut/lang/cpp/templates/_composite_type.j2 @@ -39,6 +39,22 @@ /// This type does not have a fixed port-ID. See https://forum.uavcan.org/t/choosing-message-and-service-ids/889 static constexpr bool HasFixedPortID = false; {% endif -%} + {%- assert composite_type.extent % 8 == 0 %} + {%- assert composite_type.inner_type.extent % 8 == 0 %} + /// Extent is the minimum amount of memory required to hold any serialized representation of any compatible + /// version of the data type; or, on other words, it is the the maximum possible size of received objects of this type. + /// The size is specified in bytes (rather than bits) because by definition, extent is an integer number of bytes long. + /// When allocating a deserialization (RX) buffer for this data type, it should be at least extent bytes large. + /// When allocating a serialization (TX) buffer, it is safe to use the size of the largest serialized representation + /// instead of the extent because it provides a tighter bound of the object size; it is safe because the concrete type + /// is always known during serialization (unlike deserialization). If not sure, use extent everywhere. + + static constexpr {{ typename_unsigned_length }} EXTENT_BYTES = {#- -#} + {{ composite_type.extent // 8 }}UL; + static constexpr {{ typename_unsigned_length }} SERIALIZATION_BUFFER_SIZE_BYTES = {#- -#} + {{ composite_type.inner_type.extent // 8 }}UL; + static_assert(EXTENT_BYTES >= SERIALIZATION_BUFFER_SIZE_BYTES, "Internal constraint violation"); + {%- for constant in composite_type.constants %} {% if loop.first %} // +---------------------------------------------------------------------------------------------------------------+ @@ -60,11 +76,17 @@ {%- if not nunavut.support.omit %} nunavut::support::SerializeResult - serialize(nunavut::support::span<{{ typename_byte }}> out_buffer) const + serialize(nunavut::support::bitspan out_buffer) const { - (void)out_buffer; {% from 'serialization.j2' import serialize -%} - {{ serialize(composite_type) | trim }} + {{ serialize(composite_type) | trim | remove_blank_lines | indent }} + } + + nunavut::support::SerializeResult + deserialize(nunavut::support::const_bitspan in_buffer) + { + {% from 'deserialization.j2' import deserialize -%} + {{ deserialize(composite_type) | trim | remove_blank_lines | indent }} } {%- endif %} }{{ composite_type | definition_end }} diff --git a/src/nunavut/lang/cpp/templates/_definitions.j2 b/src/nunavut/lang/cpp/templates/_definitions.j2 new file mode 100644 index 00000000..71ac4b01 --- /dev/null +++ b/src/nunavut/lang/cpp/templates/_definitions.j2 @@ -0,0 +1,12 @@ +{%- macro assert(expression) -%} + {%- if options.enable_serialization_asserts -%} + NUNAVUT_ASSERT({{ expression }}); + {%- endif -%} +{%- endmacro -%} + +{% if options.target_endianness == 'little' %} + {% set LITTLE_ENDIAN = True %} +{% elif options.target_endianness in ('any', 'big') %} + {% set LITTLE_ENDIAN = False %} +{% else %}{% assert False %} +{% endif %} diff --git a/src/nunavut/lang/cpp/templates/deserialization.j2 b/src/nunavut/lang/cpp/templates/deserialization.j2 new file mode 100644 index 00000000..1631e605 --- /dev/null +++ b/src/nunavut/lang/cpp/templates/deserialization.j2 @@ -0,0 +1,146 @@ +{#- + # Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + # Copyright (C) 2020 UAVCAN Development Team + # This software is distributed under the terms of the MIT License. + # Authors: David Lenfesty, Scott Dixon , Pavel Kirienko , + # Peter van der Perk +-#} + +{% from '_definitions.j2' import assert, LITTLE_ENDIAN %} + +{# ----------------------------------------------------------------------------------------------------------------- #} +{% macro deserialize(t) %} +{% if t.inner_type.bit_length_set.max > 0 %} + {{ _deserialize_impl(t) }} +{% else %} + (void)(in_buffer); + return 0; +{% endif %} +{% endmacro %} + + +{# ----------------------------------------------------------------------------------------------------------------- #} +{% macro _deserialize_impl(t) %} + const auto capacity_bits = in_buffer.size(); +{% if t.inner_type is StructureType %} + {% for f, offset in t.inner_type.iterate_fields_with_offsets() %} + {%- if loop.first %} + {%- assert f.data_type.alignment_requirement <= t.inner_type.alignment_requirement %} + {%- else %} + {{ _pad_to_alignment(f.data_type.alignment_requirement) }} + {%- endif %} + // {{ f }} + {{ _deserialize_any(f.data_type, (f|id), offset)|trim|remove_blank_lines }} + {% endfor %} +{% elif t.inner_type is UnionType %} + // Union tag field: {{ t.inner_type.tag_field_type }} + {#{{ _deserialize_integer(t.inner_type.tag_field_type, 'out_obj->_tag_', 0|bit_length_set)|trim|remove_blank_lines }} + {% for f, offset in t.inner_type.iterate_fields_with_offsets() %} + {{ 'if' if loop.first else 'else if' }} ({{ loop.index0 }}U == out_obj->_tag_) // {{ f }} + { + {%- assert f.data_type.alignment_requirement <= (offset.min) %} + {{ _deserialize_any(f.data_type, (f|id), offset)|trim|remove_blank_lines|indent }} + } + {%- endfor %} + else + { + return -nunavut::support::Error::REPRESENTATION_BAD_UNION_TAG; + }#} +{% else %}{% assert False %} +{% endif %} + {{ _pad_to_alignment(t.inner_type.alignment_requirement) }} + {{ assert('in_buffer.offset() % 8U == 0U') }} + auto _bits_got_ = std::min<{{ typename_unsigned_bit_length }}>(in_buffer.offset(), capacity_bits); + {{ assert('capacity_bits >= _bits_got_') }} + return { static_cast<{{ typename_unsigned_length }}>(_bits_got_ / 8U) }; +{% endmacro %} + + +{# ----------------------------------------------------------------------------------------------------------------- #} +{% macro _pad_to_alignment(n_bits) %} +{%- if n_bits > 1 -%} + {%- assert n_bits in (8, 16, 32, 64) -%} + in_buffer.align_offset_to<{{ n_bits }}U>(); +{%- endif -%} +{% endmacro %} + + +{# ----------------------------------------------------------------------------------------------------------------- #} +{% macro _deserialize_any(t, reference, offset) %} +{% if t.alignment_requirement > 1 %} + {{ assert('in_buffer.offset_alings_to(%dU)'|format(t.alignment_requirement)) }} +{% endif %} +{% if offset.is_aligned_at_byte() %} + {{ assert('in_buffer.offset_alings_to_byte()') }} +{% endif %} +{% if t is VoidType %} {{- _deserialize_void (t, offset) }} +{% elif t is BooleanType %} {{- _deserialize_boolean (t, reference, offset) }} +{% elif t is IntegerType %} {{- _deserialize_integer (t, reference, offset) }} +{% elif t is FloatType %} {{- _deserialize_float (t, reference, offset) }} +{% elif t is FixedLengthArrayType %} {{- _deserialize_fixed_length_array (t, reference, offset) }} +{% elif t is VariableLengthArrayType %} {{- _deserialize_variable_length_array(t, reference, offset) }} +{% elif t is CompositeType %} {{- _deserialize_composite (t, reference, offset) }} +{% else %}{% assert False %} +{% endif %} +{% endmacro %} + + +{# ----------------------------------------------------------------------------------------------------------------- #} +{% macro _deserialize_void(t, offset) %} + in_buffer.add_offset({{ t.bit_length }}); +{% endmacro %} + + +{# ----------------------------------------------------------------------------------------------------------------- #} +{% macro _deserialize_boolean(t, reference, offset) %} + {{ reference }} = in_buffer.getBit(); + in_buffer.add_offset(1U); +{% endmacro %} + + +{# ----------------------------------------------------------------------------------------------------------------- #} +{% macro _deserialize_integer(t, reference, offset) %} +{% set getter = 'get%s%d'|format('U' if t is UnsignedIntegerType else 'I', t|to_standard_bit_length) %} +{# Mem-copy optimization is difficult to perform on non-standard-size signed integers because the C standard does + # not define a portable way of unsigned-to-signed conversion (but the other way around is well-defined). + # See 6.3.1.8 Usual arithmetic conversions, 6.3.1.3 Signed and unsigned integers. + # This template can be greatly expanded with additional special cases if needed. + #} +{#{% if offset.is_aligned_at_byte() and t is UnsignedIntegerType and t.bit_length <= 8 %} + if ((offset_bits + {{ t.bit_length }}U) <= capacity_bits) + { + {{ reference }} = buffer[offset_bits / 8U] & {{ 2 ** t.bit_length - 1 }}U; + } + else + { + {{ reference }} = 0U; + } +{% else %}}#} + {{ reference }} = in_buffer.{{ getter }}({{ t.bit_length }}U); +{#{% endif %}#} + in_buffer.add_offset({{ t.bit_length }}U); +{% endmacro %} + + +{# ----------------------------------------------------------------------------------------------------------------- #} +{% macro _deserialize_float(t, reference, offset) %} + {# TODO: apply special case optimizations for aligned data and little-endian IEEE754-conformant platforms. #} + {{ reference }} = in_buffer.getF{{ t.bit_length }}(); + in_buffer.add_offset({{ t.bit_length }}U); +{% endmacro %} + + +{# ----------------------------------------------------------------------------------------------------------------- #} +{% macro _deserialize_fixed_length_array(t, reference, offset) %} +{% endmacro %} + + +{# ----------------------------------------------------------------------------------------------------------------- #} +{% macro _deserialize_variable_length_array(t, reference, offset) %} +{% endmacro %} + + +{# ----------------------------------------------------------------------------------------------------------------- #} +{% macro _deserialize_composite(t, reference, offset) %} + +{% endmacro %} diff --git a/src/nunavut/lang/cpp/templates/serialization.j2 b/src/nunavut/lang/cpp/templates/serialization.j2 new file mode 100644 index 00000000..529a1cfb --- /dev/null +++ b/src/nunavut/lang/cpp/templates/serialization.j2 @@ -0,0 +1,237 @@ +{#- + # Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + # Copyright (C) 2022 UAVCAN Development Team + # This software is distributed under the terms of the MIT License. + # Authors: David Lenfesty, Scott Dixon , Pavel Kirienko , + # Peter van der Perk , Pavel Pletenev +-#} + +{% from '_definitions.j2' import assert, LITTLE_ENDIAN %} + +{# ----------------------------------------------------------------------------------------------------------------- #} +{% macro serialize(t) %} +{% if t.inner_type.bit_length_set.max > 0 %} + {{ _serialize_impl(t) }} +{% else %} + (void)(out_buffer); + return 0U; +{% endif %} +{% endmacro %} + + +{# ----------------------------------------------------------------------------------------------------------------- #} +{% macro _serialize_impl(t) %} + + const {{ typename_unsigned_length }} capacity_bits = out_buffer.size(); + +{%- if options.enable_override_variable_array_capacity %} +#ifndef {{ t | full_reference_name }}_DISABLE_SERIALIZATION_BUFFER_CHECK_ +{% endif %} + if ((static_cast<{{ typename_unsigned_bit_length }}>(capacity_bits)) < {{ t.inner_type.bit_length_set.max }}UL) + { + return -nunavut::support::Error::SERIALIZATION_BUFFER_TOO_SMALL; + } +{%- if options.enable_override_variable_array_capacity %} +#endif +{% endif %} + + // Notice that fields that are not an integer number of bytes long may overrun the space allocated for them + // in the serialization buffer up to the next byte boundary. This is by design and is guaranteed to be safe. + {{ assert('out_buffer.offset_alings_to_byte()') }} +{% if t.inner_type is StructureType %} + {%- for f, offset in t.inner_type.iterate_fields_with_offsets() %} + {%- if loop.first %} + {%- assert f.data_type.alignment_requirement <= t.inner_type.alignment_requirement %} + {%- else %} + {{ _pad_to_alignment(f.data_type.alignment_requirement)|trim|remove_blank_lines }} + {%- endif %} + { // {{ f }} + {{ _serialize_any(f.data_type, (f|id), offset)|trim|remove_blank_lines|indent }} + } + {%- endfor %} +{% elif t.inner_type is UnionType %} +{# + { // Union tag field: {{ t.inner_type.tag_field_type }} + {{ + _serialize_integer(t.inner_type.tag_field_type, 'obj->_tag_', 0|bit_length_set) + |trim|remove_blank_lines|indent + }} + } + {% for f, offset in t.inner_type.iterate_fields_with_offsets() %} + {{ 'if' if loop.first else 'else if' }} ({{ loop.index0 }}U == obj->_tag_) // {{ f }} + { + {%- assert f.data_type.alignment_requirement <= (offset.min) %} + {{ _serialize_any(f.data_type, (f|id), offset)|trim|remove_blank_lines|indent }} + } + {%- endfor %} + else +#} + { + return -nunavut::support::Error::REPRESENTATION_BAD_UNION_TAG; + } +{% else %}{% assert False %} +{% endif %} + + {{ _pad_to_alignment(t.inner_type.alignment_requirement)|trim|remove_blank_lines }} + // It is assumed that we know the exact type of the serialized entity, hence we expect the size to match. +{% if not t.inner_type.bit_length_set.fixed_length %} + {{ assert('out_buffer.offset() >= %sULL'|format(t.inner_type.bit_length_set.min)) }} + {{ assert('out_buffer.offset() <= %sULL'|format(t.inner_type.bit_length_set.max)) }} +{% else %} + {{ assert('out_buffer.offset() == %sULL'|format(t.inner_type.bit_length_set.max)) }} +{% endif %} + {{ assert('out_buffer.offset_alings_to_byte()') }} + return out_buffer.offset_bytes_ceil(); + +{% endmacro %} + + +{# ----------------------------------------------------------------------------------------------------------------- #} +{% macro _pad_to_alignment(n_bits) %} +{% if n_bits > 1 %} + { + {% set ref_result = 'result'|to_template_unique_name %} + const auto {{ref_result}} = out_buffer.padAndMoveToAlignment({{ n_bits }}U); + if(not {{ref_result}}){ + return -{{ref_result}}.error(); + } + } +{% endif %} +{% endmacro %} + + +{# ----------------------------------------------------------------------------------------------------------------- #} +{% macro _serialize_any(t, reference, offset) %} +{% if t.alignment_requirement > 1 %} + {{ assert('out_buffer.offset_alings_to(%dU)'|format(t.alignment_requirement)) }} +{% endif %} +{% if offset.is_aligned_at_byte() %} + {{ assert('out_buffer.offset_alings_to_byte()') }} +{% endif %} + {# NOTICE: If this is a delimited type, we will be requiring the buffer to be at least extent-sized. + # This is a bit wasteful because when serializing we can often use a smaller buffer. #} + {% if t.bit_length_set.max > 0 %} + {{ assert('%dULL <= out_buffer.size()'|format(t.bit_length_set.max)) }} + {% endif %} + +{% if t is VoidType %} {{- _serialize_void(t, offset) }} +{% elif t is BooleanType %} {{- _serialize_boolean(t, reference, offset) }} +{% elif t is IntegerType %} {{- _serialize_integer(t, reference, offset) }} +{% elif t is FloatType %} {{- _serialize_float(t, reference, offset) }} +{% elif t is FixedLengthArrayType %} {{- _serialize_fixed_length_array(t, reference, offset) }} +{% elif t is VariableLengthArrayType %} {{- _serialize_variable_length_array(t, reference, offset) }} +{% elif t is CompositeType %} {{- _serialize_composite(t, reference, offset) }} +{% else %}{#{% assert False %}#} +{% endif %} +{% endmacro %} + + +{# ----------------------------------------------------------------------------------------------------------------- #} +{% macro _serialize_void(t, offset) %} + {% set ref_result = 'result'|to_template_unique_name %} + auto {{ ref_result }} = out_buffer.setZeros({{ t.bit_length }}UL); + if(not {{ ref_result }}){ + return -{{ ref_result }}.error(); + } + out_buffer.add_offset({{ t.bit_length }}UL); +{% endmacro %} + + +{# ----------------------------------------------------------------------------------------------------------------- #} +{% macro _serialize_boolean(t, reference, offset) %} + {% set ref_result = 'result'|to_template_unique_name %} + auto {{ ref_result }} = out_buffer.setBit({{ reference }}); + if(not {{ ref_result }}){ + return -{{ ref_result }}.error(); + } + out_buffer.add_offset(1UL); +{% endmacro %} + + +{# ----------------------------------------------------------------------------------------------------------------- #} +{% macro _serialize_integer(t, reference, offset) %} +{% if t is saturated %} + {% if not t.standard_bit_length %} + {% set ref_value = 'sat'|to_template_unique_name %} + {{ t|type_from_primitive }} {{ ref_value }} = {{ reference }}; + {% if t is UnsignedIntegerType %} + {% assert t.inclusive_value_range[0] == 0 %} + {% else %} + if ({{ ref_value }} < {{ t.inclusive_value_range[0]|literal(t) }}) + { + {{ ref_value }} = {{ t.inclusive_value_range[0]|literal(t) }}; + } + {% endif %} + if ({{ ref_value }} > {{ t.inclusive_value_range[1]|literal(t) }}) + { + {{ ref_value }} = {{ t.inclusive_value_range[1]|literal(t) }}; + } + {% else %} + {% set ref_value = reference %} + // Saturation code not emitted -- native representation matches the serialized representation. + {% endif %} +{% else %} + {% set ref_value = reference %} +{% endif %} + {% set ref_result = 'result'|to_template_unique_name %} + const auto {{ref_result}} = out_buffer.set{{ 'U' if t is UnsignedIntegerType else 'I' }}xx({#- -#} + {{ ref_value }}, {{ t.bit_length }}U); + if(not {{ref_result}}){ + return -{{ref_result}}.error(); + } + out_buffer.add_offset({{ t.bit_length }}U); +{% endmacro %} + + +{# ----------------------------------------------------------------------------------------------------------------- #} +{% macro _serialize_float(t, reference, offset) %} +{% if t is saturated %} + {% if t.bit_length not in (32, 64) %} + {% set ref_value = 'sat'|to_template_unique_name %} + {{ t|type_from_primitive }} {{ ref_value }} = {{ reference }}; + if (std::isfinite({{ ref_value }})) + { + if ({{ ref_value }} < {{ t.inclusive_value_range[0]|literal(t) }}) + { + {{ ref_value }} = {{ t.inclusive_value_range[0]|literal(t) }}; + } + if ({{ ref_value }} > {{ t.inclusive_value_range[1]|literal(t) }}) + { + {{ ref_value }} = {{ t.inclusive_value_range[1]|literal(t) }}; + } + } + {% elif t.bit_length == 32 %} + {% set ref_value = reference %} + // Saturation code not emitted -- assume the native representation of float32 is conformant. + static_assert(NUNAVUT_PLATFORM_IEEE754_FLOAT, "Native IEEE754 binary32 required. TODO: relax constraint"); + {% elif t.bit_length == 64 %} + {% set ref_value = reference %} + // Saturation code not emitted -- assume the native representation of float64 is conformant. + static_assert(NUNAVUT_PLATFORM_IEEE754_DOUBLE, "Native IEEE754 binary64 required. TODO: relax constraint"); + {% else %}{% assert False %} + {% endif %} +{% else %} + {% set ref_value = reference %} +{% endif %} + {% set ref_result = 'result'|to_template_unique_name %} + auto {{ ref_result }} = out_buffer.setF{{ t.bit_length }}({{ ref_value }}); + if(not {{ ref_result }}){ + return -{{ ref_result }}.error(); + } + out_buffer.add_offset({{ t.bit_length }}U); +{% endmacro %} + + +{# ----------------------------------------------------------------------------------------------------------------- #} +{% macro _serialize_fixed_length_array(t, reference, offset) %} +{% endmacro %} + + +{# ----------------------------------------------------------------------------------------------------------------- #} +{% macro _serialize_variable_length_array(t, reference, offset) %} +{% endmacro %} + + +{# ----------------------------------------------------------------------------------------------------------------- #} +{% macro _serialize_composite(t, reference, offset) %} +{% endmacro %} diff --git a/verification/cpp/suite/magic_enum.hpp b/verification/cpp/suite/magic_enum.hpp new file mode 100644 index 00000000..5b916be0 --- /dev/null +++ b/verification/cpp/suite/magic_enum.hpp @@ -0,0 +1,1023 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.7.3 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2021 Daniil Goncharov . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef NEARGYE_MAGIC_ENUM_HPP +#define NEARGYE_MAGIC_ENUM_HPP + +#define MAGIC_ENUM_VERSION_MAJOR 0 +#define MAGIC_ENUM_VERSION_MINOR 7 +#define MAGIC_ENUM_VERSION_PATCH 3 + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(MAGIC_ENUM_CONFIG_FILE) +#include MAGIC_ENUM_CONFIG_FILE +#endif + +#if !defined(MAGIC_ENUM_USING_ALIAS_OPTIONAL) +#include +#endif +#if !defined(MAGIC_ENUM_USING_ALIAS_STRING) +#include +#endif +#if !defined(MAGIC_ENUM_USING_ALIAS_STRING_VIEW) +#include +#endif + +#if defined(__clang__) +# pragma clang diagnostic push +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wmaybe-uninitialized" // May be used uninitialized 'return {};'. +#elif defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable : 26495) // Variable 'static_string::chars_' is uninitialized. +# pragma warning(disable : 28020) // Arithmetic overflow: Using operator '-' on a 4 byte value and then casting the result to a 8 byte value. +# pragma warning(disable : 26451) // The expression '0<=_Param_(1)&&_Param_(1)<=1-1' is not true at this call. +#endif + +// Checks magic_enum compiler compatibility. +#if defined(__clang__) && __clang_major__ >= 5 || defined(__GNUC__) && __GNUC__ >= 9 || defined(_MSC_VER) && _MSC_VER >= 1910 +# undef MAGIC_ENUM_SUPPORTED +# define MAGIC_ENUM_SUPPORTED 1 +#endif + +// Checks magic_enum compiler aliases compatibility. +#if defined(__clang__) && __clang_major__ >= 5 || defined(__GNUC__) && __GNUC__ >= 9 || defined(_MSC_VER) && _MSC_VER >= 1920 +# undef MAGIC_ENUM_SUPPORTED_ALIASES +# define MAGIC_ENUM_SUPPORTED_ALIASES 1 +#endif + +// Enum value must be greater or equals than MAGIC_ENUM_RANGE_MIN. By default MAGIC_ENUM_RANGE_MIN = -128. +// If need another min range for all enum types by default, redefine the macro MAGIC_ENUM_RANGE_MIN. +#if !defined(MAGIC_ENUM_RANGE_MIN) +# define MAGIC_ENUM_RANGE_MIN -128 +#endif + +// Enum value must be less or equals than MAGIC_ENUM_RANGE_MAX. By default MAGIC_ENUM_RANGE_MAX = 128. +// If need another max range for all enum types by default, redefine the macro MAGIC_ENUM_RANGE_MAX. +#if !defined(MAGIC_ENUM_RANGE_MAX) +# define MAGIC_ENUM_RANGE_MAX 128 +#endif + +namespace magic_enum { + +// If need another optional type, define the macro MAGIC_ENUM_USING_ALIAS_OPTIONAL. +#if defined(MAGIC_ENUM_USING_ALIAS_OPTIONAL) +MAGIC_ENUM_USING_ALIAS_OPTIONAL +#else +using std::optional; +#endif + +// If need another string_view type, define the macro MAGIC_ENUM_USING_ALIAS_STRING_VIEW. +#if defined(MAGIC_ENUM_USING_ALIAS_STRING_VIEW) +MAGIC_ENUM_USING_ALIAS_STRING_VIEW +#else +using std::string_view; +#endif + +// If need another string type, define the macro MAGIC_ENUM_USING_ALIAS_STRING. +#if defined(MAGIC_ENUM_USING_ALIAS_STRING) +MAGIC_ENUM_USING_ALIAS_STRING +#else +using std::string; +#endif + +namespace customize { + +// Enum value must be in range [MAGIC_ENUM_RANGE_MIN, MAGIC_ENUM_RANGE_MAX]. By default MAGIC_ENUM_RANGE_MIN = -128, MAGIC_ENUM_RANGE_MAX = 128. +// If need another range for all enum types by default, redefine the macro MAGIC_ENUM_RANGE_MIN and MAGIC_ENUM_RANGE_MAX. +// If need another range for specific enum type, add specialization enum_range for necessary enum type. +template +struct enum_range { + static_assert(std::is_enum_v, "magic_enum::customize::enum_range requires enum type."); + static constexpr int min = MAGIC_ENUM_RANGE_MIN; + static constexpr int max = MAGIC_ENUM_RANGE_MAX; + static_assert(max > min, "magic_enum::customize::enum_range requires max > min."); +}; + +static_assert(MAGIC_ENUM_RANGE_MAX > MAGIC_ENUM_RANGE_MIN, "MAGIC_ENUM_RANGE_MAX must be greater than MAGIC_ENUM_RANGE_MIN."); +static_assert((MAGIC_ENUM_RANGE_MAX - MAGIC_ENUM_RANGE_MIN) < (std::numeric_limits::max)(), "MAGIC_ENUM_RANGE must be less than UINT16_MAX."); + +// If need custom names for enum, add specialization enum_name for necessary enum type. +template +constexpr string_view enum_name(E) noexcept { + static_assert(std::is_enum_v, "magic_enum::customize::enum_name requires enum type."); + + return {}; +} + +} // namespace magic_enum::customize + +namespace detail { + +template +struct supported +#if defined(MAGIC_ENUM_SUPPORTED) && MAGIC_ENUM_SUPPORTED || defined(MAGIC_ENUM_NO_CHECK_SUPPORT) + : std::true_type {}; +#else + : std::false_type {}; +#endif + +template +struct has_is_flags : std::false_type {}; + +template +struct has_is_flags::is_flags)>> : std::bool_constant::is_flags)>>> {}; + +template +struct range_min : std::integral_constant {}; + +template +struct range_min::min)>> : std::integral_constant::min), customize::enum_range::min> {}; + +template +struct range_max : std::integral_constant {}; + +template +struct range_max::max)>> : std::integral_constant::max), customize::enum_range::max> {}; + +template +class static_string { + public: + constexpr explicit static_string(string_view str) noexcept : static_string{str, std::make_index_sequence{}} { + assert(str.size() == N); + } + + constexpr const char* data() const noexcept { return chars_; } + + constexpr std::size_t size() const noexcept { return N; } + + constexpr operator string_view() const noexcept { return {data(), size()}; } + + private: + template + constexpr static_string(string_view str, std::index_sequence) noexcept : chars_{str[I]..., '\0'} {} + + char chars_[N + 1]; +}; + +template <> +class static_string<0> { + public: + constexpr explicit static_string(string_view) noexcept {} + + constexpr const char* data() const noexcept { return nullptr; } + + constexpr std::size_t size() const noexcept { return 0; } + + constexpr operator string_view() const noexcept { return {}; } +}; + +constexpr string_view pretty_name(string_view name) noexcept { + for (std::size_t i = name.size(); i > 0; --i) { + if (!((name[i - 1] >= '0' && name[i - 1] <= '9') || + (name[i - 1] >= 'a' && name[i - 1] <= 'z') || + (name[i - 1] >= 'A' && name[i - 1] <= 'Z') || +#if defined(MAGIC_ENUM_ENABLE_NONASCII) + (name[i - 1] & 0x80) || +#endif + (name[i - 1] == '_'))) { + name.remove_prefix(i); + break; + } + } + + if (name.size() > 0 && ((name.front() >= 'a' && name.front() <= 'z') || + (name.front() >= 'A' && name.front() <= 'Z') || +#if defined(MAGIC_ENUM_ENABLE_NONASCII) + (name.front() & 0x80) || +#endif + (name.front() == '_'))) { + return name; + } + + return {}; // Invalid name. +} + +template +constexpr auto to_lower([[maybe_unused]] CharType ch) noexcept -> std::enable_if_t, char>, char> { +#if defined(MAGIC_ENUM_ENABLE_NONASCII) + static_assert(!std::is_same_v, "magic_enum::detail::to_lower not supported Non-ASCII feature."); + return {}; +#else + return 'A' <= ch && ch <= 'Z' ? ch - 'A' + 'a' : ch; +#endif +} + +constexpr std::size_t find(string_view str, char c) noexcept { +#if defined(__clang__) && __clang_major__ < 9 && defined(__GLIBCXX__) || defined(_MSC_VER) && _MSC_VER < 1920 && !defined(__clang__) +// https://stackoverflow.com/questions/56484834/constexpr-stdstring-viewfind-last-of-doesnt-work-on-clang-8-with-libstdc +// https://developercommunity.visualstudio.com/content/problem/360432/vs20178-regression-c-failed-in-test.html + constexpr bool workaround = true; +#else + constexpr bool workaround = false; +#endif + + if constexpr (workaround) { + for (std::size_t i = 0; i < str.size(); ++i) { + if (str[i] == c) { + return i; + } + } + + return string_view::npos; + } else { + return str.find_first_of(c); + } +} + +template +constexpr std::array, N> to_array(T (&a)[N], std::index_sequence) { + return {{a[I]...}}; +} + +template +constexpr bool cmp_equal(string_view lhs, string_view rhs, [[maybe_unused]] BinaryPredicate&& p) noexcept(std::is_nothrow_invocable_r_v) { +#if defined(_MSC_VER) && _MSC_VER < 1920 && !defined(__clang__) + // https://developercommunity.visualstudio.com/content/problem/360432/vs20178-regression-c-failed-in-test.html + // https://developercommunity.visualstudio.com/content/problem/232218/c-constexpr-string-view.html + constexpr bool workaround = true; +#else + constexpr bool workaround = false; +#endif + constexpr bool custom_predicate = + !std::is_same_v, std::equal_to> && + !std::is_same_v, std::equal_to<>>; + + if constexpr (custom_predicate || workaround) { + if (lhs.size() != rhs.size()) { + return false; + } + + const auto size = lhs.size(); + for (std::size_t i = 0; i < size; ++i) { + if (!p(lhs[i], rhs[i])) { + return false; + } + } + + return true; + } else { + return lhs == rhs; + } +} + +template +constexpr bool cmp_less(L lhs, R rhs) noexcept { + static_assert(std::is_integral_v && std::is_integral_v, "magic_enum::detail::cmp_less requires integral type."); + + if constexpr (std::is_signed_v == std::is_signed_v) { + // If same signedness (both signed or both unsigned). + return lhs < rhs; + } else if constexpr (std::is_same_v) { // bool special case + return static_cast(lhs) < rhs; + } else if constexpr (std::is_same_v) { // bool special case + return lhs < static_cast(rhs); + } else if constexpr (std::is_signed_v) { + // If 'right' is negative, then result is 'false', otherwise cast & compare. + return rhs > 0 && lhs < static_cast>(rhs); + } else { + // If 'left' is negative, then result is 'true', otherwise cast & compare. + return lhs < 0 || static_cast>(lhs) < rhs; + } +} + +template +constexpr I log2(I value) noexcept { + static_assert(std::is_integral_v, "magic_enum::detail::log2 requires integral type."); + + auto ret = I{0}; + for (; value > I{1}; value >>= I{1}, ++ret) {} + + return ret; +} + +template +inline constexpr bool is_enum_v = std::is_enum_v && std::is_same_v>; + +template +constexpr auto n() noexcept { + static_assert(is_enum_v, "magic_enum::detail::n requires enum type."); + + if constexpr (supported::value) { +#if defined(__clang__) || defined(__GNUC__) + constexpr auto name = pretty_name({__PRETTY_FUNCTION__, sizeof(__PRETTY_FUNCTION__) - 2}); +#elif defined(_MSC_VER) + constexpr auto name = pretty_name({__FUNCSIG__, sizeof(__FUNCSIG__) - 17}); +#else + constexpr auto name = string_view{}; +#endif + return static_string{name}; + } else { + return string_view{}; // Unsupported compiler. + } +} + +template +inline constexpr auto type_name_v = n(); + +template +constexpr auto n() noexcept { + static_assert(is_enum_v, "magic_enum::detail::n requires enum type."); + [[maybe_unused]] constexpr auto custom_name = customize::enum_name(V); + + if constexpr (custom_name.empty()) { + if constexpr (supported::value) { +#if defined(__clang__) || defined(__GNUC__) + constexpr auto name = pretty_name({__PRETTY_FUNCTION__, sizeof(__PRETTY_FUNCTION__) - 2}); +#elif defined(_MSC_VER) + constexpr auto name = pretty_name({__FUNCSIG__, sizeof(__FUNCSIG__) - 17}); +#else + constexpr auto name = string_view{}; +#endif + return static_string{name}; + } else { + return string_view{}; // Unsupported compiler. + } + } else { + return static_string{custom_name}; + } +} + +template +inline constexpr auto enum_name_v = n(); + +template +constexpr bool is_valid() noexcept { + static_assert(is_enum_v, "magic_enum::detail::is_valid requires enum type."); + + return n(V)>().size() != 0; +} + +template > +constexpr E value(std::size_t i) noexcept { + static_assert(is_enum_v, "magic_enum::detail::value requires enum type."); + + if constexpr (std::is_same_v) { // bool special case + static_assert(O == 0, "magic_enum::detail::value requires valid offset."); + + return static_cast(i); + } else if constexpr (IsFlags) { + return static_cast(U{1} << static_cast(static_cast(i) + O)); + } else { + return static_cast(static_cast(i) + O); + } +} + +template > +constexpr int reflected_min() noexcept { + static_assert(is_enum_v, "magic_enum::detail::reflected_min requires enum type."); + + if constexpr (IsFlags) { + return 0; + } else { + constexpr auto lhs = range_min::value; + constexpr auto rhs = (std::numeric_limits::min)(); + + if constexpr (cmp_less(rhs, lhs)) { + return lhs; + } else { + return rhs; + } + } +} + +template > +constexpr int reflected_max() noexcept { + static_assert(is_enum_v, "magic_enum::detail::reflected_max requires enum type."); + + if constexpr (IsFlags) { + return std::numeric_limits::digits - 1; + } else { + constexpr auto lhs = range_max::value; + constexpr auto rhs = (std::numeric_limits::max)(); + + if constexpr (cmp_less(lhs, rhs)) { + return lhs; + } else { + return rhs; + } + } +} + +template +inline constexpr auto reflected_min_v = reflected_min(); + +template +inline constexpr auto reflected_max_v = reflected_max(); + +template +constexpr std::size_t values_count(const bool (&valid)[N]) noexcept { + auto count = std::size_t{0}; + for (std::size_t i = 0; i < N; ++i) { + if (valid[i]) { + ++count; + } + } + + return count; +} + +template +constexpr auto values(std::index_sequence) noexcept { + static_assert(is_enum_v, "magic_enum::detail::values requires enum type."); + constexpr bool valid[sizeof...(I)] = {is_valid(I)>()...}; + constexpr std::size_t count = values_count(valid); + + if constexpr (count > 0) { + E values[count] = {}; + for (std::size_t i = 0, v = 0; v < count; ++i) { + if (valid[i]) { + values[v++] = value(i); + } + } + + return to_array(values, std::make_index_sequence{}); + } else { + return std::array{}; + } +} + +template > +constexpr auto values() noexcept { + static_assert(is_enum_v, "magic_enum::detail::values requires enum type."); + constexpr auto min = reflected_min_v; + constexpr auto max = reflected_max_v; + constexpr auto range_size = max - min + 1; + static_assert(range_size > 0, "magic_enum::enum_range requires valid size."); + static_assert(range_size < (std::numeric_limits::max)(), "magic_enum::enum_range requires valid size."); + + return values>(std::make_index_sequence{}); +} + +template > +constexpr bool is_flags_enum() noexcept { + static_assert(is_enum_v, "magic_enum::detail::is_flags_enum requires enum type."); + + if constexpr (has_is_flags::value) { + return customize::enum_range::is_flags; + } else if constexpr (std::is_same_v) { // bool special case + return false; + } else { +#if defined(MAGIC_ENUM_NO_CHECK_FLAGS) + return false; +#else + constexpr auto flags_values = values(); + constexpr auto default_values = values(); + if (flags_values.size() == 0 || default_values.size() > flags_values.size()) { + return false; + } + for (std::size_t i = 0; i < default_values.size(); ++i) { + const auto v = static_cast(default_values[i]); + if (v != 0 && (v & (v - 1)) != 0) { + return false; + } + } + return flags_values.size() > 0; +#endif + } +} + +template +inline constexpr bool is_flags_v = is_flags_enum(); + +template +inline constexpr std::array values_v = values>(); + +template > +using values_t = decltype((values_v)); + +template +inline constexpr auto count_v = values_v.size(); + +template > +inline constexpr auto min_v = (count_v > 0) ? static_cast(values_v.front()) : U{0}; + +template > +inline constexpr auto max_v = (count_v > 0) ? static_cast(values_v.back()) : U{0}; + +template +constexpr auto names(std::index_sequence) noexcept { + static_assert(is_enum_v, "magic_enum::detail::names requires enum type."); + + return std::array{{enum_name_v[I]>...}}; +} + +template +inline constexpr std::array names_v = names(std::make_index_sequence>{}); + +template > +using names_t = decltype((names_v)); + +template +constexpr auto entries(std::index_sequence) noexcept { + static_assert(is_enum_v, "magic_enum::detail::entries requires enum type."); + + return std::array, sizeof...(I)>{{{values_v[I], enum_name_v[I]>}...}}; +} + +template +inline constexpr std::array entries_v = entries(std::make_index_sequence>{}); + +template > +using entries_t = decltype((entries_v)); + +template > +constexpr bool is_sparse() noexcept { + static_assert(is_enum_v, "magic_enum::detail::is_sparse requires enum type."); + constexpr auto max = is_flags_v ? log2(max_v) : max_v; + constexpr auto min = is_flags_v ? log2(min_v) : min_v; + constexpr auto range_size = max - min + 1; + + return range_size != count_v; +} + +template +inline constexpr bool is_sparse_v = is_sparse(); + +template > +constexpr U values_ors() noexcept { + static_assert(is_enum_v, "magic_enum::detail::values_ors requires enum type."); + + auto ors = U{0}; + for (std::size_t i = 0; i < count_v; ++i) { + ors |= static_cast(values_v[i]); + } + + return ors; +} + +template +struct enable_if_enum {}; + +template +struct enable_if_enum { + using type = R; + using D = std::decay_t; + static_assert(supported::value, "magic_enum unsupported compiler (https://github.com/Neargye/magic_enum#compiler-compatibility)."); +}; + +template +using enable_if_enum_t = std::enable_if_t>, R>; + +template >>> +using enum_concept = T; + +template > +struct is_scoped_enum : std::false_type {}; + +template +struct is_scoped_enum : std::bool_constant>> {}; + +template > +struct is_unscoped_enum : std::false_type {}; + +template +struct is_unscoped_enum : std::bool_constant>> {}; + +template >> +struct underlying_type {}; + +template +struct underlying_type : std::underlying_type> {}; + +} // namespace magic_enum::detail + +// Checks is magic_enum supported compiler. +inline constexpr bool is_magic_enum_supported = detail::supported::value; + +template +using Enum = detail::enum_concept; + +// Checks whether T is an Unscoped enumeration type. +// Provides the member constant value which is equal to true, if T is an [Unscoped enumeration](https://en.cppreference.com/w/cpp/language/enum#Unscoped_enumeration) type. Otherwise, value is equal to false. +template +struct is_unscoped_enum : detail::is_unscoped_enum {}; + +template +inline constexpr bool is_unscoped_enum_v = is_unscoped_enum::value; + +// Checks whether T is an Scoped enumeration type. +// Provides the member constant value which is equal to true, if T is an [Scoped enumeration](https://en.cppreference.com/w/cpp/language/enum#Scoped_enumerations) type. Otherwise, value is equal to false. +template +struct is_scoped_enum : detail::is_scoped_enum {}; + +template +inline constexpr bool is_scoped_enum_v = is_scoped_enum::value; + +// If T is a complete enumeration type, provides a member typedef type that names the underlying type of T. +// Otherwise, if T is not an enumeration type, there is no member type. Otherwise (T is an incomplete enumeration type), the program is ill-formed. +template +struct underlying_type : detail::underlying_type {}; + +template +using underlying_type_t = typename underlying_type::type; + +// Returns type name of enum. +template +[[nodiscard]] constexpr auto enum_type_name() noexcept -> detail::enable_if_enum_t { + constexpr string_view name = detail::type_name_v>; + static_assert(name.size() > 0, "Enum type does not have a name."); + + return name; +} + +// Returns number of enum values. +template +[[nodiscard]] constexpr auto enum_count() noexcept -> detail::enable_if_enum_t { + return detail::count_v>; +} + +// Returns enum value at specified index. +// No bounds checking is performed: the behavior is undefined if index >= number of enum values. +template +[[nodiscard]] constexpr auto enum_value(std::size_t index) noexcept -> detail::enable_if_enum_t> { + using D = std::decay_t; + + if constexpr (detail::is_sparse_v) { + return assert((index < detail::count_v)), detail::values_v[index]; + } else { + constexpr bool is_flag = detail::is_flags_v; + constexpr auto min = is_flag ? detail::log2(detail::min_v) : detail::min_v; + + return assert((index < detail::count_v)), detail::value(index); + } +} + +// Returns enum value at specified index. +template +[[nodiscard]] constexpr auto enum_value() noexcept -> detail::enable_if_enum_t> { + return enum_value>(I); +} + +// Returns std::array with enum values, sorted by enum value. +template +[[nodiscard]] constexpr auto enum_values() noexcept -> detail::enable_if_enum_t> { + return detail::values_v>; +} + +// Returns name from static storage enum variable. +// This version is much lighter on the compile times and is not restricted to the enum_range limitation. +template +[[nodiscard]] constexpr auto enum_name() noexcept -> detail::enable_if_enum_t { + constexpr string_view name = detail::enum_name_v, V>; + static_assert(name.size() > 0, "Enum value does not have a name."); + + return name; +} + +// Returns name from enum value. +// If enum value does not have name or value out of range, returns empty string. +template +[[nodiscard]] constexpr auto enum_name(E value) noexcept -> detail::enable_if_enum_t { + using D = std::decay_t; + using U = underlying_type_t; + + if constexpr (detail::is_sparse_v || detail::is_flags_v) { + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (enum_value(i) == value) { + return detail::names_v[i]; + } + } + } else { + const auto v = static_cast(value); + if (v >= detail::min_v && v <= detail::max_v) { + return detail::names_v[static_cast(v - detail::min_v)]; + } + } + + return {}; // Invalid value or out of range. +} + +// Returns name from enum-flags value. +// If enum-flags value does not have name or value out of range, returns empty string. +template +[[nodiscard]] auto enum_flags_name(E value) -> detail::enable_if_enum_t { + using D = std::decay_t; + using U = underlying_type_t; + + if constexpr (detail::is_flags_v) { + string name; + auto check_value = U{0}; + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (const auto v = static_cast(enum_value(i)); (static_cast(value) & v) != 0) { + check_value |= v; + const auto n = detail::names_v[i]; + if (!name.empty()) { + name.append(1, '|'); + } + name.append(n.data(), n.size()); + } + } + + if (check_value != 0 && check_value == static_cast(value)) { + return name; + } + + return {}; // Invalid value or out of range. + } else { + return string{enum_name(value)}; + } +} + +// Returns std::array with names, sorted by enum value. +template +[[nodiscard]] constexpr auto enum_names() noexcept -> detail::enable_if_enum_t> { + return detail::names_v>; +} + +// Returns std::array with pairs (value, name), sorted by enum value. +template +[[nodiscard]] constexpr auto enum_entries() noexcept -> detail::enable_if_enum_t> { + return detail::entries_v>; +} + +// Obtains enum value from integer value. +// Returns optional with enum value. +template +[[nodiscard]] constexpr auto enum_cast(underlying_type_t value) noexcept -> detail::enable_if_enum_t>> { + using D = std::decay_t; + using U = underlying_type_t; + + if constexpr (detail::is_sparse_v) { + constexpr auto count = detail::count_v; + if constexpr (detail::is_flags_v) { + auto check_value = U{0}; + for (std::size_t i = 0; i < count; ++i) { + if (const auto v = static_cast(enum_value(i)); (value & v) != 0) { + check_value |= v; + } + } + + if (check_value != 0 && check_value == value) { + return static_cast(value); + } + } else { + for (std::size_t i = 0; i < count; ++i) { + if (value == static_cast(enum_value(i))) { + return static_cast(value); + } + } + } + } else { + constexpr auto min = detail::min_v; + constexpr auto max = detail::is_flags_v ? detail::values_ors() : detail::max_v; + + if (value >= min && value <= max) { + return static_cast(value); + } + } + + return {}; // Invalid value or out of range. +} + +// allows you to write magic_enum::enum_cast("bar", magic_enum::case_insensitive); +inline constexpr auto case_insensitive = [](auto lhs, auto rhs) noexcept + -> std::enable_if_t, char> && std::is_same_v, char>, bool> { +#if defined(MAGIC_ENUM_ENABLE_NONASCII) + static_assert(!std::is_same_v, "magic_enum::case_insensitive not supported Non-ASCII feature."); + return {}; +#else + return detail::to_lower(lhs) == detail::to_lower(rhs); +#endif +}; + +// Obtains enum value from name. +// Returns optional with enum value. +template > +[[nodiscard]] constexpr auto enum_cast(string_view value, BinaryPredicate p = {}) noexcept(std::is_nothrow_invocable_r_v) -> detail::enable_if_enum_t>> { + static_assert(std::is_invocable_r_v, "magic_enum::enum_cast requires bool(char, char) invocable predicate."); + using D = std::decay_t; + using U = underlying_type_t; + + if constexpr (detail::is_flags_v) { + auto result = U{0}; + while (!value.empty()) { + const auto d = detail::find(value, '|'); + const auto s = (d == string_view::npos) ? value : value.substr(0, d); + auto f = U{0}; + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (detail::cmp_equal(s, detail::names_v[i], p)) { + f = static_cast(enum_value(i)); + result |= f; + break; + } + } + if (f == U{0}) { + return {}; // Invalid value or out of range. + } + value.remove_prefix((d == string_view::npos) ? value.size() : d + 1); + } + + if (result != U{0}) { + return static_cast(result); + } + } else { + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (detail::cmp_equal(value, detail::names_v[i], p)) { + return enum_value(i); + } + } + } + + return {}; // Invalid value or out of range. +} + +// Returns integer value from enum value. +template +[[nodiscard]] constexpr auto enum_integer(E value) noexcept -> detail::enable_if_enum_t> { + return static_cast>(value); +} + +// Obtains index in enum values from enum value. +// Returns optional with index. +template +[[nodiscard]] constexpr auto enum_index(E value) noexcept -> detail::enable_if_enum_t> { + using D = std::decay_t; + using U = underlying_type_t; + + if constexpr (detail::is_sparse_v || detail::is_flags_v) { + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (enum_value(i) == value) { + return i; + } + } + } else { + const auto v = static_cast(value); + if (v >= detail::min_v && v <= detail::max_v) { + return static_cast(v - detail::min_v); + } + } + + return {}; // Invalid value or out of range. +} + +// Checks whether enum contains enumerator with such enum value. +template +[[nodiscard]] constexpr auto enum_contains(E value) noexcept -> detail::enable_if_enum_t { + using D = std::decay_t; + using U = underlying_type_t; + + return enum_cast(static_cast(value)).has_value(); +} + +// Checks whether enum contains enumerator with such integer value. +template +[[nodiscard]] constexpr auto enum_contains(underlying_type_t value) noexcept -> detail::enable_if_enum_t { + return enum_cast>(value).has_value(); +} + +// Checks whether enum contains enumerator with such name. +template > +[[nodiscard]] constexpr auto enum_contains(string_view value, BinaryPredicate p = {}) noexcept(std::is_nothrow_invocable_r_v) -> detail::enable_if_enum_t { + static_assert(std::is_invocable_r_v, "magic_enum::enum_contains requires bool(char, char) invocable predicate."); + + return enum_cast>(value, std::move_if_noexcept(p)).has_value(); +} + +namespace fusion_detail { + +template +constexpr std::optional fuse_one_enum(std::optional hash, E value) noexcept { + if (hash.has_value()) { + if (const auto index = enum_index(value); index.has_value()) { + // Add 1 to prevent matching 2D fusions with 3D fusions etc. + return (hash.value() << detail::log2(enum_count() + 1)) | (index.value() + 1); + } + } + return std::nullopt; +} + +template +constexpr std::optional fuse_enum(E value) noexcept { + return fuse_one_enum(0, value); +} + +template +constexpr std::optional fuse_enum(E head, Es... tail) noexcept { + return fuse_one_enum(fuse_enum(tail...), head); +} + +} // namespace magic_enum::fusion_detail + +// Returns a bijective mix of several enum values. This can be used to emulate 2D switch/case statements. +template +[[nodiscard]] constexpr auto enum_fuse(Es... values) { + static_assert((std::is_enum_v> && ...), "magic_enum::enum_fuse works only with enums"); + static_assert(sizeof...(Es) >= 2, "magic_enum::enum_fuse requires at least 2 enums"); + static_assert((detail::log2(enum_count() + 1) + ...) <= (sizeof(std::uintmax_t) * 8), "magic_enum::enum_fuse does not work for large enums"); + const auto fuse = fusion_detail::fuse_enum(values...); + return assert(fuse.has_value()), fuse; +} + +namespace ostream_operators { + +template = 0> +std::basic_ostream& operator<<(std::basic_ostream& os, E value) { + using D = std::decay_t; + using U = underlying_type_t; + + if constexpr (detail::supported::value) { + if (const auto name = magic_enum::enum_flags_name(value); !name.empty()) { + for (const auto c : name) { + os.put(c); + } + return os; + } + } + return (os << static_cast(value)); +} + +template = 0> +std::basic_ostream& operator<<(std::basic_ostream& os, optional value) { + return value.has_value() ? (os << value.value()) : os; +} + +} // namespace magic_enum::ostream_operators + +namespace bitwise_operators { + +template = 0> +constexpr E operator~(E rhs) noexcept { + return static_cast(~static_cast>(rhs)); +} + +template = 0> +constexpr E operator|(E lhs, E rhs) noexcept { + return static_cast(static_cast>(lhs) | static_cast>(rhs)); +} + +template = 0> +constexpr E operator&(E lhs, E rhs) noexcept { + return static_cast(static_cast>(lhs) & static_cast>(rhs)); +} + +template = 0> +constexpr E operator^(E lhs, E rhs) noexcept { + return static_cast(static_cast>(lhs) ^ static_cast>(rhs)); +} + +template = 0> +constexpr E& operator|=(E& lhs, E rhs) noexcept { + return lhs = (lhs | rhs); +} + +template = 0> +constexpr E& operator&=(E& lhs, E rhs) noexcept { + return lhs = (lhs & rhs); +} + +template = 0> +constexpr E& operator^=(E& lhs, E rhs) noexcept { + return lhs = (lhs ^ rhs); +} + +} // namespace magic_enum::bitwise_operators + +} // namespace magic_enum + +#if defined(__clang__) +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#elif defined(_MSC_VER) +# pragma warning(pop) +#endif + +#endif // NEARGYE_MAGIC_ENUM_HPP diff --git a/verification/cpp/suite/test_bitarray.cpp b/verification/cpp/suite/test_bitarray.cpp index 2881968e..fe10f998 100644 --- a/verification/cpp/suite/test_bitarray.cpp +++ b/verification/cpp/suite/test_bitarray.cpp @@ -1,4 +1,12 @@ -#include "gmock/gmock.h" +/* + * Copyright (c) 2022 UAVCAN Development Team. + * Authors: Pavel Pletenev + * This software is distributed under the terms of the MIT License. + * + * Tests of serialization + */ + +#include "test_helpers.hpp" #include "nunavut/support/serialization.hpp" @@ -25,6 +33,15 @@ TEST(BitSpan, Constructor) { } } +TEST(BitSpan, SetZeros) +{ + std::array srcArray{ 0xAA, 0xFF }; + nunavut::support::bitspan sp(srcArray, 10U); + auto res = sp.padAndMoveToAlignment(8U); + ASSERT_TRUE(res) << "Error was " << res.error(); + ASSERT_EQ(srcArray[1], 0x03); +} + TEST(BitSpan, AlignedPtr) { std::array srcArray{ 1, 2, 3, 4, 5 }; { @@ -66,9 +83,7 @@ TEST(BitSpan, CopyBits) { std::array dst{}; memset(dst.data(), 0, dst.size()); - nunavut::support::const_bitspan sp{src}; - nunavut::support::bitspan dstSp{dst}; - sp.copyTo(dstSp); + nunavut::support::const_bitspan{src}.copyTo(nunavut::support::bitspan{dst}); for(size_t i = 0; i < src.size(); ++i) { ASSERT_EQ(src[i], dst[i]); @@ -76,7 +91,7 @@ TEST(BitSpan, CopyBits) { } TEST(BitSpan, CopyBitsWithAlignedOffset) { - std::array src{ 1, 2, 3, 4, 5 }; + std::array src{ 0x11, 0x22, 0x33, 0x44, 0x55 }; std::array dst{}; memset(dst.data(), 0, dst.size()); @@ -97,6 +112,29 @@ TEST(BitSpan, CopyBitsWithAlignedOffset) { ASSERT_EQ(src[i], dst[i+1]); } ASSERT_EQ(0, dst[0]); + + memset(dst.data(), 0xA, dst.size()); + + nunavut::support::const_bitspan{src, 0}.copyTo(nunavut::support::bitspan{dst, 8}, 3U*8U+4U); + + for(size_t i = 0; i < src.size() - 2; ++i) + { + ASSERT_EQ(src[i], dst[i+1]); + } + EXPECT_EQ(src[3] & 0x0F, dst[4]); + ASSERT_EQ(0xA, dst[0]); +} + +TEST(BitSpan, CopyBitsWithAlignedOffsetNonByteLen) { + std::array src{ 0x0, 0x0, 0x11, 0x22, 0x33, 0x44, 0x55 }; + std::array dst{}; + memset(dst.data(), 0, dst.size()); + + nunavut::support::const_bitspan(src, 2U * 8U).copyTo(nunavut::support::bitspan{dst}, 4); + ASSERT_EQ(0x1U, dst[0]); + + nunavut::support::const_bitspan(src, 3U * 8U).copyTo(nunavut::support::bitspan{dst}, 4); + ASSERT_EQ(0x2U, dst[0]); } TEST(BitSpan, CopyBitsWithUnalignedOffset){ @@ -323,8 +361,14 @@ TEST(BitSpan, GetU16_tooSmall) TEST(BitSpan, GetU32) { - const uint8_t data[] = {0xAA, 0xAA, 0xAA, 0xAA}; - ASSERT_EQ(0xAAAAAAAAU, nunavut::support::const_bitspan(data, 0).getU32(32U)); + { + const uint8_t data[] = {0xAA, 0xAA, 0xAA, 0xAA}; + ASSERT_EQ(0xAAAAAAAAU, nunavut::support::const_bitspan(data, 0).getU32(32U)); + } + { + const uint8_t data[] = {0xFF, 0xFF, 0xFF, 0xFF}; + ASSERT_EQ(0xFFFFFFFFU, nunavut::support::const_bitspan(data, 0).getU32(32U)); + } } TEST(BitSpan, GetU32_tooSmall) @@ -339,8 +383,14 @@ TEST(BitSpan, GetU32_tooSmall) TEST(BitSpan, GetU64) { - const uint8_t data[] = {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}; - ASSERT_EQ(0xAAAAAAAAAAAAAAAAU, nunavut::support::const_bitspan(data, 0).getU64(64U)); + { + const uint8_t data[] = {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}; + ASSERT_EQ(0xAAAAAAAAAAAAAAAAU, nunavut::support::const_bitspan(data, 0).getU64(64U)); + } + { + const uint8_t data[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + ASSERT_EQ(0xFFFFFFFFFFFFFFFFU, nunavut::support::const_bitspan(data, 0).getU64(64U)); + } } TEST(BitSpan, GetU64_tooSmall) @@ -461,6 +511,178 @@ TEST(BitSpan, GetI64_zeroDataLen) ASSERT_EQ(0, nunavut::support::const_bitspan(data, 0).getI64(0U)); } +// +--------------------------------------------------------------------------+ +// | GetU/I*(8/16/32/64) out of range +// +--------------------------------------------------------------------------+ + +TEST(BitSpan, GetU8_outofrange) +{ + const uint8_t data[] = {0xFF}; + ASSERT_EQ(0x0U, nunavut::support::const_bitspan(data, 1U * 8U+1).getU8(8U)); +} + +TEST(BitSpan, GetU16_outofrange) +{ + const uint8_t data[] = {0xFF}; + ASSERT_EQ(0x0U, nunavut::support::const_bitspan(data, 2U * 8U+1).getU16(16U)); +} + +TEST(BitSpan, GetU32_outofrange) +{ + const uint8_t data[] = {0xFF}; + ASSERT_EQ(0x0U, nunavut::support::const_bitspan(data, 4U * 8U+1).getU32(32U)); +} + +TEST(BitSpan, GetU64_outofrange) +{ + const uint8_t data[] = {0xFF}; + ASSERT_EQ(0x0U, nunavut::support::const_bitspan(data, 4U * 8U+1).getU64(64U)); +} + +TEST(BitSpan, GetI8_outofrange) +{ + const uint8_t data[] = {0xFF}; + ASSERT_EQ(0x0, nunavut::support::const_bitspan(data, 1U * 8U+1).getI8(8U)); +} + +TEST(BitSpan, GetI16_outofrange) +{ + const uint8_t data[] = {0xFF}; + ASSERT_EQ(0x0, nunavut::support::const_bitspan(data, 2U * 8U+1).getI16(16U)); +} + +TEST(BitSpan, GetI32_outofrange) +{ + const uint8_t data[] = {0xFF}; + ASSERT_EQ(0x0, nunavut::support::const_bitspan(data, 4U * 8U+1).getI32(32U)); +} + +TEST(BitSpan, GetI64_outofrange) +{ + const uint8_t data[] = {0xFF}; + ASSERT_EQ(0x0, nunavut::support::const_bitspan(data, 4U * 8U+1).getI64(64U)); +} + +// +--------------------------------------------------------------------------+ +// | SetGet(U/I)(8/16/32/64) +// +--------------------------------------------------------------------------+ + +constexpr static size_t getset_n_tries = 10; + +using namespace nunavut::testing; + +TEST(BitSpan, SetGetU8) +{ + uint8_t data[getset_n_tries * 64]{0}; + for (size_t i = 0U; i < getset_n_tries; i++) + { + auto ref = randU8(); + const auto offset = i * sizeof(ref) * 8U; + auto rslt = nunavut::support::bitspan({data}, offset).setUxx(ref, 8U); + ASSERT_TRUE(rslt.has_value()) << "Error was " << rslt.error(); + auto act = nunavut::support::const_bitspan(data, offset).getU8(8U); + ASSERT_EQ(hex(ref), hex(act)) << i; + } +} + +TEST(BitSpan, SetGetU16) +{ + uint8_t data[getset_n_tries * 64]{0}; + for (size_t i = 0U; i < getset_n_tries; i++) + { + auto ref = randU16(); + const auto offset = i * sizeof(ref) * 8U; + auto rslt = nunavut::support::bitspan({data}, offset).setUxx(ref, 16U); + ASSERT_TRUE(rslt.has_value()) << "Error was " << rslt.error(); + auto act = nunavut::support::const_bitspan(data, offset).getU16(16U); + ASSERT_EQ(hex(ref), hex(act)) << i; + } +} + +TEST(BitSpan, SetGetU32) +{ + uint8_t data[getset_n_tries * 64]{0}; + for (size_t i = 0U; i < getset_n_tries; i++) + { + auto ref = randU32(); + const auto offset = i * sizeof(ref) * 8U; + auto rslt = nunavut::support::bitspan({data}, offset).setUxx(ref, 32U); + ASSERT_TRUE(rslt.has_value()) << "Error was " << rslt.error(); + auto act = nunavut::support::const_bitspan(data, offset).getU32(32U); + ASSERT_EQ(hex(ref), hex(act)) << i; + } +} + +TEST(BitSpan, SetGetU64) +{ + uint8_t data[getset_n_tries * 64]{0}; + for (size_t i = 0U; i < getset_n_tries; i++) + { + auto ref = randU64(); + const auto offset = i * sizeof(ref) * 8U; + auto rslt = nunavut::support::bitspan({data}, offset).setUxx(ref, 64U); + ASSERT_TRUE(rslt.has_value()) << "Error was " << rslt.error(); + auto act = nunavut::support::const_bitspan(data, offset).getU64(64U); + ASSERT_EQ(hex(ref), hex(act)) << i; + } +} + +TEST(BitSpan, SetGetI8) +{ + uint8_t data[getset_n_tries * 64]{0}; + for (size_t i = 0U; i < getset_n_tries; i++) + { + auto ref = randI8(); + const auto offset = i * sizeof(ref) * 8U; + auto rslt = nunavut::support::bitspan({data}, offset).setIxx(ref, 8U); + ASSERT_TRUE(rslt.has_value()) << "Error was " << rslt.error(); + auto act = nunavut::support::const_bitspan(data, offset).getI8(8U); + ASSERT_EQ(hex(ref), hex(act)) << i; + } +} + +TEST(BitSpan, SetGetI16) +{ + uint8_t data[getset_n_tries * 64]{0}; + for (size_t i = 0U; i < getset_n_tries; i++) + { + auto ref = randI16(); + const auto offset = i * sizeof(ref) * 8U; + auto rslt = nunavut::support::bitspan({data}, offset).setIxx(ref, 16U); + ASSERT_TRUE(rslt.has_value()) << "Error was " << rslt.error(); + auto act = nunavut::support::const_bitspan(data, offset).getI16(16U); + ASSERT_EQ(hex(ref), hex(act)) << i; + } +} + +TEST(BitSpan, SetGetI32) +{ + uint8_t data[getset_n_tries * 64]{0}; + for (size_t i = 0U; i < getset_n_tries; i++) + { + auto ref = randI32(); + const auto offset = i * sizeof(ref) * 8U; + auto rslt = nunavut::support::bitspan({data}, offset).setIxx(ref, 32U); + ASSERT_TRUE(rslt.has_value()) << "Error was " << rslt.error(); + auto act = nunavut::support::const_bitspan(data, offset).getI32(32U); + ASSERT_EQ(hex(ref), hex(act)) << i; + } +} + +TEST(BitSpan, SetGetI64) +{ + uint8_t data[getset_n_tries * 64]{0}; + for (size_t i = 0U; i < getset_n_tries; i++) + { + auto ref = randI64(); + const auto offset = i * sizeof(ref) * 8U; + auto rslt = nunavut::support::bitspan({data}, offset).setIxx(ref, 64U); + ASSERT_TRUE(rslt.has_value()) << "Error was " << rslt.error(); + auto act = nunavut::support::const_bitspan(data, offset).getI64(64U); + ASSERT_EQ(hex(ref), hex(act)) << i; + } +} + // +--------------------------------------------------------------------------+ // | nunavut::support::float16Pack // +--------------------------------------------------------------------------+ diff --git a/verification/cpp/suite/test_compiles.cpp b/verification/cpp/suite/test_compiles.cpp index 43923e5d..8d9e756c 100644 --- a/verification/cpp/suite/test_compiles.cpp +++ b/verification/cpp/suite/test_compiles.cpp @@ -1,6 +1,6 @@ /* * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * + * Authors: Scott Dixon , Pavel Pletenev * Sanity tests. */ #include "gmock/gmock.h" diff --git a/verification/cpp/suite/test_helpers.hpp b/verification/cpp/suite/test_helpers.hpp new file mode 100644 index 00000000..479400bd --- /dev/null +++ b/verification/cpp/suite/test_helpers.hpp @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2022 UAVCAN Development Team. + * Authors: Pavel Pletenev + * This software is distributed under the terms of the MIT License. + * + * Tests of serialization + */ + +#include "gmock/gmock.h" +#include "nunavut/support/serialization.hpp" + +#if __cplusplus > 201703L +#include "magic_enum.hpp" +testing::Message& operator<<(testing::Message& s, const nunavut::support::Error& e){ + s << magic_enum::enum_name(e); + return s; +} +#else +testing::Message& operator<<(testing::Message& s, const nunavut::support::Error& e){ + s << static_cast>(e); + return s; +} +#endif + +namespace nunavut{ +namespace testing{ + + +template +class Hex { + using ft = std::conditional_t<(sizeof(I)<=2), std::conditional_t<(std::is_signed::value), int16_t, uint16_t>, I>; +public: + explicit Hex(I n) : number_(n) {} + operator I() { return number_; } + + friend std::ostream& operator<<(std::ostream& s, const Hex& h){ + s << std::hex << static_cast(h.number_); + return s; + } + bool operator==(const Hex& other)const{ + return number_ == other.number_; + } +private: + I number_; +}; + +template +Hex hex(I&& i){ + return Hex(std::forward(i)); +} + +} // namespace testing +} // namespace nunavut + +inline int8_t randI8(void) +{ + return static_cast(rand()); +} + +inline int16_t randI16(void) +{ + return static_cast((randI8() + 1) * randI8()); +} + +inline int32_t randI32(void) +{ + return static_cast((randI16() + 1L) * randI16()); +} + +inline int64_t randI64(void) +{ + return static_cast((randI32() + 1LL) * randI32()); +} + +inline uint8_t randU8(void) +{ + return static_cast(rand()); +} + +inline uint16_t randU16(void) +{ + return static_cast((randU8() + 1) * randU8()); +} + +inline uint32_t randU32(void) +{ + return static_cast((randU16() + 1L) * randU16()); +} + +inline uint64_t randU64(void) +{ + return static_cast((randU32() + 1LL) * randU32()); +} + +inline float randF16(void) +{ + return static_cast(randI8()); +} + +inline float randF32(void) +{ + return static_cast(randI64()); +} + +inline double randF64(void) +{ + return static_cast(randI64()); +} diff --git a/verification/cpp/suite/test_serialization.cpp b/verification/cpp/suite/test_serialization.cpp new file mode 100644 index 00000000..92b909f5 --- /dev/null +++ b/verification/cpp/suite/test_serialization.cpp @@ -0,0 +1,407 @@ +/* + * Copyright (c) 2022 UAVCAN Development Team. + * Authors: Pavel Pletenev + * This software is distributed under the terms of the MIT License. + * + * Tests of serialization + */ + +#include "test_helpers.hpp" +#include "uavcan/time/TimeSystem_0_1.hpp" +#include "regulated/basics/Struct__0_1.hpp" +#include "regulated/basics/Primitive_0_1.hpp" + + + +TEST(Serialization, BasicSerialize) { + uavcan::time::TimeSystem_0_1 a; + uint8_t buffer[8]{}; + { + a.value = 1; + std::fill(std::begin(buffer), std::end(buffer), 0xAA); + const auto result = a.serialize({{buffer}}); + ASSERT_TRUE(result); + ASSERT_EQ(1U, *result); + ASSERT_EQ(1U, buffer[0]); + ASSERT_EQ(0xAA, buffer[1]); + } + { + a.value = 0xFF; + std::fill(std::begin(buffer), std::end(buffer), 0xAA); + const auto result = a.serialize({{buffer}}); + ASSERT_TRUE(result); + ASSERT_EQ(1U, *result); + ASSERT_EQ(0x0FU, buffer[0]); + ASSERT_EQ(0xAAU, buffer[1]); + } +} + +/// This was copied from C counterpart and modified for C++ +/// The reference array has been pedantically validated manually bit by bit (it did really took authors of +/// C tests about three hours). +/// The following Python script has been used to cross-check against PyUAVCAN, which has been cross-checked against +/// earlier v0 implementations beforehand: +/// +/// import sys, pathlib, importlib, pyuavcan +/// sys.path.append(str(pathlib.Path.cwd())) +/// target, lookup = sys.argv[1], sys.argv[2:] +/// for lk in lookup: +/// pyuavcan.dsdl.generate_package(lk, lookup) +/// pyuavcan.dsdl.generate_package(target, lookup) +/// from regulated.basics import Struct__0_1, DelimitedFixedSize_0_1, DelimitedVariableSize_0_1, Union_0_1 +/// s = Struct__0_1() +/// s.boolean = True +/// s.i10_4[0] = +0x5555 # saturates to +511 +/// s.i10_4[1] = -0x6666 # saturates to -512 +/// s.i10_4[2] = +0x0055 # original value retained +/// s.i10_4[3] = -0x00AA # original value retained +/// s.f16_le2 = [ +/// -65504.0, +/// +float('inf'), # negative infinity retained +/// ] +/// s.unaligned_bitpacked_3 = [1, 0, 1] +/// s.bytes_lt3 = [111, 222] +/// s.bytes_3[0] = -0x77 +/// s.bytes_3[1] = -0x11 +/// s.bytes_3[2] = +0x77 +/// s.u2_le4 = [ +/// 0x02, # retained +/// 0x11, # truncated => 1 +/// 0xFF, # truncated => 3 +/// ] +/// s.delimited_fix_le2 = [DelimitedFixedSize_0_1()] +/// s.u16_2[0] = 0x1234 +/// s.u16_2[1] = 0x5678 +/// s.aligned_bitpacked_3 = [1, 0, 0] +/// s.unaligned_bitpacked_lt3 = [1, 0] # 0b01 +/// s.delimited_var_2[0].f16 = +float('inf') +/// s.delimited_var_2[1].f64 = -1e40 # retained +/// s.aligned_bitpacked_le3 = [1] +/// sr = b''.join(pyuavcan.dsdl.serialize(s)) +/// print(len(sr), 'bytes') +/// print('\n'.join(f'0x{x:02X}U,' for x in sr)) +TEST(Serialization, StructReference) +{ + return; + regulated::basics::Struct__0_1 obj{}; + + // Initialize a reference object, serialize, and compare against the reference serialized representation. + obj.boolean = true; + obj.i10_4[0] = +0x5555; // saturates to +511 + obj.i10_4[1] = -0x6666; // saturates to -512 + obj.i10_4[2] = +0x0055; // original value retained + obj.i10_4[3] = -0x00AA; // original value retained + obj.f16_le2.emplace_back(-1e9F); // saturated to -65504 + obj.f16_le2.emplace_back(+INFINITY); // infinity retained + ASSERT_EQ(2U, obj.f16_le2.size()); + //obj.unaligned_bitpacked_3[0] = 0xF5; // 0b101, rest truncated away and ignored TODO:Fix + obj.unaligned_bitpacked_3[0] = 1; + obj.unaligned_bitpacked_3[1] = 0; + obj.unaligned_bitpacked_3[2] = 1; + //obj.sealed = 123; // ignored + obj.bytes_lt3.emplace_back(111); + obj.bytes_lt3.emplace_back(222); + ASSERT_EQ(2U, obj.bytes_lt3.size()); + obj.bytes_3[0] = -0x77; + obj.bytes_3[1] = -0x11; + obj.bytes_3[2] = +0x77; + obj.u2_le4.emplace_back(0x02); // retained + obj.u2_le4.emplace_back(0x11); // truncated => 1 + obj.u2_le4.emplace_back(0xFF); // truncated => 3 + //obj.u2_le4.emplace_back(0xFF); // ignored because the length is 3 + ASSERT_EQ(3U, obj.u2_le4.size()); + obj.delimited_fix_le2.emplace_back(); // ignored + ASSERT_EQ(1U, obj.delimited_fix_le2.size()); + obj.u16_2[0] = 0x1234; + obj.u16_2[1] = 0x5678; + obj.aligned_bitpacked_3[0] = 0xF1U; + // obj.unaligned_bitpacked_lt3.bitpacked[0] = 0xF1U; + obj.unaligned_bitpacked_lt3.emplace_back(1); + obj.unaligned_bitpacked_lt3.emplace_back(0); + ASSERT_EQ(2U, obj.unaligned_bitpacked_lt3.size()); // 0b01, rest truncated + // obj.delimited_var_2[0].f16 + // regulated_basics_DelimitedVariableSize_0_1_select_f16_(&obj.delimited_var_2[0]); + // obj.delimited_var_2[0].f16 = +1e9F; // truncated to infinity + // regulated_basics_DelimitedVariableSize_0_1_select_f64_(&obj.delimited_var_2[1]); + // obj.delimited_var_2[1].f64 = -1e40; // retained + obj.aligned_bitpacked_le3.emplace_back(1); + ASSERT_EQ(1U, obj.aligned_bitpacked_le3.size()); // only lsb is set, other truncated + + const uint8_t reference[] = { + 0xFEU, // void1, true, 6 lsb of int10 = 511 + 0x07U, // 4 msb of int10 = 511, 4 lsb of -512 = 0b_10_0000_0000 + 0x60U, // 6 msb of -512 (0x60 = 0b_0110_0000), 2 lsb of 0x0055 = 0b0001010101 + 0x15U, // 8 msb of 0b_00_0101_0101, 0x15 = 0b00010101 + 0x56U, // ALIGNED; -0x00AA in two's complement is 0x356 = 0b_11_01010110 + 0x0BU, // 2 msb of the above (0b11) followed by 8 bit of length prefix (2) of float16[<=2] f16_le2 + 0xFCU, // 2 msb of the length prefix followed by 6 lsb of (float16.min = 0xfbff = 0b_11111011_11111111) + 0xEFU, // 0b_xx_111011_11xxxxxx (continuation of the float16) + 0x03U, // 2 msb of the above (0b11) and the next float16 = +inf, represented 0x7C00 = 0b_01111100_00000000 + 0xF0U, // 0b_xx111100_00xxxxxx (continuation of the infinity) + 0x15U, // 2 msb of the above (0b01) followed by bool[3] unaligned_bitpacked_3 = [1, 0, 1], then PADDING + 0x02U, // ALIGNED; empty struct not manifested, here we have length = 2 of uint8[<3] bytes_lt3 + 0x6FU, // bytes_lt3[0] = 111 + 0xDEU, // bytes_lt3[1] = 222 + 0x89U, // bytes_3[0] = -0x77 (two's complement) + 0xEFU, // bytes_3[1] = -0x11 (two's complement) + 0x77U, // bytes_3[2] = +0x77 + 0x03U, // length = 3 of truncated uint2[<=4] u2_le4 + 0x36U, // 0b_00_11_01_10: u2_le4[0] = 0b10, u2_le4[1] = 0b01, u2_le4[2] = 0b11, then dynamic padding + 0x01U, // ALIGNED; length = 1 of DelimitedFixedSize.0.1[<=2] delimited_fix_le2 + 0x00U, // Constant DH of DelimitedFixedSize.0.1 + 0x00U, // ditto + 0x00U, // ditto + 0x00U, // ditto + 0x34U, // uint16[2] u16_2; first element = 0x1234 + 0x12U, // continuation + 0x78U, // second element = 0x5678 + 0x56U, // continuation + 0x11U, // bool[3] aligned_bitpacked_3 = [1, 0, 0]; then 5 lsb of length = 2 of bool[<3] unaligned_bitpacked_lt3 + 0x08U, // 3 msb of length = 2 (i.e., zeros), then values [1, 0], then 1 bit of padding before composite + 0x03U, // DH = 3 of the first element of DelimitedVariableSize.0.1[2] delimited_var_2 + 0x00U, // ditto + 0x00U, // ditto + 0x00U, // ditto + 0x00U, // union tag = 0, f16 selected + 0x00U, // f16 truncated to positive infinity; see representation above + 0x7CU, // ditto + 0x09U, // DH = (8 + 1) of the second element of DelimitedVariableSize.0.1[2] delimited_var_2 + 0x00U, // ditto + 0x00U, // ditto + 0x00U, // ditto + 0x02U, // union tag = 2, f64 selected (notice that union tags are always aligned by design) + 0xA5U, // float64 = -1e40 is 0xc83d6329f1c35ca5, this is the LSB + 0x5CU, // ditto + 0xC3U, // ditto + 0xF1U, // ditto + 0x29U, // ditto + 0x63U, // ditto + 0x3DU, // ditto + 0xC8U, // ditto + 0x01U, // length = 1 of bool[<=3] aligned_bitpacked_le3 + 0x01U, // the one single bit of the above, then 7 bits of dynamic padding to byte + // END OF SERIALIZED REPRESENTATION + 0x55U, // canary 1 + 0x55U, // canary 2 + 0x55U, // canary 3 + 0x55U, // canary 4 + 0x55U, // canary 5 + 0x55U, // canary 6 + 0x55U, // canary 7 + 0x55U, // canary 8 + 0x55U, // canary 9 + 0x55U, // canary 10 + 0x55U, // canary 11 + 0x55U, // canary 12 + 0x55U, // canary 13 + 0x55U, // canary 14 + 0x55U, // canary 15 + 0x55U, // canary 16 + }; + + uint8_t buf[sizeof(reference)]; + (void) memset(&buf[0], 0x55U, sizeof(buf)); // fill out canaries + + auto result = obj.serialize({{buf, sizeof(buf)}}); + ASSERT_TRUE(result) << "Error is " << static_cast(result.error()); + + ASSERT_EQ(sizeof(reference) - 16U, result.value()); + + for(size_t i=0; i< sizeof(reference); i++){ + ASSERT_EQ(reference[i], buf[i]) << "Failed at " << i; + } + + + // Check union manipulation functions. + // TEST_ASSERT_TRUE(regulated_basics_DelimitedVariableSize_0_1_is_f16_(&obj.delimited_var_2[0])); + // TEST_ASSERT_FALSE(regulated_basics_DelimitedVariableSize_0_1_is_f32_(&obj.delimited_var_2[0])); + // TEST_ASSERT_FALSE(regulated_basics_DelimitedVariableSize_0_1_is_f64_(&obj.delimited_var_2[0])); + // TEST_ASSERT_FALSE(regulated_basics_DelimitedVariableSize_0_1_is_f64_(NULL)); + // regulated_basics_DelimitedVariableSize_0_1_select_f32_(NULL); // No action; same state retained. + // TEST_ASSERT_TRUE(regulated_basics_DelimitedVariableSize_0_1_is_f16_(&obj.delimited_var_2[0])); + // TEST_ASSERT_FALSE(regulated_basics_DelimitedVariableSize_0_1_is_f32_(&obj.delimited_var_2[0])); + // TEST_ASSERT_FALSE(regulated_basics_DelimitedVariableSize_0_1_is_f64_(&obj.delimited_var_2[0])); + // TEST_ASSERT_FALSE(regulated_basics_DelimitedVariableSize_0_1_is_f64_(NULL)); + + // Test default initialization. + // (void) memset(&obj, 0x55, sizeof(obj)); // Fill using a non-zero pattern. + obj.regulated::basics::Struct__0_1::~Struct__0_1(); + new (&obj)regulated::basics::Struct__0_1(); + ASSERT_EQ(false, obj.boolean); + // ASSERT_EQ(0, obj.i10_4[0]); + // ASSERT_EQ(0, obj.i10_4[1]); + // ASSERT_EQ(0, obj.i10_4[2]); + // ASSERT_EQ(0, obj.i10_4[3]); + ASSERT_EQ(0U, obj.f16_le2.size()); + ASSERT_EQ(0, obj.unaligned_bitpacked_3[0]); + ASSERT_EQ(0, obj.unaligned_bitpacked_3[1]); + ASSERT_EQ(0, obj.unaligned_bitpacked_3[2]); + ASSERT_EQ(0U, obj.bytes_lt3.size()); + ASSERT_EQ(0, obj.bytes_3[0]); + ASSERT_EQ(0, obj.bytes_3[1]); + ASSERT_EQ(0, obj.bytes_3[2]); + ASSERT_EQ(0U, obj.u2_le4.size()); + ASSERT_EQ(0U, obj.delimited_fix_le2.size()); + ASSERT_EQ(0, obj.u16_2[0]); + ASSERT_EQ(0, obj.u16_2[1]); + ASSERT_EQ(0, obj.aligned_bitpacked_3[0]); + ASSERT_EQ(0, obj.aligned_bitpacked_3[1]); + ASSERT_EQ(0, obj.aligned_bitpacked_3[2]); + ASSERT_EQ(0U, obj.unaligned_bitpacked_lt3.size()); + // ASSERT_EQ(0, obj.delimited_var_2[0]._tag_); + ASSERT_NEAR(0, obj.delimited_var_2[0].f16, 1e-9); + // ASSERT_EQ(0, obj.delimited_var_2[1]._tag_); + ASSERT_NEAR(0, obj.delimited_var_2[1].f16, 1e-9); + ASSERT_EQ(0U, obj.aligned_bitpacked_le3.size()); + + // // Deserialize the above reference representation and compare the result against the original object. + // ASSERT_EQ(0, regulated_basics_Struct__0_1_deserialize_(&obj, &reference[0], &size)); + // ASSERT_EQ(sizeof(reference) - 16U, size); // 16 trailing bytes implicitly truncated away + + // ASSERT_EQ(true, obj.boolean); + // ASSERT_EQ(+511, obj.i10_4[0]); // saturated + // ASSERT_EQ(-512, obj.i10_4[1]); // saturated + // ASSERT_EQ(+0x55, obj.i10_4[2]); + // ASSERT_EQ(-0xAA, obj.i10_4[3]); + // ASSERT_NEAR(-65504.0, obj.f16_le2.elements[0], 1e-3); + // TEST_ASSERT_FLOAT_IS_INF(obj.f16_le2.elements[1]); + // ASSERT_EQ(2, obj.f16_le2.count); + // ASSERT_EQ(5, obj.unaligned_bitpacked_3_bitpacked_[0]); // unused MSB are zero-padded + // ASSERT_EQ(111, obj.bytes_lt3.elements[0]); + // ASSERT_EQ(222, obj.bytes_lt3.elements[1]); + // ASSERT_EQ(2, obj.bytes_lt3.count); + // ASSERT_EQ(-0x77, obj.bytes_3[0]); + // ASSERT_EQ(-0x11, obj.bytes_3[1]); + // ASSERT_EQ(+0x77, obj.bytes_3[2]); + // ASSERT_EQ(2, obj.u2_le4.elements[0]); + // ASSERT_EQ(1, obj.u2_le4.elements[1]); + // ASSERT_EQ(3, obj.u2_le4.elements[2]); + // ASSERT_EQ(3, obj.u2_le4.count); + // ASSERT_EQ(1, obj.delimited_fix_le2.count); + // ASSERT_EQ(0x1234, obj.u16_2[0]); + // ASSERT_EQ(0x5678, obj.u16_2[1]); + // ASSERT_EQ(1, obj.aligned_bitpacked_3_bitpacked_[0]); // unused MSB are zero-padded + // ASSERT_EQ(1, obj.unaligned_bitpacked_lt3.bitpacked[0]); // unused MSB are zero-padded + // ASSERT_EQ(2, obj.unaligned_bitpacked_lt3.count); + // ASSERT_EQ(0, obj.delimited_var_2[0]._tag_); + // TEST_ASSERT_FLOAT_IS_INF(obj.delimited_var_2[0].f16); + // ASSERT_EQ(2, obj.delimited_var_2[1]._tag_); + // TEST_ASSERT_DOUBLE_WITHIN(0.5, -1e+40, obj.delimited_var_2[1].f64); + // ASSERT_EQ(1, obj.aligned_bitpacked_le3.bitpacked[0]); // unused MSB are zero-padded + // ASSERT_EQ(1, obj.aligned_bitpacked_le3.count); + + // // Repeat the above, but apply implicit zero extension somewhere in the middle. + // size = 25U; + // ASSERT_EQ(0, regulated_basics_Struct__0_1_deserialize_(&obj, &reference[0], &size)); + // ASSERT_EQ(25, size); // the returned size shall not exceed the buffer size + + // ASSERT_EQ(true, obj.boolean); + // ASSERT_EQ(+511, obj.i10_4[0]); // saturated + // ASSERT_EQ(-512, obj.i10_4[1]); // saturated + // ASSERT_EQ(+0x55, obj.i10_4[2]); + // ASSERT_EQ(-0xAA, obj.i10_4[3]); + // ASSERT_NEAR(-65504.0, obj.f16_le2.elements[0], 1e-3); + // TEST_ASSERT_FLOAT_IS_INF(obj.f16_le2.elements[1]); + // ASSERT_EQ(2, obj.f16_le2.count); + // ASSERT_EQ(5, obj.unaligned_bitpacked_3_bitpacked_[0]); // unused MSB are zero-padded + // ASSERT_EQ(111, obj.bytes_lt3.elements[0]); + // ASSERT_EQ(222, obj.bytes_lt3.elements[1]); + // ASSERT_EQ(2, obj.bytes_lt3.count); + // ASSERT_EQ(-0x77, obj.bytes_3[0]); + // ASSERT_EQ(-0x11, obj.bytes_3[1]); + // ASSERT_EQ(+0x77, obj.bytes_3[2]); + // ASSERT_EQ(2, obj.u2_le4.elements[0]); + // ASSERT_EQ(1, obj.u2_le4.elements[1]); + // ASSERT_EQ(3, obj.u2_le4.elements[2]); + // ASSERT_EQ(3, obj.u2_le4.count); + // ASSERT_EQ(1, obj.delimited_fix_le2.count); + // ASSERT_EQ(0x0034, obj.u16_2[0]); // <-- IMPLICIT ZERO EXTENSION STARTS HERE + // ASSERT_EQ(0x0000, obj.u16_2[1]); // IT'S + // ASSERT_EQ(0, obj.aligned_bitpacked_3_bitpacked_[0]); // ZEROS + // ASSERT_EQ(0, obj.unaligned_bitpacked_lt3.count); // ALL + // ASSERT_EQ(0, obj.delimited_var_2[0]._tag_); // THE + // ASSERT_NEAR(0, obj.delimited_var_2[0].f16, 1e-9); // WAY + // ASSERT_EQ(0, obj.delimited_var_2[1]._tag_); // DOWN + // ASSERT_NEAR(0, obj.delimited_var_2[1].f16, 1e-9); + // ASSERT_EQ(0, obj.aligned_bitpacked_le3.count); +} + + + +TEST(Serialization, Primitive) +{ + using namespace nunavut::testing; + for (uint32_t i = 0U; i < 10; i++) + { + regulated::basics::Primitive_0_1 ref{}; + ref.a_u64 = randU64(); + ref.a_u32 = randU32(); + ref.a_u16 = randU16(); + ref.a_u8 = randU8(); + ref.a_u7 = randU8() & 127U; + ref.n_u64 = randU64(); + ref.n_u32 = randU32(); + ref.n_u16 = randU16(); + ref.n_u8 = randU8(); + ref.n_u7 = randU8() & 127U; + ref.a_i64 = randI64(); + ref.a_i32 = randI32(); + ref.a_i16 = randI16(); + ref.a_i8 = randI8(); + ref.a_i7 = randI8() % 64; + ref.n_i64 = randI64(); + ref.n_i32 = randI32(); + ref.n_i16 = randI16(); + ref.n_i8 = randI8(); + ref.n_i7 = randI8() % 64; + ref.a_f64 = randF64(); + ref.a_f32 = randF32(); + ref.a_f16 = randF16(); + ref.a_bool = randI8() % 2 == 0; + ref.n_bool = randI8() % 2 == 0; + ref.n_f64 = randF64(); + ref.n_f32 = randF32(); + ref.n_f16 = randF16(); + + uint8_t buf[regulated::basics::Primitive_0_1::SERIALIZATION_BUFFER_SIZE_BYTES]; + std::memset(buf, 0, sizeof(buf)); + auto result = ref.serialize({{buf, sizeof(buf)}}); + ASSERT_TRUE(result) << "Error is " << result.error(); + ASSERT_EQ( + static_cast(regulated::basics::Primitive_0_1::SERIALIZATION_BUFFER_SIZE_BYTES), result.value()); + + regulated::basics::Primitive_0_1 obj; + result = obj.deserialize({ {buf, sizeof(buf)} }); + ASSERT_TRUE(result); + EXPECT_EQ( + static_cast(regulated::basics::Primitive_0_1::SERIALIZATION_BUFFER_SIZE_BYTES), result.value()); + EXPECT_EQ(hex(ref.a_u64) , hex(obj.a_u64) ); + EXPECT_EQ(hex(ref.a_u32) , hex(obj.a_u32) ); + EXPECT_EQ(hex(ref.a_u16) , hex(obj.a_u16) ); + EXPECT_EQ(hex(ref.a_u8) , hex(obj.a_u8) ); + EXPECT_EQ(hex(ref.a_u7) , hex(obj.a_u7) ); + EXPECT_EQ(hex(ref.n_u64) , hex(obj.n_u64) ); + EXPECT_EQ(hex(ref.n_u32) , hex(obj.n_u32) ); + EXPECT_EQ(hex(ref.n_u16) , hex(obj.n_u16) ); + EXPECT_EQ(hex(ref.n_u8) , hex(obj.n_u8) ); + EXPECT_EQ(hex(ref.n_u7) , hex(obj.n_u7) ); + EXPECT_EQ(hex(ref.a_i64) , hex(obj.a_i64) ); + EXPECT_EQ(hex(ref.a_i32) , hex(obj.a_i32) ); + EXPECT_EQ(hex(ref.a_i16) , hex(obj.a_i16) ); + EXPECT_EQ(hex(ref.a_i8) , hex(obj.a_i8) ); + EXPECT_EQ(hex(ref.a_i7) , hex(obj.a_i7) ); + EXPECT_EQ(hex(ref.n_i64) , hex(obj.n_i64) ); + EXPECT_EQ(hex(ref.n_i32) , hex(obj.n_i32) ); + EXPECT_EQ(hex(ref.n_i16) , hex(obj.n_i16) ); + EXPECT_EQ(hex(ref.n_i8) , hex(obj.n_i8) ); + EXPECT_EQ(hex(ref.n_i7) , hex(obj.n_i7) ); + EXPECT_DOUBLE_EQ(ref.a_f64 , obj.a_f64 ); + EXPECT_FLOAT_EQ(ref.a_f32 , obj.a_f32 ); + EXPECT_FLOAT_EQ(ref.a_f16 , obj.a_f16 ); + EXPECT_EQ(ref.a_bool , obj.a_bool); + EXPECT_EQ(ref.n_bool , obj.n_bool); + EXPECT_DOUBLE_EQ(ref.n_f64 , obj.n_f64 ); + EXPECT_FLOAT_EQ(ref.n_f32 , obj.n_f32 ); + EXPECT_FLOAT_EQ(ref.n_f16 , obj.n_f16 ); + } +}