1
0
mirror of https://github.com/moparisthebest/curl synced 2024-12-25 17:48:48 -05:00
curl/lib/vquic/quiche.c
Jay Satiro 9c1806ae46 build: Disable Visual Studio warning "conditional expression is constant"
- Disable warning C4127 "conditional expression is constant" globally
  in curl_setup.h for when building with Microsoft's compiler.

This mainly affects building with the Visual Studio project files found
in the projects dir.

Prior to this change the cmake and winbuild build systems already
disabled 4127 globally for when building with Microsoft's compiler.
Also, 4127 was already disabled for all build systems in the limited
circumstance of the WHILE_FALSE macro which disabled the warning
specifically for while(0). This commit removes the WHILE_FALSE macro and
all other cruft in favor of disabling globally in curl_setup.

Background:

We have various macros that cause 0 or 1 to be evaluated, which would
cause warning C4127 in Visual Studio. For example this causes it:

    #define Curl_resolver_asynch() 1

Full behavior is not clearly defined and inconsistent across versions.
However it is documented that since VS 2015 Update 3 Microsoft has
addressed this somewhat but not entirely, not warning on while(true) for
example.

Prior to this change some C4127 warnings occurred when I built with
Visual Studio using the generated projects in the projects dir.

Closes https://github.com/curl/curl/pull/4658
2019-12-01 19:01:02 -05:00

808 lines
23 KiB
C

