Botan 3.0.0-alpha0
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/build.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 {
70 public:
71 using allocator_type = boost::asio::associated_allocator_t<Handler, Allocator>;
72 using executor_type = boost::asio::associated_executor_t<Handler, Executor1>;
73
75 {
76 return boost::asio::get_associated_allocator(m_handler);
77 }
78
79 executor_type get_executor() const noexcept
80 {
81 return boost::asio::get_associated_executor(m_handler, m_work_guard_1.get_executor());
82 }
83
84 protected:
85 template <class HandlerT>
86 AsyncBase(HandlerT&& handler, const Executor1& executor)
87 : m_handler(std::forward<HandlerT>(handler))
88 , m_work_guard_1(executor)
89 {
90 }
91
92 /**
93 * Call the completion handler.
94 *
95 * This function should only be called after an intermediate initiating function has been called.
96 *
97 * @param args Arguments forwarded to the completion handler function.
98 */
99 template<class... Args>
100 void complete_now(Args&& ... args)
101 {
102 m_work_guard_1.reset();
103 m_handler(std::forward<Args>(args)...);
104 }
105
106 Handler m_handler;
107 boost::asio::executor_work_guard<Executor1> m_work_guard_1;
108 };
109
110template <class Handler, class Stream, class MutableBufferSequence, class Allocator = std::allocator<void>>
111class AsyncReadOperation : public AsyncBase<Handler, typename Stream::executor_type, Allocator>
112 {
113 public:
114 /**
115 * Construct and invoke an AsyncReadOperation.
116 *
117 * @param handler Handler function to be called upon completion.
118 * @param stream The stream from which the data will be read
119 * @param buffers The buffers into which the data will be read.
120 * @param ec Optional error code; used to report an error to the handler function.
121 */
122 template <class HandlerT>
123 AsyncReadOperation(HandlerT&& handler,
124 Stream& stream,
125 const MutableBufferSequence& buffers,
126 const boost::system::error_code& ec = {})
128 std::forward<HandlerT>(handler),
129 stream.get_executor())
130 , m_stream(stream)
131 , m_buffers(buffers)
132 , m_decodedBytes(0)
133 {
134 this->operator()(ec, std::size_t(0), false);
135 }
136
138
139 void operator()(boost::system::error_code ec, std::size_t bytes_transferred, bool isContinuation = true)
140 {
141 reenter(this)
142 {
143 if(bytes_transferred > 0 && !ec)
144 {
145 // We have received encrypted data from the network, now hand it to TLS::Channel for decryption.
146 boost::asio::const_buffer read_buffer{m_stream.input_buffer().data(), bytes_transferred};
147 m_stream.process_encrypted_data(read_buffer, ec);
148 }
149
150 if (m_stream.shutdown_received())
151 {
152 // we just received a 'close_notify' from the peer and don't expect any more data
153 ec = boost::asio::error::eof;
154 }
155 else if (ec == boost::asio::error::eof)
156 {
157 // we did not expect this disconnection from the peer
159 }
160
161 if(!m_stream.has_received_data() && !ec && boost::asio::buffer_size(m_buffers) > 0)
162 {
163 // The channel did not decrypt a complete record yet, we need more data from the socket.
164 m_stream.next_layer().async_read_some(m_stream.input_buffer(), std::move(*this));
165 return;
166 }
167
168 if(m_stream.has_received_data() && !ec)
169 {
170 // The channel has decrypted a TLS record, now copy it to the output buffers.
171 m_decodedBytes = m_stream.copy_received_data(m_buffers);
172 }
173
174 if(!isContinuation)
175 {
176 // Make sure the handler is not called without an intermediate initiating function.
177 // "Reading" into a zero-byte buffer will complete immediately.
178 m_ec = ec;
179 yield m_stream.next_layer().async_read_some(boost::asio::mutable_buffer(), std::move(*this));
180 ec = m_ec;
181 }
182
183 this->complete_now(ec, m_decodedBytes);
184 }
185 }
186
187 private:
188 Stream& m_stream;
189 MutableBufferSequence m_buffers;
190 std::size_t m_decodedBytes;
191 boost::system::error_code m_ec;
192 };
193
194template <typename Handler, class Stream, class Allocator = std::allocator<void>>
195class AsyncWriteOperation : public AsyncBase<Handler, typename Stream::executor_type, Allocator>
196 {
197 public:
198 /**
199 * Construct and invoke an AsyncWriteOperation.
200 *
201 * @param handler Handler function to be called upon completion.
202 * @param stream The stream from which the data will be read
203 * @param plainBytesTransferred Number of bytes to be reported to the user-provided handler function as
204 * bytes_transferred. This needs to be provided since the amount of plaintext data
205 * consumed from the input buffer can differ from the amount of encrypted data written
206 * to the next layer.
207 * @param ec Optional error code; used to report an error to the handler function.
208 */
209 template <class HandlerT>
210 AsyncWriteOperation(HandlerT&& handler,
211 Stream& stream,
212 std::size_t plainBytesTransferred,
213 const boost::system::error_code& ec = {})
215 std::forward<HandlerT>(handler),
216 stream.get_executor())
217 , m_stream(stream)
218 , m_plainBytesTransferred(plainBytesTransferred)
219 {
220 this->operator()(ec, std::size_t(0), false);
221 }
222
224
225 void operator()(boost::system::error_code ec, std::size_t bytes_transferred, bool isContinuation = true)
226 {
227 reenter(this)
228 {
229 // mark the number of encrypted bytes sent to the network as "consumed"
230 // Note: bytes_transferred will be zero on first call
231 m_stream.consume_send_buffer(bytes_transferred);
232
233 if(m_stream.has_data_to_send() && !ec)
234 {
235 m_stream.next_layer().async_write_some(m_stream.send_buffer(), std::move(*this));
236 return;
237 }
238
239 if (ec == boost::asio::error::eof && !m_stream.shutdown_received())
240 {
241 // transport layer was closed by peer without receiving 'close_notify'
243 }
244
245 if(!isContinuation)
246 {
247 // Make sure the handler is not called without an intermediate initiating function.
248 // "Writing" to a zero-byte buffer will complete immediately.
249 m_ec = ec;
250 yield m_stream.next_layer().async_write_some(boost::asio::const_buffer(), std::move(*this));
251 ec = m_ec;
252 }
253
254 // The size of the sent TLS record can differ from the size of the payload due to TLS encryption. We need to
255 // tell the handler how many bytes of the original data we already processed.
256 this->complete_now(ec, m_plainBytesTransferred);
257 }
258 }
259
260 private:
261 Stream& m_stream;
262 std::size_t m_plainBytesTransferred;
263 boost::system::error_code m_ec;
264 };
265
266template <class Handler, class Stream, class Allocator = std::allocator<void>>
267class AsyncHandshakeOperation : public AsyncBase<Handler, typename Stream::executor_type, Allocator>
268 {
269 public:
270 /**
271 * Construct and invoke an AsyncHandshakeOperation.
272 *
273 * @param handler Handler function to be called upon completion.
274 * @param stream The stream from which the data will be read
275 * @param ec Optional error code; used to report an error to the handler function.
276 */
277 template<class HandlerT>
279 HandlerT&& handler,
280 Stream& stream,
281 const boost::system::error_code& ec = {})
283 std::forward<HandlerT>(handler),
284 stream.get_executor())
285 , m_stream(stream)
286 {
287 this->operator()(ec, std::size_t(0), false);
288 }
289
291
292 void operator()(boost::system::error_code ec, std::size_t bytesTransferred, bool isContinuation = true)
293 {
294 reenter(this)
295 {
296 if(ec == boost::asio::error::eof)
297 {
299 }
300
301 if(bytesTransferred > 0 && !ec)
302 {
303 // Provide encrypted TLS data received from the network to TLS::Channel for decryption
304 boost::asio::const_buffer read_buffer {m_stream.input_buffer().data(), bytesTransferred};
305 m_stream.process_encrypted_data(read_buffer, ec);
306 }
307
308 if(m_stream.has_data_to_send() && !ec)
309 {
310 // Write encrypted TLS data provided by the TLS::Channel on the wire
311
312 // Note: we construct `AsyncWriteOperation` with 0 as its last parameter (`plainBytesTransferred`). This
313 // operation will eventually call `*this` as its own handler, passing the 0 back to this call operator.
314 // This is necessary because the check of `bytesTransferred > 0` assumes that `bytesTransferred` bytes
315 // were just read and are available in input_buffer for further processing.
317 Stream,
318 Allocator>
319 op{std::move(*this), m_stream, 0};
320 return;
321 }
322
323 if(!m_stream.native_handle()->is_active() && !ec)
324 {
325 // Read more encrypted TLS data from the network
326 m_stream.next_layer().async_read_some(m_stream.input_buffer(), std::move(*this));
327 return;
328 }
329
330 if(!isContinuation)
331 {
332 // Make sure the handler is not called without an intermediate initiating function.
333 // "Reading" into a zero-byte buffer will complete immediately.
334 m_ec = ec;
335 yield m_stream.next_layer().async_read_some(boost::asio::mutable_buffer(), std::move(*this));
336 ec = m_ec;
337 }
338
339 this->complete_now(ec);
340 }
341 }
342
343 private:
344 Stream& m_stream;
345 boost::system::error_code m_ec;
346 };
347
348} // namespace detail
349} // namespace TLS
350} // namespace Botan
351
352#include <boost/asio/unyield.hpp>
353
354#endif // BOOST_VERSION
355#endif // BOTAN_ASIO_ASYNC_OPS_H_
boost::asio compatible SSL/TLS stream
Definition: asio_stream.h:49
native_handle_type native_handle()
Definition: asio_stream.h:128
std::size_t copy_received_data(MutableBufferSequence buffers)
Copy decrypted data into the user-provided buffer.
Definition: asio_stream.h:671
const next_layer_type & next_layer() const
Definition: asio_stream.h:104
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:686
boost::asio::const_buffer send_buffer() const
Definition: asio_stream.h:664
bool has_received_data() const
Check if decrypted data is available in the receive buffer.
Definition: asio_stream.h:667
const boost::asio::mutable_buffer & input_buffer()
Definition: asio_stream.h:663
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:788
executor_type get_executor() noexcept
Definition: asio_stream.h:125
bool shutdown_received() const
Indicates whether a close_notify alert has been received from the peer.
Definition: asio_stream.h:577
bool has_data_to_send() const
Check if encrypted data is available in the send buffer.
Definition: asio_stream.h:683
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:35
Definition: alg_id.cpp:13
Definition: bigint.h:1077