(#7757) [vcpkg] Switch to internal hash algorithms 📜

On non-Windows platforms, there is no standard way to get the hash of an
item -- before this PR, what we did was check for the existence of a few
common utility names (shasum, sha1, sha256, sha512), and then call that
utility on a file we created containing the contents we wish to hash.
This PR adds internal hashers for sha1, sha256, and sha512, and
standardizes the interface to allow anyone to implement hashers in the
future.

These hashers are not extremely optimized, so it's likely that in the
future we could get more optimized, but for now we just call out to
BCryptHasher on Windows, since it's standard and easy to use (and about
2x faster for sha1 and sha256, and 1.5x faster for sha512). However,
they are reasonably fast for being unoptimized. I attempted a few minor
optimizations, which actually made the code slower! So as of right now,
it's implemented as just a basic conversion of the code on Wikipedia to
C++. I have tested these on the standard NIST test vectors (and those
test vectors are located in vcpkg-test/hash.cpp).
This commit is contained in:
Nicole Mazzuca 2019-08-08 16:14:59 -07:00 committed by nicole mazzuca
parent e417ff69b7
commit 7827239593
10 changed files with 1062 additions and 199 deletions

View File

@ -6,6 +6,46 @@
namespace vcpkg::Hash
{
std::string get_string_hash(const std::string& s, const std::string& hash_type);
std::string get_file_hash(const Files::Filesystem& fs, const fs::path& path, const std::string& hash_type);
enum class Algorithm
{
Sha1,
Sha256,
Sha512,
};
const char* to_string(Algorithm algo) noexcept;
Optional<Algorithm> algorithm_from_string(StringView sv) noexcept;
struct Hasher
{
virtual void add_bytes(const void* start, const void* end) noexcept = 0;
// one may only call this once before calling `clear()` or the dtor
virtual std::string get_hash() noexcept = 0;
virtual void clear() noexcept = 0;
virtual ~Hasher() = default;
};
std::unique_ptr<Hasher> get_hasher_for(Algorithm algo) noexcept;
std::string get_bytes_hash(const void* first, const void* last, Algorithm algo) noexcept;
std::string get_string_hash(StringView s, Algorithm algo) noexcept;
std::string get_file_hash(const Files::Filesystem& fs,
const fs::path& path,
Algorithm algo,
std::error_code& ec) noexcept;
inline std::string get_file_hash(LineInfo li,
const Files::Filesystem& fs,
const fs::path& path,
Algorithm algo) noexcept
{
std::error_code ec;
const auto result = get_file_hash(fs, path, algo, ec);
if (ec)
{
Checks::exit_with_message(li, "Failure to read file for hashing: %s", ec.message());
}
return result;
}
}

View File

@ -12,15 +12,25 @@
namespace vcpkg::Strings::details
{
template<class T>
auto to_printf_arg(const T& t) -> decltype(t.to_string())
auto to_string(const T& t) -> decltype(t.to_string())
{
return t.to_string();
}
// first looks up to_string on `T` using ADL; then, if that isn't found,
// uses the above definition which returns t.to_string()
template<class T, class = std::enable_if_t<!std::is_arithmetic<T>::value>>
auto to_printf_arg(const T& t) -> decltype(to_string(t))
{
return to_string(t);
}
inline const char* to_printf_arg(const std::string& s) { return s.c_str(); }
inline const char* to_printf_arg(const char* s) { return s; }
inline const wchar_t* to_printf_arg(const wchar_t* s) { return s; }
template<class T, class = std::enable_if_t<std::is_arithmetic<T>::value>>
T to_printf_arg(T s)
{

View File

@ -40,10 +40,11 @@ namespace vcpkg
std::string to_string() const;
void to_string(std::string& out) const;
bool operator==(StringView other) const;
private:
const char* m_ptr = 0;
size_t m_size = 0;
};
bool operator==(StringView lhs, StringView rhs) noexcept;
bool operator!=(StringView lhs, StringView rhs) noexcept;
}

View File

