ossl-nghttp3.c revision 1.1 1 1.1 christos /*
2 1.1 christos * Copyright 2023-2024 The OpenSSL Project Authors. All Rights Reserved.
3 1.1 christos *
4 1.1 christos * Licensed under the Apache License 2.0 (the "License"). You may not use
5 1.1 christos * this file except in compliance with the License. You can obtain a copy
6 1.1 christos * in the file LICENSE in the source distribution or at
7 1.1 christos * https://www.openssl.org/source/license.html
8 1.1 christos */
9 1.1 christos #include "ossl-nghttp3.h"
10 1.1 christos #include <openssl/err.h>
11 1.1 christos #include <assert.h>
12 1.1 christos
13 1.1 christos #define ARRAY_LEN(x) (sizeof(x)/sizeof((x)[0]))
14 1.1 christos
15 1.1 christos enum {
16 1.1 christos OSSL_DEMO_H3_STREAM_TYPE_CTRL_SEND,
17 1.1 christos OSSL_DEMO_H3_STREAM_TYPE_QPACK_ENC_SEND,
18 1.1 christos OSSL_DEMO_H3_STREAM_TYPE_QPACK_DEC_SEND,
19 1.1 christos OSSL_DEMO_H3_STREAM_TYPE_REQ,
20 1.1 christos };
21 1.1 christos
22 1.1 christos #define BUF_SIZE 4096
23 1.1 christos
24 1.1 christos struct ossl_demo_h3_stream_st {
25 1.1 christos uint64_t id; /* QUIC stream ID */
26 1.1 christos SSL *s; /* QUIC stream SSL object */
27 1.1 christos int done_recv_fin; /* Received FIN */
28 1.1 christos void *user_data;
29 1.1 christos
30 1.1 christos uint8_t buf[BUF_SIZE];
31 1.1 christos size_t buf_cur, buf_total;
32 1.1 christos };
33 1.1 christos
34 1.1 christos DEFINE_LHASH_OF_EX(OSSL_DEMO_H3_STREAM);
35 1.1 christos
36 1.1 christos static void h3_stream_free(OSSL_DEMO_H3_STREAM *s)
37 1.1 christos {
38 1.1 christos if (s == NULL)
39 1.1 christos return;
40 1.1 christos
41 1.1 christos SSL_free(s->s);
42 1.1 christos OPENSSL_free(s);
43 1.1 christos }
44 1.1 christos
45 1.1 christos static unsigned long h3_stream_hash(const OSSL_DEMO_H3_STREAM *s)
46 1.1 christos {
47 1.1 christos return (unsigned long)s->id;
48 1.1 christos }
49 1.1 christos
50 1.1 christos static int h3_stream_eq(const OSSL_DEMO_H3_STREAM *a, const OSSL_DEMO_H3_STREAM *b)
51 1.1 christos {
52 1.1 christos if (a->id < b->id) return -1;
53 1.1 christos if (a->id > b->id) return 1;
54 1.1 christos return 0;
55 1.1 christos }
56 1.1 christos
57 1.1 christos void *OSSL_DEMO_H3_STREAM_get_user_data(const OSSL_DEMO_H3_STREAM *s)
58 1.1 christos {
59 1.1 christos return s->user_data;
60 1.1 christos }
61 1.1 christos
62 1.1 christos struct ossl_demo_h3_conn_st {
63 1.1 christos /* QUIC connection SSL object */
64 1.1 christos SSL *qconn;
65 1.1 christos /* BIO wrapping QCSO */
66 1.1 christos BIO *qconn_bio;
67 1.1 christos /* HTTP/3 connection object */
68 1.1 christos nghttp3_conn *h3conn;
69 1.1 christos /* map of stream IDs to OSSL_DEMO_H3_STREAMs */
70 1.1 christos LHASH_OF(OSSL_DEMO_H3_STREAM) *streams;
71 1.1 christos /* opaque user data pointer */
72 1.1 christos void *user_data;
73 1.1 christos
74 1.1 christos int pump_res;
75 1.1 christos size_t consumed_app_data;
76 1.1 christos
77 1.1 christos /* Forwarding callbacks */
78 1.1 christos nghttp3_recv_data recv_data_cb;
79 1.1 christos nghttp3_stream_close stream_close_cb;
80 1.1 christos nghttp3_stop_sending stop_sending_cb;
81 1.1 christos nghttp3_reset_stream reset_stream_cb;
82 1.1 christos nghttp3_deferred_consume deferred_consume_cb;
83 1.1 christos };
84 1.1 christos
85 1.1 christos void OSSL_DEMO_H3_CONN_free(OSSL_DEMO_H3_CONN *conn)
86 1.1 christos {
87 1.1 christos if (conn == NULL)
88 1.1 christos return;
89 1.1 christos
90 1.1 christos lh_OSSL_DEMO_H3_STREAM_doall(conn->streams, h3_stream_free);
91 1.1 christos
92 1.1 christos nghttp3_conn_del(conn->h3conn);
93 1.1 christos BIO_free_all(conn->qconn_bio);
94 1.1 christos lh_OSSL_DEMO_H3_STREAM_free(conn->streams);
95 1.1 christos OPENSSL_free(conn);
96 1.1 christos }
97 1.1 christos
98 1.1 christos static OSSL_DEMO_H3_STREAM *h3_conn_create_stream(OSSL_DEMO_H3_CONN *conn, int type)
99 1.1 christos {
100 1.1 christos OSSL_DEMO_H3_STREAM *s;
101 1.1 christos uint64_t flags = SSL_STREAM_FLAG_ADVANCE;
102 1.1 christos
103 1.1 christos if ((s = OPENSSL_zalloc(sizeof(OSSL_DEMO_H3_STREAM))) == NULL)
104 1.1 christos return NULL;
105 1.1 christos
106 1.1 christos if (type != OSSL_DEMO_H3_STREAM_TYPE_REQ)
107 1.1 christos flags |= SSL_STREAM_FLAG_UNI;
108 1.1 christos
109 1.1 christos if ((s->s = SSL_new_stream(conn->qconn, flags)) == NULL) {
110 1.1 christos ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,
111 1.1 christos "could not create QUIC stream object");
112 1.1 christos goto err;
113 1.1 christos }
114 1.1 christos
115 1.1 christos s->id = SSL_get_stream_id(s->s);
116 1.1 christos lh_OSSL_DEMO_H3_STREAM_insert(conn->streams, s);
117 1.1 christos return s;
118 1.1 christos
119 1.1 christos err:
120 1.1 christos OPENSSL_free(s);
121 1.1 christos return NULL;
122 1.1 christos }
123 1.1 christos
124 1.1 christos static OSSL_DEMO_H3_STREAM *h3_conn_accept_stream(OSSL_DEMO_H3_CONN *conn, SSL *qstream)
125 1.1 christos {
126 1.1 christos OSSL_DEMO_H3_STREAM *s;
127 1.1 christos
128 1.1 christos if ((s = OPENSSL_zalloc(sizeof(OSSL_DEMO_H3_STREAM))) == NULL)
129 1.1 christos return NULL;
130 1.1 christos
131 1.1 christos s->id = SSL_get_stream_id(qstream);
132 1.1 christos s->s = qstream;
133 1.1 christos lh_OSSL_DEMO_H3_STREAM_insert(conn->streams, s);
134 1.1 christos return s;
135 1.1 christos }
136 1.1 christos
137 1.1 christos static void h3_conn_remove_stream(OSSL_DEMO_H3_CONN *conn, OSSL_DEMO_H3_STREAM *s)
138 1.1 christos {
139 1.1 christos if (s == NULL)
140 1.1 christos return;
141 1.1 christos
142 1.1 christos lh_OSSL_DEMO_H3_STREAM_delete(conn->streams, s);
143 1.1 christos h3_stream_free(s);
144 1.1 christos }
145 1.1 christos
146 1.1 christos static int h3_conn_recv_data(nghttp3_conn *h3conn, int64_t stream_id,
147 1.1 christos const uint8_t *data, size_t datalen,
148 1.1 christos void *conn_user_data, void *stream_user_data)
149 1.1 christos {
150 1.1 christos OSSL_DEMO_H3_CONN *conn = conn_user_data;
151 1.1 christos
152 1.1 christos conn->consumed_app_data += datalen;
153 1.1 christos if (conn->recv_data_cb == NULL)
154 1.1 christos return 0;
155 1.1 christos
156 1.1 christos return conn->recv_data_cb(h3conn, stream_id, data, datalen,
157 1.1 christos conn_user_data, stream_user_data);
158 1.1 christos }
159 1.1 christos
160 1.1 christos static int h3_conn_stream_close(nghttp3_conn *h3conn, int64_t stream_id,
161 1.1 christos uint64_t app_error_code,
162 1.1 christos void *conn_user_data, void *stream_user_data)
163 1.1 christos {
164 1.1 christos int ret = 0;
165 1.1 christos OSSL_DEMO_H3_CONN *conn = conn_user_data;
166 1.1 christos OSSL_DEMO_H3_STREAM *stream = stream_user_data;
167 1.1 christos
168 1.1 christos if (conn->stream_close_cb != NULL)
169 1.1 christos ret = conn->stream_close_cb(h3conn, stream_id, app_error_code,
170 1.1 christos conn_user_data, stream_user_data);
171 1.1 christos
172 1.1 christos h3_conn_remove_stream(conn, stream);
173 1.1 christos return ret;
174 1.1 christos }
175 1.1 christos
176 1.1 christos static int h3_conn_stop_sending(nghttp3_conn *h3conn, int64_t stream_id,
177 1.1 christos uint64_t app_error_code,
178 1.1 christos void *conn_user_data, void *stream_user_data)
179 1.1 christos {
180 1.1 christos int ret = 0;
181 1.1 christos OSSL_DEMO_H3_CONN *conn = conn_user_data;
182 1.1 christos OSSL_DEMO_H3_STREAM *stream = stream_user_data;
183 1.1 christos
184 1.1 christos if (conn->stop_sending_cb != NULL)
185 1.1 christos ret = conn->stop_sending_cb(h3conn, stream_id, app_error_code,
186 1.1 christos conn_user_data, stream_user_data);
187 1.1 christos
188 1.1 christos SSL_free(stream->s);
189 1.1 christos stream->s = NULL;
190 1.1 christos return ret;
191 1.1 christos }
192 1.1 christos
193 1.1 christos static int h3_conn_reset_stream(nghttp3_conn *h3conn, int64_t stream_id,
194 1.1 christos uint64_t app_error_code,
195 1.1 christos void *conn_user_data, void *stream_user_data)
196 1.1 christos {
197 1.1 christos int ret = 0;
198 1.1 christos OSSL_DEMO_H3_CONN *conn = conn_user_data;
199 1.1 christos OSSL_DEMO_H3_STREAM *stream = stream_user_data;
200 1.1 christos SSL_STREAM_RESET_ARGS args = {0};
201 1.1 christos
202 1.1 christos if (conn->reset_stream_cb != NULL)
203 1.1 christos ret = conn->reset_stream_cb(h3conn, stream_id, app_error_code,
204 1.1 christos conn_user_data, stream_user_data);
205 1.1 christos
206 1.1 christos if (stream->s != NULL) {
207 1.1 christos args.quic_error_code = app_error_code;
208 1.1 christos
209 1.1 christos if (!SSL_stream_reset(stream->s, &args, sizeof(args)))
210 1.1 christos return 1;
211 1.1 christos }
212 1.1 christos
213 1.1 christos return ret;
214 1.1 christos }
215 1.1 christos
216 1.1 christos static int h3_conn_deferred_consume(nghttp3_conn *h3conn, int64_t stream_id,
217 1.1 christos size_t consumed,
218 1.1 christos void *conn_user_data, void *stream_user_data)
219 1.1 christos {
220 1.1 christos int ret = 0;
221 1.1 christos OSSL_DEMO_H3_CONN *conn = conn_user_data;
222 1.1 christos
223 1.1 christos if (conn->deferred_consume_cb != NULL)
224 1.1 christos ret = conn->deferred_consume_cb(h3conn, stream_id, consumed,
225 1.1 christos conn_user_data, stream_user_data);
226 1.1 christos
227 1.1 christos conn->consumed_app_data += consumed;
228 1.1 christos return ret;
229 1.1 christos }
230 1.1 christos
231 1.1 christos OSSL_DEMO_H3_CONN *OSSL_DEMO_H3_CONN_new_for_conn(BIO *qconn_bio,
232 1.1 christos const nghttp3_callbacks *callbacks,
233 1.1 christos const nghttp3_settings *settings,
234 1.1 christos void *user_data)
235 1.1 christos {
236 1.1 christos int ec;
237 1.1 christos OSSL_DEMO_H3_CONN *conn;
238 1.1 christos OSSL_DEMO_H3_STREAM *s_ctl_send = NULL;
239 1.1 christos OSSL_DEMO_H3_STREAM *s_qpenc_send = NULL;
240 1.1 christos OSSL_DEMO_H3_STREAM *s_qpdec_send = NULL;
241 1.1 christos nghttp3_settings dsettings = {0};
242 1.1 christos nghttp3_callbacks intl_callbacks = {0};
243 1.1 christos static const unsigned char alpn[] = {2, 'h', '3'};
244 1.1 christos
245 1.1 christos if (qconn_bio == NULL) {
246 1.1 christos ERR_raise_data(ERR_LIB_USER, ERR_R_PASSED_NULL_PARAMETER,
247 1.1 christos "QUIC connection BIO must be provided");
248 1.1 christos return NULL;
249 1.1 christos }
250 1.1 christos
251 1.1 christos if ((conn = OPENSSL_zalloc(sizeof(OSSL_DEMO_H3_CONN))) == NULL)
252 1.1 christos return NULL;
253 1.1 christos
254 1.1 christos conn->qconn_bio = qconn_bio;
255 1.1 christos conn->user_data = user_data;
256 1.1 christos
257 1.1 christos if (BIO_get_ssl(qconn_bio, &conn->qconn) == 0) {
258 1.1 christos ERR_raise_data(ERR_LIB_USER, ERR_R_PASSED_INVALID_ARGUMENT,
259 1.1 christos "BIO must be an SSL BIO");
260 1.1 christos goto err;
261 1.1 christos }
262 1.1 christos
263 1.1 christos /* Create the map of stream IDs to OSSL_DEMO_H3_STREAM structures. */
264 1.1 christos if ((conn->streams = lh_OSSL_DEMO_H3_STREAM_new(h3_stream_hash, h3_stream_eq)) == NULL)
265 1.1 christos goto err;
266 1.1 christos
267 1.1 christos /*
268 1.1 christos * If the application has not started connecting yet, helpfully
269 1.1 christos * auto-configure ALPN. If the application wants to initiate the connection
270 1.1 christos * itself, it must take care of this itself.
271 1.1 christos */
272 1.1 christos if (SSL_in_before(conn->qconn))
273 1.1 christos if (SSL_set_alpn_protos(conn->qconn, alpn, sizeof(alpn))) {
274 1.1 christos /* SSL_set_alpn_protos returns 1 on failure */
275 1.1 christos ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,
276 1.1 christos "failed to configure ALPN");
277 1.1 christos goto err;
278 1.1 christos }
279 1.1 christos
280 1.1 christos /*
281 1.1 christos * We use the QUIC stack in non-blocking mode so that we can react to
282 1.1 christos * incoming data on different streams, and e.g. incoming streams initiated
283 1.1 christos * by a server, as and when events occur.
284 1.1 christos */
285 1.1 christos BIO_set_nbio(conn->qconn_bio, 1);
286 1.1 christos
287 1.1 christos /*
288 1.1 christos * Disable default stream mode and create all streams explicitly. Each QUIC
289 1.1 christos * stream will be represented by its own QUIC stream SSL object (QSSO). This
290 1.1 christos * also automatically enables us to accept incoming streams (see
291 1.1 christos * SSL_set_incoming_stream_policy(3)).
292 1.1 christos */
293 1.1 christos if (!SSL_set_default_stream_mode(conn->qconn, SSL_DEFAULT_STREAM_MODE_NONE)) {
294 1.1 christos ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,
295 1.1 christos "failed to configure default stream mode");
296 1.1 christos goto err;
297 1.1 christos }
298 1.1 christos
299 1.1 christos /*
300 1.1 christos * HTTP/3 requires a couple of unidirectional management streams: a control
301 1.1 christos * stream and some QPACK state management streams for each side of a
302 1.1 christos * connection. These are the instances on our side (with us sending); the
303 1.1 christos * server will also create its own equivalent unidirectional streams on its
304 1.1 christos * side, which we handle subsequently as they come in (see SSL_accept_stream
305 1.1 christos * in the event handling code below).
306 1.1 christos */
307 1.1 christos if ((s_ctl_send
308 1.1 christos = h3_conn_create_stream(conn, OSSL_DEMO_H3_STREAM_TYPE_CTRL_SEND)) == NULL)
309 1.1 christos goto err;
310 1.1 christos
311 1.1 christos if ((s_qpenc_send
312 1.1 christos = h3_conn_create_stream(conn, OSSL_DEMO_H3_STREAM_TYPE_QPACK_ENC_SEND)) == NULL)
313 1.1 christos goto err;
314 1.1 christos
315 1.1 christos if ((s_qpdec_send
316 1.1 christos = h3_conn_create_stream(conn, OSSL_DEMO_H3_STREAM_TYPE_QPACK_DEC_SEND)) == NULL)
317 1.1 christos goto err;
318 1.1 christos
319 1.1 christos if (settings == NULL) {
320 1.1 christos nghttp3_settings_default(&dsettings);
321 1.1 christos settings = &dsettings;
322 1.1 christos }
323 1.1 christos
324 1.1 christos if (callbacks != NULL)
325 1.1 christos intl_callbacks = *callbacks;
326 1.1 christos
327 1.1 christos /*
328 1.1 christos * We need to do some of our own processing when many of these events occur,
329 1.1 christos * so we note the original callback functions and forward appropriately.
330 1.1 christos */
331 1.1 christos conn->recv_data_cb = intl_callbacks.recv_data;
332 1.1 christos conn->stream_close_cb = intl_callbacks.stream_close;
333 1.1 christos conn->stop_sending_cb = intl_callbacks.stop_sending;
334 1.1 christos conn->reset_stream_cb = intl_callbacks.reset_stream;
335 1.1 christos conn->deferred_consume_cb = intl_callbacks.deferred_consume;
336 1.1 christos
337 1.1 christos intl_callbacks.recv_data = h3_conn_recv_data;
338 1.1 christos intl_callbacks.stream_close = h3_conn_stream_close;
339 1.1 christos intl_callbacks.stop_sending = h3_conn_stop_sending;
340 1.1 christos intl_callbacks.reset_stream = h3_conn_reset_stream;
341 1.1 christos intl_callbacks.deferred_consume = h3_conn_deferred_consume;
342 1.1 christos
343 1.1 christos /* Create the HTTP/3 client state. */
344 1.1 christos ec = nghttp3_conn_client_new(&conn->h3conn, &intl_callbacks, settings,
345 1.1 christos NULL, conn);
346 1.1 christos if (ec < 0) {
347 1.1 christos ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,
348 1.1 christos "cannot create nghttp3 connection: %s (%d)",
349 1.1 christos nghttp3_strerror(ec), ec);
350 1.1 christos goto err;
351 1.1 christos }
352 1.1 christos
353 1.1 christos /*
354 1.1 christos * Tell the HTTP/3 stack which stream IDs are used for our outgoing control
355 1.1 christos * and QPACK streams. Note that we don't have to tell the HTTP/3 stack what
356 1.1 christos * IDs are used for incoming streams as this is inferred automatically from
357 1.1 christos * the stream type byte which starts every incoming unidirectional stream,
358 1.1 christos * so it will autodetect the correct stream IDs for the incoming control and
359 1.1 christos * QPACK streams initiated by the server.
360 1.1 christos */
361 1.1 christos ec = nghttp3_conn_bind_control_stream(conn->h3conn, s_ctl_send->id);
362 1.1 christos if (ec < 0) {
363 1.1 christos ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,
364 1.1 christos "cannot bind nghttp3 control stream: %s (%d)",
365 1.1 christos nghttp3_strerror(ec), ec);
366 1.1 christos goto err;
367 1.1 christos }
368 1.1 christos
369 1.1 christos ec = nghttp3_conn_bind_qpack_streams(conn->h3conn,
370 1.1 christos s_qpenc_send->id,
371 1.1 christos s_qpdec_send->id);
372 1.1 christos if (ec < 0) {
373 1.1 christos ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,
374 1.1 christos "cannot bind nghttp3 QPACK streams: %s (%d)",
375 1.1 christos nghttp3_strerror(ec), ec);
376 1.1 christos goto err;
377 1.1 christos }
378 1.1 christos
379 1.1 christos return conn;
380 1.1 christos
381 1.1 christos err:
382 1.1 christos nghttp3_conn_del(conn->h3conn);
383 1.1 christos h3_stream_free(s_ctl_send);
384 1.1 christos h3_stream_free(s_qpenc_send);
385 1.1 christos h3_stream_free(s_qpdec_send);
386 1.1 christos lh_OSSL_DEMO_H3_STREAM_free(conn->streams);
387 1.1 christos OPENSSL_free(conn);
388 1.1 christos return NULL;
389 1.1 christos }
390 1.1 christos
391 1.1 christos OSSL_DEMO_H3_CONN *OSSL_DEMO_H3_CONN_new_for_addr(SSL_CTX *ctx, const char *addr,
392 1.1 christos const nghttp3_callbacks *callbacks,
393 1.1 christos const nghttp3_settings *settings,
394 1.1 christos void *user_data)
395 1.1 christos {
396 1.1 christos BIO *qconn_bio = NULL;
397 1.1 christos SSL *qconn = NULL;
398 1.1 christos OSSL_DEMO_H3_CONN *conn = NULL;
399 1.1 christos const char *bare_hostname;
400 1.1 christos
401 1.1 christos /* QUIC connection setup */
402 1.1 christos if ((qconn_bio = BIO_new_ssl_connect(ctx)) == NULL)
403 1.1 christos goto err;
404 1.1 christos
405 1.1 christos /* Pass the 'hostname:port' string into the ssl_connect BIO. */
406 1.1 christos if (BIO_set_conn_hostname(qconn_bio, addr) == 0)
407 1.1 christos goto err;
408 1.1 christos
409 1.1 christos /*
410 1.1 christos * Get the 'bare' hostname out of the ssl_connect BIO. This is the hostname
411 1.1 christos * without the port.
412 1.1 christos */
413 1.1 christos bare_hostname = BIO_get_conn_hostname(qconn_bio);
414 1.1 christos if (bare_hostname == NULL)
415 1.1 christos goto err;
416 1.1 christos
417 1.1 christos if (BIO_get_ssl(qconn_bio, &qconn) == 0)
418 1.1 christos goto err;
419 1.1 christos
420 1.1 christos /* Set the hostname we will validate the X.509 certificate against. */
421 1.1 christos if (SSL_set1_host(qconn, bare_hostname) <= 0)
422 1.1 christos goto err;
423 1.1 christos
424 1.1 christos /* Configure SNI */
425 1.1 christos if (!SSL_set_tlsext_host_name(qconn, bare_hostname))
426 1.1 christos goto err;
427 1.1 christos
428 1.1 christos conn = OSSL_DEMO_H3_CONN_new_for_conn(qconn_bio, callbacks,
429 1.1 christos settings, user_data);
430 1.1 christos if (conn == NULL)
431 1.1 christos goto err;
432 1.1 christos
433 1.1 christos return conn;
434 1.1 christos
435 1.1 christos err:
436 1.1 christos BIO_free_all(qconn_bio);
437 1.1 christos return NULL;
438 1.1 christos }
439 1.1 christos
440 1.1 christos int OSSL_DEMO_H3_CONN_connect(OSSL_DEMO_H3_CONN *conn)
441 1.1 christos {
442 1.1 christos return SSL_connect(OSSL_DEMO_H3_CONN_get0_connection(conn));
443 1.1 christos }
444 1.1 christos
445 1.1 christos void *OSSL_DEMO_H3_CONN_get_user_data(const OSSL_DEMO_H3_CONN *conn)
446 1.1 christos {
447 1.1 christos return conn->user_data;
448 1.1 christos }
449 1.1 christos
450 1.1 christos SSL *OSSL_DEMO_H3_CONN_get0_connection(const OSSL_DEMO_H3_CONN *conn)
451 1.1 christos {
452 1.1 christos return conn->qconn;
453 1.1 christos }
454 1.1 christos
455 1.1 christos /* Pumps received data to the HTTP/3 stack for a single stream. */
456 1.1 christos static void h3_conn_pump_stream(OSSL_DEMO_H3_STREAM *s, void *conn_)
457 1.1 christos {
458 1.1 christos int ec;
459 1.1 christos OSSL_DEMO_H3_CONN *conn = conn_;
460 1.1 christos size_t num_bytes, consumed;
461 1.1 christos uint64_t aec;
462 1.1 christos
463 1.1 christos if (!conn->pump_res)
464 1.1 christos /*
465 1.1 christos * Handling of a previous stream in the iteration over all streams
466 1.1 christos * failed, so just do nothing.
467 1.1 christos */
468 1.1 christos return;
469 1.1 christos
470 1.1 christos for (;;) {
471 1.1 christos if (s->s == NULL /* If we already did STOP_SENDING, ignore this stream. */
472 1.1 christos /* If this is a write-only stream, there is no read data to check. */
473 1.1 christos || SSL_get_stream_read_state(s->s) == SSL_STREAM_STATE_WRONG_DIR
474 1.1 christos /*
475 1.1 christos * If we already got a FIN for this stream, there is nothing more to
476 1.1 christos * do for it.
477 1.1 christos */
478 1.1 christos || s->done_recv_fin)
479 1.1 christos break;
480 1.1 christos
481 1.1 christos /*
482 1.1 christos * Pump data from OpenSSL QUIC to the HTTP/3 stack by calling SSL_peek
483 1.1 christos * to get received data and passing it to nghttp3 using
484 1.1 christos * nghttp3_conn_read_stream. Note that this function is confusingly
485 1.1 christos * named and inputs data to the HTTP/3 stack.
486 1.1 christos */
487 1.1 christos if (s->buf_cur == s->buf_total) {
488 1.1 christos /* Need more data. */
489 1.1 christos ec = SSL_read_ex(s->s, s->buf, sizeof(s->buf), &num_bytes);
490 1.1 christos if (ec <= 0) {
491 1.1 christos num_bytes = 0;
492 1.1 christos if (SSL_get_error(s->s, ec) == SSL_ERROR_ZERO_RETURN) {
493 1.1 christos /* Stream concluded normally. Pass FIN to HTTP/3 stack. */
494 1.1 christos ec = nghttp3_conn_read_stream(conn->h3conn, s->id, NULL, 0,
495 1.1 christos /*fin=*/1);
496 1.1 christos if (ec < 0) {
497 1.1 christos ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,
498 1.1 christos "cannot pass FIN to nghttp3: %s (%d)",
499 1.1 christos nghttp3_strerror(ec), ec);
500 1.1 christos goto err;
501 1.1 christos }
502 1.1 christos
503 1.1 christos s->done_recv_fin = 1;
504 1.1 christos } else if (SSL_get_stream_read_state(s->s)
505 1.1 christos == SSL_STREAM_STATE_RESET_REMOTE) {
506 1.1 christos /* Stream was reset by peer. */
507 1.1 christos if (!SSL_get_stream_read_error_code(s->s, &aec))
508 1.1 christos goto err;
509 1.1 christos
510 1.1 christos ec = nghttp3_conn_close_stream(conn->h3conn, s->id, aec);
511 1.1 christos if (ec < 0) {
512 1.1 christos ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,
513 1.1 christos "cannot mark stream as reset: %s (%d)",
514 1.1 christos nghttp3_strerror(ec), ec);
515 1.1 christos goto err;
516 1.1 christos }
517 1.1 christos
518 1.1 christos s->done_recv_fin = 1;
519 1.1 christos } else {
520 1.1 christos /* Other error. */
521 1.1 christos goto err;
522 1.1 christos }
523 1.1 christos }
524 1.1 christos
525 1.1 christos s->buf_cur = 0;
526 1.1 christos s->buf_total = num_bytes;
527 1.1 christos }
528 1.1 christos
529 1.1 christos if (s->buf_cur == s->buf_total)
530 1.1 christos break;
531 1.1 christos
532 1.1 christos /*
533 1.1 christos * This function is confusingly named as it is is named from nghttp3's
534 1.1 christos * 'perspective'; it is used to pass data *into* the HTTP/3 stack which
535 1.1 christos * has been received from the network.
536 1.1 christos */
537 1.1 christos assert(conn->consumed_app_data == 0);
538 1.1 christos ec = nghttp3_conn_read_stream(conn->h3conn, s->id, s->buf + s->buf_cur,
539 1.1 christos s->buf_total - s->buf_cur, /*fin=*/0);
540 1.1 christos if (ec < 0) {
541 1.1 christos ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,
542 1.1 christos "nghttp3 failed to process incoming data: %s (%d)",
543 1.1 christos nghttp3_strerror(ec), ec);
544 1.1 christos goto err;
545 1.1 christos }
546 1.1 christos
547 1.1 christos /*
548 1.1 christos * read_stream reports the data it consumes from us in two different
549 1.1 christos * ways; the non-application data is returned as a number of bytes 'ec'
550 1.1 christos * above, but the number of bytes of application data has to be recorded
551 1.1 christos * by our callback. We sum the two to determine the total number of
552 1.1 christos * bytes which nghttp3 consumed.
553 1.1 christos */
554 1.1 christos consumed = ec + conn->consumed_app_data;
555 1.1 christos assert(consumed <= s->buf_total - s->buf_cur);
556 1.1 christos s->buf_cur += consumed;
557 1.1 christos conn->consumed_app_data = 0;
558 1.1 christos }
559 1.1 christos
560 1.1 christos return;
561 1.1 christos err:
562 1.1 christos conn->pump_res = 0;
563 1.1 christos }
564 1.1 christos
565 1.1 christos int OSSL_DEMO_H3_CONN_handle_events(OSSL_DEMO_H3_CONN *conn)
566 1.1 christos {
567 1.1 christos int ec, fin;
568 1.1 christos size_t i, num_vecs, written, total_written, total_len;
569 1.1 christos int64_t stream_id;
570 1.1 christos uint64_t flags;
571 1.1 christos nghttp3_vec vecs[8] = {0};
572 1.1 christos OSSL_DEMO_H3_STREAM key, *s;
573 1.1 christos SSL *snew;
574 1.1 christos
575 1.1 christos if (conn == NULL)
576 1.1 christos return 0;
577 1.1 christos
578 1.1 christos /*
579 1.1 christos * We handle events by doing three things:
580 1.1 christos *
581 1.1 christos * 1. Handle new incoming streams
582 1.1 christos * 2. Pump outgoing data from the HTTP/3 stack to the QUIC engine
583 1.1 christos * 3. Pump incoming data from the QUIC engine to the HTTP/3 stack
584 1.1 christos */
585 1.1 christos
586 1.1 christos /* 1. Check for new incoming streams */
587 1.1 christos for (;;) {
588 1.1 christos if ((snew = SSL_accept_stream(conn->qconn, SSL_ACCEPT_STREAM_NO_BLOCK)) == NULL)
589 1.1 christos break;
590 1.1 christos
591 1.1 christos /*
592 1.1 christos * Each new incoming stream gets wrapped into an OSSL_DEMO_H3_STREAM object and
593 1.1 christos * added into our stream ID map.
594 1.1 christos */
595 1.1 christos if (h3_conn_accept_stream(conn, snew) == NULL) {
596 1.1 christos SSL_free(snew);
597 1.1 christos return 0;
598 1.1 christos }
599 1.1 christos }
600 1.1 christos
601 1.1 christos /* 2. Pump outgoing data from HTTP/3 engine to QUIC. */
602 1.1 christos for (;;) {
603 1.1 christos /*
604 1.1 christos * Get a number of send vectors from the HTTP/3 engine.
605 1.1 christos *
606 1.1 christos * Note that this function is confusingly named as it is named from
607 1.1 christos * nghttp3's 'perspective': this outputs pointers to data which nghttp3
608 1.1 christos * wants to *write* to the network.
609 1.1 christos */
610 1.1 christos ec = nghttp3_conn_writev_stream(conn->h3conn, &stream_id, &fin,
611 1.1 christos vecs, ARRAY_LEN(vecs));
612 1.1 christos if (ec < 0)
613 1.1 christos return 0;
614 1.1 christos if (ec == 0)
615 1.1 christos break;
616 1.1 christos
617 1.1 christos /*
618 1.1 christos * we let SSL_write_ex2(3) to conclude the stream for us (send FIN)
619 1.1 christos * after all data are written.
620 1.1 christos */
621 1.1 christos flags = (fin == 0) ? 0 : SSL_WRITE_FLAG_CONCLUDE;
622 1.1 christos
623 1.1 christos /* For each of the vectors returned, pass it to OpenSSL QUIC. */
624 1.1 christos key.id = stream_id;
625 1.1 christos if ((s = lh_OSSL_DEMO_H3_STREAM_retrieve(conn->streams, &key)) == NULL) {
626 1.1 christos ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,
627 1.1 christos "no stream for ID %zd", stream_id);
628 1.1 christos return 0;
629 1.1 christos }
630 1.1 christos
631 1.1 christos num_vecs = ec;
632 1.1 christos total_len = nghttp3_vec_len(vecs, num_vecs);
633 1.1 christos total_written = 0;
634 1.1 christos for (i = 0; i < num_vecs; ++i) {
635 1.1 christos if (vecs[i].len == 0)
636 1.1 christos continue;
637 1.1 christos
638 1.1 christos if (s->s == NULL) {
639 1.1 christos /* Already did STOP_SENDING and threw away stream, ignore */
640 1.1 christos written = vecs[i].len;
641 1.1 christos } else if (!SSL_write_ex2(s->s, vecs[i].base, vecs[i].len, flags, &written)) {
642 1.1 christos if (SSL_get_error(s->s, 0) == SSL_ERROR_WANT_WRITE) {
643 1.1 christos /*
644 1.1 christos * We have filled our send buffer so tell nghttp3 to stop
645 1.1 christos * generating more data; we have to do this explicitly.
646 1.1 christos */
647 1.1 christos written = 0;
648 1.1 christos nghttp3_conn_block_stream(conn->h3conn, stream_id);
649 1.1 christos } else {
650 1.1 christos ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,
651 1.1 christos "writing HTTP/3 data to network failed");
652 1.1 christos return 0;
653 1.1 christos }
654 1.1 christos } else {
655 1.1 christos /*
656 1.1 christos * Tell nghttp3 it can resume generating more data in case we
657 1.1 christos * previously called block_stream.
658 1.1 christos */
659 1.1 christos nghttp3_conn_unblock_stream(conn->h3conn, stream_id);
660 1.1 christos }
661 1.1 christos
662 1.1 christos total_written += written;
663 1.1 christos if (written > 0) {
664 1.1 christos /*
665 1.1 christos * Tell nghttp3 we have consumed the data it output when we
666 1.1 christos * called writev_stream, otherwise subsequent calls to
667 1.1 christos * writev_stream will output the same data.
668 1.1 christos */
669 1.1 christos ec = nghttp3_conn_add_write_offset(conn->h3conn, stream_id, written);
670 1.1 christos if (ec < 0)
671 1.1 christos return 0;
672 1.1 christos
673 1.1 christos /*
674 1.1 christos * Tell nghttp3 it can free the buffered data because we will
675 1.1 christos * not need it again. In our case we can always do this right
676 1.1 christos * away because we copy the data into our QUIC send buffers
677 1.1 christos * rather than simply storing a reference to it.
678 1.1 christos */
679 1.1 christos ec = nghttp3_conn_add_ack_offset(conn->h3conn, stream_id, written);
680 1.1 christos if (ec < 0)
681 1.1 christos return 0;
682 1.1 christos }
683 1.1 christos }
684 1.1 christos
685 1.1 christos if (fin && total_written == total_len) {
686 1.1 christos
687 1.1 christos if (total_len == 0) {
688 1.1 christos /*
689 1.1 christos * As a special case, if nghttp3 requested to write a
690 1.1 christos * zero-length stream with a FIN, we have to tell it we did this
691 1.1 christos * by calling add_write_offset(0).
692 1.1 christos */
693 1.1 christos ec = nghttp3_conn_add_write_offset(conn->h3conn, stream_id, 0);
694 1.1 christos if (ec < 0)
695 1.1 christos return 0;
696 1.1 christos }
697 1.1 christos }
698 1.1 christos }
699 1.1 christos
700 1.1 christos /* 3. Pump incoming data from QUIC to HTTP/3 engine. */
701 1.1 christos conn->pump_res = 1; /* cleared in below call if an error occurs */
702 1.1 christos lh_OSSL_DEMO_H3_STREAM_doall_arg(conn->streams, h3_conn_pump_stream, conn);
703 1.1 christos if (!conn->pump_res)
704 1.1 christos return 0;
705 1.1 christos
706 1.1 christos return 1;
707 1.1 christos }
708 1.1 christos
709 1.1 christos int OSSL_DEMO_H3_CONN_submit_request(OSSL_DEMO_H3_CONN *conn,
710 1.1 christos const nghttp3_nv *nva, size_t nvlen,
711 1.1 christos const nghttp3_data_reader *dr,
712 1.1 christos void *user_data)
713 1.1 christos {
714 1.1 christos int ec;
715 1.1 christos OSSL_DEMO_H3_STREAM *s_req = NULL;
716 1.1 christos
717 1.1 christos if (conn == NULL) {
718 1.1 christos ERR_raise_data(ERR_LIB_USER, ERR_R_PASSED_NULL_PARAMETER,
719 1.1 christos "connection must be specified");
720 1.1 christos return 0;
721 1.1 christos }
722 1.1 christos
723 1.1 christos /* Each HTTP/3 request is represented by a stream. */
724 1.1 christos if ((s_req = h3_conn_create_stream(conn, OSSL_DEMO_H3_STREAM_TYPE_REQ)) == NULL)
725 1.1 christos goto err;
726 1.1 christos
727 1.1 christos s_req->user_data = user_data;
728 1.1 christos
729 1.1 christos ec = nghttp3_conn_submit_request(conn->h3conn, s_req->id, nva, nvlen,
730 1.1 christos dr, s_req);
731 1.1 christos if (ec < 0) {
732 1.1 christos ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,
733 1.1 christos "cannot submit HTTP/3 request: %s (%d)",
734 1.1 christos nghttp3_strerror(ec), ec);
735 1.1 christos goto err;
736 1.1 christos }
737 1.1 christos
738 1.1 christos return 1;
739 1.1 christos
740 1.1 christos err:
741 1.1 christos h3_conn_remove_stream(conn, s_req);
742 1.1 christos return 0;
743 1.1 christos }
744