ssl_stream.h
1 // Copyright 2019, Beeri 15. All rights reserved.
2 // Author: Roman Gershman (romange@gmail.com)
3 //
4 
5 #pragma once
6 
7 #include <boost/asio/ssl/stream.hpp>
8 #include "util/asio/fiber_socket.h"
9 
10 namespace util {
11 namespace http {
12 
13 namespace detail {
14 
15 class Engine {
16  public:
17  using verify_mode = ::boost::asio::ssl::verify_mode;
18  using want = ::boost::asio::ssl::detail::engine::want;
19 
20  // Construct a new engine for the specified context.
21  explicit Engine(SSL_CTX* context);
22 
23  // Destructor.
24  ~Engine();
25 
26  // Get the underlying implementation in the native type.
27  SSL* native_handle() {
28  return ssl_;
29  }
30 
31  // Set the peer verification mode.
32  boost::system::error_code set_verify_mode(verify_mode v, boost::system::error_code& ec);
33 
34  // Perform an SSL handshake using either SSL_connect (client-side) or
35  // SSL_accept (server-side).
36  want handshake(::boost::asio::ssl::stream_base::handshake_type type,
37  boost::system::error_code& ec);
38 
39  // Perform a graceful shutdown of the SSL session.
40  want shutdown(boost::system::error_code& ec);
41 
42  // Write bytes to the SSL session.
43  want write(const boost::asio::const_buffer& data, boost::system::error_code& ec,
44  std::size_t& bytes_transferred);
45 
46  // Read bytes from the SSL session.
47  want read(const boost::asio::mutable_buffer& data, boost::system::error_code& ec,
48  std::size_t& bytes_transferred);
49 
50 
51  void GetWriteBuf(boost::asio::mutable_buffer* mbuf);
52 
54  void CommitWriteBuf(size_t sz);
55 
56  void GetReadBuf(boost::asio::const_buffer* cbuf);
57  void AdvanceRead(size_t sz);
58 
59  // Map an error::eof code returned by the underlying transport according to
60  // the type and state of the SSL session. Returns a const reference to the
61  // error code object, suitable for passing to a completion handler.
62  const boost::system::error_code& map_error_code(boost::system::error_code& ec) const;
63 
64  private:
65  // Disallow copying and assignment.
66  Engine(const Engine&) = delete;
67  Engine& operator=(const Engine&) = delete;
68 
69  // Perform one operation. Returns >= 0 on success or error, want_read if the
70  // operation needs more input, or want_write if it needs to write some output
71  // before the operation can complete.
72  want perform(int (Engine::*op)(void*, std::size_t), void* data, std::size_t length,
73  boost::system::error_code& ec, std::size_t* bytes_transferred);
74 
75  // Adapt the SSL_connect function to the signature needed for perform().
76  int do_connect(void*, std::size_t);
77 
78  // Adapt the SSL_shutdown function to the signature needed for perform().
79  int do_shutdown(void*, std::size_t);
80 
81  // Adapt the SSL_read function to the signature needed for perform().
82  int do_read(void* data, std::size_t length);
83 
84  // Adapt the SSL_write function to the signature needed for perform().
85  int do_write(void* data, std::size_t length);
86 
87  SSL* ssl_;
88  BIO* ext_bio_;
89 };
90 
91 } // namespace detail
92 
93 class SslStream {
94  using Impl = ::boost::asio::ssl::stream<FiberSyncSocket>;
95 
96  SslStream(const SslStream&) = delete;
97  SslStream& operator=(const SslStream&) = delete;
98 
99  public:
100  using next_layer_type = Impl::next_layer_type;
101  using lowest_layer_type = Impl::lowest_layer_type;
102  using error_code = boost::system::error_code;
103 
104  SslStream(FiberSyncSocket&& arg, ::boost::asio::ssl::context& ctx);
105 
106  // To support socket requirements.
107  next_layer_type& next_layer() {
108  return next_layer_;
109  }
110 
111  lowest_layer_type& lowest_layer() {
112  return next_layer_.lowest_layer();
113  }
114 
115  // (fiber) SyncRead interface:
116  // https://www.boost.org/doc/libs/1_69_0/doc/html/boost_asio/reference/SyncReadStream.html
117  template <typename MBS> size_t read_some(const MBS& bufs, error_code& ec) {
118  namespace a = ::boost::asio;
119 
120  auto cb = [&](detail::Engine& eng, error_code& ec, size_t& bytes_transferred) {
121  a::mutable_buffer buffer =
122  a::detail::buffer_sequence_adapter<a::mutable_buffer, MBS>::first(bufs);
123 
124  return eng.read(buffer, ec, bytes_transferred);
125  };
126 
127  size_t res = IoLoop(cb, ec);
128  last_err_ = ec;
129  return res;
130  }
131 
134  template <typename MBS> size_t read_some(const MBS& bufs);
135 
138  template <typename BS> size_t write_some(const BS& bufs, error_code& ec) {
139  namespace a = ::boost::asio;
140  auto cb = [&](detail::Engine& eng, error_code& ec, size_t& bytes_transferred) {
141  a::const_buffer buffer = a::detail::buffer_sequence_adapter<a::const_buffer, BS>::first(bufs);
142 
143  return eng.write(buffer, ec, bytes_transferred);
144  };
145 
146  size_t res = IoLoop(cb, ec);
147  last_err_ = ec;
148  return res;
149  }
150 
153  template <typename BS> size_t write_some(const BS& bufs);
154 
155  void handshake(Impl::handshake_type type, error_code& ec);
156 
157  const error_code& last_error() const {
158  return last_err_;
159  }
160 
161  auto native_handle() {
162  return engine_.native_handle();
163  }
164 
165  private:
166  using want = detail::Engine::want;
167 
168  template <typename Operation>
169  std::size_t IoLoop(const Operation& op, boost::system::error_code& ec);
170 
171  void IoHandler(want op_code, boost::system::error_code& ec);
172 
173  detail::Engine engine_;
174  FiberSyncSocket next_layer_;
175 
176  error_code last_err_;
177 };
178 
179 template <typename Operation>
180 std::size_t SslStream::IoLoop(const Operation& op, boost::system::error_code& ec) {
181  using engine = ::boost::asio::ssl::detail::engine;
182 
183  std::size_t bytes_transferred = 0;
184  want op_code = engine::want_nothing;
185 
186  do {
187  op_code = op(engine_, ec, bytes_transferred);
188  if (ec)
189  break;
190  IoHandler(op_code, ec);
191  } while (!ec && int(op_code) < 0);
192 
193  if (ec) {
194  // Operation failed. Return result to caller.
195  engine_.map_error_code(ec);
196  return 0;
197  } else {
198  return bytes_transferred;
199  }
200 }
201 
202 } // namespace http
203 } // namespace util
size_t write_some(const BS &bufs, error_code &ec)
Definition: ssl_stream.h:138
void CommitWriteBuf(size_t sz)
sz should be less or equal to the size returned by GetWriteBuf.
Definition: ssl_stream.cc:155