diff --git a/.devcontainer/s390x/Dockerfile b/.devcontainer/s390x/Dockerfile new file mode 100644 index 000000000..21a6289c9 --- /dev/null +++ b/.devcontainer/s390x/Dockerfile @@ -0,0 +1,40 @@ +# s390x Big-Endian Test Environment for ETL +# Uses QEMU user-mode emulation to run s390x binaries on x64 host +FROM debian:trixie + +# Avoid prompts from apt +ENV DEBIAN_FRONTEND=noninteractive + +# Install QEMU user-mode emulation and s390x cross-compilation tools +RUN dpkg --add-architecture s390x && \ + apt-get update && apt-get install -y --no-install-recommends\ + qemu-user-static \ + qemu-user \ + binfmt-support \ + gcc-s390x-linux-gnu \ + g++-s390x-linux-gnu \ + cmake \ + make \ + ninja-build \ + git \ + wget \ + file \ + libc6:s390x \ + libstdc++6:s390x \ + && rm -rf /var/lib/apt/lists/* + +# Set working directory +WORKDIR /workspaces/etl + +# Verify QEMU and cross-compilation setup +RUN echo "=== Host Architecture ===" && \ + uname -m && \ + echo "" && \ + echo "=== s390x Cross Compiler ===" && \ + s390x-linux-gnu-gcc --version && \ + echo "" && \ + echo "=== QEMU s390x ===" && \ + qemu-s390x-static --version | head -n1 + +# Default command +CMD ["/bin/bash"] diff --git a/.devcontainer/s390x/devcontainer.json b/.devcontainer/s390x/devcontainer.json new file mode 100644 index 000000000..8b7f82355 --- /dev/null +++ b/.devcontainer/s390x/devcontainer.json @@ -0,0 +1,29 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/cpp +{ + "name": "s390x Big Endian (Debian)", + "build": { + "dockerfile": "./Dockerfile", + "context": "." + }, + "customizations": { + "vscode": { + "extensions": [ + "ms-vscode.cpptools", + "ms-vscode.cmake-tools" + ], + "settings": { + "cmake.sourceDirectory": "${workspaceFolder}/test", + "cmake.configureArgs": [ + "-DCMAKE_TOOLCHAIN_FILE=${workspaceFolder}/.devcontainer/s390x/toolchain-s390x.cmake", + "-DBUILD_TESTS=ON", + "-DNO_STL=OFF", + "-DETL_CXX_STANDARD=17" + ], + "cmake.buildDirectory": "${workspaceFolder}/build-s390x", + "cmake.generator": "Ninja" + } + } + }, + "remoteUser": "root" +} diff --git a/.devcontainer/s390x/toolchain-s390x.cmake b/.devcontainer/s390x/toolchain-s390x.cmake new file mode 100644 index 000000000..706884d98 --- /dev/null +++ b/.devcontainer/s390x/toolchain-s390x.cmake @@ -0,0 +1,21 @@ +# CMake toolchain file for s390x cross-compilation +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR s390x) + +# Specify the cross compiler +set(CMAKE_C_COMPILER s390x-linux-gnu-gcc) +set(CMAKE_CXX_COMPILER s390x-linux-gnu-g++) +set(CMAKE_AR s390x-linux-gnu-ar) +set(CMAKE_RANLIB s390x-linux-gnu-ranlib) +set(CMAKE_STRIP s390x-linux-gnu-strip) + +# Search for programs in the build host directories +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + +# For libraries and headers in the target directories +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + +# Set QEMU for running tests +set(CMAKE_CROSSCOMPILING_EMULATOR /usr/bin/qemu-s390x-static CACHE FILEPATH "Path to the emulator for cross-compiled binaries") diff --git a/docs/manchester.md b/docs/manchester.md index 06c0cf7d4..d73cb9581 100644 --- a/docs/manchester.md +++ b/docs/manchester.md @@ -13,8 +13,9 @@ Efficient Manchester encoding and decoding of data. The Manchester code represen - Normal and inverted Manchester encoding - Support for multiple encoding chunk sizes: 8-bit, 16-bit and 32-bit - Span-based operations or chunk-based operations -- Constexpr functions for compile-time encoding/decoding (8-bit chunk size only) +- Constexpr functions for compile-time encoding/decoding - Validation of encoded data +- Chunked span I/O uses little-endian byte order for multi-byte chunks, independent of host platform endianness ## Algorithm background diff --git a/include/etl/manchester.h b/include/etl/manchester.h index 4c35deb86..fd6e83b4d 100644 --- a/include/etl/manchester.h +++ b/include/etl/manchester.h @@ -32,10 +32,8 @@ SOFTWARE. #define ETL_MANCHESTER_INCLUDED #include "platform.h" -#include "endianness.h" #include "span.h" #include "static_assert.h" -#include ///\defgroup manchester manchester /// Manchester encoding and decoding @@ -172,18 +170,37 @@ namespace etl }; //************************************************************************* - /// Alias for memcpy. etl::mem_copy is not suitable for the Manchester - /// algorithm because all memory copies are between different types. This - /// alias is a way to respect ETL_USING_BUILTIN_MEMCPY while using the - /// memcpy function signature + /// Read a multi-byte value from a span in little-endian byte order. + ///\tparam T The type to read. + ///\param bytes The span to read from. + ///\param index The starting index in the span. + ///\return The value read from the span. //************************************************************************* - inline void* memcpy(void* dest, const void* src, std::size_t count) ETL_NOEXCEPT + template + static ETL_CONSTEXPR14 T read_little_endian(etl::span bytes, size_t index) { -#if ETL_USING_BUILTIN_MEMCPY - return __builtin_memcpy(dest, src, count); -#else - return ::memcpy(dest, src, count); -#endif + T value = 0; + for (size_t j = 0; j < sizeof(T); ++j) + { + value |= static_cast(bytes[index + j]) << (j * CHAR_BIT); + } + return value; + } + + //************************************************************************* + /// Write a multi-byte value to a span in little-endian byte order. + ///\tparam T The type to write. + ///\param bytes The span to write to. + ///\param index The starting index in the span. + ///\param value The value to write. + //************************************************************************* + template + static ETL_CONSTEXPR14 void write_little_endian(etl::span bytes, size_t index, T value) + { + for (size_t j = 0; j < sizeof(T); ++j) + { + bytes[index + j] = static_cast(value >> (j * CHAR_BIT)); + } } } // namespace private_manchester @@ -297,44 +314,13 @@ namespace etl #endif //************************************************************************* - /// Encode a span of data with the selected chunk size. - ///\param source The source data to encode. - ///\param destination The destination buffer for encoded data. - ///\tparam TChunk The chunk size for encoding (default: uint_least8_t). - //************************************************************************* - template - static typename etl::enable_if::value, void>::type - encode(etl::span decoded, etl::span encoded) - { - typedef TChunk TDecoded; - typedef typename etl::private_manchester::encoded::type TEncoded; - - ETL_ASSERT(encoded.size() >= decoded.size() * 2, ETL_ERROR(manchester_invalid_size)); - ETL_ASSERT(decoded.size() % sizeof(TDecoded) == 0, ETL_ERROR(manchester_invalid_size)); - - size_t dest_index = 0; - size_t source_index = 0; - for (size_t i = 0; i < decoded.size() / sizeof(TDecoded); ++i) - { - TDecoded decoded_value = 0; - etl::private_manchester::memcpy(&decoded_value, &decoded[source_index], sizeof(TDecoded)); - const TEncoded encoded_value = encode(decoded_value); - etl::private_manchester::memcpy(&encoded[dest_index], &encoded_value, sizeof(TEncoded)); - - source_index += sizeof(TDecoded); - dest_index += sizeof(TEncoded); - } - } - - //************************************************************************* - /// Encode a span of data with the minimum chunk size. This version is - /// constexpr so that it can be used to encode data at compile time. + /// Encode a span of data with the specified chunk size. ///\param source The source data to encode. ///\param destination The destination buffer for encoded data. ///\tparam TChunk The chunk size for encoding (default: uint_least8_t). //************************************************************************* template - static ETL_CONSTEXPR14 typename etl::enable_if::value, void>::type encode(etl::span decoded, etl::span encoded) + static ETL_CONSTEXPR14 void encode(etl::span decoded, etl::span encoded) { typedef TChunk TDecoded; typedef typename etl::private_manchester::encoded::type TEncoded; @@ -346,17 +332,9 @@ namespace etl size_t source_index = 0; for (size_t i = 0; i < decoded.size() / sizeof(TDecoded); ++i) { - const TEncoded encoded_value = encode(decoded[source_index]); - if (etl::endianness::value() == etl::endian::little) - { - encoded[dest_index] = static_cast(encoded_value); - encoded[dest_index + 1] = static_cast(encoded_value >> CHAR_BIT); - } - else - { - encoded[dest_index] = static_cast(encoded_value >> CHAR_BIT); - encoded[dest_index + 1] = static_cast(encoded_value); - } + const TDecoded decoded_value = private_manchester::read_little_endian(decoded, source_index); + const TEncoded encoded_value = encode(decoded_value); + private_manchester::write_little_endian(encoded, dest_index, encoded_value); source_index += sizeof(TDecoded); dest_index += sizeof(TEncoded); @@ -426,14 +404,13 @@ namespace etl #endif //************************************************************************* - /// Decode a span of data using specified chunk type. + /// Decode a span of data using the specified chunk type. ///\param source The source encoded data to decode. ///\param destination The destination buffer for decoded data. - ///\tparam TChunk The chunk type for decoding. + ///\tparam TChunk The chunk type for decoding (default: uint16_t). //************************************************************************* - template - static typename etl::enable_if::type>::value, void>::type - decode(etl::span encoded, etl::span decoded) + template ::type> + static ETL_CONSTEXPR14 void decode(etl::span encoded, etl::span decoded) { typedef typename private_manchester::decoded::type TDecoded; typedef TChunk TEncoded; @@ -445,53 +422,15 @@ namespace etl size_t source_index = 0; for (size_t i = 0; i < encoded.size() / sizeof(TEncoded); ++i) { - TChunk encoded_value = 0; - etl::private_manchester::memcpy(&encoded_value, &encoded[source_index], sizeof(TEncoded)); + const TEncoded encoded_value = private_manchester::read_little_endian(encoded, source_index); const TDecoded decoded_value = decode(encoded_value); - etl::private_manchester::memcpy(&decoded[dest_index], &decoded_value, sizeof(TDecoded)); + private_manchester::write_little_endian(decoded, dest_index, decoded_value); source_index += sizeof(TEncoded); dest_index += sizeof(TDecoded); } } - //************************************************************************* - /// Decode a span of data using the smallest chunk type. This version is - /// constexpr so that it can be used to decode data at compile time. - ///\param source The source encoded data to decode. - ///\param destination The destination buffer for decoded data. - ///\tparam TChunk The chunk type for decoding (default type). - //************************************************************************* - template ::type> - static ETL_CONSTEXPR14 typename etl::enable_if::type>::value, void>::type - decode(etl::span encoded, etl::span decoded) - { - typedef uint_least8_t TDecoded; - - ETL_ASSERT(decoded.size() * 2 >= encoded.size(), ETL_ERROR(manchester_invalid_size)); - ETL_ASSERT(encoded.size() % sizeof(TChunk) == 0, ETL_ERROR(manchester_invalid_size)); - - size_t dest_index = 0; - size_t source_index = 0; - for (size_t i = 0; i < encoded.size() / sizeof(TChunk); ++i) - { - TChunk encoded_value{}; - if (etl::endianness::value() == etl::endian::little) - { - encoded_value = static_cast((encoded[source_index + 1] << CHAR_BIT) | encoded[source_index]); - } - else - { - encoded_value = static_cast((encoded[source_index] << CHAR_BIT) | encoded[source_index + 1]); - } - - decoded[dest_index] = decode(encoded_value); - - source_index += sizeof(TChunk); - dest_index += sizeof(TDecoded); - } - } - //************************************************************************* // Validation functions //************************************************************************* @@ -521,15 +460,7 @@ namespace etl for (size_t i = 0; i < encoded.size(); i += sizeof(uint16_t)) { - uint16_t chunk{}; - if (etl::endianness::value() == etl::endian::little) - { - chunk = static_cast((encoded[i + 1] << CHAR_BIT) | encoded[i]); - } - else - { - chunk = static_cast((encoded[i] << CHAR_BIT) | encoded[i + 1]); - } + const uint16_t chunk = private_manchester::read_little_endian(encoded, i); if (!is_valid(chunk)) { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2bec78478..9a3bc89b8 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -524,8 +524,9 @@ target_include_directories(etl_tests add_subdirectory(UnitTest++) target_link_libraries(etl_tests PRIVATE UnitTestpp) +enable_testing() # Enable the 'make test' CMake target using the executable defined above -add_test(etl_unit_tests etl_tests) +add_test(NAME etl_unit_tests COMMAND etl_tests) # Since ctest will only show you the results of the single executable # define a target that will output all of the failing or passing tests diff --git a/test/test_manchester.cpp b/test/test_manchester.cpp index 0334712fd..bd3574354 100644 --- a/test/test_manchester.cpp +++ b/test/test_manchester.cpp @@ -71,12 +71,26 @@ SUITE(test_manchester) } #if ETL_USING_CPP14 - constexpr etl::array manchester_encoded(etl::span decoded) + constexpr etl::array manchester_encoded_uint8(etl::span decoded) { etl::array encoded{0, 0, 0, 0, 0, 0, 0, 0}; etl::manchester::encode(decoded, encoded); return encoded; } + + constexpr etl::array manchester_encoded_uint16(etl::span decoded) + { + etl::array encoded{0, 0, 0, 0, 0, 0, 0, 0}; + etl::manchester::encode(decoded, encoded); + return encoded; + } + + constexpr etl::array manchester_encoded_uint32(etl::span decoded) + { + etl::array encoded{0, 0, 0, 0, 0, 0, 0, 0}; + etl::manchester::encode(decoded, encoded); + return encoded; + } #endif TEST(encode_span) @@ -105,18 +119,41 @@ SUITE(test_manchester) CHECK_TRUE(encoded0 == encoded1); CHECK_TRUE(encoded0 == encoded2); CHECK_TRUE(encoded0 == encoded3); + } #if ETL_USING_CPP14 - static_assert(manchester_encoded(decoded)[0] == 0xAA, "Compile time encoding on range failed"); - static_assert(manchester_encoded(decoded)[1] == 0xAA, "Compile time encoding on range failed"); - static_assert(manchester_encoded(decoded)[2] == 0x55, "Compile time encoding on range failed"); - static_assert(manchester_encoded(decoded)[3] == 0x55, "Compile time encoding on range failed"); - static_assert(manchester_encoded(decoded)[4] == 0xA9, "Compile time encoding on range failed"); - static_assert(manchester_encoded(decoded)[5] == 0xAA, "Compile time encoding on range failed"); - static_assert(manchester_encoded(decoded)[6] == 0xAA, "Compile time encoding on range failed"); - static_assert(manchester_encoded(decoded)[7] == 0x6A, "Compile time encoding on range failed"); -#endif + TEST(encode_span_constexpr) + { + constexpr etl::array decoded{0x00, 0xFF, 0x01, 0x80}; + + static_assert(manchester_encoded_uint8(decoded)[0] == 0xAA, "Compile time encoding with uint8_t failed"); + static_assert(manchester_encoded_uint8(decoded)[1] == 0xAA, "Compile time encoding with uint8_t failed"); + static_assert(manchester_encoded_uint8(decoded)[2] == 0x55, "Compile time encoding with uint8_t failed"); + static_assert(manchester_encoded_uint8(decoded)[3] == 0x55, "Compile time encoding with uint8_t failed"); + static_assert(manchester_encoded_uint8(decoded)[4] == 0xA9, "Compile time encoding with uint8_t failed"); + static_assert(manchester_encoded_uint8(decoded)[5] == 0xAA, "Compile time encoding with uint8_t failed"); + static_assert(manchester_encoded_uint8(decoded)[6] == 0xAA, "Compile time encoding with uint8_t failed"); + static_assert(manchester_encoded_uint8(decoded)[7] == 0x6A, "Compile time encoding with uint8_t failed"); + + static_assert(manchester_encoded_uint16(decoded)[0] == 0xAA, "Compile time encoding with uint16_t failed"); + static_assert(manchester_encoded_uint16(decoded)[1] == 0xAA, "Compile time encoding with uint16_t failed"); + static_assert(manchester_encoded_uint16(decoded)[2] == 0x55, "Compile time encoding with uint16_t failed"); + static_assert(manchester_encoded_uint16(decoded)[3] == 0x55, "Compile time encoding with uint16_t failed"); + static_assert(manchester_encoded_uint16(decoded)[4] == 0xA9, "Compile time encoding with uint16_t failed"); + static_assert(manchester_encoded_uint16(decoded)[5] == 0xAA, "Compile time encoding with uint16_t failed"); + static_assert(manchester_encoded_uint16(decoded)[6] == 0xAA, "Compile time encoding with uint16_t failed"); + static_assert(manchester_encoded_uint16(decoded)[7] == 0x6A, "Compile time encoding with uint16_t failed"); + + static_assert(manchester_encoded_uint32(decoded)[0] == 0xAA, "Compile time encoding with uint32_t failed"); + static_assert(manchester_encoded_uint32(decoded)[1] == 0xAA, "Compile time encoding with uint32_t failed"); + static_assert(manchester_encoded_uint32(decoded)[2] == 0x55, "Compile time encoding with uint32_t failed"); + static_assert(manchester_encoded_uint32(decoded)[3] == 0x55, "Compile time encoding with uint32_t failed"); + static_assert(manchester_encoded_uint32(decoded)[4] == 0xA9, "Compile time encoding with uint32_t failed"); + static_assert(manchester_encoded_uint32(decoded)[5] == 0xAA, "Compile time encoding with uint32_t failed"); + static_assert(manchester_encoded_uint32(decoded)[6] == 0xAA, "Compile time encoding with uint32_t failed"); + static_assert(manchester_encoded_uint32(decoded)[7] == 0x6A, "Compile time encoding with uint32_t failed"); } +#endif TEST(encode_span_inverted) { @@ -233,12 +270,26 @@ SUITE(test_manchester) } #if ETL_USING_CPP14 - constexpr etl::array manchester_decoded(etl::span encoded) + constexpr etl::array manchester_decoded_uint16(etl::span encoded) { etl::array decoded{0, 0, 0, 0}; etl::manchester::decode(encoded, decoded); return decoded; } + + constexpr etl::array manchester_decoded_uint32(etl::span encoded) + { + etl::array decoded{0, 0, 0, 0}; + etl::manchester::decode(encoded, decoded); + return decoded; + } + + constexpr etl::array manchester_decoded_uint64(etl::span encoded) + { + etl::array decoded{0, 0, 0, 0}; + etl::manchester::decode(encoded, decoded); + return decoded; + } #endif TEST(decode_span) @@ -260,18 +311,33 @@ SUITE(test_manchester) CHECK_EQUAL(0x01, decoded0[2]); CHECK_EQUAL(0x80, decoded0[3]); -#if ETL_USING_CPP14 - static_assert(manchester_decoded(encoded)[0] == 0x00, "Compile time decoding on range failed"); - static_assert(manchester_decoded(encoded)[1] == 0xFF, "Compile time decoding on range failed"); - static_assert(manchester_decoded(encoded)[2] == 0x01, "Compile time decoding on range failed"); - static_assert(manchester_decoded(encoded)[3] == 0x80, "Compile time decoding on range failed"); -#endif - CHECK_TRUE(decoded0 == decoded1); CHECK_TRUE(decoded0 == decoded2); CHECK_TRUE(decoded0 == decoded3); } +#if ETL_USING_CPP14 + TEST(decode_span_constexpr) + { + constexpr etl::array encoded{0xAA, 0xAA, 0x55, 0x55, 0xA9, 0xAA, 0xAA, 0x6A}; + + static_assert(manchester_decoded_uint16(encoded)[0] == 0x00, "Compile time decoding with uint16_t failed"); + static_assert(manchester_decoded_uint16(encoded)[1] == 0xFF, "Compile time decoding with uint16_t failed"); + static_assert(manchester_decoded_uint16(encoded)[2] == 0x01, "Compile time decoding with uint16_t failed"); + static_assert(manchester_decoded_uint16(encoded)[3] == 0x80, "Compile time decoding with uint16_t failed"); + + static_assert(manchester_decoded_uint32(encoded)[0] == 0x00, "Compile time decoding with uint32_t failed"); + static_assert(manchester_decoded_uint32(encoded)[1] == 0xFF, "Compile time decoding with uint32_t failed"); + static_assert(manchester_decoded_uint32(encoded)[2] == 0x01, "Compile time decoding with uint32_t failed"); + static_assert(manchester_decoded_uint32(encoded)[3] == 0x80, "Compile time decoding with uint32_t failed"); + + static_assert(manchester_decoded_uint64(encoded)[0] == 0x00, "Compile time decoding with uint64_t failed"); + static_assert(manchester_decoded_uint64(encoded)[1] == 0xFF, "Compile time decoding with uint64_t failed"); + static_assert(manchester_decoded_uint64(encoded)[2] == 0x01, "Compile time decoding with uint64_t failed"); + static_assert(manchester_decoded_uint64(encoded)[3] == 0x80, "Compile time decoding with uint64_t failed"); + } +#endif + TEST(decode_span_inverted) { etl::array encoded{0x55, 0x55, 0xAA, 0xAA, 0x56, 0x55, 0x55, 0x95};