From e3a3e2ad9693bff88d45df4c74184a2ed92fa5a3 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 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 | 3 +- Jamfile | 30 +++ build/Jamfile.v2 | 18 -- example/Jamfile.v2 | 17 +- example/password.cpp | 2 +- include/boost/random/detail/auto_link.hpp | 40 --- .../random/detail/generator_seed_seq.hpp | 2 +- .../boost/random/detail/random_provider.hpp | 75 ++++++ .../detail/random_provider_arc4random.ipp | 32 +++ .../random/detail/random_provider_bcrypt.ipp | 78 ++++++ .../random_provider_detect_platform.hpp | 62 +++++ .../detail/random_provider_getentropy.ipp | 37 +++ .../random_provider_include_platform.hpp | 29 ++ .../random/detail/random_provider_posix.ipp | 95 +++++++ .../detail/random_provider_wincrypt.ipp | 80 ++++++ include/boost/random/entropy_error.hpp | 46 ++++ include/boost/random/random_device.hpp | 90 ++----- 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 | 75 ++++++ test/test_entropy_error.cpp | 24 ++ test/test_random_device.cpp | 10 +- 25 files changed, 1240 insertions(+), 397 deletions(-) create mode 100644 Jamfile delete mode 100644 build/Jamfile.v2 delete mode 100644 include/boost/random/detail/auto_link.hpp create mode 100644 include/boost/random/detail/random_provider.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..ef0376b154 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /doc/html /doc/reference.xml -/test/rng.saved +/example/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/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.hpp b/include/boost/random/detail/random_provider.hpp new file mode 100644 index 0000000000..a0261a7b48 --- /dev/null +++ b/include/boost/random/detail/random_provider.hpp @@ -0,0 +1,75 @@ +// +// 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 +// + +#ifndef BOOST_RANDOM_DETAIL_RANDOM_PROVIDER_HPP +#define BOOST_RANDOM_DETAIL_RANDOM_PROVIDER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +// Detection of the platform is separated from inclusion of the correct +// header to facilitate mock testing of the provider implementations. + +#include +#include + + +namespace boost { +namespace random { +namespace detail { + +//! \brief Contains code common to all random_provider implementations. +//! \note random_provider_base is required to provide this method: +//! void get_random_bytes(void *buf, size_t siz); +//! \note noncopyable because of some base implementations so +//! this makes it uniform across platforms to avoid any +//! porting surprises +class random_provider + : public detail::random_provider_base, + public noncopyable +{ +public: + //! Leverage the provider as a SeedSeq for + //! PseudoRandomNumberGeneration seeding + //! \note: See Boost.Random documentation for more details + template + void generate(Iter first, Iter last) + { + 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)(); + } + } + + //! Return the name of the selected provider + const char * name() const + { + return BOOST_RANDOM_PROVIDER_STRINGIFY(BOOST_RANDOM_PROVIDER_NAME); + } +}; + +} // detail +} // random +} // boost + +#endif // BOOST_RANDOM_DETAIL_RANDOM_PROVIDER_HPP + 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..c858618ef3 --- /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_base +{ + 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..e1caa54da5 --- /dev/null +++ b/include/boost/random/detail/random_provider_bcrypt.ipp @@ -0,0 +1,78 @@ +// +// 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 + +#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_base +{ + public: + random_provider_base() + : 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_base() 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..c559ce384c --- /dev/null +++ b/include/boost/random/detail/random_provider_getentropy.ipp @@ -0,0 +1,37 @@ +// +// 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 + +namespace boost { +namespace random { +namespace detail { + +class random_provider_base +{ + 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..3204a1f7b9 --- /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_base +{ + public: + random_provider_base() + : 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_base() 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..94f14b251f --- /dev/null +++ b/include/boost/random/detail/random_provider_wincrypt.ipp @@ -0,0 +1,80 @@ +/* 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 + +#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_base +{ + public: + random_provider_base() + : 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_base() 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..b44b05ea99 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,11 +21,8 @@ #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 namespace boost { namespace random { @@ -49,64 +49,20 @@ namespace random { * C++ only, so users should be aware that this class may not be available * on all platforms. * @endxmlnote - * - * 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, @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. - * - * Implementation Note for Windows - * - * On the Windows operating system, token is interpreted as the name - * of a cryptographic service provider. By default \random_device uses - * MS_DEF_PROV. - * - * Performance - * - * 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. - * - * - * - * - * - *
Platformtime per invocation [microseconds]
Windows 2.9
Linux 1.7
- * - * The measurement error is estimated at +/- 1 usec. */ -class random_device : private noncopyable +template +class random_device_base : public detail::random_provider { public: - typedef unsigned int result_type; + typedef Entropy result_type; 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; } + static result_type min BOOST_PREVENT_MACRO_SUBSTITUTION () + { return std::numeric_limits::min(); } /** 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(); + static result_type max BOOST_PREVENT_MACRO_SUBSTITUTION () + { return std::numeric_limits::max(); } /** * Returns: An entropy estimate for the random numbers returned by @@ -116,24 +72,16 @@ class random_device : private noncopyable * * Throws: Nothing. */ - BOOST_RANDOM_DECL double entropy() const; + double entropy() const + { return 10; } + /** Returns a random value in the range [min, max]. */ - BOOST_RANDOM_DECL unsigned int operator()(); - - /** Fills a range with random 32-bit values. */ - template - void generate(Iter begin, Iter end) - { - for(; begin != end; ++begin) { - *begin = (*this)(); - } - } - -private: - class impl; - impl * pimpl; + result_type operator()() + { result_type e; get_random_bytes(&e, sizeof(result_type)); return e; } }; +typedef random_device_base random_device; + } // namespace random using random::random_device; 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..0a9f479113 --- /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::detail::random_provider().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..655f717921 --- /dev/null +++ b/test/test_detail_random_provider.cpp @@ -0,0 +1,75 @@ +// +// 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; + boost::array ints; + + // test generator() + ints[0] = 0; + ints[1] = 0; + provider.generate(ints.begin(), ints.end()); + BOOST_TEST_NE(ints[0], ints[1]); + + // test name() + BOOST_TEST_GT(strlen(provider.name()), 4u); + + // 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..474834e92e 100644 --- a/test/test_random_device.cpp +++ b/test/test_random_device.cpp @@ -10,13 +10,14 @@ #include -#include -#include +#define BOOST_TEST_MAIN +#include -int test_main(int, char**) { +BOOST_AUTO_TEST_CASE(test_random_device) +{ boost::random_device rng; double entropy = rng.entropy(); - BOOST_CHECK_GE(entropy, 0); + BOOST_CHECK_GT(entropy, 0); for(int i = 0; i < 100; ++i) { boost::random_device::result_type val = rng(); BOOST_CHECK_GE(val, (rng.min)()); @@ -25,5 +26,4 @@ int test_main(int, char**) { boost::uint32_t a[10]; rng.generate(a, a + 10); - return 0; }