diff --git a/CMakeLists.txt b/CMakeLists.txt index 1bccc6a..cc1b776 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,7 @@ set_target_properties(cpputils PROPERTIES target_sources(cpputils PRIVATE + src/hex.cpp src/uuid.cpp ) @@ -28,6 +29,7 @@ target_compile_features(cpputils PUBLIC cxx_std_17) if(BUILD_TESTS) add_executable(cpputils_tests tests/test_main.cpp + tests/test_hex.cpp tests/test_uuid.cpp) target_include_directories(cpputils_tests PRIVATE diff --git a/include/cpputils/hex.h b/include/cpputils/hex.h new file mode 100644 index 0000000..bf0dc5f --- /dev/null +++ b/include/cpputils/hex.h @@ -0,0 +1,58 @@ +#pragma once + +#include + +namespace cpputils { + +namespace hex { + +std::string toHex(std::string const& input); +std::string fromHex(std::string const& input); + +constexpr int hexVal(char c) { + switch (c) { + case '0': + return 0; + case '1': + return 1; + case '2': + return 2; + case '3': + return 3; + case '4': + return 4; + case '5': + return 5; + case '6': + return 6; + case '7': + return 7; + case '8': + return 8; + case '9': + return 9; + case 'a': + case 'A': + return 10; + case 'b': + case 'B': + return 11; + case 'c': + case 'C': + return 12; + case 'd': + case 'D': + return 13; + case 'e': + case 'E': + return 14; + case 'f': + case 'F': + return 15; + default: + return -1; + } +} + +} // namespace hex +} // namespace cpputils diff --git a/src/hex.cpp b/src/hex.cpp new file mode 100644 index 0000000..e0ec35b --- /dev/null +++ b/src/hex.cpp @@ -0,0 +1,38 @@ +#include +#include +#include +#include + +namespace cpputils { +namespace hex { + +std::string toHex(std::string const& input) { + std::stringstream ss; + for (auto c : input) { + ss << std::hex << std::setw(2) << std::setfill('0') + << static_cast(static_cast(c)); + } + return ss.str(); +} + +std::string fromHex(std::string const& input) { + if (input.size() % 2 != 0) { + return {}; + } + std::string out; + out.reserve(input.size() / 2); + + for (size_t i = 0; i < input.size(); i += 2) { + int hi = hexVal(input[i]); + int lo = hexVal(input[i + 1]); + if (hi < 0 || lo < 0) { + return {}; + } + + out.push_back(static_cast((hi << 4) | lo)); + } + return out; +} + +} // namespace hex +} // namespace cpputils diff --git a/tests/test_hex.cpp b/tests/test_hex.cpp new file mode 100644 index 0000000..33cd8e1 --- /dev/null +++ b/tests/test_hex.cpp @@ -0,0 +1,81 @@ +#include "catch.hpp" + +#include +#include + +using namespace cpputils::hex; + +TEST_CASE("Hex conversion round-trip works", "[hex]") { + std::string original = "Hello, World!"; + std::string hex = toHex(original); + std::string result = fromHex(hex); + + REQUIRE(result == original); +} + +TEST_CASE("Empty string conversion", "[hex]") { + std::string empty = ""; + std::string hex = toHex(empty); + std::string result = fromHex(hex); + + REQUIRE(hex.empty()); + REQUIRE(result.empty()); +} + +TEST_CASE("Single character conversion", "[hex]") { + std::string single = "A"; + std::string hex = toHex(single); + std::string result = fromHex(hex); + + REQUIRE(result == single); +} + +TEST_CASE("Non-printable ASCII characters", "[hex]") { + std::string nonPrintable; + for (char c = 0; c < 32; ++c) { + nonPrintable += c; + } + + std::string hex = toHex(nonPrintable); + std::string result = fromHex(hex); + + REQUIRE(result == nonPrintable); +} + +TEST_CASE("Extended ASCII characters", "[hex]") { + std::string extended; + for (unsigned char c = 128; c < 255; ++c) { + extended += static_cast(c); + } + + std::string hex = toHex(extended); + std::string result = fromHex(hex); + + REQUIRE(result == extended); +} + +TEST_CASE("Hex string with lowercase and uppercase letters", "[hex]") { + std::string original = "abcABC123"; + std::string hex = toHex(original); + std::string result = fromHex(hex); + + REQUIRE(result == original); +} + +TEST_CASE("Odd-length hex string should fail", "[hex]") { + std::string invalidHex = "A"; + REQUIRE(fromHex(invalidHex).empty()); +} + +TEST_CASE("Hex string with invalid characters should fail", "[hex]") { + std::string invalidHex = "ZZ"; + REQUIRE(fromHex(invalidHex).empty()); +} + +TEST_CASE("Large string conversion", "[hex]") { + std::string large(10000, 'x'); + std::string hex = toHex(large); + std::string result = fromHex(hex); + + REQUIRE(result == large); +}