Home | History | Annotate | Line # | Download | only in http3
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