From 90b783cb62dbfdf4a955406bf5fc641792cbd5ae Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Thu, 21 Feb 2019 18:09:39 -0800 Subject: [PATCH] detect_ssl, async_detect_ssl are public interfaces --- CHANGELOG.md | 6 + doc/qbk/03_core/0_core.qbk | 1 - doc/qbk/03_core/6_composed.qbk | 78 +-- doc/qbk/03_core/6a_echo.qbk | 80 +++ doc/qbk/03_core/6b_detect_ssl.qbk | 109 ++++ doc/qbk/03_core/7_detect_ssl.qbk | 69 -- doc/qbk/main.qbk | 2 +- doc/qbk/quickref.xml | 2 + doc/qbk/release_notes.qbk | 17 +- example/advanced/server-flex/CMakeLists.txt | 1 - .../server-flex/advanced_server_flex.cpp | 3 +- example/common/detect_ssl.hpp | 476 -------------- example/echo-op/echo_op.cpp | 17 +- example/http/server/flex/CMakeLists.txt | 1 - example/http/server/flex/http_server_flex.cpp | 1 - .../beast/_experimental/test/handler.hpp | 41 ++ .../boost/beast/_experimental/test/tcp.hpp | 40 -- include/boost/beast/core.hpp | 1 + include/boost/beast/core/detect_ssl.hpp | 612 ++++++++++++++++++ test/beast/core/CMakeLists.txt | 1 + test/beast/core/Jamfile | 1 + test/beast/core/detect_ssl.cpp | 181 ++++++ test/doc/CMakeLists.txt | 1 - test/doc/Jamfile | 2 - test/doc/core_examples.cpp | 84 --- test/example/common/CMakeLists.txt | 1 - test/example/common/Jamfile | 1 - test/example/common/detect_ssl.cpp | 12 - 28 files changed, 1057 insertions(+), 784 deletions(-) create mode 100644 doc/qbk/03_core/6a_echo.qbk create mode 100644 doc/qbk/03_core/6b_detect_ssl.qbk delete mode 100644 doc/qbk/03_core/7_detect_ssl.qbk delete mode 100644 example/common/detect_ssl.hpp create mode 100644 include/boost/beast/core/detect_ssl.hpp create mode 100644 test/beast/core/detect_ssl.cpp delete mode 100644 test/doc/core_examples.cpp delete mode 100644 test/example/common/detect_ssl.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index d36417d7..cb72329b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +Version 218: + +* detect_ssl, async_detect_ssl are public interfaces + +-------------------------------------------------------------------------------- + Version 217: * websocket idle pings diff --git a/doc/qbk/03_core/0_core.qbk b/doc/qbk/03_core/0_core.qbk index 28cf9869..b52f0e7a 100644 --- a/doc/qbk/03_core/0_core.qbk +++ b/doc/qbk/03_core/0_core.qbk @@ -95,6 +95,5 @@ effect: [include 4_buffers.qbk] [include 5_files.qbk] [include 6_composed.qbk] -[include 7_detect_ssl.qbk] [endsect] diff --git a/doc/qbk/03_core/6_composed.qbk b/doc/qbk/03_core/6_composed.qbk index 09e83980..f419f0a6 100644 --- a/doc/qbk/03_core/6_composed.qbk +++ b/doc/qbk/03_core/6_composed.qbk @@ -8,7 +8,6 @@ ] [section Writing Composed Operations] -[block''''''] Asynchronous operations are started by calling a free function or member function known as an asynchronous ['__async_initfn__]. This function accepts @@ -111,80 +110,7 @@ composed operations: ]] ] - - -[section Echo] - -This example develops an initiating function called [*echo]. -The operation will read up to the first newline on a stream, and -then write the same line including the newline back on the stream. -First we define the input parameters and results, then declare our -initiation function. For our echo operation the only inputs are the -stream and the completion token. The output is the error code which -is usually included in all completion handler signatures. - -[example_core_echo_op_2] - -Now that we have a declaration, we will define the body of the function. -We want to achieve the following goals: perform static type checking on -the input parameters, set up the return value as per __N3747__, and launch -the composed operation by constructing an intermediate, stateful completion -handler and invoking it. - -The initiating function contains a few relatively simple parts. There is -the customization of the return value type, static type checking, building -the return value type using the helper, and creating and launching the -`echo_op` composed operation object. - -The implementation strategy is to make the composed object meet the -requirements of a completion handler by being movable, and by making it -invocable so it can be used as a continuation for the asynchronous operations -it launches. Rather than using `std::bind` or `boost::bind`, which destroys -the type information and therefore breaks the allocation and invocation hooks, -we will simply pass `std::move(*this)` as the completion handler parameter for -any operations that we initiate. For the move to work correctly, care must be -taken to ensure that no access to data members are made after the move takes -place. Here is the complete implementation of our composed operation: - -[example_core_echo_op_3] - -There are some common mistakes that should be avoided when writing -composed operations: - -* Type erasing the final handler. This will cause undefined behavior. - -* Forgetting to include a return statement after calling an - initiating function. - -* Calling a synchronous function by accident. In general composed - operations should not block for long periods of time, since this - ties up a thread running on the __io_context__. - -* Forgetting to provide `executor_type` and `get_executor` for the - composed operation. This will cause undefined behavior. For example, - if someone calls the initiating function with a strand-wrapped - function object, and there is more than thread running on the - __io_context__, the underlying stream may be accessed in a fashion - that violates safety guarantees. Beast provides class templates - to take care of this boilerplate for you. - -* Forgetting to create an object of type __executor_work_guard__ with the - type of executor returned by the stream's `get_executor` member function. - -* For operations which complete immediately (i.e. without calling an - intermediate initiating function), forgetting to use __post__ to - invoke the final handler. This breaks the following initiating - function guarantee: ['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 __post__]. The function - __bind_handler__ is provided for this purpose. - -A complete, runnable version of this example may be found in the examples -directory. - -[endsect] - - +[include 6a_echo.qbk] +[include 6b_detect_ssl.qbk] [endsect] diff --git a/doc/qbk/03_core/6a_echo.qbk b/doc/qbk/03_core/6a_echo.qbk new file mode 100644 index 00000000..e95be58b --- /dev/null +++ b/doc/qbk/03_core/6a_echo.qbk @@ -0,0 +1,80 @@ +[/ + Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + + Official repository: https://github.com/boostorg/beast +] + +[section:echo Echo] + +This example develops an initiating function called [*echo]. +The operation will read up to the first newline on a stream, and +then write the same line including the newline back on the stream. +First we define the input parameters and results, then declare our +initiation function. For our echo operation the only inputs are the +stream and the completion token. The output is the error code which +is usually included in all completion handler signatures. + +[example_core_echo_op_2] + +Now that we have a declaration, we will define the body of the function. +We want to achieve the following goals: perform static type checking on +the input parameters, set up the return value as per __N3747__, and launch +the composed operation by constructing an intermediate, stateful completion +handler and invoking it. + +The initiating function contains a few relatively simple parts. There is +the customization of the return value type, static type checking, building +the return value type using the helper, and creating and launching the +`echo_op` composed operation object. + +The implementation strategy is to make the composed object meet the +requirements of a completion handler by being movable, and by making it +invocable so it can be used as a continuation for the asynchronous operations +it launches. Rather than using `std::bind` or `boost::bind`, which destroys +the type information and therefore breaks the allocation and invocation hooks, +we will simply pass `std::move(*this)` as the completion handler parameter for +any operations that we initiate. For the move to work correctly, care must be +taken to ensure that no access to data members are made after the move takes +place. Here is the complete implementation of our composed operation: + +[example_core_echo_op_3] + +There are some common mistakes that should be avoided when writing +composed operations: + +* Type erasing the final handler. This will cause undefined behavior. + +* Forgetting to include a return statement after calling an + initiating function. + +* Calling a synchronous function by accident. In general composed + operations should not block for long periods of time, since this + ties up a thread running on the __io_context__. + +* Forgetting to provide `executor_type` and `get_executor` for the + composed operation. This will cause undefined behavior. For example, + if someone calls the initiating function with a strand-wrapped + function object, and there is more than thread running on the + __io_context__, the underlying stream may be accessed in a fashion + that violates safety guarantees. Beast provides class templates + to take care of this boilerplate for you. + +* Forgetting to create an object of type __executor_work_guard__ with the + type of executor returned by the stream's `get_executor` member function. + +* For operations which complete immediately (i.e. without calling an + intermediate initiating function), forgetting to use __post__ to + invoke the final handler. This breaks the following initiating + function guarantee: ['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 __post__]. The function + __bind_handler__ is provided for this purpose. + +A complete, runnable version of this example may be found in the examples +directory. + +[endsect] diff --git a/doc/qbk/03_core/6b_detect_ssl.qbk b/doc/qbk/03_core/6b_detect_ssl.qbk new file mode 100644 index 00000000..ddf47693 --- /dev/null +++ b/doc/qbk/03_core/6b_detect_ssl.qbk @@ -0,0 +1,109 @@ +[/ + Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + + Official repository: https://github.com/boostorg/beast +] + +[section:detect_ssl Detect SSL] + +In this example we will build a simple function to detect the presence of the +[@https://tools.ietf.org/html/rfc2246#section-7.4 TLS client handshake] +given an input buffer sequence. Then we build on the example by adding a +synchronous stream algorithm. Finally, we implement an asynchronous +detection function using a composed operation. This SSL detector may +be used to allow a server to accept both TLS and plain (unencrypted) +connections at the same port. + +Here is the declaration for a function template to detect the SSL client +handshake. The function accepts any object whose type meets the requirements +of __ConstBufferSequence__. This gives callers flexibility to use a buffer +object whose behavior is appropriate to the task. + +[example_core_detect_ssl_1] + +The algorithm examines the buffer starting from the beginning, and +performs a series of qualifying checks against the TLS specification. +When not enough data exists to be certain, the returned value of +`boost::indeterminate` informs the caller to read more data into the buffer. +The function definition for the declaration above follows: + +[example_core_detect_ssl_2] + +The detection function above is suitably generic and targeted in +focus that it may be used as a building block to create higher level +abstractions. Our goal is to create a ['stream algorithm]: a function +which is invoked with a stream, that reads or writes (or both) to achieve +a purpose. In this case, to detect the TLS client handshake. Stream +algorithms may be synchronous or asynchronous. Because synchronous algorithms +are easier to write, we start there. Then we build the asynchronous version, +trying to model it similarly to make reasoning about it easier. + +The synchronous version is implemented thusly: + +[example_core_detect_ssl_3] + +Now that we have the synchronous version, we can attempt to model the +asynchronous version similarly. A function which launches an asynchronous +operation is called an ['initiating function]. While the synchronous +version above produces an error code through an output parameter, the +asynchronous version delivers the error code to a completion handler +or other custom mechanism defined by the completion token. The signature +of the initiating function reflects these differences. + +First we declare the initiating function and document the requirments, +parameters, preconditions, and effects: + +[example_core_detect_ssl_4] + +There are two additional components required to implement the initiating +function: + +* An intermediate completion handler, called the "composed operation" + object, which holds the state of the operation while it is in progress, + and also holds the user's completion handler to be invoked when the + opeartion completes, and + +* An "initiation" function object which when invoked with parameters + captured at the call site of the initiating function, constructs the + composed operation with the captured arguments and launches it. + +Here we forward declare the composed operation type, and provide the +definition of the initiation function object. They are placed in the +`detail` namespace since they should not be public: + +[example_core_detect_ssl_5] + +The initiating function definition itself is straightforward. We perform +type checking on the parameters, and then let `net::async_initiate` +capture the parameter list along with a copy of our initiation function +object. Depending on the specialization of `async_result` for the type +of `CompletionToken`, the initiation function may be invoked immediately. +Alternatively, it may be invoked later, after the initiating function +returns. This is known as "lazy execution," and allows efficient and +expressive abstractions to be written. + +[example_core_detect_ssl_6] + +Now we will declare our composed operation. There is a considerable +amount of necessary boilerplate to get this right, but the result +is worth the effort. + +[example_core_detect_ssl_7] + +The boilerplate is all done, and now we need to implement the function +call operator that turns this composed operation a completion handler +with the signature `void(error_code, std::size_t)` which is exactly +the signature needed when performing asynchronous reads. This function +is a transformation of the synchronous version of `detect_ssl` above, +but with the inversion of flow that characterizes code written in the +callback style: + +[example_core_detect_ssl_8] + +This SSL detector is used in the advanced-flex and http-flex servers +in the example directory. + +[endsect] diff --git a/doc/qbk/03_core/7_detect_ssl.qbk b/doc/qbk/03_core/7_detect_ssl.qbk deleted file mode 100644 index 595255d5..00000000 --- a/doc/qbk/03_core/7_detect_ssl.qbk +++ /dev/null @@ -1,69 +0,0 @@ -[/ - Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) - - Distributed under the Boost Software License, Version 1.0. (See accompanying - file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) - - Official repository: https://github.com/boostorg/beast -] - -[section Example: Detect SSL] - -In this example we will build a simple function to detect the presence -of the SSL handshake given an input buffer sequence. Then we build on -the example by adding a synchronous stream algorithm. Finally, we -implement an asynchronous detection function using a composed operation. -This SSL detector may be used to allow a server to accept both SSL/TLS and -unencrypted connections at the same port. - -Here is the declaration for a function to detect the SSL client handshake. -The input to the function is simply a buffer sequence, no stream. This -allows the detection algorithm to be used elsewhere. - -[example_core_detect_ssl_1] - -The implementation checks the buffer for the presence of the SSL -Handshake message octet sequence and returns an apporopriate value: - -[example_core_detect_ssl_2] - -Now we define a stream operation. We start with the simple, -synchronous version which takes the stream and buffer as input: - -[example_core_detect_ssl_3] - -The synchronous algorithm is the model for building the asynchronous -operation which has more boilerplate. First, we declare the asynchronous -initiating function: - -[example_core_detect_ssl_4] - -The implementation of the initiating function is straightforward -and contains mostly boilerplate. It is to construct the return -type customization helper to obtain the actual handler, and -then create the composed operation and launch it. The actual -code for interacting with the stream is in the composed operation, -which is written as a separate class. - -[example_core_detect_ssl_5] - -Now we will declare our composed operation. There is a considerable -amount of necessary boilerplate to get this right, but the result -is worth the effort. - -[example_core_detect_ssl_6] - -The boilerplate is all done, and now we need to implement the function -call operator that turns this composed operation a completion handler -with the signature `void(error_code, std::size_t)` which is exactly -the signature needed when performing asynchronous reads. This function -is a transformation of the synchronous version of `detect_ssl` above, -but with the inversion of flow that characterizes code written in the -callback style: - -[example_core_detect_ssl_7] - -This SSL detector is used by the server framework in the example -directory. - -[endsect] diff --git a/doc/qbk/main.qbk b/doc/qbk/main.qbk index f0bfe154..878cabc7 100644 --- a/doc/qbk/main.qbk +++ b/doc/qbk/main.qbk @@ -117,7 +117,6 @@ the paths close to absolute. ] -[import ../../example/common/detect_ssl.hpp] [import ../../example/doc/http_examples.hpp] [import ../../example/echo-op/echo_op.cpp] [import ../../example/http/client/sync/http_client_sync.cpp] @@ -134,6 +133,7 @@ [import ../../test/doc/core_3_layers.cpp] [import ../../test/doc/websocket_3_handshake.cpp] +[import ../../include/boost/beast/core/detect_ssl.hpp] [import ../../test/beast/core/rate_policy.cpp] [section:quickref Reference] diff --git a/doc/qbk/quickref.xml b/doc/qbk/quickref.xml index b572d09c..7c55b58d 100644 --- a/doc/qbk/quickref.xml +++ b/doc/qbk/quickref.xml @@ -64,10 +64,12 @@ Functions allocate_stable 🞲 + async_detect_ssl 🞲 beast_close_socket 🞲 bind_front_handler 🞲 bind_handler close_socket 🞲 + detect_ssl 🞲 generic_category get_lowest_layer 🞲 iequals diff --git a/doc/qbk/release_notes.qbk b/doc/qbk/release_notes.qbk index a651713d..28060012 100644 --- a/doc/qbk/release_notes.qbk +++ b/doc/qbk/release_notes.qbk @@ -35,6 +35,13 @@ ! * Chat with us at the [*#beast] and [*#boost] channels in the [@https://cppalliance.org/slack/ [*C++ Slack Workspace]]. + * [role green [*More tutorials]], code like the pros! + * [link beast.using_io.asio_refresher Networking Refresher] teaches you from the ground up. + * Updated [link beast.using_io.writing_composed_operations.echo Asynchronous Echo] example + * Updated [link beast.using_io.writing_composed_operations.detect_ssl [*Detect SSL Handshake]], + now a [link beast.ref.boost__beast__async_detect_ssl public api]! + * [@../../example/websocket/server/chat-multi websocket-chat-multi] + threaded chat server with a JavaScript browser client. * [link beast.ref.boost__beast__basic_stream `basic_stream`] and [link beast.ref.boost__beast__basic_stream `tcp_stream`] offer: * Timeouts: @@ -47,8 +54,9 @@ [link beast.ref.boost__beast__unlimited_rate_policy `unlimited`], or a user-defined [link beast.concepts.RatePolicy ['RatePolicy]]! - * [[@http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1322r0.html P1322R0]] - Put the strand on the socket itself, no more `bind_executor` at call sites! + * Put the strand directly on the socket using + [@http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1322r0.html P1322R0], + no more `bind_executor` at call sites! * Base classes [link beast.ref.boost__beast__async_op_base `async_op_base`] and [link beast.ref.boost__beast__stable_async_op_base `stable_async_op_base`] @@ -56,10 +64,7 @@ * All asynchronous operations use Asio's [@boost:/doc/html/boost_asio/reference/async_initiate.html `async_initiate`] for efficient integration with Coroutines TS. - * New [@../../example/websocket/server/chat-multi websocket-chat-multi] - multi-threaded websocket chat server with a JavaScript browser client. - * New [link beast.using_io.asio_refresher Networking Refresher] explains basic concepts. - * OpenSSL is required to build tests and examples + * OpenSSL is now required to build tests and examples * See the full [link beast.release_notes [*Release Notes]] for a complete list of changes. diff --git a/example/advanced/server-flex/CMakeLists.txt b/example/advanced/server-flex/CMakeLists.txt index 094f9acb..15c92b4b 100644 --- a/example/advanced/server-flex/CMakeLists.txt +++ b/example/advanced/server-flex/CMakeLists.txt @@ -14,7 +14,6 @@ if (OPENSSL_FOUND) add_executable (advanced-server-flex ${BOOST_BEAST_FILES} - ${PROJECT_SOURCE_DIR}/example/common/detect_ssl.hpp ${PROJECT_SOURCE_DIR}/example/common/server_certificate.hpp Jamfile advanced_server_flex.cpp diff --git a/example/advanced/server-flex/advanced_server_flex.cpp b/example/advanced/server-flex/advanced_server_flex.cpp index f51f7ba0..dcb3c627 100644 --- a/example/advanced/server-flex/advanced_server_flex.cpp +++ b/example/advanced/server-flex/advanced_server_flex.cpp @@ -13,7 +13,6 @@ // //------------------------------------------------------------------------------ -#include "example/common/detect_ssl.hpp" #include "example/common/server_certificate.hpp" #include @@ -822,7 +821,7 @@ public: // Set the timeout. stream_.expires_after(std::chrono::seconds(30)); - async_detect_ssl( + beast::async_detect_ssl( stream_, buffer_, beast::bind_front_handler( diff --git a/example/common/detect_ssl.hpp b/example/common/detect_ssl.hpp deleted file mode 100644 index a941d7aa..00000000 --- a/example/common/detect_ssl.hpp +++ /dev/null @@ -1,476 +0,0 @@ -// -// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/boostorg/beast -// - -#ifndef BOOST_BEAST_EXAMPLE_COMMON_DETECT_SSL_HPP -#define BOOST_BEAST_EXAMPLE_COMMON_DETECT_SSL_HPP - -#include -#include - -//------------------------------------------------------------------------------ -// -// Example: Detect TLS/SSL -// -//------------------------------------------------------------------------------ - -//[example_core_detect_ssl_1 - -#include -#include -#include -#include - -/** Return `true` if a buffer contains a TLS/SSL client handshake. - - This function returns `true` if the beginning of the buffer - indicates that a TLS handshake is being negotiated, and that - there are at least four octets in the buffer. - - If the content of the buffer cannot possibly be a TLS handshake - request, the function returns `false`. Otherwise, if additional - octets are required, `boost::indeterminate` is returned. - - @param buffer The input buffer to inspect. This type must meet - the requirements of @b ConstBufferSequence. - - @return `boost::tribool` indicating whether the buffer contains - a TLS client handshake, does not contain a handshake, or needs - additional octets. - - @see - - http://www.ietf.org/rfc/rfc2246.txt - 7.4. Handshake protocol -*/ -template -boost::tribool -is_ssl_handshake(ConstBufferSequence const& buffers); - -//] - -//[example_core_detect_ssl_2 - -template< - class ConstBufferSequence> -boost::tribool -is_ssl_handshake( - ConstBufferSequence const& buffers) -{ - // Make sure buffers meets the requirements - static_assert( - boost::asio::is_const_buffer_sequence::value, - "ConstBufferSequence type requirements not met"); - - // Extract the first byte, which holds the - // "message" type for the Handshake protocol. - unsigned char v; - if(boost::asio::buffer_copy(boost::asio::buffer(&v, 1), buffers) < 1) - { - // We need at least one byte to really do anything - return boost::indeterminate; - } - - // Check that the message type is "SSL Handshake" (rfc2246) - if(v != 0x16) - { - // This is definitely not a handshake - return false; - } - - // At least four bytes are needed for the handshake - // so make sure that we get them before returning `true` - if(boost::asio::buffer_size(buffers) < 4) - return boost::indeterminate; - - // This can only be a TLS/SSL handshake - return true; -} - -//] - -//[example_core_detect_ssl_3 - -/** Detect a TLS/SSL handshake on a stream. - - This function reads from a stream to determine if a TLS/SSL - handshake is being received. The function call will block - until one of the following conditions is true: - - @li The disposition of the handshake is determined - - @li An error occurs - - Octets read from the stream will be stored in the passed dynamic - buffer, which may be used to perform the TLS handshake if the - detector returns true, or otherwise consumed by the caller based - on the expected protocol. - - @param stream The stream to read from. This type must meet the - requirements of @b SyncReadStream. - - @param buffer The dynamic buffer to use. This type must meet the - requirements of @b DynamicBuffer. - - @param ec Set to the error if any occurred. - - @return `boost::tribool` indicating whether the buffer contains - a TLS client handshake, does not contain a handshake, or needs - additional octets. If an error occurs, the return value is - undefined. -*/ -template< - class SyncReadStream, - class DynamicBuffer> -boost::tribool -detect_ssl( - SyncReadStream& stream, - DynamicBuffer& buffer, - boost::beast::error_code& ec) -{ - namespace beast = boost::beast; - - // Make sure arguments meet the requirements - static_assert(beast::is_sync_read_stream::value, - "SyncReadStream type requirements not met"); - static_assert( - boost::asio::is_dynamic_buffer::value, - "DynamicBuffer type requirements not met"); - - // Loop until an error occurs or we get a definitive answer - for(;;) - { - // There could already be data in the buffer - // so we do this first, before reading from the stream. - auto const result = is_ssl_handshake(buffer.data()); - - // If we got an answer, return it - if(! boost::indeterminate(result)) - { - // This is a fast way to indicate success - // without retrieving the default category. - ec = {}; - return result; - } - - // The algorithm should never need more than 4 bytes - BOOST_ASSERT(buffer.size() < 4); - - // Prepare the buffer's output area. - auto const mutable_buffer = buffer.prepare(beast::read_size(buffer, 1536)); - - // Try to fill our buffer by reading from the stream - std::size_t const bytes_transferred = stream.read_some(mutable_buffer, ec); - - // Check for an error - if(ec) - break; - - // Commit what we read into the buffer's input area. - buffer.commit(bytes_transferred); - } - - // error - return false; -} - -//] - -//[example_core_detect_ssl_4 - -/** Detect a TLS/SSL handshake asynchronously on a stream. - - This function is used to asynchronously determine if a TLS/SSL - handshake is being received. - The function call always returns immediately. The asynchronous - operation will continue until one of the following conditions - is true: - - @li The disposition of the handshake is determined - - @li An error occurs - - This operation is implemented in terms of zero or more calls to - the next layer's `async_read_some` function, and is known as a - composed operation. The program must ensure that the - stream performs no other operations until this operation completes. - - Octets read from the stream will be stored in the passed dynamic - buffer, which may be used to perform the TLS handshake if the - detector returns true, or otherwise consumed by the caller based - on the expected protocol. - - @param stream The stream to read from. This type must meet the - requirements of @b AsyncReadStream. - - @param buffer The dynamic buffer to use. This type must meet the - requirements of @b DynamicBuffer. - - @param handler The completion handler to invoke when the operation - completes. The implementation takes ownership of the handler by - performing a decay-copy. The equivalent function signature of - the handler must be: - - @code - void handler( - error_code const& error, // Set to the error, if any - boost::tribool result // The result of the detector - ); - @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 `net::post`. -*/ -template< - class AsyncReadStream, - class DynamicBuffer, - class CompletionToken> -BOOST_ASIO_INITFN_RESULT_TYPE( /*< `BOOST_ASIO_INITFN_RESULT_TYPE` customizes the return value based on the completion token >*/ - CompletionToken, - void(boost::beast::error_code, boost::tribool)) /*< This is the signature for the completion handler >*/ -async_detect_ssl( - AsyncReadStream& stream, - DynamicBuffer& buffer, - CompletionToken&& token); - -//] - -//[example_core_detect_ssl_5 - -// This is the composed operation. -template< - class AsyncReadStream, - class DynamicBuffer, - class Handler> -class detect_ssl_op; - -// Here is the implementation of the asynchronous initiation function -template< - class AsyncReadStream, - class DynamicBuffer, - class CompletionToken> -BOOST_ASIO_INITFN_RESULT_TYPE( - CompletionToken, - void(boost::beast::error_code, boost::tribool)) -async_detect_ssl( - AsyncReadStream& stream, - DynamicBuffer& buffer, - CompletionToken&& token) -{ - namespace beast = boost::beast; - - // Make sure arguments meet the requirements - static_assert(beast::is_async_read_stream::value, - "SyncReadStream type requirements not met"); - static_assert( - boost::asio::is_dynamic_buffer::value, - "DynamicBuffer type requirements not met"); - - // This helper manages some of the handler's lifetime and - // uses the result and handler specializations associated with - // the completion token to help customize the return value. - // - boost::asio::async_completion< - CompletionToken, void(beast::error_code, boost::tribool)> init{token}; - - // Create the composed operation and launch it. This is a constructor - // call followed by invocation of operator(). We use BOOST_ASIO_HANDLER_TYPE - // to convert the completion token into the correct handler type, - // allowing user defined specializations of the async result template - // to take effect. - // - detect_ssl_op< - AsyncReadStream, - DynamicBuffer, - BOOST_ASIO_HANDLER_TYPE( - CompletionToken, void(beast::error_code, boost::tribool))>( - stream, buffer, std::move(init.completion_handler))(beast::error_code{}, 0); - - // This hook lets the caller see a return value when appropriate. - // For example this might return std::future if - // CompletionToken is boost::asio::use_future. - // - // If a coroutine is used for the token, the return value from - // this function will be the `boost::tribool` representing the result. - // - return init.result.get(); -} - -//] - -//[example_core_detect_ssl_6 - -// Read from a stream to invoke is_tls_handshake asynchronously. -// This will be implemented using Asio's "stackless coroutines" -// which are based on macros forming a switch statement. The -// operation is derived from `coroutine` for this reason. -// -template< - class AsyncReadStream, - class DynamicBuffer, - class Handler> -class detect_ssl_op : public boost::asio::coroutine -{ - // This composed operation has trivial state, - // so it is just kept inside the class and can - // be cheaply copied as needed by the implementation. - - AsyncReadStream& stream_; - - // Boost.Asio and the Networking TS require an object of - // type executor_work_guard, where T is the type of - // executor returned by the stream's get_executor function, - // to persist for the duration of the asynchronous operation. - boost::asio::executor_work_guard< - decltype(std::declval().get_executor())> work_; - - DynamicBuffer& buffer_; - Handler handler_; - boost::tribool result_ = false; - -public: - // Boost.Asio requires that handlers are CopyConstructible. - // The state for this operation is cheap to copy. - detect_ssl_op(detect_ssl_op const&) = default; - - // The constructor just keeps references the callers variables. - // - template - detect_ssl_op( - AsyncReadStream& stream, - DynamicBuffer& buffer, - DeducedHandler&& handler) - : stream_(stream) - , work_(stream.get_executor()) - , buffer_(buffer) - , handler_(std::forward(handler)) - { - } - - // Associated allocator support. This is Asio's system for - // allowing the final completion handler to customize the - // memory allocation strategy used for composed operation - // states. A composed operation needs to use the same allocator - // as the final handler. These declarations achieve that. - - using allocator_type = - boost::asio::associated_allocator_t; - - allocator_type - get_allocator() const noexcept - { - return (boost::asio::get_associated_allocator)(handler_); - } - - // Executor hook. This is Asio's system for customizing the - // manner in which asynchronous completion handlers are invoked. - // A composed operation needs to use the same executor to invoke - // intermediate completion handlers as that used to invoke the - // final handler. - - using executor_type = boost::asio::associated_executor_t< - Handler, decltype(std::declval().get_executor())>; - - executor_type get_executor() const noexcept - { - return (boost::asio::get_associated_executor)(handler_, stream_.get_executor()); - } - - // Our main entry point. This will get called as our - // intermediate operations complete. Definition below. - // - void operator()(boost::beast::error_code ec, std::size_t bytes_transferred); -}; - -//] - -//[example_core_detect_ssl_7 - -// detect_ssl_op is callable with the signature -// void(error_code, bytes_transferred), -// allowing `*this` to be used as a ReadHandler -// -template< - class AsyncStream, - class DynamicBuffer, - class Handler> -void -detect_ssl_op:: -operator()(boost::beast::error_code ec, std::size_t bytes_transferred) -{ - namespace beast = boost::beast; - - // This introduces the scope of the stackless coroutine - BOOST_ASIO_CORO_REENTER(*this) - { - // There could already be data in the buffer - // so we do this first, before reading from the stream. - result_ = is_ssl_handshake(buffer_.data()); - - // If we got an answer, return it - if(! boost::indeterminate(result_)) - { - // We need to invoke the handler, but the guarantee - // is that the handler will not be called before the - // call to async_detect_ssl returns, so we must post - // the operation to the executor. The helper function - // `bind_handler` lets us bind arguments in a safe way - // that preserves the type customization hooks of the - // original handler. - BOOST_ASIO_CORO_YIELD - boost::asio::post( - stream_.get_executor(), - beast::bind_front_handler( - std::move(*this), ec, 0)); - } - else - { - // Loop until an error occurs or we get a definitive answer - for(;;) - { - // The algorithm should never need more than 4 bytes - BOOST_ASSERT(buffer_.size() < 4); - - BOOST_ASIO_CORO_YIELD - { - // Prepare the buffer's output area. - auto const mutable_buffer = buffer_.prepare(beast::read_size(buffer_, 1536)); - - // Try to fill our buffer by reading from the stream - stream_.async_read_some(mutable_buffer, std::move(*this)); - } - - // Check for an error - if(ec) - break; - - // Commit what we read into the buffer's input area. - buffer_.commit(bytes_transferred); - - // See if we can detect the handshake - result_ = is_ssl_handshake(buffer_.data()); - - // If it is detected, call the handler - if(! boost::indeterminate(result_)) - { - // We don't need bind_handler here because we were invoked - // as a result of an intermediate asynchronous operation. - break; - } - } - } - - // Invoke the final handler. - handler_(ec, result_); - } -} - -//] - -#endif diff --git a/example/echo-op/echo_op.cpp b/example/echo-op/echo_op.cpp index ee33d0b2..b91dbd5e 100644 --- a/example/echo-op/echo_op.cpp +++ b/example/echo-op/echo_op.cpp @@ -101,6 +101,11 @@ async_echo ( template class echo_op; +// This example uses the Asio's stackless "fauxroutines", implemented +// using a macro-based solution. It makes the code easier to write and +// easier to read. This include file defines the necessary macros and types. +#include + // Read a line and echo it back // template< @@ -153,12 +158,6 @@ async_echo( beast::executor_type /*< The type of executor used by the stream to dispatch asynchronous operations >*/ >; - // This example uses the Asio's stackless "fauxroutines", implemented - // using a macro-based solution. It makes the code easier to write and - // easier to read. This include file defines the necessary macros and types. - -#include - // This nested class implements the echo composed operation as a // stateful completion handler. We derive from `async_op_base` to // take care of boilerplate and we derived from net::coroutine to @@ -313,9 +312,6 @@ async_echo( } }; -// Including this file undefines the macros used by the stackless fauxroutines. -#include - // Create the composed operation and launch it. This is a constructor // call followed by invocation of operator(). We use BOOST_ASIO_HANDLER_TYPE // to convert the completion token into the correct handler type, @@ -332,6 +328,9 @@ async_echo( return init.result.get(); } +// Including this file undefines the macros used by the stackless fauxroutines. +#include + //] struct move_only_handler diff --git a/example/http/server/flex/CMakeLists.txt b/example/http/server/flex/CMakeLists.txt index f14d0379..46bf8498 100644 --- a/example/http/server/flex/CMakeLists.txt +++ b/example/http/server/flex/CMakeLists.txt @@ -14,7 +14,6 @@ if (OPENSSL_FOUND) add_executable (http-server-flex ${BOOST_BEAST_FILES} - ${PROJECT_SOURCE_DIR}/example/common/detect_ssl.hpp ${PROJECT_SOURCE_DIR}/example/common/server_certificate.hpp Jamfile http_server_flex.cpp diff --git a/example/http/server/flex/http_server_flex.cpp b/example/http/server/flex/http_server_flex.cpp index 486fab78..d986fdbe 100644 --- a/example/http/server/flex/http_server_flex.cpp +++ b/example/http/server/flex/http_server_flex.cpp @@ -13,7 +13,6 @@ // //------------------------------------------------------------------------------ -#include "example/common/detect_ssl.hpp" #include "example/common/server_certificate.hpp" #include diff --git a/include/boost/beast/_experimental/test/handler.hpp b/include/boost/beast/_experimental/test/handler.hpp index 2f8af629..ccafa950 100644 --- a/include/boost/beast/_experimental/test/handler.hpp +++ b/include/boost/beast/_experimental/test/handler.hpp @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -140,6 +141,46 @@ fail_handler(error_code ec) noexcept return handler(ec); } +/** Run an I/O context. + + This function runs and dispatches handlers on the specified + I/O context, until one of the following conditions is true: + + @li The I/O context runs out of work. + + @param ioc The I/O context to run +*/ +inline +void +run(net::io_context& ioc) +{ + ioc.run(); + ioc.restart(); +} + +/** Run an I/O context for a certain amount of time. + + This function runs and dispatches handlers on the specified + I/O context, until one of the following conditions is true: + + @li The I/O context runs out of work. + + @li No completions occur and the specified amount of time has elapsed. + + @param ioc The I/O context to run + + @param elapsed The maximum amount of time to run for. +*/ +template +void +run_for( + net::io_context& ioc, + std::chrono::duration elapsed) +{ + ioc.run_for(elapsed); + ioc.restart(); +} + } // test } // beast } // boost diff --git a/include/boost/beast/_experimental/test/tcp.hpp b/include/boost/beast/_experimental/test/tcp.hpp index dc54673d..d794b4ee 100644 --- a/include/boost/beast/_experimental/test/tcp.hpp +++ b/include/boost/beast/_experimental/test/tcp.hpp @@ -21,46 +21,6 @@ namespace boost { namespace beast { namespace test { -/** Run an I/O context. - - This function runs and dispatches handlers on the specified - I/O context, until one of the following conditions is true: - - @li The I/O context runs out of work. - - @param ioc The I/O context to run -*/ -inline -void -run(net::io_context& ioc) -{ - ioc.run(); - ioc.restart(); -} - -/** Run an I/O context for a certain amount of time. - - This function runs and dispatches handlers on the specified - I/O context, until one of the following conditions is true: - - @li The I/O context runs out of work. - - @li No completions occur and the specified amount of time has elapsed. - - @param ioc The I/O context to run - - @param elapsed The maximum amount of time to run for. -*/ -template -void -run_for( - net::io_context& ioc, - std::chrono::duration elapsed) -{ - ioc.run_for(elapsed); - ioc.restart(); -} - /** Connect two TCP sockets together. */ template diff --git a/include/boost/beast/core.hpp b/include/boost/beast/core.hpp index e0dc65dc..51c9988d 100644 --- a/include/boost/beast/core.hpp +++ b/include/boost/beast/core.hpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include diff --git a/include/boost/beast/core/detect_ssl.hpp b/include/boost/beast/core/detect_ssl.hpp new file mode 100644 index 00000000..98aad395 --- /dev/null +++ b/include/boost/beast/core/detect_ssl.hpp @@ -0,0 +1,612 @@ +// +// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_CORE_DETECT_SSL_HPP +#define BOOST_BEAST_CORE_DETECT_SSL_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace beast { + +//------------------------------------------------------------------------------ +// +// Example: Detect TLS client_hello +// +// This is an example and also a public interface. It implements +// an algorithm for determining if a "TLS client_hello" message +// is received. It can be used to implement a listening port that +// can handle both plain and TLS encrypted connections. +// +//------------------------------------------------------------------------------ + +//[example_core_detect_ssl_1 + +// By convention, the "detail" namespace means "not-public." +// Identifiers in a detail namespace are not visible in the documentation, +// and users should not directly use those identifiers in programs, otherwise +// their program may break in the future. +// +// Using a detail namespace gives the library writer the freedom to change +// the interface or behavior later, and maintain backward-compatibility. + +namespace detail { + +/** Return `true` if the buffer contains a TLS Protocol client_hello message. + + This function analyzes the bytes at the beginning of the buffer + and compares it to a valid client_hello message. This is the + message required to be sent by a client at the beginning of + any TLS (encrypted communication) session, including when + resuming a session. + + The return value will be: + + @li `true` if the contents of the buffer unambiguously define + contain a client_hello message, + + @li `false` if the contents of the buffer cannot possibly + be a valid client_hello message, or + + @li `boost::indeterminate` if the buffer contains an + insufficient number of bytes to determine the result. In + this case the caller should read more data from the relevant + stream, append it to the buffers, and call this function again. + + @param buffers The buffer sequence to inspect. + This type must meet the requirements of ConstBufferSequence. + + @return `boost::tribool` indicating whether the buffer contains + a TLS client handshake, does not contain a handshake, or needs + additional bytes to determine an outcome. + + @see + + 7.4. Handshake protocol + (RFC2246: The TLS Protocol) +*/ +template +boost::tribool +is_tls_client_hello (ConstBufferSequence const& buffers); + +} // detail + +//] + +//[example_core_detect_ssl_2 + +namespace detail { + +template +boost::tribool +is_tls_client_hello (ConstBufferSequence const& buffers) +{ + // Make sure buffers meets the requirements + static_assert( + net::is_const_buffer_sequence::value, + "ConstBufferSequence type requirements not met"); + +/* + The first message on a TLS connection must be the client_hello, + which is a type of handshake record, and it cannot be compressed + or encrypted. A plaintext record has this format: + + 0 byte record_type // 0x16 = handshake + 1 byte major // major protocol version + 2 byte minor // minor protocol version + 3-4 uint16 length // size of the payload + 5 byte handshake_type // 0x01 = client_hello + 6 uint24 length // size of the ClientHello + 9 byte major // major protocol version + 10 byte minor // minor protocol version + 11 uint32 gmt_unix_time + 15 byte random_bytes[28] + ... +*/ + + // Flatten the input buffers into a single contiguous range + // of bytes on the stack to make it easier to work with the data. + unsigned char buf[9]; + auto const n = net::buffer_copy( + net::mutable_buffer(buf, sizeof(buf)), buffers); + + // Can't do much without any bytes + if(n < 1) + return boost::indeterminate; + + // Require the first byte to be 0x16, indicating a TLS handshake record + if(buf[0] != 0x16) + return false; + + // We need at least 5 bytes to know the record payload size + if(n < 5) + return boost::indeterminate; + + // Calculate the record payload size + std::uint32_t const length = (buf[3] << 8) + buf[4]; + + // A ClientHello message payload is at least 34 bytes. + // There can be multiple handshake messages in the same record. + if(length < 34) + return false; + + // We need at least 6 bytes to know the handshake type + if(n < 6) + return boost::indeterminate; + + // The handshake_type must be 0x01 == client_hello + if(buf[5] != 0x01) + return false; + + // We need at least 9 bytes to know the payload size + if(n < 9) + return boost::indeterminate; + + // Calculate the message payload size + std::uint32_t const size = + (buf[6] << 16) + (buf[7] << 8) + buf[8]; + + // The message payload can't be bigger than the enclosing record + if(size + 4 > length) + return false; + + // This can only be a TLS client_hello message + return true; +} + +} // detail + +//] + +//[example_core_detect_ssl_3 + +/** Detect a TLS client handshake on a stream. + + This function reads from a stream to determine if a client + handshake message is being received. + + The call blocks until one of the following is true: + + @li A TLS client opening handshake is detected, + + @li The received data is invalid for a TLS client handshake, or + + @li An error occurs. + + The algorithm, known as a composed operation, is implemented + in terms of calls to the next layer's `read_some` function. + + Bytes read from the stream will be stored in the passed dynamic + buffer, which may be used to perform the TLS handshake if the + detector returns true, or be otherwise consumed by the caller based + on the expected protocol. + + @param stream The stream to read from. This type must meet the + requirements of SyncReadStream. + + @param buffer The dynamic buffer to use. This type must meet the + requirements of DynamicBuffer. + + @param ec Set to the error if any occurred. + + @return `boost::tribool` indicating whether the buffer contains + a TLS client handshake, does not contain a handshake, or needs + additional octets. If an error occurs, the return value is + undefined. +*/ +template< + class SyncReadStream, + class DynamicBuffer> +boost::tribool +detect_ssl( + SyncReadStream& stream, + DynamicBuffer& buffer, + error_code& ec) +{ + namespace beast = boost::beast; + + // Make sure arguments meet the requirements + + static_assert( + is_sync_read_stream::value, + "SyncReadStream type requirements not met"); + + static_assert( + net::is_dynamic_buffer::value, + "DynamicBuffer type requirements not met"); + + // Loop until an error occurs or we get a definitive answer + for(;;) + { + // There could already be data in the buffer + // so we do this first, before reading from the stream. + auto const result = detail::is_tls_client_hello(buffer.data()); + + // If we got an answer, return it + if(! boost::indeterminate(result)) + { + // A definite answer is a success + ec = {}; + return result; + } + + // Try to fill our buffer by reading from the stream. + // The function read_size calculates a reasonable size for the + // amount to read next, using existing capacity if possible to + // avoid allocating memory, up to the limit of 1536 bytes which + // is the size of a normal TCP frame. + + std::size_t const bytes_transferred = stream.read_some( + buffer.prepare(beast::read_size(buffer, 1536)), ec); + + // Commit what we read into the buffer's input area. + buffer.commit(bytes_transferred); + + // Check for an error + if(ec) + break; + } + + // error + return false; +} + +//] + +//[example_core_detect_ssl_4 + +/** Detect a TLS/SSL handshake asynchronously on a stream. + + This function reads asynchronously from a stream to determine + if a client handshake message is being received. + + This call always returns immediately. The asynchronous operation + will continue until one of the following conditions is true: + + @li A TLS client opening handshake is detected, + + @li The received data is invalid for a TLS client handshake, or + + @li An error occurs. + + The algorithm, known as a composed asynchronous operation, + is implemented in terms of calls to the next layer's `async_read_some` + function. The program must ensure that no other calls to + `async_read_some` are performed until this operation completes. + + Bytes read from the stream will be stored in the passed dynamic + buffer, which may be used to perform the TLS handshake if the + detector returns true, or be otherwise consumed by the caller based + on the expected protocol. + + @param stream The stream to read from. This type must meet the + requirements of AsyncReadStream. + + @param buffer The dynamic buffer to use. This type must meet the + requirements of DynamicBuffer. + + @param token The completion token used to determine the method + used to provide the result of the asynchronous operation. If + this is a completion handler, the implementation takes ownership + of the handler by performing a decay-copy, and the equivalent + function signature of the handler must be: + + @code + void handler( + error_code const& error, // Set to the error, if any + boost::tribool result // The result of the detector + ); + @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 `net::post`. +*/ +template< + class AsyncReadStream, + class DynamicBuffer, + class CompletionToken> +auto +async_detect_ssl( + AsyncReadStream& stream, + DynamicBuffer& buffer, + CompletionToken&& token) + -> typename net::async_result< + typename std::decay::type, /*< `async_result` customizes the return value based on the completion token >*/ + void(error_code, boost::tribool)>::return_type; /*< This is the signature for the completion handler >*/ +//] + +//[example_core_detect_ssl_5 + +// These implementation details don't need to be public + +namespace detail { + +// The composed operation object +template< + class DetectHandler, + class AsyncReadStream, + class DynamicBuffer> +class detect_ssl_op; + +// This is a function object which `net::async_initiate` can use to launch +// our composed operation. This is a relatively new feature in networking +// which allows the asynchronous operation to be "lazily" executed (meaning +// that it is launched later). Users don't need to worry about this, but +// authors of composed operations need to write it this way to get the +// very best performance, for example when using Coroutines TS (`co_await`). + +struct run_detect_ssl_op +{ + // The implementation of `net::async_initiate` captures the + // arguments of the initiating function, and then calls this + // function object later with the captured arguments in order + // to launch the composed operation. All we need to do here + // is take those arguments and construct our composed operation + // object. + // + // `async_initiate` takes care of transforming the completion + // token into the "real handler" which must have the correct + // signature, in this case `void(error_code, boost::tri_bool)`. + + template< + class DetectHandler, + class AsyncReadStream, + class DynamicBuffer> + void operator()( + DetectHandler&& h, + AsyncReadStream& s, + DynamicBuffer& b) + { + detect_ssl_op< + typename std::decay::type, + AsyncReadStream, + DynamicBuffer>( + std::forward(h), + s, + b); + } +}; + +} // detail + +//] + +//[example_core_detect_ssl_6 + +// Here is the implementation of the asynchronous initiation function +template< + class AsyncReadStream, + class DynamicBuffer, + class CompletionToken> +auto +async_detect_ssl( + AsyncReadStream& stream, + DynamicBuffer& buffer, + CompletionToken&& token) + -> typename net::async_result< + typename std::decay::type, + void(error_code, boost::tribool)>::return_type +{ + // Make sure arguments meet the type requirements + + static_assert( + is_async_read_stream::value, + "SyncReadStream type requirements not met"); + + static_assert( + net::is_dynamic_buffer::value, + "DynamicBuffer type requirements not met"); + + // The function `net::async_initate` uses customization points + // to allow one asynchronous initiating function to work with + // all sorts of notification systems, such as callbacks but also + // fibers, futures, coroutines, and user-defined types. + // + // It works by capturing all of the arguments using perfect + // forwarding, and then depending on the specialization of + // `net::async_result` for the type of `CompletionToken`, + // the `initiation` object will be invoked with the saved + // parameters and the actual completion handler. Our + // initiating object is `run_detect_ssl_op` + + return net::async_initiate< + CompletionToken, + void(error_code, boost::tribool)>( + detail::run_detect_ssl_op{}, + token, + stream, + buffer); +} + +//] + +//[example_core_detect_ssl_7 + +namespace detail { + +// Read from a stream, calling is_tls_client_hello on the data +// data to determine if the TLS client handshake is present. +// +// This will be implemented using Asio's "stackless coroutines" +// which are based on macros forming a switch statement. The +// operation is derived from `coroutine` for this reason. +// +// The library type `async_op_base` takes care of all of the +// boilerplate for writing composed operations, including: +// +// * Storing the user's completion handler +// * Maintaining the work guard for the handler's associated executor +// * Propagating the associated allocator of the handler +// * Propagating the associated executor of the handler +// * Deallocating temporary storage before invoking the handler +// * Posting the handler to the executor on an immediate completion +// +// `async_op_base` needs to know the type of the handler, as well +// as the executor of the I/O object being used. The metafunction +// `executor_type` returns the type of executor used by an +// I/O object. +// +template< + class DetectHandler, + class AsyncReadStream, + class DynamicBuffer> +class detect_ssl_op + : public boost::asio::coroutine + , public async_op_base< + DetectHandler, executor_type> +{ + // This composed operation has trivial state, + // so it is just kept inside the class and can + // be cheaply copied as needed by the implementation. + + AsyncReadStream& stream_; + + // The callers buffer is used to hold all received data + DynamicBuffer& buffer_; + + boost::tribool result_ = false; + +public: + // Completion handlers must be MoveConstructible. + detect_ssl_op(detect_ssl_op&&) = default; + + // Construct the operation. The handler is deduced through + // the template type `DetectHandler_`, this lets the same constructor + // work properly for both lvalues and rvalues. + // + template + detect_ssl_op( + DetectHandler_&& handler, + AsyncReadStream& stream, + DynamicBuffer& buffer) + : async_op_base>( + std::forward(handler), + stream.get_executor()) + , stream_(stream) + , buffer_(buffer) + { + // This starts the operation. We pass `false` to tell the + // algorithm that it needs to use net::post if it wants to + // complete immediately. This is required by Networking, + // as initiating functions are not allowed to invoke the + // completion handler on the caller's thread before + // returning. + (*this)({}, 0, false); + } + + // Our main entry point. This will get called as our + // intermediate operations complete. Definition below. + // + // The parameter `cont` indicates if we are being called subsequently + // from the original invocation + // + void operator()( + error_code ec, + std::size_t bytes_transferred, + bool cont = true); +}; + +} // detail + +//] + +//[example_core_detect_ssl_8 + +namespace detail { + +// This example uses the Asio's stackless "fauxroutines", implemented +// using a macro-based solution. It makes the code easier to write and +// easier to read. This include file defines the necessary macros and types. +#include + +// detect_ssl_op is callable with the signature void(error_code, bytes_transferred), +// allowing `*this` to be used as a ReadHandler +// +template< + class AsyncStream, + class DynamicBuffer, + class Handler> +void +detect_ssl_op:: +operator()(error_code ec, std::size_t bytes_transferred, bool cont) +{ + namespace beast = boost::beast; + + // This introduces the scope of the stackless coroutine + reenter(*this) + { + // Loop until an error occurs or we get a definitive answer + for(;;) + { + // There could already be a hello in the buffer so check first + result_ = is_tls_client_hello(buffer_.data()); + + // If we got an answer, then the operation is complete + if(! boost::indeterminate(result_)) + break; + + // Try to fill our buffer by reading from the stream. + // The function read_size calculates a reasonable size for the + // amount to read next, using existing capacity if possible to + // avoid allocating memory, up to the limit of 1536 bytes which + // is the size of a normal TCP frame. + // + // `async_read_some` expects a ReadHandler as the completion + // handler. The signature of a read handler is void(error_code, size_t), + // and this function matches that signature (the `cont` parameter has + // a default of true). We pass `std::move(*this)` as the completion + // handler for the read operation. This transfers ownership of this + // entire state machine back into the `async_read_some` operation. + // Care must be taken with this idiom, to ensure that parameters + // passed to the initiating function which could be invalidated + // by the move, are first moved to the stack before calling the + // initiating function. + + yield stream_.async_read_some(buffer_.prepare( + read_size(buffer_, 1536)), std::move(*this)); + + // Commit what we read into the buffer's input area. + buffer_.commit(bytes_transferred); + + // Check for an error + if(ec) + break; + } + + // Invoke the final handler. + // If `cont` is true, the handler will be invoked directly. + // Otherwise, the handler will be submitted to the executor + // through a call to `net::post`. + + this->invoke(cont, ec, result_); + } +} + +// Including this file undefines the macros used by the stackless fauxroutines. +#include + +} // detail + +//] + +} // beast +} // boost + +#endif diff --git a/test/beast/core/CMakeLists.txt b/test/beast/core/CMakeLists.txt index 6fe50495..3606549d 100644 --- a/test/beast/core/CMakeLists.txt +++ b/test/beast/core/CMakeLists.txt @@ -44,6 +44,7 @@ add_executable (tests-beast-core buffers_range.cpp buffers_suffix.cpp buffers_to_string.cpp + detect_ssl.cpp dynamic_buffer_ref.cpp error.cpp file.cpp diff --git a/test/beast/core/Jamfile b/test/beast/core/Jamfile index 3bab6352..0bd70b00 100644 --- a/test/beast/core/Jamfile +++ b/test/beast/core/Jamfile @@ -32,6 +32,7 @@ local SOURCES = buffers_range.cpp buffers_suffix.cpp buffers_to_string.cpp + detect_ssl.cpp dynamic_buffer_ref.cpp error.cpp file.cpp diff --git a/test/beast/core/detect_ssl.cpp b/test/beast/core/detect_ssl.cpp new file mode 100644 index 00000000..7e457414 --- /dev/null +++ b/test/beast/core/detect_ssl.cpp @@ -0,0 +1,181 @@ +// +// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/beast +// + +// Test that header file is self-contained. +#include + +#include +#include +#include +#include +#include +#include +namespace boost { +namespace beast { + +class detect_ssl_test : public unit_test::suite +{ +public: + void + testDetect() + { + auto const yes = + [](int n, string_view s) + { + BEAST_EXPECT(detail::is_tls_client_hello( + net::const_buffer(s.data(), n))); + }; + + auto const no = + [](int n, string_view s) + { + BEAST_EXPECT(! detail::is_tls_client_hello( + net::const_buffer(s.data(), n))); + }; + + auto const maybe = + [](int n, string_view s) + { + BEAST_EXPECT(boost::indeterminate( + detail::is_tls_client_hello( + net::const_buffer(s.data(), n)))); + }; + + maybe( 0, "\x00\x00\x00\x00\x00\x00\x00\x00\x00"); + no ( 1, "\x01\x00\x00\x00\x00\x00\x00\x00\x00"); + maybe( 1, "\x16\x00\x00\x00\x00\x00\x00\x00\x00"); + maybe( 4, "\x16\x00\x00\x00\x00\x00\x00\x00\x00"); + no ( 5, "\x16\x00\x00\x00\x00\x00\x00\x00\x00"); + maybe( 5, "\x16\x00\x00\x01\x00\x00\x00\x00\x00"); + no ( 8, "\x16\x00\x00\x01\x00\x00\x00\x00\x00"); + maybe( 8, "\x16\x00\x00\x01\x00\x01\x00\x00\x00"); + no ( 9, "\x16\x00\x00\x01\x00\x01\x01\x00\x00"); + yes ( 9, "\x16\x00\x00\x01\x00\x01\x00\x00\x00"); + } + + void + testRead() + { + net::io_context ioc; + + // true + + { + error_code ec; + flat_buffer b; + test::stream s1(ioc); + s1.append({"\x16\x00\x00\x01\x00\x01\x00\x00\x00", 9}); + auto result = detect_ssl(s1, b, ec); + BEAST_EXPECT(result == true); + BEAST_EXPECTS(! ec, ec.message()); + } + + // true + + { + error_code ec; + flat_buffer b; + test::stream s1(ioc); + auto s2 = test::connect(s1); + s1.append({"\x16\x00\x00\x01\x00\x01\x00\x00\x00", 9}); + s2.close(); + auto result = detect_ssl(s1, b, ec); + BEAST_EXPECT(result == true); + BEAST_EXPECTS(! ec, ec.message()); + } + + // false + + { + error_code ec; + flat_buffer b; + test::stream s1(ioc); + s1.append({"\x16\x00\x00\x01\x00\x01\x01\x00\x00", 9}); + auto result = detect_ssl(s1, b, ec); + BEAST_EXPECT(result == false); + BEAST_EXPECTS(! ec, ec.message()); + } + + // eof + { + error_code ec; + flat_buffer b; + test::stream s1(ioc); + auto s2 = test::connect(s1); + s1.append({"\x16\x00\x00\x01\x00", 5}); + s2.close(); + auto result = detect_ssl(s1, b, ec); + BEAST_EXPECT(result == false); + BEAST_EXPECT(ec); + } + } + + void + testAsyncRead() + { + net::io_context ioc; + + // true + + { + flat_buffer b; + test::stream s1(ioc); + s1.append({"\x16\x00\x00\x01\x00\x01\x00\x00\x00", 9}); + async_detect_ssl(s1, b, test::success_handler()); + test::run(ioc); + } + + // true + + { + flat_buffer b; + test::stream s1(ioc); + auto s2 = test::connect(s1); + s1.append({"\x16\x00\x00\x01\x00\x01\x00\x00\x00", 9}); + s2.close(); + async_detect_ssl(s1, b, test::success_handler()); + test::run(ioc); + } + + // false + + { + flat_buffer b; + test::stream s1(ioc); + s1.append({"\x16\x00\x00\x01\x00\x01\x01\x00\x00", 9}); + async_detect_ssl(s1, b, test::success_handler()); + test::run(ioc); + } + + // eof + { + flat_buffer b; + test::stream s1(ioc); + auto s2 = test::connect(s1); + s1.append({"\x16\x00\x00\x01\x00", 5}); + s2.close(); + async_detect_ssl(s1, b, + test::fail_handler(net::error::eof)); + test::run(ioc); + } + } + + void + run() override + { + testDetect(); + testRead(); + testAsyncRead(); + } +}; + +BEAST_DEFINE_TESTSUITE(beast,core,detect_ssl); + +} // beast +} // boost diff --git a/test/doc/CMakeLists.txt b/test/doc/CMakeLists.txt index 106a480b..ec6fcb40 100644 --- a/test/doc/CMakeLists.txt +++ b/test/doc/CMakeLists.txt @@ -19,7 +19,6 @@ add_executable (tests-doc Jamfile snippets.hpp snippets.ipp - core_examples.cpp core_snippets.cpp core_1_refresher.cpp core_3_layers.cpp diff --git a/test/doc/Jamfile b/test/doc/Jamfile index 4a3abefc..44161a1c 100644 --- a/test/doc/Jamfile +++ b/test/doc/Jamfile @@ -19,7 +19,6 @@ alias run-tests : [ compile core_snippets.cpp ] [ compile http_snippets.cpp ] [ compile websocket_snippets.cpp ] - [ run core_examples.cpp $(TEST_MAIN) ] [ run core_1_refresher.cpp $(TEST_MAIN) ] [ run core_3_layers.cpp $(TEST_MAIN) ] [ run http_examples.cpp $(TEST_MAIN) ] @@ -28,7 +27,6 @@ alias run-tests : exe fat-tests : $(TEST_MAIN) - core_examples.cpp core_1_refresher.cpp core_3_layers.cpp http_examples.cpp diff --git a/test/doc/core_examples.cpp b/test/doc/core_examples.cpp deleted file mode 100644 index 30afc9e9..00000000 --- a/test/doc/core_examples.cpp +++ /dev/null @@ -1,84 +0,0 @@ -// -// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/boostorg/beast -// - -#include "example/common/detect_ssl.hpp" - -#include -#include -#include -#include -#include - -namespace boost { -namespace beast { - -class examples_test - : public beast::unit_test::suite - , public beast::test::enable_yield_to -{ -public: - void - testDetect() - { - char buf[4]; - buf[0] = 0x16; - buf[1] = 0; - buf[2] = 0; - buf[3] = 0; - BEAST_EXPECT(boost::indeterminate(is_ssl_handshake( - net::buffer(buf, 0)))); - BEAST_EXPECT(boost::indeterminate(is_ssl_handshake( - net::buffer(buf, 1)))); - BEAST_EXPECT(boost::indeterminate(is_ssl_handshake( - net::buffer(buf, 2)))); - BEAST_EXPECT(boost::indeterminate(is_ssl_handshake( - net::buffer(buf, 3)))); - BEAST_EXPECT(is_ssl_handshake( - net::buffer(buf, 4))); - buf[0] = 0; - BEAST_EXPECT(! is_ssl_handshake( - net::buffer(buf, 1))); - } - - void - testRead() - { - { - test::stream ts{ioc_, "\x16***"}; - error_code ec; - flat_buffer b; - auto const result = detect_ssl(ts, b, ec); - BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(result); - } - yield_to( - [&](yield_context yield) - { - test::stream ts{ioc_, "\x16***"}; - error_code ec; - flat_buffer b; - auto const result = - async_detect_ssl(ts, b, yield[ec]); - BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(result); - }); - } - - void - run() - { - testDetect(); - testRead(); - } -}; - -BEAST_DEFINE_TESTSUITE(beast,core,examples); - -} // beast -} // boost diff --git a/test/example/common/CMakeLists.txt b/test/example/common/CMakeLists.txt index 5ee13696..5d1f8f59 100644 --- a/test/example/common/CMakeLists.txt +++ b/test/example/common/CMakeLists.txt @@ -18,7 +18,6 @@ add_executable (tests-example-common ${EXTRAS_FILES} ${TEST_MAIN} Jamfile - detect_ssl.cpp root_certificates.cpp server_certificate.cpp session_alloc.cpp diff --git a/test/example/common/Jamfile b/test/example/common/Jamfile index 76ba448e..7220bc52 100644 --- a/test/example/common/Jamfile +++ b/test/example/common/Jamfile @@ -8,7 +8,6 @@ # local SOURCES = - detect_ssl.cpp root_certificates.cpp server_certificate.cpp session_alloc.cpp diff --git a/test/example/common/detect_ssl.cpp b/test/example/common/detect_ssl.cpp deleted file mode 100644 index 3596fccc..00000000 --- a/test/example/common/detect_ssl.cpp +++ /dev/null @@ -1,12 +0,0 @@ -// -// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/boostorg/beast -// - -// Test that header file is self-contained. -#include "example/common/detect_ssl.hpp" -