From b61885786db866ee0b129e8e322bd2d6d26a07fa Mon Sep 17 00:00:00 2001 From: John Ericson Date: Fri, 5 Dec 2025 11:21:47 -0500 Subject: [PATCH 1/4] Improve wrong format message with `nix hash convert` We have the machinery to make a more informative error, telling the user what format was actually encountered, and not just that it is not the format that was requested. --- src/libutil/hash.cc | 37 +++++++++++++++++++--------- src/libutil/include/nix/util/hash.hh | 6 +++++ src/nix/hash-convert.md | 2 +- src/nix/hash.cc | 12 +++++---- tests/functional/hash-convert.sh | 8 +++--- 5 files changed, 43 insertions(+), 22 deletions(-) diff --git a/src/libutil/hash.cc b/src/libutil/hash.cc index c614f1f1c5b0..d051225a78fa 100644 --- a/src/libutil/hash.cc +++ b/src/libutil/hash.cc @@ -182,8 +182,10 @@ Hash Hash::parseSRI(std::string_view original) * * @param resolveAlgo resolves the parsed type (or throws an error when it is not * possible.) + * + * @return the parsed hash and the format it was parsed from */ -static Hash parseAnyHelper(std::string_view rest, auto resolveAlgo) +static std::pair parseAnyHelper(std::string_view rest, auto resolveAlgo) { bool isSRI = false; @@ -203,34 +205,45 @@ static Hash parseAnyHelper(std::string_view rest, auto resolveAlgo) HashAlgorithm algo = resolveAlgo(std::move(optParsedAlgo)); - auto [decode, formatName] = [&]() -> DecodeNamePair { + auto [decode, formatName, format] = [&]() -> std::tuple { if (isSRI) { /* In the SRI case, we always are using Base64. If the length is wrong, get an error later. */ - return {base64::decode, "SRI"}; + return {base64::decode, "SRI", HashFormat::SRI}; } else { /* Otherwise, decide via the length of the hash (for the given algorithm) what base encoding it is. */ - return baseExplicit(baseFromSize(rest, algo)); + auto format = baseFromSize(rest, algo); + auto [decode, formatName] = baseExplicit(format); + return {decode, formatName, format}; } }(); - return parseLowLevel(rest, algo, {decode, formatName}); + return {parseLowLevel(rest, algo, {decode, formatName}), format}; } Hash Hash::parseAnyPrefixed(std::string_view original) { - return parseAnyHelper(original, [&](std::optional optParsedAlgo) { - // Either the string or user must provide the type, if they both do they - // must agree. - if (!optParsedAlgo) - throw BadHash("hash '%s' does not include a type", original); + return parseAnyHelper( + original, + [&](std::optional optParsedAlgo) { + // Either the string or user must provide the type, if they both do they + // must agree. + if (!optParsedAlgo) + throw BadHash("hash '%s' does not include a type", original); - return *optParsedAlgo; - }); + return *optParsedAlgo; + }) + .first; } Hash Hash::parseAny(std::string_view original, std::optional optAlgo) +{ + return parseAnyReturningFormat(original, optAlgo).first; +} + +std::pair +Hash::parseAnyReturningFormat(std::string_view original, std::optional optAlgo) { return parseAnyHelper(original, [&](std::optional optParsedAlgo) { // Either the string or user must provide the type, if they both do they diff --git a/src/libutil/include/nix/util/hash.hh b/src/libutil/include/nix/util/hash.hh index e4f596091091..199a9dd490a4 100644 --- a/src/libutil/include/nix/util/hash.hh +++ b/src/libutil/include/nix/util/hash.hh @@ -79,6 +79,12 @@ struct Hash */ static Hash parseAny(std::string_view s, std::optional optAlgo); + /** + * Like `parseAny`, but also returns the format the hash was parsed from. + */ + static std::pair + parseAnyReturningFormat(std::string_view s, std::optional optAlgo); + /** * Parse a hash from a string representation like the above, except the * type prefix is mandatory is there is no separate argument. diff --git a/src/nix/hash-convert.md b/src/nix/hash-convert.md index dfb2154436ef..dcebda74a3e4 100644 --- a/src/nix/hash-convert.md +++ b/src/nix/hash-convert.md @@ -27,7 +27,7 @@ R""( ```console # nix hash convert --hash-algo sha256 --from nix32 ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0= - error: input hash 'ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0=' does not have the expected format '--from nix32' + error: input hash 'ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0=' has format 'base64', but '--from nix32' was specified # nix hash convert --hash-algo sha256 --from nix32 1b8m03r63zqhnjf7l5wnldhh7c134ap5vpj0850ymkq1iyzicy5s sha256-ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0= diff --git a/src/nix/hash.cc b/src/nix/hash.cc index d3c9ccb66a79..2945c672c2cb 100644 --- a/src/nix/hash.cc +++ b/src/nix/hash.cc @@ -248,11 +248,13 @@ struct CmdHashConvert : Command void run() override { for (const auto & s : hashStrings) { - Hash h = from == HashFormat::SRI ? Hash::parseSRI(s) : Hash::parseAny(s, algo); - if (from && from != HashFormat::SRI - && h.to_string(*from, false) != (from == HashFormat::Base16 ? toLower(s) : s)) { - auto from_as_string = printHashFormat(*from); - throw BadHash("input hash '%s' does not have the expected format for '--from %s'", s, from_as_string); + auto [h, parsedFormat] = Hash::parseAnyReturningFormat(s, algo); + if (from && *from != parsedFormat) { + throw BadHash( + "input hash '%s' has format '%s', but '--from %s' was specified", + s, + printHashFormat(parsedFormat), + printHashFormat(*from)); } logger->cout(h.to_string(to, to == HashFormat::SRI)); } diff --git a/tests/functional/hash-convert.sh b/tests/functional/hash-convert.sh index 9ef4c189de40..be76179ca87a 100755 --- a/tests/functional/hash-convert.sh +++ b/tests/functional/hash-convert.sh @@ -93,10 +93,10 @@ try3() { # Asserting input format fails. # - expectStderr 1 nix hash convert --hash-algo "$1" --from sri "$2" | grepQuiet "is not SRI" - expectStderr 1 nix hash convert --hash-algo "$1" --from nix32 "$2" | grepQuiet "input hash" - expectStderr 1 nix hash convert --hash-algo "$1" --from base16 "$3" | grepQuiet "input hash" - expectStderr 1 nix hash convert --hash-algo "$1" --from nix32 "$4" | grepQuiet "input hash" + expectStderr 1 nix hash convert --hash-algo "$1" --from sri "$2" | grepQuiet "'base16', but '--from sri'" + expectStderr 1 nix hash convert --hash-algo "$1" --from nix32 "$2" | grepQuiet "'base16', but '--from nix32'" + expectStderr 1 nix hash convert --hash-algo "$1" --from base16 "$3" | grepQuiet "'nix32', but '--from base16'" + expectStderr 1 nix hash convert --hash-algo "$1" --from nix32 "$4" | grepQuiet "'base64', but '--from nix32'" # Base-16 hashes can be in uppercase. nix hash convert --hash-algo "$1" --from base16 "$(echo "$2" | tr '[:lower:]' '[:upper:]')" From 8571c186c05e31dbbcdd6763131ca4a162a51196 Mon Sep 17 00:00:00 2001 From: John Ericson Date: Thu, 4 Dec 2025 18:16:57 -0500 Subject: [PATCH 2/4] Use new `printHashFormatDisplay` in the description of `CmdToBase` --- src/libutil/hash.cc | 20 ++++++++++++++++++-- src/libutil/include/nix/util/hash.hh | 5 +++++ src/nix/hash.cc | 5 +---- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/libutil/hash.cc b/src/libutil/hash.cc index d051225a78fa..923173e0e080 100644 --- a/src/libutil/hash.cc +++ b/src/libutil/hash.cc @@ -447,9 +447,9 @@ HashFormat parseHashFormat(std::string_view hashFormatName) throw UsageError("unknown hash format '%1%', expect 'base16', 'base32', 'base64', or 'sri'", hashFormatName); } -std::string_view printHashFormat(HashFormat HashFormat) +std::string_view printHashFormat(HashFormat hashFormat) { - switch (HashFormat) { + switch (hashFormat) { case HashFormat::Base64: return "base64"; case HashFormat::Nix32: @@ -465,6 +465,22 @@ std::string_view printHashFormat(HashFormat HashFormat) } } +std::string_view printHashFormatDisplay(HashFormat hashFormat) +{ + switch (hashFormat) { + case HashFormat::Base64: + return "base-64"; + case HashFormat::Nix32: + return "Nix base-32"; + case HashFormat::Base16: + return "base-16"; + case HashFormat::SRI: + return "SRI"; + default: + assert(false); + } +} + std::optional parseHashAlgoOpt(std::string_view s, const ExperimentalFeatureSettings & xpSettings) { if (s == "blake3") { diff --git a/src/libutil/include/nix/util/hash.hh b/src/libutil/include/nix/util/hash.hh index 199a9dd490a4..e0884250c6ff 100644 --- a/src/libutil/include/nix/util/hash.hh +++ b/src/libutil/include/nix/util/hash.hh @@ -196,6 +196,11 @@ std::optional parseHashFormatOpt(std::string_view hashFormatName); */ std::string_view printHashFormat(HashFormat hashFormat); +/** + * User-friendly display of hash format (e.g., "base-64" instead of "base64"). + */ +std::string_view printHashFormatDisplay(HashFormat hashFormat); + /** * Parse a string representing a hash algorithm. */ diff --git a/src/nix/hash.cc b/src/nix/hash.cc index 2945c672c2cb..e2733dc9db68 100644 --- a/src/nix/hash.cc +++ b/src/nix/hash.cc @@ -191,10 +191,7 @@ struct CmdToBase : Command { return fmt( "convert a hash to %s representation (deprecated, use `nix hash convert` instead)", - hashFormat == HashFormat::Base16 ? "base-16" - : hashFormat == HashFormat::Nix32 ? "base-32" - : hashFormat == HashFormat::Base64 ? "base-64" - : "SRI"); + printHashFormat(hashFormat)); } void run() override From 4ef747af90c47920691f3916503e75f42aba974b Mon Sep 17 00:00:00 2001 From: John Ericson Date: Thu, 4 Dec 2025 18:19:40 -0500 Subject: [PATCH 3/4] Split out a new `Base` from `HashFormat` This allows for better separation of concerns, and will allow better modeling of some things. --- src/libcmd/misc-store-flags.cc | 2 +- src/libexpr/primops.cc | 2 +- src/libutil-tests/base-n.cc | 22 ++ src/libutil-tests/hash.cc | 2 +- src/libutil/base-n.cc | 92 ++++++++ src/libutil/hash.cc | 198 ++++++---------- src/libutil/include/nix/util/args.hh | 2 +- src/libutil/include/nix/util/base-n.hh | 60 +++++ src/libutil/include/nix/util/hash.hh | 65 ++++-- .../include/nix/util/variant-wrapper.hh | 14 +- src/nix/hash.cc | 16 +- .../lang/eval-okay-convertHash.err.exp | 216 +++++++++--------- 12 files changed, 422 insertions(+), 269 deletions(-) diff --git a/src/libcmd/misc-store-flags.cc b/src/libcmd/misc-store-flags.cc index fd22118136b6..2aa377be55b6 100644 --- a/src/libcmd/misc-store-flags.cc +++ b/src/libcmd/misc-store-flags.cc @@ -13,7 +13,7 @@ static void hashFormatCompleter(AddCompletions & completions, size_t index, std: Args::Flag hashFormatWithDefault(std::string && longName, HashFormat * hf) { - assert(*hf == nix::HashFormat::SRI); + assert(std::holds_alternative(hf->raw)); return Args::Flag{ .longName = std::move(longName), .description = "Hash format (`base16`, `nix32`, `base64`, `sri`). Default: `sri`.", diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index ea10d4c55b57..a035b722cc00 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -4596,7 +4596,7 @@ static void prim_convertHash(EvalState & state, const PosIdx pos, Value ** args, HashFormat hf = parseHashFormat( state.forceStringNoCtx(*iteratorToHashFormat->value, pos, "while evaluating the attribute 'toHashFormat'")); - v.mkString(Hash::parseAny(hash, ha).to_string(hf, hf == HashFormat::SRI), state.mem); + v.mkString(Hash::parseAny(hash, ha).to_string(hf, std::holds_alternative(hf.raw)), state.mem); } static RegisterPrimOp primop_convertHash({ diff --git a/src/libutil-tests/base-n.cc b/src/libutil-tests/base-n.cc index b3a845365075..ef2cb5e0aa9e 100644 --- a/src/libutil-tests/base-n.cc +++ b/src/libutil-tests/base-n.cc @@ -107,4 +107,26 @@ INSTANTIATE_TEST_SUITE_P( Base64TrailingParseCase{ "7g91TBvYoYQorRTqo+rYD/i5YnWvUBLnqDhPHxBJDaBW7smuPMeRp6E6JOFuVN9bzN0QnH1ToUU0u9c2CjALEQ== cheesecake"})); +/* ---------------------------------------------------------------------------- + * parseBase, parseBaseOpt, printBase + * --------------------------------------------------------------------------*/ + +TEST(base, testRoundTripPrintParse) +{ + for (const Base base : {Base::Base64, Base::Nix32, Base::Base16}) { + ASSERT_EQ(parseBase(printBase(base)), base); + ASSERT_EQ(*parseBaseOpt(printBase(base)), base); + } +} + +TEST(base, testParseBaseOptReturnsNullopt) +{ + ASSERT_EQ(parseBaseOpt("sha0042"), std::nullopt); +} + +TEST(base, testParseBaseThrowsOnInvalid) +{ + ASSERT_THROW(parseBase("invalid"), UsageError); +} + } // namespace nix diff --git a/src/libutil-tests/hash.cc b/src/libutil-tests/hash.cc index d1b63c640cab..ace83d8ff319 100644 --- a/src/libutil-tests/hash.cc +++ b/src/libutil-tests/hash.cc @@ -193,7 +193,7 @@ TEST(hashParseExplicitFormatUnprefixed, testKnownSHA256Hashes1_wrongBase) TEST(hashFormat, testRoundTripPrintParse) { - for (const HashFormat hashFormat : {HashFormat::Base64, HashFormat::Nix32, HashFormat::Base16, HashFormat::SRI}) { + for (const HashFormat hashFormat : {HashFormat{Base::Base64}, {Base::Nix32}, {Base::Base16}, {HashFormat::SRI}}) { ASSERT_EQ(parseHashFormat(printHashFormat(hashFormat)), hashFormat); ASSERT_EQ(*parseHashFormatOpt(printHashFormat(hashFormat)), hashFormat); } diff --git a/src/libutil/base-n.cc b/src/libutil/base-n.cc index 4c9726ad2e5e..47578dd3d819 100644 --- a/src/libutil/base-n.cc +++ b/src/libutil/base-n.cc @@ -1,13 +1,79 @@ #include #include "nix/util/array-from-string-literal.hh" +#include "nix/util/error.hh" +#include "nix/util/logging.hh" #include "nix/util/util.hh" #include "nix/util/base-n.hh" +#include "nix/util/base-nix-32.hh" using namespace std::literals; namespace nix { +std::optional parseBaseOpt(std::string_view s) +{ + if (s == "base16") + return Base::Base16; + if (s == "nix32") + return Base::Nix32; + if (s == "base32") { + warn(R"("base32" is a deprecated alias for base encoding "nix32".)"); + return Base::Nix32; + } + if (s == "base64") + return Base::Base64; + return std::nullopt; +} + +Base parseBase(std::string_view s) +{ + auto base = parseBaseOpt(s); + if (base) + return *base; + throw UsageError("unknown base encoding '%1%', expected 'base16', 'nix32', or 'base64'", s); +} + +std::string_view printBase(Base base) +{ + switch (base) { + case Base::Base16: + return "base16"; + case Base::Nix32: + return "nix32"; + case Base::Base64: + return "base64"; + } + unreachable(); +} + +std::string_view printBaseDisplay(Base base) +{ + switch (base) { + case Base::Base16: + return "base-16"; + case Base::Nix32: + return "Nix base-32"; + case Base::Base64: + return "base-64"; + } + unreachable(); +} + +std::optional baseFromEncodedSize(size_t encodedSize, size_t decodedSize) +{ + if (encodedSize == base16::encodedLength(decodedSize)) + return Base::Base16; + + if (encodedSize == BaseNix32::encodedLength(decodedSize)) + return Base::Nix32; + + if (encodedSize == base64::encodedLength(decodedSize)) + return Base::Base64; + + return std::nullopt; +} + constexpr static const std::array base16Chars = "0123456789abcdef"_arrayNoNull; std::string base16::encode(std::span b) @@ -111,4 +177,30 @@ std::string base64::decode(std::string_view s) return res; } +decltype(base16::encode) * encodeForBase(Base base) +{ + switch (base) { + case Base::Base16: + return base16::encode; + case Base::Nix32: + return BaseNix32::encode; + case Base::Base64: + return base64::encode; + } + unreachable(); +} + +decltype(base16::decode) * decodeForBase(Base base) +{ + switch (base) { + case Base::Base16: + return base16::decode; + case Base::Nix32: + return BaseNix32::decode; + case Base::Base64: + return base64::decode; + } + unreachable(); +} + } // namespace nix diff --git a/src/libutil/hash.cc b/src/libutil/hash.cc index 923173e0e080..5e76716a0439 100644 --- a/src/libutil/hash.cc +++ b/src/libutil/hash.cc @@ -61,103 +61,81 @@ std::strong_ordering Hash::operator<=>(const Hash & h) const noexcept return std::strong_ordering::equivalent; } +Base HashFormat::toBase() const +{ + return std::visit( + overloaded{ + [](Base base) { return base; }, + [](const HashFormatSRI &) { return Base::Base64; }, + }, + raw); +} + std::string Hash::to_string(HashFormat hashFormat, bool includeAlgo) const { std::string s; - if (hashFormat == HashFormat::SRI || includeAlgo) { - s += printHashAlgo(algo); - s += hashFormat == HashFormat::SRI ? '-' : ':'; - } + std::visit( + overloaded{ + [&](Base) { + if (includeAlgo) { + s += printHashAlgo(algo); + s += ':'; + } + }, + [&](const HashFormatSRI &) { + // SRI format always includes the algorithm + s += printHashAlgo(algo); + s += '-'; + }, + }, + hashFormat.raw); const auto bytes = std::as_bytes(std::span{&hash[0], hashSize}); - switch (hashFormat) { - case HashFormat::Base16: - assert(hashSize); - s += base16::encode(bytes); - break; - case HashFormat::Nix32: - assert(hashSize); - s += BaseNix32::encode(bytes); - break; - case HashFormat::Base64: - case HashFormat::SRI: - assert(hashSize); - s += base64::encode(bytes); - break; - } + assert(hashSize); + s += encodeForBase(hashFormat.toBase())(bytes); return s; } Hash Hash::dummy(HashAlgorithm::SHA256); -namespace { - -/// Private convenience -struct DecodeNamePair -{ - decltype(base16::decode) * decode; - std::string_view encodingName; -}; - -} // namespace - -static DecodeNamePair baseExplicit(HashFormat format) -{ - switch (format) { - case HashFormat::Base16: - return {base16::decode, "base16"}; - case HashFormat::Nix32: - return {BaseNix32::decode, "nix32"}; - case HashFormat::Base64: - return {base64::decode, "Base64"}; - case HashFormat::SRI: - break; - } - unreachable(); -} - /** - * Given the expected size of the message once decoded it, figure out - * which encoding we are using by looking at the size of the encoded - * message. + * Given the encoded string and hash algorithm, detect the base encoding. + * Throws BadHash if no encoding matches the expected size. */ -static HashFormat baseFromSize(std::string_view rest, HashAlgorithm algo) +static Base detectBase(std::string_view s, HashAlgorithm algo) { - auto hashSize = regularHashSize(algo); - - if (rest.size() == base16::encodedLength(hashSize)) - return HashFormat::Base16; - - if (rest.size() == BaseNix32::encodedLength(hashSize)) - return HashFormat::Nix32; - - if (rest.size() == base64::encodedLength(hashSize)) - return HashFormat::Base64; - - throw BadHash("hash '%s' has wrong length for hash algorithm '%s'", rest, printHashAlgo(algo)); + auto base = baseFromEncodedSize(s.size(), regularHashSize(algo)); + if (!base) + throw BadHash("hash '%s' has wrong length for hash algorithm '%s'", s, printHashAlgo(algo)); + return *base; } /** - * Given the exact decoding function, and a display name for in error - * messages. + * @param rest the string view to parse. Must *not* include any + * `(:|-)` prefix. * - * @param rest the string view to parse. Must not include any `(:|-)` prefix. + * @param format the hash format whose underlying base is to use for + * decoding. `HashFormat` not `Base` is used just for error messages. */ static Hash parseLowLevel( std::string_view rest, HashAlgorithm algo, - DecodeNamePair pair, + HashFormat format, const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings) { Hash res{algo, xpSettings}; std::string d; try { - d = pair.decode(rest); + d = decodeForBase(format.toBase())(rest); } catch (Error & e) { e.addTrace({}, "While decoding hash '%s'", rest); } if (d.size() != res.hashSize) throw BadHash( - "invalid %s hash '%s', length %d != expected length %d", pair.encodingName, rest, d.size(), res.hashSize); + "invalid %s hash '%s', length %d != expected length %d", + printHashFormatDisplay(format), + rest, + d.size(), + res.hashSize); assert(res.hashSize); memcpy(res.hash, d.data(), res.hashSize); @@ -174,7 +152,7 @@ Hash Hash::parseSRI(std::string_view original) throw BadHash("hash '%s' is not SRI", original); HashAlgorithm parsedType = parseHashAlgo(*hashRaw); - return parseLowLevel(rest, parsedType, {base64::decode, "SRI"}); + return parseLowLevel(rest, parsedType, HashFormat::SRI); } /** @@ -205,21 +183,15 @@ static std::pair parseAnyHelper(std::string_view rest, auto re HashAlgorithm algo = resolveAlgo(std::move(optParsedAlgo)); - auto [decode, formatName, format] = [&]() -> std::tuple { - if (isSRI) { - /* In the SRI case, we always are using Base64. If the - length is wrong, get an error later. */ - return {base64::decode, "SRI", HashFormat::SRI}; - } else { - /* Otherwise, decide via the length of the hash (for the - given algorithm) what base encoding it is. */ - auto format = baseFromSize(rest, algo); - auto [decode, formatName] = baseExplicit(format); - return {decode, formatName, format}; - } - }(); + auto format = isSRI + /* In the SRI case, we always are using Base64. If the + length is wrong, get an error later. */ + ? HashFormat::SRI + /* Otherwise, decide via the length of the hash (for the + given algorithm) what base encoding it is. */ + : HashFormat{detectBase(rest, algo)}; - return {parseLowLevel(rest, algo, {decode, formatName}), format}; + return {parseLowLevel(rest, algo, format), format}; } Hash Hash::parseAnyPrefixed(std::string_view original) @@ -259,13 +231,13 @@ Hash::parseAnyReturningFormat(std::string_view original, std::optional parseHashFormatOpt(std::string_view hashFormatName) { - if (hashFormatName == "base16") - return HashFormat::Base16; - if (hashFormatName == "nix32") - return HashFormat::Nix32; - if (hashFormatName == "base32") { - warn(R"("base32" is a deprecated alias for hash format "nix32".)"); - return HashFormat::Nix32; - } - if (hashFormatName == "base64") - return HashFormat::Base64; + if (auto base = parseBaseOpt(hashFormatName)) + return *base; if (hashFormatName == "sri") return HashFormat::SRI; return std::nullopt; @@ -449,36 +413,22 @@ HashFormat parseHashFormat(std::string_view hashFormatName) std::string_view printHashFormat(HashFormat hashFormat) { - switch (hashFormat) { - case HashFormat::Base64: - return "base64"; - case HashFormat::Nix32: - return "nix32"; - case HashFormat::Base16: - return "base16"; - case HashFormat::SRI: - return "sri"; - default: - // illegal hash base enum value internally, as opposed to external input - // which should be validated with nice error message. - assert(false); - } + return std::visit( + overloaded{ + [](Base base) -> std::string_view { return printBase(base); }, + [](const HashFormatSRI &) -> std::string_view { return "sri"; }, + }, + hashFormat.raw); } std::string_view printHashFormatDisplay(HashFormat hashFormat) { - switch (hashFormat) { - case HashFormat::Base64: - return "base-64"; - case HashFormat::Nix32: - return "Nix base-32"; - case HashFormat::Base16: - return "base-16"; - case HashFormat::SRI: - return "SRI"; - default: - assert(false); - } + return std::visit( + overloaded{ + [](Base base) -> std::string_view { return printBaseDisplay(base); }, + [](const HashFormatSRI &) -> std::string_view { return "SRI"; }, + }, + hashFormat.raw); } std::optional parseHashAlgoOpt(std::string_view s, const ExperimentalFeatureSettings & xpSettings) @@ -538,15 +488,15 @@ Hash adl_serializer::from_json(const json & json, const ExperimentalFeatur auto & obj = getObject(json); auto algo = parseHashAlgo(getString(valueAt(obj, "algorithm")), xpSettings); auto formatStr = getString(valueAt(obj, "format")); - auto format = parseHashFormat(formatStr); + auto base = parseBase(formatStr); // Only base16 format is supported for JSON serialization - if (format != HashFormat::Base16) { + if (base != Base::Base16) { throw Error("hash format '%s' is not supported in JSON; only 'base16' is currently supported", formatStr); } auto & hashS = getString(valueAt(obj, "hash")); - return Hash::parseExplicitFormatUnprefixed(hashS, algo, format, xpSettings); + return Hash::parseExplicitFormatUnprefixed(hashS, algo, base, xpSettings); } void adl_serializer::to_json(json & json, const Hash & hash) diff --git a/src/libutil/include/nix/util/args.hh b/src/libutil/include/nix/util/args.hh index d793411247b8..e871175aefee 100644 --- a/src/libutil/include/nix/util/args.hh +++ b/src/libutil/include/nix/util/args.hh @@ -16,7 +16,7 @@ namespace nix { enum struct HashAlgorithm : char; -enum struct HashFormat : int; +struct HashFormat; class MultiCommand; diff --git a/src/libutil/include/nix/util/base-n.hh b/src/libutil/include/nix/util/base-n.hh index 637a06f3fd33..9befc590b292 100644 --- a/src/libutil/include/nix/util/base-n.hh +++ b/src/libutil/include/nix/util/base-n.hh @@ -1,11 +1,61 @@ #pragma once ///@file +#include #include +#include #include namespace nix { +/** + * @brief Enumeration representing base-N encoding formats for binary data. + * + * These are pure encoding formats without any additional structure. + * For hash-specific formats that include algorithm information (like SRI), + * see `HashFormat`. + */ +enum struct Base : int { + /// @brief Base 64 encoding. + /// @see [IETF RFC 4648, section 4](https://datatracker.ietf.org/doc/html/rfc4648#section-4). + Base64, + /// @brief Nix-specific base-32 encoding. @see BaseNix32 + Nix32, + /// @brief Lowercase hexadecimal encoding. @see base16Chars + Base16, +}; + +/** + * Parse a string representing a base encoding format. + * @return std::nullopt if the string is not a valid base encoding name. + */ +std::optional parseBaseOpt(std::string_view s); + +/** + * Like parseBaseOpt but throws an error if the string is invalid. + */ +Base parseBase(std::string_view s); + +/** + * The reverse of parseBaseOpt. + * @return A string suitable for parsing back with parseBaseOpt. + */ +std::string_view printBase(Base base); + +/** + * User-friendly display of base encoding (e.g., "base-64" instead of "base64"). + */ +std::string_view printBaseDisplay(Base base); + +/** + * Given the expected size of the decoded data, figure out which base encoding + * is being used by looking at the size of the encoded string. + * @param encodedSize The size of the encoded string. + * @param decodedSize The expected size of the decoded data. + * @return std::nullopt if no base encoding matches the sizes. + */ +std::optional baseFromEncodedSize(size_t encodedSize, size_t decodedSize); + namespace base16 { /** @@ -50,4 +100,14 @@ std::string decode(std::string_view s); } // namespace base64 +/** + * Get the encoding function for the given base encoding. + */ +decltype(base16::encode) * encodeForBase(Base base); + +/** + * Get the decoding function for the given base encoding. + */ +decltype(base16::decode) * decodeForBase(Base base); + } // namespace nix diff --git a/src/libutil/include/nix/util/hash.hh b/src/libutil/include/nix/util/hash.hh index e0884250c6ff..9b8fe3a17760 100644 --- a/src/libutil/include/nix/util/hash.hh +++ b/src/libutil/include/nix/util/hash.hh @@ -1,11 +1,15 @@ #pragma once ///@file +#include "nix/util/base-n.hh" #include "nix/util/configuration.hh" #include "nix/util/types.hh" #include "nix/util/serialise.hh" #include "nix/util/file-system.hh" #include "nix/util/json-impls.hh" +#include "nix/util/variant-wrapper.hh" + +#include namespace nix { @@ -37,19 +41,43 @@ constexpr inline size_t regularHashSize(HashAlgorithm type) extern const StringSet hashAlgorithms; /** - * @brief Enumeration representing the hash formats. + * @brief Tag type for SRI (Subresource Integrity) hash format. + * + * SRI format is "-". + * @see W3C recommendation [Subresource Integrity](https://www.w3.org/TR/SRI/). */ -enum struct HashFormat : int { - /// @brief Base 64 encoding. - /// @see [IETF RFC 4648, section 4](https://datatracker.ietf.org/doc/html/rfc4648#section-4). - Base64, - /// @brief Nix-specific base-32 encoding. @see BaseNix32 - Nix32, - /// @brief Lowercase hexadecimal encoding. @see base16Chars - Base16, - /// @brief ":", format of the SRI integrity attribute. - /// @see W3C recommendation [Subresource Integrity](https://www.w3.org/TR/SRI/). - SRI +struct HashFormatSRI +{ + bool operator==(const HashFormatSRI &) const = default; + auto operator<=>(const HashFormatSRI &) const = default; +}; + +/** + * @brief Hash format: either a base encoding or SRI format. + * + * This is a variant that can hold either: + * - A `Base` value (Base16, Nix32, or Base64) for plain encoded hashes + * - An `SRI` tag for Subresource Integrity format ("-") + */ +struct HashFormat +{ + using Raw = std::variant; + Raw raw; + + MAKE_WRAPPER_CONSTRUCTOR(HashFormat); + + bool operator==(const HashFormat &) const = default; + auto operator<=>(const HashFormat &) const = default; + + /** + * Get the base encoding for this hash format. + * SRI format uses Base64. + */ + Base toBase() const; + + /// Backwards compatibility constants + using enum Base; + static constexpr struct HashFormatSRI SRI{}; }; extern const StringSet hashFormats; @@ -86,28 +114,25 @@ struct Hash parseAnyReturningFormat(std::string_view s, std::optional optAlgo); /** - * Parse a hash from a string representation like the above, except the - * type prefix is mandatory is there is no separate argument. + * Parse a plain hash that must not have any prefix indicating the type. + * The type is passed in to disambiguate. */ static Hash parseAnyPrefixed(std::string_view s); /** - * Parse a plain hash that musst not have any prefix indicating the type. - * The type is passed in to disambiguate. + * Parse a plain hash that must not have any prefix indicating the type. + * The algorithm is passed in; the base encoding is auto-detected from size. */ static Hash parseNonSRIUnprefixed(std::string_view s, HashAlgorithm algo); /** * Like `parseNonSRIUnprefixed`, but the hash format has been * explicitly given. - * - * @param explicitFormat cannot be SRI, but must be one of the - * "bases". */ static Hash parseExplicitFormatUnprefixed( std::string_view s, HashAlgorithm algo, - HashFormat explicitFormat, + Base explicitFormat, const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings); static Hash parseSRI(std::string_view original); diff --git a/src/libutil/include/nix/util/variant-wrapper.hh b/src/libutil/include/nix/util/variant-wrapper.hh index 146ae07b635f..14c9a37389cd 100644 --- a/src/libutil/include/nix/util/variant-wrapper.hh +++ b/src/libutil/include/nix/util/variant-wrapper.hh @@ -22,10 +22,12 @@ * * The moral equivalent of `using Raw::Raw;` */ -#define MAKE_WRAPPER_CONSTRUCTOR(CLASS_NAME) \ - FORCE_DEFAULT_CONSTRUCTORS(CLASS_NAME) \ - \ - CLASS_NAME(auto &&... arg) \ - : raw(std::forward(arg)...) \ - { \ +#define MAKE_WRAPPER_CONSTRUCTOR(CLASS_NAME) \ + FORCE_DEFAULT_CONSTRUCTORS(CLASS_NAME) \ + \ + template \ + requires(!(sizeof...(Args) == 1 && (std::is_same_v, CLASS_NAME> && ...))) \ + CLASS_NAME(Args &&... arg) \ + : raw(std::forward(arg)...) \ + { \ } diff --git a/src/nix/hash.cc b/src/nix/hash.cc index e2733dc9db68..7e5939a8c923 100644 --- a/src/nix/hash.cc +++ b/src/nix/hash.cc @@ -37,25 +37,25 @@ struct CmdHashBase : Command addFlag({ .longName = "sri", .description = "Print the hash in SRI format.", - .handler = {&hashFormat, HashFormat::SRI}, + .handler = {&hashFormat, HashFormat{HashFormat::SRI}}, }); addFlag({ .longName = "base64", .description = "Print the hash in base-64 format.", - .handler = {&hashFormat, HashFormat::Base64}, + .handler = {&hashFormat, HashFormat{HashFormat::Base64}}, }); addFlag({ .longName = "base32", .description = "Print the hash in base-32 (Nix-specific) format.", - .handler = {&hashFormat, HashFormat::Nix32}, + .handler = {&hashFormat, HashFormat{HashFormat::Nix32}}, }); addFlag({ .longName = "base16", .description = "Print the hash in base-16 format.", - .handler = {&hashFormat, HashFormat::Base16}, + .handler = {&hashFormat, HashFormat{HashFormat::Base16}}, }); addFlag(flag::hashAlgo("type", &hashAlgo)); @@ -129,7 +129,7 @@ struct CmdHashBase : Command if (truncate && h.hashSize > 20) h = compressHash(h, 20); - logger->cout(h.to_string(hashFormat, hashFormat == HashFormat::SRI)); + logger->cout(h.to_string(hashFormat, std::holds_alternative(hashFormat.raw))); } } }; @@ -199,7 +199,9 @@ struct CmdToBase : Command if (!legacyCli) warn("The old format conversion subcommands of `nix hash` were deprecated in favor of `nix hash convert`."); for (const auto & s : args) - logger->cout(Hash::parseAny(s, hashAlgo).to_string(hashFormat, hashFormat == HashFormat::SRI)); + logger->cout( + Hash::parseAny(s, hashAlgo) + .to_string(hashFormat, std::holds_alternative(hashFormat.raw))); } }; @@ -253,7 +255,7 @@ struct CmdHashConvert : Command printHashFormat(parsedFormat), printHashFormat(*from)); } - logger->cout(h.to_string(to, to == HashFormat::SRI)); + logger->cout(h.to_string(to, std::holds_alternative(to.raw))); } } }; diff --git a/tests/functional/lang/eval-okay-convertHash.err.exp b/tests/functional/lang/eval-okay-convertHash.err.exp index 41d7467252b0..ef8b61fb4978 100644 --- a/tests/functional/lang/eval-okay-convertHash.err.exp +++ b/tests/functional/lang/eval-okay-convertHash.err.exp @@ -1,108 +1,108 @@ -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". -warning: "base32" is a deprecated alias for hash format "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". +warning: "base32" is a deprecated alias for base encoding "nix32". From f3921e3203ea5e435d1691e45d6390f99291d2bb Mon Sep 17 00:00:00 2001 From: John Ericson Date: Sat, 6 Dec 2025 05:10:10 -0500 Subject: [PATCH 4/4] Replace function pointers for base encode/decode with virtual methods --- src/libutil/base-n.cc | 62 ++++++++++++++++++++------ src/libutil/hash.cc | 4 +- src/libutil/include/nix/util/base-n.hh | 21 +++++++-- 3 files changed, 67 insertions(+), 20 deletions(-) diff --git a/src/libutil/base-n.cc b/src/libutil/base-n.cc index 47578dd3d819..86f097918d67 100644 --- a/src/libutil/base-n.cc +++ b/src/libutil/base-n.cc @@ -177,28 +177,62 @@ std::string base64::decode(std::string_view s) return res; } -decltype(base16::encode) * encodeForBase(Base base) +namespace { + +struct Base16Encoding final : BaseEncoding { - switch (base) { - case Base::Base16: - return base16::encode; - case Base::Nix32: - return BaseNix32::encode; - case Base::Base64: - return base64::encode; + std::string encode(std::span data) const override + { + return base16::encode(data); } - unreachable(); -} -decltype(base16::decode) * decodeForBase(Base base) + std::string decode(std::string_view s) const override + { + return base16::decode(s); + } +}; + +struct Nix32Encoding final : BaseEncoding +{ + std::string encode(std::span data) const override + { + return BaseNix32::encode(data); + } + + std::string decode(std::string_view s) const override + { + return BaseNix32::decode(s); + } +}; + +struct Base64Encoding final : BaseEncoding +{ + std::string encode(std::span data) const override + { + return base64::encode(data); + } + + std::string decode(std::string_view s) const override + { + return base64::decode(s); + } +}; + +const Base16Encoding base16Encoding; +const Nix32Encoding nix32Encoding; +const Base64Encoding base64Encoding; + +} // anonymous namespace + +const BaseEncoding & getBaseEncoding(Base base) { switch (base) { case Base::Base16: - return base16::decode; + return base16Encoding; case Base::Nix32: - return BaseNix32::decode; + return nix32Encoding; case Base::Base64: - return base64::decode; + return base64Encoding; } unreachable(); } diff --git a/src/libutil/hash.cc b/src/libutil/hash.cc index 5e76716a0439..77fe4cf37ff1 100644 --- a/src/libutil/hash.cc +++ b/src/libutil/hash.cc @@ -91,7 +91,7 @@ std::string Hash::to_string(HashFormat hashFormat, bool includeAlgo) const hashFormat.raw); const auto bytes = std::as_bytes(std::span{&hash[0], hashSize}); assert(hashSize); - s += encodeForBase(hashFormat.toBase())(bytes); + s += getBaseEncoding(hashFormat.toBase()).encode(bytes); return s; } @@ -125,7 +125,7 @@ static Hash parseLowLevel( Hash res{algo, xpSettings}; std::string d; try { - d = decodeForBase(format.toBase())(rest); + d = getBaseEncoding(format.toBase()).decode(rest); } catch (Error & e) { e.addTrace({}, "While decoding hash '%s'", rest); } diff --git a/src/libutil/include/nix/util/base-n.hh b/src/libutil/include/nix/util/base-n.hh index 9befc590b292..8942950825ad 100644 --- a/src/libutil/include/nix/util/base-n.hh +++ b/src/libutil/include/nix/util/base-n.hh @@ -101,13 +101,26 @@ std::string decode(std::string_view s); } // namespace base64 /** - * Get the encoding function for the given base encoding. + * Abstract interface for base-N encoding/decoding operations. */ -decltype(base16::encode) * encodeForBase(Base base); +struct BaseEncoding +{ + virtual ~BaseEncoding() = default; + + /** + * Encode arbitrary bytes to a string. + */ + virtual std::string encode(std::span data) const = 0; + + /** + * Decode a string to bytes. + */ + virtual std::string decode(std::string_view s) const = 0; +}; /** - * Get the decoding function for the given base encoding. + * Get the encoding/decoding functions for the given base encoding. */ -decltype(base16::decode) * decodeForBase(Base base); +const BaseEncoding & getBaseEncoding(Base base); } // namespace nix