// // Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // // Official repository: https://github.com/boostorg/beast // // Test that header file is self-contained. #include #include "websocket_sync_echo_server.hpp" #include #include #include #include #include #include #include #include #include namespace boost { namespace beast { namespace websocket { class stream_test : public beast::unit_test::suite , public test::enable_yield_to { public: using self = stream_test; using endpoint_type = boost::asio::ip::tcp::endpoint; using address_type = boost::asio::ip::address; using socket_type = boost::asio::ip::tcp::socket; //-------------------------------------------------------------------------- class asyncEchoServer : public std::enable_shared_from_this { std::ostream& log_; websocket::stream ws_; boost::asio::io_service::strand strand_; static_buffer<2001> buffer_; public: asyncEchoServer( std::ostream& log, test::stream stream) : log_(log) , ws_(std::move(stream)) , strand_(ws_.get_io_service()) { permessage_deflate pmd; pmd.client_enable = true; pmd.server_enable = true; ws_.set_option(pmd); } void run() { ws_.async_accept(strand_.wrap(std::bind( &asyncEchoServer::on_accept, shared_from_this(), std::placeholders::_1))); } void on_accept(error_code ec) { if(ec) return fail(ec); do_read(); } void do_read() { ws_.async_read(buffer_, strand_.wrap(std::bind( &asyncEchoServer::on_read, shared_from_this(), std::placeholders::_1))); } void on_read(error_code ec) { if(ec) return fail(ec); ws_.text(ws_.got_text()); ws_.async_write(buffer_.data(), strand_.wrap(std::bind( &asyncEchoServer::on_write, this->shared_from_this(), std::placeholders::_1))); } void on_write(error_code ec) { if(ec) return fail(ec); buffer_.consume(buffer_.size()); do_read(); } void fail(error_code ec) { if( ec != error::closed && ec != error::failed && ec != boost::asio::error::eof) log_ << "asyncEchoServer: " << ec.message() << std::endl; } }; void echoServer(test::stream& stream) { try { websocket::stream ws{stream}; permessage_deflate pmd; pmd.client_enable = true; pmd.server_enable = true; ws.set_option(pmd); ws.accept(); for(;;) { static_buffer<2001> buffer; ws.read(buffer); ws.text(ws.got_text()); ws.write(buffer.data()); } } catch(system_error const& se) { if( se.code() != error::closed && se.code() != error::failed && se.code() != boost::asio::error::eof) log << "echoServer: " << se.code().message() << std::endl; } catch(std::exception const& e) { log << "echoServer: " << e.what() << std::endl; } } void launchEchoServer(test::stream stream) { std::thread{std::bind( &stream_test::echoServer, this, std::move(stream))}.detach(); } void launchEchoServerAsync(test::stream stream) { std::make_shared( log, std::move(stream))->run(); } //-------------------------------------------------------------------------- using ws_stream_type = websocket::stream; template void doTestLoop(Test const& f) { // This number has to be high for the // test that writes the large buffer. static std::size_t constexpr limit = 1000; std::size_t n; for(n = 0; n <= limit; ++n) { test::fail_counter fc{n}; test::stream ts{ios_, fc}; try { f(ts); // Made it through ts.close(); break; } catch(system_error const& se) { BEAST_EXPECTS( se.code() == test::error::fail_error, se.code().message()); ts.close(); } catch(std::exception const& e) { fail(e.what(), __FILE__, __LINE__); ts.close(); } continue; } BEAST_EXPECT(n < limit); } template void doTest( Wrap const& w, permessage_deflate const& pmd, Launch const& launch, Test const& f) { doTestLoop( [&](test::stream& ts) { ws_stream_type ws{ts}; ws.set_option(pmd); launch(ts.remote()); w.handshake(ws, "localhost", "/"); f(ws); }); #if 0 // Lowering the read_size takes a disproportionate // amount of time for the gains in coverage . doTestLoop( [&](test::stream& ts) { ws_stream_type ws{ts}; ts.read_size(45); ws.set_option(pmd); launch(ts.remote()); w.handshake(ws, "localhost", "/"); f(ws); }); #endif } template void doCloseTest( Wrap const& w, ws_stream_type& ws, close_code code) { try { multi_buffer b; w.read(ws, b); fail("", __FILE__, __LINE__); } catch(system_error const& se) { if(se.code() != error::closed) throw; BEAST_EXPECT( ws.reason().code == code); } } template void doFailTest( Wrap const& w, ws_stream_type& 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 static std::string to_string(ConstBufferSequence const& bs) { using boost::asio::buffer_cast; using boost::asio::buffer_size; std::string s; s.reserve(buffer_size(bs)); for(boost::asio::const_buffer b : bs) s.append(buffer_cast(b), buffer_size(b)); return s; } template class cbuf_helper { std::array v_; boost::asio::const_buffer cb_; public: using value_type = decltype(cb_); using const_iterator = value_type const*; template explicit cbuf_helper(Vn... vn) : v_({{ static_cast(vn)... }}) , cb_(v_.data(), v_.size()) { } const_iterator begin() const { return &cb_; } const_iterator end() const { return begin()+1; } }; template cbuf_helper cbuf(Vn... vn) { return cbuf_helper(vn...); } template static boost::asio::const_buffers_1 sbuf(const char (&s)[N]) { return boost::asio::const_buffers_1(&s[0], N-1); } template< class DynamicBuffer, class ConstBufferSequence> static void put( DynamicBuffer& buffer, ConstBufferSequence const& buffers) { using boost::asio::buffer_copy; using boost::asio::buffer_size; buffer.commit(buffer_copy( buffer.prepare(buffer_size(buffers)), buffers)); } template static bool run_until(boost::asio::io_service& ios, std::size_t limit, Pred&& pred) { for(std::size_t i = 0; i < limit; ++i) { if(pred()) return true; ios.run_one(); } return false; } struct SyncClient { template void accept(stream& ws) const { ws.accept(); } template 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 void accept_ex(stream& ws, Decorator const& d) const { ws.accept_ex(d); } template typename std::enable_if< ! http::detail::is_header::value>::type accept_ex(stream& ws, Buffers const& buffers, Decorator const& d) const { ws.accept_ex(buffers, d); } template void accept_ex(stream& ws, http::request const& req, Decorator const& d) const { ws.accept_ex(req, d); } template 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, string_view uri, string_view path) const { ws.handshake(uri, path); } template void handshake(stream& ws, response_type& res, string_view uri, string_view path) const { ws.handshake(res, uri, path); } template void handshake_ex(stream& ws, string_view uri, string_view path, Decorator const& d) const { ws.handshake_ex(uri, path, d); } template 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, class DynamicBuffer> std::size_t read(stream& ws, DynamicBuffer& buffer) const { return ws.read(buffer); } template< class NextLayer, class MutableBufferSequence> std::size_t read_some(stream& ws, MutableBufferSequence const& buffers) const { return ws.read_some(buffers); } template< class NextLayer, class ConstBufferSequence> void write(stream& ws, ConstBufferSequence const& buffers) const { ws.write(buffers); } template< class NextLayer, class ConstBufferSequence> void write_some(stream& ws, bool fin, ConstBufferSequence const& buffers) const { ws.write_some(fin, buffers); } template< class NextLayer, class ConstBufferSequence> void write_raw(stream& ws, ConstBufferSequence const& buffers) const { boost::asio::write( ws.next_layer(), buffers); } }; class AsyncClient { yield_context& yield_; public: explicit AsyncClient(yield_context& yield) : yield_(yield) { } template void accept(stream& ws) const { error_code ec; ws.async_accept(yield_[ec]); if(ec) throw system_error{ec}; } template typename std::enable_if< ! http::detail::is_header::value>::type accept(stream& ws, Buffers const& buffers) const { error_code ec; ws.async_accept(buffers, yield_[ec]); if(ec) throw system_error{ec}; } template void accept(stream& ws, http::request const& req) const { error_code ec; ws.async_accept(req, yield_[ec]); if(ec) throw system_error{ec}; } template void accept_ex(stream& ws, Decorator const& d) const { error_code ec; ws.async_accept_ex(d, yield_[ec]); if(ec) throw system_error{ec}; } template 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, yield_[ec]); if(ec) throw system_error{ec}; } template void accept_ex(stream& ws, http::request const& req, Decorator const& d) const { error_code ec; ws.async_accept_ex(req, d, yield_[ec]); if(ec) throw system_error{ec}; } template 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, yield_[ec]); if(ec) throw system_error{ec}; } template void handshake(stream& ws, string_view uri, string_view path) const { error_code ec; ws.async_handshake( uri, path, yield_[ec]); 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, yield_[ec]); if(ec) throw system_error{ec}; } template 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, yield_[ec]); if(ec) throw system_error{ec}; } template 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, yield_[ec]); if(ec) throw system_error{ec}; } template void ping(stream& ws, ping_data const& payload) const { error_code ec; ws.async_ping(payload, yield_[ec]); if(ec) throw system_error{ec}; } template void pong(stream& ws, ping_data const& payload) const { error_code ec; ws.async_pong(payload, yield_[ec]); if(ec) throw system_error{ec}; } template void close(stream& ws, close_reason const& cr) const { error_code ec; ws.async_close(cr, yield_[ec]); if(ec) throw system_error{ec}; } template< class NextLayer, class DynamicBuffer> std::size_t read(stream& ws, DynamicBuffer& buffer) const { error_code ec; auto const bytes_written = ws.async_read(buffer, yield_[ec]); if(ec) throw system_error{ec}; return bytes_written; } template< class NextLayer, class MutableBufferSequence> std::size_t read_some(stream& ws, MutableBufferSequence const& buffers) const { error_code ec; auto const bytes_written = ws.async_read_some(buffers, yield_[ec]); if(ec) throw system_error{ec}; return bytes_written; } template< class NextLayer, class ConstBufferSequence> void write(stream& ws, ConstBufferSequence const& buffers) const { error_code ec; ws.async_write(buffers, yield_[ec]); if(ec) throw system_error{ec}; } template< class NextLayer, class ConstBufferSequence> void write_some(stream& ws, bool fin, ConstBufferSequence const& buffers) const { error_code ec; ws.async_write_some(fin, buffers, yield_[ec]); if(ec) throw system_error{ec}; } template< class NextLayer, class ConstBufferSequence> void write_raw(stream& ws, ConstBufferSequence const& buffers) const { error_code ec; boost::asio::async_write( ws.next_layer(), buffers, yield_[ec]); if(ec) throw system_error{ec}; } }; void testOptions() { stream ws(ios_); ws.auto_fragment(true); ws.write_buffer_size(2048); ws.binary(false); ws.read_message_max(1 * 1024 * 1024); try { ws.write_buffer_size(7); fail(); } catch(std::exception const&) { pass(); } } //-------------------------------------------------------------------------- template void doTestAccept(Client const& c) { class res_decorator { bool& b_; public: res_decorator(res_decorator const&) = default; explicit res_decorator(bool& b) : b_(b) { } void operator()(response_type&) const { b_ = true; } }; auto const big = [] { std::string s; s += "X1: " + std::string(2000, '*') + "\r\n"; return s; }(); // request in stream doTestLoop([&](test::stream& ts) { stream ws{ts}; ts.str( "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); c.accept(ws); // VFALCO validate contents of ws.next_layer().str? }); // request in stream, oversized { stream ws{ios_, "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"}; error_code ec = test::error::fail_error; try { c.accept(ws); 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 stream, decorator doTestLoop([&](test::stream& ts) { stream ws{ts}; ts.str( "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); bool called = false; c.accept_ex(ws, res_decorator{called}); BEAST_EXPECT(called); }); // request in stream, decorator, oversized { stream ws{ios_, "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"}; try { bool called = false; c.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 doTestLoop([&](test::stream& ts) { stream ws{ts}; c.accept(ws, sbuf( "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" )); }); // request in buffers, oversize { stream ws{ios_}; try { c.accept(ws, boost::asio::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 doTestLoop([&](test::stream& ts) { stream ws{ts}; bool called = false; c.accept_ex(ws, sbuf( "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"), res_decorator{called}); BEAST_EXPECT(called); }); // request in buffers, decorator, oversized { stream ws{ios_}; try { bool called = false; c.accept_ex(ws, boost::asio::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"), res_decorator{called}); fail("", __FILE__, __LINE__); } catch(system_error const& se) { BEAST_EXPECTS( se.code() == error::buffer_overflow, se.code().message()); } } // request in buffers and stream doTestLoop([&](test::stream& ts) { stream ws{ts}; ts.str( "Connection: upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 13\r\n" "\r\n"); ts.read_size(16); c.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{ios_, "Connection: upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 13\r\n" + big + "\r\n"}; try { c.accept(ws, sbuf( "GET / HTTP/1.1\r\n" "Host: localhost\r\n" "Upgrade: websocket\r\n" )); fail("", __FILE__, __LINE__); } catch(system_error const& se) { BEAST_EXPECTS( se.code() == http::error::buffer_overflow, se.code().message()); } } // request in buffers and stream, decorator doTestLoop([&](test::stream& ts) { stream ws{ts}; ts.str( "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; c.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{ios_, "Connection: upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 13\r\n" + big + "\r\n"}; try { bool called = false; c.accept_ex(ws, sbuf( "GET / HTTP/1.1\r\n" "Host: localhost\r\n" "Upgrade: websocket\r\n"), res_decorator{called}); fail("", __FILE__, __LINE__); } catch(system_error const& se) { BEAST_EXPECTS( se.code() == http::error::buffer_overflow, se.code().message()); } } // request in message doTestLoop([&](test::stream& ts) { stream ws{ts}; 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"); c.accept(ws, req); }); // request in message, decorator doTestLoop([&](test::stream& ts) { stream ws{ts}; 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; c.accept_ex(ws, req, res_decorator{called}); BEAST_EXPECT(called); }); // request in message, close frame in stream doTestLoop([&](test::stream& ts) { stream ws{ts}; 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.str("\x88\x82\xff\xff\xff\xff\xfc\x17"); c.accept(ws, req); try { static_buffer<1> b; c.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) doTestLoop([&](test::stream& ts) { stream ws{ts}; ts.str( "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 { c.accept(ws); fail("success", __FILE__, __LINE__); } catch(system_error const& e) { if( e.code() != websocket::error::handshake_failed && e.code() != boost::asio::error::eof) throw; } }); // Closed by client { stream ws{ios_}; ws.next_layer().remote().close(); try { c.accept(ws); fail("success", __FILE__, __LINE__); } catch(system_error const& e) { if(! BEAST_EXPECTS( e.code() == error::closed, e.code().message())) throw; } } } void testAccept() { doTestAccept(SyncClient{}); yield_to([&](yield_context yield) { doTestAccept(AsyncClient{yield}); }); } //-------------------------------------------------------------------------- template void doTestHandshake(Client const& c, Launch const& launch) { class req_decorator { bool& b_; public: req_decorator(req_decorator const&) = default; explicit req_decorator(bool& b) : b_(b) { } void operator()(request_type&) const { b_ = true; } }; // handshake doTestLoop([&](test::stream& ts) { ws_stream_type ws{ts}; launch(ts.remote()); c.handshake(ws, "localhost", "/"); }); // handshake, response doTestLoop([&](test::stream& ts) { ws_stream_type ws{ts}; launch(ts.remote()); response_type res; c.handshake(ws, res, "localhost", "/"); // VFALCO validate res? }); // handshake, decorator doTestLoop([&](test::stream& ts) { ws_stream_type ws{ts}; launch(ts.remote()); bool called = false; c.handshake_ex(ws, "localhost", "/", req_decorator{called}); BEAST_EXPECT(called); }); // handshake, response, decorator doTestLoop([&](test::stream& ts) { ws_stream_type ws{ts}; launch(ts.remote()); bool called = false; response_type res; c.handshake_ex(ws, res, "localhost", "/", req_decorator{called}); // VFALCO validate res? BEAST_EXPECT(called); }); } void testHandshake() { doTestHandshake(SyncClient{}, [&](test::stream stream) { launchEchoServer(std::move(stream)); }); yield_to([&](yield_context yield) { doTestHandshake(AsyncClient{yield}, [&](test::stream stream) { launchEchoServerAsync(std::move(stream)); }); }); } //-------------------------------------------------------------------------- void testBadHandshakes() { auto const check = [&](error_code const& ev, std::string const& s) { for(int i = 0; i < 3; ++i) { std::size_t n; switch(i) { default: case 0: n = 1; break; case 1: n = s.size() / 2; break; case 2: n = s.size() - 1; break; } stream ws{ios_}; ws.next_layer().str( s.substr(n, s.size() - n)); try { ws.accept( boost::asio::buffer(s.data(), n)); BEAST_EXPECTS(! ev, ev.message()); } catch(system_error const& se) { BEAST_EXPECTS(se.code() == ev, se.what()); } } }; // wrong version check(http::error::end_of_stream, "GET / HTTP/1.0\r\n" "Host: localhost:80\r\n" "Upgrade: WebSocket\r\n" "Connection: keep-alive,upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 13\r\n" "\r\n" ); // wrong method check(error::handshake_failed, "POST / HTTP/1.1\r\n" "Host: localhost:80\r\n" "Upgrade: WebSocket\r\n" "Connection: keep-alive,upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 13\r\n" "\r\n" ); // missing Host check(error::handshake_failed, "GET / HTTP/1.1\r\n" "Upgrade: WebSocket\r\n" "Connection: keep-alive,upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 13\r\n" "\r\n" ); // missing Sec-WebSocket-Key check(error::handshake_failed, "GET / HTTP/1.1\r\n" "Host: localhost:80\r\n" "Upgrade: WebSocket\r\n" "Connection: keep-alive,upgrade\r\n" "Sec-WebSocket-Version: 13\r\n" "\r\n" ); // missing Sec-WebSocket-Version check(error::handshake_failed, "GET / HTTP/1.1\r\n" "Host: localhost:80\r\n" "Upgrade: WebSocket\r\n" "Connection: keep-alive,upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "\r\n" ); // wrong Sec-WebSocket-Version check(error::handshake_failed, "GET / HTTP/1.1\r\n" "Host: localhost:80\r\n" "Upgrade: WebSocket\r\n" "Connection: keep-alive,upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 1\r\n" "\r\n" ); // missing upgrade token check(error::handshake_failed, "GET / HTTP/1.1\r\n" "Host: localhost:80\r\n" "Upgrade: HTTP/2\r\n" "Connection: upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 13\r\n" "\r\n" ); // missing connection token check(error::handshake_failed, "GET / HTTP/1.1\r\n" "Host: localhost:80\r\n" "Upgrade: WebSocket\r\n" "Connection: keep-alive\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 13\r\n" "\r\n" ); // valid request check({}, "GET / HTTP/1.1\r\n" "Host: localhost:80\r\n" "Upgrade: WebSocket\r\n" "Connection: upgrade\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Version: 13\r\n" "\r\n" ); } void testBadResponses() { auto const check = [&](std::string const& s) { stream ws{ios_}; ws.next_layer().str(s); ws.next_layer().remote().close(); try { ws.handshake("localhost:80", "/"); fail(); } catch(system_error const& se) { BEAST_EXPECT(se.code() == error::handshake_failed); } }; // wrong HTTP version check( "HTTP/1.0 101 Switching Protocols\r\n" "Server: beast\r\n" "Upgrade: WebSocket\r\n" "Connection: upgrade\r\n" "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" "Sec-WebSocket-Version: 13\r\n" "\r\n" ); // wrong status check( "HTTP/1.1 200 OK\r\n" "Server: beast\r\n" "Upgrade: WebSocket\r\n" "Connection: upgrade\r\n" "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" "Sec-WebSocket-Version: 13\r\n" "\r\n" ); // missing upgrade token check( "HTTP/1.1 101 Switching Protocols\r\n" "Server: beast\r\n" "Upgrade: HTTP/2\r\n" "Connection: upgrade\r\n" "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" "Sec-WebSocket-Version: 13\r\n" "\r\n" ); // missing connection token check( "HTTP/1.1 101 Switching Protocols\r\n" "Server: beast\r\n" "Upgrade: WebSocket\r\n" "Connection: keep-alive\r\n" "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" "Sec-WebSocket-Version: 13\r\n" "\r\n" ); // missing accept key check( "HTTP/1.1 101 Switching Protocols\r\n" "Server: beast\r\n" "Upgrade: WebSocket\r\n" "Connection: upgrade\r\n" "Sec-WebSocket-Version: 13\r\n" "\r\n" ); // wrong accept key check( "HTTP/1.1 101 Switching Protocols\r\n" "Server: beast\r\n" "Upgrade: WebSocket\r\n" "Connection: upgrade\r\n" "Sec-WebSocket-Accept: *\r\n" "Sec-WebSocket-Version: 13\r\n" "\r\n" ); } void testMask(endpoint_type const& ep, yield_context do_yield) { { std::vector v; for(char n = 0; n < 20; ++n) { error_code ec = test::error::fail_error; socket_type sock(ios_); sock.connect(ep, ec); if(! BEAST_EXPECTS(! ec, ec.message())) break; stream ws(sock); ws.handshake("localhost", "/", ec); if(! BEAST_EXPECTS(! ec, ec.message())) break; ws.write(boost::asio::buffer(v), ec); if(! BEAST_EXPECTS(! ec, ec.message())) break; multi_buffer b; ws.read(b, ec); if(! BEAST_EXPECTS(! ec, ec.message())) break; BEAST_EXPECT(to_string(b.data()) == std::string(v.data(), v.size())); v.push_back(n+1); } } { std::vector v; for(char n = 0; n < 20; ++n) { error_code ec = test::error::fail_error; socket_type sock(ios_); sock.connect(ep, ec); if(! BEAST_EXPECTS(! ec, ec.message())) break; stream ws(sock); ws.handshake("localhost", "/", ec); if(! BEAST_EXPECTS(! ec, ec.message())) break; ws.async_write(boost::asio::buffer(v), do_yield[ec]); if(! BEAST_EXPECTS(! ec, ec.message())) break; multi_buffer b; ws.async_read(b, do_yield[ec]); if(! BEAST_EXPECTS(! ec, ec.message())) break; BEAST_EXPECT(to_string(b.data()) == std::string(v.data(), v.size())); v.push_back(n+1); } } } void testClose() { auto const check = [&](error_code ev, string_view s) { test::stream ts{ios_}; stream ws{ts}; launchEchoServerAsync(ts.remote()); ws.handshake("localhost", "/"); ts.str(s); static_buffer<1> b; error_code ec; ws.read(b, ec); BEAST_EXPECTS(ec == ev, ec.message()); }; // payload length 1 check(error::failed, "\x88\x81\xff\xff\xff\xff\x00"); // invalid close code 1005 check(error::failed, "\x88\x82\xff\xff\xff\xff\xfc\x12"); // invalid utf8 check(error::failed, "\x88\x86\xff\xff\xff\xff\xfc\x15\x0f\xd7\x73\x43"); // VFALCO No idea why this fails // good utf8 #if 0 check({}, "\x88\x86\xff\xff\xff\xff\xfc\x15utf8"); #endif } void testPausation1() { for(int i = 0; i < 2; ++i) { boost::asio::io_service ios; stream ws{ios, ios_}; if(i == 0) launchEchoServer(ws.next_layer().remote()); else launchEchoServerAsync(ws.next_layer().remote()); ws.handshake("localhost", "/"); // Make remote send a text message with bad utf8. ws.binary(true); put(ws.next_layer().buffer(), cbuf( 0x03, 0xea, 0xf0, 0x28, 0x8c, 0xbc)); multi_buffer b; std::size_t count = 0; // Read text message with bad utf8. // Causes a close to be sent, blocking writes. ws.async_read(b, [&](error_code ec, std::size_t) { // Read should fail with protocol error ++count; BEAST_EXPECTS( ec == error::failed, ec.message()); // Reads after failure are aborted ws.async_read(b, [&](error_code ec, std::size_t) { ++count; BEAST_EXPECTS(ec == boost::asio:: error::operation_aborted, ec.message()); }); }); // Run until the read_op writes a close frame. while(! ws.wr_block_) ios.run_one(); // Write a text message, leaving // the write_op suspended as a pausation. ws.async_write(sbuf("Hello"), [&](error_code ec) { ++count; // Send is canceled because close received. BEAST_EXPECTS(ec == boost::asio:: error::operation_aborted, ec.message()); // Writes after close are aborted. ws.async_write(sbuf("World"), [&](error_code ec) { ++count; BEAST_EXPECTS(ec == boost::asio:: error::operation_aborted, ec.message()); }); }); // Run until all completions are delivered. while(! ios.stopped()) ios.run_one(); BEAST_EXPECT(count == 4); } } void testPausation2(endpoint_type const& ep) { boost::asio::io_service ios; stream ws(ios); ws.next_layer().connect(ep); ws.handshake("localhost", "/"); // Cause close to be received ws.binary(true); ws.write(sbuf("CLOSE")); multi_buffer b; std::size_t count = 0; // Read a close frame. // Sends a close frame, blocking writes. ws.async_read(b, [&](error_code ec, std::size_t) { // Read should complete with error::closed ++count; BEAST_EXPECTS(ec == error::closed, ec.message()); // Pings after a close are aborted ws.async_ping("", [&](error_code ec) { ++count; BEAST_EXPECTS(ec == boost::asio:: error::operation_aborted, ec.message()); }); }); if(! BEAST_EXPECT(run_until(ios, 100, [&]{ return ws.wr_close_; }))) return; // Try to ping ws.async_ping("payload", [&](error_code ec) { // Pings after a close are aborted ++count; BEAST_EXPECTS(ec == boost::asio:: error::operation_aborted, ec.message()); // Subsequent calls to close are aborted ws.async_close({}, [&](error_code ec) { ++count; BEAST_EXPECTS(ec == boost::asio:: error::operation_aborted, ec.message()); }); }); static std::size_t constexpr limit = 100; std::size_t n; for(n = 0; n < limit; ++n) { if(count >= 4) break; ios.run_one(); } BEAST_EXPECT(n < limit); ios.run(); } void testPausation3(endpoint_type const& ep) { boost::asio::io_service ios; stream ws(ios); ws.next_layer().connect(ep); ws.handshake("localhost", "/"); // Cause close to be received ws.binary(true); ws.write(sbuf("CLOSE")); multi_buffer b; std::size_t count = 0; ws.async_read(b, [&](error_code ec, std::size_t) { ++count; BEAST_EXPECTS(ec == error::closed, ec.message()); }); while(! ws.wr_block_) ios.run_one(); // try to close ws.async_close("payload", [&](error_code ec) { ++count; BEAST_EXPECTS(ec == boost::asio:: error::operation_aborted, ec.message()); }); static std::size_t constexpr limit = 100; std::size_t n; for(n = 0; n < limit; ++n) { if(count >= 2) break; ios.run_one(); } BEAST_EXPECT(n < limit); ios.run(); } /* https://github.com/boostorg/beast/issues/300 Write a message as two individual frames */ void testWriteFrames() { for(int i = 0; i < 2; ++i) { error_code ec; stream ws{ios_}; if(i == 0) launchEchoServer(ws.next_layer().remote()); else launchEchoServerAsync(ws.next_layer().remote()); ws.handshake("localhost", "/", ec); if(! BEAST_EXPECTS(! ec, ec.message())) return; ws.write_some(false, sbuf("u")); ws.write_some(true, sbuf("v")); multi_buffer b; ws.read(b, ec); BEAST_EXPECTS(! ec, ec.message()); } } void testAsyncWriteFrame() { for(int i = 0; i < 2; ++i) { for(;;) { error_code ec; boost::asio::io_service ios; stream ws{ios, ios_}; if(i == 0) launchEchoServer(ws.next_layer().remote()); else launchEchoServerAsync(ws.next_layer().remote()); ws.handshake("localhost", "/", ec); if(! BEAST_EXPECTS(! ec, ec.message())) break; ws.async_write_some(false, boost::asio::null_buffers{}, [&](error_code) { fail(); }); if(! BEAST_EXPECTS(! ec, ec.message())) break; // // Destruction of the io_service will cause destruction // of the write_some_op without invoking the final handler. // break; } } } //-------------------------------------------------------------------------- template void testStream( Wrap const& c, permessage_deflate const& pmd, Launch const& launch) { using boost::asio::buffer; // send empty message doTest(c, pmd, launch, [&](ws_stream_type& ws) { ws.text(true); c.write(ws, boost::asio::null_buffers{}); multi_buffer b; c.read(ws, b); BEAST_EXPECT(ws.got_text()); BEAST_EXPECT(b.size() == 0); }); // send message doTest(c, pmd, launch, [&](ws_stream_type& ws) { ws.auto_fragment(false); ws.binary(false); c.write(ws, sbuf("Hello")); multi_buffer b; c.read(ws, b); BEAST_EXPECT(ws.got_text()); BEAST_EXPECT(to_string(b.data()) == "Hello"); }); // read_some doTest(c, pmd, launch, [&](ws_stream_type& ws) { c.write(ws, sbuf("Hello")); char buf[10]; auto const bytes_written = c.read_some(ws, buffer(buf, sizeof(buf))); BEAST_EXPECT(bytes_written > 0); buf[bytes_written] = 0; BEAST_EXPECT( string_view(buf).substr(0, bytes_written) == string_view("Hello").substr(0, bytes_written)); }); // close, no payload doTest(c, pmd, launch, [&](ws_stream_type& ws) { c.close(ws, {}); }); // close with code doTest(c, pmd, launch, [&](ws_stream_type& ws) { c.close(ws, close_code::going_away); }); // send ping and message doTest(c, pmd, launch, [&](ws_stream_type& ws) { bool once = false; auto cb = [&](frame_type kind, string_view s) { BEAST_EXPECT(kind == frame_type::pong); BEAST_EXPECT(! once); once = true; BEAST_EXPECT(s == ""); }; ws.control_callback(cb); c.ping(ws, ""); ws.binary(true); c.write(ws, sbuf("Hello")); multi_buffer b; c.read(ws, b); BEAST_EXPECT(once); BEAST_EXPECT(ws.got_binary()); BEAST_EXPECT(to_string(b.data()) == "Hello"); }); // send ping and fragmented message doTest(c, pmd, launch, [&](ws_stream_type& ws) { bool once = false; auto cb = [&](frame_type kind, string_view s) { BEAST_EXPECT(kind == frame_type::pong); BEAST_EXPECT(! once); once = true; BEAST_EXPECT(s == "payload"); }; ws.control_callback(cb); ws.ping("payload"); c.write_some(ws, false, sbuf("Hello, ")); c.write_some(ws, false, sbuf("")); c.write_some(ws, true, sbuf("World!")); multi_buffer b; c.read(ws, b); BEAST_EXPECT(once); BEAST_EXPECT(to_string(b.data()) == "Hello, World!"); ws.control_callback(); }); // send pong doTest(c, pmd, launch, [&](ws_stream_type& ws) { c.pong(ws, ""); }); // send auto fragmented message doTest(c, pmd, launch, [&](ws_stream_type& ws) { ws.auto_fragment(true); ws.write_buffer_size(8); c.write(ws, sbuf("Now is the time for all good men")); multi_buffer b; c.read(ws, b); BEAST_EXPECT(to_string(b.data()) == "Now is the time for all good men"); }); // send message with write buffer limit doTest(c, pmd, launch, [&](ws_stream_type& ws) { std::string s(2000, '*'); ws.write_buffer_size(1200); c.write(ws, buffer(s.data(), s.size())); multi_buffer b; c.read(ws, b); BEAST_EXPECT(to_string(b.data()) == s); }); // unexpected cont doTest(c, pmd, launch, [&](ws_stream_type& ws) { c.write_raw(ws, cbuf( 0x80, 0x80, 0xff, 0xff, 0xff, 0xff)); doCloseTest(c, ws, close_code::protocol_error); }); // invalid fixed frame header doTest(c, pmd, launch, [&](ws_stream_type& ws) { c.write_raw(ws, cbuf( 0x8f, 0x80, 0xff, 0xff, 0xff, 0xff)); doCloseTest(c, ws, close_code::protocol_error); }); if(! pmd.client_enable) { // expected cont doTest(c, pmd, launch, [&](ws_stream_type& ws) { c.write_some(ws, false, boost::asio::null_buffers{}); c.write_raw(ws, cbuf( 0x81, 0x80, 0xff, 0xff, 0xff, 0xff)); doCloseTest(c, ws, close_code::protocol_error); }); // message size above 2^64 doTest(c, pmd, launch, [&](ws_stream_type& ws) { c.write_some(ws, false, cbuf(0x00)); c.write_raw(ws, cbuf( 0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff)); doCloseTest(c, ws, close_code::too_big); }); /* // message size exceeds max doTest(c, pmd, launch, [&](ws_stream_type& ws) { // VFALCO This was never implemented correctly ws.read_message_max(1); c.write(ws, cbuf(0x81, 0x02, '*', '*')); doCloseTest(c, ws, close_code::too_big); }); */ } // receive ping doTest(c, pmd, launch, [&](ws_stream_type& ws) { put(ws.next_layer().buffer(), cbuf( 0x89, 0x00)); bool invoked = false; auto cb = [&](frame_type kind, string_view) { BEAST_EXPECT(! invoked); BEAST_EXPECT(kind == frame_type::ping); invoked = true; }; ws.control_callback(cb); c.write(ws, sbuf("Hello")); multi_buffer b; c.read(ws, b); BEAST_EXPECT(invoked); BEAST_EXPECT(ws.got_text()); BEAST_EXPECT(to_string(b.data()) == "Hello"); }); // receive ping doTest(c, pmd, launch, [&](ws_stream_type& ws) { put(ws.next_layer().buffer(), cbuf( 0x88, 0x00)); bool invoked = false; auto cb = [&](frame_type kind, string_view) { BEAST_EXPECT(! invoked); BEAST_EXPECT(kind == frame_type::close); invoked = true; }; ws.control_callback(cb); c.write(ws, sbuf("Hello")); doCloseTest(c, ws, close_code::none); }); // receive bad utf8 doTest(c, pmd, launch, [&](ws_stream_type& ws) { put(ws.next_layer().buffer(), cbuf( 0x81, 0x06, 0x03, 0xea, 0xf0, 0x28, 0x8c, 0xbc)); doFailTest(c, ws, error::failed); }); // receive bad close doTest(c, pmd, launch, [&](ws_stream_type& ws) { put(ws.next_layer().buffer(), cbuf( 0x88, 0x02, 0x03, 0xed)); doFailTest(c, ws, error::failed); }); } //-------------------------------------------------------------------------- void run() override { BOOST_STATIC_ASSERT(std::is_constructible< stream, boost::asio::io_service&>::value); BOOST_STATIC_ASSERT(std::is_move_constructible< stream>::value); BOOST_STATIC_ASSERT(std::is_move_assignable< stream>::value); BOOST_STATIC_ASSERT(std::is_constructible< stream, socket_type&>::value); BOOST_STATIC_ASSERT(std::is_move_constructible< stream>::value); BOOST_STATIC_ASSERT(! std::is_move_assignable< stream>::value); log << "sizeof(websocket::stream) == " << sizeof(websocket::stream) << std::endl; testAccept(); permessage_deflate pmd; pmd.client_enable = false; pmd.server_enable = false; testOptions(); testHandshake(); testBadHandshakes(); testBadResponses(); testClose(); testPausation1(); testWriteFrames(); testAsyncWriteFrame(); // Legacy tests use actual TCP/IP sockets // VFALCO: Rewrite to use test::stream and // remote { auto const any = endpoint_type{ address_type::from_string("127.0.0.1"), 0}; error_code ec; ::websocket::sync_echo_server server{nullptr}; server.set_option(pmd); server.open(any, ec); BEAST_EXPECTS(! ec, ec.message()); auto const ep = server.local_endpoint(); testPausation2(ep); testPausation3(ep); } auto const doClientTests = [this](permessage_deflate const& pmd) { testStream(SyncClient{}, pmd, [&](test::stream stream) { launchEchoServer(std::move(stream)); }); #if 0 // This causes false positives on ASAN testStream(SyncClient{}, pmd, [&](test::stream stream) { launchEchoServerAsync(std::move(stream)); }); #endif yield_to( [&](yield_context yield) { testStream(AsyncClient{yield}, pmd, [&](test::stream stream) { launchEchoServer(std::move(stream)); }); }); yield_to( [&](yield_context yield) { testStream(AsyncClient{yield}, pmd, [&](test::stream stream) { launchEchoServerAsync(std::move(stream)); }); }); }; pmd.client_enable = false; pmd.server_enable = false; doClientTests(pmd); pmd.client_enable = true; pmd.server_enable = true; pmd.client_max_window_bits = 10; pmd.client_no_context_takeover = false; pmd.compLevel = 1; pmd.memLevel = 1; doClientTests(pmd); pmd.client_enable = true; pmd.server_enable = true; pmd.client_max_window_bits = 10; pmd.client_no_context_takeover = true; pmd.compLevel = 1; pmd.memLevel = 1; doClientTests(pmd); } }; BEAST_DEFINE_TESTSUITE(beast,websocket,stream); } // websocket } // beast } // boost