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