aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib/tls/asio/asio_async_ops.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/tls/asio/asio_async_ops.h')
-rw-r--r--src/lib/tls/asio/asio_async_ops.h368
1 files changed, 368 insertions, 0 deletions
diff --git a/src/lib/tls/asio/asio_async_ops.h b/src/lib/tls/asio/asio_async_ops.h
new file mode 100644
index 000000000..34d9ce0dd
--- /dev/null
+++ b/src/lib/tls/asio/asio_async_ops.h
@@ -0,0 +1,368 @@
+/*
+* Helpers for TLS ASIO Stream
+* (C) 2018-2019 Jack Lloyd
+* 2018-2019 Hannes Rantzsch, Tim Oesterreich, Rene Meusel
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+#ifndef BOTAN_ASIO_ASYNC_OPS_H_
+#define BOTAN_ASIO_ASYNC_OPS_H_
+
+#include <botan/build.h>
+
+#include <boost/version.hpp>
+#if BOOST_VERSION >= 106600
+
+#include <botan/asio_error.h>
+
+// We need to define BOOST_ASIO_DISABLE_SERIAL_PORT before any asio imports. Otherwise asio will include <termios.h>,
+// which interferes with Botan's amalgamation by defining macros like 'B0' and 'FF1'.
+#define BOOST_ASIO_DISABLE_SERIAL_PORT
+#include <boost/asio.hpp>
+#include <boost/asio/yield.hpp>
+
+namespace Botan {
+namespace TLS {
+namespace detail {
+
+/**
+ * Base class for asynchronous stream operations.
+ *
+ * Asynchronous operations, used for example to implement an interface for boost::asio::async_read_some and
+ * boost::asio::async_write_some, are based on boost::asio::coroutines.
+ * Derived operations should implement a call operator and invoke it with the correct parameters upon construction. The
+ * call operator needs to make sure that the user-provided handler is not called directly. Typically, yield / reenter is
+ * used for this in the following fashion:
+ *
+ * ```
+ * void operator()(boost::system::error_code ec, std::size_t bytes_transferred, bool isContinuation = true)
+ * {
+ * reenter(this)
+ * {
+ * // operation specific logic, repeatedly interacting with the stream_core and the next_layer (socket)
+ *
+ * // make sure intermediate initiating function is called
+ * if(!isContinuation)
+ * {
+ * yield next_layer.async_operation(empty_buffer, this);
+ * }
+ *
+ * // call the completion handler
+ * complete_now(error_code, bytes_transferred);
+ * }
+ * }
+ * ```
+ *
+ * Once the operation is completed and ready to call the completion handler it checks if an intermediate initiating
+ * function has been called using the `isContinuation` parameter. If not, it will call an asynchronous operation, such
+ * as `async_read_some`, with and empty buffer, set the object itself as the handler, and `yield`. As a result, the call
+ * operator will be invoked again, this time as a continuation, and will jump to the location where it yielded before
+ * using `reenter`. It is now safe to call the handler function via `complete_now`.
+ *
+ * \tparam Handler Type of the completion handler
+ * \tparam Executor1 Type of the asio executor (usually derived from the lower layer)
+ * \tparam Allocator Type of the allocator to be used
+ */
+template <class Handler, class Executor1, class Allocator>
+class AsyncBase : public boost::asio::coroutine
+ {
+ public:
+ using allocator_type = boost::asio::associated_allocator_t<Handler, Allocator>;
+ using executor_type = boost::asio::associated_executor_t<Handler, Executor1>;
+
+ allocator_type get_allocator() const noexcept
+ {
+ return boost::asio::get_associated_allocator(m_handler);
+ }
+
+ executor_type get_executor() const noexcept
+ {
+ return boost::asio::get_associated_executor(m_handler, m_work_guard_1.get_executor());
+ }
+
+ protected:
+ template <class HandlerT>
+ AsyncBase(HandlerT&& handler, const Executor1& executor)
+ : m_handler(std::forward<HandlerT>(handler))
+ , m_work_guard_1(executor)
+ {
+ }
+
+ /**
+ * Call the completion handler.
+ *
+ * This function should only be called after an intermediate initiating function has been called.
+ *
+ * @param args Arguments forwarded to the completion handler function.
+ */
+ template<class... Args>
+ void complete_now(Args&& ... args)
+ {
+ m_work_guard_1.reset();
+ m_handler(std::forward<Args>(args)...);
+ }
+
+ Handler m_handler;
+ boost::asio::executor_work_guard<Executor1> m_work_guard_1;
+ };
+
+template <class Handler, class Stream, class MutableBufferSequence, class Allocator = std::allocator<void>>
+class AsyncReadOperation : public AsyncBase<Handler, typename Stream::executor_type, Allocator>
+ {
+ public:
+ /**
+ * Construct and invoke an AsyncWriteOperation.
+ *
+ * @param handler Handler function to be called upon completion.
+ * @param stream The stream from which the data will be read
+ * @param buffers The buffers into which the data will be read.
+ * @param ec Optional error code; used to report an error to the handler function.
+ */
+ template <class HandlerT>
+ AsyncReadOperation(HandlerT&& handler,
+ Stream& stream,
+ const MutableBufferSequence& buffers,
+ const boost::system::error_code& ec = {})
+ : AsyncBase<Handler, typename Stream::executor_type, Allocator>(
+ std::forward<HandlerT>(handler),
+ stream.get_executor())
+ , m_stream(stream)
+ , m_buffers(buffers)
+ , m_decodedBytes(0)
+ {
+ this->operator()(ec, std::size_t(0), false);
+ }
+
+ AsyncReadOperation(AsyncReadOperation&&) = default;
+
+ void operator()(boost::system::error_code ec, std::size_t bytes_transferred, bool isContinuation = true)
+ {
+ reenter(this)
+ {
+ if(bytes_transferred > 0 && !ec)
+ {
+ // We have received encrypted data from the network, now hand it to TLS::Channel for decryption.
+ boost::asio::const_buffer read_buffer{m_stream.input_buffer().data(), bytes_transferred};
+ try
+ {
+ m_stream.native_handle()->received_data(
+ static_cast<const uint8_t*>(read_buffer.data()), read_buffer.size()
+ );
+ }
+ catch(const TLS_Exception& e)
+ {
+ ec = e.type();
+ }
+ catch(const Botan::Exception& e)
+ {
+ ec = e.error_type();
+ }
+ catch(...)
+ {
+ ec = Botan::ErrorType::Unknown;
+ }
+ }
+
+ if(!m_stream.has_received_data() && !ec && boost::asio::buffer_size(m_buffers) > 0)
+ {
+ // The channel did not decrypt a complete record yet, we need more data from the socket.
+ m_stream.next_layer().async_read_some(m_stream.input_buffer(), std::move(*this));
+ return;
+ }
+
+ if(m_stream.has_received_data() && !ec)
+ {
+ // The channel has decrypted a TLS record, now copy it to the output buffers.
+ m_decodedBytes = m_stream.copy_received_data(m_buffers);
+ }
+
+ if(!isContinuation)
+ {
+ // Make sure the handler is not called without an intermediate initiating function.
+ // "Reading" into a zero-byte buffer will complete immediately.
+ m_ec = ec;
+ yield m_stream.next_layer().async_read_some(boost::asio::mutable_buffer(), std::move(*this));
+ ec = m_ec;
+ }
+
+ this->complete_now(ec, m_decodedBytes);
+ }
+ }
+
+ private:
+ Stream& m_stream;
+ MutableBufferSequence m_buffers;
+ std::size_t m_decodedBytes;
+ boost::system::error_code m_ec;
+ };
+
+template <typename Handler, class Stream, class Allocator = std::allocator<void>>
+class AsyncWriteOperation : public AsyncBase<Handler, typename Stream::executor_type, Allocator>
+ {
+ public:
+ /**
+ * Construct and invoke an AsyncWriteOperation.
+ *
+ * @param handler Handler function to be called upon completion.
+ * @param stream The stream from which the data will be read
+ * @param plainBytesTransferred Number of bytes to be reported to the user-provided handler function as
+ * bytes_transferred. This needs to be provided since the amount of plaintext data
+ * consumed from the input buffer can differ from the amount of encrypted data written
+ * to the next layer.
+ * @param ec Optional error code; used to report an error to the handler function.
+ */
+ template <class HandlerT>
+ AsyncWriteOperation(HandlerT&& handler,
+ Stream& stream,
+ std::size_t plainBytesTransferred,
+ const boost::system::error_code& ec = {})
+ : AsyncBase<Handler, typename Stream::executor_type, Allocator>(
+ std::forward<HandlerT>(handler),
+ stream.get_executor())
+ , m_stream(stream)
+ , m_plainBytesTransferred(plainBytesTransferred)
+ {
+ this->operator()(ec, std::size_t(0), false);
+ }
+
+ AsyncWriteOperation(AsyncWriteOperation&&) = default;
+
+ void operator()(boost::system::error_code ec, std::size_t bytes_transferred, bool isContinuation = true)
+ {
+ reenter(this)
+ {
+ // mark the number of encrypted bytes sent to the network as "consumed"
+ // Note: bytes_transferred will be zero on first call
+ m_stream.consume_send_buffer(bytes_transferred);
+
+ if(m_stream.has_data_to_send() && !ec)
+ {
+ m_stream.next_layer().async_write_some(m_stream.send_buffer(), std::move(*this));
+ return;
+ }
+
+ if(!isContinuation)
+ {
+ // Make sure the handler is not called without an intermediate initiating function.
+ // "Writing" to a zero-byte buffer will complete immediately.
+ m_ec = ec;
+ yield m_stream.next_layer().async_write_some(boost::asio::const_buffer(), std::move(*this));
+ ec = m_ec;
+ }
+
+ // The size of the sent TLS record can differ from the size of the payload due to TLS encryption. We need to
+ // tell the handler how many bytes of the original data we already processed.
+ this->complete_now(ec, m_plainBytesTransferred);
+ }
+ }
+
+ private:
+ Stream& m_stream;
+ std::size_t m_plainBytesTransferred;
+ boost::system::error_code m_ec;
+ };
+
+template <class Handler, class Stream, class Allocator = std::allocator<void>>
+class AsyncHandshakeOperation : public AsyncBase<Handler, typename Stream::executor_type, Allocator>
+ {
+ public:
+ /**
+ * Construct and invoke an AsyncHandshakeOperation.
+ *
+ * @param handler Handler function to be called upon completion.
+ * @param stream The stream from which the data will be read
+ * @param ec Optional error code; used to report an error to the handler function.
+ */
+ template<class HandlerT>
+ AsyncHandshakeOperation(
+ HandlerT&& handler,
+ Stream& stream,
+ const boost::system::error_code& ec = {})
+ : AsyncBase<Handler, typename Stream::executor_type, Allocator>(
+ std::forward<HandlerT>(handler),
+ stream.get_executor())
+ , m_stream(stream)
+ {
+ this->operator()(ec, std::size_t(0), false);
+ }
+
+ AsyncHandshakeOperation(AsyncHandshakeOperation&&) = default;
+
+ void operator()(boost::system::error_code ec, std::size_t bytesTransferred, bool isContinuation = true)
+ {
+ reenter(this)
+ {
+ if(bytesTransferred > 0 && !ec)
+ {
+ // Provide encrypted TLS data received from the network to TLS::Channel for decryption
+ boost::asio::const_buffer read_buffer {m_stream.input_buffer().data(), bytesTransferred};
+ try
+ {
+ m_stream.native_handle()->received_data(
+ static_cast<const uint8_t*>(read_buffer.data()), read_buffer.size()
+ );
+ }
+ catch(const TLS_Exception& e)
+ {
+ ec = e.type();
+ }
+ catch(const Botan::Exception& e)
+ {
+ ec = e.error_type();
+ }
+ catch(...)
+ {
+ ec = Botan::ErrorType::Unknown;
+ }
+ }
+
+ if(m_stream.has_data_to_send() && !ec)
+ {
+ // Write encrypted TLS data provided by the TLS::Channel on the wire
+
+ // Note: we construct `AsyncWriteOperation` with 0 as its last parameter (`plainBytesTransferred`). This
+ // operation will eventually call `*this` as its own handler, passing the 0 back to this call operator.
+ // This is necessary because the check of `bytesTransferred > 0` assumes that `bytesTransferred` bytes
+ // were just read and are available in input_buffer for further processing.
+ AsyncWriteOperation<
+ AsyncHandshakeOperation<typename std::decay<Handler>::type, Stream, Allocator>,
+ Stream,
+ Allocator>
+ op{std::move(*this), m_stream, 0};
+ return;
+ }
+
+ if(!m_stream.native_handle()->is_active() && !ec)
+ {
+ // Read more encrypted TLS data from the network
+ m_stream.next_layer().async_read_some(m_stream.input_buffer(), std::move(*this));
+ return;
+ }
+
+ if(!isContinuation)
+ {
+ // Make sure the handler is not called without an intermediate initiating function.
+ // "Reading" into a zero-byte buffer will complete immediately.
+ m_ec = ec;
+ yield m_stream.next_layer().async_read_some(boost::asio::mutable_buffer(), std::move(*this));
+ ec = m_ec;
+ }
+
+ this->complete_now(ec);
+ }
+ }
+
+ private:
+ Stream& m_stream;
+ boost::system::error_code m_ec;
+ };
+
+} // namespace detail
+} // namespace TLS
+} // namespace Botan
+
+#include <boost/asio/unyield.hpp>
+
+#endif // BOOST_VERSION
+#endif // BOTAN_ASIO_ASYNC_OPS_H_