aboutsummaryrefslogtreecommitdiff
path: root/src/http/v3
diff options
context:
space:
mode:
Diffstat (limited to 'src/http/v3')
-rw-r--r--src/http/v3/ngx_http_v3.c109
-rw-r--r--src/http/v3/ngx_http_v3.h157
-rw-r--r--src/http/v3/ngx_http_v3_encode.c304
-rw-r--r--src/http/v3/ngx_http_v3_encode.h34
-rw-r--r--src/http/v3/ngx_http_v3_filter_module.c851
-rw-r--r--src/http/v3/ngx_http_v3_module.c389
-rw-r--r--src/http/v3/ngx_http_v3_parse.c1931
-rw-r--r--src/http/v3/ngx_http_v3_parse.h146
-rw-r--r--src/http/v3/ngx_http_v3_request.c1716
-rw-r--r--src/http/v3/ngx_http_v3_table.c715
-rw-r--r--src/http/v3/ngx_http_v3_table.h58
-rw-r--r--src/http/v3/ngx_http_v3_uni.c624
-rw-r--r--src/http/v3/ngx_http_v3_uni.h32
13 files changed, 7066 insertions, 0 deletions
diff --git a/src/http/v3/ngx_http_v3.c b/src/http/v3/ngx_http_v3.c
new file mode 100644
index 000000000..eb86b2da5
--- /dev/null
+++ b/src/http/v3/ngx_http_v3.c
@@ -0,0 +1,109 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static void ngx_http_v3_keepalive_handler(ngx_event_t *ev);
+static void ngx_http_v3_cleanup_session(void *data);
+
+
+ngx_int_t
+ngx_http_v3_init_session(ngx_connection_t *c)
+{
+ ngx_pool_cleanup_t *cln;
+ ngx_http_connection_t *hc;
+ ngx_http_v3_session_t *h3c;
+
+ hc = c->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init session");
+
+ h3c = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_session_t));
+ if (h3c == NULL) {
+ goto failed;
+ }
+
+ ngx_queue_init(&h3c->blocked);
+
+ h3c->keepalive.log = c->log;
+ h3c->keepalive.data = c;
+ h3c->keepalive.handler = ngx_http_v3_keepalive_handler;
+
+ h3c->table.send_insert_count.log = c->log;
+ h3c->table.send_insert_count.data = c;
+ h3c->table.send_insert_count.handler = ngx_http_v3_inc_insert_count_handler;
+
+ cln = ngx_pool_cleanup_add(c->pool, 0);
+ if (cln == NULL) {
+ goto failed;
+ }
+
+ cln->handler = ngx_http_v3_cleanup_session;
+ cln->data = h3c;
+
+ hc->v3_session = h3c;
+
+ return NGX_OK;
+
+failed:
+
+ ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create http3 session");
+ return NGX_ERROR;
+}
+
+
+static void
+ngx_http_v3_keepalive_handler(ngx_event_t *ev)
+{
+ ngx_connection_t *c;
+
+ c = ev->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 keepalive handler");
+
+ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR,
+ "keepalive timeout");
+}
+
+
+static void
+ngx_http_v3_cleanup_session(void *data)
+{
+ ngx_http_v3_session_t *h3c = data;
+
+ ngx_http_v3_cleanup_table(h3c);
+
+ if (h3c->keepalive.timer_set) {
+ ngx_del_timer(&h3c->keepalive);
+ }
+
+ if (h3c->table.send_insert_count.posted) {
+ ngx_delete_posted_event(&h3c->table.send_insert_count);
+ }
+}
+
+
+ngx_int_t
+ngx_http_v3_check_flood(ngx_connection_t *c)
+{
+ ngx_http_v3_session_t *h3c;
+
+ h3c = ngx_http_v3_get_session(c);
+
+ if (h3c->total_bytes / 8 > h3c->payload_bytes + 1048576) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "http3 flood detected");
+
+ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR,
+ "HTTP/3 flood detected");
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h
new file mode 100644
index 000000000..94b0d3e78
--- /dev/null
+++ b/src/http/v3/ngx_http_v3.h
@@ -0,0 +1,157 @@
+
+/*
+ * 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>
+#include <ngx_http_v3_encode.h>
+#include <ngx_http_v3_uni.h>
+#include <ngx_http_v3_table.h>
+
+
+#define NGX_HTTP_V3_ALPN_PROTO "\x02h3"
+#define NGX_HTTP_V3_HQ_ALPN_PROTO "\x0Ahq-interop"
+#define NGX_HTTP_V3_HQ_PROTO "hq-interop"
+
+#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_FIELD_SECTION_SIZE 0x06
+#define NGX_HTTP_V3_PARAM_BLOCKED_STREAMS 0x07
+
+#define NGX_HTTP_V3_MAX_TABLE_CAPACITY 4096
+
+#define NGX_HTTP_V3_STREAM_CLIENT_CONTROL 0
+#define NGX_HTTP_V3_STREAM_SERVER_CONTROL 1
+#define NGX_HTTP_V3_STREAM_CLIENT_ENCODER 2
+#define NGX_HTTP_V3_STREAM_SERVER_ENCODER 3
+#define NGX_HTTP_V3_STREAM_CLIENT_DECODER 4
+#define NGX_HTTP_V3_STREAM_SERVER_DECODER 5
+#define NGX_HTTP_V3_MAX_KNOWN_STREAM 6
+#define NGX_HTTP_V3_MAX_UNI_STREAMS 3
+
+/* HTTP/3 errors */
+#define NGX_HTTP_V3_ERR_NO_ERROR 0x100
+#define NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR 0x101
+#define NGX_HTTP_V3_ERR_INTERNAL_ERROR 0x102
+#define NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR 0x103
+#define NGX_HTTP_V3_ERR_CLOSED_CRITICAL_STREAM 0x104
+#define NGX_HTTP_V3_ERR_FRAME_UNEXPECTED 0x105
+#define NGX_HTTP_V3_ERR_FRAME_ERROR 0x106
+#define NGX_HTTP_V3_ERR_EXCESSIVE_LOAD 0x107
+#define NGX_HTTP_V3_ERR_ID_ERROR 0x108
+#define NGX_HTTP_V3_ERR_SETTINGS_ERROR 0x109
+#define NGX_HTTP_V3_ERR_MISSING_SETTINGS 0x10a
+#define NGX_HTTP_V3_ERR_REQUEST_REJECTED 0x10b
+#define NGX_HTTP_V3_ERR_REQUEST_CANCELLED 0x10c
+#define NGX_HTTP_V3_ERR_REQUEST_INCOMPLETE 0x10d
+#define NGX_HTTP_V3_ERR_CONNECT_ERROR 0x10f
+#define NGX_HTTP_V3_ERR_VERSION_FALLBACK 0x110
+
+/* QPACK errors */
+#define NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED 0x200
+#define NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR 0x201
+#define NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR 0x202
+
+
+#define ngx_http_quic_get_connection(c) \
+ ((ngx_http_connection_t *) ((c)->quic ? (c)->quic->parent->data \
+ : (c)->data))
+
+#define ngx_http_v3_get_session(c) ngx_http_quic_get_connection(c)->v3_session
+
+#define ngx_http_v3_get_module_loc_conf(c, module) \
+ ngx_http_get_module_loc_conf(ngx_http_quic_get_connection(c)->conf_ctx, \
+ module)
+
+#define ngx_http_v3_get_module_srv_conf(c, module) \
+ ngx_http_get_module_srv_conf(ngx_http_quic_get_connection(c)->conf_ctx, \
+ module)
+
+#define ngx_http_v3_finalize_connection(c, code, reason) \
+ ngx_quic_finalize_connection((c)->quic ? (c)->quic->parent : (c), \
+ code, reason)
+
+#define ngx_http_v3_shutdown_connection(c, code, reason) \
+ ngx_quic_shutdown_connection((c)->quic ? (c)->quic->parent : (c), \
+ code, reason)
+
+
+typedef struct {
+ ngx_flag_t enable;
+ ngx_flag_t enable_hq;
+ size_t max_table_capacity;
+ ngx_uint_t max_blocked_streams;
+ ngx_uint_t max_concurrent_streams;
+ ngx_quic_conf_t quic;
+} ngx_http_v3_srv_conf_t;
+
+
+struct ngx_http_v3_parse_s {
+ size_t header_limit;
+ ngx_http_v3_parse_headers_t headers;
+ ngx_http_v3_parse_data_t body;
+ ngx_array_t *cookies;
+};
+
+
+struct ngx_http_v3_session_s {
+ ngx_http_v3_dynamic_table_t table;
+
+ ngx_event_t keepalive;
+ ngx_uint_t nrequests;
+
+ ngx_queue_t blocked;
+ ngx_uint_t nblocked;
+
+ uint64_t next_request_id;
+
+ off_t total_bytes;
+ off_t payload_bytes;
+
+ unsigned goaway:1;
+ unsigned hq:1;
+
+ ngx_connection_t *known_streams[NGX_HTTP_V3_MAX_KNOWN_STREAM];
+};
+
+
+void ngx_http_v3_init_stream(ngx_connection_t *c);
+void ngx_http_v3_reset_stream(ngx_connection_t *c);
+ngx_int_t ngx_http_v3_init_session(ngx_connection_t *c);
+ngx_int_t ngx_http_v3_check_flood(ngx_connection_t *c);
+ngx_int_t ngx_http_v3_init(ngx_connection_t *c);
+void ngx_http_v3_shutdown(ngx_connection_t *c);
+
+ngx_int_t ngx_http_v3_read_request_body(ngx_http_request_t *r);
+ngx_int_t ngx_http_v3_read_unbuffered_request_body(ngx_http_request_t *r);
+
+
+extern ngx_module_t ngx_http_v3_module;
+
+
+#endif /* _NGX_HTTP_V3_H_INCLUDED_ */
diff --git a/src/http/v3/ngx_http_v3_encode.c b/src/http/v3/ngx_http_v3_encode.c
new file mode 100644
index 000000000..fb089c413
--- /dev/null
+++ b/src/http/v3/ngx_http_v3_encode.c
@@ -0,0 +1,304 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+uintptr_t
+ngx_http_v3_encode_varlen_int(u_char *p, uint64_t value)
+{
+ if (value <= 63) {
+ if (p == NULL) {
+ return 1;
+ }
+
+ *p++ = value;
+ return (uintptr_t) p;
+ }
+
+ if (value <= 16383) {
+ if (p == NULL) {
+ return 2;
+ }
+
+ *p++ = 0x40 | (value >> 8);
+ *p++ = value;
+ return (uintptr_t) p;
+ }
+
+ if (value <= 1073741823) {
+ if (p == NULL) {
+ return 4;
+ }
+
+ *p++ = 0x80 | (value >> 24);
+ *p++ = (value >> 16);
+ *p++ = (value >> 8);
+ *p++ = value;
+ return (uintptr_t) p;
+ }
+
+ if (p == NULL) {
+ return 8;
+ }
+
+ *p++ = 0xc0 | (value >> 56);
+ *p++ = (value >> 48);
+ *p++ = (value >> 40);
+ *p++ = (value >> 32);
+ *p++ = (value >> 24);
+ *p++ = (value >> 16);
+ *p++ = (value >> 8);
+ *p++ = value;
+ return (uintptr_t) p;
+}
+
+
+uintptr_t
+ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value, ngx_uint_t prefix)
+{
+ ngx_uint_t thresh, n;
+
+ thresh = (1 << prefix) - 1;
+
+ if (value < thresh) {
+ if (p == NULL) {
+ return 1;
+ }
+
+ *p++ |= value;
+ return (uintptr_t) p;
+ }
+
+ value -= thresh;
+
+ if (p == NULL) {
+ for (n = 2; value >= 128; n++) {
+ value >>= 7;
+ }
+
+ return n;
+ }
+
+ *p++ |= thresh;
+
+ while (value >= 128) {
+ *p++ = 0x80 | value;
+ value >>= 7;
+ }
+
+ *p++ = value;
+
+ return (uintptr_t) p;
+}
+
+
+uintptr_t
+ngx_http_v3_encode_field_section_prefix(u_char *p, ngx_uint_t insert_count,
+ ngx_uint_t sign, ngx_uint_t delta_base)
+{
+ if (p == NULL) {
+ return ngx_http_v3_encode_prefix_int(NULL, insert_count, 8)
+ + ngx_http_v3_encode_prefix_int(NULL, delta_base, 7);
+ }
+
+ *p = 0;
+ p = (u_char *) ngx_http_v3_encode_prefix_int(p, insert_count, 8);
+
+ *p = sign ? 0x80 : 0;
+ p = (u_char *) ngx_http_v3_encode_prefix_int(p, delta_base, 7);
+
+ return (uintptr_t) p;
+}
+
+
+uintptr_t
+ngx_http_v3_encode_field_ri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index)
+{
+ /* Indexed Field Line */
+
+ if (p == NULL) {
+ return ngx_http_v3_encode_prefix_int(NULL, index, 6);
+ }
+
+ *p = dynamic ? 0x80 : 0xc0;
+
+ return ngx_http_v3_encode_prefix_int(p, index, 6);
+}
+
+
+uintptr_t
+ngx_http_v3_encode_field_lri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index,
+ u_char *data, size_t len)
+{
+ size_t hlen;
+ u_char *p1, *p2;
+
+ /* Literal Field Line With Name Reference */
+
+ if (p == NULL) {
+ return ngx_http_v3_encode_prefix_int(NULL, index, 4)
+ + ngx_http_v3_encode_prefix_int(NULL, len, 7)
+ + len;
+ }
+
+ *p = dynamic ? 0x40 : 0x50;
+ p = (u_char *) ngx_http_v3_encode_prefix_int(p, index, 4);
+
+ p1 = p;
+ *p = 0;
+ p = (u_char *) ngx_http_v3_encode_prefix_int(p, len, 7);
+
+ if (data) {
+ p2 = p;
+ hlen = ngx_http_huff_encode(data, len, p, 0);
+
+ if (hlen) {
+ p = p1;
+ *p = 0x80;
+ p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 7);
+
+ if (p != p2) {
+ ngx_memmove(p, p2, hlen);
+ }
+
+ p += hlen;
+
+ } else {
+ p = ngx_cpymem(p, data, len);
+ }
+ }
+
+ return (uintptr_t) p;
+}
+
+
+uintptr_t
+ngx_http_v3_encode_field_l(u_char *p, ngx_str_t *name, ngx_str_t *value)
+{
+ size_t hlen;
+ u_char *p1, *p2;
+
+ /* Literal Field Line With Literal Name */
+
+ if (p == NULL) {
+ return ngx_http_v3_encode_prefix_int(NULL, name->len, 3)
+ + name->len
+ + ngx_http_v3_encode_prefix_int(NULL, value->len, 7)
+ + value->len;
+ }
+
+ p1 = p;
+ *p = 0x20;
+ p = (u_char *) ngx_http_v3_encode_prefix_int(p, name->len, 3);
+
+ p2 = p;
+ hlen = ngx_http_huff_encode(name->data, name->len, p, 1);
+
+ if (hlen) {
+ p = p1;
+ *p = 0x28;
+ p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 3);
+
+ if (p != p2) {
+ ngx_memmove(p, p2, hlen);
+ }
+
+ p += hlen;
+
+ } else {
+ ngx_strlow(p, name->data, name->len);
+ p += name->len;
+ }
+
+ p1 = p;
+ *p = 0;
+ p = (u_char *) ngx_http_v3_encode_prefix_int(p, value->len, 7);
+
+ p2 = p;
+ hlen = ngx_http_huff_encode(value->data, value->len, p, 0);
+
+ if (hlen) {
+ p = p1;
+ *p = 0x80;
+ p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 7);
+
+ if (p != p2) {
+ ngx_memmove(p, p2, hlen);
+ }
+
+ p += hlen;
+
+ } else {
+ p = ngx_cpymem(p, value->data, value->len);
+ }
+
+ return (uintptr_t) p;
+}
+
+
+uintptr_t
+ngx_http_v3_encode_field_pbi(u_char *p, ngx_uint_t index)
+{
+ /* Indexed Field Line With Post-Base Index */
+
+ if (p == NULL) {
+ return ngx_http_v3_encode_prefix_int(NULL, index, 4);
+ }
+
+ *p = 0x10;
+
+ return ngx_http_v3_encode_prefix_int(p, index, 4);
+}
+
+
+uintptr_t
+ngx_http_v3_encode_field_lpbi(u_char *p, ngx_uint_t index, u_char *data,
+ size_t len)
+{
+ size_t hlen;
+ u_char *p1, *p2;
+
+ /* Literal Field Line With Post-Base Name Reference */
+
+ if (p == NULL) {
+ return ngx_http_v3_encode_prefix_int(NULL, index, 3)
+ + ngx_http_v3_encode_prefix_int(NULL, len, 7)
+ + len;
+ }
+
+ *p = 0;
+ p = (u_char *) ngx_http_v3_encode_prefix_int(p, index, 3);
+
+ p1 = p;
+ *p = 0;
+ p = (u_char *) ngx_http_v3_encode_prefix_int(p, len, 7);
+
+ if (data) {
+ p2 = p;
+ hlen = ngx_http_huff_encode(data, len, p, 0);
+
+ if (hlen) {
+ p = p1;
+ *p = 0x80;
+ p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 7);
+
+ if (p != p2) {
+ ngx_memmove(p, p2, hlen);
+ }
+
+ p += hlen;
+
+ } else {
+ p = ngx_cpymem(p, data, len);
+ }
+ }
+
+ return (uintptr_t) p;
+}
diff --git a/src/http/v3/ngx_http_v3_encode.h b/src/http/v3/ngx_http_v3_encode.h
new file mode 100644
index 000000000..fca376da5
--- /dev/null
+++ b/src/http/v3/ngx_http_v3_encode.h
@@ -0,0 +1,34 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_HTTP_V3_ENCODE_H_INCLUDED_
+#define _NGX_HTTP_V3_ENCODE_H_INCLUDED_
+
+
+#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);
+uintptr_t ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value,
+ ngx_uint_t prefix);
+
+uintptr_t ngx_http_v3_encode_field_section_prefix(u_char *p,
+ ngx_uint_t insert_count, ngx_uint_t sign, ngx_uint_t delta_base);
+uintptr_t ngx_http_v3_encode_field_ri(u_char *p, ngx_uint_t dynamic,
+ ngx_uint_t index);
+uintptr_t ngx_http_v3_encode_field_lri(u_char *p, ngx_uint_t dynamic,
+ ngx_uint_t index, u_char *data, size_t len);
+uintptr_t ngx_http_v3_encode_field_l(u_char *p, ngx_str_t *name,
+ ngx_str_t *value);
+uintptr_t ngx_http_v3_encode_field_pbi(u_char *p, ngx_uint_t index);
+uintptr_t ngx_http_v3_encode_field_lpbi(u_char *p, ngx_uint_t index,
+ u_char *data, size_t len);
+
+
+#endif /* _NGX_HTTP_V3_ENCODE_H_INCLUDED_ */
diff --git a/src/http/v3/ngx_http_v3_filter_module.c b/src/http/v3/ngx_http_v3_filter_module.c
new file mode 100644
index 000000000..7addf275d
--- /dev/null
+++ b/src/http/v3/ngx_http_v3_filter_module.c
@@ -0,0 +1,851 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+/* static table indices */
+#define NGX_HTTP_V3_HEADER_AUTHORITY 0
+#define NGX_HTTP_V3_HEADER_PATH_ROOT 1
+#define NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO 4
+#define NGX_HTTP_V3_HEADER_DATE 6
+#define NGX_HTTP_V3_HEADER_LAST_MODIFIED 10
+#define NGX_HTTP_V3_HEADER_LOCATION 12
+#define NGX_HTTP_V3_HEADER_METHOD_GET 17
+#define NGX_HTTP_V3_HEADER_SCHEME_HTTP 22
+#define NGX_HTTP_V3_HEADER_SCHEME_HTTPS 23
+#define NGX_HTTP_V3_HEADER_STATUS_200 25
+#define NGX_HTTP_V3_HEADER_ACCEPT_ENCODING 31
+#define NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN 53
+#define NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING 59
+#define NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE 72
+#define NGX_HTTP_V3_HEADER_SERVER 92
+#define NGX_HTTP_V3_HEADER_USER_AGENT 95
+
+
+typedef struct {
+ ngx_chain_t *free;
+ ngx_chain_t *busy;
+} ngx_http_v3_filter_ctx_t;
+
+
+static ngx_int_t ngx_http_v3_header_filter(ngx_http_request_t *r);
+static ngx_int_t ngx_http_v3_body_filter(ngx_http_request_t *r,
+ ngx_chain_t *in);
+static ngx_chain_t *ngx_http_v3_create_trailers(ngx_http_request_t *r,
+ ngx_http_v3_filter_ctx_t *ctx);
+static ngx_int_t ngx_http_v3_filter_init(ngx_conf_t *cf);
+
+
+static ngx_http_module_t ngx_http_v3_filter_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_v3_filter_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ NULL, /* create location configuration */
+ NULL /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_v3_filter_module = {
+ NGX_MODULE_V1,
+ &ngx_http_v3_filter_module_ctx, /* module context */
+ NULL, /* 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_output_header_filter_pt ngx_http_next_header_filter;
+static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
+
+
+static ngx_int_t
+ngx_http_v3_header_filter(ngx_http_request_t *r)
+{
+ u_char *p;
+ size_t len, n;
+ ngx_buf_t *b;
+ ngx_str_t host, location;
+ ngx_uint_t i, port;
+ ngx_chain_t *out, *hl, *cl, **ll;
+ ngx_list_part_t *part;
+ ngx_table_elt_t *header;
+ ngx_connection_t *c;
+ ngx_http_v3_session_t *h3c;
+ ngx_http_v3_filter_ctx_t *ctx;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_core_srv_conf_t *cscf;
+ u_char addr[NGX_SOCKADDR_STRLEN];
+
+ if (r->http_version != NGX_HTTP_VERSION_30) {
+ return ngx_http_next_header_filter(r);
+ }
+
+ if (r->header_sent) {
+ return NGX_OK;
+ }
+
+ r->header_sent = 1;
+
+ if (r != r->main) {
+ return NGX_OK;
+ }
+
+ h3c = ngx_http_v3_get_session(r->connection);
+
+ if (r->method == NGX_HTTP_HEAD) {
+ r->header_only = 1;
+ }
+
+ if (r->headers_out.last_modified_time != -1) {
+ if (r->headers_out.status != NGX_HTTP_OK
+ && r->headers_out.status != NGX_HTTP_PARTIAL_CONTENT
+ && r->headers_out.status != NGX_HTTP_NOT_MODIFIED)
+ {
+ r->headers_out.last_modified_time = -1;
+ r->headers_out.last_modified = NULL;
+ }
+ }
+
+ if (r->headers_out.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;
+ r->headers_out.content_length = NULL;
+ r->headers_out.content_length_n = -1;
+ }
+
+ if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED) {
+ r->header_only = 1;
+ }
+
+ c = r->connection;
+
+ out = NULL;
+ ll = &out;
+
+ len = ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0);
+
+ if (r->headers_out.status == NGX_HTTP_OK) {
+ len += ngx_http_v3_encode_field_ri(NULL, 0,
+ NGX_HTTP_V3_HEADER_STATUS_200);
+
+ } else {
+ len += ngx_http_v3_encode_field_lri(NULL, 0,
+ NGX_HTTP_V3_HEADER_STATUS_200,
+ NULL, 3);
+ }
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (r->headers_out.server == NULL) {
+ if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) {
+ n = sizeof(NGINX_VER) - 1;
+
+ } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) {
+ n = sizeof(NGINX_VER_BUILD) - 1;
+
+ } else {
+ n = sizeof("nginx") - 1;
+ }
+
+ len += ngx_http_v3_encode_field_lri(NULL, 0,
+ NGX_HTTP_V3_HEADER_SERVER,
+ NULL, n);
+ }
+
+ if (r->headers_out.date == NULL) {
+ len += ngx_http_v3_encode_field_lri(NULL, 0, NGX_HTTP_V3_HEADER_DATE,
+ NULL, ngx_cached_http_time.len);
+ }
+
+ if (r->headers_out.content_type.len) {
+ n = r->headers_out.content_type.len;
+
+ if (r->headers_out.content_type_len == r->headers_out.content_type.len
+ && r->headers_out.charset.len)
+ {
+ n += sizeof("; charset=") - 1 + r->headers_out.charset.len;
+ }
+
+ len += ngx_http_v3_encode_field_lri(NULL, 0,
+ NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN,
+ NULL, n);
+ }
+
+ if (r->headers_out.content_length == NULL) {
+ if (r->headers_out.content_length_n > 0) {
+ len += ngx_http_v3_encode_field_lri(NULL, 0,
+ NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO,
+ NULL, NGX_OFF_T_LEN);
+
+ } else if (r->headers_out.content_length_n == 0) {
+ len += ngx_http_v3_encode_field_ri(NULL, 0,
+ NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO);
+ }
+ }
+
+ if (r->headers_out.last_modified == NULL
+ && r->headers_out.last_modified_time != -1)
+ {
+ len += ngx_http_v3_encode_field_lri(NULL, 0,
+ NGX_HTTP_V3_HEADER_LAST_MODIFIED, NULL,
+ sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1);
+ }
+
+ if (r->headers_out.location && r->headers_out.location->value.len) {
+
+ if (r->headers_out.location->value.data[0] == '/'
+ && clcf->absolute_redirect)
+ {
+ if (clcf->server_name_in_redirect) {
+ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+ host = cscf->server_name;
+
+ } else if (r->headers_in.server.len) {
+ host = r->headers_in.server;
+
+ } else {
+ host.len = NGX_SOCKADDR_STRLEN;
+ host.data = addr;
+
+ if (ngx_connection_local_sockaddr(c, &host, 0) != NGX_OK) {
+ return NGX_ERROR;
+ }
+ }
+
+ port = ngx_inet_get_port(c->local_sockaddr);
+
+ location.len = sizeof("https://") - 1 + host.len
+ + r->headers_out.location->value.len;
+
+ if (clcf->port_in_redirect) {
+ port = (port == 443) ? 0 : port;
+
+ } else {
+ port = 0;
+ }
+
+ if (port) {
+ location.len += sizeof(":65535") - 1;
+ }
+
+ location.data = ngx_pnalloc(r->pool, location.len);
+ if (location.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ p = ngx_cpymem(location.data, "https://", sizeof("https://") - 1);
+ p = ngx_cpymem(p, host.data, host.len);
+
+ if (port) {
+ p = ngx_sprintf(p, ":%ui", port);
+ }
+
+ p = ngx_cpymem(p, r->headers_out.location->value.data,
+ r->headers_out.location->value.len);
+
+ /* update r->headers_out.location->value for possible logging */
+
+ r->headers_out.location->value.len = p - location.data;
+ r->headers_out.location->value.data = location.data;
+ ngx_str_set(&r->headers_out.location->key, "Location");
+ }
+
+ r->headers_out.location->hash = 0;
+
+ len += ngx_http_v3_encode_field_lri(NULL, 0,
+ NGX_HTTP_V3_HEADER_LOCATION, NULL,
+ r->headers_out.location->value.len);
+ }
+
+#if (NGX_HTTP_GZIP)
+ if (r->gzip_vary) {
+ if (clcf->gzip_vary) {
+ len += ngx_http_v3_encode_field_ri(NULL, 0,
+ NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING);
+
+ } else {
+ r->gzip_vary = 0;
+ }
+ }
+#endif
+
+ part = &r->headers_out.headers.part;
+ header = part->elts;
+
+ for (i = 0; /* void */; i++) {
+
+ if (i >= part->nelts) {
+ if (part->next == NULL) {
+ break;
+ }
+
+ part = part->next;
+ header = part->elts;
+ i = 0;
+ }
+
+ if (header[i].hash == 0) {
+ continue;
+ }
+
+ len += ngx_http_v3_encode_field_l(NULL, &header[i].key,
+ &header[i].value);
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 header len:%uz", len);
+
+ b = ngx_create_temp_buf(r->pool, len);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->last,
+ 0, 0, 0);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 output header: \":status: %03ui\"",
+ r->headers_out.status);
+
+ if (r->headers_out.status == NGX_HTTP_OK) {
+ b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0,
+ NGX_HTTP_V3_HEADER_STATUS_200);
+
+ } else {
+ b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
+ NGX_HTTP_V3_HEADER_STATUS_200,
+ NULL, 3);
+ b->last = ngx_sprintf(b->last, "%03ui", r->headers_out.status);
+ }
+
+ if (r->headers_out.server == NULL) {
+ if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) {
+ p = (u_char *) NGINX_VER;
+ n = sizeof(NGINX_VER) - 1;
+
+ } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) {
+ p = (u_char *) NGINX_VER_BUILD;
+ n = sizeof(NGINX_VER_BUILD) - 1;
+
+ } else {
+ p = (u_char *) "nginx";
+ n = sizeof("nginx") - 1;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 output header: \"server: %*s\"", n, p);
+
+ b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
+ NGX_HTTP_V3_HEADER_SERVER,
+ p, n);
+ }
+
+ if (r->headers_out.date == NULL) {
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 output header: \"date: %V\"",
+ &ngx_cached_http_time);
+
+ b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
+ NGX_HTTP_V3_HEADER_DATE,
+ ngx_cached_http_time.data,
+ ngx_cached_http_time.len);
+ }
+
+ if (r->headers_out.content_type.len) {
+ if (r->headers_out.content_type_len == r->headers_out.content_type.len
+ && r->headers_out.charset.len)
+ {
+ n = r->headers_out.content_type.len + sizeof("; charset=") - 1
+ + r->headers_out.charset.len;
+
+ p = ngx_pnalloc(r->pool, n);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ p = ngx_cpymem(p, r->headers_out.content_type.data,
+ r->headers_out.content_type.len);
+
+ p = ngx_cpymem(p, "; charset=", sizeof("; charset=") - 1);
+
+ p = ngx_cpymem(p, r->headers_out.charset.data,
+ r->headers_out.charset.len);
+
+ /* updated r->headers_out.content_type is also needed for logging */
+
+ r->headers_out.content_type.len = n;
+ r->headers_out.content_type.data = p - n;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 output header: \"content-type: %V\"",
+ &r->headers_out.content_type);
+
+ b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
+ NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN,
+ r->headers_out.content_type.data,
+ r->headers_out.content_type.len);
+ }
+
+ if (r->headers_out.content_length == NULL
+ && r->headers_out.content_length_n >= 0)
+ {
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 output header: \"content-length: %O\"",
+ r->headers_out.content_length_n);
+
+ if (r->headers_out.content_length_n > 0) {
+ p = ngx_sprintf(b->last, "%O", r->headers_out.content_length_n);
+ n = p - b->last;
+
+ b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
+ NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO,
+ NULL, n);
+
+ b->last = ngx_sprintf(b->last, "%O",
+ r->headers_out.content_length_n);
+
+ } else {
+ b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0,
+ NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO);
+ }
+ }
+
+ if (r->headers_out.last_modified == NULL
+ && r->headers_out.last_modified_time != -1)
+ {
+ n = sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1;
+
+ p = ngx_pnalloc(r->pool, n);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_http_time(p, r->headers_out.last_modified_time);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 output header: \"last-modified: %*s\"", n, p);
+
+ b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
+ NGX_HTTP_V3_HEADER_LAST_MODIFIED,
+ p, n);
+ }
+
+ if (r->headers_out.location && r->headers_out.location->value.len) {
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 output header: \"location: %V\"",
+ &r->headers_out.location->value);
+
+ b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
+ NGX_HTTP_V3_HEADER_LOCATION,
+ r->headers_out.location->value.data,
+ r->headers_out.location->value.len);
+ }
+
+#if (NGX_HTTP_GZIP)
+ if (r->gzip_vary) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 output header: \"vary: Accept-Encoding\"");
+
+ b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0,
+ NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING);
+ }
+#endif
+
+ part = &r->headers_out.headers.part;
+ header = part->elts;
+
+ for (i = 0; /* void */; i++) {
+
+ if (i >= part->nelts) {
+ if (part->next == NULL) {
+ break;
+ }
+
+ part = part->next;
+ header = part->elts;
+ i = 0;
+ }
+
+ if (header[i].hash == 0) {
+ continue;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 output header: \"%V: %V\"",
+ &header[i].key, &header[i].value);
+
+ b->last = (u_char *) ngx_http_v3_encode_field_l(b->last,
+ &header[i].key,
+ &header[i].value);
+ }
+
+ if (r->header_only) {
+ b->last_buf = 1;
+ }
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = b;
+ cl->next = NULL;
+
+ n = b->last - b->pos;
+
+ h3c->payload_bytes += n;
+
+ len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_HEADERS)
+ + ngx_http_v3_encode_varlen_int(NULL, n);
+
+ b = ngx_create_temp_buf(r->pool, len);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last,
+ NGX_HTTP_V3_FRAME_HEADERS);
+ b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n);
+
+ hl = ngx_alloc_chain_link(r->pool);
+ if (hl == NULL) {
+ return NGX_ERROR;
+ }
+
+ hl->buf = b;
+ hl->next = cl;
+
+ *ll = hl;
+ ll = &cl->next;
+
+ if (r->headers_out.content_length_n >= 0
+ && !r->header_only && !r->expect_trailers)
+ {
+ len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_DATA)
+ + ngx_http_v3_encode_varlen_int(NULL,
+ r->headers_out.content_length_n);
+
+ b = ngx_create_temp_buf(r->pool, len);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last,
+ NGX_HTTP_V3_FRAME_DATA);
+ b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last,
+ r->headers_out.content_length_n);
+
+ h3c->payload_bytes += r->headers_out.content_length_n;
+ h3c->total_bytes += r->headers_out.content_length_n;
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = b;
+ cl->next = NULL;
+
+ *ll = cl;
+
+ } else {
+ ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_v3_filter_ctx_t));
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_http_set_ctx(r, ctx, ngx_http_v3_filter_module);
+ }
+
+ for (cl = out; cl; cl = cl->next) {
+ h3c->total_bytes += cl->buf->last - cl->buf->pos;
+ }
+
+ return ngx_http_write_filter(r, out);
+}
+
+
+static ngx_int_t
+ngx_http_v3_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+ u_char *chunk;
+ off_t size;
+ ngx_int_t rc;
+ ngx_buf_t *b;
+ ngx_chain_t *out, *cl, *tl, **ll;
+ ngx_http_v3_session_t *h3c;
+ ngx_http_v3_filter_ctx_t *ctx;
+
+ if (in == NULL) {
+ return ngx_http_next_body_filter(r, in);
+ }
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_v3_filter_module);
+ if (ctx == NULL) {
+ return ngx_http_next_body_filter(r, in);
+ }
+
+ h3c = ngx_http_v3_get_session(r->connection);
+
+ out = NULL;
+ ll = &out;
+
+ size = 0;
+ cl = in;
+
+ for ( ;; ) {
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http3 chunk: %O", ngx_buf_size(cl->buf));
+
+ size += ngx_buf_size(cl->buf);
+
+ if (cl->buf->flush
+ || cl->buf->sync
+ || ngx_buf_in_memory(cl->buf)
+ || cl->buf->in_file)
+ {
+ tl = ngx_alloc_chain_link(r->pool);
+ if (tl == NULL) {
+ return NGX_ERROR;
+ }
+
+ tl->buf = cl->buf;
+ *ll = tl;
+ ll = &tl->next;
+ }
+
+ if (cl->next == NULL) {
+ break;
+ }
+
+ cl = cl->next;
+ }
+
+ if (size) {
+ tl = ngx_chain_get_free_buf(r->pool, &ctx->free);
+ if (tl == NULL) {
+ return NGX_ERROR;
+ }
+
+ b = tl->buf;
+ chunk = b->start;
+
+ if (chunk == NULL) {
+ chunk = ngx_palloc(r->pool, NGX_HTTP_V3_VARLEN_INT_LEN * 2);
+ if (chunk == NULL) {
+ return NGX_ERROR;
+ }
+
+ b->start = chunk;
+ b->end = chunk + NGX_HTTP_V3_VARLEN_INT_LEN * 2;
+ }
+
+ b->tag = (ngx_buf_tag_t) &ngx_http_v3_filter_module;
+ b->memory = 0;
+ b->temporary = 1;
+ b->pos = chunk;
+
+ 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);
+
+ tl->next = out;
+ out = tl;
+
+ h3c->payload_bytes += size;
+ }
+
+ if (cl->buf->last_buf) {
+ tl = ngx_http_v3_create_trailers(r, ctx);
+ if (tl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf->last_buf = 0;
+
+ *ll = tl;
+
+ } else {
+ *ll = NULL;
+ }
+
+ for (cl = out; cl; cl = cl->next) {
+ h3c->total_bytes += cl->buf->last - cl->buf->pos;
+ }
+
+ rc = ngx_http_next_body_filter(r, out);
+
+ ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &out,
+ (ngx_buf_tag_t) &ngx_http_v3_filter_module);
+
+ return rc;
+}
+
+
+static ngx_chain_t *
+ngx_http_v3_create_trailers(ngx_http_request_t *r,
+ ngx_http_v3_filter_ctx_t *ctx)
+{
+ size_t len, n;
+ u_char *p;
+ ngx_buf_t *b;
+ ngx_uint_t i;
+ ngx_chain_t *cl, *hl;
+ ngx_list_part_t *part;
+ ngx_table_elt_t *header;
+ ngx_http_v3_session_t *h3c;
+
+ h3c = ngx_http_v3_get_session(r->connection);
+
+ len = 0;
+
+ part = &r->headers_out.trailers.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_field_l(NULL, &header[i].key,
+ &header[i].value);
+ }
+
+ cl = ngx_chain_get_free_buf(r->pool, &ctx->free);
+ if (cl == NULL) {
+ return NULL;
+ }
+
+ b = cl->buf;
+
+ b->tag = (ngx_buf_tag_t) &ngx_http_v3_filter_module;
+ b->memory = 0;
+ b->last_buf = 1;
+
+ if (len == 0) {
+ b->temporary = 0;
+ b->pos = b->last = NULL;
+ return cl;
+ }
+
+ b->temporary = 1;
+
+ len += ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0);
+
+ b->pos = ngx_palloc(r->pool, len);
+ if (b->pos == NULL) {
+ return NULL;
+ }
+
+ b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->pos,
+ 0, 0, 0);
+
+ part = &r->headers_out.trailers.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;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http3 output trailer: \"%V: %V\"",
+ &header[i].key, &header[i].value);
+
+ b->last = (u_char *) ngx_http_v3_encode_field_l(b->last,
+ &header[i].key,
+ &header[i].value);
+ }
+
+ n = b->last - b->pos;
+
+ h3c->payload_bytes += n;
+
+ hl = ngx_chain_get_free_buf(r->pool, &ctx->free);
+ if (hl == NULL) {
+ return NULL;
+ }
+
+ b = hl->buf;
+ p = b->start;
+
+ if (p == NULL) {
+ p = ngx_palloc(r->pool, NGX_HTTP_V3_VARLEN_INT_LEN * 2);
+ if (p == NULL) {
+ return NULL;
+ }
+
+ b->start = p;
+ b->end = p + NGX_HTTP_V3_VARLEN_INT_LEN * 2;
+ }
+
+ b->tag = (ngx_buf_tag_t) &ngx_http_v3_filter_module;
+ b->memory = 0;
+ b->temporary = 1;
+ b->pos = p;
+
+ b->last = (u_char *) ngx_http_v3_encode_varlen_int(p,
+ NGX_HTTP_V3_FRAME_HEADERS);
+ b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n);
+
+ hl->next = cl;
+
+ return hl;
+}
+
+
+static ngx_int_t
+ngx_http_v3_filter_init(ngx_conf_t *cf)
+{
+ ngx_http_next_header_filter = ngx_http_top_header_filter;
+ ngx_http_top_header_filter = ngx_http_v3_header_filter;
+
+ ngx_http_next_body_filter = ngx_http_top_body_filter;
+ ngx_http_top_body_filter = ngx_http_v3_body_filter;
+
+ return NGX_OK;
+}
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..875e5f29b
--- /dev/null
+++ b/src/http/v3/ngx_http_v3_module.c
@@ -0,0 +1,389 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ * Copyright (C) Roman Arutyunyan
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static ngx_int_t ngx_http_v3_variable(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 char *ngx_http_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+
+
+static ngx_command_t ngx_http_v3_commands[] = {
+
+ { ngx_string("http3"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_v3_srv_conf_t, enable),
+ NULL },
+
+ { ngx_string("http3_hq"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_v3_srv_conf_t, enable_hq),
+ NULL },
+
+ { ngx_string("http3_max_concurrent_streams"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_v3_srv_conf_t, max_concurrent_streams),
+ NULL },
+
+ { ngx_string("http3_stream_buffer_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_v3_srv_conf_t, quic.stream_buffer_size),
+ NULL },
+
+ { ngx_string("quic_retry"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_v3_srv_conf_t, quic.retry),
+ NULL },
+
+ { ngx_string("quic_gso"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_v3_srv_conf_t, quic.gso_enabled),
+ NULL },
+
+ { ngx_string("quic_host_key"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_http_quic_host_key,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ 0,
+ 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_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("http3"), NULL, ngx_http_v3_variable, 0, 0, 0 },
+
+ ngx_http_null_variable
+};
+
+static ngx_str_t ngx_http_quic_salt = ngx_string("ngx_quic");
+
+
+static ngx_int_t
+ngx_http_v3_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ ngx_http_v3_session_t *h3c;
+
+ if (r->connection->quic) {
+ h3c = ngx_http_v3_get_session(r->connection);
+
+ if (h3c->hq) {
+ v->len = sizeof("hq") - 1;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = (u_char *) "hq";
+
+ return NGX_OK;
+ }
+
+ v->len = sizeof("h3") - 1;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = (u_char *) "h3";
+
+ return NGX_OK;
+ }
+
+ *v = ngx_http_variable_null_value;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v3_add_variables(ngx_conf_t *cf)
+{
+ ngx_http_variable_t *var, *v;
+
+ for (v = ngx_http_v3_vars; v->name.len; v++) {
+ var = ngx_http_add_variable(cf, &v->name, v->flags);
+ if (var == NULL) {
+ return NGX_ERROR;
+ }
+
+ var->get_handler = v->get_handler;
+ var->data = v->data;
+ }
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_http_v3_create_srv_conf(ngx_conf_t *cf)
+{
+ ngx_http_v3_srv_conf_t *h3scf;
+
+ h3scf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v3_srv_conf_t));
+ if (h3scf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * h3scf->quic.host_key = { 0, NULL }
+ * h3scf->quic.stream_reject_code_uni = 0;
+ * h3scf->quic.disable_active_migration = 0;
+ * h3scf->quic.timeout = 0;
+ * h3scf->max_blocked_streams = 0;
+ */
+
+ h3scf->enable = NGX_CONF_UNSET;
+ h3scf->enable_hq = NGX_CONF_UNSET;
+ h3scf->max_table_capacity = NGX_HTTP_V3_MAX_TABLE_CAPACITY;
+ h3scf->max_concurrent_streams = NGX_CONF_UNSET_UINT;
+
+ h3scf->quic.stream_buffer_size = NGX_CONF_UNSET_SIZE;
+ h3scf->quic.max_concurrent_streams_bidi = NGX_CONF_UNSET_UINT;
+ h3scf->quic.max_concurrent_streams_uni = NGX_HTTP_V3_MAX_UNI_STREAMS;
+ h3scf->quic.retry = NGX_CONF_UNSET;
+ h3scf->quic.gso_enabled = NGX_CONF_UNSET;
+ h3scf->quic.stream_close_code = NGX_HTTP_V3_ERR_NO_ERROR;
+ h3scf->quic.stream_reject_code_bidi = NGX_HTTP_V3_ERR_REQUEST_REJECTED;
+ h3scf->quic.active_connection_id_limit = NGX_CONF_UNSET_UINT;
+
+ h3scf->quic.init = ngx_http_v3_init;
+ h3scf->quic.shutdown = ngx_http_v3_shutdown;
+
+ return h3scf;
+}
+
+
+static char *
+ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_v3_srv_conf_t *prev = parent;
+ ngx_http_v3_srv_conf_t *conf = child;
+
+ ngx_http_ssl_srv_conf_t *sscf;
+
+ ngx_conf_merge_value(conf->enable, prev->enable, 1);
+
+ ngx_conf_merge_value(conf->enable_hq, prev->enable_hq, 0);
+
+ ngx_conf_merge_uint_value(conf->max_concurrent_streams,
+ prev->max_concurrent_streams, 128);
+
+ conf->max_blocked_streams = conf->max_concurrent_streams;
+
+ ngx_conf_merge_size_value(conf->quic.stream_buffer_size,
+ prev->quic.stream_buffer_size,
+ 65536);
+
+ conf->quic.max_concurrent_streams_bidi = conf->max_concurrent_streams;
+
+ ngx_conf_merge_value(conf->quic.retry, prev->quic.retry, 0);
+ ngx_conf_merge_value(conf->quic.gso_enabled, prev->quic.gso_enabled, 0);
+
+ ngx_conf_merge_str_value(conf->quic.host_key, prev->quic.host_key, "");
+
+ ngx_conf_merge_uint_value(conf->quic.active_connection_id_limit,
+ prev->quic.active_connection_id_limit,
+ 2);
+
+ if (conf->quic.host_key.len == 0) {
+
+ conf->quic.host_key.len = NGX_QUIC_DEFAULT_HOST_KEY_LEN;
+ conf->quic.host_key.data = ngx_palloc(cf->pool,
+ conf->quic.host_key.len);
+ if (conf->quic.host_key.data == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (RAND_bytes(conf->quic.host_key.data, NGX_QUIC_DEFAULT_HOST_KEY_LEN)
+ <= 0)
+ {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ if (ngx_quic_derive_key(cf->log, "av_token_key",
+ &conf->quic.host_key, &ngx_http_quic_salt,
+ conf->quic.av_token_key, NGX_QUIC_AV_KEY_LEN)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ if (ngx_quic_derive_key(cf->log, "sr_token_key",
+ &conf->quic.host_key, &ngx_http_quic_salt,
+ conf->quic.sr_token_key, NGX_QUIC_SR_KEY_LEN)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ sscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_ssl_module);
+ conf->quic.ssl = &sscf->ssl;
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_v3_srv_conf_t *h3scf = conf;
+
+ u_char *buf;
+ size_t size;
+ ssize_t n;
+ ngx_str_t *value;
+ ngx_file_t file;
+ ngx_file_info_t fi;
+ ngx_quic_conf_t *qcf;
+
+ qcf = &h3scf->quic;
+
+ if (qcf->host_key.len) {
+ return "is duplicate";
+ }
+
+ buf = NULL;
+#if (NGX_SUPPRESS_WARN)
+ size = 0;
+#endif
+
+ value = cf->args->elts;
+
+ if (ngx_conf_full_name(cf->cycle, &value[1], 1) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_memzero(&file, sizeof(ngx_file_t));
+ file.name = value[1];
+ file.log = cf->log;
+
+ file.fd = ngx_open_file(file.name.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);
+
+ if (file.fd == NGX_INVALID_FILE) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
+ ngx_open_file_n " \"%V\" failed", &file.name);
+ return NGX_CONF_ERROR;
+ }
+
+ if (ngx_fd_info(file.fd, &fi) == NGX_FILE_ERROR) {
+ ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno,
+ ngx_fd_info_n " \"%V\" failed", &file.name);
+ goto failed;
+ }
+
+ size = ngx_file_size(&fi);
+
+ if (size == 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"%V\" zero key size", &file.name);
+ goto failed;
+ }
+
+ buf = ngx_pnalloc(cf->pool, size);
+ if (buf == NULL) {
+ goto failed;
+ }
+
+ n = ngx_read_file(&file, buf, size, 0);
+
+ if (n == NGX_ERROR) {
+ ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno,
+ ngx_read_file_n " \"%V\" failed", &file.name);
+ goto failed;
+ }
+
+ if ((size_t) n != size) {
+ ngx_conf_log_error(NGX_LOG_CRIT, cf, 0,
+ ngx_read_file_n " \"%V\" returned only "
+ "%z bytes instead of %uz", &file.name, n, size);
+ goto failed;
+ }
+
+ qcf->host_key.data = buf;
+ qcf->host_key.len = n;
+
+ if (ngx_close_file(file.fd) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
+ ngx_close_file_n " \"%V\" failed", &file.name);
+ }
+
+ return NGX_CONF_OK;
+
+failed:
+
+ if (ngx_close_file(file.fd) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
+ ngx_close_file_n " \"%V\" failed", &file.name);
+ }
+
+ if (buf) {
+ ngx_explicit_memzero(buf, size);
+ }
+
+ return NGX_CONF_ERROR;
+}
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..c51a486c3
--- /dev/null
+++ b/src/http/v3/ngx_http_v3_parse.c
@@ -0,0 +1,1931 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#define ngx_http_v3_is_v2_frame(type) \
+ ((type) == 0x02 || (type) == 0x06 || (type) == 0x08 || (type) == 0x09)
+
+
+static void ngx_http_v3_parse_start_local(ngx_buf_t *b, ngx_buf_t *loc,
+ ngx_uint_t n);
+static void ngx_http_v3_parse_end_local(ngx_buf_t *b, ngx_buf_t *loc,
+ ngx_uint_t *n);
+static ngx_int_t ngx_http_v3_parse_skip(ngx_buf_t *b, ngx_uint_t *length);
+
+static ngx_int_t ngx_http_v3_parse_varlen_int(ngx_connection_t *c,
+ ngx_http_v3_parse_varlen_int_t *st, ngx_buf_t *b);
+static 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, ngx_buf_t *b);
+
+static ngx_int_t ngx_http_v3_parse_field_section_prefix(ngx_connection_t *c,
+ ngx_http_v3_parse_field_section_prefix_t *st, ngx_buf_t *b);
+static ngx_int_t ngx_http_v3_parse_field_rep(ngx_connection_t *c,
+ ngx_http_v3_parse_field_rep_t *st, ngx_uint_t base, ngx_buf_t *b);
+static ngx_int_t ngx_http_v3_parse_literal(ngx_connection_t *c,
+ ngx_http_v3_parse_literal_t *st, ngx_buf_t *b);
+static ngx_int_t ngx_http_v3_parse_field_ri(ngx_connection_t *c,
+ ngx_http_v3_parse_field_t *st, ngx_buf_t *b);
+static ngx_int_t ngx_http_v3_parse_field_lri(ngx_connection_t *c,
+ ngx_http_v3_parse_field_t *st, ngx_buf_t *b);
+static ngx_int_t ngx_http_v3_parse_field_l(ngx_connection_t *c,
+ ngx_http_v3_parse_field_t *st, ngx_buf_t *b);
+static ngx_int_t ngx_http_v3_parse_field_pbi(ngx_connection_t *c,
+ ngx_http_v3_parse_field_t *st, ngx_buf_t *b);
+static ngx_int_t ngx_http_v3_parse_field_lpbi(ngx_connection_t *c,
+ ngx_http_v3_parse_field_t *st, ngx_buf_t *b);
+
+static ngx_int_t ngx_http_v3_parse_control(ngx_connection_t *c,
+ ngx_http_v3_parse_control_t *st, ngx_buf_t *b);
+static ngx_int_t ngx_http_v3_parse_settings(ngx_connection_t *c,
+ ngx_http_v3_parse_settings_t *st, ngx_buf_t *b);
+
+static ngx_int_t ngx_http_v3_parse_encoder(ngx_connection_t *c,
+ ngx_http_v3_parse_encoder_t *st, ngx_buf_t *b);
+static ngx_int_t ngx_http_v3_parse_field_inr(ngx_connection_t *c,
+ ngx_http_v3_parse_field_t *st, ngx_buf_t *b);
+static ngx_int_t ngx_http_v3_parse_field_iln(ngx_connection_t *c,
+ ngx_http_v3_parse_field_t *st, ngx_buf_t *b);
+
+static ngx_int_t ngx_http_v3_parse_decoder(ngx_connection_t *c,
+ ngx_http_v3_parse_decoder_t *st, ngx_buf_t *b);
+
+static ngx_int_t ngx_http_v3_parse_lookup(ngx_connection_t *c,
+ ngx_uint_t dynamic, ngx_uint_t index, ngx_str_t *name, ngx_str_t *value);
+
+
+static void
+ngx_http_v3_parse_start_local(ngx_buf_t *b, ngx_buf_t *loc, ngx_uint_t n)
+{
+ *loc = *b;
+
+ if ((size_t) (loc->last - loc->pos) > n) {
+ loc->last = loc->pos + n;
+ }
+}
+
+
+static void
+ngx_http_v3_parse_end_local(ngx_buf_t *b, ngx_buf_t *loc, ngx_uint_t *pn)
+{
+ *pn -= loc->pos - b->pos;
+ b->pos = loc->pos;
+}
+
+
+static ngx_int_t
+ngx_http_v3_parse_skip(ngx_buf_t *b, ngx_uint_t *length)
+{
+ if ((size_t) (b->last - b->pos) < *length) {
+ *length -= b->last - b->pos;
+ b->pos = b->last;
+ return NGX_AGAIN;
+ }
+
+ b->pos += *length;
+ return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_http_v3_parse_varlen_int(ngx_connection_t *c,
+ ngx_http_v3_parse_varlen_int_t *st, ngx_buf_t *b)
+{
+ 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
+ };
+
+ for ( ;; ) {
+
+ if (b->pos == b->last) {
+ return NGX_AGAIN;
+ }
+
+ ch = *b->pos++;
+
+ 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;
+ }
+ }
+
+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;
+}
+
+
+static 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, ngx_buf_t *b)
+{
+ u_char ch;
+ ngx_uint_t mask;
+ enum {
+ sw_start = 0,
+ sw_value
+ };
+
+ for ( ;; ) {
+
+ if (b->pos == b->last) {
+ return NGX_AGAIN;
+ }
+
+ ch = *b->pos++;
+
+ switch (st->state) {
+
+ case sw_start:
+
+ mask = (1 << prefix) - 1;
+ st->value = ch & mask;
+
+ if (st->value != mask) {
+ goto done;
+ }
+
+ st->shift = 0;
+ st->state = sw_value;
+ break;
+
+ case sw_value:
+
+ st->value += (uint64_t) (ch & 0x7f) << st->shift;
+
+ if (st->shift == 56
+ && ((ch & 0x80) || (st->value & 0xc000000000000000)))
+ {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client exceeded integer size limit");
+ return NGX_HTTP_V3_ERR_EXCESSIVE_LOAD;
+ }
+
+ if (ch & 0x80) {
+ st->shift += 7;
+ break;
+ }
+
+ goto done;
+ }
+ }
+
+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,
+ ngx_buf_t *b)
+{
+ ngx_buf_t loc;
+ ngx_int_t rc;
+ enum {
+ sw_start = 0,
+ sw_type,
+ sw_length,
+ sw_skip,
+ sw_prefix,
+ sw_verify,
+ sw_field_rep,
+ sw_done
+ };
+
+ for ( ;; ) {
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse headers");
+
+ st->state = sw_type;
+
+ /* fall through */
+
+ case sw_type:
+
+ rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->type = st->vlint.value;
+
+ if (ngx_http_v3_is_v2_frame(st->type)
+ || st->type == NGX_HTTP_V3_FRAME_DATA
+ || st->type == NGX_HTTP_V3_FRAME_GOAWAY
+ || st->type == NGX_HTTP_V3_FRAME_SETTINGS
+ || st->type == NGX_HTTP_V3_FRAME_MAX_PUSH_ID
+ || st->type == NGX_HTTP_V3_FRAME_CANCEL_PUSH
+ || st->type == NGX_HTTP_V3_FRAME_PUSH_PROMISE)
+ {
+ return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED;
+ }
+
+ st->state = sw_length;
+ break;
+
+ case sw_length:
+
+ rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->length = st->vlint.value;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse headers type:%ui, len:%ui",
+ st->type, st->length);
+
+ if (st->type != NGX_HTTP_V3_FRAME_HEADERS) {
+ st->state = st->length > 0 ? sw_skip : sw_type;
+ break;
+ }
+
+ if (st->length == 0) {
+ return NGX_HTTP_V3_ERR_FRAME_ERROR;
+ }
+
+ st->state = sw_prefix;
+ break;
+
+ case sw_skip:
+
+ rc = ngx_http_v3_parse_skip(b, &st->length);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->state = sw_type;
+ break;
+
+ case sw_prefix:
+
+ ngx_http_v3_parse_start_local(b, &loc, st->length);
+
+ rc = ngx_http_v3_parse_field_section_prefix(c, &st->prefix, &loc);
+
+ ngx_http_v3_parse_end_local(b, &loc, &st->length);
+
+ if (st->length == 0 && rc == NGX_AGAIN) {
+ return NGX_HTTP_V3_ERR_FRAME_ERROR;
+ }
+
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->state = sw_verify;
+ break;
+
+ case sw_verify:
+
+ rc = ngx_http_v3_check_insert_count(c, st->prefix.insert_count);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ st->state = sw_field_rep;
+
+ /* fall through */
+
+ case sw_field_rep:
+
+ ngx_http_v3_parse_start_local(b, &loc, st->length);
+
+ rc = ngx_http_v3_parse_field_rep(c, &st->field_rep, st->prefix.base,
+ &loc);
+
+ ngx_http_v3_parse_end_local(b, &loc, &st->length);
+
+ if (st->length == 0 && rc == NGX_AGAIN) {
+ return NGX_HTTP_V3_ERR_FRAME_ERROR;
+ }
+
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ if (st->length == 0) {
+ goto done;
+ }
+
+ return NGX_OK;
+ }
+ }
+
+done:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse headers done");
+
+ if (st->prefix.insert_count > 0) {
+ if (ngx_http_v3_send_ack_section(c, c->quic->id) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ ngx_http_v3_ack_insert_count(c, st->prefix.insert_count);
+ }
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_http_v3_parse_field_section_prefix(ngx_connection_t *c,
+ ngx_http_v3_parse_field_section_prefix_t *st, ngx_buf_t *b)
+{
+ u_char ch;
+ ngx_int_t rc;
+ enum {
+ sw_start = 0,
+ sw_req_insert_count,
+ sw_delta_base,
+ sw_read_delta_base
+ };
+
+ for ( ;; ) {
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse field section prefix");
+
+ st->state = sw_req_insert_count;
+
+ /* fall through */
+
+ case sw_req_insert_count:
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 8, b);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->insert_count = st->pint.value;
+ st->state = sw_delta_base;
+ break;
+
+ case sw_delta_base:
+
+ if (b->pos == b->last) {
+ return NGX_AGAIN;
+ }
+
+ ch = *b->pos;
+
+ st->sign = (ch & 0x80) ? 1 : 0;
+ st->state = sw_read_delta_base;
+
+ /* fall through */
+
+ case sw_read_delta_base:
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->delta_base = st->pint.value;
+ goto done;
+ }
+ }
+
+done:
+
+ rc = ngx_http_v3_decode_insert_count(c, &st->insert_count);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ if (st->sign) {
+ if (st->insert_count <= st->delta_base) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent negative base");
+ return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED;
+ }
+
+ 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 field section prefix done "
+ "insert_count:%ui, sign:%ui, delta_base:%ui, base:%ui",
+ st->insert_count, st->sign, st->delta_base, st->base);
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_http_v3_parse_field_rep(ngx_connection_t *c,
+ ngx_http_v3_parse_field_rep_t *st, ngx_uint_t base, ngx_buf_t *b)
+{
+ u_char ch;
+ ngx_int_t rc;
+ enum {
+ sw_start = 0,
+ sw_field_ri,
+ sw_field_lri,
+ sw_field_l,
+ sw_field_pbi,
+ sw_field_lpbi
+ };
+
+ if (st->state == sw_start) {
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse field representation");
+
+ if (b->pos == b->last) {
+ return NGX_AGAIN;
+ }
+
+ ch = *b->pos;
+
+ ngx_memzero(&st->field, sizeof(ngx_http_v3_parse_field_t));
+
+ st->field.base = base;
+
+ if (ch & 0x80) {
+ /* Indexed Field Line */
+
+ st->state = sw_field_ri;
+
+ } else if (ch & 0x40) {
+ /* Literal Field Line With Name Reference */
+
+ st->state = sw_field_lri;
+
+ } else if (ch & 0x20) {
+ /* Literal Field Line With Literal Name */
+
+ st->state = sw_field_l;
+
+ } else if (ch & 0x10) {
+ /* Indexed Field Line With Post-Base Index */
+
+ st->state = sw_field_pbi;
+
+ } else {
+ /* Literal Field Line With Post-Base Name Reference */
+
+ st->state = sw_field_lpbi;
+ }
+ }
+
+ switch (st->state) {
+
+ case sw_field_ri:
+ rc = ngx_http_v3_parse_field_ri(c, &st->field, b);
+ break;
+
+ case sw_field_lri:
+ rc = ngx_http_v3_parse_field_lri(c, &st->field, b);
+ break;
+
+ case sw_field_l:
+ rc = ngx_http_v3_parse_field_l(c, &st->field, b);
+ break;
+
+ case sw_field_pbi:
+ rc = ngx_http_v3_parse_field_pbi(c, &st->field, b);
+ break;
+
+ case sw_field_lpbi:
+ rc = ngx_http_v3_parse_field_lpbi(c, &st->field, b);
+ break;
+
+ default:
+ rc = NGX_OK;
+ }
+
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse field representation done");
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_http_v3_parse_literal(ngx_connection_t *c, ngx_http_v3_parse_literal_t *st,
+ ngx_buf_t *b)
+{
+ u_char ch;
+ ngx_uint_t n;
+ ngx_http_core_srv_conf_t *cscf;
+ enum {
+ sw_start = 0,
+ sw_value
+ };
+
+ for ( ;; ) {
+
+ 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;
+
+ cscf = ngx_http_v3_get_module_srv_conf(c, ngx_http_core_module);
+
+ if (n > cscf->large_client_header_buffers.size) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client sent too large field line");
+ return NGX_HTTP_V3_ERR_EXCESSIVE_LOAD;
+ }
+
+ if (st->huffman) {
+ n = n * 8 / 5;
+ st->huffstate = 0;
+ }
+
+ st->last = ngx_pnalloc(c->pool, n + 1);
+ if (st->last == NULL) {
+ return NGX_ERROR;
+ }
+
+ st->value.data = st->last;
+ st->state = sw_value;
+
+ /* fall through */
+
+ case sw_value:
+
+ if (b->pos == b->last) {
+ return NGX_AGAIN;
+ }
+
+ ch = *b->pos++;
+
+ if (st->huffman) {
+ if (ngx_http_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;
+ }
+ }
+
+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;
+}
+
+
+static ngx_int_t
+ngx_http_v3_parse_field_ri(ngx_connection_t *c, ngx_http_v3_parse_field_t *st,
+ ngx_buf_t *b)
+{
+ u_char ch;
+ ngx_int_t rc;
+ enum {
+ sw_start = 0,
+ sw_index
+ };
+
+ for ( ;; ) {
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse field ri");
+
+ if (b->pos == b->last) {
+ return NGX_AGAIN;
+ }
+
+ ch = *b->pos;
+
+ st->dynamic = (ch & 0x40) ? 0 : 1;
+ st->state = sw_index;
+
+ /* fall through */
+
+ case sw_index:
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, b);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->index = st->pint.value;
+ goto done;
+ }
+ }
+
+done:
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse field ri done %s%ui]",
+ st->dynamic ? "dynamic[-" : "static[", st->index);
+
+ if (st->dynamic) {
+ st->index = st->base - st->index - 1;
+ }
+
+ rc = ngx_http_v3_parse_lookup(c, st->dynamic, st->index, &st->name,
+ &st->value);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_http_v3_parse_field_lri(ngx_connection_t *c,
+ ngx_http_v3_parse_field_t *st, ngx_buf_t *b)
+{
+ u_char ch;
+ ngx_int_t rc;
+ enum {
+ sw_start = 0,
+ sw_index,
+ sw_value_len,
+ sw_read_value_len,
+ sw_value
+ };
+
+ for ( ;; ) {
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse field lri");
+
+ if (b->pos == b->last) {
+ return NGX_AGAIN;
+ }
+
+ ch = *b->pos;
+
+ st->dynamic = (ch & 0x10) ? 0 : 1;
+ st->state = sw_index;
+
+ /* fall through */
+
+ case sw_index:
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 4, b);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->index = st->pint.value;
+ st->state = sw_value_len;
+ break;
+
+ case sw_value_len:
+
+ if (b->pos == b->last) {
+ return NGX_AGAIN;
+ }
+
+ ch = *b->pos;
+
+ st->literal.huffman = (ch & 0x80) ? 1 : 0;
+ st->state = sw_read_value_len;
+
+ /* fall through */
+
+ case sw_read_value_len:
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->literal.length = st->pint.value;
+ if (st->literal.length == 0) {
+ goto done;
+ }
+
+ st->state = sw_value;
+ break;
+
+ case sw_value:
+
+ rc = ngx_http_v3_parse_literal(c, &st->literal, b);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->value = st->literal.value;
+ goto done;
+ }
+ }
+
+done:
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse field lri done %s%ui] \"%V\"",
+ st->dynamic ? "dynamic[-" : "static[",
+ st->index, &st->value);
+
+ if (st->dynamic) {
+ st->index = st->base - st->index - 1;
+ }
+
+ rc = ngx_http_v3_parse_lookup(c, st->dynamic, st->index, &st->name, NULL);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_http_v3_parse_field_l(ngx_connection_t *c,
+ ngx_http_v3_parse_field_t *st, ngx_buf_t *b)
+{
+ 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
+ };
+
+ for ( ;; ) {
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse field l");
+
+ if (b->pos == b->last) {
+ return NGX_AGAIN;
+ }
+
+ ch = *b->pos;
+
+ st->literal.huffman = (ch & 0x08) ? 1 : 0;
+ st->state = sw_name_len;
+
+ /* fall through */
+
+ case sw_name_len:
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 3, b);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->literal.length = st->pint.value;
+ if (st->literal.length == 0) {
+ return NGX_ERROR;
+ }
+
+ st->state = sw_name;
+ break;
+
+ case sw_name:
+
+ rc = ngx_http_v3_parse_literal(c, &st->literal, b);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->name = st->literal.value;
+ st->state = sw_value_len;
+ break;
+
+ case sw_value_len:
+
+ if (b->pos == b->last) {
+ return NGX_AGAIN;
+ }
+
+ ch = *b->pos;
+
+ st->literal.huffman = (ch & 0x80) ? 1 : 0;
+ st->state = sw_read_value_len;
+
+ /* fall through */
+
+ case sw_read_value_len:
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->literal.length = st->pint.value;
+ if (st->literal.length == 0) {
+ goto done;
+ }
+
+ st->state = sw_value;
+ break;
+
+ case sw_value:
+
+ rc = ngx_http_v3_parse_literal(c, &st->literal, b);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->value = st->literal.value;
+ goto done;
+ }
+ }
+
+done:
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse field l done \"%V\" \"%V\"",
+ &st->name, &st->value);
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_http_v3_parse_field_pbi(ngx_connection_t *c,
+ ngx_http_v3_parse_field_t *st, ngx_buf_t *b)
+{
+ ngx_int_t rc;
+ enum {
+ sw_start = 0,
+ sw_index
+ };
+
+ for ( ;; ) {
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse field pbi");
+
+ st->state = sw_index;
+
+ /* fall through */
+
+ case sw_index:
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 4, b);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->index = st->pint.value;
+ goto done;
+ }
+ }
+
+done:
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse field pbi done dynamic[+%ui]", st->index);
+
+ rc = ngx_http_v3_parse_lookup(c, 1, st->base + st->index, &st->name,
+ &st->value);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_http_v3_parse_field_lpbi(ngx_connection_t *c,
+ ngx_http_v3_parse_field_t *st, ngx_buf_t *b)
+{
+ u_char ch;
+ ngx_int_t rc;
+ enum {
+ sw_start = 0,
+ sw_index,
+ sw_value_len,
+ sw_read_value_len,
+ sw_value
+ };
+
+ for ( ;; ) {
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse field lpbi");
+
+ st->state = sw_index;
+
+ /* fall through */
+
+ case sw_index:
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 3, b);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->index = st->pint.value;
+ st->state = sw_value_len;
+ break;
+
+ case sw_value_len:
+
+ if (b->pos == b->last) {
+ return NGX_AGAIN;
+ }
+
+ ch = *b->pos;
+
+ st->literal.huffman = (ch & 0x80) ? 1 : 0;
+ st->state = sw_read_value_len;
+
+ /* fall through */
+
+ case sw_read_value_len:
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->literal.length = st->pint.value;
+ if (st->literal.length == 0) {
+ goto done;
+ }
+
+ st->state = sw_value;
+ break;
+
+ case sw_value:
+
+ rc = ngx_http_v3_parse_literal(c, &st->literal, b);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->value = st->literal.value;
+ goto done;
+ }
+ }
+
+done:
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse field lpbi done dynamic[+%ui] \"%V\"",
+ st->index, &st->value);
+
+ rc = ngx_http_v3_parse_lookup(c, 1, st->base + st->index, &st->name, NULL);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_http_v3_parse_lookup(ngx_connection_t *c, ngx_uint_t dynamic,
+ ngx_uint_t index, ngx_str_t *name, ngx_str_t *value)
+{
+ u_char *p;
+
+ if (!dynamic) {
+ if (ngx_http_v3_lookup_static(c, index, name, value) != NGX_OK) {
+ return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED;
+ }
+
+ return NGX_OK;
+ }
+
+ if (ngx_http_v3_lookup(c, index, name, value) != NGX_OK) {
+ return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED;
+ }
+
+ if (name) {
+ p = ngx_pnalloc(c->pool, name->len + 1);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(p, name->data, name->len);
+ p[name->len] = '\0';
+ name->data = p;
+ }
+
+ if (value) {
+ p = ngx_pnalloc(c->pool, value->len + 1);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(p, value->data, value->len);
+ p[value->len] = '\0';
+ value->data = p;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v3_parse_control(ngx_connection_t *c, ngx_http_v3_parse_control_t *st,
+ ngx_buf_t *b)
+{
+ ngx_buf_t loc;
+ ngx_int_t rc;
+ enum {
+ sw_start = 0,
+ sw_first_type,
+ sw_type,
+ sw_length,
+ sw_settings,
+ sw_skip
+ };
+
+ for ( ;; ) {
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse control");
+
+ st->state = sw_first_type;
+
+ /* fall through */
+
+ case sw_first_type:
+ case sw_type:
+
+ rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->type = st->vlint.value;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse frame type:%ui", st->type);
+
+ if (st->state == sw_first_type
+ && st->type != NGX_HTTP_V3_FRAME_SETTINGS)
+ {
+ return NGX_HTTP_V3_ERR_MISSING_SETTINGS;
+ }
+
+ if (st->state != sw_first_type
+ && st->type == NGX_HTTP_V3_FRAME_SETTINGS)
+ {
+ return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED;
+ }
+
+ if (ngx_http_v3_is_v2_frame(st->type)
+ || st->type == NGX_HTTP_V3_FRAME_DATA
+ || st->type == NGX_HTTP_V3_FRAME_HEADERS
+ || st->type == NGX_HTTP_V3_FRAME_PUSH_PROMISE)
+ {
+ return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED;
+ }
+
+ if (st->type == NGX_HTTP_V3_FRAME_CANCEL_PUSH) {
+ return NGX_HTTP_V3_ERR_ID_ERROR;
+ }
+
+ st->state = sw_length;
+ break;
+
+ case sw_length:
+
+ rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse frame len:%uL", st->vlint.value);
+
+ st->length = st->vlint.value;
+ if (st->length == 0) {
+ st->state = sw_type;
+ break;
+ }
+
+ switch (st->type) {
+
+ case NGX_HTTP_V3_FRAME_SETTINGS:
+ st->state = sw_settings;
+ 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:
+
+ ngx_http_v3_parse_start_local(b, &loc, st->length);
+
+ rc = ngx_http_v3_parse_settings(c, &st->settings, &loc);
+
+ ngx_http_v3_parse_end_local(b, &loc, &st->length);
+
+ if (st->length == 0 && rc == NGX_AGAIN) {
+ return NGX_HTTP_V3_ERR_SETTINGS_ERROR;
+ }
+
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ if (st->length == 0) {
+ st->state = sw_type;
+ }
+
+ break;
+
+ case sw_skip:
+
+ rc = ngx_http_v3_parse_skip(b, &st->length);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->state = sw_type;
+ break;
+ }
+ }
+}
+
+
+static ngx_int_t
+ngx_http_v3_parse_settings(ngx_connection_t *c,
+ ngx_http_v3_parse_settings_t *st, ngx_buf_t *b)
+{
+ ngx_int_t rc;
+ enum {
+ sw_start = 0,
+ sw_id,
+ sw_value
+ };
+
+ for ( ;; ) {
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse settings");
+
+ st->state = sw_id;
+
+ /* fall through */
+
+ case sw_id:
+
+ rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->id = st->vlint.value;
+ st->state = sw_value;
+ break;
+
+ case sw_value:
+
+ rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ if (ngx_http_v3_set_param(c, st->id, st->vlint.value) != NGX_OK) {
+ return NGX_HTTP_V3_ERR_SETTINGS_ERROR;
+ }
+
+ goto done;
+ }
+ }
+
+done:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse settings done");
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_http_v3_parse_encoder(ngx_connection_t *c, ngx_http_v3_parse_encoder_t *st,
+ ngx_buf_t *b)
+{
+ u_char ch;
+ ngx_int_t rc;
+ enum {
+ sw_start = 0,
+ sw_inr,
+ sw_iln,
+ sw_capacity,
+ sw_duplicate
+ };
+
+ for ( ;; ) {
+
+ if (st->state == sw_start) {
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse encoder instruction");
+
+ if (b->pos == b->last) {
+ return NGX_AGAIN;
+ }
+
+ ch = *b->pos;
+
+ if (ch & 0x80) {
+ /* Insert With Name Reference */
+
+ st->state = sw_inr;
+
+ } else if (ch & 0x40) {
+ /* Insert With Literal Name */
+
+ st->state = sw_iln;
+
+ } 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_field_inr(c, &st->field, b);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->state = sw_start;
+ break;
+
+ case sw_iln:
+
+ rc = ngx_http_v3_parse_field_iln(c, &st->field, b);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->state = sw_start;
+ break;
+
+ case sw_capacity:
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 5, b);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ rc = ngx_http_v3_set_capacity(c, st->pint.value);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ st->state = sw_start;
+ break;
+
+ default: /* sw_duplicate */
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 5, b);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ rc = ngx_http_v3_duplicate(c, st->pint.value);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ st->state = sw_start;
+ break;
+ }
+ }
+}
+
+
+static ngx_int_t
+ngx_http_v3_parse_field_inr(ngx_connection_t *c,
+ ngx_http_v3_parse_field_t *st, ngx_buf_t *b)
+{
+ u_char ch;
+ ngx_int_t rc;
+ enum {
+ sw_start = 0,
+ sw_name_index,
+ sw_value_len,
+ sw_read_value_len,
+ sw_value
+ };
+
+ for ( ;; ) {
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse field inr");
+
+ if (b->pos == b->last) {
+ return NGX_AGAIN;
+ }
+
+ ch = *b->pos;
+
+ st->dynamic = (ch & 0x40) ? 0 : 1;
+ st->state = sw_name_index;
+
+ /* fall through */
+
+ case sw_name_index:
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, b);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->index = st->pint.value;
+ st->state = sw_value_len;
+ break;
+
+ case sw_value_len:
+
+ if (b->pos == b->last) {
+ return NGX_AGAIN;
+ }
+
+ ch = *b->pos;
+
+ st->literal.huffman = (ch & 0x80) ? 1 : 0;
+ st->state = sw_read_value_len;
+
+ /* fall through */
+
+ case sw_read_value_len:
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->literal.length = st->pint.value;
+ if (st->literal.length == 0) {
+ st->value.len = 0;
+ goto done;
+ }
+
+ st->state = sw_value;
+ break;
+
+ case sw_value:
+
+ rc = ngx_http_v3_parse_literal(c, &st->literal, b);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->value = st->literal.value;
+ goto done;
+ }
+ }
+
+done:
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse field inr done %s[%ui] \"%V\"",
+ st->dynamic ? "dynamic" : "static",
+ st->index, &st->value);
+
+ rc = ngx_http_v3_ref_insert(c, st->dynamic, st->index, &st->value);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_http_v3_parse_field_iln(ngx_connection_t *c,
+ ngx_http_v3_parse_field_t *st, ngx_buf_t *b)
+{
+ 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
+ };
+
+ for ( ;; ) {
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse field iln");
+
+ if (b->pos == b->last) {
+ return NGX_AGAIN;
+ }
+
+ ch = *b->pos;
+
+ st->literal.huffman = (ch & 0x20) ? 1 : 0;
+ st->state = sw_name_len;
+
+ /* fall through */
+
+ case sw_name_len:
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 5, b);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->literal.length = st->pint.value;
+ if (st->literal.length == 0) {
+ return NGX_ERROR;
+ }
+
+ st->state = sw_name;
+ break;
+
+ case sw_name:
+
+ rc = ngx_http_v3_parse_literal(c, &st->literal, b);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->name = st->literal.value;
+ st->state = sw_value_len;
+ break;
+
+ case sw_value_len:
+
+ if (b->pos == b->last) {
+ return NGX_AGAIN;
+ }
+
+ ch = *b->pos;
+
+ st->literal.huffman = (ch & 0x80) ? 1 : 0;
+ st->state = sw_read_value_len;
+
+ /* fall through */
+
+ case sw_read_value_len:
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->literal.length = st->pint.value;
+ if (st->literal.length == 0) {
+ st->value.len = 0;
+ goto done;
+ }
+
+ st->state = sw_value;
+ break;
+
+ case sw_value:
+
+ rc = ngx_http_v3_parse_literal(c, &st->literal, b);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->value = st->literal.value;
+ goto done;
+ }
+ }
+
+done:
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse field iln done \"%V\":\"%V\"",
+ &st->name, &st->value);
+
+ rc = ngx_http_v3_insert(c, &st->name, &st->value);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_http_v3_parse_decoder(ngx_connection_t *c, ngx_http_v3_parse_decoder_t *st,
+ ngx_buf_t *b)
+{
+ u_char ch;
+ ngx_int_t rc;
+ enum {
+ sw_start = 0,
+ sw_ack_section,
+ sw_cancel_stream,
+ sw_inc_insert_count
+ };
+
+ for ( ;; ) {
+
+ if (st->state == sw_start) {
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse decoder instruction");
+
+ if (b->pos == b->last) {
+ return NGX_AGAIN;
+ }
+
+ ch = *b->pos;
+
+ if (ch & 0x80) {
+ /* Section Acknowledgment */
+
+ st->state = sw_ack_section;
+
+ } 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_section:
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ rc = ngx_http_v3_ack_section(c, st->pint.value);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ st->state = sw_start;
+ break;
+
+ case sw_cancel_stream:
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, b);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ rc = ngx_http_v3_cancel_stream(c, st->pint.value);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ st->state = sw_start;
+ break;
+
+ case sw_inc_insert_count:
+
+ rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, b);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ rc = ngx_http_v3_inc_insert_count(c, st->pint.value);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ st->state = sw_start;
+ break;
+ }
+ }
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_data(ngx_connection_t *c, ngx_http_v3_parse_data_t *st,
+ ngx_buf_t *b)
+{
+ ngx_int_t rc;
+ enum {
+ sw_start = 0,
+ sw_type,
+ sw_length,
+ sw_skip
+ };
+
+ for ( ;; ) {
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse data");
+
+ st->state = sw_type;
+
+ /* fall through */
+
+ case sw_type:
+
+ rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->type = st->vlint.value;
+
+ if (st->type == NGX_HTTP_V3_FRAME_HEADERS) {
+ /* trailers */
+ goto done;
+ }
+
+ if (ngx_http_v3_is_v2_frame(st->type)
+ || st->type == NGX_HTTP_V3_FRAME_GOAWAY
+ || st->type == NGX_HTTP_V3_FRAME_SETTINGS
+ || st->type == NGX_HTTP_V3_FRAME_MAX_PUSH_ID
+ || st->type == NGX_HTTP_V3_FRAME_CANCEL_PUSH
+ || st->type == NGX_HTTP_V3_FRAME_PUSH_PROMISE)
+ {
+ return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED;
+ }
+
+ st->state = sw_length;
+ break;
+
+ case sw_length:
+
+ rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->length = st->vlint.value;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse data type:%ui, len:%ui",
+ st->type, st->length);
+
+ if (st->type != NGX_HTTP_V3_FRAME_DATA && st->length > 0) {
+ st->state = sw_skip;
+ break;
+ }
+
+ st->state = sw_type;
+ return NGX_OK;
+
+ case sw_skip:
+
+ rc = ngx_http_v3_parse_skip(b, &st->length);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ st->state = sw_type;
+ break;
+ }
+ }
+
+done:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse data done");
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_uni(ngx_connection_t *c, ngx_http_v3_parse_uni_t *st,
+ ngx_buf_t *b)
+{
+ ngx_int_t rc;
+ enum {
+ sw_start = 0,
+ sw_type,
+ sw_control,
+ sw_encoder,
+ sw_decoder,
+ sw_unknown
+ };
+
+ for ( ;; ) {
+
+ switch (st->state) {
+ case sw_start:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse uni");
+
+ st->state = sw_type;
+
+ /* fall through */
+
+ case sw_type:
+
+ rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b);
+ if (rc != NGX_DONE) {
+ return rc;
+ }
+
+ rc = ngx_http_v3_register_uni_stream(c, st->vlint.value);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ switch (st->vlint.value) {
+ case NGX_HTTP_V3_STREAM_CONTROL:
+ st->state = sw_control;
+ break;
+
+ case NGX_HTTP_V3_STREAM_ENCODER:
+ st->state = sw_encoder;
+ break;
+
+ case NGX_HTTP_V3_STREAM_DECODER:
+ st->state = sw_decoder;
+ break;
+
+ default:
+ st->state = sw_unknown;
+ }
+
+ break;
+
+ case sw_control:
+
+ return ngx_http_v3_parse_control(c, &st->u.control, b);
+
+ case sw_encoder:
+
+ return ngx_http_v3_parse_encoder(c, &st->u.encoder, b);
+
+ case sw_decoder:
+
+ return ngx_http_v3_parse_decoder(c, &st->u.decoder, b);
+
+ case sw_unknown:
+
+ b->pos = b->last;
+ return NGX_AGAIN;
+ }
+ }
+}
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..ba004db5d
--- /dev/null
+++ b/src/http/v3/ngx_http_v3_parse.h
@@ -0,0 +1,146 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_HTTP_V3_PARSE_H_INCLUDED_
+#define _NGX_HTTP_V3_PARSE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ ngx_uint_t state;
+ uint64_t value;
+} ngx_http_v3_parse_varlen_int_t;
+
+
+typedef struct {
+ ngx_uint_t state;
+ ngx_uint_t shift;
+ uint64_t value;
+} ngx_http_v3_parse_prefix_int_t;
+
+
+typedef struct {
+ ngx_uint_t state;
+ uint64_t id;
+ ngx_http_v3_parse_varlen_int_t vlint;
+} ngx_http_v3_parse_settings_t;
+
+
+typedef struct {
+ ngx_uint_t state;
+ ngx_uint_t insert_count;
+ ngx_uint_t delta_base;
+ ngx_uint_t sign;
+ ngx_uint_t base;
+ ngx_http_v3_parse_prefix_int_t pint;
+} ngx_http_v3_parse_field_section_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_field_t;
+
+
+typedef struct {
+ ngx_uint_t state;
+ ngx_http_v3_parse_field_t field;
+} ngx_http_v3_parse_field_rep_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_field_section_prefix_t prefix;
+ ngx_http_v3_parse_field_rep_t field_rep;
+} ngx_http_v3_parse_headers_t;
+
+
+typedef struct {
+ ngx_uint_t state;
+ ngx_http_v3_parse_field_t field;
+ 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_http_v3_parse_varlen_int_t vlint;
+ union {
+ ngx_http_v3_parse_encoder_t encoder;
+ ngx_http_v3_parse_decoder_t decoder;
+ ngx_http_v3_parse_control_t control;
+ } u;
+} ngx_http_v3_parse_uni_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_data_t;
+
+
+/*
+ * Parse functions return codes:
+ * NGX_DONE - parsing done
+ * NGX_OK - sub-element done
+ * NGX_AGAIN - more data expected
+ * NGX_BUSY - waiting for external event
+ * NGX_ERROR - internal error
+ * NGX_HTTP_V3_ERROR_XXX - HTTP/3 or QPACK error
+ */
+
+ngx_int_t ngx_http_v3_parse_headers(ngx_connection_t *c,
+ ngx_http_v3_parse_headers_t *st, ngx_buf_t *b);
+ngx_int_t ngx_http_v3_parse_data(ngx_connection_t *c,
+ ngx_http_v3_parse_data_t *st, ngx_buf_t *b);
+ngx_int_t ngx_http_v3_parse_uni(ngx_connection_t *c,
+ ngx_http_v3_parse_uni_t *st, ngx_buf_t *b);
+
+
+#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..6f72dc402
--- /dev/null
+++ b/src/http/v3/ngx_http_v3_request.c
@@ -0,0 +1,1716 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static void ngx_http_v3_init_request_stream(ngx_connection_t *c);
+static void ngx_http_v3_wait_request_handler(ngx_event_t *rev);
+static void ngx_http_v3_cleanup_connection(void *data);
+static void ngx_http_v3_cleanup_request(void *data);
+static void ngx_http_v3_process_request(ngx_event_t *rev);
+static ngx_int_t ngx_http_v3_process_header(ngx_http_request_t *r,
+ ngx_str_t *name, ngx_str_t *value);
+static ngx_int_t ngx_http_v3_validate_header(ngx_http_request_t *r,
+ ngx_str_t *name, ngx_str_t *value);
+static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r,
+ ngx_str_t *name, ngx_str_t *value);
+static ngx_int_t ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r);
+static ngx_int_t ngx_http_v3_process_request_header(ngx_http_request_t *r);
+static ngx_int_t ngx_http_v3_cookie(ngx_http_request_t *r, ngx_str_t *value);
+static ngx_int_t ngx_http_v3_construct_cookie_header(ngx_http_request_t *r);
+static void ngx_http_v3_read_client_request_body_handler(ngx_http_request_t *r);
+static ngx_int_t ngx_http_v3_do_read_client_request_body(ngx_http_request_t *r);
+static ngx_int_t ngx_http_v3_request_body_filter(ngx_http_request_t *r,
+ ngx_chain_t *in);
+
+
+static const 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_string("CONNECT"), NGX_HTTP_CONNECT }
+};
+
+
+void
+ngx_http_v3_init_stream(ngx_connection_t *c)
+{
+ ngx_http_v3_session_t *h3c;
+ ngx_http_connection_t *hc, *phc;
+ ngx_http_v3_srv_conf_t *h3scf;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_core_srv_conf_t *cscf;
+
+ hc = c->data;
+
+ hc->ssl = 1;
+
+ clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module);
+ cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module);
+ h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module);
+
+ if (c->quic == NULL) {
+ if (ngx_http_v3_init_session(c) != NGX_OK) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ h3c = hc->v3_session;
+ ngx_add_timer(&h3c->keepalive, cscf->client_header_timeout);
+
+ h3scf->quic.timeout = clcf->keepalive_timeout;
+ ngx_quic_run(c, &h3scf->quic);
+ return;
+ }
+
+ phc = ngx_http_quic_get_connection(c);
+
+ if (phc->ssl_servername) {
+ hc->ssl_servername = phc->ssl_servername;
+#if (NGX_PCRE)
+ hc->ssl_servername_regex = phc->ssl_servername_regex;
+#endif
+ hc->conf_ctx = phc->conf_ctx;
+
+ ngx_set_connection_log(c, clcf->error_log);
+ }
+
+ if (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) {
+ ngx_http_v3_init_uni_stream(c);
+
+ } else {
+ ngx_http_v3_init_request_stream(c);
+ }
+}
+
+
+ngx_int_t
+ngx_http_v3_init(ngx_connection_t *c)
+{
+ unsigned int len;
+ const unsigned char *data;
+ ngx_http_v3_session_t *h3c;
+ ngx_http_v3_srv_conf_t *h3scf;
+ ngx_http_core_loc_conf_t *clcf;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init");
+
+ h3c = ngx_http_v3_get_session(c);
+ clcf = ngx_http_v3_get_module_loc_conf(c, ngx_http_core_module);
+ ngx_add_timer(&h3c->keepalive, clcf->keepalive_timeout);
+
+ h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module);
+
+ if (h3scf->enable_hq) {
+ if (!h3scf->enable) {
+ h3c->hq = 1;
+ return NGX_OK;
+ }
+
+ SSL_get0_alpn_selected(c->ssl->connection, &data, &len);
+
+ if (len == sizeof(NGX_HTTP_V3_HQ_PROTO) - 1
+ && ngx_strncmp(data, NGX_HTTP_V3_HQ_PROTO, len) == 0)
+ {
+ h3c->hq = 1;
+ return NGX_OK;
+ }
+ }
+
+ return ngx_http_v3_send_settings(c);
+}
+
+
+void
+ngx_http_v3_shutdown(ngx_connection_t *c)
+{
+ ngx_http_v3_session_t *h3c;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 shutdown");
+
+ h3c = ngx_http_v3_get_session(c);
+
+ if (h3c == NULL) {
+ ngx_quic_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR,
+ "connection shutdown");
+ return;
+ }
+
+ if (!h3c->goaway) {
+ h3c->goaway = 1;
+
+ if (!h3c->hq) {
+ (void) ngx_http_v3_send_goaway(c, h3c->next_request_id);
+ }
+
+ ngx_http_v3_shutdown_connection(c, NGX_HTTP_V3_ERR_NO_ERROR,
+ "connection shutdown");
+ }
+}
+
+
+static void
+ngx_http_v3_init_request_stream(ngx_connection_t *c)
+{
+ uint64_t n;
+ ngx_event_t *rev;
+ ngx_pool_cleanup_t *cln;
+ ngx_http_connection_t *hc;
+ ngx_http_v3_session_t *h3c;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_core_srv_conf_t *cscf;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init request stream");
+
+#if (NGX_STAT_STUB)
+ (void) ngx_atomic_fetch_add(ngx_stat_active, 1);
+#endif
+
+ hc = c->data;
+
+ clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module);
+
+ n = c->quic->id >> 2;
+
+ if (n >= clcf->keepalive_requests * 2) {
+ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD,
+ "too many requests per connection");
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ h3c = ngx_http_v3_get_session(c);
+
+ if (h3c->goaway) {
+ c->close = 1;
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ h3c->next_request_id = c->quic->id + 0x04;
+
+ if (n + 1 == clcf->keepalive_requests
+ || ngx_current_msec - c->start_time > clcf->keepalive_time)
+ {
+ h3c->goaway = 1;
+
+ if (!h3c->hq) {
+ if (ngx_http_v3_send_goaway(c, h3c->next_request_id) != NGX_OK) {
+ ngx_http_close_connection(c);
+ return;
+ }
+ }
+
+ ngx_http_v3_shutdown_connection(c, NGX_HTTP_V3_ERR_NO_ERROR,
+ "reached maximum number of requests");
+ }
+
+ cln = ngx_pool_cleanup_add(c->pool, 0);
+ if (cln == NULL) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ cln->handler = ngx_http_v3_cleanup_connection;
+ cln->data = c;
+
+ h3c->nrequests++;
+
+ if (h3c->keepalive.timer_set) {
+ ngx_del_timer(&h3c->keepalive);
+ }
+
+ rev = c->read;
+
+ if (!h3c->hq) {
+ rev->handler = ngx_http_v3_wait_request_handler;
+ c->write->handler = ngx_http_empty_handler;
+ }
+
+ if (rev->ready) {
+ rev->handler(rev);
+ return;
+ }
+
+ cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module);
+
+ ngx_add_timer(rev, cscf->client_header_timeout);
+ ngx_reusable_connection(c, 1);
+
+ if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+ ngx_http_close_connection(c);
+ return;
+ }
+}
+
+
+static void
+ngx_http_v3_wait_request_handler(ngx_event_t *rev)
+{
+ size_t size;
+ ssize_t n;
+ ngx_buf_t *b;
+ ngx_connection_t *c;
+ ngx_pool_cleanup_t *cln;
+ ngx_http_request_t *r;
+ ngx_http_connection_t *hc;
+ ngx_http_core_srv_conf_t *cscf;
+
+ c = rev->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 wait request handler");
+
+ if (rev->timedout) {
+ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+ c->timedout = 1;
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ if (c->close) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ hc = c->data;
+ cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module);
+
+ size = cscf->client_header_buffer_size;
+
+ b = c->buffer;
+
+ if (b == NULL) {
+ b = ngx_create_temp_buf(c->pool, size);
+ if (b == NULL) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ c->buffer = b;
+
+ } else if (b->start == NULL) {
+
+ b->start = ngx_palloc(c->pool, size);
+ if (b->start == NULL) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ b->pos = b->start;
+ b->last = b->start;
+ b->end = b->last + size;
+ }
+
+ n = c->recv(c, b->last, size);
+
+ if (n == NGX_AGAIN) {
+
+ if (!rev->timer_set) {
+ ngx_add_timer(rev, cscf->client_header_timeout);
+ ngx_reusable_connection(c, 1);
+ }
+
+ if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ /*
+ * We are trying to not hold c->buffer's memory for an idle connection.
+ */
+
+ if (ngx_pfree(c->pool, b->start) == NGX_OK) {
+ b->start = NULL;
+ }
+
+ return;
+ }
+
+ if (n == NGX_ERROR) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ if (n == 0) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client closed connection");
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ b->last += n;
+
+ c->log->action = "reading client request";
+
+ ngx_reusable_connection(c, 0);
+
+ r = ngx_http_create_request(c);
+ if (r == NULL) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ r->http_version = NGX_HTTP_VERSION_30;
+
+ r->v3_parse = ngx_pcalloc(r->pool, sizeof(ngx_http_v3_parse_t));
+ if (r->v3_parse == NULL) {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ r->v3_parse->header_limit = cscf->large_client_header_buffers.size
+ * cscf->large_client_header_buffers.num;
+
+ c->data = r;
+ c->requests = (c->quic->id >> 2) + 1;
+
+ cln = ngx_pool_cleanup_add(r->pool, 0);
+ if (cln == NULL) {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ cln->handler = ngx_http_v3_cleanup_request;
+ cln->data = r;
+
+ rev->handler = ngx_http_v3_process_request;
+ ngx_http_v3_process_request(rev);
+}
+
+
+void
+ngx_http_v3_reset_stream(ngx_connection_t *c)
+{
+ ngx_http_v3_session_t *h3c;
+ ngx_http_v3_srv_conf_t *h3scf;
+
+ h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module);
+
+ h3c = ngx_http_v3_get_session(c);
+
+ if (h3scf->max_table_capacity > 0 && !c->read->eof && !h3c->hq
+ && (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0)
+ {
+ (void) ngx_http_v3_send_cancel_stream(c, c->quic->id);
+ }
+
+ if (c->timedout) {
+ ngx_quic_reset_stream(c, NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR);
+
+ } else if (c->close) {
+ ngx_quic_reset_stream(c, NGX_HTTP_V3_ERR_REQUEST_REJECTED);
+
+ } else if (c->requests == 0 || c->error) {
+ ngx_quic_reset_stream(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR);
+ }
+}
+
+
+static void
+ngx_http_v3_cleanup_connection(void *data)
+{
+ ngx_connection_t *c = data;
+
+ ngx_http_v3_session_t *h3c;
+ ngx_http_core_loc_conf_t *clcf;
+
+ h3c = ngx_http_v3_get_session(c);
+
+ if (--h3c->nrequests == 0) {
+ clcf = ngx_http_v3_get_module_loc_conf(c, ngx_http_core_module);
+ ngx_add_timer(&h3c->keepalive, clcf->keepalive_timeout);
+ }
+}
+
+
+static void
+ngx_http_v3_cleanup_request(void *data)
+{
+ ngx_http_request_t *r = data;
+
+ if (!r->response_sent) {
+ r->connection->error = 1;
+ }
+}
+
+
+static void
+ngx_http_v3_process_request(ngx_event_t *rev)
+{
+ u_char *p;
+ ssize_t n;
+ ngx_buf_t *b;
+ ngx_int_t rc;
+ ngx_connection_t *c;
+ ngx_http_request_t *r;
+ ngx_http_v3_session_t *h3c;
+ ngx_http_core_srv_conf_t *cscf;
+ ngx_http_v3_parse_headers_t *st;
+
+ c = rev->data;
+ r = c->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http3 process request");
+
+ if (rev->timedout) {
+ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+ c->timedout = 1;
+ ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT);
+ return;
+ }
+
+ h3c = ngx_http_v3_get_session(c);
+
+ st = &r->v3_parse->headers;
+
+ b = r->header_in;
+
+ for ( ;; ) {
+
+ if (b->pos == b->last) {
+
+ if (rev->ready) {
+ n = c->recv(c, b->start, b->end - b->start);
+
+ } else {
+ n = NGX_AGAIN;
+ }
+
+ if (n == NGX_AGAIN) {
+ if (!rev->timer_set) {
+ cscf = ngx_http_get_module_srv_conf(r,
+ ngx_http_core_module);
+ ngx_add_timer(rev, cscf->client_header_timeout);
+ }
+
+ if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ }
+
+ break;
+ }
+
+ if (n == 0) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client prematurely closed connection");
+ }
+
+ if (n == 0 || n == NGX_ERROR) {
+ c->error = 1;
+ c->log->action = "reading client request";
+
+ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+ break;
+ }
+
+ b->pos = b->start;
+ b->last = b->start + n;
+ }
+
+ p = b->pos;
+
+ rc = ngx_http_v3_parse_headers(c, st, b);
+
+ if (rc > 0) {
+ ngx_quic_reset_stream(c, rc);
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "client sent invalid header");
+ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+ break;
+ }
+
+ if (rc == NGX_ERROR) {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ break;
+ }
+
+ r->request_length += b->pos - p;
+ h3c->total_bytes += b->pos - p;
+
+ if (ngx_http_v3_check_flood(c) != NGX_OK) {
+ ngx_http_close_request(r, NGX_HTTP_CLOSE);
+ break;
+ }
+
+ if (rc == NGX_BUSY) {
+ if (rev->error) {
+ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+ break;
+ }
+
+ if (!rev->timer_set) {
+ cscf = ngx_http_get_module_srv_conf(r,
+ ngx_http_core_module);
+ ngx_add_timer(rev, cscf->client_header_timeout);
+ }
+
+ if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ }
+
+ break;
+ }
+
+ if (rc == NGX_AGAIN) {
+ continue;
+ }
+
+ /* rc == NGX_OK || rc == NGX_DONE */
+
+ h3c->payload_bytes += ngx_http_v3_encode_field_l(NULL,
+ &st->field_rep.field.name,
+ &st->field_rep.field.value);
+
+ if (ngx_http_v3_process_header(r, &st->field_rep.field.name,
+ &st->field_rep.field.value)
+ != NGX_OK)
+ {
+ break;
+ }
+
+ if (rc == NGX_DONE) {
+ if (ngx_http_v3_process_request_header(r) != NGX_OK) {
+ break;
+ }
+
+ ngx_http_process_request(r);
+ break;
+ }
+ }
+
+ ngx_http_run_posted_requests(c);
+
+ return;
+}
+
+
+static ngx_int_t
+ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name,
+ ngx_str_t *value)
+{
+ size_t len;
+ ngx_table_elt_t *h;
+ ngx_http_header_t *hh;
+ ngx_http_core_srv_conf_t *cscf;
+ ngx_http_core_main_conf_t *cmcf;
+
+ static ngx_str_t cookie = ngx_string("cookie");
+
+ len = name->len + value->len;
+
+ if (len > r->v3_parse->header_limit) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent too large header");
+ ngx_http_finalize_request(r, NGX_HTTP_REQUEST_HEADER_TOO_LARGE);
+ return NGX_ERROR;
+ }
+
+ r->v3_parse->header_limit -= len;
+
+ if (ngx_http_v3_validate_header(r, name, value) != NGX_OK) {
+ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+ return NGX_ERROR;
+ }
+
+ if (r->invalid_header) {
+ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+ if (cscf->ignore_invalid_headers) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent invalid header: \"%V\"", name);
+
+ return NGX_OK;
+ }
+ }
+
+ if (name->len && name->data[0] == ':') {
+ return ngx_http_v3_process_pseudo_header(r, name, value);
+ }
+
+ if (ngx_http_v3_init_pseudo_headers(r) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (name->len == cookie.len
+ && ngx_memcmp(name->data, cookie.data, cookie.len) == 0)
+ {
+ if (ngx_http_v3_cookie(r, value) != NGX_OK) {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return NGX_ERROR;
+ }
+
+ } else {
+ h = ngx_list_push(&r->headers_in.headers);
+ if (h == NULL) {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return NGX_ERROR;
+ }
+
+ h->key = *name;
+ h->value = *value;
+ h->lowcase_key = h->key.data;
+ h->hash = ngx_hash_key(h->key.data, h->key.len);
+
+ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+ hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash,
+ h->lowcase_key, h->key.len);
+
+ if (hh && hh->handler(r, h, hh->offset) != NGX_OK) {
+ return NGX_ERROR;
+ }
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http3 header: \"%V: %V\"", name, value);
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v3_validate_header(ngx_http_request_t *r, ngx_str_t *name,
+ ngx_str_t *value)
+{
+ u_char ch;
+ ngx_uint_t i;
+ ngx_http_core_srv_conf_t *cscf;
+
+ r->invalid_header = 0;
+
+ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+ for (i = (name->data[0] == ':'); i != name->len; i++) {
+ ch = name->data[i];
+
+ if ((ch >= 'a' && ch <= 'z')
+ || (ch == '-')
+ || (ch >= '0' && ch <= '9')
+ || (ch == '_' && cscf->underscores_in_headers))
+ {
+ continue;
+ }
+
+ if (ch <= 0x20 || ch == 0x7f || ch == ':'
+ || (ch >= 'A' && ch <= 'Z'))
+ {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent invalid header name: \"%V\"", name);
+
+ return NGX_ERROR;
+ }
+
+ r->invalid_header = 1;
+ }
+
+ for (i = 0; i != value->len; i++) {
+ ch = value->data[i];
+
+ if (ch == '\0' || ch == LF || ch == CR) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent header \"%V\" with "
+ "invalid value: \"%V\"", name, value);
+
+ return NGX_ERROR;
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name,
+ ngx_str_t *value)
+{
+ u_char ch, c;
+ ngx_uint_t i;
+
+ if (r->request_line.len) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent out of order pseudo-headers");
+ goto failed;
+ }
+
+ if (name->len == 7 && ngx_strncmp(name->data, ":method", 7) == 0) {
+
+ if (r->method_name.len) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent duplicate \":method\" header");
+ goto failed;
+ }
+
+ if (value->len == 0) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent empty \":method\" header");
+ goto failed;
+ }
+
+ r->method_name = *value;
+
+ 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;
+ }
+ }
+
+ for (i = 0; i < value->len; i++) {
+ ch = value->data[i];
+
+ if ((ch < 'A' || ch > 'Z') && ch != '_' && ch != '-') {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent invalid method: \"%V\"", value);
+ goto failed;
+ }
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http3 method \"%V\" %ui", value, r->method);
+ return NGX_OK;
+ }
+
+ if (name->len == 5 && ngx_strncmp(name->data, ":path", 5) == 0) {
+
+ if (r->uri_start) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent duplicate \":path\" header");
+ goto failed;
+ }
+
+ if (value->len == 0) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent empty \":path\" header");
+ goto failed;
+ }
+
+ 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, r->connection->log, 0,
+ "client sent invalid \":path\" header: \"%V\"",
+ value);
+ goto failed;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http3 path \"%V\"", value);
+ return NGX_OK;
+ }
+
+ if (name->len == 7 && ngx_strncmp(name->data, ":scheme", 7) == 0) {
+
+ if (r->schema.len) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent duplicate \":scheme\" header");
+ goto failed;
+ }
+
+ if (value->len == 0) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent empty \":scheme\" header");
+ goto failed;
+ }
+
+ for (i = 0; i < value->len; i++) {
+ ch = value->data[i];
+
+ c = (u_char) (ch | 0x20);
+ if (c >= 'a' && c <= 'z') {
+ continue;
+ }
+
+ if (((ch >= '0' && ch <= '9')
+ || ch == '+' || ch == '-' || ch == '.')
+ && i > 0)
+ {
+ continue;
+ }
+
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent invalid \":scheme\" header: \"%V\"",
+ value);
+ goto failed;
+ }
+
+ r->schema = *value;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http3 schema \"%V\"", value);
+ return NGX_OK;
+ }
+
+ if (name->len == 10 && ngx_strncmp(name->data, ":authority", 10) == 0) {
+
+ if (r->host_start) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent duplicate \":authority\" header");
+ goto failed;
+ }
+
+ r->host_start = value->data;
+ r->host_end = value->data + value->len;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http3 authority \"%V\"", value);
+ return NGX_OK;
+ }
+
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent unknown pseudo-header \"%V\"", name);
+
+failed:
+
+ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+ return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r)
+{
+ size_t len;
+ u_char *p;
+ ngx_int_t rc;
+ ngx_str_t host;
+
+ if (r->request_line.len) {
+ return NGX_OK;
+ }
+
+ if (r->method_name.len == 0) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent no \":method\" header");
+ goto failed;
+ }
+
+ if (r->schema.len == 0) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent no \":scheme\" header");
+ goto failed;
+ }
+
+ if (r->uri_start == NULL) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent no \":path\" header");
+ goto failed;
+ }
+
+ len = r->method_name.len + 1
+ + (r->uri_end - r->uri_start) + 1
+ + sizeof("HTTP/3.0") - 1;
+
+ p = ngx_pnalloc(r->pool, len);
+ if (p == NULL) {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return NGX_ERROR;
+ }
+
+ r->request_line.data = p;
+
+ p = ngx_cpymem(p, r->method_name.data, r->method_name.len);
+ *p++ = ' ';
+ p = ngx_cpymem(p, r->uri_start, r->uri_end - r->uri_start);
+ *p++ = ' ';
+ p = ngx_cpymem(p, "HTTP/3.0", sizeof("HTTP/3.0") - 1);
+
+ r->request_line.len = p - r->request_line.data;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http3 request line: \"%V\"", &r->request_line);
+
+ ngx_str_set(&r->http_protocol, "HTTP/3.0");
+
+ if (ngx_http_process_request_uri(r) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (r->host_end) {
+
+ host.len = r->host_end - r->host_start;
+ host.data = r->host_start;
+
+ rc = ngx_http_validate_host(&host, r->pool, 0);
+
+ if (rc == NGX_DECLINED) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent invalid host in request line");
+ goto failed;
+ }
+
+ if (rc == NGX_ERROR) {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return NGX_ERROR;
+ }
+
+ if (ngx_http_set_virtual_server(r, &host) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ r->headers_in.server = host;
+ }
+
+ if (ngx_list_init(&r->headers_in.headers, r->pool, 20,
+ sizeof(ngx_table_elt_t))
+ != NGX_OK)
+ {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+
+failed:
+
+ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+ return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_v3_process_request_header(ngx_http_request_t *r)
+{
+ ssize_t n;
+ ngx_buf_t *b;
+ ngx_connection_t *c;
+ ngx_http_v3_session_t *h3c;
+ ngx_http_v3_srv_conf_t *h3scf;
+
+ c = r->connection;
+
+ if (ngx_http_v3_init_pseudo_headers(r) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ h3c = ngx_http_v3_get_session(c);
+ h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module);
+
+ if ((h3c->hq && !h3scf->enable_hq) || (!h3c->hq && !h3scf->enable)) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client attempted to request the server name "
+ "for which the negotiated protocol is disabled");
+ ngx_http_finalize_request(r, NGX_HTTP_MISDIRECTED_REQUEST);
+ return NGX_ERROR;
+ }
+
+ if (ngx_http_v3_construct_cookie_header(r) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (r->headers_in.server.len == 0) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client sent neither \":authority\" nor \"Host\" header");
+ goto failed;
+ }
+
+ if (r->headers_in.host) {
+ if (r->headers_in.host->value.len != r->headers_in.server.len
+ || ngx_memcmp(r->headers_in.host->value.data,
+ r->headers_in.server.data,
+ r->headers_in.server.len)
+ != 0)
+ {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client sent \":authority\" and \"Host\" headers "
+ "with different values");
+ goto failed;
+ }
+ }
+
+ if (r->headers_in.content_length) {
+ r->headers_in.content_length_n =
+ ngx_atoof(r->headers_in.content_length->value.data,
+ r->headers_in.content_length->value.len);
+
+ if (r->headers_in.content_length_n == NGX_ERROR) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client sent invalid \"Content-Length\" header");
+ goto failed;
+ }
+
+ } else {
+ b = r->header_in;
+ n = b->last - b->pos;
+
+ if (n == 0) {
+ n = c->recv(c, b->start, b->end - b->start);
+
+ if (n == NGX_ERROR) {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return NGX_ERROR;
+ }
+
+ if (n > 0) {
+ b->pos = b->start;
+ b->last = b->start + n;
+ }
+ }
+
+ if (n != 0) {
+ r->headers_in.chunked = 1;
+ }
+ }
+
+ if (r->method == NGX_HTTP_CONNECT) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent CONNECT method");
+ ngx_http_finalize_request(r, NGX_HTTP_NOT_ALLOWED);
+ return NGX_ERROR;
+ }
+
+ if (r->method == NGX_HTTP_TRACE) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent TRACE method");
+ ngx_http_finalize_request(r, NGX_HTTP_NOT_ALLOWED);
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+
+failed:
+
+ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+ return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_v3_cookie(ngx_http_request_t *r, ngx_str_t *value)
+{
+ ngx_str_t *val;
+ ngx_array_t *cookies;
+
+ cookies = r->v3_parse->cookies;
+
+ if (cookies == NULL) {
+ cookies = ngx_array_create(r->pool, 2, sizeof(ngx_str_t));
+ if (cookies == NULL) {
+ return NGX_ERROR;
+ }
+
+ r->v3_parse->cookies = cookies;
+ }
+
+ val = ngx_array_push(cookies);
+ if (val == NULL) {
+ return NGX_ERROR;
+ }
+
+ *val = *value;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v3_construct_cookie_header(ngx_http_request_t *r)
+{
+ u_char *buf, *p, *end;
+ size_t len;
+ ngx_str_t *vals;
+ ngx_uint_t i;
+ ngx_array_t *cookies;
+ ngx_table_elt_t *h;
+ ngx_http_header_t *hh;
+ ngx_http_core_main_conf_t *cmcf;
+
+ static ngx_str_t cookie = ngx_string("cookie");
+
+ cookies = r->v3_parse->cookies;
+
+ if (cookies == NULL) {
+ return NGX_OK;
+ }
+
+ vals = cookies->elts;
+
+ i = 0;
+ len = 0;
+
+ do {
+ len += vals[i].len + 2;
+ } while (++i != cookies->nelts);
+
+ len -= 2;
+
+ buf = ngx_pnalloc(r->pool, len + 1);
+ if (buf == NULL) {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return NGX_ERROR;
+ }
+
+ p = buf;
+ end = buf + len;
+
+ for (i = 0; /* void */ ; i++) {
+
+ p = ngx_cpymem(p, vals[i].data, vals[i].len);
+
+ if (p == end) {
+ *p = '\0';
+ break;
+ }
+
+ *p++ = ';'; *p++ = ' ';
+ }
+
+ h = ngx_list_push(&r->headers_in.headers);
+ if (h == NULL) {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return NGX_ERROR;
+ }
+
+ h->hash = ngx_hash(ngx_hash(ngx_hash(ngx_hash(
+ ngx_hash('c', 'o'), 'o'), 'k'), 'i'), 'e');
+
+ h->key.len = cookie.len;
+ h->key.data = cookie.data;
+
+ h->value.len = len;
+ h->value.data = buf;
+
+ h->lowcase_key = cookie.data;
+
+ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+ hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash,
+ h->lowcase_key, h->key.len);
+
+ if (hh == NULL) {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return NGX_ERROR;
+ }
+
+ if (hh->handler(r, h, hh->offset) != NGX_OK) {
+ /*
+ * request has been finalized already
+ * in ngx_http_process_multi_header_lines()
+ */
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_read_request_body(ngx_http_request_t *r)
+{
+ size_t preread;
+ ngx_int_t rc;
+ ngx_chain_t *cl, out;
+ ngx_http_request_body_t *rb;
+ ngx_http_core_loc_conf_t *clcf;
+
+ rb = r->request_body;
+
+ preread = r->header_in->last - r->header_in->pos;
+
+ if (preread) {
+
+ /* there is the pre-read part of the request body */
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http3 client request body preread %uz", preread);
+
+ out.buf = r->header_in;
+ out.next = NULL;
+ cl = &out;
+
+ } else {
+ cl = NULL;
+ }
+
+ rc = ngx_http_v3_request_body_filter(r, cl);
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ if (rb->rest == 0 && rb->last_saved) {
+ /* the whole request body was pre-read */
+ r->request_body_no_buffering = 0;
+ rb->post_handler(r);
+ return NGX_OK;
+ }
+
+ if (rb->rest < 0) {
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+ "negative request body rest");
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ rb->buf = ngx_create_temp_buf(r->pool, clcf->client_body_buffer_size);
+ if (rb->buf == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ r->read_event_handler = ngx_http_v3_read_client_request_body_handler;
+ r->write_event_handler = ngx_http_request_empty_handler;
+
+ return ngx_http_v3_do_read_client_request_body(r);
+}
+
+
+static void
+ngx_http_v3_read_client_request_body_handler(ngx_http_request_t *r)
+{
+ ngx_int_t rc;
+
+ if (r->connection->read->timedout) {
+ r->connection->timedout = 1;
+ ngx_http_finalize_request(r, NGX_HTTP_REQUEST_TIME_OUT);
+ return;
+ }
+
+ rc = ngx_http_v3_do_read_client_request_body(r);
+
+ if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
+ ngx_http_finalize_request(r, rc);
+ }
+}
+
+
+ngx_int_t
+ngx_http_v3_read_unbuffered_request_body(ngx_http_request_t *r)
+{
+ ngx_int_t rc;
+
+ if (r->connection->read->timedout) {
+ r->connection->timedout = 1;
+ return NGX_HTTP_REQUEST_TIME_OUT;
+ }
+
+ rc = ngx_http_v3_do_read_client_request_body(r);
+
+ if (rc == NGX_OK) {
+ r->reading_body = 0;
+ }
+
+ return rc;
+}
+
+
+static ngx_int_t
+ngx_http_v3_do_read_client_request_body(ngx_http_request_t *r)
+{
+ off_t rest;
+ size_t size;
+ ssize_t n;
+ ngx_int_t rc;
+ ngx_uint_t flush;
+ ngx_chain_t out;
+ ngx_connection_t *c;
+ ngx_http_request_body_t *rb;
+ ngx_http_core_loc_conf_t *clcf;
+
+ c = r->connection;
+ rb = r->request_body;
+ flush = 1;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 read client request body");
+
+ for ( ;; ) {
+ for ( ;; ) {
+ if (rb->rest == 0) {
+ break;
+ }
+
+ if (rb->buf->last == rb->buf->end) {
+
+ /* update chains */
+
+ rc = ngx_http_v3_request_body_filter(r, NULL);
+
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ if (rb->busy != NULL) {
+ if (r->request_body_no_buffering) {
+ if (c->read->timer_set) {
+ ngx_del_timer(c->read);
+ }
+
+ if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ return NGX_AGAIN;
+ }
+
+ if (rb->filter_need_buffering) {
+ clcf = ngx_http_get_module_loc_conf(r,
+ ngx_http_core_module);
+ ngx_add_timer(c->read, clcf->client_body_timeout);
+
+ if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ return NGX_AGAIN;
+ }
+
+ ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+ "busy buffers after request body flush");
+
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ flush = 0;
+ rb->buf->pos = rb->buf->start;
+ rb->buf->last = rb->buf->start;
+ }
+
+ size = rb->buf->end - rb->buf->last;
+ rest = rb->rest - (rb->buf->last - rb->buf->pos);
+
+ if ((off_t) size > rest) {
+ size = (size_t) rest;
+ }
+
+ if (size == 0) {
+ break;
+ }
+
+ n = c->recv(c, rb->buf->last, size);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 client request body recv %z", n);
+
+ if (n == NGX_AGAIN) {
+ break;
+ }
+
+ if (n == 0) {
+ rb->buf->last_buf = 1;
+ }
+
+ if (n == NGX_ERROR) {
+ c->error = 1;
+ return NGX_HTTP_BAD_REQUEST;
+ }
+
+ rb->buf->last += n;
+
+ /* pass buffer to request body filter chain */
+
+ flush = 0;
+ out.buf = rb->buf;
+ out.next = NULL;
+
+ rc = ngx_http_v3_request_body_filter(r, &out);
+
+ if (rc != NGX_OK) {
+ return rc;
+ }
+
+ if (rb->rest == 0) {
+ break;
+ }
+
+ if (rb->buf->last < rb->buf->end) {
+ break;
+ }
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 client request body rest %O", rb->rest);
+
+ if (flush) {
+ rc = ngx_http_v3_request_body_filter(r, NULL);
+
+ if (rc != NGX_OK) {
+ return rc;
+ }
+ }
+
+ if (rb->rest == 0 && rb->last_saved) {
+ break;
+ }
+
+ if (!c->read->ready || rb->rest == 0) {
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+ ngx_add_timer(c->read, clcf->client_body_timeout);
+
+ if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ return NGX_AGAIN;
+ }
+ }
+
+ if (c->read->timer_set) {
+ ngx_del_timer(c->read);
+ }
+
+ if (!r->request_body_no_buffering) {
+ r->read_event_handler = ngx_http_block_reading;
+ rb->post_handler(r);
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v3_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+ off_t max;
+ size_t size;
+ u_char *p;
+ ngx_int_t rc;
+ ngx_buf_t *b;
+ ngx_uint_t last;
+ ngx_chain_t *cl, *out, *tl, **ll;
+ ngx_http_v3_session_t *h3c;
+ ngx_http_request_body_t *rb;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_core_srv_conf_t *cscf;
+ ngx_http_v3_parse_data_t *st;
+
+ rb = r->request_body;
+ st = &r->v3_parse->body;
+
+ h3c = ngx_http_v3_get_session(r->connection);
+
+ if (rb->rest == -1) {
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http3 request body filter");
+
+ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+ rb->rest = cscf->large_client_header_buffers.size;
+ }
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ max = r->headers_in.content_length_n;
+
+ if (max == -1 && clcf->client_max_body_size) {
+ max = clcf->client_max_body_size;
+ }
+
+ out = NULL;
+ ll = &out;
+ last = 0;
+
+ for (cl = in; cl; cl = cl->next) {
+
+ ngx_log_debug7(NGX_LOG_DEBUG_EVENT, r->connection->log, 0,
+ "http3 body buf "
+ "t:%d f:%d %p, pos %p, size: %z file: %O, size: %O",
+ cl->buf->temporary, cl->buf->in_file,
+ cl->buf->start, cl->buf->pos,
+ cl->buf->last - cl->buf->pos,
+ cl->buf->file_pos,
+ cl->buf->file_last - cl->buf->file_pos);
+
+ if (cl->buf->last_buf) {
+ last = 1;
+ }
+
+ b = NULL;
+
+ while (cl->buf->pos < cl->buf->last) {
+
+ if (st->length == 0) {
+ p = cl->buf->pos;
+
+ rc = ngx_http_v3_parse_data(r->connection, st, cl->buf);
+
+ r->request_length += cl->buf->pos - p;
+ h3c->total_bytes += cl->buf->pos - p;
+
+ if (ngx_http_v3_check_flood(r->connection) != NGX_OK) {
+ return NGX_HTTP_CLOSE;
+ }
+
+ if (rc == NGX_AGAIN) {
+ continue;
+ }
+
+ if (rc == NGX_DONE) {
+ last = 1;
+ goto done;
+ }
+
+ if (rc > 0) {
+ ngx_quic_reset_stream(r->connection, rc);
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "client sent invalid body");
+ return NGX_HTTP_BAD_REQUEST;
+ }
+
+ if (rc == NGX_ERROR) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* rc == NGX_OK */
+
+ if (max != -1 && (uint64_t) (max - rb->received) < st->length) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "client intended to send too large "
+ "body: %O+%ui bytes",
+ rb->received, st->length);
+
+ return NGX_HTTP_REQUEST_ENTITY_TOO_LARGE;
+ }
+
+ continue;
+ }
+
+ if (b
+ && st->length <= 128
+ && (uint64_t) (cl->buf->last - cl->buf->pos) >= st->length)
+ {
+ rb->received += st->length;
+ r->request_length += st->length;
+ h3c->total_bytes += st->length;
+ h3c->payload_bytes += st->length;
+
+ if (st->length < 8) {
+
+ while (st->length) {
+ *b->last++ = *cl->buf->pos++;
+ st->length--;
+ }
+
+ } else {
+ ngx_memmove(b->last, cl->buf->pos, st->length);
+ b->last += st->length;
+ cl->buf->pos += st->length;
+ st->length = 0;
+ }
+
+ continue;
+ }
+
+ tl = ngx_chain_get_free_buf(r->pool, &rb->free);
+ if (tl == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ b = tl->buf;
+
+ ngx_memzero(b, sizeof(ngx_buf_t));
+
+ b->temporary = 1;
+ b->tag = (ngx_buf_tag_t) &ngx_http_read_client_request_body;
+ b->start = cl->buf->pos;
+ b->pos = cl->buf->pos;
+ b->last = cl->buf->last;
+ b->end = cl->buf->end;
+ b->flush = r->request_body_no_buffering;
+
+ *ll = tl;
+ ll = &tl->next;
+
+ size = cl->buf->last - cl->buf->pos;
+
+ if (size > st->length) {
+ cl->buf->pos += (size_t) st->length;
+ rb->received += st->length;
+ r->request_length += st->length;
+ h3c->total_bytes += st->length;
+ h3c->payload_bytes += st->length;
+ st->length = 0;
+
+ } else {
+ st->length -= size;
+ rb->received += size;
+ r->request_length += size;
+ h3c->total_bytes += size;
+ h3c->payload_bytes += size;
+ cl->buf->pos = cl->buf->last;
+ }
+
+ b->last = cl->buf->pos;
+ }
+ }
+
+done:
+
+ if (last) {
+
+ if (st->length > 0) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client prematurely closed stream");
+ r->connection->error = 1;
+ return NGX_HTTP_BAD_REQUEST;
+ }
+
+ if (r->headers_in.content_length_n == -1) {
+ r->headers_in.content_length_n = rb->received;
+
+ } else if (r->headers_in.content_length_n != rb->received) {
+ ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+ "client sent less body data than expected: "
+ "%O out of %O bytes of request body received",
+ rb->received, r->headers_in.content_length_n);
+ return NGX_HTTP_BAD_REQUEST;
+ }
+
+ rb->rest = 0;
+
+ tl = ngx_chain_get_free_buf(r->pool, &rb->free);
+ if (tl == NULL) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ b = tl->buf;
+
+ ngx_memzero(b, sizeof(ngx_buf_t));
+
+ b->last_buf = 1;
+
+ *ll = tl;
+
+ } else {
+
+ /* set rb->rest, amount of data we want to see next time */
+
+ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+ rb->rest = (off_t) cscf->large_client_header_buffers.size;
+ }
+
+ rc = ngx_http_top_request_body_filter(r, out);
+
+ ngx_chain_update_chains(r->pool, &rb->free, &rb->busy, &out,
+ (ngx_buf_tag_t) &ngx_http_read_client_request_body);
+
+ return rc;
+}
diff --git a/src/http/v3/ngx_http_v3_table.c b/src/http/v3/ngx_http_v3_table.c
new file mode 100644
index 000000000..f49a8fc5e
--- /dev/null
+++ b/src/http/v3/ngx_http_v3_table.c
@@ -0,0 +1,715 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#define ngx_http_v3_table_entry_size(n, v) ((n)->len + (v)->len + 32)
+
+
+static ngx_int_t ngx_http_v3_evict(ngx_connection_t *c, size_t target);
+static void ngx_http_v3_unblock(void *data);
+static ngx_int_t ngx_http_v3_new_entry(ngx_connection_t *c);
+
+
+typedef struct {
+ ngx_queue_t queue;
+ ngx_connection_t *connection;
+ ngx_uint_t *nblocked;
+} ngx_http_v3_block_t;
+
+
+static ngx_http_v3_field_t ngx_http_v3_static_table[] = {
+
+ { ngx_string(":authority"), ngx_string("") },
+ { ngx_string(":path"), ngx_string("/") },
+ { ngx_string("age"), ngx_string("0") },
+ { ngx_string("content-disposition"), ngx_string("") },
+ { ngx_string("content-length"), ngx_string("0") },
+ { ngx_string("cookie"), ngx_string("") },
+ { ngx_string("date"), ngx_string("") },
+ { ngx_string("etag"), ngx_string("") },
+ { ngx_string("if-modified-since"), ngx_string("") },
+ { ngx_string("if-none-match"), ngx_string("") },
+ { ngx_string("last-modified"), ngx_string("") },
+ { ngx_string("link"), ngx_string("") },
+ { ngx_string("location"), ngx_string("") },
+ { ngx_string("referer"), ngx_string("") },
+ { ngx_string("set-cookie"), ngx_string("") },
+ { ngx_string(":method"), ngx_string("CONNECT") },
+ { ngx_string(":method"), ngx_string("DELETE") },
+ { ngx_string(":method"), ngx_string("GET") },
+ { ngx_string(":method"), ngx_string("HEAD") },
+ { ngx_string(":method"), ngx_string("OPTIONS") },
+ { ngx_string(":method"), ngx_string("POST") },
+ { ngx_string(":method"), ngx_string("PUT") },
+ { ngx_string(":scheme"), ngx_string("http") },
+ { ngx_string(":scheme"), ngx_string("https") },
+ { ngx_string(":status"), ngx_string("103") },
+ { ngx_string(":status"), ngx_string("200") },
+ { ngx_string(":status"), ngx_string("304") },
+ { ngx_string(":status"), ngx_string("404") },
+ { ngx_string(":status"), ngx_string("503") },
+ { ngx_string("accept"), ngx_string("*/*") },
+ { ngx_string("accept"),
+ ngx_string("application/dns-message") },
+ { ngx_string("accept-encoding"), ngx_string("gzip, deflate, br") },
+ { ngx_string("accept-ranges"), ngx_string("bytes") },
+ { ngx_string("access-control-allow-headers"),
+ ngx_string("cache-control") },
+ { ngx_string("access-control-allow-headers"),
+ ngx_string("content-type") },
+ { ngx_string("access-control-allow-origin"),
+ ngx_string("*") },
+ { ngx_string("cache-control"), ngx_string("max-age=0") },
+ { ngx_string("cache-control"), ngx_string("max-age=2592000") },
+ { ngx_string("cache-control"), ngx_string("max-age=604800") },
+ { ngx_string("cache-control"), ngx_string("no-cache") },
+ { ngx_string("cache-control"), ngx_string("no-store") },
+ { ngx_string("cache-control"),
+ ngx_string("public, max-age=31536000") },
+ { ngx_string("content-encoding"), ngx_string("br") },
+ { ngx_string("content-encoding"), ngx_string("gzip") },
+ { ngx_string("content-type"),
+ ngx_string("application/dns-message") },
+ { ngx_string("content-type"),
+ ngx_string("application/javascript") },
+ { ngx_string("content-type"), ngx_string("application/json") },
+ { ngx_string("content-type"),
+ ngx_string("application/x-www-form-urlencoded") },
+ { ngx_string("content-type"), ngx_string("image/gif") },
+ { ngx_string("content-type"), ngx_string("image/jpeg") },
+ { ngx_string("content-type"), ngx_string("image/png") },
+ { ngx_string("content-type"), ngx_string("text/css") },
+ { ngx_string("content-type"),
+ ngx_string("text/html;charset=utf-8") },
+ { ngx_string("content-type"), ngx_string("text/plain") },
+ { ngx_string("content-type"),
+ ngx_string("text/plain;charset=utf-8") },
+ { ngx_string("range"), ngx_string("bytes=0-") },
+ { ngx_string("strict-transport-security"),
+ ngx_string("max-age=31536000") },
+ { ngx_string("strict-transport-security"),
+ ngx_string("max-age=31536000;includesubdomains") },
+ { ngx_string("strict-transport-security"),
+ ngx_string("max-age=31536000;includesubdomains;preload") },
+ { ngx_string("vary"), ngx_string("accept-encoding") },
+ { ngx_string("vary"), ngx_string("origin") },
+ { ngx_string("x-content-type-options"),
+ ngx_string("nosniff") },
+ { ngx_string("x-xss-protection"), ngx_string("1;mode=block") },
+ { ngx_string(":status"), ngx_string("100") },
+ { ngx_string(":status"), ngx_string("204") },
+ { ngx_string(":status"), ngx_string("206") },
+ { ngx_string(":status"), ngx_string("302") },
+ { ngx_string(":status"), ngx_string("400") },
+ { ngx_string(":status"), ngx_string("403") },
+ { ngx_string(":status"), ngx_string("421") },
+ { ngx_string(":status"), ngx_string("425") },
+ { ngx_string(":status"), ngx_string("500") },
+ { ngx_string("accept-language"), ngx_string("") },
+ { ngx_string("access-control-allow-credentials"),
+ ngx_string("FALSE") },
+ { ngx_string("access-control-allow-credentials"),
+ ngx_string("TRUE") },
+ { ngx_string("access-control-allow-headers"),
+ ngx_string("*") },
+ { ngx_string("access-control-allow-methods"),
+ ngx_string("get") },
+ { ngx_string("access-control-allow-methods"),
+ ngx_string("get, post, options") },
+ { ngx_string("access-control-allow-methods"),
+ ngx_string("options") },
+ { ngx_string("access-control-expose-headers"),
+ ngx_string("content-length") },
+ { ngx_string("access-control-request-headers"),
+ ngx_string("content-type") },
+ { ngx_string("access-control-request-method"),
+ ngx_string("get") },
+ { ngx_string("access-control-request-method"),
+ ngx_string("post") },
+ { ngx_string("alt-svc"), ngx_string("clear") },
+ { ngx_string("authorization"), ngx_string("") },
+ { ngx_string("content-security-policy"),
+ ngx_string("script-src 'none';object-src 'none';base-uri 'none'") },
+ { ngx_string("early-data"), ngx_string("1") },
+ { ngx_string("expect-ct"), ngx_string("") },
+ { ngx_string("forwarded"), ngx_string("") },
+ { ngx_string("if-range"), ngx_string("") },
+ { ngx_string("origin"), ngx_string("") },
+ { ngx_string("purpose"), ngx_string("prefetch") },
+ { ngx_string("server"), ngx_string("") },
+ { ngx_string("timing-allow-origin"), ngx_string("*") },
+ { ngx_string("upgrade-insecure-requests"),
+ ngx_string("1") },
+ { ngx_string("user-agent"), ngx_string("") },
+ { ngx_string("x-forwarded-for"), ngx_string("") },
+ { ngx_string("x-frame-options"), ngx_string("deny") },
+ { ngx_string("x-frame-options"), ngx_string("sameorigin") }
+};
+
+
+ngx_int_t
+ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic,
+ ngx_uint_t index, ngx_str_t *value)
+{
+ ngx_str_t name;
+ ngx_http_v3_session_t *h3c;
+ ngx_http_v3_dynamic_table_t *dt;
+
+ if (dynamic) {
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 ref insert dynamic[%ui] \"%V\"", index, value);
+
+ h3c = ngx_http_v3_get_session(c);
+ dt = &h3c->table;
+
+ if (dt->base + dt->nelts <= index) {
+ return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
+ }
+
+ index = dt->base + dt->nelts - 1 - index;
+
+ if (ngx_http_v3_lookup(c, index, &name, NULL) != NGX_OK) {
+ return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
+ }
+
+ } else {
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 ref insert static[%ui] \"%V\"", index, value);
+
+ if (ngx_http_v3_lookup_static(c, index, &name, NULL) != NGX_OK) {
+ return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
+ }
+ }
+
+ return ngx_http_v3_insert(c, &name, value);
+}
+
+
+ngx_int_t
+ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, ngx_str_t *value)
+{
+ u_char *p;
+ size_t size;
+ ngx_http_v3_field_t *field;
+ ngx_http_v3_session_t *h3c;
+ ngx_http_v3_dynamic_table_t *dt;
+
+ size = ngx_http_v3_table_entry_size(name, value);
+
+ h3c = ngx_http_v3_get_session(c);
+ dt = &h3c->table;
+
+ if (size > dt->capacity) {
+ ngx_log_error(NGX_LOG_ERR, c->log, 0,
+ "not enough dynamic table capacity");
+ return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
+ }
+
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 insert [%ui] \"%V\":\"%V\", size:%uz",
+ dt->base + dt->nelts, name, value, size);
+
+ p = ngx_alloc(sizeof(ngx_http_v3_field_t) + name->len + value->len,
+ c->log);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ field = (ngx_http_v3_field_t *) p;
+
+ field->name.data = p + sizeof(ngx_http_v3_field_t);
+ field->name.len = name->len;
+ field->value.data = ngx_cpymem(field->name.data, name->data, name->len);
+ field->value.len = value->len;
+ ngx_memcpy(field->value.data, value->data, value->len);
+
+ dt->elts[dt->nelts++] = field;
+ dt->size += size;
+
+ dt->insert_count++;
+
+ if (ngx_http_v3_evict(c, dt->capacity) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ ngx_post_event(&dt->send_insert_count, &ngx_posted_events);
+
+ if (ngx_http_v3_new_entry(c) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+void
+ngx_http_v3_inc_insert_count_handler(ngx_event_t *ev)
+{
+ ngx_connection_t *c;
+ ngx_http_v3_session_t *h3c;
+ ngx_http_v3_dynamic_table_t *dt;
+
+ c = ev->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 inc insert count handler");
+
+ h3c = ngx_http_v3_get_session(c);
+ dt = &h3c->table;
+
+ if (dt->insert_count > dt->ack_insert_count) {
+ if (ngx_http_v3_send_inc_insert_count(c,
+ dt->insert_count - dt->ack_insert_count)
+ != NGX_OK)
+ {
+ return;
+ }
+
+ dt->ack_insert_count = dt->insert_count;
+ }
+}
+
+
+ngx_int_t
+ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity)
+{
+ ngx_uint_t max, prev_max;
+ ngx_http_v3_field_t **elts;
+ ngx_http_v3_session_t *h3c;
+ ngx_http_v3_srv_conf_t *h3scf;
+ ngx_http_v3_dynamic_table_t *dt;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 set capacity %ui", capacity);
+
+ h3c = ngx_http_v3_get_session(c);
+ h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module);
+
+ if (capacity > h3scf->max_table_capacity) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client exceeded http3_max_table_capacity limit");
+ return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
+ }
+
+ if (ngx_http_v3_evict(c, capacity) != NGX_OK) {
+ return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
+ }
+
+ dt = &h3c->table;
+ max = capacity / 32;
+ prev_max = dt->capacity / 32;
+
+ if (max > prev_max) {
+ elts = ngx_alloc(max * sizeof(void *), c->log);
+ if (elts == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (dt->elts) {
+ ngx_memcpy(elts, dt->elts, dt->nelts * sizeof(void *));
+ ngx_free(dt->elts);
+ }
+
+ dt->elts = elts;
+ }
+
+ dt->capacity = capacity;
+
+ return NGX_OK;
+}
+
+
+void
+ngx_http_v3_cleanup_table(ngx_http_v3_session_t *h3c)
+{
+ ngx_uint_t n;
+ ngx_http_v3_dynamic_table_t *dt;
+
+ dt = &h3c->table;
+
+ if (dt->elts == NULL) {
+ return;
+ }
+
+ for (n = 0; n < dt->nelts; n++) {
+ ngx_free(dt->elts[n]);
+ }
+
+ ngx_free(dt->elts);
+}
+
+
+static ngx_int_t
+ngx_http_v3_evict(ngx_connection_t *c, size_t target)
+{
+ size_t size;
+ ngx_uint_t n;
+ ngx_http_v3_field_t *field;
+ ngx_http_v3_session_t *h3c;
+ ngx_http_v3_dynamic_table_t *dt;
+
+ h3c = ngx_http_v3_get_session(c);
+ dt = &h3c->table;
+ n = 0;
+
+ while (dt->size > target) {
+ field = dt->elts[n++];
+ size = ngx_http_v3_table_entry_size(&field->name, &field->value);
+
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 evict [%ui] \"%V\":\"%V\" size:%uz",
+ dt->base, &field->name, &field->value, size);
+
+ ngx_free(field);
+ dt->size -= size;
+ }
+
+ if (n) {
+ dt->nelts -= n;
+ dt->base += n;
+ ngx_memmove(dt->elts, &dt->elts[n], dt->nelts * sizeof(void *));
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index)
+{
+ ngx_str_t name, value;
+ ngx_http_v3_session_t *h3c;
+ ngx_http_v3_dynamic_table_t *dt;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 duplicate %ui", index);
+
+ h3c = ngx_http_v3_get_session(c);
+ dt = &h3c->table;
+
+ if (dt->base + dt->nelts <= index) {
+ return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
+ }
+
+ index = dt->base + dt->nelts - 1 - index;
+
+ if (ngx_http_v3_lookup(c, index, &name, &value) != NGX_OK) {
+ return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
+ }
+
+ return ngx_http_v3_insert(c, &name, &value);
+}
+
+
+ngx_int_t
+ngx_http_v3_ack_section(ngx_connection_t *c, ngx_uint_t stream_id)
+{
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 ack section %ui", stream_id);
+
+ /* we do not use dynamic tables */
+
+ return NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR;
+}
+
+
+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);
+
+ /* we do not use dynamic tables */
+
+ return NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR;
+}
+
+
+ngx_int_t
+ngx_http_v3_lookup_static(ngx_connection_t *c, ngx_uint_t index,
+ ngx_str_t *name, ngx_str_t *value)
+{
+ ngx_uint_t nelts;
+ ngx_http_v3_field_t *field;
+
+ nelts = sizeof(ngx_http_v3_static_table)
+ / sizeof(ngx_http_v3_static_table[0]);
+
+ if (index >= nelts) {
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 static[%ui] lookup out of bounds: %ui",
+ index, nelts);
+ return NGX_ERROR;
+ }
+
+ field = &ngx_http_v3_static_table[index];
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 static[%ui] lookup \"%V\":\"%V\"",
+ index, &field->name, &field->value);
+
+ if (name) {
+ *name = field->name;
+ }
+
+ if (value) {
+ *value = field->value;
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_lookup(ngx_connection_t *c, ngx_uint_t index, ngx_str_t *name,
+ ngx_str_t *value)
+{
+ ngx_http_v3_field_t *field;
+ ngx_http_v3_session_t *h3c;
+ ngx_http_v3_dynamic_table_t *dt;
+
+ h3c = ngx_http_v3_get_session(c);
+ dt = &h3c->table;
+
+ if (index < dt->base || index - dt->base >= dt->nelts) {
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 dynamic[%ui] lookup out of bounds: [%ui,%ui]",
+ index, dt->base, dt->base + dt->nelts);
+ return NGX_ERROR;
+ }
+
+ field = dt->elts[index - dt->base];
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 dynamic[%ui] lookup \"%V\":\"%V\"",
+ index, &field->name, &field->value);
+
+ if (name) {
+ *name = field->name;
+ }
+
+ if (value) {
+ *value = field->value;
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_decode_insert_count(ngx_connection_t *c, ngx_uint_t *insert_count)
+{
+ ngx_uint_t max_entries, full_range, max_value,
+ max_wrapped, req_insert_count;
+ ngx_http_v3_srv_conf_t *h3scf;
+ ngx_http_v3_session_t *h3c;
+ ngx_http_v3_dynamic_table_t *dt;
+
+ /* QPACK 4.5.1.1. Required Insert Count */
+
+ if (*insert_count == 0) {
+ return NGX_OK;
+ }
+
+ h3c = ngx_http_v3_get_session(c);
+ dt = &h3c->table;
+
+ h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module);
+
+ max_entries = h3scf->max_table_capacity / 32;
+ full_range = 2 * max_entries;
+
+ if (*insert_count > full_range) {
+ return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED;
+ }
+
+ max_value = dt->base + dt->nelts + max_entries;
+ max_wrapped = (max_value / full_range) * full_range;
+ req_insert_count = max_wrapped + *insert_count - 1;
+
+ if (req_insert_count > max_value) {
+ if (req_insert_count <= full_range) {
+ return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED;
+ }
+
+ req_insert_count -= full_range;
+ }
+
+ if (req_insert_count == 0) {
+ return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 decode insert_count %ui -> %ui",
+ *insert_count, req_insert_count);
+
+ *insert_count = req_insert_count;
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_check_insert_count(ngx_connection_t *c, ngx_uint_t insert_count)
+{
+ size_t n;
+ ngx_pool_cleanup_t *cln;
+ ngx_http_v3_block_t *block;
+ ngx_http_v3_session_t *h3c;
+ ngx_http_v3_srv_conf_t *h3scf;
+ ngx_http_v3_dynamic_table_t *dt;
+
+ h3c = ngx_http_v3_get_session(c);
+ dt = &h3c->table;
+
+ n = dt->base + dt->nelts;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 check insert count req:%ui, have:%ui",
+ insert_count, n);
+
+ if (n >= insert_count) {
+ return NGX_OK;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 block stream");
+
+ block = NULL;
+
+ for (cln = c->pool->cleanup; cln; cln = cln->next) {
+ if (cln->handler == ngx_http_v3_unblock) {
+ block = cln->data;
+ break;
+ }
+ }
+
+ if (block == NULL) {
+ cln = ngx_pool_cleanup_add(c->pool, sizeof(ngx_http_v3_block_t));
+ if (cln == NULL) {
+ return NGX_ERROR;
+ }
+
+ cln->handler = ngx_http_v3_unblock;
+
+ block = cln->data;
+ block->queue.prev = NULL;
+ block->connection = c;
+ block->nblocked = &h3c->nblocked;
+ }
+
+ if (block->queue.prev == NULL) {
+ h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module);
+
+ if (h3c->nblocked == h3scf->max_blocked_streams) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client exceeded http3_max_blocked_streams limit");
+
+ ngx_http_v3_finalize_connection(c,
+ NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED,
+ "too many blocked streams");
+ return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED;
+ }
+
+ h3c->nblocked++;
+ ngx_queue_insert_tail(&h3c->blocked, &block->queue);
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 blocked:%ui", h3c->nblocked);
+
+ return NGX_BUSY;
+}
+
+
+void
+ngx_http_v3_ack_insert_count(ngx_connection_t *c, uint64_t insert_count)
+{
+ ngx_http_v3_session_t *h3c;
+ ngx_http_v3_dynamic_table_t *dt;
+
+ h3c = ngx_http_v3_get_session(c);
+ dt = &h3c->table;
+
+ if (dt->ack_insert_count < insert_count) {
+ dt->ack_insert_count = insert_count;
+ }
+}
+
+
+static void
+ngx_http_v3_unblock(void *data)
+{
+ ngx_http_v3_block_t *block = data;
+
+ if (block->queue.prev) {
+ ngx_queue_remove(&block->queue);
+ block->queue.prev = NULL;
+ (*block->nblocked)--;
+ }
+}
+
+
+static ngx_int_t
+ngx_http_v3_new_entry(ngx_connection_t *c)
+{
+ ngx_queue_t *q;
+ ngx_connection_t *bc;
+ ngx_http_v3_block_t *block;
+ ngx_http_v3_session_t *h3c;
+
+ h3c = ngx_http_v3_get_session(c);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 new dynamic entry, blocked:%ui", h3c->nblocked);
+
+ while (!ngx_queue_empty(&h3c->blocked)) {
+ q = ngx_queue_head(&h3c->blocked);
+ block = (ngx_http_v3_block_t *) q;
+ bc = block->connection;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, bc->log, 0, "http3 unblock stream");
+
+ ngx_http_v3_unblock(block);
+ ngx_post_event(bc->read, &ngx_posted_events);
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_set_param(ngx_connection_t *c, uint64_t id, uint64_t value)
+{
+ switch (id) {
+
+ case NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY:
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 param QPACK_MAX_TABLE_CAPACITY:%uL", value);
+ break;
+
+ case NGX_HTTP_V3_PARAM_MAX_FIELD_SECTION_SIZE:
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 param SETTINGS_MAX_FIELD_SECTION_SIZE:%uL",
+ value);
+ break;
+
+ case NGX_HTTP_V3_PARAM_BLOCKED_STREAMS:
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 param QPACK_BLOCKED_STREAMS:%uL", value);
+ break;
+
+ default:
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 param #%uL:%uL", id, value);
+ }
+
+ return NGX_OK;
+}
diff --git a/src/http/v3/ngx_http_v3_table.h b/src/http/v3/ngx_http_v3_table.h
new file mode 100644
index 000000000..1c2fb17b9
--- /dev/null
+++ b/src/http/v3/ngx_http_v3_table.h
@@ -0,0 +1,58 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_HTTP_V3_TABLE_H_INCLUDED_
+#define _NGX_HTTP_V3_TABLE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ ngx_str_t name;
+ ngx_str_t value;
+} ngx_http_v3_field_t;
+
+
+typedef struct {
+ ngx_http_v3_field_t **elts;
+ ngx_uint_t nelts;
+ ngx_uint_t base;
+ size_t size;
+ size_t capacity;
+ uint64_t insert_count;
+ uint64_t ack_insert_count;
+ ngx_event_t send_insert_count;
+} ngx_http_v3_dynamic_table_t;
+
+
+void ngx_http_v3_inc_insert_count_handler(ngx_event_t *ev);
+void ngx_http_v3_cleanup_table(ngx_http_v3_session_t *h3c);
+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_section(ngx_connection_t *c, ngx_uint_t stream_id);
+ngx_int_t ngx_http_v3_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc);
+ngx_int_t ngx_http_v3_lookup_static(ngx_connection_t *c, ngx_uint_t index,
+ ngx_str_t *name, ngx_str_t *value);
+ngx_int_t ngx_http_v3_lookup(ngx_connection_t *c, ngx_uint_t index,
+ ngx_str_t *name, ngx_str_t *value);
+ngx_int_t ngx_http_v3_decode_insert_count(ngx_connection_t *c,
+ ngx_uint_t *insert_count);
+ngx_int_t ngx_http_v3_check_insert_count(ngx_connection_t *c,
+ ngx_uint_t insert_count);
+void ngx_http_v3_ack_insert_count(ngx_connection_t *c, uint64_t insert_count);
+ngx_int_t ngx_http_v3_set_param(ngx_connection_t *c, uint64_t id,
+ uint64_t value);
+
+
+#endif /* _NGX_HTTP_V3_TABLE_H_INCLUDED_ */
diff --git a/src/http/v3/ngx_http_v3_uni.c b/src/http/v3/ngx_http_v3_uni.c
new file mode 100644
index 000000000..2fc5b07a4
--- /dev/null
+++ b/src/http/v3/ngx_http_v3_uni.c
@@ -0,0 +1,624 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ ngx_http_v3_parse_uni_t parse;
+ 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_uni_read_handler(ngx_event_t *rev);
+static void ngx_http_v3_uni_dummy_read_handler(ngx_event_t *wev);
+static void ngx_http_v3_uni_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_init_uni_stream(ngx_connection_t *c)
+{
+ uint64_t n;
+ ngx_http_v3_session_t *h3c;
+ ngx_http_v3_uni_stream_t *us;
+
+ h3c = ngx_http_v3_get_session(c);
+ if (h3c->hq) {
+ ngx_http_v3_finalize_connection(c,
+ NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR,
+ "uni stream in hq mode");
+ c->data = NULL;
+ ngx_http_v3_close_uni_stream(c);
+ return;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init uni stream");
+
+ n = c->quic->id >> 2;
+
+ if (n >= NGX_HTTP_V3_MAX_UNI_STREAMS) {
+ ngx_http_v3_finalize_connection(c,
+ NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR,
+ "reached maximum number of uni streams");
+ c->data = NULL;
+ ngx_http_v3_close_uni_stream(c);
+ return;
+ }
+
+ ngx_quic_cancelable_stream(c);
+
+ us = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_uni_stream_t));
+ if (us == NULL) {
+ ngx_http_v3_finalize_connection(c,
+ NGX_HTTP_V3_ERR_INTERNAL_ERROR,
+ "memory allocation error");
+ c->data = NULL;
+ ngx_http_v3_close_uni_stream(c);
+ return;
+ }
+
+ us->index = -1;
+
+ c->data = us;
+
+ c->read->handler = ngx_http_v3_uni_read_handler;
+ c->write->handler = ngx_http_v3_uni_dummy_write_handler;
+
+ ngx_http_v3_uni_read_handler(c->read);
+}
+
+
+static void
+ngx_http_v3_close_uni_stream(ngx_connection_t *c)
+{
+ ngx_pool_t *pool;
+ ngx_http_v3_session_t *h3c;
+ ngx_http_v3_uni_stream_t *us;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 close stream");
+
+ us = c->data;
+
+ if (us && us->index >= 0) {
+ h3c = ngx_http_v3_get_session(c);
+ h3c->known_streams[us->index] = NULL;
+ }
+
+ c->destroyed = 1;
+
+ pool = c->pool;
+
+ ngx_close_connection(c);
+
+ ngx_destroy_pool(pool);
+}
+
+
+ngx_int_t
+ngx_http_v3_register_uni_stream(ngx_connection_t *c, uint64_t type)
+{
+ ngx_int_t index;
+ ngx_http_v3_session_t *h3c;
+ ngx_http_v3_uni_stream_t *us;
+
+ h3c = ngx_http_v3_get_session(c);
+
+ switch (type) {
+
+ 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;
+ 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;
+ 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;
+
+ break;
+
+ default:
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 stream 0x%02xL", type);
+
+ if (h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_ENCODER] == NULL
+ || h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_DECODER] == NULL
+ || h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_CONTROL] == NULL)
+ {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "missing mandatory stream");
+ return NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR;
+ }
+
+ index = -1;
+ }
+
+ if (index >= 0) {
+ if (h3c->known_streams[index]) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "stream exists");
+ return NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR;
+ }
+
+ h3c->known_streams[index] = c;
+
+ us = c->data;
+ us->index = index;
+ }
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_http_v3_uni_read_handler(ngx_event_t *rev)
+{
+ u_char buf[128];
+ ssize_t n;
+ ngx_buf_t b;
+ ngx_int_t rc;
+ ngx_connection_t *c;
+ ngx_http_v3_session_t *h3c;
+ 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");
+
+ if (c->close) {
+ ngx_http_v3_close_uni_stream(c);
+ return;
+ }
+
+ ngx_memzero(&b, sizeof(ngx_buf_t));
+
+ while (rev->ready) {
+
+ n = c->recv(c, buf, sizeof(buf));
+
+ if (n == NGX_ERROR) {
+ rc = NGX_HTTP_V3_ERR_INTERNAL_ERROR;
+ goto failed;
+ }
+
+ if (n == 0) {
+ if (us->index >= 0) {
+ rc = NGX_HTTP_V3_ERR_CLOSED_CRITICAL_STREAM;
+ goto failed;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read eof");
+ ngx_http_v3_close_uni_stream(c);
+ return;
+ }
+
+ if (n == NGX_AGAIN) {
+ break;
+ }
+
+ b.pos = buf;
+ b.last = buf + n;
+
+ h3c = ngx_http_v3_get_session(c);
+ h3c->total_bytes += n;
+
+ if (ngx_http_v3_check_flood(c) != NGX_OK) {
+ ngx_http_v3_close_uni_stream(c);
+ return;
+ }
+
+ rc = ngx_http_v3_parse_uni(c, &us->parse, &b);
+
+ if (rc == NGX_DONE) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 read done");
+ ngx_http_v3_close_uni_stream(c);
+ return;
+ }
+
+ if (rc > 0) {
+ goto failed;
+ }
+
+ if (rc != NGX_AGAIN) {
+ rc = NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR;
+ goto failed;
+ }
+ }
+
+ if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+ rc = NGX_HTTP_V3_ERR_INTERNAL_ERROR;
+ goto failed;
+ }
+
+ return;
+
+failed:
+
+ ngx_http_v3_finalize_connection(c, rc, "stream error");
+ ngx_http_v3_close_uni_stream(c);
+}
+
+
+static void
+ngx_http_v3_uni_dummy_read_handler(ngx_event_t *rev)
+{
+ u_char ch;
+ ngx_connection_t *c;
+
+ c = rev->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 dummy read handler");
+
+ if (c->close) {
+ ngx_http_v3_close_uni_stream(c);
+ return;
+ }
+
+ if (rev->ready) {
+ if (c->recv(c, &ch, 1) != 0) {
+ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, NULL);
+ ngx_http_v3_close_uni_stream(c);
+ return;
+ }
+ }
+
+ if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR,
+ NULL);
+ ngx_http_v3_close_uni_stream(c);
+ }
+}
+
+
+static void
+ngx_http_v3_uni_dummy_write_handler(ngx_event_t *wev)
+{
+ ngx_connection_t *c;
+
+ c = wev->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 dummy write handler");
+
+ if (ngx_handle_write_event(wev, 0) != NGX_OK) {
+ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR,
+ NULL);
+ ngx_http_v3_close_uni_stream(c);
+ }
+}
+
+
+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_session_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 = ngx_http_v3_get_session(c);
+
+ if (index >= 0) {
+ if (h3c->known_streams[index]) {
+ return h3c->known_streams[index];
+ }
+ }
+
+ sc = ngx_quic_open_stream(c, 0);
+ if (sc == NULL) {
+ goto failed;
+ }
+
+ ngx_quic_cancelable_stream(sc);
+
+ 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_dummy_read_handler;
+ sc->write->handler = ngx_http_v3_uni_dummy_write_handler;
+
+ if (index >= 0) {
+ h3c->known_streams[index] = sc;
+ }
+
+ n = (u_char *) ngx_http_v3_encode_varlen_int(buf, type) - buf;
+
+ h3c = ngx_http_v3_get_session(c);
+ h3c->total_bytes += n;
+
+ if (sc->send(sc, buf, n) != (ssize_t) n) {
+ goto failed;
+ }
+
+ ngx_post_event(sc->read, &ngx_posted_events);
+
+ return sc;
+
+failed:
+
+ ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create server stream");
+
+ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR,
+ "failed to create server stream");
+ if (sc) {
+ ngx_http_v3_close_uni_stream(sc);
+ }
+
+ return NULL;
+}
+
+
+ngx_int_t
+ngx_http_v3_send_settings(ngx_connection_t *c)
+{
+ u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 6];
+ size_t n;
+ ngx_connection_t *cc;
+ ngx_http_v3_session_t *h3c;
+ ngx_http_v3_srv_conf_t *h3scf;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send settings");
+
+ cc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL);
+ if (cc == NULL) {
+ return NGX_ERROR;
+ }
+
+ h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module);
+
+ n = ngx_http_v3_encode_varlen_int(NULL,
+ NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY);
+ n += ngx_http_v3_encode_varlen_int(NULL, h3scf->max_table_capacity);
+ n += ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_PARAM_BLOCKED_STREAMS);
+ n += ngx_http_v3_encode_varlen_int(NULL, h3scf->max_blocked_streams);
+
+ p = (u_char *) ngx_http_v3_encode_varlen_int(buf,
+ NGX_HTTP_V3_FRAME_SETTINGS);
+ p = (u_char *) ngx_http_v3_encode_varlen_int(p, n);
+ p = (u_char *) ngx_http_v3_encode_varlen_int(p,
+ NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY);
+ p = (u_char *) ngx_http_v3_encode_varlen_int(p, h3scf->max_table_capacity);
+ p = (u_char *) ngx_http_v3_encode_varlen_int(p,
+ NGX_HTTP_V3_PARAM_BLOCKED_STREAMS);
+ p = (u_char *) ngx_http_v3_encode_varlen_int(p, h3scf->max_blocked_streams);
+ n = p - buf;
+
+ h3c = ngx_http_v3_get_session(c);
+ h3c->total_bytes += n;
+
+ if (cc->send(cc, buf, n) != (ssize_t) n) {
+ goto failed;
+ }
+
+ return NGX_OK;
+
+failed:
+
+ ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send settings");
+
+ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD,
+ "failed to send settings");
+ ngx_http_v3_close_uni_stream(cc);
+
+ return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_http_v3_send_goaway(ngx_connection_t *c, uint64_t id)
+{
+ u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 3];
+ size_t n;
+ ngx_connection_t *cc;
+ ngx_http_v3_session_t *h3c;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send goaway %uL", id);
+
+ cc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL);
+ if (cc == NULL) {
+ return NGX_ERROR;
+ }
+
+ n = ngx_http_v3_encode_varlen_int(NULL, id);
+ p = (u_char *) ngx_http_v3_encode_varlen_int(buf, NGX_HTTP_V3_FRAME_GOAWAY);
+ p = (u_char *) ngx_http_v3_encode_varlen_int(p, n);
+ p = (u_char *) ngx_http_v3_encode_varlen_int(p, id);
+ n = p - buf;
+
+ h3c = ngx_http_v3_get_session(c);
+ h3c->total_bytes += n;
+
+ if (cc->send(cc, buf, n) != (ssize_t) n) {
+ goto failed;
+ }
+
+ return NGX_OK;
+
+failed:
+
+ ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send goaway");
+
+ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD,
+ "failed to send goaway");
+ ngx_http_v3_close_uni_stream(cc);
+
+ return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_http_v3_send_ack_section(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_http_v3_session_t *h3c;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 send section acknowledgement %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;
+
+ h3c = ngx_http_v3_get_session(c);
+ h3c->total_bytes += n;
+
+ if (dc->send(dc, buf, n) != (ssize_t) n) {
+ goto failed;
+ }
+
+ return NGX_OK;
+
+failed:
+
+ ngx_log_error(NGX_LOG_ERR, c->log, 0,
+ "failed to send section acknowledgement");
+
+ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD,
+ "failed to send section acknowledgement");
+ ngx_http_v3_close_uni_stream(dc);
+
+ return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_http_v3_send_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_http_v3_session_t *h3c;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 send stream cancellation %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;
+
+ h3c = ngx_http_v3_get_session(c);
+ h3c->total_bytes += n;
+
+ if (dc->send(dc, buf, n) != (ssize_t) n) {
+ goto failed;
+ }
+
+ return NGX_OK;
+
+failed:
+
+ ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send stream cancellation");
+
+ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD,
+ "failed to send stream cancellation");
+ ngx_http_v3_close_uni_stream(dc);
+
+ return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_http_v3_send_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_http_v3_session_t *h3c;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 send insert count increment %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;
+
+ h3c = ngx_http_v3_get_session(c);
+ h3c->total_bytes += n;
+
+ if (dc->send(dc, buf, n) != (ssize_t) n) {
+ goto failed;
+ }
+
+ return NGX_OK;
+
+failed:
+
+ ngx_log_error(NGX_LOG_ERR, c->log, 0,
+ "failed to send insert count increment");
+
+ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD,
+ "failed to send insert count increment");
+ ngx_http_v3_close_uni_stream(dc);
+
+ return NGX_ERROR;
+}
+
+
+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);
+
+ /* we do not use dynamic tables */
+
+ return NGX_OK;
+}
diff --git a/src/http/v3/ngx_http_v3_uni.h b/src/http/v3/ngx_http_v3_uni.h
new file mode 100644
index 000000000..911e153d7
--- /dev/null
+++ b/src/http/v3/ngx_http_v3_uni.h
@@ -0,0 +1,32 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_HTTP_V3_UNI_H_INCLUDED_
+#define _NGX_HTTP_V3_UNI_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+void ngx_http_v3_init_uni_stream(ngx_connection_t *c);
+ngx_int_t ngx_http_v3_register_uni_stream(ngx_connection_t *c, uint64_t type);
+
+ngx_int_t ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id);
+
+ngx_int_t ngx_http_v3_send_settings(ngx_connection_t *c);
+ngx_int_t ngx_http_v3_send_goaway(ngx_connection_t *c, uint64_t id);
+ngx_int_t ngx_http_v3_send_ack_section(ngx_connection_t *c,
+ ngx_uint_t stream_id);
+ngx_int_t ngx_http_v3_send_cancel_stream(ngx_connection_t *c,
+ ngx_uint_t stream_id);
+ngx_int_t ngx_http_v3_send_inc_insert_count(ngx_connection_t *c,
+ ngx_uint_t inc);
+
+
+#endif /* _NGX_HTTP_V3_UNI_H_INCLUDED_ */