/src/h2o/lib/common/http3client.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2018 Fastly, Kazuho Oku |
3 | | * |
4 | | * Permission is hereby granted, free of charge, to any person obtaining a copy |
5 | | * of this software and associated documentation files (the "Software"), to |
6 | | * deal in the Software without restriction, including without limitation the |
7 | | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or |
8 | | * sell copies of the Software, and to permit persons to whom the Software is |
9 | | * furnished to do so, subject to the following conditions: |
10 | | * |
11 | | * The above copyright notice and this permission notice shall be included in |
12 | | * all copies or substantial portions of the Software. |
13 | | * |
14 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
15 | | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
16 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
17 | | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
18 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
19 | | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
20 | | * IN THE SOFTWARE. |
21 | | */ |
22 | | #include <assert.h> |
23 | | #include <errno.h> |
24 | | #include <stdlib.h> |
25 | | #include <sys/types.h> |
26 | | #include "quicly.h" |
27 | | #include "h2o.h" |
28 | | #include "h2o/hostinfo.h" |
29 | | #include "h2o/httpclient.h" |
30 | | #include "h2o/http2_common.h" |
31 | | #include "h2o/http3_common.h" |
32 | | #include "h2o/http3_internal.h" |
33 | | #include "../probes_.h" |
34 | | |
35 | | /** |
36 | | * internal error code used for signalling EOS |
37 | | */ |
38 | 0 | #define ERROR_EOS H2O_HTTP3_ERROR_USER1 |
39 | | /** |
40 | | * Maxmium amount of unsent bytes to be buffered when acting as a tunnel. |
41 | | */ |
42 | | #define TUNNEL_MAX_UNSENT 16384 |
43 | | |
44 | | struct st_h2o_http3client_req_t { |
45 | | /** |
46 | | * superclass |
47 | | */ |
48 | | h2o_httpclient_t super; |
49 | | /** |
50 | | * pointer to the connection |
51 | | */ |
52 | | struct st_h2o_httpclient__h3_conn_t *conn; |
53 | | /** |
54 | | * is NULL until connection is established |
55 | | */ |
56 | | quicly_stream_t *quic; |
57 | | /** |
58 | | * currently only used for pending_requests |
59 | | */ |
60 | | h2o_linklist_t link; |
61 | | /** |
62 | | * |
63 | | */ |
64 | | uint64_t bytes_left_in_data_frame; |
65 | | /** |
66 | | * |
67 | | */ |
68 | | h2o_buffer_t *sendbuf; |
69 | | /** |
70 | | * |
71 | | */ |
72 | | struct { |
73 | | /** |
74 | | * HTTP-level buffer that contains (part of) response body received. Is the variable registered as `h2o_httpclient::buf`. |
75 | | */ |
76 | | h2o_buffer_t *body; |
77 | | /** |
78 | | * QUIC stream-level buffer that contains bytes that have not yet been processed at the HTTP/3 framing decoding level. This |
79 | | * buffer may have gaps. The beginning offset of `partial_frame` is equal to `recvstate.data_off`. |
80 | | */ |
81 | | h2o_buffer_t *stream; |
82 | | /** |
83 | | * Retains the amount of stream-level data that was available in the previous call. This value is used to see if processing |
84 | | * of new stream data is necessary. |
85 | | */ |
86 | | size_t prev_bytes_available; |
87 | | } recvbuf; |
88 | | /** |
89 | | * called when new contigious data becomes available |
90 | | */ |
91 | | quicly_error_t (*handle_input)(struct st_h2o_http3client_req_t *req, const uint8_t **src, const uint8_t *src_end, |
92 | | quicly_error_t err, const char **err_desc); |
93 | | /** |
94 | | * `proceed_req` callback. The callback is invoked when all bytes in the send buffer is emitted for the first time. |
95 | | * `bytes_inflight` contains the number of bytes being transmitted, or SIZE_MAX if nothing is inflight. |
96 | | */ |
97 | | struct { |
98 | | h2o_httpclient_proceed_req_cb cb; |
99 | | size_t bytes_inflight; |
100 | | } proceed_req; |
101 | | /** |
102 | | * |
103 | | */ |
104 | | enum { |
105 | | H2O_HTTP3CLIENT_RESPONSE_STATE_HEAD, |
106 | | H2O_HTTP3CLIENT_RESPONSE_STATE_BODY, |
107 | | H2O_HTTP3CLIENT_RESPONSE_STATE_CLOSED |
108 | | } response_state; |
109 | | /** |
110 | | * callback used for forwarding CONNECT-UDP using H3_DATAGRAMS |
111 | | */ |
112 | | h2o_httpclient_forward_datagram_cb on_read_datagrams; |
113 | | /** |
114 | | * flags |
115 | | */ |
116 | | unsigned offered_datagram_flow_id : 1; |
117 | | }; |
118 | | |
119 | | static quicly_error_t handle_input_expect_data_frame(struct st_h2o_http3client_req_t *req, const uint8_t **src, |
120 | | const uint8_t *src_end, quicly_error_t err, const char **err_desc); |
121 | | static void start_request(struct st_h2o_http3client_req_t *req); |
122 | | static int do_write_req(h2o_httpclient_t *_client, h2o_iovec_t chunk, int is_end_stream); |
123 | | |
124 | | static size_t emit_data(struct st_h2o_http3client_req_t *req, h2o_iovec_t payload) |
125 | 0 | { |
126 | 0 | size_t nbytes; |
127 | |
|
128 | 0 | { /* emit header */ |
129 | 0 | uint8_t buf[9], *p = buf; |
130 | 0 | *p++ = H2O_HTTP3_FRAME_TYPE_DATA; |
131 | 0 | p = quicly_encodev(p, payload.len); |
132 | 0 | nbytes = p - buf; |
133 | 0 | h2o_buffer_append(&req->sendbuf, buf, nbytes); |
134 | 0 | } |
135 | | |
136 | | /* emit payload */ |
137 | 0 | h2o_buffer_append(&req->sendbuf, payload.base, payload.len); |
138 | 0 | nbytes += payload.len; |
139 | |
|
140 | 0 | return nbytes; |
141 | 0 | } |
142 | | |
143 | | static void destroy_request(struct st_h2o_http3client_req_t *req) |
144 | 0 | { |
145 | 0 | assert(req->quic == NULL); |
146 | | |
147 | 0 | h2o_buffer_dispose(&req->sendbuf); |
148 | 0 | h2o_buffer_dispose(&req->recvbuf.body); |
149 | 0 | h2o_buffer_dispose(&req->recvbuf.stream); |
150 | 0 | if (h2o_timer_is_linked(&req->super._timeout)) |
151 | 0 | h2o_timer_unlink(&req->super._timeout); |
152 | 0 | if (h2o_linklist_is_linked(&req->link)) |
153 | 0 | h2o_linklist_unlink(&req->link); |
154 | 0 | free(req); |
155 | 0 | } |
156 | | |
157 | | static void detach_stream(struct st_h2o_http3client_req_t *req) |
158 | 0 | { |
159 | 0 | req->quic->callbacks = &quicly_stream_noop_callbacks; |
160 | 0 | req->quic->data = NULL; |
161 | 0 | req->quic = NULL; |
162 | 0 | } |
163 | | |
164 | | static void close_stream(struct st_h2o_http3client_req_t *req, quicly_error_t err) |
165 | 0 | { |
166 | | /* TODO are we expected to send two error codes? */ |
167 | 0 | if (!quicly_sendstate_transfer_complete(&req->quic->sendstate)) |
168 | 0 | quicly_reset_stream(req->quic, err); |
169 | 0 | if (!quicly_recvstate_transfer_complete(&req->quic->recvstate)) |
170 | 0 | quicly_request_stop(req->quic, err); |
171 | 0 | detach_stream(req); |
172 | 0 | } |
173 | | |
174 | | static void write_datagrams(h2o_httpclient_t *_client, h2o_iovec_t *datagrams, size_t num_datagrams) |
175 | 0 | { |
176 | 0 | struct st_h2o_http3client_req_t *req = H2O_STRUCT_FROM_MEMBER(struct st_h2o_http3client_req_t, super, _client); |
177 | 0 | h2o_http3_send_h3_datagrams(&req->conn->super, req->quic->stream_id, datagrams, num_datagrams); |
178 | 0 | } |
179 | | |
180 | | static struct st_h2o_httpclient__h3_conn_t *find_connection(h2o_httpclient_connection_pool_t *pool, h2o_url_t *origin) |
181 | 0 | { |
182 | 0 | int should_check_target = h2o_socketpool_is_global(pool->socketpool); |
183 | | |
184 | | /* FIXME: |
185 | | * - check connection state(e.g., max_concurrent_streams, if received GOAWAY) |
186 | | * - use hashmap |
187 | | */ |
188 | 0 | for (h2o_linklist_t *l = pool->http3.conns.next; l != &pool->http3.conns; l = l->next) { |
189 | 0 | struct st_h2o_httpclient__h3_conn_t *conn = H2O_STRUCT_FROM_MEMBER(struct st_h2o_httpclient__h3_conn_t, link, l); |
190 | 0 | if (should_check_target && !(conn->server.origin_url.scheme == origin->scheme && |
191 | 0 | h2o_memis(conn->server.origin_url.authority.base, conn->server.origin_url.authority.len, |
192 | 0 | origin->authority.base, origin->authority.len))) |
193 | 0 | continue; |
194 | 0 | return conn; |
195 | 0 | } |
196 | | |
197 | 0 | return NULL; |
198 | 0 | } |
199 | | |
200 | | static void start_pending_requests(struct st_h2o_httpclient__h3_conn_t *conn) |
201 | 0 | { |
202 | 0 | while (!h2o_linklist_is_empty(&conn->pending_requests)) { |
203 | 0 | struct st_h2o_http3client_req_t *req = |
204 | 0 | H2O_STRUCT_FROM_MEMBER(struct st_h2o_http3client_req_t, link, conn->pending_requests.next); |
205 | 0 | h2o_linklist_unlink(&req->link); |
206 | 0 | start_request(req); |
207 | 0 | } |
208 | 0 | } |
209 | | |
210 | | static void call_proceed_req(struct st_h2o_http3client_req_t *req, const char *errstr) |
211 | 0 | { |
212 | 0 | req->proceed_req.bytes_inflight = SIZE_MAX; |
213 | 0 | req->proceed_req.cb(&req->super, errstr); |
214 | 0 | } |
215 | | |
216 | | static void destroy_connection(struct st_h2o_httpclient__h3_conn_t *conn, const char *errstr) |
217 | 0 | { |
218 | 0 | assert(errstr != NULL); |
219 | 0 | if (h2o_linklist_is_linked(&conn->link)) |
220 | 0 | h2o_linklist_unlink(&conn->link); |
221 | 0 | while (!h2o_linklist_is_empty(&conn->pending_requests)) { |
222 | 0 | struct st_h2o_http3client_req_t *req = |
223 | 0 | H2O_STRUCT_FROM_MEMBER(struct st_h2o_http3client_req_t, link, conn->pending_requests.next); |
224 | 0 | h2o_linklist_unlink(&req->link); |
225 | 0 | req->super._cb.on_connect(&req->super, errstr, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); |
226 | 0 | destroy_request(req); |
227 | 0 | } |
228 | 0 | assert(h2o_linklist_is_empty(&conn->pending_requests)); |
229 | 0 | if (conn->getaddr_req != NULL) |
230 | 0 | h2o_hostinfo_getaddr_cancel(conn->getaddr_req); |
231 | 0 | h2o_timer_unlink(&conn->timeout); |
232 | 0 | free(conn->server.origin_url.authority.base); |
233 | 0 | free(conn->server.origin_url.host.base); |
234 | 0 | free(conn->handshake_properties.client.session_ticket.base); |
235 | 0 | h2o_http3_dispose_conn(&conn->super); |
236 | 0 | free(conn); |
237 | 0 | } |
238 | | |
239 | | static void destroy_connection_on_transport_close(h2o_quic_conn_t *_conn) |
240 | 0 | { |
241 | 0 | struct st_h2o_httpclient__h3_conn_t *conn = (void *)_conn; |
242 | | |
243 | | /* When a connection gets closed while request is inflight, the most probable cause is some error in the transport (or at the |
244 | | * application protocol layer). But as we do not know the exact cause, we use a generic error here. */ |
245 | 0 | destroy_connection(conn, h2o_httpclient_error_io); |
246 | 0 | } |
247 | | |
248 | | static void on_connect_timeout(h2o_timer_t *timeout) |
249 | 0 | { |
250 | 0 | struct st_h2o_httpclient__h3_conn_t *conn = H2O_STRUCT_FROM_MEMBER(struct st_h2o_httpclient__h3_conn_t, timeout, timeout); |
251 | 0 | destroy_connection(conn, h2o_httpclient_error_connect_timeout); |
252 | 0 | } |
253 | | |
254 | | static void start_connect(struct st_h2o_httpclient__h3_conn_t *conn, struct sockaddr *sa) |
255 | 0 | { |
256 | 0 | quicly_conn_t *qconn; |
257 | 0 | ptls_iovec_t address_token = ptls_iovec_init(NULL, 0); |
258 | 0 | quicly_transport_parameters_t resumed_tp; |
259 | 0 | quicly_error_t ret; |
260 | |
|
261 | 0 | assert(conn->super.super.quic == NULL); |
262 | 0 | assert(conn->getaddr_req == NULL); |
263 | 0 | assert(h2o_timer_is_linked(&conn->timeout)); |
264 | 0 | assert(conn->timeout.cb == on_connect_timeout); |
265 | | |
266 | | /* create QUIC connection context and attach */ |
267 | 0 | if (conn->ctx->http3->load_session != NULL) { |
268 | 0 | if (!conn->ctx->http3->load_session(conn->ctx, sa, conn->server.origin_url.host.base, &address_token, |
269 | 0 | &conn->handshake_properties.client.session_ticket, &resumed_tp)) |
270 | 0 | goto Fail; |
271 | 0 | } |
272 | 0 | assert(conn->ctx->http3->h3.next_cid != NULL && "to identify connections, next_cid must be set"); |
273 | 0 | if ((ret = quicly_connect(&qconn, &conn->ctx->http3->quic, conn->server.origin_url.host.base, sa, NULL, |
274 | 0 | conn->ctx->http3->h3.next_cid, address_token, &conn->handshake_properties, |
275 | 0 | conn->handshake_properties.client.session_ticket.base != NULL ? &resumed_tp : NULL, NULL)) != 0) { |
276 | 0 | conn->super.super.quic = NULL; /* just in case */ |
277 | 0 | goto Fail; |
278 | 0 | } |
279 | 0 | ++conn->ctx->http3->h3.next_cid->master_id; /* FIXME check overlap */ |
280 | 0 | if ((ret = h2o_http3_setup(&conn->super, qconn)) != 0) |
281 | 0 | goto Fail; |
282 | | |
283 | 0 | if (quicly_connection_is_ready(conn->super.super.quic)) |
284 | 0 | start_pending_requests(conn); |
285 | |
|
286 | 0 | h2o_quic_send(&conn->super.super); |
287 | |
|
288 | 0 | free(address_token.base); |
289 | 0 | return; |
290 | 0 | Fail: |
291 | 0 | free(address_token.base); |
292 | 0 | destroy_connection(conn, h2o_httpclient_error_internal); |
293 | 0 | } |
294 | | |
295 | | static void on_getaddr(h2o_hostinfo_getaddr_req_t *getaddr_req, const char *errstr, struct addrinfo *res, void *_conn) |
296 | 0 | { |
297 | 0 | struct st_h2o_httpclient__h3_conn_t *conn = _conn; |
298 | |
|
299 | 0 | assert(getaddr_req == conn->getaddr_req); |
300 | 0 | conn->getaddr_req = NULL; |
301 | |
|
302 | 0 | if (errstr != NULL) { |
303 | 0 | destroy_connection(conn, errstr); |
304 | 0 | return; |
305 | 0 | } |
306 | | |
307 | 0 | struct addrinfo *selected = h2o_hostinfo_select_one(res); |
308 | 0 | start_connect(conn, selected->ai_addr); |
309 | 0 | } |
310 | | |
311 | | static void handle_control_stream_frame(h2o_http3_conn_t *_conn, uint64_t type, const uint8_t *payload, size_t len) |
312 | 0 | { |
313 | 0 | struct st_h2o_httpclient__h3_conn_t *conn = (void *)_conn; |
314 | 0 | quicly_error_t err; |
315 | 0 | const char *err_desc = NULL; |
316 | |
|
317 | 0 | if (!h2o_http3_has_received_settings(&conn->super)) { |
318 | 0 | if (type != H2O_HTTP3_FRAME_TYPE_SETTINGS) { |
319 | 0 | err = H2O_HTTP3_ERROR_MISSING_SETTINGS; |
320 | 0 | goto Fail; |
321 | 0 | } |
322 | 0 | if ((err = h2o_http3_handle_settings_frame(&conn->super, payload, len, &err_desc)) != 0) |
323 | 0 | goto Fail; |
324 | 0 | assert(h2o_http3_has_received_settings(&conn->super)); |
325 | | /* issue requests (unless it has been done already due to 0-RTT key being available) */ |
326 | 0 | start_pending_requests(conn); |
327 | 0 | } else { |
328 | 0 | switch (type) { |
329 | 0 | case H2O_HTTP3_FRAME_TYPE_SETTINGS: |
330 | 0 | err = H2O_HTTP3_ERROR_FRAME_UNEXPECTED; |
331 | 0 | err_desc = "unexpected SETTINGS frame"; |
332 | 0 | goto Fail; |
333 | 0 | case H2O_HTTP3_FRAME_TYPE_GOAWAY: { |
334 | 0 | h2o_http3_goaway_frame_t frame; |
335 | 0 | if ((err = h2o_http3_decode_goaway_frame(&frame, payload, len, &err_desc)) != 0) |
336 | 0 | goto Fail; |
337 | | /* FIXME: stop issuing new requests */ |
338 | 0 | break; |
339 | 0 | } |
340 | 0 | default: |
341 | 0 | break; |
342 | 0 | } |
343 | 0 | } |
344 | | |
345 | 0 | return; |
346 | 0 | Fail: |
347 | 0 | h2o_quic_close_connection(&conn->super.super, err, err_desc); |
348 | 0 | } |
349 | | |
350 | | struct st_h2o_httpclient__h3_conn_t *create_connection(h2o_httpclient_ctx_t *ctx, h2o_httpclient_connection_pool_t *pool, |
351 | | h2o_url_t *origin) |
352 | 0 | { |
353 | | /* FIXME When using a non-global socket pool, let the socket pool load balance H3 connections among the list of targets being |
354 | | * available. But until then, we use the first entry. */ |
355 | 0 | if (!h2o_socketpool_is_global(pool->socketpool)) |
356 | 0 | origin = &pool->socketpool->targets.entries[0]->url; |
357 | |
|
358 | 0 | static const h2o_http3_conn_callbacks_t callbacks = {{destroy_connection_on_transport_close}, handle_control_stream_frame}; |
359 | 0 | static const h2o_http3_qpack_context_t qpack_ctx = {0 /* TODO */}; |
360 | |
|
361 | 0 | struct st_h2o_httpclient__h3_conn_t *conn = h2o_mem_alloc(sizeof(*conn)); |
362 | |
|
363 | 0 | h2o_http3_init_conn(&conn->super, &ctx->http3->h3, &callbacks, &qpack_ctx, ctx->http3->max_frame_payload_size); |
364 | 0 | memset((char *)conn + sizeof(conn->super), 0, sizeof(*conn) - sizeof(conn->super)); |
365 | 0 | conn->ctx = ctx; |
366 | 0 | h2o_url_copy(NULL, &conn->server.origin_url, origin); |
367 | 0 | sprintf(conn->server.named_serv, "%" PRIu16, h2o_url_get_port(origin)); |
368 | 0 | conn->handshake_properties.client.negotiated_protocols.list = h2o_http3_alpn; |
369 | 0 | conn->handshake_properties.client.negotiated_protocols.count = sizeof(h2o_http3_alpn) / sizeof(h2o_http3_alpn[0]); |
370 | 0 | h2o_linklist_insert(&pool->http3.conns, &conn->link); |
371 | 0 | h2o_linklist_init_anchor(&conn->pending_requests); |
372 | |
|
373 | 0 | conn->getaddr_req = h2o_hostinfo_getaddr(conn->ctx->getaddr_receiver, conn->server.origin_url.host, |
374 | 0 | h2o_iovec_init(conn->server.named_serv, strlen(conn->server.named_serv)), |
375 | 0 | ctx->http3->h3.sock.addr.ss_family, SOCK_DGRAM, IPPROTO_UDP, |
376 | 0 | AI_ADDRCONFIG | AI_NUMERICSERV, on_getaddr, conn); |
377 | 0 | h2o_timer_link(conn->ctx->loop, conn->ctx->connect_timeout, &conn->timeout); |
378 | 0 | conn->timeout.cb = on_connect_timeout; |
379 | |
|
380 | 0 | return conn; |
381 | 0 | } |
382 | | |
383 | | static void notify_response_error(struct st_h2o_http3client_req_t *req, const char *errstr) |
384 | 0 | { |
385 | 0 | assert(errstr != NULL); |
386 | | |
387 | 0 | switch (req->response_state) { |
388 | 0 | case H2O_HTTP3CLIENT_RESPONSE_STATE_HEAD: |
389 | 0 | req->super._cb.on_head(&req->super, errstr, NULL); |
390 | 0 | break; |
391 | 0 | case H2O_HTTP3CLIENT_RESPONSE_STATE_BODY: |
392 | 0 | req->super._cb.on_body(&req->super, errstr, NULL, 0); |
393 | 0 | break; |
394 | 0 | default: |
395 | 0 | break; |
396 | 0 | } |
397 | 0 | req->response_state = H2O_HTTP3CLIENT_RESPONSE_STATE_CLOSED; |
398 | 0 | } |
399 | | |
400 | | static int call_on_body(struct st_h2o_http3client_req_t *req, const char *errstr) |
401 | 0 | { |
402 | 0 | assert(req->response_state == H2O_HTTP3CLIENT_RESPONSE_STATE_BODY); |
403 | | |
404 | 0 | int ret = req->super._cb.on_body(&req->super, errstr, NULL, 0); |
405 | 0 | if (errstr != NULL) |
406 | 0 | req->response_state = H2O_HTTP3CLIENT_RESPONSE_STATE_CLOSED; |
407 | |
|
408 | 0 | return ret; |
409 | 0 | } |
410 | | |
411 | | static quicly_error_t handle_input_data_payload(struct st_h2o_http3client_req_t *req, const uint8_t **src, const uint8_t *src_end, |
412 | | quicly_error_t err, const char **err_desc) |
413 | 0 | { |
414 | | /* save data, update states */ |
415 | 0 | if (req->bytes_left_in_data_frame != 0) { |
416 | 0 | size_t payload_bytes = req->bytes_left_in_data_frame; |
417 | 0 | if (src_end - *src < payload_bytes) |
418 | 0 | payload_bytes = src_end - *src; |
419 | 0 | h2o_buffer_append(&req->recvbuf.body, *src, payload_bytes); |
420 | 0 | *src += payload_bytes; |
421 | 0 | req->bytes_left_in_data_frame -= payload_bytes; |
422 | 0 | } |
423 | 0 | if (req->bytes_left_in_data_frame == 0) |
424 | 0 | req->handle_input = handle_input_expect_data_frame; |
425 | | |
426 | | /* call the handler */ |
427 | 0 | const char *errstr = NULL; |
428 | 0 | if (*src == src_end && err != 0) { |
429 | | /* FIXME also check content-length? see what other protocol handlers do */ |
430 | 0 | errstr = err == ERROR_EOS && req->bytes_left_in_data_frame == 0 ? h2o_httpclient_error_is_eos : h2o_httpclient_error_io; |
431 | 0 | } |
432 | 0 | if (call_on_body(req, errstr) != 0) |
433 | 0 | return H2O_HTTP3_ERROR_INTERNAL; |
434 | | |
435 | 0 | return 0; |
436 | 0 | } |
437 | | |
438 | | quicly_error_t handle_input_expect_data_frame(struct st_h2o_http3client_req_t *req, const uint8_t **src, const uint8_t *src_end, |
439 | | quicly_error_t err, const char **err_desc) |
440 | 0 | { |
441 | 0 | assert(req->bytes_left_in_data_frame == 0); |
442 | 0 | if (*src == src_end) { |
443 | | /* return early if no input, no state change */ |
444 | 0 | if (err == 0) |
445 | 0 | return 0; |
446 | | /* either EOS or an unexpected close; delegate the task to the payload processing function */ |
447 | 0 | } else { |
448 | | /* otherwise, read the frame */ |
449 | 0 | h2o_http3_read_frame_t frame; |
450 | 0 | quicly_error_t ret; |
451 | 0 | if ((ret = h2o_http3_read_frame(&frame, 1, H2O_HTTP3_STREAM_TYPE_REQUEST, req->conn->super.max_frame_payload_size, src, |
452 | 0 | src_end, err_desc)) != 0) { |
453 | | /* incomplete */ |
454 | 0 | if (ret == H2O_HTTP3_ERROR_INCOMPLETE && err == 0) |
455 | 0 | return ret; |
456 | 0 | call_on_body(req, h2o_httpclient_error_malformed_frame); |
457 | 0 | return ret; |
458 | 0 | } |
459 | 0 | switch (frame.type) { |
460 | 0 | case H2O_HTTP3_FRAME_TYPE_DATA: |
461 | 0 | break; |
462 | 0 | case H2O_HTTP3_FRAME_TYPE_HEADERS: |
463 | 0 | if (req->super.upgrade_to != NULL) |
464 | 0 | return H2O_HTTP3_ERROR_FRAME_UNEXPECTED; |
465 | | /* flow continues */ |
466 | 0 | default: |
467 | | /* FIXME handle push_promise, trailers */ |
468 | 0 | return 0; |
469 | 0 | } |
470 | 0 | req->bytes_left_in_data_frame = frame.length; |
471 | 0 | } |
472 | | |
473 | | /* unexpected close of DATA frame is handled by handle_input_data_payload. We rely on the function to detect if the DATA frame |
474 | | * is closed right after the frame header */ |
475 | 0 | req->handle_input = handle_input_data_payload; |
476 | 0 | return handle_input_data_payload(req, src, src_end, err, err_desc); |
477 | 0 | } |
478 | | |
479 | | static quicly_error_t handle_input_expect_headers(struct st_h2o_http3client_req_t *req, const uint8_t **src, const uint8_t *src_end, |
480 | | quicly_error_t err, const char **err_desc) |
481 | 0 | { |
482 | 0 | h2o_http3_read_frame_t frame; |
483 | 0 | int status; |
484 | 0 | h2o_headers_t headers = {NULL}; |
485 | 0 | h2o_iovec_t datagram_flow_id = {}; |
486 | 0 | uint8_t header_ack[H2O_HPACK_ENCODE_INT_MAX_LENGTH]; |
487 | 0 | size_t header_ack_len; |
488 | 0 | int frame_is_eos; |
489 | 0 | quicly_error_t ret; |
490 | | |
491 | | /* read HEADERS frame */ |
492 | 0 | if ((ret = h2o_http3_read_frame(&frame, 1, H2O_HTTP3_STREAM_TYPE_REQUEST, req->conn->super.max_frame_payload_size, src, src_end, |
493 | 0 | err_desc)) != 0) { |
494 | 0 | if (ret == H2O_HTTP3_ERROR_INCOMPLETE) { |
495 | 0 | if (err != 0) { |
496 | 0 | notify_response_error(req, h2o_httpclient_error_io); |
497 | 0 | return 0; |
498 | 0 | } |
499 | 0 | return ret; |
500 | 0 | } |
501 | 0 | notify_response_error(req, "response header too large"); |
502 | 0 | return H2O_HTTP3_ERROR_EXCESSIVE_LOAD; /* FIXME correct code? */ |
503 | 0 | } |
504 | 0 | frame_is_eos = *src == src_end && err != 0; |
505 | 0 | if (frame.type != H2O_HTTP3_FRAME_TYPE_HEADERS) { |
506 | 0 | switch (frame.type) { |
507 | 0 | case H2O_HTTP3_FRAME_TYPE_DATA: |
508 | 0 | *err_desc = "received DATA frame before HEADERS"; |
509 | 0 | return H2O_HTTP3_ERROR_FRAME_UNEXPECTED; |
510 | 0 | default: |
511 | 0 | return 0; |
512 | 0 | } |
513 | 0 | } |
514 | 0 | if ((ret = h2o_qpack_parse_response(req->super.pool, req->conn->super.qpack.dec, req->quic->stream_id, &status, &headers, |
515 | 0 | &datagram_flow_id, header_ack, &header_ack_len, frame.payload, frame.length, err_desc)) != |
516 | 0 | 0) { |
517 | 0 | if (ret == H2O_HTTP2_ERROR_INCOMPLETE) { |
518 | | /* the request is blocked by the QPACK stream */ |
519 | 0 | req->handle_input = NULL; /* FIXME */ |
520 | 0 | return 0; |
521 | 0 | } |
522 | 0 | if (*err_desc == NULL) |
523 | 0 | *err_desc = "qpack error"; |
524 | 0 | notify_response_error(req, *err_desc); |
525 | 0 | return H2O_HTTP3_ERROR_GENERAL_PROTOCOL; /* FIXME */ |
526 | 0 | } |
527 | 0 | if (header_ack_len != 0) |
528 | 0 | h2o_http3_send_qpack_header_ack(&req->conn->super, header_ack, header_ack_len); |
529 | |
|
530 | 0 | if (datagram_flow_id.base != NULL) { |
531 | 0 | if (!req->offered_datagram_flow_id) { |
532 | 0 | *err_desc = "no offered datagram-flow-id"; |
533 | 0 | return H2O_HTTP3_ERROR_GENERAL_PROTOCOL; |
534 | 0 | } |
535 | | /* TODO validate the returned value */ |
536 | 0 | } |
537 | | |
538 | | /* handle 1xx */ |
539 | 0 | if (100 <= status && status <= 199) { |
540 | 0 | if (status == 101) { |
541 | 0 | *err_desc = "unexpected 101"; |
542 | 0 | notify_response_error(req, *err_desc); |
543 | 0 | return H2O_HTTP3_ERROR_GENERAL_PROTOCOL; |
544 | 0 | } |
545 | 0 | if (frame_is_eos) { |
546 | 0 | notify_response_error(req, h2o_httpclient_error_io); |
547 | 0 | return 0; |
548 | 0 | } |
549 | 0 | if (req->super.informational_cb != NULL && |
550 | 0 | req->super.informational_cb(&req->super, 0x300, status, h2o_iovec_init(NULL, 0), headers.entries, headers.size) != 0) { |
551 | 0 | return H2O_HTTP3_ERROR_INTERNAL; |
552 | 0 | } |
553 | 0 | return 0; |
554 | 0 | } |
555 | | |
556 | | /* handle final response, creating tunnel object if necessary */ |
557 | 0 | h2o_httpclient_on_head_t on_head = {.version = 0x300, |
558 | 0 | .msg = h2o_iovec_init(NULL, 0), |
559 | 0 | .status = status, |
560 | 0 | .headers = headers.entries, |
561 | 0 | .num_headers = headers.size}; |
562 | 0 | if (h2o_httpclient__tunnel_is_ready(&req->super, status, on_head.version)) { |
563 | 0 | on_head.forward_datagram.write_ = write_datagrams; |
564 | 0 | on_head.forward_datagram.read_ = &req->on_read_datagrams; |
565 | 0 | } |
566 | 0 | req->super._cb.on_body = req->super._cb.on_head(&req->super, frame_is_eos ? h2o_httpclient_error_is_eos : NULL, &on_head); |
567 | 0 | req->response_state = H2O_HTTP3CLIENT_RESPONSE_STATE_BODY; |
568 | 0 | if (req->super._cb.on_body == NULL) |
569 | 0 | return frame_is_eos ? 0 : H2O_HTTP3_ERROR_INTERNAL; |
570 | | |
571 | | /* handle body */ |
572 | 0 | req->handle_input = handle_input_expect_data_frame; |
573 | 0 | return 0; |
574 | 0 | } |
575 | | |
576 | | static void on_stream_destroy(quicly_stream_t *qs, quicly_error_t err) |
577 | 0 | { |
578 | 0 | struct st_h2o_http3client_req_t *req; |
579 | |
|
580 | 0 | if ((req = qs->data) == NULL) |
581 | 0 | return; |
582 | 0 | notify_response_error(req, h2o_httpclient_error_io); |
583 | 0 | detach_stream(req); |
584 | 0 | destroy_request(req); |
585 | 0 | } |
586 | | |
587 | | static void on_send_shift(quicly_stream_t *qs, size_t delta) |
588 | 0 | { |
589 | 0 | struct st_h2o_http3client_req_t *req = qs->data; |
590 | |
|
591 | 0 | assert(req != NULL); |
592 | 0 | h2o_buffer_consume(&req->sendbuf, delta); |
593 | 0 | } |
594 | | |
595 | | static void on_send_emit(quicly_stream_t *qs, size_t off, void *dst, size_t *len, int *wrote_all) |
596 | 0 | { |
597 | 0 | struct st_h2o_http3client_req_t *req = qs->data; |
598 | |
|
599 | 0 | if (*len >= req->sendbuf->size - off) { |
600 | 0 | *len = req->sendbuf->size - off; |
601 | 0 | *wrote_all = 1; |
602 | 0 | } else { |
603 | 0 | *wrote_all = 0; |
604 | 0 | } |
605 | 0 | memcpy(dst, req->sendbuf->bytes + off, *len); |
606 | |
|
607 | 0 | if (*wrote_all && req->proceed_req.bytes_inflight != SIZE_MAX) |
608 | 0 | call_proceed_req(req, NULL); |
609 | 0 | } |
610 | | |
611 | | static void on_send_stop(quicly_stream_t *qs, quicly_error_t err) |
612 | 0 | { |
613 | 0 | struct st_h2o_http3client_req_t *req; |
614 | |
|
615 | 0 | if ((req = qs->data) == NULL) |
616 | 0 | return; |
617 | | |
618 | 0 | if (!quicly_sendstate_transfer_complete(&req->quic->sendstate)) |
619 | 0 | quicly_reset_stream(req->quic, err); |
620 | |
|
621 | 0 | if (req->proceed_req.bytes_inflight != SIZE_MAX) |
622 | 0 | call_proceed_req(req, h2o_httpclient_error_io /* TODO better error code? */); |
623 | |
|
624 | 0 | if (!quicly_recvstate_transfer_complete(&req->quic->recvstate)) { |
625 | 0 | quicly_request_stop(req->quic, H2O_HTTP3_ERROR_REQUEST_CANCELLED); |
626 | 0 | notify_response_error(req, h2o_httpclient_error_io); |
627 | 0 | } |
628 | 0 | detach_stream(req); |
629 | 0 | destroy_request(req); |
630 | 0 | } |
631 | | |
632 | | static quicly_error_t on_receive_process_bytes(struct st_h2o_http3client_req_t *req, const uint8_t **src, const uint8_t *src_end, |
633 | | const char **err_desc) |
634 | 0 | { |
635 | 0 | int is_eos = quicly_recvstate_transfer_complete(&req->quic->recvstate); |
636 | 0 | assert(is_eos || *src != src_end); |
637 | 0 | quicly_error_t ret; |
638 | |
|
639 | 0 | do { |
640 | 0 | if ((ret = req->handle_input(req, src, src_end, is_eos ? ERROR_EOS : 0, err_desc)) != 0) { |
641 | 0 | if (ret == H2O_HTTP3_ERROR_INCOMPLETE) |
642 | 0 | ret = is_eos ? H2O_HTTP3_ERROR_FRAME : 0; |
643 | 0 | break; |
644 | 0 | } |
645 | 0 | } while (*src != src_end); |
646 | | |
647 | 0 | return ret; |
648 | 0 | } |
649 | | |
650 | | static void on_receive(quicly_stream_t *qs, size_t off, const void *input, size_t len) |
651 | 0 | { |
652 | 0 | struct st_h2o_http3client_req_t *req = qs->data; |
653 | 0 | size_t bytes_consumed; |
654 | 0 | quicly_error_t err = 0; |
655 | 0 | const char *err_desc = NULL; |
656 | | |
657 | | /* process the input, update stream-level receive buffer */ |
658 | 0 | if (req->recvbuf.stream->size == 0 && off == 0) { |
659 | | |
660 | | /* fast path; process the input directly, save the remaining bytes */ |
661 | 0 | const uint8_t *src = input; |
662 | 0 | err = on_receive_process_bytes(req, &src, src + len, &err_desc); |
663 | 0 | bytes_consumed = src - (const uint8_t *)input; |
664 | 0 | if (bytes_consumed != len) |
665 | 0 | h2o_buffer_append(&req->recvbuf.stream, src, len - bytes_consumed); |
666 | 0 | } else { |
667 | | /* slow path; copy data to partial_frame */ |
668 | 0 | size_t size_required = off + len; |
669 | 0 | if (req->recvbuf.stream->size < size_required) { |
670 | 0 | h2o_buffer_reserve(&req->recvbuf.stream, size_required - req->recvbuf.stream->size); |
671 | 0 | req->recvbuf.stream->size = size_required; |
672 | 0 | } |
673 | 0 | memcpy(req->recvbuf.stream->bytes + off, input, len); |
674 | | |
675 | | /* just return if no new data is available */ |
676 | 0 | size_t bytes_available = quicly_recvstate_bytes_available(&req->quic->recvstate); |
677 | 0 | if (req->recvbuf.prev_bytes_available == bytes_available) |
678 | 0 | return; |
679 | | |
680 | | /* process the bytes that have not been processed, update stream-level buffer */ |
681 | 0 | const uint8_t *src = (const uint8_t *)req->recvbuf.stream->bytes; |
682 | 0 | err = on_receive_process_bytes(req, &src, (const uint8_t *)req->recvbuf.stream->bytes + bytes_available, &err_desc); |
683 | 0 | bytes_consumed = src - (const uint8_t *)req->recvbuf.stream->bytes; |
684 | 0 | h2o_buffer_consume(&req->recvbuf.stream, bytes_consumed); |
685 | 0 | } |
686 | | |
687 | | /* update QUIC stream-level state */ |
688 | 0 | if (bytes_consumed != 0) |
689 | 0 | quicly_stream_sync_recvbuf(req->quic, bytes_consumed); |
690 | 0 | req->recvbuf.prev_bytes_available = quicly_recvstate_bytes_available(&req->quic->recvstate); |
691 | | |
692 | | /* cleanup */ |
693 | 0 | if (quicly_recvstate_transfer_complete(&req->quic->recvstate)) { |
694 | | /* destroy the request if send-side is already closed, otherwise wait until the send-side gets closed */ |
695 | 0 | if (quicly_sendstate_transfer_complete(&req->quic->sendstate)) { |
696 | 0 | detach_stream(req); |
697 | 0 | destroy_request(req); |
698 | 0 | } |
699 | 0 | } else if (err != 0) { |
700 | 0 | notify_response_error(req, h2o_httpclient_error_io); |
701 | 0 | int send_is_open = quicly_sendstate_is_open(&req->quic->sendstate); |
702 | 0 | close_stream(req, err); |
703 | | /* immediately dispose of the request if possible, or wait for the send-side to close */ |
704 | 0 | if (!send_is_open) { |
705 | 0 | destroy_request(req); |
706 | 0 | } else if (req->proceed_req.bytes_inflight != SIZE_MAX) { |
707 | 0 | call_proceed_req(req, h2o_httpclient_error_io); |
708 | 0 | destroy_request(req); |
709 | 0 | } else { |
710 | | /* wait for write_req to be called */ |
711 | 0 | } |
712 | 0 | } |
713 | 0 | } |
714 | | |
715 | | static void on_receive_reset(quicly_stream_t *qs, quicly_error_t err) |
716 | 0 | { |
717 | 0 | struct st_h2o_http3client_req_t *req = qs->data; |
718 | |
|
719 | 0 | notify_response_error(req, h2o_httpclient_error_io); |
720 | 0 | close_stream(req, H2O_HTTP3_ERROR_REQUEST_CANCELLED); |
721 | 0 | destroy_request(req); |
722 | 0 | } |
723 | | |
724 | | void start_request(struct st_h2o_http3client_req_t *req) |
725 | 0 | { |
726 | 0 | h2o_iovec_t method; |
727 | 0 | h2o_url_t url; |
728 | 0 | const h2o_header_t *headers; |
729 | 0 | size_t num_headers; |
730 | 0 | h2o_iovec_t body; |
731 | 0 | h2o_httpclient_properties_t props = {NULL}; |
732 | 0 | char datagram_flow_id_buf[sizeof(H2O_UINT64_LONGEST_STR)]; |
733 | 0 | quicly_error_t ret; |
734 | |
|
735 | 0 | assert(req->quic == NULL); |
736 | 0 | assert(!h2o_linklist_is_linked(&req->link)); |
737 | | |
738 | 0 | if ((req->super._cb.on_head = req->super._cb.on_connect(&req->super, NULL, &method, &url, &headers, &num_headers, &body, |
739 | 0 | &req->proceed_req.cb, &props, &req->conn->server.origin_url)) == NULL) { |
740 | 0 | destroy_request(req); |
741 | 0 | return; |
742 | 0 | } |
743 | | |
744 | 0 | if ((ret = quicly_open_stream(req->conn->super.super.quic, &req->quic, 0)) != 0) { |
745 | 0 | notify_response_error(req, "failed to open stream"); |
746 | 0 | destroy_request(req); |
747 | 0 | return; |
748 | 0 | } |
749 | 0 | req->quic->data = req; |
750 | | |
751 | | /* send request (TODO optimize) */ |
752 | 0 | h2o_iovec_t protocol = {}; |
753 | 0 | h2o_iovec_t datagram_flow_id = {}; |
754 | 0 | if (req->super.upgrade_to == h2o_httpclient_upgrade_to_connect && |
755 | 0 | h2o_memis(method.base, method.len, H2O_STRLIT("CONNECT-UDP")) && req->conn->super.peer_settings.h3_datagram) { |
756 | 0 | datagram_flow_id.len = sprintf(datagram_flow_id_buf, "%" PRIu64, req->quic->stream_id); |
757 | 0 | datagram_flow_id.base = datagram_flow_id_buf; |
758 | 0 | req->offered_datagram_flow_id = 1; |
759 | 0 | } else if (req->super.upgrade_to != NULL && req->super.upgrade_to != h2o_httpclient_upgrade_to_connect) { |
760 | 0 | protocol = h2o_iovec_init(req->super.upgrade_to, strlen(req->super.upgrade_to)); |
761 | 0 | } |
762 | 0 | h2o_iovec_t headers_frame = |
763 | 0 | h2o_qpack_flatten_request(req->conn->super.qpack.enc, req->super.pool, req->quic->stream_id, NULL, method, url.scheme, |
764 | 0 | url.authority, url.path, protocol, headers, num_headers, datagram_flow_id); |
765 | 0 | h2o_buffer_append(&req->sendbuf, headers_frame.base, headers_frame.len); |
766 | 0 | if (body.len != 0) |
767 | 0 | emit_data(req, body); |
768 | 0 | if (req->proceed_req.cb != NULL) { |
769 | 0 | req->super.write_req = do_write_req; |
770 | 0 | req->proceed_req.bytes_inflight = body.len; |
771 | 0 | } |
772 | 0 | if (req->proceed_req.cb == NULL && req->super.upgrade_to == NULL) |
773 | 0 | quicly_sendstate_shutdown(&req->quic->sendstate, req->sendbuf->size); |
774 | 0 | quicly_stream_sync_sendbuf(req->quic, 1); |
775 | |
|
776 | 0 | req->handle_input = handle_input_expect_headers; |
777 | 0 | } |
778 | | |
779 | | static void cancel_request(h2o_httpclient_t *_client) |
780 | 0 | { |
781 | 0 | struct st_h2o_http3client_req_t *req = (void *)_client; |
782 | 0 | if (req->quic != NULL) |
783 | 0 | close_stream(req, H2O_HTTP3_ERROR_REQUEST_CANCELLED); |
784 | 0 | destroy_request(req); |
785 | 0 | } |
786 | | |
787 | | static void do_get_conn_properties(h2o_httpclient_t *_client, h2o_httpclient_conn_properties_t *properties) |
788 | 0 | { |
789 | 0 | struct st_h2o_http3client_req_t *req = (void *)_client; |
790 | 0 | ptls_t *tls; |
791 | 0 | ptls_cipher_suite_t *cipher; |
792 | |
|
793 | 0 | if (req->quic != NULL && (tls = quicly_get_tls(req->quic->conn), (cipher = ptls_get_cipher(tls)) != NULL)) { |
794 | 0 | properties->ssl.protocol_version = "TLSv1.3"; |
795 | 0 | properties->ssl.session_reused = ptls_is_psk_handshake(tls); |
796 | 0 | properties->ssl.cipher = cipher->name; |
797 | 0 | properties->ssl.cipher_bits = (int)cipher->aead->key_size; |
798 | 0 | } else { |
799 | 0 | properties->ssl.protocol_version = NULL; |
800 | 0 | properties->ssl.session_reused = -1; |
801 | 0 | properties->ssl.cipher = NULL; |
802 | 0 | properties->ssl.cipher_bits = 0; |
803 | 0 | } |
804 | 0 | properties->sock = NULL; |
805 | 0 | } |
806 | | |
807 | | static void do_update_window(h2o_httpclient_t *_client) |
808 | 0 | { |
809 | | /* TODO Stop receiving data for the stream when `buf` grows to certain extent. Then, resume when this function is being called. |
810 | | */ |
811 | 0 | } |
812 | | |
813 | | int do_write_req(h2o_httpclient_t *_client, h2o_iovec_t chunk, int is_end_stream) |
814 | 0 | { |
815 | 0 | struct st_h2o_http3client_req_t *req = (void *)_client; |
816 | |
|
817 | 0 | assert(req->proceed_req.bytes_inflight == SIZE_MAX); |
818 | | |
819 | | /* Notify error to the application, if the stream has already been closed (due to e.g., a stream error) or if the send-side has |
820 | | * been closed (due to STOP_SENDING). Also, destroy the request if the receive side has already been closed. */ |
821 | 0 | if (req->quic == NULL || !quicly_sendstate_is_open(&req->quic->sendstate)) { |
822 | 0 | if (req->quic != NULL && quicly_recvstate_transfer_complete(&req->quic->recvstate)) |
823 | 0 | close_stream(req, H2O_HTTP3_ERROR_REQUEST_CANCELLED); |
824 | 0 | if (req->quic == NULL) |
825 | 0 | destroy_request(req); |
826 | 0 | return 1; |
827 | 0 | } |
828 | | |
829 | 0 | emit_data(req, chunk); |
830 | | |
831 | | /* shutdown if we've written all request body */ |
832 | 0 | if (is_end_stream) { |
833 | 0 | assert(quicly_sendstate_is_open(&req->quic->sendstate)); |
834 | 0 | quicly_sendstate_shutdown(&req->quic->sendstate, req->quic->sendstate.acked.ranges[0].end + req->sendbuf->size); |
835 | 0 | } else { |
836 | 0 | assert(chunk.len != 0); |
837 | 0 | } |
838 | | |
839 | 0 | req->proceed_req.bytes_inflight = chunk.len; |
840 | 0 | quicly_stream_sync_sendbuf(req->quic, 1); |
841 | 0 | h2o_quic_schedule_timer(&req->conn->super.super); |
842 | 0 | return 0; |
843 | 0 | } |
844 | | |
845 | | void h2o_httpclient__connect_h3(h2o_httpclient_t **_client, h2o_mem_pool_t *pool, void *data, h2o_httpclient_ctx_t *ctx, |
846 | | h2o_httpclient_connection_pool_t *connpool, h2o_url_t *target, const char *upgrade_to, |
847 | | h2o_httpclient_connect_cb cb) |
848 | 0 | { |
849 | 0 | struct st_h2o_httpclient__h3_conn_t *conn; |
850 | 0 | struct st_h2o_http3client_req_t *req; |
851 | |
|
852 | 0 | if ((conn = find_connection(connpool, target)) == NULL) |
853 | 0 | conn = create_connection(ctx, connpool, target); |
854 | |
|
855 | 0 | req = h2o_mem_alloc(sizeof(*req)); |
856 | 0 | *req = (struct st_h2o_http3client_req_t){ |
857 | 0 | .super = {pool, |
858 | 0 | ctx, |
859 | 0 | connpool, |
860 | 0 | &req->recvbuf.body, |
861 | 0 | data, |
862 | 0 | NULL, |
863 | 0 | {h2o_gettimeofday(ctx->loop)}, |
864 | 0 | upgrade_to, |
865 | 0 | {0}, |
866 | 0 | {0}, |
867 | 0 | cancel_request, |
868 | 0 | do_get_conn_properties, |
869 | 0 | do_update_window}, |
870 | 0 | .conn = conn, |
871 | 0 | .proceed_req = {.cb = NULL, .bytes_inflight = SIZE_MAX}, |
872 | 0 | }; |
873 | 0 | req->super._cb.on_connect = cb; |
874 | 0 | h2o_buffer_init(&req->sendbuf, &h2o_socket_buffer_prototype); |
875 | 0 | h2o_buffer_init(&req->recvbuf.body, &h2o_socket_buffer_prototype); |
876 | 0 | h2o_buffer_init(&req->recvbuf.stream, &h2o_socket_buffer_prototype); |
877 | |
|
878 | 0 | if (_client != NULL) |
879 | 0 | *_client = &req->super; |
880 | |
|
881 | 0 | if (h2o_http3_has_received_settings(&conn->super)) { |
882 | 0 | start_request(req); |
883 | 0 | h2o_quic_schedule_timer(&conn->super.super); |
884 | 0 | } else { |
885 | 0 | h2o_linklist_insert(&conn->pending_requests, &req->link); |
886 | 0 | } |
887 | 0 | } |
888 | | |
889 | | void h2o_httpclient_http3_notify_connection_update(h2o_quic_ctx_t *ctx, h2o_quic_conn_t *_conn) |
890 | 0 | { |
891 | 0 | struct st_h2o_httpclient__h3_conn_t *conn = (void *)_conn; |
892 | |
|
893 | 0 | if (h2o_timer_is_linked(&conn->timeout) && conn->timeout.cb == on_connect_timeout) { |
894 | | /* TODO check connection state? */ |
895 | 0 | h2o_timer_unlink(&conn->timeout); |
896 | 0 | } |
897 | 0 | } |
898 | | |
899 | | static quicly_error_t stream_open_cb(quicly_stream_open_t *self, quicly_stream_t *qs) |
900 | 0 | { |
901 | 0 | if (quicly_stream_is_unidirectional(qs->stream_id)) { |
902 | 0 | h2o_http3_on_create_unidirectional_stream(qs); |
903 | 0 | } else { |
904 | 0 | static const quicly_stream_callbacks_t callbacks = {on_stream_destroy, on_send_shift, on_send_emit, |
905 | 0 | on_send_stop, on_receive, on_receive_reset}; |
906 | 0 | assert(quicly_stream_is_client_initiated(qs->stream_id)); |
907 | 0 | qs->callbacks = &callbacks; |
908 | 0 | } |
909 | 0 | return 0; |
910 | 0 | } |
911 | | |
912 | | quicly_stream_open_t h2o_httpclient_http3_on_stream_open = {stream_open_cb}; |
913 | | |
914 | | static void on_receive_datagram_frame(quicly_receive_datagram_frame_t *self, quicly_conn_t *qc, ptls_iovec_t datagram) |
915 | 0 | { |
916 | 0 | struct st_h2o_httpclient__h3_conn_t *conn = |
917 | 0 | H2O_STRUCT_FROM_MEMBER(struct st_h2o_httpclient__h3_conn_t, super, *quicly_get_data(qc)); |
918 | 0 | uint64_t flow_id; |
919 | 0 | h2o_iovec_t payload; |
920 | 0 | quicly_stream_t *qs; |
921 | | |
922 | | /* decode, validate, get stream */ |
923 | 0 | if ((flow_id = h2o_http3_decode_h3_datagram(&payload, datagram.base, datagram.len)) == UINT64_MAX || |
924 | 0 | !(quicly_stream_is_client_initiated(flow_id) && !quicly_stream_is_unidirectional(flow_id))) { |
925 | 0 | h2o_quic_close_connection(&conn->super.super, H2O_HTTP3_ERROR_GENERAL_PROTOCOL, "invalid DATAGRAM frame"); |
926 | 0 | return; |
927 | 0 | } |
928 | 0 | if ((qs = quicly_get_stream(conn->super.super.quic, flow_id)) == NULL) |
929 | 0 | return; |
930 | | |
931 | 0 | struct st_h2o_http3client_req_t *req = qs->data; |
932 | 0 | if (req->on_read_datagrams != NULL) |
933 | 0 | req->on_read_datagrams(&req->super, &payload, 1); |
934 | 0 | } |
935 | | |
936 | | quicly_receive_datagram_frame_t h2o_httpclient_http3_on_receive_datagram_frame = {on_receive_datagram_frame}; |