Add websocket::stream timeouts

This commit is contained in:
Vinnie Falco
2019-02-13 08:00:07 -08:00
parent dfd08bf6ae
commit f21358186e
38 changed files with 3305 additions and 2393 deletions

View File

@@ -1,6 +1,7 @@
Version 216:
* Refactor websocket::stream operations
* Add websocket::stream timeouts
--------------------------------------------------------------------------------

View File

@@ -10,6 +10,7 @@
#ifndef BOOST_BEAST_TEST_FAIL_COUNT_HPP
#define BOOST_BEAST_TEST_FAIL_COUNT_HPP
#include <boost/beast/core/detail/config.hpp>
#include <boost/beast/core/error.hpp>
#include <boost/beast/_experimental/test/error.hpp>
#include <cstdlib>

View File

@@ -62,7 +62,7 @@ public:
template<class... Args>
void
operator()(error_code ec, Args&&... args)
operator()(error_code ec, Args&&...)
{
BEAST_EXPECT(! pass_); // can't call twice
BEAST_EXPECTS(! ec_ || ec == *ec_,

View File

@@ -629,6 +629,12 @@ connect(stream& to)
return from;
}
void
connect(stream& s1, stream& s2)
{
s1.connect(s2);
}
template<class Arg1, class... ArgN>
stream
connect(stream& to, Arg1&& arg1, ArgN&&... argn)

View File

@@ -10,6 +10,7 @@
#ifndef BOOST_BEAST_TEST_STREAM_HPP
#define BOOST_BEAST_TEST_STREAM_HPP
#include <boost/beast/core/detail/config.hpp>
#include <boost/beast/core/bind_handler.hpp>
#include <boost/beast/core/flat_buffer.hpp>
#include <boost/beast/core/string.hpp>
@@ -512,6 +513,10 @@ BOOST_BEAST_DECL
stream
connect(stream& to);
BOOST_BEAST_DECL
void
connect(stream& s1, stream& s2);
template<class Arg1, class... ArgN>
stream
connect(stream& to, Arg1&& arg1, ArgN&&... argn);

View File

@@ -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 <boost/beast/core/detail/config.hpp>
#include <boost/beast/_experimental/unit_test/suite.hpp>
#include <boost/beast/_experimental/test/handler.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <chrono>
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<class Rep, class Period>
void
run_for(
net::io_context& ioc,
std::chrono::duration<Rep, Period> 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

View File

@@ -347,8 +347,8 @@ public:
}
else
{
wg1_.reset();
h_(std::forward<Args>(args)...);
wg1_.reset();
}
}

View File

@@ -18,6 +18,7 @@
#include <boost/asio/basic_stream_socket.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/executor.hpp>
#include <boost/asio/is_executor.hpp>
#include <boost/core/empty_value.hpp>
#include <boost/config/workaround.hpp>
#include <boost/optional.hpp>
@@ -195,6 +196,9 @@ class basic_stream
: private detail::stream_base
#endif
{
static_assert(net::is_executor<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)

View File

@@ -14,7 +14,7 @@
#include <boost/beast/core/string.hpp>
#include <boost/beast/core/detail/base64.hpp>
#include <boost/beast/core/detail/sha1.hpp>
#include <boost/beast/websocket/detail/stream_base.hpp>
#include <boost/beast/websocket/detail/prng.hpp>
#include <boost/assert.hpp>
#include <array>
#include <cstdint>
@@ -53,7 +53,8 @@ make_sec_ws_key(sec_ws_key_type& key)
template<class = void>
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);

View File

@@ -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 <boost/beast/core/buffer_size.hpp>
#include <boost/beast/http/empty_body.hpp>
@@ -17,9 +17,7 @@
#include <boost/beast/websocket/option.hpp>
#include <boost/beast/websocket/role.hpp>
#include <boost/beast/websocket/detail/frame.hpp>
#include <boost/beast/websocket/detail/prng.hpp>
#include <boost/beast/websocket/detail/pmd_extension.hpp>
#include <boost/beast/websocket/detail/prng.hpp>
#include <boost/beast/zlib/deflate_stream.hpp>
#include <boost/beast/zlib/inflate_stream.hpp>
#include <boost/beast/core/buffers_suffix.hpp>
@@ -489,31 +487,6 @@ struct impl_base<false>
}
};
//------------------------------------------------------------------------------
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

View File