@ -0,0 +1,276 @@
#include <catch2/catch.hpp>
#include <vcpkg/base/hash.h>
#include <algorithm>
#include <iostream>
#include <iterator>
#include <map>
namespace Hash = vcpkg::Hash;
using vcpkg::StringView;
// Require algorithm: Hash::Algorithm::Tag to be in scope
#define CHECK_HASH(size, value, real_hash) \
do \
{ \
unsigned char data[size]; \
std::fill(std::begin(data), std::end(data), static_cast<unsigned char>(value)); \
const auto hash = Hash::get_bytes_hash(data, data + size, algorithm); \
REQUIRE(hash == real_hash); \
} while (0)
#define CHECK_HASH_OF(data, real_hash) \
do \
{ \
const auto hash = Hash::get_bytes_hash(std::begin(data), std::end(data), algorithm); \
REQUIRE(hash == real_hash); \
} while (0)
#define CHECK_HASH_STRING(data, real_hash) \
do \
{ \
const auto hash = Hash::get_string_hash(data, algorithm); \
REQUIRE(hash == real_hash); \
} while (0)
// Requires hasher: std::unique_ptr<Hash::Hasher> to be in scope
#define CHECK_HASH_LARGE(size, value, real_hash) \
do \
{ \
hasher->clear(); \
std::uint64_t remaining = size; \
unsigned char buffer[512]; \
std::fill(std::begin(buffer), std::end(buffer), static_cast<unsigned char>(value)); \
while (remaining) \
{ \
if (remaining < 512) \
{ \
hasher->add_bytes(std::begin(buffer), std::begin(buffer) + remaining); \
remaining = 0; \
} \
else \
{ \
hasher->add_bytes(std::begin(buffer), std::end(buffer)); \
remaining -= 512; \
} \
} \
REQUIRE(hasher->get_hash() == real_hash); \
} while (0)
TEST_CASE ("SHA1: basic tests", "[hash][sha1]")
{
const auto algorithm = Hash::Algorithm::Sha1;
CHECK_HASH_STRING("", "da39a3ee5e6b4b0d3255bfef95601890afd80709");
CHECK_HASH_STRING(";", "2d14ab97cc3dc294c51c0d6814f4ea45f4b4e312");
CHECK_HASH_STRING("asdifasdfnas", "b77eb8a1b4c2ef6716d7d302647e4511b1a638a6");
CHECK_HASH_STRING("asdfanvoinaoifawenflawenfiwnofvnasfjvnaslkdfjlkasjdfanm,"
"werflawoienfowanevoinwai32910u2740918741o;j;wejfqwioaher9283hrpf;asd",
"c69bcd30c196c7050906d212722dd7a7659aad04");
}
TEST_CASE ("SHA1: NIST test cases (small)", "[hash][sha1]")
{
const auto algorithm = Hash::Algorithm::Sha1;
CHECK_HASH_STRING("abc", "a9993e364706816aba3e25717850c26c9cd0d89d");
CHECK_HASH_STRING("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
"84983e441c3bd26ebaae4aa1f95129e5e54670f1");
}
TEST_CASE ("SHA256: basic tests", "[hash][sha256]")
{
const auto algorithm = Hash::Algorithm::Sha256;
CHECK_HASH_STRING("", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
CHECK_HASH_STRING(";", "41b805ea7ac014e23556e98bb374702a08344268f92489a02f0880849394a1e4");
CHECK_HASH_STRING("asdifasdfnas", "2bb1fb910831fdc11d5a3996425a84ace27aeb81c9c20ace9f60ac1b3218b291");
CHECK_HASH_STRING("asdfanvoinaoifawenflawenfiwnofvnasfjvnaslkdfjlkasjdfanm,"
"werflawoienfowanevoinwai32910u2740918741o;j;wejfqwioaher9283hrpf;asd",
"10c98034b424d4e40ca933bc524ea38b4e53290d76e8b38edc4ea2fec7f529aa");
}
TEST_CASE ("SHA256: NIST test cases (small)", "[hash][sha256]")
{
const auto algorithm = Hash::Algorithm::Sha256;
CHECK_HASH(1, 0xbd, "68325720aabd7c82f30f554b313d0570c95accbb7dc4b5aae11204c08ffe732b");
{
const unsigned char data[] = {0xc9, 0x8c, 0x8e, 0x55};
CHECK_HASH_OF(data, "7abc22c0ae5af26ce93dbb94433a0e0b2e119d014f8e7f65bd56c61ccccd9504");
}
CHECK_HASH(55, 0, "02779466cdec163811d078815c633f21901413081449002f24aa3e80f0b88ef7");
CHECK_HASH(56, 0, "d4817aa5497628e7c77e6b606107042bbba3130888c5f47a375e6179be789fbb");
CHECK_HASH(57, 0, "65a16cb7861335d5ace3c60718b5052e44660726da4cd13bb745381b235a1785");
CHECK_HASH(64, 0, "f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b");
CHECK_HASH(1000, 0, "541b3e9daa09b20bf85fa273e5cbd3e80185aa4ec298e765db87742b70138a53");
CHECK_HASH(1000, 'A', "c2e686823489ced2017f6059b8b239318b6364f6dcd835d0a519105a1eadd6e4");
CHECK_HASH(1005, 'U', "f4d62ddec0f3dd90ea1380fa16a5ff8dc4c54b21740650f24afc4120903552b0");
}
TEST_CASE ("SHA512: NIST test cases (small)", "[hash][sha512]")
{
const auto algorithm = Hash::Algorithm::Sha512;
CHECK_HASH_STRING("",
"cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f"
"63b931bd47417a81a538327af927da3e");
CHECK_HASH(111,
0,
"77ddd3a542e530fd047b8977c657ba6ce72f1492e360b2b2212cd264e75ec03882e4ff0525517ab4207d14c70c2259ba88d4d33"
"5ee0e7e20543d22102ab1788c");
CHECK_HASH(112,
0,
"2be2e788c8a8adeaa9c89a7f78904cacea6e39297d75e0573a73c756234534d6627ab4156b48a6657b29ab8beb73334040ad39e"
"ad81446bb09c70704ec707952");
CHECK_HASH(113,
0,
"0e67910bcf0f9ccde5464c63b9c850a12a759227d16b040d98986d54253f9f34322318e56b8feb86c5fb2270ed87f31252f7f68"
"493ee759743909bd75e4bb544");
CHECK_HASH(122,
0,
"4f3f095d015be4a7a7cc0b8c04da4aa09e74351e3a97651f744c23716ebd9b3e822e5077a01baa5cc0ed45b9249e88ab343d433"
"3539df21ed229da6f4a514e0f");
CHECK_HASH(1000,
0,
"ca3dff61bb23477aa6087b27508264a6f9126ee3a004f53cb8db942ed345f2f2d229b4b59c859220a1cf1913f34248e3803bab6"
"50e849a3d9a709edc09ae4a76");
CHECK_HASH(1000,
'A',
"329c52ac62d1fe731151f2b895a00475445ef74f50b979c6f7bb7cae349328c1d4cb4f7261a0ab43f936a24b000651d4a824fcd"
"d577f211aef8f806b16afe8af");
CHECK_HASH(1005,
'U',
"59f5e54fe299c6a8764c6b199e44924a37f59e2b56c3ebad939b7289210dc8e4c21b9720165b0f4d4374c90f1bf4fb4a5ace17a"
"1161798015052893a48c3d161");
}
TEST_CASE ("SHA256: NIST test cases (large)", "[.][hash-expensive][sha256-expensive]")
{
auto hasher = Hash::get_hasher_for(Hash::Algorithm::Sha256);
CHECK_HASH_LARGE(1'000'000, 0, "d29751f2649b32ff572b5e0a9f541ea660a50f94ff0beedfb0b692b924cc8025");
CHECK_HASH_LARGE(0x2000'0000, 'Z', "15a1868c12cc53951e182344277447cd0979536badcc512ad24c67e9b2d4f3dd");
CHECK_HASH_LARGE(0x4100'0000, 0, "461c19a93bd4344f9215f5ec64357090342bc66b15a148317d276e31cbc20b53");
CHECK_HASH_LARGE(0x6000'003E, 'B', "c23ce8a7895f4b21ec0daf37920ac0a262a220045a03eb2dfed48ef9b05aabea");
}
TEST_CASE ("SHA512: NIST test cases (large)", "[.][hash-expensive][sha512-expensive]")
{
auto hasher = Hash::get_hasher_for(Hash::Algorithm::Sha512);
CHECK_HASH_LARGE(1'000'000,
0,
"ce044bc9fd43269d5bbc946cbebc3bb711341115cc4abdf2edbc3ff2c57ad4b15deb699bda257fea5aef9c6e55fcf4cf9"
"dc25a8c3ce25f2efe90908379bff7ed");
CHECK_HASH_LARGE(0x2000'0000,
'Z',
"da172279f3ebbda95f6b6e1e5f0ebec682c25d3d93561a1624c2fa9009d64c7e9923f3b46bcaf11d39a531f43297992ba"
"4155c7e827bd0f1e194ae7ed6de4cac");
CHECK_HASH_LARGE(0x4100'0000,
0,
"14b1be901cb43549b4d831e61e5f9df1c791c85b50e85f9d6bc64135804ad43ce8402750edbe4e5c0fc170b99cf78b9f4"
"ecb9c7e02a157911d1bd1832d76784f");
CHECK_HASH_LARGE(0x6000'003E,
'B',
"fd05e13eb771f05190bd97d62647157ea8f1f6949a52bb6daaedbad5f578ec59b1b8d6c4a7ecb2feca6892b4dc1387716"
"70a0f3bd577eea326aed40ab7dd58b1");
}
#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)
using Catch::Benchmark::Chronometer;
void benchmark_hasher(Chronometer& meter, Hash::Hasher& hasher, std::uint64_t size, unsigned char byte) noexcept
{
unsigned char buffer[1024];
std::fill(std::begin(buffer), std::end(buffer), byte);
meter.measure([&] {
hasher.clear();
std::uint64_t remaining = size;
while (remaining)
{
if (remaining < 512)
{
hasher.add_bytes(std::begin(buffer), std::begin(buffer) + remaining);
remaining = 0;
}
else
{
hasher.add_bytes(std::begin(buffer), std::end(buffer));
remaining -= 512;
}
}
hasher.get_hash();
});
}
TEST_CASE ("SHA1: benchmark", "[.][hash][sha256][!benchmark]")
{
using Catch::Benchmark::Chronometer;
auto hasher = Hash::get_hasher_for(Hash::Algorithm::Sha1);
BENCHMARK_ADVANCED("0 x 1'000'000")(Catch::Benchmark::Chronometer meter)
{
benchmark_hasher(meter, *hasher, 1'000'000, 0);
};
BENCHMARK_ADVANCED("'Z' x 0x2000'0000")(Catch::Benchmark::Chronometer meter)
{
benchmark_hasher(meter, *hasher, 0x2000'0000, 'Z');
};
BENCHMARK_ADVANCED("0 x 0x4100'0000")(Catch::Benchmark::Chronometer meter)
{
benchmark_hasher(meter, *hasher, 0x4100'0000, 0);
};
BENCHMARK_ADVANCED("'B' x 0x6000'003E")(Catch::Benchmark::Chronometer meter)
{
benchmark_hasher(meter, *hasher, 0x6000'003E, 'B');
};
}
TEST_CASE ("SHA256: benchmark", "[.][hash][sha256][!benchmark]")
{
using Catch::Benchmark::Chronometer;
auto hasher = Hash::get_hasher_for(Hash::Algorithm::Sha256);
BENCHMARK_ADVANCED("0 x 1'000'000")(Catch::Benchmark::Chronometer meter)
{
benchmark_hasher(meter, *hasher, 1'000'000, 0);
};
BENCHMARK_ADVANCED("'Z' x 0x2000'0000")(Catch::Benchmark::Chronometer meter)
{
benchmark_hasher(meter, *hasher, 0x2000'0000, 'Z');
};
BENCHMARK_ADVANCED("0 x 0x4100'0000")(Catch::Benchmark::Chronometer meter)
{
benchmark_hasher(meter, *hasher, 0x4100'0000, 0);
};
BENCHMARK_ADVANCED("'B' x 0x6000'003E")(Catch::Benchmark::Chronometer meter)
{
benchmark_hasher(meter, *hasher, 0x6000'003E, 'B');
};
}
TEST_CASE ("SHA512: large -- benchmark", "[.][hash][sha512][!benchmark]")
{
auto hasher = Hash::get_hasher_for(Hash::Algorithm::Sha512);
BENCHMARK_ADVANCED("0 x 1'000'000")(Catch::Benchmark::Chronometer meter)
{
benchmark_hasher(meter, *hasher, 1'000'000, 0);
};
BENCHMARK_ADVANCED("'Z' x 0x2000'0000")(Catch::Benchmark::Chronometer meter)
{
benchmark_hasher(meter, *hasher, 0x2000'0000, 'Z');
};
BENCHMARK_ADVANCED("0 x 0x4100'0000")(Catch::Benchmark::Chronometer meter)
{
benchmark_hasher(meter, *hasher, 0x4100'0000, 0);
};
BENCHMARK_ADVANCED("'B' x 0x6000'003E")(Catch::Benchmark::Chronometer meter)
{
benchmark_hasher(meter, *hasher, 0x6000'003E, 'B');
};
}
#endif

View File

@ -127,7 +127,7 @@ namespace vcpkg::Downloads
const fs::path& path,
const std::string& sha512)
{
std::string actual_hash = vcpkg::Hash::get_file_hash(fs, path, "SHA512");
std::string actual_hash = vcpkg::Hash::get_file_hash(VCPKG_LINE_INFO, fs, path, Hash::Algorithm::Sha512);
// <HACK to handle NuGet.org changing nupkg hashes.>
// This is the NEW hash for 7zip

View File

@ -14,238 +14,758 @@
#ifndef NT_SUCCESS
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
#endif
#endif
namespace vcpkg::Hash
{
static void verify_has_only_allowed_chars(const std::string& s)
using uchar = unsigned char;
Optional<Algorithm> algorithm_from_string(StringView sv) noexcept
{
static const std::regex ALLOWED_CHARS{"^[a-zA-Z0-9-]*$"};
Checks::check_exit(VCPKG_LINE_INFO,
std::regex_match(s, ALLOWED_CHARS),
"Only alphanumeric chars and dashes are currently allowed. String was:\n"
" % s",
s);
if (Strings::case_insensitive_ascii_equals(sv, "SHA1"))
{
return {Algorithm::Sha1};
}
#if defined(_WIN32)
if (Strings::case_insensitive_ascii_equals(sv, "SHA256"))
{
return {Algorithm::Sha256};
}
if (Strings::case_insensitive_ascii_equals(sv, "SHA512"))
{
return {Algorithm::Sha512};
}
return {};
}
const char* to_string(Algorithm algo) noexcept
{
switch (algo)
{
case Algorithm::Sha1: return "SHA1";
case Algorithm::Sha256: return "SHA256";
case Algorithm::Sha512: return "SHA512";
default: vcpkg::Checks::exit_fail(VCPKG_LINE_INFO);
}
}
namespace
{
std::string to_hex(const unsigned char* string, const size_t bytes)
struct UInt128
{
std::uint64_t top;
std::uint64_t bottom;
UInt128() = default;
UInt128(std::uint64_t value) : top(0), bottom(value) {}
UInt128& operator<<=(int by) noexcept
{
if (by == 0)
{
return *this;
}
if (by < 64)
{
top <<= by;
const auto shift_up = bottom >> (64 - by);
top |= shift_up;
bottom <<= by;
}
else
{
top = bottom;
top <<= (by - 64);
}
return *this;
}
UInt128& operator>>=(int by) noexcept
{
if (by == 0)
{
return *this;
}
if (by < 64)
{
bottom >>= by;
const auto shift_down = top << (64 - by);
bottom |= shift_down;
top >>= by;
}
else
{
bottom = top;
bottom >>= (by - 64);
}
return *this;
}
UInt128& operator+=(std::size_t lhs) noexcept
{
// bottom + lhs > uint64::max
if (bottom > std::numeric_limits<std::uint64_t>::max() - lhs)
{
top += 1;
}
bottom += lhs;
return *this;
}
};
}
template<class T>
void top_bits(T) = delete;
static constexpr uchar top_bits(uchar x) noexcept { return x; }
static constexpr uchar top_bits(std::uint32_t x) noexcept { return (x >> 24) & 0xFF; }
static constexpr uchar top_bits(std::uint64_t x) noexcept { return (x >> 56) & 0xFF; }
static constexpr uchar top_bits(UInt128 x) noexcept { return top_bits(x.top); }
// treats UIntTy as big endian for the purpose of this mapping
template<class UIntTy>
static std::string to_hex(const UIntTy* start, const UIntTy* end) noexcept
{
static constexpr char HEX_MAP[] = "0123456789abcdef";
std::string output;
output.resize(2 * bytes);
output.resize(2 * sizeof(UIntTy) * (end - start));
size_t current_char = 0;
for (size_t i = 0; i < bytes; i++)
std::size_t output_index = 0;
for (const UIntTy* it = start; it != end; ++it)
{
// holds *it in a big-endian buffer, for copying into output
uchar buff[sizeof(UIntTy)];
UIntTy tmp = *it;
for (uchar& ch : buff)
{
ch = top_bits(tmp);
tmp <<= 8;
}
for (const auto byte : buff)
{
// high
output[current_char] = HEX_MAP[(string[i] & 0xF0) >> 4];
++current_char;
output[output_index] = HEX_MAP[(byte & 0xF0) >> 4];
++output_index;
// low
output[current_char] = HEX_MAP[(string[i] & 0x0F)];
++current_char;
output[output_index] = HEX_MAP[byte & 0x0F];
++output_index;
}
}
return output;
}
class BCryptHasher
namespace
{
struct BCryptAlgorithmHandle : Util::ResourceBase
#if defined(_WIN32)
BCRYPT_ALG_HANDLE get_alg_handle(LPCWSTR algorithm_identifier) noexcept
{
BCRYPT_ALG_HANDLE handle = nullptr;
~BCryptAlgorithmHandle()
BCRYPT_ALG_HANDLE result;
auto error = BCryptOpenAlgorithmProvider(&result, algorithm_identifier, nullptr, 0);
if (!NT_SUCCESS(error))
{
if (handle) BCryptCloseAlgorithmProvider(handle, 0);
Checks::exit_with_message(VCPKG_LINE_INFO, "Failure to open algorithm: %ls", algorithm_identifier);
}
};
struct BCryptHashHandle : Util::ResourceBase
{
BCRYPT_HASH_HANDLE handle = nullptr;
~BCryptHashHandle()
{
if (handle) BCryptDestroyHash(handle);
return result;
}
};
static void initialize_hash_handle(BCryptHashHandle& hash_handle,
const BCryptAlgorithmHandle& algorithm_handle)
struct BCryptHasher : Hasher
{
const NTSTATUS error_code =
BCryptCreateHash(algorithm_handle.handle, &hash_handle.handle, nullptr, 0, nullptr, 0, 0);
static const BCRYPT_ALG_HANDLE sha1_alg_handle;
static const BCRYPT_ALG_HANDLE sha256_alg_handle;
static const BCRYPT_ALG_HANDLE sha512_alg_handle;
explicit BCryptHasher(Algorithm algo) noexcept
{
switch (algo)
{
case Algorithm::Sha1: alg_handle = sha1_alg_handle; break;
case Algorithm::Sha256: alg_handle = sha256_alg_handle; break;
case Algorithm::Sha512: alg_handle = sha512_alg_handle; break;
default: Checks::exit_with_message(VCPKG_LINE_INFO, "Unknown algorithm");
}
clear();
}
virtual void add_bytes(const void* start_, const void* end_) noexcept override
{
// BCryptHashData takes its input as non-const, but does not modify it
uchar* start = const_cast<uchar*>(static_cast<const uchar*>(start_));
const uchar* end = static_cast<const uchar*>(end_);
Checks::check_exit(VCPKG_LINE_INFO, end - start >= 0);
// only matters on 64-bit -- BCryptHasher takes an unsigned long
// length, so if you have an array bigger than 2**32-1 elements,
// you have a problem.
#if defined(_M_AMD64) || defined(_M_ARM64)
constexpr std::ptrdiff_t max = std::numeric_limits<unsigned long>::max();
Checks::check_exit(VCPKG_LINE_INFO, end - start <= max);
#endif
const auto length = static_cast<unsigned long>(end - start);
const NTSTATUS error_code = BCryptHashData(hash_handle, start, length, 0);
Checks::check_exit(VCPKG_LINE_INFO, NT_SUCCESS(error_code), "Failed to process a chunk");
}
virtual void clear() noexcept override
{
if (hash_handle) BCryptDestroyHash(hash_handle);
const NTSTATUS error_code = BCryptCreateHash(alg_handle, &hash_handle, nullptr, 0, nullptr, 0, 0);
Checks::check_exit(VCPKG_LINE_INFO, NT_SUCCESS(error_code), "Failed to initialize the hasher");
}
static void hash_data(BCryptHashHandle& hash_handle, const unsigned char* buffer, const size_t& data_size)
virtual std::string get_hash() noexcept override
{
const NTSTATUS error_code = BCryptHashData(
hash_handle.handle, const_cast<unsigned char*>(buffer), static_cast<ULONG>(data_size), 0);
Checks::check_exit(VCPKG_LINE_INFO, NT_SUCCESS(error_code), "Failed to hash data");
}
const auto hash_size = get_hash_buffer_size();
const auto buffer = std::make_unique<uchar[]>(hash_size);
const auto hash = buffer.get();
static std::string finalize_hash_handle(const BCryptHashHandle& hash_handle, const ULONG length_in_bytes)
{
std::unique_ptr<unsigned char[]> hash_buffer = std::make_unique<UCHAR[]>(length_in_bytes);
const NTSTATUS error_code = BCryptFinishHash(hash_handle.handle, hash_buffer.get(), length_in_bytes, 0);
const NTSTATUS error_code = BCryptFinishHash(hash_handle, hash, hash_size, 0);
Checks::check_exit(VCPKG_LINE_INFO, NT_SUCCESS(error_code), "Failed to finalize the hash");
return to_hex(hash_buffer.get(), length_in_bytes);
return to_hex(hash, hash + hash_size);
}
public:
explicit BCryptHasher(std::string hash_type)
{
NTSTATUS error_code = BCryptOpenAlgorithmProvider(
&this->algorithm_handle.handle,
Strings::to_utf16(Strings::ascii_to_uppercase(std::move(hash_type))).c_str(),
nullptr,
0);
Checks::check_exit(VCPKG_LINE_INFO, NT_SUCCESS(error_code), "Failed to open the algorithm provider");
~BCryptHasher() { BCryptDestroyHash(hash_handle); }
DWORD hash_buffer_bytes;
DWORD cb_data;
error_code = BCryptGetProperty(this->algorithm_handle.handle,
private:
unsigned long get_hash_buffer_size() const
{
unsigned long hash_buffer_bytes;
unsigned long cb_data;
const NTSTATUS error_code = BCryptGetProperty(alg_handle,
BCRYPT_HASH_LENGTH,
reinterpret_cast<PUCHAR>(&hash_buffer_bytes),
sizeof(DWORD),
reinterpret_cast<uchar*>(&hash_buffer_bytes),
sizeof(hash_buffer_bytes),
&cb_data,
0);
Checks::check_exit(VCPKG_LINE_INFO, NT_SUCCESS(error_code), "Failed to get hash length");
this->length_in_bytes = hash_buffer_bytes;
return hash_buffer_bytes;
}
std::string hash_file(const fs::path& path) const
{
BCryptHashHandle hash_handle;
initialize_hash_handle(hash_handle, this->algorithm_handle);
BCRYPT_HASH_HANDLE hash_handle = nullptr;
BCRYPT_ALG_HANDLE alg_handle = nullptr;
};
FILE* file = nullptr;
const auto ec = _wfopen_s(&file, path.c_str(), L"rb");
Checks::check_exit(VCPKG_LINE_INFO, ec == 0, "Failed to open file: %s", path.u8string());
if (file != nullptr)
const BCRYPT_ALG_HANDLE BCryptHasher::sha1_alg_handle = get_alg_handle(BCRYPT_SHA1_ALGORITHM);
const BCRYPT_ALG_HANDLE BCryptHasher::sha256_alg_handle = get_alg_handle(BCRYPT_SHA256_ALGORITHM);
const BCRYPT_ALG_HANDLE BCryptHasher::sha512_alg_handle = get_alg_handle(BCRYPT_SHA512_ALGORITHM);
#else
template<class WordTy>
static WordTy shl(WordTy value, int by) noexcept
{
unsigned char buffer[4096];
while (const auto actual_size = fread(buffer, 1, sizeof(buffer), file))
{
hash_data(hash_handle, buffer, actual_size);
}
fclose(file);
return value << by;
}
return finalize_hash_handle(hash_handle, length_in_bytes);
static std::uint32_t shr32(std::uint32_t value, int by) noexcept { return value >> by; }
static std::uint32_t rol32(std::uint32_t value, int by) noexcept
{
return (value << by) | (value >> (32 - by));
}
static std::uint32_t ror32(std::uint32_t value, int by) noexcept
{
return (value >> by) | (value << (32 - by));
}
std::string hash_string(const std::string& s) const
static std::uint64_t shr64(std::uint64_t value, int by) noexcept { return value >> by; }
static std::uint64_t ror64(std::uint64_t value, int by) noexcept
{
BCryptHashHandle hash_handle;
initialize_hash_handle(hash_handle, this->algorithm_handle);
hash_data(hash_handle, reinterpret_cast<const unsigned char*>(s.c_str()), s.size());
return finalize_hash_handle(hash_handle, length_in_bytes);
return (value >> by) | (value << (64 - by));
}
template<class ShaAlgorithm>
struct ShaHasher final : Hasher
{
ShaHasher() = default;
virtual void add_bytes(const void* start, const void* end) noexcept override
{
for (;;)
{
start = add_to_unprocessed(start, end);
if (!start)
{
break; // done
}
m_impl.process_full_chunk(m_chunk);
m_current_chunk_size = 0;
}
}
virtual void clear() noexcept override
{
m_impl.clear();
// m_chunk is theoretically uninitialized, so no need to reset it
m_current_chunk_size = 0;
m_message_length = 0;
}
virtual std::string get_hash() noexcept override
{
process_last_chunk();
return to_hex(m_impl.begin(), m_impl.end());
}
private:
BCryptAlgorithmHandle algorithm_handle;
ULONG length_in_bytes;
// if unprocessed gets filled,
// returns a pointer to the remainder of the block (which might be end)
// else, returns nullptr
const void* add_to_unprocessed(const void* start_, const void* end_) noexcept
{
const uchar* start = static_cast<const uchar*>(start_);
const uchar* end = static_cast<const uchar*>(end_);
const auto remaining = chunk_size - m_current_chunk_size;
const std::size_t message_length = end - start;
if (message_length >= remaining)
{
std::copy(start, start + remaining, chunk_begin());
m_current_chunk_size += remaining;
m_message_length += remaining * 8;
return start + remaining;
}
else
{
std::copy(start, end, chunk_begin());
m_current_chunk_size += message_length;
m_message_length += message_length * 8;
return nullptr;
}
}
// called before `get_hash`
void process_last_chunk() noexcept
{
const auto message_length = m_message_length;
// append the bit '1' to the message
{
const uchar temp = 0x80;
add_to_unprocessed(&temp, &temp + 1);
}
// append 0 to the message so that the resulting length is just enough
// to add the message length
if (chunk_size - m_current_chunk_size < sizeof(m_message_length))
{
// not enough space to add the message length
// just resize and process full chunk
std::fill(chunk_begin(), m_chunk.end(), static_cast<uchar>(0));
m_impl.process_full_chunk(m_chunk);
m_current_chunk_size = 0;
}
const auto before_length = m_chunk.end() - sizeof(m_message_length);
std::fill(chunk_begin(), before_length, static_cast<uchar>(0));
std::generate(before_length, m_chunk.end(), [length = message_length]() mutable {
const auto result = top_bits(length);
length <<= 8;
return result;
});
m_impl.process_full_chunk(m_chunk);
}
auto chunk_begin() { return m_chunk.begin() + m_current_chunk_size; }
using underlying_type = typename ShaAlgorithm::underlying_type;
using message_length_type = typename ShaAlgorithm::message_length_type;
constexpr static std::size_t chunk_size = ShaAlgorithm::chunk_size;
ShaAlgorithm m_impl{};
std::array<uchar, chunk_size> m_chunk{};
std::size_t m_current_chunk_size = 0;
message_length_type m_message_length = 0;
};
}
std::string get_file_hash(const Files::Filesystem& fs, const fs::path& path, const std::string& hash_type)
template<class WordTy>
inline void sha_fill_initial_words(const uchar* chunk, WordTy* words)
{
Checks::check_exit(VCPKG_LINE_INFO, fs.exists(path), "File %s does not exist", path.u8string());
return BCryptHasher{hash_type}.hash_file(path);
}
std::string get_string_hash(const std::string& s, const std::string& hash_type)
// break chunk into 16 N-bit words
for (std::size_t word = 0; word < 16; ++word)
{
verify_has_only_allowed_chars(s);
return BCryptHasher{hash_type}.hash_string(s);
words[word] = 0;
// big-endian -- so the earliest i becomes the most significant
for (std::size_t byte = 0; byte < sizeof(WordTy); ++byte)
{
const auto bits_to_shift = static_cast<int>(8 * (sizeof(WordTy) - 1 - byte));
words[word] |= shl<WordTy>(chunk[word * sizeof(WordTy) + byte], bits_to_shift);
}
}
}
struct Sha1Algorithm
{
using underlying_type = std::uint32_t;
using message_length_type = std::uint64_t;
constexpr static std::size_t chunk_size = 64; // = 512 / 8
constexpr static std::size_t number_of_rounds = 80;
Sha1Algorithm() noexcept { clear(); }
void process_full_chunk(const std::array<uchar, chunk_size>& chunk) noexcept
{
std::uint32_t words[80];
sha_fill_initial_words(&chunk[0], words);
for (std::size_t i = 16; i < number_of_rounds; ++i)
{
const auto sum = words[i - 3] ^ words[i - 8] ^ words[i - 14] ^ words[i - 16];
words[i] = rol32(sum, 1);
}
std::uint32_t a = m_digest[0];
std::uint32_t b = m_digest[1];
std::uint32_t c = m_digest[2];
std::uint32_t d = m_digest[3];
std::uint32_t e = m_digest[4];
for (std::size_t i = 0; i < number_of_rounds; ++i)
{
std::uint32_t f;
std::uint32_t k;
if (i < 20)
{
f = (b & c) | (~b & d);
k = 0x5A827999;
}
else if (i < 40)
{
f = b ^ c ^ d;
k = 0x6ED9EBA1;
}
else if (i < 60)
{
f = (b & c) | (b & d) | (c & d);
k = 0x8F1BBCDC;
}
else
{
f = b ^ c ^ d;
k = 0xCA62C1D6;
}
auto tmp = rol32(a, 5) + f + e + k + words[i];
e = d;
d = c;
c = rol32(b, 30);
b = a;
a = tmp;
}
m_digest[0] += a;
m_digest[1] += b;
m_digest[2] += c;
m_digest[3] += d;
m_digest[4] += e;
}
void clear() noexcept
{
m_digest[0] = 0x67452301;
m_digest[1] = 0xEFCDAB89;
m_digest[2] = 0x98BADCFE;
m_digest[3] = 0x10325476;
m_digest[4] = 0xC3D2E1F0;
}
const std::uint32_t* begin() const noexcept { return &m_digest[0]; }
const std::uint32_t* end() const noexcept { return &m_digest[5]; }
std::uint32_t m_digest[5];
};
struct Sha256Algorithm
{
using underlying_type = std::uint32_t;
using message_length_type = std::uint64_t;
constexpr static std::size_t chunk_size = 64;
constexpr static std::size_t number_of_rounds = 64;
Sha256Algorithm() noexcept { clear(); }
void process_full_chunk(const std::array<uchar, chunk_size>& chunk) noexcept
{
std::uint32_t words[64];
sha_fill_initial_words(&chunk[0], words);
for (std::size_t i = 16; i < number_of_rounds; ++i)
{
const auto w0 = words[i - 15];
const auto s0 = ror32(w0, 7) ^ ror32(w0, 18) ^ shr32(w0, 3);
const auto w1 = words[i - 2];
const auto s1 = ror32(w1, 17) ^ ror32(w1, 19) ^ shr32(w1, 10);
words[i] = words[i - 16] + s0 + words[i - 7] + s1;
}
std::uint32_t local[8];
std::copy(begin(), end(), std::begin(local));
for (std::size_t i = 0; i < number_of_rounds; ++i)
{
const auto a = local[0];
const auto b = local[1];
const auto c = local[2];
const auto s0 = ror32(a, 2) ^ ror32(a, 13) ^ ror32(a, 22);
const auto maj = (a & b) ^ (a & c) ^ (b & c);
const auto tmp1 = s0 + maj;
const auto e = local[4];
const auto s1 = ror32(e, 6) ^ ror32(e, 11) ^ ror32(e, 25);
const auto ch = (e & local[5]) ^ (~e & local[6]);
const auto tmp2 = local[7] + s1 + ch + round_constants[i] + words[i];
for (std::size_t j = 7; j > 0; --j)
{
local[j] = local[j - 1];
}
local[4] += tmp2;
local[0] = tmp1 + tmp2;
}
for (std::size_t i = 0; i < 8; ++i)
{
m_digest[i] += local[i];
}
}
void clear() noexcept
{
m_digest[0] = 0x6a09e667;
m_digest[1] = 0xbb67ae85;
m_digest[2] = 0x3c6ef372;
m_digest[3] = 0xa54ff53a;
m_digest[4] = 0x510e527f;
m_digest[5] = 0x9b05688c;
m_digest[6] = 0x1f83d9ab;
m_digest[7] = 0x5be0cd19;
}
constexpr static std::array<std::uint32_t, number_of_rounds> round_constants = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2};
std::uint32_t* begin() noexcept { return &m_digest[0]; }
std::uint32_t* end() noexcept { return &m_digest[8]; }
std::uint32_t m_digest[8];
};
struct Sha512Algorithm
{
using underlying_type = std::uint64_t;
using message_length_type = UInt128;
constexpr static std::size_t chunk_size = 128; // = 1024 / 8
constexpr static std::size_t number_of_rounds = 80;
Sha512Algorithm() noexcept { clear(); }
void process_full_chunk(const std::array<uchar, chunk_size>& chunk) noexcept
{
std::uint64_t words[80];
sha_fill_initial_words(&chunk[0], words);
for (std::size_t i = 16; i < number_of_rounds; ++i)
{
const auto w0 = words[i - 15];
const auto s0 = ror64(w0, 1) ^ ror64(w0, 8) ^ shr64(w0, 7);
const auto w1 = words[i - 2];
const auto s1 = ror64(w1, 19) ^ ror64(w1, 61) ^ shr64(w1, 6);
words[i] = words[i - 16] + s0 + words[i - 7] + s1;
}
std::uint64_t local[8];
std::copy(begin(), end(), std::begin(local));
for (std::size_t i = 0; i < number_of_rounds; ++i)
{
const auto a = local[0];
const auto b = local[1];
const auto c = local[2];
const auto s0 = ror64(a, 28) ^ ror64(a, 34) ^ ror64(a, 39);
const auto maj = (a & b) ^ (a & c) ^ (b & c);
const auto tmp0 = s0 + maj;
const auto e = local[4];
const auto s1 = ror64(e, 14) ^ ror64(e, 18) ^ ror64(e, 41);
const auto ch = (e & local[5]) ^ (~e & local[6]);
const auto tmp1 = local[7] + s1 + ch + round_constants[i] + words[i];
for (std::size_t j = 7; j > 0; --j)
{
local[j] = local[j - 1];
}
local[4] += tmp1;
local[0] = tmp0 + tmp1;
}
for (std::size_t i = 0; i < 8; ++i)
{
m_digest[i] += local[i];
}
}
void clear() noexcept
{
m_digest[0] = 0x6a09e667f3bcc908;
m_digest[1] = 0xbb67ae8584caa73b;
m_digest[2] = 0x3c6ef372fe94f82b;
m_digest[3] = 0xa54ff53a5f1d36f1;
m_digest[4] = 0x510e527fade682d1;
m_digest[5] = 0x9b05688c2b3e6c1f;
m_digest[6] = 0x1f83d9abfb41bd6b;
m_digest[7] = 0x5be0cd19137e2179;
}
constexpr static std::array<std::uint64_t, number_of_rounds> round_constants = {
0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc, 0x3956c25bf348b538,
0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118, 0xd807aa98a3030242, 0x12835b0145706fbe,
0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2, 0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235,
0xc19bf174cf692694, 0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65,
0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5, 0x983e5152ee66dfab,
0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4, 0xc6e00bf33da88fc2, 0xd5a79147930aa725,
0x06ca6351e003826f, 0x142929670a0e6e70, 0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed,
0x53380d139d95b3df, 0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b,
0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30, 0xd192e819d6ef5218,
0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8, 0x19a4c116b8d2d0c8, 0x1e376c085141ab53,
0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8, 0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373,
0x682e6ff3d6b2b8a3, 0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec,
0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b, 0xca273eceea26619c,
0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178, 0x06f067aa72176fba, 0x0a637dc5a2c898a6,
0x113f9804bef90dae, 0x1b710b35131c471b, 0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc,
0x431d67c49c100d4c, 0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817};
std::uint64_t* begin() noexcept { return &m_digest[0]; }
std::uint64_t* end() noexcept { return &m_digest[8]; }
std::uint64_t m_digest[8];
};
// This is required on older compilers, since it was required in C++14
constexpr std::array<std::uint32_t, Sha256Algorithm::number_of_rounds> Sha256Algorithm::round_constants;
constexpr std::array<std::uint64_t, Sha512Algorithm::number_of_rounds> Sha512Algorithm::round_constants;
#endif
}
std::unique_ptr<Hasher> get_hasher_for(Algorithm algo) noexcept
{
#if defined(_WIN32)
return std::make_unique<BCryptHasher>(algo);
#else
static std::string get_digest_size(const std::string& hash_type)
switch (algo)
{
if (!Strings::case_insensitive_ascii_starts_with(hash_type, "SHA"))
{
Checks::exit_with_message(
VCPKG_LINE_INFO, "shasum only supports SHA hashes, but %s was provided", hash_type);
}
return hash_type.substr(3, hash_type.length() - 3);
}
static std::string parse_shasum_output(const std::string& shasum_output)
{
std::vector<std::string> split = Strings::split(shasum_output, " ");
// Checking if >= 3 because filenames with spaces will show up as multiple tokens.
// The hash is the first token so we don't need to parse the filename anyway.
Checks::check_exit(VCPKG_LINE_INFO,
split.size() >= 3,
"Expected output of the form [hash filename\n] (3+ tokens), but got\n"
"[%s] (%s tokens)",
shasum_output,
std::to_string(split.size()));
return split[0];
}
std::string get_file_hash(const Files::Filesystem& fs, const fs::path& path, const std::string& hash_type)
{
const std::string digest_size = get_digest_size(hash_type);
Checks::check_exit(VCPKG_LINE_INFO, fs.exists(path), "File %s does not exist", path.u8string());
// Try hash-specific tools, like sha512sum
{
const auto ec_data = System::cmd_execute_and_capture_output(
Strings::format(R"(sha%ssum "%s")", digest_size, path.u8string()));
if (ec_data.exit_code == 0)
{
return parse_shasum_output(ec_data.output);
}
}
// Try shasum
{
const auto ec_data = System::cmd_execute_and_capture_output(
Strings::format(R"(shasum -a %s "%s")", digest_size, path.u8string()));
if (ec_data.exit_code == 0)
{
return parse_shasum_output(ec_data.output);
}
}
Checks::exit_with_message(VCPKG_LINE_INFO, "Could not hash file %s with %s", path.u8string(), hash_type);
}
std::string get_string_hash(const std::string& s, const std::string& hash_type)
{
const std::string digest_size = get_digest_size(hash_type);
verify_has_only_allowed_chars(s);
// Try hash-specific tools, like sha512sum
{
const auto ec_data =
System::cmd_execute_and_capture_output(Strings::format(R"(echo -n "%s" | sha%ssum)", s, digest_size));
if (ec_data.exit_code == 0)
{
return parse_shasum_output(ec_data.output);
}
}
// Try shasum
{
const auto ec_data = System::cmd_execute_and_capture_output(
Strings::format(R"(echo -n "%s" | shasum -a %s)", s, digest_size));
if (ec_data.exit_code == 0)
{
return parse_shasum_output(ec_data.output);
}
}
Checks::exit_with_message(VCPKG_LINE_INFO, "Could not hash input string with %s", hash_type);
case Algorithm::Sha1: return std::make_unique<ShaHasher<Sha1Algorithm>>();
case Algorithm::Sha256: return std::make_unique<ShaHasher<Sha256Algorithm>>();
case Algorithm::Sha512: return std::make_unique<ShaHasher<Sha512Algorithm>>();
default: vcpkg::Checks::exit_with_message(VCPKG_LINE_INFO, "Unknown hashing algorithm: %s", algo);
}
#endif
}
template<class F>
static std::string do_hash(Algorithm algo, const F& f) noexcept
{
#if defined(_WIN32)
auto hasher = BCryptHasher(algo);
return f(hasher);
#else
switch (algo)
{
case Algorithm::Sha1:
{
auto hasher = ShaHasher<Sha1Algorithm>();
return f(hasher);
}
case Algorithm::Sha256:
{
auto hasher = ShaHasher<Sha256Algorithm>();
return f(hasher);
}
case Algorithm::Sha512:
{
auto hasher = ShaHasher<Sha512Algorithm>();
return f(hasher);
}
default: vcpkg::Checks::exit_with_message(VCPKG_LINE_INFO, "Unknown hashing algorithm: %s", algo);
}
#endif
}
std::string get_bytes_hash(const void* first, const void* last, Algorithm algo) noexcept
{
return do_hash(algo, [first, last](Hasher& hasher) {
hasher.add_bytes(first, last);
return hasher.get_hash();
});
}
std::string get_string_hash(StringView sv, Algorithm algo) noexcept
{
return get_bytes_hash(sv.data(), sv.data() + sv.size(), algo);
}
// TODO: use Files::Filesystem to open a file
std::string get_file_hash(const Files::Filesystem&,
const fs::path& path,
Algorithm algo,
std::error_code& ec) noexcept
{
auto file = std::fstream(path.c_str(), std::ios_base::in | std::ios_base::binary);
if (!file)
{
ec.assign(ENOENT, std::system_category());
return {};
}
return do_hash(algo, [&file, &ec](Hasher& hasher) {
constexpr std::size_t buffer_size = 4096;
auto buffer = std::make_unique<char[]>(buffer_size);
for (;;)
{
file.read(buffer.get(), buffer_size);
if (file.eof())
{
hasher.add_bytes(buffer.get(), buffer.get() + file.gcount());
return hasher.get_hash();
}
else if (file)
{
hasher.add_bytes(buffer.get(), buffer.get() + buffer_size);
}
else
{
ec = std::io_errc::stream;
return std::string();
}
}
});
}
}

View File

@ -76,8 +76,10 @@ namespace vcpkg
std::string StringView::to_string() const { return std::string(m_ptr, m_size); }
void StringView::to_string(std::string& s) const { s.append(m_ptr, m_size); }
bool StringView::operator==(StringView other) const
bool operator==(StringView lhs, StringView rhs) noexcept
{
return other.size() == size() && memcmp(data(), other.data(), size()) == 0;
return lhs.size() == lhs.size() && memcmp(lhs.data(), rhs.data(), lhs.size()) == 0;
}
bool operator!=(StringView lhs, StringView rhs) noexcept { return !(lhs == rhs); }
}

View File

@ -486,32 +486,33 @@ namespace vcpkg::Build
}
else
{
hash = Hash::get_file_hash(fs, triplet_file_path, "SHA1");
const auto algo = Hash::Algorithm::Sha1;
hash = Hash::get_file_hash(VCPKG_LINE_INFO, fs, triplet_file_path, algo);
if (auto p = pre_build_info.external_toolchain_file.get())
{
hash += "-";
hash += Hash::get_file_hash(fs, *p, "SHA1");
hash += Hash::get_file_hash(VCPKG_LINE_INFO, fs, *p, algo);
}
else if (pre_build_info.cmake_system_name == "Linux")
{
hash += "-";
hash += Hash::get_file_hash(fs, paths.scripts / "toolchains" / "linux.cmake", "SHA1");
hash += Hash::get_file_hash(VCPKG_LINE_INFO, fs, paths.scripts / "toolchains" / "linux.cmake", algo);
}
else if (pre_build_info.cmake_system_name == "Darwin")
{
hash += "-";
hash += Hash::get_file_hash(fs, paths.scripts / "toolchains" / "osx.cmake", "SHA1");
hash += Hash::get_file_hash(VCPKG_LINE_INFO, fs, paths.scripts / "toolchains" / "osx.cmake", algo);
}
else if (pre_build_info.cmake_system_name == "FreeBSD")
{
hash += "-";
hash += Hash::get_file_hash(fs, paths.scripts / "toolchains" / "freebsd.cmake", "SHA1");
hash += Hash::get_file_hash(VCPKG_LINE_INFO, fs, paths.scripts / "toolchains" / "freebsd.cmake", algo);
}
else if (pre_build_info.cmake_system_name == "Android")
{
hash += "-";
hash += Hash::get_file_hash(fs, paths.scripts / "toolchains" / "android.cmake", "SHA1");
hash += Hash::get_file_hash(VCPKG_LINE_INFO, fs, paths.scripts / "toolchains" / "android.cmake", algo);
}
s_hash_cache.emplace(triplet_file_path, hash);
@ -651,8 +652,9 @@ namespace vcpkg::Build
{
if (fs::is_regular_file(fs.status(VCPKG_LINE_INFO, port_file)))
{
port_files.emplace_back(port_file.path().filename().u8string(),
vcpkg::Hash::get_file_hash(fs, port_file, "SHA1"));
port_files.emplace_back(
port_file.path().filename().u8string(),
vcpkg::Hash::get_file_hash(VCPKG_LINE_INFO, fs, port_file, Hash::Algorithm::Sha1));
if (port_files.size() > max_port_file_count)
{
@ -679,7 +681,10 @@ namespace vcpkg::Build
abi_tag_entries.emplace_back(
"vcpkg_fixup_cmake_targets",
vcpkg::Hash::get_file_hash(fs, paths.scripts / "cmake" / "vcpkg_fixup_cmake_targets.cmake", "SHA1"));
vcpkg::Hash::get_file_hash(VCPKG_LINE_INFO,
fs,
paths.scripts / "cmake" / "vcpkg_fixup_cmake_targets.cmake",
Hash::Algorithm::Sha1));
abi_tag_entries.emplace_back("triplet", pre_build_info.triplet_abi_tag);
abi_tag_entries.emplace_back("features", Strings::join(";", config.feature_list));
@ -688,7 +693,8 @@ namespace vcpkg::Build
{
abi_tag_entries.emplace_back(
"public_abi_override",
Hash::get_string_hash(pre_build_info.public_abi_override.value_or_exit(VCPKG_LINE_INFO), "SHA1"));
Hash::get_string_hash(pre_build_info.public_abi_override.value_or_exit(VCPKG_LINE_INFO),
Hash::Algorithm::Sha1));
}
if (config.build_package_options.use_head_version == UseHeadVersion::YES)
@ -717,7 +723,8 @@ namespace vcpkg::Build
const auto abi_file_path = paths.buildtrees / name / (triplet.canonical_name() + ".vcpkg_abi_info.txt");
fs.write_contents(abi_file_path, full_abi_info, VCPKG_LINE_INFO);
return AbiTagAndFile{Hash::get_file_hash(fs, abi_file_path, "SHA1"), abi_file_path};
return AbiTagAndFile{Hash::get_file_hash(VCPKG_LINE_INFO, fs, abi_file_path, Hash::Algorithm::Sha1),
abi_file_path};
}
System::print2(

View File

@ -99,8 +99,15 @@ namespace vcpkg::Commands::Hash
Util::unused(args.parse_arguments(COMMAND_STRUCTURE));
const fs::path file_to_hash = args.command_arguments[0];
const std::string algorithm = args.command_arguments.size() == 2 ? args.command_arguments[1] : "SHA512";
const std::string hash = vcpkg::Hash::get_file_hash(paths.get_filesystem(), file_to_hash, algorithm);
auto algorithm = vcpkg::Hash::Algorithm::Sha512;
if (args.command_arguments.size() == 2)
{
algorithm = vcpkg::Hash::algorithm_from_string(args.command_arguments[1]).value_or_exit(VCPKG_LINE_INFO);
}
const std::string hash =
vcpkg::Hash::get_file_hash(VCPKG_LINE_INFO, paths.get_filesystem(), file_to_hash, algorithm);
System::print2(hash, '\n');
Checks::exit_success(VCPKG_LINE_INFO);
}

View File

@ -266,7 +266,7 @@ namespace vcpkg::Metrics
const auto match = *next;
if (match[0] != "00-00-00-00-00-00")
{
return vcpkg::Hash::get_string_hash(match[0], "SHA256");
return vcpkg::Hash::get_string_hash(match[0].str(), Hash::Algorithm::Sha256);
}
++next;
}