1
0
mirror of https://github.com/moparisthebest/curl synced 2024-08-13 17:03:50 -04:00
curl/lib/vquic/quiche.c
Alessandro Ghedini 424aa64d54
quiche: update for network path aware API
Latest version of quiche requires the application to pass the peer
address of received packets, and it provides the address for outgoing
packets back.

Closes #7120
2021-05-24 17:26:46 +02:00

887 lines
25 KiB
C

/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at https://curl.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
***************************************************************************/
#include "curl_setup.h"
#ifdef USE_QUICHE
#include <quiche.h>
#include <openssl/err.h>
#include "urldata.h"
#include "sendf.h"
#include "strdup.h"
#include "rand.h"
#include "quic.h"
#include "strcase.h"
#include "multiif.h"
#include "connect.h"
#include "strerror.h"
#include "vquic.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
#include "curl_memory.h"
#include "memdebug.h"
#define DEBUG_HTTP3
/* #define DEBUG_QUICHE */
#ifdef DEBUG_HTTP3
#define H3BUGF(x) x
#else
#define H3BUGF(x) do { } while(0)
#endif
#define QUIC_MAX_STREAMS (256*1024)
#define QUIC_MAX_DATA (1*1024*1024)
#define QUIC_IDLE_TIMEOUT (60 * 1000) /* milliseconds */
static CURLcode process_ingress(struct Curl_easy *data,
curl_socket_t sockfd,
struct quicsocket *qs);
static CURLcode flush_egress(struct Curl_easy *data, curl_socket_t sockfd,
struct quicsocket *qs);
static CURLcode http_request(struct Curl_easy *data, const void *mem,
size_t len);
static Curl_recv h3_stream_recv;
static Curl_send h3_stream_send;
static int quiche_getsock(struct Curl_easy *data,
struct connectdata *conn, curl_socket_t *socks)
{
struct SingleRequest *k = &data->req;
int bitmap = GETSOCK_BLANK;
socks[0] = conn->sock[FIRSTSOCKET];
/* in a HTTP/2 connection we can basically always get a frame so we should
always be ready for one */
bitmap |= GETSOCK_READSOCK(FIRSTSOCKET);
/* we're still uploading or the HTTP/2 layer wants to send data */
if((k->keepon & (KEEP_SEND|KEEP_SEND_PAUSE)) == KEEP_SEND)
bitmap |= GETSOCK_WRITESOCK(FIRSTSOCKET);
return bitmap;
}
static CURLcode qs_disconnect(struct Curl_easy *data,
struct quicsocket *qs)
{
DEBUGASSERT(qs);
if(qs->conn) {
(void)quiche_conn_close(qs->conn, TRUE, 0, NULL, 0);
/* flushing the egress is not a failsafe way to deliver all the
outstanding packets, but we also don't want to get stuck here... */
(void)flush_egress(data, qs->sockfd, qs);
quiche_conn_free(qs->conn);
qs->conn = NULL;
}
if(qs->h3config)
quiche_h3_config_free(qs->h3config);
if(qs->h3c)
quiche_h3_conn_free(qs->h3c);
if(qs->cfg) {
quiche_config_free(qs->cfg);
qs->cfg = NULL;
}
return CURLE_OK;
}
static CURLcode quiche_disconnect(struct Curl_easy *data,
struct connectdata *conn,
bool dead_connection)
{
struct quicsocket *qs = conn->quic;
(void)dead_connection;
return qs_disconnect(data, qs);
}
void Curl_quic_disconnect(struct Curl_easy *data,
struct connectdata *conn,
int tempindex)
{
if(conn->transport == TRNSPRT_QUIC)
qs_disconnect(data, &conn->hequic[tempindex]);
}
static unsigned int quiche_conncheck(struct Curl_easy *data,
struct connectdata *conn,
unsigned int checks_to_perform)
{
(void)data;
(void)conn;
(void)checks_to_perform;
return CONNRESULT_NONE;
}
static CURLcode quiche_do(struct Curl_easy *data, bool *done)
{
struct HTTP *stream = data->req.p.http;
stream->h3req = FALSE; /* not sent */
return Curl_http(data, done);
}
static const struct Curl_handler Curl_handler_http3 = {
"HTTPS", /* scheme */
ZERO_NULL, /* setup_connection */
quiche_do, /* do_it */
Curl_http_done, /* done */
ZERO_NULL, /* do_more */
ZERO_NULL, /* connect_it */
ZERO_NULL, /* connecting */
ZERO_NULL, /* doing */
quiche_getsock, /* proto_getsock */
quiche_getsock, /* doing_getsock */
ZERO_NULL, /* domore_getsock */
quiche_getsock, /* perform_getsock */
quiche_disconnect, /* disconnect */
ZERO_NULL, /* readwrite */
quiche_conncheck, /* connection_check */
ZERO_NULL, /* attach connection */
PORT_HTTP, /* defport */
CURLPROTO_HTTPS, /* protocol */
CURLPROTO_HTTP, /* family */
PROTOPT_SSL | PROTOPT_STREAM /* flags */
};
#ifdef DEBUG_QUICHE
static void quiche_debug_log(const char *line, void *argp)
{
(void)argp;
fprintf(stderr, "%s\n", line);
}
#endif
CURLcode Curl_quic_connect(struct Curl_easy *data,
struct connectdata *conn, curl_socket_t sockfd,
int sockindex,
const struct sockaddr *addr, socklen_t addrlen)
{
CURLcode result;
struct quicsocket *qs = &conn->hequic[sockindex];
char *keylog_file = NULL;
char ipbuf[40];
int port;
#ifdef DEBUG_QUICHE
/* initialize debug log callback only once */
static int debug_log_init = 0;
if(!debug_log_init) {
quiche_enable_debug_logging(quiche_debug_log, NULL);
debug_log_init = 1;
}
#endif
(void)addr;
(void)addrlen;
qs->sockfd = sockfd;
qs->cfg = quiche_config_new(QUICHE_PROTOCOL_VERSION);
if(!qs->cfg) {
failf(data, "can't create quiche config");
return CURLE_FAILED_INIT;
}
quiche_config_set_max_idle_timeout(qs->cfg, QUIC_IDLE_TIMEOUT);
quiche_config_set_initial_max_data(qs->cfg, QUIC_MAX_DATA);
quiche_config_set_initial_max_stream_data_bidi_local(qs->cfg, QUIC_MAX_DATA);
quiche_config_set_initial_max_stream_data_bidi_remote(qs->cfg,
QUIC_MAX_DATA);
quiche_config_set_initial_max_stream_data_uni(qs->cfg, QUIC_MAX_DATA);
quiche_config_set_initial_max_streams_bidi(qs->cfg, QUIC_MAX_STREAMS);
quiche_config_set_initial_max_streams_uni(qs->cfg, QUIC_MAX_STREAMS);
quiche_config_set_application_protos(qs->cfg,
(uint8_t *)
QUICHE_H3_APPLICATION_PROTOCOL,
sizeof(QUICHE_H3_APPLICATION_PROTOCOL)
- 1);
result = Curl_rand(data, qs->scid, sizeof(qs->scid));
if(result)
return result;
keylog_file = getenv("SSLKEYLOGFILE");
if(keylog_file)
quiche_config_log_keys(qs->cfg);
qs->conn = quiche_connect(conn->host.name, (const uint8_t *) qs->scid,
sizeof(qs->scid), addr, addrlen, qs->cfg);
if(!qs->conn) {
failf(data, "can't create quiche connection");
return CURLE_OUT_OF_MEMORY;
}
if(keylog_file)
quiche_conn_set_keylog_path(qs->conn, keylog_file);
/* Known to not work on Windows */
#if !defined(WIN32) && defined(HAVE_QUICHE_CONN_SET_QLOG_FD)
{
int qfd;
(void)Curl_qlogdir(data, qs->scid, sizeof(qs->scid), &qfd);
if(qfd != -1)
quiche_conn_set_qlog_fd(qs->conn, qfd,
"qlog title", "curl qlog");
}
#endif
result = flush_egress(data, sockfd, qs);
if(result)
return result;
/* extract the used address as a string */
if(!Curl_addr2string((struct sockaddr*)addr, addrlen, ipbuf, &port)) {
char buffer[STRERROR_LEN];
failf(data, "ssrem inet_ntop() failed with errno %d: %s",
SOCKERRNO, Curl_strerror(SOCKERRNO, buffer, sizeof(buffer)));
return CURLE_BAD_FUNCTION_ARGUMENT;
}
infof(data, "Connect socket %d over QUIC to %s:%ld\n",
sockfd, ipbuf, port);
Curl_persistconninfo(data, conn, NULL, -1);
/* for connection reuse purposes: */
conn->ssl[FIRSTSOCKET].state = ssl_connection_complete;
{
unsigned char alpn_protocols[] = QUICHE_H3_APPLICATION_PROTOCOL;
unsigned alpn_len, offset = 0;
/* Replace each ALPN length prefix by a comma. */
while(offset < sizeof(alpn_protocols) - 1) {
alpn_len = alpn_protocols[offset];
alpn_protocols[offset] = ',';
offset += 1 + alpn_len;
}
infof(data, "Sent QUIC client Initial, ALPN: %s\n",
alpn_protocols + 1);
}
return CURLE_OK;
}
static CURLcode quiche_has_connected(struct connectdata *conn,
int sockindex,
int tempindex)
{
CURLcode result;
struct quicsocket *qs = conn->quic = &conn->hequic[tempindex];
conn->recv[sockindex] = h3_stream_recv;
conn->send[sockindex] = h3_stream_send;
conn->handler = &Curl_handler_http3;
conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
conn->httpversion = 30;
conn->bundle->multiuse = BUNDLE_MULTIPLEX;
qs->h3config = quiche_h3_config_new();
if(!qs->h3config)
return CURLE_OUT_OF_MEMORY;
/* Create a new HTTP/3 connection on the QUIC connection. */
qs->h3c = quiche_h3_conn_new_with_transport(qs->conn, qs->h3config);
if(!qs->h3c) {
result = CURLE_OUT_OF_MEMORY;
goto fail;
}
if(conn->hequic[1-tempindex].cfg) {
qs = &conn->hequic[1-tempindex];
quiche_config_free(qs->cfg);
quiche_conn_free(qs->conn);
qs->cfg = NULL;
qs->conn = NULL;
}
return CURLE_OK;
fail:
quiche_h3_config_free(qs->h3config);
quiche_h3_conn_free(qs->h3c);
return result;
}
/*
* This function gets polled to check if this QUIC connection has connected.
*/
CURLcode Curl_quic_is_connected(struct Curl_easy *data,
struct connectdata *conn,
int sockindex,
bool *done)
{
CURLcode result;
struct quicsocket *qs = &conn->hequic[sockindex];
curl_socket_t sockfd = conn->tempsock[sockindex];
result = process_ingress(data, sockfd, qs);
if(result)
goto error;
result = flush_egress(data, sockfd, qs);
if(result)
goto error;
if(quiche_conn_is_established(qs->conn)) {
*done = TRUE;
result = quiche_has_connected(conn, 0, sockindex);
DEBUGF(infof(data, "quiche established connection!\n"));
}
return result;
error:
qs_disconnect(data, qs);
return result;
}
static CURLcode process_ingress(struct Curl_easy *data, int sockfd,
struct quicsocket *qs)
{
ssize_t recvd;
uint8_t *buf = (uint8_t *)data->state.buffer;
size_t bufsize = data->set.buffer_size;
struct sockaddr_storage from;
socklen_t from_len;
quiche_recv_info recv_info;
DEBUGASSERT(qs->conn);
/* in case the timeout expired */
quiche_conn_on_timeout(qs->conn);
do {
from_len = sizeof(from);
recvd = recvfrom(sockfd, buf, bufsize, 0,
(struct sockaddr *)&from, &from_len);
if((recvd < 0) && ((SOCKERRNO == EAGAIN) || (SOCKERRNO == EWOULDBLOCK)))
break;
if(recvd < 0) {
failf(data, "quiche: recvfrom() unexpectedly returned %zd "
"(errno: %d, socket %d)", recvd, SOCKERRNO, sockfd);
return CURLE_RECV_ERROR;
}
recv_info.from = (struct sockaddr *) &from;
recv_info.from_len = from_len;
recvd = quiche_conn_recv(qs->conn, buf, recvd, &recv_info);
if(recvd == QUICHE_ERR_DONE)
break;
if(recvd < 0) {
failf(data, "quiche_conn_recv() == %zd", recvd);
return CURLE_RECV_ERROR;
}
} while(1);
return CURLE_OK;
}
/*
* flush_egress drains the buffers and sends off data.
* Calls failf() on errors.
*/
static CURLcode flush_egress(struct Curl_easy *data, int sockfd,
struct quicsocket *qs)
{
ssize_t sent;
uint8_t out[1200];
int64_t timeout_ns;
quiche_send_info send_info;
do {
sent = quiche_conn_send(qs->conn, out, sizeof(out), &send_info);
if(sent == QUICHE_ERR_DONE)
break;
if(sent < 0) {
failf(data, "quiche_conn_send returned %zd", sent);
return CURLE_SEND_ERROR;
}
sent = sendto(sockfd, out, sent, 0,
(struct sockaddr *)&send_info.to, send_info.to_len);
if(sent < 0) {
failf(data, "sendto() returned %zd", sent);
return CURLE_SEND_ERROR;
}
} while(1);
/* time until the next timeout event, as nanoseconds. */
timeout_ns = quiche_conn_timeout_as_nanos(qs->conn);
if(timeout_ns)
/* expire uses milliseconds */
Curl_expire(data, (timeout_ns + 999999) / 1000000, EXPIRE_QUIC);
return CURLE_OK;
}
struct h3h1header {
char *dest;
size_t destlen; /* left to use */
size_t nlen; /* used */
};
static int cb_each_header(uint8_t *name, size_t name_len,
uint8_t *value, size_t value_len,
void *argp)
{
struct h3h1header *headers = (struct h3h1header *)argp;
size_t olen = 0;
if((name_len == 7) && !strncmp(":status", (char *)name, 7)) {
msnprintf(headers->dest,
headers->destlen, "HTTP/3 %.*s\n",
(int) value_len, value);
}
else if(!headers->nlen) {
return CURLE_HTTP3;
}
else {
msnprintf(headers->dest,
headers->destlen, "%.*s: %.*s\n",
(int)name_len, name, (int) value_len, value);
}
olen = strlen(headers->dest);
headers->destlen -= olen;
headers->nlen += olen;
headers->dest += olen;
return 0;
}
static ssize_t h3_stream_recv(struct Curl_easy *data,
int sockindex,
char *buf,
size_t buffersize,
CURLcode *curlcode)
{
ssize_t recvd = -1;
ssize_t rcode;
struct connectdata *conn = data->conn;
struct quicsocket *qs = conn->quic;
curl_socket_t sockfd = conn->sock[sockindex];
quiche_h3_event *ev;
int rc;
struct h3h1header headers;
struct HTTP *stream = data->req.p.http;
headers.dest = buf;
headers.destlen = buffersize;
headers.nlen = 0;
if(process_ingress(data, sockfd, qs)) {
infof(data, "h3_stream_recv returns on ingress\n");
*curlcode = CURLE_RECV_ERROR;
return -1;
}
while(recvd < 0) {
int64_t s = quiche_h3_conn_poll(qs->h3c, qs->conn, &ev);
if(s < 0)
/* nothing more to do */
break;
if(s != stream->stream3_id) {
/* another transfer, ignore for now */
infof(data, "Got h3 for stream %u, expects %u\n",
s, stream->stream3_id);
continue;
}
switch(quiche_h3_event_type(ev)) {
case QUICHE_H3_EVENT_HEADERS:
rc = quiche_h3_event_for_each_header(ev, cb_each_header, &headers);
if(rc) {
*curlcode = rc;
failf(data, "Error in HTTP/3 response header");
break;
}
recvd = headers.nlen;
break;
case QUICHE_H3_EVENT_DATA:
if(!stream->firstbody) {
/* add a header-body separator CRLF */
buf[0] = '\r';
buf[1] = '\n';
buf += 2;
buffersize -= 2;
stream->firstbody = TRUE;
recvd = 2; /* two bytes already */
}
else
recvd = 0;
rcode = quiche_h3_recv_body(qs->h3c, qs->conn, s, (unsigned char *)buf,
buffersize);
if(rcode <= 0) {
recvd = -1;
break;
}
recvd += rcode;
break;
case QUICHE_H3_EVENT_FINISHED:
streamclose(conn, "End of stream");
recvd = 0; /* end of stream */
break;
default:
break;
}
quiche_h3_event_free(ev);
}
if(flush_egress(data, sockfd, qs)) {
*curlcode = CURLE_SEND_ERROR;
return -1;
}
*curlcode = (-1 == recvd)? CURLE_AGAIN : CURLE_OK;
if(recvd >= 0)
/* Get this called again to drain the event queue */
Curl_expire(data, 0, EXPIRE_QUIC);
data->state.drain = (recvd >= 0) ? 1 : 0;
return recvd;
}
static ssize_t h3_stream_send(struct Curl_easy *data,
int sockindex,
const void *mem,
size_t len,
CURLcode *curlcode)
{
ssize_t sent;
struct connectdata *conn = data->conn;
struct quicsocket *qs = conn->quic;
curl_socket_t sockfd = conn->sock[sockindex];
struct HTTP *stream = data->req.p.http;
if(!stream->h3req) {
CURLcode result = http_request(data, mem, len);
if(result) {
*curlcode = CURLE_SEND_ERROR;
return -1;
}
sent = len;
}
else {
H3BUGF(infof(data, "Pass on %zd body bytes to quiche\n", len));
sent = quiche_h3_send_body(qs->h3c, qs->conn, stream->stream3_id,
(uint8_t *)mem, len, FALSE);
if(sent < 0) {
*curlcode = CURLE_SEND_ERROR;
return -1;
}
}
if(flush_egress(data, sockfd, qs)) {
*curlcode = CURLE_SEND_ERROR;
return -1;
}
*curlcode = CURLE_OK;
return sent;
}
/*
* Store quiche version info in this buffer, Prefix with a space. Return total
* length written.
*/
int Curl_quic_ver(char *p, size_t len)
{
return msnprintf(p, len, "quiche/%s", quiche_version());
}
/* Index where :authority header field will appear in request header
field list. */
#define AUTHORITY_DST_IDX 3
static CURLcode http_request(struct Curl_easy *data, const void *mem,
size_t len)
{
/*
*/
struct connectdata *conn = data->conn;
struct HTTP *stream = data->req.p.http;
size_t nheader;
size_t i;
size_t authority_idx;
char *hdbuf = (char *)mem;
char *end, *line_end;
int64_t stream3_id;
quiche_h3_header *nva = NULL;
struct quicsocket *qs = conn->quic;
CURLcode result = CURLE_OK;
stream->h3req = TRUE; /* senf off! */
/* Calculate number of headers contained in [mem, mem + len). Assumes a
correctly generated HTTP header field block. */
nheader = 0;
for(i = 1; i < len; ++i) {
if(hdbuf[i] == '\n' && hdbuf[i - 1] == '\r') {
++nheader;
++i;
}
}
if(nheader < 2)
goto fail;
/* We counted additional 2 \r\n in the first and last line. We need 3
new headers: :method, :path and :scheme. Therefore we need one
more space. */
nheader += 1;
nva = malloc(sizeof(quiche_h3_header) * nheader);
if(!nva) {
result = CURLE_OUT_OF_MEMORY;
goto fail;
}
/* Extract :method, :path from request line
We do line endings with CRLF so checking for CR is enough */
line_end = memchr(hdbuf, '\r', len);
if(!line_end) {
result = CURLE_BAD_FUNCTION_ARGUMENT; /* internal error */
goto fail;
}
/* Method does not contain spaces */
end = memchr(hdbuf, ' ', line_end - hdbuf);
if(!end || end == hdbuf)
goto fail;
nva[0].name = (unsigned char *)":method";
nva[0].name_len = strlen((char *)nva[0].name);
nva[0].value = (unsigned char *)hdbuf;
nva[0].value_len = (size_t)(end - hdbuf);
hdbuf = end + 1;
/* Path may contain spaces so scan backwards */
end = NULL;
for(i = (size_t)(line_end - hdbuf); i; --i) {
if(hdbuf[i - 1] == ' ') {
end = &hdbuf[i - 1];
break;
}
}
if(!end || end == hdbuf)
goto fail;
nva[1].name = (unsigned char *)":path";
nva[1].name_len = strlen((char *)nva[1].name);
nva[1].value = (unsigned char *)hdbuf;
nva[1].value_len = (size_t)(end - hdbuf);
nva[2].name = (unsigned char *)":scheme";
nva[2].name_len = strlen((char *)nva[2].name);
if(conn->handler->flags & PROTOPT_SSL)
nva[2].value = (unsigned char *)"https";
else
nva[2].value = (unsigned char *)"http";
nva[2].value_len = strlen((char *)nva[2].value);
authority_idx = 0;
i = 3;
while(i < nheader) {
size_t hlen;
hdbuf = line_end + 2;
/* check for next CR, but only within the piece of data left in the given
buffer */
line_end = memchr(hdbuf, '\r', len - (hdbuf - (char *)mem));
if(!line_end || (line_end == hdbuf))
goto fail;
/* header continuation lines are not supported */
if(*hdbuf == ' ' || *hdbuf == '\t')
goto fail;
for(end = hdbuf; end < line_end && *end != ':'; ++end)
;
if(end == hdbuf || end == line_end)
goto fail;
hlen = end - hdbuf;
if(hlen == 4 && strncasecompare("host", hdbuf, 4)) {
authority_idx = i;
nva[i].name = (unsigned char *)":authority";
nva[i].name_len = strlen((char *)nva[i].name);
}
else {
nva[i].name_len = (size_t)(end - hdbuf);
/* Lower case the header name for HTTP/3 */
Curl_strntolower((char *)hdbuf, hdbuf, nva[i].name_len);
nva[i].name = (unsigned char *)hdbuf;
}
hdbuf = end + 1;
while(*hdbuf == ' ' || *hdbuf == '\t')
++hdbuf;
end = line_end;
#if 0 /* This should probably go in more or less like this */
switch(inspect_header((const char *)nva[i].name, nva[i].namelen, hdbuf,
end - hdbuf)) {
case HEADERINST_IGNORE:
/* skip header fields prohibited by HTTP/2 specification. */
--nheader;
continue;
case HEADERINST_TE_TRAILERS:
nva[i].value = (uint8_t*)"trailers";
nva[i].value_len = sizeof("trailers") - 1;
break;
default:
nva[i].value = (unsigned char *)hdbuf;
nva[i].value_len = (size_t)(end - hdbuf);
}
#endif
nva[i].value = (unsigned char *)hdbuf;
nva[i].value_len = (size_t)(end - hdbuf);
++i;
}
/* :authority must come before non-pseudo header fields */
if(authority_idx && authority_idx != AUTHORITY_DST_IDX) {
quiche_h3_header authority = nva[authority_idx];
for(i = authority_idx; i > AUTHORITY_DST_IDX; --i) {
nva[i] = nva[i - 1];
}
nva[i] = authority;
}
/* Warn stream may be rejected if cumulative length of headers is too
large. */
#define MAX_ACC 60000 /* <64KB to account for some overhead */
{
size_t acc = 0;
for(i = 0; i < nheader; ++i) {
acc += nva[i].name_len + nva[i].value_len;
H3BUGF(infof(data, "h3 [%.*s: %.*s]\n",
nva[i].name_len, nva[i].name,
nva[i].value_len, nva[i].value));
}
if(acc > MAX_ACC) {
infof(data, "http_request: Warning: The cumulative length of all "
"headers exceeds %d bytes and that could cause the "
"stream to be rejected.\n", MAX_ACC);
}
}
switch(data->state.httpreq) {
case HTTPREQ_POST:
case HTTPREQ_POST_FORM:
case HTTPREQ_POST_MIME:
case HTTPREQ_PUT:
if(data->state.infilesize != -1)
stream->upload_left = data->state.infilesize;
else
/* data sending without specifying the data amount up front */
stream->upload_left = -1; /* unknown, but not zero */
stream3_id = quiche_h3_send_request(qs->h3c, qs->conn, nva, nheader,
stream->upload_left ? FALSE: TRUE);
if((stream3_id >= 0) && data->set.postfields) {
ssize_t sent = quiche_h3_send_body(qs->h3c, qs->conn, stream3_id,
(uint8_t *)data->set.postfields,
stream->upload_left, TRUE);
if(sent <= 0) {
failf(data, "quiche_h3_send_body failed!");
result = CURLE_SEND_ERROR;
}
stream->upload_left = 0; /* nothing left to send */
}
break;
default:
stream3_id = quiche_h3_send_request(qs->h3c, qs->conn, nva, nheader,
TRUE);
break;
}
Curl_safefree(nva);
if(stream3_id < 0) {
H3BUGF(infof(data, "quiche_h3_send_request returned %d\n",
stream3_id));
result = CURLE_SEND_ERROR;
goto fail;
}
infof(data, "Using HTTP/3 Stream ID: %x (easy handle %p)\n",
stream3_id, (void *)data);
stream->stream3_id = stream3_id;
return CURLE_OK;
fail:
free(nva);
return result;
}
/*
* Called from transfer.c:done_sending when we stop HTTP/3 uploading.
*/
CURLcode Curl_quic_done_sending(struct Curl_easy *data)
{
struct connectdata *conn = data->conn;
DEBUGASSERT(conn);
if(conn->handler == &Curl_handler_http3) {
/* only for HTTP/3 transfers */
ssize_t sent;
struct HTTP *stream = data->req.p.http;
struct quicsocket *qs = conn->quic;
stream->upload_done = TRUE;
sent = quiche_h3_send_body(qs->h3c, qs->conn, stream->stream3_id,
NULL, 0, TRUE);
if(sent < 0)
return CURLE_SEND_ERROR;
}
return CURLE_OK;
}
/*
* Called from http.c:Curl_http_done when a request completes.
*/
void Curl_quic_done(struct Curl_easy *data, bool premature)
{
(void)data;
(void)premature;
}
/*
* Called from transfer.c:data_pending to know if we should keep looping
* to receive more data from the connection.
*/
bool Curl_quic_data_pending(const struct Curl_easy *data)
{
(void)data;
return FALSE;
}
#endif