diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj index daabd699a..ea09ebfb5 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj +++ b/Builds/VisualStudio2015/RippleD.vcxproj @@ -284,6 +284,8 @@ + + @@ -312,10 +314,14 @@ + + + + @@ -494,6 +500,10 @@ True True + + True + True + @@ -527,6 +537,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + + + True + True + + + + + + + + + @@ -3423,6 +3501,10 @@ + + + + True True @@ -3439,6 +3521,8 @@ + + True True @@ -3461,6 +3545,8 @@ + + @@ -3485,6 +3571,8 @@ + + @@ -3905,10 +3993,6 @@ - - - - True diff --git a/Builds/VisualStudio2015/RippleD.vcxproj.filters b/Builds/VisualStudio2015/RippleD.vcxproj.filters index f47515c7e..bfd80c81d 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2015/RippleD.vcxproj.filters @@ -79,6 +79,21 @@ {2762284D-66E5-8B48-1F8E-67116DB1FC6B} + + {53D8D640-BEB0-1F2C-61D4-5459CDAC7EB4} + + + {E8E91EF5-9155-8F58-4249-3EF221768BCE} + + + {63AB4C85-5B2E-B2A7-9E4F-9F0899402D1D} + + + {E5986CF5-37A8-1EE5-1085-645EBEE517B6} + + + {28A822FF-4532-9678-23F2-D844CB0FE207} + {65697F48-7FC6-2A4B-DB6C-56781F3990B5} @@ -376,9 +391,6 @@ {44780F86-42D3-2F2B-0846-5AEE2CA6D7FE} - - {1D54E820-ADC9-94FB-19E7-653EFDE4CBE9} - {15B4B65A-0F03-7BA9-38CD-42A5712392CB} @@ -606,6 +618,9 @@ beast\asio + + beast\asio + beast\asio @@ -642,12 +657,18 @@ beast\crypto\detail + + beast\crypto\detail + beast\crypto beast\crypto + + beast\crypto + beast\crypto @@ -864,6 +885,9 @@ beast\unity + + beast\unity + beast @@ -912,6 +936,102 @@ beast\unit_test + + beast + + + beast\wsproto\detail + + + beast\wsproto\detail + + + beast\wsproto\detail + + + beast\wsproto\detail + + + beast\wsproto\detail + + + beast\wsproto\detail + + + beast\wsproto\detail + + + beast\wsproto\detail + + + beast\wsproto + + + beast\wsproto\impl + + + beast\wsproto\impl + + + beast\wsproto\impl + + + beast\wsproto\impl + + + beast\wsproto\impl + + + beast\wsproto\impl + + + beast\wsproto\impl + + + beast\wsproto\impl + + + beast\wsproto\impl + + + beast\wsproto\impl + + + beast\wsproto\impl + + + beast\wsproto\impl + + + beast\wsproto + + + beast\wsproto + + + beast\wsproto + + + beast\wsproto\src\test + + + beast\wsproto\src\test + + + beast\wsproto\src\test + + + beast\wsproto\src\test + + + beast\wsproto + + + beast\wsproto + + + beast\wsproto + beast @@ -3834,6 +3954,12 @@ ripple\server\impl + + ripple\server\impl + + + ripple\server\impl + ripple\server\impl @@ -3852,6 +3978,9 @@ ripple\server\impl + + ripple\server\impl + ripple\server\impl @@ -3873,6 +4002,9 @@ ripple\server\impl + + ripple\server\impl + ripple\server @@ -3906,6 +4038,9 @@ ripple\server + + ripple\server + ripple\shamap @@ -4311,12 +4446,6 @@ ripple\websocket - - ripple\wsproto - - - ripple\wsproto - rocksdb2\db diff --git a/SConstruct b/SConstruct index bb16b673e..ca6c95f28 100644 --- a/SConstruct +++ b/SConstruct @@ -907,6 +907,7 @@ def get_classic_sources(toolchain): append_sources(result, *list_sources('src/beast/beast/http/src', '.cpp')) append_sources(result, *list_sources('src/beast/beast/streams', '.cpp')) append_sources(result, *list_sources('src/beast/beast/test', '.cpp')) + append_sources(result, *list_sources('src/beast/beast/wsproto/src', '.cpp')) append_sources(result, *list_sources('src/ripple/beast/container', '.cpp')) append_sources(result, *list_sources('src/ripple/beast/insight', '.cpp')) append_sources(result, *list_sources('src/ripple/beast/net', '.cpp')) @@ -957,6 +958,7 @@ def get_unity_sources(toolchain): 'src/beast/beast/unity/beast_http_unity.cpp', 'src/beast/beast/unity/beast_streams_unity.cpp', 'src/beast/beast/unity/beast_test_unity.cpp', + 'src/beast/beast/unity/beast_wsproto_unity.cpp', 'src/ripple/beast/unity/beast_container_unity.cpp', 'src/ripple/beast/unity/beast_insight_unity.cpp', 'src/ripple/beast/unity/beast_net_unity.cpp', diff --git a/src/beast/Jamroot b/src/beast/Jamroot index e18ad6b7d..aed04ce28 100644 --- a/src/beast/Jamroot +++ b/src/beast/Jamroot @@ -34,7 +34,16 @@ else if [ os.name ] = HAIKU lib network ; } -build-project test/asio ; +if [ os.name ] = NT +{ + lib ssl : : ssleay32 ; + lib crypto : : libeay32 ; +} +else +{ + lib ssl ; + lib crypto ; +} project beast : requirements @@ -49,6 +58,8 @@ project beast multi static static + gcc:-std=c++14 + clang:-std=c++14 LINUX:_XOPEN_SOURCE=600 LINUX:_GNU_SOURCE=1 SOLARIS:_XOPEN_SOURCE=500 @@ -68,7 +79,10 @@ project beast msvc:_SCL_SECURE_NO_WARNINGS=1 msvc:_CRT_SECURE_NO_WARNINGS=1 : usage-requirements - . + . : build-dir bin ; + +build-project test ; +build-project examples ; diff --git a/src/beast/TODO.txt b/src/beast/TODO.txt new file mode 100644 index 000000000..d5767cb80 --- /dev/null +++ b/src/beast/TODO.txt @@ -0,0 +1,8 @@ +* kick out non-beast code + +* redo directory structure + +* Change build options to C++11 only + +* Replace Jamroot with Jamfile + diff --git a/src/beast/beast/asio/buffers_debug.h b/src/beast/beast/asio/buffers_debug.h index 19689c372..e53617996 100644 --- a/src/beast/beast/asio/buffers_debug.h +++ b/src/beast/beast/asio/buffers_debug.h @@ -27,7 +27,6 @@ namespace beast { namespace debug { template -static std::string buffers_to_string(Buffers const& bs) { diff --git a/src/ripple/wsproto/wsproto.h b/src/beast/beast/unity/beast_wsproto_unity.cpp similarity index 78% rename from src/ripple/wsproto/wsproto.h rename to src/beast/beast/unity/beast_wsproto_unity.cpp index 2f8d326dd..c41bd2ac6 100644 --- a/src/ripple/wsproto/wsproto.h +++ b/src/beast/beast/unity/beast_wsproto_unity.cpp @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ /* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2016 Ripple Labs Inc. + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -17,9 +17,5 @@ */ //============================================================================== -#ifndef RIPPLE_WSPROTO_H_INCLUDED -#define RIPPLE_WSPROTO_H_INCLUDED - -#include - -#endif +#include +#include diff --git a/src/beast/beast/wsproto.h b/src/beast/beast/wsproto.h new file mode 100644 index 000000000..542b308a3 --- /dev/null +++ b/src/beast/beast/wsproto.h @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_H_INCLUDED +#define BEAST_WSPROTO_H_INCLUDED + +#include +#include +#include +#include +#include +#include + +#endif diff --git a/src/beast/beast/wsproto/README.md b/src/beast/beast/wsproto/README.md new file mode 100644 index 000000000..22fc6a518 --- /dev/null +++ b/src/beast/beast/wsproto/README.md @@ -0,0 +1,232 @@ +# Beast.WSProto + +-------------------------------------------------------------------------------- + +Beast.WSProto provides developers with a robust WebSocket implementation +built on Boost.Asio with a consistent asynchronous model using a modern +C++ approach. + +## Introduction + +Today's web applications increasingly rely on alternatives to standard HTTP +to achieve performance and/or responsiveness. While WebSocket implementations +are widely available in common web development languages such as Javascript, +good implementations in C++ are scarce. A survey of existing C++ WebSocket +solutions reveals interfaces which have performance limitations, place +unecessary restrictions on callers, exhibit excess complexity, and fail to +take advantage of C++ features or the underlying network transport. + +Beast.WSProto is built on Boost.Asio, a robust cross platform networking +framework that is part of Boost and also offered as a standalone library. +A proposal to add networking functionality to the C++ standard library, +based on Boost.Asio, is under consideration by the standards committee. +Since the final approved networking interface for the C++ standard library +will likely closely resemble the current interface of Boost.Asio, it is +logical for Beast.WSProto to use Boost.Asio as its network transport. + +Beast.WSProto addresses the following goals: + +* **Ease of Use.** WSProto offers only one socket object, whose interface +resembles that of Boost.Asio socket as closely as possible. Users familiar +with Boost.Asio will be immediately comfortable using a `wsproto::socket`. + +* **Flexibility.** Library interfaces should provide callers with maximum +flexibility in implementation; Important decisions such as how to manage +buffers or be notified of completed asynchronous operations should be made +by callers not the library. + +* **Performance.** The implementation should achieve the highest level +of performance possible, with no penalty for using abstractions. + +* **Scalability.** The library should facilitate the development of +network applications that scale to thousands of concurrent connections. + +* **Efficiency.** The library should support techniques such as +scatter-gather I/O, and allow programs to minimise data copying. + +* **Basis for further abstraction.** The library should permit the +development of other libraries that provide higher levels of abstraction. + +Beast.WSProto takes advantage of Boost.Asio's universal Asynchronous +model, handler allocation, and handler invocation hooks. Calls to wsproto +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`), 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. + +There is no need for inheritance or virtual members in `wsproto::socket`. +All operations are templated and transparent to the compiler, allowing for +maximum inlining and optimization. + +## Usage + +All examples and identifiers mentioned in this document are written as +if the following declarations are in effect: +```C++ +#include +using namespace beast; +using namespace boost::asio; +``` + +### Creating a Socket + +To participate in a WebSocket connection, callers create an instance +of `wsproto::socket` templated on the `Stream` argument, which must meet +the requirements of `AsyncReadStream`, `AsyncWriteStream`, `SyncReadStream`, +and `SyncWriteStream`. Examples of types that meet these requirements are +`ip::tcp::socket` and `ssl::stream<...>`: +```c++ +io_service ios; +wsproto::socket ws1(ios); // owns the socket + +ssl::context ctx(ssl::context::sslv23); +wsproto::socket> wss(ios, ctx); // owns the socket + +ip::tcp::socket sock(ios); +wsproto::socket ws2(sock); // does not own the socket +``` + +### Connection Establishment + +Callers are responsible for performing tasks such as connection establishment +before attempting websocket activities. +```c++ +io_service ios; +wsproto::socket ws(ios); +ws.next_layer().connect(ip::tcp::endpoint( + ip::tcp::address::from_string("127.0.0.1"), 80)); +``` + +### WebSocket Handshake + +After the connection is established, the socket may be used to initiate +or accept a WebSocket Update request. + +```c++ +// send a WebSocket Upgrade request. +ws.handshake(); +``` + +### Sending and Receiving Messages + +After the WebSocket handshake is accomplished, callers may send and receive +messages using the message oriented interface: +```c++ +void echo(wsproto::socket& ws) +{ + streambuf sb; + wsproto::opcode op; + wsproto::read(ws, op, sb); + wsproto::write(ws, op, sb.data()); + sb.consume(sb.size()); +} +``` + +Alternatively, callers may process incoming message data +incrementally: +```c++ +void echo(wsproto::socket& ws) +{ + streambuf sb; + wsproto::msg_info mi{}; + for(;;) + { + ws.read_some(mi, sb); + if(mi.fin) + break; + } + wsproto::write(ws, op, sb.data()); +} +``` + +### Asynchronous Completions, Coroutines, and Futures + +Asynchronous versions are available for all functions: +```c++ +wsproto::async_read(ws, sb, std::bind( + &on_read, beast::asio::placeholders::error)); +``` + +Calls to WSProto asynchronous initiation functions support +asio-style completion handlers, and other completion tokens +such as support for coroutines or futures: +```c++ +void echo(wsproto::socket& ws, + boost::asio::yield_context yield) +{ + wsproto::async_read(ws, sb, yield); + std::future fut = + wsproto::async_write(ws, sb.data(), boost::use_future); + ... +} +``` + +## Implementation + +### Buffers + +Because calls to read WebSocket 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`, which meets the requirements of `Streambuf` +defined below. + +The `Streambuf` concept is intended to permit the following implementation +strategies: + +* A single contiguous character array, which is reallocated as necessary to + accommodate changes in the size of the byte sequence. This is the + implementation approach currently used in `boost::asio::basic_streambuf`. +* A sequence of one or more byte arrays, where each array is of the same + size. Additional byte array objects are appended to the sequence to + accommodate changes in the size of the byte sequence. +* A sequence of one or more byte arrays of varying sizes. Additional byte + array objects are appended to the sequence to accommodate changes in the + size of the byte sequence. This is the implementation approach currently + used in `beast::basic_streambuf`. + +#### `Streambuf` requirements: + +In the table below, `X` denotes a class, `a` denotes a value +of type `X`, `n` denotes a value convertible to `std::size_t`, +and `U` and `T` denote unspecified types. + +expression | return | type assertion/note/pre/post-condition +------------------------- | ------------- | -------------------------------------- +`X::const_buffers_type` | `T` | `T` meets the requirements for `ConstBufferSequence`. +`X::mutable_buffers_type` | `U` | `U` meets the requirements for `MutableBufferSequence`. +`a.commit(n)` | | Moves bytes from the output sequence to the input sequence. +`a.consume(n)` | | Removes bytes from the input sequence. +`a.data()` | `T` | Returns a list of buffers that represents the input sequence. +`a.prepare(n)` | `U` | Returns a list of buffers that represents the output sequence, with the given size. +`a.size()` | `std::size_t` | Returns the size of the input sequence. +`a.max_size()` | `std::size_t` | Returns the maximum size of the `Streambuf`. + +### Thread Safety + +Like a regular asio socket, a `wsproto::socket` is not thread safe. Callers are +responsible for synchronizing operations on the socket using an implicit or +explicit strand, as per the Asio documentation. A `wsproto::socket` supports +one active read and one active write at the same time (caller initiated close, +ping, and pong operations count as a write). + +### Buffering + +The implementation does not perform queueing or buffering of messages. If desired, +these features should be implemented by callers. The impact of this design is +that the caller is in full control of the allocation strategy used to store +data and the back-pressure applied on the read and write side of the underlying +TCP/IP connection. + +### The `io_service` + +The creation and operation of the `boost::asio::io_service` associated with the +Stream object underlying the `wsproto::socket` is completely left up to the +user of the library, 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. diff --git a/src/beast/beast/wsproto/detail/debug.h b/src/beast/beast/wsproto/detail/debug.h new file mode 100644 index 000000000..a3696c9ff --- /dev/null +++ b/src/beast/beast/wsproto/detail/debug.h @@ -0,0 +1,88 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_DEBUG_H_INCLUDED +#define BEAST_WSPROTO_DEBUG_H_INCLUDED + +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { +namespace detail { + +template +std::string +to_hex(boost::asio::const_buffer b) +{ + using namespace boost::asio; + std::stringstream ss; + auto p = buffer_cast(b); + auto n = buffer_size(b); + while(n--) + { + ss << + std::setfill('0') << + std::setw(2) << + std::hex << int(*p++) << " "; + } + return ss.str(); +} + +template +std::string +to_hex(Buffers const& bs) +{ + std::string s; + for(auto const& b : bs) + s.append(to_hex(boost::asio::const_buffer(b))); + return s; +} + +template +std::string +buffers_to_string(Buffers const& bs) +{ + using namespace boost::asio; + std::string s; + s.reserve(buffer_size(bs)); + for(auto const& b : bs) + s.append(buffer_cast(b), + buffer_size(b)); + return s; +} + +template +std::string +format(std::string s) +{ + auto const w = 84; + for(int n = w*(s.size()/w); n>0; n-=w) + s.insert(n, 1, '\n'); + return s; +} + +} // detail +} // wsproto +} // beast + +#endif diff --git a/src/beast/beast/wsproto/detail/decorator.h b/src/beast/beast/wsproto/detail/decorator.h new file mode 100644 index 000000000..7078849cf --- /dev/null +++ b/src/beast/beast/wsproto/detail/decorator.h @@ -0,0 +1,112 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_DECORATOR_H_INCLUDED +#define BEAST_WSPROTO_DECORATOR_H_INCLUDED + +#include +#include +#include +#include + +namespace beast { +namespace wsproto { +namespace detail { + +using request_type = http::request; + +using response_type = http::response; + +struct abstract_decorator +{ + virtual + ~abstract_decorator() = default; + + virtual + void + operator()(request_type& req) = 0; + + virtual + void + operator()(response_type& resp) = 0; +}; + +template +class decorator : public abstract_decorator +{ + T t_; + +public: + decorator() = default; + + decorator(T&& t) + : t_(std::move(t)) + { + } + + decorator(T const& t) + : t_(t) + { + } + + void + operator()(request_type& req) override + { + t_(req); + } + + void + operator()(response_type& resp) override + { + t_(resp); + } +}; + +struct default_decorator +{ + static + char const* + version() + { + return "Beast.WSProto/1.0"; + } + + template + void + operator()(http::message& req) + { + req.headers.replace("User-Agent", version()); + } + + template + void + operator()(http::message& resp) + { + resp.headers.replace("Server", version()); + } +}; + +using decorator_type = + std::unique_ptr; + +} // detail +} // wsproto +} // beast + +#endif diff --git a/src/beast/beast/wsproto/detail/error.h b/src/beast/beast/wsproto/detail/error.h new file mode 100644 index 000000000..f19e1bc03 --- /dev/null +++ b/src/beast/beast/wsproto/detail/error.h @@ -0,0 +1,104 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_DETAIL_ERROR_H_INCLUDED +#define BEAST_WSPROTO_DETAIL_ERROR_H_INCLUDED + +#include + +namespace boost { +namespace system { +template<> +struct is_error_code_enum +{ + static bool const value = true; +}; +} // system +} // boost + +namespace beast { +namespace wsproto { +namespace detail { + +class error_category : public boost::system::error_category +{ +public: + const char* + name() const noexcept override + { + return "wsproto"; + } + + std::string + message(int ev) const override + { + switch(static_cast(ev)) + { + case error::closed: return "WebSocket connection closed normally"; + case error::failed: return "WebSocket connection failed due to a protocol violation"; + case error::handshake_failed: return "WebSocket Upgrade handshake failed"; + case error::keep_alive: return "WebSocket Upgrade handshake failed but connection is still open"; + + case error::response_malformed: return "malformed HTTP response"; + case error::response_failed: return "upgrade request failed"; + case error::response_denied: return "upgrade request denied"; + case error::request_malformed: return "malformed HTTP request"; + case error::request_invalid: return "upgrade request invalid"; + case error::request_denied: return "upgrade request denied"; + default: + return "wsproto.error"; + } + } + + boost::system::error_condition + default_error_condition(int ev) const noexcept override + { + return boost::system::error_condition(ev, *this); + } + + bool + equivalent(int ev, + boost::system::error_condition const& condition + ) const noexcept override + { + return condition.value() == ev && + &condition.category() == this; + } + + bool + equivalent(error_code const& error, int ev) const noexcept override + { + return error.value() == ev && + &error.category() == this; + } +}; + +inline +boost::system::error_category const& +get_error_category() +{ + static detail::error_category const cat{}; + return cat; +} + +} // detail +} // wsproto +} // beast + +#endif diff --git a/src/beast/beast/wsproto/detail/frame.h b/src/beast/beast/wsproto/detail/frame.h new file mode 100644 index 000000000..cd9db0780 --- /dev/null +++ b/src/beast/beast/wsproto/detail/frame.h @@ -0,0 +1,375 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_FRAME_H_INCLUDED +#define BEAST_WSPROTO_FRAME_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { +namespace detail { + +// Contents of a WebSocket frame header +struct frame_header +{ + opcode op; + bool fin; + bool mask; + bool rsv1; + bool rsv2; + bool rsv3; + std::uint64_t len; + std::uint32_t key; +}; + +// holds the largest possible frame header +using fh_streambuf = + static_streambuf_n<14>; + +// holds the largest possible control frame +using frame_streambuf = + static_streambuf_n< 2 + 8 + 4 + 125 >; + +inline +bool constexpr +is_reserved(opcode op) +{ + return + (op >= opcode::rsv3 && op <= opcode::rsv7) || + (op >= opcode::crsvb && op <= opcode::crsvf); +} + +inline +bool constexpr +is_valid(opcode op) +{ + return op <= opcode::crsvf; +} + +inline +bool constexpr +is_control(opcode op) +{ + return op >= opcode::close; +} + +// Returns `true` if a close code is valid +inline +bool +is_valid(close_code code) +{ + auto const v = static_cast< + std::uint16_t>(code); + switch(v) + { + case 1000: + case 1001: + case 1002: + case 1003: + case 1007: + case 1008: + case 1009: + case 1010: + case 1011: + case 1012: + case 1013: + return true; + + // explicitly reserved + case 1004: + case 1005: + case 1006: + case 1014: + case 1015: + return false; + } + // reserved + if(v >= 1016 && v <= 2999) + return false; + // not used + if(v >= 0 && v <= 999) + return false; + return true; +} + +//------------------------------------------------------------------------------ + +// Write frame header to streambuf +// +template +void +write(Streambuf& sb, frame_header const& fh) +{ + using boost::asio::buffer; + using boost::asio::buffer_copy; + using namespace boost::endian; + std::size_t n; + std::uint8_t b[14]; + b[0] = (fh.fin ? 0x80 : 0x00) | static_cast(fh.op); + b[1] = fh.mask ? 0x80 : 0x00; + if (fh.len <= 125) + { + b[1] |= fh.len; + n = 2; + } + else if (fh.len <= 65535) + { + b[1] |= 126; + ::new(&b[2]) big_uint16_buf_t{ + (std::uint16_t)fh.len}; + n = 4; + } + else + { + b[1] |= 127; + ::new(&b[2]) big_uint64_buf_t{fh.len}; + n = 10; + } + if(fh.mask) + { + little_uint32_buf_t key(fh.key); + std::copy(key.data(), + key.data() + 4, &b[n]); + n += 4; + } + sb.commit(buffer_copy( + sb.prepare(n), buffer(b))); +} + +// Read fixed frame header +// Requires at least 2 bytes +// +template +std::size_t +read_fh1(frame_header& fh, Streambuf& sb, + role_type role, close_code& code) +{ + using boost::asio::buffer; + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + std::uint8_t b[2]; + assert(buffer_size(sb.data()) >= sizeof(b)); + sb.consume(buffer_copy(buffer(b), sb.data())); + std::size_t need; + fh.len = b[1] & 0x7f; + switch(fh.len) + { + case 126: need = 2; break; + case 127: need = 8; break; + default: + need = 0; + } + if((fh.mask = (b[1] & 0x80) != 0)) + need += 4; + fh.op = static_cast(b[0] & 0x0f); + fh.fin = (b[0] & 0x80) != 0; + fh.rsv1 = (b[0] & 0x40) != 0; + fh.rsv2 = (b[0] & 0x20) != 0; + fh.rsv3 = (b[0] & 0x10) != 0; + // invalid length for control message + if(is_control(fh.op) && fh.len > 125) + { + code = close_code::protocol_error; + return 0; + } + // reserved bits not cleared + if(fh.rsv1 || fh.rsv2 || fh.rsv3) + { + code = close_code::protocol_error; + return 0; + } + // reserved opcode + if(is_reserved(fh.op)) + { + code = close_code::protocol_error; + return 0; + } + // invalid opcode + // (only in locally generated headers) + if(! is_valid(fh.op)) + { + code = close_code::protocol_error; + return 0; + } + // fragmented control message + if(is_control(fh.op) && ! fh.fin) + { + code = close_code::protocol_error; + return 0; + } + // unmasked frame from client + if(role == role_type::server && ! fh.mask) + { + code = close_code::protocol_error; + return 0; + } + // masked frame from server + if(role == role_type::client && fh.mask) + { + code = close_code::protocol_error; + return 0; + } + code = close_code::none; + return need; +} + +// Decode variable frame header from stream +// +template +void +read_fh2(frame_header& fh, Streambuf& sb, + role_type role, close_code& code) +{ + using boost::asio::buffer; + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + using namespace boost::endian; + switch(fh.len) + { + case 126: + { + std::uint8_t b[2]; + assert(buffer_size(sb.data()) >= sizeof(b)); + sb.consume(buffer_copy(buffer(b), sb.data())); + fh.len = reinterpret_cast< + big_uint16_buf_t const*>(&b[0])->value(); + // length not canonical + if(fh.len < 126) + { + code = close_code::protocol_error; + return; + } + break; + } + case 127: + { + std::uint8_t b[8]; + assert(buffer_size(sb.data()) >= sizeof(b)); + sb.consume(buffer_copy(buffer(b), sb.data())); + fh.len = reinterpret_cast< + big_uint64_buf_t const*>(&b[0])->value(); + // length not canonical + if(fh.len < 65536) + { + code = close_code::protocol_error; + return; + } + break; + } + } + if(fh.mask) + { + std::uint8_t b[4]; + assert(buffer_size(sb.data()) >= sizeof(b)); + sb.consume(buffer_copy(buffer(b), sb.data())); + fh.key = reinterpret_cast< + little_uint32_buf_t const*>(&b[0])->value(); + } + code = close_code::none; +} + +// Read data from buffers +// This is for ping and pong payloads +// +template +void +read(ping_payload_type& data, + Buffers const& bs, close_code& code) +{ + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + using boost::asio::mutable_buffers_1; + assert(buffer_size(bs) <= data.max_size()); + data.resize(buffer_size(bs)); + buffer_copy(mutable_buffers_1{ + data.data(), data.size()}, bs); +} + +// Read close_reason, return true on success +// This is for the close payload +// +template +void +read(close_reason& cr, + Buffers const& bs, close_code& code) +{ + using boost::asio::buffer; + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + using namespace boost::endian; + auto n = buffer_size(bs); + assert(n <= 125); + if(n == 0) + { + cr = close_reason{}; + code = close_code::none; + return; + } + if(n == 1) + { + code = close_code::protocol_error; + return; + } + consuming_buffers cb(bs); + { + std::uint8_t b[2]; + buffer_copy(buffer(b), cb); + cr.code = static_cast( + reinterpret_cast< + big_uint16_buf_t const*>(&b[0])->value()); + cb.consume(2); + n -= 2; + if(! is_valid(cr.code)) + { + code = close_code::protocol_error; + return; + } + } + if(n > 0) + { + cr.reason.resize(n); + buffer_copy(buffer(&cr.reason[0], n), cb); + if(! detail::check_utf8( + cr.reason.data(), cr.reason.size())) + { + code = close_code::protocol_error; + return; + } + } + else + { + cr.reason = ""; + } + code = close_code::none; +} + +} // detail +} // wsproto +} // beast + +#endif diff --git a/src/beast/beast/wsproto/detail/hybi13.h b/src/beast/beast/wsproto/detail/hybi13.h new file mode 100644 index 000000000..ec7d56559 --- /dev/null +++ b/src/beast/beast/wsproto/detail/hybi13.h @@ -0,0 +1,66 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_HYBI13_H_INCLUDED +#define BEAST_WSPROTO_HYBI13_H_INCLUDED + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { +namespace detail { + +template +std::string +make_sec_ws_key(Gen& g) +{ + union U + { + std::array a4; + std::array a16; + }; + U u; + for(int i = 0; i < 4; ++i) + u.a4[i] = g(); + return base64_encode(u.a16.data(), u.a16.size()); +} + +template +std::string +make_sec_ws_accept(boost::string_ref const& key) +{ + std::string s(key.data(), key.size()); + s += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + beast::sha_hasher h; + h(s.data(), s.size()); + auto const digest = static_cast< + beast::sha_hasher::result_type>(h); + return base64_encode(digest.data(), digest.size()); +} + +} // detail +} // wsproto +} // beast + +#endif diff --git a/src/beast/beast/wsproto/detail/invokable.h b/src/beast/beast/wsproto/detail/invokable.h new file mode 100644 index 000000000..140246893 --- /dev/null +++ b/src/beast/beast/wsproto/detail/invokable.h @@ -0,0 +1,168 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_INVOKABLE_H_INCLUDED +#define BEAST_WSPROTO_INVOKABLE_H_INCLUDED + +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { +namespace detail { + +// "Parks" a composed operation, to invoke later +// +class invokable +{ + struct base + { + base() = default; + base(base &&) = default; + virtual ~base() = default; + virtual void move(void* p) = 0; + virtual void operator()() = 0; + }; + + template + struct holder : base + { + F f; + + holder(holder&&) = default; + + template + explicit + holder(U&& u) + : f(std::forward(u)) + { + } + + void + move(void* p) override + { + ::new(p) holder(std::move(*this)); + } + + void + operator()() override + { + F f_(std::move(f)); + this->~holder(); + // invocation of f_() can + // assign a new invokable. + f_(); + } + }; + + struct exemplar + { + std::shared_ptr _; + void operator()(){} + }; + + using buf_type = std::uint8_t[ + sizeof(holder)]; + + bool b_ = false; + alignas(holder) buf_type buf_; + +public: +#ifndef NDEBUG + ~invokable() + { + // Engaged invokables must be invoked before + // destruction otherwise the io_service + // invariants are broken w.r.t completions. + assert(! b_); + } +#endif + + invokable() = default; + invokable(invokable const&) = delete; + invokable& operator=(invokable const&) = delete; + + invokable(invokable&& other) + : b_(other.b_) + { + if(other.b_) + { + other.get().move(buf_); + other.b_ = false; + } + } + + invokable& + operator=(invokable&& other) + { + // Engaged invokables must be invoked before + // assignment otherwise the io_service + // invariants are broken w.r.t completions. + assert(! b_); + + if(other.b_) + { + b_ = true; + other.get().move(buf_); + other.b_ = false; + } + return *this; + } + + template + void + emplace(F&& f); + + void + maybe_invoke() + { + if(b_) + { + b_ = false; + get()(); + } + } + +private: + base& + get() + { + return *reinterpret_cast(buf_); + } +}; + +template +void +invokable::emplace(F&& f) +{ + static_assert(sizeof(buf_type) >= sizeof(holder), + "buffer too small"); + assert(! b_); + ::new(buf_) holder(std::forward(f)); + b_ = true; +} + +} // detail +} // wsproto +} // beast + +#endif diff --git a/src/beast/beast/wsproto/detail/mask.h b/src/beast/beast/wsproto/detail/mask.h new file mode 100644 index 000000000..220268665 --- /dev/null +++ b/src/beast/beast/wsproto/detail/mask.h @@ -0,0 +1,389 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_DETAIL_MASKGEN_H_INCLUDED +#define BEAST_WSPROTO_DETAIL_MASKGEN_H_INCLUDED + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { +namespace detail { + +// Pseudo-random source of mask keys +// +template +class maskgen_t +{ + std::mt19937 g_; + +public: + using result_type = typename std::mt19937::result_type; + + maskgen_t(maskgen_t const&) = delete; + maskgen_t& operator=(maskgen_t const&) = delete; + + maskgen_t(); + + result_type + operator()() noexcept; + + void + rekey(); +}; + +template +maskgen_t<_>::maskgen_t() +{ + rekey(); +} + +template +auto +maskgen_t<_>::operator()() noexcept -> + result_type +{ + for(;;) + if(auto key = g_()) + return key; +} + +template +void +maskgen_t<_>::rekey() +{ + std::random_device rng; + std::array e; + for(auto& i : e) + i = rng(); + std::seed_seq ss(e.begin(), e.end()); + g_.seed(ss); +} + +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; + +inline +void +prepare_key(std::uint32_t& prepared, std::uint32_t key) +{ + prepared = key; +} + +inline +void +prepare_key(std::uint64_t& prepared, std::uint32_t key) +{ + prepared = + (static_cast(key) << 32) | key; +} + +template +inline +std::enable_if_t::value, T> +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>(t) >> (bits - n))); +} + +template +inline +std::enable_if_t::value, T> +ror(T t, unsigned n = 1) +{ + auto constexpr bits = + static_cast( + sizeof(T) * CHAR_BIT); + n &= bits-1; + return static_cast((t << (bits - n)) | + (static_cast>(t) >> n)); +} + +// 32-bit Uuoptimized +// +template +void +mask_inplace_safe( + 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) + { + *p ^= key ; ++p; + *p ^= (key >> 8); ++p; + *p ^= (key >>16); ++p; + *p ^= (key >>24); ++p; + } + 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 unoptimized +// +template +void +mask_inplace_safe( + 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) + { + *p ^= key ; ++p; + *p ^= (key >> 8); ++p; + *p ^= (key >>16); ++p; + *p ^= (key >>24); ++p; + *p ^= (key >>32); ++p; + *p ^= (key >>40); ++p; + *p ^= (key >>48); ++p; + *p ^= (key >>56); ++p; + } + 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; + } +} + +// 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); +} + +inline +void +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); +} + +// Apply mask in place +// +template +void +mask_inplace( + MutableBuffers const& bs, KeyType& key) +{ + for(auto const& b : bs) + mask_inplace(b, key); +} + +} // detail +} // wsproto +} // beast + +#endif diff --git a/src/beast/beast/wsproto/detail/socket_base.h b/src/beast/beast/wsproto/detail/socket_base.h new file mode 100644 index 000000000..11abd8b5f --- /dev/null +++ b/src/beast/beast/wsproto/detail/socket_base.h @@ -0,0 +1,141 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_SOCKET_BASE_H_INCLUDED +#define BEAST_WSPROTO_SOCKET_BASE_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { +namespace detail { + +template +inline +void +maybe_throw(error_code const& ec, String const&) +{ + if(ec) + throw boost::system::system_error{ec}; +} + +template +static +std::size_t +clamp(UInt x) +{ + if(x >= std::numeric_limits::max()) + return std::numeric_limits::max(); + return static_cast(x); +} + +template +static +std::size_t +clamp(UInt x, std::size_t limit) +{ + if(x >= limit) + return limit; + return static_cast(x); +} + +//------------------------------------------------------------------------------ + +struct socket_base +{ +protected: + struct op {}; + + 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 + 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 + + 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 + + invokable rd_op_; // invoked after write completes + invokable wr_op_; // invoked after read completes + close_reason cr_; // set from received close frame + + socket_base() + : d_(std::make_unique< + decorator>()) + { + } + + socket_base(socket_base&&) = default; + socket_base(socket_base const&) = delete; + socket_base& operator=(socket_base&&) = default; + socket_base& operator=(socket_base const&) = delete; + + template + void + prepare_fh(close_code& code); + + template + void + write_close(Streambuf& sb, + close_reason const& rc); + + template + void + write_ping(Streambuf& sb, opcode op, + ping_payload_type const& data); +}; + +} // detail +} // wsproto +} // beast + +#endif diff --git a/src/beast/beast/wsproto/detail/utf8_checker.h b/src/beast/beast/wsproto/detail/utf8_checker.h new file mode 100644 index 000000000..7d35134c4 --- /dev/null +++ b/src/beast/beast/wsproto/detail/utf8_checker.h @@ -0,0 +1,184 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_UTF8_CHECKER_H_INCLUDED +#define BEAST_WSPROTO_UTF8_CHECKER_H_INCLUDED + +#include +#include +#include // DEPRECATED + +namespace beast { +namespace wsproto { +namespace detail { + +// Code adapted from +// http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ +/* + Copyright (c) 2008-2009 Bjoern Hoehrmann + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject + to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * +*/ +template +class utf8_checker_t +{ + // Table for the UTF8 decode state machine + using lut_type = std::uint8_t[400]; + static + lut_type const& + lut() + { + // 400 elements + static std::uint8_t constexpr tab[] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5f + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 60..7f + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, // 80..9f + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, // a0..bf + 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // c0..df + 0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef + 0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff + 0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, // s0..s0 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, // s1..s2 + 1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, // s3..s4 + 1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, // s5..s6 + 1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1 // s7..s8 + }; + return tab; + } + + std::uint32_t state_ = 0; + std::uint32_t codepoint_ = 0; + +public: + utf8_checker_t() = default; + utf8_checker_t(utf8_checker_t&&) = default; + utf8_checker_t(utf8_checker_t const&) = default; + utf8_checker_t& operator=(utf8_checker_t&&) = default; + utf8_checker_t& operator=(utf8_checker_t const&) = default; + + void + reset(); + + // Returns `true` on success + bool + write(void const* buffer, std::size_t size); + + // Returns `true` on success + template + bool + write(BufferSequence const& bs); + + // Returns `true` on success + bool + finish(); +}; + +template +void +utf8_checker_t<_>::reset() +{ + state_ = 0; + codepoint_ = 0; +} + +template +bool +utf8_checker_t<_>::write(void const* buffer, std::size_t size) +{ + auto p = static_cast(buffer); + auto plut = &lut()[0]; + while(size) + { + auto const byte = *p; + auto const type = plut[byte]; + if(state_) + codepoint_ = (byte & 0x3fu) | (codepoint_ << 6); + else + codepoint_ = (0xff >> type) & byte; + state_ = plut[256 + state_ * 16 + type]; + if(state_ == 1) + { + reset(); + return false; + } + ++p; + --size; + } + return true; +} + +template +template +bool +utf8_checker_t<_>::write(BufferSequence const& bs) +{ + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + for (auto const& b : bs) + if(! write(buffer_cast(b), + buffer_size(b))) + return false; + return true; +} + +template +bool +utf8_checker_t<_>::finish() +{ + auto const success = state_ == 0; + reset(); + return success; +} + +using utf8_checker = utf8_checker_t<>; + +template +bool +check_utf8(char const* p, std::size_t n) +{ + utf8_checker c; + if(! c.write(p, n)) + return false; + return c.finish(); +} + +} // detail +} // wsproto +} // beast + +#endif diff --git a/src/beast/beast/wsproto/error.h b/src/beast/beast/wsproto/error.h new file mode 100644 index 000000000..1ccb658ab --- /dev/null +++ b/src/beast/beast/wsproto/error.h @@ -0,0 +1,72 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_ERROR_H_INCLUDED +#define BEAST_WSPROTO_ERROR_H_INCLUDED + +#include + +namespace beast { +namespace wsproto { + +using error_code = boost::system::error_code; + +/// Error values +enum class error +{ + /// Both sides performed a WebSocket close + closed = 1, + + /// WebSocket connection failed, protocol violation + failed, + + /// Upgrade request failed, connection is closed + handshake_failed, + + /// Upgrade request failed, but connection is still open + keep_alive, + + /// HTTP response is malformed + response_malformed, + + /// HTTP response failed the upgrade + response_failed, + + /// Upgrade request denied for invalid fields. + response_denied, + + /// Upgrade request is malformed + request_malformed, + + /// Upgrade request fields incorrect + request_invalid, + + /// Upgrade request denied + request_denied +}; + +error_code +make_error_code(error e); + +} // wsproto +} // beast + +#include + +#endif diff --git a/src/beast/beast/wsproto/impl/accept_op.ipp b/src/beast/beast/wsproto/impl/accept_op.ipp new file mode 100644 index 000000000..c82e24cd1 --- /dev/null +++ b/src/beast/beast/wsproto/impl/accept_op.ipp @@ -0,0 +1,158 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_ACCEPT_OP_H_INCLUDED +#define BEAST_WSPROTO_ACCEPT_OP_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { + +// read and respond to an upgrade request +// +template +template +class socket::accept_op +{ + using alloc_type = + handler_alloc; + + struct data + { + socket& ws; + http::request req; + Handler h; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, socket& ws_, + Buffers const& buffers) + : ws(ws_) + , h(std::forward(h_)) + , cont(boost_asio_handler_cont_helpers:: + is_continuation(h)) + { + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + ws.stream_.buffer().commit(buffer_copy( + ws.stream_.buffer().prepare( + buffer_size(buffers)), buffers)); + } + }; + + std::shared_ptr d_; + +public: + accept_op(accept_op&&) = default; + accept_op(accept_op const&) = default; + + template + accept_op(DeducedHandler&& h, + socket& ws, Args&&... args) + : d_(std::allocate_shared(alloc_type{h}, + std::forward(h), ws, + std::forward(args)...)) + { + (*this)(error_code{}, 0, false); + } + + void operator()(error_code const& ec) + { + (*this)(ec, 0); + } + + void operator()(error_code const& ec, + std::size_t bytes_transferred, bool again = true); + + friend + auto asio_handler_allocate( + std::size_t size, accept_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + auto asio_handler_deallocate( + void* p, std::size_t size, accept_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + auto asio_handler_is_continuation(accept_op* op) + { + return op->d_->cont; + } + + template + friend + auto asio_handler_invoke(Function&& f, accept_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +template +void +socket::accept_op:: +operator()(error_code const& ec, + std::size_t bytes_transferred, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + while(! ec && d.state != 99) + { + switch(d.state) + { + case 0: + // read message + d.state = 1; + http::async_read(d.ws.next_layer_, + d.ws.stream_.buffer(), d.req, + std::move(*this)); + return; + + // got message + case 1: + // respond to request + response_op{ + std::move(d.h), d.ws, d.req, true}; + return; + } + } + d.h(ec); +} + +} // wsproto +} // beast + +#endif diff --git a/src/beast/beast/wsproto/impl/close_op.ipp b/src/beast/beast/wsproto/impl/close_op.ipp new file mode 100644 index 000000000..f17e050ea --- /dev/null +++ b/src/beast/beast/wsproto/impl/close_op.ipp @@ -0,0 +1,198 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_CLOSE_OP_H_INCLUDED +#define BEAST_WSPROTO_CLOSE_OP_H_INCLUDED + +#include +#include +#include + +namespace beast { +namespace wsproto { + +// send the close message and wait for the response +// +template +template +class socket::close_op +{ + using alloc_type = + handler_alloc; + using fb_type = + detail::frame_streambuf; + using fmb_type = + typename fb_type::mutable_buffers_type; + + struct data : op + { + socket& ws; + close_reason cr; + Handler h; + fb_type fb; + fmb_type fmb; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, socket& ws_, + close_reason const& cr_) + : ws(ws_) + , cr(cr_) + , h(std::forward(h_)) + , cont(boost_asio_handler_cont_helpers:: + is_continuation(h)) + { + ws.template write_close< + static_streambuf>(fb, cr); + } + }; + + std::shared_ptr d_; + +public: + close_op(close_op&&) = default; + close_op(close_op const&) = default; + + template + close_op(DeducedHandler&& h, + socket& ws, Args&&... args) + : d_(std::allocate_shared(alloc_type{h}, + std::forward(h), ws, + std::forward(args)...)) + { + (*this)(error_code{}, 0, false); + } + + void operator()() + { + auto& d = *d_; + d.cont = false; + (*this)(error_code{}, 0, false); + } + + void operator()(error_code const& ec) + { + (*this)(ec, 0); + } + + void + operator()(error_code ec, + std::size_t bytes_transferred, bool again = true); + + friend + auto asio_handler_allocate( + std::size_t size, close_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + auto asio_handler_deallocate( + void* p, std::size_t size, close_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + auto asio_handler_is_continuation(close_op* op) + { + return op->d_->cont; + } + + template + friend + auto asio_handler_invoke(Function&& f, close_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +template +void +socket::close_op::operator()( + error_code ec, std::size_t bytes_transferred, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + while(! ec && d.state != 99) + { + switch(d.state) + { + case 0: + if(d.ws.wr_block_) + { + // suspend + d.state = 1; + d.ws.rd_op_.template emplace< + close_op>(std::move(*this)); + return; + } + if(d.ws.error_) + { + // call handler + d.state = 99; + d.ws.get_io_service().post( + bind_handler(std::move(*this), + boost::asio::error::operation_aborted, 0)); + return; + } + d.state = 2; + break; + + // 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 + 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; + } + } + if(ec) + d.ws.error_ = true; + if(d.ws.wr_block_ == &d) + d.ws.wr_block_ = nullptr; + d.h(ec); + d.ws.rd_op_.maybe_invoke(); +} + +} // wsproto +} // beast + +#endif diff --git a/src/beast/beast/wsproto/impl/error.ipp b/src/beast/beast/wsproto/impl/error.ipp new file mode 100644 index 000000000..85a7fe954 --- /dev/null +++ b/src/beast/beast/wsproto/impl/error.ipp @@ -0,0 +1,39 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_ERROR_IPP_H_INCLUDED +#define BEAST_WSPROTO_ERROR_IPP_H_INCLUDED + +#include + +namespace beast { +namespace wsproto { + +inline +error_code +make_error_code(error e) +{ + return error_code( + static_cast(e), detail::get_error_category()); +} + +} // wsproto +} // beast + +#endif diff --git a/src/beast/beast/wsproto/impl/handshake_op.ipp b/src/beast/beast/wsproto/impl/handshake_op.ipp new file mode 100644 index 000000000..2f6538fb7 --- /dev/null +++ b/src/beast/beast/wsproto/impl/handshake_op.ipp @@ -0,0 +1,170 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_HANDSHAKE_OP_H_INCLUDED +#define BEAST_WSPROTO_HANDSHAKE_OP_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { + +// send the upgrade request and process the response +// +template +template +class socket::handshake_op +{ + using alloc_type = + handler_alloc; + + struct data + { + socket& ws; + Handler h; + std::string key; + http::request req; + http::response resp; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, socket& ws_, + boost::string_ref const& host, + boost::string_ref const& resource) + : ws(ws_) + , h(std::forward(h_)) + , req(ws.build_request(host, resource, key)) + , cont(boost_asio_handler_cont_helpers:: + is_continuation(h)) + { + } + }; + + std::shared_ptr d_; + +public: + handshake_op(handshake_op&&) = default; + handshake_op(handshake_op const&) = default; + + template + handshake_op(DeducedHandler&& h, + socket& ws, Args&&... args) + : d_(std::allocate_shared(alloc_type{h}, + std::forward(h), ws, + std::forward(args)...)) + { + (*this)(error_code{}, 0, false); + } + + void operator()(error_code const& ec) + { + (*this)(ec, 0); + } + + void operator()(error_code ec, + std::size_t bytes_transferred, bool again = true); + + friend + auto asio_handler_allocate( + std::size_t size, handshake_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + auto asio_handler_deallocate( + void* p, std::size_t size, handshake_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + auto asio_handler_is_continuation(handshake_op* op) + { + return op->d_->cont; + } + + template + friend + auto asio_handler_invoke(Function&& f, handshake_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +template +void +socket::handshake_op< + Handler>::operator()(error_code ec, + std::size_t bytes_transferred, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + while(! ec && d.state != 99) + { + switch(d.state) + { + case 0: + { + // send http upgrade + d.state = 1; + // VFALCO Do we need the ability to move + // a message on the async_write? + http::async_write(d.ws.stream_, + d.req, std::move(*this)); + return; + } + + // sent upgrade + case 1: + // read http response + d.state = 2; + http::async_read(d.ws.next_layer_, + d.ws.stream_.buffer(), d.resp, + std::move(*this)); + return; + + // got response + case 2: + { + d.ws.do_response(d.resp, d.key, ec); + // call handler + d.state = 99; + break; + } + } + } + d.h(ec); +} + +} // wsproto +} // beast + +#endif diff --git a/src/beast/beast/wsproto/impl/read_frame_op.ipp b/src/beast/beast/wsproto/impl/read_frame_op.ipp new file mode 100644 index 000000000..9c3d1f017 --- /dev/null +++ b/src/beast/beast/wsproto/impl/read_frame_op.ipp @@ -0,0 +1,519 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_READ_FRAME_OP_H_INCLUDED +#define BEAST_WSPROTO_READ_FRAME_OP_H_INCLUDED + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { + +// Reads a single message frame, +// processes any received control frames. +// +template +template +class socket::read_frame_op +{ + using alloc_type = + handler_alloc; + + using fb_type = + detail::frame_streambuf; + + using fmb_type = + typename fb_type::mutable_buffers_type; + + using smb_type = + typename Streambuf::mutable_buffers_type; + + struct data : op + { + socket& ws; + frame_info& fi; + Streambuf& sb; + smb_type smb; + Handler h; + fb_type fb; + fmb_type fmb; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, socket& ws_, + frame_info& fi_, Streambuf& sb_) + : ws(ws_) + , fi(fi_) + , sb(sb_) + , h(std::forward(h_)) + , cont(boost_asio_handler_cont_helpers:: + is_continuation(h)) + { + } + }; + + std::shared_ptr d_; + +public: + read_frame_op(read_frame_op&&) = default; + read_frame_op(read_frame_op const&) = default; + + template + read_frame_op(DeducedHandler&& h, + socket& ws, Args&&... args) + : d_(std::allocate_shared(alloc_type{h}, + std::forward(h), ws, + std::forward(args)...)) + { + (*this)(error_code{}, 0, false); + } + + void operator()() + { + auto& d = *d_; + d.cont = false; + (*this)(error_code{}, 0, false); + } + + void operator()(error_code const& ec) + { + (*this)(ec, 0); + } + + void operator()(error_code ec, + std::size_t bytes_transferred, bool again = true); + + friend + auto asio_handler_allocate( + std::size_t size, read_frame_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + auto asio_handler_deallocate( + void* p, std::size_t size, read_frame_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + auto asio_handler_is_continuation(read_frame_op* op) + { + return op->d_->cont; + } + + template + friend + auto asio_handler_invoke(Function&& f, read_frame_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +template +void +socket::read_frame_op:: +operator()(error_code ec,std::size_t bytes_transferred, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + close_code code; + while(! ec && d.state != 99) + { + switch(d.state) + { + case 0: + if(d.ws.error_) + { + // call handler + d.state = 99; + d.ws.get_io_service().post( + bind_handler(std::move(*this), + boost::asio::error::operation_aborted, 0)); + 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) + { + 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(); + if(d.ws.wr_close_) + { + d.state = 2; + break; + } + d.ws.template write_ping( + d.fb, opcode::pong, data); + if(d.ws.wr_block_) + { + 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 = 14; + 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; + break; + } + assert(d.ws.rd_fh_.op == opcode::close); + { + detail::read(d.ws.cr_, d.fb.data(), code); + if(code != close_code::none) + { + 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 = ""; + d.fb.reset(); + d.ws.template write_close< + static_streambuf>(d.fb, cr); + if(d.ws.wr_block_) + { + // suspend + d.state = 9; + d.ws.rd_op_.template emplace< + read_frame_op>(std::move(*this)); + return; + } + d.state = 10; + break; + } + // call handler; + d.state = 99; + ec = error::closed; + break; + } + + // 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;; + + // teardown + case 11: + d.state = 12; + wsproto_helpers::call_async_teardown( + d.ws.next_layer_, std::move(*this)); + return; + + 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_) + { + d.fb.reset(); + d.state = 2; + 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; + + // fail the connection + case 16: + if(! d.ws.wr_close_) + { + d.fb.reset(); + d.ws.template write_close< + static_streambuf>(d.fb, code); + if(d.ws.wr_block_) + { + // suspend + d.state = 17; + d.ws.rd_op_.template emplace< + read_frame_op>(std::move(*this)); + return; + } + d.state = 18; + break; + } + + // 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; + wsproto_helpers::call_async_teardown( + d.ws.next_layer_, std::move(*this)); + return; + + case 20: + // call handler + d.state = 99; + ec = error::failed; + break; + } + } + if(ec) + d.ws.error_ = true; + if(d.ws.wr_block_ == &d) + d.ws.wr_block_ = nullptr; + d.h(ec); + d.ws.wr_op_.maybe_invoke(); +} + +} // wsproto +} // beast + +#endif diff --git a/src/beast/beast/wsproto/impl/read_op.ipp b/src/beast/beast/wsproto/impl/read_op.ipp new file mode 100644 index 000000000..895a91514 --- /dev/null +++ b/src/beast/beast/wsproto/impl/read_op.ipp @@ -0,0 +1,144 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_READ_OP_H_INCLUDED +#define BEAST_WSPROTO_READ_OP_H_INCLUDED + +#include +#include + +namespace beast { +namespace wsproto { + +// read an entire message +// +template +template +class socket::read_op +{ + using alloc_type = + handler_alloc; + + struct data + { + socket& ws; + opcode& op; + Streambuf& sb; + Handler h; + frame_info fi; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, + socket& ws_, opcode& op_, + Streambuf& sb_) + : ws(ws_) + , op(op_) + , sb(sb_) + , h(std::forward(h_)) + , cont(boost_asio_handler_cont_helpers:: + is_continuation(h)) + { + } + }; + + std::shared_ptr d_; + +public: + read_op(read_op&&) = default; + read_op(read_op const&) = default; + + template + read_op(DeducedHandler&& h, + socket& ws, Args&&... args) + : d_(std::allocate_shared(alloc_type{h}, + std::forward(h), ws, + std::forward(args)...)) + { + (*this)(error_code{}, false); + } + + void operator()( + error_code const& ec, bool again = true); + + friend + auto asio_handler_allocate( + std::size_t size, read_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + auto asio_handler_deallocate( + void* p, std::size_t size, read_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + auto asio_handler_is_continuation(read_op* op) + { + return op->d_->cont; + } + + template + friend + auto asio_handler_invoke(Function&& f, read_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +template +void +socket::read_op:: +operator()(error_code const& ec, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + while(! ec && d.state != 99) + { + switch(d.state) + { + case 0: + // read payload + d.state = 1; + d.ws.async_read_frame( + d.fi, d.sb, std::move(*this)); + return; + + // got payload + case 1: + d.op = d.fi.op; + d.state = d.fi.fin ? 99 : 0; + break; + } + } + d.h(ec); +} + +} // wsproto +} // beast + +#endif diff --git a/src/beast/beast/wsproto/impl/response_op.ipp b/src/beast/beast/wsproto/impl/response_op.ipp new file mode 100644 index 000000000..ff7e6f7f7 --- /dev/null +++ b/src/beast/beast/wsproto/impl/response_op.ipp @@ -0,0 +1,147 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_RESPONSE_OP_H_INCLUDED +#define BEAST_WSPROTO_RESPONSE_OP_H_INCLUDED + +#include +#include +#include +#include + +namespace beast { +namespace wsproto { + +// Respond to an upgrade HTTP request +template +template +class socket::response_op +{ + using alloc_type = + handler_alloc; + + struct data + { + socket& ws; + http::response resp; + Handler h; + error_code final_ec; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, socket& ws_, + http::message const& req, + bool cont_) + : ws(ws_) + , resp(ws_.build_response(req)) + , h(std::forward(h_)) + , cont(cont_) + { + if(resp.status != 101) + final_ec = error::handshake_failed; + } + }; + + std::shared_ptr d_; + +public: + response_op(response_op&&) = default; + response_op(response_op const&) = default; + + template + response_op(DeducedHandler&& h, + socket& ws, Args&&... args) + : d_(std::allocate_shared(alloc_type{h}, + std::forward(h), ws, + std::forward(args)...)) + { + (*this)(error_code{}, false); + } + + void operator()( + error_code ec, bool again = true); + + friend + auto asio_handler_allocate( + std::size_t size, response_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + auto asio_handler_deallocate( + void* p, std::size_t size, response_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + auto asio_handler_is_continuation(response_op* op) + { + return op->d_->cont; + } + + template + friend + auto asio_handler_invoke(Function&& f, response_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +template +void +socket::response_op:: +operator()(error_code ec, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + while(! ec && d.state != 99) + { + switch(d.state) + { + case 0: + // send response + d.state = 1; + http::async_write(d.ws.next_layer_, + d.resp, std::move(*this)); + return; + + // sent response + case 1: + d.state = 99; + ec = d.final_ec; + if(! ec) + d.ws.role_ = role_type::server; + break; + } + } + d.h(ec); +} + +} // wsproto +} // beast + +#endif diff --git a/src/beast/beast/wsproto/impl/socket.ipp b/src/beast/beast/wsproto/impl/socket.ipp new file mode 100644 index 000000000..50607f9a8 --- /dev/null +++ b/src/beast/beast/wsproto/impl/socket.ipp @@ -0,0 +1,815 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_IMPL_SOCKET_IPP_INCLUDED +#define BEAST_WSPROTO_IMPL_SOCKET_IPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { + +namespace detail { + +template +void +socket_base::prepare_fh(close_code& code) +{ + // continuation without an active message + if(! rd_cont_ && rd_fh_.op == opcode::cont) + { + code = close_code::protocol_error; + return; + } + // new data frame when continuation expected + if(rd_cont_ && ! is_control(rd_fh_.op) && + rd_fh_.op != opcode::cont) + { + code = close_code::protocol_error; + return; + } + if(rd_fh_.mask) + prepare_key(rd_key_, rd_fh_.key); + if(! is_control(rd_fh_.op)) + { + if(rd_fh_.op != opcode::cont) + { + rd_size_ = rd_fh_.len; + rd_opcode_ = rd_fh_.op; + } + else + { + if(rd_size_ > std::numeric_limits< + std::uint64_t>::max() - rd_fh_.len) + { + code = close_code::too_big; + return; + } + rd_size_ += rd_fh_.len; + } + if(rd_size_ > rd_msg_max_) + { + code = close_code::too_big; + return; + } + rd_need_ = rd_fh_.len; + rd_cont_ = ! rd_fh_.fin; + } +} + +template +void +socket_base::write_close( + Streambuf& sb, close_reason const& cr) +{ + using namespace boost::endian; + frame_header fh; + fh.op = opcode::close; + fh.fin = true; + fh.rsv1 = false; + fh.rsv2 = false; + fh.rsv3 = false; + fh.len = cr.code == close_code::none ? + 0 : 2 + cr.reason.size(); + if((fh.mask = (role_ == role_type::client))) + fh.key = maskgen_(); + detail::write(sb, fh); + if(cr.code != close_code::none) + { + detail::prepared_key_type key; + if(fh.mask) + detail::prepare_key(key, fh.key); + { + std::uint8_t b[2]; + ::new(&b[0]) big_uint16_buf_t{ + (std::uint16_t)cr.code}; + auto d = sb.prepare(2); + boost::asio::buffer_copy(d, + boost::asio::buffer(b)); + if(fh.mask) + detail::mask_inplace(d, key); + sb.commit(2); + } + if(! cr.reason.empty()) + { + auto d = sb.prepare(cr.reason.size()); + boost::asio::buffer_copy(d, + boost::asio::const_buffer( + cr.reason.data(), cr.reason.size())); + if(fh.mask) + detail::mask_inplace(d, key); + sb.commit(cr.reason.size()); + } + } +} + +template +void +socket_base::write_ping(Streambuf& sb, + opcode op, ping_payload_type const& data) +{ + frame_header fh; + fh.op = op; + fh.fin = true; + fh.rsv1 = false; + fh.rsv2 = false; + fh.rsv3 = false; + fh.len = data.size(); + if((fh.mask = (role_ == role_type::client))) + fh.key = maskgen_(); + detail::write(sb, fh); + if(data.empty()) + return; + detail::prepared_key_type key; + if(fh.mask) + detail::prepare_key(key, fh.key); + auto d = sb.prepare(data.size()); + boost::asio::buffer_copy(d, + boost::asio::const_buffers_1( + data.data(), data.size())); + if(fh.mask) + detail::mask_inplace(d, key); + sb.commit(data.size()); +} + +} // detail + +//------------------------------------------------------------------------------ + +template +template +socket::socket(Args&&... args) + : next_layer_(std::forward(args)...) + , stream_(next_layer_) +{ + static_assert(is_Stream::value, + "Stream requirements not met"); +} + +template +void +socket::accept(error_code& ec) +{ + accept(boost::asio::null_buffers{}, ec); +} + +template +template +auto +socket::async_accept(AcceptHandler&& handler) +{ + return async_accept(boost::asio::null_buffers{}, + std::forward(handler)); +} + +template +template +void +socket::accept( + ConstBufferSequence const& buffers) +{ + static_assert(is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + error_code ec; + accept(buffers, ec); + detail::maybe_throw(ec, "accept"); +} + +template +template +void +socket::accept( + ConstBufferSequence const& buffers, error_code& ec) +{ + static_assert(beast::is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + stream_.buffer().commit(buffer_copy( + stream_.buffer().prepare( + buffer_size(buffers)), buffers)); + http::request m; + http::read(next_layer_, stream_.buffer(), m, ec); + if(ec) + return; + accept(m, ec); +} + +template +template +auto +socket::async_accept( + ConstBufferSequence const& bs, AcceptHandler&& handler) +{ + static_assert(beast::is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + beast::async_completion< + AcceptHandler, void(error_code) + > completion(handler); + accept_op{ + completion.handler, *this, bs}; + return completion.result.get(); +} + +template +template +void +socket::accept( + http::message const& request) +{ + error_code ec; + accept(request, ec); + detail::maybe_throw(ec, "accept"); +} + +template +template +void +socket::accept( + http::message const& req, + error_code& ec) +{ + auto resp = build_response(req); + http::write(stream_, resp, ec); + if(resp.status != 101) + { + ec = error::handshake_failed; + // VFALCO TODO Respect keep alive setting, perform + // teardown if Connection: close. + return; + } + role_ = role_type::server; +} + +template +template +auto +socket::async_accept( + http::message const& req, + AcceptHandler&& handler) +{ + beast::async_completion< + AcceptHandler, void(error_code) + > completion(handler); + response_op{ + completion.handler, *this, req, + boost_asio_handler_cont_helpers:: + is_continuation(completion.handler)}; + return completion.result.get(); +} + +template +void +socket::handshake(boost::string_ref const& host, + boost::string_ref const& resource, error_code& ec) +{ + std::string key; + http::write(stream_, + build_request(host, resource, key), ec); + if(ec) + return; + http::response resp; + http::read(next_layer_, stream_.buffer(), resp, ec); + if(ec) + return; + do_response(resp, key, ec); +} + +template +template +auto +socket::async_handshake(boost::string_ref const& host, + boost::string_ref const& resource, HandshakeHandler&& handler) +{ + beast::async_completion< + HandshakeHandler, void(error_code) + > completion(handler); + handshake_op{ + completion.handler, *this, host, resource}; + return completion.result.get(); +} + +template +void +socket::close( + close_reason const& cr, error_code& ec) +{ + assert(! wr_close_); + wr_close_ = true; + detail::frame_streambuf fb; + write_close(fb, cr); + boost::asio::write(stream_, fb.data(), ec); + error_ = ec != 0; +} + +template +template +auto +socket::async_close( + close_reason const& cr, CloseHandler&& handler) +{ + beast::async_completion< + CloseHandler, void(error_code) + > completion(handler); + close_op{ + completion.handler, *this, cr}; + return completion.result.get(); +} + +template +template +void +socket:: +read(opcode& op, Streambuf& streambuf, error_code& ec) +{ + frame_info fi; + for(;;) + { + read_frame(fi, streambuf, ec); + if(ec) + break; + op = fi.op; + if(fi.fin) + break; + } +} + +template +template +auto +socket:: +async_read(opcode& op, + Streambuf& streambuf, ReadHandler&& handler) +{ + static_assert(beast::is_Streambuf::value, + "Streambuf requirements not met"); + beast::async_completion< + ReadHandler, void(error_code) + > completion(handler); + read_op{ + completion.handler, *this, op, streambuf}; + return completion.result.get(); +} + +template +template +void +socket::read_frame(frame_info& fi, + Streambuf& streambuf, error_code& ec) +{ + close_code code{}; + for(;;) + { + if(rd_need_ == 0) + { + // read header + detail::frame_streambuf fb; + do_read_fh(fb, code, ec); + if((error_ = ec != 0)) + return; + if(code != close_code::none) + break; + if(detail::is_control(rd_fh_.op)) + { + // read control payload + if(rd_fh_.len > 0) + { + auto const mb = fb.prepare( + static_cast(rd_fh_.len)); + fb.commit(boost::asio::read(stream_, mb, ec)); + if((error_ = ec != 0)) + return; + if(rd_fh_.mask) + detail::mask_inplace(mb, rd_key_); + fb.commit(static_cast(rd_fh_.len)); + } + if(rd_fh_.op == opcode::ping) + { + ping_payload_type data; + detail::read(data, fb.data(), code); + if(code != close_code::none) + break; + fb.reset(); + write_ping( + fb, opcode::pong, data); + boost::asio::write(stream_, fb.data(), ec); + if((error_ = ec != 0)) + return; + continue; + } + else if(rd_fh_.op == opcode::pong) + { + ping_payload_type data; + detail::read(data, fb.data(), code); + if((error_ = ec != 0)) + break; + // VFALCO How to notify callers using + // the synchronous interface? + continue; + } + assert(rd_fh_.op == opcode::close); + { + detail::read(cr_, fb.data(), code); + if(code != close_code::none) + break; + if(! wr_close_) + { + auto cr = cr_; + if(cr.code == close_code::none) + cr.code = close_code::normal; + cr.reason = ""; + fb.reset(); + wr_close_ = true; + write_close(fb, cr); + boost::asio::write(stream_, fb.data(), ec); + if((error_ = ec != 0)) + return; + } + break; + } + } + if(rd_need_ == 0 && ! rd_fh_.fin) + { + // empty frame + continue; + } + } + // read payload + auto smb = streambuf.prepare( + detail::clamp(rd_need_)); + auto const bytes_transferred = + stream_.read_some(smb, ec); + if((error_ = ec != 0)) + return; + rd_need_ -= bytes_transferred; + auto const pb = prepare_buffers( + bytes_transferred, smb); + if(rd_fh_.mask) + detail::mask_inplace(pb, rd_key_); + if(rd_opcode_ == opcode::text) + { + if(! rd_utf8_check_.write(pb) || + (rd_need_ == 0 && rd_fh_.fin && + ! rd_utf8_check_.finish())) + { + code = close_code::bad_payload; + break; + } + } + streambuf.commit(bytes_transferred); + fi.op = rd_opcode_; + fi.fin = rd_fh_.fin && rd_need_ == 0; + return; + } + if(code != close_code::none) + { + // Fail the connection (per rfc6455) + if(! wr_close_) + { + wr_close_ = true; + detail::frame_streambuf fb; + write_close(fb, code); + boost::asio::write(stream_, fb.data(), ec); + if((error_ = ec != 0)) + return; + } + wsproto_helpers::call_teardown(next_layer_, ec); + if((error_ = ec != 0)) + return; + ec = error::failed; + error_ = true; + return; + } + if(! ec) + wsproto_helpers::call_teardown(next_layer_, ec); + if(! ec) + ec = error::closed; + error_ = ec != 0; +} + +template +template +auto +socket::async_read_frame(frame_info& fi, + Streambuf& streambuf, ReadHandler&& handler) +{ + static_assert(beast::is_Streambuf::value, + "Streambuf requirements not met"); + beast::async_completion< + ReadHandler, void(error_code)> completion(handler); + read_frame_op{ + completion.handler, *this, fi, streambuf}; + return completion.result.get(); +} + +template +template +void +socket::write( + ConstBufferSequence const& bs, error_code& ec) +{ + static_assert(beast::is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + using boost::asio::buffer_size; + consuming_buffers cb(bs); + auto remain = buffer_size(cb); + for(;;) + { + auto const n = + detail::clamp(remain, wr_frag_size_); + remain -= n; + auto const fin = remain <= 0; + write_frame(fin, prepare_buffers(n, cb), ec); + cb.consume(n); + if(ec) + return; + if(fin) + break; + } +} + +template +template +auto +socket::async_write( + ConstBufferSequence const& bs, WriteHandler&& handler) +{ + static_assert(beast::is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + beast::async_completion< + WriteHandler, void(error_code)> completion(handler); + write_op{ + completion.handler, *this, bs}; + return completion.result.get(); +} + +template +template +void +socket::write_frame(bool fin, + ConstBufferSequence const& bs, error_code& ec) +{ + static_assert(beast::is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + using boost::asio::mutable_buffers_1; + detail::frame_header fh; + fh.op = wr_cont_ ? opcode::cont : wr_opcode_; + wr_cont_ = ! fin; + fh.fin = fin; + fh.rsv1 = false; + fh.rsv2 = false; + fh.rsv3 = false; + fh.len = buffer_size(bs); + if((fh.mask = (role_ == role_type::client))) + fh.key = maskgen_(); + detail::fh_streambuf fh_buf; + detail::write(fh_buf, fh); + if(! fh.mask) + { + // send header and payload + boost::asio::write(stream_, + append_buffers(fh_buf.data(), bs), ec); + error_ = 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_); + 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}; + buffer_copy(mb, cb); + cb.consume(n); + remain -= n; + detail::mask_inplace(mb, key); + // send header and payload + boost::asio::write(stream_, + append_buffers(fh_buf.data(), mb), ec); + if(ec) + { + error_ = ec != 0; + return; + } + } + while(remain > 0) + { + auto const n = + detail::clamp(remain, tmp_size); + mutable_buffers_1 mb{tmp, n}; + buffer_copy(mb, cb); + cb.consume(n); + remain -= n; + detail::mask_inplace(mb, key); + // send payload + boost::asio::write(stream_, mb, ec); + if(ec) + { + error_ = ec != 0; + return; + } + } +} + +template +template +auto +socket::async_write_frame(bool fin, + ConstBufferSequence const& bs, WriteHandler&& handler) +{ + static_assert(beast::is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + beast::async_completion< + WriteHandler, void(error_code) + > completion(handler); + write_frame_op{completion.handler, + *this, fin, bs}; + return completion.result.get(); +} + +//------------------------------------------------------------------------------ + +template +http::request +socket::build_request(boost::string_ref const& host, + boost::string_ref const& resource, std::string& key) +{ + http::request req; + req.url = "/"; + req.version = 11; + req.method = http::method_t::http_get; + req.headers.insert("Host", host); + req.headers.insert("Connection", "upgrade"); + req.headers.insert("Upgrade", "websocket"); + key = detail::make_sec_ws_key(maskgen_); + req.headers.insert("Sec-WebSocket-Key", key); + req.headers.insert("Sec-WebSocket-Version", "13"); + (*d_)(req); + return req; +} + +template +template +http::response +socket::build_response( + http::message const& req) +{ + auto err = + [&](auto const& text) + { + http::response resp( + {400, http::reason_string(400), req.version}); + resp.body = text; + // VFALCO TODO respect keep-alive here + return resp; + }; + if(req.version < 11) + return err("HTTP version 1.1 required"); + if(req.method != http::method_t::http_get) + return err("Wrong method"); + if(! is_upgrade(req)) + return err("Expected Upgrade request"); + if(! req.headers.exists("Host")) + return err("Missing Host"); + if(! req.headers.exists("Sec-WebSocket-Key")) + return err("Missing Sec-WebSocket-Key"); + { + 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"); + } + if(! rfc2616::token_in_list( + req.headers["Upgrade"], "websocket")) + return err("Missing websocket Upgrade token"); + http::response resp( + {101, http::reason_string(101), req.version}); + resp.headers.insert("Upgrade", "websocket"); + resp.headers.insert("Connection", "upgrade"); + { + auto const key = + req.headers["Sec-WebSocket-Key"]; + resp.headers.insert("Sec-WebSocket-Key", key); + resp.headers.insert("Sec-WebSocket-Accept", + detail::make_sec_ws_accept(key)); + } + resp.headers.replace("Server", "Beast.WSProto"); + (*d_)(resp); + return resp; +} + +template +template +void +socket::do_response( + http::message const& resp, + boost::string_ref const& key, error_code& ec) +{ + // VFALCO Review these error codes + auto fail = [&]{ ec = error::response_failed; }; + if(resp.status != 101) + return fail(); + if(! is_upgrade(resp)) + return fail(); + if(! rfc2616::ci_equal( + resp.headers["Upgrade"], "websocket")) + return fail(); + if(! resp.headers.exists("Sec-WebSocket-Accept")) + return fail(); + if(resp.headers["Sec-WebSocket-Accept"] != + detail::make_sec_ws_accept(key)) + return fail(); + role_ = role_type::client; +} + +template +void +socket::do_read_fh( + detail::frame_streambuf& fb, + close_code& code, error_code& ec) +{ + fb.commit(boost::asio::read( + stream_, fb.prepare(2), ec)); + if(ec) + return; + auto const n = detail::read_fh1( + rd_fh_, fb, role_, code); + if(code != close_code::none) + return; + if(n > 0) + { + fb.commit(boost::asio::read( + stream_, fb.prepare(n), ec)); + if(ec) + return; + } + detail::read_fh2( + rd_fh_, fb, role_, code); + if(code != close_code::none) + return; + prepare_fh(code); +} + +} // wsproto +} // beast + +#endif diff --git a/src/beast/beast/wsproto/impl/ssl.ipp b/src/beast/beast/wsproto/impl/ssl.ipp new file mode 100644 index 000000000..ec465db63 --- /dev/null +++ b/src/beast/beast/wsproto/impl/ssl.ipp @@ -0,0 +1,170 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_SSL_IPP_INCLUDED +#define BEAST_WSPROTO_SSL_IPP_INCLUDED + +#include +#include + +namespace beast { +namespace wsproto { + +namespace detail { + +/* + +See +http://stackoverflow.com/questions/32046034/what-is-the-proper-way-to-securely-disconnect-an-asio-ssl-socket/32054476#32054476 + +Behavior of ssl::stream regarding close_ + + If the remote host calls async_shutdown then the + local host's async_read will complete with eof. + + If both hosts call async_shutdown then the calls + to async_shutdown will complete with eof. + +*/ +template +class teardown_ssl_op +{ + using stream_type = + boost::asio::ssl::stream; + + struct data + { + stream_type& stream; + Handler h; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, + stream_type& stream_) + : stream(stream_) + , h(std::forward(h_)) + , cont(boost_asio_handler_cont_helpers:: + is_continuation(h)) + { + } + }; + + std::shared_ptr d_; + +public: + template + explicit + teardown_ssl_op( + DeducedHandler&& h, + stream_type& stream) + : d_(std::make_shared( + std::forward(h), + stream)) + { + (*this)(error_code{}, false); + } + + void + operator()(error_code ec, bool again = true); + + friend + auto asio_handler_allocate(std::size_t size, + teardown_ssl_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + auto asio_handler_deallocate(void* p, + std::size_t size, teardown_ssl_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + auto asio_handler_is_continuation( + teardown_ssl_op* op) + { + return op->d_->cont; + } + + template + friend + auto asio_handler_invoke(Function&& f, + teardown_ssl_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +void +teardown_ssl_op:: +operator()(error_code ec, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + while(!ec && d.state != 99) + { + switch(d.state) + { + case 0: + d.state = 99; + d.stream.async_shutdown(*this); + return; + } + } + d.h(ec); +} + +} // detail + +//------------------------------------------------------------------------------ + +template +void +teardown( + boost::asio::ssl::stream& stream, + error_code& ec) +{ + stream.shutdown(ec); +} + +template +void +async_teardown( + boost::asio::ssl::stream& stream, + TeardownHandler&& handler) +{ + static_assert(beast::is_Handler< + TeardownHandler, void(error_code)>::value, + "TeardownHandler requirements not met"); + detail::teardown_ssl_op>{std::forward( + handler), stream}; +} + +} // wsproto +} // beast + +#endif diff --git a/src/beast/beast/wsproto/impl/teardown.ipp b/src/beast/beast/wsproto/impl/teardown.ipp new file mode 100644 index 000000000..92e50fd0f --- /dev/null +++ b/src/beast/beast/wsproto/impl/teardown.ipp @@ -0,0 +1,183 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_TEARDOWN_IPP_INCLUDED +#define BEAST_WSPROTO_TEARDOWN_IPP_INCLUDED + +#include +#include +#include + +namespace beast { +namespace wsproto { + +namespace detail { + +template +class teardown_tcp_op +{ + using socket_type = + boost::asio::ip::tcp::socket; + + struct data + { + socket_type& socket; + Handler h; + char buf[8192]; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, + socket_type& socket_) + : socket(socket_) + , h(std::forward(h_)) + , cont(boost_asio_handler_cont_helpers:: + is_continuation(h)) + { + } + }; + + std::shared_ptr d_; + +public: + template + teardown_tcp_op( + DeducedHandler&& h, + socket_type& socket) + : d_(std::make_shared( + std::forward(h), + socket)) + { + (*this)(error_code{}, 0, false); + } + + void + operator()( + error_code ec, std::size_t, bool again = true); + + friend + auto asio_handler_allocate(std::size_t size, + teardown_tcp_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + auto asio_handler_deallocate(void* p, + std::size_t size, teardown_tcp_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + auto asio_handler_is_continuation(teardown_tcp_op* op) + { + return op->d_->cont; + } + + template + friend + auto asio_handler_invoke(Function&& f, + teardown_tcp_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +void +teardown_tcp_op:: +operator()(error_code ec, std::size_t, bool again) +{ + using boost::asio::buffer; + auto& d = *d_; + d.cont = d.cont || again; + while(! ec) + { + switch(d.state) + { + case 0: + d.state = 1; + d.socket.shutdown( + boost::asio::ip::tcp::socket::shutdown_send, ec); + break; + + case 1: + d.socket.async_read_some( + buffer(d.buf), std::move(*this)); + return; + } + } + if(ec == boost::asio::error::eof) + { + d.socket.close(ec); + ec = error_code{}; + } + d.h(ec); +} + +} // detail + +//------------------------------------------------------------------------------ + +inline +void +teardown( + boost::asio::ip::tcp::socket& socket, + error_code& ec) +{ + using boost::asio::buffer; + socket.shutdown( + boost::asio::ip::tcp::socket::shutdown_send, ec); + while(! ec) + { + char buf[8192]; + auto const n = socket.read_some( + buffer(buf), ec); + if(! n) + break; + } + if(ec == boost::asio::error::eof) + ec = error_code{}; + socket.close(ec); +} + +template +inline +void +async_teardown( + boost::asio::ip::tcp::socket& socket, + TeardownHandler&& handler) +{ + static_assert(beast::is_Handler< + TeardownHandler, void(error_code)>::value, + "TeardownHandler requirements not met"); + detail::teardown_tcp_op>{std::forward< + TeardownHandler>(handler), socket}; +} + +} // wsproto +} // beast + +#endif diff --git a/src/beast/beast/wsproto/impl/write_frame_op.ipp b/src/beast/beast/wsproto/impl/write_frame_op.ipp new file mode 100644 index 000000000..a37415709 --- /dev/null +++ b/src/beast/beast/wsproto/impl/write_frame_op.ipp @@ -0,0 +1,277 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_WRITE_FRAME_OP_H_INCLUDED +#define BEAST_WSPROTO_WRITE_FRAME_OP_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { + +// write a frame +// +template +template +class socket::write_frame_op +{ + using alloc_type = + handler_alloc; + + struct data : op + { + socket& ws; + consuming_buffers cb; + Handler h; + detail::frame_header fh; + detail::fh_streambuf fh_buf; + detail::prepared_key_type key; + void* tmp; + std::size_t tmp_size; + std::uint64_t remain; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, socket& ws_, + bool fin, Buffers const& bs) + : ws(ws_) + , cb(bs) + , h(std::forward(h_)) + , cont(boost_asio_handler_cont_helpers:: + is_continuation(h)) + { + fh.op = ws.wr_cont_ ? + opcode::cont : ws.wr_opcode_; + ws.wr_cont_ = ! fin; + fh.fin = fin; + fh.rsv1 = 0; + fh.rsv2 = 0; + fh.rsv3 = 0; + fh.len = boost::asio::buffer_size(cb); + if((fh.mask = (ws.role_ == role_type::client))) + { + fh.key = ws.maskgen_(); + detail::prepare_key(key, fh.key); + tmp_size = detail::clamp( + fh.len, ws.wr_buf_size_); + tmp = boost_asio_handler_alloc_helpers:: + allocate(tmp_size, h); + remain = fh.len; + } + else + { + tmp = nullptr; + } + detail::write(fh_buf, fh); + } + + ~data() + { + if(tmp) + boost_asio_handler_alloc_helpers:: + deallocate(tmp, tmp_size, h); + } + }; + + std::shared_ptr d_; + +public: + write_frame_op(write_frame_op&&) = default; + write_frame_op(write_frame_op const&) = default; + + template + write_frame_op(DeducedHandler&& h, + socket& ws, Args&&... args) + : d_(std::make_shared( + std::forward(h), ws, + std::forward(args)...)) + { + (*this)(error_code{}, 0, false); + } + + void operator()() + { + auto& d = *d_; + d.cont = false; + (*this)(error_code{}, 0, false); + } + + void operator()(error_code ec, + std::size_t bytes_transferred, bool again = true); + + friend + auto asio_handler_allocate( + std::size_t size, write_frame_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + auto asio_handler_deallocate( + void* p, std::size_t size, write_frame_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + auto asio_handler_is_continuation(write_frame_op* op) + { + return op->d_->cont; + } + + template + friend + auto asio_handler_invoke(Function&& f, write_frame_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +template +void +socket:: +write_frame_op:: +operator()( + error_code ec, std::size_t bytes_transferred, 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) + { + switch(d.state) + { + case 0: + if(d.ws.wr_block_) + { + // suspend + d.state = 1; + d.ws.wr_op_.template emplace< + write_frame_op>(std::move(*this)); + return; + } + if(d.ws.error_) + { + // call handler + d.state = 99; + d.ws.get_io_service().post( + bind_handler(std::move(*this), + boost::asio::error::operation_aborted, 0)); + return; + } + assert(! d.ws.wr_close_); + d.state = 2; + break; + + // 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 + d.state = 99; + assert(! d.ws.wr_block_); + d.ws.wr_block_ = &d; + boost::asio::async_write(d.ws.stream_, + append_buffers(d.fh_buf.data(), d.cb), + std::move(*this)); + return; + } + auto const n = + detail::clamp(d.remain, d.tmp_size); + mutable_buffers_1 mb{d.tmp, n}; + buffer_copy(mb, d.cb); + d.cb.consume(n); + d.remain -= n; + detail::mask_inplace(mb, d.key); + // send header and payload + d.state = d.remain > 0 ? 3 : 99; + assert(! d.ws.wr_block_); + d.ws.wr_block_ = &d; + boost::asio::async_write(d.ws.stream_, + append_buffers(d.fh_buf.data(), + mb), std::move(*this)); + return; + } + + // sent masked payload + case 3: + { + auto const n = + detail::clamp(d.remain, d.tmp_size); + mutable_buffers_1 mb{d.tmp, + static_cast(n)}; + buffer_copy(mb, d.cb); + d.cb.consume(n); + d.remain -= n; + detail::mask_inplace(mb, d.key); + // send payload + if(d.remain == 0) + d.state = 99; + assert(! d.ws.wr_block_); + d.ws.wr_block_ = &d; + boost::asio::async_write( + d.ws.stream_, mb, std::move(*this)); + return; + } + } + } + if(ec) + d.ws.error_ = true; + if(d.ws.wr_block_ == &d) + d.ws.wr_block_ = nullptr; + if(d.tmp) + { + boost_asio_handler_alloc_helpers:: + deallocate(d.tmp, d.tmp_size, d.h); + d.tmp = nullptr; + } + d.h(ec); + d.ws.rd_op_.maybe_invoke(); +} + +} // wsproto +} // beast + +#endif diff --git a/src/beast/beast/wsproto/impl/write_op.ipp b/src/beast/beast/wsproto/impl/write_op.ipp new file mode 100644 index 000000000..24e48dd4d --- /dev/null +++ b/src/beast/beast/wsproto/impl/write_op.ipp @@ -0,0 +1,150 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_WRITE_OP_H_INCLUDED +#define BEAST_WSPROTO_WRITE_OP_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { + +// write a message +// +template +template +class socket::write_op +{ + using alloc_type = + handler_alloc; + + struct data : op + { + socket& ws; + consuming_buffers cb; + Handler h; + std::size_t remain; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, + socket& ws_, Buffers const& bs) + : ws(ws_) + , cb(bs) + , h(std::forward(h_)) + , remain(boost::asio::buffer_size(cb)) + , cont(boost_asio_handler_cont_helpers:: + is_continuation(h)) + { + } + }; + + std::shared_ptr d_; + +public: + write_op(write_op&&) = default; + write_op(write_op const&) = default; + + template + explicit + write_op(DeducedHandler&& h, + socket& ws, Args&&... args) + : d_(std::allocate_shared(alloc_type{h}, + std::forward(h), ws, + std::forward(args)...)) + { + (*this)(error_code{}, false); + } + + void operator()(error_code ec, bool again = true); + + friend + auto asio_handler_allocate( + std::size_t size, write_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + auto asio_handler_deallocate( + void* p, std::size_t size, write_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + auto asio_handler_is_continuation(write_op* op) + { + return op->d_->cont; + } + + template + friend + auto asio_handler_invoke(Function&& f, write_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +template +void +socket:: +write_op:: +operator()(error_code ec, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + while(! ec && d.state != 99) + { + switch(d.state) + { + case 0: + { + auto const n = std::min( + d.remain, d.ws.wr_frag_size_); + d.remain -= n; + auto const fin = d.remain <= 0; + if(fin) + d.state = 99; + d.ws.async_write_frame(fin, + prepare_buffers(n, d.cb), std::move(*this)); + d.cb.consume(n); + return; + } + } + } + d.h(ec); +} + +} // wsproto +} // beast + +#endif diff --git a/src/beast/beast/wsproto/option.h b/src/beast/beast/wsproto/option.h new file mode 100644 index 000000000..f2fbb7b1f --- /dev/null +++ b/src/beast/beast/wsproto/option.h @@ -0,0 +1,305 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_OPTION_H_INCLUDED +#define BEAST_WSPROTO_OPTION_H_INCLUDED + +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { + +/** Automatic fragmentation size option. + + Sets the maximum size of fragments generated when sending + messages on a WebSocket socket. + + When the automatic fragmentation size is non-zero, messages + exceeding the size will be split into multiple frames no + larger than the size. This setting does not affect frames + send explicitly using `write_frame` or `async_write_frame`. + + The default setting is to fragment messages into 16KB frames. + + @note Objects of this type are passed to socket::set_option. + + @par Example + Setting the automatic fragmentation size option: + @code + ... + wsproto::socket stream(ios); + stream.set_option(auto_fragment_size{8192}); + @endcode +*/ +#if GENERATING_DOCS +using auto_fragment_size = implementation_defined; +#else +struct auto_fragment_size +{ + std::size_t value; + + auto_fragment_size(std::size_t n) + : value(n) + { + } +}; +#endif + +/** HTTP decorator option. + + The decorator transforms the HTTP requests and responses used + when requesting or responding to the WebSocket Upgrade. This may + be used to set or change header fields. For example to set the + Server or User-Agent fields. The default setting applies no + transformation to the HTTP message. + + For synchronous operations, the implementation will call the + decorator before the function call to perform the operation + returns. + + For asynchronous operations, the implementation guarantees that + calls to the decorator will be made from the same implicit or + explicit strand used to call the asynchronous initiation + function. + + The default setting is no decorator. + + @note Objects of this type are passed to socket::set_option. + + @par Example + Setting the decorator. + @code + struct identity + { + template + void + operator()(http::message& m) + { + if(isRequest) + m.headers.replace("User-Agent", "MyClient"); + else + m.headers.replace("Server", "MyServer"); + } + }; + ... + websocket::stream ws(ios); + ws.set_option(decorate(identity{})); + @endcode +*/ +#if GENERATING_DOCS +using decorate = implementation_defined; +#else +template +inline +auto +decorate(Decorator&& d) +{ + return std::make_unique>>( + std::forward(d)); +} +#endif + +/** Keep-alive option. + + Determines if the connection is closed after a failed upgrade + request. + + This setting only affects the behavior of HTTP requests that + implicitly or explicitly ask for a keepalive. For HTTP requests + that indicate the connection should be closed, the connection is + closed as per rfc2616. + + The default setting is to close connections after a failed + upgrade request. + + @note Objects of this type are passed to socket::set_option. + + @par Example + Setting the keep alive option. + @code + ... + websocket::stream ws(ios); + ws.set_option(keep_alive{8192}); + @endcode +*/ +#if GENERATING_DOCS +using keep_alive = implementation_defined; +#else +struct keep_alive +{ + bool value; + + keep_alive(bool v) + : value(v) + { + } +}; +#endif + +/** Message type option. + + This controls the opcode set for outgoing messages. Valid + choices are opcode::binary or opcode::text. The setting is + only applied at the start when a caller begins a new message. + Changing the opcode after a message is started will only + take effect after the current message being sent is complete. + + The default setting is opcode::text. + + @note Objects of this type are passed to socket::set_option. + + @par Example + Setting the message type to binary. + @code + ... + websocket::stream ws(ios); + ws.set_option(message_type{opcode::binary}); + @endcode +*/ +#if GENERATING_DOCS +using message_type = implementation_defined; +#else +struct message_type +{ + opcode value; + + explicit + message_type(opcode op) + { + if(op != opcode::binary && op != opcode::text) + throw std::domain_error("bad opcode"); + value = op; + } +}; +#endif + +/** Read buffer size option. + + Sets the number of bytes allocated to the socket's read buffer. + If this is zero, then reads are not buffered. Setting this + higher can improve performance when expecting to receive + many small frames. + + The default is no buffering. + + @note Objects of this type are passed to socket::set_option. + + @par Example + Setting the read buffer size. + @code + ... + websocket::stream ws(ios); + ws.set_option(read_buffer_size{16 * 1024}); + @endcode +*/ +#if GENERATING_DOCS +using read_buffer_size = implementation_defined; +#else +struct read_buffer_size +{ + std::size_t value; + + explicit + read_buffer_size(std::size_t n) + : value(n) + { + } +}; +#endif + +/** Maximum incoming message size option. + + Sets the largest permissible incoming message size. Message + 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. + + @note Objects of this type are passed to socket::set_option. + + @par Example + Setting the maximum read message size. + @code + ... + websocket::stream ws(ios); + ws.set_option(read_message_max{65536}); + @endcode +*/ +#if GENERATING_DOCS +using read_message_max = implementation_defined; +#else +struct read_message_max +{ + std::size_t value; + + explicit + read_message_max(std::size_t n) + : value(n) + { + } +}; +#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 socket::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 + +} // wsproto +} // beast + +#endif diff --git a/src/beast/beast/wsproto/rfc6455.h b/src/beast/beast/wsproto/rfc6455.h new file mode 100644 index 000000000..84700f426 --- /dev/null +++ b/src/beast/beast/wsproto/rfc6455.h @@ -0,0 +1,163 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_RFC6455_H_INCLUDED +#define BEAST_WSPROTO_RFC6455_H_INCLUDED + +#include +#include +#include +#include + +namespace beast { +namespace wsproto { + +/** WebSocket frame header opcodes. */ +enum class opcode : std::uint8_t +{ + cont = 0, + text = 1, + binary = 2, + rsv3 = 3, + rsv4 = 4, + rsv5 = 5, + rsv6 = 6, + rsv7 = 7, + close = 8, + ping = 9, + pong = 10, + crsvb = 11, + crsvc = 12, + crsvd = 13, + crsve = 14, + crsvf = 15 +}; + +/** Close status codes. + + These codes accompany close frames. + + @see RFC 6455 7.4.1 Defined Status Codes + https://tools.ietf.org/html/rfc6455#section-7.4.1 +*/ +enum class close_code : std::uint16_t +{ + // used internally to mean "no error" + none = 0, + + normal = 1000, + going_away = 1001, + protocol_error = 1002, + unknown_data = 1003, + bad_payload = 1007, + policy_error = 1008, + too_big = 1009, + needs_extension = 1010, + internal_error = 1011, + + service_restart = 1012, + try_again_later = 1013, + + reserved1 = 1004, + no_status = 1005, // illegal on wire + abnormal = 1006, // illegal on wire + reserved2 = 1015, + + last = 5000 // satisfy warnings +}; + +#if ! GENERATING_DOCS + +using reason_string_type = + static_string<123, char>; + +/// Payload type for pings and pongs +using ping_payload_type = + static_string<125, char>; + +#endif + +/** Description of the close reason. + + This object stores the close code (if any) and the optional + utf-8 encoded implementation defined reason string. +*/ +struct close_reason +{ + /// The close code. + close_code code = close_code::none; + + /// The optional utf8-encoded reason string. + reason_string_type reason; + + /** Default constructor. + + The code will be none. Default constructed objects + will explicitly convert to bool as `false`. + */ + close_reason() = default; + + /// Construct from a code. + close_reason(close_code code_) + : code(code_) + { + } + + /// Construct from a reason. code is close_code::normal. + template + close_reason(CharT const* reason_) + : code(close_code::normal) + , reason(reason_) + { + } + + /// Construct from a code and reason. + template + close_reason(close_code code_, + CharT const* reason_) + : code(code_) + , reason(reason_) + { + } + + /// Returns `true` if a code was specified + operator bool() const + { + return code != close_code::none; + } +}; + +#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 + +} // wsproto +} // beast + +#endif diff --git a/src/beast/beast/wsproto/socket.h b/src/beast/beast/wsproto/socket.h new file mode 100644 index 000000000..d6f83ea7f --- /dev/null +++ b/src/beast/beast/wsproto/socket.h @@ -0,0 +1,1190 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_SOCKET_H_INCLUDED +#define BEAST_WSPROTO_SOCKET_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { + +/** Information about a WebSocket frame. + + This information is provided to callers during frame + read operations. +*/ +struct frame_info +{ + /// Indicates the type of message (binary or text). + opcode op; + + /// `true` if this is the last frame in the current message. + bool fin; +}; + +//-------------------------------------------------------------------- + +/** Provides message-oriented functionality using WebSocket. + + The socket class template provides asynchronous and blocking + message-oriented functionality necessary for clients and servers + to utilize the WebSocket protocol. + + @par Thread Safety + @e Distinct @e objects: Safe.@n + @e Shared @e objects: Unsafe. The application must ensure that + all asynchronous operations are performed within the same + implicit or explicit strand. + + @par Example + + To use the WebSocket socket template with an + ip::tcp::socket, you would write: + + @code + wsproto::socket ws(io_service); + @endcode + Alternatively, you can write: + @code + ip::tcp::socket sock(io_service); + wsproto::socket ws(sock); + @endcode + + @note A socket object must not be destroyed while there are + pending asynchronous operations associated with it. + + @par Concepts + AsyncReadStream, AsyncWriteStream, + Decorator, Streambuf, SyncReadStream, SyncWriteStream. +*/ +template +class socket : public detail::socket_base +{ + friend class ws_test; + + Stream next_layer_; + streambuf_readstream stream_; + +public: + /// The type of the next layer. + using next_layer_type = + std::remove_reference_t; + + /// The type of the lowest layer. + using lowest_layer_type = + typename next_layer_type::lowest_layer_type; + + /// The type of endpoint of the lowest layer. + using endpoint_type = + typename lowest_layer_type::endpoint_type; + + /// The protocol of the next layer. + using protocol_type = + typename lowest_layer_type::protocol_type; + + /// The type of resolver of the next layer. + using resolver_type = + typename protocol_type::resolver; + + /// Move constructor. + socket(socket&&) = default; + + /** Move assignment. + + Undefined behavior if operations are active or pending. + */ + socket& operator=(socket&&) = default; + + /** Construct a websocket. + + This constructor creates a websocket and initialises the + underlying stream object. + + @throws Any exceptions thrown by the Stream constructor. + + @param args The arguments to be passed to initialise the + underlying stream object. The arguments are forwarded + to the stream's constructor. + */ + template + explicit + socket(Args&&... args); + + /** Destructor. + + A stream object must not be destroyed while there are + pending asynchronous operations associated with it. + */ + ~socket() = default; + + /** Set options on the socket. + + The application must ensure that calls to set options + are performed within the same implicit or explicit strand. + + @param args One or more socket options to set. + */ +#if GENERATING_DOCS + template + void + set_option(Args&&... args) +#else + template + void + set_option(A1&& a1, A2&& a2, An&&... an) +#endif + { + set_option(std::forward(a1)); + set_option(std::forward(a2), + std::forward(an)...); + } + + void + set_option(auto_fragment_size const& o) + { + if(o.value <= 0) + wr_frag_size_ = + std::numeric_limits::max(); + else + wr_frag_size_ = o.value; + } + + void + set_option(detail::decorator_type o) + { + d_ = std::move(o); + } + + void + set_option(keep_alive const& o) + { + keep_alive_ = o.value; + } + + void + set_option(message_type const& o) + { + wr_opcode_ = o.value; + } + + void + set_option(read_buffer_size const& o) + { + stream_.reserve(o.value); + } + + void + set_option(read_message_max const& o) + { + rd_msg_max_ = o.value; + } + + void + set_option(write_buffer_size const& o) + { + wr_buf_size_ = std::max(o.value, 1024); + stream_.reserve(o.value); + } + + /** Get the io_service associated with the socket. + + This function may be used to obtain the io_service object + that the socket uses to dispatch handlers for asynchronous + operations. + + @return A reference to the io_service object that the socket + will use to dispatch handlers. Ownership is not transferred + to the caller. + */ + boost::asio::io_service& + get_io_service() + { + return next_layer_.lowest_layer().get_io_service(); + } + + /** Get a reference to the next layer. + + This function returns a reference to the next layer + in a stack of stream layers. + + @return A reference to the next layer in the stack of + stream layers. Ownership is not transferred to the caller. + */ + next_layer_type& + next_layer() + { + return next_layer_; + } + + /** Get a reference to the next layer. + + This function returns a reference to the next layer in a + stack of stream layers. + + @return A reference to the next layer in the stack of + stream layers. Ownership is not transferred to the caller. + */ + next_layer_type const& + next_layer() const + { + return next_layer_; + } + + /** Get a reference to the lowest layer. + + This function returns a reference to the lowest layer + in a stack of stream layers. + + @return A reference to the lowest layer in the stack of + stream layers. Ownership is not transferred to the caller. + */ + lowest_layer_type& + lowest_layer() + { + return next_layer_.lowest_layer(); + } + + /** Get a reference to the lowest layer. + + This function returns a reference to the lowest layer + in a stack of stream layers. + + @return A reference to the lowest layer in the stack of + stream layers. Ownership is not transferred to the caller. + */ + lowest_layer_type const& + lowest_layer() const + { + return next_layer_.lowest_layer(); + } + + /** Returns the close reason received from the peer. + + This is only valid after a read completes with error::closed. + */ + close_reason const& + reason() const + { + return cr_; + } + + /** Read and respond to a WebSocket HTTP Upgrade request. + + This function is used to synchronously read a HTTP WebSocket + Upgrade request and send the HTTP response. + + If the contents of the request are valid, the HTTP response + indicates a successful upgrade and the socket is then ready + to send and receive WebSocket protocol frames and messages. + + If the WebSocket HTTP Upgrade request cannot be satisfied, + a HTTP response is sent indicating the reason and status + code (typically 400, "Bad Request"), and an appropriate + exception will be thrown. + + The call blocks until one of the following conditions is true: + + @li An error occurs on the socket. + + @li The entire HTTP response has been sent. + + @throws boost::system::system_error Thrown on failure. + */ + void + accept() + { + error_code ec; + accept(boost::asio::null_buffers{}, ec); + detail::maybe_throw(ec, "accept"); + } + + /** Read and respond to a WebSocket HTTP Upgrade request. + + This function is used to synchronously read a HTTP WebSocket + Upgrade request and send the HTTP response. + + If the contents of the request are valid, the HTTP response + indicates a successful upgrade and the socket is then ready + to send and receive WebSocket protocol frames and messages. + + If the WebSocket HTTP Upgrade request cannot be satisfied, + a HTTP response is sent indicating the reason and status + code (typically 400, "Bad Request"). + + The call blocks until one of the following conditions is true: + + @li An error occurs on the socket. + + @li The entire HTTP response has been sent. + + @param ec Set to indicate what error occurred, if any. + */ + void + accept(error_code& ec); + + /** Start reading and responding to a WebSocket HTTP Upgrade request. + + This function is used to asynchronously read a HTTP WebSocket + Upgrade request and send the HTTP response. The function call + always returns immediately. + + If the contents of the request are valid, the HTTP response + indicates a successful upgrade and the socket is then ready + to send and receive WebSocket protocol frames and messages. + + If the WebSocket HTTP Upgrade request cannot be satisfied, + a HTTP response is sent indicating the reason and status + code (typically 400, "Bad Request"). + + @param handler The handler to be called when the request completes. + Copies will be made of the handler as required. The equivalent + 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 + auto + async_accept(AcceptHandler&& handler); + + /** Read and respond to a WebSocket HTTP Upgrade request. + + This function is used to synchronously read a HTTP WebSocket + Upgrade request and send the HTTP response. + + If the contents of the request are valid, the HTTP response + indicates a successful upgrade and the socket is then ready + to send and receive WebSocket protocol frames and messages. + + If the WebSocket HTTP Upgrade request cannot be satisfied, + a HTTP response is sent indicating the reason and status + code (typically 400, "Bad Request"). + + The call blocks until one of the following conditions is true: + + @li An error occurs on the socket. + + @li The entire HTTP response has been sent. + + @param buffers Caller provide data that has already been + received on the socket. This may be used for implementations + allowing multiple protocols on the same socket. The + buffered data will first be applied to the handshake, and + then to received WebSocket frames. The implementation will + copy the caller provided data before the function returns. + + @throws boost::system::system_error Thrown on failure. + */ + template + void + accept(ConstBufferSequence const& buffers); + + /** Read and respond to a WebSocket HTTP Upgrade request. + + This function is used to synchronously read a HTTP WebSocket + Upgrade request and send the HTTP response. + + If the contents of the request are valid, the HTTP response + indicates a successful upgrade and the socket is then ready + to send and receive WebSocket protocol frames and messages. + + If the WebSocket HTTP Upgrade request cannot be satisfied, + a HTTP response is sent indicating the reason and status + code (typically 400, "Bad Request"). + + The call blocks until one of the following conditions is true: + + @li An error occurs on the socket. + + @li The entire HTTP response has been sent. + + @param buffers Caller provide data that has already been + received on the socket. This may be used for implementations + allowing multiple protocols on the same socket. The + buffered data will first be applied to the handshake, and + then to received WebSocket frames. The implementation will + copy the caller provided data before the function returns. + + @param ec Set to indicate what error occurred, if any. + */ + template + void + accept(ConstBufferSequence const& buffers, error_code& ec); + + /** Start reading and responding to a WebSocket HTTP Upgrade request. + + This function is used to asynchronously read a HTTP WebSocket + Upgrade request and send the HTTP response. The function call + always returns immediately. + + If the contents of the request are valid, the HTTP response + indicates a successful upgrade and the socket is then ready + to send and receive WebSocket protocol frames and messages. + + If the WebSocket HTTP Upgrade request cannot be satisfied, + a HTTP response is sent indicating the reason and status + code (typically 400, "Bad Request"). + + @param buffers Caller provide data that has already been + received on the socket. This may be used for implementations + allowing multiple protocols on the same socket. The + buffered data will first be applied to the handshake, and + then to received WebSocket frames. The implementation will + copy the caller provided data before the function returns. + + @param handler The handler to be called when the request completes. + Copies will be made of the handler as required. The equivalent + 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 + auto + async_accept(ConstBufferSequence const& buffers, + AcceptHandler&& handler); + + /** Respond to a WebSocket HTTP Upgrade request + + This function is used to synchronously send the HTTP response + to a HTTP WebSocket Upgrade request. + + If the contents of the request are valid, the HTTP response + indicates a successful upgrade and the socket is then ready + to send and receive WebSocket protocol frames and messages. + + If the WebSocket HTTP Upgrade request cannot be satisfied, + a HTTP response is sent indicating the reason and status + code (typically 400, "Bad Request"). + + The call blocks until one of the following conditions is true: + + @li An error occurs on the socket. + + @li The entire HTTP response has been sent. + + @param request An object containing the HTTP Upgrade request. The + implementation will make copies as necessary before this + function call returns. + + @throws boost::system::system_error Thrown on failure. + */ + // VFALCO TODO This should also take a streambuf with any leftover bytes. + template + void + accept(http::message const& request); + + /** Respond to a WebSocket HTTP Upgrade request + + This function is used to synchronously send the HTTP response + to a HTTP WebSocket Upgrade request. + + If the contents of the request are valid, the HTTP response + indicates a successful upgrade and the socket is then ready + to send and receive WebSocket protocol frames and messages. + + If the WebSocket HTTP Upgrade request cannot be satisfied, + a HTTP response is sent indicating the reason and status + code (typically 400, "Bad Request"). + + The call blocks until one of the following conditions is true: + + @li An error occurs on the socket. + + @li The entire HTTP response has been sent. + + @param request An object containing the HTTP Upgrade request. The + implementation will make copies as necessary before this + function call returns. + + @param ec Set to indicate what error occurred, if any. + */ + template + void + accept(http::message const& request, error_code& ec); + + /** Start reading and responding to a WebSocket HTTP Upgrade request. + + This function is used to asynchronously read a HTTP WebSocket + Upgrade request and send the HTTP response. The function call + always returns immediately. + + If the contents of the request are valid, the HTTP response + indicates a successful upgrade and the socket is then ready + to send and receive WebSocket protocol frames and messages. + + If the WebSocket HTTP Upgrade request cannot be satisfied, + a HTTP response is sent indicating the reason and status + code (typically 400, "Bad Request"). + + @param request An object containing the HTTP Upgrade request. The + implementation will make copies as necessary before this + function call returns. + + @param handler The handler to be called when the request completes. + Copies will be made of the handler as required. The equivalent + 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 + auto + async_accept(http::message const& request, AcceptHandler&& handler); + + /** Send a WebSocket Upgrade request. + + This function is used to synchronously send the WebSocket + upgrade HTTP request. The call blocks until one of the + following conditions is true: + + @li An error occurs on the socket + + @li A complete HTTP response with the result of the upgrade + request is received. + + @throws boost::system::system_error Thrown on failure. + + @param host The name of the remote host, required by + the HTTP protocol. + + @param resource The requesting URI, which may not be empty, + required by the HTTP protocol. + + @par Example + @code + wsproto::socket ws(io_service); + ... + try + { + ws.upgrade("localhost", "/"); + } + catch(...) + { + // An error occurred. + } + @endcode + */ + void + handshake(boost::string_ref const& host, + boost::string_ref const& resource) + { + error_code ec; + handshake(host, resource, ec); + detail::maybe_throw(ec, "upgrade"); + } + + /** Send a WebSocket Upgrade request. + + This function is used to synchronously send the WebSocket + upgrade HTTP request. The call blocks until one of the + following conditions is true: + + @li An error occurs on the socket + + @li A complete HTTP response with the result of the upgrade + request is received. + + @param host The name of the remote host, required by + the HTTP protocol. + + @param resource The requesting URI, which may not be empty, + required by the HTTP protocol. + + @param ec Set to indicate what error occurred, if any. + + @par Example + @code + wsproto::socket ws(io_service); + ... + error_code ec; + ws.upgrade(host, resource, ec); + if(ec) + { + // An error occurred. + } + @endcode + */ + void + handshake(boost::string_ref const& host, + boost::string_ref const& resource, error_code& ec); + + /** Asynchronously send a WebSocket Upgrade request. + + This function is used to asynchronously send the WebSocket + upgrade HTTP request. This function call always returns + immediately. + + @param host The name of the remote host, required by + the HTTP protocol. Copies may be made as needed. + + @param resource The requesting URI, which may not be empty, + required by the HTTP protocol. Copies may be made as + needed. + + @param h The handler to be called when the request completes. + Copies will be made of the handler as required. The equivalent + 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 + auto + async_handshake(boost::string_ref const& host, + boost::string_ref const& resource, HandshakeHandler&& h); + + /** Perform a WebSocket close. + + This function initiates the WebSocket close procedure. + + If the close reason specifies a close code other than + close_code::none, the close frame is sent with the close code + and optional reason string. Otherwise, the close frame + is sent with no payload. + + Callers should not attempt to write WebSocket data after + initiating the close. Instead, callers should continue + reading until an error occurs. A read returning error::closed + indicates a successful connection closure. + + @param cr The reason for the close. + */ + void + close(close_reason const& cr) + { + error_code ec; + close(cr, ec); + detail::maybe_throw(ec, "close"); + } + + /** Perform a WebSocket close. + + This function initiates the WebSocket close procedure. + + If the close reason specifies a close code other than + close_code::none, the close frame is sent with the close code + and optional reason string. Otherwise, the close frame + is sent with no payload. + + Callers should not attempt to write WebSocket data after + initiating the close. Instead, callers should continue + reading until an error occurs. A read returning error::closed + indicates a successful connection closure. + + @param cr The reason for the close. + + @param ec Set to indicate what error occurred, if any. + */ + void + close(close_reason const& cr, error_code& ec); + + /** Start an asychronous WebSocket close operation. + + This function initiates the WebSocket close procedure. + + If the close reason specifies a close code other than + close_code::none, the close frame is sent with the close code + and optional reason string. Otherwise, the close frame + is sent with no payload. + + Callers should not attempt to write WebSocket data after + initiating the close. Instead, callers should continue + reading until an error occurs. A read returning error::closed + indicates a successful connection closure. + + @param cr The reason for the close. + + @param handler The handler to be called when the close 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 + auto + async_close(close_reason const& cr, CloseHandler&& handler); + + /** Read a message. + + This function is used to read a message from the + websocket. The function call will block until the message + has been read successfully, or until an error occurs. + + On success op is set to reflect the message type, binary + or text. + + @param op A value to receive the message type. + This object must remain valid until the handler is called. + + @param streambuf A stream buffer to hold the message data. + This object must remain valid until the handler is called. + + @throws boost::system::system_error Thrown on failure. + */ + template + void + read(opcode& op, Streambuf& streambuf) + { + error_code ec; + read(op, streambuf, ec); + detail::maybe_throw(ec, "read"); + } + + /** Read a message. + + This function is used to read a message from the + websocket. The function call will block until the message + has been read successfully, or until an error occurs. + + On success op is set to reflect the message type, binary + or text. + + @param op A value to receive the message type. + This object must remain valid until the handler is called. + + @param streambuf A stream buffer to hold the message data. + This object must remain valid until the handler is called. + + @param ec Set to indicate what error occurred, if any. + */ + template + void + read(opcode& op, + Streambuf& streambuf, error_code& ec); + + /** Start reading a message asynchronously. + + This function is used to asychronously read a message from + the websocket. The function call always returns immediately. + + Upon a successful completion, op is set to either binary or + text depending on the message type, and the input area of the + streambuf will hold all the message payload bytes (which may + be zero in length). + + @param op A value to receive the message type. + This object must remain valid until the handler is called. + + @param streambuf A stream buffer to hold the message data. + This object must remain valid until the handler is called. + + @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 + auto + #endif + async_read(opcode& op, + Streambuf& streambuf, ReadHandler&& handler); + + /** Read a message frame. + + This function is used to read a single message frame from + the websocket. The function call will block until one message + frame has been read successfully, or an error occurs. + + On success, fi is filled out to reflect the message payload + contents. op is set to binary or text, and the fin flag + indicates if all the message data has been read in. To read the + entire message, callers should repeat the read_frame operation + until fi.fin is true. A message with no payload will have + fi.fin == true, and zero bytes placed into the stream buffer. + + If a control frame is received while attempting to read a + message frame, the control frame is handled automatically. + If a ping control frame is received, a pong is sent immediately. + If a close control frame is received, a close is sent in + response and the read operation will return with `error::closed`. + + @param fi An object to store metadata about the message. + + @param streambuf A stream buffer to hold the message data. + + @throws boost::system::system_error Thrown on failure. + */ + template + void + read_frame(frame_info& fi, Streambuf& streambuf) + { + error_code ec; + read_frame(fi, streambuf, ec); + detail::maybe_throw(ec, "read_some"); + } + + /** Read a message frame. + + This function is used to read a single message frame from + the websocket. The function call will block until one message + frame has been read successfully, or an error occurs. + + On success, fi is filled out to reflect the message payload + contents. op is set to binary or text, and the fin flag + indicates if all the message data has been read in. To read the + entire message, callers should repeat the read_frame operation + until fi.fin is true. A message with no payload will have + fi.fin == true, and zero bytes placed into the stream buffer. + + If a control frame is received while attempting to read a + message frame, the control frame is handled automatically. + If a ping control frame is received, a pong is sent immediately. + If a close control frame is received, a close is sent in + response and the read operation will return with `error::closed`. + + @param fi An object to store metadata about the message. + + @param streambuf A stream buffer to hold the message data. + + @param ec Set to indicate what error occurred, if any. + */ + template + void + read_frame(frame_info& fi, Streambuf& streambuf, error_code& ec); + + /** Start reading a message frame asynchronously. + + This function is used to asychronously read a single message + frame from the websocket. The function call always returns + immediately. + + Upon a successful completion, fi is filled out to reflect the + message payload contents. op is set to binary or text, and the + fin flag indicates if all the message data has been read in. + To read the entire message, callers should repeat the + read_frame operation until fi.fin is true. A message with no + payload will have fi.fin == true, and zero bytes placed into + the stream buffer. + + If a control frame is received while attempting to read a + message frame, the control frame is handled automatically. + If a ping control frame is received, a pong is sent immediately. + If a close control frame is received, a close is sent in + response and the read operation will return with `error::closed`. + + @param fi An object to store metadata about the message. + This object must remain valid until the handler is called. + + @param streambuf A stream buffer to hold the message data after + any masking or decompression has been applied. This object must + remain valid until the handler is called. + + @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 + auto + async_read_frame(frame_info& fi, + Streambuf& streambuf, ReadHandler&& handler); + + /** Send a message. + + This function is used to write a message to the websocket. + The call blocks until one of the following conditions is met: + + @li The entire message is sent. + + @li An error occurs. + + The message opcode will be set to text or binary as + per the current setting of the `message_type` option. + If automatic fragmenting is enabled, the message will be + split into one or more frames as necessary. + + @param buffers The buffers containing the entire message + payload. The implementation will make copies of this object + as needed, but ownership of the underlying memory is not + transferred. The caller is responsible for ensuring that + the memory locations pointed to by buffers remains valid + until the completion handler is called. + + @throws boost::system::system_error Thrown on failure. + + @note This function always sends an entire message. To + send a message in fragments, use `socket::write`. + */ + template + void + write(ConstBufferSequence const& buffers) + { + error_code ec; + write(buffers, ec); + detail::maybe_throw(ec, "write"); + } + + /** Send a message. + + This function is used to write a message to the websocket. + The call blocks until one of the following conditions is met: + + @li The entire message is sent. + + @li An error occurs. + + The message opcode will be set to text or binary as + per the current setting of the `message_type` option. + If automatic fragmenting is enabled, the message will be + split into one or more frames as necessary. + + @param ec Set to indicate what error occurred, if any. + + @param buffers The buffers containing the entire message + payload. The implementation will make copies of this object + as needed, but ownership of the underlying memory is not + transferred. The caller is responsible for ensuring that + the memory locations pointed to by buffers remains valid + until the completion handler is called. + + @note This function always sends an entire message. To + send a message in fragments, use `socket::write`. + */ + template + void + write(ConstBufferSequence const& buffers, error_code& ec); + + /** Start writing a complete message asynchronously. + + This function is used to asychronously write a message to + the websocket. The function call always returns immediately. + + The message opcode will be set to text or binary as + per the current setting of the `message_type` option. + If automatic fragmenting is enabled, the message will be + split into one or more frames as necessary. + + @param buffers The buffers containing the entire message + payload. The implementation will make copies of this object + as needed, but ownership of the underlying memory is not + transferred. The caller is responsible for ensuring that + the memory locations pointed to by buffers remains valid + until the completion handler is called. + + @param handler The handler to be called when the write 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 + auto + #endif + async_write(ConstBufferSequence const& buffers, + WriteHandler&& handler); + + /** Send a frame. + + This function is used to write a frame to a stream. The + call will block until one of the following conditions is true: + + @li All of the data in the supplied buffers has been written. + + @li An error occurs. + + This operation is implemented in terms of one or more calls + to the stream's write_some function. The actual payload sent + may be transformed as per the WebSocket protocol settings. + + If this is the beginning of a new message, the message opcode + will be set to text or binary as per the current setting of + the `message_type` option. + + @throws boost::system::system_error Thrown on failure. + + @param fin `true` if this is the last frame in the message. + + @param buffers One or more buffers containing the frame's + payload data. + */ + template + void + write_frame(bool fin, ConstBufferSequence const& buffers) + { + error_code ec; + write_frame(fin, buffers, ec); + detail::maybe_throw(ec, "write"); + } + + /** Send a frame. + + This function is used to write a frame to a stream. The + call will block until one of the following conditions is true: + + @li All of the data in the supplied buffers has been written. + + @li An error occurs. + + This operation is implemented in terms of one or more calls + to the stream's write_some function. The actual payload sent + may be transformed as per the WebSocket protocol settings. + + If this is the beginning of a new message, the message opcode + will be set to text or binary as per the current setting of + the `message_type` option. + + @param fin `true` if this is the last frame in the message. + + @param buffers One or more buffers containing the frame's + payload data. + + @param ec Set to indicate what error occurred, if any. + */ + template + void + write_frame(bool fin, + ConstBufferSequence const& buffers, error_code& ec); + + /** Start sending a frame asynchronously. + + This function is used to asynchronously write a WebSocket + frame on the stream. This function call always returns + immediately. + + If this is the beginning of a new message, the message opcode + will be set to text or binary as per the current setting of + the `message_type` option. + + This operation is implemented in terms of one or more calls + to the stream's async_write_some function. The actual payload + sent may be transformed as per the WebSocket protocol settings. + + @param fin A bool indicating whether or not the frame is the + last frame in the corresponding WebSockets message. + + @param buffers A object meeting the requirements of + ConstBufferSequence which holds the payload data before any + masking or compression. Although the buffers object may be copied + as necessary, ownership of the underlying buffers is retained by + the caller, which must guarantee that they remain valid until + the handler is called. + + @param handler The handler to be called when the write completes. + Copies will be made of the handler as required. The equivalent + function signature of the handler must be: + @code void handler( + boost::system::error_code const& error // result of operation + ); @endcode + */ + template + auto + async_write_frame(bool fin, + ConstBufferSequence const& buffers, WriteHandler&& handler); + +private: + template class accept_op; + template class close_op; + template class handshake_op; + template class response_op; + template class read_op; + template class read_frame_op; + template class write_op; + template class write_frame_op; + + http::request + build_request(boost::string_ref const& host, + boost::string_ref const& resource, + std::string& key); + + template + http::response + build_response(http::message const& req); + + template + void + do_response(http::message const& resp, + boost::string_ref const& key, error_code& ec); + + void + do_read_fh(detail::frame_streambuf& fb, + close_code& code, error_code& ec); +}; + +} // wsproto +} // beast + +#include + +#endif diff --git a/src/beast/beast/wsproto/src/test/async_echo_peer.h b/src/beast/beast/wsproto/src/test/async_echo_peer.h new file mode 100644 index 000000000..b81fc8234 --- /dev/null +++ b/src/beast/beast/wsproto/src/test/async_echo_peer.h @@ -0,0 +1,285 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_ASYNC_ECHO_PEER_H_INCLUDED +#define BEAST_WSPROTO_ASYNC_ECHO_PEER_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { +namespace test { + +// Asynchronous WebSocket echo client/server +// +class async_echo_peer +{ +public: + static std::size_t constexpr autobahnCycles = 520; + + using error_code = boost::system::error_code; + using endpoint_type = boost::asio::ip::tcp::endpoint; + using address_type = boost::asio::ip::address; + using socket_type = boost::asio::ip::tcp::socket; + +private: + unit_test::suite& suite_; + boost::asio::io_service ios_; + socket_type sock_; + boost::asio::ip::tcp::acceptor acceptor_; + std::vector thread_; + std::size_t n_ = 0; + +public: + async_echo_peer(bool server, + endpoint_type const& ep, unit_test::suite& suite) + : suite_(suite) + , sock_(ios_) + , acceptor_(ios_) + { + if(server) + { + error_code ec; + acceptor_.open(ep.protocol(), ec); + maybe_throw(ec, "open"); + acceptor_.bind(ep, ec); + maybe_throw(ec, "bind"); + acceptor_.listen( + boost::asio::socket_base::max_connections, ec); + maybe_throw(ec, "listen"); + acceptor_.async_accept(sock_, + std::bind(&async_echo_peer::on_accept, this, + beast::asio::placeholders::error)); + } + else + { + Peer{std::move(sock_), ep, suite_}; + } + auto const n = 1; + thread_.reserve(n); + for(int i = 0; i < n; ++i) + thread_.emplace_back(suite_, + [&] { ios_.run(); }); + } + + ~async_echo_peer() + { + error_code ec; + ios_.dispatch( + [&]{ acceptor_.close(ec); }); + for(auto& t : thread_) + t.join(); + } + +private: + class Peer + { + struct data + { + int state = 0; + unit_test::suite& suite; + boost::optional ep; + wsproto::socket ws; + wsproto::opcode op; + beast::streambuf sb; + int id; + + data(socket_type&& sock_, + unit_test::suite& suite_) + : suite(suite_) + , ws(std::move(sock_)) + , id([] + { + static int n = 0; + return ++n; + }()) + { + } + + data(socket_type&& sock_, endpoint_type const& ep_, + unit_test::suite& suite_) + : suite(suite_) + , ep(ep_) + , ws(std::move(sock_)) + , id([] + { + static int n = 0; + return ++n; + }()) + { + } + }; + + std::shared_ptr d_; + + public: + Peer(Peer&&) = default; + Peer(Peer const&) = default; + Peer& operator=(Peer&&) = delete; + Peer& operator=(Peer const&) = delete; + + struct identity + { + template + void + operator()(http::message& req) + { + req.headers.replace("User-Agent", "async_echo_client"); + } + + template + void + operator()(http::message& resp) + { + resp.headers.replace("Server", "async_echo_server"); + } + }; + + template + explicit + Peer(socket_type&& sock, Args&&... args) + : d_(std::make_shared( + std::forward(sock), + std::forward(args)...)) + { + auto& d = *d_; + d.ws.set_option(decorate(identity{})); + //d.ws.set_option(auto_fragment_size(0)); + d.ws.set_option(read_message_max(64 * 1024 * 1024)); + run(); + } + + void run() + { + auto& d = *d_; + if(! d.ep) + { + d.ws.async_accept(std::move(*this)); + } + else + { + d.state = 4; + d.ws.next_layer().async_connect( + *d.ep, std::move(*this)); + } + } + + void operator()(error_code ec) + { + auto& d = *d_; + switch(d_->state) + { + // did accept + case 0: + if(ec) + return fail(ec, "async_accept"); + + // start + case 1: + if(ec) + return fail(ec, "async_handshake"); + d.sb.consume(d.sb.size()); + // read message + d.state = 2; + d.ws.async_read(d.op, d.sb, std::move(*this)); + return; + + // got message + case 2: + if(ec == wsproto::error::closed) + return; + if(ec) + return fail(ec, "async_read"); + // write message + d.state = 1; + d.ws.set_option(wsproto::message_type(d.op)); + d.ws.async_write(d.sb.data(), std::move(*this)); + return; + + // connected + case 4: + if(ec) + return fail(ec, "async_connect"); + d.state = 1; + d.ws.async_handshake( + d.ep->address().to_string() + ":" + + std::to_string(d.ep->port()), + "/", std::move(*this)); + return; + } + } + + private: + void + fail(error_code ec, std::string what) + { + if(ec != wsproto::error::closed) + { + d_->suite.log << + "#" << std::to_string(d_->id) << " " << + what << ": " << ec.message(); + } + } + }; + + void + fail(error_code ec, std::string what) + { + suite_.log << + what << ": " << ec.message(); + } + + void + maybe_throw(error_code ec, std::string what) + { + if(ec) + { + fail(ec, what); + throw ec; + } + } + + void + on_accept(error_code ec) + { + if(! acceptor_.is_open()) + return; + maybe_throw(ec, "accept"); + socket_type sock(std::move(sock_)); + if(n_ < autobahnCycles) + acceptor_.async_accept(sock_, + std::bind(&async_echo_peer::on_accept, this, + beast::asio::placeholders::error)); + Peer{std::move(sock), suite_}; + } +}; + +} // test +} // wsproto +} // beast + +#endif diff --git a/src/beast/beast/wsproto/src/test/beast_wsproto_ws_echo_test.cpp b/src/beast/beast/wsproto/src/test/beast_wsproto_ws_echo_test.cpp new file mode 100644 index 000000000..188a8200e --- /dev/null +++ b/src/beast/beast/wsproto/src/test/beast_wsproto_ws_echo_test.cpp @@ -0,0 +1,94 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { +namespace test { + +class ws_echo_test : public unit_test::suite +{ +public: + using endpoint_type = boost::asio::ip::tcp::endpoint; + using address_type = boost::asio::ip::address; + + void + run() override + { + async_echo_peer s1(true, endpoint_type{ + address_type::from_string("127.0.0.1"), + 6000 }, *this); + + sync_echo_peer s2(true, endpoint_type{ + address_type::from_string("127.0.0.1"), + 6001 }, *this); + + boost::asio::io_service ios; + boost::asio::signal_set signals( + ios, SIGINT, SIGTERM); + std::mutex m; + bool stop = false; + std::condition_variable cv; + signals.async_wait( + [&](boost::system::error_code const& ec, + int signal_number) + { + std::lock_guard lock(m); + stop = true; + cv.notify_one(); + }); + std::unique_lock lock(m); + cv.wait(lock, [&]{ return stop; }); + } +}; + +class ws_client_test : public unit_test::suite +{ +public: + using endpoint_type = boost::asio::ip::tcp::endpoint; + using address_type = boost::asio::ip::address; + + void + run() override + { + pass(); + { + async_echo_peer s1(false, endpoint_type{ + address_type::from_string("127.0.0.1"), + 9001 }, *this); + } + { + sync_echo_peer s2(false, endpoint_type{ + address_type::from_string("127.0.0.1"), + 9001 }, *this); + } + } +}; + +BEAST_DEFINE_TESTSUITE_MANUAL(ws_echo, asio, beast); +BEAST_DEFINE_TESTSUITE_MANUAL(ws_client, asio, beast); + +} // test +} // wsproto +} // beast diff --git a/src/beast/beast/wsproto/src/test/beast_wsproto_ws_test.cpp b/src/beast/beast/wsproto/src/test/beast_wsproto_ws_test.cpp new file mode 100644 index 000000000..05176ce8f --- /dev/null +++ b/src/beast/beast/wsproto/src/test/beast_wsproto_ws_test.cpp @@ -0,0 +1,362 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { + +class ws_test : public unit_test::suite +{ +public: + using error_code = boost::system::error_code; + using endpoint_type = boost::asio::ip::tcp::endpoint; + using address_type = boost::asio::ip::address; + using socket_type = boost::asio::ip::tcp::socket; + using yield_context = boost::asio::yield_context; + + endpoint_type ep_; + + //-------------------------------------------------------------------------- + + // opcodes for creating the test plans + + // concurrent read and write + struct case_1{}; + + // write a bad frame and shut down + struct case_2{}; + + //-------------------------------------------------------------------------- + + class coro_peer + { + error_code ec_; + boost::asio::io_service ios_; + boost::asio::ip::tcp::acceptor acceptor_; + socket_type sock_; + socket ws_; + opcode op_; + beast::streambuf rb_; + beast::streambuf wb_; + yield_context* yield_; + int state_ = 0; + //unit_test::suite& test_; + + public: + coro_peer(coro_peer&&) = default; + coro_peer(coro_peer const&) = delete; + coro_peer& operator=(coro_peer&&) = delete; + coro_peer& operator=(coro_peer const&) = delete; + + template + coro_peer(bool server, endpoint_type ep, + unit_test::suite& test, Ops const&... ops) + : acceptor_(ios_) + , sock_(ios_) + , ws_(sock_) + //, test_(test) + { + if(server) + { + acceptor_.open(ep.protocol()); + acceptor_.bind(ep); + acceptor_.listen( + boost::asio::socket_base::max_connections); + boost::asio::spawn(ios_, + [=](auto yield) + { + yield_ = &yield; + state_ = 10; + acceptor_.async_accept(sock_, (*yield_)[ec_]); + if(ec_) + return this->fail("accept"); + state_ = 20; + ws_.async_accept((*yield_)[ec_]); + if(ec_) + return this->fail("ws.accept"); + this->invoke(ops...); + state_ = -1; + }); + } + else + { + boost::asio::spawn(ios_, + [=](auto yield) + { + yield_ = &yield; + state_ = 30; + sock_.async_connect(ep, (*yield_)[ec_]); + if(ec_) + return this->fail("connect"); + state_ = 40; + ws_.async_handshake(ep.address().to_string() + + std::to_string(ep.port()), "/", (*yield_)[ec_]); + if(ec_) + return this->fail("handshake"); + this->invoke(ops...); + state_ = -1; + }); + } + } + + ~coro_peer() + { + } + + int + state() const + { + return state_; + } + + void + run_one() + { + ios_.run_one(); + } + + void + step_to(int to = 0) + { + while(state_ != to) + ios_.run_one(); + } + + private: + template + void fail(String const& s) + { + } + + void invoke_1(case_1) + { + ws_.async_read(op_, rb_, + [&](auto ec) + { + if(ec) + return this->fail(ec); + rb_.consume(rb_.size()); + }); + state_ = 100; + ws_.async_write( + boost::asio::null_buffers{}, (*yield_)[ec_]); + if(ec_) + return fail("write"); + } + + void invoke_1(case_2) + { + detail::frame_header fh; + fh.op = opcode::rsv5; // bad opcode + fh.fin = true; + fh.mask = true; + fh.rsv1 = false; + fh.rsv2 = false; + fh.rsv3 = false; + fh.len = 0; + fh.key = 0; + detail::write(wb_, fh); + state_ = 200; + boost::asio::async_write( + ws_.next_layer(), wb_.data(), + (*yield_)[ec_]); + if(ec_) + return fail("write"); + ws_.next_layer().shutdown( + socket_type::shutdown_both, ec_); + if(ec_) + return fail("shutdown"); + } + + inline + void + invoke() + { + } + + template + inline + void + invoke(Op op, Ops const&... ops) + { + invoke_1(op); + invoke(ops...); + } + }; + + void + testInvokable() + { + endpoint_type const ep{ + address_type::from_string( + "127.0.0.1"), 6000}; + coro_peer server(true, ep, *this, case_1{}); + coro_peer client(false, ep, *this, case_2{}); + server.step_to(10); // async_accept + client.step_to(30); // async_connect + server.step_to(20); // async_accept(ws) + client.step_to(40); // async_handshake + server.step_to(100); // case_1 + client.step_to(200); // case_2 + client.step_to(-1); + server.step_to(-1); + } + + //-------------------------------------------------------------------------- + + bool + maybe_fail(error_code const& ec, std::string const& what) + { + return expect(! ec, what + ": " + ec.message()); + } + + void + maybe_throw(error_code ec, std::string what) + { + if(ec) + { + maybe_fail(ec, what); + throw ec; + } + } + + template + static + std::string + buffers_to_string(Buffers const& bs) + { + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + std::string s; + s.reserve(buffer_size(bs)); + for(auto const& b : bs) + s.append(buffer_cast(b), + buffer_size(b)); + for(auto i = s.size(); i-- > 0;) + if(s[i] == '\r') + s.replace(i, 1, "\\r"); + else if(s[i] == '\n') + s.replace(i, 1, "\\n\n"); + return s; + } + + int + makeRequest(endpoint_type ep, std::string const& s) + { + using boost::asio::buffer; + boost::asio::io_service ios; + boost::asio::ip::tcp::socket sock(ios); + sock.connect(ep); + write(sock, append_buffers( + buffer(s), buffer("\r\n"))); + + using namespace http; + response resp; + streambuf sb; + read(sock, sb, resp); + return resp.status; + } + + void + expectStatus(endpoint_type ep, + int status, std::string const& s) + { + expect(makeRequest(ep, s) == status); + } + + void + testHandshake(endpoint_type ep) + { + expectStatus(ep, 400, "GET / HTTP/1.0\r\n"); + } + + void + syncEchoClient(endpoint_type ep) + { + using boost::asio::buffer; + error_code ec; + boost::asio::io_service ios; + wsproto::socket ws(ios); + ws.next_layer().connect(ep, ec); + if(! maybe_fail(ec, "connect")) + return; + ws.handshake(ep.address().to_string(), "/", ec); + if(! maybe_fail(ec, "upgrade")) + return; + std::string s(65535, '*'); + ws.write_frame(true, buffer(s), ec); + if(! maybe_fail(ec, "write")) + return; + boost::asio::streambuf sb; + wsproto::opcode op; + ws.read(op, sb, ec); + if(! maybe_fail(ec, "read")) + return; + if(! ec) + expect(op == wsproto::opcode::text); + expect(buffers_to_string(sb.data()) == s); + sb.consume(sb.size()); + ws.close({}, ec); + if(! maybe_fail(ec, "close")) + return; + while(! ec) + { + ws.read(op, sb, ec); + if(! ec) + sb.consume(sb.size()); + } + if(ec != error::closed) + maybe_fail(ec, "teardown"); + } + + void + run() override + { + //testInvokable(); + + { + endpoint_type ep{ + address_type::from_string("127.0.0.1"), 6000}; + testcase("Echo Server"); + test::sync_echo_peer s(true, ep, *this); + //testHandshake(ep); + syncEchoClient(ep); + } + + { + endpoint_type ep{ + address_type::from_string("127.0.0.1"), 6001}; + testcase("Async Echo Server"); + test::async_echo_peer s(true, ep, *this); + //testHandshake(ep); + syncEchoClient(ep); + } + } +}; + +BEAST_DEFINE_TESTSUITE(ws,asio,beast); + +} // wsproto +} // beast diff --git a/src/beast/beast/wsproto/src/test/sync_echo_peer.h b/src/beast/beast/wsproto/src/test/sync_echo_peer.h new file mode 100644 index 000000000..1e8e4377c --- /dev/null +++ b/src/beast/beast/wsproto/src/test/sync_echo_peer.h @@ -0,0 +1,182 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_SYNC_ECHO_PEER_H_INCLUDED +#define BEAST_WSPROTO_SYNC_ECHO_PEER_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { +namespace test { + +// Synchronous WebSocket echo client/server +// +class sync_echo_peer +{ +public: + static std::size_t constexpr autobahnCycles = 520; + + using error_code = boost::system::error_code; + using endpoint_type = boost::asio::ip::tcp::endpoint; + using address_type = boost::asio::ip::address; + using socket_type = boost::asio::ip::tcp::socket; + +private: + unit_test::suite& suite_; + boost::asio::io_service ios_; + socket_type sock_; + boost::asio::ip::tcp::acceptor acceptor_; + unit_test::thread thread_; + std::size_t n_ = 0; + +public: + sync_echo_peer(bool server, + endpoint_type ep, unit_test::suite& suite) + : suite_(suite) + , sock_(ios_) + , acceptor_(ios_) + { + error_code ec; + acceptor_.open(ep.protocol(), ec); + maybe_throw(ec, "open"); + acceptor_.bind(ep, ec); + maybe_throw(ec, "bind"); + acceptor_.listen( + boost::asio::socket_base::max_connections, ec); + maybe_throw(ec, "listen"); + acceptor_.async_accept(sock_, + std::bind(&sync_echo_peer::on_accept, this, + beast::asio::placeholders::error)); + thread_ = unit_test::thread(suite_, + [&] + { + ios_.run(); + }); + } + + ~sync_echo_peer() + { + error_code ec; + ios_.dispatch( + [&]{ acceptor_.close(ec); }); + thread_.join(); + } + +private: + void + fail(error_code ec, std::string what) + { + suite_.log << + what << ": " << ec.message(); + } + + void + maybe_throw(error_code ec, std::string what) + { + if(ec) + { + fail(ec, what); + throw ec; + } + } + + void + on_accept(error_code ec) + { + if(ec == boost::asio::error::operation_aborted) + return; + maybe_throw(ec, "accept"); + ++n_; + std::thread{ + [ + this, + sock = std::move(sock_), + work = boost::asio::io_service::work{ios_} + ]() mutable + { + do_peer(std::move(sock)); + }}.detach(); + if(n_ < autobahnCycles) + acceptor_.async_accept(sock_, + std::bind(&sync_echo_peer::on_accept, this, + beast::asio::placeholders::error)); + } + + struct identity + { + template + void + operator()(http::message& req) + { + req.headers.replace("User-Agent", "sync_echo_client"); + } + + template + void + operator()(http::message& resp) + { + resp.headers.replace("Server", "sync_echo_server"); + } + }; + + void + do_peer(socket_type&& sock) + { + wsproto::socket ws(std::move(sock)); + ws.set_option(decorate(identity{})); + ws.set_option(read_message_max(64 * 1024 * 1024)); + //ws.set_option(auto_fragment_size(0)); + error_code ec; + ws.accept(ec); + if(ec) + { + fail(ec, "accept"); + return; + } + for(;;) + { + wsproto::opcode op; + beast::streambuf sb; + ws.read(op, sb, ec); + if(ec) + break; + ws.set_option(wsproto::message_type(op)); + ws.write(sb.data(), ec); + if(ec) + break; + } + if(ec && ec != wsproto::error::closed) + { + fail(ec, "read"); + } + } +}; + +} // test +} // wsproto +} // beast + +#endif diff --git a/src/beast/beast/wsproto/ssl.h b/src/beast/beast/wsproto/ssl.h new file mode 100644 index 000000000..e99faec6b --- /dev/null +++ b/src/beast/beast/wsproto/ssl.h @@ -0,0 +1,86 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_SSL_H_INCLUDED +#define BEAST_WSPROTO_SSL_H_INCLUDED + +#include +#include +#include +#include + +namespace beast { +namespace wsproto { + +/** Tear down a `boost::asio::ssl::stream`. + + This tears down a connection. The implementation will call + the overload of this function based on the `Stream` parameter + used to consruct the socket. When `Stream` is a user defined + type, and not a `boost::asio::ip::tcp::socket` or any + `boost::asio::ssl::stream`, callers are responsible for + providing a suitable overload of this function. + + @param socket The stream to tear down. + + @param ec Set to the error if any occurred. +*/ +template +void +teardown( + boost::asio::ssl::stream& stream, + error_code& ec); + +/** Start tearing down a `boost::asio::ssl::stream`. + + This begins tearing down a connection asynchronously. + The implementation will call the overload of this function + based on the `Stream` parameter used to consruct the socket. + When `Stream` is a user defined type, and not a + `boost::asio::ip::tcp::socket` or any `boost::asio::ssl::stream`, + callers are responsible for providing a suitable overload + of this function. + + @param stream The stream to tear down. + + @param handler The handler to be called when the request completes. + Copies will be made of the handler as required. The equivalent + 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 +inline +void +async_teardown( + boost::asio::ssl::stream& stream, + TeardownHandler&& handler); + +} // wsproto +} // beast + +#include + +#endif diff --git a/src/beast/beast/wsproto/static_string.h b/src/beast/beast/wsproto/static_string.h new file mode 100644 index 000000000..45eb08589 --- /dev/null +++ b/src/beast/beast/wsproto/static_string.h @@ -0,0 +1,337 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_STATIC_STRING_H_INCLUDED +#define BEAST_WSPROTO_STATIC_STRING_H_INCLUDED + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { + +/** A string with a fixed-size storage area. + + `static_string` objects behave like `std::string` except that + the storage is not dynamically allocated but rather fixed in + size. + + These strings offer performance advantages when a protocol + imposes a natural small upper limit on the size of a value. +*/ +template> +class static_string +{ + std::size_t n_; + std::array s_; + +public: + using traits_type = Traits; + using value_type = typename Traits::char_type; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + using const_pointer = value_type const*; + using const_reference = value_type const&; + using iterator = value_type*; + using const_iterator = value_type const*; + using reverse_iterator = + std::reverse_iterator; + using const_reverse_iterator = + std::reverse_iterator; + + static_string() + { + resize(0); + } + + static_string(static_string const& s) + : n_(s.n_) + { + std::copy(&s.s_[0], &s.s_[0]+s.n_+1, &s_[0]); + } + + static_string& + operator=(static_string const& s) + { + n_ = s.n_; + std::copy(&s.s_[0], &s.s_[0]+s.n_+1, &s_[0]); + return *this; + } + + static_string(CharT const* s) + { + assign(s); + } + + static_string& operator=(CharT const* s) + { + assign(s); + return *this; + } + + reference + at(size_type pos) + { + if(pos >= n_) + throw std::out_of_range("static_string::at"); + return s_[pos]; + } + + const_reference + at(size_type pos) const + { + if(pos >= n_) + throw std::out_of_range("static_string::at"); + return s_[pos]; + } + + reference + operator[](size_type pos); + + const_reference + operator[](size_type pos) const; + + CharT& + front() + { + return s_[0]; + } + + CharT const& + front() const + { + return s_[0]; + } + + CharT& + back() + { + return s_[n_-1]; + } + + CharT const& + back() const + { + return s_[n_-1]; + } + + CharT* + data() + { + return &s_[0]; + } + + CharT const* + data() const + { + return &s_[0]; + } + + CharT const* + c_str() const + { + s_[n_] = 0; + return &s_[0]; + } + + iterator + begin() + { + return &s_[0]; + } + + const_iterator + begin() const + { + return &s_[0]; + } + + const_iterator + cbegin() const + { + return &s_[0]; + } + + iterator + end() + { + return &s_[n_]; + } + + const_iterator + end() const + { + return &s_[n_]; + } + + const_iterator + cend() const + { + return &s_[n_]; + } + + reverse_iterator + rbegin() + { + return reverse_iterator{end()}; + } + + const_reverse_iterator + rbegin() const + { + return reverse_iterator{end()}; + } + + const_reverse_iterator + crbegin() const + { + return reverse_iterator{cend()}; + } + + reverse_iterator + rend() + { + return reverse_iterator{begin()}; + } + + const_reverse_iterator + rend() const + { + return reverse_iterator{begin()}; + } + + const_reverse_iterator + crend() const + { + return reverse_iterator{cbegin()}; + } + + bool + empty() const + { + return n_ == 0; + } + + size_type + size() const + { + return n_; + } + + size_type constexpr + max_size() const + { + return N; + } + + size_type + capacity() const + { + return N; + } + + void + shrink_to_fit() + { + // no-op + } + + void + clear() + { + resize(0); + } + + // Does not perform value-initialization + void + resize(std::size_t n); + + std::basic_string + to_string() const + { + return std::basic_string< + CharT, Traits>{&s_[0], n_}; + } + +private: + void + assign(CharT const* s); +}; + +template +auto +static_string:: +operator[](size_type pos) -> + reference +{ + static CharT null{0}; + if(pos == n_) + return null; + return s_[pos]; +} + +template +auto +static_string:: +operator[](size_type pos) const -> + const_reference +{ + static CharT constexpr null{0}; + if(pos == n_) + return null; + return s_[pos]; +} + +template +void +static_string:: +resize(std::size_t n) +{ + if(n > N) + throw std::length_error( + "static_string overflow"); + n_ = n; + s_[n_] = 0; +} + +template +void +static_string:: +assign(CharT const* s) +{ + size_type n = 0; + for(auto p = s; *p; ++p) + ++n; + if(n > N) + throw std::out_of_range( + "too large"); + std::copy(s, s+n, s_.begin()); +} + +} // wsproto +} // beast + +#endif diff --git a/src/beast/beast/wsproto/teardown.h b/src/beast/beast/wsproto/teardown.h new file mode 100644 index 000000000..f184f9e63 --- /dev/null +++ b/src/beast/beast/wsproto/teardown.h @@ -0,0 +1,161 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_TEARDOWN_H_INCLUDED +#define BEAST_WSPROTO_TEARDOWN_H_INCLUDED + +#include +#include +#include + +namespace beast { +namespace wsproto { + +/** Tear down a connection. + + This tears down a connection. The implementation will call + the overload of this function based on the `Stream` parameter + used to consruct the socket. When `Stream` is a user defined + type, and not a `boost::asio::ip::tcp::socket` or any + `boost::asio::ssl::stream`, callers are responsible for + providing a suitable overload of this function. + + @param socket The socket to tear down. + + @param ec Set to the error if any occurred. +*/ +template +void +teardown(Socket& socket, error_code& ec) = delete; + +/** Start tearing down a connection. + + This begins tearing down a connection asynchronously. + The implementation will call the overload of this function + based on the `Stream` parameter used to consruct the socket. + When `Stream` is a user defined type, and not a + `boost::asio::ip::tcp::socket` or any `boost::asio::ssl::stream`, + callers are responsible for providing a suitable overload + of this function. + + @param socket The socket to tear down. + + @param handler The handler to be called when the request completes. + Copies will be made of the handler as required. The equivalent + 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 +void +async_teardown(AsyncSocket& socket, TeardownHandler&& handler) = delete; + +//------------------------------------------------------------------------------ + +/** Tear down a `boost::asio::ip::tcp::socket`. + + This tears down a connection. The implementation will call + the overload of this function based on the `Stream` parameter + used to consruct the socket. When `Stream` is a user defined + type, and not a `boost::asio::ip::tcp::socket` or any + `boost::asio::ssl::stream`, callers are responsible for + providing a suitable overload of this function. + + @param socket The socket to tear down. + + @param ec Set to the error if any occurred. +*/ +void +teardown( + boost::asio::ip::tcp::socket& socket, + error_code& ec); + +/** Start tearing down a `boost::asio::ip::tcp::socket`. + + This begins tearing down a connection asynchronously. + The implementation will call the overload of this function + based on the `Stream` parameter used to consruct the socket. + When `Stream` is a user defined type, and not a + `boost::asio::ip::tcp::socket` or any `boost::asio::ssl::stream`, + callers are responsible for providing a suitable overload + of this function. + + @param socket The socket to tear down. + + @param handler The handler to be called when the request completes. + Copies will be made of the handler as required. The equivalent + 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 +void +async_teardown( + boost::asio::ip::tcp::socket& socket, + TeardownHandler&& handler); + +} // wsproto + +//------------------------------------------------------------------------------ + +namespace wsproto_helpers { + +// Calls to teardown and async_teardown must be made from +// a namespace that does not contain any overloads of these +// functions. The wsproto_helpers namespace is defined here +// for that purpose. + +template +inline +void +call_teardown(Socket& socket, wsproto::error_code& ec) +{ + using wsproto::teardown; + teardown(socket, ec); +} + +template +inline +void +call_async_teardown(Socket& socket, TeardownHandler&& handler) +{ + using wsproto::async_teardown; + async_teardown(socket, + std::forward(handler)); +} + +} // wsproto_helpers + +} // beast + +#include + +#endif diff --git a/src/beast/doc/.gitignore b/src/beast/doc/.gitignore new file mode 100644 index 000000000..b775bdcfd --- /dev/null +++ b/src/beast/doc/.gitignore @@ -0,0 +1,4 @@ +html +temp +reference.qbk +out.txt diff --git a/src/beast/doc/Jamfile b/src/beast/doc/Jamfile new file mode 100644 index 000000000..48e72a0ab --- /dev/null +++ b/src/beast/doc/Jamfile @@ -0,0 +1,83 @@ +# +# 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) +# + +import os ; + +local broot = [ os.environ BOOST_ROOT ] ; + +project beast/doc ; + +using boostbook ; +using quickbook ; +using doxygen ; + +xml beast_boostbook : beast.qbk ; + +path-constant out : . ; + +install stylesheets + : + $(broot)/doc/src/boostbook.css + : + $(out)/html + ; + +explicit stylesheets ; + +install images + : + [ glob $(broot)/doc/src/images/*.png ] + images/beast.png + images/body.png + images/message.png + : + $(out)/html/images + ; + +explicit images ; + +install callouts + : + [ glob $(broot)/doc/src/images/callouts/*.png ] + : + $(out)/html/images/callouts + ; + +explicit callout ; + +boostbook doc + : + beast_boostbook + : + chapter.autolabel=0 + boost.image.src=images/beast.png + boost.image.alt="Beast Logo" + boost.image.w=1007 + boost.image.h=107 + boost.root=$(broot) + chapter.autolabel=0 + chunk.first.sections=1 # Chunk the first top-level section? + chunk.section.depth=8 # Depth to which sections should be chunked + generate.section.toc.level=1 # Control depth of TOC generation in sections + toc.max.depth=2 # How many levels should be created for each TOC? + toc.section.depth=2 # How deep should recursive sections appear in the TOC? + : + temp + stylesheets + images + ; + +#explicit doc ; +# nav.layout=none +# html:location=../bin/doc/html +# generate.toc="chapter nop section nop" +# root.filename=index +# output-root="../bin/html" + + +#[include reference.qbk] +#[xinclude index.xml] diff --git a/src/beast/doc/beast.dox b/src/beast/doc/beast.dox new file mode 100644 index 000000000..9529ddde6 --- /dev/null +++ b/src/beast/doc/beast.dox @@ -0,0 +1,363 @@ +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- +DOXYFILE_ENCODING = UTF-8 +PROJECT_NAME = "Beast" +PROJECT_NUMBER = +PROJECT_BRIEF = C++ Library +PROJECT_LOGO = images/beast.png +OUTPUT_DIRECTORY = +CREATE_SUBDIRS = NO +ALLOW_UNICODE_NAMES = NO +OUTPUT_LANGUAGE = English +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = +ALWAYS_DETAILED_SEC = NO +INLINE_INHERITED_MEMB = YES +FULL_PATH_NAMES = NO +STRIP_FROM_PATH = +STRIP_FROM_INC_PATH = +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = YES +QT_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = NO +INHERIT_DOCS = YES +SEPARATE_MEMBER_PAGES = NO +TAB_SIZE = 4 +ALIASES = +TCL_SUBST = +OPTIMIZE_OUTPUT_FOR_C = NO +OPTIMIZE_OUTPUT_JAVA = NO +OPTIMIZE_FOR_FORTRAN = NO +OPTIMIZE_OUTPUT_VHDL = NO +EXTENSION_MAPPING = +MARKDOWN_SUPPORT = YES +AUTOLINK_SUPPORT = YES +BUILTIN_STL_SUPPORT = NO +CPP_CLI_SUPPORT = NO +SIP_SUPPORT = NO +IDL_PROPERTY_SUPPORT = YES +DISTRIBUTE_GROUP_DOC = NO +GROUP_NESTED_COMPOUNDS = NO +SUBGROUPING = YES +INLINE_GROUPED_CLASSES = NO +INLINE_SIMPLE_STRUCTS = NO +TYPEDEF_HIDES_STRUCT = NO +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- +EXTRACT_ALL = YES +EXTRACT_PRIVATE = YES +EXTRACT_PACKAGE = YES +EXTRACT_STATIC = YES +EXTRACT_LOCAL_CLASSES = NO +EXTRACT_LOCAL_METHODS = NO +EXTRACT_ANON_NSPACES = NO +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = YES +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = NO +CASE_SENSE_NAMES = YES +HIDE_SCOPE_NAMES = NO +HIDE_COMPOUND_REFERENCE= NO +SHOW_INCLUDE_FILES = NO +SHOW_GROUPED_MEMB_INC = NO +FORCE_LOCAL_INCLUDES = NO +INLINE_INFO = NO +SORT_MEMBER_DOCS = NO +SORT_BRIEF_DOCS = NO +SORT_MEMBERS_CTORS_1ST = YES +SORT_GROUP_NAMES = NO +SORT_BY_SCOPE_NAME = NO +STRICT_PROTO_MATCHING = NO +GENERATE_TODOLIST = NO +GENERATE_TESTLIST = NO +GENERATE_BUGLIST = NO +GENERATE_DEPRECATEDLIST= NO +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = NO +SHOW_FILES = NO +SHOW_NAMESPACES = NO +FILE_VERSION_FILTER = +LAYOUT_FILE = +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_NO_PARAMDOC = NO +WARN_AS_ERROR = NO +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = \ + ../beast/asio/append_buffers.h \ + ../beast/asio/async_completion.h \ + ../beast/asio/basic_streambuf.h \ + ../beast/asio/bind_handler.h \ + ../beast/asio/buffers_adapter.h \ + ../beast/asio/consuming_buffers.h \ + ../beast/asio/handler_alloc.h \ + ../beast/asio/prepare_buffers.h \ + ../beast/asio/static_streambuf.h \ + ../beast/asio/streambuf.h \ + ../beast/asio/streambuf_readstream.h \ + ../beast/asio/type_check.h \ + ../beast/http/basic_headers.h \ + ../beast/http/basic_parser.h \ + ../beast/http/chunk_encode.h \ + ../beast/http/empty_body.h \ + ../beast/http/error.h \ + ../beast/http/message.h \ + ../beast/http/parser.h \ + ../beast/http/read.h \ + ../beast/http/resume_context.h \ + ../beast/http/stream.h \ + ../beast/http/streambuf_body.h \ + ../beast/http/string_body.h \ + ../beast/http/type_check.h \ + ../beast/http/write.h \ + ../beast/wsproto/error.h \ + ../beast/wsproto/option.h \ + ../beast/wsproto/rfc6455.h \ + ../beast/wsproto/socket.h \ + ../beast/wsproto/static_string.h \ + ../beast/wsproto/teardown.h \ + +INPUT_ENCODING = UTF-8 +FILE_PATTERNS = +RECURSIVE = NO +EXCLUDE = ../beast/http/URL.h +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = +EXCLUDE_SYMBOLS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO +FILTER_SOURCE_PATTERNS = +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = NO +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = NO +REFERENCES_RELATION = NO +REFERENCES_LINK_SOURCE = YES +SOURCE_TOOLTIPS = YES +USE_HTAGS = NO +VERBATIM_HEADERS = YES +CLANG_ASSISTED_PARSING = NO +CLANG_OPTIONS = + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = YES +COLS_IN_ALPHA_INDEX = 5 +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = NO +HTML_OUTPUT = dhtm +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_EXTRA_STYLESHEET = +HTML_EXTRA_FILES = +HTML_COLORSTYLE_HUE = 220 +HTML_COLORSTYLE_SAT = 100 +HTML_COLORSTYLE_GAMMA = 80 +HTML_TIMESTAMP = NO +HTML_DYNAMIC_SECTIONS = NO +HTML_INDEX_NUM_ENTRIES = 100 +GENERATE_DOCSET = NO +DOCSET_FEEDNAME = "Doxygen generated docs" +DOCSET_BUNDLE_ID = org.doxygen.Project +DOCSET_PUBLISHER_ID = org.doxygen.Publisher +DOCSET_PUBLISHER_NAME = Publisher +GENERATE_HTMLHELP = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +CHM_INDEX_ENCODING = +BINARY_TOC = NO +TOC_EXPAND = NO +GENERATE_QHP = NO +QCH_FILE = +QHP_NAMESPACE = org.doxygen.Project +QHP_VIRTUAL_FOLDER = doc +QHP_CUST_FILTER_NAME = +QHP_CUST_FILTER_ATTRS = +QHP_SECT_FILTER_ATTRS = +QHG_LOCATION = +GENERATE_ECLIPSEHELP = NO +ECLIPSE_DOC_ID = org.doxygen.Project +DISABLE_INDEX = NO +GENERATE_TREEVIEW = NO +ENUM_VALUES_PER_LINE = 4 +TREEVIEW_WIDTH = 250 +EXT_LINKS_IN_WINDOW = NO +FORMULA_FONTSIZE = 10 +FORMULA_TRANSPARENT = YES +USE_MATHJAX = NO +MATHJAX_FORMAT = HTML-CSS +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest +MATHJAX_EXTENSIONS = +MATHJAX_CODEFILE = +SEARCHENGINE = YES +SERVER_BASED_SEARCH = NO +EXTERNAL_SEARCH = NO +SEARCHENGINE_URL = +SEARCHDATA_FILE = searchdata.xml +EXTERNAL_SEARCH_ID = +EXTRA_SEARCH_MAPPINGS = + +#--------------------------------------------------------------------------- +# Configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = NO +LATEX_OUTPUT = latex +LATEX_CMD_NAME = latex +MAKEINDEX_CMD_NAME = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = a4 +EXTRA_PACKAGES = +LATEX_HEADER = +LATEX_FOOTER = +LATEX_EXTRA_STYLESHEET = +LATEX_EXTRA_FILES = +PDF_HYPERLINKS = YES +USE_PDFLATEX = YES +LATEX_BATCHMODE = NO +LATEX_HIDE_INDICES = NO +LATEX_SOURCE_CODE = NO +LATEX_BIB_STYLE = plain +LATEX_TIMESTAMP = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = +RTF_SOURCE_CODE = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_SUBDIR = +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the XML output +#--------------------------------------------------------------------------- +GENERATE_XML = YES +XML_OUTPUT = ../bin/doc/xml +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the DOCBOOK output +#--------------------------------------------------------------------------- +GENERATE_DOCBOOK = NO +DOCBOOK_OUTPUT = docbook +DOCBOOK_PROGRAMLISTING = NO + +#--------------------------------------------------------------------------- +# Configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- +GENERATE_AUTOGEN_DEF = NO +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = YES +EXPAND_ONLY_PREDEF = YES +SEARCH_INCLUDES = YES +INCLUDE_PATH = ../ +INCLUDE_FILE_PATTERNS = +PREDEFINED = DOXYGEN \ + GENERATING_DOCS +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +EXTERNAL_PAGES = YES +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +CLASS_DIAGRAMS = NO +MSCGEN_PATH = +DIA_PATH = +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = NO +DOT_NUM_THREADS = 0 +DOT_FONTNAME = Helvetica +DOT_FONTSIZE = 10 +DOT_FONTPATH = +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +GROUP_GRAPHS = YES +UML_LOOK = NO +UML_LIMIT_NUM_FIELDS = 10 +TEMPLATE_RELATIONS = NO +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +CALL_GRAPH = NO +CALLER_GRAPH = NO +GRAPHICAL_HIERARCHY = YES +DIRECTORY_GRAPH = YES +DOT_IMAGE_FORMAT = png +INTERACTIVE_SVG = NO +DOT_PATH = +DOTFILE_DIRS = +MSCFILE_DIRS = +DIAFILE_DIRS = +PLANTUML_JAR_PATH = +PLANTUML_INCLUDE_PATH = +DOT_GRAPH_MAX_NODES = 50 +MAX_DOT_GRAPH_DEPTH = 0 +DOT_TRANSPARENT = NO +DOT_MULTI_TARGETS = NO +GENERATE_LEGEND = YES +DOT_CLEANUP = YES diff --git a/src/beast/doc/beast.qbk b/src/beast/doc/beast.qbk new file mode 100644 index 000000000..8f0cbfa82 --- /dev/null +++ b/src/beast/doc/beast.qbk @@ -0,0 +1,177 @@ +[/ + 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) +] + +[library Beast + [quickbook 1.6] + [copyright 2013 - 2016 Vinnie Falco] + [purpose C++ Library] + [license + 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]) + ] + [authors [Falco, Vinnie]] + [category template] + [category generic] +] + +[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()`]] +[def __connect__ [@http://www.opengroup.org/onlinepubs/000095399/functions/connect.html `connect()`]] +[def __getpeername__ [@http://www.opengroup.org/onlinepubs/000095399/functions/getpeername.html `getpeername()`]] +[def __getsockname__ [@http://www.opengroup.org/onlinepubs/000095399/functions/getsockname.html `getsockname()`]] +[def __getsockopt__ [@http://www.opengroup.org/onlinepubs/000095399/functions/getsockopt.html `getsockopt()`]] +[def __ioctl__ [@http://www.opengroup.org/onlinepubs/000095399/functions/ioctl.html `ioctl()`]] +[def __recvfrom__ [@http://www.opengroup.org/onlinepubs/000095399/functions/recvfrom.html `recvfrom()`]] +[def __sendto__ [@http://www.opengroup.org/onlinepubs/000095399/functions/sendto.html `sendto()`]] +[def __setsockopt__ [@http://www.opengroup.org/onlinepubs/000095399/functions/setsockopt.html `setsockopt()`]] +[def __socket__ [@http://www.opengroup.org/onlinepubs/000095399/functions/socket.html `socket()`]] + + + +[section:intro Introduction] + +Beast is a cross-platform C++ library built on Boost, containing two modules +implementing widely used network protocols. Beast.HTTP offers a universal +model for describing, sending, and receiving HTTP messages while Beast.WSProto +provides a complete implementation of the WebSocket protocol. Their design +achieves these goals: + +* [*Symmetry.] Interfaces are role-agnostic; the same interfaces can be +used to build clients, servers, or both. + +* [*Ease of Use.] HTTP messages are modeled using simple, readily +accessible objects. Functions and classes used to send and receive HTTP +or WebSocket messages are designed to resemble Boost.Asio as closely as +possible. Users familiar with Boost.Asio will be immediately comfortable +using this library. + +* [*Flexibility.] Interfaces do not mandate specific implementation +strategies; important decisions such as buffer or thread management are +left to users of the library. + +* [*Performance.] The implementation performs competitively, making it a +realistic choice for building a high performance network server. + +* [*Scalability.] Development of network applications that scale to thousands +of concurrent connections is possible with the implementation. + +* [*Basis for further abstraction.] The interfaces facilitate the +development of other libraries that provide higher levels of abstraction. + + + +[section:requirements Requirements] + +Beast requires: + +* [*C++11.] A minimum of C++11 is needed. +* [*Boost.] Beast is built on Boost, especially Boost.Asio. +* [*OpenSSL.] If using TLS/Secure sockets (optional). + +[note Tested compilers: msvc-14+, gcc 5+, clang 3.6+] + +Most of the library is header-only; however, the HTTP parser used is written +in C. To link an application that uses Beast, it is necessary to add a single +.cpp file from beast into your project's build script. + +[endsect] + + + +[section:example Examples] + +These usage examples are intended to quickly impress upon readers the +flavor of the library. + +[note + All examples and identifiers mentioned in this document are written as + if the following statements are in effect: + ``` + #include + #include + using namespace beast; + using namespace beast::http; + using namespace boost::asio; + using namespace boost::asio::ip; + ``` +] + +Use HTTP to request the root page from a website and receive the response: +``` + std::string const host = "boost.org"; + io_service ios; + tcp::resolver r(ios); + tcp::socket sock(ios); + connect(sock, r.resolve(tcp::resolver::query{host, "http"})); + + request req(method_t::http_get, "/", 11); + req.headers.replace("Host", host + ":" + std::to_string(sock.remote_endpoint().port())); + req.headers.replace("User-Agent", "Beast"); + write(sock, prepare(req, connection(close))); + + parsed_response resp; + read(sock, resp); + ... +``` + +Establish a WebSocket connection, send a message and receive the reply: +``` + std::string const host = "boost.org"; + io_service ios; + tcp::resolver r(ios); + tcp::socket sock(ios); + connect(sock, r.resolve(tcp::resolver::query{host, "ws"})); + + wsproto::socket ws(sock); + ws.handshake(); + ws.write(ws, buffer("Hello, world!")); + + streambuf sb; + wsproto::opcode op; + ws.read(ws, op, sb); + + ws.close(); // WebSocket protocol close +``` + +[endsect] + + + +[section:credits Credits] + +Beast would not be possible without the considerable time and patience +contributed by David Schwartz, Edward Hennis, Howard Hinnant, Miguel Portilla, +Nikolaos Bougalis, Scott Determan, Scott Schurr, and Ripple Labs for +supporting its development. Thanks also to Christopher Kohloff, whose Asio +C++ library is the inspiration behind which much of the design and +documentation is based. + +[endsect] + + + +[endsect] + + + +[include http.qbk] +[include wsproto.qbk] +[include types.qbk] +[include design.qbk] +[section:quickref Quick Reference] +[xinclude quickref.xml] +[endsect] +[include reference.qbk] +[section:idx Index] +[xinclude index.xml] +[endsect] diff --git a/src/beast/doc/boostbook.dtd b/src/beast/doc/boostbook.dtd new file mode 100644 index 000000000..bd4c3f871 --- /dev/null +++ b/src/beast/doc/boostbook.dtd @@ -0,0 +1,439 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +%DocBook; diff --git a/src/beast/doc/design.qbk b/src/beast/doc/design.qbk new file mode 100644 index 000000000..316b83b6e --- /dev/null +++ b/src/beast/doc/design.qbk @@ -0,0 +1,213 @@ +[/ + 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:design Design choices] + +The implementations are driven by business needs of cryptocurrency server +applications ([link https://ripple.com Ripple] written in C++. These +needs were not met by existing solutions so new code was written. The new +code tries to avoid design flaws encountered in the already-existing software +libraries: + +* Don't sacrifice performance. + +* Don't do too much, otherwise interfaces become rigid. + +* Symmetric interfaces (client and server the same, or close to it). + +* Emulate Boost.Asio interfaces as much as possible, since Asio is + proven and it is familiar to users. + +* Let library users make the important decisions such as how to + allocate memory or how to leverage flow control. + +Beast formalizes the [link beast.types.Streambuf [*`Streambuf`]] concept, +and relies heavily on the Boost.Asio [*`ConstBufferSequence`] and +[*`MutableBufferSequence`] concepts for passing buffers to functions. +The authors have found the `Streambuf` and buffer sequence interfaces +to be optimal for interacting with Asio, and for other tasks such as +incremental parsing of data in buffers (for example, parsing websocket +frames stored in a [link beast.ref.static_streambuf `static_streambuf`]). + +During the development of Beast the authors have studied other software +packages and in particular the comments left during the Boost Review process +of other packages offering similar functionality. In this section we attempt +to address those issues. + +[variablelist +[[ + "I would also like to see instances of this library being used + in production. That would give some evidence that the design + works in practice."" +][ + Beast.HTTP and Beast.WebSocket will be used in [*rippled], an + asynchronous peer to peer server that implements the + [*Ripple Consensus Protocol]. These servers are deployed in multiple + production environments, with banks in many countries running client + applications that connect to [*rippled]. +]] + +] + + +[section:http HTTP] + +For HTTP we to model the message to maximize flexibility of implementation +strategies while allowing familiar verbs such as [*`read`] and [*`write`]. +The HTTP interface is further driven by the needs of the WebSocket module, +as a WebSocket session requires a HTTP Upgrade handshake exchange at the +start. Other design goals: + +* Don't try to invent a complete web server or client + +* Have simple free functions to send and receive messages. + +* Allow the message object to be customized, + +[variablelist + +[[ + "Some more advanced examples, e.g. including TLS with client/server + certificates would help."" +][ + The HTTP interface doesn't try to reinvent the wheel, it just uses + the `boost::asio::ip::tcp::socket` or `boost::asio::ssl::stream` that + you set up beforehand. Callers use the interfaces already existing + on those objects to make outgoing connections, accept incoming connections, + or establish TLS sessions with certificates. We find the available Asio + examples for performing these tasks sufficient. +]] + +[[ + "A built-in router?" +][ + We presume this means a facility to match expressions against the URI + in HTTP requests, and dispatch them to calling code. The authors feel + that this is a responsibility of higher level code. Beast.HTTP does + not try to offer a web server. +]] + +[[ + "Cookies? Forms/File Uploads?"" +][ + Cookies, or managing these types of HTTP headers in general, is the + responsibility of higher levels. Beast.HTTP just tries to get complete + messages to and from the calling code. It deals in the HTTP headers just + enough to process the message body and leaves the rest to callers. However, + for forms and file uploads the symmetric interface of the message class + allows HTTP requests to include arbitrary body types including those needed + to upload a file or fill out a form. +]] + +[[ + "...supporting TLS (is this a feature? If not this would be a show-stopper), + etc. +][ + Beast.HTTP does not provide direct facilities for implementing TLS + connections; however, the interfaces already existing on the + `boost::asio::ssl::stream` are available and can be used to establish + secure connections. Then, functions like `http::read` or `http::async_write` + can work with those encrypted connections with no problem. +]] + +[[ + "There should also be more examples of how to integrate the http service + with getting files from the file system, generating responses CGI-style" +][ + The design goal for the library is to not try to invent a web server. + We feel that there is a strong need for a basic implementation that + models the HTTP message and provides functions to send and receive them + over Asio. Such an implementation should serve as a building block upon + which higher abstractions such as the aforementioned HTTP service or + cgi-gateway can be built. +]] + +[[ + "You should send a 100-continue to ask for the rest of the body if required." +][ + These behaviors are best left to the calling software. A future library + can build on Beast.HTTP to provide these behaviors. +]] + +[[ + "What about HTTP/2?"" +][ + Many reviewers feel that HTTP/2 support is an essential feature of + a HTTP library. The authors agree that HTTP/2 is important but also + feel that the most sensible implementation is one that does not re-use + the same network reading and writing interface for 2 as that for 1.0 + and 1.1. + + The Beast.HTTP message model is suitable for HTTP/2 and can be re-used. + The IEFT HTTP Working Group adopted message compatiblity with HTTP/1.x + as an explicit goal. A parser can simply emit full headers after + decoding the compressed HTTP/2 headers. The stream ID is not logicaly + part of the message but rather message metadata and should be + communicated out-of-band (see below). HTTP/2 sessions begin with a + traditional HTTP/1.1 Upgrade similar in fashion to the WebSocket + upgrade. A HTTP/2 implementation can use existing Beast.HTTP primitives + to perform this handshake. + + Free functions for HTTP/2 sessions are not possible because of the + requirement to maintain per-session state. For example, to decode the + compressed headers. Or to remember and respect the remote peer's window + settings. The authors propose that a HTTP/2 implementation be written + as a separate class template, similar to the `websocket::stream` but with + additional interfaces to support version 2 features. We feel that + Beast.HTTP offers enough useful functionality to justify inclusion, + so that developers can take advantage of it right away instead of + waiting. +]] + +] + + +[endsect] + +[section:websocket WebSocket] + +[variablelist +[[ + What about message compression? +][ + The feature is not currently present in the library, but the choice + of type requirements for buffers passed to the read functions have been + made with compression in mind. There is the plan to add this feature; + however, we feel that even without compression users can begin taking + advantage of the WebSocket protocol immediately with this library. +]] + +[[ + Where is the TLS/SSL interface? +][ + The `websocket::stream` just wraps the socket or stream that you + provide (for example, a `boost::asio::ip::tcp::socket` or a + `boost::asio::ssl::stream`). You establish your TLS connection + using the interface on `ssl::stream` like shown in all of the Asio + examples, they construct your `websocket::stream` around it. + It works perfectly fine - Beast.WebSocket doesn't try to reinvent the + wheel or put a fresh coat of interface paint on the `ssl::stream`. + + The WebSocket implementation [*does] provides support for shutting down + the TLS connection through the use of the ADL compile-time virtual functions + [link beast.ref.wsproto__teardown `teardown`] and + [link beast.ref.wsproto__async_teardown `async_teardown`]. These will + properly close the connection as per rfc6455 and overloads are available + for TLS streams. Callers may provide their own overloads of these functions + for user-defined next layer types. +]] +] + +[endsect] + + + + + + + +[endsect] diff --git a/src/beast/doc/http.qbk b/src/beast/doc/http.qbk new file mode 100644 index 000000000..5120092f7 --- /dev/null +++ b/src/beast/doc/http.qbk @@ -0,0 +1,215 @@ +[/ + 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:http HTTP] + +Beast.HTTP offers programmers simple and performant models of HTTP messages and +their associated operations including synchronous and asynchronous reading and +writing using Boost.Asio. + +The HTTP protocol is described fully in +[@https://tools.ietf.org/html/rfc2616 rfc2616] + + + +[section:motivation Motivation] + +The HTTP protocol is pervasive in network applications. As C++ is a logical +choice for high performance network servers, there is great utility in solid +building blocks for manipulating, sending, and receiving HTTP messages +compliant with the Hypertext Transfer Protocol and the supplements that +follow. Unfortunately reliable implementations or industry standards do not +exist in C++. + +Beast.HTTP is built on Boost.Asio and uses HTTP parser from NodeJS, which is +extensively field tested and exceptionally robust. A proposal to add networking +functionality to the C++ standard library, based on Boost.Asio, is under +consideration by the standards committee. Since the final approved networking +interface for the C++ standard library will likely closely resemble the current +interface of Boost.Asio, it is logical for Beast.HTTP to use Boost.Asio as its +network transport. + +[note The documentation which follows assumes familiarity with +both Boost.Asio and the HTTP protocol specification described in +[@https://tools.ietf.org/html/rfc2616 rfc2616] ] + +[endsect] + + + +[section:example Example] + +All examples and identifiers mentioned in this document are written as +if the following declarations are in effect: +``` +#include +using namespace beast::http; +using namespace boost::asio; +``` + +Create a HTTP request: +``` +request req({method_t::http_get, "/", 11}); +req.headers.insert("Host", "127.0.0.1:80"); +req.headers.insert("User-Agent", "Beast.HTTP"); + +``` + +To send a message it must first be prepared through a call to `prepare`. This +customization point transforms the `message` into a `prepared_message`, +filling in some standard HTTP behavior and allowing the Body associated with +the message to perform preparatory steps. For example, a string body may set +the Content-Length and Content-Type appropriately. +``` +void send_request(ip::tcp::socket& sock, + request&& req) +{ + // Send the request on the socket + write(sock, prepare(req, connection(keep_alive)); +} +``` + +Messages can be read from the network and parsed into a `parsed_message` object, +which extends the `message` by adding parse-specific metadata such as the +keep-alive which is context sensitive (depending on the HTTP version for +example). When preparing a response for sending, `prepare` must be called with +an additional parameter, the corresponding parsed request. The implementation +inspects the contents of the request to set dependent fields of the response. +This example reads a message, builds a response, and sends it. +``` +void handle_connection(ip::tcp::socket& sock) +{ + parsed_request req; + read(sock, req); + response resp; + ... + write(sock, prepare(resp, req)); +} +``` + +[endsect] + + + +[section:message Message model] + +A HTTP message (referred to hereafter as "message") contains a request or +response line, a series of zero or more name/value pairs (collectively +termed "headers"), and a series of octets called the message body which may +be zero in length. Applications using HTTP often perform these operations +on messages: + +* [*Parse] a new message from a series of octets. + +* [*Assemble] a new message from scratch or from an existing message. + +* [*Serialize] a message into a series of octets. + +* [*Read] a message from a stream. This can be thought of as a compound +operation; a network read, followed by a [*parse]. + +* [*Write] a message to a stream. This can be thought of as a compound +operation: a [*serialize] followed by a network write. + +The spectrum of hardware and software platforms which perform these typical +HTTP operations is vast, ranging from powerful servers in large datacenters +to tiny resource-limited embedded devices. No single concrete implementation +of a class intended to model messages can efficiently serve all needs. +For example, an object that minimizes resources during parsing may not be +able to edit and change headers dynamically. A message that represents the +message body as a disk file may support sending but not parsing. Many efficient +and correct models of messages exist, supporting some or all of the +operations listed above. + +To support a variety of implementation strategies, we introduce customization +points for the header and body via class template arguments. This illustration +shows the message class template (boilerplate present in the actual +declaration has been removed for clarity): + +[$images/message.png [width 580px] [height 225px]] + +The default constructor, move special members, and copy special members are +all defaulted. A message is movable, copyable, or default constructible based +on the capabilities of its template arguments. + +Messages modeled in this fashion are ['complete], containing all of the +information required to perform the supported set of operations. They are +['first-class types], returnable from functions and composable. HTTP +requests and responses are distinct types, allowing functions to be +overloaded on the type of message. + +[endsect] + + + +[section:body Body parameter] + +The `Body` template argument in the `message` class must meet the +[link beast.types.Body [*`Body`] requirements]. It provides customization +of the data member in the message, the algorithm for parsing, and the +algorithm for serialization: + +[$images/body.png [width 510px] [height 210px]] + +The following types, provided by the library, meet the `Body` requirements: + +* [link beast.ref.http__empty_body [*`empty_body`:]] An empty message body. +Used in GET requests where there is no message body. + +* [link beast.ref.http__string_body [*`string_body`:]] A body with a +`value_type` of `std::string`. Useful for quickly putting together a request +or response with simple text in the message body (such as an error message). +Subject to the performance limit of strings if large amounts of data are +inserted. + +* [link beast.ref.http__basic_streambuf_body [*`basic_streambuf_body`:]] A body with a +`value_type` of `streambuf`. A streambuf is an efficient storage object which +uses multiple octet arrays of varying lengths to represent data. + +Instances of the optional nested types `writer` and `reader` perform +serialization and deserialization of the message body. If either or +both of these types are present, the message becomes serializable, parsable, +or both. They model [link beast.types.Reader [*`Reader`]] and +[link beast.types.Writer [*`Writer`]] respectively. + +For specialized applications, users may implement their own types which +meet the requirements. The examples included with this library provide a +Body implementation used to serve files in a HTTP server. + +[endsect] + + + +[section:headers Headers container] + +The `Headers` type represents the field/value pairs present in every HTTP +message. These types implement the +[link beast.types.FieldSequence [*`FieldSequence`]] +concept. The value type of a field sequence is an object meeting the +requirements of [link beast.types.Field [*`Field`]]. The implementation can +serialize any instance of `Headers` that meets the field sequence requirements. +This example shows a function which returns `true` if the specified field +sequence has a connect field: +``` +template +bool +has_connect(FieldSequence const& fs) +{ + return std::find_if(fs.begin(), fs.end(), + [&](auto const& field) + { + return ci_equal(field.name(), "Connect"); + }); +} +``` + +[endsect] + + + +[endsect] + diff --git a/src/beast/doc/images/beast.png b/src/beast/doc/images/beast.png new file mode 100644 index 000000000..3444d5814 Binary files /dev/null and b/src/beast/doc/images/beast.png differ diff --git a/src/beast/doc/images/beast.psd b/src/beast/doc/images/beast.psd new file mode 100644 index 000000000..81096530f Binary files /dev/null and b/src/beast/doc/images/beast.psd differ diff --git a/src/beast/doc/images/body.png b/src/beast/doc/images/body.png new file mode 100644 index 000000000..54d05550c Binary files /dev/null and b/src/beast/doc/images/body.png differ diff --git a/src/beast/doc/images/body.psd b/src/beast/doc/images/body.psd new file mode 100644 index 000000000..3d8dca09a Binary files /dev/null and b/src/beast/doc/images/body.psd differ diff --git a/src/beast/doc/images/message.png b/src/beast/doc/images/message.png new file mode 100644 index 000000000..cb07efc82 Binary files /dev/null and b/src/beast/doc/images/message.png differ diff --git a/src/beast/doc/images/message.psd b/src/beast/doc/images/message.psd new file mode 100644 index 000000000..2332dec4f Binary files /dev/null and b/src/beast/doc/images/message.psd differ diff --git a/src/beast/doc/index.xml b/src/beast/doc/index.xml new file mode 100644 index 000000000..0bbf7b76b --- /dev/null +++ b/src/beast/doc/index.xml @@ -0,0 +1,13 @@ + + + + + +
+ +
diff --git a/src/beast/doc/makeqbk.sh b/src/beast/doc/makeqbk.sh new file mode 100644 index 000000000..584cb8a93 --- /dev/null +++ b/src/beast/doc/makeqbk.sh @@ -0,0 +1,13 @@ +#!/usr/bin/bash + +# 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) + +mkdir -p ../bin/doc/xml +doxygen beast.dox +cd ../bin/doc/xml +xsltproc combine.xslt index.xml > all.xml +cd ../../../doc +xsltproc reference.xsl ../bin/doc/xml/all.xml > reference.qbk diff --git a/src/beast/doc/quickref.xml b/src/beast/doc/quickref.xml new file mode 100644 index 000000000..2f015ecfc --- /dev/null +++ b/src/beast/doc/quickref.xml @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + HTTP + + + WebSocket + + + + + + + Classes + + basic_headers + basic_parser + basic_streambuf_body + empty_body + error_code + message + resume_context + stream + streambuf_body + string_body + + Type Traits + + is_Body + + + + Functions + + async_read + async_write + chunk_encode + chunk_encode_final + read + write + + Concepts + + Body + Field + FieldSequence + Reader + Writer + + + + Classes + + close_reason + socket + static_string + + Options + + auto_fragment_size + decorate + keep_alive + read_buffer_size + read_message_max + write_buffer_size + + + + Functions + + async_teardown + teardown + + Constants + + close_code + opcode + + + + + + + + + + + + + + Core + + + + + + + Classes + + async_completion + basic_streambuf + buffers_adapter + consuming_buffers + handler_alloc + prepared_buffers + static_streambuf + static_streambuf_n + streambuf + streambuf_readstream + + + + Functions + + append_buffers + bind_handler + prepare_buffer + prepare_buffers + + + + Type Traits + + is_AsyncReadStream + is_AsyncWriteStream + is_BufferSequence + is_ConstBufferSequence + is_Handler + is_MutableBufferSequence + is_Stream + is_Streambuf + is_SyncReadStream + is_SyncWriteStream + + + + Concepts + + BufferSequence + Stream + Streambuf + + + + + + diff --git a/src/beast/doc/reference.xsl b/src/beast/doc/reference.xsl new file mode 100644 index 000000000..fd2cbb310 --- /dev/null +++ b/src/beast/doc/reference.xsl @@ -0,0 +1,1725 @@ + + + + + + + + + + + + + + + + + +[/ + 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:ref Reference] + + + + + + + + + + + + + + + + [endsect] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ``[link beast.ref.ConstBufferSequence ['ConstBufferSequence]]`` + + + ``['implementation-defined]`` + + + ``[link beast.ref.asynchronous_operations.return_type_of_an_initiating_function ['void-or-deduced]]`` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [heading + + ] + + + + + + + + + + + + + + `` + + + + + + `` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ` + + ` + + + + ` + + ` + + + + * + + + + + + + +[*] + +['] + + + + + [heading Parameters] + + + [heading Exceptions] + + + [variablelist + + ] + + + + [[ + + ][ + + ]] + + + + + + + + [heading Return Value] + + + + + + [heading Remarks] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \_ + + + + + + + + + + + + + + + + + \[ + + + + + + + \] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [link beast.ref. + + ` + + `] + + + ` + + ` + + + + + + + + + + + + + + + + + + + + + + + [link beast.ref. + + ` + + `] + + + ` + + ` + + + + + + + + + + + + + + + + + + + + + + + [link beast.ref. + + ` + + `] + + + ` + + ` + + + + + + + + + + + + + + + + + + + + + + + [link beast.ref. + + ` + + `] + + + ` + + ` + + + + + + + + + + [heading Requirements] + ['Header: ][^ + + ] + ['Convenience header: ] + + + [^beast/asio.h] + + + [^beast/http.h] + + + [^beast/wsproto.h] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [section: + + + + ] + + ``` + + + + + + : + + + + + + public + + + + + + , + + + + ``` + + + + + + + + + + + + + +[endsect] + + + + + + + [heading Types] + [table [[Name][Description]] + + + [ + + + [[link beast.ref. + + . + + [* + + ]]] [ + + + + ] + + + + + + + + + + + + + + + + + + + [[link beast.ref. + + [* + + ]]] [ + + ] + + + ] + + ] + + + [heading Member Functions] + [table [[Name][Description]] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [ + [[link beast.ref. + . + [* + + ]]] [ + + + + + + + + ] ] + + + ] + + + [heading Protected Member Functions] + [table [[Name][Description]] + + + + + + + + + + + + + + + + + + + + + + + + + [ + [[link beast.ref. + . + [* + + ]]] [ + + + + + + + + ] ] + + + ] + + + [heading Private Member Functions] + [table [[Name][Description]] + + + + + + + + + + + + + + + + + + + + + + + + + [ + [[link beast.ref. + . + [* + + ]]] [ + + + + + + + + ] ] + + + ] + + + [heading Data Members] + [table [[Name][Description]] + + + [ + [[link beast.ref. + . + [* + + ]]] [ + + ] ] + + ] + + + [heading Protected Data Members] + [table [[Name][Description]] + + + [ + [[link beast.ref. + . + [* + + ]]] [ + + ] ] + + ] + + + [heading Private Data Members] + [table [[Name][Description]] + + + [ + [[link beast.ref. + . + [* + + ]]] [ + + ] ] + + ] + + + [heading Friends] + [table [[Name][Description]] + + + + + + + + + + + + + + + + + + + + + + + + + [ + [[link beast.ref. + . + [* + + ]]] [ + + + + + + + + ] ] + + + ] + + + [heading Related Functions] + [table [[Name][Description]] + + + + + + + + + + + + + + + + + + + + + + + + + [ + [[link beast.ref.. + [*]]] + [ + + + + + + + + + ] + ] + + + + ] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [section: + + + + :: + + ] + [indexterm2 + + .. + + ] + + ``` + + + + + ``` + + ``` + + + + explicit + friend + static + virtual + + + + + + + + + ``[link beast.ref. + + . + + .overload + + + + ]``( + + ) + + const + + ; + + ``` + + + + + [section: + + + + + overload + + + + + :: + + + ( + + of + + overloads) + + ] + + + + ['Inherited from + + + + .] + + + + [indexterm2 + + .. + + ] + + + + + + + + + + + + + + + + + + ``` + + ``` + + + ``` + + ``` + + + + + + + + + + + [endsect] [endsect] + + + [endsect] + + + + + + + + + ``` using + + = + + + + + + + + + ; ``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ``` + + static + + + + + + + + + ; ``` + + + + + + + ``` + enum + + ``` + + + + [indexterm2 + + .. + + ] + + + [heading Values] + [variablelist + + [[ + + ] [ + + ]] + + ] + + + + + + + + + + + + + + + + + + + + + + + static + virtual + + + + + + ( + + ) + const + ; + + + + + template< + + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + = + + + + , + + + + + + + + + (& + + ) + + + + + + + & + + + + * + + + + + + + + + + = + + , + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [section: + + + + ] + [indexterm1 + + ] + + + + + + + + + + + ``` + + + + + + + + + + + + ``[link beast.ref. + + .overload + + + + ]``( + + ); + + ``` + + + + + + + + + [section: + + + + [section: + overload + + + + + + ( + + of + + overloads) + + ] + + [indexterm1 + + ] + + + + + + + + + + + + + + + + + + ``` + + ``` + + + + + + + + + + + [endsect] [endsect] + + + [endsect] + + + + + diff --git a/src/beast/doc/types.qbk b/src/beast/doc/types.qbk new file mode 100644 index 000000000..535a4b495 --- /dev/null +++ b/src/beast/doc/types.qbk @@ -0,0 +1,446 @@ +[/ + 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:types Type Requirements] + + + +[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` meets [*one of] the following requirements: + +* `ConstBufferSequence` +* `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 +of this type when serializing a message, and calls into it zero or more times +to provide buffers containing the data. The interface of `Writer` is intended +to allow serialization in these scenarios: + +* A body that does not entirely fit in memory. +* A body produced incrementally from coroutine output. +* A body represented by zero or more buffers already in memory. +* A body as a series of buffers when the content size is not known ahead of time. +* Body data generated on demand from other threads. +* Body data computed algorithmically. + +In this table: + +* `X` denotes a type meeting the requirements of `Writer`. + +* `a` denotes a value of type `X`. + +* `m` denotes a value of type `message const&` where + `std::is_same:value == true`. + +* `rc` is an object of type [link beast.reference.http__resume_context resume_context]. + +* `ec` is a value of type `error_code&`. + +* `wf` is a [*write function]: a function object of unspecified type provided + by the implementation which accepts any value meeting the requirements + of `ConstBufferSequence` as its single parameter. + +[table Writer 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.init(ec)`] + [`void`] + [ + Called immediately after construction. + If `ec` is set, the serialization is aborted and the error + is propagated to the caller. + ] +] +[ + [`a.content_length()`] + [`std::size_t`] + [ + If this member is present, it is called after initialization + and before calls to provide buffers. The serialized message will + have the Content-Length field set to the value returned from + this function. If this member is absent, the serialized message + body will be chunk-encoded for HTTP versions 1.1 and later, else + the serialized message body will be sent unmodified, with the + error `boost::asio::error::eof` returned to the caller, to notify + they should close the connection to indicate the end of the message. + ] +] +[ + [`a(rc, ec, wf)`] + [`boost::tribool`] + [ + Called repeatedly after `init` succeeds. + `wf` is a function object which takes as its single parameter, + any value meeting the requirements of `ConstBufferSequence`. + Buffers provided by the `writer` to this [*write function] must + remain valid until the next member function of `writer` is + invoked (which may be the destructor). This function returns `true` + to indicate all message body data has been written, or `false` + if there is more body data. If the return value is + `boost::indeterminate`, the implementation will suspend the operation + until the writer invokes `rc`. It is the writers responsibility when + returning `boost::indeterminate`, to acquire ownership of the + `resume_context` via move construction and eventually call it or else + undefined behavior results. + ] +] +] + +[note Definitions for required `Writer` member functions should be declared +inline so the generated code becomes part of the implementation. ] + +Exemplar: +``` +struct writer +{ +public: + /** Construct the writer. + + The msg object is guaranteed to exist for the lifetime of the writer. + + Exceptions: + No-throw guarantee. + + @param msg The message whose body is to be written. + */ + template + explicit + writer(message const& msg); + + /** Initialize the writer. + + Called once immediately after construction. + The writer can perform initialization which may fail. + + @param ec Contains the error code if any errors occur. + */ + void + init(error_code& ec); + + /** Returns the content length. + + If this member is present, the implementation will set the + Content-Length field accordingly. If absent, the implementation will + use chunk-encoding or terminate the connection to indicate the end + of the message. + */ + std::size_t + content_length() const; + + /** Write zero or one buffer representing the message body. + + Postconditions: + + If return value is `true`: + * Callee does not take ownership of resume. + * Callee made zero or one calls to `write`. + * There is no more data remaining to write. + + If return value is `false`: + * Callee does not take ownership of resume. + * Callee made one call to `write`. + + If return value is boost::indeterminate: + * Callee takes ownership of `resume`. + * Caller suspends the write operation + until `resume` is invoked. + + When the caller takes ownership of resume, the + asynchronous operation will not complete until the + caller destroys the object. + + @param resume A functor to call to resume the write operation + after the writer has returned boost::indeterminate. + + @param ec Set to indicate an error. This will cause an + asynchronous write operation to complete with the error. + + @param write A functor the writer will call to provide the next + set of buffers. Ownership of the buffers is not transferred, + the writer must guarantee that the buffers remain valid until the + next member function is invoked, which may be the destructor. + + @return `true` if there is data, `false` when done, + boost::indeterminate to suspend. + + @note Undefined behavior if the callee takes ownership + of resume but does not return boost::indeterminate. + */ + template + boost::tribool + operator()(resume_context&&, error_code&, WriteFunction&& write); +}; +``` + +[endsect] + + + +[section:Stream Stream] + +A `Stream` meets the following requirements: + +* `SyncReadStream` +* `SyncWriteStream` +* `AsyncReadStream` +* `AsyncWriteStream` + +[endsect] + + + +[section:Streambuf Streambuf] + +In the table below, `X` denotes a class, `a` denotes a value +of type `X`, `n` denotes a value convertible to `std::size_t`, +and `U` and `T` denote unspecified types. + +[table Streambuf requirements +[[operation] [type] [semantics, pre/post-conditions]] +[ + [`X::const_buffers_type`] + [`T`] + [`T` meets the requirements for `ConstBufferSequence`.] +] +[ + [`X::mutable_buffers_type`] + [`U`] + [`U` meets the requirements for `MutableBufferSequence`.] +] +[ + [`a.commit(n)`] + [`void`] + [Moves bytes from the output sequence to the input sequence.] +] +[ + [`a.consume(n)`] + [`void`] + [Removes bytes from the input sequence.] +] +[ + [`a.data()`] + [`T`] + [Returns a list of buffers that represents the input sequence.] +] +[ + [`a.prepare(n)`] + [`U`] + [Returns a list of buffers that represents the output sequence, with + the given size.] +] +[ + [`a.size()`] + [`std::size_t`] + [Returns the size of the input sequence.] +] +[ + [`a.max_size()`] + [`std::size_t`] + [Returns the maximum size of the `Streambuf`.] +] +] + +[endsect] + + + +[endsect] diff --git a/src/beast/doc/wsproto.qbk b/src/beast/doc/wsproto.qbk new file mode 100644 index 000000000..0ed910d04 --- /dev/null +++ b/src/beast/doc/wsproto.qbk @@ -0,0 +1,391 @@ +[/ + 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:wsproto WSProto] + +The WebSocket Protocol enables two-way communication between a client +running untrusted code in a controlled environment to a remote host that has +opted-in to communications from that code. The protocol consists of an opening +handshake followed by basic message framing, layered over TCP. The goal of +this technology is to provide a mechanism for browser-based applications that +need two-way communication with servers that does not rely on opening multiple +HTTP connections. + +Beast.WSProto provides developers with a robust WebSocket implementation +built on Boost.Asio with a consistent asynchronous model using a modern +C++ approach. + +The WebSocket protocol is described fully in +[@https://tools.ietf.org/html/rfc6455 rfc6455] + + + +[section:motivation Motivation] + +Today's web applications increasingly rely on alternatives to standard HTTP +to achieve performance and/or responsiveness. While WebSocket implementations +are widely available in common web development languages such as Javascript, +good implementations in C++ are scarce. A survey of existing C++ WebSocket +solutions reveals interfaces which lack symmetry, impose performance penalties, +and needlessly restrict implementation strategies. + +Beast.WSProto is built on Boost.Asio, a robust cross platform networking +framework that is part of Boost and also offered as a standalone library. +A proposal to add networking functionality to the C++ standard library, +based on Boost.Asio, is under consideration by the standards committee. +Since the final approved networking interface for the C++ standard library +will likely closely resemble the current interface of Boost.Asio, it is +logical for Beast.WSProto to use Boost.Asio as its network transport. + +Beast.WSProto takes advantage of Boost.Asio's extensible asynchronous +model, handler allocation, and handler invocation hooks. Calls to +Beast.WSProto 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`), +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. + +There is no need for inheritance or virtual members in `wsproto::socket`. +All operations are templated and transparent to the compiler, allowing for +maximum inlining and optimization. + +[note The documentation which follows assumes familiarity with +both Boost.Asio and the WebSocket protocol specification described in +[@https://tools.ietf.org/html/rfc6455 rfc6455] ] + +[endsect] + + + +[section:creating Creating the socket] + +The interface to Beast's WebSocket implementation is a single template +class which wraps an object meeting the requirements of `SyncStream` if +synchronous operations are performed, `AsyncStream` if asynchronous +operations are performed, or both. Arguments supplied during construction +are passed to the `Stream` constructor. Here we declare two websockets +which contain the underlying stream: +``` +io_service ios; +wsproto::socket ws(ios); + +ssl::context ctx(ssl::context::sslv23); +wsproto::socket> wss(ios, ctx); +``` + +For servers that can handshake in multiple protocols, it may be desired +to wrap a socket that already exists. The socket can be moved in: +``` + tcp::socket&& sock; + ... + wsproto::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; + ... + wsproto::socket ws(sock); +``` + +The stream 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); + wsproto::socket> ws(ios, ctx); + ... + ws.next_layer().shutdown(); // ssl::stream shutdown +``` + +[important Initiating read and write operations on the next layer while +websocket operations are being performed can break invariants, and +result in undefined behavior. ] + +[endsect] + + + +[section:connecting Making connections] + +Connections are established by using the interfaces which already exist for +the wrapped stream. For example, making an outgoing connection: +``` + std::string const host = "mywebapp.com"; + io_service ios; + tcp::resolver r(ios); + wsproto::socket ws(ios); + connect(ws.next_layer(), r.resolve(tcp::resolver::query{host, "ws"})); +``` + +Accepting an incoming connection: +``` +void do_accept(tcp::acceptor& acceptor) +{ + wsproto::socket ws(acceptor.get_io_service()); + acceptor.accept(ws.next_layer()); +} +``` + +[note Examples use synchronous interfaces for clarity of exposition. ] + +[endsect] + + + +[section:handshaking Handshaking] + +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. `wsproto::socket::hanshake` is +used to send the request with the required host and resource strings. +``` + wsproto::socket ws(ios); + ... + ws.set_option(wsproto::keep_alive(true)); + ws.handshake("ws.mywebapp.com:80", "/cgi-bin/bitcoin-prices"); +``` + +The `wsproto::socket` 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: +``` + wsproto::socket ws(ios); + ... + ws.accept(); +``` + +Servers that can handshake in multiple protocols may have already read data +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) +{ + boost::asio::streambuf sb; + read_until(sock, sb, "\r\n\r\n"); + ... + wsproto::socket ws(sock); + ws.accept(sb.data()); + ... +} +``` + +Alternatively, the caller can pass an entire HTTP request if it was +obtained elsewhere: +``` +void do_accept(tcp::socket& sock) +{ + boost::asio::streambuf sb; + http::parsed_request request; + http::read(sock, request); + ... + wsproto::socket ws(sock); + ws.accept(request); + ... +} +``` + +[note Identifiers in the `http` namespace are part of Beast.HTTP. ] + +[endsect] + + + +[section:messages Messages] + +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(wsproto::socket& ws) +{ + streambuf sb; + wsproto::opcode::value op; + ws.read(sb); + + ws.set_option(wsproto::message_type(op)); + wsproto::write(ws, sb.data()); + sb.consume(sb.size()); +} +``` + +[important Calls to `wsproto::socket::set_option` must be made from the same +implicit or explicit strand as that used to perform other operations. ] + +[endsect] + + + +[section:frames Frames] + +Some use-cases make it impractical or impossible to buffer the entire +message ahead of time: + +* Streaming multimedia to an endpoint. +* Sending a message that does not fit in memory at once. +* Providing incremental results as they become available. + +For these cases, the frame oriented interface may be used. This +example reads and echoes a complete message using this interface: +``` +void echo(wsproto::socket& ws) +{ + beast::streambuf sb; + wsproto::frame_info fi; + for(;;) + { + ws.read_frame(fi, sb); + if(fi.fin) + break; + } + ws.set_option(wsproto::message_type(fi.op)); + beast::consuming_buffers cb(sb.data()); + for(;;) + { + using boost::asio::buffer_size; + std::size_t size = std::min(buffer_size(cb)); + if(size > 512) + { + ws.write_frame(false, beast::prepare_buffers(512, cb)); + cb.consume(512); + } + else + { + ws.write_frame(true, cb); + break; + } + } +} +``` + +[endsect] + + + +[section:controlframes Control frames] + +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 `wsproto::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 +of the frames making up the message. The automatic fragment size option +gives callers control over the size of these frames: +``` + ... + ws.set_option(wsproto::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 +`wsproto::socket::close`: +``` + ws.close(); +``` + +[note To receive the `wsproto::error::closed` error, a read operation +is required. ] + +[endsect] + + + +[section:buffers Buffers] + +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`. + +The implementation does not perform queueing or buffering of messages. If +desired, these features should be provided by callers. The impact of this +design is that library users are in full control of the allocation strategy +used to store data and the back-pressure applied on the read and write side +of the underlying TCP/IP connection. + +[endsect] + + +[section:async Asynchronous interface] + +Asynchronous versions are available for all functions: +``` +wsproto::opcode op; +ws.async_read(op, sb, + [](boost::system::error_code const& ec) + { + ... + }); +``` + +Calls to asynchronous initiation functions support the extensible asynchronous +model developed by the Boost.Asio author, allowing for traditional completion +handlers, stackful or stackless coroutines, and even futures: +``` +void echo(wsproto::socket& ws, + boost::asio::yield_context yield) +{ + ws.async_read(sb, yield); + std::future fut = + ws.async_write, sb.data(), boost::use_future); + ... +} +``` + +[endsect] + + + +[section:io_service 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. + +[endsect] + + + +[section:safety Thread Safety] + +Like a regular asio socket, a `wsproto::socket` 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 +stream. This behavior is transparent to callers. + +[endsect] + + + +[endsect] + +[include quickref.xml] diff --git a/src/beast/examples/Jamfile b/src/beast/examples/Jamfile index 327a8182c..10edb82b1 100644 --- a/src/beast/examples/Jamfile +++ b/src/beast/examples/Jamfile @@ -17,3 +17,8 @@ exe http_server : ../beast/http/src/beast_http_nodejs_parser.cpp http_server.cpp ; + +exe wsproto_echo : + ../beast/http/src/beast_http_nodejs_parser.cpp + wsproto_echo.cpp + ; diff --git a/src/beast/examples/wsproto_async_echo_peer.h b/src/beast/examples/wsproto_async_echo_peer.h new file mode 100644 index 000000000..89e51cd88 --- /dev/null +++ b/src/beast/examples/wsproto_async_echo_peer.h @@ -0,0 +1,267 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_ASYNC_ECHO_PEER_H_INCLUDED +#define BEAST_WSPROTO_ASYNC_ECHO_PEER_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { + +// Asynchronous WebSocket echo client/server +// +class async_echo_peer +{ +public: + using error_code = boost::system::error_code; + using endpoint_type = boost::asio::ip::tcp::endpoint; + using address_type = boost::asio::ip::address; + using socket_type = boost::asio::ip::tcp::socket; + +private: + boost::asio::io_service ios_; + socket_type sock_; + boost::asio::ip::tcp::acceptor acceptor_; + std::vector thread_; + +public: + async_echo_peer(bool server, + endpoint_type const& ep, std::size_t threads) + : sock_(ios_) + , acceptor_(ios_) + { + if(server) + { + error_code ec; + acceptor_.open(ep.protocol(), ec); + maybe_throw(ec, "open"); + acceptor_.bind(ep, ec); + maybe_throw(ec, "bind"); + acceptor_.listen( + boost::asio::socket_base::max_connections, ec); + maybe_throw(ec, "listen"); + acceptor_.async_accept(sock_, + std::bind(&async_echo_peer::on_accept, this, + beast::asio::placeholders::error)); + } + else + { + Peer{std::move(sock_), ep}; + } + thread_.reserve(threads); + for(std::size_t i = 0; i < threads; ++i) + thread_.emplace_back( + [&]{ ios_.run(); }); + } + + ~async_echo_peer() + { + error_code ec; + ios_.dispatch( + [&]{ acceptor_.close(ec); }); + for(auto& t : thread_) + t.join(); + } + +private: + class Peer + { + struct data + { + int state = 0; + boost::optional ep; + wsproto::socket ws; + wsproto::opcode op; + beast::streambuf sb; + int id; + + data(socket_type&& sock_) + : ws(std::move(sock_)) + , id([] + { + static int n = 0; + return ++n; + }()) + { + } + + data(socket_type&& sock_, + endpoint_type const& ep_) + : ep(ep_) + , ws(std::move(sock_)) + , id([] + { + static int n = 0; + return ++n; + }()) + { + } + }; + + std::shared_ptr d_; + + public: + Peer(Peer&&) = default; + Peer(Peer const&) = default; + Peer& operator=(Peer&&) = delete; + Peer& operator=(Peer const&) = delete; + + struct identity + { + template + void + operator()(http::message& req) + { + req.headers.replace("User-Agent", "async_echo_client"); + } + + template + void + operator()(http::message& resp) + { + resp.headers.replace("Server", "async_echo_server"); + } + }; + + template + explicit + Peer(socket_type&& sock, Args&&... args) + : d_(std::make_shared( + std::forward(sock), + std::forward(args)...)) + { + auto& d = *d_; + d.ws.set_option(decorate(identity{})); + d.ws.set_option(read_message_max(64 * 1024 * 1024)); + run(); + } + + void run() + { + auto& d = *d_; + if(! d.ep) + { + d.ws.async_accept(std::move(*this)); + } + else + { + d.state = 4; + d.ws.next_layer().async_connect( + *d.ep, std::move(*this)); + } + } + + void operator()(error_code ec) + { + auto& d = *d_; + switch(d_->state) + { + // did accept + case 0: + if(ec) + return fail(ec, "async_accept"); + + // start + case 1: + if(ec) + return fail(ec, "async_handshake"); + d.sb.consume(d.sb.size()); + // read message + d.state = 2; + d.ws.async_read(d.op, d.sb, std::move(*this)); + return; + + // got message + case 2: + if(ec == wsproto::error::closed) + return; + if(ec) + return fail(ec, "async_read"); + // write message + d.state = 1; + d.ws.set_option(wsproto::message_type(d.op)); + d.ws.async_write(d.sb.data(), std::move(*this)); + return; + + // connected + case 4: + if(ec) + return fail(ec, "async_connect"); + d.state = 1; + d.ws.async_handshake( + d.ep->address().to_string() + ":" + + std::to_string(d.ep->port()), + "/", std::move(*this)); + return; + } + } + + private: + void + fail(error_code ec, std::string what) + { + if(ec != wsproto::error::closed) + std::cerr << "#" << d_->id << " " << + what << ": " << ec.message() << std::endl; + } + }; + + void + fail(error_code ec, std::string what) + { + std::cerr << + what << ": " << ec.message() << std::endl; + } + + void + maybe_throw(error_code ec, std::string what) + { + if(ec) + { + fail(ec, what); + throw ec; + } + } + + void + on_accept(error_code ec) + { + if(! acceptor_.is_open()) + return; + maybe_throw(ec, "accept"); + socket_type sock(std::move(sock_)); + acceptor_.async_accept(sock_, + std::bind(&async_echo_peer::on_accept, this, + beast::asio::placeholders::error)); + Peer{std::move(sock)}; + } +}; + +} // wsproto +} // beast + +#endif diff --git a/src/beast/examples/wsproto_echo.cpp b/src/beast/examples/wsproto_echo.cpp new file mode 100644 index 000000000..2aa9da0cc --- /dev/null +++ b/src/beast/examples/wsproto_echo.cpp @@ -0,0 +1,53 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include "wsproto_async_echo_peer.h" +#include "wsproto_sync_echo_peer.h" +#include +#include +#include + +int main() +{ + using endpoint_type = boost::asio::ip::tcp::endpoint; + using address_type = boost::asio::ip::address; + + beast::wsproto::async_echo_peer s1(true, endpoint_type{ + address_type::from_string("127.0.0.1"), 6000 }, 4); + + beast::wsproto::sync_echo_peer s2(true, endpoint_type{ + address_type::from_string("127.0.0.1"), 6001 }); + + boost::asio::io_service ios; + boost::asio::signal_set signals( + ios, SIGINT, SIGTERM); + std::mutex m; + bool stop = false; + std::condition_variable cv; + signals.async_wait( + [&](boost::system::error_code const& ec, + int signal_number) + { + std::lock_guard lock(m); + stop = true; + cv.notify_one(); + }); + std::unique_lock lock(m); + cv.wait(lock, [&]{ return stop; }); +} diff --git a/src/beast/examples/wsproto_sync_echo_peer.h b/src/beast/examples/wsproto_sync_echo_peer.h new file mode 100644 index 000000000..37aa3414e --- /dev/null +++ b/src/beast/examples/wsproto_sync_echo_peer.h @@ -0,0 +1,179 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_SYNC_ECHO_PEER_H_INCLUDED +#define BEAST_WSPROTO_SYNC_ECHO_PEER_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { + +// Synchronous WebSocket echo client/server +// +class sync_echo_peer +{ +public: + using error_code = boost::system::error_code; + using endpoint_type = boost::asio::ip::tcp::endpoint; + using address_type = boost::asio::ip::address; + using socket_type = boost::asio::ip::tcp::socket; + +private: + boost::asio::io_service ios_; + socket_type sock_; + boost::asio::ip::tcp::acceptor acceptor_; + std::thread thread_; + +public: + sync_echo_peer(bool server, endpoint_type ep) + : sock_(ios_) + , acceptor_(ios_) + { + error_code ec; + acceptor_.open(ep.protocol(), ec); + maybe_throw(ec, "open"); + acceptor_.bind(ep, ec); + maybe_throw(ec, "bind"); + acceptor_.listen( + boost::asio::socket_base::max_connections, ec); + maybe_throw(ec, "listen"); + acceptor_.async_accept(sock_, + std::bind(&sync_echo_peer::on_accept, this, + beast::asio::placeholders::error)); + thread_ = std::thread{[&]{ ios_.run(); }}; + } + + ~sync_echo_peer() + { + error_code ec; + ios_.dispatch( + [&]{ acceptor_.close(ec); }); + thread_.join(); + } + +private: + static + void + fail(error_code ec, std::string what) + { + std::cerr << + what << ": " << ec.message() << std::endl; + } + + static + void + fail(int id, error_code ec, std::string what) + { + std::cerr << "#" << std::to_string(id) << " " << + what << ": " << ec.message() << std::endl; + } + + static + void + maybe_throw(error_code ec, std::string what) + { + if(ec) + { + fail(ec, what); + throw ec; + } + } + + void + on_accept(error_code ec) + { + if(ec == boost::asio::error::operation_aborted) + return; + maybe_throw(ec, "accept"); + static int id_ = 0; + std::thread{ + [ + id = ++id_, + this, + sock = std::move(sock_), + work = boost::asio::io_service::work{ios_} + ]() mutable + { + do_peer(id, std::move(sock)); + }}.detach(); + acceptor_.async_accept(sock_, + std::bind(&sync_echo_peer::on_accept, this, + beast::asio::placeholders::error)); + } + + struct identity + { + template + void + operator()(http::message& req) + { + req.headers.replace("User-Agent", "sync_echo_client"); + } + + template + void + operator()(http::message& resp) + { + resp.headers.replace("Server", "sync_echo_server"); + } + }; + + void + do_peer(int id, socket_type&& sock) + { + wsproto::socket ws(std::move(sock)); + ws.set_option(decorate(identity{})); + ws.set_option(read_message_max(64 * 1024 * 1024)); + error_code ec; + ws.accept(ec); + if(ec) + { + fail(id, ec, "accept"); + return; + } + for(;;) + { + wsproto::opcode op; + beast::streambuf sb; + ws.read(op, sb, ec); + if(ec) + break; + ws.set_option(wsproto::message_type(op)); + ws.write(sb.data(), ec); + if(ec) + break; + } + if(ec && ec != wsproto::error::closed) + { + fail(id, ec, "read"); + } + } +}; + +} // wsproto +} // beast + +#endif diff --git a/src/beast/test/asio/Jamfile b/src/beast/test/Jamfile similarity index 85% rename from src/beast/test/asio/Jamfile rename to src/beast/test/Jamfile index 57ec7cec8..e407b93a0 100644 --- a/src/beast/test/asio/Jamfile +++ b/src/beast/test/Jamfile @@ -7,14 +7,14 @@ import os ; -path-constant main : ../../beast/unit_test/src/main.cpp ; +path-constant test_main : ../beast/unit_test/src/main.cpp ; unit-test all : append_buffers.cpp asio.cpp async_completion.cpp basic_streambuf.cpp - ../basic_headers.cpp + basic_headers.cpp bind_handler.cpp buffers_adapter.cpp buffers_debug.cpp @@ -26,5 +26,5 @@ unit-test all : streambuf.cpp streambuf_readstream.cpp type_check.cpp - $(main) + $(test_main) ; diff --git a/src/beast/test/asio/append_buffers.cpp b/src/beast/test/append_buffers.cpp similarity index 100% rename from src/beast/test/asio/append_buffers.cpp rename to src/beast/test/append_buffers.cpp diff --git a/src/beast/test/asio/asio.cpp b/src/beast/test/asio.cpp similarity index 100% rename from src/beast/test/asio/asio.cpp rename to src/beast/test/asio.cpp diff --git a/src/beast/test/asio/async_completion.cpp b/src/beast/test/async_completion.cpp similarity index 100% rename from src/beast/test/asio/async_completion.cpp rename to src/beast/test/async_completion.cpp diff --git a/src/beast/test/asio/basic_streambuf.cpp b/src/beast/test/basic_streambuf.cpp similarity index 100% rename from src/beast/test/asio/basic_streambuf.cpp rename to src/beast/test/basic_streambuf.cpp diff --git a/src/beast/test/beast_wsproto_ws_test.cpp b/src/beast/test/beast_wsproto_ws_test.cpp new file mode 100644 index 000000000..8bddcd57a --- /dev/null +++ b/src/beast/test/beast_wsproto_ws_test.cpp @@ -0,0 +1,358 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { + +class ws_test : public unit_test::suite +{ +public: + using error_code = boost::system::error_code; + using endpoint_type = boost::asio::ip::tcp::endpoint; + using address_type = boost::asio::ip::address; + using socket_type = boost::asio::ip::tcp::socket; + using yield_context = boost::asio::yield_context; + + endpoint_type ep_; + + //-------------------------------------------------------------------------- + + // opcodes for creating the test plans + + // concurrent read and write + struct case_1{}; + + // write a bad frame and shut down + struct case_2{}; + + //-------------------------------------------------------------------------- + + class coro_peer + { + error_code ec_; + boost::asio::io_service ios_; + boost::asio::ip::tcp::acceptor acceptor_; + socket_type sock_; + socket ws_; + opcode::value op_; + beast::streambuf rb_; + beast::streambuf wb_; + yield_context* yield_; + int state_ = 0; + //unit_test::suite& test_; + + public: + coro_peer(coro_peer&&) = default; + coro_peer(coro_peer const&) = delete; + coro_peer& operator=(coro_peer&&) = delete; + coro_peer& operator=(coro_peer const&) = delete; + + template + coro_peer(bool server, endpoint_type ep, + unit_test::suite& test, Ops const&... ops) + : acceptor_(ios_) + , sock_(ios_) + , ws_(sock_) + //, test_(test) + { + if(server) + { + acceptor_.open(ep.protocol()); + acceptor_.bind(ep); + acceptor_.listen( + boost::asio::socket_base::max_connections); + boost::asio::spawn(ios_, + [=](auto yield) + { + yield_ = &yield; + state_ = 10; + acceptor_.async_accept(sock_, (*yield_)[ec_]); + if(ec_) + return this->fail("accept"); + state_ = 20; + ws_.async_accept((*yield_)[ec_]); + if(ec_) + return this->fail("ws.accept"); + this->invoke(ops...); + state_ = -1; + }); + } + else + { + boost::asio::spawn(ios_, + [=](auto yield) + { + yield_ = &yield; + state_ = 30; + sock_.async_connect(ep, (*yield_)[ec_]); + if(ec_) + return this->fail("connect"); + state_ = 40; + ws_.async_handshake(ep.address().to_string() + + std::to_string(ep.port()), "/", (*yield_)[ec_]); + if(ec_) + return this->fail("handshake"); + this->invoke(ops...); + state_ = -1; + }); + } + } + + ~coro_peer() + { + } + + int + state() const + { + return state_; + } + + void + run_one() + { + ios_.run_one(); + } + + void + step_to(int to = 0) + { + while(state_ != to) + ios_.run_one(); + } + + private: + template + void fail(String const& s) + { + } + + void invoke_1(case_1) + { + async_read(ws_, op_, rb_, + [&](auto ec) + { + if(ec) + return this->fail(ec); + rb_.consume(rb_.size()); + }); + state_ = 100; + async_write(ws_, opcode::text, + boost::asio::null_buffers{}, (*yield_)[ec_]); + if(ec_) + return fail("write"); + } + + void invoke_1(case_2) + { + detail::frame_header fh; + fh.op = opcode::rsv5; // bad opcode + fh.fin = true; + fh.mask = true; + fh.rsv1 = false; + fh.rsv2 = false; + fh.rsv3 = false; + fh.len = 0; + fh.key = 0; + detail::write(wb_, fh); + state_ = 200; + boost::asio::async_write( + ws_.next_layer(), wb_.data(), + (*yield_)[ec_]); + if(ec_) + return fail("write"); + ws_.next_layer().shutdown( + socket_type::shutdown_both, ec_); + if(ec_) + return fail("shutdown"); + } + + inline + void + invoke() + { + } + + template + inline + void + invoke(Op op, Ops const&... ops) + { + invoke_1(op); + invoke(ops...); + } + }; + + void + testInvokable() + { + endpoint_type const ep{ + address_type::from_string( + "127.0.0.1"), 6000}; + coro_peer server(true, ep, *this, case_1{}); + coro_peer client(false, ep, *this, case_2{}); + server.step_to(10); // async_accept + client.step_to(30); // async_connect + server.step_to(20); // async_accept(ws) + client.step_to(40); // async_handshake + server.step_to(100); // case_1 + client.step_to(200); // case_2 + client.step_to(-1); + server.step_to(-1); + } + + //-------------------------------------------------------------------------- + + void + maybe_fail(error_code const& ec, std::string const& what) + { + expect(! ec, what + ": " + ec.message()); + } + + void + maybe_throw(error_code ec, std::string what) + { + if(ec) + { + maybe_fail(ec, what); + throw ec; + } + } + + template + static + std::string + buffers_to_string(Buffers const& bs) + { + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + std::string s; + s.reserve(buffer_size(bs)); + for(auto const& b : bs) + s.append(buffer_cast(b), + buffer_size(b)); + for(auto i = s.size(); i-- > 0;) + if(s[i] == '\r') + s.replace(i, 1, "\\r"); + else if(s[i] == '\n') + s.replace(i, 1, "\\n\n"); + return s; + } + + int + makeRequest(endpoint_type ep, std::string const& s) + { + using boost::asio::buffer; + boost::asio::io_service ios; + boost::asio::ip::tcp::socket sock(ios); + sock.connect(ep); + write(sock, append_buffers( + buffer(s), buffer("\r\n"))); + + using namespace http; + parsed_response m; + streambuf sb; + read(sock, sb, m); + return m.status; + } + + void + expectStatus(endpoint_type ep, + int status, std::string const& s) + { + expect(makeRequest(ep, s) == status); + } + + void + testHandshake(endpoint_type ep) + { + expectStatus(ep, 400, "GET / HTTP/1.0\r\n"); + } + + void + syncEchoClient(endpoint_type ep) + { + using boost::asio::buffer; + error_code ec; + boost::asio::io_service ios; + wsproto::socket ws(ios); + ws.next_layer().connect(ep, ec); + maybe_fail(ec, "connect"); + ws.handshake(ep.address().to_string(), "/", ec); + maybe_fail(ec, "upgrade"); + std::string const s = "Hello, world!"; + ws.write(wsproto::opcode::text, true, buffer(s), ec); + maybe_fail(ec, "write"); + boost::asio::streambuf sb; + wsproto::opcode::value op; + read(ws, op, sb, ec); + maybe_fail(ec, "read"); + if(! ec) + expect(op == wsproto::opcode::text); + expect(buffers_to_string(sb.data()) == s); + sb.consume(sb.size()); + ws.close({}, ec); + maybe_fail(ec, "close"); + while(! ec) + { + read(ws, op, sb, ec); + if(! ec) + sb.consume(sb.size()); + } + if(ec != error::closed) + maybe_fail(ec, "teardown"); + } + + void + run() override + { + //testInvokable(); + +#if 0 + { + endpoint_type ep{ + address_type::from_string("127.0.0.1"), 6000}; + testcase("Echo Server"); + test::sync_echo_peer s(true, ep, *this); + testHandshake(ep); + syncEchoClient(ep); + } +#endif + { + endpoint_type ep{ + address_type::from_string("127.0.0.1"), 6001}; + testcase("Async Echo Server"); + test::async_echo_peer s(true, ep, *this); + //testHandshake(ep); + syncEchoClient(ep); + } + } +}; + +BEAST_DEFINE_TESTSUITE(ws,asio,beast); + +} // wsproto +} // beast diff --git a/src/beast/test/asio/bind_handler.cpp b/src/beast/test/bind_handler.cpp similarity index 100% rename from src/beast/test/asio/bind_handler.cpp rename to src/beast/test/bind_handler.cpp diff --git a/src/beast/test/asio/buffers_adapter.cpp b/src/beast/test/buffers_adapter.cpp similarity index 100% rename from src/beast/test/asio/buffers_adapter.cpp rename to src/beast/test/buffers_adapter.cpp diff --git a/src/beast/test/asio/buffers_debug.cpp b/src/beast/test/buffers_debug.cpp similarity index 100% rename from src/beast/test/asio/buffers_debug.cpp rename to src/beast/test/buffers_debug.cpp diff --git a/src/beast/test/asio/consuming_buffers.cpp b/src/beast/test/consuming_buffers.cpp similarity index 100% rename from src/beast/test/asio/consuming_buffers.cpp rename to src/beast/test/consuming_buffers.cpp diff --git a/src/beast/test/asio/handler_alloc.cpp b/src/beast/test/handler_alloc.cpp similarity index 100% rename from src/beast/test/asio/handler_alloc.cpp rename to src/beast/test/handler_alloc.cpp diff --git a/src/beast/test/asio/placeholders.cpp b/src/beast/test/placeholders.cpp similarity index 100% rename from src/beast/test/asio/placeholders.cpp rename to src/beast/test/placeholders.cpp diff --git a/src/beast/test/asio/prepare_buffers.cpp b/src/beast/test/prepare_buffers.cpp similarity index 100% rename from src/beast/test/asio/prepare_buffers.cpp rename to src/beast/test/prepare_buffers.cpp diff --git a/src/beast/test/ssl_error.cpp b/src/beast/test/ssl_error.cpp new file mode 100644 index 000000000..d750c5de7 --- /dev/null +++ b/src/beast/test/ssl_error.cpp @@ -0,0 +1,43 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include + +namespace beast { + +class ssl_error_test : public unit_test::suite +{ +public: + void run() + { + { + boost::system::error_code ec = + boost::system::error_code (335544539, + boost::asio::error::get_ssl_category ()); + std::string const s = beast::error_message_with_ssl(ec); + expect(s == " (20,0,219) error:140000DB:SSL routines:SSL routines:short read"); + } + } +}; + +BEAST_DEFINE_TESTSUITE(ssl_error,asio,beast); + +} // beast diff --git a/src/beast/test/asio/static_streambuf.cpp b/src/beast/test/static_streambuf.cpp similarity index 100% rename from src/beast/test/asio/static_streambuf.cpp rename to src/beast/test/static_streambuf.cpp diff --git a/src/beast/test/asio/streambuf.cpp b/src/beast/test/streambuf.cpp similarity index 97% rename from src/beast/test/asio/streambuf.cpp rename to src/beast/test/streambuf.cpp index 3e5e9c62f..248771940 100644 --- a/src/beast/test/asio/streambuf.cpp +++ b/src/beast/test/streambuf.cpp @@ -67,7 +67,7 @@ public: std::integral_constant; using propagate_on_container_swap = std::integral_constant; - + template struct rebind { @@ -122,7 +122,7 @@ public: ::operator delete(p); } - std::size_t + std::size_t id() const { return id_; @@ -274,11 +274,6 @@ public: sb_type sb3(sb, alloc_type{}); //expect(sb3.get_allocator().id() == 3); } - { - using alloc_type = - test_allocator; - using sb_type = basic_streambuf; - } } void run() override diff --git a/src/beast/test/asio/streambuf_readstream.cpp b/src/beast/test/streambuf_readstream.cpp similarity index 100% rename from src/beast/test/asio/streambuf_readstream.cpp rename to src/beast/test/streambuf_readstream.cpp diff --git a/src/beast/test/asio/temp_buffer.cpp b/src/beast/test/temp_buffer.cpp similarity index 100% rename from src/beast/test/asio/temp_buffer.cpp rename to src/beast/test/temp_buffer.cpp diff --git a/src/beast/test/asio/type_check.cpp b/src/beast/test/type_check.cpp similarity index 100% rename from src/beast/test/asio/type_check.cpp rename to src/beast/test/type_check.cpp diff --git a/src/ripple/ledger/tests/View_test.cpp b/src/ripple/ledger/tests/View_test.cpp index 547e2389d..cb70efcdf 100644 --- a/src/ripple/ledger/tests/View_test.cpp +++ b/src/ripple/ledger/tests/View_test.cpp @@ -698,7 +698,7 @@ class View_test auto cfg = std::make_unique(); setupConfigForUnitTests(*cfg); - for (auto const sectionName : {"port_peer", "port_http", "port_ws"}) + for (auto const sectionName : {"port_peer", "port_rpc", "port_ws"}) { Section& s = (*cfg)[sectionName]; auto const port = s.get("port"); diff --git a/src/ripple/server/Handler.h b/src/ripple/server/Handler.h index 4dd6196b3..d40c2e689 100644 --- a/src/ripple/server/Handler.h +++ b/src/ripple/server/Handler.h @@ -21,6 +21,7 @@ #define RIPPLE_SERVER_HANDLER_H_INCLUDED #include +#include #include #include #include @@ -83,6 +84,20 @@ struct Handler /** Called when the server has finished its stop. */ virtual void onStopped (Server& server) = 0; + + // + // WebSockets + // + + /** Called on a WebSocket Upgrade request. */ + + + /** Called for each complete WebSocket message. */ + virtual + void + onWSMessage(std::shared_ptr session, + std::vector const& buffers) = 0; + }; } // ripple diff --git a/src/ripple/server/Server.h b/src/ripple/server/Server.h index 1d3f2e26f..fc52298c7 100644 --- a/src/ripple/server/Server.h +++ b/src/ripple/server/Server.h @@ -26,7 +26,12 @@ namespace ripple { -/** Multi-threaded, asynchronous HTTP server. */ +/** A multi-protocol server. + + This server maintains multiple configured listening ports, + with each listening port allows for multiple protocols including + HTTP, HTTP/S, WebSocket, Secure WebSocket, and the Peer protocol. +*/ class Server { public: diff --git a/src/ripple/server/Session.h b/src/ripple/server/Session.h index f8065d09c..eada30044 100644 --- a/src/ripple/server/Session.h +++ b/src/ripple/server/Session.h @@ -21,6 +21,7 @@ #define RIPPLE_SERVER_SESSION_H_INCLUDED #include +#include #include #include #include @@ -130,6 +131,11 @@ public: virtual void close (bool graceful) = 0; + + /** Convert the connection to WebSocket. */ + virtual + std::shared_ptr + websocketUpgrade() = 0; }; } // ripple diff --git a/src/ripple/server/WSSession.h b/src/ripple/server/WSSession.h new file mode 100644 index 000000000..36d1c6fa7 --- /dev/null +++ b/src/ripple/server/WSSession.h @@ -0,0 +1,134 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_SERVER_WSSESSION_H_INCLUDED +#define RIPPLE_SERVER_WSSESSION_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ripple { + +class WSMsg +{ +public: + WSMsg() = default; + WSMsg(WSMsg const&) = delete; + WSMsg& operator=(WSMsg const&) = delete; + virtual ~WSMsg() = default; + + /** Retrieve message data. + + Returns a tribool indicating whether or not + data is available, and a ConstBufferSequence + representing the data. + + tribool values: + maybe: Data is not ready yet + false: Data is available + true: Data is available, and + it is the last chunk of bytes. + + Derived classes that do not know when the data + ends (for example, when returning the output of a + paged database query) may return `true` and an + empty vector. + */ + virtual + std::pair> + prepare(std::size_t bytes, + std::function resume) = 0; +}; + +template +class StreambufWSMsg : public WSMsg +{ + Streambuf sb_; + std::size_t n_ = 0; + +public: + StreambufWSMsg(Streambuf&& sb) + : sb_(std::move(sb)) + { + } + + std::pair> + prepare(std::size_t bytes, + std::function) override + { + if(sb_.size() == 0) + return { true, {} }; + sb_.consume(n_); + boost::tribool done; + // VFALCO TODO respect `bytes` fully + if(bytes < sb_.size()) + { + n_ = bytes; + done = boost::indeterminate; + } + else + { + n_ = sb_.size(); + done = true; + } + std::vector vb; + auto const& data = sb_.data(); + vb.reserve(std::distance( + data.begin(), data.end())); + std::copy(data.begin(), data.end(), + std::back_inserter(vb)); + return { done, vb }; + } +}; + +struct WSSession +{ + std::shared_ptr appDefined; + + virtual + Port const& + port() const = 0; + + virtual + boost::asio::ip::tcp::endpoint const& + remote_endpoint() const = 0; + + /** Send a WebSockets message. */ + virtual + void + send(std::shared_ptr w) = 0; + + virtual + void + close() = 0; +}; + +} // ripple + +#endif diff --git a/src/ripple/server/Writer.h b/src/ripple/server/Writer.h index 1673f4e60..0af810611 100644 --- a/src/ripple/server/Writer.h +++ b/src/ripple/server/Writer.h @@ -36,7 +36,10 @@ public: bool complete() = 0; - /** Removes bytes from the input sequence. */ + /** Removes bytes from the input sequence. + + Can be called with 0. + */ virtual void consume (std::size_t bytes) = 0; diff --git a/src/ripple/server/impl/BasePeer.h b/src/ripple/server/impl/BasePeer.h new file mode 100644 index 000000000..b8ee642be --- /dev/null +++ b/src/ripple/server/impl/BasePeer.h @@ -0,0 +1,128 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_SERVER_BASEPEER_H_INCLUDED +#define RIPPLE_SERVER_BASEPEER_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ripple { + +// Common part of all peers +template +class BasePeer + : public io_list::work +{ +protected: + using clock_type = std::chrono::system_clock; + using error_code = boost::system::error_code; + using endpoint_type = boost::asio::ip::tcp::endpoint; + using waitable_timer = boost::asio::basic_waitable_timer ; + + Port const& port_; + Handler& handler_; + endpoint_type remote_address_; + beast::WrappedSink sink_; + beast::Journal j_; + + boost::asio::io_service::work work_; + boost::asio::io_service::strand strand_; + error_code ec_; + +public: + BasePeer(Port const& port, Handler& handler, + endpoint_type remote_address, + boost::asio::io_service& io_service, + beast::Journal journal); + + void + close() override; + +protected: + template + void + fail(error_code ec, String const& what); + +private: + Impl& + impl() + { + return *static_cast(this); + } +}; + +//------------------------------------------------------------------------------ + +template +BasePeer::BasePeer(Port const& port, Handler& handler, + endpoint_type remote_address, + boost::asio::io_service& io_service, + beast::Journal journal) + : port_(port) + , handler_(handler) + , remote_address_(remote_address) + , sink_(journal.sink(), + [] + { + static int id = 0; + return "##" + std::to_string(++id) + " "; + }()) + , j_(sink_) + , work_(io_service) + , strand_(io_service) +{ +} + +template +void +BasePeer::close() +{ + if (! strand_.running_in_this_thread()) + return strand_.post(std::bind( + &BasePeer::close, impl().shared_from_this())); + error_code ec; + impl().ws_.lowest_layer().close(ec); +} + +template +template +void +BasePeer::fail(error_code ec, String const& what) +{ + assert(strand_.running_in_this_thread()); + if(! ec_ && + ec != boost::asio::error::operation_aborted) + { + ec_ = ec; + JLOG(j_.trace()) << + what << ": " << ec.message(); + impl().ws_.lowest_layer().close(ec); + } +} + +} // ripple + +#endif diff --git a/src/ripple/server/impl/BaseWSPeer.h b/src/ripple/server/impl/BaseWSPeer.h new file mode 100644 index 000000000..031aa137d --- /dev/null +++ b/src/ripple/server/impl/BaseWSPeer.h @@ -0,0 +1,301 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_SERVER_BASEWSPEER_H_INCLUDED +#define RIPPLE_SERVER_BASEWSPEER_H_INCLUDED + +#include +#include +#include +#include +#include +#include + +namespace ripple { + +/** Represents an active WebSocket connection. */ +template +class BaseWSPeer + : public BasePeer + , public WSSession +{ +protected: + using clock_type = std::chrono::system_clock; + using error_code = boost::system::error_code; + using endpoint_type = boost::asio::ip::tcp::endpoint; + using waitable_timer = boost::asio::basic_waitable_timer ; + using BasePeer::fail; + using BasePeer::strand_; + +private: + friend class BasePeer; + + http_request_type request_; + beast::wsproto::opcode op_; + beast::streambuf rb_; + beast::streambuf wb_; + std::list> wq_; + bool do_close_ = false; + +public: + template + BaseWSPeer( + Port const& port, + Handler& handler, + endpoint_type remote_address, + beast::http::message&& request, + boost::asio::io_service& io_service, + beast::Journal journal); + + void + run(); + + // + // WSSession + // + + Port const& + port() const override + { + return this->port_; + } + + boost::asio::ip::tcp::endpoint const& + remote_endpoint() const override + { + return this->remote_address_; + } + + void + send(std::shared_ptr w) override; + + void + close() override; + +protected: + struct identity + { + template + void + operator()(beast::http::message& req) + { + req.headers.replace("User-Agent", + BuildInfo::getFullVersionString()); + } + + template + void + operator()(beast::http::message& resp) + { + resp.headers.replace("Server", + BuildInfo::getFullVersionString()); + } + }; + + Impl& + impl() + { + return *static_cast(this); + } + + void + on_write_sb(error_code const& ec); + + void + do_write(); + + void + on_write(error_code const& ec); + + void + on_write_fin(error_code const& ec); + + void + do_read(); + + void + on_read(error_code const& ec); + + void + on_close(error_code const& ec); + + virtual + void + do_close() = 0; +}; + +//------------------------------------------------------------------------------ + +template +template +BaseWSPeer::BaseWSPeer( + Port const& port, + Handler& handler, + endpoint_type remote_address, + beast::http::message&& request, + boost::asio::io_service& io_service, + beast::Journal journal) + : BasePeer(port, handler, remote_address, + io_service, journal) + , request_(std::move(request)) +{ +} + +template +void +BaseWSPeer::run() +{ + if(! strand_.running_in_this_thread()) + return strand_.post(std::bind( + &BaseWSPeer::run, impl().shared_from_this())); + impl().ws_.set_option(beast::wsproto::decorate(identity{})); + using namespace beast::asio; + impl().ws_.async_accept(request_, strand_.wrap(std::bind( + &BaseWSPeer::on_write_sb, impl().shared_from_this(), + placeholders::error))); +} + +template +void +BaseWSPeer::send(std::shared_ptr w) +{ + if(! strand_.running_in_this_thread()) + return strand_.post(std::bind( + &BaseWSPeer::send, impl().shared_from_this(), + std::move(w))); + wq_.emplace_back(std::move(w)); + if(wq_.size() == 1) + on_write({}); +} + +template +void +BaseWSPeer::close() +{ + if(! strand_.running_in_this_thread()) + return strand_.post(std::bind( + &BaseWSPeer::close, impl().shared_from_this())); + if(wq_.size() > 0) + do_close_ = true; + else + impl().ws_.async_close({}, strand_.wrap(std::bind( + &BaseWSPeer::on_close, impl().shared_from_this(), + beast::asio::placeholders::error))); +} + +template +void +BaseWSPeer::on_write_sb(error_code const& ec) +{ + if(ec) + return fail(ec, "write_resp"); + do_read(); +} + +template +void +BaseWSPeer::do_write() +{ + if(! strand_.running_in_this_thread()) + return strand_.post(std::bind( + &BaseWSPeer::do_write, impl().shared_from_this())); + on_write({}); +} + +template +void +BaseWSPeer::on_write(error_code const& ec) +{ + if(ec) + return fail(ec, "write"); + auto& w = *wq_.front(); + using namespace beast::asio; + auto const result = w.prepare(65536, + std::bind(&BaseWSPeer::do_write, + impl().shared_from_this())); + if(boost::indeterminate(result.first)) + return; + if(! result.first) + impl().ws_.async_write_frame( + result.first, result.second, strand_.wrap(std::bind( + &BaseWSPeer::on_write, impl().shared_from_this(), + placeholders::error))); + else + impl().ws_.async_write_frame( + result.first, result.second, strand_.wrap(std::bind( + &BaseWSPeer::on_write_fin, impl().shared_from_this(), + placeholders::error))); +} + +template +void +BaseWSPeer::on_write_fin(error_code const& ec) +{ + if(ec) + return fail(ec, "write_fin"); + wq_.pop_front(); + if(do_close_) + impl().ws_.async_close({}, strand_.wrap(std::bind( + &BaseWSPeer::on_close, impl().shared_from_this(), + beast::asio::placeholders::error))); + else if(! wq_.empty()) + on_write({}); +} + +template +void +BaseWSPeer::do_read() +{ + if(! strand_.running_in_this_thread()) + return strand_.post(std::bind( + &BaseWSPeer::do_read, impl().shared_from_this())); + using namespace beast::asio; + impl().ws_.async_read(op_, rb_, strand_.wrap( + std::bind(&BaseWSPeer::on_read, + impl().shared_from_this(), placeholders::error))); +} + +template +void +BaseWSPeer::on_read(error_code const& ec) +{ + if(ec == beast::wsproto::error::closed) + return do_close(); + if(ec) + return fail(ec, "read"); + auto const& data = rb_.data(); + std::vector b; + b.reserve(std::distance(data.begin(), data.end())); + std::copy(data.begin(), data.end(), + std::back_inserter(b)); + this->handler_.onWSMessage(impl().shared_from_this(), b); + rb_.consume(rb_.size()); + do_read(); +} + +template +void +BaseWSPeer::on_close(error_code const& ec) +{ + // great +} + +} // ripple + +#endif diff --git a/src/ripple/server/impl/Door.cpp b/src/ripple/server/impl/Door.cpp index 2bee4b2a5..eedc58a50 100644 --- a/src/ripple/server/impl/Door.cpp +++ b/src/ripple/server/impl/Door.cpp @@ -170,13 +170,15 @@ Door::Door (Handler& handler, boost::asio::io_service& io_service, , handler_(handler) , acceptor_(io_service) , strand_(io_service) - , ssl_ ( + , ssl_( port_.protocol.count("https") > 0 || //port_.protocol.count("wss") > 0 || - port_.protocol.count("peer") > 0) - , plain_ ( + port_.protocol.count("wss2") > 0 || + port_.protocol.count("peer") > 0) + , plain_( + port_.protocol.count("http") > 0 || //port_.protocol.count("ws") > 0 || - port_.protocol.count("http") > 0) + port_.protocol.count("ws2")) { error_code ec; endpoint_type const local_address = diff --git a/src/ripple/server/impl/PlainHTTPPeer.h b/src/ripple/server/impl/PlainHTTPPeer.h index 2ec1ab6da..7120d0ebc 100644 --- a/src/ripple/server/impl/PlainHTTPPeer.h +++ b/src/ripple/server/impl/PlainHTTPPeer.h @@ -21,6 +21,7 @@ #define RIPPLE_SERVER_PLAINHTTPPEER_H_INCLUDED #include +#include #include namespace ripple { @@ -44,12 +45,15 @@ public: void run(); + std::shared_ptr + websocketUpgrade() override; + private: void - do_request(); + do_request() override; void - do_close(); + do_close() override; }; //------------------------------------------------------------------------------ @@ -71,7 +75,7 @@ PlainHTTPPeer::PlainHTTPPeer (Port const& port, Handler& handler, } void -PlainHTTPPeer::run () +PlainHTTPPeer::run() { if (!handler_.onAccept (session(), remote_address_)) { @@ -88,6 +92,17 @@ PlainHTTPPeer::run () shared_from_this(), std::placeholders::_1)); } +std::shared_ptr +PlainHTTPPeer::websocketUpgrade() +{ + auto ws = ios().emplace( + port_, handler_, remote_address_, + std::move(message_), std::move(stream_), + journal_); + ws->run(); + return ws; +} + void PlainHTTPPeer::do_request() { diff --git a/src/ripple/server/impl/PlainWSPeer.h b/src/ripple/server/impl/PlainWSPeer.h new file mode 100644 index 000000000..87d925034 --- /dev/null +++ b/src/ripple/server/impl/PlainWSPeer.h @@ -0,0 +1,87 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_SERVER_PLAINWSPEER_H_INCLUDED +#define RIPPLE_SERVER_PLAINWSPEER_H_INCLUDED + +#include +#include + +namespace ripple { + +class PlainWSPeer + : public BaseWSPeer + , public std::enable_shared_from_this +{ +private: + friend class BasePeer; + friend class BaseWSPeer; + + using clock_type = std::chrono::system_clock; + using error_code = boost::system::error_code; + using endpoint_type = boost::asio::ip::tcp::endpoint; + using waitable_timer = boost::asio::basic_waitable_timer ; + using socket_type = boost::asio::ip::tcp::socket; + + beast::wsproto::socket ws_; + +public: + template + PlainWSPeer( + Port const& port, + Handler& handler, + endpoint_type remote_address, + beast::http::message&& request, + socket_type&& socket, + beast::Journal journal); + +private: + void + do_close() override; +}; + +//------------------------------------------------------------------------------ + +template +PlainWSPeer::PlainWSPeer( + Port const& port, + Handler& handler, + endpoint_type remote_address, + beast::http::message&& request, + socket_type&& socket, + beast::Journal journal) + : BaseWSPeer(port, handler, remote_address, std::move(request), + socket.get_io_service(), journal) + , ws_(std::move(socket)) +{ +} + +void +PlainWSPeer::do_close() +{ + error_code ec; + auto& sock = ws_.next_layer(); + sock.shutdown(socket_type::shutdown_both, ec); + if(ec) + return fail(ec, "do_close"); +} + +} // ripple + +#endif diff --git a/src/ripple/server/impl/SSLHTTPPeer.h b/src/ripple/server/impl/SSLHTTPPeer.h index 938e4aa92..30cef69df 100644 --- a/src/ripple/server/impl/SSLHTTPPeer.h +++ b/src/ripple/server/impl/SSLHTTPPeer.h @@ -21,6 +21,7 @@ #define RIPPLE_SERVER_SSLHTTPPEER_H_INCLUDED #include +#include #include #include @@ -47,15 +48,18 @@ public: void run(); + std::shared_ptr + websocketUpgrade() override; + private: void do_handshake (yield_context yield); void - do_request(); + do_request() override; void - do_close(); + do_close() override; void on_shutdown (error_code ec); @@ -93,6 +97,17 @@ SSLHTTPPeer::run() shared_from_this(), std::placeholders::_1)); } +std::shared_ptr +SSLHTTPPeer::websocketUpgrade() +{ + auto ws = ios().emplace( + port_, handler_, remote_address_, + std::move(message_), std::move(ssl_bundle_), + journal_); + ws->run(); + return ws; +} + void SSLHTTPPeer::do_handshake (yield_context yield) { diff --git a/src/ripple/server/impl/SSLWSPeer.h b/src/ripple/server/impl/SSLWSPeer.h new file mode 100644 index 000000000..3633606c0 --- /dev/null +++ b/src/ripple/server/impl/SSLWSPeer.h @@ -0,0 +1,106 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_SERVER_SSLWSPEER_H_INCLUDED +#define RIPPLE_SERVER_SSLWSPEER_H_INCLUDED + +#include +#include +#include +#include +#include +#include + +namespace ripple { + +class SSLWSPeer + : public BaseWSPeer + , public std::enable_shared_from_this +{ +private: + friend class BasePeer; + friend class BaseWSPeer; + + using clock_type = std::chrono::system_clock; + using error_code = boost::system::error_code; + using endpoint_type = boost::asio::ip::tcp::endpoint; + using waitable_timer = + boost::asio::basic_waitable_timer ; + + std::unique_ptr ssl_bundle_; + beast::wsproto::socket< + beast::asio::ssl_bundle::stream_type&> ws_; + +public: + template + SSLWSPeer( + Port const& port, + Handler& handler, + endpoint_type remote_endpoint, + beast::http::message&& request, + std::unique_ptr< + beast::asio::ssl_bundle>&& ssl_bundle, + beast::Journal journal); + +private: + void + do_close() override; + + void + on_shutdown(error_code ec); +}; + +//------------------------------------------------------------------------------ + +template +SSLWSPeer::SSLWSPeer( + Port const& port, + Handler& handler, + endpoint_type remote_endpoint, + beast::http::message&& request, + std::unique_ptr< + beast::asio::ssl_bundle>&& ssl_bundle, + beast::Journal journal) + : BaseWSPeer(port, handler, remote_endpoint, std::move(request), + ssl_bundle->socket.get_io_service(), journal) + , ssl_bundle_(std::move(ssl_bundle)) + , ws_(ssl_bundle_->stream) +{ +} + +void +SSLWSPeer::do_close() +{ + //start_timer(); + using namespace beast::asio; + ws_.next_layer().async_shutdown( + strand_.wrap(std::bind(&SSLWSPeer::on_shutdown, + shared_from_this(), placeholders::error))); +} + +void +SSLWSPeer::on_shutdown(error_code ec) +{ + //cancel_timer(); + ws_.lowest_layer().close(ec); +} + +} // ripple + +#endif diff --git a/src/ripple/server/impl/ServerHandlerImp.cpp b/src/ripple/server/impl/ServerHandlerImp.cpp index 668fb8958..8171ed8c9 100644 --- a/src/ripple/server/impl/ServerHandlerImp.cpp +++ b/src/ripple/server/impl/ServerHandlerImp.cpp @@ -19,6 +19,8 @@ #include #include +#include +#include #include #include #include @@ -29,13 +31,14 @@ #include #include #include +#include #include #include #include #include #include -#include #include +#include #include #include #include @@ -120,6 +123,24 @@ ServerHandlerImp::onHandoff (Session& session, boost::asio::ip::tcp::endpoint remote_address) -> Handoff { + if (session.port().protocol.count("wss2") > 0 && + isWebsocketUpgrade (request)) + { + // VFALCO TODO + Resource::Consumer usage; + //if (isUnlimited (role)) + // usage = m_resourceManager.newUnlimitedEndpoint ( + // remoteIPAddress.to_string()); + //else + usage = m_resourceManager.newInboundEndpoint( + beast::IP::from_asio(remote_address)); + auto const ws = session.websocketUpgrade(); + ws->appDefined = std::make_shared( + m_networkOPs, usage, ws); + Handoff handoff; + handoff.moved = true; + return handoff; + } if (session.port().protocol.count("wss") > 0 && isWebsocketUpgrade (request)) { @@ -142,6 +163,24 @@ ServerHandlerImp::onHandoff (Session& session, boost::asio::ip::tcp::endpoint remote_address) -> Handoff { + if (session.port().protocol.count("ws2") > 0 && + isWebsocketUpgrade (request)) + { + // VFALCO TODO + Resource::Consumer usage; + //if (isUnlimited (role)) + // usage = m_resourceManager.newUnlimitedEndpoint ( + // remoteIPAddress.to_string()); + //else + usage = m_resourceManager.newInboundEndpoint( + beast::IP::from_asio(remote_address)); + auto const ws = session.websocketUpgrade(); + ws->appDefined = std::make_shared( + m_networkOPs, usage, ws); + Handoff handoff; + handoff.moved = true; + return handoff; + } if (session.port().protocol.count("ws") > 0 && isWebsocketUpgrade (request)) { @@ -208,6 +247,45 @@ ServerHandlerImp::onRequest (Session& session) }); } +void +ServerHandlerImp::onWSMessage( + std::shared_ptr session, + std::vector const& buffers) +{ + // VFALCO This is inefficient, the JSON + // should be parsed from the buffer sequence. + std::string s; + s.reserve(boost::asio::buffer_size(buffers)); + std::copy(boost::asio::buffers_begin(buffers), + boost::asio::buffers_end(buffers), + std::back_inserter(s)); +//m_journal.error << "Recv: " << s; + Json::Value jv; + // VFALCO should we parse a coroutine instead? + if(! Json::Reader{}.parse(s, jv)) + { + // TODO Send error + return; + } + m_jobQueue.postCoro(jtCLIENT, "WS-Client", + [this, session = std::move(session), + jv = std::move(jv)](auto const& coro) + { + auto const jr = + this->processSession(session, coro, jv); + beast::streambuf sb; +//m_journal.error << "Send: " << to_string(jr); + Json::stream(jr, + [&sb](auto const p, auto const n) + { + sb.commit(boost::asio::buffer_copy( + sb.prepare(n), boost::asio::buffer(p, n))); + }); + session->send(std::make_shared< + StreambufWSMsg>(std::move(sb))); + }); +} + void ServerHandlerImp::onClose (Session& session, boost::system::error_code const&) @@ -224,6 +302,122 @@ ServerHandlerImp::onStopped (Server&) //------------------------------------------------------------------------------ +Json::Value +ServerHandlerImp::processSession( + std::shared_ptr const& session, + std::shared_ptr const& coro, + Json::Value const& jv) +{ + auto is = std::static_pointer_cast (session->appDefined); + /* + if (getConsumer().disconnect ()) + { + disconnect (); + return rpcError (rpcSLOW_DOWN); + } + */ + + // Requests without "command" are invalid. + // + if (!jv.isMember (jss::command)) + { + Json::Value jr (Json::objectValue); + + jr[jss::type] = jss::response; + jr[jss::status] = jss::error; + jr[jss::error] = jss::missingCommand; + jr[jss::request] = jv; + + if (jv.isMember (jss::id)) + { + jr[jss::id] = jv[jss::id]; + } + + /* + getConsumer().charge (Resource::feeInvalidRPC); + */ + + return jr; + } + + Resource::Charge loadType = Resource::feeReferenceRPC; + Json::Value jr (Json::objectValue); + + auto required = RPC::roleRequired (jv[jss::command].asString()); + // VFALCO TODO Get identity/credentials from HTTP headers + std::string const user = ""; + std::string const fwdfor = ""; + auto role = requestRole (required, session->port(), jv, + beast::IP::from_asio(session->remote_endpoint().address()), + user); + + if (Role::FORBID == role) + { + jr[jss::result] = rpcError (rpcFORBIDDEN); + } + else + { + // VFALCO TODO InfoSub parameter in context + RPC::Context context{ + app_.journal ("RPCHandler"), + jv, + app_, + loadType, + app_.getOPs(), + app_.getLedgerMaster(), + is->getConsumer(), + role, + coro, + is, + { user, fwdfor } + }; + RPC::doCommand (context, jr[jss::result]); + } + + /* + getConsumer().charge (loadType); + if (getConsumer().warn ()) + { + jr[jss::warning] = jss::load; + } + */ + + // Currently we will simply unwrap errors returned by the RPC + // API, in the future maybe we can make the responses + // consistent. + // + // Regularize result. This is duplicate code. + if (jr[jss::result].isMember (jss::error)) + { + jr = jr[jss::result]; + jr[jss::status] = jss::error; + jr[jss::request] = jv; + + } + else + { + jr[jss::status] = jss::success; + + // For testing resource limits on this connection. + if (jv[jss::command].asString() == "ping") + { + /* + if (getConsumer().isUnlimited()) + jr[jss::unlimited] = true; + */ + } + } + + if (jv.isMember (jss::id)) + { + jr[jss::id] = jv[jss::id]; + } + + jr[jss::type] = jss::response; + + return jr; +} + template static std::string diff --git a/src/ripple/server/impl/ServerHandlerImp.h b/src/ripple/server/impl/ServerHandlerImp.h index 89fbc1062..9fe2821c8 100644 --- a/src/ripple/server/impl/ServerHandlerImp.h +++ b/src/ripple/server/impl/ServerHandlerImp.h @@ -23,9 +23,13 @@ #include #include #include +#include +#include #include +#include #include #include +#include #include #include #include @@ -33,17 +37,46 @@ namespace ripple { +inline bool operator< (Port const& lhs, Port const& rhs) { return lhs.name < rhs.name; } +class WSInfoSub : public InfoSub +{ + std::weak_ptr ws_; + +public: + WSInfoSub(Source& source, Consumer consumer, + std::shared_ptr const& ws) + : InfoSub(source, consumer) + , ws_(ws) + { + } + + void + send(Json::Value const& jv, bool) + { + auto sp = ws_.lock(); + if(! sp) + return; + beast::streambuf sb; + write(sb, jv); + auto m = std::make_shared< + StreambufWSMsg>( + std::move(sb)); + sp->send(m); + } +}; + // Private implementation class ServerHandlerImp : public ServerHandler , public Handler { private: + Application& app_; Resource::Manager& m_resourceManager; beast::Journal m_journal; @@ -85,7 +118,7 @@ private: onStop() override; // - // HTTP::Handler + // Handler // bool @@ -105,6 +138,10 @@ private: void onRequest (Session& session) override; + void + onWSMessage(std::shared_ptr session, + std::vector const& buffers) override; + void onClose (Session& session, boost::system::error_code const&) override; @@ -114,6 +151,12 @@ private: //-------------------------------------------------------------------------- + Json::Value + processSession( + std::shared_ptr const& session, + std::shared_ptr const& coro, + Json::Value const& jv); + void processSession (std::shared_ptr const&, std::shared_ptr jobCoro); diff --git a/src/ripple/server/tests/Server_test.cpp b/src/ripple/server/tests/Server_test.cpp index d1d6e83ec..409b73959 100644 --- a/src/ripple/server/tests/Server_test.cpp +++ b/src/ripple/server/tests/Server_test.cpp @@ -131,6 +131,12 @@ public: session.close (true); } + void + onWSMessage(std::shared_ptr session, + std::vector const&) override + { + } + void onClose (Session& session, boost::system::error_code const&) override @@ -328,6 +334,12 @@ public: { } + void + onWSMessage(std::shared_ptr session, + std::vector const& buffers) override + { + } + void onClose (Session& session, boost::system::error_code const&) override diff --git a/src/ripple/test/WSClient.h b/src/ripple/test/WSClient.h index 1a8c69535..606380f08 100644 --- a/src/ripple/test/WSClient.h +++ b/src/ripple/test/WSClient.h @@ -49,6 +49,9 @@ public: std::unique_ptr makeWSClient(Config const& cfg); +std::unique_ptr +makeWS2Client(Config const& cfg); + } // test } // ripple diff --git a/src/ripple/test/impl/WSClient.cpp b/src/ripple/test/impl/WSClient.cpp index b51c456cc..9c1b1153f 100644 --- a/src/ripple/test/impl/WSClient.cpp +++ b/src/ripple/test/impl/WSClient.cpp @@ -24,7 +24,9 @@ #include #include #include -#include +#include +#include +#include #include #include @@ -47,89 +49,21 @@ class WSClientImpl : public WSClient } }; - class read_frame_op - { - struct data - { - WSClientImpl& wsc; - wsproto::frame_header fh; - boost::asio::streambuf sb; - - data(WSClientImpl& wsc_) - : wsc(wsc_) - { - } - - ~data() - { - wsc.on_read_done(); - } - }; - - std::shared_ptr d_; - - public: - read_frame_op(read_frame_op const&) = default; - read_frame_op(read_frame_op&&) = default; - - explicit - read_frame_op(WSClientImpl& wsc) - : d_(std::make_shared(wsc)) - { - read_one(); - } - - void read_one() - { - // hack - d_->sb.consume(d_->sb.size()); - d_->wsc.ws_.async_read_fh( - d_->fh, std::move(*this)); - } - - void operator()(error_code const& ec) - { - if(ec) - return d_->wsc.on_read_frame( - ec, d_->fh, 0, d_->sb.data(), - std::move(*this)); - d_->wsc.ws_.async_read(d_->fh, - d_->sb.prepare(d_->fh.len), std::move(*this)); - } - - void operator()(error_code const& ec, - wsproto::frame_header const& fh, - std::size_t bytes_transferred) - { - if(ec) - return d_->wsc.on_read_frame( - ec, d_->fh, 0, d_->sb.data(), - std::move(*this)); - if(d_->fh.mask) - { - // TODO: apply key mask to payload - } - d_->sb.commit(bytes_transferred); - return d_->wsc.on_read_frame( - ec, d_->fh, bytes_transferred, d_->sb.data(), - std::move(*this)); - } - }; - static boost::asio::ip::tcp::endpoint - getEndpoint(BasicConfig const& cfg) + getEndpoint(BasicConfig const& cfg, bool v2) { auto& log = std::cerr; ParsedPort common; parse_Port (common, cfg["server"], log); + auto const ps = v2 ? "ws2" : "ws"; for (auto const& name : cfg.section("server").values()) { if (! cfg.exists(name)) continue; ParsedPort pp; parse_Port(pp, cfg[name], log); - if(pp.protocol.count("ws") == 0) + if(pp.protocol.count(ps) == 0) continue; using boost::asio::ip::address_v4; if(*pp.ip == address_v4{0x00000000}) @@ -145,7 +79,8 @@ class WSClientImpl : public WSClient std::string buffer_string (ConstBuffers const& b) { - using namespace boost::asio; + using boost::asio::buffer; + using boost::asio::buffer_size; std::string s; s.resize(buffer_size(b)); buffer_copy(buffer(&s[0], s.size()), b); @@ -155,57 +90,53 @@ class WSClientImpl : public WSClient boost::asio::io_service ios_; boost::optional< boost::asio::io_service::work> work_; + boost::asio::io_service::strand strand_; std::thread thread_; boost::asio::ip::tcp::socket stream_; - wsproto::basic_socket ws_; + beast::wsproto::socket ws_; + beast::wsproto::opcode op_; + beast::streambuf rb_; // synchronize destructor bool b0_ = false; std::mutex m0_; std::condition_variable cv0_; - // sychronize message queue + // synchronize message queue std::mutex m_; std::condition_variable cv_; std::list> msgs_; public: - explicit - WSClientImpl(Config const& cfg) + WSClientImpl(Config const& cfg, bool v2) : work_(ios_) + , strand_(ios_) , thread_([&]{ ios_.run(); }) , stream_(ios_) , ws_(stream_) { - using namespace boost::asio; - stream_.connect(getEndpoint(cfg)); - error_code ec; - ws_.connect(ec); - if(ec) - Throw(ec); - - read_frame_op{*this}; + auto const ep = getEndpoint(cfg, v2); + stream_.connect(ep); + ws_.handshake(ep.address().to_string() + + ":" + std::to_string(ep.port()), "/"); + ws_.async_read(op_, rb_, + strand_.wrap(std::bind(&WSClientImpl::on_read_msg, + this, beast::asio::placeholders::error))); } ~WSClientImpl() override { + ws_.close({}); stream_.close(); - { - std::unique_lock lock(m0_); - cv0_.wait(lock, [&]{ return b0_; }); - } work_ = boost::none; thread_.join(); - - //stream_.shutdown(boost::asio::ip::tcp::socket::shutdown_both); - //stream_.close(); } Json::Value invoke(std::string const& cmd, Json::Value const& params) override { - using namespace boost::asio; + using boost::asio::buffer; using namespace std::chrono_literals; { @@ -214,7 +145,7 @@ public: jp = params; jp[jss::command] = cmd; auto const s = to_string(jp); - ws_.write(buffer(s)); + ws_.write_frame(true, buffer(s)); } auto jv = findMsg(5s, @@ -291,19 +222,15 @@ public: } private: - template void - on_read_frame(error_code const& ec, - wsproto::frame_header const& fh, - std::size_t bytes_transferred, - ConstBuffers const& b, - read_frame_op&& op) + on_read_msg(error_code const& ec) { - if(bytes_transferred == 0) + if(ec) return; Json::Value jv; Json::Reader jr; - jr.parse(buffer_string(b), jv); + jr.parse(buffer_string(rb_.data()), jv); + rb_.consume(rb_.size()); auto m = std::make_shared( std::move(jv)); { @@ -311,7 +238,9 @@ private: msgs_.push_front(m); cv_.notify_all(); } - op.read_one(); + ws_.async_read(op_, rb_, strand_.wrap( + std::bind(&WSClientImpl::on_read_msg, + this, beast::asio::placeholders::error))); } // Called when the read op terminates @@ -327,7 +256,13 @@ private: std::unique_ptr makeWSClient(Config const& cfg) { - return std::make_unique(cfg); + return std::make_unique(cfg, false); +} + +std::unique_ptr +makeWS2Client(Config const& cfg) +{ + return std::make_unique(cfg, true); } } // test diff --git a/src/ripple/test/impl/WSClient_test.cpp b/src/ripple/test/impl/WSClient_test.cpp index 999165ff5..c4308e971 100644 --- a/src/ripple/test/impl/WSClient_test.cpp +++ b/src/ripple/test/impl/WSClient_test.cpp @@ -21,6 +21,9 @@ #include #include #include +#include + +#include namespace ripple { namespace test { @@ -28,8 +31,19 @@ namespace test { class WSClient_test : public beast::unit_test::suite { public: + void + test_utf8checker() + { + beast::wsproto::detail::utf8_checker utf8c; + + std::uint8_t buffer[] = {0Xff}; + expect(! utf8c.write(buffer, 3)); + } + void run() override { + test_utf8checker(); + using namespace jtx; Env env(*this); auto wsc = makeWSClient(env.app().config()); @@ -37,22 +51,10 @@ public: Json::Value jv; jv["streams"] = Json::arrayValue; jv["streams"].append("ledger"); - //jv["streams"].append("server"); - //jv["streams"].append("transactions"); - //jv["streams"].append("transactions_proposed"); - log << pretty(wsc->invoke("subscribe", jv)); } env.fund(XRP(10000), "alice"); env.close(); - /* - env.fund(XRP(10000), "dan", "eric", "fred"); - env.close(); - env.fund(XRP(10000), "george", "harold", "iris"); - env.close(); - */ auto jv = wsc->getMsg(std::chrono::seconds(1)); - if(jv) - log << pretty(*jv); pass(); } }; diff --git a/src/ripple/test/jtx/impl/Env.cpp b/src/ripple/test/jtx/impl/Env.cpp index 84b7c111e..bf675e693 100644 --- a/src/ripple/test/jtx/impl/Env.cpp +++ b/src/ripple/test/jtx/impl/Env.cpp @@ -69,11 +69,11 @@ setupConfigForUnitTests (Config& cfg) cfg["port_peer"].set("ip", "127.0.0.1"); cfg["port_peer"].set("port", "8080"); cfg["port_peer"].set("protocol", "peer"); - cfg["server"].append("port_http"); - cfg["port_http"].set("ip", "127.0.0.1"); - cfg["port_http"].set("port", "8081"); - cfg["port_http"].set("protocol", "http"); - cfg["port_http"].set("admin", "127.0.0.1"); + cfg["server"].append("port_rpc"); + cfg["port_rpc"].set("ip", "127.0.0.1"); + cfg["port_rpc"].set("port", "8081"); + cfg["port_rpc"].set("protocol", "http,ws2"); + cfg["port_rpc"].set("admin", "127.0.0.1"); cfg["server"].append("port_ws"); cfg["port_ws"].set("ip", "127.0.0.1"); cfg["port_ws"].set("port", "8082"); diff --git a/src/ripple/unity/beast.cpp b/src/ripple/unity/beast.cpp index 9a72231a9..2ca9031b7 100644 --- a/src/ripple/unity/beast.cpp +++ b/src/ripple/unity/beast.cpp @@ -19,17 +19,9 @@ #if ! BEAST_COMPILE_OBJECTIVE_CPP -/* This file includes all of the beast sources needed to link. - By including them here, we avoid having to muck with the SConstruct - Makefile, Project file, or whatever. -*/ - // MUST come first! #include - -// Include this to get all the basic includes included, to prevent errors #include - #include #endif diff --git a/src/ripple/wsproto/basic_socket.h b/src/ripple/wsproto/basic_socket.h deleted file mode 100644 index 7f331fd79..000000000 --- a/src/ripple/wsproto/basic_socket.h +++ /dev/null @@ -1,595 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2016 Ripple Labs Inc. - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ -//============================================================================== - -#ifndef RIPPLE_WSPROTO_BASIC_SOCKET_H_INCLUDED -#define RIPPLE_WSPROTO_BASIC_SOCKET_H_INCLUDED - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -//------------------------------------------------------------------------------ - -#if 0//BOOST_VERSION >= 105800 - -#include -template -Int native_to_big(Int n); -{ - return boost::endian::native_to_big(n); -} - -template -Int big_to_native(Int b) -{ - return boost::endian::big_to_native(b); -} - -#else - -template -Int native_to_big(Int n); - -template -Int big_to_native(Int b); - -template<> -inline -std::uint16_t -native_to_big(std::uint16_t n) -{ - std::uint8_t* p = - reinterpret_cast(&n); - std::swap(p[0], p[1]); - return n; -} - -template<> -inline -std::uint64_t -native_to_big(std::uint64_t n) -{ - return 0; -} - -template<> -inline -std::uint16_t -big_to_native(std::uint16_t b) -{ - std::uint8_t* p = - reinterpret_cast(&b); - std::swap(p[0], p[1]); - return b; -} - -template<> -inline -std::uint64_t -big_to_native(std::uint64_t b) -{ - return 0; -} - -#endif - -//------------------------------------------------------------------------------ - -namespace wsproto { - -struct frame_header -{ - int op; - bool fin; - bool mask; - bool rsv1; - bool rsv2; - bool rsv3; - std::uint64_t len; - std::array key; - - // next offset in key, [0-4) - int offset = 0; -}; - -namespace detail { - -// decode first 2 bytes of frame header -// return: number of additional bytes needed -template -std::size_t -decode_fh1(frame_header& fh, std::uint8_t const* p) -{ - std::size_t need; - fh.len = p[1] & 0x7f; - switch(fh.len) - { - case 126: need = 2; break; - case 127: need = 8; break; - default: - need = 0; - } - if((fh.mask = (p[1] & 0x80))) - need += 4; - fh.op = p[0] & 0x0f; - fh.fin = p[0] & 0x80; - fh.rsv1 = p[0] & 0x40; - fh.rsv2 = p[0] & 0x20; - fh.rsv3 = p[0] & 0x10; - fh.offset = 0; - return need; -} - -// decode remainder of frame header -template -void -decode_fh2(frame_header& fh, std::uint8_t const* p) -{ - switch(fh.len) - { - case 126: - fh.len = - (std::uint64_t(p[0])<<8) + p[1]; - p += 2; - break; - case 127: - fh.len = 0; - for(int i = 0; i < 8; ++i) - fh.len = (fh.len << 8) + p[i]; - p += 8; - break; - default: - break; - } - if(fh.mask) - std::memcpy(fh.key.data(), p, 4); -} - -template< - class StreamBuf, - class ConstBuffers> -void -write_frame_payload(StreamBuf& sb, ConstBuffers const& cb) -{ - using namespace boost::asio; - sb.commit(buffer_copy( - sb.prepare(buffer_size(cb)), cb)); -} - -template< - class StreamBuf, - class ConstBuffers -> -void -write_frame(StreamBuf& sb, ConstBuffers const& cb) -{ - using namespace boost::asio; - int const op = 1; - bool const fin = true; - bool const mask = false; - std::array b; - b[0] = (fin ? 0x80 : 0x00) | op; - b[1] = mask ? 0x80 : 0x00; - auto const len = buffer_size(cb); - if (len <= 125) - { - b[1] |= len; - sb.commit(buffer_copy( - sb.prepare(2), buffer(&b[0], 2))); - } - else if (len <= 65535) - { - b[1] |= 126; - std::uint16_t& d = *reinterpret_cast< - std::uint16_t*>(&b[2]); - d = native_to_big(len); - sb.commit(buffer_copy( - sb.prepare(4), buffer(&b[0], 4))); - } - else - { - b[1] |= 127; - std::uint64_t& d = *reinterpret_cast< - std::uint64_t*>(&b[2]); - d = native_to_big(len); - sb.commit(buffer_copy( - sb.prepare(10), buffer(&b[0], 10))); - } - if(mask) - { - } - write_frame_payload(sb, cb); -} - -//------------------------------------------------------------------------------ - -// establish client connection -template -class connect_op -{ - using error_code = boost::system::error_code; - - struct data - { - Stream& s; - Handler h; - - data(Stream& s_, Handler&& h_) - : s(s_) - , h(std::forward(h_)) - { - } - }; - - std::shared_ptr d_; - -public: - connect_op(Stream& s_, Handler&& h_) - : d_(std::make_shared( - s_, std::forward(h_))) - { - } -}; - -// read a frame header -template -class read_fh_op -{ - using error_code = boost::system::error_code; - - struct data - { - Stream& s; - frame_header& fh; - Handler h; - int state = 0; - std::array buf; - - data(Stream& s_, frame_header& fh_, - Handler&& h_) - : s(s_) - , fh(fh_) - , h(std::forward(h_)) - { - } - }; - - std::shared_ptr d_; - -public: - read_fh_op(read_fh_op&&) = default; - read_fh_op(read_fh_op const&) = default; - - read_fh_op(Stream& s, frame_header& fh, - Handler&& h) - : d_(std::make_shared(s, fh, - std::forward(h))) - { - using namespace boost::asio; - async_read(d_->s, mutable_buffers_1( - d_->buf.data(), 2), std::move(*this)); - } - - void operator()(error_code const& ec, - std::size_t bytes_transferred) - { - using namespace boost::asio; - if(ec == error::operation_aborted) - return; - if(! ec) - { - if(d_->state == 0) - { - d_->state = 1; - async_read(d_->s, mutable_buffers_1( - d_->buf.data(), detail::decode_fh1( - d_->fh, d_->buf.data())), - std::move(*this)); - return; - } - detail::decode_fh2( - d_->fh, d_->buf.data()); - } - return d_->s.get_io_service().wrap( - std::move(d_->h))(ec); - } - - friend - auto asio_handler_allocate( - std::size_t size, read_fh_op* op) - { - return boost_asio_handler_alloc_helpers:: - allocate(size, op->d_->h); - } - - friend - auto asio_handler_deallocate( - void* p, std::size_t size, - read_fh_op* op) - { - return boost_asio_handler_alloc_helpers:: - deallocate(p, size, op->d_->h); - } - - friend - auto asio_handler_is_continuation( - read_fh_op* op) - { - return (op->d_->state != 0) ? true : - boost_asio_handler_cont_helpers:: - is_continuation(op->d_->h); - } - - template - friend - auto asio_handler_invoke( - Function&& f, read_fh_op* op) - { - return boost_asio_handler_invoke_helpers:: - invoke(f, op->d_->h); - } -}; - -// read a frame body -template -struct read_op -{ - using error_code = boost::system::error_code; - - struct data - { - Stream& s; - frame_header fh; - MutableBuffers b; - Handler h; - - data(Stream& s_, frame_header const& fh_, - MutableBuffers const& b_, Handler&& h_) - : s(s_) - , fh(fh_) - , b(b_) - , h(std::forward(h_)) - { - } - }; - - std::shared_ptr d_; - -public: - read_op(read_op&&) = default; - read_op(read_op const&) = default; - - read_op(Stream& s, frame_header const& fh, - MutableBuffers const& b, Handler&& h) - : d_(std::make_shared(s, fh, b, - std::forward(h))) - { - using namespace boost::asio; - async_read(d_->s, d_->b, - std::move(*this)); - } - - void operator()(error_code const& ec, - std::size_t bytes_transferred) - { - using namespace boost::asio; - if(ec == error::operation_aborted) - return; - if(d_->fh.mask) - { - // apply mask key - // adjust d_->fh.offset - } - return d_->s.get_io_service().wrap( - std::move(d_->h))(ec, std::move(d_->fh), - bytes_transferred); - } - - friend - auto asio_handler_allocate( - std::size_t size, read_op* op) - { - return boost_asio_handler_alloc_helpers:: - allocate(size, op->d_->h); - } - - friend - auto asio_handler_deallocate( - void* p, std::size_t size, - read_op* op) - { - return boost_asio_handler_alloc_helpers:: - deallocate(p, size, op->d_->h); - } - - friend - auto asio_handler_is_continuation( - read_op* op) - { - return boost_asio_handler_cont_helpers:: - is_continuation(op->d_->h); - } - - template - friend - auto asio_handler_invoke( - Function&& f, read_op* op) - { - return boost_asio_handler_invoke_helpers:: - invoke(f, op->d_->h); - } -}; - -} // detail - -//------------------------------------------------------------------------------ - -template < - class Stream, - class Allocator = std::allocator -> -class basic_socket -{ -private: - static_assert(! std::is_const::value, ""); - - using error_code = boost::system::error_code; - - Stream s_; - boost::asio::io_service::strand st_; - -public: - using next_layer_type = - std::remove_reference_t; - - using lowest_layer_type = - typename next_layer_type::lowest_layer_type; - - basic_socket(basic_socket&&) = default; - basic_socket(basic_socket const&) = delete; - basic_socket& operator= (basic_socket const&) = delete; - basic_socket& operator= (basic_socket&&) = delete; - - template - explicit - basic_socket(Args&&... args) - : s_(std::forward(args)...) - , st_(s_.get_io_service()) - { - } - - ~basic_socket() - { - } - - boost::asio::io_service& - get_io_service() - { - return s_.lowest_layer().get_io_service(); - } - - next_layer_type& - next_layer() - { - return s_; - } - - next_layer_type const& - next_layer() const - { - return s_; - } - - lowest_layer_type& - lowest_layer() - { - return s_.lowest_layer(); - } - - lowest_layer_type const& - lowest_layer() const - { - return s_.lowest_layer(); - } - -public: - /** Request a WebSocket upgrade. - Requirements: - Stream is connected. - */ - void - connect(error_code& ec) - { - using namespace boost::asio; - boost::asio::write(s_, buffer( - "GET / HTTP/1.1\r\n" - "Host: 127.0.0.1\r\n" - "Upgrade: websocket\r\n" - "Connection: Upgrade\r\n" - "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" - "Sec-WebSocket-Version: 13\r\n" - "\r\n"), ec); - if(ec) - return; - streambuf sb; - read_until(s_, sb, "\r\n\r\n"); - using namespace beast; - deprecated_http::body b; - deprecated_http::message m; - deprecated_http::parser p(m, b, false); - auto const used = p.write(sb.data(), ec); - if (ec || ! p.complete()) - throw std::runtime_error(ec.message()); - sb.consume(used); - } - - // write a text message - template - void - write(ConstBuffers const& cb) - { - boost::asio::streambuf sb; - wsproto::detail::write_frame(sb, cb); - boost::asio::write(s_, sb.data()); - } - -public: - // read a frame header asynchronously - template - void - async_read_fh(frame_header& fh, Handler&& h) - { - static_assert(beast::is_call_possible::value, - "Type does not meet the handler requirements"); - detail::read_fh_op{ - s_, fh, std::forward(h)}; - } - - // read a frame body asynchronously - // requires buffer_size(b) == fh.len - template - void - async_read(frame_header const& fh, - MutableBuffers const& b, Handler&& h) - { - static_assert(beast::is_call_possible::value, - "Type does not meet the handler requirements"); - detail::read_op{ - s_, fh, b, std::forward(h)}; - } -}; - -} // wsproto - -#endif diff --git a/test/ripple-websocket.js b/test/ripple-websocket.js new file mode 100644 index 000000000..387bc78ff --- /dev/null +++ b/test/ripple-websocket.js @@ -0,0 +1,23 @@ +// npm install ws +// WS_ADDRESS=127.0.0.1:6006 node ripple-websocket.js + +var WebSocket = require('ws') + +console.log(process.env.WS_ADDRESS) +var ws = new WebSocket('ws://'+process.env.WS_ADDRESS) + +ws.on('error', function(error){ + console.log(error) +}) + +ws.on('open', function () { + ws.send(JSON.stringify({ + "id": 1, + "command": "server_info" + })) +}) + +ws.on('message', function(dataString, flags) { + var data = JSON.parse(dataString) + console.log(data) +})