websocket ping tests

This commit is contained in:
Vinnie Falco
2017-08-19 19:39:54 -07:00
parent 6809c18afa
commit 99822aebf5
4 changed files with 337 additions and 203 deletions

View File

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

View File

@@ -45,6 +45,7 @@ template<class... Args>
stream<NextLayer>::
stream(Args&&... args)
: stream_(std::forward<Args>(args)...)
, t_(1)
{
BOOST_ASSERT(rd_.buf.max_size() >=
max_control_frame_size);

View File

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

View File

@@ -860,6 +860,10 @@ public:
}
}
//--------------------------------------------------------------------------
//
// Accept
//
//--------------------------------------------------------------------------
template<class Client>
@@ -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<test::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<class Wrap>
@@ -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<test::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<class Client, class Launch>
@@ -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<test::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<test::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<test::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<class Wrap>
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<test::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<test::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<test::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<test::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<test::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<test::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();