From c652cf066d0b43c7c5bc10b4d56ff99a867e7873 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Thu, 2 Feb 2017 09:05:27 -0500 Subject: [PATCH] Squashed 'src/beast/' changes from c00cd37..06f74f0 06f74f0 Set version to 1.0.0-b26 68f535f Tidy up warnings and tests: 4ee5fa9 Set version to 1.0.0-b25 229d390 Update README.md for CppCast 2017 c3e3a55 Fix deflate setup bug 439a224 WebSocket server examples and test tidying: 29565c8 Remove unnecessary include caa3b39 Fix 32-bit arm7 warnings 0474cc5 Better handler_ptr (API Change): ca38657 Fixes for websocket echo server: 797631c Set version to 1.0.0-b24 a450968 Add permessage-deflate WebSocket extension: 67e965e Make decorator copyable 42899fc Add optional yield_to arguments 61aef03 Simplify Travis package install specification 9d0d7c9 bjam use clang on MACOSX git-subtree-dir: src/beast git-subtree-split: 06f74f05f7de51d7f791a17c2b06840183332cbe --- .travis.yml | 40 +- CHANGELOG.md | 32 +- CMakeLists.txt | 24 + Jamroot | 5 + README.md | 14 +- doc/design.qbk | 13 +- doc/examples.qbk | 9 + doc/overview.qbk | 2 +- doc/quickref.xml | 1 + examples/CMakeLists.txt | 11 + examples/Jamfile.v2 | 4 + examples/http_async_server.hpp | 5 +- examples/websocket_async_echo_server.hpp | 375 ++++++ examples/websocket_echo.cpp | 56 + examples/websocket_sync_echo_server.hpp | 326 +++++ extras/beast/test/sig_wait.hpp | 2 - extras/beast/test/yield_to.hpp | 32 +- include/beast/core/detail/buffer_cat.hpp | 6 +- include/beast/core/handler_ptr.hpp | 112 +- include/beast/core/impl/basic_streambuf.ipp | 6 +- .../beast/core/impl/dynabuf_readstream.ipp | 5 +- include/beast/core/impl/handler_ptr.ipp | 51 +- include/beast/http/impl/parse.ipp | 5 +- include/beast/http/impl/read.ipp | 10 +- include/beast/http/impl/write.ipp | 10 +- include/beast/version.hpp | 2 +- include/beast/websocket/detail/decorator.hpp | 85 +- include/beast/websocket/detail/invokable.hpp | 4 +- include/beast/websocket/detail/mask.hpp | 8 +- .../beast/websocket/detail/pmd_extension.hpp | 472 +++++++ .../beast/websocket/detail/stream_base.hpp | 276 +++-- include/beast/websocket/impl/accept.ipp | 23 +- include/beast/websocket/impl/close.ipp | 5 +- include/beast/websocket/impl/handshake.ipp | 15 +- include/beast/websocket/impl/ping.ipp | 5 +- include/beast/websocket/impl/read.ipp | 478 ++++++-- include/beast/websocket/impl/ssl.ipp | 4 +- include/beast/websocket/impl/stream.ipp | 86 +- include/beast/websocket/impl/teardown.ipp | 4 +- include/beast/websocket/impl/write.ipp | 696 +++++++---- include/beast/websocket/option.hpp | 64 +- include/beast/websocket/stream.hpp | 23 +- include/beast/zlib/detail/deflate_stream.hpp | 6 +- test/Jamfile | 4 - test/core/CMakeLists.txt | 1 - test/core/dynabuf_readstream.cpp | 3 +- test/http/read.cpp | 15 +- test/http/write.cpp | 9 +- test/websocket/CMakeLists.txt | 12 - test/websocket/frame.cpp | 20 +- test/websocket/mask.cpp | 5 + test/websocket/stream.cpp | 1084 ++++++++--------- .../websocket/websocket_async_echo_server.hpp | 352 +++--- test/websocket/websocket_echo.cpp | 34 - test/websocket/websocket_sync_echo_server.hpp | 384 ++++-- test/zlib/CMakeLists.txt | 23 - 56 files changed, 3678 insertions(+), 1680 deletions(-) create mode 100644 examples/websocket_async_echo_server.hpp create mode 100644 examples/websocket_echo.cpp create mode 100644 examples/websocket_sync_echo_server.hpp create mode 100644 include/beast/websocket/detail/pmd_extension.hpp delete mode 100644 test/websocket/websocket_echo.cpp diff --git a/.travis.yml b/.travis.yml index 0035abc4c2..236e8da718 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,21 +13,25 @@ env: - VALGRIND_ROOT=$HOME/valgrind-install - BOOST_ROOT=$HOME/boost_1_61_0 - BOOST_URL='http://sourceforge.net/projects/boost/files/boost/1.61.0/boost_1_61_0.tar.gz' -packages: &gcc5_pkgs - - gcc-5 - - g++-5 - - python-software-properties - - libssl-dev - - libffi-dev - - libstdc++6 - - binutils-gold - # Provides a backtrace if the unittests crash - - gdb - # Needed for installing valgrind - - subversion - - automake - - autotools-dev - - libc6-dbg + +addons: + apt: + sources: ['ubuntu-toolchain-r-test'] + packages: + - gcc-5 + - g++-5 + - python-software-properties + - libssl-dev + - libffi-dev + - libstdc++6 + - binutils-gold + # Provides a backtrace if the unittests crash + - gdb + # Needed for installing valgrind + - subversion + - automake + - autotools-dev + - libc6-dbg matrix: include: @@ -39,10 +43,6 @@ matrix: - ADDRESS_MODEL=64 - BUILD_SYSTEM=cmake - PATH=$PWD/cmake/bin:$PATH - addons: &ao_gcc5 - apt: - sources: ['ubuntu-toolchain-r-test'] - packages: *gcc5_pkgs # Clang/UndefinedBehaviourSanitizer - compiler: clang @@ -55,7 +55,6 @@ matrix: - BUILD_SYSTEM=cmake - PATH=$PWD/cmake/bin:$PATH - PATH=$PWD/llvm-$LLVM_VERSION/bin:$PATH - addons: *ao_gcc5 # Clang/AddressSanitizer - compiler: clang @@ -65,7 +64,6 @@ matrix: - CLANG_VER=3.8 - ADDRESS_MODEL=64 - PATH=$PWD/llvm-$LLVM_VERSION/bin:$PATH - addons: *ao_gcc5 cache: directories: diff --git a/CHANGELOG.md b/CHANGELOG.md index 515988fbca..4aec8f03bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,33 @@ +1.0.0-b26 + +* Tidy up warnings and tests + +-------------------------------------------------------------------------------- + +1.0.0-b25 + +* Fixes for WebSocket echo server +* Fix 32-bit arm7 warnings +* Remove unnecessary include +* WebSocket server examples and test tidying +* Fix deflate setup bug + +API Changes: + +* Better handler_ptr + +-------------------------------------------------------------------------------- + +1.0.0-b24 + +* bjam use clang on MACOSX +* Simplify Travis package install specification +* Add optional yield_to arguments +* Make decorator copyable +* Add WebSocket permessage-deflate extension support + +-------------------------------------------------------------------------------- + 1.0.0-b23 * Tune websocket echo server for performance @@ -5,7 +35,7 @@ * Better logging in async echo server * Add copy special members * Fix message constructor and special members -* Travis CI improvements +* Travis CI improvements -------------------------------------------------------------------------------- diff --git a/CMakeLists.txt b/CMakeLists.txt index 9f760999a3..61a243c030 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,7 @@ project (Beast) set_property (GLOBAL PROPERTY USE_FOLDERS ON) if (MSVC) + # /wd4244 /wd4127 set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4244 /MP /W4 /wd4100 /bigobj /D _WIN32_WINNT=0x0601 /D _SCL_SECURE_NO_WARNINGS=1 /D _CRT_SECURE_NO_WARNINGS=1") set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd") set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Ob2 /Oi /Ot /GL /MT") @@ -96,6 +97,29 @@ endfunction() include_directories (extras) include_directories (include) +set(ZLIB_SOURCES + ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/crc32.h + ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/deflate.h + ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/inffast.h + ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/inffixed.h + ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/inflate.h + ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/inftrees.h + ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/trees.h + ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/zlib.h + ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/zutil.h + ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/adler32.c + ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/compress.c + ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/crc32.c + ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/deflate.c + ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/infback.c + ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/inffast.c + ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/inflate.c + ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/inftrees.c + ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/trees.c + ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/uncompr.c + ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/zutil.c +) + file(GLOB_RECURSE BEAST_INCLUDES ${PROJECT_SOURCE_DIR}/include/beast/*.hpp ${PROJECT_SOURCE_DIR}/include/beast/*.ipp diff --git a/Jamroot b/Jamroot index 1f82785357..543305236a 100644 --- a/Jamroot +++ b/Jamroot @@ -45,6 +45,11 @@ else lib crypto ; } +if [ os.name ] = MACOSX +{ + using clang : : ; +} + variant coverage : debug diff --git a/README.md b/README.md index c2fdc0f607..ef9b317d7a 100644 --- a/README.md +++ b/README.md @@ -8,19 +8,15 @@ (https://img.shields.io/badge/documentation-master-brightgreen.svg)](http://vinniefalco.github.io/beast/) [![License] (https://img.shields.io/badge/license-boost-brightgreen.svg)](LICENSE_1_0.txt) -# HTTP and WebSocket implementations built on Boost.Asio +# HTTP and WebSocket built on Boost.Asio in C++11 --- -## Beast at CppCon 2016 +## Appearances -Presentation -(slides: CppCon2016.pdf) - - -Beast - +| CppCast 2017 | CppCon 2016 | +| ------------ | ----------- | +| Vinnie Falco | Beast | --- diff --git a/doc/design.qbk b/doc/design.qbk index 55f62cead1..50e086a6e5 100644 --- a/doc/design.qbk +++ b/doc/design.qbk @@ -194,15 +194,10 @@ start. Other design goals: [[ What about message compression? ][ - The author is currently porting ZLib 1.2.8 to modern, header-only C++11 - that does not use macros or try to support ancient architectures. This - deflate implementation will be available as its own individually - usable interface, and also will be used to power Beast WebSocket's - permessage-deflate implementation, due Q1 of 2017. - - However, Beast currently has sufficient functionality that users can - begin taking advantage of the WebSocket protocol using this library - immediately. + Beast WebSocket supports the permessage-deflate extension described in + [@https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-00 draft-ietf-hybi-permessage-compression-00]. + The library comes with a header-only, C++11 port of ZLib's "deflate" codec + used in the implementation of the permessage-deflate extension. ]] [[ diff --git a/doc/examples.qbk b/doc/examples.qbk index fd86953671..3ad72b09a4 100644 --- a/doc/examples.qbk +++ b/doc/examples.qbk @@ -85,6 +85,15 @@ int main() } ``` +[heading WebSocket Echo Server] + +This example demonstrates both synchronous and asynchronous +WebSocket server implementations. + +* [@examples/websocket_async_echo_server.hpp] +* [@examples/websocket_ssync_echo_server.hpp] +* [@examples/websocket_echo.cpp] + [heading Secure WebSocket] Establish a WebSocket connection over an encrypted TLS connection, diff --git a/doc/overview.qbk b/doc/overview.qbk index c0639a7c8a..f0a3346172 100644 --- a/doc/overview.qbk +++ b/doc/overview.qbk @@ -59,7 +59,7 @@ your particular build system. [heading Motivation] -Beast is built on Boost.Asio A proposal to add networking functionality to the +Beast is built on Boost.Asio. A proposal to add networking functionality to the C++ standard library, based on Boost.Asio, is under consideration by the committee and on track for standardization. Since the final approved networking interface for the C++ standard library will likely closely resemble the current diff --git a/doc/quickref.xml b/doc/quickref.xml index 3fe63b3efc..238a49fedc 100644 --- a/doc/quickref.xml +++ b/doc/quickref.xml @@ -128,6 +128,7 @@ decorate keep_alive message_type + permessage_deflate pong_callback read_buffer_size read_message_max diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index d4815e907f..6d9e8edb36 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -41,6 +41,17 @@ if (NOT WIN32) target_link_libraries(http-example ${Boost_LIBRARIES} Threads::Threads) endif() +add_executable (websocket-echo + ${BEAST_INCLUDES} + websocket_async_echo_server.hpp + websocket_sync_echo_server.hpp + websocket_echo.cpp +) + +if (NOT WIN32) + target_link_libraries(websocket-echo ${Boost_LIBRARIES} Threads::Threads) +endif() + add_executable (websocket-example ${BEAST_INCLUDES} ${EXTRAS_INCLUDES} diff --git a/examples/Jamfile.v2 b/examples/Jamfile.v2 index 2dd0427bed..0bfbb59472 100644 --- a/examples/Jamfile.v2 +++ b/examples/Jamfile.v2 @@ -18,6 +18,10 @@ exe http-example : http_example.cpp ; +exe websocket-echo : + websocket_echo.cpp + ; + exe websocket-example : websocket_example.cpp ; diff --git a/examples/http_async_server.hpp b/examples/http_async_server.hpp index 2c1df4b435..2e248a3904 100644 --- a/examples/http_async_server.hpp +++ b/examples/http_async_server.hpp @@ -114,9 +114,8 @@ private: template write_op(DeducedHandler&& h, Stream& s, Args&&... args) - : d_(make_handler_ptr( - std::forward(h), s, - std::forward(args)...)) + : d_(std::forward(h), + s, std::forward(args)...) { (*this)(error_code{}, false); } diff --git a/examples/websocket_async_echo_server.hpp b/examples/websocket_async_echo_server.hpp new file mode 100644 index 0000000000..3b0b9ae11c --- /dev/null +++ b/examples/websocket_async_echo_server.hpp @@ -0,0 +1,375 @@ +// +// Copyright (c) 2013-2016 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) +// + +#ifndef WEBSOCKET_ASYNC_ECHO_SERVER_HPP +#define WEBSOCKET_ASYNC_ECHO_SERVER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace websocket { + +/** Asynchronous WebSocket echo client/server +*/ +class async_echo_server +{ +public: + using error_code = beast::error_code; + using address_type = boost::asio::ip::address; + using socket_type = boost::asio::ip::tcp::socket; + using endpoint_type = boost::asio::ip::tcp::endpoint; + +private: + struct identity + { + template + void + operator()(beast::http::message< + true, Body, Fields>& req) const + { + req.fields.replace("User-Agent", "async_echo_client"); + } + + template + void + operator()(beast::http::message< + false, Body, Fields>& resp) const + { + resp.fields.replace("Server", "async_echo_server"); + } + }; + + /** A container of type-erased option setters. + */ + template + class options_set + { + // workaround for std::function bug in msvc + struct callable + { + virtual ~callable() = default; + virtual void operator()( + beast::websocket::stream&) = 0; + }; + + template + class callable_impl : public callable + { + T t_; + + public: + template + callable_impl(U&& u) + : t_(std::forward(u)) + { + } + + void + operator()(beast::websocket::stream& ws) + { + t_(ws); + } + }; + + template + class lambda + { + Opt opt_; + + public: + lambda(lambda&&) = default; + lambda(lambda const&) = default; + + lambda(Opt const& opt) + : opt_(opt) + { + } + + void + operator()(beast::websocket::stream& ws) const + { + ws.set_option(opt_); + } + }; + + std::unordered_map> list_; + + public: + template + void + set_option(Opt const& opt) + { + std::unique_ptr p; + p.reset(new callable_impl>{opt}); + list_[std::type_index{ + typeid(Opt)}] = std::move(p); + } + + void + set_options(beast::websocket::stream& ws) + { + for(auto const& op : list_) + (*op.second)(ws); + } + }; + + std::ostream* log_; + boost::asio::io_service ios_; + socket_type sock_; + endpoint_type ep_; + boost::asio::ip::tcp::acceptor acceptor_; + std::vector thread_; + boost::optional work_; + options_set opts_; + +public: + async_echo_server(async_echo_server const&) = delete; + async_echo_server& operator=(async_echo_server const&) = delete; + + /** Constructor. + + @param log A pointer to a stream to log to, or `nullptr` + to disable logging. + + @param threads The number of threads in the io_service. + */ + async_echo_server(std::ostream* log, + std::size_t threads) + : log_(log) + , sock_(ios_) + , acceptor_(ios_) + , work_(ios_) + { + opts_.set_option( + beast::websocket::decorate(identity{})); + thread_.reserve(threads); + for(std::size_t i = 0; i < threads; ++i) + thread_.emplace_back( + [&]{ ios_.run(); }); + } + + /** Destructor. + */ + ~async_echo_server() + { + work_ = boost::none; + error_code ec; + ios_.dispatch( + [&]{ acceptor_.close(ec); }); + for(auto& t : thread_) + t.join(); + } + + /** Return the listening endpoint. + */ + endpoint_type + local_endpoint() const + { + return acceptor_.local_endpoint(); + } + + /** Set a websocket option. + + The option will be applied to all new connections. + + @param opt The option to apply. + */ + template + void + set_option(Opt const& opt) + { + opts_.set_option(opt); + } + + /** Open a listening port. + + @param ep The address and port to bind to. + + @param ec Set to the error, if any occurred. + */ + void + open(endpoint_type const& ep, error_code& ec) + { + acceptor_.open(ep.protocol(), ec); + if(ec) + return fail("open", ec); + acceptor_.set_option( + boost::asio::socket_base::reuse_address{true}); + acceptor_.bind(ep, ec); + if(ec) + return fail("bind", ec); + acceptor_.listen( + boost::asio::socket_base::max_connections, ec); + if(ec) + return fail("listen", ec); + acceptor_.async_accept(sock_, ep_, + std::bind(&async_echo_server::on_accept, this, + beast::asio::placeholders::error)); + } + +private: + class peer + { + struct data + { + async_echo_server& server; + endpoint_type ep; + int state = 0; + beast::websocket::stream ws; + boost::asio::io_service::strand strand; + beast::websocket::opcode op; + beast::streambuf db; + std::size_t id; + + data(async_echo_server& server_, + endpoint_type const& ep_, + socket_type&& sock_) + : server(server_) + , ep(ep_) + , ws(std::move(sock_)) + , strand(ws.get_io_service()) + , id([] + { + static std::atomic n{0}; + return ++n; + }()) + { + } + }; + + // VFALCO This could be unique_ptr in [Net.TS] + std::shared_ptr d_; + + public: + peer(peer&&) = default; + peer(peer const&) = default; + peer& operator=(peer&&) = delete; + peer& operator=(peer const&) = delete; + + template + explicit + peer(async_echo_server& server, + endpoint_type const& ep, socket_type&& sock, + Args&&... args) + : d_(std::make_shared(server, ep, + std::forward(sock), + std::forward(args)...)) + { + auto& d = *d_; + d.server.opts_.set_options(d.ws); + run(); + } + + void run() + { + auto& d = *d_; + d.ws.async_accept(std::move(*this)); + } + + void operator()(error_code ec, std::size_t) + { + (*this)(ec); + } + + void operator()(error_code ec) + { + using boost::asio::buffer; + using boost::asio::buffer_copy; + auto& d = *d_; + switch(d.state) + { + // did accept + case 0: + if(ec) + return fail("async_accept", ec); + + // start + case 1: + if(ec) + return fail("async_handshake", ec); + d.db.consume(d.db.size()); + // read message + d.state = 2; + d.ws.async_read(d.op, d.db, + d.strand.wrap(std::move(*this))); + return; + + // got message + case 2: + if(ec == beast::websocket::error::closed) + return; + if(ec) + return fail("async_read", ec); + // write message + d.state = 1; + d.ws.set_option( + beast::websocket::message_type(d.op)); + d.ws.async_write(d.db.data(), + d.strand.wrap(std::move(*this))); + return; + } + } + + private: + void + fail(std::string what, error_code ec) + { + auto& d = *d_; + if(d.server.log_) + if(ec != beast::websocket::error::closed) + d.server.fail("[#" + std::to_string(d.id) + + " " + boost::lexical_cast(d.ep) + + "] " + what, ec); + } + }; + + void + fail(std::string what, error_code ec) + { + if(log_) + { + static std::mutex m; + std::lock_guard lock{m}; + (*log_) << what << ": " << + ec.message() << std::endl; + } + } + + void + on_accept(error_code ec) + { + if(! acceptor_.is_open()) + return; + if(ec == boost::asio::error::operation_aborted) + return; + if(ec) + fail("accept", ec); + peer{*this, ep_, std::move(sock_)}; + acceptor_.async_accept(sock_, ep_, + std::bind(&async_echo_server::on_accept, this, + beast::asio::placeholders::error)); + } +}; + +} // websocket + +#endif diff --git a/examples/websocket_echo.cpp b/examples/websocket_echo.cpp new file mode 100644 index 0000000000..bf7ac9cb66 --- /dev/null +++ b/examples/websocket_echo.cpp @@ -0,0 +1,56 @@ +// +// Copyright (c) 2013-2016 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) +// + +#include "websocket_async_echo_server.hpp" +#include "websocket_sync_echo_server.hpp" +#include +#include +#include + +/// Block until SIGINT or SIGTERM is received. +void +sig_wait() +{ + boost::asio::io_service ios; + boost::asio::signal_set signals( + ios, SIGINT, SIGTERM); + signals.async_wait( + [&](boost::system::error_code const&, int) + { + }); + ios.run(); +} + +int main() +{ + using namespace beast::websocket; + using endpoint_type = boost::asio::ip::tcp::endpoint; + using address_type = boost::asio::ip::address; + + beast::error_code ec; + + permessage_deflate pmd; + pmd.client_enable = true; + pmd.server_enable = true; + pmd.compLevel = 3; + + websocket::async_echo_server s1{&std::cout, 1}; + s1.set_option(read_message_max{64 * 1024 * 1024}); + s1.set_option(auto_fragment{false}); + s1.set_option(pmd); + s1.open(endpoint_type{ + address_type::from_string("127.0.0.1"), 6000 }, ec); + + websocket::sync_echo_server s2{&std::cout}; + s2.set_option(read_message_max{64 * 1024 * 1024}); + s2.set_option(auto_fragment{false}); + s2.set_option(pmd); + s2.open(endpoint_type{ + address_type::from_string("127.0.0.1"), 6001 }, ec); + + sig_wait(); +} diff --git a/examples/websocket_sync_echo_server.hpp b/examples/websocket_sync_echo_server.hpp new file mode 100644 index 0000000000..a4b0ba3a04 --- /dev/null +++ b/examples/websocket_sync_echo_server.hpp @@ -0,0 +1,326 @@ +// +// Copyright (c) 2013-2016 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) +// + +#ifndef WEBSOCKET_SYNC_ECHO_SERVER_HPP +#define WEBSOCKET_SYNC_ECHO_SERVER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace websocket { + +/** Synchronous WebSocket echo client/server +*/ +class sync_echo_server +{ +public: + using error_code = beast::error_code; + using endpoint_type = boost::asio::ip::tcp::endpoint; + using address_type = boost::asio::ip::address; + using socket_type = boost::asio::ip::tcp::socket; + +private: + struct identity + { + template + void + operator()(beast::http::message< + true, Body, Fields>& req) const + { + req.fields.replace("User-Agent", "sync_echo_client"); + } + + template + void + operator()(beast::http::message< + false, Body, Fields>& resp) const + { + resp.fields.replace("Server", "sync_echo_server"); + } + }; + + /** A container of type-erased option setters. + */ + template + class options_set + { + // workaround for std::function bug in msvc + struct callable + { + virtual ~callable() = default; + virtual void operator()( + beast::websocket::stream&) = 0; + }; + + template + class callable_impl : public callable + { + T t_; + + public: + template + callable_impl(U&& u) + : t_(std::forward(u)) + { + } + + void + operator()(beast::websocket::stream& ws) + { + t_(ws); + } + }; + + template + class lambda + { + Opt opt_; + + public: + lambda(lambda&&) = default; + lambda(lambda const&) = default; + + lambda(Opt const& opt) + : opt_(opt) + { + } + + void + operator()(beast::websocket::stream& ws) const + { + ws.set_option(opt_); + } + }; + + std::unordered_map> list_; + + public: + template + void + set_option(Opt const& opt) + { + std::unique_ptr p; + p.reset(new callable_impl>{opt}); + list_[std::type_index{ + typeid(Opt)}] = std::move(p); + } + + void + set_options(beast::websocket::stream& ws) + { + for(auto const& op : list_) + (*op.second)(ws); + } + }; + + std::ostream* log_; + boost::asio::io_service ios_; + socket_type sock_; + endpoint_type ep_; + boost::asio::ip::tcp::acceptor acceptor_; + std::thread thread_; + options_set opts_; + +public: + /** Constructor. + + @param log A pointer to a stream to log to, or `nullptr` + to disable logging. + */ + sync_echo_server(std::ostream* log) + : log_(log) + , sock_(ios_) + , acceptor_(ios_) + { + opts_.set_option( + beast::websocket::decorate(identity{})); + } + + /** Destructor. + */ + ~sync_echo_server() + { + if(thread_.joinable()) + { + error_code ec; + ios_.dispatch( + [&]{ acceptor_.close(ec); }); + thread_.join(); + } + } + + /** Return the listening endpoint. + */ + endpoint_type + local_endpoint() const + { + return acceptor_.local_endpoint(); + } + + /** Set a websocket option. + + The option will be applied to all new connections. + + @param opt The option to apply. + */ + template + void + set_option(Opt const& opt) + { + opts_.set_option(opt); + } + + /** Open a listening port. + + @param ep The address and port to bind to. + + @param ec Set to the error, if any occurred. + */ + void + open(endpoint_type const& ep, error_code& ec) + { + acceptor_.open(ep.protocol(), ec); + if(ec) + return fail("open", ec); + acceptor_.set_option( + boost::asio::socket_base::reuse_address{true}); + acceptor_.bind(ep, ec); + if(ec) + return fail("bind", ec); + acceptor_.listen( + boost::asio::socket_base::max_connections, ec); + if(ec) + return fail("listen", ec); + acceptor_.async_accept(sock_, ep_, + std::bind(&sync_echo_server::on_accept, this, + beast::asio::placeholders::error)); + thread_ = std::thread{[&]{ ios_.run(); }}; + } + +private: + void + fail(std::string what, error_code ec) + { + if(log_) + { + static std::mutex m; + std::lock_guard lock{m}; + (*log_) << what << ": " << + ec.message() << std::endl; + } + } + + void + fail(std::string what, error_code ec, + int id, endpoint_type const& ep) + { + if(log_) + if(ec != beast::websocket::error::closed) + fail("[#" + std::to_string(id) + " " + + boost::lexical_cast(ep) + + "] " + what, ec); + } + + void + on_accept(error_code ec) + { + if(ec == boost::asio::error::operation_aborted) + return; + if(ec) + return fail("accept", ec); + struct lambda + { + std::size_t id; + endpoint_type ep; + sync_echo_server& self; + boost::asio::io_service::work work; + // Must be destroyed before work otherwise the + // io_service could be destroyed before the socket. + socket_type sock; + + lambda(sync_echo_server& self_, + endpoint_type const& ep_, + socket_type&& sock_) + : id([] + { + static std::atomic n{0}; + return ++n; + }()) + , ep(ep_) + , self(self_) + , work(sock_.get_io_service()) + , sock(std::move(sock_)) + { + } + + void operator()() + { + self.do_peer(id, ep, std::move(sock)); + } + }; + std::thread{lambda{*this, ep_, std::move(sock_)}}.detach(); + acceptor_.async_accept(sock_, ep_, + std::bind(&sync_echo_server::on_accept, this, + beast::asio::placeholders::error)); + } + + void + do_peer(std::size_t id, + endpoint_type const& ep, socket_type&& sock) + { + using boost::asio::buffer; + using boost::asio::buffer_copy; + beast::websocket::stream< + socket_type> ws{std::move(sock)}; + opts_.set_options(ws); + error_code ec; + ws.accept(ec); + if(ec) + { + fail("accept", ec, id, ep); + return; + } + for(;;) + { + beast::websocket::opcode op; + beast::streambuf sb; + ws.read(op, sb, ec); + if(ec) + { + auto const s = ec.message(); + break; + } + ws.set_option(beast::websocket::message_type{op}); + ws.write(sb.data(), ec); + if(ec) + break; + } + if(ec && ec != beast::websocket::error::closed) + { + fail("read", ec, id, ep); + } + } +}; + +} // websocket + +#endif diff --git a/extras/beast/test/sig_wait.hpp b/extras/beast/test/sig_wait.hpp index 5abcdec5d3..a1071bdf2d 100644 --- a/extras/beast/test/sig_wait.hpp +++ b/extras/beast/test/sig_wait.hpp @@ -9,8 +9,6 @@ #define BEAST_TEST_SIG_WAIT_HPP #include -#include -#include namespace beast { namespace test { diff --git a/extras/beast/test/yield_to.hpp b/extras/beast/test/yield_to.hpp index 5c4d3c01f9..fecd8d4bfd 100644 --- a/extras/beast/test/yield_to.hpp +++ b/extras/beast/test/yield_to.hpp @@ -21,9 +21,9 @@ namespace test { /** Mix-in to support tests using asio coroutines. - Derive from this class and use yield_to to launch test functions - inside coroutines. This is handy for testing asynchronous asio - code. + Derive from this class and use yield_to to launch test + functions inside coroutines. This is handy for testing + asynchronous asio code. */ class enable_yield_to { @@ -72,12 +72,32 @@ public: Function will be called with this signature: @code - void f(yield_context); + void f(args..., yield_context); @endcode + + @param f The Callable object to invoke. + + @param args Optional arguments forwarded to the callable object. */ - template +#if GENERATING_DOCS + template void - yield_to(Function&& f); + yield_to(F&& f, Args&&... args); +#else + template + void + yield_to(F&& f); + + template + void + yield_to(Function&& f, Arg&& arg, Args&&... args) + { + yield_to(std::bind(f, + std::forward(arg), + std::forward(args)..., + std::placeholders::_1)); + } +#endif }; template diff --git a/include/beast/core/detail/buffer_cat.hpp b/include/beast/core/detail/buffer_cat.hpp index b94d996566..e414b7ed49 100644 --- a/include/beast/core/detail/buffer_cat.hpp +++ b/include/beast/core/detail/buffer_cat.hpp @@ -83,7 +83,8 @@ class buffer_cat_helper::const_iterator iter() { return *reinterpret_cast< - iter_t*>(buf_.data()); + iter_t*>(static_cast( + buf_.data())); } template @@ -91,7 +92,8 @@ class buffer_cat_helper::const_iterator iter() const { return *reinterpret_cast< - iter_t const*>(buf_.data()); + iter_t const*>(static_cast< + void const*>(buf_.data())); } public: diff --git a/include/beast/core/handler_ptr.hpp b/include/beast/core/handler_ptr.hpp index 175be8bbd4..45285f48df 100644 --- a/include/beast/core/handler_ptr.hpp +++ b/include/beast/core/handler_ptr.hpp @@ -11,23 +11,25 @@ #include #include #include +#include #include namespace beast { -/** A smart pointer container. +/** A smart pointer container with associated completion handler. This is a smart pointer that retains shared ownership of an object through a pointer. Memory is managed using the allocation and deallocation functions associated with a completion handler, - which is also stored in the object. The object is destroyed and - its memory deallocated when one of the following happens: + which is also stored in the object. The managed object is + destroyed and its memory deallocated when one of the following + happens: @li The function @ref invoke is called. - - @li The function @ref release_handler is called - - @li The last remaining container owning the object is destroyed + + @li The function @ref release_handler is called. + + @li The last remaining container owning the object is destroyed. Objects of this type are used in the implementation of composed operations. Typically the composed operation's shared @@ -38,6 +40,10 @@ namespace beast { @note The reference count is stored using a 16 bit unsigned integer. Making more than 2^16 copies of one object results in undefined behavior. + + @tparam T The type of the owned object. + + @tparam Handler The type of the completion handler. */ template class handler_ptr @@ -60,10 +66,10 @@ class handler_ptr P* p_; - template - handler_ptr(int, DeducedHandler&& handler, Args&&... args); - public: + /// The type of element this object stores + using element_type = T; + /// The type of handler this object stores using handler_type = Handler; @@ -88,6 +94,46 @@ public: /// Copy constructor handler_ptr(handler_ptr const& other); + /** Construct a new @ref handler_ptr + + This creates a new @ref handler_ptr with an owned object + of type `T`. The allocator associated with the handler will + be used to allocate memory for the owned object. The constructor + for the owned object will be called thusly: + + @code + T(handler, std::forward(args)...) + @endcode + + @param handler The handler to associate with the owned + object. The argument will be moved. + + @param args Optional arguments forwarded to + the owned object's constructor. + */ + template + handler_ptr(Handler&& handler, Args&&... args); + + /** Construct a new @ref handler_ptr + + This creates a new @ref handler_ptr with an owned object + of type `T`. The allocator associated with the handler will + be used to allocate memory for the owned object. The constructor + for the owned object will be called thusly: + + @code + T(handler, std::forward(args)...) + @endcode + + @param handler The handler to associate with the owned + object. The argument will be copied. + + @param args Optional arguments forwarded to + the owned object's constructor. + */ + template + handler_ptr(Handler const& handler, Args&&... args); + /// Returns a reference to the handler handler_type& handler() const @@ -95,25 +141,36 @@ public: return p_->handler; } - /// Returns a pointer to the owned object + /// Returns `true` if `*this` owns an object. + explicit + operator bool() const + { + return p_ && p_->t; + } + + /** Returns a pointer to the owned object. + + If `*this` owns an object, a pointer to the + object is returned, else `nullptr` is returned. + */ T* get() const { - return p_->t; + return p_ ? p_->t : nullptr; } /// Return a reference to the owned object. T& operator*() const { - return *get(); + return *p_->t; } /// Return a pointer to the owned object. T* operator->() const { - return get(); + return p_->t; } /** Release ownership of the handler @@ -137,33 +194,6 @@ public: template void invoke(Args&&... args); - - // VFALCO The free function interface works around - // a horrible Visual Studio 15 Update 3 bug - - /** Construct a new `handler_ptr`. - - @param handler The handler. The allocator associated with - the handler will be used to allocate memory for the owned - object. This argument will be forwarded to the owned object's - constructor. - - @param args Optional arguments forwarded to - the owned object's constructor. - */ - /** @{ */ - template - friend - handler_ptr - make_handler_ptr( - CompletionHandler&& handler, Args&&... args); - - template - friend - handler_ptr - make_handler_ptr( - CompletionHandler const& handler, Args&&... args); - /** @} */ }; } // beast diff --git a/include/beast/core/impl/basic_streambuf.ipp b/include/beast/core/impl/basic_streambuf.ipp index a3fdcef706..710a5a1a7a 100644 --- a/include/beast/core/impl/basic_streambuf.ipp +++ b/include/beast/core/impl/basic_streambuf.ipp @@ -598,9 +598,9 @@ basic_streambuf::prepare(size_type n) -> while(n > 0) { auto const size = std::max(alloc_size_, n); - auto& e = *reinterpret_cast( - alloc_traits::allocate(this->member(), - sizeof(element) + size)); + auto& e = *reinterpret_cast(static_cast< + void*>(alloc_traits::allocate(this->member(), + sizeof(element) + size))); alloc_traits::construct(this->member(), &e, size); list_.push_back(e); if(out_ == list_.end()) diff --git a/include/beast/core/impl/dynabuf_readstream.ipp b/include/beast/core/impl/dynabuf_readstream.ipp index bcd7a0a517..3981c771da 100644 --- a/include/beast/core/impl/dynabuf_readstream.ipp +++ b/include/beast/core/impl/dynabuf_readstream.ipp @@ -45,9 +45,8 @@ public: template read_some_op(DeducedHandler&& h, dynabuf_readstream& srs, Args&&... args) - : d_(make_handler_ptr( - std::forward(h), srs, - std::forward(args)...)) + : d_(std::forward(h), + srs, std::forward(args)...) { (*this)(error_code{}, 0); } diff --git a/include/beast/core/impl/handler_ptr.ipp b/include/beast/core/impl/handler_ptr.ipp index 0f57a24d32..eb29e99843 100644 --- a/include/beast/core/impl/handler_ptr.ipp +++ b/include/beast/core/impl/handler_ptr.ipp @@ -39,15 +39,6 @@ P(DeducedHandler&& h, Args&&... args) } } -template -template -handler_ptr:: -handler_ptr(int, DeducedHandler&& handler, Args&&... args) - : p_(new P(std::forward(handler), - std::forward(args)...)) -{ -} - template handler_ptr:: ~handler_ptr() @@ -82,6 +73,27 @@ handler_ptr(handler_ptr const& other) ++p_->n; } +template +template +handler_ptr:: +handler_ptr(Handler&& handler, Args&&... args) + : p_(new P{std::move(handler), + std::forward(args)...}) +{ + static_assert(! std::is_array::value, + "T must not be an array type"); +} + +template +template +handler_ptr:: +handler_ptr(Handler const& handler, Args&&... args) + : p_(new P{handler, std::forward(args)...}) +{ + static_assert(! std::is_array::value, + "T must not be an array type"); +} + template auto handler_ptr:: @@ -112,27 +124,6 @@ invoke(Args&&... args) p_->handler(std::forward(args)...); } -template< - class T, class CompletionHandler, class... Args> -handler_ptr -make_handler_ptr( - CompletionHandler&& handler, Args&&... args) -{ - return handler_ptr{0, - std::move(handler), - std::forward(args)...}; -} - -template< - class T, class CompletionHandler, class... Args> -handler_ptr -make_handler_ptr( - CompletionHandler const& handler, Args&&... args) -{ - return handler_ptr{0, - handler, std::forward(args)...}; -} - } // beast #endif diff --git a/include/beast/http/impl/parse.ipp b/include/beast/http/impl/parse.ipp index 913b41b165..c2ddae8ba8 100644 --- a/include/beast/http/impl/parse.ipp +++ b/include/beast/http/impl/parse.ipp @@ -53,9 +53,8 @@ public: template parse_op(DeducedHandler&& h, Stream& s, Args&&... args) - : d_(make_handler_ptr( - std::forward(h), s, - std::forward(args)...)) + : d_(std::forward(h), + s, std::forward(args)...) { (*this)(error_code{}, 0, false); } diff --git a/include/beast/http/impl/read.ipp b/include/beast/http/impl/read.ipp index 3f3961ddee..e009170f3b 100644 --- a/include/beast/http/impl/read.ipp +++ b/include/beast/http/impl/read.ipp @@ -64,9 +64,8 @@ public: template read_header_op( DeducedHandler&& h, Stream& s, Args&&... args) - : d_(make_handler_ptr( - std::forward(h), s, - std::forward(args)...)) + : d_(std::forward(h), + s, std::forward(args)...) { (*this)(error_code{}, false); } @@ -236,9 +235,8 @@ public: template read_op(DeducedHandler&& h, Stream& s, Args&&... args) - : d_(make_handler_ptr( - std::forward(h), s, - std::forward(args)...)) + : d_(std::forward(h), + s, std::forward(args)...) { (*this)(error_code{}, false); } diff --git a/include/beast/http/impl/write.ipp b/include/beast/http/impl/write.ipp index c2f7104364..13a5078472 100644 --- a/include/beast/http/impl/write.ipp +++ b/include/beast/http/impl/write.ipp @@ -126,9 +126,8 @@ public: template write_streambuf_op(DeducedHandler&& h, Stream& s, Args&&... args) - : d_(make_handler_ptr( - std::forward(h), - s, std::forward(args)...)) + : d_(std::forward(h), + s, std::forward(args)...) { (*this)(error_code{}, 0, false); } @@ -373,9 +372,8 @@ public: template write_op(DeducedHandler&& h, Stream& s, Args&&... args) - : d_(make_handler_ptr( - std::forward(h), s, - std::forward(args)...)) + : d_(std::forward(h), + s, std::forward(args)...) { auto& d = *d_; auto sp = d_; diff --git a/include/beast/version.hpp b/include/beast/version.hpp index cc71abf074..10d4028c01 100644 --- a/include/beast/version.hpp +++ b/include/beast/version.hpp @@ -16,6 +16,6 @@ // #define BEAST_VERSION 100000 -#define BEAST_VERSION_STRING "1.0.0-b23" +#define BEAST_VERSION_STRING "1.0.0-b26" #endif diff --git a/include/beast/websocket/detail/decorator.hpp b/include/beast/websocket/detail/decorator.hpp index 1250d35b1e..4be383da23 100644 --- a/include/beast/websocket/detail/decorator.hpp +++ b/include/beast/websocket/detail/decorator.hpp @@ -12,6 +12,7 @@ #include #include #include +#include #include namespace beast { @@ -29,104 +30,136 @@ struct abstract_decorator virtual void - operator()(request_type& req) = 0; + operator()(request_type& req) const = 0; virtual void - operator()(response_type& resp) = 0; + operator()(response_type& res) const = 0; }; -template +template class decorator : public abstract_decorator { - T t_; + F f_; class call_req_possible { template().operator()( + std::declval().operator()( std::declval()), std::true_type{})> static R check(int); template static std::false_type check(...); public: - using type = decltype(check(0)); + using type = decltype(check(0)); }; class call_res_possible { template().operator()( + std::declval().operator()( std::declval()), std::true_type{})> static R check(int); template static std::false_type check(...); public: - using type = decltype(check(0)); + using type = decltype(check(0)); }; public: - decorator() = default; - - decorator(T&& t) - : t_(std::move(t)) + decorator(F&& t) + : f_(std::move(t)) { } - decorator(T const& t) - : t_(t) + decorator(F const& t) + : f_(t) { } void - operator()(request_type& req) override + operator()(request_type& req) const override { (*this)(req, typename call_req_possible::type{}); } void - operator()(response_type& resp) override + operator()(response_type& res) const override { - (*this)(resp, typename call_res_possible::type{}); + (*this)(res, typename call_res_possible::type{}); } private: void - operator()(request_type& req, std::true_type) + operator()(request_type& req, std::true_type) const { - t_(req); + f_(req); } void - operator()(request_type& req, std::false_type) + operator()(request_type& req, std::false_type) const { req.fields.replace("User-Agent", std::string{"Beast/"} + BEAST_VERSION_STRING); } void - operator()(response_type& res, std::true_type) + operator()(response_type& res, std::true_type) const { - t_(res); + f_(res); } void - operator()(response_type& res, std::false_type) + operator()(response_type& res, std::false_type) const { res.fields.replace("Server", std::string{"Beast/"} + BEAST_VERSION_STRING); } }; +class decorator_type +{ + std::shared_ptr p_; + +public: + decorator_type() = delete; + decorator_type(decorator_type&&) = default; + decorator_type(decorator_type const&) = default; + decorator_type& operator=(decorator_type&&) = default; + decorator_type& operator=(decorator_type const&) = default; + + template::type, + decorator_type>::value>> + decorator_type(F&& f) + : p_(std::make_shared>( + std::forward(f))) + { + BOOST_ASSERT(p_); + } + + void + operator()(request_type& req) + { + (*p_)(req); + BOOST_ASSERT(p_); + } + + void + operator()(response_type& res) + { + (*p_)(res); + BOOST_ASSERT(p_); + } +}; + struct default_decorator { }; -using decorator_type = - std::unique_ptr; - } // detail } // websocket } // beast diff --git a/include/beast/websocket/detail/invokable.hpp b/include/beast/websocket/detail/invokable.hpp index 7fcfc2cf9b..e2a9a24bc9 100644 --- a/include/beast/websocket/detail/invokable.hpp +++ b/include/beast/websocket/detail/invokable.hpp @@ -125,7 +125,7 @@ public: void emplace(F&& f); - void + bool maybe_invoke() { if(base_) @@ -133,7 +133,9 @@ public: auto const basep = base_; base_ = nullptr; (*basep)(); + return true; } + return false; } }; diff --git a/include/beast/websocket/detail/mask.hpp b/include/beast/websocket/detail/mask.hpp index 02ca38fda9..8fb1bcac78 100644 --- a/include/beast/websocket/detail/mask.hpp +++ b/include/beast/websocket/detail/mask.hpp @@ -60,11 +60,17 @@ void maskgen_t<_>::rekey() { std::random_device rng; +#if 0 std::array e; for(auto& i : e) i = rng(); + // VFALCO This constructor causes + // address sanitizer to fail, no idea why. std::seed_seq ss(e.begin(), e.end()); g_.seed(ss); +#else + g_.seed(rng()); +#endif } // VFALCO NOTE This generator has 5KB of state! @@ -73,7 +79,7 @@ using maskgen = maskgen_t; //------------------------------------------------------------------------------ -using prepared_key_type = +using prepared_key = std::conditional::type; diff --git a/include/beast/websocket/detail/pmd_extension.hpp b/include/beast/websocket/detail/pmd_extension.hpp new file mode 100644 index 0000000000..8bccb1c877 --- /dev/null +++ b/include/beast/websocket/detail/pmd_extension.hpp @@ -0,0 +1,472 @@ +// +// Copyright (c) 2013-2016 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) +// + +#ifndef BEAST_WEBSOCKET_DETAIL_PMD_EXTENSION_HPP +#define BEAST_WEBSOCKET_DETAIL_PMD_EXTENSION_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace websocket { +namespace detail { + +// permessage-deflate offer parameters +// +// "context takeover" means: +// preserve sliding window across messages +// +struct pmd_offer +{ + bool accept; + + // 0 = absent, or 8..15 + int server_max_window_bits; + + // -1 = present, 0 = absent, or 8..15 + int client_max_window_bits; + + // `true` if server_no_context_takeover offered + bool server_no_context_takeover; + + // `true` if client_no_context_takeover offered + bool client_no_context_takeover; +}; + +template +int +parse_bits(boost::string_ref const& s) +{ + if(s.size() == 0) + return -1; + if(s.size() > 2) + return -1; + if(s[0] < '1' || s[0] > '9') + return -1; + int i = 0; + for(auto c : s) + { + if(c < '0' || c > '9') + return -1; + i = 10 * i + (c - '0'); + } + return i; +} + +// Parse permessage-deflate request fields +// +template +void +pmd_read(pmd_offer& offer, Fields const& fields) +{ + offer.accept = false; + offer.server_max_window_bits= 0; + offer.client_max_window_bits = 0; + offer.server_no_context_takeover = false; + offer.client_no_context_takeover = false; + + using beast::detail::ci_equal; + http::ext_list list{ + fields["Sec-WebSocket-Extensions"]}; + for(auto const& ext : list) + { + if(ci_equal(ext.first, "permessage-deflate")) + { + for(auto const& param : ext.second) + { + if(ci_equal(param.first, + "server_max_window_bits")) + { + if(offer.server_max_window_bits != 0) + { + // The negotiation offer contains multiple + // extension parameters with the same name. + // + return; // MUST decline + } + if(param.second.empty()) + { + // The negotiation offer extension + // parameter is missing the value. + // + return; // MUST decline + } + offer.server_max_window_bits = + parse_bits(param.second); + if( offer.server_max_window_bits < 8 || + offer.server_max_window_bits > 15) + { + // The negotiation offer contains an + // extension parameter with an invalid value. + // + return; // MUST decline + } + } + else if(ci_equal(param.first, + "client_max_window_bits")) + { + if(offer.client_max_window_bits != 0) + { + // The negotiation offer contains multiple + // extension parameters with the same name. + // + return; // MUST decline + } + if(! param.second.empty()) + { + offer.client_max_window_bits = + parse_bits(param.second); + if( offer.client_max_window_bits < 8 || + offer.client_max_window_bits > 15) + { + // The negotiation offer contains an + // extension parameter with an invalid value. + // + return; // MUST decline + } + } + else + { + offer.client_max_window_bits = -1; + } + } + else if(ci_equal(param.first, + "server_no_context_takeover")) + { + if(offer.server_no_context_takeover) + { + // The negotiation offer contains multiple + // extension parameters with the same name. + // + return; // MUST decline + } + if(! param.second.empty()) + { + // The negotiation offer contains an + // extension parameter with an invalid value. + // + return; // MUST decline + } + offer.server_no_context_takeover = true; + } + else if(ci_equal(param.first, + "client_no_context_takeover")) + { + if(offer.client_no_context_takeover) + { + // The negotiation offer contains multiple + // extension parameters with the same name. + // + return; // MUST decline + } + if(! param.second.empty()) + { + // The negotiation offer contains an + // extension parameter with an invalid value. + // + return; // MUST decline + } + offer.client_no_context_takeover = true; + } + else + { + // The negotiation offer contains an extension + // parameter not defined for use in an offer. + // + return; // MUST decline + } + } + offer.accept = true; + return; + } + } +} + +// Set permessage-deflate fields for a client offer +// +template +void +pmd_write(Fields& fields, pmd_offer const& offer) +{ + std::string s; + s = "permessage-deflate"; + if(offer.server_max_window_bits != 0) + { + if(offer.server_max_window_bits != -1) + { + s += "; server_max_window_bits="; + s += std::to_string( + offer.server_max_window_bits); + } + else + { + s += "; server_max_window_bits"; + } + } + if(offer.client_max_window_bits != 0) + { + if(offer.client_max_window_bits != -1) + { + s += "; client_max_window_bits="; + s += std::to_string( + offer.client_max_window_bits); + } + else + { + s += "; client_max_window_bits"; + } + } + if(offer.server_no_context_takeover) + { + s += "; server_no_context_takeover"; + } + if(offer.client_no_context_takeover) + { + s += "; client_no_context_takeover"; + } + fields.replace("Sec-WebSocket-Extensions", s); +} + +// Negotiate a permessage-deflate client offer +// +template +void +pmd_negotiate( + Fields& fields, + pmd_offer& config, + pmd_offer const& offer, + permessage_deflate const& o) +{ + if(! (offer.accept && o.server_enable)) + { + config.accept = false; + return; + } + config.accept = true; + + std::string s = "permessage-deflate"; + + config.server_no_context_takeover = + offer.server_no_context_takeover || + o.server_no_context_takeover; + if(config.server_no_context_takeover) + s += "; server_no_context_takeover"; + + config.client_no_context_takeover = + o.client_no_context_takeover || + offer.client_no_context_takeover; + if(config.client_no_context_takeover) + s += "; client_no_context_takeover"; + + if(offer.server_max_window_bits != 0) + config.server_max_window_bits = std::min( + offer.server_max_window_bits, + o.server_max_window_bits); + else + config.server_max_window_bits = + o.server_max_window_bits; + if(config.server_max_window_bits < 15) + { + // ZLib's deflateInit silently treats 8 as + // 9 due to a bug, so prevent 8 from being used. + // + if(config.server_max_window_bits < 9) + config.server_max_window_bits = 9; + + s += "; server_max_window_bits="; + s += std::to_string( + config.server_max_window_bits); + } + + switch(offer.client_max_window_bits) + { + case -1: + // extension parameter is present with no value + config.client_max_window_bits = + o.client_max_window_bits; + if(config.client_max_window_bits < 15) + { + s += "; client_max_window_bits="; + s += std::to_string( + config.client_max_window_bits); + } + break; + + case 0: + /* extension parameter is absent. + + If a received extension negotiation offer doesn't have the + "client_max_window_bits" extension parameter, the corresponding + extension negotiation response to the offer MUST NOT include the + "client_max_window_bits" extension parameter. + */ + if(o.client_max_window_bits == 15) + config.client_max_window_bits = 15; + else + config.accept = false; + break; + + default: + // extension parameter has value in [8..15] + config.client_max_window_bits = std::min( + o.client_max_window_bits, + offer.client_max_window_bits); + s += "; client_max_window_bits="; + s += std::to_string( + config.client_max_window_bits); + break; + } + if(config.accept) + fields.replace("Sec-WebSocket-Extensions", s); +} + +// Normalize the server's response +// +inline +void +pmd_normalize(pmd_offer& offer) +{ + if(offer.accept) + { + if( offer.server_max_window_bits == 0) + offer.server_max_window_bits = 15; + + if( offer.client_max_window_bits == 0 || + offer.client_max_window_bits == -1) + offer.client_max_window_bits = 15; + } +} + +//-------------------------------------------------------------------- + +// Decompress into a DynamicBuffer +// +template +void +inflate( + InflateStream& zi, + DynamicBuffer& dynabuf, + boost::asio::const_buffer const& in, + error_code& ec) +{ + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + zlib::z_params zs; + zs.avail_in = buffer_size(in); + zs.next_in = buffer_cast(in); + for(;;) + { + // VFALCO we could be smarter about the size + auto const bs = dynabuf.prepare( + read_size_helper(dynabuf, 65536)); + auto const out = *bs.begin(); + zs.avail_out = buffer_size(out); + zs.next_out = buffer_cast(out); + zi.write(zs, zlib::Flush::sync, ec); + dynabuf.commit(zs.total_out); + zs.total_out = 0; + if( ec == zlib::error::need_buffers || + ec == zlib::error::end_of_stream) + { + ec = {}; + break; + } + if(ec) + return; + } +} + +// Compress a buffer sequence +// Returns: `true` if more calls are needed +// +template +bool +deflate( + DeflateStream& zo, + boost::asio::mutable_buffer& out, + consuming_buffers& cb, + bool fin, + error_code& ec) +{ + using boost::asio::buffer; + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + BOOST_ASSERT(buffer_size(out) >= 6); + zlib::z_params zs; + zs.avail_in = 0; + zs.next_in = nullptr; + zs.avail_out = buffer_size(out); + zs.next_out = buffer_cast(out); + for(auto const& in : cb) + { + zs.avail_in = buffer_size(in); + if(zs.avail_in == 0) + continue; + zs.next_in = buffer_cast(in); + zo.write(zs, zlib::Flush::none, ec); + if(ec) + { + if(ec != zlib::error::need_buffers) + return false; + BOOST_ASSERT(zs.avail_out == 0); + BOOST_ASSERT(zs.total_out == buffer_size(out)); + ec = {}; + break; + } + if(zs.avail_out == 0) + { + BOOST_ASSERT(zs.total_out == buffer_size(out)); + break; + } + BOOST_ASSERT(zs.avail_in == 0); + } + cb.consume(zs.total_in); + if(zs.avail_out > 0 && fin) + { + auto const remain = buffer_size(cb); + if(remain == 0) + { + // Inspired by Mark Adler + // https://github.com/madler/zlib/issues/149 + // + // VFALCO We could do this flush twice depending + // on how much space is in the output. + zo.write(zs, zlib::Flush::block, ec); + BOOST_ASSERT(! ec || ec == zlib::error::need_buffers); + if(ec == zlib::error::need_buffers) + ec = {}; + if(ec) + return false; + if(zs.avail_out >= 6) + { + zo.write(zs, zlib::Flush::full, ec); + BOOST_ASSERT(! ec); + // remove flush marker + zs.total_out -= 4; + out = buffer( + buffer_cast(out), zs.total_out); + return false; + } + } + } + out = buffer( + buffer_cast(out), zs.total_out); + return true; +} + +} // detail +} // websocket +} // beast + +#endif diff --git a/include/beast/websocket/detail/stream_base.hpp b/include/beast/websocket/detail/stream_base.hpp index 2256d14035..b183fc46c7 100644 --- a/include/beast/websocket/detail/stream_base.hpp +++ b/include/beast/websocket/detail/stream_base.hpp @@ -15,10 +15,13 @@ #include #include #include +#include #include #include #include #include +#include +#include #include #include #include @@ -53,20 +56,13 @@ protected: std::size_t rd_msg_max_ = 16 * 1024 * 1024; // max message size bool wr_autofrag_ = true; // auto fragment - std::size_t wr_buf_size_ = 4096; // mask buffer size + std::size_t wr_buf_size_ = 4096; // write buffer size + std::size_t rd_buf_size_ = 4096; // read buffer size opcode wr_opcode_ = opcode::text; // outgoing message type pong_cb pong_cb_; // pong callback role_type role_; // server or client bool failed_; // the connection failed - detail::frame_header rd_fh_; // current frame header - detail::prepared_key_type rd_key_; // prepared masking key - detail::utf8_checker rd_utf8_check_; // for current text msg - std::uint64_t rd_size_; // size of the current message so far - std::uint64_t rd_need_ = 0; // bytes left in msg frame payload - opcode rd_opcode_; // opcode of current msg - bool rd_cont_; // expecting a continuation frame - bool wr_close_; // sent close frame op* wr_block_; // op currenly writing @@ -75,6 +71,34 @@ protected: invokable wr_op_; // invoked after read completes close_reason cr_; // set from received close frame + // State information for the message being received + // + struct rd_t + { + // opcode of current message being read + opcode op; + + // `true` if the next frame is a continuation. + bool cont; + + // Checks that test messages are valid utf8 + detail::utf8_checker utf8; + + // Size of the current message so far. + std::uint64_t size; + + // Size of the read buffer. + // This gets set to the read buffer size option at the + // beginning of sending a message, so that the option can be + // changed mid-send without affecting the current message. + std::size_t buf_size; + + // The read buffer. Used for compression and masking. + std::unique_ptr buf; + }; + + rd_t rd_; + // State information for the message being sent // struct wr_t @@ -99,36 +123,43 @@ protected: // This gets set to the write buffer size option at the // beginning of sending a message, so that the option can be // changed mid-send without affecting the current message. - std::size_t size; + std::size_t buf_size; - // The write buffer. + // The write buffer. Used for compression and masking. // The buffer is allocated or reallocated at the beginning of // sending a message. std::unique_ptr buf; - - void - open() - { - cont = false; - size = 0; - } - - void - close() - { - buf.reset(); - } }; wr_t wr_; + // State information for the permessage-deflate extension + struct pmd_t + { + // `true` if current read message is compressed + bool rd_set; + + zlib::deflate_stream zo; + zlib::inflate_stream zi; + }; + + // If not engaged, then permessage-deflate is not + // enabled for the currently active session. + std::unique_ptr pmd_; + + // Local options for permessage-deflate + permessage_deflate pmd_opts_; + + // Offer for clients, negotiated result for servers + pmd_offer pmd_config_; + stream_base(stream_base&&) = default; stream_base(stream_base const&) = delete; stream_base& operator=(stream_base&&) = default; stream_base& operator=(stream_base const&) = delete; stream_base() - : d_(new decorator{}) + : d_(detail::default_decorator{}) { } @@ -142,15 +173,24 @@ protected: template std::size_t - read_fh1(DynamicBuffer& db, close_code::value& code); + read_fh1(detail::frame_header& fh, + DynamicBuffer& db, close_code::value& code); template void - read_fh2(DynamicBuffer& db, close_code::value& code); + read_fh2(detail::frame_header& fh, + DynamicBuffer& db, close_code::value& code); + // Called before receiving the first frame of each message template void - wr_prepare(bool compress); + rd_begin(); + + // Called before sending the first frame of each message + // + template + void + wr_begin(); template void @@ -161,7 +201,7 @@ protected: write_ping(DynamicBuffer& db, opcode op, ping_data const& data); }; -template +template void stream_base:: open(role_type role) @@ -169,30 +209,61 @@ open(role_type role) // VFALCO TODO analyze and remove dupe code in reset() role_ = role; failed_ = false; - rd_need_ = 0; - rd_cont_ = false; + rd_.cont = false; wr_close_ = false; wr_block_ = nullptr; // should be nullptr on close anyway pong_data_ = nullptr; // should be nullptr on close anyway - wr_.open(); + wr_.cont = false; + wr_.buf_size = 0; + + if(((role_ == role_type::client && pmd_opts_.client_enable) || + (role_ == role_type::server && pmd_opts_.server_enable)) && + pmd_config_.accept) + { + pmd_normalize(pmd_config_); + pmd_.reset(new pmd_t); + if(role_ == role_type::client) + { + pmd_->zi.reset( + pmd_config_.server_max_window_bits); + pmd_->zo.reset( + pmd_opts_.compLevel, + pmd_config_.client_max_window_bits, + pmd_opts_.memLevel, + zlib::Strategy::normal); + } + else + { + pmd_->zi.reset( + pmd_config_.client_max_window_bits); + pmd_->zo.reset( + pmd_opts_.compLevel, + pmd_config_.server_max_window_bits, + pmd_opts_.memLevel, + zlib::Strategy::normal); + } + } } -template +template void stream_base:: close() { - wr_.close(); + rd_.buf.reset(); + wr_.buf.reset(); + pmd_.reset(); } -// Read fixed frame header +// Read fixed frame header from buffer // Requires at least 2 bytes // template std::size_t stream_base:: -read_fh1(DynamicBuffer& db, close_code::value& code) +read_fh1(detail::frame_header& fh, + DynamicBuffer& db, close_code::value& code) { using boost::asio::buffer; using boost::asio::buffer_copy; @@ -204,48 +275,51 @@ read_fh1(DynamicBuffer& db, close_code::value& code) return 0; }; std::uint8_t b[2]; - assert(buffer_size(db.data()) >= sizeof(b)); + BOOST_ASSERT(buffer_size(db.data()) >= sizeof(b)); db.consume(buffer_copy(buffer(b), db.data())); std::size_t need; - rd_fh_.len = b[1] & 0x7f; - switch(rd_fh_.len) + fh.len = b[1] & 0x7f; + switch(fh.len) { case 126: need = 2; break; case 127: need = 8; break; default: need = 0; } - rd_fh_.mask = (b[1] & 0x80) != 0; - if(rd_fh_.mask) + fh.mask = (b[1] & 0x80) != 0; + if(fh.mask) need += 4; - rd_fh_.op = static_cast(b[0] & 0x0f); - rd_fh_.fin = (b[0] & 0x80) != 0; - rd_fh_.rsv1 = (b[0] & 0x40) != 0; - rd_fh_.rsv2 = (b[0] & 0x20) != 0; - rd_fh_.rsv3 = (b[0] & 0x10) != 0; - switch(rd_fh_.op) + fh.op = static_cast(b[0] & 0x0f); + fh.fin = (b[0] & 0x80) != 0; + fh.rsv1 = (b[0] & 0x40) != 0; + fh.rsv2 = (b[0] & 0x20) != 0; + fh.rsv3 = (b[0] & 0x10) != 0; + switch(fh.op) { case opcode::binary: case opcode::text: - if(rd_cont_) + if(rd_.cont) { // new data frame when continuation expected return err(close_code::protocol_error); } - if(rd_fh_.rsv1 || rd_fh_.rsv2 || rd_fh_.rsv3) + if((fh.rsv1 && ! pmd_) || + fh.rsv2 || fh.rsv3) { // reserved bits not cleared return err(close_code::protocol_error); } + if(pmd_) + pmd_->rd_set = fh.rsv1; break; case opcode::cont: - if(! rd_cont_) + if(! rd_.cont) { // continuation without an active message return err(close_code::protocol_error); } - if(rd_fh_.rsv1 || rd_fh_.rsv2 || rd_fh_.rsv3) + if(fh.rsv1 || fh.rsv2 || fh.rsv3) { // reserved bits not cleared return err(close_code::protocol_error); @@ -253,22 +327,22 @@ read_fh1(DynamicBuffer& db, close_code::value& code) break; default: - if(is_reserved(rd_fh_.op)) + if(is_reserved(fh.op)) { // reserved opcode return err(close_code::protocol_error); } - if(! rd_fh_.fin) + if(! fh.fin) { // fragmented control message return err(close_code::protocol_error); } - if(rd_fh_.len > 125) + if(fh.len > 125) { // invalid length for control message return err(close_code::protocol_error); } - if(rd_fh_.rsv1 || rd_fh_.rsv2 || rd_fh_.rsv3) + if(fh.rsv1 || fh.rsv2 || fh.rsv3) { // reserved bits not cleared return err(close_code::protocol_error); @@ -276,13 +350,13 @@ read_fh1(DynamicBuffer& db, close_code::value& code) break; } // unmasked frame from client - if(role_ == role_type::server && ! rd_fh_.mask) + if(role_ == role_type::server && ! fh.mask) { code = close_code::protocol_error; return 0; } // masked frame from server - if(role_ == role_type::client && rd_fh_.mask) + if(role_ == role_type::client && fh.mask) { code = close_code::protocol_error; return 0; @@ -291,27 +365,28 @@ read_fh1(DynamicBuffer& db, close_code::value& code) return need; } -// Decode variable frame header from stream +// Decode variable frame header from buffer // template void stream_base:: -read_fh2(DynamicBuffer& db, close_code::value& code) +read_fh2(detail::frame_header& fh, + DynamicBuffer& db, close_code::value& code) { using boost::asio::buffer; using boost::asio::buffer_copy; using boost::asio::buffer_size; using namespace boost::endian; - switch(rd_fh_.len) + switch(fh.len) { case 126: { std::uint8_t b[2]; - assert(buffer_size(db.data()) >= sizeof(b)); + BOOST_ASSERT(buffer_size(db.data()) >= sizeof(b)); db.consume(buffer_copy(buffer(b), db.data())); - rd_fh_.len = big_uint16_to_native(&b[0]); + fh.len = big_uint16_to_native(&b[0]); // length not canonical - if(rd_fh_.len < 126) + if(fh.len < 126) { code = close_code::protocol_error; return; @@ -321,11 +396,11 @@ read_fh2(DynamicBuffer& db, close_code::value& code) case 127: { std::uint8_t b[8]; - assert(buffer_size(db.data()) >= sizeof(b)); + BOOST_ASSERT(buffer_size(db.data()) >= sizeof(b)); db.consume(buffer_copy(buffer(b), db.data())); - rd_fh_.len = big_uint64_to_native(&b[0]); + fh.len = big_uint64_to_native(&b[0]); // length not canonical - if(rd_fh_.len < 65536) + if(fh.len < 65536) { code = close_code::protocol_error; return; @@ -333,67 +408,76 @@ read_fh2(DynamicBuffer& db, close_code::value& code) break; } } - if(rd_fh_.mask) + if(fh.mask) { std::uint8_t b[4]; - assert(buffer_size(db.data()) >= sizeof(b)); + BOOST_ASSERT(buffer_size(db.data()) >= sizeof(b)); db.consume(buffer_copy(buffer(b), db.data())); - rd_fh_.key = little_uint32_to_native(&b[0]); + fh.key = little_uint32_to_native(&b[0]); } else { // initialize this otherwise operator== breaks - rd_fh_.key = 0; + fh.key = 0; } - if(rd_fh_.mask) - prepare_key(rd_key_, rd_fh_.key); - if(! is_control(rd_fh_.op)) + if(! is_control(fh.op)) { - if(rd_fh_.op != opcode::cont) + if(fh.op != opcode::cont) { - rd_size_ = rd_fh_.len; - rd_opcode_ = rd_fh_.op; + rd_.size = 0; + rd_.op = fh.op; } else { - if(rd_size_ > (std::numeric_limits< - std::uint64_t>::max)() - rd_fh_.len) + if(rd_.size > (std::numeric_limits< + std::uint64_t>::max)() - fh.len) { code = close_code::too_big; return; } - rd_size_ += rd_fh_.len; } - if(rd_msg_max_ && rd_size_ > rd_msg_max_) - { - code = close_code::too_big; - return; - } - rd_need_ = rd_fh_.len; - rd_cont_ = ! rd_fh_.fin; + rd_.cont = ! fh.fin; } code = close_code::none; } -template +template void stream_base:: -wr_prepare(bool compress) +rd_begin() +{ + // Maintain the read buffer + if(pmd_) + { + if(! rd_.buf || rd_.buf_size != rd_buf_size_) + { + rd_.buf_size = rd_buf_size_; + rd_.buf.reset(new std::uint8_t[rd_.buf_size]); + } + } +} + +template +void +stream_base:: +wr_begin() { wr_.autofrag = wr_autofrag_; - wr_.compress = compress; - if(compress || wr_.autofrag || + wr_.compress = static_cast(pmd_); + + // Maintain the write buffer + if( wr_.compress || role_ == detail::role_type::client) { - if(! wr_.buf || wr_.size != wr_buf_size_) + if(! wr_.buf || wr_.buf_size != wr_buf_size_) { - wr_.size = wr_buf_size_; - wr_.buf.reset(new std::uint8_t[wr_.size]); + wr_.buf_size = wr_buf_size_; + wr_.buf.reset(new std::uint8_t[wr_.buf_size]); } } else { - wr_.size = wr_buf_size_; + wr_.buf_size = wr_buf_size_; wr_.buf.reset(); } } @@ -418,7 +502,7 @@ write_close(DynamicBuffer& db, close_reason const& cr) detail::write(db, fh); if(cr.code != close_code::none) { - detail::prepared_key_type key; + detail::prepared_key key; if(fh.mask) detail::prepare_key(key, fh.key); { @@ -464,7 +548,7 @@ write_ping( detail::write(db, fh); if(data.empty()) return; - detail::prepared_key_type key; + detail::prepared_key key; if(fh.mask) detail::prepare_key(key, fh.key); auto d = db.prepare(data.size()); diff --git a/include/beast/websocket/impl/accept.ipp b/include/beast/websocket/impl/accept.ipp index c33852c87b..7c3683a571 100644 --- a/include/beast/websocket/impl/accept.ipp +++ b/include/beast/websocket/impl/accept.ipp @@ -35,7 +35,7 @@ class stream::response_op { bool cont; stream& ws; - http::response resp; + http::response res; error_code final_ec; int state = 0; @@ -45,12 +45,12 @@ class stream::response_op bool cont_) : cont(cont_) , ws(ws_) - , resp(ws_.build_response(req)) + , res(ws_.build_response(req)) { // can't call stream::reset() here // otherwise accept_op will malfunction // - if(resp.status != 101) + if(res.status != 101) final_ec = error::handshake_failed; } }; @@ -64,9 +64,8 @@ public: template response_op(DeducedHandler&& h, stream& ws, Args&&... args) - : d_(make_handler_ptr( - std::forward(h), ws, - std::forward(args)...)) + : d_(std::forward(h), + ws, std::forward(args)...) { (*this)(error_code{}, false); } @@ -121,7 +120,7 @@ operator()(error_code ec, bool again) // send response d.state = 1; http::async_write(d.ws.next_layer(), - d.resp, std::move(*this)); + d.res, std::move(*this)); return; // sent response @@ -129,7 +128,11 @@ operator()(error_code ec, bool again) d.state = 99; ec = d.final_ec; if(! ec) + { + pmd_read( + d.ws.pmd_config_, d.res.fields); d.ws.open(detail::role_type::server); + } break; } } @@ -176,9 +179,8 @@ public: template accept_op(DeducedHandler&& h, stream& ws, Args&&... args) - : d_(make_handler_ptr( - std::forward(h), ws, - std::forward(args)...)) + : d_(std::forward(h), + ws, std::forward(args)...) { (*this)(error_code{}, 0, false); } @@ -412,6 +414,7 @@ accept(http::request const& req, // teardown if Connection: close. return; } + pmd_read(pmd_config_, req.fields); open(detail::role_type::server); } diff --git a/include/beast/websocket/impl/close.ipp b/include/beast/websocket/impl/close.ipp index 7e929b5889..8af2c926cd 100644 --- a/include/beast/websocket/impl/close.ipp +++ b/include/beast/websocket/impl/close.ipp @@ -56,9 +56,8 @@ public: template close_op(DeducedHandler&& h, stream& ws, Args&&... args) - : d_(make_handler_ptr( - std::forward(h), ws, - std::forward(args)...)) + : d_(std::forward(h), + ws, std::forward(args)...) { (*this)(error_code{}, false); } diff --git a/include/beast/websocket/impl/handshake.ipp b/include/beast/websocket/impl/handshake.ipp index acc9cd2d42..1397b52f7b 100644 --- a/include/beast/websocket/impl/handshake.ipp +++ b/include/beast/websocket/impl/handshake.ipp @@ -59,9 +59,8 @@ public: template handshake_op(DeducedHandler&& h, stream& ws, Args&&... args) - : d_(make_handler_ptr( - std::forward(h), ws, - std::forward(args)...)) + : d_(std::forward(h), + ws, std::forward(args)...) { (*this)(error_code{}, false); } @@ -118,6 +117,8 @@ operator()(error_code ec, bool again) d.state = 1; // VFALCO Do we need the ability to move // a message on the async_write? + pmd_read( + d.ws.pmd_config_, d.req.fields); http::async_write(d.ws.stream_, d.req, std::move(*this)); return; @@ -187,8 +188,12 @@ handshake(boost::string_ref const& host, "SyncStream requirements not met"); reset(); std::string key; - http::write(stream_, - build_request(host, resource, key), ec); + { + auto const req = + build_request(host, resource, key); + pmd_read(pmd_config_, req.fields); + http::write(stream_, req, ec); + } if(ec) return; http::response res; diff --git a/include/beast/websocket/impl/ping.ipp b/include/beast/websocket/impl/ping.ipp index d9e7ba51f1..1fbf3eb8d1 100644 --- a/include/beast/websocket/impl/ping.ipp +++ b/include/beast/websocket/impl/ping.ipp @@ -55,9 +55,8 @@ public: template ping_op(DeducedHandler&& h, stream& ws, Args&&... args) - : d_(make_handler_ptr( - std::forward(h), ws, - std::forward(args)...)) + : d_(std::forward(h), + ws, std::forward(args)...) { (*this)(error_code{}, false); } diff --git a/include/beast/websocket/impl/read.ipp b/include/beast/websocket/impl/read.ipp index d4469d7b3d..b495e30477 100644 --- a/include/beast/websocket/impl/read.ipp +++ b/include/beast/websocket/impl/read.ipp @@ -18,6 +18,7 @@ #include #include #include +#include #include namespace beast { @@ -48,6 +49,9 @@ class stream::read_frame_op frame_info& fi; DynamicBuffer& db; fb_type fb; + std::uint64_t remain; + detail::frame_header fh; + detail::prepared_key key; boost::optional dmb; boost::optional fmb; int state = 0; @@ -72,9 +76,8 @@ public: template read_frame_op(DeducedHandler&& h, stream& ws, Args&&... args) - : d_(make_handler_ptr( - std::forward(h), ws, - std::forward(args)...)) + : d_(std::forward(h), + ws, std::forward(args)...) { (*this)(error_code{}, 0, false); } @@ -142,23 +145,26 @@ template template void stream::read_frame_op:: -operator()(error_code ec,std::size_t bytes_transferred, bool again) +operator()(error_code ec, + std::size_t bytes_transferred, bool again) { using beast::detail::clamp; + using boost::asio::buffer; enum { do_start = 0, do_read_payload = 1, - do_frame_done = 3, - do_read_fh = 4, - do_control_payload = 7, - do_control = 8, - do_pong_resume = 9, - do_pong = 11, - do_close_resume = 13, - do_close = 15, - do_teardown = 16, - do_fail = 18, + do_inflate_payload = 30, + do_frame_done = 4, + do_read_fh = 5, + do_control_payload = 8, + do_control = 9, + do_pong_resume = 10, + do_pong = 12, + do_close_resume = 14, + do_close = 16, + do_teardown = 17, + do_fail = 19, do_call_handler = 99 }; @@ -181,32 +187,51 @@ operator()(error_code ec,std::size_t bytes_transferred, bool again) boost::asio::error::operation_aborted, 0)); return; } - d.state = d.ws.rd_need_ > 0 ? - do_read_payload : do_read_fh; + d.state = do_read_fh; break; //------------------------------------------------------------------ case do_read_payload: - d.state = do_read_payload + 1; - d.dmb = d.db.prepare(clamp(d.ws.rd_need_)); - // receive payload data + if(d.fh.len == 0) + { + d.state = do_frame_done; + break; + } + // Enforce message size limit + if(d.ws.rd_msg_max_ && d.fh.len > + d.ws.rd_msg_max_ - d.ws.rd_.size) + { + code = close_code::too_big; + d.state = do_fail; + break; + } + d.ws.rd_.size += d.fh.len; + d.remain = d.fh.len; + if(d.fh.mask) + detail::prepare_key(d.key, d.fh.key); + // fall through + + case do_read_payload + 1: + d.state = do_read_payload + 2; + d.dmb = d.db.prepare(clamp(d.remain)); + // Read frame payload data d.ws.stream_.async_read_some( *d.dmb, std::move(*this)); return; - case do_read_payload + 1: + case do_read_payload + 2: { - d.ws.rd_need_ -= bytes_transferred; + d.remain -= bytes_transferred; auto const pb = prepare_buffers( bytes_transferred, *d.dmb); - if(d.ws.rd_fh_.mask) - detail::mask_inplace(pb, d.ws.rd_key_); - if(d.ws.rd_opcode_ == opcode::text) + if(d.fh.mask) + detail::mask_inplace(pb, d.key); + if(d.ws.rd_.op == opcode::text) { - if(! d.ws.rd_utf8_check_.write(pb) || - (d.ws.rd_need_ == 0 && d.ws.rd_fh_.fin && - ! d.ws.rd_utf8_check_.finish())) + if(! d.ws.rd_.utf8.write(pb) || + (d.remain == 0 && d.fh.fin && + ! d.ws.rd_.utf8.finish())) { // invalid utf8 code = close_code::bad_payload; @@ -215,21 +240,102 @@ operator()(error_code ec,std::size_t bytes_transferred, bool again) } } d.db.commit(bytes_transferred); - if(d.ws.rd_need_ > 0) + if(d.remain > 0) { - d.state = do_read_payload; + d.state = do_read_payload + 1; break; } + d.state = do_frame_done; + break; + } + + //------------------------------------------------------------------ + + case do_inflate_payload: + d.remain = d.fh.len; + if(d.fh.len == 0) + { + // inflate even if fh.len == 0, otherwise we + // never emit the end-of-stream deflate block. + bytes_transferred = 0; + d.state = do_inflate_payload + 2; + break; + } + if(d.fh.mask) + detail::prepare_key(d.key, d.fh.key); // fall through + + case do_inflate_payload + 1: + { + d.state = do_inflate_payload + 2; + // Read compressed frame payload data + d.ws.stream_.async_read_some( + buffer(d.ws.rd_.buf.get(), clamp( + d.remain, d.ws.rd_.buf_size)), + std::move(*this)); + return; + } + + case do_inflate_payload + 2: + { + d.remain -= bytes_transferred; + auto const in = buffer( + d.ws.rd_.buf.get(), bytes_transferred); + if(d.fh.mask) + detail::mask_inplace(in, d.key); + auto const prev = d.db.size(); + detail::inflate(d.ws.pmd_->zi, d.db, in, ec); + d.ws.failed_ = ec != 0; + if(d.ws.failed_) + break; + if(d.remain == 0 && d.fh.fin) + { + static std::uint8_t constexpr + empty_block[4] = { + 0x00, 0x00, 0xff, 0xff }; + detail::inflate(d.ws.pmd_->zi, d.db, + buffer(&empty_block[0], 4), ec); + d.ws.failed_ = ec != 0; + if(d.ws.failed_) + break; + } + if(d.ws.rd_.op == opcode::text) + { + consuming_buffers cb{d.db.data()}; + cb.consume(prev); + if(! d.ws.rd_.utf8.write(cb) || + (d.remain == 0 && d.fh.fin && + ! d.ws.rd_.utf8.finish())) + { + // invalid utf8 + code = close_code::bad_payload; + d.state = do_fail; + break; + } + } + if(d.remain > 0) + { + d.state = do_inflate_payload + 1; + break; + } + if(d.fh.fin && ( + (d.ws.role_ == detail::role_type::client && + d.ws.pmd_config_.server_no_context_takeover) || + (d.ws.role_ == detail::role_type::server && + d.ws.pmd_config_.client_no_context_takeover))) + d.ws.pmd_->zi.reset(); + d.state = do_frame_done; + break; } //------------------------------------------------------------------ case do_frame_done: // call handler - d.fi.op = d.ws.rd_opcode_; - d.fi.fin = d.ws.rd_fh_.fin && - d.ws.rd_need_ == 0; + d.fi.op = d.ws.rd_.op; + d.fi.fin = d.fh.fin; goto upcall; //------------------------------------------------------------------ @@ -244,7 +350,8 @@ operator()(error_code ec,std::size_t bytes_transferred, bool again) { d.fb.commit(bytes_transferred); code = close_code::none; - auto const n = d.ws.read_fh1(d.fb, code); + auto const n = d.ws.read_fh1( + d.fh, d.fb, code); if(code != close_code::none) { // protocol error @@ -266,21 +373,21 @@ operator()(error_code ec,std::size_t bytes_transferred, bool again) case do_read_fh + 2: d.fb.commit(bytes_transferred); code = close_code::none; - d.ws.read_fh2(d.fb, code); + d.ws.read_fh2(d.fh, d.fb, code); if(code != close_code::none) { // protocol error d.state = do_fail; break; } - if(detail::is_control(d.ws.rd_fh_.op)) + if(detail::is_control(d.fh.op)) { - if(d.ws.rd_fh_.len > 0) + if(d.fh.len > 0) { // read control payload d.state = do_control_payload; d.fmb = d.fb.prepare(static_cast< - std::size_t>(d.ws.rd_fh_.len)); + std::size_t>(d.fh.len)); boost::asio::async_read(d.ws.stream_, *d.fmb, std::move(*this)); return; @@ -288,21 +395,29 @@ operator()(error_code ec,std::size_t bytes_transferred, bool again) d.state = do_control; break; } - if(d.ws.rd_need_ > 0) + if(d.fh.op == opcode::text || + d.fh.op == opcode::binary) + d.ws.rd_begin(); + if(d.fh.len == 0 && ! d.fh.fin) { - d.state = do_read_payload; + // Empty message frame + d.state = do_frame_done; break; } - // empty frame - d.state = do_frame_done; + if(! d.ws.pmd_ || ! d.ws.pmd_->rd_set) + d.state = do_read_payload; + else + d.state = do_inflate_payload; break; //------------------------------------------------------------------ case do_control_payload: - if(d.ws.rd_fh_.mask) - detail::mask_inplace( - *d.fmb, d.ws.rd_key_); + if(d.fh.mask) + { + detail::prepare_key(d.key, d.fh.key); + detail::mask_inplace(*d.fmb, d.key); + } d.fb.commit(bytes_transferred); d.state = do_control; // VFALCO fall through? break; @@ -310,7 +425,7 @@ operator()(error_code ec,std::size_t bytes_transferred, bool again) //------------------------------------------------------------------ case do_control: - if(d.ws.rd_fh_.op == opcode::ping) + if(d.fh.op == opcode::ping) { ping_data data; detail::read(data, d.fb.data()); @@ -335,7 +450,7 @@ operator()(error_code ec,std::size_t bytes_transferred, bool again) d.state = do_pong; break; } - else if(d.ws.rd_fh_.op == opcode::pong) + else if(d.fh.op == opcode::pong) { code = close_code::none; ping_data payload; @@ -346,7 +461,7 @@ operator()(error_code ec,std::size_t bytes_transferred, bool again) d.state = do_read_fh; break; } - BOOST_ASSERT(d.ws.rd_fh_.op == opcode::close); + BOOST_ASSERT(d.fh.op == opcode::close); { detail::read(d.ws.cr_, d.fb.data(), code); if(code != close_code::none) @@ -589,110 +704,218 @@ read_frame(frame_info& fi, DynamicBuffer& dynabuf, error_code& ec) static_assert(beast::is_DynamicBuffer::value, "DynamicBuffer requirements not met"); using beast::detail::clamp; + using boost::asio::buffer; + using boost::asio::buffer_cast; + using boost::asio::buffer_size; close_code::value code{}; for(;;) { - if(rd_need_ == 0) + // Read frame header + detail::frame_header fh; + detail::frame_streambuf fb; { - // read header - detail::frame_streambuf fb; - do_read_fh(fb, code, ec); + fb.commit(boost::asio::read( + stream_, fb.prepare(2), ec)); + failed_ = ec != 0; + if(failed_) + return; + { + auto const n = read_fh1(fh, fb, code); + if(code != close_code::none) + goto do_close; + if(n > 0) + { + fb.commit(boost::asio::read( + stream_, fb.prepare(n), ec)); + failed_ = ec != 0; + if(failed_) + return; + } + } + read_fh2(fh, fb, code); + failed_ = ec != 0; if(failed_) return; if(code != close_code::none) - break; - if(detail::is_control(rd_fh_.op)) + goto do_close; + } + if(detail::is_control(fh.op)) + { + // Read control frame payload + if(fh.len > 0) { - // read control payload - if(rd_fh_.len > 0) + auto const mb = fb.prepare( + static_cast(fh.len)); + fb.commit(boost::asio::read(stream_, mb, ec)); + failed_ = ec != 0; + if(failed_) + return; + if(fh.mask) { - auto const mb = fb.prepare( - static_cast(rd_fh_.len)); - fb.commit(boost::asio::read(stream_, mb, ec)); - failed_ = ec != 0; - if(failed_) - return; - if(rd_fh_.mask) - detail::mask_inplace(mb, rd_key_); - fb.commit(static_cast(rd_fh_.len)); + detail::prepared_key key; + detail::prepare_key(key, fh.key); + detail::mask_inplace(mb, key); } - if(rd_fh_.op == opcode::ping) + fb.commit(static_cast(fh.len)); + } + // Process control frame + if(fh.op == opcode::ping) + { + ping_data data; + detail::read(data, fb.data()); + fb.reset(); + write_ping( + fb, opcode::pong, data); + boost::asio::write(stream_, fb.data(), ec); + failed_ = ec != 0; + if(failed_) + return; + continue; + } + else if(fh.op == opcode::pong) + { + ping_data payload; + detail::read(payload, fb.data()); + if(pong_cb_) + pong_cb_(payload); + continue; + } + BOOST_ASSERT(fh.op == opcode::close); + { + detail::read(cr_, fb.data(), code); + if(code != close_code::none) + goto do_close; + if(! wr_close_) { - ping_data data; - detail::read(data, fb.data()); + auto cr = cr_; + if(cr.code == close_code::none) + cr.code = close_code::normal; + cr.reason = ""; fb.reset(); - write_ping( - fb, opcode::pong, data); + wr_close_ = true; + write_close(fb, cr); boost::asio::write(stream_, fb.data(), ec); failed_ = ec != 0; if(failed_) return; - continue; } - else if(rd_fh_.op == opcode::pong) - { - ping_data payload; - detail::read(payload, fb.data()); - if(pong_cb_) - pong_cb_(payload); - continue; - } - BOOST_ASSERT(rd_fh_.op == opcode::close); - { - detail::read(cr_, fb.data(), code); - if(code != close_code::none) - break; - if(! wr_close_) - { - auto cr = cr_; - if(cr.code == close_code::none) - cr.code = close_code::normal; - cr.reason = ""; - fb.reset(); - wr_close_ = true; - write_close(fb, cr); - boost::asio::write(stream_, fb.data(), ec); - failed_ = ec != 0; - if(failed_) - return; - } - break; - } - } - if(rd_need_ == 0 && ! rd_fh_.fin) - { - // empty frame - continue; + goto do_close; } } - // read payload - auto smb = dynabuf.prepare(clamp(rd_need_)); - auto const bytes_transferred = - stream_.read_some(smb, ec); - failed_ = ec != 0; - if(failed_) - return; - rd_need_ -= bytes_transferred; - auto const pb = prepare_buffers( - bytes_transferred, smb); - if(rd_fh_.mask) - detail::mask_inplace(pb, rd_key_); - if(rd_opcode_ == opcode::text) + if(fh.op != opcode::cont) + rd_begin(); + if(fh.len == 0 && ! fh.fin) { - if(! rd_utf8_check_.write(pb) || - (rd_need_ == 0 && rd_fh_.fin && - ! rd_utf8_check_.finish())) + // empty frame + continue; + } + auto remain = fh.len; + detail::prepared_key key; + if(fh.mask) + detail::prepare_key(key, fh.key); + if(! pmd_ || ! pmd_->rd_set) + { + // Enforce message size limit + if(rd_msg_max_ && fh.len > + rd_msg_max_ - rd_.size) { - code = close_code::bad_payload; - break; + code = close_code::too_big; + goto do_close; + } + rd_.size += fh.len; + // Read message frame payload + while(remain > 0) + { + auto b = + dynabuf.prepare(clamp(remain)); + auto const bytes_transferred = + stream_.read_some(b, ec); + failed_ = ec != 0; + if(failed_) + return; + BOOST_ASSERT(bytes_transferred > 0); + remain -= bytes_transferred; + auto const pb = prepare_buffers( + bytes_transferred, b); + if(fh.mask) + detail::mask_inplace(pb, key); + if(rd_.op == opcode::text) + { + if(! rd_.utf8.write(pb) || + (remain == 0 && fh.fin && + ! rd_.utf8.finish())) + { + code = close_code::bad_payload; + goto do_close; + } + } + dynabuf.commit(bytes_transferred); } } - dynabuf.commit(bytes_transferred); - fi.op = rd_opcode_; - fi.fin = rd_fh_.fin && rd_need_ == 0; + else + { + // Read compressed message frame payload: + // inflate even if fh.len == 0, otherwise we + // never emit the end-of-stream deflate block. + for(;;) + { + auto const bytes_transferred = + stream_.read_some(buffer(rd_.buf.get(), + clamp(remain, rd_.buf_size)), ec); + failed_ = ec != 0; + if(failed_) + return; + remain -= bytes_transferred; + auto const in = buffer( + rd_.buf.get(), bytes_transferred); + if(fh.mask) + detail::mask_inplace(in, key); + auto const prev = dynabuf.size(); + detail::inflate(pmd_->zi, dynabuf, in, ec); + failed_ = ec != 0; + if(failed_) + return; + if(remain == 0 && fh.fin) + { + static std::uint8_t constexpr + empty_block[4] = { + 0x00, 0x00, 0xff, 0xff }; + detail::inflate(pmd_->zi, dynabuf, + buffer(&empty_block[0], 4), ec); + failed_ = ec != 0; + if(failed_) + return; + } + if(rd_.op == opcode::text) + { + consuming_buffers cb{dynabuf.data()}; + cb.consume(prev); + if(! rd_.utf8.write(cb) || ( + remain == 0 && fh.fin && + ! rd_.utf8.finish())) + { + code = close_code::bad_payload; + goto do_close; + } + } + if(remain == 0) + break; + } + if(fh.fin && ( + (role_ == detail::role_type::client && + pmd_config_.server_no_context_takeover) || + (role_ == detail::role_type::server && + pmd_config_.client_no_context_takeover))) + pmd_->zi.reset(); + } + fi.op = rd_.op; + fi.fin = fh.fin; return; } +do_close: if(code != close_code::none) { // Fail the connection (per rfc6455) @@ -759,9 +982,8 @@ public: template read_op(DeducedHandler&& h, stream& ws, Args&&... args) - : d_(make_handler_ptr( - std::forward(h), ws, - std::forward(args)...)) + : d_(std::forward(h), + ws, std::forward(args)...) { (*this)(error_code{}, false); } diff --git a/include/beast/websocket/impl/ssl.ipp b/include/beast/websocket/impl/ssl.ipp index 5b3e5cf243..76f6f4fc9f 100644 --- a/include/beast/websocket/impl/ssl.ipp +++ b/include/beast/websocket/impl/ssl.ipp @@ -59,9 +59,7 @@ public: explicit teardown_ssl_op( DeducedHandler&& h, stream_type& stream) - : d_(make_handler_ptr( - std::forward( - h), stream)) + : d_(std::forward(h), stream) { (*this)(error_code{}, false); } diff --git a/include/beast/websocket/impl/stream.ipp b/include/beast/websocket/impl/stream.ipp index 2e41924986..a8c2ff6159 100644 --- a/include/beast/websocket/impl/stream.ipp +++ b/include/beast/websocket/impl/stream.ipp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -25,6 +26,7 @@ #include #include #include +#include #include namespace beast { @@ -38,6 +40,30 @@ stream(Args&&... args) { } +template +void +stream:: +set_option(permessage_deflate const& o) +{ + if( o.server_max_window_bits > 15 || + o.server_max_window_bits < 9) + throw std::invalid_argument{ + "invalid server_max_window_bits"}; + if( o.client_max_window_bits > 15 || + o.client_max_window_bits < 9) + throw std::invalid_argument{ + "invalid client_max_window_bits"}; + if( o.compLevel < 0 || + o.compLevel > 9) + throw std::invalid_argument{ + "invalid compLevel"}; + if( o.memLevel < 1 || + o.memLevel > 9) + throw std::invalid_argument{ + "invalid memLevel"}; + pmd_opts_ = o; +} + //------------------------------------------------------------------------------ template @@ -46,8 +72,7 @@ stream:: reset() { failed_ = false; - rd_need_ = 0; - rd_cont_ = false; + rd_.cont = false; wr_close_ = false; wr_.cont = false; wr_block_ = nullptr; // should be nullptr on close anyway @@ -72,7 +97,22 @@ build_request(boost::string_ref const& host, key = detail::make_sec_ws_key(maskgen_); req.fields.insert("Sec-WebSocket-Key", key); req.fields.insert("Sec-WebSocket-Version", "13"); - (*d_)(req); + if(pmd_opts_.client_enable) + { + detail::pmd_offer config; + config.accept = true; + config.server_max_window_bits = + pmd_opts_.server_max_window_bits; + config.client_max_window_bits = + pmd_opts_.client_max_window_bits; + config.server_no_context_takeover = + pmd_opts_.server_no_context_takeover; + config.client_no_context_takeover = + pmd_opts_.client_no_context_takeover; + detail::pmd_write( + req.fields, config); + } + d_(req); http::prepare(req, http::connection::upgrade); return req; } @@ -91,7 +131,7 @@ build_response(http::request const& req) res.reason = http::reason_string(res.status); res.version = req.version; res.body = text; - (*d_)(res); + d_(res); prepare(res, (is_keep_alive(req) && keep_alive_) ? http::connection::keep_alive : @@ -122,6 +162,7 @@ build_response(http::request const& req) res.reason = http::reason_string(res.status); res.version = req.version; res.fields.insert("Sec-WebSocket-Version", "13"); + d_(res); prepare(res, (is_keep_alive(req) && keep_alive_) ? http::connection::keep_alive : @@ -130,6 +171,13 @@ build_response(http::request const& req) } } http::response res; + { + detail::pmd_offer offer; + detail::pmd_offer unused; + pmd_read(offer, req.fields); + pmd_negotiate( + res.fields, unused, offer, pmd_opts_); + } res.status = 101; res.reason = http::reason_string(res.status); res.version = req.version; @@ -141,7 +189,7 @@ build_response(http::request const& req) detail::make_sec_ws_accept(key)); } res.fields.replace("Server", "Beast.WSProto"); - (*d_)(res); + d_(res); http::prepare(res, http::connection::upgrade); return res; } @@ -168,32 +216,14 @@ do_response(http::response const& res, if(res.fields["Sec-WebSocket-Accept"] != detail::make_sec_ws_accept(key)) return fail(); + detail::pmd_offer offer; + pmd_read(offer, res.fields); + // VFALCO see if offer satisfies pmd_config_, + // return an error if not. + pmd_config_ = offer; // overwrite for now open(detail::role_type::client); } -template -void -stream:: -do_read_fh(detail::frame_streambuf& fb, - close_code::value& code, error_code& ec) -{ - fb.commit(boost::asio::read( - stream_, fb.prepare(2), ec)); - if(ec) - return; - auto const n = read_fh1(fb, code); - if(code != close_code::none) - return; - if(n > 0) - { - fb.commit(boost::asio::read( - stream_, fb.prepare(n), ec)); - if(ec) - return; - } - read_fh2(fb, code); -} - } // websocket } // beast diff --git a/include/beast/websocket/impl/teardown.ipp b/include/beast/websocket/impl/teardown.ipp index b2ac39918d..f127065246 100644 --- a/include/beast/websocket/impl/teardown.ipp +++ b/include/beast/websocket/impl/teardown.ipp @@ -47,9 +47,7 @@ public: teardown_tcp_op( DeducedHandler&& h, socket_type& socket) - : d_(make_handler_ptr( - std::forward( - h), socket)) + : d_(std::forward(h), socket) { (*this)(error_code{}, 0, false); } diff --git a/include/beast/websocket/impl/write.ipp b/include/beast/websocket/impl/write.ipp index 48032314ee..1f8ea1d061 100644 --- a/include/beast/websocket/impl/write.ipp +++ b/include/beast/websocket/impl/write.ipp @@ -26,74 +26,6 @@ namespace beast { namespace websocket { -/* - template - void - write_frame(bool fin, ConstBufferSequence const& buffer) - - Depending on the settings of autofragment role, and compression, - different algorithms are used. - - 1. autofragment: false - compression: false - - In the server role, this will send a single frame in one - system call, by concatenating the frame header and the payload. - - In the client role, this will send a single frame in one system - call, using the write buffer to calculate masked data. - - 2. autofragment: true - compression: false - - In the server role, this will send one or more frames in one - system call per sent frame. Each frame is sent by concatenating - the frame header and payload. The size of each sent frame will - not exceed the write buffer size option. - - In the client role, this will send one or more frames in one - system call per sent frame, using the write buffer to calculate - masked data. The size of each sent frame will not exceed the - write buffer size option. - - 3. autofragment: false - compression: true - - In the server role, this will... - -*/ -/* - if(compress) - compress buffers into write_buffer - if(write_buffer_avail == write_buffer_size || fin`) - if(mask) - apply mask to write buffer - write frame header, write_buffer as one frame - else if(auto-fragment) - if(fin || write_buffer_avail + buffers size == write_buffer_size) - if(mask) - append buffers to write buffer - apply mask to write buffer - write frame header, write buffer as one frame - - else: - write frame header, write buffer, and buffers as one frame - else: - append buffers to write buffer - else if(mask) - copy buffers to write_buffer - apply mask to write_buffer - write frame header and possibly full write_buffer in a single call - loop: - copy buffers to write_buffer - apply mask to write_buffer - write write_buffer in a single call - else - write frame header, buffers as one frame -*/ - -//------------------------------------------------------------------------------ - template template class stream::write_frame_op @@ -104,53 +36,23 @@ class stream::write_frame_op bool cont; stream& ws; consuming_buffers cb; + bool fin; detail::frame_header fh; detail::fh_streambuf fh_buf; - detail::prepared_key_type key; - void* tmp; - std::size_t tmp_size; + detail::prepared_key key; std::uint64_t remain; int state = 0; + int entry; data(Handler& handler_, stream& ws_, - bool fin, Buffers const& bs) + bool fin_, Buffers const& bs) : handler(handler_) , cont(beast_asio_helpers:: is_continuation(handler)) , ws(ws_) , cb(bs) + , fin(fin_) { - using beast::detail::clamp; - fh.op = ws.wr_.cont ? - opcode::cont : ws.wr_opcode_; - ws.wr_.cont = ! fin; - fh.fin = fin; - fh.rsv1 = false; - fh.rsv2 = false; - fh.rsv3 = false; - fh.len = boost::asio::buffer_size(cb); - fh.mask = ws.role_ == detail::role_type::client; - if(fh.mask) - { - fh.key = ws.maskgen_(); - detail::prepare_key(key, fh.key); - tmp_size = clamp(fh.len, ws.wr_buf_size_); - tmp = beast_asio_helpers:: - allocate(tmp_size, handler); - remain = fh.len; - } - else - { - tmp = nullptr; - } - detail::write(fh_buf, fh); - } - - ~data() - { - if(tmp) - beast_asio_helpers:: - deallocate(tmp, tmp_size, handler); } }; @@ -163,21 +65,27 @@ public: template write_frame_op(DeducedHandler&& h, stream& ws, Args&&... args) - : d_(make_handler_ptr( - std::forward(h), - ws, std::forward(args)...)) + : d_(std::forward(h), + ws, std::forward(args)...) { - (*this)(error_code{}, false); + (*this)(error_code{}, 0, false); } void operator()() { - (*this)(error_code{}); + (*this)(error_code{}, 0, true); } - void operator()(error_code ec, std::size_t); + void operator()(error_code const& ec) + { + (*this)(ec, 0, true); + } - void operator()(error_code ec, bool again = true); + void operator()(error_code ec, + std::size_t bytes_transferred); + + void operator()(error_code ec, + std::size_t bytes_transferred, bool again); friend void* asio_handler_allocate( @@ -215,12 +123,12 @@ template void stream:: write_frame_op:: -operator()(error_code ec, std::size_t) +operator()(error_code ec, std::size_t bytes_transferred) { auto& d = *d_; if(ec) d.ws.failed_ = true; - (*this)(ec); + (*this)(ec, bytes_transferred, true); } template @@ -228,11 +136,24 @@ template void stream:: write_frame_op:: -operator()(error_code ec, bool again) +operator()(error_code ec, + std::size_t bytes_transferred, bool again) { using beast::detail::clamp; + using boost::asio::buffer; using boost::asio::buffer_copy; - using boost::asio::mutable_buffers_1; + using boost::asio::buffer_size; + enum + { + do_init = 0, + do_nomask_nofrag = 20, + do_nomask_frag = 30, + do_mask_nofrag = 40, + do_mask_frag = 50, + do_deflate = 60, + do_maybe_suspend = 80, + do_upcall = 99 + }; auto& d = *d_; d.cont = d.cont || again; if(ec) @@ -241,11 +162,299 @@ operator()(error_code ec, bool again) { switch(d.state) { - case 0: + case do_init: + if(! d.ws.wr_.cont) + { + d.ws.wr_begin(); + d.fh.rsv1 = d.ws.wr_.compress; + } + else + { + d.fh.rsv1 = false; + } + d.fh.rsv2 = false; + d.fh.rsv3 = false; + d.fh.op = d.ws.wr_.cont ? + opcode::cont : d.ws.wr_opcode_; + d.fh.mask = + d.ws.role_ == detail::role_type::client; + + if(d.ws.wr_.compress) + { + d.entry = do_deflate; + } + else if(! d.fh.mask) + { + if(! d.ws.wr_.autofrag) + { + d.entry = do_nomask_nofrag; + } + else + { + BOOST_ASSERT(d.ws.wr_.buf_size != 0); + d.remain = buffer_size(d.cb); + if(d.remain > d.ws.wr_.buf_size) + d.entry = do_nomask_frag; + else + d.entry = do_nomask_nofrag; + } + } + else + { + if(! d.ws.wr_.autofrag) + { + d.entry = do_mask_nofrag; + } + else + { + BOOST_ASSERT(d.ws.wr_.buf_size != 0); + d.remain = buffer_size(d.cb); + if(d.remain > d.ws.wr_.buf_size) + d.entry = do_mask_frag; + else + d.entry = do_mask_nofrag; + } + } + d.state = do_maybe_suspend; + break; + + //---------------------------------------------------------------------- + + case do_nomask_nofrag: + { + d.fh.fin = d.fin; + d.fh.len = buffer_size(d.cb); + detail::write( + d.fh_buf, d.fh); + d.ws.wr_.cont = ! d.fin; + // Send frame + d.state = do_upcall; + BOOST_ASSERT(! d.ws.wr_block_); + d.ws.wr_block_ = &d; + boost::asio::async_write(d.ws.stream_, + buffer_cat(d.fh_buf.data(), d.cb), + std::move(*this)); + return; + } + + //---------------------------------------------------------------------- + + case do_nomask_frag: + { + auto const n = clamp( + d.remain, d.ws.wr_.buf_size); + d.remain -= n; + d.fh.len = n; + d.fh.fin = d.fin ? d.remain == 0 : false; + detail::write( + d.fh_buf, d.fh); + d.ws.wr_.cont = ! d.fin; + // Send frame + d.state = d.remain == 0 ? + do_upcall : do_nomask_frag + 1; + BOOST_ASSERT(! d.ws.wr_block_); + d.ws.wr_block_ = &d; + boost::asio::async_write(d.ws.stream_, + buffer_cat(d.fh_buf.data(), + prepare_buffers(n, d.cb)), + std::move(*this)); + return; + } + + case do_nomask_frag + 1: + d.cb.consume( + bytes_transferred - d.fh_buf.size()); + d.fh_buf.reset(); + d.fh.op = opcode::cont; + if(d.ws.wr_block_ == &d) + d.ws.wr_block_ = nullptr; + if(d.ws.rd_op_.maybe_invoke()) + { + d.state = do_maybe_suspend; + d.ws.get_io_service().post( + std::move(*this)); + return; + } + d.state = d.entry; + break; + + //---------------------------------------------------------------------- + + case do_mask_nofrag: + { + d.remain = buffer_size(d.cb); + d.fh.fin = d.fin; + d.fh.len = d.remain; + d.fh.key = d.ws.maskgen_(); + detail::prepare_key(d.key, d.fh.key); + detail::write( + d.fh_buf, d.fh); + auto const n = + clamp(d.remain, d.ws.wr_.buf_size); + auto const b = + buffer(d.ws.wr_.buf.get(), n); + buffer_copy(b, d.cb); + detail::mask_inplace(b, d.key); + d.remain -= n; + d.ws.wr_.cont = ! d.fin; + // Send frame header and partial payload + d.state = d.remain == 0 ? + do_upcall : do_mask_nofrag + 1; + BOOST_ASSERT(! d.ws.wr_block_); + d.ws.wr_block_ = &d; + boost::asio::async_write(d.ws.stream_, + buffer_cat(d.fh_buf.data(), b), + std::move(*this)); + return; + } + + case do_mask_nofrag + 1: + { + d.cb.consume(d.ws.wr_.buf_size); + auto const n = + clamp(d.remain, d.ws.wr_.buf_size); + auto const b = + buffer(d.ws.wr_.buf.get(), n); + buffer_copy(b, d.cb); + detail::mask_inplace(b, d.key); + d.remain -= n; + // Send parial payload + if(d.remain == 0) + d.state = do_upcall; + boost::asio::async_write( + d.ws.stream_, b, std::move(*this)); + return; + } + + //---------------------------------------------------------------------- + + case do_mask_frag: + { + auto const n = clamp( + d.remain, d.ws.wr_.buf_size); + d.remain -= n; + d.fh.len = n; + d.fh.key = d.ws.maskgen_(); + d.fh.fin = d.fin ? d.remain == 0 : false; + detail::prepare_key(d.key, d.fh.key); + auto const b = buffer( + d.ws.wr_.buf.get(), n); + buffer_copy(b, d.cb); + detail::mask_inplace(b, d.key); + detail::write( + d.fh_buf, d.fh); + d.ws.wr_.cont = ! d.fin; + // Send frame + d.state = d.remain == 0 ? + do_upcall : do_mask_frag + 1; + BOOST_ASSERT(! d.ws.wr_block_); + d.ws.wr_block_ = &d; + boost::asio::async_write(d.ws.stream_, + buffer_cat(d.fh_buf.data(), b), + std::move(*this)); + return; + } + + case do_mask_frag + 1: + d.cb.consume( + bytes_transferred - d.fh_buf.size()); + d.fh_buf.reset(); + d.fh.op = opcode::cont; + BOOST_ASSERT(d.ws.wr_block_ == &d); + d.ws.wr_block_ = nullptr; + if(d.ws.rd_op_.maybe_invoke()) + { + d.state = do_maybe_suspend; + d.ws.get_io_service().post( + std::move(*this)); + return; + } + d.state = d.entry; + break; + + //---------------------------------------------------------------------- + + case do_deflate: + { + auto b = buffer(d.ws.wr_.buf.get(), + d.ws.wr_.buf_size); + auto const more = detail::deflate( + d.ws.pmd_->zo, b, d.cb, d.fin, ec); + d.ws.failed_ = ec != 0; + if(d.ws.failed_) + goto upcall; + auto const n = buffer_size(b); + if(n == 0) + { + // The input was consumed, but there + // is no output due to compression + // latency. + BOOST_ASSERT(! d.fin); + BOOST_ASSERT(buffer_size(d.cb) == 0); + + // We can skip the dispatch if the + // asynchronous initiation function is + // not on call stack but its hard to + // figure out so be safe and dispatch. + d.state = do_upcall; + d.ws.get_io_service().post(std::move(*this)); + return; + } + if(d.fh.mask) + { + d.fh.key = d.ws.maskgen_(); + detail::prepared_key key; + detail::prepare_key(key, d.fh.key); + detail::mask_inplace(b, key); + } + d.fh.fin = ! more; + d.fh.len = n; + detail::fh_streambuf fh_buf; + detail::write(fh_buf, d.fh); + d.ws.wr_.cont = ! d.fin; + // Send frame + d.state = more ? + do_deflate + 1 : do_deflate + 2; + BOOST_ASSERT(! d.ws.wr_block_); + d.ws.wr_block_ = &d; + boost::asio::async_write(d.ws.stream_, + buffer_cat(fh_buf.data(), b), + std::move(*this)); + return; + } + + case do_deflate + 1: + d.fh.op = opcode::cont; + d.fh.rsv1 = false; + BOOST_ASSERT(d.ws.wr_block_ == &d); + d.ws.wr_block_ = nullptr; + if(d.ws.rd_op_.maybe_invoke()) + { + d.state = do_maybe_suspend; + d.ws.get_io_service().post( + std::move(*this)); + return; + } + d.state = d.entry; + break; + + case do_deflate + 2: + if(d.fh.fin && ( + (d.ws.role_ == detail::role_type::client && + d.ws.pmd_config_.client_no_context_takeover) || + (d.ws.role_ == detail::role_type::server && + d.ws.pmd_config_.server_no_context_takeover))) + d.ws.pmd_->zo.reset(); + goto upcall; + + //---------------------------------------------------------------------- + + case do_maybe_suspend: + { if(d.ws.wr_block_) { // suspend - d.state = 3; + d.state = do_maybe_suspend + 1; d.ws.wr_op_.template emplace< write_frame_op>(std::move(*this)); return; @@ -253,79 +462,35 @@ operator()(error_code ec, bool again) if(d.ws.failed_ || d.ws.wr_close_) { // call handler - d.state = 99; + d.state = do_upcall; d.ws.get_io_service().post( bind_handler(std::move(*this), boost::asio::error::operation_aborted)); return; } - // fall through - - case 1: - { - if(! d.fh.mask) - { - // send header and entire payload - d.state = 99; - BOOST_ASSERT(! d.ws.wr_block_); - d.ws.wr_block_ = &d; - boost::asio::async_write(d.ws.stream_, - buffer_cat(d.fh_buf.data(), d.cb), - std::move(*this)); - return; - } - auto const n = clamp(d.remain, d.tmp_size); - mutable_buffers_1 mb{d.tmp, n}; - buffer_copy(mb, d.cb); - d.cb.consume(n); - d.remain -= n; - detail::mask_inplace(mb, d.key); - // send header and payload - d.state = d.remain > 0 ? 2 : 99; - BOOST_ASSERT(! d.ws.wr_block_); - d.ws.wr_block_ = &d; - boost::asio::async_write(d.ws.stream_, - buffer_cat(d.fh_buf.data(), - mb), std::move(*this)); - return; + d.state = d.entry; + break; } - // sent masked payload - case 2: - { - auto const n = clamp(d.remain, d.tmp_size); - mutable_buffers_1 mb{d.tmp, - static_cast(n)}; - buffer_copy(mb, d.cb); - d.cb.consume(n); - d.remain -= n; - detail::mask_inplace(mb, d.key); - // send payload - if(d.remain == 0) - d.state = 99; - BOOST_ASSERT(d.ws.wr_block_ == &d); - boost::asio::async_write( - d.ws.stream_, mb, std::move(*this)); - return; - } - - case 3: - d.state = 4; + case do_maybe_suspend + 1: + d.state = do_maybe_suspend + 2; d.ws.get_io_service().post(bind_handler( std::move(*this), ec)); return; - case 4: + case do_maybe_suspend + 2: if(d.ws.failed_ || d.ws.wr_close_) { // call handler ec = boost::asio::error::operation_aborted; goto upcall; } - d.state = 1; + d.state = d.entry; break; - case 99: + //---------------------------------------------------------------------- + + case do_upcall: goto upcall; } } @@ -391,120 +556,182 @@ write_frame(bool fin, using boost::asio::buffer; using boost::asio::buffer_copy; using boost::asio::buffer_size; - bool const compress = false; - if(! wr_.cont) - wr_prepare(compress); detail::frame_header fh; - fh.op = wr_.cont ? opcode::cont : wr_opcode_; - fh.rsv1 = false; + if(! wr_.cont) + { + wr_begin(); + fh.rsv1 = wr_.compress; + } + else + { + fh.rsv1 = false; + } fh.rsv2 = false; fh.rsv3 = false; + fh.op = wr_.cont ? opcode::cont : wr_opcode_; fh.mask = role_ == detail::role_type::client; - wr_.cont = ! fin; auto remain = buffer_size(buffers); - if(compress) + if(wr_.compress) { - // TODO - } - else if(! fh.mask && ! wr_.autofrag) - { - fh.fin = fin; - fh.len = remain; - detail::fh_streambuf fh_buf; - detail::write(fh_buf, fh); - boost::asio::write(stream_, - buffer_cat(fh_buf.data(), buffers), ec); - failed_ = ec != 0; - if(failed_) - return; - return; - } - else if(! fh.mask && wr_.autofrag) - { - BOOST_ASSERT(wr_.size != 0); consuming_buffers< - ConstBufferSequence> cb(buffers); + ConstBufferSequence> cb{buffers}; for(;;) { - auto const n = clamp(remain, wr_.size); - fh.len = n; - remain -= n; - fh.fin = fin ? remain == 0 : false; - detail::fh_streambuf fh_buf; - detail::write(fh_buf, fh); - boost::asio::write(stream_, - buffer_cat(fh_buf.data(), - prepare_buffers(n, cb)), ec); + auto b = buffer( + wr_.buf.get(), wr_.buf_size); + auto const more = detail::deflate( + pmd_->zo, b, cb, fin, ec); failed_ = ec != 0; if(failed_) return; - if(remain == 0) + auto const n = buffer_size(b); + if(n == 0) + { + // The input was consumed, but there + // is no output due to compression + // latency. + BOOST_ASSERT(! fin); + BOOST_ASSERT(buffer_size(cb) == 0); + fh.fin = false; + break; + } + if(fh.mask) + { + fh.key = maskgen_(); + detail::prepared_key key; + detail::prepare_key(key, fh.key); + detail::mask_inplace(b, key); + } + fh.fin = ! more; + fh.len = n; + detail::fh_streambuf fh_buf; + detail::write(fh_buf, fh); + wr_.cont = ! fin; + boost::asio::write(stream_, + buffer_cat(fh_buf.data(), b), ec); + failed_ = ec != 0; + if(failed_) + return; + if(! more) break; fh.op = opcode::cont; - cb.consume(n); + fh.rsv1 = false; + } + if(fh.fin && ( + (role_ == detail::role_type::client && + pmd_config_.client_no_context_takeover) || + (role_ == detail::role_type::server && + pmd_config_.server_no_context_takeover))) + pmd_->zo.reset(); + return; + } + if(! fh.mask) + { + if(! wr_.autofrag) + { + // no mask, no autofrag + fh.fin = fin; + fh.len = remain; + detail::fh_streambuf fh_buf; + detail::write(fh_buf, fh); + wr_.cont = ! fin; + boost::asio::write(stream_, + buffer_cat(fh_buf.data(), buffers), ec); + failed_ = ec != 0; + if(failed_) + return; + } + else + { + // no mask, autofrag + BOOST_ASSERT(wr_.buf_size != 0); + consuming_buffers< + ConstBufferSequence> cb{buffers}; + for(;;) + { + auto const n = clamp(remain, wr_.buf_size); + remain -= n; + fh.len = n; + fh.fin = fin ? remain == 0 : false; + detail::fh_streambuf fh_buf; + detail::write(fh_buf, fh); + wr_.cont = ! fin; + boost::asio::write(stream_, + buffer_cat(fh_buf.data(), + prepare_buffers(n, cb)), ec); + failed_ = ec != 0; + if(failed_) + return; + if(remain == 0) + break; + fh.op = opcode::cont; + cb.consume(n); + } } return; } - else if(fh.mask && ! wr_.autofrag) + if(! wr_.autofrag) { - fh.key = maskgen_(); - detail::prepared_key_type key; - detail::prepare_key(key, fh.key); + // mask, no autofrag fh.fin = fin; fh.len = remain; + fh.key = maskgen_(); + detail::prepared_key key; + detail::prepare_key(key, fh.key); detail::fh_streambuf fh_buf; detail::write(fh_buf, fh); consuming_buffers< - ConstBufferSequence> cb(buffers); + ConstBufferSequence> cb{buffers}; { - auto const n = clamp(remain, wr_.size); - auto const mb = buffer(wr_.buf.get(), n); - buffer_copy(mb, cb); + auto const n = clamp(remain, wr_.buf_size); + auto const b = buffer(wr_.buf.get(), n); + buffer_copy(b, cb); cb.consume(n); remain -= n; - detail::mask_inplace(mb, key); + detail::mask_inplace(b, key); + wr_.cont = ! fin; boost::asio::write(stream_, - buffer_cat(fh_buf.data(), mb), ec); + buffer_cat(fh_buf.data(), b), ec); failed_ = ec != 0; if(failed_) return; } while(remain > 0) { - auto const n = clamp(remain, wr_.size); - auto const mb = buffer(wr_.buf.get(), n); - buffer_copy(mb, cb); + auto const n = clamp(remain, wr_.buf_size); + auto const b = buffer(wr_.buf.get(), n); + buffer_copy(b, cb); cb.consume(n); remain -= n; - detail::mask_inplace(mb, key); - boost::asio::write(stream_, mb, ec); + detail::mask_inplace(b, key); + boost::asio::write(stream_, b, ec); failed_ = ec != 0; if(failed_) return; } return; } - else if(fh.mask && wr_.autofrag) { - BOOST_ASSERT(wr_.size != 0); + // mask, autofrag + BOOST_ASSERT(wr_.buf_size != 0); consuming_buffers< - ConstBufferSequence> cb(buffers); + ConstBufferSequence> cb{buffers}; for(;;) { fh.key = maskgen_(); - detail::prepared_key_type key; + detail::prepared_key key; detail::prepare_key(key, fh.key); - auto const n = clamp(remain, wr_.size); - auto const mb = buffer(wr_.buf.get(), n); - buffer_copy(mb, cb); - detail::mask_inplace(mb, key); + auto const n = clamp(remain, wr_.buf_size); + auto const b = buffer(wr_.buf.get(), n); + buffer_copy(b, cb); + detail::mask_inplace(b, key); fh.len = n; remain -= n; fh.fin = fin ? remain == 0 : false; detail::fh_streambuf fh_buf; detail::write(fh_buf, fh); boost::asio::write(stream_, - buffer_cat(fh_buf.data(), mb), ec); + buffer_cat(fh_buf.data(), b), ec); failed_ = ec != 0; if(failed_) return; @@ -552,9 +779,8 @@ public: explicit write_op(DeducedHandler&& h, stream& ws, Args&&... args) - : d_(make_handler_ptr( - std::forward(h), ws, - std::forward(args)...)) + : d_(std::forward(h), + ws, std::forward(args)...) { (*this)(error_code{}, false); } @@ -675,8 +901,6 @@ write(ConstBufferSequence const& buffers, error_code& ec) write_frame(true, buffers, ec); } -//------------------------------------------------------------------------------ - } // websocket } // beast diff --git a/include/beast/websocket/option.hpp b/include/beast/websocket/option.hpp index 4f009f9693..e656d06493 100644 --- a/include/beast/websocket/option.hpp +++ b/include/beast/websocket/option.hpp @@ -105,15 +105,7 @@ struct auto_fragment #if GENERATING_DOCS using decorate = implementation_defined; #else -template -inline -detail::decorator_type -decorate(Decorator&& d) -{ - return detail::decorator_type{new - detail::decorator::type>{ - std::forward(d)}}; -} +using decorate = detail::decorator_type; #endif /** Keep-alive option. @@ -200,6 +192,47 @@ using pong_cb = std::function; } // detail +/** permessage-deflate extension options. + + These settings control the permessage-deflate extension, + which allows messages to be compressed. + + @note Objects of this type are used with + @ref beast::websocket::stream::set_option. +*/ +struct permessage_deflate +{ + /// `true` to offer the extension in the server role + bool server_enable = false; + + /// `true` to offer the extension in the client role + bool client_enable = false; + + /** Maximum server window bits to offer + + @note Due to a bug in ZLib, this value must be greater than 8. + */ + int server_max_window_bits = 15; + + /** Maximum client window bits to offer + + @note Due to a bug in ZLib, this value must be greater than 8. + */ + int client_max_window_bits = 15; + + /// `true` if server_no_context_takeover desired + bool server_no_context_takeover = false; + + /// `true` if client_no_context_takeover desired + bool client_no_context_takeover = false; + + /// Deflate compression level 0..9 + int compLevel = 8; + + /// Deflate memory level, 1..9 + int memLevel = 4; +}; + /** Pong callback option. Sets the callback to be invoked whenever a pong is received @@ -250,12 +283,15 @@ struct pong_callback /** Read buffer size option. - Sets the number of bytes allocated to the socket's read buffer. - If this is zero, then reads are not buffered. Setting this - higher can improve performance when expecting to receive - many small frames. + Sets the size of the read buffer used by the implementation to + receive frames. The read buffer is needed when permessage-deflate + is used. - The default is no buffering. + Lowering the size of the buffer can decrease the memory requirements + for each connection, while increasing the size of the buffer can reduce + the number of calls made to the next layer to read data. + + The default setting is 4096. The minimum value is 8. @note Objects of this type are used with @ref beast::websocket::stream::set_option. diff --git a/include/beast/websocket/stream.hpp b/include/beast/websocket/stream.hpp index d5f26fb819..8c1c3cde51 100644 --- a/include/beast/websocket/stream.hpp +++ b/include/beast/websocket/stream.hpp @@ -194,10 +194,10 @@ public: #if GENERATING_DOCS set_option(implementation_defined o) #else - set_option(detail::decorator_type o) + set_option(detail::decorator_type const& o) #endif { - d_ = std::move(o); + d_ = o; } /// Set the keep-alive option @@ -214,6 +214,17 @@ public: wr_opcode_ = o.value; } + /// Set the permessage-deflate extension options + void + set_option(permessage_deflate const& o); + + /// Get the permessage-deflate extension options + void + get_option(permessage_deflate& o) + { + o = pmd_opts_; + } + /// Set the pong callback void set_option(pong_callback o) @@ -225,7 +236,9 @@ public: void set_option(read_buffer_size const& o) { - stream_.capacity(o.value); + rd_buf_size_ = o.value; + // VFALCO What was the thinking here? + //stream_.capacity(o.value); } /// Set the maximum incoming message size allowed @@ -1635,10 +1648,6 @@ private: void do_response(http::response const& resp, boost::string_ref const& key, error_code& ec); - - void - do_read_fh(detail::frame_streambuf& fb, - close_code::value& code, error_code& ec); }; } // websocket diff --git a/include/beast/zlib/detail/deflate_stream.hpp b/include/beast/zlib/detail/deflate_stream.hpp index 45dbdb98b9..c7cceb3ea9 100644 --- a/include/beast/zlib/detail/deflate_stream.hpp +++ b/include/beast/zlib/detail/deflate_stream.hpp @@ -807,7 +807,7 @@ deflate_stream::get_lut() -> //std::uint16_t bl_count[maxBits+1]; // Initialize the mapping length (0..255) -> length code (0..28) - std::uint8_t length = 0; + int length = 0; for(std::uint8_t code = 0; code < lengthCodes-1; ++code) { tables.base_length[code] = length; @@ -815,11 +815,11 @@ deflate_stream::get_lut() -> for(unsigned n = 0; n < run; ++n) tables.length_code[length++] = code; } - BOOST_ASSERT(length == 0); + BOOST_ASSERT(length == 256); // Note that the length 255 (match length 258) can be represented // in two different ways: code 284 + 5 bits or code 285, so we // overwrite length_code[255] to use the best encoding: - tables.length_code[length-1] = lengthCodes-1; + tables.length_code[255] = lengthCodes-1; // Initialize the mapping dist (0..32K) -> dist code (0..29) { diff --git a/test/Jamfile b/test/Jamfile index a023b5fd81..5ffb5d59aa 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -83,10 +83,6 @@ unit-test websocket-tests : websocket/utf8_checker.cpp ; -exe websocket-echo : - websocket/websocket_echo.cpp - ; - unit-test zlib-tests : ../extras/beast/unit_test/main.cpp zlib/zlib-1.2.8/adler32.c diff --git a/test/core/CMakeLists.txt b/test/core/CMakeLists.txt index d96d532456..1ef6f5a7e7 100644 --- a/test/core/CMakeLists.txt +++ b/test/core/CMakeLists.txt @@ -7,7 +7,6 @@ GroupSources(test/core "/") add_executable (core-tests ${BEAST_INCLUDES} ${EXTRAS_INCLUDES} - ${ZLIB_SOURCES} ../../extras/beast/unit_test/main.cpp buffer_test.hpp async_completion.cpp diff --git a/test/core/dynabuf_readstream.cpp b/test/core/dynabuf_readstream.cpp index a2ff8bc56f..59e4049832 100644 --- a/test/core/dynabuf_readstream.cpp +++ b/test/core/dynabuf_readstream.cpp @@ -132,8 +132,7 @@ public: { testSpecialMembers(); - yield_to(std::bind(&self::testRead, - this, std::placeholders::_1)); + yield_to(&self::testRead, this); } }; diff --git a/test/http/read.cpp b/test/http/read.cpp index 8775dc2f87..bce7a86eda 100644 --- a/test/http/read.cpp +++ b/test/http/read.cpp @@ -378,17 +378,10 @@ public: { testThrow(); - yield_to(std::bind(&read_test::testFailures, - this, std::placeholders::_1)); - - yield_to(std::bind(&read_test::testReadHeaders, - this, std::placeholders::_1)); - - yield_to(std::bind(&read_test::testRead, - this, std::placeholders::_1)); - - yield_to(std::bind(&read_test::testEof, - this, std::placeholders::_1)); + yield_to(&read_test::testFailures, this); + yield_to(&read_test::testReadHeaders, this); + yield_to(&read_test::testRead, this); + yield_to(&read_test::testEof, this); } }; diff --git a/test/http/write.cpp b/test/http/write.cpp index 514086d3d9..09a087b360 100644 --- a/test/http/write.cpp +++ b/test/http/write.cpp @@ -702,12 +702,9 @@ public: void run() override { - yield_to(std::bind(&write_test::testAsyncWriteHeaders, - this, std::placeholders::_1)); - yield_to(std::bind(&write_test::testAsyncWrite, - this, std::placeholders::_1)); - yield_to(std::bind(&write_test::testFailures, - this, std::placeholders::_1)); + yield_to(&write_test::testAsyncWriteHeaders, this); + yield_to(&write_test::testAsyncWrite, this); + yield_to(&write_test::testFailures, this); testOutput(); test_std_ostream(); testOstream(); diff --git a/test/websocket/CMakeLists.txt b/test/websocket/CMakeLists.txt index 9de18db9ff..9a3260c7c3 100644 --- a/test/websocket/CMakeLists.txt +++ b/test/websocket/CMakeLists.txt @@ -27,15 +27,3 @@ endif() if (MINGW) set_target_properties(websocket-tests PROPERTIES COMPILE_FLAGS "-Wa,-mbig-obj -Og") endif() - -add_executable (websocket-echo - ${BEAST_INCLUDES} - ${EXTRAS_INCLUDES} - websocket_async_echo_server.hpp - websocket_sync_echo_server.hpp - websocket_echo.cpp -) - -if (NOT WIN32) - target_link_libraries(websocket-echo ${Boost_LIBRARIES} Threads::Threads) -endif() diff --git a/test/websocket/frame.cpp b/test/websocket/frame.cpp index 522c72f8b6..eb690f65a6 100644 --- a/test/websocket/frame.cpp +++ b/test/websocket/frame.cpp @@ -80,17 +80,19 @@ public: close_code::value code; stream_base stream; stream.open(role); - auto const n = stream.read_fh1(sb, code); + detail::frame_header fh1; + auto const n = + stream.read_fh1(fh1, sb, code); if(! BEAST_EXPECT(! code)) return; if(! BEAST_EXPECT(sb.size() == n)) return; - stream.read_fh2(sb, code); + stream.read_fh2(fh1, sb, code); if(! BEAST_EXPECT(! code)) return; if(! BEAST_EXPECT(sb.size() == 0)) return; - BEAST_EXPECT(stream.rd_fh_ == fh); + BEAST_EXPECT(fh1 == fh); }; test_fh fh; @@ -130,7 +132,9 @@ public: close_code::value code; stream_base stream; stream.open(role); - auto const n = stream.read_fh1(sb, code); + detail::frame_header fh1; + auto const n = + stream.read_fh1(fh1, sb, code); if(code) { pass(); @@ -138,7 +142,7 @@ public: } if(! BEAST_EXPECT(sb.size() == n)) return; - stream.read_fh2(sb, code); + stream.read_fh2(fh1, sb, code); if(! BEAST_EXPECT(code)) return; if(! BEAST_EXPECT(sb.size() == 0)) @@ -194,7 +198,9 @@ public: stream_base stream; stream.open(role); close_code::value code; - auto const n = stream.read_fh1(sb, code); + detail::frame_header fh; + auto const n = + stream.read_fh1(fh, sb, code); if(code) { pass(); @@ -202,7 +208,7 @@ public: } if(! BEAST_EXPECT(sb.size() == n)) return; - stream.read_fh2(sb, code); + stream.read_fh2(fh, sb, code); if(! BEAST_EXPECT(code)) return; if(! BEAST_EXPECT(sb.size() == 0)) diff --git a/test/websocket/mask.cpp b/test/websocket/mask.cpp index 6f94ca1427..34bcb2d46a 100644 --- a/test/websocket/mask.cpp +++ b/test/websocket/mask.cpp @@ -28,6 +28,11 @@ public: { } + void + seed(result_type const&) + { + } + std::uint32_t operator()() { diff --git a/test/websocket/stream.cpp b/test/websocket/stream.cpp index 53a16bdd9e..632567666a 100644 --- a/test/websocket/stream.cpp +++ b/test/websocket/stream.cpp @@ -109,28 +109,6 @@ public: return false; } - template - static - void - read(stream& ws, opcode& op, DynamicBuffer& db) - { - frame_info fi; - for(;;) - { - ws.read_frame(fi, db); - op = fi.op; - if(fi.fin) - break; - } - } - - typedef void(self::*pmf_t)(endpoint_type const&, yield_context); - - void yield_to_mf(endpoint_type const& ep, pmf_t mf) - { - yield_to(std::bind(mf, this, ep, std::placeholders::_1)); - } - struct identity { template @@ -797,548 +775,8 @@ public: } #endif - void testSyncClient(endpoint_type const& ep) - { - using boost::asio::buffer; - static std::size_t constexpr limit = 200; - std::size_t n; - for(n = 0; n < limit; ++n) - { - stream> ws(n, ios_); - auto const restart = - [&](error_code ev) - { - try - { - opcode op; - streambuf db; - ws.read(op, db); - fail(); - return false; - } - catch(system_error const& se) - { - if(se.code() != ev) - throw; - } - error_code ec; - ws.lowest_layer().connect(ep, ec); - if(! BEAST_EXPECTS(! ec, ec.message())) - return false; - ws.handshake("localhost", "/"); - return true; - }; - try - { - { - // connect - error_code ec; - ws.lowest_layer().connect(ep, ec); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - } - ws.handshake("localhost", "/"); - - // send message - ws.set_option(auto_fragment{false}); - ws.set_option(message_type(opcode::text)); - ws.write(sbuf("Hello")); - { - // receive echoed message - opcode op; - streambuf db; - read(ws, op, db); - BEAST_EXPECT(op == opcode::text); - BEAST_EXPECT(to_string(db.data()) == "Hello"); - } - - // close, no payload - ws.close({}); - if(! restart(error::closed)) - return; - - // close with code - ws.close(close_code::going_away); - if(! restart(error::closed)) - return; - - // close with code and reason string - ws.close({close_code::going_away, "Going away"}); - if(! restart(error::closed)) - return; - - // send ping and message - bool pong = false; - ws.set_option(pong_callback{ - [&](ping_data const& payload) - { - BEAST_EXPECT(! pong); - pong = true; - BEAST_EXPECT(payload == ""); - }}); - ws.ping(""); - ws.set_option(message_type(opcode::binary)); - ws.write(sbuf("Hello")); - { - // receive echoed message - opcode op; - streambuf db; - ws.read(op, db); - BEAST_EXPECT(pong == 1); - BEAST_EXPECT(op == opcode::binary); - BEAST_EXPECT(to_string(db.data()) == "Hello"); - } - ws.set_option(pong_callback{}); - - // send ping and fragmented message - ws.set_option(pong_callback{ - [&](ping_data const& payload) - { - BEAST_EXPECT(payload == "payload"); - }}); - ws.ping("payload"); - ws.write_frame(false, sbuf("Hello, ")); - ws.write_frame(false, sbuf("")); - ws.write_frame(true, sbuf("World!")); - { - // receive echoed message - opcode op; - streambuf db; - ws.read(op, db); - BEAST_EXPECT(pong == 1); - BEAST_EXPECT(to_string(db.data()) == "Hello, World!"); - } - ws.set_option(pong_callback{}); - - // send pong - ws.pong(""); - - // send auto fragmented message - ws.set_option(auto_fragment{true}); - ws.set_option(write_buffer_size{8}); - ws.write(sbuf("Now is the time for all good men")); - { - // receive echoed message - opcode op; - streambuf sb; - ws.read(op, sb); - BEAST_EXPECT(to_string(sb.data()) == "Now is the time for all good men"); - } - ws.set_option(auto_fragment{false}); - ws.set_option(write_buffer_size{4096}); - - // send message with write buffer limit - { - std::string s(2000, '*'); - ws.set_option(write_buffer_size(1200)); - ws.write(buffer(s.data(), s.size())); - { - // receive echoed message - opcode op; - streambuf db; - ws.read(op, db); - BEAST_EXPECT(to_string(db.data()) == s); - } - } - - // cause ping - ws.set_option(message_type(opcode::binary)); - ws.write(sbuf("PING")); - ws.set_option(message_type(opcode::text)); - ws.write(sbuf("Hello")); - { - // receive echoed message - opcode op; - streambuf db; - ws.read(op, db); - BEAST_EXPECT(op == opcode::text); - BEAST_EXPECT(to_string(db.data()) == "Hello"); - } - - // cause close - ws.set_option(message_type(opcode::binary)); - ws.write(sbuf("CLOSE")); - if(! restart(error::closed)) - return; - - // send bad utf8 - ws.set_option(message_type(opcode::binary)); - ws.write(buffer_cat(sbuf("TEXT"), - cbuf(0x03, 0xea, 0xf0, 0x28, 0x8c, 0xbc))); - if(! restart(error::failed)) - return; - - // cause bad utf8 - ws.set_option(message_type(opcode::binary)); - ws.write(buffer_cat(sbuf("TEXT"), - cbuf(0x03, 0xea, 0xf0, 0x28, 0x8c, 0xbc))); - ws.write(sbuf("Hello")); - if(! restart(error::failed)) - return; - - // cause bad close - ws.set_option(message_type(opcode::binary)); - ws.write(buffer_cat(sbuf("RAW"), - cbuf(0x88, 0x02, 0x03, 0xed))); - if(! restart(error::failed)) - return; - - // unexpected cont - boost::asio::write(ws.next_layer(), - cbuf(0x80, 0x80, 0xff, 0xff, 0xff, 0xff)); - if(! restart(error::closed)) - return; - - // expected cont - ws.write_frame(false, boost::asio::null_buffers{}); - boost::asio::write(ws.next_layer(), - cbuf(0x81, 0x80, 0xff, 0xff, 0xff, 0xff)); - if(! restart(error::closed)) - return; - - // message size above 2^64 - ws.write_frame(false, cbuf(0x00)); - boost::asio::write(ws.next_layer(), - cbuf(0x80, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff)); - if(! restart(error::closed)) - return; - - // message size exceeds max - ws.set_option(read_message_max{1}); - ws.write(cbuf(0x00, 0x00)); - if(! restart(error::failed)) - return; - ws.set_option(read_message_max{16*1024*1024}); - - // invalid fixed frame header - boost::asio::write(ws.next_layer(), - cbuf(0x8f, 0x80, 0xff, 0xff, 0xff, 0xff)); - if(! restart(error::closed)) - return; - - // cause non-canonical extended size - ws.write(buffer_cat(sbuf("RAW"), - cbuf(0x82, 0x7e, 0x00, 0x01, 0x00))); - if(! restart(error::failed)) - return; - } - catch(system_error const&) - { - continue; - } - break; - } - BEAST_EXPECT(n < limit); - } - - void testAsyncClient( - endpoint_type const& ep, yield_context do_yield) - { - using boost::asio::buffer; - static std::size_t constexpr limit = 200; - std::size_t n; - for(n = 0; n < limit; ++n) - { - stream> ws(n, ios_); - auto const restart = - [&](error_code ev) - { - opcode op; - streambuf db; - error_code ec; - ws.async_read(op, db, do_yield[ec]); - if(! ec) - { - fail(); - return false; - } - if(ec != ev) - { - auto const s = ec.message(); - throw system_error{ec}; - } - ec = {}; - ws.lowest_layer().close(ec); - ec = {}; - ws.lowest_layer().connect(ep, ec); - if(! BEAST_EXPECTS(! ec, ec.message())) - return false; - ws.async_handshake("localhost", "/", do_yield[ec]); - if(ec) - throw system_error{ec}; - return true; - }; - try - { - error_code ec; - - // connect - ws.lowest_layer().connect(ep, ec); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - ws.async_handshake("localhost", "/", do_yield[ec]); - if(ec) - throw system_error{ec}; - - // send message - ws.set_option(auto_fragment{false}); - ws.set_option(message_type(opcode::text)); - ws.async_write(sbuf("Hello"), do_yield[ec]); - if(ec) - throw system_error{ec}; - { - // receive echoed message - opcode op; - streambuf db; - ws.async_read(op, db, do_yield[ec]); - if(ec) - throw system_error{ec}; - BEAST_EXPECT(op == opcode::text); - BEAST_EXPECT(to_string(db.data()) == "Hello"); - } - - // close, no payload - ws.async_close({}, do_yield[ec]); - if(ec) - throw system_error{ec}; - if(! restart(error::closed)) - return; - - // close with code - ws.async_close(close_code::going_away, do_yield[ec]); - if(ec) - throw system_error{ec}; - if(! restart(error::closed)) - return; - - // close with code and reason string - ws.async_close({close_code::going_away, "Going away"}, do_yield[ec]); - if(ec) - throw system_error{ec}; - if(! restart(error::closed)) - return; - - // send ping and message - bool pong = false; - { - ws.set_option(pong_callback{ - [&](ping_data const& payload) - { - BEAST_EXPECT(! pong); - pong = true; - BEAST_EXPECT(payload == ""); - }}); - ws.async_ping("", do_yield[ec]); - if(ec) - throw system_error{ec}; - ws.set_option(message_type(opcode::binary)); - ws.async_write(sbuf("Hello"), do_yield[ec]); - if(ec) - throw system_error{ec}; - // receive echoed message - opcode op; - streambuf db; - ws.async_read(op, db, do_yield[ec]); - if(ec) - throw system_error{ec}; - BEAST_EXPECT(op == opcode::binary); - BEAST_EXPECT(to_string(db.data()) == "Hello"); - ws.set_option(pong_callback{}); - } - - // send ping and fragmented message - { - ws.set_option(pong_callback{ - [&](ping_data const& payload) - { - BEAST_EXPECT(payload == "payload"); - }}); - ws.async_ping("payload", do_yield[ec]); - if(! ec) - ws.async_write_frame(false, sbuf("Hello, "), do_yield[ec]); - if(! ec) - ws.async_write_frame(false, sbuf(""), do_yield[ec]); - if(! ec) - ws.async_write_frame(true, sbuf("World!"), do_yield[ec]); - if(ec) - throw system_error{ec}; - { - // receive echoed message - opcode op; - streambuf db; - ws.async_read(op, db, do_yield[ec]); - if(ec) - throw system_error{ec}; - BEAST_EXPECT(to_string(db.data()) == "Hello, World!"); - } - ws.set_option(pong_callback{}); - } - - // send pong - ws.async_pong("", do_yield[ec]); - - // send auto fragmented message - ws.set_option(auto_fragment{true}); - ws.set_option(write_buffer_size{8}); - ws.async_write(sbuf("Now is the time for all good men"), do_yield[ec]); - { - // receive echoed message - opcode op; - streambuf db; - ws.async_read(op, db, do_yield[ec]); - if(ec) - throw system_error{ec}; - BEAST_EXPECT(to_string(db.data()) == "Now is the time for all good men"); - } - ws.set_option(auto_fragment{false}); - ws.set_option(write_buffer_size{4096}); - - // send message with mask buffer limit - { - std::string s(2000, '*'); - ws.set_option(write_buffer_size(1200)); - ws.async_write(buffer(s.data(), s.size()), do_yield[ec]); - if(ec) - throw system_error{ec}; - { - // receive echoed message - opcode op; - streambuf db; - ws.async_read(op, db, do_yield[ec]); - if(ec) - throw system_error{ec}; - BEAST_EXPECT(to_string(db.data()) == s); - } - } - - // cause ping - ws.set_option(message_type(opcode::binary)); - ws.async_write(sbuf("PING"), do_yield[ec]); - if(ec) - throw system_error{ec}; - ws.set_option(message_type(opcode::text)); - ws.async_write(sbuf("Hello"), do_yield[ec]); - if(ec) - throw system_error{ec}; - { - // receive echoed message - opcode op; - streambuf db; - ws.async_read(op, db, do_yield[ec]); - if(ec) - throw system_error{ec}; - BEAST_EXPECT(op == opcode::text); - BEAST_EXPECT(to_string(db.data()) == "Hello"); - } - - // cause close - ws.set_option(message_type(opcode::binary)); - ws.async_write(sbuf("CLOSE"), do_yield[ec]); - if(ec) - throw system_error{ec}; - if(! restart(error::closed)) - return; - - // send bad utf8 - ws.set_option(message_type(opcode::binary)); - ws.async_write(buffer_cat(sbuf("TEXT"), - cbuf(0x03, 0xea, 0xf0, 0x28, 0x8c, 0xbc)), do_yield[ec]); - if(ec) - throw system_error{ec}; - if(! restart(error::failed)) - return; - - // cause bad utf8 - ws.set_option(message_type(opcode::binary)); - ws.async_write(buffer_cat(sbuf("TEXT"), - cbuf(0x03, 0xea, 0xf0, 0x28, 0x8c, 0xbc)), do_yield[ec]); - if(ec) - throw system_error{ec}; - ws.async_write(sbuf("Hello"), do_yield[ec]); - if(! restart(error::failed)) - return; - - // cause bad close - ws.set_option(message_type(opcode::binary)); - ws.async_write(buffer_cat(sbuf("RAW"), - cbuf(0x88, 0x02, 0x03, 0xed)), do_yield[ec]); - if(ec) - throw system_error{ec}; - if(! restart(error::failed)) - return; - - // unexpected cont - boost::asio::async_write(ws.next_layer(), - cbuf(0x80, 0x80, 0xff, 0xff, 0xff, 0xff), - do_yield[ec]); - if(ec) - throw system_error{ec}; - if(! restart(error::closed)) - return; - - // expected cont - ws.async_write_frame(false, - boost::asio::null_buffers{}, do_yield[ec]); - if(ec) - throw system_error{ec}; - boost::asio::async_write(ws.next_layer(), - cbuf(0x81, 0x80, 0xff, 0xff, 0xff, 0xff), - do_yield[ec]); - if(ec) - throw system_error{ec}; - if(! restart(error::closed)) - return; - - // message size above 2^64 - ws.async_write_frame(false, cbuf(0x00), do_yield[ec]); - if(ec) - throw system_error{ec}; - boost::asio::async_write(ws.next_layer(), - cbuf(0x80, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff), - do_yield[ec]); - if(ec) - throw system_error{ec}; - if(! restart(error::closed)) - return; - - // message size exceeds max - ws.set_option(read_message_max{1}); - ws.async_write(cbuf(0x00, 0x00), do_yield[ec]); - if(ec) - throw system_error{ec}; - if(! restart(error::failed)) - return; - - // invalid fixed frame header - boost::asio::async_write(ws.next_layer(), - cbuf(0x8f, 0x80, 0xff, 0xff, 0xff, 0xff), - do_yield[ec]); - if(ec) - throw system_error{ec}; - if(! restart(error::closed)) - return; - - // cause non-canonical extended size - ws.async_write(buffer_cat(sbuf("RAW"), - cbuf(0x82, 0x7e, 0x00, 0x01, 0x00)), - do_yield[ec]); - if(ec) - throw system_error{ec}; - if(! restart(error::failed)) - return; - } - catch(system_error const&) - { - continue; - } - break; - } - BEAST_EXPECT(n < limit); - } - - void testAsyncWriteFrame(endpoint_type const& ep) + void + testAsyncWriteFrame(endpoint_type const& ep) { for(;;) { @@ -1369,6 +807,417 @@ public: } } + struct SyncClient + { + template + void + handshake(stream& ws, + boost::string_ref const& uri, + boost::string_ref const& path) const + { + ws.handshake(uri, path); + } + + template + void + ping(stream& ws, + ping_data const& payload) const + { + ws.ping(payload); + } + + template + void + pong(stream& ws, + ping_data const& payload) const + { + ws.pong(payload); + } + + template + void + close(stream& ws, + close_reason const& cr) const + { + ws.close(cr); + } + + template< + class NextLayer, class DynamicBuffer> + void + read(stream& ws, + opcode& op, DynamicBuffer& dynabuf) const + { + ws.read(op, dynabuf); + } + + template< + class NextLayer, class ConstBufferSequence> + void + write(stream& ws, + ConstBufferSequence const& buffers) const + { + ws.write(buffers); + } + + template< + class NextLayer, class ConstBufferSequence> + void + write_frame(stream& ws, bool fin, + ConstBufferSequence const& buffers) const + { + ws.write_frame(fin, buffers); + } + + template< + class NextLayer, class ConstBufferSequence> + void + write_raw(stream& ws, + ConstBufferSequence const& buffers) const + { + boost::asio::write( + ws.next_layer(), buffers); + } + }; + + class AsyncClient + { + yield_context& yield_; + + public: + explicit + AsyncClient(yield_context& yield) + : yield_(yield) + { + } + + template + void + handshake(stream& ws, + boost::string_ref const& uri, + boost::string_ref const& path) const + { + error_code ec; + ws.async_handshake(uri, path, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + ping(stream& ws, + ping_data const& payload) const + { + error_code ec; + ws.async_ping(payload, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + pong(stream& ws, + ping_data const& payload) const + { + error_code ec; + ws.async_pong(payload, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + close(stream& ws, + close_reason const& cr) const + { + error_code ec; + ws.async_close(cr, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template< + class NextLayer, class DynamicBuffer> + void + read(stream& ws, + opcode& op, DynamicBuffer& dynabuf) const + { + error_code ec; + ws.async_read(op, dynabuf, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template< + class NextLayer, class ConstBufferSequence> + void + write(stream& ws, + ConstBufferSequence const& buffers) const + { + error_code ec; + ws.async_write(buffers, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template< + class NextLayer, class ConstBufferSequence> + void + write_frame(stream& ws, bool fin, + ConstBufferSequence const& buffers) const + { + error_code ec; + ws.async_write_frame(fin, buffers, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template< + class NextLayer, class ConstBufferSequence> + void + write_raw(stream& ws, + ConstBufferSequence const& buffers) const + { + error_code ec; + boost::asio::async_write( + ws.next_layer(), buffers, yield_[ec]); + if(ec) + throw system_error{ec}; + } + }; + + struct abort_test + { + }; + + template + void + testEndpoint(Client const& c, + endpoint_type const& ep, permessage_deflate const& pmd) + { + using boost::asio::buffer; + static std::size_t constexpr limit = 200; + std::size_t n; + for(n = 0; n <= limit; ++n) + { + stream> ws{n, ios_}; + ws.set_option(pmd); + auto const restart = + [&](error_code ev) + { + try + { + opcode op; + streambuf db; + c.read(ws, op, db); + fail(); + throw abort_test{}; + } + catch(system_error const& se) + { + if(se.code() != ev) + throw; + } + error_code ec; + ws.lowest_layer().connect(ep, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + throw abort_test{}; + c.handshake(ws, "localhost", "/"); + }; + try + { + { + // connect + error_code ec; + ws.lowest_layer().connect(ep, ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + } + c.handshake(ws, "localhost", "/"); + + // send message + ws.set_option(auto_fragment{false}); + ws.set_option(message_type(opcode::text)); + c.write(ws, sbuf("Hello")); + { + // receive echoed message + opcode op; + streambuf db; + c.read(ws, op, db); + BEAST_EXPECT(op == opcode::text); + BEAST_EXPECT(to_string(db.data()) == "Hello"); + } + + // close, no payload + c.close(ws, {}); + restart(error::closed); + + // close with code + c.close(ws, close_code::going_away); + restart(error::closed); + + // close with code and reason string + c.close(ws, {close_code::going_away, "Going away"}); + restart(error::closed); + + // send ping and message + bool pong = false; + ws.set_option(pong_callback{ + [&](ping_data const& payload) + { + BEAST_EXPECT(! pong); + pong = true; + BEAST_EXPECT(payload == ""); + }}); + c.ping(ws, ""); + ws.set_option(message_type(opcode::binary)); + c.write(ws, sbuf("Hello")); + { + // receive echoed message + opcode op; + streambuf db; + c.read(ws, op, db); + BEAST_EXPECT(pong == 1); + BEAST_EXPECT(op == opcode::binary); + BEAST_EXPECT(to_string(db.data()) == "Hello"); + } + ws.set_option(pong_callback{}); + + // send ping and fragmented message + ws.set_option(pong_callback{ + [&](ping_data const& payload) + { + BEAST_EXPECT(payload == "payload"); + }}); + ws.ping("payload"); + c.write_frame(ws, false, sbuf("Hello, ")); + c.write_frame(ws, false, sbuf("")); + c.write_frame(ws, true, sbuf("World!")); + { + // receive echoed message + opcode op; + streambuf db; + c.read(ws, op, db); + BEAST_EXPECT(pong == 1); + BEAST_EXPECT(to_string(db.data()) == "Hello, World!"); + } + ws.set_option(pong_callback{}); + + // send pong + c.pong(ws, ""); + + // send auto fragmented message + ws.set_option(auto_fragment{true}); + ws.set_option(write_buffer_size{8}); + c.write(ws, sbuf("Now is the time for all good men")); + { + // receive echoed message + opcode op; + streambuf sb; + c.read(ws, op, sb); + BEAST_EXPECT(to_string(sb.data()) == "Now is the time for all good men"); + } + ws.set_option(auto_fragment{false}); + ws.set_option(write_buffer_size{4096}); + + // send message with write buffer limit + { + std::string s(2000, '*'); + ws.set_option(write_buffer_size(1200)); + c.write(ws, buffer(s.data(), s.size())); + { + // receive echoed message + opcode op; + streambuf db; + c.read(ws, op, db); + BEAST_EXPECT(to_string(db.data()) == s); + } + } + + // cause ping + ws.set_option(message_type(opcode::binary)); + c.write(ws, sbuf("PING")); + ws.set_option(message_type(opcode::text)); + c.write(ws, sbuf("Hello")); + { + // receive echoed message + opcode op; + streambuf db; + c.read(ws, op, db); + BEAST_EXPECT(op == opcode::text); + BEAST_EXPECT(to_string(db.data()) == "Hello"); + } + + // cause close + ws.set_option(message_type(opcode::binary)); + c.write(ws, sbuf("CLOSE")); + restart(error::closed); + + // send bad utf8 + ws.set_option(message_type(opcode::binary)); + c.write(ws, buffer_cat(sbuf("TEXT"), + cbuf(0x03, 0xea, 0xf0, 0x28, 0x8c, 0xbc))); + restart(error::failed); + + // cause bad utf8 + ws.set_option(message_type(opcode::binary)); + c.write(ws, buffer_cat(sbuf("TEXT"), + cbuf(0x03, 0xea, 0xf0, 0x28, 0x8c, 0xbc))); + c.write(ws, sbuf("Hello")); + restart(error::failed); + + // cause bad close + ws.set_option(message_type(opcode::binary)); + c.write(ws, buffer_cat(sbuf("RAW"), + cbuf(0x88, 0x02, 0x03, 0xed))); + restart(error::failed); + + // unexpected cont + c.write_raw(ws, + cbuf(0x80, 0x80, 0xff, 0xff, 0xff, 0xff)); + restart(error::closed); + + // invalid fixed frame header + c.write_raw(ws, + cbuf(0x8f, 0x80, 0xff, 0xff, 0xff, 0xff)); + restart(error::closed); + + // cause non-canonical extended size + c.write(ws, buffer_cat(sbuf("RAW"), + cbuf(0x82, 0x7e, 0x00, 0x01, 0x00))); + restart(error::failed); + + if(! pmd.client_enable) + { + // expected cont + c.write_frame(ws, false, boost::asio::null_buffers{}); + c.write_raw(ws, + cbuf(0x81, 0x80, 0xff, 0xff, 0xff, 0xff)); + restart(error::closed); + + // message size above 2^64 + c.write_frame(ws, false, cbuf(0x00)); + c.write_raw(ws, + cbuf(0x80, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff)); + restart(error::closed); + + // message size exceeds max + ws.set_option(read_message_max{1}); + c.write(ws, cbuf(0x00, 0x00)); + restart(error::failed); + ws.set_option(read_message_max{16*1024*1024}); + } + } + catch(system_error const&) + { + continue; + } + break; + } + BEAST_EXPECT(n < limit); + } + void run() override { static_assert(std::is_constructible< @@ -1395,37 +1244,86 @@ public: auto const any = endpoint_type{ address_type::from_string("127.0.0.1"), 0}; - for(std::size_t n = 0; n < 1; ++n) + testOptions(); + testAccept(); + testBadHandshakes(); + testBadResponses(); + { - testOptions(); - testAccept(); - testBadHandshakes(); - testBadResponses(); - { - sync_echo_server server(true, any); - auto const ep = server.local_endpoint(); - - //testInvokable1(ep); - testInvokable2(ep); - testInvokable3(ep); - testInvokable4(ep); - //testInvokable5(ep); - - testSyncClient(ep); - testAsyncWriteFrame(ep); - yield_to_mf(ep, &stream_test::testAsyncClient); - } - { - error_code ec; - async_echo_server server{nullptr, 4}; - server.open(true, any, ec); - BEAST_EXPECTS(! ec, ec.message()); - auto const ep = server.local_endpoint(); - testSyncClient(ep); - testAsyncWriteFrame(ep); - yield_to_mf(ep, &stream_test::testAsyncClient); - } + error_code ec; + ::websocket::sync_echo_server server{nullptr}; + server.open(any, ec); + BEAST_EXPECTS(! ec, ec.message()); + auto const ep = server.local_endpoint(); + //testInvokable1(ep); + testInvokable2(ep); + testInvokable3(ep); + testInvokable4(ep); + //testInvokable5(ep); + testAsyncWriteFrame(ep); } + + { + error_code ec; + ::websocket::async_echo_server server{nullptr, 4}; + server.open(any, ec); + BEAST_EXPECTS(! ec, ec.message()); + auto const ep = server.local_endpoint(); + testAsyncWriteFrame(ep); + } + + auto const doClientTests = + [this, any](permessage_deflate const& pmd) + { + { + error_code ec; + ::websocket::sync_echo_server server{nullptr}; + server.set_option(pmd); + server.open(any, ec); + BEAST_EXPECTS(! ec, ec.message()); + auto const ep = server.local_endpoint(); + testEndpoint(SyncClient{}, ep, pmd); + yield_to( + [&](yield_context yield) + { + testEndpoint( + AsyncClient{yield}, ep, pmd); + }); + } + { + error_code ec; + ::websocket::async_echo_server server{nullptr, 4}; + server.set_option(pmd); + server.open(any, ec); + BEAST_EXPECTS(! ec, ec.message()); + auto const ep = server.local_endpoint(); + testEndpoint(SyncClient{}, ep, pmd); + yield_to( + [&](yield_context yield) + { + testEndpoint( + AsyncClient{yield}, ep, pmd); + }); + } + }; + + permessage_deflate pmd; + + pmd.client_enable = false; + pmd.server_enable = false; + doClientTests(pmd); + + pmd.client_enable = true; + pmd.server_enable = true; + pmd.client_max_window_bits = 10; + pmd.client_no_context_takeover = false; + doClientTests(pmd); + + pmd.client_enable = true; + pmd.server_enable = true; + pmd.client_max_window_bits = 10; + pmd.client_no_context_takeover = true; + doClientTests(pmd); } }; diff --git a/test/websocket/websocket_async_echo_server.hpp b/test/websocket/websocket_async_echo_server.hpp index 0519deb203..61f0b9f84b 100644 --- a/test/websocket/websocket_async_echo_server.hpp +++ b/test/websocket/websocket_async_echo_server.hpp @@ -5,45 +5,153 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef BEAST_WEBSOCKET_ASYNC_ECHO_PEER_H_INCLUDED -#define BEAST_WEBSOCKET_ASYNC_ECHO_PEER_H_INCLUDED +#ifndef BEAST_WEBSOCKET_ASYNC_ECHO_SERVER_HPP +#define BEAST_WEBSOCKET_ASYNC_ECHO_SERVER_HPP #include #include -#include +#include #include #include +#include #include -#include #include -#include - +#include #include +#include +#include +#include +#include +#include +#include -namespace beast { namespace websocket { -// Asynchronous WebSocket echo client/server -// +/** Asynchronous WebSocket echo client/server +*/ class async_echo_server { public: - using endpoint_type = boost::asio::ip::tcp::endpoint; + using error_code = beast::error_code; using address_type = boost::asio::ip::address; using socket_type = boost::asio::ip::tcp::socket; + using endpoint_type = boost::asio::ip::tcp::endpoint; private: + struct identity + { + template + void + operator()(beast::http::message< + true, Body, Fields>& req) const + { + req.fields.replace("User-Agent", "async_echo_client"); + } + + template + void + operator()(beast::http::message< + false, Body, Fields>& resp) const + { + resp.fields.replace("Server", "async_echo_server"); + } + }; + + /** A container of type-erased option setters. + */ + template + class options_set + { + // workaround for std::function bug in msvc + struct callable + { + virtual ~callable() = default; + virtual void operator()( + beast::websocket::stream&) = 0; + }; + + template + class callable_impl : public callable + { + T t_; + + public: + template + callable_impl(U&& u) + : t_(std::forward(u)) + { + } + + void + operator()(beast::websocket::stream& ws) + { + t_(ws); + } + }; + + template + class lambda + { + Opt opt_; + + public: + lambda(lambda&&) = default; + lambda(lambda const&) = default; + + lambda(Opt const& opt) + : opt_(opt) + { + } + + void + operator()(beast::websocket::stream& ws) const + { + ws.set_option(opt_); + } + }; + + std::unordered_map> list_; + + public: + template + void + set_option(Opt const& opt) + { + std::unique_ptr p; + p.reset(new callable_impl>{opt}); + list_[std::type_index{ + typeid(Opt)}] = std::move(p); + } + + void + set_options(beast::websocket::stream& ws) + { + for(auto const& op : list_) + (*op.second)(ws); + } + }; + std::ostream* log_; boost::asio::io_service ios_; socket_type sock_; + endpoint_type ep_; boost::asio::ip::tcp::acceptor acceptor_; std::vector thread_; boost::optional work_; + options_set opts_; public: async_echo_server(async_echo_server const&) = delete; async_echo_server& operator=(async_echo_server const&) = delete; + /** Constructor. + + @param log A pointer to a stream to log to, or `nullptr` + to disable logging. + + @param threads The number of threads in the io_service. + */ async_echo_server(std::ostream* log, std::size_t threads) : log_(log) @@ -51,12 +159,16 @@ public: , acceptor_(ios_) , work_(ios_) { + opts_.set_option( + beast::websocket::decorate(identity{})); thread_.reserve(threads); for(std::size_t i = 0; i < threads; ++i) thread_.emplace_back( [&]{ ios_.run(); }); } + /** Destructor. + */ ~async_echo_server() { work_ = boost::none; @@ -67,148 +179,110 @@ public: t.join(); } - void - open(bool server, - endpoint_type const& ep, error_code& ec) - { - if(server) - { - acceptor_.open(ep.protocol(), ec); - if(ec) - { - if(log_) - (*log_) << "open: " << ec.message() << std::endl; - return; - } - acceptor_.set_option( - boost::asio::socket_base::reuse_address{true}); - acceptor_.bind(ep, ec); - if(ec) - { - if(log_) - (*log_) << "bind: " << ec.message() << std::endl; - return; - } - acceptor_.listen( - boost::asio::socket_base::max_connections, ec); - if(ec) - { - if(log_) - (*log_) << "listen: " << ec.message() << std::endl; - return; - } - acceptor_.async_accept(sock_, - std::bind(&async_echo_server::on_accept, this, - beast::asio::placeholders::error)); - } - else - { - Peer{*this, std::move(sock_), ep}; - } - } - + /** Return the listening endpoint. + */ endpoint_type local_endpoint() const { return acceptor_.local_endpoint(); } + /** Set a websocket option. + + The option will be applied to all new connections. + + @param opt The option to apply. + */ + template + void + set_option(Opt const& opt) + { + opts_.set_option(opt); + } + + /** Open a listening port. + + @param ep The address and port to bind to. + + @param ec Set to the error, if any occurred. + */ + void + open(endpoint_type const& ep, error_code& ec) + { + acceptor_.open(ep.protocol(), ec); + if(ec) + return fail("open", ec); + acceptor_.set_option( + boost::asio::socket_base::reuse_address{true}); + acceptor_.bind(ep, ec); + if(ec) + return fail("bind", ec); + acceptor_.listen( + boost::asio::socket_base::max_connections, ec); + if(ec) + return fail("listen", ec); + acceptor_.async_accept(sock_, ep_, + std::bind(&async_echo_server::on_accept, this, + beast::asio::placeholders::error)); + } + private: - class Peer + class peer { struct data { async_echo_server& server; + endpoint_type ep; int state = 0; - boost::optional ep; - stream ws; + beast::websocket::stream ws; boost::asio::io_service::strand strand; - opcode op; + beast::websocket::opcode op; beast::streambuf db; - int id; + std::size_t id; data(async_echo_server& server_, + endpoint_type const& ep_, socket_type&& sock_) : server(server_) - , ws(std::move(sock_)) - , strand(ws.get_io_service()) - , id([] - { - static int n = 0; - return ++n; - }()) - { - } - - data(async_echo_server& server_, - socket_type&& sock_, endpoint_type const& ep_) - : server(server_) , ep(ep_) , ws(std::move(sock_)) , strand(ws.get_io_service()) , id([] { - static int n = 0; + static std::atomic n{0}; return ++n; }()) { } }; + // VFALCO This could be unique_ptr in [Net.TS] std::shared_ptr d_; public: - Peer(Peer&&) = default; - Peer(Peer const&) = default; - Peer& operator=(Peer&&) = delete; - Peer& operator=(Peer const&) = delete; - - struct identity - { - template - void - operator()(http::message& req) - { - req.fields.replace("User-Agent", "async_echo_client"); - } - - template - void - operator()(http::message& resp) - { - resp.fields.replace("Server", "async_echo_server"); - } - }; + peer(peer&&) = default; + peer(peer const&) = default; + peer& operator=(peer&&) = delete; + peer& operator=(peer const&) = delete; template explicit - Peer(async_echo_server& server, - socket_type&& sock, Args&&... args) - : d_(std::make_shared(server, + peer(async_echo_server& server, + endpoint_type const& ep, socket_type&& sock, + Args&&... args) + : d_(std::make_shared(server, ep, std::forward(sock), std::forward(args)...)) { auto& d = *d_; - d.ws.set_option(decorate(identity{})); - d.ws.set_option(read_message_max(64 * 1024 * 1024)); - d.ws.set_option(auto_fragment{false}); - //d.ws.set_option(write_buffer_size{64 * 1024}); + d.server.opts_.set_options(d.ws); run(); } void run() { auto& d = *d_; - if(! d.ep) - { - d.ws.async_accept(std::move(*this)); - } - else - { - d.state = 4; - d.ws.next_layer().async_connect( - *d.ep, std::move(*this)); - } + d.ws.async_accept(std::move(*this)); } template @@ -220,7 +294,7 @@ private: using boost::asio::buffer_copy; if(db.size() < N-1) return false; - static_string t; + beast::static_string t; t.resize(N-1); buffer_copy(buffer(t.data(), t.size()), db.data()); @@ -245,12 +319,12 @@ private: // did accept case 0: if(ec) - return fail(ec, "async_accept"); + return fail("async_accept", ec); // start case 1: if(ec) - return fail(ec, "async_handshake"); + return fail("async_handshake", ec); d.db.consume(d.db.size()); // read message d.state = 2; @@ -260,10 +334,10 @@ private: // got message case 2: - if(ec == error::closed) + if(ec == beast::websocket::error::closed) return; if(ec) - return fail(ec, "async_read"); + return fail("async_read", ec); if(match(d.db, "RAW")) { d.state = 1; @@ -274,14 +348,16 @@ private: else if(match(d.db, "TEXT")) { d.state = 1; - d.ws.set_option(message_type{opcode::text}); + d.ws.set_option( + beast::websocket::message_type{ + beast::websocket::opcode::text}); d.ws.async_write( d.db.data(), d.strand.wrap(std::move(*this))); return; } else if(match(d.db, "PING")) { - ping_data payload; + beast::websocket::ping_data payload; d.db.consume(buffer_copy( buffer(payload.data(), payload.size()), d.db.data())); @@ -299,53 +375,36 @@ private: } // write message d.state = 1; - d.ws.set_option(message_type(d.op)); + d.ws.set_option( + beast::websocket::message_type(d.op)); d.ws.async_write(d.db.data(), d.strand.wrap(std::move(*this))); return; - - // connected - case 4: - if(ec) - return fail(ec, "async_connect"); - d.state = 1; - d.ws.async_handshake( - d.ep->address().to_string() + ":" + - boost::lexical_cast(d.ep->port()), - "/", d.strand.wrap(std::move(*this))); - return; } } private: void - fail(error_code ec, std::string what) + fail(std::string what, error_code ec) { auto& d = *d_; if(d.server.log_) - { - if(ec != error::closed) - (*d.server.log_) << "#" << d.id << " " << - what << ": " << ec.message() << std::endl; - } + if(ec != beast::websocket::error::closed) + d.server.fail("[#" + std::to_string(d.id) + + " " + boost::lexical_cast(d.ep) + + "] " + what, ec); } }; void - fail(error_code ec, std::string what) + fail(std::string what, error_code ec) { if(log_) + { + static std::mutex m; + std::lock_guard lock{m}; (*log_) << what << ": " << ec.message() << std::endl; - } - - void - maybe_throw(error_code ec, std::string what) - { - if(ec) - { - fail(ec, what); - throw ec; } } @@ -356,16 +415,15 @@ private: return; if(ec == boost::asio::error::operation_aborted) return; - maybe_throw(ec, "accept"); - socket_type sock(std::move(sock_)); - acceptor_.async_accept(sock_, + if(ec) + fail("accept", ec); + peer{*this, ep_, std::move(sock_)}; + acceptor_.async_accept(sock_, ep_, std::bind(&async_echo_server::on_accept, this, beast::asio::placeholders::error)); - Peer{*this, std::move(sock)}; } }; } // websocket -} // beast #endif diff --git a/test/websocket/websocket_echo.cpp b/test/websocket/websocket_echo.cpp deleted file mode 100644 index a4ddee49c2..0000000000 --- a/test/websocket/websocket_echo.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// -// Copyright (c) 2013-2016 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) -// - -#include "websocket_async_echo_server.hpp" -#include "websocket_sync_echo_server.hpp" -#include -#include - -int main() -{ - using endpoint_type = boost::asio::ip::tcp::endpoint; - using address_type = boost::asio::ip::address; - - try - { - boost::system::error_code ec; - beast::websocket::async_echo_server s1{nullptr, 1}; - s1.open(true, endpoint_type{ - address_type::from_string("127.0.0.1"), 6000 }, ec); - - beast::websocket::sync_echo_server s2(true, endpoint_type{ - address_type::from_string("127.0.0.1"), 6001 }); - - beast::test::sig_wait(); - } - catch(std::exception const& e) - { - std::cout << "Error: " << e.what() << std::endl; - } -} diff --git a/test/websocket/websocket_sync_echo_server.hpp b/test/websocket/websocket_sync_echo_server.hpp index 97276292ff..adaa16338a 100644 --- a/test/websocket/websocket_sync_echo_server.hpp +++ b/test/websocket/websocket_sync_echo_server.hpp @@ -5,154 +5,284 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef BEAST_WEBSOCKET_SYNC_ECHO_PEER_H_INCLUDED -#define BEAST_WEBSOCKET_SYNC_ECHO_PEER_H_INCLUDED +#ifndef BEAST_WEBSOCKET_SYNC_ECHO_SERVER_HPP +#define BEAST_WEBSOCKET_SYNC_ECHO_SERVER_HPP #include #include #include #include #include +#include #include -#include #include +#include +#include +#include #include +#include +#include +#include +#include -namespace beast { namespace websocket { -// Synchronous WebSocket echo client/server -// +/** Synchronous WebSocket echo client/server +*/ class sync_echo_server { public: + using error_code = beast::error_code; using endpoint_type = boost::asio::ip::tcp::endpoint; using address_type = boost::asio::ip::address; using socket_type = boost::asio::ip::tcp::socket; private: - bool log_ = false; - boost::asio::io_service ios_; - socket_type sock_; - boost::asio::ip::tcp::acceptor acceptor_; - std::thread thread_; - -public: - sync_echo_server(bool /*server*/, endpoint_type ep) - : sock_(ios_) - , acceptor_(ios_) - { - error_code ec; - acceptor_.open(ep.protocol(), ec); - maybe_throw(ec, "open"); - acceptor_.set_option( - boost::asio::socket_base::reuse_address{true}); - acceptor_.bind(ep, ec); - maybe_throw(ec, "bind"); - acceptor_.listen( - boost::asio::socket_base::max_connections, ec); - maybe_throw(ec, "listen"); - acceptor_.async_accept(sock_, - std::bind(&sync_echo_server::on_accept, this, - beast::asio::placeholders::error)); - thread_ = std::thread{[&]{ ios_.run(); }}; - } - - ~sync_echo_server() - { - error_code ec; - ios_.dispatch( - [&]{ acceptor_.close(ec); }); - thread_.join(); - } - - endpoint_type - local_endpoint() const - { - return acceptor_.local_endpoint(); - } - -private: - void - fail(error_code ec, std::string what) - { - if(log_) - std::cerr << - what << ": " << ec.message() << std::endl; - } - - void - fail(int id, error_code ec, std::string what) - { - if(log_) - std::cerr << "#" << boost::lexical_cast(id) << " " << - what << ": " << ec.message() << std::endl; - } - - void - maybe_throw(error_code ec, std::string what) - { - if(ec) - { - fail(ec, what); - throw ec; - } - } - - struct lambda - { - int id; - sync_echo_server& self; - boost::asio::io_service::work work; - // Must be destroyed before work otherwise the - // io_service could be destroyed before the socket. - socket_type sock; - - lambda(int id_, sync_echo_server& self_, - socket_type&& sock_) - : id(id_) - , self(self_) - , work(sock_.get_io_service()) - , sock(std::move(sock_)) - { - } - - void operator()() - { - self.do_peer(id, std::move(sock)); - } - }; - - void - on_accept(error_code ec) - { - if(ec == boost::asio::error::operation_aborted) - return; - maybe_throw(ec, "accept"); - static int id_ = 0; - std::thread{lambda{++id_, *this, std::move(sock_)}}.detach(); - acceptor_.async_accept(sock_, - std::bind(&sync_echo_server::on_accept, this, - beast::asio::placeholders::error)); - } - struct identity { template void - operator()(http::message& req) + operator()(beast::http::message< + true, Body, Fields>& req) const { req.fields.replace("User-Agent", "sync_echo_client"); } template void - operator()(http::message& resp) + operator()(beast::http::message< + false, Body, Fields>& resp) const { resp.fields.replace("Server", "sync_echo_server"); } }; + /** A container of type-erased option setters. + */ + template + class options_set + { + // workaround for std::function bug in msvc + struct callable + { + virtual ~callable() = default; + virtual void operator()( + beast::websocket::stream&) = 0; + }; + + template + class callable_impl : public callable + { + T t_; + + public: + template + callable_impl(U&& u) + : t_(std::forward(u)) + { + } + + void + operator()(beast::websocket::stream& ws) + { + t_(ws); + } + }; + + template + class lambda + { + Opt opt_; + + public: + lambda(lambda&&) = default; + lambda(lambda const&) = default; + + lambda(Opt const& opt) + : opt_(opt) + { + } + + void + operator()(beast::websocket::stream& ws) const + { + ws.set_option(opt_); + } + }; + + std::unordered_map> list_; + + public: + template + void + set_option(Opt const& opt) + { + std::unique_ptr p; + p.reset(new callable_impl>{opt}); + list_[std::type_index{ + typeid(Opt)}] = std::move(p); + } + + void + set_options(beast::websocket::stream& ws) + { + for(auto const& op : list_) + (*op.second)(ws); + } + }; + + std::ostream* log_; + boost::asio::io_service ios_; + socket_type sock_; + endpoint_type ep_; + boost::asio::ip::tcp::acceptor acceptor_; + std::thread thread_; + options_set opts_; + +public: + /** Constructor. + + @param log A pointer to a stream to log to, or `nullptr` + to disable logging. + */ + sync_echo_server(std::ostream* log) + : log_(log) + , sock_(ios_) + , acceptor_(ios_) + { + opts_.set_option( + beast::websocket::decorate(identity{})); + } + + /** Destructor. + */ + ~sync_echo_server() + { + if(thread_.joinable()) + { + error_code ec; + ios_.dispatch( + [&]{ acceptor_.close(ec); }); + thread_.join(); + } + } + + /** Return the listening endpoint. + */ + endpoint_type + local_endpoint() const + { + return acceptor_.local_endpoint(); + } + + /** Set a websocket option. + + The option will be applied to all new connections. + + @param opt The option to apply. + */ + template + void + set_option(Opt const& opt) + { + opts_.set_option(opt); + } + + /** Open a listening port. + + @param ep The address and port to bind to. + + @param ec Set to the error, if any occurred. + */ + void + open(endpoint_type const& ep, error_code& ec) + { + acceptor_.open(ep.protocol(), ec); + if(ec) + return fail("open", ec); + acceptor_.set_option( + boost::asio::socket_base::reuse_address{true}); + acceptor_.bind(ep, ec); + if(ec) + return fail("bind", ec); + acceptor_.listen( + boost::asio::socket_base::max_connections, ec); + if(ec) + return fail("listen", ec); + acceptor_.async_accept(sock_, ep_, + std::bind(&sync_echo_server::on_accept, this, + beast::asio::placeholders::error)); + thread_ = std::thread{[&]{ ios_.run(); }}; + } + +private: + void + fail(std::string what, error_code ec) + { + if(log_) + { + static std::mutex m; + std::lock_guard lock{m}; + (*log_) << what << ": " << + ec.message() << std::endl; + } + } + + void + fail(std::string what, error_code ec, + int id, endpoint_type const& ep) + { + if(log_) + if(ec != beast::websocket::error::closed) + fail("[#" + std::to_string(id) + " " + + boost::lexical_cast(ep) + + "] " + what, ec); + } + + void + on_accept(error_code ec) + { + if(ec == boost::asio::error::operation_aborted) + return; + if(ec) + return fail("accept", ec); + struct lambda + { + std::size_t id; + endpoint_type ep; + sync_echo_server& self; + boost::asio::io_service::work work; + // Must be destroyed before work otherwise the + // io_service could be destroyed before the socket. + socket_type sock; + + lambda(sync_echo_server& self_, + endpoint_type const& ep_, + socket_type&& sock_) + : id([] + { + static std::atomic n{0}; + return ++n; + }()) + , ep(ep_) + , self(self_) + , work(sock_.get_io_service()) + , sock(std::move(sock_)) + { + } + + void operator()() + { + self.do_peer(id, ep, std::move(sock)); + } + }; + std::thread{lambda{*this, ep_, std::move(sock_)}}.detach(); + acceptor_.async_accept(sock_, ep_, + std::bind(&sync_echo_server::on_accept, this, + beast::asio::placeholders::error)); + } + template static bool @@ -162,7 +292,7 @@ private: using boost::asio::buffer_copy; if(db.size() < N-1) return false; - static_string t; + beast::static_string t; t.resize(N-1); buffer_copy(buffer(t.data(), t.size()), db.data()); @@ -173,23 +303,24 @@ private: } void - do_peer(int id, socket_type&& sock) + do_peer(std::size_t id, + endpoint_type const& ep, socket_type&& sock) { using boost::asio::buffer; using boost::asio::buffer_copy; - stream ws(std::move(sock)); - ws.set_option(decorate(identity{})); - ws.set_option(read_message_max(64 * 1024 * 1024)); + beast::websocket::stream< + socket_type> ws{std::move(sock)}; + opts_.set_options(ws); error_code ec; ws.accept(ec); if(ec) { - fail(id, ec, "accept"); + fail("accept", ec, id, ep); return; } for(;;) { - opcode op; + beast::websocket::opcode op; beast::streambuf sb; ws.read(op, sb, ec); if(ec) @@ -197,7 +328,7 @@ private: auto const s = ec.message(); break; } - ws.set_option(message_type(op)); + ws.set_option(beast::websocket::message_type{op}); if(match(sb, "RAW")) { boost::asio::write( @@ -205,12 +336,14 @@ private: } else if(match(sb, "TEXT")) { - ws.set_option(message_type{opcode::text}); + ws.set_option( + beast::websocket::message_type{ + beast::websocket::opcode::text}); ws.write(sb.data(), ec); } else if(match(sb, "PING")) { - ping_data payload; + beast::websocket::ping_data payload; sb.consume(buffer_copy( buffer(payload.data(), payload.size()), sb.data())); @@ -227,14 +360,13 @@ private: if(ec) break; } - if(ec && ec != error::closed) + if(ec && ec != beast::websocket::error::closed) { - fail(id, ec, "read"); + fail("read", ec, id, ep); } } }; } // websocket -} // beast #endif diff --git a/test/zlib/CMakeLists.txt b/test/zlib/CMakeLists.txt index 3c0f2035bc..b4fcfd935b 100644 --- a/test/zlib/CMakeLists.txt +++ b/test/zlib/CMakeLists.txt @@ -4,29 +4,6 @@ GroupSources(extras/beast extras) GroupSources(include/beast beast) GroupSources(test/zlib "/") -set(ZLIB_SOURCES - zlib-1.2.8/crc32.h - zlib-1.2.8/deflate.h - zlib-1.2.8/inffast.h - zlib-1.2.8/inffixed.h - zlib-1.2.8/inflate.h - zlib-1.2.8/inftrees.h - zlib-1.2.8/trees.h - zlib-1.2.8/zlib.h - zlib-1.2.8/zutil.h - zlib-1.2.8/adler32.c - zlib-1.2.8/compress.c - zlib-1.2.8/crc32.c - zlib-1.2.8/deflate.c - zlib-1.2.8/infback.c - zlib-1.2.8/inffast.c - zlib-1.2.8/inflate.c - zlib-1.2.8/inftrees.c - zlib-1.2.8/trees.c - zlib-1.2.8/uncompr.c - zlib-1.2.8/zutil.c -) - if (MSVC) set_source_files_properties (${ZLIB_SOURCES} PROPERTIES COMPILE_FLAGS "/wd4127 /wd4131 /wd4244") endif()