diff --git a/CHANGELOG.md b/CHANGELOG.md index 5378d695..89591d92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ WebSocket: * Fix async_read_some handler signature * websocket close fixes and tests * websocket handshake uses coroutine +* websocket ping tests API Changes: diff --git a/include/boost/beast/websocket/impl/stream.ipp b/include/boost/beast/websocket/impl/stream.ipp index b62cecf7..e44dc0bf 100644 --- a/include/boost/beast/websocket/impl/stream.ipp +++ b/include/boost/beast/websocket/impl/stream.ipp @@ -45,6 +45,7 @@ template stream:: stream(Args&&... args) : stream_(std::forward(args)...) + , t_(1) { BOOST_ASSERT(rd_.buf.max_size() >= max_control_frame_size); diff --git a/include/boost/beast/websocket/stream.hpp b/include/boost/beast/websocket/stream.hpp index 178bfd34..953e0b3b 100644 --- a/include/boost/beast/websocket/stream.hpp +++ b/include/boost/beast/websocket/stream.hpp @@ -131,11 +131,11 @@ class stream // tokens are used to order reads and writes class token { - unsigned char id_ = 1; - explicit token(unsigned char id) : id_(id) {} + unsigned char id_ = 0; public: token() = default; token(token const&) = default; + explicit token(unsigned char id) : id_(id) {} operator bool() const { return id_ != 0; } bool operator==(token const& t) { return id_ == t.id_; } bool operator!=(token const& t) { return id_ != t.id_; } @@ -225,7 +225,7 @@ class stream detail::opcode::text; // outgoing message type control_cb_type ctrl_cb_; // control callback role_type role_; // server or client - bool failed_; // the connection failed + bool failed_ = true; // the connection failed bool rd_close_; // read close frame bool wr_close_; // sent close frame diff --git a/test/beast/websocket/stream.cpp b/test/beast/websocket/stream.cpp index aeeae2ad..f48d48bf 100644 --- a/test/beast/websocket/stream.cpp +++ b/test/beast/websocket/stream.cpp @@ -860,6 +860,10 @@ public: } } + //-------------------------------------------------------------------------- + // + // Accept + // //-------------------------------------------------------------------------- template @@ -1269,8 +1273,139 @@ public: { doTestAccept(AsyncClient{yield}); }); + + // + // Bad requests + // + + 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" + ); } + //-------------------------------------------------------------------------- + // + // Close + // //-------------------------------------------------------------------------- template @@ -1392,8 +1527,15 @@ public: } void - doTestCloseAsync() + testClose() { + doTestClose(SyncClient{}); + + yield_to([&](yield_context yield) + { + doTestClose(AsyncClient{yield}); + }); + auto const launch = [&](test::stream stream) { @@ -1459,58 +1601,6 @@ public: } } - void - testClose() - { - doTestClose(SyncClient{}); - - yield_to([&](yield_context yield) - { - doTestClose(AsyncClient{yield}); - }); - - doTestCloseAsync(); - } - - //-------------------------------------------------------------------------- - - void - testRead() - { - // Read close frames - { - 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\x01\x01"); - - // invalid close code 1005 - check(error::failed, - "\x88\x02\x03\xed"); - - // invalid utf8 - check(error::failed, - "\x88\x06\xfc\x15\x0f\xd7\x73\x43"); - - // good utf8 - check(error::closed, - "\x88\x06\xfc\x15utf8"); - } - } - //-------------------------------------------------------------------------- template @@ -1597,153 +1687,23 @@ public: launchEchoServerAsync(std::move(stream)); }); }); - } - //-------------------------------------------------------------------------- - - void testBadHandshakes() - { auto const check = - [&](error_code const& ev, std::string const& s) + [&](std::string const& s) + { + stream ws{ios_}; + ws.next_layer().str(s); + ws.next_layer().remote().close(); + try { - 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) + ws.handshake("localhost:80", "/"); + fail(); + } + catch(system_error const& se) { - 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); - } - }; + BEAST_EXPECT(se.code() == error::handshake_failed); + } + }; // wrong HTTP version check( "HTTP/1.0 101 Switching Protocols\r\n" @@ -1805,6 +1765,180 @@ public: ); } + //-------------------------------------------------------------------------- + // + // Ping + // + //-------------------------------------------------------------------------- + + template + void + doTestPing(Wrap const& w) + { + auto const launch = + [&](test::stream stream) + { + launchEchoServer(std::move(stream)); + //launchEchoServerAsync(std::move(stream)); + }; + + permessage_deflate pmd; + pmd.client_enable = false; + pmd.server_enable = false; + + // ping + doTest(w, pmd, launch, [&](ws_stream_type& ws) + { + w.ping(ws, {}); + }); + + // pong + doTest(w, pmd, launch, [&](ws_stream_type& ws) + { + w.pong(ws, {}); + }); + } + + void + testPing() + { + doTestPing(SyncClient{}); + + yield_to([&](yield_context yield) + { + doTestPing(AsyncClient{yield}); + }); + + auto const launch = + [&](test::stream stream) + { + launchEchoServer(std::move(stream)); + //launchEchoServerAsync(std::move(stream)); + }; + + // ping, already closed + { + stream ws{ios_}; + error_code ec; + ws.ping({}, ec); + BEAST_EXPECTS( + ec == boost::asio::error::operation_aborted, + ec.message()); + } + + // async_ping, already closed + { + boost::asio::io_service ios; + stream ws{ios}; + ws.async_ping({}, + [&](error_code ec) + { + BEAST_EXPECTS( + ec == boost::asio::error::operation_aborted, + ec.message()); + }); + ios.run(); + } + + // pong, already closed + { + stream ws{ios_}; + error_code ec; + ws.pong({}, ec); + BEAST_EXPECTS( + ec == boost::asio::error::operation_aborted, + ec.message()); + } + + // async_pong, already closed + { + boost::asio::io_service ios; + stream ws{ios}; + ws.async_pong({}, + [&](error_code ec) + { + BEAST_EXPECTS( + ec == boost::asio::error::operation_aborted, + ec.message()); + }); + ios.run(); + } + + // suspend on write + { + error_code ec; + boost::asio::io_service ios; + stream ws{ios, ios_}; + launch(ws.next_layer().remote()); + ws.handshake("localhost", "/", ec); + BEAST_EXPECTS(! ec, ec.message()); + std::size_t count = 0; + ws.async_write(sbuf("*"), + [&](error_code ec) + { + ++count; + BEAST_EXPECTS(! ec, ec.message()); + }); + BEAST_EXPECT(ws.wr_block_); + ws.async_ping("", + [&](error_code ec) + { + ++count; + BEAST_EXPECTS( + ec == boost::asio::error::operation_aborted, + ec.message()); + }); + ws.async_close({}, [&](error_code){}); + ios.run(); + BEAST_EXPECT(count == 2); + } + } + + //-------------------------------------------------------------------------- + // + // Read + // + //-------------------------------------------------------------------------- + + void + testRead() + { + // Read close frames + { + 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\x01\x01"); + + // invalid close code 1005 + check(error::failed, + "\x88\x02\x03\xed"); + + // invalid utf8 + check(error::failed, + "\x88\x06\xfc\x15\x0f\xd7\x73\x43"); + + // good utf8 + check(error::closed, + "\x88\x06\xfc\x15utf8"); + } + } + + //-------------------------------------------------------------------------- + void testMask(endpoint_type const& ep, yield_context do_yield) @@ -2366,6 +2500,8 @@ public: testAccept(); testClose(); + testHandshake(); + testPing(); testRead(); permessage_deflate pmd; @@ -2373,10 +2509,6 @@ public: pmd.server_enable = false; testOptions(); - testHandshake(); - testBadHandshakes(); - testBadResponses(); - testClose(); testPausation1(); testWriteFrames(); testAsyncWriteFrame();