detect_ssl, async_detect_ssl are public interfaces

This commit is contained in:
Vinnie Falco
2019-02-21 18:09:39 -08:00
parent b2807ae3ee
commit 90b783cb62
28 changed files with 1057 additions and 784 deletions

View File

@ -1,3 +1,9 @@
Version 218:
* detect_ssl, async_detect_ssl are public interfaces
--------------------------------------------------------------------------------
Version 217:
* websocket idle pings

View File

@ -95,6 +95,5 @@ effect:
[include 4_buffers.qbk]
[include 5_files.qbk]
[include 6_composed.qbk]
[include 7_detect_ssl.qbk]
[endsect]

View File

@ -8,7 +8,6 @@
]
[section Writing Composed Operations]
[block'''<?dbhtml stop-chunking?>''']
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]

View File

@ -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]

View File

@ -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]

View File

@ -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]

View File

@ -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]

View File

@ -64,10 +64,12 @@
<bridgehead renderas="sect3">Functions</bridgehead>
<simplelist type="vert" columns="1">
<member><link linkend="beast.ref.boost__beast__allocate_stable">allocate_stable</link>&nbsp;<emphasis role="green">&#128946;</emphasis></member>
<member><link linkend="beast.ref.boost__beast__async_detect_ssl">async_detect_ssl</link>&nbsp;<emphasis role="green">&#128946;</emphasis></member>
<member><link linkend="beast.ref.boost__beast__beast_close_socket">beast_close_socket</link>&nbsp;<emphasis role="green">&#128946;</emphasis></member>
<member><link linkend="beast.ref.boost__beast__bind_front_handler">bind_front_handler</link>&nbsp;<emphasis role="green">&#128946;</emphasis></member>
<member><link linkend="beast.ref.boost__beast__bind_handler">bind_handler</link></member>
<member><link linkend="beast.ref.boost__beast__close_socket">close_socket</link>&nbsp;<emphasis role="green">&#128946;</emphasis></member>
<member><link linkend="beast.ref.boost__beast__detect_ssl">detect_ssl</link>&nbsp;<emphasis role="green">&#128946;</emphasis></member>
<member><link linkend="beast.ref.boost__beast__generic_category">generic_category</link></member>
<member><link linkend="beast.ref.boost__beast__get_lowest_layer">get_lowest_layer</link>&nbsp;<emphasis role="green">&#128946;</emphasis></member>
<member><link linkend="beast.ref.boost__beast__iequals">iequals</link></member>

View File

@ -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.

View File

@ -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

View File

