Refactor stream operations and tests plus coverage

This commit is contained in:
Vinnie Falco committed 2017-08-31 17:52:36 -07:00
1 parent 9f089c2a33
commit 51a5a36118
22 files changed
+1290 -354

No files matched your search

+29
View File
@@ -560,6 +560,35 @@ public:
"Sec-WebSocket-Version: 13\r\n"
"\r\n"
);
// oversize key
check(error::handshake_failed,
"GET / HTTP/1.1\r\n"
"Host: localhost:80\r\n"
"Upgrade: WebSocket\r\n"
"Connection: upgrade\r\n"
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQdGhlIHNhbXBsZSBub25jZQ==\r\n"
"Sec-WebSocket-Version: 13\r\n"
"\r\n"
);
// bad version
check(error::handshake_failed,
"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: 12\r\n"
"\r\n"
);
// missing version
check(error::handshake_failed,
"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"
"\r\n"
);
// valid request
check({},
"GET / HTTP/1.1\r\n"
+267 -6
View File
@@ -39,6 +39,14 @@ public:
w.close(ws, close_code::going_away);
});
// close with code and reason
doTest(pmd, [&](ws_type& ws)
{
w.close(ws, {
close_code::going_away,
"going away"});
});
// already closed
{
echo_server es{log};
@@ -125,6 +133,17 @@ public:
}
}
// drain masked close frame
{
echo_server es{log, kind::async_client};
stream<test::stream> ws{ios_};
ws.next_layer().connect(es.stream());
ws.set_option(pmd);
es.async_handshake();
ws.accept();
w.close(ws, {});
}
// close with incomplete read message
doTest(pmd, [&](ws_type& ws)
{
@@ -147,8 +166,10 @@ public:
}
void
testCloseSuspend()
testSuspend()
{
using boost::asio::buffer;
// suspend on ping
doFailLoop([&](test::fail_counter& fc)
{
@@ -270,10 +291,10 @@ public:
ws.async_read(b,
[&](error_code ec, std::size_t)
{
++count;
if(ec != error::failed)
BOOST_THROW_EXCEPTION(
system_error{ec});
BEAST_EXPECT(++count == 1);
});
while(! ws.wr_block_)
{
@@ -285,10 +306,10 @@ public:
ws.async_close({},
[&](error_code ec)
{
++count;
if(ec != boost::asio::error::operation_aborted)
BOOST_THROW_EXCEPTION(
system_error{ec});
BEAST_EXPECT(++count == 2);
});
BEAST_EXPECT(count == 0);
ios.run();
@@ -311,10 +332,10 @@ public:
ws.async_read(b,
[&](error_code ec, std::size_t)
{
++count;
if(ec != error::closed)
BOOST_THROW_EXCEPTION(
system_error{ec});
BEAST_EXPECT(++count == 1);
});
while(! ws.wr_block_)
{
@@ -326,16 +347,256 @@ public:
ws.async_close({},
[&](error_code ec)
{
++count;
if(ec != boost::asio::error::operation_aborted)
BOOST_THROW_EXCEPTION(
system_error{ec});
BEAST_EXPECT(++count == 2);
});
BEAST_EXPECT(count == 0);
ios.run();
BEAST_EXPECT(count == 2);
});
// teardown on received close
doFailLoop([&](test::fail_counter& fc)
{
echo_server es{log};
boost::asio::io_service ios;
stream<test::stream> ws{ios, fc};
ws.next_layer().connect(es.stream());
ws.handshake("localhost", "/");
// add a close frame to the input
ws.next_layer().append(string_view{
"\x88\x00", 2});
std::size_t count = 0;
std::string const s = "Hello, world!";
ws.async_write(buffer(s),
[&](error_code ec)
{
if(ec)
BOOST_THROW_EXCEPTION(
system_error{ec});
BEAST_EXPECT(++count == 1);
});
multi_buffer b;
ws.async_read(b,
[&](error_code ec, std::size_t)
{
if(ec != boost::asio::error::operation_aborted)
BOOST_THROW_EXCEPTION(
system_error{ec});
BEAST_EXPECT(++count == 3);
});
ws.async_close({},
[&](error_code ec)
{
if(ec)
BOOST_THROW_EXCEPTION(
system_error{ec});
BEAST_EXPECT(++count == 2);
});
BEAST_EXPECT(count == 0);
ios.run();
BEAST_EXPECT(count == 3);
});
// check for deadlock
doFailLoop([&](test::fail_counter& fc)
{
echo_server es{log};
boost::asio::io_service ios;
stream<test::stream> ws{ios, fc};
ws.next_layer().connect(es.stream());
ws.handshake("localhost", "/");
// add a ping frame to the input
ws.next_layer().append(string_view{
"\x89\x00", 2});
std::size_t count = 0;
multi_buffer b;
std::string const s = "Hello, world!";
ws.async_write(buffer(s),
[&](error_code ec)
{
if(ec)
BOOST_THROW_EXCEPTION(
system_error{ec});
BEAST_EXPECT(++count == 1);
});
ws.async_read(b,
[&](error_code ec, std::size_t)
{
if(ec != boost::asio::error::operation_aborted)
BOOST_THROW_EXCEPTION(
system_error{ec});
BEAST_EXPECT(++count == 3);
});
BEAST_EXPECT(ws.rd_block_);
ws.async_close({},
[&](error_code ec)
{
if(ec)
BOOST_THROW_EXCEPTION(
system_error{ec});
BEAST_EXPECT(++count == 2);
});
BEAST_EXPECT(ws.is_open());
BEAST_EXPECT(ws.wr_block_);
BEAST_EXPECT(count == 0);
ios.run();
BEAST_EXPECT(count == 3);
});
// Four-way: close, read, write, ping
doFailLoop([&](test::fail_counter& fc)
{
echo_server es{log};
boost::asio::io_service ios;
stream<test::stream> ws{ios, fc};
ws.next_layer().connect(es.stream());
ws.handshake("localhost", "/");
std::size_t count = 0;
std::string const s = "Hello, world!";
multi_buffer b;
ws.async_close({},
[&](error_code ec)
{
if(ec)
BOOST_THROW_EXCEPTION(
system_error{ec});
BEAST_EXPECT(++count == 1);
});
ws.async_read(b,
[&](error_code ec, std::size_t)
{
if(ec != boost::asio::error::operation_aborted)
BOOST_THROW_EXCEPTION(
system_error{ec});
++count;
});
ws.async_write(buffer(s),
[&](error_code ec)
{
if(ec != boost::asio::error::operation_aborted)
BOOST_THROW_EXCEPTION(
system_error{ec});
++count;
});
ws.async_ping({},
[&](error_code ec)
{
if(ec != boost::asio::error::operation_aborted)
BOOST_THROW_EXCEPTION(
system_error{ec});
++count;
});
BEAST_EXPECT(count == 0);
ios.run();
BEAST_EXPECT(count == 4);
});
// Four-way: read, write, ping, close
doFailLoop([&](test::fail_counter& fc)
{
echo_server es{log};
boost::asio::io_service ios;
stream<test::stream> ws{ios, fc};
ws.next_layer().connect(es.stream());
ws.handshake("localhost", "/");
std::size_t count = 0;
std::string const s = "Hello, world!";
multi_buffer b;
ws.async_read(b,
[&](error_code ec, std::size_t)
{
if(ec && ec != boost::asio::error::operation_aborted)
{
BEAST_EXPECTS(ec, ec.message());
BOOST_THROW_EXCEPTION(
system_error{ec});
}
if(! ec)
BEAST_EXPECT(to_string(b.data()) == s);
++count;
});
ws.async_write(buffer(s),
[&](error_code ec)
{
if(ec)
BOOST_THROW_EXCEPTION(
system_error{ec});
BEAST_EXPECT(++count == 1);
});
ws.async_ping({},
[&](error_code ec)
{
if(ec != boost::asio::error::operation_aborted)
{
BEAST_EXPECTS(ec, ec.message());
BOOST_THROW_EXCEPTION(
system_error{ec});
}
++count;
});
ws.async_close({},
[&](error_code ec)
{
if(ec)
BOOST_THROW_EXCEPTION(
system_error{ec});
BEAST_EXPECT(++count == 2);
});
BEAST_EXPECT(count == 0);
ios.run();
BEAST_EXPECT(count == 4);
});
// Four-way: ping, read, write, close
doFailLoop([&](test::fail_counter& fc)
{
echo_server es{log};
boost::asio::io_service ios;
stream<test::stream> ws{ios, fc};
ws.next_layer().connect(es.stream());
ws.handshake("localhost", "/");
std::size_t count = 0;
std::string const s = "Hello, world!";
multi_buffer b;
ws.async_ping({},
[&](error_code ec)
{
if(ec)
BOOST_THROW_EXCEPTION(
system_error{ec});
BEAST_EXPECT(++count == 1);
});
ws.async_read(b,
[&](error_code ec, std::size_t)
{
if(ec != boost::asio::error::operation_aborted)
BOOST_THROW_EXCEPTION(
system_error{ec});
++count;
});
ws.async_write(buffer(s),
[&](error_code ec)
{
if(ec != boost::asio::error::operation_aborted)
BOOST_THROW_EXCEPTION(
system_error{ec});
++count;
});
ws.async_close({},
[&](error_code ec)
{
if(ec)
BOOST_THROW_EXCEPTION(
system_error{ec});
BEAST_EXPECT(++count == 2);
});
BEAST_EXPECT(count == 0);
ios.run();
BEAST_EXPECT(count == 4);
});
}
void
@@ -357,7 +618,7 @@ public:
run() override
{
testClose();
testCloseSuspend();
testSuspend();
testContHook();
}
};
+13 -6
View File
@@ -7,12 +7,8 @@
// Official repository: https://github.com/boostorg/beast
//
#include <boost/beast/websocket/stream.hpp>
#include <boost/beast/websocket/detail/frame.hpp>
#include <boost/beast/unit_test/suite.hpp>
#include <boost/beast/test/yield_to.hpp>
#include <initializer_list>
#include <climits>
namespace boost {
namespace beast {
@@ -21,7 +17,6 @@ namespace detail {
class frame_test
: public beast::unit_test::suite
, public test::enable_yield_to
{
public:
void testCloseCodes()
@@ -47,7 +42,7 @@ public:
test_fh()
{
op = detail::opcode::text;
fin = false;
fin = true;
mask = false;
rsv1 = false;
rsv2 = false;
@@ -57,8 +52,20 @@ public:
}
};
void
testWriteFrame()
{
test_fh fh;
fh.rsv2 = true;
fh.rsv3 = true;
fh.len = 65536;
frame_buffer fb;
write(fb, fh);
}
void run() override
{
testWriteFrame();
testCloseCodes();
}
};
+255
View File
@@ -214,10 +214,265 @@ public:
);
}
// Compression Extensions for WebSocket
//
// https://tools.ietf.org/html/rfc7692
//
void
testExtRead()
{
detail::pmd_offer po;
auto const accept =
[&](string_view s)
{
http::fields f;
f.set(http::field::sec_websocket_extensions, s);
po = detail::pmd_offer();
detail::pmd_read(po, f);
BEAST_EXPECT(po.accept);
};
auto const reject =
[&](string_view s)
{
http::fields f;
f.set(http::field::sec_websocket_extensions, s);
po = detail::pmd_offer();
detail::pmd_read(po, f);
BEAST_EXPECT(! po.accept);
};
// duplicate parameters
reject("permessage-deflate; server_max_window_bits=8; server_max_window_bits=8");
// missing value
reject("permessage-deflate; server_max_window_bits");
reject("permessage-deflate; server_max_window_bits=");
// invalid value
reject("permessage-deflate; server_max_window_bits=-1");
reject("permessage-deflate; server_max_window_bits=7");
reject("permessage-deflate; server_max_window_bits=16");
reject("permessage-deflate; server_max_window_bits=999999999999999999999999");
reject("permessage-deflate; server_max_window_bits=9a");
// duplicate parameters
reject("permessage-deflate; client_max_window_bits=8; client_max_window_bits=8");
// optional value excluded
accept("permessage-deflate; client_max_window_bits");
BEAST_EXPECT(po.client_max_window_bits == -1);
accept("permessage-deflate; client_max_window_bits=");
BEAST_EXPECT(po.client_max_window_bits == -1);
// invalid value
reject("permessage-deflate; client_max_window_bits=-1");
reject("permessage-deflate; client_max_window_bits=7");
reject("permessage-deflate; client_max_window_bits=16");
reject("permessage-deflate; client_max_window_bits=999999999999999999999999");
// duplicate parameters
reject("permessage-deflate; server_no_context_takeover; server_no_context_takeover");
// valueless parameter
accept("permessage-deflate; server_no_context_takeover");
BEAST_EXPECT(po.server_no_context_takeover);
accept("permessage-deflate; server_no_context_takeover=");
BEAST_EXPECT(po.server_no_context_takeover);
// disallowed value
reject("permessage-deflate; server_no_context_takeover=-1");
reject("permessage-deflate; server_no_context_takeover=x");
reject("permessage-deflate; server_no_context_takeover=\"yz\"");
reject("permessage-deflate; server_no_context_takeover=999999999999999999999999");
// duplicate parameters
reject("permessage-deflate; client_no_context_takeover; client_no_context_takeover");
// valueless parameter
accept("permessage-deflate; client_no_context_takeover");
BEAST_EXPECT(po.client_no_context_takeover);
accept("permessage-deflate; client_no_context_takeover=");
BEAST_EXPECT(po.client_no_context_takeover);
// disallowed value
reject("permessage-deflate; client_no_context_takeover=-1");
reject("permessage-deflate; client_no_context_takeover=x");
reject("permessage-deflate; client_no_context_takeover=\"yz\"");
reject("permessage-deflate; client_no_context_takeover=999999999999999999999999");
// unknown extension parameter
reject("permessage-deflate; unknown");
reject("permessage-deflate; unknown=");
reject("permessage-deflate; unknown=1");
reject("permessage-deflate; unknown=x");
reject("permessage-deflate; unknown=\"xy\"");
}
void
testExtWrite()
{
detail::pmd_offer po;
auto const check =
[&](string_view match)
{
http::fields f;
detail::pmd_write(f, po);
BEAST_EXPECT(
f[http::field::sec_websocket_extensions]
== match);
};
po.accept = true;
po.server_max_window_bits = 0;
po.client_max_window_bits = 0;
po.server_no_context_takeover = false;
po.client_no_context_takeover = false;
check("permessage-deflate");
po.server_max_window_bits = 10;
check("permessage-deflate; server_max_window_bits=10");
po.server_max_window_bits = -1;
check("permessage-deflate; server_max_window_bits");
po.server_max_window_bits = 0;
po.client_max_window_bits = 10;
check("permessage-deflate; client_max_window_bits=10");
po.client_max_window_bits = -1;
check("permessage-deflate; client_max_window_bits");
po.client_max_window_bits = 0;
po.server_no_context_takeover = true;
check("permessage-deflate; server_no_context_takeover");
po.server_no_context_takeover = false;
po.client_no_context_takeover = true;
check("permessage-deflate; client_no_context_takeover");
}
void
testExtNegotiate()
{
permessage_deflate pmd;
auto const reject =
[&](
string_view offer)
{
detail::pmd_offer po;
{
http::fields f;
f.set(http::field::sec_websocket_extensions, offer);
detail::pmd_read(po, f);
}
http::fields f;
detail::pmd_offer config;
detail::pmd_negotiate(f, config, po, pmd);
BEAST_EXPECT(! config.accept);
};
auto const accept =
[&](
string_view offer,
string_view result)
{
detail::pmd_offer po;
{
http::fields f;
f.set(http::field::sec_websocket_extensions, offer);
detail::pmd_read(po, f);
}
http::fields f;
detail::pmd_offer config;
detail::pmd_negotiate(f, config, po, pmd);
auto const got =
f[http::field::sec_websocket_extensions];
BEAST_EXPECTS(got == result, got);
{
detail::pmd_offer poc;
detail::pmd_read(poc, f);
detail::pmd_normalize(poc);
BEAST_EXPECT(poc.accept);
}
BEAST_EXPECT(config.server_max_window_bits != 0);
BEAST_EXPECT(config.client_max_window_bits != 0);
};
pmd.server_enable = true;
pmd.server_max_window_bits = 15;
pmd.client_max_window_bits = 15;
pmd.server_no_context_takeover = false;
pmd.client_no_context_takeover = false;
// default
accept(
"permessage-deflate",
"permessage-deflate");
// non-default server_max_window_bits
accept(
"permessage-deflate; server_max_window_bits=14",
"permessage-deflate; server_max_window_bits=14");
// explicit default server_max_window_bits
accept(
"permessage-deflate; server_max_window_bits=15",
"permessage-deflate");
// minimum window size of 8 bits (a zlib bug)
accept(
"permessage-deflate; server_max_window_bits=8",
"permessage-deflate; server_max_window_bits=9");
// non-default server_max_window_bits setting
pmd.server_max_window_bits = 10;
accept(
"permessage-deflate",
"permessage-deflate; server_max_window_bits=10");
// clamped server_max_window_bits setting #1
pmd.server_max_window_bits = 10;
accept(
"permessage-deflate; server_max_window_bits=14",
"permessage-deflate; server_max_window_bits=10");
// clamped server_max_window_bits setting #2
pmd.server_max_window_bits=8;
accept(
"permessage-deflate; server_max_window_bits=14",
"permessage-deflate; server_max_window_bits=9");
pmd.server_max_window_bits = 15;
// present with no value
accept(
"permessage-deflate; client_max_window_bits",
"permessage-deflate");
// present with no value, non-default setting
pmd.client_max_window_bits = 10;
accept(
"permessage-deflate; client_max_window_bits",
"permessage-deflate; client_max_window_bits=10");
// absent, non-default setting
pmd.client_max_window_bits = 10;
reject(
"permessage-deflate");
}
void
run() override
{
testHandshake();
testExtRead();
testExtWrite();
testExtNegotiate();
}
};
+41 -32
View File
@@ -89,40 +89,10 @@ public:
{
doTestPing(AsyncClient{yield});
});
// suspend on write
{
echo_server es{log};
error_code ec;
boost::asio::io_service ios;
stream<test::stream> ws{ios};
ws.next_layer().connect(es.stream());
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);
}
}
void
testPingSuspend()
testSuspend()
{
// suspend on write
doFailLoop([&](test::fail_counter& fc)
@@ -352,6 +322,45 @@ public:
BEAST_EXPECT(count == 2);
});
// don't ping on close
doFailLoop([&](test::fail_counter& fc)
{
echo_server es{log};
error_code ec;
boost::asio::io_service ios;
stream<test::stream> ws{ios, fc};
ws.next_layer().connect(es.stream());
ws.handshake("localhost", "/");
std::size_t count = 0;
ws.async_write(sbuf("*"),
[&](error_code ec)
{
++count;
if(ec)
BOOST_THROW_EXCEPTION(
system_error{ec});
});
BEAST_EXPECT(ws.wr_block_);
ws.async_ping("",
[&](error_code ec)
{
++count;
if(ec != boost::asio::error::operation_aborted)
BOOST_THROW_EXCEPTION(
system_error{ec});
});
ws.async_close({},
[&](error_code)
{
++count;
if(ec)
BOOST_THROW_EXCEPTION(
system_error{ec});
});
ios.run();
BEAST_EXPECT(count == 3);
});
{
echo_server es{log, kind::async};
boost::asio::io_service ios;
@@ -437,7 +446,7 @@ public:
run() override
{
testPing();
testPingSuspend();
testSuspend();
testContHook();
}
};
+388 -28
View File
@@ -12,6 +12,8 @@
#include "test.hpp"
#include <boost/asio/write.hpp>
namespace boost {
namespace beast {
namespace websocket {
@@ -104,7 +106,7 @@ public:
});
// two part message
// this triggers "fill the read buffer first"
// triggers "fill the read buffer first"
doTest(pmd, [&](ws_type& ws)
{
w.write_raw(ws, sbuf(
@@ -202,6 +204,58 @@ public:
BEAST_EXPECT(to_string(b.data()) == "Hello, World!");
});
// masked message, big
doStreamLoop([&](test::stream& ts)
{
echo_server es{log, kind::async_client};
ws_type ws{ts};
ws.next_layer().connect(es.stream());
ws.set_option(pmd);
es.async_handshake();
try
{
w.accept(ws);
std::string const s(2000, '*');
ws.auto_fragment(false);
ws.binary(false);
w.write(ws, buffer(s));
multi_buffer b;
w.read(ws, b);
BEAST_EXPECT(ws.got_text());
BEAST_EXPECT(to_string(b.data()) == s);
ws.next_layer().close();
}
catch(...)
{
ts.close();
throw;
}
});
// close
doFailLoop([&](test::fail_counter& fc)
{
echo_server es{log, kind::async};
boost::asio::io_service ios;
stream<test::stream> ws{ios, fc};
ws.next_layer().connect(es.stream());
ws.handshake("localhost", "/");
// Cause close to be received
es.async_close();
std::size_t count = 0;
multi_buffer b;
ws.async_read(b,
[&](error_code ec, std::size_t)
{
++count;
if(ec != error::closed)
BOOST_THROW_EXCEPTION(
system_error{ec});
});
ios.run();
BEAST_EXPECT(count == 1);
});
// already closed
doTest(pmd, [&](ws_type& ws)
{
@@ -249,7 +303,7 @@ public:
doReadTest(w, ws, close_code::protocol_error);
});
// receive bad close
// bad close
doTest(pmd, [&](ws_type& ws)
{
put(ws.next_layer().buffer(), cbuf(
@@ -257,15 +311,6 @@ public:
doFailTest(w, ws, error::failed);
});
// expected cont
doTest(pmd, [&](ws_type& ws)
{
w.write_some(ws, false, boost::asio::null_buffers{});
w.write_raw(ws, cbuf(
0x81, 0x80, 0xff, 0xff, 0xff, 0xff));
doReadTest(w, ws, close_code::protocol_error);
});
// message size above 2^64
doTest(pmd, [&](ws_type& ws)
{
@@ -284,14 +329,6 @@ public:
doFailTest(w, ws, error::failed);
});
// unexpected cont
doTest(pmd, [&](ws_type& ws)
{
w.write_raw(ws, cbuf(
0x80, 0x80, 0xff, 0xff, 0xff, 0xff));
doReadTest(w, ws, close_code::protocol_error);
});
// bad utf8
doTest(pmd, [&](ws_type& ws)
{
@@ -313,10 +350,23 @@ public:
doTest(pmd, [&](ws_type& ws)
{
std::string const s =
random_string() +
"Hello, world!" "\xc0";
w.write(ws, buffer(s));
doReadTest(w, ws, close_code::bad_payload);
"\x81\x7e\x0f\xa1" +
std::string(4000, '*') + "\xc0";
ws.next_layer().append(s);
multi_buffer b;
try
{
do
{
b.commit(w.read_some(ws, b.prepare(4000)));
}
while(! ws.is_message_done());
}
catch(system_error const& se)
{
if(se.code() != error::failed)
throw;
}
});
// close frames
@@ -441,6 +491,34 @@ public:
BEAST_EXPECT(to_string(b.data()) == s);
});
// masked message
doStreamLoop([&](test::stream& ts)
{
echo_server es{log, kind::async_client};
ws_type ws{ts};
ws.next_layer().connect(es.stream());
ws.set_option(pmd);
es.async_handshake();
try
{
w.accept(ws);
std::string const s = "Hello, world!";
ws.auto_fragment(false);
ws.binary(false);
w.write(ws, buffer(s));
multi_buffer b;
w.read(ws, b);
BEAST_EXPECT(ws.got_text());
BEAST_EXPECT(to_string(b.data()) == s);
ws.next_layer().close();
}
catch(...)
{
ts.close();
throw;
}
});
// empty message
doTest(pmd, [&](ws_type& ws)
{
@@ -571,11 +649,77 @@ public:
}
void
testReadSuspend()
testSuspend()
{
using boost::asio::buffer;
#if 1
// suspend on read block
doFailLoop([&](test::fail_counter& fc)
{
echo_server es{log};
boost::asio::io_service ios;
stream<test::stream> ws{ios, fc};
ws.next_layer().connect(es.stream());
ws.handshake("localhost", "/");
std::size_t count = 0;
ws.async_close({},
[&](error_code ec)
{
if(ec)
BOOST_THROW_EXCEPTION(
system_error{ec});
BEAST_EXPECT(++count == 1);
});
while(! ws.rd_block_)
ios.run_one();
multi_buffer b;
ws.async_read(b,
[&](error_code ec, std::size_t)
{
if(ec != boost::asio::error::operation_aborted)
BOOST_THROW_EXCEPTION(
system_error{ec});
BEAST_EXPECT(++count == 2);
});
ios.run();
BEAST_EXPECT(count == 2);
});
#endif
// suspend on write
// suspend on release read block
doFailLoop([&](test::fail_counter& fc)
{
//log << "fc.count()==" << fc.count() << std::endl;
echo_server es{log};
boost::asio::io_service ios;
stream<test::stream> ws{ios, fc};
ws.next_layer().connect(es.stream());
ws.handshake("localhost", "/");
std::size_t count = 0;
multi_buffer b;
ws.async_read(b,
[&](error_code ec, std::size_t)
{
if(ec != boost::asio::error::operation_aborted)
BOOST_THROW_EXCEPTION(
system_error{ec});
BEAST_EXPECT(++count == 2);
});
BOOST_ASSERT(ws.rd_block_);
ws.async_close({},
[&](error_code ec)
{
if(ec)
BOOST_THROW_EXCEPTION(
system_error{ec});
BEAST_EXPECT(++count == 1);
});
ios.run();
BEAST_EXPECT(count == 2);
});
#if 1
// suspend on write pong
doFailLoop([&](test::fail_counter& fc)
{
echo_server es{log};
@@ -592,25 +736,238 @@ public:
ws.async_read(b,
[&](error_code ec, std::size_t)
{
++count;
if(ec)
BOOST_THROW_EXCEPTION(
system_error{ec});
BEAST_EXPECT(to_string(b.data()) == s);
++count;
});
BEAST_EXPECT(ws.rd_block_);
ws.async_write(buffer(s),
[&](error_code ec)
{
++count;
if(ec)
BOOST_THROW_EXCEPTION(
system_error{ec});
++count;
});
BEAST_EXPECT(ws.wr_block_);
ios.run();
BEAST_EXPECT(count == 2);
});
// Ignore ping when closing
doFailLoop([&](test::fail_counter& fc)
{
echo_server es{log};
boost::asio::io_service ios;
stream<test::stream> ws{ios, fc};
ws.next_layer().connect(es.stream());
ws.handshake("localhost", "/");
std::size_t count = 0;
// insert fragmented message with
// a ping in between the frames.
ws.next_layer().append(string_view(
"\x01\x01*"
"\x89\x00"
"\x80\x01*", 8));
multi_buffer b;
ws.async_read(b,
[&](error_code ec, std::size_t)
{
if(ec)
BOOST_THROW_EXCEPTION(
system_error{ec});
BEAST_EXPECT(to_string(b.data()) == "**");
BEAST_EXPECT(++count == 1);
b.consume(b.size());
ws.async_read(b,
[&](error_code ec, std::size_t)
{
if(ec != boost::asio::error::operation_aborted)
BOOST_THROW_EXCEPTION(
system_error{ec});
BEAST_EXPECT(++count == 3);
});
});
BEAST_EXPECT(ws.rd_block_);
ws.async_close({},
[&](error_code ec)
{
if(ec)
BOOST_THROW_EXCEPTION(
system_error{ec});
BEAST_EXPECT(++count == 2);
});
BEAST_EXPECT(ws.wr_block_);
ios.run();
BEAST_EXPECT(count == 3);
});
// See if we are already closing
doFailLoop([&](test::fail_counter& fc)
{
echo_server es{log};
boost::asio::io_service ios;
stream<test::stream> ws{ios, fc};
ws.next_layer().connect(es.stream());
ws.handshake("localhost", "/");
std::size_t count = 0;
// insert fragmented message with
// a close in between the frames.
ws.next_layer().append(string_view(
"\x01\x01*"
"\x88\x00"
"\x80\x01*", 8));
multi_buffer b;
ws.async_read(b,
[&](error_code ec, std::size_t)
{
if(ec != boost::asio::error::operation_aborted)
BOOST_THROW_EXCEPTION(
system_error{ec});
BEAST_EXPECT(++count == 2);
});
BEAST_EXPECT(ws.rd_block_);
ws.async_close({},
[&](error_code ec)
{
if(ec)
BOOST_THROW_EXCEPTION(
system_error{ec});
BEAST_EXPECT(++count == 1);
});
BEAST_EXPECT(ws.wr_block_);
ios.run();
BEAST_EXPECT(count == 2);
});
#endif
}
void
testParseFrame()
{
auto const bad =
[&](string_view s)
{
echo_server es{log};
boost::asio::io_service ios;
stream<test::stream> ws{ios};
ws.next_layer().connect(es.stream());
ws.handshake("localhost", "/");
ws.next_layer().append(s);
error_code ec;
multi_buffer b;
ws.read(b, ec);
BEAST_EXPECT(ec);
};
// chopped frame header
{
echo_server es{log};
boost::asio::io_service ios;
stream<test::stream> ws{ios};
ws.next_layer().connect(es.stream());
ws.handshake("localhost", "/");
ws.next_layer().append(
"\x81\x7e\x01");
std::size_t count = 0;
std::string const s(257, '*');
error_code ec;
multi_buffer b;
ws.async_read(b,
[&](error_code ec, std::size_t)
{
++count;
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(to_string(b.data()) == s);
});
ios.run_one();
es.stream().write_some(
boost::asio::buffer("\x01" + s));
ios.run();
BEAST_EXPECT(count == 1);
}
// new data frame when continuation expected
bad("\x01\x01*" "\x81\x01*");
// reserved bits not cleared
bad("\xb1\x01*");
bad("\xc1\x01*");
bad("\xd1\x01*");
// continuation without an active message
bad("\x80\x01*");
// reserved bits not cleared (cont)
bad("\x01\x01*" "\xb0\x01*");
bad("\x01\x01*" "\xc0\x01*");
bad("\x01\x01*" "\xd0\x01*");
// reserved opcode
bad("\x83\x01*");
// fragmented control message
bad("\x09\x01*");
// invalid length for control message
bad("\x89\x7e\x01\x01");
// reserved bits not cleared (control)
bad("\xb9\x01*");
bad("\xc9\x01*");
bad("\xd9\x01*");
// unmasked frame from client
{
echo_server es{log, kind::async_client};
boost::asio::io_service ios;
stream<test::stream> ws{ios};
ws.next_layer().connect(es.stream());
es.async_handshake();
ws.accept();
ws.next_layer().append(
"\x81\x01*");
error_code ec;
multi_buffer b;
ws.read(b, ec);
BEAST_EXPECT(ec);
}
// masked frame from server
bad("\x81\x80\xff\xff\xff\xff");
// chopped control frame payload
{
echo_server es{log};
boost::asio::io_service ios;
stream<test::stream> ws{ios};
ws.next_layer().connect(es.stream());
ws.handshake("localhost", "/");
ws.next_layer().append(
"\x89\x02*");
std::size_t count = 0;
error_code ec;
multi_buffer b;
ws.async_read(b,
[&](error_code ec, std::size_t)
{
++count;
BEAST_EXPECTS(! ec, ec.message());
BEAST_EXPECT(to_string(b.data()) == "**");
});
ios.run_one();
es.stream().write_some(
boost::asio::buffer(
"*" "\x81\x02**"));
ios.run();
BEAST_EXPECT(count == 1);
}
// length not canonical
bad(string_view("\x81\x7e\x00\x7d", 4));
bad(string_view("\x81\x7f\x00\x00\x00\x00\x00\x00\xff\xff", 10));
}
void
@@ -631,6 +988,7 @@ public:
buf, sizeof(buf)}};
using boost::asio::asio_handler_is_continuation;
asio_handler_is_continuation(&op);
pass();
}
{
struct handler
@@ -645,6 +1003,7 @@ public:
handler{}, ws, b, 32, true};
using boost::asio::asio_handler_is_continuation;
asio_handler_is_continuation(&op);
pass();
}
}
@@ -652,7 +1011,8 @@ public:
run() override
{
testRead();
testReadSuspend();
testSuspend();
testParseFrame();
testContHook();
}
};
-2
View File
@@ -34,8 +34,6 @@ public:
req.insert("Connection", "upgrade");
BEAST_EXPECT(! is_upgrade(req));
req.insert("Upgrade", "websocket");
BEAST_EXPECT(! is_upgrade(req));
req.insert("Sec-WebSocket-Version", "13");
BEAST_EXPECT(is_upgrade(req));
}
+64 -77
View File
@@ -22,7 +22,7 @@ public:
void
testOptions()
{
stream<test::stream> ws(ios_);
stream<test::stream> ws{ios_};
ws.auto_fragment(true);
ws.write_buffer_size(2048);
ws.binary(false);
@@ -36,48 +36,71 @@ public:
{
pass();
}
auto const bad =
[&](permessage_deflate const& pmd)
{
stream<test::stream> ws{ios_};
try
{
ws.set_option(pmd);
fail("", __FILE__, __LINE__);
}
catch(std::exception const&)
{
pass();
}
};
{
permessage_deflate pmd;
pmd.server_max_window_bits = 16;
bad(pmd);
}
{
permessage_deflate pmd;
pmd.server_max_window_bits = 8;
bad(pmd);
}
{
permessage_deflate pmd;
pmd.client_max_window_bits = 16;
bad(pmd);
}
{
permessage_deflate pmd;
pmd.client_max_window_bits = 8;
bad(pmd);
}
{
permessage_deflate pmd;
pmd.compLevel = -1;
bad(pmd);
}
{
permessage_deflate pmd;
pmd.compLevel = 10;
bad(pmd);
}
{
permessage_deflate pmd;
pmd.memLevel = 0;
bad(pmd);
}
{
permessage_deflate pmd;
pmd.memLevel = 10;
bad(pmd);
}
}
//--------------------------------------------------------------------------
template<class Wrap>
void
doTestStream(Wrap const& w,
permessage_deflate const& pmd)
{
using boost::asio::buffer;
// send pong
doTest(pmd, [&](ws_type& ws)
{
w.pong(ws, "");
});
// send auto fragmented message
doTest(pmd, [&](ws_type& ws)
{
ws.auto_fragment(true);
ws.write_buffer_size(8);
w.write(ws, sbuf("Now is the time for all good men"));
multi_buffer b;
w.read(ws, b);
BEAST_EXPECT(to_string(b.data()) == "Now is the time for all good men");
});
// send message with write buffer limit
doTest(pmd, [&](ws_type& ws)
{
std::string s(2000, '*');
ws.write_buffer_size(1200);
w.write(ws, buffer(s.data(), s.size()));
multi_buffer b;
w.read(ws, b);
BEAST_EXPECT(to_string(b.data()) == s);
});
}
//--------------------------------------------------------------------------
void
run() override
{
@@ -103,42 +126,6 @@ public:
sizeof(websocket::stream<test::stream&>) << std::endl;
testOptions();
#if 0
auto const testStream =
[this](permessage_deflate const& pmd)
{
doTestStream(SyncClient{}, pmd);
yield_to(
[&](yield_context yield)
{
doTestStream(AsyncClient{yield}, pmd);
});
};
permessage_deflate pmd;
pmd.client_enable = false;
pmd.server_enable = false;
testStream(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;
testStream(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;
testStream(pmd);
#endif
}
};
+4 -4
View File
@@ -264,7 +264,7 @@ public:
Test const& f, std::size_t limit = 200)
{
std::size_t n;
for(n = 0; n <= limit; ++n)
for(n = 0; n < limit; ++n)
{
test::fail_counter fc{n};
try
@@ -288,7 +288,7 @@ public:
{
// This number has to be high for the
// test that writes the large buffer.
static std::size_t constexpr limit = 1000;
static std::size_t constexpr limit = 200;
doFailLoop(
[&](test::fail_counter& fc)
@@ -308,12 +308,12 @@ public:
{
// This number has to be high for the
// test that writes the large buffer.
static std::size_t constexpr limit = 1000;
static std::size_t constexpr limit = 200;
for(int i = 0; i < 2; ++i)
{
std::size_t n;
for(n = 0; n <= limit; ++n)
for(n = 0; n < limit; ++n)
{
test::fail_counter fc{n};
test::stream ts{ios_, fc};
+14
View File
@@ -73,6 +73,20 @@ public:
BEAST_EXPECT(b.size() == 0);
});
// fragmented message
doTest(pmd, [&](ws_type& ws)
{
ws.auto_fragment(false);
ws.binary(false);
std::string const s = "Hello, world!";
w.write_some(ws, false, buffer(s.data(), 5));
w.write_some(ws, true, buffer(s.data() + 5, s.size() - 5));
multi_buffer b;
w.read(ws, b);
BEAST_EXPECT(ws.got_text());
BEAST_EXPECT(to_string(b.data()) == s);
});
// continuation
doTest(pmd, [&](ws_type& ws)
{
@@ -114,6 +114,7 @@ struct fail_error_code : error_code
class fail_counter
{
std::size_t n_;
std::size_t i_ = 0;
error_code ec_;
public:
@@ -131,13 +132,20 @@ public:
{
}
/// Returns the fail index
std::size_t
count() const
{
return n_;
}
/// Throw an exception on the Nth failure
void
fail()
{
if(n_ > 0)
--n_;
if(! n_)
if(i_ < n_)
++i_;
if(i_ == n_)
BOOST_THROW_EXCEPTION(system_error{ec_});
}
@@ -145,9 +153,9 @@ public:
bool
fail(error_code& ec)
{
if(n_ > 0)
--n_;
if(! n_)
if(i_ < n_)
++i_;
if(i_ == n_)
{
ec = ec_;
return true;
+26 -14
View File
@@ -78,6 +78,11 @@ class stream
std::size_t write_max =
(std::numeric_limits<std::size_t>::max)();
~state()
{
BOOST_ASSERT(! op);
}
explicit
state(
boost::asio::io_service& ios_,
@@ -87,11 +92,6 @@ class stream
{
}
~state()
{
BOOST_ASSERT(! op);
}
void
on_write()
{
@@ -119,6 +119,10 @@ public:
/// Destructor
~stream()
{
{
std::unique_lock<std::mutex> lock{in_->m};
in_->op.reset();
}
auto out = out_.lock();
if(out)
{
@@ -612,16 +616,17 @@ teardown(
stream& s,
boost::system::error_code& ec)
{
if(s.in_->fc)
{
if(s.in_->fc->fail(ec))
return;
}
if( s.in_->fc &&
s.in_->fc->fail(ec))
return;
s.close();
if( s.in_->fc &&
s.in_->fc->fail(ec))
ec = boost::asio::error::eof;
else
{
s.close();
ec.assign(0, ec.category());
}
}
template<class TeardownHandler>
@@ -633,10 +638,17 @@ async_teardown(
TeardownHandler&& handler)
{
error_code ec;
if(s.in_->fc && s.in_->fc->fail(ec))
if( s.in_->fc &&
s.in_->fc->fail(ec))
return s.get_io_service().post(
bind_handler(std::move(handler), ec));
s.close();
if( s.in_->fc &&
s.in_->fc->fail(ec))
ec = boost::asio::error::eof;
else
ec.assign(0, ec.category());
s.get_io_service().post(
bind_handler(std::move(handler), ec));
}