xref: /freebsd/crypto/openssl/demos/http3/ossl-nghttp3.c (revision f25b8c9fb4f58cf61adb47d7570abe7caa6d385d)
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