Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 28 additions & 3 deletions merklecpp.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,26 @@ namespace merkle
return r;
}

static inline bool decode_hex_digit(char c, uint8_t& value)
{
if ('0' <= c && c <= '9')
{
value = static_cast<uint8_t>(c - '0');
return true;
}
if ('a' <= c && c <= 'f')
{
value = static_cast<uint8_t>(c - 'a' + 10);
return true;
}
if ('A' <= c && c <= 'F')
{
value = static_cast<uint8_t>(c - 'A' + 10);
return true;
}
return false;
}

/// @brief Template for fixed-size hashes
/// @tparam SIZE Size of the hash in number of bytes
template <size_t SIZE>
Expand Down Expand Up @@ -119,9 +139,14 @@ namespace merkle
}
for (size_t i = 0; i < SIZE; i++)
{
int tmp = 0;
sscanf(s.c_str() + 2 * i, "%02x", &tmp);
bytes[i] = tmp;
uint8_t high = 0;
uint8_t low = 0;
if (!decode_hex_digit(s[2 * i], high) ||
!decode_hex_digit(s[2 * i + 1], low))
{
throw std::runtime_error("invalid hash string");
}
bytes[i] = static_cast<uint8_t>((high << 4) | low);
}
}

Expand Down
27 changes: 27 additions & 0 deletions test/coverage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,32 @@ namespace
"deserialise_uint64_t should reject short buffers");
}

void test_hash_string_parsing()
{
const std::string valid_hex(64, 'a');
const merkle::Hash valid_hash(valid_hex);
for (const auto byte : valid_hash.bytes)
{
require(byte == 0xAA, "valid hash string parsed incorrectly");
}

std::string mixed_case_hex(64, '0');
mixed_case_hex[0] = 'A';
mixed_case_hex[1] = 'f';
const merkle::Hash mixed_case_hash(mixed_case_hex);
require(mixed_case_hash.bytes[0] == 0xAF, "mixed-case hash string parsed incorrectly");

require_throws(
[] { merkle::Hash(std::string(64, 'z')); },
"hash string should reject non-hex digits");

std::string partially_invalid(64, '0');
partially_invalid[1] = 'z';
require_throws(
[&] { (void)merkle::Hash(partially_invalid); },
"hash string should reject partially parsed hex bytes");
}

void test_path_metadata_and_equality()
{
merkle::Tree tree;
Expand Down Expand Up @@ -274,6 +300,7 @@ int main()
try
{
test_serialisation_helpers();
test_hash_string_parsing();
test_path_metadata_and_equality();
test_tree_partial_serialisation_bounds();
test_tree_assignment_and_moves();
Expand Down
5 changes: 5 additions & 0 deletions test/unit_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ TEST_CASE("HashT constructors and error paths")
const merkle::Hash h_str(valid_hex);
REQUIRE(h_str.bytes[0] == 0xAB);

valid_hex[0] = 'A';
valid_hex[1] = 'f';
const merkle::Hash mixed_case_h_str(valid_hex);
REQUIRE(mixed_case_h_str.bytes[0] == 0xAF);

// String constructor: invalid length throws
REQUIRE_THROWS(merkle::Hash(std::string(63, '0')));
REQUIRE_THROWS(merkle::Hash(std::string(65, '0')));
Expand Down
Loading