From cea683f915cd04d0b514ce72643fb5fb3ab8019e Mon Sep 17 00:00:00 2001 From: Christopher Kohlhoff Date: Fri, 22 Mar 2013 22:25:18 +1100 Subject: [PATCH] Support handshake with re-use of data already read from the wire. Add new overloads of the SSL stream's handshake() and async_handshake() functions, that accepts a ConstBufferSequence to be used as initial input to the ssl engine for the handshake procedure. Thanks go to Nick Jones , on whose work this commit is partially based. --- asio/include/Makefile.am | 1 + .../asio/detail/handler_type_requirements.hpp | 31 +++++ .../asio/ssl/detail/buffered_handshake_op.hpp | 110 ++++++++++++++++++ asio/include/asio/ssl/stream.hpp | 85 ++++++++++++++ asio/src/doc/quickref.xml | 1 + asio/src/doc/reference.xsl | 1 + .../requirements/BufferedHandshakeHandler.qbk | 54 +++++++++ asio/src/tests/unit/ssl/stream.cpp | 58 +++++++-- 8 files changed, 333 insertions(+), 8 deletions(-) create mode 100644 asio/include/asio/ssl/detail/buffered_handshake_op.hpp create mode 100644 asio/src/doc/requirements/BufferedHandshakeHandler.qbk diff --git a/asio/include/Makefile.am b/asio/include/Makefile.am index e1ccecc9..0f575fb7 100644 --- a/asio/include/Makefile.am +++ b/asio/include/Makefile.am @@ -320,6 +320,7 @@ nobase_include_HEADERS = \ asio/ssl/context_base.hpp \ asio/ssl/context.hpp \ asio/ssl/context_service.hpp \ + asio/ssl/detail/buffered_handshake_op.hpp \ asio/ssl/detail/engine.hpp \ asio/ssl/detail/handshake_op.hpp \ asio/ssl/detail/impl/engine.ipp \ diff --git a/asio/include/asio/detail/handler_type_requirements.hpp b/asio/include/asio/detail/handler_type_requirements.hpp index 8a250cfb..3c7b544b 100644 --- a/asio/include/asio/detail/handler_type_requirements.hpp +++ b/asio/include/asio/detail/handler_type_requirements.hpp @@ -368,6 +368,33 @@ struct handler_type_requirements asio::detail::lvref()), \ char(0))> +#define ASIO_BUFFERED_HANDSHAKE_HANDLER_CHECK( \ + handler_type, handler) \ + \ + typedef ASIO_HANDLER_TYPE(handler_type, \ + void(asio::error_code, std::size_t)) \ + asio_true_handler_type; \ + \ + ASIO_HANDLER_TYPE_REQUIREMENTS_ASSERT( \ + sizeof(asio::detail::two_arg_handler_test( \ + asio::detail::clvref< \ + asio_true_handler_type>(), \ + static_cast(0), \ + static_cast(0))) == 1, \ + "BufferedHandshakeHandler type requirements not met") \ + \ + typedef asio::detail::handler_type_requirements< \ + sizeof( \ + asio::detail::argbyv( \ + asio::detail::clvref< \ + asio_true_handler_type>())) + \ + sizeof( \ + asio::detail::lvref< \ + asio_true_handler_type>()( \ + asio::detail::lvref(), \ + asio::detail::lvref()), \ + char(0))> + #define ASIO_SHUTDOWN_HANDLER_CHECK( \ handler_type, handler) \ \ @@ -435,6 +462,10 @@ struct handler_type_requirements handler_type, handler) \ typedef int +#define ASIO_BUFFERED_HANDSHAKE_HANDLER_CHECK( \ + handler_type, handler) \ + typedef int + #define ASIO_SHUTDOWN_HANDLER_CHECK( \ handler_type, handler) \ typedef int diff --git a/asio/include/asio/ssl/detail/buffered_handshake_op.hpp b/asio/include/asio/ssl/detail/buffered_handshake_op.hpp new file mode 100644 index 00000000..387efc34 --- /dev/null +++ b/asio/include/asio/ssl/detail/buffered_handshake_op.hpp @@ -0,0 +1,110 @@ +// +// ssl/detail/buffered_handshake_op.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2012 Christopher M. Kohlhoff (chris at kohlhoff 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) +// + +#ifndef ASIO_SSL_DETAIL_BUFFERED_HANDSHAKE_OP_HPP +#define ASIO_SSL_DETAIL_BUFFERED_HANDSHAKE_OP_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/config.hpp" + +#if !defined(ASIO_ENABLE_OLD_SSL) +# include "asio/ssl/detail/engine.hpp" +#endif // !defined(ASIO_ENABLE_OLD_SSL) + +#include "asio/detail/push_options.hpp" + +namespace asio { +namespace ssl { +namespace detail { + +#if !defined(ASIO_ENABLE_OLD_SSL) + +template +class buffered_handshake_op +{ +public: + buffered_handshake_op(stream_base::handshake_type type, + const ConstBufferSequence& buffers) + : type_(type), + buffers_(buffers), + total_buffer_size_(asio::buffer_size(buffers_)) + { + } + + engine::want operator()(engine& eng, + asio::error_code& ec, + std::size_t& bytes_transferred) const + { + typename ConstBufferSequence::const_iterator iter = buffers_.begin(); + typename ConstBufferSequence::const_iterator end = buffers_.end(); + std::size_t accumulated_size = 0; + + for (;;) + { + engine::want want = eng.handshake(type_, ec); + if (want != engine::want_input_and_retry + || bytes_transferred == total_buffer_size_) + return want; + + // Find the next buffer piece to be fed to the engine. + while (iter != end) + { + const_buffer buffer(*iter); + + // Skip over any buffers which have already been consumed by the engine. + if (bytes_transferred >= accumulated_size + buffer_size(buffer)) + { + accumulated_size += buffer_size(buffer); + ++iter; + continue; + } + + // The current buffer may have been partially consumed by the engine on + // a previous iteration. If so, adjust the buffer to point to the + // unused portion. + if (bytes_transferred > accumulated_size) + buffer = buffer + (bytes_transferred - accumulated_size); + + // Pass the buffer to the engine, and update the bytes transferred to + // reflect the total number of bytes consumed so far. + bytes_transferred += buffer_size(buffer); + buffer = eng.put_input(buffer); + bytes_transferred -= buffer_size(buffer); + break; + } + } + } + + template + void call_handler(Handler& handler, + const asio::error_code& ec, + const std::size_t& bytes_transferred) const + { + handler(ec, bytes_transferred); + } + +private: + stream_base::handshake_type type_; + ConstBufferSequence buffers_; + std::size_t total_buffer_size_; +}; + +#endif // !defined(ASIO_ENABLE_OLD_SSL) + +} // namespace detail +} // namespace ssl +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#endif // ASIO_SSL_DETAIL_BUFFERED_HANDSHAKE_OP_HPP diff --git a/asio/include/asio/ssl/stream.hpp b/asio/include/asio/ssl/stream.hpp index 18ad517b..de5a8781 100644 --- a/asio/include/asio/ssl/stream.hpp +++ b/asio/include/asio/ssl/stream.hpp @@ -26,6 +26,7 @@ # include "asio/detail/noncopyable.hpp" # include "asio/detail/type_traits.hpp" # include "asio/ssl/context.hpp" +# include "asio/ssl/detail/buffered_handshake_op.hpp" # include "asio/ssl/detail/handshake_op.hpp" # include "asio/ssl/detail/io.hpp" # include "asio/ssl/detail/read_op.hpp" @@ -343,6 +344,47 @@ public: return ec; } + /// Perform SSL handshaking. + /** + * This function is used to perform SSL handshaking on the stream. The + * function call will block until handshaking is complete or an error occurs. + * + * @param type The type of handshaking to be performed, i.e. as a client or as + * a server. + * + * @param buffers The buffered data to be reused for the handshake. + * + * @throws asio::system_error Thrown on failure. + */ + template + void handshake(handshake_type type, const ConstBufferSequence& buffers) + { + asio::error_code ec; + handshake(type, buffers, ec); + asio::detail::throw_error(ec, "handshake"); + } + + /// Perform SSL handshaking. + /** + * This function is used to perform SSL handshaking on the stream. The + * function call will block until handshaking is complete or an error occurs. + * + * @param type The type of handshaking to be performed, i.e. as a client or as + * a server. + * + * @param buffers The buffered data to be reused for the handshake. + * + * @param ec Set to indicate what error occurred, if any. + */ + template + asio::error_code handshake(handshake_type type, + const ConstBufferSequence& buffers, asio::error_code& ec) + { + detail::io(next_layer_, core_, + detail::buffered_handshake_op(type, buffers), ec); + return ec; + } + /// Start an asynchronous SSL handshake. /** * This function is used to asynchronously perform an SSL handshake on the @@ -378,6 +420,49 @@ public: return init.result.get(); } + /// Start an asynchronous SSL handshake. + /** + * This function is used to asynchronously perform an SSL handshake on the + * stream. This function call always returns immediately. + * + * @param type The type of handshaking to be performed, i.e. as a client or as + * a server. + * + * @param buffers The buffered data to be reused for the handshake. Although + * the buffers object may be copied as necessary, ownership of the underlying + * buffers is retained by the caller, which must guarantee that they remain + * valid until the handler is called. + * + * @param handler The handler to be called when the handshake operation + * completes. Copies will be made of the handler as required. The equivalent + * function signature of the handler must be: + * @code void handler( + * const asio::error_code& error, // Result of operation. + * std::size_t bytes_transferred // Amount of buffers used in handshake. + * ); @endcode + */ + template + ASIO_INITFN_RESULT_TYPE(BufferedHandshakeHandler, + void (asio::error_code, std::size_t)) + async_handshake(handshake_type type, const ConstBufferSequence& buffers, + ASIO_MOVE_ARG(BufferedHandshakeHandler) handler) + { + // If you get an error on the following line it means that your handler does + // not meet the documented type requirements for a BufferedHandshakeHandler. + ASIO_BUFFERED_HANDSHAKE_HANDLER_CHECK( + BufferedHandshakeHandler, handler) type_check; + + asio::detail::async_result_init init( + ASIO_MOVE_CAST(BufferedHandshakeHandler)(handler)); + + detail::async_io(next_layer_, core_, + detail::buffered_handshake_op(type, buffers), + init.handler); + + return init.result.get(); + } + /// Shut down SSL on the stream. /** * This function is used to shut down SSL on the stream. The function call diff --git a/asio/src/doc/quickref.xml b/asio/src/doc/quickref.xml index 9d9715ac..6a952254 100644 --- a/asio/src/doc/quickref.xml +++ b/asio/src/doc/quickref.xml @@ -317,6 +317,7 @@ Type Requirements + BufferedHandshakeHandler HandshakeHandler ShutdownHandler diff --git a/asio/src/doc/reference.xsl b/asio/src/doc/reference.xsl index b39a6fff..5d195952 100644 --- a/asio/src/doc/reference.xsl +++ b/asio/src/doc/reference.xsl @@ -42,6 +42,7 @@ [include requirements/AsyncRandomAccessWriteDevice.qbk] [include requirements/AsyncReadStream.qbk] [include requirements/AsyncWriteStream.qbk] +[include requirements/BufferedHandshakeHandler.qbk] [include requirements/CompletionHandler.qbk] [include requirements/ComposedConnectHandler.qbk] [include requirements/ConnectHandler.qbk] diff --git a/asio/src/doc/requirements/BufferedHandshakeHandler.qbk b/asio/src/doc/requirements/BufferedHandshakeHandler.qbk new file mode 100644 index 00000000..66616cf4 --- /dev/null +++ b/asio/src/doc/requirements/BufferedHandshakeHandler.qbk @@ -0,0 +1,54 @@ +[/ + / Copyright (c) 2003-2012 Christopher M. Kohlhoff (chris at kohlhoff dot com) + / + / Distributed under the Boost Software License, Version 1.0. (See accompanying + / file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + /] + +[section:BufferedHandshakeHandler Buffered handshake handler requirements] + +A buffered handshake handler must meet the requirements for a [link +asio.reference.Handler handler]. A value `h` of a buffered handshake handler +class should work correctly in the expression `h(ec, s)`, where `ec` is an +lvalue of type `const error_code` and `s` is an lvalue of type `const size_t`. + +[heading Examples] + +A free function as a buffered handshake handler: + + void handshake_handler( + const asio::error_code& ec, + std::size_t bytes_transferred) + { + ... + } + +A buffered handshake handler function object: + + struct handshake_handler + { + ... + void operator()( + const asio::error_code& ec, + std::size_t bytes_transferred) + { + ... + } + ... + }; + +A non-static class member function adapted to a buffered handshake handler using `bind()`: + + void my_class::handshake_handler( + const asio::error_code& ec, + std::size_t bytes_transferred) + { + ... + } + ... + socket.async_handshake(..., + boost::bind(&my_class::handshake_handler, + this, asio::placeholders::error, + asio::placeholders::bytes_transferred)); + +[endsect] diff --git a/asio/src/tests/unit/ssl/stream.cpp b/asio/src/tests/unit/ssl/stream.cpp index 24c58da8..f89ce8a5 100644 --- a/asio/src/tests/unit/ssl/stream.cpp +++ b/asio/src/tests/unit/ssl/stream.cpp @@ -41,6 +41,10 @@ void handshake_handler(const asio::error_code&) { } +void buffered_handshake_handler(const asio::error_code&, std::size_t) +{ +} + void shutdown_handler(const asio::error_code&) { } @@ -110,6 +114,21 @@ void test() stream1.handshake(ssl::stream_base::client, ec); stream1.handshake(ssl::stream_base::server, ec); +#if !defined(ASIO_ENABLE_OLD_SSL) + stream1.handshake(ssl::stream_base::client, buffer(mutable_char_buffer)); + stream1.handshake(ssl::stream_base::server, buffer(mutable_char_buffer)); + stream1.handshake(ssl::stream_base::client, buffer(const_char_buffer)); + stream1.handshake(ssl::stream_base::server, buffer(const_char_buffer)); + stream1.handshake(ssl::stream_base::client, + buffer(mutable_char_buffer), ec); + stream1.handshake(ssl::stream_base::server, + buffer(mutable_char_buffer), ec); + stream1.handshake(ssl::stream_base::client, + buffer(const_char_buffer), ec); + stream1.handshake(ssl::stream_base::server, + buffer(const_char_buffer), ec); +#endif // !defined(ASIO_ENABLE_OLD_SSL) + stream1.async_handshake(ssl::stream_base::client, handshake_handler); stream1.async_handshake(ssl::stream_base::server, handshake_handler); int i1 = stream1.async_handshake(ssl::stream_base::client, lazy); @@ -117,12 +136,35 @@ void test() int i2 = stream1.async_handshake(ssl::stream_base::server, lazy); (void)i2; +#if !defined(ASIO_ENABLE_OLD_SSL) + stream1.async_handshake(ssl::stream_base::client, + buffer(mutable_char_buffer), buffered_handshake_handler); + stream1.async_handshake(ssl::stream_base::server, + buffer(mutable_char_buffer), buffered_handshake_handler); + stream1.async_handshake(ssl::stream_base::client, + buffer(const_char_buffer), buffered_handshake_handler); + stream1.async_handshake(ssl::stream_base::server, + buffer(const_char_buffer), buffered_handshake_handler); + int i3 = stream1.async_handshake(ssl::stream_base::client, + buffer(mutable_char_buffer), lazy); + (void)i3; + int i4 = stream1.async_handshake(ssl::stream_base::server, + buffer(mutable_char_buffer), lazy); + (void)i4; + int i5 = stream1.async_handshake(ssl::stream_base::client, + buffer(const_char_buffer), lazy); + (void)i5; + int i6 = stream1.async_handshake(ssl::stream_base::server, + buffer(const_char_buffer), lazy); + (void)i6; +#endif // !defined(ASIO_ENABLE_OLD_SSL) + stream1.shutdown(); stream1.shutdown(ec); stream1.async_shutdown(shutdown_handler); - int i3 = stream1.async_shutdown(lazy); - (void)i3; + int i7 = stream1.async_shutdown(lazy); + (void)i7; stream1.write_some(buffer(mutable_char_buffer)); stream1.write_some(buffer(const_char_buffer)); @@ -131,17 +173,17 @@ void test() stream1.async_write_some(buffer(mutable_char_buffer), write_some_handler); stream1.async_write_some(buffer(const_char_buffer), write_some_handler); - int i4 = stream1.async_write_some(buffer(mutable_char_buffer), lazy); - (void)i4; - int i5 = stream1.async_write_some(buffer(const_char_buffer), lazy); - (void)i5; + int i8 = stream1.async_write_some(buffer(mutable_char_buffer), lazy); + (void)i8; + int i9 = stream1.async_write_some(buffer(const_char_buffer), lazy); + (void)i9; stream1.read_some(buffer(mutable_char_buffer)); stream1.read_some(buffer(mutable_char_buffer), ec); stream1.async_read_some(buffer(mutable_char_buffer), read_some_handler); - int i6 = stream1.async_read_some(buffer(mutable_char_buffer), lazy); - (void)i6; + int i10 = stream1.async_read_some(buffer(mutable_char_buffer), lazy); + (void)i10; #if defined(ASIO_ENABLE_OLD_SSL) stream1.peek(buffer(mutable_char_buffer));