From f21358186ecad9744ed6c72618d4a4cfc36be5fb Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Wed, 13 Feb 2019 08:00:07 -0800 Subject: [PATCH] Add websocket::stream timeouts --- CHANGELOG.md | 1 + .../beast/_experimental/test/fail_count.hpp | 1 + .../beast/_experimental/test/handler.hpp | 2 +- .../beast/_experimental/test/impl/stream.hpp | 6 + .../boost/beast/_experimental/test/stream.hpp | 5 + .../boost/beast/_experimental/test/tcp.hpp | 110 ++ include/boost/beast/core/async_op_base.hpp | 2 +- include/boost/beast/core/basic_stream.hpp | 4 + .../boost/beast/websocket/detail/hybi13.hpp | 5 +- .../detail/{stream_base.hpp => impl_base.hpp} | 31 +- include/boost/beast/websocket/impl/accept.hpp | 315 ++--- include/boost/beast/websocket/impl/close.hpp | 49 +- .../boost/beast/websocket/impl/handshake.hpp | 144 +- include/boost/beast/websocket/impl/ping.hpp | 136 +- include/boost/beast/websocket/impl/read.hpp | 126 +- include/boost/beast/websocket/impl/stream.hpp | 503 +------ .../beast/websocket/impl/stream_impl.hpp | 880 +++++++++++-- include/boost/beast/websocket/impl/write.hpp | 70 +- include/boost/beast/websocket/stream.hpp | 87 +- include/boost/beast/websocket/stream_base.hpp | 139 ++ test/beast/websocket/CMakeLists.txt | 3 +- test/beast/websocket/Jamfile | 3 +- ..._stream_base.cpp => _detail_impl_base.cpp} | 4 +- test/beast/websocket/_detail_prng.cpp | 2 +- test/beast/websocket/accept.cpp | 92 +- test/beast/websocket/close.cpp | 129 +- test/beast/websocket/handshake.cpp | 113 +- test/beast/websocket/ping.cpp | 18 +- test/beast/websocket/read1.cpp | 745 ++--------- test/beast/websocket/read2.cpp | 1163 +++++++++-------- test/beast/websocket/read3.cpp | 655 ++++++++++ test/beast/websocket/stream.cpp | 45 +- test/beast/websocket/stream_explicit.cpp | 2 +- test/beast/websocket/stream_fwd.cpp | 2 +- test/beast/websocket/test.hpp | 2 +- test/beast/websocket/timer.cpp | 100 -- test/beast/websocket/write.cpp | 2 +- .../include/boost/beast/test/websocket.hpp | 2 +- 38 files changed, 3305 insertions(+), 2393 deletions(-) create mode 100644 include/boost/beast/_experimental/test/tcp.hpp rename include/boost/beast/websocket/detail/{stream_base.hpp => impl_base.hpp} (95%) create mode 100644 include/boost/beast/websocket/stream_base.hpp rename test/beast/websocket/{_detail_stream_base.cpp => _detail_impl_base.cpp} (70%) create mode 100644 test/beast/websocket/read3.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f53a582..b244fd26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ Version 216: * Refactor websocket::stream operations +* Add websocket::stream timeouts -------------------------------------------------------------------------------- diff --git a/include/boost/beast/_experimental/test/fail_count.hpp b/include/boost/beast/_experimental/test/fail_count.hpp index 5317b754..03846000 100644 --- a/include/boost/beast/_experimental/test/fail_count.hpp +++ b/include/boost/beast/_experimental/test/fail_count.hpp @@ -10,6 +10,7 @@ #ifndef BOOST_BEAST_TEST_FAIL_COUNT_HPP #define BOOST_BEAST_TEST_FAIL_COUNT_HPP +#include #include #include #include diff --git a/include/boost/beast/_experimental/test/handler.hpp b/include/boost/beast/_experimental/test/handler.hpp index de889f95..0e41d62d 100644 --- a/include/boost/beast/_experimental/test/handler.hpp +++ b/include/boost/beast/_experimental/test/handler.hpp @@ -62,7 +62,7 @@ public: template void - operator()(error_code ec, Args&&... args) + operator()(error_code ec, Args&&...) { BEAST_EXPECT(! pass_); // can't call twice BEAST_EXPECTS(! ec_ || ec == *ec_, diff --git a/include/boost/beast/_experimental/test/impl/stream.hpp b/include/boost/beast/_experimental/test/impl/stream.hpp index 3b710bdd..a916bf78 100644 --- a/include/boost/beast/_experimental/test/impl/stream.hpp +++ b/include/boost/beast/_experimental/test/impl/stream.hpp @@ -629,6 +629,12 @@ connect(stream& to) return from; } +void +connect(stream& s1, stream& s2) +{ + s1.connect(s2); +} + template stream connect(stream& to, Arg1&& arg1, ArgN&&... argn) diff --git a/include/boost/beast/_experimental/test/stream.hpp b/include/boost/beast/_experimental/test/stream.hpp index 4869c0e1..e9c1d40f 100644 --- a/include/boost/beast/_experimental/test/stream.hpp +++ b/include/boost/beast/_experimental/test/stream.hpp @@ -10,6 +10,7 @@ #ifndef BOOST_BEAST_TEST_STREAM_HPP #define BOOST_BEAST_TEST_STREAM_HPP +#include #include #include #include @@ -512,6 +513,10 @@ BOOST_BEAST_DECL stream connect(stream& to); +BOOST_BEAST_DECL +void +connect(stream& s1, stream& s2); + template stream connect(stream& to, Arg1&& arg1, ArgN&&... argn); diff --git a/include/boost/beast/_experimental/test/tcp.hpp b/include/boost/beast/_experimental/test/tcp.hpp new file mode 100644 index 00000000..ec4bb139 --- /dev/null +++ b/include/boost/beast/_experimental/test/tcp.hpp @@ -0,0 +1,110 @@ +// +// Copyright (c) 2019 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_TEST_TCP_HPP +#define BOOST_BEAST_TEST_TCP_HPP + +#include +#include +#include +#include +#include + +namespace boost { +namespace beast { +namespace test { + +/** Run an I/O context. + + This function runs and dispatches handlers on the specified + I/O context, until one of the following conditions is true: + + @li The I/O context runs out of work. +*/ +inline +void +run(net::io_context& ioc) +{ + ioc.run(); + ioc.restart(); +} + +/** Run an I/O context for a certain amount of time. + + This function runs and dispatches handlers on the specified + I/O context, until one of the following conditions is true: + + @li The I/O context runs out of work. + + @li No completions occur and the specified amount of time has elapsed. + + @param elapsed The maximum amount of time to run for. +*/ +template +void +run_for( + net::io_context& ioc, + std::chrono::duration elapsed) +{ + ioc.run_for(elapsed); + ioc.restart(); +} + +/** Connect two TCP/IP sockets together. +*/ +inline +bool +connect( + net::ip::tcp::socket& s1, + net::ip::tcp::socket& s2) + +{ + // Sockets must use the same I/O context + BOOST_ASSERT( + std::addressof(s1.get_executor().context()) == + std::addressof(s2.get_executor().context())); + auto& ioc = s1.get_executor().context(); + s1 = net::ip::tcp::socket(ioc); + s2 = net::ip::tcp::socket(ioc); + try + { + net::ip::tcp::acceptor a( + s1.get_executor().context()); + auto ep = net::ip::tcp::endpoint( + net::ip::make_address_v4("127.0.0.1"), 0); + a.open(ep.protocol()); + a.set_option( + net::socket_base::reuse_address(true)); + a.bind(ep); + a.listen(0); + ep = a.local_endpoint(); + a.async_accept(s2, test::success_handler()); + s1.async_connect(ep, test::success_handler()); + run(ioc); + if(! BEAST_EXPECT( + s1.remote_endpoint() == s2.local_endpoint())) + return false; + if(! BEAST_EXPECT( + s2.remote_endpoint() == s1.local_endpoint())) + return false; + } + catch(std::exception const& e) + { + beast::unit_test::suite::this_suite()->fail( + e.what(), __FILE__, __LINE__); + return false; + } + return true; +} + +} // test +} // beast +} // boost + +#endif diff --git a/include/boost/beast/core/async_op_base.hpp b/include/boost/beast/core/async_op_base.hpp index 07af546a..8af09e7d 100644 --- a/include/boost/beast/core/async_op_base.hpp +++ b/include/boost/beast/core/async_op_base.hpp @@ -347,8 +347,8 @@ public: } else { - wg1_.reset(); h_(std::forward(args)...); + wg1_.reset(); } } diff --git a/include/boost/beast/core/basic_stream.hpp b/include/boost/beast/core/basic_stream.hpp index 97866e00..c5a96da0 100644 --- a/include/boost/beast/core/basic_stream.hpp +++ b/include/boost/beast/core/basic_stream.hpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -195,6 +196,9 @@ class basic_stream : private detail::stream_base #endif { + static_assert(net::is_executor::value, + "Executor requirements not met"); + // friend class template declaration in a class template is ignored // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=88672 #if BOOST_WORKAROUND(BOOST_GCC, > 0) diff --git a/include/boost/beast/websocket/detail/hybi13.hpp b/include/boost/beast/websocket/detail/hybi13.hpp index 30f0ae3c..a2d7c310 100644 --- a/include/boost/beast/websocket/detail/hybi13.hpp +++ b/include/boost/beast/websocket/detail/hybi13.hpp @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include #include #include @@ -53,7 +53,8 @@ make_sec_ws_key(sec_ws_key_type& key) template void -make_sec_ws_accept(sec_ws_accept_type& accept, +make_sec_ws_accept( + sec_ws_accept_type& accept, string_view key) { BOOST_ASSERT(key.size() <= sec_ws_key_type::max_size_n); diff --git a/include/boost/beast/websocket/detail/stream_base.hpp b/include/boost/beast/websocket/detail/impl_base.hpp similarity index 95% rename from include/boost/beast/websocket/detail/stream_base.hpp rename to include/boost/beast/websocket/detail/impl_base.hpp index 9bd982bb..f6ccdae4 100644 --- a/include/boost/beast/websocket/detail/stream_base.hpp +++ b/include/boost/beast/websocket/detail/impl_base.hpp @@ -7,8 +7,8 @@ // Official repository: https://github.com/boostorg/beast // -#ifndef BOOST_BEAST_WEBSOCKET_DETAIL_STREAM_BASE_HPP -#define BOOST_BEAST_WEBSOCKET_DETAIL_STREAM_BASE_HPP +#ifndef BOOST_BEAST_WEBSOCKET_DETAIL_IMPL_BASE_HPP +#define BOOST_BEAST_WEBSOCKET_DETAIL_IMPL_BASE_HPP #include #include @@ -17,9 +17,7 @@ #include #include #include -#include #include -#include #include #include #include @@ -489,31 +487,6 @@ struct impl_base } }; -//------------------------------------------------------------------------------ - -struct stream_base -{ -protected: - enum class status - { - open, - closing, - closed, - failed - }; - - std::uint32_t - create_mask() - { - auto g = make_prng(secure_prng_); - for(;;) - if(auto key = g()) - return key; - } - - bool secure_prng_ = true; -}; - } // detail } // websocket } // beast diff --git a/include/boost/beast/websocket/impl/accept.hpp b/include/boost/beast/websocket/impl/accept.hpp index 5cfe5774..2c5f1b01 100644 --- a/include/boost/beast/websocket/impl/accept.hpp +++ b/include/boost/beast/websocket/impl/accept.hpp @@ -11,6 +11,7 @@ #define BOOST_BEAST_WEBSOCKET_IMPL_ACCEPT_IPP #include +#include #include #include #include @@ -42,7 +43,7 @@ class stream::response_op Handler, beast::executor_type> , public net::coroutine { - stream& ws_; + boost::weak_ptr wp_; error_code result_; // must come before res_ response_type& res_; @@ -53,37 +54,53 @@ public: class Decorator> response_op( Handler_&& h, - stream& ws, - http::request> const& req, - Decorator const& decorator) - : stable_async_op_base< - Handler, beast::executor_type>( - std::forward(h), ws.get_executor()) - , ws_(ws) + boost::shared_ptr const& sp, + http::request> const& req, + Decorator const& decorator, + bool cont = false) + : stable_async_op_base>( + std::forward(h), + sp->stream.get_executor()) + , wp_(sp) , res_(beast::allocate_stable(*this, - ws.build_response(req, decorator, result_))) + sp->build_response(req, decorator, result_))) { + (*this)({}, 0, cont); } void operator()( error_code ec = {}, - std::size_t bytes_transferred = 0) + std::size_t bytes_transferred = 0, + bool cont = true) { boost::ignore_unused(bytes_transferred); + auto sp = wp_.lock(); + if(! sp) + return this->invoke(cont, + net::error::operation_aborted); + auto& impl = *sp; BOOST_ASIO_CORO_REENTER(*this) { + impl.change_status(status::handshake); + impl.update_timer(this->get_executor()); + // Send response BOOST_ASIO_CORO_YIELD http::async_write( - ws_.next_layer(), res_, std::move(*this)); + impl.stream, res_, std::move(*this)); + if(impl.check_stop_now(ec)) + goto upcall; if(! ec) ec = result_; if(! ec) { - ws_.impl_->do_pmd_config(res_); - ws_.impl_->open(role_type::server); + impl.do_pmd_config(res_); + impl.open(role_type::server); } - this->invoke_now(ec); + upcall: + this->invoke(cont, ec); } } }; @@ -99,90 +116,144 @@ class stream::accept_op Handler, beast::executor_type> , public net::coroutine { - stream& ws_; + boost::weak_ptr wp_; http::request_parser& p_; Decorator d_; public: - template + template accept_op( Handler_&& h, - stream& ws, + boost::shared_ptr const& sp, + Buffers const& buffers, Decorator const& decorator) - : stable_async_op_base< - Handler, beast::executor_type>( - std::forward(h), ws.get_executor()) - , ws_(ws) + : stable_async_op_base>( + std::forward(h), + sp->stream.get_executor()) + , wp_(sp) , p_(beast::allocate_stable< http::request_parser>(*this)) , d_(decorator) { - } - - template - void run(Buffers const& buffers) - { + auto& impl = *sp; error_code ec; - auto const mb = beast::detail::dynamic_buffer_prepare( - ws_.impl_->rd_buf, buffer_size(buffers), ec, - error::buffer_overflow); - if(ec) - return (*this)(ec); - ws_.impl_->rd_buf.commit(net::buffer_copy(*mb, buffers)); + auto const mb = + beast::detail::dynamic_buffer_prepare( + impl.rd_buf, buffer_size(buffers), + ec, error::buffer_overflow); + if(! ec) + impl.rd_buf.commit( + net::buffer_copy(*mb, buffers)); (*this)(ec); } void operator()( error_code ec = {}, - std::size_t bytes_transferred = 0) + std::size_t bytes_transferred = 0, + bool cont = true) { boost::ignore_unused(bytes_transferred); - + auto sp = wp_.lock(); + if(! sp) + return this->invoke(cont, + net::error::operation_aborted); + auto& impl = *sp; BOOST_ASIO_CORO_REENTER(*this) { + impl.change_status(status::handshake); + impl.update_timer(this->get_executor()); + + // The constructor could have set ec if(ec) + goto upcall; + + BOOST_ASIO_CORO_YIELD + http::async_read(impl.stream, + impl.rd_buf, p_, std::move(*this)); + if(ec == http::error::end_of_stream) + ec = error::closed; + if(impl.check_stop_now(ec)) + goto upcall; + { - BOOST_ASIO_CORO_YIELD - net::post( - ws_.get_executor(), - beast::bind_front_handler(std::move(*this), ec)); + // Arguments from our state must be + // moved to the stack before releasing + // the handler. + auto const req = p_.release(); + auto const decorator = d_; + response_op( + this->release_handler(), + sp, req, decorator, true); + return; } - else - { - BOOST_ASIO_CORO_YIELD - http::async_read( - ws_.next_layer(), ws_.impl_->rd_buf, - p_, std::move(*this)); - if(ec == http::error::end_of_stream) - ec = error::closed; - if(! ec) - { - // Arguments from our state must be - // moved to the stack before releasing - // the handler. - auto& ws = ws_; - auto const req = p_.release(); - auto const decorator = d_; - #if 1 - return response_op{ - this->release_handler(), - ws, req, decorator}(ec); - #else - // VFALCO This *should* work but breaks - // coroutine invariants in the unit test. - // Also it calls reset() when it shouldn't. - return ws.async_accept_ex( - req, decorator, this->release_handler()); - #endif - } - } - this->invoke_now(ec); + + upcall: + this->invoke(cont, ec); } } }; //------------------------------------------------------------------------------ +template +template +void +stream:: +do_accept( + http::request> const& req, + Decorator const& decorator, + error_code& ec) +{ + impl_->change_status(status::handshake); + + error_code result; + auto const res = impl_->build_response(req, decorator, result); + http::write(impl_->stream, res, ec); + if(ec) + return; + ec = result; + if(ec) + { + // VFALCO TODO Respect keep alive setting, perform + // teardown if Connection: close. + return; + } + impl_->do_pmd_config(res); + impl_->open(role_type::server); +} + +template +template +void +stream:: +do_accept( + Buffers const& buffers, + Decorator const& decorator, + error_code& ec) +{ + impl_->reset(); + auto const mb = + beast::detail::dynamic_buffer_prepare( + impl_->rd_buf, buffer_size(buffers), ec, + error::buffer_overflow); + if(ec) + return; + impl_->rd_buf.commit(net::buffer_copy(*mb, buffers)); + + http::request_parser p; + http::read(next_layer(), impl_->rd_buf, p, ec); + if(ec == http::error::end_of_stream) + ec = error::closed; + if(ec) + return; + do_accept(p.get(), decorator, ec); +} + +//------------------------------------------------------------------------------ + template void stream:: @@ -220,8 +291,9 @@ accept(error_code& ec) { static_assert(is_sync_stream::value, "SyncStream requirements not met"); - impl_->reset(); - do_accept(&default_decorate_res, ec); + do_accept( + net::const_buffer{}, + &default_decorate_res, ec); } template @@ -235,8 +307,9 @@ accept_ex(ResponseDecorator const& decorator, error_code& ec) static_assert(detail::is_response_decorator< ResponseDecorator>::value, "ResponseDecorator requirements not met"); - impl_->reset(); - do_accept(decorator, ec); + do_accept( + net::const_buffer{}, + decorator, ec); } template @@ -295,14 +368,7 @@ accept( static_assert(net::is_const_buffer_sequence< ConstBufferSequence>::value, "ConstBufferSequence requirements not met"); - impl_->reset(); - auto const mb = beast::detail::dynamic_buffer_prepare( - impl_->rd_buf, buffer_size(buffers), ec, - error::buffer_overflow); - if(ec) - return; - impl_->rd_buf.commit(net::buffer_copy(*mb, buffers)); - do_accept(&default_decorate_res, ec); + do_accept(buffers, &default_decorate_res, ec); } template @@ -325,14 +391,7 @@ accept_ex( static_assert(net::is_const_buffer_sequence< ConstBufferSequence>::value, "ConstBufferSequence requirements not met"); - impl_->reset(); - auto const mb = beast::detail::dynamic_buffer_prepare( - impl_->rd_buf, buffer_size(buffers), ec, - error::buffer_overflow); - if(ec) - return; - impl_->rd_buf.commit(net::buffer_copy(*mb, buffers)); - do_accept(decorator, ec); + do_accept(buffers, decorator, ec); } template @@ -428,10 +487,10 @@ async_accept( accept_op< decltype(&default_decorate_res), BOOST_ASIO_HANDLER_TYPE( - AcceptHandler, void(error_code))>{ + AcceptHandler, void(error_code))>( std::move(init.completion_handler), - *this, - &default_decorate_res}({}); + impl_, net::const_buffer{}, + &default_decorate_res);; return init.result.get(); } @@ -457,10 +516,10 @@ async_accept_ex( accept_op< ResponseDecorator, BOOST_ASIO_HANDLER_TYPE( - AcceptHandler, void(error_code))>{ + AcceptHandler, void(error_code))>( std::move(init.completion_handler), - *this, - decorator}({}); + impl_, net::const_buffer{}, + decorator); return init.result.get(); } @@ -488,10 +547,9 @@ async_accept( accept_op< decltype(&default_decorate_res), BOOST_ASIO_HANDLER_TYPE( - AcceptHandler, void(error_code))>{ + AcceptHandler, void(error_code))>( std::move(init.completion_handler), - *this, - &default_decorate_res}.run(buffers); + impl_, buffers, &default_decorate_res); return init.result.get(); } @@ -524,10 +582,9 @@ async_accept_ex( accept_op< ResponseDecorator, BOOST_ASIO_HANDLER_TYPE( - AcceptHandler, void(error_code))>{ + AcceptHandler, void(error_code))>( std::move(init.completion_handler), - *this, - decorator}.run(buffers); + impl_, buffers, decorator); return init.result.get(); } @@ -549,11 +606,9 @@ async_accept( impl_->reset(); response_op< BOOST_ASIO_HANDLER_TYPE( - AcceptHandler, void(error_code))>{ + AcceptHandler, void(error_code))>( std::move(init.completion_handler), - *this, - req, - &default_decorate_res}(); + impl_, req, &default_decorate_res); return init.result.get(); } @@ -580,60 +635,12 @@ async_accept_ex( impl_->reset(); response_op< BOOST_ASIO_HANDLER_TYPE( - AcceptHandler, void(error_code))>{ + AcceptHandler, void(error_code))>( std::move(init.completion_handler), - *this, - req, - decorator}(); + impl_, req, decorator); return init.result.get(); } -//------------------------------------------------------------------------------ - -template -template -void -stream:: -do_accept( - Decorator const& decorator, - error_code& ec) -{ - http::request_parser p; - http::read(next_layer(), impl_->rd_buf, p, ec); - if(ec == http::error::end_of_stream) - ec = error::closed; - if(ec) - return; - do_accept(p.get(), decorator, ec); -} - -template -template -void -stream:: -do_accept( - http::request> const& req, - Decorator const& decorator, - error_code& ec) -{ - error_code result; - auto const res = build_response(req, decorator, result); - http::write(impl_->stream, res, ec); - if(ec) - return; - ec = result; - if(ec) - { - // VFALCO TODO Respect keep alive setting, perform - // teardown if Connection: close. - return; - } - impl_->do_pmd_config(res); - impl_->open(role_type::server); -} - } // websocket } // beast } // boost diff --git a/include/boost/beast/websocket/impl/close.hpp b/include/boost/beast/websocket/impl/close.hpp index 653dcdfe..16acb75b 100644 --- a/include/boost/beast/websocket/impl/close.hpp +++ b/include/boost/beast/websocket/impl/close.hpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -39,26 +40,28 @@ class stream::close_op Handler, beast::executor_type> , public net::coroutine { - stream& ws_; + boost::weak_ptr wp_; error_code ev_; detail::frame_buffer& fb_; public: - static constexpr int id = 4; // for soft_mutex + static constexpr int id = 5; // for soft_mutex template close_op( Handler_&& h, - stream& ws, + boost::shared_ptr const& sp, close_reason const& cr) - : stable_async_op_base< - Handler, beast::executor_type>( - std::forward(h), ws.get_executor()) - , ws_(ws) - , fb_(beast::allocate_stable(*this)) + : stable_async_op_base>( + std::forward(h), + sp->stream.get_executor()) + , wp_(sp) + , fb_(beast::allocate_stable< + detail::frame_buffer>(*this)) { // Serialize the close frame - ws.template write_close< + sp->template write_close< flat_static_buffer_base>(fb_, cr); (*this)({}, 0, false); } @@ -70,14 +73,18 @@ public: bool cont = true) { using beast::detail::clamp; - auto& impl = *ws_.impl_; + auto sp = wp_.lock(); + if(! sp) + return this->invoke(cont, + net::error::operation_aborted); + auto& impl = *sp; BOOST_ASIO_CORO_REENTER(*this) { // Acquire the write lock if(! impl.wr_block.try_lock(this)) { BOOST_ASIO_CORO_YIELD - impl.paused_close.emplace(std::move(*this)); + impl.op_close.emplace(std::move(*this)); impl.wr_block.lock(this); BOOST_ASIO_CORO_YIELD net::post(std::move(*this)); @@ -93,6 +100,7 @@ public: // Send close frame impl.wr_close = true; impl.change_status(status::closing); + impl.update_timer(this->get_executor()); BOOST_ASIO_CORO_YIELD net::async_write(impl.stream, fb_.data(), beast::detail::bind_continuation(std::move(*this))); @@ -111,7 +119,7 @@ public: if(! impl.rd_block.try_lock(this)) { BOOST_ASIO_CORO_YIELD - impl.paused_r_close.emplace(std::move(*this)); + impl.op_r_close.emplace(std::move(*this)); impl.rd_block.lock(this); BOOST_ASIO_CORO_YIELD net::post(std::move(*this)); @@ -128,7 +136,7 @@ public: for(;;) { // Read frame header - while(! ws_.parse_fh( + while(! impl.parse_fh( impl.rd_fh, impl.rd_buf, ev_)) { if(ev_) @@ -212,10 +220,11 @@ public: upcall: impl.wr_block.unlock(this); impl.rd_block.try_unlock(this) - && impl.paused_r_rd.maybe_invoke(); - impl.paused_rd.maybe_invoke() - || impl.paused_ping.maybe_invoke() - || impl.paused_wr.maybe_invoke(); + && impl.op_r_rd.maybe_invoke(); + impl.op_rd.maybe_invoke() + || impl.op_idle_ping.maybe_invoke() + || impl.op_ping.maybe_invoke() + || impl.op_wr.maybe_invoke(); this->invoke(cont, ec); } } @@ -259,7 +268,7 @@ close(close_reason const& cr, error_code& ec) impl.wr_close = true; impl.change_status(status::closing); detail::frame_buffer fb; - write_close(fb, cr); + impl.template write_close(fb, cr); net::write(impl.stream, fb.data(), ec); if(impl.check_stop_now(ec)) return; @@ -272,7 +281,7 @@ close(close_reason const& cr, error_code& ec) for(;;) { // Read frame header - while(! parse_fh( + while(! impl.parse_fh( impl.rd_fh, impl.rd_buf, ev)) { if(ev) @@ -355,7 +364,7 @@ async_close(close_reason const& cr, CloseHandler&& handler) CloseHandler, void(error_code)); close_op( - std::move(init.completion_handler), *this, cr); + std::move(init.completion_handler), impl_, cr); return init.result.get(); } diff --git a/include/boost/beast/websocket/impl/handshake.hpp b/include/boost/beast/websocket/impl/handshake.hpp index 2d3bb1af..cbb909c0 100644 --- a/include/boost/beast/websocket/impl/handshake.hpp +++ b/include/boost/beast/websocket/impl/handshake.hpp @@ -10,6 +10,7 @@ #ifndef BOOST_BEAST_WEBSOCKET_IMPL_HANDSHAKE_HPP #define BOOST_BEAST_WEBSOCKET_IMPL_HANDSHAKE_HPP +#include #include #include #include @@ -45,7 +46,7 @@ class stream::handshake_op response_type res; }; - stream& ws_; + boost::weak_ptr wp_; detail::sec_ws_key_type key_; response_type* res_p_; data& d_; @@ -54,54 +55,103 @@ public: template handshake_op( Handler_&& h, - stream& ws, + boost::shared_ptr const& sp, response_type* res_p, string_view host, string_view target, Decorator const& decorator) : stable_async_op_base>( - std::forward(h), ws.get_executor()) - , ws_(ws) + std::forward(h), + sp->stream.get_executor()) + , wp_(sp) , res_p_(res_p) , d_(beast::allocate_stable(*this)) { - d_.req = ws_.build_request( + d_.req = sp->build_request( key_, host, target, decorator); - ws_.impl_->reset(); // VFALCO I don't like this + sp->reset(); // VFALCO I don't like this } void operator()( error_code ec = {}, - std::size_t bytes_used = 0) + std::size_t bytes_used = 0, + bool cont = true) { boost::ignore_unused(bytes_used); + auto sp = wp_.lock(); + if(! sp) + return this->invoke(cont, + net::error::operation_aborted); + auto& impl = *sp; BOOST_ASIO_CORO_REENTER(*this) { - // Send HTTP Upgrade - ws_.impl_->do_pmd_config(d_.req); + impl.change_status(status::handshake); + impl.update_timer(this->get_executor()); + + // write HTTP request + impl.do_pmd_config(d_.req); BOOST_ASIO_CORO_YIELD - http::async_write(ws_.impl_->stream, + http::async_write(impl.stream, d_.req, std::move(*this)); - if(ec) + if(impl.check_stop_now(ec)) goto upcall; - // Read HTTP response + // read HTTP response BOOST_ASIO_CORO_YIELD - http::async_read(ws_.next_layer(), - ws_.impl_->rd_buf, d_.res, + http::async_read(impl.stream, + impl.rd_buf, d_.res, std::move(*this)); - if(ec) + if(impl.check_stop_now(ec)) goto upcall; - ws_.on_response(d_.res, key_, ec); + + // success + impl.reset_idle(); + impl.on_response(d_.res, key_, ec); if(res_p_) swap(d_.res, *res_p_); + upcall: - this->invoke_now(ec); + this->invoke(cont ,ec); } } }; +//------------------------------------------------------------------------------ + +template +template +void +stream:: +do_handshake( + response_type* res_p, + string_view host, + string_view target, + RequestDecorator const& decorator, + error_code& ec) +{ + response_type res; + impl_->change_status(status::handshake); + impl_->reset(); + detail::sec_ws_key_type key; + { + auto const req = impl_->build_request( + key, host, target, decorator); + this->impl_->do_pmd_config(req); + http::write(impl_->stream, req, ec); + } + if(ec) + return; + http::read(next_layer(), impl_->rd_buf, res, ec); + if(ec) + return; + impl_->on_response(res, key, ec); + if(res_p) + *res_p = std::move(res); +} + +//------------------------------------------------------------------------------ + template template BOOST_ASIO_INITFN_RESULT_TYPE( @@ -116,9 +166,10 @@ async_handshake(string_view host, BOOST_BEAST_HANDLER_INIT( HandshakeHandler, void(error_code)); handshake_op{ - std::move(init.completion_handler), *this, nullptr, host, - target, &default_decorate_req}(); + HandshakeHandler, void(error_code))>( + std::move(init.completion_handler), + impl_, nullptr, host, target, + &default_decorate_req)(); return init.result.get(); } @@ -137,9 +188,10 @@ async_handshake(response_type& res, BOOST_BEAST_HANDLER_INIT( HandshakeHandler, void(error_code)); handshake_op{ - std::move(init.completion_handler), *this, &res, host, - target, &default_decorate_req}(); + HandshakeHandler, void(error_code))>( + std::move(init.completion_handler), + impl_, &res, host, target, + &default_decorate_req)(); return init.result.get(); } @@ -161,9 +213,10 @@ async_handshake_ex(string_view host, BOOST_BEAST_HANDLER_INIT( HandshakeHandler, void(error_code)); handshake_op{ - std::move(init.completion_handler), *this, nullptr, host, - target, decorator}(); + HandshakeHandler, void(error_code))>( + std::move(init.completion_handler), + impl_, nullptr, host, target, + decorator)(); return init.result.get(); } @@ -186,9 +239,10 @@ async_handshake_ex(response_type& res, BOOST_BEAST_HANDLER_INIT( HandshakeHandler, void(error_code)); handshake_op{ - std::move(init.completion_handler), *this, &res, host, - target, decorator}(); + HandshakeHandler, void(error_code))>( + std::move(init.completion_handler), + impl_, &res, host, target, + decorator)(); return init.result.get(); } @@ -324,38 +378,6 @@ handshake_ex(response_type& res, host, target, decorator, ec); } -//------------------------------------------------------------------------------ - -template -template -void -stream:: -do_handshake( - response_type* res_p, - string_view host, - string_view target, - RequestDecorator const& decorator, - error_code& ec) -{ - response_type res; - impl_->reset(); - detail::sec_ws_key_type key; - { - auto const req = build_request( - key, host, target, decorator); - this->impl_->do_pmd_config(req); - http::write(impl_->stream, req, ec); - } - if(ec) - return; - http::read(next_layer(), impl_->rd_buf, res, ec); - if(ec) - return; - on_response(res, key, ec); - if(res_p) - *res_p = std::move(res); -} - } // websocket } // beast } // boost diff --git a/include/boost/beast/websocket/impl/ping.hpp b/include/boost/beast/websocket/impl/ping.hpp index 8f995c58..e035d18a 100644 --- a/include/boost/beast/websocket/impl/ping.hpp +++ b/include/boost/beast/websocket/impl/ping.hpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -36,7 +37,7 @@ class stream::ping_op Handler, beast::executor_type> , public net::coroutine { - stream& ws_; + boost::weak_ptr wp_; detail::frame_buffer& fb_; public: @@ -45,18 +46,19 @@ public: template ping_op( Handler_&& h, - stream& ws, + boost::shared_ptr const& sp, detail::opcode op, ping_data const& payload) - : stable_async_op_base< - Handler, beast::executor_type>( - std::forward(h), ws.get_executor()) - , ws_(ws) + : stable_async_op_base>( + std::forward(h), + sp->stream.get_executor()) + , wp_(sp) , fb_(beast::allocate_stable< detail::frame_buffer>(*this)) { // Serialize the ping or pong frame - ws.template write_ping< + sp->template write_ping< flat_static_buffer_base>(fb_, op, payload); (*this)({}, 0, false); } @@ -67,14 +69,18 @@ public: bool cont = true) { boost::ignore_unused(bytes_transferred); - auto& impl = *ws_.impl_; + auto sp = wp_.lock(); + if(! sp) + return this->invoke(cont, + net::error::operation_aborted); + auto& impl = *sp; BOOST_ASIO_CORO_REENTER(*this) { // Acquire the write lock if(! impl.wr_block.try_lock(this)) { BOOST_ASIO_CORO_YIELD - impl.paused_ping.emplace(std::move(*this)); + impl.op_ping.emplace(std::move(*this)); impl.wr_block.lock(this); BOOST_ASIO_CORO_YIELD net::post(std::move(*this)); @@ -92,9 +98,10 @@ public: upcall: impl.wr_block.unlock(this); - impl.paused_close.maybe_invoke() - || impl.paused_rd.maybe_invoke() - || impl.paused_wr.maybe_invoke(); + impl.op_close.maybe_invoke() + || impl.op_idle_ping.maybe_invoke() + || impl.op_rd.maybe_invoke() + || impl.op_wr.maybe_invoke(); this->invoke(cont, ec); } } @@ -102,6 +109,103 @@ public: //------------------------------------------------------------------------------ +#if 0 +template< + class NextLayer, + bool deflateSupported, + class Handler> +void +async_auto_ping( + stream& ws, + Handler&& handler) +{ + using handler_type = + typename std::decay::type; + + using base_type = + beast::stable_async_op_base< + handler_type, beast::executor_type< + stream>>; + + struct async_op : base_type, net::coroutine + { + boost::weak_ptr impl_; + detail::frame_buffer& fb_; + + public: + static constexpr int id = 4; // for soft_mutex + + async_op( + Handler&& h, + stream& ws) + : base_type(std::move(h), ws.get_executor()) + , impl_(ws.impl_) + , fb_(beast::allocate_stable< + detail::frame_buffer>(*this)) + { + // Serialize the ping or pong frame + ping_data payload; + ws.template write_ping< + flat_static_buffer_base>(fb_, op, payload); + (*this)({}, 0, false); + } + + void operator()( + error_code ec = {}, + std::size_t bytes_transferred = 0, + bool cont = true) + { + boost::ignore_unused(bytes_transferred); + auto sp = impl_.lock(); + if(! sp) + return; + + auto& impl = *ws_.impl_; + BOOST_ASIO_CORO_REENTER(*this) + { + // Acquire the write lock + if(! impl.wr_block.try_lock(this)) + { + BOOST_ASIO_CORO_YIELD + impl.op_idle_ping.emplace(std::move(*this)); + impl.wr_block.lock(this); + BOOST_ASIO_CORO_YIELD + net::post(std::move(*this)); + BOOST_ASSERT(impl.wr_block.is_locked(this)); + } + if(impl.check_stop_now(ec)) + goto upcall; + + // Send ping frame + BOOST_ASIO_CORO_YIELD + net::async_write(impl.stream, fb_.data(), + beast::detail::bind_continuation(std::move(*this))); + if(impl.check_stop_now(ec)) + goto upcall; + + upcall: + impl.wr_block.unlock(this); + impl.op_close.maybe_invoke() + || impl.op_ping.maybe_invoke() + || impl.op_rd.maybe_invoke() + || impl.op_wr.maybe_invoke(); + if(! cont) + { + BOOST_ASIO_CORO_YIELD + net::post(bind_front_handler( + std::move(*this), ec)); + } + this->invoke(ec); + } + } + }; + + async_op op(ws, std::forward(handler)); +} +#endif + +//------------------------------------------------------------------------------ + template void stream:: @@ -121,7 +225,7 @@ ping(ping_data const& payload, error_code& ec) if(impl_->check_stop_now(ec)) return; detail::frame_buffer fb; - write_ping( + impl_->template write_ping( fb, detail::opcode::ping, payload); net::write(impl_->stream, fb.data(), ec); if(impl_->check_stop_now(ec)) @@ -147,7 +251,7 @@ pong(ping_data const& payload, error_code& ec) if(impl_->check_stop_now(ec)) return; detail::frame_buffer fb; - write_ping( + impl_->template write_ping( fb, detail::opcode::pong, payload); net::write(impl_->stream, fb.data(), ec); if(impl_->check_stop_now(ec)) @@ -167,7 +271,7 @@ async_ping(ping_data const& payload, WriteHandler&& handler) WriteHandler, void(error_code)); ping_op( - std::move(init.completion_handler), *this, + std::move(init.completion_handler), impl_, detail::opcode::ping, payload); return init.result.get(); } @@ -185,7 +289,7 @@ async_pong(ping_data const& payload, WriteHandler&& handler) WriteHandler, void(error_code)); ping_op( - std::move(init.completion_handler), *this, + std::move(init.completion_handler), impl_, detail::opcode::pong, payload); return init.result.get(); } diff --git a/include/boost/beast/websocket/impl/read.hpp b/include/boost/beast/websocket/impl/read.hpp index 175b3623..f18525f4 100644 --- a/include/boost/beast/websocket/impl/read.hpp +++ b/include/boost/beast/websocket/impl/read.hpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -50,7 +51,7 @@ class stream::read_some_op Handler, beast::executor_type> , public net::coroutine { - stream& ws_; + boost::weak_ptr wp_; MutableBufferSequence bs_; buffers_suffix cb_; std::size_t bytes_written_ = 0; @@ -64,12 +65,13 @@ public: template read_some_op( Handler_&& h, - stream& ws, + boost::shared_ptr const& sp, MutableBufferSequence const& bs) : async_op_base< Handler, beast::executor_type>( - std::forward(h), ws.get_executor()) - , ws_(ws) + std::forward(h), + sp->stream.get_executor()) + , wp_(sp) , bs_(bs) , cb_(bs) , code_(close_code::none) @@ -83,16 +85,22 @@ public: bool cont = true) { using beast::detail::clamp; - auto& impl = *ws_.impl_; + auto sp = wp_.lock(); + if(! sp) + return this->invoke(cont, + net::error::operation_aborted, 0); + auto& impl = *sp; BOOST_ASIO_CORO_REENTER(*this) { + impl.update_timer(this->get_executor()); + acquire_read_lock: // Acquire the read lock if(! impl.rd_block.try_lock(this)) { do_suspend: BOOST_ASIO_CORO_YIELD - impl.paused_r_rd.emplace(std::move(*this)); + impl.op_r_rd.emplace(std::move(*this)); impl.rd_block.lock(this); BOOST_ASIO_CORO_YIELD net::post(std::move(*this)); @@ -139,7 +147,7 @@ public: (! impl.rd_fh.fin || impl.rd_done)) { // Read frame header - while(! ws_.parse_fh( + while(! impl.parse_fh( impl.rd_fh, impl.rd_buf, result_)) { if(result_) @@ -158,14 +166,15 @@ public: impl.rd_buf, impl.rd_buf.max_size())), std::move(*this)); BOOST_ASSERT(impl.rd_block.is_locked(this)); + impl.rd_buf.commit(bytes_transferred); if(impl.check_stop_now(ec)) goto upcall; - impl.rd_buf.commit(bytes_transferred); + impl.reset_idle(); // Allow a close operation // to acquire the read block impl.rd_block.unlock(this); - if( impl.paused_r_close.maybe_invoke()) + if( impl.op_r_close.maybe_invoke()) { // Suspend BOOST_ASSERT(impl.rd_block.is_locked()); @@ -216,7 +225,7 @@ public: impl.ctrl_cb( frame_type::ping, payload); impl.rd_fb.clear(); - ws_.template write_ping< + impl.template write_ping< flat_static_buffer_base>(impl.rd_fb, detail::opcode::pong, payload); } @@ -224,13 +233,13 @@ public: // Allow a close operation // to acquire the read block impl.rd_block.unlock(this); - impl.paused_r_close.maybe_invoke(); + impl.op_r_close.maybe_invoke(); // Acquire the write lock if(! impl.wr_block.try_lock(this)) { BOOST_ASIO_CORO_YIELD - impl.paused_rd.emplace(std::move(*this)); + impl.op_rd.emplace(std::move(*this)); impl.wr_block.lock(this); BOOST_ASIO_CORO_YIELD net::post(std::move(*this)); @@ -249,9 +258,10 @@ public: if(impl.check_stop_now(ec)) goto upcall; impl.wr_block.unlock(this); - impl.paused_close.maybe_invoke() || - impl.paused_ping.maybe_invoke() || - impl.paused_wr.maybe_invoke(); + impl.op_close.maybe_invoke() + || impl.op_idle_ping.maybe_invoke() + || impl.op_ping.maybe_invoke() + || impl.op_wr.maybe_invoke(); goto acquire_read_lock; } @@ -264,9 +274,7 @@ public: if(! cont) { BOOST_ASIO_CORO_YIELD - net::post( - ws_.get_executor(), - std::move(*this)); + net::post(std::move(*this)); BOOST_ASSERT(cont); } } @@ -291,9 +299,7 @@ public: if(! cont) { BOOST_ASIO_CORO_YIELD - net::post( - ws_.get_executor(), - std::move(*this)); + net::post(std::move(*this)); BOOST_ASSERT(cont); } } @@ -355,9 +361,10 @@ public: impl.rd_buf.prepare(read_size( impl.rd_buf, impl.rd_buf.max_size())), std::move(*this)); + impl.rd_buf.commit(bytes_transferred); if(impl.check_stop_now(ec)) goto upcall; - impl.rd_buf.commit(bytes_transferred); + impl.reset_idle(); if(impl.rd_fh.mask) detail::mask_inplace(buffers_prefix(clamp( impl.rd_remain), impl.rd_buf.data()), @@ -400,6 +407,7 @@ public: clamp(impl.rd_remain), cb_), std::move(*this)); if(impl.check_stop_now(ec)) goto upcall; + impl.reset_idle(); BOOST_ASSERT(bytes_transferred > 0); auto const mb = buffers_prefix( bytes_transferred, cb_); @@ -443,6 +451,7 @@ public: std::move(*this)); if(impl.check_stop_now(ec)) goto upcall; + impl.reset_idle(); BOOST_ASSERT(bytes_transferred > 0); impl.rd_buf.commit(bytes_transferred); if(impl.rd_fh.mask) @@ -477,9 +486,8 @@ public: else if(impl.rd_fh.fin) { // append the empty block codes - static std::uint8_t constexpr - empty_block[4] = { - 0x00, 0x00, 0xff, 0xff }; + std::uint8_t constexpr + empty_block[4] = { 0x00, 0x00, 0xff, 0xff }; zs.next_in = empty_block; zs.avail_in = sizeof(empty_block); impl.inflate(zs, zlib::Flush::sync, ec); @@ -537,7 +545,7 @@ public: if(! impl.wr_block.try_lock(this)) { BOOST_ASIO_CORO_YIELD - impl.paused_rd.emplace(std::move(*this)); + impl.op_rd.emplace(std::move(*this)); impl.wr_block.lock(this); BOOST_ASIO_CORO_YIELD net::post(std::move(*this)); @@ -546,8 +554,7 @@ public: goto upcall; } - // Set the status - impl.status_ = status::closing; + impl.change_status(status::closing); if(! impl.wr_close) { @@ -555,7 +562,7 @@ public: // Serialize close frame impl.rd_fb.clear(); - ws_.template write_close< + impl.template write_close< flat_static_buffer_base>( impl.rd_fb, code_); @@ -585,18 +592,19 @@ public: if(! ec) ec = result_; if(ec && ec != error::closed) - impl.status_ = status::failed; + impl.change_status(status::failed); else - impl.status_ = status::closed; + impl.change_status(status::closed); impl.close(); upcall: impl.rd_block.try_unlock(this); - impl.paused_r_close.maybe_invoke(); + impl.op_r_close.maybe_invoke(); if(impl.wr_block.try_unlock(this)) - impl.paused_close.maybe_invoke() - || impl.paused_ping.maybe_invoke() - || impl.paused_wr.maybe_invoke(); + impl.op_close.maybe_invoke() + || impl.op_idle_ping.maybe_invoke() + || impl.op_ping.maybe_invoke() + || impl.op_wr.maybe_invoke(); this->invoke(cont, ec, bytes_written_); } } @@ -613,7 +621,7 @@ class stream::read_op Handler, beast::executor_type> , public net::coroutine { - stream& ws_; + boost::weak_ptr wp_; DynamicBuffer& b_; std::size_t limit_; std::size_t bytes_written_ = 0; @@ -623,14 +631,15 @@ public: template read_op( Handler_&& h, - stream& ws, + boost::shared_ptr const& sp, DynamicBuffer& b, std::size_t limit, bool some) - : async_op_base< - Handler, beast::executor_type>( - std::forward(h), ws.get_executor()) - , ws_(ws) + : async_op_base>( + std::forward(h), + sp->stream.get_executor()) + , wp_(sp) , b_(b) , limit_(limit ? limit : ( std::numeric_limits::max)()) @@ -645,26 +654,35 @@ public: bool cont = true) { using beast::detail::clamp; - boost::optional mb; + auto sp = wp_.lock(); + if(! sp) + return this->invoke(cont, + net::error::operation_aborted, 0); + auto& impl = *sp; + using mutable_buffers_type = typename + DynamicBuffer::mutable_buffers_type; + boost::optional mb; BOOST_ASIO_CORO_REENTER(*this) { do { mb = beast::detail::dynamic_buffer_prepare(b_, - clamp(ws_.read_size_hint(b_), limit_), + clamp(impl.read_size_hint_db(b_), limit_), ec, error::buffer_overflow); - if(ec) + if(impl.check_stop_now(ec)) goto upcall; + + // VFALCO TODO use boost::beast::bind_continuation BOOST_ASIO_CORO_YIELD - ws_.async_read_some(*mb, - beast::detail::bind_continuation(std::move(*this))); + read_some_op( + std::move(*this), sp, *mb); b_.commit(bytes_transferred); bytes_written_ += bytes_transferred; if(ec) goto upcall; } - while(! some_ && ! ws_.is_message_done()); + while(! some_ && ! impl.rd_done); + upcall: this->invoke(cont, ec, bytes_written_); } @@ -730,7 +748,7 @@ async_read(DynamicBuffer& buffer, ReadHandler&& handler) read_op( std::move(init.completion_handler), - *this, buffer, 0, false); + impl_, buffer, 0, false); return init.result.get(); } @@ -779,7 +797,7 @@ read_some( BOOST_ASSERT(size > 0); auto mb = beast::detail::dynamic_buffer_prepare( buffer, size, ec, error::buffer_overflow); - if(ec) + if(impl_->check_stop_now(ec)) return 0; auto const bytes_written = read_some(*mb, ec); buffer.commit(bytes_written); @@ -806,7 +824,7 @@ async_read_some( read_op( std::move(init.completion_handler), - *this, buffer, limit, true); + impl_, buffer, limit, true); return init.result.get(); } @@ -862,7 +880,7 @@ loop: { // Read frame header error_code result; - while(! parse_fh(impl.rd_fh, impl.rd_buf, result)) + while(! impl.parse_fh(impl.rd_fh, impl.rd_buf, result)) { if(result) { @@ -915,7 +933,7 @@ loop: if(impl.ctrl_cb) impl.ctrl_cb(frame_type::ping, payload); detail::frame_buffer fb; - write_ping(fb, + impl.template write_ping(fb, detail::opcode::pong, payload); net::write(impl.stream, fb.data(), ec); if(impl.check_stop_now(ec)) @@ -1186,7 +1204,7 @@ async_read_some( ReadHandler, void(error_code, std::size_t)); read_some_op( - std::move(init.completion_handler), *this, buffers); + std::move(init.completion_handler), impl_, buffers); return init.result.get(); } diff --git a/include/boost/beast/websocket/impl/stream.hpp b/include/boost/beast/websocket/impl/stream.hpp index c634abbb..2544a770 100644 --- a/include/boost/beast/websocket/impl/stream.hpp +++ b/include/boost/beast/websocket/impl/stream.hpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -29,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -45,7 +47,7 @@ template template stream:: stream(Args&&... args) - : impl_(std::make_shared( + : impl_(boost::make_shared( std::forward(args)...)) { BOOST_ASSERT(impl_->rd_buf.max_size() >= @@ -140,6 +142,30 @@ read_size_hint(DynamicBuffer& buffer) const } //------------------------------------------------------------------------------ +// +// Settings +// +//------------------------------------------------------------------------------ + +// timeout + +template +void +stream:: +get_option(timeout& opt) +{ + opt = impl_->timeout_opt; +} + +template +void +stream:: +set_option(timeout const& opt) +{ + impl_->set_option(opt); +} + +// template void @@ -229,7 +255,7 @@ void stream:: secure_prng(bool value) { - this->secure_prng_ = value; + this->impl_->secure_prng_ = value; } template @@ -271,471 +297,6 @@ text() const //------------------------------------------------------------------------------ -// Attempt to read a complete frame header. -// Returns `false` if more bytes are needed -template -template -bool -stream:: -parse_fh( - detail::frame_header& fh, - DynamicBuffer& b, - error_code& ec) -{ - if(buffer_size(b.data()) < 2) - { - // need more bytes - ec = {}; - return false; - } - buffers_suffix cb{ - b.data()}; - std::size_t need; - { - std::uint8_t tmp[2]; - cb.consume(net::buffer_copy( - net::buffer(tmp), cb)); - fh.len = tmp[1] & 0x7f; - switch(fh.len) - { - case 126: need = 2; break; - case 127: need = 8; break; - default: - need = 0; - } - fh.mask = (tmp[1] & 0x80) != 0; - if(fh.mask) - need += 4; - if(buffer_size(cb) < need) - { - // need more bytes - ec = {}; - return false; - } - fh.op = static_cast< - detail::opcode>(tmp[0] & 0x0f); - fh.fin = (tmp[0] & 0x80) != 0; - fh.rsv1 = (tmp[0] & 0x40) != 0; - fh.rsv2 = (tmp[0] & 0x20) != 0; - fh.rsv3 = (tmp[0] & 0x10) != 0; - } - switch(fh.op) - { - case detail::opcode::binary: - case detail::opcode::text: - if(impl_->rd_cont) - { - // new data frame when continuation expected - ec = error::bad_data_frame; - return false; - } - if(fh.rsv2 || fh.rsv3 || - ! impl_->rd_deflated(fh.rsv1)) - { - // reserved bits not cleared - ec = error::bad_reserved_bits; - return false; - } - break; - - case detail::opcode::cont: - if(! impl_->rd_cont) - { - // continuation without an active message - ec = error::bad_continuation; - return false; - } - if(fh.rsv1 || fh.rsv2 || fh.rsv3) - { - // reserved bits not cleared - ec = error::bad_reserved_bits; - return false; - } - break; - - default: - if(detail::is_reserved(fh.op)) - { - // reserved opcode - ec = error::bad_opcode; - return false; - } - if(! fh.fin) - { - // fragmented control message - ec = error::bad_control_fragment; - return false; - } - if(fh.len > 125) - { - // invalid length for control message - ec = error::bad_control_size; - return false; - } - if(fh.rsv1 || fh.rsv2 || fh.rsv3) - { - // reserved bits not cleared - ec = error::bad_reserved_bits; - return false; - } - break; - } - if(impl_->role == role_type::server && ! fh.mask) - { - // unmasked frame from client - ec = error::bad_unmasked_frame; - return false; - } - if(impl_->role == role_type::client && fh.mask) - { - // masked frame from server - ec = error::bad_masked_frame; - return false; - } - if(detail::is_control(fh.op) && - buffer_size(cb) < need + fh.len) - { - // Make the entire control frame payload - // get read in before we return `true` - return false; - } - switch(fh.len) - { - case 126: - { - std::uint8_t tmp[2]; - BOOST_ASSERT(buffer_size(cb) >= sizeof(tmp)); - cb.consume(net::buffer_copy(net::buffer(tmp), cb)); - fh.len = detail::big_uint16_to_native(&tmp[0]); - if(fh.len < 126) - { - // length not canonical - ec = error::bad_size; - return false; - } - break; - } - case 127: - { - std::uint8_t tmp[8]; - BOOST_ASSERT(buffer_size(cb) >= sizeof(tmp)); - cb.consume(net::buffer_copy(net::buffer(tmp), cb)); - fh.len = detail::big_uint64_to_native(&tmp[0]); - if(fh.len < 65536) - { - // length not canonical - ec = error::bad_size; - return false; - } - break; - } - } - if(fh.mask) - { - std::uint8_t tmp[4]; - BOOST_ASSERT(buffer_size(cb) >= sizeof(tmp)); - cb.consume(net::buffer_copy(net::buffer(tmp), cb)); - fh.key = detail::little_uint32_to_native(&tmp[0]); - detail::prepare_key(impl_->rd_key, fh.key); - } - else - { - // initialize this otherwise operator== breaks - fh.key = 0; - } - if(! detail::is_control(fh.op)) - { - if(fh.op != detail::opcode::cont) - { - impl_->rd_size = 0; - impl_->rd_op = fh.op; - } - else - { - if(impl_->rd_size > (std::numeric_limits< - std::uint64_t>::max)() - fh.len) - { - // message size exceeds configured limit - ec = error::message_too_big; - return false; - } - } - if(! impl_->rd_deflated()) - { - if(impl_->rd_msg_max && beast::detail::sum_exceeds( - impl_->rd_size, fh.len, impl_->rd_msg_max)) - { - // message size exceeds configured limit - ec = error::message_too_big; - return false; - } - } - impl_->rd_cont = ! fh.fin; - impl_->rd_remain = fh.len; - } - b.consume(b.size() - buffer_size(cb)); - ec = {}; - return true; -} - -template -template -void -stream:: -write_close(DynamicBuffer& db, close_reason const& cr) -{ - using namespace boost::endian; - detail::frame_header fh; - fh.op = detail::opcode::close; - fh.fin = true; - fh.rsv1 = false; - fh.rsv2 = false; - fh.rsv3 = false; - fh.len = cr.code == close_code::none ? - 0 : 2 + cr.reason.size(); - if(impl_->role == role_type::client) - { - fh.mask = true; - fh.key = this->create_mask(); - } - else - { - fh.mask = false; - } - detail::write(db, fh); - if(cr.code != close_code::none) - { - detail::prepared_key key; - if(fh.mask) - detail::prepare_key(key, fh.key); - { - std::uint8_t tmp[2]; - ::new(&tmp[0]) big_uint16_buf_t{ - (std::uint16_t)cr.code}; - auto mb = db.prepare(2); - net::buffer_copy(mb, - net::buffer(tmp)); - if(fh.mask) - detail::mask_inplace(mb, key); - db.commit(2); - } - if(! cr.reason.empty()) - { - auto mb = db.prepare(cr.reason.size()); - net::buffer_copy(mb, - net::const_buffer( - cr.reason.data(), cr.reason.size())); - if(fh.mask) - detail::mask_inplace(mb, key); - db.commit(cr.reason.size()); - } - } -} - -template -template -void -stream:: -write_ping(DynamicBuffer& db, - detail::opcode code, ping_data const& data) -{ - detail::frame_header fh; - fh.op = code; - fh.fin = true; - fh.rsv1 = false; - fh.rsv2 = false; - fh.rsv3 = false; - fh.len = data.size(); - fh.mask = impl_->role == role_type::client; - if(fh.mask) - fh.key = this->create_mask(); - detail::write(db, fh); - if(data.empty()) - return; - detail::prepared_key key; - if(fh.mask) - detail::prepare_key(key, fh.key); - auto mb = db.prepare(data.size()); - net::buffer_copy(mb, - net::const_buffer( - data.data(), data.size())); - if(fh.mask) - detail::mask_inplace(mb, key); - db.commit(data.size()); -} - -//------------------------------------------------------------------------------ - -template -template -request_type -stream:: -build_request(detail::sec_ws_key_type& key, - string_view host, string_view target, - Decorator const& decorator) -{ - request_type req; - req.target(target); - req.version(11); - req.method(http::verb::get); - req.set(http::field::host, host); - req.set(http::field::upgrade, "websocket"); - req.set(http::field::connection, "upgrade"); - detail::make_sec_ws_key(key); - req.set(http::field::sec_websocket_key, key); - req.set(http::field::sec_websocket_version, "13"); - impl_->build_request_pmd(req); - decorator(req); - if(! req.count(http::field::user_agent)) - req.set(http::field::user_agent, - BOOST_BEAST_VERSION_STRING); - return req; -} - -template -template -response_type -stream:: -build_response( - http::request> const& req, - Decorator const& decorator, - error_code& result) -{ - auto const decorate = - [&decorator](response_type& res) - { - decorator(res); - if(! res.count(http::field::server)) - { - BOOST_STATIC_ASSERT(sizeof(BOOST_BEAST_VERSION_STRING) < 20); - static_string<20> s(BOOST_BEAST_VERSION_STRING); - res.set(http::field::server, s); - } - }; - auto err = - [&](error e) - { - result = e; - response_type res; - res.version(req.version()); - res.result(http::status::bad_request); - res.body() = result.message(); - res.prepare_payload(); - decorate(res); - return res; - }; - if(req.version() != 11) - return err(error::bad_http_version); - if(req.method() != http::verb::get) - return err(error::bad_method); - if(! req.count(http::field::host)) - return err(error::no_host); - { - auto const it = req.find(http::field::connection); - if(it == req.end()) - return err(error::no_connection); - if(! http::token_list{it->value()}.exists("upgrade")) - return err(error::no_connection_upgrade); - } - { - auto const it = req.find(http::field::upgrade); - if(it == req.end()) - return err(error::no_upgrade); - if(! http::token_list{it->value()}.exists("websocket")) - return err(error::no_upgrade_websocket); - } - string_view key; - { - auto const it = req.find(http::field::sec_websocket_key); - if(it == req.end()) - return err(error::no_sec_key); - key = it->value(); - if(key.size() > detail::sec_ws_key_type::max_size_n) - return err(error::bad_sec_key); - } - { - auto const it = req.find(http::field::sec_websocket_version); - if(it == req.end()) - return err(error::no_sec_version); - if(it->value() != "13") - { - response_type res; - res.result(http::status::upgrade_required); - res.version(req.version()); - res.set(http::field::sec_websocket_version, "13"); - result = error::bad_sec_version; - res.body() = result.message(); - res.prepare_payload(); - decorate(res); - return res; - } - } - - response_type res; - res.result(http::status::switching_protocols); - res.version(req.version()); - res.set(http::field::upgrade, "websocket"); - res.set(http::field::connection, "upgrade"); - { - detail::sec_ws_accept_type acc; - detail::make_sec_ws_accept(acc, key); - res.set(http::field::sec_websocket_accept, acc); - } - impl_->build_response_pmd(res, req); - decorate(res); - result = {}; - return res; -} - -// 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) -{ - auto const err = - [&](error e) - { - ec = e; - }; - if(res.result() != http::status::switching_protocols) - return err(error::upgrade_declined); - if(res.version() != 11) - return err(error::bad_http_version); - { - auto const it = res.find(http::field::connection); - if(it == res.end()) - return err(error::no_connection); - if(! http::token_list{it->value()}.exists("upgrade")) - return err(error::no_connection_upgrade); - } - { - auto const it = res.find(http::field::upgrade); - if(it == res.end()) - return err(error::no_upgrade); - if(! http::token_list{it->value()}.exists("websocket")) - return err(error::no_upgrade_websocket); - } - { - auto const it = res.find(http::field::sec_websocket_accept); - if(it == res.end()) - return err(error::no_sec_accept); - detail::sec_ws_accept_type acc; - detail::make_sec_ws_accept(acc, key); - if(acc.compare(it->value()) != 0) - return err(error::bad_sec_accept); - } - - ec = {}; - impl_->on_response_pmd(res); - impl_->open(role_type::client); -} - // _Fail the WebSocket Connection_ template void @@ -746,12 +307,12 @@ do_fail( error_code& ec) // set to the error, else set to ev { BOOST_ASSERT(ev); - impl_->status_ = status::closing; + impl_->change_status(status::closing); if(code != close_code::none && ! impl_->wr_close) { impl_->wr_close = true; detail::frame_buffer fb; - write_close< + impl_->template write_close< flat_static_buffer_base>(fb, code); net::write(impl_->stream, fb.data(), ec); if(impl_->check_stop_now(ec)) @@ -768,9 +329,9 @@ do_fail( if(! ec) ec = ev; if(ec && ec != error::closed) - impl_->status_ = status::failed; + impl_->change_status(status::failed); else - impl_->status_ = status::closed; + impl_->change_status(status::closed); impl_->close(); } diff --git a/include/boost/beast/websocket/impl/stream_impl.hpp b/include/boost/beast/websocket/impl/stream_impl.hpp index 5cfd0154..52914f1b 100644 --- a/include/boost/beast/websocket/impl/stream_impl.hpp +++ b/include/boost/beast/websocket/impl/stream_impl.hpp @@ -10,16 +10,32 @@ #ifndef BOOST_BEAST_WEBSOCKET_IMPL_STREAM_IMPL_HPP #define BOOST_BEAST_WEBSOCKET_IMPL_STREAM_IMPL_HPP +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include +#include +#include +#include #include +#include +#include #include +#include #include #include +#include #include #include #include +#include #include namespace boost { @@ -29,7 +45,7 @@ namespace websocket { template< class NextLayer, bool deflateSupported> struct stream::impl_type - : std::enable_shared_from_this + : boost::enable_shared_from_this , detail::impl_base { NextLayer stream; // The underlying stream @@ -68,17 +84,25 @@ struct stream::impl_type std::size_t wr_buf_opt /* write buffer size option setting */ = 4096; detail::fh_buffer wr_fb; // header buffer used for writes - saved_handler paused_rd; // paused read op - saved_handler paused_wr; // paused write op - saved_handler paused_ping; // paused ping op - saved_handler paused_close; // paused close op - saved_handler paused_r_rd; // paused read op (async read) - saved_handler paused_r_close; // paused close op (async read) + saved_handler op_rd; // paused read op + saved_handler op_wr; // paused write op + saved_handler op_ping; // paused ping op + saved_handler op_idle_ping; // paused idle ping op + saved_handler op_close; // paused close op + saved_handler op_r_rd; // paused read op (async read) + saved_handler op_r_close; // paused close op (async read) - boost::optional tm_auto_ping; - bool tm_opt /* true if auto-timeout option is set */ = false; - bool tm_idle; // set to false on incoming frames - time_point::duration tm_dur /* duration of timer */ = std::chrono::seconds(1); + bool secure_prng_ = true; + + bool ec_delivered = false; + bool timed_out = false; + int idle_counter = 0; + + // settings + + timeout timeout_opt; + + // template impl_type(Args&&... args) @@ -91,6 +115,9 @@ struct stream::impl_type open(role_type role_) { // VFALCO TODO analyze and remove dupe code in reset() + timer.expires_at(never()); + timed_out = false; + cr.code = close_code::none; role = role_; status_ = status::open; rd_remain = 0; @@ -106,13 +133,10 @@ struct stream::impl_type // stream exhibits undefined behavior. wr_block.reset(); rd_block.reset(); - cr.code = close_code::none; wr_cont = false; wr_buf_size = 0; - tm_idle = false; - this->open_pmd(role); } @@ -128,6 +152,8 @@ struct stream::impl_type reset() { BOOST_ASSERT(status_ != status::open); + timer.expires_at(never()); + cr.code = close_code::none; rd_remain = 0; rd_cont = false; rd_done = true; @@ -141,8 +167,6 @@ struct stream::impl_type // stream exhibits undefined behavior. wr_block.reset(); rd_block.reset(); - cr.code = close_code::none; - tm_idle = false; // VFALCO Is this needed? timer.cancel(); @@ -173,126 +197,88 @@ struct stream::impl_type } } -private: - bool - is_timer_set() const - { - return timer.expiry() == never(); - } + //-------------------------------------------------------------------------- - // returns `true` if we try sending a ping and - // getting a pong before closing an idle stream. - bool - is_auto_ping_enabled() const - { - if(tm_auto_ping.has_value()) - return *tm_auto_ping; - if(role == role_type::server) - return true; - return false; - } + template + request_type + build_request( + detail::sec_ws_key_type& key, + string_view host, string_view target, + Decorator const& decorator); - template - class timeout_handler - : boost::empty_value - { - std::weak_ptr wp_; - - public: - timeout_handler( - Executor const& ex, - std::shared_ptr const& sp) - : boost::empty_value( - boost::empty_init_t{}, ex) - , wp_(sp) - { - } - - using executor_type = Executor; - - executor_type - get_executor() const - { - return this->get(); - } - - void - operator()(error_code ec) - { - // timer canceled? - if(ec == net::error::operation_aborted) - return; - BOOST_ASSERT(! ec); - - // stream destroyed? - auto sp = wp_.lock(); - if(! sp) - return; - auto& impl = *sp; - - close_socket(get_lowest_layer(impl.stream)); -#if 0 - if(! impl.tm_idle) - { - impl.tm_idle = true; - BOOST_VERIFY( - impl.timer.expires_after(impl.tm_dur) == 0); - return impl.timer.async_wait(std::move(*this)); - } - if(impl.is_auto_ping_enabled()) - { - // send ping - } - else - { - // timeout - } -#endif - } - }; -public: - - // called when there is qualified activity void - activity() + on_response( + response_type const& res, + detail::sec_ws_key_type const& key, + error_code& ec); + + template + response_type + build_response( + http::request> const& req, + Decorator const& decorator, + error_code& result); + + // Attempt to read a complete frame header. + // Returns `false` if more bytes are needed + template + bool + parse_fh(detail::frame_header& fh, + DynamicBuffer& b, error_code& ec); + + std::uint32_t + create_mask() { - tm_idle = false; + auto g = detail::make_prng(secure_prng_); + for(;;) + if(auto key = g()) + return key; } - // Maintain the expiration timer - template - void - update_timer(Executor const& ex) + std::size_t + read_size_hint(std::size_t initial_size) const { - if(role == role_type::server) - { - if(! is_timer_set()) - { - // turn timer on - timer.expires_after(tm_dur); - timer.async_wait( - timeout_handler( - ex, this->shared_from_this())); - } - } - else if(tm_opt && ! is_timer_set()) - { - // turn timer on - timer.expires_after(tm_dur); - timer.async_wait( - timeout_handler( - ex, this->shared_from_this())); - } - else if(! tm_opt && is_timer_set()) - { - // turn timer off - timer.cancel(); - } + return this->read_size_hint_pmd( + initial_size, rd_done, rd_remain, rd_fh); } + template + std::size_t + read_size_hint_db(DynamicBuffer& buffer) const + { + auto const initial_size = (std::min)( + +tcp_frame_size, + buffer.max_size() - buffer.size()); + if(initial_size == 0) + return 1; // buffer is full + return this->read_size_hint(initial_size); + } + + template + void + write_ping(DynamicBuffer& db, + detail::opcode code, ping_data const& data); + + template + void + write_close(DynamicBuffer& db, close_reason const& cr); + //-------------------------------------------------------------------------- - bool ec_delivered = false; + void + set_option(timeout const& opt) + { + if( opt.handshake_timeout == none() && + opt.idle_timeout == none()) + { + // turn timer off + timer.cancel(); + timer.expires_at(never()); + } + + timeout_opt = opt; + } // Determine if an operation should stop and // deliver an error code to the completion handler. @@ -305,6 +291,14 @@ public: bool check_stop_now(error_code& ec) { + // Deliver the timeout to the first caller + if(timed_out) + { + timed_out = false; + ec = beast::error::timeout; + return true; + } + // If the stream is closed then abort if( status_ == status::closed || status_ == status::failed) @@ -339,8 +333,11 @@ public: { switch(new_status) { + case status::handshake: + break; + case status::closing: - BOOST_ASSERT(status_ == status::open); + //BOOST_ASSERT(status_ == status::open); break; case status::failed: @@ -353,8 +350,639 @@ public: } status_ = new_status; } + + // Called to disarm the idle timeout counter + void + reset_idle() + { + idle_counter = 0; + } + + // Maintain the expiration timer + template + void + update_timer(Executor const& ex) + { + switch(status_) + { + case status::handshake: + BOOST_ASSERT(idle_counter == 0); + if(! is_timer_set() && + timeout_opt.handshake_timeout != none()) + { + timer.expires_after( + timeout_opt.handshake_timeout); + timer.async_wait( + timeout_handler( + ex, this->weak_from_this())); + } + break; + + case status::open: + if(timeout_opt.idle_timeout != none()) + { + idle_counter = 0; + timer.expires_after( + timeout_opt.idle_timeout); + timer.async_wait( + timeout_handler( + ex, this->weak_from_this())); + } + else + { + timer.cancel(); + timer.expires_at(never()); + } + break; + + case status::closing: + if(timeout_opt.handshake_timeout != none()) + { + idle_counter = 0; + timer.expires_after( + timeout_opt.handshake_timeout); + timer.async_wait( + timeout_handler( + ex, this->weak_from_this())); + } + else + { + BOOST_ASSERT(! is_timer_set()); + } + break; + + case status::failed: + case status::closed: + // this->close(); // Is this right? + timer.cancel(); + timer.expires_at(never()); + break; + } + } + +private: + bool + is_timer_set() const + { + return timer.expiry() != never(); + } + + template + class timeout_handler + : boost::empty_value + { + boost::weak_ptr wp_; + + public: + timeout_handler( + Executor const& ex, + boost::weak_ptr&& wp) + : boost::empty_value( + boost::empty_init_t{}, ex) + , wp_(std::move(wp)) + { + } + + using executor_type = Executor; + + executor_type + get_executor() const + { + return this->get(); + } + + void + operator()(error_code ec) + { + // timer canceled? + if(ec == net::error::operation_aborted) + return; + BOOST_ASSERT(! ec); + + // stream destroyed? + auto sp = wp_.lock(); + if(! sp) + return; + auto& impl = *sp; + + switch(impl.status_) + { + case status::handshake: + impl.timed_out = true; + close_socket(get_lowest_layer(impl.stream)); + return; + + case status::open: + // timeout was disabled + if(impl.timeout_opt.idle_timeout == none()) + return; + + if( impl.timeout_opt.keep_alive_pings && + impl.idle_counter < 1) + { + // <- send ping + + ++impl.idle_counter; + impl.timer.expires_after( + impl.timeout_opt.idle_timeout / 2); + impl.timer.async_wait(std::move(*this)); + return; + } + + // timeout + impl.timed_out = true; + close_socket(get_lowest_layer(impl.stream)); + return; + + case status::closing: + impl.timed_out = true; + close_socket(get_lowest_layer(impl.stream)); + return; + + case status::closed: + case status::failed: + // nothing to do? + return; + } + } + }; }; +//-------------------------------------------------------------------------- +// +// client +// +//-------------------------------------------------------------------------- + +template +template +request_type +stream::impl_type:: +build_request( + detail::sec_ws_key_type& key, + string_view host, string_view target, + Decorator const& decorator) +{ + request_type req; + req.target(target); + req.version(11); + req.method(http::verb::get); + req.set(http::field::host, host); + req.set(http::field::upgrade, "websocket"); + req.set(http::field::connection, "upgrade"); + detail::make_sec_ws_key(key); + req.set(http::field::sec_websocket_key, key); + req.set(http::field::sec_websocket_version, "13"); + this->build_request_pmd(req); + decorator(req); + if(! req.count(http::field::user_agent)) + req.set(http::field::user_agent, + BOOST_BEAST_VERSION_STRING); + return req; +} + +// Called when the WebSocket Upgrade response is received +template +void +stream::impl_type:: +on_response( + response_type const& res, + detail::sec_ws_key_type const& key, + error_code& ec) +{ + auto const err = + [&](error e) + { + ec = e; + }; + if(res.result() != http::status::switching_protocols) + return err(error::upgrade_declined); + if(res.version() != 11) + return err(error::bad_http_version); + { + auto const it = res.find(http::field::connection); + if(it == res.end()) + return err(error::no_connection); + if(! http::token_list{it->value()}.exists("upgrade")) + return err(error::no_connection_upgrade); + } + { + auto const it = res.find(http::field::upgrade); + if(it == res.end()) + return err(error::no_upgrade); + if(! http::token_list{it->value()}.exists("websocket")) + return err(error::no_upgrade_websocket); + } + { + auto const it = res.find( + http::field::sec_websocket_accept); + if(it == res.end()) + return err(error::no_sec_accept); + detail::sec_ws_accept_type acc; + detail::make_sec_ws_accept(acc, key); + if(acc.compare(it->value()) != 0) + return err(error::bad_sec_accept); + } + + ec = {}; + this->on_response_pmd(res); + this->open(role_type::client); +} + +//-------------------------------------------------------------------------- + +template +template +response_type +stream::impl_type:: +build_response( + http::request> const& req, + Decorator const& decorator, + error_code& result) +{ + auto const decorate = + [&decorator](response_type& res) + { + decorator(res); + if(! res.count(http::field::server)) + { + BOOST_STATIC_ASSERT(sizeof(BOOST_BEAST_VERSION_STRING) < 20); + static_string<20> s(BOOST_BEAST_VERSION_STRING); + res.set(http::field::server, s); + } + }; + auto err = + [&](error e) + { + result = e; + response_type res; + res.version(req.version()); + res.result(http::status::bad_request); + res.body() = result.message(); + res.prepare_payload(); + decorate(res); + return res; + }; + if(req.version() != 11) + return err(error::bad_http_version); + if(req.method() != http::verb::get) + return err(error::bad_method); + if(! req.count(http::field::host)) + return err(error::no_host); + { + auto const it = req.find(http::field::connection); + if(it == req.end()) + return err(error::no_connection); + if(! http::token_list{it->value()}.exists("upgrade")) + return err(error::no_connection_upgrade); + } + { + auto const it = req.find(http::field::upgrade); + if(it == req.end()) + return err(error::no_upgrade); + if(! http::token_list{it->value()}.exists("websocket")) + return err(error::no_upgrade_websocket); + } + string_view key; + { + auto const it = req.find(http::field::sec_websocket_key); + if(it == req.end()) + return err(error::no_sec_key); + key = it->value(); + if(key.size() > detail::sec_ws_key_type::max_size_n) + return err(error::bad_sec_key); + } + { + auto const it = req.find(http::field::sec_websocket_version); + if(it == req.end()) + return err(error::no_sec_version); + if(it->value() != "13") + { + response_type res; + res.result(http::status::upgrade_required); + res.version(req.version()); + res.set(http::field::sec_websocket_version, "13"); + result = error::bad_sec_version; + res.body() = result.message(); + res.prepare_payload(); + decorate(res); + return res; + } + } + + response_type res; + res.result(http::status::switching_protocols); + res.version(req.version()); + res.set(http::field::upgrade, "websocket"); + res.set(http::field::connection, "upgrade"); + { + detail::sec_ws_accept_type acc; + detail::make_sec_ws_accept(acc, key); + res.set(http::field::sec_websocket_accept, acc); + } + this->build_response_pmd(res, req); + decorate(res); + result = {}; + return res; +} + +//------------------------------------------------------------------------------ + +// Attempt to read a complete frame header. +// Returns `false` if more bytes are needed +template +template +bool +stream::impl_type:: +parse_fh( + detail::frame_header& fh, + DynamicBuffer& b, + error_code& ec) +{ + if(buffer_size(b.data()) < 2) + { + // need more bytes + ec = {}; + return false; + } + buffers_suffix cb{ + b.data()}; + std::size_t need; + { + std::uint8_t tmp[2]; + cb.consume(net::buffer_copy( + net::buffer(tmp), cb)); + fh.len = tmp[1] & 0x7f; + switch(fh.len) + { + case 126: need = 2; break; + case 127: need = 8; break; + default: + need = 0; + } + fh.mask = (tmp[1] & 0x80) != 0; + if(fh.mask) + need += 4; + if(buffer_size(cb) < need) + { + // need more bytes + ec = {}; + return false; + } + fh.op = static_cast< + detail::opcode>(tmp[0] & 0x0f); + fh.fin = (tmp[0] & 0x80) != 0; + fh.rsv1 = (tmp[0] & 0x40) != 0; + fh.rsv2 = (tmp[0] & 0x20) != 0; + fh.rsv3 = (tmp[0] & 0x10) != 0; + } + switch(fh.op) + { + case detail::opcode::binary: + case detail::opcode::text: + if(rd_cont) + { + // new data frame when continuation expected + ec = error::bad_data_frame; + return false; + } + if(fh.rsv2 || fh.rsv3 || + ! this->rd_deflated(fh.rsv1)) + { + // reserved bits not cleared + ec = error::bad_reserved_bits; + return false; + } + break; + + case detail::opcode::cont: + if(! rd_cont) + { + // continuation without an active message + ec = error::bad_continuation; + return false; + } + if(fh.rsv1 || fh.rsv2 || fh.rsv3) + { + // reserved bits not cleared + ec = error::bad_reserved_bits; + return false; + } + break; + + default: + if(detail::is_reserved(fh.op)) + { + // reserved opcode + ec = error::bad_opcode; + return false; + } + if(! fh.fin) + { + // fragmented control message + ec = error::bad_control_fragment; + return false; + } + if(fh.len > 125) + { + // invalid length for control message + ec = error::bad_control_size; + return false; + } + if(fh.rsv1 || fh.rsv2 || fh.rsv3) + { + // reserved bits not cleared + ec = error::bad_reserved_bits; + return false; + } + break; + } + if(role == role_type::server && ! fh.mask) + { + // unmasked frame from client + ec = error::bad_unmasked_frame; + return false; + } + if(role == role_type::client && fh.mask) + { + // masked frame from server + ec = error::bad_masked_frame; + return false; + } + if(detail::is_control(fh.op) && + buffer_size(cb) < need + fh.len) + { + // Make the entire control frame payload + // get read in before we return `true` + return false; + } + switch(fh.len) + { + case 126: + { + std::uint8_t tmp[2]; + BOOST_ASSERT(buffer_size(cb) >= sizeof(tmp)); + cb.consume(net::buffer_copy(net::buffer(tmp), cb)); + fh.len = detail::big_uint16_to_native(&tmp[0]); + if(fh.len < 126) + { + // length not canonical + ec = error::bad_size; + return false; + } + break; + } + case 127: + { + std::uint8_t tmp[8]; + BOOST_ASSERT(buffer_size(cb) >= sizeof(tmp)); + cb.consume(net::buffer_copy(net::buffer(tmp), cb)); + fh.len = detail::big_uint64_to_native(&tmp[0]); + if(fh.len < 65536) + { + // length not canonical + ec = error::bad_size; + return false; + } + break; + } + } + if(fh.mask) + { + std::uint8_t tmp[4]; + BOOST_ASSERT(buffer_size(cb) >= sizeof(tmp)); + cb.consume(net::buffer_copy(net::buffer(tmp), cb)); + fh.key = detail::little_uint32_to_native(&tmp[0]); + detail::prepare_key(rd_key, fh.key); + } + else + { + // initialize this otherwise operator== breaks + fh.key = 0; + } + if(! detail::is_control(fh.op)) + { + if(fh.op != detail::opcode::cont) + { + rd_size = 0; + rd_op = fh.op; + } + else + { + if(rd_size > (std::numeric_limits< + std::uint64_t>::max)() - fh.len) + { + // message size exceeds configured limit + ec = error::message_too_big; + return false; + } + } + if(! this->rd_deflated()) + { + if(rd_msg_max && beast::detail::sum_exceeds( + rd_size, fh.len, rd_msg_max)) + { + // message size exceeds configured limit + ec = error::message_too_big; + return false; + } + } + rd_cont = ! fh.fin; + rd_remain = fh.len; + } + b.consume(b.size() - buffer_size(cb)); + ec = {}; + return true; +} + +template +template +void +stream::impl_type:: +write_ping(DynamicBuffer& db, + detail::opcode code, ping_data const& data) +{ + detail::frame_header fh; + fh.op = code; + fh.fin = true; + fh.rsv1 = false; + fh.rsv2 = false; + fh.rsv3 = false; + fh.len = data.size(); + fh.mask = role == role_type::client; + if(fh.mask) + fh.key = create_mask(); + detail::write(db, fh); + if(data.empty()) + return; + detail::prepared_key key; + if(fh.mask) + detail::prepare_key(key, fh.key); + auto mb = db.prepare(data.size()); + net::buffer_copy(mb, + net::const_buffer( + data.data(), data.size())); + if(fh.mask) + detail::mask_inplace(mb, key); + db.commit(data.size()); +} + +template +template +void +stream::impl_type:: +write_close(DynamicBuffer& db, close_reason const& cr) +{ + using namespace boost::endian; + detail::frame_header fh; + fh.op = detail::opcode::close; + fh.fin = true; + fh.rsv1 = false; + fh.rsv2 = false; + fh.rsv3 = false; + fh.len = cr.code == close_code::none ? + 0 : 2 + cr.reason.size(); + if(role == role_type::client) + { + fh.mask = true; + fh.key = create_mask(); + } + else + { + fh.mask = false; + } + detail::write(db, fh); + if(cr.code != close_code::none) + { + detail::prepared_key key; + if(fh.mask) + detail::prepare_key(key, fh.key); + { + std::uint8_t tmp[2]; + ::new(&tmp[0]) big_uint16_buf_t{ + (std::uint16_t)cr.code}; + auto mb = db.prepare(2); + net::buffer_copy(mb, + net::buffer(tmp)); + if(fh.mask) + detail::mask_inplace(mb, key); + db.commit(2); + } + if(! cr.reason.empty()) + { + auto mb = db.prepare(cr.reason.size()); + net::buffer_copy(mb, + net::const_buffer( + cr.reason.data(), cr.reason.size())); + if(fh.mask) + detail::mask_inplace(mb, key); + db.commit(cr.reason.size()); + } + } +} + } // websocket } // beast } // boost diff --git a/include/boost/beast/websocket/impl/write.hpp b/include/boost/beast/websocket/impl/write.hpp index 9c562a1d..cc6ab913 100644 --- a/include/boost/beast/websocket/impl/write.hpp +++ b/include/boost/beast/websocket/impl/write.hpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -51,7 +52,7 @@ class stream::write_some_op do_deflate }; - stream& ws_; + boost::weak_ptr wp_; buffers_suffix cb_; detail::frame_header fh_; detail::prepared_key key_; @@ -69,17 +70,18 @@ public: template write_some_op( Handler_&& h, - stream& ws, + boost::shared_ptr const& sp, bool fin, Buffers const& bs) : beast::async_op_base>( - std::forward(h), ws.get_executor()) - , ws_(ws) + std::forward(h), + sp->stream.get_executor()) + , wp_(sp) , cb_(bs) , fin_(fin) { - auto& impl = *ws_.impl_; + auto& impl = *sp; // Set up the outgoing frame header if(! impl.wr_cont) @@ -157,7 +159,11 @@ operator()( using beast::detail::clamp; std::size_t n; net::mutable_buffer b; - auto& impl = *ws_.impl_; + auto sp = wp_.lock(); + if(! sp) + return this->invoke(cont, + net::error::operation_aborted, 0); + auto& impl = *sp; BOOST_ASIO_CORO_REENTER(*this) { // Acquire the write lock @@ -165,7 +171,7 @@ operator()( { do_suspend: BOOST_ASIO_CORO_YIELD - impl.paused_wr.emplace(std::move(*this)); + impl.op_wr.emplace(std::move(*this)); impl.wr_block.lock(this); BOOST_ASIO_CORO_YIELD net::post(std::move(*this)); @@ -228,9 +234,10 @@ operator()( // Give up the write lock in between each frame // so that outgoing control frames might be sent. impl.wr_block.unlock(this); - if( impl.paused_close.maybe_invoke() || - impl.paused_rd.maybe_invoke() || - impl.paused_ping.maybe_invoke()) + if( impl.op_close.maybe_invoke() + || impl.op_idle_ping.maybe_invoke() + || impl.op_rd.maybe_invoke() + || impl.op_ping.maybe_invoke()) { BOOST_ASSERT(impl.wr_block.is_locked()); goto do_suspend; @@ -248,7 +255,7 @@ operator()( remain_ = beast::buffer_size(cb_); fh_.fin = fin_; fh_.len = remain_; - fh_.key = ws_.create_mask(); + fh_.key = impl.create_mask(); detail::prepare_key(key_, fh_.key); impl.wr_fb.clear(); detail::write( @@ -302,7 +309,7 @@ operator()( n = clamp(remain_, impl.wr_buf_size); remain_ -= n; fh_.len = n; - fh_.key = ws_.create_mask(); + fh_.key = impl.create_mask(); fh_.fin = fin_ ? remain_ == 0 : false; detail::prepare_key(key_, fh_.key); net::buffer_copy(net::buffer( @@ -330,9 +337,10 @@ operator()( // Give up the write lock in between each frame // so that outgoing control frames might be sent. impl.wr_block.unlock(this); - if( impl.paused_close.maybe_invoke() || - impl.paused_rd.maybe_invoke() || - impl.paused_ping.maybe_invoke()) + if( impl.op_close.maybe_invoke() + || impl.op_idle_ping.maybe_invoke() + || impl.op_rd.maybe_invoke() + || impl.op_ping.maybe_invoke()) { BOOST_ASSERT(impl.wr_block.is_locked()); goto do_suspend; @@ -365,7 +373,7 @@ operator()( } if(fh_.mask) { - fh_.key = ws_.create_mask(); + fh_.key = impl.create_mask(); detail::prepared_key key; detail::prepare_key(key, fh_.key); detail::mask_inplace(b, key); @@ -391,9 +399,10 @@ operator()( // Give up the write lock in between each frame // so that outgoing control frames might be sent. impl.wr_block.unlock(this); - if( impl.paused_close.maybe_invoke() || - impl.paused_rd.maybe_invoke() || - impl.paused_ping.maybe_invoke()) + if( impl.op_close.maybe_invoke() + || impl.op_idle_ping.maybe_invoke() + || impl.op_rd.maybe_invoke() + || impl.op_ping.maybe_invoke()) { BOOST_ASSERT(impl.wr_block.is_locked()); goto do_suspend; @@ -413,15 +422,10 @@ operator()( upcall: impl.wr_block.unlock(this); - impl.paused_close.maybe_invoke() - || impl.paused_rd.maybe_invoke() - || impl.paused_ping.maybe_invoke(); - if(! cont) - { - BOOST_ASIO_CORO_YIELD - net::post(bind_front_handler( - std::move(*this), ec, bytes_transferred_)); - } + impl.op_close.maybe_invoke() + || impl.op_idle_ping.maybe_invoke() + || impl.op_rd.maybe_invoke() + || impl.op_ping.maybe_invoke(); this->invoke(cont, ec, bytes_transferred_); } } @@ -507,7 +511,7 @@ write_some(bool fin, } if(fh.mask) { - fh.key = this->create_mask(); + fh.key = this->impl_->create_mask(); detail::prepared_key key; detail::prepare_key(key, fh.key); detail::mask_inplace(b, key); @@ -581,7 +585,7 @@ write_some(bool fin, // mask, no autofrag fh.fin = fin; fh.len = remain; - fh.key = this->create_mask(); + fh.key = this->impl_->create_mask(); detail::prepared_key key; detail::prepare_key(key, fh.key); detail::fh_buffer fh_buf; @@ -629,7 +633,7 @@ write_some(bool fin, ConstBufferSequence> cb(buffers); for(;;) { - fh.key = this->create_mask(); + fh.key = this->impl_->create_mask(); detail::prepared_key key; detail::prepare_key(key, fh.key); auto const n = @@ -676,7 +680,7 @@ async_write_some(bool fin, WriteHandler, void(error_code, std::size_t)); write_some_op( - std::move(init.completion_handler), *this, fin, bs); + std::move(init.completion_handler), impl_, fin, bs); return init.result.get(); } @@ -731,7 +735,7 @@ async_write( WriteHandler, void(error_code, std::size_t)); write_some_op( - std::move(init.completion_handler), *this, true, bs); + std::move(init.completion_handler), impl_, true, bs); return init.result.get(); } diff --git a/include/boost/beast/websocket/stream.hpp b/include/boost/beast/websocket/stream.hpp index f8d7c9c3..208cbaae 100644 --- a/include/boost/beast/websocket/stream.hpp +++ b/include/boost/beast/websocket/stream.hpp @@ -15,15 +15,17 @@ #include #include #include +#include #include -#include -#include #include +#include +#include #include #include #include #include #include +#include #include #include #include @@ -81,17 +83,16 @@ class frame_test; operations are performed within the same implicit or explicit strand. @par Example - - To use the @ref stream template with an `ip::tcp::socket`, - you would write: - + To declare the @ref stream object with a @ref tcp_stream in a + multi-threaded asynchronous program using a strand, you may write: @code - websocket::stream ws{io_context}; + websocket::stream> ws{net::io_context::strand(ioc)}; @endcode - Alternatively, you can write: + Alternatively, for a single-threaded or synchronous application + you may write: @code - ip::tcp::socket sock{io_context}; - websocket::stream ws{sock}; + websocket::stream> ws(ioc); @endcode @tparam NextLayer The type representing the next layer, to which @@ -130,14 +131,14 @@ template< bool deflateSupported> class stream #if ! BOOST_BEAST_DOXYGEN - : private detail::stream_base + : private stream_base #endif { friend class close_test; friend class frame_test; friend class ping_test; - friend class read1_test; friend class read2_test; + friend class read3_test; friend class stream_test; friend class write_test; @@ -153,7 +154,7 @@ class stream struct impl_type; - std::shared_ptr impl_; + boost::shared_ptr impl_; using time_point = typename std::chrono::steady_clock::time_point; @@ -351,6 +352,20 @@ public: // //-------------------------------------------------------------------------- +#if BOOST_BEAST_DOXYGEN + template + void + get_option(Option& opt); + + template + void + set_option(Option const& opt); +#else + + void get_option(timeout& opt); + void set_option(timeout const& opt); +#endif + /** Set the permessage-deflate extension options @throws invalid_argument if `deflateSupported == false`, and either @@ -3478,6 +3493,7 @@ private: template class close_op; template class handshake_op; template class ping_op; + template class auto_ping_op; template class read_some_op; template class read_op; template class response_op; @@ -3487,55 +3503,14 @@ private: static void default_decorate_req(request_type&) {} static void default_decorate_res(response_type&) {} - template - bool - parse_fh( - detail::frame_header& fh, - DynamicBuffer& b, - error_code& ec); - - template - void - write_close(DynamicBuffer& b, close_reason const& rc); - - template - void - write_ping(DynamicBuffer& b, - detail::opcode op, ping_data const& data); - - // - // upgrade - // - - template - request_type - build_request(detail::sec_ws_key_type& key, - string_view host, - string_view target, - Decorator const& decorator); - - template< - class Body, class Allocator, class Decorator> - response_type - build_response( - http::request> const& req, - Decorator const& decorator, - error_code& ec); - - void - on_response( - response_type const& res, - detail::sec_ws_key_type const& key, - error_code& ec); - // // accept / handshake // - template + template void do_accept( + Buffers const& buffers, Decorator const& decorator, error_code& ec); diff --git a/include/boost/beast/websocket/stream_base.hpp b/include/boost/beast/websocket/stream_base.hpp new file mode 100644 index 00000000..62c3ac1b --- /dev/null +++ b/include/boost/beast/websocket/stream_base.hpp @@ -0,0 +1,139 @@ +// +// 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 + +namespace boost { +namespace beast { +namespace websocket { + +/** This class is used as a base for the @ref websocket::stream class template to group common types and constants. +*/ +struct stream_base +{ + /// The type used to represent durations + using duration = + std::chrono::steady_clock::duration; + + /// The type used to represent time points + using time_point = + std::chrono::steady_clock::time_point; + + /// Returns the special time_point value meaning "never" + static + constexpr + time_point + never() noexcept + { + return (time_point::max)(); + } + + /// Returns the special duration value meaning "none" + static + constexpr + duration + none() noexcept + { + return (duration::max)(); + } + + /** Stream option to control the behavior of websocket timeouts. + + Timeout features are available for asynchronous operations only. + */ + struct timeout + { + /** Time limit on handshake, accept, and close operations: + + This value whether or not there is a time limit, and the + duration of that time limit, for asynchronous handshake, + accept, and close operations. If this is equal to the + value @ref none then there will be no time limit. Otherwise, + if any of the applicable operations takes longer than this + amount of time, the operation will be canceled and a + timeout error delivered to the completion handler. + */ + duration handshake_timeout = none(); + + /** The time limit after which a connection is considered idle. + */ + duration idle_timeout = none(); + + /** Automatic ping setting. + + If the idle interval is set, this setting affects the + behavior of the stream when no data is received for the + timeout interval as follows: + + @li When `keep_alive_pings` is `true`, an idle ping will be + sent automatically. If another timeout interval elapses + with no received data then the connection will be closed. + An outstanding read operation must be pending, which will + complete immediately the error @ref beast::error::timeout. + + @li When `keep_alive_pings` is `false`, the connection will be closed. + An outstanding read operation must be pending, which will + complete immediately the error @ref beast::error::timeout. + */ + bool keep_alive_pings = false; + }; + + /** Construct timeout settings with suggested values for a role. + + This constructs the timeout settings with a predefined set + of values which varies depending on the desired role. The + values are selected upon construction, regardless of the + current or actual role in use on the stream. + + @param role The role of the websocket stream + (@ref role_type::client or @ref role_type::server). + */ + static + timeout + suggested_settings(role_type role) noexcept + { + timeout opt; + switch(role) + { + case role_type::client: + opt.handshake_timeout = std::chrono::seconds(30); + opt.idle_timeout = none(); + opt.keep_alive_pings = false; + break; + + case role_type::server: + opt.handshake_timeout = std::chrono::seconds(30); + opt.idle_timeout = std::chrono::seconds(300); + opt.keep_alive_pings = true; + break; + } + } + +protected: + enum class status + { + //none, + handshake, + open, + closing, + closed, + failed // VFALCO Is this needed? + }; +}; + +} // websocket +} // beast +} // boost + +#endif diff --git a/test/beast/websocket/CMakeLists.txt b/test/beast/websocket/CMakeLists.txt index 6beb5279..9f633b23 100644 --- a/test/beast/websocket/CMakeLists.txt +++ b/test/beast/websocket/CMakeLists.txt @@ -17,7 +17,7 @@ add_executable (tests-beast-websocket ${TEST_MAIN} Jamfile _detail_prng.cpp - _detail_stream_base.cpp + _detail_impl_base.cpp test.hpp _detail_prng.cpp accept.cpp @@ -29,6 +29,7 @@ add_executable (tests-beast-websocket ping.cpp read1.cpp read2.cpp + read3.cpp rfc6455.cpp role.cpp stream.cpp diff --git a/test/beast/websocket/Jamfile b/test/beast/websocket/Jamfile index c296b7df..5dcdad4c 100644 --- a/test/beast/websocket/Jamfile +++ b/test/beast/websocket/Jamfile @@ -8,8 +8,8 @@ # local SOURCES = + _detail_impl_base.cpp _detail_prng.cpp - _detail_stream_base.cpp accept.cpp close.cpp error.cpp @@ -19,6 +19,7 @@ local SOURCES = ping.cpp read1.cpp read2.cpp + read3.cpp rfc6455.cpp role.cpp stream.cpp diff --git a/test/beast/websocket/_detail_stream_base.cpp b/test/beast/websocket/_detail_impl_base.cpp similarity index 70% rename from test/beast/websocket/_detail_stream_base.cpp rename to test/beast/websocket/_detail_impl_base.cpp index 0588478f..13e3ad51 100644 --- a/test/beast/websocket/_detail_stream_base.cpp +++ b/test/beast/websocket/_detail_impl_base.cpp @@ -1,5 +1,5 @@ // -// Copyright (w) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// 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) @@ -8,4 +8,4 @@ // // Test that header file is self-contained. -#include +#include diff --git a/test/beast/websocket/_detail_prng.cpp b/test/beast/websocket/_detail_prng.cpp index b1bfa052..45a9ebb2 100644 --- a/test/beast/websocket/_detail_prng.cpp +++ b/test/beast/websocket/_detail_prng.cpp @@ -1,5 +1,5 @@ // -// Copyright (w) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// 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) diff --git a/test/beast/websocket/accept.cpp b/test/beast/websocket/accept.cpp index e900989e..cfdf77c7 100644 --- a/test/beast/websocket/accept.cpp +++ b/test/beast/websocket/accept.cpp @@ -1,5 +1,5 @@ // -// Copyright (w) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// 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) @@ -10,6 +10,7 @@ // Test that header file is self-contained. #include +#include #include "test.hpp" #include @@ -613,6 +614,94 @@ public: ); } + void + testTimeout() + { + using tcp = net::ip::tcp; + + net::io_context ioc; + + // success + + { + stream ws1(ioc); + stream ws2(ioc); + test::connect(ws1.next_layer(), ws2.next_layer()); + + ws1.async_handshake("test", "/", test::success_handler()); + ws2.async_accept(test::success_handler()); + test::run_for(ioc, std::chrono::seconds(1)); + } + + { + stream ws1(ioc); + stream ws2(ioc); + test::connect(ws1.next_layer(), ws2.next_layer()); + + ws1.async_handshake("test", "/", test::success_handler()); + ws2.async_accept(test::success_handler()); + test::run_for(ioc, std::chrono::seconds(1)); + } + + // success, timeout enabled + + { + stream ws1(ioc); + stream ws2(ioc); + test::connect(ws1.next_layer(), ws2.next_layer()); + + ws1.set_option(stream_base::timeout{ + std::chrono::milliseconds(50), + stream_base::none(), + false}); + ws1.async_accept(test::success_handler()); + ws2.async_handshake("test", "/", test::success_handler()); + test::run_for(ioc, std::chrono::seconds(1)); + } + + { + stream ws1(ioc); + stream ws2(ioc); + test::connect(ws1.next_layer(), ws2.next_layer()); + + ws1.set_option(stream_base::timeout{ + std::chrono::milliseconds(50), + stream_base::none(), + false}); + ws1.async_accept(test::success_handler()); + ws2.async_handshake("test", "/", test::success_handler()); + test::run_for(ioc, std::chrono::seconds(1)); + } + + // timeout + + { + stream ws1(ioc); + stream ws2(ioc); + test::connect(ws1.next_layer(), ws2.next_layer()); + + ws1.set_option(stream_base::timeout{ + std::chrono::milliseconds(50), + stream_base::none(), + false}); + ws1.async_accept(test::fail_handler(beast::error::timeout)); + test::run_for(ioc, std::chrono::seconds(1)); + } + + { + stream ws1(ioc); + stream ws2(ioc); + test::connect(ws1.next_layer(), ws2.next_layer()); + + ws1.set_option(stream_base::timeout{ + std::chrono::milliseconds(50), + stream_base::none(), + false}); + ws1.async_accept(test::fail_handler(beast::error::timeout)); + test::run_for(ioc, std::chrono::seconds(1)); + } + } + void testMoveOnly() { @@ -649,6 +738,7 @@ public: run() override { testAccept(); + testTimeout(); testMoveOnly(); testAsioHandlerInvoke(); } diff --git a/test/beast/websocket/close.cpp b/test/beast/websocket/close.cpp index aa945797..05a918d2 100644 --- a/test/beast/websocket/close.cpp +++ b/test/beast/websocket/close.cpp @@ -1,5 +1,5 @@ // -// Copyright (w) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// 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) @@ -10,6 +10,8 @@ // Test that header file is self-contained. #include +#include +#include #include "test.hpp" #include @@ -168,6 +170,114 @@ public: }); } + void + testTimeout() + { + using tcp = net::ip::tcp; + + net::io_context ioc; + + // success + + { + stream ws1(ioc); + stream ws2(ioc); + test::connect(ws1.next_layer(), ws2.next_layer()); + ws1.async_handshake("test", "/", test::success_handler()); + ws2.async_accept(test::success_handler()); + test::run(ioc); + + ws1.async_close({}, test::success_handler()); + ws2.async_close({}, test::success_handler()); + test::run(ioc); + } + + { + stream ws1(ioc); + stream ws2(ioc); + test::connect(ws1.next_layer(), ws2.next_layer()); + ws1.async_handshake("test", "/", test::success_handler()); + ws2.async_accept(test::success_handler()); + test::run(ioc); + + ws1.async_close({}, test::success_handler()); + ws2.async_close({}, test::success_handler()); + test::run(ioc); + } + + // success, timeout enabled + + { + stream ws1(ioc); + stream ws2(ioc); + test::connect(ws1.next_layer(), ws2.next_layer()); + ws1.async_handshake("test", "/", test::success_handler()); + ws2.async_accept(test::success_handler()); + test::run(ioc); + + ws1.set_option(stream_base::timeout{ + std::chrono::milliseconds(50), + stream_base::none(), + false}); + ws1.async_close({}, test::success_handler()); + ws2.async_close({}, test::success_handler()); + test::run(ioc); + } + + { + stream ws1(ioc); + stream ws2(ioc); + test::connect(ws1.next_layer(), ws2.next_layer()); + ws1.async_handshake("test", "/", test::success_handler()); + ws2.async_accept(test::success_handler()); + test::run(ioc); + + ws1.set_option(stream_base::timeout{ + std::chrono::milliseconds(50), + stream_base::none(), + false}); + ws1.async_close({}, test::success_handler()); + ws2.async_close({}, test::success_handler()); + test::run(ioc); + } + + // timeout + + { + stream ws1(ioc); + stream ws2(ioc); + test::connect(ws1.next_layer(), ws2.next_layer()); + ws1.async_handshake("test", "/", test::success_handler()); + ws2.async_accept(test::success_handler()); + test::run(ioc); + + ws1.set_option(stream_base::timeout{ + std::chrono::milliseconds(50), + stream_base::none(), + false}); + ws1.async_close({}, test::fail_handler( + beast::error::timeout)); + test::run(ioc); + } + + { + stream ws1(ioc); + stream ws2(ioc); + test::connect(ws1.next_layer(), ws2.next_layer()); + ws1.async_handshake("test", "/", test::success_handler()); + ws2.async_accept(test::success_handler()); + test::run(ioc); + + ws1.set_option(stream_base::timeout{ + std::chrono::milliseconds(50), + stream_base::none(), + false}); + ws1.async_close({}, test::fail_handler( + beast::error::timeout)); + test::run(ioc); + } + } + void testSuspend() { @@ -625,28 +735,13 @@ public: } }; - void - testAsioHandlerInvoke() - { - // make sure things compile, also can set a - // breakpoint in asio_handler_invoke to make sure - // it is instantiated. - net::io_context ioc; - net::strand< - net::io_context::executor_type> s( - ioc.get_executor()); - stream ws{ioc}; - ws.async_close({}, net::bind_executor( - s, copyable_handler{})); - } - void run() override { testClose(); + testTimeout(); testSuspend(); testMoveOnly(); - testAsioHandlerInvoke(); } }; diff --git a/test/beast/websocket/handshake.cpp b/test/beast/websocket/handshake.cpp index 94653050..08dc8f72 100644 --- a/test/beast/websocket/handshake.cpp +++ b/test/beast/websocket/handshake.cpp @@ -1,5 +1,5 @@ // -// Copyright (w) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// 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) @@ -10,6 +10,9 @@ // Test that header file is self-contained. #include +#include +#include + #include "test.hpp" #include @@ -235,6 +238,96 @@ public: ); } + void + testTimeout() + { + using tcp = net::ip::tcp; + + net::io_context ioc; + + // success + + { + stream ws1(ioc); + stream ws2(ioc); + test::connect(ws1.next_layer(), ws2.next_layer()); + + ws1.async_handshake("test", "/", test::success_handler()); + ws2.async_accept(test::success_handler()); + test::run_for(ioc, std::chrono::seconds(1)); + } + + { + stream ws1(ioc); + stream ws2(ioc); + test::connect(ws1.next_layer(), ws2.next_layer()); + + ws1.async_handshake("test", "/", test::success_handler()); + ws2.async_accept(test::success_handler()); + test::run_for(ioc, std::chrono::seconds(1)); + } + + // success, timeout enabled + + { + stream ws1(ioc); + stream ws2(ioc); + test::connect(ws1.next_layer(), ws2.next_layer()); + + ws1.set_option(stream_base::timeout{ + std::chrono::milliseconds(50), + stream_base::none(), + false}); + ws1.async_handshake("test", "/", test::success_handler()); + ws2.async_accept(test::success_handler()); + test::run_for(ioc, std::chrono::seconds(1)); + } + + { + stream ws1(ioc); + stream ws2(ioc); + test::connect(ws1.next_layer(), ws2.next_layer()); + + ws1.set_option(stream_base::timeout{ + std::chrono::milliseconds(50), + stream_base::none(), + false}); + ws1.async_handshake("test", "/", test::success_handler()); + ws2.async_accept(test::success_handler()); + test::run_for(ioc, std::chrono::seconds(1)); + } + + // timeout + + { + stream ws1(ioc); + stream ws2(ioc); + test::connect(ws1.next_layer(), ws2.next_layer()); + + ws1.set_option(stream_base::timeout{ + std::chrono::milliseconds(50), + stream_base::none(), + false}); + ws1.async_handshake("test", "/", + test::fail_handler(beast::error::timeout)); + test::run_for(ioc, std::chrono::seconds(1)); + } + + { + stream ws1(ioc); + stream ws2(ioc); + test::connect(ws1.next_layer(), ws2.next_layer()); + + ws1.set_option(stream_base::timeout{ + std::chrono::milliseconds(50), + stream_base::none(), + false}); + ws1.async_handshake("test", "/", + test::fail_handler(beast::error::timeout)); + test::run_for(ioc, std::chrono::seconds(1)); + } + } + // Compression Extensions for WebSocket // // https://tools.ietf.org/html/rfc7692 @@ -504,31 +597,15 @@ public: } }; - void - testAsioHandlerInvoke() - { - // make sure things compile, also can set a - // breakpoint in asio_handler_invoke to make sure - // it is instantiated. - net::io_context ioc; - net::strand< - net::io_context::executor_type> s( - ioc.get_executor()); - stream ws{ioc}; - ws.async_handshake("localhost", "/", - net::bind_executor( - s, copyable_handler{})); - } - void run() override { testHandshake(); + testTimeout(); testExtRead(); testExtWrite(); testExtNegotiate(); testMoveOnly(); - testAsioHandlerInvoke(); } }; diff --git a/test/beast/websocket/ping.cpp b/test/beast/websocket/ping.cpp index 4e05b36d..91977c53 100644 --- a/test/beast/websocket/ping.cpp +++ b/test/beast/websocket/ping.cpp @@ -1,5 +1,5 @@ // -// Copyright (w) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// 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) @@ -440,28 +440,12 @@ public: } }; - void - testAsioHandlerInvoke() - { - // make sure things compile, also can set a - // breakpoint in asio_handler_invoke to make sure - // it is instantiated. - net::io_context ioc; - net::strand< - net::io_context::executor_type> s( - ioc.get_executor()); - stream ws{ioc}; - ws.async_ping({}, net::bind_executor( - s, copyable_handler{})); - } - void run() override { testPing(); testSuspend(); testMoveOnly(); - testAsioHandlerInvoke(); } }; diff --git a/test/beast/websocket/read1.cpp b/test/beast/websocket/read1.cpp index 6f279dae..2902cb54 100644 --- a/test/beast/websocket/read1.cpp +++ b/test/beast/websocket/read1.cpp @@ -1,5 +1,5 @@ - -// Copyright (w) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// 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) @@ -10,672 +10,139 @@ // Test that header file is self-contained. #include -#include "test.hpp" - -#include - -#include -#if BOOST_WORKAROUND(BOOST_GCC, < 80200) -#define BOOST_BEAST_SYMBOL_HIDDEN __attribute__ ((visibility("hidden"))) -#else -#define BOOST_BEAST_SYMBOL_HIDDEN -#endif +#include +#include +#include namespace boost { namespace beast { namespace websocket { -class BOOST_BEAST_SYMBOL_HIDDEN read1_test - : public websocket_test_suite +class read1_test : public unit_test::suite { public: - template void - doReadTest( - Wrap const& w, - ws_type_t& ws, - close_code code) + testTimeout() { - try + using tcp = net::ip::tcp; + + net::io_context ioc; + + // success + { - multi_buffer b; - w.read(ws, b); - fail("", __FILE__, __LINE__); - } - catch(system_error const& se) - { - if(se.code() != error::closed) - throw; - BEAST_EXPECT( - ws.reason().code == code); - } - } + stream ws1(ioc); + stream ws2(ioc); + test::connect(ws1.next_layer(), ws2.next_layer()); + ws1.async_handshake("test", "/", test::success_handler()); + ws2.async_accept(test::success_handler()); + test::run(ioc); - template - void - doFailTest( - Wrap const& w, - ws_type_t& ws, - error_code ev) - { - try - { - multi_buffer b; - w.read(ws, b); - fail("", __FILE__, __LINE__); - } - catch(system_error const& se) - { - if(se.code() != ev) - throw; - } - } - - template - void - doTestRead(Wrap const& w) - { - permessage_deflate pmd; - pmd.client_enable = false; - pmd.server_enable = false; - - // already closed - { - echo_server es{log}; - stream ws{ioc_}; - ws.next_layer().connect(es.stream()); - ws.handshake("localhost", "/"); - ws.close({}); - try - { - multi_buffer b; - w.read(ws, b); - fail("", __FILE__, __LINE__); - } - catch(system_error const& se) - { - BEAST_EXPECTS( - se.code() == net::error::operation_aborted, - se.code().message()); - } - } - - // empty, fragmented message - doTest(pmd, - [&](ws_type_t& ws) - { - ws.next_layer().append( - string_view( - "\x01\x00" "\x80\x00", 4)); - multi_buffer b; - w.read(ws, b); - BEAST_EXPECT(b.size() == 0); - }); - - // two part message - // triggers "fill the read buffer first" - doTest(pmd, - [&](ws_type_t& ws) - { - w.write_raw(ws, sbuf( - "\x01\x81\xff\xff\xff\xff")); - w.write_raw(ws, sbuf( - "\xd5")); - w.write_raw(ws, sbuf( - "\x80\x81\xff\xff\xff\xff\xd5")); - multi_buffer b; - w.read(ws, b); - BEAST_EXPECT(buffers_to_string(b.data()) == "**"); - }); - - // ping - doTest(pmd, - [&](ws_type_t& ws) - { - put(ws.next_layer().buffer(), cbuf( - 0x89, 0x00)); - bool invoked = false; - ws.control_callback( - [&](frame_type kind, string_view) - { - BEAST_EXPECT(! invoked); - BEAST_EXPECT(kind == frame_type::ping); - invoked = true; - }); - w.write(ws, sbuf("Hello")); - multi_buffer b; - w.read(ws, b); - BEAST_EXPECT(invoked); - BEAST_EXPECT(ws.got_text()); - BEAST_EXPECT(buffers_to_string(b.data()) == "Hello"); - }); - - // ping - doTest(pmd, - [&](ws_type_t& ws) - { - put(ws.next_layer().buffer(), cbuf( - 0x88, 0x00)); - bool invoked = false; - ws.control_callback( - [&](frame_type kind, string_view) - { - BEAST_EXPECT(! invoked); - BEAST_EXPECT(kind == frame_type::close); - invoked = true; - }); - w.write(ws, sbuf("Hello")); - doReadTest(w, ws, close_code::none); - }); - - // ping then message - doTest(pmd, - [&](ws_type_t& ws) - { - bool once = false; - ws.control_callback( - [&](frame_type kind, string_view s) - { - BEAST_EXPECT(kind == frame_type::pong); - BEAST_EXPECT(! once); - once = true; - BEAST_EXPECT(s == ""); - }); - w.ping(ws, ""); - ws.binary(true); - w.write(ws, sbuf("Hello")); - multi_buffer b; - w.read(ws, b); - BEAST_EXPECT(once); - BEAST_EXPECT(ws.got_binary()); - BEAST_EXPECT(buffers_to_string(b.data()) == "Hello"); - }); - - // ping then fragmented message - doTest(pmd, - [&](ws_type_t& ws) - { - bool once = false; - ws.control_callback( - [&](frame_type kind, string_view s) - { - BEAST_EXPECT(kind == frame_type::pong); - BEAST_EXPECT(! once); - once = true; - BEAST_EXPECT(s == "payload"); - }); - ws.ping("payload"); - w.write_some(ws, false, sbuf("Hello, ")); - w.write_some(ws, false, sbuf("")); - w.write_some(ws, true, sbuf("World!")); - multi_buffer b; - w.read(ws, b); - BEAST_EXPECT(once); - BEAST_EXPECT(buffers_to_string(b.data()) == "Hello, World!"); - }); - - // masked message, big - doStreamLoop([&](test::stream& ts) - { - echo_server es{log, kind::async_client}; - ws_type_t ws{ts}; - ws.next_layer().connect(es.stream()); - ws.set_option(pmd); - es.async_handshake(); - try - { - w.accept(ws); - std::string const s(2000, '*'); - ws.auto_fragment(false); - ws.binary(false); - w.write(ws, net::buffer(s)); - multi_buffer b; - w.read(ws, b); - BEAST_EXPECT(ws.got_text()); - BEAST_EXPECT(buffers_to_string(b.data()) == s); - ws.next_layer().close(); - } - catch(...) - { - ts.close(); - throw; - } - }); - - // close - doFailLoop([&](test::fail_count& fc) - { - echo_server es{log, kind::async}; - net::io_context ioc; - stream ws{ioc, fc}; - ws.next_layer().connect(es.stream()); - ws.handshake("localhost", "/"); - // Cause close to be received - es.async_close(); - std::size_t count = 0; - multi_buffer b; - ws.async_read(b, - [&](error_code ec, std::size_t) - { - ++count; - if(ec != error::closed) - BOOST_THROW_EXCEPTION( - system_error{ec}); - }); - ioc.run(); - BEAST_EXPECT(count == 1); - }); - - // already closed - doTest(pmd, - [&](ws_type_t& ws) - { - w.close(ws, {}); - multi_buffer b; - doFailTest(w, ws, - net::error::operation_aborted); - }); - - // buffer overflow - doTest(pmd, - [&](ws_type_t& ws) - { - std::string const s = "Hello, world!"; - ws.auto_fragment(false); - ws.binary(false); - w.write(ws, net::buffer(s)); - try - { - multi_buffer b(3); - w.read(ws, b); - fail("", __FILE__, __LINE__); - } - catch(system_error const& se) - { - if(se.code() != error::buffer_overflow) - throw; - } - }); - - // bad utf8, big - doTest(pmd, - [&](ws_type_t& ws) - { - auto const s = std::string(2000, '*') + - random_string(); - ws.text(true); - w.write(ws, net::buffer(s)); - doReadTest(w, ws, close_code::bad_payload); - }); - - // invalid fixed frame header - doTest(pmd, - [&](ws_type_t& ws) - { - w.write_raw(ws, cbuf( - 0x8f, 0x80, 0xff, 0xff, 0xff, 0xff)); - doReadTest(w, ws, close_code::protocol_error); - }); - - // bad close - doTest(pmd, - [&](ws_type_t& ws) - { - put(ws.next_layer().buffer(), cbuf( - 0x88, 0x02, 0x03, 0xed)); - doFailTest(w, ws, error::bad_close_code); - }); - - // message size above 2^64 - doTest(pmd, - [&](ws_type_t& ws) - { - w.write_some(ws, false, sbuf("*")); - w.write_raw(ws, cbuf( - 0x80, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff)); - doReadTest(w, ws, close_code::too_big); - }); - - // message size exceeds max - doTest(pmd, - [&](ws_type_t& ws) - { - ws.read_message_max(1); - w.write(ws, sbuf("**")); - doFailTest(w, ws, error::message_too_big); - }); - - // bad utf8 - doTest(pmd, - [&](ws_type_t& ws) - { - put(ws.next_layer().buffer(), cbuf( - 0x81, 0x06, 0x03, 0xea, 0xf0, 0x28, 0x8c, 0xbc)); - doFailTest(w, ws, error::bad_frame_payload); - }); - - // incomplete utf8 - doTest(pmd, - [&](ws_type_t& ws) - { - std::string const s = - "Hello, world!" "\xc0"; - w.write(ws, net::buffer(s)); - doReadTest(w, ws, close_code::bad_payload); - }); - - // incomplete utf8, big - doTest(pmd, - [&](ws_type_t& ws) - { - std::string const s = - "\x81\x7e\x0f\xa1" + - std::string(4000, '*') + "\xc0"; - ws.next_layer().append(s); - multi_buffer b; - try - { - do - { - b.commit(w.read_some(ws, b.prepare(4000))); - } - while(! ws.is_message_done()); - } - catch(system_error const& se) - { - if(se.code() != error::bad_frame_payload) - throw; - } - }); - - // close frames - { - auto const check = - [&](error_code ev, string_view s) - { - echo_server es{log}; - stream ws{ioc_}; - ws.next_layer().connect(es.stream()); - w.handshake(ws, "localhost", "/"); - ws.next_layer().append(s); - static_buffer<1> b; - try - { - w.read(ws, b); - fail("", __FILE__, __LINE__); - } - catch(system_error const& se) - { - BEAST_EXPECTS(se.code() == ev, - se.code().message()); - } - ws.next_layer().close(); - }; - - // payload length 1 - check(error::bad_close_size, - "\x88\x01\x01"); - - // invalid close code 1005 - check(error::bad_close_code, - "\x88\x02\x03\xed"); - - // invalid utf8 - check(error::bad_close_payload, - "\x88\x06\xfc\x15\x0f\xd7\x73\x43"); - - // good utf8 - check(error::closed, - "\x88\x06\xfc\x15utf8"); - } - } - - template - void - doTestReadDeflate(Wrap const& w) - { - permessage_deflate pmd; - pmd.client_enable = true; - pmd.server_enable = true; - pmd.client_max_window_bits = 9; - pmd.server_max_window_bits = 9; - pmd.compLevel = 1; - - // message size limit - doTest(pmd, - [&](ws_type_t& ws) - { - std::string const s = std::string(128, '*'); - w.write(ws, net::buffer(s)); - ws.read_message_max(32); - doFailTest(w, ws, error::message_too_big); - }); - - // invalid inflate block - doTest(pmd, - [&](ws_type_t& ws) - { - auto const& s = random_string(); - ws.binary(true); - ws.next_layer().append( - "\xc2\x40" + s.substr(0, 64)); flat_buffer b; - try - { - w.read(ws, b); - } - catch(system_error const& se) - { - if(se.code() == test::error::test_failure) - throw; - BEAST_EXPECTS(se.code().category() == - zlib::detail::get_error_category(), - se.code().message()); - } - catch(...) - { - throw; - } - }); + ws1.async_write(net::const_buffer("Hello, world!", 13), + test::success_handler()); + ws2.async_read(b, test::success_handler()); + test::run(ioc); + } - // no_context_takeover - pmd.server_no_context_takeover = true; - doTest(pmd, - [&](ws_type_t& ws) { - auto const& s = random_string(); - ws.binary(true); - w.write(ws, net::buffer(s)); - multi_buffer b; - w.read(ws, b); - BEAST_EXPECT(buffers_to_string(b.data()) == s); - }); - pmd.client_no_context_takeover = false; - } + stream ws1(ioc); + stream ws2(ioc); + test::connect(ws1.next_layer(), ws2.next_layer()); + ws1.async_handshake("test", "/", test::success_handler()); + ws2.async_accept(test::success_handler()); + test::run(ioc); + + flat_buffer b; + ws1.async_write(net::const_buffer("Hello, world!", 13), + test::success_handler()); + ws2.async_read(b, test::success_handler()); + test::run(ioc); + } + + // success, timeout enabled - template - void - doTestRead( - permessage_deflate const& pmd, - Wrap const& w) - { - // message - doTest(pmd, [&](ws_type& ws) { - std::string const s = "Hello, world!"; - ws.auto_fragment(false); - ws.binary(false); - w.write(ws, net::buffer(s)); - multi_buffer b; - w.read(ws, b); - BEAST_EXPECT(ws.got_text()); - BEAST_EXPECT(buffers_to_string(b.data()) == s); - }); + stream ws1(ioc); + stream ws2(ioc); + test::connect(ws1.next_layer(), ws2.next_layer()); + ws1.async_handshake("test", "/", test::success_handler()); + ws2.async_accept(test::success_handler()); + test::run(ioc); + + flat_buffer b; + ws1.set_option(stream_base::timeout{ + stream_base::none(), + std::chrono::milliseconds(50), + false}); + ws1.async_read(b, test::success_handler()); + ws2.async_write(net::const_buffer("Hello, world!", 13), + test::success_handler()); + test::run(ioc); + } - // masked message - doStreamLoop([&](test::stream& ts) { - echo_server es{log, kind::async_client}; - ws_type ws{ts}; - ws.next_layer().connect(es.stream()); - ws.set_option(pmd); - es.async_handshake(); - try - { - w.accept(ws); - std::string const s = "Hello, world!"; - ws.auto_fragment(false); - ws.binary(false); - w.write(ws, net::buffer(s)); - multi_buffer b; - w.read(ws, b); - BEAST_EXPECT(ws.got_text()); - BEAST_EXPECT(buffers_to_string(b.data()) == s); - ws.next_layer().close(); - } - catch(...) - { - ts.close(); - throw; - } - }); + stream ws1(ioc); + stream ws2(ioc); + test::connect(ws1.next_layer(), ws2.next_layer()); + ws1.async_handshake("test", "/", test::success_handler()); + ws2.async_accept(test::success_handler()); + test::run(ioc); + + flat_buffer b; + ws1.set_option(stream_base::timeout{ + stream_base::none(), + std::chrono::milliseconds(50), + false}); + ws1.async_read(b, test::success_handler()); + ws2.async_write(net::const_buffer("Hello, world!", 13), + test::success_handler()); + test::run(ioc); + } + + // timeout - // empty message - doTest(pmd, [&](ws_type& ws) { - std::string const s = ""; - ws.text(true); - w.write(ws, net::buffer(s)); - multi_buffer b; - w.read(ws, b); - BEAST_EXPECT(ws.got_text()); - BEAST_EXPECT(buffers_to_string(b.data()) == s); - }); + stream ws1(ioc); + stream ws2(ioc); + test::connect(ws1.next_layer(), ws2.next_layer()); + ws1.async_handshake("test", "/", test::success_handler()); + ws2.async_accept(test::success_handler()); + test::run(ioc); + + flat_buffer b; + ws1.set_option(stream_base::timeout{ + stream_base::none(), + std::chrono::milliseconds(50), + false}); + ws1.async_read(b, test::fail_handler( + beast::error::timeout)); + test::run(ioc); + } - // partial message - doTest(pmd, [&](ws_type& ws) { - std::string const s = "Hello"; - w.write(ws, net::buffer(s)); - char buf[3]; - auto const bytes_written = - w.read_some(ws, net::buffer(buf, sizeof(buf))); - BEAST_EXPECT(bytes_written > 0); - BEAST_EXPECT( - string_view(buf, 3).substr(0, bytes_written) == - s.substr(0, bytes_written)); - }); + stream ws1(ioc); + stream ws2(ioc); + test::connect(ws1.next_layer(), ws2.next_layer()); + ws1.async_handshake("test", "/", test::success_handler()); + ws2.async_accept(test::success_handler()); + test::run(ioc); - // partial message, dynamic buffer - doTest(pmd, [&](ws_type& ws) - { - std::string const s = "Hello, world!"; - w.write(ws, net::buffer(s)); - multi_buffer b; - auto bytes_written = - w.read_some(ws, 3, b); - BEAST_EXPECT(bytes_written > 0); - BEAST_EXPECT(buffers_to_string(b.data()) == - s.substr(0, b.size())); - w.read_some(ws, 256, b); - BEAST_EXPECT(buffers_to_string(b.data()) == s); - }); - - // big message - doTest(pmd, [&](ws_type& ws) - { - auto const& s = random_string(); - ws.binary(true); - w.write(ws, net::buffer(s)); - multi_buffer b; - w.read(ws, b); - BEAST_EXPECT(buffers_to_string(b.data()) == s); - }); - - // message, bad utf8 - doTest(pmd, [&](ws_type& ws) - { - std::string const s = "\x03\xea\xf0\x28\x8c\xbc"; - ws.auto_fragment(false); - ws.text(true); - w.write(ws, net::buffer(s)); - doReadTest(w, ws, close_code::bad_payload); - }); - } - - void - testRead() - { - doTestRead(SyncClient{}); - doTestRead(SyncClient{}); - doTestReadDeflate(SyncClient{}); - yield_to([&](yield_context yield) - { - doTestRead(AsyncClient{yield}); - doTestRead(AsyncClient{yield}); - doTestReadDeflate(AsyncClient{yield}); - }); - - permessage_deflate pmd; - pmd.client_enable = false; - pmd.server_enable = false; - doTestRead(pmd, SyncClient{}); - yield_to([&](yield_context yield) - { - doTestRead(pmd, AsyncClient{yield}); - }); - - pmd.client_enable = true; - pmd.server_enable = true; - pmd.client_max_window_bits = 9; - pmd.server_max_window_bits = 9; - pmd.compLevel = 1; - doTestRead(pmd, SyncClient{}); - yield_to([&](yield_context yield) - { - doTestRead(pmd, AsyncClient{yield}); - }); - - // Read close frames - { - auto const check = - [&](error_code ev, string_view s) - { - echo_server es{log}; - stream ws{ioc_}; - ws.next_layer().connect(es.stream()); - ws.handshake("localhost", "/"); - ws.next_layer().append(s); - static_buffer<1> b; - error_code ec; - ws.read(b, ec); - BEAST_EXPECTS(ec == ev, ec.message()); - ws.next_layer().close(); - }; - - // payload length 1 - check(error::bad_close_size, - "\x88\x01\x01"); - - // invalid close code 1005 - check(error::bad_close_code, - "\x88\x02\x03\xed"); - - // invalid utf8 - check(error::bad_close_payload, - "\x88\x06\xfc\x15\x0f\xd7\x73\x43"); - - // good utf8 - check(error::closed, - "\x88\x06\xfc\x15utf8"); + flat_buffer b; + ws1.set_option(stream_base::timeout{ + stream_base::none(), + std::chrono::milliseconds(50), + false}); + ws1.async_read(b, test::fail_handler( + beast::error::timeout)); + test::run(ioc); } } void run() override { - testRead(); + testTimeout(); } }; diff --git a/test/beast/websocket/read2.cpp b/test/beast/websocket/read2.cpp index 2cd4f64d..7fc03903 100644 --- a/test/beast/websocket/read2.cpp +++ b/test/beast/websocket/read2.cpp @@ -1,5 +1,5 @@ -// -// Copyright (w) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + +// 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) @@ -12,639 +12,670 @@ #include "test.hpp" -#include -#include -#include #include +#include +#if BOOST_WORKAROUND(BOOST_GCC, < 80200) +#define BOOST_BEAST_SYMBOL_HIDDEN __attribute__ ((visibility("hidden"))) +#else +#define BOOST_BEAST_SYMBOL_HIDDEN +#endif + namespace boost { namespace beast { namespace websocket { -class read2_test : public websocket_test_suite +class BOOST_BEAST_SYMBOL_HIDDEN read2_test + : public websocket_test_suite { public: + template void - testSuspend() + doReadTest( + Wrap const& w, + ws_type_t& ws, + close_code code) { - // suspend on read block - doFailLoop([&](test::fail_count& fc) + try + { + multi_buffer b; + w.read(ws, b); + fail("", __FILE__, __LINE__); + } + catch(system_error const& se) + { + if(se.code() != error::closed) + throw; + BEAST_EXPECT( + ws.reason().code == code); + } + } + + template + void + doFailTest( + Wrap const& w, + ws_type_t& ws, + error_code ev) + { + try + { + multi_buffer b; + w.read(ws, b); + fail("", __FILE__, __LINE__); + } + catch(system_error const& se) + { + if(se.code() != ev) + throw; + } + } + + template + void + doTestRead(Wrap const& w) + { + permessage_deflate pmd; + pmd.client_enable = false; + pmd.server_enable = false; + + // already closed { echo_server es{log}; - net::io_context ioc; - stream ws{ioc, fc}; + stream ws{ioc_}; ws.next_layer().connect(es.stream()); ws.handshake("localhost", "/"); - std::size_t count = 0; - ws.async_close({}, - [&](error_code ec) - { - if(ec) - BOOST_THROW_EXCEPTION( - system_error{ec}); - BEAST_EXPECT(++count == 1); - }); - while(! ws.impl_->rd_block.is_locked()) - ioc.run_one(); + ws.close({}); + try + { + multi_buffer b; + w.read(ws, b); + fail("", __FILE__, __LINE__); + } + catch(system_error const& se) + { + BEAST_EXPECTS( + se.code() == net::error::operation_aborted, + se.code().message()); + } + } + + // empty, fragmented message + doTest(pmd, + [&](ws_type_t& ws) + { + ws.next_layer().append( + string_view( + "\x01\x00" "\x80\x00", 4)); multi_buffer b; - ws.async_read(b, - [&](error_code ec, std::size_t) - { - if(ec != net::error::operation_aborted) - BOOST_THROW_EXCEPTION( - system_error{ec}); - BEAST_EXPECT(++count == 2); - }); - ioc.run(); - BEAST_EXPECT(count == 2); + w.read(ws, b); + BEAST_EXPECT(b.size() == 0); }); - // suspend on release read block - doFailLoop([&](test::fail_count& fc) + // two part message + // triggers "fill the read buffer first" + doTest(pmd, + [&](ws_type_t& ws) { - echo_server es{log}; - net::io_context ioc; - stream ws{ioc, fc}; - ws.next_layer().connect(es.stream()); - ws.handshake("localhost", "/"); - std::size_t count = 0; + w.write_raw(ws, sbuf( + "\x01\x81\xff\xff\xff\xff")); + w.write_raw(ws, sbuf( + "\xd5")); + w.write_raw(ws, sbuf( + "\x80\x81\xff\xff\xff\xff\xd5")); multi_buffer b; - ws.async_read(b, - [&](error_code ec, std::size_t) - { - if(ec != net::error::operation_aborted) - BOOST_THROW_EXCEPTION( - system_error{ec}); - BEAST_EXPECT(++count == 2); - }); - BOOST_ASSERT(ws.impl_->rd_block.is_locked()); - ws.async_close({}, - [&](error_code ec) - { - if(ec) - BOOST_THROW_EXCEPTION( - system_error{ec}); - BEAST_EXPECT(++count == 1); - }); - ioc.run(); - BEAST_EXPECT(count == 2); + w.read(ws, b); + BEAST_EXPECT(buffers_to_string(b.data()) == "**"); }); - // suspend on write pong + // ping + doTest(pmd, + [&](ws_type_t& ws) + { + put(ws.next_layer().buffer(), cbuf( + 0x89, 0x00)); + bool invoked = false; + ws.control_callback( + [&](frame_type kind, string_view) + { + BEAST_EXPECT(! invoked); + BEAST_EXPECT(kind == frame_type::ping); + invoked = true; + }); + w.write(ws, sbuf("Hello")); + multi_buffer b; + w.read(ws, b); + BEAST_EXPECT(invoked); + BEAST_EXPECT(ws.got_text()); + BEAST_EXPECT(buffers_to_string(b.data()) == "Hello"); + }); + + // ping + doTest(pmd, + [&](ws_type_t& ws) + { + put(ws.next_layer().buffer(), cbuf( + 0x88, 0x00)); + bool invoked = false; + ws.control_callback( + [&](frame_type kind, string_view) + { + BEAST_EXPECT(! invoked); + BEAST_EXPECT(kind == frame_type::close); + invoked = true; + }); + w.write(ws, sbuf("Hello")); + doReadTest(w, ws, close_code::none); + }); + + // ping then message + doTest(pmd, + [&](ws_type_t& ws) + { + bool once = false; + ws.control_callback( + [&](frame_type kind, string_view s) + { + BEAST_EXPECT(kind == frame_type::pong); + BEAST_EXPECT(! once); + once = true; + BEAST_EXPECT(s == ""); + }); + w.ping(ws, ""); + ws.binary(true); + w.write(ws, sbuf("Hello")); + multi_buffer b; + w.read(ws, b); + BEAST_EXPECT(once); + BEAST_EXPECT(ws.got_binary()); + BEAST_EXPECT(buffers_to_string(b.data()) == "Hello"); + }); + + // ping then fragmented message + doTest(pmd, + [&](ws_type_t& ws) + { + bool once = false; + ws.control_callback( + [&](frame_type kind, string_view s) + { + BEAST_EXPECT(kind == frame_type::pong); + BEAST_EXPECT(! once); + once = true; + BEAST_EXPECT(s == "payload"); + }); + ws.ping("payload"); + w.write_some(ws, false, sbuf("Hello, ")); + w.write_some(ws, false, sbuf("")); + w.write_some(ws, true, sbuf("World!")); + multi_buffer b; + w.read(ws, b); + BEAST_EXPECT(once); + BEAST_EXPECT(buffers_to_string(b.data()) == "Hello, World!"); + }); + + // masked message, big + doStreamLoop([&](test::stream& ts) + { + echo_server es{log, kind::async_client}; + ws_type_t ws{ts}; + ws.next_layer().connect(es.stream()); + ws.set_option(pmd); + es.async_handshake(); + try + { + w.accept(ws); + std::string const s(2000, '*'); + ws.auto_fragment(false); + ws.binary(false); + w.write(ws, net::buffer(s)); + multi_buffer b; + w.read(ws, b); + BEAST_EXPECT(ws.got_text()); + BEAST_EXPECT(buffers_to_string(b.data()) == s); + ws.next_layer().close(); + } + catch(...) + { + ts.close(); + throw; + } + }); + + // close doFailLoop([&](test::fail_count& fc) { - echo_server es{log}; + echo_server es{log, kind::async}; net::io_context ioc; - stream ws{ioc, fc}; + stream ws{ioc, fc}; ws.next_layer().connect(es.stream()); ws.handshake("localhost", "/"); - // insert a ping - ws.next_layer().append(string_view( - "\x89\x00", 2)); + // Cause close to be received + es.async_close(); std::size_t count = 0; - std::string const s = "Hello, world"; multi_buffer b; ws.async_read(b, [&](error_code ec, std::size_t) { - if(ec) - BOOST_THROW_EXCEPTION( - system_error{ec}); - BEAST_EXPECT(buffers_to_string(b.data()) == s); ++count; - }); - BEAST_EXPECT(ws.impl_->rd_block.is_locked()); - ws.async_write(net::buffer(s), - [&](error_code ec, std::size_t n) - { - if(ec) + if(ec != error::closed) BOOST_THROW_EXCEPTION( system_error{ec}); - BEAST_EXPECT(n == s.size()); - ++count; }); - BEAST_EXPECT(ws.impl_->wr_block.is_locked()); ioc.run(); - BEAST_EXPECT(count == 2); + BEAST_EXPECT(count == 1); }); - // Ignore ping when closing - doFailLoop([&](test::fail_count& fc) + // already closed + doTest(pmd, + [&](ws_type_t& ws) { - echo_server es{log}; - net::io_context ioc; - stream ws{ioc, fc}; - ws.next_layer().connect(es.stream()); - ws.handshake("localhost", "/"); - std::size_t count = 0; - // insert fragmented message with - // a ping in between the frames. - ws.next_layer().append(string_view( - "\x01\x01*" - "\x89\x00" - "\x80\x01*", 8)); + w.close(ws, {}); multi_buffer b; - ws.async_read(b, - [&](error_code ec, std::size_t) - { - if(ec) - BOOST_THROW_EXCEPTION( - system_error{ec}); - BEAST_EXPECT(buffers_to_string(b.data()) == "**"); - BEAST_EXPECT(++count == 1); - b.consume(b.size()); - ws.async_read(b, - [&](error_code ec, std::size_t) - { - if(ec != net::error::operation_aborted) - BOOST_THROW_EXCEPTION( - system_error{ec}); - BEAST_EXPECT(++count == 3); - }); - }); - BEAST_EXPECT(ws.impl_->rd_block.is_locked()); - ws.async_close({}, - [&](error_code ec) - { - if(ec) - BOOST_THROW_EXCEPTION( - system_error{ec}); - BEAST_EXPECT(++count == 2); - }); - BEAST_EXPECT(ws.impl_->wr_block.is_locked()); - ioc.run(); - BEAST_EXPECT(count == 3); + doFailTest(w, ws, + net::error::operation_aborted); }); - // See if we are already closing - doFailLoop([&](test::fail_count& fc) + // buffer overflow + doTest(pmd, + [&](ws_type_t& ws) { - echo_server es{log}; - net::io_context ioc; - stream ws{ioc, fc}; - ws.next_layer().connect(es.stream()); - ws.handshake("localhost", "/"); - std::size_t count = 0; - // insert fragmented message with - // a close in between the frames. - ws.next_layer().append(string_view( - "\x01\x01*" - "\x88\x00" - "\x80\x01*", 8)); + std::string const s = "Hello, world!"; + ws.auto_fragment(false); + ws.binary(false); + w.write(ws, net::buffer(s)); + try + { + multi_buffer b(3); + w.read(ws, b); + fail("", __FILE__, __LINE__); + } + catch(system_error const& se) + { + if(se.code() != error::buffer_overflow) + throw; + } + }); + + // bad utf8, big + doTest(pmd, + [&](ws_type_t& ws) + { + auto const s = std::string(2000, '*') + + random_string(); + ws.text(true); + w.write(ws, net::buffer(s)); + doReadTest(w, ws, close_code::bad_payload); + }); + + // invalid fixed frame header + doTest(pmd, + [&](ws_type_t& ws) + { + w.write_raw(ws, cbuf( + 0x8f, 0x80, 0xff, 0xff, 0xff, 0xff)); + doReadTest(w, ws, close_code::protocol_error); + }); + + // bad close + doTest(pmd, + [&](ws_type_t& ws) + { + put(ws.next_layer().buffer(), cbuf( + 0x88, 0x02, 0x03, 0xed)); + doFailTest(w, ws, error::bad_close_code); + }); + + // message size above 2^64 + doTest(pmd, + [&](ws_type_t& ws) + { + w.write_some(ws, false, sbuf("*")); + w.write_raw(ws, cbuf( + 0x80, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff)); + doReadTest(w, ws, close_code::too_big); + }); + + // message size exceeds max + doTest(pmd, + [&](ws_type_t& ws) + { + ws.read_message_max(1); + w.write(ws, sbuf("**")); + doFailTest(w, ws, error::message_too_big); + }); + + // bad utf8 + doTest(pmd, + [&](ws_type_t& ws) + { + put(ws.next_layer().buffer(), cbuf( + 0x81, 0x06, 0x03, 0xea, 0xf0, 0x28, 0x8c, 0xbc)); + doFailTest(w, ws, error::bad_frame_payload); + }); + + // incomplete utf8 + doTest(pmd, + [&](ws_type_t& ws) + { + std::string const s = + "Hello, world!" "\xc0"; + w.write(ws, net::buffer(s)); + doReadTest(w, ws, close_code::bad_payload); + }); + + // incomplete utf8, big + doTest(pmd, + [&](ws_type_t& ws) + { + std::string const s = + "\x81\x7e\x0f\xa1" + + std::string(4000, '*') + "\xc0"; + ws.next_layer().append(s); multi_buffer b; - ws.async_read(b, - [&](error_code ec, std::size_t) + try + { + do { - if(ec != net::error::operation_aborted) - BOOST_THROW_EXCEPTION( - system_error{ec}); - BEAST_EXPECT(++count == 2); - }); - BEAST_EXPECT(ws.impl_->rd_block.is_locked()); - ws.async_close({}, - [&](error_code ec) + b.commit(w.read_some(ws, b.prepare(4000))); + } + while(! ws.is_message_done()); + } + catch(system_error const& se) + { + if(se.code() != error::bad_frame_payload) + throw; + } + }); + + // close frames + { + auto const check = + [&](error_code ev, string_view s) + { + echo_server es{log}; + stream ws{ioc_}; + ws.next_layer().connect(es.stream()); + w.handshake(ws, "localhost", "/"); + ws.next_layer().append(s); + static_buffer<1> b; + try { - if(ec) - BOOST_THROW_EXCEPTION( - system_error{ec}); - BEAST_EXPECT(++count == 1); - }); - BEAST_EXPECT(ws.impl_->wr_block.is_locked()); - ioc.run(); - BEAST_EXPECT(count == 2); + w.read(ws, b); + fail("", __FILE__, __LINE__); + } + catch(system_error const& se) + { + BEAST_EXPECTS(se.code() == ev, + se.code().message()); + } + ws.next_layer().close(); + }; + + // payload length 1 + check(error::bad_close_size, + "\x88\x01\x01"); + + // invalid close code 1005 + check(error::bad_close_code, + "\x88\x02\x03\xed"); + + // invalid utf8 + check(error::bad_close_payload, + "\x88\x06\xfc\x15\x0f\xd7\x73\x43"); + + // good utf8 + check(error::closed, + "\x88\x06\xfc\x15utf8"); + } + } + + template + void + doTestReadDeflate(Wrap const& w) + { + permessage_deflate pmd; + pmd.client_enable = true; + pmd.server_enable = true; + pmd.client_max_window_bits = 9; + pmd.server_max_window_bits = 9; + pmd.compLevel = 1; + + // message size limit + doTest(pmd, + [&](ws_type_t& ws) + { + std::string const s = std::string(128, '*'); + w.write(ws, net::buffer(s)); + ws.read_message_max(32); + doFailTest(w, ws, error::message_too_big); + }); + + // invalid inflate block + doTest(pmd, + [&](ws_type_t& ws) + { + auto const& s = random_string(); + ws.binary(true); + ws.next_layer().append( + "\xc2\x40" + s.substr(0, 64)); + flat_buffer b; + try + { + w.read(ws, b); + } + catch(system_error const& se) + { + if(se.code() == test::error::test_failure) + throw; + BEAST_EXPECTS(se.code().category() == + zlib::detail::get_error_category(), + se.code().message()); + } + catch(...) + { + throw; + } + }); + + // no_context_takeover + pmd.server_no_context_takeover = true; + doTest(pmd, + [&](ws_type_t& ws) + { + auto const& s = random_string(); + ws.binary(true); + w.write(ws, net::buffer(s)); + multi_buffer b; + w.read(ws, b); + BEAST_EXPECT(buffers_to_string(b.data()) == s); + }); + pmd.client_no_context_takeover = false; + } + + template + void + doTestRead( + permessage_deflate const& pmd, + Wrap const& w) + { + // message + doTest(pmd, [&](ws_type& ws) + { + std::string const s = "Hello, world!"; + ws.auto_fragment(false); + ws.binary(false); + w.write(ws, net::buffer(s)); + multi_buffer b; + w.read(ws, b); + BEAST_EXPECT(ws.got_text()); + BEAST_EXPECT(buffers_to_string(b.data()) == s); + }); + + // masked message + doStreamLoop([&](test::stream& ts) + { + echo_server es{log, kind::async_client}; + ws_type ws{ts}; + ws.next_layer().connect(es.stream()); + ws.set_option(pmd); + es.async_handshake(); + try + { + w.accept(ws); + std::string const s = "Hello, world!"; + ws.auto_fragment(false); + ws.binary(false); + w.write(ws, net::buffer(s)); + multi_buffer b; + w.read(ws, b); + BEAST_EXPECT(ws.got_text()); + BEAST_EXPECT(buffers_to_string(b.data()) == s); + ws.next_layer().close(); + } + catch(...) + { + ts.close(); + throw; + } + }); + + // empty message + doTest(pmd, [&](ws_type& ws) + { + std::string const s = ""; + ws.text(true); + w.write(ws, net::buffer(s)); + multi_buffer b; + w.read(ws, b); + BEAST_EXPECT(ws.got_text()); + BEAST_EXPECT(buffers_to_string(b.data()) == s); + }); + + // partial message + doTest(pmd, [&](ws_type& ws) + { + std::string const s = "Hello"; + w.write(ws, net::buffer(s)); + char buf[3]; + auto const bytes_written = + w.read_some(ws, net::buffer(buf, sizeof(buf))); + BEAST_EXPECT(bytes_written > 0); + BEAST_EXPECT( + string_view(buf, 3).substr(0, bytes_written) == + s.substr(0, bytes_written)); + }); + + // partial message, dynamic buffer + doTest(pmd, [&](ws_type& ws) + { + std::string const s = "Hello, world!"; + w.write(ws, net::buffer(s)); + multi_buffer b; + auto bytes_written = + w.read_some(ws, 3, b); + BEAST_EXPECT(bytes_written > 0); + BEAST_EXPECT(buffers_to_string(b.data()) == + s.substr(0, b.size())); + w.read_some(ws, 256, b); + BEAST_EXPECT(buffers_to_string(b.data()) == s); + }); + + // big message + doTest(pmd, [&](ws_type& ws) + { + auto const& s = random_string(); + ws.binary(true); + w.write(ws, net::buffer(s)); + multi_buffer b; + w.read(ws, b); + BEAST_EXPECT(buffers_to_string(b.data()) == s); + }); + + // message, bad utf8 + doTest(pmd, [&](ws_type& ws) + { + std::string const s = "\x03\xea\xf0\x28\x8c\xbc"; + ws.auto_fragment(false); + ws.text(true); + w.write(ws, net::buffer(s)); + doReadTest(w, ws, close_code::bad_payload); }); } void - testParseFrame() + testRead() { - auto const bad = - [&](string_view s) + doTestRead(SyncClient{}); + doTestRead(SyncClient{}); + doTestReadDeflate(SyncClient{}); + yield_to([&](yield_context yield) + { + doTestRead(AsyncClient{yield}); + doTestRead(AsyncClient{yield}); + doTestReadDeflate(AsyncClient{yield}); + }); + + permessage_deflate pmd; + pmd.client_enable = false; + pmd.server_enable = false; + doTestRead(pmd, SyncClient{}); + yield_to([&](yield_context yield) + { + doTestRead(pmd, AsyncClient{yield}); + }); + + pmd.client_enable = true; + pmd.server_enable = true; + pmd.client_max_window_bits = 9; + pmd.server_max_window_bits = 9; + pmd.compLevel = 1; + doTestRead(pmd, SyncClient{}); + yield_to([&](yield_context yield) + { + doTestRead(pmd, AsyncClient{yield}); + }); + + // Read close frames + { + auto const check = + [&](error_code ev, string_view s) { echo_server es{log}; - net::io_context ioc; - stream ws{ioc}; + stream ws{ioc_}; ws.next_layer().connect(es.stream()); ws.handshake("localhost", "/"); ws.next_layer().append(s); + static_buffer<1> b; error_code ec; - multi_buffer b; ws.read(b, ec); - BEAST_EXPECT(ec); + BEAST_EXPECTS(ec == ev, ec.message()); + ws.next_layer().close(); }; - // chopped frame header - { - echo_server es{log}; - net::io_context ioc; - stream ws{ioc}; - ws.next_layer().connect(es.stream()); - ws.handshake("localhost", "/"); - ws.next_layer().append( - "\x81\x7e\x01"); - std::size_t count = 0; - std::string const s(257, '*'); - multi_buffer b; - ws.async_read(b, - [&](error_code ec, std::size_t) - { - ++count; - BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(buffers_to_string(b.data()) == s); - }); - ioc.run_one(); - es.stream().write_some( - net::buffer("\x01" + s)); - ioc.run(); - BEAST_EXPECT(count == 1); - } + // payload length 1 + check(error::bad_close_size, + "\x88\x01\x01"); - // new data frame when continuation expected - bad("\x01\x01*" "\x81\x01*"); + // invalid close code 1005 + check(error::bad_close_code, + "\x88\x02\x03\xed"); - // reserved bits not cleared - bad("\xb1\x01*"); - bad("\xc1\x01*"); - bad("\xd1\x01*"); + // invalid utf8 + check(error::bad_close_payload, + "\x88\x06\xfc\x15\x0f\xd7\x73\x43"); - // continuation without an active message - bad("\x80\x01*"); - - // reserved bits not cleared (cont) - bad("\x01\x01*" "\xb0\x01*"); - bad("\x01\x01*" "\xc0\x01*"); - bad("\x01\x01*" "\xd0\x01*"); - - // reserved opcode - bad("\x83\x01*"); - - // fragmented control message - bad("\x09\x01*"); - - // invalid length for control message - bad("\x89\x7e\x01\x01"); - - // reserved bits not cleared (control) - bad("\xb9\x01*"); - bad("\xc9\x01*"); - bad("\xd9\x01*"); - - // unmasked frame from client - { - echo_server es{log, kind::async_client}; - net::io_context ioc; - stream ws{ioc}; - ws.next_layer().connect(es.stream()); - es.async_handshake(); - ws.accept(); - ws.next_layer().append( - "\x81\x01*"); - error_code ec; - multi_buffer b; - ws.read(b, ec); - BEAST_EXPECT(ec); - } - - // masked frame from server - bad("\x81\x80\xff\xff\xff\xff"); - - // chopped control frame payload - { - echo_server es{log}; - net::io_context ioc; - stream ws{ioc}; - ws.next_layer().connect(es.stream()); - ws.handshake("localhost", "/"); - ws.next_layer().append( - "\x89\x02*"); - std::size_t count = 0; - multi_buffer b; - ws.async_read(b, - [&](error_code ec, std::size_t) - { - ++count; - BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(buffers_to_string(b.data()) == "**"); - }); - ioc.run_one(); - es.stream().write_some( - net::buffer( - "*" "\x81\x02**")); - ioc.run(); - BEAST_EXPECT(count == 1); - } - - // length not canonical - bad(string_view("\x81\x7e\x00\x7d", 4)); - bad(string_view("\x81\x7f\x00\x00\x00\x00\x00\x00\xff\xff", 10)); - } - - void - testIssue802() - { - for(std::size_t i = 0; i < 100; ++i) - { - echo_server es{log, kind::async}; - net::io_context ioc; - stream ws{ioc}; - ws.next_layer().connect(es.stream()); - ws.handshake("localhost", "/"); - // too-big message frame indicates payload of 2^64-1 - net::write(ws.next_layer(), sbuf( - "\x81\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff")); - multi_buffer b; - error_code ec; - ws.read(b, ec); - BEAST_EXPECT(ec == error::closed); - BEAST_EXPECT(ws.reason().code == 1009); - } - } - - void - testIssue807() - { - echo_server es{log}; - net::io_context ioc; - stream ws{ioc}; - ws.next_layer().connect(es.stream()); - ws.handshake("localhost", "/"); - ws.write(sbuf("Hello, world!")); - char buf[4]; - net::mutable_buffer b{buf, 0}; - auto const n = ws.read_some(b); - BEAST_EXPECT(n == 0); - } - - /* - When the internal read buffer contains a control frame and - stream::async_read_some is called, it is possible for the control - callback to be invoked on the caller's stack instead of through - the executor associated with the final completion handler. - */ - void - testIssue954() - { - echo_server es{log}; - multi_buffer b; - net::io_context ioc; - stream ws{ioc}; - ws.next_layer().connect(es.stream()); - ws.handshake("localhost", "/"); - // message followed by ping - ws.next_layer().append({ - "\x81\x00" - "\x89\x00", - 4}); - bool called_cb = false; - bool called_handler = false; - ws.control_callback( - [&called_cb](frame_type, string_view) - { - called_cb = true; - }); - ws.async_read(b, - [&](error_code, std::size_t) - { - called_handler = true; - }); - BEAST_EXPECT(! called_cb); - BEAST_EXPECT(! called_handler); - ioc.run(); - BEAST_EXPECT(! called_cb); - BEAST_EXPECT(called_handler); - ws.async_read(b, - [&](error_code, std::size_t) - { - }); - BEAST_EXPECT(! called_cb); - } - - /* Bishop Fox Hybrid Assessment issue 1 - - Happens with permessage-deflate enabled and a - compressed frame with the FIN bit set ends with an - invalid prefix. - */ - void - testIssueBF1() - { - permessage_deflate pmd; - pmd.client_enable = true; - pmd.server_enable = true; - - // read -#if 0 - { - echo_server es{log}; - net::io_context ioc; - stream ws{ioc}; - ws.set_option(pmd); - ws.next_layer().connect(es.stream()); - ws.handshake("localhost", "/"); - // invalid 1-byte deflate block in frame - net::write(ws.next_layer(), sbuf( - "\xc1\x81\x3a\xa1\x74\x3b\x49")); - } -#endif - { - net::io_context ioc; - stream wsc{ioc}; - stream wss{ioc}; - wsc.set_option(pmd); - wss.set_option(pmd); - wsc.next_layer().connect(wss.next_layer()); - wsc.async_handshake( - "localhost", "/", [](error_code){}); - wss.async_accept([](error_code){}); - ioc.run(); - ioc.restart(); - BEAST_EXPECT(wsc.is_open()); - BEAST_EXPECT(wss.is_open()); - // invalid 1-byte deflate block in frame - net::write(wsc.next_layer(), sbuf( - "\xc1\x81\x3a\xa1\x74\x3b\x49")); - error_code ec; - multi_buffer b; - wss.read(b, ec); - BEAST_EXPECTS(ec == zlib::error::end_of_stream, ec.message()); - } - - // async read -#if 0 - { - echo_server es{log, kind::async}; - net::io_context ioc; - stream ws{ioc}; - ws.set_option(pmd); - ws.next_layer().connect(es.stream()); - ws.handshake("localhost", "/"); - // invalid 1-byte deflate block in frame - net::write(ws.next_layer(), sbuf( - "\xc1\x81\x3a\xa1\x74\x3b\x49")); - } -#endif - { - net::io_context ioc; - stream wsc{ioc}; - stream wss{ioc}; - wsc.set_option(pmd); - wss.set_option(pmd); - wsc.next_layer().connect(wss.next_layer()); - wsc.async_handshake( - "localhost", "/", [](error_code){}); - wss.async_accept([](error_code){}); - ioc.run(); - ioc.restart(); - BEAST_EXPECT(wsc.is_open()); - BEAST_EXPECT(wss.is_open()); - // invalid 1-byte deflate block in frame - net::write(wsc.next_layer(), sbuf( - "\xc1\x81\x3a\xa1\x74\x3b\x49")); - error_code ec; - flat_buffer b; - wss.async_read(b, - [&ec](error_code ec_, std::size_t){ ec = ec_; }); - ioc.run(); - BEAST_EXPECTS(ec == zlib::error::end_of_stream, ec.message()); - } - } - - /* Bishop Fox Hybrid Assessment issue 2 - - Happens with permessage-deflate enabled, - and a deflate block with the BFINAL bit set - is encountered in a compressed payload. - */ - void - testIssueBF2() - { - permessage_deflate pmd; - pmd.client_enable = true; - pmd.server_enable = true; - - // read - { - net::io_context ioc; - stream wsc{ioc}; - stream wss{ioc}; - wsc.set_option(pmd); - wss.set_option(pmd); - wsc.next_layer().connect(wss.next_layer()); - wsc.async_handshake( - "localhost", "/", [](error_code){}); - wss.async_accept([](error_code){}); - ioc.run(); - ioc.restart(); - BEAST_EXPECT(wsc.is_open()); - BEAST_EXPECT(wss.is_open()); - // contains a deflate block with BFINAL set - net::write(wsc.next_layer(), sbuf( - "\xc1\xf8\xd1\xe4\xcc\x3e\xda\xe4\xcc\x3e" - "\x2b\x1e\x36\xc4\x2b\x1e\x36\xc4\x2b\x1e" - "\x36\x3e\x35\xae\x4f\x54\x18\xae\x4f\x7b" - "\xd1\xe4\xcc\x3e\xd1\xe4\xcc\x3e\xd1\xe4" - "\xcc\x3e\xd1\xe4\xcc\x3e\xd1\xe4\xcc\x3e" - "\xd1\x1e\x36\xc4\x2b\x1e\x36\xc4\x2b\xe4" - "\x28\x74\x52\x8e\x05\x74\x52\xa1\xcc\x3e" - "\xd1\xe4\xcc\x3e\xd1\xe4\xcc\x3e\xd1\xe4" - "\xcc\x3e\xd1\xe4\xcc\x3e\xd1\xe4\xcc\x3e" - "\xd1\xe4\xcc\x3e\xd1\xe4\xcc\x3e\xd1\xe4" - "\xcc\x3e\xd1\xe4\xcc\x3e\xd1\xe4\x36\x3e" - "\xd1\xec\xcc\x3e\xd1\xe4\xcc\x3e\xd1\xe4" - "\xcc\x3e\xd1\xe4\xcc\x3e")); - error_code ec; - flat_buffer b; - wss.read(b, ec); - BEAST_EXPECTS(ec == zlib::error::end_of_stream, ec.message()); - } - - // async read - { - net::io_context ioc; - stream wsc{ioc}; - stream wss{ioc}; - wsc.set_option(pmd); - wss.set_option(pmd); - wsc.next_layer().connect(wss.next_layer()); - wsc.async_handshake( - "localhost", "/", [](error_code){}); - wss.async_accept([](error_code){}); - ioc.run(); - ioc.restart(); - BEAST_EXPECT(wsc.is_open()); - BEAST_EXPECT(wss.is_open()); - // contains a deflate block with BFINAL set - net::write(wsc.next_layer(), sbuf( - "\xc1\xf8\xd1\xe4\xcc\x3e\xda\xe4\xcc\x3e" - "\x2b\x1e\x36\xc4\x2b\x1e\x36\xc4\x2b\x1e" - "\x36\x3e\x35\xae\x4f\x54\x18\xae\x4f\x7b" - "\xd1\xe4\xcc\x3e\xd1\xe4\xcc\x3e\xd1\xe4" - "\xcc\x3e\xd1\xe4\xcc\x3e\xd1\xe4\xcc\x3e" - "\xd1\x1e\x36\xc4\x2b\x1e\x36\xc4\x2b\xe4" - "\x28\x74\x52\x8e\x05\x74\x52\xa1\xcc\x3e" - "\xd1\xe4\xcc\x3e\xd1\xe4\xcc\x3e\xd1\xe4" - "\xcc\x3e\xd1\xe4\xcc\x3e\xd1\xe4\xcc\x3e" - "\xd1\xe4\xcc\x3e\xd1\xe4\xcc\x3e\xd1\xe4" - "\xcc\x3e\xd1\xe4\xcc\x3e\xd1\xe4\x36\x3e" - "\xd1\xec\xcc\x3e\xd1\xe4\xcc\x3e\xd1\xe4" - "\xcc\x3e\xd1\xe4\xcc\x3e")); - error_code ec; - flat_buffer b; - wss.async_read(b, - [&ec](error_code ec_, std::size_t){ ec = ec_; }); - ioc.run(); - BEAST_EXPECTS(ec == zlib::error::end_of_stream, ec.message()); - } - } - - void - testMoveOnly() - { - net::io_context ioc; - stream ws{ioc}; - ws.async_read_some( - net::mutable_buffer{}, - move_only_handler{}); - } - - struct copyable_handler - { - template - void - operator()(Args&&...) const - { - } - }; - - void - testAsioHandlerInvoke() - { - // make sure things compile, also can set a - // breakpoint in asio_handler_invoke to make sure - // it is instantiated. - { - net::io_context ioc; - net::strand< - net::io_context::executor_type> s( - ioc.get_executor()); - stream ws{ioc}; - flat_buffer b; - ws.async_read(b, net::bind_executor( - s, copyable_handler{})); + // good utf8 + check(error::closed, + "\x88\x06\xfc\x15utf8"); } } void run() override { - testParseFrame(); - testIssue802(); - testIssue807(); - testIssue954(); - testIssueBF1(); - testIssueBF2(); - testMoveOnly(); - testAsioHandlerInvoke(); + testRead(); } }; diff --git a/test/beast/websocket/read3.cpp b/test/beast/websocket/read3.cpp new file mode 100644 index 00000000..4a3f64a0 --- /dev/null +++ b/test/beast/websocket/read3.cpp @@ -0,0 +1,655 @@ +// +// 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 +// + +// Test that header file is self-contained. +#include + +#include "test.hpp" + +#include +#include +#include +#include + +namespace boost { +namespace beast { +namespace websocket { + +class read3_test : public websocket_test_suite +{ +public: + void + testSuspend() + { + // suspend on read block + doFailLoop([&](test::fail_count& fc) + { + echo_server es{log}; + net::io_context ioc; + stream ws{ioc, fc}; + ws.next_layer().connect(es.stream()); + ws.handshake("localhost", "/"); + std::size_t count = 0; + ws.async_close({}, + [&](error_code ec) + { + if(ec) + BOOST_THROW_EXCEPTION( + system_error{ec}); + BEAST_EXPECT(++count == 1); + }); + while(! ws.impl_->rd_block.is_locked()) + ioc.run_one(); + multi_buffer b; + ws.async_read(b, + [&](error_code ec, std::size_t) + { + if(ec != net::error::operation_aborted) + BOOST_THROW_EXCEPTION( + system_error{ec}); + BEAST_EXPECT(++count == 2); + }); + ioc.run(); + BEAST_EXPECT(count == 2); + }); + + // suspend on release read block + doFailLoop([&](test::fail_count& fc) + { + echo_server es{log}; + net::io_context ioc; + stream ws{ioc, fc}; + ws.next_layer().connect(es.stream()); + ws.handshake("localhost", "/"); + std::size_t count = 0; + multi_buffer b; + ws.async_read(b, + [&](error_code ec, std::size_t) + { + if(ec != net::error::operation_aborted) + BOOST_THROW_EXCEPTION( + system_error{ec}); + BEAST_EXPECT(++count == 2); + }); + BOOST_ASSERT(ws.impl_->rd_block.is_locked()); + ws.async_close({}, + [&](error_code ec) + { + if(ec) + BOOST_THROW_EXCEPTION( + system_error{ec}); + BEAST_EXPECT(++count == 1); + }); + ioc.run(); + BEAST_EXPECT(count == 2); + }); + + // suspend on write pong + doFailLoop([&](test::fail_count& fc) + { + echo_server es{log}; + net::io_context ioc; + stream ws{ioc, fc}; + ws.next_layer().connect(es.stream()); + ws.handshake("localhost", "/"); + // insert a ping + ws.next_layer().append(string_view( + "\x89\x00", 2)); + std::size_t count = 0; + std::string const s = "Hello, world"; + multi_buffer b; + ws.async_read(b, + [&](error_code ec, std::size_t) + { + if(ec) + BOOST_THROW_EXCEPTION( + system_error{ec}); + BEAST_EXPECT(buffers_to_string(b.data()) == s); + ++count; + }); + BEAST_EXPECT(ws.impl_->rd_block.is_locked()); + ws.async_write(net::buffer(s), + [&](error_code ec, std::size_t n) + { + if(ec) + BOOST_THROW_EXCEPTION( + system_error{ec}); + BEAST_EXPECT(n == s.size()); + ++count; + }); + BEAST_EXPECT(ws.impl_->wr_block.is_locked()); + ioc.run(); + BEAST_EXPECT(count == 2); + }); + + // Ignore ping when closing + doFailLoop([&](test::fail_count& fc) + { + echo_server es{log}; + net::io_context ioc; + stream ws{ioc, fc}; + ws.next_layer().connect(es.stream()); + ws.handshake("localhost", "/"); + std::size_t count = 0; + // insert fragmented message with + // a ping in between the frames. + ws.next_layer().append(string_view( + "\x01\x01*" + "\x89\x00" + "\x80\x01*", 8)); + multi_buffer b; + ws.async_read(b, + [&](error_code ec, std::size_t) + { + if(ec) + BOOST_THROW_EXCEPTION( + system_error{ec}); + BEAST_EXPECT(buffers_to_string(b.data()) == "**"); + BEAST_EXPECT(++count == 1); + b.consume(b.size()); + ws.async_read(b, + [&](error_code ec, std::size_t) + { + if(ec != net::error::operation_aborted) + BOOST_THROW_EXCEPTION( + system_error{ec}); + BEAST_EXPECT(++count == 3); + }); + }); + BEAST_EXPECT(ws.impl_->rd_block.is_locked()); + ws.async_close({}, + [&](error_code ec) + { + if(ec) + BOOST_THROW_EXCEPTION( + system_error{ec}); + BEAST_EXPECT(++count == 2); + }); + BEAST_EXPECT(ws.impl_->wr_block.is_locked()); + ioc.run(); + BEAST_EXPECT(count == 3); + }); + + // See if we are already closing + doFailLoop([&](test::fail_count& fc) + { + echo_server es{log}; + net::io_context ioc; + stream ws{ioc, fc}; + ws.next_layer().connect(es.stream()); + ws.handshake("localhost", "/"); + std::size_t count = 0; + // insert fragmented message with + // a close in between the frames. + ws.next_layer().append(string_view( + "\x01\x01*" + "\x88\x00" + "\x80\x01*", 8)); + multi_buffer b; + ws.async_read(b, + [&](error_code ec, std::size_t) + { + if(ec != net::error::operation_aborted) + BOOST_THROW_EXCEPTION( + system_error{ec}); + BEAST_EXPECT(++count == 2); + }); + BEAST_EXPECT(ws.impl_->rd_block.is_locked()); + ws.async_close({}, + [&](error_code ec) + { + if(ec) + BOOST_THROW_EXCEPTION( + system_error{ec}); + BEAST_EXPECT(++count == 1); + }); + BEAST_EXPECT(ws.impl_->wr_block.is_locked()); + ioc.run(); + BEAST_EXPECT(count == 2); + }); + } + + void + testParseFrame() + { + auto const bad = + [&](string_view s) + { + echo_server es{log}; + net::io_context ioc; + stream ws{ioc}; + ws.next_layer().connect(es.stream()); + ws.handshake("localhost", "/"); + ws.next_layer().append(s); + error_code ec; + multi_buffer b; + ws.read(b, ec); + BEAST_EXPECT(ec); + }; + + // chopped frame header + { + echo_server es{log}; + net::io_context ioc; + stream ws{ioc}; + ws.next_layer().connect(es.stream()); + ws.handshake("localhost", "/"); + ws.next_layer().append( + "\x81\x7e\x01"); + std::size_t count = 0; + std::string const s(257, '*'); + multi_buffer b; + ws.async_read(b, + [&](error_code ec, std::size_t) + { + ++count; + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(buffers_to_string(b.data()) == s); + }); + ioc.run_one(); + es.stream().write_some( + net::buffer("\x01" + s)); + ioc.run(); + BEAST_EXPECT(count == 1); + } + + // new data frame when continuation expected + bad("\x01\x01*" "\x81\x01*"); + + // reserved bits not cleared + bad("\xb1\x01*"); + bad("\xc1\x01*"); + bad("\xd1\x01*"); + + // continuation without an active message + bad("\x80\x01*"); + + // reserved bits not cleared (cont) + bad("\x01\x01*" "\xb0\x01*"); + bad("\x01\x01*" "\xc0\x01*"); + bad("\x01\x01*" "\xd0\x01*"); + + // reserved opcode + bad("\x83\x01*"); + + // fragmented control message + bad("\x09\x01*"); + + // invalid length for control message + bad("\x89\x7e\x01\x01"); + + // reserved bits not cleared (control) + bad("\xb9\x01*"); + bad("\xc9\x01*"); + bad("\xd9\x01*"); + + // unmasked frame from client + { + echo_server es{log, kind::async_client}; + net::io_context ioc; + stream ws{ioc}; + ws.next_layer().connect(es.stream()); + es.async_handshake(); + ws.accept(); + ws.next_layer().append( + "\x81\x01*"); + error_code ec; + multi_buffer b; + ws.read(b, ec); + BEAST_EXPECT(ec); + } + + // masked frame from server + bad("\x81\x80\xff\xff\xff\xff"); + + // chopped control frame payload + { + echo_server es{log}; + net::io_context ioc; + stream ws{ioc}; + ws.next_layer().connect(es.stream()); + ws.handshake("localhost", "/"); + ws.next_layer().append( + "\x89\x02*"); + std::size_t count = 0; + multi_buffer b; + ws.async_read(b, + [&](error_code ec, std::size_t) + { + ++count; + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(buffers_to_string(b.data()) == "**"); + }); + ioc.run_one(); + es.stream().write_some( + net::buffer( + "*" "\x81\x02**")); + ioc.run(); + BEAST_EXPECT(count == 1); + } + + // length not canonical + bad(string_view("\x81\x7e\x00\x7d", 4)); + bad(string_view("\x81\x7f\x00\x00\x00\x00\x00\x00\xff\xff", 10)); + } + + void + testIssue802() + { + for(std::size_t i = 0; i < 100; ++i) + { + echo_server es{log, kind::async}; + net::io_context ioc; + stream ws{ioc}; + ws.next_layer().connect(es.stream()); + ws.handshake("localhost", "/"); + // too-big message frame indicates payload of 2^64-1 + net::write(ws.next_layer(), sbuf( + "\x81\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff")); + multi_buffer b; + error_code ec; + ws.read(b, ec); + BEAST_EXPECT(ec == error::closed); + BEAST_EXPECT(ws.reason().code == 1009); + } + } + + void + testIssue807() + { + echo_server es{log}; + net::io_context ioc; + stream ws{ioc}; + ws.next_layer().connect(es.stream()); + ws.handshake("localhost", "/"); + ws.write(sbuf("Hello, world!")); + char buf[4]; + net::mutable_buffer b{buf, 0}; + auto const n = ws.read_some(b); + BEAST_EXPECT(n == 0); + } + + /* + When the internal read buffer contains a control frame and + stream::async_read_some is called, it is possible for the control + callback to be invoked on the caller's stack instead of through + the executor associated with the final completion handler. + */ + void + testIssue954() + { + echo_server es{log}; + multi_buffer b; + net::io_context ioc; + stream ws{ioc}; + ws.next_layer().connect(es.stream()); + ws.handshake("localhost", "/"); + // message followed by ping + ws.next_layer().append({ + "\x81\x00" + "\x89\x00", + 4}); + bool called_cb = false; + bool called_handler = false; + ws.control_callback( + [&called_cb](frame_type, string_view) + { + called_cb = true; + }); + ws.async_read(b, + [&](error_code, std::size_t) + { + called_handler = true; + }); + BEAST_EXPECT(! called_cb); + BEAST_EXPECT(! called_handler); + ioc.run(); + BEAST_EXPECT(! called_cb); + BEAST_EXPECT(called_handler); + ws.async_read(b, + [&](error_code, std::size_t) + { + }); + BEAST_EXPECT(! called_cb); + } + + /* Bishop Fox Hybrid Assessment issue 1 + + Happens with permessage-deflate enabled and a + compressed frame with the FIN bit set ends with an + invalid prefix. + */ + void + testIssueBF1() + { + permessage_deflate pmd; + pmd.client_enable = true; + pmd.server_enable = true; + + // read +#if 0 + { + echo_server es{log}; + net::io_context ioc; + stream ws{ioc}; + ws.set_option(pmd); + ws.next_layer().connect(es.stream()); + ws.handshake("localhost", "/"); + // invalid 1-byte deflate block in frame + net::write(ws.next_layer(), sbuf( + "\xc1\x81\x3a\xa1\x74\x3b\x49")); + } +#endif + { + net::io_context ioc; + stream wsc{ioc}; + stream wss{ioc}; + wsc.set_option(pmd); + wss.set_option(pmd); + wsc.next_layer().connect(wss.next_layer()); + wsc.async_handshake( + "localhost", "/", [](error_code){}); + wss.async_accept([](error_code){}); + ioc.run(); + ioc.restart(); + BEAST_EXPECT(wsc.is_open()); + BEAST_EXPECT(wss.is_open()); + // invalid 1-byte deflate block in frame + net::write(wsc.next_layer(), sbuf( + "\xc1\x81\x3a\xa1\x74\x3b\x49")); + error_code ec; + multi_buffer b; + wss.read(b, ec); + BEAST_EXPECTS(ec == zlib::error::end_of_stream, ec.message()); + } + + // async read +#if 0 + { + echo_server es{log, kind::async}; + net::io_context ioc; + stream ws{ioc}; + ws.set_option(pmd); + ws.next_layer().connect(es.stream()); + ws.handshake("localhost", "/"); + // invalid 1-byte deflate block in frame + net::write(ws.next_layer(), sbuf( + "\xc1\x81\x3a\xa1\x74\x3b\x49")); + } +#endif + { + net::io_context ioc; + stream wsc{ioc}; + stream wss{ioc}; + wsc.set_option(pmd); + wss.set_option(pmd); + wsc.next_layer().connect(wss.next_layer()); + wsc.async_handshake( + "localhost", "/", [](error_code){}); + wss.async_accept([](error_code){}); + ioc.run(); + ioc.restart(); + BEAST_EXPECT(wsc.is_open()); + BEAST_EXPECT(wss.is_open()); + // invalid 1-byte deflate block in frame + net::write(wsc.next_layer(), sbuf( + "\xc1\x81\x3a\xa1\x74\x3b\x49")); + error_code ec; + flat_buffer b; + wss.async_read(b, + [&ec](error_code ec_, std::size_t){ ec = ec_; }); + ioc.run(); + BEAST_EXPECTS(ec == zlib::error::end_of_stream, ec.message()); + } + } + + /* Bishop Fox Hybrid Assessment issue 2 + + Happens with permessage-deflate enabled, + and a deflate block with the BFINAL bit set + is encountered in a compressed payload. + */ + void + testIssueBF2() + { + permessage_deflate pmd; + pmd.client_enable = true; + pmd.server_enable = true; + + // read + { + net::io_context ioc; + stream wsc{ioc}; + stream wss{ioc}; + wsc.set_option(pmd); + wss.set_option(pmd); + wsc.next_layer().connect(wss.next_layer()); + wsc.async_handshake( + "localhost", "/", [](error_code){}); + wss.async_accept([](error_code){}); + ioc.run(); + ioc.restart(); + BEAST_EXPECT(wsc.is_open()); + BEAST_EXPECT(wss.is_open()); + // contains a deflate block with BFINAL set + net::write(wsc.next_layer(), sbuf( + "\xc1\xf8\xd1\xe4\xcc\x3e\xda\xe4\xcc\x3e" + "\x2b\x1e\x36\xc4\x2b\x1e\x36\xc4\x2b\x1e" + "\x36\x3e\x35\xae\x4f\x54\x18\xae\x4f\x7b" + "\xd1\xe4\xcc\x3e\xd1\xe4\xcc\x3e\xd1\xe4" + "\xcc\x3e\xd1\xe4\xcc\x3e\xd1\xe4\xcc\x3e" + "\xd1\x1e\x36\xc4\x2b\x1e\x36\xc4\x2b\xe4" + "\x28\x74\x52\x8e\x05\x74\x52\xa1\xcc\x3e" + "\xd1\xe4\xcc\x3e\xd1\xe4\xcc\x3e\xd1\xe4" + "\xcc\x3e\xd1\xe4\xcc\x3e\xd1\xe4\xcc\x3e" + "\xd1\xe4\xcc\x3e\xd1\xe4\xcc\x3e\xd1\xe4" + "\xcc\x3e\xd1\xe4\xcc\x3e\xd1\xe4\x36\x3e" + "\xd1\xec\xcc\x3e\xd1\xe4\xcc\x3e\xd1\xe4" + "\xcc\x3e\xd1\xe4\xcc\x3e")); + error_code ec; + flat_buffer b; + wss.read(b, ec); + BEAST_EXPECTS(ec == zlib::error::end_of_stream, ec.message()); + } + + // async read + { + net::io_context ioc; + stream wsc{ioc}; + stream wss{ioc}; + wsc.set_option(pmd); + wss.set_option(pmd); + wsc.next_layer().connect(wss.next_layer()); + wsc.async_handshake( + "localhost", "/", [](error_code){}); + wss.async_accept([](error_code){}); + ioc.run(); + ioc.restart(); + BEAST_EXPECT(wsc.is_open()); + BEAST_EXPECT(wss.is_open()); + // contains a deflate block with BFINAL set + net::write(wsc.next_layer(), sbuf( + "\xc1\xf8\xd1\xe4\xcc\x3e\xda\xe4\xcc\x3e" + "\x2b\x1e\x36\xc4\x2b\x1e\x36\xc4\x2b\x1e" + "\x36\x3e\x35\xae\x4f\x54\x18\xae\x4f\x7b" + "\xd1\xe4\xcc\x3e\xd1\xe4\xcc\x3e\xd1\xe4" + "\xcc\x3e\xd1\xe4\xcc\x3e\xd1\xe4\xcc\x3e" + "\xd1\x1e\x36\xc4\x2b\x1e\x36\xc4\x2b\xe4" + "\x28\x74\x52\x8e\x05\x74\x52\xa1\xcc\x3e" + "\xd1\xe4\xcc\x3e\xd1\xe4\xcc\x3e\xd1\xe4" + "\xcc\x3e\xd1\xe4\xcc\x3e\xd1\xe4\xcc\x3e" + "\xd1\xe4\xcc\x3e\xd1\xe4\xcc\x3e\xd1\xe4" + "\xcc\x3e\xd1\xe4\xcc\x3e\xd1\xe4\x36\x3e" + "\xd1\xec\xcc\x3e\xd1\xe4\xcc\x3e\xd1\xe4" + "\xcc\x3e\xd1\xe4\xcc\x3e")); + error_code ec; + flat_buffer b; + wss.async_read(b, + [&ec](error_code ec_, std::size_t){ ec = ec_; }); + ioc.run(); + BEAST_EXPECTS(ec == zlib::error::end_of_stream, ec.message()); + } + } + + void + testMoveOnly() + { + net::io_context ioc; + stream ws{ioc}; + ws.async_read_some( + net::mutable_buffer{}, + move_only_handler{}); + } + + struct copyable_handler + { + template + void + operator()(Args&&...) const + { + } + }; + + void + testAsioHandlerInvoke() + { + // make sure things compile, also can set a + // breakpoint in asio_handler_invoke to make sure + // it is instantiated. + { + net::io_context ioc; + net::strand< + net::io_context::executor_type> s( + ioc.get_executor()); + stream ws{ioc}; + flat_buffer b; + ws.async_read(b, net::bind_executor( + s, copyable_handler{})); + } + } + + void + run() override + { + testParseFrame(); + testIssue802(); + testIssue807(); + testIssue954(); + testIssueBF1(); + testIssueBF2(); + testMoveOnly(); + testAsioHandlerInvoke(); + } +}; + +BEAST_DEFINE_TESTSUITE(beast,websocket,read3); + +} // websocket +} // beast +} // boost diff --git a/test/beast/websocket/stream.cpp b/test/beast/websocket/stream.cpp index 20f7e8ac..8fcf2360 100644 --- a/test/beast/websocket/stream.cpp +++ b/test/beast/websocket/stream.cpp @@ -1,5 +1,5 @@ // -// Copyright (w) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// 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) @@ -10,6 +10,9 @@ // Test that header file is self-contained. #include +#include +#include + #include "test.hpp" namespace boost { @@ -19,6 +22,32 @@ namespace websocket { class stream_test : public websocket_test_suite { public: + void + testGetSetOption() + { + net::io_context ioc; + stream ws(ioc); + + { + ws.set_option( + stream_base::suggested_settings( + role_type::client)); + + ws.set_option( + stream_base::suggested_settings( + role_type::server)); + + ws.set_option({ + std::chrono::seconds(30), + std::chrono::seconds(300), + true}); + + stream_base::timeout opt; + ws.get_option(opt); + ws.set_option(opt); + } + } + void testOptions() { @@ -109,6 +138,19 @@ public: } } + void + testJavadoc() + { + net::io_context ioc; + { + websocket::stream> ws{net::io_context::strand(ioc)}; + } + { + websocket::stream> ws(ioc); + } + } + void run() override { @@ -136,6 +178,7 @@ public: sizeof(websocket::stream::impl_type) << std::endl; testOptions(); + testJavadoc(); } }; diff --git a/test/beast/websocket/stream_explicit.cpp b/test/beast/websocket/stream_explicit.cpp index a8bbe62d..ec55f2f9 100644 --- a/test/beast/websocket/stream_explicit.cpp +++ b/test/beast/websocket/stream_explicit.cpp @@ -1,5 +1,5 @@ // -// Copyright (w) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// 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) diff --git a/test/beast/websocket/stream_fwd.cpp b/test/beast/websocket/stream_fwd.cpp index 389436d8..dc13fa87 100644 --- a/test/beast/websocket/stream_fwd.cpp +++ b/test/beast/websocket/stream_fwd.cpp @@ -1,5 +1,5 @@ // -// Copyright (w) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// 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) diff --git a/test/beast/websocket/test.hpp b/test/beast/websocket/test.hpp index 185990e1..7ae7aaba 100644 --- a/test/beast/websocket/test.hpp +++ b/test/beast/websocket/test.hpp @@ -1,5 +1,5 @@ // -// Copyright (w) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// 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) diff --git a/test/beast/websocket/timer.cpp b/test/beast/websocket/timer.cpp index df033aa4..1a1e83a3 100644 --- a/test/beast/websocket/timer.cpp +++ b/test/beast/websocket/timer.cpp @@ -10,7 +10,6 @@ // Test that header file is self-contained. #include -#include #include #include "test.hpp" @@ -25,108 +24,9 @@ class timer_test public: using tcp = boost::asio::ip::tcp; - static - void - connect( - stream& ws1, - stream& ws2) - { - struct handler - { - void - operator()(error_code ec) - { - BEAST_EXPECTS(! ec, ec.message()); - } - }; - - tcp::acceptor a(ws1.get_executor().context()); - auto ep = tcp::endpoint( - net::ip::make_address_v4("127.0.0.1"), 0); - a.open(ep.protocol()); - a.set_option( - net::socket_base::reuse_address(true)); - a.bind(ep); - a.listen(0); - ep = a.local_endpoint(); - a.async_accept(ws2.next_layer(), handler{}); - ws1.next_layer().async_connect(ep, handler{}); - for(;;) - { - if( ws1.get_executor().context().run_one() + - ws2.get_executor().context().run_one() == 0) - { - ws1.get_executor().context().restart(); - ws2.get_executor().context().restart(); - break; - } - } - BEAST_EXPECT( - ws1.next_layer().remote_endpoint() == - ws2.next_layer().local_endpoint()); - BEAST_EXPECT( - ws2.next_layer().remote_endpoint() == - ws1.next_layer().local_endpoint()); - ws2.async_accept(handler{}); - ws1.async_handshake("test", "/", handler{}); - for(;;) - { - if( ws1.get_executor().context().run_one() + - ws2.get_executor().context().run_one() == 0) - { - ws1.get_executor().context().restart(); - ws2.get_executor().context().restart(); - break; - } - } - BEAST_EXPECT(ws1.is_open()); - BEAST_EXPECT(ws2.is_open()); - BEAST_EXPECT(! ws1.get_executor().context().stopped()); - BEAST_EXPECT(! ws2.get_executor().context().stopped()); - } - -#if 0 - void - testRead0() - { - echo_server es(log, kind::async); - - net::io_context ioc; - stream ws(ioc); - ws.next_layer().connect(es.stream()); - - ws.handshake("localhost", "/"); - flat_buffer b; - ws.async_read(b, - [&](error_code ec, std::size_t) - { - log << ec.message() << "\n"; - }); - ioc.run(); - } -#endif - - void - testRead() - { - net::io_context ioc; - stream ws1(ioc); - stream ws2(ioc); - connect(ws1, ws2); - flat_buffer b; - - ws2.async_read(b, - [&](error_code ec, std::size_t) - { - log << "ws1.async_read: " << ec.message() << "\n"; - }); - ioc.run(); - } - void run() override { - testRead(); pass(); } }; diff --git a/test/beast/websocket/write.cpp b/test/beast/websocket/write.cpp index 20887192..74587f2f 100644 --- a/test/beast/websocket/write.cpp +++ b/test/beast/websocket/write.cpp @@ -1,5 +1,5 @@ // -// Copyright (w) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// 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) diff --git a/test/extras/include/boost/beast/test/websocket.hpp b/test/extras/include/boost/beast/test/websocket.hpp index 38cca050..33feba87 100644 --- a/test/extras/include/boost/beast/test/websocket.hpp +++ b/test/extras/include/boost/beast/test/websocket.hpp @@ -1,5 +1,5 @@ // -// Copyright (w) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// 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)