1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-08 19:46:02 +01:00

Merge pull request #13685 from obsidiansystems/hash-sentinal-encapsulation

Encapsulate `invalidBase32`, avoid 0xFF magic number
This commit is contained in:
John Ericson 2025-08-04 16:01:16 -04:00 committed by GitHub
commit 0889960869
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 101 additions and 38 deletions

View file

@ -1,5 +1,6 @@
#include "nix/util/references.hh" #include "nix/util/references.hh"
#include "nix/store/path.hh" #include "nix/store/path.hh"
#include "nix/util/base-nix-32.hh"
#include <benchmark/benchmark.h> #include <benchmark/benchmark.h>
@ -10,9 +11,9 @@ using namespace nix;
template<typename OIt> template<typename OIt>
static void randomReference(std::mt19937 & urng, OIt outIter) static void randomReference(std::mt19937 & urng, OIt outIter)
{ {
auto dist = std::uniform_int_distribution<std::size_t>(0, nix32Chars.size() - 1); auto dist = std::uniform_int_distribution<std::size_t>(0, BaseNix32::characters.size() - 1);
dist(urng); dist(urng);
std::generate_n(outIter, StorePath::HashLen, [&]() { return nix32Chars[dist(urng)]; }); std::generate_n(outIter, StorePath::HashLen, [&]() { return BaseNix32::characters[dist(urng)]; });
} }
/** /**

View file

@ -0,0 +1,42 @@
#include <cassert>
#include "nix/util/base-nix-32.hh"
namespace nix {
constexpr const std::array<unsigned char, 256> BaseNix32::reverseMap = [] {
std::array<unsigned char, 256> map{};
for (size_t i = 0; i < map.size(); ++i)
map[i] = invalid; // invalid
for (unsigned char i = 0; i < 32; ++i)
map[static_cast<unsigned char>(characters[i])] = i;
return map;
}();
std::string BaseNix32::encode(std::span<const uint8_t> originalData)
{
if (originalData.size() == 0)
return {};
size_t len = encodedLength(originalData.size());
assert(len);
std::string s;
s.reserve(len);
for (int n = (int) len - 1; n >= 0; n--) {
unsigned int b = n * 5;
unsigned int i = b / 8;
unsigned int j = b % 8;
unsigned char c =
(originalData.data()[i] >> j) | (i >= originalData.size() - 1 ? 0 : originalData.data()[i + 1] << (8 - j));
s.push_back(characters[c & 0x1f]);
}
return s;
}
} // namespace nix

View file

@ -11,6 +11,7 @@
#include "nix/util/archive.hh" #include "nix/util/archive.hh"
#include "nix/util/configuration.hh" #include "nix/util/configuration.hh"
#include "nix/util/split.hh" #include "nix/util/split.hh"
#include "nix/util/base-nix-32.hh"
#include <sys/types.h> #include <sys/types.h>
#include <sys/stat.h> #include <sys/stat.h>
@ -71,39 +72,10 @@ static std::string printHash16(const Hash & hash)
return buf; return buf;
} }
// omitted: E O U T
constexpr char nix32Chars[] = "0123456789abcdfghijklmnpqrsvwxyz";
constexpr const std::array<unsigned char, 256> reverseNix32Map = [] {
std::array<unsigned char, 256> map{};
for (size_t i = 0; i < map.size(); ++i)
map[i] = 0xFF; // invalid
for (unsigned char i = 0; i < 32; ++i)
map[static_cast<unsigned char>(nix32Chars[i])] = i;
return map;
}();
static std::string printHash32(const Hash & hash) static std::string printHash32(const Hash & hash)
{ {
assert(hash.hashSize); assert(hash.hashSize);
size_t len = hash.base32Len(); return BaseNix32::encode({&hash.hash[0], hash.hashSize});
assert(len);
std::string s;
s.reserve(len);
for (int n = (int) len - 1; n >= 0; n--) {
unsigned int b = n * 5;
unsigned int i = b / 8;
unsigned int j = b % 8;
unsigned char c = (hash.hash[i] >> j) | (i >= hash.hashSize - 1 ? 0 : hash.hash[i + 1] << (8 - j));
s.push_back(nix32Chars[c & 0x1f]);
}
return s;
} }
std::string printHash16or32(const Hash & hash) std::string printHash16or32(const Hash & hash)
@ -229,11 +201,13 @@ Hash::Hash(std::string_view rest, HashAlgorithm algo, bool isSRI)
for (unsigned int n = 0; n < rest.size(); ++n) { for (unsigned int n = 0; n < rest.size(); ++n) {
char c = rest[rest.size() - n - 1]; char c = rest[rest.size() - n - 1];
unsigned char digit = reverseNix32Map[static_cast<unsigned char>(c)]; auto digit_opt = BaseNix32::lookupReverse(c);
if (digit == 0xFF) if (!digit_opt)
throw BadHash("invalid base-32 hash: '%s'", rest); throw BadHash("invalid base-32 hash: '%s'", rest);
uint8_t digit = std::move(*digit_opt);
unsigned int b = n * 5; unsigned int b = n * 5;
unsigned int i = b / 8; unsigned int i = b / 8;
unsigned int j = b % 8; unsigned int j = b % 8;

View file

@ -0,0 +1,45 @@
#pragma once
///@file
#include <array>
#include <cstdint>
#include <optional>
#include <string>
#include <span>
namespace nix {
struct BaseNix32
{
/// omitted: E O U T
constexpr static std::array<char, 32> characters = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a',
'b', 'c', 'd', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'p', 'q', 'r', 's', 'v', 'w', 'x', 'y', 'z'};
private:
static const std::array<uint8_t, 256> reverseMap;
const static constexpr uint8_t invalid = 0xFF;
public:
static inline std::optional<uint8_t> lookupReverse(char base32)
{
uint8_t digit = reverseMap[static_cast<unsigned char>(base32)];
if (digit == invalid)
return std::nullopt;
else
return digit;
}
/**
* Returns the length of a base-32 representation of this hash.
*/
static size_t encodedLength(size_t originalLength)
{
return (originalLength * 8 - 1) / 5 + 1;
}
static std::string encode(std::span<const uint8_t> originalData);
};
} // namespace nix

