From f62450b40ae49a10b37be150114b2501499dbd4a Mon Sep 17 00:00:00 2001 From: cliffg-softwarelibre Date: Mon, 27 May 2024 18:28:32 -0600 Subject: [PATCH 01/15] Added byteswap header, redesigned extract_append with endian flexibility --- include/serialize/binary_serialize.hpp | 21 +- include/serialize/byteswap.hpp | 44 +++ include/serialize/extract_append.hpp | 437 ++++++------------------- test/CMakeLists.txt | 3 +- test/byteswap_test.cpp | 41 +++ test/extract_append_test.cpp | 176 +++++----- 6 files changed, 269 insertions(+), 453 deletions(-) create mode 100644 include/serialize/byteswap.hpp create mode 100644 test/byteswap_test.cpp diff --git a/include/serialize/binary_serialize.hpp b/include/serialize/binary_serialize.hpp index 6b2c364..b56f3cc 100644 --- a/include/serialize/binary_serialize.hpp +++ b/include/serialize/binary_serialize.hpp @@ -1,15 +1,12 @@ -/** @file +/** @mainpage Binary Serialuze, Classes and Functions For Binary Data Serialization * - * @defgroup marshall_module Classes and functions for big-endian binary data - * marshalling and unmarshalling (transform objects into and out of byte streams - * for transmission over a network or for file IO). + * Serialization transforms objects into a byte stream for transmission over a + * network or for file IO. Deserialization is the converse, transforming a byte + * stream into application level objects. * - * @brief Classes and functions to transform objects into a big-endian binary stream - * of bytes (marshall) and the converse (unmarshall), transform a stream of bytes into - * objects. - * - * The @c utility-rack @c marshall and @c unmarshall functions and classes provide a - * simple and light abstraction for binary big-endian serialization. There are no + * This library differs from other binary serialization libraries in that the + * main interfaces is a "std::format" like + * These functions and classes provide a simple and light abstraction for binary big-endian serialization. There are no * message or element definitions, no embedded preprocesser syntax, and no extra * build steps. * @@ -113,8 +110,8 @@ * */ -#ifndef MARSHALL_HPP_INCLUDED -#define MARSHALL_HPP_INCLUDED +#ifndef BINARY_SERIALIZE_HPP_INCLUDED +#define BINARY_SERIALIZE_HPP_INCLUDED #include "utility/cast_ptr_to.hpp" #include "marshall/shared_buffer.hpp" diff --git a/include/serialize/byteswap.hpp b/include/serialize/byteswap.hpp new file mode 100644 index 0000000..7c354a4 --- /dev/null +++ b/include/serialize/byteswap.hpp @@ -0,0 +1,44 @@ +/** @file + * + * @brief This is a direct implementation of the C++ 23 std::byteswap function, + * for use in pre C++ 23 applications. + * + * This implementation is taken directly from the CPP Reference page: + * https://en.cppreference.com/w/cpp/numeric/byteswap + * + * @author Cliff Green, CPP Reference + * + * @copyright (c) 2024 by Cliff Green + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + * + */ + +#ifndef BYTESWAP_HPP_INCLUDED +#define BYTESWAP_HPP_INCLUDED + +#include // std::integral +#include // std::bit_cast +#include +#include // std::byte +#include // std::ranges::reverse + +namespace chops { + +template +constexpr T byteswap(T value) noexcept { + if constexpr (sizeof(T) == 1u) { + return value; + } + static_assert(std::has_unique_object_representations_v, + "T may not have padding bits"); + auto value_representation = std::bit_cast>(value); + std::ranges::reverse(value_representation); + return std::bit_cast(value_representation); +} + +} // end namespace + +#endif + diff --git a/include/serialize/extract_append.hpp b/include/serialize/extract_append.hpp index 490fbb4..be7863c 100644 --- a/include/serialize/extract_append.hpp +++ b/include/serialize/extract_append.hpp @@ -1,14 +1,14 @@ /** @file * - * @brief Functions to extract arithmetic binary values from a byte buffer (in big-endian) to - * native format; conversely, given an arithmetic binary value, append it to a buffer of bytes - * in network endian order (big-endian). + * @brief Functions to extract arithmetic binary values from a byte buffer (in either + * endian order) to native format; conversely, given an arithmetic binary value, append + * it to a buffer of bytes in the specified endian order. * - * The functions in this file are low-level, handling fundamental arithmetic types and - * extracting or appending to @c std::byte buffers. It is meant to be the lower layer - * of marshalling utilities, where the next layer up provides buffer management, - * sequences, and overloads for specific types such as @c std::string, @c bool, and - * @c std::optional. + * The functions in this file are low-level, handling fundamental arithmetic types and + * extracting or appending to @c std::byte buffers. It is meant to be the lower layer + * of serializing utilities, where the next layer up provides buffer management, + * sequences, and overloads for specific types such as @c std::string, @c bool, and + * @c std::optional. * * @note When C++ 20 @c std::endian is available, many of these functions can be made * @c constexpr and evaluated at compile time. Until then, run-time endian detection and @@ -27,7 +27,7 @@ * * @author Cliff Green, Roxanne Agerone, Uli Koehler * - * Copyright (c) 2019 by Cliff Green, Roxanne Agerone + * @copyright (c) 2019-2024 by Cliff Green, Roxanne Agerone * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -37,269 +37,21 @@ #ifndef EXTRACT_APPEND_HPP_INCLUDED #define EXTRACT_APPEND_HPP_INCLUDED -#include "utility/cast_ptr_to.hpp" +#include "serialize/byteswap.hpp" +#include // std::unsigned_integral, std::integral +#include // std::ranges:copy +#include // std::endian, std::bit_cast +#include #include // std::byte, std::size_t #include // std::uint32_t, etc -#include // std::is_arithmetic +#include // std::is_same -namespace chops { - -namespace detail { - -// since function template partial specialization is not supported (yet) in C++, overload the -// extract_val_swap and noswap function templates with a parameter corresponding to the -// sizeof the value; no data is passed in the second parameter, only using the type for overloading - -template -struct size_tag { }; - -// if char / byte, no swap needed -template -T extract_val_swap(const std::byte* buf, const size_tag<1u>*) noexcept { - return static_cast(*buf); // static_cast needed to convert std::byte to char type -} -template -T extract_val_noswap(const std::byte* buf, const size_tag<1u>*) noexcept { - return static_cast(*buf); // see note above -} - -template -T extract_val_swap(const std::byte* buf, const size_tag<2u>*) noexcept { - T tmp{}; - std::byte* p = cast_ptr_to(&tmp); - *(p+0) = *(buf+1); - *(p+1) = *(buf+0); - return tmp; -} - -template -T extract_val_noswap(const std::byte* buf, const size_tag<2u>*) noexcept { - T tmp{}; - std::byte* p = cast_ptr_to(&tmp); - *(p+0) = *(buf+0); - *(p+1) = *(buf+1); - return tmp; -} - -template -T extract_val_swap(const std::byte* buf, const size_tag<4u>*) noexcept { - T tmp{}; - std::byte* p = cast_ptr_to(&tmp); - *(p+0) = *(buf+3); - *(p+1) = *(buf+2); - *(p+2) = *(buf+1); - *(p+3) = *(buf+0); - return tmp; -} - -template -T extract_val_noswap(const std::byte* buf, const size_tag<4u>*) noexcept { - T tmp{}; - std::byte* p = cast_ptr_to(&tmp); - *(p+0) = *(buf+0); - *(p+1) = *(buf+1); - *(p+2) = *(buf+2); - *(p+3) = *(buf+3); - return tmp; -} - -template -T extract_val_swap(const std::byte* buf, const size_tag<8u>*) noexcept { - T tmp{}; - std::byte* p = cast_ptr_to(&tmp); - *(p+0) = *(buf+7); - *(p+1) = *(buf+6); - *(p+2) = *(buf+5); - *(p+3) = *(buf+4); - *(p+4) = *(buf+3); - *(p+5) = *(buf+2); - *(p+6) = *(buf+1); - *(p+7) = *(buf+0); - return tmp; -} - -template -T extract_val_noswap(const std::byte* buf, const size_tag<8u>*) noexcept { - T tmp{}; - std::byte* p = cast_ptr_to(&tmp); - *(p+0) = *(buf+0); - *(p+1) = *(buf+1); - *(p+2) = *(buf+2); - *(p+3) = *(buf+3); - *(p+4) = *(buf+4); - *(p+5) = *(buf+5); - *(p+6) = *(buf+6); - *(p+7) = *(buf+7); - return tmp; -} - -template -T extract_val_swap(const std::byte* buf, const size_tag<16u>*) noexcept { - T tmp{}; - std::byte* p = cast_ptr_to(&tmp); - *(p+0) = *(buf+15); - *(p+1) = *(buf+14); - *(p+2) = *(buf+13); - *(p+3) = *(buf+12); - *(p+4) = *(buf+11); - *(p+5) = *(buf+10); - *(p+6) = *(buf+9); - *(p+7) = *(buf+8); - *(p+8) = *(buf+7); - *(p+9) = *(buf+6); - *(p+10) = *(buf+5); - *(p+11) = *(buf+4); - *(p+12) = *(buf+3); - *(p+13) = *(buf+2); - *(p+14) = *(buf+1); - *(p+15) = *(buf+0); - return tmp; -} - -template -T extract_val_noswap(const std::byte* buf, const size_tag<16u>*) noexcept { - T tmp{}; - std::byte* p = cast_ptr_to(&tmp); - *(p+0) = *(buf+0); - *(p+1) = *(buf+1); - *(p+2) = *(buf+2); - *(p+3) = *(buf+3); - *(p+4) = *(buf+4); - *(p+5) = *(buf+5); - *(p+6) = *(buf+6); - *(p+7) = *(buf+7); - *(p+8) = *(buf+8); - *(p+9) = *(buf+9); - *(p+10) = *(buf+10); - *(p+11) = *(buf+11); - *(p+12) = *(buf+12); - *(p+13) = *(buf+13); - *(p+14) = *(buf+14); - *(p+15) = *(buf+15); - return tmp; -} - -template -std::size_t append_val_swap(std::byte* buf, const T& val, const size_tag<1u>*) noexcept { - *buf = static_cast(val); // static_cast needed to convert char to std::byte - return 1u; -} - -template -std::size_t append_val_noswap(std::byte* buf, const T& val, const size_tag<1u>*) noexcept { - *buf = static_cast(val); // see note above - return 1u; -} - -template -std::size_t append_val_swap(std::byte* buf, const T& val, const size_tag<2u>*) noexcept { - const std::byte* p = cast_ptr_to(&val); - *(buf+0) = *(p+1); - *(buf+1) = *(p+0); - return 2u; -} - -template -std::size_t append_val_noswap(std::byte* buf, const T& val, const size_tag<2u>*) noexcept { - const std::byte* p = cast_ptr_to(&val); - *(buf+0) = *(p+0); - *(buf+1) = *(p+1); - return 2u; -} -template -std::size_t append_val_swap(std::byte* buf, const T& val, const size_tag<4u>*) noexcept { - const std::byte* p = cast_ptr_to(&val); - *(buf+0) = *(p+3); - *(buf+1) = *(p+2); - *(buf+2) = *(p+1); - *(buf+3) = *(p+0); - return 4u; -} - -template -std::size_t append_val_noswap(std::byte* buf, const T& val, const size_tag<4u>*) noexcept { - const std::byte* p = cast_ptr_to(&val); - *(buf+0) = *(p+0); - *(buf+1) = *(p+1); - *(buf+2) = *(p+2); - *(buf+3) = *(p+3); - return 4u; -} - -template -std::size_t append_val_swap(std::byte* buf, const T& val, const size_tag<8u>*) noexcept { - const std::byte* p = cast_ptr_to(&val); - *(buf+0) = *(p+7); - *(buf+1) = *(p+6); - *(buf+2) = *(p+5); - *(buf+3) = *(p+4); - *(buf+4) = *(p+3); - *(buf+5) = *(p+2); - *(buf+6) = *(p+1); - *(buf+7) = *(p+0); - return 8u; -} - -template -std::size_t append_val_noswap(std::byte* buf, const T& val, const size_tag<8u>*) noexcept { - const std::byte* p = cast_ptr_to(&val); - *(buf+0) = *(p+0); - *(buf+1) = *(p+1); - *(buf+2) = *(p+2); - *(buf+3) = *(p+3); - *(buf+4) = *(p+4); - *(buf+5) = *(p+5); - *(buf+6) = *(p+6); - *(buf+7) = *(p+7); - return 8u; -} - -template -std::size_t append_val_swap(std::byte* buf, const T& val, const size_tag<16u>*) noexcept { - const std::byte* p = cast_ptr_to(&val); - *(buf+0) = *(p+15); - *(buf+1) = *(p+14); - *(buf+2) = *(p+13); - *(buf+3) = *(p+12); - *(buf+4) = *(p+11); - *(buf+5) = *(p+10); - *(buf+6) = *(p+9); - *(buf+7) = *(p+8); - *(buf+8) = *(p+7); - *(buf+9) = *(p+6); - *(buf+10) = *(p+5); - *(buf+11) = *(p+4); - *(buf+12) = *(p+3); - *(buf+13) = *(p+2); - *(buf+14) = *(p+1); - *(buf+15) = *(p+0); - return 16u; -} - -template -std::size_t append_val_noswap(std::byte* buf, const T& val, const size_tag<16u>*) noexcept { - const std::byte* p = cast_ptr_to(&val); - *(buf+0) = *(p+0); - *(buf+1) = *(p+1); - *(buf+2) = *(p+2); - *(buf+3) = *(p+3); - *(buf+4) = *(p+4); - *(buf+5) = *(p+5); - *(buf+6) = *(p+6); - *(buf+7) = *(p+7); - *(buf+8) = *(p+8); - *(buf+9) = *(p+9); - *(buf+10) = *(p+10); - *(buf+11) = *(p+11); - *(buf+12) = *(p+12); - *(buf+13) = *(p+13); - *(buf+14) = *(p+14); - *(buf+15) = *(p+15); - return 16u; -} +namespace chops { +// Old static assertions, pre-concepts +/* template constexpr void assert_size() noexcept { static_assert(sizeof(T) == 1u || sizeof(T) == 2u || sizeof(T) == 4u || @@ -316,28 +68,30 @@ constexpr void assert_arithmetic_or_byte() noexcept { static_assert(is_arithmetic_or_byte(), "Value extraction is only supported for arithmetic or std::byte types."); } +*/ -} // end namespace detail - -// C++ 20 will contain std::endian, which allows full compile time endian detection; -// until then, the endian detection and branching will be runtime, although it -// can be computed at global initialization instead of each time it is called - -inline bool detect_big_endian () noexcept { - const std::uint32_t tmp {0xDDCCBBAA}; - return *(cast_ptr_to(&tmp) + 3) == static_cast(0xAA); -} +// using C++ 20 concepts +template +concept integral_or_byte = std::integral || std::is_same_v, std::byte>; +// old endian detection code +// inline bool detect_big_endian () noexcept { +// const std::uint32_t tmp {0xDDCCBBAA}; +// return *(cast_ptr_to(&tmp) + 3) == static_cast(0xAA); +//} // should be computed at global initialization time - -const bool big_endian = detect_big_endian(); +// const bool big_endian = detect_big_endian(); /** - * @brief Extract a value in network byte order (big-endian) from a @c std::byte buffer - * into a fundamental arithmetic type in native endianness, swapping bytes as needed. + * @brief Extract a value from a @c std::byte buffer into a fundamental integral + * or @c std::byte type in native endianness, swapping bytes as needed. + * + * @tparam BufEndian The endianness of the buffer. + * + * @tparam T Type of return value. * - * This function template dispatches on specific sizes. If an unsupported size is attempted - * to be swapped, a compile time error is generated. + * Since @c T cannot be deduced, it must be specified when calling the function. If + * the endianness of the buffer matches the native endianness, no swapping is performed. * * @param buf Pointer to an array of @c std::bytes containing an object of type T in network * byte order. @@ -346,8 +100,10 @@ const bool big_endian = detect_big_endian(); * * @pre The buffer must contain at least @c sizeof(T) bytes. * - * @note Floating point swapping is supported, but care must be taken. In particular, - * the floating point representation must exactly match on both sides of the marshalling + * @note Floating point swapping is not supported. + * + * Earlier versions did support floating point, but it is brittle - + * the floating point representation must exactly match on both sides of the serialization * (most modern processors use IEEE 754 floating point representations). A byte swapped * floating point value cannot be directly accessed (e.g. passed by value), due to the * bit patterns possibly representing NaN values, which can generate hardware traps, @@ -355,22 +111,27 @@ const bool big_endian = detect_big_endian(); * An integer value, however, will always have valid bit patterns, even when byte swapped. * */ -template -T extract_val(const std::byte* buf) noexcept { - - detail::assert_size(); - detail::assert_arithmetic_or_byte(); - - return big_endian ? detail::extract_val_noswap(buf, static_cast< const detail::size_tag* >(nullptr)) : - detail::extract_val_swap(buf, static_cast< const detail::size_tag* >(nullptr)); +template +constexpr T extract_val(const std::byte* buf) noexcept { + auto value_representation = std::bit_cast>(T{}); + std::ranges::copy (buf, buf + sizeof(T), value_representation.begin()); + auto tmp_val = std::bit_cast(value_representation); + if constexpr (BufEndian != std::endian::native && sizeof(T) != 1u) { + return chops::byteswap(tmp_val); + } + return tmp_val; } /** - * @brief Append a fundamental arithmetic value to a @c std::byte buffer, swapping into network - * endian order (big-endian) as needed. + * @brief Append an integral or @c std::byte value to a @c std::byte buffer, swapping into + * the specified endian order as needed. + * + * @tparam BufEndian The endianness of the buffer. * - * This function template dispatches on specific sizes. If an unsupported size is attempted - * to be swapped, a static error is generated. + * @tparam T Type of value to append to buffer. + * + * The @c BufEndian enum must be specified, but the type of the passed in value can be + * deduced. * * @param buf Pointer to array of @c std::bytes big enough to hold the bytes of the value. * @@ -383,14 +144,15 @@ T extract_val(const std::byte* buf) noexcept { * @note See note above about floating point values. * */ -template -std::size_t append_val(std::byte* buf, const T& val) noexcept { - - detail::assert_size(); - detail::assert_arithmetic_or_byte(); - - return big_endian ? detail::append_val_noswap(buf, val, static_cast< const detail::size_tag* >(nullptr)) : - detail::append_val_swap(buf, val, static_cast< const detail::size_tag* >(nullptr)); +template +constexpr std::size_t append_val(std::byte* buf, const T& val) noexcept { + T tmp_val {val}; + if constexpr (BufEndian != std::endian::native && sizeof(T) != 1u) { + tmp_val = chops::byteswap(tmp_val); + } + auto value_representation = std::bit_cast>(tmp_val); + std::ranges::copy (value_representation, buf ); + return sizeof(T); } /** @@ -406,10 +168,11 @@ std::size_t append_val(std::byte* buf, const T& val) noexcept { * large, this algorithm is inefficient, needing more buffer space for the encoded integers than * if fixed size integer buffers were used. * - * The output of this function is (by definition) in little-endian order (which is opposite - * to the rest of the @c append and @c extract functions). However, this does not matter since - * there is no byte swapping performed, and encoding and decoding will result in the native - * endianness of the platform. + * The output of this function is (by definition) in little-endian order. However, as long as + * the two corresponding functions (or equivalent algorithms) are used consistently, the + * endianness will not matter. There is no byte swapping performed, and encoding and decoding + * will result in the native endianness of the platform. I.e. this works whether serialization + * is big-endian or little-endian. * * @note Unsigned types are not supported. * @@ -425,25 +188,23 @@ std::size_t append_val(std::byte* buf, const T& val) noexcept { * */ -template -std::size_t append_var_int(std::byte* output, T val) { - - static_assert(std::is_unsigned::value, "Only unsigned integer types are supported."); +template +constexpr std::size_t append_var_int(std::byte* output, T val) { - std::uint8_t* output_ptr = cast_ptr_to(output); - std::size_t output_size = 0; + std::uint8_t* output_ptr = reinterpret_cast(output); + std::size_t output_size = 0; - // While more than 7 bits of data are left, occupy the last output byte - // and set the next byte flag - while (val > 127) { - - output_ptr[output_size] = (static_cast (val & 127)) | 128; - //Remove the seven bits we just wrote - val >>= 7; - ++output_size; - } - output_ptr[output_size++] = static_cast (val) & 127; - return output_size; + // While more than 7 bits of data are left, occupy the last output byte + // and set the next byte flag + while (val > 127) { + + output_ptr[output_size] = (static_cast (val & 127)) | 128; + //Remove the seven bits we just wrote + val >>= 7; + ++output_size; + } + output_ptr[output_size++] = static_cast (val) & 127; + return output_size; } /** * @brief Given a buffer of @c std::bytes that hold a variable sized integer, decode @@ -459,22 +220,20 @@ std::size_t append_var_int(std::byte* output, T val) { * @return The value in native unsigned integer format. * */ -template -T extract_var_int(const std::byte* input, std::size_t input_size) { - - static_assert(std::is_unsigned::value, "Only unsigned integer types are supported."); +template +constexpr T extract_var_int(const std::byte* input, std::size_t input_size) { - const std::uint8_t* input_ptr = cast_ptr_to (input); + const std::uint8_t* input_ptr = reinterpret_cast (input); - T ret = 0; - for (std::size_t i = 0; i < input_size; ++i) { - ret |= (input_ptr[i] & 127) << (7 * i); - //If the next-byte flag is set - if(!(input_ptr[i] & 128)) { - break; - } - } - return ret; + T ret = 0; + for (std::size_t i = 0; i < input_size; ++i) { + ret |= (input_ptr[i] & 127) << (7 * i); + //If the next-byte flag is set + if(!(input_ptr[i] & 128)) { + break; + } + } + return ret; } } // end namespace diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 939001a..ee181bd 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -14,7 +14,8 @@ include ( ../cmake/download_cpm.cmake ) CPMAddPackage ( "gh:catchorg/Catch2@3.6.0" ) CPMAddPackage ( "gh:connectivecpp/utility-rack@1.0.0" ) -set ( test_app_names extract_append_test +set ( test_app_names byteswap_test + extract_append_test binary_serialize_test ) # add executable foreach ( test_app_name IN LISTS test_app_names ) diff --git a/test/byteswap_test.cpp b/test/byteswap_test.cpp new file mode 100644 index 0000000..d605531 --- /dev/null +++ b/test/byteswap_test.cpp @@ -0,0 +1,41 @@ +/** @file + * + * @brief Test scenarios for @c byteswap function. + * + * @author Cliff Green + * + * @copyright (c) 2024 by Cliff Green + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + * + */ + +#include "catch2/catch_test_macros.hpp" + + +#include // std::byte +#include // std::uint32_t, etc + +#include "serialize/byteswap.hpp" + +constexpr std::uint32_t val1 { 0xDDCCBBAAu }; +constexpr std::uint32_t val1_reversed { 0xAABBCCDDu }; +constexpr char val2 { static_cast(0xEE) }; +constexpr std::int16_t val3 { 0x0103 }; +constexpr std::int16_t val3_reversed { 0x0301 }; +constexpr std::uint64_t val4 { 0x0908070605040302ull }; +constexpr std::uint64_t val4_reversed { 0x0203040506070809ull }; + +constexpr std::int32_t val5 = 0xDEADBEEF; +constexpr std::int32_t val5_reversed = 0xEFBEADDE; + + +TEST_CASE ( "Byteswap", "[byteswap]" ) { + REQUIRE (chops::byteswap(val1) == val1_reversed); + REQUIRE (chops::byteswap(val2) == val2); + REQUIRE (chops::byteswap(val3) == val3_reversed); + REQUIRE (chops::byteswap(val4) == val4_reversed); + REQUIRE (chops::byteswap(val5) == val5_reversed); +} + diff --git a/test/extract_append_test.cpp b/test/extract_append_test.cpp index d53b812..413bcc1 100644 --- a/test/extract_append_test.cpp +++ b/test/extract_append_test.cpp @@ -18,9 +18,9 @@ #include // std::uint32_t, etc #include "serialize/extract_append.hpp" + #include "utility/repeat.hpp" #include "utility/make_byte_array.hpp" -#include "utility/cast_ptr_to.hpp" constexpr std::uint32_t val1 = 0xDDCCBBAA; constexpr char val2 = static_cast(0xEE); @@ -28,119 +28,93 @@ constexpr std::int16_t val3 = 0x01FF; constexpr std::uint64_t val4 = 0x0908070605040302; constexpr std::int32_t val5 = 0xDEADBEEF; constexpr std::byte val6 = static_cast(0xAA); -constexpr float float_val = 42.0f; -constexpr double double_val = 197.0; constexpr int arr_sz = sizeof(val1)+sizeof(val2)+sizeof(val3)+ sizeof(val4)+sizeof(val5)+sizeof(val6); -auto net_buf = chops::make_byte_array(0xDD, 0xCC, 0xBB, 0xAA, 0xEE, 0x01, 0xFF, +auto net_buf_big = chops::make_byte_array(0xDD, 0xCC, 0xBB, 0xAA, 0xEE, 0x01, 0xFF, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0xDE, 0xAD, 0xBE, 0xEF, 0xAA); +auto net_buf_little = chops::make_byte_array(0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x01, + 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xEF, 0xBE, 0xAD, 0xDE, 0xAA); -TEST_CASE ( "Size and arithmetic assertions", - "[assertions]" ) { - REQUIRE (chops::detail::is_arithmetic_or_byte()); - REQUIRE (chops::detail::is_arithmetic_or_byte()); - REQUIRE (chops::detail::is_arithmetic_or_byte()); - REQUIRE (chops::detail::is_arithmetic_or_byte()); - REQUIRE (chops::detail::is_arithmetic_or_byte()); - REQUIRE (chops::detail::is_arithmetic_or_byte()); - REQUIRE (chops::detail::is_arithmetic_or_byte()); - REQUIRE (chops::detail::is_arithmetic_or_byte()); - REQUIRE (chops::detail::is_arithmetic_or_byte()); -} - -SCENARIO ( "Endian detection", - "[endian] [little_endian]" ) { +TEST_CASE ( "Append values into a buffer", "[append_val]" ) { - GIVEN ("A little-endian system") { + std::byte buf[arr_sz]; + constexpr std::uint32_t v = 0x04030201; - WHEN ("The detect_big_endian function is called") { - THEN ("the value is false") { - REQUIRE (!chops::big_endian); - } - } - } // end given -} + SECTION ("Append_val with a single value, big endian") { + REQUIRE(chops::append_val(buf, v) == 4u); + REQUIRE (buf[0] == static_cast(0x04)); + REQUIRE (buf[1] == static_cast(0x03)); + REQUIRE (buf[2] == static_cast(0x02)); + REQUIRE (buf[3] == static_cast(0x01)); + } + SECTION ("Append_val with a single value, little endian") { + REQUIRE(chops::append_val(buf, v) == 4u); + REQUIRE (buf[0] == static_cast(0x01)); + REQUIRE (buf[1] == static_cast(0x02)); + REQUIRE (buf[2] == static_cast(0x03)); + REQUIRE (buf[3] == static_cast(0x04)); + } -SCENARIO ( "Append values into a buffer", - "[append_val]" ) { - - GIVEN ("An empty byte buffer") { - std::byte buf[arr_sz]; - - WHEN ("Append_val is called with a single value") { - constexpr std::uint32_t v = 0x04030201; - REQUIRE(chops::append_val(buf, v) == 4u); - THEN ("the buffer will contain the single value in network endian order") { - REQUIRE (buf[0] == static_cast(0x04)); - REQUIRE (buf[1] == static_cast(0x03)); - REQUIRE (buf[2] == static_cast(0x02)); - REQUIRE (buf[3] == static_cast(0x01)); - } - } - AND_WHEN ("Append_val is called with multiple values") { - std::byte* ptr = buf; - REQUIRE(chops::append_val(ptr, val1) == 4u); ptr += sizeof(val1); - REQUIRE(chops::append_val(ptr, val2) == 1u); ptr += sizeof(val2); - REQUIRE(chops::append_val(ptr, val3) == 2u); ptr += sizeof(val3); - REQUIRE(chops::append_val(ptr, val4) == 8u); ptr += sizeof(val4); - REQUIRE(chops::append_val(ptr, val5) == 4u); ptr += sizeof(val5); - REQUIRE(chops::append_val(ptr, val6) == 1u); - - THEN ("the buffer will have all of the values in network endian order") { - chops::repeat(arr_sz, [&buf] (int i) { REQUIRE (buf[i] == net_buf[i]); } ); - } - } - } // end given + SECTION ("Append_val with multiple values, big endian") { + std::byte* ptr = buf; + REQUIRE(chops::append_val(ptr, val1) == 4u); ptr += sizeof(val1); + REQUIRE(chops::append_val(ptr, val2) == 1u); ptr += sizeof(val2); + REQUIRE(chops::append_val(ptr, val3) == 2u); ptr += sizeof(val3); + REQUIRE(chops::append_val(ptr, val4) == 8u); ptr += sizeof(val4); + REQUIRE(chops::append_val(ptr, val5) == 4u); ptr += sizeof(val5); + REQUIRE(chops::append_val(ptr, val6) == 1u); + chops::repeat(arr_sz, [&buf] (int i) { REQUIRE (buf[i] == net_buf_big[i]); } ); + } + SECTION ("Append_val with multiple values, little endian") { + std::byte* ptr = buf; + REQUIRE(chops::append_val(ptr, val1) == 4u); ptr += sizeof(val1); + REQUIRE(chops::append_val(ptr, val2) == 1u); ptr += sizeof(val2); + REQUIRE(chops::append_val(ptr, val3) == 2u); ptr += sizeof(val3); + REQUIRE(chops::append_val(ptr, val4) == 8u); ptr += sizeof(val4); + REQUIRE(chops::append_val(ptr, val5) == 4u); ptr += sizeof(val5); + REQUIRE(chops::append_val(ptr, val6) == 1u); + chops::repeat(arr_sz, [&buf] (int i) { REQUIRE (buf[i] == net_buf_little[i]); } ); + } } -SCENARIO ( "Extract values from a buffer", - "[extract_val]" ) { - - GIVEN ("A buffer of bytes in network endian order") { - WHEN ("Extract_val is called for multiple values") { - - const std::byte* ptr = net_buf.data(); - std::uint32_t v1 = chops::extract_val(ptr); ptr += sizeof(v1); - char v2 = chops::extract_val(ptr); ptr += sizeof(v2); - std::int16_t v3 = chops::extract_val(ptr); ptr += sizeof(v3); - std::uint64_t v4 = chops::extract_val(ptr); ptr += sizeof(v4); - std::int32_t v5 = chops::extract_val(ptr); ptr += sizeof(v5); - std::byte v6 = chops::extract_val(ptr); - - THEN ("the values are all in native order") { - REQUIRE(v1 == val1); - REQUIRE(v2 == val2); - REQUIRE(v3 == val3); - REQUIRE(v4 == val4); - REQUIRE(v5 == val5); - REQUIRE(v6 == val6); - } - } - } // end given +TEST_CASE ( "Extract values from a buffer", "[extract_val]" ) { + + SECTION ( "Extract_val for multiple values in big endian buf") { + const std::byte* ptr = net_buf_big.data(); + std::uint32_t v1 = chops::extract_val(ptr); ptr += sizeof(v1); + char v2 = chops::extract_val(ptr); ptr += sizeof(v2); + std::int16_t v3 = chops::extract_val(ptr); ptr += sizeof(v3); + std::uint64_t v4 = chops::extract_val(ptr); ptr += sizeof(v4); + std::int32_t v5 = chops::extract_val(ptr); ptr += sizeof(v5); + std::byte v6 = chops::extract_val(ptr); + + REQUIRE(v1 == val1); + REQUIRE(v2 == val2); + REQUIRE(v3 == val3); + REQUIRE(v4 == val4); + REQUIRE(v5 == val5); + REQUIRE(v6 == val6); + } + SECTION ( "Extract_val for multiple values in little endian buf") { + const std::byte* ptr = net_buf_little.data(); + std::uint32_t v1 = chops::extract_val(ptr); ptr += sizeof(v1); + char v2 = chops::extract_val(ptr); ptr += sizeof(v2); + std::int16_t v3 = chops::extract_val(ptr); ptr += sizeof(v3); + std::uint64_t v4 = chops::extract_val(ptr); ptr += sizeof(v4); + std::int32_t v5 = chops::extract_val(ptr); ptr += sizeof(v5); + std::byte v6 = chops::extract_val(ptr); + + REQUIRE(v1 == val1); + REQUIRE(v2 == val2); + REQUIRE(v3 == val3); + REQUIRE(v4 == val4); + REQUIRE(v5 == val5); + REQUIRE(v6 == val6); + } } -SCENARIO ( "Floating point append and extract", - "[floating_point]" ) { - GIVEN ("An empty byte buffer") { - std::byte buf[16]; - - WHEN ("Two floating point values are appended then extracted") { - std::byte* ptr = buf; - REQUIRE(chops::append_val(ptr, float_val) == 4u); ptr += sizeof(float_val); - REQUIRE(chops::append_val(ptr, double_val) == 8u); - ptr = buf; - auto f = chops::extract_val(ptr); ptr += sizeof(f); - auto d = chops::extract_val(ptr); - - THEN ("the values are the same, in native order") { - REQUIRE (f == float_val); - REQUIRE (d == double_val); - } - } - } // end given -} template void test_round_trip_var_int (Src src, std::size_t exp_sz) { From fbc576434d8f3a413d1c00520b6df0565ce7b5d4 Mon Sep 17 00:00:00 2001 From: cliffg-softwarelibre Date: Mon, 27 May 2024 20:10:36 -0600 Subject: [PATCH 02/15] Adding link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d4cf757..fd8297b 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ The unit test code uses [Catch2](https://github.com/catchorg/Catch2). If the `BI The unit test uses utilities from Connective C++'s [utility-rack](https://github.com/connectivecpp/utility-rack). -Specific version (or branch) specs for the dependenies are in `test/CMakeLists.txt`. +Specific version (or branch) specs for the dependenies are in [`test/CMakeLists.txt`](test/CMakeLists.txt). ## Build and Run Unit Tests From c7ed6fa3d58f9321831d8797e8ba99a5d2aca800 Mon Sep 17 00:00:00 2001 From: cliffg-softwarelibre Date: Mon, 27 May 2024 20:12:17 -0600 Subject: [PATCH 03/15] Tweaking link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fd8297b..6da5c48 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ The unit test code uses [Catch2](https://github.com/catchorg/Catch2). If the `BI The unit test uses utilities from Connective C++'s [utility-rack](https://github.com/connectivecpp/utility-rack). -Specific version (or branch) specs for the dependenies are in [`test/CMakeLists.txt`](test/CMakeLists.txt). +Specific version (or branch) specs for the dependenies are in [test/CMakeLists.txt](test/CMakeLists.txt) in the `CPMAddPackage` commands. ## Build and Run Unit Tests From 47f890b90cae61e234cd3a9afb784e1f0d56bc38 Mon Sep 17 00:00:00 2001 From: cliffg-softwarelibre Date: Mon, 27 May 2024 20:14:36 -0600 Subject: [PATCH 04/15] Tweaking text --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6da5c48..7d3d097 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ The unit test code uses [Catch2](https://github.com/catchorg/Catch2). If the `BI The unit test uses utilities from Connective C++'s [utility-rack](https://github.com/connectivecpp/utility-rack). -Specific version (or branch) specs for the dependenies are in [test/CMakeLists.txt](test/CMakeLists.txt) in the `CPMAddPackage` commands. +Specific version (or branch) specs for the dependencies are in the [test/CMakeLists.txt](test/CMakeLists.txt) file, look for the `CPMAddPackage` commands. ## Build and Run Unit Tests From a18eed08c943a1f680c37c4603615b0e098ce051 Mon Sep 17 00:00:00 2001 From: cliffg-softwarelibre Date: Wed, 29 May 2024 18:33:58 -0600 Subject: [PATCH 05/15] updating --- include/serialize/binary_serialize.hpp | 216 ++++++++++++------------- 1 file changed, 108 insertions(+), 108 deletions(-) diff --git a/include/serialize/binary_serialize.hpp b/include/serialize/binary_serialize.hpp index b56f3cc..9ccd92e 100644 --- a/include/serialize/binary_serialize.hpp +++ b/include/serialize/binary_serialize.hpp @@ -1,112 +1,112 @@ /** @mainpage Binary Serialuze, Classes and Functions For Binary Data Serialization * - * Serialization transforms objects into a byte stream for transmission over a - * network or for file IO. Deserialization is the converse, transforming a byte - * stream into application level objects. - * - * This library differs from other binary serialization libraries in that the - * main interfaces is a "std::format" like - * These functions and classes provide a simple and light abstraction for binary big-endian serialization. There are no - * message or element definitions, no embedded preprocesser syntax, and no extra - * build steps. - * - * These facilities are useful when explicit control of every bit and byte is needed - * (and the wire protocol format is big-endian). Other marshalling and serialization - * designs have strengths and weaknesses (see higher level documentation for more - * explanation). - * - * @note The design of the binary marshall and unmarshall functions is a good fit - * for a C++ metaprogamming implementation (using variadic templates). In particular, - * the primary design concept is a mapping of two (and sometimes three) types to a - * single value. A typelist would allow a single function (or method) call to operate - * on multiple values, instead of being forced to call the @c marshall or @c unmarshall - * function once for each value (or sequence). However, the first release uses the - * simpler (no metaprogramming, no variadic templates) implementation with a hope that - * a more sophisticated version will be available in the future. - * - * The marshalling classes and functions are designed for networking (or file I/O), - * where binary data marshalling and unmarshalling is needed to send and receive - * messages (or to write or read defined portions of a file). Application code using - * this library has full control of every byte that is sent or received. Application - * objects are transformed into a @c std::byte buffer (and the converse) keeping a - * binary representation in network (big-endian) order. - * - * For example, a 32-bit binary number (either a signed or unsigned integer) in native - * endian order will be transformed into four 8-bit bytes in network (big) endian order - * for sending over a network (or for file I/O). Conversely, the four 8-bit bytes in - * network endian order will be transformed back into the original 32-bit binary number - * when received (or read as file I/O). A @c bool can be transformed into either a 8-bit, - * 16-bit, 32-bit, or 64-bit number of either 1 or 0 (and back). A sequence - * (@c std::vector or array or other container) can be transformed into a count (8-bit, - * 16-bit, et al) followed by each element of the sequence. A @c std::optional can be - * transformed into a @c bool (8-bit, 16-bit, et al) followed by the value (if present). - * - * No support is directly provided for higher level abstractions such as inheritance - * hierarchies, version numbers, type flags, or object relations. Pointers are also not - * directly supported (which would typically be part of an object relation). No specific - * wire protocol or data encoding is specified (other than big-endian). These higher - * level abstractions as well as "saving and later restoring a full application state" - * are better served by a library such as Boost Serialization or Google Protocol - * Buffers or Cap'n Proto. - * - * There is not any automatic generation of message processing code (e.g. Google - * Protocol Buffers, a language neutral message definition process that generates - * marshalling and unmarshalling code). Future C++ standards supporting reflection - * may allow higher abstractions and more automation of marshalling code, but this - * library provides a modern C++ API (post C++ 11) for direct control of the - * byte buffers. In particular, all of the build process complications required for - * code generation are not present in this (header only) library. - * - * Wire protocols that are in full text mode do not need to deal with binary endian - * swapping. However, sending or receiving data in a binary form is often desired - * for size efficiency (e.g. sending images and video, large data sets, or where - * the message size needs to be as small as possible). - * - * Functionality is provided for fundamental types, including @c bool, as well as vocabulary - * types such as @c std::string and @c std::optional. Support is also provided for sequences, - * where the number of elements is placed before the element sequence in the stream of - * bytes. - * - * Application defined types can be associated with a @c marshall and @c unmarshall - * function overload, providing a convenient way to reuse the same lower-level - * marshalling code. Specifically, a type @c MyType can be used in a sequence or in - * a @c std::optional or as part of a higher level @c struct or @c class type without needing - * to duplicate the marshalling calls within the @c MyType @c marshall and @c unmarshall - * functions. + * Serialization transforms objects into a byte stream for transmission over a + * network or for file IO. Deserialization is the converse, transforming a byte + * stream into application level objects. + * + * This library differs from other binary serialization libraries in that the + * main interfaces is a "std::format" like + * These functions and classes provide a simple and light abstraction for binary big-endian serialization. There are no + * message or element definitions, no embedded preprocesser syntax, and no extra + * build steps. + * + * These facilities are useful when explicit control of every bit and byte is needed + * (and the wire protocol format is big-endian). Other marshalling and serialization + * designs have strengths and weaknesses (see higher level documentation for more + * explanation). + * + * @note The design of the binary marshall and unmarshall functions is a good fit + * for a C++ metaprogamming implementation (using variadic templates). In particular, + * the primary design concept is a mapping of two (and sometimes three) types to a + * single value. A typelist would allow a single function (or method) call to operate + * on multiple values, instead of being forced to call the @c marshall or @c unmarshall + * function once for each value (or sequence). However, the first release uses the + * simpler (no metaprogramming, no variadic templates) implementation with a hope that + * a more sophisticated version will be available in the future. + * + * The marshalling classes and functions are designed for networking (or file I/O), + * where binary data marshalling and unmarshalling is needed to send and receive + * messages (or to write or read defined portions of a file). Application code using + * this library has full control of every byte that is sent or received. Application + * objects are transformed into a @c std::byte buffer (and the converse) keeping a + * binary representation in network (big-endian) order. + * + * For example, a 32-bit binary number (either a signed or unsigned integer) in native + * endian order will be transformed into four 8-bit bytes in network (big) endian order + * for sending over a network (or for file I/O). Conversely, the four 8-bit bytes in + * network endian order will be transformed back into the original 32-bit binary number + * when received (or read as file I/O). A @c bool can be transformed into either a 8-bit, + * 16-bit, 32-bit, or 64-bit number of either 1 or 0 (and back). A sequence + * (@c std::vector or array or other container) can be transformed into a count (8-bit, + * 16-bit, et al) followed by each element of the sequence. A @c std::optional can be + * transformed into a @c bool (8-bit, 16-bit, et al) followed by the value (if present). + * + * No support is directly provided for higher level abstractions such as inheritance + * hierarchies, version numbers, type flags, or object relations. Pointers are also not + * directly supported (which would typically be part of an object relation). No specific + * wire protocol or data encoding is specified (other than big-endian). These higher + * level abstractions as well as "saving and later restoring a full application state" + * are better served by a library such as Boost Serialization or Google Protocol + * Buffers or Cap'n Proto. + * + * There is not any automatic generation of message processing code (e.g. Google + * Protocol Buffers, a language neutral message definition process that generates + * marshalling and unmarshalling code). Future C++ standards supporting reflection + * may allow higher abstractions and more automation of marshalling code, but this + * library provides a modern C++ API (post C++ 11) for direct control of the + * byte buffers. In particular, all of the build process complications required for + * code generation are not present in this (header only) library. + * + * Wire protocols that are in full text mode do not need to deal with binary endian + * swapping. However, sending or receiving data in a binary form is often desired + * for size efficiency (e.g. sending images and video, large data sets, or where + * the message size needs to be as small as possible). + * + * Functionality is provided for fundamental types, including @c bool, as well as vocabulary + * types such as @c std::string and @c std::optional. Support is also provided for sequences, + * where the number of elements is placed before the element sequence in the stream of + * bytes. + * + * Application defined types can be associated with a @c marshall and @c unmarshall + * function overload, providing a convenient way to reuse the same lower-level + * marshalling code. Specifically, a type @c MyType can be used in a sequence or in + * a @c std::optional or as part of a higher level @c struct or @c class type without needing + * to duplicate the marshalling calls within the @c MyType @c marshall and @c unmarshall + * functions. * - * @c std::variant and @c std::any are not directly supported and require value extraction - * by the application. (Supporting @c std::variant or @c std::any might be a future - * enhancement if a good design is proposed.) @c std::wstring and other non-char strings are - * also not directly supported, and require additional calls from the application. - * - * Central to the design of these marshalling and unmarshalling functions is a mapping of - * two types to a single value. For marshalling, the two types are the native type (e.g. - * @c int, @c short, @c bool), and the type to be used for the marshalling, typically - * a fixed width integer type, as specified in the @c header (e.g. - * @c std::uint32_t, @c std::int16_t, @c std::int8_t). For unmarshalling, the same - * concept is used, a fixed width integer type that specifies the size in the byte - * buffer, and the native type, thus the application would specify that a @c std::int16_t - * in the byte buffer will be unmarshalled into an application @c int value. - * - * @note No support is provided for little-endian in the byte buffer. No support is provided - * for mixed endian (big-endian with little-endian) or where the endianness is specified as a - * type parameter. No support is provided for "in-place" swapping of values. All of these - * use cases can be implemented using other libraries such as Boost Endian. - * - * @note Performance considerations - for marshalling, iterative resizing of the output - * buffer is a fundamental operation. @c std::vector and @c mutable_shared_buffer - * @c resize methods use efficient logic for internal buffer allocations (@c mutable_shared_buffer - * uses @c std::vector internally). Custom containers used as the buffer parameter should - * have similar efficient @c resize method logic. Calling @c reserve at appropriate places may - * provide a small performance increase, at the cost of additional requirements on the buffer - * type. - * - * @author Cliff Green - * - * Copyright (c) 2019 by Cliff Green - * - * Distributed under the Boost Software License, Version 1.0. - * (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + * @c std::variant and @c std::any are not directly supported and require value extraction + * by the application. (Supporting @c std::variant or @c std::any might be a future + * enhancement if a good design is proposed.) @c std::wstring and other non-char strings are + * also not directly supported, and require additional calls from the application. + * + * Central to the design of these marshalling and unmarshalling functions is a mapping of + * two types to a single value. For marshalling, the two types are the native type (e.g. + * @c int, @c short, @c bool), and the type to be used for the marshalling, typically + * a fixed width integer type, as specified in the @c header (e.g. + * @c std::uint32_t, @c std::int16_t, @c std::int8_t). For unmarshalling, the same + * concept is used, a fixed width integer type that specifies the size in the byte + * buffer, and the native type, thus the application would specify that a @c std::int16_t + * in the byte buffer will be unmarshalled into an application @c int value. + * + * @note No support is provided for little-endian in the byte buffer. No support is provided + * for mixed endian (big-endian with little-endian) or where the endianness is specified as a + * type parameter. No support is provided for "in-place" swapping of values. All of these + * use cases can be implemented using other libraries such as Boost Endian. + * + * @note Performance considerations - for marshalling, iterative resizing of the output + * buffer is a fundamental operation. @c std::vector and @c mutable_shared_buffer + * @c resize methods use efficient logic for internal buffer allocations (@c mutable_shared_buffer + * uses @c std::vector internally). Custom containers used as the buffer parameter should + * have similar efficient @c resize method logic. Calling @c reserve at appropriate places may + * provide a small performance increase, at the cost of additional requirements on the buffer + * type. + * + * @author Cliff Green + * + * @copyright (c) 2019-2024 by Cliff Green + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) * */ @@ -114,8 +114,8 @@ #define BINARY_SERIALIZE_HPP_INCLUDED #include "utility/cast_ptr_to.hpp" -#include "marshall/shared_buffer.hpp" -#include "marshall/extract_append.hpp" +#include "buffer/shared_buffer.hpp" +#include "serialize/extract_append.hpp" #include // std::byte, std::size_t, std::nullptr_t #include // std::uint32_t, etc From 55107867b27af65426eebd6392e444ea2a329d24 Mon Sep 17 00:00:00 2001 From: cliffg-softwarelibre Date: Fri, 31 May 2024 17:32:14 -0600 Subject: [PATCH 06/15] Adding Boost License shield to main README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 7d3d097..53e8560 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ ![GH Tag](https://img.shields.io/github/v/tag/connectivecpp/binary-serialize?label=GH%20tag) +![Licence](https://img.shields.io/badge/License-Boost%201.0-blue) + ## Overview The `binary_serialize` functions and classes provide serializing and unserializing of binary data. Serialization provides a way to transform application objects into and out of byte streams that can be sent over a network (or used for file IO). From f1f28dee44d1b1836722f7340fbf215777488662 Mon Sep 17 00:00:00 2001 From: cliffg-softwarelibre Date: Fri, 31 May 2024 17:34:48 -0600 Subject: [PATCH 07/15] Fixing typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 53e8560..c1e377f 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ ![GH Tag](https://img.shields.io/github/v/tag/connectivecpp/binary-serialize?label=GH%20tag) -![Licence](https://img.shields.io/badge/License-Boost%201.0-blue) +![License](https://img.shields.io/badge/License-Boost%201.0-blue) ## Overview From 7a974bcfe616ec08a6c79e579315e2a45e1f0bf6 Mon Sep 17 00:00:00 2001 From: cliffg-softwarelibre Date: Fri, 7 Jun 2024 18:49:18 -0600 Subject: [PATCH 08/15] Enhancing README, removed reinterpret_cast from extract_append.hpp --- README.md | 14 ++++++++-- include/serialize/extract_append.hpp | 42 ++++++++++------------------ 2 files changed, 27 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index c1e377f..49eef16 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Binary Serialize, Header-Only C++ 20 Binary Serialization Classes and Functions +# Binary Serialize, Header-Only C++ 20 Binary Serialization Classes and Functions Using a `std::format` Like Syntax #### Unit Test and Documentation Generation Workflow Status @@ -14,10 +14,20 @@ ## Overview -The `binary_serialize` functions and classes provide serializing and unserializing of binary data. Serialization provides a way to transform application objects into and out of byte streams that can be sent over a network (or used for file IO). +The `binary_serialize` functions and classes provide serializing and unserializing of binary data. Serialization provides a way to transform application objects into and out of byte streams that can be sent over a network (or used for file IO). Many serialization libraries transform objects to and from text representations, but this library keeps data in binary formats. The serialization functionality in this repository is useful when explicit control is needed for every bit and byte. This allows a developer to match an existing wire protocol or encoding scheme or to define his or her own wire protocol. Support is provided for fundamental arithmetic types as well as certain C++ vocabulary types such as `std::optional`. Both big and little endian support is provided. +This library uses `std::format` style formatting. For example: + +``` +(insert example code) +``` + +The documentation overview provides a comparison with other serialization libraries as well as a rationale for the design decisions. + +Inspiration and thanks go to [Louis Langholtz](https://github.com/louis-langholtz), who steered me towards considering the `std::format` API. + ## Generated Documentation The generated Doxygen documentation for `binary_serialize` is [here](https://connectivecpp.github.io/binary-serialize/). diff --git a/include/serialize/extract_append.hpp b/include/serialize/extract_append.hpp index be7863c..ad2c14a 100644 --- a/include/serialize/extract_append.hpp +++ b/include/serialize/extract_append.hpp @@ -4,33 +4,25 @@ * endian order) to native format; conversely, given an arithmetic binary value, append * it to a buffer of bytes in the specified endian order. * - * The functions in this file are low-level, handling fundamental arithmetic types and + * The functions in this file are low-level. The handle fundamental arithmetic types and * extracting or appending to @c std::byte buffers. It is meant to be the lower layer * of serializing utilities, where the next layer up provides buffer management, * sequences, and overloads for specific types such as @c std::string, @c bool, and * @c std::optional. * - * @note When C++ 20 @c std::endian is available, many of these functions can be made - * @c constexpr and evaluated at compile time. Until then, run-time endian detection and - * copying is performed. + * @note The variable sized integer functions (@c extract_var_int, @c append_var_int) + * support the variable byte integer type in MQTT (Message Queuing Telemetry Transport), + * a commonly used IoT protocol. The code in this header is adapted from a + * Techoverflow.net article by Uli Koehler and published under the CC0 1.0 Universal + * license: + * https://techoverflow.net/2013/01/25/efficiently-encoding-variable-length-integers-in-cc/ * - * @note The variable sized integer functions (@c extract_var_int, @c append_var_int) - * support the variable byte integer type in MQTT (Message Queuing Telemetry Transport), - * a commonly used IoT protocol. The code in this header is adapted from a - * Techoverflow.net article by Uli Koehler and published under the CC0 1.0 Universal - * license: - * https://techoverflow.net/2013/01/25/efficiently-encoding-variable-length-integers-in-cc/ + * @author Cliff Green, Roxanne Agerone, Uli Koehler * - * @note This implementation has manual generated unrolled loops for the byte moving and - * swapping. This can be improved in the future by using a compile-time unrolling utility, such - * as the @c repeat function (compile time unrolling version) by Vittorio Romeo. + * @copyright (c) 2019-2024 by Cliff Green, Roxanne Agerone * - * @author Cliff Green, Roxanne Agerone, Uli Koehler - * - * @copyright (c) 2019-2024 by Cliff Green, Roxanne Agerone - * - * Distributed under the Boost Software License, Version 1.0. - * (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) * */ @@ -191,19 +183,18 @@ constexpr std::size_t append_val(std::byte* buf, const T& val) noexcept { template constexpr std::size_t append_var_int(std::byte* output, T val) { - std::uint8_t* output_ptr = reinterpret_cast(output); std::size_t output_size = 0; // While more than 7 bits of data are left, occupy the last output byte // and set the next byte flag while (val > 127) { - output_ptr[output_size] = (static_cast (val & 127)) | 128; + output[output_size] = std::bit_cast(static_cast((static_cast (val & 127)) | 128)); //Remove the seven bits we just wrote val >>= 7; ++output_size; } - output_ptr[output_size++] = static_cast (val) & 127; + output[output_size++] = std::bit_cast(static_cast(static_cast (val) & 127)); return output_size; } /** @@ -222,14 +213,11 @@ constexpr std::size_t append_var_int(std::byte* output, T val) { */ template constexpr T extract_var_int(const std::byte* input, std::size_t input_size) { - - const std::uint8_t* input_ptr = reinterpret_cast (input); - T ret = 0; for (std::size_t i = 0; i < input_size; ++i) { - ret |= (input_ptr[i] & 127) << (7 * i); + ret |= (std::bit_cast(input[i]) & 127) << (7 * i); //If the next-byte flag is set - if(!(input_ptr[i] & 128)) { + if(!(std::bit_cast(input[i]) & 128)) { break; } } From ee3b623402b2588e168ebd6cf8262a10a5e1b65c Mon Sep 17 00:00:00 2001 From: cliffg-softwarelibre Date: Wed, 19 Jun 2024 17:08:45 -0600 Subject: [PATCH 09/15] Adding example code (which may change) --- README.md | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 49eef16..5bbba12 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,33 @@ The serialization functionality in this repository is useful when explicit contr This library uses `std::format` style formatting. For example: ``` -(insert example code) + struct Hike { + unsigned int distance; + int elevation; + std::optional> name; + std::vector waypoints; + }; + // ... + chops::mutable_shared_buffer buf; + + chops::binary_serialize(buf, "{32ui}{16i}{8ui}{16ui}{16ui}{64i}", + hike_obj.distance, hike_obj.elevation, hike_obj.name, hike_obj.waypoints); + + // ... + net_obj.send(buf); + +``` + +The buffer will contain the following: + +``` + 32 bit unsigned integer containing distance value + 16 bit signed integer containing elevation value + 8 bit unsigned integer corresponding to true or false for the optional + 16 bit unsigned integer for the size of the name string + 0 - N 8 bit characters for the name string + 16 bit unsigned integer for the size of the waypoints vector + 0 - N 64 bit signed integers for each waypoint value ``` The documentation overview provides a comparison with other serialization libraries as well as a rationale for the design decisions. From b59b4c2583088f01348ddaeda37a1a3d8cd32ec8 Mon Sep 17 00:00:00 2001 From: cliffg-softwarelibre Date: Sun, 25 Aug 2024 17:35:53 -0600 Subject: [PATCH 10/15] Updated Catch2 and utility-rack versions --- test/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ee181bd..d5295bf 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -11,8 +11,8 @@ project ( binary_serialize_test LANGUAGES CXX ) # add dependencies include ( ../cmake/download_cpm.cmake ) -CPMAddPackage ( "gh:catchorg/Catch2@3.6.0" ) -CPMAddPackage ( "gh:connectivecpp/utility-rack@1.0.0" ) +CPMAddPackage ( "gh:catchorg/Catch2@3.7.0" ) +CPMAddPackage ( "gh:connectivecpp/utility-rack@1.0.3" ) set ( test_app_names byteswap_test extract_append_test From 96263800f63b75f99674991ba6455b6ba6b158ae Mon Sep 17 00:00:00 2001 From: cliffg-softwarelibre Date: Sun, 25 Aug 2024 18:00:59 -0600 Subject: [PATCH 11/15] Adding back MSVC and macos --- .github/workflows/build_run_unit_test_cmake.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_run_unit_test_cmake.yml b/.github/workflows/build_run_unit_test_cmake.yml index 22abd42..1f6764d 100644 --- a/.github/workflows/build_run_unit_test_cmake.yml +++ b/.github/workflows/build_run_unit_test_cmake.yml @@ -12,8 +12,8 @@ jobs: build_matrix: strategy: matrix: - # os: [ubuntu-latest, windows-latest, macos-14] - os: [ubuntu-latest] + os: [ubuntu-latest, windows-latest, macos-14] + # os: [ubuntu-latest] runs-on: ${{ matrix.os }} defaults: run: From 93a61d675458809a0c32b27eee8f1735922dd96c Mon Sep 17 00:00:00 2001 From: cliffg-softwarelibre Date: Sun, 25 Aug 2024 18:02:29 -0600 Subject: [PATCH 12/15] Working around Catch2 link issues with std::byte formatting --- test/extract_append_test.cpp | 44 +++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/test/extract_append_test.cpp b/test/extract_append_test.cpp index 413bcc1..ffb1293 100644 --- a/test/extract_append_test.cpp +++ b/test/extract_append_test.cpp @@ -20,7 +20,7 @@ #include "serialize/extract_append.hpp" #include "utility/repeat.hpp" -#include "utility/make_byte_array.hpp" +#include "utility/byte_array.hpp" constexpr std::uint32_t val1 = 0xDDCCBBAA; constexpr char val2 = static_cast(0xEE); @@ -44,17 +44,17 @@ TEST_CASE ( "Append values into a buffer", "[append_val]" ) { SECTION ("Append_val with a single value, big endian") { REQUIRE(chops::append_val(buf, v) == 4u); - REQUIRE (buf[0] == static_cast(0x04)); - REQUIRE (buf[1] == static_cast(0x03)); - REQUIRE (buf[2] == static_cast(0x02)); - REQUIRE (buf[3] == static_cast(0x01)); + REQUIRE (std::to_integer(buf[0]) == 0x04); + REQUIRE (std::to_integer(buf[1]) == 0x03); + REQUIRE (std::to_integer(buf[2]) == 0x02); + REQUIRE (std::to_integer(buf[3]) == 0x01); } SECTION ("Append_val with a single value, little endian") { REQUIRE(chops::append_val(buf, v) == 4u); - REQUIRE (buf[0] == static_cast(0x01)); - REQUIRE (buf[1] == static_cast(0x02)); - REQUIRE (buf[2] == static_cast(0x03)); - REQUIRE (buf[3] == static_cast(0x04)); + REQUIRE (std::to_integer(buf[0]) == 0x01); + REQUIRE (std::to_integer(buf[1]) == 0x02); + REQUIRE (std::to_integer(buf[2]) == 0x03); + REQUIRE (std::to_integer(buf[3]) == 0x04); } SECTION ("Append_val with multiple values, big endian") { @@ -65,7 +65,8 @@ TEST_CASE ( "Append values into a buffer", "[append_val]" ) { REQUIRE(chops::append_val(ptr, val4) == 8u); ptr += sizeof(val4); REQUIRE(chops::append_val(ptr, val5) == 4u); ptr += sizeof(val5); REQUIRE(chops::append_val(ptr, val6) == 1u); - chops::repeat(arr_sz, [&buf] (int i) { REQUIRE (buf[i] == net_buf_big[i]); } ); + chops::repeat(arr_sz, [&buf] (int i) { + REQUIRE (std::to_integer(buf[i]) == std::to_integer(net_buf_big[i])); } ); } SECTION ("Append_val with multiple values, little endian") { std::byte* ptr = buf; @@ -75,7 +76,8 @@ TEST_CASE ( "Append values into a buffer", "[append_val]" ) { REQUIRE(chops::append_val(ptr, val4) == 8u); ptr += sizeof(val4); REQUIRE(chops::append_val(ptr, val5) == 4u); ptr += sizeof(val5); REQUIRE(chops::append_val(ptr, val6) == 1u); - chops::repeat(arr_sz, [&buf] (int i) { REQUIRE (buf[i] == net_buf_little[i]); } ); + chops::repeat(arr_sz, [&buf] (int i) { + REQUIRE (std::to_integer(buf[i]) == std::to_integer(net_buf_little[i])); } ); } } @@ -95,7 +97,7 @@ TEST_CASE ( "Extract values from a buffer", "[extract_val]" ) { REQUIRE(v3 == val3); REQUIRE(v4 == val4); REQUIRE(v5 == val5); - REQUIRE(v6 == val6); + REQUIRE(std::to_integer(v6) == std::to_integer(val6)); } SECTION ( "Extract_val for multiple values in little endian buf") { const std::byte* ptr = net_buf_little.data(); @@ -111,7 +113,7 @@ TEST_CASE ( "Extract values from a buffer", "[extract_val]" ) { REQUIRE(v3 == val3); REQUIRE(v4 == val4); REQUIRE(v5 == val5); - REQUIRE(v6 == val6); + REQUIRE(std::to_integer(v6) == std::to_integer(val6)); } } @@ -131,9 +133,9 @@ TEST_CASE ( "Append and extract variable length integers","[append_var_int]" ) { { auto outsize = chops::append_var_int(test_buf, 0xCAFE); - REQUIRE(static_cast (test_buf[0]) == 254); - REQUIRE(static_cast (test_buf[1]) == 149); - REQUIRE(static_cast (test_buf[2]) == 3); + REQUIRE(std::to_integer(test_buf[0]) == 254); + REQUIRE(std::to_integer(test_buf[1]) == 149); + REQUIRE(std::to_integer(test_buf[2]) == 3); auto output = chops::extract_var_int(test_buf, outsize); @@ -163,23 +165,23 @@ TEST_CASE ( "Append var len integer of 127","[append_var_int]" ) { std::byte test_buf [7]; auto outsize = chops::append_var_int(test_buf, 0x7F); - REQUIRE(static_cast (test_buf[0]) == 127); + REQUIRE(std::to_integer(test_buf[0]) == 127); REQUIRE(outsize == 1); } TEST_CASE ( "Append var len integer of 128","[append_var_int]" ) { std::byte test_buf [7]; auto outsize = chops::append_var_int(test_buf, 0x80); - REQUIRE(static_cast (test_buf[0]) == 128); //byte flag set - REQUIRE(static_cast (test_buf[1]) == 1); + REQUIRE(std::to_integer(test_buf[0]) == 128); //byte flag set + REQUIRE(std::to_integer(test_buf[1]) == 1); REQUIRE(outsize == 2); } TEST_CASE ( "Append var len integer larger than 4 bytes","[append_var_int]" ) { std::byte test_buf [7]; auto outsize = chops::append_var_int(test_buf, 0x10000000); - REQUIRE(static_cast (test_buf[0]) == 128); //byte flag set - REQUIRE(static_cast (test_buf[4]) == 1); + REQUIRE(std::to_integer(test_buf[0]) == 128); //byte flag set + REQUIRE(std::to_integer(test_buf[4]) == 1); REQUIRE(outsize == 5); } From 9ec6329b256580f735a0d40d304715e3f36a4eb3 Mon Sep 17 00:00:00 2001 From: cliffg-softwarelibre Date: Tue, 10 Sep 2024 11:28:23 -0600 Subject: [PATCH 13/15] working on new API and details --- include/serialize/binary_serialize.hpp | 35 ++++++++++++++++++++++++-- include/serialize/byteswap.hpp | 15 +++++++++-- include/serialize/extract_append.hpp | 10 +++++--- 3 files changed, 52 insertions(+), 8 deletions(-) diff --git a/include/serialize/binary_serialize.hpp b/include/serialize/binary_serialize.hpp index 9ccd92e..7752902 100644 --- a/include/serialize/binary_serialize.hpp +++ b/include/serialize/binary_serialize.hpp @@ -1,11 +1,14 @@ /** @mainpage Binary Serialuze, Classes and Functions For Binary Data Serialization + * + * ## Overview * * Serialization transforms objects into a byte stream for transmission over a * network or for file IO. Deserialization is the converse, transforming a byte * stream into application level objects. * * This library differs from other binary serialization libraries in that the - * main interfaces is a "std::format" like + * main interfaces is a "std::format" like interface. + * * These functions and classes provide a simple and light abstraction for binary big-endian serialization. There are no * message or element definitions, no embedded preprocesser syntax, and no extra * build steps. @@ -113,7 +116,6 @@ #ifndef BINARY_SERIALIZE_HPP_INCLUDED #define BINARY_SERIALIZE_HPP_INCLUDED -#include "utility/cast_ptr_to.hpp" #include "buffer/shared_buffer.hpp" #include "serialize/extract_append.hpp" @@ -127,12 +129,28 @@ #include // value type of iterator #include #include +#include #include // debugging namespace chops { +template +concept supports_expandable_buffer = + std::same_as && + requires (Ctr ctr) { + { ctr.size() } -> std::integral; + ctr.resize(std::size_t{}); + { ctr.data() } -> std::same_as; + } + +template +struct expandable_buffer : Ctr { + using Endian; +} /** + * @brief Extract a sequence in network byte order from a @c std::byte buffer into the * provided container. * @@ -188,6 +206,8 @@ Container extract_sequence(const std::byte* buf) noexcept(fill in) { * the elements in the sequence. * */ + + /* template std::size_t append_sequence(std::byte* buf, Cnt cnt, Iter start, Iter end) noexcept { @@ -278,6 +298,17 @@ Buf& marshall(Buf& buf, const T& val, adl_tag) { return buf; } +namespace detail { + +template +constexpr Buf& serialize_val(Buf& buf, const T& val) { + auto old_sz = buf.size(); + buf.resize(old_sz + sizeof(CastValType)); + append_val(buf.data()+old_sz, static_cast(val)); + return buf; +} + +} /** * @brief Marshall a single arithmetic value or a @c std::byte into a buffer of bytes. * diff --git a/include/serialize/byteswap.hpp b/include/serialize/byteswap.hpp index 7c354a4..797f9c7 100644 --- a/include/serialize/byteswap.hpp +++ b/include/serialize/byteswap.hpp @@ -1,6 +1,6 @@ /** @file * - * @brief This is a direct implementation of the C++ 23 std::byteswap function, + * @brief This is an implementation of the C++ 23 @c std::byteswap function, * for use in pre C++ 23 applications. * * This implementation is taken directly from the CPP Reference page: @@ -18,7 +18,7 @@ #ifndef BYTESWAP_HPP_INCLUDED #define BYTESWAP_HPP_INCLUDED -#include // std::integral +#include // std::integral concept #include // std::bit_cast #include #include // std::byte @@ -26,6 +26,17 @@ namespace chops { +/** + * @brief Perform an in-place byte swap on an integral type (if size of the + * type is greater than one). + * + * @tparam T Type of value where swapping will occur. + * + * @param value Integral value to be byte swapped. + * + * @pre There must not be any padding bits in the type. + * + */ template constexpr T byteswap(T value) noexcept { if constexpr (sizeof(T) == 1u) { diff --git a/include/serialize/extract_append.hpp b/include/serialize/extract_append.hpp index ad2c14a..de22452 100644 --- a/include/serialize/extract_append.hpp +++ b/include/serialize/extract_append.hpp @@ -4,9 +4,9 @@ * endian order) to native format; conversely, given an arithmetic binary value, append * it to a buffer of bytes in the specified endian order. * - * The functions in this file are low-level. The handle fundamental arithmetic types and + * The functions in this file are low-level. They handle fundamental arithmetic types and * extracting or appending to @c std::byte buffers. It is meant to be the lower layer - * of serializing utilities, where the next layer up provides buffer management, + * of serializing utilities, where the next higher layer provides buffer management, * sequences, and overloads for specific types such as @c std::string, @c bool, and * @c std::optional. * @@ -78,7 +78,7 @@ concept integral_or_byte = std::integral || std::is_same_v Date: Tue, 10 Sep 2024 11:28:46 -0600 Subject: [PATCH 14/15] tweaking README comments --- README.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 5bbba12..5f6d510 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,9 @@ The `binary_serialize` functions and classes provide serializing and unserializing of binary data. Serialization provides a way to transform application objects into and out of byte streams that can be sent over a network (or used for file IO). Many serialization libraries transform objects to and from text representations, but this library keeps data in binary formats. -The serialization functionality in this repository is useful when explicit control is needed for every bit and byte. This allows a developer to match an existing wire protocol or encoding scheme or to define his or her own wire protocol. Support is provided for fundamental arithmetic types as well as certain C++ vocabulary types such as `std::optional`. Both big and little endian support is provided. +The serialization functionality in this repository is useful when explicit control is needed for every bit and byte. This allows a developer to match an existing wire protocol or encoding scheme or to define his or her own wire protocol. Support is provided for fundamental arithmetic types as well as many C++ vocabulary types such as `std::optional`. Both big and little endian support is provided. + +Full flexibility is provided for sequences such as `std::string` or `std::vector`. The number of elements can be specified to take 8 or 16 or 32 (etc) bits, followed by the sequence of chars or array elements. Similar flexibility is provided for vocabulary types such as `std::optional`, where the boolean flag can be specified as 8 or 16 or 32 (etc) bits, followed by the object (or none, if there is no value in the optional). This library uses `std::format` style formatting. For example: @@ -28,24 +30,25 @@ This library uses `std::format` style formatting. For example: std::vector waypoints; }; // ... - chops::mutable_shared_buffer buf; + chops::expandable_buffer buf; - chops::binary_serialize(buf, "{32ui}{16i}{8ui}{16ui}{16ui}{64i}", + chops::binary_serialize(buf, "{32u}{16}{8u}{16u}{16u}{64}", hike_obj.distance, hike_obj.elevation, hike_obj.name, hike_obj.waypoints); // ... - net_obj.send(buf); + net_obj.send(buf.get_buf()); ``` -The buffer will contain the following: +The buffer will contain the following (note that truncation or casting will happen between the application +object types and the serialized types as needed): ``` 32 bit unsigned integer containing distance value 16 bit signed integer containing elevation value 8 bit unsigned integer corresponding to true or false for the optional - 16 bit unsigned integer for the size of the name string - 0 - N 8 bit characters for the name string + 16 bit unsigned integer for the size of the name string (if optional is true) + 0 - N 8 bit characters for the name string (if optional is true) 16 bit unsigned integer for the size of the waypoints vector 0 - N 64 bit signed integers for each waypoint value ``` From 794e6e1bd4a0e98d4b3059983a8c42e0afd08c19 Mon Sep 17 00:00:00 2001 From: cliffg-softwarelibre Date: Tue, 10 Sep 2024 12:53:03 -0600 Subject: [PATCH 15/15] More dev --- include/serialize/binary_serialize.hpp | 76 ++++++++++++++++++++------ 1 file changed, 59 insertions(+), 17 deletions(-) diff --git a/include/serialize/binary_serialize.hpp b/include/serialize/binary_serialize.hpp index 7752902..968bf0f 100644 --- a/include/serialize/binary_serialize.hpp +++ b/include/serialize/binary_serialize.hpp @@ -144,11 +144,27 @@ concept supports_expandable_buffer = { ctr.data() } -> std::same_as; } +template +concept supports_endian_expandable_buffer = + supports_expandable_buffer && + requires (Ctr ctr) { + typename Ctr::endian_type; + } + template -struct expandable_buffer : Ctr { - using Endian; -} +class expandable_buffer { +private: + Ctr m_ctr; +public: + using endian_type = Endian; + using value_type = std::byte; + Ctr& get_buf() noexcept { return m_ctr; } + std::size_t size() noexcept { return m_ctr.size(); } + std::byte* data() noexcept { return m_ctr.data(); } + void resize(std::size_t sz) noexcept m_ctr.resize(sz); } +}; + /** * @brief Extract a sequence in network byte order from a @c std::byte buffer into the @@ -286,29 +302,55 @@ class fixed_size_byte_array { // similar can be used for the Buf template parameter struct adl_tag { }; -// lower-level function template that performs the actual buffer manipulation and -// marshalling of a single value, with an ADL tag for full namespace inclusion in the -// overload set; this function template is not called directly by application code, and -// is only used with arithmetic values or a std::byte -template -Buf& marshall(Buf& buf, const T& val, adl_tag) { - auto old_sz = buf.size(); - buf.resize(old_sz + sizeof(CastValType)); - append_val(buf.data()+old_sz, static_cast(val)); - return buf; -} namespace detail { -template +template constexpr Buf& serialize_val(Buf& buf, const T& val) { auto old_sz = buf.size(); - buf.resize(old_sz + sizeof(CastValType)); - append_val(buf.data()+old_sz, static_cast(val)); + buf.resize(old_sz + sizeof(CastTypeVal)); + append_val(buf.data()+old_sz, static_cast(val)); + return buf; +} + +template +constexpr Buf& serialize(Buf& buf, const T& val) { + return serialize_val (buf, val); +} + +template +constexpr Buf& serialize(Buf& buf, const T* seq, std::size_t sz) { + serialize_val (buf, sz); + for (int i {0}; i < sz; ++i) { + serialize_val(buf, seq[i]); + } return buf; } +template +constexpr Buf& serialize(Buf& buf, const std::string& str) { + return serialize (buf, str.data(), std.size()); +} + +template +constexpr Buf& serialize(Buf& buf, const std::optional& val) { + if (val) + serialize_val (buf, 1); + return serialize_val (buf, *val); + } + serialize_val (buf, 0); +} + } + + /** * @brief Marshall a single arithmetic value or a @c std::byte into a buffer of bytes. *