diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a786148..7cab5e43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ Version 205 * Doc work * Add detail/soft_mutex.hpp +* Add detail/prng.hpp -------------------------------------------------------------------------------- diff --git a/include/boost/beast/websocket/detail/hybi13.hpp b/include/boost/beast/websocket/detail/hybi13.hpp index 424a9cc9..30f0ae3c 100644 --- a/include/boost/beast/websocket/detail/hybi13.hpp +++ b/include/boost/beast/websocket/detail/hybi13.hpp @@ -36,11 +36,11 @@ inline void make_sec_ws_key(sec_ws_key_type& key) { - auto p = stream_prng::prng(); + auto g = make_prng(true); char a[16]; for(int i = 0; i < 16; i += 4) { - auto const v = p->secure(); + auto const v = g(); a[i ] = v & 0xff; a[i+1] = (v >> 8) & 0xff; a[i+2] = (v >> 16) & 0xff; diff --git a/include/boost/beast/websocket/detail/impl/prng.ipp b/include/boost/beast/websocket/detail/impl/prng.ipp new file mode 100644 index 00000000..b9eb1f6f --- /dev/null +++ b/include/boost/beast/websocket/detail/impl/prng.ipp @@ -0,0 +1,315 @@ +// +// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// 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) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_WEBSOCKET_DETAIL_IMPL_PRNG_IPP +#define BOOST_BEAST_WEBSOCKET_DETAIL_IMPL_PRNG_IPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace beast { +namespace websocket { +namespace detail { + +//------------------------------------------------------------------------------ + +std::uint32_t const* +prng_seed(std::seed_seq* ss) +{ + struct data + { + std::uint32_t v[8]; + + explicit + data(std::seed_seq* pss) + { + if(! pss) + { + std::random_device g; + std::seed_seq ss{ + g(), g(), g(), g(), + g(), g(), g(), g()}; + ss.generate(v, v+8); + } + else + { + pss->generate(v, v+8); + } + } + }; + static data const d(ss); + return d.v; +} + +//------------------------------------------------------------------------------ + +template +class prng_pool +{ + std::mutex m_; + T* head_ = nullptr; + +public: + static + prng_pool& + instance() + { + static prng_pool p; + return p; + } + + ~prng_pool() + { + for(auto p = head_; p;) + { + auto next = p->next; + p->~T(); + boost::alignment::aligned_free(p); + p = next; + } + } + + prng::ref + acquire() + { + { + std::lock_guard< + std::mutex> g(m_); + if(head_) + { + auto p = head_; + head_ = head_->next; + return prng::ref(*p); + } + } + auto p = boost::alignment::aligned_alloc( + 16, sizeof(T)); + if(! p) + BOOST_THROW_EXCEPTION(std::bad_alloc{}); + return prng::ref(*(::new(p) T())); + } + + void + release(T& t) + { + std::lock_guard< + std::mutex> g(m_); + t.next = head_; + head_ = &t; + } +}; + +prng::ref +make_prng_no_tls(bool secure) +{ + class fast_prng final : public prng + { + int refs_ = 0; + std::minstd_rand r_; + + public: + fast_prng* next; + + fast_prng() + : r_([] + { + static std::atomic< + std::uint64_t> nonce{0}; + auto const pv = prng_seed(); + return static_cast( + pv[0] + pv[1] + pv[2] + pv[3] + + pv[4] + pv[5] + pv[6] + pv[7] + + ++nonce); + }()) + { + } + + protected: + prng& + acquire() noexcept override + { + ++refs_; + return *this; + } + + void + release() noexcept override + { + if(--refs_ == 0) + prng_pool::instance().release(*this); + } + + value_type + operator()() noexcept override + { + return r_(); + } + }; + + class secure_prng final : public prng + { + int refs_ = 0; + beast::detail::chacha<20> r_; + + public: + secure_prng* next; + + secure_prng() + : r_(prng_seed(), [] + { + static std::atomic< + std::uint64_t> nonce{0}; + return ++nonce; + }()) + { + } + + protected: + prng& + acquire() noexcept override + { + ++refs_; + return *this; + } + + void + release() noexcept override + { + if(--refs_ == 0) + prng_pool::instance().release(*this); + } + + value_type + operator()() noexcept override + { + return r_(); + } + }; + + if(secure) + return prng_pool::instance().acquire(); + + return prng_pool::instance().acquire(); +} + +//------------------------------------------------------------------------------ + +#if ! BOOST_BEAST_NO_THREAD_LOCAL +prng::ref +make_prng_tls(bool secure) +{ + class fast_prng final : public prng + { + std::minstd_rand r_; + + public: + fast_prng() + : r_([] + { + static std::atomic< + std::uint64_t> nonce{0}; + auto const pv = prng_seed(); + return static_cast( + pv[0] + pv[1] + pv[2] + pv[3] + + pv[4] + pv[5] + pv[6] + pv[7] + + ++nonce); + }()) + { + } + + protected: + prng& + acquire() noexcept override + { + return *this; + } + + void + release() noexcept override + { + } + + value_type + operator()() noexcept override + { + return r_(); + } + }; + + class secure_prng final : public prng + { + beast::detail::chacha<20> r_; + + public: + secure_prng() + : r_(prng_seed(), [] + { + static std::atomic< + std::uint64_t> nonce{0}; + return ++nonce; + }()) + { + } + + protected: + prng& + acquire() noexcept override + { + return *this; + } + + void + release() noexcept override + { + } + + value_type + operator()() noexcept override + { + return r_(); + } + }; + + if(secure) + { + thread_local secure_prng sp; + return prng::ref(sp); + } + + thread_local fast_prng fp; + return prng::ref(fp); +} +#endif + +//------------------------------------------------------------------------------ + +prng::ref +make_prng(bool secure) +{ +#if BOOST_BEAST_NO_THREAD_LOCAL + return make_prng_no_tls(secure); +#else + return make_prng_tls(secure); +#endif +} + +} // detail +} // websocket +} // beast +} // boost + +#endif diff --git a/include/boost/beast/websocket/detail/prng.hpp b/include/boost/beast/websocket/detail/prng.hpp new file mode 100644 index 00000000..e34ebb7a --- /dev/null +++ b/include/boost/beast/websocket/detail/prng.hpp @@ -0,0 +1,138 @@ +// +// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// 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) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_WEBSOCKET_DETAIL_PRNG_HPP +#define BOOST_BEAST_WEBSOCKET_DETAIL_PRNG_HPP + +#include +#include +#include +#include + +namespace boost { +namespace beast { +namespace websocket { +namespace detail { + +// Type-erased UniformRandomBitGenerator +// with 32-bit unsigned integer value_type +// +class prng +{ +protected: + virtual ~prng() = default; + virtual prng& acquire() noexcept = 0; + virtual void release() noexcept = 0; + virtual std::uint32_t operator()() noexcept = 0; + +public: + using value_type = std::uint32_t; + + class ref + { + prng& p_; + + public: + ref& operator=(ref const&) = delete; + + ~ref() + { + p_.release(); + } + + explicit + ref(prng& p) noexcept + : p_(p.acquire()) + { + } + + ref(ref const& other) noexcept + : p_(other.p_.acquire()) + { + } + + value_type + operator()() noexcept + { + return p_(); + } + + static + value_type constexpr + min() noexcept + { + return (std::numeric_limits< + value_type>::min)(); + } + + static + value_type constexpr + max() noexcept + { + return (std::numeric_limits< + value_type>::max)(); + } + }; +}; + +//------------------------------------------------------------------------------ + +// Manually seed the prngs, must be called +// before acquiring a prng for the first time. +// +BOOST_BEAST_DECL +std::uint32_t const* +prng_seed(std::seed_seq* ss = nullptr); + +// Acquire a PRNG using the no-TLS implementation +// +BOOST_BEAST_DECL +prng::ref +make_prng_no_tls(bool secure); + +// Acquire a PRNG using the TLS implementation +// +#if ! BOOST_BEAST_NO_THREAD_LOCAL +BOOST_BEAST_DECL +prng::ref +make_prng_tls(bool secure); +#endif + +// Acquire a PRNG using the TLS implementation if it +// is available, otherwise using the no-TLS implementation. +// +BOOST_BEAST_DECL +prng::ref +make_prng(bool secure); + +//------------------------------------------------------------------------------ + +struct stream_prng +{ + bool secure_prng_ = true; + + std::uint32_t + create_mask() + { + auto g = make_prng(secure_prng_); + for(;;) + if(auto key = g()) + return key; + } + +}; + +} // detail +} // websocket +} // beast +} // boost + +#include + +#endif diff --git a/include/boost/beast/websocket/detail/stream_base.hpp b/include/boost/beast/websocket/detail/stream_base.hpp index 95237731..cc2f800d 100644 --- a/include/boost/beast/websocket/detail/stream_base.hpp +++ b/include/boost/beast/websocket/detail/stream_base.hpp @@ -15,210 +15,21 @@ #include #include #include +#include #include #include #include #include -#include #include -#include #include -#include -#include #include #include -#include -#include - -// Turn this on to avoid using thread_local -//#define BOOST_BEAST_NO_THREAD_LOCAL 1 - -#ifdef BOOST_BEAST_NO_THREAD_LOCAL -#include -#include -#endif namespace boost { namespace beast { namespace websocket { namespace detail { -struct stream_prng -{ - bool secure_prng_ = true; - - struct prng_type - { - std::minstd_rand fast; - beast::detail::chacha<20> secure; - -#if BOOST_BEAST_NO_THREAD_LOCAL - prng_type* next = nullptr; -#endif - - prng_type(std::uint32_t const* v, std::uint64_t stream) - : fast(static_cast( - v[0] + v[1] + v[2] + v[3] + v[4] + v[5] + v[6] + v[7] + stream)) - , secure(v, stream) - { - } - }; - - class prng_ref - { - prng_type* p_; - - public: - prng_ref& operator=(prng_ref&&) = delete; - - explicit - prng_ref(prng_type& p) - : p_(&p) - { - } - - prng_ref(prng_ref&& other) - : p_(boost::exchange(other.p_, nullptr)) - { - } - -#ifdef BOOST_BEAST_NO_THREAD_LOCAL - ~prng_ref() - { - if(p_) - pool::impl().release(*p_); - } -#endif - - prng_type* - operator->() const - { - return p_; - } - }; - -#ifndef BOOST_BEAST_NO_THREAD_LOCAL - static - prng_ref - prng() - { - static std::atomic stream{0}; - thread_local prng_type p{seed(), stream++}; - return prng_ref(p); - } - -#else - static - prng_ref - prng() - { - return prng_ref(pool::impl().acquire()); - } - -#endif - - static - std::uint32_t const* - seed(std::seed_seq* ss = nullptr) - { - static seed_data d(ss); - return d.v; - } - - std::uint32_t - create_mask() - { - auto p = prng(); - if(secure_prng_) - for(;;) - if(auto key = p->secure()) - return key; - for(;;) - if(auto key = p->fast()) - return key; - } - -private: - struct seed_data - { - std::uint32_t v[8]; - - explicit - seed_data(std::seed_seq* pss) - { - if(! pss) - { - std::random_device g; - std::seed_seq ss{ - g(), g(), g(), g(), g(), g(), g(), g()}; - ss.generate(v, v+8); - } - else - { - pss->generate(v, v+8); - } - } - }; - -#ifdef BOOST_BEAST_NO_THREAD_LOCAL - class pool - { - prng_type* head_ = nullptr; - std::atomic n_{0}; - std::mutex m_; - - public: - ~pool() - { - for(auto p = head_; p;) - { - auto next = p->next; - p->~prng_type(); - boost::alignment::aligned_free(p); - p = next; - } - } - - prng_type& - acquire() - { - for(;;) - { - std::lock_guard lock(m_); - if(! head_) - break; - auto p = head_; - head_ = head_->next; - return *p; - } - auto p = boost::alignment::aligned_alloc( - 16, sizeof(prng_type)); - if(! p) - BOOST_THROW_EXCEPTION(std::bad_alloc{}); - return *(::new(p) prng_type(seed(), n_++)); - } - - void - release(prng_type& p) - { - std::lock_guard lock(m_); - p.next = head_; - head_ = &p; - } - - static - pool& - impl() - { - static pool instance; - return instance; - } - }; -#endif -}; - -//------------------------------------------------------------------------------ - template struct stream_base : stream_prng { diff --git a/include/boost/beast/websocket/stream.hpp b/include/boost/beast/websocket/stream.hpp index beca0e96..03e9ecd1 100644 --- a/include/boost/beast/websocket/stream.hpp +++ b/include/boost/beast/websocket/stream.hpp @@ -3511,7 +3511,7 @@ inline void seed_prng(std::seed_seq& ss) { - detail::stream_prng::seed(&ss); + detail::prng_seed(&ss); } } // websocket diff --git a/test/beast/websocket/CMakeLists.txt b/test/beast/websocket/CMakeLists.txt index 432b7b76..20ed3d4f 100644 --- a/test/beast/websocket/CMakeLists.txt +++ b/test/beast/websocket/CMakeLists.txt @@ -17,6 +17,7 @@ add_executable (tests-beast-websocket ${TEST_MAIN} Jamfile test.hpp + _detail_prng.cpp accept.cpp close.cpp error.cpp diff --git a/test/beast/websocket/Jamfile b/test/beast/websocket/Jamfile index 4cdfcb68..7c5e5449 100644 --- a/test/beast/websocket/Jamfile +++ b/test/beast/websocket/Jamfile @@ -8,6 +8,7 @@ # local SOURCES = + _detail_prng.cpp accept.cpp close.cpp error.cpp diff --git a/test/beast/websocket/_detail_prng.cpp b/test/beast/websocket/_detail_prng.cpp new file mode 100644 index 00000000..b1bfa052 --- /dev/null +++ b/test/beast/websocket/_detail_prng.cpp @@ -0,0 +1,69 @@ +// +// Copyright (w) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// 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) +// +// Official repository: https://github.com/boostorg/beast +// + +// Test that header file is self-contained. +#include + +#include + +namespace boost { +namespace beast { +namespace websocket { +namespace detail { + +class prng_test + : public beast::unit_test::suite +{ +public: +#if 0 + char const* name() const noexcept //override + { + return "boost.beast.websocket.detail.prng"; + } +#endif + + template + void + testPrng(F const& f) + { + { + auto v = f()(); + BEAST_EXPECT( + v >= (prng::ref::min)() && + v <= (prng::ref::max)()); + } + { + auto v = f()(); + BEAST_EXPECT( + v >= (prng::ref::min)() && + v <= (prng::ref::max)()); + } + } + + void + run() override + { + testPrng([]{ return make_prng(true); }); + testPrng([]{ return make_prng(false); }); + testPrng([]{ return make_prng_no_tls(true); }); + testPrng([]{ return make_prng_no_tls(false); }); + #if ! BOOST_BEAST_NO_THREAD_LOCAL + testPrng([]{ return make_prng_tls(true); }); + testPrng([]{ return make_prng_tls(false); }); + #endif + } +}; + +//static prng_test t; +BEAST_DEFINE_TESTSUITE(beast,websocket,prng); + +} // detail +} // websocket +} // beast +} // boost