View file

@ -35,8 +35,6 @@ constexpr inline size_t regularHashSize(HashAlgorithm type)
extern const StringSet hashAlgorithms; extern const StringSet hashAlgorithms;
extern const std::array<unsigned char, 256> reverseNix32Map;
/** /**
* @brief Enumeration representing the hash formats. * @brief Enumeration representing the hash formats.
*/ */
@ -44,7 +42,7 @@ enum struct HashFormat : int {
/// @brief Base 64 encoding. /// @brief Base 64 encoding.
/// @see [IETF RFC 4648, section 4](https://datatracker.ietf.org/doc/html/rfc4648#section-4). /// @see [IETF RFC 4648, section 4](https://datatracker.ietf.org/doc/html/rfc4648#section-4).
Base64, Base64,
/// @brief Nix-specific base-32 encoding. @see nix32Chars /// @brief Nix-specific base-32 encoding. @see BaseNix32
Nix32, Nix32,
/// @brief Lowercase hexadecimal encoding. @see base16Chars /// @brief Lowercase hexadecimal encoding. @see base16Chars
Base16, Base16,

View file

@ -8,6 +8,7 @@ headers = files(
'archive.hh', 'archive.hh',
'args.hh', 'args.hh',
'args/root.hh', 'args/root.hh',
'base-nix-32.hh',
'callback.hh', 'callback.hh',
'canon-path.hh', 'canon-path.hh',
'checked-arithmetic.hh', 'checked-arithmetic.hh',

View file

@ -112,6 +112,7 @@ subdir('nix-meson-build-support/common')
sources = [config_priv_h] + files( sources = [config_priv_h] + files(
'archive.cc', 'archive.cc',
'args.cc', 'args.cc',
'base-nix-32.cc',
'canon-path.cc', 'canon-path.cc',
'compression.cc', 'compression.cc',
'compute-levels.cc', 'compute-levels.cc',

View file

@ -1,6 +1,7 @@
#include "nix/util/references.hh" #include "nix/util/references.hh"
#include "nix/util/hash.hh" #include "nix/util/hash.hh"
#include "nix/util/archive.hh" #include "nix/util/archive.hh"
#include "nix/util/base-nix-32.hh"
#include <map> #include <map>
#include <cstdlib> #include <cstdlib>
@ -17,7 +18,7 @@ static void search(std::string_view s, StringSet & hashes, StringSet & seen)
int j; int j;
bool match = true; bool match = true;
for (j = refLength - 1; j >= 0; --j) for (j = refLength - 1; j >= 0; --j)
if (reverseNix32Map[(unsigned char) s[i + j]] == 0xFF) { if (!BaseNix32::lookupReverse(s[i + j])) {
i += j + 1; i += j + 1;
match = false; match = false;
break; break;