From 2cb3834bbbc002bd0e1059474716c69a2d496620 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Thu, 25 Feb 2016 16:17:19 -0500 Subject: [PATCH] Beast.WebSocket: Beast.WebSocket provides developers with a robust WebSocket implementation built on Boost.Asio with a consistent asynchronous model using a modern C++ approach. --- Builds/VisualStudio2015/RippleD.vcxproj | 92 +- .../VisualStudio2015/RippleD.vcxproj.filters | 147 +- SConstruct | 2 + src/beast/Jamroot | 18 +- src/beast/TODO.txt | 8 + src/beast/beast/asio/buffers_debug.h | 1 - .../beast/unity/beast_wsproto_unity.cpp} | 12 +- src/beast/beast/wsproto.h | 30 + src/beast/beast/wsproto/README.md | 232 +++ src/beast/beast/wsproto/detail/debug.h | 88 + src/beast/beast/wsproto/detail/decorator.h | 112 ++ src/beast/beast/wsproto/detail/error.h | 104 + src/beast/beast/wsproto/detail/frame.h | 375 ++++ src/beast/beast/wsproto/detail/hybi13.h | 66 + src/beast/beast/wsproto/detail/invokable.h | 168 ++ src/beast/beast/wsproto/detail/mask.h | 389 ++++ src/beast/beast/wsproto/detail/socket_base.h | 141 ++ src/beast/beast/wsproto/detail/utf8_checker.h | 184 ++ src/beast/beast/wsproto/error.h | 72 + src/beast/beast/wsproto/impl/accept_op.ipp | 158 ++ src/beast/beast/wsproto/impl/close_op.ipp | 198 ++ src/beast/beast/wsproto/impl/error.ipp | 39 + src/beast/beast/wsproto/impl/handshake_op.ipp | 170 ++ .../beast/wsproto/impl/read_frame_op.ipp | 519 +++++ src/beast/beast/wsproto/impl/read_op.ipp | 144 ++ src/beast/beast/wsproto/impl/response_op.ipp | 147 ++ src/beast/beast/wsproto/impl/socket.ipp | 815 ++++++++ src/beast/beast/wsproto/impl/ssl.ipp | 170 ++ src/beast/beast/wsproto/impl/teardown.ipp | 183 ++ .../beast/wsproto/impl/write_frame_op.ipp | 277 +++ src/beast/beast/wsproto/impl/write_op.ipp | 150 ++ src/beast/beast/wsproto/option.h | 305 +++ src/beast/beast/wsproto/rfc6455.h | 163 ++ src/beast/beast/wsproto/socket.h | 1190 ++++++++++++ .../beast/wsproto/src/test/async_echo_peer.h | 285 +++ .../src/test/beast_wsproto_ws_echo_test.cpp | 94 + .../src/test/beast_wsproto_ws_test.cpp | 362 ++++ .../beast/wsproto/src/test/sync_echo_peer.h | 182 ++ src/beast/beast/wsproto/ssl.h | 86 + src/beast/beast/wsproto/static_string.h | 337 ++++ src/beast/beast/wsproto/teardown.h | 161 ++ src/beast/doc/.gitignore | 4 + src/beast/doc/Jamfile | 83 + src/beast/doc/beast.dox | 363 ++++ src/beast/doc/beast.qbk | 177 ++ src/beast/doc/boostbook.dtd | 439 +++++ src/beast/doc/design.qbk | 213 ++ src/beast/doc/http.qbk | 215 ++ src/beast/doc/images/beast.png | Bin 0 -> 42936 bytes src/beast/doc/images/beast.psd | Bin 0 -> 166000 bytes src/beast/doc/images/body.png | Bin 0 -> 6505 bytes src/beast/doc/images/body.psd | Bin 0 -> 161406 bytes src/beast/doc/images/message.png | Bin 0 -> 9532 bytes src/beast/doc/images/message.psd | Bin 0 -> 204871 bytes src/beast/doc/index.xml | 13 + src/beast/doc/makeqbk.sh | 13 + src/beast/doc/quickref.xml | 163 ++ src/beast/doc/reference.xsl | 1725 +++++++++++++++++ src/beast/doc/types.qbk | 446 +++++ src/beast/doc/wsproto.qbk | 391 ++++ src/beast/examples/Jamfile | 5 + src/beast/examples/wsproto_async_echo_peer.h | 267 +++ src/beast/examples/wsproto_echo.cpp | 53 + src/beast/examples/wsproto_sync_echo_peer.h | 179 ++ src/beast/test/{asio => }/Jamfile | 6 +- src/beast/test/{asio => }/append_buffers.cpp | 0 src/beast/test/{asio => }/asio.cpp | 0 .../test/{asio => }/async_completion.cpp | 0 src/beast/test/{asio => }/basic_streambuf.cpp | 0 src/beast/test/beast_wsproto_ws_test.cpp | 358 ++++ src/beast/test/{asio => }/bind_handler.cpp | 0 src/beast/test/{asio => }/buffers_adapter.cpp | 0 src/beast/test/{asio => }/buffers_debug.cpp | 0 .../test/{asio => }/consuming_buffers.cpp | 0 src/beast/test/{asio => }/handler_alloc.cpp | 0 src/beast/test/{asio => }/placeholders.cpp | 0 src/beast/test/{asio => }/prepare_buffers.cpp | 0 src/beast/test/ssl_error.cpp | 43 + .../test/{asio => }/static_streambuf.cpp | 0 src/beast/test/{asio => }/streambuf.cpp | 9 +- .../test/{asio => }/streambuf_readstream.cpp | 0 src/beast/test/{asio => }/temp_buffer.cpp | 0 src/beast/test/{asio => }/type_check.cpp | 0 src/ripple/ledger/tests/View_test.cpp | 2 +- src/ripple/server/Handler.h | 15 + src/ripple/server/Server.h | 7 +- src/ripple/server/Session.h | 6 + src/ripple/server/WSSession.h | 134 ++ src/ripple/server/Writer.h | 5 +- src/ripple/server/impl/BasePeer.h | 128 ++ src/ripple/server/impl/BaseWSPeer.h | 301 +++ src/ripple/server/impl/Door.cpp | 10 +- src/ripple/server/impl/PlainHTTPPeer.h | 21 +- src/ripple/server/impl/PlainWSPeer.h | 87 + src/ripple/server/impl/SSLHTTPPeer.h | 19 +- src/ripple/server/impl/SSLWSPeer.h | 106 + src/ripple/server/impl/ServerHandlerImp.cpp | 196 +- src/ripple/server/impl/ServerHandlerImp.h | 45 +- src/ripple/server/tests/Server_test.cpp | 12 + src/ripple/test/WSClient.h | 3 + src/ripple/test/impl/WSClient.cpp | 143 +- src/ripple/test/impl/WSClient_test.cpp | 26 +- src/ripple/test/jtx/impl/Env.cpp | 10 +- src/ripple/unity/beast.cpp | 8 - src/ripple/wsproto/basic_socket.h | 595 ------ test/ripple-websocket.js | 23 + 106 files changed, 14671 insertions(+), 772 deletions(-) create mode 100644 src/beast/TODO.txt rename src/{ripple/wsproto/wsproto.h => beast/beast/unity/beast_wsproto_unity.cpp} (78%) create mode 100644 src/beast/beast/wsproto.h create mode 100644 src/beast/beast/wsproto/README.md create mode 100644 src/beast/beast/wsproto/detail/debug.h create mode 100644 src/beast/beast/wsproto/detail/decorator.h create mode 100644 src/beast/beast/wsproto/detail/error.h create mode 100644 src/beast/beast/wsproto/detail/frame.h create mode 100644 src/beast/beast/wsproto/detail/hybi13.h create mode 100644 src/beast/beast/wsproto/detail/invokable.h create mode 100644 src/beast/beast/wsproto/detail/mask.h create mode 100644 src/beast/beast/wsproto/detail/socket_base.h create mode 100644 src/beast/beast/wsproto/detail/utf8_checker.h create mode 100644 src/beast/beast/wsproto/error.h create mode 100644 src/beast/beast/wsproto/impl/accept_op.ipp create mode 100644 src/beast/beast/wsproto/impl/close_op.ipp create mode 100644 src/beast/beast/wsproto/impl/error.ipp create mode 100644 src/beast/beast/wsproto/impl/handshake_op.ipp create mode 100644 src/beast/beast/wsproto/impl/read_frame_op.ipp create mode 100644 src/beast/beast/wsproto/impl/read_op.ipp create mode 100644 src/beast/beast/wsproto/impl/response_op.ipp create mode 100644 src/beast/beast/wsproto/impl/socket.ipp create mode 100644 src/beast/beast/wsproto/impl/ssl.ipp create mode 100644 src/beast/beast/wsproto/impl/teardown.ipp create mode 100644 src/beast/beast/wsproto/impl/write_frame_op.ipp create mode 100644 src/beast/beast/wsproto/impl/write_op.ipp create mode 100644 src/beast/beast/wsproto/option.h create mode 100644 src/beast/beast/wsproto/rfc6455.h create mode 100644 src/beast/beast/wsproto/socket.h create mode 100644 src/beast/beast/wsproto/src/test/async_echo_peer.h create mode 100644 src/beast/beast/wsproto/src/test/beast_wsproto_ws_echo_test.cpp create mode 100644 src/beast/beast/wsproto/src/test/beast_wsproto_ws_test.cpp create mode 100644 src/beast/beast/wsproto/src/test/sync_echo_peer.h create mode 100644 src/beast/beast/wsproto/ssl.h create mode 100644 src/beast/beast/wsproto/static_string.h create mode 100644 src/beast/beast/wsproto/teardown.h create mode 100644 src/beast/doc/.gitignore create mode 100644 src/beast/doc/Jamfile create mode 100644 src/beast/doc/beast.dox create mode 100644 src/beast/doc/beast.qbk create mode 100644 src/beast/doc/boostbook.dtd create mode 100644 src/beast/doc/design.qbk create mode 100644 src/beast/doc/http.qbk create mode 100644 src/beast/doc/images/beast.png create mode 100644 src/beast/doc/images/beast.psd create mode 100644 src/beast/doc/images/body.png create mode 100644 src/beast/doc/images/body.psd create mode 100644 src/beast/doc/images/message.png create mode 100644 src/beast/doc/images/message.psd create mode 100644 src/beast/doc/index.xml create mode 100644 src/beast/doc/makeqbk.sh create mode 100644 src/beast/doc/quickref.xml create mode 100644 src/beast/doc/reference.xsl create mode 100644 src/beast/doc/types.qbk create mode 100644 src/beast/doc/wsproto.qbk create mode 100644 src/beast/examples/wsproto_async_echo_peer.h create mode 100644 src/beast/examples/wsproto_echo.cpp create mode 100644 src/beast/examples/wsproto_sync_echo_peer.h rename src/beast/test/{asio => }/Jamfile (85%) rename src/beast/test/{asio => }/append_buffers.cpp (100%) rename src/beast/test/{asio => }/asio.cpp (100%) rename src/beast/test/{asio => }/async_completion.cpp (100%) rename src/beast/test/{asio => }/basic_streambuf.cpp (100%) create mode 100644 src/beast/test/beast_wsproto_ws_test.cpp rename src/beast/test/{asio => }/bind_handler.cpp (100%) rename src/beast/test/{asio => }/buffers_adapter.cpp (100%) rename src/beast/test/{asio => }/buffers_debug.cpp (100%) rename src/beast/test/{asio => }/consuming_buffers.cpp (100%) rename src/beast/test/{asio => }/handler_alloc.cpp (100%) rename src/beast/test/{asio => }/placeholders.cpp (100%) rename src/beast/test/{asio => }/prepare_buffers.cpp (100%) create mode 100644 src/beast/test/ssl_error.cpp rename src/beast/test/{asio => }/static_streambuf.cpp (100%) rename src/beast/test/{asio => }/streambuf.cpp (97%) rename src/beast/test/{asio => }/streambuf_readstream.cpp (100%) rename src/beast/test/{asio => }/temp_buffer.cpp (100%) rename src/beast/test/{asio => }/type_check.cpp (100%) create mode 100644 src/ripple/server/WSSession.h create mode 100644 src/ripple/server/impl/BasePeer.h create mode 100644 src/ripple/server/impl/BaseWSPeer.h create mode 100644 src/ripple/server/impl/PlainWSPeer.h create mode 100644 src/ripple/server/impl/SSLWSPeer.h delete mode 100644 src/ripple/wsproto/basic_socket.h create mode 100644 test/ripple-websocket.js 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 0000000000000000000000000000000000000000..3444d5814c8787c832b85944a28d2b2768d20330 GIT binary patch literal 42936 zcmbTdWmFtZ*Df4^-~^Z8F2UUfg1fs1cLsL}?hbzuVtuURwIJzKBZdskOib(n&jI1>CP_&0CfAW2GyD7|?DRsGuj2K(;yJJAJ(^7RMT zNmRp08EER{YT#hje-E6FF9eLaUr2mo2^VS|B=DMLjOuZ@91vpWZ*_`>qz#m z1Q8QQBL@pRCkvo0@jns`41vy00Mb`W|F~ddCoB73!M2WnfqI3E(ape)k(q&s(Z=Q< zzy3q*=%i%w|C{l@Qah@++nF#bnK%NS9gJT4!<6h_=2z_g&x-ybe6@y0-ofIvQw*#{ zfJV+XCbmwJA^_6Ye;AA{jCoj?#YBX-*jR-G#Y9EfnVGpnxVXi{xL&_lMA_LzS^t&s zzwvU5vWRkUiU^5`3JEbYi?VYwadHW=iEs+Cv9gJZG70_5D{1TKWMFG#@~>WtSG|9E zU$y=pczJ{!ObncW4k|#P^}i9IU=DNwI+_FRh=r9oh^f^rY>k1gjx_&t&mV6^OdKp+ zOpL`GfHuVc!I#J4f6>pv@qhFC53lk63ucV3#xVX99RDL&{=M`Xf&VoB8~U#&|4kkf z+t-Y7cuft_0gUE1Z}RpeMFdsc7LJx(Q;gNU*Pa}0qmzu(ciA8SZVOX3c!^IYaL|EY zT7SL+cfQ5Lv%WA)*WL8iyr>1wLM_s9%u9UzBJ>Vg0hu*#u*Qt1=%DgDaQXYn0WKWE z;=>8Q&yjA_(*qwXfE_a3cv8Xr;+5rjfd5^t=;E%GZW#8PuSIz7&-DMK_nV*T*!jeN zns2_KJRf$#q5|C_eV?^)p0WR4Uh@@Lg6r}fargEm@?;ZZ34ltAhO0O6Of$2htW0g+ ztL21Sahq-eSHrv zX8srFfU_u3kNv{!1$}h1tjOR|6Y2KduJOOxbxw+XcgF7at}Jp_K(L&dIm#rowCMPv zfdMSibx~r%#xx+aZogP+1ULQ=UFV!~%>i_-TwVe(L~eC+{9jfVs+-Nu_oEZz_j+e( zFfOWimhFCwTiVK>lL}4M@a`>}CKfRf&W^S$=@&T&c740ekY^dnsZy(D^Ty4etVzpk zTQ=VV5DHASXTR?KMVi5te7F{5@>Io7&;b%-lRSr|5M82Dd~Zt=OS@Jy&w(8%QEtg+$-Tq0Sj#zcL-wVztYa|%!zSZ|~pPWUeuKsn}_VO(M zG6S>0$K!JEzutCzd=ks`J#mZ<1AjFj=lvgZZ(XqhVU=MoLI)(KX4vW~(&t#Qa8J%GQ(uKSOar_M$ zHc!Lt1Zcv`b-)XKuY1>(3+;}?(9rS)kNfev@cV_QpP%pFDTZ-3E1azw6WK%kG5VWt z@Jj(r9*%+Uv9dFNm|fx*$qAyN*cLHigBc?nIV_WIC$C0`#EA-c3VZ+-#{rvaszw)z z2BUK+*?wm0>?8AA_g?1ZEWJ@xs@%T5$yYz{$eD}|6lyj)V{35#(W1tP&Ob@OqfcAE zpq96yH=0Bd*O{qZVQK@EL=FsYjyEtc9y2#xllaZcnAM2l@=U=$#mPVZw6`n3?)8*Z zyv!tz&+f#IWqiM}5o8FhtD{cIaHp8Mg`-vXwLxa-``X9IzN3cuK_$Tc zU``bQQwHTteCvtr$t`8TXlNFzoS8203fLS_VH?kat)^q0OO}9=m}$(7gROJnoJ0tl zRA6DC5E74(Q?tAeDwo4v*lZJ&#M0y9;~wYz0TfQjuU89V()A}+tW@CU)*hOfl2RU- zW`h)j%iUGVx{HDJ0_)#$KfL4Ml5v%25lyuyO-fAffDx zE=qFuQR;ZEwP2x`x9_Hax6PcULQI{SmRU_FTEqxIE}5m(Jj14Q;I4fC7=)d?-*!8w>*`9e5!NKar?QndwLpvp> zU?!IiKBP~cqKZx&rKKL3So{!Z%qEe5Aj04FL{tSmCtqSPkX4pY<&R^Zb(yej-z zo38F4irMVqb==@%Sdh|4{fl4?T4;9l>Rq>s7uIGRd50%;wZ)GRlR%~6@1xUdrN>^n zR=9E%HhbWwn7o?}^g#6LzV>0gPdf_^k(Edm_L5mX+C7h z;3mQ1Q-yMc#Ix++_MJ^w>H^$yydCB(Z$ta1*bjR;6D822lKj)k_Asq>NNv2HHkc7uE_*ktWhu4~#)x)1j`dmmzju%px5zxU#W`0`|!8^B!1 znBabQD{?b_8-DTy>7U8T>+xIxL(&%+^R65lfmoR;91OY|9Q5}rHot=v*Q}JkCYMT0 zQ7@~wp7D7W(3u5{V;)LVxQmNpp>2g)6c1R(;RCh7R@wvT0ESw#i^7>9Xc>h{VS_A7 z8q=b8G+O1B(Wl&*X0z;7bV{jT>>Ntc!f%D)JT|jX;8xNo?Hk_%{1gDp~E% z!$MSAx|&-<)1L$p=+@`Jj!r8tmob##eImhRR>8dSaklemu=bA%Jgrttk2A;V2UjO0 zh8MrA&r%@;W@h!HsZTmc&yRp^9QmV9k_-$cW#J3otN1;>;`Sl^@%5irMYGcPiXjPW z$7n0bLLT1uRqDK_^W+~5D-+c}S3|zZJy3VxIn&R9tUsN;7Vyq64i|X{9D~0~r5lo` z8IS7y3+KvE9cbxNIEjs4&%RK15MzT9^L59natpYe3pfAq8Km_N$m$ zGPtb)dr--hiSAS@tI6}UIPg-+zVq<1K)mvR8b4Ncn4d>eyI4c{b8pSMjBw-FuhMej z#o$`=A1tymThp6m7s(8CgV~;ydn<{kE$HW-Kz#3hZ81W3`yH#X(X8UO8UMY8^nlwd zy2~nDY3n^WK3^wA*WZw;Ut=l_5vL!IWdEk=kJhJ;{KBDdUE+x6!B8IF;R}4fwm*;F z$Wf~X-fLRkgri}POlXUKaZ>w(aA299#G!pFlth%OwsC)M>MupLc{W*Zk0QZ z{ZZzp4*fPxT7*cmsamP>aDfE%n!C>~e*8pi)F5c`(?K+AMs`oVrTd(-#Oid~b_H2m ztJR)IdfYp-&%v8;vXB@VRe_5(N8hW1gE9Wec}Iv<;2wa%)Tc}{L!=mstu zPVR$ot7~Y7sRp6!5FA`O$kDF(ZoH-|)t!~DM$u0MOkf)4@T zQ#veKQP${N`ZaTSMo+&8;|~^Z7>r)bhp5E@PwV(m^9NCm4aTh4G6KqMN3W}7;2LeV zH5U|}+;v?qhtBjMS3VBL=WBP9&7I>mo)0jrG-KoJi}gt9xwzfdZ0XKvo4M<^n~Y-z zhL%PP8GN2nuwoIa-Nh=BX!;E{iMdTm+>G%(g9SQ39t)%Uo=bQ^NOXwtq7;RQ42+zN zsvJHq_$71kP7Ak5uhInYG&Kn1XZ2<(dbS=JAK71nvBhcjR9BC6xk#Eh*6s!yF(yVO zNRKp$7DP~0;S_2P?aN*;rxw8a5x-A0&bCPiVMvVr#ipVw3$3YqVT}hPYFlFtg~Rrp zQrv?g)5W5d!}mI8w-yGJCMam0#^Ye{`(HeO!0#;0F0&E6)DmCA$XkrR!@Gf`7h=Bts~prZJ%cMLYs5#q zg+{ot-MS3772py3P=nTI@`fC$#4r&VY2Oo_=UxOczLl$A#uVD>|KrXD*ebjSBZ>ce z<~lnjf*Mz3pW2i5(8;n|J9o8B8fR>L*?=1hqjco@vzeN&SL+1F`1EGn3R8$zk(L!p z;*TFJW&0k!FVz?_v1}nS6`G|2w-Cr@x9V9=R7z`+%#6=N6Dz^n0?r<$!vo_=P5Tee z?N<{undW>LQWB4L3X!mUn|Z-u>gkly%)0pc<(~xH&)kET6GYH;WR+MmQOD=ZIEToEJ~lRss5w;xutLc`V_drb^=N zOY_@6y-*ZNToMn1#8yrG4y`S&{VZ>J(Y{j0&&YlzKI1$&T5ZAg-7fevMf@jF)>hR} z^mG(iMK_yD^+ish7?Ena_g;e4R{*@%v}n zh|+*nZ{3$GJhF$G5N>#00+1pN#&DFW>*=?Fh!HVNPh@GwAO{94Q~|deuB7>S&ep4= z9_Y9{Lqhk$@oY(2nFME9Z4I4+@881h^)3v9=p>sXqFlc10^+q4r(%1`oSg%mJp$-l zQlGQEy|nJ0SPLcM(v_`iEGiBLoR()Bksg~ewmsi%+2Fk=TEfPpuCuss4k>RL_51FV z`?ZbF%yz)e$YQ@j+bHb!nnSN19gQlIY*Kk|JjeI2=qTi`T9vzXbHoE@E)8pV z_fhG&^Vkhw8xk1$pmB-|;gb7Pwohv;gfuUow@aq_V89F~y%NVR2yhXTgCw-aCjEktI-YwP9T z5;Lrry>2wvhq{gSJ4c-etdKDxZLWBT%SY#Fc3`anW$d9**Tsy><6+@=Roh{+bJLSq zUF{E~_?_dwj6zI(Umhar=r=Z={rNqFdn-O(oVI|i#!v{h!u%@9sbZ&VF*po0g2-06 z90N3x39dpZ%M4CcSi~SzVgLH|@cA>niyM|kOfB_&;s2m2=tol8v zoCY$9KOv!naV~0E(1Mipr~tp_NtcBrGqbCCwOtB6|DV{y7pJl_SMrZ`4Bg83 zedM5A4TYl z3R06|n1+7)W-<{GYD_Cd5*Z_7**}!AKyq)=#X&#)yVkj@OGbV`2F_TWiyAII_mE$9 z))HE@LOcOL8eySGS&+ZW!%>SW`#`vk6K+8oh7FE{;=h>7;azzK1JqA=?G4h5^C7!C zNUHWWyJp(=iWw(DdqH8i%)~E4^oP>@yUrm>y6JTp4AGYL<=f4uC|rINeZuR`gTKG_ zNe7arGI!@xI*q}m?osqm56N?=7uGgWWaVitdu?q81t{OuxksYzS9!CY@9reJJU3h{ zUhp9)+am+`Cm!!h?w==q^WG!^m-S?U8a-hQKzB*2DV$8R4PELaNvb<_`R{J*hlc*G zZjO^7%zqL2Artq$79;;C_M6D4wAg<2KqBpRP;1rxN+x2v6;-3Vf|~XDoiclO{b>h- z$7)ndLW!V1aRVrKDx7Y(AJRtG9j5d%j|*}ECtaX<#f9D)9mHsw`-=` zw(E1%Vsc&5DW7}w+zjbEWlou!tNa6U(D6DFa}B4&u2#?BzUt9FqG&z)u=S!aEf@v^ML z-}^AP>em!+*f48i9VqDbikjSO^%S9&sF|V0k;%z2$GbA0-=+Dnacr#haWXaE=;Ox% zoFY~FEy-0Qo%cxB5TvU(GU+N`OVoewR2GSsaL}n&k;6p39I@q$)ub2=sG#TrXm#qC zXRytOE# zNtpzVVklvyIWqxVSI5|9MG*}pO}(txJAAVaUW`RJ{DN*PR#|(Sq7zqVZJwUZ6fe^k zy)sTVk~qQZW2Wi&lw9xhTS%se9J|nz>89L2oi4?es}J8bE8Fr64;Y%5oZ`DwrTYUF+0Fr>`&NEAFGPmdoDDz8NuH{$G49h?kZu z$jZa;k)d%xlz8-u_DdR}b6hz1C-C=#JbbFYJKuMR4cO@6+Dm2~r2x_w4z9osYG^n; z(|~p3F_b=yGK?gTPG|Oh1sebHG+9|i5W?LU;PxDwC`)&0woqNpG@@4Kln_xJPpjRA z%>Osz2NF?8p~VFG-N_*bCA5sajy`2WfjmF2{`ax{EH80iU`iqv2J8oUxrT1Rg^MU* zGqv^yp);5{aTSr}-1EZ77ot1f z`|g{lkX@|O&E-~+xJ4zyB3*>i1_jOw%;Q<$iSL;9%m^JP_F)EDwsOs2&K^35}+u{9h?C6AW7e*NwcOC@{FftRMt`r;1u5yxN;(+1VB3zEsmTyi zs>zSKMx!3|xgoy?-l5(^19jdJ<>EGVM!#Jp&&%a76d$EcBzBbCB-qgSgMcsX3pUIl z11ZY+LvTufqZ&AWnXT`22wO@qjoZ{t(V{nRP%>J4 za{+EES$=OY$&udd=vJdiKLcE5|LQqYdf@#;HB96^TS?zm10>S;_V(Z66B%F!BnrVF zuJ!gSYRT=ne^%h3JIhaizrYaXb8fxgWKD}2e^T$O(k0qpGUWngYxG<4MjKh#$izpY z(2itPY@2QX$ks)w@*2Im$F-efs$+i55_;s!LojnJs8;KF``0Zz#DPashTf`;47R?v zj!+;Qw#bK*IdoW&m795(X^_h*yKsMDS-<+x&1%hdKjX-*1Eyeh`@PiN;5=5;o@b-@ zN^RU=Pl|{Jd5!oFmz;91yX^&~hlWwF{*zx%o-LAFsfB1;d+UL2{bKH}G5wIh^+`s? zviVvFfeD8z`fOC)gLnZRr)b}Y?zfUS5+N?YgZ7shf{#bXxG@PA5kc8FKMWs24H`dY zmaySfxUM!8m7s~6IWh(R4WMKz6kF+(#gByFi)R)+`$D*)1sXJx zBn_3ZEl79L`|)utt=<;0~%VZwQyw+IG4e!LX)d*rjx?eoPa;jtksH`pyjSuhw{-g@z>g{ z7QW8%!^%y6)QNJIZOEM{Yw!hsC*~%6E zDI&sUbo(=a#MHy^`bg#MnDf-ZZUX)@tL zo^i_PExhK;`u7#Mc1509SrGhmum#(@awbKlw%O}5?R4I>NRbUe#oV@u`Xe$$WtmxX zrd;3KU+0WV6J0BN?>>kiJaPAw42)NW|yH~&btwd|*h+UqI zV#Xj6IH8>;bBS!4smWgnjuk`%HuRjw`Cno_w3dZ5MUgF6o}vvL@8iP5;tR91QdegX zvo3T>EU~FFuV5a1QX1w?L+1kBU!3CKUb249zz$_P@|rfEw~gI6XEffwA@im9$h z=tVM1arl1!_Vb0BZ zPrMI?aX7+CaSC(x^l@e)B-!cLS};=hg?n;D!9o*a6-u-q!?!*jCD)x*nXIT<*jS68 z^x0qhaTX~vLDp7mgU}{9IcKFoh4A-=&NfysY1=Ol7+YL8oDs23<_fzn;K<;j#*;Sp zetJ)FvUi(CEh?OjCPt;%&&GwC_N~pwYNdK@>n{CaHix;o%(fN{G?UMsKwO+tWNwa= z6TEzl{hR0O%aYvZJ_ImeRjhd2N$ryy#DQQHVL~Llh!^IPK~8%c)@!VgT0Y*rz!4g| zvAW~O?Z~Ep+eQY^Gnp^tz+OsR?pNZ_0{ARNJ~YHsnkn+%5^aLA!qc}1npUEhon@*u z55A}&La^5kQEK5ZU8yxPpEc+Ei7s0Z)+&bxR&67QzKWwGLJZc72a)OuQ469PTip2< zltmJU=HK*+%L_CX=euX-l>~vBOL)&C9v*hQ=bw7gyF z<1zgO)->wxk_Ur;3OPAvsiAk=7OKF) zNnx->^>=IS4^+_Hdge(H+a9Upl9@YP)&iGDQ=A{mm)XivP4#BG@$b%fgH1*eYWc(V ztb3K6@~CiC(ub;6ypMrCx9TFA@Nvi6WCAjM@6%4S24Zep^;grQ6-F=b5ah?o;opM3 z^n9-C+xK{AsJosWq90&6?Ev$l^??Fnt@=!wd6S}h0B-W@em+HG>opc-S-Pt~w_9q;jDB6;&?r5F^)G28ECG4-U^(udu+q~~)^xgiP z_2~P;mbrjonk83AN>y~wKcuqTz({Wi9{~3!c^9=ht}BY7s~vk)Y7(`pXh{n?nD>CN`UUlO>WEWhbjz#>$S-C zYXd;nW$PQimG(HIQzQ=dWC{W-#nv98OqG>c^w$UI-n(0oTbC3xtFP>aigo6ZFf6EDx;U0$d-z&(vB6_ zFcY^bjcMmvQVE*~uP#S2)A;#cqh_{6&9=>8U0?+bQ=+1vrNu*dY$UrbqGGXcK9jna zr9Rh^BWN+LAl*IM1h`ChghxPqA4fzm^1NkzvQL%iHv9fYYjRe6^2qgLwkR|Y%3pNm zKja*0gKS!&@!%EK&Hal|NxcFq6k^S85w9ivds=bwaVz!cW68_#c>Hiz6x^a40RBki zo3+$%xs%i{dm`&MgsCmJVSuBDm64~*#i~(6)5#o(v8983C!P3GaA6t@Lvh>2WQ%~E zEZ?_S^ z@-b^)2SovhQGEgt#9d&@kja08i1 zC_(pVVDX)@^1Qs4Yi^AT9`Kl9iBUK8euS5^Ia*T5%cp>=O(h=s=2{oEziS{S9>2|N zN7djfT`&5sK49FII`64u!UqGL-Wb<`NyBD$tis&awYb5W1jhYz731!9QM|b&!DhML zR$HIy>u^%|HWw5nn>IoypQuAz=G$7cbacG@UO98p-sgvt=BckF!E+GCqfhYs4&`2U zqAiulZC0OS;MB7L!}eL}Hxg1fk?~lR={Ak%_K4dGs`$?bgtfso z$M~gY56|Ie+%FrZm{5`%5oM#W?})-gHYR>p;8cJ&NVa(7b_;lA=fb*214bb{vU4=T zI4*xs{KonI+>gM1)l~*^j{g}S#JioJ&fphnoude8=xqN{XbyGFH-=eYM;(9ZRXT48 z!jmP6J2tJ*8bya<;JjS*{XNw*R4{_~=xPJ!bB|qhe7vvhJbU?Q$7hV&;Ph=nV$!)- zzsU;yIx%@SL-zrQRNdEe5i=@d2Gc)tW1KH$JK>5r;v}a}ouN5iSQ@?2q<#tIyS|{} zN_t|T#*P%9%`58RVtL=sDBn__7&$~I8Wf;pxQzMuTaTPVlK-0|2T9K3oichv&?u%2 z-qHO`k-+(h_fr)i@5Ok{f|n!xpoGZLi4pH?gVwo}jY>^xSZSM*BWz5rmwyhEr2D|t zC|ycOM8>bHGUWT`kA#bjCPrh;%tDbwUh5^hqrY5AV{(f{=s;2w!t2$i?!68c?j{8V z<%*1x3;#W~4?`tPtm!mFK=Z_C7 zwh2j~dQj>-D)qh9Y3itYSrp(uo19;wdOl)JO-vDZoSH#;Z!^8ANlD?^U!>KK*L#6b zATGan5x(N^T@dT(dL)&AjHNdoWEP<$g7$;}ag52beCmDcxVka4%U{n-9hQM@i(UKH zJs95Es5!yi!`f@CG1K#20Ptd6SBV%E72M~_{~RRnc(!DP&g=R9GGnuKbK%0BanU&F zO&e9QtJd|;QsopNP&Rzx&8O%(jMJrB>7_dE>kZ>gna<2IFwk=iAT5oM5z%~5i1%{4 zDDQ&YVD+HppUbF|Qm$5(jLZ<~|MLP$aFWet(WU4;b6*Bb^yU}zzeDPZh+skf=sNO>;*_E^s5_?Q4Hn8G_CdRb@G@jX2` zM;P0a63*YxsrwWPBn7zM7NVRxzSy?8bR|UKW11v6)B&jm3sl>WOno);(`i z6~1hkS6l=d-Cg&D+fVb1P9}~AKlxa~vTiH@q!!O5lC!tg4^P|O^f;8xeeWCk8TFV) zxJNULmh2`0js8SDCe7tzapn+McU1w;E1vVgy8F&t|L{Pjvg4EO60X|ZP_T}B!6tWAt3$TqngsbLZTMt@WP>wg+)0K-dRV#kmrH?*sF7ErBv>0MTzH+SYHviW zuCPAINu3f?c2CRG!h{=Fo-^&rOzsz|jk(U?;e*je{_H4>zmT&51?jqR%6BfewQcmWRX}W(B^PR-(mRh+ z*~1l|UE~=@UFNT~wThMD`Olm&@?QIfNG{ZbX;nO~VnW9fA9>xN0bZC6Hd`qf&qDkT zfVu?zId&!jw}XcmeqV>&3$nN2x81JR%b8Q^;RXJs?B3Y89(e2K#HDGrlv{1y>gHG6 zpNq89VlwrWippH7*2MRYuseQ(1-yTw;w?Kr-}O%beXSp4IiKzyPM?MnLJ)#fCF4K9 zz_80&ainJuPdP8M$!hR@_=DhY(~m;&>*(tzYIUV0>>m@tL|nfh*JI|X0zuLUfaUHu zH_}GFPAMhYWm!dQY79nI`0&a2_c;=5nzW^>ieY<*Xs>pIVj;a4J@h?2V5z%%&+ zkuBj0*OXCdnk+E`N&!9wJXEIVT^G@lU7vS1w8{ZIW6-U$>nB6Bec9lTx8OQ(DYc-} z5Jn4fp=96`qtCIR)0y6Sk$~N1i0?K*MQ_lOQB14X`j%ZNoa9suX5OR=bf9$Ey#(W3 zX4@H2MrBr&7A$uekCx5iq1%R|?|D?r$JO6=Fj)h#ty=nA*o1Rj{2VH`3*GxCncsdM z2CFe71ICIh!iiyv1^?joGo6UUPN_tPI_VrP5hHys=l$rafh%C)+kt0Tv1g+qty1ws%*HvV5T)m* z7W5mSlyi^QDr1XYmgqIVaB~8`rU};bt@6g&)>Q0IdUYj@?*#IV0z-v2E$G>jbVj@ zhTy|nuo>o%Y8nROtYx}YS-y(Z2LKu;;CgOC*s%`Xv#KRi{t~uVsz!|lE7@O=1}mC8 zPjbX5ZF=eF3_;;8suX+5NnE_W(fQhS%}TF_+Ew`o9>-EOy%_3Tp@^U{oLIF*CWc#| zvh;9M@@O(+<~80)M-vv!2fpwkxJfG9F>8`=LC`iXeUU2ZHW#U)vg7mlNB;T?o71>^ zFd0R}K?GD-9S?WrS{0tOYWj#LhiImN_N>k?rPdr0hy!{qZ)<>V`E7dq5~fiY;RLKM zck}*=tBYPWzh~k>D-JvMrV#KR9=<;h=d^ zN<#c%@savVk*4jUf={rutm4R|A`Q~6JW3R3qbrEQ`wU@U_o0sXUQD2ui149CksFdq z^Il0Yf0weS+h#j7Cdb6ZDVM>2SQL-;ISo}*fa8umkS-#CsHoO~v@0ta9BY5Z1|C<* zQrM^rH@G|yL6I<4XP{Gg&$4St0PjJ5bS;kYm3d_s==nerikIKJ0*B%(R_vkUTf<|I zgW^7_@WH;n)wAxNv%Kiof1K{;CFPJ4Y?pw7yA8lOhfQAETTN+(=+S4ZFBkTB3wn|3M>#jxIRhJtpo_egS0?f{t zg{LEcA?aKqn}qcqKWe0)4n~jv?vCmLx{93xWj+a3RjP?8?9M!Xhdh3pKFjWaBHn<_ zzmyH&Z-C0O;`SCz8V}4ERx4WzHKKsAy%-$=pu{la`~cDeI9+6%hKl!KzpN=^XW0TS z0#1E)Lv}eLeK%)b9>(I9JlBByo;+3^&at7QJz|SJP`nwgyfEQxDq{_!#%+$n2*4Iv z3bB@s>)&vOkDa`}$1y^wf>rJ2GEen-5H|PU8LqD0595tl-q*biN!EXI^v&A2U`plB zp_-uSZ6h-|;`khzNqe|t)F1^q^$EHdG*LoS!!L;1m8@;mDz7V*(KcWk(Sn#!E>`FS z${7xiHH$b(k#HAnDPiOfQp0ttk02v7>4)5j)ZMJ(E%Td7-mT7x(Z|8$ zO!pJ?EYI6iDxb+xY+jfmi%)%jCu?gC@Ivq<6L#if*94x8)_c+>xeh3Il!7caBb>6H zrUiFfWfyma%PL7iH9gG6FF)-{N>$2w!Ga?dV2jLpfd0nZ3T)$#??5)Zx7?kRj}x0k z$?)O$#zrhe$Rd{0hAf@SkD!WUofT9=`~clW65;nLn5O4i6Ap;8wkbQ(3B7l%2VtoG z`7%nEUP`9L3hHRX_AqzpFmv>h*Bcg_(uYHVP_&=cKH_H#X7ETFV6vC43~5eP#wJF> z!8L%0o=0y^RvI^tJI+vz(+6fRK09JavtU@3W|mNAkU3EqF$k5epWw!SQ0n@UM&+fy zi?sv~ch5K$z zH^MtMlD|d&6)UvGVMp{RkHv<^yF0jUSD~Ehg>tsx2tmTxvYGi*Ar=A$>-25m?(WU# zfGMy)oR>#V?{8xXE#9FoEPN^O8kx#AX*Lb2<)xHpZv55GvG@`b>nAx1Og~;0=zr*r zj*NVbuU>#Z_g1HoWx&wOw4LAEa5>J@ooqNI)Jf5zQ@}w_($VNb44@l9Ghe-)U+K(= zS!VvE;aTZx;j{Ckjozrod>f=5e&e&2V17o9?kI3^7PDNtt2JBf(r!gJW<#`p_nWcf zBP!}DQ{2KD1RgoeuNhALuec>n)CKFQXRG5H{- z9TgnX_JvD}R=s9bhRTSBqb3wek@oKq#>wH53N zc-_6BtG9(aQ6O9@?sSC+UmK_c6O?ze`s3=1NWi(<#jL@WoWe!eR{??|t(ZsFWf?$J z&Y^_r+GI|c##W^>+b~6)){D!LDtUu6m)XK1kRZ(zF+BXa=@a5VZ;rkmKTWU#bxX)= zr+Q_G70AX^O$L@z9pldBYY zcX_p#-yfaa*5E9H>d?mP5_7UNsh^Qaz2zuAimjJIJ#F)lu*$_ZlRoD>P07jaG>y4{ zwAez;Y}kmjg$rB5oK`Y7^Sa@pV6%v7SGrPLBbw&qi~Nuo6jy41h9`C@HyLTg_%ClV4r)ZPG!tS_lg$gRM2Az zSB|WUwCxwGl_#nK7c&8kt3PN6bK%Kd2K-R&W+S#KwuYLihEhCvOtw}y37ntI5=0)`Jq!pg zN!V3aK6~R*Ze2nH+oPkI0me`Jn(&Un6083q(@2X_T{SJjyPJ^q@W^qhp(dIV*%VOsUZ^p+r_fF6iOt7$gM|N%_)# zbF?-2@uQ6IBX*?~r&gj>RXH76Womd4&+)>3V^+hezVBmYxS)&)UhpQgk?y+0UZ0Jy zur_DM`4ptLSJc~i;%s*3$7oXh&^Gf#!_;_DL=@A37sN=ieDEL~6Kej{e;+K39TD{I;` zFErQ2NCauld}BiV9!Gmax(}chGMTah zMMN_^l^T^Kx3Iz#>2!1~s|vpgJYefSXRN*+FL89vL10Vsy>`bXvkbFmBjWSgI`A+l zX_OV)VY?{H=iEbRK$pvIV=6GLN2NNUxzNJxDRn?)M&;OxR*W}OoXk}hI|A0MW{(|{ z3=Q7rEDZAUG!GrP#Q5}^f-vXsL*RE|> zOhSO7E)ByrZQ(u#ijvc3EQ14Cich&vncp?-nS}hmh`%Qn{y}ai@#||~x{Z9Jgp5}G zX*u3<$|t>3_OkYz{i|S%Se`cQgQ&RnP4NeviV8Nou(+$e+uTB{ zceHKS|3zST$7%HK@m4QhD|G(|vNt%F}sa;kO7Z_ptgJ!eD!oVRGiod<;Q% zk%zL5$Hr5y*_xxFvhCY(EBPl+!|Y9SUysF{>t~(2kPdfG6X;{Z&_k>c5WD)e)MK8& zi-DQCr(g~rqUvrRFAM?)uX8zj&5OwV`j_45I4`)Z;##+8Gjhc}!l5hU9~EsTZwGU6 znp=mK0ZP?m2hd1*qdPo&=5Rs?SacfIUpxJwUmK4#a6%npA!KsFrp(aFC=B{^7O=+- zEMZf{#I*DN)Fki$DBNK_hi!Bv+t^cFE<^;cFQJ941853Ly5y7-yWmpajYX_jOA?#Y zXC_(!jzp7zUnI2XE_%%y0V@65b+E_P>TD8;?~BY0@*`&VbU;gv#bia#m7I;bHRaqJ zj4?Y3K&TvC_=GiKP_Bwff(W$W+06%pWqbZL5vF&;#&@}WSvbEUgOZ{VgrFw$Y~@?M zozXkWPb1% zqB9W}lRo3yxT@P$dYvT9k4}=HR9i6UE5$@51|zTp6B8oR7cVTB$i6izFJkn_=C!&} zCTsy{(`sHD|H0`$fCULq3KpaE{I<)Tny8|dAWiO*44K`G?h_}cQc_pO9VMPO3<=GC zA9RlXaN$~hRf{+}_^C5C%*#DB9Ye)Ap9%c=V?Vp7uy`Gxd9|^U)^955HhTK)xD?7W z_|74W^j&dP*9s|KOtS)vV=T###qHHb@0&4wi|MOuB*7W|=C~`j$uMfwI*c>Vsy5T( z07e2l3{^z+{;kaLMCj3z>n1+1j9;E;O$v4}tC!jYp2Fc$IYcZo6w{?`w)tgF@?9^N zAC-iXD3cE1VSX^=YC0}@)Tb(<;!p+C{ObF$=E!X#lRUrZL zKIaMO?F%(V;&fPxO^&<6j2+fUiT}jg9~|pwV3h}*fbxyy792&hMCJ6DEQZ|binuZ{ zSeOMeXhJ~|1hf&kr2ijJ=fGI!wsqmgR%6??ZCj0P+fEvrjcqn|(%3e3Y&O=n&%Nh< zKOx!koomc7p1GD@bN<~tdQeNQma>@S9cUO)s=c*`Pi--kKw9+!cVT4Xf}U;96Grak=lw=sOK29Znd1ELJuSd2|9>-4JNJ1VPa?Z~u`hH{`f z;vNsiX-Hz1T!pfHM{7lISDwro#l@5z^<=+oHQv}H2fUp5W2ip>qE=d^yY>L5XwTaOI{xff#`;dVPc?Ou96+uPR7Nzo}obl0Rl zR+{4dA6@cj%SSh%55-cLDr)S^v6BKRayOi2PwUzNih@lSg*J@3E=GFL6^~VJ#ETNd zg)uiQ+ZIkbhX4K!H^joBMPn7e z>Y6kV7azhl=yX1mAzD-9yZxi8$gi%aXGb57M4;1RyVmNkgYU7)9E-y?c>T2FwxXvf z_~ELqzVuZdlx2JD=jSIGPmp8Q6eQI5ZbOsG<9sj*&JL~BXgU_ExW2LB#AIMh`c7h&i2Ou&>VIwGK#eVa{&oq^aECBoXFEfI8T{uz+Pl6bM>YwJ>udmH01D!|M40X;ttjsg_I^F3Lzz98p1{l#$6C)9j ze8_I3I#w;R|5Z8G6-~X>!cZM~(%ZwwMWzD9Bip@28t}Bl3-Dc6@Ox_+cG#Q`PJFcV zr$arJVsTse`h1M64U%$^mDf{^ooVp3AGK-~FN)fdAa{9j?XhiA#R|uqXsjegt8tSY zy85j~+PzuuB2q>2NL8} z_tW;3Ah=W*Q2N}`ZSNIC5P-Tt;W*rB7E&EKw@-3$T_J6=Q$?w0Yr|~45waVgUUc>L z23T5_D^X3BPBq)CtlM(zuV~RN&&@G2F)_VN!rb91M33wneqUy3_}@haEOg0Tl$9gbyE3E!X_f3G>;Et0KjU|lqx;Y&8IHK~`J|&VDroI%^+3WV2 z%>L?osp0i)%+5JI2HaFLaR65}lYZ+|5HFY%^@hLUb>e5kMGh6Dv$~$Ts@E-OuhJxW zcw@V%L{bS~`Z2ngO2#nF8}62`jlMNM$D{jK+EhT#vFCA_eaCHYt{2Az(h=-{>@~U2 zM>yEF8Ux!LqPw+CDyS7kVl@XJ=;w{?7myeIN;tB~wBson@$iChj%T z&jc@*$%puSbD#J1&i}M|FKEg$>5e##@Hc}9no=PL_~zJ(Euzcguu(B zBbTj4NTe8OX?|VZ%nyqH9YyiML>D-C#LVNDg=);_u28120QEwWgJe^J6Uq?7uF^Is z*x!3X*`sJOgVMsn^~`w%$>?m^s6)^}A&sLhXH@YvSwB#37Z4*daFkhK22g>RBR4Y{ z^x39tUEjTvep>fb)xDn`HHv{_zgb+!O!-efymCn;ep`g>4;dwWA}0(CVeOpx$=7iJTDd9*U{aCfT1 z$g$*1LC4_Ov@}`yF^9Z*^H^i)<8yK#5z9)8A^X(I3bbc0S$s{=V6H?ri?$rJ6W2|@ zP{bh0h4W^PZ#{*u2f~O@ghSOdH62##0XZJq4H^)45eyY`b91$|wXQq}f-_+_p^w6P zXs{6C$`pC=`&={kL}#{;&|R(9AeIjRg@SorL&nG|o!@KhgU63|=sE?lezCXdVZ+{(w)l5%=f6U0Ivr#8u#Fpd;2Y{y*&4o@Fo#T%_!k6h->$-@H z9;ayxa}%f1;>$P&e8gT?+drRPm3{4_20W&Mk(v~k1y}wpmDsY!v{T*1SR#8n;DPxTTvJ+dukcn#%%Yo)%m{DGl4>{)~ zxZ&mWJxdR-LKqI|E4MOD7AUZy%Us($-@gz~8bdmRY zYNL_=E-dDvN+E(-Hi$k>F4R?F*5tv62j{2kEQI5-Wx!j1TKIn1i6RZUdrFhUqpozhn#Je;oE`CR*T_0jn?^gQ6D;Qi68`T3FAYAUYR zn(wvJ5O<9Dm%i>BJrq*SPm&5{z916A-y|R*q)EbSf@G}*- zCZ7h*lB+)ND2;6mV!Cmm)ARNDHf3F3R0Nh47KsWDQoIG!f-C^vk7V^K4Zn|bgOw#- zIgC+}h(Qn*0>s+x$MyKG`sXiKeV_Go40*ko(UUI3fQN=8|d4Q>B@`qQMy|=yWO*Ni-4W$&pHfc2~j@!TcB{ zr7kY-7)bl!sPhFk!YcVXA0o{#LQF1S)1=(?C*Zg&{(X&K(EBNNN8e{+NznIoYNY4u zGH{a7sytMmEP9?5YVSG+Z_1HHT9!@RE2pJYifNbRoYoe>2r3S`P))(bw~(%Bzw^>L zd(;}5H%(l!Xt+czXQvf)klX{C4l$=mQ8FrGDe+H{Ghipr<@t2n(+Fp5Mu z2{dAUBT%zUn77)lfxb>XH~01usv)rbnmG>mTSO?Y5dw*b$91zCf&>{Nyp;&YLhz@6 zVV{)Ffz^R(oIq{atz$Wv59Zvv<2wd7ss;gFjEVW&Pcx`#6 z)oti5s)k7eRzy@yUUUfuSC1y7ZU2&kPArjGdRZGK(ilG6iM*#{KwW3Q6+q4y#+pIY z8Bzna6Z`N)*I{U zl|`DFMV|Uy%Z5GcIkqQ{(k~{Po0#iP-rMFC{Se975X$Hw61i3ZGlKYI(l5zz+vDMh zXaE3!baZ8yJ?vDWHs8u<-lTmW#k7Gt;>6hfcOJVMf)%K3uDb50&ucpsp40;Ff9?4G zos$&IKxdQZB=Fn?@zgpukpJ(%3@D<2wyC1o6AjVR>)J9RlonTX#n{xB)K25lo_|S? zF~*9Tc5o!&%9VBMDyL9ZUmh024c`M0VVM3@>Vx8$xg6e7z`cSO=ly|-|Mm;B_BPb! z`<$4LhQVy)&mKo7ft|J=#W_Q`U5D7;Pox67T6D-Q!;qkX#U+w$1) z8${rg({>e(P>#j-yyWb;jeSD@w!qoujOUG%=l^;-*1T0lCs11@Y`Jj$An<-O_)00% zXhijRwdM2U9zt$?lhYGV36aR5#eR!tP?tSlDxF>j@<1SOCM0IDET;4{{?UM{DTlOo z<`fjF2_Ac^(F{(m@C8*%Oz?Fso&tBcP&_)u#P0xL_;j`PnynDW3vpQ-0F43B%BAjj zDvHEktkVXPu{OcbY3saWs2<$KCrN90v}I{y!|QoNBsZet(9PbS6Y!EA@N&o)z&%A4 zEX9HOGMje}XYf35P#OO}fs6D5`7tiW86u3N8Q}Vx$}ko|J-AqGqv%X4haU}%xX#)N z0s-2&gUXqFNSHK+B`pP_=Z?ExD$tZ|ao7_6@&{L4CC?tFOPDT?wxCC3ZzB#=&GNEU zm5u&WSsrF1sc4OlEt!ak_2pe`_uKYH`#nB*Kk#$-8`8fROhk;ukSbv&UC4w$j%-^E z1|7TK6+`j~8HI?m>|PezuiQuAFK)^NKPmuuz6NyKjR%CE0K&kzQk9A|PH6u3 zJLqU8jN zk$~XgfPZB|)NKcReV9|K`1tTrrD@{;+AUphc%cw%r5aV&&fuB*Vc@^bxm~O_j9FyS z>YtDzJu;ythqxHhzuyaf-KV(o7Bue7&L-$DnhyK`Qw14Y1X>1h8ncs~AV*N5;FZpX zhK9j!-JP92-oJodhIh&u`SU2Sf)99TrQ!Vk@9Zjo7JMV*(=tAjFES=kM2v)chayVd zGjDOtkaE`>Jw2~*^xRW@)<60!TGQ*KjSgePU>+4Vc&vCKEDUV=aa)e7tbyR#(Fa#z z-D;WM(Mx%V_D7m#-J6(uIjR4w zY$-)HN)rLxxdXG0YU?rxT=7K>$nU2jH0+zY3up{JtS_#a6U?R%CFtb2nvR=?reOYQ zphtCuh`a^kZBb~t3Io>?ReU&=B*^`>%fbFEOI=&>_5n)t(+i=!`OPetc-&xYL30(rfqZTGf&XNXcy&U(f%&5b_bYUkI zjNiT$mQBfUXJCzFfSy5T{N`>zU@Moj8VH)UFOZ)l5Xf_I7P^qbS+waKJY*8{t`W3!9}GvxcZ-F@ z#l;PJ7~tyTet3AOZTP4%fdyZ^2{k70zS~#`Ui*M{2K~wF3MO$^jmD8p41b2C_)m)% zejA^5`?avjJy~%DBN#%R@$J4xOb_8qU56suJT5U@9dZV))NHxDXn&UrQEn077#JJ$ zcRsoQY)QAzo-+N3t*imUBv2D z!ux5<0~bGh+uhw=#KGfo-3;CNe{F{@m}ytsbREU9fvmte$r0$cvK;KxM{TzRtVSAH zxn-NhsQNxz2vQn@-4>RPD&Wyl(TFC4!Pq;B#hJrRV6rcV=M+_h(yGx?>_H3f#^o<) zvZo{=UT(D;f4uhHA*fJjPscaPwAVcB)(+-)skW8$-9t#DvTCCd&LlJQc&_m8_*T^* z2|FpP_B>xo=E5yiuX`#}=%uK|JZjpsvz5|Rl2KQ-INlZ#Zg)6y4Yq49#g(@w*;*vX zDMJSF@{C7xS4ZuQC?fU?7Z*B{o)agZ{ZMhY4}jSZMJKTFzz;pi=pPi zjKU!jO3Z(u7TGN?RIDzBetBW;t?3C{EXNiU5Z}k{V#9@j>Uvfd^xTcp#+q1)J!vTz z;Eenyq139s34$Gn3S{L;VSo|KJ>H|zmRzjNKv`W3UAI6=UQ|`m<4!aF<19-t!2Y(m zwZfQq*&TdPser`Ua>oacvHT|}8!YxyH#An9Ay*s<6-iN4{m0a$VwXT#3kdd%K5NRD z1OIG;jYLZA9k{>E!0pk1gt-fcYpRLIaDKQ&ySG?RAs6wCUvgF}n z*uliej%touTVSq^*;H5o%G%A`h5c=k$e4y`2al z1|dU`Akx^c)ar%vIxSVIujslUFlm-8Sd}+Y!&4CL|&S0%$-n+crlu}g5_zl@A^A0E76F(50P-CoEJ0o zixU?X<1?^tRvtCS{$Cf0OE9=z_q!|}#RiX9hb!FP% z*91M4%Jow7YSad{UHi!V|ne!qS+$C19yUYIF@jbADF z(FVUxcL+W2Dvkv`KF|XiPi5k>$t_`@wV9J2)K?@?Bb`Y|EvHT24`^%f>mCj)!C%(r zm1-iSOotNHP(+6+10|E@3b1A>GGcg?!fsa4VO7;LMc8yP%r~-Y7}n^(R5%bz1!yrqXIC3f&x)I9Nc*dliU-PL?6 zP!OL8@qtPSZ$wz)tN=t+!ga@af_}K+iXb#s=SukB8GDc%+Dm##No7b0R$WWN=31I7 zyL!U}SD{$@yBWD{jBr(!LdOV0pR#ZYxVp5AjI_Ea+GXjY*ZnWQCHWzdpqbL$F2gC3 zV4^sJ%i_V*+(zaz9D9mpVoJ8iZF)hMce4==2p$O#q39 z<4g2xYCmLic?{_TSJ@?KaP<^*0b#`|4WSeNa?XI;1=KSWpTdvbQP0Xe&*m`t9cscoyZhwp$F!eqAZ*q;WN*%UCOk5O-sNIe#^ewXgQwOuDy$$qA z9uXw_Amym=h6|-r?NS|R4eQ2MMF&Jq9O^*u~=S?!LXg(M4qa6K*&<9IvTE(-A9pcfi2@H}i5 zvznwYl3&_)<$(Lbzq$~Z$L#z#vSDoCo%sGRs{^I*r}69jtUa&Sd`>9fJ#*{UgtA$O z5s``R)Gd-un4&3oFr0}jjYeuHY1e~QxHo%?5Eu8zvdH))tGZz659qJl4=r_>HG5N0lc8!PdkRef4FUo_w=Kq$q| z4<*Nt9+F1{eckhBf1i0&9r)TKY|6KFQt>0au2inXa#*wHE)`n<|3-=w59Ne<6W<@y zD916&%LALqfxiY~^KhKDZWDNZWTrxJb{5AN6Ew*gv2f#)vs|^l6Cn)ee|k%3n;dUM z)co^nCy{M^H_wpqz{#MO!~_Cp3WhGG)O`5wiJ=00V@LweyA?k^#}xfp3c3KMr|bod z#vH%G(jJtKPh%f}GY#t%IXIznBb}!^%?Q+&)Wk2A<$)Z@^xLsldgHvJ*tvCWU#K z#ON_p8)GKzDo+SyhE>g4Bk-=`qe!-NH6!;+8r4MZRNUMkMr4_w=_=UNm?J^Sf0k2a zw<3h1qoWTq&pPFM6J2g=A{SE0Bo!A@-Jr4&O9}I%LNc`jkJMkv%x>QEMbIIW-LYhDKf` zlXQ?Cs<3D#vUbMk9H5uO^b*JW2GS&WZDjwvP4Ee_;BC3z9JE|+ZMHpXb$V_uY3gkN zAp~72NSn$Xo>@PZeuL&Vo>V$W5w?k0ZH1plroxNah_kI~JX4xJpuv9*#p#p)xOmcI zIFuD9hQBB4djcaQWT^L>9*Kr5<|z<$n-7%_A97`%%byFX6mics+i zl8qFaj1E+DyC#xJw=PkW#!e-ccsC$(t#-JbPhOX_3dT+^`gzO-4Mrg4U8znwLrj(8 zLLm{b^_pkhWi$Gn%9?|~ME`DXe$Z8`{C6Iuo76x?{Bu9ZY_AYHyvQ7nIl%-`kTP~a zLn|VwP)Y{AptR;IT&hJt#7roUDMr3qTC1=8ap2PsB$C*=QoPHIjqxYG3<2_lWf z#etS9yaZG+=(YiZy&_Gmxp+2pQLF1J%yS5WTc}Bx0y6XQ+{ZD{LYMB}Wp&E8LviQ< zt^URf(bp}0_n#53l1=b3zMn~Hy*fo_A-whZHe})4VweD22e=gFiv2_)I#4fU3a(g~QDfWCju(2{3 z+zI$Jxd$DGo3TwLHyRrVA8AEUCBykU>yCj*1|zCehENl|8t*pDjZ%fki>-3!mGc%) zj@s_rC+_};l*o1J#6zV)q7_A?mA67Kqes@{DlfigG|m#h9oE;8xhM?h(Q=igA~qX_^iQJA+dgi?r5qZ{yyhzEE4 zllS?z?8uM}04^?aS{&~)}Aww8qp!|a%*&fw`bTpmbB$iKOL-G6so zTz!f28jHytC)W zbo`b7^=OD<_v+5lQ)aV%YM*~&^M)`9=JW&nFx)N4o*@+!g&|fPDa}Muutp$xoRC6X34Uqlog}IA4frm)5vNlCVRc#fWdpkKtQ@@>Pgt$SC1*0Bch8 zG#~ifq+bvGd{`MMnpLWXDNBwRJpu4}#@Y`AdGSFgaXHeHZHZBA>$f4e&R8%DQ+~&vI zY`j>M6Z`*^NHwl`MID@omNrvy{Z&jGm`jk-Np8=_yff0jcr2m5aE8?FBQhsX-(fV1 zxN!xW!k)F8($s-dO7R}~u9w-L-$77Z60>1Xv1nPi!Kn-h3g6>Q{6ZQO_1fDq7~GLc z;+{s**x6^DZ|4lIwL6`MvcqKz@<9{F;TT_6cXxNsXZT@W9RYW#5kC|!&apbKOfcik zWxxNf8C59jOD+i|hNoI` ziHNj@)YM@@;i(8+1#&q?MOidSBH+)NjE}&~1Sn)EX1}NOA0r{9=IpW397N0;1~g%E z?U+#h>LRB%z=inoM(W;PZCQ!ffd<^oXXulljO1e zW(8}a5s0T1_d!3;RhF~ohe!3nKpkXc_=R@_nBstP)2DkIXegWyK_WOo-^7Bk!QZ_h zLg~NpP zuWWVoO_!xS4O|;#^#At=A%M~vNEsqlfIBj!0agT1L=4x857H=v0f~TL5Wi{{CMnp= za5hwzWVk(OPhvWa^1HjYRy)hnb=bC@Hoebl%%6y~rnkF&=%4ccD=lvT=%MsW!1z5X z+>_sg<>X{+axcs+_JLh=PK_s-fc_&60w)+5Do#g&N*k_|3i6l){jmp87g`g+USVilR0~u)4FdefH>ugF(L&P=9%>D8TA*hy!Ed z|GY|i_gq@%N}R`FSRJ+Et%{X59y40d;T4@#_2_!cCGGj(sUoTd73PC)TA*|hGwj1! z^5=6qdId?dLnU6|Yq_Dvk}X$A`mVFpz)w2*K9A7rEftkIt(ar#eiBB7G9u_jn%%#V z?k8;UJXzYbBii7r?cZ6)$YQdnKjfLk(n)5f-$&4-&9KJ$>-AfaNLNJ~`ewnS;@Wew z?ehld87AO(-?+&beQ#XM5lTAE;oi9!(Ryp5|o+w@tS)>t`qei&V-*1{9zk#?fOz@0%)M0ea8 z%e`5p&9QP?Yr6C8YNWUcQ!Mt(H$&@HO!EUYJq4hxZ>(sWuc zj0{4lmjDYYxw zrA(Ph1t_j53#XyPdL6D6GsiwV$H6Zh56e2S1`qUJOlCDTHAgmOW4RqbMChNPbkx%_ z@5f5r^MI8gyGxzzyPSY#H-wBYri=5Ks1d|51W}_ulsC7$w<`YYJGX; z$=O*Y;yjOX8C~}wN$AW}%*6=5>5>Px(5a36bG|XL*`&}XvS(-M1u=<(#`CkxczUhA zg$svmZh;hfzy0XeYHg+a2`B>plb0NQ%Fd38#v$rhb2!l==bqwkIfF{3Y?PfjT1=J} zmb%MIXdW2Rg`vnB-ZFF)2Hk&78&KJ%*0FUM3dZ?! z@EC-h(>WoR0T`Q_md^3bh_(U)0|mF9_KfDl`o)Vlrp2Gwz$pH8^H_kho!xNeW>j5W zUBVMLCbc$*@;W1Ho}69&_Zhs?&91NsZ#IE;*@UO(ya5p8C}TJ94$fOxPh<~z9h{-& zRLsSg1}0vs?buD#JC@t&mY&0EM=EXJ?s2PZ0;yIayu8AP-{ZS92fG zL$gb4I}BrA@q*Mten1YocWz0{R`(2{#60>R~}_- zA>B$TMg$|@a!DvB-yxQ>Gpn|<+UEDBLsh6(Z>>z6JdCACq8ZnQBLAR5<{~W1?s)D= z@df0|)43Hi#{J_>Ot~ZsJ^N4B+;2j8=i4mj28u56i^T1j=EmcDhs~u${>%bUv6ZYF zm%IH0zZ+%_pGg60^1QYeZOqN0aO?hf9^8gDnkI@H)W2@x5Dv1+p~F0xuDnqru4F+| zulE#{^9yGDm|5WA!lic&Gup=%P2R8?fj;#hFj_*Xe;T9OpbrVc8Xmy%=Q;({)gS6?EPjLR?p;PN41Gjf4^LwwDg@g2T6 zWkG$4AoTYH^|-#i{u3A|rEE;h#gV?Q(c6c|z=)r|2_}q_F+6C)+t=apk z-t_(C@)F{zc10rZC5~=z0U|_j@Q&=bWXh^kj*wF?6`#w|kYg$9*12JF%XeoWevYSRUN_lpdF z#%}M}mGkcBz35o7@(!OHAf(p(ncDm>t#bh!NJOHu!8cobS~5$i#850H!(t2-B7R}_ z<Dwxq_@-WspADWp#%qC+eK3rzJ-cZ zGz_Os$IPmUQs+IiN2(wei7cfyv!9AMT^EoFQ8Hh6=q!3_0|GqC8glfP&;i`dD3-*T zW3GqRR_($hPMrP3g?$O&=ZMN2>2e)Z4#j2)qM5V^5+=^=n>!mjYNs-kK2KL0xHFCq z&7ZE}PpIHgWA2a>N95|_YTt`s=FW>@OIU2meD!_0X<|%^EhVxDoz$P}ElJK@+i7Ut z%O_i$3_&m{8`$Ri+guce&SRlpf>LWb{4 z5{BCX#yVw73W|z~6v=ZkTasgF5`pr(814tC?U!Iv?`Qk2YmWyM@R zm@;hV2|RBoeBifQNOfjZCHVC+^DqPCcN?vjDr0duvwqE^$|^8?a(H24XTyZt0Lh^4 z&)4%A!LRpRexI1SKteEC4g#pGd3Hw&i(_yElWCgB7+(He%?u4o3?S$|X7IMYG99z^ zc^vRrmTNA$z}Ci&?43XD-4efH--%!fn9kuX$U71kq*E+s__kl+uoRM;mo_n^q>&#m ztuepoGIzx09vAa=VmNCk(b3tdScP9N;j-o4gt`jdn)`0uho>F&zMT%u0SN~H`mqrX zu_|efz8?=OidqmisaI~k_fwpTC9D>?!;jT0?2dSXJ>{A?>?dvpmql5$GF}h9CG~mZGR75FZ70X#ma%B3 z@|u)XXFq%Fvx!hE)ZD@hhOyGvuM7$hQ`LNWmjI1P8Z zKW>KhWgbnDCFR#fKLjWHj_6gjoSbR&*t%0VFONIPpX4IN56p*W54jw6Wy#+ie0l{(l+N*mcJ z>A)*$mG-*hyjHato{x(s?w>{_!vj|XQnoTN zQm5bjdT#Lb%J#A7b#`zNk*^tRqG#L>;yQoo>hFxV@Y@RfPTu@1H8u6fz)iTEk^&+J z38~q9DvK30l!*>V5Pu(qM22Bfo#YbwAv6Op+~&G!+mEyQ5hw}iUE?6dyENUP+MoZ~ zVK&WTb=GbJ1PdjH{gnC*Z(N@m2fVY19w*AF30`*oG&@DTBY*7C*UE#%R7S6SXNuXU6G zQ^2c$J{9eiUQ~{AFCPT%Of>Zpr;qdluEhmOIhMT+$uw0CwkI?8<3yTi(;#t36e1bT zqn#bPHZhYWqkrb~OUu>VGLgkv++~jdoZH7lP%`Wb#8?@SED;eB$<ny$ z69&BBZaDOIx3p+HeVwG@3i&E5u<-dfI54uo9V;qj964&7FikWHohz0Y3VWX(XPoOR zu!s_w=P9r7&%(%Y7!a|O6D zY;QO@H8nM>xVvXa>76#6DTPDYh=~aQbOF!I9Wj1k!uub}hnwXJo42NwpkpmiK9|pH z{=I-3_zJok-DG0PjcbJ(Y%banBAs1U*f)fh)>hp4kA5h^Y{y`3C6`zH7*yNcf5Nx( z;goCYN&E$*-Tt@NSHm{o?R` zw-gS|xxDv^hS1P}#v&km*u7!?W_#{ZkO!#2|KhX|rZ5;a4-WweaAtEDnX}Zsb$U-C z0uiZe;?+Tcwbkvd{V;9aIj^j#Gr6dB?8Bc(jktf0<21OuxR|;fKZ9_|`gQK}J@PH! zl`x6Sm`uP!e*!`_ZPKB72!%XLDorAJ#o;w0+*pzS%bE*K1Lm#LMqo=9)%T5a1_|MT zkZaCo#K8Bq5%-HM=ZMhWio55$Mv~C4Cl0;GSNvg&m|(rm6&ZhU)8)FPlCL^F1vA)$ydVW5`kRXTAjO#@`Q&U~~YAz|-)~ z>YoTNw=a2MY}C_=J;&vE1cX1&h+st(pab0C1ny$)KX@Bq=`p0&!nkS9q#O^HpT$x8 zAq)LcSi3wm^6c2{ce~}dzat|w{ngZZ5A$sw1YM0GeTrv9m!053o^X{Kmy<-+XuE&l zVO4g^1#t>8q<^dlveC}dQ;X9%qd9`W+p+!Nho^w6v9ydo_p>RkeJ1&nV@H|o}9Wp#QI{U19O{doC%XqRGPC#K7d0^mN}!@6mImjwqkNi(QV&{lIb~T z;blGsPEpm6?vLgmAw`3kB1QfcK|BapjB03B4)>Qm@#tgr!@ICoh)kPcp26#c_=KkKox|7Xzyu?O(}s>tz}6amG@NbfhBm;g(RVIw@UA_Wbr%^~4MR;1 z*%RBuQEh&)r*ITb<}IP#s9|RM2*)}&iH`764h4J|8MJIorCp>UAzk_4!13izJ@CO( z|EB8Gk8XlE&nv@}WXMOD_ZiPlL4-iz0V{7O>kwE%K2OKj^ryP6JJ|Yd+S)de1vbMx z?|Eq8f*F($bI=XQVd}6EmE5edqQJ|+K^Gch?>o!vNm?2PQM8bXFkC-rpSrg*=E*N* z87CmC7%%wwTof-*nJy7p=gNV!ZYUD=e?46VLzG+BRcT290qHJ57(_ax8yvb}K)SnA zU}&TpgrQSFx?4gzhwko(*9I$7v4P=H|_jbRuQt@K7{kE6wFM$!~n!P;aK6q!k8Q@VkZ}GAKgL93r z?G%aV^)Asa&Jx<5YcuGx|9p3pf9FBv+hGKB?DNNq(QGXvlFa+=jr#ifY}PZ^573O( zOeKGSm1!pAL@uZj-eYw>Ug8xv+wI5Zvy%x53VQx5%3^hb^C8%o!o&kXL@7KeLMgI5 zm}$8EARfm;YxUVwcJtQO)_b*&Ri!m;f6-%=SEl`NWciVmh}{_(8I)9<@}J=fMFu5? zy8sF{g?>FqMbCw`kde_g-r>(NHfHUy(xqD|>e>bSOXQe~!P^D%S~g;^ zVzjr}cu@;)yZ=(T?Asbj=Js%HyHltCpzk3P!KU1PW1c*EP>g|Mi~wt2Y%J7dZVc#r z(JB{xpE;uUaNS;GXJDeHrjREoOUI?xQrD$Gh(2#j=3{XGxb4Hgd|RkvcRQ)W?YBLX zZr2P`sT6`;&bD1dw35-hXC}k_-o=NKfx&F=r_t6CAxA=bM>>mJY_IysduP^|Uw>H; z;Llu)In;%5;BDsCPXf-xRD1dq@%r%P`o+3868Ebxs zkW!9G!u8@EQgwd(RH^|gB9IN^CtQpA4*B+FIbyEgw3^0CUSuTHo;SN}jC6wN!Bkbw z$_;Raqs40~ct5{IvT_?jUW9z)9|WJCIb!4jKr7R5V~viE-tU19j|AW-{mwgwT2;O* zZ{JR{B4E>xjI=96q%SFKpOq25#PKBtI>Di%vF^PK#=pW1xOYQ&aq;*DkO5=$5P?Re zcTR>^Fz0OY&oB_Kl%(Wke`<#Arr5;xeD%cu z`#}a=<2eTI?(W_m$W;Cl(!Ew!$Cbh+77^$gnEjW=*_qNQYAE5~+IhJb?Es0NYDVX{_Lp13l7{$+vT20j7GG_AN6RQhF`;WDa^bf@mQ=4&T zYq4!Gl^FY0UHdly1|Ng(M36PmU~* zkg_6Xq({e*jQf%oD}F|dSq62#w7SV4Ra6u2^nco~eO?&n#;)D!V``0|e)WM+2e{|N zXEY9fdBL!{aBsP)t^GtU6(r$zc0d@SoSMDj<>q7qmp9#=K6;*XZHL@@pr>um9{E1? zGWfxCX*c&27-LSzDD539NYZClb-tL{9Ys0@Png*@RDoo1x#cMIo?&e#5 zU`j=RvimJ)4iIkBg5ShMAg@mgWeD0=uU7Ml`Ysd&9-V}%sj08?2nld-$erJ>A^yB* zYHEUuHTH=6I@)|5{Ns-2Ys^CC_eD@8sAH!~HAu4klU_i5)_cPC+%8+$;Hq{3(%N{@6!>^ldXkpBhiq7ZDG**>%J#1Hrd)0ue8I5q*@I{Pxex#IvVPo0A~SyT23{CH*l-Yi?gg+ zg`Dt4E1Mvw&mkkDAW87m6*gT>Mooz;rJ(xMLC68uA3F;RD`EOmM;jl(KnMyJc{qbg z24#|LK2p>b{y=12jW2P#k;H`wVde7E{)MpLrAt=;Y|AG7gPxN}URS&{2GiZ;G7YTV z#JYKOG+Ca*mH&AttaTy$M5wwinrz)a4qO3+$eHBXK5uU zylaB_T^|cb#vKS9-g;FYSG0|$W<`9g`B-z)sl&-CS;Y#BE$`vW6%Hi&`BLn20^&%}XrORH=_d9wM|Fgn;ra*h%yig(|vtDt(0hbi83SaEmLe!Sp z>g}K2)hs1B>rEO3OzX8jKY1mNZ)Fl0YlU4Qe;aK(ol1Us(zpjFs$amN2b%lN&b8&4 z@s4xpk}oVu3P!W<>TWVeawK&HnMOw0N?sYPSgEq$*BV}?D&{Bcn2lx$O_QZ;g`IEg zu+?GBWI~VRL4!AYBlk7{;BS_!SR+sB9KWq01LmEHlF$4{$NHCfK+5Z=HV{;7h#bT6 z3JSd*=^LroY6uNJX?udcw`+0Q8&Jxtx#PCAY4vc~c+jxrI5b-a?1Lrif%5O2c*8!% zRFA8D#|vJpX+Lv&9V1+c86^zs)SSK`8sT&wJXGP*$4m!GCyz@RBp4gn8 zc4hnAO*!?+#l)dXm=s#a&kNiq>H9W>?q= zUH5wgxc8iZyvQxK@4;UBiQecYdiLX`274IUa%(|BjSmE}H$R^kfpXx+U!L~q^2xil zE9QBRBBV`xO!yhbh7Oy-*1!9(lVj1`XGk<_{~ln*Py?^;lp8&_e0a`2WM;!xp;{$i<{ZAAqz zQ2+5tseGy7>DujicP72EqSEGgruhL3)@epbK|K?bDoX`btzI@c%eb~lxJsFuM5l_NZ(GN#1Qbb%M3CzrlKTlDGimJq!)8)rqdy`wK zd*zv}vaR|bwlSSn8;*xBohug}=)4(3a9*9tZT)TUgV725R!)wOqj&c1wz=Ey{36?t z;Gynd^ThErl_{UW)<_y<%x!9~kD^mfOQX1qyc{-CbtAc{qya_^T*4O8F)7dk*@@}< zW5rwDDprJwNNLy6)Ku<6-pC~(l74BPI7%VwhJ^CSqrS?L*}a(TJ>afS6Z7!A(-p|> zk8^f*{#DyLX_9Qmh>0moH-C8XFsn?z7hdzs{2# z-S|NUzRcYG^^WJ4@v{|=r$3trl?X0^9jR4%axFQryhp?CzkjJwS#lM~-w^ld0d&^a zTj@-X$Wz5f9+ZglWo8k|5>8~@96VRg@g8_+s$U14)FRWj|s zR$Tq~$dJ|Q@L;>E-G09rKd>%pb1^tlJ9{WnKfg;(oX|VjM6l58XRy`s?gv6PX7~8- zZe1Y?rJir!3*uxXly&=MOh2Z21JyR!FX5gd70X)rK^d%!&uf0Iu#@KpH|45L;Ya9+ zo+ryiVWpnejrrI~DhXH*I89I}TR&Tx%fi4}VqWFUEFsvO@}u)-{p^FGYo}JKFd*G` zd!X1XOHLablDw(R489@Pm-xxaz5I-u3k_6+cOcxQ#Gq(l};tT)h zetplq{7OH#F$VmtXEcKsj?7amwl_8L>7$;(8$;w4+5ZKXFsS{ac#?rY!@7oI;)~3- zt6N<`ftj4sP>?iA?K&zePF$96h|0^R1OJZqw~2&HgBuj7wptP+G*^$ zuNKvlM`>cEMnPD{RO{hMp@gQ`<*Or5AIM6C_8Sm~?{WWVrj1*3t@hF6s4!l?D}LtU z;@GDVi2&*KCHl7;GUUj(+juG}jptK6m#W38;c7%JYu;`S64U148F?!V*U2^W)BA0c3qDumGVepMRon=wt3fgqPMq%ce^A^ zrgdK3pdSo1wFRI#-+XPWD{z4czwI2lJF^DkpFhhh^TitP9b)lmtA3#2)5KH{h*D+A zE@|m_ZBJ~pR5786(x-pzMiBW<`hL!8f;=Wu(Cd77A~#F67CDKRQ*4D0?F}6;DKBgy z=&0!d1!daO#>eVl=7>L2ogc%TK3*?!{fmm=Y+K_B(B#NX_@bv$R~Nb{d>cPhPtpwj zs4^Mv0lSkcOfI-s;D5OBBnMT{uQ>wrq(LitP{~h&OO`eDDRc(zw#QY<1^n#nTL2_H zps&v#AFmaCu~YT~-1OyPwZLX! z+R_EC2oL`(y9YN-^u9YcaZiND(;o~c&6)BO#7<8)pBRe&(rnVNoWZWIDk{sk3mUh@ z)4E2^O!=)*;AjOqJ3m0Ap$`=;UxI-ONn(ogL)Ne}H07NRL5A3n>NJ`0J>}GX^po=C zdR+#^(=yp#MVns_UG?yC;5AXdzFFXT4tlNcIM(j&2>3+;G&K_WouqNzETf_Pla5PlcPGQ{q9 z4n=UMH(qA+#u}Vwly(4lNLY~HI{zI1fo`L#Huawh%N-RdSo z1_|N|0ojpkVG5t9r%hWMo7956CfiHcE%wr11GpnQKN5Xvcq~^zn4-g4p}{xYTzpa2 z#SuzZ_b}HQ;^47~uDkLp{<>)cA+Oleyb}zS*F%)f0s-`Rw6QANV7O+c?SKnhM=@4b z@L_YKDSE^4yCQt5YjtZofA1n66*X0f|JT&x^x?Cv{PR39F*}|wMol2+tSpTh3l4J~ zglKUsr=Csd^|oy#Tc}a7s;A}Cgdw{*iD|8RJr3t z9J;MuIZGkDIta^jruRC>%btfWWTek%6kOieE(;dbjj(Jh2Wn(x)x2ptdwOd9wg&YD ze^dm>89EwI9%2tWs#|HTw}waMr3?`fd>8+Ibcd$5vv}V9!g%5ki%y34*$T}V;}hx& z_a68_3Apx4HvilZcbpO3ZmG%TYF|y$ZK{|C`J>C^*4?R4H5~ zb$s8u?Zk_!++{%WTO9)5+xSTb$HKjWH++6)i>9Ts<)i>=y^QbV*&46o(Q~T6vGsaa zT-OL`!(L&5l;Vwp`&XXrE6-J4mA}si&vDmVIDwJZtF1S-ba4Z_ogtt|BMp2c5lt;^ zS%!6nq+|o1qo{8%L8P2fVK?S?qjgpXDjwy~vtIEt*tePewcSlKk-m5f5nRN^ zdW*KzSJ5OeX&iCheeHnIxX8QRxcN@|BB{HGsvXaH>pJzy&awZ-;)sc7WD3LXOWEEI zMZ7m-b0uyLumj>x?W@t2)LfV?T=}pR=f<(4bQ?*kspjHwrZfBaK^7ce#i9Z+LLkB0 z<3(t^pI$jvj-1QTMWNA9$A|7bWJLKcUZh$XVdf~yGE`gFX_ru9y%QFeqjys7K-*p$ z6O;U|<>3ul`B^fpUO3G|UUs{oprBCBk{Rk)HElUd%qoAnH(6-A*s#{H?9h%z$flo> znzks+^8?kO&GXMk6f;w^5}OV4A6)8oY8VGyUDht2W7KDBN>cD-gqY+_LK!79BYd)x z&`T=%>U7jqTY@4-9+4u4UKN0Ni22MHBC9A-@g>e48PS%;xwBC0nBaQ@zB0kCnj#8>7e?4q5d!k}^9xLD5b*1TlfD4_L$0D)NF>jpWWB9i&BR!OL` zXg|^Fq<6!UFKfR}TZttx3c|`-SlkJuAF&~!Gg76Pn`E9&{PAUiLxz_u9znS0)hrzN z5_cj(eo2P3wktX|PIc@1gP9|mgC{QtXV#@(355yupa1TD8+iJ|F@a5SuG`hmFkvX- z6?qi)uo=Vb@p2uC-X~bXxJ8pBe?|YMvxTMW8o096*RI+3UVNT2Gki@OD-I2W0rqx| zR-&IPXn~%qlBa#Zer4++9cv#+mA>Zh6a%sLU?w8EV1{Cy4$8AYyo3mF2yV1*??H?X zcy988XG*->x0jPjC~Y^}TlM|i5c7|$&GXiGPep^5nuim?=y&q>{FRTN`*zQ2kt--ADR@Zip1%e;SlZ)q;e3@_Wv;3L1(<&fY${JD(tURmR*#|vipEFXlr-fp12^R zjHq->A=Vb;opCOUIE5nJATJ?eXPr$Xpe6gC5WK)v!GT7XmjLOD@$lxIE9R`Y8GCg| z+(Ze6hqHyC^RbAkS$fAV`$<*_^b|E@e(e*uZ&^DWsTCRCsY$#v?!;u2&<%&mQ2Bmd zKrlsNJaymamd!G?1kzyjhVpz+v7Ma?F&im-n|=2ELD(XO zO}~L?dbZW;rjHwq%BS1B4!V)vR3YrJwg{^Av@?DmKEWp706cj z23mAz36m5!(J3NdxlI~D3LgOB#3&+AQR59@vT-ZB#?gVWm{btrM21oR+;|BU00iA-X_Ozn_mp)k6XiR(=facd>r&kCrxuA5 zRg*kge(~m|3e|@AjyaBEsH&gqkJ<(?SEgJ(%O2Sghs_ z6h`t!@h9?A=0Ewcty%|81&*g-Rz%2WKjke6I&DYNhS%G!c-kxBo3}j^dbk=nfLv^r z99qB9TVX$dGdnL;#~B>8G1Uq%Sd(N45%%S(iG#92HIP2qrmMx1Cy= zwA!0yY??BpIWQj?uNf&6GQ{@GsmU&5;{$$fQvUOCSE=Y8J5OLs?1sQq_v4PLB}vG{ zG3RuOEjBo1Rk>=>>gK+?ZHH({Un*g$!-8ttk#wa(v+l^LXC7V-OgqE|y%HqUxZ|nL zh(6F)m8_PCY@`u|aNZa9dr&0`swo#-k<$PePSMezB)P)PwrKMMZ3p?@PC}KWSTT?T zDg5(bqbhq?UsSd;d!z0McA#WUsyw}#mZi%>ZS(+w<;Uq7FfD0C84Hl$XNQ^ubA)*{ zJDsdNE&#hcufQ^Hid&xH^R(ylpNGuaEVG8_vfuC$LKZ3{fWjz)3&Hg;*t7#>UiS#9DAztYV75m_kFG3t5rGP+y;PtEq0{+&NC;n)%cV5fmTf< z@rzKv-YzY1Zic+c?|F+ZKs58@b#S*Yk>F=42q0jR*zbIE7~Wl*+%G7EAc=IXspC}= z`+6EFCnh$i2)C=YsJwfcqZDvCpL%-IPskD8Gt5;b!2*MbWaEgAqHXwvHbey!-n2Pd z(WIugG_M?US#22&o-tmP>wZ}I!pw!G8 zE+B*$H!h$kpv%-zaEx4PvOo=S5I5*BA@q@Lk_tpiXm(`(?Ai`7iXb-qH2MtZw6{)k zE6^5(-icK4>Xq^#%j!v?U*$rW4F@Ty-pe;OD-O9F;ZII_I*DR3M&aUL1BPD5)6p8G z&E$J4yXyKqgeFaBs>hOs?-Xn%YGs52_2O&Ag)W}CI!h94!zc5M&@{Vd9{kwN&D}ZP%aFCw?!CeRKPk3gE!QnXZd&$Zd}}~L)!WRQCtjj1;!LL66oGc4dkaEjy5#(YCYM9m$d{@~yqZPfXPoG_{gz=}!MB*#=#iNU)N zmmv~1;|BlnJkx_cUbQ(v#pd>3$t5L{Omj!Be)pZ;&RL^pd!39s_poE}s;Qc?921F~ zDP&iZc3jFSB26Eqw~qy8!vWJrho#}vXoF)MR#84ZsRv+uK(-_WsYAu@pkPf}%{X_Qr3l1W z)YOI)MSGF2>$~T2$11oXi#IGWgD5VJhO2HR1j4@6?5Ih&>lX}oP0_=yPDaO@0fn3- zwHuQme#OU6o*EXCRLP2kyc^L6k}D`8I57uHw#kMZZEg5?UGJszB_0}Y6sH*XAD|Y>a!?sF;xGojmL12GG4!-{R>sjLzipICj)S}3Kz;_*qyKYKS7Tpy-OQwOYuI6e1!wEY z7OQ6IbP+Q5YX6tmOS6i2BQ=tAS=Iq1V*(krT(f)gzP+0R5V z>?QnbJV*DiCqwqoH1>EISYrDpW<3#i`wK`-oVoQQc;sB~&%H=PEe*pc(x| zWt#C7W;apxO@!^fL+k7Tj78+uaCQCG8ia#*8vWmS;u8rKr}_yQq2dQ;E*qvy^`?}} zNACr@u0FW`>f(oRekDbs>URIthDfdlmWe;hqx{{rgObyagC`-+i`liC9J<9JK{vhu z#^SS#PZF^tI0}I@qfQ7nqMBZM#$8F8!-0DnEgR^*WBb-E& zkQmE|+7Hjxxe2hu+5sYxQI`90FGFA8KVP;3A|5GZr`~MEs?w>P!5bVzu0l5ICJ`tm zDVWX75epI8;IX837I>y#m&Of-U-x2!G^LAkL!%D>l1Kq_K=Sq7(y_xI6}{NPt|7THh5jFhL8?ioJSpba#mFm}QO5(SaCX3jTf9PeYr4l|81XGc) z&UD~=I0Ko&a=$BQBS%IlUOGfSzPPe3C{mL@^|eX;}@zb06kp>{^FsM3#62j z{q-is18Dj>cY-YF#tJn0uzEjCiK^QVYf$W$@G3g+{8gKM6F9odBLNiuIrSF;gs;^% zbRWK|l#0nk9gIiubUDg*1j$wBN^`sx6?v_uMQoDZVdCNZ=j-p-iimZ_{VBNYF>u#m z9Fs>KUxd1?pEI#p*OE7<^b5-cVi$AgYpMJOc0Js9v@l27*F1Rwr$52s)TnXv&4J0% z-VL;ZeZU}}NGDIDNc7;YZ(3r0uGmS1VD3SKQA2ir!hg1tzk%knj{nOSOck;-#ly@F zu(^h>p$o|+(uNvFb4XxOy%Jo#DGA{e(_oD`7Sglh%;F*V4KC6YmkGP zK~yv7M!`f40v3c5Zmwa4dJz0v+zB>J7L{PAYoJ3R>FE)SjCA#88X6ht%^>KL^z?}& z_-~}6H`C0}$V}guz`Haoq@imzD>%f=)7Flc9Q-oZ2;*=f&4|R<*jU|I16?L7l&EKF zYKo%K*Vlm|blC9>4mnPT!JfoL!pCDvWm8zRNDht3AfR~3LCk25xrPRskl^CwDm z5QD9YJ8SI-B@s@c#7sF;Q*F9I6H6kC6^(rVh!}TVKzNWNN0b2S3e7qyb2{A^*VP z!VIQ`#0$ZpZ)!#wgkwO+KXJhF3?_5PL&7pJEDz6YBFf|eOvg$T<<}qZe=3Y%il8ha zqgiyU$b%_FDxDfZWpLQAJoE;aM=-@Kgvp8^b1cY_k#rh`j21aDh7rsw$Vh(4%qI2+ z{clq(NJK#VAVCs%|5cm>US>8-I+F!Uh-zUlnbwVSZBDH&^Y~cV`;gPBd8YER5F`Gu$exc;7ki*ky-Hr$?gxF2co$ua_bsh{E(4n=>~Csyd$P z93+VRm~6DLi2Sme8w0CWzy3rE3QOJ0k-_GW85F9ctpx<=(rCeE`bKuP)+RFztgYwX-vU5B<4@hI66l{v~QZt9fW`nUrX>HuKO1qiFSphIT;Izo2zv(O~OF zrVj{%QvL6s7lPt1$vDv1Q1{3GAtaPym05lP`4 zD1V|BUVr~91;8f9{D|L{WJV}8*n-FxSU%wY zs1o}}E%zUm*g(td|L_vy*1W;GkKYRXp(}+qj$9}-{9&JiDCl(jMUx8j+<(*Yr%?Vk zaSLz_Cn8|QP#nYT8VZ*LVTRcyV8u`z!|WOgmjq#k*(G4bP#nYT8VZ*LVTRcyV8u`z z!|WOgmjq#k*(G4bP#nYT8VZ*LVTRcyV8u`z!|WOgmjq#k*(G4bP#nYT8VZ*LVTRcy zV8u`z!|WOgmjq#k*(G4bP#nYT8VZ*LVTRcyV8u`z!|WOgmjq#k*(G4bP#nYT8VZ*L zVTRcyV8u`z!|WOgmjq#k*(G4bP#nYT8VZ*LVTRcyV8u`z!|WOgmjq#k*(G4bP#nYT z8VZ*LVTRcyV8u`z!|WOgmjq#k*(G4bP#nYT8VZ*LVTRcyV8u`z!|WOgmjq#k*(G4b zP#nYT8VZ*LVTRcyV8u`z|B_wO{o8n{47hhU7H-nTcKMp2yL@pI-~-(S%-u086Um}6 zIBwCL$Y>4(ioqMWvpHTtaLXzd!jZwDQW?<^s0Zps-!^m>`mKP)^`gb0A*^W}G!E*` z#YttMyY{^2`gudpc;Eos0RcDzGGGHLV1aq?$$=1epbtpE5Z(mfh569nla2Y0=cLEr%W})HnI`GlYE9C*aa>I5Ev!VY;G^9-=hr!Q2WKR%F@ZlNC zb`gBouoyP|9}G_ahjS2}E%0#-<-`a+=0?zM1s|{k`cYd`!b5qCz_JGb+}LRYslhgW zdj=B-!OZ9&OJ*fXA$Hlb7=M9T)BhUInicFlpTV)4dxlN6Y53-!#7GcaHfWE(fpCDH{32QELkrKoeOto zQ7rvY4A`YeVKF0p;qEhcRJLi1P(J(QQ4dc@lQomWVMfrIj8I+_S-uZS!T`7&AMQyD z4IAho!}ox+3Bse2Bl43=0~B~v>TvuwCRI}~KY3oF@HHnHaOF)7gNefDdMu>6;CnKL zwFUc@!k+Mf!(wT1ck@egr5gofVcW1!4uBXE0)6Q4DULyKJV?Q|?E;8ChHL?R^1~Qn z5LEJc5DaH0{03;vLWEu-HK?~xFXTV*!+3$BhLw?s6^ITH4b$6&cy!&?ITaI_temP9Cv867zYB+kV4!t#~kTrU(mcXKPmAxDnpFzu-f zDhu6M3IPD!4$KQI%?*q~P#?#LPy&4YZ|7eG?)B%jDLkEr7R_LY3py0ypyxd@#(pL@+gk9F6Hq zsTeAYBP6&FA3Qj?Oi(Dcr+)x*mALq=?FAk%3UFmIP+m)Mn31r>W>W_$BxyR-Tz`Se z24R}+FK`)H>|uWd$7+iH|Ml!EmR$~l?Bj&$*|Lz{g7PZ=Pf#|>~XVNX~j?XS5St4znioj@4iHx#E5C6Waj zC7e49Y!D1j?||-9=%&YVB2hR>wQ5i}>c*orPL%}-gzizOJCy65fra6^Em3!H1Ov)9 z9;G=lI3gHz-+}JTm}pe)MVz5KJ%&b&h3=Qotx1oLpg}jP6;&hPxdgCsqde2(P$^;1 zO@eM&mbWKt)kJ4Ql_48Cz#TNe&4B_4!vY&-WIR?R1Z~Px0zA6Jgy2AprBgW^9d~#l z3z-!R&r6AjBs1ax;N}^P1(g4ClmiClKK`ZWfN=lHiIy$bzr_P9Go0Gvepr9Zei(Bv z)UB`u63OX@1#JW1d>R0w-u1&Y_du&I6@cPr1M;JW%FBSGTxe8^F3M2B^^XArA^+Yn zAU$0)J;5;;1Y2$mCZJMHfv0Rlvj}W>AP$wF^QRF1k0TBsYXBWHJgFg678RbLK=6T5 z22B=N+>Brvb}9{xG3c<40T}*=(FR~Zom{zw4wb?_pt?jCC_YmKB0nDkQ56{=60i&U z-~{F7Ead}jZUB5IeCNtNbVGmaIp~334?pp28rCWR8&7WnC7Kn(4I+xZ#DFwV0IEP8 zb`B;1ZJ+}?0>)q#ump2q=fDkk1Anj>P(T=nfHr-V!U^Cbbc7sB27@PoS zz5+ywVP33v`(f%l*id;@i$3ABT591bUelf$Xt)NvDVlX25=`ZyDu1#T|R8Rv=f z#|7d-aSU8EZaHohE(@27+m0*19mbu;UBunQJ;c4hy~TaT{lGQjy6_@+S-dKK9DWL( zh&RDo;hpf__(k|od=x$bpNe0H--_RhKZY;H-^7>TU*Rk9b@(E{WV1DHEv_sTcVzDlV!bN)Xi%oh9lZI$x9`8YQ|y zG+T7L=poT!(L17LqMt>ZMEk_##KwwE7c&!c6!Q}c6^j*17t0enAXX%HSL~J8H?ekc zad9&91X+S3kuI@K;+VvB ziBgGg5}lGVk`pBLCG8{^NHQc>O6Ex(k-R2ZD*0WqTS`%CvXq&Wn^dq=ywrNBLa9qq z&!oOdbxSKrYe}0+drL2sPLbX!eM0)K^atq{85tQ38B-Y#8JbM8%vPCGG7n@v%lwvA zmYpVRBO4$aExS(kpzICVx3VpAa&lATEaeu+apcy?9hSQ-_eri(UPWG4-cdeSK1n`b zzF59gzCl4sVX}gi!eWI4g*=6`3eObk6{QrXDB38J6;~+cD_&GAS8P*KQPNX#Q;JZ^ zRyv~eP^m^)LV1evT;*WpRONljx0S0@L{uiJ*r-rcR;d)K+*SFeDz2)f>ZrO@HB0rl z>QmLG5y~TGj_?@~HzI$;)e)6yB5G6A9M$M*>($Pvy;kcQIc}uYNb1PdBTtMh9oaEz z%qYuI)KP0jogDRQRF^tIeXe@A`Uds$>K{joj-EE!V|483U8C=gt{4fY}>f;3no6D*sYN@05%w=cm+YjnZ<|O3*s0^+8)sdyaOL z_FnCmQ^lv6Obws9bL#VH_-TgI!lva5&bljg&+9f2HHiy|8;JLbeI!E?om5DAucxdxPj98(CB0_-Y5ElXeEpXO zat3w=Nd^}Te$AXdlR9(P%r}NAhAxH~hPMoRjf{<0MkkDZ7;72_8t*VJH&HclH_0}6 zU@B&6ZJK0y#q_tCkr~JAj9KF>omt_tj?Ai=tu>oEyKr`uxrRB}yuiHDf?yG7vB#ov z&cr$7IeX`Pv7BTXYyrB=tR8mvjyEbAic4jVI@M4KD7BDVInYiys+Rh~P4 z?)JH#>@@5`?T*?t*&Es~v%ldW=HTS8!J*u7oFm2Yuw&Ca<9Uhm?l{Rhc{y!&`rCo_e0kJ@0#|crEcd z;??eL?Y-Xn{e12D(erQlDEI{U9QNt(we`*Qt@I=MCHj^4kM^heUsxcqz-PgM1#JuG zF5JBETYzCedcf;NT8rWqJzP9`F@5pXB??Q(OU?$01^Naa3G5-ek@u3@f*gZ(1T|4? zC|f9X!Ir_Bf`3rWskzkeA?6{uAvK}qp&LVggv|-d3#+Hu(6-ZlEp=GBdud0wTlm3n zgzif}86g=F7;%xI%m`=PVUB0UF<(SZk4%rOj53Sb9M!~fVjW=P*o)W~II7T?dK|4C zofiE$#v&#^rZd(j_H3L|93$>={IvMh@!u2d680|>T}D}UYx%_GDa$`6S|t{&z^w>c zaVu$3Qfkt-Wc%bpDbgw7DNk0CR_3m3UFEy#QtG(WI zX_-$~8?4^8x^GSJnnzirth}tAY)bZ{wR&r}u0_^`tb4lNX#K7n@f=3Z>kV@@9LiP7 zO~|d<=(4eR6JgVuO|6@kY<`$GGjI16=`GP)Dz`dsy|7JlTh6wg?P1$rKz?BgLjthvfOoI_qg3zySw(#_Pi;uFDTwSWpCa-@qID-stbJz@9#I+fB3-Y16c=p z4>Ar`9`ZbN=dkhN!$-y*S$`CNH2Ua|V*$sWAGbYz;e^hKf|F_|vrd6i(Wh!p2cCX? z#_7zhvnFRxoSS@ZN0Ca=nqpjWd~wtHu=AA{{4cz?IPc=EOS3K&UDmyP@XEw1`Bz6= zU4KpbTIx0AdcyU#8<98aZ-(Biy0zri``doEU*7S!^YpIM-3RyV?%lp`b^rPU^9NTR znmxSu$mG%a5~Gsh$A*uKo)|tUdTR8v_?hvu3(rlTUwSe7#kJBor8moL%kI5&d|C3! z_0@~l^IyLy4=AsE6a1#;E&c7UcQNm}-zR?%`>^Jt;>WyCV?XV!m|AhN(x~$4XWP$@ zzj%FlUqz{^`^x^>^DVVnt~&4ggztxI^lPsCu>0}6c46(ey2!fj`t+YlKX)`}H=Jv< zY<%40*Yxcd>lf0T-J;%duytnZtv2_zPwf%y-5qN>)jJRUHu-(O%eU)$cWjSD&(_|l zy_fo&`aU2L2!i!BR55D+H3r16Z`6VYqFdktwI}w#TM0TV0hj=9(Pdy1z#(stpFkEn zN^gmc3;b>X^4J#$P6jYA@*boDSxHGrDM?u=DOn{MX&I#v3bL{aBSxvHj8IV-r6h}8 zychT5&nHe!Mn+CvPElT7QB_`EUKM@Ft8$|#4Fv&m11L#C;`fN)Gyq-+C!&NyKEm$5 zG;#+w2T~~p4HI}lg3(I^;P9ei;u4Zl(lT(sXs{;^;6?bJiU22y6Tyq(#U#Wf#YAKc zpr?|E=tN~bF)I(Uibhn5zPRd!g45QMG)H*e3^JI>TKU38Vsfrm)8}6lwxQbIGpnZ9 zZuAbGdut!ZXk_X9FVyCqe((s%n&1CqMouSq##N|MNB_sgDOhFP0tri2AV;i zSu1mGyhc#iGk;AMM+uOaVtA_*60vV&@Z9-E9O@SV34c$4RzC^IJ0L59rKtqwfZNPh zU3#bdSFc@c{&v%w2|G8$QT9be$G!1Bb8mMkHBsww&&aO%+g@Ih^Xt6Gr0Rsrji7#6 zP$IH@!4faxu}c+Y2}KFr<_gajJacP3S5bd)@%2M<_V!*snESqBLoSO^63sHoGe34R zV%PqduFansUm#%R1OJ^%v<@!GNX!P=`R4Rcu%Pszz?dDrHnBiopVUArAl9=y2s*}O~O&~3kxF_dGmb)OEtGZzUle6QOZ z`r<%^=g9?av_s7gb@Tepw=vt(CvOUL9cTLN;iY}^121h{JUQB{^;p~V6Sew3yCY-Tw0GfNYS+_}kR_gvdS#|QS$6bZ?17KZ9)6r3{cXj@*c!cx#BEEC()+?+ zv?_Zg=e}33@J)0*^^SI3VS?e>lVT~yA4Eq)Z2o$|{Pp{F)d}nFAJ2bRw|f!u^zH*a zy)u^&@G7*bC<9kMdJ*N&iFY?L#NRF^9^c^O6mf0s=GOKlS!escE}V~mO8UHb+bzDn z?p^sIcH?J}ZmZ9HwPkbetrs;J3qzuRp2*HhTDZ)7g8e%LG;KyeLBOZP3+2=|J@c;0 zCXR7gewkuin+hRd4)XlV zrHoc_1l*m|TzX2P$vJmb-fxADEMvWv^@NAEg}FP^VEB2S@UaO-+7A&-{R;qj%N+-q zh*jD7@QKx51z-h&;q*fgoU=gZGQyyn18+9wiiE*b{viYb*uZ%Ybe4lVrYov}rhwCR zaRJkyLlQ{fl(BI~bSxR*Mq^VnsOkv9jS;w);&4Y z*a##Vw-DaXfpkwY0B{VM_x_g$lmhhBQ8V6soR0xPIF9;Q9DPPwf%m zy8%oCyToy@O9WjwI7&1iAGq=T`M^yXm=8RTj{~QR7vSb$1H5r{-WzQ{6!xHgV#N_m zbnJ(h77Bu6bMS_0qyOTMR6__3b@%)9yZCTXzT@xSXcQE~@IB0fVHx;;QwI9g2EfgP zAByniDja!lGAoqIfz#ILR3tiUgK8t-!r|Bqn!S;Bg(ninP(7l_!O$DklyJV9z@IC^ zG!#00%bU-^zR@Y(f#2RN1~!R|CM=B}^=pS6c+8)$u@gX`znHtLy*^5*G@K))vgmM_ zGnhY5D$8KHkU1PGHu=B@j^VJ#3^*K$4pYY4(l}vMmK8k|&L45Y;2Evh0s)0Tzz{os z&SI31*b#85OfZFkY48Fx2GEIsQycKz&qGn({2Q7v2{?DGXR_w!8>%E(C2E z{`r=+hGX>Thm{i2;?K+VbfHWou5}MjYA3JPY(h&mOXt?wZ$M1 z57v35GRQ&v`T|^F?c`MqSd$qp+|*bM^bJ?g5C)?e^3sja>y_FB6bDy7U`sp(z}zA! zyrmd}) zQFsp@7Kbtr_Rp~`E&Jp1V_Pmv#(+bJ?JgvC#GawX_7fJna;UKbg~TRX4mCEVQV?B` zZ*F8TgnWkziH)jGcnu+TcqJAReF)O_yMe)m5Sv+vg+w2M*aKjWj7VMu_@gqh?EcUK z_7DP*$e@`1Q46@TBGABKP+iqTA>7NJMGoc>F9qzO$dh3w6I~XERs!s@X9c5r1k+#x zs*Hx9$_%a`f8yX)sYuv9qD%42u?oij;^VjDYYUi`9SXF8%L36ix&{r?I(*HF`q5eZ zH7TNhu$_+1)?@A3P_lwu=G0?zuDQGHimK=cj zY!*hZGlkP{EI_zRa3ud*|39Xc8Hm{xXnF!#m-brUsYD?IXbsCx&9KBkL!?#o2^#vp zO6x*c43%6O1hm>i6M+X4@D&b5Nbz6;mZba(zREeESpTcEMpSY&GsG*PH9QcACPM(c z1hmrj0C7tUG(^5~o3R13!u1(&T@$pNdCdfVuuP!M6$~JPh8k2~{ZR<_8oGr2d)WR$ zE3{_-uwsPLu~rs3EPax>B*5puvVwKi8})Ezun6eeGlnJT830>OSpOnYXv1SGS5Y%> zurcJCjDoPsEriV(%ocYBhXB0Ga3!msC@X1(MQnh7z(Eb1^uBt zQ3&)<#e8`-6h=^3DBXpxhyJvopx@vg)PHUHE5z)^Cmbb-f3bsO|3ZxmofC=rL%$(V z!^eca`EorF#+U1XFo;<|jU{SR^RA#6xPFbdG8=WHV7^5MzrFZ=)^IHu>gUK{S)=dh zP`Y46ETEGI2vcAs>W^K{WO}eb$aMKom`zrwC!aNzSy;|7&q1bQe{cW^u^m7N8})Kz z5D4gd$d;f4TnCO?cU0IAqMaIe4Y2F}sIdPFcHIDrK!}wfPlk(($#fc-ZBJ*i`>UB( z2#3LC6Z$udWW#*0sBVTFop#BO^Lv?5H@lE zD+US*hH?SD4d9LE8UoOTb`de70KTyc4H*$lCji`aK8V+!{Li8@L+NPh=nyq`Xcohc zAlEKmyUa}h{;U=Q9TFV~qj6(R$6^WSM8{RHT^$hXI?UogU=TG1m0(vaS_XV7tq(v| z51KhN89^qoSc6A%@xTSsBq+YYK3Ht{i%1Zk28tQ|M*E@M9zTR|2XtW;xEJd09q$ze zgP#MqIXA~f-G~2tU;cZ}&Cw+FgAP+H$#L`HJa;#mieS z3cILqh=->sP&ev_c6rb@+Bf6-Surp_OrYWNRWcjODb~A}!WPOhf~j$WQ5QJ0F_!^< zMhE?+F)}9@9V#F^l9rx5TUv_9^Po{U(o!e@Lf~39CJVZ-PbngbkD%R$3?a92Q(-g< z3lxhxnMDp|kt4%AqZ$3Uc&qjx3?&7^d5H`N?-fR+LR$L0q=>v&UYvM3td7u|K*9pR zSa6d7T&PQgW-%H~&Ns4GkW3f4KDKOO$iUl+VV2-$nND;j_WESVI;JJ+8 zzXH9`^_A9C_{(h6K`2nLRR|n`83?t4%h4U;BjL^txE`IrVMSBn{}t_?tRtxDYa(EntpyG>gn8hmu*e!Eh(|3sB7PXg5|c%4U9E;g5W=;?aUd z7aYTZ9@fA}TxsQrDcUgCuhm z16GTNs1*8ZJeFfYwe5%V)rg=5^7A`{y4sIr=-6RpVxl#OUjpbCCc*}Wrn>ru0x&r` z&XEHR4ay+VN5#X5%3`3!KCnvgVf|=nbUHSo&qK)1ITS-oh){N77tx1IkEZf<7+)R- z=rKM&`0s&y`8UcB6=iPup$(ZMBZLV7T&jo|gzd)Y7e1m_7&8_H4*;Q`7^n11xOEeY z$FD5<`U8N1ob$EnfIRnScwi+JWSE=1fvDk3NprX4myqRcr?4U)? z7G#mj9@s|Hu=bcR6LN?+pG~!6aewrUOvf~cFD;l8#^x74{JlP?lUdZ@es=QaOt2~g z6*OvoVyzaRc3(J+0|m&I#)7-!(4Ru(N)6E_j7ATJGd|n~iQgjeFbq^ZXsZeLC7}~D zd_g9nS^J|6l_LI4N-$gfY4MdPm+`0ZRg7pQR3PXQQ6vIgx(Ax90HpDnMkRC5pfmC56{}D)E7-@Vi z@M|0Y&4XhpVs7+--h$t2v>jl9@svHBhQ!K}%PXS5eE93q6(_9A#fF3K;c%GU0W&Fr zF++mr%EX#@)b1eRNO(jRktcN{qmePl1O!LwfeV<`3F8nc(g?&HSwxZ|$&lno3M3^` zFR72TleCK@MzSK=kmi!?Nsc56X%#7rltEfYDj@A6?I#^19VXo-Rgu1tzLOeAZKQTm zK4}MB>opMsOa+TTBA5Ymff<-JQDmYN*o%k}NPL>0eSuO$7)2P1NRm{MS;#_?BuSbi zOOhukf&gu>5G+H!A);U(LI4Lz%7_RzrW2JKiA5Yin9POnTTx#AV1hGH4~)tGF5|o) z1496ZPvpN~+&?jmkK$kElN;#Y%MvOJ~#niJgB^oM}J!Cu(5km>(IP>g^J%;Qoh zE>2KTBH$a{$IStHfAZ`GWG>+zueon+<;& zxt~8+Nuk3B=q6=!P=eRlB|5_%VKg~ZK&UIz8vagj_&{m~S+fG;XHci0-$F+1mw{F? zW`6KZ5~wd&%y90yAxt)S;xf30F;rwIU8p%cKuvJ*W55AS!UCe>5d%W{_m>E_V?%_S zpwR-tY~_CWv4KNe(3YmM`Z>%K83!8Nf$16@5d_y84P=)SnH@$8CUb3RSZ*{j1+|_j zP%m+r{zv`8x^sVHb~(V7k`7|9_7WYtAp;#aB*ug=o~h*paYyp{hL|AscUy>F@eH^M z3#Q!P6aLdW`7hx|IU>~h>0kZq2*>ARVB60Ic94EJQO#|fgwv04cmVx>i{Awfcd{T> zbOM}v)Qb*m`lxVZ3tjmEN4fq-sc_X|G#urFqsNfFnDWv!G(q(l(W@U0OO(3>7Y^+3 z{DOqW;6eWy9-0Dls5-a_81u|?{_l?Se^wp#k+5>(>-oP^)5A780=pK&Hu|uQ4kr%& zS{eSXA2MvCbIr728@+$*W!Od^w$XXDV%SDUXK2xZgnrW!ehiue_PLVS#OXK-b^l&_xnJG?u#%0$sBrg}Sl+HgDZTKLGklqQ3ltx==S(kp?a#hk+DO z4|mx8h(xDbVz6Bm5X`?+|L}H0H{1MyLK+R@$%Jb!_y@9K%REpN{G}aybi6mZeucL) zMH_;YhOqVmTVX@z?Nou&K!5I3!A{uw4|b|>@&9k{R0%kSrNiH;a>MC2x-!Sz5?j>_ zIe~*`5UIhxiEtZ%A)H%qgl7-=fk4Re#Jtc1lVO=4)?vRgQpecz-J~%11rH=_?HadtFW&mcuGM!_MHMDE8)pT$=JOT zbE98L5HE^(8N`(U2h$UcD6z4Fl(Zkk#ula<;AV)}N@SE2Uz#V&AU3C38kR z19&wZT#e9}^+1#8AC;QDvvAYy1LtpTpRXh{-CkD)jPhSw^z!DRBtJ)^F{X|aMLz-D zxKX_TFXt;O98nXPAtp0c$1SViz@Y<|t`!|WdcA0u*9bA02_r=S-Z?&TuRlL` zuJmq}ofN=*1)|bPWowlB0A3?dQaH|Lpergf$v!A4Ywf!27j7TjbMVC8U6*d(*g=^w z&D?mFlKcY3JT@&j_DJQIH$Pe$Z!EW}2I8{5cM+syD#U9af)|dr9l#?TfXGN)kMu1& zkL)cd*pQYS$4W?BfAspxug}k)dwF5cnZsu)pFY2TV$b$lPoLer@ucd-xgcrLS|B&= zJ%V&LRXD)4a{=+v!tu8OW1RG4yMV~_eZ^OHEL-YEGI5VgIe7I>`NvO{Ey$z7*yRVV z?YY^5{Hm=lEl7>@aWgS-^)XbJQ5A(pdpI>BZS}2H-{Fci$@EP!f0u-r_+(3J>Z5=Y#wqLiE}*PATl%f$^Sic+?8w5ER3~2#k2P1`e>$6NOtd6UA2*7iDldgslvh>} z?EtEf0XU$xbbZFQvqf3yvjDKzw?$WYS{+~}4oF)v5;hcUTc#&E&Xu~9$%;?farbT2 z_o}WlUcM``*X~HCGM4#=WHYtg;+C_t6;-4pMY;iAVzg>IP{E0dOTh(`1f!`VN8-SU z_`Nw65cVuspi89ZK0X=-3n!s@K7%}4hY zUcGwe@|zbW`&rgwTYxC@LDoTzaMpDKXJw*WU|5hD4yfq++v<_GUVQVdzOkmE`R8|J z-L4$3g^rfyR;DIKmYxyhcsCr7aaq3m>b;v!pMQMt=*8nLW5o#a2FtQeyleS#d99;( z2M|*&JQTNl^4}$AJ<4_Y+OVl`@1eJ+@A5rR{F8IcqazNe|Drn>TO#hXJJfve-4CP9TI*#{&>hHXAw_jRLwFO-sAAnN&S9dnv+ z)zbyc@RHNLQo>XK!I!ll;%Ld^+mCD8np=K%{%S&6B7IXR3tUZIjK`>H>RMY)xAUc( zLy)dt->T}7iWN#dKy;cc@Nn%0DtImCu063@s_JqoDvFc#Tu(^}5Eq`yZeS**I>|lU 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 NxQge#@S_ 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

o7()JsP~PlOhRDP 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 N3V`I!`wC3f{|hexB!&P0 literal 0 HcmV?d00001 diff --git a/src/beast/doc/images/body.png b/src/beast/doc/images/body.png new file mode 100644 index 0000000000000000000000000000000000000000..54d05550c38043a6925146387c69305e5a75e448 GIT binary patch literal 6505 zcmbU_2UJtbwxA-?L1`jV<%+Zq3W!P*P>QG#RE(m8 zCMb%LqVy7?lz@;>1BB#9@Adxo-oNXwzy3LAoij6Mx7oAH%uaK5v=QQ$;pgJw60)h!_QbL{LZsL}{^ssH6}Q08#SN zbpSe`ED*sVwiluhZWkQg;TOW-h5<@Y69waVFo!@mA_k@qAAU9>8XON%`b8JaY5yDs zC@K5`i3x)!{V9~UgR_DKG76!ft94Wp4g`S|^z^hqI=Xs#APog=AV?bklpGP48u83%4Y!sZc9)ZfgkvY2i-x>V`fUR^bb*&8bEUc}pt*vxHAag5_g_WL#j+q6} zz)D}w@;8lt!Zo+j16u0>Er5DD)*z6Tp1HQg&s!g)ZEdLwJZktG*DfMD1{MKF{N@|N z;rkb^`TvRwwunN&VvtepNaWey7T_F=j6p^RBT)(#Zh8u;ULg?y$hhbuKbPmv*;*o^ zLe3!qtfP?O3cvIV4*3WEAhV--IzU69julYPN{=&dOLH?zD{TXPLv39{ZA)#XziwMhZiv7#OFc=Jx zNQ6S6f?Vvy#l`XQaS;)bl9Cb_Fc zfBpJ(b#--XYwOa|5}VBqPJn&nNTM0z>gdMl{+}?bVV4YiFJLOxu@u4Hzf$p zJe$dWdYtcl7Kt&qAc(1Fd=d`VE7S~AEc~>e?Mp#o-zuLO6&k!CJ<$~v94C??`L(sZ z^eseA3JcqxVo@)hGP(=%<-M!8;(&F3pU}hnqHEUY2nrHzj3vhKS&a8XbMJrvf`8s4 zFAHhyl|2KXy0i2rp%x>sNu_7Pic)Igj_!^+iYqO7JFGD$c8|CMuL5vv#V}G4bYeWE zW0a4r=F>=KKA(M{whORy#vYbgb^O{TC|07hPmnxE!F=+>uE}Dq7*8UfVqd_CsEO%g zVoyX=ugYEWC)P~lC9)xHtWgU|Zeki6^!n&#QoX4kKnzn%YrU*>yE!p{HJn(VD8&B5 zxOpqQ9=#8GD8U?SKOh>~1^}^+om7tsM2UPN3Vq;BUzl6f)2L*~HvHqOnJg?pdV+wO0wur6VEewj`tJZ1ZMbUWXqFC(ol@9eh^crfn`X^XU3x& z8IK+?_IF*RA7e#CeEl@u$GnLCT3g~qJQ2j2-wuK-;t9_Ke=sQ|?Sa6waUBU8_QvEd zWB5rNgw|^(wKE2#p~I(WY|vpiGqZ$VLN3822mEXuMYod}`=OMefA2hP0r)%ED@XX; z@_ge$+02ta>AgxJ5^vokJ*P&~URZ2JztC!4;KgJCHnQjf+pUbqFS-m|$3-BET-G$V zuF?NWd_B1Yy)g;pW8+p+ONMVJ(OhA2uD z>)v~6@6)I4(V5#Bzj`u1kcV)5NV$e zqr^_6ySk%_Bs3-$52-yw6)^PakUWPZ43E}VZZ;)y2hM_9(B0dp)}#yX9U&S zH&0G!-Fzzdr(i)Ee8&~Tae@l=INf%<>PRdg#Fm!dx@5yNqqD9yVw-B z>z~q+lAU*!wi-Q(3B4r@4KWUH8J{>Ikb9g~sXch8#5qIt_D*>VuUQIOJ{?o==-m5e z9HS(7E_%)Qv3;a0{&43@Z;uQwT7P^{&>a!V4{|u{Lr5^{)U|Vp+<}#GfnGc`ili3bRxa_8)ZnBpcCuf1|JIoLA(G2Z2GvViM zI&j?H8ZVH@ZOxuVWiCsr&g^dLw%FlwcSM5RQL`J1NrP5`5{-YPj(2xX$zmi93R`^| zu!BmnLxmO&!Z8YVxO_%Q-Px9wrdz&T2k4!fO_%nK-r*W@)ho4B&ds#1Zt0!w zTb5j+6ao59oGgCF;DiC!*aMyWgf?W(oV-&3t(T7;6T~Qu+yAcenX-z|7MbBf-?^^= zwu-Xl8#6@rYz&l;7b(V8t+KDmnAJN;$v0`~>%Jn&0(r`%W=bz6!N<^sc2^zwtl%b$2PB{)S=1Tlm8pH=Q+ zJJVnFiL-e>Zs}v+E=O@vPDFfsc9a}J`bN)m_RYAeZJpbw4~!AxbzsT?>QKz;)qY4t zbMs29Ws%Ch={=rF~DrXgMZ% z>7(L7Cv}ozmk0xvdq8|uR)rm&)ftnGC<_99m(92#SR9vz6I(*1VLo1&d3J5K+nDp!w@2yy& z5N^r&NvKt9dOAcXz5&b+KCQf*?Xvb}&Y@B_1h5r#Ib-M8z2)lsjshYVo1dq&IN}EG z*mXJN$Sw@ID+{?gLc9KnWc-o+5HD;Z#jZ$14~@iUXL7N7Iw$DLtjVv2vJy90l2 zi2gkX{>$L#Q8eqqO%B?|B>RDW8z%|=%g^6gGccuu>YvwhDluZ&Gikz$zxqtqZF$Y9 z^1~kA#~#hJF)%#D-jie;#;VGiQx%^O`M(M$9%KP7Pk|7`-t0?Wy$)1LgUkiXhoiEq22afE<^*rfh2Q;P8PNXnawxbQXqCP# zw>ER|>j8YO-Saf^=i+Bz`~Ft4Lt9DxIDQ$^S=UN?Vnp`SNU_nFZ#?k>ONJe2Q9O{+ z(eYkU)xP%{%}(Wrsu7^}xK^hAk4WH&>41Yr2tDQR#HbZa^C{dNeRmD%WlghHRB6NN z`G@a)-}W?0RA9f?nlzS{s^v6ZX@(WJ@x=+{+pEdE$FI=)PRr68(kHW2X0rp^#?NfW z`Z-MW{~jG>D^T-eLve40kVQu6>nw-3?C+hLdcpc?y%WK%grID( zqZqHwxrE!$Jd9pqRzyi?)q(jWe$_HL8<~>wopLr9O<6gc&AqjE8xKkF+Yj0>eT~(M zz~ssV>XjQVZ&gMWHVrk_ADj0vY&Q=sJ*EfEWegDaOJ;o8VS|xOZi3&07#C4+sytoq zozvRq(v`wO<2BDD?}HY{V)WPG3*TwW?e%|}^NL zTf8Xd)`VA#uHhT|$XqI;ize?$`hiZ`o;Y9f2zVq|q^5gcAq8J%S`-Oh)~e8b!-qlV+B9trezp@N+aM$Sn(=n0E-y(KHjjVq6krl6d%Fet zhytwDtIK|6QN8|R{l+%2`A>X<@Dp*fCypyC#q#5XUiu~&PHT-N0@suo*Y6y0^9d82 zdX<@ftq`hqM*7P1tXfsWii@c7Y-CJ6V`~OvTYBG^3~&kNH<^^hI3zZ?&L_=JtT(X* zwHg!09B7LrQdOgM31@w7EG7&*Z}>7P!OW`4yPHwis$grY*@;S$pR`L}W?eMa zm6h>0f6A}r1c_em$$N!g$WdhOq7ywI5m(7*T?Qx1p`uu%xi88l+(TxE2PhYjwVj?K z!(KGr;N32|%?ejW&R=YsJk&GmJZ3*G38kid(Y6ZQJN9`^wMlGJAteBCx5;|YrT-H1 ziAA{M{=B2<$CyWAuQA$8*S`ss348VQ7<7yqFxwc{O$y2D>qd)g*}2nIM%&D*rRa7S zu`}YIZmIc;&u+VjmK&fI;p6!!vQCind&It;OU3X$MmM0?6hkm+E~513ZQPMs6Z1O= zWQ~7ok%m%m3%*jBm_`QCH4OA0#{~8pJ0PShps`=_w^AJMlyp`GN%&_=gI{Wy_3 zg+uy(98adwh%?|1AA{1zon8DewdYRi7uuiQKK?yVe-*Maw<)(NlZd-aAD?V_t$F|# zyApwAcWFa5V-s;MR*MhshT&qJ7f^NC3Ksd)>Bf+`3@3L1I zKxSXI`-|uOGAc#RUCDjmbnEq-dya%WIv#uF$IC{WoE6B7-}=o)EZy!dyl;I^5k7j~ zDr6~c1yXgV(tcAes%+7vv!O*EZP%sjVfGQPfmxMfm8Xl`9BRA#s_I?RCwTu@3tRk~POZg1MRURBnJHJMEaCZ8G-@HzkvTfftYIS@^vF0(}=9*AO0Y$9_U&D&y`AiSU+gzI+S#% z=GxvDzP>$!@KJAYa#gPRYHHP#Vl_?Mvk&?f&1Y0#ihfqCYJVXYb`d`E*@o$!YeY?8R4sN_s(s^`b=$_U}si5 zYR#PCu`F7FoN99#&S+@Hypz+19o@g}L_@ENwn%QZ?Y3sisIvf0<$m9I;_dAofXD11 z@2~Z!ek?W1tf6p$HuGt4Y?!@E*;!m}*PiR?Yx+YDbT3AUG2Q+dnWF;tUZ#19NoJPW>#@dVT>Q;MWwA??` z#ck*``A$0RRVO~4y51q&w`P(=qt+=Ge1V&tkjd(z(yOLwN6Me^c5VgK^olvmipRH4LYPz2fdfbipbX0$ zXF-(}-%>ulq_LDLzfaG6;g4GR$E~LFOULiVANDr8l#{}LY&WhO4lZ- z8|S3-oz~HvO(>+qwUnE<^sLXEab73Ld^)6F+G>a{RXKf4%7bIM`k(%zn@g z%V`lUOzgWg*t@TDXL%kg`eI`H1&^$um*@$y0{hczy!J6C#w^kj|ZOHiT2Ik#LBJ%#aL8B*~036V64(dslZg-YAOW ziOQ-G1ymLpPrMlsP(apEe?`0)l|ys&|Gnz&IYPih*qq@#@v9SFhfydR3h_ z(p1O{O!Yi#!@#_*r9JF|7Z%eUlo^=qO zDLAdSz>~NZ4DCIFB7_SknS=tH!zyGC%p6c8rl$)zIRn$PvU76M&l57l^o%qS|8oYU zXAR2E8I+MLsDHhOCE;m^qj>(HsiVfI#o=ve?^2h`J}511(V|5I7iA8#IZD#f^Yilw zOh(24q!{2_Qt7fR9#H8#QvssE8D(`AIm+y=GFzoUa4qv}Rj#4Eds9KdKeb*J_FzGk z&Vjs+1B+}GX^SoPwDf^uS`aca4gpOD6*#OGm(4NFW-A}2)ZA2RbJ?7wHoH(zm?QL? zSyowWTjcCNB&})kQ28N9)=?Ihbr|TAGeFE5ke)LwJ$F!6-k|h+ybcn@Fd$0GCNL(U zEs8BJOC*&1Y3Uh6@8c`tTjn2&( zrGX90dJNbpjxvZMOL zPAgG9@_#?I0uS>b#++xQLqJQufQ+i&c?9;?`6IprE zhVtfeL%Mz=@87Z;&Kr+%&5*bR}i?p8B4kRJGd0CS! z71m(|sB$=_p_mY&p)sWi1VVe5)8(itatR}C#Y==`l(foOR#_q}w3Js_=ejVy2}6Z} z0|ySKsG@R<(Osv^vTxCsZ(nO9yhLfQw;n9X+?9Z8nHxLJLD%UZ@j=!&>ldMjs zrNnx&tEw<`WKR!A^jHDWy!2E;VdKKQP7B)C$A}N=izofB8D2T*We9p9t)i(;E}dMh zqCl$>>)fT2t5pC{X>Evn^1zMF@=PsRGt)f7y66@ThldDw}XjNjJyL583iUO@ltaF!6 zu2xZ?Rf%=((#h2-3bZP*&RsgWT1A0YCDyr1Cs(T|(5l2bcj@G66$M(ASm!RCT&Neit-*g=e4@i;+1ixpoVamKjWQIe^an-`WyWfMK=vq-hWRVrv-uLM|to!I?_YwtNcOx z;c+5=OE9Q^JdA2Wb)k-vz(W#vh}zquB!I@HwnGnkV*Df#p0*pkMjgQqATt zMH`4OoOO{9{~iwCNkqQ`pIeHY_DO|fNIhWKTrAKoV+9ibMUx!arO2=k28=TaLRef& z9@`b!9T>a}CM3=*#)&p~z8cTvi(GaJC#*ZpTR_iI)Z;ojfFPc`&~u6Md>&7uJWKRk zTu}-Bjv}1x#TCW${3@RRys(P+K6)aauU%MXU4-WycI;R*C=V0^VkZ&hLc6LODZW zwg(_vF(ySagOV8es;w zjP{}z*eZ+5_(@G=l@WXN!yx>|Y+(@SQQ>Pmba<$db(}kp8Mk$0(citpVmc(V=!;h) z#t>|`iHS3yn=>}E=YECn@r-!>8}So$6JDd7Wqed%1yiSC`>|u8k|c(1vCw4NvW~1P zjEFN?KQ;gcLoOT4BsLmG#1uA-&0?3ZB38;OU<)i_e_~g#YuOE~n%&OsX7{m&*kkNT zwvKIN|6nh%*V)_5$9A&4%+J1H->?8X!s?(H#2Zo#9SmI!Jq>3W&NXBh@(jZaqYV=c zQw_5WW@VwzA!<&Zp4Z97W8V(qKG}J~#N2Nq{ zj5;Ig?5MP;yr>aT6QZU?&50_Bx-4pG)YVZpMy-r`AnNZ?8=_u{dOK=olt1d5sKe1L zIyt&?bg$?E(RtAY(Nm%?ik=_sh`v1f`sh2NABcW3`i1DN(H}(nqrZ**B_=MWLyQnJ zAZBpPxR~iNMKPDfTpsh+n7d;hjoA?MYRm^QpT-1Y8e>yqyT_gzJ1BO1?1ixozsGNh-yZ)({Lcx=2|W`s62>H4lu(&)Wx^c^k0-pCusvaa zLS3RU@vOu_iBl4b6PG03l=x8M3yIqjzeuc0YM0bEX=u{4q)U^oNLrcnWYTL%A1D2k zoSfV{IY0S=E04 zQeCMxraqSXO6osTe{R#E&A>L}+Z4B1(PmYf4Q;l!`Mzyp+q2q^XnRT9rETwM`%K$y zZND`p8qYQs7%j%jjjN2$8$UE2Zr7n*db=s@D%$@fr>sshIxX(B zs?+99{?5^z&+a_Fb9v{RI}`-9!z>3;Z(9%qa_!+yqHXS{O8w>>)dka}F&e3d%f4Iu6N(w(|cdp`>Edh&P+Np@67pU-gf58X9oK8 z>@%s)pZcurv*)bDv+~X=J?qZ1ww`tL?7nBuJo~z{H=g}f-!6T}_g&g|ZQqakrS==% z@3MXm^xM%tu76(t1^w^s?>i^zoa}Q-&slZOwsWJ-%{jO1+&oEEgXV_olZ`KR^A-^cT{9 z%s3~bC}UN|j?C1|F_|kepUeCq>)b4B*6OT}vO8o?%Dyi9+#E;FlQ{=+`{bH) z@5%izuVbDm@2`1p?LLqXmNs zmKS(OMUNUg>iSXdjcz}B`slky|8q?5F(qTxjtPv-9=mL;cU8|LTO43565xp77bkb0#__zA!0j()p8ao%HeKvnE$g-Z+I#89(LLDf>)) zO_!NAT@ZJH>4H@kd^t6J>Yt`=E$mP@xA5`8BhyAsyJ_0q>HVfxO@Dbt+Zh+ncx=Ye znWJXjGSh!y+J(z6e0x^6S!J`HyD0vm85ccr(c#&nXWu^ii;J@_zV_l>bNbF%Jm<|z zx?NI!$>zCj=UV1IV~#b?G(T>xw@k4-U^zT*{JeYS1&Rua?kxJISSr4)_<(h&^%m>? z`9tU5GXJZRp(VGL94H-LdPnKEWd&t-m;G?*xJ&Q5^yq>q3)U=<%V(B9Rgq9(u6Vw( zedU76S8Y9Pi*4`Q&$VA`_g^;XvfD2UI3_qAaT=U+oX@#BLYaD}s$bPLRi7>#ws6(L zpBK$o^vvRRiz^quv*es5e_67B>6oPtFN;}LwCv?S_4?BlfBJO!h~@WRZn%8jbUC4 z>RmSvzxmNy+TF7Bmd|gUbnAxOgxjva?a=LWZ-4WStUKSMW&J@$C_$8TC2wYF;Qfxln;ci$7E zo_Oxb0Z-olROhE|s9`l#HD5n%etOrs3F}^dChwUipFQi@d)9YYfBgo-h9w&U8%sC( zpPTjE`_G^M{L3#4eqsHlft%L+qt`!HZSJ)BrWcc5y!u6X%hD}}y>{=nFO|IX`O9-( z-uudhuk3jBf>+;rZNh7BygugjSGJDWy5)_bZ~WuUL2o|)R^D41-_Ch^!#mmUtbaHA z-SzL~ytiRn?zZQA`Mww4AM*Z-+lOy|>4Q-pyuM@nj<AODq@Zt~s9~FP} z)$a1$KkQk!r*5zNb1UKQH?HoBwnE zU;P(Xf0_E_9s7Ikf9$J_ul{jh%mLrmv%mi08~ZnP-(LG&yYKG#zTfxH9+VEg6Sy$& z#Se}jNOdZQ8WyNIz{lDp}gK1c2;i?Gk{zC)&`PMYS_Tw=>B5VE!k` zuNsB}%UET`0tnMzG&4lS#Ky%ZBqk+eVkt7xz@nnHNFy`E7^0(MqGIFY5@MrMG7;G> zI;L0q^w<#>SUU8+?23%IjyK=`^vE;&bej6oyv!`emG2kCpLI)N;L{(9oY|cpSa;Rg zqi&s6Jo@DaT{)+1pZ=Nk$7im7<%6ove;U*Gwui2H_V!nI{NJI6*T1&&i^DVKFTD1S zM>f2^>&qj#V`r8uy6(<38@GPA|7bfF6$NZ#I8O0#vDqAjUg_;)fWc+GJH%#OadSt) z;H9bW2QvH2`_yseEd_<0ikw+LoE1k1#Gjr0@^&Ec;AzF9r{}n=p9N7kJ_db5D9C$Q zN;EIC9UIOLPcL}9`}Wec9nM<$-j2_zjQm;xVdW0n2XQ;s^`Hry^i$1-`exS=qV50JnbKYmX8>A z&y3f19{BdL)obrvR(^PHN$sS=AADPvao5(b_jky?s!z%dW4~T_#zQkEl~;bS;rhlK z$3EKdy>&}%|M8or?oEETOa5u;H!Z*X!AY#c?0u6~jeb6Rcl{TYwG*c{+<&LcZhwCG z{-PBJFPggTwQir@bdTk!pWj*i>PtIbUEF=;)=yvEKl_Hm&$1(5Tu`)j;Ia$jd_SJ+ zy!6R!@5?ORx_9VBN7rrJ?^yfB(GRZp+eXu}1x4ix>!PJ)-oK=OzwncHKYqN?_qNR5 z`uM(|p5AiwiL^akSNt;j*2aaojfWq(tZmPlwY%?JyyDZ9GCR2Uv2*4=n<29wH}%MyP*;~fSMvzb;>N;Ji<$LCTh|zN!9!o*>ZeBfDA? z0#|fsDC%+Mp+g(i&HL+`{vBQ}9-sc%YX|xsT5)g5wSyj! z`0DT#jSG7=T=b&&O~J0Z8FShm-t+$4hg0kF_bi_|^Tmw~kNxw(&8s)AssH4sDo^c- z3;(jMG-`4KCpC>dP z9;Owx=h68|mz_S}I_52J-Rv8pH_sWmH+jy4vZE_rTCr>At@eA*xMX+3ck_n-cO|ISd{oWntoSo2S(1Pszr}n(Fe2f3v#<6Qk*2t{Q3w>@~ zxq4BD!(#@k!&dh^h2>y|Fs zb;HNe&RK=${%zC6i}KbV{&?%2SvSr6@|pKH)~BvtaYaMXp*d0G`|rQ*{MvWY-^~AR z-OqzBUAAS+zMcnenzizs&AX=8_S}`zc%OC4{cAt`==GuJev`g_*Wm0Y+0C<;kNjX= zx9ml`=gdy4KU%!B=axxTAI|y2^VKcmrwlKdQoB7i>zTY^L&xr0vgh3immT@=KyBMa z2L{yHz+ac0yJ6Foi|Vo$A3eS8BULZ_Sh}fi=8nfVb^oT{g8LtSWaG@)<14aXsOwts zRMqN7-a6Qj+IV|f$s-#8SAJjVe0ljR-+AT5r`As&Ewj?MQ`4N2KkR_{bnY-w) zX}0wV=K$L@&(N2zsQ&Q653gH#dCeQy@9p_!dE>*wM_w*?Th|xy0#yG z)28dTpFiZ{RSTYc|K^?P-#xwK>GdyM7+sO-tlYR@!iup)M`k_KnEBK2(bMNOKJcOO zz2|TIvT|)%&9uq8cFnr{;Dqh_th;+Y(y;CRIaNCkG_F4Q&D>vR&TM#YTf^CRh8}NQ zcm9|+qU&nXdw%ws?U|c?vA!a+KW+EsyY6UM`$XY_ZeRYRX3+CO&bxP{*%vt zyJ)S<;%m?OVav;9pC9f%V)VTF$A_9wHys-5w5qdE|$l zUvxcp_tyG#pEmYint6A9?(wu~o2X0;W_L+xndScb^ z@s05%Yij3C-8-!UJ@&!@i+esBTO}^*mi%VSkh!y#ZNBk&G*D*k*vs#@d&QLL_g%Vi z`&aEp|84&qU+uv)n>UZF*xvu?UBf(a`!k;S@$F9zZ1OeOcHds|@YgG@nfU!RA5J)W zPs4YXhSSfmy1)JCt$SxbS>oI9-RkwC4F2 z2m9itr&cX|=82;8AI7~Aw`<7o?WZN*uxNNu&zryAdGOHY)i>>b;>*Fyx;5sqWk(xp z7Tk98)%y)Er!07V$i!z>_kU}uc<&o`m8I9tJ)E1|bLj2wFBtgE$_*FQc6sZGeKXhX zTK~>7XU-3NxM6GU!ljcN=l(qW{lcV-etSN+^YE3czKxl%^6N{>lkO|nuxsn!e}3~s z-IUEoR$STeny=5*59jwd^OdK*-+J|m8#-Rn^W*z|{y}CtZG-=E%el+iOxcw*;E5}5 zJrKXW&xbP~cJ{n%RgaJVdf%=~WLCSazVgQ0SqJ{M^|6inKU;NGw*kG6w0mJ_;Nbhj zu}_yibluwL>V~hMdBf4ZjYC!(o?Y<3g>Q>7k|{V8Q%Wo@Ao?ZEN*V?6@9DL!vOST0zz4OoO zcFvx$ZO@D$4G+A1_x@Ew|L$CP!Q6FUe=K8a{HGbu|Ge#R`HK6$t{t4)e)NL(b{}5$ z_s!dC4$n-!XY$sKI}NKgXB?co<=OJRb8fG_{R7)$g)&>UWyj-t&mFaBXX67e#||A` zXWe?}#^<-iSN!eCfpt?J|9a0i=PWz$%%K$?^y#lJ+6bXHbnedg9?8jWm{d1$>GziP zPhI}tuEDFSKUoNIBeSX%_q<#;^q)^2wvG5^=IpW+QwKcp(qDVNXI!{*&XmnNYXAQ5 zlZW?KAACk;V>W%*;QX$~>fGJ69d9YTYx~kkKc6+?f;3e)-1{PwmL*xqa`V z4SQ|tw%LAK@$QP;IkU62D~it)`GdpL?tJU9K5M>y=y%=Z-O8FxFK%8;$FG45suUAUb5$&M5qekFy#3gF8@o*T-FbWCqNGQ+@0lh`?UNMP}X_I!<$ z*2kHl8+SXQqURY-4?bUN=%=QMLYj2`(FR(Fbr>@EGg*bz*Ol?rQp)=fE+0#(cQb|+ zaP@w|4-7E+YQP|TCLldu(9ZjZndLEraPvfI@C}WgXziK4G{OJk44A&flYwDXJX8-u zk5D}fXNT7#%Amn842%lGR-rM~S3td!t=rZ^aS0PEY_v|LmPJ8WN5lni;eQct#o;m9 zGp)VTbBy+IygMbLl=`L9K<9z~O&%Z_p#DX_g$E-pMZ~n3W^t5QU2w%C_bT!*Ay*Re z3!E0=X_BP4C0OQ_lRpCFI8UQ_rO<19nRE1FeE&gn(PEQau7XGylX0x{LhA)p7LsTr zZQ;i!XudIAnvq+Z>c7I@$@MM#eVU_^JJ%8NN#v_C)mmItWYx-SM{Br_X@n(KQ(X>Q zc?1IOLX}XX0fy?5Qf|YCfr@!_c%4$5rDgM7qXDTb=z)XetSuI5ORbI(|^++TQoPDF~JbT$CxIM&7*X1fU`Qv zu{K?-)i$NlHp$|0p{l5~7MKWBlzJ$14I8nOELjDOsYM<_MSSqnxPRrO!npFMQDbl5x+!b)83MWq6Mb>$R)C7@qBMg(5 z!ZoH?DK(;z6EC8O@s;y!NEL%Ck-p|oX)RaWpwwY9)mmXMwK#EfI(Y7tl{G#6^ z^@%(UhA)VlqS5?Bp2L1u05s`cG67EDSkcSbEuXH*P z7gKIo;^6N~%BTZT6LY`6qVjp=^iCC~__E6Ri`97kzMyh}#u5rVr+t2Se0fnh!4KA+ zU^q)FsEl#AxbG#MkB?{i%fc0+b^?70j6OewP(mOPSXv#^$||hnxjSO#Yb$r&)~g#Y zcm84rsos>g=^xdlFD}SvL!v_U_O+BK!A?+P-r`iZ6J{Z*UoeVN;IGA0kD>1=AOpdf zwxqBWe!Cr+LG%9Nu)=<{aEaDw@Fxva?(p6SrvRI_cp}jc`ZK%+7rO-2pKc1w4Hx`5 zs^Hl{ZsH?hfE1%`Q`s_HiTtWUnle_&N)W#gEnkk*RyG&um%t~LjF+Lf2J(M{k%Rsv z0$w?HLMIn<2U1x8mmJcYPq7FwWq_!qh-S-?lKxW2Cu^*uthg9Mpenc`!YGj{$JrDI z-@;eGoEzbS1w2L|-$rxQdB& zCT&Ve8Zsm)F-?sqD{>_zQUF3QfY=;(;;)Hm1W)MKD4){QP3;eahYchA@$Cf*B&EG{ zYE@;Z7OMXh!iWe6SF0D6zOdA41r46-?0O~atgIju!iq+Kde zP9aZAPwIe>nG>oaF9kS~&)sZcU>$rjRedeuPCb$Z>9)eYH`KAEE%vbrIw8?yS8 zr9Ne;Pgy3W>9aTb?2SH8uP;vNi&OgIl)fUXugK~vvig)|nn`y4y3-ZUA zXss-9m8xDj*cc-4f7wDh{R_LPo*!xBo#)%iD#?3TNQe7~;<&^o=UeEs6q?BA-Y1&( z5xi z2b7opm8z4Io6buov2*@wcqu3new$nhmolfISjx`J%*zx8M%;Aq6!bRz*RYf}M(}}8 zAL__?9K+{-`%w3r<$ZImy((kss)I@aU3CcRow^zta@=pJUf+zIKaPo(E946~LVA{v zDP#$`LLTlg)0#2QXjzJbO{0A~wAl{ zu!;(KuqCMsi7igPfg)|X(>lh1O}f|%Xes9#7-p6gyGos!y_Pl=UmC%=#Kj?Us#^yY z6B3lP*peCGpg+Q9+$ zv_DbX$&f}>YqEtWG3_`dVRQ?XrS0KRc@9xF+k!IHTS`S48BC=%m(5vfv#T9D7}E@X zlpNCnF0xvW*#bmkD@CYqg4N_>Zqu4l;56DZi!C!19Htny*+|n2k4coSmKx+Lz&R#S zMsuiXRM18;?XHH0Sd;?jLi;;$2zHTmRM^%tb&>~r#CcmPv zu#s;Z-_cae_c5hSw(%WCwCzbWh*5Hi+(xXEyUC}^J!OMfkCSk_3TMcPVkdd1JV#6v zlf_iAt=LX%5F5pN#noc0I6^ECM~h>{@nVs9m3WPKop_^ozxbf|u((EiOngQBT>L`Z zFMcl`7LSOl#CvdPU@vxYe>Ml({m#P%!a;0sujpQh>;XAe5H*~zy@@aqx(MCn1hJz$ zSe`8=h)H6K*hVz6i~F(JY?=Ip9K#-z1-3?PFGnjmCs?g^p0Sf~mJ*`5cB}P6f#*>K zCwOZKR|o>mXD$uN6AZt*a7oP}(HhrDYRvJbn`f!53~Y8DuFBE}tLE zC^>8r>Ub&GWdazb0(bgwkc*`^rP=R*m2nbbWu8P>SwUEwh|@}~lj#(I@{p#^rD0ky zBDjMxwB@nM_QEC&5G4F$5ZM9p(#BeWO=V-*NGkledkULuqf~`sv%3k8yO*%porKue z+H@ab8ttHKx{FW=jNC)Ww-p6mP;~=S7vji~1y)xn zzTZ<)8XhsRtixTF%t*Pz_cGFMaG{+qvtcsr*;W!bE zj$4W%602~)fRhuKn~yZRi)yA7hQiUB%6D9_r(BJj*K7_o^w{)J1Ii&blmc>9)qIq# z2>rIwrK#w!k9ezTGX8iX`dGC~*UvTkL)XuB{rosKh_0XWVOZDC!@LS2R8H+Y4^89L z_47zqkqBailvQntHK>2;`nfi9s_W-gT|XyXQrFM9+0sfY;6JLL4;#kM{R^E47_-R2 z&-sg`)8FWs&xokU!-WD6n?SMJmtN_aJGarffr!-p+EN6L!F1YbnPnkAR#1z>yViap;hQKz>lDdp!^JD*oqr`Odr6jfL#3p&jl-f--a zoaQs7VWTcdwFI}ILEj?O_)Jk-MOpjQSrKSm+DTeJ9kC;4IZmWYPu)sw3`1=NWaycyL9I+ z-MOn3&Ryd$jaClFC~}6T?}Av^05%!F4*avRMJIA!;x21pOGw9clG2^9pf40w=}uVs zCJDZ?MAxI~Lruy+uIthICJAiaQyo(DO%f5GQfe}ehm_&tdEg#*|4k@svzsJT^6PqZ zVp_;0KQt+>Z<6?ZJ1+hQHc9AuwBkq=bSMj21G=+R$d~G5I7|HoPo8#v2v44|*F|{p zNF@?^sW54}o8R$sF)s3(a^q^b-xU0%_+N|trefhA;ne)0ly3!-w_U9I$xJ7V<)*$; z#y|0{&8(jFFvEf$5S1NuOk1XZBQl;EisLuyAoN48Z2Edmle4h&L$B1uHvP~m?R2VB@z5)M zJ%`Nd=DfT9qw6_1Nm4(YNW5SPPaaOC`$YxM zWYztmxQ(kH9t$!w@k``xgU2~MR@b9XNj>_%@DQmmzo=jH)=BUIfnasr&q9r(E zsOhO)Nq-D#XGTyv=o?k$Mm70gHmE!pPh)WZMLkEVg2$Z5H|ozy=9Zt@)s)(*f7G}Z z_(wTCyfDe+RtjyIe^hszOKZiMQBJgENh|z?sEq%Jk5n`YKGsKSM(cf~ep|hDKPlZ$ zigZEUPYR!|(9R|XF^>Iu2m+7&eh31a{elR>|48RYW#k>}H>3MWQK$GX^^?jyw!L*f zDcw&>_mc|$%%JWkrTaSxQtEPo?k7cR;;Ha)Kz)5jU*F;T zVAO+c;qan=98h21!5LWkNBl4!Tnf)-JHD7q-we<{vMomGAMw*a;-`PaFZ4lQ{Uh6@ z^igs8g!63fA4MM?rc2Q&BECzfO_Vn?P2S8TdGk}`%}tQ&dbIML0{tV~s-vQ7Nx4=3 z$ae6n5ov#nk8JCHQo5hizvp9nVSZ9A_25bQLwN8^ygAZ?XRMn%RG7Dn-{7>v67+wk zIt8wf>EBiSSHJ6Y(D^~{54s%C<-mVR4(M9Lsje{)e*Y=%(96|FCw+9%M<-nl=yD)j z4(M8gt~KaVQJ0FkR1BAjIzQCA!a88}b|4nBXuuPVW@EHCt8P5qUk^j@~i3m$p_*m`bw~J#a<(RpN@>fA*?cwwwV8^47Iq1*Uu&S8IBgqdWE}w4iqBmW zwI;CH_%{uuB(}I7i5#c4z_nfQ$q2Y@pJq{#?B2odW1**`%piL|;}M5gtXGx;Uh|K^ zbjeI|d#l}ynccF#(Jiw?!7Z6v0Rr?UB81xA`bb_4-V1zbrB) zRC{a84J<+MxJ|O#=r%P_mVGEcL6UtQZvYN3buxfTazOS6+{`UEvSgWxlVxvBKw@<= z^B!fgS7x2fvc$xI>Wy zwK@c7XBbUBA7dWb%#427Xq0_Mq;#WHGh=@5#{d?C5uxbsM?2QLv$O}@lc5VU&d^j+? zc>Q|;tC2;EK3N3qL$H$EvXOznOqBg0STgG-$X@1?1;#)b4#rN1AyhUKtT?;Ggc@0B zM1k=lKE*|4)w9#way9TpO0Vn@jgmxJY6K=6Wv6+4l2nZ-Hv-H7KQqdHiPEAKJaS+U zfU=rK*$}WdFy?MR(<-%PVxt`AA&^FIgUpbe2uDrJ@E+qe@@z?dkuek66)6D_$Phr8 z86>e5I5bK}Sd>7|QKBT(fe?};ey6?|BmwPPEgLDz6D-;c7`ypnRJGUs5uUCHyJTcW zIoBZwk6zg%DUY)E8Xjb*memJ$fF7a+$!DjBD7PHAngW=y#;qmM(pK}dz}M?jqN3fh z+oTp^toHHp!qGSx&u@WHZfOVFuEG5sFvB}a-N9naUawzPLy+Qq;@|JsQIOCr{hjS& zaT2I)l#C4w9n09r8u)LlfLuMwFW|FoJ|;cFUedIT68Kc;nfJT~y>h51890nf+978#ke6Mvsxqg5dY5CAZZ|M#mRKxdtWol_QNC0Q0`Eaf2PweZ0Xg7Ciz^gB zV>YrRl+LRy$|)a1#6tTG$mkrH?7sBzuKwBsc<+B@wlW&O(sDNwN<(Ab+)NuP5pXay3dtkR3Gg zLNb|<-Yk0#A!n_d36MpS*NcX%@tPpi&E9JKieB$NRg?jgJ5b~C6D7jP9Yf@S?2Gad zD$y8az`gNa*@u5Mz@2w6N|1~M%;=N79-qvBbRf#>LFJwF?-b1+%-xE+QB&C`lQ?&D4wCW(*;ykjH#9zD^EtrVG-3?+J188K~8zp$m^(@xx@w!b$U}JLQHCk}H zjVRFUCY%I+U=xdl@G$!P?=dF);%36nNK+FK2*Qg|`Y1fH2tmX9(n-ECy227Z$Re8N6YyAA=?} z0l!y*0QJfZL_?pSsGn-`_`P1VvC-}Gd)-G^s@ZF*3HU&K6Pn!wA?fz|z4ivyM)G>h z)J;VILjQ?pqF3?+{AgSZf95BsUz#wkx=9`5g~;BH?rPFii~*n9T?3FF+3l{bKEnEe z=l#`SK9lToL)AcgcyI?A)nG=_3xq(xU4wf(q+h`A@e>z#WjB=)g$%z?3BRy7zZWt{ z_MnUS1L994TuIFmm_J}d=X2MyjuQHzpvVh1pqm);`#4l2G)sUAz9Fav*^g0yXp?Gm zd%XbwHU@wsXcU7!U1K^-+z%Er$bm+0Cla8N$Lj-%5)p-dymF&ADuBTrZ_CwKs(uk{-6T&HMl#PQKZ*w=P=D0%z8i%LX3jW%j@3A;slZ~Ts+w2 zYOp@EjUc#yzm9dNhA1VxxV~78hZ@Qgeav?0#x&l_yf|{y;T2 zm6=h9&kce=$^>de1$-Ur37C*J%;=GQW;2Fo!Gqxh(7DovR?*Ps(63;~XOittpoikg z>od|*q#kxEyraLG8@z8o$dIyx-U}4?~x0@6Ruq4_{ zvXf9nHoF8_9s?GyoKE3DiJ_P>ULkj2l-Zp^buU7UckQdr^q&5!_x9 z*C2?S2Df-K#E20YNgT*_qc^Mw^f)8$5+H>DqYwZ@S*}Kr(O%wdVeEkgMbv=FzF)$j zdZAuOJ`4eXYF9ce>dS>dtV#BJ1E^yvGz=(pjK*=`fnNr-d9^#EvH^5G z=9c|_XaPVh;FV=FAWLX>k#wUV z^hFq$CRtFP3~m`?4HU5(d4$33!>e2NDzDMTfEnBxCArNMfNtQ{3_wU{NdrN71WD9D z5X2yeD6@=?uD!>Ib|1;?C_(m6a>C9`$pbt$GccOzNnrwJzk1;k@UIW+#=7wE6VC#* zJXYB?Bw(JaJD}6QG@Rz!59Lx!%i)|jrBX^4;I91Z&hIDA)7scj8T~l)?x;l<)T*^` znoX~k^(iu|l)8J=s^SSlP+^tkb`$2meu{zjfRJU>CB6pbm-Xlq`GQWF`$ z2Bub^fme|*P$C=gPE~`=2uvDMj;I*b;hDlUGM#Gow}9s+;MerN5A`6LH{hbWX;iMq z^Lt3I#ml@gDPEyNcpNpTlAjv)mxz4SO4Oc2XQCMqn#v2$Pq3Bz#6HcZX#jmsl!n?8 zjYh>pCL?qSPqI^mHw}F$E(UzqBsl-&rHA{Ntd3gJrasNhTKPqi)=j*muf z?XJ;%FX%w@B`HW^DV7yFfC#4H(}!#Wu>YoG@|LAjjgaW``OETU!BNk4Q?;$nOLkgElgS z3Wox5+;~_{93dq?ro50ZJU>CB6peB;>>%7=9I5y=$OfiX07E8p=wM#FtGi zA|+iNXE24c74B}D4we(I7HOHVoMh!*FQ1~86K7Ls@ca%9p5I}OfNnCbI?#}sTB!%o zKnquTx)$Dur5%}HT9;KVCm9wJheI%krHBs1W?DGGr*K6pSKw&uL0z9ZtP-b`4auYaiehBAu&_Fq^YSIA%DY@jbu-K*GF)I$HGca07w&nDF%o+|f~STpt|B-p8Q z{a4p4nPssYti?RV*07E2dG-vefh+6=JgsA9MxM$^`}eAo~R!SBTypbrxF47=CsMoPCN)(CD1S>W9$Fsxs~Ay*LU z+%lH$Jl-0tA|}A?ry5aisga#cNli5#iI(i~OI{Ra#ss$@;coPJkj`v|tDT!1@l2i? zGaN8ve|tBDA`bv!DG+a7tZ>0y2%c$Jx9lXpSX^^|tVEn-H zBM5;g*^k9bLKQ@m>M))O0rCNa+lJBCAV*6~b~kc2ziPMxWBC_eQf}@aT8%YeU!$Ca zwQnp#69jKPEpB6NGXTh5_~25Y5#VA>fQ|s>!OEOOPtjN)mKx-QfPnN$q@e~wTpU^o z-V9jZ>>;^Ul#OB0wHgjmz2K4NCXX3-SO9Ib4w0C@Q+bG!u(mDAs7Sz%C0z1U zFn9y-Pw;tzcoQ~gRl?f?7h-}Xkxx)HNFfkm3GZq=2yT%=HN2zXrvtAzBy*Dopj#HW zkDCY3S zw(PCL!nPYOn@C&(j}!nUoV`BymdkFcIWkh=gfdok;knen;>;c|*IziPqZpx;glujC zjvtP)8A^;d3ZdNLT0vQfCX}%fYy?=eoaC*OdjeyWM8(yBb83PZ6KOy-3F;4^VhO%Z zaaJ`VrQm6l<7*Ujy^-j4LNrrFc6pzsG?jF4!{=`J_cXlc_MtxZlw03H=+&_#SwkU1D6o4 zA)m+uvl1MJum=d6nh@9x`oXnLHiN((IHph!lsNr4fzk6C*jeZqc48#X%P0&b!s$av zrSLL!9_YORm6Q9!2Lp8Tau#w^YUH-dBB71iQ%-XbZSr3Q6cY*2glgh*r7IDcjm$@a z7v$2=YM?IYB^;`abccUjQ>@Pg0KePqg)|dnIIsjDDTo*-(FnIZ4?J9q3fk4Eo;x1c z_z?=p(ccWQ0P?Y#fE~%)g6ub&u@ym(y^sy$cJC)B3b|Fh18@#Ob>ih}vsXrs?+o`L zst-W9eZVjRacsdLY&>w32!JNx2?syg@Y?ely^64iMaq z!4D$WP-CoPZGmdQ1I7pdU1X?kloR|W#{75&oJ6p15NX;U0T}Q-@&#zGJeU2X5?ZRW+b>}h?4Xpi;-l>h&SXGe_#etBF!rbf&a&vJc(ZM z|L9cK7OW>+!Cpu1-+%oNl=j=LDgxjsf?yb<5zDamvSz$+w*NAXb~!-cuVV}cSx zrV|tFUbg_%1X~-(R?2{_aa@t>iY7t<;?Zt-(lWCRa-YhMP); zkgX)aRx+u!k~z#)!szI2U|nl49Qj}zkrG4J5(d_Q3_V38^9X~Q!H7{Cp2VmPA85nP zBN$FGZiHg^Oo9?k<`HxP#XRCLp(~Jigg~;kVkHc`plO7C1jUQ&BO{H`h{dSyVX&aQ zL}@{=0~J2J`@Mk84J0!xRp>C#U)-eCq~q6Uf~le*2xnlUUr~C^A$!PV3_vj=+k$G{ zlhj@jnaJm#YX?BlLnanChkQ~L+LPNufpB}MT0zpM+C!iSbd@gEWXj@^)Ms2!gBi1% zK}_h2z5pob^Ea~dPyrP~XvC;A(M>`%jCdw}v6>(VsGFN(*ohQ14%Y*ipbbK?s}p8b zHK~gx76q-Tp6f7-8L?GTF?{?Ah9`v#!Ps{BZA`v^uzG#Y^QGFQ4pn#g#}wIl^`k zfQCu4UC>O40vQG>ZiGLY59G6h1ZJW#6{6u*jY5G11qE?K!k7SPW&&n!qz-)Z&QQz_o7c3Q7UnHH4FvCN)ZSt4>^{DiGm1{wvVKCYSGiVHBN9<{AfOc z;D_+*Bq`RjU7$NRQm9QMX?MC^F&fb0P+!blnCy6faR3{{qgW%@Pr|K{8dxJR#>{5) zMzAC-DcSAi)`&zU!`|cgfk~Gulwi~-2{itRHf-pv8KI`Ih2gn!+L(uOB z>(;V1U=q2;O(X(>WCO%_1%3}RJkY8E7*K?{YIp?jY}O196+bdOWUo{ww|ApOWG@Jb z8k3F3C|?}6JG9nS?G8aH6YLy;oJYovnphFBe2%Y2Y1MvCN9_(4H*KA_{V9#OwWEu4%_a$ zP;?|WigROi2-~yVf>`4=*0C73Wc0WjSZ7+nF)^;pDsi;M(=DXR)imxxn*z>a0GrM+ zT$^A-kivr3j?iD+>avaCLpV*G!s1}=Iw&+)vTVyd1%@)y_sSNvJ_~U}p4SYdzF?lmnD%KqDWDaLZi;{7~t@%O^@csL)6a)y_l?YiHB| zn;N`%jAqy{9Cam@=Sn375H^-itrL^|T{&FQEnpiUz)Had9GaC+0?RH0 z48m1Y?P}JA(6q-)-RKGRbw~O6u6&O(Gt{E9I`)Rs-Z6j z8r@NttHz2*jf^Q*j&~a%3*F4fh~#crXv7k$pPo6n&@KohQyvxspm2yuLBzd&It9eT zsD>)u02%`X$?{5orq1zpLkU*#rn7_s9xw*sT`MPHsgt-8^nj|FEP=kV&r2+ZJk*O} zOM-uy!5c(46`j-%&gVQrG&On^osx>u@Qs3{h;CQISB1jx^%B0`aD0I$#~0-RUpoQe z_F9O};7Jeh7f`OQXI*JQMy!!dFnquzp5Rgr?HG+gLNB(eCUSEDY(%_4d>{!-^bji( zIngwoXwAYF-V)SDo03jlRFvv{fW20 zGPL5@jSNpa11gfT+Tth17I{De%dr#p!U#tX1_U5l13`uKc$BE62~-p0aR7k;{>X4gP?Qg<>GqQ9&9C+M8KZ%+WjI6o7#wSrP&`FaiJp zCn+EyF*0+!aXyO|=kGK!Zm;=_96gQ8B~auiM)+d=6$>?}CVx();h2~mrw^f>Ft;H{ z0v34i3;87ChfWc~0uQ2*P7=Ok*r$xgSmeQ}KD2!s%RX4*A-*y4^&sg7%5U7o;z-V; zHqWrnX_n=4h9F4r>VRwx24UmBok+2c(s-*sZ=vz>3RJ(}cd-^3W52Hq!tvi$4H zRYsG=<)RmQp0?PP!Pw+PAuT^o7&1gi6NOx1*f6@&Te=!HTfI|Qe!h@aV6&A^TVl74 zu~oXXw4%@ty1=h}lvGH|5Ynjx<-bzouY;Y)s#p;|DqoC4_zLiedFA8s<;>b&0hpHg zuYlTWG)DkI8=Hr70>=Zzg+SQJ#&C3998t0LHJ`+mFb4ox0NR8n8&vJ7D4+xsDMzT| zw*sC9pcMe*WRvl`2taLkw*YSwE5xag(^S$1i7Do!y9Z8-Yx}(6Hv_x+zWxDi)8_)Y!!|upwTm$BdEfOEr*)f6l&JV zfSS=1YUas+n%)#@vz=oTBQ8b8AVyu`bCW}d8Td|$kNgN>-h@-`^;&`!0 zyh^-AyiUAPTqWKk-Yc#a?-w5w9~Re$kBP5{pNn6J`^E3Y!{QOKPOKLjM5sJ+tel7q zylv!e^67FRhMR>^SvmQkjmZ1}f2l zDunT=!d}X+GUn=S$(4^&u&sWdOi@IJ#(3JG8ftkG84f;bmoot)_*ga)tdygQm@JYV z=@|ntQf3JAE#*!t^$qnQOQ7%F6IY9rAV)~2Tds1;gJ?<1S7Opd^$u77m_bqLnf#X| zP+B@5;5Uc==BjD)ROspXT6q~_@F_j`6htc{h*w5dFg82*loNc)4L;=spMqs%0##Ho z2tP9jKQjnFGYCI38|p(^CJ?8ZXXf%ZUh_;;9bzMhaaKCTW$~(KX?n~+F6z^=vUnVa zm6ZeSIV}rBz%PfCl`jn7gv}PUhjfa~4q}$Az$2}?u&lyfZZ%oT!j)>GIn*fvLU?Ky z=L`%rY1ztv^4s$(+!e|{vfNr(Y<1A!m6n~OaY>Nw*;>;dxhkf) z8h*L?yc>inc6`DTo*IFJgv|@qDv#GHFGI*AJn}Ntdk~*I-UITKZWk)Apvvj8RgAG( zTvZM$CubhIm&hxVr(zjYuA9pAaAkmqNOcJ&KA})iWQHaKS}HR%8H2J=TdJo@ewF&@ z>J+H8A&CN#73dbKtWcy0ghdV@@csf(f#{&9$jl)CkQQpn905HjEuFYvU>+xb`apD3 zs#_kys5tGSuzZAR?L>EkL8PFtj9_=q;Cu&BqLcthLe=1nlYxF&SyqnMAjf3nX+c3o zhWt>-Ofp2l7a~Lp;xZ571E3%}02-o00SehcuR)0vlpiM;v`0b#b3!F6g5WlBWy}Jb z&VeU8D-F{&h-P|&G;I*fPyEYAH{QnTxTXn}i1AEivvB@X2iKE2oMlsI19O2B{?mg+ z4#xsgn!10pQW_$TdMyne=*-kBTHj$H?KI1#1(Hsiw+feZ$|MC1G<@{sKLkpd&&on5 zi7%zGf>IR^Au0SvhMnhA%aLKO~kJg zGcJX=mSCdEimzQjKymF-n{sFw-02W#cI0;eDv3NR8v%F_a(FvYDVpqTSt%2t2`~B$ zrD)R=Ei2{Nx`bxU6#AX0M%t7`%gQ>oMy)A}E2j}0eWJD>iMB82t?W8soA424lp?7~$gn z!Km!WC>DLZ7?A~InxgPTFb4C9oJRPeu^UPU7lhDvpgUqV0%{KK+3Gv=ApV{!41{K( z4K$?gD74@T0O}}oM*(~eNH_yee3C=xf~PF9eTA-JRe*HkWa0^%W0&>Gx+ z0~f$nX?>gi20nnTqtc+|{Q#Cx>+=NppcUl=`5=_%*F+SIvtw`zNzmc)nT4_Mmq>`sO8XQOFYJ4D4&AFTwkvk~*V2&y<#s6>bY-1xy&ih_N z4!Lj5-Fw-+#Yy(v=Gu^=fTl?9a=En93Xm{L5ZH;Wu>l4MkPnGF2xBCGon!!hQyetF z1jr}N5r{wBht9xfD6uCzTM0H}Dhh;nOxY(p>WOuCwwku0(|HKVl5Sgjx4UHJ_f&QF zG|5@+@MZ5fC+f5$r@N}U>Z#}D|GadKli$bCb?@)Hzr9ru{_KI*Ce~{A+Ju_>scm4t z?Q;O@fLDF+x#j!YrTZi2mhJCB=GJ3>ANibYe|tSg{ngA6sZSR6l%V5*X-#D=36J{h*M z2upg2uk(xa{73x0rv5tK#6QOG86yKV#&`V5zkfv=@s<7w_y0A%%P(-{=hZ(n{*gTE zn)3KJVgCCBJ>7%|GirbarZOx!w;)aNOL zG)a=>M@(Mi0FjxANy!ipB(Y&kHm>-8cGF~w?bM1tVbh~dGk*|IncK}WCzBY3Ec#<4 z2C#IO03DDTHWqkP&cH#$MHD}=`zv#ii+fk_s2?Lyj!Kg>7IHHqsjN0 z+z2r>zo76EErmvAZnIoMn zanvl=*_~^z;UpX=Yv{I`nk4%=eUl7*;i);7TPmB<1d~`dgk~{znfO0@ZwZd(DYMsR zGLf0eQnk~C=So61d!l4uQ*mKzBWUsf*<9Hl5YA0fyZdZZCX{w@rgbW=d)C|M4jyU) zZ%y2|J~Pv0pmhJ{ZtUEmnTZ=yw@6rL&8OxjW(~v9KHH$IjjUyMYI5#cu@Ql}H2HmK zieA4yAs+8TS$`)*P3!lAq9%Esu}Ml!m|qw^gV-61nx>XD4HFqI6isL6oyo`a^u)BG zzG=^oncbw%Tim#j?q{*pG0=WyazYeMpR_lvk4k$yVUs*KkUpB4yE!opXU^q^~1QN zPcy|PA6%|?jV%x(-x0YtGcy4ef*Iw*?Bv8$CS|#C${J)_Tx@g<3i1{K6aFXR$Uc>1 z-s2?Fb9Q>-W=_vrH@o02qi5bAqi3I&yAUhn>VLf`)=(a z2pBViu@P8&Q%ui{u{hWrY!Mb`=31s=`ZYG0-p???t+^DTP36;=n!Ij6nl#NpTDRQ{ z96L9Q)!Wf-mm=D$g8G5cw~kLgO-;>C+&my3iTRKXJr~|DsPfgwb<-9s5*mRdO)h? zU5-lC$F8&HaN|*11|=Vl&YA*2iPfeAx~sHMOJclt3PiVIS8Ej;_Nldc;!CU0_Ji3| z#04^Ne;jOb^OnI12f$rZd6%QI$zxGzGdB%dNL3ohN`g^4cQ9{~3tzF>aZnXJC4k`| zRL#2_k*Z+zgG1UpOtK3u%mEMz{)Vv%e#`rw*GN?V3;zD+TzPb& z7owOTi8z5fQBvm4T1oDaE40ofQYRzW{}XB{4rtzD$hC-xq?vG4-q?Y@osu1YSZVRVmx0zcpc3TYevg`wJ zYc{y>(c~--E<8cJFp|GuSfbFpnZ#>Nyn@6Z1W~w1NQKynjRM=K%UpVsh7yV)I!sr5 zU5N`zRE7%}8N#}PSTkh%<5T<1A+zpAsCLBSr;;hqM9xkz-F z1@$%p+4Lmpl0C9Vc(|7cnQQQv<*uB6)3;)zVEp*xhjK8UWS)3D@>3k}fbwG!Mg7YU zo3%$c5SSu|^Knm;$0I*dR@c9_&zNKCCegs_Q`GTgEW+}Q0P!Xl;&8)&tH&WUy&l{{ zYG(1XX7RR)^E)1?>77)`2y={5^Ha!+lpD$0WXCEwCV_pNl2aJ)fRdAD8T++7Ad@{} z@%~8;ChQQBGdDSL-Q<&YNe-n%VtOzd*7O)lbo@rA*W-K0&zwa2B@76m4TFxlO`5!>AvQD`o6qzCqDjOy)RGMxfNIA z&Zg9`pYf-|`FtEEX#O|NQFYl_-WSzH^@92b ze0RvF)z1Lb{~l*T{hXTN>czCpC3T73QsaEdT(#BCay1)c|KHa+erQ_#Ug;yH+0v)@ z_k8KcOMj>IGo_zYD{4ypWp$;`arS%4BqL3zSJZEmE|kuzd+IytyXsD9L~V0|+vUPq z_8VjWdzYx*_SB^MqH30&Wev|Twk{2qo+(}8n=UVNGIB<*{l_^A-JYPWoTYrl%uN2e zte)A24#&Ai4tvRgj=TQ*1n15Cgo;WNrN37CyZrlTX{Pk)(%&!rEMKua$EtsHM|&A# zzg6~r#w|v=$*I!+t@L#1snU@8fqF+hD2IIos!jFh>RaloDpGH7VCUD5d5@-%qc5l}WGGSJ zWc;_(9c1=b9?u?qhLiR`qkq}_?DhNK>7hu4_tRE8KmCC&Ig5X$huTj4jvn!qa{cD_ zceH;^pKsR}SL#}?)t&imexKW{x7U^y+qzxnV6=LBTaUF@7MJUry1wiz*6V9`^_X92 zg`SGF?$rEhlWzy=1Yt{sN!a2nZXHJd*39^IeYWi^EH930y?EbYJ%#YV~-Qjp; zN1o|8i%a8eecw@wceVDlex|7+9lNUHsu~AKE7wyh)Skz z^vaqUbW{c%cQ*NcLbGJ;e>iDXDq07ssS{5niK-+zWW**HbjkCDuD`5b*X31be7XI= zjwzk_Q>Hn>h;3)-UOJ%iImp>wRS5yDyhg0_?j-SEji8-C!WhB z_YAm_4XFJy%#i+yB|Z_#fK1E`$Om6}|He%4kM!`yg0sPfbcZ`%cM`qM{5G8V?r>T^ z+flx*sE*cE8IGwnm`VsLJlJHGy(1lfJIi(za6?U*#0|`WAa`SFSd; zozZ0(Hd)+q6pW=7x6Pg_e@m79gt>CCv<@ zhc?>n`_QD_-uSj2ilgYhE=3)-y`~b|DbVF6*;Mi z`l=OmKWhI!=eJ5VRrnCP*@xHla3`uXx*vx^<$f;h_L?3_s$q7Ib%n)2YWX1C@hjbC z!*Fb?JB{I_R>>#%u(h0o>;GFY8|q;=Q7ZZ!{TJ*}yeW(Rt{&+`8cupclu`7KE_I@N zI)q#5?LX6RYZYyUpVg*&mF~@~yYjQ{A$YLRJyfQ9-Kk6Wa?|%cl@8!CK+qk)5dF(C ziS7fxD<8k1hdN>OTRe4`%t^$t)#x538CU&k%;DKlP;GK}cBkQo9e&5~Q)f$G2!l?9 zbS0thhwpO4wja<((^4bHFT}NKQxuLz6DAeF=wXDiOmw44J9F?7fmE%@1BA=ot>W~?f@1NAFPx#T9; z0^2LriObhXw@{;zxjEz}G*OXI>727Dx(>yLei}j}p^va}Lv?3)+d0SO_M$V6d4W(K z^U&qQ4Rn)9H?aspt8=nu8Cf%rk{jRU%K*wgx)$m^=>^)au+%E_Y_bBk^@l>PNU5*r zpHQLCh#_A`5@|Lq387QfOLWzRY7zgMx?)HcW3R$a4WFl}{7&S#?CD61y6t$wZc4Kk zbU@ocC9IJ3x~)!?{?IlFl-G$>jR!r@FI1Urg=d7=11mGky%I-aAH=fORO|J`@gKQTwvSAM3qfNe}5h8BuOLeT=inENFSZJ@!8@ge*F!oKJ5H&j; zuaXk(Vn(saT{F2SXJohy`xofFH*@jaw3kYu)&(KMLdL! z#}Yql78r+}hC(vJNQe9qg1X#356=Z+3z-x|3{8_^6bmi|)br-$1KD zxmYz5RbyHalnXC;=F1eSjRbNluNL$5X;IC0g|O(Ja!qO9-3~GCd<{#f3L)enBCi*r zmGbE)ya#82TzpUdHjD#GJ)noz~+-w3l*>dL7 z^>Jssjx<=p8H9Eo!qr8d;m5l&&oc>wpmDsdjBID^!;rfBDkB;E*)&*7H~~AagZt_a zA=3rgx&iDXWa4`dbrOgsf+H#@xCmxa6+fLJc3Fm0DCUx1pwAfQ033mQ8v5|QvfGf8W&{B4Mg!RKwTNr8;-}6CyLv=4mX3$Rj?n!Hqz%&(3p_WZf(Nf4bvtP#s)5E& z<^yK5a*=Nsx=5^wWn5K-SauUFGv~Z?c6=T=gI|EYLnyO7)LM5IH&4q<5uo1aRF(%U4NFRa-St zf0Y*UTuYijGDMW7ZrE^*fkg?{LiKSm1l5qoLhi67inii4_}e#|zKfn!RoM!kH>1*< zQEL$-m633X5dTbyA*a{yATawKvQ{%i(Xyj70o@w_Hi%-G5yQqmSebSDI5$d=q6!v7C z^DclQrzP6R2Uu-mdHKd6{P~PEg_bY^Fz>T^q`j$=h50-B4dn1$3S%z>_>!Or zg!+pjHeyV`ybsd9$HXrYMW*8jwufq+$n-@`q2*&XO298)SH^uBMx8hH`K(W3K(Zj{ z@9VN`GMH4H)9=YB{QX(2#>G2czoUQG=nTPLb~_~b-5v={(JD4H^)zw$ zaMY#~1QyW-;DT8BLE}hw0uZ$EJ`pEGKk}Pg3#1*&=0W(J8C6`yriT;CMQ$@5?2hsA zoA>d==~V?B54EcPQ_{29OmB*%@tZH|&*{?QuK8XR%u>O(6C;F5KM(B1@k4{h_+BbT zl_UVLCj?81Xo;u6?PkPRH{p@sxQVQyQ_iE|m1|UikCVqe8MFcHP$QxrIt*79cqATN zfIJCyMH2y7_zq`LvIcH)trYORP|AN%{}$4}G(NwGMPlBd{b265zFo-EE*c!VVtQ13kshL@+@{2kd>~S(|UYJF%^9D-o+y!@9$& z`URkKY@mSnpe#@*5zwH4a~7&f1b01Pj>>Hqx6gAM$&)ozv$Qa`=>oG66?=NH9aSjG;_k8iv!Yj-3t+|l^jm~Xtf9}$cskyPUf;wH1n>;*iF4J0}=)C?oS zNTMf70c>#|lZ*Q0VO16n4Do6NMc~!2BzuL@xUVm#D`&ZGfZWK+1qPo>2>Zwrd<;(6 ztegknbz;)fQBxie$Z!r^EABjuV8j8g9TlXAOpEojD>t{Zm6x1(LTn-mV@Z8+ah$~` zMB+sx%0S(v^+YHjhO?wt&lSO-QQ%8h+Hkl0U~2xT%ldc_hSR^NFJPfv-|ROmp-bW* zm0I{r>1xwJNr(vX73o2`T2!4ZmdVOupEHYPI^a??6Dq_ESXgbDw&G1PL9<#&ul=&b z3i!qn@BvNbHlF?7)s8HWueKK!xtpvmIOA=W+g=`bmJPGplNul|B0=&z_>LpQ znnZV}Yc*h7SddodB_4(-R*_I_278RoypplCfB}>VdV$RqMuna7^$Jadr+jf2o-_(Z zCZ4SqMI%cqu9tSBF1rhNoP}?CtaoTW)qE+i#*7F z8cCD*9SE1^%#f&h9CbvsvA43VBuvuv0-(96|fxu?(s}<3m?&`>pE ztD=!ZeTmsuTf!oNs)%3lKz|G<-(VjJAvGio5_6-8ia_RLP-uwTB16jJ@I?&?5SZF_ z_@M|l!H!H!~!Xnkwtb-;Z|ZZli+qIj0qDSWwkqKho#XFe_0uQ#mE`mKKN#`2o91Bme2RTyzz@&Q{_w!< z&yqfCwL0HtbA%i(0s(>DDtKEDL)P#ewsDwUj{a{7!F7tjwf{X5kSPFhLg_lY_YTQe^tQv+ScN0d`Y$Te@&kefG8q)N8jPX|1mo$aQxdk7R@c~ z%k6r79(YR+^=onwfvUiZ=NjZ-=nWcwx@r)&P0|Q6Mq#IOAIT{jFntd-75FvyE5tl~ zM8J9LVcbSfDCqoY3L!-G0j({bWEYUdA%m`sAxwbe!#40MHmV5L*+a4{ z7OYEF#)W*owqPAcT%;ZJJt7w+?^2e84Ht@F9Upy5oi*yr-ZaUcm-TPz@_M~4*#yuD zL5TLMggw}h)pmj@Bo4B^A&xtAB084vD~dY};`^O#{BDACu;+ z5B)AWy#U;ssIvg;B}h(yJ7p2ZZ~;MuBJ!^b+&v52pF_QOfjdbT)I4-?hxO0hcoOll@Rb=7k=p}q5R1x6mt$~$Li-NfcE)3yEa;CIz(9 z4Upx-{np7VZay?AdpH6x0fk5VZN6y1@fGm{P^p^>4#habN!=O0OYFjP&*Isk1$V6; zrk8vqyv6EKm0S8EY+_)&(F>t|MhK864lDsg+aXOZG!WkZ7=wr}Wik;UOXE&8Y$8+w z3$QC5eo6y@#EfFR+~+9Ukr02*|B?q6&!oWrC`~AaQW3X5ale`V70VN(6eRd>Nm7%Hz!_HRaTPNnZUPR2aT913c9hp_Qj$h!0Yjl`Rg}zb znovNojg9|N>n60&sKmo0FJW^B1)@A}90i#Upq^>rClrW6;^O7Zl*~JesVR8|kr-cg z7RZ{;&pYD~+8M`XpstVY2cgAcI&z{s#UursFfjES>`;L!rI-+3S)yma{ZUUuD%4M# z<(RyHi}DcfMpTIP9}0d0;(^~FE^saN8_2hG{f3lF#BX2>{03hV2|hc9D`wna@i)#H zzabZs^VV|^l|nG$$v|e^)sf6;2jtjjqbAqahyVjkfFXS!)4tL=95XuL82>9wmz^?mxG|dO#M?BIM(ahDO=Bj*W_Iz9q)A>W$FQ*%6^E5E*t4v;DGl2_ ztdyM^u#i8BV$qn;fnF9LsQ?f(N@0L<-Tiw$vjZDrhsrVXsl~ZGw7%ciY8W3d#&GIKQ=$gn0F0Am^N zkUdx8Sw6bFUL28QEY5vCua2R##ie*`5nEq>v(_z50#rooWpnYnLixb#UXKXtUWUaBR=`W~4 zUfEgk$fS5SpCV^PM5}HQz*OBUt-!i*4fHlXKT2j;podl{s4iGxV0cE#`Y6Q&W)ezwDB5b)CeW2D3)lQj}V7UM+;8p<503 zy@R4srYY&GA)@$D^gXCk&JD5@2@PnivL);oNl#O5+-Zi@hD1q7R-y%M@PIfq$x9*& z?@_&SmtP4a+ZPW8s%BYe`uoJ4rNDx+(|zieWu<>?KsT)XUezw!=}{hrhu`X1zbw=G zg5FcZY&({dsL|S4$1JLv_JoOc)G|x^%=ui~UDI5=`FK3j-X8Je4&?0~U%c_(P+v9P z{m#FIIFjMd?wyp+Z<%mTzcto&A7veHnOrg{qyB2GrQ^3ud?~A+{)!nyUQ>E_&9&`S zMpi0ncfS)}do5G{f0AC?JMWWRgDunk4%&?BX&;Nr{K=;_gNi3FtBkb*&o}j5Va4-9 zD%1{Hhy7-{_XAOjEhHgo!VA(tD2HYknrQ!WXfo&|G{dlpP9#(s)@r6|?{M|lmMDWz zlWi#i^diLuHK@y$I(n)UlHrrBwH-{-)5D^)Za|EOmlh*-^sPGmp zg{KFs&juWzS5Iz@_U=`W1a2d$putBH5^FA=beWeeRFIagN>R-t>$D4NKC!jhDZf)R zVfdXo4S8LY{5JefwJ`uX_IN>=FoLPc>#JVd^7_fI*|uvRZOD5O4XgdA+a_v94K==5 zvldZnLm0H$jGC^W6X76r+XYgb@Y-!52G)_HZ^)5ag0Y6@*1#unpa(_Y2!l^>sT!xG zI8gm|ff6UUhFhp`JFVlE8UM*+tL4t0L_N1W>8pjBZhOx{>TYfKuB&N{ck!weA{_tc z-mrhSd+@kF?{qneb2=&R#+$`j^X@j_O&qS~yg0<6YtLm6oRj93Z|l#$#OJsAs6n^= z%DkT90rRB}-S(t@0Oc@1wdk^t)LH4)qyL_v0uJ6_)TGNAzOBnr(nXdDR7MeH)R6EN z8`O|9lFG!pTJcfp(ye|Tp&oDmYtw~5_%TwB#~XsF_2O_rYRrlGjqx5Wb$=4rr(V!M ze0{p5(cspor(}}if1OxPqE5Zlp-72p^Vk%`P;=29e`6 zq)v-U;{NK^Qz{Hz?fNcekotAECVmmBmxJRdC>!Pt8%_`Er+z)9!hqJWTT+a2szV1^ zFv^uWc3w2*I60RqT$Qpv4nL6h=?_@PZdK|4*RrS75!1tgJgMn<22tT0Dmj(O;{|er zhPTM65j$u-yQR(`*0c**3XmnHmi0obNhHnu3t=Nd-yHUHu)21k2Qd5b*S5>_jUPyT zyF64<8>Y~e8u#oTb?)Enc`M6>Nbp2z-ff5M>fpO*r?v8M!*RUCu(LXT=cku?ACCP} zzb9S4gt6+ceqCDjcP8~qrcSz_f^!+OuT!M$H2XLo`lbF(dzL9ax`exiWS$<-Y`FL^ ze7v(TyO^F9t@(i|(!QM^`y8X^JN%Q{r#_EmUhSLgad5=d9t&($xQ=*)fML+y#=9EOI)y|aG5JG`@GRA5C5SK8s_S3>- zt3Uq7X|?gAPDB>aQtLf9$oPX$7<~Ig)&t+}b&T;oWx&!tKgL+V z_G$TV@7Tu4&%f_3KYkC(e{ZJ;E%#gaySx1W6#VX;@=34t1uFfp)bO&@!yc7vQw)Pr znrjw%m~)ZsG6$vp)-1FC!;dAuc+!h&3rqUI6wY?n^nt63&6oDUt#(b9cv{7JgcFIy zWiolv(<;`!GJ+=`MW5IzR!NM=H`DZAziN66a^0#~KzfAo0G6z-9pRW{i`NFY9F@f% z@)Tsd^uew&m977wD;_1w*WX#jg4YMNHuSeq!jC?$I9=gj)_t;%eH==hE^&a1I$7sI zjvTgnX~0W0S@VHT6i(v?19+;iKyzH21DqNxYdx*5v@T7tTC@1FS@-Y9^}^d}b)~6G Jd-M^}{~xKj#E}31 literal 0 HcmV?d00001 diff --git a/src/beast/doc/images/message.png b/src/beast/doc/images/message.png new file mode 100644 index 0000000000000000000000000000000000000000..cb07efc82fb17c79d46203234e71e2be83191183 GIT binary patch literal 9532 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 NT}1~~qx>M^zW_u4LyG_a literal 0 HcmV?d00001 diff --git a/src/beast/doc/images/message.psd b/src/beast/doc/images/message.psd new file mode 100644 index 0000000000000000000000000000000000000000..2332dec4f13b8266645efbb16deb88e7098c1c85 GIT binary patch literal 204871 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 + + + + +

+ +
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) +})