Path: blob/main/crypto/openssl/demos/http3/ossl-nghttp3.c
39536 views
/*1* Copyright 2023-2024 The OpenSSL Project Authors. All Rights Reserved.2*3* Licensed under the Apache License 2.0 (the "License"). You may not use4* this file except in compliance with the License. You can obtain a copy5* in the file LICENSE in the source distribution or at6* https://www.openssl.org/source/license.html7*/8#include "ossl-nghttp3.h"9#include <openssl/err.h>10#include <assert.h>1112#define ARRAY_LEN(x) (sizeof(x)/sizeof((x)[0]))1314enum {15OSSL_DEMO_H3_STREAM_TYPE_CTRL_SEND,16OSSL_DEMO_H3_STREAM_TYPE_QPACK_ENC_SEND,17OSSL_DEMO_H3_STREAM_TYPE_QPACK_DEC_SEND,18OSSL_DEMO_H3_STREAM_TYPE_REQ,19};2021#define BUF_SIZE 40962223struct ossl_demo_h3_stream_st {24uint64_t id; /* QUIC stream ID */25SSL *s; /* QUIC stream SSL object */26int done_recv_fin; /* Received FIN */27void *user_data;2829uint8_t buf[BUF_SIZE];30size_t buf_cur, buf_total;31};3233DEFINE_LHASH_OF_EX(OSSL_DEMO_H3_STREAM);3435static void h3_stream_free(OSSL_DEMO_H3_STREAM *s)36{37if (s == NULL)38return;3940SSL_free(s->s);41OPENSSL_free(s);42}4344static unsigned long h3_stream_hash(const OSSL_DEMO_H3_STREAM *s)45{46return (unsigned long)s->id;47}4849static int h3_stream_eq(const OSSL_DEMO_H3_STREAM *a, const OSSL_DEMO_H3_STREAM *b)50{51if (a->id < b->id) return -1;52if (a->id > b->id) return 1;53return 0;54}5556void *OSSL_DEMO_H3_STREAM_get_user_data(const OSSL_DEMO_H3_STREAM *s)57{58return s->user_data;59}6061struct ossl_demo_h3_conn_st {62/* QUIC connection SSL object */63SSL *qconn;64/* BIO wrapping QCSO */65BIO *qconn_bio;66/* HTTP/3 connection object */67nghttp3_conn *h3conn;68/* map of stream IDs to OSSL_DEMO_H3_STREAMs */69LHASH_OF(OSSL_DEMO_H3_STREAM) *streams;70/* opaque user data pointer */71void *user_data;7273int pump_res;74size_t consumed_app_data;7576/* Forwarding callbacks */77nghttp3_recv_data recv_data_cb;78nghttp3_stream_close stream_close_cb;79nghttp3_stop_sending stop_sending_cb;80nghttp3_reset_stream reset_stream_cb;81nghttp3_deferred_consume deferred_consume_cb;82};8384void OSSL_DEMO_H3_CONN_free(OSSL_DEMO_H3_CONN *conn)85{86if (conn == NULL)87return;8889lh_OSSL_DEMO_H3_STREAM_doall(conn->streams, h3_stream_free);9091nghttp3_conn_del(conn->h3conn);92BIO_free_all(conn->qconn_bio);93lh_OSSL_DEMO_H3_STREAM_free(conn->streams);94OPENSSL_free(conn);95}9697static OSSL_DEMO_H3_STREAM *h3_conn_create_stream(OSSL_DEMO_H3_CONN *conn, int type)98{99OSSL_DEMO_H3_STREAM *s;100uint64_t flags = SSL_STREAM_FLAG_ADVANCE;101102if ((s = OPENSSL_zalloc(sizeof(OSSL_DEMO_H3_STREAM))) == NULL)103return NULL;104105if (type != OSSL_DEMO_H3_STREAM_TYPE_REQ)106flags |= SSL_STREAM_FLAG_UNI;107108if ((s->s = SSL_new_stream(conn->qconn, flags)) == NULL) {109ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,110"could not create QUIC stream object");111goto err;112}113114s->id = SSL_get_stream_id(s->s);115lh_OSSL_DEMO_H3_STREAM_insert(conn->streams, s);116return s;117118err:119OPENSSL_free(s);120return NULL;121}122123static OSSL_DEMO_H3_STREAM *h3_conn_accept_stream(OSSL_DEMO_H3_CONN *conn, SSL *qstream)124{125OSSL_DEMO_H3_STREAM *s;126127if ((s = OPENSSL_zalloc(sizeof(OSSL_DEMO_H3_STREAM))) == NULL)128return NULL;129130s->id = SSL_get_stream_id(qstream);131s->s = qstream;132lh_OSSL_DEMO_H3_STREAM_insert(conn->streams, s);133return s;134}135136static void h3_conn_remove_stream(OSSL_DEMO_H3_CONN *conn, OSSL_DEMO_H3_STREAM *s)137{138if (s == NULL)139return;140141lh_OSSL_DEMO_H3_STREAM_delete(conn->streams, s);142h3_stream_free(s);143}144145static int h3_conn_recv_data(nghttp3_conn *h3conn, int64_t stream_id,146const uint8_t *data, size_t datalen,147void *conn_user_data, void *stream_user_data)148{149OSSL_DEMO_H3_CONN *conn = conn_user_data;150151conn->consumed_app_data += datalen;152if (conn->recv_data_cb == NULL)153return 0;154155return conn->recv_data_cb(h3conn, stream_id, data, datalen,156conn_user_data, stream_user_data);157}158159static int h3_conn_stream_close(nghttp3_conn *h3conn, int64_t stream_id,160uint64_t app_error_code,161void *conn_user_data, void *stream_user_data)162{163int ret = 0;164OSSL_DEMO_H3_CONN *conn = conn_user_data;165OSSL_DEMO_H3_STREAM *stream = stream_user_data;166167if (conn->stream_close_cb != NULL)168ret = conn->stream_close_cb(h3conn, stream_id, app_error_code,169conn_user_data, stream_user_data);170171h3_conn_remove_stream(conn, stream);172return ret;173}174175static int h3_conn_stop_sending(nghttp3_conn *h3conn, int64_t stream_id,176uint64_t app_error_code,177void *conn_user_data, void *stream_user_data)178{179int ret = 0;180OSSL_DEMO_H3_CONN *conn = conn_user_data;181OSSL_DEMO_H3_STREAM *stream = stream_user_data;182183if (conn->stop_sending_cb != NULL)184ret = conn->stop_sending_cb(h3conn, stream_id, app_error_code,185conn_user_data, stream_user_data);186187SSL_free(stream->s);188stream->s = NULL;189return ret;190}191192static int h3_conn_reset_stream(nghttp3_conn *h3conn, int64_t stream_id,193uint64_t app_error_code,194void *conn_user_data, void *stream_user_data)195{196int ret = 0;197OSSL_DEMO_H3_CONN *conn = conn_user_data;198OSSL_DEMO_H3_STREAM *stream = stream_user_data;199SSL_STREAM_RESET_ARGS args = {0};200201if (conn->reset_stream_cb != NULL)202ret = conn->reset_stream_cb(h3conn, stream_id, app_error_code,203conn_user_data, stream_user_data);204205if (stream->s != NULL) {206args.quic_error_code = app_error_code;207208if (!SSL_stream_reset(stream->s, &args, sizeof(args)))209return 1;210}211212return ret;213}214215static int h3_conn_deferred_consume(nghttp3_conn *h3conn, int64_t stream_id,216size_t consumed,217void *conn_user_data, void *stream_user_data)218{219int ret = 0;220OSSL_DEMO_H3_CONN *conn = conn_user_data;221222if (conn->deferred_consume_cb != NULL)223ret = conn->deferred_consume_cb(h3conn, stream_id, consumed,224conn_user_data, stream_user_data);225226conn->consumed_app_data += consumed;227return ret;228}229230OSSL_DEMO_H3_CONN *OSSL_DEMO_H3_CONN_new_for_conn(BIO *qconn_bio,231const nghttp3_callbacks *callbacks,232const nghttp3_settings *settings,233void *user_data)234{235int ec;236OSSL_DEMO_H3_CONN *conn;237OSSL_DEMO_H3_STREAM *s_ctl_send = NULL;238OSSL_DEMO_H3_STREAM *s_qpenc_send = NULL;239OSSL_DEMO_H3_STREAM *s_qpdec_send = NULL;240nghttp3_settings dsettings = {0};241nghttp3_callbacks intl_callbacks = {0};242static const unsigned char alpn[] = {2, 'h', '3'};243244if (qconn_bio == NULL) {245ERR_raise_data(ERR_LIB_USER, ERR_R_PASSED_NULL_PARAMETER,246"QUIC connection BIO must be provided");247return NULL;248}249250if ((conn = OPENSSL_zalloc(sizeof(OSSL_DEMO_H3_CONN))) == NULL)251return NULL;252253conn->qconn_bio = qconn_bio;254conn->user_data = user_data;255256if (BIO_get_ssl(qconn_bio, &conn->qconn) == 0) {257ERR_raise_data(ERR_LIB_USER, ERR_R_PASSED_INVALID_ARGUMENT,258"BIO must be an SSL BIO");259goto err;260}261262/* Create the map of stream IDs to OSSL_DEMO_H3_STREAM structures. */263if ((conn->streams = lh_OSSL_DEMO_H3_STREAM_new(h3_stream_hash, h3_stream_eq)) == NULL)264goto err;265266/*267* If the application has not started connecting yet, helpfully268* auto-configure ALPN. If the application wants to initiate the connection269* itself, it must take care of this itself.270*/271if (SSL_in_before(conn->qconn))272if (SSL_set_alpn_protos(conn->qconn, alpn, sizeof(alpn))) {273/* SSL_set_alpn_protos returns 1 on failure */274ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,275"failed to configure ALPN");276goto err;277}278279/*280* We use the QUIC stack in non-blocking mode so that we can react to281* incoming data on different streams, and e.g. incoming streams initiated282* by a server, as and when events occur.283*/284BIO_set_nbio(conn->qconn_bio, 1);285286/*287* Disable default stream mode and create all streams explicitly. Each QUIC288* stream will be represented by its own QUIC stream SSL object (QSSO). This289* also automatically enables us to accept incoming streams (see290* SSL_set_incoming_stream_policy(3)).291*/292if (!SSL_set_default_stream_mode(conn->qconn, SSL_DEFAULT_STREAM_MODE_NONE)) {293ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,294"failed to configure default stream mode");295goto err;296}297298/*299* HTTP/3 requires a couple of unidirectional management streams: a control300* stream and some QPACK state management streams for each side of a301* connection. These are the instances on our side (with us sending); the302* server will also create its own equivalent unidirectional streams on its303* side, which we handle subsequently as they come in (see SSL_accept_stream304* in the event handling code below).305*/306if ((s_ctl_send307= h3_conn_create_stream(conn, OSSL_DEMO_H3_STREAM_TYPE_CTRL_SEND)) == NULL)308goto err;309310if ((s_qpenc_send311= h3_conn_create_stream(conn, OSSL_DEMO_H3_STREAM_TYPE_QPACK_ENC_SEND)) == NULL)312goto err;313314if ((s_qpdec_send315= h3_conn_create_stream(conn, OSSL_DEMO_H3_STREAM_TYPE_QPACK_DEC_SEND)) == NULL)316goto err;317318if (settings == NULL) {319nghttp3_settings_default(&dsettings);320settings = &dsettings;321}322323if (callbacks != NULL)324intl_callbacks = *callbacks;325326/*327* We need to do some of our own processing when many of these events occur,328* so we note the original callback functions and forward appropriately.329*/330conn->recv_data_cb = intl_callbacks.recv_data;331conn->stream_close_cb = intl_callbacks.stream_close;332conn->stop_sending_cb = intl_callbacks.stop_sending;333conn->reset_stream_cb = intl_callbacks.reset_stream;334conn->deferred_consume_cb = intl_callbacks.deferred_consume;335336intl_callbacks.recv_data = h3_conn_recv_data;337intl_callbacks.stream_close = h3_conn_stream_close;338intl_callbacks.stop_sending = h3_conn_stop_sending;339intl_callbacks.reset_stream = h3_conn_reset_stream;340intl_callbacks.deferred_consume = h3_conn_deferred_consume;341342/* Create the HTTP/3 client state. */343ec = nghttp3_conn_client_new(&conn->h3conn, &intl_callbacks, settings,344NULL, conn);345if (ec < 0) {346ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,347"cannot create nghttp3 connection: %s (%d)",348nghttp3_strerror(ec), ec);349goto err;350}351352/*353* Tell the HTTP/3 stack which stream IDs are used for our outgoing control354* and QPACK streams. Note that we don't have to tell the HTTP/3 stack what355* IDs are used for incoming streams as this is inferred automatically from356* the stream type byte which starts every incoming unidirectional stream,357* so it will autodetect the correct stream IDs for the incoming control and358* QPACK streams initiated by the server.359*/360ec = nghttp3_conn_bind_control_stream(conn->h3conn, s_ctl_send->id);361if (ec < 0) {362ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,363"cannot bind nghttp3 control stream: %s (%d)",364nghttp3_strerror(ec), ec);365goto err;366}367368ec = nghttp3_conn_bind_qpack_streams(conn->h3conn,369s_qpenc_send->id,370s_qpdec_send->id);371if (ec < 0) {372ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,373"cannot bind nghttp3 QPACK streams: %s (%d)",374nghttp3_strerror(ec), ec);375goto err;376}377378return conn;379380err:381nghttp3_conn_del(conn->h3conn);382h3_stream_free(s_ctl_send);383h3_stream_free(s_qpenc_send);384h3_stream_free(s_qpdec_send);385lh_OSSL_DEMO_H3_STREAM_free(conn->streams);386OPENSSL_free(conn);387return NULL;388}389390OSSL_DEMO_H3_CONN *OSSL_DEMO_H3_CONN_new_for_addr(SSL_CTX *ctx, const char *addr,391const nghttp3_callbacks *callbacks,392const nghttp3_settings *settings,393void *user_data)394{395BIO *qconn_bio = NULL;396SSL *qconn = NULL;397OSSL_DEMO_H3_CONN *conn = NULL;398const char *bare_hostname;399400/* QUIC connection setup */401if ((qconn_bio = BIO_new_ssl_connect(ctx)) == NULL)402goto err;403404/* Pass the 'hostname:port' string into the ssl_connect BIO. */405if (BIO_set_conn_hostname(qconn_bio, addr) == 0)406goto err;407408/*409* Get the 'bare' hostname out of the ssl_connect BIO. This is the hostname410* without the port.411*/412bare_hostname = BIO_get_conn_hostname(qconn_bio);413if (bare_hostname == NULL)414goto err;415416if (BIO_get_ssl(qconn_bio, &qconn) == 0)417goto err;418419/* Set the hostname we will validate the X.509 certificate against. */420if (SSL_set1_host(qconn, bare_hostname) <= 0)421goto err;422423/* Configure SNI */424if (!SSL_set_tlsext_host_name(qconn, bare_hostname))425goto err;426427conn = OSSL_DEMO_H3_CONN_new_for_conn(qconn_bio, callbacks,428settings, user_data);429if (conn == NULL)430goto err;431432return conn;433434err:435BIO_free_all(qconn_bio);436return NULL;437}438439int OSSL_DEMO_H3_CONN_connect(OSSL_DEMO_H3_CONN *conn)440{441return SSL_connect(OSSL_DEMO_H3_CONN_get0_connection(conn));442}443444void *OSSL_DEMO_H3_CONN_get_user_data(const OSSL_DEMO_H3_CONN *conn)445{446return conn->user_data;447}448449SSL *OSSL_DEMO_H3_CONN_get0_connection(const OSSL_DEMO_H3_CONN *conn)450{451return conn->qconn;452}453454/* Pumps received data to the HTTP/3 stack for a single stream. */455static void h3_conn_pump_stream(OSSL_DEMO_H3_STREAM *s, void *conn_)456{457int ec;458OSSL_DEMO_H3_CONN *conn = conn_;459size_t num_bytes, consumed;460uint64_t aec;461462if (!conn->pump_res)463/*464* Handling of a previous stream in the iteration over all streams465* failed, so just do nothing.466*/467return;468469for (;;) {470if (s->s == NULL /* If we already did STOP_SENDING, ignore this stream. */471/* If this is a write-only stream, there is no read data to check. */472|| SSL_get_stream_read_state(s->s) == SSL_STREAM_STATE_WRONG_DIR473/*474* If we already got a FIN for this stream, there is nothing more to475* do for it.476*/477|| s->done_recv_fin)478break;479480/*481* Pump data from OpenSSL QUIC to the HTTP/3 stack by calling SSL_peek482* to get received data and passing it to nghttp3 using483* nghttp3_conn_read_stream. Note that this function is confusingly484* named and inputs data to the HTTP/3 stack.485*/486if (s->buf_cur == s->buf_total) {487/* Need more data. */488ec = SSL_read_ex(s->s, s->buf, sizeof(s->buf), &num_bytes);489if (ec <= 0) {490num_bytes = 0;491if (SSL_get_error(s->s, ec) == SSL_ERROR_ZERO_RETURN) {492/* Stream concluded normally. Pass FIN to HTTP/3 stack. */493ec = nghttp3_conn_read_stream(conn->h3conn, s->id, NULL, 0,494/*fin=*/1);495if (ec < 0) {496ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,497"cannot pass FIN to nghttp3: %s (%d)",498nghttp3_strerror(ec), ec);499goto err;500}501502s->done_recv_fin = 1;503} else if (SSL_get_stream_read_state(s->s)504== SSL_STREAM_STATE_RESET_REMOTE) {505/* Stream was reset by peer. */506if (!SSL_get_stream_read_error_code(s->s, &aec))507goto err;508509ec = nghttp3_conn_close_stream(conn->h3conn, s->id, aec);510if (ec < 0) {511ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,512"cannot mark stream as reset: %s (%d)",513nghttp3_strerror(ec), ec);514goto err;515}516517s->done_recv_fin = 1;518} else {519/* Other error. */520goto err;521}522}523524s->buf_cur = 0;525s->buf_total = num_bytes;526}527528if (s->buf_cur == s->buf_total)529break;530531/*532* This function is confusingly named as it is is named from nghttp3's533* 'perspective'; it is used to pass data *into* the HTTP/3 stack which534* has been received from the network.535*/536assert(conn->consumed_app_data == 0);537ec = nghttp3_conn_read_stream(conn->h3conn, s->id, s->buf + s->buf_cur,538s->buf_total - s->buf_cur, /*fin=*/0);539if (ec < 0) {540ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,541"nghttp3 failed to process incoming data: %s (%d)",542nghttp3_strerror(ec), ec);543goto err;544}545546/*547* read_stream reports the data it consumes from us in two different548* ways; the non-application data is returned as a number of bytes 'ec'549* above, but the number of bytes of application data has to be recorded550* by our callback. We sum the two to determine the total number of551* bytes which nghttp3 consumed.552*/553consumed = ec + conn->consumed_app_data;554assert(consumed <= s->buf_total - s->buf_cur);555s->buf_cur += consumed;556conn->consumed_app_data = 0;557}558559return;560err:561conn->pump_res = 0;562}563564int OSSL_DEMO_H3_CONN_handle_events(OSSL_DEMO_H3_CONN *conn)565{566int ec, fin;567size_t i, num_vecs, written, total_written, total_len;568int64_t stream_id;569uint64_t flags;570nghttp3_vec vecs[8] = {0};571OSSL_DEMO_H3_STREAM key, *s;572SSL *snew;573574if (conn == NULL)575return 0;576577/*578* We handle events by doing three things:579*580* 1. Handle new incoming streams581* 2. Pump outgoing data from the HTTP/3 stack to the QUIC engine582* 3. Pump incoming data from the QUIC engine to the HTTP/3 stack583*/584585/* 1. Check for new incoming streams */586for (;;) {587if ((snew = SSL_accept_stream(conn->qconn, SSL_ACCEPT_STREAM_NO_BLOCK)) == NULL)588break;589590/*591* Each new incoming stream gets wrapped into an OSSL_DEMO_H3_STREAM object and592* added into our stream ID map.593*/594if (h3_conn_accept_stream(conn, snew) == NULL) {595SSL_free(snew);596return 0;597}598}599600/* 2. Pump outgoing data from HTTP/3 engine to QUIC. */601for (;;) {602/*603* Get a number of send vectors from the HTTP/3 engine.604*605* Note that this function is confusingly named as it is named from606* nghttp3's 'perspective': this outputs pointers to data which nghttp3607* wants to *write* to the network.608*/609ec = nghttp3_conn_writev_stream(conn->h3conn, &stream_id, &fin,610vecs, ARRAY_LEN(vecs));611if (ec < 0)612return 0;613if (ec == 0)614break;615616/*617* we let SSL_write_ex2(3) to conclude the stream for us (send FIN)618* after all data are written.619*/620flags = (fin == 0) ? 0 : SSL_WRITE_FLAG_CONCLUDE;621622/* For each of the vectors returned, pass it to OpenSSL QUIC. */623key.id = stream_id;624if ((s = lh_OSSL_DEMO_H3_STREAM_retrieve(conn->streams, &key)) == NULL) {625ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,626"no stream for ID %zd", stream_id);627return 0;628}629630num_vecs = ec;631total_len = nghttp3_vec_len(vecs, num_vecs);632total_written = 0;633for (i = 0; i < num_vecs; ++i) {634if (vecs[i].len == 0)635continue;636637if (s->s == NULL) {638/* Already did STOP_SENDING and threw away stream, ignore */639written = vecs[i].len;640} else if (!SSL_write_ex2(s->s, vecs[i].base, vecs[i].len, flags, &written)) {641if (SSL_get_error(s->s, 0) == SSL_ERROR_WANT_WRITE) {642/*643* We have filled our send buffer so tell nghttp3 to stop644* generating more data; we have to do this explicitly.645*/646written = 0;647nghttp3_conn_block_stream(conn->h3conn, stream_id);648} else {649ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,650"writing HTTP/3 data to network failed");651return 0;652}653} else {654/*655* Tell nghttp3 it can resume generating more data in case we656* previously called block_stream.657*/658nghttp3_conn_unblock_stream(conn->h3conn, stream_id);659}660661total_written += written;662if (written > 0) {663/*664* Tell nghttp3 we have consumed the data it output when we665* called writev_stream, otherwise subsequent calls to666* writev_stream will output the same data.667*/668ec = nghttp3_conn_add_write_offset(conn->h3conn, stream_id, written);669if (ec < 0)670return 0;671672/*673* Tell nghttp3 it can free the buffered data because we will674* not need it again. In our case we can always do this right675* away because we copy the data into our QUIC send buffers676* rather than simply storing a reference to it.677*/678ec = nghttp3_conn_add_ack_offset(conn->h3conn, stream_id, written);679if (ec < 0)680return 0;681}682}683684if (fin && total_written == total_len) {685686if (total_len == 0) {687/*688* As a special case, if nghttp3 requested to write a689* zero-length stream with a FIN, we have to tell it we did this690* by calling add_write_offset(0).691*/692ec = nghttp3_conn_add_write_offset(conn->h3conn, stream_id, 0);693if (ec < 0)694return 0;695}696}697}698699/* 3. Pump incoming data from QUIC to HTTP/3 engine. */700conn->pump_res = 1; /* cleared in below call if an error occurs */701lh_OSSL_DEMO_H3_STREAM_doall_arg(conn->streams, h3_conn_pump_stream, conn);702if (!conn->pump_res)703return 0;704705return 1;706}707708int OSSL_DEMO_H3_CONN_submit_request(OSSL_DEMO_H3_CONN *conn,709const nghttp3_nv *nva, size_t nvlen,710const nghttp3_data_reader *dr,711void *user_data)712{713int ec;714OSSL_DEMO_H3_STREAM *s_req = NULL;715716if (conn == NULL) {717ERR_raise_data(ERR_LIB_USER, ERR_R_PASSED_NULL_PARAMETER,718"connection must be specified");719return 0;720}721722/* Each HTTP/3 request is represented by a stream. */723if ((s_req = h3_conn_create_stream(conn, OSSL_DEMO_H3_STREAM_TYPE_REQ)) == NULL)724goto err;725726s_req->user_data = user_data;727728ec = nghttp3_conn_submit_request(conn->h3conn, s_req->id, nva, nvlen,729dr, s_req);730if (ec < 0) {731ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,732"cannot submit HTTP/3 request: %s (%d)",733nghttp3_strerror(ec), ec);734goto err;735}736737return 1;738739err:740h3_conn_remove_stream(conn, s_req);741return 0;742}743744745