From b4216913a5938b84baa251402396093e506b1553 Mon Sep 17 00:00:00 2001 From: trigaux Date: Tue, 20 Jun 2023 09:24:28 -0700 Subject: [PATCH] Adding all the headers and getting the repo setup. --- .clang-format | 4 + .github/workflows/cmake.yml | 52 +++++++ .gitignore | 34 ++++- CMakeLists.txt | 55 ++++++++ benchmark/CMakeLists.txt | 21 +++ benchmark/hex_endec.cpp | 73 ++++++++++ benchmark/name.cpp | 130 +++++++++++++++++ include/quicr/hex_endec.h | 263 ++++++++++++++++++++++++++++++++++ include/quicr/name.h | 166 ++++++++++++++++++++++ include/quicr/namespace.h | 59 ++++++++ include/quicr_name | 3 + src/CMakeLists.txt | 19 +++ src/quicr_name.cpp | 271 ++++++++++++++++++++++++++++++++++++ src/quicr_namespace.cpp | 79 +++++++++++ test/CMakeLists.txt | 27 ++++ test/hex_endec.cpp | 99 +++++++++++++ test/main.cpp | 2 + test/name.cpp | 178 +++++++++++++++++++++++ test/namespace.cpp | 50 +++++++ 19 files changed, 1578 insertions(+), 7 deletions(-) create mode 100644 .clang-format create mode 100644 .github/workflows/cmake.yml create mode 100644 CMakeLists.txt create mode 100644 benchmark/CMakeLists.txt create mode 100644 benchmark/hex_endec.cpp create mode 100644 benchmark/name.cpp create mode 100644 include/quicr/hex_endec.h create mode 100644 include/quicr/name.h create mode 100644 include/quicr/namespace.h create mode 100644 include/quicr_name create mode 100644 src/CMakeLists.txt create mode 100644 src/quicr_name.cpp create mode 100644 src/quicr_namespace.cpp create mode 100644 test/CMakeLists.txt create mode 100644 test/hex_endec.cpp create mode 100644 test/main.cpp create mode 100644 test/name.cpp create mode 100644 test/namespace.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..6e53850 --- /dev/null +++ b/.clang-format @@ -0,0 +1,4 @@ +--- +Language: Cpp +BasedOnStyle: Microsoft +... diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml new file mode 100644 index 0000000..5a5b91b --- /dev/null +++ b/.github/workflows/cmake.yml @@ -0,0 +1,52 @@ +name: CMake + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + strategy: + matrix: + os: [ macos-latest, ubuntu-latest ] + + runs-on: ${{ matrix.os }} + continue-on-error: true + + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Install Packages + run: brew install pkg-config + if: matrix.os != 'ubuntu-latest' + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILD_TESTING=ON -DBUILD_BENCHMARKING=ON + + - name: Build + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -j$(getconf _NPROCESSORS_ONLN) --parallel 16 + + - name: Test + working-directory: ${{github.workspace}}/build + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} + + - name: Benchmark + run: ${{github.workspace}}/build/benchmark/qname_benchmark + + diff --git a/.gitignore b/.gitignore index 259148f..3ccc64c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,10 @@ -# Prerequisites -*.d - # Compiled Object files *.slo *.lo *.o *.obj +*.d +*.tmp # Precompiled Headers *.gch @@ -16,10 +15,6 @@ *.dylib *.dll -# Fortran module files -*.mod -*.smod - # Compiled Static libraries *.lai *.la @@ -30,3 +25,28 @@ *.exe *.out *.app + +# Temp Files +.DS_Store +*.swp +*~ +/build + +# log files +*.log + +# IDEA +.idea +cmake-build-debug + +# Editor files/directories +/.vscode +/.vs +/CMakeSettings.json +.projections.json + +# vcpkg +/vcpkg_installed + +# Other +.tmp.dprobes.h diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..67d5f2e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,55 @@ +cmake_minimum_required(VERSION 3.13) + +# Build tests by default only if not a sub-project +if(DEFINED PROJECT_NAME) + option(qname_BUILD_TESTS "Build tests for qname" OFF) + option(qname_BUILD_BENCHMARKS "Build benchmarks for qname" OFF) +else() + option(qname_BUILD_TESTS "Build tests for qname" ON) + option(qname_BUILD_BENCHMARKS "Build benchmarks for qname" ON) +endif() + +project(qname + VERSION 1.0.0.0 + DESCRIPTION "Quicr Name library" + LANGUAGES CXX) + +option(CLANG_TIDY "Perform linting with clang-tidy" OFF) + +if(CLANG_TIDY) + find_program(CLANG_TIDY_EXE NAMES "clang-tidy") + if(CLANG_TIDY_EXE) + set(CMAKE_CXX_CLANG_TIDY ${CLANG_TIDY_EXE}) + else() + message(WARNING "clang-tidy requested, but not found") + endif() +endif() + +add_subdirectory(src) + +include(FetchContent) + +if(BUILD_TESTING AND qname_BUILD_TESTS) + FetchContent_Declare( + doctest + GIT_REPOSITORY https://github.com/doctest/doctest.git + ) + FetchContent_MakeAvailable(doctest) + + include(CTest) + enable_testing() + add_subdirectory(test) +endif() + +if (BUILD_BENCHMARKING AND qname_BUILD_BENCHMARKS) + set(BENCHMARK_USE_BUNDLED_GTEST OFF) + set(BENCHMARK_ENABLE_TESTING OFF) + FetchContent_Declare( + benchmark + GIT_REPOSITORY https://github.com/google/benchmark.git + GIT_TAG main + ) + FetchContent_MakeAvailable(benchmark) + + add_subdirectory(benchmark) +endif() diff --git a/benchmark/CMakeLists.txt b/benchmark/CMakeLists.txt new file mode 100644 index 0000000..cab6d62 --- /dev/null +++ b/benchmark/CMakeLists.txt @@ -0,0 +1,21 @@ +add_executable(qname_benchmark + name.cpp + hex_endec.cpp) + +target_link_libraries(qname_benchmark PRIVATE qname benchmark::benchmark benchmark::benchmark_main) +target_include_directories(qname_benchmark PRIVATE ${PROJECT_SOURCE_DIR}/src) + +target_compile_options(qname_benchmark + PRIVATE + $<$,$,$>: -Wpedantic -Wextra -Wall> + $<$: >) + +set_target_properties(qname_benchmark + PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED YES + CXX_EXTENSIONS ON) +if(MSVC) + target_compile_definitions(qname_benchmark _CRT_SECURE_NO_WARNINGS) +endif() + diff --git a/benchmark/hex_endec.cpp b/benchmark/hex_endec.cpp new file mode 100644 index 0000000..271f667 --- /dev/null +++ b/benchmark/hex_endec.cpp @@ -0,0 +1,73 @@ +#include + +#include + +static void +HexEndec_Encode4x32_to_128(benchmark::State& state) +{ + quicr::HexEndec<128, 32, 32, 32, 32> format; + for (auto _ : state) { + format.Encode(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF); + } +} + +static void +HexEndec_Decode128_to_4x32(benchmark::State& state) +{ + quicr::HexEndec<128, 32, 32, 32, 32> format; + for (auto _ : state) { + format.Decode("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); + } +} + +static void +HexEndec_Encode4x16_to_64(benchmark::State& state) +{ + quicr::HexEndec<64, 16, 16, 16, 16> format; + for (auto _ : state) { + format.Encode(0xFFFFu, 0xFFFFu, 0xFFFFu, 0xFFFFu); + } +} + +static void +HexEndec_Decode64_to_4x16(benchmark::State& state) +{ + quicr::HexEndec<64, 16, 16, 16, 16> format; + for (auto _ : state) { + format.Decode("0xFFFFFFFFFFFFFFFF"); + } +} + +BENCHMARK(HexEndec_Encode4x32_to_128); +BENCHMARK(HexEndec_Decode128_to_4x32); +BENCHMARK(HexEndec_Encode4x16_to_64); +BENCHMARK(HexEndec_Decode64_to_4x16); + +static void +HexEndec_RealEncode(benchmark::State& state) +{ + quicr::HexEndec<128, 24, 8, 24, 8, 16, 48> format; + auto time = std::time(0); + const uint32_t orgId = 0x00A11CEE; + const uint8_t appId = 0x00; + const uint32_t confId = 0x00F00001; + const uint8_t mediaType = 0x00 | 0x1; + const uint16_t clientId = 0xFFFF; + const uint64_t uniqueId = time; + for (auto _ : state) { + format.Encode(orgId, appId, confId, mediaType, clientId, uniqueId); + } +} + +static void +HexEndec_RealDecode(benchmark::State& state) +{ + quicr::HexEndec<128, 24, 8, 24, 8, 16, 48> format; + quicr::Name qname = 0xA11CEE00F00001000000000000000000_name; + for (auto _ : state) { + format.Decode(qname); + } +} + +BENCHMARK(HexEndec_RealEncode); +BENCHMARK(HexEndec_RealDecode); diff --git a/benchmark/name.cpp b/benchmark/name.cpp new file mode 100644 index 0000000..13852e1 --- /dev/null +++ b/benchmark/name.cpp @@ -0,0 +1,130 @@ +#include + +#include +#include + +static void +Name_ConstructFromHexString(benchmark::State& state) +{ + std::string str = "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"; + for (auto _ : state) { + [[maybe_unused]] quicr::Name __(str); + } +} + +static void +Name_ConstructFromHexStringView(benchmark::State& state) +{ + std::string_view str = "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"; + for (auto _ : state) { + [[maybe_unused]] quicr::Name __(str); + } +} + +static void +Name_ConstructFromVector(benchmark::State& state) +{ + std::vector data = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF + }; + for (auto _ : state) { + [[maybe_unused]] quicr::Name __(data); + } +} + +static void +Name_ConstructFromBytePointer(benchmark::State& state) +{ + std::vector vec_data = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF }; + + uint8_t* data = vec_data.data(); + size_t length = vec_data.size(); + + for (auto _ : state) { + [[maybe_unused]] quicr::Name __(data, length); + } +} + +static void +Name_CopyConstruct(benchmark::State& state) +{ + quicr::Name name = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF_name; + for (auto _ : state) { + [[maybe_unused]] quicr::Name __(name); + } +} + +static void +Name_LeftShift(benchmark::State& state) +{ + quicr::Name name = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF_name; + for (auto _ : state) { + name << 64; + } +} + +static void +Name_RightShift(benchmark::State& state) +{ + quicr::Name name = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF_name; + for (auto _ : state) { + name >> 64; + } +} + +static void +Name_Add(benchmark::State& state) +{ + quicr::Name name = 0x0_name; + for (auto _ : state) { + ++name; + } +} + +static void +Name_Sub(benchmark::State& state) +{ + quicr::Name name = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF_name; + for (auto _ : state) { + --name; + } +} + +static void +Name_ToHex(benchmark::State& state) +{ + quicr::Name name = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF_name; + for (auto _ : state) { + name.to_hex(); + } +} + +BENCHMARK(Name_ConstructFromHexString); +BENCHMARK(Name_ConstructFromHexStringView); +BENCHMARK(Name_ConstructFromVector); +BENCHMARK(Name_ConstructFromBytePointer); +BENCHMARK(Name_CopyConstruct); +BENCHMARK(Name_LeftShift); +BENCHMARK(Name_RightShift); +BENCHMARK(Name_Add); +BENCHMARK(Name_Sub); +BENCHMARK(Name_ToHex); + +constexpr quicr::Name object_id_mask = 0x00000000000000000000000000001111_name; +constexpr quicr::Name group_id_mask = 0x00000000000000000000111111110000_name; +static void +Name_RealArithmetic(benchmark::State& state) +{ + quicr::Name name = 0xA11CEE00F00001000000000000000000_name; + for (auto _ : state) { + name = (name & ~object_id_mask) | (++name & object_id_mask); + + auto group_id_bits = (++(name >> 16) << 16) & group_id_mask; + name = ((name & ~group_id_mask) | group_id_bits) & ~object_id_mask; + } +} + +BENCHMARK(Name_RealArithmetic); diff --git a/include/quicr/hex_endec.h b/include/quicr/hex_endec.h new file mode 100644 index 0000000..3b023c8 --- /dev/null +++ b/include/quicr/hex_endec.h @@ -0,0 +1,263 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace quicr +{ +/** + * @brief Encodes/Decodes a hex string from/into a list of unsigned integers + * values. + * + * The hex string created by/passed to this class is of the format: + * 0xXX...XYY...YZZ...Z.... + * |____||____||____| + * Dist0 Dist1 Dist2 ... + * |_____________________| + * Size + * Where Dist is the distribution of bits for each value provided. For Example: + * HexEndec<64, 32, 24, 8> + * Describes a 64 bit hex value, distributed into 3 sections 32bit, 24bit, and + * 8bit respectively. + * + * @tparam Size The maximum size in bits of the Hex string + * @tparam ...Dist The distribution of bits for each value passed. + */ +template class HexEndec +{ + template struct is_valid_uint : std::bool_constant<(std::is_unsigned_v && ...)> + { + }; + + public: + HexEndec() + { + static_assert((Size & (Size - 1)) == 0, "Size must be a power of 2"); + static_assert(Size == (Dist + ...), "Total bits must be equal to Size"); + } + + /** + * @brief Encodes the last Dist bits of values in order according to + * distribution of Dist and builds a hex string that is the size in bits of + * Size. + * + * @tparam ...UInt_ts The unsigned integer types to be passed. + * @param ...values The unsigned values to be encoded into the hex string. + * + * @returns Hex string containing the provided values distributed according to + * Dist in order. + */ + template static inline std::string Encode(UInt_ts... values) + { + static_assert((Size & (Size - 1)) == 0, "Size must be a power of 2"); + static_assert(Size == (Dist + ...), "Total bits cannot exceed specified size"); + static_assert(sizeof...(Dist) == sizeof...(UInt_ts), "Number of values should match distribution of bits"); + static_assert(is_valid_uint::value, "Arguments must all be unsigned integers"); + + std::array distribution{Dist...}; + return Encode(std::span(distribution), std::forward(values)...); + } + + template static inline std::string Encode(std::span distribution, UInt_ts... values) + { + static_assert((Size & (Size - 1)) == 0, "Size must be a power of 2"); + static_assert(is_valid_uint::value, "Arguments must all be unsigned integers"); + assert(distribution.size() == sizeof...(UInt_ts) && "Number of values should match distribution of bits"); + + std::array vals{values...}; + return Encode(distribution, std::span(vals)); + } + + template + static inline std::string Encode(std::array distribution, UInt_ts... values) + { + return Encode(std::span(distribution), std::forward(values)...); + } + + template + static inline typename std::enable_if::type Encode(std::span distribution, + std::span values) + { + static_assert((Size & (Size - 1)) == 0, "Size must be a power of 2"); + assert(distribution.size() == values.size() && "Number of values should match distribution of bits"); + + uint64_t bits = 0; + for (size_t i = 0; i < values.size(); ++i) + { + const uint64_t &value = values[i]; + uint8_t dist = distribution[i]; + bits <<= dist; + bits |= (value & ~(~0x0ull << dist)); + }; + + return std::string("0x") + uint_to_hex(bits); + } + + template + static inline typename std::enable_if::type Encode(std::vector distribution, + std::vector values) + { + return Encode(std::span(distribution), std::span(values)); + } + + template + static inline typename std::enable_if::type Encode(std::array distribution, + std::array values) + { + return Encode(std::span(distribution), std::span(values)); + } + + template + static inline typename std::enable_if::type Encode(std::span distribution, + std::span values) + { + static_assert((Size & (Size - 1)) == 0, "Size must be a power of 2"); + assert(distribution.size() == values.size() && "Number of values should match distribution of bits"); + + std::bitset bits; + for (size_t i = 0; i < values.size(); ++i) + { + const uint64_t &value = values[i]; + uint8_t dist = distribution[i]; + bits <<= dist; + bits |= std::bitset(dist >= sizeof(uint64_t) * 8 ? value : (value & ~(~0x0ull << dist))); + }; + + static const std::bitset bits_mask = (std::bitset().set() >> 64).flip(); + constexpr size_t sizeof_uint64_bits = sizeof(uint64_t) * 8; + + std::string out_hex = "0x"; + for (size_t i = 0; i < Size / sizeof_uint64_bits; ++i) + { + out_hex += uint_to_hex(((bits & bits_mask) >> (Size - sizeof_uint64_bits)).to_ullong()); + bits <<= sizeof_uint64_bits; + } + + return out_hex; + } + + template + static inline typename std::enable_if::type Encode(std::vector distribution, + std::vector values) + { + return Encode(std::span(distribution), std::span(values)); + } + + template + static inline typename std::enable_if::type Encode(std::array distribution, + std::array values) + { + return Encode(std::span(distribution), std::span(values)); + } + + /** + * @brief Decodes a hex string that has size in bits of Size into a list of + * values sized according to Dist in order. + * + * @tparam Uint_t The unsigned integer type to return. + * @param hex The hex string to decode. Must have a length in bytes + * corresponding to the size in bits of Size. + * + * @returns Structured binding of values decoded from hex string corresponding + * in order to the size of Dist. + */ + template ::value, Uint_t>> + static inline std::array Decode(std::string_view hex) + { + static_assert((Size & (Size - 1)) == 0, "Size must be a power of 2"); + static_assert(Size >= (Dist + ...), "Total bits cannot exceed specified size"); + + std::array distribution{Dist...}; + auto result = Decode(distribution, hex); + std::array out; + std::copy_n(std::make_move_iterator(result.begin()), sizeof...(Dist), out.begin()); + + return out; + } + + template ::value, Uint_t>> + static inline std::array Decode(const quicr::Name &name) + { + static_assert((Size & (Size - 1)) == 0, "Size must be a power of 2"); + static_assert(Size >= (Dist + ...), "Total bits cannot exceed specified size"); + + std::array distribution{Dist...}; + auto result = Decode(distribution, std::string_view(name.to_hex())); + std::array out; + std::copy_n(std::make_move_iterator(result.begin()), sizeof...(Dist), out.begin()); + + return out; + } + + template ::value, Uint_t>> + static inline typename std::enable_if>::type Decode(std::span distribution, + std::string_view hex) + { + static_assert((Size & (Size - 1)) == 0, "Size must be a power of 2"); + + const auto dist_size = distribution.size(); + std::vector result(dist_size); + uint64_t bits = hex_to_uint(hex); + for (int i = dist_size - 1; i >= 0; --i) + { + const auto dist = distribution[i]; + result[i] = bits & ~(~0x0ull << dist); + bits >>= dist; + } + + return result; + } + + template ::value, Uint_t>> + static inline typename std::enable_if>::type Decode(std::span distribution, + std::string_view hex) + { + static_assert((Size & (Size - 1)) == 0, "Size must be a power of 2"); + + if (hex.starts_with("0x")) + hex.remove_prefix(2); + + constexpr uint8_t hex_length = Size / 4; + if (hex.length() != hex_length) + throw std::runtime_error("Hex string value must be " + std::to_string(hex_length) + " characters (" + + std::to_string(Size / 8) + " bytes). Got: " + std::to_string(hex.length())); + + std::bitset bits; + constexpr uint8_t section_length = sizeof(uint64_t) * 2; + constexpr size_t sizeof_uint64_bits = section_length * 4; + constexpr size_t num_sections = Size / sizeof_uint64_bits; + for (size_t i = 0, j = 0; i < (section_length * num_sections); i += section_length) + { + bits |= std::bitset(hex_to_uint(hex.substr(i, section_length))) + << (sizeof_uint64_bits * (num_sections - ++j)); + } + + const auto dist_size = distribution.size(); + std::vector result(dist_size); + for (size_t i = 0; i < dist_size; ++i) + { + const auto dist = distribution[i]; + result[i] = (bits >> (Size - dist)).to_ullong(); + bits <<= dist; + } + + return result; + } + + template + static inline std::vector Decode(std::span distribution, const quicr::Name &name) + { + return Decode(distribution, std::string_view(name.to_hex())); + } +}; +} // namespace quicr diff --git a/include/quicr/name.h b/include/quicr/name.h new file mode 100644 index 0000000..6e256a6 --- /dev/null +++ b/include/quicr/name.h @@ -0,0 +1,166 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace quicr +{ + +constexpr uint64_t hexchar_to_uint(char x) +{ + uint64_t y = 0; + if ('0' <= x && x <= '9') + y += x - '0'; + else if ('A' <= x && x <= 'F') + y += x - 'A' + 10; + else if ('a' <= x && x <= 'f') + y += x - 'a' + 10; + + return y; +} + +template , T>::type> +constexpr char uint_to_hexchar(T b) +{ + char x = ' '; + if (b > 9) + x = b + 'A' - 10; + else + x = b + '0'; + + return x; +} + +template , T>::type> +constexpr T hex_to_uint(std::string_view x) +{ + if (x.starts_with("0x")) + x.remove_prefix(2); + + T y = 0; + for (size_t i = 0; i < x.length(); ++i) + { + y *= 16ull; + y += hexchar_to_uint(x[i]); + } + + return y; +} + +template , T>::type> std::string uint_to_hex(T y) +{ + char x[sizeof(T) * 2 + 1] = ""; + for (int i = sizeof(T) * 2 - 1; i >= 0; --i) + { + T b = y & 0x0F; + x[i] = uint_to_hexchar(b); + y -= b; + y /= 16; + } + x[sizeof(T) * 2] = '\0'; + + return x; +} + +/** + * Name specific exception, thrown only on creation of name where string + * byte count is too long. + */ +struct NameException : public std::runtime_error +{ + using std::runtime_error::runtime_error; +}; + +/** + * @brief Name class used for passing data in bits. + */ +class Name +{ + public: + Name() = default; + constexpr Name(const Name &other) = default; + Name(Name &&other) = default; + Name(uint8_t *data, size_t length); + Name(const uint8_t *data, size_t length); + Name(const std::vector &data); + + constexpr Name(std::string_view hex_value) + { + if (hex_value.starts_with("0x")) + hex_value.remove_prefix(2); + + if (hex_value.length() > sizeof(Name) * 2) + throw NameException("Hex string cannot be longer than " + std::to_string(sizeof(Name) * 2) + " bytes"); + + if (hex_value.length() > sizeof(Name)) + { + _hi = hex_to_uint(hex_value.substr(0, hex_value.length() - sizeof(Name))); + _low = hex_to_uint(hex_value.substr(hex_value.length() - sizeof(Name), sizeof(Name))); + } + else + { + _hi = 0; + _low = hex_to_uint(hex_value.substr(0, hex_value.length())); + } + } + + ~Name() = default; + + std::string to_hex() const; + std::uint8_t operator[](std::size_t offset) const; + + Name operator>>(uint16_t value) const; + Name operator>>=(uint16_t value); + Name operator<<(uint16_t value) const; + Name operator<<=(uint16_t value); + Name operator+(uint64_t value) const; + void operator+=(uint64_t value); + Name operator++(); + Name operator++(int); + Name operator--(); + Name operator--(int); + Name operator-(uint64_t value) const; + void operator-=(uint64_t value); + Name operator&(uint64_t value) const; + void operator&=(uint64_t value); + Name operator|(uint64_t value) const; + void operator|=(uint64_t value); + Name operator&(const Name &other) const; + void operator&=(const Name &other); + Name operator|(const Name &other) const; + void operator|=(const Name &other); + Name operator^(const Name &other) const; + void operator^=(const Name &other); + + constexpr Name operator~() const + { + Name name(*this); + name._hi = ~_hi; + name._low = ~_low; + return name; + } + + constexpr Name &operator=(const Name &other) = default; + constexpr Name &operator=(Name &&other) = default; + + friend bool operator<(const Name &a, const Name &b); + friend bool operator>(const Name &a, const Name &b); + friend bool operator==(const Name &a, const Name &b); + friend bool operator!=(const Name &a, const Name &b); + + friend std::ostream &operator<<(std::ostream &os, const Name &name); + + private: + uint64_t _hi; + uint64_t _low; +}; +} // namespace quicr + +constexpr quicr::Name operator""_name(const char *x) +{ + return {std::string_view(x)}; +} diff --git a/include/quicr/namespace.h b/include/quicr/namespace.h new file mode 100644 index 0000000..5a69554 --- /dev/null +++ b/include/quicr/namespace.h @@ -0,0 +1,59 @@ +#pragma once + +#include + +#include + +namespace quicr +{ + +/** + * @brief A prefix for a quicr::Name + */ +class Namespace +{ + public: + Namespace() = default; + constexpr Namespace(const Namespace &ns) = default; + Namespace(Namespace &&ns) = default; + Namespace(const Name &name, uint8_t sig_bits); + Namespace(Name &&name, uint8_t sig_bits); + Namespace(std::string_view str); + + Namespace &operator=(const Namespace &other) = default; + Namespace &operator=(Namespace &&other) = default; + + bool contains(const Name &name) const; + bool contains(const Namespace &prefix) const + { + return contains(prefix._name); + } + + constexpr Name name() const + { + return _name; + } + constexpr uint8_t length() const + { + return _sig_bits; + } + std::string to_hex() const; + + friend bool operator==(const Namespace &a, const Namespace &b); + friend bool operator!=(const Namespace &a, const Namespace &b); + + friend bool operator>(const Namespace &a, const Namespace &b); + friend bool operator>(const Namespace &a, const Name &b); + friend bool operator>(const Name &a, const Namespace &b); + + friend bool operator<(const Namespace &a, const Namespace &b); + friend bool operator<(const Namespace &a, const Name &b); + friend bool operator<(const Name &a, const Namespace &b); + + friend std::ostream &operator<<(std::ostream &os, const Namespace &ns); + + private: + Name _name; + uint8_t _sig_bits; +}; +} // namespace quicr diff --git a/include/quicr_name b/include/quicr_name new file mode 100644 index 0000000..ade27c4 --- /dev/null +++ b/include/quicr_name @@ -0,0 +1,3 @@ +#include +#include +#include diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..1d57ece --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,19 @@ +add_library(qname + quicr_name.cpp + quicr_namespace.cpp) + +target_compile_options(qname + PRIVATE + $<$,$,$>: -Wpedantic -Wextra -Wall> + $<$: >) +set_target_properties(qname + PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED YES + CXX_EXTENSIONS OFF) +if(MSVC) + target_compile_definitions(qname _CRT_SECURE_NO_WARNINGS) +endif() + +target_include_directories(qname PUBLIC ${PROJECT_SOURCE_DIR}/include) + diff --git a/src/quicr_name.cpp b/src/quicr_name.cpp new file mode 100644 index 0000000..54277f6 --- /dev/null +++ b/src/quicr_name.cpp @@ -0,0 +1,271 @@ +#include + +#include +#include +#include +#include + +namespace quicr +{ +Name::Name(uint8_t *data, size_t length) +{ + if (length > sizeof(Name) * 2) + throw NameException("Byte array length cannot be longer than length of Name: " + + std::to_string(sizeof(Name) * 2)); + + constexpr size_t size_of = sizeof(Name) / 2; + std::memcpy(&_low, data, size_of); + std::memcpy(&_hi, data + size_of, size_of); +} + +Name::Name(const uint8_t *data, size_t length) +{ + if (length > sizeof(Name) * 2) + throw NameException("Byte array length cannot be longer than length of Name: " + + std::to_string(sizeof(Name) * 2)); + + constexpr size_t size_of = sizeof(Name) / 2; + std::memcpy(&_low, data, size_of); + std::memcpy(&_hi, data + size_of, size_of); +} + +Name::Name(const std::vector &data) +{ + if (data.size() > sizeof(Name)) + throw NameException("Byte array length cannot be longer than length of Name: " + std::to_string(sizeof(Name))); + + constexpr size_t size_of = sizeof(Name) / 2; + std::memcpy(&_low, data.data(), size_of); + std::memcpy(&_hi, data.data() + size_of, size_of); +} + +std::string Name::to_hex() const +{ + std::string hex = "0x"; + hex += uint_to_hex(_hi); + hex += uint_to_hex(_low); + + return hex; +} + +std::uint8_t Name::operator[](std::size_t index) const +{ + if (index >= sizeof(Name)) + throw std::out_of_range("Cannot access index outside of max size of quicr::Name"); + + if (index < sizeof(uint64_t)) + return (_low >> (index * 8)) & 0xff; + return (_hi >> ((index - sizeof(uint64_t)) * 8)) & 0xff; +} + +Name Name::operator>>(uint16_t value) const +{ + Name name(*this); + name >>= value; + return name; +} + +static constexpr size_t uint64_t_bit_size = sizeof(Name) * 4; + +Name Name::operator>>=(uint16_t value) +{ + if (value == 0) + return *this; + + if (value < uint64_t_bit_size) + { + _low = _low >> value; + _low |= _hi << (uint64_t_bit_size - value); + _hi = _hi >> value; + } + else + { + _low = _hi >> (value - uint64_t_bit_size); + _hi = 0; + } + + return *this; +} + +Name Name::operator<<(uint16_t value) const +{ + Name name(*this); + name <<= value; + return name; +} + +Name Name::operator<<=(uint16_t value) +{ + if (value == 0) + return *this; + + if (value < uint64_t_bit_size) + { + _hi = _hi << value; + _hi |= _low >> (uint64_t_bit_size - value); + _low = _low << value; + } + else + { + _hi = _low << (value - uint64_t_bit_size); + _low = 0; + } + + return *this; +} + +Name Name::operator+(uint64_t value) const +{ + Name name(*this); + name += value; + return name; +} + +void Name::operator+=(uint64_t value) +{ + if (_low + value < _low) + { + ++_hi; + } + _low += value; +} + +Name Name::operator-(uint64_t value) const +{ + Name name(*this); + name -= value; + return name; +} + +void Name::operator-=(uint64_t value) +{ + if (_low - value > _low) + { + --_hi; + } + _low -= value; +} + +Name Name::operator++() +{ + *this += 1; + return *this; +} + +Name Name::operator++(int) +{ + Name name(*this); + ++(*this); + return name; +} + +Name Name::operator--() +{ + *this -= 1; + return *this; +} + +Name Name::operator--(int) +{ + Name name(*this); + --(*this); + return name; +} + +Name Name::operator&(uint64_t value) const +{ + Name name(*this); + name &= value; + return name; +} + +void Name::operator&=(uint64_t value) +{ + _low &= value; +} + +Name Name::operator|(uint64_t value) const +{ + Name name(*this); + name |= value; + return name; +} + +void Name::operator|=(uint64_t value) +{ + _low |= value; +} + +Name Name::operator&(const Name &other) const +{ + Name name = *this; + name &= other; + return name; +} + +void Name::operator&=(const Name &other) +{ + _hi &= other._hi; + _low &= other._low; +} + +Name Name::operator|(const Name &other) const +{ + Name name = *this; + name |= other; + return name; +} + +void Name::operator|=(const Name &other) +{ + _hi |= other._hi; + _low |= other._low; +} + +Name Name::operator^(const Name &other) const +{ + Name name = *this; + name ^= other; + return name; +} + +void Name::operator^=(const Name &other) +{ + _hi ^= other._hi; + _low ^= other._low; +} + +bool operator==(const Name &a, const Name &b) +{ + return a._hi == b._hi && a._low == b._low; +} + +bool operator!=(const Name &a, const Name &b) +{ + return !(a == b); +} + +bool operator>(const Name &a, const Name &b) +{ + if (a._hi > b._hi) + return true; + if (b._hi > a._hi) + return false; + return a._low > b._low; +} + +bool operator<(const Name &a, const Name &b) +{ + if (a._hi < b._hi) + return true; + if (b._hi < a._hi) + return false; + return a._low < b._low; +} + +std::ostream &operator<<(std::ostream &os, const Name &name) +{ + os << name.to_hex(); + return os; +} +} // namespace quicr diff --git a/src/quicr_namespace.cpp b/src/quicr_namespace.cpp new file mode 100644 index 0000000..3dc919c --- /dev/null +++ b/src/quicr_namespace.cpp @@ -0,0 +1,79 @@ +#include + +namespace quicr +{ + +Namespace::Namespace(const Name &name, uint8_t sig_bits) + : _name{name & ~(~0x0_name >> sig_bits)}, _sig_bits{sig_bits} +{ +} + +Namespace::Namespace(Name &&name, uint8_t sig_bits) + : _name{name & ~(~0x0_name >> sig_bits)}, _sig_bits{sig_bits} +{ +} + +Namespace::Namespace(std::string_view str) +{ + auto delim_pos = str.find_first_of('/'); + _name = str.substr(0, delim_pos); + str.remove_prefix(delim_pos + 1); + _sig_bits = std::atoi(str.data()); +} + +bool Namespace::contains(const Name &name) const +{ + return (name & ~(~0x0_name >> _sig_bits)) == _name; +} + +std::string Namespace::to_hex() const +{ + return _name.to_hex() + "/" + std::to_string(_sig_bits); +} + +bool operator==(const Namespace &a, const Namespace &b) +{ + return a._name == b._name && a._sig_bits == b._sig_bits; +} + +bool operator!=(const Namespace &a, const Namespace &b) +{ + return !(a == b); +} + +bool operator>(const Namespace &a, const Namespace &b) +{ + return a._name > b._name; +} + +bool operator>(const Namespace &a, const Name &b) +{ + return a._name > b; +} + +bool operator>(const Name &a, const Namespace &b) +{ + return a > b._name; +} + +bool operator<(const Namespace &a, const Namespace &b) +{ + return a._name < b._name; +} + +bool operator<(const Namespace &a, const Name &b) +{ + return a._name < b; +} + +bool operator<(const Name &a, const Namespace &b) +{ + return a < b._name; +} + +std::ostream &operator<<(std::ostream &os, const Namespace &ns) +{ + os << ns.to_hex(); + return os; +} +} // namespace quicr diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..dfdef4f --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,27 @@ +# Test Binary + +add_executable(qname_test + main.cpp + name.cpp + namespace.cpp + hex_endec.cpp) +target_include_directories(qname_test PRIVATE ${PROJECT_SOURCE_DIR}/src ${DOCTEST_INCLUDE_DIR}) + +target_link_libraries(qname_test PRIVATE qname doctest::doctest) + +target_compile_options(qname_test + PRIVATE + $<$,$,$>: -Wpedantic -Wextra -Wall> + $<$: >) + +set_target_properties(qname_test + PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED YES + CXX_EXTENSIONS ON) +if(MSVC) + target_compile_definitions(qname_test _CRT_SECURE_NO_WARNINGS) +endif() + +include(${doctest_SOURCE_DIR}/scripts/cmake/doctest.cmake) +doctest_discover_tests(qname_test) \ No newline at end of file diff --git a/test/hex_endec.cpp b/test/hex_endec.cpp new file mode 100644 index 0000000..9814813 --- /dev/null +++ b/test/hex_endec.cpp @@ -0,0 +1,99 @@ +#include + +#include + +TEST_CASE("quicr::HexEndec 256bit Encode/Decode Test") +{ + const std::string hex_value = "0x1111111111111111222222222222222233333333333333334444444444444400"; + const uint64_t first_part = 0x1111111111111111ull; + const uint64_t second_part = 0x2222222222222222ull; + const uint64_t third_part = 0x3333333333333333ull; + const uint64_t fourth_part = 0x44444444444444ull; + const uint8_t last_part = 0x00ull; + + quicr::HexEndec<256, 64, 64, 64, 56, 8> formatter_256bit; + const std::string mask = formatter_256bit.Encode(first_part, second_part, third_part, fourth_part, last_part); + CHECK_EQ(mask, hex_value); + + const auto [one, two, three, four, last] = formatter_256bit.Decode(std::string_view(hex_value)); + CHECK_EQ(one, first_part); + CHECK_EQ(two, second_part); + CHECK_EQ(three, third_part); + CHECK_EQ(four, fourth_part); + CHECK_EQ(last, last_part); +} + +TEST_CASE("quicr::HexEndec 128bit Encode/Decode Test") +{ + const std::string hex_value = "0x11111111111111112222222222222200"; + const uint64_t first_part = 0x1111111111111111ull; + const uint64_t second_part = 0x22222222222222ull; + const uint8_t third_part = 0x00ull; + + quicr::HexEndec<128, 64, 56, 8> formatter_128bit; + const std::string mask = formatter_128bit.Encode(first_part, second_part, third_part); + CHECK_EQ(mask, hex_value); + + const auto [one, two, three] = formatter_128bit.Decode(std::string_view(hex_value)); + CHECK_EQ(one, first_part); + CHECK_EQ(two, second_part); + CHECK_EQ(three, third_part); +} + +TEST_CASE("quicr::HexEndec 128bit Encode/Decode Container Test") +{ + const std::string_view hex_value = "0x11111111111111112222222222222200"; + const uint64_t first_part = 0x1111111111111111ull; + const uint64_t second_part = 0x22222222222222ull; + const uint8_t third_part = 0x00ull; + + std::array dist = {64, 56, 8}; + std::array vals = {first_part, second_part, third_part}; + const std::string mask = quicr::HexEndec<128>::Encode(dist, vals); + CHECK_EQ(mask, hex_value); + + std::vector out = quicr::HexEndec<128>::Decode(dist, hex_value); + CHECK_EQ(out[0], first_part); + CHECK_EQ(out[1], second_part); + CHECK_EQ(out[2], third_part); +} + +TEST_CASE("quicr::HexEndec 64bit Encode/Decode Test") +{ + const std::string_view hex_value = "0x1111111122222200"; + const uint64_t first_part = 0x11111111ull; + const uint64_t second_part = 0x222222ull; + const uint8_t third_part = 0x00ull; + + quicr::HexEndec<64, 32, 24, 8> formatter_64bit; + const std::string mask = formatter_64bit.Encode(first_part, second_part, third_part); + CHECK_EQ(mask, hex_value); + + const auto [one, two, three] = formatter_64bit.Decode(hex_value); + CHECK_EQ(one, first_part); + CHECK_EQ(two, second_part); + CHECK_EQ(three, third_part); +} + +TEST_CASE("quicr::HexEndec Decode Throw Test") +{ + const std::string_view valid_hex_value = "0x11111111111111112222222222222200"; + const std::string_view invalid_hex_value = "0x111111111111111122222222222222"; + const std::string_view another_invalid_hex_value = "0x1111111111111111222222222222220000"; + + quicr::HexEndec<128, 64, 56, 8> formatter_128bit; + CHECK_NOTHROW(formatter_128bit.Decode(valid_hex_value)); + CHECK_THROWS(formatter_128bit.Decode(invalid_hex_value)); + CHECK_THROWS(formatter_128bit.Decode(another_invalid_hex_value)); +} + +TEST_CASE("quicr::HexEndec Decode quicr::Name") +{ + const quicr::Name name = 0x11111111111111112222222222222233_name; + std::array results; + + quicr::HexEndec<128, 64, 56, 8> formatter_128bit; + CHECK_NOTHROW(results = formatter_128bit.Decode(name)); + CHECK_EQ(results, std::array{0x1111111111111111, 0x22222222222222, 0x33}); + CHECK_EQ(results[0], 0x1111111111111111); +} diff --git a/test/main.cpp b/test/main.cpp new file mode 100644 index 0000000..0a3f254 --- /dev/null +++ b/test/main.cpp @@ -0,0 +1,2 @@ +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include diff --git a/test/name.cpp b/test/name.cpp new file mode 100644 index 0000000..fc1fe4c --- /dev/null +++ b/test/name.cpp @@ -0,0 +1,178 @@ +#include + +#include + +#include + +TEST_CASE("quicr::Name Constructor Tests") +{ + constexpr quicr::Name val42(0x42_name); + constexpr quicr::Name hex42(0x42_name); + CHECK_EQ(val42, hex42); + + CHECK_LT(0x123_name, 0x124_name); + CHECK_GT(0x123_name, 0x122_name); + CHECK_NE(0x123_name, 0x122_name); + + CHECK_GT(0x20000000000000001_name, 0x10000000000000002_name); + CHECK_LT(0x10000000000000002_name, 0x20000000000000001_name); + + CHECK_NOTHROW(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF_name); + CHECK_THROWS(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0_name); + + CHECK(std::is_trivial_v); + CHECK(std::is_trivially_constructible_v); + CHECK(std::is_trivially_default_constructible_v); + CHECK(std::is_trivially_destructible_v); + CHECK(std::is_trivially_copyable_v); + CHECK(std::is_trivially_copy_assignable_v); + CHECK(std::is_trivially_move_constructible_v); + CHECK(std::is_trivially_move_assignable_v); +} + +TEST_CASE("quicr::Name To Hex Tests") +{ + { + std::string_view original_hex = "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"; + quicr::Name name = original_hex; + + CHECK_EQ(name.to_hex(), original_hex); + } + { + std::string_view original_hex = "0xFFFFFFFFFFFFFFFF0000000000000000"; + quicr::Name name = original_hex; + + CHECK_EQ(name.to_hex(), original_hex); + } + { + std::string_view long_hex = "0x0000000000000000FFFFFFFFFFFFFFFF"; + quicr::Name long_name = long_hex; + + std::string_view short_hex = "0xFFFFFFFFFFFFFFFF"; + quicr::Name not_short_name = short_hex; + CHECK_EQ(long_name.to_hex(), long_hex); + CHECK_NE(not_short_name.to_hex(), short_hex); + CHECK_EQ(long_name.to_hex(), not_short_name.to_hex()); + CHECK_EQ(long_name, not_short_name); + } +} + +TEST_CASE("quicr::Name Bit Shifting Tests") +{ + CHECK_EQ((0x1234_name >> 4), 0x123_name); + CHECK_EQ((0x1234_name << 4), 0x12340_name); + + { + const quicr::Name unshifted_32bit = 0x123456789abcdeff00000000_name; + const quicr::Name shifted_32bit = 0x123456789abcdeff_name; + CHECK_EQ((unshifted_32bit >> 32), shifted_32bit); + CHECK_EQ((shifted_32bit << 32), unshifted_32bit); + } + + { + quicr::Name unshifted_64bit = 0x123456789abcdeff123456789abcdeff_name; + quicr::Name shifted_64bit = 0x123456789abcdeff_name; + quicr::Name shifted_72bit = 0x123456789abcde_name; + CHECK_EQ((unshifted_64bit >> 64), shifted_64bit); + CHECK_EQ((unshifted_64bit >> 72), shifted_72bit); + CHECK_EQ((shifted_64bit >> 8), shifted_72bit); + } + + { + quicr::Name unshifted_64bit = 0x123456789abcdeff_name; + quicr::Name shifted_64bit = 0x123456789abcdeff0000000000000000_name; + quicr::Name shifted_72bit = 0x3456789abcdeff000000000000000000_name; + CHECK_EQ((unshifted_64bit << 64), shifted_64bit); + CHECK_EQ((unshifted_64bit << 72), shifted_72bit); + CHECK_EQ((shifted_64bit << 8), shifted_72bit); + } + + { + const quicr::Name unshifted_bits = 0x00000000000000000000000000000001_name; + quicr::Name bits = unshifted_bits; + for (int i = 0; i < 64; ++i) + bits <<= 1; + + CHECK_EQ(bits, 0x00000000000000010000000000000000_name); + + for (int i = 0; i < 64; ++i) + bits >>= 1; + + CHECK_EQ(bits, unshifted_bits); + } +} + +TEST_CASE("quicr::Name Arithmetic Tests") +{ + quicr::Name val42 = 0x42_name; + quicr::Name val41 = 0x41_name; + quicr::Name val43 = 0x43_name; + CHECK_EQ(val42 + 1, val43); + CHECK_EQ(val42 - 1, val41); + + CHECK_EQ(0x00000000000000010000000000000000_name + 1, 0x00000000000000010000000000000001_name); + CHECK_EQ(0x0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF_name + 1, 0x10000000000000000000000000000000_name); + CHECK_EQ(0x0000000000000000FFFFFFFFFFFFFFFF_name + 0xFFFFFFFF, 0x000000000000000100000000FFFFFFFE_name); + + CHECK_EQ(0x00000000000000010000000000000000_name - 1, 0x0000000000000000FFFFFFFFFFFFFFFF_name); + CHECK_EQ(0x0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF_name - 1, 0x0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE_name); + CHECK_EQ(0x0000000000000000FFFFFFFFFFFFFFFF_name - 0xFFFFFFFFFFFFFFFF, 0x00000000000000000000000000000000_name); + CHECK_EQ(0x00000000000000010000000000000000_name - 2, 0x0000000000000000FFFFFFFFFFFFFFFE_name); + + quicr::Name val42_copy(val42); + CHECK_EQ(val42_copy, val42); + CHECK_NE(val42_copy++, val43); + CHECK_EQ(val42_copy, val43); + CHECK_NE(val42_copy--, val42); + CHECK_EQ(val42_copy, val42); + CHECK_EQ(++val42_copy, val43); + CHECK_EQ(--val42_copy, val42); +} + +TEST_CASE("quicr::Name Bitwise Not Tests") +{ + constexpr quicr::Name zeros = 0x0_name; + constexpr quicr::Name ones = ~zeros; + + quicr::Name expected_ones = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF_name; + auto literal_ones = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF_name; + + CHECK_NE(ones, zeros); + CHECK_EQ(ones, expected_ones); + CHECK_EQ(literal_ones, expected_ones); +} + +TEST_CASE("quicr::Name Byte Array Tests") +{ + std::vector byte_arr(sizeof(quicr::Name)); + for (size_t i = 0; i < sizeof(quicr::Name) / 2; ++i) + { + byte_arr[i] = static_cast(0x0); + byte_arr[i + sizeof(quicr::Name) / 2] = static_cast((0x1000000000000000 >> 8 * i)); + } + + CHECK_FALSE(byte_arr.empty()); + CHECK_EQ(byte_arr.size(), 16); + + quicr::Name name_to_bytes = 0x10000000000000000000000000000000_name; + quicr::Name name_from_bytes(byte_arr); + CHECK_EQ(name_from_bytes, name_to_bytes); + + quicr::Name name_from_byte_ptr(byte_arr.data(), byte_arr.size()); + CHECK_EQ(name_from_byte_ptr, name_to_bytes); +} + +TEST_CASE("quicr::Name Logical Arithmetic Tests") +{ + auto arith_and = 0x01010101010101010101010101010101_name & 0x10101010101010101010101010101010_name; + CHECK_EQ(arith_and, 0x0_name); + + auto arith_and2 = 0x0101010101010101_name & 0x1010101010101010; + CHECK_EQ(arith_and2, 0x0_name); + + auto arith_or = 0x01010101010101010101010101010101_name | 0x10101010101010101010101010101010_name; + CHECK_EQ(arith_or, 0x11111111111111111111111111111111_name); + + auto arith_or2 = 0x0101010101010101_name | 0x1010101010101010; + CHECK_EQ(arith_or2, 0x1111111111111111_name); +} diff --git a/test/namespace.cpp b/test/namespace.cpp new file mode 100644 index 0000000..fd10c8d --- /dev/null +++ b/test/namespace.cpp @@ -0,0 +1,50 @@ +#include + +#include + +#include + +TEST_CASE("quicr::Namespace Constructor Tests") +{ + CHECK(std::is_trivial_v); + CHECK(std::is_trivially_constructible_v); + CHECK(std::is_trivially_default_constructible_v); + CHECK(std::is_trivially_destructible_v); + CHECK(std::is_trivially_copyable_v); + CHECK(std::is_trivially_copy_assignable_v); + CHECK(std::is_trivially_move_constructible_v); + CHECK(std::is_trivially_move_assignable_v); +} + +TEST_CASE("quicr::Namespace Contains Names Test") +{ + quicr::Namespace base_namespace(0x11111111111111112222222222222200_name, 120); + + quicr::Name valid_name = 0x111111111111111122222222222222FF_name; + CHECK(base_namespace.contains(valid_name)); + + quicr::Name another_valid_name = 0x11111111111111112222222222222211_name; + CHECK(base_namespace.contains(another_valid_name)); + + quicr::Name invalid_name = 0x11111111111111112222222222222300_name; + CHECK_FALSE(base_namespace.contains(invalid_name)); +} + +TEST_CASE("quicr::Namespace Contains Namespaces Test") +{ + quicr::Namespace base_namespace(0x11111111111111112222222222220000_name, 112); + + quicr::Namespace valid_namespace(0x11111111111111112222222222222200_name, 120); + CHECK(base_namespace.contains(valid_namespace)); + + quicr::Namespace invalid_namespace(0x11111111111111112222222222000000_name, 104); + CHECK_FALSE(base_namespace.contains(invalid_namespace)); +} + +TEST_CASE("quicr::Namespace String Constructor Test") +{ + quicr::Namespace ns = std::string_view("0xA11CEE00000001010007000000000000/80"); + CHECK_EQ(ns.to_hex(), "0xA11CEE00000001010007000000000000/80"); + CHECK_EQ(ns.name(), 0xA11CEE00000001010007000000000000_name); + CHECK_EQ(ns.length(), 80); +}