summaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/core/ngx_connection.c12
-rw-r--r--src/core/ngx_connection.h8
-rw-r--r--src/core/ngx_core.h39
-rw-r--r--src/event/ngx_event.c36
-rw-r--r--src/event/ngx_event_openssl.c5
-rw-r--r--src/event/ngx_event_openssl.h7
-rw-r--r--src/event/ngx_event_quic.c2488
-rw-r--r--src/event/ngx_event_quic.h95
-rw-r--r--src/event/ngx_event_quic_protection.c1016
-rw-r--r--src/event/ngx_event_quic_protection.h45
-rw-r--r--src/event/ngx_event_quic_transport.c1749
-rw-r--r--src/event/ngx_event_quic_transport.h298
-rw-r--r--src/http/modules/ngx_http_chunked_filter_module.c58
-rw-r--r--src/http/modules/ngx_http_ssl_module.c22
-rw-r--r--src/http/ngx_http.c33
-rw-r--r--src/http/ngx_http.h6
-rw-r--r--src/http/ngx_http_core_module.c16
-rw-r--r--src/http/ngx_http_core_module.h4
-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.c194
-rw-r--r--src/http/ngx_http_request.h7
-rw-r--r--src/http/ngx_http_request_body.c29
-rw-r--r--src/http/v3/ngx_http_v3.c96
-rw-r--r--src/http/v3/ngx_http_v3.h116
-rw-r--r--src/http/v3/ngx_http_v3_module.c302
-rw-r--r--src/http/v3/ngx_http_v3_parse.c1481
-rw-r--r--src/http/v3/ngx_http_v3_parse.h155
-rw-r--r--src/http/v3/ngx_http_v3_request.c669
-rw-r--r--src/http/v3/ngx_http_v3_streams.c571
-rw-r--r--src/http/v3/ngx_http_v3_tables.c416
31 files changed, 9927 insertions, 88 deletions
diff --git a/src/core/ngx_connection.c b/src/core/ngx_connection.c
index 33682532a..9ec1cd7ac 100644
--- a/src/core/ngx_connection.c
+++ b/src/core/ngx_connection.c
@@ -1178,11 +1178,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);
}
@@ -1191,7 +1186,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);
@@ -1223,6 +1218,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..b3a36cf05 100644
--- a/src/core/ngx_connection.h
+++ b/src/core/ngx_connection.h
@@ -147,13 +147,15 @@ 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_SSL || NGX_COMPAT)
- ngx_ssl_connection_t *ssl;
+ ngx_quic_connection_t *quic;
+ ngx_quic_stream_t *qs;
+ 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..eb3acd663 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>
+#include <ngx_event_quic.h>
+#include <ngx_event_quic_transport.h>
+#include <ngx_event_quic_protection.h>
#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..f0ab73afe 100644
--- a/src/event/ngx_event.c
+++ b/src/event/ngx_event.c
@@ -268,6 +268,22 @@ ngx_process_events_and_timers(ngx_cycle_t *cycle)
ngx_int_t
ngx_handle_read_event(ngx_event_t *rev, ngx_uint_t flags)
{
+ 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;
+ }
+
if (ngx_event_flags & NGX_USE_CLEAR_EVENT) {
/* kqueue, epoll */
@@ -338,14 +354,26 @@ 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 (c->qs) {
+
+ if (!wev->active && !wev->ready) {
+ wev->active = 1;
+
+ } else if (wev->active && wev->ready) {
+ wev->active = 0;
+ }
+
+ return NGX_OK;
+ }
+
if (ngx_event_flags & NGX_USE_CLEAR_EVENT) {
/* kqueue, epoll */
@@ -916,6 +944,10 @@ ngx_send_lowat(ngx_connection_t *c, size_t lowat)
{
int sndlowat;
+ if (c->qs) {
+ return NGX_OK;
+ }
+
#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 91b415caa..2fd254446 100644
--- a/src/event/ngx_event_openssl.c
+++ b/src/event/ngx_event_openssl.c
@@ -2735,6 +2735,11 @@ ngx_ssl_shutdown(ngx_connection_t *c)
int n, sslerr, mode;
ngx_err_t err;
+ if (c->qs) {
+ /* QUIC streams inherit SSL object */
+ return NGX_OK;
+ }
+
if (SSL_in_init(c->ssl->connection)) {
/*
* OpenSSL 1.0.2f complains if SSL_shutdown() is called during
diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h
index 61da0c5db..bd90e47fe 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..b71cb6e0d
--- /dev/null
+++ b/src/event/ngx_event_quic.c
@@ -0,0 +1,2488 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.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_ns(level) \
+ ((level) == ssl_encryption_initial) ? 0 \
+ : (((level) == ssl_encryption_handshake) ? 1 : 2)
+
+#define NGX_QUIC_NAMESPACE_LAST (NGX_QUIC_ENCRYPTION_LAST - 1)
+
+#define NGX_QUIC_STREAMS_INC 16
+#define NGX_QUIC_STREAMS_LIMIT (1ULL < 60)
+
+
+typedef enum {
+ NGX_QUIC_ST_INITIAL, /* connection just created */
+ NGX_QUIC_ST_HANDSHAKE, /* handshake started */
+ NGX_QUIC_ST_EARLY_DATA, /* handshake in progress */
+ NGX_QUIC_ST_APPLICATION /* handshake complete */
+} ngx_quic_state_t;
+
+
+typedef struct {
+ ngx_rbtree_t tree;
+ ngx_rbtree_node_t sentinel;
+ ngx_connection_handler_pt handler;
+
+ ngx_uint_t id_counter;
+} ngx_quic_streams_t;
+
+
+typedef struct {
+ ngx_quic_secret_t client_secret;
+ ngx_quic_secret_t server_secret;
+
+ uint64_t pnum;
+ uint64_t largest; /* number received from peer */
+
+ ngx_queue_t frames;
+ ngx_queue_t sent;
+} ngx_quic_namespace_t;
+
+
+struct ngx_quic_connection_s {
+ ngx_str_t scid;
+ ngx_str_t dcid;
+ ngx_str_t token;
+
+ ngx_uint_t client_tp_done;
+ ngx_quic_tp_t tp;
+ ngx_quic_tp_t ctp;
+
+ ngx_quic_state_t state;
+
+ ngx_quic_namespace_t ns[NGX_QUIC_NAMESPACE_LAST];
+ ngx_quic_secrets_t keys[NGX_QUIC_ENCRYPTION_LAST];
+ ngx_quic_secrets_t next_key;
+ uint64_t crypto_offset_out[NGX_QUIC_ENCRYPTION_LAST];
+ uint64_t crypto_offset_in[NGX_QUIC_ENCRYPTION_LAST];
+
+ ngx_ssl_t *ssl;
+
+ ngx_event_t push;
+ ngx_event_t retry;
+ ngx_queue_t free_frames;
+
+#if (NGX_DEBUG)
+ ngx_uint_t nframes;
+#endif
+
+ ngx_quic_streams_t streams;
+ ngx_uint_t max_data;
+
+ uint64_t cur_streams;
+ uint64_t max_streams;
+
+ unsigned send_timer_set:1;
+ unsigned closing:1;
+ unsigned key_phase:1;
+};
+
+
+#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_tp_t *tp, ngx_quic_header_t *pkt,
+ ngx_connection_handler_pt handler);
+static ngx_int_t ngx_quic_init_connection(ngx_connection_t *c);
+static void ngx_quic_input_handler(ngx_event_t *rev);
+static void ngx_quic_close_connection(ngx_connection_t *c);
+
+static ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b);
+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_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_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_namespace_t *ns, uint64_t min, uint64_t max);
+static ngx_int_t ngx_quic_handle_crypto_frame(ngx_connection_t *c,
+ ngx_quic_header_t *pkt, ngx_quic_crypto_frame_t *frame);
+static ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c,
+ ngx_quic_header_t *pkt, ngx_quic_stream_frame_t *frame);
+static ngx_int_t ngx_quic_handle_max_streams(ngx_connection_t *c);
+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 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_ns(ngx_connection_t *c,
+ ngx_quic_namespace_t *ns, ngx_uint_t nsi);
+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_queue_t *frames);
+
+static void ngx_quic_set_packet_number(ngx_quic_header_t *pkt,
+ ngx_quic_namespace_t *ns);
+static void ngx_quic_retransmit_handler(ngx_event_t *ev);
+static ngx_int_t ngx_quic_retransmit_ns(ngx_connection_t *c,
+ ngx_quic_namespace_t *ns, ngx_msec_t *waitp);
+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_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 void ngx_quic_stream_cleanup_handler(void *data);
+static ngx_chain_t *ngx_quic_stream_send_chain(ngx_connection_t *c,
+ ngx_chain_t *in, off_t limit);
+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 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);
+
+ ngx_quic_hexdump(c->log, "level:%d read secret",
+ rsecret, secret_len, level);
+
+ keys = &c->quic->keys[level];
+
+ if (level == ssl_encryption_early_data) {
+ c->quic->state = NGX_QUIC_ST_EARLY_DATA;
+ }
+
+ 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);
+
+ ngx_quic_hexdump(c->log, "level:%d write secret",
+ wsecret, secret_len, level);
+
+ 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);
+
+ ngx_quic_hexdump(c->log, "level:%d read", rsecret, secret_len, level);
+
+ 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) {
+ c->quic->state = NGX_QUIC_ST_EARLY_DATA;
+ return 1;
+ }
+
+ ngx_quic_hexdump(c->log, "level:%d write", wsecret, secret_len, level);
+
+ 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;
+ const uint8_t *client_params;
+ ngx_quic_frame_t *frame;
+ ngx_connection_t *c;
+ ngx_quic_connection_t *qc;
+
+ c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
+ qc = c->quic;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "ngx_quic_add_handshake_data");
+
+ /* XXX: obtain client parameters after the handshake? */
+ if (!qc->client_tp_done) {
+
+ SSL_get_peer_quic_transport_params(ssl_conn, &client_params,
+ &client_params_len);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "SSL_get_peer_quic_transport_params(): params_len %ui",
+ client_params_len);
+
+ if (client_params_len != 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)
+ {
+ return NGX_ERROR;
+ }
+
+ 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;
+ }
+
+ qc->client_tp_done = 1;
+ }
+ }
+
+ frame = ngx_quic_alloc_frame(c, len);
+ if (frame == NULL) {
+ return 0;
+ }
+
+ ngx_memcpy(frame->data, data, len);
+
+ frame->level = level;
+ frame->type = NGX_QUIC_FT_CRYPTO;
+ frame->u.crypto.offset += qc->crypto_offset_out[level];
+ frame->u.crypto.len = len;
+ frame->u.crypto.data = frame->data;
+
+ qc->crypto_offset_out[level] += len;
+
+ ngx_sprintf(frame->info, "crypto, generated by SSL len=%ui level=%d", len, level);
+
+ ngx_quic_queue_frame(qc, frame);
+
+ return 1;
+}
+
+
+static int
+ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn)
+{
+ 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, "ngx_quic_flush_flight()");
+
+ 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_frame_t *frame;
+
+ c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "ngx_quic_send_alert(), lvl=%d, alert=%d",
+ (int) level, (int) alert);
+
+ frame = ngx_quic_alloc_frame(c, 0);
+ if (frame == NULL) {
+ return 0;
+ }
+
+ frame->level = level;
+ frame->type = NGX_QUIC_FT_CONNECTION_CLOSE;
+ frame->u.close.error_code = 0x100 + alert;
+ ngx_sprintf(frame->info, "cc from send_alert level=%d", frame->level);
+
+ ngx_quic_queue_frame(c->quic, frame);
+
+ if (ngx_quic_output(c) != NGX_OK) {
+ return 0;
+ }
+
+ return 1;
+}
+
+
+void
+ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp,
+ ngx_connection_handler_pt handler)
+{
+ 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, tp, &pkt, handler) != NGX_OK) {
+ ngx_quic_close_connection(c);
+ return;
+ }
+
+ ngx_add_timer(c->read, 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_tp_t *tp,
+ ngx_quic_header_t *pkt, ngx_connection_handler_pt handler)
+{
+ ngx_uint_t i;
+ ngx_quic_tp_t *ctp;
+ ngx_quic_secrets_t *keys;
+ ngx_quic_connection_t *qc;
+ static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE];
+
+ if (ngx_buf_size(pkt->raw) < 1200) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "too small UDP datagram");
+ return NGX_ERROR;
+ }
+
+ if (ngx_quic_parse_long_header(pkt) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (!ngx_quic_pkt_in(pkt->flags)) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "invalid initial packet: 0x%xi", pkt->flags);
+ return NGX_ERROR;
+ }
+
+ if (ngx_quic_parse_initial_header(pkt) != NGX_OK) {
+ 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;
+ }
+
+ qc->state = NGX_QUIC_ST_INITIAL;
+
+ ngx_rbtree_init(&qc->streams.tree, &qc->streams.sentinel,
+ ngx_quic_rbtree_insert_stream);
+
+ for (i = 0; i < 3; i++) {
+ ngx_queue_init(&qc->ns[i].frames);
+ ngx_queue_init(&qc->ns[i].sent);
+ }
+
+ ngx_queue_init(&qc->free_frames);
+
+ qc->retry.log = c->log;
+ qc->retry.data = c;
+ qc->retry.handler = ngx_quic_retransmit_handler;
+ qc->retry.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->tp = *tp;
+ qc->streams.handler = handler;
+
+ ctp = &qc->ctp;
+ ctp->max_packet_size = NGX_QUIC_DEFAULT_MAX_PACKET_SIZE;
+ ctp->ack_delay_exponent = NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT;
+ ctp->max_ack_delay = NGX_QUIC_DEFAULT_MAX_ACK_DELAY;
+
+ qc->dcid.len = pkt->dcid.len;
+ qc->dcid.data = ngx_pnalloc(c->pool, pkt->dcid.len);
+ if (qc->dcid.data == NULL) {
+ return NGX_ERROR;
+ }
+ ngx_memcpy(qc->dcid.data, pkt->dcid.data, qc->dcid.len);
+
+ 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);
+
+ qc->token.len = pkt->token.len;
+ qc->token.data = ngx_pnalloc(c->pool, qc->token.len);
+ if (qc->token.data == NULL) {
+ return NGX_ERROR;
+ }
+ ngx_memcpy(qc->token.data, pkt->token.data, qc->token.len);
+
+ keys = &c->quic->keys[ssl_encryption_initial];
+
+ if (ngx_quic_set_initial_secret(c->pool, &keys->client, &keys->server,
+ &qc->dcid)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ pkt->secret = &keys->client;
+ pkt->level = ssl_encryption_initial;
+ pkt->plaintext = buf;
+
+ if (ngx_quic_decrypt(pkt, NULL) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (pkt->pn != 0) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "invalid initial packet number %L", pkt->pn);
+ 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;
+
+ return ngx_quic_input(c, pkt->raw);
+}
+
+
+static ngx_int_t
+ngx_quic_init_connection(ngx_connection_t *c)
+{
+ int n, sslerr;
+ u_char *p;
+ 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,
+ "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);
+ /* 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);
+ if (len < 0) {
+ return NGX_ERROR;
+ }
+
+ if (SSL_set_quic_transport_params(ssl_conn, p, len) == 0) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "SSL_set_quic_transport_params() failed");
+ return NGX_ERROR;
+ }
+
+ qc->max_streams = qc->tp.initial_max_streams_bidi;
+ qc->state = NGX_QUIC_ST_HANDSHAKE;
+
+ 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;
+ }
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "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 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_DEFAULT_MAX_PACKET_SIZE];
+
+ b.start = buf;
+ b.end = buf + sizeof(buf);
+ b.pos = b.last = b.start;
+
+ c = rev->data;
+ qc = c->quic;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0, "quic input handler");
+
+ if (qc->closing) {
+ ngx_quic_close_connection(c);
+ return;
+ }
+
+ if (rev->timedout) {
+ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+ ngx_quic_close_connection(c);
+ return;
+ }
+
+ if (c->close) {
+ ngx_quic_close_connection(c);
+ return;
+ }
+
+ n = c->recv(c, b.start, b.end - b.start);
+
+ if (n == NGX_AGAIN) {
+ return;
+ }
+
+ if (n == NGX_ERROR) {
+ c->read->eof = 1;
+ ngx_quic_close_connection(c);
+ return;
+ }
+
+ b.last += n;
+
+ if (ngx_quic_input(c, &b) != NGX_OK) {
+ ngx_quic_close_connection(c);
+ 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)
+{
+#if (NGX_DEBUG)
+ ngx_uint_t ns;
+#endif
+ ngx_pool_t *pool;
+ ngx_event_t *rev;
+ ngx_rbtree_t *tree;
+ ngx_rbtree_node_t *node;
+ ngx_quic_stream_t *qs;
+ ngx_quic_connection_t *qc;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "close quic connection");
+
+ qc = c->quic;
+
+ if (qc) {
+ qc->closing = 1;
+ tree = &qc->streams.tree;
+
+ if (tree->root != tree->sentinel) {
+ if (c->read->timer_set) {
+ ngx_del_timer(c->read);
+ }
+
+#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->ready = 1;
+ rev->pending_eof = 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;
+ }
+
+ if (qc->push.timer_set) {
+ ngx_del_timer(&qc->push);
+ }
+
+ if (qc->retry.timer_set) {
+ ngx_del_timer(&qc->retry);
+ }
+ }
+
+ if (c->ssl) {
+ (void) ngx_ssl_shutdown(c);
+ }
+
+#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_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 (pkt.flags == 0) {
+ /* XXX: no idea WTF is this, just ignore */
+ ngx_log_error(NGX_LOG_ALERT, c->log, 0, "FIREFOX: ZEROES");
+ break;
+ }
+
+ // 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,
+ "BUG: unknown quic state");
+ return NGX_ERROR;
+ }
+
+ } else {
+ rc = ngx_quic_app_input(c, &pkt);
+ }
+
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ /* b->pos is at header end, adjust by actual packet length */
+ p = b->pos + pkt.len;
+ b->pos = p; /* reset b->pos to the next packet start */
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt)
+{
+ ngx_ssl_conn_t *ssl_conn;
+ ngx_quic_secrets_t *keys;
+ static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE];
+
+ c->log->action = "processing initial quic packet";
+
+ ssl_conn = c->ssl->connection;
+
+ if (ngx_quic_parse_long_header(pkt) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ 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;
+
+ if (ngx_quic_decrypt(pkt, ssl_conn) != NGX_OK) {
+ 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_quic_secrets_t *keys;
+ ngx_quic_connection_t *qc;
+ static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_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,
+ "no read keys yet, packet ignored");
+ return NGX_DECLINED;
+ }
+
+ /* extract cleartext data into pkt */
+ if (ngx_quic_parse_long_header(pkt) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (pkt->dcid.len != qc->dcid.len) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic dcidl");
+ return NGX_ERROR;
+ }
+
+ if (ngx_memcmp(pkt->dcid.data, qc->dcid.data, qc->dcid.len) != 0) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic dcid");
+ return NGX_ERROR;
+ }
+
+ if (pkt->scid.len != qc->scid.len) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "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, c->log, 0, "unexpected quic scid");
+ return NGX_ERROR;
+ }
+
+ if (!ngx_quic_pkt_hs(pkt->flags)) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "invalid packet type: 0x%xi", pkt->flags);
+ 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;
+
+ if (ngx_quic_decrypt(pkt, c->ssl->connection) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ 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_quic_secrets_t *keys;
+ ngx_quic_connection_t *qc;
+ static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE];
+
+ c->log->action = "processing early data quic packet";
+
+ qc = c->quic;
+
+ /* extract cleartext data into pkt */
+ if (ngx_quic_parse_long_header(pkt) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (pkt->dcid.len != qc->dcid.len) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic dcidl");
+ return NGX_ERROR;
+ }
+
+ if (ngx_memcmp(pkt->dcid.data, qc->dcid.data, qc->dcid.len) != 0) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic dcid");
+ return NGX_ERROR;
+ }
+
+ if (pkt->scid.len != qc->scid.len) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "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, c->log, 0, "unexpected quic scid");
+ return NGX_ERROR;
+ }
+
+ if (!ngx_quic_pkt_zrtt(pkt->flags)) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "invalid packet type: 0x%xi", pkt->flags);
+ return NGX_ERROR;
+ }
+
+ if (ngx_quic_parse_handshake_header(pkt) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (c->quic->state != NGX_QUIC_ST_EARLY_DATA) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected 0-RTT packet");
+ return NGX_OK;
+ }
+
+ keys = &c->quic->keys[ssl_encryption_early_data];
+
+ pkt->secret = &keys->client;
+ pkt->level = ssl_encryption_early_data;
+ pkt->plaintext = buf;
+
+ if (ngx_quic_decrypt(pkt, c->ssl->connection) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ return ngx_quic_payload_handler(c, pkt);
+}
+
+
+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_connection_t *qc;
+ static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_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,
+ "no read keys yet, packet ignored");
+ return NGX_DECLINED;
+ }
+
+ if (ngx_quic_parse_short_header(pkt, &qc->dcid) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ pkt->secret = &keys->client;
+ pkt->next = &next->client;
+ pkt->key_phase = c->quic->key_phase;
+ pkt->level = ssl_encryption_application;
+ pkt->plaintext = buf;
+
+ if (ngx_quic_decrypt(pkt, c->ssl->connection) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ /* 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_this, do_close;
+ ngx_quic_frame_t frame, *ack_frame;
+ ngx_quic_connection_t *qc;
+
+
+ qc = c->quic;
+
+ p = pkt->payload.data;
+ end = p + pkt->payload.len;
+
+ ack_this = 0;
+ do_close = 0;
+
+ while (p < end) {
+
+ c->log->action = "parsing frames";
+
+ len = ngx_quic_parse_frame(pkt, p, end, &frame);
+
+ if (len == NGX_DECLINED) {
+ /* TODO: handle protocol violation:
+ * such frame not allowed in this packet
+ */
+ return NGX_ERROR;
+ }
+
+ if (len < 0) {
+ 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;
+ }
+
+ break;
+
+ case NGX_QUIC_FT_CRYPTO:
+
+ if (ngx_quic_handle_crypto_frame(c, pkt, &frame.u.crypto)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ ack_this = 1;
+ break;
+
+ case NGX_QUIC_FT_PADDING:
+ /* no action required */
+ break;
+
+ case NGX_QUIC_FT_PING:
+ ack_this = 1;
+ break;
+
+ case NGX_QUIC_FT_CONNECTION_CLOSE:
+ case NGX_QUIC_FT_CONNECTION_CLOSE2:
+ do_close = 1;
+ 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.u.stream)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ ack_this = 1;
+ break;
+
+ case NGX_QUIC_FT_MAX_DATA:
+ c->quic->max_data = frame.u.max_data.max_data;
+ ack_this = 1;
+ 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;
+ }
+
+ ack_this = 1;
+ 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;
+ }
+
+ ack_this = 1;
+ break;
+
+ case NGX_QUIC_FT_NEW_CONNECTION_ID:
+ case NGX_QUIC_FT_RETIRE_CONNECTION_ID:
+ case NGX_QUIC_FT_NEW_TOKEN:
+ case NGX_QUIC_FT_RESET_STREAM:
+ case NGX_QUIC_FT_STOP_SENDING:
+ case NGX_QUIC_FT_PATH_CHALLENGE:
+ case NGX_QUIC_FT_PATH_RESPONSE:
+
+ /* TODO: handle */
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "frame handler not implemented");
+ ack_this = 1;
+ break;
+
+ default:
+ return NGX_ERROR;
+ }
+ }
+
+ if (p != end) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "trailing garbage in payload: %ui bytes", end - p);
+ return NGX_ERROR;
+ }
+
+ if (do_close) {
+ return NGX_DONE;
+ }
+
+ if (ack_this == 0) {
+ /* do not ack packets with ACKs and PADDING */
+ return NGX_OK;
+ }
+
+ c->log->action = "generating acknowledgment";
+
+ // packet processed, ACK it now if required
+ // TODO: if (ack_required) ... - currently just ack each packet
+
+ ack_frame = ngx_quic_alloc_frame(c, 0);
+ if (ack_frame == NULL) {
+ return NGX_ERROR;
+ }
+
+ ack_frame->level = (pkt->level == ssl_encryption_early_data)
+ ? ssl_encryption_application
+ : pkt->level;
+
+ ack_frame->type = NGX_QUIC_FT_ACK;
+ ack_frame->u.ack.largest = pkt->pn;
+ /* only ack immediate packet ]*/
+ ack_frame->u.ack.first_range = 0;
+
+ ngx_sprintf(ack_frame->info, "ACK for PN=%d from frame handler level=%d", pkt->pn, ack_frame->level);
+ ngx_quic_queue_frame(qc, ack_frame);
+
+ // TODO: call output() after processing some special frames?
+ 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 gap, range;
+ ngx_uint_t i, min, max;
+ ngx_quic_namespace_t *ns;
+
+ ns = &c->quic->ns[ngx_quic_ns(pkt->level)];
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "ngx_quic_handle_ack_frame in namespace %d",
+ ngx_quic_ns(pkt->level));
+
+ /*
+ * TODO: 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) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "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, ns, min, max) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ /* 13.2.3. Receiver Tracking of ACK Frames */
+ if (ns->largest < max) {
+ ns->largest = max;
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "updated largest received: %ui", max);
+ }
+
+ 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 >= min) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "invalid range %ui in ack frame", i);
+ return NGX_ERROR;
+ }
+
+ max = min - 1 - gap;
+
+ if (range > max + 1) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "invalid range %ui in ack frame", i);
+ return NGX_ERROR;
+ }
+
+ min = max - range + 1;
+
+ if (ngx_quic_handle_ack_frame_range(c, ns, min, max) != NGX_OK) {
+ return NGX_ERROR;
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_namespace_t *ns,
+ uint64_t min, uint64_t max)
+{
+ ngx_uint_t found;
+ ngx_queue_t *q, range;
+ ngx_quic_frame_t *f;
+
+ found = 0;
+
+ ngx_queue_init(&range);
+
+ q = ngx_queue_head(&ns->sent);
+
+ while (q != ngx_queue_sentinel(&ns->sent)) {
+
+ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
+
+ if (f->pnum >= min && f->pnum <= max) {
+ q = ngx_queue_next(q);
+ ngx_queue_remove(&f->queue);
+ ngx_quic_free_frame(c, f);
+ found = 1;
+
+ } else {
+ q = ngx_queue_next(q);
+ }
+ }
+
+ if (!found) {
+
+ if (max <= ns->pnum) {
+ /* duplicate ACK or ACK for non-ack-eliciting frame */
+ return NGX_OK;
+ }
+
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "ACK for the packet not in sent queue ");
+ // TODO: handle error properly: PROTOCOL VIOLATION?
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt,
+ ngx_quic_crypto_frame_t *f)
+{
+ int sslerr;
+ ssize_t n;
+ uint64_t *curr_offset;
+ ngx_ssl_conn_t *ssl_conn;
+ ngx_quic_connection_t *qc;
+
+ qc = c->quic;
+
+ curr_offset = &qc->crypto_offset_in[pkt->level];
+
+ if (f->offset != *curr_offset) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "crypto frame with unexpected offset");
+
+ /* TODO: support reordering/buffering of data */
+ return NGX_ERROR;
+ }
+
+ ssl_conn = c->ssl->connection;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "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->len))
+ {
+ ngx_ssl_error(NGX_LOG_INFO, c->log, 0,
+ "SSL_provide_quic_data() failed");
+ return NGX_ERROR;
+ }
+
+ *curr_offset += f->len;
+
+ 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)) {
+ c->quic->state = NGX_QUIC_ST_APPLICATION;
+
+ 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,
+ "handshake completed successfully");
+
+#if (NGX_QUIC_DRAFT_VERSION >= 27)
+ {
+ ngx_quic_frame_t *frame;
+
+ 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);
+ }
+#endif
+
+ /*
+ * 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;
+ }
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "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_stream_frame_t *f)
+{
+ size_t n;
+ ngx_buf_t *b;
+ ngx_event_t *rev;
+ ngx_quic_stream_t *sn;
+ ngx_quic_connection_t *qc;
+
+ qc = c->quic;
+
+ sn = ngx_quic_find_stream(&qc->streams.tree, f->stream_id);
+
+ if (sn) {
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "existing stream");
+ 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, "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);
+ }
+
+ return NGX_OK;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "stream is new");
+
+ n = (f->stream_id & NGX_QUIC_STREAM_UNIDIRECTIONAL)
+ ? qc->tp.initial_max_stream_data_uni
+ : qc->tp.initial_max_stream_data_bidi_remote;
+
+ if (n < NGX_QUIC_STREAM_BUFSIZE) {
+ n = NGX_QUIC_STREAM_BUFSIZE;
+ }
+
+ if (n < f->length) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "no space in stream buffer");
+ return NGX_ERROR;
+ }
+
+ sn = ngx_quic_create_stream(c, f->stream_id, n);
+ if (sn == NULL) {
+ return NGX_ERROR;
+ }
+
+ b = sn->b;
+ 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 ((f->stream_id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) {
+ ngx_quic_handle_max_streams(c);
+ }
+
+ qc->streams.handler(sn->c);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_quic_handle_max_streams(ngx_connection_t *c)
+{
+ ngx_quic_frame_t *frame;
+ ngx_quic_connection_t *qc;
+
+ qc = c->quic;
+ qc->cur_streams++;
+
+ if (qc->cur_streams + NGX_QUIC_STREAMS_INC / 2 < qc->max_streams) {
+ return NGX_OK;
+ }
+
+ frame = ngx_quic_alloc_frame(c, 0);
+ if (frame == NULL) {
+ return NGX_ERROR;
+ }
+
+ qc->max_streams = ngx_max(qc->max_streams + NGX_QUIC_STREAMS_INC,
+ NGX_QUIC_STREAMS_LIMIT);
+
+ frame->level = ssl_encryption_application;
+ frame->type = NGX_QUIC_FT_MAX_STREAMS;
+ frame->u.max_streams.limit = qc->max_streams;
+ frame->u.max_streams.bidi = 1;
+
+ ngx_sprintf(frame->info, "MAX_STREAMS limit:%d bidi:%d level=%d",
+ (int) frame->u.max_streams.limit,
+ (int) frame->u.max_streams.bidi,
+ frame->level);
+
+ ngx_quic_queue_frame(qc, frame);
+
+ 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)
+{
+ ngx_quic_frame_t *frame;
+
+ frame = ngx_quic_alloc_frame(c, 0);
+ if (frame == NULL) {
+ return NGX_ERROR;
+ }
+
+ frame->level = pkt->level;
+ frame->type = NGX_QUIC_FT_MAX_STREAMS;
+ frame->u.max_streams.limit = ngx_max(f->limit * 2, NGX_QUIC_STREAMS_LIMIT);
+ frame->u.max_streams.bidi = f->bidi;
+
+ c->quic->max_streams = frame->u.max_streams.limit;
+
+ ngx_sprintf(frame->info, "MAX_STREAMS limit:%d bidi:%d level=%d",
+ (int) frame->u.max_streams.limit,
+ (int) frame->u.max_streams.bidi,
+ frame->level);
+
+ ngx_quic_queue_frame(c->quic, frame);
+
+ 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;
+ sn = ngx_quic_find_stream(&qc->streams.tree, f->id);
+
+ if (sn == NULL) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "unknown stream id:%uL", f->id);
+ return NGX_ERROR;
+ }
+
+ b = sn->b;
+ n = (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:%d limit:%d level=%d",
+ (int) frame->u.max_stream_data.id,
+ (int) frame->u.max_stream_data.limit,
+ frame->level);
+
+ ngx_quic_queue_frame(c->quic, frame);
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame)
+{
+ ngx_quic_namespace_t *ns;
+
+ ns = &qc->ns[ngx_quic_ns(frame->level)];
+
+ ngx_queue_insert_tail(&ns->frames, &frame->queue);
+
+ /* TODO: check PUSH flag on stream and call output */
+
+ if (!qc->push.timer_set && !qc->closing) {
+ ngx_add_timer(&qc->push, qc->tp.max_ack_delay);
+ }
+}
+
+
+static ngx_int_t
+ngx_quic_output(ngx_connection_t *c)
+{
+ ngx_uint_t i;
+ ngx_quic_namespace_t *ns;
+ ngx_quic_connection_t *qc;
+
+ c->log->action = "sending frames";
+
+ qc = c->quic;
+
+ for (i = 0; i < 3; i++) {
+ ns = &qc->ns[i];
+ if (ngx_quic_output_ns(c, ns, 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);
+ }
+
+ if (!qc->retry.timer_set && !qc->closing) {
+ ngx_add_timer(&qc->retry, qc->tp.max_ack_delay);
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_quic_output_ns(ngx_connection_t *c, ngx_quic_namespace_t *ns,
+ ngx_uint_t nsi)
+{
+ size_t len, hlen, n;
+ ngx_int_t rc;
+ ngx_queue_t *q, range;
+ ngx_quic_frame_t *f;
+ ngx_quic_connection_t *qc;
+
+ qc = c->quic;
+
+ if (ngx_queue_empty(&ns->frames)) {
+ return NGX_OK;
+ }
+
+ hlen = (nsi == 2) ? NGX_QUIC_MAX_SHORT_HEADER
+ : NGX_QUIC_MAX_LONG_HEADER;
+
+ hlen += EVP_GCM_TLS_TAG_LEN;
+
+ q = ngx_queue_head(&ns->frames);
+
+ do {
+ len = 0;
+ ngx_queue_init(&range);
+
+ do {
+ /* process group of frames that fits into packet */
+ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
+
+ n = ngx_quic_create_frame(NULL, f);
+
+ if (len && hlen + len + n > qc->ctp.max_packet_size) {
+ break;
+ }
+
+ q = ngx_queue_next(q);
+
+ f->first = ngx_current_msec;
+
+ ngx_queue_remove(&f->queue);
+ ngx_queue_insert_tail(&range, &f->queue);
+
+ len += n;
+
+ } while (q != ngx_queue_sentinel(&ns->frames));
+
+ rc = ngx_quic_send_frames(c, &range);
+
+ if (rc == NGX_OK) {
+ /*
+ * frames are moved into the sent queue
+ * to wait for ack/be retransmitted
+ */
+ ngx_queue_add(&ns->sent, &range);
+
+ } else if (rc == NGX_DONE) {
+
+ /* no ack is expected for this frames, can free them */
+ ngx_quic_free_frames(c, &range);
+
+ } else {
+ return NGX_ERROR;
+ }
+
+
+ } while (q != ngx_queue_sentinel(&ns->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;
+
+ q = ngx_queue_head(frames);
+
+ do {
+ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
+ q = ngx_queue_next(q);
+
+ ngx_quic_free_frame(c, f);
+
+ } while (q != ngx_queue_sentinel(frames));
+}
+
+
+/* pack a group of frames [start; end) into memory p and send as single packet */
+static ngx_int_t
+ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames)
+{
+ ssize_t len;
+ u_char *p;
+ ngx_msec_t now;
+ ngx_str_t out, res;
+ ngx_queue_t *q;
+ ngx_quic_frame_t *f, *start;
+ ngx_quic_header_t pkt;
+ ngx_quic_secrets_t *keys;
+ ngx_quic_namespace_t *ns;
+ ngx_quic_connection_t *qc;
+ static ngx_str_t initial_token = ngx_null_string;
+ static u_char src[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE];
+ static u_char dst[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE];
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "ngx_quic_send_frames");
+
+ q = ngx_queue_head(frames);
+ start = ngx_queue_data(q, ngx_quic_frame_t, queue);
+
+ ns = &c->quic->ns[ngx_quic_ns(start->level)];
+
+ ngx_memzero(&pkt, sizeof(ngx_quic_header_t));
+
+ 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, "frame: %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 = ns->pnum;
+ }
+
+ if (start->level == ssl_encryption_initial) {
+ /* ack will not be sent in initial packets due to initial keys being
+ * discarded when handshake start.
+ * Thus consider initial packets as non-ack-eliciting
+ */
+ pkt.need_ack = 0;
+ }
+
+ out.len = p - out.data;
+
+ while (out.len < 4) {
+ *p++ = NGX_QUIC_FT_PADDING;
+ out.len++;
+ }
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "packet ready: %ui bytes at level %d need_ack: %ui",
+ out.len, start->level, pkt.need_ack);
+
+ qc = c->quic;
+
+ keys = &c->quic->keys[start->level];
+
+ pkt.secret = &keys->server;
+
+ if (start->level == ssl_encryption_initial) {
+ pkt.flags = NGX_QUIC_PKT_INITIAL;
+ pkt.token = initial_token;
+
+ } else if (start->level == ssl_encryption_handshake) {
+ pkt.flags = NGX_QUIC_PKT_HANDSHAKE;
+
+ } else {
+ // TODO: macro, set FIXED bit
+ pkt.flags = 0x40 | (c->quic->key_phase ? NGX_QUIC_PKT_KPHASE : 0);
+ }
+
+ ngx_quic_set_packet_number(&pkt, ns);
+
+ pkt.log = c->log;
+ pkt.level = start->level;
+ pkt.dcid = qc->dcid;
+ pkt.scid = qc->scid;
+ pkt.payload = out;
+
+ res.data = dst;
+
+ if (ngx_quic_encrypt(&pkt, c->ssl->connection, &res) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ ngx_quic_hexdump0(c->log, "packet to send", res.data, res.len);
+
+ 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 */
+ ns->pnum++;
+
+ now = ngx_current_msec;
+ start->last = now;
+
+ return pkt.need_ack ? NGX_OK : NGX_DONE;
+}
+
+
+static void
+ngx_quic_set_packet_number(ngx_quic_header_t *pkt, ngx_quic_namespace_t *ns)
+{
+ uint64_t delta;
+
+ delta = ns->pnum - ns->largest;
+ pkt->number = ns->pnum;
+
+ if (delta <= 0x7F) {
+ pkt->num_len = 1;
+ pkt->trunc = ns->pnum & 0xff;
+
+ } else if (delta <= 0x7FFF) {
+ pkt->num_len = 2;
+ pkt->flags |= 0x1;
+ pkt->trunc = ns->pnum & 0xffff;
+
+ } else if (delta <= 0x7FFFFF) {
+ pkt->num_len = 3;
+ pkt->flags |= 0x2;
+ pkt->trunc = ns->pnum & 0xffffff;
+
+ } else {
+ pkt->num_len = 4;
+ pkt->flags |= 0x3;
+ pkt->trunc = ns->pnum & 0xffffffff;
+ }
+}
+
+
+static void
+ngx_quic_retransmit_handler(ngx_event_t *ev)
+{
+ ngx_uint_t i;
+ ngx_msec_t wait, nswait;
+ ngx_connection_t *c;
+ ngx_quic_connection_t *qc;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+ "retransmit timer");
+
+ c = ev->data;
+ qc = c->quic;
+
+ wait = 0;
+
+ for (i = 0; i < NGX_QUIC_NAMESPACE_LAST; i++) {
+ if (ngx_quic_retransmit_ns(c, &qc->ns[i], &nswait) != NGX_OK) {
+ ngx_quic_close_connection(c);
+ return;
+ }
+
+ if (i == 0) {
+ wait = nswait;
+
+ } else if (nswait > 0 && nswait < wait) {
+ wait = nswait;
+ }
+ }
+
+ if (wait > 0) {
+ ngx_add_timer(&qc->retry, wait);
+ }
+}
+
+
+static void
+ngx_quic_push_handler(ngx_event_t *ev)
+{
+ ngx_connection_t *c;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "push timer");
+
+ c = ev->data;
+
+ if (ngx_quic_output(c) != NGX_OK) {
+ ngx_quic_close_connection(c);
+ return;
+ }
+}
+
+
+static ngx_int_t
+ngx_quic_retransmit_ns(ngx_connection_t *c, ngx_quic_namespace_t *ns,
+ ngx_msec_t *waitp)
+{
+ uint64_t pn;
+ ngx_msec_t now, wait;
+ ngx_queue_t *q, range;
+ ngx_quic_frame_t *f, *start;
+ ngx_quic_connection_t *qc;
+
+ qc = c->quic;
+
+ now = ngx_current_msec;
+ wait = 0;
+
+ if (ngx_queue_empty(&ns->sent)) {
+ *waitp = 0;
+ return NGX_OK;
+ }
+
+ q = ngx_queue_head(&ns->sent);
+ start = ngx_queue_data(q, ngx_quic_frame_t, queue);
+ pn = start->pnum;
+ f = start;
+
+ do {
+ 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 (start->first + qc->tp.max_idle_timeout < now) {
+ ngx_log_error(NGX_LOG_ERR, c->log, 0,
+ "retransmission timeout");
+ return NGX_DECLINED;
+ }
+
+ 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(&ns->sent));
+
+ wait = start->last + qc->tp.max_ack_delay - now;
+
+ if ((ngx_msec_int_t) wait > 0) {
+ break;
+ }
+
+ /* NGX_DONE is impossible here, such frames don't get into this queue */
+ if (ngx_quic_send_frames(c, &range) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ /* move frames group to the end of queue */
+ ngx_queue_add(&ns->sent, &range);
+
+ } while (q != ngx_queue_sentinel(&ns->sent));
+
+ *waitp = wait;
+
+ return NGX_OK;
+}
+
+
+ngx_connection_t *
+ngx_quic_create_uni_stream(ngx_connection_t *c)
+{
+ ngx_uint_t id;
+ ngx_quic_stream_t *qs, *sn;
+ ngx_quic_connection_t *qc;
+
+ qs = c->qs;
+ qc = qs->parent->quic;
+
+ id = (qc->streams.id_counter << 2)
+ | NGX_QUIC_STREAM_SERVER_INITIATED
+ | NGX_QUIC_STREAM_UNIDIRECTIONAL;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "creating server uni stream #%ui id %ui",
+ qc->streams.id_counter, id);
+
+ qc->streams.id_counter++;
+
+ sn = ngx_quic_create_stream(qs->parent, id, 0);
+ 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_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;
+
+ 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;
+ }
+
+ 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->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 = c->log;
+ sn->c->write->log = c->log;
+
+ 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_quic_stream_t *qs;
+
+ qs = c->qs;
+ b = qs->b;
+ rev = c->read;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic recv: eof:%d, avail:%z",
+ 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_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic recv() not ready");
+ return NGX_AGAIN;
+ }
+
+ len = ngx_min(b->last - b->pos, (ssize_t) size);
+
+ ngx_memcpy(buf, b->pos, len);
+
+ b->pos += len;
+
+ if (b->pos == b->last) {
+ b->pos = b->start;
+ b->last = b->start;
+ rev->ready = rev->pending_eof;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic recv: %z of %uz", len, size);
+
+ return len;
+}
+
+
+static ssize_t
+ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size)
+{
+ 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;
+
+ if (qc->closing) {
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic send: %uz", size);
+
+ frame = ngx_quic_alloc_frame(pc, size);
+ if (frame == NULL) {
+ return 0;
+ }
+
+ ngx_memcpy(frame->data, buf, size);
+
+ 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 = size;
+ frame->u.stream.data = frame->data;
+
+ c->sent += size;
+
+ ngx_sprintf(frame->info, "stream %xi len=%ui level=%d",
+ qs->id, size, frame->level);
+
+ ngx_quic_queue_frame(qc, frame);
+
+ 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_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic stream cleanup");
+
+ ngx_rbtree_delete(&qc->streams.tree, &qs->node);
+
+ if (qc->closing) {
+ ngx_post_event(pc->read, &ngx_posted_events);
+ return;
+ }
+
+ if ((qs->id & 0x03) == NGX_QUIC_STREAM_UNIDIRECTIONAL) {
+ /* do not send fin for client unidirectional streams */
+ return;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic send fin");
+
+ 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 %xi fin=1 level=%d", qs->id, frame->level);
+
+ ngx_quic_queue_frame(qc, frame);
+
+ (void) ngx_quic_output(pc);
+}
+
+
+static ngx_chain_t *
+ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in,
+ off_t limit)
+{
+ size_t len;
+ ssize_t n;
+ ngx_buf_t *b;
+
+ for ( /* void */; in; in = in->next) {
+ b = in->buf;
+
+ if (!ngx_buf_in_memory(b)) {
+ continue;
+ }
+
+ if (ngx_buf_size(b) == 0) {
+ continue;
+ }
+
+ len = b->last - b->pos;
+
+ n = ngx_quic_stream_send(c, b->pos, len);
+
+ if (n == NGX_ERROR) {
+ return NGX_CHAIN_ERROR;
+ }
+
+ if (n == NGX_AGAIN) {
+ return in;
+ }
+
+ if (n != (ssize_t) len) {
+ b->pos += n;
+ return in;
+ }
+ }
+
+ return NULL;
+}
+
+
+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);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "reuse quic frame n:%ui", qc->nframes);
+
+ } 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
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "alloc quic frame n:%ui", qc->nframes);
+ }
+
+ ngx_memzero(frame, sizeof(ngx_quic_frame_t));
+
+ frame->data = p;
+
+ return frame;
+}
+
+
+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);
+ }
+
+ ngx_queue_insert_head(&qc->free_frames, &frame->queue);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "free quic frame n:%ui", qc->nframes);
+}
diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h
new file mode 100644
index 000000000..baf7fb36c
--- /dev/null
+++ b/src/event/ngx_event_quic.h
@@ -0,0 +1,95 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_EVENT_QUIC_H_INCLUDED_
+#define _NGX_EVENT_QUIC_H_INCLUDED_
+
+
+#include <ngx_event_openssl.h>
+
+
+#define NGX_QUIC_DRAFT_VERSION 27
+#define NGX_QUIC_VERSION (0xff000000 + NGX_QUIC_DRAFT_VERSION)
+
+#define NGX_QUIC_MAX_SHORT_HEADER 25
+#define NGX_QUIC_MAX_LONG_HEADER 346
+
+#define NGX_QUIC_DEFAULT_MAX_PACKET_SIZE 65527
+#define NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT 3
+#define NGX_QUIC_DEFAULT_MAX_ACK_DELAY 25
+
+#define NGX_QUIC_STREAM_SERVER_INITIATED 0x01
+#define NGX_QUIC_STREAM_UNIDIRECTIONAL 0x02
+
+#define NGX_QUIC_STREAM_BUFSIZE 16384
+
+
+typedef struct {
+ /* configurable */
+ ngx_msec_t max_idle_timeout;
+ ngx_msec_t max_ack_delay;
+
+ ngx_uint_t max_packet_size;
+ ngx_uint_t initial_max_data;
+ ngx_uint_t initial_max_stream_data_bidi_local;
+ ngx_uint_t initial_max_stream_data_bidi_remote;
+ ngx_uint_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;
+
+ /* TODO */
+ ngx_uint_t original_connection_id;
+ u_char stateless_reset_token[16];
+ void *preferred_address;
+} ngx_quic_tp_t;
+
+
+struct ngx_quic_stream_s {
+ ngx_rbtree_node_t node;
+ ngx_connection_t *parent;
+ ngx_connection_t *c;
+ uint64_t id;
+ ngx_buf_t *b;
+};
+
+
+void ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp,
+ ngx_connection_handler_pt handler);
+ngx_connection_t *ngx_quic_create_uni_stream(ngx_connection_t *c);
+
+
+/********************************* DEBUG *************************************/
+
+#if (NGX_DEBUG)
+
+#define ngx_quic_hexdump(log, fmt, 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_debug(NGX_LOG_DEBUG_EVENT, log, 0, \
+ "%s: " fmt " %*s%s, len: %uz", \
+ __FUNCTION__, __VA_ARGS__, m, buf, \
+ len < 2048 ? "" : "...", len); \
+ } \
+} while (0)
+
+#else
+
+#define ngx_quic_hexdump(log, fmt, data, len, ...)
+
+#endif
+
+#define ngx_quic_hexdump0(log, fmt, data, len) \
+ ngx_quic_hexdump(log, fmt "%s", data, len, "") \
+
+
+#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..86300e68c
--- /dev/null
+++ b/src/event/ngx_event_quic_protection.c
@@ -0,0 +1,1016 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.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);
+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 ssize_t ngx_quic_create_long_packet(ngx_quic_header_t *pkt,
+ ngx_ssl_conn_t *ssl_conn, ngx_str_t *res);
+static ssize_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_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] =
+ "\xc3\xee\xf7\x12\xc7\x2e\xbb\x5a\x11\xa7"
+ "\xd2\x43\x2b\xb4\x63\x65\xbe\xf9\xf5\x02";
+
+ /* 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
+ };
+
+ ngx_quic_hexdump0(pool->log, "salt", salt, sizeof(salt));
+ ngx_quic_hexdump0(pool->log, "initial secret", is, is_len);
+
+ /* 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;
+ }
+
+ ngx_quic_hexdump(pool->log, "%V info", info, info_len, label);
+ ngx_quic_hexdump(pool->log, "%V key", out->data, out->len, label);
+
+ 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_debug(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 ssize_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;
+
+ ngx_quic_hexdump0(pkt->log, "ad", ad.data, ad.len);
+
+ if (ngx_quic_ciphers(ssl_conn, &ciphers, pkt->level) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "ngx_quic_create_long_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);
+
+ ngx_quic_hexdump0(pkt->log, "server_iv", pkt->secret->iv.data, 12);
+ ngx_quic_hexdump0(pkt->log, "nonce", nonce, 12);
+
+ 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;
+ }
+
+ ngx_quic_hexdump0(pkt->log, "sample", sample, 16);
+ ngx_quic_hexdump0(pkt->log, "mask", mask, 5);
+
+ /* 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 ssize_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;
+
+ ngx_quic_hexdump0(pkt->log, "ad", ad.data, ad.len);
+
+ if (ngx_quic_ciphers(ssl_conn, &ciphers, pkt->level) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "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);
+
+ ngx_quic_hexdump0(pkt->log, "server_iv", pkt->secret->iv.data, 12);
+ ngx_quic_hexdump0(pkt->log, "nonce", nonce, 12);
+
+ 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;
+ }
+
+ ngx_quic_hexdump0(pkt->log, "sample", sample, 16);
+ ngx_quic_hexdump0(pkt->log, "mask", mask, 5);
+
+ /* 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 uint64_t
+ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask)
+{
+ u_char *p;
+ uint64_t value;
+
+ p = *pos;
+ value = *p++ ^ *mask++;
+
+ while (--len) {
+ value = (value << 8) + (*p++ ^ *mask++);
+ }
+
+ *pos = p;
+ return value;
+}
+
+
+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);
+}
+
+
+ssize_t
+ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn,
+ ngx_str_t *res)
+{
+ if (pkt->level == ssl_encryption_application) {
+ return ngx_quic_create_short_packet(pkt, ssl_conn, 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)
+{
+ u_char clearflags, *p, *sample;
+ 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;
+
+ ngx_quic_hexdump0(pkt->log, "sample", sample, 16);
+
+ /* header protection */
+
+ if (ngx_quic_tls_hp(pkt->log, ciphers.hp, secret, mask, sample)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ 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]);
+
+ pkt->pn = pn;
+
+ ngx_quic_hexdump0(pkt->log, "mask", mask, 5);
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "quic clear flags: %xi", 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;
+
+ } else {
+ in.len = pkt->data + pkt->len - p;
+ }
+
+ 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);
+
+ ngx_quic_hexdump0(pkt->log, "nonce", nonce, 12);
+ ngx_quic_hexdump0(pkt->log, "ad", ad.data, ad.len);
+
+ pkt->payload.len = in.len - EVP_GCM_TLS_TAG_LEN;
+
+ if (NGX_QUIC_DEFAULT_MAX_PACKET_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);
+
+ ngx_quic_hexdump0(pkt->log, "packet payload",
+ pkt->payload.data, pkt->payload.len);
+
+ return rc;
+}
+
diff --git a/src/event/ngx_event_quic_protection.h b/src/event/ngx_event_quic_protection.h
new file mode 100644
index 000000000..800184182
--- /dev/null
+++ b/src/event/ngx_event_quic_protection.h
@@ -0,0 +1,45 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_
+#define _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_
+
+
+#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);
+
+ssize_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);
+
+
+#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..99c474de9
--- /dev/null
+++ b/src/event/ngx_event_quic_transport.c
@@ -0,0 +1,1749 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.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 u_char *ngx_quic_parse_int_multi(u_char *pos, u_char *end, ...);
+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_uint16(u_char *pos, u_char *end, uint16_t *value); // usage depends on NGX_QUIC_VERSION
+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 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_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_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",
+ "SERVER_BUSY",
+ "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",
+ "",
+ "CRYPTO_BUFFER_EXCEEDED",
+ "",
+ "CRYPTO_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) {
+ printf("OOPS >=\n");
+ return NULL;
+ }
+
+ p = pos;
+ len = 1 << ((*p & 0xc0) >> 6);
+
+ value = *p++ & 0x3f;
+
+ if ((size_t)(end - p) < (len - 1)) {
+ printf("LEN TOO BIG: need %ld have %ld\n", len, end - p);
+ return NULL;
+ }
+
+ while (--len) {
+ value = (value << 8) + *p++;
+ }
+
+ *out = value;
+
+ return p;
+}
+
+
+static ngx_inline u_char *
+ngx_quic_parse_int_multi(u_char *pos, u_char *end, ...)
+{
+ u_char *p;
+ va_list ap;
+ uint64_t *item;
+
+ p = pos;
+
+ va_start(ap, end);
+
+ do {
+ item = va_arg(ap, uint64_t *);
+ if (item == NULL) {
+ break;
+ }
+
+ p = ngx_quic_parse_int(p, end, item);
+ if (p == NULL) {
+ return NULL;
+ }
+
+ } while (1);
+
+ va_end(ap);
+
+ 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_uint16(u_char *pos, u_char *end, uint16_t *value)
+{
+ if ((size_t)(end - pos) < sizeof(uint16_t)) {
+ return NULL;
+ }
+
+ *value = ngx_quic_parse_uint16(pos);
+
+ return pos + sizeof(uint16_t);
+}
+
+
+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 len;//, len2;
+
+ p = *pos;
+ len = 0;
+
+ while (value >> ((1 << len) * 8 - 2)) {
+ len++;
+ }
+
+ *p = len << 6;
+
+// len2 =
+ len = (1 << len);
+ len--;
+ *p |= value >> (len * 8);
+ p++;
+
+ while (len) {
+ *p++ = value >> ((len-- - 1) * 8);
+ }
+
+ *pos = p;
+// return len2;
+}
+
+
+u_char *
+ngx_quic_error_text(uint64_t error_code)
+{
+ 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;
+
+ ngx_quic_hexdump0(pkt->log, "long input", pkt->data, pkt->len);
+
+ p = ngx_quic_read_uint8(p, end, &pkt->flags);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "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, "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,
+ "packet is too small to read version");
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "quic flags:%xi version:%xD", pkt->flags, pkt->version);
+
+ if (pkt->version != NGX_QUIC_VERSION) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "unsupported quic version: 0x%xi", 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,
+ "packet is too small to read dcid len");
+ 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,
+ "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,
+ "packet is too small to read scid len");
+ 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,
+ "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->scid.len;
+ p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len);
+
+ *p++ = pkt->dcid.len;
+ p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.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->scid.data, pkt->scid.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;
+}
+
+
+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;
+
+ ngx_quic_hexdump0(pkt->log, "short input", pkt->data, pkt->len);
+
+ p = ngx_quic_read_uint8(p, end, &pkt->flags);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "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, "not a short packet");
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "quic flags:%xi", pkt->flags);
+
+ 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,
+ "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,
+ "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,
+ "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, "bad packet length");
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "quic packet length: %uL", varint);
+
+ if (varint > (uint64_t) ((pkt->data + pkt->len) - p)) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "truncated initial packet");
+ return NGX_ERROR;
+ }
+
+ pkt->raw->pos = p;
+ pkt->len = varint;
+
+ ngx_quic_hexdump0(pkt->log, "DCID", pkt->dcid.data, pkt->dcid.len);
+ ngx_quic_hexdump0(pkt->log, "SCID", pkt->scid.data, pkt->scid.len);
+ ngx_quic_hexdump0(pkt->log, "token", pkt->token.data, pkt->token.len);
+
+ 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, "bad packet length");
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "quic packet length: %uL", plen);
+
+ if (plen > (uint64_t)((pkt->data + pkt->len) - p)) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "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;
+ uint8_t flags;
+ uint64_t varint;
+ ngx_uint_t i;
+
+ flags = pkt->flags;
+ p = start;
+
+ p = ngx_quic_parse_int(p, end, &varint);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "failed to obtain quic frame type");
+ return NGX_ERROR;
+ }
+
+ f->type = varint;
+
+ switch (f->type) {
+
+ case NGX_QUIC_FT_CRYPTO:
+
+ if (ngx_quic_pkt_zrtt(flags)) {
+ goto not_allowed;
+ }
+
+ p = ngx_quic_parse_int(p, end, &f->u.crypto.offset);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "failed to parse crypto frame offset");
+ return NGX_ERROR;
+ }
+
+ p = ngx_quic_parse_int(p, end, &f->u.crypto.len);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "failed to parse crypto frame len");
+ return NGX_ERROR;
+ }
+
+ p = ngx_quic_read_bytes(p, end, f->u.crypto.len, &f->u.crypto.data);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "failed to parse crypto frame data");
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "quic CRYPTO frame length: %uL off:%uL pp:%p",
+ f->u.crypto.len, f->u.crypto.offset,
+ f->u.crypto.data);
+
+ ngx_quic_hexdump0(pkt->log, "CRYPTO frame contents",
+ f->u.crypto.data, f->u.crypto.len);
+ break;
+
+ case NGX_QUIC_FT_PADDING:
+
+ /* allowed in any packet type */
+
+ while (p < end && *p == NGX_QUIC_FT_PADDING) {
+ p++;
+ }
+
+ break;
+
+ case NGX_QUIC_FT_ACK:
+ case NGX_QUIC_FT_ACK_ECN:
+
+ if (ngx_quic_pkt_zrtt(flags)) {
+ goto not_allowed;
+ }
+
+ p = ngx_quic_parse_int_multi(p, end, &f->u.ack.largest,
+ &f->u.ack.delay, &f->u.ack.range_count,
+ &f->u.ack.first_range, NULL);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "failed to parse ack frame");
+ return NGX_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_multi(p, end, &varint, &varint, NULL);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "failed to parse ack frame range %ui", i);
+ return NGX_ERROR;
+ }
+ }
+
+ f->u.ack.ranges_end = p;
+
+ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "ACK: { largest=%ui delay=%ui count=%ui first=%ui}",
+ 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) {
+
+ p = ngx_quic_parse_int_multi(p, end, &f->u.ack.ect0,
+ &f->u.ack.ect1, &f->u.ack.ce, NULL);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "failed to parse ack frame ECT counts", i);
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "ACK ECN counters: %ui %ui %ui",
+ f->u.ack.ect0, f->u.ack.ect1, f->u.ack.ce);
+ }
+
+ break;
+
+ case NGX_QUIC_FT_PING:
+
+ /* allowed in any packet type */
+
+ break;
+
+ case NGX_QUIC_FT_NEW_CONNECTION_ID:
+
+ if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) {
+ goto not_allowed;
+ }
+
+ p = ngx_quic_parse_int_multi(p, end, &f->u.ncid.seqnum,
+ &f->u.ncid.retire, NULL);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "failed to parse new connection id frame");
+ return NGX_ERROR;
+ }
+
+ p = ngx_quic_read_uint8(p, end, &f->u.ncid.len);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "failed to parse new connection id length");
+ return NGX_ERROR;
+ }
+
+ p = ngx_quic_copy_bytes(p, end, f->u.ncid.len, f->u.ncid.cid);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "failed to parse new connection id cid");
+ return NGX_ERROR;
+ }
+
+ p = ngx_quic_copy_bytes(p, end, 16, f->u.ncid.srt);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "failed to parse new connection id srt");
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "NCID: { seq=%ui retire=%ui len=%ui}",
+ f->u.ncid.seqnum, f->u.ncid.retire, f->u.ncid.len);
+ break;
+
+ case NGX_QUIC_FT_CONNECTION_CLOSE2:
+
+ if (!ngx_quic_short_pkt(flags)) {
+ goto not_allowed;
+ }
+
+ /* fall through */
+
+ case NGX_QUIC_FT_CONNECTION_CLOSE:
+
+ if (ngx_quic_pkt_zrtt(flags)) {
+ goto not_allowed;
+ }
+
+ p = ngx_quic_parse_int(p, end, &f->u.close.error_code);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "failed to parse close connection frame error code");
+ return NGX_ERROR;
+ }
+
+ if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) {
+ p = ngx_quic_parse_int(p, end, &f->u.close.frame_type);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "failed to parse close connection frame type");
+ return NGX_ERROR;
+ }
+ }
+
+ p = ngx_quic_parse_int(p, end, &varint);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "failed to parse close reason length");
+ return NGX_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) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "failed to parse close reason");
+ return NGX_ERROR;
+ }
+
+ if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) {
+
+ if (f->u.close.error_code >= NGX_QUIC_ERR_LAST) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "unkown error code: %ui, truncated",
+ f->u.close.error_code);
+ f->u.close.error_code = NGX_QUIC_ERR_LAST - 1;
+ }
+
+ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "CONN.CLOSE: { %s (0x%xi) type=0x%xi 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,
+ "CONN.CLOSE2: { (0x%xi) 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:
+
+ if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) {
+ goto not_allowed;
+ }
+
+ 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) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "failed to parse stream frame id");
+ return NGX_ERROR;
+ }
+
+ if (f->type & 0x04) {
+ p = ngx_quic_parse_int(p, end, &f->u.stream.offset);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "failed to parse stream frame offset");
+ return NGX_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) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "failed to parse stream frame length");
+ return NGX_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) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "failed to parse stream frame data len=%ui "
+ "offset=%ui", f->u.stream.length, f->u.stream.offset);
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug7(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "STREAM frame { 0x%xi id 0x%xi offset 0x%xi "
+ "len 0x%xi 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);
+
+ ngx_quic_hexdump0(pkt->log, "STREAM frame contents",
+ f->u.stream.data, f->u.stream.length);
+ break;
+
+ case NGX_QUIC_FT_MAX_DATA:
+
+ if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) {
+ goto not_allowed;
+ }
+
+ p = ngx_quic_parse_int(p, end, &f->u.max_data.max_data);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "failed to parse max data frame");
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "MAX_DATA frame { Maximum Data %ui }",
+ f->u.max_data.max_data);
+ break;
+
+ case NGX_QUIC_FT_RESET_STREAM:
+
+ if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) {
+ goto not_allowed;
+ }
+
+ p = ngx_quic_parse_int_multi(p, end, &f->u.reset_stream.id,
+ &f->u.reset_stream.error_code,
+ &f->u.reset_stream.final_size, NULL);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "failed to parse reset stream frame");
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "RESET STREAM frame"
+ " { id 0x%xi error_code 0x%xi final_size 0x%xi }",
+ f->u.reset_stream.id, f->u.reset_stream.error_code,
+ f->u.reset_stream.final_size);
+ break;
+
+ case NGX_QUIC_FT_STOP_SENDING:
+
+ if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) {
+ goto not_allowed;
+ }
+
+ p = ngx_quic_parse_int_multi(p, end, &f->u.stop_sending.id,
+ &f->u.stop_sending.error_code, NULL);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "failed to parse stop sending frame");
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "STOP SENDING frame { id 0x%xi error_code 0x%xi}",
+ f->u.stop_sending.id, f->u.stop_sending.error_code);
+
+ break;
+
+ case NGX_QUIC_FT_STREAMS_BLOCKED:
+ case NGX_QUIC_FT_STREAMS_BLOCKED2:
+
+ if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) {
+ goto not_allowed;
+ }
+
+ p = ngx_quic_parse_int(p, end, &f->u.streams_blocked.limit);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "failed to parse streams blocked frame limit");
+ return NGX_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,
+ "STREAMS BLOCKED frame { limit %ui bidi: %d }",
+ f->u.streams_blocked.limit,
+ f->u.streams_blocked.bidi);
+
+ break;
+
+ case NGX_QUIC_FT_HANDSHAKE_DONE:
+ /* only sent by server, not by client */
+ goto not_allowed;
+
+ case NGX_QUIC_FT_NEW_TOKEN:
+
+ if (!ngx_quic_short_pkt(flags)) {
+ goto not_allowed;
+ }
+
+ /* TODO: implement */
+
+ ngx_log_error(NGX_LOG_ALERT, pkt->log, 0,
+ "unimplemented frame type 0x%xi in packet", f->type);
+
+ break;
+
+ case NGX_QUIC_FT_MAX_STREAMS:
+ case NGX_QUIC_FT_MAX_STREAMS2:
+
+ if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) {
+ goto not_allowed;
+ }
+
+ p = ngx_quic_parse_int(p, end, &f->u.max_streams.limit);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "failed to parse max streams frame limit");
+ return NGX_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,
+ "MAX STREAMS frame { limit %ui bidi: %d }",
+ f->u.max_streams.limit,
+ f->u.max_streams.bidi);
+ break;
+
+ case NGX_QUIC_FT_MAX_STREAM_DATA:
+
+ if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) {
+ goto not_allowed;
+ }
+
+ p = ngx_quic_parse_int_multi(p, end, &f->u.max_stream_data.id,
+ &f->u.max_stream_data.limit, NULL);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "failed to parse max stream data frame");
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "MAX STREAM DATA frame { id: %ui limit: %ui }",
+ f->u.max_stream_data.id,
+ f->u.max_stream_data.limit);
+ break;
+
+ case NGX_QUIC_FT_DATA_BLOCKED:
+
+ if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) {
+ goto not_allowed;
+ }
+
+ p = ngx_quic_parse_int(p, end, &f->u.data_blocked.limit);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "failed to parse data blocked frame limit");
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "DATA BLOCKED frame { limit %ui }",
+ f->u.data_blocked.limit);
+ break;
+
+ case NGX_QUIC_FT_STREAM_DATA_BLOCKED:
+
+ if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) {
+ goto not_allowed;
+ }
+
+ p = ngx_quic_parse_int_multi(p, end, &f->u.stream_data_blocked.id,
+ &f->u.stream_data_blocked.limit, NULL);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "failed to parse tream data blocked frame");
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "STREAM DATA BLOCKED frame { id: %ui limit: %ui }",
+ f->u.stream_data_blocked.id,
+ f->u.stream_data_blocked.limit);
+ break;
+
+ case NGX_QUIC_FT_RETIRE_CONNECTION_ID:
+
+ if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) {
+ goto not_allowed;
+ }
+
+ p = ngx_quic_parse_int(p, end, &f->u.retire_cid.sequence_number);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "failed to parse retire connection id"
+ " frame sequence number");
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "RETIRE CONNECTION ID frame { sequence_number %ui }",
+ f->u.retire_cid.sequence_number);
+ break;
+
+ case NGX_QUIC_FT_PATH_CHALLENGE:
+
+ if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) {
+ goto not_allowed;
+ }
+
+ p = ngx_quic_copy_bytes(p, end, 8, f->u.path_challenge.data);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "failed to get path challenge frame data");
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "PATH CHALLENGE frame");
+
+ ngx_quic_hexdump0(pkt->log, "path challenge data",
+ f->u.path_challenge.data, 8);
+ break;
+
+ case NGX_QUIC_FT_PATH_RESPONSE:
+
+ if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) {
+ goto not_allowed;
+ }
+
+ p = ngx_quic_copy_bytes(p, end, 8, f->u.path_response.data);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "failed to get path response frame data");
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "PATH RESPONSE frame");
+
+ ngx_quic_hexdump0(pkt->log, "path response data",
+ f->u.path_response.data, 8);
+ break;
+
+ default:
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "unknown frame type 0x%xi in packet", f->type);
+
+ return NGX_ERROR;
+ }
+
+ return p - start;
+
+not_allowed:
+
+ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
+ "frame type 0x%xi is not allowed in packet with flags 0x%xi",
+ f->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,
+ "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,
+ "failed to parse ack frame range");
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
+ "ACK range: gap %ui range %ui", *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_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:
+ 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);
+
+ 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(0);
+ 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, 0);
+ 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->len);
+ len += crypto->len;
+
+ 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->len);
+ p = ngx_cpymem(p, crypto->data, crypto->len);
+
+ 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_stream(u_char *p, ngx_quic_stream_frame_t *sf)
+{
+ size_t len;
+ u_char *start;
+
+ if (!sf->len) {
+#if 0
+ ngx_log_error(NGX_LOG_INFO, log, 0,
+ "attempt to generate a stream frame without length");
+#endif
+ // XXX: handle error in caller
+ return NGX_ERROR;
+ }
+
+ 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;
+
+ switch (id) {
+ case NGX_QUIC_TP_ORIGINAL_CONNECTION_ID:
+ case NGX_QUIC_TP_STATELESS_RESET_TOKEN:
+ case NGX_QUIC_TP_PREFERRED_ADDRESS:
+ // TODO
+ return NGX_DECLINED;
+ }
+
+ 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_PACKET_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;
+
+ default:
+ return NGX_DECLINED;
+ }
+
+ switch (id) {
+
+ case NGX_QUIC_TP_MAX_IDLE_TIMEOUT:
+ dst->max_idle_timeout = varint;
+ break;
+
+ case NGX_QUIC_TP_MAX_PACKET_SIZE:
+ dst->max_packet_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;
+
+ 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)
+{
+ ngx_int_t rc;
+
+#if (NGX_QUIC_DRAFT_VERSION < 27)
+
+ uint16_t id, len, tp_len;
+
+ p = ngx_quic_read_uint16(p, end, &tp_len);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, log, 0,
+ "failed to parse total transport params length");
+ return NGX_ERROR;
+ }
+
+ while (p < end) {
+
+ p = ngx_quic_read_uint16(p, end, &id);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, log, 0,
+ "failed to parse transport param id");
+ return NGX_ERROR;
+ }
+
+ p = ngx_quic_read_uint16(p, end, &len);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, log, 0,
+ "failed to parse transport param id 0x%xi 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,
+ "failed to parse transport param id 0x%xi data", id);
+ return NGX_ERROR;
+ }
+
+ if (rc == NGX_DECLINED) {
+ ngx_log_error(NGX_LOG_INFO, log, 0,
+ "unknown transport param id 0x%xi, skipped", id);
+ }
+
+ p += len;
+ };
+
+#else
+
+ uint64_t id, len;
+
+ while (p < end) {
+ p = ngx_quic_parse_int(p, end, &id);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, log, 0,
+ "failed to parse transport param id");
+ return NGX_ERROR;
+ }
+
+ p = ngx_quic_parse_int(p, end, &len);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_INFO, log, 0,
+ "failed to parse transport param id 0x%xi 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,
+ "failed to parse transport param id 0x%xi data", id);
+ return NGX_ERROR;
+ }
+
+ if (rc == NGX_DECLINED) {
+ ngx_log_error(NGX_LOG_INFO, log, 0,
+ "unknown transport param id 0x%xi,skipped", id);
+ }
+
+ p += len;
+
+ }
+
+#endif
+
+ if (p != end) {
+ ngx_log_error(NGX_LOG_INFO, log, 0,
+ "trailing garbage in transport parameters: %ui bytes",
+ end - p);
+ return NGX_ERROR;
+ }
+
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, log, 0,
+ "client transport parameters parsed successfully");
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
+ "disable active migration: %ui",
+ tp->disable_active_migration);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "idle timeout: %ui",
+ tp->max_idle_timeout);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "max packet size: %ui",
+ tp->max_packet_size);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "max data: %ui",
+ tp->initial_max_data);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
+ "max stream data bidi local: %ui",
+ tp->initial_max_stream_data_bidi_local);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
+ "max stream data bidi remote: %ui",
+ tp->initial_max_stream_data_bidi_remote);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "max stream data uni: %ui",
+ tp->initial_max_stream_data_uni);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
+ "initial max streams bidi: %ui",
+ tp->initial_max_streams_bidi);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "initial max streams uni: %ui",
+ tp->initial_max_streams_uni);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "ack delay exponent: %ui",
+ tp->ack_delay_exponent);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "max ack delay: %ui",
+ tp->max_ack_delay);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
+ "active connection id limit: %ui",
+ tp->active_connection_id_limit);
+
+ 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;
+}
+
+
+ssize_t
+ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp)
+{
+ u_char *p;
+ size_t len;
+
+#if (NGX_QUIC_DRAFT_VERSION < 27)
+
+/* older drafts with static transport parameters encoding */
+
+#define ngx_quic_tp_len(id, value) \
+ 4 + ngx_quic_varint_len(value)
+
+#define ngx_quic_tp_vint(id, value) \
+ do { \
+ p = ngx_quic_write_uint16(p, id); \
+ p = ngx_quic_write_uint16(p, ngx_quic_varint_len(value)); \
+ ngx_quic_build_int(&p, value); \
+ } while (0)
+
+#else
+
+/* recent drafts with variable integer transport parameters encoding */
+
+#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)
+
+#endif
+
+ p = pos;
+
+ len = ngx_quic_tp_len(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT,
+ tp->active_connection_id_limit);
+
+ 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 (pos == NULL) {
+#if (NGX_QUIC_DRAFT_VERSION < 27)
+ len += 2;
+#endif
+ return len;
+ }
+
+#if (NGX_QUIC_DRAFT_VERSION < 27)
+ /* TLS extension length */
+ p = ngx_quic_write_uint16(p, len);
+#endif
+
+ ngx_quic_tp_vint(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT,
+ tp->active_connection_id_limit);
+
+ 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_hexdump0(ngx_cycle->log, "transport parameters", pos, p - pos);
+
+ 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;
+
+ if (p == NULL) {
+ len = ngx_quic_varint_len(NGX_QUIC_FT_CONNECTION_CLOSE);
+ len += ngx_quic_varint_len(cl->error_code);
+ 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, NGX_QUIC_FT_CONNECTION_CLOSE);
+ ngx_quic_build_int(&p, cl->error_code);
+ 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..c44db4144
--- /dev/null
+++ b/src/event/ngx_event_quic_transport.h
@@ -0,0 +1,298 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_EVENT_QUIC_WIRE_H_INCLUDED_
+#define _NGX_EVENT_QUIC_WIRE_H_INCLUDED_
+
+
+#include <ngx_event_openssl.h>
+
+
+#define ngx_quic_long_pkt(flags) ((flags) & 0x80) /* 17.2 */
+#define ngx_quic_short_pkt(flags) (((flags) & 0x80) == 0) /* 17.3 */
+
+/* Long packet types */
+#define NGX_QUIC_PKT_INITIAL 0xC0 /* 17.2.2 */
+#define NGX_QUIC_PKT_ZRTT 0xD0 /* 17.2.3 */
+#define NGX_QUIC_PKT_HANDSHAKE 0xE0 /* 17.2.4 */
+#define NGX_QUIC_PKT_RETRY 0xF0 /* 17.2.5 */
+#define NGX_QUIC_PKT_KPHASE 0x04 /* 17.3 */
+
+#define ngx_quic_pkt_in(flags) (((flags) & 0xF0) == NGX_QUIC_PKT_INITIAL)
+#define ngx_quic_pkt_zrtt(flags) (((flags) & 0xF0) == NGX_QUIC_PKT_ZRTT)
+#define ngx_quic_pkt_hs(flags) (((flags) & 0xF0) == NGX_QUIC_PKT_HANDSHAKE)
+#define ngx_quic_pkt_retry(flags) (((flags) & 0xF0) == 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_CLOSE2 0x1D
+#define NGX_QUIC_FT_HANDSHAKE_DONE 0x1E
+
+/* 22.4. QUIC Transport Error Codes Registry */
+#define NGX_QUIC_ERR_NO_ERROR 0x00
+#define NGX_QUIC_ERR_INTERNAL_ERROR 0x01
+#define NGX_QUIC_ERR_SERVER_BUSY 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
+/* 0xC is not defined */
+#define NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED 0x0D
+/* 0xE is not defined */
+#define NGX_QUIC_ERR_CRYPTO_ERROR 0x10
+
+#define NGX_QUIC_ERR_LAST NGX_QUIC_ERR_CRYPTO_ERROR
+
+/* Transport parameters */
+#define NGX_QUIC_TP_ORIGINAL_CONNECTION_ID 0x00
+#define NGX_QUIC_TP_MAX_IDLE_TIMEOUT 0x01
+#define NGX_QUIC_TP_STATELESS_RESET_TOKEN 0x02
+#define NGX_QUIC_TP_MAX_PACKET_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
+
+
+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 offset;
+ uint64_t len;
+ u_char *data;
+} ngx_quic_crypto_frame_t;
+
+
+typedef struct {
+ uint64_t seqnum;
+ uint64_t retire;
+ uint8_t len;
+ u_char cid[20];
+ u_char srt[16];
+} ngx_quic_new_conn_id_frame_t;
+
+
+typedef struct {
+ uint8_t type;
+ uint64_t stream_id;
+ uint64_t offset;
+ uint64_t length;
+ unsigned off:1;
+ unsigned len:1;
+ unsigned fin:1;
+ u_char *data;
+} 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_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;
+ ngx_msec_t first;
+ ngx_msec_t last;
+ 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_new_conn_id_frame_t ncid;
+ 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;
+ 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;
+
+ /* filled in by parser */
+ ngx_buf_t *raw; /* udp datagram */
+
+ u_char *data; /* quic packet */
+ size_t len;
+
+ /* cleartext fields */
+ 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);
+
+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);
+
+#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_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c
index 495e628d3..a48d3b924 100644
--- a/src/http/modules/ngx_http_ssl_module.c
+++ b/src/http/modules/ngx_http_ssl_module.c
@@ -371,7 +371,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_V3)
ngx_http_connection_t *hc;
#endif
#if (NGX_HTTP_V2 || NGX_DEBUG)
@@ -388,9 +388,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_V3)
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;
@@ -398,6 +400,12 @@ 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
{
srv = (unsigned char *) NGX_HTTP_NPN_ADVERTISE;
srvlen = sizeof(NGX_HTTP_NPN_ADVERTISE) - 1;
@@ -1142,7 +1150,7 @@ 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.http3) {
continue;
}
@@ -1156,6 +1164,14 @@ ngx_http_ssl_init(ngx_conf_t *cf)
cscf->file_name, cscf->line);
return NGX_ERROR;
}
+
+ if (addr[a].opt.http3 && !(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 ... http3\" directive in %s:%ui",
+ cscf->file_name, cscf->line);
+ return NGX_ERROR;
+ }
}
}
diff --git a/src/http/ngx_http.c b/src/http/ngx_http.c
index 79ef9c644..10f88fabb 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;
@@ -1199,6 +1203,9 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
#if (NGX_HTTP_V2)
ngx_uint_t http2;
#endif
+#if (NGX_HTTP_SSL)
+ ngx_uint_t http3;
+#endif
/*
* we cannot compare whole sockaddr struct's as kernel
@@ -1234,6 +1241,9 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
#if (NGX_HTTP_V2)
http2 = lsopt->http2 || addr[i].opt.http2;
#endif
+#if (NGX_HTTP_SSL)
+ http3 = lsopt->http3 || addr[i].opt.http3;
+#endif
if (lsopt->set) {
@@ -1270,6 +1280,9 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
#if (NGX_HTTP_V2)
addr[i].opt.http2 = http2;
#endif
+#if (NGX_HTTP_SSL)
+ addr[i].opt.http3 = http3;
+#endif
return NGX_OK;
}
@@ -1313,6 +1326,17 @@ ngx_http_add_address(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
#endif
+#if (NGX_HTTP_SSL && !defined NGX_OPENSSL_QUIC)
+
+ if (lsopt->http3) {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "nginx was built with OpenSSL that lacks QUIC "
+ "support, HTTP/3 is not enabled for %V",
+ &lsopt->addr_text);
+ }
+
+#endif
+
addr = ngx_array_push(&port->addrs);
if (addr == NULL) {
return NGX_ERROR;
@@ -1735,6 +1759,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;
@@ -1802,6 +1827,9 @@ ngx_http_add_addrs(ngx_conf_t *cf, ngx_http_port_t *hport,
#if (NGX_HTTP_V2)
addrs[i].conf.http2 = addr[i].opt.http2;
#endif
+#if (NGX_HTTP_SSL)
+ 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
@@ -1867,6 +1895,9 @@ ngx_http_add_addrs6(ngx_conf_t *cf, ngx_http_port_t *hport,
#if (NGX_HTTP_V2)
addrs6[i].conf.http2 = addr[i].opt.http2;
#endif
+#if (NGX_HTTP_SSL)
+ 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..a0946c95a 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
@@ -60,6 +63,9 @@ struct ngx_http_chunked_s {
ngx_uint_t state;
off_t size;
off_t length;
+#if (NGX_HTTP_V3)
+ void *h3_parse;
+#endif
};
diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c
index 3671558d8..9bb89ee37 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,19 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
#endif
}
+ if (ngx_strcmp(value[n].data, "http3") == 0) {
+#if (NGX_HTTP_V3)
+ 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\": "
diff --git a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h
index 2aadae7ff..e7c117c9e 100644
--- a/src/http/ngx_http_core_module.h
+++ b/src/http/ngx_http_core_module.h
@@ -75,6 +75,7 @@ typedef struct {
unsigned wildcard:1;
unsigned ssl:1;
unsigned http2:1;
+ unsigned http3:1;
#if (NGX_HAVE_INET6)
unsigned ipv6only:1;
#endif
@@ -86,6 +87,7 @@ typedef struct {
int backlog;
int rcvbuf;
int sndbuf;
+ int type;
#if (NGX_HAVE_SETFIB)
int setfib;
#endif
@@ -237,6 +239,7 @@ struct ngx_http_addr_conf_s {
unsigned ssl:1;
unsigned http2:1;
+ unsigned http3:1;
unsigned proxy_protocol:1;
};
@@ -266,6 +269,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..92bcf12ad 100644
--- a/src/http/ngx_http_parse.c
+++ b/src/http/ngx_http_parse.c
@@ -144,6 +144,7 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b)
/* HTTP methods: GET, HEAD, POST */
case sw_start:
r->request_start = p;
+ r->method_start = p;
if (ch == CR || ch == LF) {
break;
@@ -158,7 +159,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) {
@@ -2184,6 +2185,12 @@ ngx_http_parse_chunked(ngx_http_request_t *r, ngx_buf_t *b,
sw_trailer_header_almost_done
} state;
+#if (NGX_HTTP_V3)
+ if (r->http_version == NGX_HTTP_VERSION_30) {
+ return ngx_http_v3_parse_request_body(r, b, ctx);
+ }
+#endif
+
state = ctx->state;
if (state == sw_chunk_data && ctx->size == 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 eb53996b1..082938e00 100644
--- a/src/http/ngx_http_request.c
+++ b/src/http/ngx_http_request.c
@@ -64,6 +64,9 @@ static void ngx_http_ssl_handshake(ngx_event_t *rev);
static void ngx_http_ssl_handshake_handler(ngx_connection_t *c);
#endif
+#if (NGX_HTTP_V3)
+static void ngx_http_quic_stream_handler(ngx_connection_t *c);
+#endif
static char *ngx_http_client_errors[] = {
@@ -218,7 +221,16 @@ ngx_http_init_connection(ngx_connection_t *c)
ngx_http_in6_addr_t *addr6;
#endif
- hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t));
+#if (NGX_HTTP_V3)
+ if (c->type == SOCK_DGRAM) {
+ hc = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_connection_t));
+ hc->quic = 1;
+ hc->ssl = 1;
+
+ } else
+#endif
+ hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t));
+
if (hc == NULL) {
ngx_http_close_connection(c);
return;
@@ -324,6 +336,23 @@ ngx_http_init_connection(ngx_connection_t *c)
rev->handler = ngx_http_wait_request_handler;
c->write->handler = ngx_http_empty_handler;
+ if (c->shared) {
+ rev->ready = 1;
+ }
+
+#if (NGX_HTTP_V3)
+ if (hc->quic) {
+ ngx_http_v3_srv_conf_t *v3cf;
+ ngx_http_ssl_srv_conf_t *sscf;
+
+ v3cf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module);
+ sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module);
+
+ ngx_quic_run(c, &sscf->ssl, &v3cf->quic, ngx_http_quic_stream_handler);
+ return;
+ }
+#endif
+
#if (NGX_HTTP_V2)
if (hc->addr_conf->http2) {
rev->handler = ngx_http_v2_init;
@@ -371,6 +400,64 @@ ngx_http_init_connection(ngx_connection_t *c)
}
+#if (NGX_HTTP_V3)
+
+static void
+ngx_http_quic_stream_handler(ngx_connection_t *c)
+{
+ ngx_event_t *rev;
+ ngx_connection_t *pc;
+ ngx_http_log_ctx_t *ctx;
+ ngx_http_connection_t *hc;
+ ngx_http_v3_connection_t *h3c;
+
+ pc = c->qs->parent;
+ h3c = pc->data;
+
+ if (c->qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) {
+ ngx_http_v3_handle_client_uni_stream(c);
+ return;
+ }
+
+ hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t));
+ if (hc == NULL) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ 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) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ ctx->connection = c;
+ ctx->request = NULL;
+ ctx->current_request = NULL;
+
+ c->log->connection = c->number;
+ c->log->handler = ngx_http_log_error;
+ c->log->data = ctx;
+ c->log->action = "waiting for request";
+
+ c->log_error = NGX_ERROR_INFO;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 new stream id:0x%uXL", c->qs->id);
+
+ rev = c->read;
+ rev->handler = ngx_http_wait_request_handler;
+ c->write->handler = ngx_http_empty_handler;
+
+ rev->handler(rev);
+}
+
+#endif
+
+
static void
ngx_http_wait_request_handler(ngx_event_t *rev)
{
@@ -619,6 +706,13 @@ 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->quic) {
+ r->http_version = NGX_HTTP_VERSION_30;
+ r->headers_in.chunked = 1;
+ }
+#endif
+
r->headers_in.content_length_n = -1;
r->headers_in.keep_alive_n = -1;
r->headers_out.content_length_n = -1;
@@ -1068,7 +1162,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_header(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,13 +1179,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->request_start; /* XXX */
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;
+ r->method_name.len = r->method_end - r->method_start;
+ r->method_name.data = r->method_start;
if (r->http_protocol.data) {
r->http_protocol.len = r->request_end - r->http_protocol.data;
@@ -1153,6 +1256,15 @@ ngx_http_process_request_line(ngx_event_t *rev)
break;
}
+ if (rc == NGX_DONE) {
+ 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 */
@@ -1343,7 +1455,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 ( ;; ) {
@@ -1397,11 +1509,21 @@ 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);
+ break;
+#endif
+
+ default: /* HTTP/1.x */
+ rc = ngx_http_parse_header_line(r, r->header_in,
+ cscf->underscores_in_headers);
+ }
if (rc == NGX_OK) {
+ /* XXX */
r->request_length += r->header_in->pos - r->header_name_start;
if (r->invalid_header && cscf->ignore_invalid_headers) {
@@ -1427,11 +1549,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 +1595,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->header_name_start; /* XXX */
r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE;
@@ -1582,7 +1710,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 = request_line ? r->request_start : r->header_name_start; /* XXX */
cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
@@ -1661,45 +1789,59 @@ ngx_http_alloc_large_header_buffer(ngx_http_request_t *r,
r->request_end = new + (r->request_end - old);
}
- r->method_end = new + (r->method_end - old);
+ if (r->method_start >= old && r->method_start < r->header_in->pos) {
+ r->method_start = new + (r->method_start - old);
+ r->method_end = new + (r->method_end - old);
+ }
- r->uri_start = new + (r->uri_start - old);
- r->uri_end = new + (r->uri_end - old);
+ if (r->uri_start >= old && r->uri_start < r->header_in->pos) {
+ r->uri_start = new + (r->uri_start - old);
+ r->uri_end = new + (r->uri_end - old);
+ }
- if (r->schema_start) {
+ if (r->schema_start >= old && r->schema_start < r->header_in->pos) {
r->schema_start = new + (r->schema_start - old);
r->schema_end = new + (r->schema_end - old);
}
- if (r->host_start) {
+ if (r->host_start >= old && r->host_start < r->header_in->pos) {
r->host_start = new + (r->host_start - old);
if (r->host_end) {
r->host_end = new + (r->host_end - old);
}
}
- if (r->port_start) {
+ if (r->port_start >= old && r->port_start < r->header_in->pos) {
r->port_start = new + (r->port_start - old);
r->port_end = new + (r->port_end - old);
}
- if (r->uri_ext) {
+ if (r->uri_ext >= old && r->uri_ext < r->header_in->pos) {
r->uri_ext = new + (r->uri_ext - old);
}
- if (r->args_start) {
+ if (r->args_start >= old && r->args_start < r->header_in->pos) {
r->args_start = new + (r->args_start - old);
}
- if (r->http_protocol.data) {
+ if (r->http_protocol.data >= old
+ && r->http_protocol.data < r->header_in->pos)
+ {
r->http_protocol.data = new + (r->http_protocol.data - old);
}
} else {
- r->header_name_start = new;
- r->header_name_end = new + (r->header_name_end - old);
- r->header_start = new + (r->header_start - old);
- r->header_end = new + (r->header_end - old);
+ if (r->header_name_start >= old
+ && r->header_name_start < r->header_in->pos)
+ {
+ r->header_name_start = new;
+ r->header_name_end = new + (r->header_name_end - old);
+ }
+
+ if (r->header_start >= old && r->header_start < r->header_in->pos) {
+ r->header_start = new + (r->header_start - old);
+ r->header_end = new + (r->header_end - old);
+ }
}
r->header_in = b;
@@ -1924,7 +2066,7 @@ 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);
diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h
index 70c2d424d..772d53b2d 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
@@ -323,6 +324,7 @@ typedef struct {
ngx_chain_t *free;
unsigned ssl:1;
+ unsigned quic:1;
unsigned proxy_protocol:1;
} ngx_http_connection_t;
@@ -583,6 +585,7 @@ struct ngx_http_request_s {
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 c4f092e59..b07d8562f 100644
--- a/src/http/ngx_http_request_body.c
+++ b/src/http/ngx_http_request_body.c
@@ -343,11 +343,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;
}
@@ -355,7 +354,7 @@ ngx_http_do_read_client_request_body(ngx_http_request_t *r)
rb->buf->last += n;
r->request_length += n;
- if (n == rest) {
+ if (n == rest || n == 0) {
/* pass buffer to request body filter chain */
out.buf = rb->buf;
@@ -805,11 +804,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;
}
@@ -914,6 +909,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;
}
@@ -950,7 +950,16 @@ ngx_http_request_body_chunked_filter(ngx_http_request_t *r, ngx_chain_t *in)
}
r->headers_in.content_length_n = 0;
- rb->rest = 3;
+
+#if (NGX_HTTP_V3)
+ if (r->http_version == NGX_HTTP_VERSION_30) {
+ rb->rest = 1;
+
+ } else
+#endif
+ {
+ rb->rest = 3;
+ }
}
out = NULL;
diff --git a/src/http/v3/ngx_http_v3.c b/src/http/v3/ngx_http_v3.c
new file mode 100644
index 000000000..cca84dbc1
--- /dev/null
+++ b/src/http/v3/ngx_http_v3.c
@@ -0,0 +1,96 @@
+
+/*
+ * 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 3;
+ }
+
+ *p++ = 0x80 | (value >> 16);
+ *p++ = (value >> 8);
+ *p++ = value;
+ return (uintptr_t) p;
+
+ }
+
+ if (p == NULL) {
+ return 4;
+ }
+
+ *p++ = 0xc0 | (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;
+
+ for (n = 10; n > 1; n--) {
+ if (value >> (7 * (n - 1))) {
+ break;
+ }
+ }
+
+ if (p == NULL) {
+ return n + 1;
+ }
+
+ *p++ |= thresh;
+
+ for ( /* void */ ; n > 1; n--) {
+ *p++ = 0x80 | (value >> 7 * (n - 1));
+ }
+
+ *p++ = value & 0x7f;
+
+ return (uintptr_t) p;
+}
diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h
new file mode 100644
index 000000000..3f35e985e
--- /dev/null
+++ b/src/http/v3/ngx_http_v3.h
@@ -0,0 +1,116 @@
+
+/*
+ * 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
+
+
+typedef struct {
+ ngx_quic_tp_t quic;
+} ngx_http_v3_srv_conf_t;
+
+
+typedef struct {
+ ngx_http_connection_t hc;
+
+ ngx_array_t *dynamic;
+ ngx_connection_t *known_streams[NGX_HTTP_V3_MAX_KNOWN_STREAM];
+} ngx_http_v3_connection_t;
+
+
+typedef struct {
+ ngx_str_t name;
+ ngx_str_t value;
+} ngx_http_v3_header_t;
+
+
+ngx_int_t ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b);
+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);
+
+void ngx_http_v3_handle_client_uni_stream(ngx_connection_t *c);
+
+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_http_v3_header_t *ngx_http_v3_lookup_table(ngx_connection_t *c,
+ ngx_uint_t dynamic, ngx_uint_t index);
+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_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_module.c b/src/http/v3/ngx_http_v3_module.c
new file mode 100644
index 000000000..279210337
--- /dev/null
+++ b/src/http/v3/ngx_http_v3_module.c
@@ -0,0 +1,302 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ * Copyright (C) Roman Arutyunyan
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static ngx_command_t ngx_http_v3_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_http_v3_srv_conf_t, quic.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_http_v3_srv_conf_t, quic.max_ack_delay),
+ NULL },
+
+ { ngx_string("quic_max_packet_size"),
+ 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, quic.max_packet_size),
+ NULL },
+
+ { ngx_string("quic_initial_max_data"),
+ 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, quic.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_num_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_v3_srv_conf_t, quic.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_num_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_v3_srv_conf_t, quic.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_num_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_v3_srv_conf_t, quic.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_http_v3_srv_conf_t, quic.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_http_v3_srv_conf_t, quic.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_http_v3_srv_conf_t, quic.ack_delay_exponent),
+ NULL },
+
+ { 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_http_v3_srv_conf_t, quic.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_http_v3_srv_conf_t, quic.active_connection_id_limit),
+ NULL },
+
+ ngx_null_command
+};
+
+
+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_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 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 */
+
+ NULL, /* create location configuration */
+ NULL /* 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("quic"), NULL, ngx_http_variable_quic,
+ 0, 0, 0 },
+
+ { ngx_string("http3"), NULL, ngx_http_variable_http3,
+ 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_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 *v3cf;
+
+ v3cf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v3_srv_conf_t));
+ if (v3cf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ * v3cf->quic.original_connection_id = 0;
+ * v3cf->quic.stateless_reset_token = { 0 }
+ * conf->quic.preferred_address = NULL
+ */
+
+ v3cf->quic.max_idle_timeout = NGX_CONF_UNSET_MSEC;
+ v3cf->quic.max_ack_delay = NGX_CONF_UNSET_MSEC;
+
+ v3cf->quic.max_packet_size = NGX_CONF_UNSET_UINT;
+ v3cf->quic.initial_max_data = NGX_CONF_UNSET_UINT;
+ v3cf->quic.initial_max_stream_data_bidi_local = NGX_CONF_UNSET_UINT;
+ v3cf->quic.initial_max_stream_data_bidi_remote = NGX_CONF_UNSET_UINT;
+ v3cf->quic.initial_max_stream_data_uni = NGX_CONF_UNSET_UINT;
+ v3cf->quic.initial_max_streams_bidi = NGX_CONF_UNSET_UINT;
+ v3cf->quic.initial_max_streams_uni = NGX_CONF_UNSET_UINT;
+ v3cf->quic.ack_delay_exponent = NGX_CONF_UNSET_UINT;
+ v3cf->quic.disable_active_migration = NGX_CONF_UNSET_UINT;
+ v3cf->quic.active_connection_id_limit = NGX_CONF_UNSET_UINT;
+
+ return v3cf;
+}
+
+
+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_msec_value(conf->quic.max_idle_timeout,
+ prev->quic.max_idle_timeout, 60000);
+
+ // > 2 ^ 14 is invalid
+ ngx_conf_merge_msec_value(conf->quic.max_ack_delay,
+ prev->quic.max_ack_delay,
+ NGX_QUIC_DEFAULT_MAX_ACK_DELAY);
+
+ // < 1200 is invalid
+ ngx_conf_merge_uint_value(conf->quic.max_packet_size,
+ prev->quic.max_packet_size,
+ NGX_QUIC_DEFAULT_MAX_PACKET_SIZE);
+
+ ngx_conf_merge_uint_value(conf->quic.initial_max_data,
+ prev->quic.initial_max_data, 10000000);
+
+ ngx_conf_merge_uint_value(conf->quic.initial_max_stream_data_bidi_local,
+ prev->quic.initial_max_stream_data_bidi_local,
+ 255);
+
+ ngx_conf_merge_uint_value(conf->quic.initial_max_stream_data_bidi_remote,
+ prev->quic.initial_max_stream_data_bidi_remote,
+ 255);
+
+ ngx_conf_merge_uint_value(conf->quic.initial_max_stream_data_uni,
+ prev->quic.initial_max_stream_data_uni, 255);
+
+ ngx_conf_merge_uint_value(conf->quic.initial_max_streams_bidi,
+ prev->quic.initial_max_streams_bidi, 16);
+
+ ngx_conf_merge_uint_value(conf->quic.initial_max_streams_uni,
+ prev->quic.initial_max_streams_uni, 16);
+
+ // > 20 is invalid
+ ngx_conf_merge_uint_value(conf->quic.ack_delay_exponent,
+ prev->quic.ack_delay_exponent,
+ NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT);
+
+ ngx_conf_merge_uint_value(conf->quic.disable_active_migration,
+ prev->quic.disable_active_migration, 1);
+
+ // < 2 is invalid
+ ngx_conf_merge_uint_value(conf->quic.active_connection_id_limit,
+ prev->quic.active_connection_id_limit, 2);
+
+ 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..3be3802ed
--- /dev/null
+++ b/src/http/v3/ngx_http_v3_parse.c
@@ -0,0 +1,1481 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+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)
+{
+ enum {
+ sw_start = 0,
+ sw_value
+ };
+
+ switch (st->state) {
+
+ case sw_start:
+
+ st->mask = (1 << prefix) - 1;
+ st->value = (ch & st->mask);
+
+ if (st->value != st->mask) {
+ goto done;
+ }
+
+ st->value = 0;
+ st->state = sw_value;
+ break;
+
+ case sw_value:
+
+ st->value = (st->value << 7) + (ch & 0x7f);
+ if (ch & 0x80) {
+ break;
+ }
+
+ st->value += st->mask;
+ 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_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_ERROR;
+ }
+
+ st->state = sw_length;
+ break;
+
+ case sw_length:
+
+ if (ngx_http_v3_parse_varlen_int(c, &st->vlint, ch) != NGX_DONE) {
+ break;
+ }
+
+ 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_ERROR;
+ }
+
+ rc = ngx_http_v3_parse_header_block_prefix(c, &st->prefix, ch);
+
+ if (rc == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (rc != NGX_DONE) {
+ break;
+ }
+
+ if (st->length == 0) {
+ return NGX_ERROR;
+ }
+
+ st->state = sw_header_rep;
+ break;
+
+ case sw_header_rep:
+
+ rc = ngx_http_v3_parse_header_rep(c, &st->header_rep, st->prefix.base,
+ ch);
+
+ if (rc == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (--st->length == 0) {
+ if (rc != NGX_DONE) {
+ return NGX_ERROR;
+ }
+
+ goto done;
+ }
+
+ if (rc == NGX_DONE) {
+ return NGX_OK;
+ }
+
+ break;
+ }
+
+ return NGX_AGAIN;
+
+done:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse headers done");
+
+ 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)
+{
+ 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:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 8, ch) != NGX_DONE) {
+ break;
+ }
+
+ 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:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch) != NGX_DONE) {
+ break;
+ }
+
+ st->delta_base = st->pint.value;
+ goto done;
+ }
+
+ return NGX_AGAIN;
+
+done:
+
+ 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 "
+ "i:%ui, s:%ui, d:%ui, base:%uL",
+ 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_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (rc == NGX_AGAIN) {
+ return NGX_AGAIN;
+ }
+
+ /* rc == NGX_DONE */
+
+ 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;
+ 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;
+
+ 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_http_v3_header_t *h;
+ 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:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 6, ch) != NGX_DONE) {
+ break;
+ }
+
+ 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;
+ }
+
+ h = ngx_http_v3_lookup_table(c, st->dynamic, st->index);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ st->name = h->name;
+ st->value = h->value;
+ 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;
+ ngx_http_v3_header_t *h;
+ 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:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 4, ch) != NGX_DONE) {
+ break;
+ }
+
+ 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:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch) != NGX_DONE) {
+ break;
+ }
+
+ 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_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (rc == NGX_DONE) {
+ st->value = st->literal.value;
+ goto done;
+ }
+
+ break;
+ }
+
+ 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;
+ }
+
+ h = ngx_http_v3_lookup_table(c, st->dynamic, st->index);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ st->name = h->name;
+ 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:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 3, ch) != NGX_DONE) {
+ break;
+ }
+
+ 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_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (rc == NGX_DONE) {
+ 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:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch) != NGX_DONE) {
+ break;
+ }
+
+ 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_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (rc == NGX_DONE) {
+ st->value = st->literal.value;
+ goto done;
+ }
+
+ break;
+ }
+
+ 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_http_v3_header_t *h;
+ 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:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 4, ch) != NGX_DONE) {
+ break;
+ }
+
+ 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);
+
+ h = ngx_http_v3_lookup_table(c, 1, st->base + st->index);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ st->name = h->name;
+ st->value = h->value;
+ 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;
+ ngx_http_v3_header_t *h;
+ 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:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 3, ch) != NGX_DONE) {
+ break;
+ }
+
+ 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:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch) != NGX_DONE) {
+ break;
+ }
+
+ 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_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (rc == NGX_DONE) {
+ st->value = st->literal.value;
+ goto done;
+ }
+
+ break;
+ }
+
+ 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);
+
+ h = ngx_http_v3_lookup_table(c, 1, st->base + st->index);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ st->name = h->name;
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+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_type,
+ sw_length,
+ 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_type;
+
+ /* fall through */
+
+ case sw_type:
+
+ if (ngx_http_v3_parse_varlen_int(c, &st->vlint, ch) != NGX_DONE) {
+ break;
+ }
+
+ st->type = st->vlint.value;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse frame type:%ui", st->type);
+
+ st->state = sw_length;
+ break;
+
+ case sw_length:
+
+ if (ngx_http_v3_parse_varlen_int(c, &st->vlint, ch) != NGX_DONE) {
+ break;
+ }
+
+ 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_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_settings:
+
+ rc = ngx_http_v3_parse_settings(c, &st->settings, ch);
+
+ if (rc == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (--st->length > 0) {
+ break;
+ }
+
+ if (rc != NGX_DONE) {
+ return NGX_ERROR;
+ }
+
+ st->state = sw_type;
+ break;
+
+ case sw_max_push_id:
+
+ if (ngx_http_v3_parse_varlen_int(c, &st->vlint, ch) != NGX_DONE) {
+ break;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse MAX_PUSH_ID:%uL", st->vlint.value);
+
+ 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)
+{
+ 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:
+
+ if (ngx_http_v3_parse_varlen_int(c, &st->vlint, ch) != NGX_DONE) {
+ break;
+ }
+
+ st->id = st->vlint.value;
+ st->state = sw_value;
+ break;
+
+ case sw_value:
+
+ if (ngx_http_v3_parse_varlen_int(c, &st->vlint, ch) != NGX_DONE) {
+ break;
+ }
+
+ if (ngx_http_v3_set_param(c, st->id, st->vlint.value) != NGX_OK) {
+ return NGX_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_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (rc != NGX_DONE) {
+ break;
+ }
+
+ goto done;
+
+ case sw_iwnr:
+
+ rc = ngx_http_v3_parse_header_iwnr(c, &st->header, ch);
+
+ if (rc == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (rc != NGX_DONE) {
+ break;
+ }
+
+ goto done;
+
+ case sw_capacity:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 5, ch) != NGX_DONE) {
+ break;
+ }
+
+ if (ngx_http_v3_set_capacity(c, st->pint.value) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ goto done;
+
+ case sw_duplicate:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 5, ch) != NGX_DONE) {
+ break;
+ }
+
+ if (ngx_http_v3_duplicate(c, st->pint.value) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ 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_DONE;
+}
+
+
+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:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 6, ch) != NGX_DONE) {
+ break;
+ }
+
+ 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:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch) != NGX_DONE) {
+ break;
+ }
+
+ 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_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (rc == NGX_DONE) {
+ st->value = st->literal.value;
+ goto done;
+ }
+
+ break;
+ }
+
+ 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);
+
+ if (ngx_http_v3_ref_insert(c, st->dynamic, st->index, &st->value) != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ 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:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 5, ch) != NGX_DONE) {
+ break;
+ }
+
+ 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_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (rc == NGX_DONE) {
+ 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:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch) != NGX_DONE) {
+ break;
+ }
+
+ 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_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (rc == NGX_DONE) {
+ st->value = st->literal.value;
+ goto done;
+ }
+
+ break;
+ }
+
+ 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);
+
+ if (ngx_http_v3_insert(c, &st->name, &st->value) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ 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;
+
+ 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:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 6, ch) != NGX_DONE) {
+ break;
+ }
+
+ if (ngx_http_v3_ack_header(c, st->pint.value) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ goto done;
+
+ case sw_cancel_stream:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 6, ch) != NGX_DONE) {
+ break;
+ }
+
+ if (ngx_http_v3_cancel_stream(c, st->pint.value) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ goto done;
+
+ case sw_inc_insert_count:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 6, ch) != NGX_DONE) {
+ break;
+ }
+
+ if (ngx_http_v3_inc_insert_count(c, st->pint.value) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ 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_DONE;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_data(ngx_connection_t *c, ngx_http_v3_parse_data_t *st,
+ u_char ch)
+{
+ 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:
+
+ if (ngx_http_v3_parse_varlen_int(c, &st->vlint, ch) != NGX_DONE) {
+ break;
+ }
+
+ if (st->vlint.value != NGX_HTTP_V3_FRAME_DATA) {
+ return NGX_ERROR;
+ }
+
+ st->state = sw_length;
+ break;
+
+ case sw_length:
+
+ if (ngx_http_v3_parse_varlen_int(c, &st->vlint, ch) != NGX_DONE) {
+ break;
+ }
+
+ 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..ec78c7c35
--- /dev/null
+++ b/src/http/v3/ngx_http_v3_parse.h
@@ -0,0 +1,155 @@
+
+/*
+ * 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 mask;
+ 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;
+
+
+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..e0ee0c882
--- /dev/null
+++ b/src/http/v3/ngx_http_v3_request.c
@@ -0,0 +1,669 @@
+
+/*
+ * 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_process_pseudo_header(ngx_http_request_t *r,
+ ngx_str_t *name, ngx_str_t *value);
+
+
+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_header(ngx_http_request_t *r, ngx_buf_t *b)
+{
+ size_t n;
+ u_char *p;
+ ngx_int_t rc;
+ ngx_str_t *name, *value;
+ ngx_connection_t *c;
+ ngx_http_v3_parse_headers_t *st;
+ enum {
+ sw_start = 0,
+ sw_prev,
+ sw_headers,
+ sw_last,
+ sw_done
+ };
+
+ 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;
+ }
+
+ switch (r->state) {
+
+ case sw_prev:
+ r->state = sw_headers;
+ return NGX_OK;
+
+ case sw_done:
+ goto done;
+
+ case sw_last:
+ r->state = sw_done;
+ return NGX_OK;
+
+ default:
+ break;
+ }
+
+ while (b->pos < b->last) {
+ rc = ngx_http_v3_parse_headers(c, st, *b->pos++);
+
+ if (rc == NGX_ERROR) {
+ goto failed;
+ }
+
+ if (rc == NGX_AGAIN) {
+ continue;
+ }
+
+ name = &st->header_rep.header.name;
+ value = &st->header_rep.header.value;
+
+ if (r->state == sw_start) {
+
+ if (ngx_http_v3_process_pseudo_header(r, name, value) == NGX_OK) {
+ if (rc == NGX_OK) {
+ continue;
+ }
+
+ r->state = sw_done;
+
+ } else if (rc == NGX_OK) {
+ r->state = sw_prev;
+
+ } else {
+ r->state = sw_last;
+ }
+
+ n = (r->method_end - r->method_start) + 1
+ + (r->uri_end - r->uri_start) + 1
+ + sizeof("HTTP/3") - 1;
+
+ p = ngx_pnalloc(c->pool, n);
+ 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;
+
+ } else if (rc == NGX_DONE) {
+ r->state = sw_done;
+ }
+
+ 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;
+ r->header_hash = ngx_hash_key(name->data, name->len);
+
+ /* XXX r->lowcase_index = i; */
+
+ return NGX_OK;
+ }
+
+ return NGX_AGAIN;
+
+failed:
+
+ return r->state == sw_start ? NGX_HTTP_PARSE_INVALID_REQUEST
+ : NGX_HTTP_PARSE_INVALID_HEADER;
+
+done:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header done");
+
+ return NGX_HTTP_PARSE_HEADER_DONE;
+}
+
+
+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 == 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_uint_t i, j;
+ ngx_chain_t *hl, *cl, *bl;
+ ngx_list_part_t *part;
+ ngx_table_elt_t *header;
+ ngx_connection_t *c;
+ ngx_http_core_loc_conf_t *clcf;
+
+ c = r->connection;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 create header");
+
+ len = 2;
+
+ if (r->headers_out.status == NGX_HTTP_OK) {
+ len += ngx_http_v3_encode_prefix_int(NULL, 25, 6);
+
+ } else {
+ len += 3 + ngx_http_v3_encode_prefix_int(NULL, 25, 4)
+ + ngx_http_v3_encode_prefix_int(NULL, 3, 7);
+ }
+
+ 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_prefix_int(NULL, 92, 4)
+ + ngx_http_v3_encode_prefix_int(NULL, n, 7) + n;
+ }
+
+ if (r->headers_out.date == NULL) {
+ len += ngx_http_v3_encode_prefix_int(NULL, 6, 4)
+ + ngx_http_v3_encode_prefix_int(NULL, ngx_cached_http_time.len,
+ 7)
+ + 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_prefix_int(NULL, 53, 4)
+ + ngx_http_v3_encode_prefix_int(NULL, n, 7) + n;
+ }
+
+ if (r->headers_out.content_length_n > 0) {
+ len += ngx_http_v3_encode_prefix_int(NULL, 4, 4) + 1 + NGX_OFF_T_LEN;
+
+ } else if (r->headers_out.content_length_n == 0) {
+ len += ngx_http_v3_encode_prefix_int(NULL, 4, 6);
+ }
+
+ if (r->headers_out.last_modified == NULL
+ && r->headers_out.last_modified_time != -1)
+ {
+ len += ngx_http_v3_encode_prefix_int(NULL, 10, 4) + 1
+ + sizeof("Last-Modified: Mon, 28 Sep 1970 06:00:00 GMT");
+ }
+
+ /* XXX location */
+
+#if (NGX_HTTP_GZIP)
+ if (r->gzip_vary) {
+ if (clcf->gzip_vary) {
+ /* Vary: Accept-Encoding */
+ len += ngx_http_v3_encode_prefix_int(NULL, 59, 6);
+
+ } 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_prefix_int(NULL, header[i].key.len, 3)
+ + header[i].key.len
+ + ngx_http_v3_encode_prefix_int(NULL, header[i].value.len, 7 )
+ + header[i].value.len;
+ }
+
+ 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++ = 0;
+ *b->last++ = 0;
+
+ if (r->headers_out.status == NGX_HTTP_OK) {
+ /* :status: 200 */
+ *b->last = 0xc0;
+ b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 25, 6);
+
+ } else {
+ /* :status: 200 */
+ *b->last = 0x70;
+ b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 25, 4);
+ *b->last = 0;
+ b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 3, 7);
+ 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;
+ }
+
+ /* server */
+ *b->last = 0x70;
+ b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 92, 4);
+ *b->last = 0;
+ b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, n, 7);
+ b->last = ngx_cpymem(b->last, p, n);
+ }
+
+ if (r->headers_out.date == NULL) {
+ /* date */
+ *b->last = 0x70;
+ b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 6, 4);
+ *b->last = 0;
+ b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last,
+ ngx_cached_http_time.len, 7);
+ b->last = ngx_cpymem(b->last, 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;
+ }
+
+ /* content-type: text/plain */
+ *b->last = 0x70;
+ b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 53, 4);
+ *b->last = 0;
+ b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, n, 7);
+
+ p = b->last;
+ b->last = ngx_copy(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_copy(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_n > 0) {
+ /* content-length: 0 */
+ *b->last = 0x70;
+ b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 4, 4);
+ p = b->last++;
+ b->last = ngx_sprintf(b->last, "%O", r->headers_out.content_length_n);
+ *p = b->last - p - 1;
+
+ } else if (r->headers_out.content_length_n == 0) {
+ /* content-length: 0 */
+ *b->last = 0xc0;
+ b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 4, 6);
+ }
+
+ if (r->headers_out.last_modified == NULL
+ && r->headers_out.last_modified_time != -1)
+ {
+ /* last-modified */
+ *b->last = 0x70;
+ b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 10, 4);
+ p = b->last++;
+ b->last = ngx_http_time(b->last, r->headers_out.last_modified_time);
+ *p = b->last - p - 1;
+ }
+
+#if (NGX_HTTP_GZIP)
+ if (r->gzip_vary) {
+ /* vary: accept-encoding */
+ *b->last = 0xc0;
+ b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 59, 6);
+ }
+#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 = 0x30;
+ b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last,
+ header[i].key.len,
+ 3);
+ for (j = 0; j < header[i].key.len; j++) {
+ *b->last++ = ngx_tolower(header[i].key.data[j]);
+ }
+
+ *b->last = 0;
+ b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last,
+ header[i].value.len,
+ 7);
+ b->last = ngx_copy(b->last, header[i].value.data, header[i].value.len);
+ }
+
+ 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 = 1 + ngx_http_v3_encode_varlen_int(NULL, n);
+
+ b = ngx_create_temp_buf(c->pool, len);
+ if (b == NULL) {
+ return NULL;
+ }
+
+ *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;
+
+ if (r->headers_out.content_length_n >= 0) {
+ len = 1 + ngx_http_v3_encode_varlen_int(NULL,
+ r->headers_out.content_length_n);
+
+ b = ngx_create_temp_buf(c->pool, len);
+ if (b == NULL) {
+ NULL;
+ }
+
+ *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);
+
+ bl = ngx_alloc_chain_link(c->pool);
+ if (bl == NULL) {
+ return NULL;
+ }
+
+ bl->buf = b;
+ bl->next = NULL;
+ cl->next = bl;
+ }
+
+ return hl;
+}
+
+
+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;
+}
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..6078725d7
--- /dev/null
+++ b/src/http/v3/ngx_http_v3_streams.c
@@ -0,0 +1,571 @@
+
+/*
+ * 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;
+
+
+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 ngx_connection_t *ngx_http_v3_get_uni_stream(ngx_connection_t *c,
+ ngx_uint_t type);
+
+
+void
+ngx_http_v3_handle_client_uni_stream(ngx_connection_t *c)
+{
+ ngx_http_v3_uni_stream_t *us;
+
+ c->log->connection = c->number;
+
+ ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL);
+ ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_ENCODER);
+ ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 new uni stream id:0x%uxL", c->qs->id);
+
+ us = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_uni_stream_t));
+ if (us == NULL) {
+ ngx_http_v3_close_uni_stream(c);
+ return;
+ }
+
+ 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);
+}
+
+
+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;
+ 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_ERROR) {
+ goto failed;
+ }
+
+ if (n == NGX_AGAIN || n != 1) {
+ break;
+ }
+
+ 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");
+ goto failed;
+ }
+
+ us->index = index;
+ h3c->known_streams[index] = c;
+ }
+
+ if (n) {
+ us->data = ngx_pcalloc(c->pool, n);
+ if (us->data == NULL) {
+ 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) {
+ goto failed;
+ }
+
+ return;
+
+failed:
+
+ 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 || n == 0) {
+ goto failed;
+ }
+
+ 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_ERROR) {
+ goto failed;
+ }
+
+ if (rc == NGX_DONE) {
+ goto done;
+ }
+
+ /* rc == NGX_AGAIN */
+ }
+ }
+
+ if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+ goto failed;
+ }
+
+ return;
+
+done:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read done");
+
+failed:
+
+ 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_close_uni_stream(c);
+ }
+}
+
+
+/* XXX async & buffered stream writes */
+
+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_create_uni_stream(c);
+ 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;
+
+ 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;
+}
+
+
+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;
+}
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..f58f190f1
--- /dev/null
+++ b/src/http/v3/ngx_http_v3_tables.c
@@ -0,0 +1,416 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static ngx_array_t *ngx_http_v3_get_dynamic_table(ngx_connection_t *c);
+static ngx_int_t ngx_http_v3_new_header(ngx_connection_t *c);
+
+
+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_array_t *dt;
+ ngx_http_v3_header_t *ref, *h;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 ref insert %s[$ui] \"%V\"",
+ dynamic ? "dynamic" : "static", index, value);
+
+ ref = ngx_http_v3_lookup_table(c, dynamic, index);
+ if (ref == NULL) {
+ return NGX_ERROR;
+ }
+
+ dt = ngx_http_v3_get_dynamic_table(c);
+ if (dt == NULL) {
+ return NGX_ERROR;
+ }
+
+ h = ngx_array_push(dt);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ h->name = ref->name;
+ h->value = *value;
+
+ if (ngx_http_v3_new_header(c) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name,
+ ngx_str_t *value)
+{
+ ngx_array_t *dt;
+ ngx_http_v3_header_t *h;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 insert \"%V\":\"%V\"", name, value);
+
+ dt = ngx_http_v3_get_dynamic_table(c);
+ if (dt == NULL) {
+ return NGX_ERROR;
+ }
+
+ h = ngx_array_push(dt);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ h->name = *name;
+ h->value = *value;
+
+ 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_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 set capacity %ui", capacity);
+
+ /* XXX ignore capacity */
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index)
+{
+ ngx_array_t *dt;
+ ngx_http_v3_header_t *ref, *h;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 duplicate %ui", index);
+
+ ref = ngx_http_v3_lookup_table(c, 1, index);
+ if (ref == NULL) {
+ return NGX_ERROR;
+ }
+
+ dt = ngx_http_v3_get_dynamic_table(c);
+ if (dt == NULL) {
+ return NGX_ERROR;
+ }
+
+ h = ngx_array_push(dt);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ *h = *ref;
+
+ if (ngx_http_v3_new_header(c) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+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;
+}
+
+
+static ngx_array_t *
+ngx_http_v3_get_dynamic_table(ngx_connection_t *c)
+{
+ ngx_connection_t *pc;
+ ngx_http_v3_connection_t *h3c;
+
+ pc = c->qs->parent;
+ h3c = pc->data;
+
+ if (h3c->dynamic) {
+ return h3c->dynamic;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 create dynamic table");
+
+ h3c->dynamic = ngx_array_create(pc->pool, 1, sizeof(ngx_http_v3_header_t));
+
+ return h3c->dynamic;
+}
+
+
+ngx_http_v3_header_t *
+ngx_http_v3_lookup_table(ngx_connection_t *c, ngx_uint_t dynamic,
+ ngx_uint_t index)
+{
+ ngx_uint_t nelts;
+ ngx_array_t *dt;
+ ngx_http_v3_header_t *table;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 lookup %s[%ui]",
+ dynamic ? "dynamic" : "static", index);
+
+ if (dynamic) {
+ dt = ngx_http_v3_get_dynamic_table(c);
+ if (dt == NULL) {
+ return NULL;
+ }
+
+ table = dt->elts;
+ nelts = dt->nelts;
+
+ } else {
+ table = ngx_http_v3_static_table;
+ nelts = sizeof(ngx_http_v3_static_table)
+ / sizeof(ngx_http_v3_static_table[0]);
+ }
+
+ if (index >= nelts) {
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 lookup out of bounds: %ui", nelts);
+ return NULL;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 lookup \"%V\":\"%V\"",
+ &table[index].name, &table[index].value);
+
+ return &table[index];
+}
+
+
+ngx_int_t
+ngx_http_v3_check_insert_count(ngx_connection_t *c, ngx_uint_t insert_count)
+{
+ size_t n;
+ ngx_http_v3_connection_t *h3c;
+
+ h3c = c->qs->parent->data;
+ n = h3c->dynamic ? h3c->dynamic->nelts : 0;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 check insert count %ui/%ui", insert_count, n);
+
+ if (n < insert_count) {
+ /* XXX how to get notified? */
+ /* XXX wake all streams on any arrival to the encoder stream? */
+ return NGX_AGAIN;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v3_new_header(ngx_connection_t *c)
+{
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 new dynamic header");
+
+ /* XXX report all waiting streams of a new header */
+
+ 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;
+}