// // Copyright (w) 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 "test.hpp" namespace boost { namespace beast { namespace websocket { class stream_write_test : public websocket_test_suite { public: template void doTestWrite(Wrap const& w) { using boost::asio::buffer; permessage_deflate pmd; pmd.client_enable = false; pmd.server_enable = false; // message doTest(pmd, [&](ws_type& ws) { ws.auto_fragment(false); ws.binary(false); std::string const s = "Hello, world!"; w.write(ws, buffer(s)); multi_buffer b; w.read(ws, b); BEAST_EXPECT(ws.got_text()); BEAST_EXPECT(to_string(b.data()) == s); }); // empty message doTest(pmd, [&](ws_type& ws) { ws.text(true); w.write(ws, boost::asio::null_buffers{}); multi_buffer b; w.read(ws, b); BEAST_EXPECT(ws.got_text()); BEAST_EXPECT(b.size() == 0); }); // continuation doTest(pmd, [&](ws_type& ws) { std::string const s = "Hello"; std::size_t const chop = 3; BOOST_ASSERT(chop < s.size()); w.write_some(ws, false, buffer(s.data(), chop)); w.write_some(ws, true, buffer( s.data() + chop, s.size() - chop)); flat_buffer b; w.read(ws, b); BEAST_EXPECT(to_string(b.data()) == s); }); // mask doTest(pmd, [&](ws_type& ws) { ws.auto_fragment(false); std::string const s = "Hello"; w.write(ws, buffer(s)); flat_buffer b; w.read(ws, b); BEAST_EXPECT(to_string(b.data()) == s); }); // mask (large) doTest(pmd, [&](ws_type& ws) { ws.auto_fragment(false); ws.write_buffer_size(16); std::string const s(32, '*'); w.write(ws, buffer(s)); flat_buffer b; w.read(ws, b); BEAST_EXPECT(to_string(b.data()) == s); }); // mask, autofrag doTest(pmd, [&](ws_type& ws) { ws.auto_fragment(true); std::string const s(16384, '*'); w.write(ws, buffer(s)); flat_buffer b; w.read(ws, b); BEAST_EXPECT(to_string(b.data()) == s); }); // nomask doTestLoop([&](test::stream& ts) { echo_server es{log, kind::async_client}; ws_type ws{ts}; ws.next_layer().connect(es.stream()); try { es.async_handshake(); w.accept(ws); ws.auto_fragment(false); std::string const s = "Hello"; w.write(ws, buffer(s)); flat_buffer b; w.read(ws, b); BEAST_EXPECT(to_string(b.data()) == s); w.close(ws, {}); } catch(...) { ts.close(); throw; } ts.close(); }); // nomask, autofrag doTestLoop([&](test::stream& ts) { echo_server es{log, kind::async_client}; ws_type ws{ts}; ws.next_layer().connect(es.stream()); try { es.async_handshake(); w.accept(ws); ws.auto_fragment(true); std::string const s(16384, '*'); w.write(ws, buffer(s)); flat_buffer b; w.read(ws, b); BEAST_EXPECT(to_string(b.data()) == s); w.close(ws, {}); } catch(...) { ts.close(); throw; } ts.close(); }); pmd.client_enable = true; pmd.server_enable = true; pmd.compLevel = 1; // deflate doTest(pmd, [&](ws_type& ws) { auto const& s = random_string(); ws.binary(true); w.write(ws, buffer(s)); flat_buffer b; w.read(ws, b); BEAST_EXPECT(to_string(b.data()) == s); }); // deflate, continuation doTest(pmd, [&](ws_type& ws) { std::string const s = "Hello"; std::size_t const chop = 3; BOOST_ASSERT(chop < s.size()); // This call should produce no // output due to compression latency. w.write_some(ws, false, buffer(s.data(), chop)); w.write_some(ws, true, buffer( s.data() + chop, s.size() - chop)); flat_buffer b; w.read(ws, b); BEAST_EXPECT(to_string(b.data()) == s); }); // deflate, no context takeover pmd.client_no_context_takeover = true; doTest(pmd, [&](ws_type& ws) { auto const& s = random_string(); ws.binary(true); w.write(ws, buffer(s)); flat_buffer b; w.read(ws, b); BEAST_EXPECT(to_string(b.data()) == s); }); } void testWrite() { using boost::asio::buffer; doTestWrite(SyncClient{}); yield_to([&](yield_context yield) { doTestWrite(AsyncClient{yield}); }); // already closed { stream ws{ios_}; error_code ec; ws.write(sbuf(""), ec); BEAST_EXPECTS( ec == boost::asio::error::operation_aborted, ec.message()); } // async, already closed { boost::asio::io_service ios; stream ws{ios}; ws.async_write(sbuf(""), [&](error_code ec) { BEAST_EXPECTS( ec == boost::asio::error::operation_aborted, ec.message()); }); ios.run(); } // suspend on write { echo_server es{log}; error_code ec; boost::asio::io_service ios; 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_ping("", [&](error_code ec) { ++count; BEAST_EXPECTS(! ec, ec.message()); }); BEAST_EXPECT(ws.wr_block_); ws.async_write(sbuf("*"), [&](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); } // suspend on write, nomask, frag { echo_server es{log, kind::async_client}; error_code ec; boost::asio::io_service ios; stream ws{ios}; ws.next_layer().connect(es.stream()); es.async_handshake(); ws.accept(ec); BEAST_EXPECTS(! ec, ec.message()); std::size_t count = 0; std::string const s(16384, '*'); ws.auto_fragment(true); ws.async_write(buffer(s), [&](error_code ec) { ++count; BEAST_EXPECTS(! ec, ec.message()); }); BEAST_EXPECT(ws.wr_block_); ws.async_ping("", [&](error_code ec) { ++count; BEAST_EXPECTS(! ec, ec.message()); }); ios.run(); ios.reset(); BEAST_EXPECT(count == 2); flat_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); ws.async_close({}, [&](error_code ec) { ++count; BEAST_EXPECTS(! ec, ec.message()); }); }); ios.run(); BEAST_EXPECT(count == 4); } // suspend on write, mask, frag { echo_server es{log, kind::async}; error_code ec; boost::asio::io_service ios; stream ws{ios}; ws.next_layer().connect(es.stream()); ws.handshake("localhost", "/", ec); BEAST_EXPECTS(! ec, ec.message()); std::size_t count = 0; std::string const s(16384, '*'); ws.auto_fragment(true); ws.async_write(buffer(s), [&](error_code ec) { ++count; BEAST_EXPECTS(! ec, ec.message()); }); BEAST_EXPECT(ws.wr_block_); ws.async_ping("", [&](error_code ec) { ++count; BEAST_EXPECTS(! ec, ec.message()); }); ios.run(); ios.reset(); BEAST_EXPECT(count == 2); flat_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); ws.async_close({}, [&](error_code ec) { ++count; BEAST_EXPECTS(! ec, ec.message()); }); }); ios.run(); BEAST_EXPECT(count == 4); } // suspend on write, deflate { echo_server es{log, kind::async}; error_code ec; boost::asio::io_service ios; stream ws{ios}; { permessage_deflate pmd; pmd.client_enable = true; pmd.compLevel = 1; ws.set_option(pmd); } ws.next_layer().connect(es.stream()); ws.handshake("localhost", "/", ec); BEAST_EXPECTS(! ec, ec.message()); std::size_t count = 0; auto const& s = random_string(); ws.binary(true); ws.async_write(buffer(s), [&](error_code ec) { ++count; BEAST_EXPECTS(! ec, ec.message()); }); BEAST_EXPECT(ws.wr_block_); ws.async_ping("", [&](error_code ec) { ++count; BEAST_EXPECTS(! ec, ec.message()); }); ios.run(); ios.reset(); BEAST_EXPECT(count == 2); flat_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); ws.async_close({}, [&](error_code ec) { ++count; BEAST_EXPECTS(! ec, ec.message()); }); }); ios.run(); BEAST_EXPECT(count == 4); } } /* https://github.com/boostorg/beast/issues/300 Write a message as two individual frames */ void testIssue300() { for(int i = 0; i < 2; ++i ) { echo_server es{log, i==1 ? kind::async : kind::sync}; boost::asio::io_service ios; stream ws{ios}; ws.next_layer().connect(es.stream()); error_code ec; 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 testWriteSuspend() { for(int i = 0; i < 2; ++i ) { echo_server es{log, i==1 ? kind::async : kind::sync}; boost::asio::io_service ios; stream ws{ios}; ws.next_layer().connect(es.stream()); 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 testAsyncWriteFrame() { for(int i = 0; i < 2; ++i) { for(;;) { echo_server es{log, i==1 ? kind::async : kind::sync}; boost::asio::io_service ios; stream ws{ios}; ws.next_layer().connect(es.stream()); error_code ec; 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; } } } void run() override { testWrite(); testWriteSuspend(); testIssue300(); testAsyncWriteFrame(); } }; BEAST_DEFINE_TESTSUITE(beast,websocket,stream_write); } // websocket } // beast } // boost