diff --git a/CMakeLists.txt b/CMakeLists.txt index eefa361..804d950 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,6 +55,7 @@ set(BOOST_SRC_DIR ${DEFAULT_BOOST_SRC_DIR} CACHE STRING "Boost source dir to use # The boost super-project requires one explicit dependency per-line. set(BOOST_WS_PROTO_DEPENDENCIES Boost::http_proto + Boost::url ) foreach (BOOST_WS_PROTO_DEPENDENCY ${BOOST_WS_PROTO_DEPENDENCIES}) @@ -126,13 +127,14 @@ endif () #------------------------------------------------- set_property(GLOBAL PROPERTY USE_FOLDERS ON) -file(GLOB_RECURSE BOOST_WS_PROTO_HEADERS CONFIGURE_DEPENDS include/boost/*.hpp include/boost/*.natvis) +file(GLOB_RECURSE BOOST_WS_PROTO_HEADERS CONFIGURE_DEPENDS include/boost/ws_proto/*.hpp include/boost/ws_proto/*.natvis) file(GLOB_RECURSE BOOST_WS_PROTO_SOURCES CONFIGURE_DEPENDS src/*.cpp src/*.hpp) -source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/include/boost PREFIX "" FILES ${BOOST_WS_PROTO_HEADERS}) -source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/src PREFIX "ws_proto" FILES ${BOOST_WS_PROTO_SOURCES}) +source_group("" FILES "include/boost/ws_proto.hpp" "build/Jamfile") +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/include/boost/ws_proto PREFIX "include" FILES ${BOOST_WS_PROTO_HEADERS}) +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/src PREFIX "src" FILES ${BOOST_WS_PROTO_SOURCES}) -add_library(boost_ws_proto ${BOOST_WS_PROTO_HEADERS} ${BOOST_WS_PROTO_SOURCES}) +add_library(boost_ws_proto include/boost/ws_proto.hpp build/Jamfile ${BOOST_WS_PROTO_HEADERS} ${BOOST_WS_PROTO_SOURCES}) add_library(Boost::ws_proto ALIAS boost_ws_proto) target_compile_features(boost_ws_proto PUBLIC cxx_constexpr) target_include_directories(boost_ws_proto PUBLIC "${PROJECT_SOURCE_DIR}/include") diff --git a/build/Jamfile b/build/Jamfile index 6a4a29b..364d237 100644 --- a/build/Jamfile +++ b/build/Jamfile @@ -44,6 +44,7 @@ lib boost_ws_proto : requirements /boost//buffers /boost//http_proto + /boost//url ../ BOOST_WS_PROTO_SOURCE : usage-requirements diff --git a/include/boost/ws_proto/client.hpp b/include/boost/ws_proto/client.hpp new file mode 100644 index 0000000..9fdfce7 --- /dev/null +++ b/include/boost/ws_proto/client.hpp @@ -0,0 +1,44 @@ +// +// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.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/cppalliance/ws_proto +// + +#ifndef BOOST_WS_PROTO_CLIENT_HPP +#define BOOST_WS_PROTO_CLIENT_HPP + +#include + +namespace boost { +namespace ws_proto { + +enum class frame_type +{ + ping, + pong, + close, + cont, + data +}; + +/** A WebSocket client +*/ +class client +{ +public: + /** Add an outgoing frame + */ + template + bool + write( + frame_type kind, + ConstBufferSequence const& payload); +}; + +} // ws_proto +} // boost + +#endif diff --git a/include/boost/ws_proto/handshake.hpp b/include/boost/ws_proto/handshake.hpp index 8b786aa..37f54b0 100644 --- a/include/boost/ws_proto/handshake.hpp +++ b/include/boost/ws_proto/handshake.hpp @@ -26,6 +26,8 @@ system::result is_upgrade( http_proto::request_view const& req) noexcept; +/** Return a Websocket Upgrade HTTP request +*/ BOOST_WS_PROTO_DECL http_proto::request make_upgrade( diff --git a/src/handshake.cpp b/src/handshake.cpp index b5a844b..34b7d2d 100644 --- a/src/handshake.cpp +++ b/src/handshake.cpp @@ -8,6 +8,7 @@ // #include +#include "src/impl/hybi13.hpp" namespace boost { namespace ws_proto { @@ -19,5 +20,33 @@ is_upgrade( return false; } +http_proto::request +make_upgrade( + urls::url_view target) +{ + http_proto::request req; + req.set_start_line( + http_proto::method::get, + target.buffer(), + http_proto::version::http_1_1); + req.set(http_proto::field::host, "host"); + req.set(http_proto::field::connection, "Upgrade"); + req.set(http_proto::field::upgrade, "websocket"); + + detail::request_key key; + detail::make_request_key(key); + req.set(http_proto::field::sec_websocket_key, + core::string_view(key.data, key.size)); + req.set(http_proto::field::sec_websocket_version, "13"); + + /* + this->build_request_pmd(req); + decorator_opt(req); + decorator(req); + */ + + return req; +} + } // ws_proto } // boost diff --git a/src/impl/base64.cpp b/src/impl/base64.cpp new file mode 100644 index 0000000..33a0e6b --- /dev/null +++ b/src/impl/base64.cpp @@ -0,0 +1,179 @@ +// +// Copyright (c) 2016-2019 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/cppalliance/ws_proto +// + +/* + Portions from http://www.adp-gmbh.ch/cpp/common/base64.html + Copyright notice: + + base64.cpp and base64.h + + Copyright (C) 2004-2008 Rene Nyffenegger + + This source code is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this source code must not be misrepresented; you must not + claim that you wrote the original source code. If you use this source code + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original source code. + + 3. This notice may not be removed or altered from any source distribution. + + Rene Nyffenegger rene.nyffenegger@adp-gmbh.ch +*/ + +#include "src/impl/base64.hpp" +#include +#include +#include + +namespace boost { +namespace ws_proto { + +char const* +base64_alphabet() noexcept +{ + static char constexpr tab[] = { + "ABCDEFGHIJKLMNOP" + "QRSTUVWXYZabcdef" + "ghijklmnopqrstuv" + "wxyz0123456789+/" + }; + return &tab[0]; +} + +signed char const* +base64_inverse() noexcept +{ + static signed char constexpr tab[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0-15 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 16-31 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, // 32-47 + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, // 48-63 + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 64-79 + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, // 80-95 + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 96-111 + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, // 112-127 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 128-143 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 144-159 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 160-175 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 176-191 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 192-207 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 208-223 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 224-239 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 // 240-255 + }; + return &tab[0]; +} + +/** Encode a series of octets as a padded, base64 string. + + The resulting string will not be null terminated. + + @par Requires + + The memory pointed to by `out` points to valid memory + of at least `encoded_size(len)` bytes. + + @return The number of characters written to `out`. This + will exclude any null termination. +*/ +std::size_t +base64_encode(void* dest, void const* src, std::size_t len) +{ + char* out = static_cast(dest); + char const* in = static_cast(src); + auto const tab = base64_alphabet(); + + for(auto n = len / 3; n--;) + { + *out++ = tab[ (in[0] & 0xfc) >> 2]; + *out++ = tab[((in[0] & 0x03) << 4) + ((in[1] & 0xf0) >> 4)]; + *out++ = tab[((in[2] & 0xc0) >> 6) + ((in[1] & 0x0f) << 2)]; + *out++ = tab[ in[2] & 0x3f]; + in += 3; + } + + switch(len % 3) + { + case 2: + *out++ = tab[ (in[0] & 0xfc) >> 2]; + *out++ = tab[((in[0] & 0x03) << 4) + ((in[1] & 0xf0) >> 4)]; + *out++ = tab[ (in[1] & 0x0f) << 2]; + *out++ = '='; + break; + + case 1: + *out++ = tab[ (in[0] & 0xfc) >> 2]; + *out++ = tab[((in[0] & 0x03) << 4)]; + *out++ = '='; + *out++ = '='; + break; + + case 0: + break; + } + + return out - static_cast(dest); +} + +std::pair +base64_decode(void* dest, char const* src, std::size_t len) +{ + char* out = static_cast(dest); + auto in = reinterpret_cast(src); + unsigned char c3[3], c4[4] = {0,0,0,0}; + int i = 0; + int j = 0; + + auto const inverse = base64_inverse(); + + while(len-- && *in != '=') + { + auto const v = inverse[*in]; + if(v == -1) + break; + ++in; + c4[i] = v; + if(++i == 4) + { + c3[0] = (c4[0] << 2) + ((c4[1] & 0x30) >> 4); + c3[1] = ((c4[1] & 0xf) << 4) + ((c4[2] & 0x3c) >> 2); + c3[2] = ((c4[2] & 0x3) << 6) + c4[3]; + + for(i = 0; i < 3; i++) + *out++ = c3[i]; + i = 0; + } + } + + if(i) + { + c3[0] = ( c4[0] << 2) + ((c4[1] & 0x30) >> 4); + c3[1] = ((c4[1] & 0xf) << 4) + ((c4[2] & 0x3c) >> 2); + c3[2] = ((c4[2] & 0x3) << 6) + c4[3]; + + for(j = 0; j < i - 1; j++) + *out++ = c3[j]; + } + + return {out - static_cast(dest), + in - reinterpret_cast(src)}; +} + +} // ws_proto +} // boost diff --git a/src/impl/base64.hpp b/src/impl/base64.hpp new file mode 100644 index 0000000..7d1e9a2 --- /dev/null +++ b/src/impl/base64.hpp @@ -0,0 +1,73 @@ +// +// Copyright (c) 2016-2025 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/cppalliance/ws_proto +// + +#ifndef BOOST_WS_PROTO_SRC_IMPL_BASE64_HPP +#define BOOST_WS_PROTO_SRC_IMPL_BASE64_HPP + +#include +#include + +namespace boost { +namespace ws_proto { + +char const* +base64_alphabet() noexcept; + +signed char const* +base64_inverse() noexcept; + +/// Returns max chars needed to encode a base64 string +std::size_t +constexpr +base64_encoded_size(std::size_t n) +{ + return 4 * ((n + 2) / 3); +} + +/// Returns max bytes needed to decode a base64 string +std::size_t +constexpr +base64_decoded_size(std::size_t n) +{ + return n / 4 * 3; // requires n&3==0, smaller +} + +/** Encode a series of octets as a padded, base64 string. + + The resulting string will not be null terminated. + + @par Requires + + The memory pointed to by `out` points to valid memory + of at least `encoded_size(len)` bytes. + + @return The number of characters written to `out`. This + will exclude any null termination. +*/ +std::size_t +base64_encode(void* dest, void const* src, std::size_t len); + +/** Decode a padded base64 string into a series of octets. + + @par Requires + + The memory pointed to by `out` points to valid memory + of at least `decoded_size(len)` bytes. + + @return The number of octets written to `out`, and + the number of characters read from the input string, + expressed as a pair. +*/ +std::pair +base64_decode(void* dest, char const* src, std::size_t len); + +} // ws_proto +} // boost + +#endif diff --git a/src/impl/chacha.hpp b/src/impl/chacha.hpp new file mode 100644 index 0000000..e377728 --- /dev/null +++ b/src/impl/chacha.hpp @@ -0,0 +1,126 @@ +// +// Copyright (c) 2016-2025 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/cppalliance/ws_proto +// + +// +// This is a derivative work, original copyright follows: +// + +/* + Copyright (c) 2015 Orson Peters + + This software is provided 'as-is', without any express or implied warranty. In no event will the + authors be held liable for any damages arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, including commercial + applications, and to alter it and redistribute it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not claim that you wrote the + original software. If you use this software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be misrepresented as + being the original software. + + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef BOOST_WS_PROTO_SRC_IMPL_CHACHA_HPP +#define BOOST_WS_PROTO_SRC_IMPL_CHACHA_HPP + +#include +#include + +namespace boost { +namespace ws_proto { +namespace detail { + +template +class chacha +{ + alignas(16) std::uint32_t block_[16]; + std::uint32_t keysetup_[8]; + std::uint64_t ctr_ = 0; + int idx_ = 16; + + void generate_block() + { + std::uint32_t constexpr constants[4] = { + 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574 }; + std::uint32_t input[16]; + for (int i = 0; i < 4; ++i) + input[i] = constants[i]; + for (int i = 0; i < 8; ++i) + input[4 + i] = keysetup_[i]; + input[12] = (ctr_ / 16) & 0xffffffffu; + input[13] = (ctr_ / 16) >> 32; + input[14] = input[15] = 0xdeadbeef; // Could use 128-bit counter. + for (int i = 0; i < 16; ++i) + block_[i] = input[i]; + chacha_core(); + for (int i = 0; i < 16; ++i) + block_[i] += input[i]; + } + + void chacha_core() + { + #define BOOST_WS_PROTO_CHACHA_ROTL32(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + + #define BOOST_WS_PROTO_CHACHA_QUARTERROUND(x, a, b, c, d) \ + x[a] = x[a] + x[b]; x[d] ^= x[a]; x[d] = BOOST_WS_PROTO_CHACHA_ROTL32(x[d], 16); \ + x[c] = x[c] + x[d]; x[b] ^= x[c]; x[b] = BOOST_WS_PROTO_CHACHA_ROTL32(x[b], 12); \ + x[a] = x[a] + x[b]; x[d] ^= x[a]; x[d] = BOOST_WS_PROTO_CHACHA_ROTL32(x[d], 8); \ + x[c] = x[c] + x[d]; x[b] ^= x[c]; x[b] = BOOST_WS_PROTO_CHACHA_ROTL32(x[b], 7) + + for (unsigned i = 0; i < R; i += 2) + { + BOOST_WS_PROTO_CHACHA_QUARTERROUND(block_, 0, 4, 8, 12); + BOOST_WS_PROTO_CHACHA_QUARTERROUND(block_, 1, 5, 9, 13); + BOOST_WS_PROTO_CHACHA_QUARTERROUND(block_, 2, 6, 10, 14); + BOOST_WS_PROTO_CHACHA_QUARTERROUND(block_, 3, 7, 11, 15); + BOOST_WS_PROTO_CHACHA_QUARTERROUND(block_, 0, 5, 10, 15); + BOOST_WS_PROTO_CHACHA_QUARTERROUND(block_, 1, 6, 11, 12); + BOOST_WS_PROTO_CHACHA_QUARTERROUND(block_, 2, 7, 8, 13); + BOOST_WS_PROTO_CHACHA_QUARTERROUND(block_, 3, 4, 9, 14); + } + + #undef BOOST_WS_PROTO_CHACHA_QUARTERROUND + #undef BOOST_WS_PROTO_CHACHA_ROTL32 + } + +public: + static constexpr std::size_t state_size = sizeof(chacha::keysetup_); + + using result_type = std::uint32_t; + + chacha(std::uint32_t const* v, std::uint64_t stream) + { + for (int i = 0; i < 6; ++i) + keysetup_[i] = v[i]; + keysetup_[6] = v[6] + (stream & 0xffffffff); + keysetup_[7] = v[7] + ((stream >> 32) & 0xffffffff); + } + + std::uint32_t + operator()() + { + if(idx_ == 16) + { + idx_ = 0; + ++ctr_; + generate_block(); + } + return block_[idx_++]; + } +}; + +} // detail +} // ws_proto +} // boost + +#endif diff --git a/src/impl/hybi13.cpp b/src/impl/hybi13.cpp new file mode 100644 index 0000000..faf93ef --- /dev/null +++ b/src/impl/hybi13.cpp @@ -0,0 +1,56 @@ +// +// Copyright (c) 2016-2019 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/cppalliance/ws_proto +// + +#include "src/impl/hybi13.hpp" +#include "src/impl/base64.hpp" +#include "src/impl/prng.hpp" +#include "src/impl/sha1.hpp" +#include + +#include +#include + +namespace boost { +namespace ws_proto { +namespace detail { + +void +make_request_key( + request_key& key) +{ + auto g = make_prng(true); + std::uint32_t a[4]; + for (auto& v : a) + v = g(); + key.size = base64_encode( + key.data, &a[0], sizeof(a)); +} + +void +make_response_key( + response_key& out, + core::string_view in) +{ + BOOST_ASSERT(in.size() <= request_key::capacity); + core::string_view const guid( + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); + sha1::context ctx; + sha1::init(ctx); + sha1::update(ctx, in.data(), in.size()); + sha1::update(ctx, guid.data(), guid.size()); + char digest[sha1::context::digest_size]; + sha1::finish(ctx, &digest[0]); + out.size = base64_encode( + out.data, &digest[0], sizeof(digest)); +} + +} // detail +} // ws_proto +} // boost + diff --git a/src/impl/hybi13.hpp b/src/impl/hybi13.hpp new file mode 100644 index 0000000..cce1e4e --- /dev/null +++ b/src/impl/hybi13.hpp @@ -0,0 +1,48 @@ +// +// Copyright (c) 2016-2025 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/cppalliance/ws_proto +// + +#ifndef BOOST_WS_PROTO_SRC_IMPL_HYBI13_HPP +#define BOOST_WS_PROTO_SRC_IMPL_HYBI13_HPP + +#include +#include "src/impl/base64.hpp" +#include + +namespace boost { +namespace ws_proto { +namespace detail { + +struct request_key +{ + static constexpr std::size_t capacity = + base64_encoded_size(16); + std::size_t size = 0; + char data[capacity]; +}; + +struct response_key +{ + static constexpr std::size_t capacity = + base64_encoded_size(20); + std::size_t size = 0; + char data[capacity]; +}; + +void make_request_key( + request_key& out); + +void make_response_key( + response_key& out, + core::string_view in); + +} // detail +} // ws_proto +} // boost + +#endif diff --git a/src/impl/pcg.hpp b/src/impl/pcg.hpp new file mode 100644 index 0000000..add4cee --- /dev/null +++ b/src/impl/pcg.hpp @@ -0,0 +1,65 @@ +// +// Copyright (c) 2016-2025 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/cppalliance/ws_proto +// + +#ifndef BOOST_WS_PROTO_SRC_IMPL_PCG_HPP +#define BOOST_WS_PROTO_SRC_IMPL_PCG_HPP + +#include +#include +#include + +namespace boost { +namespace ws_proto { +namespace detail { + +class pcg +{ + std::uint64_t state_ = 0; + std::uint64_t increment_; + +public: + using result_type = std::uint32_t; + + // Initialize the generator. + // There are no restrictions on the input values. + pcg( + std::uint64_t seed, + std::uint64_t stream) + { + // increment must be odd + increment_ = 2 * stream + 1; + boost::ignore_unused((*this)()); + state_ += seed; + boost::ignore_unused((*this)()); + } + + std::uint32_t + operator()() + { + std::uint64_t const p = state_; + state_ = p * + 6364136223846793005ULL + + increment_; + std::uint32_t const x = + static_cast( + ((p >> 18) ^ p) >> 27); + std::uint32_t const r = p >> 59; + #ifdef BOOST_MSVC + return _rotr(x, r); + #else + return (x >> r) | (x << ((1 + ~r) & 31)); + #endif + } +}; + +} // detail +} // ws_proto +} // boost + +#endif diff --git a/src/impl/prng.cpp b/src/impl/prng.cpp new file mode 100644 index 0000000..3ea48da --- /dev/null +++ b/src/impl/prng.cpp @@ -0,0 +1,142 @@ +// +// Copyright (c) 2016-2019 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/cppalliance/ws_proto +// + +#include "src/impl/prng.hpp" +#include "src/impl/chacha.hpp" +#include "src/impl/pcg.hpp" +#include +#include +#include +#include + +namespace boost { +namespace ws_proto { +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; +} + +//------------------------------------------------------------------------------ + +std::uint32_t +make_nonce() +{ + static std::atomic nonce{0}; + return ++nonce; +} + +detail::pcg +make_pcg() +{ + auto const pv = prng_seed(); + return detail::pcg{ + ((static_cast(pv[0])<<32)+pv[1]) ^ + ((static_cast(pv[2])<<32)+pv[3]) ^ + ((static_cast(pv[4])<<32)+pv[5]) ^ + ((static_cast(pv[6])<<32)+pv[7]), make_nonce()}; +} + +#ifdef BOOST_NO_CXX11_THREAD_LOCAL + +inline +std::uint32_t +secure_generate() +{ + struct generator + { + std::uint32_t operator()() + { + std::lock_guard guard{mtx}; + return gen(); + } + + beast::detail::chacha<20> gen; + std::mutex mtx; + }; + static generator gen{beast::detail::chacha<20>{prng_seed(), make_nonce()}}; + return gen(); +} + +inline +std::uint32_t +fast_generate() +{ + struct generator + { + std::uint32_t operator()() + { + std::lock_guard guard{mtx}; + return gen(); + } + + beast::detail::pcg gen; + std::mutex mtx; + }; + static generator gen{make_pcg()}; + return gen(); +} + +#else + +std::uint32_t +secure_generate() +{ + thread_local static detail::chacha<20> gen{prng_seed(), make_nonce()}; + return gen(); +} + +inline +std::uint32_t +fast_generate() +{ + thread_local static detail::pcg gen{make_pcg()}; + return gen(); +} + +#endif + +generator +make_prng(bool secure) +{ + if (secure) + return &secure_generate; + else + return &fast_generate; +} + +} // detail +} // ws_proto +} // boost diff --git a/src/impl/prng.hpp b/src/impl/prng.hpp new file mode 100644 index 0000000..05cbc99 --- /dev/null +++ b/src/impl/prng.hpp @@ -0,0 +1,41 @@ +// +// Copyright (c) 2016-2025 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/cppalliance/ws_proto +// + +#ifndef BOOST_WS_PROTO_SRC_IMPL_PRNG_HPP +#define BOOST_WS_PROTO_SRC_IMPL_PRNG_HPP + +#include +#include +#include + +namespace boost { +namespace ws_proto { +namespace detail { + +using generator = std::uint32_t(*)(); + +//------------------------------------------------------------------------------ + +// Manually seed the prngs, must be called +// before acquiring a prng for the first time. +// +std::uint32_t const* +prng_seed(std::seed_seq* ss = nullptr); + +// Acquire a PRNG using the TLS implementation if it +// is available, otherwise using the no-TLS implementation. +// +generator +make_prng(bool secure); + +} // detail +} // ws_proto +} // boost + +#endif diff --git a/src/impl/sha1.cpp b/src/impl/sha1.cpp new file mode 100644 index 0000000..79f53ed --- /dev/null +++ b/src/impl/sha1.cpp @@ -0,0 +1,293 @@ +// +// Copyright (c) 2016-2025 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/cppalliance/ws_proto +// + +#include "src/impl/sha1.hpp" + +#include +#include +#include + +// Based on https://github.com/vog/sha1 +/* + Original authors: + Steve Reid (Original C Code) + Bruce Guenter (Small changes to fit into bglibs) + Volker Grabsch (Translation to simpler C++ Code) + Eugene Hopkinson (Safety improvements) + Vincent Falco (beast adaptation) +*/ + +namespace boost { +namespace ws_proto { +namespace sha1 { + +namespace { + +inline +std::uint32_t +rol(std::uint32_t value, std::size_t bits) +{ + return (value << bits) | (value >> (32 - bits)); +} + +inline +std::uint32_t +blk(std::uint32_t block[BLOCK_INTS], std::size_t i) +{ + return rol( + block[(i+13)&15] ^ block[(i+8)&15] ^ + block[(i+2)&15] ^ block[i], 1); +} + +inline +void +R0(std::uint32_t block[BLOCK_INTS], std::uint32_t v, + std::uint32_t &w, std::uint32_t x, std::uint32_t y, + std::uint32_t &z, std::size_t i) +{ + z += ((w&(x^y))^y) + block[i] + 0x5a827999 + rol(v, 5); + w = rol(w, 30); +} + + +inline +void +R1(std::uint32_t block[BLOCK_INTS], std::uint32_t v, + std::uint32_t &w, std::uint32_t x, std::uint32_t y, + std::uint32_t &z, std::size_t i) +{ + block[i] = blk(block, i); + z += ((w&(x^y))^y) + block[i] + 0x5a827999 + rol(v, 5); + w = rol(w, 30); +} + +inline +void +R2(std::uint32_t block[BLOCK_INTS], std::uint32_t v, + std::uint32_t &w, std::uint32_t x, std::uint32_t y, + std::uint32_t &z, std::size_t i) +{ + block[i] = blk(block, i); + z += (w^x^y) + block[i] + 0x6ed9eba1 + rol(v, 5); + w = rol(w, 30); +} + +inline +void +R3(std::uint32_t block[BLOCK_INTS], std::uint32_t v, + std::uint32_t &w, std::uint32_t x, std::uint32_t y, + std::uint32_t &z, std::size_t i) +{ + block[i] = blk(block, i); + z += (((w|x)&y)|(w&x)) + block[i] + 0x8f1bbcdc + rol(v, 5); + w = rol(w, 30); +} + +inline +void +R4(std::uint32_t block[BLOCK_INTS], std::uint32_t v, + std::uint32_t &w, std::uint32_t x, std::uint32_t y, + std::uint32_t &z, std::size_t i) +{ + block[i] = blk(block, i); + z += (w^x^y) + block[i] + 0xca62c1d6 + rol(v, 5); + w = rol(w, 30); +} + +inline +void +make_block(std::uint8_t const* p, + std::uint32_t block[BLOCK_INTS]) +{ + for(std::size_t i = 0; i < BLOCK_INTS; i++) + block[i] = + (static_cast(p[4*i+3])) | + (static_cast(p[4*i+2]))<< 8 | + (static_cast(p[4*i+1]))<<16 | + (static_cast(p[4*i+0]))<<24; +} + +inline +void +transform( + std::uint32_t digest[], std::uint32_t block[BLOCK_INTS]) +{ + std::uint32_t a = digest[0]; + std::uint32_t b = digest[1]; + std::uint32_t c = digest[2]; + std::uint32_t d = digest[3]; + std::uint32_t e = digest[4]; + + R0(block, a, b, c, d, e, 0); + R0(block, e, a, b, c, d, 1); + R0(block, d, e, a, b, c, 2); + R0(block, c, d, e, a, b, 3); + R0(block, b, c, d, e, a, 4); + R0(block, a, b, c, d, e, 5); + R0(block, e, a, b, c, d, 6); + R0(block, d, e, a, b, c, 7); + R0(block, c, d, e, a, b, 8); + R0(block, b, c, d, e, a, 9); + R0(block, a, b, c, d, e, 10); + R0(block, e, a, b, c, d, 11); + R0(block, d, e, a, b, c, 12); + R0(block, c, d, e, a, b, 13); + R0(block, b, c, d, e, a, 14); + R0(block, a, b, c, d, e, 15); + R1(block, e, a, b, c, d, 0); + R1(block, d, e, a, b, c, 1); + R1(block, c, d, e, a, b, 2); + R1(block, b, c, d, e, a, 3); + R2(block, a, b, c, d, e, 4); + R2(block, e, a, b, c, d, 5); + R2(block, d, e, a, b, c, 6); + R2(block, c, d, e, a, b, 7); + R2(block, b, c, d, e, a, 8); + R2(block, a, b, c, d, e, 9); + R2(block, e, a, b, c, d, 10); + R2(block, d, e, a, b, c, 11); + R2(block, c, d, e, a, b, 12); + R2(block, b, c, d, e, a, 13); + R2(block, a, b, c, d, e, 14); + R2(block, e, a, b, c, d, 15); + R2(block, d, e, a, b, c, 0); + R2(block, c, d, e, a, b, 1); + R2(block, b, c, d, e, a, 2); + R2(block, a, b, c, d, e, 3); + R2(block, e, a, b, c, d, 4); + R2(block, d, e, a, b, c, 5); + R2(block, c, d, e, a, b, 6); + R2(block, b, c, d, e, a, 7); + R3(block, a, b, c, d, e, 8); + R3(block, e, a, b, c, d, 9); + R3(block, d, e, a, b, c, 10); + R3(block, c, d, e, a, b, 11); + R3(block, b, c, d, e, a, 12); + R3(block, a, b, c, d, e, 13); + R3(block, e, a, b, c, d, 14); + R3(block, d, e, a, b, c, 15); + R3(block, c, d, e, a, b, 0); + R3(block, b, c, d, e, a, 1); + R3(block, a, b, c, d, e, 2); + R3(block, e, a, b, c, d, 3); + R3(block, d, e, a, b, c, 4); + R3(block, c, d, e, a, b, 5); + R3(block, b, c, d, e, a, 6); + R3(block, a, b, c, d, e, 7); + R3(block, e, a, b, c, d, 8); + R3(block, d, e, a, b, c, 9); + R3(block, c, d, e, a, b, 10); + R3(block, b, c, d, e, a, 11); + R4(block, a, b, c, d, e, 12); + R4(block, e, a, b, c, d, 13); + R4(block, d, e, a, b, c, 14); + R4(block, c, d, e, a, b, 15); + R4(block, b, c, d, e, a, 0); + R4(block, a, b, c, d, e, 1); + R4(block, e, a, b, c, d, 2); + R4(block, d, e, a, b, c, 3); + R4(block, c, d, e, a, b, 4); + R4(block, b, c, d, e, a, 5); + R4(block, a, b, c, d, e, 6); + R4(block, e, a, b, c, d, 7); + R4(block, d, e, a, b, c, 8); + R4(block, c, d, e, a, b, 9); + R4(block, b, c, d, e, a, 10); + R4(block, a, b, c, d, e, 11); + R4(block, e, a, b, c, d, 12); + R4(block, d, e, a, b, c, 13); + R4(block, c, d, e, a, b, 14); + R4(block, b, c, d, e, a, 15); + + digest[0] += a; + digest[1] += b; + digest[2] += c; + digest[3] += d; + digest[4] += e; +} + +} // (anon) + +void +init(context& ctx) noexcept +{ + ctx.buflen = 0; + ctx.blocks = 0; + ctx.digest[0] = 0x67452301; + ctx.digest[1] = 0xefcdab89; + ctx.digest[2] = 0x98badcfe; + ctx.digest[3] = 0x10325476; + ctx.digest[4] = 0xc3d2e1f0; +} + +void +update( + context& ctx, + void const* message, + std::size_t size) noexcept +{ + auto p = static_cast< + std::uint8_t const*>(message); + for(;;) + { + auto const n = (std::min)( + size, sizeof(ctx.buf) - ctx.buflen); + std::memcpy(ctx.buf + ctx.buflen, p, n); + ctx.buflen += n; + if(ctx.buflen != 64) + return; + p += n; + size -= n; + ctx.buflen = 0; + std::uint32_t block[BLOCK_INTS]; + make_block(ctx.buf, block); + transform(ctx.digest, block); + ++ctx.blocks; + } +} + +void +finish( + context& ctx, + void* digest) noexcept +{ + std::uint64_t total_bits = + (ctx.blocks*64 + ctx.buflen) * 8; + // pad + ctx.buf[ctx.buflen++] = 0x80; + auto const buflen = ctx.buflen; + while(ctx.buflen < 64) + ctx.buf[ctx.buflen++] = 0x00; + std::uint32_t block[BLOCK_INTS]; + make_block(ctx.buf, block); + if(buflen > BLOCK_BYTES - 8) + { + transform(ctx.digest, block); + for(size_t i = 0; i < BLOCK_INTS - 2; i++) + block[i] = 0; + } + + /* Append total_bits, split this uint64_t into two uint32_t */ + block[BLOCK_INTS - 1] = total_bits & 0xffffffff; + block[BLOCK_INTS - 2] = (total_bits >> 32); + transform(ctx.digest, block); + for(std::size_t i = 0; i < DIGEST_BYTES/4; i++) + { + std::uint8_t* d = + static_cast(digest) + 4 * i; + d[3] = ctx.digest[i] & 0xff; + d[2] = (ctx.digest[i] >> 8) & 0xff; + d[1] = (ctx.digest[i] >> 16) & 0xff; + d[0] = (ctx.digest[i] >> 24) & 0xff; + } +} + +} // sha1 +} // ws_proto +} // boost diff --git a/src/impl/sha1.hpp b/src/impl/sha1.hpp new file mode 100644 index 0000000..ecb5a57 --- /dev/null +++ b/src/impl/sha1.hpp @@ -0,0 +1,63 @@ +// +// Copyright (c) 2016-2025 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/cppalliance/ws_proto +// + +#ifndef BOOST_WS_PROTO_IMPL_SHA1_HPP +#define BOOST_WS_PROTO_IMPL_SHA1_HPP + +#include +#include + +// Based on https://github.com/vog/sha1 +/* + Original authors: + Steve Reid (Original C Code) + Bruce Guenter (Small changes to fit into bglibs) + Volker Grabsch (Translation to simpler C++ Code) + Eugene Hopkinson (Safety improvements) + Vincent Falco (beast adaptation) +*/ + +namespace boost { +namespace ws_proto { +namespace sha1 { + +static std::size_t constexpr BLOCK_INTS = 16; +static std::size_t constexpr BLOCK_BYTES = 64; +static std::size_t constexpr DIGEST_BYTES = 20; + +struct context +{ + static unsigned int constexpr block_size = BLOCK_BYTES; + static unsigned int constexpr digest_size = 20; + + std::size_t buflen; + std::size_t blocks; + std::uint32_t digest[5]; + std::uint8_t buf[block_size]; +}; + +void +init(context& ctx) noexcept; + +void +update( + context& ctx, + void const* message, + std::size_t size) noexcept; + +void +finish( + context& ctx, + void* digest) noexcept; + +} // sha1 +} // ws_proto +} // boost + +#endif diff --git a/test/unit/client.cpp b/test/unit/client.cpp new file mode 100644 index 0000000..3330c93 --- /dev/null +++ b/test/unit/client.cpp @@ -0,0 +1,31 @@ +// +// Copyright (c) 2025 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/cppalliance/ws_proto +// + +// Test that header file is self-contained. +#include + +#include "test_suite.hpp" + +namespace boost { +namespace ws_proto { + +struct client_test +{ + void + run() + { + } +}; + +TEST_SUITE( + client_test, + "boost.ws_proto.client"); + +} // ws_proto +} // boost