include/boost/corosio/openssl_stream.hpp

100.0% Lines (11 / 11) 100.0% Functions (8 / 8)
openssl_stream.hpp
f(x) Functions (8)
Function Calls Lines Blocks
boost::corosio::openssl_stream::openssl_stream<boost::capy::any_stream>(boost::capy::any_stream, boost::corosio::tls_context const&) :84 1x 100.0% 64.0% boost::corosio::openssl_stream::openssl_stream<boost::corosio::test::basic_mocket<boost::corosio::tcp_socket> >(boost::corosio::test::basic_mocket<boost::corosio::tcp_socket>, boost::corosio::tls_context const&) :84 4x 100.0% 64.0% boost::corosio::openssl_stream::openssl_stream<boost::corosio::test::partial_write_transport>(boost::corosio::test::partial_write_transport, boost::corosio::tls_context const&) :84 1x 100.0% 64.0% boost::corosio::openssl_stream::openssl_stream<boost::corosio::openssl_stream_test::flush_fail_stream>(boost::corosio::openssl_stream_test::flush_fail_stream*, boost::corosio::tls_context const&) :100 5x 100.0% 60.0% boost::corosio::openssl_stream::openssl_stream<boost::corosio::tcp_socket>(boost::corosio::tcp_socket*, boost::corosio::tls_context const&) :100 1989x 100.0% 60.0% boost::corosio::openssl_stream::openssl_stream<boost::corosio::test::basic_mocket<boost::corosio::tcp_socket> >(boost::corosio::test::basic_mocket<boost::corosio::tcp_socket>*, boost::corosio::tls_context const&) :100 292x 100.0% 60.0% boost::corosio::openssl_stream::openssl_stream<boost::corosio::test::gated_stream>(boost::corosio::test::gated_stream*, boost::corosio::tls_context const&) :100 1x 100.0% 60.0% boost::corosio::openssl_stream::openssl_stream<boost::corosio::test::partial_error_stream>(boost::corosio::test::partial_error_stream*, boost::corosio::tls_context const&) :100 1x 100.0% 60.0%
Line TLA Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco ([email protected])
3 // Copyright (c) 2026 Michael Vandeberg
4 // Copyright (c) 2026 Steve Gerbino
5 //
6 // Distributed under the Boost Software License, Version 1.0. (See accompanying
7 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
8 //
9 // Official repository: https://github.com/cppalliance/corosio
10 //
11
12 #ifndef BOOST_COROSIO_OPENSSL_STREAM_HPP
13 #define BOOST_COROSIO_OPENSSL_STREAM_HPP
14
15 #include <boost/corosio/detail/config.hpp>
16 #include <boost/corosio/tls_context.hpp>
17 #include <boost/corosio/tls_stream.hpp>
18 #include <boost/capy/detail/buffer_array.hpp>
19 #include <boost/capy/concept/stream.hpp>
20 #include <boost/capy/io/any_stream.hpp>
21 #include <boost/capy/results.hpp>
22 #include <boost/capy/task.hpp>
23
24 #include <concepts>
25 #include <memory>
26 #include <system_error>
27 #include <tuple>
28
29 namespace boost::corosio {
30
31 /** Encrypts and decrypts a stream using OpenSSL.
32
33 This class wraps an underlying stream satisfying `capy::Stream`
34 and provides TLS encryption using the OpenSSL library.
35
36 Derives from @ref tls_stream to provide a runtime-polymorphic
37 interface. The TLS operations are implemented as coroutines
38 that orchestrate reads and writes on the underlying stream.
39
40 @par Construction Modes
41
42 Two construction modes are supported:
43
44 - **Owning**: Pass stream by value. The `openssl_stream` takes
45 ownership. The stream is moved into internal storage.
46
47 - **Reference**: Pass stream by pointer. The `openssl_stream`
48 does not own the stream. The caller must ensure the stream
49 outlives this object.
50
51 @par Thread Safety
52 Distinct objects: Safe.@n
53 Shared objects: Unsafe, with one exception: one read operation and
54 one write operation may be in flight simultaneously. `shutdown()`
55 may overlap a pending read. On a multi-threaded execution context,
56 all operations on one stream must run within the same
57 `capy::strand`, or must otherwise never run concurrently. A
58 single-threaded context needs no strand.
59
60 @par Example
61 @par !example openssl_stream
62
63 @see @ref tls_stream, @ref wolfssl_stream
64 */
65 class BOOST_COROSIO_DECL openssl_stream final : public tls_stream
66 {
67 struct implementation;
68 implementation* impl_;
69
70 public:
71 /** Construct an OpenSSL stream (owning mode).
72
73 Takes ownership of the underlying stream by moving it into
74 internal storage. The stream is destroyed when this
75 `openssl_stream` is destroyed.
76
77 @param stream The stream to take ownership of. Must satisfy
78 `capy::Stream` and must not be an `openssl_stream`; that
79 case binds to the move constructor instead.
80 @param ctx The TLS context containing configuration.
81 */
82 template<capy::Stream S>
83 requires(!std::same_as<std::decay_t<S>, openssl_stream>)
84 6x openssl_stream(S stream, tls_context const& ctx)
85 6x : impl_(adopt(std::make_unique<S>(std::move(stream)), ctx))
86 {
87 6x }
88
89 /** Construct an OpenSSL stream (reference mode).
90
91 Wraps the underlying stream without taking ownership. The
92 caller must ensure the stream remains valid for the lifetime
93 of this `openssl_stream`.
94
95 @param stream Pointer to the stream to wrap. Must satisfy
96 `capy::Stream`.
97 @param ctx The TLS context containing configuration.
98 */
99 template<capy::Stream S>
100 2288x openssl_stream(S* stream, tls_context const& ctx)
101 2288x : impl_(make_implementation(capy::any_stream(stream), {}, ctx))
102 {
103 2288x }
104
105 /** Destroy the OpenSSL stream.
106
107 Operations still in flight complete with
108 `capy::error::canceled` without touching this object, and the
109 TLS state they need is released when the last of them
110 finishes. In owning mode the underlying stream is destroyed
111 when the last of those operations finishes. An underlying
112 stream with a `cancel()` member is told to cancel them now. In reference
113 mode, and for an owned stream without `cancel()`, an operation
114 parked on the underlying stream finishes when that stream's
115 operation does.
116 */
117 ~openssl_stream() override;
118
119 /** Move construct from another OpenSSL stream.
120
121 Operations in flight on @p other continue on this stream.
122
123 @param other The source stream. After the move,
124 @p other may only be destroyed or assigned to.
125 */
126 openssl_stream(openssl_stream&& other) noexcept;
127
128 /** Move assign from another OpenSSL stream.
129
130 Operations in flight on this stream complete as if it were
131 destroyed; those on @p other continue on this stream.
132
133 @param other The source stream. After the move,
134 @p other may only be destroyed or assigned to.
135
136 @return `*this`.
137 */
138 openssl_stream& operator=(openssl_stream&& other) noexcept;
139
140 /** Asynchronously perform the TLS handshake.
141
142 Suspends the calling coroutine until the handshake
143 completes, an error occurs, or the operation is
144 cancelled via stop token.
145
146 @pre The underlying stream must be connected. No other
147 TLS operation may be in progress on this stream.
148
149 @param role The handshake role, client or server.
150
151 @return An awaitable yielding `(error_code)`.
152 */
153 [[nodiscard]] capy::task<capy::results<>> handshake(tls_role role) override;
154
155 /** Asynchronously shut down the TLS session.
156
157 Sends a close_notify alert and waits for the peer's
158 close_notify response. Supports cancellation via
159 stop token.
160
161 @pre A handshake must have completed successfully. May overlap
162 a pending read. That read completes with `capy::error::eof`
163 when the peer answers the close_notify. No concurrent write
164 may be in progress.
165
166 @par Postconditions
167 If the transport ends before the peer's close_notify is
168 received, the result is `capy::error::stream_truncated`, not
169 success. A shutdown stopped mid-flight reports canceled. Any
170 other transport error propagates unchanged.
171
172 @return An awaitable yielding `(error_code)`.
173 */
174 [[nodiscard]] capy::task<capy::results<>> shutdown() override;
175
176 /// Cancel operations pending on the owned underlying stream.
177 void cancel() noexcept override;
178
179 /** Reset TLS session state for reuse.
180
181 Clears internal buffers and session data so the stream
182 can perform a new handshake on the same underlying
183 connection. The previous session is discarded and never
184 resumed, so a handshake after `reset()` is always a full
185 handshake.
186
187 @pre No TLS operation may be in progress on this stream.
188
189 @note If the backend cannot restore a clean session state,
190 subsequent handshakes fail rather than proceed on a
191 partially cleared session.
192 */
193 void reset() override;
194
195 /** Set the peer hostname for SNI and certificate verification.
196
197 @param hostname The peer name to send as SNI and match against the
198 certificate.
199 */
200 void set_hostname(std::string_view hostname) override;
201
202 /// Return the underlying stream.
203 capy::any_stream& next_layer() noexcept override;
204
205 /// Return the underlying stream.
206 capy::any_stream const& next_layer() const noexcept override;
207
208 /// Return the TLS backend name ("openssl").
209 std::string_view name() const noexcept override;
210
211 /// Return the ALPN protocol negotiated during the handshake, or empty.
212 std::string_view alpn_protocol() const noexcept override;
213
214 protected:
215 /// @copydoc tls_stream::do_read_some
216 capy::task<capy::results<std::size_t>> do_read_some(
217 capy::detail::mutable_buffer_array<capy::detail::max_iovec_> buffers)
218 override;
219
220 /// @copydoc tls_stream::do_write_some
221 capy::task<capy::results<std::size_t>> do_write_some(
222 capy::detail::const_buffer_array<capy::detail::max_iovec_> buffers)
223 override;
224
225 private:
226 static implementation* make_implementation(
227 capy::any_stream stream,
228 detail::tls_owned_transport owned,
229 tls_context const& ctx);
230
231 // The transport is boxed apart from the any_stream that refers to
232 // it, so it outlives any operation still parked on it after the
233 // stream is destroyed.
234 template<class S>
235 static implementation*
236 6x adopt(std::unique_ptr<S> owned, tls_context const& ctx)
237 {
238 12x auto* impl = make_implementation(
239 12x capy::any_stream(owned.get()),
240 detail::make_owned_transport(owned.get()), ctx);
241 6x std::ignore = owned.release();
242 6x return impl;
243 }
244 };
245
246 /** Return the error category for raw OpenSSL errors.
247
248 Errors reported by @ref openssl_stream that originate from the OpenSSL
249 error queue (`ERR_get_error`) are assigned this category. Its
250 `message()` decodes the packed OpenSSL error code using OpenSSL's own
251 diagnostic strings. Printing such an `error_code` therefore yields a
252 readable description, for example "certificate verify failed".
253
254 OpenSSL errors whose library is `ERR_LIB_SYS` are reported with
255 `std::system_category()` instead, since their reason code is a genuine
256 `errno` value.
257
258 @return A reference to a static category object with name
259 `"corosio.openssl"`.
260 */
261 BOOST_COROSIO_DECL std::error_category const& openssl_category() noexcept;
262
263 } // namespace boost::corosio
264
265 #endif
266