Path: blob/main/crypto/openssl/demos/http3/ossl-nghttp3.c
109446 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)52return -1;53if (a->id > b->id)54return 1;55return 0;56}5758void *OSSL_DEMO_H3_STREAM_get_user_data(const OSSL_DEMO_H3_STREAM *s)59{60return s->user_data;61}6263struct ossl_demo_h3_conn_st {64/* QUIC connection SSL object */65SSL *qconn;66/* BIO wrapping QCSO */67BIO *qconn_bio;68/* HTTP/3 connection object */69nghttp3_conn *h3conn;70/* map of stream IDs to OSSL_DEMO_H3_STREAMs */71LHASH_OF(OSSL_DEMO_H3_STREAM) *streams;72/* opaque user data pointer */73void *user_data;7475int pump_res;76size_t consumed_app_data;7778/* Forwarding callbacks */79nghttp3_recv_data recv_data_cb;80nghttp3_stream_close stream_close_cb;81nghttp3_stop_sending stop_sending_cb;82nghttp3_reset_stream reset_stream_cb;83nghttp3_deferred_consume deferred_consume_cb;84};8586void OSSL_DEMO_H3_CONN_free(OSSL_DEMO_H3_CONN *conn)87{88if (conn == NULL)89return;9091lh_OSSL_DEMO_H3_STREAM_doall(conn->streams, h3_stream_free);9293nghttp3_conn_del(conn->h3conn);94BIO_free_all(conn->qconn_bio);95lh_OSSL_DEMO_H3_STREAM_free(conn->streams);96OPENSSL_free(conn);97}9899static OSSL_DEMO_H3_STREAM *h3_conn_create_stream(OSSL_DEMO_H3_CONN *conn, int type)100{101OSSL_DEMO_H3_STREAM *s;102uint64_t flags = SSL_STREAM_FLAG_ADVANCE;103104if ((s = OPENSSL_zalloc(sizeof(OSSL_DEMO_H3_STREAM))) == NULL)105return NULL;106107if (type != OSSL_DEMO_H3_STREAM_TYPE_REQ)108flags |= SSL_STREAM_FLAG_UNI;109110if ((s->s = SSL_new_stream(conn->qconn, flags)) == NULL) {111ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,112"could not create QUIC stream object");113goto err;114}115116s->id = SSL_get_stream_id(s->s);117lh_OSSL_DEMO_H3_STREAM_insert(conn->streams, s);118return s;119120err:121OPENSSL_free(s);122return NULL;123}124125static OSSL_DEMO_H3_STREAM *h3_conn_accept_stream(OSSL_DEMO_H3_CONN *conn, SSL *qstream)126{127OSSL_DEMO_H3_STREAM *s;128129if ((s = OPENSSL_zalloc(sizeof(OSSL_DEMO_H3_STREAM))) == NULL)130return NULL;131132s->id = SSL_get_stream_id(qstream);133s->s = qstream;134lh_OSSL_DEMO_H3_STREAM_insert(conn->streams, s);135return s;136}137138static void h3_conn_remove_stream(OSSL_DEMO_H3_CONN *conn, OSSL_DEMO_H3_STREAM *s)139{140if (s == NULL)141return;142143lh_OSSL_DEMO_H3_STREAM_delete(conn->streams, s);144h3_stream_free(s);145}146147static int h3_conn_recv_data(nghttp3_conn *h3conn, int64_t stream_id,148const uint8_t *data, size_t datalen,149void *conn_user_data, void *stream_user_data)150{151OSSL_DEMO_H3_CONN *conn = conn_user_data;152153conn->consumed_app_data += datalen;154if (conn->recv_data_cb == NULL)155return 0;156157return conn->recv_data_cb(h3conn, stream_id, data, datalen,158conn_user_data, stream_user_data);159}160161static int h3_conn_stream_close(nghttp3_conn *h3conn, int64_t stream_id,162uint64_t app_error_code,163void *conn_user_data, void *stream_user_data)164{165int ret = 0;166OSSL_DEMO_H3_CONN *conn = conn_user_data;167OSSL_DEMO_H3_STREAM *stream = stream_user_data;168169if (conn->stream_close_cb != NULL)170ret = conn->stream_close_cb(h3conn, stream_id, app_error_code,171conn_user_data, stream_user_data);172173h3_conn_remove_stream(conn, stream);174return ret;175}176177static int h3_conn_stop_sending(nghttp3_conn *h3conn, int64_t stream_id,178uint64_t app_error_code,179void *conn_user_data, void *stream_user_data)180{181int ret = 0;182OSSL_DEMO_H3_CONN *conn = conn_user_data;183OSSL_DEMO_H3_STREAM *stream = stream_user_data;184185if (conn->stop_sending_cb != NULL)186ret = conn->stop_sending_cb(h3conn, stream_id, app_error_code,187conn_user_data, stream_user_data);188189SSL_free(stream->s);190stream->s = NULL;191return ret;192}193194static int h3_conn_reset_stream(nghttp3_conn *h3conn, int64_t stream_id,195uint64_t app_error_code,196void *conn_user_data, void *stream_user_data)197{198int ret = 0;199OSSL_DEMO_H3_CONN *conn = conn_user_data;200OSSL_DEMO_H3_STREAM *stream = stream_user_data;201SSL_STREAM_RESET_ARGS args = { 0 };202203if (conn->reset_stream_cb != NULL)204ret = conn->reset_stream_cb(h3conn, stream_id, app_error_code,205conn_user_data, stream_user_data);206207if (stream->s != NULL) {208args.quic_error_code = app_error_code;209210if (!SSL_stream_reset(stream->s, &args, sizeof(args)))211return 1;212}213214return ret;215}216217static int h3_conn_deferred_consume(nghttp3_conn *h3conn, int64_t stream_id,218size_t consumed,219void *conn_user_data, void *stream_user_data)220{221int ret = 0;222OSSL_DEMO_H3_CONN *conn = conn_user_data;223224if (conn->deferred_consume_cb != NULL)225ret = conn->deferred_consume_cb(h3conn, stream_id, consumed,226conn_user_data, stream_user_data);227228conn->consumed_app_data += consumed;229return ret;230}231232OSSL_DEMO_H3_CONN *OSSL_DEMO_H3_CONN_new_for_conn(BIO *qconn_bio,233const nghttp3_callbacks *callbacks,234const nghttp3_settings *settings,235void *user_data)236{237int ec;238OSSL_DEMO_H3_CONN *conn;239OSSL_DEMO_H3_STREAM *s_ctl_send = NULL;240OSSL_DEMO_H3_STREAM *s_qpenc_send = NULL;241OSSL_DEMO_H3_STREAM *s_qpdec_send = NULL;242nghttp3_settings dsettings = { 0 };243nghttp3_callbacks intl_callbacks = { 0 };244static const unsigned char alpn[] = { 2, 'h', '3' };245246if (qconn_bio == NULL) {247ERR_raise_data(ERR_LIB_USER, ERR_R_PASSED_NULL_PARAMETER,248"QUIC connection BIO must be provided");249return NULL;250}251252if ((conn = OPENSSL_zalloc(sizeof(OSSL_DEMO_H3_CONN))) == NULL)253return NULL;254255conn->qconn_bio = qconn_bio;256conn->user_data = user_data;257258if (BIO_get_ssl(qconn_bio, &conn->qconn) == 0) {259ERR_raise_data(ERR_LIB_USER, ERR_R_PASSED_INVALID_ARGUMENT,260"BIO must be an SSL BIO");261goto err;262}263264/* Create the map of stream IDs to OSSL_DEMO_H3_STREAM structures. */265if ((conn->streams = lh_OSSL_DEMO_H3_STREAM_new(h3_stream_hash, h3_stream_eq)) == NULL)266goto err;267268/*269* If the application has not started connecting yet, helpfully270* auto-configure ALPN. If the application wants to initiate the connection271* itself, it must take care of this itself.272*/273if (SSL_in_before(conn->qconn))274if (SSL_set_alpn_protos(conn->qconn, alpn, sizeof(alpn))) {275/* SSL_set_alpn_protos returns 1 on failure */276ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,277"failed to configure ALPN");278goto err;279}280281/*282* We use the QUIC stack in non-blocking mode so that we can react to283* incoming data on different streams, and e.g. incoming streams initiated284* by a server, as and when events occur.285*/286BIO_set_nbio(conn->qconn_bio, 1);287288/*289* Disable default stream mode and create all streams explicitly. Each QUIC290* stream will be represented by its own QUIC stream SSL object (QSSO). This291* also automatically enables us to accept incoming streams (see292* SSL_set_incoming_stream_policy(3)).293*/294if (!SSL_set_default_stream_mode(conn->qconn, SSL_DEFAULT_STREAM_MODE_NONE)) {295ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,296"failed to configure default stream mode");297goto err;298}299300/*301* HTTP/3 requires a couple of unidirectional management streams: a control302* stream and some QPACK state management streams for each side of a303* connection. These are the instances on our side (with us sending); the304* server will also create its own equivalent unidirectional streams on its305* side, which we handle subsequently as they come in (see SSL_accept_stream306* in the event handling code below).307*/308if ((s_ctl_send309= h3_conn_create_stream(conn, OSSL_DEMO_H3_STREAM_TYPE_CTRL_SEND))310== NULL)311goto err;312313if ((s_qpenc_send314= h3_conn_create_stream(conn, OSSL_DEMO_H3_STREAM_TYPE_QPACK_ENC_SEND))315== NULL)316goto err;317318if ((s_qpdec_send319= h3_conn_create_stream(conn, OSSL_DEMO_H3_STREAM_TYPE_QPACK_DEC_SEND))320== NULL)321goto err;322323if (settings == NULL) {324nghttp3_settings_default(&dsettings);325settings = &dsettings;326}327328if (callbacks != NULL)329intl_callbacks = *callbacks;330331/*332* We need to do some of our own processing when many of these events occur,333* so we note the original callback functions and forward appropriately.334*/335conn->recv_data_cb = intl_callbacks.recv_data;336conn->stream_close_cb = intl_callbacks.stream_close;337conn->stop_sending_cb = intl_callbacks.stop_sending;338conn->reset_stream_cb = intl_callbacks.reset_stream;339conn->deferred_consume_cb = intl_callbacks.deferred_consume;340341intl_callbacks.recv_data = h3_conn_recv_data;342intl_callbacks.stream_close = h3_conn_stream_close;343intl_callbacks.stop_sending = h3_conn_stop_sending;344intl_callbacks.reset_stream = h3_conn_reset_stream;345intl_callbacks.deferred_consume = h3_conn_deferred_consume;346347/* Create the HTTP/3 client state. */348ec = nghttp3_conn_client_new(&conn->h3conn, &intl_callbacks, settings,349NULL, conn);350if (ec < 0) {351ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,352"cannot create nghttp3 connection: %s (%d)",353nghttp3_strerror(ec), ec);354goto err;355}356357/*358* Tell the HTTP/3 stack which stream IDs are used for our outgoing control359* and QPACK streams. Note that we don't have to tell the HTTP/3 stack what360* IDs are used for incoming streams as this is inferred automatically from361* the stream type byte which starts every incoming unidirectional stream,362* so it will autodetect the correct stream IDs for the incoming control and363* QPACK streams initiated by the server.364*/365ec = nghttp3_conn_bind_control_stream(conn->h3conn, s_ctl_send->id);366if (ec < 0) {367ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,368"cannot bind nghttp3 control stream: %s (%d)",369nghttp3_strerror(ec), ec);370goto err;371}372373ec = nghttp3_conn_bind_qpack_streams(conn->h3conn,374s_qpenc_send->id,375s_qpdec_send->id);376if (ec < 0) {377ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,378"cannot bind nghttp3 QPACK streams: %s (%d)",379nghttp3_strerror(ec), ec);380goto err;381}382383return conn;384385err:386nghttp3_conn_del(conn->h3conn);387h3_stream_free(s_ctl_send);388h3_stream_free(s_qpenc_send);389h3_stream_free(s_qpdec_send);390lh_OSSL_DEMO_H3_STREAM_free(conn->streams);391OPENSSL_free(conn);392return NULL;393}394395OSSL_DEMO_H3_CONN *OSSL_DEMO_H3_CONN_new_for_addr(SSL_CTX *ctx, const char *addr,396const nghttp3_callbacks *callbacks,397const nghttp3_settings *settings,398void *user_data)399{400BIO *qconn_bio = NULL;401SSL *qconn = NULL;402OSSL_DEMO_H3_CONN *conn = NULL;403const char *bare_hostname;404405/* QUIC connection setup */406if ((qconn_bio = BIO_new_ssl_connect(ctx)) == NULL)407goto err;408409/* Pass the 'hostname:port' string into the ssl_connect BIO. */410if (BIO_set_conn_hostname(qconn_bio, addr) == 0)411goto err;412413/*414* Get the 'bare' hostname out of the ssl_connect BIO. This is the hostname415* without the port.416*/417bare_hostname = BIO_get_conn_hostname(qconn_bio);418if (bare_hostname == NULL)419goto err;420421if (BIO_get_ssl(qconn_bio, &qconn) == 0)422goto err;423424/* Set the hostname we will validate the X.509 certificate against. */425if (SSL_set1_host(qconn, bare_hostname) <= 0)426goto err;427428/* Configure SNI */429if (!SSL_set_tlsext_host_name(qconn, bare_hostname))430goto err;431432conn = OSSL_DEMO_H3_CONN_new_for_conn(qconn_bio, callbacks,433settings, user_data);434if (conn == NULL)435goto err;436437return conn;438439err:440BIO_free_all(qconn_bio);441return NULL;442}443444int OSSL_DEMO_H3_CONN_connect(OSSL_DEMO_H3_CONN *conn)445{446return SSL_connect(OSSL_DEMO_H3_CONN_get0_connection(conn));447}448449void *OSSL_DEMO_H3_CONN_get_user_data(const OSSL_DEMO_H3_CONN *conn)450{451return conn->user_data;452}453454SSL *OSSL_DEMO_H3_CONN_get0_connection(const OSSL_DEMO_H3_CONN *conn)455{456return conn->qconn;457}458459/* Pumps received data to the HTTP/3 stack for a single stream. */460static void h3_conn_pump_stream(OSSL_DEMO_H3_STREAM *s, void *conn_)461{462int ec;463OSSL_DEMO_H3_CONN *conn = conn_;464size_t num_bytes, consumed;465uint64_t aec;466467if (!conn->pump_res)468/*469* Handling of a previous stream in the iteration over all streams470* failed, so just do nothing.471*/472return;473474for (;;) {475if (s->s == NULL /* If we already did STOP_SENDING, ignore this stream. */476/* If this is a write-only stream, there is no read data to check. */477|| SSL_get_stream_read_state(s->s) == SSL_STREAM_STATE_WRONG_DIR478/*479* If we already got a FIN for this stream, there is nothing more to480* do for it.481*/482|| s->done_recv_fin)483break;484485/*486* Pump data from OpenSSL QUIC to the HTTP/3 stack by calling SSL_peek487* to get received data and passing it to nghttp3 using488* nghttp3_conn_read_stream. Note that this function is confusingly489* named and inputs data to the HTTP/3 stack.490*/491if (s->buf_cur == s->buf_total) {492/* Need more data. */493ec = SSL_read_ex(s->s, s->buf, sizeof(s->buf), &num_bytes);494if (ec <= 0) {495num_bytes = 0;496if (SSL_get_error(s->s, ec) == SSL_ERROR_ZERO_RETURN) {497/* Stream concluded normally. Pass FIN to HTTP/3 stack. */498ec = nghttp3_conn_read_stream(conn->h3conn, s->id, NULL, 0,499/*fin=*/1);500if (ec < 0) {501ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,502"cannot pass FIN to nghttp3: %s (%d)",503nghttp3_strerror(ec), ec);504goto err;505}506507s->done_recv_fin = 1;508} else if (SSL_get_stream_read_state(s->s)509== SSL_STREAM_STATE_RESET_REMOTE) {510/* Stream was reset by peer. */511if (!SSL_get_stream_read_error_code(s->s, &aec))512goto err;513514ec = nghttp3_conn_close_stream(conn->h3conn, s->id, aec);515if (ec < 0) {516ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,517"cannot mark stream as reset: %s (%d)",518nghttp3_strerror(ec), ec);519goto err;520}521522s->done_recv_fin = 1;523} else {524/* Other error. */525goto err;526}527}528529s->buf_cur = 0;530s->buf_total = num_bytes;531}532533if (s->buf_cur == s->buf_total)534break;535536/*537* This function is confusingly named as it is is named from nghttp3's538* 'perspective'; it is used to pass data *into* the HTTP/3 stack which539* has been received from the network.540*/541assert(conn->consumed_app_data == 0);542ec = nghttp3_conn_read_stream(conn->h3conn, s->id, s->buf + s->buf_cur,543s->buf_total - s->buf_cur, /*fin=*/0);544if (ec < 0) {545ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,546"nghttp3 failed to process incoming data: %s (%d)",547nghttp3_strerror(ec), ec);548goto err;549}550551/*552* read_stream reports the data it consumes from us in two different553* ways; the non-application data is returned as a number of bytes 'ec'554* above, but the number of bytes of application data has to be recorded555* by our callback. We sum the two to determine the total number of556* bytes which nghttp3 consumed.557*/558consumed = ec + conn->consumed_app_data;559assert(consumed <= s->buf_total - s->buf_cur);560s->buf_cur += consumed;561conn->consumed_app_data = 0;562}563564return;565err:566conn->pump_res = 0;567}568569int OSSL_DEMO_H3_CONN_handle_events(OSSL_DEMO_H3_CONN *conn)570{571int ec, fin;572size_t i, num_vecs, written, total_written, total_len;573int64_t stream_id;574uint64_t flags;575nghttp3_vec vecs[8] = { 0 };576OSSL_DEMO_H3_STREAM key, *s;577SSL *snew;578579if (conn == NULL)580return 0;581582/*583* We handle events by doing three things:584*585* 1. Handle new incoming streams586* 2. Pump outgoing data from the HTTP/3 stack to the QUIC engine587* 3. Pump incoming data from the QUIC engine to the HTTP/3 stack588*/589590/* 1. Check for new incoming streams */591for (;;) {592if ((snew = SSL_accept_stream(conn->qconn, SSL_ACCEPT_STREAM_NO_BLOCK)) == NULL)593break;594595/*596* Each new incoming stream gets wrapped into an OSSL_DEMO_H3_STREAM object and597* added into our stream ID map.598*/599if (h3_conn_accept_stream(conn, snew) == NULL) {600SSL_free(snew);601return 0;602}603}604605/* 2. Pump outgoing data from HTTP/3 engine to QUIC. */606for (;;) {607/*608* Get a number of send vectors from the HTTP/3 engine.609*610* Note that this function is confusingly named as it is named from611* nghttp3's 'perspective': this outputs pointers to data which nghttp3612* wants to *write* to the network.613*/614ec = nghttp3_conn_writev_stream(conn->h3conn, &stream_id, &fin,615vecs, ARRAY_LEN(vecs));616if (ec < 0)617return 0;618if (ec == 0)619break;620621/*622* we let SSL_write_ex2(3) to conclude the stream for us (send FIN)623* after all data are written.624*/625flags = (fin == 0) ? 0 : SSL_WRITE_FLAG_CONCLUDE;626627/* For each of the vectors returned, pass it to OpenSSL QUIC. */628key.id = stream_id;629if ((s = lh_OSSL_DEMO_H3_STREAM_retrieve(conn->streams, &key)) == NULL) {630ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,631"no stream for ID %zd", stream_id);632return 0;633}634635num_vecs = ec;636total_len = nghttp3_vec_len(vecs, num_vecs);637total_written = 0;638for (i = 0; i < num_vecs; ++i) {639if (vecs[i].len == 0)640continue;641642if (s->s == NULL) {643/* Already did STOP_SENDING and threw away stream, ignore */644written = vecs[i].len;645} else if (!SSL_write_ex2(s->s, vecs[i].base, vecs[i].len, flags, &written)) {646if (SSL_get_error(s->s, 0) == SSL_ERROR_WANT_WRITE) {647/*648* We have filled our send buffer so tell nghttp3 to stop649* generating more data; we have to do this explicitly.650*/651written = 0;652nghttp3_conn_block_stream(conn->h3conn, stream_id);653} else {654ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,655"writing HTTP/3 data to network failed");656return 0;657}658} else {659/*660* Tell nghttp3 it can resume generating more data in case we661* previously called block_stream.662*/663nghttp3_conn_unblock_stream(conn->h3conn, stream_id);664}665666total_written += written;667if (written > 0) {668/*669* Tell nghttp3 we have consumed the data it output when we670* called writev_stream, otherwise subsequent calls to671* writev_stream will output the same data.672*/673ec = nghttp3_conn_add_write_offset(conn->h3conn, stream_id, written);674if (ec < 0)675return 0;676677/*678* Tell nghttp3 it can free the buffered data because we will679* not need it again. In our case we can always do this right680* away because we copy the data into our QUIC send buffers681* rather than simply storing a reference to it.682*/683ec = nghttp3_conn_add_ack_offset(conn->h3conn, stream_id, written);684if (ec < 0)685return 0;686}687}688689if (fin && total_written == total_len) {690691if (total_len == 0) {692/*693* As a special case, if nghttp3 requested to write a694* zero-length stream with a FIN, we have to tell it we did this695* by calling add_write_offset(0).696*/697ec = nghttp3_conn_add_write_offset(conn->h3conn, stream_id, 0);698if (ec < 0)699return 0;700}701}702}703704/* 3. Pump incoming data from QUIC to HTTP/3 engine. */705conn->pump_res = 1; /* cleared in below call if an error occurs */706lh_OSSL_DEMO_H3_STREAM_doall_arg(conn->streams, h3_conn_pump_stream, conn);707if (!conn->pump_res)708return 0;709710return 1;711}712713int OSSL_DEMO_H3_CONN_submit_request(OSSL_DEMO_H3_CONN *conn,714const nghttp3_nv *nva, size_t nvlen,715const nghttp3_data_reader *dr,716void *user_data)717{718int ec;719OSSL_DEMO_H3_STREAM *s_req = NULL;720721if (conn == NULL) {722ERR_raise_data(ERR_LIB_USER, ERR_R_PASSED_NULL_PARAMETER,723"connection must be specified");724return 0;725}726727/* Each HTTP/3 request is represented by a stream. */728if ((s_req = h3_conn_create_stream(conn, OSSL_DEMO_H3_STREAM_TYPE_REQ)) == NULL)729goto err;730731s_req->user_data = user_data;732733ec = nghttp3_conn_submit_request(conn->h3conn, s_req->id, nva, nvlen,734dr, s_req);735if (ec < 0) {736ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR,737"cannot submit HTTP/3 request: %s (%d)",738nghttp3_strerror(ec), ec);739goto err;740}741742return 1;743744err:745h3_conn_remove_stream(conn, s_req);746return 0;747}748749750