diff options
author | Brad King <brad.king@kitware.com> | 2020-03-04 19:34:39 (GMT) |
---|---|---|
committer | Brad King <brad.king@kitware.com> | 2020-03-04 19:34:39 (GMT) |
commit | d61c3bd50520ce0179f52a129d5c660aebad88ba (patch) | |
tree | 4476c31b8b03de36811d94b5d9282318e84784b2 /Utilities/cmcurl/lib/vquic/quiche.c | |
parent | 84dc14a967b78431c95ed3203e5dd301b6897267 (diff) | |
parent | 735ea3001ae98636a4cb7caf15a40960c0da39a1 (diff) | |
download | CMake-d61c3bd50520ce0179f52a129d5c660aebad88ba.zip CMake-d61c3bd50520ce0179f52a129d5c660aebad88ba.tar.gz CMake-d61c3bd50520ce0179f52a129d5c660aebad88ba.tar.bz2 |
Merge branch 'upstream-curl' into update-curl
* upstream-curl:
curl 2020-03-04 (b8d13668)
Diffstat (limited to 'Utilities/cmcurl/lib/vquic/quiche.c')
-rw-r--r-- | Utilities/cmcurl/lib/vquic/quiche.c | 807 |
1 files changed, 807 insertions, 0 deletions
diff --git a/Utilities/cmcurl/lib/vquic/quiche.c b/Utilities/cmcurl/lib/vquic/quiche.c new file mode 100644 index 0000000..d09ba70 --- /dev/null +++ b/Utilities/cmcurl/lib/vquic/quiche.c @@ -0,0 +1,807 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2020, 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_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; + + 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 |