#include "string.hpp"
#include <ccache/util/assertions.hpp>
+#include <ccache/util/bytes.hpp>
#include <ccache/util/expected.hpp>
#include <ccache/util/filesystem.hpp>
#include <ccache/util/format.hpp>
return join(path_list, k_path_delimiter);
}
+tl::expected<Bytes, std::string>
+parse_base16(std::string_view hex_string)
+{
+ if (hex_string.size() % 2 != 0) {
+ return tl::unexpected(
+ FMT("invalid hex string (odd length): \"{}\"", hex_string));
+ }
+
+ const auto from_hex_digit = [](char ch) -> std::optional<uint8_t> {
+ if (ch >= '0' && ch <= '9') {
+ return ch - '0';
+ } else if (ch >= 'a' && ch <= 'f') {
+ return ch - 'a' + 10;
+ } else if (ch >= 'A' && ch <= 'F') {
+ return ch - 'A' + 10;
+ } else {
+ return std::nullopt;
+ }
+ };
+
+ Bytes result;
+ result.reserve(hex_string.size() / 2);
+
+ for (size_t i = 0; i < hex_string.size(); i += 2) {
+ auto high = from_hex_digit(hex_string[i]);
+ auto low = from_hex_digit(hex_string[i + 1]);
+
+ if (!high) {
+ return tl::unexpected(
+ FMT("invalid hex character at position {}: \"{}\"", i, hex_string));
+ }
+ if (!low) {
+ return tl::unexpected(
+ FMT("invalid hex character at position {}: \"{}\"", i + 1, hex_string));
+ }
+
+ result.push_back(static_cast<uint8_t>((*high << 4) | *low));
+ }
+
+ return result;
+}
+
tl::expected<double, std::string>
parse_double(const std::string& value)
{
// --- Interface ---
+class Bytes;
+
enum class SizeUnitPrefixType { binary, decimal };
enum class TimeZone { local, utc };
// Join paths into a string with system-dependent delimiter.
std::string join_path_list(const std::vector<std::filesystem::path>& path_list);
+// Parse a hexadecimal string into bytes. The input string must have an even
+// length and contain only hexadecimal digits (0-9, a-f, A-F).
+//
+// Returns an error string if the input is not a valid hexadecimal string.
+tl::expected<Bytes, std::string> parse_base16(std::string_view hex_string);
+
// Parse a string into a double.
//
// Returns an error string if `value` cannot be parsed as a double.
// this program; if not, write to the Free Software Foundation, Inc., 51
// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#include <ccache/util/bytes.hpp>
#include <ccache/util/string.hpp>
#include <doctest/doctest.h>
CHECK(util::format_base16({data, sizeof(data)}) == "00010203");
}
+TEST_CASE("util::parse_base16")
+{
+ SUBCASE("empty string")
+ {
+ auto result = util::parse_base16("");
+ REQUIRE(result);
+ CHECK(result->empty());
+ }
+
+ SUBCASE("valid hex strings")
+ {
+ auto result1 = util::parse_base16("666f6f00");
+ REQUIRE(result1);
+ CHECK(result1->size() == 4);
+ CHECK(result1->at(0) == 0x66);
+ CHECK(result1->at(1) == 0x6f);
+ CHECK(result1->at(2) == 0x6f);
+ CHECK(result1->at(3) == 0x00);
+
+ auto result2 = util::parse_base16("00010203");
+ REQUIRE(result2);
+ CHECK(result2->size() == 4);
+ CHECK(result2->at(0) == 0x00);
+ CHECK(result2->at(1) == 0x01);
+ CHECK(result2->at(2) == 0x02);
+ CHECK(result2->at(3) == 0x03);
+ }
+
+ SUBCASE("uppercase hex")
+ {
+ auto result = util::parse_base16("DEADBEEF");
+ REQUIRE(result);
+ CHECK(result->size() == 4);
+ CHECK(result->at(0) == 0xde);
+ CHECK(result->at(1) == 0xad);
+ CHECK(result->at(2) == 0xbe);
+ CHECK(result->at(3) == 0xef);
+ }
+
+ SUBCASE("mixed case hex")
+ {
+ auto result = util::parse_base16("DeAdBeEf");
+ REQUIRE(result);
+ CHECK(result->size() == 4);
+ CHECK(result->at(0) == 0xde);
+ CHECK(result->at(1) == 0xad);
+ CHECK(result->at(2) == 0xbe);
+ CHECK(result->at(3) == 0xef);
+ }
+
+ SUBCASE("odd length string")
+ {
+ auto result = util::parse_base16("abc");
+ REQUIRE(!result);
+ CHECK(result.error().find("odd length") != std::string::npos);
+ }
+
+ SUBCASE("invalid characters")
+ {
+ auto result1 = util::parse_base16("xyz!");
+ REQUIRE(!result1);
+ CHECK(result1.error() == "invalid hex character at position 0: \"xyz!\"");
+
+ auto result2 = util::parse_base16("12!4");
+ REQUIRE(!result2);
+ CHECK(result2.error() == "invalid hex character at position 2: \"12!4\"");
+
+ auto result3 = util::parse_base16("abcg");
+ REQUIRE(!result3);
+ CHECK(result3.error() == "invalid hex character at position 3: \"abcg\"");
+ }
+
+ SUBCASE("round trip")
+ {
+ util::Bytes original = {0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0};
+ auto hex = util::format_base16(original);
+ auto result = util::parse_base16(hex);
+ REQUIRE(result);
+ CHECK(*result == original);
+ }
+}
+
TEST_CASE("util::format_base32hex")
{
// Test vectors (without padding) from RFC 4648.