From 7f90b99375d17a20d965768662ed4e7fd4608e89 Mon Sep 17 00:00:00 2001 From: "James E. King III" Date: Tue, 5 Dec 2017 16:50:32 -0500 Subject: [PATCH] Update the random_device implementation so that it: * Supports more platforms optimally, for example CloudABI, OpenBSD, and Windows UWP * Is easier to maintain as each platform's implementation is in a separate file * Removes the library dependency on Boost.System * Is header-only, and thus makes Boost.Random header-only * Is well-tested for happy and sad paths Removes the token-based random_device explicit constructor. Adds a new exception "entropy_error" to handle errors getting entropy. Removed the detail auto_link implementation inside Boost.Random as it is no longer necessary - the one in Boost.Config is sufficient. Also added a top-level Jamfile that builds the example subdirectory with each build, as one of the examples needed to be updated in order to build. This will prevent rot in the example directory. Note: Other libraries that link against Boost.Random (like Boost.Uuid) will fail to build until they stop trying to link against Boost.Random. This fixes #20 This fixes #22 --- .gitignore | 2 +- Jamfile | 30 +++ build/Jamfile.v2 | 18 -- doc/Jamfile.v2 | 1 - doc/nondet_random.qbk | 103 -------- doc/random.qbk | 30 ++- example/Jamfile.v2 | 17 +- example/password.cpp | 2 +- include/boost/random/detail/auto_link.hpp | 40 --- .../random/detail/generator_seed_seq.hpp | 2 +- .../detail/random_provider_arc4random.ipp | 32 +++ .../random/detail/random_provider_bcrypt.ipp | 79 ++++++ .../random_provider_detect_platform.hpp | 62 +++++ .../detail/random_provider_getentropy.ipp | 38 +++ .../random_provider_include_platform.hpp | 29 ++ .../random/detail/random_provider_posix.ipp | 95 +++++++ .../detail/random_provider_wincrypt.ipp | 81 ++++++ include/boost/random/entropy_error.hpp | 46 ++++ include/boost/random/random_device.hpp | 162 +++++++----- src/random_device.cpp | 250 ------------------ test/Jamfile.v2 | 110 +++++++- test/mock_random.cpp | 115 ++++++++ test/mock_random.hpp | 248 +++++++++++++++++ test/test_bench_random.cpp | 69 +++++ test/test_detail_random_provider.cpp | 65 +++++ test/test_entropy_error.cpp | 24 ++ test/test_random_device.cpp | 34 ++- 27 files changed, 1275 insertions(+), 509 deletions(-) create mode 100644 Jamfile delete mode 100644 build/Jamfile.v2 delete mode 100644 doc/nondet_random.qbk delete mode 100644 include/boost/random/detail/auto_link.hpp create mode 100644 include/boost/random/detail/random_provider_arc4random.ipp create mode 100644 include/boost/random/detail/random_provider_bcrypt.ipp create mode 100644 include/boost/random/detail/random_provider_detect_platform.hpp create mode 100644 include/boost/random/detail/random_provider_getentropy.ipp create mode 100644 include/boost/random/detail/random_provider_include_platform.hpp create mode 100644 include/boost/random/detail/random_provider_posix.ipp create mode 100644 include/boost/random/detail/random_provider_wincrypt.ipp create mode 100644 include/boost/random/entropy_error.hpp delete mode 100644 src/random_device.cpp create mode 100644 test/mock_random.cpp create mode 100644 test/mock_random.hpp create mode 100644 test/test_bench_random.cpp create mode 100644 test/test_detail_random_provider.cpp create mode 100644 test/test_entropy_error.cpp 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; } +