]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/boost/libs/beast/test/beast/websocket/accept.cpp
import new upstream nautilus stable release 14.2.8
[ceph.git] / ceph / src / boost / libs / beast / test / beast / websocket / accept.cpp
index 9f6545958aecf6bbfe8f6892cf67ac321b00e96d..9c1e58bf1dea928943c5c8a25d08ba4deee01f8e 100644 (file)
@@ -1,5 +1,5 @@
 //
-// Copyright (w) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
+// Copyright (c) 2016-2019 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)
 // Test that header file is self-contained.
 #include <boost/beast/websocket/stream.hpp>
 
+#include <boost/beast/_experimental/test/stream.hpp>
+#include <boost/beast/_experimental/test/tcp.hpp>
+#include <boost/beast/_experimental/unit_test/suite.hpp>
 #include "test.hpp"
 
-#include <boost/asio/io_service.hpp>
-#include <boost/asio/strand.hpp>
-
 namespace boost {
 namespace beast {
 namespace websocket {
 
-class accept_test : public websocket_test_suite
+class accept_test : public unit_test::suite //: public websocket_test_suite
 {
 public:
-    template<class Wrap>
-    void
-    doTestAccept(Wrap const& w)
+    class res_decorator
     {
-        class res_decorator
+        bool& b_;
+
+    public:
+        res_decorator(res_decorator const&) = default;
+
+        explicit
+        res_decorator(bool& b)
+            : b_(b)
         {
-            bool& b_;
+        }
 
-        public:
-            res_decorator(res_decorator const&) = default;
+        void
+        operator()(response_type&) const
+        {
+            this->b_ = true;
+        }
+    };
+
+    template<std::size_t N>
+    static
+    net::const_buffer
+    sbuf(const char (&s)[N])
+    {
+        return net::const_buffer(&s[0], N-1);
+    }
 
-            explicit
-            res_decorator(bool& b)
-                : b_(b)
+    static
+    void
+    fail_loop(
+        std::function<void(stream<test::stream>&)> f,
+        std::chrono::steady_clock::duration amount =
+            std::chrono::seconds(5))
+    {
+        using clock_type = std::chrono::steady_clock;
+        auto const expires_at =
+            clock_type::now() + amount;
+        net::io_context ioc;
+        for(std::size_t n = 0;;++n)
+        {
+            test::fail_count fc(n);
+            try
             {
+                stream<test::stream> ws(ioc, fc);
+                auto tr = connect(ws.next_layer());
+                f(ws);
+                break;
             }
-
-            void
-            operator()(response_type&) const
+            catch(system_error const& se)
             {
-                this->b_ = true;
+                // VFALCO Commented this out after the short
+                // read change, because it converts test_failure
+                // into http::partial_message
+                //
+                boost::ignore_unused(se);
+            #if 0
+                if(! BEAST_EXPECTS(
+                    se.code() == test::error::test_failure,
+                    se.code().message()))
+                    throw;
+            #endif
+                if(! BEAST_EXPECTS(
+                    clock_type::now() < expires_at,
+                    "a test timeout occurred"))
+                    break;
             }
-        };
+        }
+    }
 
-        auto const big = []
-        {
-            std::string s;
-            s += "X1: " + std::string(2000, '*') + "\r\n";
-            return s;
-        }();
+    template<class Api>
+    void
+    testMatrix(Api api)
+    {
+        net::io_context ioc;
 
         // request in stream
-        doStreamLoop([&](test::stream& ts)
+        fail_loop([&](stream<test::stream>& ws)
         {
-            stream<test::stream&> ws{ts};
-            auto tr = connect(ws.next_layer());
-            ts.append(
+            ws.next_layer().append(
                 "GET / HTTP/1.1\r\n"
                 "Host: localhost\r\n"
                 "Upgrade: websocket\r\n"
@@ -66,59 +109,207 @@ public:
                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
                 "Sec-WebSocket-Version: 13\r\n"
                 "\r\n");
-            ts.read_size(20);
-            w.accept(ws);
-            // VFALCO validate contents of ws.next_layer().str?
+            ws.next_layer().read_size(20);
+            api.accept(ws);
         });
 
-        // request in stream, oversized
+        // request in stream, decorator
+        fail_loop([&](stream<test::stream>& ws)
         {
-            stream<test::stream> ws{ioc_,
+            ws.next_layer().append(
                 "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"};
-            auto tr = connect(ws.next_layer());
-            try
-            {
-                w.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());
-            }
-        }
+                "\r\n");
+            ws.next_layer().read_size(20);
+            bool called = false;
+            ws.set_option(stream_base::decorator(
+                res_decorator{called}));
+            api.accept(ws);
+            BEAST_EXPECT(called);
+        });
 
-        // request in stream, decorator
-        doStreamLoop([&](test::stream& ts)
+        // request in buffers
+        fail_loop([&](stream<test::stream>& ws)
         {
-            stream<test::stream&> ws{ts};
-            auto tr = connect(ws.next_layer());
-            ts.append(
+            api.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, decorator
+        fail_loop([&](stream<test::stream>& ws)
+        {
+            bool called = false;
+            ws.set_option(stream_base::decorator(
+                res_decorator{called}));
+            api.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"));
+            BEAST_EXPECT(called);
+        });
+
+        // request in buffers and stream
+        fail_loop([&](stream<test::stream>& ws)
+        {
+            ws.next_layer().append(
+                "Connection: upgrade\r\n"
+                "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
+                "Sec-WebSocket-Version: 13\r\n"
+                "\r\n");
+            ws.next_layer().read_size(16);
+            api.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, decorator
+        fail_loop([&](stream<test::stream>& ws)
+        {
+            ws.next_layer().append(
                 "Connection: upgrade\r\n"
                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
                 "Sec-WebSocket-Version: 13\r\n"
                 "\r\n");
-            ts.read_size(20);
+            ws.next_layer().read_size(16);
             bool called = false;
-            w.accept_ex(ws, res_decorator{called});
+            ws.set_option(stream_base::decorator(
+                res_decorator{called}));
+            api.accept(ws, sbuf(
+                "GET / HTTP/1.1\r\n"
+                "Host: localhost\r\n"
+                "Upgrade: websocket\r\n"));
             BEAST_EXPECT(called);
         });
 
-        // request in stream, decorator, oversized
+        // request in message
+        {
+            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");
+
+            fail_loop([&](stream<test::stream>& ws)
+            {
+                api.accept(ws, req);
+            });
+        }
+
+        // request in message, decorator
+        {
+            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");
+
+            fail_loop([&](stream<test::stream>& ws)
+            {
+                bool called = false;
+                ws.set_option(stream_base::decorator(
+                    res_decorator{called}));
+                api.accept(ws, req);
+                BEAST_EXPECT(called);
+            });
+        }
+
+        // request in message, close frame in stream
+        {
+            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");
+
+            fail_loop([&](stream<test::stream>& ws)
+            {
+                ws.next_layer().append("\x88\x82\xff\xff\xff\xff\xfc\x17");
+                api.accept(ws, req);
+                try
+                {
+                    static_buffer<1> b;
+                    api.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)
+        fail_loop([&](stream<test::stream>& ws)
+        {
+            ws.next_layer().append(
+                "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");
+            ws.next_layer().read_size(20);
+            try
+            {
+                api.accept(ws);
+                BEAST_FAIL();
+            }
+            catch(system_error const& e)
+            {
+                if( e.code() != websocket::error::no_sec_key &&
+                    e.code() != net::error::eof)
+                    throw;
+            }
+        });
+    }
+
+    template<class Api>
+    void
+    testOversized(Api const& api)
+    {
+        net::io_context ioc;
+
+        auto const big = []
+        {
+            std::string s;
+            s += "X1: " + std::string(2000, '*') + "\r\n";
+            return s;
+        }();
+
+        // request in stream
         {
-            stream<test::stream> ws{ioc_,
+            stream<test::stream> ws{ioc,
                 "GET / HTTP/1.1\r\n"
                 "Host: localhost\r\n"
                 "Upgrade: websocket\r\n"
@@ -130,9 +321,8 @@ public:
             auto tr = connect(ws.next_layer());
             try
             {
-                bool called = false;
-                w.accept_ex(ws, res_decorator{called});
-                fail("", __FILE__, __LINE__);
+                api.accept(ws);
+                BEAST_FAIL();
             }
             catch(system_error const& se)
             {
@@ -143,29 +333,42 @@ public:
             }
         }
 
-        // request in buffers
-        doStreamLoop([&](test::stream& ts)
+        // request in stream, decorator
         {
-            stream<test::stream&> ws{ts};
-            auto tr = connect(ws.next_layer());
-            w.accept(ws, sbuf(
+            stream<test::stream> ws{ioc,
                 "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"
-            ));
-        });
+                + big +
+                "\r\n"};
+            auto tr = connect(ws.next_layer());
+            try
+            {
+                bool called = false;
+                ws.set_option(stream_base::decorator(
+                    res_decorator{called}));
+                api.accept(ws);
+                BEAST_FAIL();
+            }
+            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, oversize
+        // request in buffers
         {
-            stream<test::stream> ws{ioc_};
+            stream<test::stream> ws{ioc};
             auto tr = connect(ws.next_layer());
             try
             {
-                w.accept(ws, boost::asio::buffer(
+                api.accept(ws, net::buffer(
                     "GET / HTTP/1.1\r\n"
                     "Host: localhost\r\n"
                     "Upgrade: websocket\r\n"
@@ -175,7 +378,7 @@ public:
                     + big +
                     "\r\n"
                 ));
-                fail("", __FILE__, __LINE__);
+                BEAST_FAIL();
             }
             catch(system_error const& se)
             {
@@ -186,31 +389,15 @@ public:
         }
 
         // request in buffers, decorator
-        doStreamLoop([&](test::stream& ts)
         {
-            stream<test::stream&> ws{ts};
-            auto tr = connect(ws.next_layer());
-            bool called = false;
-            w.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<test::stream> ws{ioc_};
+            stream<test::stream> ws{ioc};
             auto tr = connect(ws.next_layer());
             try
             {
                 bool called = false;
-                w.accept_ex(ws, boost::asio::buffer(
+                ws.set_option(stream_base::decorator(
+                    res_decorator{called}));
+                api.accept(ws, net::buffer(
                     "GET / HTTP/1.1\r\n"
                     "Host: localhost\r\n"
                     "Upgrade: websocket\r\n"
@@ -218,9 +405,8 @@ public:
                     "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
                     "Sec-WebSocket-Version: 13\r\n"
                     + big +
-                    "\r\n"),
-                    res_decorator{called});
-                fail("", __FILE__, __LINE__);
+                    "\r\n"));
+                BEAST_FAIL();
             }
             catch(system_error const& se)
             {
@@ -231,27 +417,8 @@ public:
         }
 
         // request in buffers and stream
-        doStreamLoop([&](test::stream& ts)
         {
-            stream<test::stream&> ws{ts};
-            auto tr = connect(ws.next_layer());
-            ts.append(
-                "Connection: upgrade\r\n"
-                "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
-                "Sec-WebSocket-Version: 13\r\n"
-                "\r\n");
-            ts.read_size(16);
-            w.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<test::stream> ws{ioc_,
+            stream<test::stream> ws{ioc,
                 "Connection: upgrade\r\n"
                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
                 "Sec-WebSocket-Version: 13\r\n"
@@ -260,12 +427,12 @@ public:
             auto tr = connect(ws.next_layer());
             try
             {
-                w.accept(ws, sbuf(
+                api.accept(ws, websocket_test_suite::sbuf(
                     "GET / HTTP/1.1\r\n"
                     "Host: localhost\r\n"
                     "Upgrade: websocket\r\n"
                 ));
-                fail("", __FILE__, __LINE__);
+                BEAST_FAIL();
             }
             catch(system_error const& se)
             {
@@ -276,28 +443,8 @@ public:
         }
 
         // request in buffers and stream, decorator
-        doStreamLoop([&](test::stream& ts)
-        {
-            stream<test::stream&> ws{ts};
-            auto tr = connect(ws.next_layer());
-            ts.append(
-                "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;
-            w.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<test::stream> ws{ioc_,
+            stream<test::stream> ws{ioc,
                 "Connection: upgrade\r\n"
                 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
                 "Sec-WebSocket-Version: 13\r\n"
@@ -307,12 +454,13 @@ public:
             try
             {
                 bool called = false;
-                w.accept_ex(ws, sbuf(
+                ws.set_option(stream_base::decorator(
+                    res_decorator{called}));
+                api.accept(ws, websocket_test_suite::sbuf(
                     "GET / HTTP/1.1\r\n"
                     "Host: localhost\r\n"
-                    "Upgrade: websocket\r\n"),
-                    res_decorator{called});
-                fail("", __FILE__, __LINE__);
+                    "Upgrade: websocket\r\n"));
+                BEAST_FAIL();
             }
             catch(system_error const& se)
             {
@@ -321,137 +469,15 @@ public:
                     se.code().message());
             }
         }
-
-        // request in message
-        doStreamLoop([&](test::stream& ts)
-        {
-            stream<test::stream&> ws{ts};
-            auto tr = connect(ws.next_layer());
-            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");
-            w.accept(ws, req);
-        });
-
-        // request in message, decorator
-        doStreamLoop([&](test::stream& ts)
-        {
-            stream<test::stream&> ws{ts};
-            auto tr = connect(ws.next_layer());
-            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;
-            w.accept_ex(ws, req,
-                res_decorator{called});
-            BEAST_EXPECT(called);
-        });
-
-        // request in message, close frame in stream
-        doStreamLoop([&](test::stream& ts)
-        {
-            stream<test::stream&> ws{ts};
-            auto tr = connect(ws.next_layer());
-            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.append("\x88\x82\xff\xff\xff\xff\xfc\x17");
-            w.accept(ws, req);
-            try
-            {
-                static_buffer<1> b;
-                w.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)
-        doStreamLoop([&](test::stream& ts)
-        {
-            stream<test::stream&> ws{ts};
-            auto tr = connect(ws.next_layer());
-            ts.append(
-                "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
-            {
-                w.accept(ws);
-                fail("success", __FILE__, __LINE__);
-            }
-            catch(system_error const& e)
-            {
-                if( e.code() !=
-                        websocket::error::no_sec_key &&
-                    e.code() !=
-                        boost::asio::error::eof)
-                    throw;
-            }
-        });
-
-        // Closed by client
-        {
-            stream<test::stream> ws{ioc_};
-            auto tr = connect(ws.next_layer());
-            tr.close();
-            try
-            {
-                w.accept(ws);
-                fail("success", __FILE__, __LINE__);
-            }
-            catch(system_error const& e)
-            {
-                if(! BEAST_EXPECTS(
-                    e.code() == error::closed,
-                    e.code().message()))
-                    throw;
-            }
-        }
     }
 
     void
-    testAccept()
+    testInvalidInputs()
     {
-        doTestAccept(SyncClient{});
-
-        yield_to([&](yield_context yield)
-        {
-            doTestAccept(AsyncClient{yield});
-        });
-
-        //
-        // Bad requests
-        //
+        net::io_context ioc;
 
         auto const check =
-        [&](error_code const& ev, std::string const& s)
+        [&](error_code ev, string_view s)
         {
             for(int i = 0; i < 3; ++i)
             {
@@ -469,14 +495,14 @@ public:
                     n = s.size() - 1;
                     break;
                 }
-                stream<test::stream> ws{ioc_};
+                stream<test::stream> ws(ioc);
                 auto tr = connect(ws.next_layer());
                 ws.next_layer().append(
                     s.substr(n, s.size() - n));
+                tr.close();
                 try
                 {
-                    ws.accept(
-                        boost::asio::buffer(s.data(), n));
+                    ws.accept(net::buffer(s.data(), n));
                     BEAST_EXPECTS(! ev, ev.message());
                 }
                 catch(system_error const& se)
@@ -496,6 +522,7 @@ public:
             "Sec-WebSocket-Version: 13\r\n"
             "\r\n"
         );
+
         // bad method
         check(error::bad_method,
             "POST / HTTP/1.1\r\n"
@@ -506,6 +533,7 @@ public:
             "Sec-WebSocket-Version: 13\r\n"
             "\r\n"
         );
+
         // no Host
         check(error::no_host,
             "GET / HTTP/1.1\r\n"
@@ -515,6 +543,7 @@ public:
             "Sec-WebSocket-Version: 13\r\n"
             "\r\n"
         );
+
         // no Connection
         check(error::no_connection,
             "GET / HTTP/1.1\r\n"
@@ -524,6 +553,7 @@ public:
             "Sec-WebSocket-Version: 13\r\n"
             "\r\n"
         );
+
         // no Connection upgrade
         check(error::no_connection_upgrade,
             "GET / HTTP/1.1\r\n"
@@ -534,6 +564,7 @@ public:
             "Sec-WebSocket-Version: 13\r\n"
             "\r\n"
         );
+
         // no Upgrade
         check(error::no_upgrade,
             "GET / HTTP/1.1\r\n"
@@ -543,6 +574,7 @@ public:
             "Sec-WebSocket-Version: 13\r\n"
             "\r\n"
         );
+
         // no Upgrade websocket
         check(error::no_upgrade_websocket,
             "GET / HTTP/1.1\r\n"
@@ -553,6 +585,7 @@ public:
             "Sec-WebSocket-Version: 13\r\n"
             "\r\n"
         );
+
         // no Sec-WebSocket-Key
         check(error::no_sec_key,
             "GET / HTTP/1.1\r\n"
@@ -562,6 +595,7 @@ public:
             "Sec-WebSocket-Version: 13\r\n"
             "\r\n"
         );
+
         // bad Sec-WebSocket-Key
         check(error::bad_sec_key,
             "GET / HTTP/1.1\r\n"
@@ -572,6 +606,7 @@ public:
             "Sec-WebSocket-Version: 13\r\n"
             "\r\n"
         );
+
         // no Sec-WebSocket-Version
         check(error::no_sec_version,
             "GET / HTTP/1.1\r\n"
@@ -581,6 +616,7 @@ public:
             "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
             "\r\n"
         );
+
         // bad Sec-WebSocket-Version
         check(error::bad_sec_version,
             "GET / HTTP/1.1\r\n"
@@ -591,6 +627,7 @@ public:
             "Sec-WebSocket-Version: 1\r\n"
             "\r\n"
         );
+
         // bad Sec-WebSocket-Version
         check(error::bad_sec_version,
             "GET / HTTP/1.1\r\n"
@@ -601,6 +638,7 @@ public:
             "Sec-WebSocket-Version: 12\r\n"
             "\r\n"
         );
+
         // valid request
         check({},
             "GET / HTTP/1.1\r\n"
@@ -614,40 +652,174 @@ public:
     }
 
     void
-    testMoveOnly()
+    testEndOfStream()
     {
-        boost::asio::io_context ioc;
-        stream<test::stream> ws{ioc};
-        ws.async_accept(move_only_handler{});
+        net::io_context ioc;
+        {
+            stream<test::stream> ws(ioc);
+            auto tr = connect(ws.next_layer());
+            tr.close();
+            try
+            {
+                test_sync_api api;
+                api.accept(ws, net::const_buffer{});
+                BEAST_FAIL();
+            }
+            catch(system_error const& se)
+            {
+                BEAST_EXPECTS(
+                    se.code() == error::closed,
+                    se.code().message());
+            }
+        }
+        {
+            stream<test::stream> ws(ioc);
+            auto tr = connect(ws.next_layer());
+            tr.close();
+            try
+            {
+                test_async_api api;
+                api.accept(ws, net::const_buffer{});
+                BEAST_FAIL();
+            }
+            catch(system_error const& se)
+            {
+                BEAST_EXPECTS(
+                    se.code() == error::closed,
+                    se.code().message());
+            }
+        }
     }
 
-    struct copyable_handler
+    void
+    testAsync()
     {
-        template<class... Args>
-        void
-        operator()(Args&&...) const
+        using tcp = net::ip::tcp;
+
+        net::io_context ioc;
+
+        // success, no timeout
+
         {
+            stream<tcp::socket> ws1(ioc);
+            stream<tcp::socket> ws2(ioc);
+            test::connect(ws1.next_layer(), ws2.next_layer());
+
+            ws1.async_handshake("test", "/", test::success_handler());
+            ws2.async_accept(test::success_handler());
+            test::run_for(ioc, std::chrono::seconds(1));
         }
-    };
 
-    void
-    testAsioHandlerInvoke()
-    {
-        // make sure things compile, also can set a
-        // breakpoint in asio_handler_invoke to make sure
-        // it is instantiated.
-        boost::asio::io_context ioc;
-        boost::asio::io_service::strand s{ioc};
-        stream<test::stream> ws{ioc};
-        ws.async_accept(s.wrap(copyable_handler{}));
+        {
+            stream<test::stream> ws1(ioc);
+            stream<test::stream> ws2(ioc);
+            test::connect(ws1.next_layer(), ws2.next_layer());
+
+            ws1.async_handshake("test", "/", test::success_handler());
+            ws2.async_accept(test::success_handler());
+            test::run_for(ioc, std::chrono::seconds(1));
+        }
+
+        // success, timeout enabled
+
+        {
+            stream<tcp::socket> ws1(ioc);
+            stream<tcp::socket> ws2(ioc);
+            test::connect(ws1.next_layer(), ws2.next_layer());
+
+            ws1.set_option(stream_base::timeout{
+                std::chrono::milliseconds(50),
+                stream_base::none(),
+                false});
+            ws1.async_accept(test::success_handler());
+            ws2.async_handshake("test", "/", test::success_handler());
+            test::run_for(ioc, std::chrono::seconds(1));
+        }
+
+        {
+            stream<test::stream> ws1(ioc);
+            stream<test::stream> ws2(ioc);
+            test::connect(ws1.next_layer(), ws2.next_layer());
+
+            ws1.set_option(stream_base::timeout{
+                std::chrono::milliseconds(50),
+                stream_base::none(),
+                false});
+            ws1.async_accept(test::success_handler());
+            ws2.async_handshake("test", "/", test::success_handler());
+            test::run_for(ioc, std::chrono::seconds(1));
+        }
+
+        // timeout
+
+        {
+            stream<tcp::socket> ws1(ioc);
+            stream<tcp::socket> ws2(ioc);
+            test::connect(ws1.next_layer(), ws2.next_layer());
+
+            ws1.set_option(stream_base::timeout{
+                std::chrono::milliseconds(50),
+                stream_base::none(),
+                false});
+            ws1.async_accept(test::fail_handler(beast::error::timeout));
+            test::run_for(ioc, std::chrono::seconds(1));
+        }
+
+        {
+            stream<test::stream> ws1(ioc);
+            stream<test::stream> ws2(ioc);
+            test::connect(ws1.next_layer(), ws2.next_layer());
+
+            ws1.set_option(stream_base::timeout{
+                std::chrono::milliseconds(50),
+                stream_base::none(),
+                false});
+            ws1.async_accept(test::fail_handler(beast::error::timeout));
+            test::run_for(ioc, std::chrono::seconds(1));
+        }
+
+        // abandoned operation
+
+        {
+            {
+                stream<tcp::socket> ws1(ioc);
+                ws1.async_accept(test::fail_handler(
+                    net::error::operation_aborted));
+            }
+            test::run(ioc);
+        }
+
+        {
+            {
+                stream<tcp::socket> ws1(ioc);
+                string_view s =
+                    "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";
+                error_code ec;
+                http::request_parser<http::empty_body> p;
+                p.put(net::const_buffer(s.data(), s.size()), ec);
+                ws1.async_accept(p.get(), test::fail_handler(
+                    net::error::operation_aborted));
+            }
+            test::run(ioc);
+        }
     }
 
     void
     run() override
     {
-        testAccept();
-        testMoveOnly();
-        testAsioHandlerInvoke();
+        testMatrix(test_sync_api{});
+        testMatrix(test_async_api{});
+        testOversized(test_sync_api{});
+        testOversized(test_async_api{});
+        testInvalidInputs();
+        testEndOfStream();
+        testAsync();
     }
 };