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