From 085fb66b26376fded85dce579b8ff11f53c3a788 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sun, 17 Feb 2019 05:23:40 -0800 Subject: [PATCH] websocket test coverage --- .../beast/websocket/detail/impl_base.hpp | 12 +- include/boost/beast/websocket/impl/accept.hpp | 132 +++- .../beast/websocket/impl/stream_impl.hpp | 115 +-- include/boost/beast/websocket/stream.hpp | 2 +- test/beast/websocket/accept.cpp | 744 ++++++++++-------- test/beast/websocket/test.hpp | 606 ++++++++++++++ 6 files changed, 1152 insertions(+), 459 deletions(-) diff --git a/include/boost/beast/websocket/detail/impl_base.hpp b/include/boost/beast/websocket/detail/impl_base.hpp index f6ccdae4..111ee015 100644 --- a/include/boost/beast/websocket/detail/impl_base.hpp +++ b/include/boost/beast/websocket/detail/impl_base.hpp @@ -189,13 +189,7 @@ struct impl_base build_response_pmd( http::response& res, http::request> const& req) - { - pmd_offer offer; - pmd_offer unused; - pmd_read(offer, req); - pmd_negotiate(res, unused, offer, pmd_opts_); - } + http::basic_fields> const& req); void on_response_pmd( @@ -399,9 +393,7 @@ struct impl_base build_response_pmd( http::response&, http::request> const&) - { - } + http::basic_fields> const&); void on_response_pmd( diff --git a/include/boost/beast/websocket/impl/accept.hpp b/include/boost/beast/websocket/impl/accept.hpp index 2c5f1b01..ef0266ff 100644 --- a/include/boost/beast/websocket/impl/accept.hpp +++ b/include/boost/beast/websocket/impl/accept.hpp @@ -10,7 +10,6 @@ #ifndef BOOST_BEAST_WEBSOCKET_IMPL_ACCEPT_IPP #define BOOST_BEAST_WEBSOCKET_IMPL_ACCEPT_IPP -#include #include #include #include @@ -19,10 +18,11 @@ #include #include #include -#include +#include #include #include #include +#include #include #include #include @@ -34,6 +34,134 @@ namespace boost { namespace beast { namespace websocket { +//------------------------------------------------------------------------------ + +namespace detail { + +template +void +impl_base:: +build_response_pmd( + http::response& res, + http::request> const& req) +{ + pmd_offer offer; + pmd_offer unused; + pmd_read(offer, req); + pmd_negotiate(res, unused, offer, pmd_opts_); +} + +template +void +impl_base:: +build_response_pmd( + http::response&, + http::request> const&) +{ +} + +} // detail + +template +template +response_type +stream::impl_type:: +build_response( + http::request> const& req, + Decorator const& decorator, + error_code& result) +{ + auto const decorate = + [&decorator](response_type& res) + { + decorator(res); + if(! res.count(http::field::server)) + { + BOOST_STATIC_ASSERT(sizeof(BOOST_BEAST_VERSION_STRING) < 20); + static_string<20> s(BOOST_BEAST_VERSION_STRING); + res.set(http::field::server, s); + } + }; + auto err = + [&](error e) + { + result = e; + response_type res; + res.version(req.version()); + res.result(http::status::bad_request); + res.body() = result.message(); + res.prepare_payload(); + decorate(res); + return res; + }; + if(req.version() != 11) + return err(error::bad_http_version); + if(req.method() != http::verb::get) + return err(error::bad_method); + if(! req.count(http::field::host)) + return err(error::no_host); + { + auto const it = req.find(http::field::connection); + if(it == req.end()) + return err(error::no_connection); + if(! http::token_list{it->value()}.exists("upgrade")) + return err(error::no_connection_upgrade); + } + { + auto const it = req.find(http::field::upgrade); + if(it == req.end()) + return err(error::no_upgrade); + if(! http::token_list{it->value()}.exists("websocket")) + return err(error::no_upgrade_websocket); + } + string_view key; + { + auto const it = req.find(http::field::sec_websocket_key); + if(it == req.end()) + return err(error::no_sec_key); + key = it->value(); + if(key.size() > detail::sec_ws_key_type::max_size_n) + return err(error::bad_sec_key); + } + { + auto const it = req.find(http::field::sec_websocket_version); + if(it == req.end()) + return err(error::no_sec_version); + if(it->value() != "13") + { + response_type res; + res.result(http::status::upgrade_required); + res.version(req.version()); + res.set(http::field::sec_websocket_version, "13"); + result = error::bad_sec_version; + res.body() = result.message(); + res.prepare_payload(); + decorate(res); + return res; + } + } + + response_type res; + res.result(http::status::switching_protocols); + res.version(req.version()); + res.set(http::field::upgrade, "websocket"); + res.set(http::field::connection, "upgrade"); + { + detail::sec_ws_accept_type acc; + detail::make_sec_ws_accept(acc, key); + res.set(http::field::sec_websocket_accept, acc); + } + this->build_response_pmd(res, req); + decorate(res); + result = {}; + return res; +} + +//------------------------------------------------------------------------------ + /** Respond to an HTTP request */ template diff --git a/include/boost/beast/websocket/impl/stream_impl.hpp b/include/boost/beast/websocket/impl/stream_impl.hpp index ffebaf05..b700a640 100644 --- a/include/boost/beast/websocket/impl/stream_impl.hpp +++ b/include/boost/beast/websocket/impl/stream_impl.hpp @@ -10,7 +10,14 @@ #ifndef BOOST_BEAST_WEBSOCKET_IMPL_STREAM_IMPL_HPP #define BOOST_BEAST_WEBSOCKET_IMPL_STREAM_IMPL_HPP -#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -23,14 +30,6 @@ #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include #include #include #include @@ -588,104 +587,6 @@ on_response( this->open(role_type::client); } -//-------------------------------------------------------------------------- - -template -template -response_type -stream::impl_type:: -build_response( - http::request> const& req, - Decorator const& decorator, - error_code& result) -{ - auto const decorate = - [&decorator](response_type& res) - { - decorator(res); - if(! res.count(http::field::server)) - { - BOOST_STATIC_ASSERT(sizeof(BOOST_BEAST_VERSION_STRING) < 20); - static_string<20> s(BOOST_BEAST_VERSION_STRING); - res.set(http::field::server, s); - } - }; - auto err = - [&](error e) - { - result = e; - response_type res; - res.version(req.version()); - res.result(http::status::bad_request); - res.body() = result.message(); - res.prepare_payload(); - decorate(res); - return res; - }; - if(req.version() != 11) - return err(error::bad_http_version); - if(req.method() != http::verb::get) - return err(error::bad_method); - if(! req.count(http::field::host)) - return err(error::no_host); - { - auto const it = req.find(http::field::connection); - if(it == req.end()) - return err(error::no_connection); - if(! http::token_list{it->value()}.exists("upgrade")) - return err(error::no_connection_upgrade); - } - { - auto const it = req.find(http::field::upgrade); - if(it == req.end()) - return err(error::no_upgrade); - if(! http::token_list{it->value()}.exists("websocket")) - return err(error::no_upgrade_websocket); - } - string_view key; - { - auto const it = req.find(http::field::sec_websocket_key); - if(it == req.end()) - return err(error::no_sec_key); - key = it->value(); - if(key.size() > detail::sec_ws_key_type::max_size_n) - return err(error::bad_sec_key); - } - { - auto const it = req.find(http::field::sec_websocket_version); - if(it == req.end()) - return err(error::no_sec_version); - if(it->value() != "13") - { - response_type res; - res.result(http::status::upgrade_required); - res.version(req.version()); - res.set(http::field::sec_websocket_version, "13"); - result = error::bad_sec_version; - res.body() = result.message(); - res.prepare_payload(); - decorate(res); - return res; - } - } - - response_type res; - res.result(http::status::switching_protocols); - res.version(req.version()); - res.set(http::field::upgrade, "websocket"); - res.set(http::field::connection, "upgrade"); - { - detail::sec_ws_accept_type acc; - detail::make_sec_ws_accept(acc, key); - res.set(http::field::sec_websocket_accept, acc); - } - this->build_response_pmd(res, req); - decorate(res); - result = {}; - return res; -} - //------------------------------------------------------------------------------ // Attempt to read a complete frame header. diff --git a/include/boost/beast/websocket/stream.hpp b/include/boost/beast/websocket/stream.hpp index a7c441ad..7c28c5c2 100644 --- a/include/boost/beast/websocket/stream.hpp +++ b/include/boost/beast/websocket/stream.hpp @@ -3570,10 +3570,10 @@ seed_prng(std::seed_seq& ss) } // beast } // boost +#include // must be first #include #include #include -#include #include #include #include diff --git a/test/beast/websocket/accept.cpp b/test/beast/websocket/accept.cpp index cfdf77c7..41430c3b 100644 --- a/test/beast/websocket/accept.cpp +++ b/test/beast/websocket/accept.cpp @@ -10,7 +10,9 @@ // Test that header file is self-contained. #include +#include #include +#include #include "test.hpp" #include @@ -20,90 +22,82 @@ namespace boost { namespace beast { namespace websocket { -class accept_test : public websocket_test_suite +class accept_test : public unit_test::suite //: public websocket_test_suite { public: - template - void - doTestAccept(Wrap const& w) + class res_decorator { - class res_decorator + bool& b_; + + public: + res_decorator(res_decorator const&) = default; + + explicit + res_decorator(bool& b) + : b_(b) { - bool& b_; + } - public: - res_decorator(res_decorator const&) = default; - - explicit - res_decorator(bool& b) - : b_(b) - { - } - - void - operator()(response_type&) const - { - this->b_ = true; - } - }; - - auto const big = [] + void + operator()(response_type&) const { - std::string s; - s += "X1: " + std::string(2000, '*') + "\r\n"; - return s; - }(); + this->b_ = true; + } + }; - // request in stream - doStreamLoop([&](test::stream& ts) - { - stream ws{ts}; - auto tr = connect(ws.next_layer()); - ts.append( - "GET / HTTP/1.1\r\n" - "Host: localhost\r\n" - "Upgrade: websocket\r\n" - "Connection: upgrade\r\n" - "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" - "Sec-WebSocket-Version: 13\r\n" - "\r\n"); - ts.read_size(20); - w.accept(ws); - // VFALCO validate contents of ws.next_layer().str? - }); + template + static + net::const_buffer + sbuf(const char (&s)[N]) + { + return net::const_buffer(&s[0], N-1); + } - // request in stream, oversized + static + void + fail_loop( + std::function&)> f, + std::chrono::steady_clock::duration amount = + std::chrono::seconds(5)) + { + using clock_type = std::chrono::steady_clock; + auto const expires_at = + clock_type::now() + amount; + net::io_context ioc; + for(std::size_t n = 0;;++n) { - stream ws{ioc_, - "GET / HTTP/1.1\r\n" - "Host: localhost\r\n" - "Upgrade: websocket\r\n" - "Connection: upgrade\r\n" - "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" - "Sec-WebSocket-Version: 13\r\n" - + big + - "\r\n"}; - auto tr = connect(ws.next_layer()); + test::fail_count fc(n); try { - w.accept(ws); - fail("", __FILE__, __LINE__); + stream ws(ioc, fc); + auto tr = connect(ws.next_layer()); + f(ws); + break; } catch(system_error const& se) { - // VFALCO Its the http error category... - BEAST_EXPECTS( - se.code() == http::error::buffer_overflow, - se.code().message()); + if(! BEAST_EXPECTS( + se.code() == test::error::test_failure, + se.code().message())) + throw; + if(! BEAST_EXPECTS( + clock_type::now() < expires_at, + "a test timeout occurred")) + break; } } + } - // request in stream, decorator - doStreamLoop([&](test::stream& ts) + template + void + testMatrix(Api api) + { + net::io_context ioc; + + // request in stream + fail_loop([&](stream& ws) { - stream ws{ts}; - auto tr = connect(ws.next_layer()); - ts.append( + ws.next_layer().append( "GET / HTTP/1.1\r\n" "Host: localhost\r\n" "Upgrade: websocket\r\n" @@ -111,45 +105,31 @@ public: "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 13\r\n" "\r\n"); - ts.read_size(20); + ws.next_layer().read_size(20); + api.accept(ws); + }); + + // request in stream, decorator + fail_loop([&](stream& ws) + { + ws.next_layer().append( + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n"); + ws.next_layer().read_size(20); bool called = false; - w.accept_ex(ws, res_decorator{called}); + api.accept_ex(ws, res_decorator{called}); BEAST_EXPECT(called); }); - // request in stream, decorator, oversized - { - stream ws{ioc_, - "GET / HTTP/1.1\r\n" - "Host: localhost\r\n" - "Upgrade: websocket\r\n" - "Connection: upgrade\r\n" - "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" - "Sec-WebSocket-Version: 13\r\n" - + big + - "\r\n"}; - auto tr = connect(ws.next_layer()); - try - { - bool called = false; - w.accept_ex(ws, res_decorator{called}); - fail("", __FILE__, __LINE__); - } - catch(system_error const& se) - { - // VFALCO Its the http error category... - BEAST_EXPECTS( - se.code() == http::error::buffer_overflow, - se.code().message()); - } - } - // request in buffers - doStreamLoop([&](test::stream& ts) + fail_loop([&](stream& ws) { - stream ws{ts}; - auto tr = connect(ws.next_layer()); - w.accept(ws, sbuf( + api.accept(ws, sbuf( "GET / HTTP/1.1\r\n" "Host: localhost\r\n" "Upgrade: websocket\r\n" @@ -160,39 +140,11 @@ public: )); }); - // request in buffers, oversize - { - stream ws{ioc_}; - auto tr = connect(ws.next_layer()); - try - { - w.accept(ws, net::buffer( - "GET / HTTP/1.1\r\n" - "Host: localhost\r\n" - "Upgrade: websocket\r\n" - "Connection: upgrade\r\n" - "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" - "Sec-WebSocket-Version: 13\r\n" - + big + - "\r\n" - )); - fail("", __FILE__, __LINE__); - } - catch(system_error const& se) - { - BEAST_EXPECTS( - se.code() == error::buffer_overflow, - se.code().message()); - } - } - // request in buffers, decorator - doStreamLoop([&](test::stream& ts) + fail_loop([&](stream& ws) { - stream ws{ts}; - auto tr = connect(ws.next_layer()); bool called = false; - w.accept_ex(ws, sbuf( + api.accept_ex(ws, sbuf( "GET / HTTP/1.1\r\n" "Host: localhost\r\n" "Upgrade: websocket\r\n" @@ -204,14 +156,235 @@ public: BEAST_EXPECT(called); }); - // request in buffers, decorator, oversized + // request in buffers and stream + fail_loop([&](stream& ws) { - stream ws{ioc_}; + ws.next_layer().append( + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n"); + ws.next_layer().read_size(16); + api.accept(ws, sbuf( + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n" + )); + // VFALCO validate contents of ws.next_layer().str? + }); + + // request in buffers and stream, decorator + fail_loop([&](stream& ws) + { + ws.next_layer().append( + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n"); + ws.next_layer().read_size(16); + bool called = false; + api.accept_ex(ws, sbuf( + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n"), + res_decorator{called}); + BEAST_EXPECT(called); + }); + + // request in message + { + request_type req; + req.method(http::verb::get); + req.target("/"); + req.version(11); + req.insert(http::field::host, "localhost"); + req.insert(http::field::upgrade, "websocket"); + req.insert(http::field::connection, "upgrade"); + req.insert(http::field::sec_websocket_key, "dGhlIHNhbXBsZSBub25jZQ=="); + req.insert(http::field::sec_websocket_version, "13"); + + fail_loop([&](stream& ws) + { + api.accept(ws, req); + }); + } + + // request in message, decorator + { + request_type req; + req.method(http::verb::get); + req.target("/"); + req.version(11); + req.insert(http::field::host, "localhost"); + req.insert(http::field::upgrade, "websocket"); + req.insert(http::field::connection, "upgrade"); + req.insert(http::field::sec_websocket_key, "dGhlIHNhbXBsZSBub25jZQ=="); + req.insert(http::field::sec_websocket_version, "13"); + + fail_loop([&](stream& ws) + { + bool called = false; + api.accept_ex(ws, req, + res_decorator{called}); + BEAST_EXPECT(called); + }); + } + + // request in message, close frame in stream + { + request_type req; + req.method(http::verb::get); + req.target("/"); + req.version(11); + req.insert(http::field::host, "localhost"); + req.insert(http::field::upgrade, "websocket"); + req.insert(http::field::connection, "upgrade"); + req.insert(http::field::sec_websocket_key, "dGhlIHNhbXBsZSBub25jZQ=="); + req.insert(http::field::sec_websocket_version, "13"); + + fail_loop([&](stream& ws) + { + ws.next_layer().append("\x88\x82\xff\xff\xff\xff\xfc\x17"); + api.accept(ws, req); + try + { + static_buffer<1> b; + api.read(ws, b); + fail("success", __FILE__, __LINE__); + } + catch(system_error const& e) + { + if(e.code() != websocket::error::closed) + throw; + } + }); + } + + // failed handshake (missing Sec-WebSocket-Key) + fail_loop([&](stream& ws) + { + ws.next_layer().append( + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n"); + ws.next_layer().read_size(20); + try + { + api.accept(ws); + BEAST_FAIL(); + } + catch(system_error const& e) + { + if( e.code() != websocket::error::no_sec_key && + e.code() != net::error::eof) + throw; + } + }); + } + + template + void + testOversized(Api const& api) + { + net::io_context ioc; + + auto const big = [] + { + std::string s; + s += "X1: " + std::string(2000, '*') + "\r\n"; + return s; + }(); + + // request in stream + { + stream ws{ioc, + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + + big + + "\r\n"}; + auto tr = connect(ws.next_layer()); + try + { + api.accept(ws); + BEAST_FAIL(); + } + catch(system_error const& se) + { + // VFALCO Its the http error category... + BEAST_EXPECTS( + se.code() == http::error::buffer_overflow, + se.code().message()); + } + } + + // request in stream, decorator + { + stream ws{ioc, + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + + big + + "\r\n"}; auto tr = connect(ws.next_layer()); try { bool called = false; - w.accept_ex(ws, net::buffer( + api.accept_ex(ws, res_decorator{called}); + BEAST_FAIL(); + } + catch(system_error const& se) + { + // VFALCO Its the http error category... + BEAST_EXPECTS( + se.code() == http::error::buffer_overflow, + se.code().message()); + } + } + + // request in buffers + { + stream ws{ioc}; + auto tr = connect(ws.next_layer()); + try + { + api.accept(ws, net::buffer( + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + + big + + "\r\n" + )); + BEAST_FAIL(); + } + catch(system_error const& se) + { + BEAST_EXPECTS( + se.code() == error::buffer_overflow, + se.code().message()); + } + } + + // request in buffers, decorator + { + stream ws{ioc}; + auto tr = connect(ws.next_layer()); + try + { + bool called = false; + api.accept_ex(ws, net::buffer( "GET / HTTP/1.1\r\n" "Host: localhost\r\n" "Upgrade: websocket\r\n" @@ -221,7 +394,7 @@ public: + big + "\r\n"), res_decorator{called}); - fail("", __FILE__, __LINE__); + BEAST_FAIL(); } catch(system_error const& se) { @@ -232,27 +405,8 @@ public: } // request in buffers and stream - doStreamLoop([&](test::stream& ts) { - stream ws{ts}; - auto tr = connect(ws.next_layer()); - ts.append( - "Connection: upgrade\r\n" - "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" - "Sec-WebSocket-Version: 13\r\n" - "\r\n"); - ts.read_size(16); - w.accept(ws, sbuf( - "GET / HTTP/1.1\r\n" - "Host: localhost\r\n" - "Upgrade: websocket\r\n" - )); - // VFALCO validate contents of ws.next_layer().str? - }); - - // request in buffers and stream, oversized - { - stream ws{ioc_, + stream ws{ioc, "Connection: upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 13\r\n" @@ -261,12 +415,12 @@ public: auto tr = connect(ws.next_layer()); try { - w.accept(ws, sbuf( + api.accept(ws, websocket_test_suite::sbuf( "GET / HTTP/1.1\r\n" "Host: localhost\r\n" "Upgrade: websocket\r\n" )); - fail("", __FILE__, __LINE__); + BEAST_FAIL(); } catch(system_error const& se) { @@ -277,28 +431,8 @@ public: } // request in buffers and stream, decorator - doStreamLoop([&](test::stream& ts) { - stream ws{ts}; - auto tr = connect(ws.next_layer()); - ts.append( - "Connection: upgrade\r\n" - "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" - "Sec-WebSocket-Version: 13\r\n" - "\r\n"); - ts.read_size(16); - bool called = false; - w.accept_ex(ws, sbuf( - "GET / HTTP/1.1\r\n" - "Host: localhost\r\n" - "Upgrade: websocket\r\n"), - res_decorator{called}); - BEAST_EXPECT(called); - }); - - // request in buffers and stream, decorator, oversize - { - stream ws{ioc_, + stream ws{ioc, "Connection: upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 13\r\n" @@ -308,12 +442,12 @@ public: try { bool called = false; - w.accept_ex(ws, sbuf( + api.accept_ex(ws, websocket_test_suite::sbuf( "GET / HTTP/1.1\r\n" "Host: localhost\r\n" "Upgrade: websocket\r\n"), res_decorator{called}); - fail("", __FILE__, __LINE__); + BEAST_FAIL(); } catch(system_error const& se) { @@ -322,137 +456,15 @@ public: se.code().message()); } } - - // request in message - doStreamLoop([&](test::stream& ts) - { - stream ws{ts}; - auto tr = connect(ws.next_layer()); - request_type req; - req.method(http::verb::get); - req.target("/"); - req.version(11); - req.insert(http::field::host, "localhost"); - req.insert(http::field::upgrade, "websocket"); - req.insert(http::field::connection, "upgrade"); - req.insert(http::field::sec_websocket_key, "dGhlIHNhbXBsZSBub25jZQ=="); - req.insert(http::field::sec_websocket_version, "13"); - w.accept(ws, req); - }); - - // request in message, decorator - doStreamLoop([&](test::stream& ts) - { - stream ws{ts}; - auto tr = connect(ws.next_layer()); - request_type req; - req.method(http::verb::get); - req.target("/"); - req.version(11); - req.insert(http::field::host, "localhost"); - req.insert(http::field::upgrade, "websocket"); - req.insert(http::field::connection, "upgrade"); - req.insert(http::field::sec_websocket_key, "dGhlIHNhbXBsZSBub25jZQ=="); - req.insert(http::field::sec_websocket_version, "13"); - bool called = false; - w.accept_ex(ws, req, - res_decorator{called}); - BEAST_EXPECT(called); - }); - - // request in message, close frame in stream - doStreamLoop([&](test::stream& ts) - { - stream ws{ts}; - auto tr = connect(ws.next_layer()); - request_type req; - req.method(http::verb::get); - req.target("/"); - req.version(11); - req.insert(http::field::host, "localhost"); - req.insert(http::field::upgrade, "websocket"); - req.insert(http::field::connection, "upgrade"); - req.insert(http::field::sec_websocket_key, "dGhlIHNhbXBsZSBub25jZQ=="); - req.insert(http::field::sec_websocket_version, "13"); - ts.append("\x88\x82\xff\xff\xff\xff\xfc\x17"); - w.accept(ws, req); - try - { - static_buffer<1> b; - w.read(ws, b); - fail("success", __FILE__, __LINE__); - } - catch(system_error const& e) - { - if(e.code() != websocket::error::closed) - throw; - } - }); - - // failed handshake (missing Sec-WebSocket-Key) - doStreamLoop([&](test::stream& ts) - { - stream ws{ts}; - auto tr = connect(ws.next_layer()); - ts.append( - "GET / HTTP/1.1\r\n" - "Host: localhost\r\n" - "Upgrade: websocket\r\n" - "Connection: upgrade\r\n" - "Sec-WebSocket-Version: 13\r\n" - "\r\n"); - ts.read_size(20); - try - { - w.accept(ws); - fail("success", __FILE__, __LINE__); - } - catch(system_error const& e) - { - if( e.code() != - websocket::error::no_sec_key && - e.code() != - net::error::eof) - throw; - } - }); - - // Closed by client - { - stream ws{ioc_}; - auto tr = connect(ws.next_layer()); - tr.close(); - try - { - w.accept(ws); - fail("success", __FILE__, __LINE__); - } - catch(system_error const& e) - { - if(! BEAST_EXPECTS( - e.code() == error::closed, - e.code().message())) - throw; - } - } } void - testAccept() + testInvalidInputs() { - doTestAccept(SyncClient{}); - - yield_to([&](yield_context yield) - { - doTestAccept(AsyncClient{yield}); - }); - - // - // Bad requests - // + net::io_context ioc; auto const check = - [&](error_code const& ev, std::string const& s) + [&](error_code ev, string_view s) { for(int i = 0; i < 3; ++i) { @@ -470,14 +482,14 @@ public: n = s.size() - 1; break; } - stream ws{ioc_}; + stream ws(ioc); auto tr = connect(ws.next_layer()); ws.next_layer().append( s.substr(n, s.size() - n)); + tr.close(); try { - ws.accept( - net::buffer(s.data(), n)); + ws.accept(net::buffer(s.data(), n)); BEAST_EXPECTS(! ev, ev.message()); } catch(system_error const& se) @@ -497,6 +509,7 @@ public: "Sec-WebSocket-Version: 13\r\n" "\r\n" ); + // bad method check(error::bad_method, "POST / HTTP/1.1\r\n" @@ -507,6 +520,7 @@ public: "Sec-WebSocket-Version: 13\r\n" "\r\n" ); + // no Host check(error::no_host, "GET / HTTP/1.1\r\n" @@ -516,6 +530,7 @@ public: "Sec-WebSocket-Version: 13\r\n" "\r\n" ); + // no Connection check(error::no_connection, "GET / HTTP/1.1\r\n" @@ -525,6 +540,7 @@ public: "Sec-WebSocket-Version: 13\r\n" "\r\n" ); + // no Connection upgrade check(error::no_connection_upgrade, "GET / HTTP/1.1\r\n" @@ -535,6 +551,7 @@ public: "Sec-WebSocket-Version: 13\r\n" "\r\n" ); + // no Upgrade check(error::no_upgrade, "GET / HTTP/1.1\r\n" @@ -544,6 +561,7 @@ public: "Sec-WebSocket-Version: 13\r\n" "\r\n" ); + // no Upgrade websocket check(error::no_upgrade_websocket, "GET / HTTP/1.1\r\n" @@ -554,6 +572,7 @@ public: "Sec-WebSocket-Version: 13\r\n" "\r\n" ); + // no Sec-WebSocket-Key check(error::no_sec_key, "GET / HTTP/1.1\r\n" @@ -563,6 +582,7 @@ public: "Sec-WebSocket-Version: 13\r\n" "\r\n" ); + // bad Sec-WebSocket-Key check(error::bad_sec_key, "GET / HTTP/1.1\r\n" @@ -573,6 +593,7 @@ public: "Sec-WebSocket-Version: 13\r\n" "\r\n" ); + // no Sec-WebSocket-Version check(error::no_sec_version, "GET / HTTP/1.1\r\n" @@ -582,6 +603,7 @@ public: "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "\r\n" ); + // bad Sec-WebSocket-Version check(error::bad_sec_version, "GET / HTTP/1.1\r\n" @@ -592,6 +614,7 @@ public: "Sec-WebSocket-Version: 1\r\n" "\r\n" ); + // bad Sec-WebSocket-Version check(error::bad_sec_version, "GET / HTTP/1.1\r\n" @@ -602,6 +625,7 @@ public: "Sec-WebSocket-Version: 12\r\n" "\r\n" ); + // valid request check({}, "GET / HTTP/1.1\r\n" @@ -615,13 +639,53 @@ public: } void - testTimeout() + testEndOfStream() + { + net::io_context ioc; + { + stream ws(ioc); + auto tr = connect(ws.next_layer()); + tr.close(); + try + { + test_sync_api api; + api.accept(ws, net::const_buffer{}); + BEAST_FAIL(); + } + catch(system_error const& se) + { + BEAST_EXPECTS( + se.code() == error::closed, + se.code().message()); + } + } + { + stream ws(ioc); + auto tr = connect(ws.next_layer()); + tr.close(); + try + { + test_async_api api; + api.accept(ws, net::const_buffer{}); + BEAST_FAIL(); + } + catch(system_error const& se) + { + BEAST_EXPECTS( + se.code() == error::closed, + se.code().message()); + } + } + } + + void + testAsync() { using tcp = net::ip::tcp; net::io_context ioc; - // success + // success, no timeout { stream ws1(ioc); @@ -700,47 +764,49 @@ public: ws1.async_accept(test::fail_handler(beast::error::timeout)); test::run_for(ioc, std::chrono::seconds(1)); } - } - void - testMoveOnly() - { - net::io_context ioc; - stream ws{ioc}; - ws.async_accept(move_only_handler{}); - } + // abandoned operation - struct copyable_handler - { - template - void - operator()(Args&&...) const { + { + stream ws1(ioc); + ws1.async_accept(test::fail_handler( + net::error::operation_aborted)); + } + test::run(ioc); } - }; - void - testAsioHandlerInvoke() - { - // make sure things compile, also can set a - // breakpoint in asio_handler_invoke to make sure - // it is instantiated. - net::io_context ioc; - net::strand< - net::io_context::executor_type> s( - ioc.get_executor()); - stream ws{ioc}; - ws.async_accept(net::bind_executor( - s, copyable_handler{})); + { + { + stream ws1(ioc); + string_view s = + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n"; + error_code ec; + http::request_parser p; + p.put(net::const_buffer(s.data(), s.size()), ec); + ws1.async_accept(p.get(), test::fail_handler( + net::error::operation_aborted)); + } + test::run(ioc); + } } void run() override { - testAccept(); - testTimeout(); - testMoveOnly(); - testAsioHandlerInvoke(); + testMatrix(test_sync_api{}); + testMatrix(test_async_api{}); + testOversized(test_sync_api{}); + testOversized(test_async_api{}); + testInvalidInputs(); + testEndOfStream(); + testAsync(); } }; diff --git a/test/beast/websocket/test.hpp b/test/beast/websocket/test.hpp index 7ae7aaba..1a1f4413 100644 --- a/test/beast/websocket/test.hpp +++ b/test/beast/websocket/test.hpp @@ -1013,6 +1013,612 @@ public: }; }; +struct test_sync_api +{ + template + void + accept( + stream& ws) const + { + ws.accept(); + } + + template< + class NextLayer, bool deflateSupported, + class Buffers> + typename std::enable_if< + ! http::detail::is_header::value>::type + accept(stream& ws, + Buffers const& buffers) const + { + ws.accept(buffers); + } + + template + void + accept( + stream& ws, + http::request const& req) const + { + ws.accept(req); + } + + template< + class NextLayer, bool deflateSupported, + class Decorator> + void + accept_ex( + stream& ws, + Decorator const& d) const + { + ws.accept_ex(d); + } + + template< + class NextLayer, bool deflateSupported, + class Buffers, class Decorator> + typename std::enable_if< + ! http::detail::is_header::value>::type + accept_ex( + stream& ws, + Buffers const& buffers, + Decorator const& d) const + { + ws.accept_ex(buffers, d); + } + + template< + class NextLayer, bool deflateSupported, + class Decorator> + void + accept_ex( + stream& ws, + http::request const& req, + Decorator const& d) const + { + ws.accept_ex(req, d); + } + + template< + class NextLayer, bool deflateSupported, + class Buffers, class Decorator> + void + accept_ex( + stream& ws, + http::request const& req, + Buffers const& buffers, + Decorator const& d) const + { + ws.accept_ex(req, buffers, d); + } + + template + void + handshake( + stream& ws, + response_type& res, + string_view uri, + string_view path) const + { + ws.handshake(res, uri, path); + } + + template< + class NextLayer, bool deflateSupported, + class Decorator> + void + handshake_ex( + stream& ws, + string_view uri, + string_view path, + Decorator const& d) const + { + ws.handshake_ex(uri, path, d); + } + + template< + class NextLayer, bool deflateSupported, + class Decorator> + void + handshake_ex( + stream& ws, + response_type& res, + string_view uri, + string_view path, + Decorator const& d) const + { + ws.handshake_ex(res, uri, path, d); + } + + template + void + ping(stream& ws, + ping_data const& payload) const + { + ws.ping(payload); + } + + template + void + pong(stream& ws, + ping_data const& payload) const + { + ws.pong(payload); + } + + template + void + close(stream& ws, + close_reason const& cr) const + { + ws.close(cr); + } + + template< + class NextLayer, bool deflateSupported, + class DynamicBuffer> + std::size_t + read(stream& ws, + DynamicBuffer& buffer) const + { + return ws.read(buffer); + } + + template< + class NextLayer, bool deflateSupported, + class DynamicBuffer> + std::size_t + read_some( + stream& ws, + std::size_t limit, + DynamicBuffer& buffer) const + { + return ws.read_some(buffer, limit); + } + + template< + class NextLayer, bool deflateSupported, + class MutableBufferSequence> + std::size_t + read_some( + stream& ws, + MutableBufferSequence const& buffers) const + { + return ws.read_some(buffers); + } + + template< + class NextLayer, bool deflateSupported, + class ConstBufferSequence> + std::size_t + write( + stream& ws, + ConstBufferSequence const& buffers) const + { + return ws.write(buffers); + } + + template< + class NextLayer, bool deflateSupported, + class ConstBufferSequence> + std::size_t + write_some( + stream& ws, + bool fin, + ConstBufferSequence const& buffers) const + { + return ws.write_some(fin, buffers); + } + + template< + class NextLayer, bool deflateSupported, + class ConstBufferSequence> + std::size_t + write_raw( + stream& ws, + ConstBufferSequence const& buffers) const + { + return net::write( + ws.next_layer(), buffers); + } +}; + +//-------------------------------------------------------------------------- + +class test_async_api +{ + struct handler + { + error_code& ec_; + std::size_t* n_ = 0; + bool pass_ = false; + + explicit + handler(error_code& ec) + : ec_(ec) + { + } + + explicit + handler(error_code& ec, std::size_t& n) + : ec_(ec) + , n_(&n) + { + *n_ = 0; + } + + handler(handler&& other) + : ec_(other.ec_) + , pass_(boost::exchange(other.pass_, true)) + { + } + + ~handler() + { + BEAST_EXPECT(pass_); + } + + void + operator()(error_code ec) + { + BEAST_EXPECT(! pass_); + pass_ = true; + ec_ = ec; + } + + void + operator()(error_code ec, std::size_t n) + { + BEAST_EXPECT(! pass_); + pass_ = true; + ec_ = ec; + if(n_) + *n_ = n; + } + }; + +public: + template + void + accept( + stream& ws) const + { + error_code ec; + ws.async_accept(handler(ec)); + ws.get_executor().context().run(); + ws.get_executor().context().restart(); + if(ec) + throw system_error{ec}; + } + + template< + class NextLayer, bool deflateSupported, + class Buffers> + typename std::enable_if< + ! http::detail::is_header::value>::type + accept( + stream& ws, + Buffers const& buffers) const + { + error_code ec; + ws.async_accept(buffers, handler(ec)); + ws.get_executor().context().run(); + ws.get_executor().context().restart(); + if(ec) + throw system_error{ec}; + } + + template + void + accept( + stream& ws, + http::request const& req) const + { + error_code ec; + ws.async_accept(req, handler(ec)); + ws.get_executor().context().run(); + ws.get_executor().context().restart(); + if(ec) + throw system_error{ec}; + } + + template< + class NextLayer, bool deflateSupported, + class Decorator> + void + accept_ex( + stream& ws, + Decorator const& d) const + { + error_code ec; + ws.async_accept_ex(d, handler(ec)); + ws.get_executor().context().run(); + ws.get_executor().context().restart(); + if(ec) + throw system_error{ec}; + } + + template< + class NextLayer, bool deflateSupported, + class Buffers, class Decorator> + typename std::enable_if< + ! http::detail::is_header::value>::type + accept_ex( + stream& ws, + Buffers const& buffers, + Decorator const& d) const + { + error_code ec; + ws.async_accept_ex(buffers, d, handler(ec)); + ws.get_executor().context().run(); + ws.get_executor().context().restart(); + if(ec) + throw system_error{ec}; + } + + template< + class NextLayer, bool deflateSupported, + class Decorator> + void + accept_ex( + stream& ws, + http::request const& req, + Decorator const& d) const + { + error_code ec; + ws.async_accept_ex(req, d, handler(ec)); + ws.get_executor().context().run(); + ws.get_executor().context().restart(); + if(ec) + throw system_error{ec}; + } + + template< + class NextLayer, bool deflateSupported, + class Buffers, class Decorator> + void + accept_ex( + stream& ws, + http::request const& req, + Buffers const& buffers, + Decorator const& d) const + { + error_code ec; + ws.async_accept_ex( + req, buffers, d, handler(ec)); + ws.get_executor().context().run(); + ws.get_executor().context().restart(); + if(ec) + throw system_error{ec}; + } + + template< + class NextLayer, bool deflateSupported> + void + handshake( + stream& ws, + string_view uri, + string_view path) const + { + error_code ec; + ws.async_handshake( + uri, path, handler(ec)); + ws.get_executor().context().run(); + ws.get_executor().context().restart(); + if(ec) + throw system_error{ec}; + } + + template + void + handshake( + stream& ws, + response_type& res, + string_view uri, + string_view path) const + { + error_code ec; + ws.async_handshake( + res, uri, path, handler(ec)); + ws.get_executor().context().run(); + ws.get_executor().context().restart(); + if(ec) + throw system_error{ec}; + } + + template< + class NextLayer, bool deflateSupported, + class Decorator> + void + handshake_ex( + stream& ws, + string_view uri, + string_view path, + Decorator const &d) const + { + error_code ec; + ws.async_handshake_ex( + uri, path, d, handler(ec)); + ws.get_executor().context().run(); + ws.get_executor().context().restart(); + if(ec) + throw system_error{ec}; + } + + template< + class NextLayer, bool deflateSupported, + class Decorator> + void + handshake_ex( + stream& ws, + response_type& res, + string_view uri, + string_view path, + Decorator const &d) const + { + error_code ec; + ws.async_handshake_ex( + res, uri, path, d, handler(ec)); + ws.get_executor().context().run(); + ws.get_executor().context().restart(); + if(ec) + throw system_error{ec}; + } + + template + void + ping( + stream& ws, + ping_data const& payload) const + { + error_code ec; + ws.async_ping(payload, handler(ec)); + ws.get_executor().context().run(); + ws.get_executor().context().restart(); + if(ec) + throw system_error{ec}; + } + + template + void + pong( + stream& ws, + ping_data const& payload) const + { + error_code ec; + ws.async_pong(payload, handler(ec)); + ws.get_executor().context().run(); + ws.get_executor().context().restart(); + if(ec) + throw system_error{ec}; + } + + template + void + close( + stream& ws, + close_reason const& cr) const + { + error_code ec; + ws.async_close(cr, handler(ec)); + ws.get_executor().context().run(); + ws.get_executor().context().restart(); + if(ec) + throw system_error{ec}; + } + + template< + class NextLayer, bool deflateSupported, + class DynamicBuffer> + std::size_t + read( + stream& ws, + DynamicBuffer& buffer) const + { + error_code ec; + std::size_t n; + ws.async_read(buffer, handler(ec, n)); + ws.get_executor().context().run(); + ws.get_executor().context().restart(); + if(ec) + throw system_error{ec}; + return n; + } + + template< + class NextLayer, bool deflateSupported, + class DynamicBuffer> + std::size_t + read_some( + stream& ws, + std::size_t limit, + DynamicBuffer& buffer) const + { + error_code ec; + std::size_t n; + ws.async_read_some(buffer, limit, handler(ec, n)); + ws.get_executor().context().run(); + ws.get_executor().context().restart(); + if(ec) + throw system_error{ec}; + return n; + } + + template< + class NextLayer, bool deflateSupported, + class MutableBufferSequence> + std::size_t + read_some( + stream& ws, + MutableBufferSequence const& buffers) const + { + error_code ec; + std::size_t n; + ws.async_read_some(buffers, handler(ec, n)); + ws.get_executor().context().run(); + ws.get_executor().context().restart(); + if(ec) + throw system_error{ec}; + return n; + } + + template< + class NextLayer, bool deflateSupported, + class ConstBufferSequence> + std::size_t + write( + stream& ws, + ConstBufferSequence const& buffers) const + { + error_code ec; + std::size_t n; + ws.async_write(buffers, handler(ec, n)); + ws.get_executor().context().run(); + ws.get_executor().context().restart(); + if(ec) + throw system_error{ec}; + return n; + } + + template< + class NextLayer, bool deflateSupported, + class ConstBufferSequence> + std::size_t + write_some( + stream& ws, + bool fin, + ConstBufferSequence const& buffers) const + { + error_code ec; + std::size_t n; + ws.async_write_some(fin, buffers, handler(ec, n)); + ws.get_executor().context().run(); + ws.get_executor().context().restart(); + if(ec) + throw system_error{ec}; + return n; + } + + template< + class NextLayer, bool deflateSupported, + class ConstBufferSequence> + std::size_t + write_raw( + stream& ws, + ConstBufferSequence const& buffers) const + { + error_code ec; + std::size_t n; + net::async_write(ws.next_layer(), + buffers, handler(ec, n)); + ws.get_executor().context().run(); + ws.get_executor().context().restart(); + if(ec) + throw system_error{ec}; + return n; + } +}; + } // websocket } // beast } // boost