Botan  2.15.0
Crypto and TLS for C++11
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 
25 namespace Botan {
26 namespace TLS {
27 namespace 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  */
67 template <class Handler, class Executor1, class Allocator>
68 class 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 
74  allocator_type get_allocator() const noexcept
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 
110 template <class Handler, class Stream, class MutableBufferSequence, class Allocator = std::allocator<void>>
111 class 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 
194 template <typename Handler, class Stream, class Allocator = std::allocator<void>>
195 class 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 
266 template <class Handler, class Stream, class Allocator = std::allocator<void>>
267 class 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_
void operator()(boost::system::error_code ec, std::size_t bytesTransferred, bool isContinuation=true)
AsyncBase(HandlerT &&handler, const Executor1 &executor)
AsyncHandshakeOperation(HandlerT &&handler, Stream &stream, const boost::system::error_code &ec={})
bool shutdown_received() const
Indicates whether a close_notify alert has been received from the peer.
Definition: asio_stream.h:561
boost::asio::associated_executor_t< Handler, Stream::executor_type > executor_type
void complete_now(Args &&... args)
void operator()(boost::system::error_code ec, std::size_t bytes_transferred, bool isContinuation=true)
Definition: bigint.h:1142
AsyncWriteOperation(HandlerT &&handler, Stream &stream, std::size_t plainBytesTransferred, const boost::system::error_code &ec={})
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:772
bool has_data_to_send() const
Check if encrypted data is available in the send buffer.
Definition: asio_stream.h:664
AsyncReadOperation(HandlerT &&handler, Stream &stream, const MutableBufferSequence &buffers, const boost::system::error_code &ec={})
boost::asio::const_buffer send_buffer() const
Definition: asio_stream.h:645
const boost::asio::mutable_buffer & input_buffer()
Definition: asio_stream.h:644
std::size_t copy_received_data(MutableBufferSequence buffers)
Copy decrypted data into the user-provided buffer.
Definition: asio_stream.h:652
native_handle_type native_handle()
Definition: asio_stream.h:130
executor_type get_executor() noexcept
Definition: asio_stream.h:127
boost::asio compatible SSL/TLS stream
Definition: asio_stream.h:48
bool has_received_data() const
Check if decrypted data is available in the receive buffer.
Definition: asio_stream.h:648
Definition: alg_id.cpp:13
boost::asio::executor_work_guard< Executor1 > m_work_guard_1
void operator()(boost::system::error_code ec, std::size_t bytes_transferred, bool isContinuation=true)
allocator_type get_allocator() const noexcept
boost::asio::associated_allocator_t< Handler, Allocator > allocator_type
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:667
executor_type get_executor() const noexcept
const next_layer_type & next_layer() const
Definition: asio_stream.h:106