diff --git a/deps/ngtcp2/nghttp3/lib/includes/nghttp3/nghttp3.h b/deps/ngtcp2/nghttp3/lib/includes/nghttp3/nghttp3.h index d3dd9217d4b0..37d50c83925f 100644 --- a/deps/ngtcp2/nghttp3/lib/includes/nghttp3/nghttp3.h +++ b/deps/ngtcp2/nghttp3/lib/includes/nghttp3/nghttp3.h @@ -335,6 +335,27 @@ typedef uint64_t nghttp3_duration; * that might generating excessive load. */ #define NGHTTP3_ERR_H3_EXCESSIVE_LOAD -610 +/** + * @macro + * + * :macro:`NGHTTP3_ERR_H3_MESSAGE_ERROR` indicates that HTTP message + * was malformed. + */ +#define NGHTTP3_ERR_H3_MESSAGE_ERROR -611 +/** + * @macro + * + * :macro:`NGHTTP3_ERR_WT_SESSION_GONE` indicates that WebTransport + * session was terminated or rejected. + */ +#define NGHTTP3_ERR_WT_SESSION_GONE -612 +/** + * @macro + * + * :macro:`NGHTTP3_ERR_WT_BUFFERED_STREAM_REJECTED` indicates that + * buffering WebTransport data stream was rejected. + */ +#define NGHTTP3_ERR_WT_BUFFERED_STREAM_REJECTED -613 /** * @macro * @@ -503,6 +524,38 @@ typedef uint64_t nghttp3_duration; * error code ``QPACK_DECODER_STREAM_ERROR``. */ #define NGHTTP3_QPACK_DECODER_STREAM_ERROR 0x0202 +/** + * @macro + * + * :macro:`NGHTTP3_WT_BUFFERED_STREAM_REJECTED` is WebTransport error + * code ``WT_BUFFERED_STREAM_REJECTED``. + */ +#define NGHTTP3_WT_BUFFERED_STREAM_REJECTED 0x3994BD84 +/** + * @macro + * + * :macro:`NGHTTP3_WT_SESSION_GONE` is WebTransport error code + * ``WT_SESSION_GONE``. + */ +#define NGHTTP3_WT_SESSION_GONE 0x170D7B68 +/** + * @macro + * + * :macro:`NGHTTP3_WT_ALPN_ERROR` is WebTransport error code + * ``WT_ALPN_ERROR``. + * + * https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3-15 + */ +#define NGHTTP3_WT_ALPN_ERROR 0x0817B3DD +/** + * @macro + * + * :macro:`NGHTTP3_WT_REQUIREMENTS_NOT_MET` is WebTransport error code + * ``WT_REQUIREMENTS_NOT_MET``. + * + * https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3-15 + */ +#define NGHTTP3_WT_REQUIREMENTS_NOT_MET 0x212C0D48 /** * @functypedef @@ -1899,6 +1952,16 @@ typedef struct nghttp3_settings { * .. version-added:: 1.13.0 */ nghttp3_qpack_indexing_strat qpack_indexing_strat; + /** + * :member:`wt_enabled`, if set to nonzero, enables WebTransport. + * + * https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3-15 + * + * TODO For client, it might be better to always enable + * WebTransport. Only draft version of client needs to send + * SETTINGS_WT_ENABLED. + */ + uint8_t wt_enabled; } nghttp3_settings; #define NGHTTP3_PROTO_SETTINGS_V1 1 @@ -1939,6 +2002,12 @@ typedef struct nghttp3_proto_settings { * Datagrams (see :rfc:`9297`). */ uint8_t h3_datagram; + /** + * :member:`wt_enabled`, if set to nonzero, enables WebTransport. + * + * https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3-15 + */ + uint8_t wt_enabled; } nghttp3_proto_settings; /** @@ -2312,6 +2381,69 @@ typedef int (*nghttp3_stream_close2)(nghttp3_conn *conn, uint32_t flags, void *conn_user_data, void *stream_user_data); +/** + * @functypedef + * + * :type:`nghttp3_recv_wt_data` is a callback function which is + * invoked when data is received on WebTransport data stream. + * |session_id| is the WebTransport session ID. |stream_id| is the + * stream ID of the WebTransport data stream. |data| points to the + * received data, and its length is |datalen|. + * + * The application is responsible for increasing flow control credit + * (say, increasing by |datalen| bytes). + * + * The implementation of this callback must return 0 if it succeeds. + * Returning :macro:`NGHTTP3_ERR_CALLBACK_FAILURE` will return to the + * caller immediately. Any values other than 0 is treated as + * :macro:`NGHTTP3_ERR_CALLBACK_FAILURE`. + */ +typedef int (*nghttp3_recv_wt_data)(nghttp3_conn *conn, int64_t session_id, + int64_t stream_id, const uint8_t *data, + size_t datalen, void *conn_user_data, + void *stream_user_data); + +/** + * @functypedef + * + * :type:`nghttp3_wt_data_stream_open` is a callback function which is + * invoked when a remote stream denoted by |stream_id| is identified + * as WebTransport data stream that belongs to WebTransport session + * identified by |session_id|. This callback function is called after + * WebTransport session is confirmed. + * + * The implementation of this callback must return 0 if it succeeds. + * Returning :macro:`NGHTTP3_ERR_CALLBACK_FAILURE` will return to the + * caller immediately. Any values other than 0 is treated as + * :macro:`NGHTTP3_ERR_CALLBACK_FAILURE`. + */ +typedef int (*nghttp3_wt_data_stream_open)(nghttp3_conn *conn, + int64_t session_id, + int64_t stream_id, + void *conn_user_data, + void *stream_user_data); + +/** + * @functypedef + * + * :type:`nghttp3_recv_wt_close_session` is a callback function which + * is invoked when WT_CLOSE_SESSION Capsule is received. The + * WebTransport session is identified by |session_id|. + * |wt_error_code| is Application Error Code. The buffer pointed by + * |msg| of length |msglen| contains Application Error Message. + * + * The implementation of this callback must return 0 if it succeeds. + * Returning :macro:`NGHTTP3_ERR_CALLBACK_FAILURE` will return to the + * caller immediately. Any values other than 0 is treated as + * :macro:`NGHTTP3_ERR_CALLBACK_FAILURE`. + */ +typedef int (*nghttp3_recv_wt_close_session)(nghttp3_conn *conn, + int64_t session_id, + uint32_t wt_error_code, + const uint8_t *msg, size_t msglen, + void *conn_user_data, + void *stream_user_data); + #define NGHTTP3_CALLBACKS_V1 1 #define NGHTTP3_CALLBACKS_V2 2 #define NGHTTP3_CALLBACKS_V3 3 @@ -2462,6 +2594,22 @@ typedef struct nghttp3_callbacks { * .. version-added:: 1.18.0 */ nghttp3_stream_close2 stream_close2; + /** + * :member:`recv_wt_data` is a callback function which is invoked + * when data on WebTransport data stream is received. + */ + nghttp3_recv_wt_data recv_wt_data; + /** + * :member:`wt_data_stream_open` is a callback function which is + * invoked when a remote stream is identified as WebTransport data + * stream. + */ + nghttp3_wt_data_stream_open wt_data_stream_open; + /** + * :member:`recv_wt_close_session` is a callback function which is + * invoked when WT_CLOSE_SESSION Capsule is received. + */ + nghttp3_recv_wt_close_session recv_wt_close_session; } nghttp3_callbacks; /** @@ -3476,6 +3624,165 @@ NGHTTP3_EXTERN int nghttp3_conn_is_drained(nghttp3_conn *conn); */ NGHTTP3_EXTERN int nghttp3_conn_is_drained2(const nghttp3_conn *conn); +/** + * @function + * + * `nghttp3_conn_submit_wt_request` works like + * `nghttp3_conn_submit_request`, but it is specifically tailored for + * WebTransport session establishment. |nva| of length |nvlen| + * specifies HTTP request header fields. They must contain at least + * the following fields: + * + * - :method = "CONNECT" + * - :scheme = "https" + * - :protocol = "webtransport-h3" + * - :authority + * - :path + * + * The application must also set the following settings: + * + * - :member:`nghttp3_settings.h3_datagram` = 1 + * - :member:`nghttp3_settings.wt_enabled` = 1 + * + * It also must send the following QUIC transport parameters: + * + * - max_datagram_frame_size > 0 + * - reset_stream_at + * + * The application should wait for SETTINGS frame from server and make + * sure that it satisfies server-side requirements for WebTransport. + * + * After receiving 2xx response from server, WebTransport session is + * established. `nghttp3_conn_open_wt_data_stream` is used to open + * WebTransport data streams. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * TBD + */ +NGHTTP3_EXTERN int nghttp3_conn_submit_wt_request(nghttp3_conn *conn, + int64_t stream_id, + const nghttp3_nv *nva, + size_t nvlen, + void *stream_user_data); + +/** + * @function + * + * `nghttp3_conn_submit_wt_response` works like + * `nghttp3_conn_submit_response`, but it is specifically tailored for + * WebTransport session establishment. |nva| of length |nvlen| + * specifies HTTP response header fields. It must contain 2xx status + * code in :status field. + * + * The application should make sure that the stream denoted by + * |stream_id| is a request stream that requests WebTransport session + * establishment. If this function is called inside + * :member:`nghttp3_callbacks.end_headers` callback, + * `nghttp3_conn_server_confirm_wt_session` is called internally, and + * it establishes WebTransport session. If this function is called + * outside of the callback, the application must call + * `nghttp3_conn_server_confirm_wt_session` after calling this + * function. + * + * If `nghttp3_conn_submit_response` is used against the WebTransport + * upgrade request, it means refusal of the request regardless of HTTP + * status code. The application is responsible to set non-2xx status + * code when `nghttp3_conn_submit_response` is used. If + * `nghttp3_conn_submit_response` is called from + * :member:`nghttp3_callbacks.end_headers` callback, + * :member:`nghttp3_callbacks.stop_sending` callback is automatically + * called. If `nghttp3_conn_submit_response` is called outside of the + * :member:`nghttp3_callbacks.end_headers` callback, + * :member:`nghttp3_callbacks.stop_sending` is not called + * automatically. The application should tell QUIC stack to send + * STOP_SENDING frame to this stream. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * TBD + */ +NGHTTP3_EXTERN int nghttp3_conn_submit_wt_response(nghttp3_conn *conn, + int64_t stream_id, + const nghttp3_nv *nva, + size_t nvlen); + +/** + * @function + * + * `nghttp3_conn_server_confirm_wt_session` establishes WebTransport + * session. This should be called after + * `nghttp3_conn_submit_wt_response` call if it is not called inside + * `nghttp3_callbacks.end_headers` callback. + * + * Only server can call this function. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * TBD + */ +NGHTTP3_EXTERN int nghttp3_conn_server_confirm_wt_session(nghttp3_conn *conn, + int64_t session_id, + nghttp3_tstamp ts); + +/** + * @function + * + * `nghttp3_conn_open_wt_data_stream` opens WebTransport data stream. + * |session_id| is the stream ID that established WebTransport + * session. |stream_id| is the stream ID to write data, and it can be + * both bidirectional and unidirectional. |dr| must not be NULL, and + * it must have non-NULL callback. + * + * This function can be also used to start writing to the + * bidirectional stream initiated by the remote endpoint. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * TBD + */ +NGHTTP3_EXTERN int nghttp3_conn_open_wt_data_stream( + nghttp3_conn *conn, int64_t session_id, int64_t stream_id, + const nghttp3_data_reader *dr, void *stream_user_data); + +/** + * @function + * + * `nghttp3_conn_close_wt_session` closes WebTransport session denoted + * by |session_id| which is the stream ID that established + * WebTransport session. |wt_error_code| is WebTransport error code. + * Upon calling this function, all existing WebTransport data streams + * are shutdown. |msg| of |msglen| bytes is the application error + * message, which is optional. |msglen| must be less than or equal to + * 1024. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * TBD + */ +NGHTTP3_EXTERN int nghttp3_conn_close_wt_session(nghttp3_conn *conn, + int64_t session_id, + uint32_t wt_error_code, + const uint8_t *msg, + size_t msglen); + +/** + * @function + * + * `nghttp3_conn_get_stream_wt_session_id` returns the WebTransport + * session ID of a stream denoted by |stream_id| if it is WebTransport + * data stream. If the stream is not found, it is not a WebTransport + * data stream, or it is unable to get session ID, this function + * returns -1. + */ +NGHTTP3_EXTERN int64_t nghttp3_conn_get_stream_wt_session_id( + const nghttp3_conn *conn, int64_t stream_id); + /** * @function * diff --git a/deps/ngtcp2/nghttp3/lib/includes/nghttp3/version.h b/deps/ngtcp2/nghttp3/lib/includes/nghttp3/version.h index adb41c1c7b2d..4b87e9052f5a 100644 --- a/deps/ngtcp2/nghttp3/lib/includes/nghttp3/version.h +++ b/deps/ngtcp2/nghttp3/lib/includes/nghttp3/version.h @@ -31,7 +31,7 @@ * * Version number of the nghttp3 library release. */ -#define NGHTTP3_VERSION "1.18.0" +#define NGHTTP3_VERSION "1.19.0-DEV" /** * @macro @@ -41,6 +41,6 @@ * number, 8 bits for minor and 8 bits for patch. Version 1.2.3 * becomes 0x010203. */ -#define NGHTTP3_VERSION_NUM 0x011200 +#define NGHTTP3_VERSION_NUM 0x011300 #endif /* !defined(NGHTTP3_VERSION_H) */ diff --git a/deps/ngtcp2/nghttp3/lib/nghttp3_conn.c b/deps/ngtcp2/nghttp3/lib/nghttp3_conn.c index 04b50ac3b632..60781a21f6b4 100644 --- a/deps/ngtcp2/nghttp3/lib/nghttp3_conn.c +++ b/deps/ngtcp2/nghttp3/lib/nghttp3_conn.c @@ -36,9 +36,22 @@ #include "nghttp3_unreachable.h" #include "nghttp3_settings.h" #include "nghttp3_callbacks.h" +#include "nghttp3_wt.h" nghttp3_objalloc_def(chunk, nghttp3_chunk, oplent) +/* + * conn_remote_stream returns nonzero if |stream_id| is a remote + * stream ID. + */ +static int conn_remote_stream(const nghttp3_conn *conn, int64_t stream_id) { + if (conn->server) { + return !(stream_id & 0x1); + } + + return stream_id & 0x1; +} + /* * conn_remote_stream_uni returns nonzero if |stream_id| is remote * unidirectional stream ID. @@ -50,6 +63,30 @@ static int conn_remote_stream_uni(const nghttp3_conn *conn, int64_t stream_id) { return (stream_id & 0x03) == 0x03; } +static int conn_wt_enabled(const nghttp3_conn *conn) { + const nghttp3_settings *local_settings = &conn->local.settings; + const nghttp3_proto_settings *remote_settings = &conn->remote.settings; + + if (!local_settings->wt_enabled || !local_settings->h3_datagram) { + return 0; + } + + if (conn->server) { + return (!(conn->flags & NGHTTP3_CONN_FLAG_SETTINGS_RECVED) || + /* TODO client sends SETTINGS_WT_ENABLED for draft + versions only. But some client implementations do not + send it. For interop purpose, do not require this + remote setting for now. */ + (/* remote_settings->wt_enabled && */ remote_settings + ->h3_datagram)) && + local_settings->enable_connect_protocol; + } + + return remote_settings->wt_enabled && + remote_settings->enable_connect_protocol && + remote_settings->h3_datagram; +} + static int conn_call_begin_headers(nghttp3_conn *conn, nghttp3_stream *stream) { int rv; @@ -251,6 +288,70 @@ static int conn_call_end_origin(nghttp3_conn *conn) { return 0; } +static int conn_call_recv_data(nghttp3_conn *conn, const nghttp3_stream *stream, + const uint8_t *data, size_t datalen) { + int rv; + + if (!conn->callbacks.recv_data) { + return 0; + } + + rv = conn->callbacks.recv_data(conn, stream->node.id, data, datalen, + conn->user_data, stream->user_data); + if (rv != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_recv_wt_data(nghttp3_conn *conn, + const nghttp3_stream *stream, + const uint8_t *data, size_t datalen) { + nghttp3_wt_session *wt_session; + int rv; + + if (!conn->callbacks.recv_wt_data) { + return 0; + } + + wt_session = stream->wt.session; + + assert(wt_session); + + rv = conn->callbacks.recv_wt_data(conn, wt_session->session_id, + stream->node.id, data, datalen, + conn->user_data, stream->user_data); + if (rv != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_wt_data_stream_open(nghttp3_conn *conn, + const nghttp3_stream *stream) { + nghttp3_wt_session *wt_session; + int rv; + + if (!conn->callbacks.wt_data_stream_open) { + return 0; + } + + wt_session = stream->wt.session; + + assert(wt_session); + + rv = conn->callbacks.wt_data_stream_open(conn, wt_session->session_id, + stream->node.id, conn->user_data, + stream->user_data); + if (rv != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + + return 0; +} + static int conn_glitch_ratelim_drain(nghttp3_conn *conn, uint64_t n, nghttp3_tstamp ts) { if (ts == UINT64_MAX) { @@ -401,11 +502,25 @@ int nghttp3_conn_server_new_versioned(nghttp3_conn **pconn, return 0; } +static void remove_wt_session_ref(nghttp3_wt_session *wt_session) { + nghttp3_stream *stream; + + for (stream = wt_session->head; stream; stream = stream->wt.next) { + assert(stream->wt.session == wt_session); + + stream->wt.session = NULL; + } +} + static int free_stream(void *data, void *ptr) { nghttp3_stream *stream = data; (void)ptr; + if (nghttp3_stream_wt_ctrl(stream)) { + remove_wt_session_ref(stream->wt.session); + } + nghttp3_stream_del(stream); return 0; @@ -501,6 +616,10 @@ nghttp3_ssize nghttp3_conn_read_stream2(nghttp3_conn *conn, int64_t stream_id, return rv; } + if (conn_wt_enabled(conn)) { + stream->flags |= NGHTTP3_STREAM_FLAG_MAYBE_WT_DATA; + } + if ((conn->flags & NGHTTP3_CONN_FLAG_GOAWAY_QUEUED) && conn->tx.goaway_id <= stream_id) { stream->rstate.state = NGHTTP3_REQ_STREAM_STATE_IGN_REST; @@ -527,7 +646,9 @@ nghttp3_ssize nghttp3_conn_read_stream2(nghttp3_conn *conn, int64_t stream_id, } stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_INITIAL; - } else if (nghttp3_server_stream_uni(stream_id)) { + } else if (nghttp3_server_stream_uni(stream_id) || + (conn_wt_enabled(conn) && + nghttp3_server_stream_bidi(stream_id))) { if (srclen == 0 && fin) { return 0; } @@ -537,6 +658,10 @@ nghttp3_ssize nghttp3_conn_read_stream2(nghttp3_conn *conn, int64_t stream_id, return rv; } + if (!(stream_id & 0x2) && conn_wt_enabled(conn)) { + stream->flags |= NGHTTP3_STREAM_FLAG_MAYBE_WT_DATA; + } + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; } else { /* client doesn't expect to receive new bidirectional stream or @@ -545,23 +670,26 @@ nghttp3_ssize nghttp3_conn_read_stream2(nghttp3_conn *conn, int64_t stream_id, } } else if (conn->server) { assert(nghttp3_client_stream_bidi(stream_id) || - nghttp3_client_stream_uni(stream_id)); + nghttp3_client_stream_uni(stream_id) || + (conn_wt_enabled(conn) && nghttp3_server_stream_bidi(stream_id))); } else { assert(nghttp3_client_stream_bidi(stream_id) || - nghttp3_server_stream_uni(stream_id)); + nghttp3_server_stream_uni(stream_id) || + (conn_wt_enabled(conn) && nghttp3_server_stream_bidi(stream_id))); } if (srclen == 0 && !fin) { return 0; } + if (fin) { + stream->flags |= NGHTTP3_STREAM_FLAG_READ_EOF; + } + if (nghttp3_stream_uni(stream_id)) { return nghttp3_conn_read_uni(conn, stream, src, srclen, fin, ts); } - if (fin) { - stream->flags |= NGHTTP3_STREAM_FLAG_READ_EOF; - } return nghttp3_conn_read_bidi(conn, &bidi_nproc, stream, src, srclen, fin, ts); } @@ -613,6 +741,12 @@ static nghttp3_ssize conn_read_type(nghttp3_conn *conn, nghttp3_stream *stream, conn->flags |= NGHTTP3_CONN_FLAG_QPACK_DECODER_OPENED; stream->type = NGHTTP3_STREAM_TYPE_QPACK_DECODER; break; + case NGHTTP3_STREAM_TYPE_WT_STREAM: + if (!conn_wt_enabled(conn)) { + return NGHTTP3_ERR_H3_STREAM_CREATION_ERROR; + } + stream->type = NGHTTP3_STREAM_TYPE_WT_STREAM; + break; default: stream->type = NGHTTP3_STREAM_TYPE_UNKNOWN; break; @@ -705,6 +839,10 @@ nghttp3_ssize nghttp3_conn_read_uni(nghttp3_conn *conn, nghttp3_stream *stream, } nconsumed = nghttp3_conn_read_qpack_decoder(conn, src, srclen); break; + case NGHTTP3_STREAM_TYPE_WT_STREAM: + nconsumed = + nghttp3_conn_read_wt_stream_uni(conn, stream, src, srclen, fin, ts); + break; case NGHTTP3_STREAM_TYPE_UNKNOWN: nconsumed = (nghttp3_ssize)srclen; break; @@ -797,7 +935,7 @@ nghttp3_ssize nghttp3_conn_read_control(nghttp3_conn *conn, case NGHTTP3_FRAME_SETTINGS: /* SETTINGS frame might be empty. */ if (rstate->left == 0) { - rv = conn_call_recv_settings(conn); + rv = nghttp3_conn_on_settings_received(conn); if (rv != 0) { return rv; } @@ -895,7 +1033,7 @@ nghttp3_ssize nghttp3_conn_read_control(nghttp3_conn *conn, case NGHTTP3_CTRL_STREAM_STATE_SETTINGS: for (;;) { if (rstate->left == 0) { - rv = conn_call_recv_settings(conn); + rv = nghttp3_conn_on_settings_received(conn); if (rv != 0) { return rv; } @@ -1013,7 +1151,7 @@ nghttp3_ssize nghttp3_conn_read_control(nghttp3_conn *conn, break; } - rv = conn_call_recv_settings(conn); + rv = nghttp3_conn_on_settings_received(conn); if (rv != 0) { return rv; } @@ -1338,6 +1476,35 @@ nghttp3_ssize nghttp3_conn_read_control(nghttp3_conn *conn, return (nghttp3_ssize)nconsumed; } +static int conn_unlink_wt_session(nghttp3_conn *conn, + nghttp3_stream *wt_ctrl_stream) { + nghttp3_wt_session *wt_session = wt_ctrl_stream->wt.session; + nghttp3_stream *stream, *next; + int rv; + (void)rv; + + for (stream = wt_session->head; stream;) { + next = stream->wt.next; + + assert(stream->wt.session); + + stream->wt.session = NULL; + stream->wt.prev = stream->wt.next = NULL; + + rv = nghttp3_conn_shutdown_wt_data_stream(conn, stream, + NGHTTP3_WT_SESSION_GONE); + if (rv != 0) { + return rv; + } + + stream = next; + } + + wt_session->head = NULL; + + return 0; +} + static int conn_delete_stream(nghttp3_conn *conn, nghttp3_stream *stream, uint32_t flags, uint64_t rx_app_error_code, uint64_t tx_app_error_code) { @@ -1385,6 +1552,15 @@ static int conn_delete_stream(nghttp3_conn *conn, nghttp3_stream *stream, } } + if (nghttp3_stream_wt_ctrl(stream)) { + rv = conn_unlink_wt_session(conn, stream); + if (rv != 0) { + return rv; + } + } else if (nghttp3_stream_wt_data(stream)) { + nghttp3_wt_session_remove_stream(stream->wt.session, stream); + } + if (conn->server && nghttp3_client_stream_bidi(stream->node.id)) { assert(conn->remote.bidi.num_streams > 0); @@ -1518,6 +1694,7 @@ nghttp3_ssize nghttp3_conn_read_bidi(nghttp3_conn *conn, size_t *pnproc, int rv; nghttp3_stream_read_state *rstate = &stream->rstate; nghttp3_varint_read_state *rvint = &rstate->rvint; + nghttp3_stream *wt_ctrl_stream; nghttp3_ssize nread; size_t nconsumed = 0; int busy = 0; @@ -1529,7 +1706,8 @@ nghttp3_ssize nghttp3_conn_read_bidi(nghttp3_conn *conn, size_t *pnproc, return (nghttp3_ssize)srclen; } - if (stream->flags & NGHTTP3_STREAM_FLAG_QPACK_DECODE_BLOCKED) { + if (stream->flags & (NGHTTP3_STREAM_FLAG_QPACK_DECODE_BLOCKED | + NGHTTP3_STREAM_FLAG_WT_SESSION_BLOCKED)) { *pnproc = 0; if (srclen == 0) { @@ -1638,6 +1816,43 @@ nghttp3_ssize nghttp3_conn_read_bidi(nghttp3_conn *conn, size_t *pnproc, } rstate->state = NGHTTP3_REQ_STREAM_STATE_HEADERS; + break; + case NGHTTP3_EXFR_WT_STREAM_BIDI: + if (!nghttp3_stream_wt_data(stream) && + !(stream->flags & NGHTTP3_STREAM_FLAG_MAYBE_WT_DATA)) { + return NGHTTP3_ERR_H3_FRAME_ERROR; + } + + stream->flags &= (uint16_t)~NGHTTP3_STREAM_FLAG_MAYBE_WT_DATA; + + if (conn->server) { + if (stream->rx.hstate != NGHTTP3_HTTP_STATE_REQ_INITIAL) { + return NGHTTP3_ERR_H3_FRAME_ERROR; + } + } else if (stream->rx.hstate != NGHTTP3_HTTP_STATE_RESP_INITIAL) { + return NGHTTP3_ERR_H3_FRAME_ERROR; + } + + /* rstate->left is Session ID */ + rv = nghttp3_conn_on_wt_stream(conn, stream, (int64_t)rstate->left); + if (rv != 0) { + if (!nghttp3_err_is_wt(rv)) { + return rv; + } + + stream->rstate.state = NGHTTP3_REQ_STREAM_STATE_IGN_REST; + + rv = nghttp3_conn_abort_stream( + conn, stream, nghttp3_err_infer_quic_app_error_code(rv)); + if (rv != 0) { + return rv; + } + + break; + } + + rstate->state = NGHTTP3_REQ_STREAM_STATE_BEFORE_WT_DATA; + break; case NGHTTP3_FRAME_PUSH_PROMISE: /* We do not support push */ case NGHTTP3_FRAME_CANCEL_PUSH: @@ -1657,6 +1872,8 @@ nghttp3_ssize nghttp3_conn_read_bidi(nghttp3_conn *conn, size_t *pnproc, return NGHTTP3_ERR_H3_EXCESSIVE_LOAD; } + stream->flags &= (uint16_t)~NGHTTP3_STREAM_FLAG_MAYBE_WT_DATA; + /* TODO Handle reserved frame type */ busy = 1; rstate->state = NGHTTP3_REQ_STREAM_STATE_IGN_FRAME; @@ -1665,11 +1882,29 @@ nghttp3_ssize nghttp3_conn_read_bidi(nghttp3_conn *conn, size_t *pnproc, break; case NGHTTP3_REQ_STREAM_STATE_DATA: len = (size_t)nghttp3_min(rstate->left, (uint64_t)(end - p)); - rv = nghttp3_conn_on_data(conn, stream, p, len); - if (rv != 0) { - return rv; + nread = nghttp3_conn_on_data(conn, stream, p, len); + if (nread < 0) { + if (!nghttp3_err_is_wt((int)nread)) { + return nread; + } + + rv = nghttp3_conn_shutdown_wt_session( + conn, stream, nghttp3_err_infer_quic_app_error_code((int)nread)); + if (rv != 0) { + return rv; + } + + /* Now that the stream is in + NGHTTP3_REQ_STREAM_STATE_IGN_REST, end_stream callback is + not called. */ + + /* Pretend that all stream data have been consumed */ + nconsumed += len; + + goto almost_done; } p += len; + nconsumed += (size_t)nread; rstate->left -= len; if (rstate->left) { @@ -1741,7 +1976,9 @@ nghttp3_ssize nghttp3_conn_read_bidi(nghttp3_conn *conn, size_t *pnproc, return rv; } } - /* fall through */ + + rv = conn_call_end_headers(conn, stream, p == end && fin); + break; case NGHTTP3_HTTP_STATE_RESP_HEADERS_BEGIN: rv = conn_call_end_headers(conn, stream, p == end && fin); break; @@ -1763,6 +2000,62 @@ nghttp3_ssize nghttp3_conn_read_bidi(nghttp3_conn *conn, size_t *pnproc, nghttp3_stream_read_state_reset(rstate); + if (conn->server) { + if (stream->rx.hstate == NGHTTP3_HTTP_STATE_REQ_HEADERS_END) { + if (stream->wt.session && (stream->wt.session->flags & + NGHTTP3_WT_SESSION_FLAG_RESP_SUBMITTED)) { + /* Server has submitted WebTransport session. */ + rv = nghttp3_conn_on_wt_session_confirmed(conn, stream, ts); + if (rv != 0) { + return rv; + } + } else if (stream->rx.http.flags & NGHTTP3_HTTP_FLAG_WEBTRANSPORT) { + if (stream->flags & NGHTTP3_STREAM_FLAG_RESP_SUBMITTED) { + /* Server refused WebTransport upgrade request */ + stream->rstate.state = NGHTTP3_REQ_STREAM_STATE_IGN_REST; + + rv = conn_call_stop_sending(conn, stream, NGHTTP3_H3_NO_ERROR); + if (rv != 0) { + return rv; + } + } else { + /* Server has not submitted response */ + stream->flags |= NGHTTP3_STREAM_FLAG_WT_SESSION_BLOCKED; + if (p != end) { + rv = nghttp3_stream_buffer_data(stream, p, (size_t)(end - p)); + if (rv != 0) { + return rv; + } + } + + *pnproc = (size_t)(p - src); + + return (nghttp3_ssize)nconsumed; + } + } + } + } else if (stream->rx.hstate == NGHTTP3_HTTP_STATE_RESP_HEADERS_END && + stream->wt.session) { + if (stream->rx.http.status_code / 100 == 2) { + rv = nghttp3_conn_on_wt_session_confirmed(conn, stream, ts); + if (rv != 0) { + return rv; + } + } else { + /* Server refused WebTransport negotiation. Reset the session + stream. This could be a redirect, but client is instructed + not to follow the redirect automatically. Most of the + case, we cannot do anything but just close the stream. */ + stream->rstate.state = NGHTTP3_REQ_STREAM_STATE_IGN_REST; + + rv = nghttp3_conn_abort_stream(conn, stream, + NGHTTP3_H3_REQUEST_CANCELLED); + if (rv != 0) { + return rv; + } + } + } + break; case NGHTTP3_REQ_STREAM_STATE_IGN_FRAME: len = (size_t)nghttp3_min(rstate->left, (uint64_t)(end - p)); @@ -1776,6 +2069,40 @@ nghttp3_ssize nghttp3_conn_read_bidi(nghttp3_conn *conn, size_t *pnproc, nghttp3_stream_read_state_reset(rstate); break; + case NGHTTP3_REQ_STREAM_STATE_BEFORE_WT_DATA: + rstate->state = NGHTTP3_REQ_STREAM_STATE_WT_DATA; + + assert(stream->wt.session); + + wt_ctrl_stream = + nghttp3_conn_find_stream(conn, stream->wt.session->session_id); + + if (!(wt_ctrl_stream->wt.session->flags & + NGHTTP3_WT_SESSION_FLAG_CONFIRMED)) { + stream->flags |= NGHTTP3_STREAM_FLAG_WT_SESSION_BLOCKED; + + if (p != end) { + rv = nghttp3_stream_buffer_data(stream, p, (size_t)(end - p)); + if (rv != 0) { + return rv; + } + } + + *pnproc = (size_t)(p - src); + + return (nghttp3_ssize)nconsumed; + } + + break; + case NGHTTP3_REQ_STREAM_STATE_WT_DATA: + rv = conn_call_recv_wt_data(conn, stream, p, (size_t)(end - p)); + if (rv != 0) { + return rv; + } + + p = end; + + goto almost_done; case NGHTTP3_REQ_STREAM_STATE_IGN_REST: nconsumed += (size_t)(end - p); *pnproc = (size_t)(end - src); @@ -1795,10 +2122,16 @@ nghttp3_ssize nghttp3_conn_read_bidi(nghttp3_conn *conn, size_t *pnproc, if (rv != 0) { return rv; } + + /* Fall through */ + /* When a stream is closed without any data */ + case NGHTTP3_REQ_STREAM_STATE_BEFORE_WT_DATA: + case NGHTTP3_REQ_STREAM_STATE_WT_DATA: rv = conn_call_end_stream(conn, stream); if (rv != 0) { return rv; } + break; case NGHTTP3_REQ_STREAM_STATE_IGN_REST: break; @@ -1811,8 +2144,8 @@ nghttp3_ssize nghttp3_conn_read_bidi(nghttp3_conn *conn, size_t *pnproc, return (nghttp3_ssize)nconsumed; } -int nghttp3_conn_on_data(nghttp3_conn *conn, nghttp3_stream *stream, - const uint8_t *data, size_t datalen) { +nghttp3_ssize nghttp3_conn_on_data(nghttp3_conn *conn, nghttp3_stream *stream, + const uint8_t *data, size_t datalen) { int rv; rv = nghttp3_http_on_data_chunk(stream, datalen); @@ -1820,17 +2153,21 @@ int nghttp3_conn_on_data(nghttp3_conn *conn, nghttp3_stream *stream, return rv; } - if (!conn->callbacks.recv_data) { - return 0; + if (!stream->wt.session) { + return conn_call_recv_data(conn, stream, data, datalen); } - rv = conn->callbacks.recv_data(conn, stream->node.id, data, datalen, - conn->user_data, stream->user_data); + /* The stream data must be buffered until WebTransport session has + been confirmed. */ + assert(stream->wt.session->flags & NGHTTP3_WT_SESSION_FLAG_CONFIRMED); + + rv = nghttp3_conn_read_wt_ctrl_stream(conn, stream, data, datalen); if (rv != 0) { - return NGHTTP3_ERR_CALLBACK_FAILURE; + return rv; } - return 0; + /* WebTransport control stream has consumed all data */ + return (nghttp3_ssize)datalen; } static nghttp3_pq *conn_get_sched_pq(nghttp3_conn *conn, nghttp3_tnode *tnode) { @@ -1975,6 +2312,12 @@ int nghttp3_conn_on_settings_entry_received(nghttp3_conn *conn, break; } +#if SIZE_MAX < UINT64_MAX + if (ent->value > SIZE_MAX) { + return NGHTTP3_ERR_H3_SETTINGS_ERROR; + } +#endif /* SIZE_MAX < UINT64_MAX */ + dest->qpack_max_dtable_capacity = (size_t)ent->value; nghttp3_qpack_encoder_set_max_dtable_capacity(&conn->qenc, @@ -1989,6 +2332,12 @@ int nghttp3_conn_on_settings_entry_received(nghttp3_conn *conn, break; } +#if SIZE_MAX < UINT64_MAX + if (ent->value > SIZE_MAX) { + return NGHTTP3_ERR_H3_SETTINGS_ERROR; + } +#endif /* SIZE_MAX < UINT64_MAX */ + dest->qpack_blocked_streams = (size_t)ent->value; nghttp3_qpack_encoder_set_max_blocked_streams( @@ -2025,6 +2374,14 @@ int nghttp3_conn_on_settings_entry_received(nghttp3_conn *conn, dest->h3_datagram = (uint8_t)ent->value; break; + case NGHTTP3_SETTINGS_ID_WT_ENABLED: + /* compat for pre draft-15 */ + case NGHTTP3_SETTINGS_ID_WT_MAX_SESSIONS: + case NGHTTP3_SETTINGS_ID_WT_MAX_SESSIONS_DRAFT7: + /* compat for ancient draft */ + case NGHTTP3_SETTINGS_ID_ENABLE_WEBTRANSPORT_DRAFT2: + dest->wt_enabled = ent->value != 0; + break; case NGHTTP3_H2_SETTINGS_ID_ENABLE_PUSH: case NGHTTP3_H2_SETTINGS_ID_MAX_CONCURRENT_STREAMS: case NGHTTP3_H2_SETTINGS_ID_INITIAL_WINDOW_SIZE: @@ -2038,6 +2395,37 @@ int nghttp3_conn_on_settings_entry_received(nghttp3_conn *conn, return 0; } +static int abort_wt_session(void *data, void *ptr) { + nghttp3_conn *conn = ptr; + nghttp3_stream *stream = data; + + if (!nghttp3_stream_wt_ctrl(stream)) { + return 0; + } + + stream->rstate.state = NGHTTP3_REQ_STREAM_STATE_IGN_REST; + + return nghttp3_conn_abort_stream(conn, stream, + NGHTTP3_H3_GENERAL_PROTOCOL_ERROR); +} + +int nghttp3_conn_on_settings_received(nghttp3_conn *conn) { + int rv; + + conn->flags |= NGHTTP3_CONN_FLAG_SETTINGS_RECVED; + + rv = conn_call_recv_settings(conn); + if (rv != 0) { + return rv; + } + + if (!conn->local.settings.wt_enabled || conn_wt_enabled(conn)) { + return 0; + } + + return nghttp3_map_each(&conn->streams, abort_wt_session, conn); +} + static int conn_on_priority_update_stream(nghttp3_conn *conn, const nghttp3_frame_priority_update *fr) { @@ -2079,6 +2467,11 @@ conn_on_priority_update_stream(nghttp3_conn *conn, stream->node.pri = fr->pri; stream->flags |= NGHTTP3_STREAM_FLAG_PRIORITY_UPDATE_RECVED; + + if (conn_wt_enabled(conn)) { + stream->flags |= NGHTTP3_STREAM_FLAG_MAYBE_WT_DATA; + } + stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_INITIAL; return 0; @@ -2347,7 +2740,7 @@ nghttp3_ssize nghttp3_conn_writev_stream(nghttp3_conn *conn, return ncnt; } - if (nghttp3_client_stream_bidi(stream->node.id) && + if (nghttp3_stream_schedulable(stream) && !nghttp3_stream_require_schedule(stream)) { nghttp3_conn_unschedule_stream(conn, stream); } @@ -2386,7 +2779,7 @@ int nghttp3_conn_add_write_offset(nghttp3_conn *conn, int64_t stream_id, stream->unscheduled_nwrite += n; - if (!nghttp3_client_stream_bidi(stream->node.id)) { + if (!nghttp3_stream_schedulable(stream)) { return 0; } @@ -2428,6 +2821,22 @@ int nghttp3_conn_update_ack_offset(nghttp3_conn *conn, int64_t stream_id, return nghttp3_stream_update_ack_offset(stream, offset); } +static nghttp3_ssize wt_session_read_data(nghttp3_conn *conn, int64_t stream_id, + nghttp3_vec *vec, size_t veccnt, + uint32_t *pflags, + void *conn_user_data, + void *stream_user_data) { + (void)conn; + (void)stream_id; + (void)vec; + (void)veccnt; + (void)pflags; + (void)conn_user_data; + (void)stream_user_data; + + return NGHTTP3_ERR_WOULDBLOCK; +} + static int conn_submit_headers_data(nghttp3_conn *conn, nghttp3_stream *stream, const nghttp3_nv *nva, size_t nvlen, const nghttp3_data_reader *dr) { @@ -2452,7 +2861,7 @@ static int conn_submit_headers_data(nghttp3_conn *conn, nghttp3_stream *stream, .nvlen = nvlen, }; - if (dr) { + if (dr && dr->read_data != wt_session_read_data) { rv = nghttp3_stream_frq_emplace(stream, &fr); if (rv != 0) { return rv; @@ -2579,6 +2988,8 @@ int nghttp3_conn_submit_response(nghttp3_conn *conn, int64_t stream_id, stream->flags |= NGHTTP3_STREAM_FLAG_WRITE_END_STREAM; } + stream->flags |= NGHTTP3_STREAM_FLAG_RESP_SUBMITTED; + return conn_submit_headers_data(conn, stream, nva, nvlen, dr); } @@ -2656,14 +3067,27 @@ int nghttp3_conn_shutdown(nghttp3_conn *conn) { } int nghttp3_conn_reject_stream(nghttp3_conn *conn, nghttp3_stream *stream) { + return nghttp3_conn_abort_stream(conn, stream, NGHTTP3_H3_REQUEST_REJECTED); +} + +int nghttp3_conn_abort_stream(nghttp3_conn *conn, nghttp3_stream *stream, + uint64_t error_code) { int rv; + int remote_uni = conn_remote_stream_uni(conn, stream->node.id); + int bidi = !nghttp3_stream_uni(stream->node.id); - rv = conn_call_stop_sending(conn, stream, NGHTTP3_H3_REQUEST_REJECTED); - if (rv != 0) { - return rv; + if (remote_uni || bidi) { + rv = conn_call_stop_sending(conn, stream, error_code); + if (rv != 0) { + return rv; + } + } + + if (remote_uni) { + return 0; } - return conn_call_reset_stream(conn, stream, NGHTTP3_H3_REQUEST_REJECTED); + return conn_call_reset_stream(conn, stream, error_code); } void nghttp3_conn_block_stream(nghttp3_conn *conn, int64_t stream_id) { @@ -2676,7 +3100,7 @@ void nghttp3_conn_block_stream(nghttp3_conn *conn, int64_t stream_id) { stream->flags |= NGHTTP3_STREAM_FLAG_FC_BLOCKED; stream->unscheduled_nwrite = 0; - if (nghttp3_client_stream_bidi(stream->node.id)) { + if (nghttp3_stream_schedulable(stream)) { nghttp3_conn_unschedule_stream(conn, stream); } } @@ -2691,7 +3115,7 @@ void nghttp3_conn_shutdown_stream_write(nghttp3_conn *conn, int64_t stream_id) { stream->flags |= NGHTTP3_STREAM_FLAG_SHUT_WR; stream->unscheduled_nwrite = 0; - if (nghttp3_client_stream_bidi(stream->node.id)) { + if (nghttp3_stream_schedulable(stream)) { nghttp3_conn_unschedule_stream(conn, stream); } } @@ -2705,7 +3129,7 @@ int nghttp3_conn_unblock_stream(nghttp3_conn *conn, int64_t stream_id) { stream->flags &= (uint16_t)~NGHTTP3_STREAM_FLAG_FC_BLOCKED; - if (nghttp3_client_stream_bidi(stream->node.id) && + if (nghttp3_stream_schedulable(stream) && nghttp3_stream_require_schedule(stream)) { return nghttp3_conn_ensure_stream_scheduled(conn, stream); } @@ -2739,7 +3163,7 @@ int nghttp3_conn_resume_stream(nghttp3_conn *conn, int64_t stream_id) { stream->flags &= (uint16_t)~NGHTTP3_STREAM_FLAG_READ_DATA_BLOCKED; - if (nghttp3_client_stream_bidi(stream->node.id) && + if (nghttp3_stream_schedulable(stream) && nghttp3_stream_require_schedule(stream)) { return nghttp3_conn_ensure_stream_scheduled(conn, stream); } @@ -2765,8 +3189,7 @@ int nghttp3_conn_close_stream2(nghttp3_conn *conn, uint32_t flags, return NGHTTP3_ERR_STREAM_NOT_FOUND; } - if (nghttp3_stream_uni(stream_id) && - stream->type != NGHTTP3_STREAM_TYPE_UNKNOWN) { + if (nghttp3_stream_critical(stream)) { return NGHTTP3_ERR_H3_CLOSED_CRITICAL_STREAM; } @@ -2793,6 +3216,13 @@ int nghttp3_conn_shutdown_stream_read(nghttp3_conn *conn, int64_t stream_id) { } stream->flags |= NGHTTP3_STREAM_FLAG_SHUT_RD; + + /* If stream is WebTransport data stream, do not send QPACK Stream + Cancellation. */ + if (nghttp3_stream_wt_data(stream) || + (stream->flags & NGHTTP3_STREAM_FLAG_WT_DATA)) { + return 0; + } } return nghttp3_qpack_decoder_cancel_stream(&conn->qdec, stream_id); @@ -3032,5 +3462,801 @@ int nghttp3_conn_is_stream_flushed(const nghttp3_conn *conn, fr = nghttp3_ringbuf_get(&stream->frq, 0); - return fr->hd.type == NGHTTP3_FRAME_DATA; + return fr->hd.type == NGHTTP3_FRAME_DATA || + (fr->hd.type == NGHTTP3_FRAME_EX_WT && + fr->wt.fr.hd.type == NGHTTP3_EXFR_WT_STREAM_DATA); +} + +int nghttp3_conn_submit_wt_request(nghttp3_conn *conn, int64_t stream_id, + const nghttp3_nv *nva, size_t nvlen, + void *stream_user_data) { + int rv; + nghttp3_stream *stream; + + if (!conn_wt_enabled(conn)) { + return NGHTTP3_ERR_INVALID_STATE; + } + + rv = nghttp3_conn_submit_request( + conn, stream_id, nva, nvlen, + &(nghttp3_data_reader){.read_data = wt_session_read_data}, + stream_user_data); + if (rv != 0) { + return rv; + } + + stream = nghttp3_conn_find_stream(conn, stream_id); + + assert(stream); + + return nghttp3_conn_open_wt_session(conn, stream); +} + +int nghttp3_conn_submit_wt_response(nghttp3_conn *conn, int64_t stream_id, + const nghttp3_nv *nva, size_t nvlen) { + int rv; + nghttp3_stream *stream; + + if (!conn_wt_enabled(conn)) { + return NGHTTP3_ERR_INVALID_STATE; + } + + rv = nghttp3_conn_submit_response( + conn, stream_id, nva, nvlen, + &(nghttp3_data_reader){.read_data = wt_session_read_data}); + if (rv != 0) { + return rv; + } + + stream = nghttp3_conn_find_stream(conn, stream_id); + + if (!stream->wt.session) { + rv = nghttp3_conn_open_wt_session(conn, stream); + if (rv != 0) { + return rv; + } + } + + stream->wt.session->flags |= NGHTTP3_WT_SESSION_FLAG_RESP_SUBMITTED; + + return 0; +} + +int nghttp3_conn_server_confirm_wt_session(nghttp3_conn *conn, + int64_t session_id, + nghttp3_tstamp ts) { + nghttp3_stream *wt_ctrl_stream; + + wt_ctrl_stream = nghttp3_conn_find_stream(conn, session_id); + if (!wt_ctrl_stream) { + return NGHTTP3_ERR_STREAM_NOT_FOUND; + } + + assert(wt_ctrl_stream->wt.session); + assert(wt_ctrl_stream->wt.session->flags & + NGHTTP3_WT_SESSION_FLAG_RESP_SUBMITTED); + + wt_ctrl_stream->flags &= (uint16_t)~NGHTTP3_STREAM_FLAG_WT_SESSION_BLOCKED; + + return nghttp3_conn_on_wt_session_confirmed(conn, wt_ctrl_stream, ts); +} + +int nghttp3_conn_open_wt_session(nghttp3_conn *conn, nghttp3_stream *stream) { + return nghttp3_wt_session_new(&stream->wt.session, stream->node.id, + conn->mem); +} + +int nghttp3_conn_open_wt_data_stream(nghttp3_conn *conn, int64_t session_id, + int64_t stream_id, + const nghttp3_data_reader *dr, + void *stream_user_data) { + nghttp3_stream *stream, *wt_ctrl_stream; + nghttp3_wt_session *wt_session; + nghttp3_frame *fr; + uint64_t type; + int rv; + int remote_bidi = 0; + + if (conn->server) { + assert(nghttp3_client_stream_bidi(stream_id) || + nghttp3_server_stream_bidi(stream_id) || + nghttp3_server_stream_uni(stream_id)); + } else { + assert(nghttp3_client_stream_bidi(stream_id) || + nghttp3_server_stream_bidi(stream_id) || + nghttp3_client_stream_uni(stream_id)); + } + + /* TODO Check session flow control */ + + assert(dr); + + if (conn->flags & NGHTTP3_CONN_FLAG_GOAWAY_RECVED) { + return NGHTTP3_ERR_CONN_CLOSING; + } + + wt_ctrl_stream = nghttp3_conn_find_stream(conn, session_id); + if (!wt_ctrl_stream || !wt_ctrl_stream->wt.session) { + return NGHTTP3_ERR_INVALID_ARGUMENT; + } + + wt_session = wt_ctrl_stream->wt.session; + + stream = nghttp3_conn_find_stream(conn, stream_id); + if (stream) { + if (conn->server) { + assert(nghttp3_client_stream_bidi(stream_id)); + } else { + assert(nghttp3_server_stream_bidi(stream_id)); + } + + /* TODO verify that we do not start writing more than once. */ + + /* Normally, stream->wt.session is not NULL because we must + identify WT stream header first. The only exception is a + stream create by priority update on server side. But it must + be client initiated bidi stream, and we must wait for its WT + header. */ + if (!stream->wt.session) { + return NGHTTP3_ERR_INVALID_ARGUMENT; + } + + if (stream->flags & NGHTTP3_STREAM_FLAG_WRITE_END_STREAM) { + return NGHTTP3_ERR_INVALID_STATE; + } + + remote_bidi = 1; + + if (stream_user_data) { + stream->user_data = stream_user_data; + } + + if (conn->server) { + stream->flags |= NGHTTP3_STREAM_FLAG_SERVER_PRIORITY_SET; + } + + assert(!nghttp3_tnode_is_scheduled(&stream->node)); + } else { + if (conn->server) { + assert(nghttp3_server_stream_bidi(stream_id) || + nghttp3_server_stream_uni(stream_id)); + } else { + assert(nghttp3_client_stream_bidi(stream_id) || + nghttp3_client_stream_uni(stream_id)); + } + + rv = nghttp3_conn_create_stream(conn, &stream, stream_id); + if (rv != 0) { + return rv; + } + + nghttp3_wt_session_add_stream(wt_session, stream); + + if (conn->server) { + stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_INITIAL; + } else { + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + } + + stream->user_data = stream_user_data; + + if (stream_id & 0x2) { + stream->flags |= NGHTTP3_STREAM_FLAG_SHUT_RD; + stream->type = NGHTTP3_STREAM_TYPE_WT_STREAM; + } + } + + stream->node.pri = (nghttp3_pri){ + .urgency = NGHTTP3_DEFAULT_URGENCY, + .inc = 1, + }; + + if (stream_id & 0x2) { + type = NGHTTP3_EXFR_WT_STREAM_UNI; + } else if (remote_bidi) { + type = NGHTTP3_EXFR_WT_STREAM_DATA; + } else { + type = NGHTTP3_EXFR_WT_STREAM_BIDI; + stream->rstate.state = NGHTTP3_REQ_STREAM_STATE_BEFORE_WT_DATA; + } + + rv = nghttp3_stream_frq_emplace(stream, &fr); + if (rv != 0) { + return rv; + } + + fr->wt = (nghttp3_frame_ex_wt){ + .type = NGHTTP3_FRAME_EX_WT, + .fr.wt_stream = + { + .type = type, + .session_id = session_id, + .dr = *dr, + }, + }; + + if (nghttp3_stream_require_schedule(stream)) { + return nghttp3_conn_ensure_stream_scheduled(conn, stream); + } + + return 0; +} + +int nghttp3_conn_close_wt_session(nghttp3_conn *conn, int64_t session_id, + uint32_t wt_error_code, const uint8_t *msg, + size_t msglen) { + nghttp3_stream *stream; + nghttp3_wt_session *wt_session; + nghttp3_frame *fr; + int rv; + + stream = nghttp3_conn_find_stream(conn, session_id); + if (stream == NULL) { + return NGHTTP3_ERR_STREAM_NOT_FOUND; + } + + if (!nghttp3_stream_wt_ctrl(stream) || msglen > 1024) { + return NGHTTP3_ERR_INVALID_ARGUMENT; + } + + if (stream->flags & NGHTTP3_STREAM_FLAG_WRITE_END_STREAM) { + return NGHTTP3_ERR_INVALID_STATE; + } + + stream->flags |= NGHTTP3_STREAM_FLAG_WRITE_END_STREAM; + + wt_session = stream->wt.session; + + assert(!wt_session->tx.error_msg.base); + + if (msglen) { + wt_session->tx.error_msg.base = nghttp3_mem_malloc(conn->mem, msglen); + if (!wt_session->tx.error_msg.base) { + return NGHTTP3_ERR_NOMEM; + } + + memcpy(wt_session->tx.error_msg.base, msg, msglen); + wt_session->tx.error_msg.len = msglen; + } + + rv = nghttp3_stream_frq_emplace(stream, &fr); + if (rv != 0) { + return rv; + } + + fr->cpsl = (nghttp3_frame_ex_cpsl){ + .type = NGHTTP3_FRAME_EX_CPSL, + .fr.wt_close_session = + { + .type = NGHTTP3_EXFR_CPSL_WT_CLOSE_SESSION, + .error_code = wt_error_code, + .error_msg = wt_session->tx.error_msg, + }, + }; + + if (nghttp3_stream_require_schedule(stream)) { + rv = nghttp3_conn_schedule_stream(conn, stream); + if (rv != 0) { + return rv; + } + } + + rv = nghttp3_conn_shutdown_all_wt_data_streams(conn, stream, + NGHTTP3_WT_SESSION_GONE); + if (rv != 0) { + return rv; + } + + stream->rstate.state = NGHTTP3_REQ_STREAM_STATE_IGN_REST; + + + return 0; + // turn it off for now as it set up firefox and chromium + // return conn_call_stop_sending(conn, stream, NGHTTP3_WT_SESSION_GONE); +} + +int nghttp3_conn_on_wt_stream(nghttp3_conn *conn, nghttp3_stream *stream, + int64_t session_id) { + nghttp3_stream *wt_ctrl_stream; + nghttp3_wt_session *wt_session; + int rv; + + if (!nghttp3_client_stream_bidi(session_id)) { + return NGHTTP3_ERR_WT_BUFFERED_STREAM_REJECTED; + } + + if (stream->wt.session) { + assert(stream->wt.session->session_id != stream->node.id); + + if (stream->wt.session->session_id != session_id) { + return NGHTTP3_ERR_WT_BUFFERED_STREAM_REJECTED; + } + + return 0; + } + + wt_ctrl_stream = nghttp3_conn_find_stream(conn, session_id); + if (wt_ctrl_stream) { + if (conn->server) { + if ((wt_ctrl_stream->flags & NGHTTP3_STREAM_FLAG_RESP_SUBMITTED) && + !wt_ctrl_stream->wt.session) { + /* Server has submitted the regular non-WebTransport response. + wt_ctrl_stream is not WebTransport session stream. */ + return NGHTTP3_ERR_WT_BUFFERED_STREAM_REJECTED; + } + } else if (!wt_ctrl_stream->wt.session) { + /* On client side, if it has not submitted the request with + nghttp3_conn_submit_wt_request, it is not WebTransport + session stream. */ + return NGHTTP3_ERR_WT_BUFFERED_STREAM_REJECTED; + } + } else { + if (!conn->server) { + /* On client's perspective, if session stream is not found, we are + sure that session is gone. */ + return NGHTTP3_ERR_WT_SESSION_GONE; + } + + if (nghttp3_ord_stream_id(session_id) > + conn->remote.bidi.max_client_streams) { + return NGHTTP3_ERR_WT_BUFFERED_STREAM_REJECTED; + } + + if ((conn->flags & NGHTTP3_CONN_FLAG_GOAWAY_QUEUED) && + conn->tx.goaway_id <= session_id) { + return NGHTTP3_ERR_WT_BUFFERED_STREAM_REJECTED; + } + + rv = conn_bidi_idtr_open(conn, session_id); + if (rv != 0) { + if (nghttp3_err_is_fatal(rv)) { + return rv; + } + + return NGHTTP3_ERR_WT_SESSION_GONE; + } + + conn->rx.max_stream_id_bidi = + nghttp3_max(conn->rx.max_stream_id_bidi, session_id); + rv = nghttp3_conn_create_stream(conn, &wt_ctrl_stream, session_id); + if (rv != 0) { + return rv; + } + + wt_ctrl_stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_INITIAL; + } + + if (!wt_ctrl_stream->wt.session) { + rv = nghttp3_conn_open_wt_session(conn, wt_ctrl_stream); + if (rv != 0) { + return rv; + } + } + + wt_session = wt_ctrl_stream->wt.session; + + assert(wt_session); + + nghttp3_wt_session_add_stream(wt_session, stream); + + if (wt_ctrl_stream->wt.session->flags & NGHTTP3_WT_SESSION_FLAG_CONFIRMED) { + return conn_call_wt_data_stream_open(conn, stream); + } + + return 0; +} + +int nghttp3_conn_on_wt_session_confirmed(nghttp3_conn *conn, + nghttp3_stream *wt_ctrl_stream, + nghttp3_tstamp ts) { + nghttp3_stream *stream; + nghttp3_wt_session *wt_session = wt_ctrl_stream->wt.session; + int rv; + + wt_session->flags |= NGHTTP3_WT_SESSION_FLAG_CONFIRMED; + + /* TODO Is stream gone during iteration? */ + for (stream = wt_session->head; stream; stream = stream->wt.next) { + stream->flags &= (uint16_t)~NGHTTP3_STREAM_FLAG_WT_SESSION_BLOCKED; + + if (conn_remote_stream(conn, stream->node.id)) { + rv = conn_call_wt_data_stream_open(conn, stream); + if (rv != 0) { + return rv; + } + } + + rv = nghttp3_conn_process_blocked_wt_stream_data(conn, stream, ts); + if (rv != 0) { + return rv; + } + } + + return nghttp3_conn_process_blocked_wt_stream_data(conn, wt_ctrl_stream, ts); +} + +nghttp3_ssize nghttp3_conn_read_wt_stream_uni(nghttp3_conn *conn, + nghttp3_stream *stream, + const uint8_t *src, size_t srclen, + int fin, nghttp3_tstamp ts) { + const uint8_t *p = src, *end = src ? src + srclen : src; + int rv; + nghttp3_stream_read_state *rstate = &stream->rstate; + nghttp3_varint_read_state *rvint = &rstate->rvint; + nghttp3_ssize nread; + size_t nconsumed = 0; + nghttp3_stream *wt_ctrl_stream; + (void)ts; + + if ((stream->flags & NGHTTP3_STREAM_FLAG_SHUT_RD)) { + return (nghttp3_ssize)srclen; + } + + if (srclen == 0) { + goto almost_done; + } + + if (stream->flags & NGHTTP3_STREAM_FLAG_WT_SESSION_BLOCKED) { + if (srclen == 0) { + return 0; + } + + rv = nghttp3_stream_buffer_data(stream, p, srclen); + if (rv != 0) { + return rv; + } + + return 0; + } + + switch (rstate->state) { + case NGHTTP3_WT_STREAM_STATE_SESSION_ID: + assert(end - p > 0); + nread = nghttp3_read_varint(rvint, p, end, fin); + if (nread < 0) { + return NGHTTP3_ERR_H3_FRAME_ERROR; + } + + p += nread; + nconsumed += (size_t)nread; + if (rvint->left) { + /* TODO What should we do if unidirectional stream is closed + before reading Session ID? */ + break; + } + + rstate->left = rvint->acc; + nghttp3_varint_read_state_reset(rvint); + + /* rstate->left is Session ID */ + rv = nghttp3_conn_on_wt_stream(conn, stream, (int64_t)rstate->left); + if (rv != 0) { + if (!nghttp3_err_is_wt(rv)) { + return rv; + } + + stream->rstate.state = NGHTTP3_WT_STREAM_STATE_IGN_REST; + + rv = nghttp3_conn_abort_stream(conn, stream, + nghttp3_err_infer_quic_app_error_code(rv)); + if (rv != 0) { + return rv; + } + + nconsumed += (size_t)(end - p); + + return (nghttp3_ssize)nconsumed; + } + + rstate->state = NGHTTP3_WT_STREAM_STATE_DATA; + + wt_ctrl_stream = + nghttp3_conn_find_stream(conn, stream->wt.session->session_id); + + if (!(wt_ctrl_stream->wt.session->flags & + NGHTTP3_WT_SESSION_FLAG_CONFIRMED)) { + stream->flags |= NGHTTP3_STREAM_FLAG_WT_SESSION_BLOCKED; + + if (p != end) { + rv = nghttp3_stream_buffer_data(stream, p, (size_t)(end - p)); + if (rv != 0) { + return rv; + } + } + + return (nghttp3_ssize)nconsumed; + } + + if (p == end) { + break; + } + + /* Fall through */ + case NGHTTP3_WT_STREAM_STATE_DATA: + rv = conn_call_recv_wt_data(conn, stream, p, (size_t)(end - p)); + if (rv != 0) { + return rv; + } + + break; + case NGHTTP3_WT_STREAM_STATE_IGN_REST: + nconsumed += (size_t)(end - p); + + return (nghttp3_ssize)nconsumed; + } + +almost_done: + if (fin) { + rv = conn_call_end_stream(conn, stream); + if (rv != 0) { + return rv; + } + } + + return (nghttp3_ssize)nconsumed; +} + +int nghttp3_conn_process_blocked_wt_stream_data(nghttp3_conn *conn, + nghttp3_stream *stream, + nghttp3_tstamp ts) { + nghttp3_buf *buf; + nghttp3_ssize nconsumed; + size_t nproc; + int rv; + size_t len; + + for (;;) { + len = nghttp3_ringbuf_len(&stream->inq); + if (len == 0) { + break; + } + + buf = nghttp3_ringbuf_get(&stream->inq, 0); + + if (nghttp3_stream_uni(stream->node.id)) { + nconsumed = nghttp3_conn_read_wt_stream_uni( + conn, stream, buf->pos, nghttp3_buf_len(buf), + len == 1 && (stream->flags & NGHTTP3_STREAM_FLAG_READ_EOF), ts); + } else { + nconsumed = nghttp3_conn_read_bidi( + conn, &nproc, stream, buf->pos, nghttp3_buf_len(buf), + len == 1 && (stream->flags & NGHTTP3_STREAM_FLAG_READ_EOF), ts); + } + + if (nconsumed < 0) { + return (int)nconsumed; + } + + rv = conn_call_deferred_consume(conn, stream, (size_t)nconsumed); + if (rv != 0) { + return rv; + } + + nghttp3_buf_free(buf, stream->mem); + nghttp3_ringbuf_pop_front(&stream->inq); + } + + return 0; +} + +int nghttp3_conn_shutdown_wt_session(nghttp3_conn *conn, + nghttp3_stream *wt_ctrl_stream, + uint64_t error_code) { + int rv; + + rv = + nghttp3_conn_shutdown_all_wt_data_streams(conn, wt_ctrl_stream, error_code); + if (rv != 0) { + return rv; + } + + wt_ctrl_stream->rstate.state = NGHTTP3_REQ_STREAM_STATE_IGN_REST; + + return nghttp3_conn_abort_stream(conn, wt_ctrl_stream, error_code); +} + +int nghttp3_conn_shutdown_all_wt_data_streams(nghttp3_conn *conn, + nghttp3_stream *wt_ctrl_stream, + uint64_t error_code) { + nghttp3_wt_session *wt_session = wt_ctrl_stream->wt.session; + nghttp3_stream *stream; + int rv; + + for (stream = wt_session->head; stream; stream = stream->wt.next) { + rv = nghttp3_conn_shutdown_wt_data_stream(conn, stream, error_code); + if (rv != 0) { + return rv; + } + } + + return 0; +} + +int nghttp3_conn_shutdown_wt_data_stream(nghttp3_conn *conn, + nghttp3_stream *stream, + uint64_t error_code) { + if (stream->node.id & 0x2) { + stream->rstate.state = NGHTTP3_WT_STREAM_STATE_IGN_REST; + } else { + stream->rstate.state = NGHTTP3_REQ_STREAM_STATE_IGN_REST; + } + + return nghttp3_conn_abort_stream(conn, stream, error_code); +} + +int64_t nghttp3_conn_get_stream_wt_session_id(const nghttp3_conn *conn, + int64_t stream_id) { + const nghttp3_stream *stream = nghttp3_conn_find_stream(conn, stream_id); + + if (!stream || !nghttp3_stream_wt_data(stream)) { + return -1; + } + + return stream->wt.session->session_id; +} + +int nghttp3_conn_read_wt_ctrl_stream(nghttp3_conn *conn, + const nghttp3_stream *stream, + const uint8_t *src, size_t srclen) { + const uint8_t *p, *end; + nghttp3_wt_session *wts = stream->wt.session; + nghttp3_wt_ctrl_read_state *rstate = &wts->rstate; + nghttp3_varint_read_state *rvint = &rstate->rvint; + nghttp3_ssize nread; + nghttp3_exfr_cpsl *cpsl = &rstate->cpsl; + size_t len; + size_t i; + int rv; + + if (srclen == 0) { + return 0; + } + + p = src; + end = src + srclen; + + for (; p != end;) { + switch (rstate->state) { + case NGHTTP3_WT_CTRL_STREAM_STATE_TYPE: + assert(end - p > 0); + nread = nghttp3_read_varint(rvint, p, end, /* fin = */ 0); + + assert(nread > 0); + + p += nread; + if (rvint->left) { + return 0; + } + + rstate->cpsl.hd.type = rvint->acc; + + nghttp3_varint_read_state_reset(rvint); + rstate->state = NGHTTP3_WT_CTRL_STREAM_STATE_LENGTH; + if (p == end) { + return 0; + } + /* Fall through */ + case NGHTTP3_WT_CTRL_STREAM_STATE_LENGTH: + assert(end - p > 0); + nread = nghttp3_read_varint(rvint, p, end, /* fin = */ 0); + assert(nread > 0); + + p += nread; + if (rvint->left) { + return 0; + } + + rstate->left = rvint->acc; + nghttp3_varint_read_state_reset(rvint); + + switch (rstate->cpsl.hd.type) { + case NGHTTP3_EXFR_CPSL_WT_CLOSE_SESSION: + if (rstate->left < sizeof(uint32_t) || + rstate->left > sizeof(uint32_t) + /* largest message size */ 1024) { + /* TODO Find better error code */ + return NGHTTP3_ERR_H3_MESSAGE_ERROR; + } + + rstate->field_left = sizeof(uint32_t); + rstate->state = + NGHTTP3_WT_CTRL_STREAM_STATE_WT_CLOSE_SESSION_ERROR_CODE; + + break; + default: + /* TODO Add rate limit after we implement all supported + capsules. */ + if (rstate->left == 0) { + nghttp3_wt_ctrl_read_state_reset(rstate); + break; + } + + rstate->state = NGHTTP3_WT_CTRL_STREAM_STATE_IGN; + } + + break; + case NGHTTP3_WT_CTRL_STREAM_STATE_WT_CLOSE_SESSION_ERROR_CODE: + len = nghttp3_min(rstate->field_left, (size_t)(end - p)); + + for (i = 0; i < len; ++i) { + cpsl->wt_close_session.error_code <<= 8; + cpsl->wt_close_session.error_code += *p++; + } + + rstate->left -= len; + rstate->field_left -= len; + if (rstate->field_left) { + break; + } + + wts->rx.error_code = cpsl->wt_close_session.error_code; + + if (rstate->left == 0) { + if (conn->callbacks.recv_wt_close_session) { + rv = conn->callbacks.recv_wt_close_session( + conn, wts->session_id, wts->rx.error_code, NULL, 0, conn->user_data, + stream->user_data); + if (rv != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + } + + nghttp3_wt_ctrl_read_state_reset(rstate); + + return NGHTTP3_ERR_WT_SESSION_GONE; + } + + rstate->state = NGHTTP3_WT_CTRL_STREAM_STATE_WT_CLOSE_SESSION_ERROR_MSG; + + wts->rx.error_msg.base = + nghttp3_mem_malloc(conn->mem, (size_t)rstate->left); + if (!wts->rx.error_msg.base) { + return NGHTTP3_ERR_NOMEM; + } + + if (p == end) { + return 0; + } + + /* Fall through */ + case NGHTTP3_WT_CTRL_STREAM_STATE_WT_CLOSE_SESSION_ERROR_MSG: + len = (size_t)nghttp3_min(rstate->left, (uint64_t)(end - p)); + + memcpy(wts->rx.error_msg.base + wts->rx.error_msg.len, p, len); + wts->rx.error_msg.len += len; + + p += len; + rstate->left -= len; + + if (rstate->left) { + break; + } + + if (conn->callbacks.recv_wt_close_session) { + rv = conn->callbacks.recv_wt_close_session( + conn, wts->session_id, wts->rx.error_code, wts->rx.error_msg.base, + wts->rx.error_msg.len, conn->user_data, stream->user_data); + if (rv != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + } + + nghttp3_wt_ctrl_read_state_reset(rstate); + + return NGHTTP3_ERR_WT_SESSION_GONE; + case NGHTTP3_WT_CTRL_STREAM_STATE_IGN: + len = (size_t)nghttp3_min(rstate->left, (uint64_t)(end - p)); + p += len; + rstate->left -= len; + + if (rstate->left) { + return 0; + } + + nghttp3_wt_ctrl_read_state_reset(rstate); + + break; + } + } + + return 0; } diff --git a/deps/ngtcp2/nghttp3/lib/nghttp3_conn.h b/deps/ngtcp2/nghttp3/lib/nghttp3_conn.h index 6841b1c343a3..101def930b75 100644 --- a/deps/ngtcp2/nghttp3/lib/nghttp3_conn.h +++ b/deps/ngtcp2/nghttp3/lib/nghttp3_conn.h @@ -202,8 +202,8 @@ nghttp3_ssize nghttp3_conn_read_qpack_decoder(nghttp3_conn *conn, const uint8_t *src, size_t srclen); -int nghttp3_conn_on_data(nghttp3_conn *conn, nghttp3_stream *stream, - const uint8_t *data, size_t datalen); +nghttp3_ssize nghttp3_conn_on_data(nghttp3_conn *conn, nghttp3_stream *stream, + const uint8_t *data, size_t datalen); int nghttp3_conn_on_priority_update(nghttp3_conn *conn, const nghttp3_frame_priority_update *fr); @@ -216,6 +216,8 @@ nghttp3_ssize nghttp3_conn_on_headers(nghttp3_conn *conn, int nghttp3_conn_on_settings_entry_received(nghttp3_conn *conn, const nghttp3_frame_settings *fr); +int nghttp3_conn_on_settings_received(nghttp3_conn *conn); + int nghttp3_conn_qpack_blocked_streams_push(nghttp3_conn *conn, nghttp3_stream *stream); @@ -233,10 +235,47 @@ void nghttp3_conn_unschedule_stream(nghttp3_conn *conn, nghttp3_stream *stream); int nghttp3_conn_reject_stream(nghttp3_conn *conn, nghttp3_stream *stream); +int nghttp3_conn_abort_stream(nghttp3_conn *conn, nghttp3_stream *stream, + uint64_t error_code); + /* * nghttp3_conn_get_next_tx_stream returns next stream to send. It * returns NULL if there is no such stream. */ nghttp3_stream *nghttp3_conn_get_next_tx_stream(nghttp3_conn *conn); +int nghttp3_conn_open_wt_session(nghttp3_conn *conn, nghttp3_stream *stream); + +int nghttp3_conn_on_wt_stream(nghttp3_conn *conn, nghttp3_stream *stream, + int64_t session_id); + +nghttp3_ssize nghttp3_conn_read_wt_stream_uni(nghttp3_conn *conn, + nghttp3_stream *stream, + const uint8_t *src, size_t srclen, + int fin, nghttp3_tstamp ts); + +int nghttp3_conn_on_wt_session_confirmed(nghttp3_conn *conn, + nghttp3_stream *wt_ctrl_stream, + nghttp3_tstamp ts); + +int nghttp3_conn_process_blocked_wt_stream_data(nghttp3_conn *conn, + nghttp3_stream *stream, + nghttp3_tstamp ts); + +int nghttp3_conn_shutdown_wt_session(nghttp3_conn *conn, + nghttp3_stream *wt_ctrl_stream, + uint64_t error_code); + +int nghttp3_conn_shutdown_all_wt_data_streams(nghttp3_conn *conn, + nghttp3_stream *wt_ctrl_stream, + uint64_t error_code); + +int nghttp3_conn_shutdown_wt_data_stream(nghttp3_conn *conn, + nghttp3_stream *stream, + uint64_t error_code); + +int nghttp3_conn_read_wt_ctrl_stream(nghttp3_conn *conn, + const nghttp3_stream *stream, + const uint8_t *src, size_t srclen); + #endif /* !defined(NGHTTP3_CONN_H) */ diff --git a/deps/ngtcp2/nghttp3/lib/nghttp3_conv.c b/deps/ngtcp2/nghttp3/lib/nghttp3_conv.c index 031ac78d815f..a90e1d25b70e 100644 --- a/deps/ngtcp2/nghttp3/lib/nghttp3_conv.c +++ b/deps/ngtcp2/nghttp3/lib/nghttp3_conv.c @@ -79,6 +79,12 @@ const uint8_t *nghttp3_get_varint(int64_t *dest, const uint8_t *p) { return p; } +const uint8_t *nghttp3_get_uint32be(uint32_t *dest, const uint8_t *p) { + memcpy(dest, p, sizeof(*dest)); + *dest = ntohl(*dest); + return p + sizeof(*dest); +} + uint8_t *nghttp3_put_uint64be(uint8_t *p, uint64_t n) { n = nghttp3_htonl64(n); return nghttp3_cpymem(p, (const uint8_t *)&n, sizeof(n)); diff --git a/deps/ngtcp2/nghttp3/lib/nghttp3_conv.h b/deps/ngtcp2/nghttp3/lib/nghttp3_conv.h index bd1c518fa663..bd6f29a55db5 100644 --- a/deps/ngtcp2/nghttp3/lib/nghttp3_conv.h +++ b/deps/ngtcp2/nghttp3/lib/nghttp3_conv.h @@ -92,6 +92,13 @@ # define ntohs(N) _byteswap_ushort(N) #endif /* defined(WIN32) */ +/* + * nghttp3_get_uint32be reads 4 bytes from |p| as 32 bits unsigned + * integer encoded as network byte order, and stores it in the buffer + * pointed by |dest| in host byte order. It returns |p| + 4. + */ +const uint8_t *nghttp3_get_uint32be(uint32_t *dest, const uint8_t *p); + /* * nghttp3_put_uint64be writes |n| in host byte order in |p| in * network byte order. It returns the one beyond of the last written diff --git a/deps/ngtcp2/nghttp3/lib/nghttp3_err.c b/deps/ngtcp2/nghttp3/lib/nghttp3_err.c index eff6ea6a63a2..c770fd4cbd1f 100644 --- a/deps/ngtcp2/nghttp3/lib/nghttp3_err.c +++ b/deps/ngtcp2/nghttp3/lib/nghttp3_err.c @@ -76,6 +76,10 @@ const char *nghttp3_strerror(int liberr) { return "ERR_H3_STREAM_CREATION_ERROR"; case NGHTTP3_ERR_H3_EXCESSIVE_LOAD: return "ERR_H3_EXCESSIVE_LOAD"; + case NGHTTP3_ERR_WT_SESSION_GONE: + return "ERR_WT_SESSION_GONE"; + case NGHTTP3_ERR_WT_BUFFERED_STREAM_REJECTED: + return "ERR_WT_BUFFERED_STREAM_REJECTED"; case NGHTTP3_ERR_NOMEM: return "ERR_NOMEM"; case NGHTTP3_ERR_CALLBACK_FAILURE: @@ -122,10 +126,20 @@ uint64_t nghttp3_err_infer_quic_app_error_code(int liberr) { return NGHTTP3_H3_EXCESSIVE_LOAD; case NGHTTP3_ERR_MALFORMED_HTTP_HEADER: case NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING: + case NGHTTP3_ERR_H3_MESSAGE_ERROR: return NGHTTP3_H3_MESSAGE_ERROR; + case NGHTTP3_ERR_WT_SESSION_GONE: + return NGHTTP3_WT_SESSION_GONE; + case NGHTTP3_ERR_WT_BUFFERED_STREAM_REJECTED: + return NGHTTP3_WT_BUFFERED_STREAM_REJECTED; default: return NGHTTP3_H3_GENERAL_PROTOCOL_ERROR; } } int nghttp3_err_is_fatal(int liberr) { return liberr < NGHTTP3_ERR_FATAL; } + +int nghttp3_err_is_wt(int liberr) { + return liberr == NGHTTP3_ERR_WT_SESSION_GONE || + liberr == NGHTTP3_ERR_WT_BUFFERED_STREAM_REJECTED; +} diff --git a/deps/ngtcp2/nghttp3/lib/nghttp3_err.h b/deps/ngtcp2/nghttp3/lib/nghttp3_err.h index 6f8205cc17ce..8dbc234e0aec 100644 --- a/deps/ngtcp2/nghttp3/lib/nghttp3_err.h +++ b/deps/ngtcp2/nghttp3/lib/nghttp3_err.h @@ -31,4 +31,10 @@ #include +/* + * nghttp3_err_wt returns nonzero if |liberr| is one of WebTransport + * errors. + */ +int nghttp3_err_is_wt(int liberr); + #endif /* !defined(NGHTTP3_ERR_H) */ diff --git a/deps/ngtcp2/nghttp3/lib/nghttp3_frame.c b/deps/ngtcp2/nghttp3/lib/nghttp3_frame.c index 2efba7472c25..1742e0756fc6 100644 --- a/deps/ngtcp2/nghttp3/lib/nghttp3_frame.c +++ b/deps/ngtcp2/nghttp3/lib/nghttp3_frame.c @@ -133,6 +133,44 @@ size_t nghttp3_frame_write_origin_len(uint64_t *ppayloadlen, payloadlen; } +uint8_t *nghttp3_frame_write_wt_stream(uint8_t *p, + const nghttp3_exfr_wt_stream *fr) { + p = nghttp3_put_uvarint(p, fr->type); + return nghttp3_put_uvarint(p, (uint64_t)fr->session_id); +} + +size_t nghttp3_frame_write_wt_stream_len(const nghttp3_exfr_wt_stream *fr) { + return nghttp3_put_uvarintlen(fr->type) + + nghttp3_put_uvarintlen((uint64_t)fr->session_id); +} + +uint8_t *nghttp3_frame_write_cpsl_wt_close_session( + uint8_t *p, const nghttp3_exfr_cpsl_wt_close_session *fr, + uint64_t payloadlen) { + p = nghttp3_frame_write_hd(p, NGHTTP3_FRAME_DATA, payloadlen); + p = nghttp3_frame_write_hd(p, fr->type, + sizeof(fr->error_code) + fr->error_msg.len); + p = nghttp3_put_uint32be(p, fr->error_code); + + if (fr->error_msg.len) { + p = nghttp3_cpymem(p, fr->error_msg.base, fr->error_msg.len); + } + + return p; +} + +size_t nghttp3_frame_write_cpsl_wt_close_session_len( + uint64_t *ppayloadlen, const nghttp3_exfr_cpsl_wt_close_session *fr) { + size_t cpsl_payloadlen = sizeof(fr->error_code) + fr->error_msg.len; + size_t payloadlen = nghttp3_put_uvarintlen(fr->type) + + nghttp3_put_uvarintlen(cpsl_payloadlen) + cpsl_payloadlen; + + *ppayloadlen = payloadlen; + + return nghttp3_put_uvarintlen(NGHTTP3_FRAME_DATA) + + nghttp3_put_uvarintlen(payloadlen) + payloadlen; +} + int nghttp3_nva_copy(nghttp3_nv **pnva, const nghttp3_nv *nva, size_t nvlen, const nghttp3_mem *mem) { size_t i; diff --git a/deps/ngtcp2/nghttp3/lib/nghttp3_frame.h b/deps/ngtcp2/nghttp3/lib/nghttp3_frame.h index 7806cadbcf5f..77b4d37b8fb2 100644 --- a/deps/ngtcp2/nghttp3/lib/nghttp3_frame.h +++ b/deps/ngtcp2/nghttp3/lib/nghttp3_frame.h @@ -46,6 +46,24 @@ #define NGHTTP3_FRAME_PRIORITY_UPDATE_PUSH_ID 0x0F0701U /* ORIGIN: https://datatracker.ietf.org/doc/html/rfc9412 */ #define NGHTTP3_FRAME_ORIGIN 0x0CU +/* WebTransport extended frame type */ +#define NGHTTP3_FRAME_EX_WT 0x4000000000000001ULL +/* WT_STREAM: + https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3-14 */ +#define NGHTTP3_EXFR_WT_STREAM_BIDI 0x41U +#define NGHTTP3_EXFR_WT_STREAM_UNI 0x54U +#define NGHTTP3_EXFR_WT_STREAM_DATA 0x00U + +/* HTTP Capsule extended frame type */ +#define NGHTTP3_FRAME_EX_CPSL 0x4000000000000002ULL +#define NGHTTP3_EXFR_CPSL_WT_CLOSE_SESSION 0x2843U +#define NGHTTP3_EXFR_CPSL_WT_DRAIN_SESSION 0x78AEU +#define NGHTTP3_EXFR_CPSL_WT_MAX_STREAMS_BIDI 0x190B4D3FU +#define NGHTTP3_EXFR_CPSL_WT_MAX_STREAMS_UNI 0x190B4D40U +#define NGHTTP3_EXFR_CPSL_WT_STREAMS_BLOCKED_BIDI 0x190B4D43U +#define NGHTTP3_EXFR_CPSL_WT_STREAMS_BLOCKED_UNI 0x190B4D44U +#define NGHTTP3_EXFR_CPSL_WT_MAX_DATA 0x190B4D3DU +#define NGHTTP3_EXFR_CPSL_WT_DATA_BLOCKED 0x190B4D41U /* Frame types that are reserved for HTTP/2, and must not be used in HTTP/3. */ @@ -76,6 +94,14 @@ typedef struct nghttp3_frame_headers { #define NGHTTP3_SETTINGS_ID_QPACK_BLOCKED_STREAMS 0x07U #define NGHTTP3_SETTINGS_ID_ENABLE_CONNECT_PROTOCOL 0x08U #define NGHTTP3_SETTINGS_ID_H3_DATAGRAM 0x33U +/* https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3-15 */ +#define NGHTTP3_SETTINGS_ID_WT_ENABLED 0x2C7CF000U +/* https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3-14 */ +#define NGHTTP3_SETTINGS_ID_WT_MAX_SESSIONS 0x14E9CD29U +/* https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3-07 */ +#define NGHTTP3_SETTINGS_ID_WT_MAX_SESSIONS_DRAFT7 0xC671706AU +/* https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3-02 */ +#define NGHTTP3_SETTINGS_ID_ENABLE_WEBTRANSPORT_DRAFT2 0x2B603742U #define NGHTTP3_H2_SETTINGS_ID_ENABLE_PUSH 0x2U #define NGHTTP3_H2_SETTINGS_ID_MAX_CONCURRENT_STREAMS 0x3U @@ -132,6 +158,42 @@ typedef struct nghttp3_frame_origin { nghttp3_vec origin_list; } nghttp3_frame_origin; +typedef struct nghttp3_exfr_hd { + uint64_t type; +} nghttp3_exfr_hd; + +typedef struct nghttp3_exfr_wt_stream { + uint64_t type; + int64_t session_id; + nghttp3_data_reader dr; +} nghttp3_exfr_wt_stream; + +typedef union nghttp3_exfr_wt { + nghttp3_exfr_hd hd; + nghttp3_exfr_wt_stream wt_stream; +} nghttp3_exfr_wt; + +typedef struct nghttp3_frame_ex_wt { + uint64_t type; + nghttp3_exfr_wt fr; +} nghttp3_frame_ex_wt; + +typedef struct nghttp3_exfr_cpsl_wt_close_session { + uint64_t type; + nghttp3_vec error_msg; + uint32_t error_code; +} nghttp3_exfr_cpsl_wt_close_session; + +typedef union nghttp3_exfr_cpsl { + nghttp3_exfr_hd hd; + nghttp3_exfr_cpsl_wt_close_session wt_close_session; +} nghttp3_exfr_cpsl; + +typedef struct nghttp3_frame_ex_cpsl { + uint64_t type; + nghttp3_exfr_cpsl fr; +} nghttp3_frame_ex_cpsl; + typedef union nghttp3_frame { nghttp3_frame_hd hd; nghttp3_frame_data data; @@ -140,6 +202,8 @@ typedef union nghttp3_frame { nghttp3_frame_goaway goaway; nghttp3_frame_priority_update priority_update; nghttp3_frame_origin origin; + nghttp3_frame_ex_wt wt; + nghttp3_frame_ex_cpsl cpsl; } nghttp3_frame; /* @@ -233,6 +297,18 @@ uint8_t *nghttp3_frame_write_origin(uint8_t *dest, size_t nghttp3_frame_write_origin_len(uint64_t *ppayloadlen, const nghttp3_frame_origin *fr); +uint8_t *nghttp3_frame_write_wt_stream(uint8_t *dest, + const nghttp3_exfr_wt_stream *fr); + +size_t nghttp3_frame_write_wt_stream_len(const nghttp3_exfr_wt_stream *fr); + +uint8_t *nghttp3_frame_write_cpsl_wt_close_session( + uint8_t *dest, const nghttp3_exfr_cpsl_wt_close_session *fr, + uint64_t payloadlen); + +size_t nghttp3_frame_write_cpsl_wt_close_session_len( + uint64_t *ppayloadlen, const nghttp3_exfr_cpsl_wt_close_session *fr); + /* * nghttp3_nva_copy copies name/value pairs from |nva|, which contains * |nvlen| pairs, to |*nva_ptr|, which is dynamically allocated so diff --git a/deps/ngtcp2/nghttp3/lib/nghttp3_http.c b/deps/ngtcp2/nghttp3/lib/nghttp3_http.c index 4194a404b33f..56dc0426aa73 100644 --- a/deps/ngtcp2/nghttp3/lib/nghttp3_http.c +++ b/deps/ngtcp2/nghttp3/lib/nghttp3_http.c @@ -384,6 +384,12 @@ static int http_request_on_header(nghttp3_http_state *http, !check_pseudo_header(http, nv, NGHTTP3_HTTP_FLAG__PROTOCOL)) { return NGHTTP3_ERR_MALFORMED_HTTP_HEADER; } + + if (lstrieq("webtransport-h3", nv->value->base, nv->value->len) || + lstrieq("webtransport", nv->value->base, nv->value->len)) { + http->flags |= NGHTTP3_HTTP_FLAG_WEBTRANSPORT; + } + break; case NGHTTP3_QPACK_TOKEN_HOST: if (!check_authority(nv->value->base, nv->value->len)) { diff --git a/deps/ngtcp2/nghttp3/lib/nghttp3_http.h b/deps/ngtcp2/nghttp3/lib/nghttp3_http.h index 2bdf3110027c..ec32414c1d1d 100644 --- a/deps/ngtcp2/nghttp3/lib/nghttp3_http.h +++ b/deps/ngtcp2/nghttp3/lib/nghttp3_http.h @@ -82,6 +82,8 @@ typedef struct nghttp3_http_state nghttp3_http_state; while parsing priority header field. */ #define NGHTTP3_HTTP_FLAG_BAD_PRIORITY 0x010000U +#define NGHTTP3_HTTP_FLAG_WEBTRANSPORT 0x020000U + /* * This function is called when HTTP header field |nv| received for * |http|. This function will validate |nv| against the current state diff --git a/deps/ngtcp2/nghttp3/lib/nghttp3_map.c b/deps/ngtcp2/nghttp3/lib/nghttp3_map.c index 7858d4cc3eb1..0a34be7bba13 100644 --- a/deps/ngtcp2/nghttp3/lib/nghttp3_map.c +++ b/deps/ngtcp2/nghttp3/lib/nghttp3_map.c @@ -275,7 +275,12 @@ int nghttp3_map_insert(nghttp3_map *map, nghttp3_map_key_type key, void *data) { return 0; } - return map_resize(map, map->hashbits + 1); + rv = map_resize(map, map->hashbits + 1); + if (rv != 0) { + nghttp3_map_remove(map, key); + } + + return rv; } void *nghttp3_map_find(const nghttp3_map *map, nghttp3_map_key_type key) { diff --git a/deps/ngtcp2/nghttp3/lib/nghttp3_qpack.c b/deps/ngtcp2/nghttp3/lib/nghttp3_qpack.c index 8e950a201d89..7b244a4d1b6e 100644 --- a/deps/ngtcp2/nghttp3/lib/nghttp3_qpack.c +++ b/deps/ngtcp2/nghttp3/lib/nghttp3_qpack.c @@ -2895,6 +2895,12 @@ nghttp3_ssize nghttp3_qpack_decoder_read_encoder(nghttp3_qpack_decoder *decoder, if (decoder->opcode == NGHTTP3_QPACK_ES_OPCODE_SET_DTABLE_CAP) { DEBUGF("qpack::decode: Set dtable capacity to %" PRIu64 "\n", decoder->rstate.left); +#if SIZE_MAX < UINT64_MAX + if (decoder->rstate.left > SIZE_MAX) { + return NGHTTP3_ERR_QPACK_ENCODER_STREAM_ERROR; + } +#endif /* SIZE_MAX < UINT64_MAX */ + rv = nghttp3_qpack_decoder_set_max_dtable_capacity( decoder, (size_t)decoder->rstate.left); if (rv != 0) { diff --git a/deps/ngtcp2/nghttp3/lib/nghttp3_stream.c b/deps/ngtcp2/nghttp3/lib/nghttp3_stream.c index 09298006f4f6..6fc40fe9d3b1 100644 --- a/deps/ngtcp2/nghttp3/lib/nghttp3_stream.c +++ b/deps/ngtcp2/nghttp3/lib/nghttp3_stream.c @@ -36,6 +36,7 @@ #include "nghttp3_http.h" #include "nghttp3_vec.h" #include "nghttp3_unreachable.h" +#include "nghttp3_wt.h" /* NGHTTP3_STREAM_MAX_COPY_THRES is the maximum size of buffer which makes a copy to outq. */ @@ -167,6 +168,10 @@ void nghttp3_stream_del(nghttp3_stream *stream) { delete_frq(&stream->frq, stream->mem); nghttp3_tnode_free(&stream->node); + if (nghttp3_stream_wt_ctrl(stream)) { + nghttp3_wt_session_del(stream->wt.session, stream->mem); + } + nghttp3_objalloc_stream_release(stream->stream_objalloc, stream); } @@ -294,6 +299,47 @@ int nghttp3_stream_fill_outq(nghttp3_stream *stream) { return rv; } + break; + case NGHTTP3_FRAME_EX_WT: + switch (fr->wt.fr.hd.type) { + case NGHTTP3_EXFR_WT_STREAM_BIDI: + case NGHTTP3_EXFR_WT_STREAM_UNI: + rv = nghttp3_stream_write_wt_stream(stream, &fr->wt.fr.wt_stream); + if (rv != 0) { + return rv; + } + + fr->wt.fr.wt_stream.type = NGHTTP3_EXFR_WT_STREAM_DATA; + + /* fall through */ + case NGHTTP3_EXFR_WT_STREAM_DATA: + rv = nghttp3_stream_write_wt_stream_data(stream, &data_eof, + &fr->wt.fr.wt_stream); + if (rv != 0) { + return rv; + } + + if ((stream->flags & NGHTTP3_STREAM_FLAG_READ_DATA_BLOCKED) || + !data_eof) { + return 0; + } + + break; + } + + break; + case NGHTTP3_FRAME_EX_CPSL: + switch (fr->cpsl.fr.hd.type) { + case NGHTTP3_EXFR_CPSL_WT_CLOSE_SESSION: + rv = nghttp3_stream_write_cpsl_wt_close_session( + stream, &fr->cpsl.fr.wt_close_session); + if (rv != 0) { + return rv; + } + + break; + } + break; default: /* TODO Not implemented */ @@ -372,6 +418,31 @@ int nghttp3_stream_write_settings(nghttp3_stream *stream, ++fr.niv; } + if (local_settings->wt_enabled) { + /* For client, only draft version sends SETTINGS_WT_ENABLED. */ + ents[fr.niv++] = (nghttp3_settings_entry){ + .id = NGHTTP3_SETTINGS_ID_WT_ENABLED, + .value = 1, + }; + + /* compat for pre draft-15 */ + ents[fr.niv++] = (nghttp3_settings_entry){ + .id = NGHTTP3_SETTINGS_ID_WT_MAX_SESSIONS, + .value = 1, + }; + + ents[fr.niv++] = (nghttp3_settings_entry){ + .id = NGHTTP3_SETTINGS_ID_WT_MAX_SESSIONS_DRAFT7, + .value = 1, + }; + + /* compat for ancient draft */ + ents[fr.niv++] = (nghttp3_settings_entry){ + .id = NGHTTP3_SETTINGS_ID_ENABLE_WEBTRANSPORT_DRAFT2, + .value = 1, + }; + } + len = nghttp3_frame_write_settings_len(&payloadlen, &fr); rv = nghttp3_stream_ensure_chunk(stream, len); @@ -478,6 +549,150 @@ int nghttp3_stream_write_origin(nghttp3_stream *stream, return nghttp3_stream_outq_add(stream, &tbuf); } +int nghttp3_stream_write_wt_stream(nghttp3_stream *stream, + const nghttp3_exfr_wt_stream *fr) { + size_t len; + int rv; + nghttp3_buf *chunk; + nghttp3_typed_buf tbuf; + + len = nghttp3_frame_write_wt_stream_len(fr); + + rv = nghttp3_stream_ensure_chunk(stream, len); + if (rv != 0) { + return rv; + } + + chunk = nghttp3_stream_get_chunk(stream); + nghttp3_typed_buf_shared_init(&tbuf, chunk); + + chunk->last = nghttp3_frame_write_wt_stream(chunk->last, fr); + + tbuf.buf.last = chunk->last; + + return nghttp3_stream_outq_add(stream, &tbuf); +} + +int nghttp3_stream_write_wt_stream_data(nghttp3_stream *stream, int *peof, + const nghttp3_exfr_wt_stream *fr) { + int rv; + nghttp3_typed_buf tbuf; + nghttp3_buf buf; + nghttp3_read_data_callback read_data = fr->dr.read_data; + nghttp3_conn *conn = stream->conn; + uint64_t datalen; + uint32_t flags = 0; + nghttp3_vec vec[8]; + nghttp3_vec *v; + nghttp3_ssize sveccnt; + size_t i; + + assert(!(stream->flags & NGHTTP3_STREAM_FLAG_READ_DATA_BLOCKED)); + assert(read_data); + assert(conn); + + *peof = 0; + + sveccnt = read_data(conn, stream->node.id, vec, nghttp3_arraylen(vec), &flags, + conn->user_data, stream->user_data); + if (sveccnt < 0) { + if (sveccnt == NGHTTP3_ERR_WOULDBLOCK) { + stream->flags |= NGHTTP3_STREAM_FLAG_READ_DATA_BLOCKED; + return 0; + } + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + + rv = nghttp3_vec_len_uvarint(&datalen, vec, (size_t)sveccnt); + if (rv != 0) { + return NGHTTP3_ERR_STREAM_DATA_OVERFLOW; + } + + assert(datalen || flags & NGHTTP3_DATA_FLAG_EOF); + + if (flags & NGHTTP3_DATA_FLAG_EOF) { + *peof = 1; + + stream->flags |= NGHTTP3_STREAM_FLAG_WRITE_END_STREAM; + if (datalen == 0) { + if (nghttp3_stream_outq_write_done(stream)) { + /* If this is the last data and its is 0 length, we don't need + send data. We rely on the non-emptiness of outq to + schedule stream, so add empty tbuf to outq to just send + fin. */ + nghttp3_buf_init(&buf); + nghttp3_typed_buf_init(&tbuf, &buf, NGHTTP3_BUF_TYPE_PRIVATE); + return nghttp3_stream_outq_add(stream, &tbuf); + } + + /* We are going to send data, but nothing to send this time. */ + + return 0; + } + } + + assert(datalen); + + for (i = 0; i < (size_t)sveccnt; ++i) { + v = &vec[i]; + if (v->len == 0) { + continue; + } + nghttp3_buf_wrap_init(&buf, v->base, v->len); + buf.last = buf.end; + nghttp3_typed_buf_init(&tbuf, &buf, NGHTTP3_BUF_TYPE_ALIEN); + rv = nghttp3_stream_outq_add(stream, &tbuf); + if (rv != 0) { + return rv; + } + } + + return 0; +} + +int nghttp3_stream_write_cpsl_wt_close_session( + nghttp3_stream *stream, const nghttp3_exfr_cpsl_wt_close_session *fr) { + int rv; + nghttp3_buf *chunk; + nghttp3_buf buf; + nghttp3_typed_buf tbuf; + size_t cpsl_payloadlen = sizeof(fr->error_code) + fr->error_msg.len; + size_t fr_hdlen = nghttp3_frame_write_hd_len(fr->type, cpsl_payloadlen); + uint64_t payloadlen = fr_hdlen + cpsl_payloadlen; + + rv = nghttp3_stream_ensure_chunk( + stream, nghttp3_frame_write_hd_len(NGHTTP3_FRAME_DATA, payloadlen) + + fr_hdlen + sizeof(fr->error_code)); + if (rv != 0) { + return rv; + } + + chunk = nghttp3_stream_get_chunk(stream); + nghttp3_typed_buf_shared_init(&tbuf, chunk); + + chunk->last = + nghttp3_frame_write_hd(chunk->last, NGHTTP3_FRAME_DATA, payloadlen); + chunk->last = nghttp3_frame_write_hd(chunk->last, fr->type, cpsl_payloadlen); + chunk->last = nghttp3_put_uint32be(chunk->last, fr->error_code); + + tbuf.buf.last = chunk->last; + + rv = nghttp3_stream_outq_add(stream, &tbuf); + if (rv != 0) { + return rv; + } + + if (fr->error_msg.len == 0) { + return 0; + } + + nghttp3_buf_wrap_init(&buf, fr->error_msg.base, fr->error_msg.len); + buf.last = buf.end; + nghttp3_typed_buf_init(&tbuf, &buf, NGHTTP3_BUF_TYPE_ALIEN_NO_ACK); + + return nghttp3_stream_outq_add(stream, &tbuf); +} + int nghttp3_stream_write_headers(nghttp3_stream *stream, const nghttp3_frame_headers *fr) { nghttp3_conn *conn = stream->conn; @@ -848,6 +1063,11 @@ int nghttp3_stream_require_schedule(const nghttp3_stream *stream) { !(stream->flags & NGHTTP3_STREAM_FLAG_READ_DATA_BLOCKED)); } +int nghttp3_stream_schedulable(const nghttp3_stream *stream) { + return !nghttp3_stream_uni(stream->node.id) || + stream->type == NGHTTP3_STREAM_TYPE_WT_STREAM; +} + size_t nghttp3_stream_writev(nghttp3_stream *stream, int *pfin, nghttp3_vec *vec, size_t veccnt) { nghttp3_ringbuf *outq = &stream->outq; @@ -1235,8 +1455,25 @@ int nghttp3_stream_empty_headers_allowed(const nghttp3_stream *stream) { } } +int nghttp3_stream_critical(const nghttp3_stream *stream) { + return nghttp3_stream_uni(stream->node.id) && + (stream->type == NGHTTP3_STREAM_TYPE_CONTROL || + stream->type == NGHTTP3_STREAM_TYPE_QPACK_ENCODER || + stream->type == NGHTTP3_STREAM_TYPE_QPACK_DECODER); +} + int nghttp3_stream_uni(int64_t stream_id) { return (stream_id & 0x2) != 0; } +int nghttp3_stream_wt_ctrl(const nghttp3_stream *stream) { + return stream->wt.session && + stream->wt.session->session_id == stream->node.id; +} + +int nghttp3_stream_wt_data(const nghttp3_stream *stream) { + return stream->wt.session && + stream->wt.session->session_id != stream->node.id; +} + int nghttp3_client_stream_bidi(int64_t stream_id) { return (stream_id & 0x3) == 0; } @@ -1248,3 +1485,7 @@ int nghttp3_client_stream_uni(int64_t stream_id) { int nghttp3_server_stream_uni(int64_t stream_id) { return (stream_id & 0x3) == 0x3; } + +int nghttp3_server_stream_bidi(int64_t stream_id) { + return (stream_id & 0x3) == 0x1; +} diff --git a/deps/ngtcp2/nghttp3/lib/nghttp3_stream.h b/deps/ngtcp2/nghttp3/lib/nghttp3_stream.h index d313b1c192b9..509a11cf0217 100644 --- a/deps/ngtcp2/nghttp3/lib/nghttp3_stream.h +++ b/deps/ngtcp2/nghttp3/lib/nghttp3_stream.h @@ -42,8 +42,9 @@ #define NGHTTP3_STREAM_MIN_CHUNK_SIZE 256 /* NGHTTP3_MIN_UNSENT_BYTES is the minimum unsent bytes which is large - enough to fill outgoing single QUIC packet. */ -#define NGHTTP3_MIN_UNSENT_BYTES 4096 + enough to fill outgoing single QUIC packet or TLS record in case of + QMux (Cut 2 bytes for QMux record length). */ +#define NGHTTP3_MIN_UNSENT_BYTES 16382 /* NGHTTP3_STREAM_MIN_WRITELEN is the minimum length of write to cause the stream to reschedule. */ @@ -56,6 +57,7 @@ typedef uint64_t nghttp3_stream_type; #define NGHTTP3_STREAM_TYPE_PUSH 0x01U #define NGHTTP3_STREAM_TYPE_QPACK_ENCODER 0x02U #define NGHTTP3_STREAM_TYPE_QPACK_DECODER 0x03U +#define NGHTTP3_STREAM_TYPE_WT_STREAM 0x54U #define NGHTTP3_STREAM_TYPE_UNKNOWN UINT64_MAX typedef enum nghttp3_ctrl_stream_state { @@ -78,10 +80,19 @@ typedef enum nghttp3_req_stream_state { NGHTTP3_REQ_STREAM_STATE_FRAME_LENGTH, NGHTTP3_REQ_STREAM_STATE_DATA, NGHTTP3_REQ_STREAM_STATE_HEADERS, + NGHTTP3_REQ_STREAM_STATE_BEFORE_WT_DATA, + NGHTTP3_REQ_STREAM_STATE_WT_DATA, NGHTTP3_REQ_STREAM_STATE_IGN_FRAME, NGHTTP3_REQ_STREAM_STATE_IGN_REST, } nghttp3_req_stream_state; +/* stream state for WebTransport unidirectional data stream */ +typedef enum nghttp3_wt_stream_state { + NGHTTP3_WT_STREAM_STATE_SESSION_ID, + NGHTTP3_WT_STREAM_STATE_DATA, + NGHTTP3_WT_STREAM_STATE_IGN_REST, +} nghttp3_wt_stream_state; + typedef struct nghttp3_varint_read_state { uint64_t acc; size_t left; @@ -95,6 +106,8 @@ typedef struct nghttp3_stream_read_state { int state; } nghttp3_stream_read_state; +typedef struct nghttp3_wt_session nghttp3_wt_session; + /* NGHTTP3_STREAM_FLAG_NONE indicates that no flag is set. */ #define NGHTTP3_STREAM_FLAG_NONE 0x0000U /* NGHTTP3_STREAM_FLAG_TYPE_IDENTIFIED is set when a unidirectional @@ -128,6 +141,18 @@ typedef struct nghttp3_stream_read_state { /* NGHTTP3_STREAM_FLAG_PRIORITY_UPDATE_RECVED indicates that server received PRIORITY_UPDATE frame for this stream. */ #define NGHTTP3_STREAM_FLAG_PRIORITY_UPDATE_RECVED 0x0800U +/* NGHTTP3_STREAM_FLAG_MAYBE_WT_DATA indicates that the stream may be + WebTransport data stream. */ +#define NGHTTP3_STREAM_FLAG_MAYBE_WT_DATA 0x1000U +/* NGHTTP3_STREAM_FLAG_WT_DATA indicates that the stream is + WebTransport data stream. */ +#define NGHTTP3_STREAM_FLAG_WT_DATA 0x2000U +/* NGHTTP3_STREAM_FLAG_WT_SESSION_BLOCKED indicates that the stream is + blocked because WebTransport session has not been established. */ +#define NGHTTP3_STREAM_FLAG_WT_SESSION_BLOCKED 0x4000U +/* NGHTTP3_STREAM_FLAG_RESP_SUBMITTED indicates that HTTP/3 response + has been submitted via nghttp3_conn_submit_response. */ +#define NGHTTP3_STREAM_FLAG_RESP_SUBMITTED 0x8000U typedef enum nghttp3_stream_http_state { NGHTTP3_HTTP_STATE_NONE, @@ -235,6 +260,12 @@ struct nghttp3_stream { nghttp3_http_state http; } rx; + struct { + nghttp3_wt_session *session; + nghttp3_stream *prev; + nghttp3_stream *next; + } wt; + uint16_t flags; }; @@ -310,6 +341,15 @@ int nghttp3_stream_write_priority_update( int nghttp3_stream_write_origin(nghttp3_stream *stream, const nghttp3_frame_origin *fr); +int nghttp3_stream_write_wt_stream(nghttp3_stream *stream, + const nghttp3_exfr_wt_stream *fr); + +int nghttp3_stream_write_wt_stream_data(nghttp3_stream *stream, int *peof, + const nghttp3_exfr_wt_stream *fr); + +int nghttp3_stream_write_cpsl_wt_close_session( + nghttp3_stream *stream, const nghttp3_exfr_cpsl_wt_close_session *frent); + int nghttp3_stream_ensure_chunk(nghttp3_stream *stream, size_t need); nghttp3_buf *nghttp3_stream_get_chunk(nghttp3_stream *stream); @@ -344,6 +384,8 @@ int nghttp3_stream_is_active(nghttp3_stream *stream); */ int nghttp3_stream_require_schedule(const nghttp3_stream *stream); +int nghttp3_stream_schedulable(const nghttp3_stream *stream); + int nghttp3_stream_buffer_data(nghttp3_stream *stream, const uint8_t *src, size_t srclen); @@ -358,6 +400,12 @@ int nghttp3_stream_transit_rx_http_state(nghttp3_stream *stream, int nghttp3_stream_empty_headers_allowed(const nghttp3_stream *stream); +int nghttp3_stream_wt_ctrl(const nghttp3_stream *stream); + +int nghttp3_stream_wt_data(const nghttp3_stream *stream); + +int nghttp3_stream_critical(const nghttp3_stream *stream); + /* * nghttp3_stream_uni returns nonzero if stream identified by * |stream_id| is unidirectional. @@ -382,4 +430,6 @@ int nghttp3_client_stream_uni(int64_t stream_id); */ int nghttp3_server_stream_uni(int64_t stream_id); +int nghttp3_server_stream_bidi(int64_t stream_id); + #endif /* !defined(NGHTTP3_STREAM_H) */ diff --git a/deps/ngtcp2/nghttp3/lib/nghttp3_wt.c b/deps/ngtcp2/nghttp3/lib/nghttp3_wt.c new file mode 100644 index 000000000000..91331206c5c6 --- /dev/null +++ b/deps/ngtcp2/nghttp3/lib/nghttp3_wt.c @@ -0,0 +1,95 @@ +/* + * nghttp3 + * + * Copyright (c) 2025 nghttp3 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp3_wt.h" + +#include + +#include "nghttp3_mem.h" + +int nghttp3_wt_session_new(nghttp3_wt_session **pwts, int64_t session_id, + const nghttp3_mem *mem) { + *pwts = nghttp3_mem_malloc(mem, sizeof(**pwts)); + if (*pwts == NULL) { + return NGHTTP3_ERR_NOMEM; + } + + **pwts = (nghttp3_wt_session){ + .session_id = session_id, + }; + + return 0; +} + +void nghttp3_wt_session_del(nghttp3_wt_session *wts, const nghttp3_mem *mem) { + if (!wts) { + return; + } + + nghttp3_mem_free(mem, wts->rx.error_msg.base); + nghttp3_mem_free(mem, wts->tx.error_msg.base); + + nghttp3_mem_free(mem, wts); +} + +void nghttp3_wt_session_add_stream(nghttp3_wt_session *wts, + nghttp3_stream *stream) { + assert(!stream->wt.session); + assert(!stream->wt.prev); + assert(!stream->wt.next); + + stream->wt.session = wts; + stream->flags |= NGHTTP3_STREAM_FLAG_WT_DATA; + + if (wts->head) { + stream->wt.next = wts->head; + wts->head->wt.prev = stream; + } + + wts->head = stream; +} + +void nghttp3_wt_session_remove_stream(nghttp3_wt_session *wts, + nghttp3_stream *stream) { + assert(stream->wt.session); + + if (stream->wt.prev) { + stream->wt.prev->wt.next = stream->wt.next; + } + + if (stream->wt.next) { + stream->wt.next->wt.prev = stream->wt.prev; + } + + if (wts->head == stream) { + wts->head = stream->wt.next; + } + + stream->wt.session = NULL; + stream->wt.prev = stream->wt.next = NULL; +} + +void nghttp3_wt_ctrl_read_state_reset(nghttp3_wt_ctrl_read_state *rstate) { + *rstate = (nghttp3_wt_ctrl_read_state){0}; +} diff --git a/deps/ngtcp2/nghttp3/lib/nghttp3_wt.h b/deps/ngtcp2/nghttp3/lib/nghttp3_wt.h new file mode 100644 index 000000000000..eb4b2df13fc8 --- /dev/null +++ b/deps/ngtcp2/nghttp3/lib/nghttp3_wt.h @@ -0,0 +1,86 @@ +/* + * nghttp3 + * + * Copyright (c) 2025 nghttp3 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP3_WT_H +#define NGHTTP3_WT_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* defined(HAVE_CONFIG_H) */ + +#include + +#include "nghttp3_stream.h" + +/* NGHTTP3_WT_SESSION_FLAG_CONFIRMED indicates that WebTransport + session has been established. */ +#define NGHTTP3_WT_SESSION_FLAG_CONFIRMED 0x1 +/* NGHTTP3_WT_SESSION_FLAG_RESP_SUBMITTED indicates that HTTP/3 + response has been submitted via nghttp3_conn_submit_wt_response. */ +#define NGHTTP3_WT_SESSION_FLAG_RESP_SUBMITTED 0x2 + +typedef enum nghttp3_wt_ctrl_stream_state { + NGHTTP3_WT_CTRL_STREAM_STATE_TYPE, + NGHTTP3_WT_CTRL_STREAM_STATE_LENGTH, + NGHTTP3_WT_CTRL_STREAM_STATE_WT_CLOSE_SESSION_ERROR_CODE, + NGHTTP3_WT_CTRL_STREAM_STATE_WT_CLOSE_SESSION_ERROR_MSG, + NGHTTP3_WT_CTRL_STREAM_STATE_IGN, +} nghttp3_wt_ctrl_stream_state; + +typedef struct nghttp3_wt_ctrl_read_state { + nghttp3_varint_read_state rvint; + nghttp3_exfr_cpsl cpsl; + uint64_t left; + size_t field_left; + int state; +} nghttp3_wt_ctrl_read_state; + +typedef struct nghttp3_wt_session { + nghttp3_wt_ctrl_read_state rstate; + struct { + nghttp3_vec error_msg; + } tx; + struct { + nghttp3_vec error_msg; + uint32_t error_code; + } rx; + int64_t session_id; + nghttp3_stream *head; + uint32_t flags; +} nghttp3_wt_session; + +void nghttp3_wt_session_add_stream(nghttp3_wt_session *wts, + nghttp3_stream *stream); + +void nghttp3_wt_session_remove_stream(nghttp3_wt_session *wts, + nghttp3_stream *stream); + +int nghttp3_wt_session_new(nghttp3_wt_session **pwts, int64_t stream_id, + const nghttp3_mem *mem); + +void nghttp3_wt_session_del(nghttp3_wt_session *wts, const nghttp3_mem *mem); + +void nghttp3_wt_ctrl_read_state_reset(nghttp3_wt_ctrl_read_state *rstate); + +#endif /* !defined(NGHTTP3_WT_H) */ diff --git a/deps/ngtcp2/ngtcp2.gyp b/deps/ngtcp2/ngtcp2.gyp index 7ae627924851..939b9b9eba44 100644 --- a/deps/ngtcp2/ngtcp2.gyp +++ b/deps/ngtcp2/ngtcp2.gyp @@ -90,6 +90,7 @@ 'nghttp3/lib/nghttp3_unreachable.c', 'nghttp3/lib/nghttp3_vec.c', 'nghttp3/lib/nghttp3_version.c', + 'nghttp3/lib/nghttp3_wt.c', ], 'ngtcp2_test_server_sources': [ 'ngtcp2/examples/tls_server_session_ossl.cc', diff --git a/doc/api/quic.md b/doc/api/quic.md index a9e7414f4a3e..ed086e3821ac 100644 --- a/doc/api/quic.md +++ b/doc/api/quic.md @@ -1349,6 +1349,11 @@ added: v23.8.0 will buffer before `writeSync()` returns `false`. When the buffered data exceeds this limit, the caller should wait for drain before writing more. **Default:** `65536` (64 KB). + * `waitUntilAvailable` {boolean} When true the promise will wait until flow + control will allow to open the stream. If set to false, the function + will fail synchronously, if flow control will not allow to open the stream + immediately. + **Default:** `false` * `onheaders` {Function} Callback for received initial response headers. Called with `(headers)`. * `ontrailers` {Function} Callback for received trailing headers. @@ -1390,6 +1395,11 @@ added: v23.8.0 will buffer before `writeSync()` returns `false`. When the buffered data exceeds this limit, the caller should wait for drain before writing more. **Default:** `65536` (64 KB). + * `waitUntilAvailable` {boolean} When true the promise will wait until flow + control will allow to open the stream. If set to false, the function + will fail synchronously, if flow control will not allow to open the stream + immediately. + **Default:** `false` * `onheaders` {Function} Callback for received initial response headers. Called with `(headers)`. * `ontrailers` {Function} Callback for received trailing headers. @@ -1921,6 +1931,19 @@ Either `'application'` or `'transport'`. Indicates the namespace of added: v23.8.0 --> +### `stream.ready` + + + +* Type: {Promise} + +A promise that is immediately fulfilled, if the stream fits within +flow control limits or fulfilled when the pending stream is created. +It rejects, if a pending stream is closed with an error before being +created. + ### `stream.closed`