#pragma once #include #include #include #include #include #include #include namespace xrpl { #ifndef __INTELLISENSE__ static_assert( // NOLINTNEXTLINE(misc-redundant-expression) std::is_integral_v && std::is_unsigned_v, "The XRPL default PRNG engine must return an unsigned integral type."); static_assert( // NOLINTNEXTLINE(misc-redundant-expression) std::numeric_limits::max() >= std::numeric_limits::max(), "The XRPL default PRNG engine return must be at least 64 bits wide."); #endif namespace detail { // Determines if a type can be called like an Engine // NOLINTNEXTLINE(readability-redundant-typename): typename required by MSVC template using is_engine = std::is_invocable_r; } // namespace detail /** * Return the default random engine. * * This engine is guaranteed to be deterministic, but by * default will be randomly seeded. It is NOT cryptographically * secure and MUST NOT be used to generate randomness that * will be used for keys, secure cookies, IVs, padding, etc. * * Each thread gets its own instance of the engine which * will be randomly seeded. */ inline beast::xor_shift_engine& defaultPrng() { // This is used to seed the thread-specific PRNGs on demand static beast::xor_shift_engine kSeeder = [] { std::random_device rng; std::uniform_int_distribution distribution{1}; return beast::xor_shift_engine(distribution(rng)); }(); // This protects the seeder static std::mutex kM; // The thread-specific PRNGs: thread_local beast::xor_shift_engine kEngine = [] { std::uint64_t seed = 0; { std::scoped_lock const lk(kM); std::uniform_int_distribution distribution{1}; seed = distribution(kSeeder); } return beast::xor_shift_engine{seed}; }(); return kEngine; } /** * Return a uniformly distributed random integer. * * @param min The smallest value to return. If not specified * the value defaults to 0. * @param max The largest value to return. If not specified * the value defaults to the largest value that * can be represented. * * The randomness is generated by the specified engine (or * the default engine if one is not specified). The result * is cryptographically secure only when the engine passed * into the function is cryptographically secure. * * @note The range is always a closed interval, so calling * rand_int(-5, 15) can return any integer in the * closed interval [-5, 15]; similarly, calling * rand_int(7) can return any integer in the closed * interval [0, 7]. */ /** @{ */ template Integral randInt(Engine& engine, Integral min, Integral max) requires(std::is_integral_v && detail::is_engine::value) { XRPL_ASSERT(max > min, "xrpl::randInt : max over min inputs"); // This should have no state and constructing it should // be very cheap. If that turns out not to be the case // it could be hand-optimized. return std::uniform_int_distribution(min, max)(engine); } template Integral randInt(Integral min, Integral max) requires(std::is_integral_v) { return randInt(defaultPrng(), min, max); } template Integral randInt(Engine& engine, Integral max) requires(std::is_integral_v && detail::is_engine::value) { return randInt(engine, Integral(0), max); } template Integral randInt(Integral max) requires(std::is_integral_v) { return randInt(defaultPrng(), max); } template Integral randInt(Engine& engine) requires(std::is_integral_v && detail::is_engine::value) { return randInt(engine, std::numeric_limits::max()); } template Integral randInt() requires(std::is_integral_v) { return randInt(defaultPrng(), std::numeric_limits::max()); } /** @} */ /** * Return a random byte */ /** @{ */ template Byte randByte(Engine& engine) requires( (std::is_same_v || std::is_same_v) && detail::is_engine::value) { return static_cast(randInt( engine, std::numeric_limits::min(), std::numeric_limits::max())); } template Byte randByte() requires(std::is_same_v || std::is_same_v) { return randByte(defaultPrng()); } /** @} */ /** * Return a random boolean value */ /** @{ */ template inline bool randBool(Engine& engine) { return randInt(engine, 1) == 1; } inline bool randBool() { return randBool(defaultPrng()); } /** @} */ } // namespace xrpl