@ -13,7 +13,6 @@
//
//------------------------------------------------------------------------------
#include "example/common/detect_ssl.hpp"
#include "example/common/server_certificate.hpp"
#include <boost/beast/core.hpp>
@ -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(

View File

@ -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 <boost/assert.hpp>
#include <boost/config.hpp>
//------------------------------------------------------------------------------
//
// Example: Detect TLS/SSL
//
//------------------------------------------------------------------------------
//[example_core_detect_ssl_1
#include <boost/beast/core.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/asio/executor_work_guard.hpp>
#include <boost/logic/tribool.hpp>
/** 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<class ConstBufferSequence>
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<ConstBufferSequence>::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<SyncReadStream>::value,
"SyncReadStream type requirements not met");
static_assert(
boost::asio::is_dynamic_buffer<DynamicBuffer>::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
<em>composed operation</em>. 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<AsyncReadStream>::value,
"SyncReadStream type requirements not met");
static_assert(
boost::asio::is_dynamic_buffer<DynamicBuffer>::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<error_code, boost::tribool> 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<T>, 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<AsyncReadStream&>().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<class DeducedHandler>
detect_ssl_op(
AsyncReadStream& stream,
DynamicBuffer& buffer,
DeducedHandler&& handler)
: stream_(stream)
, work_(stream.get_executor())
, buffer_(buffer)
, handler_(std::forward<DeducedHandler>(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<Handler>;
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<AsyncReadStream&>().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<AsyncStream, DynamicBuffer, Handler>::
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

View File

@ -101,6 +101,11 @@ async_echo (
template<class AsyncStream, class Handler>
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 <boost/asio/yield.hpp>
// Read a line and echo it back
//
template<
@ -153,12 +158,6 @@ async_echo(
beast::executor_type<AsyncStream> /*< 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 <boost/asio/yield.hpp>
// 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 <boost/asio/yield.hpp>
// 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 <boost/asio/unyield.hpp>
//]
struct move_only_handler

View File

@ -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

View File

@ -13,7 +13,6 @@
//
//------------------------------------------------------------------------------
#include "example/common/detect_ssl.hpp"
#include "example/common/server_certificate.hpp"
#include <boost/beast/core.hpp>

View File

@ -12,6 +12,7 @@
#include <boost/beast/_experimental/unit_test/suite.hpp>
#include <boost/beast/core/error.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/core/exchange.hpp>
#include <boost/optional.hpp>
@ -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<class Rep, class Period>
void
run_for(
net::io_context& ioc,
std::chrono::duration<Rep, Period> elapsed)
{
ioc.run_for(elapsed);
ioc.restart();
}
} // test
} // beast
} // boost

View File

@ -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<class Rep, class Period>
void
run_for(
net::io_context& ioc,
std::chrono::duration<Rep, Period> elapsed)
{
ioc.run_for(elapsed);
ioc.restart();
}
/** Connect two TCP sockets together.
*/
template<class Executor>

View File

@ -23,6 +23,7 @@
#include <boost/beast/core/buffers_range.hpp>
#include <boost/beast/core/buffers_suffix.hpp>
#include <boost/beast/core/buffers_to_string.hpp>
#include <boost/beast/core/detect_ssl.hpp>
#include <boost/beast/core/dynamic_buffer_ref.hpp>
#include <boost/beast/core/error.hpp>
#include <boost/beast/core/file.hpp>

View File

@ -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 <boost/beast/core/detail/config.hpp>
#include <boost/beast/core/async_op_base.hpp>
#include <boost/beast/core/error.hpp>
#include <boost/beast/core/read_size.hpp>
#include <boost/beast/core/stream_traits.hpp>
#include <boost/logic/tribool.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/asio/coroutine.hpp>
#include <type_traits>
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 <em>ConstBufferSequence</em>.
@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
<a href="https://tools.ietf.org/html/rfc2246#section-7.4">7.4. Handshake protocol</a>
(RFC2246: The TLS Protocol)
*/
template <class ConstBufferSequence>
boost::tribool
is_tls_client_hello (ConstBufferSequence const& buffers);
} // detail
//]
//[example_core_detect_ssl_2
namespace detail {
template <class ConstBufferSequence>
boost::tribool
is_tls_client_hello (ConstBufferSequence const& buffers)
{
// Make sure buffers meets the requirements
static_assert(
net::is_const_buffer_sequence<ConstBufferSequence>::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 <em>composed operation</em>, 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 <em>SyncReadStream</em>.
@param buffer The dynamic buffer to use. This type must meet the
requirements of <em>DynamicBuffer</em>.
@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<SyncReadStream>::value,
"SyncReadStream type requirements not met");
static_assert(
net::is_dynamic_buffer<DynamicBuffer>::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 <em>composed asynchronous operation</em>,
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 <em>AsyncReadStream</em>.
@param buffer The dynamic buffer to use. This type must meet the
requirements of <em>DynamicBuffer</em>.
@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<CompletionToken>::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<DetectHandler>::type,
AsyncReadStream,
DynamicBuffer>(
std::forward<DetectHandler>(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<CompletionToken>::type,
void(error_code, boost::tribool)>::return_type
{
// Make sure arguments meet the type requirements
static_assert(
is_async_read_stream<AsyncReadStream>::value,
"SyncReadStream type requirements not met");
static_assert(
net::is_dynamic_buffer<DynamicBuffer>::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<AsyncReadStream>>
{
// 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<class DetectHandler_>
detect_ssl_op(
DetectHandler_&& handler,
AsyncReadStream& stream,
DynamicBuffer& buffer)
: async_op_base<DetectHandler_,
executor_type<AsyncReadStream>>(
std::forward<DetectHandler_>(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 <boost/asio/yield.hpp>
// 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<AsyncStream, DynamicBuffer, Handler>::
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 <boost/asio/unyield.hpp>
} // detail
//]
} // beast
} // boost
#endif

View File

@ -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

View File

@ -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

View File

@ -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 <boost/beast/core/detect_ssl.hpp>
#include <boost/beast/_experimental/unit_test/suite.hpp>
#include <boost/beast/_experimental/test/stream.hpp>
#include <boost/beast/_experimental/test/handler.hpp>
#include <boost/beast/core/flat_buffer.hpp>
#include <boost/beast/core/string.hpp>
#include <boost/core/exchange.hpp>
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

View File

@ -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

View File

@ -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

View File

@ -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 <boost/beast/core/flat_buffer.hpp>
#include <boost/beast/core/ostream.hpp>
#include <boost/beast/_experimental/test/stream.hpp>
#include <boost/beast/test/yield_to.hpp>
#include <boost/beast/_experimental/unit_test/suite.hpp>
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

View File

@ -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

View File

@ -8,7 +8,6 @@
#
local SOURCES =
detect_ssl.cpp
root_certificates.cpp
server_certificate.cpp
session_alloc.cpp

View File

@ -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"