quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

curl_quiche.c (49632B)


      1 /***************************************************************************
      2  *                                  _   _ ____  _
      3  *  Project                     ___| | | |  _ \| |
      4  *                             / __| | | | |_) | |
      5  *                            | (__| |_| |  _ <| |___
      6  *                             \___|\___/|_| \_\_____|
      7  *
      8  * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
      9  *
     10  * This software is licensed as described in the file COPYING, which
     11  * you should have received as part of this distribution. The terms
     12  * are also available at https://curl.se/docs/copyright.html.
     13  *
     14  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
     15  * copies of the Software, and permit persons to whom the Software is
     16  * furnished to do so, under the terms of the COPYING file.
     17  *
     18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
     19  * KIND, either express or implied.
     20  *
     21  * SPDX-License-Identifier: curl
     22  *
     23  ***************************************************************************/
     24 
     25 #include "../curl_setup.h"
     26 
     27 #ifdef USE_QUICHE
     28 #include <quiche.h>
     29 #include <openssl/err.h>
     30 #include <openssl/ssl.h>
     31 #include "../bufq.h"
     32 #include "../uint-hash.h"
     33 #include "../urldata.h"
     34 #include "../cfilters.h"
     35 #include "../cf-socket.h"
     36 #include "../sendf.h"
     37 #include "../strdup.h"
     38 #include "../rand.h"
     39 #include "../multiif.h"
     40 #include "../connect.h"
     41 #include "../progress.h"
     42 #include "../strerror.h"
     43 #include "../http1.h"
     44 #include "vquic.h"
     45 #include "vquic_int.h"
     46 #include "vquic-tls.h"
     47 #include "curl_quiche.h"
     48 #include "../transfer.h"
     49 #include "../url.h"
     50 #include "../curlx/inet_pton.h"
     51 #include "../vtls/openssl.h"
     52 #include "../vtls/keylog.h"
     53 #include "../vtls/vtls.h"
     54 
     55 /* The last 3 #include files should be in this order */
     56 #include "../curl_printf.h"
     57 #include "../curl_memory.h"
     58 #include "../memdebug.h"
     59 
     60 /* HTTP/3 error values defined in RFC 9114, ch. 8.1 */
     61 #define CURL_H3_NO_ERROR  (0x0100)
     62 
     63 #define QUIC_MAX_STREAMS              (100)
     64 
     65 #define H3_STREAM_WINDOW_SIZE  (128 * 1024)
     66 #define H3_STREAM_CHUNK_SIZE    (16 * 1024)
     67 /* The pool keeps spares around and half of a full stream windows seems good.
     68  * More does not seem to improve performance. The benefit of the pool is that
     69  * stream buffer to not keep spares. Memory consumption goes down when streams
     70  * run empty, have a large upload done, etc. */
     71 #define H3_STREAM_POOL_SPARES \
     72           (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE ) / 2
     73 /* Receive and Send max number of chunks just follows from the
     74  * chunk size and window size */
     75 #define H3_STREAM_RECV_CHUNKS \
     76           (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE)
     77 #define H3_STREAM_SEND_CHUNKS \
     78           (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE)
     79 
     80 /*
     81  * Store quiche version info in this buffer.
     82  */
     83 void Curl_quiche_ver(char *p, size_t len)
     84 {
     85   (void)msnprintf(p, len, "quiche/%s", quiche_version());
     86 }
     87 
     88 struct cf_quiche_ctx {
     89   struct cf_quic_ctx q;
     90   struct ssl_peer peer;
     91   struct curl_tls_ctx tls;
     92   quiche_conn *qconn;
     93   quiche_config *cfg;
     94   quiche_h3_conn *h3c;
     95   quiche_h3_config *h3config;
     96   uint8_t scid[QUICHE_MAX_CONN_ID_LEN];
     97   struct curltime started_at;        /* time the current attempt started */
     98   struct curltime handshake_at;      /* time connect handshake finished */
     99   struct bufc_pool stream_bufcp;     /* chunk pool for streams */
    100   struct uint_hash streams;          /* hash `data->mid` to `stream_ctx` */
    101   curl_off_t data_recvd;
    102   BIT(initialized);
    103   BIT(goaway);                       /* got GOAWAY from server */
    104   BIT(x509_store_setup);             /* if x509 store has been set up */
    105   BIT(shutdown_started);             /* queued shutdown packets */
    106 };
    107 
    108 #ifdef DEBUG_QUICHE
    109 /* initialize debug log callback only once */
    110 static int debug_log_init = 0;
    111 static void quiche_debug_log(const char *line, void *argp)
    112 {
    113   (void)argp;
    114   fprintf(stderr, "%s\n", line);
    115 }
    116 #endif
    117 
    118 static void h3_stream_hash_free(unsigned int id, void *stream);
    119 
    120 static void cf_quiche_ctx_init(struct cf_quiche_ctx *ctx)
    121 {
    122   DEBUGASSERT(!ctx->initialized);
    123 #ifdef DEBUG_QUICHE
    124   if(!debug_log_init) {
    125     quiche_enable_debug_logging(quiche_debug_log, NULL);
    126     debug_log_init = 1;
    127   }
    128 #endif
    129   Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE,
    130                   H3_STREAM_POOL_SPARES);
    131   Curl_uint_hash_init(&ctx->streams, 63, h3_stream_hash_free);
    132   ctx->data_recvd = 0;
    133   ctx->initialized = TRUE;
    134 }
    135 
    136 static void cf_quiche_ctx_free(struct cf_quiche_ctx *ctx)
    137 {
    138   if(ctx && ctx->initialized) {
    139     /* quiche just freed it */
    140     ctx->tls.ossl.ssl = NULL;
    141     Curl_vquic_tls_cleanup(&ctx->tls);
    142     Curl_ssl_peer_cleanup(&ctx->peer);
    143     vquic_ctx_free(&ctx->q);
    144     Curl_bufcp_free(&ctx->stream_bufcp);
    145     Curl_uint_hash_destroy(&ctx->streams);
    146   }
    147   free(ctx);
    148 }
    149 
    150 static void cf_quiche_ctx_close(struct cf_quiche_ctx *ctx)
    151 {
    152   if(ctx->h3c)
    153     quiche_h3_conn_free(ctx->h3c);
    154   if(ctx->h3config)
    155     quiche_h3_config_free(ctx->h3config);
    156   if(ctx->qconn)
    157     quiche_conn_free(ctx->qconn);
    158   if(ctx->cfg)
    159     quiche_config_free(ctx->cfg);
    160 }
    161 
    162 static CURLcode cf_flush_egress(struct Curl_cfilter *cf,
    163                                 struct Curl_easy *data);
    164 
    165 /**
    166  * All about the H3 internals of a stream
    167  */
    168 struct h3_stream_ctx {
    169   curl_uint64_t id; /* HTTP/3 protocol stream identifier */
    170   struct bufq recvbuf; /* h3 response */
    171   struct h1_req_parser h1; /* h1 request parsing */
    172   curl_uint64_t error3; /* HTTP/3 stream error code */
    173   BIT(opened); /* TRUE after stream has been opened */
    174   BIT(closed); /* TRUE on stream close */
    175   BIT(reset);  /* TRUE on stream reset */
    176   BIT(send_closed); /* stream is locally closed */
    177   BIT(resp_hds_complete);  /* final response has been received */
    178   BIT(resp_got_header); /* TRUE when h3 stream has recvd some HEADER */
    179   BIT(quic_flow_blocked); /* stream is blocked by QUIC flow control */
    180 };
    181 
    182 static void h3_stream_ctx_free(struct h3_stream_ctx *stream)
    183 {
    184   Curl_bufq_free(&stream->recvbuf);
    185   Curl_h1_req_parse_free(&stream->h1);
    186   free(stream);
    187 }
    188 
    189 static void h3_stream_hash_free(unsigned int id, void *stream)
    190 {
    191   (void)id;
    192   DEBUGASSERT(stream);
    193   h3_stream_ctx_free((struct h3_stream_ctx *)stream);
    194 }
    195 
    196 typedef bool cf_quiche_svisit(struct Curl_cfilter *cf,
    197                               struct Curl_easy *sdata,
    198                               struct h3_stream_ctx *stream,
    199                               void *user_data);
    200 
    201 struct cf_quiche_visit_ctx {
    202   struct Curl_cfilter *cf;
    203   struct Curl_multi *multi;
    204   cf_quiche_svisit *cb;
    205   void *user_data;
    206 };
    207 
    208 static bool cf_quiche_stream_do(unsigned int mid, void *val, void *user_data)
    209 {
    210   struct cf_quiche_visit_ctx *vctx = user_data;
    211   struct h3_stream_ctx *stream = val;
    212   struct Curl_easy *sdata = Curl_multi_get_easy(vctx->multi, mid);
    213   if(sdata)
    214     return vctx->cb(vctx->cf, sdata, stream, vctx->user_data);
    215   return TRUE;
    216 }
    217 
    218 static void cf_quiche_for_all_streams(struct Curl_cfilter *cf,
    219                                       struct Curl_multi *multi,
    220                                       cf_quiche_svisit *do_cb,
    221                                       void *user_data)
    222 {
    223   struct cf_quiche_ctx *ctx = cf->ctx;
    224   struct cf_quiche_visit_ctx vctx;
    225   vctx.cf = cf;
    226   vctx.multi = multi;
    227   vctx.cb = do_cb;
    228   vctx.user_data = user_data;
    229   Curl_uint_hash_visit(&ctx->streams, cf_quiche_stream_do, &vctx);
    230 }
    231 
    232 static bool cf_quiche_do_resume(struct Curl_cfilter *cf,
    233                                 struct Curl_easy *sdata,
    234                                 struct h3_stream_ctx *stream,
    235                                 void *user_data)
    236 {
    237   (void)user_data;
    238   if(stream->quic_flow_blocked) {
    239     stream->quic_flow_blocked = FALSE;
    240     Curl_multi_mark_dirty(sdata);
    241     CURL_TRC_CF(sdata, cf, "[%"FMT_PRIu64"] unblock", stream->id);
    242   }
    243   return TRUE;
    244 }
    245 
    246 static bool cf_quiche_do_expire(struct Curl_cfilter *cf,
    247                                 struct Curl_easy *sdata,
    248                                 struct h3_stream_ctx *stream,
    249                                 void *user_data)
    250 {
    251   (void)stream;
    252   (void)user_data;
    253   CURL_TRC_CF(sdata, cf, "conn closed, mark as dirty");
    254   Curl_multi_mark_dirty(sdata);
    255   return TRUE;
    256 }
    257 
    258 static CURLcode h3_data_setup(struct Curl_cfilter *cf,
    259                               struct Curl_easy *data)
    260 {
    261   struct cf_quiche_ctx *ctx = cf->ctx;
    262   struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
    263 
    264   if(stream)
    265     return CURLE_OK;
    266 
    267   stream = calloc(1, sizeof(*stream));
    268   if(!stream)
    269     return CURLE_OUT_OF_MEMORY;
    270 
    271   stream->id = -1;
    272   Curl_bufq_initp(&stream->recvbuf, &ctx->stream_bufcp,
    273                   H3_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT);
    274   Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN);
    275 
    276   if(!Curl_uint_hash_set(&ctx->streams, data->mid, stream)) {
    277     h3_stream_ctx_free(stream);
    278     return CURLE_OUT_OF_MEMORY;
    279   }
    280 
    281   return CURLE_OK;
    282 }
    283 
    284 static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data)
    285 {
    286   struct cf_quiche_ctx *ctx = cf->ctx;
    287   struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
    288   CURLcode result;
    289 
    290   (void)cf;
    291   if(stream) {
    292     CURL_TRC_CF(data, cf, "[%"FMT_PRIu64"] easy handle is done", stream->id);
    293     if(ctx->qconn && !stream->closed) {
    294       quiche_conn_stream_shutdown(ctx->qconn, stream->id,
    295                                   QUICHE_SHUTDOWN_READ, CURL_H3_NO_ERROR);
    296       if(!stream->send_closed) {
    297         quiche_conn_stream_shutdown(ctx->qconn, stream->id,
    298                                     QUICHE_SHUTDOWN_WRITE, CURL_H3_NO_ERROR);
    299         stream->send_closed = TRUE;
    300       }
    301       stream->closed = TRUE;
    302       result = cf_flush_egress(cf, data);
    303       if(result)
    304         CURL_TRC_CF(data, cf, "data_done, flush egress -> %d", result);
    305     }
    306     Curl_uint_hash_remove(&ctx->streams, data->mid);
    307   }
    308 }
    309 
    310 static void cf_quiche_expire_conn_closed(struct Curl_cfilter *cf,
    311                                          struct Curl_easy *data)
    312 {
    313   DEBUGASSERT(data->multi);
    314   CURL_TRC_CF(data, cf, "conn closed, expire all transfers");
    315   cf_quiche_for_all_streams(cf, data->multi, cf_quiche_do_expire, NULL);
    316 }
    317 
    318 /*
    319  * write_resp_raw() copies response data in raw format to the `data`'s
    320   * receive buffer. If not enough space is available, it appends to the
    321  * `data`'s overflow buffer.
    322  */
    323 static CURLcode write_resp_raw(struct Curl_cfilter *cf,
    324                                struct Curl_easy *data,
    325                                const void *mem, size_t memlen)
    326 {
    327   struct cf_quiche_ctx *ctx = cf->ctx;
    328   struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
    329   CURLcode result = CURLE_OK;
    330   size_t nwritten;
    331 
    332   (void)cf;
    333   if(!stream)
    334     return CURLE_RECV_ERROR;
    335   result = Curl_bufq_write(&stream->recvbuf, mem, memlen, &nwritten);
    336   if(result)
    337     return result;
    338 
    339   if(nwritten < memlen) {
    340     /* This MUST not happen. Our recbuf is dimensioned to hold the
    341      * full max_stream_window and then some for this very reason. */
    342     DEBUGASSERT(0);
    343     return CURLE_RECV_ERROR;
    344   }
    345   return result;
    346 }
    347 
    348 struct cb_ctx {
    349   struct Curl_cfilter *cf;
    350   struct Curl_easy *data;
    351 };
    352 
    353 static int cb_each_header(uint8_t *name, size_t name_len,
    354                           uint8_t *value, size_t value_len,
    355                           void *argp)
    356 {
    357   struct cb_ctx *x = argp;
    358   struct cf_quiche_ctx *ctx = x->cf->ctx;
    359   struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, x->data);
    360   CURLcode result;
    361 
    362   if(!stream)
    363     return CURLE_OK;
    364 
    365   if((name_len == 7) && !strncmp(HTTP_PSEUDO_STATUS, (char *)name, 7)) {
    366     CURL_TRC_CF(x->data, x->cf, "[%" FMT_PRIu64 "] status: %.*s",
    367                 stream->id, (int)value_len, value);
    368     result = write_resp_raw(x->cf, x->data, "HTTP/3 ", sizeof("HTTP/3 ") - 1);
    369     if(!result)
    370       result = write_resp_raw(x->cf, x->data, value, value_len);
    371     if(!result)
    372       result = write_resp_raw(x->cf, x->data, " \r\n", 3);
    373   }
    374   else {
    375     CURL_TRC_CF(x->data, x->cf, "[%" FMT_PRIu64 "] header: %.*s: %.*s",
    376                 stream->id, (int)name_len, name,
    377                 (int)value_len, value);
    378     result = write_resp_raw(x->cf, x->data, name, name_len);
    379     if(!result)
    380       result = write_resp_raw(x->cf, x->data, ": ", 2);
    381     if(!result)
    382       result = write_resp_raw(x->cf, x->data, value, value_len);
    383     if(!result)
    384       result = write_resp_raw(x->cf, x->data, "\r\n", 2);
    385   }
    386   if(result) {
    387     CURL_TRC_CF(x->data, x->cf, "[%"FMT_PRIu64"] on header error %d",
    388                 stream->id, result);
    389   }
    390   return result;
    391 }
    392 
    393 static CURLcode stream_resp_read(void *reader_ctx,
    394                                  unsigned char *buf, size_t len,
    395                                  size_t *pnread)
    396 {
    397   struct cb_ctx *x = reader_ctx;
    398   struct cf_quiche_ctx *ctx = x->cf->ctx;
    399   struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, x->data);
    400   ssize_t nread;
    401 
    402   *pnread = 0;
    403   if(!stream)
    404     return CURLE_RECV_ERROR;
    405 
    406   nread = quiche_h3_recv_body(ctx->h3c, ctx->qconn, stream->id, buf, len);
    407   if(nread >= 0) {
    408     *pnread = (size_t)nread;
    409     return CURLE_OK;
    410   }
    411   else
    412     return CURLE_AGAIN;
    413 }
    414 
    415 static CURLcode cf_recv_body(struct Curl_cfilter *cf,
    416                              struct Curl_easy *data)
    417 {
    418   struct cf_quiche_ctx *ctx = cf->ctx;
    419   struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
    420   size_t nread;
    421   struct cb_ctx cb_ctx;
    422   CURLcode result = CURLE_OK;
    423 
    424   if(!stream)
    425     return CURLE_RECV_ERROR;
    426 
    427   if(!stream->resp_hds_complete) {
    428     result = write_resp_raw(cf, data, "\r\n", 2);
    429     if(result)
    430       return result;
    431     stream->resp_hds_complete = TRUE;
    432   }
    433 
    434   cb_ctx.cf = cf;
    435   cb_ctx.data = data;
    436   result = Curl_bufq_slurp(&stream->recvbuf,
    437                            stream_resp_read, &cb_ctx, &nread);
    438 
    439   if(result && result != CURLE_AGAIN) {
    440     CURL_TRC_CF(data, cf, "[%"FMT_PRIu64"] recv_body error %zu",
    441                 stream->id, nread);
    442     failf(data, "Error %d in HTTP/3 response body for stream[%"FMT_PRIu64"]",
    443           result, stream->id);
    444     stream->closed = TRUE;
    445     stream->reset = TRUE;
    446     stream->send_closed = TRUE;
    447     streamclose(cf->conn, "Reset of stream");
    448     return result;
    449   }
    450   return CURLE_OK;
    451 }
    452 
    453 #ifdef DEBUGBUILD
    454 static const char *cf_ev_name(quiche_h3_event *ev)
    455 {
    456   switch(quiche_h3_event_type(ev)) {
    457   case QUICHE_H3_EVENT_HEADERS:
    458     return "HEADERS";
    459   case QUICHE_H3_EVENT_DATA:
    460     return "DATA";
    461   case QUICHE_H3_EVENT_RESET:
    462     return "RESET";
    463   case QUICHE_H3_EVENT_FINISHED:
    464     return "FINISHED";
    465   case QUICHE_H3_EVENT_GOAWAY:
    466     return "GOAWAY";
    467   default:
    468     return "Unknown";
    469   }
    470 }
    471 #else
    472 #define cf_ev_name(x)   ""
    473 #endif
    474 
    475 static CURLcode h3_process_event(struct Curl_cfilter *cf,
    476                                  struct Curl_easy *data,
    477                                  struct h3_stream_ctx *stream,
    478                                  quiche_h3_event *ev)
    479 {
    480   struct cb_ctx cb_ctx;
    481   CURLcode result = CURLE_OK;
    482   int rc;
    483 
    484   if(!stream)
    485     return CURLE_OK;
    486   switch(quiche_h3_event_type(ev)) {
    487   case QUICHE_H3_EVENT_HEADERS:
    488     stream->resp_got_header = TRUE;
    489     cb_ctx.cf = cf;
    490     cb_ctx.data = data;
    491     rc = quiche_h3_event_for_each_header(ev, cb_each_header, &cb_ctx);
    492     if(rc) {
    493       failf(data, "Error %d in HTTP/3 response header for stream[%"
    494             FMT_PRIu64"]", rc, stream->id);
    495       return CURLE_RECV_ERROR;
    496     }
    497     CURL_TRC_CF(data, cf, "[%"FMT_PRIu64"] <- [HEADERS]", stream->id);
    498     break;
    499 
    500   case QUICHE_H3_EVENT_DATA:
    501     if(!stream->closed) {
    502       result = cf_recv_body(cf, data);
    503     }
    504     break;
    505 
    506   case QUICHE_H3_EVENT_RESET:
    507     CURL_TRC_CF(data, cf, "[%"FMT_PRIu64"] RESET", stream->id);
    508     stream->closed = TRUE;
    509     stream->reset = TRUE;
    510     stream->send_closed = TRUE;
    511     streamclose(cf->conn, "Reset of stream");
    512     break;
    513 
    514   case QUICHE_H3_EVENT_FINISHED:
    515     CURL_TRC_CF(data, cf, "[%"FMT_PRIu64"] CLOSED", stream->id);
    516     if(!stream->resp_hds_complete) {
    517       result = write_resp_raw(cf, data, "\r\n", 2);
    518       if(result)
    519         return result;
    520       stream->resp_hds_complete = TRUE;
    521     }
    522     stream->closed = TRUE;
    523     streamclose(cf->conn, "End of stream");
    524     break;
    525 
    526   case QUICHE_H3_EVENT_GOAWAY:
    527     CURL_TRC_CF(data, cf, "[%"FMT_PRIu64"] <- [GOAWAY]", stream->id);
    528     break;
    529 
    530   default:
    531     CURL_TRC_CF(data, cf, "[%"FMT_PRIu64"] recv, unhandled event %d",
    532                 stream->id, quiche_h3_event_type(ev));
    533     break;
    534   }
    535   return result;
    536 }
    537 
    538 static CURLcode cf_quiche_ev_process(struct Curl_cfilter *cf,
    539                                      struct Curl_easy *data,
    540                                      struct h3_stream_ctx *stream,
    541                                      quiche_h3_event *ev)
    542 {
    543   CURLcode result = h3_process_event(cf, data, stream, ev);
    544   Curl_multi_mark_dirty(data);
    545   if(result)
    546     CURL_TRC_CF(data, cf, "error processing event %s "
    547                 "for [%"FMT_PRIu64"] -> %d", cf_ev_name(ev),
    548                 stream->id, result);
    549   return result;
    550 }
    551 
    552 struct cf_quich_disp_ctx {
    553   curl_uint64_t stream_id;
    554   struct Curl_cfilter *cf;
    555   struct Curl_multi *multi;
    556   quiche_h3_event *ev;
    557   CURLcode result;
    558 };
    559 
    560 static bool cf_quiche_disp_event(unsigned int mid, void *val, void *user_data)
    561 {
    562   struct cf_quich_disp_ctx *dctx = user_data;
    563   struct h3_stream_ctx *stream = val;
    564 
    565   if(stream->id == dctx->stream_id) {
    566     struct Curl_easy *sdata = Curl_multi_get_easy(dctx->multi, mid);
    567     if(sdata)
    568       dctx->result = cf_quiche_ev_process(dctx->cf, sdata, stream, dctx->ev);
    569     return FALSE; /* stop iterating */
    570   }
    571   return TRUE;
    572 }
    573 
    574 static CURLcode cf_poll_events(struct Curl_cfilter *cf,
    575                                struct Curl_easy *data)
    576 {
    577   struct cf_quiche_ctx *ctx = cf->ctx;
    578   struct h3_stream_ctx *stream = NULL;
    579   quiche_h3_event *ev;
    580 
    581   /* Take in the events and distribute them to the transfers. */
    582   while(ctx->h3c) {
    583     curl_int64_t stream3_id = quiche_h3_conn_poll(ctx->h3c, ctx->qconn, &ev);
    584     if(stream3_id == QUICHE_H3_ERR_DONE) {
    585       break;
    586     }
    587     else if(stream3_id < 0) {
    588       CURL_TRC_CF(data, cf, "error poll: %"FMT_PRId64, stream3_id);
    589       return CURLE_HTTP3;
    590     }
    591     else {
    592       struct cf_quich_disp_ctx dctx;
    593       dctx.stream_id = (curl_uint64_t)stream3_id;
    594       dctx.cf = cf;
    595       dctx.multi = data->multi;
    596       dctx.ev = ev;
    597       dctx.result = CURLE_OK;
    598       stream = H3_STREAM_CTX(ctx, data);
    599       if(stream && stream->id == dctx.stream_id) {
    600         /* event for calling transfer */
    601         CURLcode result = cf_quiche_ev_process(cf, data, stream, ev);
    602         quiche_h3_event_free(ev);
    603         if(result)
    604           return result;
    605       }
    606       else {
    607         /* another transfer, do not return errors, as they are not for
    608          * the calling transfer */
    609         Curl_uint_hash_visit(&ctx->streams, cf_quiche_disp_event, &dctx);
    610         quiche_h3_event_free(ev);
    611       }
    612     }
    613   }
    614   return CURLE_OK;
    615 }
    616 
    617 struct recv_ctx {
    618   struct Curl_cfilter *cf;
    619   struct Curl_easy *data;
    620   int pkts;
    621 };
    622 
    623 static CURLcode recv_pkt(const unsigned char *pkt, size_t pktlen,
    624                          struct sockaddr_storage *remote_addr,
    625                          socklen_t remote_addrlen, int ecn,
    626                          void *userp)
    627 {
    628   struct recv_ctx *r = userp;
    629   struct cf_quiche_ctx *ctx = r->cf->ctx;
    630   quiche_recv_info recv_info;
    631   ssize_t nread;
    632 
    633   (void)ecn;
    634   ++r->pkts;
    635 
    636   recv_info.to = (struct sockaddr *)&ctx->q.local_addr;
    637   recv_info.to_len = ctx->q.local_addrlen;
    638   recv_info.from = (struct sockaddr *)remote_addr;
    639   recv_info.from_len = remote_addrlen;
    640 
    641   nread = quiche_conn_recv(ctx->qconn,
    642                            (unsigned char *)CURL_UNCONST(pkt), pktlen,
    643                            &recv_info);
    644   if(nread < 0) {
    645     if(QUICHE_ERR_DONE == nread) {
    646       if(quiche_conn_is_draining(ctx->qconn)) {
    647         CURL_TRC_CF(r->data, r->cf, "ingress, connection is draining");
    648         return CURLE_RECV_ERROR;
    649       }
    650       if(quiche_conn_is_closed(ctx->qconn)) {
    651         CURL_TRC_CF(r->data, r->cf, "ingress, connection is closed");
    652         return CURLE_RECV_ERROR;
    653       }
    654       CURL_TRC_CF(r->data, r->cf, "ingress, quiche is DONE");
    655       return CURLE_OK;
    656     }
    657     else if(QUICHE_ERR_TLS_FAIL == nread) {
    658       long verify_ok = SSL_get_verify_result(ctx->tls.ossl.ssl);
    659       if(verify_ok != X509_V_OK) {
    660         failf(r->data, "SSL certificate problem: %s",
    661               X509_verify_cert_error_string(verify_ok));
    662         return CURLE_PEER_FAILED_VERIFICATION;
    663       }
    664     }
    665     else {
    666       failf(r->data, "quiche_conn_recv() == %zd", nread);
    667       return CURLE_RECV_ERROR;
    668     }
    669   }
    670   else if((size_t)nread < pktlen) {
    671     CURL_TRC_CF(r->data, r->cf, "ingress, quiche only read %zd/%zu bytes",
    672                 nread, pktlen);
    673   }
    674 
    675   return CURLE_OK;
    676 }
    677 
    678 static CURLcode cf_process_ingress(struct Curl_cfilter *cf,
    679                                    struct Curl_easy *data)
    680 {
    681   struct cf_quiche_ctx *ctx = cf->ctx;
    682   struct recv_ctx rctx;
    683   CURLcode result;
    684 
    685   DEBUGASSERT(ctx->qconn);
    686   result = Curl_vquic_tls_before_recv(&ctx->tls, cf, data);
    687   if(result)
    688     return result;
    689 
    690   rctx.cf = cf;
    691   rctx.data = data;
    692   rctx.pkts = 0;
    693 
    694   result = vquic_recv_packets(cf, data, &ctx->q, 1000, recv_pkt, &rctx);
    695   if(result)
    696     return result;
    697 
    698   if(rctx.pkts > 0) {
    699     /* quiche digested ingress packets. It might have opened flow control
    700      * windows again. */
    701     DEBUGASSERT(data->multi);
    702     cf_quiche_for_all_streams(cf, data->multi, cf_quiche_do_resume, NULL);
    703   }
    704   return cf_poll_events(cf, data);
    705 }
    706 
    707 struct read_ctx {
    708   struct Curl_cfilter *cf;
    709   struct Curl_easy *data;
    710   quiche_send_info send_info;
    711 };
    712 
    713 static CURLcode read_pkt_to_send(void *userp,
    714                                  unsigned char *buf, size_t buflen,
    715                                  size_t *pnread)
    716 {
    717   struct read_ctx *x = userp;
    718   struct cf_quiche_ctx *ctx = x->cf->ctx;
    719   ssize_t n;
    720 
    721   *pnread = 0;
    722   n = quiche_conn_send(ctx->qconn, buf, buflen, &x->send_info);
    723   if(n == QUICHE_ERR_DONE)
    724     return CURLE_AGAIN;
    725 
    726   if(n < 0) {
    727     failf(x->data, "quiche_conn_send returned %zd", n);
    728     return CURLE_SEND_ERROR;
    729   }
    730   *pnread = (size_t)n;
    731   return CURLE_OK;
    732 }
    733 
    734 /*
    735  * flush_egress drains the buffers and sends off data.
    736  * Calls failf() on errors.
    737  */
    738 static CURLcode cf_flush_egress(struct Curl_cfilter *cf,
    739                                 struct Curl_easy *data)
    740 {
    741   struct cf_quiche_ctx *ctx = cf->ctx;
    742   size_t nread;
    743   CURLcode result;
    744   curl_int64_t expiry_ns;
    745   curl_int64_t timeout_ns;
    746   struct read_ctx readx;
    747   size_t pkt_count, gsolen;
    748 
    749   expiry_ns = quiche_conn_timeout_as_nanos(ctx->qconn);
    750   if(!expiry_ns) {
    751     quiche_conn_on_timeout(ctx->qconn);
    752     if(quiche_conn_is_closed(ctx->qconn)) {
    753       if(quiche_conn_is_timed_out(ctx->qconn))
    754         failf(data, "connection closed by idle timeout");
    755       else
    756         failf(data, "connection closed by server");
    757       /* Connection timed out, expire all transfers belonging to it
    758        * as will not get any more POLL events here. */
    759       cf_quiche_expire_conn_closed(cf, data);
    760       return CURLE_SEND_ERROR;
    761     }
    762   }
    763 
    764   result = vquic_flush(cf, data, &ctx->q);
    765   if(result) {
    766     if(result == CURLE_AGAIN) {
    767       Curl_expire(data, 1, EXPIRE_QUIC);
    768       return CURLE_OK;
    769     }
    770     return result;
    771   }
    772 
    773   readx.cf = cf;
    774   readx.data = data;
    775   memset(&readx.send_info, 0, sizeof(readx.send_info));
    776   pkt_count = 0;
    777   gsolen = quiche_conn_max_send_udp_payload_size(ctx->qconn);
    778   for(;;) {
    779     /* add the next packet to send, if any, to our buffer */
    780     result = Curl_bufq_sipn(&ctx->q.sendbuf, 0,
    781                             read_pkt_to_send, &readx, &nread);
    782     if(result) {
    783       if(result != CURLE_AGAIN)
    784         return result;
    785       /* Nothing more to add, flush and leave */
    786       result = vquic_send(cf, data, &ctx->q, gsolen);
    787       if(result) {
    788         if(result == CURLE_AGAIN) {
    789           Curl_expire(data, 1, EXPIRE_QUIC);
    790           return CURLE_OK;
    791         }
    792         return result;
    793       }
    794       goto out;
    795     }
    796 
    797     ++pkt_count;
    798     if(nread < gsolen || pkt_count >= MAX_PKT_BURST) {
    799       result = vquic_send(cf, data, &ctx->q, gsolen);
    800       if(result) {
    801         if(result == CURLE_AGAIN) {
    802           Curl_expire(data, 1, EXPIRE_QUIC);
    803           return CURLE_OK;
    804         }
    805         goto out;
    806       }
    807       pkt_count = 0;
    808     }
    809   }
    810 
    811 out:
    812   timeout_ns = quiche_conn_timeout_as_nanos(ctx->qconn);
    813   if(timeout_ns % 1000000)
    814     timeout_ns += 1000000;
    815     /* expire resolution is milliseconds */
    816   Curl_expire(data, (timeout_ns / 1000000), EXPIRE_QUIC);
    817   return result;
    818 }
    819 
    820 static CURLcode recv_closed_stream(struct Curl_cfilter *cf,
    821                                    struct Curl_easy *data,
    822                                    size_t *pnread)
    823 {
    824   struct cf_quiche_ctx *ctx = cf->ctx;
    825   struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
    826   CURLcode result = CURLE_OK;
    827 
    828   DEBUGASSERT(stream);
    829   *pnread = 0;
    830   if(stream->reset) {
    831     failf(data,
    832           "HTTP/3 stream %" FMT_PRIu64 " reset by server", stream->id);
    833     result = data->req.bytecount ? CURLE_PARTIAL_FILE : CURLE_HTTP3;
    834     CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] cf_recv, was reset -> %d",
    835                 stream->id, result);
    836   }
    837   else if(!stream->resp_got_header) {
    838     failf(data,
    839           "HTTP/3 stream %" FMT_PRIu64 " was closed cleanly, but before "
    840           "getting all response header fields, treated as error",
    841           stream->id);
    842     result = CURLE_HTTP3;
    843   }
    844   return result;
    845 }
    846 
    847 static CURLcode cf_quiche_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
    848                                char *buf, size_t len, size_t *pnread)
    849 {
    850   struct cf_quiche_ctx *ctx = cf->ctx;
    851   struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
    852   CURLcode result = CURLE_OK;
    853 
    854   *pnread = 0;
    855   vquic_ctx_update_time(&ctx->q);
    856 
    857   if(!stream)
    858     return CURLE_RECV_ERROR;
    859 
    860 
    861   if(!Curl_bufq_is_empty(&stream->recvbuf)) {
    862     result = Curl_bufq_cread(&stream->recvbuf, buf, len, pnread);
    863     CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] read recvbuf(len=%zu) "
    864                 "-> %d, %zu", stream->id, len, result, *pnread);
    865     if(result)
    866       goto out;
    867   }
    868 
    869   if(cf_process_ingress(cf, data)) {
    870     CURL_TRC_CF(data, cf, "cf_recv, error on ingress");
    871     result = CURLE_RECV_ERROR;
    872     goto out;
    873   }
    874 
    875   /* recvbuf had nothing before, maybe after progressing ingress? */
    876   if(!*pnread && !Curl_bufq_is_empty(&stream->recvbuf)) {
    877     result = Curl_bufq_cread(&stream->recvbuf, buf, len, pnread);
    878     CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] read recvbuf(len=%zu) "
    879                 "-> %d, %zu", stream->id, len, result, *pnread);
    880     if(result)
    881       goto out;
    882   }
    883 
    884   if(*pnread) {
    885     if(stream->closed)
    886       Curl_multi_mark_dirty(data);
    887   }
    888   else {
    889     if(stream->closed)
    890       result = recv_closed_stream(cf, data, pnread);
    891     else if(quiche_conn_is_draining(ctx->qconn)) {
    892       failf(data, "QUIC connection is draining");
    893       result = CURLE_HTTP3;
    894     }
    895     else
    896       result = CURLE_AGAIN;
    897   }
    898 
    899 out:
    900   result = Curl_1st_err(result, cf_flush_egress(cf, data));
    901   if(*pnread > 0)
    902     ctx->data_recvd += *pnread;
    903   CURL_TRC_CF(data, cf, "[%"FMT_PRIu64"] cf_recv(total=%"
    904               FMT_OFF_T ") -> %d, %zu",
    905               stream->id, ctx->data_recvd, result, *pnread);
    906   return result;
    907 }
    908 
    909 static CURLcode cf_quiche_send_body(struct Curl_cfilter *cf,
    910                                     struct Curl_easy *data,
    911                                     struct h3_stream_ctx *stream,
    912                                     const void *buf, size_t len, bool eos,
    913                                     size_t *pnwritten)
    914 {
    915   struct cf_quiche_ctx *ctx = cf->ctx;
    916   ssize_t nwritten;
    917 
    918   *pnwritten = 0;
    919   nwritten = quiche_h3_send_body(ctx->h3c, ctx->qconn, stream->id,
    920                                  (uint8_t *)CURL_UNCONST(buf), len, eos);
    921   if(nwritten == QUICHE_H3_ERR_DONE || (nwritten == 0 && len > 0)) {
    922     /* Blocked on flow control and should HOLD sending. But when do we open
    923      * again? */
    924     if(!quiche_conn_stream_writable(ctx->qconn, stream->id, len)) {
    925       CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] send_body(len=%zu) "
    926                   "-> window exhausted", stream->id, len);
    927       stream->quic_flow_blocked = TRUE;
    928     }
    929     return CURLE_AGAIN;
    930   }
    931   else if(nwritten == QUICHE_H3_TRANSPORT_ERR_INVALID_STREAM_STATE) {
    932     CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] send_body(len=%zu) "
    933                 "-> invalid stream state", stream->id, len);
    934     return CURLE_HTTP3;
    935   }
    936   else if(nwritten == QUICHE_H3_TRANSPORT_ERR_FINAL_SIZE) {
    937     CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] send_body(len=%zu) "
    938                 "-> exceeds size", stream->id, len);
    939     return CURLE_SEND_ERROR;
    940   }
    941   else if(nwritten < 0) {
    942     CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] send_body(len=%zu) "
    943                 "-> quiche err %zd", stream->id, len, nwritten);
    944     return CURLE_SEND_ERROR;
    945   }
    946   else {
    947     if(eos && (len == (size_t)nwritten))
    948       stream->send_closed = TRUE;
    949     CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] send body(len=%zu, "
    950                 "eos=%d) -> %zd",
    951                 stream->id, len, stream->send_closed, nwritten);
    952     *pnwritten = (size_t)nwritten;
    953     return CURLE_OK;
    954   }
    955 }
    956 
    957 /* Index where :authority header field will appear in request header
    958    field list. */
    959 #define AUTHORITY_DST_IDX 3
    960 
    961 static CURLcode h3_open_stream(struct Curl_cfilter *cf,
    962                                struct Curl_easy *data,
    963                                const char *buf, size_t blen, bool eos,
    964                                size_t *pnwritten)
    965 {
    966   struct cf_quiche_ctx *ctx = cf->ctx;
    967   struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
    968   size_t nheader, i;
    969   curl_int64_t stream3_id;
    970   struct dynhds h2_headers;
    971   quiche_h3_header *nva = NULL;
    972   CURLcode result = CURLE_OK;
    973   ssize_t nwritten;
    974 
    975   *pnwritten = 0;
    976   if(!stream) {
    977     result = h3_data_setup(cf, data);
    978     if(result)
    979       return result;
    980     stream = H3_STREAM_CTX(ctx, data);
    981     DEBUGASSERT(stream);
    982   }
    983 
    984   Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST);
    985 
    986   DEBUGASSERT(stream);
    987   nwritten = Curl_h1_req_parse_read(&stream->h1, buf, blen, NULL, 0, &result);
    988   if(nwritten < 0)
    989     goto out;
    990   if(!stream->h1.done) {
    991     /* need more data */
    992     goto out;
    993   }
    994   DEBUGASSERT(stream->h1.req);
    995 
    996   result = Curl_http_req_to_h2(&h2_headers, stream->h1.req, data);
    997   if(result)
    998     goto out;
    999 
   1000   /* no longer needed */
   1001   Curl_h1_req_parse_free(&stream->h1);
   1002 
   1003   nheader = Curl_dynhds_count(&h2_headers);
   1004   nva = malloc(sizeof(quiche_h3_header) * nheader);
   1005   if(!nva) {
   1006     result = CURLE_OUT_OF_MEMORY;
   1007     goto out;
   1008   }
   1009 
   1010   for(i = 0; i < nheader; ++i) {
   1011     struct dynhds_entry *e = Curl_dynhds_getn(&h2_headers, i);
   1012     nva[i].name = (unsigned char *)e->name;
   1013     nva[i].name_len = e->namelen;
   1014     nva[i].value = (unsigned char *)e->value;
   1015     nva[i].value_len = e->valuelen;
   1016   }
   1017 
   1018   *pnwritten = (size_t)nwritten;
   1019   buf += *pnwritten;
   1020   blen -= *pnwritten;
   1021 
   1022   if(eos && !blen)
   1023     stream->send_closed = TRUE;
   1024 
   1025   stream3_id = quiche_h3_send_request(ctx->h3c, ctx->qconn, nva, nheader,
   1026                                       stream->send_closed);
   1027   if(stream3_id < 0) {
   1028     if(QUICHE_H3_ERR_STREAM_BLOCKED == stream3_id) {
   1029       /* quiche seems to report this error if the connection window is
   1030        * exhausted. Which happens frequently and intermittent. */
   1031       CURL_TRC_CF(data, cf, "[%"FMT_PRIu64"] blocked", stream->id);
   1032       stream->quic_flow_blocked = TRUE;
   1033       result = CURLE_AGAIN;
   1034       goto out;
   1035     }
   1036     else {
   1037       CURL_TRC_CF(data, cf, "send_request(%s) -> %" FMT_PRIu64,
   1038                   data->state.url, stream3_id);
   1039     }
   1040     result = CURLE_SEND_ERROR;
   1041     goto out;
   1042   }
   1043 
   1044   DEBUGASSERT(!stream->opened);
   1045   stream->id = stream3_id;
   1046   stream->opened = TRUE;
   1047   stream->closed = FALSE;
   1048   stream->reset = FALSE;
   1049 
   1050   if(Curl_trc_is_verbose(data)) {
   1051     infof(data, "[HTTP/3] [%" FMT_PRIu64 "] OPENED stream for %s",
   1052           stream->id, data->state.url);
   1053     for(i = 0; i < nheader; ++i) {
   1054       infof(data, "[HTTP/3] [%" FMT_PRIu64 "] [%.*s: %.*s]", stream->id,
   1055             (int)nva[i].name_len, nva[i].name,
   1056             (int)nva[i].value_len, nva[i].value);
   1057     }
   1058   }
   1059 
   1060   if(blen) {  /* after the headers, there was request BODY data */
   1061     size_t bwritten;
   1062     CURLcode r2 = CURLE_OK;
   1063 
   1064     r2 = cf_quiche_send_body(cf, data, stream, buf, blen, eos, &bwritten);
   1065     if(r2 && (CURLE_AGAIN != r2)) {  /* real error, fail */
   1066       result = r2;
   1067     }
   1068     else if(bwritten > 0) {
   1069       *pnwritten += (size_t)bwritten;
   1070     }
   1071   }
   1072 
   1073 out:
   1074   free(nva);
   1075   Curl_dynhds_free(&h2_headers);
   1076   return result;
   1077 }
   1078 
   1079 static CURLcode cf_quiche_send(struct Curl_cfilter *cf, struct Curl_easy *data,
   1080                                const void *buf, size_t len, bool eos,
   1081                                size_t *pnwritten)
   1082 {
   1083   struct cf_quiche_ctx *ctx = cf->ctx;
   1084   struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
   1085   CURLcode result;
   1086 
   1087   *pnwritten = 0;
   1088   vquic_ctx_update_time(&ctx->q);
   1089 
   1090   result = cf_process_ingress(cf, data);
   1091   if(result)
   1092     goto out;
   1093 
   1094   if(!stream || !stream->opened) {
   1095     result = h3_open_stream(cf, data, buf, len, eos, pnwritten);
   1096     if(result)
   1097       goto out;
   1098     stream = H3_STREAM_CTX(ctx, data);
   1099   }
   1100   else if(stream->closed) {
   1101     if(stream->resp_hds_complete) {
   1102       /* sending request body on a stream that has been closed by the
   1103        * server. If the server has send us a final response, we should
   1104        * silently discard the send data.
   1105        * This happens for example on redirects where the server, instead
   1106        * of reading the full request body just closed the stream after
   1107        * sending the 30x response.
   1108        * This is sort of a race: had the transfer loop called recv first,
   1109        * it would see the response and stop/discard sending on its own- */
   1110       CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] discarding data"
   1111                   "on closed stream with response", stream->id);
   1112       result = CURLE_OK;
   1113       *pnwritten = len;
   1114       goto out;
   1115     }
   1116     CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] send_body(len=%zu) "
   1117                 "-> stream closed", stream->id, len);
   1118     result = CURLE_HTTP3;
   1119     goto out;
   1120   }
   1121   else {
   1122     result = cf_quiche_send_body(cf, data, stream, buf, len, eos, pnwritten);
   1123   }
   1124 
   1125 out:
   1126   result = Curl_1st_err(result, cf_flush_egress(cf, data));
   1127 
   1128   CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] cf_send(len=%zu) -> %d, %zu",
   1129               stream ? stream->id : (curl_uint64_t)~0, len,
   1130               result, *pnwritten);
   1131   return result;
   1132 }
   1133 
   1134 static bool stream_is_writeable(struct Curl_cfilter *cf,
   1135                                 struct Curl_easy *data)
   1136 {
   1137   struct cf_quiche_ctx *ctx = cf->ctx;
   1138   struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
   1139 
   1140   return stream && (quiche_conn_stream_writable(
   1141     ctx->qconn, (curl_uint64_t)stream->id, 1) > 0);
   1142 }
   1143 
   1144 static void cf_quiche_adjust_pollset(struct Curl_cfilter *cf,
   1145                                      struct Curl_easy *data,
   1146                                      struct easy_pollset *ps)
   1147 {
   1148   struct cf_quiche_ctx *ctx = cf->ctx;
   1149   bool want_recv, want_send;
   1150 
   1151   if(!ctx->qconn)
   1152     return;
   1153 
   1154   Curl_pollset_check(data, ps, ctx->q.sockfd, &want_recv, &want_send);
   1155   if(want_recv || want_send) {
   1156     struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
   1157     bool c_exhaust, s_exhaust;
   1158 
   1159     c_exhaust = FALSE; /* Have not found any call in quiche that tells
   1160                           us if the connection itself is blocked */
   1161     s_exhaust = want_send && stream && stream->opened &&
   1162                 (stream->quic_flow_blocked || !stream_is_writeable(cf, data));
   1163     want_recv = (want_recv || c_exhaust || s_exhaust);
   1164     want_send = (!s_exhaust && want_send) ||
   1165                  !Curl_bufq_is_empty(&ctx->q.sendbuf);
   1166 
   1167     Curl_pollset_set(data, ps, ctx->q.sockfd, want_recv, want_send);
   1168   }
   1169 }
   1170 
   1171 /*
   1172  * Called from transfer.c:data_pending to know if we should keep looping
   1173  * to receive more data from the connection.
   1174  */
   1175 static bool cf_quiche_data_pending(struct Curl_cfilter *cf,
   1176                                    const struct Curl_easy *data)
   1177 {
   1178   struct cf_quiche_ctx *ctx = cf->ctx;
   1179   const struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
   1180   (void)cf;
   1181   return stream && !Curl_bufq_is_empty(&stream->recvbuf);
   1182 }
   1183 
   1184 static CURLcode h3_data_pause(struct Curl_cfilter *cf,
   1185                               struct Curl_easy *data,
   1186                               bool pause)
   1187 {
   1188   /* There seems to exist no API in quiche to shrink/enlarge the streams
   1189    * windows. As we do in HTTP/2. */
   1190   (void)cf;
   1191   if(!pause) {
   1192     Curl_multi_mark_dirty(data);
   1193   }
   1194   return CURLE_OK;
   1195 }
   1196 
   1197 static CURLcode cf_quiche_data_event(struct Curl_cfilter *cf,
   1198                                      struct Curl_easy *data,
   1199                                      int event, int arg1, void *arg2)
   1200 {
   1201   struct cf_quiche_ctx *ctx = cf->ctx;
   1202   CURLcode result = CURLE_OK;
   1203 
   1204   (void)arg1;
   1205   (void)arg2;
   1206   switch(event) {
   1207   case CF_CTRL_DATA_SETUP:
   1208     break;
   1209   case CF_CTRL_DATA_PAUSE:
   1210     result = h3_data_pause(cf, data, (arg1 != 0));
   1211     break;
   1212   case CF_CTRL_DATA_DONE:
   1213     h3_data_done(cf, data);
   1214     break;
   1215   case CF_CTRL_DATA_DONE_SEND: {
   1216     struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
   1217     if(stream && !stream->send_closed) {
   1218       unsigned char body[1];
   1219       size_t sent;
   1220 
   1221       stream->send_closed = TRUE;
   1222       body[0] = 'X';
   1223       result = cf_quiche_send(cf, data, body, 0, TRUE, &sent);
   1224       CURL_TRC_CF(data, cf, "[%"FMT_PRIu64"] DONE_SEND -> %d, %zu",
   1225                   stream->id, result, sent);
   1226     }
   1227     break;
   1228   }
   1229   case CF_CTRL_DATA_IDLE: {
   1230     struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
   1231     if(stream && !stream->closed) {
   1232       result = cf_flush_egress(cf, data);
   1233       if(result)
   1234         CURL_TRC_CF(data, cf, "data idle, flush egress -> %d", result);
   1235     }
   1236     break;
   1237   }
   1238   default:
   1239     break;
   1240   }
   1241   return result;
   1242 }
   1243 
   1244 static CURLcode cf_quiche_ctx_open(struct Curl_cfilter *cf,
   1245                                    struct Curl_easy *data)
   1246 {
   1247   struct cf_quiche_ctx *ctx = cf->ctx;
   1248   int rv;
   1249   CURLcode result;
   1250   const struct Curl_sockaddr_ex *sockaddr;
   1251 static const struct alpn_spec ALPN_SPEC_H3 = {
   1252   { "h3" }, 1
   1253 };
   1254 
   1255   DEBUGASSERT(ctx->q.sockfd != CURL_SOCKET_BAD);
   1256   DEBUGASSERT(ctx->initialized);
   1257 
   1258   result = vquic_ctx_init(&ctx->q);
   1259   if(result)
   1260     return result;
   1261 
   1262   ctx->cfg = quiche_config_new(QUICHE_PROTOCOL_VERSION);
   1263   if(!ctx->cfg) {
   1264     failf(data, "cannot create quiche config");
   1265     return CURLE_FAILED_INIT;
   1266   }
   1267   quiche_config_enable_pacing(ctx->cfg, FALSE);
   1268   quiche_config_set_initial_max_data(ctx->cfg, (1 * 1024 * 1024)
   1269     /* (QUIC_MAX_STREAMS/2) * H3_STREAM_WINDOW_SIZE */);
   1270   quiche_config_set_initial_max_streams_bidi(ctx->cfg, QUIC_MAX_STREAMS);
   1271   quiche_config_set_initial_max_streams_uni(ctx->cfg, QUIC_MAX_STREAMS);
   1272   quiche_config_set_initial_max_stream_data_bidi_local(ctx->cfg,
   1273     H3_STREAM_WINDOW_SIZE);
   1274   quiche_config_set_initial_max_stream_data_bidi_remote(ctx->cfg,
   1275     H3_STREAM_WINDOW_SIZE);
   1276   quiche_config_set_initial_max_stream_data_uni(ctx->cfg,
   1277     H3_STREAM_WINDOW_SIZE);
   1278   quiche_config_set_disable_active_migration(ctx->cfg, TRUE);
   1279 
   1280   quiche_config_set_max_connection_window(ctx->cfg,
   1281     10 * QUIC_MAX_STREAMS * H3_STREAM_WINDOW_SIZE);
   1282   quiche_config_set_max_stream_window(ctx->cfg, 10 * H3_STREAM_WINDOW_SIZE);
   1283   quiche_config_set_application_protos(ctx->cfg,
   1284                        (uint8_t *)CURL_UNCONST(QUICHE_H3_APPLICATION_PROTOCOL),
   1285                                        sizeof(QUICHE_H3_APPLICATION_PROTOCOL)
   1286                                        - 1);
   1287 
   1288   result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->peer,
   1289                                &ALPN_SPEC_H3, NULL, NULL, cf, NULL);
   1290   if(result)
   1291     return result;
   1292 
   1293   result = Curl_rand(data, ctx->scid, sizeof(ctx->scid));
   1294   if(result)
   1295     return result;
   1296 
   1297   Curl_cf_socket_peek(cf->next, data, &ctx->q.sockfd, &sockaddr, NULL);
   1298   ctx->q.local_addrlen = sizeof(ctx->q.local_addr);
   1299   rv = getsockname(ctx->q.sockfd, (struct sockaddr *)&ctx->q.local_addr,
   1300                    &ctx->q.local_addrlen);
   1301   if(rv == -1)
   1302     return CURLE_QUIC_CONNECT_ERROR;
   1303 
   1304   ctx->qconn = quiche_conn_new_with_tls((const uint8_t *)ctx->scid,
   1305                                         sizeof(ctx->scid), NULL, 0,
   1306                                         (struct sockaddr *)&ctx->q.local_addr,
   1307                                         ctx->q.local_addrlen,
   1308                                         &sockaddr->curl_sa_addr,
   1309                                         sockaddr->addrlen,
   1310                                         ctx->cfg, ctx->tls.ossl.ssl, FALSE);
   1311   if(!ctx->qconn) {
   1312     failf(data, "cannot create quiche connection");
   1313     return CURLE_OUT_OF_MEMORY;
   1314   }
   1315 
   1316   /* Known to not work on Windows */
   1317 #if !defined(_WIN32) && defined(HAVE_QUICHE_CONN_SET_QLOG_FD)
   1318   {
   1319     int qfd;
   1320     (void)Curl_qlogdir(data, ctx->scid, sizeof(ctx->scid), &qfd);
   1321     if(qfd != -1)
   1322       quiche_conn_set_qlog_fd(ctx->qconn, qfd,
   1323                               "qlog title", "curl qlog");
   1324   }
   1325 #endif
   1326 
   1327   result = cf_flush_egress(cf, data);
   1328   if(result)
   1329     return result;
   1330 
   1331   {
   1332     unsigned char alpn_protocols[] = QUICHE_H3_APPLICATION_PROTOCOL;
   1333     unsigned alpn_len, offset = 0;
   1334 
   1335     /* Replace each ALPN length prefix by a comma. */
   1336     while(offset < sizeof(alpn_protocols) - 1) {
   1337       alpn_len = alpn_protocols[offset];
   1338       alpn_protocols[offset] = ',';
   1339       offset += 1 + alpn_len;
   1340     }
   1341 
   1342     CURL_TRC_CF(data, cf, "Sent QUIC client Initial, ALPN: %s",
   1343                 alpn_protocols + 1);
   1344   }
   1345 
   1346   return CURLE_OK;
   1347 }
   1348 
   1349 static CURLcode cf_quiche_verify_peer(struct Curl_cfilter *cf,
   1350                                       struct Curl_easy *data)
   1351 {
   1352   struct cf_quiche_ctx *ctx = cf->ctx;
   1353 
   1354   cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
   1355 
   1356   return Curl_vquic_tls_verify_peer(&ctx->tls, cf, data, &ctx->peer);
   1357 }
   1358 
   1359 static CURLcode cf_quiche_connect(struct Curl_cfilter *cf,
   1360                                   struct Curl_easy *data,
   1361                                   bool *done)
   1362 {
   1363   struct cf_quiche_ctx *ctx = cf->ctx;
   1364   CURLcode result = CURLE_OK;
   1365 
   1366   if(cf->connected) {
   1367     *done = TRUE;
   1368     return CURLE_OK;
   1369   }
   1370 
   1371   /* Connect the UDP filter first */
   1372   if(!cf->next->connected) {
   1373     result = Curl_conn_cf_connect(cf->next, data, done);
   1374     if(result || !*done)
   1375       return result;
   1376   }
   1377 
   1378   *done = FALSE;
   1379   vquic_ctx_update_time(&ctx->q);
   1380 
   1381   if(!ctx->qconn) {
   1382     result = cf_quiche_ctx_open(cf, data);
   1383     if(result)
   1384       goto out;
   1385     ctx->started_at = ctx->q.last_op;
   1386     result = cf_flush_egress(cf, data);
   1387     /* we do not expect to be able to recv anything yet */
   1388     goto out;
   1389   }
   1390 
   1391   result = cf_process_ingress(cf, data);
   1392   if(result)
   1393     goto out;
   1394 
   1395   result = cf_flush_egress(cf, data);
   1396   if(result)
   1397     goto out;
   1398 
   1399   if(quiche_conn_is_established(ctx->qconn)) {
   1400     ctx->handshake_at = ctx->q.last_op;
   1401     CURL_TRC_CF(data, cf, "handshake complete after %dms",
   1402                 (int)curlx_timediff(ctx->handshake_at, ctx->started_at));
   1403     result = cf_quiche_verify_peer(cf, data);
   1404     if(!result) {
   1405       CURL_TRC_CF(data, cf, "peer verified");
   1406       ctx->h3config = quiche_h3_config_new();
   1407       if(!ctx->h3config) {
   1408         result = CURLE_OUT_OF_MEMORY;
   1409         goto out;
   1410       }
   1411 
   1412       /* Create a new HTTP/3 connection on the QUIC connection. */
   1413       ctx->h3c = quiche_h3_conn_new_with_transport(ctx->qconn, ctx->h3config);
   1414       if(!ctx->h3c) {
   1415         result = CURLE_OUT_OF_MEMORY;
   1416         goto out;
   1417       }
   1418       cf->connected = TRUE;
   1419       cf->conn->alpn = CURL_HTTP_VERSION_3;
   1420       *done = TRUE;
   1421       connkeep(cf->conn, "HTTP/3 default");
   1422     }
   1423   }
   1424   else if(quiche_conn_is_draining(ctx->qconn)) {
   1425     /* When a QUIC server instance is shutting down, it may send us a
   1426      * CONNECTION_CLOSE right away. Our connection then enters the DRAINING
   1427      * state. The CONNECT may work in the near future again. Indicate
   1428      * that as a "weird" reply. */
   1429     result = CURLE_WEIRD_SERVER_REPLY;
   1430   }
   1431 
   1432 out:
   1433 #ifndef CURL_DISABLE_VERBOSE_STRINGS
   1434   if(result && result != CURLE_AGAIN) {
   1435     struct ip_quadruple ip;
   1436 
   1437     Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip);
   1438     infof(data, "connect to %s port %u failed: %s",
   1439           ip.remote_ip, ip.remote_port, curl_easy_strerror(result));
   1440   }
   1441 #endif
   1442   return result;
   1443 }
   1444 
   1445 static CURLcode cf_quiche_shutdown(struct Curl_cfilter *cf,
   1446                                    struct Curl_easy *data, bool *done)
   1447 {
   1448   struct cf_quiche_ctx *ctx = cf->ctx;
   1449   CURLcode result = CURLE_OK;
   1450 
   1451   if(cf->shutdown || !ctx || !ctx->qconn) {
   1452     *done = TRUE;
   1453     return CURLE_OK;
   1454   }
   1455 
   1456   *done = FALSE;
   1457   if(!ctx->shutdown_started) {
   1458     int err;
   1459 
   1460     ctx->shutdown_started = TRUE;
   1461     vquic_ctx_update_time(&ctx->q);
   1462     err = quiche_conn_close(ctx->qconn, TRUE, 0, NULL, 0);
   1463     if(err) {
   1464       CURL_TRC_CF(data, cf, "error %d adding shutdown packet, "
   1465                   "aborting shutdown", err);
   1466       result = CURLE_SEND_ERROR;
   1467       goto out;
   1468     }
   1469   }
   1470 
   1471   if(!Curl_bufq_is_empty(&ctx->q.sendbuf)) {
   1472     CURL_TRC_CF(data, cf, "shutdown, flushing sendbuf");
   1473     result = cf_flush_egress(cf, data);
   1474     if(result)
   1475       goto out;
   1476   }
   1477 
   1478   if(Curl_bufq_is_empty(&ctx->q.sendbuf)) {
   1479     /* sent everything, quiche does not seem to support a graceful
   1480      * shutdown waiting for a reply, so ware done. */
   1481     CURL_TRC_CF(data, cf, "shutdown completely sent off, done");
   1482     *done = TRUE;
   1483   }
   1484   else {
   1485     CURL_TRC_CF(data, cf, "shutdown sending blocked");
   1486   }
   1487 
   1488 out:
   1489   return result;
   1490 }
   1491 
   1492 static void cf_quiche_close(struct Curl_cfilter *cf, struct Curl_easy *data)
   1493 {
   1494   if(cf->ctx) {
   1495     bool done;
   1496     (void)cf_quiche_shutdown(cf, data, &done);
   1497     cf_quiche_ctx_close(cf->ctx);
   1498   }
   1499 }
   1500 
   1501 static void cf_quiche_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
   1502 {
   1503   (void)data;
   1504   if(cf->ctx) {
   1505     cf_quiche_ctx_free(cf->ctx);
   1506     cf->ctx = NULL;
   1507   }
   1508 }
   1509 
   1510 static CURLcode cf_quiche_query(struct Curl_cfilter *cf,
   1511                                 struct Curl_easy *data,
   1512                                 int query, int *pres1, void *pres2)
   1513 {
   1514   struct cf_quiche_ctx *ctx = cf->ctx;
   1515 
   1516   switch(query) {
   1517   case CF_QUERY_MAX_CONCURRENT: {
   1518     curl_uint64_t max_streams = CONN_ATTACHED(cf->conn);
   1519     if(!ctx->goaway && ctx->qconn) {
   1520       max_streams += quiche_conn_peer_streams_left_bidi(ctx->qconn);
   1521     }
   1522     *pres1 = (max_streams > INT_MAX) ? INT_MAX : (int)max_streams;
   1523     CURL_TRC_CF(data, cf, "query conn[%" FMT_OFF_T "]: "
   1524                 "MAX_CONCURRENT -> %d (%u in use)",
   1525                 cf->conn->connection_id, *pres1, CONN_ATTACHED(cf->conn));
   1526     return CURLE_OK;
   1527   }
   1528   case CF_QUERY_CONNECT_REPLY_MS:
   1529     if(ctx->q.got_first_byte) {
   1530       timediff_t ms = curlx_timediff(ctx->q.first_byte_at, ctx->started_at);
   1531       *pres1 = (ms < INT_MAX) ? (int)ms : INT_MAX;
   1532     }
   1533     else
   1534       *pres1 = -1;
   1535     return CURLE_OK;
   1536   case CF_QUERY_TIMER_CONNECT: {
   1537     struct curltime *when = pres2;
   1538     if(ctx->q.got_first_byte)
   1539       *when = ctx->q.first_byte_at;
   1540     return CURLE_OK;
   1541   }
   1542   case CF_QUERY_TIMER_APPCONNECT: {
   1543     struct curltime *when = pres2;
   1544     if(cf->connected)
   1545       *when = ctx->handshake_at;
   1546     return CURLE_OK;
   1547   }
   1548   case CF_QUERY_HTTP_VERSION:
   1549     *pres1 = 30;
   1550     return CURLE_OK;
   1551   case CF_QUERY_SSL_INFO:
   1552   case CF_QUERY_SSL_CTX_INFO: {
   1553     struct curl_tlssessioninfo *info = pres2;
   1554     if(Curl_vquic_tls_get_ssl_info(&ctx->tls,
   1555                                    (query == CF_QUERY_SSL_INFO), info))
   1556       return CURLE_OK;
   1557     break;
   1558   }
   1559   default:
   1560     break;
   1561   }
   1562   return cf->next ?
   1563     cf->next->cft->query(cf->next, data, query, pres1, pres2) :
   1564     CURLE_UNKNOWN_OPTION;
   1565 }
   1566 
   1567 static bool cf_quiche_conn_is_alive(struct Curl_cfilter *cf,
   1568                                     struct Curl_easy *data,
   1569                                     bool *input_pending)
   1570 {
   1571   struct cf_quiche_ctx *ctx = cf->ctx;
   1572   bool alive = TRUE;
   1573 
   1574   *input_pending = FALSE;
   1575   if(!ctx->qconn)
   1576     return FALSE;
   1577 
   1578   if(quiche_conn_is_closed(ctx->qconn)) {
   1579     if(quiche_conn_is_timed_out(ctx->qconn))
   1580       CURL_TRC_CF(data, cf, "connection was closed due to idle timeout");
   1581     else
   1582       CURL_TRC_CF(data, cf, "connection is closed");
   1583     return FALSE;
   1584   }
   1585 
   1586   if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending))
   1587     return FALSE;
   1588 
   1589   if(*input_pending) {
   1590     /* This happens before we have sent off a request and the connection is
   1591        not in use by any other transfer, there should not be any data here,
   1592        only "protocol frames" */
   1593     *input_pending = FALSE;
   1594     if(cf_process_ingress(cf, data))
   1595       alive = FALSE;
   1596     else {
   1597       alive = TRUE;
   1598     }
   1599   }
   1600 
   1601   return alive;
   1602 }
   1603 
   1604 struct Curl_cftype Curl_cft_http3 = {
   1605   "HTTP/3",
   1606   CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX | CF_TYPE_HTTP,
   1607   0,
   1608   cf_quiche_destroy,
   1609   cf_quiche_connect,
   1610   cf_quiche_close,
   1611   cf_quiche_shutdown,
   1612   cf_quiche_adjust_pollset,
   1613   cf_quiche_data_pending,
   1614   cf_quiche_send,
   1615   cf_quiche_recv,
   1616   cf_quiche_data_event,
   1617   cf_quiche_conn_is_alive,
   1618   Curl_cf_def_conn_keep_alive,
   1619   cf_quiche_query,
   1620 };
   1621 
   1622 CURLcode Curl_cf_quiche_create(struct Curl_cfilter **pcf,
   1623                                struct Curl_easy *data,
   1624                                struct connectdata *conn,
   1625                                const struct Curl_addrinfo *ai)
   1626 {
   1627   struct cf_quiche_ctx *ctx = NULL;
   1628   struct Curl_cfilter *cf = NULL, *udp_cf = NULL;
   1629   CURLcode result;
   1630 
   1631   (void)data;
   1632   (void)conn;
   1633   ctx = calloc(1, sizeof(*ctx));
   1634   if(!ctx) {
   1635     result = CURLE_OUT_OF_MEMORY;
   1636     goto out;
   1637   }
   1638   cf_quiche_ctx_init(ctx);
   1639 
   1640   result = Curl_cf_create(&cf, &Curl_cft_http3, ctx);
   1641   if(result)
   1642     goto out;
   1643 
   1644   result = Curl_cf_udp_create(&udp_cf, data, conn, ai, TRNSPRT_QUIC);
   1645   if(result)
   1646     goto out;
   1647 
   1648   udp_cf->conn = cf->conn;
   1649   udp_cf->sockindex = cf->sockindex;
   1650   cf->next = udp_cf;
   1651 
   1652 out:
   1653   *pcf = (!result) ? cf : NULL;
   1654   if(result) {
   1655     if(udp_cf)
   1656       Curl_conn_cf_discard_sub(cf, udp_cf, data, TRUE);
   1657     Curl_safefree(cf);
   1658     cf_quiche_ctx_free(ctx);
   1659   }
   1660 
   1661   return result;
   1662 }
   1663 
   1664 #endif