From 841ab8474b2c2c1e6e34218ecfe7479e3d16f645 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sat, 18 Nov 2017 16:52:18 -0800 Subject: [PATCH] permessage-deflate is a compile-time feature (API Change): fix #849 This adds an additional `bool` template parameter to `websocket::stream`: * When deflateSupported is `true`, the stream will be capable of negotiating the permessage-deflate websocket extension per the configured run-time settings. * When deflateSupported is `false`, the stream will never negotiate the permessage-deflate websocket extension. Furthermore, all of the code necessary for implementing the permessage-deflate extension will be excluded from function instantiations. The resulting emitted object code should be smaller. --- CHANGELOG.md | 3 + doc/qbk/06_websocket/1_streams.qbk | 28 +- .../boost/beast/websocket/detail/frame.hpp | 5 +- .../beast/websocket/detail/pmd_extension.hpp | 80 +-- .../beast/websocket/detail/stream_base.hpp | 141 +++++ .../beast/websocket/detail/type_traits.hpp | 4 +- include/boost/beast/websocket/impl/accept.ipp | 151 +++--- include/boost/beast/websocket/impl/close.ipp | 28 +- .../boost/beast/websocket/impl/handshake.ipp | 93 ++-- include/boost/beast/websocket/impl/ping.ipp | 40 +- include/boost/beast/websocket/impl/read.ipp | 123 +++-- include/boost/beast/websocket/impl/stream.ipp | 507 +++++++++++------- include/boost/beast/websocket/impl/write.ipp | 162 ++++-- include/boost/beast/websocket/stream.hpp | 230 ++++++-- include/boost/beast/websocket/stream_fwd.hpp | 3 +- test/beast/websocket/read1.cpp | 93 ++-- test/beast/websocket/stream.cpp | 2 + test/beast/websocket/test.hpp | 288 ++++++---- test/beast/websocket/write.cpp | 43 +- test/doc/websocket_snippets.cpp | 12 +- 20 files changed, 1315 insertions(+), 721 deletions(-) create mode 100644 include/boost/beast/websocket/detail/stream_base.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d022df9..14846b23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +-------------------------------------------------------------------------------- + Version 151: * Sanitizer failures are errors @@ -13,6 +15,7 @@ WebSocket: API Changes: * http::parser is not MoveConstructible +* permessage-deflate is a compile-time feature -------------------------------------------------------------------------------- diff --git a/doc/qbk/06_websocket/1_streams.qbk b/doc/qbk/06_websocket/1_streams.qbk index 3bb62e1c..fc2bc595 100644 --- a/doc/qbk/06_websocket/1_streams.qbk +++ b/doc/qbk/06_websocket/1_streams.qbk @@ -10,13 +10,27 @@ [section Creating Streams] The interface to the WebSocket implementation is a single template class -[link beast.ref.boost__beast__websocket__stream `stream`] -which wraps an existing network transport object or other type of -octet oriented stream. The wrapped object is called the "next layer" -and must meet the requirements of __SyncStream__ if synchronous -operations are performed, __AsyncStream__ if asynchronous operations -are performed, or both. Any arguments supplied during construction of -the stream wrapper are passed to next layer's constructor. +[link beast.ref.boost__beast__websocket__stream `stream`]: + +[ws_snippet_26] + +An instance of the stream wraps an existing network transport object +or other type of octet oriented stream. The wrapped object is called +the "next layer" and must meet the requirements of __SyncStream__ if +synchronous operations are performed, __AsyncStream__ if asynchronous +operations are performed, or both. Any arguments supplied during +construction of the stream wrapper are passed to next layer's constructor. + +The value of `deflateSupported` determines if the stream will support +(but not require) the permessage-deflate extension +([@https://tools.ietf.org/html/rfc7692 rfc7692]) +negotiation during handshaking. This extension allows messages to be +optionally automatically compressed using the deflate algorithm prior +to transmission. When this boolean value is `false`, the extension is +disabled. Applications which do not intend to use the permessage-deflate +extension may set the value to `false` to enjoy a reduction in the size +of the compiled output, as the necessary compression code (included with +Beast) will not be compiled in. Here we declare a websocket stream over a TCP/IP socket with ownership of the socket. The `io_context` argument is forwarded to the wrapped diff --git a/include/boost/beast/websocket/detail/frame.hpp b/include/boost/beast/websocket/detail/frame.hpp index 95080aee..c75a8cc0 100644 --- a/include/boost/beast/websocket/detail/frame.hpp +++ b/include/boost/beast/websocket/detail/frame.hpp @@ -75,7 +75,7 @@ native_to_little_uint32(std::uint32_t v, void* buf) p[3] = (v >> 24) & 0xff; } -/** WebSocket frame header opcodes. */ +// frame header opcodes enum class opcode : std::uint8_t { cont = 0, @@ -110,8 +110,7 @@ struct frame_header }; // holds the largest possible frame header -using fh_buffer = - flat_static_buffer<14>; +using fh_buffer = flat_static_buffer<14>; // holds the largest possible control frame using frame_buffer = diff --git a/include/boost/beast/websocket/detail/pmd_extension.hpp b/include/boost/beast/websocket/detail/pmd_extension.hpp index a28b844c..ce4f2425 100644 --- a/include/boost/beast/websocket/detail/pmd_extension.hpp +++ b/include/boost/beast/websocket/detail/pmd_extension.hpp @@ -19,6 +19,7 @@ #include #include #include +#include namespace boost { namespace beast { @@ -355,85 +356,6 @@ pmd_normalize(pmd_offer& offer) } } -//-------------------------------------------------------------------- - -// Compress a buffer sequence -// Returns: `true` if more calls are needed -// -template -bool -deflate( - DeflateStream& zo, - boost::asio::mutable_buffer& out, - buffers_suffix& cb, - bool fin, - std::size_t& total_in, - error_code& ec) -{ - using boost::asio::buffer; - BOOST_ASSERT(out.size() >= 6); - zlib::z_params zs; - zs.avail_in = 0; - zs.next_in = nullptr; - zs.avail_out = out.size(); - zs.next_out = out.data(); - for(auto in : beast::detail::buffers_range(cb)) - { - zs.avail_in = in.size(); - if(zs.avail_in == 0) - continue; - zs.next_in = in.data(); - 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 == out.size()); - ec.assign(0, ec.category()); - break; - } - if(zs.avail_out == 0) - { - BOOST_ASSERT(zs.total_out == out.size()); - break; - } - BOOST_ASSERT(zs.avail_in == 0); - } - total_in = zs.total_in; - cb.consume(zs.total_in); - if(zs.avail_out > 0 && fin) - { - auto const remain = boost::asio::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.assign(0, ec.category()); - 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(out.data(), zs.total_out); - return false; - } - } - } - ec.assign(0, ec.category()); - out = buffer(out.data(), zs.total_out); - return true; -} - } // detail } // websocket } // beast diff --git a/include/boost/beast/websocket/detail/stream_base.hpp b/include/boost/beast/websocket/detail/stream_base.hpp new file mode 100644 index 00000000..1c4a378f --- /dev/null +++ b/include/boost/beast/websocket/detail/stream_base.hpp @@ -0,0 +1,141 @@ +// +// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_WEBSOCKET_STREAM_BASE_HPP +#define BOOST_BEAST_WEBSOCKET_STREAM_BASE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace beast { +namespace websocket { +namespace detail { + +template +struct stream_base +{ + // State information for the permessage-deflate extension + struct pmd_type + { + // `true` if current read message is compressed + bool rd_set = false; + + zlib::deflate_stream zo; + zlib::inflate_stream zi; + }; + + std::unique_ptr pmd_; // pmd settings or nullptr + permessage_deflate pmd_opts_; // local pmd options + detail::pmd_offer pmd_config_; // offer (client) or negotiation (server) + + // return `true` if current message is deflated + bool + rd_deflated() const + { + return pmd_ && pmd_->rd_set; + } + + // set whether current message is deflated + // returns `false` on protocol violation + bool + rd_deflated(bool rsv1) + { + if(pmd_) + { + pmd_->rd_set = rsv1; + return true; + } + return ! rsv1; // pmd not negotiated + } + + template + bool + deflate( + boost::asio::mutable_buffer& out, + buffers_suffix& cb, + bool fin, + std::size_t& total_in, + error_code& ec); + + void + do_context_takeover_write(role_type role); + + void + inflate( + zlib::z_params& zs, + zlib::Flush flush, + error_code& ec); + + void + do_context_takeover_read(role_type role); +}; + +template<> +struct stream_base +{ + // These stubs are for avoiding linking in the zlib + // code when permessage-deflate is not enabled. + + bool + rd_deflated() const + { + return false; + } + + bool + rd_deflated(bool rsv1) + { + return ! rsv1; + } + + template + bool + deflate( + boost::asio::mutable_buffer&, + buffers_suffix&, + bool, + std::size_t&, + error_code&) + { + return false; + } + + void + do_context_takeover_write(role_type) + { + } + + void + inflate( + zlib::z_params&, + zlib::Flush, + error_code&) + { + } + + void + do_context_takeover_read(role_type) + { + } +}; + +} // detail +} // websocket +} // beast +} // boost + +#endif diff --git a/include/boost/beast/websocket/detail/type_traits.hpp b/include/boost/beast/websocket/detail/type_traits.hpp index 6c280614..6913174f 100644 --- a/include/boost/beast/websocket/detail/type_traits.hpp +++ b/include/boost/beast/websocket/detail/type_traits.hpp @@ -19,12 +19,12 @@ namespace websocket { namespace detail { template -using is_RequestDecorator = +using is_request_decorator = typename beast::detail::is_invocable::type; template -using is_ResponseDecorator = +using is_response_decorator = typename beast::detail::is_invocable::type; diff --git a/include/boost/beast/websocket/impl/accept.ipp b/include/boost/beast/websocket/impl/accept.ipp index e85bfe45..bbacad3f 100644 --- a/include/boost/beast/websocket/impl/accept.ipp +++ b/include/boost/beast/websocket/impl/accept.ipp @@ -34,20 +34,23 @@ namespace beast { namespace websocket { // Respond to an upgrade HTTP request -template +template template -class stream::response_op +class stream::response_op : public boost::asio::coroutine { struct data { - stream& ws; + stream& ws; response_type res; template - data(Handler const&, stream& ws_, http::request< - Body, http::basic_fields> const& req, - Decorator const& decorator) + data( + Handler const&, + stream& ws_, + http::request> const& req, + Decorator const& decorator) : ws(ws_) , res(ws_.build_response(req, decorator)) { @@ -62,7 +65,7 @@ public: template response_op(DeducedHandler&& h, - stream& ws, Args&&... args) + stream& ws, Args&&... args) : d_(std::forward(h), ws, std::forward(args)...) { @@ -78,7 +81,8 @@ public: } using executor_type = boost::asio::associated_executor_t< - Handler, decltype(std::declval&>().get_executor())>; + Handler, decltype(std::declval< + stream&>().get_executor())>; executor_type get_executor() const noexcept @@ -100,10 +104,10 @@ public: } }; -template +template template void -stream:: +stream:: response_op:: operator()( error_code ec, @@ -121,7 +125,7 @@ operator()( ec = error::handshake_failed; if(! ec) { - pmd_read(d.ws.pmd_config_, d.res); + d.ws.do_pmd_config(d.res, is_deflate_supported{}); d.ws.open(role_type::server); } d_.invoke(ec); @@ -132,18 +136,20 @@ operator()( // read and respond to an upgrade request // -template +template template -class stream::accept_op +class stream::accept_op : public boost::asio::coroutine { struct data { - stream& ws; + stream& ws; Decorator decorator; http::request_parser p; - data(Handler const&, stream& ws_, - Decorator const& decorator_) + data( + Handler const&, + stream& ws_, + Decorator const& decorator_) : ws(ws_) , decorator(decorator_) { @@ -158,7 +164,7 @@ public: template accept_op(DeducedHandler&& h, - stream& ws, Args&&... args) + stream& ws, Args&&... args) : d_(std::forward(h), ws, std::forward(args)...) { @@ -174,7 +180,7 @@ public: } using executor_type = boost::asio::associated_executor_t< - Handler, decltype(std::declval&>().get_executor())>; + Handler, decltype(std::declval&>().get_executor())>; executor_type get_executor() const noexcept @@ -199,11 +205,11 @@ public: } }; -template +template template template void -stream:: +stream:: accept_op:: run(Buffers const& buffers) { @@ -228,10 +234,10 @@ run(Buffers const& buffers) (*this)(ec); } -template +template template void -stream:: +stream:: accept_op:: operator()(error_code ec, std::size_t) { @@ -280,9 +286,9 @@ operator()(error_code ec, std::size_t) //------------------------------------------------------------------------------ -template +template void -stream:: +stream:: accept() { static_assert(is_sync_stream::value, @@ -293,15 +299,15 @@ accept() BOOST_THROW_EXCEPTION(system_error{ec}); } -template +template template void -stream:: +stream:: accept_ex(ResponseDecorator const& decorator) { static_assert(is_sync_stream::value, "SyncStream requirements not met"); - static_assert(detail::is_ResponseDecorator< + static_assert(detail::is_response_decorator< ResponseDecorator>::value, "ResponseDecorator requirements not met"); error_code ec; @@ -310,9 +316,9 @@ accept_ex(ResponseDecorator const& decorator) BOOST_THROW_EXCEPTION(system_error{ec}); } -template +template void -stream:: +stream:: accept(error_code& ec) { static_assert(is_sync_stream::value, @@ -321,26 +327,26 @@ accept(error_code& ec) do_accept(&default_decorate_res, ec); } -template +template template void -stream:: +stream:: accept_ex(ResponseDecorator const& decorator, error_code& ec) { static_assert(is_sync_stream::value, "SyncStream requirements not met"); - static_assert(detail::is_ResponseDecorator< + static_assert(detail::is_response_decorator< ResponseDecorator>::value, "ResponseDecorator requirements not met"); reset(); do_accept(decorator, ec); } -template +template template typename std::enable_if::value>::type -stream:: +stream:: accept(ConstBufferSequence const& buffers) { static_assert(is_sync_stream::value, @@ -354,13 +360,13 @@ accept(ConstBufferSequence const& buffers) BOOST_THROW_EXCEPTION(system_error{ec}); } -template +template template< class ConstBufferSequence, class ResponseDecorator> typename std::enable_if::value>::type -stream:: +stream:: accept_ex( ConstBufferSequence const& buffers, ResponseDecorator const &decorator) @@ -370,7 +376,7 @@ accept_ex( static_assert(boost::asio::is_const_buffer_sequence< ConstBufferSequence>::value, "ConstBufferSequence requirements not met"); - static_assert(detail::is_ResponseDecorator< + static_assert(detail::is_response_decorator< ResponseDecorator>::value, "ResponseDecorator requirements not met"); error_code ec; @@ -379,11 +385,11 @@ accept_ex( BOOST_THROW_EXCEPTION(system_error{ec}); } -template +template template typename std::enable_if::value>::type -stream:: +stream:: accept( ConstBufferSequence const& buffers, error_code& ec) { @@ -412,13 +418,13 @@ accept( do_accept(&default_decorate_res, ec); } -template +template template< class ConstBufferSequence, class ResponseDecorator> typename std::enable_if::value>::type -stream:: +stream:: accept_ex( ConstBufferSequence const& buffers, ResponseDecorator const& decorator, @@ -451,10 +457,10 @@ accept_ex( do_accept(decorator, ec); } -template +template template void -stream:: +stream:: accept( http::request> const& req) @@ -467,12 +473,12 @@ accept( BOOST_THROW_EXCEPTION(system_error{ec}); } -template +template template< class Body, class Allocator, class ResponseDecorator> void -stream:: +stream:: accept_ex( http::request> const& req, @@ -480,7 +486,7 @@ accept_ex( { static_assert(is_sync_stream::value, "SyncStream requirements not met"); - static_assert(detail::is_ResponseDecorator< + static_assert(detail::is_response_decorator< ResponseDecorator>::value, "ResponseDecorator requirements not met"); error_code ec; @@ -489,10 +495,10 @@ accept_ex( BOOST_THROW_EXCEPTION(system_error{ec}); } -template +template template void -stream:: +stream:: accept( http::request> const& req, @@ -504,12 +510,12 @@ accept( do_accept(req, &default_decorate_res, ec); } -template +template template< class Body, class Allocator, class ResponseDecorator> void -stream:: +stream:: accept_ex( http::request> const& req, @@ -518,7 +524,7 @@ accept_ex( { static_assert(is_sync_stream::value, "SyncStream requirements not met"); - static_assert(detail::is_ResponseDecorator< + static_assert(detail::is_response_decorator< ResponseDecorator>::value, "ResponseDecorator requirements not met"); reset(); @@ -527,12 +533,12 @@ accept_ex( //------------------------------------------------------------------------------ -template +template template< class AcceptHandler> BOOST_ASIO_INITFN_RESULT_TYPE( AcceptHandler, void(error_code)) -stream:: +stream:: async_accept( AcceptHandler&& handler) { @@ -551,20 +557,20 @@ async_accept( return init.result.get(); } -template +template template< class ResponseDecorator, class AcceptHandler> BOOST_ASIO_INITFN_RESULT_TYPE( AcceptHandler, void(error_code)) -stream:: +stream:: async_accept_ex( ResponseDecorator const& decorator, AcceptHandler&& handler) { static_assert(is_async_stream::value, "AsyncStream requirements not met"); - static_assert(detail::is_ResponseDecorator< + static_assert(detail::is_response_decorator< ResponseDecorator>::value, "ResponseDecorator requirements not met"); boost::asio::async_completion +template template< class ConstBufferSequence, class AcceptHandler> @@ -588,7 +594,7 @@ typename std::enable_if< ! http::detail::is_header::value, BOOST_ASIO_INITFN_RESULT_TYPE( AcceptHandler, void(error_code))>::type -stream:: +stream:: async_accept( ConstBufferSequence const& buffers, AcceptHandler&& handler) @@ -611,7 +617,7 @@ async_accept( return init.result.get(); } -template +template template< class ConstBufferSequence, class ResponseDecorator, @@ -620,7 +626,7 @@ typename std::enable_if< ! http::detail::is_header::value, BOOST_ASIO_INITFN_RESULT_TYPE( AcceptHandler, void(error_code))>::type -stream:: +stream:: async_accept_ex( ConstBufferSequence const& buffers, ResponseDecorator const& decorator, @@ -631,7 +637,7 @@ async_accept_ex( static_assert(boost::asio::is_const_buffer_sequence< ConstBufferSequence>::value, "ConstBufferSequence requirements not met"); - static_assert(detail::is_ResponseDecorator< + static_assert(detail::is_response_decorator< ResponseDecorator>::value, "ResponseDecorator requirements not met"); boost::asio::async_completion +template template< class Body, class Allocator, class AcceptHandler> BOOST_ASIO_INITFN_RESULT_TYPE( AcceptHandler, void(error_code)) -stream:: +stream:: async_accept( http::request> const& req, AcceptHandler&& handler) @@ -674,14 +680,14 @@ async_accept( return init.result.get(); } -template +template template< class Body, class Allocator, class ResponseDecorator, class AcceptHandler> BOOST_ASIO_INITFN_RESULT_TYPE( AcceptHandler, void(error_code)) -stream:: +stream:: async_accept_ex( http::request> const& req, ResponseDecorator const& decorator, @@ -689,7 +695,7 @@ async_accept_ex( { static_assert(is_async_stream::value, "AsyncStream requirements not met"); - static_assert(detail::is_ResponseDecorator< + static_assert(detail::is_response_decorator< ResponseDecorator>::value, "ResponseDecorator requirements not met"); boost::asio::async_completion +template template void -stream:: +stream:: do_accept( Decorator const& decorator, error_code& ec) @@ -725,13 +731,14 @@ do_accept( do_accept(p.get(), decorator, ec); } -template +template template void -stream:: +stream:: do_accept( - http::request> const& req, + http::request> const& req, Decorator const& decorator, error_code& ec) { @@ -746,7 +753,7 @@ do_accept( // teardown if Connection: close. return; } - pmd_read(pmd_config_, res); + do_pmd_config(res, is_deflate_supported{}); open(role_type::server); } diff --git a/include/boost/beast/websocket/impl/close.ipp b/include/boost/beast/websocket/impl/close.ipp index 23c026aa..7a7bb579 100644 --- a/include/boost/beast/websocket/impl/close.ipp +++ b/include/boost/beast/websocket/impl/close.ipp @@ -34,14 +34,14 @@ namespace websocket { frame. Finally it invokes the teardown operation to shut down the underlying connection. */ -template +template template -class stream::close_op +class stream::close_op : public boost::asio::coroutine { struct state { - stream& ws; + stream& ws; detail::frame_buffer fb; error_code ev; token tok; @@ -49,7 +49,7 @@ class stream::close_op state( Handler const&, - stream& ws_, + stream& ws_, close_reason const& cr) : ws(ws_) , tok(ws.tok_.unique()) @@ -69,7 +69,7 @@ public: template close_op( DeducedHandler&& h, - stream& ws, + stream& ws, close_reason const& cr) : d_(std::forward(h), ws, cr) { @@ -85,7 +85,7 @@ public: } using executor_type = boost::asio::associated_executor_t< - Handler, decltype(std::declval&>().get_executor())>; + Handler, decltype(std::declval&>().get_executor())>; executor_type get_executor() const noexcept @@ -109,10 +109,10 @@ public: } }; -template +template template void -stream:: +stream:: close_op:: operator()( error_code ec, @@ -327,9 +327,9 @@ operator()( //------------------------------------------------------------------------------ -template +template void -stream:: +stream:: close(close_reason const& cr) { static_assert(is_sync_stream::value, @@ -340,9 +340,9 @@ close(close_reason const& cr) BOOST_THROW_EXCEPTION(system_error{ec}); } -template +template void -stream:: +stream:: close(close_reason const& cr, error_code& ec) { static_assert(is_sync_stream::value, @@ -434,11 +434,11 @@ close(close_reason const& cr, error_code& ec) ec.assign(0, ec.category()); } -template +template template BOOST_ASIO_INITFN_RESULT_TYPE( CloseHandler, void(error_code)) -stream:: +stream:: async_close(close_reason const& cr, CloseHandler&& handler) { static_assert(is_async_stream::value, diff --git a/include/boost/beast/websocket/impl/handshake.ipp b/include/boost/beast/websocket/impl/handshake.ipp index 0de20986..edab6542 100644 --- a/include/boost/beast/websocket/impl/handshake.ipp +++ b/include/boost/beast/websocket/impl/handshake.ipp @@ -33,25 +33,27 @@ namespace websocket { // send the upgrade request and process the response // -template +template template -class stream::handshake_op +class stream::handshake_op : public boost::asio::coroutine { struct data { - stream& ws; + stream& ws; response_type* res_p; detail::sec_ws_key_type key; http::request req; response_type res; template - data(Handler const&, stream& ws_, + data( + Handler const&, + stream& ws_, response_type* res_p_, - string_view host, - string_view target, - Decorator const& decorator) + string_view host, + string_view target, + Decorator const& decorator) : ws(ws_) , res_p(res_p_) , req(ws.build_request(key, @@ -69,7 +71,7 @@ public: template handshake_op(DeducedHandler&& h, - stream& ws, Args&&... args) + stream& ws, Args&&... args) : d_(std::forward(h), ws, std::forward(args)...) { @@ -85,7 +87,7 @@ public: } using executor_type = boost::asio::associated_executor_t< - Handler, decltype(std::declval&>().get_executor())>; + Handler, decltype(std::declval&>().get_executor())>; executor_type get_executor() const noexcept @@ -108,17 +110,18 @@ public: } }; -template +template template void -stream::handshake_op:: +stream:: +handshake_op:: operator()(error_code ec, std::size_t) { auto& d = *d_; BOOST_ASIO_CORO_REENTER(*this) { // Send HTTP Upgrade - pmd_read(d.ws.pmd_config_, d.req); + d.ws.do_pmd_config(d.req, is_deflate_supported{}); BOOST_ASIO_CORO_YIELD http::async_write(d.ws.stream_, d.req, std::move(*this)); @@ -146,11 +149,11 @@ operator()(error_code ec, std::size_t) } } -template +template template BOOST_ASIO_INITFN_RESULT_TYPE( HandshakeHandler, void(error_code)) -stream:: +stream:: async_handshake(string_view host, string_view target, HandshakeHandler&& handler) @@ -166,11 +169,11 @@ async_handshake(string_view host, return init.result.get(); } -template +template template BOOST_ASIO_INITFN_RESULT_TYPE( HandshakeHandler, void(error_code)) -stream:: +stream:: async_handshake(response_type& res, string_view host, string_view target, @@ -187,11 +190,11 @@ async_handshake(response_type& res, return init.result.get(); } -template +template template BOOST_ASIO_INITFN_RESULT_TYPE( HandshakeHandler, void(error_code)) -stream:: +stream:: async_handshake_ex(string_view host, string_view target, RequestDecorator const& decorator, @@ -199,7 +202,7 @@ async_handshake_ex(string_view host, { static_assert(is_async_stream::value, "AsyncStream requirements not met"); - static_assert(detail::is_RequestDecorator< + static_assert(detail::is_request_decorator< RequestDecorator>::value, "RequestDecorator requirements not met"); boost::asio::async_completion +template template BOOST_ASIO_INITFN_RESULT_TYPE( HandshakeHandler, void(error_code)) -stream:: +stream:: async_handshake_ex(response_type& res, string_view host, string_view target, @@ -224,7 +227,7 @@ async_handshake_ex(response_type& res, { static_assert(is_async_stream::value, "AsyncStream requirements not met"); - static_assert(detail::is_RequestDecorator< + static_assert(detail::is_request_decorator< RequestDecorator>::value, "RequestDecorator requirements not met"); boost::asio::async_completion +template void -stream:: +stream:: handshake(string_view host, string_view target) { @@ -251,9 +254,9 @@ handshake(string_view host, BOOST_THROW_EXCEPTION(system_error{ec}); } -template +template void -stream:: +stream:: handshake(response_type& res, string_view host, string_view target) @@ -266,17 +269,17 @@ handshake(response_type& res, BOOST_THROW_EXCEPTION(system_error{ec}); } -template +template template void -stream:: +stream:: handshake_ex(string_view host, string_view target, RequestDecorator const& decorator) { static_assert(is_sync_stream::value, "SyncStream requirements not met"); - static_assert(detail::is_RequestDecorator< + static_assert(detail::is_request_decorator< RequestDecorator>::value, "RequestDecorator requirements not met"); error_code ec; @@ -285,10 +288,10 @@ handshake_ex(string_view host, BOOST_THROW_EXCEPTION(system_error{ec}); } -template +template template void -stream:: +stream:: handshake_ex(response_type& res, string_view host, string_view target, @@ -296,7 +299,7 @@ handshake_ex(response_type& res, { static_assert(is_sync_stream::value, "SyncStream requirements not met"); - static_assert(detail::is_RequestDecorator< + static_assert(detail::is_request_decorator< RequestDecorator>::value, "RequestDecorator requirements not met"); error_code ec; @@ -305,9 +308,9 @@ handshake_ex(response_type& res, BOOST_THROW_EXCEPTION(system_error{ec}); } -template +template void -stream:: +stream:: handshake(string_view host, string_view target, error_code& ec) { @@ -317,9 +320,9 @@ handshake(string_view host, host, target, &default_decorate_req, ec); } -template +template void -stream:: +stream:: handshake(response_type& res, string_view host, string_view target, @@ -331,10 +334,10 @@ handshake(response_type& res, host, target, &default_decorate_req, ec); } -template +template template void -stream:: +stream:: handshake_ex(string_view host, string_view target, RequestDecorator const& decorator, @@ -342,17 +345,17 @@ handshake_ex(string_view host, { static_assert(is_sync_stream::value, "SyncStream requirements not met"); - static_assert(detail::is_RequestDecorator< + static_assert(detail::is_request_decorator< RequestDecorator>::value, "RequestDecorator requirements not met"); do_handshake(nullptr, host, target, decorator, ec); } -template +template template void -stream:: +stream:: handshake_ex(response_type& res, string_view host, string_view target, @@ -361,7 +364,7 @@ handshake_ex(response_type& res, { static_assert(is_sync_stream::value, "SyncStream requirements not met"); - static_assert(detail::is_RequestDecorator< + static_assert(detail::is_request_decorator< RequestDecorator>::value, "RequestDecorator requirements not met"); do_handshake(&res, @@ -370,10 +373,10 @@ handshake_ex(response_type& res, //------------------------------------------------------------------------------ -template +template template void -stream:: +stream:: do_handshake( response_type* res_p, string_view host, @@ -387,7 +390,7 @@ do_handshake( { auto const req = build_request( key, host, target, decorator); - pmd_read(pmd_config_, req); + do_pmd_config(req, is_deflate_supported{}); http::write(stream_, req, ec); } if(ec) diff --git a/include/boost/beast/websocket/impl/ping.ipp b/include/boost/beast/websocket/impl/ping.ipp index a53eaad3..f4067548 100644 --- a/include/boost/beast/websocket/impl/ping.ipp +++ b/include/boost/beast/websocket/impl/ping.ipp @@ -32,20 +32,20 @@ namespace websocket { It only sends the frames it does not make attempts to read any frame data. */ -template +template template -class stream::ping_op +class stream::ping_op : public boost::asio::coroutine { struct state { - stream& ws; + stream& ws; detail::frame_buffer fb; token tok; state( Handler const&, - stream& ws_, + stream& ws_, detail::opcode op, ping_data const& payload) : ws(ws_) @@ -67,7 +67,7 @@ public: template ping_op( DeducedHandler&& h, - stream& ws, + stream& ws, detail::opcode op, ping_data const& payload) : d_(std::forward(h), @@ -85,7 +85,7 @@ public: } using executor_type = boost::asio::associated_executor_t< - Handler, decltype(std::declval&>().get_executor())>; + Handler, decltype(std::declval&>().get_executor())>; executor_type get_executor() const noexcept @@ -107,10 +107,10 @@ public: } }; -template +template template void -stream:: +stream:: ping_op:: operator()(error_code ec, std::size_t) { @@ -174,9 +174,9 @@ operator()(error_code ec, std::size_t) //------------------------------------------------------------------------------ -template +template void -stream:: +stream:: ping(ping_data const& payload) { error_code ec; @@ -185,9 +185,9 @@ ping(ping_data const& payload) BOOST_THROW_EXCEPTION(system_error{ec}); } -template +template void -stream:: +stream:: ping(ping_data const& payload, error_code& ec) { // Make sure the stream is open @@ -201,9 +201,9 @@ ping(ping_data const& payload, error_code& ec) return; } -template +template void -stream:: +stream:: pong(ping_data const& payload) { error_code ec; @@ -212,9 +212,9 @@ pong(ping_data const& payload) BOOST_THROW_EXCEPTION(system_error{ec}); } -template +template void -stream:: +stream:: pong(ping_data const& payload, error_code& ec) { // Make sure the stream is open @@ -228,11 +228,11 @@ pong(ping_data const& payload, error_code& ec) return; } -template +template template BOOST_ASIO_INITFN_RESULT_TYPE( WriteHandler, void(error_code)) -stream:: +stream:: async_ping(ping_data const& payload, WriteHandler&& handler) { static_assert(is_async_stream::value, @@ -246,11 +246,11 @@ async_ping(ping_data const& payload, WriteHandler&& handler) return init.result.get(); } -template +template template BOOST_ASIO_INITFN_RESULT_TYPE( WriteHandler, void(error_code)) -stream:: +stream:: async_pong(ping_data const& payload, WriteHandler&& handler) { static_assert(is_async_stream::value, diff --git a/include/boost/beast/websocket/impl/read.ipp b/include/boost/beast/websocket/impl/read.ipp index a93888eb..5489133d 100644 --- a/include/boost/beast/websocket/impl/read.ipp +++ b/include/boost/beast/websocket/impl/read.ipp @@ -35,19 +35,52 @@ namespace boost { namespace beast { namespace websocket { +namespace detail { + +template<> +inline +void +stream_base:: +inflate( + zlib::z_params& zs, + zlib::Flush flush, + error_code& ec) +{ + this->pmd_->zi.write(zs, flush, ec); +} + +template<> +inline +void +stream_base:: +do_context_takeover_read(role_type role) +{ + if((role == role_type::client && + pmd_config_.server_no_context_takeover) || + (role == role_type::server && + pmd_config_.client_no_context_takeover)) + { + pmd_->zi.reset(); + } +} + +} // detail + +//------------------------------------------------------------------------------ + /* Read some message frame data. Also reads and handles control frames. */ -template +template template< class MutableBufferSequence, class Handler> -class stream::read_some_op +class stream::read_some_op : public boost::asio::coroutine { Handler h_; - stream& ws_; + stream& ws_; MutableBufferSequence bs_; buffers_suffix cb_; std::size_t bytes_written_ = 0; @@ -64,7 +97,7 @@ public: template read_some_op( DeducedHandler&& h, - stream& ws, + stream& ws, MutableBufferSequence const& bs) : h_(std::forward(h)) , ws_(ws) @@ -85,7 +118,7 @@ public: } using executor_type = boost::asio::associated_executor_t< - Handler, decltype(std::declval&>().get_executor())>; + Handler, decltype(std::declval&>().get_executor())>; executor_type get_executor() const noexcept @@ -114,10 +147,10 @@ public: } }; -template +template template void -stream:: +stream:: read_some_op:: operator()( error_code ec, @@ -404,7 +437,7 @@ operator()( } ws_.rd_done_ = false; } - if(! ws_.pmd_ || ! ws_.pmd_->rd_set) + if(! ws_.rd_deflated()) { if(ws_.rd_remain_ > 0) { @@ -546,7 +579,7 @@ operator()( 0x00, 0x00, 0xff, 0xff }; zs.next_in = empty_block; zs.avail_in = sizeof(empty_block); - ws_.pmd_->zi.write(zs, zlib::Flush::sync, ec); + ws_.inflate(zs, zlib::Flush::sync, ec); if(! ec) { // https://github.com/madler/zlib/issues/280 @@ -555,12 +588,7 @@ operator()( } if(! ws_.check_ok(ec)) goto upcall; - if( - (ws_.role_ == role_type::client && - ws_.pmd_config_.server_no_context_takeover) || - (ws_.role_ == role_type::server && - ws_.pmd_config_.client_no_context_takeover)) - ws_.pmd_->zi.reset(); + ws_.do_context_takeover_read(ws_.role_); ws_.rd_done_ = true; break; } @@ -568,7 +596,7 @@ operator()( { break; } - ws_.pmd_->zi.write(zs, zlib::Flush::sync, ec); + ws_.inflate(zs, zlib::Flush::sync, ec); if(! ws_.check_ok(ec)) goto upcall; if(ws_.rd_msg_max_ && beast::detail::sum_exceeds( @@ -699,15 +727,15 @@ operator()( //------------------------------------------------------------------------------ -template +template template< class DynamicBuffer, class Handler> -class stream::read_op +class stream::read_op : public boost::asio::coroutine { Handler h_; - stream& ws_; + stream& ws_; DynamicBuffer& b_; std::size_t limit_; std::size_t bytes_written_ = 0; @@ -723,7 +751,7 @@ public: template read_op( DeducedHandler&& h, - stream& ws, + stream& ws, DynamicBuffer& b, std::size_t limit, bool some) @@ -743,7 +771,7 @@ public: } using executor_type = boost::asio::associated_executor_t< - Handler, decltype(std::declval&>().get_executor())>; + Handler, decltype(std::declval&>().get_executor())>; executor_type get_executor() const noexcept @@ -765,10 +793,10 @@ public: } }; -template +template template void -stream:: +stream:: read_op:: operator()( error_code ec, @@ -816,10 +844,10 @@ operator()( //------------------------------------------------------------------------------ -template +template template std::size_t -stream:: +stream:: read(DynamicBuffer& buffer) { static_assert(is_sync_stream::value, @@ -834,10 +862,10 @@ read(DynamicBuffer& buffer) return bytes_written; } -template +template template std::size_t -stream:: +stream:: read(DynamicBuffer& buffer, error_code& ec) { static_assert(is_sync_stream::value, @@ -856,11 +884,11 @@ read(DynamicBuffer& buffer, error_code& ec) return bytes_written; } -template +template template BOOST_ASIO_INITFN_RESULT_TYPE( ReadHandler, void(error_code, std::size_t)) -stream:: +stream:: async_read(DynamicBuffer& buffer, ReadHandler&& handler) { static_assert(is_async_stream::value, @@ -884,10 +912,10 @@ async_read(DynamicBuffer& buffer, ReadHandler&& handler) //------------------------------------------------------------------------------ -template +template template std::size_t -stream:: +stream:: read_some( DynamicBuffer& buffer, std::size_t limit) @@ -905,10 +933,10 @@ read_some( return bytes_written; } -template +template template std::size_t -stream:: +stream:: read_some( DynamicBuffer& buffer, std::size_t limit, @@ -941,11 +969,11 @@ read_some( return bytes_written; } -template +template template BOOST_ASIO_INITFN_RESULT_TYPE( ReadHandler, void(error_code, std::size_t)) -stream:: +stream:: async_read_some( DynamicBuffer& buffer, std::size_t limit, @@ -972,10 +1000,10 @@ async_read_some( //------------------------------------------------------------------------------ -template +template template std::size_t -stream:: +stream:: read_some( MutableBufferSequence const& buffers) { @@ -991,10 +1019,10 @@ read_some( return bytes_written; } -template +template template std::size_t -stream:: +stream:: read_some( MutableBufferSequence const& buffers, error_code& ec) @@ -1125,7 +1153,7 @@ loop: { ec.assign(0, ec.category()); } - if(! pmd_ || ! pmd_->rd_set) + if(! this->rd_deflated()) { if(rd_remain_ > 0) { @@ -1273,7 +1301,7 @@ loop: 0x00, 0x00, 0xff, 0xff }; zs.next_in = empty_block; zs.avail_in = sizeof(empty_block); - pmd_->zi.write(zs, zlib::Flush::sync, ec); + this->inflate(zs, zlib::Flush::sync, ec); if(! ec) { // https://github.com/madler/zlib/issues/280 @@ -1282,12 +1310,7 @@ loop: } if(! check_ok(ec)) return bytes_written; - if( - (role_ == role_type::client && - pmd_config_.server_no_context_takeover) || - (role_ == role_type::server && - pmd_config_.client_no_context_takeover)) - pmd_->zi.reset(); + this->do_context_takeover_read(role_); rd_done_ = true; break; } @@ -1295,7 +1318,7 @@ loop: { break; } - pmd_->zi.write(zs, zlib::Flush::sync, ec); + this->inflate(zs, zlib::Flush::sync, ec); if(! check_ok(ec)) return bytes_written; if(rd_msg_max_ && beast::detail::sum_exceeds( @@ -1328,11 +1351,11 @@ loop: return bytes_written; } -template +template template BOOST_ASIO_INITFN_RESULT_TYPE( ReadHandler, void(error_code, std::size_t)) -stream:: +stream:: async_read_some( MutableBufferSequence const& buffers, ReadHandler&& handler) diff --git a/include/boost/beast/websocket/impl/stream.ipp b/include/boost/beast/websocket/impl/stream.ipp index f8e4a5fc..e307c76d 100644 --- a/include/boost/beast/websocket/impl/stream.ipp +++ b/include/boost/beast/websocket/impl/stream.ipp @@ -40,9 +40,9 @@ namespace boost { namespace beast { namespace websocket { -template +template template -stream:: +stream:: stream(Args&&... args) : stream_(std::forward(args)...) , tok_(1) @@ -51,16 +51,221 @@ stream(Args&&... args) max_control_frame_size); } -template +template +template std::size_t -stream:: +stream:: +read_size_hint(DynamicBuffer& buffer) const +{ + static_assert( + boost::asio::is_dynamic_buffer::value, + "DynamicBuffer requirements not met"); + auto const initial_size = (std::min)( + +tcp_frame_size, + buffer.max_size() - buffer.size()); + if(initial_size == 0) + return 1; // buffer is full + return read_size_hint(initial_size); +} + +//------------------------------------------------------------------------------ + +template +void +stream:: +set_option(permessage_deflate const& o, std::true_type) +{ + if( o.server_max_window_bits > 15 || + o.server_max_window_bits < 9) + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "invalid server_max_window_bits"}); + if( o.client_max_window_bits > 15 || + o.client_max_window_bits < 9) + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "invalid client_max_window_bits"}); + if( o.compLevel < 0 || + o.compLevel > 9) + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "invalid compLevel"}); + if( o.memLevel < 1 || + o.memLevel > 9) + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "invalid memLevel"}); + this->pmd_opts_ = o; +} + +template +void +stream:: +set_option(permessage_deflate const& o, std::false_type) +{ + if(o.client_enable || o.server_enable) + { + // Can't enable permessage-deflate + // when deflateSupported == false. + // + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "deflateSupported == false"}); + } +} + +template +void +stream:: +open(role_type role) +{ + // VFALCO TODO analyze and remove dupe code in reset() + role_ = role; + status_ = status::open; + rd_remain_ = 0; + rd_cont_ = false; + rd_done_ = true; + // Can't clear this because accept uses it + //rd_buf_.reset(); + rd_fh_.fin = false; + rd_close_ = false; + wr_close_ = false; + wr_block_.reset(); + rd_block_.reset(); + cr_.code = close_code::none; + + wr_cont_ = false; + wr_buf_size_ = 0; + + open_pmd(is_deflate_supported{}); +} + +template +inline +void +stream:: +open_pmd(std::true_type) +{ + if(((role_ == role_type::client && + this->pmd_opts_.client_enable) || + (role_ == role_type::server && + this->pmd_opts_.server_enable)) && + this->pmd_config_.accept) + { + pmd_normalize(this->pmd_config_); + this->pmd_.reset(new typename + detail::stream_base::pmd_type); + if(role_ == role_type::client) + { + this->pmd_->zi.reset( + this->pmd_config_.server_max_window_bits); + this->pmd_->zo.reset( + this->pmd_opts_.compLevel, + this->pmd_config_.client_max_window_bits, + this->pmd_opts_.memLevel, + zlib::Strategy::normal); + } + else + { + this->pmd_->zi.reset( + this->pmd_config_.client_max_window_bits); + this->pmd_->zo.reset( + this->pmd_opts_.compLevel, + this->pmd_config_.server_max_window_bits, + this->pmd_opts_.memLevel, + zlib::Strategy::normal); + } + } +} + +template +void +stream:: +close() +{ + wr_buf_.reset(); + close_pmd(is_deflate_supported{}); +} + +template +void +stream:: +reset() +{ + BOOST_ASSERT(status_ != status::open); + rd_remain_ = 0; + rd_cont_ = false; + rd_done_ = true; + rd_buf_.consume(rd_buf_.size()); + rd_fh_.fin = false; + rd_close_ = false; + wr_close_ = false; + wr_cont_ = false; + wr_block_.reset(); + rd_block_.reset(); + cr_.code = close_code::none; +} + +// Called before each write frame +template +inline +void +stream:: +begin_msg(std::true_type) +{ + wr_frag_ = wr_frag_opt_; + wr_compress_ = static_cast(this->pmd_); + + // Maintain the write buffer + if( wr_compress_ || + role_ == role_type::client) + { + if(! wr_buf_ || wr_buf_size_ != wr_buf_opt_) + { + wr_buf_size_ = wr_buf_opt_; + wr_buf_ = boost::make_unique_noinit< + std::uint8_t[]>(wr_buf_size_); + } + } + else + { + wr_buf_size_ = wr_buf_opt_; + wr_buf_.reset(); + } +} + +// Called before each write frame +template +inline +void +stream:: +begin_msg(std::false_type) +{ + wr_frag_ = wr_frag_opt_; + + // Maintain the write buffer + if(role_ == role_type::client) + { + if(! wr_buf_ || wr_buf_size_ != wr_buf_opt_) + { + wr_buf_size_ = wr_buf_opt_; + wr_buf_ = boost::make_unique_noinit< + std::uint8_t[]>(wr_buf_size_); + } + } + else + { + wr_buf_size_ = wr_buf_opt_; + wr_buf_.reset(); + } +} + +template +std::size_t +stream:: read_size_hint( - std::size_t initial_size) const + std::size_t initial_size, + std::true_type) const { using beast::detail::clamp; std::size_t result; BOOST_ASSERT(initial_size > 0); - if(! pmd_ || (! rd_done_ && ! pmd_->rd_set)) + if(! this->pmd_ || (! rd_done_ && ! this->pmd_->rd_set)) { // current message is uncompressed @@ -85,164 +290,45 @@ done: return result; } -template -template +template std::size_t -stream:: -read_size_hint(DynamicBuffer& buffer) const +stream:: +read_size_hint( + std::size_t initial_size, + std::false_type) const { - static_assert( - boost::asio::is_dynamic_buffer::value, - "DynamicBuffer requirements not met"); - auto const initial_size = (std::min)( - +tcp_frame_size, - buffer.max_size() - buffer.size()); - if(initial_size == 0) - return 1; // buffer is full - return read_size_hint(initial_size); -} - -template -void -stream:: -set_option(permessage_deflate const& o) -{ - if( o.server_max_window_bits > 15 || - o.server_max_window_bits < 9) - BOOST_THROW_EXCEPTION(std::invalid_argument{ - "invalid server_max_window_bits"}); - if( o.client_max_window_bits > 15 || - o.client_max_window_bits < 9) - BOOST_THROW_EXCEPTION(std::invalid_argument{ - "invalid client_max_window_bits"}); - if( o.compLevel < 0 || - o.compLevel > 9) - BOOST_THROW_EXCEPTION(std::invalid_argument{ - "invalid compLevel"}); - if( o.memLevel < 1 || - o.memLevel > 9) - BOOST_THROW_EXCEPTION(std::invalid_argument{ - "invalid memLevel"}); - pmd_opts_ = o; -} - -//------------------------------------------------------------------------------ - -template -void -stream:: -open(role_type role) -{ - // VFALCO TODO analyze and remove dupe code in reset() - role_ = role; - status_ = status::open; - rd_remain_ = 0; - rd_cont_ = false; - rd_done_ = true; - // Can't clear this because accept uses it - //rd_buf_.reset(); - rd_fh_.fin = false; - rd_close_ = false; - wr_close_ = false; - wr_block_.reset(); - rd_block_.reset(); - cr_.code = close_code::none; - - 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) + using beast::detail::clamp; + std::size_t result; + BOOST_ASSERT(initial_size > 0); + // compression is not supported + if(rd_done_) { - 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); - } + // first message frame + result = initial_size; } -} - -template -void -stream:: -close() -{ - wr_buf_.reset(); - pmd_.reset(); -} - -template -void -stream:: -reset() -{ - BOOST_ASSERT(status_ != status::open); - rd_remain_ = 0; - rd_cont_ = false; - rd_done_ = true; - rd_buf_.consume(rd_buf_.size()); - rd_fh_.fin = false; - rd_close_ = false; - wr_close_ = false; - wr_cont_ = false; - wr_block_.reset(); - rd_block_.reset(); - cr_.code = close_code::none; -} - -// Called before each write frame -template -void -stream:: -begin_msg() -{ - wr_frag_ = wr_frag_opt_; - wr_compress_ = static_cast(pmd_); - - // Maintain the write buffer - if( wr_compress_ || - role_ == role_type::client) + else if(rd_fh_.fin) { - if(! wr_buf_ || wr_buf_size_ != wr_buf_opt_) - { - wr_buf_size_ = wr_buf_opt_; - wr_buf_ = boost::make_unique_noinit< - std::uint8_t[]>(wr_buf_size_); - } + // last message frame + BOOST_ASSERT(rd_remain_ > 0); + result = clamp(rd_remain_); } else { - wr_buf_size_ = wr_buf_opt_; - wr_buf_.reset(); + result = (std::max)( + initial_size, clamp(rd_remain_)); } + BOOST_ASSERT(result != 0); + return result; } //------------------------------------------------------------------------------ // Attempt to read a complete frame header. // Returns `false` if more bytes are needed -template +template template bool -stream:: +stream:: parse_fh( detail::frame_header& fh, DynamicBuffer& b, @@ -301,14 +387,12 @@ parse_fh( // new data frame when continuation expected return err(close_code::protocol_error); } - if((fh.rsv1 && ! pmd_) || - fh.rsv2 || fh.rsv3) + if(fh.rsv2 || fh.rsv3 || + ! this->rd_deflated(fh.rsv1)) { // reserved bits not cleared return err(close_code::protocol_error); } - if(pmd_) - pmd_->rd_set = fh.rsv1; break; case detail::opcode::cont: @@ -411,7 +495,7 @@ parse_fh( std::uint64_t>::max)() - fh.len) return err(close_code::too_big); } - if(! pmd_ || ! pmd_->rd_set) + if(! this->rd_deflated()) { if(rd_msg_max_ && beast::detail::sum_exceeds( rd_size_, fh.len, rd_msg_max_)) @@ -425,10 +509,10 @@ parse_fh( return true; } -template +template template void -stream:: +stream:: write_close(DynamicBuffer& db, close_reason const& cr) { using namespace boost::endian; @@ -479,10 +563,10 @@ write_close(DynamicBuffer& db, close_reason const& cr) } } -template +template template void -stream:: +stream:: write_ping(DynamicBuffer& db, detail::opcode code, ping_data const& data) { @@ -513,14 +597,13 @@ write_ping(DynamicBuffer& db, //------------------------------------------------------------------------------ -template +template template request_type -stream:: +stream:: build_request(detail::sec_ws_key_type& key, - string_view host, - string_view target, - Decorator const& decorator) + string_view host, string_view target, + Decorator const& decorator) { request_type req; req.target(target); @@ -532,20 +615,7 @@ build_request(detail::sec_ws_key_type& key, detail::make_sec_ws_key(key, wr_gen_); req.set(http::field::sec_websocket_key, key); req.set(http::field::sec_websocket_version, "13"); - 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, config); - } + build_request_pmd(req, is_deflate_supported{}); decorator(req); if(! req.count(http::field::user_agent)) req.set(http::field::user_agent, @@ -553,13 +623,36 @@ build_request(detail::sec_ws_key_type& key, return req; } -template +template +inline +void +stream:: +build_request_pmd(request_type& req, std::true_type) +{ + if(this->pmd_opts_.client_enable) + { + detail::pmd_offer config; + config.accept = true; + config.server_max_window_bits = + this->pmd_opts_.server_max_window_bits; + config.client_max_window_bits = + this->pmd_opts_.client_max_window_bits; + config.server_no_context_takeover = + this->pmd_opts_.server_no_context_takeover; + config.client_no_context_takeover = + this->pmd_opts_.client_no_context_takeover; + detail::pmd_write(req, config); + } +} + +template template response_type -stream:: -build_response(http::request> const& req, - Decorator const& decorator) +stream:: +build_response( + http::request> const& req, + Decorator const& decorator) { auto const decorate = [&decorator](response_type& res) @@ -614,12 +707,7 @@ build_response(http::request +template +template +inline void -stream:: -on_response(response_type const& res, - detail::sec_ws_key_type const& key, error_code& ec) +stream:: +build_response_pmd( + response_type& res, + http::request> const& req, + std::true_type) +{ + detail::pmd_offer offer; + detail::pmd_offer unused; + pmd_read(offer, req); + pmd_negotiate(res, unused, offer, this->pmd_opts_); +} + +// Called when the WebSocket Upgrade response is received +template +void +stream:: +on_response( + response_type const& res, + detail::sec_ws_key_type const& key, + error_code& ec) { bool const success = [&]() { @@ -664,18 +772,29 @@ on_response(response_type const& res, return; } ec.assign(0, ec.category()); + on_response_pmd(res, is_deflate_supported{}); + open(role_type::client); +} + +template +inline +void +stream:: +on_response_pmd( + response_type const& res, + std::true_type) +{ detail::pmd_offer offer; pmd_read(offer, res); // VFALCO see if offer satisfies pmd_config_, // return an error if not. - pmd_config_ = offer; // overwrite for now - open(role_type::client); + this->pmd_config_ = offer; // overwrite for now } // _Fail the WebSocket Connection_ -template +template void -stream:: +stream:: do_fail( std::uint16_t code, // if set, send a close frame first error_code ev, // error code to use upon success diff --git a/include/boost/beast/websocket/impl/write.ipp b/include/boost/beast/websocket/impl/write.ipp index dfb719dc..eccde945 100644 --- a/include/boost/beast/websocket/impl/write.ipp +++ b/include/boost/beast/websocket/impl/write.ipp @@ -33,13 +33,113 @@ namespace boost { namespace beast { namespace websocket { -template +namespace detail { + +// Compress a buffer sequence +// Returns: `true` if more calls are needed +// +template<> +template +bool +stream_base:: +deflate( + boost::asio::mutable_buffer& out, + buffers_suffix& cb, + bool fin, + std::size_t& total_in, + error_code& ec) +{ + using boost::asio::buffer; + BOOST_ASSERT(out.size() >= 6); + auto& zo = this->pmd_->zo; + zlib::z_params zs; + zs.avail_in = 0; + zs.next_in = nullptr; + zs.avail_out = out.size(); + zs.next_out = out.data(); + for(auto in : beast::detail::buffers_range(cb)) + { + zs.avail_in = in.size(); + if(zs.avail_in == 0) + continue; + zs.next_in = in.data(); + 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 == out.size()); + ec.assign(0, ec.category()); + break; + } + if(zs.avail_out == 0) + { + BOOST_ASSERT(zs.total_out == out.size()); + break; + } + BOOST_ASSERT(zs.avail_in == 0); + } + total_in = zs.total_in; + cb.consume(zs.total_in); + if(zs.avail_out > 0 && fin) + { + auto const remain = boost::asio::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.assign(0, ec.category()); + 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(out.data(), zs.total_out); + return false; + } + } + } + ec.assign(0, ec.category()); + out = buffer(out.data(), zs.total_out); + return true; +} + +template<> +inline +void +stream_base:: +do_context_takeover_write(role_type role) +{ + if((role == role_type::client && + this->pmd_config_.client_no_context_takeover) || + (role == role_type::server && + this->pmd_config_.server_no_context_takeover)) + { + this->pmd_->zo.reset(); + } +} + +} // detail + +//------------------------------------------------------------------------------ + +template template -class stream::write_some_op +class stream::write_some_op : public boost::asio::coroutine { Handler h_; - stream& ws_; + stream& ws_; buffers_suffix cb_; detail::frame_header fh_; detail::prepared_key key_; @@ -59,7 +159,7 @@ public: template write_some_op( DeducedHandler&& h, - stream& ws, + stream& ws, bool fin, Buffers const& bs) : h_(std::forward(h)) @@ -80,7 +180,7 @@ public: } using executor_type = boost::asio::associated_executor_t< - Handler, decltype(std::declval&>().get_executor())>; + Handler, decltype(std::declval&>().get_executor())>; executor_type get_executor() const noexcept @@ -109,10 +209,10 @@ public: } }; -template +template template void -stream:: +stream:: write_some_op:: operator()( error_code ec, @@ -397,8 +497,7 @@ operator()( { b = buffer(ws_.wr_buf_.get(), ws_.wr_buf_size_); - more_ = detail::deflate(ws_.pmd_->zo, - b, cb_, fin_, in_, ec); + more_ = ws_.deflate(b, cb_, fin_, in_, ec); if(! ws_.check_ok(ec)) goto upcall; n = buffer_size(b); @@ -450,12 +549,8 @@ operator()( } else { - if(fh_.fin && ( - (ws_.role_ == role_type::client && - ws_.pmd_config_.client_no_context_takeover) || - (ws_.role_ == role_type::server && - ws_.pmd_config_.server_no_context_takeover))) - ws_.pmd_->zo.reset(); + if(fh_.fin) + ws_.do_context_takeover_write(ws_.role_); goto upcall; } } @@ -479,10 +574,10 @@ operator()( //------------------------------------------------------------------------------ -template +template template std::size_t -stream:: +stream:: write_some(bool fin, ConstBufferSequence const& buffers) { static_assert(is_sync_stream::value, @@ -498,10 +593,10 @@ write_some(bool fin, ConstBufferSequence const& buffers) return bytes_transferred; } -template +template template std::size_t -stream:: +stream:: write_some(bool fin, ConstBufferSequence const& buffers, error_code& ec) { @@ -543,9 +638,8 @@ write_some(bool fin, { auto b = buffer( wr_buf_.get(), wr_buf_size_); - auto const more = detail::deflate( - pmd_->zo, b, cb, fin, - bytes_transferred, ec); + auto const more = this->deflate( + b, cb, fin, bytes_transferred, ec); if(! check_ok(ec)) return bytes_transferred; auto const n = buffer_size(b); @@ -581,12 +675,8 @@ write_some(bool fin, fh.op = detail::opcode::cont; fh.rsv1 = false; } - if(fh.fin && ( - (role_ == role_type::client && - pmd_config_.client_no_context_takeover) || - (role_ == role_type::server && - pmd_config_.server_no_context_takeover))) - pmd_->zo.reset(); + if(fh.fin) + this->do_context_takeover_write(role_); } else if(! fh.mask) { @@ -711,11 +801,11 @@ write_some(bool fin, return bytes_transferred; } -template +template template BOOST_ASIO_INITFN_RESULT_TYPE( WriteHandler, void(error_code, std::size_t)) -stream:: +stream:: async_write_some(bool fin, ConstBufferSequence const& bs, WriteHandler&& handler) { @@ -735,10 +825,10 @@ async_write_some(bool fin, //------------------------------------------------------------------------------ -template +template template std::size_t -stream:: +stream:: write(ConstBufferSequence const& buffers) { static_assert(is_sync_stream::value, @@ -753,10 +843,10 @@ write(ConstBufferSequence const& buffers) return bytes_transferred; } -template +template template std::size_t -stream:: +stream:: write(ConstBufferSequence const& buffers, error_code& ec) { static_assert(is_sync_stream::value, @@ -767,11 +857,11 @@ write(ConstBufferSequence const& buffers, error_code& ec) return write_some(true, buffers, ec); } -template +template template BOOST_ASIO_INITFN_RESULT_TYPE( WriteHandler, void(error_code, std::size_t)) -stream:: +stream:: async_write( ConstBufferSequence const& bs, WriteHandler&& handler) { diff --git a/include/boost/beast/websocket/stream.hpp b/include/boost/beast/websocket/stream.hpp index e3f7ba35..46090d93 100644 --- a/include/boost/beast/websocket/stream.hpp +++ b/include/boost/beast/websocket/stream.hpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -29,8 +30,6 @@ #include #include #include -#include -#include #include #include #include @@ -108,6 +107,12 @@ class frame_test; For asynchronous operations, the type must support the @b AsyncStream concept. + @tparam deflateSupported A `bool` indicating whether or not the + stream will be capable of negotiating the permessage-deflate websocket + extension. Note that even if this is set to `true`, the permessage + deflate options (set by the caller at runtime) must still have the + feature enabled for a successful negotiation to occur. + @note A stream object must not be moved or destroyed while there are pending asynchronous operations associated with it. @@ -116,8 +121,13 @@ class frame_test; @b DynamicBuffer, @b SyncStream */ -template +template< + class NextLayer, + bool deflateSupported> class stream +#ifndef BOOST_BEAST_DOXYGEN + : private detail::stream_base +#endif { friend class close_test; friend class frame_test; @@ -126,7 +136,7 @@ class stream friend class read2_test; friend class stream_test; friend class write_test; - + /* The read buffer has to be at least as large as the largest possible control frame including the frame header. @@ -134,8 +144,6 @@ class stream static std::size_t constexpr max_control_frame_size = 2 + 8 + 4 + 125; static std::size_t constexpr tcp_frame_size = 1536; - struct op {}; - using control_cb_type = std::function; @@ -154,16 +162,6 @@ class stream void reset() { id_ = 0; } }; - // State information for the permessage-deflate extension - struct pmd_t - { - // `true` if current read message is compressed - bool rd_set = false; - - zlib::deflate_stream zo; - zlib::inflate_stream zi; - }; - enum class status { open, @@ -231,14 +229,14 @@ class stream detail::pausation paused_wr_; // paused write op detail::pausation paused_ping_; // paused ping op detail::pausation paused_close_; // paused close op - detail::pausation paused_r_rd_; // paused read op (read) - detail::pausation paused_r_close_;// paused close op (read) - - std::unique_ptr pmd_; // pmd settings or nullptr - permessage_deflate pmd_opts_; // local pmd options - detail::pmd_offer pmd_config_; // offer (client) or negotiation (server) + detail::pausation paused_r_rd_; // paused read op (async read) + detail::pausation paused_r_close_;// paused close op (async read) public: + /// Indicates if the permessage-deflate extension is supported + using is_deflate_supported = + std::integral_constant; + /// The type of the next layer. using next_layer_type = typename std::remove_reference::type; @@ -444,7 +442,11 @@ public: */ std::size_t read_size_hint( - std::size_t initial_size = +tcp_frame_size) const; + std::size_t initial_size = +tcp_frame_size) const + { + return read_size_hint(initial_size, + is_deflate_supported{}); + } /** Returns a suggested maximum buffer size for the next call to read. @@ -475,15 +477,22 @@ public: // //-------------------------------------------------------------------------- - /// Set the permessage-deflate extension options + /** Set the permessage-deflate extension options + + @throws invalid_argument if `deflateSupported == false`, and either + `client_enable` or `server_enable` is `true`. + */ void - set_option(permessage_deflate const& o); + set_option(permessage_deflate const& o) + { + set_option(o, is_deflate_supported{}); + } /// Get the permessage-deflate extension options void get_option(permessage_deflate& o) { - o = pmd_opts_; + get_option(o, is_deflate_supported{}); } /** Set the automatic fragmentation option. @@ -2353,8 +2362,8 @@ public: next layer's `async_write_some` functions, and is known as a composed operation. The program must ensure that the stream performs no other write operations (such as @ref async_ping, - @ref stream::async_write, @ref stream::async_write_some, or - @ref stream::async_close) until this operation completes. + @ref async_write, @ref async_write_some, or @ref async_close) + until this operation completes. If the close reason specifies a close code other than @ref beast::websocket::close_code::none, the close frame is @@ -3172,8 +3181,8 @@ public: to the next layer's `async_write_some` functions, and is known as a composed operation. The program must ensure that the stream performs no other write operations (such as - stream::async_write, stream::async_write_some, or - stream::async_close). + @ref async_write, @ref async_write_some, or + @ref async_close). The current setting of the @ref binary option controls whether the message opcode is set to text or binary. If the @@ -3301,8 +3310,8 @@ public: as a composed operation. The actual payload sent may be transformed as per the WebSocket protocol settings. The program must ensure that the stream performs no other write - operations (such as stream::async_write, stream::async_write_some, - or stream::async_close). + operations (such as @ref async_write, @ref async_write_some, + or @ref async_close). If this is the beginning of a new message, the message opcode will be set to text or binary as per the current setting of @@ -3351,10 +3360,65 @@ private: static void default_decorate_req(request_type&) {} static void default_decorate_res(response_type&) {} + void + set_option(permessage_deflate const& o, std::true_type); + + void + set_option(permessage_deflate const&, std::false_type); + + void + get_option(permessage_deflate& o, std::true_type) + { + o = this->pmd_opts_; + } + + void + get_option(permessage_deflate& o, std::false_type) + { + o = {}; + o.client_enable = false; + o.server_enable = false; + } + void open(role_type role); + + void open_pmd(std::true_type); + + void open_pmd(std::false_type) + { + } + void close(); + + void close_pmd(std::true_type) + { + this->pmd_.reset(); + } + + void close_pmd(std::false_type) + { + } + void reset(); - void begin_msg(); + + void begin_msg() + { + begin_msg(is_deflate_supported{}); + } + + void begin_msg(std::true_type); + + void begin_msg(std::false_type); + + std::size_t + read_size_hint( + std::size_t initial_size, + std::true_type) const; + + std::size_t + read_size_hint( + std::size_t initial_size, + std::false_type) const; bool check_open(error_code& ec) @@ -3394,6 +3458,10 @@ private: write_ping(DynamicBuffer& b, detail::opcode op, ping_data const& data); + // + // upgrade + // + template request_type build_request(detail::sec_ws_key_type& key, @@ -3401,28 +3469,94 @@ private: string_view target, Decorator const& decorator); - template - response_type - build_response(http::request> const& req, - Decorator const& decorator); + void + build_request_pmd(request_type& req, std::true_type); void - on_response(response_type const& resp, - detail::sec_ws_key_type const& key, error_code& ec); + build_request_pmd(request_type&, std::false_type) + { + } + + template< + class Body, class Allocator, class Decorator> + response_type + build_response( + http::request> const& req, + Decorator const& decorator); + + template + void + build_response_pmd( + response_type& res, + http::request> const& req, + std::true_type); + + template + void + build_response_pmd( + response_type&, + http::request> const&, + std::false_type) + { + } + + void + on_response( + response_type const& res, + detail::sec_ws_key_type const& key, + error_code& ec); + + void + on_response_pmd( + response_type const& res, + std::true_type); + + void + on_response_pmd( + response_type const&, + std::false_type) + { + } + + // + // accept / handshake + // + + template + void + do_pmd_config( + http::basic_fields const& h, + std::true_type) + { + pmd_read(this->pmd_config_, h); + } + + template + void + do_pmd_config( + http::basic_fields const&, + std::false_type) + { + } template void - do_accept(Decorator const& decorator, + do_accept( + Decorator const& decorator, error_code& ec); - template void - do_accept(http::request> const& req, - Decorator const& decorator, error_code& ec); + do_accept( + http::request> const& req, + Decorator const& decorator, + error_code& ec); template void @@ -3431,6 +3565,10 @@ private: RequestDecorator const& decorator, error_code& ec); + // + // fail + // + void do_fail( std::uint16_t code, diff --git a/include/boost/beast/websocket/stream_fwd.hpp b/include/boost/beast/websocket/stream_fwd.hpp index d8e9778b..97ffcb49 100644 --- a/include/boost/beast/websocket/stream_fwd.hpp +++ b/include/boost/beast/websocket/stream_fwd.hpp @@ -17,7 +17,8 @@ namespace beast { namespace websocket { template< - class NextLayer> + class NextLayer, + bool deflateSupported = true> class stream; } // websocket diff --git a/test/beast/websocket/read1.cpp b/test/beast/websocket/read1.cpp index 1de53099..6bb26af0 100644 --- a/test/beast/websocket/read1.cpp +++ b/test/beast/websocket/read1.cpp @@ -23,11 +23,11 @@ namespace websocket { class read1_test : public websocket_test_suite { public: - template + template void doReadTest( Wrap const& w, - ws_type& ws, + ws_type_t& ws, close_code code) { try @@ -45,11 +45,11 @@ public: } } - template + template void doFailTest( Wrap const& w, - ws_type& ws, + ws_type_t& ws, error_code ev) { try @@ -65,7 +65,7 @@ public: } } - template + template void doTestRead(Wrap const& w) { @@ -78,7 +78,7 @@ public: // already closed { echo_server es{log}; - stream ws{ioc_}; + stream ws{ioc_}; ws.next_layer().connect(es.stream()); ws.handshake("localhost", "/"); ws.close({}); @@ -97,7 +97,8 @@ public: } // empty, fragmented message - doTest(pmd, [&](ws_type& ws) + doTest(pmd, + [&](ws_type_t& ws) { ws.next_layer().append( string_view( @@ -109,7 +110,8 @@ public: // two part message // triggers "fill the read buffer first" - doTest(pmd, [&](ws_type& ws) + doTest(pmd, + [&](ws_type_t& ws) { w.write_raw(ws, sbuf( "\x01\x81\xff\xff\xff\xff")); @@ -123,7 +125,8 @@ public: }); // ping - doTest(pmd, [&](ws_type& ws) + doTest(pmd, + [&](ws_type_t& ws) { put(ws.next_layer().buffer(), cbuf( 0x89, 0x00)); @@ -144,7 +147,8 @@ public: }); // ping - doTest(pmd, [&](ws_type& ws) + doTest(pmd, + [&](ws_type_t& ws) { put(ws.next_layer().buffer(), cbuf( 0x88, 0x00)); @@ -161,7 +165,8 @@ public: }); // ping then message - doTest(pmd, [&](ws_type& ws) + doTest(pmd, + [&](ws_type_t& ws) { bool once = false; ws.control_callback( @@ -183,7 +188,8 @@ public: }); // ping then fragmented message - doTest(pmd, [&](ws_type& ws) + doTest(pmd, + [&](ws_type_t& ws) { bool once = false; ws.control_callback( @@ -208,7 +214,7 @@ public: doStreamLoop([&](test::stream& ts) { echo_server es{log, kind::async_client}; - ws_type ws{ts}; + ws_type_t ws{ts}; ws.next_layer().connect(es.stream()); ws.set_option(pmd); es.async_handshake(); @@ -237,7 +243,7 @@ public: { echo_server es{log, kind::async}; boost::asio::io_context ioc; - stream ws{ioc, fc}; + stream ws{ioc, fc}; ws.next_layer().connect(es.stream()); ws.handshake("localhost", "/"); // Cause close to be received @@ -257,7 +263,8 @@ public: }); // already closed - doTest(pmd, [&](ws_type& ws) + doTest(pmd, + [&](ws_type_t& ws) { w.close(ws, {}); multi_buffer b; @@ -266,7 +273,8 @@ public: }); // buffer overflow - doTest(pmd, [&](ws_type& ws) + doTest(pmd, + [&](ws_type_t& ws) { std::string const s = "Hello, world!"; ws.auto_fragment(false); @@ -286,7 +294,8 @@ public: }); // bad utf8, big - doTest(pmd, [&](ws_type& ws) + doTest(pmd, + [&](ws_type_t& ws) { auto const s = std::string(2000, '*') + random_string(); @@ -296,7 +305,8 @@ public: }); // invalid fixed frame header - doTest(pmd, [&](ws_type& ws) + doTest(pmd, + [&](ws_type_t& ws) { w.write_raw(ws, cbuf( 0x8f, 0x80, 0xff, 0xff, 0xff, 0xff)); @@ -304,7 +314,8 @@ public: }); // bad close - doTest(pmd, [&](ws_type& ws) + doTest(pmd, + [&](ws_type_t& ws) { put(ws.next_layer().buffer(), cbuf( 0x88, 0x02, 0x03, 0xed)); @@ -312,7 +323,8 @@ public: }); // message size above 2^64 - doTest(pmd, [&](ws_type& ws) + doTest(pmd, + [&](ws_type_t& ws) { w.write_some(ws, false, sbuf("*")); w.write_raw(ws, cbuf( @@ -322,7 +334,8 @@ public: }); // message size exceeds max - doTest(pmd, [&](ws_type& ws) + doTest(pmd, + [&](ws_type_t& ws) { ws.read_message_max(1); w.write(ws, sbuf("**")); @@ -330,7 +343,8 @@ public: }); // bad utf8 - doTest(pmd, [&](ws_type& ws) + doTest(pmd, + [&](ws_type_t& ws) { put(ws.next_layer().buffer(), cbuf( 0x81, 0x06, 0x03, 0xea, 0xf0, 0x28, 0x8c, 0xbc)); @@ -338,7 +352,8 @@ public: }); // incomplete utf8 - doTest(pmd, [&](ws_type& ws) + doTest(pmd, + [&](ws_type_t& ws) { std::string const s = "Hello, world!" "\xc0"; @@ -347,7 +362,8 @@ public: }); // incomplete utf8, big - doTest(pmd, [&](ws_type& ws) + doTest(pmd, + [&](ws_type_t& ws) { std::string const s = "\x81\x7e\x0f\xa1" + @@ -375,7 +391,7 @@ public: [&](error_code ev, string_view s) { echo_server es{log}; - stream ws{ioc_}; + stream ws{ioc_}; ws.next_layer().connect(es.stream()); w.handshake(ws, "localhost", "/"); ws.next_layer().append(s); @@ -410,11 +426,15 @@ public: check(error::closed, "\x88\x06\xfc\x15utf8"); } + } - // - // permessage-deflate - // + template + void + doTestReadDeflate(Wrap const& w) + { + using boost::asio::buffer; + permessage_deflate pmd; pmd.client_enable = true; pmd.server_enable = true; pmd.client_max_window_bits = 9; @@ -422,7 +442,8 @@ public: pmd.compLevel = 1; // message size limit - doTest(pmd, [&](ws_type& ws) + doTest(pmd, + [&](ws_type_t& ws) { std::string const s = std::string(128, '*'); w.write(ws, buffer(s)); @@ -431,7 +452,8 @@ public: }); // invalid inflate block - doTest(pmd, [&](ws_type& ws) + doTest(pmd, + [&](ws_type_t& ws) { auto const& s = random_string(); ws.binary(true); @@ -458,7 +480,8 @@ public: // no_context_takeover pmd.server_no_context_takeover = true; - doTest(pmd, [&](ws_type& ws) + doTest(pmd, + [&](ws_type_t& ws) { auto const& s = random_string(); ws.binary(true); @@ -587,10 +610,14 @@ public: { using boost::asio::buffer; - doTestRead(SyncClient{}); + doTestRead(SyncClient{}); + doTestRead(SyncClient{}); + doTestReadDeflate(SyncClient{}); yield_to([&](yield_context yield) { - doTestRead(AsyncClient{yield}); + doTestRead(AsyncClient{yield}); + doTestRead(AsyncClient{yield}); + doTestReadDeflate(AsyncClient{yield}); }); permessage_deflate pmd; diff --git a/test/beast/websocket/stream.cpp b/test/beast/websocket/stream.cpp index 1367b567..2bac48ea 100644 --- a/test/beast/websocket/stream.cpp +++ b/test/beast/websocket/stream.cpp @@ -122,6 +122,8 @@ public: BOOST_STATIC_ASSERT(! std::is_move_assignable< stream>::value); + log << "sizeof(websocket::stream_base) == " << + sizeof(websocket::detail::stream_base) << std::endl; log << "sizeof(websocket::stream) == " << sizeof(websocket::stream) << std::endl; diff --git a/test/beast/websocket/test.hpp b/test/beast/websocket/test.hpp index 739657eb..579c6b5b 100644 --- a/test/beast/websocket/test.hpp +++ b/test/beast/websocket/test.hpp @@ -35,6 +35,10 @@ class websocket_test_suite , public test::enable_yield_to { public: + template + using ws_type_t = + websocket::stream; + using ws_type = websocket::stream; @@ -303,7 +307,7 @@ public: , limit); } - template + template void doTest( permessage_deflate const& pmd, @@ -320,7 +324,7 @@ public: { test::fail_counter fc{n}; test::stream ts{ioc_, fc}; - ws_type ws{ts}; + ws_type_t ws{ts}; ws.set_option(pmd); echo_server es{log, i==1 ? @@ -481,146 +485,171 @@ public: struct SyncClient { - template + template void - accept(stream& ws) const + accept( + stream& ws) const { ws.accept(); } - template + template< + class NextLayer, bool deflateSupported, + class Buffers> typename std::enable_if< ! http::detail::is_header::value>::type - accept(stream& ws, + accept(stream& ws, Buffers const& buffers) const { ws.accept(buffers); } - template + template void - accept(stream& ws, + accept( + stream& ws, http::request const& req) const { ws.accept(req); } - template + template< + class NextLayer, bool deflateSupported, + class Decorator> void - accept_ex(stream& ws, + accept_ex( + stream& ws, Decorator const& d) const { ws.accept_ex(d); } - template typename std::enable_if< ! http::detail::is_header::value>::type - accept_ex(stream& ws, + accept_ex( + stream& ws, Buffers const& buffers, - Decorator const& d) const + Decorator const& d) const { ws.accept_ex(buffers, d); } - template + template< + class NextLayer, bool deflateSupported, + class Decorator> void - accept_ex(stream& ws, + accept_ex( + stream& ws, http::request const& req, - Decorator const& d) const + Decorator const& d) const { ws.accept_ex(req, d); } - template void - accept_ex(stream& ws, + accept_ex( + stream& ws, http::request const& req, - Buffers const& buffers, - Decorator const& d) const + Buffers const& buffers, + Decorator const& d) const { ws.accept_ex(req, buffers, d); } - template + template void - handshake(stream& ws, + handshake( + stream& ws, string_view uri, - string_view path) const + string_view path) const { ws.handshake(uri, path); } - template + template void - handshake(stream& ws, + handshake( + stream& ws, response_type& res, - string_view uri, - string_view path) const + string_view uri, + string_view path) const { ws.handshake(res, uri, path); } - template + template< + class NextLayer, bool deflateSupported, + class Decorator> void - handshake_ex(stream& ws, + handshake_ex( + stream& ws, string_view uri, - string_view path, - Decorator const& d) const + string_view path, + Decorator const& d) const { ws.handshake_ex(uri, path, d); } - template + template< + class NextLayer, bool deflateSupported, + class Decorator> void - handshake_ex(stream& ws, + handshake_ex( + stream& ws, response_type& res, - string_view uri, - string_view path, - Decorator const& d) const + string_view uri, + string_view path, + Decorator const& d) const { ws.handshake_ex(res, uri, path, d); } - template + template void - ping(stream& ws, + ping(stream& ws, ping_data const& payload) const { ws.ping(payload); } - template + template void - pong(stream& ws, + pong(stream& ws, ping_data const& payload) const { ws.pong(payload); } - template + template void - close(stream& ws, + close(stream& ws, close_reason const& cr) const { ws.close(cr); } template< - class NextLayer, class DynamicBuffer> + class NextLayer, bool deflateSupported, + class DynamicBuffer> std::size_t - read(stream& ws, + read(stream& ws, DynamicBuffer& buffer) const { return ws.read(buffer); } template< - class NextLayer, class DynamicBuffer> + class NextLayer, bool deflateSupported, + class DynamicBuffer> std::size_t - read_some(stream& ws, + read_some( + stream& ws, std::size_t limit, DynamicBuffer& buffer) const { @@ -628,36 +657,45 @@ public: } template< - class NextLayer, class MutableBufferSequence> + class NextLayer, bool deflateSupported, + class MutableBufferSequence> std::size_t - read_some(stream& ws, + read_some( + stream& ws, MutableBufferSequence const& buffers) const { return ws.read_some(buffers); } template< - class NextLayer, class ConstBufferSequence> + class NextLayer, bool deflateSupported, + class ConstBufferSequence> std::size_t - write(stream& ws, + write( + stream& ws, ConstBufferSequence const& buffers) const { return ws.write(buffers); } template< - class NextLayer, class ConstBufferSequence> + class NextLayer, bool deflateSupported, + class ConstBufferSequence> std::size_t - write_some(stream& ws, bool fin, + write_some( + stream& ws, + bool fin, ConstBufferSequence const& buffers) const { return ws.write_some(fin, buffers); } template< - class NextLayer, class ConstBufferSequence> + class NextLayer, bool deflateSupported, + class ConstBufferSequence> std::size_t - write_raw(stream& ws, + write_raw( + stream& ws, ConstBufferSequence const& buffers) const { return boost::asio::write( @@ -678,9 +716,9 @@ public: { } - template + template void - accept(stream& ws) const + accept(stream& ws) const { error_code ec; ws.async_accept(yield_[ec]); @@ -688,10 +726,13 @@ public: throw system_error{ec}; } - template + template< + class NextLayer, bool deflateSupported, + class Buffers> typename std::enable_if< ! http::detail::is_header::value>::type - accept(stream& ws, + accept( + stream& ws, Buffers const& buffers) const { error_code ec; @@ -700,9 +741,10 @@ public: throw system_error{ec}; } - template + template void - accept(stream& ws, + accept( + stream& ws, http::request const& req) const { error_code ec; @@ -711,10 +753,12 @@ public: throw system_error{ec}; } - template void - accept_ex(stream& ws, + accept_ex( + stream& ws, Decorator const& d) const { error_code ec; @@ -723,13 +767,15 @@ public: throw system_error{ec}; } - template typename std::enable_if< ! http::detail::is_header::value>::type - accept_ex(stream& ws, + accept_ex( + stream& ws, Buffers const& buffers, - Decorator const& d) const + Decorator const& d) const { error_code ec; ws.async_accept_ex(buffers, d, yield_[ec]); @@ -737,11 +783,14 @@ public: throw system_error{ec}; } - template + template< + class NextLayer, bool deflateSupported, + class Decorator> void - accept_ex(stream& ws, + accept_ex( + stream& ws, http::request const& req, - Decorator const& d) const + Decorator const& d) const { error_code ec; ws.async_accept_ex(req, d, yield_[ec]); @@ -749,13 +798,15 @@ public: throw system_error{ec}; } - template void - accept_ex(stream& ws, + accept_ex( + stream& ws, http::request const& req, - Buffers const& buffers, - Decorator const& d) const + Buffers const& buffers, + Decorator const& d) const { error_code ec; ws.async_accept_ex( @@ -764,11 +815,13 @@ public: throw system_error{ec}; } - template + template< + class NextLayer, bool deflateSupported> void - handshake(stream& ws, + handshake( + stream& ws, string_view uri, - string_view path) const + string_view path) const { error_code ec; ws.async_handshake( @@ -777,12 +830,13 @@ public: throw system_error{ec}; } - template + template void - handshake(stream& ws, + handshake( + stream& ws, response_type& res, - string_view uri, - string_view path) const + string_view uri, + string_view path) const { error_code ec; ws.async_handshake( @@ -791,12 +845,15 @@ public: throw system_error{ec}; } - template + template< + class NextLayer, bool deflateSupported, + class Decorator> void - handshake_ex(stream& ws, + handshake_ex( + stream& ws, string_view uri, - string_view path, - Decorator const &d) const + string_view path, + Decorator const &d) const { error_code ec; ws.async_handshake_ex( @@ -805,13 +862,16 @@ public: throw system_error{ec}; } - template + template< + class NextLayer, bool deflateSupported, + class Decorator> void - handshake_ex(stream& ws, + handshake_ex( + stream& ws, response_type& res, - string_view uri, - string_view path, - Decorator const &d) const + string_view uri, + string_view path, + Decorator const &d) const { error_code ec; ws.async_handshake_ex( @@ -820,9 +880,10 @@ public: throw system_error{ec}; } - template + template void - ping(stream& ws, + ping( + stream& ws, ping_data const& payload) const { error_code ec; @@ -831,9 +892,10 @@ public: throw system_error{ec}; } - template + template void - pong(stream& ws, + pong( + stream& ws, ping_data const& payload) const { error_code ec; @@ -842,9 +904,10 @@ public: throw system_error{ec}; } - template + template void - close(stream& ws, + close( + stream& ws, close_reason const& cr) const { error_code ec; @@ -854,9 +917,11 @@ public: } template< - class NextLayer, class DynamicBuffer> + class NextLayer, bool deflateSupported, + class DynamicBuffer> std::size_t - read(stream& ws, + read( + stream& ws, DynamicBuffer& buffer) const { error_code ec; @@ -868,9 +933,11 @@ public: } template< - class NextLayer, class DynamicBuffer> + class NextLayer, bool deflateSupported, + class DynamicBuffer> std::size_t - read_some(stream& ws, + read_some( + stream& ws, std::size_t limit, DynamicBuffer& buffer) const { @@ -883,9 +950,11 @@ public: } template< - class NextLayer, class MutableBufferSequence> + class NextLayer, bool deflateSupported, + class MutableBufferSequence> std::size_t - read_some(stream& ws, + read_some( + stream& ws, MutableBufferSequence const& buffers) const { error_code ec; @@ -897,9 +966,11 @@ public: } template< - class NextLayer, class ConstBufferSequence> + class NextLayer, bool deflateSupported, + class ConstBufferSequence> std::size_t - write(stream& ws, + write( + stream& ws, ConstBufferSequence const& buffers) const { error_code ec; @@ -911,9 +982,12 @@ public: } template< - class NextLayer, class ConstBufferSequence> + class NextLayer, bool deflateSupported, + class ConstBufferSequence> std::size_t - write_some(stream& ws, bool fin, + write_some( + stream& ws, + bool fin, ConstBufferSequence const& buffers) const { error_code ec; @@ -925,9 +999,11 @@ public: } template< - class NextLayer, class ConstBufferSequence> + class NextLayer, bool deflateSupported, + class ConstBufferSequence> std::size_t - write_raw(stream& ws, + write_raw( + stream& ws, ConstBufferSequence const& buffers) const { error_code ec; diff --git a/test/beast/websocket/write.cpp b/test/beast/websocket/write.cpp index 9642ac86..3bbf1e3a 100644 --- a/test/beast/websocket/write.cpp +++ b/test/beast/websocket/write.cpp @@ -19,7 +19,7 @@ namespace websocket { class write_test : public websocket_test_suite { public: - template + template void doTestWrite(Wrap const& w) { @@ -50,7 +50,8 @@ public: } // message - doTest(pmd, [&](ws_type& ws) + doTest(pmd, + [&](ws_type_t& ws) { ws.auto_fragment(false); ws.binary(false); @@ -63,7 +64,8 @@ public: }); // empty message - doTest(pmd, [&](ws_type& ws) + doTest(pmd, + [&](ws_type_t& ws) { ws.text(true); w.write(ws, boost::asio::const_buffer{}); @@ -74,7 +76,8 @@ public: }); // fragmented message - doTest(pmd, [&](ws_type& ws) + doTest(pmd, + [&](ws_type_t& ws) { ws.auto_fragment(false); ws.binary(false); @@ -88,7 +91,8 @@ public: }); // continuation - doTest(pmd, [&](ws_type& ws) + doTest(pmd, + [&](ws_type_t& ws) { std::string const s = "Hello"; std::size_t const chop = 3; @@ -103,7 +107,8 @@ public: }); // mask - doTest(pmd, [&](ws_type& ws) + doTest(pmd, + [&](ws_type_t& ws) { ws.auto_fragment(false); std::string const s = "Hello"; @@ -114,7 +119,8 @@ public: }); // mask (large) - doTest(pmd, [&](ws_type& ws) + doTest(pmd, + [&](ws_type_t& ws) { ws.auto_fragment(false); ws.write_buffer_size(16); @@ -126,7 +132,8 @@ public: }); // mask, autofrag - doTest(pmd, [&](ws_type& ws) + doTest(pmd, + [&](ws_type_t& ws) { ws.auto_fragment(true); std::string const s(16384, '*'); @@ -140,7 +147,7 @@ public: doStreamLoop([&](test::stream& ts) { echo_server es{log, kind::async_client}; - ws_type ws{ts}; + ws_type_t ws{ts}; ws.next_layer().connect(es.stream()); try { @@ -166,7 +173,7 @@ public: doStreamLoop([&](test::stream& ts) { echo_server es{log, kind::async_client}; - ws_type ws{ts}; + ws_type_t ws{ts}; ws.next_layer().connect(es.stream()); try { @@ -187,7 +194,15 @@ public: } ts.close(); }); + } + template + void + doTestWriteDeflate(Wrap const& w) + { + using boost::asio::buffer; + + permessage_deflate pmd; pmd.client_enable = true; pmd.server_enable = true; pmd.compLevel = 1; @@ -238,11 +253,15 @@ public: { using boost::asio::buffer; - doTestWrite(SyncClient{}); + doTestWrite(SyncClient{}); + doTestWrite(SyncClient{}); + doTestWriteDeflate(SyncClient{}); yield_to([&](yield_context yield) { - doTestWrite(AsyncClient{yield}); + doTestWrite(AsyncClient{yield}); + doTestWrite(AsyncClient{yield}); + doTestWriteDeflate(AsyncClient{yield}); }); } diff --git a/test/doc/websocket_snippets.cpp b/test/doc/websocket_snippets.cpp index 2f38506e..ec356146 100644 --- a/test/doc/websocket_snippets.cpp +++ b/test/doc/websocket_snippets.cpp @@ -57,7 +57,7 @@ boost::asio::ip::tcp::socket sock{ioc}; { //[ws_snippet_6 - std::string const host = "mywebapp.com"; + std::string const host = "example.com"; boost::asio::ip::tcp::resolver r{ioc}; stream ws{ioc}; auto const results = r.resolve(host, "ws"); @@ -310,6 +310,16 @@ struct custom_wrapper //] +//[ws_snippet_26 + +// A WebSocket stream +template< + class NextLayer, + bool deflateSupported = true> +class stream; + +//] + } // doc_ws_snippets //------------------------------------------------------------------------------