aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/core/ngx_connection.c18
-rw-r--r--src/core/ngx_connection.h12
-rw-r--r--src/core/ngx_core.h39
-rw-r--r--src/event/ngx_event.c46
-rw-r--r--src/event/ngx_event_openssl.c7
-rw-r--r--src/event/ngx_event_openssl.h7
-rw-r--r--src/event/ngx_event_quic.c4699
-rw-r--r--src/event/ngx_event_quic.h149
-rw-r--r--src/event/ngx_event_quic_protection.c1165
-rw-r--r--src/event/ngx_event_quic_protection.h49
-rw-r--r--src/event/ngx_event_quic_transport.c1779
-rw-r--r--src/event/ngx_event_quic_transport.h347
-rw-r--r--src/http/modules/ngx_http_chunked_filter_module.c58
-rw-r--r--src/http/modules/ngx_http_quic_module.c345
-rw-r--r--src/http/modules/ngx_http_quic_module.h25
-rw-r--r--src/http/modules/ngx_http_ssl_module.c43
-rw-r--r--src/http/ngx_http.c54
-rw-r--r--src/http/ngx_http.h10
-rw-r--r--src/http/ngx_http_core_module.c46
-rw-r--r--src/http/ngx_http_core_module.h6
-rw-r--r--src/http/ngx_http_header_filter_module.c28
-rw-r--r--src/http/ngx_http_parse.c14
-rw-r--r--src/http/ngx_http_request.c210
-rw-r--r--src/http/ngx_http_request.h7
-rw-r--r--src/http/ngx_http_request_body.c44
-rw-r--r--src/http/v3/ngx_http_v3.h206
-rw-r--r--src/http/v3/ngx_http_v3_encode.c227
-rw-r--r--src/http/v3/ngx_http_v3_module.c291
-rw-r--r--src/http/v3/ngx_http_v3_parse.c1582
-rw-r--r--src/http/v3/ngx_http_v3_parse.h165
-rw-r--r--src/http/v3/ngx_http_v3_request.c1545
-rw-r--r--src/http/v3/ngx_http_v3_streams.c819
-rw-r--r--src/http/v3/ngx_http_v3_tables.c686
-rw-r--r--src/stream/ngx_stream.c6
-rw-r--r--src/stream/ngx_stream.h6
-rw-r--r--src/stream/ngx_stream_core_module.c32
-rw-r--r--src/stream/ngx_stream_handler.c21
-rw-r--r--src/stream/ngx_stream_quic_module.c343
-rw-r--r--src/stream/ngx_stream_quic_module.h20
39 files changed, 15055 insertions, 101 deletions
diff --git a/src/core/ngx_connection.c b/src/core/ngx_connection.c
index c082d0dac..21f9980f2 100644
--- a/src/core/ngx_connection.c
+++ b/src/core/ngx_connection.c
@@ -1034,6 +1034,12 @@ ngx_close_listening_sockets(ngx_cycle_t *cycle)
ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++) {
+#if (NGX_QUIC)
+ if (ls[i].quic) {
+ continue;
+ }
+#endif
+
c = ls[i].connection;
if (c) {
@@ -1176,11 +1182,6 @@ ngx_close_connection(ngx_connection_t *c)
ngx_uint_t log_error, level;
ngx_socket_t fd;
- if (c->fd == (ngx_socket_t) -1) {
- ngx_log_error(NGX_LOG_ALERT, c->log, 0, "connection already closed");
- return;
- }
-
if (c->read->timer_set) {
ngx_del_timer(c->read);
}
@@ -1189,7 +1190,7 @@ ngx_close_connection(ngx_connection_t *c)
ngx_del_timer(c->write);
}
- if (!c->shared) {
+ if (!c->shared && c->fd != (ngx_socket_t) -1) {
if (ngx_del_conn) {
ngx_del_conn(c, NGX_CLOSE_EVENT);
@@ -1221,6 +1222,11 @@ ngx_close_connection(ngx_connection_t *c)
ngx_free_connection(c);
+ if (c->fd == (ngx_socket_t) -1) {
+ ngx_log_debug0(NGX_LOG_DEBUG_CORE, c->log, 0, "connection has no fd");
+ return;
+ }
+
fd = c->fd;
c->fd = (ngx_socket_t) -1;
diff --git a/src/core/ngx_connection.h b/src/core/ngx_connection.h
index ad6556d0c..2ce0f153b 100644
--- a/src/core/ngx_connection.h
+++ b/src/core/ngx_connection.h
@@ -75,6 +75,7 @@ struct ngx_listening_s {
unsigned reuseport:1;
unsigned add_reuseport:1;
unsigned keepalive:2;
+ unsigned quic:1;
unsigned deferred_accept:1;
unsigned delete_deferred:1;
@@ -147,13 +148,18 @@ struct ngx_connection_s {
socklen_t socklen;
ngx_str_t addr_text;
- ngx_proxy_protocol_t *proxy_protocol;
+ ngx_proxy_protocol_t *proxy_protocol;
+
+#if (NGX_QUIC || NGX_COMPAT)
+ ngx_quic_connection_t *quic;
+ ngx_quic_stream_t *qs;
+#endif
#if (NGX_SSL || NGX_COMPAT)
- ngx_ssl_connection_t *ssl;
+ ngx_ssl_connection_t *ssl;
#endif
- ngx_udp_connection_t *udp;
+ ngx_udp_connection_t *udp;
struct sockaddr *local_sockaddr;
socklen_t local_socklen;
diff --git a/src/core/ngx_core.h b/src/core/ngx_core.h
index 7ecdca0cb..ade35be73 100644
--- a/src/core/ngx_core.h
+++ b/src/core/ngx_core.h
@@ -12,23 +12,25 @@
#include <ngx_config.h>
-typedef struct ngx_module_s ngx_module_t;
-typedef struct ngx_conf_s ngx_conf_t;
-typedef struct ngx_cycle_s ngx_cycle_t;
-typedef struct ngx_pool_s ngx_pool_t;
-typedef struct ngx_chain_s ngx_chain_t;
-typedef struct ngx_log_s ngx_log_t;
-typedef struct ngx_open_file_s ngx_open_file_t;
-typedef struct ngx_command_s ngx_command_t;
-typedef struct ngx_file_s ngx_file_t;
-typedef struct ngx_event_s ngx_event_t;
-typedef struct ngx_event_aio_s ngx_event_aio_t;
-typedef struct ngx_connection_s ngx_connection_t;
-typedef struct ngx_thread_task_s ngx_thread_task_t;
-typedef struct ngx_ssl_s ngx_ssl_t;
-typedef struct ngx_proxy_protocol_s ngx_proxy_protocol_t;
-typedef struct ngx_ssl_connection_s ngx_ssl_connection_t;
-typedef struct ngx_udp_connection_s ngx_udp_connection_t;
+typedef struct ngx_module_s ngx_module_t;
+typedef struct ngx_conf_s ngx_conf_t;
+typedef struct ngx_cycle_s ngx_cycle_t;
+typedef struct ngx_pool_s ngx_pool_t;
+typedef struct ngx_chain_s ngx_chain_t;
+typedef struct ngx_log_s ngx_log_t;
+typedef struct ngx_open_file_s ngx_open_file_t;
+typedef struct ngx_command_s ngx_command_t;
+typedef struct ngx_file_s ngx_file_t;
+typedef struct ngx_event_s ngx_event_t;
+typedef struct ngx_event_aio_s ngx_event_aio_t;
+typedef struct ngx_connection_s ngx_connection_t;
+typedef struct ngx_thread_task_s ngx_thread_task_t;
+typedef struct ngx_ssl_s ngx_ssl_t;
+typedef struct ngx_proxy_protocol_s ngx_proxy_protocol_t;
+typedef struct ngx_quic_connection_s ngx_quic_connection_t;
+typedef struct ngx_quic_stream_s ngx_quic_stream_t;
+typedef struct ngx_ssl_connection_s ngx_ssl_connection_t;
+typedef struct ngx_udp_connection_s ngx_udp_connection_t;
typedef void (*ngx_event_handler_pt)(ngx_event_t *ev);
typedef void (*ngx_connection_handler_pt)(ngx_connection_t *c);
@@ -82,6 +84,9 @@ typedef void (*ngx_connection_handler_pt)(ngx_connection_t *c);
#include <ngx_resolver.h>
#if (NGX_OPENSSL)
#include <ngx_event_openssl.h>
+#if (NGX_OPENSSL_QUIC)
+#include <ngx_event_quic.h>
+#endif
#endif
#include <ngx_process_cycle.h>
#include <ngx_conf_file.h>
diff --git a/src/event/ngx_event.c b/src/event/ngx_event.c
index 402a7f5e2..de32630fd 100644
--- a/src/event/ngx_event.c
+++ b/src/event/ngx_event.c
@@ -268,6 +268,26 @@ ngx_process_events_and_timers(ngx_cycle_t *cycle)
ngx_int_t
ngx_handle_read_event(ngx_event_t *rev, ngx_uint_t flags)
{
+#if (NGX_QUIC)
+
+ ngx_connection_t *c;
+
+ c = rev->data;
+
+ if (c->qs) {
+
+ if (!rev->active && !rev->ready) {
+ rev->active = 1;
+
+ } else if (rev->active && (rev->ready || (flags & NGX_CLOSE_EVENT))) {
+ rev->active = 0;
+ }
+
+ return NGX_OK;
+ }
+
+#endif
+
if (ngx_event_flags & NGX_USE_CLEAR_EVENT) {
/* kqueue, epoll */
@@ -338,14 +358,30 @@ ngx_handle_write_event(ngx_event_t *wev, size_t lowat)
{
ngx_connection_t *c;
- if (lowat) {
- c = wev->data;
+ c = wev->data;
+ if (lowat) {
if (ngx_send_lowat(c, lowat) == NGX_ERROR) {
return NGX_ERROR;
}
}
+#if (NGX_QUIC)
+
+ if (c->qs) {
+
+ if (!wev->active && !wev->ready) {
+ wev->active = 1;
+
+ } else if (wev->active && wev->ready) {
+ wev->active = 0;
+ }
+
+ return NGX_OK;
+ }
+
+#endif
+
if (ngx_event_flags & NGX_USE_CLEAR_EVENT) {
/* kqueue, epoll */
@@ -916,6 +952,12 @@ ngx_send_lowat(ngx_connection_t *c, size_t lowat)
{
int sndlowat;
+#if (NGX_QUIC)
+ if (c->qs) {
+ return NGX_OK;
+ }
+#endif
+
#if (NGX_HAVE_LOWAT_EVENT)
if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c
index f387c720d..8077f40a9 100644
--- a/src/event/ngx_event_openssl.c
+++ b/src/event/ngx_event_openssl.c
@@ -2778,6 +2778,13 @@ ngx_ssl_shutdown(ngx_connection_t *c)
ngx_err_t err;
ngx_uint_t tries;
+#if (NGX_QUIC)
+ if (c->qs) {
+ /* QUIC streams inherit SSL object */
+ return NGX_OK;
+ }
+#endif
+
ngx_ssl_ocsp_cleanup(c);
if (SSL_in_init(c->ssl->connection)) {
diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h
index 4909f021e..8ed778748 100644
--- a/src/event/ngx_event_openssl.h
+++ b/src/event/ngx_event_openssl.h
@@ -14,6 +14,7 @@
#include <openssl/ssl.h>
#include <openssl/err.h>
+#include <openssl/aes.h>
#include <openssl/bn.h>
#include <openssl/conf.h>
#include <openssl/crypto.h>
@@ -22,6 +23,12 @@
#include <openssl/engine.h>
#endif
#include <openssl/evp.h>
+#ifdef OPENSSL_IS_BORINGSSL
+#include <openssl/hkdf.h>
+#include <openssl/chacha.h>
+#else
+#include <openssl/kdf.h>
+#endif
#include <openssl/hmac.h>
#ifndef OPENSSL_NO_OCSP
#include <openssl/ocsp.h>
diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c
new file mode 100644
index 000000000..42650ab1a
--- /dev/null
+++ b/src/event/ngx_event_quic.c
@@ -0,0 +1,4699 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_event_quic_transport.h>
+#include <ngx_event_quic_protection.h>
+
+
+/* 0-RTT and 1-RTT data exist in the same packet number space,
+ * so we have 3 packet number spaces:
+ *
+ * 0 - Initial
+ * 1 - Handshake
+ * 2 - 0-RTT and 1-RTT
+ */
+#define ngx_quic_get_send_ctx(qc, level) \
+ ((level) == ssl_encryption_initial) ? &((qc)->send_ctx[0]) \
+ : (((level) == ssl_encryption_handshake) ? &((qc)->send_ctx[1]) \
+ : &((qc)->send_ctx[2]))
+
+#define NGX_QUIC_SEND_CTX_LAST (NGX_QUIC_ENCRYPTION_LAST - 1)
+
+#define NGX_QUIC_STREAMS_INC 16
+#define NGX_QUIC_STREAMS_LIMIT (1ULL < 60)
+
+/*
+ * 7.4. Cryptographic Message Buffering
+ * Implementations MUST support buffering at least 4096 bytes of data
+ */
+#define NGX_QUIC_MAX_BUFFERED 65535
+
+#define NGX_QUIC_STREAM_GONE (void *) -1
+
+
+typedef struct {
+ ngx_rbtree_t tree;
+ ngx_rbtree_node_t sentinel;
+
+ uint64_t received;
+ uint64_t sent;
+ uint64_t recv_max_data;
+ uint64_t send_max_data;
+
+ uint64_t server_max_streams_uni;
+ uint64_t server_max_streams_bidi;
+ uint64_t server_streams_uni;
+ uint64_t server_streams_bidi;
+
+ uint64_t client_max_streams_uni;
+ uint64_t client_max_streams_bidi;
+ uint64_t client_streams_uni;
+ uint64_t client_streams_bidi;
+} ngx_quic_streams_t;
+
+
+typedef struct {
+ size_t in_flight;
+ size_t window;
+ size_t ssthresh;
+ ngx_msec_t recovery_start;
+} ngx_quic_congestion_t;
+
+
+/*
+ * 12.3. Packet Numbers
+ *
+ * Conceptually, a packet number space is the context in which a packet
+ * can be processed and acknowledged. Initial packets can only be sent
+ * with Initial packet protection keys and acknowledged in packets which
+ * are also Initial packets.
+*/
+typedef struct {
+ ngx_quic_secret_t client_secret;
+ ngx_quic_secret_t server_secret;
+
+ uint64_t pnum; /* to be sent */
+ uint64_t largest_ack; /* received from peer */
+ uint64_t largest_pn; /* received from peer */
+
+ ngx_queue_t frames;
+ ngx_queue_t sent;
+
+ size_t frames_len;
+} ngx_quic_send_ctx_t;
+
+
+struct ngx_quic_connection_s {
+ ngx_str_t scid;
+ ngx_str_t dcid;
+ ngx_str_t odcid;
+ ngx_str_t token;
+
+ ngx_uint_t client_tp_done;
+ ngx_quic_tp_t tp;
+ ngx_quic_tp_t ctp;
+
+ ngx_quic_send_ctx_t send_ctx[NGX_QUIC_SEND_CTX_LAST];
+ ngx_quic_secrets_t keys[NGX_QUIC_ENCRYPTION_LAST];
+ ngx_quic_secrets_t next_key;
+ ngx_quic_frames_stream_t crypto[NGX_QUIC_ENCRYPTION_LAST];
+
+ ngx_quic_conf_t *conf;
+
+ ngx_ssl_t *ssl;
+
+ ngx_event_t push;
+ ngx_event_t pto;
+ ngx_event_t close;
+ ngx_queue_t free_frames;
+ ngx_msec_t last_cc;
+
+ ngx_msec_t latest_rtt;
+ ngx_msec_t avg_rtt;
+ ngx_msec_t min_rtt;
+ ngx_msec_t rttvar;
+
+ ngx_msec_t pto_count;
+
+#if (NGX_DEBUG)
+ ngx_uint_t nframes;
+#endif
+
+ ngx_quic_streams_t streams;
+ ngx_quic_congestion_t congestion;
+ size_t received;
+
+ ngx_uint_t error;
+ enum ssl_encryption_level_t error_level;
+ ngx_uint_t error_ftype;
+ const char *error_reason;
+
+ unsigned error_app:1;
+ unsigned send_timer_set:1;
+ unsigned closing:1;
+ unsigned draining:1;
+ unsigned key_phase:1;
+ unsigned in_retry:1;
+ unsigned initialized:1;
+ unsigned validated:1;
+};
+
+
+typedef ngx_int_t (*ngx_quic_frame_handler_pt)(ngx_connection_t *c,
+ ngx_quic_frame_t *frame, void *data);
+
+
+#if BORINGSSL_API_VERSION >= 10
+static int ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn,
+ enum ssl_encryption_level_t level, const SSL_CIPHER *cipher,
+ const uint8_t *secret, size_t secret_len);
+static int ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn,
+ enum ssl_encryption_level_t level, const SSL_CIPHER *cipher,
+ const uint8_t *secret, size_t secret_len);
+#else
+static int ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn,
+ enum ssl_encryption_level_t level, const uint8_t *read_secret,
+ const uint8_t *write_secret, size_t secret_len);
+#endif
+
+static int ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn,
+ enum ssl_encryption_level_t level, const uint8_t *data, size_t len);
+static int ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn);
+static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn,
+ enum ssl_encryption_level_t level, uint8_t alert);
+
+
+static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl,
+ ngx_quic_conf_t *conf, ngx_quic_header_t *pkt);
+static ngx_int_t ngx_quic_new_dcid(ngx_connection_t *c, ngx_str_t *odcid);
+static ngx_int_t ngx_quic_retry(ngx_connection_t *c);
+static ngx_int_t ngx_quic_new_token(ngx_connection_t *c, ngx_str_t *token);
+static ngx_int_t ngx_quic_validate_token(ngx_connection_t *c,
+ ngx_quic_header_t *pkt);
+static ngx_int_t ngx_quic_init_connection(ngx_connection_t *c);
+static ngx_inline size_t ngx_quic_max_udp_payload(ngx_connection_t *c);
+static void ngx_quic_input_handler(ngx_event_t *rev);
+
+static void ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc);
+static ngx_int_t ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc);
+static void ngx_quic_close_timer_handler(ngx_event_t *ev);
+static ngx_int_t ngx_quic_close_streams(ngx_connection_t *c,
+ ngx_quic_connection_t *qc);
+
+static ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b);
+static ngx_inline u_char *ngx_quic_skip_zero_padding(ngx_buf_t *b);
+static ngx_int_t ngx_quic_retry_input(ngx_connection_t *c,
+ ngx_quic_header_t *pkt);
+static ngx_int_t ngx_quic_initial_input(ngx_connection_t *c,
+ ngx_quic_header_t *pkt);
+static ngx_int_t ngx_quic_handshake_input(ngx_connection_t *c,
+ ngx_quic_header_t *pkt);
+static ngx_int_t ngx_quic_early_input(ngx_connection_t *c,
+ ngx_quic_header_t *pkt);
+static ngx_int_t ngx_quic_check_peer(ngx_quic_connection_t *qc,
+ ngx_quic_header_t *pkt);
+static ngx_int_t ngx_quic_app_input(ngx_connection_t *c,
+ ngx_quic_header_t *pkt);
+static ngx_int_t ngx_quic_payload_handler(ngx_connection_t *c,
+ ngx_quic_header_t *pkt);
+static ngx_int_t ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_header_t *pkt);
+static ngx_int_t ngx_quic_ack_delay(ngx_connection_t *c,
+ struct timeval *received, enum ssl_encryption_level_t level);
+static ngx_int_t ngx_quic_send_cc(ngx_connection_t *c);
+static ngx_int_t ngx_quic_send_new_token(ngx_connection_t *c);
+
+static ngx_int_t ngx_quic_handle_ack_frame(ngx_connection_t *c,
+ ngx_quic_header_t *pkt, ngx_quic_ack_frame_t *f);
+static ngx_int_t ngx_quic_handle_ack_frame_range(ngx_connection_t *c,
+ ngx_quic_send_ctx_t *ctx, uint64_t min, uint64_t max,
+ ngx_msec_t *send_time);
+static void ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack,
+ enum ssl_encryption_level_t level, ngx_msec_t send_time);
+static ngx_inline ngx_msec_t ngx_quic_pto(ngx_connection_t *c,
+ ngx_quic_send_ctx_t *ctx);
+static void ngx_quic_handle_stream_ack(ngx_connection_t *c,
+ ngx_quic_frame_t *f);
+
+static ngx_int_t ngx_quic_handle_ordered_frame(ngx_connection_t *c,
+ ngx_quic_frames_stream_t *fs, ngx_quic_frame_t *frame,
+ ngx_quic_frame_handler_pt handler, void *data);
+static ngx_int_t ngx_quic_adjust_frame_offset(ngx_connection_t *c,
+ ngx_quic_frame_t *f, uint64_t offset_in);
+static ngx_int_t ngx_quic_buffer_frame(ngx_connection_t *c,
+ ngx_quic_frames_stream_t *stream, ngx_quic_frame_t *f);
+
+static ngx_int_t ngx_quic_handle_crypto_frame(ngx_connection_t *c,
+ ngx_quic_header_t *pkt, ngx_quic_frame_t *frame);
+static ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c,
+ ngx_quic_frame_t *frame, void *data);
+static ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c,
+ ngx_quic_header_t *pkt, ngx_quic_frame_t *frame);
+static ngx_int_t ngx_quic_stream_input(ngx_connection_t *c,
+ ngx_quic_frame_t *frame, void *data);
+
+static ngx_int_t ngx_quic_handle_max_data_frame(ngx_connection_t *c,
+ ngx_quic_max_data_frame_t *f);
+static ngx_int_t ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c,
+ ngx_quic_header_t *pkt, ngx_quic_streams_blocked_frame_t *f);
+static ngx_int_t ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c,
+ ngx_quic_header_t *pkt, ngx_quic_stream_data_blocked_frame_t *f);
+static ngx_int_t ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c,
+ ngx_quic_header_t *pkt, ngx_quic_max_stream_data_frame_t *f);
+static ngx_int_t ngx_quic_handle_reset_stream_frame(ngx_connection_t *c,
+ ngx_quic_header_t *pkt, ngx_quic_reset_stream_frame_t *f);
+static ngx_int_t ngx_quic_handle_stop_sending_frame(ngx_connection_t *c,
+ ngx_quic_header_t *pkt, ngx_quic_stop_sending_frame_t *f);
+static ngx_int_t ngx_quic_handle_max_streams_frame(ngx_connection_t *c,
+ ngx_quic_header_t *pkt, ngx_quic_max_streams_frame_t *f);
+
+static void ngx_quic_queue_frame(ngx_quic_connection_t *qc,
+ ngx_quic_frame_t *frame);
+
+static ngx_int_t ngx_quic_output(ngx_connection_t *c);
+static ngx_int_t ngx_quic_output_frames(ngx_connection_t *c,
+ ngx_quic_send_ctx_t *ctx);
+static void ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames);
+static ngx_int_t ngx_quic_send_frames(ngx_connection_t *c,
+ ngx_quic_send_ctx_t *ctx, ngx_queue_t *frames);
+
+static void ngx_quic_set_packet_number(ngx_quic_header_t *pkt,
+ ngx_quic_send_ctx_t *ctx);
+static void ngx_quic_pto_handler(ngx_event_t *ev);
+static ngx_int_t ngx_quic_detect_lost(ngx_connection_t *c, ngx_uint_t ack);
+static void ngx_quic_push_handler(ngx_event_t *ev);
+
+static void ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp,
+ ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
+static ngx_quic_stream_t *ngx_quic_find_stream(ngx_rbtree_t *rbtree,
+ uint64_t id);
+static ngx_quic_stream_t *ngx_quic_create_client_stream(ngx_connection_t *c,
+ uint64_t id);
+static ngx_quic_stream_t *ngx_quic_create_stream(ngx_connection_t *c,
+ uint64_t id, size_t rcvbuf_size);
+static ssize_t ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf,
+ size_t size);
+static ssize_t ngx_quic_stream_send(ngx_connection_t *c, u_char *buf,
+ size_t size);
+static ngx_chain_t *ngx_quic_stream_send_chain(ngx_connection_t *c,
+ ngx_chain_t *in, off_t limit);
+static size_t ngx_quic_max_stream_frame(ngx_quic_connection_t *qc);
+static size_t ngx_quic_max_stream_flow(ngx_connection_t *c);
+static void ngx_quic_stream_cleanup_handler(void *data);
+static ngx_quic_frame_t *ngx_quic_alloc_frame(ngx_connection_t *c, size_t size);
+static void ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame);
+
+static void ngx_quic_congestion_ack(ngx_connection_t *c,
+ ngx_quic_frame_t *frame);
+static void ngx_quic_congestion_lost(ngx_connection_t *c,
+ ngx_quic_frame_t *frame);
+
+
+static SSL_QUIC_METHOD quic_method = {
+#if BORINGSSL_API_VERSION >= 10
+ ngx_quic_set_read_secret,
+ ngx_quic_set_write_secret,
+#else
+ ngx_quic_set_encryption_secrets,
+#endif
+ ngx_quic_add_handshake_data,
+ ngx_quic_flush_flight,
+ ngx_quic_send_alert,
+};
+
+
+#if BORINGSSL_API_VERSION >= 10
+
+static int
+ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn,
+ enum ssl_encryption_level_t level, const SSL_CIPHER *cipher,
+ const uint8_t *rsecret, size_t secret_len)
+{
+ ngx_connection_t *c;
+ ngx_quic_secrets_t *keys;
+
+ c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
+
+#ifdef NGX_QUIC_DEBUG_CRYPTO
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic ngx_quic_set_read_secret() level:%d", level);
+ ngx_quic_hexdump(c->log, "quic read secret", rsecret, secret_len);
+#endif
+
+ keys = &c->quic->keys[level];
+
+ return ngx_quic_set_encryption_secret(c->pool, ssl_conn, level,
+ rsecret, secret_len,
+ &keys->client);
+}
+
+
+static int
+ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn,
+ enum ssl_encryption_level_t level, const SSL_CIPHER *cipher,
+ const uint8_t *wsecret, size_t secret_len)
+{
+ ngx_connection_t *c;
+ ngx_quic_secrets_t *keys;
+
+ c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
+
+#ifdef NGX_QUIC_DEBUG_CRYPTO
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic ngx_quic_set_write_secret() level:%d", level);
+ ngx_quic_hexdump(c->log, "quic write secret", wsecret, secret_len);
+#endif
+
+ keys = &c->quic->keys[level];
+
+ return ngx_quic_set_encryption_secret(c->pool, ssl_conn, level,
+ wsecret, secret_len,
+ &keys->server);
+}
+
+#else
+
+static int
+ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn,
+ enum ssl_encryption_level_t level, const uint8_t *rsecret,
+ const uint8_t *wsecret, size_t secret_len)
+{
+ ngx_int_t rc;
+ ngx_connection_t *c;
+ ngx_quic_secrets_t *keys;
+
+ c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
+
+#ifdef NGX_QUIC_DEBUG_CRYPTO
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic ngx_quic_set_encryption_secrets() level:%d", level);
+ ngx_quic_hexdump(c->log, "quic read", rsecret, secret_len);
+#endif
+
+ keys = &c->quic->keys[level];
+
+ rc = ngx_quic_set_encryption_secret(c->pool, ssl_conn, level,
+ rsecret, secret_len,
+ &keys->client);
+ if (rc != 1) {
+ return rc;
+ }
+
+ if (level == ssl_encryption_early_data) {
+ return 1;
+ }
+
+#ifdef NGX_QUIC_DEBUG_CRYPTO
+ ngx_quic_hexdump(c->log, "quic write", wsecret, secret_len);
+#endif
+
+ return ngx_quic_set_encryption_secret(c->pool, ssl_conn, level,
+ wsecret, secret_len,
+ &keys->server);
+}
+
+#endif
+
+
+static int
+ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn,
+ enum ssl_encryption_level_t level, const uint8_t *data, size_t len)
+{
+ u_char *p, *end;
+ size_t client_params_len, fsize, limit;
+ const uint8_t *client_params;
+ ngx_quic_frame_t *frame;
+ ngx_connection_t *c;
+ ngx_quic_connection_t *qc;
+ ngx_quic_frames_stream_t *fs;
+
+ c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
+ qc = c->quic;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic ngx_quic_add_handshake_data");
+
+ if (!qc->client_tp_done) {
+ /*
+ * things to do once during handshake: check ALPN and transport
+ * parameters; we want to break handshake if something is wrong
+ * here;
+ */
+
+#if defined(TLSEXT_TYPE_application_layer_protocol_negotiation)
+ if (qc->conf->require_alpn) {
+ unsigned int len;
+ const unsigned char *data;
+
+ SSL_get0_alpn_selected(c->ssl->connection, &data, &len);
+
+ if (len == 0) {
+ qc->error = 0x100 + SSL_AD_NO_APPLICATION_PROTOCOL;
+ qc->error_reason = "unsupported protocol in ALPN extension";
+
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "quic unsupported protocol in ALPN extension");
+ return 0;
+ }
+ }
+#endif
+
+ SSL_get_peer_quic_transport_params(ssl_conn, &client_params,
+ &client_params_len);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic SSL_get_peer_quic_transport_params():"
+ " params_len %ui", client_params_len);
+
+ if (client_params_len == 0) {
+ /* quic-tls 8.2 */
+ qc->error = NGX_QUIC_ERR_CRYPTO(SSL_AD_MISSING_EXTENSION);
+ qc->error_reason = "missing transport parameters";
+
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "missing transport parameters");
+ return 0;
+ }
+
+ p = (u_char *) client_params;
+ end = p + client_params_len;
+
+ if (ngx_quic_parse_transport_params(p, end, &qc->ctp, c->log)
+ != NGX_OK)
+ {
+ qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR;
+ qc->error_reason = "failed to process transport parameters";
+
+ return 0;
+ }
+
+ if (qc->ctp.max_idle_timeout > 0
+ && qc->ctp.max_idle_timeout < qc->tp.max_idle_timeout)
+ {
+ qc->tp.max_idle_timeout = qc->ctp.max_idle_timeout;
+ }
+
+ if (qc->ctp.max_udp_payload_size < NGX_QUIC_MIN_INITIAL_SIZE
+ || qc->ctp.max_udp_payload_size > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE)
+ {
+ qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR;
+ qc->error_reason = "invalid maximum packet size";
+
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "quic maximum packet size is invalid");
+ return 0;
+ }
+
+ if (qc->ctp.max_udp_payload_size > ngx_quic_max_udp_payload(c)) {
+ qc->ctp.max_udp_payload_size = ngx_quic_max_udp_payload(c);
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic client maximum packet size truncated");
+ }
+
+#if (NGX_QUIC_DRAFT_VERSION >= 28)
+ if (qc->scid.len != qc->ctp.initial_scid.len
+ || ngx_memcmp(qc->scid.data, qc->ctp.initial_scid.data,
+ qc->scid.len) != 0)
+ {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "quic client initial_source_connection_id "
+ "mismatch");
+ return 0;
+ }
+#endif
+
+ qc->streams.server_max_streams_bidi = qc->ctp.initial_max_streams_bidi;
+ qc->streams.server_max_streams_uni = qc->ctp.initial_max_streams_uni;
+
+ qc->client_tp_done = 1;
+ }
+
+ /*
+ * we need to fit at least 1 frame into a packet, thus account head/tail;
+ * 17 = 1 + 8x2 is max header for CRYPTO frame, with 1 byte for frame type
+ */
+ limit = qc->ctp.max_udp_payload_size - NGX_QUIC_MAX_LONG_HEADER - 17
+ - EVP_GCM_TLS_TAG_LEN;
+
+ fs = &qc->crypto[level];
+
+ p = (u_char *) data;
+ end = (u_char *) data + len;
+
+ while (p < end) {
+
+ fsize = ngx_min(limit, (size_t) (end - p));
+
+ frame = ngx_quic_alloc_frame(c, fsize);
+ if (frame == NULL) {
+ return 0;
+ }
+
+ ngx_memcpy(frame->data, p, fsize);
+
+ frame->level = level;
+ frame->type = NGX_QUIC_FT_CRYPTO;
+ frame->u.crypto.offset = fs->sent;
+ frame->u.crypto.length = fsize;
+ frame->u.crypto.data = frame->data;
+
+ fs->sent += fsize;
+ p += fsize;
+
+ ngx_sprintf(frame->info, "crypto, generated by SSL len=%ui level=%d",
+ fsize, level);
+
+ ngx_quic_queue_frame(qc, frame);
+ }
+
+ return 1;
+}
+
+
+static int
+ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn)
+{
+#if (NGX_DEBUG)
+ ngx_connection_t *c;
+
+ c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic ngx_quic_flush_flight()");
+#endif
+ return 1;
+}
+
+
+static int
+ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level,
+ uint8_t alert)
+{
+ ngx_connection_t *c;
+ ngx_quic_connection_t *qc;
+
+ c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic ngx_quic_send_alert(), lvl=%d, alert=%d",
+ (int) level, (int) alert);
+
+ qc = c->quic;
+ if (qc == NULL) {
+ return 1;
+ }
+
+ qc->error_level = level;
+ qc->error = NGX_QUIC_ERR_CRYPTO(alert);
+ qc->error_reason = "TLS alert";
+ qc->error_app = 0;
+ qc->error_ftype = 0;
+
+ if (ngx_quic_send_cc(c) != NGX_OK) {
+ return 0;
+ }
+
+ return 1;
+}
+
+
+void
+ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_conf_t *conf)
+{
+ ngx_buf_t *b;
+ ngx_quic_header_t pkt;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic run");
+
+ c->log->action = "QUIC initialization";
+
+ ngx_memzero(&pkt, sizeof(ngx_quic_header_t));
+
+ b = c->buffer;
+
+ pkt.log = c->log;
+ pkt.raw = b;
+ pkt.data = b->start;
+ pkt.len = b->last - b->start;
+
+ if (ngx_quic_new_connection(c, ssl, conf, &pkt) != NGX_OK) {
+ ngx_quic_close_connection(c, NGX_ERROR);
+ return;
+ }
+
+ ngx_add_timer(c->read, c->quic->in_retry ? NGX_QUIC_RETRY_TIMEOUT
+ : c->quic->tp.max_idle_timeout);
+
+ c->read->handler = ngx_quic_input_handler;
+
+ return;
+}
+
+
+static ngx_int_t
+ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl,
+ ngx_quic_conf_t *conf, ngx_quic_header_t *pkt)
+{
+ ngx_int_t rc;
+ ngx_uint_t i;
+ ngx_quic_tp_t *ctp;
+ ngx_quic_secrets_t *keys;
+ ngx_quic_send_ctx_t *ctx;
+ ngx_quic_connection_t *qc;
+ static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
+
+ if (ngx_buf_size(pkt->raw) < NGX_QUIC_MIN_INITIAL_SIZE) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "quic UDP datagram is too small for initial packet");
+ return NGX_ERROR;
+ }
+
+ rc = ngx_quic_parse_long_header(pkt);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ if (!ngx_quic_pkt_in(pkt->flags)) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "quic invalid initial packet: 0x%xd", pkt->flags);
+ return NGX_ERROR;
+ }
+
+ if (ngx_quic_parse_initial_header(pkt) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (pkt->dcid.len < NGX_QUIC_CID_LEN_MIN) {
+ /* 7.2. Negotiating Connection IDs */
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "quic too short dcid in initial packet: length %i",
+ pkt->dcid.len);
+ return NGX_ERROR;
+ }
+
+ c->log->action = "creating new quic connection";
+
+ qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t));
+ if (qc == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_rbtree_init(&qc->streams.tree, &qc->streams.sentinel,
+ ngx_quic_rbtree_insert_stream);
+
+ for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) {
+ ngx_queue_init(&qc->send_ctx[i].frames);
+ ngx_queue_init(&qc->send_ctx[i].sent);
+ qc->send_ctx[i].largest_pn = (uint64_t) -1;
+ }
+
+ for (i = 0; i < NGX_QUIC_ENCRYPTION_LAST; i++) {
+ ngx_queue_init(&qc->crypto[i].frames);
+ }
+
+ ngx_queue_init(&qc->free_frames);
+
+ qc->avg_rtt = NGX_QUIC_INITIAL_RTT;
+ qc->rttvar = NGX_QUIC_INITIAL_RTT / 2;
+ qc->min_rtt = NGX_TIMER_INFINITE;
+
+ /*
+ * qc->latest_rtt = 0
+ */
+
+ qc->received = pkt->raw->last - pkt->raw->start;
+
+ qc->pto.log = c->log;
+ qc->pto.data = c;
+ qc->pto.handler = ngx_quic_pto_handler;
+ qc->pto.cancelable = 1;
+
+ qc->push.log = c->log;
+ qc->push.data = c;
+ qc->push.handler = ngx_quic_push_handler;
+ qc->push.cancelable = 1;
+
+ c->quic = qc;
+ qc->ssl = ssl;
+ qc->conf = conf;
+ qc->tp = conf->tp;
+
+ ctp = &qc->ctp;
+ ctp->max_udp_payload_size = ngx_quic_max_udp_payload(c);
+ ctp->ack_delay_exponent = NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT;
+ ctp->max_ack_delay = NGX_QUIC_DEFAULT_MAX_ACK_DELAY;
+
+ qc->streams.recv_max_data = qc->tp.initial_max_data;
+
+ qc->streams.client_max_streams_uni = qc->tp.initial_max_streams_uni;
+ qc->streams.client_max_streams_bidi = qc->tp.initial_max_streams_bidi;
+
+ qc->congestion.window = ngx_min(10 * qc->tp.max_udp_payload_size,
+ ngx_max(2 * qc->tp.max_udp_payload_size,
+ 14720));
+ qc->congestion.ssthresh = NGX_MAX_SIZE_T_VALUE;
+ qc->congestion.recovery_start = ngx_current_msec;
+
+ if (ngx_quic_new_dcid(c, &pkt->dcid) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+#if (NGX_QUIC_DRAFT_VERSION >= 28)
+ qc->tp.original_dcid = c->quic->odcid;
+#endif
+ qc->tp.initial_scid = c->quic->dcid;
+
+ qc->scid.len = pkt->scid.len;
+ qc->scid.data = ngx_pnalloc(c->pool, qc->scid.len);
+ if (qc->scid.data == NULL) {
+ return NGX_ERROR;
+ }
+ ngx_memcpy(qc->scid.data, pkt->scid.data, qc->scid.len);
+
+ keys = &c->quic->keys[ssl_encryption_initial];
+
+ if (ngx_quic_set_initial_secret(c->pool, &keys->client, &keys->server,
+ &qc->odcid)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ qc->initialized = 1;
+
+ if (ngx_terminate || ngx_exiting) {
+ qc->error = NGX_QUIC_ERR_CONNECTION_REFUSED;
+ return NGX_ERROR;
+ }
+
+ if (pkt->token.len) {
+ rc = ngx_quic_validate_token(c, pkt);
+
+ if (rc == NGX_ERROR) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid token");
+ return NGX_ERROR;
+ }
+
+ if (rc == NGX_DECLINED) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic expired token");
+ return ngx_quic_retry(c);
+ }
+
+ /* NGX_OK */
+ qc->validated = 1;
+
+ } else if (conf->retry) {
+ return ngx_quic_retry(c);
+ }
+
+ pkt->secret = &keys->client;
+ pkt->level = ssl_encryption_initial;
+ pkt->plaintext = buf;
+
+ ctx = ngx_quic_get_send_ctx(qc, pkt->level);
+
+ if (ngx_quic_decrypt(pkt, NULL, &ctx->largest_pn) != NGX_OK) {
+ qc->error = pkt->error;
+ qc->error_reason = "failed to decrypt packet";
+
+ return NGX_ERROR;
+ }
+
+ if (ngx_quic_init_connection(c) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_quic_payload_handler(c, pkt) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ /* pos is at header end, adjust by actual packet length */
+ pkt->raw->pos += pkt->len;
+
+ (void) ngx_quic_skip_zero_padding(pkt->raw);
+
+ return ngx_quic_input(c, pkt->raw);
+}
+
+
+static ngx_int_t
+ngx_quic_new_dcid(ngx_connection_t *c, ngx_str_t *odcid)
+{
+ uint8_t len;
+ ngx_quic_connection_t *qc;
+
+ qc = c->quic;
+
+ if (RAND_bytes(&len, sizeof(len)) != 1) {
+ return NGX_ERROR;
+ }
+
+ len = len % 10 + 10;
+
+ qc->dcid.len = len;
+ qc->dcid.data = ngx_pnalloc(c->pool, len);
+ if (qc->dcid.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (RAND_bytes(qc->dcid.data, len) != 1) {
+ return NGX_ERROR;
+ }
+
+#ifdef NGX_QUIC_DEBUG_PACKETS
+ ngx_quic_hexdump(c->log, "quic server CID", qc->dcid.data, qc->dcid.len);
+#endif
+
+ qc->odcid.len = odcid->len;
+ qc->odcid.data = ngx_pstrdup(c->pool, odcid);
+ if (qc->odcid.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_quic_retry(ngx_connection_t *c)
+{
+ ssize_t len;
+ ngx_str_t res, token;
+ ngx_quic_header_t pkt;
+ u_char buf[NGX_QUIC_RETRY_BUFFER_SIZE];
+
+ if (ngx_quic_new_token(c, &token) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ ngx_memzero(&pkt, sizeof(ngx_quic_header_t));
+ pkt.flags = NGX_QUIC_PKT_FIXED_BIT | NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_RETRY;
+ pkt.log = c->log;
+ pkt.odcid = c->quic->odcid;
+ pkt.dcid = c->quic->scid;
+ pkt.scid = c->quic->dcid;
+ pkt.token = token;
+
+ res.data = buf;
+
+ if (ngx_quic_encrypt(&pkt, NULL, &res) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+#ifdef NGX_QUIC_DEBUG_PACKETS
+ ngx_quic_hexdump(c->log, "quic packet to send", res.data, res.len);
+#endif
+
+ len = c->send(c, res.data, res.len);
+ if (len == NGX_ERROR || (size_t) len != res.len) {
+ return NGX_ERROR;
+ }
+
+ c->quic->token = token;
+#if (NGX_QUIC_DRAFT_VERSION < 28)
+ c->quic->tp.original_dcid = c->quic->odcid;
+#endif
+ c->quic->tp.retry_scid = c->quic->dcid;
+ c->quic->in_retry = 1;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_quic_new_token(ngx_connection_t *c, ngx_str_t *token)
+{
+ int len, iv_len;
+ u_char *data, *p, *key, *iv;
+ ngx_msec_t now;
+ EVP_CIPHER_CTX *ctx;
+ const EVP_CIPHER *cipher;
+ struct sockaddr_in *sin;
+#if (NGX_HAVE_INET6)
+ struct sockaddr_in6 *sin6;
+#endif
+ u_char in[NGX_QUIC_MAX_TOKEN_SIZE];
+
+ switch (c->sockaddr->sa_family) {
+
+#if (NGX_HAVE_INET6)
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *) c->sockaddr;
+
+ len = sizeof(struct in6_addr);
+ data = sin6->sin6_addr.s6_addr;
+
+ break;
+#endif
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+ case AF_UNIX:
+
+ len = ngx_min(c->addr_text.len, NGX_QUIC_MAX_TOKEN_SIZE - sizeof(now));
+ data = c->addr_text.data;
+
+ break;
+#endif
+
+ default: /* AF_INET */
+ sin = (struct sockaddr_in *) c->sockaddr;
+
+ len = sizeof(in_addr_t);
+ data = (u_char *) &sin->sin_addr;
+
+ break;
+ }
+
+ p = ngx_cpymem(in, data, len);
+
+ now = ngx_current_msec;
+ len += sizeof(now);
+ ngx_memcpy(p, &now, sizeof(now));
+
+ cipher = EVP_aes_256_cbc();
+ iv_len = EVP_CIPHER_iv_length(cipher);
+
+ token->len = iv_len + len + EVP_CIPHER_block_size(cipher);
+ token->data = ngx_pnalloc(c->pool, token->len);
+ if (token->data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ctx = EVP_CIPHER_CTX_new();
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ key = c->quic->conf->token_key;
+ iv = token->data;
+
+ if (RAND_bytes(iv, iv_len) <= 0
+ || !EVP_EncryptInit_ex(ctx, cipher, NULL, key, iv))
+ {
+ EVP_CIPHER_CTX_free(ctx);
+ return NGX_ERROR;
+ }
+
+ token->len = iv_len;
+
+ if (EVP_EncryptUpdate(ctx, token->data + token->len, &len, in, len) != 1) {
+ EVP_CIPHER_CTX_free(ctx);
+ return NGX_ERROR;
+ }
+
+ token->len += len;
+
+ if (EVP_EncryptFinal_ex(ctx, token->data + token->len, &len) <= 0) {
+ EVP_CIPHER_CTX_free(ctx);
+ return NGX_ERROR;
+ }
+
+ token->len += len;
+
+ EVP_CIPHER_CTX_free(ctx);
+
+#ifdef NGX_QUIC_DEBUG_PACKETS
+ ngx_quic_hexdump(c->log, "quic new token", token->data, token->len);
+#endif
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_quic_validate_token(ngx_connection_t *c, ngx_quic_header_t *pkt)
+{
+ int len, tlen, iv_len;
+ u_char *key, *iv, *p, *data;
+ ngx_msec_t msec;
+ EVP_CIPHER_CTX *ctx;
+ const EVP_CIPHER *cipher;
+ struct sockaddr_in *sin;
+#if (NGX_HAVE_INET6)
+ struct sockaddr_in6 *sin6;
+#endif
+ ngx_quic_connection_t *qc;
+ u_char tdec[NGX_QUIC_MAX_TOKEN_SIZE];
+
+ if (pkt->token.len == 0) {
+ return NGX_ERROR;
+ }
+
+ qc = c->quic;
+
+ /* Retry token */
+
+ if (qc->token.len) {
+ if (pkt->token.len != qc->token.len) {
+ goto bad_token;
+ }
+
+ if (ngx_memcmp(pkt->token.data, qc->token.data, pkt->token.len) != 0) {
+ goto bad_token;
+ }
+
+ return NGX_OK;
+ }
+
+ /* NEW_TOKEN in a previous connection */
+
+ cipher = EVP_aes_256_cbc();
+ key = c->quic->conf->token_key;
+ iv = pkt->token.data;
+ iv_len = EVP_CIPHER_iv_length(cipher);
+
+ /* sanity checks */
+
+ if (pkt->token.len < (size_t) iv_len + EVP_CIPHER_block_size(cipher)) {
+ goto bad_token;
+ }
+
+ if (pkt->token.len > (size_t) iv_len + NGX_QUIC_MAX_TOKEN_SIZE) {
+ goto bad_token;
+ }
+
+ ctx = EVP_CIPHER_CTX_new();
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (!EVP_DecryptInit_ex(ctx, cipher, NULL, key, iv)) {
+ EVP_CIPHER_CTX_free(ctx);
+ return NGX_ERROR;
+ }
+
+ p = pkt->token.data + iv_len;
+ len = pkt->token.len - iv_len;
+
+ if (EVP_DecryptUpdate(ctx, tdec, &len, p, len) != 1) {
+ EVP_CIPHER_CTX_free(ctx);
+ goto bad_token;
+ }
+
+ if (EVP_DecryptFinal_ex(ctx, tdec + len, &tlen) <= 0) {
+ EVP_CIPHER_CTX_free(ctx);
+ goto bad_token;
+ }
+
+ EVP_CIPHER_CTX_free(ctx);
+
+ switch (c->sockaddr->sa_family) {
+
+#if (NGX_HAVE_INET6)
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *) c->sockaddr;
+
+ len = sizeof(struct in6_addr);
+ data = sin6->sin6_addr.s6_addr;
+
+ break;
+#endif
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+ case AF_UNIX:
+
+ len = ngx_min(c->addr_text.len, NGX_QUIC_MAX_TOKEN_SIZE - sizeof(msec));
+ data = c->addr_text.data;
+
+ break;
+#endif
+
+ default: /* AF_INET */
+ sin = (struct sockaddr_in *) c->sockaddr;
+
+ len = sizeof(in_addr_t);
+ data = (u_char *) &sin->sin_addr;
+
+ break;
+ }
+
+ if (ngx_memcmp(tdec, data, len) != 0) {
+ goto bad_token;
+ }
+
+ ngx_memcpy(&msec, tdec + len, sizeof(msec));
+
+ if (ngx_current_msec - msec > NGX_QUIC_RETRY_LIFETIME) {
+ return NGX_DECLINED;
+ }
+
+ return NGX_OK;
+
+bad_token:
+
+ qc->error = NGX_QUIC_ERR_INVALID_TOKEN;
+ qc->error_reason = "invalid_token";
+
+ return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_quic_init_connection(ngx_connection_t *c)
+{
+ u_char *p;
+ size_t clen;
+ ssize_t len;
+ ngx_ssl_conn_t *ssl_conn;
+ ngx_quic_connection_t *qc;
+
+ qc = c->quic;
+
+ if (ngx_ssl_create_connection(qc->ssl, c, NGX_SSL_BUFFER) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ ssl_conn = c->ssl->connection;
+
+ if (SSL_set_quic_method(ssl_conn, &quic_method) == 0) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "quic SSL_set_quic_method() failed");
+ return NGX_ERROR;
+ }
+
+#ifdef SSL_READ_EARLY_DATA_SUCCESS
+ if (SSL_CTX_get_max_early_data(qc->ssl->ctx)) {
+ SSL_set_quic_early_data_enabled(ssl_conn, 1);
+ }
+#endif
+
+ len = ngx_quic_create_transport_params(NULL, NULL, &qc->tp, &clen);
+ /* always succeeds */
+
+ p = ngx_pnalloc(c->pool, len);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ len = ngx_quic_create_transport_params(p, p + len, &qc->tp, NULL);
+ if (len < 0) {
+ return NGX_ERROR;
+ }
+
+#ifdef NGX_QUIC_DEBUG_PACKETS
+ ngx_quic_hexdump(c->log, "quic transport parameters", p, len);
+#endif
+
+ if (SSL_set_quic_transport_params(ssl_conn, p, len) == 0) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "quic SSL_set_quic_transport_params() failed");
+ return NGX_ERROR;
+ }
+
+#if NGX_OPENSSL_QUIC_ZRTT_CTX
+ if (SSL_set_quic_early_data_context(ssl_conn, p, clen) == 0) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "quic SSL_set_quic_early_data_context() failed");
+ return NGX_ERROR;
+ }
+#endif
+
+ return NGX_OK;
+}
+
+
+static ngx_inline size_t
+ngx_quic_max_udp_payload(ngx_connection_t *c)
+{
+ /* TODO: path MTU discovery */
+
+#if (NGX_HAVE_INET6)
+ if (c->sockaddr->sa_family == AF_INET6) {
+ return NGX_QUIC_MAX_UDP_PAYLOAD_OUT6;
+ }
+#endif
+
+ return NGX_QUIC_MAX_UDP_PAYLOAD_OUT;
+}
+
+
+static void
+ngx_quic_input_handler(ngx_event_t *rev)
+{
+ ssize_t n;
+ ngx_buf_t b;
+ ngx_connection_t *c;
+ ngx_quic_connection_t *qc;
+ static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
+
+ ngx_memzero(&b, sizeof(ngx_buf_t));
+ b.start = buf;
+ b.end = buf + sizeof(buf);
+ b.pos = b.last = b.start;
+ b.memory = 1;
+
+ c = rev->data;
+ qc = c->quic;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0, "quic input handler");
+
+ if (rev->timedout) {
+ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
+ "quic client timed out");
+ ngx_quic_close_connection(c, NGX_DONE);
+ return;
+ }
+
+ if (c->close) {
+ qc->error_reason = "graceful shutdown";
+ ngx_quic_close_connection(c, NGX_OK);
+ return;
+ }
+
+ n = c->recv(c, b.start, b.end - b.start);
+
+ if (n == NGX_AGAIN) {
+ if (qc->closing) {
+ ngx_quic_close_connection(c, NGX_OK);
+ }
+ return;
+ }
+
+ if (n == NGX_ERROR) {
+ c->read->eof = 1;
+ ngx_quic_close_connection(c, NGX_ERROR);
+ return;
+ }
+
+ b.last += n;
+ qc->received += n;
+
+ if (ngx_quic_input(c, &b) != NGX_OK) {
+ ngx_quic_close_connection(c, NGX_ERROR);
+ return;
+ }
+
+ qc->send_timer_set = 0;
+ ngx_add_timer(rev, qc->tp.max_idle_timeout);
+}
+
+
+static void
+ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc)
+{
+ ngx_pool_t *pool;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic ngx_quic_close_connection, rc: %i", rc);
+
+ if (!c->quic || !c->quic->initialized) {
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic close connection early error");
+
+ } else if (ngx_quic_close_quic(c, rc) == NGX_AGAIN) {
+ return;
+ }
+
+ if (c->ssl) {
+ (void) ngx_ssl_shutdown(c);
+ }
+
+ if (c->read->timer_set) {
+ ngx_del_timer(c->read);
+ }
+
+#if (NGX_STAT_STUB)
+ (void) ngx_atomic_fetch_add(ngx_stat_active, -1);
+#endif
+
+ c->destroyed = 1;
+
+ pool = c->pool;
+
+ ngx_close_connection(c);
+
+ ngx_destroy_pool(pool);
+}
+
+
+static ngx_int_t
+ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc)
+{
+ ngx_uint_t i;
+ ngx_quic_send_ctx_t *ctx;
+ ngx_quic_connection_t *qc;
+
+ qc = c->quic;
+
+ if (!qc->closing) {
+
+ /* drop packets from retransmit queues, no ack is expected */
+ for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) {
+ ctx = ngx_quic_get_send_ctx(qc, i);
+ ngx_quic_free_frames(c, &ctx->sent);
+ }
+
+ if (rc == NGX_DONE) {
+
+ /*
+ * 10.2. Idle Timeout
+ *
+ * If the idle timeout is enabled by either peer, a connection is
+ * silently closed and its state is discarded when it remains idle
+ */
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic closing %s connection",
+ qc->draining ? "drained" : "idle");
+
+ } else {
+
+ /*
+ * 10.3. Immediate Close
+ *
+ * An endpoint sends a CONNECTION_CLOSE frame (Section 19.19)
+ * to terminate the connection immediately.
+ */
+
+ qc->error_level = c->ssl ? SSL_quic_read_level(c->ssl->connection)
+ : ssl_encryption_initial;
+
+ if (rc == NGX_OK) {
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic immediate close, drain = %d",
+ qc->draining);
+
+ qc->close.log = c->log;
+ qc->close.data = c;
+ qc->close.handler = ngx_quic_close_timer_handler;
+ qc->close.cancelable = 1;
+
+ ctx = ngx_quic_get_send_ctx(qc, qc->error_level);
+
+ ngx_add_timer(&qc->close, 3 * ngx_quic_pto(c, ctx));
+
+ qc->error = NGX_QUIC_ERR_NO_ERROR;
+
+ } else {
+ if (qc->error == 0 && !qc->error_app) {
+ qc->error = NGX_QUIC_ERR_INTERNAL_ERROR;
+ }
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic immediate close due to %serror: %ui %s",
+ qc->error_app ? "app " : "", qc->error,
+ qc->error_reason ? qc->error_reason : "");
+ }
+
+ (void) ngx_quic_send_cc(c);
+
+ if (qc->error_level == ssl_encryption_handshake) {
+ /* for clients that might not have handshake keys */
+ qc->error_level = ssl_encryption_initial;
+ (void) ngx_quic_send_cc(c);
+ }
+ }
+
+ qc->closing = 1;
+ }
+
+ if (rc == NGX_ERROR && qc->close.timer_set) {
+ /* do not wait for timer in case of fatal error */
+ ngx_del_timer(&qc->close);
+ }
+
+ if (ngx_quic_close_streams(c, qc) == NGX_AGAIN) {
+ return NGX_AGAIN;
+ }
+
+ if (qc->close.timer_set) {
+ return NGX_AGAIN;
+ }
+
+ for (i = 0; i < NGX_QUIC_ENCRYPTION_LAST; i++) {
+ ngx_quic_free_frames(c, &qc->crypto[i].frames);
+ }
+
+ for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) {
+ ngx_quic_free_frames(c, &qc->send_ctx[i].frames);
+ ngx_quic_free_frames(c, &qc->send_ctx[i].sent);
+ }
+
+ if (qc->push.timer_set) {
+ ngx_del_timer(&qc->push);
+ }
+
+ if (qc->pto.timer_set) {
+ ngx_del_timer(&qc->pto);
+ }
+
+ if (qc->push.posted) {
+ ngx_delete_posted_event(&qc->push);
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic part of connection is terminated");
+
+ /* may be tested from SSL callback during SSL shutdown */
+ c->quic = NULL;
+
+ return NGX_OK;
+}
+
+
+void
+ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err,
+ const char *reason)
+{
+ ngx_quic_connection_t *qc;
+
+ qc = c->quic;
+ qc->error = err;
+ qc->error_reason = reason;
+ qc->error_app = 1;
+ qc->error_ftype = 0;
+
+ ngx_quic_close_connection(c, NGX_ERROR);
+}
+
+
+static void
+ngx_quic_close_timer_handler(ngx_event_t *ev)
+{
+ ngx_connection_t *c;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic close timer");
+
+ c = ev->data;
+ ngx_quic_close_connection(c, NGX_DONE);
+}
+
+
+static ngx_int_t
+ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc)
+{
+ ngx_event_t *rev, *wev;
+ ngx_rbtree_t *tree;
+ ngx_rbtree_node_t *node;
+ ngx_quic_stream_t *qs;
+
+#if (NGX_DEBUG)
+ ngx_uint_t ns;
+#endif
+
+ tree = &qc->streams.tree;
+
+ if (tree->root == tree->sentinel) {
+ return NGX_OK;
+ }
+
+#if (NGX_DEBUG)
+ ns = 0;
+#endif
+
+ for (node = ngx_rbtree_min(tree->root, tree->sentinel);
+ node;
+ node = ngx_rbtree_next(tree, node))
+ {
+ qs = (ngx_quic_stream_t *) node;
+
+ rev = qs->c->read;
+ rev->error = 1;
+ rev->ready = 1;
+
+ wev = qs->c->write;
+ wev->error = 1;
+ wev->ready = 1;
+
+ ngx_post_event(rev, &ngx_posted_events);
+
+ if (rev->timer_set) {
+ ngx_del_timer(rev);
+ }
+
+#if (NGX_DEBUG)
+ ns++;
+#endif
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic connection has %ui active streams", ns);
+
+ return NGX_AGAIN;
+}
+
+
+static ngx_int_t
+ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b)
+{
+ u_char *p;
+ ngx_int_t rc;
+ ngx_quic_header_t pkt;
+
+ p = b->pos;
+
+ while (p < b->last) {
+ c->log->action = "processing quic packet";
+
+ ngx_memzero(&pkt, sizeof(ngx_quic_header_t));
+ pkt.raw = b;
+ pkt.data = p;
+ pkt.len = b->last - p;
+ pkt.log = c->log;
+ pkt.flags = p[0];
+
+ if (c->quic->in_retry) {
+ return ngx_quic_retry_input(c, &pkt);
+ }
+
+ /* TODO: check current state */
+ if (ngx_quic_long_pkt(pkt.flags)) {
+
+ if (ngx_quic_pkt_in(pkt.flags)) {
+ rc = ngx_quic_initial_input(c, &pkt);
+
+ } else if (ngx_quic_pkt_hs(pkt.flags)) {
+ rc = ngx_quic_handshake_input(c, &pkt);
+
+ } else if (ngx_quic_pkt_zrtt(pkt.flags)) {
+ rc = ngx_quic_early_input(c, &pkt);
+
+ } else {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "quic unknown long packet type");
+ return NGX_ERROR;
+ }
+
+ } else {
+ rc = ngx_quic_app_input(c, &pkt);
+ }
+
+ if (rc == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ /* NGX_OK || NGX_DECLINED */
+
+ /*
+ * we get NGX_DECLINED when there are no keys [yet] available
+ * to decrypt packet.
+ * Instead of queueing it, we ignore it and rely on the sender's
+ * retransmission:
+ *
+ * 12.2. Coalescing Packets:
+ *
+ * For example, if decryption fails (because the keys are
+ * not available or any other reason), the receiver MAY either
+ * discard or buffer the packet for later processing and MUST
+ * attempt to process the remaining packets.
+ */
+
+ /* b->pos is at header end, adjust by actual packet length */
+ b->pos += pkt.len;
+ p = ngx_quic_skip_zero_padding(b);
+ }
+
+ return NGX_OK;
+}
+
+
+/* firefox workaround: skip zero padding at the end of quic packet */
+static ngx_inline u_char *
+ngx_quic_skip_zero_padding(ngx_buf_t *b)
+{
+ while (b->pos < b->last && *(b->pos) == 0) {
+ b->pos++;
+ }
+
+ return b->pos;
+}
+
+
+static ngx_int_t
+ngx_quic_retry_input(ngx_connection_t *c, ngx_quic_header_t *pkt)
+{
+ ngx_int_t rc;
+ ngx_quic_secrets_t *keys;
+ ngx_quic_send_ctx_t *ctx;
+ ngx_quic_connection_t *qc;
+ static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
+
+ c->log->action = "retrying quic connection";
+
+ if (ngx_buf_size(pkt->raw) < NGX_QUIC_MIN_INITIAL_SIZE) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "quic UDP datagram is too small for initial packet");
+ return NGX_OK;
+ }
+
+ rc = ngx_quic_parse_long_header(pkt);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ if (ngx_quic_pkt_zrtt(pkt->flags)) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "quic discard inflight 0-RTT packet");
+ return NGX_OK;
+ }
+
+ if (!ngx_quic_pkt_in(pkt->flags)) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "quic invalid initial packet: 0x%xd", pkt->flags);
+ return NGX_ERROR;
+ }
+
+ if (ngx_quic_parse_initial_header(pkt) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_quic_new_dcid(c, &pkt->dcid) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ qc = c->quic;
+ qc->tp.initial_scid = c->quic->dcid;
+
+ keys = &c->quic->keys[ssl_encryption_initial];
+
+ if (ngx_quic_set_initial_secret(c->pool, &keys->client, &keys->server,
+ &qc->odcid)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ c->quic->in_retry = 0;
+
+ if (ngx_quic_validate_token(c, pkt) != NGX_OK) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid token");
+ return NGX_ERROR;
+ }
+
+ qc->validated = 1;
+
+ pkt->secret = &keys->client;
+ pkt->level = ssl_encryption_initial;
+ pkt->plaintext = buf;
+
+ ctx = ngx_quic_get_send_ctx(qc, pkt->level);
+
+ if (ngx_quic_decrypt(pkt, NULL, &ctx->largest_pn) != NGX_OK) {
+ qc->error = pkt->error;
+ return NGX_ERROR;
+ }
+
+ if (ngx_quic_init_connection(c) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_quic_payload_handler(c, pkt) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ /* pos is at header end, adjust by actual packet length */
+ pkt->raw->pos += pkt->len;
+
+ (void) ngx_quic_skip_zero_padding(pkt->raw);
+
+ return ngx_quic_input(c, pkt->raw);
+}
+
+
+static ngx_int_t
+ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt)
+{
+ ngx_int_t rc;
+ ngx_ssl_conn_t *ssl_conn;
+ ngx_quic_secrets_t *keys;
+ ngx_quic_send_ctx_t *ctx;
+ static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
+
+ c->log->action = "processing initial quic packet";
+
+ ssl_conn = c->ssl->connection;
+
+ rc = ngx_quic_parse_long_header(pkt);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ if (ngx_quic_parse_initial_header(pkt) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ keys = &c->quic->keys[ssl_encryption_initial];
+
+ pkt->secret = &keys->client;
+ pkt->level = ssl_encryption_initial;
+ pkt->plaintext = buf;
+
+ ctx = ngx_quic_get_send_ctx(c->quic, pkt->level);
+
+ if (ngx_quic_decrypt(pkt, ssl_conn, &ctx->largest_pn) != NGX_OK) {
+ c->quic->error = pkt->error;
+ return NGX_ERROR;
+ }
+
+ return ngx_quic_payload_handler(c, pkt);
+}
+
+
+static ngx_int_t
+ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt)
+{
+ ngx_int_t rc;
+ ngx_queue_t *q;
+ ngx_quic_frame_t *f;
+ ngx_quic_secrets_t *keys;
+ ngx_quic_send_ctx_t *ctx;
+ ngx_quic_connection_t *qc;
+ static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
+
+ c->log->action = "processing handshake quic packet";
+
+ qc = c->quic;
+
+ keys = &c->quic->keys[ssl_encryption_handshake];
+
+ if (keys->client.key.len == 0) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "quic no read keys yet, packet ignored");
+ return NGX_DECLINED;
+ }
+
+ /* extract cleartext data into pkt */
+ rc = ngx_quic_parse_long_header(pkt);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ if (ngx_quic_check_peer(qc, pkt) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_quic_parse_handshake_header(pkt) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ pkt->secret = &keys->client;
+ pkt->level = ssl_encryption_handshake;
+ pkt->plaintext = buf;
+
+ ctx = ngx_quic_get_send_ctx(qc, pkt->level);
+
+ if (ngx_quic_decrypt(pkt, c->ssl->connection, &ctx->largest_pn) != NGX_OK) {
+ qc->error = pkt->error;
+ return NGX_ERROR;
+ }
+
+ /*
+ * 4.10.1. The successful use of Handshake packets indicates
+ * that no more Initial packets need to be exchanged
+ */
+ ctx = ngx_quic_get_send_ctx(c->quic, ssl_encryption_initial);
+
+ while (!ngx_queue_empty(&ctx->sent)) {
+ q = ngx_queue_head(&ctx->sent);
+ ngx_queue_remove(q);
+
+ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
+ ngx_quic_congestion_ack(c, f);
+ ngx_quic_free_frame(c, f);
+ }
+
+ qc->validated = 1;
+ qc->pto_count = 0;
+
+ return ngx_quic_payload_handler(c, pkt);
+}
+
+
+static ngx_int_t
+ngx_quic_early_input(ngx_connection_t *c, ngx_quic_header_t *pkt)
+{
+ ngx_int_t rc;
+ ngx_quic_secrets_t *keys;
+ ngx_quic_send_ctx_t *ctx;
+ ngx_quic_connection_t *qc;
+ static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
+
+ c->log->action = "processing early data quic packet";
+
+ qc = c->quic;
+
+ /* extract cleartext data into pkt */
+ rc = ngx_quic_parse_long_header(pkt);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ if (ngx_quic_check_peer(qc, pkt) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_quic_parse_handshake_header(pkt) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ keys = &c->quic->keys[ssl_encryption_early_data];
+
+ if (keys->client.key.len == 0) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "quic no 0-RTT keys yet, packet ignored");
+ return NGX_DECLINED;
+ }
+
+
+ pkt->secret = &keys->client;
+ pkt->level = ssl_encryption_early_data;
+ pkt->plaintext = buf;
+
+ ctx = ngx_quic_get_send_ctx(qc, pkt->level);
+
+ if (ngx_quic_decrypt(pkt, c->ssl->connection, &ctx->largest_pn) != NGX_OK) {
+ qc->error = pkt->error;
+ return NGX_ERROR;
+ }
+
+ return ngx_quic_payload_handler(c, pkt);
+}
+
+
+static ngx_int_t
+ngx_quic_check_peer(ngx_quic_connection_t *qc, ngx_quic_header_t *pkt)
+{
+ ngx_str_t *dcid;
+
+ dcid = ngx_quic_pkt_zrtt(pkt->flags) ? &qc->odcid : &qc->dcid;
+
+ if (pkt->dcid.len != dcid->len) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic unexpected quic dcidl");
+ return NGX_ERROR;
+ }
+
+ if (ngx_memcmp(pkt->dcid.data, dcid->data, dcid->len) != 0) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic unexpected quic dcid");
+ return NGX_ERROR;
+ }
+
+ if (pkt->scid.len != qc->scid.len) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic unexpected quic scidl");
+ return NGX_ERROR;
+ }
+
+ if (ngx_memcmp(pkt->scid.data, qc->scid.data, qc->scid.len) != 0) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic unexpected quic scid");
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt)
+{
+ ngx_int_t rc;
+ ngx_quic_secrets_t *keys, *next, tmp;
+ ngx_quic_send_ctx_t *ctx;
+ ngx_quic_connection_t *qc;
+ static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
+
+ c->log->action = "processing application data quic packet";
+
+ qc = c->quic;
+
+ keys = &c->quic->keys[ssl_encryption_application];
+ next = &c->quic->next_key;
+
+ if (keys->client.key.len == 0) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "quic no read keys yet, packet ignored");
+ return NGX_DECLINED;
+ }
+
+ rc = ngx_quic_parse_short_header(pkt, &qc->dcid);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ pkt->secret = &keys->client;
+ pkt->next = &next->client;
+ pkt->key_phase = c->quic->key_phase;
+ pkt->level = ssl_encryption_application;
+ pkt->plaintext = buf;
+
+ ctx = ngx_quic_get_send_ctx(qc, pkt->level);
+
+ rc = ngx_quic_decrypt(pkt, c->ssl->connection, &ctx->largest_pn);
+
+ if (rc != NGX_OK) {
+ qc->error = pkt->error;
+ return rc;
+ }
+
+ ngx_gettimeofday(&pkt->received);
+
+ /* switch keys on Key Phase change */
+
+ if (pkt->key_update) {
+ c->quic->key_phase ^= 1;
+
+ tmp = *keys;
+ *keys = *next;
+ *next = tmp;
+ }
+
+ rc = ngx_quic_payload_handler(c, pkt);
+
+ if (rc == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ /* generate next keys */
+
+ if (pkt->key_update) {
+ if (ngx_quic_key_update(c, keys, next) != NGX_OK) {
+ return NGX_ERROR;
+ }
+ }
+
+ return rc;
+}
+
+
+static ngx_int_t
+ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt)
+{
+ u_char *end, *p;
+ ssize_t len;
+ ngx_uint_t ack_sent, do_close;
+ ngx_quic_frame_t frame;
+ ngx_quic_connection_t *qc;
+
+ qc = c->quic;
+
+ if (qc->closing) {
+ /*
+ * 10.1 Closing and Draining Connection States
+ * ... delayed or reordered packets are properly discarded.
+ *
+ * An endpoint retains only enough information to generate
+ * a packet containing a CONNECTION_CLOSE frame and to identify
+ * packets as belonging to the connection.
+ */
+
+ qc->error_level = pkt->level;
+ qc->error = NGX_QUIC_ERR_NO_ERROR;
+ qc->error_reason = "connection is closing, packet discarded";
+ qc->error_ftype = 0;
+ qc->error_app = 0;
+
+ return ngx_quic_send_cc(c);
+ }
+
+ p = pkt->payload.data;
+ end = p + pkt->payload.len;
+
+ ack_sent = 0;
+ do_close = 0;
+
+ while (p < end) {
+
+ c->log->action = "parsing frames";
+
+ len = ngx_quic_parse_frame(pkt, p, end, &frame);
+
+ if (len < 0) {
+ qc->error = pkt->error;
+ return NGX_ERROR;
+ }
+
+ c->log->action = "handling frames";
+
+ p += len;
+
+ switch (frame.type) {
+
+ case NGX_QUIC_FT_ACK:
+ if (ngx_quic_handle_ack_frame(c, pkt, &frame.u.ack) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ continue;
+
+ case NGX_QUIC_FT_PADDING:
+ /* no action required */
+ continue;
+
+ case NGX_QUIC_FT_CONNECTION_CLOSE:
+ case NGX_QUIC_FT_CONNECTION_CLOSE_APP:
+ do_close = 1;
+ continue;
+ }
+
+ /* got there with ack-eliciting packet */
+
+ if (!ack_sent) {
+ if (ngx_quic_send_ack(c, pkt) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ ack_sent = 1;
+ }
+
+ switch (frame.type) {
+
+ case NGX_QUIC_FT_CRYPTO:
+
+ if (ngx_quic_handle_crypto_frame(c, pkt, &frame) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ break;
+
+ case NGX_QUIC_FT_PING:
+ break;
+
+ case NGX_QUIC_FT_STREAM0:
+ case NGX_QUIC_FT_STREAM1:
+ case NGX_QUIC_FT_STREAM2:
+ case NGX_QUIC_FT_STREAM3:
+ case NGX_QUIC_FT_STREAM4:
+ case NGX_QUIC_FT_STREAM5:
+ case NGX_QUIC_FT_STREAM6:
+ case NGX_QUIC_FT_STREAM7:
+
+ if (ngx_quic_handle_stream_frame(c, pkt, &frame) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ break;
+
+ case NGX_QUIC_FT_MAX_DATA:
+
+ if (ngx_quic_handle_max_data_frame(c, &frame.u.max_data) != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ break;
+
+ case NGX_QUIC_FT_STREAMS_BLOCKED:
+ case NGX_QUIC_FT_STREAMS_BLOCKED2:
+
+ if (ngx_quic_handle_streams_blocked_frame(c, pkt,
+ &frame.u.streams_blocked)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ break;
+
+ case NGX_QUIC_FT_STREAM_DATA_BLOCKED:
+
+ if (ngx_quic_handle_stream_data_blocked_frame(c, pkt,
+ &frame.u.stream_data_blocked)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ break;
+
+ case NGX_QUIC_FT_MAX_STREAM_DATA:
+
+ if (ngx_quic_handle_max_stream_data_frame(c, pkt,
+ &frame.u.max_stream_data)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ break;
+
+ case NGX_QUIC_FT_RESET_STREAM:
+
+ if (ngx_quic_handle_reset_stream_frame(c, pkt,
+ &frame.u.reset_stream)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ break;
+
+ case NGX_QUIC_FT_STOP_SENDING:
+
+ if (ngx_quic_handle_stop_sending_frame(c, pkt,
+ &frame.u.stop_sending)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ break;
+
+ case NGX_QUIC_FT_MAX_STREAMS:
+ case NGX_QUIC_FT_MAX_STREAMS2:
+
+ if (ngx_quic_handle_max_streams_frame(c, pkt, &frame.u.max_streams)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ break;
+
+ case NGX_QUIC_FT_NEW_CONNECTION_ID:
+ case NGX_QUIC_FT_RETIRE_CONNECTION_ID:
+ case NGX_QUIC_FT_PATH_CHALLENGE:
+ case NGX_QUIC_FT_PATH_RESPONSE:
+
+ /* TODO: handle */
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic frame handler not implemented");
+ break;
+
+ default:
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic missing frame handler");
+ return NGX_ERROR;
+ }
+ }
+
+ if (p != end) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "quic trailing garbage in payload: %ui bytes", end - p);
+
+ qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR;
+ return NGX_ERROR;
+ }
+
+ if (do_close) {
+ qc->draining = 1;
+ ngx_quic_close_connection(c, NGX_OK);
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_header_t *pkt)
+{
+ ngx_quic_frame_t *frame;
+
+ c->log->action = "generating acknowledgment";
+
+ /* every ACK-eliciting packet is acknowledged, TODO ACK Ranges */
+
+ frame = ngx_quic_alloc_frame(c, 0);
+ if (frame == NULL) {
+ return NGX_ERROR;
+ }
+
+ frame->level = (pkt->level == ssl_encryption_early_data)
+ ? ssl_encryption_application
+ : pkt->level;
+
+ frame->type = NGX_QUIC_FT_ACK;
+ frame->u.ack.largest = pkt->pn;
+ frame->u.ack.delay = ngx_quic_ack_delay(c, &pkt->received, frame->level);
+
+ ngx_sprintf(frame->info, "ACK for PN=%uL from frame handler level=%d",
+ pkt->pn, frame->level);
+ ngx_quic_queue_frame(c->quic, frame);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_quic_ack_delay(ngx_connection_t *c, struct timeval *received,
+ enum ssl_encryption_level_t level)
+{
+ ngx_int_t ack_delay;
+ struct timeval tv;
+
+ ack_delay = 0;
+
+ if (level == ssl_encryption_application) {
+ ngx_gettimeofday(&tv);
+ ack_delay = (tv.tv_sec - received->tv_sec) * 1000000
+ + tv.tv_usec - received->tv_usec;
+ ack_delay >>= c->quic->ctp.ack_delay_exponent;
+ }
+
+ return ack_delay;
+}
+
+
+static ngx_int_t
+ngx_quic_send_cc(ngx_connection_t *c)
+{
+ ngx_quic_frame_t *frame;
+ ngx_quic_connection_t *qc;
+
+ qc = c->quic;
+
+ if (qc->draining) {
+ return NGX_OK;
+ }
+
+ if (qc->closing
+ && ngx_current_msec - qc->last_cc < NGX_QUIC_CC_MIN_INTERVAL)
+ {
+ /* dot not send CC too often */
+ return NGX_OK;
+ }
+
+ frame = ngx_quic_alloc_frame(c, 0);
+ if (frame == NULL) {
+ return NGX_ERROR;
+ }
+
+ frame->level = qc->error_level;
+ frame->type = NGX_QUIC_FT_CONNECTION_CLOSE;
+ frame->u.close.error_code = qc->error;
+ frame->u.close.frame_type = qc->error_ftype;
+ frame->u.close.app = qc->error_app;
+
+ if (qc->error_reason) {
+ frame->u.close.reason.len = ngx_strlen(qc->error_reason);
+ frame->u.close.reason.data = (u_char *) qc->error_reason;
+ }
+
+ ngx_snprintf(frame->info, sizeof(frame->info) - 1,
+ "CONNECTION_CLOSE%s err:%ui level:%d ft:%ui reason:\"%s\"",
+ qc->error_app ? "_APP" : "", qc->error, qc->error_level,
+ qc->error_ftype, qc->error_reason ? qc->error_reason : "-");
+
+ ngx_quic_queue_frame(c->quic, frame);
+
+ qc->last_cc = ngx_current_msec;
+
+ return ngx_quic_output(c);
+}
+
+
+static ngx_int_t
+ngx_quic_send_new_token(ngx_connection_t *c)
+{
+ ngx_str_t token;
+ ngx_quic_frame_t *frame;
+
+ if (!c->quic->conf->retry) {
+ return NGX_OK;
+ }
+
+ if (ngx_quic_new_token(c, &token) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ frame = ngx_quic_alloc_frame(c, 0);
+ if (frame == NULL) {
+ return NGX_ERROR;
+ }
+
+ frame->level = ssl_encryption_application;
+ frame->type = NGX_QUIC_FT_NEW_TOKEN;
+ frame->u.token.length = token.len;
+ frame->u.token.data = token.data;
+ ngx_sprintf(frame->info, "NEW_TOKEN");
+ ngx_quic_queue_frame(c->quic, frame);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt,
+ ngx_quic_ack_frame_t *ack)
+{
+ ssize_t n;
+ u_char *pos, *end;
+ uint64_t min, max, gap, range;
+ ngx_msec_t send_time;
+ ngx_uint_t i;
+ ngx_quic_send_ctx_t *ctx;
+ ngx_quic_connection_t *qc;
+
+ qc = c->quic;
+
+ ctx = ngx_quic_get_send_ctx(qc, pkt->level);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic ngx_quic_handle_ack_frame level %d", pkt->level);
+
+ /*
+ * If any computed packet number is negative, an endpoint MUST
+ * generate a connection error of type FRAME_ENCODING_ERROR.
+ * (19.3.1)
+ */
+
+ if (ack->first_range > ack->largest) {
+ qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR;
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "quic invalid first range in ack frame");
+ return NGX_ERROR;
+ }
+
+ min = ack->largest - ack->first_range;
+ max = ack->largest;
+
+ if (ngx_quic_handle_ack_frame_range(c, ctx, min, max, &send_time)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ /* 13.2.3. Receiver Tracking of ACK Frames */
+ if (ctx->largest_ack < max) {
+ ctx->largest_ack = max;
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic updated largest received ack: %uL", max);
+
+ /*
+ * An endpoint generates an RTT sample on receiving an
+ * ACK frame that meets the following two conditions:
+ *
+ * - the largest acknowledged packet number is newly acknowledged
+ * - at least one of the newly acknowledged packets was ack-eliciting.
+ */
+
+ if (send_time != NGX_TIMER_INFINITE) {
+ ngx_quic_rtt_sample(c, ack, pkt->level, send_time);
+ }
+ }
+
+ pos = ack->ranges_start;
+ end = ack->ranges_end;
+
+ for (i = 0; i < ack->range_count; i++) {
+
+ n = ngx_quic_parse_ack_range(pkt, pos, end, &gap, &range);
+ if (n == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+ pos += n;
+
+ if (gap + 2 > min) {
+ qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR;
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "quic invalid range %ui in ack frame", i);
+ return NGX_ERROR;
+ }
+
+ max = min - gap - 2;
+
+ if (range > max) {
+ qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR;
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "quic invalid range %ui in ack frame", i);
+ return NGX_ERROR;
+ }
+
+ min = max - range;
+
+ if (ngx_quic_handle_ack_frame_range(c, ctx, min, max, &send_time)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+ }
+
+ return ngx_quic_detect_lost(c, 1);
+}
+
+
+static ngx_int_t
+ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx,
+ uint64_t min, uint64_t max, ngx_msec_t *send_time)
+{
+ uint64_t found_num;
+ ngx_uint_t found;
+ ngx_queue_t *q;
+ ngx_quic_frame_t *f;
+ ngx_quic_connection_t *qc;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic handle ack range: min:%uL max:%uL", min, max);
+
+ qc = c->quic;
+
+ *send_time = NGX_TIMER_INFINITE;
+ found = 0;
+ found_num = 0;
+
+ q = ngx_queue_last(&ctx->sent);
+
+ while (q != ngx_queue_sentinel(&ctx->sent)) {
+
+ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
+ q = ngx_queue_prev(q);
+
+ if (f->pnum >= min && f->pnum <= max) {
+ ngx_quic_congestion_ack(c, f);
+
+ ngx_quic_handle_stream_ack(c, f);
+
+ if (f->pnum > found_num || !found) {
+ *send_time = f->last;
+ found_num = f->pnum;
+ }
+
+ ngx_queue_remove(&f->queue);
+ ngx_quic_free_frame(c, f);
+ found = 1;
+ }
+ }
+
+ if (!found) {
+
+ if (max < ctx->pnum) {
+ /* duplicate ACK or ACK for non-ack-eliciting frame */
+ return NGX_OK;
+ }
+
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "quic ACK for the packet not sent");
+
+ qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION;
+ qc->error_ftype = NGX_QUIC_FT_ACK;
+ qc->error_reason = "unknown packet number";
+
+ return NGX_ERROR;
+ }
+
+ if (!qc->push.timer_set) {
+ ngx_post_event(&qc->push, &ngx_posted_events);
+ }
+
+ qc->pto_count = 0;
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack,
+ enum ssl_encryption_level_t level, ngx_msec_t send_time)
+{
+ ngx_msec_t latest_rtt, ack_delay, adjusted_rtt, rttvar_sample;
+ ngx_quic_connection_t *qc;
+
+ qc = c->quic;
+
+ latest_rtt = ngx_current_msec - send_time;
+ qc->latest_rtt = latest_rtt;
+
+ if (qc->min_rtt == NGX_TIMER_INFINITE) {
+ qc->min_rtt = latest_rtt;
+ qc->avg_rtt = latest_rtt;
+ qc->rttvar = latest_rtt / 2;
+
+ } else {
+ qc->min_rtt = ngx_min(qc->min_rtt, latest_rtt);
+
+
+ if (level == ssl_encryption_application) {
+ ack_delay = ack->delay * (1 << qc->ctp.ack_delay_exponent) / 1000;
+ ack_delay = ngx_min(ack_delay, qc->ctp.max_ack_delay);
+
+ } else {
+ ack_delay = 0;
+ }
+
+ adjusted_rtt = latest_rtt;
+
+ if (qc->min_rtt + ack_delay < latest_rtt) {
+ adjusted_rtt -= ack_delay;
+ }
+
+ qc->avg_rtt = 0.875 * qc->avg_rtt + 0.125 * adjusted_rtt;
+ rttvar_sample = ngx_abs((ngx_msec_int_t) (qc->avg_rtt - adjusted_rtt));
+ qc->rttvar = 0.75 * qc->rttvar + 0.25 * rttvar_sample;
+ }
+
+ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic rtt sample: latest %M, min %M, avg %M, var %M",
+ latest_rtt, qc->min_rtt, qc->avg_rtt, qc->rttvar);
+}
+
+
+static ngx_inline ngx_msec_t
+ngx_quic_pto(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx)
+{
+ ngx_msec_t duration;
+ ngx_quic_connection_t *qc;
+
+ qc = c->quic;
+
+ /* PTO calculation: quic-recovery, Appendix 8 */
+ duration = qc->avg_rtt;
+
+ duration += ngx_max(4 * qc->rttvar, NGX_QUIC_TIME_GRANULARITY);
+ duration <<= qc->pto_count;
+
+ if (qc->congestion.in_flight == 0) { /* no in-flight packets */
+ return duration;
+ }
+
+ if (ctx == &qc->send_ctx[2] && c->ssl->handshaked) {
+ /* application send space */
+
+ duration += qc->tp.max_ack_delay << qc->pto_count;
+ }
+
+ return duration;
+}
+
+
+static void
+ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f)
+{
+ uint64_t sent, unacked;
+ ngx_event_t *wev;
+ ngx_quic_stream_t *sn;
+ ngx_quic_connection_t *qc;
+
+ if (f->type < NGX_QUIC_FT_STREAM0 || f->type > NGX_QUIC_FT_STREAM7) {
+ return;
+ }
+
+ qc = c->quic;
+
+ sn = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id);
+ if (sn == NULL) {
+ return;
+ }
+
+ wev = sn->c->write;
+ sent = sn->c->sent;
+ unacked = sent - sn->acked;
+
+ if (unacked >= NGX_QUIC_STREAM_BUFSIZE && wev->active) {
+ wev->ready = 1;
+ ngx_post_event(wev, &ngx_posted_events);
+ }
+
+ sn->acked += f->u.stream.length;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, sn->c->log, 0,
+ "quic stream ack %uL acked:%uL, unacked:%uL",
+ f->u.stream.length, sn->acked, sent - sn->acked);
+}
+
+
+static ngx_int_t
+ngx_quic_handle_ordered_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs,
+ ngx_quic_frame_t *frame, ngx_quic_frame_handler_pt handler, void *data)
+{
+ size_t full_len;
+ ngx_int_t rc;
+ ngx_queue_t *q;
+ ngx_quic_ordered_frame_t *f;
+
+ f = &frame->u.ord;
+
+ if (f->offset > fs->received) {
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic out-of-order frame: expecting %uL got %uL",
+ fs->received, f->offset);
+
+ return ngx_quic_buffer_frame(c, fs, frame);
+ }
+
+ if (f->offset < fs->received) {
+
+ if (ngx_quic_adjust_frame_offset(c, frame, fs->received)
+ == NGX_DONE)
+ {
+ /* old/duplicate data range */
+ return NGX_OK;
+ }
+
+ /* intersecting data range, frame modified */
+ }
+
+ /* f->offset == fs->received */
+
+ rc = handler(c, frame, data);
+ if (rc == NGX_ERROR) {
+ return NGX_ERROR;
+
+ } else if (rc == NGX_DONE) {
+ /* handler destroyed stream, queue no longer exists */
+ return NGX_OK;
+ }
+
+ /* rc == NGX_OK */
+
+ fs->received += f->length;
+
+ /* now check the queue if we can continue with buffered frames */
+
+ do {
+ q = ngx_queue_head(&fs->frames);
+ if (q == ngx_queue_sentinel(&fs->frames)) {
+ break;
+ }
+
+ frame = ngx_queue_data(q, ngx_quic_frame_t, queue);
+ f = &frame->u.ord;
+
+ if (f->offset > fs->received) {
+ /* gap found, nothing more to do */
+ break;
+ }
+
+ full_len = f->length;
+
+ if (f->offset < fs->received) {
+
+ if (ngx_quic_adjust_frame_offset(c, frame, fs->received)
+ == NGX_DONE)
+ {
+ /* old/duplicate data range */
+ ngx_queue_remove(q);
+ fs->total -= f->length;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic skipped buffered frame, total %ui",
+ fs->total);
+ ngx_quic_free_frame(c, frame);
+ continue;
+ }
+
+ /* frame was adjusted, proceed to input */
+ }
+
+ /* f->offset == fs->received */
+
+ rc = handler(c, frame, data);
+
+ if (rc == NGX_ERROR) {
+ return NGX_ERROR;
+
+ } else if (rc == NGX_DONE) {
+ /* handler destroyed stream, queue no longer exists */
+ return NGX_OK;
+ }
+
+ fs->received += f->length;
+ fs->total -= full_len;
+
+ ngx_queue_remove(q);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic consumed buffered frame, total %ui", fs->total);
+
+ ngx_quic_free_frame(c, frame);
+
+ } while (1);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_quic_adjust_frame_offset(ngx_connection_t *c, ngx_quic_frame_t *frame,
+ uint64_t offset_in)
+{
+ size_t tail;
+ ngx_quic_ordered_frame_t *f;
+
+ f = &frame->u.ord;
+
+ tail = offset_in - f->offset;
+
+ if (tail >= f->length) {
+ /* range preceeding already received data or duplicate, ignore */
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic old or duplicate data in ordered frame, ignored");
+ return NGX_DONE;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic adjusted ordered frame data start to expected offset");
+
+ /* intersecting range: adjust data size */
+
+ f->offset += tail;
+ f->data += tail;
+ f->length -= tail;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_quic_buffer_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs,
+ ngx_quic_frame_t *frame)
+{
+ u_char *data;
+ ngx_queue_t *q;
+ ngx_quic_frame_t *dst, *item;
+ ngx_quic_ordered_frame_t *f, *df;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic ngx_quic_buffer_frame");
+
+ f = &frame->u.ord;
+
+ /* frame start offset is in the future, buffer it */
+
+ /* check limit on total size used by all buffered frames, not actual data */
+ if (NGX_QUIC_MAX_BUFFERED - fs->total < f->length) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "quic ordered input buffer limit exceeded");
+ return NGX_ERROR;
+ }
+
+ dst = ngx_quic_alloc_frame(c, f->length);
+ if (dst == NULL) {
+ return NGX_ERROR;
+ }
+
+ data = dst->data;
+ ngx_memcpy(dst, frame, sizeof(ngx_quic_frame_t));
+ dst->data = data;
+
+ ngx_memcpy(dst->data, f->data, f->length);
+
+ df = &dst->u.ord;
+ df->data = dst->data;
+
+ fs->total += f->length;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic ordered frame with unexpected offset:"
+ " buffered, total %ui", fs->total);
+
+ /* TODO: do we need some timeout for this queue ? */
+
+ if (ngx_queue_empty(&fs->frames)) {
+ ngx_queue_insert_after(&fs->frames, &dst->queue);
+ return NGX_OK;
+ }
+
+ for (q = ngx_queue_last(&fs->frames);
+ q != ngx_queue_sentinel(&fs->frames);
+ q = ngx_queue_prev(q))
+ {
+ item = ngx_queue_data(q, ngx_quic_frame_t, queue);
+ f = &item->u.ord;
+
+ if (f->offset < df->offset) {
+ ngx_queue_insert_after(q, &dst->queue);
+ return NGX_OK;
+ }
+ }
+
+ ngx_queue_insert_after(&fs->frames, &dst->queue);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt,
+ ngx_quic_frame_t *frame)
+{
+ ngx_quic_connection_t *qc;
+ ngx_quic_frames_stream_t *fs;
+
+ qc = c->quic;
+ fs = &qc->crypto[pkt->level];
+
+ return ngx_quic_handle_ordered_frame(c, fs, frame, ngx_quic_crypto_input,
+ NULL);
+}
+
+
+static ngx_int_t
+ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data)
+{
+ int n, sslerr;
+ ngx_queue_t *q;
+ ngx_ssl_conn_t *ssl_conn;
+ ngx_quic_send_ctx_t *ctx;
+ ngx_quic_crypto_frame_t *f;
+
+ f = &frame->u.crypto;
+
+ ssl_conn = c->ssl->connection;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic SSL_quic_read_level: %d, SSL_quic_write_level: %d",
+ (int) SSL_quic_read_level(ssl_conn),
+ (int) SSL_quic_write_level(ssl_conn));
+
+ if (!SSL_provide_quic_data(ssl_conn, SSL_quic_read_level(ssl_conn),
+ f->data, f->length))
+ {
+ ngx_ssl_error(NGX_LOG_INFO, c->log, 0,
+ "SSL_provide_quic_data() failed");
+ return NGX_ERROR;
+ }
+
+ n = SSL_do_handshake(ssl_conn);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n);
+
+ if (n == -1) {
+ sslerr = SSL_get_error(ssl_conn, n);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d",
+ sslerr);
+
+ if (sslerr != SSL_ERROR_WANT_READ) {
+ ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_do_handshake() failed");
+ return NGX_ERROR;
+ }
+
+ } else if (n == 1 && !SSL_in_init(ssl_conn)) {
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic ssl cipher: %s", SSL_get_cipher(ssl_conn));
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic handshake completed successfully");
+
+ c->ssl->handshaked = 1;
+
+ frame = ngx_quic_alloc_frame(c, 0);
+ if (frame == NULL) {
+ return NGX_ERROR;
+ }
+
+ /* 12.4 Frames and frame types, figure 8 */
+ frame->level = ssl_encryption_application;
+ frame->type = NGX_QUIC_FT_HANDSHAKE_DONE;
+ ngx_sprintf(frame->info, "HANDSHAKE DONE on handshake completed");
+ ngx_quic_queue_frame(c->quic, frame);
+
+ if (ngx_quic_send_new_token(c) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ /*
+ * Generating next keys before a key update is received.
+ * See quic-tls 9.4 Header Protection Timing Side-Channels.
+ */
+
+ if (ngx_quic_key_update(c, &c->quic->keys[ssl_encryption_application],
+ &c->quic->next_key)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ /*
+ * 4.10.2 An endpoint MUST discard its handshake keys
+ * when the TLS handshake is confirmed
+ */
+ ctx = ngx_quic_get_send_ctx(c->quic, ssl_encryption_handshake);
+
+ while (!ngx_queue_empty(&ctx->sent)) {
+ q = ngx_queue_head(&ctx->sent);
+ ngx_queue_remove(q);
+
+ frame = ngx_queue_data(q, ngx_quic_frame_t, queue);
+ ngx_quic_congestion_ack(c, frame);
+ ngx_quic_free_frame(c, frame);
+ }
+
+ c->quic->pto_count = 0;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic SSL_quic_read_level: %d, SSL_quic_write_level: %d",
+ (int) SSL_quic_read_level(ssl_conn),
+ (int) SSL_quic_write_level(ssl_conn));
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt,
+ ngx_quic_frame_t *frame)
+{
+ ngx_pool_t *pool;
+ ngx_connection_t *sc;
+ ngx_quic_stream_t *sn;
+ ngx_quic_connection_t *qc;
+ ngx_quic_stream_frame_t *f;
+ ngx_quic_frames_stream_t *fs;
+
+ qc = c->quic;
+ f = &frame->u.stream;
+
+ if ((f->stream_id & NGX_QUIC_STREAM_UNIDIRECTIONAL)
+ && (f->stream_id & NGX_QUIC_STREAM_SERVER_INITIATED))
+ {
+ qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR;
+ return NGX_ERROR;
+ }
+
+ sn = ngx_quic_find_stream(&qc->streams.tree, f->stream_id);
+
+ if (sn == NULL) {
+ sn = ngx_quic_create_client_stream(c, f->stream_id);
+
+ if (sn == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (sn == NGX_QUIC_STREAM_GONE) {
+ return NGX_OK;
+ }
+
+ sc = sn->c;
+ fs = &sn->fs;
+
+ if (ngx_quic_handle_ordered_frame(c, fs, frame, ngx_quic_stream_input,
+ sn)
+ != NGX_OK)
+ {
+ pool = sc->pool;
+
+ ngx_close_connection(sc);
+ ngx_destroy_pool(pool);
+
+ return NGX_ERROR;
+ }
+
+ sc->listening->handler(sc);
+
+ return NGX_OK;
+ }
+
+ fs = &sn->fs;
+
+ return ngx_quic_handle_ordered_frame(c, fs, frame, ngx_quic_stream_input,
+ sn);
+}
+
+
+static ngx_int_t
+ngx_quic_stream_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data)
+{
+ uint64_t id;
+ ngx_buf_t *b;
+ ngx_event_t *rev;
+ ngx_quic_stream_t *sn;
+ ngx_quic_connection_t *qc;
+ ngx_quic_stream_frame_t *f;
+
+ qc = c->quic;
+ sn = data;
+
+ f = &frame->u.stream;
+ id = f->stream_id;
+
+ b = sn->b;
+
+ if ((size_t) ((b->pos - b->start) + (b->end - b->last)) < f->length) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "quic no space in stream buffer");
+ return NGX_ERROR;
+ }
+
+ if ((size_t) (b->end - b->last) < f->length) {
+ b->last = ngx_movemem(b->start, b->pos, b->last - b->pos);
+ b->pos = b->start;
+ }
+
+ b->last = ngx_cpymem(b->last, f->data, f->length);
+
+ rev = sn->c->read;
+ rev->ready = 1;
+
+ if (f->fin) {
+ rev->pending_eof = 1;
+ }
+
+ if (rev->active) {
+ rev->handler(rev);
+ }
+
+ /* check if stream was destroyed by handler */
+ if (ngx_quic_find_stream(&qc->streams.tree, id) == NULL) {
+ return NGX_DONE;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_quic_handle_max_data_frame(ngx_connection_t *c,
+ ngx_quic_max_data_frame_t *f)
+{
+ ngx_event_t *wev;
+ ngx_rbtree_t *tree;
+ ngx_rbtree_node_t *node;
+ ngx_quic_stream_t *qs;
+ ngx_quic_connection_t *qc;
+
+ qc = c->quic;
+ tree = &qc->streams.tree;
+
+ if (f->max_data <= qc->streams.send_max_data) {
+ return NGX_OK;
+ }
+
+ if (qc->streams.sent >= qc->streams.send_max_data) {
+
+ for (node = ngx_rbtree_min(tree->root, tree->sentinel);
+ node;
+ node = ngx_rbtree_next(tree, node))
+ {
+ qs = (ngx_quic_stream_t *) node;
+ wev = qs->c->write;
+
+ if (wev->active) {
+ wev->ready = 1;
+ ngx_post_event(wev, &ngx_posted_events);
+ }
+ }
+ }
+
+ qc->streams.send_max_data = f->max_data;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c,
+ ngx_quic_header_t *pkt, ngx_quic_streams_blocked_frame_t *f)
+{
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c,
+ ngx_quic_header_t *pkt, ngx_quic_stream_data_blocked_frame_t *f)
+{
+ size_t n;
+ ngx_buf_t *b;
+ ngx_quic_frame_t *frame;
+ ngx_quic_stream_t *sn;
+ ngx_quic_connection_t *qc;
+
+ qc = c->quic;
+
+ if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL)
+ && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED))
+ {
+ qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR;
+ return NGX_ERROR;
+ }
+
+ sn = ngx_quic_find_stream(&qc->streams.tree, f->id);
+
+ if (sn == NULL) {
+ sn = ngx_quic_create_client_stream(c, f->id);
+
+ if (sn == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (sn == NGX_QUIC_STREAM_GONE) {
+ return NGX_OK;
+ }
+
+ b = sn->b;
+ n = b->end - b->last;
+
+ sn->c->listening->handler(sn->c);
+
+ } else {
+ b = sn->b;
+ n = sn->fs.received + (b->pos - b->start) + (b->end - b->last);
+ }
+
+ frame = ngx_quic_alloc_frame(c, 0);
+ if (frame == NULL) {
+ return NGX_ERROR;
+ }
+
+ frame->level = pkt->level;
+ frame->type = NGX_QUIC_FT_MAX_STREAM_DATA;
+ frame->u.max_stream_data.id = f->id;
+ frame->u.max_stream_data.limit = n;
+
+ ngx_sprintf(frame->info, "MAX_STREAM_DATA id:0x%xL limit:%uL level=%d",
+ frame->u.max_stream_data.id,
+ frame->u.max_stream_data.limit,
+ frame->level);
+
+ ngx_quic_queue_frame(c->quic, frame);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c,
+ ngx_quic_header_t *pkt, ngx_quic_max_stream_data_frame_t *f)
+{
+ uint64_t sent;
+ ngx_event_t *wev;
+ ngx_quic_stream_t *sn;
+ ngx_quic_connection_t *qc;
+
+ qc = c->quic;
+
+ if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL)
+ && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0)
+ {
+ qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR;
+ return NGX_ERROR;
+ }
+
+ sn = ngx_quic_find_stream(&qc->streams.tree, f->id);
+
+ if (sn == NULL) {
+ sn = ngx_quic_create_client_stream(c, f->id);
+
+ if (sn == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (sn == NGX_QUIC_STREAM_GONE) {
+ return NGX_OK;
+ }
+
+ if (f->limit > sn->send_max_data) {
+ sn->send_max_data = f->limit;
+ }
+
+ sn->c->listening->handler(sn->c);
+
+ return NGX_OK;
+ }
+
+ if (f->limit <= sn->send_max_data) {
+ return NGX_OK;
+ }
+
+ sent = sn->c->sent;
+
+ if (sent >= sn->send_max_data) {
+ wev = sn->c->write;
+
+ if (wev->active) {
+ wev->ready = 1;
+ ngx_post_event(wev, &ngx_posted_events);
+ }
+ }
+
+ sn->send_max_data = f->limit;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_quic_handle_reset_stream_frame(ngx_connection_t *c,
+ ngx_quic_header_t *pkt, ngx_quic_reset_stream_frame_t *f)
+{
+ ngx_event_t *rev;
+ ngx_connection_t *sc;
+ ngx_quic_stream_t *sn;
+ ngx_quic_connection_t *qc;
+
+ qc = c->quic;
+
+ if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL)
+ && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED))
+ {
+ qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR;
+ return NGX_ERROR;
+ }
+
+ sn = ngx_quic_find_stream(&qc->streams.tree, f->id);
+
+ if (sn == NULL) {
+ sn = ngx_quic_create_client_stream(c, f->id);
+
+ if (sn == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (sn == NGX_QUIC_STREAM_GONE) {
+ return NGX_OK;
+ }
+
+ sc = sn->c;
+
+ rev = sc->read;
+ rev->error = 1;
+ rev->ready = 1;
+
+ sc->listening->handler(sc);
+
+ return NGX_OK;
+ }
+
+ rev = sn->c->read;
+ rev->error = 1;
+ rev->ready = 1;
+
+ if (rev->active) {
+ rev->handler(rev);
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_quic_handle_stop_sending_frame(ngx_connection_t *c,
+ ngx_quic_header_t *pkt, ngx_quic_stop_sending_frame_t *f)
+{
+ ngx_event_t *wev;
+ ngx_connection_t *sc;
+ ngx_quic_stream_t *sn;
+ ngx_quic_connection_t *qc;
+
+ qc = c->quic;
+
+ if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL)
+ && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0)
+ {
+ qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR;
+ return NGX_ERROR;
+ }
+
+ sn = ngx_quic_find_stream(&qc->streams.tree, f->id);
+
+ if (sn == NULL) {
+ sn = ngx_quic_create_client_stream(c, f->id);
+
+ if (sn == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (sn == NGX_QUIC_STREAM_GONE) {
+ return NGX_OK;
+ }
+
+ sc = sn->c;
+
+ wev = sc->write;
+ wev->error = 1;
+ wev->ready = 1;
+
+ sc->listening->handler(sc);
+
+ return NGX_OK;
+ }
+
+ wev = sn->c->write;
+ wev->error = 1;
+ wev->ready = 1;
+
+ if (wev->active) {
+ wev->handler(wev);
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_quic_handle_max_streams_frame(ngx_connection_t *c,
+ ngx_quic_header_t *pkt, ngx_quic_max_streams_frame_t *f)
+{
+ ngx_quic_connection_t *qc;
+
+ qc = c->quic;
+
+ if (f->bidi) {
+ if (qc->streams.server_max_streams_bidi < f->limit) {
+ qc->streams.server_max_streams_bidi = f->limit;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic max_streams_bidi:%uL", f->limit);
+ }
+
+ } else {
+ if (qc->streams.server_max_streams_uni < f->limit) {
+ qc->streams.server_max_streams_uni = f->limit;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic max_streams_uni:%uL", f->limit);
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame)
+{
+ ngx_quic_send_ctx_t *ctx;
+
+ ctx = ngx_quic_get_send_ctx(qc, frame->level);
+
+ ngx_queue_insert_tail(&ctx->frames, &frame->queue);
+
+ frame->len = ngx_quic_create_frame(NULL, frame);
+ /* always succeeds */
+
+ ctx->frames_len += frame->len;
+
+ if (qc->closing) {
+ return;
+ }
+
+ /* TODO: TCP_NODELAY analogue ? TCP_CORK and others... */
+
+ if (ctx->frames_len < NGX_QUIC_MIN_DATA_NODELAY) {
+ if (!qc->push.timer_set) {
+ ngx_add_timer(&qc->push, qc->tp.max_ack_delay);
+ }
+
+ } else {
+ ngx_post_event(&qc->push, &ngx_posted_events);
+ }
+}
+
+
+static ngx_int_t
+ngx_quic_output(ngx_connection_t *c)
+{
+ ngx_uint_t i;
+ ngx_quic_connection_t *qc;
+
+ c->log->action = "sending frames";
+
+ qc = c->quic;
+
+ for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) {
+ if (ngx_quic_output_frames(c, &qc->send_ctx[i]) != NGX_OK) {
+ return NGX_ERROR;
+ }
+ }
+
+ if (!qc->send_timer_set && !qc->closing) {
+ qc->send_timer_set = 1;
+ ngx_add_timer(c->read, qc->tp.max_idle_timeout);
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_quic_output_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx)
+{
+ size_t len, hlen;
+ ngx_uint_t need_ack;
+ ngx_queue_t *q, range;
+ ngx_quic_frame_t *f;
+ ngx_quic_congestion_t *cg;
+ ngx_quic_connection_t *qc;
+
+ qc = c->quic;
+ cg = &qc->congestion;
+
+ if (ngx_queue_empty(&ctx->frames)) {
+ return NGX_OK;
+ }
+
+ q = ngx_queue_head(&ctx->frames);
+ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
+
+ /* all frames in same send_ctx share same level */
+ hlen = (f->level == ssl_encryption_application) ? NGX_QUIC_MAX_SHORT_HEADER
+ : NGX_QUIC_MAX_LONG_HEADER;
+ hlen += EVP_GCM_TLS_TAG_LEN;
+
+ do {
+ len = 0;
+ need_ack = 0;
+ ngx_queue_init(&range);
+
+ do {
+ /* process group of frames that fits into packet */
+ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
+
+ if (len && hlen + len + f->len > qc->ctp.max_udp_payload_size) {
+ break;
+ }
+
+ if (f->need_ack) {
+ need_ack = 1;
+ }
+
+ if (need_ack && cg->in_flight + len + f->len > cg->window) {
+ break;
+ }
+
+ if (!qc->validated) {
+ /*
+ * Prior to validation, endpoints are limited in what they
+ * are able to send. During the handshake, a server cannot
+ * send more than three times the data it receives;
+ */
+
+ if (((c->sent + len + f->len) / 3) > qc->received) {
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic hit amplification limit"
+ " received %uz sent %O",
+ qc->received, c->sent);
+ break;
+ }
+ }
+
+ q = ngx_queue_next(q);
+
+ f->first = ngx_current_msec;
+
+ ngx_queue_remove(&f->queue);
+ ngx_queue_insert_tail(&range, &f->queue);
+ ctx->frames_len -= f->len;
+
+ len += f->len;
+
+ } while (q != ngx_queue_sentinel(&ctx->frames));
+
+ if (ngx_queue_empty(&range)) {
+ break;
+ }
+
+ if (ngx_quic_send_frames(c, ctx, &range) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ } while (q != ngx_queue_sentinel(&ctx->frames));
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames)
+{
+ ngx_queue_t *q;
+ ngx_quic_frame_t *f;
+
+ do {
+ q = ngx_queue_head(frames);
+
+ if (q == ngx_queue_sentinel(frames)) {
+ break;
+ }
+
+ ngx_queue_remove(q);
+
+ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
+
+ ngx_quic_free_frame(c, f);
+ } while (1);
+}
+
+
+static ngx_int_t
+ngx_quic_send_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx,
+ ngx_queue_t *frames)
+{
+ ssize_t len;
+ u_char *p;
+ ngx_msec_t now;
+ ngx_str_t out, res;
+ ngx_queue_t *q;
+ ngx_ssl_conn_t *ssl_conn;
+ ngx_quic_frame_t *f, *start;
+ ngx_quic_header_t pkt;
+ ngx_quic_secrets_t *keys;
+ ngx_quic_connection_t *qc;
+ static ngx_str_t initial_token = ngx_null_string;
+ static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
+ static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic ngx_quic_send_frames");
+
+ ssl_conn = c->ssl ? c->ssl->connection : NULL;
+
+ q = ngx_queue_head(frames);
+ start = ngx_queue_data(q, ngx_quic_frame_t, queue);
+
+ ngx_memzero(&pkt, sizeof(ngx_quic_header_t));
+
+ now = ngx_current_msec;
+
+ p = src;
+ out.data = src;
+
+ for (q = ngx_queue_head(frames);
+ q != ngx_queue_sentinel(frames);
+ q = ngx_queue_next(q))
+ {
+ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic frame out: %s", f->info);
+
+ len = ngx_quic_create_frame(p, f);
+ if (len == -1) {
+ return NGX_ERROR;
+ }
+
+ if (f->need_ack) {
+ pkt.need_ack = 1;
+ }
+
+ p += len;
+ f->pnum = ctx->pnum;
+ f->last = now;
+ }
+
+ out.len = p - out.data;
+
+ while (out.len < 4) {
+ *p++ = NGX_QUIC_FT_PADDING;
+ out.len++;
+ }
+
+ qc = c->quic;
+
+ keys = &c->quic->keys[start->level];
+
+ pkt.secret = &keys->server;
+
+ pkt.flags = NGX_QUIC_PKT_FIXED_BIT;
+
+ if (start->level == ssl_encryption_initial) {
+ pkt.flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_INITIAL;
+ pkt.token = initial_token;
+
+ } else if (start->level == ssl_encryption_handshake) {
+ pkt.flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_HANDSHAKE;
+
+ } else {
+ if (c->quic->key_phase) {
+ pkt.flags |= NGX_QUIC_PKT_KPHASE;
+ }
+ }
+
+ ngx_quic_set_packet_number(&pkt, ctx);
+
+ pkt.log = c->log;
+ pkt.level = start->level;
+ pkt.dcid = qc->scid;
+ pkt.scid = qc->dcid;
+ pkt.payload = out;
+
+ res.data = dst;
+
+ ngx_log_debug6(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic packet ready: %ui bytes at level %d"
+ " need_ack: %d number: %L encoded %d:0x%xD",
+ out.len, start->level, pkt.need_ack, pkt.number,
+ pkt.num_len, pkt.trunc);
+
+ if (ngx_quic_encrypt(&pkt, ssl_conn, &res) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+#ifdef NGX_QUIC_DEBUG_PACKETS
+ ngx_quic_hexdump(c->log, "quic packet to send", res.data, res.len);
+#endif
+
+ len = c->send(c, res.data, res.len);
+ if (len == NGX_ERROR || (size_t) len != res.len) {
+ return NGX_ERROR;
+ }
+
+ /* len == NGX_OK || NGX_AGAIN */
+ ctx->pnum++;
+
+ if (pkt.need_ack) {
+ /* move frames into the sent queue to wait for ack */
+
+ if (qc->closing) {
+ /* if we are closing, any ack will be discarded */
+ ngx_quic_free_frames(c, frames);
+
+ } else {
+ ngx_queue_add(&ctx->sent, frames);
+ if (qc->pto.timer_set) {
+ ngx_del_timer(&qc->pto);
+ }
+ ngx_add_timer(&qc->pto, ngx_quic_pto(c, ctx));
+
+ start->plen = len;
+ }
+
+ qc->congestion.in_flight += len;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic congestion send if:%uz",
+ qc->congestion.in_flight);
+ } else {
+ /* no ack is expected for this frames, so we can free them */
+ ngx_quic_free_frames(c, frames);
+ }
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_quic_set_packet_number(ngx_quic_header_t *pkt, ngx_quic_send_ctx_t *ctx)
+{
+ uint64_t delta;
+
+ delta = ctx->pnum - ctx->largest_ack;
+ pkt->number = ctx->pnum;
+
+ if (delta <= 0x7F) {
+ pkt->num_len = 1;
+ pkt->trunc = ctx->pnum & 0xff;
+
+ } else if (delta <= 0x7FFF) {
+ pkt->num_len = 2;
+ pkt->flags |= 0x1;
+ pkt->trunc = ctx->pnum & 0xffff;
+
+ } else if (delta <= 0x7FFFFF) {
+ pkt->num_len = 3;
+ pkt->flags |= 0x2;
+ pkt->trunc = ctx->pnum & 0xffffff;
+
+ } else {
+ pkt->num_len = 4;
+ pkt->flags |= 0x3;
+ pkt->trunc = ctx->pnum & 0xffffffff;
+ }
+}
+
+
+static void
+ngx_quic_pto_handler(ngx_event_t *ev)
+{
+ ngx_connection_t *c;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic pto timer");
+
+ c = ev->data;
+
+ if (ngx_quic_detect_lost(c, 0) != NGX_OK) {
+ ngx_quic_close_connection(c, NGX_ERROR);
+ return;
+ }
+
+ c->quic->pto_count++;
+}
+
+
+static void
+ngx_quic_push_handler(ngx_event_t *ev)
+{
+ ngx_connection_t *c;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic push timer");
+
+ c = ev->data;
+
+ if (ngx_quic_output(c) != NGX_OK) {
+ ngx_quic_close_connection(c, NGX_ERROR);
+ return;
+ }
+}
+
+
+static ngx_int_t
+ngx_quic_detect_lost(ngx_connection_t *c, ngx_uint_t ack)
+{
+ uint64_t pn;
+ ngx_uint_t i;
+ ngx_msec_t now, wait, min_wait, thr;
+ ngx_queue_t *q, range;
+ ngx_quic_frame_t *f, *start;
+ ngx_quic_send_ctx_t *ctx;
+ ngx_quic_connection_t *qc;
+
+ qc = c->quic;
+ now = ngx_current_msec;
+
+ min_wait = 0;
+
+#if (NGX_SUPPRESS_WARN)
+ thr = 0;
+#endif
+
+ if (ack) {
+ thr = NGX_QUIC_TIME_THR * ngx_max(qc->latest_rtt, qc->avg_rtt);
+ thr = ngx_max(thr, NGX_QUIC_TIME_GRANULARITY);
+ }
+
+ for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) {
+
+ ctx = &qc->send_ctx[i];
+
+ if (ngx_queue_empty(&ctx->sent)) {
+ continue;
+ }
+
+ if (!ack) {
+ thr = ngx_quic_pto(c, ctx);
+ }
+
+ q = ngx_queue_head(&ctx->sent);
+
+ do {
+ start = ngx_queue_data(q, ngx_quic_frame_t, queue);
+
+ wait = start->last + thr - now;
+
+ if ((ngx_msec_int_t) wait >= 0) {
+
+ if (min_wait == 0 || wait < min_wait) {
+ min_wait = wait;
+ }
+
+ if (!ack) {
+ break;
+ }
+
+ if ((start->pnum > ctx->largest_ack)
+ || ((ctx->largest_ack - start->pnum) < NGX_QUIC_PKT_THR))
+ {
+ break;
+ }
+ }
+
+ pn = start->pnum;
+
+ ngx_queue_init(&range);
+
+ /* send frames with same packet number to the wire */
+ do {
+ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
+
+ if (f->pnum != pn) {
+ break;
+ }
+
+ q = ngx_queue_next(q);
+
+ ngx_queue_remove(&f->queue);
+ ngx_queue_insert_tail(&range, &f->queue);
+
+ } while (q != ngx_queue_sentinel(&ctx->sent));
+
+ ngx_quic_congestion_lost(c, start);
+
+ if (ngx_quic_send_frames(c, ctx, &range) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ } while (q != ngx_queue_sentinel(&ctx->sent));
+ }
+
+ if (qc->pto.timer_set) {
+ ngx_del_timer(&qc->pto);
+ }
+
+ if (min_wait > 0) {
+ ngx_add_timer(&qc->pto, min_wait);
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_connection_t *
+ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi)
+{
+ size_t rcvbuf_size;
+ uint64_t id;
+ ngx_quic_stream_t *qs, *sn;
+ ngx_quic_connection_t *qc;
+
+ qs = c->qs;
+ qc = qs->parent->quic;
+
+ if (bidi) {
+ if (qc->streams.server_streams_bidi
+ >= qc->streams.server_max_streams_bidi)
+ {
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic too many server bidi streams: %uL",
+ qc->streams.server_streams_bidi);
+ return NULL;
+ }
+
+ id = (qc->streams.server_streams_bidi << 2)
+ | NGX_QUIC_STREAM_SERVER_INITIATED;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic creating server bidi stream %uL/%uL id:0x%xL",
+ qc->streams.server_streams_bidi,
+ qc->streams.server_max_streams_bidi, id);
+
+ qc->streams.server_streams_bidi++;
+ rcvbuf_size = qc->tp.initial_max_stream_data_bidi_local;
+
+ } else {
+ if (qc->streams.server_streams_uni
+ >= qc->streams.server_max_streams_uni)
+ {
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic too many server uni streams: %uL",
+ qc->streams.server_streams_uni);
+ return NULL;
+ }
+
+ id = (qc->streams.server_streams_uni << 2)
+ | NGX_QUIC_STREAM_SERVER_INITIATED
+ | NGX_QUIC_STREAM_UNIDIRECTIONAL;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic creating server uni stream %uL/%uL id:0x%xL",
+ qc->streams.server_streams_uni,
+ qc->streams.server_max_streams_uni, id);
+
+ qc->streams.server_streams_uni++;
+ rcvbuf_size = 0;
+ }
+
+ sn = ngx_quic_create_stream(qs->parent, id, rcvbuf_size);
+ if (sn == NULL) {
+ return NULL;
+ }
+
+ return sn->c;
+}
+
+
+static void
+ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp,
+ ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
+{
+ ngx_rbtree_node_t **p;
+ ngx_quic_stream_t *qn, *qnt;
+
+ for ( ;; ) {
+ qn = (ngx_quic_stream_t *) node;
+ qnt = (ngx_quic_stream_t *) temp;
+
+ p = (qn->id < qnt->id) ? &temp->left : &temp->right;
+
+ if (*p == sentinel) {
+ break;
+ }
+
+ temp = *p;
+ }
+
+ *p = node;
+ node->parent = temp;
+ node->left = sentinel;
+ node->right = sentinel;
+ ngx_rbt_red(node);
+}
+
+
+static ngx_quic_stream_t *
+ngx_quic_find_stream(ngx_rbtree_t *rbtree, uint64_t id)
+{
+ ngx_rbtree_node_t *node, *sentinel;
+ ngx_quic_stream_t *qn;
+
+ node = rbtree->root;
+ sentinel = rbtree->sentinel;
+
+ while (node != sentinel) {
+ qn = (ngx_quic_stream_t *) node;
+
+ if (id == qn->id) {
+ return qn;
+ }
+
+ node = (id < qn->id) ? node->left : node->right;
+ }
+
+ return NULL;
+}
+
+
+static ngx_quic_stream_t *
+ngx_quic_create_client_stream(ngx_connection_t *c, uint64_t id)
+{
+ size_t n;
+ uint64_t min_id;
+ ngx_quic_stream_t *sn;
+ ngx_quic_connection_t *qc;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic stream id 0x%xL is new", id);
+
+ qc = c->quic;
+
+ if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) {
+
+ if (id & NGX_QUIC_STREAM_SERVER_INITIATED) {
+ if ((id >> 2) < qc->streams.server_streams_uni) {
+ return NGX_QUIC_STREAM_GONE;
+ }
+
+ qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR;
+ return NULL;
+ }
+
+ if ((id >> 2) < qc->streams.client_streams_uni) {
+ return NGX_QUIC_STREAM_GONE;
+ }
+
+ if ((id >> 2) >= qc->streams.client_max_streams_uni) {
+ qc->error = NGX_QUIC_ERR_STREAM_LIMIT_ERROR;
+ return NULL;
+ }
+
+ min_id = (qc->streams.client_streams_uni << 2)
+ | NGX_QUIC_STREAM_UNIDIRECTIONAL;
+ qc->streams.client_streams_uni = (id >> 2) + 1;
+ n = qc->tp.initial_max_stream_data_uni;
+
+ } else {
+
+ if (id & NGX_QUIC_STREAM_SERVER_INITIATED) {
+ if ((id >> 2) < qc->streams.server_streams_bidi) {
+ return NGX_QUIC_STREAM_GONE;
+ }
+
+ qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR;
+ return NULL;
+ }
+
+ if ((id >> 2) < qc->streams.client_streams_bidi) {
+ return NGX_QUIC_STREAM_GONE;
+ }
+
+ if ((id >> 2) >= qc->streams.client_max_streams_bidi) {
+ qc->error = NGX_QUIC_ERR_STREAM_LIMIT_ERROR;
+ return NULL;
+ }
+
+ min_id = (qc->streams.client_streams_bidi << 2);
+ qc->streams.client_streams_bidi = (id >> 2) + 1;
+ n = qc->tp.initial_max_stream_data_bidi_remote;
+ }
+
+ if (n < NGX_QUIC_STREAM_BUFSIZE) {
+ n = NGX_QUIC_STREAM_BUFSIZE;
+ }
+
+ /*
+ * 2.1. Stream Types and Identifiers
+ *
+ * Within each type, streams are created with numerically increasing
+ * stream IDs. A stream ID that is used out of order results in all
+ * streams of that type with lower-numbered stream IDs also being
+ * opened.
+ */
+
+ for ( /* void */ ; min_id < id; min_id += 0x04) {
+
+ sn = ngx_quic_create_stream(c, min_id, n);
+ if (sn == NULL) {
+ return NULL;
+ }
+
+ sn->c->listening->handler(sn->c);
+ }
+
+ return ngx_quic_create_stream(c, id, n);
+}
+
+
+static ngx_quic_stream_t *
+ngx_quic_create_stream(ngx_connection_t *c, uint64_t id, size_t rcvbuf_size)
+{
+ ngx_log_t *log;
+ ngx_pool_t *pool;
+ ngx_quic_stream_t *sn;
+ ngx_pool_cleanup_t *cln;
+ ngx_quic_connection_t *qc;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic stream id 0x%xL create", id);
+
+ qc = c->quic;
+
+ pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, c->log);
+ if (pool == NULL) {
+ return NULL;
+ }
+
+ sn = ngx_pcalloc(pool, sizeof(ngx_quic_stream_t));
+ if (sn == NULL) {
+ ngx_destroy_pool(pool);
+ return NULL;
+ }
+
+ sn->node.key = id;
+ sn->parent = c;
+ sn->id = id;
+
+ sn->b = ngx_create_temp_buf(pool, rcvbuf_size);
+ if (sn->b == NULL) {
+ ngx_destroy_pool(pool);
+ return NULL;
+ }
+
+ ngx_queue_init(&sn->fs.frames);
+
+ log = ngx_palloc(pool, sizeof(ngx_log_t));
+ if (log == NULL) {
+ ngx_destroy_pool(pool);
+ return NULL;
+ }
+
+ *log = *c->log;
+ pool->log = log;
+
+ sn->c = ngx_get_connection(-1, log);
+ if (sn->c == NULL) {
+ ngx_destroy_pool(pool);
+ return NULL;
+ }
+
+ sn->c->qs = sn;
+ sn->c->type = SOCK_STREAM;
+ sn->c->pool = pool;
+ sn->c->ssl = c->ssl;
+ sn->c->sockaddr = c->sockaddr;
+ sn->c->listening = c->listening;
+ sn->c->addr_text = c->addr_text;
+ sn->c->local_sockaddr = c->local_sockaddr;
+ sn->c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1);
+
+ sn->c->recv = ngx_quic_stream_recv;
+ sn->c->send = ngx_quic_stream_send;
+ sn->c->send_chain = ngx_quic_stream_send_chain;
+
+ sn->c->read->log = log;
+ sn->c->write->log = log;
+
+ log->connection = sn->c->number;
+
+ if ((id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0
+ || (id & NGX_QUIC_STREAM_SERVER_INITIATED))
+ {
+ sn->c->write->ready = 1;
+ }
+
+ if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) {
+ if (id & NGX_QUIC_STREAM_SERVER_INITIATED) {
+ sn->send_max_data = qc->ctp.initial_max_stream_data_uni;
+ }
+
+ } else {
+ if (id & NGX_QUIC_STREAM_SERVER_INITIATED) {
+ sn->send_max_data = qc->ctp.initial_max_stream_data_bidi_remote;
+ } else {
+ sn->send_max_data = qc->ctp.initial_max_stream_data_bidi_local;
+ }
+ }
+
+ cln = ngx_pool_cleanup_add(pool, 0);
+ if (cln == NULL) {
+ ngx_close_connection(sn->c);
+ ngx_destroy_pool(pool);
+ return NULL;
+ }
+
+ cln->handler = ngx_quic_stream_cleanup_handler;
+ cln->data = sn->c;
+
+ ngx_rbtree_insert(&c->quic->streams.tree, &sn->node);
+
+ return sn;
+}
+
+
+static ssize_t
+ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size)
+{
+ ssize_t len;
+ ngx_buf_t *b;
+ ngx_event_t *rev;
+ ngx_connection_t *pc;
+ ngx_quic_frame_t *frame;
+ ngx_quic_stream_t *qs;
+ ngx_quic_connection_t *qc;
+
+ qs = c->qs;
+ b = qs->b;
+ pc = qs->parent;
+ qc = pc->quic;
+ rev = c->read;
+
+ if (rev->error) {
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic stream id 0x%xL recv: eof:%d, avail:%z",
+ qs->id, rev->pending_eof, b->last - b->pos);
+
+ if (b->pos == b->last) {
+ rev->ready = 0;
+
+ if (rev->pending_eof) {
+ rev->eof = 1;
+ return 0;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic stream id 0x%xL recv() not ready", qs->id);
+ return NGX_AGAIN;
+ }
+
+ len = ngx_min(b->last - b->pos, (ssize_t) size);
+
+ ngx_memcpy(buf, b->pos, len);
+
+ b->pos += len;
+ qc->streams.received += len;
+
+ if (b->pos == b->last) {
+ b->pos = b->start;
+ b->last = b->start;
+ rev->ready = rev->pending_eof;
+ }
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic stream id 0x%xL recv: %z of %uz", qs->id, len, size);
+
+ if (!rev->pending_eof) {
+ frame = ngx_quic_alloc_frame(pc, 0);
+ if (frame == NULL) {
+ return NGX_ERROR;
+ }
+
+ frame->level = ssl_encryption_application;
+ frame->type = NGX_QUIC_FT_MAX_STREAM_DATA;
+ frame->u.max_stream_data.id = qs->id;
+ frame->u.max_stream_data.limit = qs->fs.received + (b->pos - b->start)
+ + (b->end - b->last);
+
+ ngx_sprintf(frame->info,
+ "MAX_STREAM_DATA id:0x%xL limit:%uL l=%d on recv",
+ frame->u.max_stream_data.id,
+ frame->u.max_stream_data.limit,
+ frame->level);
+
+ ngx_quic_queue_frame(pc->quic, frame);
+ }
+
+ if ((qc->streams.recv_max_data / 2) < qc->streams.received) {
+
+ frame = ngx_quic_alloc_frame(pc, 0);
+
+ if (frame == NULL) {
+ return NGX_ERROR;
+ }
+
+ qc->streams.recv_max_data *= 2;
+
+ frame->level = ssl_encryption_application;
+ frame->type = NGX_QUIC_FT_MAX_DATA;
+ frame->u.max_data.max_data = qc->streams.recv_max_data;
+
+ ngx_sprintf(frame->info, "MAX_DATA max_data:%uL level=%d on recv",
+ frame->u.max_data.max_data, frame->level);
+
+ ngx_quic_queue_frame(pc->quic, frame);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic stream id 0x%xL recv: increased max data: %uL",
+ qs->id, qc->streams.recv_max_data);
+ }
+
+ return len;
+}
+
+
+static ssize_t
+ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size)
+{
+ ngx_buf_t b;
+ ngx_chain_t cl;
+
+ ngx_memzero(&b, sizeof(ngx_buf_t));
+
+ b.memory = 1;
+ b.pos = buf;
+ b.last = buf + size;
+
+ cl.buf = &b;
+ cl.next = NULL;
+
+ if (ngx_quic_stream_send_chain(c, &cl, 0) == NGX_CHAIN_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (b.pos == buf) {
+ return NGX_AGAIN;
+ }
+
+ return b.pos - buf;
+}
+
+
+static ngx_chain_t *
+ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit)
+{
+ u_char *p;
+ size_t n, max, max_frame, max_flow, max_limit, len;
+#if (NGX_DEBUG)
+ size_t sent;
+#endif
+ ngx_buf_t *b;
+#if (NGX_DEBUG)
+ ngx_uint_t nframes;
+#endif
+ ngx_event_t *wev;
+ ngx_chain_t *cl;
+ ngx_connection_t *pc;
+ ngx_quic_frame_t *frame;
+ ngx_quic_stream_t *qs;
+ ngx_quic_connection_t *qc;
+
+ qs = c->qs;
+ pc = qs->parent;
+ qc = pc->quic;
+ wev = c->write;
+
+ if (wev->error) {
+ return NGX_CHAIN_ERROR;
+ }
+
+ max_frame = ngx_quic_max_stream_frame(qc);
+ max_flow = ngx_quic_max_stream_flow(c);
+ max_limit = limit;
+
+#if (NGX_DEBUG)
+ sent = 0;
+ nframes = 0;
+#endif
+
+ for ( ;; ) {
+ max = ngx_min(max_frame, max_flow);
+
+ if (limit) {
+ max = ngx_min(max, max_limit);
+ }
+
+ for (cl = in, n = 0; in; in = in->next) {
+
+ if (!ngx_buf_in_memory(in->buf)) {
+ continue;
+ }
+
+ n += ngx_buf_size(in->buf);
+
+ if (n > max) {
+ n = max;
+ break;
+ }
+ }
+
+ if (n == 0) {
+ wev->ready = (max_flow ? 1 : 0);
+ break;
+ }
+
+ frame = ngx_quic_alloc_frame(pc, n);
+ if (frame == NULL) {
+ return NGX_CHAIN_ERROR;
+ }
+
+ frame->level = ssl_encryption_application;
+ frame->type = NGX_QUIC_FT_STREAM6; /* OFF=1 LEN=1 FIN=0 */
+ frame->u.stream.off = 1;
+ frame->u.stream.len = 1;
+ frame->u.stream.fin = 0;
+
+ frame->u.stream.type = frame->type;
+ frame->u.stream.stream_id = qs->id;
+ frame->u.stream.offset = c->sent;
+ frame->u.stream.length = n;
+ frame->u.stream.data = frame->data;
+
+ ngx_sprintf(frame->info, "STREAM id:0x%xL len:%uz level:%d",
+ qs->id, n, frame->level);
+
+ c->sent += n;
+ qc->streams.sent += n;
+ max_flow -= n;
+
+ if (limit) {
+ max_limit -= n;
+ }
+
+#if (NGX_DEBUG)
+ sent += n;
+ nframes++;
+#endif
+
+ for (p = frame->data; n > 0; cl = cl->next) {
+ b = cl->buf;
+
+ if (!ngx_buf_in_memory(b)) {
+ continue;
+ }
+
+ len = ngx_min(n, (size_t) (b->last - b->pos));
+ p = ngx_cpymem(p, b->pos, len);
+
+ b->pos += len;
+ n -= len;
+ }
+
+ ngx_quic_queue_frame(qc, frame);
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic send_chain sent:%uz, frames:%ui", sent, nframes);
+
+ return in;
+}
+
+
+static size_t
+ngx_quic_max_stream_frame(ngx_quic_connection_t *qc)
+{
+ /*
+ * we need to fit at least 1 frame into a packet, thus account head/tail;
+ * 25 = 1 + 8x3 is max header for STREAM frame, with 1 byte for frame type
+ */
+
+ return qc->ctp.max_udp_payload_size - NGX_QUIC_MAX_SHORT_HEADER - 25
+ - EVP_GCM_TLS_TAG_LEN;
+}
+
+
+static size_t
+ngx_quic_max_stream_flow(ngx_connection_t *c)
+{
+ size_t size;
+ uint64_t sent, unacked;
+ ngx_quic_stream_t *qs;
+ ngx_quic_connection_t *qc;
+
+ qs = c->qs;
+ qc = qs->parent->quic;
+
+ size = NGX_QUIC_STREAM_BUFSIZE;
+ sent = c->sent;
+ unacked = sent - qs->acked;
+
+ if (qc->streams.send_max_data == 0) {
+ qc->streams.send_max_data = qc->ctp.initial_max_data;
+ }
+
+ if (unacked >= NGX_QUIC_STREAM_BUFSIZE) {
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic send flow hit buffer size");
+ return 0;
+ }
+
+ if (unacked + size > NGX_QUIC_STREAM_BUFSIZE) {
+ size = NGX_QUIC_STREAM_BUFSIZE - unacked;
+ }
+
+ if (qc->streams.sent >= qc->streams.send_max_data) {
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic send flow hit MAX_DATA");
+ return 0;
+ }
+
+ if (qc->streams.sent + size > qc->streams.send_max_data) {
+ size = qc->streams.send_max_data - qc->streams.sent;
+ }
+
+ if (sent >= qs->send_max_data) {
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic send flow hit MAX_STREAM_DATA");
+ return 0;
+ }
+
+ if (sent + size > qs->send_max_data) {
+ size = qs->send_max_data - sent;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic send flow: %uz", size);
+
+ return size;
+}
+
+
+static void
+ngx_quic_stream_cleanup_handler(void *data)
+{
+ ngx_connection_t *c = data;
+
+ ngx_connection_t *pc;
+ ngx_quic_frame_t *frame;
+ ngx_quic_stream_t *qs;
+ ngx_quic_connection_t *qc;
+
+ qs = c->qs;
+ pc = qs->parent;
+ qc = pc->quic;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic stream id 0x%xL cleanup", qs->id);
+
+ ngx_rbtree_delete(&qc->streams.tree, &qs->node);
+ ngx_quic_free_frames(pc, &qs->fs.frames);
+
+ if (qc->closing) {
+ /* schedule handler call to continue ngx_quic_close_connection() */
+ ngx_post_event(pc->read, &ngx_posted_events);
+ return;
+ }
+
+ if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) {
+ frame = ngx_quic_alloc_frame(pc, 0);
+ if (frame == NULL) {
+ return;
+ }
+
+ frame->level = ssl_encryption_application;
+ frame->type = NGX_QUIC_FT_MAX_STREAMS;
+
+ if (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) {
+ frame->u.max_streams.limit = ++qc->streams.client_max_streams_uni;
+ frame->u.max_streams.bidi = 0;
+
+ } else {
+ frame->u.max_streams.limit = ++qc->streams.client_max_streams_bidi;
+ frame->u.max_streams.bidi = 1;
+ }
+
+ ngx_sprintf(frame->info, "MAX_STREAMS limit:%uL bidi:%ui level=%d",
+ frame->u.max_streams.limit,
+ frame->u.max_streams.bidi,
+ (int) frame->level);
+
+ ngx_quic_queue_frame(qc, frame);
+
+ if (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) {
+ /* do not send fin for client unidirectional streams */
+ return;
+ }
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic stream id 0x%xL send fin", qs->id);
+
+ frame = ngx_quic_alloc_frame(pc, 0);
+ if (frame == NULL) {
+ return;
+ }
+
+ frame->level = ssl_encryption_application;
+ frame->type = NGX_QUIC_FT_STREAM7; /* OFF=1 LEN=1 FIN=1 */
+ frame->u.stream.off = 1;
+ frame->u.stream.len = 1;
+ frame->u.stream.fin = 1;
+
+ frame->u.stream.type = frame->type;
+ frame->u.stream.stream_id = qs->id;
+ frame->u.stream.offset = c->sent;
+ frame->u.stream.length = 0;
+ frame->u.stream.data = NULL;
+
+ ngx_sprintf(frame->info, "stream 0x%xL fin=1 level=%d",
+ qs->id, frame->level);
+
+ ngx_quic_queue_frame(qc, frame);
+
+ (void) ngx_quic_output(pc);
+}
+
+
+static ngx_quic_frame_t *
+ngx_quic_alloc_frame(ngx_connection_t *c, size_t size)
+{
+ u_char *p;
+ ngx_queue_t *q;
+ ngx_quic_frame_t *frame;
+ ngx_quic_connection_t *qc;
+
+ if (size) {
+ p = ngx_alloc(size, c->log);
+ if (p == NULL) {
+ return NULL;
+ }
+
+ } else {
+ p = NULL;
+ }
+
+ qc = c->quic;
+
+ if (!ngx_queue_empty(&qc->free_frames)) {
+
+ q = ngx_queue_head(&qc->free_frames);
+ frame = ngx_queue_data(q, ngx_quic_frame_t, queue);
+
+ ngx_queue_remove(&frame->queue);
+
+#ifdef NGX_QUIC_DEBUG_FRAMES_ALLOC
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic reuse frame n:%ui", qc->nframes);
+#endif
+
+ } else {
+ frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t));
+ if (frame == NULL) {
+ ngx_free(p);
+ return NULL;
+ }
+
+#if (NGX_DEBUG)
+ ++qc->nframes;
+#endif
+
+#ifdef NGX_QUIC_DEBUG_FRAMES_ALLOC
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic alloc frame n:%ui", qc->nframes);
+#endif
+ }
+
+ ngx_memzero(frame, sizeof(ngx_quic_frame_t));
+
+ frame->data = p;
+
+ return frame;
+}
+
+
+static void
+ngx_quic_congestion_ack(ngx_connection_t *c, ngx_quic_frame_t *f)
+{
+ ngx_msec_t timer;
+ ngx_quic_congestion_t *cg;
+ ngx_quic_connection_t *qc;
+
+ if (f->plen == 0) {
+ return;
+ }
+
+ qc = c->quic;
+ cg = &qc->congestion;
+
+ cg->in_flight -= f->plen;
+
+ timer = f->last - cg->recovery_start;
+
+ if ((ngx_msec_int_t) timer <= 0) {
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic congestion ack recovery win:%uz, ss:%uz, if:%uz",
+ cg->window, cg->ssthresh, cg->in_flight);
+
+ return;
+ }
+
+ if (cg->window < cg->ssthresh) {
+ cg->window += f->plen;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic congestion slow start win:%uz, ss:%uz, if:%uz",
+ cg->window, cg->ssthresh, cg->in_flight);
+
+ } else {
+ cg->window += qc->tp.max_udp_payload_size * f->plen / cg->window;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic congestion avoidance win:%uz, ss:%uz, if:%uz",
+ cg->window, cg->ssthresh, cg->in_flight);
+ }
+
+ /* prevent recovery_start from wrapping */
+
+ timer = cg->recovery_start - ngx_current_msec + qc->tp.max_idle_timeout * 2;
+
+ if ((ngx_msec_int_t) timer < 0) {
+ cg->recovery_start = ngx_current_msec - qc->tp.max_idle_timeout * 2;
+ }
+}
+
+
+static void
+ngx_quic_congestion_lost(ngx_connection_t *c, ngx_quic_frame_t *f)
+{
+ ngx_msec_t timer;
+ ngx_quic_congestion_t *cg;
+ ngx_quic_connection_t *qc;
+
+ if (f->plen == 0) {
+ return;
+ }
+
+ qc = c->quic;
+ cg = &qc->congestion;
+
+ cg->in_flight -= f->plen;
+
+ timer = f->last - cg->recovery_start;
+
+ if ((ngx_msec_int_t) timer <= 0) {
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic congestion lost recovery win:%uz, ss:%uz, if:%uz",
+ cg->window, cg->ssthresh, cg->in_flight);
+
+ return;
+ }
+
+ cg->recovery_start = ngx_current_msec;
+ cg->window /= 2;
+
+ if (cg->window < qc->tp.max_udp_payload_size * 2) {
+ cg->window = qc->tp.max_udp_payload_size * 2;
+ }
+
+ cg->ssthresh = cg->window;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic congestion lost win:%uz, ss:%uz, if:%uz",
+ cg->window, cg->ssthresh, cg->in_flight);
+}
+
+
+static void
+ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame)
+{
+ ngx_quic_connection_t *qc;
+
+ qc = c->quic;
+
+ if (frame->data) {
+ ngx_free(frame->data);
+ frame->data = NULL;
+ }
+
+ ngx_queue_insert_head(&qc->free_frames, &frame->queue);
+
+#ifdef NGX_QUIC_DEBUG_FRAMES_ALLOC
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic free frame n:%ui", qc->nframes);
+#endif
+}
diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h
new file mode 100644
index 000000000..2d43defef
--- /dev/null
+++ b/src/event/ngx_event_quic.h
@@ -0,0 +1,149 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_EVENT_QUIC_H_INCLUDED_
+#define _NGX_EVENT_QUIC_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+/* Supported drafts: 27, 28 */
+#ifndef NGX_QUIC_DRAFT_VERSION
+#define NGX_QUIC_DRAFT_VERSION 27
+#endif
+#define NGX_QUIC_VERSION (0xff000000 + NGX_QUIC_DRAFT_VERSION)
+
+#define NGX_QUIC_MAX_SHORT_HEADER 25 /* 1 flags + 20 dcid + 4 pn */
+#define NGX_QUIC_MAX_LONG_HEADER 56
+ /* 1 flags + 4 version + 2 x (1 + 20) s/dcid + 4 pn + 4 len + token len */
+
+#define NGX_QUIC_MAX_UDP_PAYLOAD_SIZE 65527
+#define NGX_QUIC_MAX_UDP_PAYLOAD_OUT 1252
+#define NGX_QUIC_MAX_UDP_PAYLOAD_OUT6 1232
+
+#define NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT 3
+#define NGX_QUIC_DEFAULT_MAX_ACK_DELAY 25
+
+#define NGX_QUIC_RETRY_TIMEOUT 3000
+#define NGX_QUIC_RETRY_LIFETIME 30000
+#define NGX_QUIC_RETRY_BUFFER_SIZE 128
+ /* 1 flags + 4 version + 3 x (1 + 20) s/o/dcid + itag + token(44) */
+#define NGX_QUIC_MAX_TOKEN_SIZE 32
+ /* sizeof(struct in6_addr) + sizeof(ngx_msec_t) up to AES-256 block size */
+
+/* quic-recovery, section 6.2.2, kInitialRtt */
+#define NGX_QUIC_INITIAL_RTT 333 /* ms */
+
+/* quic-recovery, section 6.1.1, Packet Threshold */
+#define NGX_QUIC_PKT_THR 3 /* packets */
+/* quic-recovery, section 6.1.2, Time Threshold */
+#define NGX_QUIC_TIME_THR 1.125
+#define NGX_QUIC_TIME_GRANULARITY 1 /* ms */
+
+#define NGX_QUIC_CC_MIN_INTERVAL 1000 /* 1s */
+
+#define NGX_QUIC_MIN_INITIAL_SIZE 1200
+
+/* if we have so much data, send immediately */
+/* TODO: configurable ? */
+#define NGX_QUIC_MIN_DATA_NODELAY 512 /* bytes */
+
+#define NGX_QUIC_STREAM_SERVER_INITIATED 0x01
+#define NGX_QUIC_STREAM_UNIDIRECTIONAL 0x02
+
+#define NGX_QUIC_STREAM_BUFSIZE 65536
+
+
+typedef struct {
+ /* configurable */
+ ngx_msec_t max_idle_timeout;
+ ngx_msec_t max_ack_delay;
+
+ size_t max_udp_payload_size;
+ size_t initial_max_data;
+ size_t initial_max_stream_data_bidi_local;
+ size_t initial_max_stream_data_bidi_remote;
+ size_t initial_max_stream_data_uni;
+ ngx_uint_t initial_max_streams_bidi;
+ ngx_uint_t initial_max_streams_uni;
+ ngx_uint_t ack_delay_exponent;
+ ngx_uint_t disable_active_migration;
+ ngx_uint_t active_connection_id_limit;
+ ngx_str_t original_dcid;
+ ngx_str_t initial_scid;
+ ngx_str_t retry_scid;
+
+ /* TODO */
+ u_char stateless_reset_token[16];
+ void *preferred_address;
+} ngx_quic_tp_t;
+
+
+typedef struct {
+ ngx_quic_tp_t tp;
+ ngx_flag_t retry;
+ ngx_flag_t require_alpn;
+ u_char token_key[32]; /* AES 256 */
+} ngx_quic_conf_t;
+
+
+typedef struct {
+ uint64_t sent;
+ uint64_t received;
+ ngx_queue_t frames; /* reorder queue */
+ size_t total; /* size of buffered data */
+} ngx_quic_frames_stream_t;
+
+
+struct ngx_quic_stream_s {
+ ngx_rbtree_node_t node;
+ ngx_connection_t *parent;
+ ngx_connection_t *c;
+ uint64_t id;
+ uint64_t acked;
+ uint64_t send_max_data;
+ ngx_buf_t *b;
+ ngx_quic_frames_stream_t fs;
+};
+
+
+void ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_conf_t *conf);
+ngx_connection_t *ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi);
+void ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err,
+ const char *reason);
+
+
+/********************************* DEBUG *************************************/
+
+/* #define NGX_QUIC_DEBUG_PACKETS */ /* dump packet contents */
+/* #define NGX_QUIC_DEBUG_FRAMES */ /* dump frames contents */
+/* #define NGX_QUIC_DEBUG_FRAMES_ALLOC */ /* log frames alloc/reuse/free */
+/* #define NGX_QUIC_DEBUG_CRYPTO */
+
+#if (NGX_DEBUG)
+
+#define ngx_quic_hexdump(log, label, data, len) \
+do { \
+ ngx_int_t m; \
+ u_char buf[2048]; \
+ \
+ if (log->log_level & NGX_LOG_DEBUG_EVENT) { \
+ m = ngx_hex_dump(buf, (u_char *) data, ngx_min(len, 1024)) - buf; \
+ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, log, 0, \
+ label " len:%uz data:%*s%s", \
+ len, m, buf, len < 2048 ? "" : "..."); \
+ } \
+} while (0)
+
+#else
+
+#define ngx_quic_hexdump(log, fmt, data, len)
+
+#endif
+
+#endif /* _NGX_EVENT_QUIC_H_INCLUDED_ */
diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c
new file mode 100644
index 000000000..721944b97
--- /dev/null
+++ b/src/event/ngx_event_quic_protection.c
@@ -0,0 +1,1165 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_event_quic_transport.h>
+#include <ngx_event_quic_protection.h>
+
+
+#define NGX_QUIC_IV_LEN 12
+
+#define NGX_AES_128_GCM_SHA256 0x1301
+#define NGX_AES_256_GCM_SHA384 0x1302
+#define NGX_CHACHA20_POLY1305_SHA256 0x1303
+
+
+#ifdef OPENSSL_IS_BORINGSSL
+#define ngx_quic_cipher_t EVP_AEAD
+#else
+#define ngx_quic_cipher_t EVP_CIPHER
+#endif
+
+typedef struct {
+ const ngx_quic_cipher_t *c;
+ const EVP_CIPHER *hp;
+ const EVP_MD *d;
+} ngx_quic_ciphers_t;
+
+
+static ngx_int_t ngx_hkdf_expand(u_char *out_key, size_t out_len,
+ const EVP_MD *digest, const u_char *prk, size_t prk_len,
+ const u_char *info, size_t info_len);
+static ngx_int_t ngx_hkdf_extract(u_char *out_key, size_t *out_len,
+ const EVP_MD *digest, const u_char *secret, size_t secret_len,
+ const u_char *salt, size_t salt_len);
+
+static uint64_t ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask,
+ uint64_t *largest_pn);
+static void ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn);
+static ngx_int_t ngx_quic_ciphers(ngx_ssl_conn_t *ssl_conn,
+ ngx_quic_ciphers_t *ciphers, enum ssl_encryption_level_t level);
+
+static ngx_int_t ngx_quic_tls_open(const ngx_quic_cipher_t *cipher,
+ ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in,
+ ngx_str_t *ad, ngx_log_t *log);
+static ngx_int_t ngx_quic_tls_seal(const ngx_quic_cipher_t *cipher,
+ ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in,
+ ngx_str_t *ad, ngx_log_t *log);
+static ngx_int_t ngx_quic_tls_hp(ngx_log_t *log, const EVP_CIPHER *cipher,
+ ngx_quic_secret_t *s, u_char *out, u_char *in);
+static ngx_int_t ngx_quic_hkdf_expand(ngx_pool_t *pool, const EVP_MD *digest,
+ ngx_str_t *out, ngx_str_t *label, const uint8_t *prk, size_t prk_len);
+
+static ngx_int_t ngx_quic_create_long_packet(ngx_quic_header_t *pkt,
+ ngx_ssl_conn_t *ssl_conn, ngx_str_t *res);
+static ngx_int_t ngx_quic_create_short_packet(ngx_quic_header_t *pkt,
+ ngx_ssl_conn_t *ssl_conn, ngx_str_t *res);
+static ngx_int_t ngx_quic_create_retry_packet(ngx_quic_header_t *pkt,
+ ngx_str_t *res);
+
+
+static ngx_int_t
+ngx_quic_ciphers(ngx_ssl_conn_t *ssl_conn, ngx_quic_ciphers_t *ciphers,
+ enum ssl_encryption_level_t level)
+{
+ ngx_int_t id, len;
+ const SSL_CIPHER *cipher;
+
+ if (level == ssl_encryption_initial) {
+ id = NGX_AES_128_GCM_SHA256;
+
+ } else {
+ cipher = SSL_get_current_cipher(ssl_conn);
+ if (cipher == NULL) {
+ return NGX_ERROR;
+ }
+
+ id = SSL_CIPHER_get_id(cipher) & 0xffff;
+ }
+
+ switch (id) {
+
+ case NGX_AES_128_GCM_SHA256:
+#ifdef OPENSSL_IS_BORINGSSL
+ ciphers->c = EVP_aead_aes_128_gcm();
+#else
+ ciphers->c = EVP_aes_128_gcm();
+#endif
+ ciphers->hp = EVP_aes_128_ctr();
+ ciphers->d = EVP_sha256();
+ len = 16;
+ break;
+
+ case NGX_AES_256_GCM_SHA384:
+#ifdef OPENSSL_IS_BORINGSSL
+ ciphers->c = EVP_aead_aes_256_gcm();
+#else
+ ciphers->c = EVP_aes_256_gcm();
+#endif
+ ciphers->hp = EVP_aes_256_ctr();
+ ciphers->d = EVP_sha384();
+ len = 32;
+ break;
+
+ case NGX_CHACHA20_POLY1305_SHA256:
+#ifdef OPENSSL_IS_BORINGSSL
+ ciphers->c = EVP_aead_chacha20_poly1305();
+#else
+ ciphers->c = EVP_chacha20_poly1305();
+#endif
+#ifdef OPENSSL_IS_BORINGSSL
+ ciphers->hp = (const EVP_CIPHER *) EVP_aead_chacha20_poly1305();
+#else
+ ciphers->hp = EVP_chacha20();
+#endif
+ ciphers->d = EVP_sha256();
+ len = 32;
+ break;
+
+ default:
+ return NGX_ERROR;
+ }
+
+ return len;
+}
+
+
+ngx_int_t
+ngx_quic_set_initial_secret(ngx_pool_t *pool, ngx_quic_secret_t *client,
+ ngx_quic_secret_t *server, ngx_str_t *secret)
+{
+ size_t is_len;
+ uint8_t is[SHA256_DIGEST_LENGTH];
+ ngx_uint_t i;
+ const EVP_MD *digest;
+ const EVP_CIPHER *cipher;
+
+ static const uint8_t salt[20] =
+#if (NGX_QUIC_DRAFT_VERSION >= 29)
+ "\xaf\xbf\xec\x28\x99\x93\xd2\x4c\x9e\x97"
+ "\x86\xf1\x9c\x61\x11\xe0\x43\x90\xa8\x99";
+#else
+ "\xc3\xee\xf7\x12\xc7\x2e\xbb\x5a\x11\xa7"
+ "\xd2\x43\x2b\xb4\x63\x65\xbe\xf9\xf5\x02";
+#endif
+
+ /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */
+
+ cipher = EVP_aes_128_gcm();
+ digest = EVP_sha256();
+
+ if (ngx_hkdf_extract(is, &is_len, digest, secret->data, secret->len,
+ salt, sizeof(salt))
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ ngx_str_t iss = {
+ .data = is,
+ .len = is_len
+ };
+
+#ifdef NGX_QUIC_DEBUG_CRYPTO
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pool->log, 0,
+ "quic ngx_quic_set_initial_secret");
+ ngx_quic_hexdump(pool->log, "quic salt", salt, sizeof(salt));
+ ngx_quic_hexdump(pool->log, "quic initial secret", is, is_len);
+#endif
+
+ /* draft-ietf-quic-tls-23#section-5.2 */
+ client->secret.len = SHA256_DIGEST_LENGTH;
+ server->secret.len = SHA256_DIGEST_LENGTH;
+
+ client->key.len = EVP_CIPHER_key_length(cipher);
+ server->key.len = EVP_CIPHER_key_length(cipher);
+
+ client->hp.len = EVP_CIPHER_key_length(cipher);
+ server->hp.len = EVP_CIPHER_key_length(cipher);
+
+ client->iv.len = EVP_CIPHER_iv_length(cipher);
+ server->iv.len = EVP_CIPHER_iv_length(cipher);
+
+ struct {
+ ngx_str_t label;
+ ngx_str_t *key;
+ ngx_str_t *prk;
+ } seq[] = {
+
+ /* draft-ietf-quic-tls-23#section-5.2 */
+ { ngx_string("tls13 client in"), &client->secret, &iss },
+ {
+ ngx_string("tls13 quic key"),
+ &client->key,
+ &client->secret,
+ },
+ {
+ ngx_string("tls13 quic iv"),
+ &client->iv,
+ &client->secret,
+ },
+ {
+ /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */
+ ngx_string("tls13 quic hp"),
+ &client->hp,
+ &client->secret,
+ },
+ { ngx_string("tls13 server in"), &server->secret, &iss },
+ {
+ /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */
+ ngx_string("tls13 quic key"),
+ &server->key,
+ &server->secret,
+ },
+ {
+ ngx_string("tls13 quic iv"),
+ &server->iv,
+ &server->secret,
+ },
+ {
+ /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */
+ ngx_string("tls13 quic hp"),
+ &server->hp,
+ &server->secret,
+ },
+
+ };
+
+ for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) {
+
+ if (ngx_quic_hkdf_expand(pool, digest, seq[i].key, &seq[i].label,
+ seq[i].prk->data, seq[i].prk->len)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_quic_hkdf_expand(ngx_pool_t *pool, const EVP_MD *digest, ngx_str_t *out,
+ ngx_str_t *label, const uint8_t *prk, size_t prk_len)
+{
+ size_t info_len;
+ uint8_t *p;
+ uint8_t info[20];
+
+ if (out->data == NULL) {
+ out->data = ngx_pnalloc(pool, out->len);
+ if (out->data == NULL) {
+ return NGX_ERROR;
+ }
+ }
+
+ info_len = 2 + 1 + label->len + 1;
+
+ info[0] = 0;
+ info[1] = out->len;
+ info[2] = label->len;
+ p = ngx_cpymem(&info[3], label->data, label->len);
+ *p = '\0';
+
+ if (ngx_hkdf_expand(out->data, out->len, digest,
+ prk, prk_len, info, info_len)
+ != NGX_OK)
+ {
+ ngx_ssl_error(NGX_LOG_INFO, pool->log, 0,
+ "ngx_hkdf_expand(%V) failed", label);
+ return NGX_ERROR;
+ }
+
+#ifdef NGX_QUIC_DEBUG_CRYPTO
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pool->log, 0,
+ "quic ngx_quic_hkdf_expand %V keys", label);
+ ngx_quic_hexdump(pool->log, "quic info", info, info_len);
+ ngx_quic_hexdump(pool->log, "quic key", out->data, out->len);
+#endif
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_hkdf_expand(u_char *out_key, size_t out_len, const EVP_MD *digest,
+ const uint8_t *prk, size_t prk_len, const u_char *info, size_t info_len)
+{
+#ifdef OPENSSL_IS_BORINGSSL
+ if (HKDF_expand(out_key, out_len, digest, prk, prk_len, info, info_len)
+ == 0)
+ {
+ return NGX_ERROR;
+ }
+#else
+
+ EVP_PKEY_CTX *pctx;
+
+ pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
+
+ if (EVP_PKEY_derive_init(pctx) <= 0) {
+ return NGX_ERROR;
+ }
+
+ if (EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) <= 0) {
+ return NGX_ERROR;
+ }
+
+ if (EVP_PKEY_CTX_set_hkdf_md(pctx, digest) <= 0) {
+ return NGX_ERROR;
+ }
+
+ if (EVP_PKEY_CTX_set1_hkdf_key(pctx, prk, prk_len) <= 0) {
+ return NGX_ERROR;
+ }
+
+ if (EVP_PKEY_CTX_add1_hkdf_info(pctx, info, info_len) <= 0) {
+ return NGX_ERROR;
+ }
+
+ if (EVP_PKEY_derive(pctx, out_key, &out_len) <= 0) {
+ return NGX_ERROR;
+ }
+
+#endif
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_hkdf_extract(u_char *out_key, size_t *out_len, const EVP_MD *digest,
+ const u_char *secret, size_t secret_len, const u_char *salt,
+ size_t salt_len)
+{
+#ifdef OPENSSL_IS_BORINGSSL
+ if (HKDF_extract(out_key, out_len, digest, secret, secret_len, salt,
+ salt_len)
+ == 0)
+ {
+ return NGX_ERROR;
+ }
+#else
+
+ EVP_PKEY_CTX *pctx;
+
+ pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
+
+ if (EVP_PKEY_derive_init(pctx) <= 0) {
+ return NGX_ERROR;
+ }
+
+ if (EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY) <= 0) {
+ return NGX_ERROR;
+ }
+
+ if (EVP_PKEY_CTX_set_hkdf_md(pctx, digest) <= 0) {
+ return NGX_ERROR;
+ }
+
+ if (EVP_PKEY_CTX_set1_hkdf_key(pctx, secret, secret_len) <= 0) {
+ return NGX_ERROR;
+ }
+
+ if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt, salt_len) <= 0) {
+ return NGX_ERROR;
+ }
+
+ if (EVP_PKEY_derive(pctx, out_key, out_len) <= 0) {
+ return NGX_ERROR;
+ }
+
+#endif
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_quic_tls_open(const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s,
+ ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad,
+ ngx_log_t *log)
+{
+
+#ifdef OPENSSL_IS_BORINGSSL
+ EVP_AEAD_CTX *ctx;
+
+ ctx = EVP_AEAD_CTX_new(cipher, s->key.data, s->key.len,
+ EVP_AEAD_DEFAULT_TAG_LENGTH);
+ if (ctx == NULL) {
+ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_new() failed");
+ return NGX_ERROR;
+ }
+
+ if (EVP_AEAD_CTX_open(ctx, out->data, &out->len, out->len, nonce, s->iv.len,
+ in->data, in->len, ad->data, ad->len)
+ != 1)
+ {
+ EVP_AEAD_CTX_free(ctx);
+ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_open() failed");
+ return NGX_ERROR;
+ }
+
+ EVP_AEAD_CTX_free(ctx);
+#else
+ int len;
+ u_char *tag;
+ EVP_CIPHER_CTX *ctx;
+
+ ctx = EVP_CIPHER_CTX_new();
+ if (ctx == NULL) {
+ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CIPHER_CTX_new() failed");
+ return NGX_ERROR;
+ }
+
+ if (EVP_DecryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1) {
+ EVP_CIPHER_CTX_free(ctx);
+ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptInit_ex() failed");
+ return NGX_ERROR;
+ }
+
+ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, s->iv.len, NULL)
+ == 0)
+ {
+ EVP_CIPHER_CTX_free(ctx);
+ ngx_ssl_error(NGX_LOG_INFO, log, 0,
+ "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_IVLEN) failed");
+ return NGX_ERROR;
+ }
+
+ if (EVP_DecryptInit_ex(ctx, NULL, NULL, s->key.data, nonce) != 1) {
+ EVP_CIPHER_CTX_free(ctx);
+ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptInit_ex() failed");
+ return NGX_ERROR;
+ }
+
+ if (EVP_DecryptUpdate(ctx, NULL, &len, ad->data, ad->len) != 1) {
+ EVP_CIPHER_CTX_free(ctx);
+ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptUpdate() failed");
+ return NGX_ERROR;
+ }
+
+ if (EVP_DecryptUpdate(ctx, out->data, &len, in->data,
+ in->len - EVP_GCM_TLS_TAG_LEN)
+ != 1)
+ {
+ EVP_CIPHER_CTX_free(ctx);
+ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptUpdate() failed");
+ return NGX_ERROR;
+ }
+
+ out->len = len;
+ tag = in->data + in->len - EVP_GCM_TLS_TAG_LEN;
+
+ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, EVP_GCM_TLS_TAG_LEN, tag)
+ == 0)
+ {
+ EVP_CIPHER_CTX_free(ctx);
+ ngx_ssl_error(NGX_LOG_INFO, log, 0,
+ "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_TAG) failed");
+ return NGX_ERROR;
+ }
+
+ if (EVP_DecryptFinal_ex(ctx, out->data + len, &len) <= 0) {
+ EVP_CIPHER_CTX_free(ctx);
+ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptFinal_ex failed");
+ return NGX_ERROR;
+ }
+
+ out->len += len;
+
+ EVP_CIPHER_CTX_free(ctx);
+#endif
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_quic_tls_seal(const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s,
+ ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad, ngx_log_t *log)
+{
+
+#ifdef OPENSSL_IS_BORINGSSL
+ EVP_AEAD_CTX *ctx;
+
+ ctx = EVP_AEAD_CTX_new(cipher, s->key.data, s->key.len,
+ EVP_AEAD_DEFAULT_TAG_LENGTH);
+ if (ctx == NULL) {
+ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_new() failed");
+ return NGX_ERROR;
+ }
+
+ if (EVP_AEAD_CTX_seal(ctx, out->data, &out->len, out->len, nonce, s->iv.len,
+ in->data, in->len, ad->data, ad->len)
+ != 1)
+ {
+ EVP_AEAD_CTX_free(ctx);
+ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_seal() failed");
+ return NGX_ERROR;
+ }
+
+ EVP_AEAD_CTX_free(ctx);
+#else
+ int len;
+ EVP_CIPHER_CTX *ctx;
+
+ ctx = EVP_CIPHER_CTX_new();
+ if (ctx == NULL) {
+ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CIPHER_CTX_new() failed");
+ return NGX_ERROR;
+ }
+
+ if (EVP_EncryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1) {
+ EVP_CIPHER_CTX_free(ctx);
+ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed");
+ return NGX_ERROR;
+ }
+
+ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, s->iv.len, NULL)
+ == 0)
+ {
+ EVP_CIPHER_CTX_free(ctx);
+ ngx_ssl_error(NGX_LOG_INFO, log, 0,
+ "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_IVLEN) failed");
+ return NGX_ERROR;
+ }
+
+ if (EVP_EncryptInit_ex(ctx, NULL, NULL, s->key.data, nonce) != 1) {
+ EVP_CIPHER_CTX_free(ctx);
+ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed");
+ return NGX_ERROR;
+ }
+
+ if (EVP_EncryptUpdate(ctx, NULL, &len, ad->data, ad->len) != 1) {
+ EVP_CIPHER_CTX_free(ctx);
+ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptUpdate() failed");
+ return NGX_ERROR;
+ }
+
+ if (EVP_EncryptUpdate(ctx, out->data, &len, in->data, in->len) != 1) {
+ EVP_CIPHER_CTX_free(ctx);
+ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptUpdate() failed");
+ return NGX_ERROR;
+ }
+
+ out->len = len;
+
+ if (EVP_EncryptFinal_ex(ctx, out->data + out->len, &len) <= 0) {
+ EVP_CIPHER_CTX_free(ctx);
+ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptFinal_ex failed");
+ return NGX_ERROR;
+ }
+
+ out->len += len;
+
+ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, EVP_GCM_TLS_TAG_LEN,
+ out->data + in->len)
+ == 0)
+ {
+ EVP_CIPHER_CTX_free(ctx);
+ ngx_ssl_error(NGX_LOG_INFO, log, 0,
+ "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_GET_TAG) failed");
+ return NGX_ERROR;
+ }
+
+ EVP_CIPHER_CTX_free(ctx);
+
+ out->len += EVP_GCM_TLS_TAG_LEN;
+#endif
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_quic_tls_hp(ngx_log_t *log, const EVP_CIPHER *cipher,
+ ngx_quic_secret_t *s, u_char *out, u_char *in)
+{
+ int outlen;
+ EVP_CIPHER_CTX *ctx;
+ u_char zero[5] = {0};
+
+#ifdef OPENSSL_IS_BORINGSSL
+ uint32_t counter;
+
+ ngx_memcpy(&counter, in, sizeof(uint32_t));
+
+ if (cipher == (const EVP_CIPHER *) EVP_aead_chacha20_poly1305()) {
+ CRYPTO_chacha_20(out, zero, 5, s->hp.data, &in[4], counter);
+ return NGX_OK;
+ }
+#endif
+
+ ctx = EVP_CIPHER_CTX_new();
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (EVP_EncryptInit_ex(ctx, cipher, NULL, s->hp.data, in) != 1) {
+ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed");
+ goto failed;
+ }
+
+ if (!EVP_EncryptUpdate(ctx, out, &outlen, zero, 5)) {
+ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptUpdate() failed");
+ goto failed;
+ }
+
+ if (!EVP_EncryptFinal_ex(ctx, out + 5, &outlen)) {
+ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptFinal_Ex() failed");
+ goto failed;
+ }
+
+ EVP_CIPHER_CTX_free(ctx);
+
+ return NGX_OK;
+
+failed:
+
+ EVP_CIPHER_CTX_free(ctx);
+
+ return NGX_ERROR;
+}
+
+
+int
+ngx_quic_set_encryption_secret(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn,
+ enum ssl_encryption_level_t level, const uint8_t *secret,
+ size_t secret_len, ngx_quic_secret_t *peer_secret)
+{
+ ngx_int_t key_len;
+ ngx_uint_t i;
+ ngx_quic_ciphers_t ciphers;
+
+ key_len = ngx_quic_ciphers(ssl_conn, &ciphers, level);
+
+ if (key_len == NGX_ERROR) {
+ ngx_ssl_error(NGX_LOG_INFO, pool->log, 0, "unexpected cipher");
+ return 0;
+ }
+
+ if (level == ssl_encryption_initial) {
+ return 0;
+ }
+
+ peer_secret->secret.data = ngx_pnalloc(pool, secret_len);
+ if (peer_secret->secret.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ peer_secret->secret.len = secret_len;
+ ngx_memcpy(peer_secret->secret.data, secret, secret_len);
+
+ peer_secret->key.len = key_len;
+ peer_secret->iv.len = NGX_QUIC_IV_LEN;
+ peer_secret->hp.len = key_len;
+
+ struct {
+ ngx_str_t label;
+ ngx_str_t *key;
+ const uint8_t *secret;
+ } seq[] = {
+ { ngx_string("tls13 quic key"), &peer_secret->key, secret },
+ { ngx_string("tls13 quic iv"), &peer_secret->iv, secret },
+ { ngx_string("tls13 quic hp"), &peer_secret->hp, secret },
+ };
+
+ for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) {
+
+ if (ngx_quic_hkdf_expand(pool, ciphers.d, seq[i].key, &seq[i].label,
+ seq[i].secret, secret_len)
+ != NGX_OK)
+ {
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+
+ngx_int_t
+ngx_quic_key_update(ngx_connection_t *c, ngx_quic_secrets_t *current,
+ ngx_quic_secrets_t *next)
+{
+ ngx_uint_t i;
+ ngx_quic_ciphers_t ciphers;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic key update");
+
+ if (ngx_quic_ciphers(c->ssl->connection, &ciphers,
+ ssl_encryption_application)
+ == NGX_ERROR)
+ {
+ return NGX_ERROR;
+ }
+
+ next->client.secret.len = current->client.secret.len;
+ next->client.key.len = current->client.key.len;
+ next->client.iv.len = current->client.iv.len;
+ next->client.hp = current->client.hp;
+
+ next->server.secret.len = current->server.secret.len;
+ next->server.key.len = current->server.key.len;
+ next->server.iv.len = current->server.iv.len;
+ next->server.hp = current->server.hp;
+
+ struct {
+ ngx_str_t label;
+ ngx_str_t *key;
+ ngx_str_t *secret;
+ } seq[] = {
+ {
+ ngx_string("tls13 quic ku"),
+ &next->client.secret,
+ &current->client.secret,
+ },
+ {
+ ngx_string("tls13 quic key"),
+ &next->client.key,
+ &next->client.secret,
+ },
+ {
+ ngx_string("tls13 quic iv"),
+ &next->client.iv,
+ &next->client.secret,
+ },
+ {
+ ngx_string("tls13 quic ku"),
+ &next->server.secret,
+ &current->server.secret,
+ },
+ {
+ ngx_string("tls13 quic key"),
+ &next->server.key,
+ &next->server.secret,
+ },
+ {
+ ngx_string("tls13 quic iv"),
+ &next->server.iv,
+ &next->server.secret,
+ },
+ };
+
+ for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) {
+
+ if (ngx_quic_hkdf_expand(c->pool, ciphers.d, seq[i].key, &seq[i].label,
+ seq[i].secret->data, seq[i].secret->len)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_quic_create_long_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn,
+ ngx_str_t *res)
+{
+ u_char *pnp, *sample;
+ ngx_str_t ad, out;
+ ngx_uint_t i;
+ ngx_quic_ciphers_t ciphers;
+ u_char nonce[12], mask[16];
+
+ out.len = pkt->payload.len + EVP_GCM_TLS_TAG_LEN;
+
+ ad.data = res->data;
+ ad.len = ngx_quic_create_long_header(pkt, ad.data, out.len, &pnp);
+
+ out.data = res->data + ad.len;
+
+#ifdef NGX_QUIC_DEBUG_CRYPTO
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "quic ngx_quic_create_long_packet");
+ ngx_quic_hexdump(pkt->log, "quic ad", ad.data, ad.len);
+#endif
+
+ if (ngx_quic_ciphers(ssl_conn, &ciphers, pkt->level) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(nonce, pkt->secret->iv.data, pkt->secret->iv.len);
+ ngx_quic_compute_nonce(nonce, sizeof(nonce), pkt->number);
+
+#ifdef NGX_QUIC_DEBUG_CRYPTO
+ ngx_quic_hexdump(pkt->log, "quic server_iv", pkt->secret->iv.data, 12);
+ ngx_quic_hexdump(pkt->log, "quic nonce", nonce, 12);
+#endif
+
+ if (ngx_quic_tls_seal(ciphers.c, pkt->secret, &out,
+ nonce, &pkt->payload, &ad, pkt->log)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ sample = &out.data[4 - pkt->num_len];
+ if (ngx_quic_tls_hp(pkt->log, ciphers.hp, pkt->secret, mask, sample)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+#ifdef NGX_QUIC_DEBUG_CRYPTO
+ ngx_quic_hexdump(pkt->log, "quic sample", sample, 16);
+ ngx_quic_hexdump(pkt->log, "quic mask", mask, 5);
+#endif
+
+ /* quic-tls: 5.4.1. Header Protection Application */
+ ad.data[0] ^= mask[0] & 0x0f;
+
+ for (i = 0; i < pkt->num_len; i++) {
+ pnp[i] ^= mask[i + 1];
+ }
+
+ res->len = ad.len + out.len;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_quic_create_short_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn,
+ ngx_str_t *res)
+{
+ u_char *pnp, *sample;
+ ngx_str_t ad, out;
+ ngx_uint_t i;
+ ngx_quic_ciphers_t ciphers;
+ u_char nonce[12], mask[16];
+
+ out.len = pkt->payload.len + EVP_GCM_TLS_TAG_LEN;
+
+ ad.data = res->data;
+ ad.len = ngx_quic_create_short_header(pkt, ad.data, out.len, &pnp);
+
+ out.data = res->data + ad.len;
+
+#ifdef NGX_QUIC_DEBUG_CRYPTO
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "quic ngx_quic_create_short_packet");
+ ngx_quic_hexdump(pkt->log, "quic ad", ad.data, ad.len);
+#endif
+
+ if (ngx_quic_ciphers(ssl_conn, &ciphers, pkt->level) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "quic ngx_quic_create_short_packet: number %L,"
+ " encoded %d:0x%xD", pkt->number, (int) pkt->num_len,
+ pkt->trunc);
+
+ ngx_memcpy(nonce, pkt->secret->iv.data, pkt->secret->iv.len);
+ ngx_quic_compute_nonce(nonce, sizeof(nonce), pkt->number);
+
+#ifdef NGX_QUIC_DEBUG_CRYPTO
+ ngx_quic_hexdump(pkt->log, "quic server_iv", pkt->secret->iv.data, 12);
+ ngx_quic_hexdump(pkt->log, "quic nonce", nonce, 12);
+#endif
+
+ if (ngx_quic_tls_seal(ciphers.c, pkt->secret, &out,
+ nonce, &pkt->payload, &ad, pkt->log)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ sample = &out.data[4 - pkt->num_len];
+ if (ngx_quic_tls_hp(pkt->log, ciphers.hp, pkt->secret, mask, sample)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+#ifdef NGX_QUIC_DEBUG_CRYPTO
+ ngx_quic_hexdump(pkt->log, "quic sample", sample, 16);
+ ngx_quic_hexdump(pkt->log, "quic mask", mask, 5);
+#endif
+
+ /* quic-tls: 5.4.1. Header Protection Application */
+ ad.data[0] ^= mask[0] & 0x1f;
+
+ for (i = 0; i < pkt->num_len; i++) {
+ pnp[i] ^= mask[i + 1];
+ }
+
+ res->len = ad.len + out.len;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, ngx_str_t *res)
+{
+ u_char *start;
+ ngx_str_t ad, itag;
+ ngx_quic_secret_t secret;
+ ngx_quic_ciphers_t ciphers;
+
+ /* 5.8. Retry Packet Integrity */
+ static u_char key[16] =
+#if (NGX_QUIC_DRAFT_VERSION >= 29)
+ "\xcc\xce\x18\x7e\xd0\x9a\x09\xd0\x57\x28\x15\x5a\x6c\xb9\x6b\xe1";
+#else
+ "\x4d\x32\xec\xdb\x2a\x21\x33\xc8\x41\xe4\x04\x3d\xf2\x7d\x44\x30";
+#endif
+ static u_char nonce[12] =
+#if (NGX_QUIC_DRAFT_VERSION >= 29)
+ "\xe5\x49\x30\xf9\x7f\x21\x36\xf0\x53\x0a\x8c\x1c";
+#else
+ "\x4d\x16\x11\xd0\x55\x13\xa5\x52\xc5\x87\xd5\x75";
+#endif
+ static ngx_str_t in = ngx_string("");
+
+ ad.data = res->data;
+ ad.len = ngx_quic_create_retry_itag(pkt, ad.data, &start);
+
+ itag.data = ad.data + ad.len;
+ itag.len = EVP_GCM_TLS_TAG_LEN;
+
+#ifdef NGX_QUIC_DEBUG_CRYPTO
+ ngx_quic_hexdump(pkt->log, "quic retry itag", ad.data, ad.len);
+#endif
+
+ if (ngx_quic_ciphers(NULL, &ciphers, pkt->level) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ secret.key.len = sizeof(key);
+ secret.key.data = key;
+ secret.iv.len = sizeof(nonce);
+
+ if (ngx_quic_tls_seal(ciphers.c, &secret, &itag, nonce, &in, &ad, pkt->log)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ res->len = itag.data + itag.len - start;
+ res->data = start;
+
+ return NGX_OK;
+}
+
+
+static uint64_t
+ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask,
+ uint64_t *largest_pn)
+{
+ u_char *p;
+ uint64_t truncated_pn, expected_pn, candidate_pn;
+ uint64_t pn_nbits, pn_win, pn_hwin, pn_mask;
+
+ pn_nbits = ngx_min(len * 8, 62);
+
+ p = *pos;
+ truncated_pn = *p++ ^ *mask++;
+
+ while (--len) {
+ truncated_pn = (truncated_pn << 8) + (*p++ ^ *mask++);
+ }
+
+ *pos = p;
+
+ expected_pn = *largest_pn + 1;
+ pn_win = 1ULL << pn_nbits;
+ pn_hwin = pn_win / 2;
+ pn_mask = pn_win - 1;
+
+ candidate_pn = (expected_pn & ~pn_mask) | truncated_pn;
+
+ if ((int64_t) candidate_pn <= (int64_t) (expected_pn - pn_hwin)
+ && candidate_pn < (1ULL << 62) - pn_win)
+ {
+ candidate_pn += pn_win;
+
+ } else if (candidate_pn > expected_pn + pn_hwin
+ && candidate_pn >= pn_win)
+ {
+ candidate_pn -= pn_win;
+ }
+
+ *largest_pn = ngx_max((int64_t) *largest_pn, (int64_t) candidate_pn);
+
+ return candidate_pn;
+}
+
+
+static void
+ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn)
+{
+ nonce[len - 4] ^= (pn & 0xff000000) >> 24;
+ nonce[len - 3] ^= (pn & 0x00ff0000) >> 16;
+ nonce[len - 2] ^= (pn & 0x0000ff00) >> 8;
+ nonce[len - 1] ^= (pn & 0x000000ff);
+}
+
+
+ngx_int_t
+ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn,
+ ngx_str_t *res)
+{
+ if (ngx_quic_short_pkt(pkt->flags)) {
+ return ngx_quic_create_short_packet(pkt, ssl_conn, res);
+ }
+
+ if (ngx_quic_pkt_retry(pkt->flags)) {
+ return ngx_quic_create_retry_packet(pkt, res);
+ }
+
+ return ngx_quic_create_long_packet(pkt, ssl_conn, res);
+}
+
+
+ngx_int_t
+ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn,
+ uint64_t *largest_pn)
+{
+ u_char clearflags, *p, *sample;
+ uint8_t badflags;
+ uint64_t pn;
+ ngx_int_t pnl, rc, key_phase;
+ ngx_str_t in, ad;
+ ngx_quic_secret_t *secret;
+ ngx_quic_ciphers_t ciphers;
+ uint8_t mask[16], nonce[12];
+
+ if (ngx_quic_ciphers(ssl_conn, &ciphers, pkt->level) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ secret = pkt->secret;
+
+ p = pkt->raw->pos;
+
+ /* draft-ietf-quic-tls-23#section-5.4.2:
+ * the Packet Number field is assumed to be 4 bytes long
+ * draft-ietf-quic-tls-23#section-5.4.[34]:
+ * AES-Based and ChaCha20-Based header protections sample 16 bytes
+ */
+
+ sample = p + 4;
+
+#ifdef NGX_QUIC_DEBUG_CRYPTO
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "quic ngx_quic_decrypt()");
+ ngx_quic_hexdump(pkt->log, "quic sample", sample, 16);
+#endif
+
+ /* header protection */
+
+ if (ngx_quic_tls_hp(pkt->log, ciphers.hp, secret, mask, sample)
+ != NGX_OK)
+ {
+ pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION;
+ return NGX_DECLINED;
+ }
+
+ if (ngx_quic_long_pkt(pkt->flags)) {
+ clearflags = pkt->flags ^ (mask[0] & 0x0f);
+
+ } else {
+ clearflags = pkt->flags ^ (mask[0] & 0x1f);
+ key_phase = (clearflags & NGX_QUIC_PKT_KPHASE) != 0;
+
+ if (key_phase != pkt->key_phase) {
+ secret = pkt->next;
+ pkt->key_update = 1;
+ }
+ }
+
+ pnl = (clearflags & 0x03) + 1;
+ pn = ngx_quic_parse_pn(&p, pnl, &mask[1], largest_pn);
+
+ pkt->pn = pn;
+ pkt->flags = clearflags;
+
+#ifdef NGX_QUIC_DEBUG_CRYPTO
+ ngx_quic_hexdump(pkt->log, "quic mask", mask, 5);
+#endif
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "quic clear flags: %xd", clearflags);
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "quic packet number: %uL, len: %xi", pn, pnl);
+
+ /* packet protection */
+
+ in.data = p;
+
+ if (ngx_quic_long_pkt(pkt->flags)) {
+ in.len = pkt->len - pnl;
+ badflags = clearflags & NGX_QUIC_PKT_LONG_RESERVED_BIT;
+
+ } else {
+ in.len = pkt->data + pkt->len - p;
+ badflags = clearflags & NGX_QUIC_PKT_SHORT_RESERVED_BIT;
+ }
+
+ ad.len = p - pkt->data;
+ ad.data = pkt->plaintext;
+
+ ngx_memcpy(ad.data, pkt->data, ad.len);
+ ad.data[0] = clearflags;
+
+ do {
+ ad.data[ad.len - pnl] = pn >> (8 * (pnl - 1)) % 256;
+ } while (--pnl);
+
+ ngx_memcpy(nonce, secret->iv.data, secret->iv.len);
+ ngx_quic_compute_nonce(nonce, sizeof(nonce), pn);
+
+#ifdef NGX_QUIC_DEBUG_CRYPTO
+ ngx_quic_hexdump(pkt->log, "quic nonce", nonce, 12);
+ ngx_quic_hexdump(pkt->log, "quic ad", ad.data, ad.len);
+#endif
+
+ pkt->payload.len = in.len - EVP_GCM_TLS_TAG_LEN;
+
+ if (NGX_QUIC_MAX_UDP_PAYLOAD_SIZE - ad.len < pkt->payload.len) {
+ return NGX_ERROR;
+ }
+
+ pkt->payload.data = pkt->plaintext + ad.len;
+
+ rc = ngx_quic_tls_open(ciphers.c, secret, &pkt->payload,
+ nonce, &in, &ad, pkt->log);
+
+#if defined(NGX_QUIC_DEBUG_CRYPTO) && defined(NGX_QUIC_DEBUG_PACKETS)
+ ngx_quic_hexdump(pkt->log, "quic packet payload",
+ pkt->payload.data, pkt->payload.len);
+#endif
+
+ if (rc != NGX_OK) {
+ pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION;
+ return NGX_DECLINED;
+ }
+
+ if (badflags) {
+ /*
+ * An endpoint MUST treat receipt of a packet that has
+ * a non-zero value for these bits, after removing both
+ * packet and header protection, as a connection error
+ * of type PROTOCOL_VIOLATION.
+ */
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "quic reserved bit set in packet");
+ pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION;
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
diff --git a/src/event/ngx_event_quic_protection.h b/src/event/ngx_event_quic_protection.h
new file mode 100644
index 000000000..37d7c37b1
--- /dev/null
+++ b/src/event/ngx_event_quic_protection.h
@@ -0,0 +1,49 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_
+#define _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#define NGX_QUIC_ENCRYPTION_LAST ((ssl_encryption_application) + 1)
+
+
+typedef struct ngx_quic_secret_s {
+ ngx_str_t secret;
+ ngx_str_t key;
+ ngx_str_t iv;
+ ngx_str_t hp;
+} ngx_quic_secret_t;
+
+
+typedef struct {
+ ngx_quic_secret_t client;
+ ngx_quic_secret_t server;
+} ngx_quic_secrets_t;
+
+
+ngx_int_t ngx_quic_set_initial_secret(ngx_pool_t *pool,
+ ngx_quic_secret_t *client, ngx_quic_secret_t *server,
+ ngx_str_t *secret);
+
+int ngx_quic_set_encryption_secret(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn,
+ enum ssl_encryption_level_t level, const uint8_t *secret, size_t secret_len,
+ ngx_quic_secret_t *peer_secret);
+
+ngx_int_t ngx_quic_key_update(ngx_connection_t *c,
+ ngx_quic_secrets_t *current, ngx_quic_secrets_t *next);
+
+ngx_int_t ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn,
+ ngx_str_t *res);
+ngx_int_t ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn,
+ uint64_t *largest_pn);
+
+
+#endif /* _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ */
diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c
new file mode 100644
index 000000000..dd50ba390
--- /dev/null
+++ b/src/event/ngx_event_quic_transport.c
@@ -0,0 +1,1779 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_event_quic_transport.h>
+
+
+#if (NGX_HAVE_NONALIGNED)
+
+#define ngx_quic_parse_uint16(p) ntohs(*(uint16_t *) (p))
+#define ngx_quic_parse_uint32(p) ntohl(*(uint32_t *) (p))
+
+#define ngx_quic_write_uint16 ngx_quic_write_uint16_aligned
+#define ngx_quic_write_uint32 ngx_quic_write_uint32_aligned
+
+#else
+
+#define ngx_quic_parse_uint16(p) ((p)[0] << 8 | (p)[1])
+#define ngx_quic_parse_uint32(p) \
+ ((uint32_t) (p)[0] << 24 | (p)[1] << 16 | (p)[2] << 8 | (p)[3])
+
+#define ngx_quic_write_uint16(p, s) \
+ ((p)[0] = (u_char) ((s) >> 8), \
+ (p)[1] = (u_char) (s), \
+ (p) + sizeof(uint16_t))
+
+#define ngx_quic_write_uint32(p, s) \
+ ((p)[0] = (u_char) ((s) >> 24), \
+ (p)[1] = (u_char) ((s) >> 16), \
+ (p)[2] = (u_char) ((s) >> 8), \
+ (p)[3] = (u_char) (s), \
+ (p) + sizeof(uint32_t))
+
+#endif
+
+#define ngx_quic_write_uint24(p, s) \
+ ((p)[0] = (u_char) ((s) >> 16), \
+ (p)[1] = (u_char) ((s) >> 8), \
+ (p)[2] = (u_char) (s), \
+ (p) + 3)
+
+#define ngx_quic_write_uint16_aligned(p, s) \
+ (*(uint16_t *) (p) = htons((uint16_t) (s)), (p) + sizeof(uint16_t))
+
+#define ngx_quic_write_uint32_aligned(p, s) \
+ (*(uint32_t *) (p) = htonl((uint32_t) (s)), (p) + sizeof(uint32_t))
+
+#define ngx_quic_varint_len(value) \
+ ((value) <= 63 ? 1 \
+ : ((uint32_t) value) <= 16383 ? 2 \
+ : ((uint64_t) value) <= 1073741823 ? 4 \
+ : 8)
+
+
+static u_char *ngx_quic_parse_int(u_char *pos, u_char *end, uint64_t *out);
+static void ngx_quic_build_int(u_char **pos, uint64_t value);
+
+static u_char *ngx_quic_read_uint8(u_char *pos, u_char *end, uint8_t *value);
+static u_char *ngx_quic_read_uint32(u_char *pos, u_char *end, uint32_t *value);
+static u_char *ngx_quic_read_bytes(u_char *pos, u_char *end, size_t len,
+ u_char **out);
+static u_char *ngx_quic_copy_bytes(u_char *pos, u_char *end, size_t len,
+ u_char *dst);
+
+static ngx_int_t ngx_quic_frame_allowed(ngx_quic_header_t *pkt,
+ ngx_uint_t frame_type);
+static size_t ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack);
+static size_t ngx_quic_create_crypto(u_char *p,
+ ngx_quic_crypto_frame_t *crypto);
+static size_t ngx_quic_create_hs_done(u_char *p);
+static size_t ngx_quic_create_new_token(u_char *p,
+ ngx_quic_new_token_frame_t *token);
+static size_t ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf);
+static size_t ngx_quic_create_max_streams(u_char *p,
+ ngx_quic_max_streams_frame_t *ms);
+static size_t ngx_quic_create_max_stream_data(u_char *p,
+ ngx_quic_max_stream_data_frame_t *ms);
+static size_t ngx_quic_create_max_data(u_char *p,
+ ngx_quic_max_data_frame_t *md);
+static size_t ngx_quic_create_close(u_char *p, ngx_quic_close_frame_t *cl);
+
+static ngx_int_t ngx_quic_parse_transport_param(u_char *p, u_char *end,
+ uint16_t id, ngx_quic_tp_t *dst);
+
+
+/* literal errors indexed by corresponding value */
+static char *ngx_quic_errors[] = {
+ "NO_ERROR",
+ "INTERNAL_ERROR",
+ "CONNECTION_REFUSED",
+ "FLOW_CONTROL_ERROR",
+ "STREAM_LIMIT_ERROR",
+ "STREAM_STATE_ERROR",
+ "FINAL_SIZE_ERROR",
+ "FRAME_ENCODING_ERROR",
+ "TRANSPORT_PARAMETER_ERROR",
+ "CONNECTION_ID_LIMIT_ERROR",
+ "PROTOCOL_VIOLATION",
+ "INVALID_TOKEN",
+ "APPLICATION_ERROR",
+ "CRYPTO_BUFFER_EXCEEDED",
+ "KEY_UPDATE_ERROR",
+};
+
+
+static ngx_inline u_char *
+ngx_quic_parse_int(u_char *pos, u_char *end, uint64_t *out)
+{
+ u_char *p;
+ uint64_t value;
+ ngx_uint_t len;
+
+ if (pos >= end) {
+ return NULL;
+ }
+
+ p = pos;
+ len = 1 << ((*p & 0xc0) >> 6);
+
+ value = *p++ & 0x3f;
+
+ if ((size_t)(end - p) < (len - 1)) {
+ return NULL;
+ }
+
+ while (--len) {
+ value = (value << 8) + *p++;
+ }
+
+ *out = value;
+
+ return p;
+}
+
+
+static ngx_inline u_char *
+ngx_quic_read_uint8(u_char *pos, u_char *end, uint8_t *value)
+{
+ if ((size_t)(end - pos) < 1) {
+ return NULL;
+ }
+
+ *value = *pos;
+
+ return pos + 1;
+}
+
+
+static ngx_inline u_char *
+ngx_quic_read_uint32(u_char *pos, u_char *end, uint32_t *value)
+{
+ if ((size_t)(end - pos) < sizeof(uint32_t)) {
+ return NULL;
+ }
+
+ *value = ngx_quic_parse_uint32(pos);
+
+ return pos + sizeof(uint32_t);
+}
+
+
+static ngx_inline u_char *
+ngx_quic_read_bytes(u_char *pos, u_char *end, size_t len, u_char **out)
+{
+ if ((size_t)(end - pos) < len) {
+ return NULL;
+ }
+
+ *out = pos;
+
+ return pos + len;
+}
+
+
+static u_char *
+ngx_quic_copy_bytes(u_char *pos, u_char *end, size_t len, u_char *dst)
+{
+ if ((size_t)(end - pos) < len) {
+ return NULL;
+ }
+
+ ngx_memcpy(dst, pos, len);
+
+ return pos + len;
+}
+
+
+static void
+ngx_quic_build_int(u_char **pos, uint64_t value)
+{
+ u_char *p;
+ ngx_uint_t bits, len;
+
+ p = *pos;
+ bits = 0;
+
+ while (value >> ((8 << bits) - 2)) {
+ bits++;
+ }
+
+ len = (1 << bits);
+
+ while (len--) {
+ *p++ = value >> (len * 8);
+ }
+
+ **pos |= bits << 6;
+ *pos = p;
+}
+
+
+u_char *
+ngx_quic_error_text(uint64_t error_code)
+{
+ if (error_code >= NGX_QUIC_ERR_CRYPTO_ERROR) {
+ return (u_char *) "handshake error";
+ }
+
+ if (error_code >= NGX_QUIC_ERR_LAST) {
+ return (u_char *) "unknown error";
+ }
+
+ return (u_char *) ngx_quic_errors[error_code];
+}
+
+
+ngx_int_t
+ngx_quic_parse_long_header(ngx_quic_header_t *pkt)
+{
+ u_char *p, *end;
+ uint8_t idlen;
+
+ p = pkt->data;
+ end = pkt->data + pkt->len;
+
+#ifdef NGX_QUIC_DEBUG_PACKETS
+ ngx_quic_hexdump(pkt->log, "quic long packet in", pkt->data, pkt->len);
+#endif
+
+ p = ngx_quic_read_uint8(p, end, &pkt->flags);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "quic packet is too small to read flags");
+ return NGX_ERROR;
+ }
+
+ if (!ngx_quic_long_pkt(pkt->flags)) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic not a long packet");
+ return NGX_ERROR;
+ }
+
+ p = ngx_quic_read_uint32(p, end, &pkt->version);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "quic packet is too small to read version");
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "quic long packet flags:%xd version:%xD",
+ pkt->flags, pkt->version);
+
+ if (!(pkt->flags & NGX_QUIC_PKT_FIXED_BIT)) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic fixed bit is not set");
+ return NGX_DECLINED;
+ }
+
+ if (pkt->version != NGX_QUIC_VERSION) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "quic unsupported version: 0x%xD", pkt->version);
+ return NGX_ERROR;
+ }
+
+ p = ngx_quic_read_uint8(p, end, &idlen);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "quic packet is too small to read dcid len");
+ return NGX_ERROR;
+ }
+
+ if (idlen > NGX_QUIC_CID_LEN_MAX) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "quic packet dcid is too long");
+ return NGX_ERROR;
+ }
+
+ pkt->dcid.len = idlen;
+
+ p = ngx_quic_read_bytes(p, end, idlen, &pkt->dcid.data);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "quic packet is too small to read dcid");
+ return NGX_ERROR;
+ }
+
+ p = ngx_quic_read_uint8(p, end, &idlen);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "quic packet is too small to read scid len");
+ return NGX_ERROR;
+ }
+
+ if (idlen > NGX_QUIC_CID_LEN_MAX) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "quic packet scid is too long");
+ return NGX_ERROR;
+ }
+
+ pkt->scid.len = idlen;
+
+ p = ngx_quic_read_bytes(p, end, idlen, &pkt->scid.data);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "quic packet is too small to read scid");
+ return NGX_ERROR;
+ }
+
+ pkt->raw->pos = p;
+
+ return NGX_OK;
+}
+
+
+size_t
+ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out,
+ size_t pkt_len, u_char **pnp)
+{
+ u_char *p, *start;
+
+ p = start = out;
+
+ *p++ = pkt->flags;
+
+ p = ngx_quic_write_uint32(p, NGX_QUIC_VERSION);
+
+ *p++ = pkt->dcid.len;
+ p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len);
+
+ *p++ = pkt->scid.len;
+ p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len);
+
+ if (pkt->level == ssl_encryption_initial) {
+ ngx_quic_build_int(&p, pkt->token.len);
+ }
+
+ ngx_quic_build_int(&p, pkt_len + pkt->num_len);
+
+ *pnp = p;
+
+ switch (pkt->num_len) {
+ case 1:
+ *p++ = pkt->trunc;
+ break;
+ case 2:
+ p = ngx_quic_write_uint16(p, pkt->trunc);
+ break;
+ case 3:
+ p = ngx_quic_write_uint24(p, pkt->trunc);
+ break;
+ case 4:
+ p = ngx_quic_write_uint32(p, pkt->trunc);
+ break;
+ }
+
+ return p - start;
+}
+
+
+size_t
+ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out,
+ size_t pkt_len, u_char **pnp)
+{
+ u_char *p, *start;
+
+ p = start = out;
+
+ *p++ = pkt->flags;
+
+ p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len);
+
+ *pnp = p;
+
+ switch (pkt->num_len) {
+ case 1:
+ *p++ = pkt->trunc;
+ break;
+ case 2:
+ p = ngx_quic_write_uint16(p, pkt->trunc);
+ break;
+ case 3:
+ p = ngx_quic_write_uint24(p, pkt->trunc);
+ break;
+ case 4:
+ p = ngx_quic_write_uint32(p, pkt->trunc);
+ break;
+ }
+
+ return p - start;
+}
+
+
+size_t
+ngx_quic_create_retry_itag(ngx_quic_header_t *pkt, u_char *out,
+ u_char **start)
+{
+ u_char *p;
+
+ p = out;
+
+ *p++ = pkt->odcid.len;
+ p = ngx_cpymem(p, pkt->odcid.data, pkt->odcid.len);
+
+ *start = p;
+
+ *p++ = 0xff;
+
+ p = ngx_quic_write_uint32(p, NGX_QUIC_VERSION);
+
+ *p++ = pkt->dcid.len;
+ p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len);
+
+ *p++ = pkt->scid.len;
+ p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len);
+
+ p = ngx_cpymem(p, pkt->token.data, pkt->token.len);
+
+ return p - out;
+}
+
+
+ngx_int_t
+ngx_quic_parse_short_header(ngx_quic_header_t *pkt, ngx_str_t *dcid)
+{
+ u_char *p, *end;
+
+ p = pkt->data;
+ end = pkt->data + pkt->len;
+
+#ifdef NGX_QUIC_DEBUG_PACKETS
+ ngx_quic_hexdump(pkt->log, "quic short packet in", pkt->data, pkt->len);
+#endif
+
+ p = ngx_quic_read_uint8(p, end, &pkt->flags);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "quic packet is too small to read flags");
+ return NGX_ERROR;
+ }
+
+ if (!ngx_quic_short_pkt(pkt->flags)) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic not a short packet");
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "quic short packet flags:%xd", pkt->flags);
+
+ if (!(pkt->flags & NGX_QUIC_PKT_FIXED_BIT)) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic fixed bit is not set");
+ return NGX_DECLINED;
+ }
+
+ if (ngx_memcmp(p, dcid->data, dcid->len) != 0) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "unexpected quic dcid");
+ return NGX_ERROR;
+ }
+
+ pkt->dcid.len = dcid->len;
+
+ p = ngx_quic_read_bytes(p, end, dcid->len, &pkt->dcid.data);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "quic packet is too small to read dcid");
+ return NGX_ERROR;
+ }
+
+ pkt->raw->pos = p;
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_quic_parse_initial_header(ngx_quic_header_t *pkt)
+{
+ u_char *p, *end;
+ uint64_t varint;
+
+ p = pkt->raw->pos;
+
+ end = pkt->raw->last;
+
+ pkt->log->action = "parsing quic initial header";
+
+ p = ngx_quic_parse_int(p, end, &varint);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "quic failed to parse token length");
+ return NGX_ERROR;
+ }
+
+ pkt->token.len = varint;
+
+ p = ngx_quic_read_bytes(p, end, pkt->token.len, &pkt->token.data);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "quic packet too small to read token data");
+ return NGX_ERROR;
+ }
+
+ p = ngx_quic_parse_int(p, end, &varint);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic bad packet length");
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "quic initial packet length: %uL", varint);
+
+ if (varint > (uint64_t) ((pkt->data + pkt->len) - p)) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "quic truncated initial packet");
+ return NGX_ERROR;
+ }
+
+ pkt->raw->pos = p;
+ pkt->len = varint;
+
+#ifdef NGX_QUIC_DEBUG_PACKETS
+ ngx_quic_hexdump(pkt->log, "quic DCID", pkt->dcid.data, pkt->dcid.len);
+ ngx_quic_hexdump(pkt->log, "quic SCID", pkt->scid.data, pkt->scid.len);
+ ngx_quic_hexdump(pkt->log, "quic token", pkt->token.data, pkt->token.len);
+#endif
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt)
+{
+ u_char *p, *end;
+ uint64_t plen;
+
+ p = pkt->raw->pos;
+ end = pkt->raw->last;
+
+ pkt->log->action = "parsing quic handshake header";
+
+ p = ngx_quic_parse_int(p, end, &plen);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic bad packet length");
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "quic handshake packet length: %uL", plen);
+
+ if (plen > (uint64_t)((pkt->data + pkt->len) - p)) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "quic truncated handshake packet");
+ return NGX_ERROR;
+ }
+
+ pkt->raw->pos = p;
+ pkt->len = plen;
+
+ return NGX_OK;
+}
+
+
+#define ngx_quic_stream_bit_off(val) (((val) & 0x04) ? 1 : 0)
+#define ngx_quic_stream_bit_len(val) (((val) & 0x02) ? 1 : 0)
+#define ngx_quic_stream_bit_fin(val) (((val) & 0x01) ? 1 : 0)
+
+ssize_t
+ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end,
+ ngx_quic_frame_t *f)
+{
+ u_char *p;
+ uint64_t varint;
+ ngx_uint_t i;
+
+ p = start;
+
+ p = ngx_quic_parse_int(p, end, &varint);
+ if (p == NULL) {
+ pkt->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR;
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "quic failed to obtain quic frame type");
+ return NGX_ERROR;
+ }
+
+ f->type = varint;
+
+ if (ngx_quic_frame_allowed(pkt, f->type) != NGX_OK) {
+ pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION;
+ return NGX_ERROR;
+ }
+
+ switch (f->type) {
+
+ case NGX_QUIC_FT_CRYPTO:
+
+ p = ngx_quic_parse_int(p, end, &f->u.crypto.offset);
+ if (p == NULL) {
+ goto error;
+ }
+
+ p = ngx_quic_parse_int(p, end, &f->u.crypto.length);
+ if (p == NULL) {
+ goto error;
+ }
+
+ p = ngx_quic_read_bytes(p, end, f->u.crypto.length, &f->u.crypto.data);
+ if (p == NULL) {
+ goto error;
+ }
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "quic frame in: CRYPTO length: %uL off:%uL pp:%p",
+ f->u.crypto.length, f->u.crypto.offset,
+ f->u.crypto.data);
+
+#ifdef NGX_QUIC_DEBUG_FRAMES
+ ngx_quic_hexdump(pkt->log, "quic CRYPTO frame",
+ f->u.crypto.data, f->u.crypto.length);
+#endif
+ break;
+
+ case NGX_QUIC_FT_PADDING:
+
+ while (p < end && *p == NGX_QUIC_FT_PADDING) {
+ p++;
+ }
+
+ break;
+
+ case NGX_QUIC_FT_ACK:
+ case NGX_QUIC_FT_ACK_ECN:
+
+ if (!((p = ngx_quic_parse_int(p, end, &f->u.ack.largest))
+ && (p = ngx_quic_parse_int(p, end, &f->u.ack.delay))
+ && (p = ngx_quic_parse_int(p, end, &f->u.ack.range_count))
+ && (p = ngx_quic_parse_int(p, end, &f->u.ack.first_range))))
+ {
+ goto error;
+ }
+
+ f->u.ack.ranges_start = p;
+
+ /* process all ranges to get bounds, values are ignored */
+ for (i = 0; i < f->u.ack.range_count; i++) {
+
+ p = ngx_quic_parse_int(p, end, &varint);
+ if (p) {
+ p = ngx_quic_parse_int(p, end, &varint);
+ }
+
+ if (p == NULL) {
+ goto error;
+ }
+ }
+
+ f->u.ack.ranges_end = p;
+
+ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "quic frame in ACK largest:%uL delay:%uL"
+ " count:%uL first:%uL", f->u.ack.largest,
+ f->u.ack.delay,
+ f->u.ack.range_count,
+ f->u.ack.first_range);
+
+ if (f->type == NGX_QUIC_FT_ACK_ECN) {
+
+ if (!((p = ngx_quic_parse_int(p, end, &f->u.ack.ect0))
+ && (p = ngx_quic_parse_int(p, end, &f->u.ack.ect1))
+ && (p = ngx_quic_parse_int(p, end, &f->u.ack.ce))))
+ {
+ goto error;
+ }
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "quic ACK ECN counters: %uL %uL %uL",
+ f->u.ack.ect0, f->u.ack.ect1, f->u.ack.ce);
+ }
+
+ break;
+
+ case NGX_QUIC_FT_PING:
+ break;
+
+ case NGX_QUIC_FT_NEW_CONNECTION_ID:
+
+ p = ngx_quic_parse_int(p, end, &f->u.ncid.seqnum);
+ if (p == NULL) {
+ goto error;
+ }
+
+ p = ngx_quic_parse_int(p, end, &f->u.ncid.retire);
+ if (p == NULL) {
+ goto error;
+ }
+
+ p = ngx_quic_read_uint8(p, end, &f->u.ncid.len);
+ if (p == NULL) {
+ goto error;
+ }
+
+ p = ngx_quic_copy_bytes(p, end, f->u.ncid.len, f->u.ncid.cid);
+ if (p == NULL) {
+ goto error;
+ }
+
+ p = ngx_quic_copy_bytes(p, end, 16, f->u.ncid.srt);
+ if (p == NULL) {
+ goto error;
+ }
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "quic frame in: NCID seq:%uL retire:%uL len:%ud",
+ f->u.ncid.seqnum, f->u.ncid.retire, f->u.ncid.len);
+ break;
+
+ case NGX_QUIC_FT_CONNECTION_CLOSE:
+ case NGX_QUIC_FT_CONNECTION_CLOSE_APP:
+
+ p = ngx_quic_parse_int(p, end, &f->u.close.error_code);
+ if (p == NULL) {
+ goto error;
+ }
+
+ if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) {
+ p = ngx_quic_parse_int(p, end, &f->u.close.frame_type);
+ if (p == NULL) {
+ goto error;
+ }
+ }
+
+ p = ngx_quic_parse_int(p, end, &varint);
+ if (p == NULL) {
+ goto error;
+ }
+
+ f->u.close.reason.len = varint;
+
+ p = ngx_quic_read_bytes(p, end, f->u.close.reason.len,
+ &f->u.close.reason.data);
+ if (p == NULL) {
+ goto error;
+ }
+
+ if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) {
+
+ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "quic frame in CONNECTION_CLOSE"
+ " err:%s code:0x%xL type:0x%xL reason:'%V'",
+ ngx_quic_error_text(f->u.close.error_code),
+ f->u.close.error_code, f->u.close.frame_type,
+ &f->u.close.reason);
+ } else {
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "quic frame in: CONNECTION_CLOSE_APP:"
+ " code:0x%xL reason:'%V'",
+ f->u.close.error_code, &f->u.close.reason);
+ }
+
+ break;
+
+ case NGX_QUIC_FT_STREAM0:
+ case NGX_QUIC_FT_STREAM1:
+ case NGX_QUIC_FT_STREAM2:
+ case NGX_QUIC_FT_STREAM3:
+ case NGX_QUIC_FT_STREAM4:
+ case NGX_QUIC_FT_STREAM5:
+ case NGX_QUIC_FT_STREAM6:
+ case NGX_QUIC_FT_STREAM7:
+
+ f->u.stream.type = f->type;
+
+ f->u.stream.off = ngx_quic_stream_bit_off(f->type);
+ f->u.stream.len = ngx_quic_stream_bit_len(f->type);
+ f->u.stream.fin = ngx_quic_stream_bit_fin(f->type);
+
+ p = ngx_quic_parse_int(p, end, &f->u.stream.stream_id);
+ if (p == NULL) {
+ goto error;
+ }
+
+ if (f->type & 0x04) {
+ p = ngx_quic_parse_int(p, end, &f->u.stream.offset);
+ if (p == NULL) {
+ goto error;
+ }
+
+ } else {
+ f->u.stream.offset = 0;
+ }
+
+ if (f->type & 0x02) {
+ p = ngx_quic_parse_int(p, end, &f->u.stream.length);
+ if (p == NULL) {
+ goto error;
+ }
+
+ } else {
+ f->u.stream.length = end - p; /* up to packet end */
+ }
+
+ p = ngx_quic_read_bytes(p, end, f->u.stream.length,
+ &f->u.stream.data);
+ if (p == NULL) {
+ goto error;
+ }
+
+ ngx_log_debug7(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "quic frame in: STREAM type:0x%xi id:0x%xL offset:0x%xL "
+ "len:0x%xL bits off:%d len:%d fin:%d",
+ f->type, f->u.stream.stream_id, f->u.stream.offset,
+ f->u.stream.length, f->u.stream.off, f->u.stream.len,
+ f->u.stream.fin);
+
+#ifdef NGX_QUIC_DEBUG_FRAMES
+ ngx_quic_hexdump(pkt->log, "quic STREAM frame",
+ f->u.stream.data, f->u.stream.length);
+#endif
+ break;
+
+ case NGX_QUIC_FT_MAX_DATA:
+
+ p = ngx_quic_parse_int(p, end, &f->u.max_data.max_data);
+ if (p == NULL) {
+ goto error;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "quic frame in: MAX_DATA max_data:%uL",
+ f->u.max_data.max_data);
+ break;
+
+ case NGX_QUIC_FT_RESET_STREAM:
+
+ if (!((p = ngx_quic_parse_int(p, end, &f->u.reset_stream.id))
+ && (p = ngx_quic_parse_int(p, end, &f->u.reset_stream.error_code))
+ && (p = ngx_quic_parse_int(p, end,
+ &f->u.reset_stream.final_size))))
+ {
+ goto error;
+ }
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "quic frame in: RESET_STREAM"
+ " id:0x%xL error_code:0x%xL final_size:0x%xL",
+ f->u.reset_stream.id, f->u.reset_stream.error_code,
+ f->u.reset_stream.final_size);
+ break;
+
+ case NGX_QUIC_FT_STOP_SENDING:
+
+ p = ngx_quic_parse_int(p, end, &f->u.stop_sending.id);
+ if (p == NULL) {
+ goto error;
+ }
+
+ p = ngx_quic_parse_int(p, end, &f->u.stop_sending.error_code);
+ if (p == NULL) {
+ goto error;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "quic frame in: STOP_SENDING id:0x%xL error_code:0x%xL",
+ f->u.stop_sending.id, f->u.stop_sending.error_code);
+
+ break;
+
+ case NGX_QUIC_FT_STREAMS_BLOCKED:
+ case NGX_QUIC_FT_STREAMS_BLOCKED2:
+
+ p = ngx_quic_parse_int(p, end, &f->u.streams_blocked.limit);
+ if (p == NULL) {
+ goto error;
+ }
+
+ f->u.streams_blocked.bidi =
+ (f->type == NGX_QUIC_FT_STREAMS_BLOCKED) ? 1 : 0;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "quic frame in: STREAMS_BLOCKED limit:%uL bidi:%ui",
+ f->u.streams_blocked.limit,
+ f->u.streams_blocked.bidi);
+
+ break;
+
+ case NGX_QUIC_FT_NEW_TOKEN:
+ /* TODO: implement */
+
+ ngx_log_error(NGX_LOG_ALERT, pkt->log, 0,
+ "quic unimplemented frame type 0x%xi in packet", f->type);
+
+ break;
+
+ case NGX_QUIC_FT_MAX_STREAMS:
+ case NGX_QUIC_FT_MAX_STREAMS2:
+
+ p = ngx_quic_parse_int(p, end, &f->u.max_streams.limit);
+ if (p == NULL) {
+ goto error;
+ }
+
+ f->u.max_streams.bidi = (f->type == NGX_QUIC_FT_MAX_STREAMS) ? 1 : 0;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "quic frame in: MAX_STREAMS limit:%uL bidi:%ui",
+ f->u.max_streams.limit,
+ f->u.max_streams.bidi);
+ break;
+
+ case NGX_QUIC_FT_MAX_STREAM_DATA:
+
+ p = ngx_quic_parse_int(p, end, &f->u.max_stream_data.id);
+ if (p == NULL) {
+ goto error;
+ }
+
+ p = ngx_quic_parse_int(p, end, &f->u.max_stream_data.limit);
+ if (p == NULL) {
+ goto error;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "quic frame in: MAX_STREAM_DATA id:0x%xL limit:%uL",
+ f->u.max_stream_data.id,
+ f->u.max_stream_data.limit);
+ break;
+
+ case NGX_QUIC_FT_DATA_BLOCKED:
+
+ p = ngx_quic_parse_int(p, end, &f->u.data_blocked.limit);
+ if (p == NULL) {
+ goto error;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "quic frame in: DATA_BLOCKED limit:%uL",
+ f->u.data_blocked.limit);
+ break;
+
+ case NGX_QUIC_FT_STREAM_DATA_BLOCKED:
+
+ p = ngx_quic_parse_int(p, end, &f->u.stream_data_blocked.id);
+ if (p == NULL) {
+ goto error;
+ }
+
+ p = ngx_quic_parse_int(p, end, &f->u.stream_data_blocked.limit);
+ if (p == NULL) {
+ goto error;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "quic frame in: STREAM_DATA_BLOCKED"
+ " id:0x%xL limit:%uL",
+ f->u.stream_data_blocked.id,
+ f->u.stream_data_blocked.limit);
+ break;
+
+ case NGX_QUIC_FT_RETIRE_CONNECTION_ID:
+
+ p = ngx_quic_parse_int(p, end, &f->u.retire_cid.sequence_number);
+ if (p == NULL) {
+ goto error;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "quic frame in: RETIRE_CONNECTION_ID"
+ " sequence_number:%uL",
+ f->u.retire_cid.sequence_number);
+ break;
+
+ case NGX_QUIC_FT_PATH_CHALLENGE:
+
+ p = ngx_quic_copy_bytes(p, end, 8, f->u.path_challenge.data);
+ if (p == NULL) {
+ goto error;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "quic frame in: PATH_CHALLENGE");
+
+#ifdef NGX_QUIC_DEBUG_FRAMES
+ ngx_quic_hexdump(pkt->log, "quic PATH_CHALLENGE frame data",
+ f->u.path_challenge.data, 8);
+#endif
+ break;
+
+ case NGX_QUIC_FT_PATH_RESPONSE:
+
+ p = ngx_quic_copy_bytes(p, end, 8, f->u.path_response.data);
+ if (p == NULL) {
+ goto error;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "quic frame in: PATH_RESPONSE");
+
+#ifdef NGX_QUIC_DEBUG_FRAMES
+ ngx_quic_hexdump(pkt->log, "quic PATH_RESPONSE frame data",
+ f->u.path_response.data, 8);
+#endif
+ break;
+
+ default:
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "quic unknown frame type 0x%xi", f->type);
+ return NGX_ERROR;
+ }
+
+ return p - start;
+
+error:
+
+ pkt->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR;
+
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "quic failed to parse frame type 0x%xi", f->type);
+
+ return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_quic_frame_allowed(ngx_quic_header_t *pkt, ngx_uint_t frame_type)
+{
+ uint8_t ptype;
+
+ /* frame permissions per packet: 4 bits: IH01: 12.4, Table 3 */
+ static uint8_t ngx_quic_frame_masks[] = {
+ /* PADDING */ 0xF,
+ /* PING */ 0xF,
+ /* ACK */ 0xD,
+ /* ACK_ECN */ 0xD,
+ /* RESET_STREAM */ 0x3,
+ /* STOP_SENDING */ 0x3,
+ /* CRYPTO */ 0xD,
+ /* NEW_TOKEN */ 0x0, /* only sent by server */
+ /* STREAM0 */ 0x3,
+ /* STREAM1 */ 0x3,
+ /* STREAM2 */ 0x3,
+ /* STREAM3 */ 0x3,
+ /* STREAM4 */ 0x3,
+ /* STREAM5 */ 0x3,
+ /* STREAM6 */ 0x3,
+ /* STREAM7 */ 0x3,
+ /* MAX_DATA */ 0x3,
+ /* MAX_STREAM_DATA */ 0x3,
+ /* MAX_STREAMS */ 0x3,
+ /* MAX_STREAMS2 */ 0x3,
+ /* DATA_BLOCKED */ 0x3,
+ /* STREAM_DATA_BLOCKED */ 0x3,
+ /* STREAMS_BLOCKED */ 0x3,
+ /* STREAMS_BLOCKED2 */ 0x3,
+ /* NEW_CONNECTION_ID */ 0x3,
+ /* RETIRE_CONNECTION_ID */ 0x3,
+ /* PATH_CHALLENGE */ 0x3,
+ /* PATH_RESPONSE */ 0x3,
+#if (NGX_QUIC_DRAFT_VERSION >= 28)
+ /* CONNECTION_CLOSE */ 0xF,
+ /* CONNECTION_CLOSE2 */ 0x3,
+#else
+ /* CONNECTION_CLOSE */ 0xD,
+ /* CONNECTION_CLOSE2 */ 0x1,
+#endif
+ /* HANDSHAKE_DONE */ 0x0, /* only sent by server */
+ };
+
+ if (ngx_quic_long_pkt(pkt->flags)) {
+
+ if (ngx_quic_pkt_in(pkt->flags)) {
+ ptype = 8; /* initial */
+
+ } else if (ngx_quic_pkt_hs(pkt->flags)) {
+ ptype = 4; /* handshake */
+
+ } else {
+ ptype = 2; /* zero-rtt */
+ }
+
+ } else {
+ ptype = 1; /* application data */
+ }
+
+ if (ptype & ngx_quic_frame_masks[frame_type]) {
+ return NGX_OK;
+ }
+
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "quic frame type 0x%xi is not "
+ "allowed in packet with flags 0x%xd",
+ frame_type, pkt->flags);
+
+ return NGX_DECLINED;
+}
+
+
+ssize_t
+ngx_quic_parse_ack_range(ngx_quic_header_t *pkt, u_char *start, u_char *end,
+ uint64_t *gap, uint64_t *range)
+{
+ u_char *p;
+
+ p = start;
+
+ p = ngx_quic_parse_int(p, end, gap);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "quic failed to parse ack frame gap");
+ return NGX_ERROR;
+ }
+
+ p = ngx_quic_parse_int(p, end, range);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "quic failed to parse ack frame range");
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "quic ACK range: gap %uL range %uL", *gap, *range);
+
+ return p - start;
+}
+
+
+ssize_t
+ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f)
+{
+ /*
+ * QUIC-recovery, section 2:
+ *
+ * Ack-eliciting Frames: All frames other than ACK, PADDING, and
+ * CONNECTION_CLOSE are considered ack-eliciting.
+ */
+ f->need_ack = 1;
+
+ switch (f->type) {
+ case NGX_QUIC_FT_ACK:
+ f->need_ack = 0;
+ return ngx_quic_create_ack(p, &f->u.ack);
+
+ case NGX_QUIC_FT_CRYPTO:
+ return ngx_quic_create_crypto(p, &f->u.crypto);
+
+ case NGX_QUIC_FT_HANDSHAKE_DONE:
+ return ngx_quic_create_hs_done(p);
+
+ case NGX_QUIC_FT_NEW_TOKEN:
+ return ngx_quic_create_new_token(p, &f->u.token);
+
+ case NGX_QUIC_FT_STREAM0:
+ case NGX_QUIC_FT_STREAM1:
+ case NGX_QUIC_FT_STREAM2:
+ case NGX_QUIC_FT_STREAM3:
+ case NGX_QUIC_FT_STREAM4:
+ case NGX_QUIC_FT_STREAM5:
+ case NGX_QUIC_FT_STREAM6:
+ case NGX_QUIC_FT_STREAM7:
+ return ngx_quic_create_stream(p, &f->u.stream);
+
+ case NGX_QUIC_FT_CONNECTION_CLOSE:
+ case NGX_QUIC_FT_CONNECTION_CLOSE_APP:
+ f->need_ack = 0;
+ return ngx_quic_create_close(p, &f->u.close);
+
+ case NGX_QUIC_FT_MAX_STREAMS:
+ return ngx_quic_create_max_streams(p, &f->u.max_streams);
+
+ case NGX_QUIC_FT_MAX_STREAM_DATA:
+ return ngx_quic_create_max_stream_data(p, &f->u.max_stream_data);
+
+ case NGX_QUIC_FT_MAX_DATA:
+ return ngx_quic_create_max_data(p, &f->u.max_data);
+
+ default:
+ /* BUG: unsupported frame type generated */
+ return NGX_ERROR;
+ }
+}
+
+
+static size_t
+ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack)
+{
+ size_t len;
+ u_char *start;
+
+ /* minimal ACK packet */
+
+ if (p == NULL) {
+ len = ngx_quic_varint_len(NGX_QUIC_FT_ACK);
+ len += ngx_quic_varint_len(ack->largest);
+ len += ngx_quic_varint_len(ack->delay);
+ len += ngx_quic_varint_len(0);
+ len += ngx_quic_varint_len(ack->first_range);
+
+ return len;
+ }
+
+ start = p;
+
+ ngx_quic_build_int(&p, NGX_QUIC_FT_ACK);
+ ngx_quic_build_int(&p, ack->largest);
+ ngx_quic_build_int(&p, ack->delay);
+ ngx_quic_build_int(&p, 0);
+ ngx_quic_build_int(&p, ack->first_range);
+
+ return p - start;
+}
+
+
+static size_t
+ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto)
+{
+ size_t len;
+ u_char *start;
+
+ if (p == NULL) {
+ len = ngx_quic_varint_len(NGX_QUIC_FT_CRYPTO);
+ len += ngx_quic_varint_len(crypto->offset);
+ len += ngx_quic_varint_len(crypto->length);
+ len += crypto->length;
+
+ return len;
+ }
+
+ start = p;
+
+ ngx_quic_build_int(&p, NGX_QUIC_FT_CRYPTO);
+ ngx_quic_build_int(&p, crypto->offset);
+ ngx_quic_build_int(&p, crypto->length);
+ p = ngx_cpymem(p, crypto->data, crypto->length);
+
+ return p - start;
+}
+
+
+static size_t
+ngx_quic_create_hs_done(u_char *p)
+{
+ u_char *start;
+
+ if (p == NULL) {
+ return ngx_quic_varint_len(NGX_QUIC_FT_HANDSHAKE_DONE);
+ }
+
+ start = p;
+
+ ngx_quic_build_int(&p, NGX_QUIC_FT_HANDSHAKE_DONE);
+
+ return p - start;
+}
+
+
+static size_t
+ngx_quic_create_new_token(u_char *p, ngx_quic_new_token_frame_t *token)
+{
+ size_t len;
+ u_char *start;
+
+ if (p == NULL) {
+ len = ngx_quic_varint_len(NGX_QUIC_FT_NEW_TOKEN);
+ len += ngx_quic_varint_len(token->length);
+ len += token->length;
+
+ return len;
+ }
+
+ start = p;
+
+ ngx_quic_build_int(&p, NGX_QUIC_FT_NEW_TOKEN);
+ ngx_quic_build_int(&p, token->length);
+ p = ngx_cpymem(p, token->data, token->length);
+
+ return p - start;
+}
+
+
+static size_t
+ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf)
+{
+ size_t len;
+ u_char *start;
+
+ if (p == NULL) {
+ len = ngx_quic_varint_len(sf->type);
+
+ if (sf->off) {
+ len += ngx_quic_varint_len(sf->offset);
+ }
+
+ len += ngx_quic_varint_len(sf->stream_id);
+
+ /* length is always present in generated frames */
+ len += ngx_quic_varint_len(sf->length);
+
+ len += sf->length;
+
+ return len;
+ }
+
+ start = p;
+
+ ngx_quic_build_int(&p, sf->type);
+ ngx_quic_build_int(&p, sf->stream_id);
+
+ if (sf->off) {
+ ngx_quic_build_int(&p, sf->offset);
+ }
+
+ /* length is always present in generated frames */
+ ngx_quic_build_int(&p, sf->length);
+
+ p = ngx_cpymem(p, sf->data, sf->length);
+
+ return p - start;
+}
+
+
+static size_t
+ngx_quic_create_max_streams(u_char *p, ngx_quic_max_streams_frame_t *ms)
+{
+ size_t len;
+ u_char *start;
+ ngx_uint_t type;
+
+ type = ms->bidi ? NGX_QUIC_FT_MAX_STREAMS : NGX_QUIC_FT_MAX_STREAMS2;
+
+ if (p == NULL) {
+ len = ngx_quic_varint_len(type);
+ len += ngx_quic_varint_len(ms->limit);
+ return len;
+ }
+
+ start = p;
+
+ ngx_quic_build_int(&p, type);
+ ngx_quic_build_int(&p, ms->limit);
+
+ return p - start;
+}
+
+
+static ngx_int_t
+ngx_quic_parse_transport_param(u_char *p, u_char *end, uint16_t id,
+ ngx_quic_tp_t *dst)
+{
+ uint64_t varint;
+ ngx_str_t str;
+
+ varint = 0;
+ ngx_str_null(&str);
+
+ switch (id) {
+
+ case NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION:
+ /* zero-length option */
+ if (end - p != 0) {
+ return NGX_ERROR;
+ }
+ dst->disable_active_migration = 1;
+ return NGX_OK;
+
+ case NGX_QUIC_TP_MAX_IDLE_TIMEOUT:
+ case NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE:
+ case NGX_QUIC_TP_INITIAL_MAX_DATA:
+ case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL:
+ case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE:
+ case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI:
+ case NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI:
+ case NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI:
+ case NGX_QUIC_TP_ACK_DELAY_EXPONENT:
+ case NGX_QUIC_TP_MAX_ACK_DELAY:
+ case NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT:
+
+ p = ngx_quic_parse_int(p, end, &varint);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+ break;
+
+ case NGX_QUIC_TP_INITIAL_SCID:
+
+ str.len = end - p;
+ p = ngx_quic_read_bytes(p, end, str.len, &str.data);
+ break;
+
+ default:
+ return NGX_DECLINED;
+ }
+
+ switch (id) {
+
+ case NGX_QUIC_TP_MAX_IDLE_TIMEOUT:
+ dst->max_idle_timeout = varint;
+ break;
+
+ case NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE:
+ dst->max_udp_payload_size = varint;
+ break;
+
+ case NGX_QUIC_TP_INITIAL_MAX_DATA:
+ dst->initial_max_data = varint;
+ break;
+
+ case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL:
+ dst->initial_max_stream_data_bidi_local = varint;
+ break;
+
+ case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE:
+ dst->initial_max_stream_data_bidi_remote = varint;
+ break;
+
+ case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI:
+ dst->initial_max_stream_data_uni = varint;
+ break;
+
+ case NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI:
+ dst->initial_max_streams_bidi = varint;
+ break;
+
+ case NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI:
+ dst->initial_max_streams_uni = varint;
+ break;
+
+ case NGX_QUIC_TP_ACK_DELAY_EXPONENT:
+ dst->ack_delay_exponent = varint;
+ break;
+
+ case NGX_QUIC_TP_MAX_ACK_DELAY:
+ dst->max_ack_delay = varint;
+ break;
+
+ case NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT:
+ dst->active_connection_id_limit = varint;
+ break;
+
+ case NGX_QUIC_TP_INITIAL_SCID:
+ dst->initial_scid = str;
+ break;
+
+ default:
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp,
+ ngx_log_t *log)
+{
+ uint64_t id, len;
+ ngx_int_t rc;
+
+ while (p < end) {
+ p = ngx_quic_parse_int(p, end, &id);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, log, 0,
+ "quic failed to parse transport param id");
+ return NGX_ERROR;
+ }
+
+ switch (id) {
+ case NGX_QUIC_TP_ORIGINAL_DCID:
+ case NGX_QUIC_TP_PREFERRED_ADDRESS:
+ case NGX_QUIC_TP_RETRY_SCID:
+ case NGX_QUIC_TP_STATELESS_RESET_TOKEN:
+ ngx_log_error(NGX_LOG_INFO, log, 0,
+ "quic client sent forbidden transport param"
+ " id 0x%xL", id);
+ return NGX_ERROR;
+ }
+
+ p = ngx_quic_parse_int(p, end, &len);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, log, 0,
+ "quic failed to parse"
+ " transport param id 0x%xL length", id);
+ return NGX_ERROR;
+ }
+
+ rc = ngx_quic_parse_transport_param(p, p + len, id, tp);
+
+ if (rc == NGX_ERROR) {
+ ngx_log_error(NGX_LOG_INFO, log, 0,
+ "quic failed to parse"
+ " transport param id 0x%xL data", id);
+ return NGX_ERROR;
+ }
+
+ if (rc == NGX_DECLINED) {
+ ngx_log_error(NGX_LOG_INFO, log, 0,
+ "quic unknown transport param id 0x%xL, skipped", id);
+ }
+
+ p += len;
+ }
+
+ if (p != end) {
+ ngx_log_error(NGX_LOG_INFO, log, 0,
+ "quic trailing garbage in"
+ " transport parameters: %ui bytes",
+ end - p);
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, log, 0,
+ "quic transport parameters parsed ok");
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
+ "quic tp disable active migration: %ui",
+ tp->disable_active_migration);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp idle_timeout: %ui",
+ tp->max_idle_timeout);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
+ "quic tp max_udp_payload_size: %ui",
+ tp->max_udp_payload_size);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp max_data: %ui",
+ tp->initial_max_data);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
+ "quic tp max_stream_data_bidi_local: %ui",
+ tp->initial_max_stream_data_bidi_local);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
+ "quic tp max_stream_data_bidi_remote: %ui",
+ tp->initial_max_stream_data_bidi_remote);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
+ "quic tp max_stream_data_uni: %ui",
+ tp->initial_max_stream_data_uni);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
+ "quic tp initial_max_streams_bidi: %ui",
+ tp->initial_max_streams_bidi);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
+ "quic tp initial_max_streams_uni: %ui",
+ tp->initial_max_streams_uni);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
+ "quic tp ack_delay_exponent: %ui",
+ tp->ack_delay_exponent);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp max_ack_delay: %ui",
+ tp->max_ack_delay);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
+ "quic tp active_connection_id_limit: %ui",
+ tp->active_connection_id_limit);
+
+#if (NGX_QUIC_DRAFT_VERSION >= 28)
+ ngx_quic_hexdump(log, "quic tp initial_source_connection_id:",
+ tp->initial_scid.data, tp->initial_scid.len);
+#endif
+
+ return NGX_OK;
+}
+
+
+static size_t
+ngx_quic_create_max_stream_data(u_char *p, ngx_quic_max_stream_data_frame_t *ms)
+{
+ size_t len;
+ u_char *start;
+
+ if (p == NULL) {
+ len = ngx_quic_varint_len(NGX_QUIC_FT_MAX_STREAM_DATA);
+ len += ngx_quic_varint_len(ms->id);
+ len += ngx_quic_varint_len(ms->limit);
+ return len;
+ }
+
+ start = p;
+
+ ngx_quic_build_int(&p, NGX_QUIC_FT_MAX_STREAM_DATA);
+ ngx_quic_build_int(&p, ms->id);
+ ngx_quic_build_int(&p, ms->limit);
+
+ return p - start;
+}
+
+
+static size_t
+ngx_quic_create_max_data(u_char *p, ngx_quic_max_data_frame_t *md)
+{
+ size_t len;
+ u_char *start;
+
+ if (p == NULL) {
+ len = ngx_quic_varint_len(NGX_QUIC_FT_MAX_DATA);
+ len += ngx_quic_varint_len(md->max_data);
+ return len;
+ }
+
+ start = p;
+
+ ngx_quic_build_int(&p, NGX_QUIC_FT_MAX_DATA);
+ ngx_quic_build_int(&p, md->max_data);
+
+ return p - start;
+}
+
+
+ssize_t
+ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp,
+ size_t *clen)
+{
+ u_char *p;
+ size_t len;
+
+#define ngx_quic_tp_len(id, value) \
+ ngx_quic_varint_len(id) \
+ + ngx_quic_varint_len(value) \
+ + ngx_quic_varint_len(ngx_quic_varint_len(value))
+
+#define ngx_quic_tp_vint(id, value) \
+ do { \
+ ngx_quic_build_int(&p, id); \
+ ngx_quic_build_int(&p, ngx_quic_varint_len(value)); \
+ ngx_quic_build_int(&p, value); \
+ } while (0)
+
+#define ngx_quic_tp_strlen(id, value) \
+ ngx_quic_varint_len(id) \
+ + ngx_quic_varint_len(value.len) \
+ + value.len
+
+#define ngx_quic_tp_str(id, value) \
+ do { \
+ ngx_quic_build_int(&p, id); \
+ ngx_quic_build_int(&p, value.len); \
+ p = ngx_cpymem(p, value.data, value.len); \
+ } while (0)
+
+ p = pos;
+
+ len = ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_DATA, tp->initial_max_data);
+
+ len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI,
+ tp->initial_max_streams_uni);
+
+ len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI,
+ tp->initial_max_streams_bidi);
+
+ len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL,
+ tp->initial_max_stream_data_bidi_local);
+
+ len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE,
+ tp->initial_max_stream_data_bidi_remote);
+
+ len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI,
+ tp->initial_max_stream_data_uni);
+
+ len += ngx_quic_tp_len(NGX_QUIC_TP_MAX_IDLE_TIMEOUT,
+ tp->max_idle_timeout);
+
+ if (clen) {
+ *clen = len;
+ }
+
+ len += ngx_quic_tp_len(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT,
+ tp->active_connection_id_limit);
+
+#if (NGX_QUIC_DRAFT_VERSION >= 28)
+ len += ngx_quic_tp_strlen(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid);
+ len += ngx_quic_tp_strlen(NGX_QUIC_TP_INITIAL_SCID, tp->initial_scid);
+
+ if (tp->retry_scid.len) {
+ len += ngx_quic_tp_strlen(NGX_QUIC_TP_RETRY_SCID, tp->retry_scid);
+ }
+#else
+ if (tp->original_dcid.len) {
+ len += ngx_quic_tp_strlen(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid);
+ }
+#endif
+
+ if (pos == NULL) {
+ return len;
+ }
+
+ ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_DATA,
+ tp->initial_max_data);
+
+ ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI,
+ tp->initial_max_streams_uni);
+
+ ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI,
+ tp->initial_max_streams_bidi);
+
+ ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL,
+ tp->initial_max_stream_data_bidi_local);
+
+ ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE,
+ tp->initial_max_stream_data_bidi_remote);
+
+ ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI,
+ tp->initial_max_stream_data_uni);
+
+ ngx_quic_tp_vint(NGX_QUIC_TP_MAX_IDLE_TIMEOUT,
+ tp->max_idle_timeout);
+
+ ngx_quic_tp_vint(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT,
+ tp->active_connection_id_limit);
+
+#if (NGX_QUIC_DRAFT_VERSION >= 28)
+ ngx_quic_tp_str(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid);
+ ngx_quic_tp_str(NGX_QUIC_TP_INITIAL_SCID, tp->initial_scid);
+
+ if (tp->retry_scid.len) {
+ ngx_quic_tp_str(NGX_QUIC_TP_RETRY_SCID, tp->retry_scid);
+ }
+#else
+ if (tp->original_dcid.len) {
+ ngx_quic_tp_str(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid);
+ }
+#endif
+
+ return p - pos;
+}
+
+
+static size_t
+ngx_quic_create_close(u_char *p, ngx_quic_close_frame_t *cl)
+{
+ size_t len;
+ u_char *start;
+ ngx_uint_t type;
+
+ type = cl->app ? NGX_QUIC_FT_CONNECTION_CLOSE_APP
+ : NGX_QUIC_FT_CONNECTION_CLOSE;
+
+ if (p == NULL) {
+ len = ngx_quic_varint_len(type);
+ len += ngx_quic_varint_len(cl->error_code);
+
+ if (!cl->app) {
+ len += ngx_quic_varint_len(cl->frame_type);
+ }
+
+ len += ngx_quic_varint_len(cl->reason.len);
+ len += cl->reason.len;
+
+ return len;
+ }
+
+ start = p;
+
+ ngx_quic_build_int(&p, type);
+ ngx_quic_build_int(&p, cl->error_code);
+
+ if (!cl->app) {
+ ngx_quic_build_int(&p, cl->frame_type);
+ }
+
+ ngx_quic_build_int(&p, cl->reason.len);
+ p = ngx_cpymem(p, cl->reason.data, cl->reason.len);
+
+ return p - start;
+}
diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h
new file mode 100644
index 000000000..b1dd5aef5
--- /dev/null
+++ b/src/event/ngx_event_quic_transport.h
@@ -0,0 +1,347 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_EVENT_QUIC_WIRE_H_INCLUDED_
+#define _NGX_EVENT_QUIC_WIRE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+/* QUIC flags in first byte, see quic-transport 17.2 and 17.3 */
+
+#define NGX_QUIC_PKT_LONG 0x80 /* header form */
+#define NGX_QUIC_PKT_FIXED_BIT 0x40
+#define NGX_QUIC_PKT_TYPE 0x30 /* in long packet */
+#define NGX_QUIC_PKT_KPHASE 0x04 /* in short packet */
+
+#define NGX_QUIC_PKT_LONG_RESERVED_BIT 0x0C
+#define NGX_QUIC_PKT_SHORT_RESERVED_BIT 0x18
+
+#define ngx_quic_long_pkt(flags) ((flags) & NGX_QUIC_PKT_LONG)
+#define ngx_quic_short_pkt(flags) (((flags) & NGX_QUIC_PKT_LONG) == 0)
+
+/* Long packet types */
+#define NGX_QUIC_PKT_INITIAL 0x00
+#define NGX_QUIC_PKT_ZRTT 0x10
+#define NGX_QUIC_PKT_HANDSHAKE 0x20
+#define NGX_QUIC_PKT_RETRY 0x30
+
+#define ngx_quic_pkt_in(flags) \
+ (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_INITIAL)
+#define ngx_quic_pkt_zrtt(flags) \
+ (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_ZRTT)
+#define ngx_quic_pkt_hs(flags) \
+ (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_HANDSHAKE)
+#define ngx_quic_pkt_retry(flags) \
+ (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_RETRY)
+
+
+/* 12.4. Frames and Frame Types */
+#define NGX_QUIC_FT_PADDING 0x00
+#define NGX_QUIC_FT_PING 0x01
+#define NGX_QUIC_FT_ACK 0x02
+#define NGX_QUIC_FT_ACK_ECN 0x03
+#define NGX_QUIC_FT_RESET_STREAM 0x04
+#define NGX_QUIC_FT_STOP_SENDING 0x05
+#define NGX_QUIC_FT_CRYPTO 0x06
+#define NGX_QUIC_FT_NEW_TOKEN 0x07
+#define NGX_QUIC_FT_STREAM0 0x08
+#define NGX_QUIC_FT_STREAM1 0x09
+#define NGX_QUIC_FT_STREAM2 0x0A
+#define NGX_QUIC_FT_STREAM3 0x0B
+#define NGX_QUIC_FT_STREAM4 0x0C
+#define NGX_QUIC_FT_STREAM5 0x0D
+#define NGX_QUIC_FT_STREAM6 0x0E
+#define NGX_QUIC_FT_STREAM7 0x0F
+#define NGX_QUIC_FT_MAX_DATA 0x10
+#define NGX_QUIC_FT_MAX_STREAM_DATA 0x11
+#define NGX_QUIC_FT_MAX_STREAMS 0x12
+#define NGX_QUIC_FT_MAX_STREAMS2 0x13
+#define NGX_QUIC_FT_DATA_BLOCKED 0x14
+#define NGX_QUIC_FT_STREAM_DATA_BLOCKED 0x15
+#define NGX_QUIC_FT_STREAMS_BLOCKED 0x16
+#define NGX_QUIC_FT_STREAMS_BLOCKED2 0x17
+#define NGX_QUIC_FT_NEW_CONNECTION_ID 0x18
+#define NGX_QUIC_FT_RETIRE_CONNECTION_ID 0x19
+#define NGX_QUIC_FT_PATH_CHALLENGE 0x1A
+#define NGX_QUIC_FT_PATH_RESPONSE 0x1B
+#define NGX_QUIC_FT_CONNECTION_CLOSE 0x1C
+#define NGX_QUIC_FT_CONNECTION_CLOSE_APP 0x1D
+#define NGX_QUIC_FT_HANDSHAKE_DONE 0x1E
+
+/* 22.4. QUIC Transport Error Codes Registry */
+/* Keep in sync with ngx_quic_errors[] */
+#define NGX_QUIC_ERR_NO_ERROR 0x00
+#define NGX_QUIC_ERR_INTERNAL_ERROR 0x01
+#define NGX_QUIC_ERR_CONNECTION_REFUSED 0x02
+#define NGX_QUIC_ERR_FLOW_CONTROL_ERROR 0x03
+#define NGX_QUIC_ERR_STREAM_LIMIT_ERROR 0x04
+#define NGX_QUIC_ERR_STREAM_STATE_ERROR 0x05
+#define NGX_QUIC_ERR_FINAL_SIZE_ERROR 0x06
+#define NGX_QUIC_ERR_FRAME_ENCODING_ERROR 0x07
+#define NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR 0x08
+#define NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR 0x09
+#define NGX_QUIC_ERR_PROTOCOL_VIOLATION 0x0A
+#define NGX_QUIC_ERR_INVALID_TOKEN 0x0B
+#define NGX_QUIC_ERR_APPLICATION_ERROR 0x0C
+#define NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED 0x0D
+#define NGX_QUIC_ERR_KEY_UPDATE_ERROR 0x0E
+
+#define NGX_QUIC_ERR_LAST 0x0F
+#define NGX_QUIC_ERR_CRYPTO_ERROR 0x100
+
+#define NGX_QUIC_ERR_CRYPTO(e) (NGX_QUIC_ERR_CRYPTO_ERROR + (e))
+
+
+/* Transport parameters */
+#define NGX_QUIC_TP_ORIGINAL_DCID 0x00
+#define NGX_QUIC_TP_MAX_IDLE_TIMEOUT 0x01
+#define NGX_QUIC_TP_STATELESS_RESET_TOKEN 0x02
+#define NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE 0x03
+#define NGX_QUIC_TP_INITIAL_MAX_DATA 0x04
+#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL 0x05
+#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE 0x06
+#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI 0x07
+#define NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI 0x08
+#define NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI 0x09
+#define NGX_QUIC_TP_ACK_DELAY_EXPONENT 0x0A
+#define NGX_QUIC_TP_MAX_ACK_DELAY 0x0B
+#define NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION 0x0C
+#define NGX_QUIC_TP_PREFERRED_ADDRESS 0x0D
+#define NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT 0x0E
+#define NGX_QUIC_TP_INITIAL_SCID 0x0F
+#define NGX_QUIC_TP_RETRY_SCID 0x10
+
+#define NGX_QUIC_CID_LEN_MIN 8
+#define NGX_QUIC_CID_LEN_MAX 20
+
+
+typedef struct {
+ uint64_t largest;
+ uint64_t delay;
+ uint64_t range_count;
+ uint64_t first_range;
+ uint64_t ect0;
+ uint64_t ect1;
+ uint64_t ce;
+ u_char *ranges_start;
+ u_char *ranges_end;
+} ngx_quic_ack_frame_t;
+
+
+typedef struct {
+ uint64_t seqnum;
+ uint64_t retire;
+ uint8_t len;
+ u_char cid[NGX_QUIC_CID_LEN_MAX];
+ u_char srt[16];
+} ngx_quic_new_conn_id_frame_t;
+
+
+typedef struct {
+ uint64_t length;
+ u_char *data;
+} ngx_quic_new_token_frame_t;
+
+/*
+ * common layout for CRYPTO and STREAM frames;
+ * conceptually, CRYPTO frame is also a stream
+ * frame lacking some properties
+ */
+typedef struct {
+ uint64_t offset;
+ uint64_t length;
+ u_char *data;
+} ngx_quic_ordered_frame_t;
+
+typedef ngx_quic_ordered_frame_t ngx_quic_crypto_frame_t;
+
+
+typedef struct {
+ /* initial fields same as in ngx_quic_ordered_frame_t */
+ uint64_t offset;
+ uint64_t length;
+ u_char *data;
+
+ uint8_t type;
+ uint64_t stream_id;
+ unsigned off:1;
+ unsigned len:1;
+ unsigned fin:1;
+} ngx_quic_stream_frame_t;
+
+
+typedef struct {
+ uint64_t max_data;
+} ngx_quic_max_data_frame_t;
+
+
+typedef struct {
+ uint64_t error_code;
+ uint64_t frame_type;
+ ngx_str_t reason;
+ ngx_uint_t app; /* unsigned app:1; */
+} ngx_quic_close_frame_t;
+
+
+typedef struct {
+ uint64_t id;
+ uint64_t error_code;
+ uint64_t final_size;
+} ngx_quic_reset_stream_frame_t;
+
+
+typedef struct {
+ uint64_t id;
+ uint64_t error_code;
+} ngx_quic_stop_sending_frame_t;
+
+
+typedef struct {
+ uint64_t limit;
+ ngx_uint_t bidi; /* unsigned: bidi:1 */
+} ngx_quic_streams_blocked_frame_t;
+
+
+typedef struct {
+ uint64_t limit;
+ ngx_uint_t bidi; /* unsigned: bidi:1 */
+} ngx_quic_max_streams_frame_t;
+
+
+typedef struct {
+ uint64_t id;
+ uint64_t limit;
+} ngx_quic_max_stream_data_frame_t;
+
+
+typedef struct {
+ uint64_t limit;
+} ngx_quic_data_blocked_frame_t;
+
+
+typedef struct {
+ uint64_t id;
+ uint64_t limit;
+} ngx_quic_stream_data_blocked_frame_t;
+
+
+typedef struct {
+ uint64_t sequence_number;
+} ngx_quic_retire_cid_frame_t;
+
+
+typedef struct {
+ u_char data[8];
+} ngx_quic_path_challenge_frame_t;
+
+
+typedef struct ngx_quic_frame_s ngx_quic_frame_t;
+
+struct ngx_quic_frame_s {
+ ngx_uint_t type;
+ enum ssl_encryption_level_t level;
+ ngx_queue_t queue;
+ uint64_t pnum;
+ size_t plen;
+ ngx_msec_t first;
+ ngx_msec_t last;
+ ssize_t len;
+ ngx_uint_t need_ack;
+ /* unsigned need_ack:1; */
+
+ u_char *data;
+ union {
+ ngx_quic_ack_frame_t ack;
+ ngx_quic_crypto_frame_t crypto;
+ ngx_quic_ordered_frame_t ord;
+ ngx_quic_new_conn_id_frame_t ncid;
+ ngx_quic_new_token_frame_t token;
+ ngx_quic_stream_frame_t stream;
+ ngx_quic_max_data_frame_t max_data;
+ ngx_quic_close_frame_t close;
+ ngx_quic_reset_stream_frame_t reset_stream;
+ ngx_quic_stop_sending_frame_t stop_sending;
+ ngx_quic_streams_blocked_frame_t streams_blocked;
+ ngx_quic_max_streams_frame_t max_streams;
+ ngx_quic_max_stream_data_frame_t max_stream_data;
+ ngx_quic_data_blocked_frame_t data_blocked;
+ ngx_quic_stream_data_blocked_frame_t stream_data_blocked;
+ ngx_quic_retire_cid_frame_t retire_cid;
+ ngx_quic_path_challenge_frame_t path_challenge;
+ ngx_quic_path_challenge_frame_t path_response;
+ } u;
+ u_char info[128]; /* for debug */
+};
+
+
+typedef struct {
+ ngx_log_t *log;
+
+ struct ngx_quic_secret_s *secret;
+ struct ngx_quic_secret_s *next;
+ struct timeval received;
+ uint64_t number;
+ uint8_t num_len;
+ uint32_t trunc;
+ uint8_t flags;
+ uint32_t version;
+ ngx_str_t token;
+ enum ssl_encryption_level_t level;
+ ngx_uint_t error;
+
+ /* filled in by parser */
+ ngx_buf_t *raw; /* udp datagram */
+
+ u_char *data; /* quic packet */
+ size_t len;
+
+ /* cleartext fields */
+ ngx_str_t odcid; /* retry packet tag */
+ ngx_str_t dcid;
+ ngx_str_t scid;
+ uint64_t pn;
+ u_char *plaintext;
+ ngx_str_t payload; /* decrypted data */
+
+ unsigned need_ack:1;
+ unsigned key_phase:1;
+ unsigned key_update:1;
+} ngx_quic_header_t;
+
+
+u_char *ngx_quic_error_text(uint64_t error_code);
+
+ngx_int_t ngx_quic_parse_long_header(ngx_quic_header_t *pkt);
+size_t ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out,
+ size_t pkt_len, u_char **pnp);
+
+ngx_int_t ngx_quic_parse_short_header(ngx_quic_header_t *pkt,
+ ngx_str_t *dcid);
+size_t ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out,
+ size_t pkt_len, u_char **pnp);
+
+size_t ngx_quic_create_retry_itag(ngx_quic_header_t *pkt, u_char *out,
+ u_char **start);
+
+ngx_int_t ngx_quic_parse_initial_header(ngx_quic_header_t *pkt);
+ngx_int_t ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt);
+
+ssize_t ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end,
+ ngx_quic_frame_t *frame);
+ssize_t ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f);
+
+ssize_t ngx_quic_parse_ack_range(ngx_quic_header_t *pkt, u_char *start,
+ u_char *end, uint64_t *gap, uint64_t *range);
+
+ngx_int_t ngx_quic_parse_transport_params(u_char *p, u_char *end,
+ ngx_quic_tp_t *tp, ngx_log_t *log);
+ssize_t ngx_quic_create_transport_params(u_char *p, u_char *end,
+ ngx_quic_tp_t *tp, size_t *clen);
+
+#endif /* _NGX_EVENT_QUIC_WIRE_H_INCLUDED_ */
diff --git a/src/http/modules/ngx_http_chunked_filter_module.c b/src/http/modules/ngx_http_chunked_filter_module.c
index 4d6fd3eed..87b032496 100644
--- a/src/http/modules/ngx_http_chunked_filter_module.c
+++ b/src/http/modules/ngx_http_chunked_filter_module.c
@@ -18,7 +18,7 @@ typedef struct {
static ngx_int_t ngx_http_chunked_filter_init(ngx_conf_t *cf);
static ngx_chain_t *ngx_http_chunked_create_trailers(ngx_http_request_t *r,
- ngx_http_chunked_filter_ctx_t *ctx);
+ ngx_http_chunked_filter_ctx_t *ctx, size_t size);
static ngx_http_module_t ngx_http_chunked_filter_module_ctx = {
@@ -106,6 +106,7 @@ ngx_http_chunked_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
u_char *chunk;
off_t size;
+ size_t n;
ngx_int_t rc;
ngx_buf_t *b;
ngx_chain_t *out, *cl, *tl, **ll;
@@ -161,29 +162,50 @@ ngx_http_chunked_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
chunk = b->start;
if (chunk == NULL) {
- /* the "0000000000000000" is 64-bit hexadecimal string */
- chunk = ngx_palloc(r->pool, sizeof("0000000000000000" CRLF) - 1);
+#if (NGX_HTTP_V3)
+ if (r->http_version == NGX_HTTP_VERSION_30) {
+ n = NGX_HTTP_V3_VARLEN_INT_LEN * 2;
+
+ } else
+#endif
+ {
+ /* the "0000000000000000" is 64-bit hexadecimal string */
+ n = sizeof("0000000000000000" CRLF) - 1;
+ }
+
+ chunk = ngx_palloc(r->pool, n);
if (chunk == NULL) {
return NGX_ERROR;
}
b->start = chunk;
- b->end = chunk + sizeof("0000000000000000" CRLF) - 1;
+ b->end = chunk + n;
}
b->tag = (ngx_buf_tag_t) &ngx_http_chunked_filter_module;
b->memory = 0;
b->temporary = 1;
b->pos = chunk;
- b->last = ngx_sprintf(chunk, "%xO" CRLF, size);
+
+#if (NGX_HTTP_V3)
+ if (r->http_version == NGX_HTTP_VERSION_30) {
+ b->last = (u_char *) ngx_http_v3_encode_varlen_int(chunk,
+ NGX_HTTP_V3_FRAME_DATA);
+ b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, size);
+
+ } else
+#endif
+ {
+ b->last = ngx_sprintf(chunk, "%xO" CRLF, size);
+ }
tl->next = out;
out = tl;
}
if (cl->buf->last_buf) {
- tl = ngx_http_chunked_create_trailers(r, ctx);
+ tl = ngx_http_chunked_create_trailers(r, ctx, size);
if (tl == NULL) {
return NGX_ERROR;
}
@@ -192,11 +214,12 @@ ngx_http_chunked_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
*ll = tl;
- if (size == 0) {
- tl->buf->pos += 2;
- }
-
- } else if (size > 0) {
+ } else if (size > 0
+#if (NGX_HTTP_V3)
+ && r->http_version != NGX_HTTP_VERSION_30
+#endif
+ )
+ {
tl = ngx_chain_get_free_buf(r->pool, &ctx->free);
if (tl == NULL) {
return NGX_ERROR;
@@ -227,7 +250,7 @@ ngx_http_chunked_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
static ngx_chain_t *
ngx_http_chunked_create_trailers(ngx_http_request_t *r,
- ngx_http_chunked_filter_ctx_t *ctx)
+ ngx_http_chunked_filter_ctx_t *ctx, size_t size)
{
size_t len;
ngx_buf_t *b;
@@ -236,6 +259,12 @@ ngx_http_chunked_create_trailers(ngx_http_request_t *r,
ngx_list_part_t *part;
ngx_table_elt_t *header;
+#if (NGX_HTTP_V3)
+ if (r->http_version == NGX_HTTP_VERSION_30) {
+ return ngx_http_v3_create_trailers(r);
+ }
+#endif
+
len = 0;
part = &r->headers_out.trailers.part;
@@ -288,7 +317,10 @@ ngx_http_chunked_create_trailers(ngx_http_request_t *r,
b->last = b->pos;
- *b->last++ = CR; *b->last++ = LF;
+ if (size > 0) {
+ *b->last++ = CR; *b->last++ = LF;
+ }
+
*b->last++ = '0';
*b->last++ = CR; *b->last++ = LF;
diff --git a/src/http/modules/ngx_http_quic_module.c b/src/http/modules/ngx_http_quic_module.c
new file mode 100644
index 000000000..9888e2eae
--- /dev/null
+++ b/src/http/modules/ngx_http_quic_module.c
@@ -0,0 +1,345 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ * Copyright (C) Roman Arutyunyan
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static ngx_int_t ngx_http_variable_quic(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_quic_add_variables(ngx_conf_t *cf);
+static void *ngx_http_quic_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_http_quic_merge_srv_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+static char *ngx_http_quic_max_ack_delay(ngx_conf_t *cf, void *post,
+ void *data);
+static char *ngx_http_quic_max_udp_payload_size(ngx_conf_t *cf, void *post,
+ void *data);
+
+
+static ngx_conf_post_t ngx_http_quic_max_ack_delay_post =
+ { ngx_http_quic_max_ack_delay };
+static ngx_conf_post_t ngx_http_quic_max_udp_payload_size_post =
+ { ngx_http_quic_max_udp_payload_size };
+static ngx_conf_num_bounds_t ngx_http_quic_ack_delay_exponent_bounds =
+ { ngx_conf_check_num_bounds, 0, 20 };
+static ngx_conf_num_bounds_t ngx_http_quic_active_connection_id_limit_bounds =
+ { ngx_conf_check_num_bounds, 2, -1 };
+
+
+static ngx_command_t ngx_http_quic_commands[] = {
+
+ { ngx_string("quic_max_idle_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_quic_conf_t, tp.max_idle_timeout),
+ NULL },
+
+ { ngx_string("quic_max_ack_delay"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_quic_conf_t, tp.max_ack_delay),
+ &ngx_http_quic_max_ack_delay_post },
+
+ { ngx_string("quic_max_udp_payload_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_quic_conf_t, tp.max_udp_payload_size),
+ &ngx_http_quic_max_udp_payload_size_post },
+
+ { ngx_string("quic_initial_max_data"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_quic_conf_t, tp.initial_max_data),
+ NULL },
+
+ { ngx_string("quic_initial_max_stream_data_bidi_local"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_quic_conf_t, tp.initial_max_stream_data_bidi_local),
+ NULL },
+
+ { ngx_string("quic_initial_max_stream_data_bidi_remote"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_quic_conf_t, tp.initial_max_stream_data_bidi_remote),
+ NULL },
+
+ { ngx_string("quic_initial_max_stream_data_uni"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_quic_conf_t, tp.initial_max_stream_data_uni),
+ NULL },
+
+ { ngx_string("quic_initial_max_streams_bidi"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_quic_conf_t, tp.initial_max_streams_bidi),
+ NULL },
+
+ { ngx_string("quic_initial_max_streams_uni"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_quic_conf_t, tp.initial_max_streams_uni),
+ NULL },
+
+ { ngx_string("quic_ack_delay_exponent"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_quic_conf_t, tp.ack_delay_exponent),
+ &ngx_http_quic_ack_delay_exponent_bounds },
+
+ { ngx_string("quic_active_migration"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_quic_conf_t, tp.disable_active_migration),
+ NULL },
+
+ { ngx_string("quic_active_connection_id_limit"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_quic_conf_t, tp.active_connection_id_limit),
+ &ngx_http_quic_active_connection_id_limit_bounds },
+
+ { ngx_string("quic_retry"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_quic_conf_t, retry),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_quic_module_ctx = {
+ ngx_http_quic_add_variables, /* preconfiguration */
+ NULL, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ ngx_http_quic_create_srv_conf, /* create server configuration */
+ ngx_http_quic_merge_srv_conf, /* merge server configuration */
+
+ NULL, /* create location configuration */
+ NULL /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_quic_module = {
+ NGX_MODULE_V1,
+ &ngx_http_quic_module_ctx, /* module context */
+ ngx_http_quic_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_variable_t ngx_http_quic_vars[] = {
+
+ { ngx_string("quic"), NULL, ngx_http_variable_quic, 0, 0, 0 },
+
+ ngx_http_null_variable
+};
+
+
+static ngx_int_t
+ngx_http_variable_quic(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ if (r->connection->qs) {
+
+ v->len = 4;
+ v->valid = 1;
+ v->no_cacheable = 1;
+ v->not_found = 0;
+ v->data = (u_char *) "quic";
+ return NGX_OK;
+ }
+
+ v->not_found = 1;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_quic_add_variables(ngx_conf_t *cf)
+{
+ ngx_http_variable_t *var, *v;
+
+ for (v = ngx_http_quic_vars; v->name.len; v++) {
+ var = ngx_http_add_variable(cf, &v->name, v->flags);
+ if (var == NULL) {
+ return NGX_ERROR;
+ }
+
+ var->get_handler = v->get_handler;
+ var->data = v->data;
+ }
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_http_quic_create_srv_conf(ngx_conf_t *cf)
+{
+ ngx_quic_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_quic_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * conf->tp.original_dcid = { 0, NULL };
+ * conf->tp.initial_scid = { 0, NULL };
+ * conf->tp.retry_scid = { 0, NULL };
+ * conf->tp.stateless_reset_token = { 0 }
+ * conf->tp.preferred_address = NULL
+ */
+
+ conf->tp.max_idle_timeout = NGX_CONF_UNSET_MSEC;
+ conf->tp.max_ack_delay = NGX_CONF_UNSET_MSEC;
+ conf->tp.max_udp_payload_size = NGX_CONF_UNSET_SIZE;
+ conf->tp.initial_max_data = NGX_CONF_UNSET_SIZE;
+ conf->tp.initial_max_stream_data_bidi_local = NGX_CONF_UNSET_SIZE;
+ conf->tp.initial_max_stream_data_bidi_remote = NGX_CONF_UNSET_SIZE;
+ conf->tp.initial_max_stream_data_uni = NGX_CONF_UNSET_SIZE;
+ conf->tp.initial_max_streams_bidi = NGX_CONF_UNSET_UINT;
+ conf->tp.initial_max_streams_uni = NGX_CONF_UNSET_UINT;
+ conf->tp.ack_delay_exponent = NGX_CONF_UNSET_UINT;
+ conf->tp.disable_active_migration = NGX_CONF_UNSET_UINT;
+ conf->tp.active_connection_id_limit = NGX_CONF_UNSET_UINT;
+
+ conf->retry = NGX_CONF_UNSET;
+ conf->require_alpn = 1;
+
+ return conf;
+}
+
+
+static char *
+ngx_http_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_quic_conf_t *prev = parent;
+ ngx_quic_conf_t *conf = child;
+
+ ngx_conf_merge_msec_value(conf->tp.max_idle_timeout,
+ prev->tp.max_idle_timeout, 60000);
+
+ ngx_conf_merge_msec_value(conf->tp.max_ack_delay,
+ prev->tp.max_ack_delay,
+ NGX_QUIC_DEFAULT_MAX_ACK_DELAY);
+
+ ngx_conf_merge_size_value(conf->tp.max_udp_payload_size,
+ prev->tp.max_udp_payload_size,
+ NGX_QUIC_MAX_UDP_PAYLOAD_SIZE);
+
+ ngx_conf_merge_size_value(conf->tp.initial_max_data,
+ prev->tp.initial_max_data,
+ 16 * NGX_QUIC_STREAM_BUFSIZE);
+
+ ngx_conf_merge_size_value(conf->tp.initial_max_stream_data_bidi_local,
+ prev->tp.initial_max_stream_data_bidi_local,
+ NGX_QUIC_STREAM_BUFSIZE);
+
+ ngx_conf_merge_size_value(conf->tp.initial_max_stream_data_bidi_remote,
+ prev->tp.initial_max_stream_data_bidi_remote,
+ NGX_QUIC_STREAM_BUFSIZE);
+
+ ngx_conf_merge_size_value(conf->tp.initial_max_stream_data_uni,
+ prev->tp.initial_max_stream_data_uni,
+ NGX_QUIC_STREAM_BUFSIZE);
+
+ ngx_conf_merge_uint_value(conf->tp.initial_max_streams_bidi,
+ prev->tp.initial_max_streams_bidi, 16);
+
+ ngx_conf_merge_uint_value(conf->tp.initial_max_streams_uni,
+ prev->tp.initial_max_streams_uni, 16);
+
+ ngx_conf_merge_uint_value(conf->tp.ack_delay_exponent,
+ prev->tp.ack_delay_exponent,
+ NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT);
+
+ ngx_conf_merge_uint_value(conf->tp.disable_active_migration,
+ prev->tp.disable_active_migration, 1);
+
+ ngx_conf_merge_uint_value(conf->tp.active_connection_id_limit,
+ prev->tp.active_connection_id_limit, 2);
+
+ ngx_conf_merge_value(conf->retry, prev->retry, 0);
+
+ if (conf->retry) {
+ if (RAND_bytes(conf->token_key, sizeof(conf->token_key)) <= 0) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_quic_max_ack_delay(ngx_conf_t *cf, void *post, void *data)
+{
+ ngx_msec_t *sp = data;
+
+ if (*sp > 16384) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"quic_max_ack_delay\" must be less than 16384");
+
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_quic_max_udp_payload_size(ngx_conf_t *cf, void *post, void *data)
+{
+ size_t *sp = data;
+
+ if (*sp < NGX_QUIC_MIN_INITIAL_SIZE
+ || *sp > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE)
+ {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"quic_max_udp_payload_size\" must be between "
+ "%d and %d",
+ NGX_QUIC_MIN_INITIAL_SIZE,
+ NGX_QUIC_MAX_UDP_PAYLOAD_SIZE);
+
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
diff --git a/src/http/modules/ngx_http_quic_module.h b/src/http/modules/ngx_http_quic_module.h
new file mode 100644
index 000000000..e744eb197
--- /dev/null
+++ b/src/http/modules/ngx_http_quic_module.h
@@ -0,0 +1,25 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ * Copyright (C) Roman Arutyunyan
+ */
+
+
+#ifndef _NGX_HTTP_QUIC_H_INCLUDED_
+#define _NGX_HTTP_QUIC_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#define NGX_HTTP_QUIC_ALPN(s) NGX_HTTP_QUIC_ALPN_DRAFT(s)
+#define NGX_HTTP_QUIC_ALPN_DRAFT(s) "\x05hq-" #s
+#define NGX_HTTP_QUIC_ALPN_ADVERTISE NGX_HTTP_QUIC_ALPN(NGX_QUIC_DRAFT_VERSION)
+
+
+extern ngx_module_t ngx_http_quic_module;
+
+
+#endif /* _NGX_HTTP_QUIC_H_INCLUDED_ */
diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c
index d7072a626..409514821 100644
--- a/src/http/modules/ngx_http_ssl_module.c
+++ b/src/http/modules/ngx_http_ssl_module.c
@@ -402,7 +402,7 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out,
#if (NGX_DEBUG)
unsigned int i;
#endif
-#if (NGX_HTTP_V2)
+#if (NGX_HTTP_V2 || NGX_HTTP_QUIC)
ngx_http_connection_t *hc;
#endif
#if (NGX_HTTP_V2 || NGX_DEBUG)
@@ -419,9 +419,11 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out,
}
#endif
-#if (NGX_HTTP_V2)
+#if (NGX_HTTP_V2 || NGX_HTTP_QUIC)
hc = c->data;
+#endif
+#if (NGX_HTTP_V2)
if (hc->addr_conf->http2) {
srv =
(unsigned char *) NGX_HTTP_V2_ALPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE;
@@ -429,6 +431,18 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out,
} else
#endif
+#if (NGX_HTTP_V3)
+ if (hc->addr_conf->http3) {
+ srv = (unsigned char *) NGX_HTTP_V3_ALPN_ADVERTISE;
+ srvlen = sizeof(NGX_HTTP_V3_ALPN_ADVERTISE) - 1;
+ } else
+#endif
+#if (NGX_HTTP_QUIC)
+ if (hc->addr_conf->quic) {
+ srv = (unsigned char *) NGX_HTTP_QUIC_ALPN_ADVERTISE;
+ srvlen = sizeof(NGX_HTTP_QUIC_ALPN_ADVERTISE) - 1;
+ } else
+#endif
{
srv = (unsigned char *) NGX_HTTP_NPN_ADVERTISE;
srvlen = sizeof(NGX_HTTP_NPN_ADVERTISE) - 1;
@@ -1239,6 +1253,7 @@ static ngx_int_t
ngx_http_ssl_init(ngx_conf_t *cf)
{
ngx_uint_t a, p, s;
+ const char *name;
ngx_http_conf_addr_t *addr;
ngx_http_conf_port_t *port;
ngx_http_ssl_srv_conf_t *sscf;
@@ -1288,18 +1303,36 @@ ngx_http_ssl_init(ngx_conf_t *cf)
addr = port[p].addrs.elts;
for (a = 0; a < port[p].addrs.nelts; a++) {
- if (!addr[a].opt.ssl) {
+ if (!addr[a].opt.ssl && !addr[a].opt.quic) {
continue;
}
+ if (addr[a].opt.http3) {
+ name = "http3";
+
+ } else if (addr[a].opt.quic) {
+ name = "quic";
+
+ } else {
+ name = "ssl";
+ }
+
cscf = addr[a].default_server;
sscf = cscf->ctx->srv_conf[ngx_http_ssl_module.ctx_index];
if (sscf->certificates == NULL) {
ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
"no \"ssl_certificate\" is defined for "
- "the \"listen ... ssl\" directive in %s:%ui",
- cscf->file_name, cscf->line);
+ "the \"listen ... %s\" directive in %s:%ui",
+ name, cscf->file_name, cscf->line);
+ return NGX_ERROR;
+ }
+
+ if (addr[a].opt.quic && !(sscf->protocols & NGX_SSL_TLSv1_3)) {
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "\"ssl_protocols\" did not enable TLSv1.3 for "
+ "the \"listen ... %s\" directives in %s:%ui",
+ name, cscf->file_name, cscf->line);
return NGX_ERROR;
}
}
diff --git a/src/http/ngx_http.c b/src/http/ngx_http.c
index a35e9bb8a..b075f30be 100644
--- a/src/http/ngx_http.c
+++ b/src/http/ngx_http.c
@@ -1163,7 +1163,10 @@ ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
port = cmcf->ports->elts;
for (i = 0; i < cmcf->ports->nelts; i++) {
- if (p != port[i].port || sa->sa_family != port[i].family) {
+ if (p != port[i].port
+ || lsopt->type != port[i].type
+ || sa->sa_family != port[i].family)
+ {
continue;
}
@@ -1180,6 +1183,7 @@ ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
}
port->family = sa->sa_family;
+ port->type = lsopt->type;
port->port = p;
port->addrs.elts = NULL;
@@ -1196,9 +1200,15 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
#if (NGX_HTTP_SSL)
ngx_uint_t ssl;
#endif
+#if (NGX_HTTP_QUIC)
+ ngx_uint_t quic;
+#endif
#if (NGX_HTTP_V2)
ngx_uint_t http2;
#endif
+#if (NGX_HTTP_V3)
+ ngx_uint_t http3;
+#endif
/*
* we cannot compare whole sockaddr struct's as kernel
@@ -1231,9 +1241,15 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
#if (NGX_HTTP_SSL)
ssl = lsopt->ssl || addr[i].opt.ssl;
#endif
+#if (NGX_HTTP_QUIC)
+ quic = lsopt->quic || addr[i].opt.quic;
+#endif
#if (NGX_HTTP_V2)
http2 = lsopt->http2 || addr[i].opt.http2;
#endif
+#if (NGX_HTTP_V3)
+ http3 = lsopt->http3 || addr[i].opt.http3;
+#endif
if (lsopt->set) {
@@ -1267,9 +1283,15 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
#if (NGX_HTTP_SSL)
addr[i].opt.ssl = ssl;
#endif
+#if (NGX_HTTP_QUIC)
+ addr[i].opt.quic = quic;
+#endif
#if (NGX_HTTP_V2)
addr[i].opt.http2 = http2;
#endif
+#if (NGX_HTTP_V3)
+ addr[i].opt.http3 = http3;
+#endif
return NGX_OK;
}
@@ -1313,6 +1335,17 @@ ngx_http_add_address(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
#endif
+#if (NGX_HTTP_QUIC && !defined NGX_OPENSSL_QUIC)
+
+ if (lsopt->quic) {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "nginx was built with OpenSSL that lacks QUIC "
+ "support, QUIC is not enabled for %V",
+ &lsopt->addr_text);
+ }
+
+#endif
+
addr = ngx_array_push(&port->addrs);
if (addr == NULL) {
return NGX_ERROR;
@@ -1735,6 +1768,7 @@ ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr)
}
#endif
+ ls->type = addr->opt.type;
ls->backlog = addr->opt.backlog;
ls->rcvbuf = addr->opt.rcvbuf;
ls->sndbuf = addr->opt.sndbuf;
@@ -1770,6 +1804,12 @@ ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr)
ls->reuseport = addr->opt.reuseport;
#endif
+ ls->wildcard = addr->opt.wildcard;
+
+#if (NGX_HTTP_QUIC)
+ ls->quic = addr->opt.quic;
+#endif
+
return ls;
}
@@ -1799,9 +1839,15 @@ ngx_http_add_addrs(ngx_conf_t *cf, ngx_http_port_t *hport,
#if (NGX_HTTP_SSL)
addrs[i].conf.ssl = addr[i].opt.ssl;
#endif
+#if (NGX_HTTP_QUIC)
+ addrs[i].conf.quic = addr[i].opt.quic;
+#endif
#if (NGX_HTTP_V2)
addrs[i].conf.http2 = addr[i].opt.http2;
#endif
+#if (NGX_HTTP_V3)
+ addrs[i].conf.http3 = addr[i].opt.http3;
+#endif
addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol;
if (addr[i].hash.buckets == NULL
@@ -1864,9 +1910,15 @@ ngx_http_add_addrs6(ngx_conf_t *cf, ngx_http_port_t *hport,
#if (NGX_HTTP_SSL)
addrs6[i].conf.ssl = addr[i].opt.ssl;
#endif
+#if (NGX_HTTP_QUIC)
+ addrs6[i].conf.quic = addr[i].opt.quic;
+#endif
#if (NGX_HTTP_V2)
addrs6[i].conf.http2 = addr[i].opt.http2;
#endif
+#if (NGX_HTTP_V3)
+ addrs6[i].conf.http3 = addr[i].opt.http3;
+#endif
addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol;
if (addr[i].hash.buckets == NULL
diff --git a/src/http/ngx_http.h b/src/http/ngx_http.h
index 8b43857ee..2a3d81a37 100644
--- a/src/http/ngx_http.h
+++ b/src/http/ngx_http.h
@@ -38,6 +38,9 @@ typedef u_char *(*ngx_http_log_handler_pt)(ngx_http_request_t *r,
#if (NGX_HTTP_V2)
#include <ngx_http_v2.h>
#endif
+#if (NGX_HTTP_V3)
+#include <ngx_http_v3.h>
+#endif
#if (NGX_HTTP_CACHE)
#include <ngx_http_cache.h>
#endif
@@ -47,6 +50,9 @@ typedef u_char *(*ngx_http_log_handler_pt)(ngx_http_request_t *r,
#if (NGX_HTTP_SSL)
#include <ngx_http_ssl_module.h>
#endif
+#if (NGX_HTTP_QUIC)
+#include <ngx_http_quic_module.h>
+#endif
struct ngx_http_log_ctx_s {
@@ -60,6 +66,9 @@ struct ngx_http_chunked_s {
ngx_uint_t state;
off_t size;
off_t length;
+#if (NGX_HTTP_V3)
+ void *h3_parse;
+#endif
};
@@ -84,6 +93,7 @@ ngx_int_t ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
void ngx_http_init_connection(ngx_connection_t *c);
void ngx_http_close_connection(ngx_connection_t *c);
+u_char *ngx_http_log_error(ngx_log_t *log, u_char *buf, size_t len);
#if (NGX_HTTP_SSL && defined SSL_CTRL_SET_TLSEXT_HOSTNAME)
int ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg);
diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c
index 3671558d8..ef8b649ef 100644
--- a/src/http/ngx_http_core_module.c
+++ b/src/http/ngx_http_core_module.c
@@ -819,7 +819,7 @@ ngx_http_handler(ngx_http_request_t *r)
if (!r->internal) {
switch (r->headers_in.connection_type) {
case 0:
- r->keepalive = (r->http_version > NGX_HTTP_VERSION_10);
+ r->keepalive = (r->http_version == NGX_HTTP_VERSION_11);
break;
case NGX_HTTP_CONNECTION_CLOSE:
@@ -3880,6 +3880,7 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
ngx_memzero(&lsopt, sizeof(ngx_http_listen_opt_t));
lsopt.backlog = NGX_LISTEN_BACKLOG;
+ lsopt.type = SOCK_STREAM;
lsopt.rcvbuf = -1;
lsopt.sndbuf = -1;
#if (NGX_HAVE_SETFIB)
@@ -4078,6 +4079,33 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
#endif
}
+ if (ngx_strcmp(value[n].data, "quic") == 0) {
+#if (NGX_HTTP_QUIC)
+ lsopt.quic = 1;
+ lsopt.type = SOCK_DGRAM;
+ continue;
+#else
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "the \"quic\" parameter requires "
+ "ngx_http_quic_module");
+ return NGX_CONF_ERROR;
+#endif
+ }
+
+ if (ngx_strcmp(value[n].data, "http3") == 0) {
+#if (NGX_HTTP_V3)
+ lsopt.quic = 1;
+ lsopt.http3 = 1;
+ lsopt.type = SOCK_DGRAM;
+ continue;
+#else
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "the \"http3\" parameter requires "
+ "ngx_http_v3_module");
+ return NGX_CONF_ERROR;
+#endif
+ }
+
if (ngx_strcmp(value[n].data, "spdy") == 0) {
ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
"invalid parameter \"spdy\": "
@@ -4187,6 +4215,22 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
return NGX_CONF_ERROR;
}
+#if (NGX_HTTP_SSL)
+
+#if (NGX_HTTP_V3)
+ if (lsopt.ssl && lsopt.http3) {
+ return "\"ssl\" parameter is incompatible with \"http3\"";
+ }
+#endif
+
+#if (NGX_HTTP_QUIC)
+ if (lsopt.ssl && lsopt.quic) {
+ return "\"ssl\" parameter is incompatible with \"quic\"";
+ }
+#endif
+
+#endif
+
for (n = 0; n < u.naddrs; n++) {
lsopt.sockaddr = u.addrs[n].sockaddr;
lsopt.socklen = u.addrs[n].socklen;
diff --git a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h
index 2aadae7ff..02e2a9e59 100644
--- a/src/http/ngx_http_core_module.h
+++ b/src/http/ngx_http_core_module.h
@@ -74,7 +74,9 @@ typedef struct {
unsigned bind:1;
unsigned wildcard:1;
unsigned ssl:1;
+ unsigned quic:1;
unsigned http2:1;
+ unsigned http3:1;
#if (NGX_HAVE_INET6)
unsigned ipv6only:1;
#endif
@@ -86,6 +88,7 @@ typedef struct {
int backlog;
int rcvbuf;
int sndbuf;
+ int type;
#if (NGX_HAVE_SETFIB)
int setfib;
#endif
@@ -236,7 +239,9 @@ struct ngx_http_addr_conf_s {
ngx_http_virtual_names_t *virtual_names;
unsigned ssl:1;
+ unsigned quic:1;
unsigned http2:1;
+ unsigned http3:1;
unsigned proxy_protocol:1;
};
@@ -266,6 +271,7 @@ typedef struct {
typedef struct {
ngx_int_t family;
+ ngx_int_t type;
in_port_t port;
ngx_array_t addrs; /* array of ngx_http_conf_addr_t */
} ngx_http_conf_port_t;
diff --git a/src/http/ngx_http_header_filter_module.c b/src/http/ngx_http_header_filter_module.c
index 9b8940590..85edf3657 100644
--- a/src/http/ngx_http_header_filter_module.c
+++ b/src/http/ngx_http_header_filter_module.c
@@ -187,6 +187,29 @@ ngx_http_header_filter(ngx_http_request_t *r)
r->header_only = 1;
}
+ if (r->headers_out.status_line.len == 0) {
+ if (r->headers_out.status == NGX_HTTP_NO_CONTENT
+ || r->headers_out.status == NGX_HTTP_NOT_MODIFIED)
+ {
+ r->header_only = 1;
+ }
+ }
+
+#if (NGX_HTTP_V3)
+
+ if (r->http_version == NGX_HTTP_VERSION_30) {
+ ngx_chain_t *cl;
+
+ cl = ngx_http_v3_create_header(r);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ return ngx_http_write_filter(r, cl);
+ }
+
+#endif
+
if (r->headers_out.last_modified_time != -1) {
if (r->headers_out.status != NGX_HTTP_OK
&& r->headers_out.status != NGX_HTTP_PARTIAL_CONTENT
@@ -220,7 +243,6 @@ ngx_http_header_filter(ngx_http_request_t *r)
/* 2XX */
if (status == NGX_HTTP_NO_CONTENT) {
- r->header_only = 1;
ngx_str_null(&r->headers_out.content_type);
r->headers_out.last_modified_time = -1;
r->headers_out.last_modified = NULL;
@@ -237,10 +259,6 @@ ngx_http_header_filter(ngx_http_request_t *r)
{
/* 3XX */
- if (status == NGX_HTTP_NOT_MODIFIED) {
- r->header_only = 1;
- }
-
status = status - NGX_HTTP_MOVED_PERMANENTLY + NGX_HTTP_OFF_3XX;
status_line = &ngx_http_status_lines[status];
len += ngx_http_status_lines[status].len;
diff --git a/src/http/ngx_http_parse.c b/src/http/ngx_http_parse.c
index cfc42f9dd..a351f2b27 100644
--- a/src/http/ngx_http_parse.c
+++ b/src/http/ngx_http_parse.c
@@ -143,7 +143,9 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b)
/* HTTP methods: GET, HEAD, POST */
case sw_start:
+ r->parse_start = p;
r->request_start = p;
+ r->method_start = p;
if (ch == CR || ch == LF) {
break;
@@ -158,7 +160,7 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b)
case sw_method:
if (ch == ' ') {
- r->method_end = p - 1;
+ r->method_end = p;
m = r->request_start;
switch (p - m) {
@@ -831,6 +833,10 @@ done:
r->request_end = p;
}
+ if (r->http_protocol.data) {
+ r->http_protocol.len = r->request_end - r->http_protocol.data;
+ }
+
r->http_version = r->http_major * 1000 + r->http_minor;
r->state = sw_start;
@@ -882,6 +888,7 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b,
/* first char */
case sw_start:
+ r->parse_start = p;
r->header_name_start = p;
r->invalid_header = 0;
@@ -2370,6 +2377,11 @@ ngx_http_parse_chunked(ngx_http_request_t *r, ngx_buf_t *b,
}
}
+ if (b->last_buf) {
+ /* XXX client prematurely closed connection */
+ return NGX_ERROR;
+ }
+
data:
ctx->state = state;
diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c
index f80785d8f..7dbbcceb2 100644
--- a/src/http/ngx_http_request.c
+++ b/src/http/ngx_http_request.c
@@ -55,7 +55,6 @@ static ngx_int_t ngx_http_post_action(ngx_http_request_t *r);
static void ngx_http_close_request(ngx_http_request_t *r, ngx_int_t error);
static void ngx_http_log_request(ngx_http_request_t *r);
-static u_char *ngx_http_log_error(ngx_log_t *log, u_char *buf, size_t len);
static u_char *ngx_http_log_error_handler(ngx_http_request_t *r,
ngx_http_request_t *sr, u_char *buf, size_t len);
@@ -303,6 +302,46 @@ ngx_http_init_connection(ngx_connection_t *c)
/* the default server configuration for the address:port */
hc->conf_ctx = hc->addr_conf->default_server->ctx;
+#if (NGX_HTTP_QUIC)
+
+ if (hc->addr_conf->quic) {
+ ngx_quic_conf_t *qcf;
+ ngx_http_ssl_srv_conf_t *sscf;
+
+#if (NGX_HTTP_V3)
+
+ if (hc->addr_conf->http3) {
+ ngx_int_t rc;
+
+ rc = ngx_http_v3_init_connection(c);
+
+ if (rc == NGX_ERROR) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ if (rc == NGX_DONE) {
+ return;
+ }
+ }
+
+#endif
+
+ if (c->qs == NULL) {
+ c->log->connection = c->number;
+
+ qcf = ngx_http_get_module_srv_conf(hc->conf_ctx,
+ ngx_http_quic_module);
+ sscf = ngx_http_get_module_srv_conf(hc->conf_ctx,
+ ngx_http_ssl_module);
+
+ ngx_quic_run(c, &sscf->ssl, qcf);
+ return;
+ }
+ }
+
+#endif
+
ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t));
if (ctx == NULL) {
ngx_http_close_connection(c);
@@ -619,6 +658,12 @@ ngx_http_alloc_request(ngx_connection_t *c)
r->method = NGX_HTTP_UNKNOWN;
r->http_version = NGX_HTTP_VERSION_10;
+#if (NGX_HTTP_V3)
+ if (hc->addr_conf->http3) {
+ r->http_version = NGX_HTTP_VERSION_30;
+ }
+#endif
+
r->headers_in.content_length_n = -1;
r->headers_in.keep_alive_n = -1;
r->headers_out.content_length_n = -1;
@@ -1068,7 +1113,16 @@ ngx_http_process_request_line(ngx_event_t *rev)
}
}
- rc = ngx_http_parse_request_line(r, r->header_in);
+ switch (r->http_version) {
+#if (NGX_HTTP_V3)
+ case NGX_HTTP_VERSION_30:
+ rc = ngx_http_v3_parse_request(r, r->header_in);
+ break;
+#endif
+
+ default: /* HTTP/1.x */
+ rc = ngx_http_parse_request_line(r, r->header_in);
+ }
if (rc == NGX_OK) {
@@ -1076,17 +1130,13 @@ ngx_http_process_request_line(ngx_event_t *rev)
r->request_line.len = r->request_end - r->request_start;
r->request_line.data = r->request_start;
- r->request_length = r->header_in->pos - r->request_start;
+ r->request_length = r->header_in->pos - r->parse_start;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http request line: \"%V\"", &r->request_line);
- r->method_name.len = r->method_end - r->request_start + 1;
- r->method_name.data = r->request_line.data;
-
- if (r->http_protocol.data) {
- r->http_protocol.len = r->request_end - r->http_protocol.data;
- }
+ r->method_name.len = r->method_end - r->method_start;
+ r->method_name.data = r->method_start;
if (ngx_http_process_request_uri(r) != NGX_OK) {
break;
@@ -1153,6 +1203,15 @@ ngx_http_process_request_line(ngx_event_t *rev)
break;
}
+ if (rc == NGX_BUSY) {
+ if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ break;
+ }
+
if (rc != NGX_AGAIN) {
/* there was error while a request line parsing */
@@ -1182,8 +1241,8 @@ ngx_http_process_request_line(ngx_event_t *rev)
}
if (rv == NGX_DECLINED) {
- r->request_line.len = r->header_in->end - r->request_start;
- r->request_line.data = r->request_start;
+ r->request_line.len = r->header_in->end - r->parse_start;
+ r->request_line.data = r->parse_start;
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client sent too long URI");
@@ -1343,7 +1402,7 @@ ngx_http_process_request_headers(ngx_event_t *rev)
cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
- rc = NGX_AGAIN;
+ rc = NGX_OK;
for ( ;; ) {
@@ -1359,7 +1418,7 @@ ngx_http_process_request_headers(ngx_event_t *rev)
}
if (rv == NGX_DECLINED) {
- p = r->header_name_start;
+ p = r->parse_start;
r->lingering_close = 1;
@@ -1379,7 +1438,7 @@ ngx_http_process_request_headers(ngx_event_t *rev)
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client sent too long header line: \"%*s...\"",
- len, r->header_name_start);
+ len, r->parse_start);
ngx_http_finalize_request(r,
NGX_HTTP_REQUEST_HEADER_TOO_LARGE);
@@ -1397,21 +1456,32 @@ ngx_http_process_request_headers(ngx_event_t *rev)
/* the host header could change the server configuration context */
cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
- rc = ngx_http_parse_header_line(r, r->header_in,
- cscf->underscores_in_headers);
+ switch (r->http_version) {
+#if (NGX_HTTP_V3)
+ case NGX_HTTP_VERSION_30:
+ rc = ngx_http_v3_parse_header(r, r->header_in,
+ cscf->underscores_in_headers);
+ break;
+#endif
+
+ default: /* HTTP/1.x */
+ rc = ngx_http_parse_header_line(r, r->header_in,
+ cscf->underscores_in_headers);
+ }
if (rc == NGX_OK) {
- r->request_length += r->header_in->pos - r->header_name_start;
+ r->request_length += r->header_in->pos - r->parse_start;
if (r->invalid_header && cscf->ignore_invalid_headers) {
/* there was error while a header line parsing */
ngx_log_error(NGX_LOG_INFO, c->log, 0,
- "client sent invalid header line: \"%*s\"",
- r->header_end - r->header_name_start,
- r->header_name_start);
+ "client sent invalid header line: \"%*s: %*s\"",
+ r->header_name_end - r->header_name_start,
+ r->header_name_start,
+ r->header_end - r->header_start, r->header_start);
continue;
}
@@ -1427,11 +1497,17 @@ ngx_http_process_request_headers(ngx_event_t *rev)
h->key.len = r->header_name_end - r->header_name_start;
h->key.data = r->header_name_start;
- h->key.data[h->key.len] = '\0';
+
+ if (h->key.data[h->key.len]) {
+ h->key.data[h->key.len] = '\0';
+ }
h->value.len = r->header_end - r->header_start;
h->value.data = r->header_start;
- h->value.data[h->value.len] = '\0';
+
+ if (h->value.data[h->value.len]) {
+ h->value.data[h->value.len] = '\0';
+ }
h->lowcase_key = ngx_pnalloc(r->pool, h->key.len);
if (h->lowcase_key == NULL) {
@@ -1467,7 +1543,7 @@ ngx_http_process_request_headers(ngx_event_t *rev)
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http header done");
- r->request_length += r->header_in->pos - r->header_name_start;
+ r->request_length += r->header_in->pos - r->parse_start;
r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE;
@@ -1582,7 +1658,7 @@ ngx_http_alloc_large_header_buffer(ngx_http_request_t *r,
return NGX_OK;
}
- old = request_line ? r->request_start : r->header_name_start;
+ old = r->parse_start;
cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
@@ -1660,6 +1736,14 @@ ngx_http_alloc_large_header_buffer(ngx_http_request_t *r,
b->pos = new + (r->header_in->pos - old);
b->last = new + (r->header_in->pos - old);
+ r->parse_start = new;
+
+ r->header_in = b;
+
+ if (r->http_version > NGX_HTTP_VERSION_11) {
+ return NGX_OK;
+ }
+
if (request_line) {
r->request_start = new;
@@ -1708,8 +1792,6 @@ ngx_http_alloc_large_header_buffer(ngx_http_request_t *r,
r->header_end = new + (r->header_end - old);
}
- r->header_in = b;
-
return NGX_OK;
}
@@ -1930,13 +2012,46 @@ ngx_http_process_request_header(ngx_http_request_t *r)
return NGX_ERROR;
}
- if (r->headers_in.host == NULL && r->http_version > NGX_HTTP_VERSION_10) {
+ if (r->headers_in.host == NULL && r->http_version == NGX_HTTP_VERSION_11) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"client sent HTTP/1.1 request without \"Host\" header");
ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
return NGX_ERROR;
}
+ if (r->headers_in.host == NULL && r->http_version == NGX_HTTP_VERSION_20) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent HTTP/2 request without "
+ "\":authority\" or \"Host\" header");
+ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+ return NGX_ERROR;
+ }
+
+ if (r->http_version == NGX_HTTP_VERSION_30) {
+ if (r->headers_in.server.len == 0) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent HTTP/3 request without "
+ "\":authority\" or \"Host\" header");
+ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+ return NGX_ERROR;
+ }
+
+ if (r->headers_in.host) {
+ if (r->headers_in.host->value.len != r->headers_in.server.len
+ || ngx_memcmp(r->headers_in.host->value.data,
+ r->headers_in.server.data,
+ r->headers_in.server.len)
+ != 0)
+ {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent HTTP/3 request with different "
+ "values of \":authority\" and \"Host\" headers");
+ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+ return NGX_ERROR;
+ }
+ }
+ }
+
if (r->headers_in.content_length) {
r->headers_in.content_length_n =
ngx_atoof(r->headers_in.content_length->value.data,
@@ -1975,6 +2090,12 @@ ngx_http_process_request_header(ngx_http_request_t *r)
}
}
+#if (NGX_HTTP_V3)
+ if (r->http_version == NGX_HTTP_VERSION_30) {
+ r->headers_in.chunked = 1;
+ }
+#endif
+
if (r->headers_in.connection_type == NGX_HTTP_CONNECTION_KEEP_ALIVE) {
if (r->headers_in.keep_alive) {
r->headers_in.keep_alive_n =
@@ -2907,6 +3028,19 @@ ngx_http_test_reading(ngx_http_request_t *r)
#endif
+#if (NGX_HTTP_QUIC)
+
+ if (c->qs) {
+ if (c->read->error) {
+ err = 0;
+ goto closed;
+ }
+
+ return;
+ }
+
+#endif
+
#if (NGX_HAVE_KQUEUE)
if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
@@ -3390,11 +3524,13 @@ ngx_http_set_lingering_close(ngx_http_request_t *r)
}
}
- if (ngx_shutdown_socket(c->fd, NGX_WRITE_SHUTDOWN) == -1) {
- ngx_connection_error(c, ngx_socket_errno,
- ngx_shutdown_socket_n " failed");
- ngx_http_close_request(r, 0);
- return;
+ if (c->fd != NGX_INVALID_FILE) {
+ if (ngx_shutdown_socket(c->fd, NGX_WRITE_SHUTDOWN) == -1) {
+ ngx_connection_error(c, ngx_socket_errno,
+ ngx_shutdown_socket_n " failed");
+ ngx_http_close_request(r, 0);
+ return;
+ }
}
if (rev->ready) {
@@ -3726,7 +3862,7 @@ ngx_http_close_connection(ngx_connection_t *c)
}
-static u_char *
+u_char *
ngx_http_log_error(ngx_log_t *log, u_char *buf, size_t len)
{
u_char *p;
@@ -3773,15 +3909,15 @@ ngx_http_log_error_handler(ngx_http_request_t *r, ngx_http_request_t *sr,
len -= p - buf;
buf = p;
- if (r->request_line.data == NULL && r->request_start) {
- for (p = r->request_start; p < r->header_in->last; p++) {
+ if (r->request_line.data == NULL && r->parse_start) {
+ for (p = r->parse_start; p < r->header_in->last; p++) {
if (*p == CR || *p == LF) {
break;
}
}
- r->request_line.len = p - r->request_start;
- r->request_line.data = r->request_start;
+ r->request_line.len = p - r->parse_start;
+ r->request_line.data = r->parse_start;
}
if (r->request_line.len) {
diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h
index 70c2d424d..3d33d93d4 100644
--- a/src/http/ngx_http_request.h
+++ b/src/http/ngx_http_request.h
@@ -24,6 +24,7 @@
#define NGX_HTTP_VERSION_10 1000
#define NGX_HTTP_VERSION_11 1001
#define NGX_HTTP_VERSION_20 2000
+#define NGX_HTTP_VERSION_30 3000
#define NGX_HTTP_UNKNOWN 0x0001
#define NGX_HTTP_GET 0x0002
@@ -577,12 +578,14 @@ struct ngx_http_request_s {
* via ngx_http_ephemeral_t
*/
+ u_char *parse_start;
u_char *uri_start;
u_char *uri_end;
u_char *uri_ext;
u_char *args_start;
u_char *request_start;
u_char *request_end;
+ u_char *method_start;
u_char *method_end;
u_char *schema_start;
u_char *schema_end;
@@ -591,6 +594,10 @@ struct ngx_http_request_s {
u_char *port_start;
u_char *port_end;
+#if (NGX_HTTP_V3)
+ void *h3_parse;
+#endif
+
unsigned http_minor:16;
unsigned http_major:16;
};
diff --git a/src/http/ngx_http_request_body.c b/src/http/ngx_http_request_body.c
index 71d7e9ab8..204253ca2 100644
--- a/src/http/ngx_http_request_body.c
+++ b/src/http/ngx_http_request_body.c
@@ -329,11 +329,10 @@ ngx_http_do_read_client_request_body(ngx_http_request_t *r)
}
if (n == 0) {
- ngx_log_error(NGX_LOG_INFO, c->log, 0,
- "client prematurely closed connection");
+ rb->buf->last_buf = 1;
}
- if (n == 0 || n == NGX_ERROR) {
+ if (n == NGX_ERROR) {
c->error = 1;
return NGX_HTTP_BAD_REQUEST;
}
@@ -799,7 +798,16 @@ ngx_http_discard_request_body_filter(ngx_http_request_t *r, ngx_buf_t *b)
for ( ;; ) {
- rc = ngx_http_parse_chunked(r, b, rb->chunked);
+ switch (r->http_version) {
+#if (NGX_HTTP_V3)
+ case NGX_HTTP_VERSION_30:
+ rc = ngx_http_v3_parse_request_body(r, b, rb->chunked);
+ break;
+#endif
+
+ default: /* HTTP/1.x */
+ rc = ngx_http_parse_chunked(r, b, rb->chunked);
+ }
if (rc == NGX_OK) {
@@ -871,11 +879,7 @@ ngx_http_test_expect(ngx_http_request_t *r)
if (r->expect_tested
|| r->headers_in.expect == NULL
- || r->http_version < NGX_HTTP_VERSION_11
-#if (NGX_HTTP_V2)
- || r->stream != NULL
-#endif
- )
+ || r->http_version != NGX_HTTP_VERSION_11)
{
return NGX_OK;
}
@@ -980,6 +984,11 @@ ngx_http_request_body_length_filter(ngx_http_request_t *r, ngx_chain_t *in)
b->last_buf = 1;
}
+ if (cl->buf->last_buf && rb->rest > 0) {
+ /* XXX client prematurely closed connection */
+ return NGX_ERROR;
+ }
+
*ll = tl;
ll = &tl->next;
}
@@ -1020,6 +1029,12 @@ ngx_http_request_body_chunked_filter(ngx_http_request_t *r, ngx_chain_t *in)
r->headers_in.content_length_n = 0;
rb->rest = cscf->large_client_header_buffers.size;
+
+#if (NGX_HTTP_V3)
+ if (r->http_version == NGX_HTTP_VERSION_30) {
+ rb->rest = 1;
+ }
+#endif
}
out = NULL;
@@ -1040,7 +1055,16 @@ ngx_http_request_body_chunked_filter(ngx_http_request_t *r, ngx_chain_t *in)
cl->buf->file_pos,
cl->buf->file_last - cl->buf->file_pos);
- rc = ngx_http_parse_chunked(r, cl->buf, rb->chunked);
+ switch (r->http_version) {
+#if (NGX_HTTP_V3)
+ case NGX_HTTP_VERSION_30:
+ rc = ngx_http_v3_parse_request_body(r, cl->buf, rb->chunked);
+ break;
+#endif
+
+ default: /* HTTP/1.x */
+ rc = ngx_http_parse_chunked(r, cl->buf, rb->chunked);
+ }
if (rc == NGX_OK) {
diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h
new file mode 100644
index 000000000..aab27b3ac
--- /dev/null
+++ b/src/http/v3/ngx_http_v3.h
@@ -0,0 +1,206 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_HTTP_V3_H_INCLUDED_
+#define _NGX_HTTP_V3_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <ngx_http_v3_parse.h>
+
+
+#define NGX_HTTP_V3_ALPN(s) NGX_HTTP_V3_ALPN_DRAFT(s)
+#define NGX_HTTP_V3_ALPN_DRAFT(s) "\x05h3-" #s
+#define NGX_HTTP_V3_ALPN_ADVERTISE NGX_HTTP_V3_ALPN(NGX_QUIC_DRAFT_VERSION)
+
+#define NGX_HTTP_V3_VARLEN_INT_LEN 4
+#define NGX_HTTP_V3_PREFIX_INT_LEN 11
+
+#define NGX_HTTP_V3_STREAM_CONTROL 0x00
+#define NGX_HTTP_V3_STREAM_PUSH 0x01
+#define NGX_HTTP_V3_STREAM_ENCODER 0x02
+#define NGX_HTTP_V3_STREAM_DECODER 0x03
+
+#define NGX_HTTP_V3_FRAME_DATA 0x00
+#define NGX_HTTP_V3_FRAME_HEADERS 0x01
+#define NGX_HTTP_V3_FRAME_CANCEL_PUSH 0x03
+#define NGX_HTTP_V3_FRAME_SETTINGS 0x04
+#define NGX_HTTP_V3_FRAME_PUSH_PROMISE 0x05
+#define NGX_HTTP_V3_FRAME_GOAWAY 0x07
+#define NGX_HTTP_V3_FRAME_MAX_PUSH_ID 0x0d
+
+#define NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY 0x01
+#define NGX_HTTP_V3_PARAM_MAX_HEADER_LIST_SIZE 0x06
+#define NGX_HTTP_V3_PARAM_BLOCKED_STREAMS 0x07
+
+#define NGX_HTTP_V3_STREAM_CLIENT_CONTROL 0
+#define NGX_HTTP_V3_STREAM_SERVER_CONTROL 1
+#define NGX_HTTP_V3_STREAM_CLIENT_ENCODER 2
+#define NGX_HTTP_V3_STREAM_SERVER_ENCODER 3
+#define NGX_HTTP_V3_STREAM_CLIENT_DECODER 4
+#define NGX_HTTP_V3_STREAM_SERVER_DECODER 5
+#define NGX_HTTP_V3_MAX_KNOWN_STREAM 6
+
+#define NGX_HTTP_V3_DEFAULT_MAX_FIELD_SIZE 4096
+#define NGX_HTTP_V3_DEFAULT_MAX_TABLE_CAPACITY 16384
+#define NGX_HTTP_V3_DEFAULT_MAX_BLOCKED_STREAMS 16
+#define NGX_HTTP_V3_DEFAULT_MAX_CONCURRENT_PUSHES 10
+
+/* HTTP/3 errors */
+#define NGX_HTTP_V3_ERR_NO_ERROR 0x100
+#define NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR 0x101
+#define NGX_HTTP_V3_ERR_INTERNAL_ERROR 0x102
+#define NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR 0x103
+#define NGX_HTTP_V3_ERR_CLOSED_CRITICAL_STREAM 0x104
+#define NGX_HTTP_V3_ERR_FRAME_UNEXPECTED 0x105
+#define NGX_HTTP_V3_ERR_FRAME_ERROR 0x106
+#define NGX_HTTP_V3_ERR_EXCESSIVE_LOAD 0x107
+#define NGX_HTTP_V3_ERR_ID_ERROR 0x108
+#define NGX_HTTP_V3_ERR_SETTINGS_ERROR 0x109
+#define NGX_HTTP_V3_ERR_MISSING_SETTINGS 0x10a
+#define NGX_HTTP_V3_ERR_REQUEST_REJECTED 0x10b
+#define NGX_HTTP_V3_ERR_REQUEST_CANCELLED 0x10c
+#define NGX_HTTP_V3_ERR_REQUEST_INCOMPLETE 0x10d
+#define NGX_HTTP_V3_ERR_CONNECT_ERROR 0x10f
+#define NGX_HTTP_V3_ERR_VERSION_FALLBACK 0x110
+
+/* QPACK errors */
+#define NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED 0x200
+#define NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR 0x201
+#define NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR 0x202
+
+
+#define ngx_http_v3_get_module_srv_conf(c, module) \
+ ngx_http_get_module_srv_conf( \
+ ((ngx_http_v3_connection_t *) c->qs->parent->data)->hc.conf_ctx, \
+ module)
+
+#define ngx_http_v3_finalize_connection(c, code, reason) \
+ ngx_quic_finalize_connection(c->qs->parent, code, reason)
+
+
+typedef struct {
+ ngx_quic_tp_t quic;
+ size_t max_field_size;
+ size_t max_table_capacity;
+ ngx_uint_t max_blocked_streams;
+ ngx_uint_t max_concurrent_pushes;
+} ngx_http_v3_srv_conf_t;
+
+
+typedef struct {
+ ngx_flag_t push_preload;
+ ngx_flag_t push;
+ ngx_array_t *pushes;
+} ngx_http_v3_loc_conf_t;
+
+
+typedef struct {
+ ngx_str_t name;
+ ngx_str_t value;
+} ngx_http_v3_header_t;
+
+
+typedef struct {
+ ngx_http_v3_header_t **elts;
+ ngx_uint_t nelts;
+ ngx_uint_t base;
+ size_t size;
+ size_t capacity;
+} ngx_http_v3_dynamic_table_t;
+
+
+typedef struct {
+ ngx_http_connection_t hc;
+ ngx_http_v3_dynamic_table_t table;
+
+ ngx_queue_t blocked;
+ ngx_uint_t nblocked;
+
+ ngx_queue_t pushing;
+ ngx_uint_t npushing;
+ uint64_t next_push_id;
+ uint64_t max_push_id;
+
+ ngx_uint_t settings_sent;
+ /* unsigned settings_sent:1; */
+ ngx_connection_t *known_streams[NGX_HTTP_V3_MAX_KNOWN_STREAM];
+} ngx_http_v3_connection_t;
+
+
+ngx_int_t ngx_http_v3_init_connection(ngx_connection_t *c);
+
+ngx_int_t ngx_http_v3_parse_request(ngx_http_request_t *r, ngx_buf_t *b);
+ngx_int_t ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b,
+ ngx_uint_t allow_underscores);
+ngx_int_t ngx_http_v3_parse_request_body(ngx_http_request_t *r, ngx_buf_t *b,
+ ngx_http_chunked_t *ctx);
+ngx_chain_t *ngx_http_v3_create_header(ngx_http_request_t *r);
+ngx_chain_t *ngx_http_v3_create_trailers(ngx_http_request_t *r);
+
+uintptr_t ngx_http_v3_encode_varlen_int(u_char *p, uint64_t value);
+uintptr_t ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value,
+ ngx_uint_t prefix);
+
+uintptr_t ngx_http_v3_encode_header_block_prefix(u_char *p,
+ ngx_uint_t insert_count, ngx_uint_t sign, ngx_uint_t delta_base);
+uintptr_t ngx_http_v3_encode_header_ri(u_char *p, ngx_uint_t dynamic,
+ ngx_uint_t index);
+uintptr_t ngx_http_v3_encode_header_lri(u_char *p, ngx_uint_t dynamic,
+ ngx_uint_t index, u_char *data, size_t len);
+uintptr_t ngx_http_v3_encode_header_l(u_char *p, ngx_str_t *name,
+ ngx_str_t *value);
+uintptr_t ngx_http_v3_encode_header_pbi(u_char *p, ngx_uint_t index);
+uintptr_t ngx_http_v3_encode_header_lpbi(u_char *p, ngx_uint_t index,
+ u_char *data, size_t len);
+
+ngx_connection_t *ngx_http_v3_create_push_stream(ngx_connection_t *c,
+ uint64_t push_id);
+ngx_int_t ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic,
+ ngx_uint_t index, ngx_str_t *value);
+ngx_int_t ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name,
+ ngx_str_t *value);
+ngx_int_t ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity);
+ngx_int_t ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index);
+ngx_int_t ngx_http_v3_ack_header(ngx_connection_t *c, ngx_uint_t stream_id);
+ngx_int_t ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id);
+ngx_int_t ngx_http_v3_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc);
+ngx_int_t ngx_http_v3_lookup_static(ngx_connection_t *c, ngx_uint_t index,
+ ngx_str_t *name, ngx_str_t *value);
+ngx_int_t ngx_http_v3_lookup(ngx_connection_t *c, ngx_uint_t index,
+ ngx_str_t *name, ngx_str_t *value);
+ngx_int_t ngx_http_v3_decode_insert_count(ngx_connection_t *c,
+ ngx_uint_t *insert_count);
+ngx_int_t ngx_http_v3_check_insert_count(ngx_connection_t *c,
+ ngx_uint_t insert_count);
+ngx_int_t ngx_http_v3_set_param(ngx_connection_t *c, uint64_t id,
+ uint64_t value);
+ngx_int_t ngx_http_v3_set_max_push_id(ngx_connection_t *c,
+ uint64_t max_push_id);
+ngx_int_t ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id);
+
+ngx_int_t ngx_http_v3_client_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic,
+ ngx_uint_t index, ngx_str_t *value);
+ngx_int_t ngx_http_v3_client_insert(ngx_connection_t *c, ngx_str_t *name,
+ ngx_str_t *value);
+ngx_int_t ngx_http_v3_client_set_capacity(ngx_connection_t *c,
+ ngx_uint_t capacity);
+ngx_int_t ngx_http_v3_client_duplicate(ngx_connection_t *c, ngx_uint_t index);
+ngx_int_t ngx_http_v3_client_ack_header(ngx_connection_t *c,
+ ngx_uint_t stream_id);
+ngx_int_t ngx_http_v3_client_cancel_stream(ngx_connection_t *c,
+ ngx_uint_t stream_id);
+ngx_int_t ngx_http_v3_client_inc_insert_count(ngx_connection_t *c,
+ ngx_uint_t inc);
+
+
+extern ngx_module_t ngx_http_v3_module;
+
+
+#endif /* _NGX_HTTP_V3_H_INCLUDED_ */
diff --git a/src/http/v3/ngx_http_v3_encode.c b/src/http/v3/ngx_http_v3_encode.c
new file mode 100644
index 000000000..a80310faf
--- /dev/null
+++ b/src/http/v3/ngx_http_v3_encode.c
@@ -0,0 +1,227 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+uintptr_t
+ngx_http_v3_encode_varlen_int(u_char *p, uint64_t value)
+{
+ if (value <= 63) {
+ if (p == NULL) {
+ return 1;
+ }
+
+ *p++ = value;
+ return (uintptr_t) p;
+ }
+
+ if (value <= 16383) {
+ if (p == NULL) {
+ return 2;
+ }
+
+ *p++ = 0x40 | (value >> 8);
+ *p++ = value;
+ return (uintptr_t) p;
+ }
+
+ if (value <= 1073741823) {
+ if (p == NULL) {
+ return 4;
+ }
+
+ *p++ = 0x80 | (value >> 24);
+ *p++ = (value >> 16);
+ *p++ = (value >> 8);
+ *p++ = value;
+ return (uintptr_t) p;
+ }
+
+ if (p == NULL) {
+ return 8;
+ }
+
+ *p++ = 0xc0 | (value >> 56);
+ *p++ = (value >> 48);
+ *p++ = (value >> 40);
+ *p++ = (value >> 32);
+ *p++ = (value >> 24);
+ *p++ = (value >> 16);
+ *p++ = (value >> 8);
+ *p++ = value;
+ return (uintptr_t) p;
+}
+
+
+uintptr_t
+ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value, ngx_uint_t prefix)
+{
+ ngx_uint_t thresh, n;
+
+ thresh = (1 << prefix) - 1;
+
+ if (value < thresh) {
+ if (p == NULL) {
+ return 1;
+ }
+
+ *p++ |= value;
+ return (uintptr_t) p;
+ }
+
+ value -= thresh;
+
+ if (p == NULL) {
+ for (n = 2; value >= 128; n++) {
+ value >>= 7;
+ }
+
+ return n;
+ }
+
+ *p++ |= thresh;
+
+ while (value >= 128) {
+ *p++ = 0x80 | value;
+ value >>= 7;
+ }
+
+ *p++ = value;
+
+ return (uintptr_t) p;
+}
+
+
+uintptr_t
+ngx_http_v3_encode_header_block_prefix(u_char *p, ngx_uint_t insert_count,
+ ngx_uint_t sign, ngx_uint_t delta_base)
+{
+ if (p == NULL) {
+ return ngx_http_v3_encode_prefix_int(NULL, insert_count, 8)
+ + ngx_http_v3_encode_prefix_int(NULL, delta_base, 7);
+ }
+
+ *p = 0;
+ p = (u_char *) ngx_http_v3_encode_prefix_int(p, insert_count, 8);
+
+ *p = sign ? 0x80 : 0;
+ p = (u_char *) ngx_http_v3_encode_prefix_int(p, delta_base, 7);
+
+ return (uintptr_t) p;
+}
+
+
+uintptr_t
+ngx_http_v3_encode_header_ri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index)
+{
+ /* Indexed Header Field */
+
+ if (p == NULL) {
+ return ngx_http_v3_encode_prefix_int(NULL, index, 6);
+ }
+
+ *p = dynamic ? 0x80 : 0xc0;
+
+ return ngx_http_v3_encode_prefix_int(p, index, 6);
+}
+
+
+uintptr_t
+ngx_http_v3_encode_header_lri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index,
+ u_char *data, size_t len)
+{
+ /* Literal Header Field With Name Reference */
+
+ if (p == NULL) {
+ return ngx_http_v3_encode_prefix_int(NULL, index, 4)
+ + ngx_http_v3_encode_prefix_int(NULL, len, 7)
+ + len;
+ }
+
+ *p = dynamic ? 0x60 : 0x70;
+ p = (u_char *) ngx_http_v3_encode_prefix_int(p, index, 4);
+
+ *p = 0;
+ p = (u_char *) ngx_http_v3_encode_prefix_int(p, len, 7);
+
+ if (data) {
+ p = ngx_cpymem(p, data, len);
+ }
+
+ return (uintptr_t) p;
+}
+
+
+uintptr_t
+ngx_http_v3_encode_header_l(u_char *p, ngx_str_t *name, ngx_str_t *value)
+{
+ /* Literal Header Field Without Name Reference */
+
+ if (p == NULL) {
+ return ngx_http_v3_encode_prefix_int(NULL, name->len, 3)
+ + name->len
+ + ngx_http_v3_encode_prefix_int(NULL, value->len, 7)
+ + value->len;
+ }
+
+ *p = 0x30;
+ p = (u_char *) ngx_http_v3_encode_prefix_int(p, name->len, 3);
+
+ ngx_strlow(p, name->data, name->len);
+ p += name->len;
+
+ *p = 0;
+ p = (u_char *) ngx_http_v3_encode_prefix_int(p, value->len, 7);
+
+ p = ngx_cpymem(p, value->data, value->len);
+
+ return (uintptr_t) p;
+}
+
+
+uintptr_t
+ngx_http_v3_encode_header_pbi(u_char *p, ngx_uint_t index)
+{
+ /* Indexed Header Field With Post-Base Index */
+
+ if (p == NULL) {
+ return ngx_http_v3_encode_prefix_int(NULL, index, 4);
+ }
+
+ *p = 0x10;
+
+ return ngx_http_v3_encode_prefix_int(p, index, 4);
+}
+
+
+uintptr_t
+ngx_http_v3_encode_header_lpbi(u_char *p, ngx_uint_t index, u_char *data,
+ size_t len)
+{
+ /* Literal Header Field With Post-Base Name Reference */
+
+ if (p == NULL) {
+ return ngx_http_v3_encode_prefix_int(NULL, index, 3)
+ + ngx_http_v3_encode_prefix_int(NULL, len, 7)
+ + len;
+ }
+
+ *p = 0x08;
+ p = (u_char *) ngx_http_v3_encode_prefix_int(p, index, 3);
+
+ *p = 0;
+ p = (u_char *) ngx_http_v3_encode_prefix_int(p, len, 7);
+
+ if (data) {
+ p = ngx_cpymem(p, data, len);
+ }
+
+ return (uintptr_t) p;
+}
diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c
new file mode 100644
index 000000000..89748f5f4
--- /dev/null
+++ b/src/http/v3/ngx_http_v3_module.c
@@ -0,0 +1,291 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ * Copyright (C) Roman Arutyunyan
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static ngx_int_t ngx_http_variable_http3(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_v3_add_variables(ngx_conf_t *cf);
+static void *ngx_http_v3_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+static void *ngx_http_v3_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_v3_merge_loc_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+static char *ngx_http_v3_push(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+
+
+static ngx_command_t ngx_http_v3_commands[] = {
+
+ { ngx_string("http3_max_field_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_v3_srv_conf_t, max_field_size),
+ NULL },
+
+ { ngx_string("http3_max_table_capacity"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_v3_srv_conf_t, max_table_capacity),
+ NULL },
+
+ { ngx_string("http3_max_blocked_streams"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_v3_srv_conf_t, max_blocked_streams),
+ NULL },
+
+ { ngx_string("http3_max_concurrent_pushes"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_v3_srv_conf_t, max_concurrent_pushes),
+ NULL },
+
+ { ngx_string("http3_push"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_http_v3_push,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("http3_push_preload"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_v3_loc_conf_t, push_preload),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_v3_module_ctx = {
+ ngx_http_v3_add_variables, /* preconfiguration */
+ NULL, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ ngx_http_v3_create_srv_conf, /* create server configuration */
+ ngx_http_v3_merge_srv_conf, /* merge server configuration */
+
+ ngx_http_v3_create_loc_conf, /* create location configuration */
+ ngx_http_v3_merge_loc_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_v3_module = {
+ NGX_MODULE_V1,
+ &ngx_http_v3_module_ctx, /* module context */
+ ngx_http_v3_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_variable_t ngx_http_v3_vars[] = {
+
+ { ngx_string("http3"), NULL, ngx_http_variable_http3, 0, 0, 0 },
+
+ ngx_http_null_variable
+};
+
+
+static ngx_int_t
+ngx_http_variable_http3(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ v->valid = 1;
+ v->no_cacheable = 1;
+ v->not_found = 0;
+
+ v->data = ngx_pnalloc(r->pool, sizeof("h3-xx") - 1);
+ if (v->data == NULL) {
+ return NGX_ERROR;
+ }
+
+ v->len = ngx_sprintf(v->data, "h3-%d", NGX_QUIC_DRAFT_VERSION) - v->data;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v3_add_variables(ngx_conf_t *cf)
+{
+ ngx_http_variable_t *var, *v;
+
+ for (v = ngx_http_v3_vars; v->name.len; v++) {
+ var = ngx_http_add_variable(cf, &v->name, v->flags);
+ if (var == NULL) {
+ return NGX_ERROR;
+ }
+
+ var->get_handler = v->get_handler;
+ var->data = v->data;
+ }
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_http_v3_create_srv_conf(ngx_conf_t *cf)
+{
+ ngx_http_v3_srv_conf_t *h3scf;
+
+ h3scf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v3_srv_conf_t));
+ if (h3scf == NULL) {
+ return NULL;
+ }
+
+ h3scf->max_field_size = NGX_CONF_UNSET_SIZE;
+ h3scf->max_table_capacity = NGX_CONF_UNSET_SIZE;
+ h3scf->max_blocked_streams = NGX_CONF_UNSET_UINT;
+ h3scf->max_concurrent_pushes = NGX_CONF_UNSET_UINT;
+
+ return h3scf;
+}
+
+
+static char *
+ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_v3_srv_conf_t *prev = parent;
+ ngx_http_v3_srv_conf_t *conf = child;
+
+ ngx_conf_merge_size_value(conf->max_field_size,
+ prev->max_field_size,
+ NGX_HTTP_V3_DEFAULT_MAX_FIELD_SIZE);
+
+ ngx_conf_merge_size_value(conf->max_table_capacity,
+ prev->max_table_capacity,
+ NGX_HTTP_V3_DEFAULT_MAX_TABLE_CAPACITY);
+
+ ngx_conf_merge_uint_value(conf->max_blocked_streams,
+ prev->max_blocked_streams,
+ NGX_HTTP_V3_DEFAULT_MAX_BLOCKED_STREAMS);
+
+ ngx_conf_merge_uint_value(conf->max_concurrent_pushes,
+ prev->max_concurrent_pushes,
+ NGX_HTTP_V3_DEFAULT_MAX_CONCURRENT_PUSHES);
+
+ return NGX_CONF_OK;
+}
+
+
+static void *
+ngx_http_v3_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_v3_loc_conf_t *h3lcf;
+
+ h3lcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v3_loc_conf_t));
+ if (h3lcf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * h3lcf->pushes = NULL;
+ */
+
+ h3lcf->push_preload = NGX_CONF_UNSET;
+ h3lcf->push = NGX_CONF_UNSET;
+
+ return h3lcf;
+}
+
+
+static char *
+ngx_http_v3_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_v3_loc_conf_t *prev = parent;
+ ngx_http_v3_loc_conf_t *conf = child;
+
+ ngx_conf_merge_value(conf->push, prev->push, 1);
+
+ if (conf->push && conf->pushes == NULL) {
+ conf->pushes = prev->pushes;
+ }
+
+ ngx_conf_merge_value(conf->push_preload, prev->push_preload, 0);
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_v3_push(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_v3_loc_conf_t *h3lcf = conf;
+
+ ngx_str_t *value;
+ ngx_http_complex_value_t *cv;
+ ngx_http_compile_complex_value_t ccv;
+
+ value = cf->args->elts;
+
+ if (ngx_strcmp(value[1].data, "off") == 0) {
+
+ if (h3lcf->pushes) {
+ return "\"off\" parameter cannot be used with URI";
+ }
+
+ if (h3lcf->push == 0) {
+ return "is duplicate";
+ }
+
+ h3lcf->push = 0;
+ return NGX_CONF_OK;
+ }
+
+ if (h3lcf->push == 0) {
+ return "URI cannot be used with \"off\" parameter";
+ }
+
+ h3lcf->push = 1;
+
+ if (h3lcf->pushes == NULL) {
+ h3lcf->pushes = ngx_array_create(cf->pool, 1,
+ sizeof(ngx_http_complex_value_t));
+ if (h3lcf->pushes == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ cv = ngx_array_push(h3lcf->pushes);
+ if (cv == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[1];
+ ccv.complex_value = cv;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c
new file mode 100644
index 000000000..1a7aa17f8
--- /dev/null
+++ b/src/http/v3/ngx_http_v3_parse.c
@@ -0,0 +1,1582 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static ngx_int_t ngx_http_v3_parse_lookup(ngx_connection_t *c,
+ ngx_uint_t dynamic, ngx_uint_t index, ngx_str_t *name, ngx_str_t *value);
+
+
+ngx_int_t
+ngx_http_v3_parse_varlen_int(ngx_connection_t *c,
+ ngx_http_v3_parse_varlen_int_t *st, u_char ch)
+{
+ enum {
+ sw_start = 0,
+ sw_length_2,
+ sw_length_3,
+ sw_length_4,
+ sw_length_5,
+ sw_length_6,
+ sw_length_7,
+ sw_length_8
+ };
+
+ switch (st->state) {
+
+ case sw_start:
+
+ st->value = ch;
+ if (st->value & 0xc0) {
+ st->state = sw_length_2;
+ break;
+ }
+
+ goto done;
+
+ case sw_length_2:
+
+ st->value = (st->value << 8) + ch;
+ if ((st->value & 0xc000) == 0x4000) {
+ st->value &= 0x3fff;
+ goto done;
+ }
+
+ st->state = sw_length_3;
+ break;
+
+ case sw_length_4:
+
+ st->value = (st->value << 8) + ch;
+ if ((st->value & 0xc0000000) == 0x80000000) {
+ st->value &= 0x3fffffff;
+ goto done;
+ }
+
+ st->state = sw_length_5;
+ break;
+
+ case sw_length_3:
+ case sw_length_5:
+ case sw_length_6:
+ case sw_length_7:
+
+ st->value = (st->value << 8) + ch;
+ st->state++;
+ break;
+
+ case sw_length_8:
+
+ st->value = (st->value << 8) + ch;
+ st->value &= 0x3fffffffffffffff;
+ goto done;
+ }
+
+ return NGX_AGAIN;
+
+done:
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse varlen int %uL", st->value);
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_prefix_int(ngx_connection_t *c,
+ ngx_http_v3_parse_prefix_int_t *st, ngx_uint_t prefix, u_char ch)
+{
+ ngx_uint_t mask;
+ enum {
+ sw_start = 0,
+ sw_value
+ };
+
+ switch (st->state) {
+
+ case sw_start:
+
+ mask = (1 << prefix) - 1;
+ st->value = ch & mask;
+
+ if (st->value != mask) {
+ goto done;
+ }
+
+ st->shift = 0;
+ st->state = sw_value;
+ break;
+
+ case sw_value:
+
+ st->value += (uint64_t) (ch & 0x7f) << st->shift;
+
+ if (st->shift == 56
+ && ((ch & 0x80) || (st->value & 0xc000000000000000)))
+ {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client exceeded integer size limit");
+ return NGX_HTTP_V3_ERR_EXCESSIVE_LOAD;
+ }
+
+ if (ch & 0x80) {
+ st->shift += 7;
+ break;
+ }
+
+ goto done;
+ }
+
+ return NGX_AGAIN;
+
+done:
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse prefix int %uL", st->value);
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st,
+ u_char ch)
+{
+ ngx_int_t rc;
+ enum {
+ sw_start = 0,
+ sw_length,
+ sw_prefix,
+ sw_verify,
+ sw_header_rep,
+ sw_done
+ };
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse headers");
+
+ if (ch != NGX_HTTP_V3_FRAME_HEADERS) {
+ return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED;
+ }
+
+ st->state = sw_length;
+ break;
+
+ case sw_length:
+
+ rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->length = st->vlint.value;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse headers len:%ui", st->length);
+
+ st->state = sw_prefix;
+ break;
+
+ case sw_prefix:
+
+ if (st->length-- == 0) {
+ return NGX_HTTP_V3_ERR_FRAME_ERROR;
+ }
+
+ rc = ngx_http_v3_parse_header_block_prefix(c, &st->prefix, ch);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ if (st->length == 0) {
+ return NGX_HTTP_V3_ERR_FRAME_ERROR;
+ }
+
+ st->state = sw_verify;
+ break;
+
+ case sw_verify:
+
+ rc = ngx_http_v3_check_insert_count(c, st->prefix.insert_count);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ st->state = sw_header_rep;
+
+ /* fall through */
+
+ case sw_header_rep:
+
+ rc = ngx_http_v3_parse_header_rep(c, &st->header_rep, st->prefix.base,
+ ch);
+
+ if (--st->length == 0 && rc == NGX_AGAIN) {
+ return NGX_HTTP_V3_ERR_FRAME_ERROR;
+ }
+
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ if (st->length == 0) {
+ goto done;
+ }
+
+ return NGX_OK;
+ }
+
+ return NGX_AGAIN;
+
+done:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse headers done");
+
+ if (st->prefix.insert_count > 0) {
+ if (ngx_http_v3_client_ack_header(c, c->qs->id) != NGX_OK) {
+ return NGX_ERROR;
+ }
+ }
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_header_block_prefix(ngx_connection_t *c,
+ ngx_http_v3_parse_header_block_prefix_t *st, u_char ch)
+{
+ ngx_int_t rc;
+ enum {
+ sw_start = 0,
+ sw_req_insert_count,
+ sw_delta_base,
+ sw_read_delta_base
+ };
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse header block prefix");
+
+ st->state = sw_req_insert_count;
+
+ /* fall through */
+
+ case sw_req_insert_count:
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 8, ch);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->insert_count = st->pint.value;
+ st->state = sw_delta_base;
+ break;
+
+ case sw_delta_base:
+
+ st->sign = (ch & 0x80) ? 1 : 0;
+ st->state = sw_read_delta_base;
+
+ /* fall through */
+
+ case sw_read_delta_base:
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->delta_base = st->pint.value;
+ goto done;
+ }
+
+ return NGX_AGAIN;
+
+done:
+
+ rc = ngx_http_v3_decode_insert_count(c, &st->insert_count);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ if (st->sign) {
+ st->base = st->insert_count - st->delta_base - 1;
+ } else {
+ st->base = st->insert_count + st->delta_base;
+ }
+
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse header block prefix done "
+ "insert_count:%ui, sign:%ui, delta_base:%ui, base:%ui",
+ st->insert_count, st->sign, st->delta_base, st->base);
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_header_rep(ngx_connection_t *c,
+ ngx_http_v3_parse_header_rep_t *st, ngx_uint_t base, u_char ch)
+{
+ ngx_int_t rc;
+ enum {
+ sw_start = 0,
+ sw_header_ri,
+ sw_header_lri,
+ sw_header_l,
+ sw_header_pbi,
+ sw_header_lpbi
+ };
+
+ if (st->state == sw_start) {
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse header representation");
+
+ ngx_memzero(&st->header, sizeof(ngx_http_v3_parse_header_t));
+
+ st->header.base = base;
+
+ if (ch & 0x80) {
+ /* Indexed Header Field */
+
+ st->state = sw_header_ri;
+
+ } else if (ch & 0x40) {
+ /* Literal Header Field With Name Reference */
+
+ st->state = sw_header_lri;
+
+ } else if (ch & 0x20) {
+ /* Literal Header Field Without Name Reference */
+
+ st->state = sw_header_l;
+
+ } else if (ch & 0x10) {
+ /* Indexed Header Field With Post-Base Index */
+
+ st->state = sw_header_pbi;
+
+ } else {
+ /* Literal Header Field With Post-Base Name Reference */
+
+ st->state = sw_header_lpbi;
+ }
+ }
+
+ switch (st->state) {
+
+ case sw_header_ri:
+ rc = ngx_http_v3_parse_header_ri(c, &st->header, ch);
+ break;
+
+ case sw_header_lri:
+ rc = ngx_http_v3_parse_header_lri(c, &st->header, ch);
+ break;
+
+ case sw_header_l:
+ rc = ngx_http_v3_parse_header_l(c, &st->header, ch);
+ break;
+
+ case sw_header_pbi:
+ rc = ngx_http_v3_parse_header_pbi(c, &st->header, ch);
+ break;
+
+ case sw_header_lpbi:
+ rc = ngx_http_v3_parse_header_lpbi(c, &st->header, ch);
+ break;
+
+ default:
+ rc = NGX_OK;
+ }
+
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse header representation done");
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_literal(ngx_connection_t *c, ngx_http_v3_parse_literal_t *st,
+ u_char ch)
+{
+ ngx_uint_t n;
+ ngx_http_v3_srv_conf_t *h3scf;
+ enum {
+ sw_start = 0,
+ sw_value
+ };
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse literal huff:%ui, len:%ui",
+ st->huffman, st->length);
+
+ n = st->length;
+
+ h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module);
+
+ if (n > h3scf->max_field_size) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client exceeded http3_max_field_size limit");
+ return NGX_HTTP_V3_ERR_EXCESSIVE_LOAD;
+ }
+
+ if (st->huffman) {
+ n = n * 8 / 5;
+ st->huffstate = 0;
+ }
+
+ st->last = ngx_pnalloc(c->pool, n + 1);
+ if (st->last == NULL) {
+ return NGX_ERROR;
+ }
+
+ st->value.data = st->last;
+ st->state = sw_value;
+
+ /* fall through */
+
+ case sw_value:
+
+ if (st->huffman) {
+ if (ngx_http_v2_huff_decode(&st->huffstate, &ch, 1, &st->last,
+ st->length == 1, c->log)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ } else {
+ *st->last++ = ch;
+ }
+
+ if (--st->length) {
+ break;
+ }
+
+ st->value.len = st->last - st->value.data;
+ *st->last = '\0';
+ goto done;
+ }
+
+ return NGX_AGAIN;
+
+done:
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse literal done \"%V\"", &st->value);
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_header_ri(ngx_connection_t *c, ngx_http_v3_parse_header_t *st,
+ u_char ch)
+{
+ ngx_int_t rc;
+ enum {
+ sw_start = 0,
+ sw_index
+ };
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header ri");
+
+ st->dynamic = (ch & 0x40) ? 0 : 1;
+ st->state = sw_index;
+
+ /* fall through */
+
+ case sw_index:
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, ch);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->index = st->pint.value;
+ goto done;
+ }
+
+ return NGX_AGAIN;
+
+done:
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse header ri done %s%ui]",
+ st->dynamic ? "dynamic[-" : "static[", st->index);
+
+ if (st->dynamic) {
+ st->index = st->base - st->index - 1;
+ }
+
+ rc = ngx_http_v3_parse_lookup(c, st->dynamic, st->index, &st->name,
+ &st->value);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_header_lri(ngx_connection_t *c,
+ ngx_http_v3_parse_header_t *st, u_char ch)
+{
+ ngx_int_t rc;
+ enum {
+ sw_start = 0,
+ sw_index,
+ sw_value_len,
+ sw_read_value_len,
+ sw_value
+ };
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header lri");
+
+ st->dynamic = (ch & 0x10) ? 0 : 1;
+ st->state = sw_index;
+
+ /* fall through */
+
+ case sw_index:
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 4, ch);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->index = st->pint.value;
+ st->state = sw_value_len;
+ break;
+
+ case sw_value_len:
+
+ st->literal.huffman = (ch & 0x80) ? 1 : 0;
+ st->state = sw_read_value_len;
+
+ /* fall through */
+
+ case sw_read_value_len:
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->literal.length = st->pint.value;
+ if (st->literal.length == 0) {
+ goto done;
+ }
+
+ st->state = sw_value;
+ break;
+
+ case sw_value:
+
+ rc = ngx_http_v3_parse_literal(c, &st->literal, ch);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->value = st->literal.value;
+ goto done;
+ }
+
+ return NGX_AGAIN;
+
+done:
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse header lri done %s%ui] \"%V\"",
+ st->dynamic ? "dynamic[-" : "static[",
+ st->index, &st->value);
+
+ if (st->dynamic) {
+ st->index = st->base - st->index - 1;
+ }
+
+ rc = ngx_http_v3_parse_lookup(c, st->dynamic, st->index, &st->name, NULL);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_header_l(ngx_connection_t *c,
+ ngx_http_v3_parse_header_t *st, u_char ch)
+{
+ ngx_int_t rc;
+ enum {
+ sw_start = 0,
+ sw_name_len,
+ sw_name,
+ sw_value_len,
+ sw_read_value_len,
+ sw_value
+ };
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header l");
+
+ st->literal.huffman = (ch & 0x08) ? 1 : 0;
+ st->state = sw_name_len;
+
+ /* fall through */
+
+ case sw_name_len:
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 3, ch);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->literal.length = st->pint.value;
+ if (st->literal.length == 0) {
+ return NGX_ERROR;
+ }
+
+ st->state = sw_name;
+ break;
+
+ case sw_name:
+
+ rc = ngx_http_v3_parse_literal(c, &st->literal, ch);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->name = st->literal.value;
+ st->state = sw_value_len;
+ break;
+
+ case sw_value_len:
+
+ st->literal.huffman = (ch & 0x80) ? 1 : 0;
+ st->state = sw_read_value_len;
+
+ /* fall through */
+
+ case sw_read_value_len:
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->literal.length = st->pint.value;
+ if (st->literal.length == 0) {
+ goto done;
+ }
+
+ st->state = sw_value;
+ break;
+
+ case sw_value:
+
+ rc = ngx_http_v3_parse_literal(c, &st->literal, ch);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->value = st->literal.value;
+ goto done;
+ }
+
+ return NGX_AGAIN;
+
+done:
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse header l done \"%V\" \"%V\"",
+ &st->name, &st->value);
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_header_pbi(ngx_connection_t *c,
+ ngx_http_v3_parse_header_t *st, u_char ch)
+{
+ ngx_int_t rc;
+ enum {
+ sw_start = 0,
+ sw_index
+ };
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header pbi");
+
+ st->state = sw_index;
+
+ /* fall through */
+
+ case sw_index:
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 4, ch);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->index = st->pint.value;
+ goto done;
+ }
+
+ return NGX_AGAIN;
+
+done:
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse header pbi done dynamic[+%ui]", st->index);
+
+ rc = ngx_http_v3_parse_lookup(c, 1, st->base + st->index, &st->name,
+ &st->value);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_header_lpbi(ngx_connection_t *c,
+ ngx_http_v3_parse_header_t *st, u_char ch)
+{
+ ngx_int_t rc;
+ enum {
+ sw_start = 0,
+ sw_index,
+ sw_value_len,
+ sw_read_value_len,
+ sw_value
+ };
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse header lpbi");
+
+ st->state = sw_index;
+
+ /* fall through */
+
+ case sw_index:
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 3, ch);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->index = st->pint.value;
+ st->state = sw_value_len;
+ break;
+
+ case sw_value_len:
+
+ st->literal.huffman = (ch & 0x80) ? 1 : 0;
+ st->state = sw_read_value_len;
+
+ /* fall through */
+
+ case sw_read_value_len:
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->literal.length = st->pint.value;
+ if (st->literal.length == 0) {
+ goto done;
+ }
+
+ st->state = sw_value;
+ break;
+
+ case sw_value:
+
+ rc = ngx_http_v3_parse_literal(c, &st->literal, ch);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->value = st->literal.value;
+ goto done;
+ }
+
+ return NGX_AGAIN;
+
+done:
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse header lpbi done dynamic[+%ui] \"%V\"",
+ st->index, &st->value);
+
+ rc = ngx_http_v3_parse_lookup(c, 1, st->base + st->index, &st->name, NULL);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_http_v3_parse_lookup(ngx_connection_t *c, ngx_uint_t dynamic,
+ ngx_uint_t index, ngx_str_t *name, ngx_str_t *value)
+{
+ u_char *p;
+
+ if (!dynamic) {
+ if (ngx_http_v3_lookup_static(c, index, name, value) != NGX_OK) {
+ return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED;
+ }
+
+ return NGX_OK;
+ }
+
+ if (ngx_http_v3_lookup(c, index, name, value) != NGX_OK) {
+ return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED;
+ }
+
+ if (name) {
+ p = ngx_pnalloc(c->pool, name->len + 1);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(p, name->data, name->len);
+ p[name->len] = '\0';
+ name->data = p;
+ }
+
+ if (value) {
+ p = ngx_pnalloc(c->pool, value->len + 1);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(p, value->data, value->len);
+ p[value->len] = '\0';
+ value->data = p;
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_control(ngx_connection_t *c, void *data, u_char ch)
+{
+ ngx_http_v3_parse_control_t *st = data;
+
+ ngx_int_t rc;
+ enum {
+ sw_start = 0,
+ sw_first_type,
+ sw_type,
+ sw_length,
+ sw_cancel_push,
+ sw_settings,
+ sw_max_push_id,
+ sw_skip
+ };
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse control");
+
+ st->state = sw_first_type;
+
+ /* fall through */
+
+ case sw_first_type:
+ case sw_type:
+
+ rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->type = st->vlint.value;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse frame type:%ui", st->type);
+
+ if (st->state == sw_first_type
+ && st->type != NGX_HTTP_V3_FRAME_SETTINGS)
+ {
+ return NGX_HTTP_V3_ERR_MISSING_SETTINGS;
+ }
+
+ st->state = sw_length;
+ break;
+
+ case sw_length:
+
+ rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse frame len:%uL", st->vlint.value);
+
+ st->length = st->vlint.value;
+ if (st->length == 0) {
+ st->state = sw_type;
+ break;
+ }
+
+ switch (st->type) {
+
+ case NGX_HTTP_V3_FRAME_CANCEL_PUSH:
+ st->state = sw_cancel_push;
+ break;
+
+ case NGX_HTTP_V3_FRAME_SETTINGS:
+ st->state = sw_settings;
+ break;
+
+ case NGX_HTTP_V3_FRAME_MAX_PUSH_ID:
+ st->state = sw_max_push_id;
+ break;
+
+ default:
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse skip unknown frame");
+ st->state = sw_skip;
+ }
+
+ break;
+
+ case sw_cancel_push:
+
+ rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch);
+
+ if (--st->length == 0 && rc == NGX_AGAIN) {
+ return NGX_HTTP_V3_ERR_FRAME_ERROR;
+ }
+
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ rc = ngx_http_v3_cancel_push(c, st->vlint.value);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ st->state = sw_type;
+ break;
+
+ case sw_settings:
+
+ rc = ngx_http_v3_parse_settings(c, &st->settings, ch);
+
+ if (--st->length == 0 && rc == NGX_AGAIN) {
+ return NGX_HTTP_V3_ERR_SETTINGS_ERROR;
+ }
+
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ if (st->length == 0) {
+ st->state = sw_type;
+ }
+
+ break;
+
+ case sw_max_push_id:
+
+ rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch);
+
+ if (--st->length == 0 && rc == NGX_AGAIN) {
+ return NGX_HTTP_V3_ERR_FRAME_ERROR;
+ }
+
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ rc = ngx_http_v3_set_max_push_id(c, st->vlint.value);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ st->state = sw_type;
+ break;
+
+ case sw_skip:
+
+ if (--st->length == 0) {
+ st->state = sw_type;
+ }
+
+ break;
+ }
+
+ return NGX_AGAIN;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_settings(ngx_connection_t *c,
+ ngx_http_v3_parse_settings_t *st, u_char ch)
+{
+ ngx_int_t rc;
+ enum {
+ sw_start = 0,
+ sw_id,
+ sw_value
+ };
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse settings");
+
+ st->state = sw_id;
+
+ /* fall through */
+
+ case sw_id:
+
+ rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->id = st->vlint.value;
+ st->state = sw_value;
+ break;
+
+ case sw_value:
+
+ rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ if (ngx_http_v3_set_param(c, st->id, st->vlint.value) != NGX_OK) {
+ return NGX_HTTP_V3_ERR_SETTINGS_ERROR;
+ }
+
+ goto done;
+ }
+
+ return NGX_AGAIN;
+
+done:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse settings done");
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_encoder(ngx_connection_t *c, void *data, u_char ch)
+{
+ ngx_http_v3_parse_encoder_t *st = data;
+
+ ngx_int_t rc;
+ enum {
+ sw_start = 0,
+ sw_inr,
+ sw_iwnr,
+ sw_capacity,
+ sw_duplicate
+ };
+
+ if (st->state == sw_start) {
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse encoder instruction");
+
+ if (ch & 0x80) {
+ /* Insert With Name Reference */
+
+ st->state = sw_inr;
+
+ } else if (ch & 0x40) {
+ /* Insert Without Name Reference */
+
+ st->state = sw_iwnr;
+
+ } else if (ch & 0x20) {
+ /* Set Dynamic Table Capacity */
+
+ st->state = sw_capacity;
+
+ } else {
+ /* Duplicate */
+
+ st->state = sw_duplicate;
+ }
+ }
+
+ switch (st->state) {
+
+ case sw_inr:
+
+ rc = ngx_http_v3_parse_header_inr(c, &st->header, ch);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ goto done;
+
+ case sw_iwnr:
+
+ rc = ngx_http_v3_parse_header_iwnr(c, &st->header, ch);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ goto done;
+
+ case sw_capacity:
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 5, ch);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ rc = ngx_http_v3_set_capacity(c, st->pint.value);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ goto done;
+
+ case sw_duplicate:
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 5, ch);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ rc = ngx_http_v3_duplicate(c, st->pint.value);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ goto done;
+ }
+
+ return NGX_AGAIN;
+
+done:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse encoder instruction done");
+
+ st->state = sw_start;
+ return NGX_AGAIN;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_header_inr(ngx_connection_t *c,
+ ngx_http_v3_parse_header_t *st, u_char ch)
+{
+ ngx_int_t rc;
+ enum {
+ sw_start = 0,
+ sw_name_index,
+ sw_value_len,
+ sw_read_value_len,
+ sw_value
+ };
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header inr");
+
+ st->dynamic = (ch & 0x40) ? 0 : 1;
+ st->state = sw_name_index;
+
+ /* fall through */
+
+ case sw_name_index:
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, ch);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->index = st->pint.value;
+ st->state = sw_value_len;
+ break;
+
+ case sw_value_len:
+
+ st->literal.huffman = (ch & 0x80) ? 1 : 0;
+ st->state = sw_read_value_len;
+
+ /* fall through */
+
+ case sw_read_value_len:
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->literal.length = st->pint.value;
+ if (st->literal.length == 0) {
+ goto done;
+ }
+
+ st->state = sw_value;
+ break;
+
+ case sw_value:
+
+ rc = ngx_http_v3_parse_literal(c, &st->literal, ch);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->value = st->literal.value;
+ goto done;
+ }
+
+ return NGX_AGAIN;
+
+done:
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse header inr done %s[%ui] \"%V\"",
+ st->dynamic ? "dynamic" : "static",
+ st->index, &st->value);
+
+ rc = ngx_http_v3_ref_insert(c, st->dynamic, st->index, &st->value);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_header_iwnr(ngx_connection_t *c,
+ ngx_http_v3_parse_header_t *st, u_char ch)
+{
+ ngx_int_t rc;
+ enum {
+ sw_start = 0,
+ sw_name_len,
+ sw_name,
+ sw_value_len,
+ sw_read_value_len,
+ sw_value
+ };
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse header iwnr");
+
+ st->literal.huffman = (ch & 0x20) ? 1 : 0;
+ st->state = sw_name_len;
+
+ /* fall through */
+
+ case sw_name_len:
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 5, ch);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->literal.length = st->pint.value;
+ if (st->literal.length == 0) {
+ return NGX_ERROR;
+ }
+
+ st->state = sw_name;
+ break;
+
+ case sw_name:
+
+ rc = ngx_http_v3_parse_literal(c, &st->literal, ch);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->name = st->literal.value;
+ st->state = sw_value_len;
+ break;
+
+ case sw_value_len:
+
+ st->literal.huffman = (ch & 0x80) ? 1 : 0;
+ st->state = sw_read_value_len;
+
+ /* fall through */
+
+ case sw_read_value_len:
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->literal.length = st->pint.value;
+ if (st->literal.length == 0) {
+ goto done;
+ }
+
+ st->state = sw_value;
+ break;
+
+ case sw_value:
+
+ rc = ngx_http_v3_parse_literal(c, &st->literal, ch);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->value = st->literal.value;
+ goto done;
+ }
+
+ return NGX_AGAIN;
+
+done:
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse header iwnr done \"%V\":\"%V\"",
+ &st->name, &st->value);
+
+ rc = ngx_http_v3_insert(c, &st->name, &st->value);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_decoder(ngx_connection_t *c, void *data, u_char ch)
+{
+ ngx_http_v3_parse_decoder_t *st = data;
+
+ ngx_int_t rc;
+ enum {
+ sw_start = 0,
+ sw_ack_header,
+ sw_cancel_stream,
+ sw_inc_insert_count
+ };
+
+ if (st->state == sw_start) {
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse decoder instruction");
+
+ if (ch & 0x80) {
+ /* Header Acknowledgement */
+
+ st->state = sw_ack_header;
+
+ } else if (ch & 0x40) {
+ /* Stream Cancellation */
+
+ st->state = sw_cancel_stream;
+
+ } else {
+ /* Insert Count Increment */
+
+ st->state = sw_inc_insert_count;
+ }
+ }
+
+ switch (st->state) {
+
+ case sw_ack_header:
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ rc = ngx_http_v3_ack_header(c, st->pint.value);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ goto done;
+
+ case sw_cancel_stream:
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, ch);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ rc = ngx_http_v3_cancel_stream(c, st->pint.value);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ goto done;
+
+ case sw_inc_insert_count:
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, ch);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ rc = ngx_http_v3_inc_insert_count(c, st->pint.value);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ goto done;
+ }
+
+ return NGX_AGAIN;
+
+done:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse decoder instruction done");
+
+ st->state = sw_start;
+ return NGX_AGAIN;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_data(ngx_connection_t *c, ngx_http_v3_parse_data_t *st,
+ u_char ch)
+{
+ ngx_int_t rc;
+ enum {
+ sw_start = 0,
+ sw_type,
+ sw_length
+ };
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse data");
+
+ st->state = sw_type;
+
+ /* fall through */
+
+ case sw_type:
+
+ rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ if (st->vlint.value != NGX_HTTP_V3_FRAME_DATA) {
+ return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED;
+ }
+
+ st->state = sw_length;
+ break;
+
+ case sw_length:
+
+ rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->length = st->vlint.value;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse data frame len:%ui", st->length);
+
+ goto done;
+ }
+
+ return NGX_AGAIN;
+
+done:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse data done");
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
diff --git a/src/http/v3/ngx_http_v3_parse.h b/src/http/v3/ngx_http_v3_parse.h
new file mode 100644
index 000000000..0c0af33b7
--- /dev/null
+++ b/src/http/v3/ngx_http_v3_parse.h
@@ -0,0 +1,165 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_HTTP_V3_PARSE_H_INCLUDED_
+#define _NGX_HTTP_V3_PARSE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ ngx_uint_t state;
+ uint64_t value;
+} ngx_http_v3_parse_varlen_int_t;
+
+
+typedef struct {
+ ngx_uint_t state;
+ ngx_uint_t shift;
+ uint64_t value;
+} ngx_http_v3_parse_prefix_int_t;
+
+
+typedef struct {
+ ngx_uint_t state;
+ uint64_t id;
+ ngx_http_v3_parse_varlen_int_t vlint;
+} ngx_http_v3_parse_settings_t;
+
+
+typedef struct {
+ ngx_uint_t state;
+ ngx_uint_t insert_count;
+ ngx_uint_t delta_base;
+ ngx_uint_t sign;
+ ngx_uint_t base;
+ ngx_http_v3_parse_prefix_int_t pint;
+} ngx_http_v3_parse_header_block_prefix_t;
+
+
+typedef struct {
+ ngx_uint_t state;
+ ngx_uint_t length;
+ ngx_uint_t huffman;
+ ngx_str_t value;
+ u_char *last;
+ u_char huffstate;
+} ngx_http_v3_parse_literal_t;
+
+
+typedef struct {
+ ngx_uint_t state;
+ ngx_uint_t index;
+ ngx_uint_t base;
+ ngx_uint_t dynamic;
+
+ ngx_str_t name;
+ ngx_str_t value;
+
+ ngx_http_v3_parse_prefix_int_t pint;
+ ngx_http_v3_parse_literal_t literal;
+} ngx_http_v3_parse_header_t;
+
+
+typedef struct {
+ ngx_uint_t state;
+ ngx_http_v3_parse_header_t header;
+} ngx_http_v3_parse_header_rep_t;
+
+
+typedef struct {
+ ngx_uint_t state;
+ ngx_uint_t length;
+ ngx_http_v3_parse_varlen_int_t vlint;
+ ngx_http_v3_parse_header_block_prefix_t prefix;
+ ngx_http_v3_parse_header_rep_t header_rep;
+} ngx_http_v3_parse_headers_t;
+
+
+typedef struct {
+ ngx_uint_t state;
+ ngx_http_v3_parse_header_t header;
+ ngx_http_v3_parse_prefix_int_t pint;
+} ngx_http_v3_parse_encoder_t;
+
+
+typedef struct {
+ ngx_uint_t state;
+ ngx_http_v3_parse_prefix_int_t pint;
+} ngx_http_v3_parse_decoder_t;
+
+
+typedef struct {
+ ngx_uint_t state;
+ ngx_uint_t type;
+ ngx_uint_t length;
+ ngx_http_v3_parse_varlen_int_t vlint;
+ ngx_http_v3_parse_settings_t settings;
+} ngx_http_v3_parse_control_t;
+
+
+typedef struct {
+ ngx_uint_t state;
+ ngx_uint_t length;
+ ngx_http_v3_parse_varlen_int_t vlint;
+} ngx_http_v3_parse_data_t;
+
+
+/*
+ * Parse functions return codes:
+ * NGX_DONE - parsing done
+ * NGX_OK - sub-element done
+ * NGX_AGAIN - more data expected
+ * NGX_BUSY - waiting for external event
+ * NGX_ERROR - internal error
+ * NGX_HTTP_V3_ERROR_XXX - HTTP/3 or QPACK error
+ */
+
+ngx_int_t ngx_http_v3_parse_varlen_int(ngx_connection_t *c,
+ ngx_http_v3_parse_varlen_int_t *st, u_char ch);
+ngx_int_t ngx_http_v3_parse_prefix_int(ngx_connection_t *c,
+ ngx_http_v3_parse_prefix_int_t *st, ngx_uint_t prefix, u_char ch);
+
+ngx_int_t ngx_http_v3_parse_headers(ngx_connection_t *c,
+ ngx_http_v3_parse_headers_t *st, u_char ch);
+ngx_int_t ngx_http_v3_parse_header_block_prefix(ngx_connection_t *c,
+ ngx_http_v3_parse_header_block_prefix_t *st, u_char ch);
+ngx_int_t ngx_http_v3_parse_header_rep(ngx_connection_t *c,
+ ngx_http_v3_parse_header_rep_t *st, ngx_uint_t base, u_char ch);
+ngx_int_t ngx_http_v3_parse_literal(ngx_connection_t *c,
+ ngx_http_v3_parse_literal_t *st, u_char ch);
+ngx_int_t ngx_http_v3_parse_header_ri(ngx_connection_t *c,
+ ngx_http_v3_parse_header_t *st, u_char ch);
+ngx_int_t ngx_http_v3_parse_header_lri(ngx_connection_t *c,
+ ngx_http_v3_parse_header_t *st, u_char ch);
+ngx_int_t ngx_http_v3_parse_header_l(ngx_connection_t *c,
+ ngx_http_v3_parse_header_t *st, u_char ch);
+ngx_int_t ngx_http_v3_parse_header_pbi(ngx_connection_t *c,
+ ngx_http_v3_parse_header_t *st, u_char ch);
+ngx_int_t ngx_http_v3_parse_header_lpbi(ngx_connection_t *c,
+ ngx_http_v3_parse_header_t *st, u_char ch);
+
+ngx_int_t ngx_http_v3_parse_control(ngx_connection_t *c, void *data, u_char ch);
+ngx_int_t ngx_http_v3_parse_settings(ngx_connection_t *c,
+ ngx_http_v3_parse_settings_t *st, u_char ch);
+
+ngx_int_t ngx_http_v3_parse_encoder(ngx_connection_t *c, void *data, u_char ch);
+ngx_int_t ngx_http_v3_parse_header_inr(ngx_connection_t *c,
+ ngx_http_v3_parse_header_t *st, u_char ch);
+ngx_int_t ngx_http_v3_parse_header_iwnr(ngx_connection_t *c,
+ ngx_http_v3_parse_header_t *st, u_char ch);
+
+ngx_int_t ngx_http_v3_parse_decoder(ngx_connection_t *c, void *data, u_char ch);
+
+ngx_int_t ngx_http_v3_parse_data(ngx_connection_t *c,
+ ngx_http_v3_parse_data_t *st, u_char ch);
+
+
+#endif /* _NGX_HTTP_V3_PARSE_H_INCLUDED_ */
diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c
new file mode 100644
index 000000000..a0259be11
--- /dev/null
+++ b/src/http/v3/ngx_http_v3_request.c
@@ -0,0 +1,1545 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+/* static table indices */
+#define NGX_HTTP_V3_HEADER_AUTHORITY 0
+#define NGX_HTTP_V3_HEADER_PATH_ROOT 1
+#define NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO 4
+#define NGX_HTTP_V3_HEADER_DATE 6
+#define NGX_HTTP_V3_HEADER_LAST_MODIFIED 10
+#define NGX_HTTP_V3_HEADER_LOCATION 12
+#define NGX_HTTP_V3_HEADER_METHOD_GET 17
+#define NGX_HTTP_V3_HEADER_SCHEME_HTTP 22
+#define NGX_HTTP_V3_HEADER_SCHEME_HTTPS 23
+#define NGX_HTTP_V3_HEADER_STATUS_200 25
+#define NGX_HTTP_V3_HEADER_ACCEPT_ENCODING 31
+#define NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN 53
+#define NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING 59
+#define NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE 72
+#define NGX_HTTP_V3_HEADER_SERVER 92
+#define NGX_HTTP_V3_HEADER_USER_AGENT 95
+
+
+static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r,
+ ngx_str_t *name, ngx_str_t *value);
+static ngx_int_t ngx_http_v3_push_resources(ngx_http_request_t *r,
+ ngx_chain_t ***out);
+static ngx_int_t ngx_http_v3_push_resource(ngx_http_request_t *r,
+ ngx_str_t *path, ngx_chain_t ***out);
+static ngx_int_t ngx_http_v3_create_push_request(
+ ngx_http_request_t *pr, ngx_str_t *path, uint64_t push_id);
+static ngx_int_t ngx_http_v3_set_push_header(ngx_http_request_t *r,
+ const char *name, ngx_str_t *value);
+static void ngx_http_v3_push_request_handler(ngx_event_t *ev);
+static ngx_chain_t *ngx_http_v3_create_push_promise(ngx_http_request_t *r,
+ ngx_str_t *path, uint64_t push_id);
+
+
+struct {
+ ngx_str_t name;
+ ngx_uint_t method;
+} ngx_http_v3_methods[] = {
+
+ { ngx_string("GET"), NGX_HTTP_GET },
+ { ngx_string("POST"), NGX_HTTP_POST },
+ { ngx_string("HEAD"), NGX_HTTP_HEAD },
+ { ngx_string("OPTIONS"), NGX_HTTP_OPTIONS },
+ { ngx_string("PROPFIND"), NGX_HTTP_PROPFIND },
+ { ngx_string("PUT"), NGX_HTTP_PUT },
+ { ngx_string("MKCOL"), NGX_HTTP_MKCOL },
+ { ngx_string("DELETE"), NGX_HTTP_DELETE },
+ { ngx_string("COPY"), NGX_HTTP_COPY },
+ { ngx_string("MOVE"), NGX_HTTP_MOVE },
+ { ngx_string("PROPPATCH"), NGX_HTTP_PROPPATCH },
+ { ngx_string("LOCK"), NGX_HTTP_LOCK },
+ { ngx_string("UNLOCK"), NGX_HTTP_UNLOCK },
+ { ngx_string("PATCH"), NGX_HTTP_PATCH },
+ { ngx_string("TRACE"), NGX_HTTP_TRACE }
+};
+
+
+ngx_int_t
+ngx_http_v3_parse_request(ngx_http_request_t *r, ngx_buf_t *b)
+{
+ size_t len;
+ u_char *p;
+ ngx_int_t rc, n;
+ ngx_str_t *name, *value;
+ ngx_connection_t *c;
+ ngx_http_v3_parse_headers_t *st;
+
+ c = r->connection;
+ st = r->h3_parse;
+
+ if (st == NULL) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header");
+
+ st = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_parse_headers_t));
+ if (st == NULL) {
+ goto failed;
+ }
+
+ r->h3_parse = st;
+ r->parse_start = b->pos;
+ r->state = 1;
+ }
+
+ while (b->pos < b->last) {
+ rc = ngx_http_v3_parse_headers(c, st, *b->pos);
+
+ if (rc > 0) {
+ ngx_http_v3_finalize_connection(c, rc,
+ "could not parse request headers");
+ goto failed;
+ }
+
+ if (rc == NGX_ERROR) {
+ goto failed;
+ }
+
+ if (rc == NGX_BUSY) {
+ return NGX_BUSY;
+ }
+
+ b->pos++;
+
+ if (rc == NGX_AGAIN) {
+ continue;
+ }
+
+ name = &st->header_rep.header.name;
+ value = &st->header_rep.header.value;
+
+ n = ngx_http_v3_process_pseudo_header(r, name, value);
+
+ if (n == NGX_ERROR) {
+ goto failed;
+ }
+
+ if (n == NGX_OK && rc == NGX_OK) {
+ continue;
+ }
+
+ ngx_str_set(&r->http_protocol, "HTTP/3.0");
+
+ len = (r->method_end - r->method_start) + 1
+ + (r->uri_end - r->uri_start) + 1
+ + sizeof("HTTP/3") - 1;
+
+ p = ngx_pnalloc(c->pool, len);
+ if (p == NULL) {
+ goto failed;
+ }
+
+ r->request_start = p;
+
+ p = ngx_cpymem(p, r->method_start, r->method_end - r->method_start);
+ *p++ = ' ';
+ p = ngx_cpymem(p, r->uri_start, r->uri_end - r->uri_start);
+ *p++ = ' ';
+ p = ngx_cpymem(p, "HTTP/3", sizeof("HTTP/3") - 1);
+
+ r->request_end = p;
+ r->state = 0;
+
+ return NGX_OK;
+ }
+
+ return NGX_AGAIN;
+
+failed:
+
+ return NGX_HTTP_PARSE_INVALID_REQUEST;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b,
+ ngx_uint_t allow_underscores)
+{
+ u_char ch;
+ ngx_int_t rc;
+ ngx_str_t *name, *value;
+ ngx_uint_t hash, i, n;
+ ngx_connection_t *c;
+ ngx_http_v3_parse_headers_t *st;
+ enum {
+ sw_start = 0,
+ sw_done,
+ sw_next,
+ sw_header
+ };
+
+ c = r->connection;
+ st = r->h3_parse;
+
+ switch (r->state) {
+
+ case sw_start:
+ r->parse_start = b->pos;
+
+ if (st->state) {
+ r->state = sw_next;
+ goto done;
+ }
+
+ name = &st->header_rep.header.name;
+
+ if (name->len && name->data[0] != ':') {
+ r->state = sw_done;
+ goto done;
+ }
+
+ /* fall through */
+
+ case sw_done:
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse header done");
+ return NGX_HTTP_PARSE_HEADER_DONE;
+
+ case sw_next:
+ r->parse_start = b->pos;
+ r->invalid_header = 0;
+ break;
+
+ case sw_header:
+ break;
+ }
+
+ while (b->pos < b->last) {
+ rc = ngx_http_v3_parse_headers(c, st, *b->pos++);
+
+ if (rc > 0) {
+ ngx_http_v3_finalize_connection(c, rc,
+ "could not parse request headers");
+ return NGX_HTTP_PARSE_INVALID_HEADER;
+ }
+
+ if (rc == NGX_ERROR) {
+ return NGX_HTTP_PARSE_INVALID_HEADER;
+ }
+
+ if (rc == NGX_DONE) {
+ r->state = sw_done;
+ goto done;
+ }
+
+ if (rc == NGX_OK) {
+ r->state = sw_next;
+ goto done;
+ }
+ }
+
+ r->state = sw_header;
+ return NGX_AGAIN;
+
+done:
+
+ name = &st->header_rep.header.name;
+ value = &st->header_rep.header.value;
+
+ r->header_name_start = name->data;
+ r->header_name_end = name->data + name->len;
+ r->header_start = value->data;
+ r->header_end = value->data + value->len;
+
+ hash = 0;
+ i = 0;
+
+ for (n = 0; n < name->len; n++) {
+ ch = name->data[n];
+
+ if (ch >= 'A' && ch <= 'Z') {
+ /*
+ * A request or response containing uppercase
+ * header field names MUST be treated as malformed
+ */
+ return NGX_HTTP_PARSE_INVALID_HEADER;
+ }
+
+ if (ch == '\0') {
+ return NGX_HTTP_PARSE_INVALID_HEADER;
+ }
+
+ if (ch == '_' && !allow_underscores) {
+ r->invalid_header = 1;
+ continue;
+ }
+
+ if ((ch < 'a' || ch > 'z')
+ && (ch < '0' || ch > '9')
+ && ch != '-' && ch != '_')
+ {
+ r->invalid_header = 1;
+ continue;
+ }
+
+ hash = ngx_hash(hash, ch);
+ r->lowcase_header[i++] = ch;
+ i &= (NGX_HTTP_LC_HEADER_LEN - 1);
+ }
+
+ r->header_hash = hash;
+ r->lowcase_index = i;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name,
+ ngx_str_t *value)
+{
+ ngx_uint_t i;
+ ngx_connection_t *c;
+
+ if (name->len == 0 || name->data[0] != ':') {
+ return NGX_DONE;
+ }
+
+ c = r->connection;
+
+ if (name->len == 7 && ngx_strncmp(name->data, ":method", 7) == 0) {
+ r->method_start = value->data;
+ r->method_end = value->data + value->len;
+
+ for (i = 0; i < sizeof(ngx_http_v3_methods)
+ / sizeof(ngx_http_v3_methods[0]); i++)
+ {
+ if (value->len == ngx_http_v3_methods[i].name.len
+ && ngx_strncmp(value->data, ngx_http_v3_methods[i].name.data,
+ value->len) == 0)
+ {
+ r->method = ngx_http_v3_methods[i].method;
+ break;
+ }
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 method \"%V\" %ui", value, r->method);
+ return NGX_OK;
+ }
+
+ if (name->len == 5 && ngx_strncmp(name->data, ":path", 5) == 0) {
+ r->uri_start = value->data;
+ r->uri_end = value->data + value->len;
+
+ if (ngx_http_parse_uri(r) != NGX_OK) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client sent invalid :path header: \"%V\"", value);
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 path \"%V\"", value);
+
+ return NGX_OK;
+ }
+
+ if (name->len == 7 && ngx_strncmp(name->data, ":scheme", 7) == 0) {
+ r->schema_start = value->data;
+ r->schema_end = value->data + value->len;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 schema \"%V\"", value);
+
+ return NGX_OK;
+ }
+
+ if (name->len == 10 && ngx_strncmp(name->data, ":authority", 10) == 0) {
+ r->host_start = value->data;
+ r->host_end = value->data + value->len;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 authority \"%V\"", value);
+
+ return NGX_OK;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 unknown pseudo header \"%V\" \"%V\"", name, value);
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_request_body(ngx_http_request_t *r, ngx_buf_t *b,
+ ngx_http_chunked_t *ctx)
+{
+ ngx_int_t rc;
+ ngx_connection_t *c;
+ ngx_http_v3_parse_data_t *st;
+
+ c = r->connection;
+ st = ctx->h3_parse;
+
+ if (st == NULL) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse request body");
+
+ st = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_parse_data_t));
+ if (st == NULL) {
+ goto failed;
+ }
+
+ r->h3_parse = st;
+ }
+
+ if (ctx->size) {
+ ctx->length = ctx->size + 1;
+ return (b->pos == b->last) ? NGX_AGAIN : NGX_OK;
+ }
+
+ while (b->pos < b->last) {
+ rc = ngx_http_v3_parse_data(c, st, *b->pos++);
+
+ if (rc > 0) {
+ ngx_http_v3_finalize_connection(c, rc,
+ "could not parse request body");
+ goto failed;
+ }
+
+ if (rc == NGX_ERROR) {
+ goto failed;
+ }
+
+ if (rc == NGX_AGAIN) {
+ continue;
+ }
+
+ /* rc == NGX_DONE */
+
+ ctx->size = st->length;
+ return NGX_OK;
+ }
+
+ if (!b->last_buf) {
+ ctx->length = 1;
+ return NGX_AGAIN;
+ }
+
+ if (st->state) {
+ goto failed;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header done");
+
+ return NGX_DONE;
+
+failed:
+
+ return NGX_ERROR;
+}
+
+
+ngx_chain_t *
+ngx_http_v3_create_header(ngx_http_request_t *r)
+{
+ u_char *p;
+ size_t len, n;
+ ngx_buf_t *b;
+ ngx_str_t host;
+ ngx_uint_t i, port;
+ ngx_chain_t *out, *hl, *cl, **ll;
+ ngx_list_part_t *part;
+ ngx_table_elt_t *header;
+ ngx_connection_t *c;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_core_srv_conf_t *cscf;
+ u_char addr[NGX_SOCKADDR_STRLEN];
+
+ c = r->connection;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 create header");
+
+ out = NULL;
+ ll = &out;
+
+ if ((c->qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0
+ && r->method != NGX_HTTP_HEAD)
+ {
+ if (ngx_http_v3_push_resources(r, &ll) != NGX_OK) {
+ return NULL;
+ }
+ }
+
+ len = ngx_http_v3_encode_header_block_prefix(NULL, 0, 0, 0);
+
+ if (r->headers_out.status == NGX_HTTP_OK) {
+ len += ngx_http_v3_encode_header_ri(NULL, 0,
+ NGX_HTTP_V3_HEADER_STATUS_200);
+
+ } else {
+ len += ngx_http_v3_encode_header_lri(NULL, 0,
+ NGX_HTTP_V3_HEADER_STATUS_200,
+ NULL, 3);
+ }
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (r->headers_out.server == NULL) {
+ if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) {
+ n = sizeof(NGINX_VER) - 1;
+
+ } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) {
+ n = sizeof(NGINX_VER_BUILD) - 1;
+
+ } else {
+ n = sizeof("nginx") - 1;
+ }
+
+ len += ngx_http_v3_encode_header_lri(NULL, 0,
+ NGX_HTTP_V3_HEADER_SERVER,
+ NULL, n);
+ }
+
+ if (r->headers_out.date == NULL) {
+ len += ngx_http_v3_encode_header_lri(NULL, 0, NGX_HTTP_V3_HEADER_DATE,
+ NULL, ngx_cached_http_time.len);
+ }
+
+ if (r->headers_out.content_type.len) {
+ n = r->headers_out.content_type.len;
+
+ if (r->headers_out.content_type_len == r->headers_out.content_type.len
+ && r->headers_out.charset.len)
+ {
+ n += sizeof("; charset=") - 1 + r->headers_out.charset.len;
+ }
+
+ len += ngx_http_v3_encode_header_lri(NULL, 0,
+ NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN,
+ NULL, n);
+ }
+
+ if (r->headers_out.content_length == NULL) {
+ if (r->headers_out.content_length_n > 0) {
+ len += ngx_http_v3_encode_header_lri(NULL, 0,
+ NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO,
+ NULL, NGX_OFF_T_LEN);
+
+ } else if (r->headers_out.content_length_n == 0) {
+ len += ngx_http_v3_encode_header_ri(NULL, 0,
+ NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO);
+ }
+ }
+
+ if (r->headers_out.last_modified == NULL
+ && r->headers_out.last_modified_time != -1)
+ {
+ len += ngx_http_v3_encode_header_lri(NULL, 0,
+ NGX_HTTP_V3_HEADER_LAST_MODIFIED, NULL,
+ sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1);
+ }
+
+ if (r->headers_out.location
+ && r->headers_out.location->value.len
+ && r->headers_out.location->value.data[0] == '/'
+ && clcf->absolute_redirect)
+ {
+ r->headers_out.location->hash = 0;
+
+ if (clcf->server_name_in_redirect) {
+ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+ host = cscf->server_name;
+
+ } else if (r->headers_in.server.len) {
+ host = r->headers_in.server;
+
+ } else {
+ host.len = NGX_SOCKADDR_STRLEN;
+ host.data = addr;
+
+ if (ngx_connection_local_sockaddr(c, &host, 0) != NGX_OK) {
+ return NULL;
+ }
+ }
+
+ port = ngx_inet_get_port(c->local_sockaddr);
+
+ n = sizeof("https://") - 1 + host.len
+ + r->headers_out.location->value.len;
+
+ if (clcf->port_in_redirect) {
+ port = (port == 443) ? 0 : port;
+
+ } else {
+ port = 0;
+ }
+
+ if (port) {
+ n += sizeof(":65535") - 1;
+ }
+
+ len += ngx_http_v3_encode_header_lri(NULL, 0,
+ NGX_HTTP_V3_HEADER_LOCATION, NULL, n);
+
+ } else {
+ ngx_str_null(&host);
+ port = 0;
+ }
+
+#if (NGX_HTTP_GZIP)
+ if (r->gzip_vary) {
+ if (clcf->gzip_vary) {
+ len += ngx_http_v3_encode_header_ri(NULL, 0,
+ NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING);
+
+ } else {
+ r->gzip_vary = 0;
+ }
+ }
+#endif
+
+ part = &r->headers_out.headers.part;
+ header = part->elts;
+
+ for (i = 0; /* void */; i++) {
+
+ if (i >= part->nelts) {
+ if (part->next == NULL) {
+ break;
+ }
+
+ part = part->next;
+ header = part->elts;
+ i = 0;
+ }
+
+ if (header[i].hash == 0) {
+ continue;
+ }
+
+ len += ngx_http_v3_encode_header_l(NULL, &header[i].key,
+ &header[i].value);
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 header len:%uz", len);
+
+ b = ngx_create_temp_buf(r->pool, len);
+ if (b == NULL) {
+ return NULL;
+ }
+
+ b->last = (u_char *) ngx_http_v3_encode_header_block_prefix(b->last,
+ 0, 0, 0);
+
+ if (r->headers_out.status == NGX_HTTP_OK) {
+ b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0,
+ NGX_HTTP_V3_HEADER_STATUS_200);
+
+ } else {
+ b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0,
+ NGX_HTTP_V3_HEADER_STATUS_200,
+ NULL, 3);
+ b->last = ngx_sprintf(b->last, "%03ui", r->headers_out.status);
+ }
+
+ if (r->headers_out.server == NULL) {
+ if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) {
+ p = (u_char *) NGINX_VER;
+ n = sizeof(NGINX_VER) - 1;
+
+ } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) {
+ p = (u_char *) NGINX_VER_BUILD;
+ n = sizeof(NGINX_VER_BUILD) - 1;
+
+ } else {
+ p = (u_char *) "nginx";
+ n = sizeof("nginx") - 1;
+ }
+
+ b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0,
+ NGX_HTTP_V3_HEADER_SERVER,
+ p, n);
+ }
+
+ if (r->headers_out.date == NULL) {
+ b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0,
+ NGX_HTTP_V3_HEADER_DATE,
+ ngx_cached_http_time.data,
+ ngx_cached_http_time.len);
+ }
+
+ if (r->headers_out.content_type.len) {
+ n = r->headers_out.content_type.len;
+
+ if (r->headers_out.content_type_len == r->headers_out.content_type.len
+ && r->headers_out.charset.len)
+ {
+ n += sizeof("; charset=") - 1 + r->headers_out.charset.len;
+ }
+
+ b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0,
+ NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN,
+ NULL, n);
+
+ p = b->last;
+ b->last = ngx_cpymem(b->last, r->headers_out.content_type.data,
+ r->headers_out.content_type.len);
+
+ if (r->headers_out.content_type_len == r->headers_out.content_type.len
+ && r->headers_out.charset.len)
+ {
+ b->last = ngx_cpymem(b->last, "; charset=",
+ sizeof("; charset=") - 1);
+ b->last = ngx_cpymem(b->last, r->headers_out.charset.data,
+ r->headers_out.charset.len);
+
+ /* update r->headers_out.content_type for possible logging */
+
+ r->headers_out.content_type.len = b->last - p;
+ r->headers_out.content_type.data = p;
+ }
+ }
+
+ if (r->headers_out.content_length == NULL) {
+ if (r->headers_out.content_length_n > 0) {
+ p = ngx_sprintf(b->last, "%O", r->headers_out.content_length_n);
+ n = p - b->last;
+
+ b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0,
+ NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO,
+ NULL, n);
+
+ b->last = ngx_sprintf(b->last, "%O",
+ r->headers_out.content_length_n);
+
+ } else if (r->headers_out.content_length_n == 0) {
+ b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0,
+ NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO);
+ }
+ }
+
+ if (r->headers_out.last_modified == NULL
+ && r->headers_out.last_modified_time != -1)
+ {
+ b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0,
+ NGX_HTTP_V3_HEADER_LAST_MODIFIED, NULL,
+ sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1);
+
+ b->last = ngx_http_time(b->last, r->headers_out.last_modified_time);
+ }
+
+ if (host.data) {
+ n = sizeof("https://") - 1 + host.len
+ + r->headers_out.location->value.len;
+
+ if (port) {
+ n += ngx_sprintf(b->last, ":%ui", port) - b->last;
+ }
+
+ b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0,
+ NGX_HTTP_V3_HEADER_LOCATION,
+ NULL, n);
+
+ p = b->last;
+ b->last = ngx_cpymem(b->last, "https://", sizeof("https://") - 1);
+ b->last = ngx_cpymem(b->last, host.data, host.len);
+
+ if (port) {
+ b->last = ngx_sprintf(b->last, ":%ui", port);
+ }
+
+ b->last = ngx_cpymem(b->last, r->headers_out.location->value.data,
+ r->headers_out.location->value.len);
+
+ /* update r->headers_out.location->value for possible logging */
+
+ r->headers_out.location->value.len = b->last - p;
+ r->headers_out.location->value.data = p;
+ ngx_str_set(&r->headers_out.location->key, "Location");
+ }
+
+#if (NGX_HTTP_GZIP)
+ if (r->gzip_vary) {
+ b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0,
+ NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING);
+ }
+#endif
+
+ part = &r->headers_out.headers.part;
+ header = part->elts;
+
+ for (i = 0; /* void */; i++) {
+
+ if (i >= part->nelts) {
+ if (part->next == NULL) {
+ break;
+ }
+
+ part = part->next;
+ header = part->elts;
+ i = 0;
+ }
+
+ if (header[i].hash == 0) {
+ continue;
+ }
+
+ b->last = (u_char *) ngx_http_v3_encode_header_l(b->last,
+ &header[i].key,
+ &header[i].value);
+ }
+
+ if (r->header_only) {
+ b->last_buf = 1;
+ }
+
+ cl = ngx_alloc_chain_link(c->pool);
+ if (cl == NULL) {
+ return NULL;
+ }
+
+ cl->buf = b;
+ cl->next = NULL;
+
+ n = b->last - b->pos;
+
+ len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_HEADERS)
+ + ngx_http_v3_encode_varlen_int(NULL, n);
+
+ b = ngx_create_temp_buf(c->pool, len);
+ if (b == NULL) {
+ return NULL;
+ }
+
+ b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last,
+ NGX_HTTP_V3_FRAME_HEADERS);
+ b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n);
+
+ hl = ngx_alloc_chain_link(c->pool);
+ if (hl == NULL) {
+ return NULL;
+ }
+
+ hl->buf = b;
+ hl->next = cl;
+
+ *ll = hl;
+ ll = &cl->next;
+
+ if (r->headers_out.content_length_n >= 0 && !r->header_only) {
+ len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_DATA)
+ + ngx_http_v3_encode_varlen_int(NULL,
+ r->headers_out.content_length_n);
+
+ b = ngx_create_temp_buf(c->pool, len);
+ if (b == NULL) {
+ return NULL;
+ }
+
+ b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last,
+ NGX_HTTP_V3_FRAME_DATA);
+ b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last,
+ r->headers_out.content_length_n);
+
+ cl = ngx_alloc_chain_link(c->pool);
+ if (cl == NULL) {
+ return NULL;
+ }
+
+ cl->buf = b;
+ cl->next = NULL;
+
+ *ll = cl;
+ }
+
+ return out;
+}
+
+
+ngx_chain_t *
+ngx_http_v3_create_trailers(ngx_http_request_t *r)
+{
+ ngx_buf_t *b;
+ ngx_chain_t *cl;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http3 create trailers");
+
+ /* XXX */
+
+ b = ngx_calloc_buf(r->pool);
+ if (b == NULL) {
+ return NULL;
+ }
+
+ b->last_buf = 1;
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NULL;
+ }
+
+ cl->buf = b;
+ cl->next = NULL;
+
+ return cl;
+}
+
+
+static ngx_int_t
+ngx_http_v3_push_resources(ngx_http_request_t *r, ngx_chain_t ***out)
+{
+ u_char *start, *end, *last;
+ ngx_str_t path;
+ ngx_int_t rc;
+ ngx_uint_t i, push;
+ ngx_table_elt_t **h;
+ ngx_http_v3_loc_conf_t *h3lcf;
+ ngx_http_complex_value_t *pushes;
+
+ h3lcf = ngx_http_get_module_loc_conf(r, ngx_http_v3_module);
+
+ if (h3lcf->pushes) {
+ pushes = h3lcf->pushes->elts;
+
+ for (i = 0; i < h3lcf->pushes->nelts; i++) {
+
+ if (ngx_http_complex_value(r, &pushes[i], &path) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (path.len == 0) {
+ continue;
+ }
+
+ if (path.len == 3 && ngx_strncmp(path.data, "off", 3) == 0) {
+ continue;
+ }
+
+ rc = ngx_http_v3_push_resource(r, &path, out);
+
+ if (rc == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (rc == NGX_ABORT) {
+ return NGX_OK;
+ }
+
+ /* NGX_OK, NGX_DECLINED */
+ }
+ }
+
+ if (!h3lcf->push_preload) {
+ return NGX_OK;
+ }
+
+ h = r->headers_out.link.elts;
+
+ for (i = 0; i < r->headers_out.link.nelts; i++) {
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http3 parse link: \"%V\"", &h[i]->value);
+
+ start = h[i]->value.data;
+ end = h[i]->value.data + h[i]->value.len;
+
+ next_link:
+
+ while (start < end && *start == ' ') { start++; }
+
+ if (start == end || *start++ != '<') {
+ continue;
+ }
+
+ while (start < end && *start == ' ') { start++; }
+
+ for (last = start; last < end && *last != '>'; last++) {
+ /* void */
+ }
+
+ if (last == start || last == end) {
+ continue;
+ }
+
+ path.len = last - start;
+ path.data = start;
+
+ start = last + 1;
+
+ while (start < end && *start == ' ') { start++; }
+
+ if (start == end) {
+ continue;
+ }
+
+ if (*start == ',') {
+ start++;
+ goto next_link;
+ }
+
+ if (*start++ != ';') {
+ continue;
+ }
+
+ last = ngx_strlchr(start, end, ',');
+
+ if (last == NULL) {
+ last = end;
+ }
+
+ push = 0;
+
+ for ( ;; ) {
+
+ while (start < last && *start == ' ') { start++; }
+
+ if (last - start >= 6
+ && ngx_strncasecmp(start, (u_char *) "nopush", 6) == 0)
+ {
+ start += 6;
+
+ if (start == last || *start == ' ' || *start == ';') {
+ push = 0;
+ break;
+ }
+
+ goto next_param;
+ }
+
+ if (last - start >= 11
+ && ngx_strncasecmp(start, (u_char *) "rel=preload", 11) == 0)
+ {
+ start += 11;
+
+ if (start == last || *start == ' ' || *start == ';') {
+ push = 1;
+ }
+
+ goto next_param;
+ }
+
+ if (last - start >= 4
+ && ngx_strncasecmp(start, (u_char *) "rel=", 4) == 0)
+ {
+ start += 4;
+
+ while (start < last && *start == ' ') { start++; }
+
+ if (start == last || *start++ != '"') {
+ goto next_param;
+ }
+
+ for ( ;; ) {
+
+ while (start < last && *start == ' ') { start++; }
+
+ if (last - start >= 7
+ && ngx_strncasecmp(start, (u_char *) "preload", 7) == 0)
+ {
+ start += 7;
+
+ if (start < last && (*start == ' ' || *start == '"')) {
+ push = 1;
+ break;
+ }
+ }
+
+ while (start < last && *start != ' ' && *start != '"') {
+ start++;
+ }
+
+ if (start == last) {
+ break;
+ }
+
+ if (*start == '"') {
+ break;
+ }
+
+ start++;
+ }
+ }
+
+ next_param:
+
+ start = ngx_strlchr(start, last, ';');
+
+ if (start == NULL) {
+ break;
+ }
+
+ start++;
+ }
+
+ if (push) {
+ while (path.len && path.data[path.len - 1] == ' ') {
+ path.len--;
+ }
+ }
+
+ if (push && path.len
+ && !(path.len > 1 && path.data[0] == '/' && path.data[1] == '/'))
+ {
+ rc = ngx_http_v3_push_resource(r, &path, out);
+
+ if (rc == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (rc == NGX_ABORT) {
+ return NGX_OK;
+ }
+
+ /* NGX_OK, NGX_DECLINED */
+ }
+
+ if (last < end) {
+ start = last + 1;
+ goto next_link;
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v3_push_resource(ngx_http_request_t *r, ngx_str_t *path,
+ ngx_chain_t ***ll)
+{
+ uint64_t push_id;
+ ngx_int_t rc;
+ ngx_chain_t *cl;
+ ngx_connection_t *c;
+ ngx_http_v3_srv_conf_t *h3scf;
+ ngx_http_v3_connection_t *h3c;
+
+ c = r->connection;
+ h3c = c->qs->parent->data;
+ h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module);
+
+ ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 push \"%V\" pushing:%ui/%ui id:%uL/%uL",
+ path, h3c->npushing, h3scf->max_concurrent_pushes,
+ h3c->next_push_id, h3c->max_push_id);
+
+ if (!ngx_path_separator(path->data[0])) {
+ ngx_log_error(NGX_LOG_WARN, c->log, 0,
+ "non-absolute path \"%V\" not pushed", path);
+ return NGX_DECLINED;
+ }
+
+ if (h3c->next_push_id > h3c->max_push_id) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 abort pushes due to max_push_id");
+ return NGX_ABORT;
+ }
+
+ if (h3c->npushing >= h3scf->max_concurrent_pushes) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 abort pushes due to max_concurrent_pushes");
+ return NGX_ABORT;
+ }
+
+ push_id = h3c->next_push_id++;
+
+ rc = ngx_http_v3_create_push_request(r, path, push_id);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ cl = ngx_http_v3_create_push_promise(r, path, push_id);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ for (**ll = cl; **ll; *ll = &(**ll)->next);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v3_create_push_request(ngx_http_request_t *pr, ngx_str_t *path,
+ uint64_t push_id)
+{
+ ngx_pool_t *pool;
+ ngx_connection_t *c, *pc;
+ ngx_http_request_t *r;
+ ngx_http_log_ctx_t *ctx;
+ ngx_http_connection_t *hc;
+ ngx_http_core_srv_conf_t *cscf;
+ ngx_http_v3_connection_t *h3c;
+
+ pc = pr->connection;
+
+ r = NULL;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+ "http3 create push request id:%uL", push_id);
+
+ c = ngx_http_v3_create_push_stream(pc, push_id);
+ if (c == NULL) {
+ return NGX_ABORT;
+ }
+
+ hc = ngx_palloc(c->pool, sizeof(ngx_http_connection_t));
+ if (hc == NULL) {
+ goto failed;
+ }
+
+ h3c = c->qs->parent->data;
+ ngx_memcpy(hc, h3c, sizeof(ngx_http_connection_t));
+ c->data = hc;
+
+ ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t));
+ if (ctx == NULL) {
+ goto failed;
+ }
+
+ ctx->connection = c;
+ ctx->request = NULL;
+ ctx->current_request = NULL;
+
+ c->log->handler = ngx_http_log_error;
+ c->log->data = ctx;
+ c->log->action = "processing pushed request headers";
+
+ c->log_error = NGX_ERROR_INFO;
+
+ r = ngx_http_create_request(c);
+ if (r == NULL) {
+ goto failed;
+ }
+
+ c->data = r;
+
+ ngx_str_set(&r->http_protocol, "HTTP/3.0");
+
+ r->method_name = ngx_http_core_get_method;
+ r->method = NGX_HTTP_GET;
+
+ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+ r->header_in = ngx_create_temp_buf(r->pool,
+ cscf->client_header_buffer_size);
+ if (r->header_in == NULL) {
+ goto failed;
+ }
+
+ if (ngx_list_init(&r->headers_in.headers, r->pool, 4,
+ sizeof(ngx_table_elt_t))
+ != NGX_OK)
+ {
+ goto failed;
+ }
+
+ r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE;
+
+ r->schema.data = ngx_pstrdup(r->pool, &pr->schema);
+ if (r->schema.data == NULL) {
+ goto failed;
+ }
+
+ r->schema.len = pr->schema.len;
+
+ r->uri_start = ngx_pstrdup(r->pool, path);
+ if (r->uri_start == NULL) {
+ goto failed;
+ }
+
+ r->uri_end = r->uri_start + path->len;
+
+ if (ngx_http_parse_uri(r) != NGX_OK) {
+ goto failed;
+ }
+
+ if (ngx_http_process_request_uri(r) != NGX_OK) {
+ goto failed;
+ }
+
+ if (ngx_http_v3_set_push_header(r, "host", &pr->headers_in.server)
+ != NGX_OK)
+ {
+ goto failed;
+ }
+
+ if (pr->headers_in.accept_encoding) {
+ if (ngx_http_v3_set_push_header(r, "accept-encoding",
+ &pr->headers_in.accept_encoding->value)
+ != NGX_OK)
+ {
+ goto failed;
+ }
+ }
+
+ if (pr->headers_in.accept_language) {
+ if (ngx_http_v3_set_push_header(r, "accept-language",
+ &pr->headers_in.accept_language->value)
+ != NGX_OK)
+ {
+ goto failed;
+ }
+ }
+
+ if (pr->headers_in.user_agent) {
+ if (ngx_http_v3_set_push_header(r, "user-agent",
+ &pr->headers_in.user_agent->value)
+ != NGX_OK)
+ {
+ goto failed;
+ }
+ }
+
+ c->read->handler = ngx_http_v3_push_request_handler;
+ c->read->handler = ngx_http_v3_push_request_handler;
+
+ ngx_post_event(c->read, &ngx_posted_events);
+
+ return NGX_OK;
+
+failed:
+
+ if (r) {
+ ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ }
+
+ c->destroyed = 1;
+
+ pool = c->pool;
+
+ ngx_close_connection(c);
+
+ ngx_destroy_pool(pool);
+
+ return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_v3_set_push_header(ngx_http_request_t *r, const char *name,
+ ngx_str_t *value)
+{
+ u_char *p;
+ ngx_table_elt_t *h;
+ ngx_http_header_t *hh;
+ ngx_http_core_main_conf_t *cmcf;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http3 push header \"%s\": \"%V\"", name, value);
+
+ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+ p = ngx_pnalloc(r->pool, value->len + 1);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(p, value->data, value->len);
+ p[value->len] = '\0';
+
+ h = ngx_list_push(&r->headers_in.headers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ h->key.data = (u_char *) name;
+ h->key.len = ngx_strlen(name);
+ h->hash = ngx_hash_key(h->key.data, h->key.len);
+ h->lowcase_key = (u_char *) name;
+ h->value.data = p;
+ h->value.len = value->len;
+
+ hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash,
+ h->lowcase_key, h->key.len);
+
+ if (hh && hh->handler(r, h, hh->offset) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_http_v3_push_request_handler(ngx_event_t *ev)
+{
+ ngx_connection_t *c;
+ ngx_http_request_t *r;
+
+ c = ev->data;
+ r = c->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 push request handler");
+
+ ngx_http_process_request(r);
+}
+
+
+static ngx_chain_t *
+ngx_http_v3_create_push_promise(ngx_http_request_t *r, ngx_str_t *path,
+ uint64_t push_id)
+{
+ size_t n, len;
+ ngx_buf_t *b;
+ ngx_chain_t *hl, *cl;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http3 create push promise id:%uL", push_id);
+
+ len = ngx_http_v3_encode_varlen_int(NULL, push_id);
+
+ len += ngx_http_v3_encode_header_block_prefix(NULL, 0, 0, 0);
+
+ len += ngx_http_v3_encode_header_ri(NULL, 0,
+ NGX_HTTP_V3_HEADER_METHOD_GET);
+
+ len += ngx_http_v3_encode_header_lri(NULL, 0,
+ NGX_HTTP_V3_HEADER_AUTHORITY,
+ NULL, r->headers_in.server.len);
+
+ if (path->len == 1 && path->data[0] == '/') {
+ len += ngx_http_v3_encode_header_ri(NULL, 0,
+ NGX_HTTP_V3_HEADER_PATH_ROOT);
+
+ } else {
+ len += ngx_http_v3_encode_header_lri(NULL, 0,
+ NGX_HTTP_V3_HEADER_PATH_ROOT,
+ NULL, path->len);
+ }
+
+ if (r->schema.len == 5 && ngx_strncmp(r->schema.data, "https", 5) == 0) {
+ len += ngx_http_v3_encode_header_ri(NULL, 0,
+ NGX_HTTP_V3_HEADER_SCHEME_HTTPS);
+
+ } else if (r->schema.len == 4
+ && ngx_strncmp(r->schema.data, "http", 4) == 0)
+ {
+ len += ngx_http_v3_encode_header_ri(NULL, 0,
+ NGX_HTTP_V3_HEADER_SCHEME_HTTP);
+
+ } else {
+ len += ngx_http_v3_encode_header_lri(NULL, 0,
+ NGX_HTTP_V3_HEADER_SCHEME_HTTP,
+ NULL, r->schema.len);
+ }
+
+ if (r->headers_in.accept_encoding) {
+ len += ngx_http_v3_encode_header_lri(NULL, 0,
+ NGX_HTTP_V3_HEADER_ACCEPT_ENCODING, NULL,
+ r->headers_in.accept_encoding->value.len);
+ }
+
+ if (r->headers_in.accept_language) {
+ len += ngx_http_v3_encode_header_lri(NULL, 0,
+ NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE, NULL,
+ r->headers_in.accept_language->value.len);
+ }
+
+ if (r->headers_in.user_agent) {
+ len += ngx_http_v3_encode_header_lri(NULL, 0,
+ NGX_HTTP_V3_HEADER_USER_AGENT, NULL,
+ r->headers_in.user_agent->value.len);
+ }
+
+ b = ngx_create_temp_buf(r->pool, len);
+ if (b == NULL) {
+ return NULL;
+ }
+
+ b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, push_id);
+
+ b->last = (u_char *) ngx_http_v3_encode_header_block_prefix(b->last,
+ 0, 0, 0);
+
+ b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0,
+ NGX_HTTP_V3_HEADER_METHOD_GET);
+
+ b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0,
+ NGX_HTTP_V3_HEADER_AUTHORITY,
+ r->headers_in.server.data,
+ r->headers_in.server.len);
+
+ if (path->len == 1 && path->data[0] == '/') {
+ b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0,
+ NGX_HTTP_V3_HEADER_PATH_ROOT);
+
+ } else {
+ b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0,
+ NGX_HTTP_V3_HEADER_PATH_ROOT,
+ path->data, path->len);
+ }
+
+ if (r->schema.len == 5 && ngx_strncmp(r->schema.data, "https", 5) == 0) {
+ b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0,
+ NGX_HTTP_V3_HEADER_SCHEME_HTTPS);
+
+ } else if (r->schema.len == 4
+ && ngx_strncmp(r->schema.data, "http", 4) == 0)
+ {
+ b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0,
+ NGX_HTTP_V3_HEADER_SCHEME_HTTP);
+
+ } else {
+ b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0,
+ NGX_HTTP_V3_HEADER_SCHEME_HTTP,
+ r->schema.data, r->schema.len);
+ }
+
+ if (r->headers_in.accept_encoding) {
+ b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0,
+ NGX_HTTP_V3_HEADER_ACCEPT_ENCODING,
+ r->headers_in.accept_encoding->value.data,
+ r->headers_in.accept_encoding->value.len);
+ }
+
+ if (r->headers_in.accept_language) {
+ b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0,
+ NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE,
+ r->headers_in.accept_language->value.data,
+ r->headers_in.accept_language->value.len);
+ }
+
+ if (r->headers_in.user_agent) {
+ b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0,
+ NGX_HTTP_V3_HEADER_USER_AGENT,
+ r->headers_in.user_agent->value.data,
+ r->headers_in.user_agent->value.len);
+ }
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NULL;
+ }
+
+ cl->buf = b;
+ cl->next = NULL;
+
+ n = b->last - b->pos;
+
+ len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_PUSH_PROMISE)
+ + ngx_http_v3_encode_varlen_int(NULL, n);
+
+ b = ngx_create_temp_buf(r->pool, len);
+ if (b == NULL) {
+ return NULL;
+ }
+
+ b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last,
+ NGX_HTTP_V3_FRAME_PUSH_PROMISE);
+ b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n);
+
+ hl = ngx_alloc_chain_link(r->pool);
+ if (hl == NULL) {
+ return NULL;
+ }
+
+ hl->buf = b;
+ hl->next = cl;
+
+ return hl;
+}
diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c
new file mode 100644
index 000000000..8d5147f4d
--- /dev/null
+++ b/src/http/v3/ngx_http_v3_streams.c
@@ -0,0 +1,819 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef ngx_int_t (*ngx_http_v3_handler_pt)(ngx_connection_t *c, void *data,
+ u_char ch);
+
+
+typedef struct {
+ ngx_http_v3_handler_pt handler;
+ void *data;
+ ngx_int_t index;
+} ngx_http_v3_uni_stream_t;
+
+
+typedef struct {
+ ngx_queue_t queue;
+ uint64_t id;
+ ngx_connection_t *connection;
+ ngx_uint_t *npushing;
+} ngx_http_v3_push_t;
+
+
+static void ngx_http_v3_close_uni_stream(ngx_connection_t *c);
+static void ngx_http_v3_read_uni_stream_type(ngx_event_t *rev);
+static void ngx_http_v3_uni_read_handler(ngx_event_t *rev);
+static void ngx_http_v3_dummy_write_handler(ngx_event_t *wev);
+static void ngx_http_v3_push_cleanup(void *data);
+static ngx_connection_t *ngx_http_v3_get_uni_stream(ngx_connection_t *c,
+ ngx_uint_t type);
+static ngx_int_t ngx_http_v3_send_settings(ngx_connection_t *c);
+
+
+ngx_int_t
+ngx_http_v3_init_connection(ngx_connection_t *c)
+{
+ ngx_http_connection_t *hc;
+ ngx_http_v3_uni_stream_t *us;
+ ngx_http_v3_connection_t *h3c;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init connection");
+
+ hc = c->data;
+
+ if (c->qs == NULL) {
+ h3c = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_connection_t));
+ if (h3c == NULL) {
+ return NGX_ERROR;
+ }
+
+ h3c->hc = *hc;
+
+ ngx_queue_init(&h3c->blocked);
+ ngx_queue_init(&h3c->pushing);
+
+ c->data = h3c;
+ return NGX_OK;
+ }
+
+ if (ngx_http_v3_send_settings(c) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if ((c->qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) {
+ return NGX_OK;
+ }
+
+ us = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_uni_stream_t));
+ if (us == NULL) {
+ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR,
+ NULL);
+ return NGX_ERROR;
+ }
+
+ us->index = -1;
+
+ c->data = us;
+
+ c->read->handler = ngx_http_v3_read_uni_stream_type;
+ c->write->handler = ngx_http_v3_dummy_write_handler;
+
+ ngx_http_v3_read_uni_stream_type(c->read);
+
+ return NGX_DONE;
+}
+
+
+static void
+ngx_http_v3_close_uni_stream(ngx_connection_t *c)
+{
+ ngx_pool_t *pool;
+ ngx_http_v3_connection_t *h3c;
+ ngx_http_v3_uni_stream_t *us;
+
+ us = c->data;
+ h3c = c->qs->parent->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 close stream");
+
+ if (us->index >= 0) {
+ h3c->known_streams[us->index] = NULL;
+ }
+
+ c->destroyed = 1;
+
+ pool = c->pool;
+
+ ngx_close_connection(c);
+
+ ngx_destroy_pool(pool);
+}
+
+
+static void
+ngx_http_v3_read_uni_stream_type(ngx_event_t *rev)
+{
+ u_char ch;
+ ssize_t n;
+ ngx_int_t index, rc;
+ ngx_connection_t *c;
+ ngx_http_v3_connection_t *h3c;
+ ngx_http_v3_uni_stream_t *us;
+
+ c = rev->data;
+ us = c->data;
+ h3c = c->qs->parent->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read stream type");
+
+ while (rev->ready) {
+
+ n = c->recv(c, &ch, 1);
+
+ if (n == NGX_AGAIN) {
+ break;
+ }
+
+ if (n == 0) {
+ rc = NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR;
+ goto failed;
+ }
+
+ if (n != 1) {
+ rc = NGX_HTTP_V3_ERR_INTERNAL_ERROR;
+ goto failed;
+ }
+
+ switch (ch) {
+
+ case NGX_HTTP_V3_STREAM_ENCODER:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 encoder stream");
+
+ index = NGX_HTTP_V3_STREAM_CLIENT_ENCODER;
+ us->handler = ngx_http_v3_parse_encoder;
+ n = sizeof(ngx_http_v3_parse_encoder_t);
+
+ break;
+
+ case NGX_HTTP_V3_STREAM_DECODER:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 decoder stream");
+
+ index = NGX_HTTP_V3_STREAM_CLIENT_DECODER;
+ us->handler = ngx_http_v3_parse_decoder;
+ n = sizeof(ngx_http_v3_parse_decoder_t);
+
+ break;
+
+ case NGX_HTTP_V3_STREAM_CONTROL:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 control stream");
+
+ index = NGX_HTTP_V3_STREAM_CLIENT_CONTROL;
+ us->handler = ngx_http_v3_parse_control;
+ n = sizeof(ngx_http_v3_parse_control_t);
+
+ break;
+
+ default:
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 stream 0x%02xi", (ngx_int_t) ch);
+ index = -1;
+ n = 0;
+ }
+
+ if (index >= 0) {
+ if (h3c->known_streams[index]) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "stream exists");
+ rc = NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR;
+ goto failed;
+ }
+
+ us->index = index;
+ h3c->known_streams[index] = c;
+ }
+
+ if (n) {
+ us->data = ngx_pcalloc(c->pool, n);
+ if (us->data == NULL) {
+ rc = NGX_HTTP_V3_ERR_INTERNAL_ERROR;
+ goto failed;
+ }
+ }
+
+ rev->handler = ngx_http_v3_uni_read_handler;
+ ngx_http_v3_uni_read_handler(rev);
+ return;
+ }
+
+ if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+ rc = NGX_HTTP_V3_ERR_INTERNAL_ERROR;
+ goto failed;
+ }
+
+ return;
+
+failed:
+
+ ngx_http_v3_finalize_connection(c, rc, "could not read stream type");
+ ngx_http_v3_close_uni_stream(c);
+}
+
+
+static void
+ngx_http_v3_uni_read_handler(ngx_event_t *rev)
+{
+ u_char buf[128];
+ ssize_t n;
+ ngx_int_t rc, i;
+ ngx_connection_t *c;
+ ngx_http_v3_uni_stream_t *us;
+
+ c = rev->data;
+ us = c->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read handler");
+
+ while (rev->ready) {
+
+ n = c->recv(c, buf, sizeof(buf));
+
+ if (n == NGX_ERROR) {
+ rc = NGX_HTTP_V3_ERR_INTERNAL_ERROR;
+ goto failed;
+ }
+
+ if (n == 0) {
+ if (us->index >= 0) {
+ rc = NGX_HTTP_V3_ERR_CLOSED_CRITICAL_STREAM;
+ goto failed;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read eof");
+ ngx_http_v3_close_uni_stream(c);
+ return;
+ }
+
+ if (n == NGX_AGAIN) {
+ break;
+ }
+
+ if (us->handler == NULL) {
+ continue;
+ }
+
+ for (i = 0; i < n; i++) {
+
+ rc = us->handler(c, us->data, buf[i]);
+
+ if (rc == NGX_DONE) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 read done");
+ ngx_http_v3_close_uni_stream(c);
+ return;
+ }
+
+ if (rc > 0) {
+ goto failed;
+ }
+
+ if (rc != NGX_AGAIN) {
+ rc = NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR;
+ goto failed;
+ }
+ }
+ }
+
+ if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+ rc = NGX_HTTP_V3_ERR_INTERNAL_ERROR;
+ goto failed;
+ }
+
+ return;
+
+failed:
+
+ ngx_http_v3_finalize_connection(c, rc, "stream error");
+ ngx_http_v3_close_uni_stream(c);
+}
+
+
+static void
+ngx_http_v3_dummy_write_handler(ngx_event_t *wev)
+{
+ ngx_connection_t *c;
+
+ c = wev->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 dummy write handler");
+
+ if (ngx_handle_write_event(wev, 0) != NGX_OK) {
+ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR,
+ NULL);
+ ngx_http_v3_close_uni_stream(c);
+ }
+}
+
+
+/* XXX async & buffered stream writes */
+
+ngx_connection_t *
+ngx_http_v3_create_push_stream(ngx_connection_t *c, uint64_t push_id)
+{
+ u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 2];
+ size_t n;
+ ngx_connection_t *sc;
+ ngx_pool_cleanup_t *cln;
+ ngx_http_v3_push_t *push;
+ ngx_http_v3_connection_t *h3c;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 create push stream id:%uL", push_id);
+
+ sc = ngx_quic_open_stream(c, 0);
+ if (sc == NULL) {
+ return NULL;
+ }
+
+ p = buf;
+ p = (u_char *) ngx_http_v3_encode_varlen_int(p, NGX_HTTP_V3_STREAM_PUSH);
+ p = (u_char *) ngx_http_v3_encode_varlen_int(p, push_id);
+ n = p - buf;
+
+ if (sc->send(sc, buf, n) != (ssize_t) n) {
+ goto failed;
+ }
+
+ cln = ngx_pool_cleanup_add(sc->pool, sizeof(ngx_http_v3_push_t));
+ if (cln == NULL) {
+ goto failed;
+ }
+
+ h3c = c->qs->parent->data;
+ h3c->npushing++;
+
+ cln->handler = ngx_http_v3_push_cleanup;
+
+ push = cln->data;
+ push->id = push_id;
+ push->connection = sc;
+ push->npushing = &h3c->npushing;
+
+ ngx_queue_insert_tail(&h3c->pushing, &push->queue);
+
+ return sc;
+
+failed:
+
+ ngx_http_v3_close_uni_stream(sc);
+
+ return NULL;
+}
+
+
+static void
+ngx_http_v3_push_cleanup(void *data)
+{
+ ngx_http_v3_push_t *push = data;
+
+ ngx_queue_remove(&push->queue);
+ (*push->npushing)--;
+}
+
+
+static ngx_connection_t *
+ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type)
+{
+ u_char buf[NGX_HTTP_V3_VARLEN_INT_LEN];
+ size_t n;
+ ngx_int_t index;
+ ngx_connection_t *sc;
+ ngx_http_v3_connection_t *h3c;
+ ngx_http_v3_uni_stream_t *us;
+
+ switch (type) {
+ case NGX_HTTP_V3_STREAM_ENCODER:
+ index = NGX_HTTP_V3_STREAM_SERVER_ENCODER;
+ break;
+ case NGX_HTTP_V3_STREAM_DECODER:
+ index = NGX_HTTP_V3_STREAM_SERVER_DECODER;
+ break;
+ case NGX_HTTP_V3_STREAM_CONTROL:
+ index = NGX_HTTP_V3_STREAM_SERVER_CONTROL;
+ break;
+ default:
+ index = -1;
+ }
+
+ h3c = c->qs->parent->data;
+
+ if (index >= 0) {
+ if (h3c->known_streams[index]) {
+ return h3c->known_streams[index];
+ }
+ }
+
+ sc = ngx_quic_open_stream(c, 0);
+ if (sc == NULL) {
+ return NULL;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 create uni stream, type:%ui", type);
+
+ us = ngx_pcalloc(sc->pool, sizeof(ngx_http_v3_uni_stream_t));
+ if (us == NULL) {
+ goto failed;
+ }
+
+ us->index = index;
+
+ sc->data = us;
+
+ sc->read->handler = ngx_http_v3_uni_read_handler;
+ sc->write->handler = ngx_http_v3_dummy_write_handler;
+
+ if (index >= 0) {
+ h3c->known_streams[index] = sc;
+ }
+
+ n = (u_char *) ngx_http_v3_encode_varlen_int(buf, type) - buf;
+
+ if (sc->send(sc, buf, n) != (ssize_t) n) {
+ goto failed;
+ }
+
+ return sc;
+
+failed:
+
+ ngx_http_v3_close_uni_stream(sc);
+
+ return NULL;
+}
+
+
+static ngx_int_t
+ngx_http_v3_send_settings(ngx_connection_t *c)
+{
+ u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 6];
+ size_t n;
+ ngx_connection_t *cc;
+ ngx_http_v3_srv_conf_t *h3scf;
+ ngx_http_v3_connection_t *h3c;
+
+ h3c = c->qs->parent->data;
+
+ if (h3c->settings_sent) {
+ return NGX_OK;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send settings");
+
+ cc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL);
+ if (cc == NULL) {
+ return NGX_DECLINED;
+ }
+
+ h3scf = ngx_http_get_module_srv_conf(h3c->hc.conf_ctx, ngx_http_v3_module);
+
+ n = ngx_http_v3_encode_varlen_int(NULL,
+ NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY);
+ n += ngx_http_v3_encode_varlen_int(NULL, h3scf->max_table_capacity);
+ n += ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_PARAM_BLOCKED_STREAMS);
+ n += ngx_http_v3_encode_varlen_int(NULL, h3scf->max_blocked_streams);
+
+ p = (u_char *) ngx_http_v3_encode_varlen_int(buf,
+ NGX_HTTP_V3_FRAME_SETTINGS);
+ p = (u_char *) ngx_http_v3_encode_varlen_int(p, n);
+ p = (u_char *) ngx_http_v3_encode_varlen_int(p,
+ NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY);
+ p = (u_char *) ngx_http_v3_encode_varlen_int(p, h3scf->max_table_capacity);
+ p = (u_char *) ngx_http_v3_encode_varlen_int(p,
+ NGX_HTTP_V3_PARAM_BLOCKED_STREAMS);
+ p = (u_char *) ngx_http_v3_encode_varlen_int(p, h3scf->max_blocked_streams);
+ n = p - buf;
+
+ if (cc->send(cc, buf, n) != (ssize_t) n) {
+ goto failed;
+ }
+
+ h3c->settings_sent = 1;
+
+ return NGX_OK;
+
+failed:
+
+ ngx_http_v3_close_uni_stream(cc);
+
+ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR,
+ "could not send settings");
+
+ return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_http_v3_client_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic,
+ ngx_uint_t index, ngx_str_t *value)
+{
+ u_char *p, buf[NGX_HTTP_V3_PREFIX_INT_LEN * 2];
+ size_t n;
+ ngx_connection_t *ec;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 client ref insert, %s[%ui] \"%V\"",
+ dynamic ? "dynamic" : "static", index, value);
+
+ ec = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_ENCODER);
+ if (ec == NULL) {
+ return NGX_ERROR;
+ }
+
+ p = buf;
+
+ *p = (dynamic ? 0x80 : 0xc0);
+ p = (u_char *) ngx_http_v3_encode_prefix_int(p, index, 6);
+
+ /* XXX option for huffman? */
+ *p = 0;
+ p = (u_char *) ngx_http_v3_encode_prefix_int(p, value->len, 7);
+
+ n = p - buf;
+
+ if (ec->send(ec, buf, n) != (ssize_t) n) {
+ goto failed;
+ }
+
+ if (ec->send(ec, value->data, value->len) != (ssize_t) value->len) {
+ goto failed;
+ }
+
+ return NGX_OK;
+
+failed:
+
+ ngx_http_v3_close_uni_stream(ec);
+
+ return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_http_v3_client_insert(ngx_connection_t *c, ngx_str_t *name,
+ ngx_str_t *value)
+{
+ u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN];
+ size_t n;
+ ngx_connection_t *ec;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 client insert \"%V\":\"%V\"", name, value);
+
+ ec = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_ENCODER);
+ if (ec == NULL) {
+ return NGX_ERROR;
+ }
+
+ /* XXX option for huffman? */
+ buf[0] = 0x40;
+ n = (u_char *) ngx_http_v3_encode_prefix_int(buf, name->len, 5) - buf;
+
+ if (ec->send(ec, buf, n) != (ssize_t) n) {
+ goto failed;
+ }
+
+ if (ec->send(ec, name->data, name->len) != (ssize_t) name->len) {
+ goto failed;
+ }
+
+ /* XXX option for huffman? */
+ buf[0] = 0;
+ n = (u_char *) ngx_http_v3_encode_prefix_int(buf, value->len, 7) - buf;
+
+ if (ec->send(ec, buf, n) != (ssize_t) n) {
+ goto failed;
+ }
+
+ if (ec->send(ec, value->data, value->len) != (ssize_t) value->len) {
+ goto failed;
+ }
+
+ return NGX_OK;
+
+failed:
+
+ ngx_http_v3_close_uni_stream(ec);
+
+ return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_http_v3_client_set_capacity(ngx_connection_t *c, ngx_uint_t capacity)
+{
+ u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN];
+ size_t n;
+ ngx_connection_t *ec;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 client set capacity %ui", capacity);
+
+ ec = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_ENCODER);
+ if (ec == NULL) {
+ return NGX_ERROR;
+ }
+
+ buf[0] = 0x20;
+ n = (u_char *) ngx_http_v3_encode_prefix_int(buf, capacity, 5) - buf;
+
+ if (ec->send(ec, buf, n) != (ssize_t) n) {
+ ngx_http_v3_close_uni_stream(ec);
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_client_duplicate(ngx_connection_t *c, ngx_uint_t index)
+{
+ u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN];
+ size_t n;
+ ngx_connection_t *ec;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 client duplicate %ui", index);
+
+ ec = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_ENCODER);
+ if (ec == NULL) {
+ return NGX_ERROR;
+ }
+
+ buf[0] = 0;
+ n = (u_char *) ngx_http_v3_encode_prefix_int(buf, index, 5) - buf;
+
+ if (ec->send(ec, buf, n) != (ssize_t) n) {
+ ngx_http_v3_close_uni_stream(ec);
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_client_ack_header(ngx_connection_t *c, ngx_uint_t stream_id)
+{
+ u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN];
+ size_t n;
+ ngx_connection_t *dc;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 client ack header %ui", stream_id);
+
+ dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER);
+ if (dc == NULL) {
+ return NGX_ERROR;
+ }
+
+ buf[0] = 0x80;
+ n = (u_char *) ngx_http_v3_encode_prefix_int(buf, stream_id, 7) - buf;
+
+ if (dc->send(dc, buf, n) != (ssize_t) n) {
+ ngx_http_v3_close_uni_stream(dc);
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_client_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id)
+{
+ u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN];
+ size_t n;
+ ngx_connection_t *dc;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 client cancel stream %ui", stream_id);
+
+ dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER);
+ if (dc == NULL) {
+ return NGX_ERROR;
+ }
+
+ buf[0] = 0x40;
+ n = (u_char *) ngx_http_v3_encode_prefix_int(buf, stream_id, 6) - buf;
+
+ if (dc->send(dc, buf, n) != (ssize_t) n) {
+ ngx_http_v3_close_uni_stream(dc);
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_client_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc)
+{
+ u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN];
+ size_t n;
+ ngx_connection_t *dc;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 client increment insert count %ui", inc);
+
+ dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER);
+ if (dc == NULL) {
+ return NGX_ERROR;
+ }
+
+ buf[0] = 0;
+ n = (u_char *) ngx_http_v3_encode_prefix_int(buf, inc, 6) - buf;
+
+ if (dc->send(dc, buf, n) != (ssize_t) n) {
+ ngx_http_v3_close_uni_stream(dc);
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_set_max_push_id(ngx_connection_t *c, uint64_t max_push_id)
+{
+ ngx_http_v3_connection_t *h3c;
+
+ h3c = c->qs->parent->data;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 MAX_PUSH_ID:%uL", max_push_id);
+
+ if (max_push_id < h3c->max_push_id) {
+ return NGX_HTTP_V3_ERR_ID_ERROR;
+ }
+
+ h3c->max_push_id = max_push_id;
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id)
+{
+ ngx_queue_t *q;
+ ngx_http_request_t *r;
+ ngx_http_v3_push_t *push;
+ ngx_http_v3_connection_t *h3c;
+
+ h3c = c->qs->parent->data;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 CANCEL_PUSH:%uL", push_id);
+
+ if (push_id >= h3c->next_push_id) {
+ return NGX_HTTP_V3_ERR_ID_ERROR;
+ }
+
+ for (q = ngx_queue_head(&h3c->pushing);
+ q != ngx_queue_sentinel(&h3c->pushing);
+ q = ngx_queue_next(&h3c->pushing))
+ {
+ push = (ngx_http_v3_push_t *) q;
+
+ if (push->id != push_id) {
+ continue;
+ }
+
+ r = push->connection->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http3 cancel push");
+
+ ngx_http_finalize_request(r, NGX_HTTP_CLOSE);
+
+ break;
+ }
+
+ return NGX_OK;
+}
diff --git a/src/http/v3/ngx_http_v3_tables.c b/src/http/v3/ngx_http_v3_tables.c
new file mode 100644
index 000000000..bf4f1449c
--- /dev/null
+++ b/src/http/v3/ngx_http_v3_tables.c
@@ -0,0 +1,686 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#define ngx_http_v3_table_entry_size(n, v) ((n)->len + (v)->len + 32)
+
+
+static ngx_int_t ngx_http_v3_evict(ngx_connection_t *c, size_t need);
+static void ngx_http_v3_cleanup_table(void *data);
+static void ngx_http_v3_unblock(void *data);
+static ngx_int_t ngx_http_v3_new_header(ngx_connection_t *c);
+
+
+typedef struct {
+ ngx_queue_t queue;
+ ngx_connection_t *connection;
+ ngx_uint_t *nblocked;
+} ngx_http_v3_block_t;
+
+
+static ngx_http_v3_header_t ngx_http_v3_static_table[] = {
+
+ { ngx_string(":authority"), ngx_string("") },
+ { ngx_string(":path"), ngx_string("/") },
+ { ngx_string("age"), ngx_string("0") },
+ { ngx_string("content-disposition"), ngx_string("") },
+ { ngx_string("content-length"), ngx_string("0") },
+ { ngx_string("cookie"), ngx_string("") },
+ { ngx_string("date"), ngx_string("") },
+ { ngx_string("etag"), ngx_string("") },
+ { ngx_string("if-modified-since"), ngx_string("") },
+ { ngx_string("if-none-match"), ngx_string("") },
+ { ngx_string("last-modified"), ngx_string("") },
+ { ngx_string("link"), ngx_string("") },
+ { ngx_string("location"), ngx_string("") },
+ { ngx_string("referer"), ngx_string("") },
+ { ngx_string("set-cookie"), ngx_string("") },
+ { ngx_string(":method"), ngx_string("CONNECT") },
+ { ngx_string(":method"), ngx_string("DELETE") },
+ { ngx_string(":method"), ngx_string("GET") },
+ { ngx_string(":method"), ngx_string("HEAD") },
+ { ngx_string(":method"), ngx_string("OPTIONS") },
+ { ngx_string(":method"), ngx_string("POST") },
+ { ngx_string(":method"), ngx_string("PUT") },
+ { ngx_string(":scheme"), ngx_string("http") },
+ { ngx_string(":scheme"), ngx_string("https") },
+ { ngx_string(":status"), ngx_string("103") },
+ { ngx_string(":status"), ngx_string("200") },
+ { ngx_string(":status"), ngx_string("304") },
+ { ngx_string(":status"), ngx_string("404") },
+ { ngx_string(":status"), ngx_string("503") },
+ { ngx_string("accept"), ngx_string("*/*") },
+ { ngx_string("accept"),
+ ngx_string("application/dns-message") },
+ { ngx_string("accept-encoding"), ngx_string("gzip, deflate, br") },
+ { ngx_string("accept-ranges"), ngx_string("bytes") },
+ { ngx_string("access-control-allow-headers"),
+ ngx_string("cache-control") },
+ { ngx_string("access-control-allow-headers"),
+ ngx_string("content-type") },
+ { ngx_string("access-control-allow-origin"),
+ ngx_string("*") },
+ { ngx_string("cache-control"), ngx_string("max-age=0") },
+ { ngx_string("cache-control"), ngx_string("max-age=2592000") },
+ { ngx_string("cache-control"), ngx_string("max-age=604800") },
+ { ngx_string("cache-control"), ngx_string("no-cache") },
+ { ngx_string("cache-control"), ngx_string("no-store") },
+ { ngx_string("cache-control"),
+ ngx_string("public, max-age=31536000") },
+ { ngx_string("content-encoding"), ngx_string("br") },
+ { ngx_string("content-encoding"), ngx_string("gzip") },
+ { ngx_string("content-type"),
+ ngx_string("application/dns-message") },
+ { ngx_string("content-type"),
+ ngx_string("application/javascript") },
+ { ngx_string("content-type"), ngx_string("application/json") },
+ { ngx_string("content-type"),
+ ngx_string("application/x-www-form-urlencoded") },
+ { ngx_string("content-type"), ngx_string("image/gif") },
+ { ngx_string("content-type"), ngx_string("image/jpeg") },
+ { ngx_string("content-type"), ngx_string("image/png") },
+ { ngx_string("content-type"), ngx_string("text/css") },
+ { ngx_string("content-type"),
+ ngx_string("text/html;charset=utf-8") },
+ { ngx_string("content-type"), ngx_string("text/plain") },
+ { ngx_string("content-type"),
+ ngx_string("text/plain;charset=utf-8") },
+ { ngx_string("range"), ngx_string("bytes=0-") },
+ { ngx_string("strict-transport-security"),
+ ngx_string("max-age=31536000") },
+ { ngx_string("strict-transport-security"),
+ ngx_string("max-age=31536000;includesubdomains") },
+ { ngx_string("strict-transport-security"),
+ ngx_string("max-age=31536000;includesubdomains;preload") },
+ { ngx_string("vary"), ngx_string("accept-encoding") },
+ { ngx_string("vary"), ngx_string("origin") },
+ { ngx_string("x-content-type-options"),
+ ngx_string("nosniff") },
+ { ngx_string("x-xss-protection"), ngx_string("1;mode=block") },
+ { ngx_string(":status"), ngx_string("100") },
+ { ngx_string(":status"), ngx_string("204") },
+ { ngx_string(":status"), ngx_string("206") },
+ { ngx_string(":status"), ngx_string("302") },
+ { ngx_string(":status"), ngx_string("400") },
+ { ngx_string(":status"), ngx_string("403") },
+ { ngx_string(":status"), ngx_string("421") },
+ { ngx_string(":status"), ngx_string("425") },
+ { ngx_string(":status"), ngx_string("500") },
+ { ngx_string("accept-language"), ngx_string("") },
+ { ngx_string("access-control-allow-credentials"),
+ ngx_string("FALSE") },
+ { ngx_string("access-control-allow-credentials"),
+ ngx_string("TRUE") },
+ { ngx_string("access-control-allow-headers"),
+ ngx_string("*") },
+ { ngx_string("access-control-allow-methods"),
+ ngx_string("get") },
+ { ngx_string("access-control-allow-methods"),
+ ngx_string("get, post, options") },
+ { ngx_string("access-control-allow-methods"),
+ ngx_string("options") },
+ { ngx_string("access-control-expose-headers"),
+ ngx_string("content-length") },
+ { ngx_string("access-control-request-headers"),
+ ngx_string("content-type") },
+ { ngx_string("access-control-request-method"),
+ ngx_string("get") },
+ { ngx_string("access-control-request-method"),
+ ngx_string("post") },
+ { ngx_string("alt-svc"), ngx_string("clear") },
+ { ngx_string("authorization"), ngx_string("") },
+ { ngx_string("content-security-policy"),
+ ngx_string("script-src 'none';object-src 'none';base-uri 'none'") },
+ { ngx_string("early-data"), ngx_string("1") },
+ { ngx_string("expect-ct"), ngx_string("") },
+ { ngx_string("forwarded"), ngx_string("") },
+ { ngx_string("if-range"), ngx_string("") },
+ { ngx_string("origin"), ngx_string("") },
+ { ngx_string("purpose"), ngx_string("prefetch") },
+ { ngx_string("server"), ngx_string("") },
+ { ngx_string("timing-allow-origin"), ngx_string("*") },
+ { ngx_string("upgrade-insecure-requests"),
+ ngx_string("1") },
+ { ngx_string("user-agent"), ngx_string("") },
+ { ngx_string("x-forwarded-for"), ngx_string("") },
+ { ngx_string("x-frame-options"), ngx_string("deny") },
+ { ngx_string("x-frame-options"), ngx_string("sameorigin") }
+};
+
+
+ngx_int_t
+ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic,
+ ngx_uint_t index, ngx_str_t *value)
+{
+ ngx_str_t name;
+
+ if (dynamic) {
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 ref insert dynamic[%ui] \"%V\"", index, value);
+
+ if (ngx_http_v3_lookup(c, index, &name, NULL) != NGX_OK) {
+ return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
+ }
+
+ } else {
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 ref insert static[%ui] \"%V\"", index, value);
+
+ if (ngx_http_v3_lookup_static(c, index, &name, NULL) != NGX_OK) {
+ return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
+ }
+ }
+
+ return ngx_http_v3_insert(c, &name, value);
+}
+
+
+ngx_int_t
+ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, ngx_str_t *value)
+{
+ u_char *p;
+ size_t size;
+ ngx_http_v3_header_t *h;
+ ngx_http_v3_connection_t *h3c;
+ ngx_http_v3_dynamic_table_t *dt;
+
+ size = ngx_http_v3_table_entry_size(name, value);
+
+ if (ngx_http_v3_evict(c, size) != NGX_OK) {
+ return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
+ }
+
+ h3c = c->qs->parent->data;
+ dt = &h3c->table;
+
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 insert [%ui] \"%V\":\"%V\", size:%uz",
+ dt->base + dt->nelts, name, value, size);
+
+ p = ngx_alloc(sizeof(ngx_http_v3_header_t) + name->len + value->len,
+ c->log);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ h = (ngx_http_v3_header_t *) p;
+
+ h->name.data = p + sizeof(ngx_http_v3_header_t);
+ h->name.len = name->len;
+ h->value.data = ngx_cpymem(h->name.data, name->data, name->len);
+ h->value.len = value->len;
+ ngx_memcpy(h->value.data, value->data, value->len);
+
+ dt->elts[dt->nelts++] = h;
+ dt->size += size;
+
+ /* TODO increment can be sent less often */
+
+ if (ngx_http_v3_client_inc_insert_count(c, 1) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_http_v3_new_header(c) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity)
+{
+ ngx_uint_t max, prev_max;
+ ngx_connection_t *pc;
+ ngx_pool_cleanup_t *cln;
+ ngx_http_v3_header_t **elts;
+ ngx_http_v3_srv_conf_t *h3scf;
+ ngx_http_v3_connection_t *h3c;
+ ngx_http_v3_dynamic_table_t *dt;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 set capacity %ui", capacity);
+
+ pc = c->qs->parent;
+ h3c = pc->data;
+ h3scf = ngx_http_get_module_srv_conf(h3c->hc.conf_ctx, ngx_http_v3_module);
+
+ if (capacity > h3scf->max_table_capacity) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client exceeded http3_max_table_capacity limit");
+ return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
+ }
+
+ dt = &h3c->table;
+
+ if (dt->size > capacity) {
+ if (ngx_http_v3_evict(c, dt->size - capacity) != NGX_OK) {
+ return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
+ }
+ }
+
+ max = capacity / 32;
+ prev_max = dt->capacity / 32;
+
+ if (max > prev_max) {
+ elts = ngx_alloc(max * sizeof(void *), c->log);
+ if (elts == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (dt->elts == NULL) {
+ cln = ngx_pool_cleanup_add(pc->pool, 0);
+ if (cln == NULL) {
+ return NGX_ERROR;
+ }
+
+ cln->handler = ngx_http_v3_cleanup_table;
+ cln->data = dt;
+
+ } else {
+ ngx_memcpy(elts, dt->elts, dt->nelts * sizeof(void *));
+ ngx_free(dt->elts);
+ }
+
+ dt->elts = elts;
+ }
+
+ dt->capacity = capacity;
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_http_v3_cleanup_table(void *data)
+{
+ ngx_http_v3_dynamic_table_t *dt = data;
+
+ ngx_uint_t n;
+
+ for (n = 0; n < dt->nelts; n++) {
+ ngx_free(dt->elts[n]);
+ }
+
+ ngx_free(dt->elts);
+}
+
+
+static ngx_int_t
+ngx_http_v3_evict(ngx_connection_t *c, size_t need)
+{
+ size_t size, target;
+ ngx_uint_t n;
+ ngx_http_v3_header_t *h;
+ ngx_http_v3_connection_t *h3c;
+ ngx_http_v3_dynamic_table_t *dt;
+
+ h3c = c->qs->parent->data;
+ dt = &h3c->table;
+
+ if (need > dt->capacity) {
+ ngx_log_error(NGX_LOG_ERR, c->log, 0,
+ "not enough dynamic table capacity");
+ return NGX_ERROR;
+ }
+
+ target = dt->capacity - need;
+ n = 0;
+
+ while (dt->size > target) {
+ h = dt->elts[n++];
+ size = ngx_http_v3_table_entry_size(&h->name, &h->value);
+
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 evict [%ui] \"%V\":\"%V\" size:%uz",
+ dt->base, &h->name, &h->value, size);
+
+ ngx_free(h);
+ dt->size -= size;
+ }
+
+ if (n) {
+ dt->nelts -= n;
+ dt->base += n;
+ ngx_memmove(dt->elts, &dt->elts[n], dt->nelts * sizeof(void *));
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index)
+{
+ ngx_str_t name, value;
+ ngx_http_v3_connection_t *h3c;
+ ngx_http_v3_dynamic_table_t *dt;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 duplicate %ui", index);
+
+ h3c = c->qs->parent->data;
+ dt = &h3c->table;
+
+ if (dt->base + dt->nelts <= index) {
+ return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
+ }
+
+ index = dt->base + dt->nelts - 1 - index;
+
+ if (ngx_http_v3_lookup(c, index, &name, &value) != NGX_OK) {
+ return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
+ }
+
+ return ngx_http_v3_insert(c, &name, &value);
+}
+
+
+ngx_int_t
+ngx_http_v3_ack_header(ngx_connection_t *c, ngx_uint_t stream_id)
+{
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 ack header %ui", stream_id);
+
+ /* XXX */
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id)
+{
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 cancel stream %ui", stream_id);
+
+ /* XXX */
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc)
+{
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 increment insert count %ui", inc);
+
+ /* XXX */
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_lookup_static(ngx_connection_t *c, ngx_uint_t index,
+ ngx_str_t *name, ngx_str_t *value)
+{
+ ngx_uint_t nelts;
+ ngx_http_v3_header_t *h;
+
+ nelts = sizeof(ngx_http_v3_static_table)
+ / sizeof(ngx_http_v3_static_table[0]);
+
+ if (index >= nelts) {
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 static[%ui] lookup out of bounds: %ui",
+ index, nelts);
+ return NGX_ERROR;
+ }
+
+ h = &ngx_http_v3_static_table[index];
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 static[%ui] lookup \"%V\":\"%V\"",
+ index, &h->name, &h->value);
+
+ if (name) {
+ *name = h->name;
+ }
+
+ if (value) {
+ *value = h->value;
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_lookup(ngx_connection_t *c, ngx_uint_t index, ngx_str_t *name,
+ ngx_str_t *value)
+{
+ ngx_http_v3_header_t *h;
+ ngx_http_v3_connection_t *h3c;
+ ngx_http_v3_dynamic_table_t *dt;
+
+ h3c = c->qs->parent->data;
+ dt = &h3c->table;
+
+ if (index < dt->base || index - dt->base >= dt->nelts) {
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 dynamic[%ui] lookup out of bounds: [%ui,%ui]",
+ index, dt->base, dt->base + dt->nelts);
+ return NGX_ERROR;
+ }
+
+ h = dt->elts[index - dt->base];
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 dynamic[%ui] lookup \"%V\":\"%V\"",
+ index, &h->name, &h->value);
+
+ if (name) {
+ *name = h->name;
+ }
+
+ if (value) {
+ *value = h->value;
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_decode_insert_count(ngx_connection_t *c, ngx_uint_t *insert_count)
+{
+ ngx_uint_t max_entries, full_range, max_value,
+ max_wrapped, req_insert_count;
+ ngx_http_v3_srv_conf_t *h3scf;
+ ngx_http_v3_connection_t *h3c;
+ ngx_http_v3_dynamic_table_t *dt;
+
+ /* QPACK 4.5.1.1. Required Insert Count */
+
+ if (*insert_count == 0) {
+ return NGX_OK;
+ }
+
+ h3c = c->qs->parent->data;
+ dt = &h3c->table;
+
+ h3scf = ngx_http_get_module_srv_conf(h3c->hc.conf_ctx, ngx_http_v3_module);
+
+ max_entries = h3scf->max_table_capacity / 32;
+ full_range = 2 * max_entries;
+
+ if (*insert_count > full_range) {
+ return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED;
+ }
+
+ max_value = dt->base + dt->nelts + max_entries;
+ max_wrapped = (max_value / full_range) * full_range;
+ req_insert_count = max_wrapped + *insert_count - 1;
+
+ if (req_insert_count > max_value) {
+ if (req_insert_count <= full_range) {
+ return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED;
+ }
+
+ req_insert_count -= full_range;
+ }
+
+ if (req_insert_count == 0) {
+ return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 decode insert_count %ui -> %ui",
+ *insert_count, req_insert_count);
+
+ *insert_count = req_insert_count;
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_check_insert_count(ngx_connection_t *c, ngx_uint_t insert_count)
+{
+ size_t n;
+ ngx_connection_t *pc;
+ ngx_pool_cleanup_t *cln;
+ ngx_http_v3_block_t *block;
+ ngx_http_v3_srv_conf_t *h3scf;
+ ngx_http_v3_connection_t *h3c;
+ ngx_http_v3_dynamic_table_t *dt;
+
+ pc = c->qs->parent;
+ h3c = pc->data;
+ dt = &h3c->table;
+
+ n = dt->base + dt->nelts;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 check insert count req:%ui, have:%ui",
+ insert_count, n);
+
+ if (n >= insert_count) {
+ return NGX_OK;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 block stream");
+
+ block = NULL;
+
+ for (cln = c->pool->cleanup; cln; cln = cln->next) {
+ if (cln->handler == ngx_http_v3_unblock) {
+ block = cln->data;
+ break;
+ }
+ }
+
+ if (block == NULL) {
+ cln = ngx_pool_cleanup_add(c->pool, sizeof(ngx_http_v3_block_t));
+ if (cln == NULL) {
+ return NGX_ERROR;
+ }
+
+ cln->handler = ngx_http_v3_unblock;
+
+ block = cln->data;
+ block->queue.prev = NULL;
+ block->connection = c;
+ block->nblocked = &h3c->nblocked;
+ }
+
+ if (block->queue.prev == NULL) {
+ h3scf = ngx_http_get_module_srv_conf(h3c->hc.conf_ctx,
+ ngx_http_v3_module);
+
+ if (h3c->nblocked == h3scf->max_blocked_streams) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client exceeded http3_max_blocked_streams limit");
+ return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED;
+ }
+
+ h3c->nblocked++;
+ ngx_queue_insert_tail(&h3c->blocked, &block->queue);
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 blocked:%ui", h3c->nblocked);
+
+ return NGX_BUSY;
+}
+
+
+static void
+ngx_http_v3_unblock(void *data)
+{
+ ngx_http_v3_block_t *block = data;
+
+ if (block->queue.prev) {
+ ngx_queue_remove(&block->queue);
+ block->queue.prev = NULL;
+ (*block->nblocked)--;
+ }
+}
+
+
+static ngx_int_t
+ngx_http_v3_new_header(ngx_connection_t *c)
+{
+ ngx_queue_t *q;
+ ngx_connection_t *bc;
+ ngx_http_v3_block_t *block;
+ ngx_http_v3_connection_t *h3c;
+
+ h3c = c->qs->parent->data;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 new dynamic header, blocked:%ui", h3c->nblocked);
+
+ while (!ngx_queue_empty(&h3c->blocked)) {
+ q = ngx_queue_head(&h3c->blocked);
+ block = (ngx_http_v3_block_t *) q;
+ bc = block->connection;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, bc->log, 0, "http3 unblock stream");
+
+ ngx_http_v3_unblock(block);
+ ngx_post_event(bc->read, &ngx_posted_events);
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_set_param(ngx_connection_t *c, uint64_t id, uint64_t value)
+{
+ switch (id) {
+
+ case NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY:
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 param QPACK_MAX_TABLE_CAPACITY:%uL", value);
+ break;
+
+ case NGX_HTTP_V3_PARAM_MAX_HEADER_LIST_SIZE:
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 param SETTINGS_MAX_HEADER_LIST_SIZE:%uL", value);
+ break;
+
+ case NGX_HTTP_V3_PARAM_BLOCKED_STREAMS:
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 param QPACK_BLOCKED_STREAMS:%uL", value);
+ break;
+
+ default:
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 param #%uL:%uL", id, value);
+ }
+
+ return NGX_OK;
+}
diff --git a/src/stream/ngx_stream.c b/src/stream/ngx_stream.c
index 78356754e..bc4aa09a3 100644
--- a/src/stream/ngx_stream.c
+++ b/src/stream/ngx_stream.c
@@ -572,6 +572,9 @@ ngx_stream_add_addrs(ngx_conf_t *cf, ngx_stream_port_t *stport,
#if (NGX_STREAM_SSL)
addrs[i].conf.ssl = addr[i].opt.ssl;
#endif
+#if (NGX_STREAM_QUIC)
+ addrs[i].conf.quic = addr[i].opt.quic;
+#endif
addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol;
addrs[i].conf.addr_text = addr[i].opt.addr_text;
}
@@ -607,6 +610,9 @@ ngx_stream_add_addrs6(ngx_conf_t *cf, ngx_stream_port_t *stport,
#if (NGX_STREAM_SSL)
addrs6[i].conf.ssl = addr[i].opt.ssl;
#endif
+#if (NGX_STREAM_QUIC)
+ addrs6[i].conf.quic = addr[i].opt.quic;
+#endif
addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol;
addrs6[i].conf.addr_text = addr[i].opt.addr_text;
}
diff --git a/src/stream/ngx_stream.h b/src/stream/ngx_stream.h
index 9e3583295..6cf5eee09 100644
--- a/src/stream/ngx_stream.h
+++ b/src/stream/ngx_stream.h
@@ -16,6 +16,10 @@
#include <ngx_stream_ssl_module.h>
#endif
+#if (NGX_STREAM_QUIC)
+#include <ngx_stream_quic_module.h>
+#endif
+
typedef struct ngx_stream_session_s ngx_stream_session_t;
@@ -51,6 +55,7 @@ typedef struct {
unsigned bind:1;
unsigned wildcard:1;
unsigned ssl:1;
+ unsigned quic:1;
#if (NGX_HAVE_INET6)
unsigned ipv6only:1;
#endif
@@ -73,6 +78,7 @@ typedef struct {
ngx_stream_conf_ctx_t *ctx;
ngx_str_t addr_text;
unsigned ssl:1;
+ unsigned quic:1;
unsigned proxy_protocol:1;
} ngx_stream_addr_conf_t;
diff --git a/src/stream/ngx_stream_core_module.c b/src/stream/ngx_stream_core_module.c
index 9b6afe974..6990ae3f6 100644
--- a/src/stream/ngx_stream_core_module.c
+++ b/src/stream/ngx_stream_core_module.c
@@ -325,6 +325,9 @@ ngx_stream_core_content_phase(ngx_stream_session_t *s,
cscf = ngx_stream_get_module_srv_conf(s, ngx_stream_core_module);
if (c->type == SOCK_STREAM
+#if (NGX_STREAM_QUIC)
+ && c->qs == NULL
+#endif
&& cscf->tcp_nodelay
&& ngx_tcp_nodelay(c) != NGX_OK)
{
@@ -741,6 +744,29 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
#endif
}
+ if (ngx_strcmp(value[i].data, "quic") == 0) {
+#if (NGX_STREAM_QUIC)
+ ngx_stream_ssl_conf_t *sslcf;
+
+ sslcf = ngx_stream_conf_get_module_srv_conf(cf,
+ ngx_stream_ssl_module);
+
+ sslcf->listen = 1;
+ sslcf->file = cf->conf_file->file.name.data;
+ sslcf->line = cf->conf_file->line;
+
+ ls->quic = 1;
+ ls->type = SOCK_DGRAM;
+
+ continue;
+#else
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "the \"quic\" parameter requires "
+ "ngx_stream_quic_module");
+ return NGX_CONF_ERROR;
+#endif
+ }
+
if (ngx_strncmp(value[i].data, "so_keepalive=", 13) == 0) {
if (ngx_strcmp(&value[i].data[13], "on") == 0) {
@@ -852,6 +878,12 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
}
#endif
+#if (NGX_STREAM_SSL && NGX_STREAM_QUIC)
+ if (ls->ssl && ls->quic) {
+ return "\"ssl\" parameter is incompatible with \"quic\"";
+ }
+#endif
+
if (ls->so_keepalive) {
return "\"so_keepalive\" parameter is incompatible with \"udp\"";
}
diff --git a/src/stream/ngx_stream_handler.c b/src/stream/ngx_stream_handler.c
index 669b6a18d..2b0848a67 100644
--- a/src/stream/ngx_stream_handler.c
+++ b/src/stream/ngx_stream_handler.c
@@ -115,6 +115,27 @@ ngx_stream_init_connection(ngx_connection_t *c)
}
}
+#if (NGX_STREAM_QUIC)
+
+ if (addr_conf->quic) {
+ ngx_quic_conf_t *qcf;
+ ngx_stream_ssl_conf_t *scf;
+
+ if (c->qs == NULL) {
+ c->log->connection = c->number;
+
+ qcf = ngx_stream_get_module_srv_conf(addr_conf->ctx,
+ ngx_stream_quic_module);
+ scf = ngx_stream_get_module_srv_conf(addr_conf->ctx,
+ ngx_stream_ssl_module);
+
+ ngx_quic_run(c, &scf->ssl, qcf);
+ return;
+ }
+ }
+
+#endif
+
s = ngx_pcalloc(c->pool, sizeof(ngx_stream_session_t));
if (s == NULL) {
ngx_stream_close_connection(c);
diff --git a/src/stream/ngx_stream_quic_module.c b/src/stream/ngx_stream_quic_module.c
new file mode 100644
index 000000000..362855f1a
--- /dev/null
+++ b/src/stream/ngx_stream_quic_module.c
@@ -0,0 +1,343 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ * Copyright (C) Roman Arutyunyan
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_stream.h>
+
+
+static ngx_int_t ngx_stream_variable_quic(ngx_stream_session_t *s,
+ ngx_stream_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_stream_quic_add_variables(ngx_conf_t *cf);
+static void *ngx_stream_quic_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_stream_quic_merge_srv_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+static char *ngx_stream_quic_max_ack_delay(ngx_conf_t *cf, void *post,
+ void *data);
+static char *ngx_stream_quic_max_udp_payload_size(ngx_conf_t *cf, void *post,
+ void *data);
+
+
+static ngx_conf_post_t ngx_stream_quic_max_ack_delay_post =
+ { ngx_stream_quic_max_ack_delay };
+static ngx_conf_post_t ngx_stream_quic_max_udp_payload_size_post =
+ { ngx_stream_quic_max_udp_payload_size };
+static ngx_conf_num_bounds_t ngx_stream_quic_ack_delay_exponent_bounds =
+ { ngx_conf_check_num_bounds, 0, 20 };
+static ngx_conf_num_bounds_t
+ ngx_stream_quic_active_connection_id_limit_bounds =
+ { ngx_conf_check_num_bounds, 2, -1 };
+
+
+static ngx_command_t ngx_stream_quic_commands[] = {
+
+ { ngx_string("quic_max_idle_timeout"),
+ NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_STREAM_SRV_CONF_OFFSET,
+ offsetof(ngx_quic_conf_t, tp.max_idle_timeout),
+ NULL },
+
+ { ngx_string("quic_max_ack_delay"),
+ NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_STREAM_SRV_CONF_OFFSET,
+ offsetof(ngx_quic_conf_t, tp.max_ack_delay),
+ &ngx_stream_quic_max_ack_delay_post },
+
+ { ngx_string("quic_max_udp_payload_size"),
+ NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_STREAM_SRV_CONF_OFFSET,
+ offsetof(ngx_quic_conf_t, tp.max_udp_payload_size),
+ &ngx_stream_quic_max_udp_payload_size_post },
+
+ { ngx_string("quic_initial_max_data"),
+ NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_STREAM_SRV_CONF_OFFSET,
+ offsetof(ngx_quic_conf_t, tp.initial_max_data),
+ NULL },
+
+ { ngx_string("quic_initial_max_stream_data_bidi_local"),
+ NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_STREAM_SRV_CONF_OFFSET,
+ offsetof(ngx_quic_conf_t, tp.initial_max_stream_data_bidi_local),
+ NULL },
+
+ { ngx_string("quic_initial_max_stream_data_bidi_remote"),
+ NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_STREAM_SRV_CONF_OFFSET,
+ offsetof(ngx_quic_conf_t, tp.initial_max_stream_data_bidi_remote),
+ NULL },
+
+ { ngx_string("quic_initial_max_stream_data_uni"),
+ NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_STREAM_SRV_CONF_OFFSET,
+ offsetof(ngx_quic_conf_t, tp.initial_max_stream_data_uni),
+ NULL },
+
+ { ngx_string("quic_initial_max_streams_bidi"),
+ NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_STREAM_SRV_CONF_OFFSET,
+ offsetof(ngx_quic_conf_t, tp.initial_max_streams_bidi),
+ NULL },
+
+ { ngx_string("quic_initial_max_streams_uni"),
+ NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_STREAM_SRV_CONF_OFFSET,
+ offsetof(ngx_quic_conf_t, tp.initial_max_streams_uni),
+ NULL },
+
+ { ngx_string("quic_ack_delay_exponent"),
+ NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_STREAM_SRV_CONF_OFFSET,
+ offsetof(ngx_quic_conf_t, tp.ack_delay_exponent),
+ &ngx_stream_quic_ack_delay_exponent_bounds },
+
+ { ngx_string("quic_active_migration"),
+ NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_STREAM_SRV_CONF_OFFSET,
+ offsetof(ngx_quic_conf_t, tp.disable_active_migration),
+ NULL },
+
+ { ngx_string("quic_active_connection_id_limit"),
+ NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_STREAM_SRV_CONF_OFFSET,
+ offsetof(ngx_quic_conf_t, tp.active_connection_id_limit),
+ &ngx_stream_quic_active_connection_id_limit_bounds },
+
+ { ngx_string("quic_retry"),
+ NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_STREAM_SRV_CONF_OFFSET,
+ offsetof(ngx_quic_conf_t, retry),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_stream_module_t ngx_stream_quic_module_ctx = {
+ ngx_stream_quic_add_variables, /* preconfiguration */
+ NULL, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ ngx_stream_quic_create_srv_conf, /* create server configuration */
+ ngx_stream_quic_merge_srv_conf, /* merge server configuration */
+};
+
+
+ngx_module_t ngx_stream_quic_module = {
+ NGX_MODULE_V1,
+ &ngx_stream_quic_module_ctx, /* module context */
+ ngx_stream_quic_commands, /* module directives */
+ NGX_STREAM_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_stream_variable_t ngx_stream_quic_vars[] = {
+
+ { ngx_string("quic"), NULL, ngx_stream_variable_quic, 0, 0, 0 },
+
+ ngx_stream_null_variable
+};
+
+
+static ngx_int_t
+ngx_stream_variable_quic(ngx_stream_session_t *s,
+ ngx_stream_variable_value_t *v, uintptr_t data)
+{
+ if (s->connection->qs) {
+
+ v->len = 4;
+ v->valid = 1;
+ v->no_cacheable = 1;
+ v->not_found = 0;
+ v->data = (u_char *) "quic";
+ return NGX_OK;
+ }
+
+ v->not_found = 1;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_quic_add_variables(ngx_conf_t *cf)
+{
+ ngx_stream_variable_t *var, *v;
+
+ for (v = ngx_stream_quic_vars; v->name.len; v++) {
+ var = ngx_stream_add_variable(cf, &v->name, v->flags);
+ if (var == NULL) {
+ return NGX_ERROR;
+ }
+
+ var->get_handler = v->get_handler;
+ var->data = v->data;
+ }
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_stream_quic_create_srv_conf(ngx_conf_t *cf)
+{
+ ngx_quic_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_quic_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * conf->tp.original_dcid = { 0, NULL };
+ * conf->tp.initial_scid = { 0, NULL };
+ * conf->tp.retry_scid = { 0, NULL };
+ * conf->tp.stateless_reset_token = { 0 }
+ * conf->tp.preferred_address = NULL
+ * conf->require_alpn = 0;
+ */
+
+ conf->tp.max_idle_timeout = NGX_CONF_UNSET_MSEC;
+ conf->tp.max_ack_delay = NGX_CONF_UNSET_MSEC;
+ conf->tp.max_udp_payload_size = NGX_CONF_UNSET_SIZE;
+ conf->tp.initial_max_data = NGX_CONF_UNSET_SIZE;
+ conf->tp.initial_max_stream_data_bidi_local = NGX_CONF_UNSET_SIZE;
+ conf->tp.initial_max_stream_data_bidi_remote = NGX_CONF_UNSET_SIZE;
+ conf->tp.initial_max_stream_data_uni = NGX_CONF_UNSET_SIZE;
+ conf->tp.initial_max_streams_bidi = NGX_CONF_UNSET_UINT;
+ conf->tp.initial_max_streams_uni = NGX_CONF_UNSET_UINT;
+ conf->tp.ack_delay_exponent = NGX_CONF_UNSET_UINT;
+ conf->tp.disable_active_migration = NGX_CONF_UNSET_UINT;
+ conf->tp.active_connection_id_limit = NGX_CONF_UNSET_UINT;
+
+ conf->retry = NGX_CONF_UNSET;
+
+ return conf;
+}
+
+
+static char *
+ngx_stream_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_quic_conf_t *prev = parent;
+ ngx_quic_conf_t *conf = child;
+
+ ngx_conf_merge_msec_value(conf->tp.max_idle_timeout,
+ prev->tp.max_idle_timeout, 60000);
+
+ ngx_conf_merge_msec_value(conf->tp.max_ack_delay,
+ prev->tp.max_ack_delay,
+ NGX_QUIC_DEFAULT_MAX_ACK_DELAY);
+
+ ngx_conf_merge_size_value(conf->tp.max_udp_payload_size,
+ prev->tp.max_udp_payload_size,
+ NGX_QUIC_MAX_UDP_PAYLOAD_SIZE);
+
+ ngx_conf_merge_size_value(conf->tp.initial_max_data,
+ prev->tp.initial_max_data,
+ 16 * NGX_QUIC_STREAM_BUFSIZE);
+
+ ngx_conf_merge_size_value(conf->tp.initial_max_stream_data_bidi_local,
+ prev->tp.initial_max_stream_data_bidi_local,
+ NGX_QUIC_STREAM_BUFSIZE);
+
+ ngx_conf_merge_size_value(conf->tp.initial_max_stream_data_bidi_remote,
+ prev->tp.initial_max_stream_data_bidi_remote,
+ NGX_QUIC_STREAM_BUFSIZE);
+
+ ngx_conf_merge_size_value(conf->tp.initial_max_stream_data_uni,
+ prev->tp.initial_max_stream_data_uni,
+ NGX_QUIC_STREAM_BUFSIZE);
+
+ ngx_conf_merge_uint_value(conf->tp.initial_max_streams_bidi,
+ prev->tp.initial_max_streams_bidi, 16);
+
+ ngx_conf_merge_uint_value(conf->tp.initial_max_streams_uni,
+ prev->tp.initial_max_streams_uni, 16);
+
+ ngx_conf_merge_uint_value(conf->tp.ack_delay_exponent,
+ prev->tp.ack_delay_exponent,
+ NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT);
+
+ ngx_conf_merge_uint_value(conf->tp.disable_active_migration,
+ prev->tp.disable_active_migration, 1);
+
+ ngx_conf_merge_uint_value(conf->tp.active_connection_id_limit,
+ prev->tp.active_connection_id_limit, 2);
+
+ ngx_conf_merge_value(conf->retry, prev->retry, 0);
+
+ if (conf->retry) {
+ if (RAND_bytes(conf->token_key, sizeof(conf->token_key)) <= 0) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_stream_quic_max_ack_delay(ngx_conf_t *cf, void *post, void *data)
+{
+ ngx_msec_t *sp = data;
+
+ if (*sp > 16384) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"quic_max_ack_delay\" must be less than 16384");
+
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_stream_quic_max_udp_payload_size(ngx_conf_t *cf, void *post, void *data)
+{
+ size_t *sp = data;
+
+ if (*sp < NGX_QUIC_MIN_INITIAL_SIZE
+ || *sp > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE)
+ {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"quic_max_udp_payload_size\" must be between "
+ "%d and %d",
+ NGX_QUIC_MIN_INITIAL_SIZE,
+ NGX_QUIC_MAX_UDP_PAYLOAD_SIZE);
+
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
diff --git a/src/stream/ngx_stream_quic_module.h b/src/stream/ngx_stream_quic_module.h
new file mode 100644
index 000000000..6ac4d96f0
--- /dev/null
+++ b/src/stream/ngx_stream_quic_module.h
@@ -0,0 +1,20 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_STREAM_QUIC_H_INCLUDED_
+#define _NGX_STREAM_QUIC_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_stream.h>
+
+
+extern ngx_module_t ngx_stream_quic_module;
+
+
+#endif /* _NGX_STREAM_QUIC_H_INCLUDED_ */