diff --git a/.gitignore b/.gitignore index 5d9efeea76..b2fcc1860a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ /doc/html /doc/reference.xml -/test/rng.saved +**/rng.saved diff --git a/Jamfile b/Jamfile new file mode 100644 index 0000000000..7573075484 --- /dev/null +++ b/Jamfile @@ -0,0 +1,30 @@ +# Boost.Random Library Jamfile +# +# Copyright (c) 2017 James E. King, III +# +# Use, modification, and distribution are subject to the +# Boost Software License, Version 1.0. (See accompanying file +# LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +project libs/random + : requirements + + all + + clang:-Wextra + clang:-ansi +# clang:-pedantic + clang:-Wno-c++11-long-long + + gcc:-Wextra + gcc:-ansi +# gcc:-pedantic + gcc:-Wno-long-long + ; + +# pedantic mode disabled due to issue in multiprecision +# https://github.com/boostorg/multiprecision/issues/34 + +# please order by name to ease maintenance +build-project example ; +build-project test ; diff --git a/build/Jamfile.v2 b/build/Jamfile.v2 deleted file mode 100644 index b4e9ceae30..0000000000 --- a/build/Jamfile.v2 +++ /dev/null @@ -1,18 +0,0 @@ -# Jamfile.v2 -# -# Copyright (c) 2010 -# Steven Watanabe -# -# Distributed under the Boost Software License, Version 1.0. (See -# accompanying file LICENSE_1_0.txt or copy at -# http://www.boost.org/LICENSE_1_0.txt) - -project /boost/random - : source-location ../src - : requirements shared:BOOST_RANDOM_DYN_LINK - : usage-requirements shared:BOOST_RANDOM_DYN_LINK -; - -lib boost_random : [ glob *.cpp ] /boost//system ; - -boost-install boost_random ; diff --git a/doc/Jamfile.v2 b/doc/Jamfile.v2 index 1a985a0028..4a8281578e 100644 --- a/doc/Jamfile.v2 +++ b/doc/Jamfile.v2 @@ -94,7 +94,6 @@ doxygen reference : random_distribution=\"@xmlonly random distribution @endxmlonly\" \\ pseudo_random_number_generator=\"@xmlonly pseudo-random number generator @endxmlonly\" \\ uniform_random_number_generator=\"@xmlonly uniform random number generator @endxmlonly\" \\ - nondeterministic_random_number_generator=\"@xmlonly non-deterministic random number generator @endxmlonly\" \\ generators=\"@xmlonly generators @endxmlonly\" \\ distributions=\"@xmlonly distributions @endxmlonly\" \\ additive_combine_engine=\"@xmlonly additive_combine_engine @endxmlonly\" \\ diff --git a/doc/nondet_random.qbk b/doc/nondet_random.qbk deleted file mode 100644 index edfa635e14..0000000000 --- a/doc/nondet_random.qbk +++ /dev/null @@ -1,103 +0,0 @@ -[/ - / Copyright (c) 2009 Steven Watanabe - / - / Distributed under the Boost Software License, Version 1.0. (See - / accompanying file LICENSE_1_0.txt or copy at - / http://www.boost.org/LICENSE_1_0.txt) -] - -[section Header Synopsis] - - namespace boost { - class random_device; - } // namespace boost - -[endsect] - -[section Class random_device] - -[section Synopsis] - - class random_device : noncopyable - { - public: - typedef unsigned int result_type; - static const bool has_fixed_range = true; - static const result_type min_value = /* implementation defined */; - static const result_type max_value = /* implementation defined */; - result_type min() const; - result_type max() const; - explicit random_device(const std::string& token = default_token); - ~random_device(); - double entropy() const; - unsigned int operator()(); - }; - -[endsect] - -[section Description] - -Class `random_device` models a non-deterministic random number generator. It -uses one or more implementation-defined stochastic processes to generate a -sequence of uniformly distributed non-deterministic random numbers. For those -environments where a non-deterministic random number generator is not -available, class random_device must not be implemented. See - -[:"Randomness Recommendations for Security", D. Eastlake, S. Crocker, -J. Schiller, Network Working Group, RFC 1750, December 1994] - -for further discussions. - -[note Some operating systems abstract the computer hardware enough to make it -difficult to non-intrusively monitor stochastic processes. However, several do -provide a special device for exactly this purpose. It seems to be impossible -to emulate the functionality using Standard C++ only, so users should be aware -that this class may not be available on all platforms.] - -[endsect] - -[section Members] - - explicit random_device(const std::string& token = default_token) - -Effects: Constructs a random_device, optionally using the given token as an -access specification (for example, a URL) to some implementation-defined -service for monitoring a stochastic process. - - double entropy() const - -Returns: An entropy estimate for the random numbers returned by `operator()`, -in the range `min()` to `log2(max()+1)`. A deterministic random number -generator (e.g. a pseudo-random number engine) has entropy 0. - -Throws: Nothing. - -[endsect] - -Implementation Note for Linux -On the Linux operating system, token is interpreted as a filesystem path. It -is assumed that this path denotes an operating system pseudo-device which -generates a stream of non-deterministic random numbers. The pseudo-device -should never signal an error or end-of-file. Otherwise, std::ios_base::failure -is thrown. By default, random_device uses the /dev/urandom pseudo-device to -retrieve the random numbers. Another option would be to specify the -/dev/random pseudo-device, which blocks on reads if the entropy pool has no -more random bits available. - -[endsect] - -[section Performance] - -The test program nondet_random_speed.cpp measures the execution times of the -nondet_random.hpp implementation of the above algorithms in a tight loop. -The performance has been evaluated on a Pentium Pro 200 MHz with gcc 2.95.2, -Linux 2.2.13, glibc 2.1.2. - -[table preformance - [[class] [time per invocation \[usec\]]] - [[random_device] [92.0]] -] - -The measurement error is estimated at +/- 1 usec. - -[endsect] diff --git a/doc/random.qbk b/doc/random.qbk index fd7a06ee27..a8d9aa56ba 100644 --- a/doc/random.qbk +++ b/doc/random.qbk @@ -107,14 +107,32 @@ You should read the [concepts concepts documentation] for an introduction and th definition of the basic concepts. For a quick start, it may be sufficient to have a look at [@boost:/libs/random/example/random_demo.cpp random_demo.cpp]. -For a very quick start, here's an example: +For a very quick start, here's an example for rolling one die: - ``[classref boost::random::mt19937]`` rng; // produces randomness out of thin air - // see pseudo-random number generators + ``[classref boost::random::random_device]`` entropy; // operating-system provided entropy generator ``[classref boost::random::uniform_int_distribution]<>`` six(1,6); - // distribution that maps to 1..6 - // see random number distributions - int x = six(rng); // simulate rolling a die + // distribution that maps to 1..6 + // see random number distributions + int x = six(entropy); // simulate rolling a die + +If you are going to be rolling a large number of dies, you can initialize a +__PseudoRandomNumberGenerator once, then reuse it. This may be more optimal +than accessing the operating-system provided entropy generator directly many times - as +always you should test that the solution meets your performance needs: + + ``[classref boost::random::random_device]`` entropy; // operating-system provided entropy generator + ``[classref boost::random::mt19937]`` prng; // produces randomness out of thin air + // see pseudo-random number generators + ``[classref boost::random::uniform_int_distribution]<>`` six(1,6); + // distribution that maps to 1..6 + // see random number distributions + prng.seed(entropy); // seed the PRNG + int total = 0; + const size_t rolls = 65536; + for (size_t roll = 0; roll < rolls; ++roll) { + total += six(prng); // simulate rolling a die + } + double avg = static_cast(total) / static_cast(rolls); [endsect] diff --git a/example/Jamfile.v2 b/example/Jamfile.v2 index a57fe3f71d..1f60a5d29e 100644 --- a/example/Jamfile.v2 +++ b/example/Jamfile.v2 @@ -7,6 +7,21 @@ # accompanying file LICENSE_1_0.txt or copy at # http://www.boost.org/LICENSE_1_0.txt) +project libs/random/example + : requirements + + # boost.jam defines BOOST_ALL_NO_LIB for builds + # which cannot be undefined? + msvc:BOOST_RANDOM_FORCE_AUTO_LINK + gcc-mingw:"-lbcrypt" + + # boost::random needs this setting for a warning free build: + msvc:_SCL_SECURE_NO_WARNINGS + + # link static for easier debugging - uncomment if you need to debug... + # static +; + run die.cpp ; run weighted_die.cpp ; -run password.cpp /boost//random ; +run password.cpp ; diff --git a/example/password.cpp b/example/password.cpp index e8eef38e75..48a9c63399 100644 --- a/example/password.cpp +++ b/example/password.cpp @@ -16,9 +16,9 @@ password. */ - #include #include +#include int main() { /*<< We first define the characters that we're going diff --git a/include/boost/random/detail/auto_link.hpp b/include/boost/random/detail/auto_link.hpp deleted file mode 100644 index acbebdd4f0..0000000000 --- a/include/boost/random/detail/auto_link.hpp +++ /dev/null @@ -1,40 +0,0 @@ -/* boost random auto_link.hpp header file - * - * Copyright Steven Watanabe 2010 - * Distributed under the Boost Software License, Version 1.0. (See - * accompanying file LICENSE_1_0.txt or copy at - * http://www.boost.org/LICENSE_1_0.txt) - * - * $Id$ - */ - -#ifndef BOOST_RANDOM_DETAIL_AUTO_LINK_HPP -#define BOOST_RANDOM_DETAIL_AUTO_LINK_HPP - -#include - -#if defined(BOOST_ALL_DYN_LINK) || defined(BOOST_RANDOM_DYN_LINK) - #if defined(BOOST_RANDOM_SOURCE) - #define BOOST_RANDOM_DECL BOOST_SYMBOL_EXPORT - #else - #define BOOST_RANDOM_DECL BOOST_SYMBOL_IMPORT - #endif -#endif - -#ifndef BOOST_RANDOM_DECL - #define BOOST_RANDOM_DECL -#endif - -#if !defined(BOOST_RANDOM_NO_LIB) && !defined(BOOST_ALL_NO_LIB) && !defined(BOOST_RANDOM_SOURCE) - -#define BOOST_LIB_NAME boost_random - -#if defined(BOOST_RANDOM_DYN_LINK) || defined(BOOST_ALL_DYN_LINK) - #define BOOST_DYN_LINK -#endif - -#include - -#endif - -#endif diff --git a/include/boost/random/detail/generator_seed_seq.hpp b/include/boost/random/detail/generator_seed_seq.hpp index 7e13483464..fe568771df 100644 --- a/include/boost/random/detail/generator_seed_seq.hpp +++ b/include/boost/random/detail/generator_seed_seq.hpp @@ -1,4 +1,4 @@ -/* boost random/mersenne_twister.hpp header file +/* boost random/detail/generator_seed_seq.hpp header file * * Copyright Jens Maurer 2000-2001 * Copyright Steven Watanabe 2010 diff --git a/include/boost/random/detail/random_provider_arc4random.ipp b/include/boost/random/detail/random_provider_arc4random.ipp new file mode 100644 index 0000000000..8076c45e29 --- /dev/null +++ b/include/boost/random/detail/random_provider_arc4random.ipp @@ -0,0 +1,32 @@ +// +// Copyright (c) 2017 James E. King III +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENCE_1_0.txt) +// +// "A Replacement Call for Random" +// https://man.openbsd.org/arc4random.3 +// + +#include + +namespace boost { +namespace random { +namespace detail { + +class random_provider +{ + public: + //! Obtain entropy and place it into a memory location + //! \param[in] buf the location to write entropy + //! \param[in] siz the number of bytes to acquire + void get_random_bytes(void *buf, size_t siz) + { + arc4random_buf(buf, siz); + } +}; + +} // detail +} // random +} // boost diff --git a/include/boost/random/detail/random_provider_bcrypt.ipp b/include/boost/random/detail/random_provider_bcrypt.ipp new file mode 100644 index 0000000000..cc9e21f421 --- /dev/null +++ b/include/boost/random/detail/random_provider_bcrypt.ipp @@ -0,0 +1,79 @@ +// +// Copyright (c) 2017 James E. King III +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENCE_1_0.txt) +// +// BCrypt provider for entropy +// + +#include +#include +#include +#include +#include + +#if defined(BOOST_RANDOM_FORCE_AUTO_LINK) || (!defined(BOOST_ALL_NO_LIB) && !defined(BOOST_RANDOM_NO_LIB)) +# define BOOST_LIB_NAME "bcrypt" +# define BOOST_AUTO_LINK_NOMANGLE +# include +# undef BOOST_AUTO_LINK_NOMANGLE +#endif + +namespace boost { +namespace random { +namespace detail { + +class random_provider +{ + public: + random_provider() + : hProv_(NULL) + { + boost::winapi::NTSTATUS_ status = + boost::winapi::BCryptOpenAlgorithmProvider( + &hProv_, + boost::winapi::BCRYPT_RNG_ALGORITHM_, + NULL, + 0); + + if (status) + { + BOOST_THROW_EXCEPTION(entropy_error(status, "BCryptOpenAlgorithmProvider")); + } + } + + ~random_provider() BOOST_NOEXCEPT + { + if (hProv_) + { + ignore_unused(boost::winapi::BCryptCloseAlgorithmProvider(hProv_, 0)); + } + } + + //! Obtain entropy and place it into a memory location + //! \param[in] buf the location to write entropy + //! \param[in] siz the number of bytes to acquire + void get_random_bytes(void *buf, size_t siz) + { + boost::winapi::NTSTATUS_ status = + boost::winapi::BCryptGenRandom( + hProv_, + static_cast(buf), + siz, + 0); + + if (status) + { + BOOST_THROW_EXCEPTION(entropy_error(status, "BCryptGenRandom")); + } + } + + private: + boost::winapi::BCRYPT_ALG_HANDLE_ hProv_; +}; + +} // detail +} // random +} // boost diff --git a/include/boost/random/detail/random_provider_detect_platform.hpp b/include/boost/random/detail/random_provider_detect_platform.hpp new file mode 100644 index 0000000000..204c1b80a6 --- /dev/null +++ b/include/boost/random/detail/random_provider_detect_platform.hpp @@ -0,0 +1,62 @@ +// +// Copyright (c) 2017 James E. King III +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENCE_1_0.txt) +// +// Platform-specific random entropy provider platform detection +// + +#ifndef BOOST_RANDOM_DETAIL_RANDOM_PROVIDER_PLATFORM_DETECTION_HPP +#define BOOST_RANDOM_DETAIL_RANDOM_PROVIDER_PLATFORM_DETECTION_HPP + +#include +#include +#include +#include + +// +// Platform Detection - will load in the correct header and +// will define the class random_provider_base. +// + +#if BOOST_OS_BSD_OPEN >= BOOST_VERSION_NUMBER(2, 1, 0) || BOOST_LIB_C_CLOUDABI +# define BOOST_RANDOM_PROVIDER_ARC4RANDOM +# define BOOST_RANDOM_PROVIDER_NAME arc4random + +#elif BOOST_OS_WINDOWS +# include +# if BOOST_WINAPI_PARTITION_APP_SYSTEM && \ + !defined(BOOST_RANDOM_PROVIDER_FORCE_WINCRYPT) && \ + !defined(_WIN32_WCE) && \ + (defined(BOOST_WINAPI_IS_MINGW_W64) || \ + (BOOST_USE_WINAPI_VERSION >= BOOST_WINAPI_VERSION_WIN6)) +# define BOOST_RANDOM_PROVIDER_BCRYPT +# define BOOST_RANDOM_PROVIDER_NAME bcrypt + +# elif BOOST_WINAPI_PARTITION_DESKTOP || BOOST_WINAPI_PARTITION_SYSTEM +# define BOOST_RANDOM_PROVIDER_WINCRYPT +# define BOOST_RANDOM_PROVIDER_NAME wincrypt +# else +# error Unable to find a suitable windows entropy provider +# endif + +#elif BOOST_LIB_C_GNU >= BOOST_VERSION_NUMBER(2, 25, 0) && !defined(BOOST_RANDOM_PROVIDER_FORCE_POSIX) +# define BOOST_RANDOM_PROVIDER_GETENTROPY +# define BOOST_RANDOM_PROVIDER_NAME getentropy + +#else +# define BOOST_RANDOM_PROVIDER_POSIX +# define BOOST_RANDOM_PROVIDER_NAME posix + +#endif + +#define BOOST_RANDOM_PROVIDER_STRINGIFY2(X) #X +#define BOOST_RANDOM_PROVIDER_STRINGIFY(X) BOOST_RANDOM_PROVIDER_STRINGIFY2(X) + +#if defined(BOOST_RANDOM_PROVIDER_SHOW) +#pragma message("BOOST_RANDOM_PROVIDER_NAME " BOOST_RANDOM_PROVIDER_STRINGIFY(BOOST_RANDOM_PROVIDER_NAME)) +#endif + +#endif // BOOST_RANDOM_DETAIL_RANDOM_PROVIDER_PLATFORM_DETECTION_HPP diff --git a/include/boost/random/detail/random_provider_getentropy.ipp b/include/boost/random/detail/random_provider_getentropy.ipp new file mode 100644 index 0000000000..339412e7d8 --- /dev/null +++ b/include/boost/random/detail/random_provider_getentropy.ipp @@ -0,0 +1,38 @@ +// +// Copyright (c) 2017 James E. King III +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENCE_1_0.txt) +// +// getentropy() capable platforms +// + +#include +#include +#include +#include + +namespace boost { +namespace random { +namespace detail { + +class random_provider +{ + public: + //! Obtain entropy and place it into a memory location + //! \param[in] buf the location to write entropy + //! \param[in] siz the number of bytes to acquire + void get_random_bytes(void *buf, size_t siz) + { + if (-1 == getentropy(buf, siz)) + { + int err = errno; + BOOST_THROW_EXCEPTION(entropy_error(err, "getentropy")); + } + } +}; + +} // detail +} // random +} // boost diff --git a/include/boost/random/detail/random_provider_include_platform.hpp b/include/boost/random/detail/random_provider_include_platform.hpp new file mode 100644 index 0000000000..70db1774e0 --- /dev/null +++ b/include/boost/random/detail/random_provider_include_platform.hpp @@ -0,0 +1,29 @@ +// +// Copyright (c) 2017 James E. King III +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENCE_1_0.txt) +// +// Platform-specific random entropy provider platform definition +// + +#ifndef BOOST_RANDOM_DETAIL_RANDOM_PROVIDER_PLATFORM_INCLUDE_HPP +#define BOOST_RANDOM_DETAIL_RANDOM_PROVIDER_PLATFORM_INCLUDE_HPP + +#if defined(BOOST_RANDOM_PROVIDER_ARC4RANDOM) +# include +#elif defined(BOOST_RANDOM_PROVIDER_BCRYPT) +# include +#elif defined(BOOST_RANDOM_PROVIDER_GETENTROPY) +# include +#elif defined(BOOST_RANDOM_PROVIDER_POSIX) +# include +#elif defined(BOOST_RANDOM_PROVIDER_WINCRYPT) +# include +#else +# error "Unknown random provider platform" +#endif + +#endif // BOOST_RANDOM_DETAIL_RANDOM_PROVIDER_PLATFORM_INCLUDE_HPP + diff --git a/include/boost/random/detail/random_provider_posix.ipp b/include/boost/random/detail/random_provider_posix.ipp new file mode 100644 index 0000000000..3d822447fc --- /dev/null +++ b/include/boost/random/detail/random_provider_posix.ipp @@ -0,0 +1,95 @@ +/* boost random/detail/random_provider_posix implementation +* +* Copyright Jens Maurer 2000 +* Copyright 2007 Andy Tompkins. +* Copyright Steven Watanabe 2010-2011 +* Copyright 2017 James E. King III +* +* Distributed under the Boost Software License, Version 1.0. (See +* accompanying file LICENSE_1_0.txt or copy at +* http://www.boost.org/LICENSE_1_0.txt) +* +* $Id$ +*/ + +#include +#include +#include +#include +#include // open +#include +#include +#if defined(BOOST_HAS_UNISTD_H) +#include +#endif + +#ifndef BOOST_RANDOM_PROVIDER_POSIX_IMPL_CLOSE +#define BOOST_RANDOM_PROVIDER_POSIX_IMPL_CLOSE ::close +#endif +#ifndef BOOST_RANDOM_PROVIDER_POSIX_IMPL_OPEN +#define BOOST_RANDOM_PROVIDER_POSIX_IMPL_OPEN ::open +#endif +#ifndef BOOST_RANDOM_PROVIDER_POSIX_IMPL_READ +#define BOOST_RANDOM_PROVIDER_POSIX_IMPL_READ ::read +#endif + +namespace boost { +namespace random { +namespace detail { + +class random_provider +{ + public: + random_provider() + : fd_(0) + { + int flags = O_RDONLY; +#if defined(O_CLOEXEC) + flags |= O_CLOEXEC; +#endif + fd_ = BOOST_RANDOM_PROVIDER_POSIX_IMPL_OPEN("/dev/urandom", flags); + + if (-1 == fd_) + { + int err = errno; + BOOST_THROW_EXCEPTION(entropy_error(err, "open /dev/urandom")); + } + } + + ~random_provider() BOOST_NOEXCEPT + { + if (fd_) + { + ignore_unused(BOOST_RANDOM_PROVIDER_POSIX_IMPL_CLOSE(fd_)); + } + } + + //! Obtain entropy and place it into a memory location + //! \param[in] buf the location to write entropy + //! \param[in] siz the number of bytes to acquire + void get_random_bytes(void *buf, size_t siz) + { + size_t offset = 0; + do + { + ssize_t sz = BOOST_RANDOM_PROVIDER_POSIX_IMPL_READ( + fd_, static_cast(buf) + offset, siz - offset); + + if (sz < 1) + { + int err = errno; + BOOST_THROW_EXCEPTION(entropy_error(err, "read")); + } + + offset += sz; + + } while (offset < siz); + } + + private: + int fd_; +}; + +} // detail +} // random +} // boost diff --git a/include/boost/random/detail/random_provider_wincrypt.ipp b/include/boost/random/detail/random_provider_wincrypt.ipp new file mode 100644 index 0000000000..747288195c --- /dev/null +++ b/include/boost/random/detail/random_provider_wincrypt.ipp @@ -0,0 +1,81 @@ +/* boost random/detail/random_provider_wincrypt implementation +* +* Copyright Jens Maurer 2000 +* Copyright 2007 Andy Tompkins. +* Copyright Steven Watanabe 2010-2011 +* Copyright 2017 James E. King III +* +* Distributed under the Boost Software License, Version 1.0. (See +* accompanying file LICENSE_1_0.txt or copy at +* http://www.boost.org/LICENSE_1_0.txt) +* +* $Id$ +*/ + +#include +#include +#include +#include +#include + +#if defined(BOOST_RANDOM_FORCE_AUTO_LINK) || (!defined(BOOST_ALL_NO_LIB) && !defined(BOOST_RANDOM_NO_LIB)) +# if defined(_WIN32_WCE) +# define BOOST_LIB_NAME "coredll" +# else +# define BOOST_LIB_NAME "advapi32" +# endif +# define BOOST_AUTO_LINK_NOMANGLE +# include +# undef BOOST_AUTO_LINK_NOMANGLE +#endif + +namespace boost { +namespace random { +namespace detail { + +class random_provider +{ + public: + random_provider() + : hProv_(NULL) + { + if (!boost::winapi::CryptAcquireContextW( + &hProv_, + NULL, + NULL, + boost::winapi::PROV_RSA_FULL_, + boost::winapi::CRYPT_VERIFYCONTEXT_ | boost::winapi::CRYPT_SILENT_)) + { + boost::winapi::DWORD_ err = boost::winapi::GetLastError(); + BOOST_THROW_EXCEPTION(entropy_error(err, "CryptAcquireContext")); + } + } + + ~random_provider() BOOST_NOEXCEPT + { + if (hProv_) + { + ignore_unused(boost::winapi::CryptReleaseContext(hProv_, 0)); + } + } + + //! Obtain entropy and place it into a memory location + //! \param[in] buf the location to write entropy + //! \param[in] siz the number of bytes to acquire + void get_random_bytes(void *buf, size_t siz) + { + if (!boost::winapi::CryptGenRandom(hProv_, siz, + static_cast(buf))) + { + boost::winapi::DWORD_ err = boost::winapi::GetLastError(); + BOOST_THROW_EXCEPTION(entropy_error(err, "CryptGenRandom")); + } + } + + private: + boost::winapi::HCRYPTPROV_ hProv_; +}; + +} // detail +} // random +} // boost diff --git a/include/boost/random/entropy_error.hpp b/include/boost/random/entropy_error.hpp new file mode 100644 index 0000000000..a273fcd784 --- /dev/null +++ b/include/boost/random/entropy_error.hpp @@ -0,0 +1,46 @@ +// +// Copyright (c) 2017 James E. King III +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENCE_1_0.txt) +// +// Entropy error class +// + +#ifndef BOOST_RANDOM_ENTROPY_ERROR_HPP +#define BOOST_RANDOM_ENTROPY_ERROR_HPP + +#include +#include +#include + +namespace boost { +namespace random { + +//! \brief Given boost::system::system_error is in a module that +//! is not header-only, we define our own exception type +//! to handle entropy provider errors instead. +class entropy_error : public std::runtime_error +{ +public: + entropy_error(boost::intmax_t errCode, const std::string& message) + : std::runtime_error(message) + , m_errcode(errCode) + { + } + + virtual boost::intmax_t errcode() const + { + return m_errcode; + } + +private: + boost::intmax_t m_errcode; +}; + +} // random +} // boost + +#endif // BOOST_RANDOM_ENTROPY_ERROR_HPP + diff --git a/include/boost/random/random_device.hpp b/include/boost/random/random_device.hpp index 8f3903c953..40892951d8 100644 --- a/include/boost/random/random_device.hpp +++ b/include/boost/random/random_device.hpp @@ -2,6 +2,8 @@ * * Copyright Jens Maurer 2000 * Copyright Steven Watanabe 2010-2011 + * Copyright 2017 James E. King III + * * Distributed under the Boost Software License, Version 1.0. (See * accompanying file LICENSE_1_0.txt or copy at * http://www.boost.org/LICENSE_1_0.txt) @@ -10,6 +12,7 @@ * * Revision history * 2000-02-18 Portability fixes (thanks to Beman Dawes) + * 2017-12-05 Converted to use random_provider */ // See http://www.boost.org/libs/random for documentation. @@ -18,17 +21,22 @@ #ifndef BOOST_RANDOM_RANDOM_DEVICE_HPP #define BOOST_RANDOM_RANDOM_DEVICE_HPP -#include -#include -#include -#include -#include // force autolink to find Boost.System +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace boost { namespace random { /** - * Class \random_device models a \nondeterministic_random_number_generator. + * Class \random_device models a non-deterministic UniformRandomNumberGenerator. * It uses one or more implementation-defined stochastic processes to * generate a sequence of uniformly distributed non-deterministic random * numbers. For those environments where a non-deterministic random number @@ -50,88 +58,100 @@ namespace random { * on all platforms. * @endxmlnote * - * Implementation Note for Linux + * \random_device offers two methods for entropy generation: * - * On the Linux operating system, token is interpreted as a filesystem - * path. It is assumed that this path denotes an operating system - * pseudo-device which generates a stream of non-deterministic random - * numbers. The pseudo-device should never signal an error or end-of-file. - * Otherwise, @c std::ios_base::failure is thrown. By default, - * \random_device uses the /dev/urandom pseudo-device to retrieve - * the random numbers. Another option would be to specify the /dev/random - * pseudo-device, which blocks on reads if the entropy pool has no more - * random bits available. + * 1. Call operator() to generate a random unsigned integer + * 2. Call get_random_bytes(void *, size_t) to fill any memory + * area with random data. * - * Implementation Note for Windows + * The selection logic for the entropy provider is as follows: * - * On the Windows operating system, token is interpreted as the name - * of a cryptographic service provider. By default \random_device uses - * MS_DEF_PROV. + * 1. On CloudABI, or on OpenBSD version 2.1 or later, `arc4random` will be used. + * 2. On Windows platforms, the `bcrypt` provider is used unless targeting Windows CE or Windows XP, where the `wincrypt` provider is used. + * 3. On Linux platforms with glibc >= 2.25, `getentropy` is used, otherwise it is treated as a POSIX platform. + * 4. On POSIX platforms, entropy is obtained by reading from `/dev/urandom`. * - * Performance + * The following macros can be used: * - * The test program - * nondet_random_speed.cpp measures the execution times of the - * random_device.hpp implementation of the above algorithms in a tight - * loop. The performance has been evaluated on an - * Intel(R) Core(TM) i7 CPU Q 840 \@ 1.87GHz, 1867 Mhz with - * Visual C++ 2010, Microsoft Windows 7 Professional and with gcc 4.4.5, - * Ubuntu Linux 2.6.35-25-generic. + * * `BOOST_RANDOM_NO_LIB` (Windows) - disable auto-linking for the `bcrypt` and `wincrypt` provider when building with MSVC + * * `BOOST_RANDOM_PROVIDER_SHOW` - display the chosen entropy provider at compile time * - * - * - * - * - *
Platformtime per invocation [microseconds]
Windows 2.9
Linux 1.7
- * - * The measurement error is estimated at +/- 1 usec. + * [endsect] */ -class random_device : private noncopyable +class random_device : public noncopyable, public detail::random_provider { public: + //! \note required by the UniformRandomNumberGenerator concept. typedef unsigned int result_type; + + //! \note this is an old Boost.Random concept requirement BOOST_STATIC_CONSTANT(bool, has_fixed_range = false); - /** Returns the smallest value that the \random_device can produce. */ - static result_type min BOOST_PREVENT_MACRO_SUBSTITUTION () { return 0; } - /** Returns the largest value that the \random_device can produce. */ - static result_type max BOOST_PREVENT_MACRO_SUBSTITUTION () { return ~0u; } - - /** Constructs a @c random_device, optionally using the default device. */ - BOOST_RANDOM_DECL random_device(); - /** - * Constructs a @c random_device, optionally using the given token as an - * access specification (for example, a URL) to some implementation-defined - * service for monitoring a stochastic process. - */ - BOOST_RANDOM_DECL explicit random_device(const std::string& token); - - BOOST_RANDOM_DECL ~random_device(); - - /** - * Returns: An entropy estimate for the random numbers returned by - * operator(), in the range min() to log2( max()+1). A deterministic - * random number generator (e.g. a pseudo-random number engine) - * has entropy 0. - * - * Throws: Nothing. - */ - BOOST_RANDOM_DECL double entropy() const; - /** Returns a random value in the range [min, max]. */ - BOOST_RANDOM_DECL unsigned int operator()(); - - /** Fills a range with random 32-bit values. */ + //! \returns the smallest value that the \random_device can produce. + //! \note required by the UniformRandomNumberGenerator concept. + static result_type min BOOST_PREVENT_MACRO_SUBSTITUTION () + { + return std::numeric_limits::min(); + } + + //! \returns the largest value that the random_device can produce. + //! \note required by the UniformRandomNumberGenerator concept. + static result_type max BOOST_PREVENT_MACRO_SUBSTITUTION () + { + return std::numeric_limits::max(); + } + + //! \returns a random value in the range [min(), max()]. + //! \note required by the UniformRandomNumberGenerator concept. + //! \throws entropy_error + result_type operator()() + { + result_type e; + get_random_bytes(&e, sizeof(result_type)); + return e; + } + + //! \returns An entropy estimate for the random numbers returned by + //! operator(), in the range min() to log2(max()+1). + //! \note A deterministic random number generator (e.g. a pseudo-random + //! number engine) has entropy 0. + double entropy() const + { + return 10; + } + + //! \brief Allow \random_device to fulfill the SeedSeq concept + //! and therefore be compatible as a PseudoRandomNumberGenerator + //! seed() argument. + //! \param first the first of the sequence requested + //! \param last the last of the sequence requested + //! \throws entropy_error + //! \example + //! boost::random_device rng; + //! boost::mt19937 twister; + //! twister.seed(rng); + //! template - void generate(Iter begin, Iter end) + void generate(Iter first, Iter last) { - for(; begin != end; ++begin) { - *begin = (*this)(); + typedef typename std::iterator_traits::value_type value_type; + BOOST_STATIC_ASSERT(is_integral::value); + BOOST_STATIC_ASSERT(is_unsigned::value); + BOOST_STATIC_ASSERT(sizeof(value_type) * CHAR_BIT >= 32); + + for (; first != last; ++first) + { + get_random_bytes(&*first, sizeof(*first)); + *first &= (std::numeric_limits::max)(); } } -private: - class impl; - impl * pimpl; + //! \returns the name of the selected entropy provider + //! \note this value is not guaranteed to be stable across boost releases + const char * name() const + { + return BOOST_RANDOM_PROVIDER_STRINGIFY(BOOST_RANDOM_PROVIDER_NAME); + } }; } // namespace random diff --git a/src/random_device.cpp b/src/random_device.cpp deleted file mode 100644 index 8ec3863107..0000000000 --- a/src/random_device.cpp +++ /dev/null @@ -1,250 +0,0 @@ -/* boost random_device.cpp implementation - * - * Copyright Jens Maurer 2000 - * Copyright Steven Watanabe 2010-2011 - * Distributed under the Boost Software License, Version 1.0. (See - * accompanying file LICENSE_1_0.txt or copy at - * http://www.boost.org/LICENSE_1_0.txt) - * - * $Id$ - * - */ - -#define BOOST_RANDOM_SOURCE - -#include -#include -#include -#include -#include -#include -#include -#include - -#if !defined(BOOST_NO_INCLASS_MEMBER_INITIALIZATION) && !BOOST_WORKAROUND(BOOST_MSVC, BOOST_TESTED_AT(1600)) -// A definition is required even for integral static constants -const bool boost::random::random_device::has_fixed_range; -#endif - -// WinRT target. -#if !defined(BOOST_RANDOM_WINDOWS_RUNTIME) -# if defined(__cplusplus_winrt) -# include -# if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP) -# define BOOST_RANDOM_WINDOWS_RUNTIME 1 -# endif -# endif -#endif - -#if defined(BOOST_WINDOWS) - -#if !defined(BOOST_RANDOM_WINDOWS_RUNTIME) -#include -#include -#include // std::invalid_argument -#else -using namespace Platform; -using namespace Windows::Security::Cryptography; -#endif - -#define BOOST_AUTO_LINK_NOMANGLE -#define BOOST_LIB_NAME "Advapi32" -#include - -#ifdef __MINGW32__ - -extern "C" { - -// mingw's wincrypt.h appears to be missing some things -WINADVAPI -BOOL -WINAPI -CryptEnumProvidersA( - DWORD dwIndex, - DWORD *pdwReserved, - DWORD dwFlags, - DWORD *pdwProvType, - LPSTR szProvName, - DWORD *pcbProvName - ); - -} - -#endif - -namespace { -#if !defined(BOOST_RANDOM_WINDOWS_RUNTIME) -const char * const default_token = MS_DEF_PROV_A; -#else -const char * const default_token = ""; -#endif -} - -class boost::random::random_device::impl -{ -public: - impl(const std::string & token) : provider(token) { -#if !defined(BOOST_RANDOM_WINDOWS_RUNTIME) - char buffer[80]; - DWORD type; - DWORD len; - - // Find the type of a specific provider - for(DWORD i = 0; ; ++i) { - len = sizeof(buffer); - if(!CryptEnumProvidersA(i, NULL, 0, &type, buffer, &len)) { - if (GetLastError() == ERROR_NO_MORE_ITEMS) break; - continue; - } - if(buffer == provider) { - break; - } - } - - if(!CryptAcquireContextA(&hProv, NULL, provider.c_str(), type, - CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) { - error("Could not acquire CSP context"); - } -#endif - } - -#if !defined(BOOST_RANDOM_WINDOWS_RUNTIME) - ~impl() { - if(!CryptReleaseContext(hProv, 0)) error("Could not release CSP context"); - } -#endif - - unsigned int next() { - unsigned int result; - -#if !defined(BOOST_RANDOM_WINDOWS_RUNTIME) - if(!CryptGenRandom(hProv, sizeof(result), - static_cast(static_cast(&result)))) { - error("error while reading"); - } -#else - auto buffer = CryptographicBuffer::GenerateRandom(sizeof(result)); - auto data = ref new Array(buffer->Length); - CryptographicBuffer::CopyToByteArray(buffer, &data); - memcpy(&result, data->begin(), data->end() - data->begin()); -#endif - - return result; - } - -private: -#if !defined(BOOST_RANDOM_WINDOWS_RUNTIME) - void error(const char * msg) { - DWORD error_code = GetLastError(); - boost::throw_exception( - boost::system::system_error( - error_code, boost::system::system_category(), - std::string("boost::random_device: ") + msg + - " Cryptographic Service Provider " + provider)); - } - HCRYPTPROV hProv; -#endif - const std::string provider; -}; - -#else - -namespace { -// the default is the unlimited capacity device, using some secure hash -// try "/dev/random" for blocking when the entropy pool has drained -const char * const default_token = "/dev/urandom"; -} - -/* - * This uses the POSIX interface for unbuffered reading. - * Using buffered std::istream would consume entropy which may - * not actually be used. Entropy is a precious good we avoid - * wasting. - */ - -#if defined(__GNUC__) && defined(_CXXRT_STD_NAME) -// I have severe difficulty to get the POSIX includes to work with -// -fhonor-std and Dietmar Kuhl's standard C++ library. Hack around that -// problem for now. -extern "C" { -static const int O_RDONLY = 0; -extern int open(const char *__file, int __oflag, ...); -extern int read(int __fd, __ptr_t __buf, size_t __nbytes); -extern int close(int __fd); -} -#else -#include -#include -#include // open -#include // read, close -#endif - -#include // errno -#include // strerror -#include // std::invalid_argument - - -class boost::random::random_device::impl -{ -public: - impl(const std::string & token) : path(token) { - fd = open(token.c_str(), O_RDONLY); - if(fd < 0) - error("cannot open"); - } - - ~impl() { if(close(fd) < 0) error("could not close"); } - - unsigned int next() { - unsigned int result; - std::size_t offset = 0; - do { - long sz = read(fd, reinterpret_cast(&result) + offset, sizeof(result) - offset); - if(sz == -1) - error("error while reading"); - else if(sz == 0) { - errno = 0; - error("EOF while reading"); - } - offset += sz; - } while(offset < sizeof(result)); - return result; - } - -private: - void error(const char * msg) { - int error_code = errno; - boost::throw_exception( - boost::system::system_error( - error_code, boost::system::system_category(), - std::string("boost::random_device: ") + msg + - " random-number pseudo-device " + path)); - } - const std::string path; - int fd; -}; - -#endif // BOOST_WINDOWS - -BOOST_RANDOM_DECL boost::random::random_device::random_device() - : pimpl(new impl(default_token)) -{} - -BOOST_RANDOM_DECL boost::random::random_device::random_device(const std::string& token) - : pimpl(new impl(token)) -{} - -BOOST_RANDOM_DECL boost::random_device::~random_device() -{ - delete pimpl; -} - -BOOST_RANDOM_DECL double boost::random_device::entropy() const -{ - return 10; -} - -BOOST_RANDOM_DECL unsigned int boost::random_device::operator()() -{ - return pimpl->next(); -} diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 25424bc671..3e2e90b8c9 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -1,5 +1,7 @@ # Copyright 2003 Jens Maurer # Copyright 2009-2011 Steven Watanabe +# Copyright 2017 James E. King III +# # Distributed under the Boost Software License, Version 1.0. (See accompany- # ing file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -8,14 +10,25 @@ # bring in rules for testing import testing ; -project /boost/random/test : requirements msvc:_SCL_SECURE_NO_WARNINGS ; +project libs/random/test + : requirements + + # boost.jam defines BOOST_ALL_NO_LIB for builds + # which cannot be undefined? + msvc:BOOST_RANDOM_FORCE_AUTO_LINK + gcc-mingw:"-lbcrypt" + + # boost::random needs this setting for a warning free build: + msvc:_SCL_SECURE_NO_WARNINGS + + # link static for easier debugging, if you need to debug... + # static +; run test_const_mod.cpp /boost//unit_test_framework ; run test_generate_canonical.cpp /boost//unit_test_framework ; run test_random_number_generator.cpp /boost//unit_test_framework ; -run ../example/random_demo.cpp ; -run test_random_device.cpp /boost//random : : : static : test_random_device ; -run test_random_device.cpp /boost//random : : : shared : test_random_device_dll ; +run test_random_device.cpp /boost//unit_test_framework ; run test_minstd_rand0.cpp /boost//unit_test_framework ; run test_minstd_rand.cpp /boost//unit_test_framework ; @@ -124,11 +137,6 @@ run test_non_central_chi_squared_distribution.cpp /boost//unit_test_framework ; run test_hyperexponential.cpp ; run test_hyperexponential_distribution.cpp /boost//unit_test_framework ; -# run nondet_random_speed.cpp ; -# run random_device.cpp ; -# run random_speed.cpp ; -# run statistic_tests.cpp ; - exe statistic_tests.exe : statistic_tests.cpp ; explicit statistic_tests.exe ; @@ -141,3 +149,87 @@ explicit statistic_tests ; run multiprecision_int_test.cpp /boost//unit_test_framework ; # This one runs too slow in debug mode, we really need inline expansions turned on amonst other things: run multiprecision_float_test.cpp /boost//unit_test_framework : : : release ; + +# +# random_provider tests: +# + +# a small benchmark test for random generation +run test_bench_random.cpp ../../timer/build//boost_timer : : : clang-cloudabi:no static ; + +run test_entropy_error.cpp ; + +# tests for the header-only random provider +# there are a number of variations to test all compile-time branches +# and to make sure we test all the error handling code paths + +run test_detail_random_provider.cpp + : : : + BOOST_RANDOM_PROVIDER_SHOW # output the selected provider at compile time + : test_detail_random_provider_happy_default ; # test the auto-selected happy path + +run test_detail_random_provider.cpp + : : : + _WIN32_WINNT=0x0600 # will force bcrypt over wincrypt + BOOST_RANDOM_PROVIDER_SHOW # output the selected provider at compile time + no # do not build + windows:yes # except for windows + : test_detail_random_provider_happy_bcrypt ; + +run test_detail_random_provider.cpp + mock_random + : : : + _WIN32_WINNT=0x0600 # will force bcrypt over wincrypt + BOOST_RANDOM_PROVIDER_NO_LIB # disable any auto-linking + BOOST_RANDOM_PROVIDER_SHOW # output the selected provider at compile time + BOOST_RANDOM_TEST_RANDOM_MOCK # mock wincrypt to force error path testing + no # do not build + windows:yes # except for windows + : test_detail_random_provider_sad_bcrypt ; + +# mock testing the wincrypt paths requires a DLL +lib mock_random + : mock_random.cpp + : shared + no # do not build on any target-os + windows:yes ; # except for windows + +run test_detail_random_provider.cpp + : : : + _WIN32_WINNT=0x0501 # will force wincrypt over bcrypt + BOOST_RANDOM_PROVIDER_SHOW # output the selected provider at compile time + no # do not build on any target-os + windows:yes # except for windows + : test_detail_random_provider_happy_wincrypt ; + +run test_detail_random_provider.cpp + mock_random + : : : + _WIN32_WINNT=0x0501 # will force wincrypt over bcrypt + BOOST_RANDOM_PROVIDER_NO_LIB # disable any auto-linking + BOOST_RANDOM_PROVIDER_SHOW # output the selected provider at compile time + BOOST_RANDOM_TEST_RANDOM_MOCK # mock wincrypt to force error path testing + no # do not build on any target-os + windows:yes # except for windows + : test_detail_random_provider_sad_wincrypt ; + +# CI builds in travis will eventually select getentropy when they move +# to a version of ubuntu with glibc-2.25 on it, so when that happens keep +# testing the posix provider: +run test_detail_random_provider.cpp + : : : + BOOST_RANDOM_PROVIDER_FORCE_POSIX # will force POSIX over getentropy + BOOST_RANDOM_PROVIDER_SHOW # output the selected provider at compile time + windows:no # do not bother running on windows + clang-cloudabi:no # no need to build under cloudabi + : test_detail_random_provider_happy_posix ; + +run test_detail_random_provider.cpp + : : : + BOOST_RANDOM_PROVIDER_FORCE_POSIX # will force POSIX over getentropy + BOOST_RANDOM_PROVIDER_SHOW # output the selected provider at compile time + BOOST_RANDOM_TEST_RANDOM_MOCK # redirect code to use mock system calls + windows:no # do not bother running on windows + clang-cloudabi:no # no need to build under cloudabi + : test_detail_random_provider_sad_posix ; + diff --git a/test/mock_random.cpp b/test/mock_random.cpp new file mode 100644 index 0000000000..5ae4689542 --- /dev/null +++ b/test/mock_random.cpp @@ -0,0 +1,115 @@ +// +// Copyright (c) 2017 James E. King III +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENCE_1_0.txt) +// +// The contents of this file are compiled into a loadable +// library that is used for mocking purposes so that the error +// paths in the random_provider implementations are exercised. +// + +#include +#include + +#if defined(BOOST_WINDOWS) + +#include + +// WinAPI is not currently set up well for building mocks, as +// the definitions of wincrypt APIs all use BOOST_SYMBOL_IMPORT +// therefore we cannot include it, but we need some of the types +// so they are defined here... +namespace boost { +namespace winapi { + typedef ULONG_PTR_ HCRYPTPROV_; +} +} + +// wincrypt has to be mocked through a DLL pretending to be +// the real thing as the official APIs use __declspec(dllimport) + +#include +std::deque wincrypt_next_result; + +BOOST_SYMBOL_EXPORT bool expectations_capable() +{ + return true; +} + +BOOST_SYMBOL_EXPORT bool expectations_met() +{ + return wincrypt_next_result.empty(); +} + +BOOST_SYMBOL_EXPORT void expect_next_call_success(bool success) +{ + wincrypt_next_result.push_back(success ? 1 : 0); +} + +BOOST_SYMBOL_EXPORT bool provider_acquires_context() +{ + return true; +} + +extern "C" { + +BOOST_SYMBOL_EXPORT +boost::winapi::BOOL_ WINAPI +CryptAcquireContextW( + boost::winapi::HCRYPTPROV_ *phProv, + boost::winapi::LPCWSTR_ szContainer, + boost::winapi::LPCWSTR_ szProvider, + boost::winapi::DWORD_ dwProvType, + boost::winapi::DWORD_ dwFlags) +{ + boost::ignore_unused(phProv); + boost::ignore_unused(szContainer); + boost::ignore_unused(szProvider); + boost::ignore_unused(dwProvType); + boost::ignore_unused(dwFlags); + + boost::winapi::BOOL_ result = wincrypt_next_result.front(); + wincrypt_next_result.pop_front(); + return result; +} + +BOOST_SYMBOL_EXPORT +boost::winapi::BOOL_ WINAPI +CryptGenRandom( + boost::winapi::HCRYPTPROV_ hProv, + boost::winapi::DWORD_ dwLen, + boost::winapi::BYTE_ *pbBuffer) +{ + boost::ignore_unused(hProv); + boost::ignore_unused(dwLen); + boost::ignore_unused(pbBuffer); + + boost::winapi::BOOL_ result = wincrypt_next_result.front(); + wincrypt_next_result.pop_front(); + return result; +} + +// the implementation ignores the result of close because it +// happens in a destructor +BOOST_SYMBOL_EXPORT +boost::winapi::BOOL_ WINAPI +CryptReleaseContext( + boost::winapi::HCRYPTPROV_ hProv, +#if defined(_MSC_VER) && (_MSC_VER+0) >= 1500 && (_MSC_VER+0) < 1900 && BOOST_USE_NTDDI_VERSION < BOOST_WINAPI_NTDDI_WINXP + // see winapi crypt.hpp for more details on why these differ... + boost::winapi::ULONG_PTR_ dwFlags +#else + boost::winapi::DWORD_ dwFlags +#endif +) +{ + boost::ignore_unused(hProv); + boost::ignore_unused(dwFlags); + return true; +} + +} // end extern "C" + +#endif diff --git a/test/mock_random.hpp b/test/mock_random.hpp new file mode 100644 index 0000000000..779ba352db --- /dev/null +++ b/test/mock_random.hpp @@ -0,0 +1,248 @@ +// +// Copyright (c) 2017 James E. King III +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENCE_1_0.txt) +// +// Mocks are used to test sad paths by forcing error responses +// + +#include +#include + +#if defined(BOOST_RANDOM_TEST_RANDOM_MOCK) + +#if defined(BOOST_RANDOM_PROVIDER_WINCRYPT) || defined(BOOST_RANDOM_PROVIDER_POSIX) +#define BOOST_RANDOM_TEST_RANDOM_MOCK_LINKAGE BOOST_SYMBOL_IMPORT +#else +#define BOOST_RANDOM_TEST_RANDOM_MOCK_LINKAGE +#endif + +//! \returns true if the provider can be mocked - if not then the test +//! should skip negative testing +BOOST_RANDOM_TEST_RANDOM_MOCK_LINKAGE bool expectations_capable(); + +//! Ensure all expectations for calls were consumed. This means the number +//! of expected calls was met. +//! \returns true if all expectations were met +BOOST_RANDOM_TEST_RANDOM_MOCK_LINKAGE bool expectations_met(); + +//! Set the response of the next mocked random/crypto call - builds up +//! a queue of responses. If the queue empties and another call is made, +//! the test will core. +//! \param[in] success true for success response, false for failure +BOOST_RANDOM_TEST_RANDOM_MOCK_LINKAGE void expect_next_call_success(bool success); + +//! \returns true if the provider acquires a context +BOOST_RANDOM_TEST_RANDOM_MOCK_LINKAGE bool provider_acquires_context(); + +#if defined(BOOST_RANDOM_PROVIDER_ARC4RANDOM) + +// arc4random cannot fail therefore it needs no mocking at all! + +bool expectations_capable() +{ + return false; +} + +bool expectations_met() +{ + throw std::logic_error("expectations not supported"); +} + +void expect_next_call_success(bool success) +{ + boost::ignore_unused(success); + throw std::logic_error("expectations not supported"); +} + +bool provider_acquires_context() +{ + throw std::logic_error("expectations not supported"); +} + +#elif defined(BOOST_RANDOM_PROVIDER_BCRYPT) + +#include +#include +std::deque bcrypt_next_result; + +bool expectations_capable() +{ + return true; +} + +bool expectations_met() +{ + return bcrypt_next_result.empty(); +} + +void expect_next_call_success(bool success) +{ + bcrypt_next_result.push_back(success ? 0 : 17); +} + +bool provider_acquires_context() +{ + return true; +} + +boost::winapi::NTSTATUS_ WINAPI +BCryptOpenAlgorithmProvider( + boost::winapi::BCRYPT_ALG_HANDLE_ *phAlgorithm, + boost::winapi::LPCWSTR_ pszAlgId, + boost::winapi::LPCWSTR_ pszImplementation, + boost::winapi::DWORD_ dwFlags +) +{ + boost::ignore_unused(phAlgorithm); + boost::ignore_unused(pszAlgId); + boost::ignore_unused(pszImplementation); + boost::ignore_unused(dwFlags); + + boost::winapi::NTSTATUS_ result = bcrypt_next_result.front(); + bcrypt_next_result.pop_front(); + return result; +} + +boost::winapi::NTSTATUS_ WINAPI +BCryptGenRandom( + boost::winapi::BCRYPT_ALG_HANDLE_ hAlgorithm, + boost::winapi::PUCHAR_ pbBuffer, + boost::winapi::ULONG_ cbBuffer, + boost::winapi::ULONG_ dwFlags +) +{ + boost::ignore_unused(hAlgorithm); + boost::ignore_unused(pbBuffer); + boost::ignore_unused(cbBuffer); + boost::ignore_unused(dwFlags); + + boost::winapi::NTSTATUS_ result = bcrypt_next_result.front(); + bcrypt_next_result.pop_front(); + return result; +} + +// the implementation ignores the result of close because it +// happens in a destructor +boost::winapi::NTSTATUS_ WINAPI +BCryptCloseAlgorithmProvider( + boost::winapi::BCRYPT_ALG_HANDLE_ hAlgorithm, + boost::winapi::ULONG_ dwFlags +) +{ + boost::ignore_unused(hAlgorithm); + boost::ignore_unused(dwFlags); + return 0; +} + +#elif defined(BOOST_RANDOM_PROVIDER_GETENTROPY) + +// +// This stubbing technique works on unix because of how the loader resolves +// functions. Locally defined functions resolve first. +// + +#include +#include +std::deque getentropy_next_result; + +bool expectations_capable() +{ + return true; +} + +bool expectations_met() +{ + return getentropy_next_result.empty(); +} + +void expect_next_call_success(bool success) +{ + getentropy_next_result.push_back(success ? 0 : -1); +} + +bool provider_acquires_context() +{ + return false; +} + +int getentropy(void *buffer, size_t length) +{ + boost::ignore_unused(buffer); + boost::ignore_unused(length); + + int result = getentropy_next_result.front(); + getentropy_next_result.pop_front(); + return result; +} + +#elif defined(BOOST_RANDOM_PROVIDER_POSIX) + +#include +#include +#include +std::deque posix_next_result; // bool success + +bool expectations_capable() +{ + return true; +} + +bool expectations_met() +{ + return posix_next_result.empty(); +} + +void expect_next_call_success(bool success) +{ + posix_next_result.push_back(success); +} + +bool provider_acquires_context() +{ + return true; +} + +int mockopen(const char *fname, int flags) +{ + boost::ignore_unused(fname); + boost::ignore_unused(flags); + + bool success = posix_next_result.front(); + posix_next_result.pop_front(); + return success ? 17 : -1; +} + +ssize_t mockread(int fd, void *buf, size_t siz) +{ + boost::ignore_unused(fd); + boost::ignore_unused(buf); + + // first call siz is 4, in a success case we return 1 + // forcing a second loop to come through size 3 + + if (siz < 4) { return boost::numeric_cast(siz); } + if (siz > 4) { throw std::logic_error("unexpected siz"); } + + bool success = posix_next_result.front(); + posix_next_result.pop_front(); + return success ? 1 : 0; +} + +#define BOOST_RANDOM_PROVIDER_POSIX_IMPL_OPEN mockopen +#define BOOST_RANDOM_PROVIDER_POSIX_IMPL_READ mockread + +#elif defined(BOOST_RANDOM_PROVIDER_WINCRYPT) + +// Nothing to declare, since the expectation methods were already +// defined as import, we will link against a mock library + +#else + +#error support needed here for testing + +#endif + +#endif // BOOST_RANDOM_TEST_RANDOM_MOCK diff --git a/test/test_bench_random.cpp b/test/test_bench_random.cpp new file mode 100644 index 0000000000..cd0b2b4760 --- /dev/null +++ b/test/test_bench_random.cpp @@ -0,0 +1,69 @@ +// +// Copyright (c) 2017 James E. King III +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENCE_1_0.txt) +// +// benchmark for random_generators in different forms +// + +#include +#include +#include +#include +#include +#include + +#if defined(BOOST_RANDOM_LIMITED_BENCH) +// must be a Valgrind, UBsan, or other stressful check job +#define GEN_LOOPS 10 +#define REUSE_LOOPS 100 +#else +#define GEN_LOOPS 10000 +#define REUSE_LOOPS 1000000 +#endif + +template +void auto_timed_ctordtor(size_t count) +{ + boost::timer::auto_cpu_timer t; + for (size_t i = 0; i < count; ++i) + { + Provider gen; + boost::ignore_unused(gen); + } +} + +template +void auto_timed_bench(size_t count) +{ + Provider gen; + { + boost::timer::auto_cpu_timer t; + for (size_t i = 0; i < count; ++i) + { + typename Provider::result_type u = gen(); + boost::ignore_unused(u); + } + } +} + +int main(int, char*[]) +{ + std::cout << "Operating system entropy provider: " + << boost::random::random_device().name() << std::endl; + + std::cout << "Construction/destruction (overhead) time for boost::random_device " + << "(" << GEN_LOOPS << " iterations): " << std::endl; + auto_timed_ctordtor(GEN_LOOPS); + std::cout << std::endl; + + std::cout << "Benchmark boost:::random_device operator()()" + << "(reused for " << REUSE_LOOPS << " loops):" << std::endl; + auto_timed_bench(REUSE_LOOPS); + std::cout << std::endl; + + return 0; +} + diff --git a/test/test_detail_random_provider.cpp b/test/test_detail_random_provider.cpp new file mode 100644 index 0000000000..f68fd0fec0 --- /dev/null +++ b/test/test_detail_random_provider.cpp @@ -0,0 +1,65 @@ +// +// Copyright (c) 2017 James E. King III +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENCE_1_0.txt) +// +// Positive and negative testing for detail::random_provider +// + +#include +#include +#include +#include +#include + +// The mock needs to load first for posix system call redirection to work properly +#include "mock_random.hpp" +#include + + +int main(int, char*[]) +{ +#if !defined(BOOST_RANDOM_TEST_RANDOM_MOCK) // Positive Testing + + boost::random::detail::random_provider provider; + + // test get_random_bytes() + char buf1[64]; + char buf2[64]; + provider.get_random_bytes(buf1, 64); + provider.get_random_bytes(buf2, 64); + BOOST_TEST_NE(0, memcmp(buf1, buf2, 64)); + +#else // Negative Testing + + if (expectations_capable()) + { + // Test fail acquiring context if the provider supports it + if (provider_acquires_context()) + { + expect_next_call_success(false); + BOOST_TEST_THROWS(boost::random::detail::random_provider(), + boost::random::entropy_error); + BOOST_TEST(expectations_met()); + } + + // Test fail acquiring entropy + if (provider_acquires_context()) + { + expect_next_call_success(true); + } + expect_next_call_success(false); + // 4 is important for the posix negative test (partial read) to work properly + // as it sees a size of 4, returns 1, causing a 2nd loop to read 3 more bytes... + char buf[4]; + BOOST_TEST_THROWS(boost::random::detail::random_provider().get_random_bytes(buf, 4), + boost::random::entropy_error); + BOOST_TEST(expectations_met()); + } + +#endif + + return boost::report_errors(); +} diff --git a/test/test_entropy_error.cpp b/test/test_entropy_error.cpp new file mode 100644 index 0000000000..b3b514e3f3 --- /dev/null +++ b/test/test_entropy_error.cpp @@ -0,0 +1,24 @@ +// +// Copyright (c) 2017 James E. King III +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENCE_1_0.txt) +// +// Entropy error class test +// + +#include +#include +#include + +int main(int, char*[]) +{ + using namespace boost::random; + + entropy_error err(6, "foo"); + BOOST_TEST_EQ(6, err.errcode()); + BOOST_TEST_CSTR_EQ("foo", err.what()); + + return boost::report_errors(); +} diff --git a/test/test_random_device.cpp b/test/test_random_device.cpp index 2f9e61c76e..0510c93dae 100644 --- a/test/test_random_device.cpp +++ b/test/test_random_device.cpp @@ -9,21 +9,41 @@ */ #include +#include -#include -#include +#define BOOST_RANDOM_TEST_BUFSIZ 256 -int test_main(int, char**) { +#define BOOST_TEST_MAIN +#include + +BOOST_AUTO_TEST_CASE(test_random_device) +{ boost::random_device rng; - double entropy = rng.entropy(); - BOOST_CHECK_GE(entropy, 0); - for(int i = 0; i < 100; ++i) { + + BOOST_CHECK(typeid(boost::random_device::result_type) == typeid(unsigned int)); + + BOOST_CHECK(!boost::random_device::has_fixed_range); + + BOOST_CHECK_EQUAL(rng.min(), 0u); + BOOST_CHECK_EQUAL(rng.max(), ~0u); + + BOOST_CHECK_GT(rng.entropy(), 0); + + for (int i = 0; i < 100; ++i) { boost::random_device::result_type val = rng(); BOOST_CHECK_GE(val, (rng.min)()); BOOST_CHECK_LE(val, (rng.max)()); } + char buf[BOOST_RANDOM_TEST_BUFSIZ]; + char buf2[BOOST_RANDOM_TEST_BUFSIZ]; + rng.get_random_bytes(buf, BOOST_RANDOM_TEST_BUFSIZ); + rng.get_random_bytes(buf2, BOOST_RANDOM_TEST_BUFSIZ); + BOOST_CHECK_NE(0, ::memcmp(buf, buf2, BOOST_RANDOM_TEST_BUFSIZ)); + + BOOST_TEST_MESSAGE(boost::format("entropy provider: %1%") % rng.name()); + boost::uint32_t a[10]; rng.generate(a, a + 10); - return 0; } +