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/store/path.hh"
#include "nix/util/base-nix-32.hh"
#include <benchmark/benchmark.h>
@ -10,9 +11,9 @@ using namespace nix;
template<typename OIt>
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);
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/configuration.hh"
#include "nix/util/split.hh"
#include "nix/util/base-nix-32.hh"
#include <sys/types.h>
#include <sys/stat.h>
@ -71,39 +72,10 @@ static std::string printHash16(const Hash & hash)
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)
{
assert(hash.hashSize);
size_t len = hash.base32Len();
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;
return BaseNix32::encode({&hash.hash[0], hash.hashSize});
}
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) {
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);
uint8_t digit = std::move(*digit_opt);
unsigned int b = n * 5;
unsigned int i = 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 std::array<unsigned char, 256> reverseNix32Map;
/**
* @brief Enumeration representing the hash formats.
*/
@ -44,7 +42,7 @@ 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 nix32Chars
/// @brief Nix-specific base-32 encoding. @see BaseNix32
Nix32,
/// @brief Lowercase hexadecimal encoding. @see base16Chars
Base16,

View file

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

View file

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

View file

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