2017-06-08 22:03:58 -07:00
//
2017-07-24 09:42:36 -07:00
// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
2017-06-08 22:03:58 -07:00
//
// 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)
//
2017-07-20 13:40:34 -07:00
// Official repository: https://github.com/boostorg/beast
//
2017-06-08 22:03:58 -07:00
2017-07-20 13:40:34 -07:00
# ifndef BOOST_BEAST_EXAMPLE_COMMON_DETECT_SSL_HPP
# define BOOST_BEAST_EXAMPLE_COMMON_DETECT_SSL_HPP
2017-06-18 06:51:51 -07:00
2017-06-08 22:03:58 -07:00
# include <boost/assert.hpp>
# include <boost/config.hpp>
//------------------------------------------------------------------------------
//
// Example: Detect TLS/SSL
//
//------------------------------------------------------------------------------
2017-06-18 06:51:51 -07:00
//[example_core_detect_ssl_1
2017-06-08 22:03:58 -07:00
2017-07-20 13:40:34 -07:00
# include <boost/beast.hpp>
2017-06-08 22:03:58 -07:00
# 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 ) ;
//]
2017-07-20 13:40:34 -07:00
using namespace boost : : beast ;
2017-06-08 22:03:58 -07:00
2017-06-18 06:51:51 -07:00
//[example_core_detect_ssl_2
2017-06-08 22:03:58 -07:00
template <
class ConstBufferSequence >
boost : : tribool
is_ssl_handshake (
ConstBufferSequence const & buffers )
{
// Make sure buffers meets the requirements
static_assert ( is_const_buffer_sequence < ConstBufferSequence > : : value ,
" ConstBufferSequence requirements not met " ) ;
// We need at least one byte to really do anything
if ( boost : : asio : : buffer_size ( buffers ) < 1 )
return boost : : indeterminate ;
// Extract the first byte, which holds the
// "message" type for the Handshake protocol.
unsigned char v ;
boost : : asio : : buffer_copy ( boost : : asio : : buffer ( & v , 1 ) , buffers ) ;
// 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 ;
}
//]
2017-06-18 06:51:51 -07:00
//[example_core_detect_ssl_3
2017-06-08 22:03:58 -07:00
/** 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 ,
error_code & ec )
{
// Make sure arguments meet the requirements
static_assert ( is_sync_read_stream < SyncReadStream > : : value ,
" SyncReadStream requirements not met " ) ;
static_assert ( is_dynamic_buffer < DynamicBuffer > : : value ,
" DynamicBuffer requirements not met " ) ;
2017-06-13 07:08:52 -07:00
2017-06-08 22:03:58 -07:00
// 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 ) )
2017-06-13 07:08:52 -07:00
{
ec = { } ; // indicate no error
2017-06-08 22:03:58 -07:00
return result ;
2017-06-13 07:08:52 -07:00
}
2017-06-08 22:03:58 -07:00
// The algorithm should never need more than 4 bytes
BOOST_ASSERT ( buffer . size ( ) < 4 ) ;
2017-06-12 05:01:24 -07:00
// Create up to 4 bytes of space in the buffer's output area.
auto const mutable_buffer = buffer . prepare ( 4 - buffer . size ( ) ) ;
// Try to fill our buffer by reading from the stream
std : : size_t const bytes_transferred = stream . read_some ( mutable_buffer , ec ) ;
2017-06-08 22:03:58 -07:00
// Check for an error
if ( ec )
break ;
2017-06-12 05:01:24 -07:00
// Commit what we read into the buffer's input area.
buffer . commit ( bytes_transferred ) ;
2017-06-08 22:03:58 -07:00
}
// error
return false ;
}
//]
2017-06-18 06:51:51 -07:00
//[example_core_detect_ssl_4
2017-06-08 22:03:58 -07:00
/** 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 handler to be called when the request
completes . Copies will be made of the handler as required .
The equivalent function signature of the handler must be :
@ code
void handler (
error_code const & error , // 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 ` boost : : asio : : io_service : : post ` .
*/
template <
class AsyncReadStream ,
class DynamicBuffer ,
class CompletionToken >
2017-07-23 22:49:57 -07:00
async_return_type < /*< The [link beast.ref.boost__beast__async_return_type `async_return_type`] customizes the return value based on the completion token >*/
2017-06-08 22:03:58 -07:00
CompletionToken ,
void ( error_code , boost : : tribool ) > /*< This is the signature for the completion handler >*/
async_detect_ssl (
AsyncReadStream & stream ,
DynamicBuffer & buffer ,
CompletionToken & & token ) ;
//]
2017-06-18 06:51:51 -07:00
//[example_core_detect_ssl_5
2017-06-08 22:03:58 -07:00
// This is the composed operation.
template <
class AsyncReadStream ,
class DynamicBuffer ,
class Handler >
class detect_ssl_op ;
// Here is the implementation of the asynchronous initation function
template <
class AsyncReadStream ,
class DynamicBuffer ,
class CompletionToken >
async_return_type <
CompletionToken ,
void ( error_code , boost : : tribool ) >
async_detect_ssl (
AsyncReadStream & stream ,
DynamicBuffer & buffer ,
CompletionToken & & token )
{
// Make sure arguments meet the requirements
static_assert ( is_async_read_stream < AsyncReadStream > : : value ,
" SyncReadStream requirements not met " ) ;
static_assert ( is_dynamic_buffer < DynamicBuffer > : : value ,
" DynamicBuffer 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.
//
2017-07-20 13:40:34 -07:00
boost : : beast : : async_completion <
CompletionToken , void ( boost : : beast : : error_code , boost : : tribool ) > init { token } ;
2017-06-08 22:03:58 -07:00
// Create the composed operation and launch it. This is a constructor
// call followed by invocation of operator(). We use 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 , handler_type <
CompletionToken , void ( error_code , boost : : tribool ) > > {
stream , buffer , init . completion_handler } (
2017-07-20 13:40:34 -07:00
boost : : beast : : error_code { } , 0 ) ;
2017-06-08 22:03:58 -07:00
// 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 ( ) ;
}
//]
2017-06-18 06:51:51 -07:00
//[example_core_detect_ssl_6
2017-06-08 22:03:58 -07:00
// Read from a stream to invoke is_tls_handshake asynchronously
//
template <
class AsyncReadStream ,
class DynamicBuffer ,
class Handler >
class detect_ssl_op
{
// This composed operation has trivial state,
// so it is just kept inside the class and can
// be cheaply copied as needed by the implementation.
// Indicates what step in the operation's state
// machine to perform next, starting from zero.
int step_ = 0 ;
AsyncReadStream & stream_ ;
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 varaibles.
//
template < class DeducedHandler >
detect_ssl_op ( AsyncReadStream & stream ,
DynamicBuffer & buffer , DeducedHandler & & handler )
: stream_ ( stream )
, buffer_ ( buffer )
, handler_ ( std : : forward < DeducedHandler > ( handler ) )
{
}
// Determines if the next asynchronous operation represents a
// continuation of the asynchronous flow of control associated
// with the final handler. If we are past step two, it means
// we have performed an asynchronous operation therefore any
// subsequent operation would represent a continuation.
// Otherwise, we propagate the handler's associated value of
// is_continuation. Getting this right means the implementation
// may schedule the invokation of the invoked functions more
// efficiently.
//
friend bool asio_handler_is_continuation ( detect_ssl_op * op )
{
// This next call is structured to permit argument
// dependent lookup to take effect.
using boost : : asio : : asio_handler_is_continuation ;
// Always use std::addressof to pass the pointer to the handler,
// otherwise an unwanted overload of operator& may be called instead.
return op - > step_ > 2 | |
asio_handler_is_continuation ( std : : addressof ( op - > handler_ ) ) ;
}
// Handler hook forwarding. These free functions invoke the hooks
// associated with the final completion handler. In effect, they
// make the Asio implementation treat our composed operation the
// same way it would treat the final completion handler for the
// purpose of memory allocation and invocation.
//
// Our implementation just passes through the call to the hook
// associated with the final handler.
friend void * asio_handler_allocate ( std : : size_t size , detect_ssl_op * op )
{
using boost : : asio : : asio_handler_allocate ;
return asio_handler_allocate ( size , std : : addressof ( op - > handler_ ) ) ;
}
friend void asio_handler_deallocate ( void * p , std : : size_t size , detect_ssl_op * op )
{
using boost : : asio : : asio_handler_deallocate ;
return asio_handler_deallocate ( p , size , std : : addressof ( op - > handler_ ) ) ;
}
template < class Function >
friend void asio_handler_invoke ( Function & & f , detect_ssl_op * op )
{
using boost : : asio : : asio_handler_invoke ;
return asio_handler_invoke ( f , std : : addressof ( op - > handler_ ) ) ;
}
// Our main entry point. This will get called as our
// intermediate operations complete. Definition below.
//
2017-07-20 13:40:34 -07:00
void operator ( ) ( boost : : beast : : error_code ec , std : : size_t bytes_transferred ) ;
2017-06-08 22:03:58 -07:00
} ;
//]
2017-06-18 06:51:51 -07:00
//[example_core_detect_ssl_7
2017-06-08 22:03:58 -07:00
// 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 > : :
2017-07-20 13:40:34 -07:00
operator ( ) ( boost : : beast : : error_code ec , std : : size_t bytes_transferred )
2017-06-08 22:03:58 -07:00
{
// Execute the state machine
switch ( step_ )
{
// Initial state
case 0 :
// See if we can detect the handshake
result_ = is_ssl_handshake ( buffer_ . data ( ) ) ;
// If there's a result, call the handler
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 io_service. The helper function
// `bind_handler` lets us bind arguments in a safe way
// that preserves the type customization hooks of the
// original handler.
step_ = 1 ;
return stream_ . get_io_service ( ) . post (
bind_handler ( std : : move ( * this ) , ec , 0 ) ) ;
}
// The algorithm should never need more than 4 bytes
BOOST_ASSERT ( buffer_ . size ( ) < 4 ) ;
step_ = 2 ;
do_read :
// We need more bytes, but no more than four total.
return stream_ . async_read_some ( buffer_ . prepare ( 4 - buffer_ . size ( ) ) , std : : move ( * this ) ) ;
case 1 :
// Call the handler
break ;
case 2 :
// Set this so that asio_handler_is_continuation knows that
// the next asynchronous operation represents a continuation
// of the initial asynchronous operation.
step_ = 3 ;
BOOST_FALLTHROUGH ;
case 3 :
if ( ec )
{
// Deliver the error to the handler
result_ = false ;
// We don't need bind_handler here because we were invoked
// as a result of an intermediate asynchronous operation.
break ;
}
// Commit the bytes that we read
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 ;
}
// Read some more
goto do_read ;
}
// Invoke the final handler.
handler_ ( ec , result_ ) ;
}
//]
2017-06-18 06:51:51 -07:00
# endif