src/openssl/src/detail/engine.hpp

100.0% Lines (7/7) 100.0% List of functions (2/3) -% Branches (0/0)
engine.hpp
f(x) Functions (3)
Line TLA Hits Source Code
1 //
2 // Copyright (c) 2026 Steve Gerbino
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 // Official repository: https://github.com/cppalliance/corosio
8 //
9
10 #ifndef SRC_OPENSSL_DETAIL_ENGINE_HPP
11 #define SRC_OPENSSL_DETAIL_ENGINE_HPP
12
13 #include <boost/corosio/detail/config.hpp>
14 #include <boost/corosio/tls_context.hpp>
15 #include <boost/corosio/tls_stream.hpp>
16
17 #include "src/tls/detail/engine_types.hpp"
18
19 #include <cstddef>
20 #include <string>
21 #include <system_error>
22 #include <utility>
23
24 // Opaque OpenSSL session handles, mirroring the vendor's own typedef
25 // targets (`typedef struct ssl_st SSL` / `typedef struct bio_st
26 // BIO`). This header stays vendor-free so a TU may hold both
27 // backends' engines: the real OpenSSL and WolfSSL headers cannot
28 // coexist (WolfSSL's compatibility layer clashes with genuine
29 // OpenSSL declarations).
30 struct ssl_st;
31 struct bio_st;
32
33 namespace boost::corosio {
34
35 namespace detail {
36
37 class openssl_native_context;
38
39 // Backend scope: both backends spell their engine `engine`, and both
40 // libraries (plus their unit tests) link into one binary, so each
41 // class needs a distinct qualified name.
42 namespace openssl {
43
44 /** Synchronous, transport-free OpenSSL record engine.
45
46 Owns the SSL session and its byte interface (a memory BIO pair)
47 and concentrates every SSL-result-to-error-code decision in one
48 mapping site (`perform`). The coroutine driver keeps the
49 transport, claims, and buffering; it only shuttles bytes through
50 `put_input` / `get_output` as directed by `engine_want` verdicts.
51
52 Data flow through the BIO pair:
53
54 App -> SSL_write -> int_bio -> get_output -> transport write
55 App <- SSL_read <- int_bio <- put_input <- transport read
56
57 @par Thread Safety
58 Distinct objects: Safe.@n
59 Shared objects: Unsafe.
60 */
61 // Exported so the transport-free engine unit tests can link against
62 // shared library builds (hidden visibility / DLL boundaries).
63 class BOOST_COROSIO_DECL engine
64 {
65 2802x ssl_st* ssl_ = nullptr;
66 2802x bio_st* ext_bio_ = nullptr;
67
68 // Cached at init; the per-context cache returns the same object
69 // for the stream's context every time, so one lookup suffices.
70 2802x openssl_native_context* nc_ = nullptr;
71
72 // Set when SSL_clear() or SSL_set_session() fails in reset().
73 // Neither has a documented partial-failure contract, so the SSL*
74 // is left in an unknown (or still-resumable) state; the driver
75 // refuses the next handshake instead of resuming on it. Engine-
76 // owned because only engine-internal operations can latch it.
77 2802x bool clear_failed_ = false;
78
79 public:
80 /** Whether a failed transport write keeps drained ciphertext.
81
82 Ciphertext the driver already drained from the engine when a
83 transport write fails is dropped; a later flush does not
84 resend it.
85 */
86
87 /// Destroy the engine, releasing the session and BIO pair.
88 ~engine();
89
90 8406x engine() = default;
91 engine(engine const&) = delete;
92 engine& operator=(engine const&) = delete;
93
94 /** Create the SSL session and BIO pair from a TLS context.
95
96 Must succeed before any other member is used. A context whose
97 native build failed is reported unconditionally: the cache
98 retains a failed build permanently and the error queue may
99 already be drained, so a queue-derived code could read as
100 success.
101
102 @param ctx The TLS context supplying the native `SSL_CTX`.
103
104 @return An error if the session could not be created.
105 */
106 std::error_code init(tls_context const& ctx);
107
108 /** Reset the session for a fresh handshake.
109
110 Preserves the `SSL*` and BIO pair, releases session state,
111 drops the negotiated session (a resumed handshake would skip
112 certificate/hostname re-verification), and drains stale bytes
113 from the output BIO. Failures latch `clear_failed()`.
114 */
115 void reset();
116
117 /// Check whether a prior `reset()` left the session unusable.
118 bool
119 clear_failed() const noexcept
120 {
121 return clear_failed_;
122 }
123
124 /// Check whether the native context build rejected its configuration.
125 bool context_setup_failed() const noexcept;
126
127 /** Check that the native context can back a handshake.
128
129 A requested configuration could not be applied when the
130 native context was built (inverted protocol window, rejected
131 cipher/version, or an unparseable CRL); refuse the handshake
132 rather than proceed with weakened or unexpected settings.
133
134 @return An error when the context build rejected its
135 configuration.
136 */
137 std::error_code check_context() const noexcept;
138
139 /** Check that the session survived its last reset.
140
141 A failed `reset()` leaves the session in an unknown (or
142 still-resumable) state; refuse the next handshake rather than
143 resume on the unknown remainder of a failed clear.
144
145 @return An error when a prior `reset()` failed.
146 */
147 std::error_code check_session() const noexcept;
148
149 /** Prepare the session for a handshake in the given role.
150
151 Applies SNI/hostname verification and installs the context's
152 ALPN offer, both for client handshakes only; a server
153 handshake clears any name left by a prior client-role
154 handshake so client certificates are never hostname-matched.
155 Fails closed rather than handshake without a requested check.
156
157 @param ctx Unused; the session was built from it at `init`.
158 @param role Handshake role.
159 @param hostname Peer name for SNI/verification; empty for
160 none.
161
162 @return An error when a requested setting could not be
163 applied.
164 */
165 std::error_code prepare(
166 tls_context const& ctx, tls_role role, std::string const& hostname);
167
168 /** Apply SNI and hostname verification for the next handshake.
169
170 An empty hostname clears any previously applied name. IP
171 literals are excluded from SNI and matched against the
172 certificate's iPAddress entries instead of its DNS names.
173
174 @param hostname Peer name, or empty to clear.
175
176 @return `true` on success.
177 */
178 bool apply_hostname(std::string const& hostname);
179
180 /** Install the context's ALPN offer on the session.
181
182 No-op success when the context configured no protocols. Only
183 meaningful for client handshakes.
184
185 @return `true` when the offer (if any) was installed.
186 */
187 bool apply_alpn_offer();
188
189 /** Record the ALPN protocol selected during the handshake.
190
191 Assigns `out` only when a protocol was negotiated, leaving it
192 untouched otherwise.
193
194 @param out Receives the selected protocol.
195 */
196 void capture_alpn(std::string& out) const;
197
198 /** Run one synchronous engine step and map its outcome.
199
200 All error mapping lives here: a `done` verdict with a truthy
201 `ec` is terminal and already mapped. Output-first: when the
202 step leaves pending output, the verdict is
203 `output_then_retry` / `output_then_done` so ciphertext
204 reaches the peer before the driver parks on input. A received
205 close_notify (`read` / `write`) reports `eof` with a plain
206 `done`: it queues no output, so no flush precedes it.
207
208 @param op Which operation to advance.
209 @param data Application buffer (`read` / `write` only).
210 @param len Application buffer size in bytes.
211
212 @return The mapped verdict for this step.
213 */
214 engine_result perform(engine_op op, void* data, std::size_t len);
215
216 /** Stage transport bytes into the engine.
217
218 @param data Bytes received from the transport.
219 @param len Number of bytes offered.
220
221 @return The number of bytes accepted; may be zero when the
222 staging BIO is full.
223 */
224 std::size_t put_input(unsigned char const* data, std::size_t len);
225
226 /** Return the writable staging region for a zero-copy transport read.
227
228 The transport reads ciphertext directly into the returned span,
229 then reports how much landed via `input_committed`, avoiding the
230 copy a `put_input` deposit would incur. The region is the
231 contiguous run of the BIO pair's buffer, so its size may be less
232 than the total free space near the buffer's wrap.
233
234 @return Pointer and size of the contiguous writable region; the
235 size is zero when the staging BIO is full.
236 */
237 std::pair<unsigned char*, std::size_t> input_area();
238
239 /** Commit bytes the transport read into the `input_area` span.
240
241 @param n Number of bytes written into the region.
242 */
243 void input_committed(std::size_t n);
244
245 /// Return the number of ciphertext bytes awaiting transport write.
246 std::size_t pending_output() const;
247
248 /** Drain staged ciphertext for transport write.
249
250 @param data Destination buffer.
251 @param len Destination capacity in bytes.
252
253 @return The number of bytes drained; zero when nothing could
254 be read.
255 */
256 std::size_t get_output(unsigned char* data, std::size_t len);
257
258 /// Check whether the peer's close_notify has been received.
259 bool received_shutdown() const;
260
261 /// Return the underlying session handle (tests only).
262 ssl_st*
263 3x native_handle() const noexcept
264 {
265 3x return ssl_;
266 }
267 };
268
269 } // namespace openssl
270
271 } // namespace detail
272
273 } // namespace boost::corosio
274
275 #endif
276