diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index 1c6a704..7fdd1a0 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -64,9 +64,9 @@ if (${ZSERIO_CODE_COVERAGE_ENABLE}) if (${ZSERIO_CODE_COVERAGE_FAIL_ON_INCOMPLETE}) list(APPEND COV_PARAMS "INCOMPLETE_COVERAGE_FAIL") if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - list(APPEND COV_PARAMS "99.3") + list(APPEND COV_PARAMS "99.5") else () - list(APPEND COV_PARAMS "99.3") + list(APPEND COV_PARAMS "99.5") endif () endif () #list(APPEND COV_PARAMS "EXCLUDE_SOURCES") diff --git a/runtime/ClangTidySuppressions.txt b/runtime/ClangTidySuppressions.txt index 88acf80..dba13ab 100644 --- a/runtime/ClangTidySuppressions.txt +++ b/runtime/ClangTidySuppressions.txt @@ -14,6 +14,21 @@ cppcoreguidelines-pro-bounds-array-to-pointer-decay:src/zserio/Span.h:113 # This is necessary for convenient usage of CppRuntimeException cppcoreguidelines-pro-bounds-array-to-pointer-decay:src/zserio/CppRuntimeException.h:193 +# The following is filtered out because bounds are checked naturally by implementation. Therefore method 'at' +# would only bring the performance drop. +cppcoreguidelines-pro-bounds-constant-array-index:src/zserio/BitStreamReader.cpp:301 +cppcoreguidelines-pro-bounds-constant-array-index:src/zserio/BitStreamReader.cpp:315 +cppcoreguidelines-pro-bounds-constant-array-index:src/zserio/BitStreamWriter.cpp:357 +cppcoreguidelines-pro-bounds-constant-array-index:src/zserio/BitStreamWriter.cpp:368 +cppcoreguidelines-pro-bounds-constant-array-index:src/zserio/BitStreamWriter.cpp:379 +cppcoreguidelines-pro-bounds-constant-array-index:src/zserio/BitStreamWriter.cpp:380 +cppcoreguidelines-pro-bounds-constant-array-index:src/zserio/BitStreamWriter.cpp:386 +cppcoreguidelines-pro-bounds-constant-array-index:src/zserio/BitStreamWriter.cpp:391 +cppcoreguidelines-pro-bounds-constant-array-index:src/zserio/BitStreamWriter.cpp:392 +cppcoreguidelines-pro-bounds-constant-array-index:src/zserio/BitStreamWriter.cpp:398 +cppcoreguidelines-pro-bounds-constant-array-index:src/zserio/BitStreamWriter.cpp:594 +cppcoreguidelines-pro-bounds-constant-array-index:src/zserio/BitStreamWriter.cpp:662 + # The following is filtered out because bounds are checked naturally by implementation. Therefore method 'at' # would only bring the performance drop. cppcoreguidelines-pro-bounds-constant-array-index:src/zserio/StringConvertUtil.h:43 @@ -22,6 +37,9 @@ cppcoreguidelines-pro-bounds-constant-array-index:src/zserio/StringConvertUtil.h cppcoreguidelines-pro-bounds-constant-array-index:src/zserio/StringConvertUtil.h:54 cppcoreguidelines-pro-bounds-constant-array-index:src/zserio/StringConvertUtil.h:55 +# This is false positive, the member is initialized. +cppcoreguidelines-pro-type-member-init:src/zserio/BitStreamWriter.h:71 + # This is necessary for implementation of reading and writing to the file. cppcoreguidelines-pro-type-reinterpret-cast:src/zserio/FileUtil.cpp:18 cppcoreguidelines-pro-type-reinterpret-cast:src/zserio/FileUtil.cpp:48 diff --git a/runtime/src/CMakeLists.txt b/runtime/src/CMakeLists.txt index 188958e..e6576d0 100644 --- a/runtime/src/CMakeLists.txt +++ b/runtime/src/CMakeLists.txt @@ -22,6 +22,12 @@ set(ZSERIO_CPP17_RUNTIME_LIB_SRCS zserio/BitFieldUtil.cpp zserio/BitPositionUtil.h zserio/BitFieldUtil.h + zserio/BitSizeOfCalculator.cpp + zserio/BitSizeOfCalculator.h + zserio/BitStreamReader.cpp + zserio/BitStreamReader.h + zserio/BitStreamWriter.cpp + zserio/BitStreamWriter.h zserio/BuiltInOperators.cpp zserio/BuiltInOperators.h zserio/CppRuntimeException.cpp diff --git a/runtime/src/zserio/BitSizeOfCalculator.cpp b/runtime/src/zserio/BitSizeOfCalculator.cpp new file mode 100644 index 0000000..a6781bf --- /dev/null +++ b/runtime/src/zserio/BitSizeOfCalculator.cpp @@ -0,0 +1,183 @@ +#include +#include + +#include "zserio/BitSizeOfCalculator.h" +#include "zserio/CppRuntimeException.h" + +namespace zserio +{ + +static const std::array VARIN16_MAX_VALUES = { + (UINT64_C(1) << (6)) - 1, + (UINT64_C(1) << (6 + 8)) - 1, +}; + +static const std::array VARINT32_MAX_VALUES = { + (UINT64_C(1) << (6)) - 1, + (UINT64_C(1) << (6 + 7)) - 1, + (UINT64_C(1) << (6 + 7 + 7)) - 1, + (UINT64_C(1) << (6 + 7 + 7 + 8)) - 1, +}; + +static const std::array VARINT64_MAX_VALUES = { + (UINT64_C(1) << (6)) - 1, + (UINT64_C(1) << (6 + 7)) - 1, + (UINT64_C(1) << (6 + 7 + 7)) - 1, + (UINT64_C(1) << (6 + 7 + 7 + 7)) - 1, + (UINT64_C(1) << (6 + 7 + 7 + 7 + 7)) - 1, + (UINT64_C(1) << (6 + 7 + 7 + 7 + 7 + 7)) - 1, + (UINT64_C(1) << (6 + 7 + 7 + 7 + 7 + 7 + 7)) - 1, + (UINT64_C(1) << (6 + 7 + 7 + 7 + 7 + 7 + 7 + 8)) - 1, +}; + +static const std::array VARUINT16_MAX_VALUES = { + (UINT64_C(1) << (7)) - 1, + (UINT64_C(1) << (7 + 8)) - 1, +}; + +static const std::array VARUINT32_MAX_VALUES = { + (UINT64_C(1) << (7)) - 1, + (UINT64_C(1) << (7 + 7)) - 1, + (UINT64_C(1) << (7 + 7 + 7)) - 1, + (UINT64_C(1) << (7 + 7 + 7 + 8)) - 1, +}; + +static const std::array VARUINT64_MAX_VALUES = { + (UINT64_C(1) << (7)) - 1, + (UINT64_C(1) << (7 + 7)) - 1, + (UINT64_C(1) << (7 + 7 + 7)) - 1, + (UINT64_C(1) << (7 + 7 + 7 + 7)) - 1, + (UINT64_C(1) << (7 + 7 + 7 + 7 + 7)) - 1, + (UINT64_C(1) << (7 + 7 + 7 + 7 + 7 + 7)) - 1, + (UINT64_C(1) << (7 + 7 + 7 + 7 + 7 + 7 + 7)) - 1, + (UINT64_C(1) << (7 + 7 + 7 + 7 + 7 + 7 + 7 + 8)) - 1, +}; + +static const std::array VARINT_MAX_VALUES = { + (UINT64_C(1) << (6)) - 1, + (UINT64_C(1) << (6 + 7)) - 1, + (UINT64_C(1) << (6 + 7 + 7)) - 1, + (UINT64_C(1) << (6 + 7 + 7 + 7)) - 1, + (UINT64_C(1) << (6 + 7 + 7 + 7 + 7)) - 1, + (UINT64_C(1) << (6 + 7 + 7 + 7 + 7 + 7)) - 1, + (UINT64_C(1) << (6 + 7 + 7 + 7 + 7 + 7 + 7)) - 1, + (UINT64_C(1) << (6 + 7 + 7 + 7 + 7 + 7 + 7 + 7)) - 1, + (UINT64_C(1) << (6 + 7 + 7 + 7 + 7 + 7 + 7 + 7 + 8)) - 1, +}; + +static const std::array VARUINT_MAX_VALUES = { + (UINT64_C(1) << (7)) - 1, + (UINT64_C(1) << (7 + 7)) - 1, + (UINT64_C(1) << (7 + 7 + 7)) - 1, + (UINT64_C(1) << (7 + 7 + 7 + 7)) - 1, + (UINT64_C(1) << (7 + 7 + 7 + 7 + 7)) - 1, + (UINT64_C(1) << (7 + 7 + 7 + 7 + 7 + 7)) - 1, + (UINT64_C(1) << (7 + 7 + 7 + 7 + 7 + 7 + 7)) - 1, + (UINT64_C(1) << (7 + 7 + 7 + 7 + 7 + 7 + 7 + 7)) - 1, + UINT64_MAX, +}; + +static const std::array VARSIZE_MAX_VALUES = { + (UINT64_C(1) << (7)) - 1, + (UINT64_C(1) << (7 + 7)) - 1, + (UINT64_C(1) << (7 + 7 + 7)) - 1, + (UINT64_C(1) << (7 + 7 + 7 + 7)) - 1, + (UINT64_C(1) << (2 + 7 + 7 + 7 + 8)) - 1, +}; + +template +static size_t bitSizeOfVarIntImpl( + uint64_t value, const std::array& maxValues, const char* varIntName) +{ + size_t byteSize = 1; + for (uint64_t maxValue : maxValues) + { + if (value <= maxValue) + { + break; + } + byteSize++; + } + + if (byteSize > maxValues.size()) + { + throw CppRuntimeException("BitSizeOfCalculator: Value '") + << value << "' is out of range for " << varIntName << "!"; + } + + return byteSize * 8; +} + +template +static uint64_t convertToAbsValue(T value) +{ + return static_cast((value < 0) ? -value : value); +} + +size_t bitSizeOfVarInt16(int16_t value) +{ + return bitSizeOfVarIntImpl(convertToAbsValue(value), VARIN16_MAX_VALUES, "varint16"); +} + +size_t bitSizeOfVarInt32(int32_t value) +{ + return bitSizeOfVarIntImpl(convertToAbsValue(value), VARINT32_MAX_VALUES, "varint32"); +} + +size_t bitSizeOfVarInt64(int64_t value) +{ + return bitSizeOfVarIntImpl(convertToAbsValue(value), VARINT64_MAX_VALUES, "varint64"); +} + +size_t bitSizeOfVarUInt16(uint16_t value) +{ + return bitSizeOfVarIntImpl(value, VARUINT16_MAX_VALUES, "varuint16"); +} + +size_t bitSizeOfVarUInt32(uint32_t value) +{ + return bitSizeOfVarIntImpl(value, VARUINT32_MAX_VALUES, "varuint32"); +} + +size_t bitSizeOfVarUInt64(uint64_t value) +{ + return bitSizeOfVarIntImpl(value, VARUINT64_MAX_VALUES, "varuint64"); +} + +size_t bitSizeOfVarInt(int64_t value) +{ + if (value == INT64_MIN) + { + return 8; // INT64_MIN is stored as -0 + } + + return bitSizeOfVarIntImpl(convertToAbsValue(value), VARINT_MAX_VALUES, "varint"); +} + +size_t bitSizeOfVarUInt(uint64_t value) +{ + return bitSizeOfVarIntImpl(value, VARUINT_MAX_VALUES, "varuint"); +} + +size_t bitSizeOfVarSize(uint32_t value) +{ + return bitSizeOfVarIntImpl(value, VARSIZE_MAX_VALUES, "varsize"); +} + +size_t bitSizeOfBytes(Span bytesValue) +{ + const size_t bytesSize = bytesValue.size(); + + // the bytes consists of varsize for size followed by the bytes + return bitSizeOfVarSize(convertSizeToUInt32(bytesSize)) + bytesSize * 8; +} + +size_t bitSizeOfString(std::string_view stringValue) +{ + const size_t stringSize = stringValue.size(); + + // the string consists of varsize for size followed by the UTF-8 encoded string + return bitSizeOfVarSize(convertSizeToUInt32(stringSize)) + stringSize * 8; +} + +} // namespace zserio diff --git a/runtime/src/zserio/BitSizeOfCalculator.h b/runtime/src/zserio/BitSizeOfCalculator.h new file mode 100644 index 0000000..971baf5 --- /dev/null +++ b/runtime/src/zserio/BitSizeOfCalculator.h @@ -0,0 +1,134 @@ +#ifndef ZSERIO_BITSIZEOF_CALCULATOR_H_INC +#define ZSERIO_BITSIZEOF_CALCULATOR_H_INC + +#include +#include +#include + +#include "zserio/BitBuffer.h" +#include "zserio/BitPositionUtil.h" +#include "zserio/SizeConvertUtil.h" +#include "zserio/Span.h" +#include "zserio/Types.h" + +namespace zserio +{ + +/** + * Calculates bit size of Zserio varint16 type. + * + * \param value Varint16 value. + * + * \return Bit size of the current varint16 value. + */ +size_t bitSizeOfVarInt16(int16_t value); + +/** + * Calculates bit size of Zserio varint32 type. + * + * \param value Varint32 value. + * + * \return Bit size of the current varint32 value. + */ +size_t bitSizeOfVarInt32(int32_t value); + +/** + * Calculates bit size of Zserio varint64 type. + * + * \param value Varint64 value. + * + * \return Bit size of the current varint64 value. + */ +size_t bitSizeOfVarInt64(int64_t value); + +/** + * Calculates bit size of Zserio varuint16 type. + * + * \param value Varuint16 value. + * + * \return Bit size of the current varuint16 value. + */ +size_t bitSizeOfVarUInt16(uint16_t value); + +/** + * Calculates bit size of Zserio varuint32 type. + * + * \param value Varuint32 value. + * + * \return Bit size of the current varuint32 value. + */ +size_t bitSizeOfVarUInt32(uint32_t value); + +/** + * Calculates bit size of Zserio varuint64 type. + * + * \param value Varuint64 value. + * + * \return Bit size of the current varuint64 value. + */ +size_t bitSizeOfVarUInt64(uint64_t value); + +/** + * Calculates bit size of Zserio varint type. + * + * \param value Varint value. + * + * \return Bit size of the current varint value. + */ +size_t bitSizeOfVarInt(int64_t value); + +/** + * Calculates bit size of Zserio varuint type. + * + * \param value Varuint value. + * + * \return Bit size of the current varuint value. + */ +size_t bitSizeOfVarUInt(uint64_t value); + +/** + * Calculates bit size of Zserio varsize type. + * + * \param value Varsize value. + * + * \return Bit size of the current varsize value. + */ +size_t bitSizeOfVarSize(uint32_t value); + +/** + * Calculates bit size of bytes. + * + * \param bytesValue Span representing the bytes value. + * + * \return Bit size of the given bytes value. + */ +size_t bitSizeOfBytes(Span bytesValue); + +/** + * Calculates bit size of the string. + * + * \param stringValue String view for which to calculate bit size. + * + * \return Bit size of the given string. + */ +size_t bitSizeOfString(std::string_view stringValue); + +/** + * Calculates bit size of the bit buffer. + * + * \param bitBuffer Bit buffer for which to calculate bit size. + * + * \return Bit size of the given bit buffer. + */ +template +size_t bitSizeOfBitBuffer(const BasicBitBuffer& bitBuffer) +{ + const size_t bitBufferSize = bitBuffer.getBitSize(); + + // bit buffer consists of varsize for bit size followed by the bits + return bitSizeOfVarSize(convertSizeToUInt32(bitBufferSize)) + bitBufferSize; +} + +} // namespace zserio + +#endif // ifndef ZSERIO_BITSIZEOF_CALCULATOR_H_INC diff --git a/runtime/src/zserio/BitStreamReader.cpp b/runtime/src/zserio/BitStreamReader.cpp new file mode 100644 index 0000000..c596d61 --- /dev/null +++ b/runtime/src/zserio/BitStreamReader.cpp @@ -0,0 +1,873 @@ +#include +#include + +#include "zserio/BitStreamReader.h" +#include "zserio/CppRuntimeException.h" +#include "zserio/FloatUtil.h" +#include "zserio/RuntimeArch.h" + +namespace zserio +{ + +namespace +{ + +// max size calculated to prevent overflows in internal comparisons +const size_t MAX_BUFFER_SIZE = std::numeric_limits::max() / 8 - 4; + +using BitPosType = BitStreamReader::BitPosType; +using ReaderContext = BitStreamReader::ReaderContext; + +#ifdef ZSERIO_RUNTIME_64BIT +using BaseType = uint64_t; +using BaseSignedType = int64_t; +#else +using BaseType = uint32_t; +using BaseSignedType = int32_t; +#endif + +#ifdef ZSERIO_RUNTIME_64BIT +const std::array MASK_TABLE = { + UINT64_C(0x00), + UINT64_C(0x0001), + UINT64_C(0x0003), + UINT64_C(0x0007), + UINT64_C(0x000f), + UINT64_C(0x001f), + UINT64_C(0x003f), + UINT64_C(0x007f), + UINT64_C(0x00ff), + UINT64_C(0x01ff), + UINT64_C(0x03ff), + UINT64_C(0x07ff), + UINT64_C(0x0fff), + UINT64_C(0x1fff), + UINT64_C(0x3fff), + UINT64_C(0x7fff), + UINT64_C(0xffff), + UINT64_C(0x0001ffff), + UINT64_C(0x0003ffff), + UINT64_C(0x0007ffff), + UINT64_C(0x000fffff), + UINT64_C(0x001fffff), + UINT64_C(0x003fffff), + UINT64_C(0x007fffff), + UINT64_C(0x00ffffff), + UINT64_C(0x01ffffff), + UINT64_C(0x03ffffff), + UINT64_C(0x07ffffff), + UINT64_C(0x0fffffff), + UINT64_C(0x1fffffff), + UINT64_C(0x3fffffff), + UINT64_C(0x7fffffff), + UINT64_C(0xffffffff), + + UINT64_C(0x00000001ffffffff), + UINT64_C(0x00000003ffffffff), + UINT64_C(0x00000007ffffffff), + UINT64_C(0x0000000fffffffff), + UINT64_C(0x0000001fffffffff), + UINT64_C(0x0000003fffffffff), + UINT64_C(0x0000007fffffffff), + UINT64_C(0x000000ffffffffff), + UINT64_C(0x000001ffffffffff), + UINT64_C(0x000003ffffffffff), + UINT64_C(0x000007ffffffffff), + UINT64_C(0x00000fffffffffff), + UINT64_C(0x00001fffffffffff), + UINT64_C(0x00003fffffffffff), + UINT64_C(0x00007fffffffffff), + UINT64_C(0x0000ffffffffffff), + UINT64_C(0x0001ffffffffffff), + UINT64_C(0x0003ffffffffffff), + UINT64_C(0x0007ffffffffffff), + UINT64_C(0x000fffffffffffff), + UINT64_C(0x001fffffffffffff), + UINT64_C(0x003fffffffffffff), + UINT64_C(0x007fffffffffffff), + UINT64_C(0x00ffffffffffffff), + UINT64_C(0x01ffffffffffffff), + UINT64_C(0x03ffffffffffffff), + UINT64_C(0x07ffffffffffffff), + UINT64_C(0x0fffffffffffffff), + UINT64_C(0x1fffffffffffffff), + UINT64_C(0x3fffffffffffffff), + UINT64_C(0x7fffffffffffffff), + UINT64_C(0xffffffffffffffff), +}; +#else +const std::array MASK_TABLE = { + UINT32_C(0x00), + UINT32_C(0x0001), + UINT32_C(0x0003), + UINT32_C(0x0007), + UINT32_C(0x000f), + UINT32_C(0x001f), + UINT32_C(0x003f), + UINT32_C(0x007f), + UINT32_C(0x00ff), + UINT32_C(0x01ff), + UINT32_C(0x03ff), + UINT32_C(0x07ff), + UINT32_C(0x0fff), + UINT32_C(0x1fff), + UINT32_C(0x3fff), + UINT32_C(0x7fff), + UINT32_C(0xffff), + UINT32_C(0x0001ffff), + UINT32_C(0x0003ffff), + UINT32_C(0x0007ffff), + UINT32_C(0x000fffff), + UINT32_C(0x001fffff), + UINT32_C(0x003fffff), + UINT32_C(0x007fffff), + UINT32_C(0x00ffffff), + UINT32_C(0x01ffffff), + UINT32_C(0x03ffffff), + UINT32_C(0x07ffffff), + UINT32_C(0x0fffffff), + UINT32_C(0x1fffffff), + UINT32_C(0x3fffffff), + UINT32_C(0x7fffffff), + UINT32_C(0xffffffff), +}; +#endif + +const uint8_t VARINT_SIGN_1 = UINT8_C(0x80); +const uint8_t VARINT_BYTE_1 = UINT8_C(0x3f); +const uint8_t VARINT_BYTE_N = UINT8_C(0x7f); +const uint8_t VARINT_HAS_NEXT_1 = UINT8_C(0x40); +const uint8_t VARINT_HAS_NEXT_N = UINT8_C(0x80); + +const uint8_t VARUINT_BYTE = UINT8_C(0x7f); +const uint8_t VARUINT_HAS_NEXT = UINT8_C(0x80); + +const uint32_t VARSIZE_MAX_VALUE = (UINT32_C(1) << 31U) - 1; + +#ifdef ZSERIO_RUNTIME_64BIT +inline BaseType parse64(Span::const_iterator bufferIt) +{ + return static_cast(*bufferIt) << 56U | static_cast(*(bufferIt + 1)) << 48U | + static_cast(*(bufferIt + 2)) << 40U | static_cast(*(bufferIt + 3)) << 32U | + static_cast(*(bufferIt + 4)) << 24U | static_cast(*(bufferIt + 5)) << 16U | + static_cast(*(bufferIt + 6)) << 8U | static_cast(*(bufferIt + 7)); +} + +inline BaseType parse56(Span::const_iterator bufferIt) +{ + return static_cast(*bufferIt) << 48U | static_cast(*(bufferIt + 1)) << 40U | + static_cast(*(bufferIt + 2)) << 32U | static_cast(*(bufferIt + 3)) << 24U | + static_cast(*(bufferIt + 4)) << 16U | static_cast(*(bufferIt + 5)) << 8U | + static_cast(*(bufferIt + 6)); +} + +inline BaseType parse48(Span::const_iterator bufferIt) +{ + return static_cast(*bufferIt) << 40U | static_cast(*(bufferIt + 1)) << 32U | + static_cast(*(bufferIt + 2)) << 24U | static_cast(*(bufferIt + 3)) << 16U | + static_cast(*(bufferIt + 4)) << 8U | static_cast(*(bufferIt + 5)); +} + +inline BaseType parse40(Span::const_iterator bufferIt) +{ + return static_cast(*bufferIt) << 32U | static_cast(*(bufferIt + 1)) << 24U | + static_cast(*(bufferIt + 2)) << 16U | static_cast(*(bufferIt + 3)) << 8U | + static_cast(*(bufferIt + 4)); +} +#endif +inline BaseType parse32(Span::const_iterator bufferIt) +{ + return static_cast(*bufferIt) << 24U | static_cast(*(bufferIt + 1)) << 16U | + static_cast(*(bufferIt + 2)) << 8U | static_cast(*(bufferIt + 3)); +} + +inline BaseType parse24(Span::const_iterator bufferIt) +{ + return static_cast(*bufferIt) << 16U | static_cast(*(bufferIt + 1)) << 8U | + static_cast(*(bufferIt + 2)); +} + +inline BaseType parse16(Span::const_iterator bufferIt) +{ + return static_cast(*bufferIt) << 8U | static_cast(*(bufferIt + 1)); +} + +inline BaseType parse8(Span::const_iterator bufferIt) +{ + return static_cast(*bufferIt); +} + +/** Optimization which increases chances to inline checkNumBits and checkNumBits64. */ +inline void throwNumBitsIsNotValid(uint8_t numBits) +{ + throw CppRuntimeException("BitStreamReader: ReadBits #") + << numBits << " is not valid, reading from stream failed!"; +} + +/** Checks numBits validity for 32-bit reads. */ +inline void checkNumBits(uint8_t numBits) +{ + if (numBits == 0 || numBits > 32) + { + throwNumBitsIsNotValid(numBits); + } +} + +/** Checks numBits validity for 64-bit reads. */ +inline void checkNumBits64(uint8_t numBits) +{ + if (numBits == 0 || numBits > 64) + { + throwNumBitsIsNotValid(numBits); + } +} + +/** Optimization which increases chances to inline loadCacheNext. */ +inline void throwEof() +{ + throw CppRuntimeException("BitStreamReader: Reached eof(), reading from stream failed!"); +} + +/** Loads next 32/64 bits to 32/64 bit-cache. */ +inline void loadCacheNext(ReaderContext& ctx, uint8_t numBits) +{ + static const uint8_t cacheBitSize = sizeof(BaseType) * 8; + + // ctx.bitIndex is always byte aligned and ctx.cacheNumBits is always zero in this call + const size_t byteIndex = ctx.bitIndex >> 3U; + if (ctx.bufferBitSize >= ctx.bitIndex + cacheBitSize) + { + ctx.cache = +#ifdef ZSERIO_RUNTIME_64BIT + parse64(ctx.buffer.begin() + byteIndex); +#else + parse32(ctx.buffer.begin() + byteIndex); +#endif + ctx.cacheNumBits = cacheBitSize; + } + else + { + if (ctx.bitIndex + numBits > ctx.bufferBitSize) + { + throwEof(); + } + + ctx.cacheNumBits = static_cast(ctx.bufferBitSize - ctx.bitIndex); + + // buffer must be always available in full bytes, even if some last bits are not used + const uint8_t alignedNumBits = static_cast((ctx.cacheNumBits + 7U) & ~0x7U); + + switch (alignedNumBits) + { +#ifdef ZSERIO_RUNTIME_64BIT + case 64: + ctx.cache = parse64(ctx.buffer.begin() + byteIndex); + break; + case 56: + ctx.cache = parse56(ctx.buffer.begin() + byteIndex); + break; + case 48: + ctx.cache = parse48(ctx.buffer.begin() + byteIndex); + break; + case 40: + ctx.cache = parse40(ctx.buffer.begin() + byteIndex); + break; +#endif + case 32: + ctx.cache = parse32(ctx.buffer.begin() + byteIndex); + break; + case 24: + ctx.cache = parse24(ctx.buffer.begin() + byteIndex); + break; + case 16: + ctx.cache = parse16(ctx.buffer.begin() + byteIndex); + break; + default: // 8 + ctx.cache = parse8(ctx.buffer.begin() + byteIndex); + break; + } + + ctx.cache >>= static_cast(alignedNumBits - ctx.cacheNumBits); + } +} + +/** Unchecked implementation of readBits. */ +inline BaseType readBitsImpl(ReaderContext& ctx, uint8_t numBits) +{ + BaseType value = 0; + if (ctx.cacheNumBits < numBits) + { + // read all remaining cache bits + value = ctx.cache & MASK_TABLE[ctx.cacheNumBits]; + ctx.bitIndex += ctx.cacheNumBits; + numBits = static_cast(numBits - ctx.cacheNumBits); + + // load next piece of buffer into cache + loadCacheNext(ctx, numBits); + + // add the remaining bits to the result + // if numBits is sizeof(BaseType) * 8 here, value is already 0 (see MASK_TABLE[0]) + if (numBits < sizeof(BaseType) * 8) + { + value <<= numBits; + } + } + value |= ((ctx.cache >> static_cast(ctx.cacheNumBits - numBits)) & MASK_TABLE[numBits]); + ctx.cacheNumBits = static_cast(ctx.cacheNumBits - numBits); + ctx.bitIndex += numBits; + + return value; +} + +/** Unchecked version of readSignedBits. */ +inline BaseSignedType readSignedBitsImpl(ReaderContext& ctx, uint8_t numBits) +{ + static const uint8_t typeSize = sizeof(BaseSignedType) * 8; + BaseType value = readBitsImpl(ctx, numBits); + + // Skip the signed overflow correction if numBits == typeSize. + // In that case, the value that comes out the readBits function + // is already correct. + if (numBits != 0 && numBits < typeSize && + (value >= (static_cast(1) << static_cast(numBits - 1)))) + { + value -= static_cast(1) << numBits; + } + + return static_cast(value); +} + +#ifndef ZSERIO_RUNTIME_64BIT +/** Unchecked implementation of readBits64. Always reads > 32bit! */ +inline uint64_t readBits64Impl(ReaderContext& ctx, uint8_t numBits) +{ + // read the first 32 bits + numBits = static_cast(numBits - 32); + uint64_t value = readBitsImpl(ctx, 32); + + // add the remaining bits + value <<= numBits; + value |= readBitsImpl(ctx, numBits); + + return value; +} +#endif + +} // namespace + +BitStreamReader::ReaderContext::ReaderContext(Span readBuffer, size_t readBufferBitSize) : + buffer(readBuffer), + bufferBitSize(readBufferBitSize), + cache(0), + cacheNumBits(0), + bitIndex(0) +{ + if (buffer.size() > MAX_BUFFER_SIZE) + { + throw CppRuntimeException("BitStreamReader: Buffer size exceeded limit '") + << MAX_BUFFER_SIZE << "' bytes!"; + } +} + +BitStreamReader::BitStreamReader(const uint8_t* buffer, size_t bufferByteSize) : + BitStreamReader(Span(buffer, bufferByteSize)) +{} + +BitStreamReader::BitStreamReader(Span buffer) : + m_context(buffer, buffer.size() * 8) +{} + +BitStreamReader::BitStreamReader(Span buffer, size_t bufferBitSize) : + m_context(buffer, bufferBitSize) +{ + if (buffer.size() < (bufferBitSize + 7) / 8) + { + throw CppRuntimeException("BitStreamReader: Wrong buffer bit size ('") + << buffer.size() << "' < '" << (bufferBitSize + 7) / 8 << "')!"; + } +} + +BitStreamReader::BitStreamReader(const uint8_t* buffer, size_t bufferBitSize, BitsTag) : + m_context(Span(buffer, (bufferBitSize + 7) / 8), bufferBitSize) +{} + +uint32_t BitStreamReader::readBits(uint8_t numBits) +{ + checkNumBits(numBits); + + return static_cast(readBitsImpl(m_context, numBits)); +} + +uint64_t BitStreamReader::readBits64(uint8_t numBits) +{ + checkNumBits64(numBits); + +#ifdef ZSERIO_RUNTIME_64BIT + return readBitsImpl(m_context, numBits); +#else + if (numBits <= 32) + { + return readBitsImpl(m_context, numBits); + } + + return readBits64Impl(m_context, numBits); +#endif +} + +int64_t BitStreamReader::readSignedBits64(uint8_t numBits) +{ + checkNumBits64(numBits); + +#ifdef ZSERIO_RUNTIME_64BIT + return readSignedBitsImpl(m_context, numBits); +#else + if (numBits <= 32) + { + return readSignedBitsImpl(m_context, numBits); + } + + int64_t value = static_cast(readBits64Impl(m_context, numBits)); + + // Skip the signed overflow correction if numBits == 64. + // In that case, the value that comes out the readBits function + // is already correct. + const bool needsSignExtension = + numBits < 64 && (static_cast(value) >= (UINT64_C(1) << (numBits - 1))); + if (needsSignExtension) + { + value = static_cast(static_cast(value) - (UINT64_C(1) << numBits)); + } + + return value; +#endif +} + +int32_t BitStreamReader::readSignedBits(uint8_t numBits) +{ + checkNumBits(numBits); + + return static_cast(readSignedBitsImpl(m_context, numBits)); +} + +int64_t BitStreamReader::readVarInt64() +{ + uint8_t byte = static_cast(readBitsImpl(m_context, 8)); // byte 1 + const bool sign = (byte & VARINT_SIGN_1) != 0; + uint64_t result = byte & VARINT_BYTE_1; + if ((byte & VARINT_HAS_NEXT_1) == 0) + { + return sign ? -static_cast(result) : static_cast(result); + } + + byte = static_cast(readBitsImpl(m_context, 8)); // byte 2 + result = result << 7U | static_cast(byte & VARINT_BYTE_N); + if ((byte & VARINT_HAS_NEXT_N) == 0) + { + return sign ? -static_cast(result) : static_cast(result); + } + + byte = static_cast(readBitsImpl(m_context, 8)); // byte 3 + result = static_cast(result) << 7U | static_cast(byte & VARINT_BYTE_N); + if ((byte & VARINT_HAS_NEXT_N) == 0) + { + return sign ? -static_cast(result) : static_cast(result); + } + + byte = static_cast(readBitsImpl(m_context, 8)); // byte 4 + result = result << 7U | static_cast(byte & VARINT_BYTE_N); + if ((byte & VARINT_HAS_NEXT_N) == 0) + { + return sign ? -static_cast(result) : static_cast(result); + } + + byte = static_cast(readBitsImpl(m_context, 8)); // byte 5 + result = result << 7U | static_cast(byte & VARINT_BYTE_N); + if ((byte & VARINT_HAS_NEXT_N) == 0) + { + return sign ? -static_cast(result) : static_cast(result); + } + + byte = static_cast(readBitsImpl(m_context, 8)); // byte 6 + result = result << 7U | static_cast(byte & VARINT_BYTE_N); + if ((byte & VARINT_HAS_NEXT_N) == 0) + { + return sign ? -static_cast(result) : static_cast(result); + } + + byte = static_cast(readBitsImpl(m_context, 8)); // byte 7 + result = result << 7U | static_cast(byte & VARINT_BYTE_N); + if ((byte & VARINT_HAS_NEXT_N) == 0) + { + return sign ? -static_cast(result) : static_cast(result); + } + + result = result << 8U | static_cast(readBitsImpl(m_context, 8)); // byte 8 + return sign ? -static_cast(result) : static_cast(result); +} + +int32_t BitStreamReader::readVarInt32() +{ + uint8_t byte = static_cast(readBitsImpl(m_context, 8)); // byte 1 + const bool sign = (byte & VARINT_SIGN_1) != 0; + uint32_t result = byte & VARINT_BYTE_1; + if ((byte & VARINT_HAS_NEXT_1) == 0) + { + return sign ? -static_cast(result) : static_cast(result); + } + + byte = static_cast(readBitsImpl(m_context, 8)); // byte 2 + result = result << 7U | static_cast(byte & VARINT_BYTE_N); + if ((byte & VARINT_HAS_NEXT_N) == 0) + { + return sign ? -static_cast(result) : static_cast(result); + } + + byte = static_cast(readBitsImpl(m_context, 8)); // byte 3 + result = result << 7U | static_cast(byte & VARINT_BYTE_N); + if ((byte & VARINT_HAS_NEXT_N) == 0) + { + return sign ? -static_cast(result) : static_cast(result); + } + + result = result << 8U | static_cast(readBitsImpl(m_context, 8)); // byte 4 + return sign ? -static_cast(result) : static_cast(result); +} + +int16_t BitStreamReader::readVarInt16() +{ + uint8_t byte = static_cast(readBitsImpl(m_context, 8)); // byte 1 + const bool sign = (byte & VARINT_SIGN_1) != 0; + uint16_t result = byte & VARINT_BYTE_1; + if ((byte & VARINT_HAS_NEXT_1) == 0) + { + return sign ? static_cast(-result) : static_cast(result); + } + + result = static_cast(result << 8U); + result = static_cast(result | readBitsImpl(m_context, 8)); // byte 2 + return sign ? static_cast(-result) : static_cast(result); +} + +uint64_t BitStreamReader::readVarUInt64() +{ + uint8_t byte = static_cast(readBitsImpl(m_context, 8)); // byte 1 + uint64_t result = byte & VARUINT_BYTE; + if ((byte & VARUINT_HAS_NEXT) == 0) + { + return result; + } + + byte = static_cast(readBitsImpl(m_context, 8)); // byte 2 + result = result << 7U | static_cast(byte & VARUINT_BYTE); + if ((byte & VARUINT_HAS_NEXT) == 0) + { + return result; + } + + byte = static_cast(readBitsImpl(m_context, 8)); // byte 3 + result = result << 7U | static_cast(byte & VARUINT_BYTE); + if ((byte & VARUINT_HAS_NEXT) == 0) + { + return result; + } + + byte = static_cast(readBitsImpl(m_context, 8)); // byte 4 + result = result << 7U | static_cast(byte & VARUINT_BYTE); + if ((byte & VARUINT_HAS_NEXT) == 0) + { + return result; + } + + byte = static_cast(readBitsImpl(m_context, 8)); // byte 5 + result = result << 7U | static_cast(byte & VARUINT_BYTE); + if ((byte & VARUINT_HAS_NEXT) == 0) + { + return result; + } + + byte = static_cast(readBitsImpl(m_context, 8)); // byte 6 + result = result << 7U | static_cast(byte & VARUINT_BYTE); + if ((byte & VARUINT_HAS_NEXT) == 0) + { + return result; + } + + byte = static_cast(readBitsImpl(m_context, 8)); // byte 7 + result = result << 7U | static_cast(byte & VARUINT_BYTE); + if ((byte & VARUINT_HAS_NEXT) == 0) + { + return result; + } + + result = result << 8U | static_cast(readBitsImpl(m_context, 8)); // byte 8 + return result; +} + +uint32_t BitStreamReader::readVarUInt32() +{ + uint8_t byte = static_cast(readBitsImpl(m_context, 8)); // byte 1 + uint32_t result = byte & VARUINT_BYTE; + if ((byte & VARUINT_HAS_NEXT) == 0) + { + return result; + } + + byte = static_cast(readBitsImpl(m_context, 8)); // byte 2 + result = result << 7U | static_cast(byte & VARUINT_BYTE); + if ((byte & VARUINT_HAS_NEXT) == 0) + { + return result; + } + + byte = static_cast(readBitsImpl(m_context, 8)); // byte 3 + result = result << 7U | static_cast(byte & VARUINT_BYTE); + if ((byte & VARUINT_HAS_NEXT) == 0) + { + return result; + } + + result = result << 8U | static_cast(readBitsImpl(m_context, 8)); // byte 4 + return result; +} + +uint16_t BitStreamReader::readVarUInt16() +{ + uint8_t byte = static_cast(readBitsImpl(m_context, 8)); // byte 1 + uint16_t result = byte & VARUINT_BYTE; + if ((byte & VARUINT_HAS_NEXT) == 0) + { + return result; + } + + result = static_cast(result << 8U); + result = static_cast(result | readBitsImpl(m_context, 8)); // byte 2 + return result; +} + +int64_t BitStreamReader::readVarInt() +{ + uint8_t byte = static_cast(readBitsImpl(m_context, 8)); // byte 1 + const bool sign = (byte & VARINT_SIGN_1) != 0; + uint64_t result = byte & VARINT_BYTE_1; + if ((byte & VARINT_HAS_NEXT_1) == 0) + { + return sign ? (result == 0 ? INT64_MIN : -static_cast(result)) : static_cast(result); + } + + byte = static_cast(readBitsImpl(m_context, 8)); // byte 2 + result = result << 7U | static_cast(byte & VARINT_BYTE_N); + if ((byte & VARINT_HAS_NEXT_N) == 0) + { + return sign ? -static_cast(result) : static_cast(result); + } + + byte = static_cast(readBitsImpl(m_context, 8)); // byte 3 + result = result << 7U | static_cast(byte & VARINT_BYTE_N); + if ((byte & VARINT_HAS_NEXT_N) == 0) + { + return sign ? -static_cast(result) : static_cast(result); + } + + byte = static_cast(readBitsImpl(m_context, 8)); // byte 4 + result = result << 7U | static_cast(byte & VARINT_BYTE_N); + if ((byte & VARINT_HAS_NEXT_N) == 0) + { + return sign ? -static_cast(result) : static_cast(result); + } + + byte = static_cast(readBitsImpl(m_context, 8)); // byte 5 + result = result << 7U | static_cast(byte & VARINT_BYTE_N); + if ((byte & VARINT_HAS_NEXT_N) == 0) + { + return sign ? -static_cast(result) : static_cast(result); + } + + byte = static_cast(readBitsImpl(m_context, 8)); // byte 6 + result = result << 7U | static_cast(byte & VARINT_BYTE_N); + if ((byte & VARINT_HAS_NEXT_N) == 0) + { + return sign ? -static_cast(result) : static_cast(result); + } + + byte = static_cast(readBitsImpl(m_context, 8)); // byte 7 + result = result << 7U | static_cast(byte & VARINT_BYTE_N); + if ((byte & VARINT_HAS_NEXT_N) == 0) + { + return sign ? -static_cast(result) : static_cast(result); + } + + byte = static_cast(readBitsImpl(m_context, 8)); // byte 8 + result = result << 7U | static_cast(byte & VARINT_BYTE_N); + if ((byte & VARINT_HAS_NEXT_N) == 0) + { + return sign ? -static_cast(result) : static_cast(result); + } + + result = result << 8U | static_cast(readBitsImpl(m_context, 8)); // byte 9 + return sign ? -static_cast(result) : static_cast(result); +} + +uint64_t BitStreamReader::readVarUInt() +{ + uint8_t byte = static_cast(readBitsImpl(m_context, 8)); // byte 1 + uint64_t result = byte & VARUINT_BYTE; + if ((byte & VARUINT_HAS_NEXT) == 0) + { + return result; + } + + byte = static_cast(readBitsImpl(m_context, 8)); // byte 2 + result = result << 7U | static_cast(byte & VARUINT_BYTE); + if ((byte & VARUINT_HAS_NEXT) == 0) + { + return result; + } + + byte = static_cast(readBitsImpl(m_context, 8)); // byte 3 + result = result << 7U | static_cast(byte & VARUINT_BYTE); + if ((byte & VARUINT_HAS_NEXT) == 0) + { + return result; + } + + byte = static_cast(readBitsImpl(m_context, 8)); // byte 4 + result = result << 7U | static_cast(byte & VARUINT_BYTE); + if ((byte & VARUINT_HAS_NEXT) == 0) + { + return result; + } + + byte = static_cast(readBitsImpl(m_context, 8)); // byte 5 + result = result << 7U | static_cast(byte & VARUINT_BYTE); + if ((byte & VARUINT_HAS_NEXT) == 0) + { + return result; + } + + byte = static_cast(readBitsImpl(m_context, 8)); // byte 6 + result = result << 7U | static_cast(byte & VARUINT_BYTE); + if ((byte & VARUINT_HAS_NEXT) == 0) + { + return result; + } + + byte = static_cast(readBitsImpl(m_context, 8)); // byte 7 + result = result << 7U | static_cast(byte & VARUINT_BYTE); + if ((byte & VARUINT_HAS_NEXT) == 0) + { + return result; + } + + byte = static_cast(readBitsImpl(m_context, 8)); // byte 8 + result = result << 7U | static_cast(byte & VARUINT_BYTE); + if ((byte & VARUINT_HAS_NEXT) == 0) + { + return result; + } + + result = result << 8U | static_cast(readBitsImpl(m_context, 8)); // byte 9 + return result; +} + +uint32_t BitStreamReader::readVarSize() +{ + uint8_t byte = static_cast(readBitsImpl(m_context, 8)); // byte 1 + uint32_t result = byte & VARUINT_BYTE; + if ((byte & VARUINT_HAS_NEXT) == 0) + { + return result; + } + + byte = static_cast(readBitsImpl(m_context, 8)); // byte 2 + result = result << 7U | static_cast(byte & VARUINT_BYTE); + if ((byte & VARUINT_HAS_NEXT) == 0) + { + return result; + } + + byte = static_cast(readBitsImpl(m_context, 8)); // byte 3 + result = result << 7U | static_cast(byte & VARUINT_BYTE); + if ((byte & VARUINT_HAS_NEXT) == 0) + { + return result; + } + + byte = static_cast(readBitsImpl(m_context, 8)); // byte 4 + result = result << 7U | static_cast(byte & VARUINT_BYTE); + if ((byte & VARUINT_HAS_NEXT) == 0) + { + return result; + } + + result = result << 8U | static_cast(readBitsImpl(m_context, 8)); // byte 5 + if (result > VARSIZE_MAX_VALUE) + { + throw CppRuntimeException("BitStreamReader: Read value '") + << result << "' is out of range for varsize type!"; + } + + return result; +} + +float BitStreamReader::readFloat16() +{ + const uint16_t halfPrecisionFloatValue = static_cast(readBitsImpl(m_context, 16)); + + return convertUInt16ToFloat(halfPrecisionFloatValue); +} + +float BitStreamReader::readFloat32() +{ + const uint32_t singlePrecisionFloatValue = static_cast(readBitsImpl(m_context, 32)); + + return convertUInt32ToFloat(singlePrecisionFloatValue); +} + +double BitStreamReader::readFloat64() +{ +#ifdef ZSERIO_RUNTIME_64BIT + const uint64_t doublePrecisionFloatValue = static_cast(readBitsImpl(m_context, 64)); +#else + const uint64_t doublePrecisionFloatValue = readBits64Impl(m_context, 64); +#endif + + return convertUInt64ToDouble(doublePrecisionFloatValue); +} + +bool BitStreamReader::readBool() +{ + return readBitsImpl(m_context, 1) != 0; +} + +void BitStreamReader::setBitPosition(BitPosType position) +{ + if (position > m_context.bufferBitSize) + { + throw CppRuntimeException("BitStreamReader: Reached eof(), setting of bit position failed!"); + } + + m_context.bitIndex = (position / 8) * 8; // set to byte aligned position + m_context.cacheNumBits = 0; // invalidate cache + const uint8_t skip = static_cast(position - m_context.bitIndex); + if (skip != 0) + { + (void)readBits(skip); + } +} + +void BitStreamReader::alignTo(size_t alignment) +{ + const BitPosType offset = getBitPosition() % alignment; + if (offset != 0) + { + const uint8_t skip = static_cast(alignment - offset); + (void)readBits64(skip); + } +} + +uint8_t BitStreamReader::readByte() +{ + return static_cast(readBitsImpl(m_context, 8)); +} + +} // namespace zserio diff --git a/runtime/src/zserio/BitStreamReader.h b/runtime/src/zserio/BitStreamReader.h new file mode 100644 index 0000000..9c2895b --- /dev/null +++ b/runtime/src/zserio/BitStreamReader.h @@ -0,0 +1,391 @@ +#ifndef ZSERIO_BIT_STREAM_READER_H_INC +#define ZSERIO_BIT_STREAM_READER_H_INC + +#include +#include +#include +#include + +#include "zserio/BitBuffer.h" +#include "zserio/RebindAlloc.h" +#include "zserio/Span.h" +#include "zserio/String.h" +#include "zserio/Types.h" +#include "zserio/Vector.h" + +namespace zserio +{ + +/** + * Reader class which allows to read various data from the bit stream. + */ +class BitStreamReader +{ +public: + /** Type for bit position. */ + using BitPosType = size_t; + + /** + * Context of the reader defining its state. + */ + struct ReaderContext + { + /** + * Constructor. + * + * \param readBuffer Span to the buffer to read. + * \param readBufferBitSize Size of the buffer in bits. + */ + explicit ReaderContext(Span readBuffer, size_t readBufferBitSize); + + /** + * Destructor. + */ + ~ReaderContext() = default; + + /** + * Copying and moving is disallowed! + * \{ + */ + ReaderContext(const ReaderContext&) = delete; + ReaderContext& operator=(const ReaderContext&) = delete; + + ReaderContext(const ReaderContext&&) = delete; + ReaderContext& operator=(const ReaderContext&&) = delete; + /** + * \} + */ + + Span buffer; /**< Buffer to read from. */ + const BitPosType bufferBitSize; /**< Size of the buffer in bits. */ + + uintptr_t cache; /**< Bit cache to optimize bit reading. */ + uint8_t cacheNumBits; /**< Num bits available in the bit cache. */ + + BitPosType bitIndex; /**< Current bit index. */ + }; + + /** + * Constructor from raw buffer. + * + * \param buffer Pointer to the buffer to read. + * \param bufferByteSize Size of the buffer in bytes. + */ + explicit BitStreamReader(const uint8_t* buffer, size_t bufferByteSize); + + /** + * Constructor from buffer passed as a Span. + * + * \param buffer Buffer to read. + */ + explicit BitStreamReader(Span buffer); + + /** + * Constructor from buffer passed as a Span with exact bit size. + * + * \param buffer Buffer to read. + * \param bufferBitSize Size of the buffer in bits. + */ + explicit BitStreamReader(Span buffer, size_t bufferBitSize); + + /** + * Constructor from raw buffer with exact bit size. + * + * \param buffer Pointer to buffer to read. + * \param bufferBitSize Size of the buffer in bits. + */ + explicit BitStreamReader(const uint8_t* buffer, size_t bufferBitSize, BitsTag); + + /** + * Constructor from bit buffer. + * + * \param bitBuffer Bit buffer to read from. + */ + template + explicit BitStreamReader(const BasicBitBuffer& bitBuffer) : + BitStreamReader(bitBuffer.getData(), bitBuffer.getBitSize()) + {} + + /** + * Destructor. + */ + ~BitStreamReader() = default; + + /** + * Reads unsigned bits up to 32-bits. + * + * \param numBits Number of bits to read. + * + * \return Read bits. + */ + uint32_t readBits(uint8_t numBits = 32); + + /** + * Reads unsigned bits up to 64-bits. + * + * \param numBits Number of bits to read. + * + * \return Read bits. + */ + uint64_t readBits64(uint8_t numBits = 64); + + /** + * Reads signed bits up to 32-bits. + * + * \param numBits Number of bits to read. + * + * \return Read bits. + */ + int32_t readSignedBits(uint8_t numBits = 32); + + /** + * Reads signed bits up to 64-bits. + * + * \param numBits Number of bits to read. + * + * \return Read bits. + */ + int64_t readSignedBits64(uint8_t numBits = 64); + + /** + * Reads signed variable integer up to 64 bits. + * + * \return Read varint64. + */ + int64_t readVarInt64(); + + /** + * Reads signed variable integer up to 32 bits. + * + * \return Read varint32. + */ + int32_t readVarInt32(); + + /** + * Reads signed variable integer up to 16 bits. + * + * \return Read varint16. + */ + int16_t readVarInt16(); + + /** + * Read unsigned variable integer up to 64 bits. + * + * \return Read varuint64. + */ + uint64_t readVarUInt64(); + + /** + * Read unsigned variable integer up to 32 bits. + * + * \return Read varuint32. + */ + uint32_t readVarUInt32(); + + /** + * Read unsigned variable integer up to 16 bits. + * + * \return Read varuint16. + */ + uint16_t readVarUInt16(); + + /** + * Reads signed variable integer up to 72 bits. + * + * \return Read varint. + */ + int64_t readVarInt(); + + /** + * Read unsigned variable integer up to 72 bits. + * + * \return Read varuint. + */ + uint64_t readVarUInt(); + + /** + * Read variable size integer up to 40 bits. + * + * \return Read varsize. + */ + uint32_t readVarSize(); + + /** + * Reads 16-bit float. + * + * \return Read float16. + */ + float readFloat16(); + + /** + * Reads 32-bit float. + * + * \return Read float32. + */ + float readFloat32(); + + /** + * Reads 64-bit float double. + * + * \return Read float64. + */ + double readFloat64(); + + /** + * Reads bytes. + * + * \param alloc Allocator to use. + * + * \return Read bytes as a vector. + */ + template > + vector readBytes(const ALLOC& alloc = ALLOC()) + { + const size_t len = static_cast(readVarSize()); + const BitPosType beginBitPosition = getBitPosition(); + if ((beginBitPosition & 0x07U) != 0) + { + // we are not aligned to byte + vector value{alloc}; + value.reserve(len); + for (size_t i = 0; i < len; ++i) + { + value.push_back(readByte()); + } + return value; + } + else + { + // we are aligned to byte + setBitPosition(beginBitPosition + len * 8); + Span::iterator beginIt = m_context.buffer.begin() + beginBitPosition / 8; + return vector(beginIt, beginIt + len, alloc); + } + } + + /** + * Reads an UTF-8 string. + * + * \param alloc Allocator to use. + * + * \return Read string. + */ + template > + string readString(const ALLOC& alloc = ALLOC()) + { + const size_t len = static_cast(readVarSize()); + const BitPosType beginBitPosition = getBitPosition(); + if ((beginBitPosition & 0x07U) != 0) + { + // we are not aligned to byte + string value{alloc}; + value.reserve(len); + for (size_t i = 0; i < len; ++i) + { + using char_traits = std::char_traits; + const char readCharacter = + char_traits::to_char_type(static_cast(readByte())); + value.push_back(readCharacter); + } + return value; + } + else + { + // we are aligned to byte + setBitPosition(beginBitPosition + len * 8); + Span::iterator beginIt = m_context.buffer.begin() + beginBitPosition / 8; + return string(beginIt, beginIt + len, alloc); + } + } + + /** + * Reads bool as a single bit. + * + * \return Read bool value. + */ + bool readBool(); + + /** + * Reads a bit buffer. + * + * \param alloc Allocator to use. + * + * \return Read bit buffer. + */ + template > + BasicBitBuffer> readBitBuffer(const ALLOC& allocator = ALLOC()) + { + const size_t bitSize = static_cast(readVarSize()); + const size_t numBytesToRead = bitSize / 8; + const uint8_t numRestBits = static_cast(bitSize - numBytesToRead * 8); + BasicBitBuffer> bitBuffer(bitSize, allocator); + Span buffer = bitBuffer.getData(); + const BitPosType beginBitPosition = getBitPosition(); + const Span::iterator itEnd = buffer.begin() + numBytesToRead; + if ((beginBitPosition & 0x07U) != 0) + { + // we are not aligned to byte + for (Span::iterator it = buffer.begin(); it != itEnd; ++it) + { + *it = static_cast(readBits(8)); + } + } + else + { + // we are aligned to byte + setBitPosition(beginBitPosition + numBytesToRead * 8); + Span::const_iterator sourceIt = m_context.buffer.begin() + beginBitPosition / 8; + (void)std::copy(sourceIt, sourceIt + numBytesToRead, buffer.begin()); + } + + if (numRestBits > 0) + { + *itEnd = static_cast(readBits(numRestBits) << (8U - numRestBits)); + } + + return bitBuffer; + } + + /** + * Gets current bit position. + * + * \return Current bit position. + */ + BitPosType getBitPosition() const + { + return m_context.bitIndex; + } + + /** + * Sets current bit position. Use with caution! + * + * \param position New bit position. + */ + void setBitPosition(BitPosType position); + + /** + * Moves current bit position to perform the requested bit alignment. + * + * \param alignment Size of the alignment in bits. + */ + void alignTo(size_t alignment); + + /** + * Gets size of the underlying buffer in bits. + * + * \return Buffer bit size. + */ + size_t getBufferBitSize() const + { + return m_context.bufferBitSize; + } + +private: + uint8_t readByte(); + + ReaderContext m_context; +}; + +} // namespace zserio + +#endif // ifndef ZSERIO_BIT_STREAM_READER_H_INC diff --git a/runtime/src/zserio/BitStreamWriter.cpp b/runtime/src/zserio/BitStreamWriter.cpp new file mode 100644 index 0000000..4bb9fe3 --- /dev/null +++ b/runtime/src/zserio/BitStreamWriter.cpp @@ -0,0 +1,681 @@ +#include +#include +#include +#include +#include + +#include "zserio/BitSizeOfCalculator.h" +#include "zserio/BitStreamWriter.h" +#include "zserio/CppRuntimeException.h" +#include "zserio/FloatUtil.h" + +namespace zserio +{ + +static const std::array MAX_U32_VALUES = { + 0x00U, + 0x0001U, + 0x0003U, + 0x0007U, + 0x000fU, + 0x001fU, + 0x003fU, + 0x007fU, + 0x00ffU, + 0x01ffU, + 0x03ffU, + 0x07ffU, + 0x0fffU, + 0x1fffU, + 0x3fffU, + 0x7fffU, + 0xffffU, + 0x0001ffffU, + 0x0003ffffU, + 0x0007ffffU, + 0x000fffffU, + 0x001fffffU, + 0x003fffffU, + 0x007fffffU, + 0x00ffffffU, + 0x01ffffffU, + 0x03ffffffU, + 0x07ffffffU, + 0x0fffffffU, + 0x1fffffffU, + 0x3fffffffU, + 0x7fffffffU, + 0xffffffffU, +}; + +static const std::array MIN_I32_VALUES = { + 0, + -0x0001, + -0x0002, + -0x0004, + -0x0008, + -0x0010, + -0x0020, + -0x0040, + -0x0080, + -0x0100, + -0x0200, + -0x0400, + -0x0800, + -0x1000, + -0x2000, + -0x4000, + -0x8000, + -0x00010000, + -0x00020000, + -0x00040000, + -0x00080000, + -0x00100000, + -0x00200000, + -0x00400000, + -0x00800000, + -0x01000000, + -0x02000000, + -0x04000000, + -0x08000000, + -0x10000000, + -0x20000000, + -0x40000000, + INT32_MIN, +}; + +static const std::array MAX_I32_VALUES = { + 0x00, + 0x0000, + 0x0001, + 0x0003, + 0x0007, + 0x000f, + 0x001f, + 0x003f, + 0x007f, + 0x00ff, + 0x01ff, + 0x03ff, + 0x07ff, + 0x0fff, + 0x1fff, + 0x3fff, + 0x7fff, + 0x0000ffff, + 0x0001ffff, + 0x0003ffff, + 0x0007ffff, + 0x000fffff, + 0x001fffff, + 0x003fffff, + 0x007fffff, + 0x00ffffff, + 0x01ffffff, + 0x03ffffff, + 0x07ffffff, + 0x0fffffff, + 0x1fffffff, + 0x3fffffff, + 0x7fffffff, +}; + +static const std::array MAX_U64_VALUES = { + 0x00ULL, + 0x0001ULL, + 0x0003ULL, + 0x0007ULL, + 0x000fULL, + 0x001fULL, + 0x003fULL, + 0x007fULL, + 0x00ffULL, + 0x01ffULL, + 0x03ffULL, + 0x07ffULL, + 0x0fffULL, + 0x1fffULL, + 0x3fffULL, + 0x7fffULL, + 0xffffULL, + 0x0001ffffULL, + 0x0003ffffULL, + 0x0007ffffULL, + 0x000fffffULL, + 0x001fffffULL, + 0x003fffffULL, + 0x007fffffULL, + 0x00ffffffULL, + 0x01ffffffULL, + 0x03ffffffULL, + 0x07ffffffULL, + 0x0fffffffULL, + 0x1fffffffULL, + 0x3fffffffULL, + 0x7fffffffULL, + 0xffffffffULL, + 0x0001ffffffffULL, + 0x0003ffffffffULL, + 0x0007ffffffffULL, + 0x000fffffffffULL, + 0x001fffffffffULL, + 0x003fffffffffULL, + 0x007fffffffffULL, + 0x00ffffffffffULL, + 0x01ffffffffffULL, + 0x03ffffffffffULL, + 0x07ffffffffffULL, + 0x0fffffffffffULL, + 0x1fffffffffffULL, + 0x3fffffffffffULL, + 0x7fffffffffffULL, + 0xffffffffffffULL, + 0x0001ffffffffffffULL, + 0x0003ffffffffffffULL, + 0x0007ffffffffffffULL, + 0x000fffffffffffffULL, + 0x001fffffffffffffULL, + 0x003fffffffffffffULL, + 0x007fffffffffffffULL, + 0x00ffffffffffffffULL, + 0x01ffffffffffffffULL, + 0x03ffffffffffffffULL, + 0x07ffffffffffffffULL, + 0x0fffffffffffffffULL, + 0x1fffffffffffffffULL, + 0x3fffffffffffffffULL, + 0x7fffffffffffffffULL, + 0xffffffffffffffffULL, +}; + +static const std::array MIN_I64_VALUES = { + 0LL, + -0x0001LL, + -0x0002LL, + -0x0004LL, + -0x0008LL, + -0x0010LL, + -0x0020LL, + -0x0040LL, + -0x0080LL, + -0x0100LL, + -0x0200LL, + -0x0400LL, + -0x0800LL, + -0x1000LL, + -0x2000LL, + -0x4000LL, + -0x8000LL, + -0x00010000LL, + -0x00020000LL, + -0x00040000LL, + -0x00080000LL, + -0x00100000LL, + -0x00200000LL, + -0x00400000LL, + -0x00800000LL, + -0x01000000LL, + -0x02000000LL, + -0x04000000LL, + -0x08000000LL, + -0x10000000LL, + -0x20000000LL, + -0x40000000LL, + -0x80000000LL, + -0x000100000000LL, + -0x000200000000LL, + -0x000400000000LL, + -0x000800000000LL, + -0x001000000000LL, + -0x002000000000LL, + -0x004000000000LL, + -0x008000000000LL, + -0x010000000000LL, + -0x020000000000LL, + -0x040000000000LL, + -0x080000000000LL, + -0x100000000000LL, + -0x200000000000LL, + -0x400000000000LL, + -0x800000000000LL, + -0x0001000000000000LL, + -0x0002000000000000LL, + -0x0004000000000000LL, + -0x0008000000000000LL, + -0x0010000000000000LL, + -0x0020000000000000LL, + -0x0040000000000000LL, + -0x0080000000000000LL, + -0x0100000000000000LL, + -0x0200000000000000LL, + -0x0400000000000000LL, + -0x0800000000000000LL, + -0x1000000000000000LL, + -0x2000000000000000LL, + -0x4000000000000000LL, + INT64_MIN, +}; + +static const std::array MAX_I64_VALUES = { + 0x00LL, + 0x0000LL, + 0x0001LL, + 0x0003LL, + 0x0007LL, + 0x000fLL, + 0x001fLL, + 0x003fLL, + 0x007fLL, + 0x00ffLL, + 0x01ffLL, + 0x03ffLL, + 0x07ffLL, + 0x0fffLL, + 0x1fffLL, + 0x3fffLL, + 0x7fffLL, + 0x0000ffffLL, + 0x0001ffffLL, + 0x0003ffffLL, + 0x0007ffffLL, + 0x000fffffLL, + 0x001fffffLL, + 0x003fffffLL, + 0x007fffffLL, + 0x00ffffffLL, + 0x01ffffffLL, + 0x03ffffffLL, + 0x07ffffffLL, + 0x0fffffffLL, + 0x1fffffffLL, + 0x3fffffffLL, + 0x7fffffffLL, + 0x0000ffffffffLL, + 0x0001ffffffffLL, + 0x0003ffffffffLL, + 0x0007ffffffffLL, + 0x000fffffffffLL, + 0x001fffffffffLL, + 0x003fffffffffLL, + 0x007fffffffffLL, + 0x00ffffffffffLL, + 0x01ffffffffffLL, + 0x03ffffffffffLL, + 0x07ffffffffffLL, + 0x0fffffffffffLL, + 0x1fffffffffffLL, + 0x3fffffffffffLL, + 0x7fffffffffffLL, + 0x0000ffffffffffffLL, + 0x0001ffffffffffffLL, + 0x0003ffffffffffffLL, + 0x0007ffffffffffffLL, + 0x000fffffffffffffLL, + 0x001fffffffffffffLL, + 0x003fffffffffffffLL, + 0x007fffffffffffffLL, + 0x00ffffffffffffffLL, + 0x01ffffffffffffffLL, + 0x03ffffffffffffffLL, + 0x07ffffffffffffffLL, + 0x0fffffffffffffffLL, + 0x1fffffffffffffffLL, + 0x3fffffffffffffffLL, + 0x7fffffffffffffffLL, +}; + +BitStreamWriter::BitStreamWriter(uint8_t* buffer, size_t bufferBitSize, BitsTag) : + m_buffer(buffer, (bufferBitSize + 7) / 8), + m_bitIndex(0), + m_bufferBitSize(bufferBitSize) +{} + +BitStreamWriter::BitStreamWriter(uint8_t* buffer, size_t bufferByteSize) : + BitStreamWriter(Span(buffer, bufferByteSize)) +{} + +BitStreamWriter::BitStreamWriter(Span buffer) : + m_buffer(buffer), + m_bitIndex(0), + m_bufferBitSize(buffer.size() * 8) +{} + +BitStreamWriter::BitStreamWriter(Span buffer, size_t bufferBitSize) : + m_buffer(buffer), + m_bitIndex(0), + m_bufferBitSize(bufferBitSize) +{ + if (buffer.size() < (bufferBitSize + 7) / 8) + { + throw CppRuntimeException("BitStreamWriter: Wrong buffer bit size ('") + << buffer.size() << "' < '" << (bufferBitSize + 7) / 8 << "')!"; + } +} + +void BitStreamWriter::writeBits(uint32_t data, uint8_t numBits) +{ + if (numBits == 0 || numBits > sizeof(uint32_t) * 8 || data > MAX_U32_VALUES[numBits]) + { + throw CppRuntimeException("BitStreamWriter: Writing of ") + << numBits << "-bits value '" << data << "' failed!"; + } + + writeUnsignedBits(data, numBits); +} + +void BitStreamWriter::writeBits64(uint64_t data, uint8_t numBits) +{ + if (numBits == 0 || numBits > sizeof(uint64_t) * 8 || data > MAX_U64_VALUES[numBits]) + { + throw CppRuntimeException("BitStreamWriter: Writing of ") + << numBits << "-bits value '" << data << "' failed!"; + } + + writeUnsignedBits64(data, numBits); +} + +void BitStreamWriter::writeSignedBits(int32_t data, uint8_t numBits) +{ + if (numBits == 0 || numBits > sizeof(int32_t) * 8 || data < MIN_I32_VALUES[numBits] || + data > MAX_I32_VALUES[numBits]) + { + throw CppRuntimeException("BitStreamWriter: Writing of ") + << numBits << "-bits value '" << data << "' failed!"; + } + + writeUnsignedBits(static_cast(data) & MAX_U32_VALUES[numBits], numBits); +} + +void BitStreamWriter::writeSignedBits64(int64_t data, uint8_t numBits) +{ + if (numBits == 0 || numBits > sizeof(int64_t) * 8 || data < MIN_I64_VALUES[numBits] || + data > MAX_I64_VALUES[numBits]) + { + throw CppRuntimeException("BitStreamWriter: Writing of ") + << numBits << "-bits value '" << data << "' failed!"; + } + + writeUnsignedBits64(static_cast(data) & MAX_U64_VALUES[numBits], numBits); +} + +void BitStreamWriter::writeVarInt64(int64_t data) +{ + writeSignedVarNum(data, 8, zserio::bitSizeOfVarInt64(data) / 8); +} + +void BitStreamWriter::writeVarInt32(int32_t data) +{ + writeSignedVarNum(data, 4, zserio::bitSizeOfVarInt32(data) / 8); +} + +void BitStreamWriter::writeVarInt16(int16_t data) +{ + writeSignedVarNum(data, 2, zserio::bitSizeOfVarInt16(data) / 8); +} + +void BitStreamWriter::writeVarUInt64(uint64_t data) +{ + writeUnsignedVarNum(data, 8, zserio::bitSizeOfVarUInt64(data) / 8); +} + +void BitStreamWriter::writeVarUInt32(uint32_t data) +{ + writeUnsignedVarNum(data, 4, zserio::bitSizeOfVarUInt32(data) / 8); +} + +void BitStreamWriter::writeVarUInt16(uint16_t data) +{ + writeUnsignedVarNum(data, 2, zserio::bitSizeOfVarUInt16(data) / 8); +} + +void BitStreamWriter::writeVarInt(int64_t data) +{ + if (data == INT64_MIN) + { + writeBits(0x80, 8); // INT64_MIN is encoded as -0 + } + else + { + writeSignedVarNum(data, 9, zserio::bitSizeOfVarInt(data) / 8); + } +} + +void BitStreamWriter::writeVarUInt(uint64_t data) +{ + writeUnsignedVarNum(data, 9, zserio::bitSizeOfVarUInt(data) / 8); +} + +void BitStreamWriter::writeVarSize(uint32_t data) +{ + writeUnsignedVarNum(data, 5, zserio::bitSizeOfVarSize(data) / 8); +} + +void BitStreamWriter::writeFloat16(float data) +{ + const uint16_t halfPrecisionFloat = convertFloatToUInt16(data); + writeUnsignedBits(halfPrecisionFloat, 16); +} + +void BitStreamWriter::writeFloat32(float data) +{ + const uint32_t singlePrecisionFloat = convertFloatToUInt32(data); + writeUnsignedBits(singlePrecisionFloat, 32); +} + +void BitStreamWriter::writeFloat64(double data) +{ + const uint64_t doublePrecisionFloat = convertDoubleToUInt64(data); + writeUnsignedBits64(doublePrecisionFloat, 64); +} + +void BitStreamWriter::writeBytes(Span data) +{ + const size_t len = data.size(); + writeVarSize(convertSizeToUInt32(len)); + + const BitPosType beginBitPosition = getBitPosition(); + if ((beginBitPosition & 0x07U) != 0) + { + // we are not aligned to byte + for (size_t i = 0; i < len; ++i) + { + writeBits(data[i], 8); + } + } + else + { + // we are aligned to bytes + setBitPosition(beginBitPosition + len * 8); + if (hasWriteBuffer()) + { + (void)std::copy(data.begin(), data.end(), m_buffer.begin() + beginBitPosition / 8); + } + } +} + +void BitStreamWriter::writeString(std::string_view data) +{ + const size_t len = data.size(); + writeVarSize(convertSizeToUInt32(len)); + + const BitPosType beginBitPosition = getBitPosition(); + if ((beginBitPosition & 0x07U) != 0) + { + // we are not aligned to byte + for (size_t i = 0; i < len; ++i) + { + writeBits(static_cast(std::char_traits::to_int_type(data[i])), 8); + } + } + else + { + // we are aligned to bytes + setBitPosition(beginBitPosition + len * 8); + if (hasWriteBuffer()) + { + (void)std::copy(data.begin(), data.begin() + len, m_buffer.data() + beginBitPosition / 8); + } + } +} + +void BitStreamWriter::writeBool(bool data) +{ + writeBits((data ? 1 : 0), 1); +} + +void BitStreamWriter::setBitPosition(BitPosType position) +{ + if (hasWriteBuffer()) + { + checkCapacity(position); + } + + m_bitIndex = position; +} + +void BitStreamWriter::alignTo(size_t alignment) +{ + const BitPosType offset = getBitPosition() % alignment; + if (offset != 0) + { + const uint8_t skip = static_cast(alignment - offset); + writeBits64(0, skip); + } +} + +const uint8_t* BitStreamWriter::getWriteBuffer() const +{ + return m_buffer.data(); +} + +Span BitStreamWriter::getBuffer() const +{ + return m_buffer; +} + +void BitStreamWriter::writeUnsignedBits(uint32_t data, uint8_t numBits) +{ + if (!hasWriteBuffer()) + { + m_bitIndex += numBits; + return; + } + + checkCapacity(m_bitIndex + numBits); + + uint8_t restNumBits = numBits; + const uint8_t bitsUsed = m_bitIndex & 0x07U; + uint8_t bitsFree = static_cast(8 - bitsUsed); + size_t byteIndex = m_bitIndex / 8; + + if (restNumBits > bitsFree) + { + // first part + const uint8_t shiftNum = static_cast(restNumBits - bitsFree); + const uint8_t maskedByte = static_cast(m_buffer[byteIndex] & ~(0xFFU >> bitsUsed)); + m_buffer[byteIndex++] = static_cast(maskedByte | (data >> shiftNum)); + restNumBits = static_cast(restNumBits - bitsFree); + + // middle parts + while (restNumBits >= 8) + { + restNumBits = static_cast(restNumBits - 8); + m_buffer[byteIndex++] = static_cast((data >> restNumBits) & MAX_U32_VALUES[8]); + } + + // reset bits free + bitsFree = 8; + } + + // last part + if (restNumBits > 0) + { + const uint8_t shiftNum = static_cast(bitsFree - restNumBits); + const uint32_t mask = MAX_U32_VALUES[restNumBits]; + const uint8_t maskedByte = + m_buffer[byteIndex] & static_cast(~static_cast(mask << shiftNum)); + m_buffer[byteIndex] = static_cast(maskedByte | ((data & mask) << shiftNum)); + } + + m_bitIndex += numBits; +} + +inline void BitStreamWriter::writeUnsignedBits64(uint64_t data, uint8_t numBits) +{ + if (numBits <= 32) + { + writeUnsignedBits(static_cast(data), numBits); + } + else + { + writeUnsignedBits(static_cast(data >> 32U), static_cast(numBits - 32)); + writeUnsignedBits(static_cast(data), 32); + } +} + +inline void BitStreamWriter::writeSignedVarNum(int64_t value, size_t maxVarBytes, size_t numVarBytes) +{ + const uint64_t absValue = static_cast(value < 0 ? -value : value); + writeVarNum(absValue, true, value < 0, maxVarBytes, numVarBytes); +} + +inline void BitStreamWriter::writeUnsignedVarNum(uint64_t value, size_t maxVarBytes, size_t numVarBytes) +{ + writeVarNum(value, false, false, maxVarBytes, numVarBytes); +} + +inline void BitStreamWriter::writeVarNum( + uint64_t value, bool hasSign, bool isNegative, size_t maxVarBytes, size_t numVarBytes) +{ + static const std::array bitMasks = {0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF}; + const bool hasMaxByteRange = (numVarBytes == maxVarBytes); + + for (size_t i = 0; i < numVarBytes; i++) + { + uint8_t byte = 0x00; + uint8_t numBits = 8; + const bool hasNextByte = (i < numVarBytes - 1); + const bool hasSignBit = (hasSign && i == 0); + if (hasSignBit) + { + if (isNegative) + { + byte |= 0x80U; + } + numBits--; + } + if (hasNextByte) + { + numBits--; + const uint8_t add = static_cast(0x01U << numBits); + byte = static_cast(byte | add); // use bit 6 if signed bit is present, use bit 7 otherwise + } + else // this is the last byte + { + if (!hasMaxByteRange) // next byte indicator is not used in last byte in case of max byte range + { + numBits--; + } + } + + const size_t shiftBits = (numVarBytes - (i + 1)) * 7 + ((hasMaxByteRange && hasNextByte) ? 1 : 0); + const uint8_t add = static_cast((value >> shiftBits) & bitMasks[numBits - 1U]); + byte = static_cast(byte | add); + writeUnsignedBits(byte, 8); + } +} + +inline void BitStreamWriter::throwInsufficientCapacityException() const +{ + throw InsufficientCapacityException("BitStreamWriter: Reached end of bit buffer!"); +} + +inline void BitStreamWriter::checkCapacity(size_t bitSize) const +{ + if (bitSize > m_bufferBitSize) + { + throwInsufficientCapacityException(); + } +} + +} // namespace zserio diff --git a/runtime/src/zserio/BitStreamWriter.h b/runtime/src/zserio/BitStreamWriter.h new file mode 100644 index 0000000..faf82f4 --- /dev/null +++ b/runtime/src/zserio/BitStreamWriter.h @@ -0,0 +1,346 @@ +#ifndef ZSERIO_BIT_STREAM_WRITER_H_INC +#define ZSERIO_BIT_STREAM_WRITER_H_INC + +#include +#include +#include +#include + +#include "zserio/BitBuffer.h" +#include "zserio/CppRuntimeException.h" +#include "zserio/SizeConvertUtil.h" +#include "zserio/Span.h" +#include "zserio/Types.h" + +namespace zserio +{ + +/** + * Writer class which allows to write various data to the bit stream. + */ +class BitStreamWriter +{ +public: + /** Exception throw in case of insufficient capacity of the given buffer. */ + class InsufficientCapacityException : public CppRuntimeException + { + public: + using CppRuntimeException::CppRuntimeException; + }; + + /** Type for bit position. */ + using BitPosType = size_t; + + /** + * Constructor from externally allocated byte buffer. + * + * \param buffer External byte buffer to create from. + * \param bufferBitSize Size of the buffer in bits. + */ + explicit BitStreamWriter(uint8_t* buffer, size_t bufferBitSize, BitsTag); + + /** + * Constructor from externally allocated byte buffer. + * + * \param buffer External byte buffer to create from. + * \param bufferByteSize Size of the buffer in bytes. + */ + explicit BitStreamWriter(uint8_t* buffer, size_t bufferByteSize); + + /** + * Constructor from externally allocated byte buffer. + * + * \param buffer External buffer to create from as a Span. + */ + explicit BitStreamWriter(Span buffer); + + /** + * Constructor from externally allocated byte buffer with exact bit size. + * + * \param buffer External buffer to create from as a Span. + * \param bufferBitSize Size of the buffer in bits. + */ + explicit BitStreamWriter(Span buffer, size_t bufferBitSize); + + /** + * Constructor from externally allocated bit buffer. + * + * \param bitBuffer External bit buffer to create from. + */ + template + explicit BitStreamWriter(BasicBitBuffer& bitBuffer) : + BitStreamWriter(bitBuffer.getData(), bitBuffer.getBitSize()) + {} + + /** + * Destructor. + */ + ~BitStreamWriter() = default; + + /** + * Copying and moving is disallowed! + * \{ + */ + BitStreamWriter(const BitStreamWriter&) = delete; + BitStreamWriter& operator=(const BitStreamWriter&) = delete; + + BitStreamWriter(const BitStreamWriter&&) = delete; + BitStreamWriter& operator=(BitStreamWriter&&) = delete; + /** + * \} + */ + + /** + * Writes unsigned bits up to 32 bits. + * + * \param data Data to write. + * \param numBits Number of bits to write. + */ + void writeBits(uint32_t data, uint8_t numBits = 32); + + /** + * Writes unsigned bits up to 64 bits. + * + * \param data Data to write. + * \param numBits Number of bits to write. + */ + void writeBits64(uint64_t data, uint8_t numBits = 64); + + /** + * Writes signed bits up to 32 bits. + * + * \param data Data to write. + * \param numBits Number of bits to write. + */ + void writeSignedBits(int32_t data, uint8_t numBits = 32); + + /** + * Writes signed bits up to 64 bits. + * + * \param data Data to write. + * \param numBits Number of bits to write. + */ + void writeSignedBits64(int64_t data, uint8_t numBits = 64); + + /** + * Writes signed variable integer up to 64 bits. + * + * \param data Varint64 to write. + */ + void writeVarInt64(int64_t data); + + /** + * Writes signed variable integer up to 32 bits. + * + * \param data Varint32 to write. + */ + void writeVarInt32(int32_t data); + + /** + * Writes signed variable integer up to 16 bits. + * + * \param data Varint16 to write. + */ + void writeVarInt16(int16_t data); + + /** + * Writes unsigned variable integer up to 64 bits. + * + * \param data Varuint64 to write. + */ + void writeVarUInt64(uint64_t data); + + /** + * Writes unsigned variable integer up to 32 bits. + * + * \param data Varuint32 to write. + */ + void writeVarUInt32(uint32_t data); + + /** + * Writes unsigned variable integer up to 16 bits. + * + * \param data Varuint16 to write. + */ + void writeVarUInt16(uint16_t data); + + /** + * Writes signed variable integer up to 72 bits. + * + * \param data Varuint64 to write. + */ + void writeVarInt(int64_t data); + + /** + * Writes signed variable integer up to 72 bits. + * + * \param data Varuint64 to write. + */ + void writeVarUInt(uint64_t data); + + /** + * Writes variable size integer up to 40 bits. + * + * \param data Varsize to write. + */ + void writeVarSize(uint32_t data); + + /** + * Writes 16-bit float. + * + * \param data Float16 to write. + */ + void writeFloat16(float data); + + /** + * Writes 32-bit float. + * + * \param data Float32 to write. + */ + void writeFloat32(float data); + + /** + * Writes 64-bit float. + * + * \param data Float64 to write. + */ + void writeFloat64(double data); + + /** + * Writes bytes. + * + * \param data Bytes to write. + */ + void writeBytes(Span data); + + /** + * Writes UTF-8 string. + * + * \param data String view to write. + */ + void writeString(std::string_view data); + + /** + * Writes bool as a single bit. + * + * \param data Bool to write. + */ + void writeBool(bool data); + + /** + * Writes bit buffer. + * + * \param bitBuffer Bit buffer to write. + */ + template + void writeBitBuffer(const BasicBitBuffer& bitBuffer) + { + const size_t bitSize = bitBuffer.getBitSize(); + writeVarSize(convertSizeToUInt32(bitSize)); + + Span buffer = bitBuffer.getData(); + size_t numBytesToWrite = bitSize / 8; + const uint8_t numRestBits = static_cast(bitSize - numBytesToWrite * 8); + const BitPosType beginBitPosition = getBitPosition(); + const Span::iterator itEnd = buffer.begin() + numBytesToWrite; + if ((beginBitPosition & 0x07U) != 0) + { + // we are not aligned to byte + for (Span::iterator it = buffer.begin(); it != itEnd; ++it) + { + writeUnsignedBits(*it, 8); + } + } + else + { + // we are aligned to byte + setBitPosition(beginBitPosition + numBytesToWrite * 8); + if (hasWriteBuffer()) + { + (void)std::copy(buffer.begin(), buffer.begin() + numBytesToWrite, + m_buffer.data() + beginBitPosition / 8); + } + } + + if (numRestBits > 0) + { + writeUnsignedBits(static_cast(*itEnd) >> (8U - numRestBits), numRestBits); + } + } + + /** + * Gets current bit position. + * + * \return Current bit position. + */ + BitPosType getBitPosition() const + { + return m_bitIndex; + } + + /** + * Sets current bit position. Use with caution! + * + * \param position New bit position. + */ + void setBitPosition(BitPosType position); + + /** + * Moves current bit position to perform the requested bit alignment. + * + * \param alignment Size of the alignment in bits. + */ + void alignTo(size_t alignment); + + /** + * Gets whether the writer has assigned a write buffer. + * + * \return True when a buffer is assigned. False otherwise. + */ + bool hasWriteBuffer() const + { + return m_buffer.data() != nullptr; + } + + /** + * Gets the write buffer. + * + * \return Pointer to the beginning of write buffer. + */ + const uint8_t* getWriteBuffer() const; + + /** + * Gets the write buffer as span. + * + * \return Span which represents the write buffer. + */ + Span getBuffer() const; + + /** + * Gets size of the underlying buffer in bits. + * + * \return Buffer bit size. + */ + size_t getBufferBitSize() const + { + return m_bufferBitSize; + } + +private: + void writeUnsignedBits(uint32_t data, uint8_t numBits); + void writeUnsignedBits64(uint64_t data, uint8_t numBits); + void writeSignedVarNum(int64_t value, size_t maxVarBytes, size_t numVarBytes); + void writeUnsignedVarNum(uint64_t value, size_t maxVarBytes, size_t numVarBytes); + void writeVarNum(uint64_t value, bool hasSign, bool isNegative, size_t maxVarBytes, size_t numVarBytes); + + void checkCapacity(size_t bitSize) const; + void throwInsufficientCapacityException() const; + + Span m_buffer; + size_t m_bitIndex; + size_t m_bufferBitSize; +}; + +} // namespace zserio + +#endif // ifndef ZSERIO_BIT_STREAM_WRITER_H_INC diff --git a/runtime/test/CMakeLists.txt b/runtime/test/CMakeLists.txt index 3e01eae..24fde0d 100644 --- a/runtime/test/CMakeLists.txt +++ b/runtime/test/CMakeLists.txt @@ -15,6 +15,10 @@ set(ZSERIO_CPP17_RUNTIME_TEST_SRCS zserio/BitBufferTest.cpp zserio/BitFieldUtilTest.cpp zserio/BitPositionUtilTest.cpp + zserio/BitSizeOfCalculatorTest.cpp + zserio/BitStreamReaderTest.cpp + zserio/BitStreamTest.cpp + zserio/BitStreamWriterTest.cpp zserio/BuiltInOperatorsTest.cpp zserio/CppRuntimeExceptionTest.cpp zserio/CppRuntimeVersionTest.cpp diff --git a/runtime/test/zserio/BitSizeOfCalculatorTest.cpp b/runtime/test/zserio/BitSizeOfCalculatorTest.cpp new file mode 100644 index 0000000..3ac7dbd --- /dev/null +++ b/runtime/test/zserio/BitSizeOfCalculatorTest.cpp @@ -0,0 +1,278 @@ +#include "gtest/gtest.h" +#include "zserio/BitSizeOfCalculator.h" +#include "zserio/CppRuntimeException.h" + +namespace zserio +{ + +TEST(BitSizeOfCalculatorTest, bitSizeOfVarInt16) +{ + EXPECT_EQ(8, bitSizeOfVarInt16(0)); + + EXPECT_EQ(8, bitSizeOfVarInt16(static_cast(1U << (0U)))); + EXPECT_EQ(8, bitSizeOfVarInt16(-static_cast(1U << (0U)))); + EXPECT_EQ(8, bitSizeOfVarInt16(static_cast((1U << (6U)) - 1))); + EXPECT_EQ(8, bitSizeOfVarInt16(-static_cast((1U << (6U)) - 1))); + + EXPECT_EQ(16, bitSizeOfVarInt16(static_cast(1U << (6U)))); + EXPECT_EQ(16, bitSizeOfVarInt16(-static_cast(1U << (6U)))); + EXPECT_EQ(16, bitSizeOfVarInt16(static_cast((1U << (6U + 8)) - 1))); + EXPECT_EQ(16, bitSizeOfVarInt16(-static_cast((1U << (6U + 8)) - 1))); + + const int16_t outOfRangeValue = static_cast(1U << (6U + 8)); + ASSERT_THROW(bitSizeOfVarInt16(outOfRangeValue), CppRuntimeException); +} + +TEST(BitSizeOfCalculatorTest, bitSizeOfVarInt32) +{ + EXPECT_EQ(8, bitSizeOfVarInt32(0)); + + EXPECT_EQ(8, bitSizeOfVarInt32(static_cast(1U << (0U)))); + EXPECT_EQ(8, bitSizeOfVarInt32(-static_cast(1U << (0U)))); + EXPECT_EQ(8, bitSizeOfVarInt32(static_cast((1U << (6U)) - 1))); + EXPECT_EQ(8, bitSizeOfVarInt32(-static_cast(((1U << (6U)) - 1)))); + + EXPECT_EQ(16, bitSizeOfVarInt32(static_cast(1U << (6U)))); + EXPECT_EQ(16, bitSizeOfVarInt32(-static_cast(1U << (6U)))); + EXPECT_EQ(16, bitSizeOfVarInt32(static_cast((1U << (6U + 7)) - 1))); + EXPECT_EQ(16, bitSizeOfVarInt32(-static_cast(((1U << (6U + 7)) - 1)))); + + EXPECT_EQ(24, bitSizeOfVarInt32(static_cast(1U << (6U + 7)))); + EXPECT_EQ(24, bitSizeOfVarInt32(-static_cast(1U << (6U + 7)))); + EXPECT_EQ(24, bitSizeOfVarInt32(static_cast((1U << (6U + 7 + 7)) - 1))); + EXPECT_EQ(24, bitSizeOfVarInt32(-static_cast((1U << (6U + 7 + 7)) - 1))); + + EXPECT_EQ(32, bitSizeOfVarInt32(static_cast(1U << (6U + 7 + 7)))); + EXPECT_EQ(32, bitSizeOfVarInt32(-static_cast(1U << (6U + 7 + 7)))); + EXPECT_EQ(32, bitSizeOfVarInt32(static_cast((1U << (6U + 7 + 7 + 8)) - 1))); + EXPECT_EQ(32, bitSizeOfVarInt32(-static_cast((1U << (6U + 7 + 7 + 8)) - 1))); + + const int32_t outOfRangeValue = static_cast(1U << (6U + 7 + 7 + 8)); + ASSERT_THROW(bitSizeOfVarInt32(outOfRangeValue), CppRuntimeException); +} + +TEST(BitSizeOfCalculatorTest, bitSizeOfVarInt64) +{ + EXPECT_EQ(8, bitSizeOfVarInt64(0)); + + EXPECT_EQ(8, bitSizeOfVarInt64(static_cast(UINT64_C(1) << (0U)))); + EXPECT_EQ(8, bitSizeOfVarInt64(-static_cast(UINT64_C(1) << (0U)))); + EXPECT_EQ(8, bitSizeOfVarInt64(static_cast((UINT64_C(1) << (6U)) - 1))); + EXPECT_EQ(8, bitSizeOfVarInt64(-static_cast((UINT64_C(1) << (6U)) - 1))); + + EXPECT_EQ(16, bitSizeOfVarInt64(static_cast(UINT64_C(1) << (6U)))); + EXPECT_EQ(16, bitSizeOfVarInt64(-static_cast(UINT64_C(1) << (6U)))); + EXPECT_EQ(16, bitSizeOfVarInt64(static_cast((UINT64_C(1) << (6U + 7)) - 1))); + EXPECT_EQ(16, bitSizeOfVarInt64(-static_cast((UINT64_C(1) << (6U + 7)) - 1))); + + EXPECT_EQ(24, bitSizeOfVarInt64(static_cast(UINT64_C(1) << (6U + 7)))); + EXPECT_EQ(24, bitSizeOfVarInt64(-static_cast(UINT64_C(1) << (6U + 7)))); + EXPECT_EQ(24, bitSizeOfVarInt64(static_cast((UINT64_C(1) << (6U + 7 + 7)) - 1))); + EXPECT_EQ(24, bitSizeOfVarInt64(-static_cast((UINT64_C(1) << (6U + 7 + 7)) - 1))); + + EXPECT_EQ(32, bitSizeOfVarInt64(static_cast(UINT64_C(1) << (6U + 7 + 7)))); + EXPECT_EQ(32, bitSizeOfVarInt64(-static_cast(UINT64_C(1) << (6U + 7 + 7)))); + EXPECT_EQ(32, bitSizeOfVarInt64(static_cast((UINT64_C(1) << (6U + 7 + 7 + 7)) - 1))); + EXPECT_EQ(32, bitSizeOfVarInt64(-static_cast((UINT64_C(1) << (6U + 7 + 7 + 7)) - 1))); + + EXPECT_EQ(40, bitSizeOfVarInt64(static_cast(UINT64_C(1) << (6U + 7 + 7 + 7)))); + EXPECT_EQ(40, bitSizeOfVarInt64(-static_cast(UINT64_C(1) << (6U + 7 + 7 + 7)))); + EXPECT_EQ(40, bitSizeOfVarInt64(static_cast((UINT64_C(1) << (6U + 7 + 7 + 7 + 7)) - 1))); + EXPECT_EQ(40, bitSizeOfVarInt64(-static_cast((UINT64_C(1) << (6U + 7 + 7 + 7 + 7)) - 1))); + + EXPECT_EQ(48, bitSizeOfVarInt64(static_cast(UINT64_C(1) << (6U + 7 + 7 + 7 + 7)))); + EXPECT_EQ(48, bitSizeOfVarInt64(-static_cast(UINT64_C(1) << (6U + 7 + 7 + 7 + 7)))); + EXPECT_EQ(48, bitSizeOfVarInt64(static_cast((UINT64_C(1) << (6U + 7 + 7 + 7 + 7 + 7)) - 1))); + EXPECT_EQ(48, bitSizeOfVarInt64(-static_cast((UINT64_C(1) << (6U + 7 + 7 + 7 + 7 + 7)) - 1))); + + EXPECT_EQ(56, bitSizeOfVarInt64(static_cast(UINT64_C(1) << (6U + 7 + 7 + 7 + 7 + 7)))); + EXPECT_EQ(56, bitSizeOfVarInt64(-static_cast(UINT64_C(1) << (6U + 7 + 7 + 7 + 7 + 7)))); + EXPECT_EQ(56, bitSizeOfVarInt64(static_cast((UINT64_C(1) << (6U + 7 + 7 + 7 + 7 + 7 + 7)) - 1))); + EXPECT_EQ(56, bitSizeOfVarInt64(-static_cast((UINT64_C(1) << (6U + 7 + 7 + 7 + 7 + 7 + 7)) - 1))); + + EXPECT_EQ(64, bitSizeOfVarInt64(static_cast(UINT64_C(1) << (6U + 7 + 7 + 7 + 7 + 7 + 7)))); + EXPECT_EQ(64, bitSizeOfVarInt64(-static_cast(UINT64_C(1) << (6U + 7 + 7 + 7 + 7 + 7 + 7)))); + EXPECT_EQ( + 64, bitSizeOfVarInt64(static_cast((UINT64_C(1) << (6U + 7 + 7 + 7 + 7 + 7 + 7 + 8)) - 1))); + EXPECT_EQ(64, + bitSizeOfVarInt64(-static_cast((UINT64_C(1) << (6U + 7 + 7 + 7 + 7 + 7 + 7 + 8)) - 1))); + + const int64_t outOfRangeValue = static_cast(UINT64_C(1) << (6U + 7 + 7 + 7 + 7 + 7 + 7 + 8)); + ASSERT_THROW(bitSizeOfVarInt64(outOfRangeValue), CppRuntimeException); +} + +TEST(BitSizeOfCalculatorTest, bitSizeOfVarUInt16) +{ + EXPECT_EQ(8, bitSizeOfVarUInt16(0)); + + EXPECT_EQ(8, bitSizeOfVarUInt16(1U << (0U))); + EXPECT_EQ(8, bitSizeOfVarUInt16((1U << (7U)) - 1)); + + EXPECT_EQ(16, bitSizeOfVarUInt16(1U << (7U))); + EXPECT_EQ(16, bitSizeOfVarUInt16((1U << (7U + 8)) - 1)); + + const uint16_t outOfRangeValue = 1U << (7U + 8); + ASSERT_THROW(bitSizeOfVarUInt16(outOfRangeValue), CppRuntimeException); +} + +TEST(BitSizeOfCalculatorTest, bitSizeOfVarUInt32) +{ + EXPECT_EQ(8, bitSizeOfVarUInt32(0)); + + EXPECT_EQ(8, bitSizeOfVarUInt32(1U << (0U))); + EXPECT_EQ(8, bitSizeOfVarUInt32((1U << (7U)) - 1)); + + EXPECT_EQ(16, bitSizeOfVarUInt32(1U << (7U))); + EXPECT_EQ(16, bitSizeOfVarUInt32((1U << (7U + 7)) - 1)); + + EXPECT_EQ(24, bitSizeOfVarUInt32(1U << (7U + 7))); + EXPECT_EQ(24, bitSizeOfVarUInt32((1U << (7U + 7 + 7)) - 1)); + + EXPECT_EQ(32, bitSizeOfVarUInt32(1U << (7U + 7 + 7))); + EXPECT_EQ(32, bitSizeOfVarUInt32((1U << (7U + 7 + 7 + 8)) - 1)); + + const uint32_t outOfRangeValue = 1U << (7U + 7 + 7 + 8); + ASSERT_THROW(bitSizeOfVarUInt32(outOfRangeValue), CppRuntimeException); +} + +TEST(BitSizeOfCalculatorTest, bitSizeOfVarUInt64) +{ + EXPECT_EQ(8, bitSizeOfVarUInt64(0)); + + EXPECT_EQ(8, bitSizeOfVarUInt64(UINT64_C(1) << (0U))); + EXPECT_EQ(8, bitSizeOfVarUInt64((UINT64_C(1) << (7U)) - 1)); + + EXPECT_EQ(16, bitSizeOfVarUInt64(UINT64_C(1) << (7U))); + EXPECT_EQ(16, bitSizeOfVarUInt64((UINT64_C(1) << (7U + 7)) - 1)); + + EXPECT_EQ(24, bitSizeOfVarUInt64(UINT64_C(1) << (7U + 7))); + EXPECT_EQ(24, bitSizeOfVarUInt64((UINT64_C(1) << (7U + 7 + 7)) - 1)); + + EXPECT_EQ(32, bitSizeOfVarUInt64(UINT64_C(1) << (7U + 7 + 7))); + EXPECT_EQ(32, bitSizeOfVarUInt64((UINT64_C(1) << (7U + 7 + 7 + 7)) - 1)); + + EXPECT_EQ(40, bitSizeOfVarUInt64(UINT64_C(1) << (7U + 7 + 7 + 7))); + EXPECT_EQ(40, bitSizeOfVarUInt64((UINT64_C(1) << (7U + 7 + 7 + 7 + 7)) - 1)); + + EXPECT_EQ(48, bitSizeOfVarUInt64(UINT64_C(1) << (7U + 7 + 7 + 7 + 7))); + EXPECT_EQ(48, bitSizeOfVarUInt64((UINT64_C(1) << (7U + 7 + 7 + 7 + 7 + 7)) - 1)); + + EXPECT_EQ(56, bitSizeOfVarUInt64(UINT64_C(1) << (7U + 7 + 7 + 7 + 7 + 7))); + EXPECT_EQ(56, bitSizeOfVarUInt64((UINT64_C(1) << (7U + 7 + 7 + 7 + 7 + 7 + 7)) - 1)); + + EXPECT_EQ(64, bitSizeOfVarUInt64(UINT64_C(1) << (7U + 7 + 7 + 7 + 7 + 7 + 7))); + EXPECT_EQ(64, bitSizeOfVarUInt64((UINT64_C(1) << (7U + 7 + 7 + 7 + 7 + 7 + 7 + 8)) - 1)); + + const uint64_t outOfRangeValue = UINT64_C(1) << (7U + 7 + 7 + 7 + 7 + 7 + 7 + 8); + ASSERT_THROW(bitSizeOfVarUInt64(outOfRangeValue), CppRuntimeException); +} + +TEST(BitSizeOfCalculatorTest, bitSizeOfVarInt) +{ + EXPECT_EQ(8, bitSizeOfVarInt(INT64_C(0))); + EXPECT_EQ(8, bitSizeOfVarInt(-static_cast(UINT64_C(1) << 6U) + 1)); + EXPECT_EQ(8, bitSizeOfVarInt(static_cast(UINT64_C(1) << 6U) - 1)); + EXPECT_EQ(16, bitSizeOfVarInt(-static_cast(UINT64_C(1) << 6U))); + EXPECT_EQ(16, bitSizeOfVarInt(static_cast(UINT64_C(1) << 6U))); + EXPECT_EQ(16, bitSizeOfVarInt(-static_cast(UINT64_C(1) << 13U) + 1)); + EXPECT_EQ(16, bitSizeOfVarInt(static_cast(UINT64_C(1) << 13U) - 1)); + EXPECT_EQ(24, bitSizeOfVarInt(-static_cast(UINT64_C(1) << 13U))); + EXPECT_EQ(24, bitSizeOfVarInt(static_cast(UINT64_C(1) << 13U))); + EXPECT_EQ(24, bitSizeOfVarInt(-static_cast(UINT64_C(1) << 20U) + 1)); + EXPECT_EQ(24, bitSizeOfVarInt(static_cast(UINT64_C(1) << 20U) - 1)); + EXPECT_EQ(32, bitSizeOfVarInt(-static_cast(UINT64_C(1) << 20U))); + EXPECT_EQ(32, bitSizeOfVarInt(static_cast(UINT64_C(1) << 20U))); + EXPECT_EQ(32, bitSizeOfVarInt(-static_cast(UINT64_C(1) << 27U) + 1)); + EXPECT_EQ(32, bitSizeOfVarInt(static_cast(UINT64_C(1) << 27U) - 1)); + EXPECT_EQ(40, bitSizeOfVarInt(-static_cast(UINT64_C(1) << 27U))); + EXPECT_EQ(40, bitSizeOfVarInt(static_cast(UINT64_C(1) << 27U))); + EXPECT_EQ(40, bitSizeOfVarInt(-static_cast(UINT64_C(1) << 34U) + 1)); + EXPECT_EQ(40, bitSizeOfVarInt(static_cast(UINT64_C(1) << 34U) - 1)); + EXPECT_EQ(48, bitSizeOfVarInt(-static_cast(UINT64_C(1) << 34U))); + EXPECT_EQ(48, bitSizeOfVarInt(static_cast(UINT64_C(1) << 34U))); + EXPECT_EQ(48, bitSizeOfVarInt(-static_cast(UINT64_C(1) << 41U) + 1)); + EXPECT_EQ(48, bitSizeOfVarInt(static_cast(UINT64_C(1) << 41U) - 1)); + EXPECT_EQ(56, bitSizeOfVarInt(-static_cast(UINT64_C(1) << 41U))); + EXPECT_EQ(56, bitSizeOfVarInt(static_cast(UINT64_C(1) << 41U))); + EXPECT_EQ(56, bitSizeOfVarInt(-static_cast(UINT64_C(1) << 48U) + 1)); + EXPECT_EQ(56, bitSizeOfVarInt(static_cast(UINT64_C(1) << 48U) - 1)); + EXPECT_EQ(64, bitSizeOfVarInt(-static_cast(UINT64_C(1) << 48U))); + EXPECT_EQ(64, bitSizeOfVarInt(static_cast(UINT64_C(1) << 48U))); + EXPECT_EQ(64, bitSizeOfVarInt(-static_cast(UINT64_C(1) << 55U) + 1)); + EXPECT_EQ(64, bitSizeOfVarInt(static_cast(UINT64_C(1) << 55U) - 1)); + EXPECT_EQ(72, bitSizeOfVarInt(-static_cast(UINT64_C(1) << 55U))); + EXPECT_EQ(72, bitSizeOfVarInt(static_cast(UINT64_C(1) << 55U))); + EXPECT_EQ(72, bitSizeOfVarInt(INT64_MIN + 1)); + EXPECT_EQ(72, bitSizeOfVarInt(INT64_MAX)); + + // special case, INT64_MIN is stored as -0 + EXPECT_EQ(8, bitSizeOfVarInt(INT64_MIN)); +} + +TEST(BitSizeOfCalculatorTest, bitSizeOfVarUInt) +{ + EXPECT_EQ(8, bitSizeOfVarUInt(UINT64_C(0))); + EXPECT_EQ(8, bitSizeOfVarUInt((UINT64_C(1) << 7U) - 1)); + EXPECT_EQ(16, bitSizeOfVarUInt((UINT64_C(1) << 7U))); + EXPECT_EQ(16, bitSizeOfVarUInt((UINT64_C(1) << 14U) - 1)); + EXPECT_EQ(24, bitSizeOfVarUInt((UINT64_C(1) << 14U))); + EXPECT_EQ(24, bitSizeOfVarUInt((UINT64_C(1) << 21U) - 1)); + EXPECT_EQ(32, bitSizeOfVarUInt((UINT64_C(1) << 21U))); + EXPECT_EQ(32, bitSizeOfVarUInt((UINT64_C(1) << 28U) - 1)); + EXPECT_EQ(40, bitSizeOfVarUInt((UINT64_C(1) << 28U))); + EXPECT_EQ(40, bitSizeOfVarUInt((UINT64_C(1) << 35U) - 1)); + EXPECT_EQ(48, bitSizeOfVarUInt((UINT64_C(1) << 35U))); + EXPECT_EQ(48, bitSizeOfVarUInt((UINT64_C(1) << 42U) - 1)); + EXPECT_EQ(56, bitSizeOfVarUInt((UINT64_C(1) << 42U))); + EXPECT_EQ(56, bitSizeOfVarUInt((UINT64_C(1) << 49U) - 1)); + EXPECT_EQ(64, bitSizeOfVarUInt((UINT64_C(1) << 49U))); + EXPECT_EQ(64, bitSizeOfVarUInt((UINT64_C(1) << 56U) - 1)); + EXPECT_EQ(72, bitSizeOfVarUInt((UINT64_C(1) << 56U))); + EXPECT_EQ(72, bitSizeOfVarUInt(UINT64_MAX)); +} + +TEST(BitSizeOfCalculatorTest, bitSizeOfVarSize) +{ + EXPECT_EQ(8, bitSizeOfVarSize(0)); + + EXPECT_EQ(8, bitSizeOfVarSize(1U << (0U))); + EXPECT_EQ(8, bitSizeOfVarSize((1U << (7U)) - 1)); + + EXPECT_EQ(16, bitSizeOfVarSize(1U << (7U))); + EXPECT_EQ(16, bitSizeOfVarSize((1U << (7U + 7)) - 1)); + + EXPECT_EQ(24, bitSizeOfVarSize(1U << (7U + 7))); + EXPECT_EQ(24, bitSizeOfVarSize((1U << (7U + 7 + 7)) - 1)); + + EXPECT_EQ(32, bitSizeOfVarSize(1U << (7U + 7 + 7))); + EXPECT_EQ(32, bitSizeOfVarSize((1U << (7U + 7 + 7 + 7)) - 1)); + + EXPECT_EQ(40, bitSizeOfVarSize(1U << (7U + 7 + 7 + 7))); + EXPECT_EQ(40, bitSizeOfVarSize((1U << (2U + 7 + 7 + 7 + 8)) - 1)); + + const uint32_t outOfRangeValue = 1U << (2U + 7 + 7 + 7 + 8); + ASSERT_THROW(bitSizeOfVarSize(outOfRangeValue), CppRuntimeException); +} + +TEST(BitSizeOfCalculatorTest, bitSizeOfString) +{ + EXPECT_EQ((1 + 1) * 8, bitSizeOfString(std::string("T"))); + EXPECT_EQ((1 + 4) * 8, bitSizeOfString(std::string("Test"))); + + const size_t testStringLength = static_cast(1U << 7U); + std::string testString(testStringLength, '\xAB'); + EXPECT_EQ((2 + testStringLength) * 8, bitSizeOfString(testString)); +} + +TEST(BitSizeOfCalculatorTest, bitSizeOfBitBuffer) +{ + EXPECT_EQ(8 + 8, bitSizeOfBitBuffer(BitBuffer(std::vector({0xAB, 0xC0}), 8))); + EXPECT_EQ(8 + 11, bitSizeOfBitBuffer(BitBuffer(std::vector({0xAB, 0xC0}), 11))); + EXPECT_EQ(8 + 16, bitSizeOfBitBuffer(BitBuffer(std::vector({0xAB, 0xCD}), 16))); + EXPECT_EQ(8 + 16, bitSizeOfBitBuffer(BitBuffer(std::vector({0xAB, 0xCD})))); + + EXPECT_EQ(8 + 15 * 8 + 7, bitSizeOfBitBuffer(BitBuffer(std::vector(16), 127))); + EXPECT_EQ(16 + 16 * 8, bitSizeOfBitBuffer(BitBuffer(std::vector(16), 128))); +} + +} // namespace zserio diff --git a/runtime/test/zserio/BitStreamReaderTest.cpp b/runtime/test/zserio/BitStreamReaderTest.cpp new file mode 100644 index 0000000..33f35f2 --- /dev/null +++ b/runtime/test/zserio/BitStreamReaderTest.cpp @@ -0,0 +1,176 @@ +#include +#include + +#include "gtest/gtest.h" +#include "zserio/BitStreamReader.h" +#include "zserio/CppRuntimeException.h" + +namespace zserio +{ + +class BitStreamReaderTest : public ::testing::Test +{ +public: + BitStreamReaderTest() : + m_byteBuffer(), + m_reader(m_byteBuffer.data(), m_byteBuffer.size()) + { + m_byteBuffer.fill(0); + } + +protected: + std::array m_byteBuffer; + BitStreamReader m_reader; +}; + +TEST_F(BitStreamReaderTest, spanConstructor) +{ + const std::array data = {0xAE, 0xEA, 0x80}; + const Span span(data); + BitStreamReader reader(span); + + ASSERT_EQ(span.size() * 8, reader.getBufferBitSize()); + ASSERT_EQ(0xAEE, reader.readBits(12)); + ASSERT_EQ(0xA, reader.readBits(4)); + ASSERT_EQ(0x80, reader.readBits(8)); + + ASSERT_THROW(reader.readBits(1), CppRuntimeException); +} + +TEST_F(BitStreamReaderTest, spanConstructorWithBitSize) +{ + const std::array data = {0xAE, 0xEA, 0x80}; + const Span span(data); + BitStreamReader reader(span, 23); + ASSERT_THROW(BitStreamReader wrongReader(span, 25), CppRuntimeException); + + ASSERT_EQ(23, reader.getBufferBitSize()); + ASSERT_EQ(0xAEE, reader.readBits(12)); + ASSERT_EQ(0xA, reader.readBits(4)); + ASSERT_EQ(0x40, reader.readBits(7)); + + ASSERT_THROW(reader.readBits(1), CppRuntimeException); +} + +TEST_F(BitStreamReaderTest, bitBufferConstructor) +{ + const std::vector data = {0xAE, 0xEA, 0x80}; + BitBuffer bitBuffer(data, 17); + BitStreamReader reader(bitBuffer); + + ASSERT_EQ(bitBuffer.getBitSize(), reader.getBufferBitSize()); + ASSERT_EQ(0xAEE, reader.readBits(12)); + ASSERT_EQ(0xA, reader.readBits(4)); + ASSERT_EQ(1, reader.readBits(1)); + + ASSERT_THROW(reader.readBits(1), CppRuntimeException); + + ASSERT_THROW(BitStreamReader(nullptr, std::numeric_limits::max() / 8), CppRuntimeException); +} + +TEST_F(BitStreamReaderTest, bitBufferConstructorOverflow) +{ + const std::vector data = {0xFF, 0xFF, 0xF0}; + BitBuffer bitBuffer(data, 19); + BitStreamReader reader(bitBuffer); + + ASSERT_EQ(bitBuffer.getBitSize(), reader.getBufferBitSize()); + ASSERT_THROW(reader.readBits(20), CppRuntimeException); +} + +TEST_F(BitStreamReaderTest, readUnalignedData) +{ + // number expected to read at offset + const uint8_t testValue = 123; + + for (uint8_t offset = 0; offset <= 64; ++offset) + { + BitBuffer buffer(8U + offset); + + // write test value at offset to data buffer + buffer.getData()[offset / 8U] = static_cast( + buffer.getData()[offset / 8U] | static_cast(testValue >> (offset % 8U))); + if (offset % 8 != 0) // don't write behind the buffer + { + buffer.getData()[offset / 8U + 1] = static_cast(buffer.getData()[offset / 8U + 1] | + static_cast(testValue << (8U - (offset % 8U)))); + } + + BitStreamReader reader(buffer); + + // read offset bits + if (offset > 0) + { + ASSERT_EQ(0, reader.readBits64(offset)); + } + + // read magic number + ASSERT_EQ(testValue, reader.readBits(8)) << "Offset: " << offset; + + // check eof + ASSERT_THROW(reader.readBits(1), CppRuntimeException) << "Offset: " << offset; + } +} + +TEST_F(BitStreamReaderTest, readBits) +{ + // check invalid bitlength acceptance + ASSERT_THROW(m_reader.readBits(255), CppRuntimeException); + ASSERT_THROW(m_reader.readBits(0), CppRuntimeException); + ASSERT_THROW(m_reader.readBits(33), CppRuntimeException); +} + +TEST_F(BitStreamReaderTest, readBits64) +{ + // check invalid bit length acceptance + ASSERT_THROW(m_reader.readBits64(255), CppRuntimeException); + ASSERT_THROW(m_reader.readBits64(0), CppRuntimeException); + ASSERT_THROW(m_reader.readBits64(65), CppRuntimeException); +} + +TEST_F(BitStreamReaderTest, readSignedBits) +{ + // check invalid bit length acceptance + ASSERT_THROW(m_reader.readSignedBits(255), CppRuntimeException); + ASSERT_THROW(m_reader.readSignedBits(0), CppRuntimeException); + ASSERT_THROW(m_reader.readSignedBits(33), CppRuntimeException); +} + +TEST_F(BitStreamReaderTest, readSignedBits64) +{ + // check invalid bit length acceptance + ASSERT_THROW(m_reader.readSignedBits64(255), CppRuntimeException); + ASSERT_THROW(m_reader.readSignedBits64(0), CppRuntimeException); + ASSERT_THROW(m_reader.readSignedBits64(65), CppRuntimeException); +} + +TEST_F(BitStreamReaderTest, readVarSize) +{ + { + // overflow, 2^32 - 1 is too much ({ 0x83, 0xFF, 0xFF, 0xFF, 0xFF } is the maximum) + const std::array buffer = {0x87, 0xFF, 0xFF, 0xFF, 0xFF}; + zserio::BitStreamReader reader(buffer.data(), buffer.size()); + ASSERT_THROW(reader.readVarSize(), CppRuntimeException); + } + + { + // overflow, 2^36 - 1 is too much ({ 0x83, 0xFF, 0xFF, 0xFF, 0xFF } is the maximum) + const std::array buffer = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + zserio::BitStreamReader reader(buffer.data(), buffer.size()); + ASSERT_THROW(reader.readVarSize(), CppRuntimeException); + } +} + +TEST_F(BitStreamReaderTest, getBitPosition) +{ + ASSERT_EQ(0, m_reader.getBitPosition()); + m_reader.readBits(10); + ASSERT_EQ(10, m_reader.getBitPosition()); +} + +TEST_F(BitStreamReaderTest, getBufferBitSize) +{ + ASSERT_EQ(m_byteBuffer.size() * 8, m_reader.getBufferBitSize()); +} + +} // namespace zserio diff --git a/runtime/test/zserio/BitStreamTest.cpp b/runtime/test/zserio/BitStreamTest.cpp new file mode 100644 index 0000000..db01389 --- /dev/null +++ b/runtime/test/zserio/BitStreamTest.cpp @@ -0,0 +1,712 @@ +#include +#include +#include +#include + +#include "gtest/gtest.h" +#include "zserio/BitStreamReader.h" +#include "zserio/BitStreamWriter.h" +#include "zserio/CppRuntimeException.h" +#include "zserio/Types.h" +#include "zserio/Vector.h" + +namespace zserio +{ + +class BitStreamTest : public ::testing::Test +{ +public: + BitStreamTest() : + m_byteBuffer(), + m_externalWriter(m_byteBuffer.data(), m_byteBuffer.size()), + m_dummyWriter(nullptr, 0) + { + m_byteBuffer.fill(0); + } + +protected: + template + void testImpl(const std::array& values, std::function writerFunc, + std::function readerFunc, uint8_t maxStartBitPos) + { + testBitStreamValues(values, m_externalWriter, writerFunc, readerFunc, maxStartBitPos); + testBitStreamValues(values, m_dummyWriter, writerFunc, readerFunc, maxStartBitPos); + } + + template + void testBitStreamValues(const std::array& values, BitStreamWriter& writer, + std::function writerFunc, std::function readerFunc, + uint8_t maxStartBitPos) + { + for (uint8_t bitPos = 0; bitPos < maxStartBitPos; ++bitPos) + { + if (bitPos > 0) + { + writer.writeBits64(0, bitPos); + } + for (size_t i = 0; i < N; ++i) + { + writerFunc(writer, values.at(i)); + } + + if (!writer.hasWriteBuffer()) + { + continue; + } + + BitStreamReader reader(writer.getWriteBuffer(), writer.getBitPosition(), BitsTag()); + if (bitPos > 0) + { + reader.readBits64(bitPos); + } + for (size_t i = 0; i < N; ++i) + { + ASSERT_EQ(readerFunc(reader), values.at(i)) << "[bitPos=" << bitPos << "]"; + } + + writer.setBitPosition(0); + m_byteBuffer.fill(0); + } + } + + void testReadBits(BitStreamWriter& writer) + { + writer.writeBits(1, 1); + writer.writeBits(2, 2); + writer.writeBits(42, 12); + writer.writeBits(15999999, 24); + writer.writeBits(7, 3); + + if (!writer.hasWriteBuffer()) + { + return; + } + + BitStreamReader reader(writer.getWriteBuffer(), writer.getBitPosition(), BitsTag()); + ASSERT_EQ(1, reader.readBits(1)); + ASSERT_EQ(2, reader.readBits(2)); + ASSERT_EQ(42, reader.readBits(12)); + ASSERT_EQ(15999999, reader.readBits(24)); + ASSERT_EQ(7, reader.readBits(3)); + } + + void testReadBits64(BitStreamWriter& writer) + { + writer.writeBits(1, 1); + writer.writeBits64(UINT64_C(42424242424242), 48); + writer.writeBits64(UINT64_C(0xFFFFFFFFFFFFFFFE), 64); + + if (!writer.hasWriteBuffer()) + { + return; + } + + BitStreamReader reader(writer.getWriteBuffer(), writer.getBitPosition(), BitsTag()); + ASSERT_EQ(1, reader.readBits(1)); + ASSERT_EQ(UINT64_C(42424242424242), reader.readBits64(48)); + ASSERT_EQ(UINT64_C(0xFFFFFFFFFFFFFFFE), reader.readBits64(64)); + } + + void testReadSignedBits(BitStreamWriter& writer) + { + writer.writeSignedBits(-1, 5); + writer.writeSignedBits(3, 12); + writer.writeSignedBits(-142, 9); + + if (!writer.hasWriteBuffer()) + { + return; + } + + BitStreamReader reader(writer.getWriteBuffer(), writer.getBitPosition(), BitsTag()); + ASSERT_EQ(-1, reader.readSignedBits(5)); + ASSERT_EQ(3, reader.readSignedBits(12)); + ASSERT_EQ(-142, reader.readSignedBits(9)); + } + + void testReadSignedBits64(BitStreamWriter& writer) + { + writer.writeSignedBits64(INT64_C(1), 4); + writer.writeSignedBits64(INT64_C(-1), 48); + writer.writeSignedBits64(INT64_C(-42424242), 61); + writer.writeSignedBits64(INT64_C(-820816), 32); + + if (!writer.hasWriteBuffer()) + { + return; + } + + BitStreamReader reader(writer.getWriteBuffer(), writer.getBitPosition(), BitsTag()); + ASSERT_EQ(INT64_C(1), reader.readSignedBits(4)); + ASSERT_EQ(INT64_C(-1), reader.readSignedBits64(48)); + ASSERT_EQ(INT64_C(-42424242), reader.readSignedBits64(61)); + ASSERT_EQ(INT64_C(-820816), reader.readSignedBits64(32)); + } + + void testAlignedBytes(BitStreamWriter& writer) + { + // reads aligned data directly from buffer, bit cache should remain empty + writer.writeBits(UINT8_C(0xCA), 8); + writer.writeBits(UINT16_C(0xCAFE), 16); + writer.writeBits(UINT32_C(0xCAFEC0), 24); + writer.writeBits(UINT32_C(0xCAFEC0DE), 32); + writer.writeBits64(UINT64_C(0xCAFEC0DEDE), 40); + writer.writeBits64(UINT64_C(0xCAFEC0DEDEAD), 48); + writer.writeBits64(UINT64_C(0xCAFEC0DEDEADFA), 56); + writer.writeBits64(UINT64_C(0xCAFEC0DEDEADFACE), 64); + + if (!writer.hasWriteBuffer()) + { + return; + } + + BitStreamReader reader(writer.getWriteBuffer(), writer.getBitPosition(), BitsTag()); + ASSERT_EQ(UINT8_C(0xCA), reader.readBits(8)); + ASSERT_EQ(UINT16_C(0xCAFE), reader.readBits(16)); + ASSERT_EQ(UINT32_C(0xCAFEC0), reader.readBits(24)); + ASSERT_EQ(UINT32_C(0xCAFEC0DE), reader.readBits(32)); + ASSERT_EQ(UINT64_C(0xCAFEC0DEDE), reader.readBits64(40)); + ASSERT_EQ(UINT64_C(0xCAFEC0DEDEAD), reader.readBits64(48)); + ASSERT_EQ(UINT64_C(0xCAFEC0DEDEADFA), reader.readBits64(56)); + ASSERT_EQ(UINT64_C(0xCAFEC0DEDEADFACE), reader.readBits64(64)); + } + + void testSetBitPosition(BitStreamWriter& writer) + { + ASSERT_EQ(0, writer.getBitPosition()); + writer.writeBits(1, 1); + ASSERT_EQ(1, writer.getBitPosition()); + writer.alignTo(4); + ASSERT_EQ(4, writer.getBitPosition()); + writer.writeBits(5, 5); + ASSERT_EQ(9, writer.getBitPosition()); + if (writer.hasWriteBuffer()) + { + ASSERT_THROW(writer.setBitPosition(m_byteBuffer.size() * 8 + 1), CppRuntimeException); + } + else + { + // dummy buffer + writer.setBitPosition(m_byteBuffer.size() * 8 + 1); + ASSERT_EQ(m_byteBuffer.size() * 8 + 1, writer.getBitPosition()); + } + writer.setBitPosition(4); + ASSERT_EQ(4, writer.getBitPosition()); + writer.writeBits(3, 3); + ASSERT_EQ(7, writer.getBitPosition()); + + if (!writer.hasWriteBuffer()) + { + return; + } + + BitStreamReader reader(writer.getWriteBuffer(), writer.getBitPosition(), BitsTag()); + ASSERT_EQ(0, reader.getBitPosition()); + ASSERT_EQ(1, reader.readBits(1)); + ASSERT_EQ(1, reader.getBitPosition()); + reader.alignTo(4); + ASSERT_EQ(4, reader.getBitPosition()); + ASSERT_EQ(3, reader.readBits(3)); + ASSERT_EQ(7, reader.getBitPosition()); + ASSERT_THROW(reader.setBitPosition(writer.getBitPosition() + 1), CppRuntimeException); + + reader.setBitPosition(4); + ASSERT_EQ(4, reader.getBitPosition()); + ASSERT_EQ(3, reader.readBits(3)); + ASSERT_EQ(7, reader.getBitPosition()); + } + + void testAlignTo(BitStreamWriter& writer) + { + writer.writeBits(1, 1); + writer.alignTo(4); + ASSERT_EQ(4, writer.getBitPosition()); + writer.writeBits(1, 1); + writer.alignTo(4); + ASSERT_EQ(8, writer.getBitPosition()); + writer.writeBits(37, 11); + writer.alignTo(8); + ASSERT_EQ(24, writer.getBitPosition()); + writer.writeBits(1, 1); + writer.alignTo(16); + ASSERT_EQ(32, writer.getBitPosition()); + writer.writeBits(13, 13); + writer.alignTo(32); + ASSERT_EQ(64, writer.getBitPosition()); + writer.writeBits(42, 15); + writer.alignTo(64); + ASSERT_EQ(128, writer.getBitPosition()); + writer.writeBits(99, 9); + ASSERT_EQ(137, writer.getBitPosition()); + + if (!writer.hasWriteBuffer()) + { + return; + } + + BitStreamReader reader(writer.getWriteBuffer(), writer.getBitPosition(), BitsTag()); + ASSERT_EQ(1, reader.readBits(1)); + reader.alignTo(4); + ASSERT_EQ(1, reader.readBits(1)); + reader.alignTo(4); + ASSERT_EQ(37, reader.readBits(11)); + reader.alignTo(8); + ASSERT_EQ(1, reader.readBits(1)); + reader.alignTo(16); + ASSERT_EQ(13, reader.readBits(13)); + reader.alignTo(32); + ASSERT_EQ(42, reader.readBits(15)); + reader.alignTo(64); + ASSERT_EQ(99, reader.readBits(9)); + ASSERT_EQ(137, reader.getBitPosition()); + } + + std::array m_byteBuffer; + BitStreamWriter m_externalWriter; + BitStreamWriter m_dummyWriter; +}; + +TEST_F(BitStreamTest, readBits) +{ + testReadBits(m_externalWriter); + testReadBits(m_dummyWriter); +} + +TEST_F(BitStreamTest, readBits64) +{ + testReadBits64(m_externalWriter); + testReadBits64(m_dummyWriter); +} + +TEST_F(BitStreamTest, readSignedBits) +{ + testReadSignedBits(m_externalWriter); + testReadSignedBits(m_dummyWriter); +} + +TEST_F(BitStreamTest, readSignedBits64) +{ + testReadSignedBits64(m_externalWriter); + testReadSignedBits64(m_dummyWriter); +} + +TEST_F(BitStreamTest, alignedBytes) +{ + testAlignedBytes(m_externalWriter); + testAlignedBytes(m_dummyWriter); +} + +TEST_F(BitStreamTest, readVarInt64) +{ + const std::array values = { + INT64_C(0), + INT64_C(-32), + INT64_C(32), + INT64_C(-4096), + INT64_C(4096), + INT64_C(-524288), + INT64_C(524288), + INT64_C(-67108864), + INT64_C(67108864), + INT64_C(-8589934592), + INT64_C(8589934592), + INT64_C(-1099511627776), + INT64_C(1099511627776), + INT64_C(-140737488355328), + INT64_C(140737488355328), + INT64_C(-18014398509481984), + INT64_C(18014398509481984), + + (INT64_C(1) << (0)), + (INT64_C(1) << (6)) - 1, + + (INT64_C(1) << (6)), + (INT64_C(1) << (6 + 7)) - 1, + + (INT64_C(1) << (6 + 7)), + (INT64_C(1) << (6 + 7 + 7)) - 1, + + (INT64_C(1) << (6 + 7 + 7)), + (INT64_C(1) << (6 + 7 + 7 + 7)) - 1, + + (INT64_C(1) << (6 + 7 + 7 + 7)), + (INT64_C(1) << (6 + 7 + 7 + 7 + 7)) - 1, + + (INT64_C(1) << (6 + 7 + 7 + 7 + 7)), + (INT64_C(1) << (6 + 7 + 7 + 7 + 7 + 7)) - 1, + + (INT64_C(1) << (6 + 7 + 7 + 7 + 7 + 7)), + (INT64_C(1) << (6 + 7 + 7 + 7 + 7 + 7 + 7)) - 1, + + (INT64_C(1) << (6 + 7 + 7 + 7 + 7 + 7 + 7)), + (INT64_C(1) << (6 + 7 + 7 + 7 + 7 + 7 + 7 + 8)) - 1, + }; + + std::function writerFunc = &BitStreamWriter::writeVarInt64; + std::function readerFunc = &BitStreamReader::readVarInt64; + + testImpl(values, writerFunc, readerFunc, 63); +} + +TEST_F(BitStreamTest, readVarInt32) +{ + const std::array values = { + static_cast(0), + static_cast(-32), + static_cast(32), + static_cast(-4096), + static_cast(4096), + static_cast(-524288), + static_cast(524288), + static_cast(-67108864), + static_cast(67108864), + + static_cast(1U << (0U)), + static_cast(1U << (6U)) - 1, + + static_cast(1U << (6U)), + static_cast(1U << (6U + 7)) - 1, + + static_cast(1U << (6U + 7)), + static_cast(1U << (6U + 7 + 7)) - 1, + + static_cast(1U << (6U + 7 + 7)), + static_cast(1U << (6U + 7 + 7 + 8)) - 1, + }; + + std::function writerFunc = &BitStreamWriter::writeVarInt32; + std::function readerFunc = &BitStreamReader::readVarInt32; + + testImpl(values, writerFunc, readerFunc, 31); +} + +TEST_F(BitStreamTest, readVarInt16) +{ + const std::array values = { + static_cast(0), + static_cast(-32), + static_cast(32), + static_cast(-4096), + static_cast(4096), + + static_cast(1U << (0U)), + static_cast(1U << (6U)) - 1, + + static_cast(1U << (6U)), + static_cast(1U << (6 + 8U)) - 1, + }; + + std::function writerFunc = &BitStreamWriter::writeVarInt16; + std::function readerFunc = &BitStreamReader::readVarInt16; + + testImpl(values, writerFunc, readerFunc, 15); +} + +TEST_F(BitStreamTest, readVarUInt64) +{ + const std::array values = { + 0, + 262144, + 524288, + + (UINT64_C(1) << (0U)), + (UINT64_C(1) << (7U)) - 1, + + (UINT64_C(1) << (7U)), + (UINT64_C(1) << (7U + 7)) - 1, + + (UINT64_C(1) << (7U + 7)), + (UINT64_C(1) << (7U + 7 + 7)) - 1, + + (UINT64_C(1) << (7U + 7 + 7)), + (UINT64_C(1) << (7U + 7 + 7 + 7)) - 1, + + (UINT64_C(1) << (7U + 7 + 7 + 7)), + (UINT64_C(1) << (7U + 7 + 7 + 7 + 7)) - 1, + + (UINT64_C(1) << (7U + 7 + 7 + 7 + 7)), + (UINT64_C(1) << (7U + 7 + 7 + 7 + 7 + 7)) - 1, + + (UINT64_C(1) << (7U + 7 + 7 + 7 + 7 + 7)), + (UINT64_C(1) << (7U + 7 + 7 + 7 + 7 + 7 + 7)) - 1, + + (UINT64_C(1) << (7U + 7 + 7 + 7 + 7 + 7 + 7)), + (UINT64_C(1) << (7U + 7 + 7 + 7 + 7 + 7 + 7 + 8)) - 1, + }; + + std::function writerFunc = &BitStreamWriter::writeVarUInt64; + std::function readerFunc = &BitStreamReader::readVarUInt64; + + testImpl(values, writerFunc, readerFunc, 63); +} + +TEST_F(BitStreamTest, readVarUInt32) +{ + const std::array values = { + 0, + 65536, + 131072, + + (1U << (0U)), + (1U << (7U)) - 1, + + (1U << (7U)), + (1U << (7U + 7)) - 1, + + (1U << (7U + 7)), + (1U << (7U + 7 + 7)) - 1, + + (1U << (7U + 7 + 7)), + (1U << (7U + 7 + 7 + 8)) - 1, + }; + + std::function writerFunc = &BitStreamWriter::writeVarUInt32; + std::function readerFunc = &BitStreamReader::readVarUInt32; + + testImpl(values, writerFunc, readerFunc, 31); +} + +TEST_F(BitStreamTest, readVarUInt16) +{ + const std::array values = { + 0, + 8192, + 16384, + + (1U << (0U)), + (1U << (6U)) - 1, + + (1U << (6U)), + (1U << (6U + 8)) - 1, + }; + + std::function writerFunc = &BitStreamWriter::writeVarUInt16; + std::function readerFunc = &BitStreamReader::readVarUInt16; + + testImpl(values, writerFunc, readerFunc, 15); +} + +TEST_F(BitStreamTest, readVarInt) +{ + const std::array values = { + // 1 byte + 0, + -1, + 1, + -static_cast(UINT64_C(1) << 6U) + 1, + static_cast(UINT64_C(1) << 6U) - 1, + // 2 bytes + -static_cast(UINT64_C(1) << 6U), + static_cast(UINT64_C(1) << 6U), + -static_cast(UINT64_C(1) << 13U) + 1, + static_cast(UINT64_C(1) << 13U) - 1, + // 3 bytes + -static_cast(UINT64_C(1) << 13U), + static_cast(UINT64_C(1) << 13U), + -static_cast(UINT64_C(1) << 20U) + 1, + static_cast(UINT64_C(1) << 20U) - 1, + // 4 bytes + -static_cast(UINT64_C(1) << 20U), + static_cast(UINT64_C(1) << 20U), + -static_cast(UINT64_C(1) << 27U) + 1, + static_cast(UINT64_C(1) << 27U) - 1, + // 5 bytes + -static_cast(UINT64_C(1) << 27U), + static_cast(UINT64_C(1) << 27U), + -static_cast(UINT64_C(1) << 34U) + 1, + static_cast(UINT64_C(1) << 34U) - 1, + // 6 bytes + -static_cast(UINT64_C(1) << 34U), + static_cast(UINT64_C(1) << 34U), + -static_cast(UINT64_C(1) << 41U) + 1, + static_cast(UINT64_C(1) << 41U) - 1, + // 7 bytes + -static_cast(UINT64_C(1) << 41U), + static_cast(UINT64_C(1) << 41U), + -static_cast(UINT64_C(1) << 48U) + 1, + static_cast(UINT64_C(1) << 48U) - 1, + // 8 bytes + -static_cast(UINT64_C(1) << 48U), + static_cast(UINT64_C(1) << 48U), + -static_cast(UINT64_C(1) << 55U) + 1, + static_cast(UINT64_C(1) << 55U) - 1, + // 9 bytes + -static_cast(UINT64_C(1) << 55U), + static_cast(UINT64_C(1) << 55U), + INT64_MIN + 1, + INT64_MAX, + + // special case - stored as -0 (1 byte) + INT64_MIN, + }; + + std::function writerFunc = &BitStreamWriter::writeVarInt; + std::function readerFunc = &BitStreamReader::readVarInt; + + testImpl(values, writerFunc, readerFunc, 63); +} + +TEST_F(BitStreamTest, readVarUInt) +{ + const std::array values = { + // 1 byte + 0, + 1, + (UINT64_C(1) << 7U) - 1, + // 2 bytes + (UINT64_C(1) << 7U), + (UINT64_C(1) << 14U) - 1, + // 3 bytes + (UINT64_C(1) << 14U), + (UINT64_C(1) << 21U) - 1, + // 4 bytes + (UINT64_C(1) << 21U), + (UINT64_C(1) << 28U) - 1, + // 5 bytes + (UINT64_C(1) << 28U), + (UINT64_C(1) << 35U) - 1, + // 6 bytes + (UINT64_C(1) << 35U), + (UINT64_C(1) << 42U) - 1, + // 7 bytes + (UINT64_C(1) << 42U), + (UINT64_C(1) << 49U) - 1, + // 8 bytes + (UINT64_C(1) << 49U), + (UINT64_C(1) << 56U) - 1, + // 9 bytes + (UINT64_C(1) << 56U), + UINT64_MAX, + }; + + std::function writerFunc = &BitStreamWriter::writeVarUInt; + std::function readerFunc = &BitStreamReader::readVarUInt; + + testImpl(values, writerFunc, readerFunc, 63); +} + +TEST_F(BitStreamTest, readVarSize) +{ + const std::array values = { + 0, + 65536, + 131072, + + (1U << (0U)), + (1U << (7U)) - 1, + + (1U << (7U)), + (1U << (7U + 7)) - 1, + + (1U << (7U + 7)), + (1U << (7U + 7 + 7)) - 1, + + (1U << (7U + 7 + 7)), + (1U << (7U + 7 + 7 + 7)) - 1, + + (1U << (7U + 7 + 7 + 7)), + (1U << (7U + 7 + 7 + 7 + 3)) - 1, + }; + + std::function writerFunc = &BitStreamWriter::writeVarSize; + std::function readerFunc = &BitStreamReader::readVarSize; + + testImpl(values, writerFunc, readerFunc, 31); +} + +TEST_F(BitStreamTest, readFloat16) +{ + const std::array values = {2.0, -2.0, 0.6171875, 0.875, 9.875, 42.5}; + + std::function writerFunc = &BitStreamWriter::writeFloat16; + std::function readerFunc = &BitStreamReader::readFloat16; + + testImpl(values, writerFunc, readerFunc, 15); +} + +TEST_F(BitStreamTest, readFloat32) +{ + const std::array values = {2.0, -2.0, 0.6171875, 0.875, 9.875, 42.5}; + + std::function writerFunc = &BitStreamWriter::writeFloat32; + std::function readerFunc = &BitStreamReader::readFloat32; + + testImpl(values, writerFunc, readerFunc, 31); +} + +TEST_F(BitStreamTest, readFloat64) +{ + const std::array values = {2.0, -2.0, 0.6171875, 0.875, 9.875, 42.5}; + + std::function writerFunc = &BitStreamWriter::writeFloat64; + std::function readerFunc = &BitStreamReader::readFloat64; + + testImpl(values, writerFunc, readerFunc, 61); +} + +TEST_F(BitStreamTest, readString) +{ + const std::array values = { + "Hello World", "\n\t%^@(*aAzZ01234569$%^!?<>[]](){}-=+~:;/|\\\"\'Hello World2\0nonWrittenPart", + "Price: \xE2\x82\xAC 3 what's this? -> \xC2\xA2" /* '€' '¢' */ + }; + + std::function writerFunc = &BitStreamWriter::writeString; + std::function readerFunc = std::bind( + &BitStreamReader::readString>, std::placeholders::_1, std::allocator()); + + testImpl(values, writerFunc, readerFunc, 7); +} + +TEST_F(BitStreamTest, readBool) +{ + const std::array values = {true, false}; + + std::function writerFunc = &BitStreamWriter::writeBool; + std::function readerFunc = &BitStreamReader::readBool; + + testImpl(values, writerFunc, readerFunc, 1); +} + +TEST_F(BitStreamTest, readBitBuffer) +{ + const std::array values = {BitBuffer(std::vector({0xAB, 0xE0}), 11), + BitBuffer(std::vector({0xAB, 0xCD, 0xFE}), 23)}; + + std::function writerFunc = + &BitStreamWriter::writeBitBuffer>; + std::function readerFunc = std::bind( + &BitStreamReader::readBitBuffer>, std::placeholders::_1, + std::allocator()); + + testImpl(values, writerFunc, readerFunc, 7); +} + +TEST_F(BitStreamTest, readBytes) +{ + const std::array, 2> values = { + vector{{0, 255}}, + vector{{1, 127, 128, 254}}, + }; + + std::function&)> writerFunc = &BitStreamWriter::writeBytes; + std::function(BitStreamReader&)> readerFunc = std::bind( + &BitStreamReader::readBytes>, std::placeholders::_1, + std::allocator()); + + testImpl(values, writerFunc, readerFunc, 7); +} + +TEST_F(BitStreamTest, setBitPosition) +{ + testSetBitPosition(m_externalWriter); + testSetBitPosition(m_dummyWriter); +} + +TEST_F(BitStreamTest, alignTo) +{ + testAlignTo(m_externalWriter); + testAlignTo(m_dummyWriter); +} + +} // namespace zserio diff --git a/runtime/test/zserio/BitStreamWriterTest.cpp b/runtime/test/zserio/BitStreamWriterTest.cpp new file mode 100644 index 0000000..d2705ce --- /dev/null +++ b/runtime/test/zserio/BitStreamWriterTest.cpp @@ -0,0 +1,358 @@ +#include +#include + +#include "gtest/gtest.h" +#include "zserio/BitStreamWriter.h" +#include "zserio/CppRuntimeException.h" + +namespace zserio +{ + +class BitStreamWriterTest : public ::testing::Test +{ +public: + BitStreamWriterTest() : + m_externalBuffer(), + m_externalBufferWriter(m_externalBuffer.data(), m_externalBuffer.size()), + m_dummyBufferWriter(nullptr, 0) + { + m_externalBuffer.fill(0); + } + +protected: + std::array m_externalBuffer; + BitStreamWriter m_externalBufferWriter; + BitStreamWriter m_dummyBufferWriter; +}; + +TEST_F(BitStreamWriterTest, rawConstructor) +{ + std::array data = {0x00, 0x00}; + BitStreamWriter writer(data.data(), data.size()); + + ASSERT_EQ(data.data(), writer.getBuffer().data()); + ASSERT_EQ(data.size() * 8, writer.getBufferBitSize()); + + writer.writeBits(0x1F, 5); + writer.writeBits(0x07, 3); + ASSERT_EQ(0xFF, writer.getBuffer()[0]); + ASSERT_THROW(writer.writeBits(0xFFFF, 16), CppRuntimeException); + writer.writeBits(0x07, 3); + writer.writeBits(0x00, 5); + ASSERT_EQ(0xE0, writer.getBuffer()[1]); +} + +TEST_F(BitStreamWriterTest, rawConstructorWithBitSize) +{ + std::array data = {0x00, 0x00}; + BitStreamWriter writer(data.data(), 15, BitsTag()); + + ASSERT_EQ(data.data(), writer.getBuffer().data()); + ASSERT_EQ(15, writer.getBufferBitSize()); + + writer.writeBits(0x1F, 5); + writer.writeBits(0x07, 3); + ASSERT_EQ(0xFF, writer.getBuffer()[0]); + ASSERT_THROW(writer.writeBits(0xFF, 8), CppRuntimeException); + writer.writeBits(0x07, 3); + writer.writeBits(0x00, 4); + ASSERT_EQ(0xE0, writer.getBuffer()[1]); +} + +TEST_F(BitStreamWriterTest, spanConstructor) +{ + std::array data = {0x00, 0x00}; + const Span span(data); + BitStreamWriter writer(span); + + ASSERT_EQ(span.data(), writer.getBuffer().data()); + ASSERT_EQ(span.size() * 8, writer.getBufferBitSize()); + + writer.writeBits(0x1F, 5); + writer.writeBits(0x07, 3); + ASSERT_EQ(0xFF, writer.getBuffer()[0]); + ASSERT_THROW(writer.writeBits(0xFFFF, 16), CppRuntimeException); + writer.writeBits(0x07, 3); + writer.writeBits(0x00, 5); + ASSERT_EQ(0xE0, writer.getBuffer()[1]); +} + +TEST_F(BitStreamWriterTest, spanConstructorWithBitSize) +{ + std::array data = {0x00, 0x00}; + const Span span(data); + BitStreamWriter writer(span, 15); + ASSERT_THROW(BitStreamWriter wrongWriter(span, 17), CppRuntimeException); + + ASSERT_EQ(span.data(), writer.getBuffer().data()); + ASSERT_EQ(15, writer.getBufferBitSize()); + + writer.writeBits(0x1F, 5); + writer.writeBits(0x07, 3); + ASSERT_EQ(0xFF, writer.getBuffer()[0]); + ASSERT_THROW(writer.writeBits(0xFF, 8), CppRuntimeException); + writer.writeBits(0x07, 3); + writer.writeBits(0x00, 4); + ASSERT_EQ(0xE0, writer.getBuffer()[1]); +} + +TEST_F(BitStreamWriterTest, bitBufferConstructor) +{ + BitBuffer bitBuffer(11); + BitStreamWriter writer(bitBuffer); + + ASSERT_EQ(bitBuffer.getBuffer(), writer.getBuffer().data()); + ASSERT_EQ(bitBuffer.getBitSize(), writer.getBufferBitSize()); + + writer.writeBits(0x1F, 5); + writer.writeBits(0x07, 3); + ASSERT_EQ(0xFF, writer.getBuffer()[0]); + ASSERT_THROW(writer.writeBits(0x0F, 4), CppRuntimeException); + writer.writeBits(0x07, 3); + ASSERT_EQ(0xE0, writer.getBuffer()[1]); +} + +TEST_F(BitStreamWriterTest, writeUnalignedData) +{ + // number expected to be written at offset + const uint8_t testValue = 123; + + for (uint8_t offset = 0; offset <= 64; ++offset) + { + BitBuffer bitBuffer(8U + offset); + // fill the buffer with 1s to check proper masking + std::memset(bitBuffer.getBuffer(), 0xFF, bitBuffer.getByteSize()); + + BitStreamWriter writer(bitBuffer); + + if (offset > 0) + { + writer.writeBits64(0, offset); + } + writer.writeBits(testValue, 8); + + // check eof + ASSERT_THROW(writer.writeBits64(0, 1), CppRuntimeException); + + // check written value + uint8_t writtenTestValue = static_cast(bitBuffer.getData()[offset / 8U] << (offset % 8U)); + if (offset % 8 != 0) + { + const uint8_t val = + static_cast(bitBuffer.getData()[offset / 8U + 1U] >> (8U - (offset % 8U))); + writtenTestValue = static_cast(writtenTestValue | val); + } + ASSERT_EQ(testValue, writtenTestValue) << "Offset: " << offset; + } +} + +TEST_F(BitStreamWriterTest, writeBits) +{ + // check invalid bitlength acceptance + const std::array numBitsArray = {255, 0, 33}; + for (uint8_t numBits : numBitsArray) + { + ASSERT_THROW(m_externalBufferWriter.writeBits(0, numBits), CppRuntimeException); + ASSERT_THROW(m_externalBufferWriter.writeBits(1, numBits), CppRuntimeException); + } + + // check value out of range + for (int i = 1; i < 32; ++i) + { + const uint32_t maxUnsigned = static_cast((UINT64_C(1) << i) - 1); + m_externalBufferWriter.writeBits(maxUnsigned, static_cast(i)); + + const uint32_t maxUnsignedViolation = maxUnsigned + 1; + ASSERT_THROW(m_externalBufferWriter.writeBits(maxUnsignedViolation, static_cast(i)), + CppRuntimeException); + } +} + +TEST_F(BitStreamWriterTest, writeBits64) +{ + // check invalid bitlength acceptance + const std::array numBitsArray = {255, 0, 65}; + for (uint8_t numBits : numBitsArray) + { + ASSERT_THROW(m_externalBufferWriter.writeBits64(0, numBits), CppRuntimeException); + ASSERT_THROW(m_externalBufferWriter.writeBits64(1, numBits), CppRuntimeException); + } + + // check value out of range + for (int i = 1; i < 64; ++i) + { + const uint64_t maxUnsigned = (UINT64_C(1) << i) - 1; + m_externalBufferWriter.writeBits64(maxUnsigned, static_cast(i)); + + const uint64_t maxUnsignedViolation = maxUnsigned + 1; + ASSERT_THROW(m_externalBufferWriter.writeBits64(maxUnsignedViolation, static_cast(i)), + CppRuntimeException); + } +} + +TEST_F(BitStreamWriterTest, writeSignedBits) +{ + // check invalid bitlength acceptance + const std::array numBitsArray = {255, 0, 33}; + for (uint8_t numBits : numBitsArray) + { + ASSERT_THROW(m_externalBufferWriter.writeSignedBits(0, numBits), CppRuntimeException); + ASSERT_THROW(m_externalBufferWriter.writeSignedBits(1, numBits), CppRuntimeException); + } + + // check value out of range + for (uint32_t i = 1; i < 32; ++i) + { + const int32_t minSigned = -static_cast(1U << (i - 1U)); + const int32_t maxSigned = static_cast((1U << (i - 1U)) - 1U); + m_externalBufferWriter.writeSignedBits(minSigned, static_cast(i)); + m_externalBufferWriter.writeSignedBits(maxSigned, static_cast(i)); + + const int32_t minSignedViolation = minSigned - 1; + const int32_t maxSignedViolation = maxSigned + 1; + ASSERT_THROW(m_externalBufferWriter.writeSignedBits(minSignedViolation, static_cast(i)), + CppRuntimeException); + ASSERT_THROW(m_externalBufferWriter.writeSignedBits(maxSignedViolation, static_cast(i)), + CppRuntimeException); + } +} + +TEST_F(BitStreamWriterTest, writeSignedBits64) +{ + // check invalid bitlength acceptance + const std::array numBitsArray = {255, 0, 65}; + for (uint8_t numBits : numBitsArray) + { + ASSERT_THROW(m_externalBufferWriter.writeSignedBits64(0, numBits), CppRuntimeException); + ASSERT_THROW(m_externalBufferWriter.writeSignedBits64(1, numBits), CppRuntimeException); + } + + // check value out of range + for (int i = 1; i < 64; ++i) + { + const int64_t minSigned = -(INT64_C(1) << (i - 1)); + const int64_t maxSigned = (INT64_C(1) << (i - 1)) - 1; + m_externalBufferWriter.writeSignedBits64(minSigned, static_cast(i)); + m_externalBufferWriter.writeSignedBits64(maxSigned, static_cast(i)); + + const int64_t minSignedViolation = minSigned - 1; + const int64_t maxSignedViolation = maxSigned + 1; + ASSERT_THROW(m_externalBufferWriter.writeSignedBits64(minSignedViolation, static_cast(i)), + CppRuntimeException); + ASSERT_THROW(m_externalBufferWriter.writeSignedBits64(maxSignedViolation, static_cast(i)), + CppRuntimeException); + } +} + +TEST_F(BitStreamWriterTest, writeVarInt64) +{ + // check value out of range + const int64_t outOfRangeValue = + static_cast(UINT64_C(1) << (6U + 7U + 7U + 7U + 7U + 7U + 7U + 8U)); + ASSERT_THROW(m_externalBufferWriter.writeVarInt64(outOfRangeValue), CppRuntimeException); +} + +TEST_F(BitStreamWriterTest, writeVarInt32) +{ + // check value out of range + const int32_t outOfRangeValue = static_cast(1U << (6U + 7U + 7U + 8U)); + ASSERT_THROW(m_externalBufferWriter.writeVarInt32(outOfRangeValue), CppRuntimeException); +} + +TEST_F(BitStreamWriterTest, writeVarInt16) +{ + // check value out of range + const int16_t outOfRangeValue = static_cast(1U << (6U + 8U)); + ASSERT_THROW(m_externalBufferWriter.writeVarInt16(outOfRangeValue), CppRuntimeException); +} + +TEST_F(BitStreamWriterTest, writeVarUInt64) +{ + // check value out of range + const uint64_t outOfRangeValue = UINT64_C(1) << (7U + 7U + 7U + 7U + 7U + 7U + 7U + 8U); + ASSERT_THROW(m_externalBufferWriter.writeVarUInt64(outOfRangeValue), CppRuntimeException); +} + +TEST_F(BitStreamWriterTest, writeVarUInt32) +{ + // check value out of range + const uint32_t outOfRangeValue = UINT32_C(1) << (7U + 7U + 7U + 8U); + ASSERT_THROW(m_externalBufferWriter.writeVarUInt32(outOfRangeValue), CppRuntimeException); +} + +TEST_F(BitStreamWriterTest, writeVarUInt16) +{ + // check value out of range + const uint16_t outOfRangeValue = static_cast(1U << (7U + 8U)); + ASSERT_THROW(m_externalBufferWriter.writeVarUInt16(outOfRangeValue), CppRuntimeException); +} + +TEST_F(BitStreamWriterTest, writeVarInt) +{ + ASSERT_NO_THROW(m_externalBufferWriter.writeVarInt(INT64_MIN)); + ASSERT_NO_THROW(m_externalBufferWriter.writeVarInt(INT64_MAX)); +} + +TEST_F(BitStreamWriterTest, writeVarUInt) +{ + ASSERT_NO_THROW(m_externalBufferWriter.writeVarUInt(0)); + ASSERT_NO_THROW(m_externalBufferWriter.writeVarUInt(UINT64_MAX)); +} + +TEST_F(BitStreamWriterTest, writeVarSize) +{ + // check value out of range + const uint32_t outOfRangeValue = UINT32_C(1) << (2U + 7U + 7U + 7U + 8U); + ASSERT_THROW(m_externalBufferWriter.writeVarSize(outOfRangeValue), CppRuntimeException); +} + +TEST_F(BitStreamWriterTest, writeBitBuffer) +{ + static const size_t bitBufferBitSize = 24; + BitBuffer bitBuffer(std::vector{0xAB, 0xAB, 0xAB}, bitBufferBitSize); + + { + ASSERT_NO_THROW(m_externalBufferWriter.writeBitBuffer(bitBuffer)); + Span buffer = m_externalBufferWriter.getBuffer(); + // first byte is bit buffer size + BitBuffer readBitBuffer{buffer.last(buffer.size() - 1), bitBufferBitSize}; + ASSERT_EQ(bitBuffer, readBitBuffer); + } + + ASSERT_NO_THROW(m_dummyBufferWriter.writeBitBuffer(bitBuffer)); + ASSERT_EQ(bitBufferBitSize + 8, m_dummyBufferWriter.getBitPosition()); // first byte is bit buffer size +} + +TEST_F(BitStreamWriterTest, hasWriteBuffer) +{ + ASSERT_TRUE(m_externalBufferWriter.hasWriteBuffer()); + ASSERT_FALSE(m_dummyBufferWriter.hasWriteBuffer()); +} + +TEST_F(BitStreamWriterTest, getWriteBuffer) +{ + ASSERT_EQ(m_externalBuffer.data(), m_externalBufferWriter.getWriteBuffer()); + + ASSERT_EQ(nullptr, m_dummyBufferWriter.getWriteBuffer()); +} + +TEST_F(BitStreamWriterTest, getBuffer) +{ + ASSERT_EQ(m_externalBuffer.data(), m_externalBufferWriter.getBuffer().data()); + + ASSERT_EQ(nullptr, m_dummyBufferWriter.getBuffer().data()); +} + +TEST_F(BitStreamWriterTest, dummyBufferTest) +{ + m_dummyBufferWriter.writeBits(1, 1); + m_dummyBufferWriter.alignTo(4); + m_dummyBufferWriter.writeBits(1, 1); + m_dummyBufferWriter.alignTo(4); + m_dummyBufferWriter.writeBits(37, 11); + m_dummyBufferWriter.alignTo(8); + m_dummyBufferWriter.writeBits(1, 1); + ASSERT_EQ(25, m_dummyBufferWriter.getBitPosition()); +} + +} // namespace zserio