src/openssl/src/openssl_stream.cpp

91.1% Lines (204/224) 100.0% List of functions (33/33) 75.3% Branches (143/190)
openssl_stream.cpp
f(x) Functions (33)
Function Calls Lines Branches Blocks
boost::corosio::(anonymous namespace)::tls_method_compat() :66 2399x 100.0% 100.0% boost::corosio::(anonymous namespace)::apply_hostname_verification(ssl_st*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) :76 2425x 100.0% 100.0% 100.0% boost::corosio::(anonymous namespace)::normalize_openssl_shutdown_read_error(std::error_code) :92 9x 88.9% 71.4% 95.8% boost::corosio::(anonymous namespace)::openssl_category_impl::name() const :109 1x 100.0% 100.0% boost::corosio::(anonymous namespace)::openssl_category_impl::message(int) const :115 2x 100.0% 100.0% 72.7% boost::corosio::(anonymous namespace)::make_openssl_error(unsigned long) :128 11x 75.0% 50.0% 71.4% boost::corosio::openssl_category() :139 14x 100.0% 75.0% 100.0% boost::corosio::detail::password_callback(char*, int, int, void*) :154 1x 84.6% 55.6% 81.2% boost::corosio::detail::sni_callback(ssl_st*, int*, void*) :176 3x 100.0% 72.7% 94.4% boost::corosio::detail::openssl_native_context::openssl_native_context(boost::corosio::detail::tls_context_data const&) :201 2399x 94.7% 87.8% 92.7% boost::corosio::detail::openssl_native_context::~openssl_native_context() :333 4798x 100.0% 100.0% boost::corosio::detail::get_openssl_context(boost::corosio::detail::tls_context_data const&) :341 2405x 100.0% 100.0% boost::corosio::detail::get_openssl_context(boost::corosio::detail::tls_context_data const&)::{lambda()#1}::operator()() const :344 2399x 100.0% 50.0% 71.4% boost::corosio::openssl_stream::impl::impl(boost::capy::any_stream&, boost::corosio::tls_context) :363 2405x 100.0% 100.0% 64.3% boost::corosio::openssl_stream::impl::~impl() :369 2405x 100.0% 50.0% 100.0% boost::corosio::openssl_stream::impl::reset() :377 20x 77.8% 62.5% 83.3% boost::corosio::openssl_stream::impl::flush_output() :397 76041x 100.0% 100.0% 43.8% boost::corosio::openssl_stream::impl::read_input() :428 38661x 100.0% 100.0% 43.8% boost::corosio::openssl_stream::impl::do_read_some(boost::capy::detail::buffer_array<16ull, false>) :449 36636x 100.0% 100.0% 42.1% boost::corosio::openssl_stream::impl::do_write_some(boost::capy::detail::buffer_array<16ull, true>) :532 36612x 100.0% 100.0% 42.1% boost::corosio::openssl_stream::impl::do_handshake(int) :592 1591x 100.0% 100.0% 43.8% boost::corosio::openssl_stream::impl::do_shutdown() :644 37x 100.0% 100.0% 43.8% boost::corosio::openssl_stream::impl::init_ssl() :711 2405x 57.9% 53.3% 65.0% boost::corosio::openssl_stream::make_impl(boost::capy::any_stream&, boost::corosio::tls_context const&) :746 2405x 71.4% 44.4% 52.4% boost::corosio::openssl_stream::~openssl_stream() :760 2407x 0.0% 100.0% boost::corosio::openssl_stream::openssl_stream(boost::corosio::openssl_stream&&) :765 2x 100.0% 50.0% 100.0% boost::corosio::openssl_stream::operator=(boost::corosio::openssl_stream&&) :775 1x 100.0% 50.0% 100.0% boost::corosio::openssl_stream::do_read_some(boost::capy::detail::buffer_array<16ull, false>) :790 36636x 100.0% 100.0% 42.1% boost::corosio::openssl_stream::do_write_some(boost::capy::detail::buffer_array<16ull, true>) :797 36612x 100.0% 100.0% 42.1% boost::corosio::openssl_stream::handshake(boost::corosio::tls_stream::handshake_type) :804 1591x 100.0% 100.0% 43.8% boost::corosio::openssl_stream::shutdown() :810 37x 100.0% 100.0% 43.8% boost::corosio::openssl_stream::reset() :816 16x 100.0% 100.0% boost::corosio::openssl_stream::name() const :822 1x 100.0% 100.0%
Line Branch TLA Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco ([email protected])
3 // Copyright (c) 2026 Michael Vandeberg
4 //
5 // Distributed under the Boost Software License, Version 1.0. (See accompanying
6 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7 //
8 // Official repository: https://github.com/cppalliance/corosio
9 //
10
11 #include <boost/corosio/openssl_stream.hpp>
12 #include <boost/corosio/detail/config.hpp>
13 #include <boost/capy/detail/buffer_array.hpp>
14 #include <boost/capy/ex/async_mutex.hpp>
15 #include <boost/capy/error.hpp>
16 #include <boost/capy/write.hpp>
17
18 // Internal context implementation
19 #include "src/tls/detail/context_impl.hpp"
20
21 #include <openssl/ssl.h>
22 #include <openssl/err.h>
23 #include <openssl/bio.h>
24 #include <openssl/x509.h>
25
26 #include <algorithm>
27 #include <array>
28 #include <cstring>
29 #include <vector>
30
31 /*
32 openssl_stream Architecture
33 ===========================
34
35 TLS layer wrapping an underlying stream (via any_stream). Supports one
36 concurrent read_some and one concurrent write_some (like Asio's ssl::stream).
37
38 Data Flow (using BIO pairs)
39 ---------------------------
40 App -> SSL_write -> int_bio_ -> BIO_read(ext_bio_) -> out_buf_ -> s_->write_some -> Network
41 App <- SSL_read <- int_bio_ <- BIO_write(ext_bio_) <- in_buf_ <- s_->read_some <- Network
42
43 WANT_READ / WANT_WRITE Pattern
44 ------------------------------
45 OpenSSL's SSL_read/SSL_write return SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE
46 when they need I/O. Our coroutine handles this by:
47
48 1. Call SSL_read or SSL_write
49 2. Check for pending output in ext_bio_ via BIO_ctrl_pending
50 3. If output pending: write to network via s_->write_some
51 4. If SSL_ERROR_WANT_READ: read from network into ext_bio_ via s_->read_some + BIO_write
52 5. Loop back to step 1
53
54 Renegotiation causes cross-direction I/O: SSL_read may need to write
55 handshake data, SSL_write may need to read. Each operation handles
56 whatever I/O direction OpenSSL requests.
57 */
58
59 namespace boost::corosio {
60
61 namespace {
62
63 constexpr std::size_t default_buffer_size = 16384;
64
65 inline SSL_METHOD const*
66 2399x tls_method_compat() noexcept
67 {
68 #if OPENSSL_VERSION_NUMBER >= 0x10100000L
69 2399x return TLS_method();
70 #else
71 return SSLv23_method();
72 #endif
73 }
74
75 inline void
76 2425x apply_hostname_verification(SSL* ssl, std::string const& hostname)
77 {
78
2/2
✓ Branch 3 → 4 taken 2421 times.
✓ Branch 3 → 5 taken 4 times.
2425x if (hostname.empty())
79 2421x return;
80
81 4x SSL_set_tlsext_host_name(ssl, hostname.c_str());
82
83 #if OPENSSL_VERSION_NUMBER >= 0x10100000L
84 4x SSL_set1_host(ssl, hostname.c_str());
85 #else
86 if (auto* param = SSL_get0_param(ssl))
87 X509_VERIFY_PARAM_set1_host(param, hostname.c_str(), 0);
88 #endif
89 }
90
91 inline std::error_code
92 9x normalize_openssl_shutdown_read_error(std::error_code ec) noexcept
93 {
94
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 9 times.
9x if (!ec)
95 return ec;
96
97
2/2
✓ Branch 10 → 11 taken 1 time.
✓ Branch 10 → 20 taken 2 times.
12x if (ec == make_error_code(capy::error::eof) ||
98
1/2
✓ Branch 13 → 14 taken 1 time.
✗ Branch 13 → 20 not taken.
4x ec == make_error_code(capy::error::canceled) ||
99
1/2
✓ Branch 16 → 17 taken 1 time.
✗ Branch 16 → 20 not taken.
2x ec == std::errc::connection_reset ||
100
5/6
✓ Branch 7 → 8 taken 3 times.
✓ Branch 7 → 20 taken 6 times.
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 21 taken 1 time.
✓ Branch 22 → 23 taken 8 times.
✓ Branch 22 → 24 taken 1 time.
13x ec == std::errc::connection_aborted || ec == std::errc::broken_pipe)
101 8x return make_error_code(capy::error::stream_truncated);
102
103 1x return ec;
104 }
105
106 class openssl_category_impl final : public std::error_category
107 {
108 char const*
109 1x name() const noexcept override
110 {
111 1x return "corosio.openssl";
112 }
113
114 std::string
115 2x message(int value) const override
116 {
117 char buf[256];
118
1/1
✓ Branch 2 → 3 taken 2 times.
2x ::ERR_error_string_n(
119 static_cast<unsigned long>(value), buf, sizeof(buf));
120
1/1
✓ Branch 5 → 6 taken 2 times.
4x return buf;
121 }
122 };
123
124 // Convert a packed OpenSSL error (from ERR_get_error) into an error_code.
125 // Codes from the ERR_LIB_SYS library carry a genuine errno reason and are
126 // reported with the system category; everything else uses openssl_category.
127 inline std::error_code
128 11x make_openssl_error(unsigned long err) noexcept
129 {
130
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 6 taken 11 times.
11x if (ERR_GET_LIB(err) == ERR_LIB_SYS)
131 return std::error_code(
132 static_cast<int>(ERR_GET_REASON(err)), std::system_category());
133 11x return std::error_code(static_cast<int>(err), openssl_category());
134 }
135
136 } // namespace
137
138 std::error_category const&
139 14x openssl_category() noexcept
140 {
141
3/4
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 7 taken 13 times.
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 7 not taken.
14x static openssl_category_impl instance;
142 14x return instance;
143 }
144
145 //
146 // Native context caching
147 //
148
149 namespace detail {
150
151 static int sni_ctx_data_index = -1;
152
153 static int
154 1x password_callback(char* buf, int size, int rwflag, void* userdata)
155 {
156 1x auto* cd = static_cast<tls_context_data const*>(userdata);
157
3/6
✓ Branch 2 → 3 taken 1 time.
✗ Branch 2 → 5 not taken.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 1 time.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 1 time.
1x if (!cd || !cd->password_callback)
158 return 0;
159
160 1x tls_password_purpose purpose = (rwflag == 0)
161 1x ? tls_password_purpose::for_reading
162 : tls_password_purpose::for_writing;
163
164 std::string password =
165
1/1
✓ Branch 9 → 10 taken 1 time.
1x cd->password_callback(static_cast<std::size_t>(size), purpose);
166
167 1x int len = static_cast<int>(password.size());
168
1/2
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 1 time.
1x if (len > size)
169 len = size;
170
171 1x std::memcpy(buf, password.data(), static_cast<std::size_t>(len));
172 1x return len;
173 1x }
174
175 static int
176 3x sni_callback(SSL* ssl, int* /* alert */, void* /* arg */)
177 {
178 3x char const* servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
179
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 2 times.
3x if (!servername)
180 1x return SSL_TLSEXT_ERR_NOACK;
181
182 2x SSL_CTX* ctx = SSL_get_SSL_CTX(ssl);
183 auto* cd = static_cast<tls_context_data const*>(
184 2x SSL_CTX_get_ex_data(ctx, sni_ctx_data_index));
185
186
3/6
✓ Branch 7 → 8 taken 2 times.
✗ Branch 7 → 11 not taken.
✓ Branch 9 → 10 taken 2 times.
✗ Branch 9 → 11 not taken.
✓ Branch 12 → 13 taken 2 times.
✗ Branch 12 → 17 not taken.
2x if (cd && cd->servername_callback)
187 {
188
3/3
✓ Branch 14 → 15 taken 2 times.
✓ Branch 15 → 16 taken 1 time.
✓ Branch 15 → 17 taken 1 time.
2x if (!cd->servername_callback(servername))
189 1x return SSL_TLSEXT_ERR_ALERT_FATAL;
190 }
191
192 1x return SSL_TLSEXT_ERR_OK;
193 }
194
195 class openssl_native_context : public native_context_base
196 {
197 public:
198 SSL_CTX* ctx_;
199 tls_context_data const* cd_;
200
201 2399x explicit openssl_native_context(tls_context_data const& cd)
202 4798x : ctx_(nullptr)
203 2399x , cd_(&cd)
204 {
205
1/1
✓ Branch 4 → 5 taken 2399 times.
2399x ctx_ = SSL_CTX_new(tls_method_compat());
206
1/2
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 2399 times.
2399x if (!ctx_)
207 return;
208
209
2/2
✓ Branch 7 → 8 taken 4 times.
✓ Branch 7 → 10 taken 2395 times.
2399x if (sni_ctx_data_index < 0)
210 4x sni_ctx_data_index =
211
1/1
✓ Branch 8 → 9 taken 4 times.
4x SSL_CTX_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr);
212
213
1/1
✓ Branch 10 → 11 taken 2399 times.
2399x SSL_CTX_set_ex_data(
214 ctx_, sni_ctx_data_index, const_cast<tls_context_data*>(&cd));
215
216
2/2
✓ Branch 12 → 13 taken 3 times.
✓ Branch 12 → 14 taken 2396 times.
2399x if (cd.servername_callback)
217
1/1
✓ Branch 13 → 14 taken 3 times.
3x SSL_CTX_set_tlsext_servername_callback(ctx_, sni_callback);
218
219
1/1
✓ Branch 14 → 15 taken 2399 times.
2399x SSL_CTX_set_mode(ctx_, SSL_MODE_ENABLE_PARTIAL_WRITE);
220
1/1
✓ Branch 15 → 16 taken 2399 times.
2399x SSL_CTX_set_mode(ctx_, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
221 #if defined(SSL_MODE_RELEASE_BUFFERS)
222
1/1
✓ Branch 16 → 17 taken 2399 times.
2399x SSL_CTX_set_mode(ctx_, SSL_MODE_RELEASE_BUFFERS);
223 #endif
224
225 2399x int verify_mode_flag = SSL_VERIFY_NONE;
226
2/2
✓ Branch 17 → 18 taken 1195 times.
✓ Branch 17 → 19 taken 1204 times.
2399x if (cd.verification_mode == tls_verify_mode::peer)
227 1195x verify_mode_flag = SSL_VERIFY_PEER;
228
2/2
✓ Branch 19 → 20 taken 3 times.
✓ Branch 19 → 21 taken 1201 times.
1204x else if (cd.verification_mode == tls_verify_mode::require_peer)
229 3x verify_mode_flag =
230 SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
231
1/1
✓ Branch 21 → 22 taken 2399 times.
2399x SSL_CTX_set_verify(ctx_, verify_mode_flag, nullptr);
232
233
2/2
✓ Branch 23 → 24 taken 1200 times.
✓ Branch 23 → 37 taken 1199 times.
2399x if (!cd.entity_certificate.empty())
234 {
235
1/1
✓ Branch 26 → 27 taken 1200 times.
1200x BIO* bio = BIO_new_mem_buf(
236 1200x cd.entity_certificate.data(),
237 1200x static_cast<int>(cd.entity_certificate.size()));
238
1/2
✓ Branch 27 → 28 taken 1200 times.
✗ Branch 27 → 37 not taken.
1200x if (bio)
239 {
240 1200x X509* cert = nullptr;
241
1/2
✓ Branch 28 → 29 taken 1200 times.
✗ Branch 28 → 31 not taken.
1200x if (cd.entity_cert_format == tls_file_format::pem)
242
1/1
✓ Branch 29 → 30 taken 1200 times.
1200x cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
243 else
244 cert = d2i_X509_bio(bio, nullptr);
245
2/2
✓ Branch 33 → 34 taken 1199 times.
✓ Branch 33 → 36 taken 1 time.
1200x if (cert)
246 {
247
1/1
✓ Branch 34 → 35 taken 1199 times.
1199x SSL_CTX_use_certificate(ctx_, cert);
248
1/1
✓ Branch 35 → 36 taken 1199 times.
1199x X509_free(cert);
249 }
250
1/1
✓ Branch 36 → 37 taken 1200 times.
1200x BIO_free(bio);
251 }
252 }
253
254
2/2
✓ Branch 38 → 39 taken 1 time.
✓ Branch 38 → 53 taken 2398 times.
2399x if (!cd.certificate_chain.empty())
255 {
256
1/1
✓ Branch 41 → 42 taken 1 time.
1x BIO* bio = BIO_new_mem_buf(
257 1x cd.certificate_chain.data(),
258 1x static_cast<int>(cd.certificate_chain.size()));
259
1/2
✓ Branch 42 → 43 taken 1 time.
✗ Branch 42 → 53 not taken.
1x if (bio)
260 {
261 X509* entity =
262
1/1
✓ Branch 43 → 44 taken 1 time.
1x PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
263
1/2
✓ Branch 44 → 45 taken 1 time.
✗ Branch 44 → 47 not taken.
1x if (entity)
264 {
265
1/1
✓ Branch 45 → 46 taken 1 time.
1x SSL_CTX_use_certificate(ctx_, entity);
266
1/1
✓ Branch 46 → 47 taken 1 time.
1x X509_free(entity);
267 }
268
269 X509* cert;
270
1/1
✓ Branch 49 → 50 taken 2 times.
2x while ((cert = PEM_read_bio_X509(
271
2/2
✓ Branch 50 → 48 taken 1 time.
✓ Branch 50 → 51 taken 1 time.
2x bio, nullptr, nullptr, nullptr)) != nullptr)
272 {
273
1/1
✓ Branch 48 → 49 taken 1 time.
1x SSL_CTX_add_extra_chain_cert(ctx_, cert);
274 }
275
1/1
✓ Branch 51 → 52 taken 1 time.
1x ERR_clear_error();
276
1/1
✓ Branch 52 → 53 taken 1 time.
1x BIO_free(bio);
277 }
278 }
279
280
2/2
✓ Branch 54 → 55 taken 1201 times.
✓ Branch 54 → 72 taken 1198 times.
2399x if (!cd.private_key.empty())
281 {
282
1/1
✓ Branch 57 → 58 taken 1201 times.
1201x BIO* bio = BIO_new_mem_buf(
283 1201x cd.private_key.data(), static_cast<int>(cd.private_key.size()));
284
1/2
✓ Branch 58 → 59 taken 1201 times.
✗ Branch 58 → 72 not taken.
1201x if (bio)
285 {
286 1201x EVP_PKEY* pkey = nullptr;
287
1/2
✓ Branch 59 → 60 taken 1201 times.
✗ Branch 59 → 66 not taken.
1201x if (cd.private_key_format == tls_file_format::pem)
288 {
289
2/2
✓ Branch 61 → 62 taken 1 time.
✓ Branch 61 → 64 taken 1200 times.
1201x if (cd.password_callback)
290
1/1
✓ Branch 62 → 63 taken 1 time.
1x pkey = PEM_read_bio_PrivateKey(
291 bio, nullptr, password_callback,
292 const_cast<tls_context_data*>(&cd));
293 else
294
1/1
✓ Branch 64 → 65 taken 1200 times.
1200x pkey = PEM_read_bio_PrivateKey(
295 bio, nullptr, nullptr, nullptr);
296 }
297 else
298 pkey = d2i_PrivateKey_bio(bio, nullptr);
299
2/2
✓ Branch 68 → 69 taken 1200 times.
✓ Branch 68 → 71 taken 1 time.
1201x if (pkey)
300 {
301
1/1
✓ Branch 69 → 70 taken 1200 times.
1200x SSL_CTX_use_PrivateKey(ctx_, pkey);
302
1/1
✓ Branch 70 → 71 taken 1200 times.
1200x EVP_PKEY_free(pkey);
303 }
304
1/1
✓ Branch 71 → 72 taken 1201 times.
1201x BIO_free(bio);
305 }
306 }
307
308
1/1
✓ Branch 72 → 73 taken 2399 times.
2399x X509_STORE* store = SSL_CTX_get_cert_store(ctx_);
309
2/2
✓ Branch 87 → 75 taken 1209 times.
✓ Branch 87 → 88 taken 2399 times.
3608x for (auto const& ca : cd.ca_certificates)
310 {
311
1/1
✓ Branch 78 → 79 taken 1209 times.
1209x BIO* bio = BIO_new_mem_buf(ca.data(), static_cast<int>(ca.size()));
312
1/2
✓ Branch 79 → 80 taken 1209 times.
✗ Branch 79 → 85 not taken.
1209x if (bio)
313 {
314
1/1
✓ Branch 80 → 81 taken 1209 times.
1209x X509* cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
315
2/2
✓ Branch 81 → 82 taken 1208 times.
✓ Branch 81 → 84 taken 1 time.
1209x if (cert)
316 {
317
1/1
✓ Branch 82 → 83 taken 1208 times.
1208x X509_STORE_add_cert(store, cert);
318
1/1
✓ Branch 83 → 84 taken 1208 times.
1208x X509_free(cert);
319 }
320
1/1
✓ Branch 84 → 85 taken 1209 times.
1209x BIO_free(bio);
321 }
322 }
323
324
1/1
✓ Branch 88 → 89 taken 2399 times.
2399x SSL_CTX_set_verify_depth(ctx_, cd.verify_depth);
325
326
2/2
✓ Branch 90 → 91 taken 4 times.
✓ Branch 90 → 94 taken 2395 times.
2399x if (!cd.ciphersuites.empty())
327 {
328
1/1
✓ Branch 91 → 92 taken 4 times.
4x SSL_CTX_set_security_level(ctx_, 0);
329
1/1
✓ Branch 93 → 94 taken 4 times.
4x SSL_CTX_set_cipher_list(ctx_, cd.ciphersuites.c_str());
330 }
331 }
332
333 4798x ~openssl_native_context() override
334 2399x {
335
1/2
✓ Branch 2 → 3 taken 2399 times.
✗ Branch 2 → 4 not taken.
2399x if (ctx_)
336 2399x SSL_CTX_free(ctx_);
337 4798x }
338 };
339
340 inline SSL_CTX*
341 2405x get_openssl_context(tls_context_data const& cd)
342 {
343 static char key;
344
2/4
✓ Branch 2 → 3 taken 2405 times.
✓ Branch 3 → 4 taken 2399 times.
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 8 not taken.
4804x auto* p = cd.find(&key, [&] { return new openssl_native_context(cd); });
345 2405x return static_cast<openssl_native_context*>(p)->ctx_;
346 }
347
348 } // namespace detail
349
350 struct openssl_stream::impl
351 {
352 capy::any_stream* s_;
353 tls_context ctx_;
354 SSL* ssl_ = nullptr;
355 BIO* ext_bio_ = nullptr;
356 bool used_ = false;
357
358 std::vector<char> in_buf_;
359 std::vector<char> out_buf_;
360
361 capy::async_mutex io_cm_;
362
363 2405x impl(capy::any_stream& s, tls_context ctx) : s_(&s), ctx_(std::move(ctx))
364 {
365
1/1
✓ Branch 7 → 8 taken 2405 times.
2405x in_buf_.resize(default_buffer_size);
366
1/1
✓ Branch 8 → 9 taken 2405 times.
2405x out_buf_.resize(default_buffer_size);
367 2405x }
368
369 2405x ~impl()
370 {
371
1/2
✓ Branch 2 → 3 taken 2405 times.
✗ Branch 2 → 4 not taken.
2405x if (ext_bio_)
372 2405x BIO_free(ext_bio_);
373
1/2
✓ Branch 4 → 5 taken 2405 times.
✗ Branch 4 → 6 not taken.
2405x if (ssl_)
374 2405x SSL_free(ssl_);
375 2405x }
376
377 20x void reset()
378 {
379
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 20 times.
20x if (!ssl_)
380 return;
381
382 // Preserves SSL* and BIO pair, releases session state
383
1/1
✓ Branch 4 → 5 taken 20 times.
20x SSL_clear(ssl_);
384
385 // Drain stale data from the external BIO
386 char drain[1024];
387
2/3
✓ Branch 7 → 8 taken 20 times.
✗ Branch 8 → 6 not taken.
✓ Branch 8 → 9 taken 20 times.
20x while (BIO_ctrl_pending(ext_bio_) > 0)
388 BIO_read(ext_bio_, drain, sizeof(drain));
389
390 // SSL_clear clears per-session settings; reapply hostname
391 20x auto& cd = detail::get_tls_context_data(ctx_);
392
1/1
✓ Branch 10 → 11 taken 20 times.
20x apply_hostname_verification(ssl_, cd.hostname);
393
394 20x used_ = false;
395 }
396
397
1/1
✓ Branch 5 → 6 taken 76041 times.
76041x capy::task<std::error_code> flush_output()
398 {
399 while (BIO_ctrl_pending(ext_bio_) > 0)
400 {
401 std::size_t got = 0;
402 while (BIO_ctrl_pending(ext_bio_) > 0 && got < out_buf_.size())
403 {
404 int put = static_cast<int>(BIO_ctrl_pending(ext_bio_));
405 put = (std::min)(put, static_cast<int>(out_buf_.size() - got));
406 int r = BIO_read(ext_bio_, out_buf_.data() + got, put);
407 if (r <= 0)
408 break;
409 got += static_cast<std::size_t>(r);
410 }
411 if (got == 0)
412 break;
413
414 {
415 auto [lec] = co_await io_cm_.lock();
416 if (lec)
417 co_return lec;
418 capy::async_mutex::lock_guard io_guard(&io_cm_);
419 auto [ec, n] = co_await capy::write(
420 *s_, capy::const_buffer(out_buf_.data(), got));
421 if (ec)
422 co_return ec;
423 }
424 }
425 co_return std::error_code{};
426 152082x }
427
428
1/1
✓ Branch 5 → 6 taken 38661 times.
38661x capy::task<std::error_code> read_input()
429 {
430 auto [lec] = co_await io_cm_.lock();
431 if (lec)
432 co_return lec;
433 capy::async_mutex::lock_guard io_guard(&io_cm_);
434 auto [ec, n] = co_await s_->read_some(
435 capy::mutable_buffer(in_buf_.data(), in_buf_.size()));
436 if (ec)
437 co_return ec;
438
439 int got = BIO_write(ext_bio_, in_buf_.data(), static_cast<int>(n));
440 if (got < static_cast<int>(n))
441 {
442 co_return make_error_code(std::errc::no_buffer_space);
443 }
444
445 co_return std::error_code{};
446 77322x }
447
448 capy::io_task<std::size_t>
449
1/1
✓ Branch 6 → 7 taken 36636 times.
36636x do_read_some(capy::detail::mutable_buffer_array<capy::detail::max_iovec_> buffers)
450 {
451 std::error_code ec;
452 std::size_t total_read = 0;
453
454 for (auto& buf : buffers)
455 {
456 char* dest = static_cast<char*>(buf.data());
457 int remaining = static_cast<int>(buf.size());
458
459 while (remaining > 0)
460 {
461 ERR_clear_error();
462 int ret = SSL_read(ssl_, dest, remaining);
463
464 if (ret > 0)
465 {
466 dest += ret;
467 remaining -= ret;
468 total_read += static_cast<std::size_t>(ret);
469
470 if (total_read > 0)
471 co_return {std::error_code{}, total_read};
472 }
473 else
474 {
475 int err = SSL_get_error(ssl_, ret);
476
477 if (err == SSL_ERROR_WANT_WRITE)
478 {
479 ec = co_await flush_output();
480 if (ec)
481 co_return {ec, total_read};
482 }
483 else if (err == SSL_ERROR_WANT_READ)
484 {
485 ec = co_await flush_output();
486 if (ec)
487 co_return {ec, total_read};
488
489 ec = co_await read_input();
490 if (ec)
491 {
492 if (ec == make_error_code(capy::error::eof))
493 {
494 if (SSL_get_shutdown(ssl_) &
495 SSL_RECEIVED_SHUTDOWN)
496 ec = make_error_code(capy::error::eof);
497 else
498 ec = make_error_code(
499 capy::error::stream_truncated);
500 }
501 co_return {ec, total_read};
502 }
503 }
504 else if (err == SSL_ERROR_ZERO_RETURN)
505 {
506 co_return {
507 make_error_code(capy::error::eof), total_read};
508 }
509 else if (err == SSL_ERROR_SYSCALL)
510 {
511 unsigned long ssl_err = ERR_get_error();
512 if (ssl_err == 0)
513 ec = make_error_code(capy::error::stream_truncated);
514 else
515 ec = make_openssl_error(ssl_err);
516 co_return {ec, total_read};
517 }
518 else
519 {
520 unsigned long ssl_err = ERR_get_error();
521 ec = make_openssl_error(ssl_err);
522 co_return {ec, total_read};
523 }
524 }
525 }
526 }
527
528 co_return {std::error_code{}, total_read};
529 73272x }
530
531 capy::io_task<std::size_t>
532
1/1
✓ Branch 6 → 7 taken 36612 times.
36612x do_write_some(capy::detail::const_buffer_array<capy::detail::max_iovec_> buffers)
533 {
534 std::error_code ec;
535 std::size_t total_written = 0;
536
537 for (auto const& buf : buffers)
538 {
539 char const* src = static_cast<char const*>(buf.data());
540 int remaining = static_cast<int>(buf.size());
541
542 while (remaining > 0)
543 {
544 ERR_clear_error();
545 int ret = SSL_write(ssl_, src, remaining);
546
547 if (ret > 0)
548 {
549 src += ret;
550 remaining -= ret;
551 total_written += static_cast<std::size_t>(ret);
552
553 if (total_written > 0)
554 {
555 ec = co_await flush_output();
556 co_return {ec, total_written};
557 }
558 }
559 else
560 {
561 int err = SSL_get_error(ssl_, ret);
562
563 if (err == SSL_ERROR_WANT_WRITE)
564 {
565 ec = co_await flush_output();
566 if (ec)
567 co_return {ec, total_written};
568 }
569 else if (err == SSL_ERROR_WANT_READ)
570 {
571 ec = co_await flush_output();
572 if (ec)
573 co_return {ec, total_written};
574
575 ec = co_await read_input();
576 if (ec)
577 co_return {ec, total_written};
578 }
579 else
580 {
581 unsigned long ssl_err = ERR_get_error();
582 ec = make_openssl_error(ssl_err);
583 co_return {ec, total_written};
584 }
585 }
586 }
587 }
588
589 co_return {std::error_code{}, total_written};
590 73224x }
591
592
1/1
✓ Branch 5 → 6 taken 1591 times.
1591x capy::io_task<> do_handshake(int type)
593 {
594 if (used_)
595 reset();
596
597 std::error_code ec;
598
599 while (true)
600 {
601 ERR_clear_error();
602 int ret;
603 if (type == openssl_stream::client)
604 ret = SSL_connect(ssl_);
605 else
606 ret = SSL_accept(ssl_);
607
608 if (ret == 1)
609 {
610 used_ = true;
611 ec = co_await flush_output();
612 co_return {ec};
613 }
614 else
615 {
616 int err = SSL_get_error(ssl_, ret);
617
618 if (err == SSL_ERROR_WANT_WRITE)
619 {
620 ec = co_await flush_output();
621 if (ec)
622 co_return {ec};
623 }
624 else if (err == SSL_ERROR_WANT_READ)
625 {
626 ec = co_await flush_output();
627 if (ec)
628 co_return {ec};
629
630 ec = co_await read_input();
631 if (ec)
632 co_return {ec};
633 }
634 else
635 {
636 unsigned long ssl_err = ERR_get_error();
637 ec = make_openssl_error(ssl_err);
638 co_return {ec};
639 }
640 }
641 }
642 3182x }
643
644
1/1
✓ Branch 5 → 6 taken 37 times.
37x capy::io_task<> do_shutdown()
645 {
646 std::error_code ec;
647
648 while (true)
649 {
650 ERR_clear_error();
651 int ret = SSL_shutdown(ssl_);
652
653 if (ret == 1)
654 {
655 ec = co_await flush_output();
656 co_return {ec};
657 }
658 else if (ret == 0)
659 {
660 ec = co_await flush_output();
661 if (ec)
662 co_return {ec};
663
664 ec = co_await read_input();
665 if (ec)
666 {
667 ec = normalize_openssl_shutdown_read_error(ec);
668 co_return {ec};
669 }
670 }
671 else
672 {
673 int err = SSL_get_error(ssl_, ret);
674
675 if (err == SSL_ERROR_WANT_WRITE)
676 {
677 ec = co_await flush_output();
678 if (ec)
679 co_return {ec};
680 }
681 else if (err == SSL_ERROR_WANT_READ)
682 {
683 ec = co_await flush_output();
684 if (ec)
685 co_return {ec};
686
687 ec = co_await read_input();
688 if (ec)
689 {
690 ec = normalize_openssl_shutdown_read_error(ec);
691 co_return {ec};
692 }
693 }
694 else
695 {
696 unsigned long ssl_err = ERR_get_error();
697 if (ssl_err == 0 && err == SSL_ERROR_SYSCALL)
698 {
699 ec = {};
700 }
701 else
702 {
703 ec = make_openssl_error(ssl_err);
704 }
705 co_return {ec};
706 }
707 }
708 }
709 74x }
710
711 2405x std::error_code init_ssl()
712 {
713 2405x auto& cd = detail::get_tls_context_data(ctx_);
714
1/1
✓ Branch 3 → 4 taken 2405 times.
2405x SSL_CTX* native_ctx = detail::get_openssl_context(cd);
715
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 7 taken 2405 times.
2405x if (!native_ctx)
716 {
717 unsigned long err = ERR_get_error();
718 return make_openssl_error(err);
719 }
720
721
1/1
✓ Branch 7 → 8 taken 2405 times.
2405x ssl_ = SSL_new(native_ctx);
722
1/2
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 11 taken 2405 times.
2405x if (!ssl_)
723 {
724 unsigned long err = ERR_get_error();
725 return make_openssl_error(err);
726 }
727
728 2405x BIO* int_bio = nullptr;
729
2/3
✓ Branch 11 → 12 taken 2405 times.
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 16 taken 2405 times.
2405x if (!BIO_new_bio_pair(&int_bio, 0, &ext_bio_, 0))
730 {
731 unsigned long err = ERR_get_error();
732 SSL_free(ssl_);
733 ssl_ = nullptr;
734 return make_openssl_error(err);
735 }
736
737
1/1
✓ Branch 16 → 17 taken 2405 times.
2405x SSL_set_bio(ssl_, int_bio, int_bio);
738
739
1/1
✓ Branch 17 → 18 taken 2405 times.
2405x apply_hostname_verification(ssl_, cd.hostname);
740
741 2405x return {};
742 }
743 };
744
745 openssl_stream::impl*
746 2405x openssl_stream::make_impl(capy::any_stream& stream, tls_context const& ctx)
747 {
748
2/4
✓ Branch 2 → 3 taken 2405 times.
✓ Branch 4 → 5 taken 2405 times.
✗ Branch 19 → 20 not taken.
✗ Branch 19 → 21 not taken.
2405x auto* p = new impl(stream, ctx);
749
750
1/1
✓ Branch 6 → 7 taken 2405 times.
2405x auto ec = p->init_ssl();
751
1/2
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 13 taken 2405 times.
2405x if (ec)
752 {
753 delete p;
754 return nullptr;
755 }
756
757 2405x return p;
758 }
759
760 2407x openssl_stream::~openssl_stream()
761 {
762
2/2
✓ Branch 2 → 3 taken 2404 times.
✓ Branch 2 → 5 taken 3 times.
2407x delete impl_;
763 2407x }
764
765 2x openssl_stream::openssl_stream(openssl_stream&& other) noexcept
766 2x : stream_(std::move(other.stream_))
767 2x , impl_(other.impl_)
768 {
769 2x other.impl_ = nullptr;
770
1/2
✓ Branch 5 → 6 taken 2 times.
✗ Branch 5 → 7 not taken.
2x if (impl_)
771 2x impl_->s_ = &stream_;
772 2x }
773
774 openssl_stream&
775 1x openssl_stream::operator=(openssl_stream&& other) noexcept
776 {
777
1/2
✓ Branch 2 → 3 taken 1 time.
✗ Branch 2 → 10 not taken.
1x if (this != &other)
778 {
779
1/2
✓ Branch 3 → 4 taken 1 time.
✗ Branch 3 → 6 not taken.
1x delete impl_;
780 1x stream_ = std::move(other.stream_);
781 1x impl_ = other.impl_;
782 1x other.impl_ = nullptr;
783
1/2
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 10 not taken.
1x if (impl_)
784 1x impl_->s_ = &stream_;
785 }
786 1x return *this;
787 }
788
789 capy::io_task<std::size_t>
790
1/1
✓ Branch 6 → 7 taken 36636 times.
36636x openssl_stream::do_read_some(
791 capy::detail::mutable_buffer_array<capy::detail::max_iovec_> buffers)
792 {
793 co_return co_await impl_->do_read_some(buffers);
794 73272x }
795
796 capy::io_task<std::size_t>
797
1/1
✓ Branch 6 → 7 taken 36612 times.
36612x openssl_stream::do_write_some(
798 capy::detail::const_buffer_array<capy::detail::max_iovec_> buffers)
799 {
800 co_return co_await impl_->do_write_some(buffers);
801 73224x }
802
803 capy::io_task<>
804
1/1
✓ Branch 5 → 6 taken 1591 times.
1591x openssl_stream::handshake(handshake_type type)
805 {
806 co_return co_await impl_->do_handshake(type);
807 3182x }
808
809 capy::io_task<>
810
1/1
✓ Branch 5 → 6 taken 37 times.
37x openssl_stream::shutdown()
811 {
812 co_return co_await impl_->do_shutdown();
813 74x }
814
815 void
816 16x openssl_stream::reset()
817 {
818 16x impl_->reset();
819 16x }
820
821 std::string_view
822 1x openssl_stream::name() const noexcept
823 {
824 1x return "openssl";
825 }
826
827 } // namespace boost::corosio
828