Botan 3.1.1
Crypto and TLS for C&
asio_async_ops.h
Go to the documentation of this file.
1/*
2* Helpers for TLS ASIO Stream
3* (C) 2018-2020 Jack Lloyd
4* 2018-2020 Hannes Rantzsch, Tim Oesterreich, Rene Meusel
5*
6* Botan is released under the Simplified BSD License (see license.txt)
7*/
8
9#ifndef BOTAN_ASIO_ASYNC_OPS_H_
10#define BOTAN_ASIO_ASYNC_OPS_H_
11
12#include <botan/types.h>
13
14#include <boost/version.hpp>
15#if BOOST_VERSION >= 106600
16
17 #include <botan/asio_error.h>
18
19 // We need to define BOOST_ASIO_DISABLE_SERIAL_PORT before any asio imports. Otherwise asio will include <termios.h>,
20 // which interferes with Botan's amalgamation by defining macros like 'B0' and 'FF1'.
21 #define BOOST_ASIO_DISABLE_SERIAL_PORT
22 #include <boost/asio.hpp>
23 #include <boost/asio/yield.hpp>
24
25namespace Botan {
26namespace TLS {
27namespace detail {
28
29/**
30 * Base class for asynchronous stream operations.
31 *
32 * Asynchronous operations, used for example to implement an interface for boost::asio::async_read_some and
33 * boost::asio::async_write_some, are based on boost::asio::coroutines.
34 * Derived operations should implement a call operator and invoke it with the correct parameters upon construction. The
35 * call operator needs to make sure that the user-provided handler is not called directly. Typically, yield / reenter is
36 * used for this in the following fashion:
37 *
38 * ```
39 * void operator()(boost::system::error_code ec, std::size_t bytes_transferred, bool isContinuation = true)
40 * {
41 * reenter(this)
42 * {
43 * // operation specific logic, repeatedly interacting with the stream_core and the next_layer (socket)
44 *
45 * // make sure intermediate initiating function is called
46 * if(!isContinuation)
47 * {
48 * yield next_layer.async_operation(empty_buffer, this);
49 * }
50 *
51 * // call the completion handler
52 * complete_now(error_code, bytes_transferred);
53 * }
54 * }
55 * ```
56 *
57 * Once the operation is completed and ready to call the completion handler it checks if an intermediate initiating
58 * function has been called using the `isContinuation` parameter. If not, it will call an asynchronous operation, such
59 * as `async_read_some`, with and empty buffer, set the object itself as the handler, and `yield`. As a result, the call
60 * operator will be invoked again, this time as a continuation, and will jump to the location where it yielded before
61 * using `reenter`. It is now safe to call the handler function via `complete_now`.
62 *
63 * \tparam Handler Type of the completion handler
64 * \tparam Executor1 Type of the asio executor (usually derived from the lower layer)
65 * \tparam Allocator Type of the allocator to be used
66 */
67template <class Handler, class Executor1, class Allocator>
68class AsyncBase : public boost::asio::coroutine {
69 public:
70 using allocator_type = boost::asio::associated_allocator_t<Handler, Allocator>;
71 using executor_type = boost::asio::associated_executor_t<Handler, Executor1>;
72
73 allocator_type get_allocator() const noexcept { return boost::asio::get_associated_allocator(m_handler); }
74
75 executor_type get_executor() const noexcept {
76 return boost::asio::get_associated_executor(m_handler, m_work_guard_1.get_executor());
77 }
78
79 protected:
80 template <class HandlerT>
81 AsyncBase(HandlerT&& handler, const Executor1& executor) :
82 m_handler(std::forward<HandlerT>(handler)), m_work_guard_1(executor) {}
83
84 /**
85 * Call the completion handler.
86 *
87 * This function should only be called after an intermediate initiating function has been called.
88 *
89 * @param args Arguments forwarded to the completion handler function.
90 */
91 template <class... Args>
92 void complete_now(Args&&... args) {
93 m_work_guard_1.reset();
94 m_handler(std::forward<Args>(args)...);
95 }
96
97 Handler m_handler;
98 boost::asio::executor_work_guard<Executor1> m_work_guard_1;
99};
100
101template <class Handler, class Stream, class MutableBufferSequence, class Allocator = std::allocator<void>>
102class AsyncReadOperation : public AsyncBase<Handler, typename Stream::executor_type, Allocator> {
103 public:
104 /**
105 * Construct and invoke an AsyncReadOperation.
106 *
107 * @param handler Handler function to be called upon completion.
108 * @param stream The stream from which the data will be read
109 * @param buffers The buffers into which the data will be read.
110 * @param ec Optional error code; used to report an error to the handler function.
111 */
112 template <class HandlerT>
113 AsyncReadOperation(HandlerT&& handler,
114 Stream& stream,
115 const MutableBufferSequence& buffers,
116 const boost::system::error_code& ec = {}) :
118 stream.get_executor()),
119 m_stream(stream),
120 m_buffers(buffers),
121 m_decodedBytes(0) {
122 this->operator()(ec, std::size_t(0), false);
123 }
124
126
127 void operator()(boost::system::error_code ec, std::size_t bytes_transferred, bool isContinuation = true) {
128 reenter(this) {
129 if(bytes_transferred > 0 && !ec) {
130 // We have received encrypted data from the network, now hand it to TLS::Channel for decryption.
131 boost::asio::const_buffer read_buffer{m_stream.input_buffer().data(), bytes_transferred};
132 m_stream.process_encrypted_data(read_buffer, ec);
133 }
134
135 if(m_stream.shutdown_received()) {
136 // we just received a 'close_notify' from the peer and don't expect any more data
137 ec = boost::asio::error::eof;
138 } else if(ec == boost::asio::error::eof) {
139 // we did not expect this disconnection from the peer
141 }
142
143 if(!m_stream.has_received_data() && !ec && boost::asio::buffer_size(m_buffers) > 0) {
144 // The channel did not decrypt a complete record yet, we need more data from the socket.
145 m_stream.next_layer().async_read_some(m_stream.input_buffer(), std::move(*this));
146 return;
147 }
148
149 if(m_stream.has_received_data() && !ec) {
150 // The channel has decrypted a TLS record, now copy it to the output buffers.
151 m_decodedBytes = m_stream.copy_received_data(m_buffers);
152 }
153
154 if(!isContinuation) {
155 // Make sure the handler is not called without an intermediate initiating function.
156 // "Reading" into a zero-byte buffer will complete immediately.
157 m_ec = ec;
158 yield m_stream.next_layer().async_read_some(boost::asio::mutable_buffer(), std::move(*this));
159 ec = m_ec;
160 }
161
162 this->complete_now(ec, m_decodedBytes);
163 }
164 }
165
166 private:
167 Stream& m_stream;
168 MutableBufferSequence m_buffers;
169 std::size_t m_decodedBytes;
170 boost::system::error_code m_ec;
171};
172
173template <typename Handler, class Stream, class Allocator = std::allocator<void>>
174class AsyncWriteOperation : public AsyncBase<Handler, typename Stream::executor_type, Allocator> {
175 public:
176 /**
177 * Construct and invoke an AsyncWriteOperation.
178 *
179 * @param handler Handler function to be called upon completion.
180 * @param stream The stream from which the data will be read
181 * @param plainBytesTransferred Number of bytes to be reported to the user-provided handler function as
182 * bytes_transferred. This needs to be provided since the amount of plaintext data
183 * consumed from the input buffer can differ from the amount of encrypted data written
184 * to the next layer.
185 * @param ec Optional error code; used to report an error to the handler function.
186 */
187 template <class HandlerT>
188 AsyncWriteOperation(HandlerT&& handler,
189 Stream& stream,
190 std::size_t plainBytesTransferred,
191 const boost::system::error_code& ec = {}) :
193 stream.get_executor()),
194 m_stream(stream),
195 m_plainBytesTransferred(plainBytesTransferred) {
196 this->operator()(ec, std::size_t(0), false);
197 }
198
200
201 void operator()(boost::system::error_code ec, std::size_t bytes_transferred, bool isContinuation = true) {
202 reenter(this) {
203 // mark the number of encrypted bytes sent to the network as "consumed"
204 // Note: bytes_transferred will be zero on first call
205 m_stream.consume_send_buffer(bytes_transferred);
206
207 if(m_stream.has_data_to_send() && !ec) {
208 m_stream.next_layer().async_write_some(m_stream.send_buffer(), std::move(*this));
209 return;
210 }
211
212 if(ec == boost::asio::error::eof && !m_stream.shutdown_received()) {
213 // transport layer was closed by peer without receiving 'close_notify'
215 }
216
217 if(!isContinuation) {
218 // Make sure the handler is not called without an intermediate initiating function.
219 // "Writing" to a zero-byte buffer will complete immediately.
220 m_ec = ec;
221 yield m_stream.next_layer().async_write_some(boost::asio::const_buffer(), std::move(*this));
222 ec = m_ec;
223 }
224
225 // The size of the sent TLS record can differ from the size of the payload due to TLS encryption. We need to
226 // tell the handler how many bytes of the original data we already processed.
227 this->complete_now(ec, m_plainBytesTransferred);
228 }
229 }
230
231 private:
232 Stream& m_stream;
233 std::size_t m_plainBytesTransferred;
234 boost::system::error_code m_ec;
235};
236
237template <class Handler, class Stream, class Allocator = std::allocator<void>>
238class AsyncHandshakeOperation : public AsyncBase<Handler, typename Stream::executor_type, Allocator> {
239 public:
240 /**
241 * Construct and invoke an AsyncHandshakeOperation.
242 *
243 * @param handler Handler function to be called upon completion.
244 * @param stream The stream from which the data will be read
245 * @param ec Optional error code; used to report an error to the handler function.
246 */
247 template <class HandlerT>
248 AsyncHandshakeOperation(HandlerT&& handler, Stream& stream, const boost::system::error_code& ec = {}) :
250 stream.get_executor()),
251 m_stream(stream) {
252 this->operator()(ec, std::size_t(0), false);
253 }
254
256
257 void operator()(boost::system::error_code ec, std::size_t bytesTransferred, bool isContinuation = true) {
258 reenter(this) {
259 if(ec == boost::asio::error::eof) {
261 }
262
263 if(bytesTransferred > 0 && !ec) {
264 // Provide encrypted TLS data received from the network to TLS::Channel for decryption
265 boost::asio::const_buffer read_buffer{m_stream.input_buffer().data(), bytesTransferred};
266 m_stream.process_encrypted_data(read_buffer, ec);
267 }
268
269 if(m_stream.has_data_to_send() && !ec) {
270 // Write encrypted TLS data provided by the TLS::Channel on the wire
271
272 // Note: we construct `AsyncWriteOperation` with 0 as its last parameter (`plainBytesTransferred`). This
273 // operation will eventually call `*this` as its own handler, passing the 0 back to this call operator.
274 // This is necessary because the check of `bytesTransferred > 0` assumes that `bytesTransferred` bytes
275 // were just read and are available in input_buffer for further processing.
277 Stream,
278 Allocator>
279 op{std::move(*this), m_stream, 0};
280 return;
281 }
282
283 if(!m_stream.native_handle()->is_active() && !ec) {
284 // Read more encrypted TLS data from the network
285 m_stream.next_layer().async_read_some(m_stream.input_buffer(), std::move(*this));
286 return;
287 }
288
289 if(!isContinuation) {
290 // Make sure the handler is not called without an intermediate initiating function.
291 // "Reading" into a zero-byte buffer will complete immediately.
292 m_ec = ec;
293 yield m_stream.next_layer().async_read_some(boost::asio::mutable_buffer(), std::move(*this));
294 ec = m_ec;
295 }
296
297 this->complete_now(ec);
298 }
299 }
300
301 private:
302 Stream& m_stream;
303 boost::system::error_code m_ec;
304};
305
306} // namespace detail
307} // namespace TLS
308} // namespace Botan
309
310 #include <boost/asio/unyield.hpp>
311
312#endif // BOOST_VERSION
313#endif // BOTAN_ASIO_ASYNC_OPS_H_
boost::asio compatible SSL/TLS stream
Definition: asio_stream.h:48
native_handle_type native_handle()
Definition: asio_stream.h:130
std::size_t copy_received_data(MutableBufferSequence buffers)
Copy decrypted data into the user-provided buffer.
Definition: asio_stream.h:620
const next_layer_type & next_layer() const
Definition: asio_stream.h:101
void consume_send_buffer(std::size_t bytesConsumed)
Mark bytes in the send buffer as consumed, removing them from the buffer.
Definition: asio_stream.h:634
boost::asio::const_buffer send_buffer() const
Definition: asio_stream.h:613
bool has_received_data() const
Check if decrypted data is available in the receive buffer.
Definition: asio_stream.h:616
const boost::asio::mutable_buffer & input_buffer()
Definition: asio_stream.h:611
void process_encrypted_data(const boost::asio::const_buffer &read_buffer, boost::system::error_code &ec)
Pass encrypted data to the native handle for processing.
Definition: asio_stream.h:729
executor_type get_executor() noexcept
Definition: asio_stream.h:126
bool shutdown_received() const
Indicates whether a close_notify alert has been received from the peer.
Definition: asio_stream.h:538
bool has_data_to_send() const
Check if encrypted data is available in the send buffer.
Definition: asio_stream.h:631
boost::asio::associated_executor_t< Handler, Executor1 > executor_type
boost::asio::associated_allocator_t< Handler, Allocator > allocator_type
executor_type get_executor() const noexcept
void complete_now(Args &&... args)
boost::asio::executor_work_guard< Executor1 > m_work_guard_1
AsyncBase(HandlerT &&handler, const Executor1 &executor)
allocator_type get_allocator() const noexcept
AsyncHandshakeOperation(AsyncHandshakeOperation &&)=default
AsyncHandshakeOperation(HandlerT &&handler, Stream &stream, const boost::system::error_code &ec={})
void operator()(boost::system::error_code ec, std::size_t bytesTransferred, bool isContinuation=true)
void operator()(boost::system::error_code ec, std::size_t bytes_transferred, bool isContinuation=true)
AsyncReadOperation(AsyncReadOperation &&)=default
AsyncReadOperation(HandlerT &&handler, Stream &stream, const MutableBufferSequence &buffers, const boost::system::error_code &ec={})
void operator()(boost::system::error_code ec, std::size_t bytes_transferred, bool isContinuation=true)
AsyncWriteOperation(HandlerT &&handler, Stream &stream, std::size_t plainBytesTransferred, const boost::system::error_code &ec={})
AsyncWriteOperation(AsyncWriteOperation &&)=default
@ StreamTruncated
Definition: asio_error.h:33
Definition: alg_id.cpp:13
Definition: bigint.h:1030