{#-
 # Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 # Copyright (C) 2020  UAVCAN Development Team  <uavcan.org>
 # This software is distributed under the terms of the MIT License.
 # Authors: David Lenfesty, Scott Dixon <dixonsco@amazon.com>, Pavel Kirienko <pavel@uavcan.org>,
 #          Peter van der Perk <peter.vanderperk@nxp.com>
-#}

{%- macro assert(expression) -%}
    {%- if options.enable_serialization_asserts -%}
    NUNAVUT_ASSERT({{ expression }});
    {%- endif -%}
{%- endmacro -%}

{%- macro float32_union() -%}
    typedef union  // NOSONAR
    {
        uint32_t bits;
        {{typename_float_32}} real;
    } Float32Bits;
{%- endmacro -%}

// UAVCAN common serialization support routines.                                                             +-+ +-+
// This file is based on canard_dsdl.h, which is part of Libcanard.                                          | | | |
//                                                                                                           \  -  /
// AUTOGENERATED, DO NOT EDIT.                                                                                 ---
//                                                                                                              o
//---------------------------------------------------------------------------------------------------------------------
// Language Options
{% for key, value in options.items() -%}
//     {{ key }}:  {{ value }}
{% endfor %}

#ifndef NUNAVUT_SUPPORT_SERIALIZATION_H_INCLUDED
#define NUNAVUT_SUPPORT_SERIALIZATION_H_INCLUDED

#ifdef __cplusplus
#   if (__cplusplus < 201100L)
#       error "Unsupported language: ISO C11, C++11, or a newer version of either is required."
#   endif
extern "C"
{
#else
#   if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 201112L)
#       error "Unsupported language: ISO C11 or a newer version is required."
#   endif
#endif

#include <string.h>
{% if not options.omit_float_serialization_support %}
#include <float.h>
#include <math.h>  // For isfinite().
{% endif -%}
#include <stdbool.h>
#include <stdint.h>
#include <assert.h>  // For static_assert (C11) and assert() if NUNAVUT_ASSERT is used.

static_assert(sizeof({{ typename_unsigned_bit_length }}) >= sizeof({{ typename_unsigned_length }}),
    "The bit-length type used by Nunavut, {{ typename_unsigned_bit_length }}, "
    "is smaller than this platform's {{ typename_unsigned_length }} type. "
    "Nunavut serialization relies on {{ typename_unsigned_length }} to {{ typename_unsigned_bit_length }} conversions "
    "that do not lose data. You will need to regenerate Nunavut serialization support with a larger "
    "unsigned_bit_length type specified.");

{% for key, value in options.items() -%}
#define {{ "NUNAVUT_SUPPORT_LANGUAGE_OPTION_{}".format(key) | ln.c.macrofy }} {{ value | to_static_assertion_value }}
{% endfor %}

/// Nunavut returns 0 for success and < 0 for any failure. It is always adequate to check that error_value < 0
/// to detect errors or error_value == 0 for success.
///
/// Nunavut serialization will never define more than 127 errors and the reserved error numbers are [-1,-127]
/// (-128 is not used). Error code 1 is currently also not used to avoid conflicts with 3rd-party software.
///
/// Return values > 0 for Nunavut serialization are undefined.
#define NUNAVUT_SUCCESS 0
// API usage errors:
#define NUNAVUT_ERROR_INVALID_ARGUMENT                  2
#define NUNAVUT_ERROR_SERIALIZATION_BUFFER_TOO_SMALL    3
// Invalid representation (caused by bad input data, not API misuse):
#define NUNAVUT_ERROR_REPRESENTATION_BAD_ARRAY_LENGTH        10
#define NUNAVUT_ERROR_REPRESENTATION_BAD_UNION_TAG           11
#define NUNAVUT_ERROR_REPRESENTATION_BAD_DELIMITER_HEADER    12

{% if not options.omit_float_serialization_support -%}
/// Detect whether the target platform is compatible with IEEE 754.
#define NUNAVUT_PLATFORM_IEEE754_FLOAT \
    ((FLT_RADIX == 2) && (FLT_MANT_DIG == 24) && (FLT_MIN_EXP == -125) && (FLT_MAX_EXP == 128))
#define NUNAVUT_PLATFORM_IEEE754_DOUBLE \
    ((FLT_RADIX == 2) && (DBL_MANT_DIG == 53) && (DBL_MIN_EXP == -1021) && (DBL_MAX_EXP == 1024))
{% endif -%}

{%- if options.enable_serialization_asserts %}
#ifndef NUNAVUT_ASSERT
// By default Nunavut does not generate assert statements since the logic to halt a program is platform
// dependent and because this header requires an absolute minimum from a platform and from the C standard library.
// Most platforms can simply define "NUNAVUT_ASSERT(x)=assert(x)" (<assert.h> is always included by Nunavut).
#   error "You must either define NUNAVUT_ASSERT or you need to disable assertions" \
          " when generating serialization support code using Nunavut language options"
#endif
{% endif -%}

{%- if options.target_endianness == 'little' %}
#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && (__BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__)
#   error "This code has been generated for little-endian platforms only. " \
          "To generate portable endianness-invariant code, set the Nunavut option target_endianness='any' " \
          "and regenerate the code. This change will have some performance impact for little-endian machines."
#endif
{%- elif options.target_endianness in ('any', 'big') %}
// This code is endianness-invariant. Use target_endianness='little' to generate little-endian-optimized code.
{%- else %}{%- assert False %}
{%- endif %}

// ---------------------------------------------------- HELPERS ----------------------------------------------------

/// Returns the smallest value.
static inline {{ typename_unsigned_bit_length }} nunavutChooseMin(const {{ typename_unsigned_bit_length }} a, {# -#}
                                                                  const {{ typename_unsigned_bit_length }} b)
{
    return (a < b) ? a : b;
}

/// 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.
///
///      buffer                                                                buffer
///      origin                                                                 end
///         [------------------------- buffer_size_bytes ------------------------]
///         [--------------- fragment_offset_bits ---------------][--- fragment_length_bits ---]
///                                                               [-- out bits --]
///
static inline {{ typename_unsigned_bit_length }} nunavutSaturateBufferFragmentBitLength(
    const {{ typename_unsigned_length }} buffer_size_bytes, {# -#}
    const {{ typename_unsigned_bit_length }} fragment_offset_bits, {# -#}
    const {{ typename_unsigned_bit_length }} fragment_length_bits)
{
    const {{ typename_unsigned_bit_length }} size_bits = ({{ typename_unsigned_bit_length }})buffer_size_bytes * 8U;
    const {{ typename_unsigned_bit_length }} tail_bits = size_bits - nunavutChooseMin(size_bits, fragment_offset_bits);
    return nunavutChooseMin(fragment_length_bits, tail_bits);
}

// ---------------------------------------------------- BIT ARRAY ----------------------------------------------------

/// Copy the specified number of bits from the source buffer into the destination buffer in accordance with the
/// DSDL bit-level serialization specification. The offsets may be arbitrary (may exceed 8 bits).
/// If both offsets are byte-aligned, the function invokes memmove() and possibly adjusts the last byte separately.
/// If the source and the destination overlap AND the offsets are not byte-aligned, the behavior is undefined.
/// If either source or destination pointers are NULL, the behavior is undefined.
/// Arguments:
///     dst             Destination buffer. Shall be at least ceil(length_bits/8) bytes large.
///     dst_offset_bits Offset in bits from the destination pointer. May exceed 8.
///     length_bits     The number of bits to copy. Both source and destination shall be large enough.
///     src             Source buffer. Shall be at least ceil(length_bits/8) bytes large.
///     src_offset_bits Offset in bits from the source pointer. May exceed 8.
static inline void nunavutCopyBits(void* const dst,
                                   const {{ typename_unsigned_bit_length }} dst_offset_bits,
                                   const {{ typename_unsigned_bit_length }} length_bits,
                                   const void* const src,
                                   const {{ typename_unsigned_bit_length }} src_offset_bits)
{
    {{ assert('src != NULL') }}
    {{ assert('dst != NULL') }}
    {{ assert('src != dst') }}
    if ((0U == (src_offset_bits % 8U)) && (0U == (dst_offset_bits % 8U)))  // Aligned copy, optimized, most common case.
    {
        const {{ typename_unsigned_length }} length_bytes = ({{ typename_unsigned_length }})(length_bits / 8U);
        // Intentional violation of MISRA: Pointer arithmetics. This is done to remove the API constraint that
        // offsets be under 8 bits. Fewer constraints reduce the chance of API misuse.
        const uint8_t* const psrc = (src_offset_bits / 8U) + (const uint8_t*) src;  // NOSONAR NOLINT
        uint8_t*       const pdst = (dst_offset_bits / 8U) +       (uint8_t*) dst;  // NOSONAR NOLINT
        (void) memmove(pdst, psrc, length_bytes);
        const uint8_t length_mod = (uint8_t)(length_bits % 8U);
        if (0U != length_mod)  // If the length is unaligned, the last byte requires special treatment.
        {
            // Intentional violation of MISRA: Pointer arithmetics. It is unavoidable in this context.
            const uint8_t* const last_src = psrc + length_bytes;  // NOLINT NOSONAR
            uint8_t* const last_dst       = pdst + length_bytes;  // NOLINT NOSONAR
            {{ assert('length_mod < 8U') }}
            const uint8_t mask = (uint8_t)((1U << length_mod) - 1U);
            *last_dst = (*last_dst & (uint8_t)~mask) | (*last_src & mask);
        }
    }
    else
    {
        // The algorithm was originally designed by Ben Dyer for Libuavcan v0:
        // https://github.com/OpenCyphal/libuavcan/blob/legacy-v0/libuavcan/src/marshal/uc_bit_array_copy.cpp
        // This version is modified for v1 where the bit order is the opposite.
        const uint8_t* const psrc = (const uint8_t*) src;
        uint8_t*       const pdst =       (uint8_t*) dst;
        {{ typename_unsigned_bit_length }}       src_off  = src_offset_bits;
        {{ typename_unsigned_bit_length }}       dst_off  = dst_offset_bits;
        const {{ typename_unsigned_bit_length }} last_bit = src_off + length_bits;
        {{ assert(
            '((psrc < pdst) ? ((uintptr_t)(psrc + ((src_offset_bits + length_bits + 8U) / 8U)) <= (uintptr_t)pdst) : 1)'
        ) }}
        {{ assert(
            '((psrc > pdst) ? ((uintptr_t)(pdst + ((dst_offset_bits + length_bits + 8U) / 8U)) <= (uintptr_t)psrc) : 1)'
        ) }}
        while (last_bit > src_off)
        {
            const uint8_t src_mod = (uint8_t)(src_off % 8U);
            const uint8_t dst_mod = (uint8_t)(dst_off % 8U);
            const uint8_t max_mod = (src_mod > dst_mod) ? src_mod : dst_mod;
            const uint8_t size = (uint8_t) nunavutChooseMin(8U - max_mod, last_bit - src_off);
            {{ assert('size > 0U') }}
            {{ assert('size <= 8U') }}
            // Suppress a false warning from Clang-Tidy & Sonar that size is being over-shifted. It's not.
            const uint8_t mask = (uint8_t)((((1U << size) - 1U) << dst_mod) & 0xFFU);  // NOLINT NOSONAR
            {{ assert('mask > 0U') }}
            // Intentional violation of MISRA: indexing on a pointer.
            // This simplifies the implementation greatly and avoids pointer arithmetics.
            const uint8_t in = (uint8_t)((uint8_t)(psrc[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 = pdst[dst_off / 8U] & ((uint8_t) ~mask);  // NOSONAR
            const uint8_t b = in & mask;
            // Intentional violation of MISRA: indexing on a pointer.
            // This simplifies the implementation greatly and avoids pointer arithmetics.
            pdst[dst_off / 8U] = a | b;  // NOSONAR
            src_off += size;
            dst_off += size;
        }
        {{ assert('last_bit == src_off') }}
    }
}

/// This function is intended for deserialization of contiguous sequences of zero-cost primitives.
/// It extracts (len_bits) bits that are offset by (off_bits) from the origin of (buf) whose size is (buf_size_bytes).
/// If the requested (len_bits+off_bits) overruns the buffer, the missing bits are implicitly zero-extended.
/// If (len_bits % 8 != 0), the output buffer is right-zero-padded up to the next byte boundary.
/// If (off_bits % 8 == 0), the operation is delegated to memmove(); otherwise, a much slower unaligned bit copy
/// algorithm is employed. See @ref nunavutCopyBits() for further details.
static inline void nunavutGetBits(void* const output,
                                  const void* const buf,
                                  const {{ typename_unsigned_bit_length }} buf_size_bytes,
                                  const {{ typename_unsigned_bit_length }} off_bits,
                                  const {{ typename_unsigned_bit_length }} len_bits)
{
    {{ assert('output != NULL') }}
    {{ assert('buf != NULL') }}
    const {{ typename_unsigned_bit_length }} sat_bits = nunavutSaturateBufferFragmentBitLength({# -#}
        buf_size_bytes, off_bits, len_bits);
    // Apply implicit zero extension. Normally, this is a no-op unless (len_bits > sat_bits) or (len_bits % 8 != 0).
    // The former case ensures that if we're copying <8 bits, the MSB in the destination will be zeroed out.
    (void) memset(((uint8_t*)output) + (sat_bits / 8U), 0, ((len_bits + 7U) / 8U) - (sat_bits / 8U));
    nunavutCopyBits(output, 0U, sat_bits, buf, off_bits);
}

// ---------------------------------------------------- INTEGER ----------------------------------------------------

/// Serialize a DSDL field value at the specified bit offset from the beginning of the destination buffer.
/// The behavior is undefined if the input pointer is NULL. The time complexity is linear of the bit length.
/// One-bit-wide signed integers are processed without raising an error but the result is unspecified.
///
/// Arguments:
///     buf             Destination buffer where the result will be stored.
///     buf_size_bytes  Size of the above, in bytes.
///     off_bits        Offset, in bits, from the beginning of the buffer. May exceed one byte.
///     value           The value itself (in case of integers it is promoted to 64-bit for unification).
///     len_bits        Length of the serialized representation, in bits. Zero has no effect. Values >64 bit saturated.

static inline {{typename_error_type}} nunavutSetBit(
    uint8_t* const buf,
    const {{ typename_unsigned_length }} buf_size_bytes,
    const {{ typename_unsigned_bit_length }} off_bits,
    const bool value)
{
    {{ assert('buf != NULL') }}
    if ((buf_size_bytes * 8) <= off_bits)
    {
        return -NUNAVUT_ERROR_SERIALIZATION_BUFFER_TOO_SMALL;
    }
    const uint8_t val = value ? 1U : 0U;
    nunavutCopyBits(buf, off_bits, 1U, &val, 0U);
    return NUNAVUT_SUCCESS;
}

static inline {{typename_error_type}} nunavutSetUxx(
    uint8_t* const buf,
    const {{ typename_unsigned_length }} buf_size_bytes,
    const {{ typename_unsigned_bit_length }} off_bits,
    const uint64_t value,
    const uint8_t len_bits)
{
    static_assert(64U == (sizeof(uint64_t) * 8U), "Unexpected size of uint64_t");
    {{ assert('buf != NULL') }}
    if ((buf_size_bytes * 8) < (off_bits + len_bits))
    {
        return -NUNAVUT_ERROR_SERIALIZATION_BUFFER_TOO_SMALL;
    }
    const {{ typename_unsigned_bit_length }} saturated_len_bits = nunavutChooseMin(len_bits, 64U);
{%- if options.target_endianness == 'little' %}
    nunavutCopyBits(buf, off_bits, saturated_len_bits, (const uint8_t*) &value, 0U);
{%- elif options.target_endianness in ('any', 'big') %}
    const uint8_t tmp[sizeof(uint64_t)] = {
        (uint8_t)((value >> 0U) & 0xFFU),
        (uint8_t)((value >> 8U) & 0xFFU),
        (uint8_t)((value >> 16U) & 0xFFU),
        (uint8_t)((value >> 24U) & 0xFFU),
        (uint8_t)((value >> 32U) & 0xFFU),
        (uint8_t)((value >> 40U) & 0xFFU),
        (uint8_t)((value >> 48U) & 0xFFU),
        (uint8_t)((value >> 56U) & 0xFFU),
    };
    nunavutCopyBits(buf, off_bits, saturated_len_bits, &tmp[0], 0U);
{%- else %}{%- assert False %}
{%- endif %}
    return NUNAVUT_SUCCESS;
}

static inline {{typename_error_type}} nunavutSetIxx(
    uint8_t* const buf,
    const {{ typename_unsigned_length }} buf_size_bytes,
    const {{ typename_unsigned_bit_length }} off_bits,
    const int64_t value,
    const uint8_t len_bits)
{
    // The naive sign conversion is safe and portable according to the C standard:
    // 6.3.1.3.3: if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more
    // than the maximum value that can be represented in the new type until the value is in the range of the new type.
    return nunavutSetUxx(buf, buf_size_bytes, off_bits, (uint64_t) value, len_bits);
}

/// Deserialize a DSDL field value located at the specified bit offset from the beginning of the source buffer.
/// If the deserialized value extends beyond the end of the buffer, the missing bits are taken as zero, as required
/// by the DSDL specification (see Implicit Zero Extension Rule, IZER).
///
/// If len_bits is greater than the return type, extra bits will be truncated per standard narrowing conversion rules.
/// If len_bits is shorter than the return type, missing bits will be zero per standard integer promotion rules.
/// Essentially, for integers, it would be enough to have 64-bit versions only; narrower variants exist only to avoid
/// narrowing type conversions of the result and for some performance gains.
///
/// The behavior is undefined if the input pointer is NULL. The time complexity is linear of the bit length.
/// One-bit-wide signed integers are processed without raising an error but the result is unspecified.
///
/// Arguments:
///     buf             Source buffer where the serialized representation will be read from.
///     buf_size_bytes  The size of the source buffer, in bytes. Reads past this limit will return zero bits.
///     off_bits        Offset, in bits, from the beginning of the buffer. May exceed one byte.
///     len_bits        Length of the serialized representation, in bits. Zero returns 0. Out-of-range values saturated.

static inline uint8_t nunavutGetU8(const uint8_t* const buf,
                                   const {{ typename_unsigned_length }} buf_size_bytes,
                                   const {{ typename_unsigned_bit_length }} off_bits,
                                   const uint8_t len_bits);

static inline bool nunavutGetBit(const uint8_t* const buf,
                                 const {{ typename_unsigned_length }} buf_size_bytes,
                                 const {{ typename_unsigned_bit_length }} off_bits)
{
    return 1U == nunavutGetU8(buf, buf_size_bytes, off_bits, 1U);
}

static inline uint8_t nunavutGetU8(const uint8_t* const buf,
                                   const {{ typename_unsigned_length }} buf_size_bytes,
                                   const {{ typename_unsigned_bit_length }} off_bits,
                                   const uint8_t len_bits)
{
    {{ assert('buf != NULL') }}
    const {{ typename_unsigned_bit_length }} bits = {# -#}
        nunavutSaturateBufferFragmentBitLength(buf_size_bytes, off_bits, nunavutChooseMin(len_bits, 8U));
    {{ assert('bits <= (sizeof(uint8_t) * 8U)') }}
    uint8_t val = 0;
    nunavutCopyBits(&val, 0U, bits, buf, off_bits);
    return val;
}

static inline uint16_t nunavutGetU16(const uint8_t* const buf,
                                     const {{ typename_unsigned_length }} buf_size_bytes,
                                     const {{ typename_unsigned_bit_length }} off_bits,
                                     const uint8_t len_bits)
{
    {{ assert('buf != NULL') }}
    const {{ typename_unsigned_bit_length }} bits = {# -#}
        nunavutSaturateBufferFragmentBitLength(buf_size_bytes, off_bits, nunavutChooseMin(len_bits, 16U));
    {{ assert('bits <= (sizeof(uint16_t) * 8U)') }}
{%- if options.target_endianness == 'little' %}
    uint16_t val = 0U;
    nunavutCopyBits(&val, 0U, bits, buf, off_bits);
    return val;
{%- elif options.target_endianness in ('any', 'big') %}
    uint8_t tmp[sizeof(uint16_t)] = {0};
    nunavutCopyBits(&tmp[0], 0U, bits, buf, off_bits);
    return (uint16_t)(tmp[0] | (uint16_t)(((uint16_t) tmp[1]) << 8U));
{%- else %}{%- assert False %}
{%- endif %}
}

static inline uint32_t nunavutGetU32(const uint8_t* const buf,
                                     const {{ typename_unsigned_length }} buf_size_bytes,
                                     const {{ typename_unsigned_bit_length }} off_bits,
                                     const uint8_t len_bits)
{
    {{ assert('buf != NULL') }}
    const {{ typename_unsigned_bit_length }} bits = {# -#}
        nunavutSaturateBufferFragmentBitLength(buf_size_bytes, off_bits, nunavutChooseMin(len_bits, 32U));
    {{ assert('bits <= (sizeof(uint32_t) * 8U)') }}
{%- if options.target_endianness == 'little' %}
    uint32_t val = 0U;
    nunavutCopyBits(&val, 0U, bits, buf, off_bits);
    return val;
{%- elif options.target_endianness in ('any', 'big') %}
    uint8_t tmp[sizeof(uint32_t)] = {0};
    nunavutCopyBits(&tmp[0], 0U, bits, buf, off_bits);
    return (uint32_t)(tmp[0] | ((uint32_t) tmp[1] << 8U) | ((uint32_t) tmp[2] << 16U) | ((uint32_t) tmp[3] << 24U));
{%- else %}{%- assert False %}
{%- endif %}
}

static inline uint64_t nunavutGetU64(const uint8_t* const buf,
                                     const {{ typename_unsigned_length }} buf_size_bytes,
                                     const {{ typename_unsigned_bit_length }} off_bits,
                                     const uint8_t len_bits)
{
    {{ assert('buf != NULL') }}
    const {{ typename_unsigned_bit_length }} bits = {# -#}
        nunavutSaturateBufferFragmentBitLength(buf_size_bytes, off_bits, nunavutChooseMin(len_bits, 64U));
    {{ assert('bits <= (sizeof(uint64_t) * 8U)') }}
{%- if options.target_endianness == 'little' %}
    uint64_t val = 0U;
    nunavutCopyBits(&val, 0U, bits, buf, off_bits);
    return val;
{%- elif options.target_endianness in ('any', 'big') %}
    uint8_t tmp[sizeof(uint64_t)] = {0};
    nunavutCopyBits(&tmp[0], 0U, bits, buf, off_bits);
    return (uint64_t)(tmp[0] |
                      ((uint64_t) tmp[1] << 8U) |
                      ((uint64_t) tmp[2] << 16U) |
                      ((uint64_t) tmp[3] << 24U) |
                      ((uint64_t) tmp[4] << 32U) |
                      ((uint64_t) tmp[5] << 40U) |
                      ((uint64_t) tmp[6] << 48U) |
                      ((uint64_t) tmp[7] << 56U));
{%- else %}{%- assert False %}
{%- endif %}
}

static inline int8_t nunavutGetI8(const uint8_t* const buf,
                                  const {{ typename_unsigned_length }} buf_size_bytes,
                                  const {{ typename_unsigned_bit_length }} off_bits,
                                  const uint8_t len_bits)
{
    const uint8_t sat = (uint8_t) nunavutChooseMin(len_bits, 8U);
    uint8_t       val = nunavutGetU8(buf, buf_size_bytes, off_bits, sat);
    const bool    neg = (sat > 0U) && ((val & (1ULL << (sat - 1U))) != 0U);
    val = ((sat < 8U) && neg) ? (uint8_t)(val | ~((1U << sat) - 1U)) : val;  // Sign extension
    return neg ? (int8_t)((-(int8_t)(uint8_t) ~val) - 1) : (int8_t) val;
}

static inline int16_t nunavutGetI16(const uint8_t* const buf,
                                    const {{ typename_unsigned_length }} buf_size_bytes,
                                    const {{ typename_unsigned_bit_length }} off_bits,
                                    const uint8_t len_bits)
{
    const uint8_t sat = (uint8_t) nunavutChooseMin(len_bits, 16U);
    uint16_t      val = nunavutGetU16(buf, buf_size_bytes, off_bits, sat);
    const bool    neg = (sat > 0U) && ((val & (1ULL << (sat - 1U))) != 0U);
    val = ((sat < 16U) && neg) ? (uint16_t)(val | ~((1U << sat) - 1U)) : val;  // Sign extension
    return neg ? (int16_t)((-(int16_t)(uint16_t) ~val) - 1) : (int16_t) val;
}

static inline int32_t nunavutGetI32(const uint8_t* const buf,
                                    const {{ typename_unsigned_length }} buf_size_bytes,
                                    const {{ typename_unsigned_bit_length }} off_bits,
                                    const uint8_t len_bits)
{
    const uint8_t sat = (uint8_t) nunavutChooseMin(len_bits, 32U);
    uint32_t      val = nunavutGetU32(buf, buf_size_bytes, off_bits, sat);
    const bool    neg = (sat > 0U) && ((val & (1ULL << (sat - 1U))) != 0U);
    val = ((sat < 32U) && neg) ? (uint32_t)(val | ~((1UL << sat) - 1U)) : val;  // Sign extension
    return neg ? (int32_t)((-(int32_t) ~val) - 1) : (int32_t) val;
}

static inline int64_t nunavutGetI64(const uint8_t* const buf,
                                    const {{ typename_unsigned_length }} buf_size_bytes,
                                    const {{ typename_unsigned_bit_length }} off_bits,
                                    const uint8_t len_bits)
{
    const uint8_t sat = (uint8_t) nunavutChooseMin(len_bits, 64U);
    uint64_t      val = nunavutGetU64(buf, buf_size_bytes, off_bits, sat);
    const bool    neg = (sat > 0U) && ((val & (1ULL << (sat - 1U))) != 0U);
    val = ((sat < 64U) && neg) ? (uint64_t)(val | ~((1ULL << sat) - 1U)) : val;  // Sign extension
    return neg ? (int64_t)((-(int64_t) ~val) - 1) : (int64_t) val;
}

{%- if not options.omit_float_serialization_support %}

// ---------------------------------------------------- FLOAT16 ----------------------------------------------------

static_assert(NUNAVUT_PLATFORM_IEEE754_FLOAT,
              "The target platform does not support IEEE754 floating point operations.");
static_assert(32U == (sizeof({{typename_float_32}}) * 8U), "Unsupported floating point model");

/// Converts a single-precision float into the binary representation of the value as a half-precision IEEE754 value.
static inline uint16_t nunavutFloat16Pack(const {{typename_float_32}} value)
{
    {{ float32_union() }}

    // The no-lint statements suppress the warning about the use of union. This is required for low-level bit access.
    const uint32_t round_mask = ~(uint32_t) 0x0FFFU;
    Float32Bits    f32inf;  // NOSONAR
    Float32Bits    f16inf;  // NOSONAR
    Float32Bits    magic;   // NOSONAR
    Float32Bits    in;      // NOSONAR
    f32inf.bits = ((uint32_t) 255U) << 23U;
    f16inf.bits = ((uint32_t) 31U) << 23U;
    magic.bits = ((uint32_t) 15U) << 23U;
    in.real = value;
    const uint32_t sign = in.bits & (((uint32_t) 1U) << 31U);
    in.bits ^= sign;
    uint16_t out = 0;
    if (in.bits >= f32inf.bits)
    {
        if ((in.bits & 0x7FFFFFUL) != 0)
        {
            out = 0x7E00U;
        }
        else
        {
            out = (in.bits > f32inf.bits) ? (uint16_t) 0x7FFFU : (uint16_t) 0x7C00U;
        }
    }
    else
    {
        in.bits &= round_mask;
        in.real *= magic.real;
        in.bits -= round_mask;
        if (in.bits > f16inf.bits)
        {
            in.bits = f16inf.bits;
        }
        out = (uint16_t)(in.bits >> 13U);
    }
    out |= (uint16_t)(sign >> 16U);
    return out;
}

static inline {{typename_float_32}} nunavutFloat16Unpack(const uint16_t value)
{
    {{ float32_union() }}

    // The no-lint statements suppress the warning about the use of union. This is required for low-level bit access.
    Float32Bits magic;    // NOSONAR
    Float32Bits inf_nan;  // NOSONAR
    Float32Bits out;      // NOSONAR
    magic.bits = ((uint32_t) 0xEFU) << 23U;
    inf_nan.bits = ((uint32_t) 0x8FU) << 23U;
    out.bits = ((uint32_t)(value & 0x7FFFU)) << 13U;
    out.real *= magic.real;
    if (out.real >= inf_nan.real)
    {
        out.bits |= ((uint32_t) 0xFFU) << 23U;
    }
    out.bits |= ((uint32_t)(value & 0x8000U)) << 16U;
    return out.real;
}

static inline {{typename_error_type}} nunavutSetF16(
    uint8_t* const buf,
    const {{ typename_unsigned_length }} buf_size_bytes,
    const {{ typename_unsigned_bit_length }} off_bits,
    const {{ typename_float_32 }} value)
{
    return nunavutSetUxx(buf, buf_size_bytes, off_bits, nunavutFloat16Pack(value), 16U);
}

static inline {{typename_float_32}} nunavutGetF16(
    const uint8_t* const buf,
    const {{ typename_unsigned_length }} buf_size_bytes,
    const {{ typename_unsigned_bit_length }} off_bits)
{
    return nunavutFloat16Unpack(nunavutGetU16(buf, buf_size_bytes, off_bits, 16U));
}

// ---------------------------------------------------- FLOAT32 ----------------------------------------------------

static_assert(NUNAVUT_PLATFORM_IEEE754_FLOAT,
              "The target platform does not support IEEE754 floating point operations.");
static_assert(32U == (sizeof({{typename_float_32}}) * 8U), "Unsupported floating point model");

static inline {{typename_error_type}} nunavutSetF32(
    uint8_t* const buf,
    const {{ typename_unsigned_length }} buf_size_bytes,
    const {{ typename_unsigned_bit_length }} off_bits,
    const {{ typename_float_32 }} value)
{
    // Intentional violation of MISRA: use union to perform fast conversion from an IEEE 754-compatible native
    // representation into a serializable integer. The assumptions about the target platform properties are made
    // clear. In the future we may add a more generic conversion that is platform-invariant.
    union  // NOSONAR
    {
        {{typename_float_32}} fl;
        uint32_t in;
    } const tmp = {value};  // NOSONAR
    return nunavutSetUxx(buf, buf_size_bytes, off_bits, tmp.in, sizeof(tmp) * 8U);
}

static inline {{typename_float_32}} nunavutGetF32(
    const uint8_t* const buf,
    const {{ typename_unsigned_length }} buf_size_bytes,
    const {{ typename_unsigned_bit_length }} off_bits)
{
    // Intentional violation of MISRA: use union to perform fast conversion to an IEEE 754-compatible native
    // representation into a serializable integer. The assumptions about the target platform properties are made
    // clear. In the future we may add a more generic conversion that is platform-invariant.
    union  // NOSONAR
    {
        uint32_t in;
        {{typename_float_32}} fl;
    } const tmp = {nunavutGetU32(buf, buf_size_bytes, off_bits, 32U)};
    return tmp.fl;
}

// ---------------------------------------------------- FLOAT64 ----------------------------------------------------

static_assert(NUNAVUT_PLATFORM_IEEE754_DOUBLE,
              "The target platform does not support IEEE754 double-precision floating point operations.");
static_assert(64U == (sizeof({{typename_float_64}}) * 8U), "Unsupported floating point model");

static inline {{typename_error_type}} nunavutSetF64(
    uint8_t* const buf,
    const {{ typename_unsigned_length }} buf_size_bytes,
    const {{ typename_unsigned_bit_length }} off_bits,
    const {{typename_float_64 }} value)
{
    // Intentional violation of MISRA: use union to perform fast conversion from an IEEE 754-compatible native
    // representation into a serializable integer. The assumptions about the target platform properties are made
    // clear. In the future we may add a more generic conversion that is platform-invariant.
    union  // NOSONAR
    {
        {{typename_float_64}} fl;
        uint64_t in;
    } const tmp = {value};  // NOSONAR
    return nunavutSetUxx(buf, buf_size_bytes, off_bits, tmp.in, sizeof(tmp) * 8U);
}

static inline {{typename_float_64}} nunavutGetF64(
    const uint8_t* const buf,
    const {{ typename_unsigned_length }} buf_size_bytes,
    const {{ typename_unsigned_bit_length }} off_bits)
{
    // Intentional violation of MISRA: use union to perform fast conversion to an IEEE 754-compatible native
    // representation into a serializable integer. The assumptions about the target platform properties are made
    // clear. In the future we may add a more generic conversion that is platform-invariant.
    union  // NOSONAR
    {
        uint64_t in;
        {{typename_float_64}} fl;
    } const tmp = {nunavutGetU64(buf, buf_size_bytes, off_bits, 64U)};
    return tmp.fl;
}

{% endif -%}

#ifdef __cplusplus
}
#endif

#endif // NUNAVUT_SUPPORT_SERIALIZATION_H_INCLUDED