@@ -11,6 +11,7 @@
#define BOOST_BEAST_WEBSOCKET_IMPL_ACCEPT_IPP
#include <boost/beast/core/buffer_size.hpp>
#include <boost/beast/websocket/impl/stream_impl.hpp>
#include <boost/beast/websocket/detail/type_traits.hpp>
#include <boost/beast/http/empty_body.hpp>
#include <boost/beast/http/parser.hpp>
@@ -42,7 +43,7 @@ class stream<NextLayer, deflateSupported>::response_op
Handler, beast::executor_type<stream>>
, public net::coroutine
{
stream<NextLayer, deflateSupported>& ws_;
boost::weak_ptr<impl_type> wp_;
error_code result_; // must come before res_
response_type& res_;
@@ -53,37 +54,53 @@ public:
class Decorator>
response_op(
Handler_&& h,
stream<NextLayer, deflateSupported>& ws,
http::request<Body, http::basic_fields<Allocator>> const& req,
Decorator const& decorator)
: stable_async_op_base<
Handler, beast::executor_type<stream>>(
std::forward<Handler_>(h), ws.get_executor())
, ws_(ws)
boost::shared_ptr<impl_type> const& sp,
http::request<Body,
http::basic_fields<Allocator>> const& req,
Decorator const& decorator,
bool cont = false)
: stable_async_op_base<Handler,
beast::executor_type<stream>>(
std::forward<Handler_>(h),
sp->stream.get_executor())
, wp_(sp)
, res_(beast::allocate_stable<response_type>(*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<NextLayer, deflateSupported>::accept_op
Handler, beast::executor_type<stream>>
, public net::coroutine
{
stream<NextLayer, deflateSupported>& ws_;
boost::weak_ptr<impl_type> wp_;
http::request_parser<http::empty_body>& p_;
Decorator d_;
public:
template<class Handler_>
template<class Handler_, class Buffers>
accept_op(
Handler_&& h,
stream<NextLayer, deflateSupported>& ws,
boost::shared_ptr<impl_type> const& sp,
Buffers const& buffers,
Decorator const& decorator)
: stable_async_op_base<
Handler, beast::executor_type<stream>>(
std::forward<Handler_>(h), ws.get_executor())
, ws_(ws)
: stable_async_op_base<Handler,
beast::executor_type<stream>>(
std::forward<Handler_>(h),
sp->stream.get_executor())
, wp_(sp)
, p_(beast::allocate_stable<
http::request_parser<http::empty_body>>(*this))
, d_(decorator)
{
}
template<class Buffers>
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<Handler>(
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<Handler>{
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<class NextLayer, bool deflateSupported>
template<class Body, class Allocator,
class Decorator>
void
stream<NextLayer, deflateSupported>::
do_accept(
http::request<Body,
http::basic_fields<Allocator>> 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<class NextLayer, bool deflateSupported>
template<class Buffers, class Decorator>
void
stream<NextLayer, deflateSupported>::
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<http::empty_body> 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<class NextLayer, bool deflateSupported>
void
stream<NextLayer, deflateSupported>::
@@ -220,8 +291,9 @@ accept(error_code& ec)
{
static_assert(is_sync_stream<next_layer_type>::value,
"SyncStream requirements not met");
impl_->reset();
do_accept(&default_decorate_res, ec);
do_accept(
net::const_buffer{},
&default_decorate_res, ec);
}
template<class NextLayer, bool deflateSupported>
@@ -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<class NextLayer, bool deflateSupported>
@@ -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<class NextLayer, bool deflateSupported>
@@ -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<class NextLayer, bool deflateSupported>
@@ -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<class NextLayer, bool deflateSupported>
template<class Decorator>
void
stream<NextLayer, deflateSupported>::
do_accept(
Decorator const& decorator,
error_code& ec)
{
http::request_parser<http::empty_body> 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<class NextLayer, bool deflateSupported>
template<class Body, class Allocator,
class Decorator>
void
stream<NextLayer, deflateSupported>::
do_accept(
http::request<Body,
http::basic_fields<Allocator>> 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

View File

@@ -12,6 +12,7 @@
#include <boost/beast/websocket/teardown.hpp>
#include <boost/beast/websocket/detail/mask.hpp>
#include <boost/beast/websocket/impl/stream_impl.hpp>
#include <boost/beast/core/async_op_base.hpp>
#include <boost/beast/core/flat_static_buffer.hpp>
#include <boost/beast/core/stream_traits.hpp>
@@ -39,26 +40,28 @@ class stream<NextLayer, deflateSupported>::close_op
Handler, beast::executor_type<stream>>
, public net::coroutine
{
stream<NextLayer, deflateSupported>& ws_;
boost::weak_ptr<impl_type> 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<class Handler_>
close_op(
Handler_&& h,
stream<NextLayer, deflateSupported>& ws,
boost::shared_ptr<impl_type> const& sp,
close_reason const& cr)
: stable_async_op_base<
Handler, beast::executor_type<stream>>(
std::forward<Handler_>(h), ws.get_executor())
, ws_(ws)
, fb_(beast::allocate_stable<detail::frame_buffer>(*this))
: stable_async_op_base<Handler,
beast::executor_type<stream>>(
std::forward<Handler_>(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<flat_static_buffer_base>(fb, cr);
impl.template write_close<flat_static_buffer_base>(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<BOOST_ASIO_HANDLER_TYPE(
CloseHandler, void(error_code))>(
std::move(init.completion_handler), *this, cr);
std::move(init.completion_handler), impl_, cr);
return init.result.get();
}

View File

@@ -10,6 +10,7 @@
#ifndef BOOST_BEAST_WEBSOCKET_IMPL_HANDSHAKE_HPP
#define BOOST_BEAST_WEBSOCKET_IMPL_HANDSHAKE_HPP
#include <boost/beast/websocket/impl/stream_impl.hpp>
#include <boost/beast/websocket/detail/type_traits.hpp>
#include <boost/beast/http/empty_body.hpp>
#include <boost/beast/http/message.hpp>
@@ -45,7 +46,7 @@ class stream<NextLayer, deflateSupported>::handshake_op
response_type res;
};
stream<NextLayer, deflateSupported>& ws_;
boost::weak_ptr<impl_type> wp_;
detail::sec_ws_key_type key_;
response_type* res_p_;
data& d_;
@@ -54,54 +55,103 @@ public:
template<class Handler_, class Decorator>
handshake_op(
Handler_&& h,
stream& ws,
boost::shared_ptr<impl_type> const& sp,
response_type* res_p,
string_view host, string_view target,
Decorator const& decorator)
: stable_async_op_base<Handler,
beast::executor_type<stream>>(
std::forward<Handler_>(h), ws.get_executor())
, ws_(ws)
std::forward<Handler_>(h),
sp->stream.get_executor())
, wp_(sp)
, res_p_(res_p)
, d_(beast::allocate_stable<data>(*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<class NextLayer, bool deflateSupported>
template<class RequestDecorator>
void
stream<NextLayer, deflateSupported>::
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<class NextLayer, bool deflateSupported>
template<class HandshakeHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(
@@ -116,9 +166,10 @@ async_handshake(string_view host,
BOOST_BEAST_HANDLER_INIT(
HandshakeHandler, void(error_code));
handshake_op<BOOST_ASIO_HANDLER_TYPE(
HandshakeHandler, void(error_code))>{
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<BOOST_ASIO_HANDLER_TYPE(
HandshakeHandler, void(error_code))>{
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<BOOST_ASIO_HANDLER_TYPE(
HandshakeHandler, void(error_code))>{
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<BOOST_ASIO_HANDLER_TYPE(
HandshakeHandler, void(error_code))>{
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<class NextLayer, bool deflateSupported>
template<class RequestDecorator>
void
stream<NextLayer, deflateSupported>::
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

View File

@@ -15,6 +15,7 @@
#include <boost/beast/core/stream_traits.hpp>
#include <boost/beast/core/detail/bind_continuation.hpp>
#include <boost/beast/websocket/detail/frame.hpp>
#include <boost/beast/websocket/impl/stream_impl.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/asio/post.hpp>
#include <boost/throw_exception.hpp>
@@ -36,7 +37,7 @@ class stream<NextLayer, deflateSupported>::ping_op
Handler, beast::executor_type<stream>>
, public net::coroutine
{
stream<NextLayer, deflateSupported>& ws_;
boost::weak_ptr<impl_type> wp_;
detail::frame_buffer& fb_;
public:
@@ -45,18 +46,19 @@ public:
template<class Handler_>
ping_op(
Handler_&& h,
stream<NextLayer, deflateSupported>& ws,
boost::shared_ptr<impl_type> const& sp,
detail::opcode op,
ping_data const& payload)
: stable_async_op_base<
Handler, beast::executor_type<stream>>(
std::forward<Handler_>(h), ws.get_executor())
, ws_(ws)
: stable_async_op_base<Handler,
beast::executor_type<stream>>(
std::forward<Handler_>(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<NextLayer, deflateSupported>& ws,
Handler&& handler)
{
using handler_type =
typename std::decay<Handler>::type;
using base_type =
beast::stable_async_op_base<
handler_type, beast::executor_type<
stream<NextLayer, deflateSupported>>>;
struct async_op : base_type, net::coroutine
{
boost::weak_ptr<impl_type> impl_;
detail::frame_buffer& fb_;
public:
static constexpr int id = 4; // for soft_mutex
async_op(
Handler&& h,
stream<NextLayer, deflateSupported>& 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>(handler));
}
#endif
//------------------------------------------------------------------------------
template<class NextLayer, bool deflateSupported>
void
stream<NextLayer, deflateSupported>::
@@ -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<flat_static_buffer_base>(
impl_->template write_ping<flat_static_buffer_base>(
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<flat_static_buffer_base>(
impl_->template write_ping<flat_static_buffer_base>(
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<BOOST_ASIO_HANDLER_TYPE(
WriteHandler, void(error_code))>(
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<BOOST_ASIO_HANDLER_TYPE(
WriteHandler, void(error_code))>(
std::move(init.completion_handler), *this,
std::move(init.completion_handler), impl_,
detail::opcode::pong, payload);
return init.result.get();
}

View File

@@ -13,6 +13,7 @@
#include <boost/beast/core/buffer_size.hpp>
#include <boost/beast/websocket/teardown.hpp>
#include <boost/beast/websocket/detail/mask.hpp>
#include <boost/beast/websocket/impl/stream_impl.hpp>
#include <boost/beast/core/async_op_base.hpp>
#include <boost/beast/core/bind_handler.hpp>
#include <boost/beast/core/buffers_prefix.hpp>
@@ -50,7 +51,7 @@ class stream<NextLayer, deflateSupported>::read_some_op
Handler, beast::executor_type<stream>>
, public net::coroutine
{
stream& ws_;
boost::weak_ptr<impl_type> wp_;
MutableBufferSequence bs_;
buffers_suffix<MutableBufferSequence> cb_;
std::size_t bytes_written_ = 0;
@@ -64,12 +65,13 @@ public:
template<class Handler_>
read_some_op(
Handler_&& h,
stream<NextLayer, deflateSupported>& ws,
boost::shared_ptr<impl_type> const& sp,
MutableBufferSequence const& bs)
: async_op_base<
Handler, beast::executor_type<stream>>(
std::forward<Handler_>(h), ws.get_executor())
, ws_(ws)
std::forward<Handler_>(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<NextLayer, deflateSupported>::read_op
Handler, beast::executor_type<stream>>
, public net::coroutine
{
stream<NextLayer, deflateSupported>& ws_;
boost::weak_ptr<impl_type> wp_;
DynamicBuffer& b_;
std::size_t limit_;
std::size_t bytes_written_ = 0;
@@ -623,14 +631,15 @@ public:
template<class Handler_>
read_op(
Handler_&& h,
stream<NextLayer, deflateSupported>& ws,
boost::shared_ptr<impl_type> const& sp,
DynamicBuffer& b,
std::size_t limit,
bool some)
: async_op_base<
Handler, beast::executor_type<stream>>(
std::forward<Handler_>(h), ws.get_executor())
, ws_(ws)
: async_op_base<Handler,
beast::executor_type<stream>>(
std::forward<Handler_>(h),
sp->stream.get_executor())
, wp_(sp)
, b_(b)
, limit_(limit ? limit : (
std::numeric_limits<std::size_t>::max)())
@@ -645,26 +654,35 @@ public:
bool cont = true)
{
using beast::detail::clamp;
boost::optional<typename
DynamicBuffer::mutable_buffers_type> 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<mutable_buffers_type> 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<mutable_buffers_type, read_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<DynamicBuffer, BOOST_ASIO_HANDLER_TYPE(
ReadHandler, void(error_code, std::size_t))>(
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<DynamicBuffer, BOOST_ASIO_HANDLER_TYPE(
ReadHandler, void(error_code, std::size_t))>(
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<flat_static_buffer_base>(fb,
impl.template write_ping<flat_static_buffer_base>(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<MutableBufferSequence, BOOST_ASIO_HANDLER_TYPE(
ReadHandler, void(error_code, std::size_t))>(
std::move(init.completion_handler), *this, buffers);
std::move(init.completion_handler), impl_, buffers);
return init.result.get();
}

View File

@@ -15,6 +15,7 @@
#include <boost/beast/websocket/teardown.hpp>
#include <boost/beast/websocket/detail/hybi13.hpp>
#include <boost/beast/websocket/detail/mask.hpp>
#include <boost/beast/websocket/impl/stream_impl.hpp>
#include <boost/beast/version.hpp>
#include <boost/beast/http/read.hpp>
#include <boost/beast/http/write.hpp>
@@ -29,6 +30,7 @@
#include <boost/asio/steady_timer.hpp>
#include <boost/assert.hpp>
#include <boost/endian/buffers.hpp>
#include <boost/make_shared.hpp>
#include <boost/make_unique.hpp>
#include <boost/throw_exception.hpp>
#include <algorithm>
@@ -45,7 +47,7 @@ template<class NextLayer, bool deflateSupported>
template<class... Args>
stream<NextLayer, deflateSupported>::
stream(Args&&... args)
: impl_(std::make_shared<impl_type>(
: impl_(boost::make_shared<impl_type>(
std::forward<Args>(args)...))
{
BOOST_ASSERT(impl_->rd_buf.max_size() >=
@@ -140,6 +142,30 @@ read_size_hint(DynamicBuffer& buffer) const
}
//------------------------------------------------------------------------------
//
// Settings
//
//------------------------------------------------------------------------------
// timeout
template<class NextLayer, bool deflateSupported>
void
stream<NextLayer, deflateSupported>::
get_option(timeout& opt)
{
opt = impl_->timeout_opt;
}
template<class NextLayer, bool deflateSupported>
void
stream<NextLayer, deflateSupported>::
set_option(timeout const& opt)
{
impl_->set_option(opt);
}
//
template<class NextLayer, bool deflateSupported>
void
@@ -229,7 +255,7 @@ void
stream<NextLayer, deflateSupported>::
secure_prng(bool value)
{
this->secure_prng_ = value;
this->impl_->secure_prng_ = value;
}
template<class NextLayer, bool deflateSupported>
@@ -271,471 +297,6 @@ text() const
//------------------------------------------------------------------------------
// Attempt to read a complete frame header.
// Returns `false` if more bytes are needed
template<class NextLayer, bool deflateSupported>
template<class DynamicBuffer>
bool
stream<NextLayer, deflateSupported>::
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<typename
DynamicBuffer::const_buffers_type> 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<class NextLayer, bool deflateSupported>
template<class DynamicBuffer>
void
stream<NextLayer, deflateSupported>::
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<class NextLayer, bool deflateSupported>
template<class DynamicBuffer>
void
stream<NextLayer, deflateSupported>::
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<class NextLayer, bool deflateSupported>
template<class Decorator>
request_type
stream<NextLayer, deflateSupported>::
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<class NextLayer, bool deflateSupported>
template<class Body, class Allocator, class Decorator>
response_type
stream<NextLayer, deflateSupported>::
build_response(
http::request<Body,
http::basic_fields<Allocator>> 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<class NextLayer, bool deflateSupported>
void
stream<NextLayer, deflateSupported>::
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<class NextLayer, bool deflateSupported>
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();
}

View File

@@ -10,16 +10,32 @@
#ifndef BOOST_BEAST_WEBSOCKET_IMPL_STREAM_IMPL_HPP
#define BOOST_BEAST_WEBSOCKET_IMPL_STREAM_IMPL_HPP
#include <boost/beast/version.hpp>
#include <boost/beast/http/read.hpp>
#include <boost/beast/http/write.hpp>
#include <boost/beast/http/rfc7230.hpp>
#include <boost/beast/core/buffers_cat.hpp>
#include <boost/beast/core/buffers_prefix.hpp>
#include <boost/beast/core/buffers_suffix.hpp>
#include <boost/beast/core/flat_static_buffer.hpp>
#include <boost/beast/core/saved_handler.hpp>
#include <boost/beast/core/static_buffer.hpp>
#include <boost/beast/core/stream_traits.hpp>
#include <boost/beast/core/detail/clamp.hpp>
#include <boost/beast/core/detail/type_traits.hpp>
#include <boost/beast/websocket/rfc6455.hpp>
#include <boost/beast/websocket/detail/frame.hpp>
#include <boost/beast/websocket/detail/hybi13.hpp>
#include <boost/beast/websocket/detail/mask.hpp>
#include <boost/beast/websocket/detail/pmd_extension.hpp>
#include <boost/beast/websocket/detail/prng.hpp>
#include <boost/beast/websocket/detail/soft_mutex.hpp>
#include <boost/beast/websocket/detail/utf8_checker.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/bind_executor.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/core/empty_value.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/optional.hpp>
namespace boost {
@@ -29,7 +45,7 @@ namespace websocket {
template<
class NextLayer, bool deflateSupported>
struct stream<NextLayer, deflateSupported>::impl_type
: std::enable_shared_from_this<impl_type>
: boost::enable_shared_from_this<impl_type>
, detail::impl_base<deflateSupported>
{
NextLayer stream; // The underlying stream
@@ -68,17 +84,25 @@ struct stream<NextLayer, deflateSupported>::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<bool> 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<class... Args>
impl_type(Args&&... args)
@@ -91,6 +115,9 @@ struct stream<NextLayer, deflateSupported>::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<NextLayer, deflateSupported>::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<NextLayer, deflateSupported>::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<NextLayer, deflateSupported>::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<NextLayer, deflateSupported>::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<class Decorator>
request_type
build_request(
detail::sec_ws_key_type& key,
string_view host, string_view target,
Decorator const& decorator);
template<class Executor>
class timeout_handler
: boost::empty_value<Executor>
{
std::weak_ptr<impl_type> wp_;
public:
timeout_handler(
Executor const& ex,
std::shared_ptr<impl_type> const& sp)
: boost::empty_value<Executor>(
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<class Body, class Allocator, class Decorator>
response_type
build_response(
http::request<Body,
http::basic_fields<Allocator>> const& req,
Decorator const& decorator,
error_code& result);
// Attempt to read a complete frame header.
// Returns `false` if more bytes are needed
template<class DynamicBuffer>
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<class Executor>
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<Executor>(
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<Executor>(
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<class DynamicBuffer>
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<class DynamicBuffer>
void
write_ping(DynamicBuffer& db,
detail::opcode code, ping_data const& data);
template<class DynamicBuffer>
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<class Executor>
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<Executor>(
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<Executor>(
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<Executor>(
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 Executor>
class timeout_handler
: boost::empty_value<Executor>
{
boost::weak_ptr<impl_type> wp_;
public:
timeout_handler(
Executor const& ex,
boost::weak_ptr<impl_type>&& wp)
: boost::empty_value<Executor>(
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<class NextLayer, bool deflateSupported>
template<class Decorator>
request_type
stream<NextLayer, deflateSupported>::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<class NextLayer, bool deflateSupported>
void
stream<NextLayer, deflateSupported>::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<class NextLayer, bool deflateSupported>
template<class Body, class Allocator, class Decorator>
response_type
stream<NextLayer, deflateSupported>::impl_type::
build_response(
http::request<Body,
http::basic_fields<Allocator>> 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<class NextLayer, bool deflateSupported>
template<class DynamicBuffer>
bool
stream<NextLayer, deflateSupported>::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<typename
DynamicBuffer::const_buffers_type> 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<class NextLayer, bool deflateSupported>
template<class DynamicBuffer>
void
stream<NextLayer, deflateSupported>::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<class NextLayer, bool deflateSupported>
template<class DynamicBuffer>
void
stream<NextLayer, deflateSupported>::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

View File

@@ -24,6 +24,7 @@
#include <boost/beast/core/detail/clamp.hpp>
#include <boost/beast/core/detail/config.hpp>
#include <boost/beast/websocket/detail/frame.hpp>
#include <boost/beast/websocket/impl/stream_impl.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/assert.hpp>
#include <boost/config.hpp>
@@ -51,7 +52,7 @@ class stream<NextLayer, deflateSupported>::write_some_op
do_deflate
};
stream& ws_;
boost::weak_ptr<impl_type> wp_;
buffers_suffix<Buffers> cb_;
detail::frame_header fh_;
detail::prepared_key key_;
@@ -69,17 +70,18 @@ public:
template<class Handler_>
write_some_op(
Handler_&& h,
stream<NextLayer, deflateSupported>& ws,
boost::shared_ptr<impl_type> const& sp,
bool fin,
Buffers const& bs)
: beast::async_op_base<Handler,
beast::executor_type<stream>>(
std::forward<Handler_>(h), ws.get_executor())
, ws_(ws)
std::forward<Handler_>(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<flat_static_buffer_base>(
@@ -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<ConstBufferSequence, BOOST_ASIO_HANDLER_TYPE(
WriteHandler, void(error_code, std::size_t))>(
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<ConstBufferSequence, BOOST_ASIO_HANDLER_TYPE(
WriteHandler, void(error_code, std::size_t))>(
std::move(init.completion_handler), *this, true, bs);
std::move(init.completion_handler), impl_, true, bs);
return init.result.get();
}

View File

@@ -15,15 +15,17 @@
#include <boost/beast/websocket/option.hpp>
#include <boost/beast/websocket/role.hpp>
#include <boost/beast/websocket/rfc6455.hpp>
#include <boost/beast/websocket/stream_base.hpp>
#include <boost/beast/websocket/stream_fwd.hpp>
#include <boost/beast/websocket/detail/pmd_extension.hpp>
#include <boost/beast/websocket/detail/stream_base.hpp>
#include <boost/beast/websocket/detail/hybi13.hpp>
#include <boost/beast/websocket/detail/impl_base.hpp>
#include <boost/beast/websocket/detail/pmd_extension.hpp>
#include <boost/beast/core/string.hpp>
#include <boost/beast/core/detail/type_traits.hpp>
#include <boost/beast/http/detail/type_traits.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/asio/error.hpp>
#include <boost/shared_ptr.hpp>
#include <algorithm>
#include <cstdint>
#include <functional>
@@ -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<ip::tcp::socket> ws{io_context};
websocket::stream<tcp_stream<
net::io_context::strand>> 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<ip::tcp::socket&> ws{sock};
websocket::stream<tcp_stream<net::io_context::executor_type>> 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_type> impl_;
boost::shared_ptr<impl_type> impl_;
using time_point = typename
std::chrono::steady_clock::time_point;
@@ -351,6 +352,20 @@ public:
//
//--------------------------------------------------------------------------
#if BOOST_BEAST_DOXYGEN
template<class Option>
void
get_option(Option& opt);
template<class Option>
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> class close_op;
template<class> class handshake_op;
template<class> class ping_op;
template<class> class auto_ping_op;
template<class, class> class read_some_op;
template<class, class> class read_op;
template<class> class response_op;
@@ -3487,55 +3503,14 @@ private:
static void default_decorate_req(request_type&) {}
static void default_decorate_res(response_type&) {}
template<class DynamicBuffer>
bool
parse_fh(
detail::frame_header& fh,
DynamicBuffer& b,
error_code& ec);
template<class DynamicBuffer>
void
write_close(DynamicBuffer& b, close_reason const& rc);
template<class DynamicBuffer>
void
write_ping(DynamicBuffer& b,
detail::opcode op, ping_data const& data);
//
// upgrade
//
template<class Decorator>
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<Body,
http::basic_fields<Allocator>> 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<class Decorator>
template<class Buffers, class Decorator>
void
do_accept(
Buffers const& buffers,
Decorator const& decorator,
error_code& ec);

View File

@@ -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 <boost/beast/core/detail/config.hpp>
#include <boost/beast/websocket/role.hpp>
#include <chrono>
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

View File

@@ -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

View File

@@ -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

View File

@@ -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 <boost/beast/websocket/detail/stream_base.hpp>
#include <boost/beast/websocket/detail/impl_base.hpp>

View File

@@ -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)

View File

@@ -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 <boost/beast/websocket/stream.hpp>
#include <boost/beast/_experimental/test/tcp.hpp>
#include "test.hpp"
#include <boost/asio/io_context.hpp>
@@ -613,6 +614,94 @@ public:
);
}
void
testTimeout()
{
using tcp = net::ip::tcp;
net::io_context ioc;
// success
{
stream<tcp::socket> ws1(ioc);
stream<tcp::socket> 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<test::stream> ws1(ioc);
stream<test::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<tcp::socket> ws1(ioc);
stream<tcp::socket> 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<test::stream> ws1(ioc);
stream<test::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<tcp::socket> ws1(ioc);
stream<tcp::socket> 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<test::stream> ws1(ioc);
stream<test::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();
}

View File

@@ -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 <boost/beast/websocket/stream.hpp>
#include <boost/beast/_experimental/test/stream.hpp>
#include <boost/beast/_experimental/test/tcp.hpp>
#include "test.hpp"
#include <boost/asio/io_context.hpp>
@@ -168,6 +170,114 @@ public:
});
}
void
testTimeout()
{
using tcp = net::ip::tcp;
net::io_context ioc;
// success
{
stream<tcp::socket> ws1(ioc);
stream<tcp::socket> 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<test::stream> ws1(ioc);
stream<test::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<tcp::socket> ws1(ioc);
stream<tcp::socket> 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<test::stream> ws1(ioc);
stream<test::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<tcp::socket> ws1(ioc);
stream<tcp::socket> 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<test::stream> ws1(ioc);
stream<test::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<test::stream> ws{ioc};
ws.async_close({}, net::bind_executor(
s, copyable_handler{}));
}
void
run() override
{
testClose();
testTimeout();
testSuspend();
testMoveOnly();
testAsioHandlerInvoke();
}
};

View File

@@ -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 <boost/beast/websocket/stream.hpp>
#include <boost/beast/_experimental/test/stream.hpp>
#include <boost/beast/_experimental/test/tcp.hpp>
#include "test.hpp"
#include <boost/asio/io_context.hpp>
@@ -235,6 +238,96 @@ public:
);
}
void
testTimeout()
{
using tcp = net::ip::tcp;
net::io_context ioc;
// success
{
stream<tcp::socket> ws1(ioc);
stream<tcp::socket> 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<test::stream> ws1(ioc);
stream<test::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<tcp::socket> ws1(ioc);
stream<tcp::socket> 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<test::stream> ws1(ioc);
stream<test::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<tcp::socket> ws1(ioc);
stream<tcp::socket> 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<test::stream> ws1(ioc);
stream<test::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<test::stream> ws{ioc};
ws.async_handshake("localhost", "/",
net::bind_executor(
s, copyable_handler{}));
}
void
run() override
{
testHandshake();
testTimeout();
testExtRead();
testExtWrite();
testExtNegotiate();
testMoveOnly();
testAsioHandlerInvoke();
}
};

View File

@@ -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<test::stream> ws{ioc};
ws.async_ping({}, net::bind_executor(
s, copyable_handler{}));
}
void
run() override
{
testPing();
testSuspend();
testMoveOnly();
testAsioHandlerInvoke();
}
};

View File

@@ -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 <boost/beast/websocket/stream.hpp>
#include "test.hpp"
#include <boost/asio/write.hpp>
#include <boost/config/workaround.hpp>
#if BOOST_WORKAROUND(BOOST_GCC, < 80200)
#define BOOST_BEAST_SYMBOL_HIDDEN __attribute__ ((visibility("hidden")))
#else
#define BOOST_BEAST_SYMBOL_HIDDEN
#endif
#include <boost/beast/_experimental/test/stream.hpp>
#include <boost/beast/_experimental/test/tcp.hpp>
#include <boost/beast/core/flat_buffer.hpp>
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<class Wrap, bool deflateSupported>
void
doReadTest(
Wrap const& w,
ws_type_t<deflateSupported>& 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<tcp::socket> ws1(ioc);
stream<tcp::socket> 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<class Wrap, bool deflateSupported>
void
doFailTest(
Wrap const& w,
ws_type_t<deflateSupported>& 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<bool deflateSupported = true, class Wrap>
void
doTestRead(Wrap const& w)
{
permessage_deflate pmd;
pmd.client_enable = false;
pmd.server_enable = false;
// already closed
{
echo_server es{log};
stream<test::stream, deflateSupported> 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<deflateSupported>(pmd,
[&](ws_type_t<deflateSupported>& 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<deflateSupported>(pmd,
[&](ws_type_t<deflateSupported>& 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<deflateSupported>(pmd,
[&](ws_type_t<deflateSupported>& 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<deflateSupported>(pmd,
[&](ws_type_t<deflateSupported>& 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<deflateSupported>(pmd,
[&](ws_type_t<deflateSupported>& 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<deflateSupported>(pmd,
[&](ws_type_t<deflateSupported>& 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<deflateSupported> 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<test::stream, deflateSupported> 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<deflateSupported>(pmd,
[&](ws_type_t<deflateSupported>& ws)
{
w.close(ws, {});
multi_buffer b;
doFailTest(w, ws,
net::error::operation_aborted);
});
// buffer overflow
doTest<deflateSupported>(pmd,
[&](ws_type_t<deflateSupported>& 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<deflateSupported>(pmd,
[&](ws_type_t<deflateSupported>& 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<deflateSupported>(pmd,
[&](ws_type_t<deflateSupported>& ws)
{
w.write_raw(ws, cbuf(
0x8f, 0x80, 0xff, 0xff, 0xff, 0xff));
doReadTest(w, ws, close_code::protocol_error);
});
// bad close
doTest<deflateSupported>(pmd,
[&](ws_type_t<deflateSupported>& ws)
{
put(ws.next_layer().buffer(), cbuf(
0x88, 0x02, 0x03, 0xed));
doFailTest(w, ws, error::bad_close_code);
});
// message size above 2^64
doTest<deflateSupported>(pmd,
[&](ws_type_t<deflateSupported>& 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<deflateSupported>(pmd,
[&](ws_type_t<deflateSupported>& ws)
{
ws.read_message_max(1);
w.write(ws, sbuf("**"));
doFailTest(w, ws, error::message_too_big);
});
// bad utf8
doTest<deflateSupported>(pmd,
[&](ws_type_t<deflateSupported>& 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<deflateSupported>(pmd,
[&](ws_type_t<deflateSupported>& 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<deflateSupported>(pmd,
[&](ws_type_t<deflateSupported>& 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<test::stream, deflateSupported> 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<class Wrap>
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<true>(pmd,
[&](ws_type_t<true>& 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<true>(pmd,
[&](ws_type_t<true>& 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<true>(pmd,
[&](ws_type_t<true>& 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<test::stream> ws1(ioc);
stream<test::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<class Wrap>
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<tcp::socket> ws1(ioc);
stream<tcp::socket> 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<test::stream> ws1(ioc);
stream<test::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<tcp::socket> ws1(ioc);
stream<tcp::socket> 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<test::stream> ws1(ioc);
stream<test::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<false>(SyncClient{});
doTestRead<true>(SyncClient{});
doTestReadDeflate(SyncClient{});
yield_to([&](yield_context yield)
{
doTestRead<false>(AsyncClient{yield});
doTestRead<true>(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<test::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();
}
};

File diff suppressed because it is too large Load Diff

View File

@@ -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 <boost/beast/websocket/stream.hpp>
#include "test.hpp"
#include <boost/asio/buffer.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/strand.hpp>
#include <boost/asio/write.hpp>
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<test::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<test::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<test::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<test::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<test::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<test::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<test::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<test::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<test::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<test::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<test::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<test::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<test::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<test::stream> wsc{ioc};
stream<test::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<test::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<test::stream> wsc{ioc};
stream<test::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<test::stream> wsc{ioc};
stream<test::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<test::stream> wsc{ioc};
stream<test::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<test::stream> ws{ioc};
ws.async_read_some(
net::mutable_buffer{},
move_only_handler{});
}
struct copyable_handler
{
template<class... Args>
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<test::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

View File

@@ -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 <boost/beast/websocket/stream.hpp>
#include <boost/beast/core/tcp_stream.hpp>
#include <boost/asio/strand.hpp>
#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<test::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<tcp_stream<
net::io_context::strand>> ws{net::io_context::strand(ioc)};
}
{
websocket::stream<tcp_stream<net::io_context::executor_type>> ws(ioc);
}
}
void
run() override
{
@@ -136,6 +178,7 @@ public:
sizeof(websocket::stream<test::stream&>::impl_type) << std::endl;
testOptions();
testJavadoc();
}
};

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -10,7 +10,6 @@
// Test that header file is self-contained.
#include <boost/beast/websocket/stream.hpp>
#include <boost/beast/core/flat_buffer.hpp>
#include <boost/asio/ip/tcp.hpp>
#include "test.hpp"
@@ -25,108 +24,9 @@ class timer_test
public:
using tcp = boost::asio::ip::tcp;
static
void
connect(
stream<tcp::socket>& ws1,
stream<tcp::socket>& 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<test::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<tcp::socket> ws1(ioc);
stream<tcp::socket> 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();
}
};

View File

@@ -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)

View File

@@ -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)