diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj index 748d05d439..b91e4be582 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj +++ b/Builds/VisualStudio2015/RippleD.vcxproj @@ -236,26 +236,14 @@ - - - - - - - - - - - - @@ -306,8 +294,6 @@ - - @@ -352,6 +338,8 @@ + + @@ -394,8 +382,6 @@ - - @@ -428,6 +414,8 @@ + + diff --git a/Builds/VisualStudio2015/RippleD.vcxproj.filters b/Builds/VisualStudio2015/RippleD.vcxproj.filters index f954ba6a4c..cb63157e0f 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2015/RippleD.vcxproj.filters @@ -537,24 +537,9 @@ . - - extras\beast\unit_test - extras\beast\unit_test - - extras\beast\unit_test - - - extras\beast\unit_test - - - extras\beast\unit_test - - - extras\beast\unit_test - extras\beast\unit_test\detail @@ -564,9 +549,6 @@ extras\beast\unit_test - - extras\beast\unit_test - extras\beast\unit_test @@ -642,9 +624,6 @@ include\beast\core\detail - - include\beast\core\detail - include\beast\core\detail @@ -711,6 +690,9 @@ include\beast\http + + include\beast\http + include\beast\http\detail @@ -774,9 +756,6 @@ include\beast\http - - include\beast\http - include\beast\http @@ -825,6 +804,9 @@ include\beast\websocket\impl + + include\beast\websocket\impl + include\beast\websocket\impl diff --git a/src/beast/.travis.yml b/src/beast/.travis.yml index 4feaf6a82a..04f04d4df6 100644 --- a/src/beast/.travis.yml +++ b/src/beast/.travis.yml @@ -34,8 +34,8 @@ packages: &gcc5_pkgs - autotools-dev - libc6-dbg -packages: &clang36_pkgs - - clang-3.6 +packages: &clang38_pkgs + - clang-3.8 - g++-5 - python-software-properties - libssl-dev @@ -53,56 +53,57 @@ packages: &clang36_pkgs matrix: include: # GCC/Debug + # - compiler: gcc + # env: GCC_VER=5 VARIANT=debug ADDRESS_MODEL=64 + # addons: &ao_gcc5 + # apt: + # sources: ['ubuntu-toolchain-r-test'] + # packages: *gcc5_pkgs + + # # GCC/Release + # - compiler: gcc + # env: GCC_VER=5 VARIANT=release ADDRESS_MODEL=64 + # addons: *ao_gcc5 + + # Coverage - compiler: gcc - env: GCC_VER=5 VARIANT=debug ADDRESS_MODEL=64 + env: GCC_VER=5 VARIANT=coverage ADDRESS_MODEL=64 addons: &ao_gcc5 apt: sources: ['ubuntu-toolchain-r-test'] packages: *gcc5_pkgs - # - compiler: gcc - # env: GCC_VER=5 VARIANT=debug ADDRESS_MODEL=32 - # addons: *ao_gcc5 + # # Clang/Debug + # - compiler: clang + # env: GCC_VER=5 VARIANT=debug CLANG_VER=3.8 ADDRESS_MODEL=64 + # addons: &ao_clang38 + # apt: + # sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.8'] + # packages: *clang38_pkgs - # GCC/Release - - compiler: gcc - env: GCC_VER=5 VARIANT=release ADDRESS_MODEL=64 - addons: *ao_gcc5 + # # Clang/Release + # - compiler: clang + # env: GCC_VER=5 VARIANT=release CLANG_VER=3.8 ADDRESS_MODEL=64 + # addons: *ao_clang38 - # # - compiler: gcc - # # env: GCC_VER=5 VARIANT=release ADDRESS_MODEL=32 - # # addons: *ao_gcc5 - - # Clang/Debug + # Clang/AddressSanitizer - compiler: clang - env: GCC_VER=5 VARIANT=debug CLANG_VER=3.6 ADDRESS_MODEL=64 - addons: &ao_clang36 + env: GCC_VER=5 VARIANT=asan CLANG_VER=3.8 ADDRESS_MODEL=64 + addons: &ao_clang38 apt: - sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.6'] - packages: *clang36_pkgs + sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.8'] + packages: *clang38_pkgs - # # - compiler: clang - # # env: GCC_VER=5 VARIANT=debug CLANG_VER=3.6 ADDRESS_MODEL=32 - # # addons: *ao_clang36 + # Clang/MemorySanitizer + # VFALCO Generates false positives unless libc++ is compiled with msan turned on + #- compiler: clang + # env: GCC_VER=5 VARIANT=msan CLANG_VER=3.8 ADDRESS_MODEL=64 MSAN_OPTIONS=poison_in_dtor=1,sanitize-memory-track-origins=2 + # addons: *ao_clang38 - # Clang/Release + # Clang/UndefinedBehaviourSanitizer - compiler: clang - env: GCC_VER=5 VARIANT=release CLANG_VER=3.6 ADDRESS_MODEL=64 - addons: *ao_clang36 - - # # - compiler: clang - # # env: GCC_VER=5 VARIANT=release CLANG_VER=3.6 ADDRESS_MODEL=32 - # # addons: *ao_clang36 - - # Coverage - - compiler: gcc - env: GCC_VER=5 VARIANT=coverage ADDRESS_MODEL=64 - addons: *ao_gcc5 - - # ASAN - - compiler: gcc - env: GCC_VER=5 VARIANT=asan ADDRESS_MODEL=64 - addons: *ao_gcc5 + env: GCC_VER=5 VARIANT=usan CLANG_VER=3.8 ADDRESS_MODEL=64 + addons: *ao_clang38 cache: directories: diff --git a/src/beast/Jamroot b/src/beast/Jamroot index 5013bd0058..f0f9f097c3 100644 --- a/src/beast/Jamroot +++ b/src/beast/Jamroot @@ -61,6 +61,22 @@ variant asan "-fsanitize=address" ; +variant msan + : + debug + : + "-fsanitize=memory -fno-omit-frame-pointer -fsanitize-memory-track-origins=2 -fsanitize-memory-use-after-dtor" + "-fsanitize=memory" + ; + +variant usan + : + debug + : + "-fsanitize=undefined -fno-omit-frame-pointer" + "-fsanitize=undefined" + ; + project beast : requirements . @@ -90,7 +106,7 @@ project beast SOLARIS:__EXTENSIONS__ SOLARIS:socket SOLARIS:nsl - NT:_WIN32_WINNT=0x0501 + NT:_WIN32_WINNT=0x0601 NT,cw:ws2_32 NT,cw:mswsock NT,gcc:ws2_32 diff --git a/src/beast/README.md b/src/beast/README.md index f5eaaeaad6..87612d41aa 100644 --- a/src/beast/README.md +++ b/src/beast/README.md @@ -2,7 +2,8 @@ [![Build Status](https://travis-ci.org/vinniefalco/Beast.svg?branch=master)](https://travis-ci.org/vinniefalco/Beast) [![codecov] (https://codecov.io/gh/vinniefalco/Beast/branch/master/graph/badge.svg)](https://codecov.io/gh/vinniefalco/Beast) [![Documentation] -(https://img.shields.io/badge/documentation-master-brightgreen.svg)](http://vinniefalco.github.io/beast/) +(https://img.shields.io/badge/documentation-master-brightgreen.svg)](http://vinniefalco.github.io/beast/) [![License] +(https://img.shields.io/badge/license-boost-brightgreen.svg)](LICENSE_1_0.txt) Beast provides implementations of the HTTP and WebSocket protocols built on top of Boost.Asio and other parts of boost. diff --git a/src/beast/TODO.txt b/src/beast/TODO.txt index f830acf700..c848551631 100644 --- a/src/beast/TODO.txt +++ b/src/beast/TODO.txt @@ -32,6 +32,13 @@ WebSocket: * Don't try to read requests into empty_body * Give callers control over the http request/response used during handshake * Investigate poor autobahn results in Debug builds +* Fall through composed operation switch cases +* Replace stream::error_ with stream::state_, example states: ok, error, abort_io + Need a cancel state so waking up a ping stored in invokable knows to call the + final handler with operation_aborted +* Use close_code::no_code instead of close_code::none +* Make request_type, response_type public APIs, + use in stream member function signatures HTTP: * Define Parser concept in HTTP diff --git a/src/beast/doc/beast.qbk b/src/beast/doc/beast.qbk index ffb0b52f7c..1b8cd35246 100644 --- a/src/beast/doc/beast.qbk +++ b/src/beast/doc/beast.qbk @@ -22,7 +22,6 @@ [template mdash[] '''— '''] [template indexterm1[term1] ''''''[term1]''''''] [template indexterm2[term1 term2] ''''''[term1]''''''[term2]''''''] -[template ticket[number]''''''#[number]''''''] [def __POSIX__ /POSIX/] [def __Windows__ /Windows/] [def __accept__ [@http://www.opengroup.org/onlinepubs/000095399/functions/accept.html `accept()`]] @@ -191,8 +190,15 @@ supporting its development. [include websocket.qbk] [section:types Type Requirements] -[include core_types.qbk] -[include http_types.qbk] +[include types/Body.qbk] +[include types/BufferSequence.qbk] +[include types/Field.qbk] +[include types/FieldSequence.qbk] +[include types/Parser.qbk] +[include types/Reader.qbk] +[include types/Streambuf.qbk] +[include types/Streams.qbk] +[include types/Writer.qbk] [endsect] [include design.qbk] diff --git a/src/beast/doc/quickref.xml b/src/beast/doc/quickref.xml index 20562e03f6..8aec05241c 100644 --- a/src/beast/doc/quickref.xml +++ b/src/beast/doc/quickref.xml @@ -41,14 +41,19 @@ Type Traits + is_Body is_Parser + is_ReadableBody + is_WritableBody Functions + async_parse async_read async_write + parse prepare read write @@ -62,6 +67,7 @@ Body Field FieldSequence + Parser Reader Writer @@ -70,16 +76,20 @@ Classes close_reason + ping_data stream + reason_string Options auto_fragment_size decorate keep_alive + mask_buffer_size + message_type + pong_callback read_buffer_size read_message_max - write_buffer_size @@ -91,6 +101,7 @@ Constants close_code + error opcode @@ -163,10 +174,10 @@ Concepts BufferSequence - AsyncStream - Stream + AsyncStream + Stream Streambuf - SyncStream + SyncStream diff --git a/src/beast/doc/reference.xsl b/src/beast/doc/reference.xsl index f57b329160..0824f07747 100644 --- a/src/beast/doc/reference.xsl +++ b/src/beast/doc/reference.xsl @@ -1528,7 +1528,7 @@ - class ``[link beast.types.stream.AsyncStream [*AsyncStream]]`` + class ``[link beast.types.streams.AsyncStream [*AsyncStream]]`` class ``[@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/AsyncReadStream.html [*AsyncReadStream]]`` @@ -1556,13 +1556,13 @@ class ``[@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/MutableBufferSequence.html [*MutableBufferSequence]]`` - class ``[link beast.types.stream.Stream [*Stream]]`` + class ``[link beast.types.streams.Stream [*Stream]]`` class ``[link beast.types.Streambuf [*Streambuf]]`` - class ``[link beast.types.stream.SyncStream [*SyncStream]]`` + class ``[link beast.types.streams.SyncStream [*SyncStream]]`` class ``[@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/SyncReadStream.html [*SyncReadStream]]`` diff --git a/src/beast/doc/types/Body.qbk b/src/beast/doc/types/Body.qbk new file mode 100644 index 0000000000..95c91417a0 --- /dev/null +++ b/src/beast/doc/types/Body.qbk @@ -0,0 +1,50 @@ +[/ + Copyright (c) 2013-2016 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) +] + +[section:Body Body] + +In this table: + +* `X` is a type meeting the requirements of [*`Body`]. + +[table Body requirements +[[operation] [type] [semantics, pre/post-conditions]] +[ + [`X::value_type`] + [] + [ + The type of the `message::body` member. + If this is not movable or not copyable, the containing message + will be not movable or not copyable. + ] +] +[ + [`X:value_type{}`] + [] + [`DefaultConstructible`] +] +[ + [`Body::reader`] + [] + [ + If present, a type meeting the requirements of + [link beast.types.Reader [*`Reader`]]. + Provides an implementation to parse the body. + ] +] +[ + [`Body::writer`] + [] + [ + If present, a type meeting the requirements of + [link beast.types.Writer [*`Writer`]]. + Provides an implementation to serialize the body. + ] +] +] + +[endsect] diff --git a/src/beast/doc/types/BufferSequence.qbk b/src/beast/doc/types/BufferSequence.qbk new file mode 100644 index 0000000000..04457281b6 --- /dev/null +++ b/src/beast/doc/types/BufferSequence.qbk @@ -0,0 +1,15 @@ +[/ + Copyright (c) 2013-2016 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) +] + +[section:BufferSequence BufferSequence] + +A `BufferSequence` is a type meeting either of the following requirements: + +* [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/ConstBufferSequence.html [*`ConstBufferSequence`]] +* [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/MutableBufferSequence.html [*`MutableBufferSequence`]] + +[endsect] diff --git a/src/beast/doc/types/Field.qbk b/src/beast/doc/types/Field.qbk new file mode 100644 index 0000000000..e5d8b22c6f --- /dev/null +++ b/src/beast/doc/types/Field.qbk @@ -0,0 +1,41 @@ +[/ + Copyright (c) 2013-2016 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) +] + +[section:Field Field] + +A [*`Field`] represents a single HTTP header field/value pair. + +In this table: + +* `X` denotes a type meeting the requirements of [*`Field`]. +* `a` denotes a value of type `X`. + +[table Field requirements + +[[operation][type][semantics, pre/post-conditions]] +[ + [`a.name()`] + [`boost::string_ref`] + [ + This function returns a value implicitly convertible to + `boost::string_ref` containing the case-insensitive field + name, without leading or trailing white space. + ] +] +[ + [`a.value()`] + [`boost::string_ref`] + [ + This function returns a value implicitly convertible to + `boost::string_ref` containing the value for the field. The + value is considered canonical if there is no leading or + trailing whitespace. + ] +] +] + +[endsect] diff --git a/src/beast/doc/types/FieldSequence.qbk b/src/beast/doc/types/FieldSequence.qbk new file mode 100644 index 0000000000..c0cd123bff --- /dev/null +++ b/src/beast/doc/types/FieldSequence.qbk @@ -0,0 +1,51 @@ +[/ + Copyright (c) 2013-2016 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) +] + +[section:FieldSequence FieldSequence] + +A [*`FieldSequence`] is an iterable container whose value type meets +the requirements of [link beast.types.Field [*`Field`]]. + +In this table: + +* `X` denotes a type that meets the requirements of [*`FieldSequence`]. + +* `a` is a value of type `X`. + +[table FieldSequence requirements +[[operation][type][semantics, pre/post-conditions]] +[ + [`X::value_type`] + [] + [ + A type that meets the requirements of `Field`. + ] +] +[ + [`X::const_iterator`] + [] + [ + A type that meets the requirements of `ForwardIterator`. + ] +] +[ + [`a.begin()`] + [`X::const_iterator`] + [ + Returns an iterator to the beginning of the field sequence. + ] +] +[ + [`a.end()`] + [`X::const_iterator`] + [ + Returns an iterator to the end of the field sequence. + ] +] +] + +[endsect] diff --git a/src/beast/doc/types/Parser.qbk b/src/beast/doc/types/Parser.qbk new file mode 100644 index 0000000000..0690f23f64 --- /dev/null +++ b/src/beast/doc/types/Parser.qbk @@ -0,0 +1,58 @@ +[/ + Copyright (c) 2013-2016 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) +] + +[section:Parser Parser] + +A [*`Parser`] is used to deserialize HTTP/1 messages from [link beast.types.streams streams]. +Objects of this type are used with [link beast.ref.http__parse http::parse] and +[link beast.ref.http__async_parse http::async_parse]. + +In this table: + +* `X` denotes a type meeting the requirements of [*`Parser`]. + +* `a` denotes a value of type `X`. + +* `b` is a value meeting the requirements of [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/ConvertibleToConstBuffer.html [*`ConvertibleToConstBuffer`]]. + +* `ec` is a value of type [link beast.ref.error_code `error_code&`]. + +[table Parser requirements +[[operation] [type] [semantics, pre/post-conditions]] +[ + [`a.complete()`] + [`bool`] + [ + Returns `true` when a complete HTTP/1 message has been parsed. + ] +] +[ + [`a.write(b, ec)`] + [`std::size_t`] + [ + Parses the octets in the specified input buffer sequentially until + an error occurs, the end of the buffer is reached, or a complete + HTTP/1 message has been parsed. If an error occurs, `ec` is set + to the error code and parsing stops. This function returns the + number of bytes consumed from the input buffer. + ] +] +[ + [`a.write_eof(ec)`] + [`void`] + [ + Indicates to the parser that no more octets will be available. + Typically this function is called when the end of stream is reached. + For example, if a call to `boost::asio::ip::tcp::socket::read_some` + generates a `boost::asio::error::eof` error. Some HTTP/1 messages + determine the end of the message body by an end of file marker or + closing of the connection. + ] +] +] + +[endsect] diff --git a/src/beast/doc/types/Reader.qbk b/src/beast/doc/types/Reader.qbk new file mode 100644 index 0000000000..a94db4d0bb --- /dev/null +++ b/src/beast/doc/types/Reader.qbk @@ -0,0 +1,54 @@ +[/ + Copyright (c) 2013-2016 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) +] + +[section:Reader Reader] + +Parser implementations will construct the corresponding `reader` object +during the parse. This customization point allows the Body to determine +the strategy for storing incoming message body data. + +In this table: + +* `X` denotes a type meeting the requirements of [*`Reader`]. + +* `a` denotes a value of type `X`. + +* `p` is any pointer. + +* `n` is a value convertible to `std::size_t`. + +* `ec` is a value of type `error_code&`. + +* `m` denotes a value of type `message const&` where + `std::is_same:value == true` + + +[table Reader requirements +[[operation] [type] [semantics, pre/post-conditions]] +[ + [`X a(m);`] + [] + [ + `a` is constructible from `m`. The lifetime of `m` is + guaranteed to end no earlier than after `a` is destroyed. + ] +] +[ + [`a.write(p, n, ec)`] + [`void`] + [ + Deserializes the input sequence into the body. + If `ec` is set, the deserialization is aborted and the error + is returned to the caller. + ] +] +] + +[note Definitions for required `Reader` member functions should be declared +inline so the generated code becomes part of the implementation. ] + +[endsect] diff --git a/src/beast/doc/core_types.qbk b/src/beast/doc/types/Streambuf.qbk similarity index 50% rename from src/beast/doc/core_types.qbk rename to src/beast/doc/types/Streambuf.qbk index 1efe460b44..60bf88ed27 100644 --- a/src/beast/doc/core_types.qbk +++ b/src/beast/doc/types/Streambuf.qbk @@ -5,54 +5,32 @@ file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] - - -[section:BufferSequence BufferSequence] - -A `BufferSequence` is a type meeting either of the following requirements: - -* [@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/ConstBufferSequence.html [*`ConstBufferSequence`]] -* [@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/MutableBufferSequence.html [*`MutableBufferSequence`]] - -[endsect] - - - -[section:stream Streams] - -Stream types represent objects capable of performing synchronous or -asynchronous I/O. They are based on concepts from `boost::asio`. - -[heading:Stream Stream] - -A type modeling [*`Stream`] meets either or both of the following requirements: - -* [link beast.types.stream.AsyncStream [*`AsyncStream`]] -* [link beast.types.stream.SyncStream [*`SyncStream`]] - -[heading:AsyncStream AsyncStream] - -A type modeling [*`AsyncStream`] meets the following requirements: - -* [@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/AsyncReadStream.html [*`AsyncReadStream`]] -* [@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/AsyncWriteStream.html [*`AsyncWriteStream`]] - -[heading:SyncStream SyncStream] - -A type modeling [*`SyncStream`] meets the following requirements: - -* [@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/SyncReadStream.html [*`SyncReadStream`]] -* [@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/SyncWriteStream.html [*`SyncWriteStream`]] - -[endsect] - - - [section:Streambuf Streambuf] +A [*`Streambuf`] represents a logical octet sequence divided in two sections, +the input sequence and the output sequence. Octets are written to the output +sequence, then moved to the input sequence where they are available for +reading. When some or all of the input sequence is no longer needed, it may +be consumed. + +The interface to this concept is intended to permit the following +implementation strategies: + +* A single contiguous octet array, which is reallocated as necessary to + accommodate changes in the size of the octet sequence. + +* A sequence of one or more octet arrays, where each array is of the same + size. Additional octet array objects are appended to the sequence to + accommodate changes in the size of the octet sequence. + +* A sequence of one or more octet arrays of varying sizes. Additional octet + array objects are appended to the sequence to accommodate changes in the + size of the character sequence. This is the implementation approached + currently offered by [link beast.ref.basic_streambuf `basic_streambuf`]. + In the table below: -* `X` denotes a class +* `X` denotes a class meeting the requirements of [*`Streambuf`] * `a` denotes a value of type `X` * `n` denotes a value convertible to `std::size_t` * `U`, `T` denote unspecified types. @@ -62,12 +40,12 @@ In the table below: [ [`X::const_buffers_type`] [`T`] - [`T` meets the requirements for `ConstBufferSequence`.] + [`T` meets the requirements for [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/ConstBufferSequence.html `ConstBufferSequence`].] ] [ [`X::mutable_buffers_type`] [`U`] - [`U` meets the requirements for `MutableBufferSequence`.] + [`U` meets the requirements for [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/MutableBufferSequence.html `MutableBufferSequence`].] ] [ [`a.commit(n)`] @@ -98,7 +76,7 @@ In the table below: [ [`a.max_size()`] [`std::size_t`] - [Returns the maximum size of the `Streambuf`.] + [Returns the maximum size of the stream buffer.] ] [ [`read_size_helper(a, n)`] diff --git a/src/beast/doc/types/Streams.qbk b/src/beast/doc/types/Streams.qbk new file mode 100644 index 0000000000..fb0920a106 --- /dev/null +++ b/src/beast/doc/types/Streams.qbk @@ -0,0 +1,34 @@ +[/ + Copyright (c) 2013-2016 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) +] + +[section:streams Streams] + +Stream types represent objects capable of performing synchronous or +asynchronous I/O. They are based on concepts from `boost::asio`. + +[heading:Stream Stream] + +A type modeling [*`Stream`] meets either or both of the following requirements: + +* [*`AsyncStream`] +* [*`SyncStream`] + +[heading:AsyncStream AsyncStream] + +A type modeling [*`AsyncStream`] meets the following requirements: + +* [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/AsyncReadStream.html [*`AsyncReadStream`]] +* [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/AsyncWriteStream.html [*`AsyncWriteStream`]] + +[heading:SyncStream SyncStream] + +A type modeling [*`SyncStream`] meets the following requirements: + +* [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/SyncReadStream.html [*`SyncReadStream`]] +* [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/SyncWriteStream.html [*`SyncWriteStream`]] + +[endsect] diff --git a/src/beast/doc/http_types.qbk b/src/beast/doc/types/Writer.qbk similarity index 60% rename from src/beast/doc/http_types.qbk rename to src/beast/doc/types/Writer.qbk index bf5f6a349a..f27565272f 100644 --- a/src/beast/doc/http_types.qbk +++ b/src/beast/doc/types/Writer.qbk @@ -5,199 +5,6 @@ file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] - - -[section:Body Body] - -In this table: - -* `X` is a type meeting the requirements of [*`Body`]. - -[table Body requirements -[[operation] [type] [semantics, pre/post-conditions]] -[ - [`X::value_type`] - [] - [ - The type of the `message::body` member. - If this is not movable or not copyable, the containing message - will be not movable or not copyable. - ] -] -[ - [`X:value_type{}`] - [] - [`DefaultConstructible`] -] -[ - [`Body::reader`] - [] - [ - If present, a type meeting the requirements of - [link beast.types.Reader [*`Reader`]]. - Provides an implementation to parse the body. - ] -] -[ - [`Body::writer`] - [] - [ - If present, a type meeting the requirements of - [link beast.types.Writer [*`Writer`]]. - Provides an implementation to serialize the body. - ] -] -] - -[endsect] - - - -[section:BufferSequence BufferSequence] - -A `BufferSequence` is a type meeting either of the following requirements: - -* [@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/ConstBufferSequence.html [*`ConstBufferSequence`]] -* [@http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/MutableBufferSequence.html [*`MutableBufferSequence`]] - -[endsect] - - - -[section:Field Field] - -A [*`Field`] represents a single HTTP header field/value pair. - -In this table: - -* `X` denotes a type meeting the requirements of [*`Field`]. -* `a` denotes a value of type `X`. - -[table Field requirements - -[[operation][type][semantics, pre/post-conditions]] -[ - [`a.name()`] - [`boost::string_ref`] - [ - This function returns a value implicitly convertible to - `boost::string_ref` containing the case-insensitve field - name, without leading or trailing white space. - ] -] -[ - [`a.value()`] - [`boost::string_ref`] - [ - This function returns a value implicitly convertible to - `boost::string_ref` containing the value for the field. The - value is considered canonical if there is no leading or - trailing whitespace. - ] -] -] - -[endsect] - - - -[section:FieldSequence FieldSequence] - -A [*`FieldSequence`] is an iterable container whose value type meets -the requirements of [link beast.types.Field [*`Field`]]. - -In this table: - -* `X` denotes a type that meets the requirements of [*`FieldSequence`]. - -* `a` is a value of type `X`. - -[table FieldSequence requirements -[[operation][type][semantics, pre/post-conditions]] -[ - [`X::value_type`] - [] - [ - A type that meets the requirements of `Field`. - ] -] -[ - [`X::const_iterator`] - [] - [ - A type that meets the requirements of `ForwardIterator`. - ] -] -[ - [`a.begin()`] - [`X::const_iterator`] - [ - Returns an iterator to the beginning of the field sequence. - ] -] -[ - [`a.end()`] - [`X::const_iterator`] - [ - Returns an iterator to the end of the field sequence. - ] -] -] - -[endsect] - - - -[section:Reader Reader] - -Parser implementations will construct the corresponding `reader` object -during the parse. This customization point allows the Body to determine -the strategy for storing incoming message body data. - -In this table: - -* `X` denotes a type meeting the requirements of [*`Reader`]. - -* `a` denotes a value of type `X`. - -* `p` is any pointer. - -* `n` is a value convertible to `std::size_t`. - -* `ec` is a value of type `error_code&`. - -* `m` denotes a value of type `message const&` where - `std::is_same:value == true` - - -[table Reader requirements -[[operation] [type] [semantics, pre/post-conditions]] -[ - [`X a(m);`] - [] - [ - `a` is constructible from `m`. The lifetime of `m` is - guaranteed to end no earlier than after `a` is destroyed. - ] -] -[ - [`a.write(p, n, ec)`] - [`void`] - [ - Deserializes the input sequence into the body. - If `ec` is set, the deserialization is aborted and the error - is returned to the caller. - ] -] -] - -[note Definitions for required `Reader` member functions should be declared -inline so the generated code becomes part of the implementation. ] - -[endsect] - - - [section:Writer Writer] A `Writer` serializes the message body. The implementation creates an instance @@ -370,4 +177,3 @@ public: ``` [endsect] - diff --git a/src/beast/doc/websocket.qbk b/src/beast/doc/websocket.qbk index 80eb0b580e..617deb2a5b 100644 --- a/src/beast/doc/websocket.qbk +++ b/src/beast/doc/websocket.qbk @@ -46,13 +46,15 @@ model, handler allocation, and handler invocation hooks. Calls to Beast.WebSocket asynchronous initiation functions allow callers the choice of using a completion handler, stackful or stackless coroutines, futures, or user defined customizations (for example, Boost.Fiber). The -implementation uses handler invocation hooks (`asio_handler_invoke`), +implementation uses handler invocation hooks +([@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/asio_handler_invoke.html `asio_handler_invoke`]), providing execution guarantees on composed operations in a manner -identical to Boost.Asio. The implementation also uses handler allocation -hooks (`asio_handler_allocate`) when allocating memory internally for -composed operations. +identical to Boost.Asio. The implementation also uses handler allocation hooks +([@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/asio_handler_allocate.html `asio_handler_allocate`]) +when allocating memory internally for composed operations. -There is no need for inheritance or virtual members in `websocket::stream`. +There is no need for inheritance or virtual members in a +[link beast.ref.websocket__stream `beast::websocket::stream`]. All operations are templated and transparent to the compiler, allowing for maximum inlining and optimization. @@ -67,43 +69,47 @@ both Boost.Asio and the WebSocket protocol specification described in [section:creating Creating the socket] The interface to Beast's WebSocket implementation is a single template -class [link beast.ref.websocket__stream `websocket::stream`] which wraps a -"next layer" object. The next layer object must meet the requirements of -`SyncReadStream` and `SyncWriteStream` if synchronous operations are performed, -`AsyncReadStream` and `AsyncWriteStream` is asynchronous operations are -performed, or both. Arguments supplied during construction are passed to -next layer's constructor. Here we declare two websockets which have ownership -of the next layer: +class [link beast.ref.websocket__stream `beast::websocket::stream`] which +wraps a "next layer" object. The next layer object must meet the requirements +of [link beast.types.streams.SyncStream [*`SyncReadStream`]] if synchronous +operations are performed, or +[link beast.types.streams.AsyncStream [*`AsyncStream`]] if asynchronous +operations are performed, or both. Arguments supplied during construction are +passed to next layer's constructor. Here we declare two websockets which have +ownership of the next layer: ``` -io_service ios; -websocket::stream ws(ios); +boost::asio::io_service ios; +beast::websocket::stream ws(ios); -ssl::context ctx(ssl::context::sslv23); -websocket::stream> wss(ios, ctx); +boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23); +beast::websocket::stream< + boost::asio::ssl::stream> wss(ios, ctx); ``` For servers that can handshake in multiple protocols, it may be desired to wrap an object that already exists. This socket can be moved in: ``` - tcp::socket&& sock; + boost::asio::ip::tcp::socket&& sock; ... - websocket::stream ws(std::move(sock)); + beast::websocket::stream< + boost::asio::ip::tcp::socket> ws(std::move(sock)); ``` Or, the wrapper can be constructed with a non-owning reference. In this case, the caller is responsible for managing the lifetime of the underlying socket being wrapped: ``` - tcp::socket sock; + boost::asio::ip::tcp::socket sock; ... - websocket::stream ws(sock); + beast::websocket::stream ws(sock); ``` The layer being wrapped can be accessed through the websocket's "next layer", permitting callers to interact directly with its interface. ``` - ssl::context ctx(ssl::context::sslv23); - websocket::stream> ws(ios, ctx); + boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23); + beast::websocket::stream< + boost::asio::ssl::stream> ws(ios, ctx); ... ws.next_layer().shutdown(); // ssl::stream shutdown ``` @@ -122,17 +128,18 @@ Connections are established by using the interfaces which already exist for the next layer. For example, making an outgoing connection: ``` std::string const host = "mywebapp.com"; - io_service ios; - tcp::resolver r(ios); - websocket::stream ws(ios); - connect(ws.next_layer(), r.resolve(tcp::resolver::query{host, "ws"})); + boost::asio::io_service ios; + boost::asio::ip::tcp::resolver r(ios); + beast::websocket::stream ws(ios); + boost::asio::connect(ws.next_layer(), + r.resolve(boost::asio::ip::tcp::resolver::query{host, "ws"})); ``` Accepting an incoming connection: ``` -void do_accept(tcp::acceptor& acceptor) +void do_accept(boost::asio::ip::tcp::acceptor& acceptor) { - websocket::stream ws(acceptor.get_io_service()); + beast::websocket::stream ws(acceptor.get_io_service()); acceptor.accept(ws.next_layer()); } ``` @@ -149,26 +156,26 @@ A WebSocket session begins when one side sends the HTTP Upgrade request for websocket, and the other side sends an appropriate HTTP response indicating that the request was accepted and that the connection has been upgraded. The HTTP Upgrade request must include the Host HTTP field, -and the URI of the resource to request. `hanshake` is used to send the +and the URI of the resource to request. `handshake` is used to send the request with the required host and resource strings. ``` - websocket::stream ws(ios); + beast::websocket::stream ws(ios); ... - ws.set_option(websocket::keep_alive(true)); - ws.handshake("ws.mywebapp.com:80", "/cgi-bin/bitcoin-prices"); + ws.set_option(beast::websocket::keep_alive(true)); + ws.handshake("ws.example.com:80", "/cgi-bin/bitcoin-prices"); ``` -The `websocket::stream` automatically handles receiving and processing -the HTTP response to the handshake request. The call to handshake is -successful if a HTTP response is received with the 101 "Switching Protocols" -status code. On failure, an error is returned or an exception is thrown. -Depending on the keep alive setting, the socket may remain open for a -subsequent handshake attempt +The [link beast.ref.websocket__stream `beast::websocket::stream`] automatically +handles receiving and processing the HTTP response to the handshake request. +The call to handshake is successful if a HTTP response is received with the +101 "Switching Protocols" status code. On failure, an error is returned or an +exception is thrown. Depending on the keep alive setting, the socket may remain +open for a subsequent handshake attempt Performing a handshake for an incoming websocket upgrade request operates similarly. If the handshake fails, an error is returned or exception thrown: ``` - websocket::stream ws(ios); + beast::websocket::stream ws(ios); ... ws.accept(); ``` @@ -178,12 +185,12 @@ on the connection, or might have already received an entire HTTP request containing the upgrade request. Overloads of `accept` allow callers to pass in this additional buffered handshake data. ``` -void do_accept(tcp::socket& sock) +void do_accept(boost::asio::ip::tcp::socket& sock) { boost::asio::streambuf sb; - read_until(sock, sb, "\r\n\r\n"); + boost::asio::read_until(sock, sb, "\r\n\r\n"); ... - websocket::stream ws(sock); + beast::websocket::stream ws(sock); ws.accept(sb.data()); ... } @@ -192,12 +199,12 @@ void do_accept(tcp::socket& sock) Alternatively, the caller can pass an entire HTTP request if it was obtained elsewhere: ``` -void do_accept(tcp::socket& sock) +void do_accept(boost::asio::ip::tcp::socket& sock) { boost::asio::streambuf sb; - http::request request; - http::read(sock, request); - if(http::is_upgrade(request)) + beast::http::request request; + beast::http::read(sock, request); + if(beast::http::is_upgrade(request)) { websocket::stream ws(sock); ws.accept(request); @@ -206,8 +213,6 @@ void do_accept(tcp::socket& sock) } ``` -[note Identifiers in the `http` namespace are part of Beast.HTTP. ] - [endsect] @@ -218,20 +223,21 @@ After the WebSocket handshake is accomplished, callers may send and receive messages using the message oriented interface. This interface requires that all of the buffers representing the message are known ahead of time: ``` -void echo(websocket::stream& ws) +void echo(beast::websocket::stream& ws) { - streambuf sb; - websocket::opcode::value op; + beast::streambuf sb; + beast::websocket::opcode::value op; ws.read(sb); - ws.set_option(websocket::message_type(op)); - websocket::write(ws, sb.data()); + ws.set_option(beast::websocket::message_type(op)); + ws.write(sb.data()); sb.consume(sb.size()); } ``` -[important Calls to `set_option` must be made from the same implicit -or explicit strand as that used to perform other operations. ] +[important Calls to [link beast.ref.websocket__stream.set_option `set_option`] +must be made from the same implicit or explicit strand as that used to perform +other operations. ] [endsect] @@ -249,18 +255,19 @@ message ahead of time: For these cases, the frame oriented interface may be used. This example reads and echoes a complete message using this interface: ``` -void echo(websocket::stream& ws) +void echo(beast::websocket::stream& ws) { - streambuf sb; - websocket::frame_info fi; + beast::streambuf sb; + beast::websocket::frame_info fi; for(;;) { ws.read_frame(fi, sb); if(fi.fin) break; } - ws.set_option(websocket::message_type(fi.op)); - consuming_buffers cb(sb.data()); + ws.set_option(beast::websocket::message_type(fi.op)); + beast::consuming_buffers< + beast::streambuf::const_buffers_type> cb(sb.data()); for(;;) { using boost::asio::buffer_size; @@ -287,10 +294,11 @@ void echo(websocket::stream& ws) During read operations, the implementation automatically reads and processes WebSocket control frames such as ping, pong, and close. Pings are replied -to as soon as possible, pongs are noted. The receipt of a close frame -initiates the WebSocket close procedure, eventually resulting in the -error code `websocket::error::closed` being delivered to the caller in -a subsequent read operation, assuming no other error takes place. +to as soon as possible, pongs are delivered to the pong callback. The receipt +of a close frame initiates the WebSocket close procedure, eventually resulting +in the error code [link beast.ref.websocket__error `error::closed`] being +delivered to the caller in a subsequent read operation, assuming no other error +takes place. To ensure timely delivery of control frames, large messages are broken up into smaller sized frames. The implementation chooses the size and number @@ -298,19 +306,49 @@ of the frames making up the message. The automatic fragment size option gives callers control over the size of these frames: ``` ... - ws.set_option(websocket::auto_fragment_size(8192)); + ws.set_option(beast::websocket::auto_fragment_size(8192)); ``` The WebSocket protocol defines a procedure and control message for initiating a close of the session. Handling of close initiated by the remote end of the connection is performed automatically. To manually initiate a close, use -`websocket::stream::close`: +[link beast.ref.websocket__stream.close `close`]: ``` ws.close(); ``` -[note To receive the `websocket::error::closed` error, a read operation -is required. ] +[note To receive the [link beast.ref.websocket__error `error::closed`] +error, a read operation is required. ] + +[endsect] + + + +[section:pongs Pong messages] + +To receive pong control frames, callers may register a "pong callback" using +[link beast.ref.websocket__stream.set_option `set_option`]: + +the following signature: +``` + void on_pong(ping_data const& payload); + ... + ws.set_option(pong_callback{&on_pong}); +``` + +When a pong callback is registered, any pongs received through either +synchronous read functions or asynchronous read functions will invoke the +pong callback, passing the payload in the pong message as the argument. + +Unlike regular completion handlers used in calls to asynchronous initiation +functions, the pong callback only needs to be set once. The callback is not +reset when a pong is received. The same callback is used for both synchronous +and asynchronous reads. The pong callback is passive; in order to receive +pongs, a synchronous or asynchronous stream read function must be active. + +[note When an asynchronous read function receives a pong, the the pong callback +is invoked in the same manner as that used to invoke the final completion +handler of the corresponding read function.] [endsect] @@ -320,7 +358,8 @@ is required. ] Because calls to read data may return a variable amount of bytes, the interface to calls that read data require an object that meets the requirements -of `Streambuf`. This concept is modeled on `boost::asio::basic_streambuf`. +of [link beast.types.Streambuf [*`Streambuf`]]. This concept is modeled on +[@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/basic_streambuf.html `boost::asio::basic_streambuf`]. The implementation does not perform queueing or buffering of messages. If desired, these features should be provided by callers. The impact of this @@ -331,6 +370,7 @@ of the underlying TCP/IP connection. [endsect] + [section:async Asynchronous interface] Asynchronous versions are available for all functions: @@ -361,12 +401,14 @@ void echo(websocket::stream& ws, -[section:io_service io_service] +[section:io_service The io_service] -The creation and operation of the `boost::asio::io_service` associated with -the underlying stream is left to the callers, permitting any implementation -strategy including one that does not require threads for environments where -threads are unavailable. Beast.WSProto itself does not use or require threads. +The creation and operation of the +[@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/io_service.html `boost::asio::io_service`] +associated with the underlying stream is left to the callers, permitting any +implementation strategy including one that does not require threads for +environments where threads are unavailable. Beast.WebSocket itself does not +use or require threads. [endsect] @@ -374,13 +416,13 @@ threads are unavailable. Beast.WSProto itself does not use or require threads. [section:safety Thread Safety] -Like a regular asio socket, a `websocket::stream` is not thread safe. Callers -are responsible for synchronizing operations on the socket using an implicit -or explicit strand, as per the Asio documentation. The asynchronous interface -supports one active read and one active write simultaneously. Undefined -behavior results if two or more reads or two or more writes are attempted -concurrently. Caller initiated WebSocket ping, pong, and close operations -each count as an active write. +Like a regular asio socket, a [link beast.ref.websocket__stream `stream`] is +not thread safe. Callers are responsible for synchronizing operations on the +socket using an implicit or explicit strand, as per the Asio documentation. +The asynchronous interface supports one active read and one active write +simultaneously. Undefined behavior results if two or more reads or two or +more writes are attempted concurrently. Caller initiated WebSocket ping, pong, +and close operations each count as an active write. The implementation uses composed asynchronous operations internally; a high level read can cause both reads and writes to take place on the underlying diff --git a/src/beast/extras/beast/test/fail_stream.hpp b/src/beast/extras/beast/test/fail_stream.hpp index 594bc7098e..2bbfaf1e14 100644 --- a/src/beast/extras/beast/test/fail_stream.hpp +++ b/src/beast/extras/beast/test/fail_stream.hpp @@ -157,25 +157,35 @@ public: return next_layer_.async_write_some(buffers, std::forward(handler)); } + + friend + void + teardown(fail_stream& stream, + boost::system::error_code& ec) + { + if(stream.pfc_->fail(ec)) + return; + websocket_helpers::call_teardown(stream.next_layer(), ec); + } + + template + friend + void + async_teardown(fail_stream& stream, + TeardownHandler&& handler) + { + error_code ec; + if(stream.pfc_->fail(ec)) + { + stream.get_io_service().post( + bind_handler(std::move(handler), ec)); + return; + } + websocket_helpers::call_async_teardown( + stream.next_layer(), std::forward(handler)); + } }; -template -void -teardown(fail_stream& stream, - boost::system::error_code& ec) -{ - websocket_helpers::call_teardown(stream.next_layer(), ec); -} - -template -void -async_teardown(fail_stream& stream, - TeardownHandler&& handler) -{ - websocket_helpers::call_async_teardown( - stream.next_layer(), std::forward(handler)); -} - } // test } // beast diff --git a/src/beast/extras/beast/unit_test/abstract_ostream.hpp b/src/beast/extras/beast/unit_test/abstract_ostream.hpp deleted file mode 100644 index 8a41841a61..0000000000 --- a/src/beast/extras/beast/unit_test/abstract_ostream.hpp +++ /dev/null @@ -1,20 +0,0 @@ -// -// Copyright (c) 2013-2016 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) -// - -#ifndef BEAST_UNIT_TEST_ABSTRACT_OSTREAM_HPP -#define BEAST_UNIT_TEST_ABSTRACT_OSTREAM_HPP - -#include - -namespace beast { - -/** An abstract ostream for `char`. */ -using abstract_ostream = basic_abstract_ostream ; - -} // beast - -#endif diff --git a/src/beast/extras/beast/unit_test/basic_abstract_ostream.hpp b/src/beast/extras/beast/unit_test/basic_abstract_ostream.hpp deleted file mode 100644 index 6a3a7222b3..0000000000 --- a/src/beast/extras/beast/unit_test/basic_abstract_ostream.hpp +++ /dev/null @@ -1,85 +0,0 @@ -// -// Copyright (c) 2013-2016 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) -// - -#ifndef BEAST_UNIT_TEST_BASIC_ABSTRACT_OSTREAM_HPP -#define BEAST_UNIT_TEST_BASIC_ABSTRACT_OSTREAM_HPP - -#include -#include -#include -#include - -namespace beast { - -/** Abstraction for an output stream similar to std::basic_ostream. */ -template < - class CharT, - class Traits = std::char_traits -> -class basic_abstract_ostream -{ -public: - using string_type = std::basic_string ; - using scoped_stream_type = basic_scoped_ostream ; - - basic_abstract_ostream() = default; - - virtual - ~basic_abstract_ostream() = default; - - basic_abstract_ostream (basic_abstract_ostream const&) = default; - basic_abstract_ostream& operator= ( - basic_abstract_ostream const&) = default; - - /** Returns `true` if the stream is active. - Inactive streams do not produce output. - */ - /** @{ */ - virtual - bool - active() const - { - return true; - } - - explicit - operator bool() const - { - return active(); - } - /** @} */ - - /** Called to output each string. */ - virtual - void - write (string_type const& s) = 0; - - scoped_stream_type - operator<< (std::ostream& manip (std::ostream&)) - { - return scoped_stream_type (manip, - [this](string_type const& s) - { - this->write (s); - }); - } - - template - scoped_stream_type - operator<< (T const& t) - { - return scoped_stream_type (t, - [this](string_type const& s) - { - this->write (s); - }); - } -}; - -} // beast - -#endif diff --git a/src/beast/extras/beast/unit_test/basic_scoped_ostream.hpp b/src/beast/extras/beast/unit_test/basic_scoped_ostream.hpp deleted file mode 100644 index c8283a521b..0000000000 --- a/src/beast/extras/beast/unit_test/basic_scoped_ostream.hpp +++ /dev/null @@ -1,136 +0,0 @@ -// -// Copyright (c) 2013-2016 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) -// - -#ifndef BEAST_UNIT_TEST_BASIC_SCOPED_OSTREAM_HPP -#define BEAST_UNIT_TEST_BASIC_SCOPED_OSTREAM_HPP - -#include -#include -#include - -// gcc libstd++ doesn't have move constructors for basic_ostringstream -// http://gcc.gnu.org/bugzilla/show_bug.cgi?id=54316 -// -#ifndef BEAST_NO_STDLIB_STREAM_MOVE -# ifdef __GLIBCXX__ -# define BEAST_NO_STDLIB_STREAM_MOVE 1 -# else -# define BEAST_NO_STDLIB_STREAM_MOVE 0 -# endif -#endif - -namespace beast { - -template < - class CharT, - class Traits -> -class basic_abstract_ostream; - -/** Scoped output stream that forwards to a functor upon destruction. */ -template < - class CharT, - class Traits = std::char_traits , - class Allocator = std::allocator -> -class basic_scoped_ostream -{ -private: - using handler_t = std::function const&)>; - - using stream_type = std::basic_ostringstream < - CharT, Traits, Allocator>; - - handler_t m_handler; - -#if BEAST_NO_STDLIB_STREAM_MOVE - std::unique_ptr m_ss; - - stream_type& stream() - { - return *m_ss; - } - -#else - stream_type m_ss; - - stream_type& stream() - { - return m_ss; - } - -#endif - -public: - using string_type = std::basic_string ; - - // Disallow copy since that would duplicate the output - basic_scoped_ostream (basic_scoped_ostream const&) = delete; - basic_scoped_ostream& operator= (basic_scoped_ostream const) = delete; - - template - explicit basic_scoped_ostream (Handler&& handler) - : m_handler (std::forward (handler)) - #if BEAST_NO_STDLIB_STREAM_MOVE - , m_ss (new stream_type()) - #endif - { - } - - template - basic_scoped_ostream (T const& t, Handler&& handler) - : m_handler (std::forward (handler)) - #if BEAST_NO_STDLIB_STREAM_MOVE - , m_ss (new stream_type()) - #endif - { - stream() << t; - } - - basic_scoped_ostream (basic_abstract_ostream < - CharT, Traits>& ostream) - : m_handler ( - [&](string_type const& s) - { - ostream.write (s); - }) - { - } - - basic_scoped_ostream (basic_scoped_ostream&& other) - : m_handler (std::move (other.m_handler)) - , m_ss (std::move (other.m_ss)) - { - } - - ~basic_scoped_ostream() - { - auto const& s (stream().str()); - if (! s.empty()) - m_handler (s); - } - - basic_scoped_ostream& - operator<< (std::ostream& manip (std::ostream&)) - { - stream() << manip; - return *this; - } - - template - basic_scoped_ostream& - operator<< (T const& t) - { - stream() << t; - return *this; - } -}; - -} // beast - -#endif diff --git a/src/beast/extras/beast/unit_test/basic_std_ostream.hpp b/src/beast/extras/beast/unit_test/basic_std_ostream.hpp deleted file mode 100644 index 62b21966ab..0000000000 --- a/src/beast/extras/beast/unit_test/basic_std_ostream.hpp +++ /dev/null @@ -1,60 +0,0 @@ -// -// Copyright (c) 2013-2016 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) -// - -#ifndef BEAST_UNIT_TEST_BASIC_STD_OSTREAM_HPP -#define BEAST_UNIT_TEST_BASIC_STD_OSTREAM_HPP - -#include -#include - -namespace beast { - -/** Wraps an existing std::basic_ostream as an abstract_ostream. */ -template < - class CharT, - class Traits = std::char_traits -> -class basic_std_ostream - : public basic_abstract_ostream -{ -private: - using typename basic_abstract_ostream ::string_type; - - std::reference_wrapper m_stream; - -public: - explicit basic_std_ostream ( - std::basic_ostream & stream) - : m_stream (stream) - { - } - - void - write (string_type const& s) override - { - m_stream.get() << s << std::endl; - } -}; - -using std_ostream = basic_std_ostream ; - -//------------------------------------------------------------------------------ - -/** Returns a basic_std_ostream using template argument deduction. */ -template < - class CharT, - class Traits = std::char_traits -> -basic_std_ostream -make_std_ostream (std::basic_ostream & stream) -{ - return basic_std_ostream (stream); -} - -} // beast - -#endif diff --git a/src/beast/extras/beast/unit_test/debug_ostream.hpp b/src/beast/extras/beast/unit_test/debug_ostream.hpp deleted file mode 100644 index 60d19f8ef2..0000000000 --- a/src/beast/extras/beast/unit_test/debug_ostream.hpp +++ /dev/null @@ -1,78 +0,0 @@ -// -// Copyright (c) 2013-2016 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) -// - -#ifndef BEAST_UNIT_TEST_DEBUG_OSTREAM_HPP -#define BEAST_UNIT_TEST_DEBUG_OSTREAM_HPP - -#include -#include - -#ifdef _MSC_VER -# ifndef WIN32_LEAN_AND_MEAN // VC_EXTRALEAN -# define WIN32_LEAN_AND_MEAN -#include -# undef WIN32_LEAN_AND_MEAN -# else -#include -# endif -# ifdef min -# undef min -# endif -# ifdef max -# undef max -# endif -#endif - -namespace beast { - -#ifdef _MSC_VER -/** A basic_abstract_ostream that redirects output to an attached debugger. */ -class debug_ostream - : public abstract_ostream -{ -private: - bool m_debugger; - -public: - debug_ostream() - : m_debugger (IsDebuggerPresent() != FALSE) - { - // Note that the check for an attached debugger is made only - // during construction time, for efficiency. A stream created before - // the debugger is attached will not have output redirected. - } - - void - write (string_type const& s) override - { - if (m_debugger) - { - OutputDebugStringA ((s + "\n").c_str()); - return; - } - - std::cout << s << std::endl; - } -}; - -#else -class debug_ostream - : public abstract_ostream -{ -public: - void - write (string_type const& s) override - { - std::cout << s << std::endl; - } -}; - -#endif - -} // beast - -#endif diff --git a/src/beast/extras/beast/unit_test/dstream.hpp b/src/beast/extras/beast/unit_test/dstream.hpp new file mode 100644 index 0000000000..2bd8e59032 --- /dev/null +++ b/src/beast/extras/beast/unit_test/dstream.hpp @@ -0,0 +1,144 @@ +// +// Copyright (c) 2013-2016 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) +// + +#ifndef BEAST_UNIT_TEST_DSTREAM_HPP +#define BEAST_UNIT_TEST_DSTREAM_HPP + +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +# ifndef NOMINMAX +# define NOMINMAX 1 +# endif +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# include +# undef WIN32_LEAN_AND_MEAN +# undef NOMINMAX +#endif + +namespace beast { +namespace unit_test { + +namespace detail { + +#ifdef _MSC_VER + +template +class dstream_buf + : public std::basic_stringbuf +{ + bool dbg_; + + template + void write(T const*) = delete; + + void write(char const* s) + { + if(dbg_) + OutputDebugStringA(s); + else + std::cout << s; + } + + void write(wchar_t const* s) + { + if(dbg_) + OutputDebugStringW(s); + else + std::wcout << s; + } + +public: + dstream_buf() + : dbg_(IsDebuggerPresent() != FALSE) + { + } + + ~dstream_buf() + { + sync(); + } + + int + sync() override + { + write(this->str().c_str()); + this->str(""); + return 0; + } +}; + +#else + +template +class dstream_buf + : public std::basic_stringbuf +{ + template + void write(T const*) = delete; + + void write(char const* s) + { + std::cout << s; + } + + void write(wchar_t const* s) + { + std::wcout << s; + } + +public: + ~dstream_buf() + { + sync(); + } + + int + sync() override + { + write(this->str().c_str()); + this->str(""); + return 0; + } +}; + +#endif + +} // detail + +/// A std::ostream that redirects output to the debugger if attached. +template< + class CharT, + class Traits = std::char_traits, + class Allocator = std::allocator +> +class basic_dstream + : private boost::base_from_member< + detail::dstream_buf> + , public std::basic_ostream +{ +public: + basic_dstream() + : std::basic_ostream(&this->member) + { + } +}; + +using dstream = basic_dstream; +using dwstream = basic_dstream; + +} // test +} // beast + +#endif diff --git a/src/beast/extras/beast/unit_test/global_suites.hpp b/src/beast/extras/beast/unit_test/global_suites.hpp index c1f8525210..f6e1ab2cf1 100644 --- a/src/beast/extras/beast/unit_test/global_suites.hpp +++ b/src/beast/extras/beast/unit_test/global_suites.hpp @@ -15,7 +15,8 @@ namespace unit_test { namespace detail { -template +/// Holds test suites registered during static initialization. +inline suite_list& global_suites() { @@ -23,26 +24,20 @@ global_suites() return s; } -template +template struct insert_suite { - template - insert_suite (char const* name, char const* module, - char const* library, bool manual); + insert_suite(char const* name, char const* module, + char const* library, bool manual) + { + global_suites().insert( + name, module, library, manual); + } }; -template -template -insert_suite::insert_suite (char const* name, - char const* module, char const* library, bool manual) -{ - global_suites().insert ( - name, module, library, manual); -} - } // detail -/** Holds suites registered during static initialization. */ +/// Holds test suites registered during static initialization. inline suite_list const& global_suites() diff --git a/src/beast/extras/beast/unit_test/main.cpp b/src/beast/extras/beast/unit_test/main.cpp index 8cdf803ed9..d461ea83b3 100644 --- a/src/beast/extras/beast/unit_test/main.cpp +++ b/src/beast/extras/beast/unit_test/main.cpp @@ -6,12 +6,13 @@ // #include +#include #include #include #include #include -#include #include +#include #include #include @@ -25,11 +26,10 @@ # endif #endif -#include - namespace beast { namespace unit_test { +static std::string prefix(suite_info const& s) { @@ -38,32 +38,34 @@ prefix(suite_info const& s) return " "; } -template +static void -print(Log& log, suite_list const& c) +print(std::ostream& os, suite_list const& c) { std::size_t manual = 0; for(auto const& s : c) { - log << - prefix (s) << - s.full_name(); + os << prefix (s) << s.full_name() << '\n'; if(s.manual()) ++manual; } - log << + os << amount(c.size(), "suite") << " total, " << - amount(manual, "manual suite") + amount(manual, "manual suite") << + '\n' ; } -template +// Print the list of suites +// Used with the --print command line option +static void -print(Log& log) +print(std::ostream& os) { - log << "------------------------------------------"; - print(log, global_suites()); - log << "------------------------------------------"; + os << "------------------------------------------\n"; + print(os, global_suites()); + os << "------------------------------------------" << + std::endl; } } // unit_test @@ -97,11 +99,11 @@ int main(int ac, char const* av[]) po::store(po::parse_command_line(ac, av, desc), vm); po::notify(vm); - beast::debug_ostream log; + dstream log; if(vm.count("help")) { - log << desc; + log << desc << std::endl; } else if(vm.count("print")) { diff --git a/src/beast/extras/beast/unit_test/print.hpp b/src/beast/extras/beast/unit_test/print.hpp deleted file mode 100644 index 0651e64bb9..0000000000 --- a/src/beast/extras/beast/unit_test/print.hpp +++ /dev/null @@ -1,67 +0,0 @@ -// -// Copyright (c) 2013-2016 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) -// - -#ifndef BEAST_UNIT_TEST_PRINT_H_INCLUDED -#define BEAST_UNIT_TEST_PRINT_H_INCLUDED - -#include -#include -#include -#include - -#include -#include - -namespace beast { -namespace unit_test { - -/** Write test results to the specified output stream. */ -/** @{ */ -template -void -print (results const& r, abstract_ostream& stream) -{ - for (auto const& s : r) - { - for (auto const& c : s) - { - stream << - s.name() << - (c.name().empty() ? "" : ("." + c.name())); - - std::size_t i (1); - for (auto const& t : c.tests) - { - if (! t.pass) - stream << - "#" << i << - " failed: " << t.reason; - ++i; - } - } - } - - stream << - amount (r.size(), "suite") << ", " << - amount (r.cases(), "case") << ", " << - amount (r.total(), "test") << " total, " << - amount (r.failed(), "failure") - ; -} - -template -void -print (results const& r, std::ostream& stream = std::cout) -{ - auto s (make_std_ostream (stream)); - print (r, s); -} - -} // unit_test -} // beast - -#endif diff --git a/src/beast/extras/beast/unit_test/reporter.hpp b/src/beast/extras/beast/unit_test/reporter.hpp index b5e27ef454..2d155946c9 100644 --- a/src/beast/extras/beast/unit_test/reporter.hpp +++ b/src/beast/extras/beast/unit_test/reporter.hpp @@ -10,8 +10,6 @@ #include #include -#include -#include #include #include #include @@ -30,7 +28,7 @@ namespace detail { /** A simple test runner that writes everything to a stream in real time. The totals are output when the object is destroyed. */ -template +template class reporter : public runner { private: @@ -42,7 +40,11 @@ private: std::size_t total = 0; std::size_t failed = 0; - case_results (std::string const& name_ = ""); + explicit + case_results(std::string name_ = "") + : name(std::move(name_)) + { + } }; struct suite_results @@ -51,14 +53,16 @@ private: std::size_t cases = 0; std::size_t total = 0; std::size_t failed = 0; - typename clock_type::time_point start = - clock_type::now(); + typename clock_type::time_point start = clock_type::now(); explicit - suite_results (std::string const& name_ = ""); + suite_results(std::string const& name_ = "") + : name(std::move(name_)) + { + } void - add (case_results const& r); + add(case_results const& r); }; struct results @@ -76,35 +80,30 @@ private: std::size_t total = 0; std::size_t failed = 0; std::vector top; - typename clock_type::time_point start = - clock_type::now(); + typename clock_type::time_point start = clock_type::now(); void add (suite_results const& r); }; - boost::optional std_ostream_; - std::reference_wrapper stream_; + std::ostream& os_; results results_; suite_results suite_results_; case_results case_results_; public: - reporter (reporter const&) = delete; - reporter& operator= (reporter const&) = delete; + reporter(reporter const&) = delete; + reporter& operator=(reporter const&) = delete; ~reporter(); explicit - reporter (std::ostream& stream = std::cout); - - explicit - reporter (beast::abstract_ostream& stream); + reporter(std::ostream& os = std::cout); private: static std::string - fmtdur (typename clock_type::duration const& d); + fmtdur(typename clock_type::duration const& d); virtual void @@ -137,42 +136,27 @@ private: //------------------------------------------------------------------------------ -template -reporter<_>::case_results::case_results ( - std::string const& name_) - : name (name_) -{ -} - -template -reporter<_>::suite_results::suite_results ( - std::string const& name_) - : name (name_) -{ -} - -template +template void -reporter<_>::suite_results::add (case_results const& r) +reporter<_>:: +suite_results::add(case_results const& r) { ++cases; total += r.total; failed += r.failed; } -template +template void -reporter<_>::results::add ( - suite_results const& r) +reporter<_>:: +results::add(suite_results const& r) { ++suites; total += r.total; cases += r.cases; failed += r.failed; - - auto const elapsed = - clock_type::now() - r.start; - if (elapsed >= std::chrono::seconds(1)) + auto const elapsed = clock_type::now() - r.start; + if (elapsed >= std::chrono::seconds{1}) { auto const iter = std::lower_bound(top.begin(), top.end(), elapsed, @@ -196,50 +180,40 @@ reporter<_>::results::add ( //------------------------------------------------------------------------------ -template -reporter<_>::reporter ( - std::ostream& stream) - : std_ostream_ (std::ref (stream)) - , stream_ (*std_ostream_) +template +reporter<_>:: +reporter(std::ostream& os) + : os_(os) { } -template +template reporter<_>::~reporter() { - if (results_.top.size() > 0) + if(results_.top.size() > 0) { - stream_.get() << "Longest suite times:"; - for (auto const& i : results_.top) - stream_.get() << std::setw(8) << - fmtdur(i.second) << " " << i.first; + os_ << "Longest suite times:\n"; + for(auto const& i : results_.top) + os_ << std::setw(8) << + fmtdur(i.second) << " " << i.first << '\n'; } - auto const elapsed = - clock_type::now() - results_.start; - stream_.get() << + auto const elapsed = clock_type::now() - results_.start; + os_ << fmtdur(elapsed) << ", " << - amount (results_.suites, "suite") << ", " << - amount (results_.cases, "case") << ", " << - amount (results_.total, "test") << " total, " << - amount (results_.failed, "failure"); + amount{results_.suites, "suite"} << ", " << + amount{results_.cases, "case"} << ", " << + amount{results_.total, "test"} << " total, " << + amount{results_.failed, "failure"} << + std::endl; } -template -reporter<_>::reporter ( - abstract_ostream& stream) - : stream_ (stream) -{ -} - -template +template std::string -reporter<_>::fmtdur ( - typename clock_type::duration const& d) +reporter<_>::fmtdur(typename clock_type::duration const& d) { using namespace std::chrono; - auto const ms = - duration_cast(d); - if (ms < seconds(1)) + auto const ms = duration_cast(d); + if (ms < seconds{1}) return std::to_string(ms.count()) + "ms"; std::stringstream ss; ss << std::fixed << std::setprecision(1) << @@ -247,67 +221,68 @@ reporter<_>::fmtdur ( return ss.str(); } -template +template void -reporter<_>::on_suite_begin ( - suite_info const& info) +reporter<_>:: +on_suite_begin(suite_info const& info) { - suite_results_ = suite_results (info.full_name()); + suite_results_ = suite_results{info.full_name()}; } -template +template void reporter<_>::on_suite_end() { - results_.add (suite_results_); + results_.add(suite_results_); } -template +template void -reporter<_>::on_case_begin ( - std::string const& name) +reporter<_>:: +on_case_begin(std::string const& name) { case_results_ = case_results (name); - - stream_.get() << + os_ << suite_results_.name << (case_results_.name.empty() ? - "" : (" " + case_results_.name)); + "" : (" " + case_results_.name)) << std::endl; } -template +template void -reporter<_>::on_case_end() +reporter<_>:: +on_case_end() { - suite_results_.add (case_results_); + suite_results_.add(case_results_); } -template +template void -reporter<_>::on_pass() +reporter<_>:: +on_pass() { ++case_results_.total; } -template +template void -reporter<_>::on_fail ( - std::string const& reason) +reporter<_>:: +on_fail(std::string const& reason) { ++case_results_.failed; ++case_results_.total; - stream_.get() << - "#" << case_results_.total << - " failed" << - (reason.empty() ? "" : ": ") << reason; + os_ << + "#" << case_results_.total << " failed" << + (reason.empty() ? "" : ": ") << reason << std::endl; } -template +template void -reporter<_>::on_log ( - std::string const& s) +reporter<_>:: +on_log(std::string const& s) { - stream_.get() << s; + os_ << s; + os_.flush(); } } // detail diff --git a/src/beast/extras/beast/unit_test/runner.hpp b/src/beast/extras/beast/unit_test/runner.hpp index 1ff085df8e..038985659d 100644 --- a/src/beast/extras/beast/unit_test/runner.hpp +++ b/src/beast/extras/beast/unit_test/runner.hpp @@ -9,9 +9,9 @@ #define BEAST_UNIT_TEST_RUNNER_H_INCLUDED #include -#include #include #include +#include #include namespace beast { @@ -23,28 +23,6 @@ namespace unit_test { */ class runner { -private: - // Reroutes log output to the runner - class stream_t : public abstract_ostream - { - private: - runner& owner_; - - public: - stream_t() = delete; - stream_t& operator= (stream_t const&) = delete; - - template - stream_t (runner& owner); - - void - write (string_type const& s) override - { - owner_.log (s); - } - }; - - stream_t stream_; std::string arg_; bool default_ = false; bool failed_ = false; @@ -52,21 +30,20 @@ private: std::recursive_mutex mutex_; public: + runner() = default; virtual ~runner() = default; - runner (runner const&) = default; - runner& operator= (runner const&) = default; - - template - runner(); + runner(runner const&) = delete; + runner& operator=(runner const&) = delete; /** Set the argument string. + The argument string is available to suites and allows for customization of the test. Each suite defines its own syntax for the argumnet string. The same argument is passed to all suites. */ void - arg (std::string const& s) + arg(std::string const& s) { arg_ = s; } @@ -81,7 +58,7 @@ public: /** Run the specified suite. @return `true` if any conditions failed. */ - template + template bool run (suite_info const& s); @@ -124,69 +101,63 @@ public: bool run_each_if (SequenceContainer const& c, Pred pred = Pred{}); -private: +protected: // // Overrides // - /** Called when a new suite starts. */ + /// Called when a new suite starts. virtual void - on_suite_begin (suite_info const&) + on_suite_begin(suite_info const&) { } - /** Called when a suite ends. */ + /// Called when a suite ends. virtual void on_suite_end() { } - /** Called when a new case starts. */ + /// Called when a new case starts. virtual void - on_case_begin (std::string const&) + on_case_begin(std::string const&) { } - /** Called when a new case ends. */ + /// Called when a new case ends. virtual void on_case_end() { } - /** Called for each passing condition. */ + /// Called for each passing condition. virtual void - on_pass () + on_pass() { } - /** Called for each failing condition. */ + /// Called for each failing condition. virtual void - on_fail (std::string const&) + on_fail(std::string const&) { } - /** Called when a test logs output. */ + /// Called when a test logs output. virtual void - on_log (std::string const&) + on_log(std::string const&) { } private: friend class suite; - abstract_ostream& - stream() - { - return stream_; - } - // Start a new testcase. template void @@ -207,20 +178,6 @@ private: //------------------------------------------------------------------------------ -template -runner::stream_t::stream_t (runner& owner) - : owner_ (owner) -{ -} - -//------------------------------------------------------------------------------ - -template -runner::runner() - : stream_ (*this) -{ -} - template bool runner::run (suite_info const& s) diff --git a/src/beast/extras/beast/unit_test/suite.hpp b/src/beast/extras/beast/unit_test/suite.hpp index 6ffa6eb227..476c0b0aac 100644 --- a/src/beast/extras/beast/unit_test/suite.hpp +++ b/src/beast/extras/beast/unit_test/suite.hpp @@ -9,15 +9,23 @@ #define BEAST_UNIT_TEST_SUITE_HPP #include -#include +#include #include +#include namespace beast { namespace unit_test { class thread; +enum abort_t +{ + no_abort_on_fail, + abort_on_fail +}; + /** A testsuite class. + Derived classes execute a series of testcases, where each testcase is a series of pass/fail tests. To provide a unit test using this class, derive from it and use the BEAST_DEFINE_UNIT_TEST macro in a @@ -25,13 +33,6 @@ class thread; */ class suite { -public: - enum abort_t - { - no_abort_on_fail, - abort_on_fail - }; - private: bool abort_ = false; bool aborted_ = false; @@ -44,95 +45,100 @@ private: char const* what() const noexcept override { - return "suite aborted"; + return "test suite aborted"; } }; -public: - // Memberspace - class log_t + template + class log_buf + : public std::basic_stringbuf { - private: - friend class suite; - suite* suite_ = nullptr; + suite& suite_; public: - log_t () = default; + explicit + log_buf(suite& self) + : suite_(self) + { + } - template - abstract_ostream::scoped_stream_type - operator<< (T const& t); + ~log_buf() + { + sync(); + } - /** Returns the raw stream used for output. */ - abstract_ostream& - stream(); + int + sync() override + { + auto const& s = this->str(); + if(s.size() > 0) + suite_.runner_->on_log(s); + this->str(""); + return 0; + } + }; + + template< + class CharT, + class Traits = std::char_traits, + class Allocator = std::allocator + > + class log_os : public std::basic_ostream + { + log_buf buf_; + + public: + explicit + log_os(suite& self) + : std::basic_ostream(&buf_) + , buf_(self) + { + } }; -private: class scoped_testcase; - // Memberspace class testcase_t { - private: - friend class suite; - suite* suite_ = nullptr; + suite& suite_; std::stringstream ss_; public: - testcase_t() = default; + explicit + testcase_t(suite& self) + : suite_(self) + { + } /** Open a new testcase. - A testcase is a series of evaluated test conditions. A test suite - may have multiple test cases. A test is associated with the last - opened testcase. When the test first runs, a default unnamed - case is opened. Tests with only one case may omit the call - to testcase. - @param abort If `true`, the suite will be stopped on first failure. + + A testcase is a series of evaluated test conditions. A test + suite may have multiple test cases. A test is associated with + the last opened testcase. When the test first runs, a default + unnamed case is opened. Tests with only one case may omit the + call to testcase. + + @param abort Determines if suite continues running after a failure. */ void - operator() (std::string const& name, + operator()(std::string const& name, abort_t abort = no_abort_on_fail); - /** Stream style composition of testcase names. */ - /** @{ */ scoped_testcase - operator() (abort_t abort); + operator()(abort_t abort); template scoped_testcase - operator<< (T const& t); - /** @} */ + operator<<(T const& t); }; public: - /** Type for scoped stream logging. - To use this type, declare a local variable of the type - on the stack in derived class member function and construct - it from log.stream(); + /** Logging output stream. - @code - - scoped_stream ss (log.stream(); - - ss << "Hello" << std::endl; - ss << "world" << std::endl; - - @endcode - - Streams constructed in this fashion will not have the line - ending automatically appended. - - Thread safety: - - The scoped_stream may only be used by one thread. - Multiline output sent to the stream will be atomically - written to the underlying abstract_Ostream + Text sent to the log output stream will be forwarded to + the output stream associated with the runner. */ - using scoped_stream = abstract_ostream::scoped_stream_type; - - /** Memberspace for logging. */ - log_t log; + log_os log; /** Memberspace for declaring test cases. */ testcase_t testcase; @@ -145,6 +151,12 @@ public: return *p_this_suite(); } + suite() + : log(*this) + , testcase(*this) + { + } + /** Invokes the test using the specified runner. Data members are set up here instead of the constructor as a convenience to writing the derived class to avoid repetition of @@ -272,84 +284,51 @@ private: //------------------------------------------------------------------------------ -template -inline -abstract_ostream::scoped_stream_type -suite::log_t::operator<< (T const& t) -{ - return suite_->runner_->stream() << t; -} - -/** Returns the raw stream used for output. */ -inline -abstract_ostream& -suite::log_t::stream() -{ - return suite_->runner_->stream(); -} - -//------------------------------------------------------------------------------ - // Helper for streaming testcase names class suite::scoped_testcase { private: - suite* suite_; - std::stringstream* ss_; + suite& suite_; + std::stringstream& ss_; public: - ~scoped_testcase(); + scoped_testcase& operator=(scoped_testcase const&) = delete; - scoped_testcase (suite* s, std::stringstream* ss); + ~scoped_testcase() + { + auto const& name = ss_.str(); + if(! name.empty()) + suite_.runner_->testcase (name); + } - template - scoped_testcase (suite* s, std::stringstream* ss, T const& t); + scoped_testcase(suite& self, std::stringstream& ss) + : suite_(self) + , ss_(ss) + { + ss_.clear(); + ss_.str({}); + } - scoped_testcase& operator= (scoped_testcase const&) = delete; + template + scoped_testcase(suite& self, + std::stringstream& ss, T const& t) + : suite_(self) + , ss_(ss) + { + ss_.clear(); + ss_.str({}); + ss_ << t; + } - template + template scoped_testcase& - operator<< (T const& t); + operator<<(T const& t) + { + ss_ << t; + return *this; + } }; -inline -suite::scoped_testcase::~scoped_testcase() -{ - auto const& name (ss_->str()); - if (! name.empty()) - suite_->runner_->testcase (name); -} - -inline -suite::scoped_testcase::scoped_testcase (suite* s, std::stringstream* ss) - : suite_ (s) - , ss_ (ss) -{ - ss_->clear(); - ss_->str({}); - -} - -template -inline -suite::scoped_testcase::scoped_testcase (suite* s, std::stringstream* ss, T const& t) - : suite_ (s) - , ss_ (ss) -{ - ss_->clear(); - ss_->str({}); - *ss_ << t; -} - -template -inline -suite::scoped_testcase& -suite::scoped_testcase::operator<< (T const& t) -{ - *ss_ << t; - return *this; -} - //------------------------------------------------------------------------------ inline @@ -357,16 +336,16 @@ void suite::testcase_t::operator() (std::string const& name, abort_t abort) { - suite_->abort_ = abort == abort_on_fail; - suite_->runner_->testcase (name); + suite_.abort_ = abort == abort_on_fail; + suite_.runner_->testcase (name); } inline suite::scoped_testcase suite::testcase_t::operator() (abort_t abort) { - suite_->abort_ = abort == abort_on_fail; - return { suite_, &ss_ }; + suite_.abort_ = abort == abort_on_fail; + return { suite_, ss_ }; } template @@ -374,7 +353,7 @@ inline suite::scoped_testcase suite::testcase_t::operator<< (T const& t) { - return { suite_, &ss_, t }; + return { suite_, ss_, t }; } //------------------------------------------------------------------------------ @@ -511,8 +490,6 @@ void suite::run (runner& r) { runner_ = &r; - log.suite_ = this; - testcase.suite_ = this; try { diff --git a/src/beast/extras/beast/unit_test/suite_info.hpp b/src/beast/extras/beast/unit_test/suite_info.hpp index 530ac008a1..428d81ee13 100644 --- a/src/beast/extras/beast/unit_test/suite_info.hpp +++ b/src/beast/extras/beast/unit_test/suite_info.hpp @@ -8,6 +8,7 @@ #ifndef BEAST_UNIT_TEST_SUITE_INFO_HPP #define BEAST_UNIT_TEST_SUITE_INFO_HPP +#include #include #include #include @@ -20,19 +21,28 @@ class runner; /** Associates a unit test type with metadata. */ class suite_info { -private: - using run_type = std::function ; + using run_type = std::function; std::string name_; std::string module_; std::string library_; - bool m_manual; - run_type m_run; + bool manual_; + run_type run_; public: - template - suite_info (std::string const& name, std::string const& module, - std::string const& library, bool manual, run_type run); + suite_info( + std::string name, + std::string module, + std::string library, + bool manual, + run_type run) + : name_(std::move(name)) + , module_(std::move(module)) + , library_(std::move(library)) + , manual_(manual) + , run_(std::move(run)) + { + } std::string const& name() const @@ -52,61 +62,58 @@ public: return library_; } - /** Returns `true` if this suite only runs manually. */ + /// Returns `true` if this suite only runs manually. bool manual() const { - return m_manual; + return manual_; } - /** Return the canonical suite name as a string. */ - template + /// Return the canonical suite name as a string. std::string - full_name() const; - - /** Run a new instance of the associated test suite. */ - void - run (runner& r) const + full_name() const { - m_run (r); + return library_ + "." + module_ + "." + name_; + } + + /// Run a new instance of the associated test suite. + void + run(runner& r) const + { + run_ (r); + } + + friend + bool + operator<(suite_info const& lhs, suite_info const& rhs) + { + return + std::tie(lhs.library_, lhs.module_, lhs.name_) < + std::tie(rhs.library_, rhs.module_, rhs.name_); } }; //------------------------------------------------------------------------------ -template -suite_info::suite_info (std::string const& name, std::string const& module, - std::string const& library, bool manual, run_type run) - : name_ (name) - , module_ (module) - , library_ (library) - , m_manual (manual) - , m_run (std::move (run)) -{ -} - -template -std::string -suite_info::full_name() const -{ - return library_ + "." + module_ + "." + name_; -} - -inline -bool -operator< (suite_info const& lhs, suite_info const& rhs) -{ - return lhs.full_name() < rhs.full_name(); -} - -/** Convenience for producing suite_info for a given test type. */ -template +/// Convenience for producing suite_info for a given test type. +template suite_info -make_suite_info (std::string const& name, std::string const& module, - std::string const& library, bool manual) +make_suite_info( + std::string name, + std::string module, + std::string library, + bool manual) { - return suite_info(name, module, library, manual, - [](runner& r) { return Suite{}(r); }); + return suite_info( + std::move(name), + std::move(module), + std::move(library), + manual, + [](runner& r) + { + Suite{}(r); + } + ); } } // unit_test diff --git a/src/beast/extras/beast/unit_test/suite_list.hpp b/src/beast/extras/beast/unit_test/suite_list.hpp index 5feacf26d7..d8f62c5df6 100644 --- a/src/beast/extras/beast/unit_test/suite_list.hpp +++ b/src/beast/extras/beast/unit_test/suite_list.hpp @@ -18,23 +18,27 @@ namespace beast { namespace unit_test { -/** A container of test suites. */ +/// A container of test suites. class suite_list : public detail::const_container > { private: #ifndef NDEBUG - std::unordered_set names_; - std::unordered_set classes_; + std::unordered_set names_; + std::unordered_set classes_; #endif public: /** Insert a suite into the set. + The suite must not already exist. */ template void - insert (char const* name, char const* module, char const* library, + insert( + char const* name, + char const* module, + char const* library, bool manual); }; @@ -42,7 +46,10 @@ public: template void -suite_list::insert (char const* name, char const* module, char const* library, +suite_list::insert( + char const* name, + char const* module, + char const* library, bool manual) { #ifndef NDEBUG @@ -59,9 +66,8 @@ suite_list::insert (char const* name, char const* module, char const* library, assert (result.second); // Duplicate type } #endif - - cont().emplace (std::move (make_suite_info ( - name, module, library, manual))); + cont().emplace(make_suite_info( + name, module, library, manual)); } } // unit_test diff --git a/src/beast/include/beast/core/detail/temp_dir.hpp b/src/beast/include/beast/core/detail/temp_dir.hpp deleted file mode 100644 index 54a0d905a0..0000000000 --- a/src/beast/include/beast/core/detail/temp_dir.hpp +++ /dev/null @@ -1,73 +0,0 @@ -// -// Copyright (c) 2013-2016 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) -// - -#ifndef BEAST_DETAIL_TEMP_DIR_H_INCLUDED -#define BEAST_DETAIL_TEMP_DIR_H_INCLUDED - -#include -#include - -namespace beast { -namespace detail { - -/** RAII temporary directory. - - The directory and all its contents are deleted when - the instance of `temp_dir` is destroyed. -*/ -class temp_dir -{ - boost::filesystem::path path_; - -public: -#if ! GENERATING_DOCS - temp_dir(const temp_dir&) = delete; - temp_dir& operator=(const temp_dir&) = delete; -#endif - - /// Construct a temporary directory. - temp_dir() - { - auto const dir = - boost::filesystem::temp_directory_path(); - do - { - path_ = - dir / boost::filesystem::unique_path(); - } - while(boost::filesystem::exists(path_)); - boost::filesystem::create_directory (path_); - } - - /// Destroy a temporary directory. - ~temp_dir() - { - boost::filesystem::remove_all (path_); - } - - /// Get the native path for the temporary directory - std::string - path() const - { - return path_.string(); - } - - /** Get the native path for the a file. - - The file does not need to exist. - */ - std::string - file(std::string const& name) const - { - return (path_ / name).string(); - } -}; - -} // detail -} // beast - -#endif diff --git a/src/beast/include/beast/core/detail/unit_test.h b/src/beast/include/beast/core/detail/unit_test.h deleted file mode 100644 index a53379e0ba..0000000000 --- a/src/beast/include/beast/core/detail/unit_test.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// Copyright (c) 2013-2016 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) -// - -#ifndef BEAST_DETAIL_UNIT_TEST_H_INCLUDED -#define BEAST_DETAIL_UNIT_TEST_H_INCLUDED - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#endif diff --git a/src/beast/include/beast/http/concepts.hpp b/src/beast/include/beast/http/concepts.hpp new file mode 100644 index 0000000000..d8641050aa --- /dev/null +++ b/src/beast/include/beast/http/concepts.hpp @@ -0,0 +1,156 @@ +// +// Copyright (c) 2013-2016 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) +// + +#ifndef BEAST_HTTP_TYPE_CHECK_HPP +#define BEAST_HTTP_TYPE_CHECK_HPP + +#include +#include +#include +#include + +namespace beast { +namespace http { + +namespace detail { + +template +class has_value_type +{ + template + static std::true_type check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); +public: + static bool constexpr value = type::value; +}; + +template::value> +struct extract_value_type +{ + using type = void; +}; + +template +struct extract_value_type +{ + using type = typename T::value_type; +}; + +template +class has_reader +{ + template + static std::true_type check(int); + template + static std::false_type check(...); +public: + using type = decltype(check(0)); +}; + +template +class has_writer +{ + template + static std::true_type check(int); + template + static std::false_type check(...); +public: + using type = decltype(check(0)); +}; + +template +struct is_Body +{ + using type = std::integral_constant::value && + std::is_default_constructible< + typename extract_value_type::type>::value + >; +}; + +template +class is_Parser +{ + template().complete()), + bool>> + static R check1(int); + template + static std::false_type check1(...); + using type1 = decltype(check1(0)); + + template().write( + std::declval(), + std::declval())), + std::size_t>> + static R check2(int); + template + static std::false_type check2(...); + using type2 = decltype(check2(0)); + + template().write_eof(std::declval()), + std::true_type{})> + static R check3(int); + template + static std::false_type check3(...); + using type3 = decltype(check3(0)); + +public: + using type = std::integral_constant; +}; + +} // detail + +/// Determine if `T` meets the requirements of `Body`. +template +#if GENERATING_DOCS +struct is_Body : std::integral_constant{}; +#else +using is_Body = typename detail::is_Body::type; +#endif + +/// Determine if `T` meets the requirements of `ReadableBody`. +template +#if GENERATING_DOCS +struct is_ReadableBody : std::integral_constant{}; +#else +using is_ReadableBody = typename detail::has_reader::type; +#endif + +/// Determine if `T` meets the requirements of `WritableBody`. +template +#if GENERATING_DOCS +struct is_WritableBody : std::integral_constant{}; +#else +using is_WritableBody = typename detail::has_writer::type; +#endif + +/// Determine if `T` meets the requirements of `Parser`. +template +#if GENERATING_DOCS +struct is_Parser : std::integral_constant{}; +#else +using is_Parser = typename detail::is_Parser::type; +#endif + +} // http +} // beast + +#endif diff --git a/src/beast/include/beast/http/empty_body.hpp b/src/beast/include/beast/http/empty_body.hpp index 885a5605f7..8eb44064d3 100644 --- a/src/beast/include/beast/http/empty_body.hpp +++ b/src/beast/include/beast/http/empty_body.hpp @@ -34,20 +34,6 @@ struct empty_body private: #endif - struct reader - { - template - explicit - reader(message&) - { - } - - void - write(void const*, std::size_t, error_code&) - { - } - }; - struct writer { writer(writer const&) = delete; diff --git a/src/beast/include/beast/http/impl/basic_parser_v1.ipp b/src/beast/include/beast/http/impl/basic_parser_v1.ipp index 2b40d3e424..4e6a67761f 100644 --- a/src/beast/include/beast/http/impl/basic_parser_v1.ipp +++ b/src/beast/include/beast/http/impl/basic_parser_v1.ipp @@ -463,8 +463,9 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) case s_header_field: { - for(; p != end; ch = *++p) + for(; p != end; ++p) { + ch = *p; auto c = to_field_char(ch); if(! c) break; @@ -660,8 +661,9 @@ write(boost::asio::const_buffer const& buffer, error_code& ec) case s_header_value_text: { - for(; p != end; ch = *++p) + for(; p != end; ++p) { + ch = *p; if(ch == '\r') { if(cb(nullptr)) diff --git a/src/beast/include/beast/http/impl/message_v1.ipp b/src/beast/include/beast/http/impl/message_v1.ipp index 11f3641427..16a85a58bf 100644 --- a/src/beast/include/beast/http/impl/message_v1.ipp +++ b/src/beast/include/beast/http/impl/message_v1.ipp @@ -105,33 +105,6 @@ prepare_content_length(prepare_info& pi, } // detail -template -void -prepare_connection( - message_v1& msg) -{ - if(msg.version >= 11) - { - if(! msg.headers.exists("Content-Length") && - ! rfc2616::token_in_list( - msg.headers["Transfer-Encoding"], "chunked")) - if(! rfc2616::token_in_list( - msg.headers["Connection"], "close")) - msg.headers.insert("Connection", "close"); - } - else - { - if(! msg.headers.exists("Content-Length")) - { - // VFALCO We are erasing the whole header when we - // should be removing just the keep-alive. - if(rfc2616::token_in_list( - msg.headers["Connection"], "keep-alive")) - msg.headers.erase("Connection"); - } - } -} - template< bool isRequest, class Body, class Headers, class... Options> diff --git a/src/beast/include/beast/http/impl/read.ipp b/src/beast/include/beast/http/impl/read.ipp index 87c0af4c5c..0d22a4b5c0 100644 --- a/src/beast/include/beast/http/impl/read.ipp +++ b/src/beast/include/beast/http/impl/read.ipp @@ -8,8 +8,8 @@ #ifndef BEAST_HTTP_IMPL_READ_IPP_HPP #define BEAST_HTTP_IMPL_READ_IPP_HPP +#include #include -#include #include #include #include @@ -323,6 +323,12 @@ void parse(SyncReadStream& stream, Streambuf& streambuf, Parser& parser) { + static_assert(is_SyncReadStream::value, + "SyncReadStream requirements not met"); + static_assert(is_Streambuf::value, + "Streambuf requirements not met"); + static_assert(is_Parser::value, + "Parser requirements not met"); error_code ec; parse(stream, streambuf, parser, ec); if(ec) @@ -403,6 +409,8 @@ read(SyncReadStream& stream, Streambuf& streambuf, "SyncReadStream requirements not met"); static_assert(is_Streambuf::value, "Streambuf requirements not met"); + static_assert(is_ReadableBody::value, + "ReadableBody requirements not met"); error_code ec; read(stream, streambuf, msg, ec); if(ec) @@ -420,6 +428,8 @@ read(SyncReadStream& stream, Streambuf& streambuf, "SyncReadStream requirements not met"); static_assert(is_Streambuf::value, "Streambuf requirements not met"); + static_assert(is_ReadableBody::value, + "ReadableBody requirements not met"); parser_v1 p; parse(stream, streambuf, p, ec); if(ec) @@ -441,6 +451,8 @@ async_read(AsyncReadStream& stream, Streambuf& streambuf, "AsyncReadStream requirements not met"); static_assert(is_Streambuf::value, "Streambuf requirements not met"); + static_assert(is_ReadableBody::value, + "ReadableBody requirements not met"); beast::async_completion completion(handler); detail::read_op #include #include #include @@ -448,6 +449,8 @@ write(SyncWriteStream& stream, { static_assert(is_SyncWriteStream::value, "SyncWriteStream requirements not met"); + static_assert(is_WritableBody::value, + "WritableBody requirements not met"); error_code ec; write(stream, msg, ec); if(ec) @@ -463,6 +466,8 @@ write(SyncWriteStream& stream, { static_assert(is_SyncWriteStream::value, "SyncWriteStream requirements not met"); + static_assert(is_WritableBody::value, + "WritableBody requirements not met"); detail::write_preparation wp(msg); wp.init(ec); if(ec) @@ -546,6 +551,8 @@ async_write(AsyncWriteStream& stream, static_assert( is_AsyncWriteStream::value, "AsyncWriteStream requirements not met"); + static_assert(is_WritableBody::value, + "WritableBody requirements not met"); beast::async_completion completion(handler); detail::write_op const& msg) { + static_assert(is_WritableBody::value, + "WritableBody requirements not met"); detail::ostream_SyncStream oss(os); error_code ec; write(oss, msg, ec); diff --git a/src/beast/include/beast/http/parser_v1.hpp b/src/beast/include/beast/http/parser_v1.hpp index 8831297a53..46f8dee88d 100644 --- a/src/beast/include/beast/http/parser_v1.hpp +++ b/src/beast/include/beast/http/parser_v1.hpp @@ -9,6 +9,7 @@ #define BEAST_HTTP_PARSER_V1_HPP #include +#include #include #include #include @@ -54,6 +55,9 @@ public: message_v1; private: + static_assert(is_ReadableBody::value, + "ReadableBody requirements not met"); + std::string field_; std::string value_; message_type m_; diff --git a/src/beast/include/beast/http/type_check.hpp b/src/beast/include/beast/http/type_check.hpp deleted file mode 100644 index 180e1b7d0b..0000000000 --- a/src/beast/include/beast/http/type_check.hpp +++ /dev/null @@ -1,60 +0,0 @@ -// -// Copyright (c) 2013-2016 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) -// - -#ifndef BEAST_HTTP_TYPE_CHECK_HPP -#define BEAST_HTTP_TYPE_CHECK_HPP - -#include -#include -#include -#include - -namespace beast { -namespace http { - -/// Determine if `T` meets the requirements of `Parser`. -template -class is_Parser -{ - template().complete()), - bool>> - static R check1(int); - template - static std::false_type check1(...); - using type1 = decltype(check1(0)); - - template().write( - std::declval(), - std::declval())), - std::size_t>> - static R check2(int); - template - static std::false_type check2(...); - using type2 = decltype(check2(0)); - - template().write_eof(std::declval()), - std::true_type{})> - static R check3(int); - template - static std::false_type check3(...); - using type3 = decltype(check3(0)); - -public: - /// `true` if `T` meets the requirements. - static bool const value = - type1::value && type2::value && type3::value; -}; - -} // http -} // beast - -#endif diff --git a/src/beast/include/beast/version.hpp b/src/beast/include/beast/version.hpp index 619ab10b7b..aa098487a8 100644 --- a/src/beast/include/beast/version.hpp +++ b/src/beast/include/beast/version.hpp @@ -16,6 +16,6 @@ // #define BEAST_VERSION 100000 -#define BEAST_VERSION_STRING "1.0.0-b4" +#define BEAST_VERSION_STRING "1.0.0-b5" #endif diff --git a/src/beast/include/beast/websocket/detail/frame.hpp b/src/beast/include/beast/websocket/detail/frame.hpp index 470c46cbe2..05955631c1 100644 --- a/src/beast/include/beast/websocket/detail/frame.hpp +++ b/src/beast/include/beast/websocket/detail/frame.hpp @@ -23,6 +23,16 @@ namespace beast { namespace websocket { namespace detail { +/// Identifies the role of a WebSockets stream. +enum class role_type +{ + /// Stream is operating as a client. + client, + + /// Stream is operating as a server. + server +}; + // Contents of a WebSocket frame header struct frame_header { @@ -286,8 +296,7 @@ read_fh2(frame_header& fh, Streambuf& sb, // template void -read(ping_payload_type& data, - Buffers const& bs, close_code::value& code) +read(ping_data& data, Buffers const& bs) { using boost::asio::buffer_copy; using boost::asio::buffer_size; diff --git a/src/beast/include/beast/websocket/detail/mask.hpp b/src/beast/include/beast/websocket/detail/mask.hpp index 29d49c6b75..78f6f1f8b8 100644 --- a/src/beast/include/beast/websocket/detail/mask.hpp +++ b/src/beast/include/beast/websocket/detail/mask.hpp @@ -71,9 +71,9 @@ using maskgen = maskgen_t; //------------------------------------------------------------------------------ -//using prepared_key_type = std::size_t; -using prepared_key_type = std::uint32_t; -//using prepared_key_type = std::uint64_t; +using prepared_key_type = + std::conditional::type; inline void @@ -93,19 +93,6 @@ prepare_key(std::uint64_t& prepared, std::uint32_t key) template inline typename std::enable_if::value, T>::type -rol(T t, unsigned n = 1) -{ - auto constexpr bits = - static_cast( - sizeof(T) * CHAR_BIT); - n &= bits-1; - return static_cast((t << n) | ( - static_cast::type>(t) >> (bits - n))); -} - -template -inline -typename std::enable_if::value, T>::type ror(T t, unsigned n = 1) { auto constexpr bits = @@ -120,7 +107,7 @@ ror(T t, unsigned n = 1) // template void -mask_inplace_safe( +mask_inplace_general( boost::asio::mutable_buffer const& b, std::uint32_t& key) { @@ -151,7 +138,7 @@ mask_inplace_safe( // template void -mask_inplace_safe( +mask_inplace_general( boost::asio::mutable_buffer const& b, std::uint64_t& key) { @@ -186,164 +173,13 @@ mask_inplace_safe( } } -// 32-bit optimized -template -void -mask_inplace_32( - boost::asio::mutable_buffer const& b, - std::uint32_t& key) -{ - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - auto n = buffer_size(b); - auto p = buffer_cast(b); - auto m = reinterpret_cast< - uintptr_t>(p) % sizeof(key); - switch(m) - { - case 1: *p ^= key ; ++p; --n; - case 2: *p ^= (key >> 8); ++p; --n; - case 3: *p ^= (key >>16); ++p; --n; - key = ror(key, m * 8); - case 0: - break; - } - for(auto i = n / sizeof(key); i; --i) - { - *reinterpret_cast< - std::uint32_t*>(p) ^= key; - p += sizeof(key); - } - n %= sizeof(key); - switch(n) - { - case 3: p[2] ^= (key >>16); - case 2: p[1] ^= (key >> 8); - case 1: p[0] ^= key; - key = ror(key, n*8); - default: - break; - } -} - -// 64-bit optimized -// -template -void -mask_inplace_64( - boost::asio::mutable_buffer const& b, - std::uint64_t& key) -{ - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - auto n = buffer_size(b); - auto p = buffer_cast(b); - auto m = reinterpret_cast< - uintptr_t>(p) % sizeof(key); - switch(m) - { - case 1: *p ^= key ; ++p; --n; - case 2: *p ^= (key >> 8); ++p; --n; - case 3: *p ^= (key >>16); ++p; --n; - case 4: *p ^= (key >>24); ++p; --n; - case 5: *p ^= (key >>32); ++p; --n; - case 6: *p ^= (key >>40); ++p; --n; - case 7: *p ^= (key >>48); ++p; --n; - key = ror(key, m * 8); - case 0: - break; - } - for(auto i = n / sizeof(key); i; --i) - { - *reinterpret_cast< - std::uint64_t*>(p) ^= key; - p += sizeof(key); - } - n %= sizeof(key); - switch(n) - { - case 3: p[2] ^= (key >>16); - case 2: p[1] ^= (key >> 8); - case 1: p[0] ^= key; - key = ror(key, n*8); - default: - break; - } -} - -// 32-bit x86 optimized -// -template -void -mask_inplace_x86( - boost::asio::mutable_buffer const& b, - std::uint32_t& key) -{ - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - auto n = buffer_size(b); - auto p = buffer_cast(b); - for(auto i = n / sizeof(key); i; --i) - { - *reinterpret_cast< - std::uint32_t*>(p) ^= key; - p += sizeof(key); - } - n %= sizeof(key); - switch(n) - { - case 3: p[2] ^= (key >>16); - case 2: p[1] ^= (key >> 8); - case 1: p[0] ^= key; - key = ror(key, n*8); - default: - break; - } -} - -// 64-bit amd64 optimized -// -template -void -mask_inplace_amd( - boost::asio::mutable_buffer const& b, - std::uint64_t& key) -{ - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - auto n = buffer_size(b); - auto p = buffer_cast(b); - for(auto i = n / sizeof(key); i; --i) - { - *reinterpret_cast< - std::uint64_t*>(p) ^= key; - p += sizeof(key); - } - n %= sizeof(key); - switch(n) - { - case 7: p[6] ^= (key >>16); - case 6: p[5] ^= (key >> 8); - case 5: p[4] ^= key; - case 4: p[3] ^= (key >>24); - case 3: p[2] ^= (key >>16); - case 2: p[1] ^= (key >> 8); - case 1: p[0] ^= key; - key = ror(key, n*8); - default: - break; - } -} - inline void mask_inplace( boost::asio::mutable_buffer const& b, std::uint32_t& key) { - mask_inplace_safe(b, key); - //mask_inplace_32(b, key); - //mask_inplace_x86(b, key); + mask_inplace_general(b, key); } inline @@ -352,9 +188,7 @@ mask_inplace( boost::asio::mutable_buffer const& b, std::uint64_t& key) { - mask_inplace_safe(b, key); - //mask_inplace_64(b, key); - //mask_inplace_amd(b, key); + mask_inplace_general(b, key); } // Apply mask in place diff --git a/src/beast/include/beast/websocket/detail/stream_base.hpp b/src/beast/include/beast/websocket/detail/stream_base.hpp index 112205f76c..daecf145d2 100644 --- a/src/beast/include/beast/websocket/detail/stream_base.hpp +++ b/src/beast/include/beast/websocket/detail/stream_base.hpp @@ -30,15 +30,6 @@ namespace beast { namespace websocket { namespace detail { -template -inline -void -maybe_throw(error_code const& ec, String const&) -{ - if(ec) - throw system_error{ec}; -} - template static std::size_t @@ -59,6 +50,8 @@ clamp(UInt x, std::size_t limit) return static_cast(x); } +using pong_cb = std::function; + //------------------------------------------------------------------------------ struct stream_base @@ -69,42 +62,46 @@ protected: detail::maskgen maskgen_; // source of mask keys decorator_type d_; // adorns http messages bool keep_alive_ = false; // close on failed upgrade - role_type role_; // server or client - bool error_ = false; // non-zero ec was delivered - std::size_t rd_msg_max_ = 16 * 1024 * 1024; // max message size + std::size_t + wr_frag_size_ = 16 * 1024; // size of auto-fragments + std::size_t mask_buf_size_ = 4096; // mask buffer size + opcode wr_opcode_ = opcode::text; // outgoing message type + pong_cb pong_cb_; // pong callback + role_type role_; // server or client + bool failed_; // the connection failed + detail::frame_header rd_fh_; // current frame header detail::prepared_key_type rd_key_; // prepared masking key detail::utf8_checker rd_utf8_check_;// for current text msg std::uint64_t rd_size_; // size of the current message so far std::uint64_t rd_need_ = 0; // bytes left in msg frame payload opcode rd_opcode_; // opcode of current msg - bool rd_cont_ = false; // expecting a continuation frame - bool rd_close_ = false; // got close frame - op* rd_block_ = nullptr; // op currently reading + bool rd_cont_; // expecting a continuation frame - std::size_t - wr_frag_size_ = 16 * 1024; // size of auto-fragments - std::size_t wr_buf_size_ = 4096; // write buffer size - opcode wr_opcode_ = opcode::text; // outgoing message type - bool wr_close_ = false; // sent close frame - bool wr_cont_ = false; // next write is continuation frame - op* wr_block_ = nullptr; // op currenly writing + bool wr_close_; // sent close frame + bool wr_cont_; // next write is continuation frame + op* wr_block_; // op currenly writing + ping_data* pong_data_; // where to put pong payload invokable rd_op_; // invoked after write completes invokable wr_op_; // invoked after read completes close_reason cr_; // set from received close frame + stream_base(stream_base&&) = default; + stream_base(stream_base const&) = delete; + stream_base& operator=(stream_base&&) = default; + stream_base& operator=(stream_base const&) = delete; + stream_base() : d_(new decorator{}) { } - stream_base(stream_base&&) = default; - stream_base(stream_base const&) = delete; - stream_base& operator=(stream_base&&) = default; - stream_base& operator=(stream_base const&) = delete; + template + void + open(role_type role); template void @@ -118,7 +115,7 @@ protected: template void write_ping(Streambuf& sb, opcode op, - ping_payload_type const& data); + ping_data const& data); }; } // detail diff --git a/src/beast/include/beast/websocket/impl/accept_op.ipp b/src/beast/include/beast/websocket/impl/accept_op.ipp index b897832cf9..bc94ac352e 100644 --- a/src/beast/include/beast/websocket/impl/accept_op.ipp +++ b/src/beast/include/beast/websocket/impl/accept_op.ipp @@ -33,7 +33,7 @@ class stream::accept_op struct data { stream& ws; - http::request_v1 req; + http::request_v1 req; Handler h; bool cont; int state = 0; @@ -48,6 +48,7 @@ class stream::accept_op { using boost::asio::buffer_copy; using boost::asio::buffer_size; + ws.reset(); ws.stream_.buffer().commit(buffer_copy( ws.stream_.buffer().prepare( buffer_size(buffers)), buffers)); @@ -133,8 +134,14 @@ operator()(error_code const& ec, // got message case 1: // respond to request +#if 1 + // VFALCO I have no idea why passing std::move(*this) crashes + d.state = 99; + d.ws.async_accept(d.req, *this); +#else response_op{ std::move(d.h), d.ws, d.req, true}; +#endif return; } } diff --git a/src/beast/include/beast/websocket/impl/close_op.ipp b/src/beast/include/beast/websocket/impl/close_op.ipp index 4573cb4100..379b756dd9 100644 --- a/src/beast/include/beast/websocket/impl/close_op.ipp +++ b/src/beast/include/beast/websocket/impl/close_op.ipp @@ -21,12 +21,9 @@ template template class stream::close_op { - using alloc_type = - handler_alloc; - using fb_type = - detail::frame_streambuf; - using fmb_type = - typename fb_type::mutable_buffers_type; + using alloc_type = handler_alloc; + + using fb_type = detail::frame_streambuf; struct data : op { @@ -64,24 +61,19 @@ public: std::forward(h), ws, std::forward(args)...)) { - (*this)(error_code{}, 0, false); + (*this)(error_code{}, false); } void operator()() { - auto& d = *d_; - d.cont = false; - (*this)(error_code{}, 0, false); - } - - void operator()(error_code const& ec) - { - (*this)(ec, 0); + (*this)(error_code{}); } void - operator()(error_code ec, - std::size_t bytes_transferred, bool again = true); + operator()(error_code ec, std::size_t); + + void + operator()(error_code ec, bool again = true); friend void* asio_handler_allocate( @@ -117,12 +109,26 @@ public: template template void -stream::close_op::operator()( - error_code ec, std::size_t bytes_transferred, bool again) +stream::close_op:: +operator()(error_code ec, std::size_t) +{ + auto& d = *d_; + if(ec) + d.ws.failed_ = true; + (*this)(ec); +} + +template +template +void +stream::close_op:: +operator()(error_code ec, bool again) { auto& d = *d_; d.cont = d.cont || again; - while(! ec && d.state != 99) + if(ec) + goto upcall; + for(;;) { switch(d.state) { @@ -130,49 +136,52 @@ stream::close_op::operator()( if(d.ws.wr_block_) { // suspend - d.state = 1; - d.ws.rd_op_.template emplace< + d.state = 2; + d.ws.wr_op_.template emplace< close_op>(std::move(*this)); return; } - if(d.ws.error_) + if(d.ws.failed_ || d.ws.wr_close_) { // call handler - d.state = 99; d.ws.get_io_service().post( bind_handler(std::move(*this), - boost::asio::error::operation_aborted, 0)); + boost::asio::error::operation_aborted)); return; } - d.state = 2; - break; + // fall through - // resume case 1: - if(d.ws.error_) - { - // call handler - d.state = 99; - ec = boost::asio::error::operation_aborted; - break; - } - d.state = 2; - break; - - case 2: - // send close + // send close frame d.state = 99; - assert(! d.ws.wr_close_); d.ws.wr_close_ = true; assert(! d.ws.wr_block_); d.ws.wr_block_ = &d; boost::asio::async_write(d.ws.stream_, d.fb.data(), std::move(*this)); return; + + case 2: + d.state = 3; + d.ws.get_io_service().post( + bind_handler(std::move(*this), ec)); + return; + + case 3: + if(d.ws.failed_ || d.ws.wr_close_) + { + // call handler + ec = boost::asio::error::operation_aborted; + goto upcall; + } + d.state = 1; + break; + + case 99: + goto upcall; } } - if(ec) - d.ws.error_ = true; +upcall: if(d.ws.wr_block_ == &d) d.ws.wr_block_ = nullptr; d.ws.rd_op_.maybe_invoke(); diff --git a/src/beast/include/beast/websocket/impl/handshake_op.ipp b/src/beast/include/beast/websocket/impl/handshake_op.ipp index e126c6a0d3..96afabb9c9 100644 --- a/src/beast/include/beast/websocket/impl/handshake_op.ipp +++ b/src/beast/include/beast/websocket/impl/handshake_op.ipp @@ -48,6 +48,7 @@ class stream::handshake_op , cont(boost_asio_handler_cont_helpers:: is_continuation(h)) { + ws.reset(); } }; @@ -64,16 +65,11 @@ public: std::forward(h), ws, std::forward(args)...)) { - (*this)(error_code{}, 0, false); + (*this)(error_code{}, false); } - void operator()(error_code const& ec) - { - (*this)(ec, 0); - } - - void operator()(error_code ec, - std::size_t bytes_transferred, bool again = true); + void + operator()(error_code ec, bool again = true); friend void* asio_handler_allocate( @@ -109,9 +105,8 @@ public: template template void -stream::handshake_op< - Handler>::operator()(error_code ec, - std::size_t bytes_transferred, bool again) +stream::handshake_op:: +operator()(error_code ec, bool again) { auto& d = *d_; d.cont = d.cont || again; diff --git a/src/beast/include/beast/websocket/impl/ping_op.ipp b/src/beast/include/beast/websocket/impl/ping_op.ipp new file mode 100644 index 0000000000..4689789627 --- /dev/null +++ b/src/beast/include/beast/websocket/impl/ping_op.ipp @@ -0,0 +1,193 @@ +// +// Copyright (c) 2013-2016 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) +// + +#ifndef BEAST_WEBSOCKET_IMPL_PING_OP_HPP +#define BEAST_WEBSOCKET_IMPL_PING_OP_HPP + +#include +#include +#include +#include + +namespace beast { +namespace websocket { + +// write a ping frame +// +template +template +class stream::ping_op +{ + using alloc_type = + handler_alloc; + + struct data : op + { + stream& ws; + Handler h; + detail::frame_streambuf fb; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, stream& ws_, + ping_data const& payload) + : ws(ws_) + , h(std::forward(h_)) + , cont(boost_asio_handler_cont_helpers:: + is_continuation(h)) + { + using boost::asio::buffer; + using boost::asio::buffer_copy; + ws.template write_ping( + fb, opcode::ping, payload); + } + }; + + std::shared_ptr d_; + +public: + ping_op(ping_op&&) = default; + ping_op(ping_op const&) = default; + + template + ping_op(DeducedHandler&& h, + stream& ws, Args&&... args) + : d_(std::make_shared( + std::forward(h), ws, + std::forward(args)...)) + { + (*this)(error_code{}, false); + } + + void operator()() + { + (*this)(error_code{}); + } + + void operator()(error_code ec, std::size_t); + + void operator()(error_code ec, bool again = true); + + friend + void* asio_handler_allocate( + std::size_t size, ping_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + void asio_handler_deallocate( + void* p, std::size_t size, ping_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + bool asio_handler_is_continuation(ping_op* op) + { + return op->d_->cont; + } + + template + friend + void asio_handler_invoke(Function&& f, ping_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +template +void +stream::ping_op:: +operator()(error_code ec, std::size_t) +{ + auto& d = *d_; + if(ec) + d.ws.failed_ = true; + (*this)(ec); +} + +template +template +void +stream:: +ping_op:: +operator()(error_code ec, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + if(ec) + goto upcall; + for(;;) + { + switch(d.state) + { + case 0: + if(d.ws.wr_block_) + { + // suspend + d.state = 2; + d.ws.wr_op_.template emplace< + ping_op>(std::move(*this)); + return; + } + if(d.ws.failed_ || d.ws.wr_close_) + { + // call handler + d.state = 99; + d.ws.get_io_service().post( + bind_handler(std::move(*this), + boost::asio::error::operation_aborted)); + return; + } + // fall through + + case 1: + // send ping frame + d.state = 99; + assert(! d.ws.wr_block_); + d.ws.wr_block_ = &d; + boost::asio::async_write(d.ws.stream_, + d.fb.data(), std::move(*this)); + return; + + case 2: + d.state = 3; + d.ws.get_io_service().post( + bind_handler(std::move(*this), ec)); + return; + + case 3: + if(d.ws.failed_ || d.ws.wr_close_) + { + // call handler + ec = boost::asio::error::operation_aborted; + goto upcall; + } + d.state = 1; + break; + + case 99: + goto upcall; + } + } +upcall: + if(d.ws.wr_block_ == &d) + d.ws.wr_block_ = nullptr; + d.ws.rd_op_.maybe_invoke(); + d.h(ec); +} + +} // websocket +} // beast + +#endif diff --git a/src/beast/include/beast/websocket/impl/read_frame_op.ipp b/src/beast/include/beast/websocket/impl/read_frame_op.ipp index 44c7f39b3d..776d88ca9d 100644 --- a/src/beast/include/beast/websocket/impl/read_frame_op.ipp +++ b/src/beast/include/beast/websocket/impl/read_frame_op.ipp @@ -81,18 +81,19 @@ public: void operator()() { - auto& d = *d_; - d.cont = false; - (*this)(error_code{}, 0, false); + (*this)(error_code{}, 0, true); } void operator()(error_code const& ec) { - (*this)(ec, 0); + (*this)(ec, 0, true); } void operator()(error_code ec, - std::size_t bytes_transferred, bool again = true); + std::size_t bytes_transferred); + + void operator()(error_code ec, + std::size_t bytes_transferred, bool again); friend void* asio_handler_allocate( @@ -129,373 +130,415 @@ template template void stream::read_frame_op:: -operator()(error_code ec,std::size_t bytes_transferred, bool again) +operator()(error_code ec, std::size_t bytes_transferred) { auto& d = *d_; - d.cont = d.cont || again; - close_code::value code = close_code::none; - while(! ec && d.state != 99) + if(ec) + d.ws.failed_ = true; + (*this)(ec, bytes_transferred, true); +} + +template +template +void +stream::read_frame_op:: +operator()(error_code ec,std::size_t bytes_transferred, bool again) +{ + enum { - switch(d.state) + do_start = 0, + do_read_payload = 1, + do_frame_done = 3, + do_read_fh = 4, + do_control_payload = 7, + do_control = 8, + do_pong_resume = 9, + do_pong = 11, + do_close_resume = 13, + do_close = 15, + do_fail = 18, + + do_call_handler = 99 + }; + + auto& d = *d_; + if(! ec) + { + d.cont = d.cont || again; + close_code::value code = close_code::none; + do { - case 0: - if(d.ws.error_) + switch(d.state) { + case do_start: + if(d.ws.failed_) + { + d.state = do_call_handler; + d.ws.get_io_service().post( + bind_handler(std::move(*this), + boost::asio::error::operation_aborted, 0)); + return; + } + d.state = d.ws.rd_need_ > 0 ? + do_read_payload : do_read_fh; + break; + + //------------------------------------------------------------------ + + case do_read_payload: + d.state = do_read_payload + 1; + d.smb = d.sb.prepare( + detail::clamp(d.ws.rd_need_)); + // receive payload data + d.ws.stream_.async_read_some( + *d.smb, std::move(*this)); + return; + + case do_read_payload + 1: + { + d.ws.rd_need_ -= bytes_transferred; + auto const pb = prepare_buffers( + bytes_transferred, *d.smb); + if(d.ws.rd_fh_.mask) + detail::mask_inplace(pb, d.ws.rd_key_); + if(d.ws.rd_opcode_ == opcode::text) + { + if(! d.ws.rd_utf8_check_.write(pb) || + (d.ws.rd_need_ == 0 && d.ws.rd_fh_.fin && + ! d.ws.rd_utf8_check_.finish())) + { + // invalid utf8 + code = close_code::bad_payload; + d.state = do_fail; + break; + } + } + d.sb.commit(bytes_transferred); + if(d.ws.rd_need_ > 0) + { + d.state = do_read_payload; + break; + } + // fall through + } + + //------------------------------------------------------------------ + + case do_frame_done: // call handler - d.state = 99; - d.ws.get_io_service().post( - bind_handler(std::move(*this), - boost::asio::error::operation_aborted, 0)); + d.fi.op = d.ws.rd_opcode_; + d.fi.fin = d.ws.rd_fh_.fin && + d.ws.rd_need_ == 0; + goto upcall; + + //------------------------------------------------------------------ + + case do_read_fh: + d.state = do_read_fh + 1; + boost::asio::async_read(d.ws.stream_, + d.fb.prepare(2), std::move(*this)); + return; + + case do_read_fh + 1: + { + d.fb.commit(bytes_transferred); + code = close_code::none; + auto const n = detail::read_fh1( + d.ws.rd_fh_, d.fb, d.ws.role_, code); + if(code != close_code::none) + { + // protocol error + d.state = do_fail; + break; + } + d.state = do_read_fh + 2; + if (n == 0) + { + bytes_transferred = 0; + break; + } + // read variable header + boost::asio::async_read(d.ws.stream_, + d.fb.prepare(n), std::move(*this)); return; } - if(d.ws.rd_need_ > 0) - { - d.state = 1; - break; - } - d.state = 2; - break; - case 1: - // read payload - d.state = 3; - d.smb = d.sb.prepare( - detail::clamp(d.ws.rd_need_)); - d.ws.stream_.async_read_some( - *d.smb, std::move(*this)); - return; - - case 2: - // read fixed header - d.state = 5; - boost::asio::async_read(d.ws.stream_, - d.fb.prepare(2), std::move(*this)); - return; - - // got payload - case 3: - { - d.ws.rd_need_ -= bytes_transferred; - auto const pb = prepare_buffers( - bytes_transferred, *d.smb); - if(d.ws.rd_fh_.mask) - detail::mask_inplace(pb, d.ws.rd_key_); - if(d.ws.rd_opcode_ == opcode::text) - { - if(! d.ws.rd_utf8_check_.write(pb) || - (d.ws.rd_need_ == 0 && d.ws.rd_fh_.fin && - ! d.ws.rd_utf8_check_.finish())) - { - // invalid utf8 - d.state = 16; - code = close_code::bad_payload; - break; - } - } - d.sb.commit(bytes_transferred); - d.state = 4; - break; - } - - // call handler - case 4: - d.state = 99; - d.fi.op = d.ws.rd_opcode_; - d.fi.fin = d.ws.rd_fh_.fin && - d.ws.rd_need_ == 0; - break; - - // got fixed header - case 5: - { - d.fb.commit(bytes_transferred); - code = close_code::none; - auto const n = detail::read_fh1( - d.ws.rd_fh_, d.fb, d.ws.role_, code); - if(code != close_code::none) - { - // protocol error - d.state = 16; - break; - } - d.state = 6; - if (n == 0) - { - bytes_transferred = 0; - break; - } - // read variable header - boost::asio::async_read(d.ws.stream_, - d.fb.prepare(n), std::move(*this)); - return; - } - - // got variable header - case 6: - d.fb.commit(bytes_transferred); - code = close_code::none; - detail::read_fh2(d.ws.rd_fh_, - d.fb, d.ws.role_, code); - if(code == close_code::none) - d.ws.prepare_fh(code); - if(code != close_code::none) - { - // protocol error - d.state = 16; - break; - } - if(detail::is_control(d.ws.rd_fh_.op)) - { - if(d.ws.rd_fh_.len > 0) - { - // read control payload - d.state = 7; - d.fmb = d.fb.prepare(static_cast< - std::size_t>(d.ws.rd_fh_.len)); - boost::asio::async_read(d.ws.stream_, - *d.fmb, std::move(*this)); - return; - } - d.state = 8; - break; - } - if(d.ws.rd_need_ > 0) - { - d.state = 1; - break; - } - if(! d.ws.rd_fh_.fin) - { - d.state = 2; - break; - } - // empty frame with fin - d.state = 4; - break; - - // got control payload - case 7: - if(d.ws.rd_fh_.mask) - detail::mask_inplace( - *d.fmb, d.ws.rd_key_); - d.fb.commit(bytes_transferred); - d.state = 8; - break; - - // do control - case 8: - if(d.ws.rd_fh_.op == opcode::ping) - { + case do_read_fh + 2: + d.fb.commit(bytes_transferred); code = close_code::none; - ping_payload_type data; - detail::read(data, d.fb.data(), code); + detail::read_fh2(d.ws.rd_fh_, + d.fb, d.ws.role_, code); + if(code == close_code::none) + d.ws.prepare_fh(code); if(code != close_code::none) { // protocol error - d.state = 16; + d.state = do_fail; break; } - d.fb.reset(); - if(d.ws.wr_close_) + if(detail::is_control(d.ws.rd_fh_.op)) { - d.state = 2; + if(d.ws.rd_fh_.len > 0) + { + // read control payload + d.state = do_control_payload; + d.fmb = d.fb.prepare(static_cast< + std::size_t>(d.ws.rd_fh_.len)); + boost::asio::async_read(d.ws.stream_, + *d.fmb, std::move(*this)); + return; + } + d.state = do_control; break; } - d.ws.template write_ping( - d.fb, opcode::pong, data); - if(d.ws.wr_block_) + if(d.ws.rd_need_ > 0) { - assert(d.ws.wr_block_ != &d); - // suspend - d.state = 13; - d.ws.rd_op_.template emplace< - read_frame_op>(std::move(*this)); - return; + d.state = do_read_payload; + break; } - d.state = 14; + // empty frame + d.state = do_frame_done; break; - } - else if(d.ws.rd_fh_.op == opcode::pong) - { - code = close_code::none; - ping_payload_type data; - detail::read(data, d.fb.data(), code); - if(code != close_code::none) - { - // protocol error - d.state = 16; - break; - } - d.fb.reset(); - // VFALCO TODO maybe_invoke an async pong handler - // For now just ignore the pong. - d.state = 2; + + //------------------------------------------------------------------ + + case do_control_payload: + if(d.ws.rd_fh_.mask) + detail::mask_inplace( + *d.fmb, d.ws.rd_key_); + d.fb.commit(bytes_transferred); + d.state = do_control; // VFALCO fall through? break; - } - assert(d.ws.rd_fh_.op == opcode::close); - { - detail::read(d.ws.cr_, d.fb.data(), code); - if(code != close_code::none) + + //------------------------------------------------------------------ + + case do_control: + if(d.ws.rd_fh_.op == opcode::ping) { - d.state = 16; - break; - } - if(! d.ws.wr_close_) - { - auto cr = d.ws.cr_; - if(cr.code == close_code::none) - cr.code = close_code::normal; - cr.reason = ""; + ping_data data; + detail::read(data, d.fb.data()); d.fb.reset(); - d.ws.template write_close< - static_streambuf>(d.fb, cr); + if(d.ws.wr_close_) + { + // ignore ping when closing + d.state = do_read_fh; + break; + } + d.ws.template write_ping( + d.fb, opcode::pong, data); if(d.ws.wr_block_) { // suspend - d.state = 9; + d.state = do_pong_resume; + assert(d.ws.wr_block_ != &d); d.ws.rd_op_.template emplace< read_frame_op>(std::move(*this)); return; } - d.state = 10; + d.state = do_pong; break; } - // call handler; - d.state = 99; - ec = error::closed; - break; - } + else if(d.ws.rd_fh_.op == opcode::pong) + { + code = close_code::none; + ping_data payload; + detail::read(payload, d.fb.data()); + if(d.ws.pong_cb_) + d.ws.pong_cb_(payload); + d.fb.reset(); + d.state = do_read_fh; + break; + } + assert(d.ws.rd_fh_.op == opcode::close); + { + detail::read(d.ws.cr_, d.fb.data(), code); + if(code != close_code::none) + { + // protocol error + d.state = do_fail; + break; + } + if(! d.ws.wr_close_) + { + auto cr = d.ws.cr_; + if(cr.code == close_code::none) + cr.code = close_code::normal; + cr.reason = ""; + d.fb.reset(); + d.ws.template write_close< + static_streambuf>(d.fb, cr); + if(d.ws.wr_block_) + { + // suspend + d.state = do_close_resume; + d.ws.rd_op_.template emplace< + read_frame_op>(std::move(*this)); + return; + } + d.state = do_close; + break; + } + // call handler; + ec = error::closed; + goto upcall; + } - // resume - case 9: - if(d.ws.error_) - { - // call handler - d.state = 99; - ec = boost::asio::error::operation_aborted; - break; - } - if(d.ws.wr_close_) - { - // call handler - d.state = 99; - ec = error::closed; - break; - } - d.state = 10; - break; + //------------------------------------------------------------------ - // send close - case 10: - d.state = 11; - assert(! d.ws.wr_block_); - d.ws.wr_block_ = &d; - boost::asio::async_write(d.ws.stream_, - d.fb.data(), std::move(*this)); - return;; + case do_pong_resume: + d.state = do_pong_resume + 1; + d.ws.get_io_service().post(bind_handler( + std::move(*this), ec, bytes_transferred)); + return; - // teardown - case 11: - d.state = 12; - websocket_helpers::call_async_teardown( - d.ws.next_layer(), std::move(*this)); - return; + case do_pong_resume + 1: + if(d.ws.failed_) + { + // call handler + ec = boost::asio::error::operation_aborted; + goto upcall; + } + d.state = do_pong; + break; // VFALCO fall through? - case 12: - // call handler - d.state = 99; - ec = error::closed; - break; + //------------------------------------------------------------------ - // resume - case 13: - if(d.ws.error_) - { - // call handler - d.state = 99; - ec = boost::asio::error::operation_aborted; - break; - } - if(d.ws.wr_close_) - { + case do_pong: + if(d.ws.wr_close_) + { + // ignore ping when closing + d.fb.reset(); + d.state = do_read_fh; + break; + } + // send pong + d.state = do_pong + 1; + assert(! d.ws.wr_block_); + d.ws.wr_block_ = &d; + boost::asio::async_write(d.ws.stream_, + d.fb.data(), std::move(*this)); + return; + + case do_pong + 1: d.fb.reset(); - d.state = 2; + d.state = do_read_fh; + d.ws.wr_block_ = nullptr; break; - } - d.state = 14; - break; - case 14: - // write ping/pong - d.state = 15; - assert(! d.ws.wr_block_); - d.ws.wr_block_ = &d; - boost::asio::async_write(d.ws.stream_, - d.fb.data(), std::move(*this)); - return; + //------------------------------------------------------------------ - // sent ping/pong - case 15: - d.fb.reset(); - d.state = 2; - d.ws.wr_block_ = nullptr; - break; + case do_close_resume: + d.state = do_close_resume + 1; + d.ws.get_io_service().post(bind_handler( + std::move(*this), ec, bytes_transferred)); + return; - // fail the connection - case 16: - if(! d.ws.wr_close_) - { + case do_close_resume + 1: + if(d.ws.failed_) + { + // call handler + d.state = do_call_handler; + ec = boost::asio::error::operation_aborted; + break; + } + if(d.ws.wr_close_) + { + // call handler + ec = error::closed; + goto upcall; + } + d.state = do_close; + break; + + //------------------------------------------------------------------ + + case do_close: + d.state = do_close + 1; + d.ws.wr_close_ = true; + assert(! d.ws.wr_block_); + d.ws.wr_block_ = &d; + boost::asio::async_write(d.ws.stream_, + d.fb.data(), std::move(*this)); + return; + + case do_close + 1: + d.state = do_close + 2; + websocket_helpers::call_async_teardown( + d.ws.next_layer(), std::move(*this)); + return; + + case do_close + 2: + // call handler + ec = error::closed; + goto upcall; + + //------------------------------------------------------------------ + + case do_fail: + if(d.ws.wr_close_) + { + d.state = do_fail + 4; + break; + } d.fb.reset(); d.ws.template write_close< static_streambuf>(d.fb, code); if(d.ws.wr_block_) { // suspend - d.state = 17; + d.state = do_fail + 2; d.ws.rd_op_.template emplace< read_frame_op>(std::move(*this)); return; } - d.state = 18; + // fall through + + case do_fail + 1: + d.ws.failed_ = true; + // send close frame + d.state = do_fail + 4; + d.ws.wr_close_ = true; + assert(! d.ws.wr_block_); + d.ws.wr_block_ = &d; + boost::asio::async_write(d.ws.stream_, + d.fb.data(), std::move(*this)); + return; + + case do_fail + 2: + d.state = do_fail + 3; + d.ws.get_io_service().post(bind_handler( + std::move(*this), ec, bytes_transferred)); + return; + + case do_fail + 3: + if(d.ws.failed_) + { + d.state = do_fail + 5; + break; + } + d.state = do_fail + 1; break; + + case do_fail + 4: + d.state = do_fail + 5; + websocket_helpers::call_async_teardown( + d.ws.next_layer(), std::move(*this)); + return; + + case do_fail + 5: + // call handler + ec = error::failed; + goto upcall; + + //------------------------------------------------------------------ + + case do_call_handler: + goto upcall; } - - // resume - case 17: - if(d.ws.wr_close_) - { - d.state = 19; - break; - } - d.state = 18; - break; - - case 18: - // send close - d.state = 19; - d.ws.wr_close_ = true; - assert(! d.ws.wr_block_); - d.ws.wr_block_ = &d; - boost::asio::async_write(d.ws.stream_, - d.fb.data(), std::move(*this)); - return; - - // teardown - case 19: - d.state = 20; - websocket_helpers::call_async_teardown( - d.ws.next_layer(), std::move(*this)); - return; - - case 20: - // call handler - d.state = 99; - ec = error::failed; - break; } + while(! ec); } - if(ec) - d.ws.error_ = true; +upcall: if(d.ws.wr_block_ == &d) d.ws.wr_block_ = nullptr; d.ws.wr_op_.maybe_invoke(); diff --git a/src/beast/include/beast/websocket/impl/read_op.ipp b/src/beast/include/beast/websocket/impl/read_op.ipp index 514fb7148e..89f840e22b 100644 --- a/src/beast/include/beast/websocket/impl/read_op.ipp +++ b/src/beast/include/beast/websocket/impl/read_op.ipp @@ -105,24 +105,34 @@ operator()(error_code const& ec, bool again) { auto& d = *d_; d.cont = d.cont || again; - while(! ec && d.state != 99) + while(! ec) { switch(d.state) { case 0: // read payload d.state = 1; +#if 0 + // VFALCO This causes dereference of null, because + // the handler is moved from the data block + // before asio_handler_deallocate is called. d.ws.async_read_frame( d.fi, d.sb, std::move(*this)); +#else + d.ws.async_read_frame(d.fi, d.sb, *this); +#endif return; // got payload case 1: d.op = d.fi.op; - d.state = d.fi.fin ? 99 : 0; + if(d.fi.fin) + goto upcall; + d.state = 0; break; } } +upcall: d.h(ec); } diff --git a/src/beast/include/beast/websocket/impl/response_op.ipp b/src/beast/include/beast/websocket/impl/response_op.ipp index 98287940a9..e1ed45e125 100644 --- a/src/beast/include/beast/websocket/impl/response_op.ipp +++ b/src/beast/include/beast/websocket/impl/response_op.ipp @@ -44,6 +44,9 @@ class stream::response_op , h(std::forward(h_)) , cont(cont_) { + // can't call stream::reset() here + // otherwise accept_op will malfunction + // if(resp.status != 101) final_ec = error::handshake_failed; } @@ -123,7 +126,7 @@ operator()(error_code ec, bool again) d.state = 99; ec = d.final_ec; if(! ec) - d.ws.role_ = role_type::server; + d.ws.open(detail::role_type::server); break; } } diff --git a/src/beast/include/beast/websocket/impl/stream.ipp b/src/beast/include/beast/websocket/impl/stream.ipp index ddc3f7aa9e..569f43b6e1 100644 --- a/src/beast/include/beast/websocket/impl/stream.ipp +++ b/src/beast/include/beast/websocket/impl/stream.ipp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -40,6 +41,13 @@ namespace websocket { namespace detail { +template +void +stream_base::open(role_type role) +{ + role_ = role; +} + template void stream_base::prepare_fh(close_code::value& code) @@ -76,7 +84,7 @@ stream_base::prepare_fh(close_code::value& code) } rd_size_ += rd_fh_.len; } - if(rd_size_ > rd_msg_max_) + if(rd_msg_max_ && rd_size_ > rd_msg_max_) { code = close_code::too_big; return; @@ -100,7 +108,7 @@ stream_base::write_close( fh.rsv3 = false; fh.len = cr.code == close_code::none ? 0 : 2 + cr.reason.size(); - fh.mask = role_ == role_type::client; + fh.mask = role_ == detail::role_type::client; if(fh.mask) fh.key = maskgen_(); detail::write(sb, fh); @@ -136,7 +144,7 @@ stream_base::write_close( template void stream_base::write_ping(Streambuf& sb, - opcode op, ping_payload_type const& data) + opcode op, ping_data const& data) { frame_header fh; fh.op = op; @@ -184,7 +192,8 @@ accept() "SyncStream requirements not met"); error_code ec; accept(boost::asio::null_buffers{}, ec); - detail::maybe_throw(ec, "accept"); + if(ec) + throw system_error{ec}; } template @@ -223,7 +232,8 @@ accept(ConstBufferSequence const& buffers) "ConstBufferSequence requirements not met"); error_code ec; accept(buffers, ec); - detail::maybe_throw(ec, "accept"); + if(ec) + throw system_error{ec}; } template @@ -239,10 +249,11 @@ accept(ConstBufferSequence const& buffers, error_code& ec) "ConstBufferSequence requirements not met"); using boost::asio::buffer_copy; using boost::asio::buffer_size; + reset(); stream_.buffer().commit(buffer_copy( stream_.buffer().prepare( buffer_size(buffers)), buffers)); - http::request_v1 m; + http::request_v1 m; http::read(next_layer(), stream_.buffer(), m, ec); if(ec) return; @@ -279,7 +290,8 @@ accept(http::request_v1 const& request) "SyncStream requirements not met"); error_code ec; accept(request, ec); - detail::maybe_throw(ec, "accept"); + if(ec) + throw system_error{ec}; } template @@ -291,8 +303,11 @@ accept(http::request_v1 const& req, { static_assert(is_SyncStream::value, "SyncStream requirements not met"); + reset(); auto const res = build_response(req); http::write(stream_, res, ec); + if(ec) + return; if(res.status != 101) { ec = error::handshake_failed; @@ -300,7 +315,7 @@ accept(http::request_v1 const& req, // teardown if Connection: close. return; } - role_ = role_type::server; + open(detail::role_type::server); } template @@ -316,6 +331,7 @@ async_accept(http::request_v1 const& req, beast::async_completion< AcceptHandler, void(error_code) > completion(handler); + reset(); response_op{ completion.handler, *this, req, boost_asio_handler_cont_helpers:: @@ -333,7 +349,8 @@ handshake(boost::string_ref const& host, "SyncStream requirements not met"); error_code ec; handshake(host, resource, ec); - detail::maybe_throw(ec, "upgrade"); + if(ec) + throw system_error{ec}; } template @@ -344,6 +361,7 @@ handshake(boost::string_ref const& host, { static_assert(is_SyncStream::value, "SyncStream requirements not met"); + reset(); std::string key; http::write(stream_, build_request(host, resource, key), ec); @@ -383,7 +401,8 @@ close(close_reason const& cr) "SyncStream requirements not met"); error_code ec; close(cr, ec); - detail::maybe_throw(ec, "close"); + if(ec) + throw system_error{ec}; } template @@ -398,7 +417,7 @@ close(close_reason const& cr, error_code& ec) detail::frame_streambuf fb; write_close(fb, cr); boost::asio::write(stream_, fb.data(), ec); - error_ = ec != 0; + failed_ = ec != 0; } template @@ -418,6 +437,45 @@ async_close(close_reason const& cr, CloseHandler&& handler) return completion.result.get(); } +template +void +stream:: +ping(ping_data const& payload) +{ + error_code ec; + ping(payload, ec); + if(ec) + throw system_error{ec}; +} + +template +void +stream:: +ping(ping_data const& payload, error_code& ec) +{ + detail::frame_streambuf sb; + write_ping( + sb, opcode::ping, payload); + boost::asio::write(stream_, sb.data(), ec); +} + +template +template +typename async_completion< + PingHandler, void(error_code)>::result_type +stream:: +async_ping(ping_data const& payload, PingHandler&& handler) +{ + static_assert(is_AsyncStream::value, + "AsyncStream requirements requirements not met"); + beast::async_completion< + PingHandler, void(error_code) + > completion(handler); + ping_op{ + completion.handler, *this, payload}; + return completion.result.get(); +} + template template void @@ -428,7 +486,8 @@ read(opcode& op, Streambuf& streambuf) "SyncStream requirements not met"); error_code ec; read(op, streambuf, ec); - detail::maybe_throw(ec, "read"); + if(ec) + throw system_error{ec}; } template @@ -481,7 +540,8 @@ read_frame(frame_info& fi, Streambuf& streambuf) "SyncStream requirements not met"); error_code ec; read_frame(fi, streambuf, ec); - detail::maybe_throw(ec, "read_some"); + if(ec) + throw system_error{ec}; } template @@ -500,8 +560,8 @@ read_frame(frame_info& fi, Streambuf& streambuf, error_code& ec) // read header detail::frame_streambuf fb; do_read_fh(fb, code, ec); - error_ = ec != 0; - if(error_) + failed_ = ec != 0; + if(failed_) return; if(code != close_code::none) break; @@ -513,8 +573,8 @@ read_frame(frame_info& fi, Streambuf& streambuf, error_code& ec) auto const mb = fb.prepare( static_cast(rd_fh_.len)); fb.commit(boost::asio::read(stream_, mb, ec)); - error_ = ec != 0; - if(error_) + failed_ = ec != 0; + if(failed_) return; if(rd_fh_.mask) detail::mask_inplace(mb, rd_key_); @@ -522,27 +582,23 @@ read_frame(frame_info& fi, Streambuf& streambuf, error_code& ec) } if(rd_fh_.op == opcode::ping) { - ping_payload_type data; - detail::read(data, fb.data(), code); - if(code != close_code::none) - break; + ping_data data; + detail::read(data, fb.data()); fb.reset(); write_ping( fb, opcode::pong, data); boost::asio::write(stream_, fb.data(), ec); - error_ = ec != 0; - if(error_) + failed_ = ec != 0; + if(failed_) return; continue; } else if(rd_fh_.op == opcode::pong) { - ping_payload_type data; - detail::read(data, fb.data(), code); - if(code != close_code::none) - break; - // VFALCO How to notify callers using - // the synchronous interface? + ping_data payload; + detail::read(payload, fb.data()); + if(pong_cb_) + pong_cb_(payload); continue; } assert(rd_fh_.op == opcode::close); @@ -560,8 +616,8 @@ read_frame(frame_info& fi, Streambuf& streambuf, error_code& ec) wr_close_ = true; write_close(fb, cr); boost::asio::write(stream_, fb.data(), ec); - error_ = ec != 0; - if(error_) + failed_ = ec != 0; + if(failed_) return; } break; @@ -578,8 +634,8 @@ read_frame(frame_info& fi, Streambuf& streambuf, error_code& ec) detail::clamp(rd_need_)); auto const bytes_transferred = stream_.read_some(smb, ec); - error_ = ec != 0; - if(error_) + failed_ = ec != 0; + if(failed_) return; rd_need_ -= bytes_transferred; auto const pb = prepare_buffers( @@ -610,23 +666,23 @@ read_frame(frame_info& fi, Streambuf& streambuf, error_code& ec) detail::frame_streambuf fb; write_close(fb, code); boost::asio::write(stream_, fb.data(), ec); - error_ = ec != 0; - if(error_) + failed_ = ec != 0; + if(failed_) return; } websocket_helpers::call_teardown(next_layer(), ec); - error_ = ec != 0; - if(error_) + failed_ = ec != 0; + if(failed_) return; ec = error::failed; - error_ = true; + failed_ = true; return; } if(! ec) websocket_helpers::call_teardown(next_layer(), ec); if(! ec) ec = error::closed; - error_ = ec != 0; + failed_ = ec != 0; } template @@ -658,7 +714,8 @@ write(ConstBufferSequence const& buffers) "SyncStream requirements not met"); error_code ec; write(buffers, ec); - detail::maybe_throw(ec, "write"); + if(ec) + throw system_error{ec}; } template @@ -719,7 +776,8 @@ write_frame(bool fin, ConstBufferSequence const& buffers) "SyncStream requirements not met"); error_code ec; write_frame(fin, buffers, ec); - detail::maybe_throw(ec, "write"); + if(ec) + throw system_error{ec}; } template @@ -744,7 +802,7 @@ write_frame(bool fin, ConstBufferSequence const& bs, error_code& ec) fh.rsv2 = false; fh.rsv3 = false; fh.len = buffer_size(bs); - fh.mask = role_ == role_type::client; + fh.mask = role_ == detail::role_type::client; if(fh.mask) fh.key = maskgen_(); detail::fh_streambuf fh_buf; @@ -754,22 +812,21 @@ write_frame(bool fin, ConstBufferSequence const& bs, error_code& ec) // send header and payload boost::asio::write(stream_, buffer_cat(fh_buf.data(), bs), ec); - error_ = ec != 0; + failed_ = ec != 0; return; } detail::prepared_key_type key; detail::prepare_key(key, fh.key); - auto const tmp_size = detail::clamp( - fh.len, wr_buf_size_); + auto const tmp_size = + detail::clamp(fh.len, mask_buf_size_); std::unique_ptr up( new std::uint8_t[tmp_size]); - auto const tmp = up.get(); std::uint64_t remain = fh.len; consuming_buffers cb(bs); { auto const n = detail::clamp(remain, tmp_size); - mutable_buffers_1 mb{tmp, n}; + mutable_buffers_1 mb{up.get(), n}; buffer_copy(mb, cb); cb.consume(n); remain -= n; @@ -779,7 +836,7 @@ write_frame(bool fin, ConstBufferSequence const& bs, error_code& ec) buffer_cat(fh_buf.data(), mb), ec); if(ec) { - error_ = ec != 0; + failed_ = ec != 0; return; } } @@ -787,7 +844,7 @@ write_frame(bool fin, ConstBufferSequence const& bs, error_code& ec) { auto const n = detail::clamp(remain, tmp_size); - mutable_buffers_1 mb{tmp, n}; + mutable_buffers_1 mb{up.get(), n}; buffer_copy(mb, cb); cb.consume(n); remain -= n; @@ -796,7 +853,7 @@ write_frame(bool fin, ConstBufferSequence const& bs, error_code& ec) boost::asio::write(stream_, mb, ec); if(ec) { - error_ = ec != 0; + failed_ = ec != 0; return; } } @@ -826,6 +883,23 @@ async_write_frame(bool fin, //------------------------------------------------------------------------------ +template +void +stream:: +reset() +{ + failed_ = false; + rd_need_ = 0; + rd_cont_ = false; + wr_close_ = false; + wr_cont_ = false; + wr_block_ = nullptr; // should be nullptr on close anyway + pong_data_ = nullptr; // should be nullptr on close anyway + + stream_.buffer().consume( + stream_.buffer().size()); +} + template http::request_v1 stream:: @@ -860,8 +934,11 @@ build_response(http::request_v1 const& req) res.reason = http::reason_string(res.status); res.version = req.version; res.body = text; - // VFALCO TODO respect keep-alive here - prepare(res); + (*d_)(res); + prepare(res, + (is_keep_alive(req) && keep_alive_) ? + http::connection::keep_alive : + http::connection::close); return res; }; if(req.version < 11) @@ -874,17 +951,28 @@ build_response(http::request_v1 const& req) return err("Missing Host"); if(! req.headers.exists("Sec-WebSocket-Key")) return err("Missing Sec-WebSocket-Key"); + if(! rfc2616::token_in_list( + req.headers["Upgrade"], "websocket")) + return err("Missing websocket Upgrade token"); { auto const version = req.headers["Sec-WebSocket-Version"]; if(version.empty()) return err("Missing Sec-WebSocket-Version"); if(version != "13") - return err("Unsupported Sec-WebSocket-Version"); + { + http::response_v1 res; + res.status = 426; + res.reason = http::reason_string(res.status); + res.version = req.version; + res.headers.insert("Sec-WebSocket-Version", "13"); + prepare(res, + (is_keep_alive(req) && keep_alive_) ? + http::connection::keep_alive : + http::connection::close); + return res; + } } - if(! rfc2616::token_in_list( - req.headers["Upgrade"], "websocket")) - return err("Missing websocket Upgrade token"); http::response_v1 res; res.status = 101; res.reason = http::reason_string(res.status); @@ -893,7 +981,6 @@ build_response(http::request_v1 const& req) { auto const key = req.headers["Sec-WebSocket-Key"]; - res.headers.insert("Sec-WebSocket-Key", key); res.headers.insert("Sec-WebSocket-Accept", detail::make_sec_ws_accept(key)); } @@ -912,6 +999,8 @@ do_response(http::response_v1 const& res, { // VFALCO Review these error codes auto fail = [&]{ ec = error::response_failed; }; + if(res.version < 11) + return fail(); if(res.status != 101) return fail(); if(! is_upgrade(res)) @@ -924,7 +1013,7 @@ do_response(http::response_v1 const& res, if(res.headers["Sec-WebSocket-Accept"] != detail::make_sec_ws_accept(key)) return fail(); - role_ = role_type::client; + open(detail::role_type::client); } template diff --git a/src/beast/include/beast/websocket/impl/teardown.ipp b/src/beast/include/beast/websocket/impl/teardown.ipp index 41daef592e..fa7133b282 100644 --- a/src/beast/include/beast/websocket/impl/teardown.ipp +++ b/src/beast/include/beast/websocket/impl/teardown.ipp @@ -56,8 +56,7 @@ public: } void - operator()( - error_code ec, std::size_t, bool again = true); + operator()(error_code ec, std::size_t, bool again = true); friend void* asio_handler_allocate(std::size_t size, diff --git a/src/beast/include/beast/websocket/impl/write_frame_op.ipp b/src/beast/include/beast/websocket/impl/write_frame_op.ipp index 7cc100ad03..f1b3336753 100644 --- a/src/beast/include/beast/websocket/impl/write_frame_op.ipp +++ b/src/beast/include/beast/websocket/impl/write_frame_op.ipp @@ -57,17 +57,17 @@ class stream::write_frame_op opcode::cont : ws.wr_opcode_; ws.wr_cont_ = ! fin; fh.fin = fin; - fh.rsv1 = 0; - fh.rsv2 = 0; - fh.rsv3 = 0; + fh.rsv1 = false; + fh.rsv2 = false; + fh.rsv3 = false; fh.len = boost::asio::buffer_size(cb); - fh.mask = ws.role_ == role_type::client; + fh.mask = ws.role_ == detail::role_type::client; if(fh.mask) { fh.key = ws.maskgen_(); detail::prepare_key(key, fh.key); tmp_size = detail::clamp( - fh.len, ws.wr_buf_size_); + fh.len, ws.mask_buf_size_); tmp = boost_asio_handler_alloc_helpers:: allocate(tmp_size, h); remain = fh.len; @@ -100,18 +100,17 @@ public: std::forward(h), ws, std::forward(args)...)) { - (*this)(error_code{}, 0, false); + (*this)(error_code{}, false); } void operator()() { - auto& d = *d_; - d.cont = false; - (*this)(error_code{}, 0, false); + (*this)(error_code{}); } - void operator()(error_code ec, - std::size_t bytes_transferred, bool again = true); + void operator()(error_code ec, std::size_t); + + void operator()(error_code ec, bool again = true); friend void* asio_handler_allocate( @@ -144,19 +143,33 @@ public: } }; +template +template +void +stream:: +write_frame_op:: +operator()(error_code ec, std::size_t) +{ + auto& d = *d_; + if(ec) + d.ws.failed_ = true; + (*this)(ec); +} + template template void stream:: write_frame_op:: -operator()( - error_code ec, std::size_t bytes_transferred, bool again) +operator()(error_code ec, bool again) { using boost::asio::buffer_copy; using boost::asio::mutable_buffers_1; auto& d = *d_; d.cont = d.cont || again; - while(! ec && d.state != 99) + if(ec) + goto upcall; + for(;;) { switch(d.state) { @@ -164,41 +177,27 @@ operator()( if(d.ws.wr_block_) { // suspend - d.state = 1; + d.state = 3; d.ws.wr_op_.template emplace< write_frame_op>(std::move(*this)); return; } - if(d.ws.error_) + if(d.ws.failed_ || d.ws.wr_close_) { // call handler d.state = 99; d.ws.get_io_service().post( bind_handler(std::move(*this), - boost::asio::error::operation_aborted, 0)); + boost::asio::error::operation_aborted)); return; } - assert(! d.ws.wr_close_); - d.state = 2; - break; + // fall through - // resume case 1: - if(d.ws.error_) - { - // call handler - d.state = 99; - ec = boost::asio::error::operation_aborted; - break; - } - d.state = 2; - break; - - case 2: { if(! d.fh.mask) { - // send header and payload + // send header and entire payload d.state = 99; assert(! d.ws.wr_block_); d.ws.wr_block_ = &d; @@ -215,7 +214,7 @@ operator()( d.remain -= n; detail::mask_inplace(mb, d.key); // send header and payload - d.state = d.remain > 0 ? 3 : 99; + d.state = d.remain > 0 ? 2 : 99; assert(! d.ws.wr_block_); d.ws.wr_block_ = &d; boost::asio::async_write(d.ws.stream_, @@ -225,7 +224,7 @@ operator()( } // sent masked payload - case 3: + case 2: { auto const n = detail::clamp(d.remain, d.tmp_size); @@ -238,24 +237,41 @@ operator()( // send payload if(d.remain == 0) d.state = 99; - assert(! d.ws.wr_block_); - d.ws.wr_block_ = &d; + assert(d.ws.wr_block_ == &d); boost::asio::async_write( d.ws.stream_, mb, std::move(*this)); return; } + + case 3: + d.state = 4; + d.ws.get_io_service().post(bind_handler( + std::move(*this), ec)); + return; + + case 4: + if(d.ws.failed_ || d.ws.wr_close_) + { + // call handler + ec = boost::asio::error::operation_aborted; + goto upcall; + } + d.state = 1; + break; + + case 99: + goto upcall; } } - if(ec) - d.ws.error_ = true; - if(d.ws.wr_block_ == &d) - d.ws.wr_block_ = nullptr; +upcall: if(d.tmp) { boost_asio_handler_alloc_helpers:: deallocate(d.tmp, d.tmp_size, d.h); d.tmp = nullptr; } + if(d.ws.wr_block_ == &d) + d.ws.wr_block_ = nullptr; d.ws.rd_op_.maybe_invoke(); d.h(ec); } diff --git a/src/beast/include/beast/websocket/impl/write_op.ipp b/src/beast/include/beast/websocket/impl/write_op.ipp index 0bbcea255d..ebffc2623b 100644 --- a/src/beast/include/beast/websocket/impl/write_op.ipp +++ b/src/beast/include/beast/websocket/impl/write_op.ipp @@ -109,7 +109,7 @@ operator()(error_code ec, bool again) { auto& d = *d_; d.cont = d.cont || again; - while(! ec && d.state != 99) + if(! ec) { switch(d.state) { @@ -126,6 +126,9 @@ operator()(error_code ec, bool again) d.ws.async_write_frame(fin, pb, std::move(*this)); return; } + + case 99: + break; } } d.h(ec); diff --git a/src/beast/include/beast/websocket/option.hpp b/src/beast/include/beast/websocket/option.hpp index abe7db35df..a26927a25c 100644 --- a/src/beast/include/beast/websocket/option.hpp +++ b/src/beast/include/beast/websocket/option.hpp @@ -8,11 +8,13 @@ #ifndef BEAST_WEBSOCKET_OPTION_HPP #define BEAST_WEBSOCKET_OPTION_HPP +#include #include #include #include #include #include +#include namespace beast { namespace websocket { @@ -147,6 +149,45 @@ struct keep_alive }; #endif +/** Mask buffer size option. + + Sets the size of the buffer allocated when the implementation + must allocate memory to apply the mask to a payload. Only affects + streams operating in the client role, since only clients send + masked frames. Lowering the size of the buffer can decrease the + memory requirements for each connection, while increasing the size + of the buffer can reduce the number of calls made to the next + layer to write masked data. + + The default setting is 4096. The minimum value is 1. + + @note Objects of this type are passed to @ref stream::set_option. + + @par Example + Setting the write buffer size. + @code + ... + websocket::stream ws(ios); + ws.set_option(mask_buffer_size{8192}); + @endcode +*/ +#if GENERATING_DOCS +using mask_buffer_size = implementation_defined; +#else +struct mask_buffer_size +{ + std::size_t value; + + explicit + mask_buffer_size(std::size_t n) + : value(n) + { + if(n == 0) + throw std::domain_error("invalid mask buffer size"); + } +}; +#endif + /** Message type option. This controls the opcode set for outgoing messages. Valid @@ -184,6 +225,50 @@ struct message_type }; #endif +/** Pong callback option. + + Sets the callback to be invoked whenever a pong is received + during a call to @ref read, @ref read_frame, @ref async_read, + or @ref async_read_frame. + + Unlike completion handlers, the callback will be invoked for + each received pong during a call to any synchronous or + asynchronous read function. The operation is passive, with + no associated error code, and triggered by reads. + + The signature of the callback must be: + @code + void callback( + ping_data const& payload // Payload of the pong frame + ); + @endcode + + If the read operation receiving a pong frame is an asynchronous + operation, the callback will be invoked using the same method as + that used to invoke the final handler. + + @note To remove the pong callback, construct the option with + no parameters: `set_option(pong_callback{})` +*/ +#if GENERATING_DOCS +using pong_callback = implementation_defined; +#else +struct pong_callback +{ + detail::pong_cb value; + + pong_callback() = default; + pong_callback(pong_callback&&) = default; + pong_callback(pong_callback const&) = default; + + explicit + pong_callback(detail::pong_cb f) + : value(std::move(f)) + { + } +}; +#endif + /** Read buffer size option. Sets the number of bytes allocated to the socket's read buffer. @@ -224,7 +309,8 @@ struct read_buffer_size frame headers indicating a size that would bring the total message size over this limit will cause a protocol failure. - The default setting is 16 megabytes. + The default setting is 16 megabytes. A value of zero indicates + a limit of `std::numeric_limits::max()`. @note Objects of this type are passed to @ref stream::set_option. @@ -251,44 +337,6 @@ struct read_message_max }; #endif -/** Write buffer size option. - - Sets the number of bytes allocated to the socket's write buffer. - This buffer is used to hold masked frame payload data. Lowering - the size of the buffer can decrease the memory requirements for - each connection, at the cost of an increased number of calls to - perform socket writes. - - This setting does not affect connections operating in the server - role, since servers do not apply a masking key to frame payloads. - - The default setting is 4096. The minimum value is 1024. - - @note Objects of this type are passed to @ref stream::set_option. - - @par Example - Setting the write buffer size. - @code - ... - websocket::stream ws(ios); - ws.set_option(write_buffer_size{8192}); - @endcode -*/ -#if GENERATING_DOCS -using write_buffer_size = implementation_defined; -#else -struct write_buffer_size -{ - std::size_t value; - - explicit - write_buffer_size(std::size_t n) - : value(n) - { - } -}; -#endif - } // websocket } // beast diff --git a/src/beast/include/beast/websocket/rfc6455.hpp b/src/beast/include/beast/websocket/rfc6455.hpp index 49ed2fe08a..22e43b2c55 100644 --- a/src/beast/include/beast/websocket/rfc6455.hpp +++ b/src/beast/include/beast/websocket/rfc6455.hpp @@ -60,6 +60,13 @@ enum protocol_error = 1002, unknown_data = 1003, + + /// Indicates a received close frame has no close code + //no_code = 1005, // TODO + + /// Indicates the connection was closed without receiving a close frame + no_close = 1006, + bad_payload = 1007, policy_error = 1008, too_big = 1009, @@ -80,12 +87,11 @@ enum } // close_code #endif -#if ! GENERATING_DOCS -using reason_string_type = - static_string<123, char>; -using ping_payload_type = - static_string<125, char>; -#endif +/// The type representing the reason string in a close frame. +using reason_string = static_string<123, char>; + +/// The type representing the payload of ping and pong messages. +using ping_data = static_string<125, char>; /** Description of the close reason. @@ -98,7 +104,7 @@ struct close_reason close_code::value code = close_code::none; /// The optional utf8-encoded reason string. - reason_string_type reason; + reason_string reason; /** Default constructor. @@ -114,17 +120,17 @@ struct close_reason } /// Construct from a reason. code is close_code::normal. - template - close_reason(CharT const* reason_) + template + close_reason(char const (&reason_)[N]) : code(close_code::normal) , reason(reason_) { } /// Construct from a code and reason. - template + template close_reason(close_code::value code_, - CharT const* reason_) + char const (&reason_)[N]) : code(code_) , reason(reason_) { @@ -137,20 +143,6 @@ struct close_reason } }; -#if ! GENERATING_DOCS - -/// Identifies the role of a WebSockets stream. -enum class role_type -{ - /// Stream is operating as a client. - client, - - /// Stream is operating as a server. - server -}; - -#endif - } // websocket } // beast diff --git a/src/beast/include/beast/websocket/stream.hpp b/src/beast/include/beast/websocket/stream.hpp index 76bf03f1cb..7ee2b1602b 100644 --- a/src/beast/include/beast/websocket/stream.hpp +++ b/src/beast/include/beast/websocket/stream.hpp @@ -69,8 +69,8 @@ struct frame_info @tparam NextLayer The type representing the next layer, to which data will be read and written during operations. For synchronous operations, the type must support the @b `SyncStream` concept. - For asynchronous operations, the type must support the @b `AsyncStream` - concept. + For asynchronous operations, the type must support the + @b `AsyncStream` concept. @note A stream object must not be destroyed while there are pending asynchronous operations associated with it. @@ -84,7 +84,7 @@ struct frame_info template class stream : public detail::stream_base { - friend class ws_test; + friend class stream_test; streambuf_readstream stream_; @@ -124,12 +124,12 @@ public: /** Construct a WebSocket stream. - This constructor creates a websocket stream and initialises + This constructor creates a websocket stream and initializes the next layer object. @throws Any exceptions thrown by the NextLayer constructor. - @param args The arguments to be passed to initialise the + @param args The arguments to be passed to initialize the next layer object. The arguments are forwarded to the next layer's constructor. */ @@ -198,6 +198,13 @@ public: wr_opcode_ = o.value; } + /// Set the pong callback + void + set_option(pong_callback o) + { + pong_cb_ = std::move(o.value); + } + /// Set the read buffer size void set_option(read_buffer_size const& o) @@ -212,11 +219,11 @@ public: rd_msg_max_ = o.value; } - /// Set the size of the write buffer + /// Set the size of the mask buffer void - set_option(write_buffer_size const& o) + set_option(mask_buffer_size const& o) { - wr_buf_size_ = std::max(o.value, 1024); + mask_buf_size_ = o.value; stream_.capacity(o.value); } @@ -788,7 +795,7 @@ public: /** Send a WebSocket close frame. - This function is used to sycnhronously send a close frame on + This function is used to synchronously send a close frame on the stream. The call blocks until one of the following is true: @li The close frame finishes sending. @@ -817,7 +824,7 @@ public: /** Send a WebSocket close frame. - This function is used to sycnhronously send a close frame on + This function is used to synchronously send a close frame on the stream. The call blocks until one of the following is true: @li The close frame finishes sending. @@ -844,7 +851,7 @@ public: void close(close_reason const& cr, error_code& ec); - /** Start an asycnhronous operation to send a WebSocket close frame. + /** Start an asynchronous operation to send a WebSocket close frame. This function is used to asynchronously send a close frame on the stream. This function call always returns immediately. The @@ -858,7 +865,7 @@ public: This operation is implemented in terms of one or more calls to the next layer's `async_write_some` functions, and is known as a composed operation. The program must ensure that the - stream performs no other write operations (such as + stream performs no other write operations (such as @ref async_ping, @ref stream::async_write, @ref stream::async_write_frame, or @ref stream::async_close) until this operation completes. @@ -896,6 +903,84 @@ public: #endif async_close(close_reason const& cr, CloseHandler&& handler); + /** Send a WebSocket ping frame. + + This function is used to synchronously send a ping frame on + the stream. The call blocks until one of the following is true: + + @li The ping frame finishes sending. + + @li An error occurs on the stream. + + This function is implemented in terms of one or more calls to the + next layer's `write_some` functions. + + @param payload The payload of the ping message, which may be empty. + + @throws boost::system::system_error Thrown on failure. + */ + void + ping(ping_data const& payload); + + /** Send a WebSocket ping frame. + + This function is used to synchronously send a ping frame on + the stream. The call blocks until one of the following is true: + + @li The ping frame finishes sending. + + @li An error occurs on the stream. + + This function is implemented in terms of one or more calls to the + next layer's `write_some` functions. + + @param payload The payload of the ping message, which may be empty. + + @param ec Set to indicate what error occurred, if any. + */ + void + ping(ping_data const& payload, error_code& ec); + + /** Start an asynchronous operation to send a WebSocket ping frame. + + This function is used to asynchronously send a ping frame to + the stream. The function call always returns immediately. The + asynchronous operation will continue until one of the following + is true: + + @li The entire ping frame is sent. + + @li An error occurs on the stream. + + This operation is implemented in terms of one or more calls to the + next layer's `async_write_some` functions, and is known as a + composed operation. The program must ensure that the + stream performs no other writes until this operation completes. + + @param payload The payload of the ping message, which may be empty. + + @param handler The handler to be called when the read operation + completes. Copies will be made of the handler as required. The + function signature of the handler must be: + @code + void handler( + error_code const& error // Result of operation + ); + @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using `boost::asio::io_service::post`. + */ + template + #if GENERATING_DOCS + void_or_deduced + #else + typename async_completion< + PingHandler, void(error_code)>::result_type + #endif + async_ping(ping_data const& payload, PingHandler&& handler); + /** Read a message from the stream. This function is used to synchronously read a message from @@ -969,7 +1054,7 @@ public: /** Start an asynchronous operation to read a message from the stream. - This function is used to asychronously read a message from + This function is used to asynchronously read a message from the stream. The function call always returns immediately. The asynchronous operation will continue until one of the following is true: @@ -981,7 +1066,7 @@ public: This operation is implemented in terms of one or more calls to the next layer's `async_read_some` and `async_write_some` functions, and is known as a composed operation. The program must - ensure that the stream performs no other until this operation + ensure that the stream performs no other reads until this operation completes. Upon a success, op is set to either binary or text depending on @@ -989,15 +1074,14 @@ public: hold all the message payload bytes (which may be zero in length). Control frames encountered while reading frame or message data - are handled automatically. Pings are replied to, pongs are noted, - and close frames initiate the WebSocket close procedure. When a - close frame is received, this call will eventually return - @ref error::closed. Because of the need to handle control frames, - read operations can cause writes to take place. These writes are - managed transparently; callers can still have one active - asynchronous read and asynchronous write operation pending - simultaneously (a user initiated call to @ref async_close - counts as a write). + are handled automatically. Pings are replied to, pongs cause + an outstanding call to `async_ping` to complete, and close + frames initiate the WebSocket close procedure. When a close + frame is received, this call will eventually return + @ref error::closed. Because of the need to handle control + frames, these read operations can cause writes to take place. + Despite this, calls to `async_read` and `async_read_frame` + only count as read operations. @param op A value to receive the message type. This object must remain valid until the handler is called. @@ -1104,7 +1188,7 @@ public: /** Start an asynchronous operation to read a message frame from the stream. - This function is used to asychronously read a single message + This function is used to asynchronously read a single message frame from the websocket. The function call always returns immediately. The asynchronous operation will continue until one of the following conditions is true: @@ -1116,7 +1200,7 @@ public: This operation is implemented in terms of one or more calls to the next layer's `async_read_some` and `async_write_some` functions, and is known as a composed operation. The program must - ensure that the stream performs no other until this operation + ensure that the stream performs no other reads until this operation completes. Upon a successful completion, fi is filled out to reflect the @@ -1242,7 +1326,7 @@ public: /** Start an asynchronous operation to write a message to the stream. - This function is used to asychronously write a message to + This function is used to asynchronously write a message to the stream. The function call always returns immediately. The asynchronous operation will continue until one of the following conditions is true: @@ -1407,11 +1491,15 @@ private: template class accept_op; template class close_op; template class handshake_op; + template class ping_op; template class response_op; - template class read_op; - template class read_frame_op; template class write_op; template class write_frame_op; + template class read_op; + template class read_frame_op; + + void + reset(); http::request_v1 build_request(boost::string_ref const& host, diff --git a/src/beast/scripts/build-and-test.sh b/src/beast/scripts/build-and-test.sh index 42f04810cc..d71b6e23cd 100755 --- a/src/beast/scripts/build-and-test.sh +++ b/src/beast/scripts/build-and-test.sh @@ -6,7 +6,16 @@ set -ex ################################## ENVIRONMENT ################################# -export PATH=$VALGRIND_ROOT/bin:$LCOV_ROOT/usr/bin:$PATH +# If not CI, then set some defaults +if [[ "${CI:-}" == "" ]]; then + TRAVIS_BRANCH=${TRAVIS_BRANCH:-feature} + CC=${CC:-gcc} + ADDRESS_MODEL=${ADDRESS_MODEL:-64} + VARIANT=${VARIANT:-debug} + # If running locally we assume we have lcov/valgrind on PATH +else + export PATH=$VALGRIND_ROOT/bin:$LCOV_ROOT/usr/bin:$PATH +fi echo "using toolset: $CC" echo "using variant: $VARIANT" @@ -19,10 +28,25 @@ function run_tests_with_gdb { for x in bin/**/*-tests; do scripts/run-with-gdb.sh "$x"; done } +function run_tests { + for x in bin/**/*-tests; do "$x"; done +} + +num_procs=1 +if [[ $(uname) == "Darwin" ]]; then + num_procs=$(sysctl -n hw.ncpu) +elif [[ $(uname -s) == "Linux" ]]; then + num_procs=$(lscpu -p | grep -v '^#' | sort -u -t, -k 2,4 | wc -l) # physical cores + virt_num_procs=$(nproc) # CircleCI returns 32 phys procs, but 1 virt proc + if (("$virt_num_procs" < "$num_procs")); then + num_procs=$virt_num_procs + fi +fi + function build_beast { $BOOST_ROOT/bjam toolset=$CC \ variant=$VARIANT \ - address-model=$ADDRESS_MODEL + address-model=$ADDRESS_MODEL -j${num_procs} } ##################################### BUILD #################################### @@ -38,24 +62,24 @@ if [[ $VARIANT == "coverage" ]]; then lcov --no-external -c -i -d . -o baseline.info > /dev/null # Perform test - run_tests_with_gdb + run_tests # Run autobahn tests - export SERVER=`find . -name "websocket-echo"` - nohup scripts/run-with-gdb.sh $SERVER& + export SERVER=$(find . -name "websocket-echo") + nohup $SERVER& # We need to wait a while so wstest can connect! sleep 5 -# cd scripts && wstest -m fuzzingclient -# cd .. + cd scripts && wstest -m fuzzingclient + cd .. # Show the output cat nohup.out rm nohup.out jobs + sleep 5 # Kill it gracefully kill -INT %1 - sleep 1 - kill -INT %1 || echo "Dead already" + wait # Create test coverage data file lcov --no-external -c -d . -o testrun.info > /dev/null diff --git a/src/beast/scripts/install-dependencies.sh b/src/beast/scripts/install-dependencies.sh index 1408d709e9..34eb860481 100755 --- a/src/beast/scripts/install-dependencies.sh +++ b/src/beast/scripts/install-dependencies.sh @@ -9,7 +9,7 @@ do test -x $( type -p ${g}-$GCC_VER ) ln -sv $(type -p ${g}-$GCC_VER) $HOME/bin/${g} done -for c in clang clang++ +for c in clang clang++ llvm-symbolizer do test -x $( type -p ${c}-$CLANG_VER ) ln -sv $(type -p ${c}-$CLANG_VER) $HOME/bin/${c} diff --git a/src/beast/test/Jamfile b/src/beast/test/Jamfile index f8030dd7ba..02fb70a1b7 100644 --- a/src/beast/test/Jamfile +++ b/src/beast/test/Jamfile @@ -35,6 +35,7 @@ unit-test core-tests : core/write_streambuf.cpp core/detail/base64.cpp core/detail/empty_base_optimization.cpp + core/detail/get_lowest_layer.cpp core/detail/sha1.cpp ; @@ -43,6 +44,7 @@ unit-test http-tests : http/basic_headers.cpp http/basic_parser_v1.cpp http/body_type.cpp + http/concepts.cpp http/empty_body.cpp http/headers.cpp http/message.cpp @@ -57,7 +59,6 @@ unit-test http-tests : http/status.cpp http/streambuf_body.cpp http/string_body.cpp - http/type_check.cpp http/write.cpp http/detail/chunk_encode.cpp ; @@ -77,6 +78,7 @@ unit-test websocket-tests : websocket/teardown.cpp websocket/detail/frame.cpp websocket/detail/mask.cpp + websocket/detail/stream_base.cpp websocket/detail/utf8_checker.cpp ; diff --git a/src/beast/test/core/CMakeLists.txt b/src/beast/test/core/CMakeLists.txt index defd66f6b8..530a60e86f 100644 --- a/src/beast/test/core/CMakeLists.txt +++ b/src/beast/test/core/CMakeLists.txt @@ -29,6 +29,7 @@ add_executable (core-tests write_streambuf.cpp detail/base64.cpp detail/empty_base_optimization.cpp + detail/get_lowest_layer.cpp detail/sha1.cpp ) diff --git a/src/beast/test/core/buffer_cat.cpp b/src/beast/test/core/buffer_cat.cpp index c943e41611..c78f75c677 100644 --- a/src/beast/test/core/buffer_cat.cpp +++ b/src/beast/test/core/buffer_cat.cpp @@ -66,6 +66,14 @@ public: expect(buffer_size(buffer_cat( sb1.data(), sb2.data())) == 12); } + for(auto it = bs.begin(); it != bs.end(); ++it) + { + decltype(bs)::const_iterator copy; + copy = it; + expect(copy == it); + copy = copy; + expect(copy == it); + } } void testIterators() diff --git a/src/beast/test/core/detail/get_lowest_layer.cpp b/src/beast/test/core/detail/get_lowest_layer.cpp new file mode 100644 index 0000000000..1ffc281735 --- /dev/null +++ b/src/beast/test/core/detail/get_lowest_layer.cpp @@ -0,0 +1,88 @@ +// +// Copyright (c) 2013-2016 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 + +#include +#include + +namespace beast { +namespace detail { + +class get_lowest_layer_test + : public beast::unit_test::suite +{ +public: + struct F1 + { + }; + + struct F2 + { + }; + + template + struct F3 + { + using next_layer_type = + typename std::remove_reference::type; + + using lowest_layer_type = typename + get_lowest_layer::type; + }; + + template + struct F4 + { + using next_layer_type = + typename std::remove_reference::type; + + using lowest_layer_type = typename + get_lowest_layer::type; + }; + + void + run() + { + static_assert(! has_lowest_layer::value, ""); + static_assert(! has_lowest_layer::value, ""); + static_assert(has_lowest_layer>::value, ""); + static_assert(has_lowest_layer>>::value, ""); + + static_assert(std::is_same< + get_lowest_layer::type, F1>::value, ""); + + static_assert(std::is_same< + get_lowest_layer::type, F2>::value, ""); + + static_assert(std::is_same< + get_lowest_layer>::type, F1>::value, ""); + + static_assert(std::is_same< + get_lowest_layer>::type, F2>::value, ""); + + static_assert(std::is_same< + get_lowest_layer>::type, F1>::value, ""); + + static_assert(std::is_same< + get_lowest_layer>::type, F2>::value, ""); + + static_assert(std::is_same< + get_lowest_layer>>::type, F1>::value, ""); + + static_assert(std::is_same< + get_lowest_layer>>::type, F2>::value, ""); + + pass(); + } +}; + +BEAST_DEFINE_TESTSUITE(get_lowest_layer,core,beast); + +} // detail +} // beast diff --git a/src/beast/test/core/prepare_buffers.cpp b/src/beast/test/core/prepare_buffers.cpp index 4c9d9df851..38d71a4aff 100644 --- a/src/beast/test/core/prepare_buffers.cpp +++ b/src/beast/test/core/prepare_buffers.cpp @@ -89,6 +89,7 @@ public: void testIterator() { + using boost::asio::buffer_size; using boost::asio::const_buffer; char b[3]; std::array bs{{ @@ -98,7 +99,12 @@ public: auto pb = prepare_buffers(2, bs); std::size_t n = 0; for(auto it = pb.end(); it != pb.begin(); --it) + { + decltype(pb)::const_iterator it2(std::move(it)); + expect(buffer_size(*it2) == 1); + it = std::move(it2); ++n; + } expect(n == 2); } diff --git a/src/beast/test/http/CMakeLists.txt b/src/beast/test/http/CMakeLists.txt index f3959b5b75..6b2b8c374c 100644 --- a/src/beast/test/http/CMakeLists.txt +++ b/src/beast/test/http/CMakeLists.txt @@ -10,6 +10,7 @@ add_executable (http-tests basic_headers.cpp basic_parser_v1.cpp body_type.cpp + concepts.cpp empty_body.cpp headers.cpp message.cpp @@ -24,7 +25,6 @@ add_executable (http-tests status.cpp streambuf_body.cpp string_body.cpp - type_check.cpp write.cpp detail/chunk_encode.cpp ) diff --git a/src/beast/test/http/basic_parser_v1.cpp b/src/beast/test/http/basic_parser_v1.cpp index 99355e01d1..bef3d4cdbe 100644 --- a/src/beast/test/http/basic_parser_v1.cpp +++ b/src/beast/test/http/basic_parser_v1.cpp @@ -532,6 +532,7 @@ public: void testInvalidMatrix() { using boost::asio::buffer; + using boost::asio::buffer_copy; std::string s; for(std::size_t n = 0;; ++n) @@ -549,15 +550,24 @@ public: s[n] = 0; for(std::size_t m = 1; m < len - 1; ++m) { + // Use separately allocated buffers so + // address sanitizer has something to chew on. + // + std::unique_ptr p1(new char[m]); + std::unique_ptr p2(new char[len - m]); + auto const b1 = buffer(p1.get(), m); + auto const b2 = buffer(p2.get(), len - m); + buffer_copy(b1, buffer(s.data(), m)); + buffer_copy(b2, buffer(s.data() + m, len - m)); null_parser p; error_code ec; - p.write(buffer(s.data(), m), ec); + p.write(b1, ec); if(ec) { pass(); continue; } - p.write(buffer(s.data() + m, len - m), ec); + p.write(b2, ec); expect(ec); } } diff --git a/src/beast/test/http/type_check.cpp b/src/beast/test/http/concepts.cpp similarity index 88% rename from src/beast/test/http/type_check.cpp rename to src/beast/test/http/concepts.cpp index 82020206a3..e540397bdc 100644 --- a/src/beast/test/http/type_check.cpp +++ b/src/beast/test/http/concepts.cpp @@ -6,4 +6,4 @@ // // Test that header file is self-contained. -#include +#include diff --git a/src/beast/test/http/message_v1.cpp b/src/beast/test/http/message_v1.cpp index 0b61131742..d86097ddd8 100644 --- a/src/beast/test/http/message_v1.cpp +++ b/src/beast/test/http/message_v1.cpp @@ -59,6 +59,7 @@ public: } catch(std::exception const&) { + pass(); } m.headers.erase("Content-Length"); m.headers.insert("Connection", "keep-alive"); @@ -69,7 +70,12 @@ public: } catch(std::exception const&) { + pass(); } + m.version = 11; + m.headers.erase("Connection"); + m.headers.insert("Connection", "close"); + expect(! is_keep_alive(m)); } void run() override diff --git a/src/beast/test/http/parser_bench.cpp b/src/beast/test/http/parser_bench.cpp index 086d96ed2e..aacda98d6c 100644 --- a/src/beast/test/http/parser_bench.cpp +++ b/src/beast/test/http/parser_bench.cpp @@ -85,7 +85,7 @@ public: { using namespace std::chrono; using clock_type = std::chrono::high_resolution_clock; - log << name; + log << name << std::endl; for(std::size_t trial = 1; trial <= repeat; ++trial) { auto const t0 = clock_type::now(); @@ -93,7 +93,7 @@ public: auto const elapsed = clock_type::now() - t0; log << "Trial " << trial << ": " << - duration_cast(elapsed).count() << " ms"; + duration_cast(elapsed).count() << " ms" << std::endl; } } @@ -109,10 +109,10 @@ public: static std::size_t constexpr Repeat = 50; log << "sizeof(request parser) == " << - sizeof(basic_parser_v1>); + sizeof(basic_parser_v1>) << '\n'; log << "sizeof(response parser) == " << - sizeof(basic_parser_v1>); + sizeof(basic_parser_v1>)<< '\n'; testcase << "Parser speed test, " << ((Repeat * size_ + 512) / 1024) << "KB in " << diff --git a/src/beast/test/http/streambuf_body.cpp b/src/beast/test/http/streambuf_body.cpp index a036af6bba..33ac29b70a 100644 --- a/src/beast/test/http/streambuf_body.cpp +++ b/src/beast/test/http/streambuf_body.cpp @@ -12,8 +12,10 @@ #include #include #include +#include #include #include +#include namespace beast { namespace http { @@ -25,16 +27,18 @@ class streambuf_body_test : public beast::unit_test::suite public: void run() override { - test::string_stream ss(ios_, + std::string const s = "HTTP/1.1 200 OK\r\n" "Server: test\r\n" "Content-Length: 3\r\n" "\r\n" - "xyz"); + "xyz"; + test::string_stream ss(ios_, s); parser_v1 p; streambuf sb; parse(ss, sb, p); expect(to_string(p.get().body.data()) == "xyz"); + expect(boost::lexical_cast(p.get()) == s); } }; diff --git a/src/beast/test/websocket/CMakeLists.txt b/src/beast/test/websocket/CMakeLists.txt index 933f0b5a7a..7a52207b60 100644 --- a/src/beast/test/websocket/CMakeLists.txt +++ b/src/beast/test/websocket/CMakeLists.txt @@ -16,6 +16,7 @@ add_executable (websocket-tests teardown.cpp detail/frame.cpp detail/mask.cpp + detail/stream_base.cpp detail/utf8_checker.cpp ) diff --git a/src/beast/test/websocket/detail/frame.cpp b/src/beast/test/websocket/detail/frame.cpp index 30f38927cb..d3e54b2b6b 100644 --- a/src/beast/test/websocket/detail/frame.cpp +++ b/src/beast/test/websocket/detail/frame.cpp @@ -225,7 +225,6 @@ public: testCloseCodes(); testFrameHeader(); testBadFrameHeaders(); - pass(); } }; diff --git a/src/beast/test/websocket/detail/mask.cpp b/src/beast/test/websocket/detail/mask.cpp index 653bfa002d..201b516049 100644 --- a/src/beast/test/websocket/detail/mask.cpp +++ b/src/beast/test/websocket/detail/mask.cpp @@ -5,7 +5,9 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // +// Test that header file is self-contained. #include + #include namespace beast { diff --git a/src/beast/test/websocket/detail/stream_base.cpp b/src/beast/test/websocket/detail/stream_base.cpp new file mode 100644 index 0000000000..e76eeae183 --- /dev/null +++ b/src/beast/test/websocket/detail/stream_base.cpp @@ -0,0 +1,40 @@ +// +// Copyright (c) 2013-2016 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 + +#include +#include +#include + +namespace beast { +namespace websocket { +namespace detail { + +class stream_base_test : public beast::unit_test::suite +{ +public: + void testClamp() + { + expect(detail::clamp( + std::numeric_limits::max()) == + std::numeric_limits::max()); + } + + void run() override + { + testClamp(); + } +}; + +BEAST_DEFINE_TESTSUITE(stream_base,websocket,beast); + +} // detail +} // websocket +} // beast + diff --git a/src/beast/test/websocket/stream.cpp b/src/beast/test/websocket/stream.cpp index 1d402f9d2e..de0b00fda7 100644 --- a/src/beast/test/websocket/stream.cpp +++ b/src/beast/test/websocket/stream.cpp @@ -20,6 +20,8 @@ #include #include #include +#include +#include namespace beast { namespace websocket { @@ -29,41 +31,143 @@ class stream_test , public test::enable_yield_to { public: + using self = stream_test; using endpoint_type = boost::asio::ip::tcp::endpoint; using address_type = boost::asio::ip::address; using socket_type = boost::asio::ip::tcp::socket; - void testClamp() + struct con { - expect(detail::clamp( - std::numeric_limits::max()) == - std::numeric_limits::max()); + stream ws; + + con(endpoint_type const& ep, boost::asio::io_service& ios) + : ws(ios) + { + ws.next_layer().connect(ep); + ws.handshake("localhost", "/"); + } + }; + + template + class cbuf_helper + { + std::array v_; + boost::asio::const_buffer cb_; + + public: + using value_type = decltype(cb_); + using const_iterator = value_type const*; + + template + explicit + cbuf_helper(Vn... vn) + : v_({{ static_cast(vn)... }}) + , cb_(v_.data(), v_.size()) + { + } + + const_iterator + begin() const + { + return &cb_; + } + + const_iterator + end() const + { + return begin()+1; + } + }; + + template + cbuf_helper + cbuf(Vn... vn) + { + return cbuf_helper(vn...); } - void testSpecialMembers() + template + static + boost::asio::const_buffers_1 + sbuf(const char (&s)[N]) { - stream ws(ios_); - { - stream ws2(std::move(ws)); - } - { - stream ws2(ios_); - ws = std::move(ws2); - } - expect(&ws.get_io_service() == &ios_); - pass(); + return boost::asio::const_buffers_1(&s[0], N-1); } + template + static + bool + run_until(boost::asio::io_service& ios, + std::size_t limit, Pred&& pred) + { + for(std::size_t i = 0; i < limit; ++i) + { + if(pred()) + return true; + ios.run_one(); + } + return false; + } + + template + static + void + read(stream& ws, opcode& op, Streambuf& sb) + { + frame_info fi; + for(;;) + { + ws.read_frame(fi, sb); + op = fi.op; + if(fi.fin) + break; + } + } + + typedef void(self::*pmf_t)(endpoint_type const&, yield_context); + + void yield_to_mf(endpoint_type const& ep, pmf_t mf) + { + yield_to(std::bind(mf, this, ep, std::placeholders::_1)); + } + + struct identity + { + template + void + operator()(http::message&) + { + } + + template + void + operator()(http::message&) + { + } + }; + void testOptions() { stream ws(ios_); - ws.set_option(message_type(opcode::binary)); + ws.set_option(auto_fragment_size{2048}); + ws.set_option(decorate(identity{})); + ws.set_option(keep_alive{false}); + ws.set_option(mask_buffer_size(2048)); + ws.set_option(message_type{opcode::text}); ws.set_option(read_buffer_size(8192)); ws.set_option(read_message_max(1 * 1024 * 1024)); - ws.set_option(write_buffer_size(2048)); try { - ws.set_option(message_type(opcode::close)); + ws.set_option(mask_buffer_size(0)); + fail(); + } + catch(std::exception const&) + { + pass(); + } + try + { + message_type{opcode::close}; fail(); } catch(std::exception const&) @@ -72,352 +176,261 @@ public: } } - template - static - boost::asio::const_buffers_1 - strbuf(const char (&s)[N]) - { - return boost::asio::const_buffers_1(&s[0], N-1); - } - - void testAccept(yield_context do_yield) + void testAccept() { { - stream ws(ios_, - "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"); - try + static std::size_t constexpr limit = 100; + std::size_t n; + for(n = 0; n < limit; ++n) { - ws.accept(); - pass(); - } - catch(...) - { - fail(); - } - } - { - stream ws(ios_, - "GET / HTTP/1.1\r\n" - "\r\n"); - try - { - ws.accept(); - fail(); - } - catch(...) - { - pass(); - } - } - { - stream ws(ios_, - "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"); - error_code ec; - ws.accept(ec); - expect(! ec, ec.message()); - } - { - stream ws(ios_, - "GET / HTTP/1.1\r\n" - "\r\n"); - error_code ec; - ws.accept(ec); - expect(ec); - } - { - stream ws(ios_, - "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"); - error_code ec; - ws.async_accept(do_yield[ec]); - expect(! ec, ec.message()); - } - { - stream ws(ios_, - "GET / HTTP/1.1\r\n" - "\r\n"); - error_code ec; - ws.async_accept(do_yield[ec]); - expect(ec); - } - { - stream ws(ios_, - "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"); - try - { - ws.accept(strbuf( - "GET / HTTP/1.1\r\n")); - pass(); - } - catch(...) - { - fail(); - } - } - { - stream ws(ios_, - "\r\n"); - try - { - ws.accept(strbuf( - "GET / HTTP/1.1\r\n")); - fail(); - } - catch(...) - { - pass(); - } - } - { - stream ws(ios_, - "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"); - error_code ec; - ws.accept(strbuf( - "GET / HTTP/1.1\r\n"), ec); - expect(! ec, ec.message()); - } - { - stream ws(ios_, - "GET / HTTP/1.1\r\n" - "\r\n"); - error_code ec; - ws.accept(ec); - expect(ec); - } - { - stream ws(ios_, - "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"); - error_code ec; - ws.async_accept(strbuf( - "GET / HTTP/1.1\r\n"), do_yield[ec]); - expect(! ec, ec.message()); - } - { - stream ws(ios_, - "\r\n"); - error_code ec; - ws.async_accept(strbuf( - "GET / HTTP/1.1\r\n"), do_yield[ec]); - expect(ec); - } - } - - void testHandshake(endpoint_type const& ep, - yield_context do_yield) - { - { - // disconnected socket - socket_type sock(ios_); - stream ws(sock); - try - { - ws.handshake("localhost", "/"); - fail(); - } - catch(boost::system::system_error const&) - { - pass(); - } - catch(...) - { - fail(); - } - error_code ec; - ws.handshake("localhost", "/", ec); - if(! expect(ec)) - return; - ws.async_handshake("localhost", "/", do_yield[ec]); - if(! expect(ec)) - return; - } - { - error_code ec; - socket_type sock(ios_); - sock.connect(ep, ec); - if(! expect(! ec, ec.message())) - return; - stream ws(sock); - ws.handshake("localhost", "/", ec); - if(! expect(! ec, ec.message())) - return; - ws.close({}, ec); - if(! expect(! ec, ec.message())) - return; - streambuf sb; - opcode op; - ws.read(op, sb, ec); - if(! expect(ec == error::closed, ec.message())) - return; - expect(ws.reason().code == close_code::normal); - } - { - error_code ec; - socket_type sock(ios_); - sock.connect(ep, ec); - if(! expect(! ec, ec.message())) - return; - stream ws(sock); - ws.async_handshake("localhost", "/", do_yield[ec]); - if(! expect(! ec, ec.message())) - return; - ws.async_close({}, do_yield[ec]); - if(! expect(! ec, ec.message())) - return; - streambuf sb; - opcode op; - ws.async_read(op, sb, do_yield[ec]); - if(! expect(ec == error::closed, ec.message())) - return; - expect(ws.reason().code == close_code::normal); - } - } - - void testErrorHandling(endpoint_type const& ep, - yield_context do_yield) - { - static std::size_t constexpr limit = 100; - std::size_t n; - - // synchronous, exceptions - for(n = 0; n < limit; ++n) - { - error_code ec; - socket_type sock(ios_); - sock.connect(ep, ec); - if(! expect(! ec, ec.message())) - break; - stream> ws(n, sock); - try - { - ws.handshake("localhost", "/"); - ws.write(boost::asio::const_buffers_1( - "Hello", 5)); - opcode op; - streambuf sb; - ws.read(op, sb); - expect(op == opcode::text); - expect(to_string(sb.data()) == "Hello"); - ws.close({}); + // valid + http::request_v1 req; + req.method = "GET"; + req.url = "/"; + req.version = 11; + req.headers.insert("Host", "localhost"); + req.headers.insert("Upgrade", "websocket"); + req.headers.insert("Connection", "upgrade"); + req.headers.insert("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ=="); + req.headers.insert("Sec-WebSocket-Version", "13"); + stream> ws(n, ios_, ""); try { - ws.read(op, sb); + ws.accept(req); + break; } - catch(boost::system::system_error const& se) + catch(system_error const&) { - if(se.code() == error::closed) - break; - throw; } + } + expect(n < limit); + } + { + // valid + stream ws(ios_, + "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" + ); + try + { + ws.accept(); + pass(); + } + catch(system_error const&) + { fail(); - break; - } - catch(boost::system::system_error const&) - { } } - expect(n < limit); - - // synchronous, error codes - for(n = 0; n < limit; ++n) { - error_code ec; - socket_type sock(ios_); - sock.connect(ep, ec); - if(! expect(! ec, ec.message())) - break; - stream> ws(n, sock); - ws.handshake("localhost", "/", ec); - if(ec) - continue; - ws.write(boost::asio::const_buffers_1( - "Hello", 5), ec); - if(ec) - continue; - opcode op; - streambuf sb; - ws.read(op, sb, ec); - if(ec) - continue; - expect(op == opcode::text); - expect(to_string(sb.data()) == "Hello"); - ws.close({}, ec); - if(ec) - continue; - ws.read(op, sb, ec); - if(ec == error::closed) + // invalid + stream ws(ios_, + "GET / HTTP/1.0\r\n" + "\r\n" + ); + try + { + ws.accept(); + fail(); + } + catch(system_error const&) { pass(); - break; } } - expect(n < limit); + } - // asynchronous - for(n = 0; n < limit; ++n) - { - error_code ec; - socket_type sock(ios_); - sock.connect(ep, ec); - if(! expect(! ec, ec.message())) - break; - stream> ws(n, sock); - ws.async_handshake("localhost", "/", do_yield[ec]); - if(ec) - break; - ws.async_write(boost::asio::const_buffers_1( - "Hello", 5), do_yield[ec]); - if(ec) - continue; - opcode op; - streambuf sb; - ws.async_read(op, sb, do_yield[ec]); - if(ec) - continue; - expect(op == opcode::text); - expect(to_string(sb.data()) == "Hello"); - ws.async_close({}, do_yield[ec]); - if(ec) - continue; - ws.async_read(op, sb, do_yield[ec]); - if(ec == error::closed) + void testBadHandshakes() + { + auto const check = + [&](error_code const& ev, std::string const& s) { - pass(); - break; - } - } - expect(n < limit); + for(std::size_t i = 0; i < s.size(); ++i) + { + stream ws(ios_, + s.substr(i, s.size() - i)); + ws.set_option(keep_alive{true}); + try + { + ws.accept(boost::asio::buffer( + s.substr(0, i), i)); + expect(! ev); + } + catch(system_error const& se) + { + expect(se.code() == ev); + } + } + }; + // wrong version + check(error::handshake_failed, + "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) + { + stream ws(ios_, s); + try + { + ws.handshake("localhost:80", "/"); + fail(); + } + catch(system_error const& se) + { + expect(se.code() == error::response_failed); + } + }; + // wrong HTTP version + check( + "HTTP/1.0 101 Switching Protocols\r\n" + "Server: beast\r\n" + "Upgrade: WebSocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n" + ); + // wrong status + check( + "HTTP/1.1 200 OK\r\n" + "Server: beast\r\n" + "Upgrade: WebSocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n" + ); + // missing upgrade token + check( + "HTTP/1.1 101 Switching Protocols\r\n" + "Server: beast\r\n" + "Upgrade: HTTP/2\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n" + ); + // missing connection token + check( + "HTTP/1.1 101 Switching Protocols\r\n" + "Server: beast\r\n" + "Upgrade: WebSocket\r\n" + "Connection: keep-alive\r\n" + "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n" + ); + // missing accept key + check( + "HTTP/1.1 101 Switching Protocols\r\n" + "Server: beast\r\n" + "Upgrade: WebSocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n" + ); + // wrong accept key + check( + "HTTP/1.1 101 Switching Protocols\r\n" + "Server: beast\r\n" + "Upgrade: WebSocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Accept: *\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n" + ); } void testMask(endpoint_type const& ep, @@ -477,59 +490,8 @@ public: } } - struct con - { - stream ws; - - con(endpoint_type const& ep, boost::asio::io_service& ios) - : ws(ios) - { - ws.next_layer().connect(ep); - ws.handshake("localhost", "/"); - } - }; - - template - class cbuf_helper - { - std::array v_; - boost::asio::const_buffer cb_; - - public: - using value_type = decltype(cb_); - using const_iterator = value_type const*; - - template - explicit - cbuf_helper(Vn... vn) - : v_({{ static_cast(vn)... }}) - , cb_(v_.data(), v_.size()) - { - } - - const_iterator - begin() const - { - return &cb_; - } - - const_iterator - end() const - { - return begin()+1; - } - }; - - template - cbuf_helper - cbuf(Vn... vn) - { - return cbuf_helper(vn...); - } - void testClose(endpoint_type const& ep, yield_context do_yield) { - using boost::asio::buffer; { // payload length 1 con c(ep, ios_); @@ -558,7 +520,815 @@ public: } } - void testWriteFrame(endpoint_type const& ep) +#if 0 + void testInvokable1(endpoint_type const& ep) + { + boost::asio::io_service ios; + stream ws(ios); + ws.next_layer().connect(ep); + ws.handshake("localhost", "/"); + + // Make remote send a ping frame + ws.set_option(message_type(opcode::text)); + ws.write(buffer_cat(sbuf("PING"), sbuf("ping"))); + + std::size_t count = 0; + + // Write a text message + ++count; + ws.async_write(sbuf("Hello"), + [&](error_code ec) + { + --count; + }); + + // Read + opcode op; + streambuf sb; + ++count; + ws.async_read(op, sb, + [&](error_code ec) + { + --count; + }); + // 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 invokable. + ws.async_write(sbuf("Hello"), + [&](error_code ec) + { + ++count; + // Send is canceled because close received. + expect(ec == boost::asio:: + error::operation_aborted, + ec.message()); + // Writes after close are aborted. + ws.async_write(sbuf("World"), + [&](error_code ec) + { + ++count; + expect(ec == boost::asio:: + error::operation_aborted, + ec.message()); + }); + }); + // Run until all completions are delivered. + static std::size_t constexpr limit = 100; + std::size_t n; + for(n = 0; n < limit; ++n) + { + if(count >= 4) + break; + ios.run_one(); + } + expect(n < limit); + ios.run(); + } +#endif + + void testInvokable2(endpoint_type const& ep) + { + boost::asio::io_service ios; + stream ws(ios); + ws.next_layer().connect(ep); + ws.handshake("localhost", "/"); + + // Make remote send a text message with bad utf8. + ws.set_option(message_type(opcode::binary)); + ws.write(buffer_cat(sbuf("TEXT"), + cbuf(0x03, 0xea, 0xf0, 0x28, 0x8c, 0xbc))); + opcode op; + streambuf sb; + std::size_t count = 0; + // Read text message with bad utf8. + // Causes a close to be sent, blocking writes. + ws.async_read(op, sb, + [&](error_code ec) + { + // Read should fail with protocol error + ++count; + expect(ec == error::failed, + ec.message()); + // Reads after failure are aborted + ws.async_read(op, sb, + [&](error_code ec) + { + ++count; + expect(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 invokable. + ws.async_write(sbuf("Hello"), + [&](error_code ec) + { + ++count; + // Send is canceled because close received. + expect(ec == boost::asio:: + error::operation_aborted, + ec.message()); + // Writes after close are aborted. + ws.async_write(sbuf("World"), + [&](error_code ec) + { + ++count; + expect(ec == boost::asio:: + error::operation_aborted, + ec.message()); + }); + }); + // Run until all completions are delivered. + static std::size_t constexpr limit = 100; + std::size_t n; + for(n = 0; n < limit; ++n) + { + if(count >= 4) + break; + ios.run_one(); + } + expect(n < limit); + ios.run(); + } + + void testInvokable3(endpoint_type const& ep) + { + boost::asio::io_service ios; + stream ws(ios); + ws.next_layer().connect(ep); + ws.handshake("localhost", "/"); + + // Cause close to be received + ws.set_option(message_type(opcode::binary)); + ws.write(sbuf("CLOSE")); + opcode op; + streambuf sb; + std::size_t count = 0; + // Read a close frame. + // Sends a close frame, blocking writes. + ws.async_read(op, sb, + [&](error_code ec) + { + // Read should complete with error::closed + ++count; + expect(ec == error::closed, + ec.message()); + // Pings after a close are aborted + ws.async_ping("", + [&](error_code ec) + { + ++count; + expect(ec == boost::asio:: + error::operation_aborted, + ec.message()); + }); + }); + if(! expect(run_until(ios, 100, + [&]{ return ws.wr_close_; }))) + return; + // Try to ping + ws.async_ping("payload", + [&](error_code ec) + { + // Pings after a close are aborted + ++count; + expect(ec == boost::asio:: + error::operation_aborted, + ec.message()); + // Subsequent calls to close are aborted + ws.async_close({}, + [&](error_code ec) + { + ++count; + expect(ec == boost::asio:: + error::operation_aborted, + ec.message()); + }); + }); + static std::size_t constexpr limit = 100; + std::size_t n; + for(n = 0; n < limit; ++n) + { + if(count >= 4) + break; + ios.run_one(); + } + expect(n < limit); + ios.run(); + } + + void testInvokable4(endpoint_type const& ep) + { + boost::asio::io_service ios; + stream ws(ios); + ws.next_layer().connect(ep); + ws.handshake("localhost", "/"); + + // Cause close to be received + ws.set_option(message_type(opcode::binary)); + ws.write(sbuf("CLOSE")); + opcode op; + streambuf sb; + std::size_t count = 0; + ws.async_read(op, sb, + [&](error_code ec) + { + ++count; + expect(ec == error::closed, + ec.message()); + }); + while(! ws.wr_block_) + ios.run_one(); + // try to close + ws.async_close("payload", + [&](error_code ec) + { + ++count; + expect(ec == boost::asio:: + error::operation_aborted, + ec.message()); + }); + static std::size_t constexpr limit = 100; + std::size_t n; + for(n = 0; n < limit; ++n) + { + if(count >= 2) + break; + ios.run_one(); + } + expect(n < limit); + ios.run(); + } + +#if 0 + void testInvokable5(endpoint_type const& ep) + { + boost::asio::io_service ios; + stream ws(ios); + ws.next_layer().connect(ep); + ws.handshake("localhost", "/"); + + ws.async_write(sbuf("CLOSE"), + [&](error_code ec) + { + expect(! ec); + ws.async_write(sbuf("PING"), + [&](error_code ec) + { + expect(! ec); + }); + }); + opcode op; + streambuf sb; + ws.async_read(op, sb, + [&](error_code ec) + { + expect(ec == error::closed, ec.message()); + }); + if(! expect(run_until(ios, 100, + [&]{ return ios.stopped(); }))) + return; + } +#endif + + void testSyncClient(endpoint_type const& ep) + { + using boost::asio::buffer; + static std::size_t constexpr limit = 200; + std::size_t n; + for(n = 0; n < limit; ++n) + { + stream> ws(n, ios_); + auto const restart = + [&](error_code ev) + { + try + { + opcode op; + streambuf sb; + ws.read(op, sb); + fail(); + return false; + } + catch(boost::system::system_error const& se) + { + if(se.code() != ev) + throw; + } + error_code ec; + ws.lowest_layer().connect(ep, ec); + if(! expect(! ec, ec.message())) + return false; + ws.handshake("localhost", "/"); + return true; + }; + try + { + { + // connect + error_code ec; + ws.lowest_layer().connect(ep, ec); + if(! expect(! ec, ec.message())) + return; + } + ws.handshake("localhost", "/"); + + // send message + ws.set_option(auto_fragment_size(0)); + ws.set_option(message_type(opcode::text)); + ws.write(sbuf("Hello")); + { + // receive echoed message + opcode op; + streambuf sb; + read(ws, op, sb); + expect(op == opcode::text); + expect(to_string(sb.data()) == "Hello"); + } + + // close, no payload + ws.close({}); + if(! restart(error::closed)) + return; + + // close with code + ws.close(close_code::going_away); + if(! restart(error::closed)) + return; + + // close with code and reason string + ws.close({close_code::going_away, "Going away"}); + if(! restart(error::closed)) + return; + + // send ping and message + bool pong = false; + ws.set_option(pong_callback{ + [&](ping_data const& payload) + { + expect(! pong); + pong = true; + expect(payload == ""); + }}); + ws.ping(""); + ws.set_option(message_type(opcode::binary)); + ws.write(sbuf("Hello")); + { + // receive echoed message + opcode op; + streambuf sb; + ws.read(op, sb); + expect(pong == 1); + expect(op == opcode::binary); + expect(to_string(sb.data()) == "Hello"); + } + ws.set_option(pong_callback{}); + + // send ping and fragmented message + ws.set_option(pong_callback{ + [&](ping_data const& payload) + { + expect(payload == "payload"); + }}); + ws.ping("payload"); + ws.write_frame(false, sbuf("Hello, ")); + ws.write_frame(false, sbuf("")); + ws.write_frame(true, sbuf("World!")); + { + // receive echoed message + opcode op; + streambuf sb; + ws.read(op, sb); + expect(pong == 1); + expect(to_string(sb.data()) == "Hello, World!"); + } + ws.set_option(pong_callback{}); + + // send auto fragmented message + ws.set_option(auto_fragment_size(3)); + ws.write(sbuf("Hello")); + { + // receive echoed message + opcode op; + streambuf sb; + ws.read(op, sb); + expect(to_string(sb.data()) == "Hello"); + } + ws.set_option(auto_fragment_size(0)); + + // send message with write buffer limit + { + std::string s(2000, '*'); + ws.set_option(mask_buffer_size(1200)); + ws.write(buffer(s.data(), s.size())); + { + // receive echoed message + opcode op; + streambuf sb; + ws.read(op, sb); + expect(to_string(sb.data()) == s); + } + } + + // cause ping + ws.set_option(message_type(opcode::binary)); + ws.write(sbuf("PING")); + ws.set_option(message_type(opcode::text)); + ws.write(sbuf("Hello")); + { + // receive echoed message + opcode op; + streambuf sb; + ws.read(op, sb); + expect(op == opcode::text); + expect(to_string(sb.data()) == "Hello"); + } + + // cause close + ws.set_option(message_type(opcode::binary)); + ws.write(sbuf("CLOSE")); + if(! restart(error::closed)) + return; + + // send bad utf8 + ws.set_option(message_type(opcode::binary)); + ws.write(buffer_cat(sbuf("TEXT"), + cbuf(0x03, 0xea, 0xf0, 0x28, 0x8c, 0xbc))); + if(! restart(error::failed)) + return; + + // cause bad utf8 + ws.set_option(message_type(opcode::binary)); + ws.write(buffer_cat(sbuf("TEXT"), + cbuf(0x03, 0xea, 0xf0, 0x28, 0x8c, 0xbc))); + ws.write(sbuf("Hello")); + if(! restart(error::failed)) + return; + + // cause bad close + ws.set_option(message_type(opcode::binary)); + ws.write(buffer_cat(sbuf("RAW"), + cbuf(0x88, 0x02, 0x03, 0xed))); + if(! restart(error::failed)) + return; + + // unexpected cont + boost::asio::write(ws.next_layer(), + cbuf(0x80, 0x80, 0xff, 0xff, 0xff, 0xff)); + if(! restart(error::closed)) + return; + + // expected cont + ws.write_frame(false, boost::asio::null_buffers{}); + boost::asio::write(ws.next_layer(), + cbuf(0x81, 0x80, 0xff, 0xff, 0xff, 0xff)); + if(! restart(error::closed)) + return; + + // message size above 2^64 + ws.write_frame(false, cbuf(0x00)); + boost::asio::write(ws.next_layer(), + cbuf(0x80, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff)); + if(! restart(error::closed)) + return; + + // message size exceeds max + ws.set_option(read_message_max{1}); + ws.write(cbuf(0x00, 0x00)); + if(! restart(error::failed)) + return; + ws.set_option(read_message_max{16*1024*1024}); + + // invalid fixed frame header + boost::asio::write(ws.next_layer(), + cbuf(0x8f, 0x80, 0xff, 0xff, 0xff, 0xff)); + if(! restart(error::closed)) + return; + + // cause non-canonical extended size + ws.write(buffer_cat(sbuf("RAW"), + cbuf(0x82, 0x7e, 0x00, 0x01, 0x00))); + if(! restart(error::failed)) + return; + } + catch(system_error const&) + { + continue; + } + break; + } + expect(n < limit); + } + + void testAsyncClient( + endpoint_type const& ep, yield_context do_yield) + { + using boost::asio::buffer; + static std::size_t constexpr limit = 200; + std::size_t n; + for(n = 190; n < limit; ++n) + { + stream> ws(n, ios_); + auto const restart = + [&](error_code ev) + { + opcode op; + streambuf sb; + error_code ec; + ws.async_read(op, sb, do_yield[ec]); + if(! ec) + { + fail(); + return false; + } + if(ec != ev) + { + auto const s = ec.message(); + throw system_error{ec}; + } + ec = {}; + ws.lowest_layer().close(ec); + ec = {}; + ws.lowest_layer().connect(ep, ec); + if(! expect(! ec, ec.message())) + return false; + ws.async_handshake("localhost", "/", do_yield[ec]); + if(ec) + throw system_error{ec}; + return true; + }; + try + { + error_code ec; + + // connect + ws.lowest_layer().connect(ep, ec); + if(! expect(! ec, ec.message())) + return; + ws.async_handshake("localhost", "/", do_yield[ec]); + if(ec) + throw system_error{ec}; + + // send message + ws.set_option(auto_fragment_size(0)); + ws.set_option(message_type(opcode::text)); + ws.async_write(sbuf("Hello"), do_yield[ec]); + if(ec) + throw system_error{ec}; + { + // receive echoed message + opcode op; + streambuf sb; + ws.async_read(op, sb, do_yield[ec]); + if(ec) + throw system_error{ec}; + expect(op == opcode::text); + expect(to_string(sb.data()) == "Hello"); + } + + // close, no payload + ws.async_close({}, do_yield[ec]); + if(ec) + throw system_error{ec}; + if(! restart(error::closed)) + return; + + // close with code + ws.async_close(close_code::going_away, do_yield[ec]); + if(ec) + throw system_error{ec}; + if(! restart(error::closed)) + return; + + // close with code and reason string + ws.async_close({close_code::going_away, "Going away"}, do_yield[ec]); + if(ec) + throw system_error{ec}; + if(! restart(error::closed)) + return; + + // send ping and message + bool pong = false; + { + ws.set_option(pong_callback{ + [&](ping_data const& payload) + { + expect(! pong); + pong = true; + expect(payload == ""); + }}); + ws.async_ping("", do_yield[ec]); + if(ec) + throw system_error{ec}; + ws.set_option(message_type(opcode::binary)); + ws.async_write(sbuf("Hello"), do_yield[ec]); + if(ec) + throw system_error{ec}; + // receive echoed message + opcode op; + streambuf sb; + ws.async_read(op, sb, do_yield[ec]); + if(ec) + throw system_error{ec}; + expect(op == opcode::binary); + expect(to_string(sb.data()) == "Hello"); + ws.set_option(pong_callback{}); + } + + // send ping and fragmented message + { + ws.set_option(pong_callback{ + [&](ping_data const& payload) + { + expect(payload == "payload"); + }}); + ws.async_ping("payload", do_yield[ec]); + if(! ec) + ws.async_write_frame(false, sbuf("Hello, "), do_yield[ec]); + if(! ec) + ws.async_write_frame(false, sbuf(""), do_yield[ec]); + if(! ec) + ws.async_write_frame(true, sbuf("World!"), do_yield[ec]); + if(ec) + throw system_error{ec}; + { + // receive echoed message + opcode op; + streambuf sb; + ws.async_read(op, sb, do_yield[ec]); + if(ec) + throw system_error{ec}; + expect(to_string(sb.data()) == "Hello, World!"); + } + ws.set_option(pong_callback{}); + } + + // send auto fragmented message + ws.set_option(auto_fragment_size(3)); + ws.async_write(sbuf("Hello"), do_yield[ec]); + { + // receive echoed message + opcode op; + streambuf sb; + ws.async_read(op, sb, do_yield[ec]); + if(ec) + throw system_error{ec}; + expect(to_string(sb.data()) == "Hello"); + } + ws.set_option(auto_fragment_size(0)); + + // send message with mask buffer limit + { + std::string s(2000, '*'); + ws.set_option(mask_buffer_size(1200)); + ws.async_write(buffer(s.data(), s.size()), do_yield[ec]); + if(ec) + throw system_error{ec}; + { + // receive echoed message + opcode op; + streambuf sb; + ws.async_read(op, sb, do_yield[ec]); + if(ec) + throw system_error{ec}; + expect(to_string(sb.data()) == s); + } + } + + // cause ping + ws.set_option(message_type(opcode::binary)); + ws.async_write(sbuf("PING"), do_yield[ec]); + if(ec) + throw system_error{ec}; + ws.set_option(message_type(opcode::text)); + ws.async_write(sbuf("Hello"), do_yield[ec]); + if(ec) + throw system_error{ec}; + { + // receive echoed message + opcode op; + streambuf sb; + ws.async_read(op, sb, do_yield[ec]); + if(ec) + throw system_error{ec}; + expect(op == opcode::text); + expect(to_string(sb.data()) == "Hello"); + } + + // cause close + ws.set_option(message_type(opcode::binary)); + ws.async_write(sbuf("CLOSE"), do_yield[ec]); + if(ec) + throw system_error{ec}; + if(! restart(error::closed)) + return; + + // send bad utf8 + ws.set_option(message_type(opcode::binary)); + ws.async_write(buffer_cat(sbuf("TEXT"), + cbuf(0x03, 0xea, 0xf0, 0x28, 0x8c, 0xbc)), do_yield[ec]); + if(ec) + throw system_error{ec}; + if(! restart(error::failed)) + return; + + // cause bad utf8 + ws.set_option(message_type(opcode::binary)); + ws.async_write(buffer_cat(sbuf("TEXT"), + cbuf(0x03, 0xea, 0xf0, 0x28, 0x8c, 0xbc)), do_yield[ec]); + if(ec) + throw system_error{ec}; + ws.async_write(sbuf("Hello"), do_yield[ec]); + if(! restart(error::failed)) + return; + + // cause bad close + ws.set_option(message_type(opcode::binary)); + ws.async_write(buffer_cat(sbuf("RAW"), + cbuf(0x88, 0x02, 0x03, 0xed)), do_yield[ec]); + if(ec) + throw system_error{ec}; + if(! restart(error::failed)) + return; + + // unexpected cont + boost::asio::async_write(ws.next_layer(), + cbuf(0x80, 0x80, 0xff, 0xff, 0xff, 0xff), + do_yield[ec]); + if(ec) + throw system_error{ec}; + if(! restart(error::closed)) + return; + + // expected cont + ws.async_write_frame(false, + boost::asio::null_buffers{}, do_yield[ec]); + if(ec) + throw system_error{ec}; + boost::asio::async_write(ws.next_layer(), + cbuf(0x81, 0x80, 0xff, 0xff, 0xff, 0xff), + do_yield[ec]); + if(ec) + throw system_error{ec}; + if(! restart(error::closed)) + return; + + // message size above 2^64 + ws.async_write_frame(false, cbuf(0x00), do_yield[ec]); + if(ec) + throw system_error{ec}; + boost::asio::async_write(ws.next_layer(), + cbuf(0x80, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff), + do_yield[ec]); + if(ec) + throw system_error{ec}; + if(! restart(error::closed)) + return; + + // message size exceeds max + ws.set_option(read_message_max{1}); + ws.async_write(cbuf(0x00, 0x00), do_yield[ec]); + if(ec) + throw system_error{ec}; + if(! restart(error::failed)) + return; + + // invalid fixed frame header + boost::asio::async_write(ws.next_layer(), + cbuf(0x8f, 0x80, 0xff, 0xff, 0xff, 0xff), + do_yield[ec]); + if(ec) + throw system_error{ec}; + if(! restart(error::closed)) + return; + + // cause non-canonical extended size + ws.async_write(buffer_cat(sbuf("RAW"), + cbuf(0x82, 0x7e, 0x00, 0x01, 0x00)), + do_yield[ec]); + if(ec) + throw system_error{ec}; + if(! restart(error::failed)) + return; + } + catch(system_error const&) + { + continue; + } + break; + } + expect(n < limit); + } + + void testAsyncWriteFrame(endpoint_type const& ep) { for(;;) { @@ -574,7 +1344,13 @@ public: break; ws.async_write_frame(false, boost::asio::null_buffers{}, - [](error_code){ }); + [&](error_code) + { + fail(); + }); + ws.next_layer().cancel(ec); + if(! expect(! ec, ec.message())) + break; // // Destruction of the io_service will cause destruction // of the write_frame_op without invoking the final handler. @@ -585,53 +1361,55 @@ public: void run() override { - testClamp(); + static_assert(std::is_constructible< + stream, boost::asio::io_service&>::value, ""); - testSpecialMembers(); + static_assert(std::is_move_constructible< + stream>::value, ""); - testOptions(); + static_assert(std::is_move_assignable< + stream>::value, ""); - yield_to(std::bind(&stream_test::testAccept, - this, std::placeholders::_1)); + static_assert(std::is_constructible< + stream, socket_type&>::value, ""); + + static_assert(std::is_move_constructible< + stream>::value, ""); + + static_assert(! std::is_move_assignable< + stream>::value, ""); auto const any = endpoint_type{ address_type::from_string("127.0.0.1"), 0}; + + for(std::size_t n = 0; n < 1; ++n) { - sync_echo_peer server(true, any); - auto const ep = server.local_endpoint(); - - yield_to(std::bind(&stream_test::testHandshake, - this, ep, std::placeholders::_1)); - - yield_to(std::bind(&stream_test::testErrorHandling, - this, ep, std::placeholders::_1)); - - yield_to(std::bind(&stream_test::testMask, - this, ep, std::placeholders::_1)); - - yield_to(std::bind(&stream_test::testClose, - this, ep, std::placeholders::_1)); - - testWriteFrame(ep); + testOptions(); + testAccept(); + testBadHandshakes(); + testBadResponses(); + { + sync_echo_peer server(true, any); + auto const ep = server.local_endpoint(); + + //testInvokable1(ep); + testInvokable2(ep); + testInvokable3(ep); + testInvokable4(ep); + //testInvokable5(ep); + + testSyncClient(ep); + testAsyncWriteFrame(ep); + yield_to_mf(ep, &stream_test::testAsyncClient); + } + { + async_echo_peer server(true, any, 4); + auto const ep = server.local_endpoint(); + testSyncClient(ep); + testAsyncWriteFrame(ep); + yield_to_mf(ep, &stream_test::testAsyncClient); + } } - { - async_echo_peer server(true, any, 1); - auto const ep = server.local_endpoint(); - - yield_to(std::bind(&stream_test::testHandshake, - this, ep, std::placeholders::_1)); - - yield_to(std::bind(&stream_test::testErrorHandling, - this, ep, std::placeholders::_1)); - - yield_to(std::bind(&stream_test::testMask, - this, ep, std::placeholders::_1)); - - yield_to(std::bind(&stream_test::testClose, - this, ep, std::placeholders::_1)); - } - - pass(); } }; diff --git a/src/beast/test/websocket/websocket_async_echo_peer.hpp b/src/beast/test/websocket/websocket_async_echo_peer.hpp index 1efa9ed78a..e374dc8cbc 100644 --- a/src/beast/test/websocket/websocket_async_echo_peer.hpp +++ b/src/beast/test/websocket/websocket_async_echo_peer.hpp @@ -91,14 +91,16 @@ private: bool log; int state = 0; boost::optional ep; - websocket::stream ws; - websocket::opcode op; + stream ws; + boost::asio::io_service::strand strand; + opcode op; beast::streambuf sb; int id; data(bool log_, socket_type&& sock_) : log(log_) , ws(std::move(sock_)) + , strand(ws.get_io_service()) , id([] { static int n = 0; @@ -112,6 +114,7 @@ private: : log(log_) , ep(ep_) , ws(std::move(sock_)) + , strand(ws.get_io_service()) , id([] { static int n = 0; @@ -174,8 +177,34 @@ private: } } + template + static + bool + match(Streambuf& sb, char const(&s)[N]) + { + using boost::asio::buffer; + using boost::asio::buffer_copy; + if(sb.size() < N-1) + return false; + static_string t; + t.resize(N-1); + buffer_copy(buffer(t.data(), t.size()), + sb.data()); + if(t != s) + return false; + sb.consume(N-1); + return true; + } + + void operator()(error_code ec, std::size_t) + { + (*this)(ec); + } + void operator()(error_code ec) { + using boost::asio::buffer; + using boost::asio::buffer_copy; auto& d = *d_; switch(d.state) { @@ -191,19 +220,54 @@ private: d.sb.consume(d.sb.size()); // read message d.state = 2; - d.ws.async_read(d.op, d.sb, std::move(*this)); + d.ws.async_read(d.op, d.sb, + d.strand.wrap(std::move(*this))); return; // got message case 2: - if(ec == websocket::error::closed) + if(ec == error::closed) return; if(ec) return fail(ec, "async_read"); + if(match(d.sb, "RAW")) + { + d.state = 1; + boost::asio::async_write(d.ws.next_layer(), + d.sb.data(), d.strand.wrap(std::move(*this))); + return; + } + else if(match(d.sb, "TEXT")) + { + d.state = 1; + d.ws.set_option(message_type{opcode::text}); + d.ws.async_write( + d.sb.data(), d.strand.wrap(std::move(*this))); + return; + } + else if(match(d.sb, "PING")) + { + ping_data payload; + d.sb.consume(buffer_copy( + buffer(payload.data(), payload.size()), + d.sb.data())); + d.state = 1; + d.ws.async_ping(payload, + d.strand.wrap(std::move(*this))); + return; + } + else if(match(d.sb, "CLOSE")) + { + d.state = 1; + d.ws.async_close({}, + d.strand.wrap(std::move(*this))); + return; + } // write message d.state = 1; - d.ws.set_option(websocket::message_type(d.op)); - d.ws.async_write(d.sb.data(), std::move(*this)); + d.ws.set_option(message_type(d.op)); + d.ws.async_write(d.sb.data(), + d.strand.wrap(std::move(*this))); return; // connected @@ -214,7 +278,7 @@ private: d.ws.async_handshake( d.ep->address().to_string() + ":" + std::to_string(d.ep->port()), - "/", std::move(*this)); + "/", d.strand.wrap(std::move(*this))); return; } } @@ -226,7 +290,7 @@ private: auto& d = *d_; if(d.log) { - if(ec != websocket::error::closed) + if(ec != error::closed) std::cerr << "#" << d_->id << " " << what << ": " << ec.message() << std::endl; } @@ -256,6 +320,8 @@ private: { if(! acceptor_.is_open()) return; + if(ec == boost::asio::error::operation_aborted) + return; maybe_throw(ec, "accept"); socket_type sock(std::move(sock_)); acceptor_.async_accept(sock_, diff --git a/src/beast/test/websocket/websocket_sync_echo_peer.hpp b/src/beast/test/websocket/websocket_sync_echo_peer.hpp index d45a67fcf3..6d0665461f 100644 --- a/src/beast/test/websocket/websocket_sync_echo_peer.hpp +++ b/src/beast/test/websocket/websocket_sync_echo_peer.hpp @@ -101,15 +101,17 @@ private: { int id; sync_echo_peer& self; - socket_type sock; boost::asio::io_service::work work; + // Must be destroyed before work otherwise the + // io_service could be destroyed before the socket. + socket_type sock; lambda(int id_, sync_echo_peer& self_, socket_type&& sock_) : id(id_) , self(self_) + , work(sock_.get_io_service()) , sock(std::move(sock_)) - , work(sock.get_io_service()) { } @@ -149,10 +151,31 @@ private: } }; + template + static + bool + match(Streambuf& sb, char const(&s)[N]) + { + using boost::asio::buffer; + using boost::asio::buffer_copy; + if(sb.size() < N-1) + return false; + static_string t; + t.resize(N-1); + buffer_copy(buffer(t.data(), t.size()), + sb.data()); + if(t != s) + return false; + sb.consume(N-1); + return true; + } + void do_peer(int id, socket_type&& sock) { - websocket::stream ws(std::move(sock)); + using boost::asio::buffer; + using boost::asio::buffer_copy; + stream ws(std::move(sock)); ws.set_option(decorate(identity{})); ws.set_option(read_message_max(64 * 1024 * 1024)); error_code ec; @@ -164,17 +187,45 @@ private: } for(;;) { - websocket::opcode op; + opcode op; beast::streambuf sb; ws.read(op, sb, ec); if(ec) + { + auto const s = ec.message(); break; - ws.set_option(websocket::message_type(op)); - ws.write(sb.data(), ec); + } + ws.set_option(message_type(op)); + if(match(sb, "RAW")) + { + boost::asio::write( + ws.next_layer(), sb.data(), ec); + } + else if(match(sb, "TEXT")) + { + ws.set_option(message_type{opcode::text}); + ws.write(sb.data(), ec); + } + else if(match(sb, "PING")) + { + ping_data payload; + sb.consume(buffer_copy( + buffer(payload.data(), payload.size()), + sb.data())); + ws.ping(payload, ec); + } + else if(match(sb, "CLOSE")) + { + ws.close({}, ec); + } + else + { + ws.write(sb.data(), ec); + } if(ec) break; } - if(ec && ec != websocket::error::closed) + if(ec && ec != error::closed) { fail(id, ec, "read"); }