2 // Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7 // Official repository: https://github.com/boostorg/beast
10 #ifndef BOOST_BEAST_EXAMPLE_COMMON_DETECT_SSL_HPP
11 #define BOOST_BEAST_EXAMPLE_COMMON_DETECT_SSL_HPP
13 #include <boost/assert.hpp>
14 #include <boost/config.hpp>
16 //------------------------------------------------------------------------------
18 // Example: Detect TLS/SSL
20 //------------------------------------------------------------------------------
22 //[example_core_detect_ssl_1
24 #include <boost/beast/core.hpp>
25 #include <boost/logic/tribool.hpp>
27 /** Return `true` if a buffer contains a TLS/SSL client handshake.
29 This function returns `true` if the beginning of the buffer
30 indicates that a TLS handshake is being negotiated, and that
31 there are at least four octets in the buffer.
33 If the content of the buffer cannot possibly be a TLS handshake
34 request, the function returns `false`. Otherwise, if additional
35 octets are required, `boost::indeterminate` is returned.
37 @param buffer The input buffer to inspect. This type must meet
38 the requirements of @b ConstBufferSequence.
40 @return `boost::tribool` indicating whether the buffer contains
41 a TLS client handshake, does not contain a handshake, or needs
46 http://www.ietf.org/rfc/rfc2246.txt
47 7.4. Handshake protocol
49 template<class ConstBufferSequence>
51 is_ssl_handshake(ConstBufferSequence const& buffers);
55 //[example_core_detect_ssl_2
58 class ConstBufferSequence>
61 ConstBufferSequence const& buffers)
63 // Make sure buffers meets the requirements
65 boost::asio::is_const_buffer_sequence<ConstBufferSequence>::value,
66 "ConstBufferSequence requirements not met");
68 // We need at least one byte to really do anything
69 if(boost::asio::buffer_size(buffers) < 1)
70 return boost::indeterminate;
72 // Extract the first byte, which holds the
73 // "message" type for the Handshake protocol.
75 boost::asio::buffer_copy(boost::asio::buffer(&v, 1), buffers);
77 // Check that the message type is "SSL Handshake" (rfc2246)
80 // This is definitely not a handshake
84 // At least four bytes are needed for the handshake
85 // so make sure that we get them before returning `true`
86 if(boost::asio::buffer_size(buffers) < 4)
87 return boost::indeterminate;
89 // This can only be a TLS/SSL handshake
95 //[example_core_detect_ssl_3
97 /** Detect a TLS/SSL handshake on a stream.
99 This function reads from a stream to determine if a TLS/SSL
100 handshake is being received. The function call will block
101 until one of the following conditions is true:
103 @li The disposition of the handshake is determined
107 Octets read from the stream will be stored in the passed dynamic
108 buffer, which may be used to perform the TLS handshake if the
109 detector returns true, or otherwise consumed by the caller based
110 on the expected protocol.
112 @param stream The stream to read from. This type must meet the
113 requirements of @b SyncReadStream.
115 @param buffer The dynamic buffer to use. This type must meet the
116 requirements of @b DynamicBuffer.
118 @param ec Set to the error if any occurred.
120 @return `boost::tribool` indicating whether the buffer contains
121 a TLS client handshake, does not contain a handshake, or needs
122 additional octets. If an error occurs, the return value is
126 class SyncReadStream,
130 SyncReadStream& stream,
131 DynamicBuffer& buffer,
132 boost::beast::error_code& ec)
134 namespace beast = boost::beast;
136 // Make sure arguments meet the requirements
137 static_assert(beast::is_sync_read_stream<SyncReadStream>::value,
138 "SyncReadStream requirements not met");
140 boost::asio::is_dynamic_buffer<DynamicBuffer>::value,
141 "DynamicBuffer requirements not met");
143 // Loop until an error occurs or we get a definitive answer
146 // There could already be data in the buffer
147 // so we do this first, before reading from the stream.
148 auto const result = is_ssl_handshake(buffer.data());
150 // If we got an answer, return it
151 if(! boost::indeterminate(result))
153 // This is a fast way to indicate success
154 // without retrieving the default category.
155 ec.assign(0, ec.category());
159 // The algorithm should never need more than 4 bytes
160 BOOST_ASSERT(buffer.size() < 4);
162 // Prepare the buffer's output area.
163 auto const mutable_buffer = buffer.prepare(beast::read_size(buffer, 1536));
165 // Try to fill our buffer by reading from the stream
166 std::size_t const bytes_transferred = stream.read_some(mutable_buffer, ec);
168 // Check for an error
172 // Commit what we read into the buffer's input area.
173 buffer.commit(bytes_transferred);
182 //[example_core_detect_ssl_4
184 /** Detect a TLS/SSL handshake asynchronously on a stream.
186 This function is used to asynchronously determine if a TLS/SSL
187 handshake is being received.
188 The function call always returns immediately. The asynchronous
189 operation will continue until one of the following conditions
192 @li The disposition of the handshake is determined
196 This operation is implemented in terms of zero or more calls to
197 the next layer's `async_read_some` function, and is known as a
198 <em>composed operation</em>. The program must ensure that the
199 stream performs no other operations until this operation completes.
201 Octets read from the stream will be stored in the passed dynamic
202 buffer, which may be used to perform the TLS handshake if the
203 detector returns true, or otherwise consumed by the caller based
204 on the expected protocol.
206 @param stream The stream to read from. This type must meet the
207 requirements of @b AsyncReadStream.
209 @param buffer The dynamic buffer to use. This type must meet the
210 requirements of @b DynamicBuffer.
212 @param handler The handler to be called when the request
213 completes. Copies will be made of the handler as required.
214 The equivalent function signature of the handler must be:
217 error_code const& error, // Set to the error, if any
218 boost::tribool result // The result of the detector
221 Regardless of whether the asynchronous operation completes
222 immediately or not, the handler will not be invoked from within
223 this function. Invocation of the handler will be performed in a
224 manner equivalent to using `boost::asio::io_context::post`.
227 class AsyncReadStream,
229 class CompletionToken>
230 BOOST_ASIO_INITFN_RESULT_TYPE( /*< `BOOST_ASIO_INITFN_RESULT_TYPE` customizes the return value based on the completion token >*/
232 void(boost::beast::error_code, boost::tribool)) /*< This is the signature for the completion handler >*/
234 AsyncReadStream& stream,
235 DynamicBuffer& buffer,
236 CompletionToken&& token);
240 //[example_core_detect_ssl_5
242 // This is the composed operation.
244 class AsyncReadStream,
249 // Here is the implementation of the asynchronous initation function
251 class AsyncReadStream,
253 class CompletionToken>
254 BOOST_ASIO_INITFN_RESULT_TYPE(
256 void(boost::beast::error_code, boost::tribool))
258 AsyncReadStream& stream,
259 DynamicBuffer& buffer,
260 CompletionToken&& token)
262 namespace beast = boost::beast;
264 // Make sure arguments meet the requirements
265 static_assert(beast::is_async_read_stream<AsyncReadStream>::value,
266 "SyncReadStream requirements not met");
268 boost::asio::is_dynamic_buffer<DynamicBuffer>::value,
269 "DynamicBuffer requirements not met");
271 // This helper manages some of the handler's lifetime and
272 // uses the result and handler specializations associated with
273 // the completion token to help customize the return value.
275 boost::asio::async_completion<
276 CompletionToken, void(beast::error_code, boost::tribool)> init{token};
278 // Create the composed operation and launch it. This is a constructor
279 // call followed by invocation of operator(). We use BOOST_ASIO_HANDLER_TYPE
280 // to convert the completion token into the correct handler type,
281 // allowing user defined specializations of the async result template
287 BOOST_ASIO_HANDLER_TYPE(
288 CompletionToken, void(beast::error_code, boost::tribool))>{
289 stream, buffer, init.completion_handler}(beast::error_code{}, 0);
291 // This hook lets the caller see a return value when appropriate.
292 // For example this might return std::future<error_code, boost::tribool> if
293 // CompletionToken is boost::asio::use_future.
295 // If a coroutine is used for the token, the return value from
296 // this function will be the `boost::tribool` representing the result.
298 return init.result.get();
303 //[example_core_detect_ssl_6
305 // Read from a stream to invoke is_tls_handshake asynchronously
308 class AsyncReadStream,
313 // This composed operation has trivial state,
314 // so it is just kept inside the class and can
315 // be cheaply copied as needed by the implementation.
317 // Indicates what step in the operation's state
318 // machine to perform next, starting from zero.
321 AsyncReadStream& stream_;
322 DynamicBuffer& buffer_;
324 boost::tribool result_ = false;
327 // Boost.Asio requires that handlers are CopyConstructible.
328 // The state for this operation is cheap to copy.
329 detect_ssl_op(detect_ssl_op const&) = default;
331 // The constructor just keeps references the callers varaibles.
333 template<class DeducedHandler>
335 AsyncReadStream& stream,
336 DynamicBuffer& buffer,
337 DeducedHandler&& handler)
340 , handler_(std::forward<DeducedHandler>(handler))
344 // Associated allocator support. This is Asio's system for
345 // allowing the final completion handler to customize the
346 // memory allocation strategy used for composed operation
347 // states. A composed operation needs to use the same allocator
348 // as the final handler. These declarations achieve that.
350 using allocator_type =
351 boost::asio::associated_allocator_t<Handler>;
354 get_allocator() const noexcept
356 return boost::asio::get_associated_allocator(handler_);
359 // Executor hook. This is Asio's system for customizing the
360 // manner in which asynchronous completion handlers are invoked.
361 // A composed operation needs to use the same executor to invoke
362 // intermediate completion handlers as that used to invoke the
365 using executor_type = boost::asio::associated_executor_t<
366 Handler, decltype(std::declval<AsyncReadStream&>().get_executor())>;
368 executor_type get_executor() const noexcept
370 return boost::asio::get_associated_executor(handler_, stream_.get_executor());
373 // Determines if the next asynchronous operation represents a
374 // continuation of the asynchronous flow of control associated
375 // with the final handler. If we are past step two, it means
376 // we have performed an asynchronous operation therefore any
377 // subsequent operation would represent a continuation.
378 // Otherwise, we propagate the handler's associated value of
379 // is_continuation. Getting this right means the implementation
380 // may schedule the invokation of the invoked functions more
383 friend bool asio_handler_is_continuation(detect_ssl_op* op)
385 // This next call is structured to permit argument
386 // dependent lookup to take effect.
387 using boost::asio::asio_handler_is_continuation;
389 // Always use std::addressof to pass the pointer to the handler,
390 // otherwise an unwanted overload of operator& may be called instead.
391 return op->step_ > 2 ||
392 asio_handler_is_continuation(std::addressof(op->handler_));
395 // Our main entry point. This will get called as our
396 // intermediate operations complete. Definition below.
398 void operator()(boost::beast::error_code ec, std::size_t bytes_transferred);
403 //[example_core_detect_ssl_7
405 // detect_ssl_op is callable with the signature
406 // void(error_code, bytes_transferred),
407 // allowing `*this` to be used as a ReadHandler
414 detect_ssl_op<AsyncStream, DynamicBuffer, Handler>::
415 operator()(boost::beast::error_code ec, std::size_t bytes_transferred)
417 namespace beast = boost::beast;
419 // Execute the state machine
424 // See if we can detect the handshake
425 result_ = is_ssl_handshake(buffer_.data());
427 // If there's a result, call the handler
428 if(! boost::indeterminate(result_))
430 // We need to invoke the handler, but the guarantee
431 // is that the handler will not be called before the
432 // call to async_detect_ssl returns, so we must post
433 // the operation to the executor. The helper function
434 // `bind_handler` lets us bind arguments in a safe way
435 // that preserves the type customization hooks of the
438 return boost::asio::post(
439 stream_.get_executor(),
440 beast::bind_handler(std::move(*this), ec, 0));
443 // The algorithm should never need more than 4 bytes
444 BOOST_ASSERT(buffer_.size() < 4);
449 // We need more bytes, but no more than four total.
450 return stream_.async_read_some(buffer_.prepare(beast::read_size(buffer_, 1536)), std::move(*this));
457 // Set this so that asio_handler_is_continuation knows that
458 // the next asynchronous operation represents a continuation
459 // of the initial asynchronous operation.
466 // Deliver the error to the handler
469 // We don't need bind_handler here because we were invoked
470 // as a result of an intermediate asynchronous operation.
474 // Commit the bytes that we read
475 buffer_.commit(bytes_transferred);
477 // See if we can detect the handshake
478 result_ = is_ssl_handshake(buffer_.data());
480 // If it is detected, call the handler
481 if(! boost::indeterminate(result_))
483 // We don't need bind_handler here because we were invoked
484 // as a result of an intermediate asynchronous operation.
492 // Invoke the final handler.
493 handler_(ec, result_);