xref: /third_party/curl/lib/vquic/curl_msh3.c (revision 13498266)
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_MSH3
28
29#include "urldata.h"
30#include "timeval.h"
31#include "multiif.h"
32#include "sendf.h"
33#include "curl_trc.h"
34#include "cfilters.h"
35#include "cf-socket.h"
36#include "connect.h"
37#include "progress.h"
38#include "http1.h"
39#include "curl_msh3.h"
40#include "socketpair.h"
41#include "vtls/vtls.h"
42#include "vquic/vquic.h"
43
44/* The last 3 #include files should be in this order */
45#include "curl_printf.h"
46#include "curl_memory.h"
47#include "memdebug.h"
48
49#ifdef CURL_DISABLE_SOCKETPAIR
50#error "MSH3 cannot be build with CURL_DISABLE_SOCKETPAIR set"
51#endif
52
53#define H3_STREAM_WINDOW_SIZE (128 * 1024)
54#define H3_STREAM_CHUNK_SIZE   (16 * 1024)
55#define H3_STREAM_RECV_CHUNKS \
56          (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE)
57
58#ifdef _WIN32
59#define msh3_lock CRITICAL_SECTION
60#define msh3_lock_initialize(lock) InitializeCriticalSection(lock)
61#define msh3_lock_uninitialize(lock) DeleteCriticalSection(lock)
62#define msh3_lock_acquire(lock) EnterCriticalSection(lock)
63#define msh3_lock_release(lock) LeaveCriticalSection(lock)
64#else /* !_WIN32 */
65#include <pthread.h>
66#define msh3_lock pthread_mutex_t
67#define msh3_lock_initialize(lock) do { \
68  pthread_mutexattr_t attr; \
69  pthread_mutexattr_init(&attr); \
70  pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); \
71  pthread_mutex_init(lock, &attr); \
72  pthread_mutexattr_destroy(&attr); \
73}while(0)
74#define msh3_lock_uninitialize(lock) pthread_mutex_destroy(lock)
75#define msh3_lock_acquire(lock) pthread_mutex_lock(lock)
76#define msh3_lock_release(lock) pthread_mutex_unlock(lock)
77#endif /* _WIN32 */
78
79
80static void MSH3_CALL msh3_conn_connected(MSH3_CONNECTION *Connection,
81                                          void *IfContext);
82static void MSH3_CALL msh3_conn_shutdown_complete(MSH3_CONNECTION *Connection,
83                                          void *IfContext);
84static void MSH3_CALL msh3_conn_new_request(MSH3_CONNECTION *Connection,
85                                          void *IfContext,
86                                          MSH3_REQUEST *Request);
87static void MSH3_CALL msh3_header_received(MSH3_REQUEST *Request,
88                                           void *IfContext,
89                                           const MSH3_HEADER *Header);
90static bool MSH3_CALL msh3_data_received(MSH3_REQUEST *Request,
91                                        void *IfContext, uint32_t *Length,
92                                        const uint8_t *Data);
93static void MSH3_CALL msh3_complete(MSH3_REQUEST *Request, void *IfContext,
94                                    bool Aborted, uint64_t AbortError);
95static void MSH3_CALL msh3_shutdown_complete(MSH3_REQUEST *Request,
96                                             void *IfContext);
97static void MSH3_CALL msh3_data_sent(MSH3_REQUEST *Request,
98                                     void *IfContext, void *SendContext);
99
100
101void Curl_msh3_ver(char *p, size_t len)
102{
103  uint32_t v[4];
104  MsH3Version(v);
105  (void)msnprintf(p, len, "msh3/%d.%d.%d.%d", v[0], v[1], v[2], v[3]);
106}
107
108#define SP_LOCAL   0
109#define SP_REMOTE  1
110
111struct cf_msh3_ctx {
112  MSH3_API *api;
113  MSH3_CONNECTION *qconn;
114  struct Curl_sockaddr_ex addr;
115  curl_socket_t sock[2]; /* fake socket pair until we get support in msh3 */
116  char l_ip[MAX_IPADR_LEN];          /* local IP as string */
117  int l_port;                        /* local port number */
118  struct cf_call_data call_data;
119  struct curltime connect_started;   /* time the current attempt started */
120  struct curltime handshake_at;      /* time connect handshake finished */
121  /* Flags written by msh3/msquic thread */
122  bool handshake_complete;
123  bool handshake_succeeded;
124  bool connected;
125  /* Flags written by curl thread */
126  BIT(verbose);
127  BIT(active);
128};
129
130/* How to access `call_data` from a cf_msh3 filter */
131#undef CF_CTX_CALL_DATA
132#define CF_CTX_CALL_DATA(cf)  \
133  ((struct cf_msh3_ctx *)(cf)->ctx)->call_data
134
135/**
136 * All about the H3 internals of a stream
137 */
138struct stream_ctx {
139  struct MSH3_REQUEST *req;
140  struct bufq recvbuf;   /* h3 response */
141#ifdef _WIN32
142  CRITICAL_SECTION recv_lock;
143#else /* !_WIN32 */
144  pthread_mutex_t recv_lock;
145#endif /* _WIN32 */
146  uint64_t error3; /* HTTP/3 stream error code */
147  int status_code; /* HTTP status code */
148  CURLcode recv_error;
149  bool closed;
150  bool reset;
151  bool upload_done;
152  bool firstheader;  /* FALSE until headers arrive */
153  bool recv_header_complete;
154};
155
156#define H3_STREAM_CTX(d)    ((struct stream_ctx *)(((d) && (d)->req.p.http)? \
157                             ((struct HTTP *)(d)->req.p.http)->h3_ctx \
158                               : NULL))
159#define H3_STREAM_LCTX(d)   ((struct HTTP *)(d)->req.p.http)->h3_ctx
160#define H3_STREAM_ID(d)     (H3_STREAM_CTX(d)? \
161                             H3_STREAM_CTX(d)->id : -2)
162
163
164static CURLcode h3_data_setup(struct Curl_cfilter *cf,
165                              struct Curl_easy *data)
166{
167  struct stream_ctx *stream = H3_STREAM_CTX(data);
168
169  if(stream)
170    return CURLE_OK;
171
172  stream = calloc(1, sizeof(*stream));
173  if(!stream)
174    return CURLE_OUT_OF_MEMORY;
175
176  H3_STREAM_LCTX(data) = stream;
177  stream->req = ZERO_NULL;
178  msh3_lock_initialize(&stream->recv_lock);
179  Curl_bufq_init2(&stream->recvbuf, H3_STREAM_CHUNK_SIZE,
180                  H3_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT);
181  CURL_TRC_CF(data, cf, "data setup");
182  return CURLE_OK;
183}
184
185static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data)
186{
187  struct stream_ctx *stream = H3_STREAM_CTX(data);
188
189  (void)cf;
190  if(stream) {
191    CURL_TRC_CF(data, cf, "easy handle is done");
192    Curl_bufq_free(&stream->recvbuf);
193    free(stream);
194    H3_STREAM_LCTX(data) = NULL;
195  }
196}
197
198static void drain_stream_from_other_thread(struct Curl_easy *data,
199                                           struct stream_ctx *stream)
200{
201  unsigned char bits;
202
203  /* risky */
204  bits = CURL_CSELECT_IN;
205  if(stream && !stream->upload_done)
206    bits |= CURL_CSELECT_OUT;
207  if(data->state.select_bits != bits) {
208    data->state.select_bits = bits;
209    /* cannot expire from other thread */
210  }
211}
212
213static void drain_stream(struct Curl_cfilter *cf,
214                         struct Curl_easy *data)
215{
216  struct stream_ctx *stream = H3_STREAM_CTX(data);
217  unsigned char bits;
218
219  (void)cf;
220  bits = CURL_CSELECT_IN;
221  if(stream && !stream->upload_done)
222    bits |= CURL_CSELECT_OUT;
223  if(data->state.select_bits != bits) {
224    data->state.select_bits = bits;
225    Curl_expire(data, 0, EXPIRE_RUN_NOW);
226  }
227}
228
229static const MSH3_CONNECTION_IF msh3_conn_if = {
230  msh3_conn_connected,
231  msh3_conn_shutdown_complete,
232  msh3_conn_new_request
233};
234
235static void MSH3_CALL msh3_conn_connected(MSH3_CONNECTION *Connection,
236                                          void *IfContext)
237{
238  struct Curl_cfilter *cf = IfContext;
239  struct cf_msh3_ctx *ctx = cf->ctx;
240  struct Curl_easy *data = CF_DATA_CURRENT(cf);
241  (void)Connection;
242
243  CURL_TRC_CF(data, cf, "[MSH3] connected");
244  ctx->handshake_succeeded = true;
245  ctx->connected = true;
246  ctx->handshake_complete = true;
247}
248
249static void MSH3_CALL msh3_conn_shutdown_complete(MSH3_CONNECTION *Connection,
250                                          void *IfContext)
251{
252  struct Curl_cfilter *cf = IfContext;
253  struct cf_msh3_ctx *ctx = cf->ctx;
254  struct Curl_easy *data = CF_DATA_CURRENT(cf);
255
256  (void)Connection;
257  CURL_TRC_CF(data, cf, "[MSH3] shutdown complete");
258  ctx->connected = false;
259  ctx->handshake_complete = true;
260}
261
262static void MSH3_CALL msh3_conn_new_request(MSH3_CONNECTION *Connection,
263                                          void *IfContext,
264                                          MSH3_REQUEST *Request)
265{
266  (void)Connection;
267  (void)IfContext;
268  (void)Request;
269}
270
271static const MSH3_REQUEST_IF msh3_request_if = {
272  msh3_header_received,
273  msh3_data_received,
274  msh3_complete,
275  msh3_shutdown_complete,
276  msh3_data_sent
277};
278
279/* Decode HTTP status code.  Returns -1 if no valid status code was
280   decoded. (duplicate from http2.c) */
281static int decode_status_code(const char *value, size_t len)
282{
283  int i;
284  int res;
285
286  if(len != 3) {
287    return -1;
288  }
289
290  res = 0;
291
292  for(i = 0; i < 3; ++i) {
293    char c = value[i];
294
295    if(c < '0' || c > '9') {
296      return -1;
297    }
298
299    res *= 10;
300    res += c - '0';
301  }
302
303  return res;
304}
305
306/*
307 * write_resp_raw() copies response data in raw format to the `data`'s
308  * receive buffer. If not enough space is available, it appends to the
309 * `data`'s overflow buffer.
310 */
311static CURLcode write_resp_raw(struct Curl_easy *data,
312                               const void *mem, size_t memlen)
313{
314  struct stream_ctx *stream = H3_STREAM_CTX(data);
315  CURLcode result = CURLE_OK;
316  ssize_t nwritten;
317
318  if(!stream)
319    return CURLE_RECV_ERROR;
320
321  nwritten = Curl_bufq_write(&stream->recvbuf, mem, memlen, &result);
322  if(nwritten < 0) {
323    return result;
324  }
325
326  if((size_t)nwritten < memlen) {
327    /* This MUST not happen. Our recbuf is dimensioned to hold the
328     * full max_stream_window and then some for this very reason. */
329    DEBUGASSERT(0);
330    return CURLE_RECV_ERROR;
331  }
332  return result;
333}
334
335static void MSH3_CALL msh3_header_received(MSH3_REQUEST *Request,
336                                           void *userp,
337                                           const MSH3_HEADER *hd)
338{
339  struct Curl_easy *data = userp;
340  struct stream_ctx *stream = H3_STREAM_CTX(data);
341  CURLcode result;
342  (void)Request;
343
344  if(!stream || stream->recv_header_complete) {
345    return;
346  }
347
348  msh3_lock_acquire(&stream->recv_lock);
349
350  if((hd->NameLength == 7) &&
351     !strncmp(HTTP_PSEUDO_STATUS, (char *)hd->Name, 7)) {
352    char line[14]; /* status line is always 13 characters long */
353    size_t ncopy;
354
355    DEBUGASSERT(!stream->firstheader);
356    stream->status_code = decode_status_code(hd->Value, hd->ValueLength);
357    DEBUGASSERT(stream->status_code != -1);
358    ncopy = msnprintf(line, sizeof(line), "HTTP/3 %03d \r\n",
359                      stream->status_code);
360    result = write_resp_raw(data, line, ncopy);
361    if(result)
362      stream->recv_error = result;
363    stream->firstheader = TRUE;
364  }
365  else {
366    /* store as an HTTP1-style header */
367    DEBUGASSERT(stream->firstheader);
368    result = write_resp_raw(data, hd->Name, hd->NameLength);
369    if(!result)
370      result = write_resp_raw(data, ": ", 2);
371    if(!result)
372      result = write_resp_raw(data, hd->Value, hd->ValueLength);
373    if(!result)
374      result = write_resp_raw(data, "\r\n", 2);
375    if(result) {
376      stream->recv_error = result;
377    }
378  }
379
380  drain_stream_from_other_thread(data, stream);
381  msh3_lock_release(&stream->recv_lock);
382}
383
384static bool MSH3_CALL msh3_data_received(MSH3_REQUEST *Request,
385                                         void *IfContext, uint32_t *buflen,
386                                         const uint8_t *buf)
387{
388  struct Curl_easy *data = IfContext;
389  struct stream_ctx *stream = H3_STREAM_CTX(data);
390  CURLcode result;
391  bool rv = FALSE;
392
393  /* TODO: we would like to limit the amount of data we are buffer here.
394   * There seems to be no mechanism in msh3 to adjust flow control and
395   * it is undocumented what happens if we return FALSE here or less
396   * length (buflen is an inout parameter).
397   */
398  (void)Request;
399  if(!stream)
400    return FALSE;
401
402  msh3_lock_acquire(&stream->recv_lock);
403
404  if(!stream->recv_header_complete) {
405    result = write_resp_raw(data, "\r\n", 2);
406    if(result) {
407      stream->recv_error = result;
408      goto out;
409    }
410    stream->recv_header_complete = true;
411  }
412
413  result = write_resp_raw(data, buf, *buflen);
414  if(result) {
415    stream->recv_error = result;
416  }
417  rv = TRUE;
418
419out:
420  msh3_lock_release(&stream->recv_lock);
421  return rv;
422}
423
424static void MSH3_CALL msh3_complete(MSH3_REQUEST *Request, void *IfContext,
425                                    bool aborted, uint64_t error)
426{
427  struct Curl_easy *data = IfContext;
428  struct stream_ctx *stream = H3_STREAM_CTX(data);
429
430  (void)Request;
431  if(!stream)
432    return;
433  msh3_lock_acquire(&stream->recv_lock);
434  stream->closed = TRUE;
435  stream->recv_header_complete = true;
436  if(error)
437    stream->error3 = error;
438  if(aborted)
439    stream->reset = TRUE;
440  msh3_lock_release(&stream->recv_lock);
441}
442
443static void MSH3_CALL msh3_shutdown_complete(MSH3_REQUEST *Request,
444                                             void *IfContext)
445{
446  struct Curl_easy *data = IfContext;
447  struct stream_ctx *stream = H3_STREAM_CTX(data);
448
449  if(!stream)
450    return;
451  (void)Request;
452  (void)stream;
453}
454
455static void MSH3_CALL msh3_data_sent(MSH3_REQUEST *Request,
456                                     void *IfContext, void *SendContext)
457{
458  struct Curl_easy *data = IfContext;
459  struct stream_ctx *stream = H3_STREAM_CTX(data);
460  if(!stream)
461    return;
462  (void)Request;
463  (void)stream;
464  (void)SendContext;
465}
466
467static ssize_t recv_closed_stream(struct Curl_cfilter *cf,
468                                  struct Curl_easy *data,
469                                  CURLcode *err)
470{
471  struct stream_ctx *stream = H3_STREAM_CTX(data);
472  ssize_t nread = -1;
473
474  if(!stream) {
475    *err = CURLE_RECV_ERROR;
476    return -1;
477  }
478  (void)cf;
479  if(stream->reset) {
480    failf(data, "HTTP/3 stream reset by server");
481    *err = CURLE_PARTIAL_FILE;
482    CURL_TRC_CF(data, cf, "cf_recv, was reset -> %d", *err);
483    goto out;
484  }
485  else if(stream->error3) {
486    failf(data, "HTTP/3 stream was not closed cleanly: (error %zd)",
487          (ssize_t)stream->error3);
488    *err = CURLE_HTTP3;
489    CURL_TRC_CF(data, cf, "cf_recv, closed uncleanly -> %d", *err);
490    goto out;
491  }
492  else {
493    CURL_TRC_CF(data, cf, "cf_recv, closed ok -> %d", *err);
494  }
495  *err = CURLE_OK;
496  nread = 0;
497
498out:
499  return nread;
500}
501
502static void set_quic_expire(struct Curl_cfilter *cf, struct Curl_easy *data)
503{
504  struct stream_ctx *stream = H3_STREAM_CTX(data);
505
506  /* we have no indication from msh3 when it would be a good time
507   * to juggle the connection again. So, we compromise by calling
508   * us again every some milliseconds. */
509  (void)cf;
510  if(stream && stream->req && !stream->closed) {
511    Curl_expire(data, 10, EXPIRE_QUIC);
512  }
513  else {
514    Curl_expire(data, 50, EXPIRE_QUIC);
515  }
516}
517
518static ssize_t cf_msh3_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
519                            char *buf, size_t len, CURLcode *err)
520{
521  struct stream_ctx *stream = H3_STREAM_CTX(data);
522  ssize_t nread = -1;
523  struct cf_call_data save;
524
525  (void)cf;
526  if(!stream) {
527    *err = CURLE_RECV_ERROR;
528    return -1;
529  }
530  CF_DATA_SAVE(save, cf, data);
531  CURL_TRC_CF(data, cf, "req: recv with %zu byte buffer", len);
532
533  msh3_lock_acquire(&stream->recv_lock);
534
535  if(stream->recv_error) {
536    failf(data, "request aborted");
537    *err = stream->recv_error;
538    goto out;
539  }
540
541  *err = CURLE_OK;
542
543  if(!Curl_bufq_is_empty(&stream->recvbuf)) {
544    nread = Curl_bufq_read(&stream->recvbuf,
545                           (unsigned char *)buf, len, err);
546    CURL_TRC_CF(data, cf, "read recvbuf(len=%zu) -> %zd, %d",
547                len, nread, *err);
548    if(nread < 0)
549      goto out;
550    if(stream->closed)
551      drain_stream(cf, data);
552  }
553  else if(stream->closed) {
554    nread = recv_closed_stream(cf, data, err);
555    goto out;
556  }
557  else {
558    CURL_TRC_CF(data, cf, "req: nothing here, call again");
559    *err = CURLE_AGAIN;
560  }
561
562out:
563  msh3_lock_release(&stream->recv_lock);
564  set_quic_expire(cf, data);
565  CF_DATA_RESTORE(cf, save);
566  return nread;
567}
568
569static ssize_t cf_msh3_send(struct Curl_cfilter *cf, struct Curl_easy *data,
570                            const void *buf, size_t len, CURLcode *err)
571{
572  struct cf_msh3_ctx *ctx = cf->ctx;
573  struct stream_ctx *stream = H3_STREAM_CTX(data);
574  struct h1_req_parser h1;
575  struct dynhds h2_headers;
576  MSH3_HEADER *nva = NULL;
577  size_t nheader, i;
578  ssize_t nwritten = -1;
579  struct cf_call_data save;
580  bool eos;
581
582  CF_DATA_SAVE(save, cf, data);
583
584  Curl_h1_req_parse_init(&h1, H1_PARSE_DEFAULT_MAX_LINE_LEN);
585  Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST);
586
587  /* Sizes must match for cast below to work" */
588  DEBUGASSERT(stream);
589  CURL_TRC_CF(data, cf, "req: send %zu bytes", len);
590
591  if(!stream->req) {
592    /* The first send on the request contains the headers and possibly some
593       data. Parse out the headers and create the request, then if there is
594       any data left over go ahead and send it too. */
595    nwritten = Curl_h1_req_parse_read(&h1, buf, len, NULL, 0, err);
596    if(nwritten < 0)
597      goto out;
598    DEBUGASSERT(h1.done);
599    DEBUGASSERT(h1.req);
600
601    *err = Curl_http_req_to_h2(&h2_headers, h1.req, data);
602    if(*err) {
603      nwritten = -1;
604      goto out;
605    }
606
607    nheader = Curl_dynhds_count(&h2_headers);
608    nva = malloc(sizeof(MSH3_HEADER) * nheader);
609    if(!nva) {
610      *err = CURLE_OUT_OF_MEMORY;
611      nwritten = -1;
612      goto out;
613    }
614
615    for(i = 0; i < nheader; ++i) {
616      struct dynhds_entry *e = Curl_dynhds_getn(&h2_headers, i);
617      nva[i].Name = e->name;
618      nva[i].NameLength = e->namelen;
619      nva[i].Value = e->value;
620      nva[i].ValueLength = e->valuelen;
621    }
622
623    switch(data->state.httpreq) {
624    case HTTPREQ_POST:
625    case HTTPREQ_POST_FORM:
626    case HTTPREQ_POST_MIME:
627    case HTTPREQ_PUT:
628      /* known request body size or -1 */
629      eos = FALSE;
630      break;
631    default:
632      /* there is not request body */
633      eos = TRUE;
634      stream->upload_done = TRUE;
635      break;
636    }
637
638    CURL_TRC_CF(data, cf, "req: send %zu headers", nheader);
639    stream->req = MsH3RequestOpen(ctx->qconn, &msh3_request_if, data,
640                                  nva, nheader,
641                                  eos ? MSH3_REQUEST_FLAG_FIN :
642                                  MSH3_REQUEST_FLAG_NONE);
643    if(!stream->req) {
644      failf(data, "request open failed");
645      *err = CURLE_SEND_ERROR;
646      goto out;
647    }
648    *err = CURLE_OK;
649    nwritten = len;
650    goto out;
651  }
652  else {
653    /* request is open */
654    CURL_TRC_CF(data, cf, "req: send %zu body bytes", len);
655    if(len > 0xFFFFFFFF) {
656      len = 0xFFFFFFFF;
657    }
658
659    if(!MsH3RequestSend(stream->req, MSH3_REQUEST_FLAG_NONE, buf,
660                        (uint32_t)len, stream)) {
661      *err = CURLE_SEND_ERROR;
662      goto out;
663    }
664
665    /* TODO - msh3/msquic will hold onto this memory until the send complete
666       event. How do we make sure curl doesn't free it until then? */
667    *err = CURLE_OK;
668    nwritten = len;
669  }
670
671out:
672  set_quic_expire(cf, data);
673  free(nva);
674  Curl_h1_req_parse_free(&h1);
675  Curl_dynhds_free(&h2_headers);
676  CF_DATA_RESTORE(cf, save);
677  return nwritten;
678}
679
680static void cf_msh3_adjust_pollset(struct Curl_cfilter *cf,
681                                   struct Curl_easy *data,
682                                   struct easy_pollset *ps)
683{
684  struct cf_msh3_ctx *ctx = cf->ctx;
685  struct stream_ctx *stream = H3_STREAM_CTX(data);
686  struct cf_call_data save;
687
688  CF_DATA_SAVE(save, cf, data);
689  if(stream && ctx->sock[SP_LOCAL] != CURL_SOCKET_BAD) {
690    if(stream->recv_error) {
691      Curl_pollset_add_in(data, ps, ctx->sock[SP_LOCAL]);
692      drain_stream(cf, data);
693    }
694    else if(stream->req) {
695      Curl_pollset_add_out(data, ps, ctx->sock[SP_LOCAL]);
696      drain_stream(cf, data);
697    }
698  }
699}
700
701static bool cf_msh3_data_pending(struct Curl_cfilter *cf,
702                                 const struct Curl_easy *data)
703{
704  struct stream_ctx *stream = H3_STREAM_CTX(data);
705  struct cf_call_data save;
706  bool pending = FALSE;
707
708  CF_DATA_SAVE(save, cf, data);
709
710  (void)cf;
711  if(stream && stream->req) {
712    msh3_lock_acquire(&stream->recv_lock);
713    CURL_TRC_CF((struct Curl_easy *)data, cf, "data pending = %zu",
714                Curl_bufq_len(&stream->recvbuf));
715    pending = !Curl_bufq_is_empty(&stream->recvbuf);
716    msh3_lock_release(&stream->recv_lock);
717    if(pending)
718      drain_stream(cf, (struct Curl_easy *)data);
719  }
720
721  CF_DATA_RESTORE(cf, save);
722  return pending;
723}
724
725static void cf_msh3_active(struct Curl_cfilter *cf, struct Curl_easy *data)
726{
727  struct cf_msh3_ctx *ctx = cf->ctx;
728
729  /* use this socket from now on */
730  cf->conn->sock[cf->sockindex] = ctx->sock[SP_LOCAL];
731  /* the first socket info gets set at conn and data */
732  if(cf->sockindex == FIRSTSOCKET) {
733    cf->conn->remote_addr = &ctx->addr;
734  #ifdef ENABLE_IPV6
735    cf->conn->bits.ipv6 = (ctx->addr.family == AF_INET6)? TRUE : FALSE;
736  #endif
737    Curl_persistconninfo(data, cf->conn, ctx->l_ip, ctx->l_port);
738  }
739  ctx->active = TRUE;
740}
741
742static CURLcode h3_data_pause(struct Curl_cfilter *cf,
743                              struct Curl_easy *data,
744                              bool pause)
745{
746  if(!pause) {
747    drain_stream(cf, data);
748    Curl_expire(data, 0, EXPIRE_RUN_NOW);
749  }
750  return CURLE_OK;
751}
752
753static CURLcode cf_msh3_data_event(struct Curl_cfilter *cf,
754                                   struct Curl_easy *data,
755                                   int event, int arg1, void *arg2)
756{
757  struct stream_ctx *stream = H3_STREAM_CTX(data);
758  struct cf_call_data save;
759  CURLcode result = CURLE_OK;
760
761  CF_DATA_SAVE(save, cf, data);
762
763  (void)arg1;
764  (void)arg2;
765  switch(event) {
766  case CF_CTRL_DATA_SETUP:
767    result = h3_data_setup(cf, data);
768    break;
769  case CF_CTRL_DATA_PAUSE:
770    result = h3_data_pause(cf, data, (arg1 != 0));
771    break;
772  case CF_CTRL_DATA_DONE:
773    h3_data_done(cf, data);
774    break;
775  case CF_CTRL_DATA_DONE_SEND:
776    CURL_TRC_CF(data, cf, "req: send done");
777    if(stream) {
778      stream->upload_done = TRUE;
779      if(stream->req) {
780        char buf[1];
781        if(!MsH3RequestSend(stream->req, MSH3_REQUEST_FLAG_FIN,
782                            buf, 0, data)) {
783          result = CURLE_SEND_ERROR;
784        }
785      }
786    }
787    break;
788  case CF_CTRL_CONN_INFO_UPDATE:
789    CURL_TRC_CF(data, cf, "req: update info");
790    cf_msh3_active(cf, data);
791    break;
792  default:
793    break;
794  }
795
796  CF_DATA_RESTORE(cf, save);
797  return result;
798}
799
800static CURLcode cf_connect_start(struct Curl_cfilter *cf,
801                                 struct Curl_easy *data)
802{
803  struct cf_msh3_ctx *ctx = cf->ctx;
804  struct ssl_primary_config *conn_config;
805  MSH3_ADDR addr = {0};
806  CURLcode result;
807  bool verify;
808
809  conn_config = Curl_ssl_cf_get_primary_config(cf);
810  if(!conn_config)
811    return CURLE_FAILED_INIT;
812  verify = !!conn_config->verifypeer;
813
814  memcpy(&addr, &ctx->addr.sa_addr, ctx->addr.addrlen);
815  MSH3_SET_PORT(&addr, (uint16_t)cf->conn->remote_port);
816
817  if(verify && (conn_config->CAfile || conn_config->CApath)) {
818    /* TODO: need a way to provide trust anchors to MSH3 */
819#ifdef DEBUGBUILD
820    /* we need this for our test cases to run */
821    CURL_TRC_CF(data, cf, "non-standard CA not supported, "
822                "switching off verifypeer in DEBUG mode");
823    verify = 0;
824#else
825    CURL_TRC_CF(data, cf, "non-standard CA not supported, "
826                "attempting with built-in verification");
827#endif
828  }
829
830  CURL_TRC_CF(data, cf, "connecting to %s:%d (verify=%d)",
831              cf->conn->host.name, (int)cf->conn->remote_port, verify);
832
833  ctx->api = MsH3ApiOpen();
834  if(!ctx->api) {
835    failf(data, "can't create msh3 api");
836    return CURLE_FAILED_INIT;
837  }
838
839  ctx->qconn = MsH3ConnectionOpen(ctx->api,
840                                  &msh3_conn_if,
841                                  cf,
842                                  cf->conn->host.name,
843                                  &addr,
844                                  !verify);
845  if(!ctx->qconn) {
846    failf(data, "can't create msh3 connection");
847    if(ctx->api) {
848      MsH3ApiClose(ctx->api);
849      ctx->api = NULL;
850    }
851    return CURLE_FAILED_INIT;
852  }
853
854  result = h3_data_setup(cf, data);
855  if(result)
856    return result;
857
858  return CURLE_OK;
859}
860
861static CURLcode cf_msh3_connect(struct Curl_cfilter *cf,
862                                struct Curl_easy *data,
863                                bool blocking, bool *done)
864{
865  struct cf_msh3_ctx *ctx = cf->ctx;
866  struct cf_call_data save;
867  CURLcode result = CURLE_OK;
868
869  (void)blocking;
870  if(cf->connected) {
871    *done = TRUE;
872    return CURLE_OK;
873  }
874
875  CF_DATA_SAVE(save, cf, data);
876
877  if(ctx->sock[SP_LOCAL] == CURL_SOCKET_BAD) {
878    if(Curl_socketpair(AF_UNIX, SOCK_STREAM, 0, &ctx->sock[0]) < 0) {
879      ctx->sock[SP_LOCAL] = CURL_SOCKET_BAD;
880      ctx->sock[SP_REMOTE] = CURL_SOCKET_BAD;
881      return CURLE_COULDNT_CONNECT;
882    }
883  }
884
885  *done = FALSE;
886  if(!ctx->qconn) {
887    ctx->connect_started = Curl_now();
888    result = cf_connect_start(cf, data);
889    if(result)
890      goto out;
891  }
892
893  if(ctx->handshake_complete) {
894    ctx->handshake_at = Curl_now();
895    if(ctx->handshake_succeeded) {
896      CURL_TRC_CF(data, cf, "handshake succeeded");
897      cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
898      cf->conn->httpversion = 30;
899      cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX;
900      cf->connected = TRUE;
901      cf->conn->alpn = CURL_HTTP_VERSION_3;
902      *done = TRUE;
903      connkeep(cf->conn, "HTTP/3 default");
904      Curl_pgrsTime(data, TIMER_APPCONNECT);
905    }
906    else {
907      failf(data, "failed to connect, handshake failed");
908      result = CURLE_COULDNT_CONNECT;
909    }
910  }
911
912out:
913  CF_DATA_RESTORE(cf, save);
914  return result;
915}
916
917static void cf_msh3_close(struct Curl_cfilter *cf, struct Curl_easy *data)
918{
919  struct cf_msh3_ctx *ctx = cf->ctx;
920  struct cf_call_data save;
921
922  (void)data;
923  CF_DATA_SAVE(save, cf, data);
924
925  if(ctx) {
926    CURL_TRC_CF(data, cf, "destroying");
927    if(ctx->qconn) {
928      MsH3ConnectionClose(ctx->qconn);
929      ctx->qconn = NULL;
930    }
931    if(ctx->api) {
932      MsH3ApiClose(ctx->api);
933      ctx->api = NULL;
934    }
935
936    if(ctx->active) {
937      /* We share our socket at cf->conn->sock[cf->sockindex] when active.
938       * If it is no longer there, someone has stolen (and hopefully
939       * closed it) and we just forget about it.
940       */
941      ctx->active = FALSE;
942      if(ctx->sock[SP_LOCAL] == cf->conn->sock[cf->sockindex]) {
943        CURL_TRC_CF(data, cf, "cf_msh3_close(%d) active",
944                    (int)ctx->sock[SP_LOCAL]);
945        cf->conn->sock[cf->sockindex] = CURL_SOCKET_BAD;
946      }
947      else {
948        CURL_TRC_CF(data, cf, "cf_socket_close(%d) no longer at "
949                    "conn->sock[], discarding", (int)ctx->sock[SP_LOCAL]);
950        ctx->sock[SP_LOCAL] = CURL_SOCKET_BAD;
951      }
952      if(cf->sockindex == FIRSTSOCKET)
953        cf->conn->remote_addr = NULL;
954    }
955    if(ctx->sock[SP_LOCAL] != CURL_SOCKET_BAD) {
956      sclose(ctx->sock[SP_LOCAL]);
957    }
958    if(ctx->sock[SP_REMOTE] != CURL_SOCKET_BAD) {
959      sclose(ctx->sock[SP_REMOTE]);
960    }
961    ctx->sock[SP_LOCAL] = CURL_SOCKET_BAD;
962    ctx->sock[SP_REMOTE] = CURL_SOCKET_BAD;
963  }
964  CF_DATA_RESTORE(cf, save);
965}
966
967static void cf_msh3_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
968{
969  struct cf_call_data save;
970
971  CF_DATA_SAVE(save, cf, data);
972  cf_msh3_close(cf, data);
973  free(cf->ctx);
974  cf->ctx = NULL;
975  /* no CF_DATA_RESTORE(cf, save); its gone */
976
977}
978
979static CURLcode cf_msh3_query(struct Curl_cfilter *cf,
980                              struct Curl_easy *data,
981                              int query, int *pres1, void *pres2)
982{
983  struct cf_msh3_ctx *ctx = cf->ctx;
984
985  switch(query) {
986  case CF_QUERY_MAX_CONCURRENT: {
987    /* TODO: we do not have access to this so far, fake it */
988    (void)ctx;
989    *pres1 = 100;
990    return CURLE_OK;
991  }
992  case CF_QUERY_TIMER_CONNECT: {
993    struct curltime *when = pres2;
994    /* we do not know when the first byte arrived */
995    if(cf->connected)
996      *when = ctx->handshake_at;
997    return CURLE_OK;
998  }
999  case CF_QUERY_TIMER_APPCONNECT: {
1000    struct curltime *when = pres2;
1001    if(cf->connected)
1002      *when = ctx->handshake_at;
1003    return CURLE_OK;
1004  }
1005  default:
1006    break;
1007  }
1008  return cf->next?
1009    cf->next->cft->query(cf->next, data, query, pres1, pres2) :
1010    CURLE_UNKNOWN_OPTION;
1011}
1012
1013static bool cf_msh3_conn_is_alive(struct Curl_cfilter *cf,
1014                                  struct Curl_easy *data,
1015                                  bool *input_pending)
1016{
1017  struct cf_msh3_ctx *ctx = cf->ctx;
1018
1019  (void)data;
1020  *input_pending = FALSE;
1021  return ctx && ctx->sock[SP_LOCAL] != CURL_SOCKET_BAD && ctx->qconn &&
1022         ctx->connected;
1023}
1024
1025struct Curl_cftype Curl_cft_http3 = {
1026  "HTTP/3",
1027  CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX,
1028  0,
1029  cf_msh3_destroy,
1030  cf_msh3_connect,
1031  cf_msh3_close,
1032  Curl_cf_def_get_host,
1033  cf_msh3_adjust_pollset,
1034  cf_msh3_data_pending,
1035  cf_msh3_send,
1036  cf_msh3_recv,
1037  cf_msh3_data_event,
1038  cf_msh3_conn_is_alive,
1039  Curl_cf_def_conn_keep_alive,
1040  cf_msh3_query,
1041};
1042
1043CURLcode Curl_cf_msh3_create(struct Curl_cfilter **pcf,
1044                             struct Curl_easy *data,
1045                             struct connectdata *conn,
1046                             const struct Curl_addrinfo *ai)
1047{
1048  struct cf_msh3_ctx *ctx = NULL;
1049  struct Curl_cfilter *cf = NULL;
1050  CURLcode result;
1051
1052  (void)data;
1053  (void)conn;
1054  (void)ai; /* TODO: msh3 resolves itself? */
1055  ctx = calloc(1, sizeof(*ctx));
1056  if(!ctx) {
1057    result = CURLE_OUT_OF_MEMORY;
1058    goto out;
1059  }
1060  Curl_sock_assign_addr(&ctx->addr, ai, TRNSPRT_QUIC);
1061  ctx->sock[SP_LOCAL] = CURL_SOCKET_BAD;
1062  ctx->sock[SP_REMOTE] = CURL_SOCKET_BAD;
1063
1064  result = Curl_cf_create(&cf, &Curl_cft_http3, ctx);
1065
1066out:
1067  *pcf = (!result)? cf : NULL;
1068  if(result) {
1069    Curl_safefree(cf);
1070    Curl_safefree(ctx);
1071  }
1072
1073  return result;
1074}
1075
1076bool Curl_conn_is_msh3(const struct Curl_easy *data,
1077                       const struct connectdata *conn,
1078                       int sockindex)
1079{
1080  struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL;
1081
1082  (void)data;
1083  for(; cf; cf = cf->next) {
1084    if(cf->cft == &Curl_cft_http3)
1085      return TRUE;
1086    if(cf->cft->flags & CF_TYPE_IP_CONNECT)
1087      return FALSE;
1088  }
1089  return FALSE;
1090}
1091
1092#endif /* USE_MSH3 */
1093