/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */ #ifndef MPT_RANDOM_DEVICE_HPP #define MPT_RANDOM_DEVICE_HPP #include "mpt/base/bit.hpp" #include "mpt/base/detect.hpp" #include "mpt/base/macros.hpp" #include "mpt/base/math.hpp" #include "mpt/base/integer.hpp" #include "mpt/base/namespace.hpp" #include "mpt/crc/crc.hpp" #include "mpt/endian/integer.hpp" #include "mpt/mutex/mutex.hpp" #include "mpt/out_of_memory/out_of_memory.hpp" #include "mpt/random/engine.hpp" #include "mpt/random/engine_lcg.hpp" #include "mpt/random/random.hpp" #include #include #include #include #include #include #include namespace mpt { inline namespace MPT_INLINE_NS { inline constexpr uint32 DETERMINISTIC_RNG_SEED = 3141592653u; // pi template struct default_radom_seed_hash { }; template <> struct default_radom_seed_hash { using type = mpt::crc16; }; template <> struct default_radom_seed_hash { using type = mpt::crc16; }; template <> struct default_radom_seed_hash { using type = mpt::crc32c; }; template <> struct default_radom_seed_hash { using type = mpt::crc64_jones; }; class prng_random_device_time_seeder { public: template inline T generate_seed() { // Note: CRC is actually not that good a choice here, but it is simple and we // already have an implementaion available. Better choices for mixing entropy // would be a hash function with proper avalanche characteristics or a block // or stream cipher with any pre-choosen random key and IV. The only aspect we // really need here is whitening of the bits. typename mpt::default_radom_seed_hash::type hash; { uint64be time; time = std::chrono::duration_cast(std::chrono::system_clock().now().time_since_epoch()).count(); std::byte bytes[sizeof(time)]; std::memcpy(bytes, &time, sizeof(time)); hash(std::begin(bytes), std::end(bytes)); } #if !defined(MPT_COMPILER_QUIRK_CHRONO_NO_HIGH_RESOLUTION_CLOCK) // Avoid std::chrono::high_resolution_clock on Emscripten because availability is problematic in AudioWorklet context. { uint64be time; time = std::chrono::duration_cast(std::chrono::high_resolution_clock().now().time_since_epoch()).count(); std::byte bytes[sizeof(time)]; std::memcpy(bytes, &time, sizeof(time)); hash(std::begin(bytes), std::end(bytes)); } #endif // !MPT_COMPILER_QUIRK_CHRONO_NO_HIGH_RESOLUTION_CLOCK return static_cast(hash.result()); } public: prng_random_device_time_seeder() = default; }; // C++11 std::random_device may be implemented as a deterministic PRNG. // There is no way to seed this PRNG and it is allowed to be seeded with the // same value on each program invocation. This makes std::random_device // completely useless even as a non-cryptographic entropy pool. // We fallback to time-seeded std::mt19937 if std::random_device::entropy() is // 0 or less. class sane_random_device { private: mpt::mutex m; std::string token; #if !defined(MPT_COMPILER_QUIRK_RANDOM_NO_RANDOM_DEVICE) std::unique_ptr prd; bool rd_reliable{false}; #endif // !MPT_COMPILER_QUIRK_RANDOM_NO_RANDOM_DEVICE std::unique_ptr rd_fallback; public: using result_type = unsigned int; private: void init_fallback() { if (!rd_fallback) { if (token.length() > 0) { uint64 seed_val = mpt::prng_random_device_time_seeder().generate_seed(); std::vector seeds; seeds.push_back(static_cast(seed_val >> 32)); seeds.push_back(static_cast(seed_val >> 0)); for (std::size_t i = 0; i < token.length(); ++i) { seeds.push_back(static_cast(static_cast(token[i]))); } std::seed_seq seed(seeds.begin(), seeds.end()); rd_fallback = std::make_unique(seed); } else { uint64 seed_val = mpt::prng_random_device_time_seeder().generate_seed(); unsigned int seeds[2]; seeds[0] = static_cast(seed_val >> 32); seeds[1] = static_cast(seed_val >> 0); std::seed_seq seed(seeds + 0, seeds + 2); rd_fallback = std::make_unique(seed); } } } public: sane_random_device() { #if !defined(MPT_COMPILER_QUIRK_RANDOM_NO_RANDOM_DEVICE) try { prd = std::make_unique(); rd_reliable = ((*prd).entropy() > 0.0); } catch (mpt::out_of_memory e) { mpt::rethrow_out_of_memory(e); } catch (const std::exception &) { rd_reliable = false; } if (!rd_reliable) { init_fallback(); } #else // MPT_COMPILER_QUIRK_RANDOM_NO_RANDOM_DEVICE init_fallback(); #endif // !MPT_COMPILER_QUIRK_RANDOM_NO_RANDOM_DEVICE } sane_random_device(const std::string & token_) : token(token_) { #if !defined(MPT_COMPILER_QUIRK_RANDOM_NO_RANDOM_DEVICE) try { prd = std::make_unique(token); rd_reliable = ((*prd).entropy() > 0.0); } catch (mpt::out_of_memory e) { mpt::rethrow_out_of_memory(e); } catch (const std::exception &) { rd_reliable = false; } if (!rd_reliable) { init_fallback(); } #else // MPT_COMPILER_QUIRK_RANDOM_NO_RANDOM_DEVICE init_fallback(); #endif // !MPT_COMPILER_QUIRK_RANDOM_NO_RANDOM_DEVICE } static MPT_CONSTEXPRINLINE result_type min() { return std::numeric_limits::min(); } static MPT_CONSTEXPRINLINE result_type max() { return std::numeric_limits::max(); } static MPT_CONSTEXPRINLINE int result_bits() { return sizeof(result_type) * 8; } result_type operator()() { mpt::lock_guard l(m); result_type result = 0; #if !defined(MPT_COMPILER_QUIRK_RANDOM_NO_RANDOM_DEVICE) if (prd) { try { if constexpr (std::random_device::min() != 0 || !mpt::is_mask(std::random_device::max())) { // insane std::random_device // This implementation is not exactly uniformly distributed but good enough // for OpenMPT. constexpr double rd_min = static_cast(std::random_device::min()); constexpr double rd_max = static_cast(std::random_device::max()); constexpr double rd_range = rd_max - rd_min; constexpr double rd_size = rd_range + 1.0; const double rd_entropy = mpt::log2(rd_size); const int iterations = static_cast(std::ceil(result_bits() / rd_entropy)); double tmp = 0.0; for (int i = 0; i < iterations; ++i) { tmp = (tmp * rd_size) + (static_cast((*prd)()) - rd_min); } double result_01 = std::floor(tmp / std::pow(rd_size, iterations)); result = static_cast(std::floor(result_01 * (static_cast(max() - min()) + 1.0))) + min(); } else { // sane std::random_device result = 0; std::size_t rd_bits = mpt::lower_bound_entropy_bits(std::random_device::max()); for (std::size_t entropy = 0; entropy < (sizeof(result_type) * 8); entropy += rd_bits) { if (rd_bits < (sizeof(result_type) * 8)) { result = (result << rd_bits) | static_cast((*prd)()); } else { result = result | static_cast((*prd)()); } } } } catch (const std::exception &) { rd_reliable = false; init_fallback(); } } else { rd_reliable = false; } if (!rd_reliable) { // std::random_device is unreliable // XOR the generated random number with more entropy from the time-seeded // PRNG. // Note: This is safe even if the std::random_device itself is implemented // as a std::mt19937 PRNG because we are very likely using a different // seed. result ^= mpt::random(*rd_fallback); } #else // MPT_COMPILER_QUIRK_RANDOM_NO_RANDOM_DEVICE result ^= mpt::random(*rd_fallback); #endif // !MPT_COMPILER_QUIRK_RANDOM_NO_RANDOM_DEVICE return result; } }; class prng_random_device_deterministic_seeder { protected: template constexpr T generate_seed() noexcept { return static_cast(mpt::DETERMINISTIC_RNG_SEED); } protected: prng_random_device_deterministic_seeder() = default; }; template class prng_random_device : private seeder { public: using result_type = unsigned int; private: mpt::mutex m; Trng rng; public: prng_random_device() : rng(seeder::template generate_seed()) { return; } prng_random_device(const std::string &) : rng(seeder::template generate_seed()) { return; } static MPT_CONSTEXPRINLINE result_type min() { return std::numeric_limits::min(); } static MPT_CONSTEXPRINLINE result_type max() { return std::numeric_limits::max(); } static MPT_CONSTEXPRINLINE int result_bits() { return sizeof(unsigned int) * 8; } result_type operator()() { mpt::lock_guard l(m); return mpt::random(rng); } }; using deterministc_random_device = mpt::prng_random_device; } // namespace MPT_INLINE_NS } // namespace mpt #endif // MPT_RANDOM_DEVICE_HPP