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