/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2019, 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.haxx.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"
/* 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 connectdata *conn,
curl_socket_t sockfd,
struct quicsocket *qs);
static CURLcode flush_egress(struct connectdata *conn, curl_socket_t sockfd,
struct quicsocket *qs);
static CURLcode http_request(struct connectdata *conn, const void *mem,
size_t len);
static Curl_recv h3_stream_recv;
static Curl_send h3_stream_send;
static int quiche_getsock(struct connectdata *conn, curl_socket_t *socks)
{
struct SingleRequest *k = &conn->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 int quiche_perform_getsock(const struct connectdata *conn,
curl_socket_t *socks)
{
return quiche_getsock((struct connectdata *)conn, socks);
}
static CURLcode quiche_disconnect(struct connectdata *conn,
bool dead_connection)
{
struct quicsocket *qs = conn->quic;
(void)dead_connection;
quiche_h3_config_free(qs->h3config);
quiche_h3_conn_free(qs->h3c);
quiche_config_free(qs->cfg);
quiche_conn_free(qs->conn);
return CURLE_OK;
}
static unsigned int quiche_conncheck(struct connectdata *conn,
unsigned int checks_to_perform)
{
(void)conn;
(void)checks_to_perform;
return CONNRESULT_NONE;
}
static CURLcode quiche_do(struct connectdata *conn, bool *done)
{
struct HTTP *stream = conn->data->req.protop;
stream->h3req = FALSE; /* not sent */
return Curl_http(conn, 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_perform_getsock, /* perform_getsock */
quiche_disconnect, /* disconnect */
ZERO_NULL, /* readwrite */
quiche_conncheck, /* connection_check */
PORT_HTTP, /* defport */
CURLPROTO_HTTPS, /* protocol */
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 connectdata *conn, curl_socket_t sockfd,
int sockindex,
const struct sockaddr *addr, socklen_t addrlen)
{
CURLcode result;
struct quicsocket *qs = &conn->hequic[sockindex];
struct Curl_easy *data = conn->data;
#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->cfg = quiche_config_new(QUICHE_PROTOCOL_VERSION);
if(!qs->cfg) {
failf(data, "can't create quiche config");
return CURLE_FAILED_INIT;
}
quiche_config_set_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;
if(getenv("SSLKEYLOGFILE"))
quiche_config_log_keys(qs->cfg);
qs->conn = quiche_connect(conn->host.name, (const uint8_t *) qs->scid,
sizeof(qs->scid), qs->cfg);
if(!qs->conn) {
failf(data, "can't create quiche connection");
return CURLE_OUT_OF_MEMORY;
}
result = flush_egress(conn, sockfd, qs);
if(result)
return result;
/* store the used address as a string */
if(!Curl_addr2string((struct sockaddr*)addr, addrlen,
conn->primary_ip, &conn->primary_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;
}
memcpy(conn->ip_addr_str, conn->primary_ip, MAX_IPADR_LEN);
Curl_persistconninfo(conn);
/* for connection reuse purposes: */
conn->ssl[FIRSTSOCKET].state = ssl_connection_complete;
infof(data, "Sent QUIC client Initial, ALPN: %s\n",
QUICHE_H3_APPLICATION_PROTOCOL + 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 connectdata *conn, int sockindex,
bool *done)
{
CURLcode result;
struct quicsocket *qs = &conn->hequic[sockindex];
curl_socket_t sockfd = conn->tempsock[sockindex];
result = process_ingress(conn, sockfd, qs);
if(result)
return result;
result = flush_egress(conn, sockfd, qs);
if(result)
return result;
if(quiche_conn_is_established(qs->conn)) {
*done = TRUE;
result = quiche_has_connected(conn, 0, sockindex);
DEBUGF(infof(conn->data, "quiche established connection!\n"));
}
return result;
}
static CURLcode process_ingress(struct connectdata *conn, int sockfd,
struct quicsocket *qs)
{
ssize_t recvd;
struct Curl_easy *data = conn->data;
uint8_t *buf = (uint8_t *)data->state.buffer;
size_t bufsize = data->set.buffer_size;
/* in case the timeout expired */
quiche_conn_on_timeout(qs->conn);
do {
recvd = recv(sockfd, buf, bufsize, 0);
if((recvd < 0) && ((SOCKERRNO == EAGAIN) || (SOCKERRNO == EWOULDBLOCK)))
break;
if(recvd < 0) {
failf(conn->data, "quiche: recv() unexpectedly returned %d "
"(errno: %d, socket %d)", recvd, SOCKERRNO, sockfd);
return CURLE_RECV_ERROR;
}
recvd = quiche_conn_recv(qs->conn, buf, recvd);
if(recvd == QUICHE_ERR_DONE)
break;
if(recvd < 0) {
failf(conn->data, "quiche_conn_recv() == %d", 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 connectdata *conn, int sockfd,
struct quicsocket *qs)
{
ssize_t sent;
static uint8_t out[1200];
int64_t timeout_ns;
do {
sent = quiche_conn_send(qs->conn, out, sizeof(out));
if(sent == QUICHE_ERR_DONE)
break;
if(sent < 0) {
failf(conn->data, "quiche_conn_send returned %zd\n",
sent);
return CURLE_SEND_ERROR;
}
sent = send(sockfd, out, sent, 0);
if(sent < 0) {
failf(conn->data, "send() returned %zd\n", 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(conn->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 connectdata *conn,
int sockindex,
char *buf,
size_t buffersize,
CURLcode *curlcode)
{
ssize_t recvd = -1;
ssize_t rcode;
struct quicsocket *qs = conn->quic;
curl_socket_t sockfd = conn->sock[sockindex];
quiche_h3_event *ev;
int rc;
struct h3h1header headers;
struct Curl_easy *data = conn->data;
struct HTTP *stream = data->req.protop;
headers.dest = buf;
headers.destlen = buffersize;
headers.nlen = 0;
if(process_ingress(conn, 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(conn, 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 connectdata *conn,
int sockindex,
const void *mem,
size_t len,
CURLcode *curlcode)
{
ssize_t sent;
struct quicsocket *qs = conn->quic;
curl_socket_t sockfd = conn->sock[sockindex];
struct HTTP *stream = conn->data->req.protop;
if(!stream->h3req) {
CURLcode result = http_request(conn, mem, len);
if(result) {
*curlcode = CURLE_SEND_ERROR;
return -1;
}
sent = len;
}
else {
H3BUGF(infof(conn->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(conn, 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 connectdata *conn, const void *mem,
size_t len)
{
/*
*/
struct HTTP *stream = conn->data->req.protop;
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;
struct Curl_easy *data = conn->data;
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 != 0 && 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 %zu bytes and that could cause the "
"stream to be rejected.\n", MAX_ACC);
}
}
switch(data->set.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 connectdata *conn)
{
if(conn->handler == &Curl_handler_http3) {
/* only for HTTP/3 transfers */
ssize_t sent;
struct HTTP *stream = conn->data->req.protop;
struct quicsocket *qs = conn->quic;
fprintf(stderr, "!!! Curl_quic_done_sending\n");
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