From: Joel Rosdahl Date: Sat, 25 Oct 2025 17:21:09 +0000 (+0200) Subject: feat: Use base16 encoding for all hash digest keys X-Git-Tag: v4.13~38^2~6 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7d64f3baf1e99ebbaf185aeef5426f04de0638de;p=thirdparty%2Fccache.git feat: Use base16 encoding for all hash digest keys This reverts the mixed encoding scheme introduced in commit 47cb00f7 ("Encode hash digests as 4 base16 digits + 29 base32hex digits") back to pure base16 encoding. Background: The mixed base16/base32hex encoding was introduced to reduce filename lengths slightly, thereby reducing the number of system calls when scanning local cache directories. At the time, "the effect is very small but there is no real downside". However, with the introduction of remote storage helpers implemented outside ccache's code base, a downside has emerged: key encoding inconsistency. Remote storage helpers must now either: 1. reimplement ccache's custom encoding algorithm to maintain key consistency between the remote storage and ccache's local storage (and logs), or 2. use standard base16 encoding, resulting in different key representations between ccache and the remote storage. By standardizing on base16 encoding throughout, we ensure that: - remote storage helpers can use simple, standard base16 encoding - keys are represented identically in ccache's local storage and remote storage - external implementations don't need to replicate custom encoding logic - debugging and key correlation across systems is straightforward The minor filesystem performance benefit of shorter filenames is outweighed by the ecosystem benefits of a simple, standard encoding scheme. Local storage will for now be backward compatible: to avoid invalidating existing cache entries, ccache will write in the new format but will try both the new and the legacy format on cache lookup. The legacy format is also still used for hashes that are part of the input hash. When some other input hash material is changed (which invalidates cache entries anyway) in the future, we'll drop the legacy format completely. --- diff --git a/src/ccache/ccache.cpp b/src/ccache/ccache.cpp index 0bb3fa33..efcd95b5 100644 --- a/src/ccache/ccache.cpp +++ b/src/ccache/ccache.cpp @@ -457,7 +457,7 @@ remember_include_file(Context& ctx, return tl::unexpected(Statistic::bad_input_file); } cpp_hash.hash_delimiter(using_pch_sum ? "pch_sum_hash" : "pch_hash"); - cpp_hash.hash(util::format_digest(file_digest)); + cpp_hash.hash(util::format_legacy_digest(file_digest)); } if (ctx.config.direct_mode()) { @@ -474,7 +474,7 @@ remember_include_file(Context& ctx, if (depend_mode_hash) { depend_mode_hash->hash_delimiter("include"); - depend_mode_hash->hash(util::format_digest(file_digest)); + depend_mode_hash->hash(util::format_legacy_digest(file_digest)); } } @@ -917,14 +917,14 @@ update_manifest(Context& ctx, }; }); if (added) { - LOG("Added result key to manifest {}", util::format_digest(manifest_key)); + LOG("Added result key to manifest {}", util::format_base16(manifest_key)); core::CacheEntry::Header header(ctx.config, core::CacheEntryType::manifest); ctx.storage.put(manifest_key, core::CacheEntryType::manifest, core::CacheEntry::serialize(header, ctx.manifest)); } else { LOG("Did not add result key to manifest {}", - util::format_digest(manifest_key)); + util::format_base16(manifest_key)); } } @@ -1279,7 +1279,7 @@ to_cache(Context& ctx, ASSERT(false); } LOG_RAW("Got result key from dependency file"); - LOG("Result key: {}", util::format_digest(*result_key)); + LOG("Result key: {}", util::format_base16(*result_key)); } ASSERT(result_key); @@ -2326,7 +2326,7 @@ get_manifest_key(Context& ctx, Hash& hash) ctx.config.set_direct_mode(false); return {}; } - hash.hash(util::format_digest(input_file_digest)); + hash.hash(util::format_legacy_digest(input_file_digest)); return hash.digest(); } @@ -2436,7 +2436,7 @@ get_result_key_from_manifest(Context& ctx, const Hash::Digest& manifest_key) }); if (read_manifests > 1 && !ctx.config.remote_only()) { LOG("Storing merged manifest {} locally", - util::format_digest(manifest_key)); + util::format_base16(manifest_key)); core::CacheEntry::Header header(ctx.config, core::CacheEntryType::manifest); ctx.storage.local.put(manifest_key, core::CacheEntryType::manifest, @@ -2509,7 +2509,7 @@ calculate_result_and_manifest_key(Context& ctx, if (direct_mode) { TRY_ASSIGN(manifest_key, get_manifest_key(ctx, hash)); if (manifest_key && !ctx.config.recache()) { - LOG("Manifest key: {}", util::format_digest(*manifest_key)); + LOG("Manifest key: {}", util::format_base16(*manifest_key)); result_key = get_result_key_from_manifest(ctx, *manifest_key); } } else if (ctx.args_info.arch_args.empty()) { @@ -2542,7 +2542,7 @@ calculate_result_and_manifest_key(Context& ctx, } if (result_key) { - LOG("Result key: {}", util::format_digest(*result_key)); + LOG("Result key: {}", util::format_base16(*result_key)); } return std::make_pair(result_key, manifest_key); } @@ -2593,12 +2593,12 @@ from_cache(Context& ctx, FromCacheCallMode mode, const Hash::Digest& result_key) deserializer.visit(result_retriever); } catch (core::ResultRetriever::WriteError& e) { LOG("Write error when retrieving result from {}: {}", - util::format_digest(result_key), + util::format_base16(result_key), e.what()); return tl::unexpected(Statistic::bad_output_file); } catch (core::Error& e) { LOG("Failed to get result from {}: {}", - util::format_digest(result_key), + util::format_base16(result_key), e.what()); return false; } diff --git a/src/ccache/core/mainoptions.cpp b/src/ccache/core/mainoptions.cpp index 4e0d34e0..65b30893 100644 --- a/src/ccache/core/mainoptions.cpp +++ b/src/ccache/core/mainoptions.cpp @@ -665,7 +665,7 @@ process_main_options(int argc, const char* const* argv) const auto result = arg == "-" ? hash.hash_fd(STDIN_FILENO) : hash.hash_file(arg); if (result) { - PRINT(stdout, "{}\n", util::format_digest(hash.digest())); + PRINT(stdout, "{}\n", util::format_base16(hash.digest())); } else { PRINT(stderr, "Error: Failed to hash {}: {}\n", arg, result.error()); return EXIT_FAILURE; diff --git a/src/ccache/core/manifest.cpp b/src/ccache/core/manifest.cpp index 12c7bf10..d2b84bef 100644 --- a/src/ccache/core/manifest.cpp +++ b/src/ccache/core/manifest.cpp @@ -166,7 +166,7 @@ Manifest::look_up_result_digest(const Context& ctx) const const auto& result = m_results[i - 1]; LOG("Considering result entry {} ({})", i - 1, - util::format_digest(result.key)); + util::format_base16(result.key)); if (result_matches(ctx, result, stated_files, hashed_files)) { LOG("Result entry {} matched in manifest", i - 1); return result.key; @@ -438,8 +438,8 @@ Manifest::result_matches( if (hashed_files_iter->second != fi.digest) { LOG("Mismatch for {}: hash {} != {}", path, - util::format_digest(hashed_files_iter->second), - util::format_digest(fi.digest)); + util::format_base16(hashed_files_iter->second), + util::format_base16(fi.digest)); return false; } } @@ -462,7 +462,7 @@ Manifest::inspect(FILE* const stream) const PRINT(stream, " {}:\n", i); PRINT(stream, " Path index: {}\n", m_file_infos[i].index); PRINT( - stream, " Hash: {}\n", util::format_digest(m_file_infos[i].digest)); + stream, " Hash: {}\n", util::format_base16(m_file_infos[i].digest)); PRINT(stream, " File size: {}\n", m_file_infos[i].fsize); if (m_file_infos[i].mtime == util::TimePoint()) { PRINT_RAW(stream, " Mtime: -\n"); @@ -490,7 +490,7 @@ Manifest::inspect(FILE* const stream) const PRINT(stream, " {}", file_info_index); } PRINT_RAW(stream, "\n"); - PRINT(stream, " Key: {}\n", util::format_digest(m_results[i].key)); + PRINT(stream, " Key: {}\n", util::format_base16(m_results[i].key)); } } diff --git a/src/ccache/hashutil.cpp b/src/ccache/hashutil.cpp index bb76ed9f..ead716b2 100644 --- a/src/ccache/hashutil.cpp +++ b/src/ccache/hashutil.cpp @@ -256,7 +256,7 @@ hash_source_code_file(const Context& ctx, // macro expansions. Hash hash; - hash.hash(util::format_digest(digest)); + hash.hash(util::format_legacy_digest(digest)); if (result.contains(HashSourceCode::found_date)) { LOG("Found __DATE__ in {}", path); @@ -320,7 +320,7 @@ hash_binary_file(const Context& ctx, Hash& hash, const fs::path& path) Hash::Digest digest; const bool success = hash_binary_file(ctx, digest, path); if (success) { - hash.hash(util::format_digest(digest)); + hash.hash(util::format_legacy_digest(digest)); } return success; } diff --git a/src/ccache/storage/local/localstorage.cpp b/src/ccache/storage/local/localstorage.cpp index 9af0cf19..778e785b 100644 --- a/src/ccache/storage/local/localstorage.cpp +++ b/src/ccache/storage/local/localstorage.cpp @@ -502,7 +502,7 @@ LocalStorage::get(const Hash::Digest& key, const core::CacheEntryType type) const auto value = util::read_file(cache_file.path); if (value) { LOG("Retrieved {} from local storage ({})", - util::format_digest(key), + util::format_base16(key), cache_file.path); // Update modification timestamp to save file from LRU cleanup. @@ -513,7 +513,7 @@ LocalStorage::get(const Hash::Digest& key, const core::CacheEntryType type) LOG("Failed to read {}: {}", cache_file.path, value.error()); } } else { - LOG("No {} in local storage", util::format_digest(key)); + LOG("No {} in local storage", util::format_base16(key)); } increment_statistic(return_value ? Statistic::local_storage_read_hit @@ -555,7 +555,7 @@ LocalStorage::put(const Hash::Digest& key, } LOG("Stored {} in local storage ({})", - util::format_digest(key), + util::format_base16(key), cache_file.path); m_stored_data = true; @@ -588,7 +588,7 @@ LocalStorage::put(const Hash::Digest& key, // be done almost anywhere, but we might as well do it near the end as we save // the stat call if we exit early. util::create_cachedir_tag( - FMT("{}/{}", m_config.cache_dir(), util::format_digest(key)[0])); + FMT("{}/{}", m_config.cache_dir(), util::format_base16(key)[0])); } void @@ -596,7 +596,7 @@ LocalStorage::remove(const Hash::Digest& key, const core::CacheEntryType type) { const auto cache_file = look_up_cache_file(key, type); if (!cache_file.dir_entry) { - LOG("No {} to remove from local storage", util::format_digest(key)); + LOG("No {} to remove from local storage", util::format_base16(key)); return; } @@ -611,7 +611,7 @@ LocalStorage::remove(const Hash::Digest& key, const core::CacheEntryType type) } LOG("Removed {} from local storage ({})", - util::format_digest(key), + util::format_base16(key), cache_file.path); increment_files_and_size_counters( key, -1, -static_cast(cache_file.dir_entry.size_on_disk() / 1024)); @@ -1075,7 +1075,7 @@ LocalStorage::look_up_cache_file(const Hash::Digest& key, const core::CacheEntryType type) const { const auto key_string = - FMT("{}{}", util::format_digest(key), suffix_from_type(type)); + FMT("{}{}", util::format_base16(key), suffix_from_type(type)); for (uint8_t level = k_min_cache_levels; level <= k_max_cache_levels; ++level) { @@ -1113,7 +1113,7 @@ LocalStorage::move_to_wanted_cache_level(const StatisticsCounters& counters, const auto wanted_level = calculate_wanted_cache_level(counters.get(Statistic::files_in_cache)); const auto wanted_path = get_path_in_cache( - wanted_level, util::format_digest(key) + suffix_from_type(type)); + wanted_level, util::format_base16(key) + suffix_from_type(type)); if (cache_file_path != wanted_path) { core::ensure_dir_exists(wanted_path.parent_path()); diff --git a/src/ccache/storage/remote/filestorage.cpp b/src/ccache/storage/remote/filestorage.cpp index 574b2da7..1f34b6e4 100644 --- a/src/ccache/storage/remote/filestorage.cpp +++ b/src/ccache/storage/remote/filestorage.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2021-2025 Joel Rosdahl and other contributors +// Copyright (C) 2021-2026 Joel Rosdahl and other contributors // // See doc/authors.adoc for a complete list of contributors. // @@ -189,10 +189,10 @@ FileStorageBackend::get_entry_path(const Hash::Digest& key) const { switch (m_layout) { case Layout::flat: - return FMT("{}/{}", m_dir, util::format_digest(key)); + return FMT("{}/{}", m_dir, util::format_base16(key)); case Layout::subdirs: { - const auto key_str = util::format_digest(key); + const auto key_str = util::format_base16(key); const uint8_t digits = 2; ASSERT(key_str.length() > digits); return FMT("{}/{:.{}}/{}", m_dir, key_str, digits, &key_str[digits]); diff --git a/src/ccache/storage/remote/httpstorage.cpp b/src/ccache/storage/remote/httpstorage.cpp index be4d7afe..5f7d8719 100644 --- a/src/ccache/storage/remote/httpstorage.cpp +++ b/src/ccache/storage/remote/httpstorage.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2021-2025 Joel Rosdahl and other contributors +// Copyright (C) 2021-2026 Joel Rosdahl and other contributors // // See doc/authors.adoc for a complete list of contributors. // @@ -282,19 +282,18 @@ HttpStorageBackend::get_entry_path(const Hash::Digest& key) const const auto sha256_hex_size = 64; static_assert(std::tuple_size() == 20, "Update below if digest size changes"); - std::string hex_digits = util::format_base16(key); - hex_digits.append(hex_digits.data(), sha256_hex_size - hex_digits.size()); - LOG("Translated key {} to Bazel layout ac/{}", - util::format_digest(key), - hex_digits); - return FMT("{}ac/{}", m_url_path, hex_digits); + std::string hex_key = util::format_base16(key); + std::string bazel_key = + FMT("ac/{}{:.{}}", hex_key, hex_key, sha256_hex_size - hex_key.size()); + LOG("Translated key {} to Bazel layout {}", hex_key, bazel_key); + return FMT("{}{}", m_url_path, bazel_key); } case Layout::flat: - return m_url_path + util::format_digest(key); + return m_url_path + util::format_base16(key); case Layout::subdirs: { - const auto key_str = util::format_digest(key); + const auto key_str = util::format_base16(key); const uint8_t digits = 2; ASSERT(key_str.length() > digits); return FMT("{}{:.{}}/{}", m_url_path, key_str, digits, &key_str[digits]); diff --git a/src/ccache/storage/remote/redisstorage.cpp b/src/ccache/storage/remote/redisstorage.cpp index bd163e7d..f7515a5b 100644 --- a/src/ccache/storage/remote/redisstorage.cpp +++ b/src/ccache/storage/remote/redisstorage.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2021-2025 Joel Rosdahl and other contributors +// Copyright (C) 2021-2026 Joel Rosdahl and other contributors // // See doc/authors.adoc for a complete list of contributors. // @@ -344,7 +344,7 @@ RedisStorageBackend::redis_command(const char* format, ...) std::string RedisStorageBackend::get_key_string(const Hash::Digest& digest) const { - return FMT("{}:{}", m_prefix, util::format_digest(digest)); + return FMT("{}:{}", m_prefix, util::format_base16(digest)); } } // namespace diff --git a/src/ccache/storage/storage.cpp b/src/ccache/storage/storage.cpp index c9ce6c35..6020b99c 100644 --- a/src/ccache/storage/storage.cpp +++ b/src/ccache/storage/storage.cpp @@ -479,7 +479,7 @@ Storage::get_from_remote_storage(const Hash::Digest& key, auto& value = *result; if (value) { LOG("Retrieved {} from {} ({:.2f} ms)", - util::format_digest(key), + util::format_base16(key), backend->url_for_logging, ms); local.increment_statistic(core::Statistic::remote_storage_read_hit); @@ -491,7 +491,7 @@ Storage::get_from_remote_storage(const Hash::Digest& key, } } else { LOG("No {} in {} ({:.2f} ms)", - util::format_digest(key), + util::format_base16(key), backend->url_for_logging, ms); local.increment_statistic(core::Statistic::remote_storage_read_miss); @@ -506,7 +506,7 @@ Storage::put_in_remote_storage(const Hash::Digest& key, { if (!core::CacheEntry::Header(value).self_contained) { LOG("Not putting {} in remote storage since it's not self-contained", - util::format_digest(key)); + util::format_base16(key)); return; } @@ -528,7 +528,7 @@ Storage::put_in_remote_storage(const Hash::Digest& key, const bool stored = *result; LOG("{} {} in {} ({:.2f} ms)", stored ? "Stored" : "Did not have to store", - util::format_digest(key), + util::format_base16(key), backend->url_for_logging, ms); local.increment_statistic(core::Statistic::remote_storage_write); @@ -555,12 +555,12 @@ Storage::remove_from_remote_storage(const Hash::Digest& key) const bool removed = *result; if (removed) { LOG("Removed {} from {} ({:.2f} ms)", - util::format_digest(key), + util::format_base16(key), backend->url_for_logging, ms); } else { LOG("No {} to remove from {} ({:.2f} ms)", - util::format_digest(key), + util::format_base16(key), backend->url_for_logging, ms); } diff --git a/src/ccache/util/string.cpp b/src/ccache/util/string.cpp index 86b783c8..90266422 100644 --- a/src/ccache/util/string.cpp +++ b/src/ccache/util/string.cpp @@ -164,7 +164,7 @@ format_base32hex(nonstd::span data) } std::string -format_digest(nonstd::span data) +format_legacy_digest(nonstd::span data) { const size_t base16_bytes = 2; ASSERT(data.size() >= base16_bytes); diff --git a/src/ccache/util/string.hpp b/src/ccache/util/string.hpp index e8c4989d..a5ee5bcd 100644 --- a/src/ccache/util/string.hpp +++ b/src/ccache/util/string.hpp @@ -70,14 +70,6 @@ std::string format_base16(nonstd::span data); // characters will be added. std::string format_base32hex(nonstd::span data); -// Format a hash digest representing `data`. -// -// The first two bytes are encoded as four lowercase base16 digits to maintain -// compatibility with the cleanup algorithm in older ccache versions and to -// allow for up to four uniform cache levels. The rest are encoded as lowercase -// base32hex digits without padding characters. -std::string format_digest(nonstd::span data); - // Format `ms` as a duration string. std::string format_duration(std::chrono::milliseconds ms); @@ -85,6 +77,14 @@ std::string format_duration(std::chrono::milliseconds ms); std::string format_human_readable_diff(int64_t diff, SizeUnitPrefixType prefix_type); +// Format a hash digest representing `data`. +// +// The first two bytes are encoded as four lowercase base16 digits to maintain +// compatibility with the cleanup algorithm in older ccache versions and to +// allow for up to four uniform cache levels. The rest are encoded as lowercase +// base32hex digits without padding characters. +std::string format_legacy_digest(nonstd::span data); + // Format `size` as a human-readable string. std::string format_human_readable_size(uint64_t size, SizeUnitPrefixType prefix_type); diff --git a/test/run b/test/run index 092b78f2..1b8a781b 100755 --- a/test/run +++ b/test/run @@ -208,6 +208,12 @@ expect_missing() { fi } +expect_equal() { + if [ "$1" != "$2" ]; then + test_failed_internal "$1 != $2" + fi +} + expect_equal_content() { if [ ! -e "$1" ]; then test_failed_internal "expect_equal_content: $1 missing" diff --git a/test/suites/base.bash b/test/suites/base.bash index 76650831..b3ce9e66 100644 --- a/test/suites/base.bash +++ b/test/suites/base.bash @@ -1907,18 +1907,8 @@ fi # ------------------------------------------------------------------------- TEST "--hash-file" - $CCACHE --hash-file /dev/null > hash.out - printf "a" | $CCACHE --hash-file - >> hash.out - - hash_0='af1396svbud1kqg40jfa6reciicrpcisi' - hash_1='17765vetiqd4ae95qpbhfb1ut8gj42r6m' - - if grep "$hash_0" hash.out >/dev/null 2>&1 && \ - grep "$hash_1" hash.out >/dev/null 2>&1; then - : OK - else - test_failed "Unexpected output of --hash-file" - fi + expect_equal $($CCACHE --hash-file /dev/null) af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9 + expect_equal $(printf a | $CCACHE --hash-file -) 17762fddd969a453925d65717ac3eea21320b66b } # ============================================================================= diff --git a/test/suites/direct.bash b/test/suites/direct.bash index 45fac9c0..d6de9669 100644 --- a/test/suites/direct.bash +++ b/test/suites/direct.bash @@ -1245,9 +1245,9 @@ EOF manifest=`find $CCACHE_DIR -name '*M'` $CCACHE --inspect $manifest >manifest.dump - checksum_test1_h='b7273h0ksdehi0o4pitg5jeehal3i54ns' - checksum_test2_h='24f1315jch5tcndjbm6uejtu8q3lf9100' - checksum_test3_h='56a6dkffffv485aepk44seaq3i6lbepq2' + checksum_test1_h='b7271c414e35d190304ccbb02cdce8aaa391497e' + checksum_test2_h='24f1184b3644bd65db35d8de74fbe468757a4200' + checksum_test3_h='56a66d1ef7bfe44154ecd084e395a1c8d55bb3a1' if grep "Hash: $checksum_test1_h" manifest.dump >/dev/null 2>&1 && \ grep "Hash: $checksum_test2_h" manifest.dump >/dev/null 2>&1 && \ diff --git a/unittest/test_hash.cpp b/unittest/test_hash.cpp index ec3cfce1..337c17b6 100644 --- a/unittest/test_hash.cpp +++ b/unittest/test_hash.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2010-2024 Joel Rosdahl and other contributors +// Copyright (C) 2010-2025 Joel Rosdahl and other contributors // // See doc/authors.adoc for a complete list of contributors. // @@ -27,26 +27,26 @@ TEST_CASE("known strings") { SUBCASE("initial state") { - CHECK(util::format_digest(Hash().digest()) - == "af1396svbud1kqg40jfa6reciicrpcisi"); + CHECK(util::format_base16(Hash().digest()) + == "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9"); } SUBCASE("empty string") { - CHECK(util::format_digest(Hash().hash("").digest()) - == "af1396svbud1kqg40jfa6reciicrpcisi"); + CHECK(util::format_base16(Hash().hash("").digest()) + == "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9"); } SUBCASE("a") { - CHECK(util::format_digest(Hash().hash("a").digest()) - == "17765vetiqd4ae95qpbhfb1ut8gj42r6m"); + CHECK(util::format_base16(Hash().hash("a").digest()) + == "17762fddd969a453925d65717ac3eea21320b66b"); } SUBCASE("message digest") { - CHECK(util::format_digest(Hash().hash("message digest").digest()) - == "7bc2kbnbinerv6ruptldpdrb8ko93hcdo"); + CHECK(util::format_base16(Hash().hash("message digest").digest()) + == "7bc2a2eeb95ddbf9b7ecf6adcb76b453091c58dc"); } SUBCASE("long string") @@ -54,8 +54,8 @@ TEST_CASE("known strings") const char long_string[] = "123456789012345678901234567890123456789012345678901234567890" "12345678901234567890"; - CHECK(util::format_digest(Hash().hash(long_string).digest()) - == "f263ljqhc8co1ee8rpeq98bt654o9o2qm"); + CHECK(util::format_base16(Hash().hash(long_string).digest()) + == "f263acf51621980b9c8de5da4a17d314984e05ab"); } } @@ -65,14 +65,17 @@ TEST_CASE("Hash::digest should not alter state") h.hash("message"); h.digest(); h.hash(" digest"); - CHECK(util::format_digest(h.digest()) == "7bc2kbnbinerv6ruptldpdrb8ko93hcdo"); + CHECK(util::format_base16(h.digest()) + == "7bc2a2eeb95ddbf9b7ecf6adcb76b453091c58dc"); } TEST_CASE("Hash::digest should be idempotent") { Hash h; - CHECK(util::format_digest(h.digest()) == "af1396svbud1kqg40jfa6reciicrpcisi"); - CHECK(util::format_digest(h.digest()) == "af1396svbud1kqg40jfa6reciicrpcisi"); + CHECK(util::format_base16(h.digest()) + == "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9"); + CHECK(util::format_base16(h.digest()) + == "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9"); } TEST_SUITE_END();