aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey A. Osokin <osa@FreeBSD.org>2022-01-05 16:45:15 +0000
committerSergey A. Osokin <osa@FreeBSD.org>2022-01-05 16:47:20 +0000
commitcada18903ccc63ec77dfb54bf36d8b050682ef69 (patch)
tree8f32ca3c825b6135ef156542cc16c5f4327fc6a0
parent4a0e524371da88340fa77947f067069629829d4c (diff)
downloadports-cada18903ccc63ec77dfb54bf36d8b050682ef69.tar.gz
ports-cada18903ccc63ec77dfb54bf36d8b050682ef69.zip
www/nginx-devel: initial support for HTTPv3
Bump PORTREVISION. Based on ideas from: ashish
-rw-r--r--www/nginx-devel/Makefile12
-rw-r--r--www/nginx-devel/Makefile.options.desc1
-rw-r--r--www/nginx-devel/files/extra-patch-httpv325543
3 files changed, 25554 insertions, 2 deletions
diff --git a/www/nginx-devel/Makefile b/www/nginx-devel/Makefile
index 9140dbf72c4f..226d737b15b1 100644
--- a/www/nginx-devel/Makefile
+++ b/www/nginx-devel/Makefile
@@ -2,7 +2,7 @@
PORTNAME?= nginx
PORTVERSION= 1.21.5
-PORTREVISION= 3
+PORTREVISION= 4
CATEGORIES= www
MASTER_SITES= https://nginx.org/download/ \
LOCAL/osa
@@ -72,7 +72,7 @@ OPTIONS_GROUP_HTTPGRP= GOOGLE_PERFTOOLS HTTP HTTP_ADDITION HTTP_AUTH_REQ \
HTTP_CACHE HTTP_DAV HTTP_DEGRADATION HTTP_FLV HTTP_GUNZIP_FILTER \
HTTP_GZIP_STATIC HTTP_IMAGE_FILTER HTTP_MP4 HTTP_PERL \
HTTP_RANDOM_INDEX HTTP_REALIP HTTP_SECURE_LINK HTTP_SLICE HTTP_SSL \
- HTTP_STATUS HTTP_SUB HTTP_XSLT HTTPV2
+ HTTP_STATUS HTTP_SUB HTTP_XSLT HTTPV2 HTTPV3
OPTIONS_GROUP_MAILGRP= MAIL MAIL_IMAP MAIL_POP3 MAIL_SMTP MAIL_SSL
@@ -170,6 +170,14 @@ HTTP_XSLT_USE= GNOME=libxml2,libxslt
HTTP_XSLT_VARS= DSO_BASEMODS+=http_xslt_module
HTTPV2_IMPLIES= HTTP_SSL
HTTPV2_CONFIGURE_ON= --with-http_v2_module
+HTTPV3_BUILD_DEPENDS= ${LOCALBASE}/bin/bssl:security/boringssl
+HTTPV3_RUN_DEPENDS= ${LOCALBASE}/bin/bssl:security/boringssl
+HTTPV3_CONFIGURE_ON= --with-ld-opt="-L ${LOCALBASE}/lib -Wl,-rpath,${LOCALBASE}/lib" \
+ --with-http_ssl_module \
+ --build=nginx-quic \
+ --with-stream_quic_module \
+ --with-http_v3_module
+HTTPV3_EXTRA_PATCHES= ${PATCHDIR}/extra-patch-httpv3:-p1
MAIL_VARS= DSO_BASEMODS+=mail
MAIL_IMAP_CONFIGURE_OFF= --without-mail_imap_module
MAIL_POP3_CONFIGURE_OFF= --without-mail_pop3_module
diff --git a/www/nginx-devel/Makefile.options.desc b/www/nginx-devel/Makefile.options.desc
index 0424d95d8150..b39e871d0a9b 100644
--- a/www/nginx-devel/Makefile.options.desc
+++ b/www/nginx-devel/Makefile.options.desc
@@ -22,6 +22,7 @@ GSSAPI_DESC= GSSAPI implementation (imply HTTP_AUTH_KRB5)
HEADERS_MORE_DESC= 3rd party headers_more module
HTTPGRP_DESC= Modules that require HTTP module
HTTPV2_DESC= Enable HTTP/2 protocol support (SSL req.)
+HTTPV3_DESC= Enable HTTP/3 protocol support (BoringSSL req.)
HTTP_ACCEPT_LANGUAGE_DESC= 3rd party accept_language module
HTTP_ADDITION_DESC= Enable http_addition module
HTTP_AUTH_DIGEST_DESC= 3rd party http_authdigest module
diff --git a/www/nginx-devel/files/extra-patch-httpv3 b/www/nginx-devel/files/extra-patch-httpv3
new file mode 100644
index 000000000000..dac679832645
--- /dev/null
+++ b/www/nginx-devel/files/extra-patch-httpv3
@@ -0,0 +1,25543 @@
+diff -r 67408b4a12c0 auto/lib/openssl/conf
+--- a/auto/lib/openssl/conf Tue Dec 28 18:28:38 2021 +0300
++++ b/auto/lib/openssl/conf Tue Jan 04 18:14:15 2022 -0500
+@@ -5,12 +5,16 @@
+
+ if [ $OPENSSL != NONE ]; then
+
++ have=NGX_OPENSSL . auto/have
++ have=NGX_SSL . auto/have
++
++ if [ $USE_OPENSSL_QUIC = YES ]; then
++ have=NGX_QUIC . auto/have
++ fi
++
+ case "$CC" in
+
+ cl | bcc32)
+- have=NGX_OPENSSL . auto/have
+- have=NGX_SSL . auto/have
+-
+ CFLAGS="$CFLAGS -DNO_SYS_TYPES_H"
+
+ CORE_INCS="$CORE_INCS $OPENSSL/openssl/include"
+@@ -33,9 +37,6 @@
+ ;;
+
+ *)
+- have=NGX_OPENSSL . auto/have
+- have=NGX_SSL . auto/have
+-
+ CORE_INCS="$CORE_INCS $OPENSSL/.openssl/include"
+ CORE_DEPS="$CORE_DEPS $OPENSSL/.openssl/include/openssl/ssl.h"
+ CORE_LIBS="$CORE_LIBS $OPENSSL/.openssl/lib/libssl.a"
+@@ -139,4 +140,28 @@
+ exit 1
+ fi
+
++ if [ $USE_OPENSSL_QUIC = YES ]; then
++
++ ngx_feature="OpenSSL QUIC support"
++ ngx_feature_name="NGX_QUIC"
++ ngx_feature_run=no
++ ngx_feature_incs="#include <openssl/ssl.h>"
++ ngx_feature_path=
++ ngx_feature_libs="-lssl -lcrypto $NGX_LIBDL $NGX_LIBPTHREAD"
++ ngx_feature_test="SSL_set_quic_method(NULL, NULL)"
++ . auto/feature
++
++ if [ $ngx_found = no ]; then
++
++cat << END
++
++$0: error: certain modules require OpenSSL QUIC support.
++You can either do not enable the modules, or install the OpenSSL library with
++QUIC support into the system, or build the OpenSSL library with QUIC support
++statically from the source with nginx by using --with-openssl=<path> option.
++
++END
++ exit 1
++ fi
++ fi
+ fi
+diff -r 67408b4a12c0 auto/make
+--- a/auto/make Tue Dec 28 18:28:38 2021 +0300
++++ b/auto/make Tue Jan 04 18:14:15 2022 -0500
+@@ -6,9 +6,10 @@
+ echo "creating $NGX_MAKEFILE"
+
+ mkdir -p $NGX_OBJS/src/core $NGX_OBJS/src/event $NGX_OBJS/src/event/modules \
++ $NGX_OBJS/src/event/quic \
+ $NGX_OBJS/src/os/unix $NGX_OBJS/src/os/win32 \
+- $NGX_OBJS/src/http $NGX_OBJS/src/http/v2 $NGX_OBJS/src/http/modules \
+- $NGX_OBJS/src/http/modules/perl \
++ $NGX_OBJS/src/http $NGX_OBJS/src/http/v2 $NGX_OBJS/src/http/v3 \
++ $NGX_OBJS/src/http/modules $NGX_OBJS/src/http/modules/perl \
+ $NGX_OBJS/src/mail \
+ $NGX_OBJS/src/stream \
+ $NGX_OBJS/src/misc
+diff -r 67408b4a12c0 auto/modules
+--- a/auto/modules Tue Dec 28 18:28:38 2021 +0300
++++ b/auto/modules Tue Jan 04 18:14:15 2022 -0500
+@@ -102,7 +102,7 @@
+ fi
+
+
+- if [ $HTTP_V2 = YES ]; then
++ if [ $HTTP_V2 = YES -o $HTTP_V3 = YES ]; then
+ HTTP_SRCS="$HTTP_SRCS $HTTP_HUFF_SRCS"
+ fi
+
+@@ -124,6 +124,7 @@
+ # ngx_http_header_filter
+ # ngx_http_chunked_filter
+ # ngx_http_v2_filter
++ # ngx_http_v3_filter
+ # ngx_http_range_header_filter
+ # ngx_http_gzip_filter
+ # ngx_http_postpone_filter
+@@ -156,6 +157,7 @@
+ ngx_http_header_filter_module \
+ ngx_http_chunked_filter_module \
+ ngx_http_v2_filter_module \
++ ngx_http_v3_filter_module \
+ ngx_http_range_header_filter_module \
+ ngx_http_gzip_filter_module \
+ ngx_http_postpone_filter_module \
+@@ -217,6 +219,17 @@
+ . auto/module
+ fi
+
++ if [ $HTTP_V3 = YES ]; then
++ ngx_module_name=ngx_http_v3_filter_module
++ ngx_module_incs=
++ ngx_module_deps=
++ ngx_module_srcs=src/http/v3/ngx_http_v3_filter_module.c
++ ngx_module_libs=
++ ngx_module_link=$HTTP_V3
++
++ . auto/module
++ fi
++
+ if :; then
+ ngx_module_name=ngx_http_range_header_filter_module
+ ngx_module_incs=
+@@ -426,6 +439,33 @@
+ . auto/module
+ fi
+
++ if [ $HTTP_V3 = YES ]; then
++ USE_OPENSSL_QUIC=YES
++ HTTP_SSL=YES
++
++ have=NGX_HTTP_V3 . auto/have
++ have=NGX_HTTP_HEADERS . auto/have
++
++ ngx_module_name=ngx_http_v3_module
++ ngx_module_incs=src/http/v3
++ ngx_module_deps="src/http/v3/ngx_http_v3.h \
++ src/http/v3/ngx_http_v3_encode.h \
++ src/http/v3/ngx_http_v3_parse.h \
++ src/http/v3/ngx_http_v3_table.h \
++ src/http/v3/ngx_http_v3_uni.h"
++ ngx_module_srcs="src/http/v3/ngx_http_v3.c \
++ src/http/v3/ngx_http_v3_encode.c \
++ src/http/v3/ngx_http_v3_parse.c \
++ src/http/v3/ngx_http_v3_table.c \
++ src/http/v3/ngx_http_v3_uni.c \
++ src/http/v3/ngx_http_v3_request.c \
++ src/http/v3/ngx_http_v3_module.c"
++ ngx_module_libs=
++ ngx_module_link=$HTTP_V3
++
++ . auto/module
++ fi
++
+ if :; then
+ ngx_module_name=ngx_http_static_module
+ ngx_module_incs=
+@@ -1035,6 +1075,20 @@
+
+ ngx_module_incs=
+
++ if [ $STREAM_QUIC = YES ]; then
++ USE_OPENSSL_QUIC=YES
++ have=NGX_STREAM_QUIC . auto/have
++ STREAM_SSL=YES
++
++ ngx_module_name=ngx_stream_quic_module
++ ngx_module_deps=src/stream/ngx_stream_quic_module.h
++ ngx_module_srcs=src/stream/ngx_stream_quic_module.c
++ ngx_module_libs=
++ ngx_module_link=$STREAM_QUIC
++
++ . auto/module
++ fi
++
+ if [ $STREAM_SSL = YES ]; then
+ USE_OPENSSL=YES
+ have=NGX_STREAM_SSL . auto/have
+@@ -1272,6 +1326,60 @@
+ fi
+
+
++if [ $USE_OPENSSL_QUIC = YES ]; then
++ ngx_module_type=CORE
++ ngx_module_name=ngx_quic_module
++ ngx_module_incs=
++ ngx_module_deps="src/event/quic/ngx_event_quic.h \
++ src/event/quic/ngx_event_quic_transport.h \
++ src/event/quic/ngx_event_quic_protection.h \
++ src/event/quic/ngx_event_quic_connection.h \
++ src/event/quic/ngx_event_quic_frames.h \
++ src/event/quic/ngx_event_quic_connid.h \
++ src/event/quic/ngx_event_quic_migration.h \
++ src/event/quic/ngx_event_quic_streams.h \
++ src/event/quic/ngx_event_quic_ssl.h \
++ src/event/quic/ngx_event_quic_tokens.h \
++ src/event/quic/ngx_event_quic_ack.h \
++ src/event/quic/ngx_event_quic_output.h \
++ src/event/quic/ngx_event_quic_socket.h"
++ ngx_module_srcs="src/event/quic/ngx_event_quic.c \
++ src/event/quic/ngx_event_quic_transport.c \
++ src/event/quic/ngx_event_quic_protection.c \
++ src/event/quic/ngx_event_quic_frames.c \
++ src/event/quic/ngx_event_quic_connid.c \
++ src/event/quic/ngx_event_quic_migration.c \
++ src/event/quic/ngx_event_quic_streams.c \
++ src/event/quic/ngx_event_quic_ssl.c \
++ src/event/quic/ngx_event_quic_tokens.c \
++ src/event/quic/ngx_event_quic_ack.c \
++ src/event/quic/ngx_event_quic_output.c \
++ src/event/quic/ngx_event_quic_socket.c"
++
++ ngx_module_libs=
++ ngx_module_link=YES
++ ngx_module_order=
++
++ . auto/module
++
++ if [ $QUIC_BPF = YES -a $SO_COOKIE_FOUND = YES ]; then
++ ngx_module_type=CORE
++ ngx_module_name=ngx_quic_bpf_module
++ ngx_module_incs=
++ ngx_module_deps=
++ ngx_module_srcs="src/event/quic/ngx_event_quic_bpf.c \
++ src/event/quic/ngx_event_quic_bpf_code.c"
++ ngx_module_libs=
++ ngx_module_link=YES
++ ngx_module_order=
++
++ . auto/module
++
++ have=NGX_QUIC_BPF . auto/have
++ fi
++fi
++
++
+ if [ $USE_PCRE = YES ]; then
+ ngx_module_type=CORE
+ ngx_module_name=ngx_regex_module
+diff -r 67408b4a12c0 auto/options
+--- a/auto/options Tue Dec 28 18:28:38 2021 +0300
++++ b/auto/options Tue Jan 04 18:14:15 2022 -0500
+@@ -45,6 +45,8 @@
+
+ NGX_FILE_AIO=NO
+
++QUIC_BPF=NO
++
+ HTTP=YES
+
+ NGX_HTTP_LOG_PATH=
+@@ -59,6 +61,7 @@
+ HTTP_GZIP=YES
+ HTTP_SSL=NO
+ HTTP_V2=NO
++HTTP_V3=NO
+ HTTP_SSI=YES
+ HTTP_REALIP=NO
+ HTTP_XSLT=NO
+@@ -116,6 +119,7 @@
+
+ STREAM=NO
+ STREAM_SSL=NO
++STREAM_QUIC=NO
+ STREAM_REALIP=NO
+ STREAM_LIMIT_CONN=YES
+ STREAM_ACCESS=YES
+@@ -149,6 +153,7 @@
+ PCRE2=YES
+
+ USE_OPENSSL=NO
++USE_OPENSSL_QUIC=NO
+ OPENSSL=NONE
+
+ USE_ZLIB=NO
+@@ -166,6 +171,8 @@
+ NGX_GOOGLE_PERFTOOLS=NO
+ NGX_CPP_TEST=NO
+
++SO_COOKIE_FOUND=NO
++
+ NGX_LIBATOMIC=NO
+
+ NGX_CPU_CACHE_LINE=
+@@ -211,6 +218,8 @@
+
+ --with-file-aio) NGX_FILE_AIO=YES ;;
+
++ --without-quic_bpf_module) QUIC_BPF=NONE ;;
++
+ --with-ipv6)
+ NGX_POST_CONF_MSG="$NGX_POST_CONF_MSG
+ $0: warning: the \"--with-ipv6\" option is deprecated"
+@@ -228,6 +237,7 @@
+
+ --with-http_ssl_module) HTTP_SSL=YES ;;
+ --with-http_v2_module) HTTP_V2=YES ;;
++ --with-http_v3_module) HTTP_V3=YES ;;
+ --with-http_realip_module) HTTP_REALIP=YES ;;
+ --with-http_addition_module) HTTP_ADDITION=YES ;;
+ --with-http_xslt_module) HTTP_XSLT=YES ;;
+@@ -314,6 +324,7 @@
+ --with-stream) STREAM=YES ;;
+ --with-stream=dynamic) STREAM=DYNAMIC ;;
+ --with-stream_ssl_module) STREAM_SSL=YES ;;
++ --with-stream_quic_module) STREAM_QUIC=YES ;;
+ --with-stream_realip_module) STREAM_REALIP=YES ;;
+ --with-stream_geoip_module) STREAM_GEOIP=YES ;;
+ --with-stream_geoip_module=dynamic)
+@@ -443,8 +454,11 @@
+
+ --with-file-aio enable file AIO support
+
++ --without-quic_bpf_module disable ngx_quic_bpf_module
++
+ --with-http_ssl_module enable ngx_http_ssl_module
+ --with-http_v2_module enable ngx_http_v2_module
++ --with-http_v3_module enable ngx_http_v3_module
+ --with-http_realip_module enable ngx_http_realip_module
+ --with-http_addition_module enable ngx_http_addition_module
+ --with-http_xslt_module enable ngx_http_xslt_module
+@@ -533,6 +547,7 @@
+ --with-stream enable TCP/UDP proxy module
+ --with-stream=dynamic enable dynamic TCP/UDP proxy module
+ --with-stream_ssl_module enable ngx_stream_ssl_module
++ --with-stream_quic_module enable ngx_stream_quic_module
+ --with-stream_realip_module enable ngx_stream_realip_module
+ --with-stream_geoip_module enable ngx_stream_geoip_module
+ --with-stream_geoip_module=dynamic enable dynamic ngx_stream_geoip_module
+diff -r 67408b4a12c0 auto/os/linux
+--- a/auto/os/linux Tue Dec 28 18:28:38 2021 +0300
++++ b/auto/os/linux Tue Jan 04 18:14:15 2022 -0500
+@@ -233,3 +233,63 @@
+
+
+ CC_AUX_FLAGS="$cc_aux_flags -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64"
++
++
++# BPF sockhash
++
++ngx_feature="BPF sockhash"
++ngx_feature_name="NGX_HAVE_BPF"
++ngx_feature_run=no
++ngx_feature_incs="#include <linux/bpf.h>
++ #include <sys/syscall.h>"
++ngx_feature_path=
++ngx_feature_libs=
++ngx_feature_test="union bpf_attr attr = { 0 };
++
++ attr.map_flags = 0;
++ attr.map_type = BPF_MAP_TYPE_SOCKHASH;
++
++ syscall(__NR_bpf, 0, &attr, 0);"
++. auto/feature
++
++if [ $ngx_found = yes ]; then
++ CORE_SRCS="$CORE_SRCS src/core/ngx_bpf.c"
++ CORE_DEPS="$CORE_DEPS src/core/ngx_bpf.h"
++
++ if [ $QUIC_BPF != NONE ]; then
++ QUIC_BPF=YES
++ fi
++fi
++
++
++ngx_feature="SO_COOKIE"
++ngx_feature_name="NGX_HAVE_SO_COOKIE"
++ngx_feature_run=no
++ngx_feature_incs="#include <sys/socket.h>
++ #include <stdint.h>"
++ngx_feature_path=
++ngx_feature_libs=
++ngx_feature_test="socklen_t optlen = sizeof(uint64_t);
++ uint64_t cookie;
++ getsockopt(0, SOL_SOCKET, SO_COOKIE, &cookie, &optlen)"
++. auto/feature
++
++if [ $ngx_found = yes ]; then
++ SO_COOKIE_FOUND=YES
++fi
++
++
++# UDP segmentation offloading
++
++ngx_feature="UDP_SEGMENT"
++ngx_feature_name="NGX_HAVE_UDP_SEGMENT"
++ngx_feature_run=no
++ngx_feature_incs="#include <sys/socket.h>
++ #include <stdint.h>
++ #include <netinet/udp.h>"
++ngx_feature_path=
++ngx_feature_libs=
++ngx_feature_test="socklen_t optlen = sizeof(int);
++ int val;
++ getsockopt(0, SOL_UDP, UDP_SEGMENT, &val, &optlen)"
++. auto/feature
+diff -r 67408b4a12c0 auto/sources
+--- a/auto/sources Tue Dec 28 18:28:38 2021 +0300
++++ b/auto/sources Tue Jan 04 18:14:15 2022 -0500
+@@ -83,13 +83,14 @@
+
+ EVENT_MODULES="ngx_events_module ngx_event_core_module"
+
+-EVENT_INCS="src/event src/event/modules"
++EVENT_INCS="src/event src/event/modules src/event/quic"
+
+ EVENT_DEPS="src/event/ngx_event.h \
+ src/event/ngx_event_timer.h \
+ src/event/ngx_event_posted.h \
+ src/event/ngx_event_connect.h \
+- src/event/ngx_event_pipe.h"
++ src/event/ngx_event_pipe.h \
++ src/event/ngx_event_udp.h"
+
+ EVENT_SRCS="src/event/ngx_event.c \
+ src/event/ngx_event_timer.c \
+diff -r 67408b4a12c0 src/core/nginx.c
+--- a/src/core/nginx.c Tue Dec 28 18:28:38 2021 +0300
++++ b/src/core/nginx.c Tue Jan 04 18:14:15 2022 -0500
+@@ -680,6 +680,9 @@
+
+ ls = cycle->listening.elts;
+ for (i = 0; i < cycle->listening.nelts; i++) {
++ if (ls[i].ignore) {
++ continue;
++ }
+ p = ngx_sprintf(p, "%ud;", ls[i].fd);
+ }
+
+diff -r 67408b4a12c0 src/core/ngx_bpf.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/core/ngx_bpf.c Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,143 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++
++#define NGX_BPF_LOGBUF_SIZE (16 * 1024)
++
++
++static ngx_inline int
++ngx_bpf(enum bpf_cmd cmd, union bpf_attr *attr, unsigned int size)
++{
++ return syscall(__NR_bpf, cmd, attr, size);
++}
++
++
++void
++ngx_bpf_program_link(ngx_bpf_program_t *program, const char *symbol, int fd)
++{
++ ngx_uint_t i;
++ ngx_bpf_reloc_t *rl;
++
++ rl = program->relocs;
++
++ for (i = 0; i < program->nrelocs; i++) {
++ if (ngx_strcmp(rl[i].name, symbol) == 0) {
++ program->ins[rl[i].offset].src_reg = 1;
++ program->ins[rl[i].offset].imm = fd;
++ }
++ }
++}
++
++
++int
++ngx_bpf_load_program(ngx_log_t *log, ngx_bpf_program_t *program)
++{
++ int fd;
++ union bpf_attr attr;
++#if (NGX_DEBUG)
++ char buf[NGX_BPF_LOGBUF_SIZE];
++#endif
++
++ ngx_memzero(&attr, sizeof(union bpf_attr));
++
++ attr.license = (uintptr_t) program->license;
++ attr.prog_type = program->type;
++ attr.insns = (uintptr_t) program->ins;
++ attr.insn_cnt = program->nins;
++
++#if (NGX_DEBUG)
++ /* for verifier errors */
++ attr.log_buf = (uintptr_t) buf;
++ attr.log_size = NGX_BPF_LOGBUF_SIZE;
++ attr.log_level = 1;
++#endif
++
++ fd = ngx_bpf(BPF_PROG_LOAD, &attr, sizeof(attr));
++ if (fd < 0) {
++ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
++ "failed to load BPF program");
++
++ ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0,
++ "bpf verifier: %s", buf);
++
++ return -1;
++ }
++
++ return fd;
++}
++
++
++int
++ngx_bpf_map_create(ngx_log_t *log, enum bpf_map_type type, int key_size,
++ int value_size, int max_entries, uint32_t map_flags)
++{
++ int fd;
++ union bpf_attr attr;
++
++ ngx_memzero(&attr, sizeof(union bpf_attr));
++
++ attr.map_type = type;
++ attr.key_size = key_size;
++ attr.value_size = value_size;
++ attr.max_entries = max_entries;
++ attr.map_flags = map_flags;
++
++ fd = ngx_bpf(BPF_MAP_CREATE, &attr, sizeof(attr));
++ if (fd < 0) {
++ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
++ "failed to create BPF map");
++ return NGX_ERROR;
++ }
++
++ return fd;
++}
++
++
++int
++ngx_bpf_map_update(int fd, const void *key, const void *value, uint64_t flags)
++{
++ union bpf_attr attr;
++
++ ngx_memzero(&attr, sizeof(union bpf_attr));
++
++ attr.map_fd = fd;
++ attr.key = (uintptr_t) key;
++ attr.value = (uintptr_t) value;
++ attr.flags = flags;
++
++ return ngx_bpf(BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr));
++}
++
++
++int
++ngx_bpf_map_delete(int fd, const void *key)
++{
++ union bpf_attr attr;
++
++ ngx_memzero(&attr, sizeof(union bpf_attr));
++
++ attr.map_fd = fd;
++ attr.key = (uintptr_t) key;
++
++ return ngx_bpf(BPF_MAP_DELETE_ELEM, &attr, sizeof(attr));
++}
++
++
++int
++ngx_bpf_map_lookup(int fd, const void *key, void *value)
++{
++ union bpf_attr attr;
++
++ ngx_memzero(&attr, sizeof(union bpf_attr));
++
++ attr.map_fd = fd;
++ attr.key = (uintptr_t) key;
++ attr.value = (uintptr_t) value;
++
++ return ngx_bpf(BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr));
++}
+diff -r 67408b4a12c0 src/core/ngx_bpf.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/core/ngx_bpf.h Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,43 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#ifndef _NGX_BPF_H_INCLUDED_
++#define _NGX_BPF_H_INCLUDED_
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++
++#include <linux/bpf.h>
++
++
++typedef struct {
++ char *name;
++ int offset;
++} ngx_bpf_reloc_t;
++
++typedef struct {
++ char *license;
++ enum bpf_prog_type type;
++ struct bpf_insn *ins;
++ size_t nins;
++ ngx_bpf_reloc_t *relocs;
++ size_t nrelocs;
++} ngx_bpf_program_t;
++
++
++void ngx_bpf_program_link(ngx_bpf_program_t *program, const char *symbol,
++ int fd);
++int ngx_bpf_load_program(ngx_log_t *log, ngx_bpf_program_t *program);
++
++int ngx_bpf_map_create(ngx_log_t *log, enum bpf_map_type type, int key_size,
++ int value_size, int max_entries, uint32_t map_flags);
++int ngx_bpf_map_update(int fd, const void *key, const void *value,
++ uint64_t flags);
++int ngx_bpf_map_delete(int fd, const void *key);
++int ngx_bpf_map_lookup(int fd, const void *key, void *value);
++
++#endif /* _NGX_BPF_H_INCLUDED_ */
+diff -r 67408b4a12c0 src/core/ngx_connection.c
+--- a/src/core/ngx_connection.c Tue Dec 28 18:28:38 2021 +0300
++++ b/src/core/ngx_connection.c Tue Jan 04 18:14:15 2022 -0500
+@@ -1037,6 +1037,12 @@
+ ls = cycle->listening.elts;
+ for (i = 0; i < cycle->listening.nelts; i++) {
+
++#if (NGX_QUIC)
++ if (ls[i].quic) {
++ continue;
++ }
++#endif
++
+ c = ls[i].connection;
+
+ if (c) {
+diff -r 67408b4a12c0 src/core/ngx_connection.h
+--- a/src/core/ngx_connection.h Tue Dec 28 18:28:38 2021 +0300
++++ b/src/core/ngx_connection.h Tue Jan 04 18:14:15 2022 -0500
+@@ -73,6 +73,7 @@
+ unsigned reuseport:1;
+ unsigned add_reuseport:1;
+ unsigned keepalive:2;
++ unsigned quic:1;
+
+ unsigned deferred_accept:1;
+ unsigned delete_deferred:1;
+@@ -147,6 +148,10 @@
+
+ ngx_proxy_protocol_t *proxy_protocol;
+
++#if (NGX_QUIC || NGX_COMPAT)
++ ngx_quic_stream_t *quic;
++#endif
++
+ #if (NGX_SSL || NGX_COMPAT)
+ ngx_ssl_connection_t *ssl;
+ #endif
+diff -r 67408b4a12c0 src/core/ngx_core.h
+--- a/src/core/ngx_core.h Tue Dec 28 18:28:38 2021 +0300
++++ b/src/core/ngx_core.h Tue Jan 04 18:14:15 2022 -0500
+@@ -27,6 +27,7 @@
+ typedef struct ngx_thread_task_s ngx_thread_task_t;
+ typedef struct ngx_ssl_s ngx_ssl_t;
+ typedef struct ngx_proxy_protocol_s ngx_proxy_protocol_t;
++typedef struct ngx_quic_stream_s ngx_quic_stream_t;
+ typedef struct ngx_ssl_connection_s ngx_ssl_connection_t;
+ typedef struct ngx_udp_connection_s ngx_udp_connection_t;
+
+@@ -82,6 +83,9 @@
+ #include <ngx_resolver.h>
+ #if (NGX_OPENSSL)
+ #include <ngx_event_openssl.h>
++#if (NGX_QUIC)
++#include <ngx_event_quic.h>
++#endif
+ #endif
+ #include <ngx_process_cycle.h>
+ #include <ngx_conf_file.h>
+@@ -91,6 +95,9 @@
+ #include <ngx_connection.h>
+ #include <ngx_syslog.h>
+ #include <ngx_proxy_protocol.h>
++#if (NGX_HAVE_BPF)
++#include <ngx_bpf.h>
++#endif
+
+
+ #define LF (u_char) '\n'
+diff -r 67408b4a12c0 src/event/ngx_event.c
+--- a/src/event/ngx_event.c Tue Dec 28 18:28:38 2021 +0300
++++ b/src/event/ngx_event.c Tue Jan 04 18:14:15 2022 -0500
+@@ -266,6 +266,18 @@
+ ngx_int_t
+ ngx_handle_read_event(ngx_event_t *rev, ngx_uint_t flags)
+ {
++#if (NGX_QUIC)
++
++ ngx_connection_t *c;
++
++ c = rev->data;
++
++ if (c->quic) {
++ return ngx_quic_handle_read_event(rev, flags);
++ }
++
++#endif
++
+ if (ngx_event_flags & NGX_USE_CLEAR_EVENT) {
+
+ /* kqueue, epoll */
+@@ -336,9 +348,15 @@
+ {
+ ngx_connection_t *c;
+
++ c = wev->data;
++
++#if (NGX_QUIC)
++ if (c->quic) {
++ return ngx_quic_handle_write_event(wev, lowat);
++ }
++#endif
++
+ if (lowat) {
+- c = wev->data;
+-
+ if (ngx_send_lowat(c, lowat) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+@@ -917,6 +935,12 @@
+ {
+ int sndlowat;
+
++#if (NGX_QUIC)
++ if (c->quic) {
++ return NGX_OK;
++ }
++#endif
++
+ #if (NGX_HAVE_LOWAT_EVENT)
+
+ if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
+diff -r 67408b4a12c0 src/event/ngx_event.h
+--- a/src/event/ngx_event.h Tue Dec 28 18:28:38 2021 +0300
++++ b/src/event/ngx_event.h Tue Jan 04 18:14:15 2022 -0500
+@@ -493,12 +493,6 @@
+
+
+ void ngx_event_accept(ngx_event_t *ev);
+-#if !(NGX_WIN32)
+-void ngx_event_recvmsg(ngx_event_t *ev);
+-void ngx_udp_rbtree_insert_value(ngx_rbtree_node_t *temp,
+- ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
+-#endif
+-void ngx_delete_udp_connection(void *data);
+ ngx_int_t ngx_trylock_accept_mutex(ngx_cycle_t *cycle);
+ ngx_int_t ngx_enable_accept_events(ngx_cycle_t *cycle);
+ u_char *ngx_accept_log_error(ngx_log_t *log, u_char *buf, size_t len);
+@@ -528,6 +522,7 @@
+
+ #include <ngx_event_timer.h>
+ #include <ngx_event_posted.h>
++#include <ngx_event_udp.h>
+
+ #if (NGX_WIN32)
+ #include <ngx_iocp_module.h>
+diff -r 67408b4a12c0 src/event/ngx_event_openssl.c
+--- a/src/event/ngx_event_openssl.c Tue Dec 28 18:28:38 2021 +0300
++++ b/src/event/ngx_event_openssl.c Tue Jan 04 18:14:15 2022 -0500
+@@ -3146,6 +3146,13 @@
+ ngx_err_t err;
+ ngx_uint_t tries;
+
++#if (NGX_QUIC)
++ if (c->quic) {
++ /* QUIC streams inherit SSL object */
++ return NGX_OK;
++ }
++#endif
++
+ rc = NGX_OK;
+
+ ngx_ssl_ocsp_cleanup(c);
+diff -r 67408b4a12c0 src/event/ngx_event_openssl.h
+--- a/src/event/ngx_event_openssl.h Tue Dec 28 18:28:38 2021 +0300
++++ b/src/event/ngx_event_openssl.h Tue Jan 04 18:14:15 2022 -0500
+@@ -24,6 +24,14 @@
+ #include <openssl/engine.h>
+ #endif
+ #include <openssl/evp.h>
++#if (NGX_QUIC)
++#ifdef OPENSSL_IS_BORINGSSL
++#include <openssl/hkdf.h>
++#include <openssl/chacha.h>
++#else
++#include <openssl/kdf.h>
++#endif
++#endif
+ #include <openssl/hmac.h>
+ #ifndef OPENSSL_NO_OCSP
+ #include <openssl/ocsp.h>
+diff -r 67408b4a12c0 src/event/ngx_event_udp.c
+--- a/src/event/ngx_event_udp.c Tue Dec 28 18:28:38 2021 +0300
++++ b/src/event/ngx_event_udp.c Tue Jan 04 18:14:15 2022 -0500
+@@ -12,52 +12,37 @@
+
+ #if !(NGX_WIN32)
+
+-struct ngx_udp_connection_s {
+- ngx_rbtree_node_t node;
+- ngx_connection_t *connection;
+- ngx_buf_t *buffer;
+-};
+-
+-
+ static void ngx_close_accepted_udp_connection(ngx_connection_t *c);
+ static ssize_t ngx_udp_shared_recv(ngx_connection_t *c, u_char *buf,
+ size_t size);
+-static ngx_int_t ngx_insert_udp_connection(ngx_connection_t *c);
++static ngx_int_t ngx_create_udp_connection(ngx_connection_t *c);
+ static ngx_connection_t *ngx_lookup_udp_connection(ngx_listening_t *ls,
+- struct sockaddr *sockaddr, socklen_t socklen,
+- struct sockaddr *local_sockaddr, socklen_t local_socklen);
++ ngx_str_t *key, struct sockaddr *local_sockaddr, socklen_t local_socklen);
+
+
+ void
+ ngx_event_recvmsg(ngx_event_t *ev)
+ {
++ size_t len;
+ ssize_t n;
++ ngx_str_t key;
+ ngx_buf_t buf;
+ ngx_log_t *log;
+ ngx_err_t err;
+- socklen_t socklen, local_socklen;
++ socklen_t local_socklen;
+ ngx_event_t *rev, *wev;
+ struct iovec iov[1];
+ struct msghdr msg;
+ ngx_sockaddr_t sa, lsa;
+- struct sockaddr *sockaddr, *local_sockaddr;
++ ngx_udp_dgram_t dgram;
++ struct sockaddr *local_sockaddr;
+ ngx_listening_t *ls;
+ ngx_event_conf_t *ecf;
+ ngx_connection_t *c, *lc;
+ static u_char buffer[65535];
+
+-#if (NGX_HAVE_MSGHDR_MSG_CONTROL)
+-
+-#if (NGX_HAVE_IP_RECVDSTADDR)
+- u_char msg_control[CMSG_SPACE(sizeof(struct in_addr))];
+-#elif (NGX_HAVE_IP_PKTINFO)
+- u_char msg_control[CMSG_SPACE(sizeof(struct in_pktinfo))];
+-#endif
+-
+-#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO)
+- u_char msg_control6[CMSG_SPACE(sizeof(struct in6_pktinfo))];
+-#endif
+-
++#if (NGX_HAVE_ADDRINFO_CMSG)
++ u_char msg_control[CMSG_SPACE(sizeof(ngx_addrinfo_t))];
+ #endif
+
+ if (ev->timedout) {
+@@ -92,25 +77,13 @@
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 1;
+
+-#if (NGX_HAVE_MSGHDR_MSG_CONTROL)
+-
++#if (NGX_HAVE_ADDRINFO_CMSG)
+ if (ls->wildcard) {
++ msg.msg_control = &msg_control;
++ msg.msg_controllen = sizeof(msg_control);
+
+-#if (NGX_HAVE_IP_RECVDSTADDR || NGX_HAVE_IP_PKTINFO)
+- if (ls->sockaddr->sa_family == AF_INET) {
+- msg.msg_control = &msg_control;
+- msg.msg_controllen = sizeof(msg_control);
+- }
+-#endif
+-
+-#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO)
+- if (ls->sockaddr->sa_family == AF_INET6) {
+- msg.msg_control = &msg_control6;
+- msg.msg_controllen = sizeof(msg_control6);
+- }
+-#endif
+- }
+-
++ ngx_memzero(&msg_control, sizeof(msg_control));
++ }
+ #endif
+
+ n = recvmsg(lc->fd, &msg, 0);
+@@ -129,7 +102,7 @@
+ return;
+ }
+
+-#if (NGX_HAVE_MSGHDR_MSG_CONTROL)
++#if (NGX_HAVE_ADDRINFO_CMSG)
+ if (msg.msg_flags & (MSG_TRUNC|MSG_CTRUNC)) {
+ ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
+ "recvmsg() truncated data");
+@@ -137,21 +110,21 @@
+ }
+ #endif
+
+- sockaddr = msg.msg_name;
+- socklen = msg.msg_namelen;
++ dgram.sockaddr = msg.msg_name;
++ dgram.socklen = msg.msg_namelen;
+
+- if (socklen > (socklen_t) sizeof(ngx_sockaddr_t)) {
+- socklen = sizeof(ngx_sockaddr_t);
++ if (dgram.socklen > (socklen_t) sizeof(ngx_sockaddr_t)) {
++ dgram.socklen = sizeof(ngx_sockaddr_t);
+ }
+
+- if (socklen == 0) {
++ if (dgram.socklen == 0) {
+
+ /*
+ * on Linux recvmsg() returns zero msg_namelen
+ * when receiving packets from unbound AF_UNIX sockets
+ */
+
+- socklen = sizeof(struct sockaddr);
++ dgram.socklen = sizeof(struct sockaddr);
+ ngx_memzero(&sa, sizeof(struct sockaddr));
+ sa.sockaddr.sa_family = ls->sockaddr->sa_family;
+ }
+@@ -159,7 +132,7 @@
+ local_sockaddr = ls->sockaddr;
+ local_socklen = ls->socklen;
+
+-#if (NGX_HAVE_MSGHDR_MSG_CONTROL)
++#if (NGX_HAVE_ADDRINFO_CMSG)
+
+ if (ls->wildcard) {
+ struct cmsghdr *cmsg;
+@@ -171,66 +144,43 @@
+ cmsg != NULL;
+ cmsg = CMSG_NXTHDR(&msg, cmsg))
+ {
+-
+-#if (NGX_HAVE_IP_RECVDSTADDR)
+-
+- if (cmsg->cmsg_level == IPPROTO_IP
+- && cmsg->cmsg_type == IP_RECVDSTADDR
+- && local_sockaddr->sa_family == AF_INET)
+- {
+- struct in_addr *addr;
+- struct sockaddr_in *sin;
+-
+- addr = (struct in_addr *) CMSG_DATA(cmsg);
+- sin = (struct sockaddr_in *) local_sockaddr;
+- sin->sin_addr = *addr;
+-
++ if (ngx_get_srcaddr_cmsg(cmsg, local_sockaddr) == NGX_OK) {
+ break;
+ }
+-
+-#elif (NGX_HAVE_IP_PKTINFO)
+-
+- if (cmsg->cmsg_level == IPPROTO_IP
+- && cmsg->cmsg_type == IP_PKTINFO
+- && local_sockaddr->sa_family == AF_INET)
+- {
+- struct in_pktinfo *pkt;
+- struct sockaddr_in *sin;
+-
+- pkt = (struct in_pktinfo *) CMSG_DATA(cmsg);
+- sin = (struct sockaddr_in *) local_sockaddr;
+- sin->sin_addr = pkt->ipi_addr;
+-
+- break;
+- }
+-
+-#endif
+-
+-#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO)
+-
+- if (cmsg->cmsg_level == IPPROTO_IPV6
+- && cmsg->cmsg_type == IPV6_PKTINFO
+- && local_sockaddr->sa_family == AF_INET6)
+- {
+- struct in6_pktinfo *pkt6;
+- struct sockaddr_in6 *sin6;
+-
+- pkt6 = (struct in6_pktinfo *) CMSG_DATA(cmsg);
+- sin6 = (struct sockaddr_in6 *) local_sockaddr;
+- sin6->sin6_addr = pkt6->ipi6_addr;
+-
+- break;
+- }
+-
+-#endif
+-
+ }
+ }
+
+ #endif
+
+- c = ngx_lookup_udp_connection(ls, sockaddr, socklen, local_sockaddr,
+- local_socklen);
++ key.data = (u_char *) dgram.sockaddr;
++ key.len = dgram.socklen;
++
++#if (NGX_HAVE_UNIX_DOMAIN)
++
++ if (dgram.sockaddr->sa_family == AF_UNIX) {
++ struct sockaddr_un *saun = (struct sockaddr_un *) dgram.sockaddr;
++
++ if (dgram.socklen <= (socklen_t) offsetof(struct sockaddr_un,
++ sun_path)
++ || saun->sun_path[0] == '\0')
++ {
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0,
++ "unbound unix socket");
++ key.len = 0;
++ }
++ }
++
++#endif
++
++#if (NGX_QUIC)
++ if (ls->quic) {
++ if (ngx_quic_get_packet_dcid(ev->log, buffer, n, &key) != NGX_OK) {
++ goto next;
++ }
++ }
++#endif
++
++ c = ngx_lookup_udp_connection(ls, &key, local_sockaddr, local_socklen);
+
+ if (c) {
+
+@@ -252,10 +202,14 @@
+
+ buf.pos = buffer;
+ buf.last = buffer + n;
++ buf.start = buf.pos;
++ buf.end = buffer + sizeof(buffer);
+
+ rev = c->read;
+
+- c->udp->buffer = &buf;
++ dgram.buffer = &buf;
++
++ c->udp->dgram = &dgram;
+
+ rev->ready = 1;
+ rev->active = 0;
+@@ -263,7 +217,7 @@
+ rev->handler(rev);
+
+ if (c->udp) {
+- c->udp->buffer = NULL;
++ c->udp->dgram = NULL;
+ }
+
+ rev->ready = 0;
+@@ -286,7 +240,7 @@
+
+ c->shared = 1;
+ c->type = SOCK_DGRAM;
+- c->socklen = socklen;
++ c->socklen = dgram.socklen;
+
+ #if (NGX_STAT_STUB)
+ (void) ngx_atomic_fetch_add(ngx_stat_active, 1);
+@@ -298,13 +252,21 @@
+ return;
+ }
+
+- c->sockaddr = ngx_palloc(c->pool, socklen);
++ len = dgram.socklen;
++
++#if (NGX_QUIC)
++ if (ls->quic) {
++ len = NGX_SOCKADDRLEN;
++ }
++#endif
++
++ c->sockaddr = ngx_palloc(c->pool, len);
+ if (c->sockaddr == NULL) {
+ ngx_close_accepted_udp_connection(c);
+ return;
+ }
+
+- ngx_memcpy(c->sockaddr, sockaddr, socklen);
++ ngx_memcpy(c->sockaddr, dgram.sockaddr, dgram.socklen);
+
+ log = ngx_palloc(c->pool, sizeof(ngx_log_t));
+ if (log == NULL) {
+@@ -405,7 +367,7 @@
+ }
+ #endif
+
+- if (ngx_insert_udp_connection(c) != NGX_OK) {
++ if (ngx_create_udp_connection(c) != NGX_OK) {
+ ngx_close_accepted_udp_connection(c);
+ return;
+ }
+@@ -448,17 +410,17 @@
+ ssize_t n;
+ ngx_buf_t *b;
+
+- if (c->udp == NULL || c->udp->buffer == NULL) {
++ if (c->udp == NULL || c->udp->dgram == NULL) {
+ return NGX_AGAIN;
+ }
+
+- b = c->udp->buffer;
++ b = c->udp->dgram->buffer;
+
+ n = ngx_min(b->last - b->pos, (ssize_t) size);
+
+ ngx_memcpy(buf, b->pos, n);
+
+- c->udp->buffer = NULL;
++ c->udp->dgram = NULL;
+
+ c->read->ready = 0;
+ c->read->active = 1;
+@@ -494,8 +456,8 @@
+ udpt = (ngx_udp_connection_t *) temp;
+ ct = udpt->connection;
+
+- rc = ngx_cmp_sockaddr(c->sockaddr, c->socklen,
+- ct->sockaddr, ct->socklen, 1);
++ rc = ngx_memn2cmp(udp->key.data, udpt->key.data,
++ udp->key.len, udpt->key.len);
+
+ if (rc == 0 && c->listening->wildcard) {
+ rc = ngx_cmp_sockaddr(c->local_sockaddr, c->local_socklen,
+@@ -521,12 +483,18 @@
+
+
+ static ngx_int_t
+-ngx_insert_udp_connection(ngx_connection_t *c)
++ngx_create_udp_connection(ngx_connection_t *c)
+ {
+- uint32_t hash;
++ ngx_str_t key;
+ ngx_pool_cleanup_t *cln;
+ ngx_udp_connection_t *udp;
+
++#if (NGX_QUIC)
++ if (c->listening->quic) {
++ return NGX_OK;
++ }
++#endif
++
+ if (c->udp) {
+ return NGX_OK;
+ }
+@@ -536,19 +504,6 @@
+ return NGX_ERROR;
+ }
+
+- udp->connection = c;
+-
+- ngx_crc32_init(hash);
+- ngx_crc32_update(&hash, (u_char *) c->sockaddr, c->socklen);
+-
+- if (c->listening->wildcard) {
+- ngx_crc32_update(&hash, (u_char *) c->local_sockaddr, c->local_socklen);
+- }
+-
+- ngx_crc32_final(hash);
+-
+- udp->node.key = hash;
+-
+ cln = ngx_pool_cleanup_add(c->pool, 0);
+ if (cln == NULL) {
+ return NGX_ERROR;
+@@ -557,7 +512,10 @@
+ cln->data = c;
+ cln->handler = ngx_delete_udp_connection;
+
+- ngx_rbtree_insert(&c->listening->rbtree, &udp->node);
++ key.data = (u_char *) c->sockaddr;
++ key.len = c->socklen;
++
++ ngx_insert_udp_connection(c, udp, &key);
+
+ c->udp = udp;
+
+@@ -566,6 +524,30 @@
+
+
+ void
++ngx_insert_udp_connection(ngx_connection_t *c, ngx_udp_connection_t *udp,
++ ngx_str_t *key)
++{
++ uint32_t hash;
++
++ ngx_crc32_init(hash);
++
++ ngx_crc32_update(&hash, key->data, key->len);
++
++ if (c->listening->wildcard) {
++ ngx_crc32_update(&hash, (u_char *) c->local_sockaddr, c->local_socklen);
++ }
++
++ ngx_crc32_final(hash);
++
++ udp->connection = c;
++ udp->key = *key;
++ udp->node.key = hash;
++
++ ngx_rbtree_insert(&c->listening->rbtree, &udp->node);
++}
++
++
++void
+ ngx_delete_udp_connection(void *data)
+ {
+ ngx_connection_t *c = data;
+@@ -581,8 +563,8 @@
+
+
+ static ngx_connection_t *
+-ngx_lookup_udp_connection(ngx_listening_t *ls, struct sockaddr *sockaddr,
+- socklen_t socklen, struct sockaddr *local_sockaddr, socklen_t local_socklen)
++ngx_lookup_udp_connection(ngx_listening_t *ls, ngx_str_t *key,
++ struct sockaddr *local_sockaddr, socklen_t local_socklen)
+ {
+ uint32_t hash;
+ ngx_int_t rc;
+@@ -590,27 +572,15 @@
+ ngx_rbtree_node_t *node, *sentinel;
+ ngx_udp_connection_t *udp;
+
+-#if (NGX_HAVE_UNIX_DOMAIN)
+-
+- if (sockaddr->sa_family == AF_UNIX) {
+- struct sockaddr_un *saun = (struct sockaddr_un *) sockaddr;
+-
+- if (socklen <= (socklen_t) offsetof(struct sockaddr_un, sun_path)
+- || saun->sun_path[0] == '\0')
+- {
+- ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0,
+- "unbound unix socket");
+- return NULL;
+- }
++ if (key->len == 0) {
++ return NULL;
+ }
+
+-#endif
+-
+ node = ls->rbtree.root;
+ sentinel = ls->rbtree.sentinel;
+
+ ngx_crc32_init(hash);
+- ngx_crc32_update(&hash, (u_char *) sockaddr, socklen);
++ ngx_crc32_update(&hash, key->data, key->len);
+
+ if (ls->wildcard) {
+ ngx_crc32_update(&hash, (u_char *) local_sockaddr, local_socklen);
+@@ -636,8 +606,7 @@
+
+ c = udp->connection;
+
+- rc = ngx_cmp_sockaddr(sockaddr, socklen,
+- c->sockaddr, c->socklen, 1);
++ rc = ngx_memn2cmp(key->data, udp->key.data, key->len, udp->key.len);
+
+ if (rc == 0 && ls->wildcard) {
+ rc = ngx_cmp_sockaddr(local_sockaddr, local_socklen,
+@@ -645,6 +614,13 @@
+ }
+
+ if (rc == 0) {
++
++#if (NGX_QUIC)
++ if (ls->quic && c->udp != udp) {
++ c->udp = udp;
++ }
++#endif
++
+ return c;
+ }
+
+diff -r 67408b4a12c0 src/event/ngx_event_udp.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/ngx_event_udp.h Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,76 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#ifndef _NGX_EVENT_UDP_H_INCLUDED_
++#define _NGX_EVENT_UDP_H_INCLUDED_
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++
++
++#if !(NGX_WIN32)
++
++#if ((NGX_HAVE_MSGHDR_MSG_CONTROL) \
++ && (NGX_HAVE_IP_SENDSRCADDR || NGX_HAVE_IP_RECVDSTADDR \
++ || NGX_HAVE_IP_PKTINFO \
++ || (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO)))
++#define NGX_HAVE_ADDRINFO_CMSG 1
++#endif
++
++
++typedef struct {
++ ngx_buf_t *buffer;
++ struct sockaddr *sockaddr;
++ socklen_t socklen;
++} ngx_udp_dgram_t;
++
++
++struct ngx_udp_connection_s {
++ ngx_rbtree_node_t node;
++ ngx_connection_t *connection;
++ ngx_str_t key;
++ ngx_udp_dgram_t *dgram;
++};
++
++
++#if (NGX_HAVE_ADDRINFO_CMSG)
++
++typedef union {
++#if (NGX_HAVE_IP_SENDSRCADDR || NGX_HAVE_IP_RECVDSTADDR)
++ struct in_addr addr;
++#endif
++
++#if (NGX_HAVE_IP_PKTINFO)
++ struct in_pktinfo pkt;
++#endif
++
++#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO)
++ struct in6_pktinfo pkt6;
++#endif
++} ngx_addrinfo_t;
++
++size_t ngx_set_srcaddr_cmsg(struct cmsghdr *cmsg,
++ struct sockaddr *local_sockaddr);
++ngx_int_t ngx_get_srcaddr_cmsg(struct cmsghdr *cmsg,
++ struct sockaddr *local_sockaddr);
++
++#endif
++
++
++void ngx_event_recvmsg(ngx_event_t *ev);
++ssize_t ngx_sendmsg(ngx_connection_t *c, struct msghdr *msg, int flags);
++void ngx_udp_rbtree_insert_value(ngx_rbtree_node_t *temp,
++ ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
++void ngx_insert_udp_connection(ngx_connection_t *c, ngx_udp_connection_t *udp,
++ ngx_str_t *key);
++
++#endif
++
++void ngx_delete_udp_connection(void *data);
++
++
++#endif /* _NGX_EVENT_UDP_H_INCLUDED_ */
+diff -r 67408b4a12c0 src/event/quic/bpf/bpfgen.sh
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/bpf/bpfgen.sh Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,113 @@
++#!/bin/bash
++
++export LANG=C
++
++set -e
++
++if [ $# -lt 1 ]; then
++ echo "Usage: PROGNAME=foo LICENSE=bar $0 <bpf object file>"
++ exit 1
++fi
++
++
++self=$0
++filename=$1
++funcname=$PROGNAME
++
++generate_head()
++{
++ cat << END
++/* AUTO-GENERATED, DO NOT EDIT. */
++
++#include <stddef.h>
++#include <stdint.h>
++
++#include "ngx_bpf.h"
++
++
++END
++}
++
++generate_tail()
++{
++ cat << END
++
++ngx_bpf_program_t $PROGNAME = {
++ .relocs = bpf_reloc_prog_$funcname,
++ .nrelocs = sizeof(bpf_reloc_prog_$funcname)
++ / sizeof(bpf_reloc_prog_$funcname[0]),
++ .ins = bpf_insn_prog_$funcname,
++ .nins = sizeof(bpf_insn_prog_$funcname)
++ / sizeof(bpf_insn_prog_$funcname[0]),
++ .license = "$LICENSE",
++ .type = BPF_PROG_TYPE_SK_REUSEPORT,
++};
++
++END
++}
++
++process_relocations()
++{
++ echo "static ngx_bpf_reloc_t bpf_reloc_prog_$funcname[] = {"
++
++ objdump -r $filename | awk '{
++
++ if (enabled && $NF > 0) {
++ off = strtonum(sprintf("0x%s", $1));
++ name = $3;
++
++ printf(" { \"%s\", %d },\n", name, off/8);
++ }
++
++ if ($1 == "OFFSET") {
++ enabled=1;
++ }
++}'
++ echo "};"
++ echo
++}
++
++process_section()
++{
++ echo "static struct bpf_insn bpf_insn_prog_$funcname[] = {"
++ echo " /* opcode dst src offset imm */"
++
++ section_info=$(objdump -h $filename --section=$funcname | grep "1 $funcname")
++
++ # dd doesn't know hex
++ length=$(printf "%d" 0x$(echo $section_info | cut -d ' ' -f3))
++ offset=$(printf "%d" 0x$(echo $section_info | cut -d ' ' -f6))
++
++ for ins in $(dd if="$filename" bs=1 count=$length skip=$offset status=none | xxd -p -c 8)
++ do
++ opcode=0x${ins:0:2}
++ srcdst=0x${ins:2:2}
++
++ # bytes are dumped in LE order
++ offset=0x${ins:6:2}${ins:4:2} # short
++ immedi=0x${ins:14:2}${ins:12:2}${ins:10:2}${ins:8:2} # int
++
++ dst="$(($srcdst & 0xF))"
++ src="$(($srcdst & 0xF0))"
++ src="$(($src >> 4))"
++
++ opcode=$(printf "0x%x" $opcode)
++ dst=$(printf "BPF_REG_%d" $dst)
++ src=$(printf "BPF_REG_%d" $src)
++ offset=$(printf "%d" $offset)
++ immedi=$(printf "0x%x" $immedi)
++
++ printf " { %4s, %11s, %11s, (int16_t) %6s, %10s },\n" $opcode $dst $src $offset $immedi
++ done
++
++cat << END
++};
++
++END
++}
++
++generate_head
++process_relocations
++process_section
++generate_tail
++
+diff -r 67408b4a12c0 src/event/quic/bpf/makefile
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/bpf/makefile Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,30 @@
++CFLAGS=-O2 -Wall
++
++LICENSE=BSD
++
++PROGNAME=ngx_quic_reuseport_helper
++RESULT=ngx_event_quic_bpf_code
++DEST=../$(RESULT).c
++
++all: $(RESULT)
++
++$(RESULT): $(PROGNAME).o
++ LICENSE=$(LICENSE) PROGNAME=$(PROGNAME) bash ./bpfgen.sh $< > $@
++
++DEFS=-DPROGNAME=\"$(PROGNAME)\" \
++ -DLICENSE_$(LICENSE) \
++ -DLICENSE=\"$(LICENSE)\" \
++
++$(PROGNAME).o: $(PROGNAME).c
++ clang $(CFLAGS) $(DEFS) -target bpf -c $< -o $@
++
++install: $(RESULT)
++ cp $(RESULT) $(DEST)
++
++clean:
++ @rm -f $(RESULT) *.o
++
++debug: $(PROGNAME).o
++ llvm-objdump -S -no-show-raw-insn $<
++
++.DELETE_ON_ERROR:
+diff -r 67408b4a12c0 src/event/quic/bpf/ngx_quic_reuseport_helper.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/bpf/ngx_quic_reuseport_helper.c Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,140 @@
++#include <errno.h>
++#include <linux/string.h>
++#include <linux/udp.h>
++#include <linux/bpf.h>
++/*
++ * the bpf_helpers.h is not included into linux-headers, only available
++ * with kernel sources in "tools/lib/bpf/bpf_helpers.h" or in libbpf.
++ */
++#include <bpf/bpf_helpers.h>
++
++
++#if !defined(SEC)
++#define SEC(NAME) __attribute__((section(NAME), used))
++#endif
++
++
++#if defined(LICENSE_GPL)
++
++/*
++ * To see debug:
++ *
++ * echo 1 > /sys/kernel/debug/tracing/events/bpf_trace/enable
++ * cat /sys/kernel/debug/tracing/trace_pipe
++ * echo 0 > /sys/kernel/debug/tracing/events/bpf_trace/enable
++ */
++
++#define debugmsg(fmt, ...) \
++do { \
++ char __buf[] = fmt; \
++ bpf_trace_printk(__buf, sizeof(__buf), ##__VA_ARGS__); \
++} while (0)
++
++#else
++
++#define debugmsg(fmt, ...)
++
++#endif
++
++char _license[] SEC("license") = LICENSE;
++
++/*****************************************************************************/
++
++#define NGX_QUIC_PKT_LONG 0x80 /* header form */
++#define NGX_QUIC_SERVER_CID_LEN 20
++
++
++#define advance_data(nbytes) \
++ offset += nbytes; \
++ if (start + offset > end) { \
++ debugmsg("cannot read %ld bytes at offset %ld", nbytes, offset); \
++ goto failed; \
++ } \
++ data = start + offset - 1;
++
++
++#define ngx_quic_parse_uint64(p) \
++ (((__u64)(p)[0] << 56) | \
++ ((__u64)(p)[1] << 48) | \
++ ((__u64)(p)[2] << 40) | \
++ ((__u64)(p)[3] << 32) | \
++ ((__u64)(p)[4] << 24) | \
++ ((__u64)(p)[5] << 16) | \
++ ((__u64)(p)[6] << 8) | \
++ ((__u64)(p)[7]))
++
++/*
++ * actual map object is created by the "bpf" system call,
++ * all pointers to this variable are replaced by the bpf loader
++ */
++struct bpf_map_def SEC("maps") ngx_quic_sockmap;
++
++
++SEC(PROGNAME)
++int ngx_quic_select_socket_by_dcid(struct sk_reuseport_md *ctx)
++{
++ int rc;
++ __u64 key;
++ size_t len, offset;
++ unsigned char *start, *end, *data, *dcid;
++
++ start = ctx->data;
++ end = (unsigned char *) ctx->data_end;
++ offset = 0;
++
++ advance_data(sizeof(struct udphdr)); /* data at UDP header */
++ advance_data(1); /* data at QUIC flags */
++
++ if (data[0] & NGX_QUIC_PKT_LONG) {
++
++ advance_data(4); /* data at QUIC version */
++ advance_data(1); /* data at DCID len */
++
++ len = data[0]; /* read DCID length */
++
++ if (len < 8) {
++ /* it's useless to search for key in such short DCID */
++ return SK_PASS;
++ }
++
++ } else {
++ len = NGX_QUIC_SERVER_CID_LEN;
++ }
++
++ dcid = &data[1];
++ advance_data(len); /* we expect the packet to have full DCID */
++
++ /* make verifier happy */
++ if (dcid + sizeof(__u64) > end) {
++ goto failed;
++ }
++
++ key = ngx_quic_parse_uint64(dcid);
++
++ rc = bpf_sk_select_reuseport(ctx, &ngx_quic_sockmap, &key, 0);
++
++ switch (rc) {
++ case 0:
++ debugmsg("nginx quic socket selected by key 0x%llx", key);
++ return SK_PASS;
++
++ /* kernel returns positive error numbers, errno.h defines positive */
++ case -ENOENT:
++ debugmsg("nginx quic default route for key 0x%llx", key);
++ /* let the default reuseport logic decide which socket to choose */
++ return SK_PASS;
++
++ default:
++ debugmsg("nginx quic bpf_sk_select_reuseport err: %d key 0x%llx",
++ rc, key);
++ goto failed;
++ }
++
++failed:
++ /*
++ * SK_DROP will generate ICMP, but we may want to process "invalid" packet
++ * in userspace quic to investigate further and finally react properly
++ * (maybe ignore, maybe send something in response or close connection)
++ */
++ return SK_PASS;
++}
+diff -r 67408b4a12c0 src/event/quic/ngx_event_quic.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic.c Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,1489 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++#include <ngx_event.h>
++#include <ngx_event_quic_connection.h>
++
++
++static ngx_quic_connection_t *ngx_quic_new_connection(ngx_connection_t *c,
++ ngx_quic_conf_t *conf, ngx_quic_header_t *pkt);
++static ngx_int_t ngx_quic_handle_stateless_reset(ngx_connection_t *c,
++ ngx_quic_header_t *pkt);
++static void ngx_quic_input_handler(ngx_event_t *rev);
++
++static ngx_int_t ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc);
++static void ngx_quic_close_timer_handler(ngx_event_t *ev);
++
++static ngx_int_t ngx_quic_handle_datagram(ngx_connection_t *c, ngx_buf_t *b,
++ ngx_quic_conf_t *conf);
++static ngx_int_t ngx_quic_handle_packet(ngx_connection_t *c,
++ ngx_quic_conf_t *conf, ngx_quic_header_t *pkt);
++static ngx_int_t ngx_quic_handle_payload(ngx_connection_t *c,
++ ngx_quic_header_t *pkt);
++static ngx_int_t ngx_quic_check_csid(ngx_quic_connection_t *qc,
++ ngx_quic_header_t *pkt);
++static ngx_int_t ngx_quic_handle_frames(ngx_connection_t *c,
++ ngx_quic_header_t *pkt);
++
++static void ngx_quic_push_handler(ngx_event_t *ev);
++
++
++static ngx_core_module_t ngx_quic_module_ctx = {
++ ngx_string("quic"),
++ NULL,
++ NULL
++};
++
++
++ngx_module_t ngx_quic_module = {
++ NGX_MODULE_V1,
++ &ngx_quic_module_ctx, /* module context */
++ NULL, /* module directives */
++ NGX_CORE_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
++};
++
++
++#if (NGX_DEBUG)
++
++void
++ngx_quic_connstate_dbg(ngx_connection_t *c)
++{
++ u_char *p, *last;
++ ngx_quic_connection_t *qc;
++ u_char buf[NGX_MAX_ERROR_STR];
++
++ p = buf;
++ last = p + sizeof(buf);
++
++ qc = ngx_quic_get_connection(c);
++
++ p = ngx_slprintf(p, last, "state:");
++
++ if (qc) {
++
++ if (qc->error) {
++ p = ngx_slprintf(p, last, "%s", qc->error_app ? " app" : "");
++ p = ngx_slprintf(p, last, " error:%ui", qc->error);
++
++ if (qc->error_reason) {
++ p = ngx_slprintf(p, last, " \"%s\"", qc->error_reason);
++ }
++ }
++
++ p = ngx_slprintf(p, last, "%s", qc->shutdown ? " shutdown" : "");
++ p = ngx_slprintf(p, last, "%s", qc->closing ? " closing" : "");
++ p = ngx_slprintf(p, last, "%s", qc->draining ? " draining" : "");
++ p = ngx_slprintf(p, last, "%s", qc->key_phase ? " kp" : "");
++
++ } else {
++ p = ngx_slprintf(p, last, " early");
++ }
++
++ if (c->read->timer_set) {
++ p = ngx_slprintf(p, last,
++ qc && qc->send_timer_set ? " send:%M" : " read:%M",
++ c->read->timer.key - ngx_current_msec);
++ }
++
++ if (qc) {
++
++ if (qc->push.timer_set) {
++ p = ngx_slprintf(p, last, " push:%M",
++ qc->push.timer.key - ngx_current_msec);
++ }
++
++ if (qc->pto.timer_set) {
++ p = ngx_slprintf(p, last, " pto:%M",
++ qc->pto.timer.key - ngx_current_msec);
++ }
++
++ if (qc->close.timer_set) {
++ p = ngx_slprintf(p, last, " close:%M",
++ qc->close.timer.key - ngx_current_msec);
++ }
++ }
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic %*s", p - buf, buf);
++}
++
++#endif
++
++
++ngx_int_t
++ngx_quic_apply_transport_params(ngx_connection_t *c, ngx_quic_tp_t *ctp)
++{
++ ngx_str_t scid;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ scid.data = qc->socket->cid->id;
++ scid.len = qc->socket->cid->len;
++
++ if (scid.len != ctp->initial_scid.len
++ || ngx_memcmp(scid.data, ctp->initial_scid.data, scid.len) != 0)
++ {
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic client initial_source_connection_id mismatch");
++ return NGX_ERROR;
++ }
++
++ if (ctp->max_udp_payload_size < NGX_QUIC_MIN_INITIAL_SIZE
++ || ctp->max_udp_payload_size > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE)
++ {
++ qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR;
++ qc->error_reason = "invalid maximum packet size";
++
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic maximum packet size is invalid");
++ return NGX_ERROR;
++
++ } else if (ctp->max_udp_payload_size > ngx_quic_max_udp_payload(c)) {
++ ctp->max_udp_payload_size = ngx_quic_max_udp_payload(c);
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic client maximum packet size truncated");
++ }
++
++ if (ctp->active_connection_id_limit < 2) {
++ qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR;
++ qc->error_reason = "invalid active_connection_id_limit";
++
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic active_connection_id_limit is invalid");
++ return NGX_ERROR;
++ }
++
++ if (ctp->ack_delay_exponent > 20) {
++ qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR;
++ qc->error_reason = "invalid ack_delay_exponent";
++
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic ack_delay_exponent is invalid");
++ return NGX_ERROR;
++ }
++
++ if (ctp->max_ack_delay >= 16384) {
++ qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR;
++ qc->error_reason = "invalid max_ack_delay";
++
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic max_ack_delay is invalid");
++ return NGX_ERROR;
++ }
++
++ if (ctp->max_idle_timeout > 0
++ && ctp->max_idle_timeout < qc->tp.max_idle_timeout)
++ {
++ qc->tp.max_idle_timeout = ctp->max_idle_timeout;
++ }
++
++ qc->streams.server_max_streams_bidi = ctp->initial_max_streams_bidi;
++ qc->streams.server_max_streams_uni = ctp->initial_max_streams_uni;
++
++ ngx_memcpy(&qc->ctp, ctp, sizeof(ngx_quic_tp_t));
++
++ return NGX_OK;
++}
++
++
++void
++ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf)
++{
++ ngx_int_t rc;
++ ngx_quic_connection_t *qc;
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic run");
++
++ rc = ngx_quic_handle_datagram(c, c->buffer, conf);
++ if (rc != NGX_OK) {
++ ngx_quic_close_connection(c, rc == NGX_DECLINED ? NGX_DONE : NGX_ERROR);
++ return;
++ }
++
++ qc = ngx_quic_get_connection(c);
++
++ if (qc == NULL) {
++ ngx_quic_close_connection(c, NGX_DONE);
++ return;
++ }
++
++ ngx_add_timer(c->read, qc->tp.max_idle_timeout);
++ ngx_quic_connstate_dbg(c);
++
++ c->read->handler = ngx_quic_input_handler;
++
++ return;
++}
++
++
++static ngx_quic_connection_t *
++ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf,
++ ngx_quic_header_t *pkt)
++{
++ ngx_uint_t i;
++ ngx_quic_tp_t *ctp;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t));
++ if (qc == NULL) {
++ return NULL;
++ }
++
++ qc->keys = ngx_quic_keys_new(c->pool);
++ if (qc->keys == NULL) {
++ return NULL;
++ }
++
++ qc->version = pkt->version;
++
++ ngx_rbtree_init(&qc->streams.tree, &qc->streams.sentinel,
++ ngx_quic_rbtree_insert_stream);
++
++ for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) {
++ ngx_queue_init(&qc->send_ctx[i].frames);
++ ngx_queue_init(&qc->send_ctx[i].sending);
++ ngx_queue_init(&qc->send_ctx[i].sent);
++ qc->send_ctx[i].largest_pn = NGX_QUIC_UNSET_PN;
++ qc->send_ctx[i].largest_ack = NGX_QUIC_UNSET_PN;
++ qc->send_ctx[i].largest_range = NGX_QUIC_UNSET_PN;
++ qc->send_ctx[i].pending_ack = NGX_QUIC_UNSET_PN;
++ }
++
++ qc->send_ctx[0].level = ssl_encryption_initial;
++ qc->send_ctx[1].level = ssl_encryption_handshake;
++ qc->send_ctx[2].level = ssl_encryption_application;
++
++ ngx_queue_init(&qc->free_frames);
++
++ qc->avg_rtt = NGX_QUIC_INITIAL_RTT;
++ qc->rttvar = NGX_QUIC_INITIAL_RTT / 2;
++ qc->min_rtt = NGX_TIMER_INFINITE;
++ qc->first_rtt = NGX_TIMER_INFINITE;
++
++ /*
++ * qc->latest_rtt = 0
++ */
++
++ qc->pto.log = c->log;
++ qc->pto.data = c;
++ qc->pto.handler = ngx_quic_pto_handler;
++ qc->pto.cancelable = 1;
++
++ qc->push.log = c->log;
++ qc->push.data = c;
++ qc->push.handler = ngx_quic_push_handler;
++ qc->push.cancelable = 1;
++
++ qc->path_validation.log = c->log;
++ qc->path_validation.data = c;
++ qc->path_validation.handler = ngx_quic_path_validation_handler;
++ qc->path_validation.cancelable = 1;
++
++ qc->conf = conf;
++
++ if (ngx_quic_init_transport_params(&qc->tp, conf) != NGX_OK) {
++ return NULL;
++ }
++
++ ctp = &qc->ctp;
++
++ /* defaults to be used before actual client parameters are received */
++ ctp->max_udp_payload_size = ngx_quic_max_udp_payload(c);
++ ctp->ack_delay_exponent = NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT;
++ ctp->max_ack_delay = NGX_QUIC_DEFAULT_MAX_ACK_DELAY;
++ ctp->active_connection_id_limit = 2;
++
++ ngx_queue_init(&qc->streams.uninitialized);
++
++ qc->streams.recv_max_data = qc->tp.initial_max_data;
++ qc->streams.recv_window = qc->streams.recv_max_data;
++
++ qc->streams.client_max_streams_uni = qc->tp.initial_max_streams_uni;
++ qc->streams.client_max_streams_bidi = qc->tp.initial_max_streams_bidi;
++
++ qc->congestion.window = ngx_min(10 * qc->tp.max_udp_payload_size,
++ ngx_max(2 * qc->tp.max_udp_payload_size,
++ 14720));
++ qc->congestion.ssthresh = (size_t) -1;
++ qc->congestion.recovery_start = ngx_current_msec;
++
++ if (pkt->validated && pkt->retried) {
++ qc->tp.retry_scid.len = pkt->dcid.len;
++ qc->tp.retry_scid.data = ngx_pstrdup(c->pool, &pkt->dcid);
++ if (qc->tp.retry_scid.data == NULL) {
++ return NULL;
++ }
++ }
++
++ if (ngx_quic_keys_set_initial_secret(c->pool, qc->keys, &pkt->dcid,
++ qc->version)
++ != NGX_OK)
++ {
++ return NULL;
++ }
++
++ qc->validated = pkt->validated;
++
++ if (ngx_quic_open_sockets(c, qc, pkt) != NGX_OK) {
++ return NULL;
++ }
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic connection created");
++
++ return qc;
++}
++
++
++static ngx_int_t
++ngx_quic_handle_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt)
++{
++ u_char *tail, ch;
++ ngx_uint_t i;
++ ngx_queue_t *q;
++ ngx_quic_client_id_t *cid;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ /* A stateless reset uses an entire UDP datagram */
++ if (!pkt->first) {
++ return NGX_DECLINED;
++ }
++
++ tail = pkt->raw->last - NGX_QUIC_SR_TOKEN_LEN;
++
++ for (q = ngx_queue_head(&qc->client_ids);
++ q != ngx_queue_sentinel(&qc->client_ids);
++ q = ngx_queue_next(q))
++ {
++ cid = ngx_queue_data(q, ngx_quic_client_id_t, queue);
++
++ if (cid->seqnum == 0 || cid->refcnt == 0) {
++ /*
++ * No stateless reset token in initial connection id.
++ * Don't accept a token from an unused connection id.
++ */
++ continue;
++ }
++
++ /* constant time comparison */
++
++ for (ch = 0, i = 0; i < NGX_QUIC_SR_TOKEN_LEN; i++) {
++ ch |= tail[i] ^ cid->sr_token[i];
++ }
++
++ if (ch == 0) {
++ return NGX_OK;
++ }
++ }
++
++ return NGX_DECLINED;
++}
++
++
++static void
++ngx_quic_input_handler(ngx_event_t *rev)
++{
++ ngx_int_t rc;
++ ngx_buf_t *b;
++ ngx_connection_t *c;
++ ngx_quic_connection_t *qc;
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0, "quic input handler");
++
++ c = rev->data;
++ qc = ngx_quic_get_connection(c);
++
++ c->log->action = "handling quic input";
++
++ if (rev->timedout) {
++ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
++ "quic client timed out");
++ ngx_quic_close_connection(c, NGX_DONE);
++ return;
++ }
++
++ if (c->close) {
++ qc->error_reason = "graceful shutdown";
++ ngx_quic_close_connection(c, NGX_OK);
++ return;
++ }
++
++ if (!rev->ready) {
++ if (qc->closing) {
++ ngx_quic_close_connection(c, NGX_OK);
++
++ } else if (qc->shutdown) {
++ ngx_quic_shutdown_quic(c);
++ }
++
++ return;
++ }
++
++ b = c->udp->dgram->buffer;
++
++ rc = ngx_quic_handle_datagram(c, b, NULL);
++
++ if (rc == NGX_ERROR) {
++ ngx_quic_close_connection(c, NGX_ERROR);
++ return;
++ }
++
++ if (rc == NGX_DECLINED) {
++ return;
++ }
++
++ /* rc == NGX_OK */
++
++ qc->send_timer_set = 0;
++ ngx_add_timer(rev, qc->tp.max_idle_timeout);
++
++ ngx_quic_connstate_dbg(c);
++}
++
++
++void
++ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc)
++{
++ ngx_pool_t *pool;
++ ngx_quic_connection_t *qc;
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic ngx_quic_close_connection rc:%i", rc);
++
++ qc = ngx_quic_get_connection(c);
++
++ if (qc == NULL) {
++ if (rc == NGX_ERROR) {
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic close connection early error");
++ }
++
++ } else if (ngx_quic_close_quic(c, rc) == NGX_AGAIN) {
++ return;
++ }
++
++ if (c->ssl) {
++ (void) ngx_ssl_shutdown(c);
++ }
++
++ if (c->read->timer_set) {
++ ngx_del_timer(c->read);
++ }
++
++#if (NGX_STAT_STUB)
++ (void) ngx_atomic_fetch_add(ngx_stat_active, -1);
++#endif
++
++ c->destroyed = 1;
++
++ pool = c->pool;
++
++ ngx_close_connection(c);
++
++ ngx_destroy_pool(pool);
++}
++
++
++static ngx_int_t
++ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc)
++{
++ ngx_uint_t i;
++ ngx_quic_send_ctx_t *ctx;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ if (!qc->closing) {
++
++ /* drop packets from retransmit queues, no ack is expected */
++ for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) {
++ ngx_quic_free_frames(c, &qc->send_ctx[i].sent);
++ }
++
++ if (rc == NGX_DONE) {
++
++ /*
++ * RFC 9000, 10.1. Idle Timeout
++ *
++ * If a max_idle_timeout is specified by either endpoint in its
++ * transport parameters (Section 18.2), the connection is silently
++ * closed and its state is discarded when it remains idle
++ */
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic closing %s connection",
++ qc->draining ? "drained" : "idle");
++
++ } else {
++
++ /*
++ * RFC 9000, 10.2. Immediate Close
++ *
++ * An endpoint sends a CONNECTION_CLOSE frame (Section 19.19)
++ * to terminate the connection immediately.
++ */
++
++ qc->error_level = c->ssl ? SSL_quic_read_level(c->ssl->connection)
++ : ssl_encryption_initial;
++
++ if (rc == NGX_OK) {
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic immediate close drain:%d",
++ qc->draining);
++
++ qc->close.log = c->log;
++ qc->close.data = c;
++ qc->close.handler = ngx_quic_close_timer_handler;
++ qc->close.cancelable = 1;
++
++ ctx = ngx_quic_get_send_ctx(qc, qc->error_level);
++
++ ngx_add_timer(&qc->close, 3 * ngx_quic_pto(c, ctx));
++
++ qc->error = NGX_QUIC_ERR_NO_ERROR;
++
++ } else {
++ if (qc->error == 0 && !qc->error_app) {
++ qc->error = NGX_QUIC_ERR_INTERNAL_ERROR;
++ }
++
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic immediate close due to %s error: %ui %s",
++ qc->error_app ? "app " : "", qc->error,
++ qc->error_reason ? qc->error_reason : "");
++ }
++
++ (void) ngx_quic_send_cc(c);
++
++ if (qc->error_level == ssl_encryption_handshake) {
++ /* for clients that might not have handshake keys */
++ qc->error_level = ssl_encryption_initial;
++ (void) ngx_quic_send_cc(c);
++ }
++ }
++
++ qc->closing = 1;
++ }
++
++ if (rc == NGX_ERROR && qc->close.timer_set) {
++ /* do not wait for timer in case of fatal error */
++ ngx_del_timer(&qc->close);
++ }
++
++ if (ngx_quic_close_streams(c, qc) == NGX_AGAIN) {
++ return NGX_AGAIN;
++ }
++
++ if (qc->push.timer_set) {
++ ngx_del_timer(&qc->push);
++ }
++
++ if (qc->pto.timer_set) {
++ ngx_del_timer(&qc->pto);
++ }
++
++ if (qc->path_validation.timer_set) {
++ ngx_del_timer(&qc->path_validation);
++ }
++
++ if (qc->push.posted) {
++ ngx_delete_posted_event(&qc->push);
++ }
++
++ if (qc->close.timer_set) {
++ return NGX_AGAIN;
++ }
++
++ ngx_quic_close_sockets(c);
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic part of connection is terminated");
++
++ /* may be tested from SSL callback during SSL shutdown */
++ c->udp = NULL;
++
++ return NGX_OK;
++}
++
++
++void
++ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err,
++ const char *reason)
++{
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++ qc->error = err;
++ qc->error_reason = reason;
++ qc->error_app = 1;
++ qc->error_ftype = 0;
++
++ ngx_quic_close_connection(c, NGX_ERROR);
++}
++
++
++void
++ngx_quic_shutdown_connection(ngx_connection_t *c, ngx_uint_t err,
++ const char *reason)
++{
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++ qc->shutdown = 1;
++ qc->shutdown_code = err;
++ qc->shutdown_reason = reason;
++
++ ngx_quic_shutdown_quic(c);
++}
++
++
++static void
++ngx_quic_close_timer_handler(ngx_event_t *ev)
++{
++ ngx_connection_t *c;
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic close timer");
++
++ c = ev->data;
++ ngx_quic_close_connection(c, NGX_DONE);
++}
++
++
++static ngx_int_t
++ngx_quic_handle_datagram(ngx_connection_t *c, ngx_buf_t *b,
++ ngx_quic_conf_t *conf)
++{
++ size_t size;
++ u_char *p, *start;
++ ngx_int_t rc;
++ ngx_uint_t good;
++ ngx_quic_header_t pkt;
++ ngx_quic_connection_t *qc;
++
++ good = 0;
++
++ size = b->last - b->pos;
++
++ p = start = b->pos;
++
++ while (p < b->last) {
++
++ ngx_memzero(&pkt, sizeof(ngx_quic_header_t));
++ pkt.raw = b;
++ pkt.data = p;
++ pkt.len = b->last - p;
++ pkt.log = c->log;
++ pkt.first = (p == start) ? 1 : 0;
++ pkt.flags = p[0];
++ pkt.raw->pos++;
++
++ rc = ngx_quic_handle_packet(c, conf, &pkt);
++
++#if (NGX_DEBUG)
++ if (pkt.parsed) {
++ ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic packet %s done decr:%d pn:%L perr:%ui rc:%i",
++ ngx_quic_level_name(pkt.level), pkt.decrypted,
++ pkt.pn, pkt.error, rc);
++ } else {
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic packet done parse failed rc:%i", rc);
++ }
++#endif
++
++ if (rc == NGX_ERROR) {
++ return NGX_ERROR;
++ }
++
++ if (rc == NGX_DONE) {
++ /* stop further processing */
++ return NGX_DECLINED;
++ }
++
++ if (rc == NGX_OK) {
++ good = 1;
++ }
++
++ /* NGX_OK || NGX_DECLINED */
++
++ /*
++ * we get NGX_DECLINED when there are no keys [yet] available
++ * to decrypt packet.
++ * Instead of queueing it, we ignore it and rely on the sender's
++ * retransmission:
++ *
++ * RFC 9000, 12.2. Coalescing Packets
++ *
++ * For example, if decryption fails (because the keys are
++ * not available or for any other reason), the receiver MAY either
++ * discard or buffer the packet for later processing and MUST
++ * attempt to process the remaining packets.
++ *
++ * We also skip packets that don't match connection state
++ * or cannot be parsed properly.
++ */
++
++ /* b->pos is at header end, adjust by actual packet length */
++ b->pos = pkt.data + pkt.len;
++
++ p = b->pos;
++ }
++
++ if (!good) {
++ return NGX_DECLINED;
++ }
++
++ qc = ngx_quic_get_connection(c);
++
++ if (qc) {
++ qc->received += size;
++
++ if ((uint64_t) (c->sent + qc->received) / 8 >
++ (qc->streams.sent + qc->streams.recv_last) + 1048576)
++ {
++ ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic flood detected");
++
++ qc->error = NGX_QUIC_ERR_NO_ERROR;
++ qc->error_reason = "QUIC flood detected";
++ return NGX_ERROR;
++ }
++ }
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_handle_packet(ngx_connection_t *c, ngx_quic_conf_t *conf,
++ ngx_quic_header_t *pkt)
++{
++ ngx_int_t rc;
++ ngx_quic_connection_t *qc;
++
++ c->log->action = "parsing quic packet";
++
++ rc = ngx_quic_parse_packet(pkt);
++
++ if (rc == NGX_DECLINED || rc == NGX_ERROR) {
++ return rc;
++ }
++
++ pkt->parsed = 1;
++
++ c->log->action = "processing quic packet";
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic packet rx dcid len:%uz %xV",
++ pkt->dcid.len, &pkt->dcid);
++
++#if (NGX_DEBUG)
++ if (pkt->level != ssl_encryption_application) {
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic packet rx scid len:%uz %xV",
++ pkt->scid.len, &pkt->scid);
++ }
++
++ if (pkt->level == ssl_encryption_initial) {
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic address validation token len:%uz %xV",
++ pkt->token.len, &pkt->token);
++ }
++#endif
++
++ qc = ngx_quic_get_connection(c);
++
++ if (qc) {
++
++ if (rc == NGX_ABORT) {
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic unsupported version: 0x%xD", pkt->version);
++ return NGX_DECLINED;
++ }
++
++ if (pkt->level != ssl_encryption_application) {
++
++ if (pkt->version != qc->version) {
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic version mismatch: 0x%xD", pkt->version);
++ return NGX_DECLINED;
++ }
++
++ if (pkt->first) {
++ if (ngx_quic_find_path(c, c->udp->dgram->sockaddr,
++ c->udp->dgram->socklen)
++ == NULL)
++ {
++ /* packet comes from unknown path, possibly migration */
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic too early migration attempt");
++ return NGX_DECLINED;
++ }
++ }
++
++ if (ngx_quic_check_csid(qc, pkt) != NGX_OK) {
++ return NGX_DECLINED;
++ }
++
++ }
++
++ rc = ngx_quic_handle_payload(c, pkt);
++
++ if (rc == NGX_DECLINED && pkt->level == ssl_encryption_application) {
++ if (ngx_quic_handle_stateless_reset(c, pkt) == NGX_OK) {
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic stateless reset packet detected");
++
++ qc->draining = 1;
++ ngx_quic_close_connection(c, NGX_OK);
++
++ return NGX_OK;
++ }
++ }
++
++ return rc;
++ }
++
++ /* packet does not belong to a connection */
++
++ if (rc == NGX_ABORT) {
++ return ngx_quic_negotiate_version(c, pkt);
++ }
++
++ if (pkt->level == ssl_encryption_application) {
++ return ngx_quic_send_stateless_reset(c, conf, pkt);
++ }
++
++ if (pkt->level != ssl_encryption_initial) {
++ return NGX_ERROR;
++ }
++
++ c->log->action = "processing initial packet";
++
++ if (pkt->dcid.len < NGX_QUIC_CID_LEN_MIN) {
++ /* RFC 9000, 7.2. Negotiating Connection IDs */
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic too short dcid in initial"
++ " packet: len:%i", pkt->dcid.len);
++ return NGX_ERROR;
++ }
++
++ /* process retry and initialize connection IDs */
++
++ if (pkt->token.len) {
++
++ rc = ngx_quic_validate_token(c, conf->av_token_key, pkt);
++
++ if (rc == NGX_ERROR) {
++ /* internal error */
++ return NGX_ERROR;
++
++ } else if (rc == NGX_ABORT) {
++ /* token cannot be decrypted */
++ return ngx_quic_send_early_cc(c, pkt,
++ NGX_QUIC_ERR_INVALID_TOKEN,
++ "cannot decrypt token");
++ } else if (rc == NGX_DECLINED) {
++ /* token is invalid */
++
++ if (pkt->retried) {
++ /* invalid address validation token */
++ return ngx_quic_send_early_cc(c, pkt,
++ NGX_QUIC_ERR_INVALID_TOKEN,
++ "invalid address validation token");
++ } else if (conf->retry) {
++ /* invalid NEW_TOKEN */
++ return ngx_quic_send_retry(c, conf, pkt);
++ }
++ }
++
++ /* NGX_OK */
++
++ } else if (conf->retry) {
++ return ngx_quic_send_retry(c, conf, pkt);
++
++ } else {
++ pkt->odcid = pkt->dcid;
++ }
++
++ if (ngx_terminate || ngx_exiting) {
++ if (conf->retry) {
++ return ngx_quic_send_retry(c, conf, pkt);
++ }
++
++ return NGX_ERROR;
++ }
++
++ c->log->action = "creating quic connection";
++
++ qc = ngx_quic_new_connection(c, conf, pkt);
++ if (qc == NULL) {
++ return NGX_ERROR;
++ }
++
++ return ngx_quic_handle_payload(c, pkt);
++}
++
++
++static ngx_int_t
++ngx_quic_handle_payload(ngx_connection_t *c, ngx_quic_header_t *pkt)
++{
++ ngx_int_t rc;
++ ngx_quic_send_ctx_t *ctx;
++ ngx_quic_connection_t *qc;
++ static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
++
++ qc = ngx_quic_get_connection(c);
++
++ qc->error = 0;
++ qc->error_reason = 0;
++
++ c->log->action = "decrypting packet";
++
++ if (!ngx_quic_keys_available(qc->keys, pkt->level)) {
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic no %s keys, ignoring packet",
++ ngx_quic_level_name(pkt->level));
++ return NGX_DECLINED;
++ }
++
++#if !defined (OPENSSL_IS_BORINGSSL)
++ /* OpenSSL provides read keys for an application level before it's ready */
++
++ if (pkt->level == ssl_encryption_application
++ && SSL_quic_read_level(c->ssl->connection)
++ < ssl_encryption_application)
++ {
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic no %s keys ready, ignoring packet",
++ ngx_quic_level_name(pkt->level));
++ return NGX_DECLINED;
++ }
++#endif
++
++ pkt->keys = qc->keys;
++ pkt->key_phase = qc->key_phase;
++ pkt->plaintext = buf;
++
++ ctx = ngx_quic_get_send_ctx(qc, pkt->level);
++
++ rc = ngx_quic_decrypt(pkt, &ctx->largest_pn);
++ if (rc != NGX_OK) {
++ qc->error = pkt->error;
++ qc->error_reason = "failed to decrypt packet";
++ return rc;
++ }
++
++ pkt->decrypted = 1;
++
++ if (pkt->first) {
++ if (ngx_quic_update_paths(c, pkt) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++
++ if (c->ssl == NULL) {
++ if (ngx_quic_init_connection(c) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++
++ if (pkt->level == ssl_encryption_handshake) {
++ /*
++ * RFC 9001, 4.9.1. Discarding Initial Keys
++ *
++ * The successful use of Handshake packets indicates
++ * that no more Initial packets need to be exchanged
++ */
++ ngx_quic_discard_ctx(c, ssl_encryption_initial);
++
++ if (qc->socket->path->state != NGX_QUIC_PATH_VALIDATED) {
++ qc->socket->path->state = NGX_QUIC_PATH_VALIDATED;
++ qc->socket->path->limited = 0;
++ ngx_post_event(&qc->push, &ngx_posted_events);
++ }
++ }
++
++ if (qc->closing) {
++ /*
++ * RFC 9000, 10.2. Immediate Close
++ *
++ * ... delayed or reordered packets are properly discarded.
++ *
++ * In the closing state, an endpoint retains only enough information
++ * to generate a packet containing a CONNECTION_CLOSE frame and to
++ * identify packets as belonging to the connection.
++ */
++
++ qc->error_level = pkt->level;
++ qc->error = NGX_QUIC_ERR_NO_ERROR;
++ qc->error_reason = "connection is closing, packet discarded";
++ qc->error_ftype = 0;
++ qc->error_app = 0;
++
++ return ngx_quic_send_cc(c);
++ }
++
++ pkt->received = ngx_current_msec;
++
++ c->log->action = "handling payload";
++
++ if (pkt->level != ssl_encryption_application) {
++ return ngx_quic_handle_frames(c, pkt);
++ }
++
++ if (!pkt->key_update) {
++ return ngx_quic_handle_frames(c, pkt);
++ }
++
++ /* switch keys and generate next on Key Phase change */
++
++ qc->key_phase ^= 1;
++ ngx_quic_keys_switch(c, qc->keys);
++
++ rc = ngx_quic_handle_frames(c, pkt);
++ if (rc != NGX_OK) {
++ return rc;
++ }
++
++ return ngx_quic_keys_update(c, qc->keys);
++}
++
++
++void
++ngx_quic_discard_ctx(ngx_connection_t *c, enum ssl_encryption_level_t level)
++{
++ ngx_queue_t *q;
++ ngx_quic_frame_t *f;
++ ngx_quic_socket_t *qsock;
++ ngx_quic_send_ctx_t *ctx;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ if (!ngx_quic_keys_available(qc->keys, level)) {
++ return;
++ }
++
++ ngx_quic_keys_discard(qc->keys, level);
++
++ qc->pto_count = 0;
++
++ ctx = ngx_quic_get_send_ctx(qc, level);
++
++ ngx_quic_free_chain(c, ctx->crypto);
++
++ while (!ngx_queue_empty(&ctx->sent)) {
++ q = ngx_queue_head(&ctx->sent);
++ ngx_queue_remove(q);
++
++ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
++ ngx_quic_congestion_ack(c, f);
++ ngx_quic_free_frame(c, f);
++ }
++
++ while (!ngx_queue_empty(&ctx->frames)) {
++ q = ngx_queue_head(&ctx->frames);
++ ngx_queue_remove(q);
++
++ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
++ ngx_quic_congestion_ack(c, f);
++ ngx_quic_free_frame(c, f);
++ }
++
++ if (level == ssl_encryption_initial) {
++ /* close temporary listener with odcid */
++ qsock = ngx_quic_find_socket(c, NGX_QUIC_UNSET_PN);
++ if (qsock) {
++ ngx_quic_close_socket(c, qsock);
++ }
++ }
++
++ ctx->send_ack = 0;
++
++ ngx_quic_set_lost_timer(c);
++}
++
++
++static ngx_int_t
++ngx_quic_check_csid(ngx_quic_connection_t *qc, ngx_quic_header_t *pkt)
++{
++ ngx_queue_t *q;
++ ngx_quic_client_id_t *cid;
++
++ for (q = ngx_queue_head(&qc->client_ids);
++ q != ngx_queue_sentinel(&qc->client_ids);
++ q = ngx_queue_next(q))
++ {
++ cid = ngx_queue_data(q, ngx_quic_client_id_t, queue);
++
++ if (pkt->scid.len == cid->len
++ && ngx_memcmp(pkt->scid.data, cid->id, cid->len) == 0)
++ {
++ return NGX_OK;
++ }
++ }
++
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic unexpected quic scid");
++ return NGX_ERROR;
++}
++
++
++static ngx_int_t
++ngx_quic_handle_frames(ngx_connection_t *c, ngx_quic_header_t *pkt)
++{
++ u_char *end, *p;
++ ssize_t len;
++ ngx_buf_t buf;
++ ngx_uint_t do_close, nonprobing;
++ ngx_chain_t chain;
++ ngx_quic_frame_t frame;
++ ngx_quic_socket_t *qsock;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ p = pkt->payload.data;
++ end = p + pkt->payload.len;
++
++ do_close = 0;
++ nonprobing = 0;
++
++ while (p < end) {
++
++ c->log->action = "parsing frames";
++
++ ngx_memzero(&frame, sizeof(ngx_quic_frame_t));
++ ngx_memzero(&buf, sizeof(ngx_buf_t));
++ buf.temporary = 1;
++
++ chain.buf = &buf;
++ chain.next = NULL;
++ frame.data = &chain;
++
++ len = ngx_quic_parse_frame(pkt, p, end, &frame);
++
++ if (len < 0) {
++ qc->error = pkt->error;
++ return NGX_ERROR;
++ }
++
++ ngx_quic_log_frame(c->log, &frame, 0);
++
++ c->log->action = "handling frames";
++
++ p += len;
++
++ switch (frame.type) {
++ /* probing frames */
++ case NGX_QUIC_FT_PADDING:
++ case NGX_QUIC_FT_PATH_CHALLENGE:
++ case NGX_QUIC_FT_PATH_RESPONSE:
++ case NGX_QUIC_FT_NEW_CONNECTION_ID:
++ break;
++
++ /* non-probing frames */
++ default:
++ nonprobing = 1;
++ break;
++ }
++
++ switch (frame.type) {
++
++ case NGX_QUIC_FT_ACK:
++ if (ngx_quic_handle_ack_frame(c, pkt, &frame) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ continue;
++
++ case NGX_QUIC_FT_PADDING:
++ /* no action required */
++ continue;
++
++ case NGX_QUIC_FT_CONNECTION_CLOSE:
++ case NGX_QUIC_FT_CONNECTION_CLOSE_APP:
++ do_close = 1;
++ continue;
++ }
++
++ /* got there with ack-eliciting packet */
++ pkt->need_ack = 1;
++
++ switch (frame.type) {
++
++ case NGX_QUIC_FT_CRYPTO:
++
++ if (ngx_quic_handle_crypto_frame(c, pkt, &frame) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_PING:
++ break;
++
++ case NGX_QUIC_FT_STREAM:
++
++ if (ngx_quic_handle_stream_frame(c, pkt, &frame) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_MAX_DATA:
++
++ if (ngx_quic_handle_max_data_frame(c, &frame.u.max_data) != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_STREAMS_BLOCKED:
++ case NGX_QUIC_FT_STREAMS_BLOCKED2:
++
++ if (ngx_quic_handle_streams_blocked_frame(c, pkt,
++ &frame.u.streams_blocked)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_DATA_BLOCKED:
++
++ if (ngx_quic_handle_data_blocked_frame(c, pkt,
++ &frame.u.data_blocked)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_STREAM_DATA_BLOCKED:
++
++ if (ngx_quic_handle_stream_data_blocked_frame(c, pkt,
++ &frame.u.stream_data_blocked)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_MAX_STREAM_DATA:
++
++ if (ngx_quic_handle_max_stream_data_frame(c, pkt,
++ &frame.u.max_stream_data)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_RESET_STREAM:
++
++ if (ngx_quic_handle_reset_stream_frame(c, pkt,
++ &frame.u.reset_stream)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_STOP_SENDING:
++
++ if (ngx_quic_handle_stop_sending_frame(c, pkt,
++ &frame.u.stop_sending)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_MAX_STREAMS:
++ case NGX_QUIC_FT_MAX_STREAMS2:
++
++ if (ngx_quic_handle_max_streams_frame(c, pkt, &frame.u.max_streams)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_PATH_CHALLENGE:
++
++ if (ngx_quic_handle_path_challenge_frame(c, &frame.u.path_challenge)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_PATH_RESPONSE:
++
++ if (ngx_quic_handle_path_response_frame(c, &frame.u.path_response)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_NEW_CONNECTION_ID:
++
++ if (ngx_quic_handle_new_connection_id_frame(c, &frame.u.ncid)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_RETIRE_CONNECTION_ID:
++
++ if (ngx_quic_handle_retire_connection_id_frame(c,
++ &frame.u.retire_cid)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ break;
++
++ default:
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic missing frame handler");
++ return NGX_ERROR;
++ }
++ }
++
++ if (p != end) {
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic trailing garbage in payload:%ui bytes", end - p);
++
++ qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR;
++ return NGX_ERROR;
++ }
++
++ if (do_close) {
++ qc->draining = 1;
++ ngx_quic_close_connection(c, NGX_OK);
++ }
++
++ qsock = ngx_quic_get_socket(c);
++
++ if (qsock != qc->socket) {
++
++ if (qsock->path != qc->socket->path && nonprobing) {
++ /*
++ * RFC 9000, 9.2. Initiating Connection Migration
++ *
++ * An endpoint can migrate a connection to a new local
++ * address by sending packets containing non-probing frames
++ * from that address.
++ */
++ if (ngx_quic_handle_migration(c, pkt) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++ /*
++ * else: packet arrived via non-default socket;
++ * no reason to change active path
++ */
++ }
++
++ if (ngx_quic_ack_packet(c, pkt) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ return NGX_OK;
++}
++
++
++static void
++ngx_quic_push_handler(ngx_event_t *ev)
++{
++ ngx_connection_t *c;
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic push timer");
++
++ c = ev->data;
++
++ if (ngx_quic_output(c) != NGX_OK) {
++ ngx_quic_close_connection(c, NGX_ERROR);
++ return;
++ }
++
++ ngx_quic_connstate_dbg(c);
++}
++
++
++void
++ngx_quic_shutdown_quic(ngx_connection_t *c)
++{
++ ngx_rbtree_t *tree;
++ ngx_rbtree_node_t *node;
++ ngx_quic_stream_t *qs;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ if (qc->closing) {
++ return;
++ }
++
++ tree = &qc->streams.tree;
++
++ if (tree->root != tree->sentinel) {
++ for (node = ngx_rbtree_min(tree->root, tree->sentinel);
++ node;
++ node = ngx_rbtree_next(tree, node))
++ {
++ qs = (ngx_quic_stream_t *) node;
++
++ if (!qs->cancelable) {
++ return;
++ }
++ }
++ }
++
++ ngx_quic_finalize_connection(c, qc->shutdown_code, qc->shutdown_reason);
++}
++
++
++uint32_t
++ngx_quic_version(ngx_connection_t *c)
++{
++ uint32_t version;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ version = qc->version;
++
++ return (version & 0xff000000) == 0xff000000 ? version & 0xff : version;
++}
+diff -r 67408b4a12c0 src/event/quic/ngx_event_quic.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic.h Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,87 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#ifndef _NGX_EVENT_QUIC_H_INCLUDED_
++#define _NGX_EVENT_QUIC_H_INCLUDED_
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++
++
++#define NGX_QUIC_MAX_UDP_PAYLOAD_SIZE 65527
++
++#define NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT 3
++#define NGX_QUIC_DEFAULT_MAX_ACK_DELAY 25
++#define NGX_QUIC_DEFAULT_HOST_KEY_LEN 32
++#define NGX_QUIC_SR_KEY_LEN 32
++#define NGX_QUIC_AV_KEY_LEN 32
++
++#define NGX_QUIC_SR_TOKEN_LEN 16
++
++#define NGX_QUIC_MIN_INITIAL_SIZE 1200
++
++#define NGX_QUIC_STREAM_SERVER_INITIATED 0x01
++#define NGX_QUIC_STREAM_UNIDIRECTIONAL 0x02
++
++
++typedef struct {
++ ngx_ssl_t *ssl;
++
++ ngx_flag_t retry;
++ ngx_flag_t gso_enabled;
++ ngx_flag_t disable_active_migration;
++ ngx_msec_t timeout;
++ ngx_str_t host_key;
++ size_t mtu;
++ size_t stream_buffer_size;
++ ngx_uint_t max_concurrent_streams_bidi;
++ ngx_uint_t max_concurrent_streams_uni;
++ ngx_int_t stream_close_code;
++ ngx_int_t stream_reject_code_uni;
++ ngx_int_t stream_reject_code_bidi;
++
++ u_char av_token_key[NGX_QUIC_AV_KEY_LEN];
++ u_char sr_token_key[NGX_QUIC_SR_KEY_LEN];
++} ngx_quic_conf_t;
++
++
++struct ngx_quic_stream_s {
++ ngx_rbtree_node_t node;
++ ngx_queue_t queue;
++ ngx_connection_t *parent;
++ ngx_connection_t *connection;
++ uint64_t id;
++ uint64_t acked;
++ uint64_t send_max_data;
++ uint64_t recv_max_data;
++ uint64_t recv_offset;
++ uint64_t recv_window;
++ uint64_t recv_last;
++ uint64_t final_size;
++ ngx_chain_t *in;
++ ngx_chain_t *out;
++ ngx_uint_t cancelable; /* unsigned cancelable:1; */
++};
++
++
++void ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf);
++ngx_connection_t *ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi);
++void ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err,
++ const char *reason);
++void ngx_quic_shutdown_connection(ngx_connection_t *c, ngx_uint_t err,
++ const char *reason);
++ngx_int_t ngx_quic_reset_stream(ngx_connection_t *c, ngx_uint_t err);
++ngx_int_t ngx_quic_shutdown_stream(ngx_connection_t *c, int how);
++uint32_t ngx_quic_version(ngx_connection_t *c);
++ngx_int_t ngx_quic_handle_read_event(ngx_event_t *rev, ngx_uint_t flags);
++ngx_int_t ngx_quic_handle_write_event(ngx_event_t *wev, size_t lowat);
++ngx_int_t ngx_quic_get_packet_dcid(ngx_log_t *log, u_char *data, size_t len,
++ ngx_str_t *dcid);
++ngx_int_t ngx_quic_derive_key(ngx_log_t *log, const char *label,
++ ngx_str_t *secret, ngx_str_t *salt, u_char *out, size_t len);
++
++#endif /* _NGX_EVENT_QUIC_H_INCLUDED_ */
+diff -r 67408b4a12c0 src/event/quic/ngx_event_quic_ack.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_ack.c Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,1190 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++#include <ngx_event.h>
++#include <ngx_event_quic_connection.h>
++
++
++#define NGX_QUIC_MAX_ACK_GAP 2
++
++/* RFC 9002, 6.1.1. Packet Threshold: kPacketThreshold */
++#define NGX_QUIC_PKT_THR 3 /* packets */
++/* RFC 9002, 6.1.2. Time Threshold: kGranularity */
++#define NGX_QUIC_TIME_GRANULARITY 1 /* ms */
++
++/* RFC 9002, 7.6.1. Duration: kPersistentCongestionThreshold */
++#define NGX_QUIC_PERSISTENT_CONGESTION_THR 3
++
++
++/* send time of ACK'ed packets */
++typedef struct {
++ ngx_msec_t max_pn;
++ ngx_msec_t oldest;
++ ngx_msec_t newest;
++} ngx_quic_ack_stat_t;
++
++
++static ngx_inline ngx_msec_t ngx_quic_lost_threshold(ngx_quic_connection_t *qc);
++static void ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack,
++ enum ssl_encryption_level_t level, ngx_msec_t send_time);
++static ngx_int_t ngx_quic_handle_ack_frame_range(ngx_connection_t *c,
++ ngx_quic_send_ctx_t *ctx, uint64_t min, uint64_t max,
++ ngx_quic_ack_stat_t *st);
++static void ngx_quic_drop_ack_ranges(ngx_connection_t *c,
++ ngx_quic_send_ctx_t *ctx, uint64_t pn);
++static ngx_int_t ngx_quic_detect_lost(ngx_connection_t *c,
++ ngx_quic_ack_stat_t *st);
++static ngx_msec_t ngx_quic_pcg_duration(ngx_connection_t *c);
++static void ngx_quic_persistent_congestion(ngx_connection_t *c);
++static void ngx_quic_congestion_lost(ngx_connection_t *c,
++ ngx_quic_frame_t *frame);
++static void ngx_quic_lost_handler(ngx_event_t *ev);
++
++
++/* RFC 9002, 6.1.2. Time Threshold: kTimeThreshold, kGranularity */
++static ngx_inline ngx_msec_t
++ngx_quic_lost_threshold(ngx_quic_connection_t *qc)
++{
++ ngx_msec_t thr;
++
++ thr = ngx_max(qc->latest_rtt, qc->avg_rtt);
++ thr += thr >> 3;
++
++ return ngx_max(thr, NGX_QUIC_TIME_GRANULARITY);
++}
++
++
++ngx_int_t
++ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt,
++ ngx_quic_frame_t *f)
++{
++ ssize_t n;
++ u_char *pos, *end;
++ uint64_t min, max, gap, range;
++ ngx_uint_t i;
++ ngx_quic_ack_stat_t send_time;
++ ngx_quic_send_ctx_t *ctx;
++ ngx_quic_ack_frame_t *ack;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ ctx = ngx_quic_get_send_ctx(qc, pkt->level);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic ngx_quic_handle_ack_frame level:%d", pkt->level);
++
++ ack = &f->u.ack;
++
++ /*
++ * RFC 9000, 19.3.1. ACK Ranges
++ *
++ * If any computed packet number is negative, an endpoint MUST
++ * generate a connection error of type FRAME_ENCODING_ERROR.
++ */
++
++ if (ack->first_range > ack->largest) {
++ qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR;
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic invalid first range in ack frame");
++ return NGX_ERROR;
++ }
++
++ min = ack->largest - ack->first_range;
++ max = ack->largest;
++
++ send_time.oldest = NGX_TIMER_INFINITE;
++ send_time.newest = NGX_TIMER_INFINITE;
++
++ if (ngx_quic_handle_ack_frame_range(c, ctx, min, max, &send_time)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ /* RFC 9000, 13.2.4. Limiting Ranges by Tracking ACK Frames */
++ if (ctx->largest_ack < max || ctx->largest_ack == NGX_QUIC_UNSET_PN) {
++ ctx->largest_ack = max;
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic updated largest received ack:%uL", max);
++
++ /*
++ * RFC 9002, 5.1. Generating RTT Samples
++ *
++ * An endpoint generates an RTT sample on receiving an
++ * ACK frame that meets the following two conditions:
++ *
++ * - the largest acknowledged packet number is newly acknowledged
++ * - at least one of the newly acknowledged packets was ack-eliciting.
++ */
++
++ if (send_time.max_pn != NGX_TIMER_INFINITE) {
++ ngx_quic_rtt_sample(c, ack, pkt->level, send_time.max_pn);
++ }
++ }
++
++ if (f->data) {
++ pos = f->data->buf->pos;
++ end = f->data->buf->last;
++
++ } else {
++ pos = NULL;
++ end = NULL;
++ }
++
++ for (i = 0; i < ack->range_count; i++) {
++
++ n = ngx_quic_parse_ack_range(pkt->log, pos, end, &gap, &range);
++ if (n == NGX_ERROR) {
++ return NGX_ERROR;
++ }
++ pos += n;
++
++ if (gap + 2 > min) {
++ qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR;
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic invalid range:%ui in ack frame", i);
++ return NGX_ERROR;
++ }
++
++ max = min - gap - 2;
++
++ if (range > max) {
++ qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR;
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic invalid range:%ui in ack frame", i);
++ return NGX_ERROR;
++ }
++
++ min = max - range;
++
++ if (ngx_quic_handle_ack_frame_range(c, ctx, min, max, &send_time)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++ }
++
++ return ngx_quic_detect_lost(c, &send_time);
++}
++
++
++static void
++ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack,
++ enum ssl_encryption_level_t level, ngx_msec_t send_time)
++{
++ ngx_msec_t latest_rtt, ack_delay, adjusted_rtt, rttvar_sample;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ latest_rtt = ngx_current_msec - send_time;
++ qc->latest_rtt = latest_rtt;
++
++ if (qc->min_rtt == NGX_TIMER_INFINITE) {
++ qc->min_rtt = latest_rtt;
++ qc->avg_rtt = latest_rtt;
++ qc->rttvar = latest_rtt / 2;
++ qc->first_rtt = ngx_current_msec;
++
++ } else {
++ qc->min_rtt = ngx_min(qc->min_rtt, latest_rtt);
++
++ ack_delay = ack->delay * (1 << qc->ctp.ack_delay_exponent) / 1000;
++
++ if (c->ssl->handshaked) {
++ ack_delay = ngx_min(ack_delay, qc->ctp.max_ack_delay);
++ }
++
++ adjusted_rtt = latest_rtt;
++
++ if (qc->min_rtt + ack_delay < latest_rtt) {
++ adjusted_rtt -= ack_delay;
++ }
++
++ qc->avg_rtt += (adjusted_rtt >> 3) - (qc->avg_rtt >> 3);
++ rttvar_sample = ngx_abs((ngx_msec_int_t) (qc->avg_rtt - adjusted_rtt));
++ qc->rttvar += (rttvar_sample >> 2) - (qc->rttvar >> 2);
++ }
++
++ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic rtt sample latest:%M min:%M avg:%M var:%M",
++ latest_rtt, qc->min_rtt, qc->avg_rtt, qc->rttvar);
++}
++
++
++static ngx_int_t
++ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx,
++ uint64_t min, uint64_t max, ngx_quic_ack_stat_t *st)
++{
++ ngx_uint_t found;
++ ngx_queue_t *q;
++ ngx_quic_frame_t *f;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ st->max_pn = NGX_TIMER_INFINITE;
++ found = 0;
++
++ q = ngx_queue_head(&ctx->sent);
++
++ while (q != ngx_queue_sentinel(&ctx->sent)) {
++
++ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
++ q = ngx_queue_next(q);
++
++ if (f->pnum > max) {
++ break;
++ }
++
++ if (f->pnum >= min) {
++ ngx_quic_congestion_ack(c, f);
++
++ switch (f->type) {
++ case NGX_QUIC_FT_ACK:
++ case NGX_QUIC_FT_ACK_ECN:
++ ngx_quic_drop_ack_ranges(c, ctx, f->u.ack.largest);
++ break;
++
++ case NGX_QUIC_FT_STREAM:
++ ngx_quic_handle_stream_ack(c, f);
++ break;
++ }
++
++ if (f->pnum == max) {
++ st->max_pn = f->last;
++ }
++
++ /* save earliest and latest send times of frames ack'ed */
++ if (st->oldest == NGX_TIMER_INFINITE || f->last < st->oldest) {
++ st->oldest = f->last;
++ }
++
++ if (st->newest == NGX_TIMER_INFINITE || f->last > st->newest) {
++ st->newest = f->last;
++ }
++
++ ngx_queue_remove(&f->queue);
++ ngx_quic_free_frame(c, f);
++ found = 1;
++ }
++ }
++
++ if (!found) {
++
++ if (max < ctx->pnum) {
++ /* duplicate ACK or ACK for non-ack-eliciting frame */
++ return NGX_OK;
++ }
++
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic ACK for the packet not sent");
++
++ qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION;
++ qc->error_ftype = NGX_QUIC_FT_ACK;
++ qc->error_reason = "unknown packet number";
++
++ return NGX_ERROR;
++ }
++
++ if (!qc->push.timer_set) {
++ ngx_post_event(&qc->push, &ngx_posted_events);
++ }
++
++ qc->pto_count = 0;
++
++ return NGX_OK;
++}
++
++
++void
++ngx_quic_congestion_ack(ngx_connection_t *c, ngx_quic_frame_t *f)
++{
++ ngx_uint_t blocked;
++ ngx_msec_t timer;
++ ngx_quic_congestion_t *cg;
++ ngx_quic_connection_t *qc;
++
++ if (f->plen == 0) {
++ return;
++ }
++
++ qc = ngx_quic_get_connection(c);
++ cg = &qc->congestion;
++
++ blocked = (cg->in_flight >= cg->window) ? 1 : 0;
++
++ cg->in_flight -= f->plen;
++
++ timer = f->last - cg->recovery_start;
++
++ if ((ngx_msec_int_t) timer <= 0) {
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic congestion ack recovery win:%uz ss:%z if:%uz",
++ cg->window, cg->ssthresh, cg->in_flight);
++
++ goto done;
++ }
++
++ if (cg->window < cg->ssthresh) {
++ cg->window += f->plen;
++
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic congestion slow start win:%uz ss:%z if:%uz",
++ cg->window, cg->ssthresh, cg->in_flight);
++
++ } else {
++ cg->window += qc->tp.max_udp_payload_size * f->plen / cg->window;
++
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic congestion avoidance win:%uz ss:%z if:%uz",
++ cg->window, cg->ssthresh, cg->in_flight);
++ }
++
++ /* prevent recovery_start from wrapping */
++
++ timer = cg->recovery_start - ngx_current_msec + qc->tp.max_idle_timeout * 2;
++
++ if ((ngx_msec_int_t) timer < 0) {
++ cg->recovery_start = ngx_current_msec - qc->tp.max_idle_timeout * 2;
++ }
++
++done:
++
++ if (blocked && cg->in_flight < cg->window) {
++ ngx_post_event(&qc->push, &ngx_posted_events);
++ }
++}
++
++
++static void
++ngx_quic_drop_ack_ranges(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx,
++ uint64_t pn)
++{
++ uint64_t base;
++ ngx_uint_t i, smallest, largest;
++ ngx_quic_ack_range_t *r;
++
++ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic ngx_quic_drop_ack_ranges pn:%uL largest:%uL"
++ " fr:%uL nranges:%ui", pn, ctx->largest_range,
++ ctx->first_range, ctx->nranges);
++
++ base = ctx->largest_range;
++
++ if (base == NGX_QUIC_UNSET_PN) {
++ return;
++ }
++
++ if (ctx->pending_ack != NGX_QUIC_UNSET_PN && pn >= ctx->pending_ack) {
++ ctx->pending_ack = NGX_QUIC_UNSET_PN;
++ }
++
++ largest = base;
++ smallest = largest - ctx->first_range;
++
++ if (pn >= largest) {
++ ctx->largest_range = NGX_QUIC_UNSET_PN;
++ ctx->first_range = 0;
++ ctx->nranges = 0;
++ return;
++ }
++
++ if (pn >= smallest) {
++ ctx->first_range = largest - pn - 1;
++ ctx->nranges = 0;
++ return;
++ }
++
++ for (i = 0; i < ctx->nranges; i++) {
++ r = &ctx->ranges[i];
++
++ largest = smallest - r->gap - 2;
++ smallest = largest - r->range;
++
++ if (pn >= largest) {
++ ctx->nranges = i;
++ return;
++ }
++ if (pn >= smallest) {
++ r->range = largest - pn - 1;
++ ctx->nranges = i + 1;
++ return;
++ }
++ }
++}
++
++
++static ngx_int_t
++ngx_quic_detect_lost(ngx_connection_t *c, ngx_quic_ack_stat_t *st)
++{
++ ngx_uint_t i, nlost;
++ ngx_msec_t now, wait, thr, oldest, newest;
++ ngx_queue_t *q;
++ ngx_quic_frame_t *start;
++ ngx_quic_send_ctx_t *ctx;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++ now = ngx_current_msec;
++ thr = ngx_quic_lost_threshold(qc);
++
++ /* send time of lost packets across all send contexts */
++ oldest = NGX_TIMER_INFINITE;
++ newest = NGX_TIMER_INFINITE;
++
++ nlost = 0;
++
++ for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) {
++
++ ctx = &qc->send_ctx[i];
++
++ if (ctx->largest_ack == NGX_QUIC_UNSET_PN) {
++ continue;
++ }
++
++ while (!ngx_queue_empty(&ctx->sent)) {
++
++ q = ngx_queue_head(&ctx->sent);
++ start = ngx_queue_data(q, ngx_quic_frame_t, queue);
++
++ if (start->pnum > ctx->largest_ack) {
++ break;
++ }
++
++ wait = start->last + thr - now;
++
++ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic detect_lost pnum:%uL thr:%M wait:%i level:%d",
++ start->pnum, thr, (ngx_int_t) wait, start->level);
++
++ if ((ngx_msec_int_t) wait > 0
++ && ctx->largest_ack - start->pnum < NGX_QUIC_PKT_THR)
++ {
++ break;
++ }
++
++ if (start->last > qc->first_rtt) {
++
++ if (oldest == NGX_TIMER_INFINITE || start->last < oldest) {
++ oldest = start->last;
++ }
++
++ if (newest == NGX_TIMER_INFINITE || start->last > newest) {
++ newest = start->last;
++ }
++
++ nlost++;
++ }
++
++ ngx_quic_resend_frames(c, ctx);
++ }
++ }
++
++
++ /* RFC 9002, 7.6.2. Establishing Persistent Congestion */
++
++ /*
++ * Once acknowledged, packets are no longer tracked. Thus no send time
++ * information is available for such packets. This limits persistent
++ * congestion algorithm to packets mentioned within ACK ranges of the
++ * latest ACK frame.
++ */
++
++ if (st && nlost >= 2 && (st->newest < oldest || st->oldest > newest)) {
++
++ if (newest - oldest > ngx_quic_pcg_duration(c)) {
++ ngx_quic_persistent_congestion(c);
++ }
++ }
++
++ ngx_quic_set_lost_timer(c);
++
++ return NGX_OK;
++}
++
++
++static ngx_msec_t
++ngx_quic_pcg_duration(ngx_connection_t *c)
++{
++ ngx_msec_t duration;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ duration = qc->avg_rtt;
++ duration += ngx_max(4 * qc->rttvar, NGX_QUIC_TIME_GRANULARITY);
++ duration += qc->ctp.max_ack_delay;
++ duration *= NGX_QUIC_PERSISTENT_CONGESTION_THR;
++
++ return duration;
++}
++
++
++static void
++ngx_quic_persistent_congestion(ngx_connection_t *c)
++{
++ ngx_quic_congestion_t *cg;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++ cg = &qc->congestion;
++
++ cg->recovery_start = ngx_current_msec;
++ cg->window = qc->tp.max_udp_payload_size * 2;
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic persistent congestion win:%uz", cg->window);
++}
++
++
++void
++ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx)
++{
++ ngx_queue_t *q;
++ ngx_quic_frame_t *f, *start;
++ ngx_quic_stream_t *qs;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++ q = ngx_queue_head(&ctx->sent);
++ start = ngx_queue_data(q, ngx_quic_frame_t, queue);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic resend packet pnum:%uL", start->pnum);
++
++ ngx_quic_congestion_lost(c, start);
++
++ do {
++ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
++
++ if (f->pnum != start->pnum) {
++ break;
++ }
++
++ q = ngx_queue_next(q);
++
++ ngx_queue_remove(&f->queue);
++
++ switch (f->type) {
++ case NGX_QUIC_FT_ACK:
++ case NGX_QUIC_FT_ACK_ECN:
++ if (ctx->level == ssl_encryption_application) {
++ /* force generation of most recent acknowledgment */
++ ctx->send_ack = NGX_QUIC_MAX_ACK_GAP;
++ }
++
++ ngx_quic_free_frame(c, f);
++ break;
++
++ case NGX_QUIC_FT_PING:
++ case NGX_QUIC_FT_PATH_RESPONSE:
++ case NGX_QUIC_FT_CONNECTION_CLOSE:
++ ngx_quic_free_frame(c, f);
++ break;
++
++ case NGX_QUIC_FT_MAX_DATA:
++ f->u.max_data.max_data = qc->streams.recv_max_data;
++ ngx_quic_queue_frame(qc, f);
++ break;
++
++ case NGX_QUIC_FT_MAX_STREAMS:
++ case NGX_QUIC_FT_MAX_STREAMS2:
++ f->u.max_streams.limit = f->u.max_streams.bidi
++ ? qc->streams.client_max_streams_bidi
++ : qc->streams.client_max_streams_uni;
++ ngx_quic_queue_frame(qc, f);
++ break;
++
++ case NGX_QUIC_FT_MAX_STREAM_DATA:
++ qs = ngx_quic_find_stream(&qc->streams.tree,
++ f->u.max_stream_data.id);
++ if (qs == NULL) {
++ ngx_quic_free_frame(c, f);
++ break;
++ }
++
++ f->u.max_stream_data.limit = qs->recv_max_data;
++ ngx_quic_queue_frame(qc, f);
++ break;
++
++ case NGX_QUIC_FT_STREAM:
++ qs = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id);
++
++ if (qs && qs->connection->write->error) {
++ /* RESET_STREAM was sent */
++ ngx_quic_free_frame(c, f);
++ break;
++ }
++
++ /* fall through */
++
++ default:
++ ngx_queue_insert_tail(&ctx->frames, &f->queue);
++ }
++
++ } while (q != ngx_queue_sentinel(&ctx->sent));
++
++ if (qc->closing) {
++ return;
++ }
++
++ ngx_post_event(&qc->push, &ngx_posted_events);
++}
++
++
++static void
++ngx_quic_congestion_lost(ngx_connection_t *c, ngx_quic_frame_t *f)
++{
++ ngx_uint_t blocked;
++ ngx_msec_t timer;
++ ngx_quic_congestion_t *cg;
++ ngx_quic_connection_t *qc;
++
++ if (f->plen == 0) {
++ return;
++ }
++
++ qc = ngx_quic_get_connection(c);
++ cg = &qc->congestion;
++
++ blocked = (cg->in_flight >= cg->window) ? 1 : 0;
++
++ cg->in_flight -= f->plen;
++ f->plen = 0;
++
++ timer = f->last - cg->recovery_start;
++
++ if ((ngx_msec_int_t) timer <= 0) {
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic congestion lost recovery win:%uz ss:%z if:%uz",
++ cg->window, cg->ssthresh, cg->in_flight);
++
++ goto done;
++ }
++
++ cg->recovery_start = ngx_current_msec;
++ cg->window /= 2;
++
++ if (cg->window < qc->tp.max_udp_payload_size * 2) {
++ cg->window = qc->tp.max_udp_payload_size * 2;
++ }
++
++ cg->ssthresh = cg->window;
++
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic congestion lost win:%uz ss:%z if:%uz",
++ cg->window, cg->ssthresh, cg->in_flight);
++
++done:
++
++ if (blocked && cg->in_flight < cg->window) {
++ ngx_post_event(&qc->push, &ngx_posted_events);
++ }
++}
++
++
++void
++ngx_quic_set_lost_timer(ngx_connection_t *c)
++{
++ ngx_uint_t i;
++ ngx_msec_t now;
++ ngx_queue_t *q;
++ ngx_msec_int_t lost, pto, w;
++ ngx_quic_frame_t *f;
++ ngx_quic_send_ctx_t *ctx;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++ now = ngx_current_msec;
++
++ lost = -1;
++ pto = -1;
++
++ for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) {
++ ctx = &qc->send_ctx[i];
++
++ if (ngx_queue_empty(&ctx->sent)) {
++ continue;
++ }
++
++ if (ctx->largest_ack != NGX_QUIC_UNSET_PN) {
++ q = ngx_queue_head(&ctx->sent);
++ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
++ w = (ngx_msec_int_t) (f->last + ngx_quic_lost_threshold(qc) - now);
++
++ if (f->pnum <= ctx->largest_ack) {
++ if (w < 0 || ctx->largest_ack - f->pnum >= NGX_QUIC_PKT_THR) {
++ w = 0;
++ }
++
++ if (lost == -1 || w < lost) {
++ lost = w;
++ }
++ }
++ }
++
++ q = ngx_queue_last(&ctx->sent);
++ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
++ w = (ngx_msec_int_t) (f->last + ngx_quic_pto(c, ctx) - now);
++
++ if (w < 0) {
++ w = 0;
++ }
++
++ if (pto == -1 || w < pto) {
++ pto = w;
++ }
++ }
++
++ if (qc->pto.timer_set) {
++ ngx_del_timer(&qc->pto);
++ }
++
++ if (lost != -1) {
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic lost timer lost:%M", lost);
++
++ qc->pto.handler = ngx_quic_lost_handler;
++ ngx_add_timer(&qc->pto, lost);
++ return;
++ }
++
++ if (pto != -1) {
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic lost timer pto:%M", pto);
++
++ qc->pto.handler = ngx_quic_pto_handler;
++ ngx_add_timer(&qc->pto, pto);
++ return;
++ }
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic lost timer unset");
++}
++
++
++ngx_msec_t
++ngx_quic_pto(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx)
++{
++ ngx_msec_t duration;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ /* RFC 9002, Appendix A.8. Setting the Loss Detection Timer */
++ duration = qc->avg_rtt;
++
++ duration += ngx_max(4 * qc->rttvar, NGX_QUIC_TIME_GRANULARITY);
++ duration <<= qc->pto_count;
++
++ if (qc->congestion.in_flight == 0) { /* no in-flight packets */
++ return duration;
++ }
++
++ if (ctx->level == ssl_encryption_application && c->ssl->handshaked) {
++ duration += qc->ctp.max_ack_delay << qc->pto_count;
++ }
++
++ return duration;
++}
++
++
++static
++void ngx_quic_lost_handler(ngx_event_t *ev)
++{
++ ngx_connection_t *c;
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic lost timer");
++
++ c = ev->data;
++
++ if (ngx_quic_detect_lost(c, NULL) != NGX_OK) {
++ ngx_quic_close_connection(c, NGX_ERROR);
++ }
++
++ ngx_quic_connstate_dbg(c);
++}
++
++
++void
++ngx_quic_pto_handler(ngx_event_t *ev)
++{
++ ngx_uint_t i;
++ ngx_msec_t now;
++ ngx_queue_t *q, *next;
++ ngx_connection_t *c;
++ ngx_quic_frame_t *f;
++ ngx_quic_send_ctx_t *ctx;
++ ngx_quic_connection_t *qc;
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic pto timer");
++
++ c = ev->data;
++ qc = ngx_quic_get_connection(c);
++ now = ngx_current_msec;
++
++ for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) {
++
++ ctx = &qc->send_ctx[i];
++
++ if (ngx_queue_empty(&ctx->sent)) {
++ continue;
++ }
++
++ q = ngx_queue_head(&ctx->sent);
++ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
++
++ if (f->pnum <= ctx->largest_ack
++ && ctx->largest_ack != NGX_QUIC_UNSET_PN)
++ {
++ continue;
++ }
++
++ if ((ngx_msec_int_t) (f->last + ngx_quic_pto(c, ctx) - now) > 0) {
++ continue;
++ }
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic pto %s pto_count:%ui",
++ ngx_quic_level_name(ctx->level), qc->pto_count);
++
++ for (q = ngx_queue_head(&ctx->frames);
++ q != ngx_queue_sentinel(&ctx->frames);
++ /* void */)
++ {
++ next = ngx_queue_next(q);
++ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
++
++ if (f->type == NGX_QUIC_FT_PING) {
++ ngx_queue_remove(q);
++ ngx_quic_free_frame(c, f);
++ }
++
++ q = next;
++ }
++
++ for (q = ngx_queue_head(&ctx->sent);
++ q != ngx_queue_sentinel(&ctx->sent);
++ /* void */)
++ {
++ next = ngx_queue_next(q);
++ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
++
++ if (f->type == NGX_QUIC_FT_PING) {
++ ngx_quic_congestion_lost(c, f);
++ ngx_queue_remove(q);
++ ngx_quic_free_frame(c, f);
++ }
++
++ q = next;
++ }
++
++ /* enforce 2 udp datagrams */
++
++ f = ngx_quic_alloc_frame(c);
++ if (f == NULL) {
++ break;
++ }
++
++ f->level = ctx->level;
++ f->type = NGX_QUIC_FT_PING;
++ f->flush = 1;
++
++ ngx_quic_queue_frame(qc, f);
++
++ f = ngx_quic_alloc_frame(c);
++ if (f == NULL) {
++ break;
++ }
++
++ f->level = ctx->level;
++ f->type = NGX_QUIC_FT_PING;
++
++ ngx_quic_queue_frame(qc, f);
++ }
++
++ qc->pto_count++;
++
++ ngx_quic_connstate_dbg(c);
++}
++
++
++ngx_int_t
++ngx_quic_ack_packet(ngx_connection_t *c, ngx_quic_header_t *pkt)
++{
++ uint64_t base, largest, smallest, gs, ge, gap, range, pn;
++ uint64_t prev_pending;
++ ngx_uint_t i, nr;
++ ngx_quic_send_ctx_t *ctx;
++ ngx_quic_ack_range_t *r;
++ ngx_quic_connection_t *qc;
++
++ c->log->action = "preparing ack";
++
++ qc = ngx_quic_get_connection(c);
++
++ ctx = ngx_quic_get_send_ctx(qc, pkt->level);
++
++ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic ngx_quic_ack_packet pn:%uL largest %L fr:%uL"
++ " nranges:%ui", pkt->pn, (int64_t) ctx->largest_range,
++ ctx->first_range, ctx->nranges);
++
++ prev_pending = ctx->pending_ack;
++
++ if (pkt->need_ack) {
++
++ ngx_post_event(&qc->push, &ngx_posted_events);
++
++ if (ctx->send_ack == 0) {
++ ctx->ack_delay_start = ngx_current_msec;
++ }
++
++ ctx->send_ack++;
++
++ if (ctx->pending_ack == NGX_QUIC_UNSET_PN
++ || ctx->pending_ack < pkt->pn)
++ {
++ ctx->pending_ack = pkt->pn;
++ }
++ }
++
++ base = ctx->largest_range;
++ pn = pkt->pn;
++
++ if (base == NGX_QUIC_UNSET_PN) {
++ ctx->largest_range = pn;
++ ctx->largest_received = pkt->received;
++ return NGX_OK;
++ }
++
++ if (base == pn) {
++ return NGX_OK;
++ }
++
++ largest = base;
++ smallest = largest - ctx->first_range;
++
++ if (pn > base) {
++
++ if (pn - base == 1) {
++ ctx->first_range++;
++ ctx->largest_range = pn;
++ ctx->largest_received = pkt->received;
++
++ return NGX_OK;
++
++ } else {
++ /* new gap in front of current largest */
++
++ /* no place for new range, send current range as is */
++ if (ctx->nranges == NGX_QUIC_MAX_RANGES) {
++
++ if (prev_pending != NGX_QUIC_UNSET_PN) {
++ if (ngx_quic_send_ack(c, ctx) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++
++ if (prev_pending == ctx->pending_ack || !pkt->need_ack) {
++ ctx->pending_ack = NGX_QUIC_UNSET_PN;
++ }
++ }
++
++ gap = pn - base - 2;
++ range = ctx->first_range;
++
++ ctx->first_range = 0;
++ ctx->largest_range = pn;
++ ctx->largest_received = pkt->received;
++
++ /* packet is out of order, force send */
++ if (pkt->need_ack) {
++ ctx->send_ack = NGX_QUIC_MAX_ACK_GAP;
++ }
++
++ i = 0;
++
++ goto insert;
++ }
++ }
++
++ /* pn < base, perform lookup in existing ranges */
++
++ /* packet is out of order */
++ if (pkt->need_ack) {
++ ctx->send_ack = NGX_QUIC_MAX_ACK_GAP;
++ }
++
++ if (pn >= smallest && pn <= largest) {
++ return NGX_OK;
++ }
++
++#if (NGX_SUPPRESS_WARN)
++ r = NULL;
++#endif
++
++ for (i = 0; i < ctx->nranges; i++) {
++ r = &ctx->ranges[i];
++
++ ge = smallest - 1;
++ gs = ge - r->gap;
++
++ if (pn >= gs && pn <= ge) {
++
++ if (gs == ge) {
++ /* gap size is exactly one packet, now filled */
++
++ /* data moves to previous range, current is removed */
++
++ if (i == 0) {
++ ctx->first_range += r->range + 2;
++
++ } else {
++ ctx->ranges[i - 1].range += r->range + 2;
++ }
++
++ nr = ctx->nranges - i - 1;
++ if (nr) {
++ ngx_memmove(&ctx->ranges[i], &ctx->ranges[i + 1],
++ sizeof(ngx_quic_ack_range_t) * nr);
++ }
++
++ ctx->nranges--;
++
++ } else if (pn == gs) {
++ /* current gap shrinks from tail (current range grows) */
++ r->gap--;
++ r->range++;
++
++ } else if (pn == ge) {
++ /* current gap shrinks from head (previous range grows) */
++ r->gap--;
++
++ if (i == 0) {
++ ctx->first_range++;
++
++ } else {
++ ctx->ranges[i - 1].range++;
++ }
++
++ } else {
++ /* current gap is split into two parts */
++
++ gap = ge - pn - 1;
++ range = 0;
++
++ if (ctx->nranges == NGX_QUIC_MAX_RANGES) {
++ if (prev_pending != NGX_QUIC_UNSET_PN) {
++ if (ngx_quic_send_ack(c, ctx) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++
++ if (prev_pending == ctx->pending_ack || !pkt->need_ack) {
++ ctx->pending_ack = NGX_QUIC_UNSET_PN;
++ }
++ }
++
++ r->gap = pn - gs - 1;
++ goto insert;
++ }
++
++ return NGX_OK;
++ }
++
++ largest = smallest - r->gap - 2;
++ smallest = largest - r->range;
++
++ if (pn >= smallest && pn <= largest) {
++ /* this packet number is already known */
++ return NGX_OK;
++ }
++
++ }
++
++ if (pn == smallest - 1) {
++ /* extend first or last range */
++
++ if (i == 0) {
++ ctx->first_range++;
++
++ } else {
++ r->range++;
++ }
++
++ return NGX_OK;
++ }
++
++ /* nothing found, add new range at the tail */
++
++ if (ctx->nranges == NGX_QUIC_MAX_RANGES) {
++ /* packet is too old to keep it */
++
++ if (pkt->need_ack) {
++ return ngx_quic_send_ack_range(c, ctx, pn, pn);
++ }
++
++ return NGX_OK;
++ }
++
++ gap = smallest - 2 - pn;
++ range = 0;
++
++insert:
++
++ if (ctx->nranges < NGX_QUIC_MAX_RANGES) {
++ ctx->nranges++;
++ }
++
++ ngx_memmove(&ctx->ranges[i + 1], &ctx->ranges[i],
++ sizeof(ngx_quic_ack_range_t) * (ctx->nranges - i - 1));
++
++ ctx->ranges[i].gap = gap;
++ ctx->ranges[i].range = range;
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_generate_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx)
++{
++ ngx_msec_t delay;
++ ngx_quic_connection_t *qc;
++
++ if (!ctx->send_ack) {
++ return NGX_OK;
++ }
++
++ if (ctx->level == ssl_encryption_application) {
++
++ delay = ngx_current_msec - ctx->ack_delay_start;
++ qc = ngx_quic_get_connection(c);
++
++ if (ctx->send_ack < NGX_QUIC_MAX_ACK_GAP
++ && delay < qc->tp.max_ack_delay)
++ {
++ if (!qc->push.timer_set && !qc->closing) {
++ ngx_add_timer(&qc->push,
++ qc->tp.max_ack_delay - delay);
++ }
++
++ return NGX_OK;
++ }
++ }
++
++ if (ngx_quic_send_ack(c, ctx) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ ctx->send_ack = 0;
++
++ return NGX_OK;
++}
+diff -r 67408b4a12c0 src/event/quic/ngx_event_quic_ack.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_ack.h Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,30 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#ifndef _NGX_EVENT_QUIC_ACK_H_INCLUDED_
++#define _NGX_EVENT_QUIC_ACK_H_INCLUDED_
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++
++
++ngx_int_t ngx_quic_handle_ack_frame(ngx_connection_t *c,
++ ngx_quic_header_t *pkt, ngx_quic_frame_t *f);
++
++void ngx_quic_congestion_ack(ngx_connection_t *c,
++ ngx_quic_frame_t *frame);
++void ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx);
++void ngx_quic_set_lost_timer(ngx_connection_t *c);
++void ngx_quic_pto_handler(ngx_event_t *ev);
++ngx_msec_t ngx_quic_pto(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx);
++
++ngx_int_t ngx_quic_ack_packet(ngx_connection_t *c,
++ ngx_quic_header_t *pkt);
++ngx_int_t ngx_quic_generate_ack(ngx_connection_t *c,
++ ngx_quic_send_ctx_t *ctx);
++
++#endif /* _NGX_EVENT_QUIC_ACK_H_INCLUDED_ */
+diff -r 67408b4a12c0 src/event/quic/ngx_event_quic_bpf.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_bpf.c Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,657 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++
++
++#define NGX_QUIC_BPF_VARNAME "NGINX_BPF_MAPS"
++#define NGX_QUIC_BPF_VARSEP ';'
++#define NGX_QUIC_BPF_ADDRSEP '#'
++
++
++#define ngx_quic_bpf_get_conf(cycle) \
++ (ngx_quic_bpf_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_quic_bpf_module)
++
++#define ngx_quic_bpf_get_old_conf(cycle) \
++ cycle->old_cycle->conf_ctx ? ngx_quic_bpf_get_conf(cycle->old_cycle) \
++ : NULL
++
++#define ngx_core_get_conf(cycle) \
++ (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module)
++
++
++typedef struct {
++ ngx_queue_t queue;
++ int map_fd;
++
++ struct sockaddr *sockaddr;
++ socklen_t socklen;
++ ngx_uint_t unused; /* unsigned unused:1; */
++} ngx_quic_sock_group_t;
++
++
++typedef struct {
++ ngx_flag_t enabled;
++ ngx_uint_t map_size;
++ ngx_queue_t groups; /* of ngx_quic_sock_group_t */
++} ngx_quic_bpf_conf_t;
++
++
++static void *ngx_quic_bpf_create_conf(ngx_cycle_t *cycle);
++static ngx_int_t ngx_quic_bpf_module_init(ngx_cycle_t *cycle);
++
++static void ngx_quic_bpf_cleanup(void *data);
++static ngx_inline void ngx_quic_bpf_close(ngx_log_t *log, int fd,
++ const char *name);
++
++static ngx_quic_sock_group_t *ngx_quic_bpf_find_group(ngx_quic_bpf_conf_t *bcf,
++ ngx_listening_t *ls);
++static ngx_quic_sock_group_t *ngx_quic_bpf_alloc_group(ngx_cycle_t *cycle,
++ struct sockaddr *sa, socklen_t socklen);
++static ngx_quic_sock_group_t *ngx_quic_bpf_create_group(ngx_cycle_t *cycle,
++ ngx_listening_t *ls);
++static ngx_quic_sock_group_t *ngx_quic_bpf_get_group(ngx_cycle_t *cycle,
++ ngx_listening_t *ls);
++static ngx_int_t ngx_quic_bpf_group_add_socket(ngx_cycle_t *cycle,
++ ngx_listening_t *ls);
++static uint64_t ngx_quic_bpf_socket_key(ngx_fd_t fd, ngx_log_t *log);
++
++static ngx_int_t ngx_quic_bpf_export_maps(ngx_cycle_t *cycle);
++static ngx_int_t ngx_quic_bpf_import_maps(ngx_cycle_t *cycle);
++
++extern ngx_bpf_program_t ngx_quic_reuseport_helper;
++
++
++static ngx_command_t ngx_quic_bpf_commands[] = {
++
++ { ngx_string("quic_bpf"),
++ NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_FLAG,
++ ngx_conf_set_flag_slot,
++ 0,
++ offsetof(ngx_quic_bpf_conf_t, enabled),
++ NULL },
++
++ ngx_null_command
++};
++
++
++static ngx_core_module_t ngx_quic_bpf_module_ctx = {
++ ngx_string("quic_bpf"),
++ ngx_quic_bpf_create_conf,
++ NULL
++};
++
++
++ngx_module_t ngx_quic_bpf_module = {
++ NGX_MODULE_V1,
++ &ngx_quic_bpf_module_ctx, /* module context */
++ ngx_quic_bpf_commands, /* module directives */
++ NGX_CORE_MODULE, /* module type */
++ NULL, /* init master */
++ ngx_quic_bpf_module_init, /* init module */
++ NULL, /* init process */
++ NULL, /* init thread */
++ NULL, /* exit thread */
++ NULL, /* exit process */
++ NULL, /* exit master */
++ NGX_MODULE_V1_PADDING
++};
++
++
++static void *
++ngx_quic_bpf_create_conf(ngx_cycle_t *cycle)
++{
++ ngx_quic_bpf_conf_t *bcf;
++
++ bcf = ngx_pcalloc(cycle->pool, sizeof(ngx_quic_bpf_conf_t));
++ if (bcf == NULL) {
++ return NULL;
++ }
++
++ bcf->enabled = NGX_CONF_UNSET;
++ bcf->map_size = NGX_CONF_UNSET_UINT;
++
++ ngx_queue_init(&bcf->groups);
++
++ return bcf;
++}
++
++
++static ngx_int_t
++ngx_quic_bpf_module_init(ngx_cycle_t *cycle)
++{
++ ngx_uint_t i;
++ ngx_listening_t *ls;
++ ngx_core_conf_t *ccf;
++ ngx_pool_cleanup_t *cln;
++ ngx_quic_bpf_conf_t *bcf;
++
++ if (ngx_test_config) {
++ /*
++ * during config test, SO_REUSEPORT socket option is
++ * not set, thus making further processing meaningless
++ */
++ return NGX_OK;
++ }
++
++ ccf = ngx_core_get_conf(cycle);
++ bcf = ngx_quic_bpf_get_conf(cycle);
++
++ ngx_conf_init_value(bcf->enabled, 0);
++
++ bcf->map_size = ccf->worker_processes * 4;
++
++ cln = ngx_pool_cleanup_add(cycle->pool, 0);
++ if (cln == NULL) {
++ goto failed;
++ }
++
++ cln->data = bcf;
++ cln->handler = ngx_quic_bpf_cleanup;
++
++ if (ngx_inherited && ngx_is_init_cycle(cycle->old_cycle)) {
++ if (ngx_quic_bpf_import_maps(cycle) != NGX_OK) {
++ goto failed;
++ }
++ }
++
++ ls = cycle->listening.elts;
++
++ for (i = 0; i < cycle->listening.nelts; i++) {
++ if (ls[i].quic && ls[i].reuseport) {
++ if (ngx_quic_bpf_group_add_socket(cycle, &ls[i]) != NGX_OK) {
++ goto failed;
++ }
++ }
++ }
++
++ if (ngx_quic_bpf_export_maps(cycle) != NGX_OK) {
++ goto failed;
++ }
++
++ return NGX_OK;
++
++failed:
++
++ if (ngx_is_init_cycle(cycle->old_cycle)) {
++ ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
++ "ngx_quic_bpf_module failed to initialize, check limits");
++
++ /* refuse to start */
++ return NGX_ERROR;
++ }
++
++ /*
++ * returning error now will lead to master process exiting immediately
++ * leaving worker processes orphaned, what is really unexpected.
++ * Instead, just issue a not about failed initialization and try
++ * to cleanup a bit. Still program can be already loaded to kernel
++ * for some reuseport groups, and there is no way to revert, so
++ * behaviour may be inconsistent.
++ */
++
++ ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
++ "ngx_quic_bpf_module failed to initialize properly, ignored."
++ "please check limits and note that nginx state now "
++ "can be inconsistent and restart may be required");
++
++ return NGX_OK;
++}
++
++
++static void
++ngx_quic_bpf_cleanup(void *data)
++{
++ ngx_quic_bpf_conf_t *bcf = (ngx_quic_bpf_conf_t *) data;
++
++ ngx_queue_t *q;
++ ngx_quic_sock_group_t *grp;
++
++ for (q = ngx_queue_head(&bcf->groups);
++ q != ngx_queue_sentinel(&bcf->groups);
++ q = ngx_queue_next(q))
++ {
++ grp = ngx_queue_data(q, ngx_quic_sock_group_t, queue);
++
++ ngx_quic_bpf_close(ngx_cycle->log, grp->map_fd, "map");
++ }
++}
++
++
++static ngx_inline void
++ngx_quic_bpf_close(ngx_log_t *log, int fd, const char *name)
++{
++ if (close(fd) != -1) {
++ return;
++ }
++
++ ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
++ "quic bpf close %s fd:%d failed", name, fd);
++}
++
++
++static ngx_quic_sock_group_t *
++ngx_quic_bpf_find_group(ngx_quic_bpf_conf_t *bcf, ngx_listening_t *ls)
++{
++ ngx_queue_t *q;
++ ngx_quic_sock_group_t *grp;
++
++ for (q = ngx_queue_head(&bcf->groups);
++ q != ngx_queue_sentinel(&bcf->groups);
++ q = ngx_queue_next(q))
++ {
++ grp = ngx_queue_data(q, ngx_quic_sock_group_t, queue);
++
++ if (ngx_cmp_sockaddr(ls->sockaddr, ls->socklen,
++ grp->sockaddr, grp->socklen, 1)
++ == NGX_OK)
++ {
++ return grp;
++ }
++ }
++
++ return NULL;
++}
++
++
++static ngx_quic_sock_group_t *
++ngx_quic_bpf_alloc_group(ngx_cycle_t *cycle, struct sockaddr *sa,
++ socklen_t socklen)
++{
++ ngx_quic_bpf_conf_t *bcf;
++ ngx_quic_sock_group_t *grp;
++
++ bcf = ngx_quic_bpf_get_conf(cycle);
++
++ grp = ngx_pcalloc(cycle->pool, sizeof(ngx_quic_sock_group_t));
++ if (grp == NULL) {
++ return NULL;
++ }
++
++ grp->socklen = socklen;
++ grp->sockaddr = ngx_palloc(cycle->pool, socklen);
++ if (grp->sockaddr == NULL) {
++ return NULL;
++ }
++ ngx_memcpy(grp->sockaddr, sa, socklen);
++
++ ngx_queue_insert_tail(&bcf->groups, &grp->queue);
++
++ return grp;
++}
++
++
++static ngx_quic_sock_group_t *
++ngx_quic_bpf_create_group(ngx_cycle_t *cycle, ngx_listening_t *ls)
++{
++ int progfd, failed, flags, rc;
++ ngx_quic_bpf_conf_t *bcf;
++ ngx_quic_sock_group_t *grp;
++
++ bcf = ngx_quic_bpf_get_conf(cycle);
++
++ if (!bcf->enabled) {
++ return NULL;
++ }
++
++ grp = ngx_quic_bpf_alloc_group(cycle, ls->sockaddr, ls->socklen);
++ if (grp == NULL) {
++ return NULL;
++ }
++
++ grp->map_fd = ngx_bpf_map_create(cycle->log, BPF_MAP_TYPE_SOCKHASH,
++ sizeof(uint64_t), sizeof(uint64_t),
++ bcf->map_size, 0);
++ if (grp->map_fd == -1) {
++ goto failed;
++ }
++
++ flags = fcntl(grp->map_fd, F_GETFD);
++ if (flags == -1) {
++ ngx_log_error(NGX_LOG_EMERG, cycle->log, errno,
++ "quic bpf getfd failed");
++ goto failed;
++ }
++
++ /* need to inherit map during binary upgrade after exec */
++ flags &= ~FD_CLOEXEC;
++
++ rc = fcntl(grp->map_fd, F_SETFD, flags);
++ if (rc == -1) {
++ ngx_log_error(NGX_LOG_EMERG, cycle->log, errno,
++ "quic bpf setfd failed");
++ goto failed;
++ }
++
++ ngx_bpf_program_link(&ngx_quic_reuseport_helper,
++ "ngx_quic_sockmap", grp->map_fd);
++
++ progfd = ngx_bpf_load_program(cycle->log, &ngx_quic_reuseport_helper);
++ if (progfd < 0) {
++ goto failed;
++ }
++
++ failed = 0;
++
++ if (setsockopt(ls->fd, SOL_SOCKET, SO_ATTACH_REUSEPORT_EBPF,
++ &progfd, sizeof(int))
++ == -1)
++ {
++ ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
++ "quic bpf setsockopt(SO_ATTACH_REUSEPORT_EBPF) failed");
++ failed = 1;
++ }
++
++ ngx_quic_bpf_close(cycle->log, progfd, "program");
++
++ if (failed) {
++ goto failed;
++ }
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
++ "quic bpf sockmap created fd:%d", grp->map_fd);
++ return grp;
++
++failed:
++
++ if (grp->map_fd != -1) {
++ ngx_quic_bpf_close(cycle->log, grp->map_fd, "map");
++ }
++
++ ngx_queue_remove(&grp->queue);
++
++ return NULL;
++}
++
++
++static ngx_quic_sock_group_t *
++ngx_quic_bpf_get_group(ngx_cycle_t *cycle, ngx_listening_t *ls)
++{
++ ngx_quic_bpf_conf_t *bcf, *old_bcf;
++ ngx_quic_sock_group_t *grp, *ogrp;
++
++ bcf = ngx_quic_bpf_get_conf(cycle);
++
++ grp = ngx_quic_bpf_find_group(bcf, ls);
++ if (grp) {
++ return grp;
++ }
++
++ old_bcf = ngx_quic_bpf_get_old_conf(cycle);
++
++ if (old_bcf == NULL) {
++ return ngx_quic_bpf_create_group(cycle, ls);
++ }
++
++ ogrp = ngx_quic_bpf_find_group(old_bcf, ls);
++ if (ogrp == NULL) {
++ return ngx_quic_bpf_create_group(cycle, ls);
++ }
++
++ grp = ngx_quic_bpf_alloc_group(cycle, ls->sockaddr, ls->socklen);
++ if (grp == NULL) {
++ return NULL;
++ }
++
++ grp->map_fd = dup(ogrp->map_fd);
++ if (grp->map_fd == -1) {
++ ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
++ "quic bpf failed to duplicate bpf map descriptor");
++
++ ngx_queue_remove(&grp->queue);
++
++ return NULL;
++ }
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
++ "quic bpf sockmap fd duplicated old:%d new:%d",
++ ogrp->map_fd, grp->map_fd);
++
++ return grp;
++}
++
++
++static ngx_int_t
++ngx_quic_bpf_group_add_socket(ngx_cycle_t *cycle, ngx_listening_t *ls)
++{
++ uint64_t cookie;
++ ngx_quic_bpf_conf_t *bcf;
++ ngx_quic_sock_group_t *grp;
++
++ bcf = ngx_quic_bpf_get_conf(cycle);
++
++ grp = ngx_quic_bpf_get_group(cycle, ls);
++
++ if (grp == NULL) {
++ if (!bcf->enabled) {
++ return NGX_OK;
++ }
++
++ return NGX_ERROR;
++ }
++
++ grp->unused = 0;
++
++ cookie = ngx_quic_bpf_socket_key(ls->fd, cycle->log);
++ if (cookie == (uint64_t) NGX_ERROR) {
++ return NGX_ERROR;
++ }
++
++ /* map[cookie] = socket; for use in kernel helper */
++ if (ngx_bpf_map_update(grp->map_fd, &cookie, &ls->fd, BPF_ANY) == -1) {
++ ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
++ "quic bpf failed to update socket map key=%xL", cookie);
++ return NGX_ERROR;
++ }
++
++ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
++ "quic bpf sockmap fd:%d add socket:%d cookie:0x%xL worker:%ui",
++ grp->map_fd, ls->fd, cookie, ls->worker);
++
++ /* do not inherit this socket */
++ ls->ignore = 1;
++
++ return NGX_OK;
++}
++
++
++static uint64_t
++ngx_quic_bpf_socket_key(ngx_fd_t fd, ngx_log_t *log)
++{
++ uint64_t cookie;
++ socklen_t optlen;
++
++ optlen = sizeof(cookie);
++
++ if (getsockopt(fd, SOL_SOCKET, SO_COOKIE, &cookie, &optlen) == -1) {
++ ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
++ "quic bpf getsockopt(SO_COOKIE) failed");
++
++ return (ngx_uint_t) NGX_ERROR;
++ }
++
++ return cookie;
++}
++
++
++static ngx_int_t
++ngx_quic_bpf_export_maps(ngx_cycle_t *cycle)
++{
++ u_char *p, *buf;
++ size_t len;
++ ngx_str_t *var;
++ ngx_queue_t *q;
++ ngx_core_conf_t *ccf;
++ ngx_quic_bpf_conf_t *bcf;
++ ngx_quic_sock_group_t *grp;
++
++ ccf = ngx_core_get_conf(cycle);
++ bcf = ngx_quic_bpf_get_conf(cycle);
++
++ len = sizeof(NGX_QUIC_BPF_VARNAME) + 1;
++
++ q = ngx_queue_head(&bcf->groups);
++
++ while (q != ngx_queue_sentinel(&bcf->groups)) {
++
++ grp = ngx_queue_data(q, ngx_quic_sock_group_t, queue);
++
++ q = ngx_queue_next(q);
++
++ if (grp->unused) {
++ /*
++ * map was inherited, but it is not used in this configuration;
++ * do not pass such map further and drop the group to prevent
++ * interference with changes during reload
++ */
++
++ ngx_quic_bpf_close(cycle->log, grp->map_fd, "map");
++ ngx_queue_remove(&grp->queue);
++
++ continue;
++ }
++
++ len += NGX_INT32_LEN + 1 + NGX_SOCKADDR_STRLEN + 1;
++ }
++
++ len++;
++
++ buf = ngx_palloc(cycle->pool, len);
++ if (buf == NULL) {
++ return NGX_ERROR;
++ }
++
++ p = ngx_cpymem(buf, NGX_QUIC_BPF_VARNAME "=",
++ sizeof(NGX_QUIC_BPF_VARNAME));
++
++ for (q = ngx_queue_head(&bcf->groups);
++ q != ngx_queue_sentinel(&bcf->groups);
++ q = ngx_queue_next(q))
++ {
++ grp = ngx_queue_data(q, ngx_quic_sock_group_t, queue);
++
++ p = ngx_sprintf(p, "%ud", grp->map_fd);
++
++ *p++ = NGX_QUIC_BPF_ADDRSEP;
++
++ p += ngx_sock_ntop(grp->sockaddr, grp->socklen, p,
++ NGX_SOCKADDR_STRLEN, 1);
++
++ *p++ = NGX_QUIC_BPF_VARSEP;
++ }
++
++ *p = '\0';
++
++ var = ngx_array_push(&ccf->env);
++ if (var == NULL) {
++ return NGX_ERROR;
++ }
++
++ var->data = buf;
++ var->len = sizeof(NGX_QUIC_BPF_VARNAME) - 1;
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_bpf_import_maps(ngx_cycle_t *cycle)
++{
++ int s;
++ u_char *inherited, *p, *v;
++ ngx_uint_t in_fd;
++ ngx_addr_t tmp;
++ ngx_quic_bpf_conf_t *bcf;
++ ngx_quic_sock_group_t *grp;
++
++ inherited = (u_char *) getenv(NGX_QUIC_BPF_VARNAME);
++
++ if (inherited == NULL) {
++ return NGX_OK;
++ }
++
++ bcf = ngx_quic_bpf_get_conf(cycle);
++
++#if (NGX_SUPPRESS_WARN)
++ s = -1;
++#endif
++
++ in_fd = 1;
++
++ for (p = inherited, v = p; *p; p++) {
++
++ switch (*p) {
++
++ case NGX_QUIC_BPF_ADDRSEP:
++
++ if (!in_fd) {
++ ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
++ "quic bpf failed to parse inherited env");
++ return NGX_ERROR;
++ }
++ in_fd = 0;
++
++ s = ngx_atoi(v, p - v);
++ if (s == NGX_ERROR) {
++ ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
++ "quic bpf failed to parse inherited map fd");
++ return NGX_ERROR;
++ }
++
++ v = p + 1;
++ break;
++
++ case NGX_QUIC_BPF_VARSEP:
++
++ if (in_fd) {
++ ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
++ "quic bpf failed to parse inherited env");
++ return NGX_ERROR;
++ }
++ in_fd = 1;
++
++ grp = ngx_pcalloc(cycle->pool,
++ sizeof(ngx_quic_sock_group_t));
++ if (grp == NULL) {
++ return NGX_ERROR;
++ }
++
++ grp->map_fd = s;
++
++ if (ngx_parse_addr_port(cycle->pool, &tmp, v, p - v)
++ != NGX_OK)
++ {
++ ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
++ "quic bpf failed to parse inherited"
++ " address '%*s'", p - v , v);
++
++ ngx_quic_bpf_close(cycle->log, s, "inherited map");
++
++ return NGX_ERROR;
++ }
++
++ grp->sockaddr = tmp.sockaddr;
++ grp->socklen = tmp.socklen;
++
++ grp->unused = 1;
++
++ ngx_queue_insert_tail(&bcf->groups, &grp->queue);
++
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
++ "quic bpf sockmap inherited with "
++ "fd:%d address:%*s",
++ grp->map_fd, p - v, v);
++ v = p + 1;
++ break;
++
++ default:
++ break;
++ }
++ }
++
++ return NGX_OK;
++}
+diff -r 67408b4a12c0 src/event/quic/ngx_event_quic_bpf_code.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_bpf_code.c Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,88 @@
++/* AUTO-GENERATED, DO NOT EDIT. */
++
++#include <stddef.h>
++#include <stdint.h>
++
++#include "ngx_bpf.h"
++
++
++static ngx_bpf_reloc_t bpf_reloc_prog_ngx_quic_reuseport_helper[] = {
++ { "ngx_quic_sockmap", 55 },
++};
++
++static struct bpf_insn bpf_insn_prog_ngx_quic_reuseport_helper[] = {
++ /* opcode dst src offset imm */
++ { 0x79, BPF_REG_4, BPF_REG_1, (int16_t) 0, 0x0 },
++ { 0x79, BPF_REG_3, BPF_REG_1, (int16_t) 8, 0x0 },
++ { 0xbf, BPF_REG_2, BPF_REG_4, (int16_t) 0, 0x0 },
++ { 0x7, BPF_REG_2, BPF_REG_0, (int16_t) 0, 0x8 },
++ { 0x2d, BPF_REG_2, BPF_REG_3, (int16_t) 54, 0x0 },
++ { 0xbf, BPF_REG_5, BPF_REG_4, (int16_t) 0, 0x0 },
++ { 0x7, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0x9 },
++ { 0x2d, BPF_REG_5, BPF_REG_3, (int16_t) 51, 0x0 },
++ { 0xb7, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0x14 },
++ { 0xb7, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x9 },
++ { 0x71, BPF_REG_6, BPF_REG_2, (int16_t) 0, 0x0 },
++ { 0x67, BPF_REG_6, BPF_REG_0, (int16_t) 0, 0x38 },
++ { 0xc7, BPF_REG_6, BPF_REG_0, (int16_t) 0, 0x38 },
++ { 0x65, BPF_REG_6, BPF_REG_0, (int16_t) 10, 0xffffffff },
++ { 0xbf, BPF_REG_2, BPF_REG_4, (int16_t) 0, 0x0 },
++ { 0x7, BPF_REG_2, BPF_REG_0, (int16_t) 0, 0xd },
++ { 0x2d, BPF_REG_2, BPF_REG_3, (int16_t) 42, 0x0 },
++ { 0xbf, BPF_REG_5, BPF_REG_4, (int16_t) 0, 0x0 },
++ { 0x7, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0xe },
++ { 0x2d, BPF_REG_5, BPF_REG_3, (int16_t) 39, 0x0 },
++ { 0xb7, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0xe },
++ { 0x71, BPF_REG_5, BPF_REG_2, (int16_t) 0, 0x0 },
++ { 0xb7, BPF_REG_6, BPF_REG_0, (int16_t) 0, 0x8 },
++ { 0x2d, BPF_REG_6, BPF_REG_5, (int16_t) 35, 0x0 },
++ { 0xf, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0x0 },
++ { 0xf, BPF_REG_4, BPF_REG_5, (int16_t) 0, 0x0 },
++ { 0x2d, BPF_REG_4, BPF_REG_3, (int16_t) 32, 0x0 },
++ { 0xbf, BPF_REG_4, BPF_REG_2, (int16_t) 0, 0x0 },
++ { 0x7, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x9 },
++ { 0x2d, BPF_REG_4, BPF_REG_3, (int16_t) 29, 0x0 },
++ { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 1, 0x0 },
++ { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x38 },
++ { 0x71, BPF_REG_3, BPF_REG_2, (int16_t) 2, 0x0 },
++ { 0x67, BPF_REG_3, BPF_REG_0, (int16_t) 0, 0x30 },
++ { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 },
++ { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 3, 0x0 },
++ { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x28 },
++ { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 },
++ { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 4, 0x0 },
++ { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x20 },
++ { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 },
++ { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 5, 0x0 },
++ { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x18 },
++ { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 },
++ { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 6, 0x0 },
++ { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x10 },
++ { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 },
++ { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 7, 0x0 },
++ { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x8 },
++ { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 },
++ { 0x71, BPF_REG_2, BPF_REG_2, (int16_t) 8, 0x0 },
++ { 0x4f, BPF_REG_3, BPF_REG_2, (int16_t) 0, 0x0 },
++ { 0x7b, BPF_REG_10, BPF_REG_3, (int16_t) 65528, 0x0 },
++ { 0xbf, BPF_REG_3, BPF_REG_10, (int16_t) 0, 0x0 },
++ { 0x7, BPF_REG_3, BPF_REG_0, (int16_t) 0, 0xfffffff8 },
++ { 0x18, BPF_REG_2, BPF_REG_0, (int16_t) 0, 0x0 },
++ { 0x0, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x0 },
++ { 0xb7, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x0 },
++ { 0x85, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x52 },
++ { 0xb7, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x1 },
++ { 0x95, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x0 },
++};
++
++
++ngx_bpf_program_t ngx_quic_reuseport_helper = {
++ .relocs = bpf_reloc_prog_ngx_quic_reuseport_helper,
++ .nrelocs = sizeof(bpf_reloc_prog_ngx_quic_reuseport_helper)
++ / sizeof(bpf_reloc_prog_ngx_quic_reuseport_helper[0]),
++ .ins = bpf_insn_prog_ngx_quic_reuseport_helper,
++ .nins = sizeof(bpf_insn_prog_ngx_quic_reuseport_helper)
++ / sizeof(bpf_insn_prog_ngx_quic_reuseport_helper[0]),
++ .license = "BSD",
++ .type = BPF_PROG_TYPE_SK_REUSEPORT,
++};
+diff -r 67408b4a12c0 src/event/quic/ngx_event_quic_connection.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_connection.h Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,274 @@
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#ifndef _NGX_EVENT_QUIC_CONNECTION_H_INCLUDED_
++#define _NGX_EVENT_QUIC_CONNECTION_H_INCLUDED_
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++#include <ngx_event.h>
++
++
++/* #define NGX_QUIC_DEBUG_PACKETS */ /* dump packet contents */
++/* #define NGX_QUIC_DEBUG_FRAMES */ /* dump frames contents */
++/* #define NGX_QUIC_DEBUG_ALLOC */ /* log frames and bufs alloc */
++/* #define NGX_QUIC_DEBUG_CRYPTO */
++
++typedef struct ngx_quic_connection_s ngx_quic_connection_t;
++typedef struct ngx_quic_server_id_s ngx_quic_server_id_t;
++typedef struct ngx_quic_client_id_s ngx_quic_client_id_t;
++typedef struct ngx_quic_send_ctx_s ngx_quic_send_ctx_t;
++typedef struct ngx_quic_socket_s ngx_quic_socket_t;
++typedef struct ngx_quic_path_s ngx_quic_path_t;
++typedef struct ngx_quic_keys_s ngx_quic_keys_t;
++
++#include <ngx_event_quic_transport.h>
++#include <ngx_event_quic_protection.h>
++#include <ngx_event_quic_frames.h>
++#include <ngx_event_quic_migration.h>
++#include <ngx_event_quic_connid.h>
++#include <ngx_event_quic_streams.h>
++#include <ngx_event_quic_ssl.h>
++#include <ngx_event_quic_tokens.h>
++#include <ngx_event_quic_ack.h>
++#include <ngx_event_quic_output.h>
++#include <ngx_event_quic_socket.h>
++
++
++/* RFC 9002, 6.2.2. Handshakes and New Paths: kInitialRtt */
++#define NGX_QUIC_INITIAL_RTT 333 /* ms */
++
++#define NGX_QUIC_UNSET_PN (uint64_t) -1
++
++#define NGX_QUIC_SEND_CTX_LAST (NGX_QUIC_ENCRYPTION_LAST - 1)
++
++/* 0-RTT and 1-RTT data exist in the same packet number space,
++ * so we have 3 packet number spaces:
++ *
++ * 0 - Initial
++ * 1 - Handshake
++ * 2 - 0-RTT and 1-RTT
++ */
++#define ngx_quic_get_send_ctx(qc, level) \
++ ((level) == ssl_encryption_initial) ? &((qc)->send_ctx[0]) \
++ : (((level) == ssl_encryption_handshake) ? &((qc)->send_ctx[1]) \
++ : &((qc)->send_ctx[2]))
++
++#define ngx_quic_get_connection(c) \
++ (((c)->udp) ? (((ngx_quic_socket_t *)((c)->udp))->quic) : NULL)
++
++#define ngx_quic_get_socket(c) ((ngx_quic_socket_t *)((c)->udp))
++
++
++struct ngx_quic_client_id_s {
++ ngx_queue_t queue;
++ uint64_t seqnum;
++ size_t len;
++ u_char id[NGX_QUIC_CID_LEN_MAX];
++ u_char sr_token[NGX_QUIC_SR_TOKEN_LEN];
++ ngx_uint_t refcnt;
++};
++
++
++struct ngx_quic_server_id_s {
++ uint64_t seqnum;
++ size_t len;
++ u_char id[NGX_QUIC_CID_LEN_MAX];
++};
++
++
++struct ngx_quic_path_s {
++ ngx_queue_t queue;
++ struct sockaddr *sockaddr;
++ socklen_t socklen;
++ ngx_uint_t state;
++ ngx_uint_t limited; /* unsigned limited:1; */
++ ngx_msec_t expires;
++ ngx_msec_t last_seen;
++ ngx_uint_t tries;
++ off_t sent;
++ off_t received;
++ u_char challenge1[8];
++ u_char challenge2[8];
++ ngx_uint_t refcnt;
++ uint64_t seqnum;
++ ngx_str_t addr_text;
++ u_char text[NGX_SOCKADDR_STRLEN];
++};
++
++
++struct ngx_quic_socket_s {
++ ngx_udp_connection_t udp;
++ ngx_quic_connection_t *quic;
++ ngx_queue_t queue;
++
++ ngx_quic_server_id_t sid;
++
++ ngx_quic_path_t *path;
++ ngx_quic_client_id_t *cid;
++};
++
++
++typedef struct {
++ ngx_rbtree_t tree;
++ ngx_rbtree_node_t sentinel;
++ ngx_queue_t uninitialized;
++
++ uint64_t sent;
++ uint64_t recv_offset;
++ uint64_t recv_window;
++ uint64_t recv_last;
++ uint64_t recv_max_data;
++ uint64_t send_max_data;
++
++ uint64_t server_max_streams_uni;
++ uint64_t server_max_streams_bidi;
++ uint64_t server_streams_uni;
++ uint64_t server_streams_bidi;
++
++ uint64_t client_max_streams_uni;
++ uint64_t client_max_streams_bidi;
++ uint64_t client_streams_uni;
++ uint64_t client_streams_bidi;
++
++ ngx_uint_t initialized;
++ /* unsigned initialized:1; */
++} ngx_quic_streams_t;
++
++
++typedef struct {
++ size_t in_flight;
++ size_t window;
++ size_t ssthresh;
++ ngx_msec_t recovery_start;
++} ngx_quic_congestion_t;
++
++
++/*
++ * RFC 9000, 12.3. Packet Numbers
++ *
++ * Conceptually, a packet number space is the context in which a packet
++ * can be processed and acknowledged. Initial packets can only be sent
++ * with Initial packet protection keys and acknowledged in packets that
++ * are also Initial packets.
++ */
++struct ngx_quic_send_ctx_s {
++ enum ssl_encryption_level_t level;
++
++ ngx_chain_t *crypto;
++ uint64_t crypto_received;
++ uint64_t crypto_sent;
++
++ uint64_t pnum; /* to be sent */
++ uint64_t largest_ack; /* received from peer */
++ uint64_t largest_pn; /* received from peer */
++
++ ngx_queue_t frames; /* generated frames */
++ ngx_queue_t sending; /* frames assigned to pkt */
++ ngx_queue_t sent; /* frames waiting ACK */
++
++ uint64_t pending_ack; /* non sent ack-eliciting */
++ uint64_t largest_range;
++ uint64_t first_range;
++ ngx_msec_t largest_received;
++ ngx_msec_t ack_delay_start;
++ ngx_uint_t nranges;
++ ngx_quic_ack_range_t ranges[NGX_QUIC_MAX_RANGES];
++ ngx_uint_t send_ack;
++};
++
++
++struct ngx_quic_connection_s {
++ uint32_t version;
++
++ ngx_quic_socket_t *socket;
++ ngx_quic_socket_t *backup;
++
++ ngx_queue_t sockets;
++ ngx_queue_t paths;
++ ngx_queue_t client_ids;
++ ngx_queue_t free_sockets;
++ ngx_queue_t free_paths;
++ ngx_queue_t free_client_ids;
++
++ ngx_uint_t nsockets;
++ ngx_uint_t nclient_ids;
++ uint64_t max_retired_seqnum;
++ uint64_t client_seqnum;
++ uint64_t server_seqnum;
++ uint64_t path_seqnum;
++
++ ngx_quic_tp_t tp;
++ ngx_quic_tp_t ctp;
++
++ ngx_quic_send_ctx_t send_ctx[NGX_QUIC_SEND_CTX_LAST];
++
++ ngx_quic_keys_t *keys;
++
++ ngx_quic_conf_t *conf;
++
++ ngx_event_t push;
++ ngx_event_t pto;
++ ngx_event_t close;
++ ngx_event_t path_validation;
++ ngx_msec_t last_cc;
++
++ ngx_msec_t first_rtt;
++ ngx_msec_t latest_rtt;
++ ngx_msec_t avg_rtt;
++ ngx_msec_t min_rtt;
++ ngx_msec_t rttvar;
++
++ ngx_uint_t pto_count;
++
++ ngx_queue_t free_frames;
++ ngx_buf_t *free_bufs;
++ ngx_buf_t *free_shadow_bufs;
++
++ ngx_uint_t nframes;
++#ifdef NGX_QUIC_DEBUG_ALLOC
++ ngx_uint_t nbufs;
++ ngx_uint_t nshadowbufs;
++#endif
++
++ ngx_quic_streams_t streams;
++ ngx_quic_congestion_t congestion;
++
++ off_t received;
++
++ ngx_uint_t error;
++ enum ssl_encryption_level_t error_level;
++ ngx_uint_t error_ftype;
++ const char *error_reason;
++
++ ngx_uint_t shutdown_code;
++ const char *shutdown_reason;
++
++ unsigned error_app:1;
++ unsigned send_timer_set:1;
++ unsigned closing:1;
++ unsigned shutdown:1;
++ unsigned draining:1;
++ unsigned key_phase:1;
++ unsigned validated:1;
++ unsigned client_tp_done:1;
++};
++
++
++ngx_int_t ngx_quic_apply_transport_params(ngx_connection_t *c,
++ ngx_quic_tp_t *ctp);
++void ngx_quic_discard_ctx(ngx_connection_t *c,
++ enum ssl_encryption_level_t level);
++void ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc);
++void ngx_quic_shutdown_quic(ngx_connection_t *c);
++
++#if (NGX_DEBUG)
++void ngx_quic_connstate_dbg(ngx_connection_t *c);
++#else
++#define ngx_quic_connstate_dbg(c)
++#endif
++
++#endif /* _NGX_EVENT_QUIC_CONNECTION_H_INCLUDED_ */
+diff -r 67408b4a12c0 src/event/quic/ngx_event_quic_connid.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_connid.c Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,613 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++#include <ngx_event.h>
++#include <ngx_event_quic_connection.h>
++
++#define NGX_QUIC_MAX_SERVER_IDS 8
++
++
++#if (NGX_QUIC_BPF)
++static ngx_int_t ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id);
++#endif
++static ngx_int_t ngx_quic_send_retire_connection_id(ngx_connection_t *c,
++ uint64_t seqnum);
++
++static ngx_quic_client_id_t *ngx_quic_alloc_client_id(ngx_connection_t *c,
++ ngx_quic_connection_t *qc);
++static ngx_int_t ngx_quic_replace_retired_client_id(ngx_connection_t *c,
++ ngx_quic_client_id_t *retired_cid);
++static ngx_int_t ngx_quic_send_server_id(ngx_connection_t *c,
++ ngx_quic_server_id_t *sid);
++
++
++ngx_int_t
++ngx_quic_create_server_id(ngx_connection_t *c, u_char *id)
++{
++ if (RAND_bytes(id, NGX_QUIC_SERVER_CID_LEN) != 1) {
++ return NGX_ERROR;
++ }
++
++#if (NGX_QUIC_BPF)
++ if (ngx_quic_bpf_attach_id(c, id) != NGX_OK) {
++ ngx_log_error(NGX_LOG_ERR, c->log, 0,
++ "quic bpf failed to generate socket key");
++ /* ignore error, things still may work */
++ }
++#endif
++
++ return NGX_OK;
++}
++
++
++#if (NGX_QUIC_BPF)
++
++static ngx_int_t
++ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id)
++{
++ int fd;
++ uint64_t cookie;
++ socklen_t optlen;
++
++ fd = c->listening->fd;
++
++ optlen = sizeof(cookie);
++
++ if (getsockopt(fd, SOL_SOCKET, SO_COOKIE, &cookie, &optlen) == -1) {
++ ngx_log_error(NGX_LOG_ERR, c->log, ngx_socket_errno,
++ "quic getsockopt(SO_COOKIE) failed");
++
++ return NGX_ERROR;
++ }
++
++ ngx_quic_dcid_encode_key(id, cookie);
++
++ return NGX_OK;
++}
++
++#endif
++
++
++ngx_int_t
++ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c,
++ ngx_quic_new_conn_id_frame_t *f)
++{
++ uint64_t seq;
++ ngx_str_t id;
++ ngx_queue_t *q;
++ ngx_quic_client_id_t *cid, *item;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ if (f->seqnum < qc->max_retired_seqnum) {
++ /*
++ * RFC 9000, 19.15. NEW_CONNECTION_ID Frame
++ *
++ * An endpoint that receives a NEW_CONNECTION_ID frame with
++ * a sequence number smaller than the Retire Prior To field
++ * of a previously received NEW_CONNECTION_ID frame MUST send
++ * a corresponding RETIRE_CONNECTION_ID frame that retires
++ * the newly received connection ID, unless it has already
++ * done so for that sequence number.
++ */
++
++ if (ngx_quic_send_retire_connection_id(c, f->seqnum) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ goto retire;
++ }
++
++ cid = NULL;
++
++ for (q = ngx_queue_head(&qc->client_ids);
++ q != ngx_queue_sentinel(&qc->client_ids);
++ q = ngx_queue_next(q))
++ {
++ item = ngx_queue_data(q, ngx_quic_client_id_t, queue);
++
++ if (item->seqnum == f->seqnum) {
++ cid = item;
++ break;
++ }
++ }
++
++ if (cid) {
++ /*
++ * Transmission errors, timeouts, and retransmissions might cause the
++ * same NEW_CONNECTION_ID frame to be received multiple times.
++ */
++
++ if (cid->len != f->len
++ || ngx_strncmp(cid->id, f->cid, f->len) != 0
++ || ngx_strncmp(cid->sr_token, f->srt, NGX_QUIC_SR_TOKEN_LEN) != 0)
++ {
++ /*
++ * ..if a sequence number is used for different connection IDs,
++ * the endpoint MAY treat that receipt as a connection error
++ * of type PROTOCOL_VIOLATION.
++ */
++ qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION;
++ qc->error_reason = "seqnum refers to different connection id/token";
++ return NGX_ERROR;
++ }
++
++ } else {
++
++ id.data = f->cid;
++ id.len = f->len;
++
++ if (ngx_quic_create_client_id(c, &id, f->seqnum, f->srt) == NULL) {
++ return NGX_ERROR;
++ }
++ }
++
++retire:
++
++ if (qc->max_retired_seqnum && f->retire <= qc->max_retired_seqnum) {
++ /*
++ * Once a sender indicates a Retire Prior To value, smaller values sent
++ * in subsequent NEW_CONNECTION_ID frames have no effect. A receiver
++ * MUST ignore any Retire Prior To fields that do not increase the
++ * largest received Retire Prior To value.
++ */
++ goto done;
++ }
++
++ qc->max_retired_seqnum = f->retire;
++
++ q = ngx_queue_head(&qc->client_ids);
++
++ while (q != ngx_queue_sentinel(&qc->client_ids)) {
++
++ cid = ngx_queue_data(q, ngx_quic_client_id_t, queue);
++ q = ngx_queue_next(q);
++
++ if (cid->seqnum >= f->retire) {
++ continue;
++ }
++
++ /* this connection id must be retired */
++ seq = cid->seqnum;
++
++ if (cid->refcnt) {
++ /* we are going to retire client id which is in use */
++ if (ngx_quic_replace_retired_client_id(c, cid) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ } else {
++ ngx_quic_unref_client_id(c, cid);
++ }
++
++ if (ngx_quic_send_retire_connection_id(c, seq) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++
++done:
++
++ if (qc->nclient_ids > qc->tp.active_connection_id_limit) {
++ /*
++ * RFC 9000, 5.1.1. Issuing Connection IDs
++ *
++ * After processing a NEW_CONNECTION_ID frame and
++ * adding and retiring active connection IDs, if the number of active
++ * connection IDs exceeds the value advertised in its
++ * active_connection_id_limit transport parameter, an endpoint MUST
++ * close the connection with an error of type CONNECTION_ID_LIMIT_ERROR.
++ */
++ qc->error = NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR;
++ qc->error_reason = "too many connection ids received";
++ return NGX_ERROR;
++ }
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_send_retire_connection_id(ngx_connection_t *c, uint64_t seqnum)
++{
++ ngx_quic_frame_t *frame;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ frame = ngx_quic_alloc_frame(c);
++ if (frame == NULL) {
++ return NGX_ERROR;
++ }
++
++ frame->level = ssl_encryption_application;
++ frame->type = NGX_QUIC_FT_RETIRE_CONNECTION_ID;
++ frame->u.retire_cid.sequence_number = seqnum;
++
++ ngx_quic_queue_frame(qc, frame);
++
++ /* we are no longer going to use this client id */
++
++ return NGX_OK;
++}
++
++
++static ngx_quic_client_id_t *
++ngx_quic_alloc_client_id(ngx_connection_t *c, ngx_quic_connection_t *qc)
++{
++ ngx_queue_t *q;
++ ngx_quic_client_id_t *cid;
++
++ if (!ngx_queue_empty(&qc->free_client_ids)) {
++
++ q = ngx_queue_head(&qc->free_client_ids);
++ cid = ngx_queue_data(q, ngx_quic_client_id_t, queue);
++
++ ngx_queue_remove(&cid->queue);
++
++ ngx_memzero(cid, sizeof(ngx_quic_client_id_t));
++
++ } else {
++
++ cid = ngx_pcalloc(c->pool, sizeof(ngx_quic_client_id_t));
++ if (cid == NULL) {
++ return NULL;
++ }
++ }
++
++ return cid;
++}
++
++
++ngx_quic_client_id_t *
++ngx_quic_create_client_id(ngx_connection_t *c, ngx_str_t *id,
++ uint64_t seqnum, u_char *token)
++{
++ ngx_quic_client_id_t *cid;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ cid = ngx_quic_alloc_client_id(c, qc);
++ if (cid == NULL) {
++ return NULL;
++ }
++
++ cid->seqnum = seqnum;
++
++ cid->len = id->len;
++ ngx_memcpy(cid->id, id->data, id->len);
++
++ if (token) {
++ ngx_memcpy(cid->sr_token, token, NGX_QUIC_SR_TOKEN_LEN);
++ }
++
++ ngx_queue_insert_tail(&qc->client_ids, &cid->queue);
++ qc->nclient_ids++;
++
++ if (seqnum > qc->client_seqnum) {
++ qc->client_seqnum = seqnum;
++ }
++
++ ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic cid #%uL received id:%uz:%xV:%*xs",
++ cid->seqnum, id->len, id,
++ (size_t) NGX_QUIC_SR_TOKEN_LEN, cid->sr_token);
++
++ return cid;
++}
++
++
++ngx_quic_client_id_t *
++ngx_quic_next_client_id(ngx_connection_t *c)
++{
++ ngx_queue_t *q;
++ ngx_quic_client_id_t *cid;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ for (q = ngx_queue_head(&qc->client_ids);
++ q != ngx_queue_sentinel(&qc->client_ids);
++ q = ngx_queue_next(q))
++ {
++ cid = ngx_queue_data(q, ngx_quic_client_id_t, queue);
++
++ if (cid->refcnt == 0) {
++ return cid;
++ }
++ }
++
++ return NULL;
++}
++
++
++ngx_quic_client_id_t *
++ngx_quic_used_client_id(ngx_connection_t *c, ngx_quic_path_t *path)
++{
++ ngx_queue_t *q;
++ ngx_quic_socket_t *qsock;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ /* best guess: cid used by active path is good for us */
++ if (qc->socket->path == path) {
++ return qc->socket->cid;
++ }
++
++ for (q = ngx_queue_head(&qc->sockets);
++ q != ngx_queue_sentinel(&qc->sockets);
++ q = ngx_queue_next(q))
++ {
++ qsock = ngx_queue_data(q, ngx_quic_socket_t, queue);
++
++ if (qsock->path && qsock->path == path) {
++ return qsock->cid;
++ }
++ }
++
++ return NULL;
++}
++
++
++ngx_int_t
++ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c,
++ ngx_quic_retire_cid_frame_t *f)
++{
++ ngx_quic_path_t *path;
++ ngx_quic_socket_t *qsock, **tmp;
++ ngx_quic_client_id_t *cid;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ if (f->sequence_number >= qc->server_seqnum) {
++ /*
++ * RFC 9000, 19.16.
++ *
++ * Receipt of a RETIRE_CONNECTION_ID frame containing a sequence
++ * number greater than any previously sent to the peer MUST be
++ * treated as a connection error of type PROTOCOL_VIOLATION.
++ */
++ qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION;
++ qc->error_reason = "sequence number of id to retire was never issued";
++
++ return NGX_ERROR;
++ }
++
++ qsock = ngx_quic_get_socket(c);
++
++ if (qsock->sid.seqnum == f->sequence_number) {
++
++ /*
++ * RFC 9000, 19.16.
++ *
++ * The sequence number specified in a RETIRE_CONNECTION_ID frame MUST
++ * NOT refer to the Destination Connection ID field of the packet in
++ * which the frame is contained. The peer MAY treat this as a
++ * connection error of type PROTOCOL_VIOLATION.
++ */
++
++ qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION;
++ qc->error_reason = "sequence number of id to retire refers DCID";
++
++ return NGX_ERROR;
++ }
++
++ qsock = ngx_quic_find_socket(c, f->sequence_number);
++ if (qsock == NULL) {
++ return NGX_OK;
++ }
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic socket #%uL is retired", qsock->sid.seqnum);
++
++ /* check if client is willing to retire sid we have in use */
++ if (qsock->sid.seqnum == qc->socket->sid.seqnum) {
++ tmp = &qc->socket;
++
++ } else if (qc->backup && qsock->sid.seqnum == qc->backup->sid.seqnum) {
++ tmp = &qc->backup;
++
++ } else {
++
++ ngx_quic_close_socket(c, qsock);
++
++ /* restore socket count up to a limit after deletion */
++ if (ngx_quic_create_sockets(c) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ return NGX_OK;
++ }
++
++ /* preserve path/cid from retired socket */
++ path = qsock->path;
++ cid = qsock->cid;
++
++ /* ensure that closing_socket will not drop path and cid */
++ path->refcnt++;
++ cid->refcnt++;
++
++ ngx_quic_close_socket(c, qsock);
++
++ /* restore original values */
++ path->refcnt--;
++ cid->refcnt--;
++
++ /* restore socket count up to a limit after deletion */
++ if (ngx_quic_create_sockets(c) != NGX_OK) {
++ goto failed;
++ }
++
++ qsock = ngx_quic_get_unconnected_socket(c);
++ if (qsock == NULL) {
++ qc->error = NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR;
++ qc->error_reason = "not enough server IDs";
++ goto failed;
++ }
++
++ ngx_quic_connect(c, qsock, path, cid);
++
++ ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic %s socket is now #%uL:%uL:%uL (%s)",
++ (*tmp) == qc->socket ? "active" : "backup",
++ qsock->sid.seqnum, qsock->cid->seqnum,
++ qsock->path->seqnum,
++ ngx_quic_path_state_str(qsock->path));
++
++ /* restore active/backup pointer in quic connection */
++ *tmp = qsock;
++
++ return NGX_OK;
++
++failed:
++
++ /*
++ * socket was closed, path and cid were preserved artifically
++ * to be reused, but it didn't happen, thus unref here
++ */
++
++ ngx_quic_unref_path(c, path);
++ ngx_quic_unref_client_id(c, cid);
++
++ return NGX_ERROR;
++}
++
++
++ngx_int_t
++ngx_quic_create_sockets(ngx_connection_t *c)
++{
++ ngx_uint_t n;
++ ngx_quic_socket_t *qsock;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ n = ngx_min(NGX_QUIC_MAX_SERVER_IDS, qc->ctp.active_connection_id_limit);
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic create sockets has:%ui max:%ui", qc->nsockets, n);
++
++ while (qc->nsockets < n) {
++
++ qsock = ngx_quic_create_socket(c, qc);
++ if (qsock == NULL) {
++ return NGX_ERROR;
++ }
++
++ if (ngx_quic_listen(c, qc, qsock) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ if (ngx_quic_send_server_id(c, &qsock->sid) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_send_server_id(ngx_connection_t *c, ngx_quic_server_id_t *sid)
++{
++ ngx_str_t dcid;
++ ngx_quic_frame_t *frame;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ dcid.len = sid->len;
++ dcid.data = sid->id;
++
++ frame = ngx_quic_alloc_frame(c);
++ if (frame == NULL) {
++ return NGX_ERROR;
++ }
++
++ frame->level = ssl_encryption_application;
++ frame->type = NGX_QUIC_FT_NEW_CONNECTION_ID;
++ frame->u.ncid.seqnum = sid->seqnum;
++ frame->u.ncid.retire = 0;
++ frame->u.ncid.len = NGX_QUIC_SERVER_CID_LEN;
++ ngx_memcpy(frame->u.ncid.cid, sid->id, NGX_QUIC_SERVER_CID_LEN);
++
++ if (ngx_quic_new_sr_token(c, &dcid, qc->conf->sr_token_key,
++ frame->u.ncid.srt)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ ngx_quic_queue_frame(qc, frame);
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_replace_retired_client_id(ngx_connection_t *c,
++ ngx_quic_client_id_t *retired_cid)
++{
++ ngx_queue_t *q;
++ ngx_quic_socket_t *qsock;
++ ngx_quic_client_id_t *cid;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ for (q = ngx_queue_head(&qc->sockets);
++ q != ngx_queue_sentinel(&qc->sockets);
++ q = ngx_queue_next(q))
++ {
++ qsock = ngx_queue_data(q, ngx_quic_socket_t, queue);
++
++ if (qsock->cid == retired_cid) {
++
++ cid = ngx_quic_next_client_id(c);
++ if (cid == NULL) {
++ return NGX_ERROR;
++ }
++
++ qsock->cid = cid;
++ cid->refcnt++;
++
++ ngx_quic_unref_client_id(c, retired_cid);
++
++ if (retired_cid->refcnt == 0) {
++ return NGX_OK;
++ }
++ }
++ }
++
++ return NGX_OK;
++}
++
++
++void
++ngx_quic_unref_client_id(ngx_connection_t *c, ngx_quic_client_id_t *cid)
++{
++ ngx_quic_connection_t *qc;
++
++ if (cid->refcnt) {
++ cid->refcnt--;
++ } /* else: unused client id */
++
++ if (cid->refcnt) {
++ return;
++ }
++
++ qc = ngx_quic_get_connection(c);
++
++ ngx_queue_remove(&cid->queue);
++ ngx_queue_insert_head(&qc->free_client_ids, &cid->queue);
++
++ qc->nclient_ids--;
++}
+diff -r 67408b4a12c0 src/event/quic/ngx_event_quic_connid.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_connid.h Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,30 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#ifndef _NGX_EVENT_QUIC_CONNID_H_INCLUDED_
++#define _NGX_EVENT_QUIC_CONNID_H_INCLUDED_
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++
++
++ngx_int_t ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c,
++ ngx_quic_retire_cid_frame_t *f);
++ngx_int_t ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c,
++ ngx_quic_new_conn_id_frame_t *f);
++
++ngx_int_t ngx_quic_create_sockets(ngx_connection_t *c);
++ngx_int_t ngx_quic_create_server_id(ngx_connection_t *c, u_char *id);
++
++ngx_quic_client_id_t *ngx_quic_create_client_id(ngx_connection_t *c,
++ ngx_str_t *id, uint64_t seqnum, u_char *token);
++ngx_quic_client_id_t *ngx_quic_next_client_id(ngx_connection_t *c);
++ngx_quic_client_id_t *ngx_quic_used_client_id(ngx_connection_t *c,
++ ngx_quic_path_t *path);
++void ngx_quic_unref_client_id(ngx_connection_t *c, ngx_quic_client_id_t *cid);
++
++#endif /* _NGX_EVENT_QUIC_CONNID_H_INCLUDED_ */
+diff -r 67408b4a12c0 src/event/quic/ngx_event_quic_frames.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_frames.c Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,800 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++#include <ngx_event.h>
++#include <ngx_event_quic_connection.h>
++
++
++#define NGX_QUIC_BUFFER_SIZE 4096
++
++#define ngx_quic_buf_refs(b) (b)->shadow->num
++#define ngx_quic_buf_inc_refs(b) ngx_quic_buf_refs(b)++
++#define ngx_quic_buf_dec_refs(b) ngx_quic_buf_refs(b)--
++#define ngx_quic_buf_set_refs(b, v) ngx_quic_buf_refs(b) = v
++
++
++static ngx_buf_t *ngx_quic_alloc_buf(ngx_connection_t *c);
++static void ngx_quic_free_buf(ngx_connection_t *c, ngx_buf_t *b);
++static ngx_buf_t *ngx_quic_clone_buf(ngx_connection_t *c, ngx_buf_t *b);
++
++
++static ngx_buf_t *
++ngx_quic_alloc_buf(ngx_connection_t *c)
++{
++ u_char *p;
++ ngx_buf_t *b;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ b = qc->free_bufs;
++
++ if (b) {
++ qc->free_bufs = b->shadow;
++ p = b->start;
++
++ } else {
++ b = qc->free_shadow_bufs;
++
++ if (b) {
++ qc->free_shadow_bufs = b->shadow;
++
++#ifdef NGX_QUIC_DEBUG_ALLOC
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic use shadow buffer n:%ui %ui",
++ ++qc->nbufs, --qc->nshadowbufs);
++#endif
++
++ } else {
++ b = ngx_palloc(c->pool, sizeof(ngx_buf_t));
++ if (b == NULL) {
++ return NULL;
++ }
++
++#ifdef NGX_QUIC_DEBUG_ALLOC
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic new buffer n:%ui", ++qc->nbufs);
++#endif
++ }
++
++ p = ngx_pnalloc(c->pool, NGX_QUIC_BUFFER_SIZE);
++ if (p == NULL) {
++ return NULL;
++ }
++ }
++
++#ifdef NGX_QUIC_DEBUG_ALLOC
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic alloc buffer %p", b);
++#endif
++
++ ngx_memzero(b, sizeof(ngx_buf_t));
++
++ b->tag = (ngx_buf_tag_t) &ngx_quic_alloc_buf;
++ b->temporary = 1;
++ b->shadow = b;
++
++ b->start = p;
++ b->pos = p;
++ b->last = p;
++ b->end = p + NGX_QUIC_BUFFER_SIZE;
++
++ ngx_quic_buf_set_refs(b, 1);
++
++ return b;
++}
++
++
++static void
++ngx_quic_free_buf(ngx_connection_t *c, ngx_buf_t *b)
++{
++ ngx_buf_t *shadow;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ ngx_quic_buf_dec_refs(b);
++
++#ifdef NGX_QUIC_DEBUG_ALLOC
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic free buffer %p r:%ui",
++ b, (ngx_uint_t) ngx_quic_buf_refs(b));
++#endif
++
++ shadow = b->shadow;
++
++ if (ngx_quic_buf_refs(b) == 0) {
++ shadow->shadow = qc->free_bufs;
++ qc->free_bufs = shadow;
++ }
++
++ if (b != shadow) {
++ b->shadow = qc->free_shadow_bufs;
++ qc->free_shadow_bufs = b;
++ }
++
++}
++
++
++static ngx_buf_t *
++ngx_quic_clone_buf(ngx_connection_t *c, ngx_buf_t *b)
++{
++ ngx_buf_t *nb;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ nb = qc->free_shadow_bufs;
++
++ if (nb) {
++ qc->free_shadow_bufs = nb->shadow;
++
++ } else {
++ nb = ngx_palloc(c->pool, sizeof(ngx_buf_t));
++ if (nb == NULL) {
++ return NULL;
++ }
++
++#ifdef NGX_QUIC_DEBUG_ALLOC
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic new shadow buffer n:%ui", ++qc->nshadowbufs);
++#endif
++ }
++
++ *nb = *b;
++
++ ngx_quic_buf_inc_refs(b);
++
++#ifdef NGX_QUIC_DEBUG_ALLOC
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic clone buffer %p %p r:%ui",
++ b, nb, (ngx_uint_t) ngx_quic_buf_refs(b));
++#endif
++
++ return nb;
++}
++
++
++ngx_quic_frame_t *
++ngx_quic_alloc_frame(ngx_connection_t *c)
++{
++ ngx_queue_t *q;
++ ngx_quic_frame_t *frame;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ if (!ngx_queue_empty(&qc->free_frames)) {
++
++ q = ngx_queue_head(&qc->free_frames);
++ frame = ngx_queue_data(q, ngx_quic_frame_t, queue);
++
++ ngx_queue_remove(&frame->queue);
++
++#ifdef NGX_QUIC_DEBUG_ALLOC
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic reuse frame n:%ui", qc->nframes);
++#endif
++
++ } else if (qc->nframes < 10000) {
++ frame = ngx_palloc(c->pool, sizeof(ngx_quic_frame_t));
++ if (frame == NULL) {
++ return NULL;
++ }
++
++ ++qc->nframes;
++
++#ifdef NGX_QUIC_DEBUG_ALLOC
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic alloc frame n:%ui", qc->nframes);
++#endif
++
++ } else {
++ ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic flood detected");
++ return NULL;
++ }
++
++ ngx_memzero(frame, sizeof(ngx_quic_frame_t));
++
++ return frame;
++}
++
++
++void
++ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame)
++{
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ if (frame->data) {
++ ngx_quic_free_chain(c, frame->data);
++ }
++
++ ngx_queue_insert_head(&qc->free_frames, &frame->queue);
++
++#ifdef NGX_QUIC_DEBUG_ALLOC
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic free frame n:%ui", qc->nframes);
++#endif
++}
++
++
++void
++ngx_quic_trim_chain(ngx_chain_t *in, size_t size)
++{
++ size_t n;
++ ngx_buf_t *b;
++
++ while (in && size > 0) {
++ b = in->buf;
++ n = ngx_min((size_t) (b->last - b->pos), size);
++
++ b->pos += n;
++ size -= n;
++
++ if (b->pos == b->last) {
++ in = in->next;
++ }
++ }
++}
++
++
++void
++ngx_quic_free_chain(ngx_connection_t *c, ngx_chain_t *in)
++{
++ ngx_chain_t *cl;
++
++ while (in) {
++ cl = in;
++ in = in->next;
++
++ ngx_quic_free_buf(c, cl->buf);
++ ngx_free_chain(c->pool, cl);
++ }
++}
++
++
++void
++ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames)
++{
++ ngx_queue_t *q;
++ ngx_quic_frame_t *f;
++
++ do {
++ q = ngx_queue_head(frames);
++
++ if (q == ngx_queue_sentinel(frames)) {
++ break;
++ }
++
++ ngx_queue_remove(q);
++
++ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
++
++ ngx_quic_free_frame(c, f);
++ } while (1);
++}
++
++
++void
++ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame)
++{
++ ngx_quic_send_ctx_t *ctx;
++
++ ctx = ngx_quic_get_send_ctx(qc, frame->level);
++
++ ngx_queue_insert_tail(&ctx->frames, &frame->queue);
++
++ frame->len = ngx_quic_create_frame(NULL, frame);
++ /* always succeeds */
++
++ if (qc->closing) {
++ return;
++ }
++
++ ngx_post_event(&qc->push, &ngx_posted_events);
++}
++
++
++ngx_int_t
++ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, size_t len)
++{
++ size_t shrink;
++ ngx_quic_frame_t *nf;
++ ngx_quic_ordered_frame_t *of, *onf;
++
++ switch (f->type) {
++ case NGX_QUIC_FT_CRYPTO:
++ case NGX_QUIC_FT_STREAM:
++ break;
++
++ default:
++ return NGX_DECLINED;
++ }
++
++ if ((size_t) f->len <= len) {
++ return NGX_OK;
++ }
++
++ shrink = f->len - len;
++
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic split frame now:%uz need:%uz shrink:%uz",
++ f->len, len, shrink);
++
++ of = &f->u.ord;
++
++ if (of->length <= shrink) {
++ return NGX_DECLINED;
++ }
++
++ of->length -= shrink;
++ f->len = ngx_quic_create_frame(NULL, f);
++
++ if ((size_t) f->len > len) {
++ ngx_log_error(NGX_LOG_ERR, c->log, 0, "could not split QUIC frame");
++ return NGX_ERROR;
++ }
++
++ nf = ngx_quic_alloc_frame(c);
++ if (nf == NULL) {
++ return NGX_ERROR;
++ }
++
++ *nf = *f;
++ onf = &nf->u.ord;
++ onf->offset += of->length;
++ onf->length = shrink;
++ nf->len = ngx_quic_create_frame(NULL, nf);
++
++ f->data = ngx_quic_read_chain(c, &nf->data, of->length);
++ if (f->data == NGX_CHAIN_ERROR) {
++ return NGX_ERROR;
++ }
++
++ ngx_queue_insert_after(&f->queue, &nf->queue);
++
++ return NGX_OK;
++}
++
++
++ngx_chain_t *
++ngx_quic_read_chain(ngx_connection_t *c, ngx_chain_t **chain, off_t limit)
++{
++ off_t n;
++ ngx_buf_t *b;
++ ngx_chain_t *out, *cl, **ll;
++
++ out = *chain;
++
++ for (ll = &out; *ll; ll = &(*ll)->next) {
++ b = (*ll)->buf;
++
++ if (b->sync) {
++ /* hole */
++ break;
++ }
++
++ if (limit == 0) {
++ break;
++ }
++
++ n = b->last - b->pos;
++
++ if (n > limit) {
++ goto split;
++ }
++
++ limit -= n;
++ }
++
++ *chain = *ll;
++ *ll = NULL;
++
++ return out;
++
++split:
++
++ cl = ngx_alloc_chain_link(c->pool);
++ if (cl == NULL) {
++ return NGX_CHAIN_ERROR;
++ }
++
++ cl->buf = ngx_quic_clone_buf(c, b);
++ if (cl->buf == NULL) {
++ return NGX_CHAIN_ERROR;
++ }
++
++ cl->buf->pos += limit;
++ b->last = cl->buf->pos;
++ b->last_buf = 0;
++
++ ll = &(*ll)->next;
++ cl->next = *ll;
++ *ll = NULL;
++ *chain = cl;
++
++ return out;
++}
++
++
++ngx_chain_t *
++ngx_quic_alloc_chain(ngx_connection_t *c)
++{
++ ngx_chain_t *cl;
++
++ cl = ngx_alloc_chain_link(c->pool);
++ if (cl == NULL) {
++ return NULL;
++ }
++
++ cl->buf = ngx_quic_alloc_buf(c);
++ if (cl->buf == NULL) {
++ return NULL;
++ }
++
++ return cl;
++}
++
++
++ngx_chain_t *
++ngx_quic_copy_buf(ngx_connection_t *c, u_char *data, size_t len)
++{
++ size_t n;
++ ngx_buf_t *b;
++ ngx_chain_t *cl, *out, **ll;
++
++ out = NULL;
++ ll = &out;
++
++ while (len) {
++ cl = ngx_quic_alloc_chain(c);
++ if (cl == NULL) {
++ return NGX_CHAIN_ERROR;
++ }
++
++ b = cl->buf;
++ n = ngx_min((size_t) (b->end - b->last), len);
++
++ b->last = ngx_cpymem(b->last, data, n);
++
++ data += n;
++ len -= n;
++
++ *ll = cl;
++ ll = &cl->next;
++ }
++
++ *ll = NULL;
++
++ return out;
++}
++
++
++ngx_chain_t *
++ngx_quic_write_chain(ngx_connection_t *c, ngx_chain_t **chain, ngx_chain_t *in,
++ off_t limit, off_t offset)
++{
++ off_t n;
++ u_char *p;
++ ngx_buf_t *b;
++ ngx_chain_t *cl, *sl;
++
++ while (in && limit) {
++ cl = *chain;
++
++ if (cl == NULL) {
++ cl = ngx_quic_alloc_chain(c);
++ if (cl == NULL) {
++ return NGX_CHAIN_ERROR;
++ }
++
++ cl->buf->last = cl->buf->end;
++ cl->buf->sync = 1; /* hole */
++ cl->next = NULL;
++ *chain = cl;
++ }
++
++ b = cl->buf;
++ n = b->last - b->pos;
++
++ if (n <= offset) {
++ offset -= n;
++ chain = &cl->next;
++ continue;
++ }
++
++ if (b->sync && offset > 0) {
++ /* split hole at offset */
++
++ b->sync = 0;
++
++ sl = ngx_quic_read_chain(c, &cl, offset);
++ if (cl == NGX_CHAIN_ERROR) {
++ return NGX_CHAIN_ERROR;
++ }
++
++ sl->buf->sync = 1;
++ cl->buf->sync = 1;
++
++ *chain = sl;
++ sl->next = cl;
++ continue;
++ }
++
++ for (p = b->pos + offset; p != b->last && in && limit; /* void */ ) {
++ n = ngx_min(b->last - p, in->buf->last - in->buf->pos);
++ n = ngx_min(n, limit);
++
++ if (b->sync) {
++ ngx_memcpy(p, in->buf->pos, n);
++ }
++
++ p += n;
++ in->buf->pos += n;
++ offset += n;
++ limit -= n;
++
++ if (in->buf->pos == in->buf->last) {
++ in = in->next;
++ }
++ }
++
++ if (b->sync && p == b->last) {
++ b->sync = 0;
++ continue;
++ }
++
++ if (b->sync && p != b->pos) {
++ /* split hole at p - b->pos */
++
++ b->sync = 0;
++
++ sl = ngx_quic_read_chain(c, &cl, p - b->pos);
++ if (sl == NGX_CHAIN_ERROR) {
++ return NGX_CHAIN_ERROR;
++ }
++
++ cl->buf->sync = 1;
++
++ *chain = sl;
++ sl->next = cl;
++ }
++ }
++
++ return in;
++}
++
++
++#if (NGX_DEBUG)
++
++void
++ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx)
++{
++ u_char *p, *last, *pos, *end;
++ ssize_t n;
++ uint64_t gap, range, largest, smallest;
++ ngx_uint_t i;
++ u_char buf[NGX_MAX_ERROR_STR];
++
++ p = buf;
++ last = buf + sizeof(buf);
++
++ switch (f->type) {
++
++ case NGX_QUIC_FT_CRYPTO:
++ p = ngx_slprintf(p, last, "CRYPTO len:%uL off:%uL",
++ f->u.crypto.length, f->u.crypto.offset);
++
++#ifdef NGX_QUIC_DEBUG_FRAMES
++ {
++ ngx_chain_t *cl;
++
++ p = ngx_slprintf(p, last, " data:");
++
++ for (cl = f->data; cl; cl = cl->next) {
++ p = ngx_slprintf(p, last, "%*xs",
++ cl->buf->last - cl->buf->pos, cl->buf->pos);
++ }
++ }
++#endif
++
++ break;
++
++ case NGX_QUIC_FT_PADDING:
++ p = ngx_slprintf(p, last, "PADDING");
++ break;
++
++ case NGX_QUIC_FT_ACK:
++ case NGX_QUIC_FT_ACK_ECN:
++
++ p = ngx_slprintf(p, last, "ACK n:%ui delay:%uL ",
++ f->u.ack.range_count, f->u.ack.delay);
++
++ if (f->data) {
++ pos = f->data->buf->pos;
++ end = f->data->buf->last;
++
++ } else {
++ pos = NULL;
++ end = NULL;
++ }
++
++ largest = f->u.ack.largest;
++ smallest = f->u.ack.largest - f->u.ack.first_range;
++
++ if (largest == smallest) {
++ p = ngx_slprintf(p, last, "%uL", largest);
++
++ } else {
++ p = ngx_slprintf(p, last, "%uL-%uL", largest, smallest);
++ }
++
++ for (i = 0; i < f->u.ack.range_count; i++) {
++ n = ngx_quic_parse_ack_range(log, pos, end, &gap, &range);
++ if (n == NGX_ERROR) {
++ break;
++ }
++
++ pos += n;
++
++ largest = smallest - gap - 2;
++ smallest = largest - range;
++
++ if (largest == smallest) {
++ p = ngx_slprintf(p, last, " %uL", largest);
++
++ } else {
++ p = ngx_slprintf(p, last, " %uL-%uL", largest, smallest);
++ }
++ }
++
++ if (f->type == NGX_QUIC_FT_ACK_ECN) {
++ p = ngx_slprintf(p, last, " ECN counters ect0:%uL ect1:%uL ce:%uL",
++ f->u.ack.ect0, f->u.ack.ect1, f->u.ack.ce);
++ }
++ break;
++
++ case NGX_QUIC_FT_PING:
++ p = ngx_slprintf(p, last, "PING");
++ break;
++
++ case NGX_QUIC_FT_NEW_CONNECTION_ID:
++ p = ngx_slprintf(p, last,
++ "NEW_CONNECTION_ID seq:%uL retire:%uL len:%ud",
++ f->u.ncid.seqnum, f->u.ncid.retire, f->u.ncid.len);
++ break;
++
++ case NGX_QUIC_FT_RETIRE_CONNECTION_ID:
++ p = ngx_slprintf(p, last, "RETIRE_CONNECTION_ID seqnum:%uL",
++ f->u.retire_cid.sequence_number);
++ break;
++
++ case NGX_QUIC_FT_CONNECTION_CLOSE:
++ case NGX_QUIC_FT_CONNECTION_CLOSE_APP:
++ p = ngx_slprintf(p, last, "CONNECTION_CLOSE%s err:%ui",
++ f->type == NGX_QUIC_FT_CONNECTION_CLOSE ? "" : "_APP",
++ f->u.close.error_code);
++
++ if (f->u.close.reason.len) {
++ p = ngx_slprintf(p, last, " %V", &f->u.close.reason);
++ }
++
++ if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) {
++ p = ngx_slprintf(p, last, " ft:%ui", f->u.close.frame_type);
++ }
++
++ break;
++
++ case NGX_QUIC_FT_STREAM:
++ p = ngx_slprintf(p, last, "STREAM id:0x%xL", f->u.stream.stream_id);
++
++ if (f->u.stream.off) {
++ p = ngx_slprintf(p, last, " off:%uL", f->u.stream.offset);
++ }
++
++ if (f->u.stream.len) {
++ p = ngx_slprintf(p, last, " len:%uL", f->u.stream.length);
++ }
++
++ if (f->u.stream.fin) {
++ p = ngx_slprintf(p, last, " fin:1");
++ }
++
++#ifdef NGX_QUIC_DEBUG_FRAMES
++ {
++ ngx_chain_t *cl;
++
++ p = ngx_slprintf(p, last, " data:");
++
++ for (cl = f->data; cl; cl = cl->next) {
++ p = ngx_slprintf(p, last, "%*xs",
++ cl->buf->last - cl->buf->pos, cl->buf->pos);
++ }
++ }
++#endif
++
++ break;
++
++ case NGX_QUIC_FT_MAX_DATA:
++ p = ngx_slprintf(p, last, "MAX_DATA max_data:%uL on recv",
++ f->u.max_data.max_data);
++ break;
++
++ case NGX_QUIC_FT_RESET_STREAM:
++ p = ngx_slprintf(p, last, "RESET_STREAM"
++ " id:0x%xL error_code:0x%xL final_size:0x%xL",
++ f->u.reset_stream.id, f->u.reset_stream.error_code,
++ f->u.reset_stream.final_size);
++ break;
++
++ case NGX_QUIC_FT_STOP_SENDING:
++ p = ngx_slprintf(p, last, "STOP_SENDING id:0x%xL err:0x%xL",
++ f->u.stop_sending.id, f->u.stop_sending.error_code);
++ break;
++
++ case NGX_QUIC_FT_STREAMS_BLOCKED:
++ case NGX_QUIC_FT_STREAMS_BLOCKED2:
++ p = ngx_slprintf(p, last, "STREAMS_BLOCKED limit:%uL bidi:%ui",
++ f->u.streams_blocked.limit, f->u.streams_blocked.bidi);
++ break;
++
++ case NGX_QUIC_FT_MAX_STREAMS:
++ case NGX_QUIC_FT_MAX_STREAMS2:
++ p = ngx_slprintf(p, last, "MAX_STREAMS limit:%uL bidi:%ui",
++ f->u.max_streams.limit, f->u.max_streams.bidi);
++ break;
++
++ case NGX_QUIC_FT_MAX_STREAM_DATA:
++ p = ngx_slprintf(p, last, "MAX_STREAM_DATA id:0x%xL limit:%uL",
++ f->u.max_stream_data.id, f->u.max_stream_data.limit);
++ break;
++
++
++ case NGX_QUIC_FT_DATA_BLOCKED:
++ p = ngx_slprintf(p, last, "DATA_BLOCKED limit:%uL",
++ f->u.data_blocked.limit);
++ break;
++
++ case NGX_QUIC_FT_STREAM_DATA_BLOCKED:
++ p = ngx_slprintf(p, last, "STREAM_DATA_BLOCKED id:0x%xL limit:%uL",
++ f->u.stream_data_blocked.id,
++ f->u.stream_data_blocked.limit);
++ break;
++
++ case NGX_QUIC_FT_PATH_CHALLENGE:
++ p = ngx_slprintf(p, last, "PATH_CHALLENGE data:0x%*xs",
++ sizeof(f->u.path_challenge.data),
++ f->u.path_challenge.data);
++ break;
++
++ case NGX_QUIC_FT_PATH_RESPONSE:
++ p = ngx_slprintf(p, last, "PATH_RESPONSE data:0x%*xs",
++ sizeof(f->u.path_challenge.data),
++ f->u.path_challenge.data);
++ break;
++
++ case NGX_QUIC_FT_NEW_TOKEN:
++ p = ngx_slprintf(p, last, "NEW_TOKEN");
++ break;
++
++ case NGX_QUIC_FT_HANDSHAKE_DONE:
++ p = ngx_slprintf(p, last, "HANDSHAKE DONE");
++ break;
++
++ default:
++ p = ngx_slprintf(p, last, "unknown type 0x%xi", f->type);
++ break;
++ }
++
++ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, log, 0, "quic frame %s %s %*s",
++ tx ? "tx" : "rx", ngx_quic_level_name(f->level),
++ p - buf, buf);
++}
++
++#endif
+diff -r 67408b4a12c0 src/event/quic/ngx_event_quic_frames.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_frames.h Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,42 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#ifndef _NGX_EVENT_QUIC_FRAMES_H_INCLUDED_
++#define _NGX_EVENT_QUIC_FRAMES_H_INCLUDED_
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++
++
++typedef ngx_int_t (*ngx_quic_frame_handler_pt)(ngx_connection_t *c,
++ ngx_quic_frame_t *frame, void *data);
++
++
++ngx_quic_frame_t *ngx_quic_alloc_frame(ngx_connection_t *c);
++void ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame);
++void ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames);
++void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame);
++ngx_int_t ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f,
++ size_t len);
++
++ngx_chain_t *ngx_quic_alloc_chain(ngx_connection_t *c);
++ngx_chain_t *ngx_quic_copy_buf(ngx_connection_t *c, u_char *data,
++ size_t len);
++void ngx_quic_trim_chain(ngx_chain_t *in, size_t size);
++void ngx_quic_free_chain(ngx_connection_t *c, ngx_chain_t *in);
++ngx_chain_t *ngx_quic_read_chain(ngx_connection_t *c, ngx_chain_t **chain,
++ off_t limit);
++ngx_chain_t *ngx_quic_write_chain(ngx_connection_t *c, ngx_chain_t **chain,
++ ngx_chain_t *in, off_t limit, off_t offset);
++
++#if (NGX_DEBUG)
++void ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx);
++#else
++#define ngx_quic_log_frame(log, f, tx)
++#endif
++
++#endif /* _NGX_EVENT_QUIC_FRAMES_H_INCLUDED_ */
+diff -r 67408b4a12c0 src/event/quic/ngx_event_quic_migration.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_migration.c Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,689 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++#include <ngx_event.h>
++#include <ngx_event_quic_connection.h>
++
++
++static void ngx_quic_set_connection_path(ngx_connection_t *c,
++ ngx_quic_path_t *path);
++static ngx_int_t ngx_quic_validate_path(ngx_connection_t *c,
++ ngx_quic_path_t *path);
++static ngx_int_t ngx_quic_send_path_challenge(ngx_connection_t *c,
++ ngx_quic_path_t *path);
++static ngx_int_t ngx_quic_path_restore(ngx_connection_t *c);
++static ngx_quic_path_t *ngx_quic_alloc_path(ngx_connection_t *c);
++
++
++ngx_int_t
++ngx_quic_handle_path_challenge_frame(ngx_connection_t *c,
++ ngx_quic_path_challenge_frame_t *f)
++{
++ ngx_quic_path_t *path;
++ ngx_quic_frame_t frame, *fp;
++ ngx_quic_socket_t *qsock;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ ngx_memzero(&frame, sizeof(ngx_quic_frame_t));
++
++ frame.level = ssl_encryption_application;
++ frame.type = NGX_QUIC_FT_PATH_RESPONSE;
++ frame.u.path_response = *f;
++
++ /*
++ * RFC 9000, 8.2.2. Path Validation Responses
++ *
++ * A PATH_RESPONSE frame MUST be sent on the network path where the
++ * PATH_CHALLENGE frame was received.
++ */
++ qsock = ngx_quic_get_socket(c);
++ path = qsock->path;
++
++ /*
++ * An endpoint MUST expand datagrams that contain a PATH_RESPONSE frame
++ * to at least the smallest allowed maximum datagram size of 1200 bytes.
++ */
++ if (ngx_quic_frame_sendto(c, &frame, 1200, path) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ if (qsock == qc->socket) {
++ /*
++ * RFC 9000, 9.3.3. Off-Path Packet Forwarding
++ *
++ * An endpoint that receives a PATH_CHALLENGE on an active path SHOULD
++ * send a non-probing packet in response.
++ */
++
++ fp = ngx_quic_alloc_frame(c);
++ if (fp == NULL) {
++ return NGX_ERROR;
++ }
++
++ fp->level = ssl_encryption_application;
++ fp->type = NGX_QUIC_FT_PING;
++
++ ngx_quic_queue_frame(qc, fp);
++ }
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_handle_path_response_frame(ngx_connection_t *c,
++ ngx_quic_path_challenge_frame_t *f)
++{
++ ngx_uint_t rst;
++ ngx_queue_t *q;
++ ngx_quic_path_t *path, *prev;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ /*
++ * RFC 9000, 8.2.3. Successful Path Validation
++ *
++ * A PATH_RESPONSE frame received on any network path validates the path
++ * on which the PATH_CHALLENGE was sent.
++ */
++
++ for (q = ngx_queue_head(&qc->paths);
++ q != ngx_queue_sentinel(&qc->paths);
++ q = ngx_queue_next(q))
++ {
++ path = ngx_queue_data(q, ngx_quic_path_t, queue);
++
++ if (path->state != NGX_QUIC_PATH_VALIDATING) {
++ continue;
++ }
++
++ if (ngx_memcmp(path->challenge1, f->data, sizeof(f->data)) == 0
++ || ngx_memcmp(path->challenge2, f->data, sizeof(f->data)) == 0)
++ {
++ goto valid;
++ }
++ }
++
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic stale PATH_RESPONSE ignored");
++
++ return NGX_OK;
++
++valid:
++
++ /*
++ * RFC 9000, 9.4. Loss Detection and Congestion Control
++ *
++ * On confirming a peer's ownership of its new address,
++ * an endpoint MUST immediately reset the congestion controller
++ * and round-trip time estimator for the new path to initial values
++ * unless the only change in the peer's address is its port number.
++ */
++
++ rst = 1;
++
++ if (qc->backup) {
++ prev = qc->backup->path;
++
++ if (ngx_cmp_sockaddr(prev->sockaddr, prev->socklen,
++ path->sockaddr, path->socklen, 0)
++ == NGX_OK)
++ {
++ /* address did not change */
++ rst = 0;
++ }
++ }
++
++ if (rst) {
++ ngx_memzero(&qc->congestion, sizeof(ngx_quic_congestion_t));
++
++ qc->congestion.window = ngx_min(10 * qc->tp.max_udp_payload_size,
++ ngx_max(2 * qc->tp.max_udp_payload_size,
++ 14720));
++ qc->congestion.ssthresh = (size_t) -1;
++ qc->congestion.recovery_start = ngx_current_msec;
++ }
++
++ /*
++ * RFC 9000, 9.3. Responding to Connection Migration
++ *
++ * After verifying a new client address, the server SHOULD
++ * send new address validation tokens (Section 8) to the client.
++ */
++
++ if (ngx_quic_send_new_token(c, path) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic path #%uL successfully validated", path->seqnum);
++
++ path->state = NGX_QUIC_PATH_VALIDATED;
++ path->limited = 0;
++
++ return NGX_OK;
++}
++
++
++static ngx_quic_path_t *
++ngx_quic_alloc_path(ngx_connection_t *c)
++{
++ ngx_queue_t *q;
++ struct sockaddr *sa;
++ ngx_quic_path_t *path;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ if (!ngx_queue_empty(&qc->free_paths)) {
++
++ q = ngx_queue_head(&qc->free_paths);
++ path = ngx_queue_data(q, ngx_quic_path_t, queue);
++
++ ngx_queue_remove(&path->queue);
++
++ sa = path->sockaddr;
++ ngx_memzero(path, sizeof(ngx_quic_path_t));
++ path->sockaddr = sa;
++
++ } else {
++
++ path = ngx_pcalloc(c->pool, sizeof(ngx_quic_path_t));
++ if (path == NULL) {
++ return NULL;
++ }
++
++ path->sockaddr = ngx_palloc(c->pool, NGX_SOCKADDRLEN);
++ if (path->sockaddr == NULL) {
++ return NULL;
++ }
++ }
++
++ return path;
++}
++
++
++ngx_quic_path_t *
++ngx_quic_add_path(ngx_connection_t *c, struct sockaddr *sockaddr,
++ socklen_t socklen)
++{
++ ngx_quic_path_t *path;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ path = ngx_quic_alloc_path(c);
++ if (path == NULL) {
++ return NULL;
++ }
++
++ path->state = NGX_QUIC_PATH_NEW;
++ path->limited = 1;
++
++ path->seqnum = qc->path_seqnum++;
++ path->last_seen = ngx_current_msec;
++
++ path->socklen = socklen;
++ ngx_memcpy(path->sockaddr, sockaddr, socklen);
++
++ path->addr_text.data = path->text;
++ path->addr_text.len = ngx_sock_ntop(sockaddr, socklen, path->text,
++ NGX_SOCKADDR_STRLEN, 1);
++
++ ngx_queue_insert_tail(&qc->paths, &path->queue);
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic path #%uL created src:%V",
++ path->seqnum, &path->addr_text);
++
++ return path;
++}
++
++
++ngx_quic_path_t *
++ngx_quic_find_path(ngx_connection_t *c, struct sockaddr *sockaddr,
++ socklen_t socklen)
++{
++ ngx_queue_t *q;
++ ngx_quic_path_t *path;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ for (q = ngx_queue_head(&qc->paths);
++ q != ngx_queue_sentinel(&qc->paths);
++ q = ngx_queue_next(q))
++ {
++ path = ngx_queue_data(q, ngx_quic_path_t, queue);
++
++ if (ngx_cmp_sockaddr(sockaddr, socklen,
++ path->sockaddr, path->socklen, 1)
++ == NGX_OK)
++ {
++ return path;
++ }
++ }
++
++ return NULL;
++}
++
++
++ngx_int_t
++ngx_quic_update_paths(ngx_connection_t *c, ngx_quic_header_t *pkt)
++{
++ off_t len;
++ ngx_quic_path_t *path;
++ ngx_quic_socket_t *qsock;
++ ngx_quic_client_id_t *cid;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++ qsock = ngx_quic_get_socket(c);
++
++ if (c->udp->dgram == NULL) {
++ /* 1st ever packet in connection, path already exists */
++ path = qsock->path;
++ goto update;
++ }
++
++ path = ngx_quic_find_path(c, c->udp->dgram->sockaddr,
++ c->udp->dgram->socklen);
++
++ if (path == NULL) {
++ path = ngx_quic_add_path(c, c->udp->dgram->sockaddr,
++ c->udp->dgram->socklen);
++ if (path == NULL) {
++ return NGX_ERROR;
++ }
++
++ if (qsock->path) {
++ /* NAT rebinding case: packet to same CID, but from new address */
++
++ ngx_quic_unref_path(c, qsock->path);
++
++ qsock->path = path;
++ path->refcnt++;
++
++ goto update;
++ }
++
++ } else if (qsock->path) {
++ goto update;
++ }
++
++ /* prefer unused client IDs if available */
++ cid = ngx_quic_next_client_id(c);
++ if (cid == NULL) {
++
++ /* try to reuse connection ID used on the same path */
++ cid = ngx_quic_used_client_id(c, path);
++ if (cid == NULL) {
++
++ qc->error = NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR;
++ qc->error_reason = "no available client ids for new path";
++
++ ngx_log_error(NGX_LOG_ERR, c->log, 0,
++ "no available client ids for new path");
++
++ return NGX_ERROR;
++ }
++ }
++
++ ngx_quic_connect(c, qsock, path, cid);
++
++update:
++
++ if (path->state != NGX_QUIC_PATH_NEW) {
++ /* force limits/revalidation for paths that were not seen recently */
++ if (ngx_current_msec - path->last_seen > qc->tp.max_idle_timeout) {
++ path->state = NGX_QUIC_PATH_NEW;
++ path->limited = 1;
++ path->sent = 0;
++ path->received = 0;
++ }
++ }
++
++ path->last_seen = ngx_current_msec;
++
++ len = pkt->raw->last - pkt->raw->start;
++
++ /* TODO: this may be too late in some cases;
++ * for example, if error happens during decrypt(), we cannot
++ * send CC, if error happens in 1st packet, due to amplification
++ * limit, because path->received = 0
++ *
++ * should we account garbage as received or only decrypting packets?
++ */
++ path->received += len;
++
++ ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic packet via #%uL:%uL:%uL"
++ " size:%O path recvd:%O sent:%O limited:%ui",
++ qsock->sid.seqnum, qsock->cid->seqnum, path->seqnum,
++ len, path->received, path->sent, path->limited);
++
++ return NGX_OK;
++}
++
++
++static void
++ngx_quic_set_connection_path(ngx_connection_t *c, ngx_quic_path_t *path)
++{
++ size_t len;
++
++ ngx_memcpy(c->sockaddr, path->sockaddr, path->socklen);
++ c->socklen = path->socklen;
++
++ if (c->addr_text.data) {
++ len = ngx_min(c->addr_text.len, path->addr_text.len);
++
++ ngx_memcpy(c->addr_text.data, path->addr_text.data, len);
++ c->addr_text.len = len;
++ }
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic send path set to #%uL addr:%V",
++ path->seqnum, &path->addr_text);
++}
++
++
++ngx_int_t
++ngx_quic_handle_migration(ngx_connection_t *c, ngx_quic_header_t *pkt)
++{
++ ngx_quic_path_t *next;
++ ngx_quic_socket_t *qsock;
++ ngx_quic_send_ctx_t *ctx;
++ ngx_quic_connection_t *qc;
++
++ /* got non-probing packet via non-active socket with different path */
++
++ qc = ngx_quic_get_connection(c);
++
++ /* current socket, different from active */
++ qsock = ngx_quic_get_socket(c);
++
++ next = qsock->path; /* going to migrate to this path... */
++
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic migration from #%uL:%uL:%uL (%s)"
++ " to #%uL:%uL:%uL (%s)",
++ qc->socket->sid.seqnum, qc->socket->cid->seqnum,
++ qc->socket->path->seqnum,
++ ngx_quic_path_state_str(qc->socket->path),
++ qsock->sid.seqnum, qsock->cid->seqnum, next->seqnum,
++ ngx_quic_path_state_str(next));
++
++ if (next->state == NGX_QUIC_PATH_NEW) {
++ if (ngx_quic_validate_path(c, qsock->path) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++
++ ctx = ngx_quic_get_send_ctx(qc, pkt->level);
++
++ /*
++ * RFC 9000, 9.3. Responding to Connection Migration
++ *
++ * An endpoint only changes the address to which it sends packets in
++ * response to the highest-numbered non-probing packet.
++ */
++ if (pkt->pn != ctx->largest_pn) {
++ return NGX_OK;
++ }
++
++ /* switching connection to new path */
++
++ ngx_quic_set_connection_path(c, next);
++
++ /*
++ * RFC 9000, 9.5. Privacy Implications of Connection Migration
++ *
++ * An endpoint MUST NOT reuse a connection ID when sending to
++ * more than one destination address.
++ */
++
++ /* preserve valid path we are migrating from */
++ if (qc->socket->path->state == NGX_QUIC_PATH_VALIDATED) {
++
++ if (qc->backup) {
++ ngx_quic_close_socket(c, qc->backup);
++ }
++
++ qc->backup = qc->socket;
++
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic backup socket is now #%uL:%uL:%uL (%s)",
++ qc->backup->sid.seqnum, qc->backup->cid->seqnum,
++ qc->backup->path->seqnum,
++ ngx_quic_path_state_str(qc->backup->path));
++ }
++
++ qc->socket = qsock;
++
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic active socket is now #%uL:%uL:%uL (%s)",
++ qsock->sid.seqnum, qsock->cid->seqnum,
++ qsock->path->seqnum, ngx_quic_path_state_str(qsock->path));
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_validate_path(ngx_connection_t *c, ngx_quic_path_t *path)
++{
++ ngx_msec_t pto;
++ ngx_quic_send_ctx_t *ctx;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic initiated validation of new path #%uL",
++ path->seqnum);
++
++ path->state = NGX_QUIC_PATH_VALIDATING;
++
++ if (RAND_bytes(path->challenge1, 8) != 1) {
++ return NGX_ERROR;
++ }
++
++ if (RAND_bytes(path->challenge2, 8) != 1) {
++ return NGX_ERROR;
++ }
++
++ if (ngx_quic_send_path_challenge(c, path) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application);
++ pto = ngx_quic_pto(c, ctx);
++
++ path->expires = ngx_current_msec + pto;
++ path->tries = NGX_QUIC_PATH_RETRIES;
++
++ if (!qc->path_validation.timer_set) {
++ ngx_add_timer(&qc->path_validation, pto);
++ }
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_send_path_challenge(ngx_connection_t *c, ngx_quic_path_t *path)
++{
++ ngx_quic_frame_t frame;
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic path #%uL send path challenge tries:%ui",
++ path->seqnum, path->tries);
++
++ ngx_memzero(&frame, sizeof(ngx_quic_frame_t));
++
++ frame.level = ssl_encryption_application;
++ frame.type = NGX_QUIC_FT_PATH_CHALLENGE;
++
++ ngx_memcpy(frame.u.path_challenge.data, path->challenge1, 8);
++
++ /*
++ * RFC 9000, 8.2.1. Initiating Path Validation
++ *
++ * An endpoint MUST expand datagrams that contain a PATH_CHALLENGE frame
++ * to at least the smallest allowed maximum datagram size of 1200 bytes,
++ * unless the anti-amplification limit for the path does not permit
++ * sending a datagram of this size.
++ */
++
++ /* same applies to PATH_RESPONSE frames */
++ if (ngx_quic_frame_sendto(c, &frame, 1200, path) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ ngx_memcpy(frame.u.path_challenge.data, path->challenge2, 8);
++
++ if (ngx_quic_frame_sendto(c, &frame, 1200, path) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ return NGX_OK;
++}
++
++
++void
++ngx_quic_path_validation_handler(ngx_event_t *ev)
++{
++ ngx_msec_t now;
++ ngx_queue_t *q;
++ ngx_msec_int_t left, next, pto;
++ ngx_quic_path_t *path;
++ ngx_connection_t *c;
++ ngx_quic_send_ctx_t *ctx;
++ ngx_quic_connection_t *qc;
++
++ c = ev->data;
++ qc = ngx_quic_get_connection(c);
++
++ ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application);
++ pto = ngx_quic_pto(c, ctx);
++
++ next = -1;
++ now = ngx_current_msec;
++
++ for (q = ngx_queue_head(&qc->paths);
++ q != ngx_queue_sentinel(&qc->paths);
++ q = ngx_queue_next(q))
++ {
++ path = ngx_queue_data(q, ngx_quic_path_t, queue);
++
++ if (path->state != NGX_QUIC_PATH_VALIDATING) {
++ continue;
++ }
++
++ left = path->expires - now;
++
++ if (left > 0) {
++
++ if (next == -1 || left < next) {
++ next = path->expires;
++ }
++
++ continue;
++ }
++
++ if (--path->tries) {
++ path->expires = ngx_current_msec + pto;
++
++ if (next == -1 || pto < next) {
++ next = pto;
++ }
++
++ /* retransmit */
++ (void) ngx_quic_send_path_challenge(c, path);
++
++ continue;
++ }
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0,
++ "quic path #%uL validation failed", path->seqnum);
++
++ /* found expired path */
++
++ path->state = NGX_QUIC_PATH_NEW;
++ path->limited = 1;
++
++ /*
++ * RFC 9000, 9.4. Loss Detection and Congestion Control
++ *
++ * If the timer fires before the PATH_RESPONSE is received, the
++ * endpoint might send a new PATH_CHALLENGE and restart the timer for
++ * a longer period of time. This timer SHOULD be set as described in
++ * Section 6.2.1 of [QUIC-RECOVERY] and MUST NOT be more aggressive.
++ */
++
++ if (qc->socket->path != path) {
++ /* the path was not actually used */
++ continue;
++ }
++
++ if (ngx_quic_path_restore(c) != NGX_OK) {
++ qc->error = NGX_QUIC_ERR_NO_VIABLE_PATH;
++ qc->error_reason = "no viable path";
++ ngx_quic_close_connection(c, NGX_ERROR);
++ return;
++ }
++ }
++
++ if (next != -1) {
++ ngx_add_timer(&qc->path_validation, next);
++ }
++}
++
++
++static ngx_int_t
++ngx_quic_path_restore(ngx_connection_t *c)
++{
++ ngx_quic_socket_t *qsock;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ /*
++ * RFC 9000, 9.1. Probing a New Path
++ *
++ * Failure to validate a path does not cause the connection to end
++ *
++ * RFC 9000, 9.3.2. On-Path Address Spoofing
++ *
++ * To protect the connection from failing due to such a spurious
++ * migration, an endpoint MUST revert to using the last validated
++ * peer address when validation of a new peer address fails.
++ */
++
++ if (qc->backup == NULL) {
++ return NGX_ERROR;
++ }
++
++ qc->socket = qc->backup;
++ qc->backup = NULL;
++
++ qsock = qc->socket;
++
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic active socket is restored to #%uL:%uL:%uL"
++ " (%s), no backup",
++ qsock->sid.seqnum, qsock->cid->seqnum, qsock->path->seqnum,
++ ngx_quic_path_state_str(qsock->path));
++
++ ngx_quic_set_connection_path(c, qsock->path);
++
++ return NGX_OK;
++}
+diff -r 67408b4a12c0 src/event/quic/ngx_event_quic_migration.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_migration.h Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,42 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#ifndef _NGX_EVENT_QUIC_MIGRATION_H_INCLUDED_
++#define _NGX_EVENT_QUIC_MIGRATION_H_INCLUDED_
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++
++#define NGX_QUIC_PATH_RETRIES 3
++
++#define NGX_QUIC_PATH_NEW 0
++#define NGX_QUIC_PATH_VALIDATING 1
++#define NGX_QUIC_PATH_VALIDATED 2
++
++
++#define ngx_quic_path_state_str(p) \
++ ((p)->state == NGX_QUIC_PATH_NEW) ? "new" : \
++ (((p)->state == NGX_QUIC_PATH_VALIDATED) ? "validated" : "validating")
++
++
++ngx_int_t ngx_quic_handle_path_challenge_frame(ngx_connection_t *c,
++ ngx_quic_path_challenge_frame_t *f);
++ngx_int_t ngx_quic_handle_path_response_frame(ngx_connection_t *c,
++ ngx_quic_path_challenge_frame_t *f);
++
++ngx_quic_path_t *ngx_quic_find_path(ngx_connection_t *c,
++ struct sockaddr *sockaddr, socklen_t socklen);
++ngx_quic_path_t *ngx_quic_add_path(ngx_connection_t *c,
++ struct sockaddr *sockaddr, socklen_t socklen);
++
++ngx_int_t ngx_quic_update_paths(ngx_connection_t *c, ngx_quic_header_t *pkt);
++ngx_int_t ngx_quic_handle_migration(ngx_connection_t *c,
++ ngx_quic_header_t *pkt);
++
++void ngx_quic_path_validation_handler(ngx_event_t *ev);
++
++#endif /* _NGX_EVENT_QUIC_MIGRATION_H_INCLUDED_ */
+diff -r 67408b4a12c0 src/event/quic/ngx_event_quic_output.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_output.c Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,1270 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++#include <ngx_event.h>
++#include <ngx_event_quic_connection.h>
++
++
++#define NGX_QUIC_MAX_UDP_PAYLOAD_OUT 1252
++#define NGX_QUIC_MAX_UDP_PAYLOAD_OUT6 1232
++
++#define NGX_QUIC_MAX_UDP_SEGMENT_BUF 65487 /* 65K - IPv6 header */
++#define NGX_QUIC_MAX_SEGMENTS 64 /* UDP_MAX_SEGMENTS */
++
++#define NGX_QUIC_RETRY_TOKEN_LIFETIME 3 /* seconds */
++#define NGX_QUIC_NEW_TOKEN_LIFETIME 600 /* seconds */
++#define NGX_QUIC_RETRY_BUFFER_SIZE 256
++ /* 1 flags + 4 version + 3 x (1 + 20) s/o/dcid + itag + token(64) */
++
++/*
++ * RFC 9000, 10.3. Stateless Reset
++ *
++ * Endpoints MUST discard packets that are too small to be valid QUIC
++ * packets. With the set of AEAD functions defined in [QUIC-TLS],
++ * short header packets that are smaller than 21 bytes are never valid.
++ */
++#define NGX_QUIC_MIN_PKT_LEN 21
++
++#define NGX_QUIC_MIN_SR_PACKET 43 /* 5 rand + 16 srt + 22 padding */
++#define NGX_QUIC_MAX_SR_PACKET 1200
++
++#define NGX_QUIC_CC_MIN_INTERVAL 1000 /* 1s */
++
++#define NGX_QUIC_SOCKET_RETRY_DELAY 10 /* ms, for NGX_AGAIN on write */
++
++
++static ngx_int_t ngx_quic_create_datagrams(ngx_connection_t *c);
++static void ngx_quic_commit_send(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx);
++static void ngx_quic_revert_send(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx,
++ uint64_t pnum);
++#if ((NGX_HAVE_UDP_SEGMENT) && (NGX_HAVE_MSGHDR_MSG_CONTROL))
++static ngx_uint_t ngx_quic_allow_segmentation(ngx_connection_t *c);
++static ngx_int_t ngx_quic_create_segments(ngx_connection_t *c);
++static ssize_t ngx_quic_send_segments(ngx_connection_t *c, u_char *buf,
++ size_t len, struct sockaddr *sockaddr, socklen_t socklen, size_t segment);
++#endif
++static ssize_t ngx_quic_output_packet(ngx_connection_t *c,
++ ngx_quic_send_ctx_t *ctx, u_char *data, size_t max, size_t min);
++static void ngx_quic_init_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx,
++ ngx_quic_header_t *pkt);
++static ngx_uint_t ngx_quic_get_padding_level(ngx_connection_t *c);
++static ssize_t ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len,
++ struct sockaddr *sockaddr, socklen_t socklen);
++static void ngx_quic_set_packet_number(ngx_quic_header_t *pkt,
++ ngx_quic_send_ctx_t *ctx);
++static size_t ngx_quic_path_limit(ngx_connection_t *c, ngx_quic_path_t *path,
++ size_t size);
++
++
++size_t
++ngx_quic_max_udp_payload(ngx_connection_t *c)
++{
++ /* TODO: path MTU discovery */
++
++#if (NGX_HAVE_INET6)
++ if (c->sockaddr->sa_family == AF_INET6) {
++ return NGX_QUIC_MAX_UDP_PAYLOAD_OUT6;
++ }
++#endif
++
++ return NGX_QUIC_MAX_UDP_PAYLOAD_OUT;
++}
++
++
++ngx_int_t
++ngx_quic_output(ngx_connection_t *c)
++{
++ size_t in_flight;
++ ngx_int_t rc;
++ ngx_quic_congestion_t *cg;
++ ngx_quic_connection_t *qc;
++
++ c->log->action = "sending frames";
++
++ qc = ngx_quic_get_connection(c);
++ cg = &qc->congestion;
++
++ in_flight = cg->in_flight;
++
++#if ((NGX_HAVE_UDP_SEGMENT) && (NGX_HAVE_MSGHDR_MSG_CONTROL))
++ if (ngx_quic_allow_segmentation(c)) {
++ rc = ngx_quic_create_segments(c);
++ } else
++#endif
++ {
++ rc = ngx_quic_create_datagrams(c);
++ }
++
++ if (rc != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ if (in_flight != cg->in_flight && !qc->send_timer_set && !qc->closing) {
++ qc->send_timer_set = 1;
++ ngx_add_timer(c->read, qc->tp.max_idle_timeout);
++ }
++
++ ngx_quic_set_lost_timer(c);
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_create_datagrams(ngx_connection_t *c)
++{
++ size_t len, min;
++ ssize_t n;
++ u_char *p;
++ uint64_t preserved_pnum[NGX_QUIC_SEND_CTX_LAST];
++ ngx_uint_t i, pad;
++ ngx_quic_path_t *path;
++ ngx_quic_send_ctx_t *ctx;
++ ngx_quic_congestion_t *cg;
++ ngx_quic_connection_t *qc;
++ static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
++
++ qc = ngx_quic_get_connection(c);
++ cg = &qc->congestion;
++ path = qc->socket->path;
++
++ while (cg->in_flight < cg->window) {
++
++ p = dst;
++
++ len = ngx_min(qc->ctp.max_udp_payload_size,
++ NGX_QUIC_MAX_UDP_PAYLOAD_SIZE);
++
++ len = ngx_quic_path_limit(c, path, len);
++
++ pad = ngx_quic_get_padding_level(c);
++
++ for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) {
++
++ ctx = &qc->send_ctx[i];
++
++ preserved_pnum[i] = ctx->pnum;
++
++ if (ngx_quic_generate_ack(c, ctx) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ min = (i == pad && p - dst < NGX_QUIC_MIN_INITIAL_SIZE)
++ ? NGX_QUIC_MIN_INITIAL_SIZE - (p - dst) : 0;
++
++ if (min > len) {
++ continue;
++ }
++
++ n = ngx_quic_output_packet(c, ctx, p, len, min);
++ if (n == NGX_ERROR) {
++ return NGX_ERROR;
++ }
++
++ p += n;
++ len -= n;
++ }
++
++ len = p - dst;
++ if (len == 0) {
++ break;
++ }
++
++ n = ngx_quic_send(c, dst, len, path->sockaddr, path->socklen);
++
++ if (n == NGX_ERROR) {
++ return NGX_ERROR;
++ }
++
++ if (n == NGX_AGAIN) {
++ for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) {
++ ngx_quic_revert_send(c, &qc->send_ctx[i], preserved_pnum[i]);
++ }
++
++ ngx_add_timer(&qc->push, NGX_QUIC_SOCKET_RETRY_DELAY);
++ break;
++ }
++
++ for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) {
++ ngx_quic_commit_send(c, &qc->send_ctx[i]);
++ }
++
++ path->sent += len;
++ }
++
++ return NGX_OK;
++}
++
++
++static void
++ngx_quic_commit_send(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx)
++{
++ ngx_queue_t *q;
++ ngx_quic_frame_t *f;
++ ngx_quic_congestion_t *cg;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ cg = &qc->congestion;
++
++ while (!ngx_queue_empty(&ctx->sending)) {
++
++ q = ngx_queue_head(&ctx->sending);
++ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
++
++ ngx_queue_remove(q);
++
++ if (f->pkt_need_ack && !qc->closing) {
++ ngx_queue_insert_tail(&ctx->sent, q);
++
++ cg->in_flight += f->plen;
++
++ } else {
++ ngx_quic_free_frame(c, f);
++ }
++ }
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic congestion send if:%uz", cg->in_flight);
++}
++
++
++static void
++ngx_quic_revert_send(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx,
++ uint64_t pnum)
++{
++ ngx_queue_t *q;
++
++ while (!ngx_queue_empty(&ctx->sending)) {
++
++ q = ngx_queue_last(&ctx->sending);
++ ngx_queue_remove(q);
++ ngx_queue_insert_head(&ctx->frames, q);
++ }
++
++ ctx->pnum = pnum;
++}
++
++
++#if ((NGX_HAVE_UDP_SEGMENT) && (NGX_HAVE_MSGHDR_MSG_CONTROL))
++
++static ngx_uint_t
++ngx_quic_allow_segmentation(ngx_connection_t *c)
++{
++ size_t bytes, len;
++ ngx_queue_t *q;
++ ngx_quic_frame_t *f;
++ ngx_quic_send_ctx_t *ctx;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ if (!qc->conf->gso_enabled) {
++ return 0;
++ }
++
++ if (qc->socket->path->limited) {
++ /* don't even try to be faster on non-validated paths */
++ return 0;
++ }
++
++ ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_initial);
++ if (!ngx_queue_empty(&ctx->frames)) {
++ return 0;
++ }
++
++ ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_handshake);
++ if (!ngx_queue_empty(&ctx->frames)) {
++ return 0;
++ }
++
++ ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application);
++
++ bytes = 0;
++
++ len = ngx_min(qc->ctp.max_udp_payload_size,
++ NGX_QUIC_MAX_UDP_SEGMENT_BUF);
++
++ for (q = ngx_queue_head(&ctx->frames);
++ q != ngx_queue_sentinel(&ctx->frames);
++ q = ngx_queue_next(q))
++ {
++ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
++
++ bytes += f->len;
++
++ if (bytes > len * 3) {
++ /* require at least ~3 full packets to batch */
++ return 1;
++ }
++ }
++
++ return 0;
++}
++
++
++static ngx_int_t
++ngx_quic_create_segments(ngx_connection_t *c)
++{
++ size_t len, segsize;
++ ssize_t n;
++ u_char *p, *end;
++ uint64_t preserved_pnum;
++ ngx_uint_t nseg;
++ ngx_quic_path_t *path;
++ ngx_quic_send_ctx_t *ctx;
++ ngx_quic_congestion_t *cg;
++ ngx_quic_connection_t *qc;
++ static u_char dst[NGX_QUIC_MAX_UDP_SEGMENT_BUF];
++
++ qc = ngx_quic_get_connection(c);
++ cg = &qc->congestion;
++ path = qc->socket->path;
++
++ ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application);
++
++ if (ngx_quic_generate_ack(c, ctx) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ segsize = ngx_min(qc->ctp.max_udp_payload_size,
++ NGX_QUIC_MAX_UDP_SEGMENT_BUF);
++ p = dst;
++ end = dst + sizeof(dst);
++
++ nseg = 0;
++
++ preserved_pnum = ctx->pnum;
++
++ for ( ;; ) {
++
++ len = ngx_min(segsize, (size_t) (end - p));
++
++ if (len && cg->in_flight < cg->window) {
++
++ n = ngx_quic_output_packet(c, ctx, p, len, len);
++ if (n == NGX_ERROR) {
++ return NGX_ERROR;
++ }
++
++ if (n) {
++ p += n;
++ nseg++;
++ }
++
++ } else {
++ n = 0;
++ }
++
++ if (p == dst) {
++ break;
++ }
++
++ if (n == 0 || nseg == NGX_QUIC_MAX_SEGMENTS) {
++ n = ngx_quic_send_segments(c, dst, p - dst, path->sockaddr,
++ path->socklen, segsize);
++ if (n == NGX_ERROR) {
++ return NGX_ERROR;
++ }
++
++ if (n == NGX_AGAIN) {
++ ngx_quic_revert_send(c, ctx, preserved_pnum);
++
++ ngx_add_timer(&qc->push, NGX_QUIC_SOCKET_RETRY_DELAY);
++ break;
++ }
++
++ ngx_quic_commit_send(c, ctx);
++
++ path->sent += n;
++
++ p = dst;
++ nseg = 0;
++ preserved_pnum = ctx->pnum;
++ }
++ }
++
++ return NGX_OK;
++}
++
++
++static ssize_t
++ngx_quic_send_segments(ngx_connection_t *c, u_char *buf, size_t len,
++ struct sockaddr *sockaddr, socklen_t socklen, size_t segment)
++{
++ size_t clen;
++ ssize_t n;
++ uint16_t *valp;
++ struct iovec iov;
++ struct msghdr msg;
++ struct cmsghdr *cmsg;
++
++#if defined(NGX_HAVE_ADDRINFO_CMSG)
++ char msg_control[CMSG_SPACE(sizeof(uint16_t))
++ + CMSG_SPACE(sizeof(ngx_addrinfo_t))];
++#else
++ char msg_control[CMSG_SPACE(sizeof(uint16_t))];
++#endif
++
++ ngx_memzero(&msg, sizeof(struct msghdr));
++ ngx_memzero(msg_control, sizeof(msg_control));
++
++ iov.iov_len = len;
++ iov.iov_base = buf;
++
++ msg.msg_iov = &iov;
++ msg.msg_iovlen = 1;
++
++ msg.msg_name = sockaddr;
++ msg.msg_namelen = socklen;
++
++ msg.msg_control = msg_control;
++ msg.msg_controllen = sizeof(msg_control);
++
++ cmsg = CMSG_FIRSTHDR(&msg);
++
++ cmsg->cmsg_level = SOL_UDP;
++ cmsg->cmsg_type = UDP_SEGMENT;
++ cmsg->cmsg_len = CMSG_LEN(sizeof(uint16_t));
++
++ clen = CMSG_SPACE(sizeof(uint16_t));
++
++ valp = (void *) CMSG_DATA(cmsg);
++ *valp = segment;
++
++#if defined(NGX_HAVE_ADDRINFO_CMSG)
++ if (c->listening && c->listening->wildcard && c->local_sockaddr) {
++ cmsg = CMSG_NXTHDR(&msg, cmsg);
++ clen += ngx_set_srcaddr_cmsg(cmsg, c->local_sockaddr);
++ }
++#endif
++
++ msg.msg_controllen = clen;
++
++ n = ngx_sendmsg(c, &msg, 0);
++ if (n < 0) {
++ return n;
++ }
++
++ c->sent += n;
++
++ return n;
++}
++
++#endif
++
++
++
++static ngx_uint_t
++ngx_quic_get_padding_level(ngx_connection_t *c)
++{
++ ngx_queue_t *q;
++ ngx_quic_frame_t *f;
++ ngx_quic_send_ctx_t *ctx;
++ ngx_quic_connection_t *qc;
++
++ /*
++ * RFC 9000, 14.1. Initial Datagram Size
++ *
++ * Similarly, a server MUST expand the payload of all UDP datagrams
++ * carrying ack-eliciting Initial packets to at least the smallest
++ * allowed maximum datagram size of 1200 bytes.
++ */
++
++ qc = ngx_quic_get_connection(c);
++ ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_initial);
++
++ for (q = ngx_queue_head(&ctx->frames);
++ q != ngx_queue_sentinel(&ctx->frames);
++ q = ngx_queue_next(q))
++ {
++ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
++
++ if (f->need_ack) {
++ ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_handshake);
++
++ if (ngx_queue_empty(&ctx->frames)) {
++ return 0;
++ }
++
++ return 1;
++ }
++ }
++
++ return NGX_QUIC_SEND_CTX_LAST;
++}
++
++
++static ssize_t
++ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx,
++ u_char *data, size_t max, size_t min)
++{
++ size_t len, pad, min_payload, max_payload;
++ u_char *p;
++ ssize_t flen;
++ ngx_str_t res;
++ ngx_int_t rc;
++ ngx_uint_t nframes, expand;
++ ngx_msec_t now;
++ ngx_queue_t *q;
++ ngx_quic_frame_t *f;
++ ngx_quic_header_t pkt;
++ static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
++
++ if (ngx_queue_empty(&ctx->frames)) {
++ return 0;
++ }
++
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic output %s packet max:%uz min:%uz",
++ ngx_quic_level_name(ctx->level), max, min);
++
++ ngx_quic_init_packet(c, ctx, &pkt);
++
++ min_payload = ngx_quic_payload_size(&pkt, min);
++ max_payload = ngx_quic_payload_size(&pkt, max);
++
++ /* RFC 9001, 5.4.2. Header Protection Sample */
++ pad = 4 - pkt.num_len;
++ min_payload = ngx_max(min_payload, pad);
++
++ if (min_payload > max_payload) {
++ return 0;
++ }
++
++ now = ngx_current_msec;
++ nframes = 0;
++ p = src;
++ len = 0;
++ expand = 0;
++
++ for (q = ngx_queue_head(&ctx->frames);
++ q != ngx_queue_sentinel(&ctx->frames);
++ q = ngx_queue_next(q))
++ {
++ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
++
++ if (!expand && (f->type == NGX_QUIC_FT_PATH_RESPONSE
++ || f->type == NGX_QUIC_FT_PATH_CHALLENGE))
++ {
++ /*
++ * RFC 9000, 8.2.1. Initiating Path Validation
++ *
++ * An endpoint MUST expand datagrams that contain a
++ * PATH_CHALLENGE frame to at least the smallest allowed
++ * maximum datagram size of 1200 bytes...
++ *
++ * (same applies to PATH_RESPONSE frames)
++ */
++
++ if (max < 1200) {
++ /* expanded packet will not fit */
++ break;
++ }
++
++ if (min < 1200) {
++ min = 1200;
++
++ min_payload = ngx_quic_payload_size(&pkt, min);
++ }
++
++ expand = 1;
++ }
++
++ if (len >= max_payload) {
++ break;
++ }
++
++ if (len + f->len > max_payload) {
++ rc = ngx_quic_split_frame(c, f, max_payload - len);
++
++ if (rc == NGX_ERROR) {
++ return NGX_ERROR;
++ }
++
++ if (rc == NGX_DECLINED) {
++ break;
++ }
++ }
++
++ if (f->need_ack) {
++ pkt.need_ack = 1;
++ }
++
++ ngx_quic_log_frame(c->log, f, 1);
++
++ flen = ngx_quic_create_frame(p, f);
++ if (flen == -1) {
++ return NGX_ERROR;
++ }
++
++ len += flen;
++ p += flen;
++
++ f->pnum = ctx->pnum;
++ f->first = now;
++ f->last = now;
++ f->plen = 0;
++
++ nframes++;
++
++ if (f->flush) {
++ break;
++ }
++ }
++
++ if (nframes == 0) {
++ return 0;
++ }
++
++ if (len < min_payload) {
++ ngx_memset(p, NGX_QUIC_FT_PADDING, min_payload - len);
++ len = min_payload;
++ }
++
++ pkt.payload.data = src;
++ pkt.payload.len = len;
++
++ res.data = data;
++
++ ngx_log_debug6(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic packet tx %s bytes:%ui"
++ " need_ack:%d number:%L encoded nl:%d trunc:0x%xD",
++ ngx_quic_level_name(ctx->level), pkt.payload.len,
++ pkt.need_ack, pkt.number, pkt.num_len, pkt.trunc);
++
++ if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ ctx->pnum++;
++
++ if (pkt.need_ack) {
++ q = ngx_queue_head(&ctx->frames);
++ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
++
++ f->plen = res.len;
++ }
++
++ while (nframes--) {
++ q = ngx_queue_head(&ctx->frames);
++ f = ngx_queue_data(q, ngx_quic_frame_t, queue);
++
++ f->pkt_need_ack = pkt.need_ack;
++
++ ngx_queue_remove(q);
++ ngx_queue_insert_tail(&ctx->sending, q);
++ }
++
++ return res.len;
++}
++
++
++static void
++ngx_quic_init_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx,
++ ngx_quic_header_t *pkt)
++{
++ ngx_quic_socket_t *qsock;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ qsock = qc->socket;
++
++ ngx_memzero(pkt, sizeof(ngx_quic_header_t));
++
++ pkt->flags = NGX_QUIC_PKT_FIXED_BIT;
++
++ if (ctx->level == ssl_encryption_initial) {
++ pkt->flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_INITIAL;
++
++ } else if (ctx->level == ssl_encryption_handshake) {
++ pkt->flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_HANDSHAKE;
++
++ } else {
++ if (qc->key_phase) {
++ pkt->flags |= NGX_QUIC_PKT_KPHASE;
++ }
++ }
++
++ pkt->dcid.data = qsock->cid->id;
++ pkt->dcid.len = qsock->cid->len;
++
++ pkt->scid.data = qsock->sid.id;
++ pkt->scid.len = qsock->sid.len;
++
++ pkt->version = qc->version;
++ pkt->log = c->log;
++ pkt->level = ctx->level;
++
++ pkt->keys = qc->keys;
++
++ ngx_quic_set_packet_number(pkt, ctx);
++}
++
++
++static ssize_t
++ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len,
++ struct sockaddr *sockaddr, socklen_t socklen)
++{
++ ssize_t n;
++ struct iovec iov;
++ struct msghdr msg;
++#if defined(NGX_HAVE_ADDRINFO_CMSG)
++ struct cmsghdr *cmsg;
++ char msg_control[CMSG_SPACE(sizeof(ngx_addrinfo_t))];
++#endif
++
++ ngx_memzero(&msg, sizeof(struct msghdr));
++
++ iov.iov_len = len;
++ iov.iov_base = buf;
++
++ msg.msg_iov = &iov;
++ msg.msg_iovlen = 1;
++
++ msg.msg_name = sockaddr;
++ msg.msg_namelen = socklen;
++
++#if defined(NGX_HAVE_ADDRINFO_CMSG)
++ if (c->listening && c->listening->wildcard && c->local_sockaddr) {
++
++ msg.msg_control = msg_control;
++ msg.msg_controllen = sizeof(msg_control);
++ ngx_memzero(msg_control, sizeof(msg_control));
++
++ cmsg = CMSG_FIRSTHDR(&msg);
++
++ msg.msg_controllen = ngx_set_srcaddr_cmsg(cmsg, c->local_sockaddr);
++ }
++#endif
++
++ n = ngx_sendmsg(c, &msg, 0);
++ if (n < 0) {
++ return n;
++ }
++
++ c->sent += n;
++
++ return n;
++}
++
++
++static void
++ngx_quic_set_packet_number(ngx_quic_header_t *pkt, ngx_quic_send_ctx_t *ctx)
++{
++ uint64_t delta;
++
++ delta = ctx->pnum - ctx->largest_ack;
++ pkt->number = ctx->pnum;
++
++ if (delta <= 0x7F) {
++ pkt->num_len = 1;
++ pkt->trunc = ctx->pnum & 0xff;
++
++ } else if (delta <= 0x7FFF) {
++ pkt->num_len = 2;
++ pkt->flags |= 0x1;
++ pkt->trunc = ctx->pnum & 0xffff;
++
++ } else if (delta <= 0x7FFFFF) {
++ pkt->num_len = 3;
++ pkt->flags |= 0x2;
++ pkt->trunc = ctx->pnum & 0xffffff;
++
++ } else {
++ pkt->num_len = 4;
++ pkt->flags |= 0x3;
++ pkt->trunc = ctx->pnum & 0xffffffff;
++ }
++}
++
++
++ngx_int_t
++ngx_quic_negotiate_version(ngx_connection_t *c, ngx_quic_header_t *inpkt)
++{
++ size_t len;
++ ngx_quic_header_t pkt;
++ static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "sending version negotiation packet");
++
++ pkt.log = c->log;
++ pkt.flags = NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_FIXED_BIT;
++ pkt.dcid = inpkt->scid;
++ pkt.scid = inpkt->dcid;
++
++ len = ngx_quic_create_version_negotiation(&pkt, buf);
++
++#ifdef NGX_QUIC_DEBUG_PACKETS
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic vnego packet to send len:%uz %*xs", len, len, buf);
++#endif
++
++ (void) ngx_quic_send(c, buf, len, c->sockaddr, c->socklen);
++
++ return NGX_ERROR;
++}
++
++
++ngx_int_t
++ngx_quic_send_stateless_reset(ngx_connection_t *c, ngx_quic_conf_t *conf,
++ ngx_quic_header_t *pkt)
++{
++ u_char *token;
++ size_t len, max;
++ uint16_t rndbytes;
++ u_char buf[NGX_QUIC_MAX_SR_PACKET];
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic handle stateless reset output");
++
++ if (pkt->len <= NGX_QUIC_MIN_PKT_LEN) {
++ return NGX_DECLINED;
++ }
++
++ if (pkt->len <= NGX_QUIC_MIN_SR_PACKET) {
++ len = pkt->len - 1;
++
++ } else {
++ max = ngx_min(NGX_QUIC_MAX_SR_PACKET, pkt->len * 3);
++
++ if (RAND_bytes((u_char *) &rndbytes, sizeof(rndbytes)) != 1) {
++ return NGX_ERROR;
++ }
++
++ len = (rndbytes % (max - NGX_QUIC_MIN_SR_PACKET + 1))
++ + NGX_QUIC_MIN_SR_PACKET;
++ }
++
++ if (RAND_bytes(buf, len - NGX_QUIC_SR_TOKEN_LEN) != 1) {
++ return NGX_ERROR;
++ }
++
++ buf[0] &= ~NGX_QUIC_PKT_LONG;
++ buf[0] |= NGX_QUIC_PKT_FIXED_BIT;
++
++ token = &buf[len - NGX_QUIC_SR_TOKEN_LEN];
++
++ if (ngx_quic_new_sr_token(c, &pkt->dcid, conf->sr_token_key, token)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ (void) ngx_quic_send(c, buf, len, c->sockaddr, c->socklen);
++
++ return NGX_DECLINED;
++}
++
++
++ngx_int_t
++ngx_quic_send_cc(ngx_connection_t *c)
++{
++ ngx_quic_frame_t *frame;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ if (qc->draining) {
++ return NGX_OK;
++ }
++
++ if (qc->closing
++ && ngx_current_msec - qc->last_cc < NGX_QUIC_CC_MIN_INTERVAL)
++ {
++ /* dot not send CC too often */
++ return NGX_OK;
++ }
++
++ frame = ngx_quic_alloc_frame(c);
++ if (frame == NULL) {
++ return NGX_ERROR;
++ }
++
++ frame->level = qc->error_level;
++ frame->type = qc->error_app ? NGX_QUIC_FT_CONNECTION_CLOSE_APP
++ : NGX_QUIC_FT_CONNECTION_CLOSE;
++ frame->u.close.error_code = qc->error;
++ frame->u.close.frame_type = qc->error_ftype;
++
++ if (qc->error_reason) {
++ frame->u.close.reason.len = ngx_strlen(qc->error_reason);
++ frame->u.close.reason.data = (u_char *) qc->error_reason;
++ }
++
++ ngx_quic_queue_frame(qc, frame);
++
++ qc->last_cc = ngx_current_msec;
++
++ return ngx_quic_output(c);
++}
++
++
++ngx_int_t
++ngx_quic_send_early_cc(ngx_connection_t *c, ngx_quic_header_t *inpkt,
++ ngx_uint_t err, const char *reason)
++{
++ ssize_t len;
++ ngx_str_t res;
++ ngx_quic_frame_t frame;
++ ngx_quic_header_t pkt;
++
++ static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
++ static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
++
++ ngx_memzero(&frame, sizeof(ngx_quic_frame_t));
++ ngx_memzero(&pkt, sizeof(ngx_quic_header_t));
++
++ frame.level = inpkt->level;
++ frame.type = NGX_QUIC_FT_CONNECTION_CLOSE;
++ frame.u.close.error_code = err;
++
++ frame.u.close.reason.data = (u_char *) reason;
++ frame.u.close.reason.len = ngx_strlen(reason);
++
++ len = ngx_quic_create_frame(NULL, &frame);
++ if (len > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) {
++ return NGX_ERROR;
++ }
++
++ ngx_quic_log_frame(c->log, &frame, 1);
++
++ len = ngx_quic_create_frame(src, &frame);
++ if (len == -1) {
++ return NGX_ERROR;
++ }
++
++ pkt.keys = ngx_quic_keys_new(c->pool);
++ if (pkt.keys == NULL) {
++ return NGX_ERROR;
++ }
++
++ if (ngx_quic_keys_set_initial_secret(c->pool, pkt.keys, &inpkt->dcid,
++ inpkt->version)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ pkt.flags = NGX_QUIC_PKT_FIXED_BIT | NGX_QUIC_PKT_LONG
++ | NGX_QUIC_PKT_INITIAL;
++
++ pkt.num_len = 1;
++ /*
++ * pkt.num = 0;
++ * pkt.trunc = 0;
++ */
++
++ pkt.version = inpkt->version;
++ pkt.log = c->log;
++ pkt.level = inpkt->level;
++ pkt.dcid = inpkt->scid;
++ pkt.scid = inpkt->dcid;
++ pkt.payload.data = src;
++ pkt.payload.len = len;
++
++ res.data = dst;
++
++ if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ if (ngx_quic_send(c, res.data, res.len, c->sockaddr, c->socklen) < 0) {
++ return NGX_ERROR;
++ }
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_send_retry(ngx_connection_t *c, ngx_quic_conf_t *conf,
++ ngx_quic_header_t *inpkt)
++{
++ time_t expires;
++ ssize_t len;
++ ngx_str_t res, token;
++ ngx_quic_header_t pkt;
++
++ u_char buf[NGX_QUIC_RETRY_BUFFER_SIZE];
++ u_char dcid[NGX_QUIC_SERVER_CID_LEN];
++
++ expires = ngx_time() + NGX_QUIC_RETRY_TOKEN_LIFETIME;
++
++ if (ngx_quic_new_token(c, c->sockaddr, c->socklen, conf->av_token_key,
++ &token, &inpkt->dcid, expires, 1)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ ngx_memzero(&pkt, sizeof(ngx_quic_header_t));
++ pkt.flags = NGX_QUIC_PKT_FIXED_BIT | NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_RETRY;
++ pkt.version = inpkt->version;
++ pkt.log = c->log;
++
++ pkt.odcid = inpkt->dcid;
++ pkt.dcid = inpkt->scid;
++
++ /* TODO: generate routable dcid */
++ if (RAND_bytes(dcid, NGX_QUIC_SERVER_CID_LEN) != 1) {
++ return NGX_ERROR;
++ }
++
++ pkt.scid.len = NGX_QUIC_SERVER_CID_LEN;
++ pkt.scid.data = dcid;
++
++ pkt.token = token;
++
++ res.data = buf;
++
++ if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++#ifdef NGX_QUIC_DEBUG_PACKETS
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic packet to send len:%uz %xV", res.len, &res);
++#endif
++
++ len = ngx_quic_send(c, res.data, res.len, c->sockaddr, c->socklen);
++ if (len < 0) {
++ return NGX_ERROR;
++ }
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic retry packet sent to %xV", &pkt.dcid);
++
++ /*
++ * RFC 9000, 17.2.5.1. Sending a Retry Packet
++ *
++ * A server MUST NOT send more than one Retry
++ * packet in response to a single UDP datagram.
++ * NGX_DONE will stop quic_input() from processing further
++ */
++ return NGX_DONE;
++}
++
++
++ngx_int_t
++ngx_quic_send_new_token(ngx_connection_t *c, ngx_quic_path_t *path)
++{
++ time_t expires;
++ ngx_str_t token;
++ ngx_quic_frame_t *frame;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ expires = ngx_time() + NGX_QUIC_NEW_TOKEN_LIFETIME;
++
++ if (ngx_quic_new_token(c, path->sockaddr, path->socklen,
++ qc->conf->av_token_key, &token, NULL, expires, 0)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ frame = ngx_quic_alloc_frame(c);
++ if (frame == NULL) {
++ return NGX_ERROR;
++ }
++
++ frame->level = ssl_encryption_application;
++ frame->type = NGX_QUIC_FT_NEW_TOKEN;
++ frame->u.token.length = token.len;
++ frame->u.token.data = token.data;
++
++ ngx_quic_queue_frame(qc, frame);
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx)
++{
++ size_t len, left;
++ uint64_t ack_delay;
++ ngx_buf_t *b;
++ ngx_uint_t i;
++ ngx_chain_t *cl, **ll;
++ ngx_quic_frame_t *frame;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ ack_delay = ngx_current_msec - ctx->largest_received;
++ ack_delay *= 1000;
++ ack_delay >>= qc->tp.ack_delay_exponent;
++
++ frame = ngx_quic_alloc_frame(c);
++ if (frame == NULL) {
++ return NGX_ERROR;
++ }
++
++ ll = &frame->data;
++ b = NULL;
++
++ for (i = 0; i < ctx->nranges; i++) {
++ len = ngx_quic_create_ack_range(NULL, ctx->ranges[i].gap,
++ ctx->ranges[i].range);
++
++ left = b ? b->end - b->last : 0;
++
++ if (left < len) {
++ cl = ngx_quic_alloc_chain(c);
++ if (cl == NULL) {
++ return NGX_ERROR;
++ }
++
++ *ll = cl;
++ ll = &cl->next;
++
++ b = cl->buf;
++ left = b->end - b->last;
++
++ if (left < len) {
++ return NGX_ERROR;
++ }
++ }
++
++ b->last += ngx_quic_create_ack_range(b->last, ctx->ranges[i].gap,
++ ctx->ranges[i].range);
++
++ frame->u.ack.ranges_length += len;
++ }
++
++ *ll = NULL;
++
++ frame->level = ctx->level;
++ frame->type = NGX_QUIC_FT_ACK;
++ frame->u.ack.largest = ctx->largest_range;
++ frame->u.ack.delay = ack_delay;
++ frame->u.ack.range_count = ctx->nranges;
++ frame->u.ack.first_range = ctx->first_range;
++
++ ngx_quic_queue_frame(qc, frame);
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_send_ack_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx,
++ uint64_t smallest, uint64_t largest)
++{
++ ngx_quic_frame_t *frame;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ frame = ngx_quic_alloc_frame(c);
++ if (frame == NULL) {
++ return NGX_ERROR;
++ }
++
++ frame->level = ctx->level;
++ frame->type = NGX_QUIC_FT_ACK;
++ frame->u.ack.largest = largest;
++ frame->u.ack.delay = 0;
++ frame->u.ack.range_count = 0;
++ frame->u.ack.first_range = largest - smallest;
++
++ ngx_quic_queue_frame(qc, frame);
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame,
++ size_t min, ngx_quic_path_t *path)
++{
++ size_t min_payload, pad;
++ ssize_t len, sent;
++ ngx_str_t res;
++ ngx_quic_header_t pkt;
++ ngx_quic_send_ctx_t *ctx;
++ ngx_quic_connection_t *qc;
++
++ static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
++ static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
++
++ qc = ngx_quic_get_connection(c);
++ ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application);
++
++ ngx_quic_init_packet(c, ctx, &pkt);
++
++ min = ngx_quic_path_limit(c, path, min);
++
++ min_payload = min ? ngx_quic_payload_size(&pkt, min) : 0;
++
++ pad = 4 - pkt.num_len;
++ min_payload = ngx_max(min_payload, pad);
++
++ len = ngx_quic_create_frame(NULL, frame);
++ if (len > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) {
++ return NGX_ERROR;
++ }
++
++ ngx_quic_log_frame(c->log, frame, 1);
++
++ len = ngx_quic_create_frame(src, frame);
++ if (len == -1) {
++ return NGX_ERROR;
++ }
++
++ if (len < (ssize_t) min_payload) {
++ ngx_memset(src + len, NGX_QUIC_FT_PADDING, min_payload - len);
++ len = min_payload;
++ }
++
++ pkt.payload.data = src;
++ pkt.payload.len = len;
++
++ res.data = dst;
++
++ if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ ctx->pnum++;
++
++ sent = ngx_quic_send(c, res.data, res.len, path->sockaddr, path->socklen);
++ if (sent < 0) {
++ return NGX_ERROR;
++ }
++
++ path->sent += sent;
++
++ return NGX_OK;
++}
++
++
++static size_t
++ngx_quic_path_limit(ngx_connection_t *c, ngx_quic_path_t *path, size_t size)
++{
++ off_t max;
++
++ if (path->limited) {
++ max = path->received * 3;
++ max = (path->sent >= max) ? 0 : max - path->sent;
++
++ if ((off_t) size > max) {
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic path limit %uz - %O", size, max);
++ return max;
++ }
++ }
++
++ return size;
++}
+diff -r 67408b4a12c0 src/event/quic/ngx_event_quic_output.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_output.h Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,40 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#ifndef _NGX_EVENT_QUIC_OUTPUT_H_INCLUDED_
++#define _NGX_EVENT_QUIC_OUTPUT_H_INCLUDED_
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++
++
++size_t ngx_quic_max_udp_payload(ngx_connection_t *c);
++
++ngx_int_t ngx_quic_output(ngx_connection_t *c);
++
++ngx_int_t ngx_quic_negotiate_version(ngx_connection_t *c,
++ ngx_quic_header_t *inpkt);
++
++ngx_int_t ngx_quic_send_stateless_reset(ngx_connection_t *c,
++ ngx_quic_conf_t *conf, ngx_quic_header_t *pkt);
++ngx_int_t ngx_quic_send_cc(ngx_connection_t *c);
++ngx_int_t ngx_quic_send_early_cc(ngx_connection_t *c,
++ ngx_quic_header_t *inpkt, ngx_uint_t err, const char *reason);
++
++ngx_int_t ngx_quic_send_retry(ngx_connection_t *c,
++ ngx_quic_conf_t *conf, ngx_quic_header_t *pkt);
++ngx_int_t ngx_quic_send_new_token(ngx_connection_t *c, ngx_quic_path_t *path);
++
++ngx_int_t ngx_quic_send_ack(ngx_connection_t *c,
++ ngx_quic_send_ctx_t *ctx);
++ngx_int_t ngx_quic_send_ack_range(ngx_connection_t *c,
++ ngx_quic_send_ctx_t *ctx, uint64_t smallest, uint64_t largest);
++
++ngx_int_t ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame,
++ size_t min, ngx_quic_path_t *path);
++
++#endif /* _NGX_EVENT_QUIC_OUTPUT_H_INCLUDED_ */
+diff -r 67408b4a12c0 src/event/quic/ngx_event_quic_protection.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_protection.c Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,1186 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++#include <ngx_event.h>
++#include <ngx_event_quic_connection.h>
++
++
++/* RFC 5116, 5.1 and RFC 8439, 2.3 for all supported ciphers */
++#define NGX_QUIC_IV_LEN 12
++/* RFC 9001, 5.4.1. Header Protection Application: 5-byte mask */
++#define NGX_QUIC_HP_LEN 5
++
++#define NGX_QUIC_AES_128_KEY_LEN 16
++
++#define NGX_AES_128_GCM_SHA256 0x1301
++#define NGX_AES_256_GCM_SHA384 0x1302
++#define NGX_CHACHA20_POLY1305_SHA256 0x1303
++
++
++#ifdef OPENSSL_IS_BORINGSSL
++#define ngx_quic_cipher_t EVP_AEAD
++#else
++#define ngx_quic_cipher_t EVP_CIPHER
++#endif
++
++
++typedef struct {
++ const ngx_quic_cipher_t *c;
++ const EVP_CIPHER *hp;
++ const EVP_MD *d;
++} ngx_quic_ciphers_t;
++
++
++typedef struct ngx_quic_secret_s {
++ ngx_str_t secret;
++ ngx_str_t key;
++ ngx_str_t iv;
++ ngx_str_t hp;
++} ngx_quic_secret_t;
++
++
++typedef struct {
++ ngx_quic_secret_t client;
++ ngx_quic_secret_t server;
++} ngx_quic_secrets_t;
++
++
++struct ngx_quic_keys_s {
++ ngx_quic_secrets_t secrets[NGX_QUIC_ENCRYPTION_LAST];
++ ngx_quic_secrets_t next_key;
++ ngx_uint_t cipher;
++};
++
++
++static ngx_int_t ngx_hkdf_expand(u_char *out_key, size_t out_len,
++ const EVP_MD *digest, const u_char *prk, size_t prk_len,
++ const u_char *info, size_t info_len);
++static ngx_int_t ngx_hkdf_extract(u_char *out_key, size_t *out_len,
++ const EVP_MD *digest, const u_char *secret, size_t secret_len,
++ const u_char *salt, size_t salt_len);
++
++static uint64_t ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask,
++ uint64_t *largest_pn);
++static void ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn);
++static ngx_int_t ngx_quic_ciphers(ngx_uint_t id,
++ ngx_quic_ciphers_t *ciphers, enum ssl_encryption_level_t level);
++
++static ngx_int_t ngx_quic_tls_open(const ngx_quic_cipher_t *cipher,
++ ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in,
++ ngx_str_t *ad, ngx_log_t *log);
++static ngx_int_t ngx_quic_tls_seal(const ngx_quic_cipher_t *cipher,
++ ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in,
++ ngx_str_t *ad, ngx_log_t *log);
++static ngx_int_t ngx_quic_tls_hp(ngx_log_t *log, const EVP_CIPHER *cipher,
++ ngx_quic_secret_t *s, u_char *out, u_char *in);
++static ngx_int_t ngx_quic_hkdf_expand(ngx_pool_t *pool, const EVP_MD *digest,
++ ngx_str_t *out, ngx_str_t *label, const uint8_t *prk, size_t prk_len);
++
++static ngx_int_t ngx_quic_create_packet(ngx_quic_header_t *pkt,
++ ngx_str_t *res);
++static ngx_int_t ngx_quic_create_retry_packet(ngx_quic_header_t *pkt,
++ ngx_str_t *res);
++
++
++static ngx_int_t
++ngx_quic_ciphers(ngx_uint_t id, ngx_quic_ciphers_t *ciphers,
++ enum ssl_encryption_level_t level)
++{
++ ngx_int_t len;
++
++ if (level == ssl_encryption_initial) {
++ id = NGX_AES_128_GCM_SHA256;
++ }
++
++ switch (id) {
++
++ case NGX_AES_128_GCM_SHA256:
++#ifdef OPENSSL_IS_BORINGSSL
++ ciphers->c = EVP_aead_aes_128_gcm();
++#else
++ ciphers->c = EVP_aes_128_gcm();
++#endif
++ ciphers->hp = EVP_aes_128_ctr();
++ ciphers->d = EVP_sha256();
++ len = 16;
++ break;
++
++ case NGX_AES_256_GCM_SHA384:
++#ifdef OPENSSL_IS_BORINGSSL
++ ciphers->c = EVP_aead_aes_256_gcm();
++#else
++ ciphers->c = EVP_aes_256_gcm();
++#endif
++ ciphers->hp = EVP_aes_256_ctr();
++ ciphers->d = EVP_sha384();
++ len = 32;
++ break;
++
++ case NGX_CHACHA20_POLY1305_SHA256:
++#ifdef OPENSSL_IS_BORINGSSL
++ ciphers->c = EVP_aead_chacha20_poly1305();
++#else
++ ciphers->c = EVP_chacha20_poly1305();
++#endif
++#ifdef OPENSSL_IS_BORINGSSL
++ ciphers->hp = (const EVP_CIPHER *) EVP_aead_chacha20_poly1305();
++#else
++ ciphers->hp = EVP_chacha20();
++#endif
++ ciphers->d = EVP_sha256();
++ len = 32;
++ break;
++
++ default:
++ return NGX_ERROR;
++ }
++
++ return len;
++}
++
++
++ngx_int_t
++ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, ngx_quic_keys_t *keys,
++ ngx_str_t *secret, uint32_t version)
++{
++ size_t is_len;
++ uint8_t is[SHA256_DIGEST_LENGTH];
++ ngx_uint_t i;
++ const EVP_MD *digest;
++ ngx_quic_secret_t *client, *server;
++
++ static const uint8_t salt[20] =
++ "\x38\x76\x2c\xf7\xf5\x59\x34\xb3\x4d\x17"
++ "\x9a\xe6\xa4\xc8\x0c\xad\xcc\xbb\x7f\x0a";
++ static const uint8_t salt29[20] =
++ "\xaf\xbf\xec\x28\x99\x93\xd2\x4c\x9e\x97"
++ "\x86\xf1\x9c\x61\x11\xe0\x43\x90\xa8\x99";
++
++ client = &keys->secrets[ssl_encryption_initial].client;
++ server = &keys->secrets[ssl_encryption_initial].server;
++
++ /*
++ * RFC 9001, section 5. Packet Protection
++ *
++ * Initial packets use AEAD_AES_128_GCM. The hash function
++ * for HKDF when deriving initial secrets and keys is SHA-256.
++ */
++
++ digest = EVP_sha256();
++ is_len = SHA256_DIGEST_LENGTH;
++
++ if (ngx_hkdf_extract(is, &is_len, digest, secret->data, secret->len,
++ (version & 0xff000000) ? salt29 : salt, sizeof(salt))
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ ngx_str_t iss = {
++ .data = is,
++ .len = is_len
++ };
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pool->log, 0,
++ "quic ngx_quic_set_initial_secret");
++#ifdef NGX_QUIC_DEBUG_CRYPTO
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pool->log, 0,
++ "quic salt len:%uz %*xs", sizeof(salt), sizeof(salt), salt);
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pool->log, 0,
++ "quic initial secret len:%uz %*xs", is_len, is_len, is);
++#endif
++
++ client->secret.len = SHA256_DIGEST_LENGTH;
++ server->secret.len = SHA256_DIGEST_LENGTH;
++
++ client->key.len = NGX_QUIC_AES_128_KEY_LEN;
++ server->key.len = NGX_QUIC_AES_128_KEY_LEN;
++
++ client->hp.len = NGX_QUIC_AES_128_KEY_LEN;
++ server->hp.len = NGX_QUIC_AES_128_KEY_LEN;
++
++ client->iv.len = NGX_QUIC_IV_LEN;
++ server->iv.len = NGX_QUIC_IV_LEN;
++
++ struct {
++ ngx_str_t label;
++ ngx_str_t *key;
++ ngx_str_t *prk;
++ } seq[] = {
++ /* labels per RFC 9001, 5.1. Packet Protection Keys */
++ { ngx_string("tls13 client in"), &client->secret, &iss },
++ { ngx_string("tls13 quic key"), &client->key, &client->secret },
++ { ngx_string("tls13 quic iv"), &client->iv, &client->secret },
++ { ngx_string("tls13 quic hp"), &client->hp, &client->secret },
++ { ngx_string("tls13 server in"), &server->secret, &iss },
++ { ngx_string("tls13 quic key"), &server->key, &server->secret },
++ { ngx_string("tls13 quic iv"), &server->iv, &server->secret },
++ { ngx_string("tls13 quic hp"), &server->hp, &server->secret },
++ };
++
++ for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) {
++
++ if (ngx_quic_hkdf_expand(pool, digest, seq[i].key, &seq[i].label,
++ seq[i].prk->data, seq[i].prk->len)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++ }
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_hkdf_expand(ngx_pool_t *pool, const EVP_MD *digest, ngx_str_t *out,
++ ngx_str_t *label, const uint8_t *prk, size_t prk_len)
++{
++ size_t info_len;
++ uint8_t *p;
++ uint8_t info[20];
++
++ if (out->data == NULL) {
++ out->data = ngx_pnalloc(pool, out->len);
++ if (out->data == NULL) {
++ return NGX_ERROR;
++ }
++ }
++
++ info_len = 2 + 1 + label->len + 1;
++
++ info[0] = 0;
++ info[1] = out->len;
++ info[2] = label->len;
++ p = ngx_cpymem(&info[3], label->data, label->len);
++ *p = '\0';
++
++ if (ngx_hkdf_expand(out->data, out->len, digest,
++ prk, prk_len, info, info_len)
++ != NGX_OK)
++ {
++ ngx_ssl_error(NGX_LOG_INFO, pool->log, 0,
++ "ngx_hkdf_expand(%V) failed", label);
++ return NGX_ERROR;
++ }
++
++#ifdef NGX_QUIC_DEBUG_CRYPTO
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pool->log, 0,
++ "quic expand %V key len:%uz %xV", label, out->len, out);
++#endif
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_hkdf_expand(u_char *out_key, size_t out_len, const EVP_MD *digest,
++ const uint8_t *prk, size_t prk_len, const u_char *info, size_t info_len)
++{
++#ifdef OPENSSL_IS_BORINGSSL
++
++ if (HKDF_expand(out_key, out_len, digest, prk, prk_len, info, info_len)
++ == 0)
++ {
++ return NGX_ERROR;
++ }
++
++ return NGX_OK;
++
++#else
++
++ EVP_PKEY_CTX *pctx;
++
++ pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
++ if (pctx == NULL) {
++ return NGX_ERROR;
++ }
++
++ if (EVP_PKEY_derive_init(pctx) <= 0) {
++ goto failed;
++ }
++
++ if (EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) <= 0) {
++ goto failed;
++ }
++
++ if (EVP_PKEY_CTX_set_hkdf_md(pctx, digest) <= 0) {
++ goto failed;
++ }
++
++ if (EVP_PKEY_CTX_set1_hkdf_key(pctx, prk, prk_len) <= 0) {
++ goto failed;
++ }
++
++ if (EVP_PKEY_CTX_add1_hkdf_info(pctx, info, info_len) <= 0) {
++ goto failed;
++ }
++
++ if (EVP_PKEY_derive(pctx, out_key, &out_len) <= 0) {
++ goto failed;
++ }
++
++ EVP_PKEY_CTX_free(pctx);
++
++ return NGX_OK;
++
++failed:
++
++ EVP_PKEY_CTX_free(pctx);
++
++ return NGX_ERROR;
++
++#endif
++}
++
++
++static ngx_int_t
++ngx_hkdf_extract(u_char *out_key, size_t *out_len, const EVP_MD *digest,
++ const u_char *secret, size_t secret_len, const u_char *salt,
++ size_t salt_len)
++{
++#ifdef OPENSSL_IS_BORINGSSL
++
++ if (HKDF_extract(out_key, out_len, digest, secret, secret_len, salt,
++ salt_len)
++ == 0)
++ {
++ return NGX_ERROR;
++ }
++
++ return NGX_OK;
++
++#else
++
++ EVP_PKEY_CTX *pctx;
++
++ pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
++ if (pctx == NULL) {
++ return NGX_ERROR;
++ }
++
++ if (EVP_PKEY_derive_init(pctx) <= 0) {
++ goto failed;
++ }
++
++ if (EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY) <= 0) {
++ goto failed;
++ }
++
++ if (EVP_PKEY_CTX_set_hkdf_md(pctx, digest) <= 0) {
++ goto failed;
++ }
++
++ if (EVP_PKEY_CTX_set1_hkdf_key(pctx, secret, secret_len) <= 0) {
++ goto failed;
++ }
++
++ if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt, salt_len) <= 0) {
++ goto failed;
++ }
++
++ if (EVP_PKEY_derive(pctx, out_key, out_len) <= 0) {
++ goto failed;
++ }
++
++ EVP_PKEY_CTX_free(pctx);
++
++ return NGX_OK;
++
++failed:
++
++ EVP_PKEY_CTX_free(pctx);
++
++ return NGX_ERROR;
++
++#endif
++}
++
++
++static ngx_int_t
++ngx_quic_tls_open(const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s,
++ ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad,
++ ngx_log_t *log)
++{
++
++#ifdef OPENSSL_IS_BORINGSSL
++ EVP_AEAD_CTX *ctx;
++
++ ctx = EVP_AEAD_CTX_new(cipher, s->key.data, s->key.len,
++ EVP_AEAD_DEFAULT_TAG_LENGTH);
++ if (ctx == NULL) {
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_new() failed");
++ return NGX_ERROR;
++ }
++
++ if (EVP_AEAD_CTX_open(ctx, out->data, &out->len, out->len, nonce, s->iv.len,
++ in->data, in->len, ad->data, ad->len)
++ != 1)
++ {
++ EVP_AEAD_CTX_free(ctx);
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_open() failed");
++ return NGX_ERROR;
++ }
++
++ EVP_AEAD_CTX_free(ctx);
++#else
++ int len;
++ u_char *tag;
++ EVP_CIPHER_CTX *ctx;
++
++ ctx = EVP_CIPHER_CTX_new();
++ if (ctx == NULL) {
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CIPHER_CTX_new() failed");
++ return NGX_ERROR;
++ }
++
++ if (EVP_DecryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1) {
++ EVP_CIPHER_CTX_free(ctx);
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptInit_ex() failed");
++ return NGX_ERROR;
++ }
++
++ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, s->iv.len, NULL)
++ == 0)
++ {
++ EVP_CIPHER_CTX_free(ctx);
++ ngx_ssl_error(NGX_LOG_INFO, log, 0,
++ "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_IVLEN) failed");
++ return NGX_ERROR;
++ }
++
++ if (EVP_DecryptInit_ex(ctx, NULL, NULL, s->key.data, nonce) != 1) {
++ EVP_CIPHER_CTX_free(ctx);
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptInit_ex() failed");
++ return NGX_ERROR;
++ }
++
++ if (EVP_DecryptUpdate(ctx, NULL, &len, ad->data, ad->len) != 1) {
++ EVP_CIPHER_CTX_free(ctx);
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptUpdate() failed");
++ return NGX_ERROR;
++ }
++
++ if (EVP_DecryptUpdate(ctx, out->data, &len, in->data,
++ in->len - EVP_GCM_TLS_TAG_LEN)
++ != 1)
++ {
++ EVP_CIPHER_CTX_free(ctx);
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptUpdate() failed");
++ return NGX_ERROR;
++ }
++
++ out->len = len;
++ tag = in->data + in->len - EVP_GCM_TLS_TAG_LEN;
++
++ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, EVP_GCM_TLS_TAG_LEN, tag)
++ == 0)
++ {
++ EVP_CIPHER_CTX_free(ctx);
++ ngx_ssl_error(NGX_LOG_INFO, log, 0,
++ "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_TAG) failed");
++ return NGX_ERROR;
++ }
++
++ if (EVP_DecryptFinal_ex(ctx, out->data + len, &len) <= 0) {
++ EVP_CIPHER_CTX_free(ctx);
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptFinal_ex failed");
++ return NGX_ERROR;
++ }
++
++ out->len += len;
++
++ EVP_CIPHER_CTX_free(ctx);
++#endif
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_tls_seal(const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s,
++ ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad, ngx_log_t *log)
++{
++
++#ifdef OPENSSL_IS_BORINGSSL
++ EVP_AEAD_CTX *ctx;
++
++ ctx = EVP_AEAD_CTX_new(cipher, s->key.data, s->key.len,
++ EVP_AEAD_DEFAULT_TAG_LENGTH);
++ if (ctx == NULL) {
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_new() failed");
++ return NGX_ERROR;
++ }
++
++ if (EVP_AEAD_CTX_seal(ctx, out->data, &out->len, out->len, nonce, s->iv.len,
++ in->data, in->len, ad->data, ad->len)
++ != 1)
++ {
++ EVP_AEAD_CTX_free(ctx);
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_seal() failed");
++ return NGX_ERROR;
++ }
++
++ EVP_AEAD_CTX_free(ctx);
++#else
++ int len;
++ EVP_CIPHER_CTX *ctx;
++
++ ctx = EVP_CIPHER_CTX_new();
++ if (ctx == NULL) {
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CIPHER_CTX_new() failed");
++ return NGX_ERROR;
++ }
++
++ if (EVP_EncryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1) {
++ EVP_CIPHER_CTX_free(ctx);
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed");
++ return NGX_ERROR;
++ }
++
++ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, s->iv.len, NULL)
++ == 0)
++ {
++ EVP_CIPHER_CTX_free(ctx);
++ ngx_ssl_error(NGX_LOG_INFO, log, 0,
++ "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_IVLEN) failed");
++ return NGX_ERROR;
++ }
++
++ if (EVP_EncryptInit_ex(ctx, NULL, NULL, s->key.data, nonce) != 1) {
++ EVP_CIPHER_CTX_free(ctx);
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed");
++ return NGX_ERROR;
++ }
++
++ if (EVP_EncryptUpdate(ctx, NULL, &len, ad->data, ad->len) != 1) {
++ EVP_CIPHER_CTX_free(ctx);
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptUpdate() failed");
++ return NGX_ERROR;
++ }
++
++ if (EVP_EncryptUpdate(ctx, out->data, &len, in->data, in->len) != 1) {
++ EVP_CIPHER_CTX_free(ctx);
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptUpdate() failed");
++ return NGX_ERROR;
++ }
++
++ out->len = len;
++
++ if (EVP_EncryptFinal_ex(ctx, out->data + out->len, &len) <= 0) {
++ EVP_CIPHER_CTX_free(ctx);
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptFinal_ex failed");
++ return NGX_ERROR;
++ }
++
++ out->len += len;
++
++ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, EVP_GCM_TLS_TAG_LEN,
++ out->data + in->len)
++ == 0)
++ {
++ EVP_CIPHER_CTX_free(ctx);
++ ngx_ssl_error(NGX_LOG_INFO, log, 0,
++ "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_GET_TAG) failed");
++ return NGX_ERROR;
++ }
++
++ EVP_CIPHER_CTX_free(ctx);
++
++ out->len += EVP_GCM_TLS_TAG_LEN;
++#endif
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_tls_hp(ngx_log_t *log, const EVP_CIPHER *cipher,
++ ngx_quic_secret_t *s, u_char *out, u_char *in)
++{
++ int outlen;
++ EVP_CIPHER_CTX *ctx;
++ u_char zero[NGX_QUIC_HP_LEN] = {0};
++
++#ifdef OPENSSL_IS_BORINGSSL
++ uint32_t cnt;
++
++ ngx_memcpy(&cnt, in, sizeof(uint32_t));
++
++ if (cipher == (const EVP_CIPHER *) EVP_aead_chacha20_poly1305()) {
++ CRYPTO_chacha_20(out, zero, NGX_QUIC_HP_LEN, s->hp.data, &in[4], cnt);
++ return NGX_OK;
++ }
++#endif
++
++ ctx = EVP_CIPHER_CTX_new();
++ if (ctx == NULL) {
++ return NGX_ERROR;
++ }
++
++ if (EVP_EncryptInit_ex(ctx, cipher, NULL, s->hp.data, in) != 1) {
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed");
++ goto failed;
++ }
++
++ if (!EVP_EncryptUpdate(ctx, out, &outlen, zero, NGX_QUIC_HP_LEN)) {
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptUpdate() failed");
++ goto failed;
++ }
++
++ if (!EVP_EncryptFinal_ex(ctx, out + NGX_QUIC_HP_LEN, &outlen)) {
++ ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptFinal_Ex() failed");
++ goto failed;
++ }
++
++ EVP_CIPHER_CTX_free(ctx);
++
++ return NGX_OK;
++
++failed:
++
++ EVP_CIPHER_CTX_free(ctx);
++
++ return NGX_ERROR;
++}
++
++
++ngx_int_t
++ngx_quic_keys_set_encryption_secret(ngx_pool_t *pool, ngx_uint_t is_write,
++ ngx_quic_keys_t *keys, enum ssl_encryption_level_t level,
++ const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len)
++{
++ ngx_int_t key_len;
++ ngx_uint_t i;
++ ngx_quic_secret_t *peer_secret;
++ ngx_quic_ciphers_t ciphers;
++
++ peer_secret = is_write ? &keys->secrets[level].server
++ : &keys->secrets[level].client;
++
++ keys->cipher = SSL_CIPHER_get_protocol_id(cipher);
++
++ key_len = ngx_quic_ciphers(keys->cipher, &ciphers, level);
++
++ if (key_len == NGX_ERROR) {
++ ngx_ssl_error(NGX_LOG_INFO, pool->log, 0, "unexpected cipher");
++ return NGX_ERROR;
++ }
++
++ peer_secret->secret.data = ngx_pnalloc(pool, secret_len);
++ if (peer_secret->secret.data == NULL) {
++ return NGX_ERROR;
++ }
++
++ peer_secret->secret.len = secret_len;
++ ngx_memcpy(peer_secret->secret.data, secret, secret_len);
++
++ peer_secret->key.len = key_len;
++ peer_secret->iv.len = NGX_QUIC_IV_LEN;
++ peer_secret->hp.len = key_len;
++
++ struct {
++ ngx_str_t label;
++ ngx_str_t *key;
++ const uint8_t *secret;
++ } seq[] = {
++ { ngx_string("tls13 quic key"), &peer_secret->key, secret },
++ { ngx_string("tls13 quic iv"), &peer_secret->iv, secret },
++ { ngx_string("tls13 quic hp"), &peer_secret->hp, secret },
++ };
++
++ for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) {
++
++ if (ngx_quic_hkdf_expand(pool, ciphers.d, seq[i].key, &seq[i].label,
++ seq[i].secret, secret_len)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++ }
++
++ return NGX_OK;
++}
++
++
++ngx_quic_keys_t *
++ngx_quic_keys_new(ngx_pool_t *pool)
++{
++ return ngx_pcalloc(pool, sizeof(ngx_quic_keys_t));
++}
++
++
++ngx_uint_t
++ngx_quic_keys_available(ngx_quic_keys_t *keys,
++ enum ssl_encryption_level_t level)
++{
++ return keys->secrets[level].client.key.len != 0;
++}
++
++
++void
++ngx_quic_keys_discard(ngx_quic_keys_t *keys,
++ enum ssl_encryption_level_t level)
++{
++ keys->secrets[level].client.key.len = 0;
++}
++
++
++void
++ngx_quic_keys_switch(ngx_connection_t *c, ngx_quic_keys_t *keys)
++{
++ ngx_quic_secrets_t *current, *next, tmp;
++
++ current = &keys->secrets[ssl_encryption_application];
++ next = &keys->next_key;
++
++ tmp = *current;
++ *current = *next;
++ *next = tmp;
++}
++
++
++ngx_int_t
++ngx_quic_keys_update(ngx_connection_t *c, ngx_quic_keys_t *keys)
++{
++ ngx_uint_t i;
++ ngx_quic_ciphers_t ciphers;
++ ngx_quic_secrets_t *current, *next;
++
++ current = &keys->secrets[ssl_encryption_application];
++ next = &keys->next_key;
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic key update");
++
++ if (ngx_quic_ciphers(keys->cipher, &ciphers, ssl_encryption_application)
++ == NGX_ERROR)
++ {
++ return NGX_ERROR;
++ }
++
++ next->client.secret.len = current->client.secret.len;
++ next->client.key.len = current->client.key.len;
++ next->client.iv.len = NGX_QUIC_IV_LEN;
++ next->client.hp = current->client.hp;
++
++ next->server.secret.len = current->server.secret.len;
++ next->server.key.len = current->server.key.len;
++ next->server.iv.len = NGX_QUIC_IV_LEN;
++ next->server.hp = current->server.hp;
++
++ struct {
++ ngx_str_t label;
++ ngx_str_t *key;
++ ngx_str_t *secret;
++ } seq[] = {
++ {
++ ngx_string("tls13 quic ku"),
++ &next->client.secret,
++ &current->client.secret,
++ },
++ {
++ ngx_string("tls13 quic key"),
++ &next->client.key,
++ &next->client.secret,
++ },
++ {
++ ngx_string("tls13 quic iv"),
++ &next->client.iv,
++ &next->client.secret,
++ },
++ {
++ ngx_string("tls13 quic ku"),
++ &next->server.secret,
++ &current->server.secret,
++ },
++ {
++ ngx_string("tls13 quic key"),
++ &next->server.key,
++ &next->server.secret,
++ },
++ {
++ ngx_string("tls13 quic iv"),
++ &next->server.iv,
++ &next->server.secret,
++ },
++ };
++
++ for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) {
++
++ if (ngx_quic_hkdf_expand(c->pool, ciphers.d, seq[i].key, &seq[i].label,
++ seq[i].secret->data, seq[i].secret->len)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++ }
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_create_packet(ngx_quic_header_t *pkt, ngx_str_t *res)
++{
++ u_char *pnp, *sample;
++ ngx_str_t ad, out;
++ ngx_uint_t i;
++ ngx_quic_secret_t *secret;
++ ngx_quic_ciphers_t ciphers;
++ u_char nonce[NGX_QUIC_IV_LEN], mask[NGX_QUIC_HP_LEN];
++
++ ad.data = res->data;
++ ad.len = ngx_quic_create_header(pkt, ad.data, &pnp);
++
++ out.len = pkt->payload.len + EVP_GCM_TLS_TAG_LEN;
++ out.data = res->data + ad.len;
++
++#ifdef NGX_QUIC_DEBUG_CRYPTO
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
++ "quic ad len:%uz %xV", ad.len, &ad);
++#endif
++
++ if (ngx_quic_ciphers(pkt->keys->cipher, &ciphers, pkt->level) == NGX_ERROR)
++ {
++ return NGX_ERROR;
++ }
++
++ secret = &pkt->keys->secrets[pkt->level].server;
++
++ ngx_memcpy(nonce, secret->iv.data, secret->iv.len);
++ ngx_quic_compute_nonce(nonce, sizeof(nonce), pkt->number);
++
++ if (ngx_quic_tls_seal(ciphers.c, secret, &out,
++ nonce, &pkt->payload, &ad, pkt->log)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ sample = &out.data[4 - pkt->num_len];
++ if (ngx_quic_tls_hp(pkt->log, ciphers.hp, secret, mask, sample)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ /* RFC 9001, 5.4.1. Header Protection Application */
++ ad.data[0] ^= mask[0] & ngx_quic_pkt_hp_mask(pkt->flags);
++
++ for (i = 0; i < pkt->num_len; i++) {
++ pnp[i] ^= mask[i + 1];
++ }
++
++ res->len = ad.len + out.len;
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, ngx_str_t *res)
++{
++ u_char *start;
++ ngx_str_t ad, itag;
++ ngx_quic_secret_t secret;
++ ngx_quic_ciphers_t ciphers;
++
++ /* 5.8. Retry Packet Integrity */
++ static u_char key[16] =
++ "\xbe\x0c\x69\x0b\x9f\x66\x57\x5a\x1d\x76\x6b\x54\xe3\x68\xc8\x4e";
++ static u_char key29[16] =
++ "\xcc\xce\x18\x7e\xd0\x9a\x09\xd0\x57\x28\x15\x5a\x6c\xb9\x6b\xe1";
++ static u_char nonce[NGX_QUIC_IV_LEN] =
++ "\x46\x15\x99\xd3\x5d\x63\x2b\xf2\x23\x98\x25\xbb";
++ static u_char nonce29[NGX_QUIC_IV_LEN] =
++ "\xe5\x49\x30\xf9\x7f\x21\x36\xf0\x53\x0a\x8c\x1c";
++ static ngx_str_t in = ngx_string("");
++
++ ad.data = res->data;
++ ad.len = ngx_quic_create_retry_itag(pkt, ad.data, &start);
++
++ itag.data = ad.data + ad.len;
++ itag.len = EVP_GCM_TLS_TAG_LEN;
++
++#ifdef NGX_QUIC_DEBUG_CRYPTO
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
++ "quic retry itag len:%uz %xV", ad.len, &ad);
++#endif
++
++ if (ngx_quic_ciphers(0, &ciphers, pkt->level) == NGX_ERROR) {
++ return NGX_ERROR;
++ }
++
++ secret.key.len = sizeof(key);
++ secret.key.data = (pkt->version & 0xff000000) ? key29 : key;
++ secret.iv.len = NGX_QUIC_IV_LEN;
++
++ if (ngx_quic_tls_seal(ciphers.c, &secret, &itag,
++ (pkt->version & 0xff000000) ? nonce29 : nonce,
++ &in, &ad, pkt->log)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ res->len = itag.data + itag.len - start;
++ res->data = start;
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_derive_key(ngx_log_t *log, const char *label, ngx_str_t *secret,
++ ngx_str_t *salt, u_char *out, size_t len)
++{
++ size_t is_len, info_len;
++ uint8_t *p;
++ const EVP_MD *digest;
++
++ uint8_t is[SHA256_DIGEST_LENGTH];
++ uint8_t info[20];
++
++ digest = EVP_sha256();
++ is_len = SHA256_DIGEST_LENGTH;
++
++ if (ngx_hkdf_extract(is, &is_len, digest, secret->data, secret->len,
++ salt->data, salt->len)
++ != NGX_OK)
++ {
++ ngx_ssl_error(NGX_LOG_INFO, log, 0,
++ "ngx_hkdf_extract(%s) failed", label);
++ return NGX_ERROR;
++ }
++
++ info[0] = 0;
++ info[1] = len;
++ info[2] = ngx_strlen(label);
++
++ info_len = 2 + 1 + info[2] + 1;
++
++ if (info_len >= 20) {
++ ngx_log_error(NGX_LOG_INFO, log, 0,
++ "ngx_quic_create_key label \"%s\" too long", label);
++ return NGX_ERROR;
++ }
++
++ p = ngx_cpymem(&info[3], label, info[2]);
++ *p = '\0';
++
++ if (ngx_hkdf_expand(out, len, digest, is, is_len, info, info_len) != NGX_OK)
++ {
++ ngx_ssl_error(NGX_LOG_INFO, log, 0,
++ "ngx_hkdf_expand(%s) failed", label);
++ return NGX_ERROR;
++ }
++
++ return NGX_OK;
++}
++
++
++static uint64_t
++ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask,
++ uint64_t *largest_pn)
++{
++ u_char *p;
++ uint64_t truncated_pn, expected_pn, candidate_pn;
++ uint64_t pn_nbits, pn_win, pn_hwin, pn_mask;
++
++ pn_nbits = ngx_min(len * 8, 62);
++
++ p = *pos;
++ truncated_pn = *p++ ^ *mask++;
++
++ while (--len) {
++ truncated_pn = (truncated_pn << 8) + (*p++ ^ *mask++);
++ }
++
++ *pos = p;
++
++ expected_pn = *largest_pn + 1;
++ pn_win = 1ULL << pn_nbits;
++ pn_hwin = pn_win / 2;
++ pn_mask = pn_win - 1;
++
++ candidate_pn = (expected_pn & ~pn_mask) | truncated_pn;
++
++ if ((int64_t) candidate_pn <= (int64_t) (expected_pn - pn_hwin)
++ && candidate_pn < (1ULL << 62) - pn_win)
++ {
++ candidate_pn += pn_win;
++
++ } else if (candidate_pn > expected_pn + pn_hwin
++ && candidate_pn >= pn_win)
++ {
++ candidate_pn -= pn_win;
++ }
++
++ *largest_pn = ngx_max((int64_t) *largest_pn, (int64_t) candidate_pn);
++
++ return candidate_pn;
++}
++
++
++static void
++ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn)
++{
++ nonce[len - 4] ^= (pn & 0xff000000) >> 24;
++ nonce[len - 3] ^= (pn & 0x00ff0000) >> 16;
++ nonce[len - 2] ^= (pn & 0x0000ff00) >> 8;
++ nonce[len - 1] ^= (pn & 0x000000ff);
++}
++
++
++ngx_int_t
++ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_str_t *res)
++{
++ if (ngx_quic_pkt_retry(pkt->flags)) {
++ return ngx_quic_create_retry_packet(pkt, res);
++ }
++
++ return ngx_quic_create_packet(pkt, res);
++}
++
++
++ngx_int_t
++ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn)
++{
++ u_char *p, *sample;
++ size_t len;
++ uint64_t pn, lpn;
++ ngx_int_t pnl, rc, key_phase;
++ ngx_str_t in, ad;
++ ngx_quic_secret_t *secret;
++ ngx_quic_ciphers_t ciphers;
++ uint8_t nonce[NGX_QUIC_IV_LEN], mask[NGX_QUIC_HP_LEN];
++
++ if (ngx_quic_ciphers(pkt->keys->cipher, &ciphers, pkt->level) == NGX_ERROR)
++ {
++ return NGX_ERROR;
++ }
++
++ secret = &pkt->keys->secrets[pkt->level].client;
++
++ p = pkt->raw->pos;
++ len = pkt->data + pkt->len - p;
++
++ /*
++ * RFC 9001, 5.4.2. Header Protection Sample
++ * 5.4.3. AES-Based Header Protection
++ * 5.4.4. ChaCha20-Based Header Protection
++ *
++ * the Packet Number field is assumed to be 4 bytes long
++ * AES and ChaCha20 algorithms sample 16 bytes
++ */
++
++ if (len < EVP_GCM_TLS_TAG_LEN + 4) {
++ return NGX_DECLINED;
++ }
++
++ sample = p + 4;
++
++ /* header protection */
++
++ if (ngx_quic_tls_hp(pkt->log, ciphers.hp, secret, mask, sample)
++ != NGX_OK)
++ {
++ return NGX_DECLINED;
++ }
++
++ pkt->flags ^= mask[0] & ngx_quic_pkt_hp_mask(pkt->flags);
++
++ if (ngx_quic_short_pkt(pkt->flags)) {
++ key_phase = (pkt->flags & NGX_QUIC_PKT_KPHASE) != 0;
++
++ if (key_phase != pkt->key_phase) {
++ secret = &pkt->keys->next_key.client;
++ pkt->key_update = 1;
++ }
++ }
++
++ lpn = *largest_pn;
++
++ pnl = (pkt->flags & 0x03) + 1;
++ pn = ngx_quic_parse_pn(&p, pnl, &mask[1], &lpn);
++
++ pkt->pn = pn;
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
++ "quic packet rx clearflags:%xd", pkt->flags);
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
++ "quic packet rx number:%uL len:%xi", pn, pnl);
++
++ /* packet protection */
++
++ in.data = p;
++ in.len = len - pnl;
++
++ ad.len = p - pkt->data;
++ ad.data = pkt->plaintext;
++
++ ngx_memcpy(ad.data, pkt->data, ad.len);
++ ad.data[0] = pkt->flags;
++
++ do {
++ ad.data[ad.len - pnl] = pn >> (8 * (pnl - 1)) % 256;
++ } while (--pnl);
++
++ ngx_memcpy(nonce, secret->iv.data, secret->iv.len);
++ ngx_quic_compute_nonce(nonce, sizeof(nonce), pn);
++
++#ifdef NGX_QUIC_DEBUG_CRYPTO
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
++ "quic ad len:%uz %xV", ad.len, &ad);
++#endif
++
++ pkt->payload.len = in.len - EVP_GCM_TLS_TAG_LEN;
++ pkt->payload.data = pkt->plaintext + ad.len;
++
++ rc = ngx_quic_tls_open(ciphers.c, secret, &pkt->payload,
++ nonce, &in, &ad, pkt->log);
++ if (rc != NGX_OK) {
++ return NGX_DECLINED;
++ }
++
++ if (pkt->payload.len == 0) {
++ /*
++ * RFC 9000, 12.4. Frames and Frame Types
++ *
++ * An endpoint MUST treat receipt of a packet containing no
++ * frames as a connection error of type PROTOCOL_VIOLATION.
++ */
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic zero-length packet");
++ pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION;
++ return NGX_ERROR;
++ }
++
++ if (pkt->flags & ngx_quic_pkt_rb_mask(pkt->flags)) {
++ /*
++ * RFC 9000, Reserved Bits
++ *
++ * An endpoint MUST treat receipt of a packet that has
++ * a non-zero value for these bits, after removing both
++ * packet and header protection, as a connection error
++ * of type PROTOCOL_VIOLATION.
++ */
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
++ "quic reserved bit set in packet");
++ pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION;
++ return NGX_ERROR;
++ }
++
++#if defined(NGX_QUIC_DEBUG_CRYPTO) && defined(NGX_QUIC_DEBUG_PACKETS)
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
++ "quic packet payload len:%uz %xV",
++ pkt->payload.len, &pkt->payload);
++#endif
++
++ *largest_pn = lpn;
++
++ return NGX_OK;
++}
+diff -r 67408b4a12c0 src/event/quic/ngx_event_quic_protection.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_protection.h Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,37 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#ifndef _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_
++#define _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++
++#include <ngx_event_quic_transport.h>
++
++
++#define NGX_QUIC_ENCRYPTION_LAST ((ssl_encryption_application) + 1)
++
++
++ngx_quic_keys_t *ngx_quic_keys_new(ngx_pool_t *pool);
++ngx_int_t ngx_quic_keys_set_initial_secret(ngx_pool_t *pool,
++ ngx_quic_keys_t *keys, ngx_str_t *secret, uint32_t version);
++ngx_int_t ngx_quic_keys_set_encryption_secret(ngx_pool_t *pool,
++ ngx_uint_t is_write, ngx_quic_keys_t *keys,
++ enum ssl_encryption_level_t level, const SSL_CIPHER *cipher,
++ const uint8_t *secret, size_t secret_len);
++ngx_uint_t ngx_quic_keys_available(ngx_quic_keys_t *keys,
++ enum ssl_encryption_level_t level);
++void ngx_quic_keys_discard(ngx_quic_keys_t *keys,
++ enum ssl_encryption_level_t level);
++void ngx_quic_keys_switch(ngx_connection_t *c, ngx_quic_keys_t *keys);
++ngx_int_t ngx_quic_keys_update(ngx_connection_t *c, ngx_quic_keys_t *keys);
++ngx_int_t ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_str_t *res);
++ngx_int_t ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn);
++
++
++#endif /* _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ */
+diff -r 67408b4a12c0 src/event/quic/ngx_event_quic_socket.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_socket.c Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,311 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++#include <ngx_event.h>
++#include <ngx_event_quic_connection.h>
++
++
++ngx_int_t
++ngx_quic_open_sockets(ngx_connection_t *c, ngx_quic_connection_t *qc,
++ ngx_quic_header_t *pkt)
++{
++ ngx_quic_path_t *path;
++ ngx_quic_socket_t *qsock, *tmp;
++ ngx_quic_client_id_t *cid;
++
++ /*
++ * qc->nclient_ids = 0
++ * qc->nsockets = 0
++ * qc->max_retired_seqnum = 0
++ * qc->client_seqnum = 0
++ */
++
++ ngx_queue_init(&qc->sockets);
++ ngx_queue_init(&qc->free_sockets);
++
++ ngx_queue_init(&qc->paths);
++ ngx_queue_init(&qc->free_paths);
++
++ ngx_queue_init(&qc->client_ids);
++ ngx_queue_init(&qc->free_client_ids);
++
++ qc->tp.original_dcid.len = pkt->odcid.len;
++ qc->tp.original_dcid.data = ngx_pstrdup(c->pool, &pkt->odcid);
++ if (qc->tp.original_dcid.data == NULL) {
++ return NGX_ERROR;
++ }
++
++ /* socket to use for further processing (id auto-generated) */
++ qsock = ngx_quic_create_socket(c, qc);
++ if (qsock == NULL) {
++ return NGX_ERROR;
++ }
++
++ /* socket is listening at new server id */
++ if (ngx_quic_listen(c, qc, qsock) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ qc->tp.initial_scid.len = qsock->sid.len;
++ qc->tp.initial_scid.data = ngx_pnalloc(c->pool, qsock->sid.len);
++ if (qc->tp.initial_scid.data == NULL) {
++ goto failed;
++ }
++ ngx_memcpy(qc->tp.initial_scid.data, qsock->sid.id, qsock->sid.len);
++
++ /* for all packets except first, this is set at udp layer */
++ c->udp = &qsock->udp;
++
++ /* ngx_quic_get_connection(c) macro is now usable */
++
++ /* we have a client identified by scid */
++ cid = ngx_quic_create_client_id(c, &pkt->scid, 0, NULL);
++ if (cid == NULL) {
++ goto failed;
++ }
++
++ /* the client arrived from this path */
++ path = ngx_quic_add_path(c, c->sockaddr, c->socklen);
++ if (path == NULL) {
++ goto failed;
++ }
++
++ if (pkt->validated) {
++ path->state = NGX_QUIC_PATH_VALIDATED;
++ path->limited = 0;
++ }
++
++ /* now bind socket to client and path */
++ ngx_quic_connect(c, qsock, path, cid);
++
++ tmp = ngx_pcalloc(c->pool, sizeof(ngx_quic_socket_t));
++ if (tmp == NULL) {
++ goto failed;
++ }
++
++ tmp->sid.seqnum = NGX_QUIC_UNSET_PN; /* temporary socket */
++
++ ngx_memcpy(tmp->sid.id, pkt->odcid.data, pkt->odcid.len);
++ tmp->sid.len = pkt->odcid.len;
++
++ if (ngx_quic_listen(c, qc, tmp) != NGX_OK) {
++ goto failed;
++ }
++
++ ngx_quic_connect(c, tmp, path, cid);
++
++ /* use this socket as default destination */
++ qc->socket = qsock;
++
++ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic active socket is #%uL:%uL:%uL (%s)",
++ qsock->sid.seqnum, qsock->cid->seqnum, qsock->path->seqnum,
++ ngx_quic_path_state_str(qsock->path));
++
++ return NGX_OK;
++
++failed:
++
++ ngx_rbtree_delete(&c->listening->rbtree, &qsock->udp.node);
++ c->udp = NULL;
++
++ return NGX_ERROR;
++}
++
++
++ngx_quic_socket_t *
++ngx_quic_create_socket(ngx_connection_t *c, ngx_quic_connection_t *qc)
++{
++ ngx_queue_t *q;
++ ngx_quic_socket_t *sock;
++
++ if (!ngx_queue_empty(&qc->free_sockets)) {
++
++ q = ngx_queue_head(&qc->free_sockets);
++ sock = ngx_queue_data(q, ngx_quic_socket_t, queue);
++
++ ngx_queue_remove(&sock->queue);
++
++ ngx_memzero(sock, sizeof(ngx_quic_socket_t));
++
++ } else {
++
++ sock = ngx_pcalloc(c->pool, sizeof(ngx_quic_socket_t));
++ if (sock == NULL) {
++ return NULL;
++ }
++ }
++
++ sock->sid.len = NGX_QUIC_SERVER_CID_LEN;
++ if (ngx_quic_create_server_id(c, sock->sid.id) != NGX_OK) {
++ return NULL;
++ }
++
++ sock->sid.seqnum = qc->server_seqnum++;
++
++ return sock;
++}
++
++
++void
++ngx_quic_close_socket(ngx_connection_t *c, ngx_quic_socket_t *qsock)
++{
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ ngx_queue_remove(&qsock->queue);
++ ngx_queue_insert_head(&qc->free_sockets, &qsock->queue);
++
++ ngx_rbtree_delete(&c->listening->rbtree, &qsock->udp.node);
++ qc->nsockets--;
++
++ if (qsock->path) {
++ ngx_quic_unref_path(c, qsock->path);
++ }
++
++ if (qsock->cid) {
++ ngx_quic_unref_client_id(c, qsock->cid);
++ }
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic socket #%L closed nsock:%ui",
++ (int64_t) qsock->sid.seqnum, qc->nsockets);
++}
++
++
++void
++ngx_quic_unref_path(ngx_connection_t *c, ngx_quic_path_t *path)
++{
++ ngx_quic_connection_t *qc;
++
++ path->refcnt--;
++
++ if (path->refcnt) {
++ return;
++ }
++
++ qc = ngx_quic_get_connection(c);
++
++ ngx_queue_remove(&path->queue);
++ ngx_queue_insert_head(&qc->free_paths, &path->queue);
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic path #%uL addr:%V removed",
++ path->seqnum, &path->addr_text);
++}
++
++
++ngx_int_t
++ngx_quic_listen(ngx_connection_t *c, ngx_quic_connection_t *qc,
++ ngx_quic_socket_t *qsock)
++{
++ ngx_str_t id;
++ ngx_quic_server_id_t *sid;
++
++ sid = &qsock->sid;
++
++ id.data = sid->id;
++ id.len = sid->len;
++
++ ngx_insert_udp_connection(c, &qsock->udp, &id);
++
++ ngx_queue_insert_tail(&qc->sockets, &qsock->queue);
++
++ qc->nsockets++;
++ qsock->quic = qc;
++
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic socket #%L listening at sid:%xV nsock:%ui",
++ (int64_t) sid->seqnum, &id, qc->nsockets);
++
++ return NGX_OK;
++}
++
++
++void
++ngx_quic_connect(ngx_connection_t *c, ngx_quic_socket_t *sock,
++ ngx_quic_path_t *path, ngx_quic_client_id_t *cid)
++{
++ sock->path = path;
++ path->refcnt++;
++
++ sock->cid = cid;
++ cid->refcnt++;
++
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic socket #%L connected to cid #%uL path:%uL",
++ (int64_t) sock->sid.seqnum,
++ sock->cid->seqnum, path->seqnum);
++}
++
++
++void
++ngx_quic_close_sockets(ngx_connection_t *c)
++{
++ ngx_queue_t *q;
++ ngx_quic_socket_t *qsock;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ while (!ngx_queue_empty(&qc->sockets)) {
++ q = ngx_queue_head(&qc->sockets);
++ qsock = ngx_queue_data(q, ngx_quic_socket_t, queue);
++
++ ngx_quic_close_socket(c, qsock);
++ }
++}
++
++
++ngx_quic_socket_t *
++ngx_quic_find_socket(ngx_connection_t *c, uint64_t seqnum)
++{
++ ngx_queue_t *q;
++ ngx_quic_socket_t *qsock;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ for (q = ngx_queue_head(&qc->sockets);
++ q != ngx_queue_sentinel(&qc->sockets);
++ q = ngx_queue_next(q))
++ {
++ qsock = ngx_queue_data(q, ngx_quic_socket_t, queue);
++
++ if (qsock->sid.seqnum == seqnum) {
++ return qsock;
++ }
++ }
++
++ return NULL;
++}
++
++
++ngx_quic_socket_t *
++ngx_quic_get_unconnected_socket(ngx_connection_t *c)
++{
++ ngx_queue_t *q;
++ ngx_quic_socket_t *sock;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ for (q = ngx_queue_head(&qc->sockets);
++ q != ngx_queue_sentinel(&qc->sockets);
++ q = ngx_queue_next(q))
++ {
++ sock = ngx_queue_data(q, ngx_quic_socket_t, queue);
++
++ if (sock->cid == NULL) {
++ return sock;
++ }
++ }
++
++ return NULL;
++}
+diff -r 67408b4a12c0 src/event/quic/ngx_event_quic_socket.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_socket.h Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,33 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#ifndef _NGX_EVENT_QUIC_SOCKET_H_INCLUDED_
++#define _NGX_EVENT_QUIC_SOCKET_H_INCLUDED_
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++
++
++ngx_int_t ngx_quic_open_sockets(ngx_connection_t *c,
++ ngx_quic_connection_t *qc, ngx_quic_header_t *pkt);
++void ngx_quic_close_sockets(ngx_connection_t *c);
++
++ngx_quic_socket_t *ngx_quic_create_socket(ngx_connection_t *c,
++ ngx_quic_connection_t *qc);
++ngx_int_t ngx_quic_listen(ngx_connection_t *c, ngx_quic_connection_t *qc,
++ ngx_quic_socket_t *qsock);
++void ngx_quic_close_socket(ngx_connection_t *c, ngx_quic_socket_t *qsock);
++
++void ngx_quic_unref_path(ngx_connection_t *c, ngx_quic_path_t *path);
++void ngx_quic_connect(ngx_connection_t *c, ngx_quic_socket_t *qsock,
++ ngx_quic_path_t *path, ngx_quic_client_id_t *cid);
++
++ngx_quic_socket_t *ngx_quic_find_socket(ngx_connection_t *c, uint64_t seqnum);
++ngx_quic_socket_t *ngx_quic_get_unconnected_socket(ngx_connection_t *c);
++
++
++#endif /* _NGX_EVENT_QUIC_SOCKET_H_INCLUDED_ */
+diff -r 67408b4a12c0 src/event/quic/ngx_event_quic_ssl.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_ssl.c Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,614 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++#include <ngx_event.h>
++#include <ngx_event_quic_connection.h>
++
++
++/*
++ * RFC 9000, 7.5. Cryptographic Message Buffering
++ *
++ * Implementations MUST support buffering at least 4096 bytes of data
++ */
++#define NGX_QUIC_MAX_BUFFERED 65535
++
++
++#if BORINGSSL_API_VERSION >= 10
++static int ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn,
++ enum ssl_encryption_level_t level, const SSL_CIPHER *cipher,
++ const uint8_t *secret, size_t secret_len);
++static int ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn,
++ enum ssl_encryption_level_t level, const SSL_CIPHER *cipher,
++ const uint8_t *secret, size_t secret_len);
++#else
++static int ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn,
++ enum ssl_encryption_level_t level, const uint8_t *read_secret,
++ const uint8_t *write_secret, size_t secret_len);
++#endif
++
++static int ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn,
++ enum ssl_encryption_level_t level, const uint8_t *data, size_t len);
++static int ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn);
++static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn,
++ enum ssl_encryption_level_t level, uint8_t alert);
++static ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data);
++
++
++static SSL_QUIC_METHOD quic_method = {
++#if BORINGSSL_API_VERSION >= 10
++ ngx_quic_set_read_secret,
++ ngx_quic_set_write_secret,
++#else
++ ngx_quic_set_encryption_secrets,
++#endif
++ ngx_quic_add_handshake_data,
++ ngx_quic_flush_flight,
++ ngx_quic_send_alert,
++};
++
++
++#if BORINGSSL_API_VERSION >= 10
++
++static int
++ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn,
++ enum ssl_encryption_level_t level, const SSL_CIPHER *cipher,
++ const uint8_t *rsecret, size_t secret_len)
++{
++ ngx_connection_t *c;
++ ngx_quic_connection_t *qc;
++
++ c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
++ qc = ngx_quic_get_connection(c);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic ngx_quic_set_read_secret() level:%d", level);
++#ifdef NGX_QUIC_DEBUG_CRYPTO
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic read secret len:%uz %*xs", secret_len,
++ secret_len, rsecret);
++#endif
++
++ if (ngx_quic_keys_set_encryption_secret(c->pool, 0, qc->keys, level,
++ cipher, rsecret, secret_len)
++ != NGX_OK)
++ {
++ return 0;
++ }
++
++ if (level == ssl_encryption_early_data) {
++ if (ngx_quic_init_streams(c) != NGX_OK) {
++ return 0;
++ }
++ }
++
++ return 1;
++}
++
++
++static int
++ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn,
++ enum ssl_encryption_level_t level, const SSL_CIPHER *cipher,
++ const uint8_t *wsecret, size_t secret_len)
++{
++ ngx_connection_t *c;
++ ngx_quic_connection_t *qc;
++
++ c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
++ qc = ngx_quic_get_connection(c);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic ngx_quic_set_write_secret() level:%d", level);
++#ifdef NGX_QUIC_DEBUG_CRYPTO
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic write secret len:%uz %*xs", secret_len,
++ secret_len, wsecret);
++#endif
++
++ if (ngx_quic_keys_set_encryption_secret(c->pool, 1, qc->keys, level,
++ cipher, wsecret, secret_len)
++ != NGX_OK)
++ {
++ return 0;
++ }
++
++ return 1;
++}
++
++#else
++
++static int
++ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn,
++ enum ssl_encryption_level_t level, const uint8_t *rsecret,
++ const uint8_t *wsecret, size_t secret_len)
++{
++ ngx_connection_t *c;
++ const SSL_CIPHER *cipher;
++ ngx_quic_connection_t *qc;
++
++ c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
++ qc = ngx_quic_get_connection(c);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic ngx_quic_set_encryption_secrets() level:%d", level);
++#ifdef NGX_QUIC_DEBUG_CRYPTO
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic read secret len:%uz %*xs", secret_len,
++ secret_len, rsecret);
++#endif
++
++ cipher = SSL_get_current_cipher(ssl_conn);
++
++ if (ngx_quic_keys_set_encryption_secret(c->pool, 0, qc->keys, level,
++ cipher, rsecret, secret_len)
++ != NGX_OK)
++ {
++ return 0;
++ }
++
++ if (level == ssl_encryption_early_data) {
++ if (ngx_quic_init_streams(c) != NGX_OK) {
++ return 0;
++ }
++
++ return 1;
++ }
++
++#ifdef NGX_QUIC_DEBUG_CRYPTO
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic write secret len:%uz %*xs", secret_len,
++ secret_len, wsecret);
++#endif
++
++ if (ngx_quic_keys_set_encryption_secret(c->pool, 1, qc->keys, level,
++ cipher, wsecret, secret_len)
++ != NGX_OK)
++ {
++ return 0;
++ }
++
++ return 1;
++}
++
++#endif
++
++
++static int
++ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn,
++ enum ssl_encryption_level_t level, const uint8_t *data, size_t len)
++{
++ u_char *p, *end;
++ size_t client_params_len;
++ const uint8_t *client_params;
++ ngx_quic_tp_t ctp;
++ ngx_quic_frame_t *frame;
++ ngx_connection_t *c;
++ ngx_quic_send_ctx_t *ctx;
++ ngx_quic_connection_t *qc;
++#if defined(TLSEXT_TYPE_application_layer_protocol_negotiation)
++ unsigned int alpn_len;
++ const unsigned char *alpn_data;
++#endif
++
++ c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
++ qc = ngx_quic_get_connection(c);
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic ngx_quic_add_handshake_data");
++
++ if (!qc->client_tp_done) {
++ /*
++ * things to do once during handshake: check ALPN and transport
++ * parameters; we want to break handshake if something is wrong
++ * here;
++ */
++
++#if defined(TLSEXT_TYPE_application_layer_protocol_negotiation)
++
++ SSL_get0_alpn_selected(ssl_conn, &alpn_data, &alpn_len);
++
++ if (alpn_len == 0) {
++ qc->error = 0x100 + SSL_AD_NO_APPLICATION_PROTOCOL;
++ qc->error_reason = "unsupported protocol in ALPN extension";
++
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic unsupported protocol in ALPN extension");
++ return 0;
++ }
++
++#endif
++
++ SSL_get_peer_quic_transport_params(ssl_conn, &client_params,
++ &client_params_len);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic SSL_get_peer_quic_transport_params():"
++ " params_len:%ui", client_params_len);
++
++ if (client_params_len == 0) {
++ /* RFC 9001, 8.2. QUIC Transport Parameters Extension */
++ qc->error = NGX_QUIC_ERR_CRYPTO(SSL_AD_MISSING_EXTENSION);
++ qc->error_reason = "missing transport parameters";
++
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "missing transport parameters");
++ return 0;
++ }
++
++ p = (u_char *) client_params;
++ end = p + client_params_len;
++
++ /* defaults for parameters not sent by client */
++ ngx_memcpy(&ctp, &qc->ctp, sizeof(ngx_quic_tp_t));
++
++ if (ngx_quic_parse_transport_params(p, end, &ctp, c->log)
++ != NGX_OK)
++ {
++ qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR;
++ qc->error_reason = "failed to process transport parameters";
++
++ return 0;
++ }
++
++ if (ngx_quic_apply_transport_params(c, &ctp) != NGX_OK) {
++ return 0;
++ }
++
++ qc->client_tp_done = 1;
++ }
++
++ ctx = ngx_quic_get_send_ctx(qc, level);
++
++ frame = ngx_quic_alloc_frame(c);
++ if (frame == NULL) {
++ return 0;
++ }
++
++ frame->data = ngx_quic_copy_buf(c, (u_char *) data, len);
++ if (frame->data == NGX_CHAIN_ERROR) {
++ return 0;
++ }
++
++ frame->level = level;
++ frame->type = NGX_QUIC_FT_CRYPTO;
++ frame->u.crypto.offset = ctx->crypto_sent;
++ frame->u.crypto.length = len;
++
++ ctx->crypto_sent += len;
++
++ ngx_quic_queue_frame(qc, frame);
++
++ return 1;
++}
++
++
++static int
++ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn)
++{
++#if (NGX_DEBUG)
++ ngx_connection_t *c;
++
++ c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic ngx_quic_flush_flight()");
++#endif
++ return 1;
++}
++
++
++static int
++ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level,
++ uint8_t alert)
++{
++ ngx_connection_t *c;
++ ngx_quic_connection_t *qc;
++
++ c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic ngx_quic_send_alert() level:%s alert:%d",
++ ngx_quic_level_name(level), (int) alert);
++
++ /* already closed on regular shutdown */
++
++ qc = ngx_quic_get_connection(c);
++ if (qc == NULL) {
++ return 1;
++ }
++
++ qc->error = NGX_QUIC_ERR_CRYPTO(alert);
++
++ return 1;
++}
++
++
++ngx_int_t
++ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt,
++ ngx_quic_frame_t *frame)
++{
++ size_t len;
++ uint64_t last;
++ ngx_buf_t *b;
++ ngx_chain_t *cl, **ll;
++ ngx_quic_send_ctx_t *ctx;
++ ngx_quic_connection_t *qc;
++ ngx_quic_crypto_frame_t *f;
++
++ qc = ngx_quic_get_connection(c);
++ ctx = ngx_quic_get_send_ctx(qc, pkt->level);
++ f = &frame->u.crypto;
++
++ /* no overflow since both values are 62-bit */
++ last = f->offset + f->length;
++
++ if (last > ctx->crypto_received + NGX_QUIC_MAX_BUFFERED) {
++ qc->error = NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED;
++ return NGX_ERROR;
++ }
++
++ if (last <= ctx->crypto_received) {
++ if (pkt->level == ssl_encryption_initial) {
++ /* speeding up handshake completion */
++
++ if (!ngx_queue_empty(&ctx->sent)) {
++ ngx_quic_resend_frames(c, ctx);
++
++ ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_handshake);
++ while (!ngx_queue_empty(&ctx->sent)) {
++ ngx_quic_resend_frames(c, ctx);
++ }
++ }
++ }
++
++ return NGX_OK;
++ }
++
++ if (f->offset > ctx->crypto_received) {
++ if (ngx_quic_write_chain(c, &ctx->crypto, frame->data, f->length,
++ f->offset - ctx->crypto_received)
++ == NGX_CHAIN_ERROR)
++ {
++ return NGX_ERROR;
++ }
++
++ return NGX_OK;
++ }
++
++ ngx_quic_trim_chain(frame->data, ctx->crypto_received - f->offset);
++
++ if (ngx_quic_crypto_input(c, frame->data) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ ngx_quic_trim_chain(ctx->crypto, last - ctx->crypto_received);
++ ctx->crypto_received = last;
++
++ cl = ctx->crypto;
++ ll = &cl;
++ len = 0;
++
++ while (*ll) {
++ b = (*ll)->buf;
++
++ if (b->sync && b->pos != b->last) {
++ /* hole */
++ break;
++ }
++
++ len += b->last - b->pos;
++ ll = &(*ll)->next;
++ }
++
++ ctx->crypto_received += len;
++ ctx->crypto = *ll;
++ *ll = NULL;
++
++ if (cl) {
++ if (ngx_quic_crypto_input(c, cl) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ ngx_quic_free_chain(c, cl);
++ }
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data)
++{
++ int n, sslerr;
++ ngx_buf_t *b;
++ ngx_chain_t *cl;
++ ngx_ssl_conn_t *ssl_conn;
++ ngx_quic_frame_t *frame;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ ssl_conn = c->ssl->connection;
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic SSL_quic_read_level:%d SSL_quic_write_level:%d",
++ (int) SSL_quic_read_level(ssl_conn),
++ (int) SSL_quic_write_level(ssl_conn));
++
++ for (cl = data; cl; cl = cl->next) {
++ b = cl->buf;
++
++ if (!SSL_provide_quic_data(ssl_conn, SSL_quic_read_level(ssl_conn),
++ b->pos, b->last - b->pos))
++ {
++ ngx_ssl_error(NGX_LOG_INFO, c->log, 0,
++ "SSL_provide_quic_data() failed");
++ return NGX_ERROR;
++ }
++ }
++
++ n = SSL_do_handshake(ssl_conn);
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic SSL_quic_read_level:%d SSL_quic_write_level:%d",
++ (int) SSL_quic_read_level(ssl_conn),
++ (int) SSL_quic_write_level(ssl_conn));
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n);
++
++ if (n <= 0) {
++ sslerr = SSL_get_error(ssl_conn, n);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d",
++ sslerr);
++
++ if (sslerr != SSL_ERROR_WANT_READ) {
++ ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_do_handshake() failed");
++ qc->error_reason = "handshake failed";
++ return NGX_ERROR;
++ }
++
++ return NGX_OK;
++ }
++
++ if (SSL_in_init(ssl_conn)) {
++ return NGX_OK;
++ }
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic ssl cipher:%s", SSL_get_cipher(ssl_conn));
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic handshake completed successfully");
++
++ c->ssl->handshaked = 1;
++
++ frame = ngx_quic_alloc_frame(c);
++ if (frame == NULL) {
++ return NGX_ERROR;
++ }
++
++ frame->level = ssl_encryption_application;
++ frame->type = NGX_QUIC_FT_HANDSHAKE_DONE;
++ ngx_quic_queue_frame(qc, frame);
++
++ if (qc->conf->retry) {
++ if (ngx_quic_send_new_token(c, qc->socket->path) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++
++ /*
++ * RFC 9001, 9.5. Header Protection Timing Side Channels
++ *
++ * Generating next keys before a key update is received.
++ */
++
++ if (ngx_quic_keys_update(c, qc->keys) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ /*
++ * RFC 9001, 4.9.2. Discarding Handshake Keys
++ *
++ * An endpoint MUST discard its Handshake keys
++ * when the TLS handshake is confirmed.
++ */
++ ngx_quic_discard_ctx(c, ssl_encryption_handshake);
++
++ /* start accepting clients on negotiated number of server ids */
++ if (ngx_quic_create_sockets(c) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ if (ngx_quic_init_streams(c) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_init_connection(ngx_connection_t *c)
++{
++ u_char *p;
++ size_t clen;
++ ssize_t len;
++ ngx_str_t dcid;
++ ngx_ssl_conn_t *ssl_conn;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ if (ngx_ssl_create_connection(qc->conf->ssl, c, NGX_SSL_BUFFER) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ c->ssl->no_wait_shutdown = 1;
++
++ ssl_conn = c->ssl->connection;
++
++ if (SSL_set_quic_method(ssl_conn, &quic_method) == 0) {
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic SSL_set_quic_method() failed");
++ return NGX_ERROR;
++ }
++
++#ifdef SSL_READ_EARLY_DATA_SUCCESS
++ if (SSL_CTX_get_max_early_data(qc->conf->ssl->ctx)) {
++ SSL_set_quic_early_data_enabled(ssl_conn, 1);
++ }
++#endif
++
++#if BORINGSSL_API_VERSION >= 13
++ SSL_set_quic_use_legacy_codepoint(ssl_conn, qc->version != 1);
++#endif
++
++ dcid.data = qc->socket->sid.id;
++ dcid.len = qc->socket->sid.len;
++
++ if (ngx_quic_new_sr_token(c, &dcid, qc->conf->sr_token_key, qc->tp.sr_token)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ len = ngx_quic_create_transport_params(NULL, NULL, &qc->tp, &clen);
++ /* always succeeds */
++
++ p = ngx_pnalloc(c->pool, len);
++ if (p == NULL) {
++ return NGX_ERROR;
++ }
++
++ len = ngx_quic_create_transport_params(p, p + len, &qc->tp, NULL);
++ if (len < 0) {
++ return NGX_ERROR;
++ }
++
++#ifdef NGX_QUIC_DEBUG_PACKETS
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic transport parameters len:%uz %*xs", len, len, p);
++#endif
++
++ if (SSL_set_quic_transport_params(ssl_conn, p, len) == 0) {
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic SSL_set_quic_transport_params() failed");
++ return NGX_ERROR;
++ }
++
++#if BORINGSSL_API_VERSION >= 11
++ if (SSL_set_quic_early_data_context(ssl_conn, p, clen) == 0) {
++ ngx_log_error(NGX_LOG_INFO, c->log, 0,
++ "quic SSL_set_quic_early_data_context() failed");
++ return NGX_ERROR;
++ }
++#endif
++
++ return NGX_OK;
++}
+diff -r 67408b4a12c0 src/event/quic/ngx_event_quic_ssl.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_ssl.h Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,19 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#ifndef _NGX_EVENT_QUIC_SSL_H_INCLUDED_
++#define _NGX_EVENT_QUIC_SSL_H_INCLUDED_
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++
++ngx_int_t ngx_quic_init_connection(ngx_connection_t *c);
++
++ngx_int_t ngx_quic_handle_crypto_frame(ngx_connection_t *c,
++ ngx_quic_header_t *pkt, ngx_quic_frame_t *frame);
++
++#endif /* _NGX_EVENT_QUIC_SSL_H_INCLUDED_ */
+diff -r 67408b4a12c0 src/event/quic/ngx_event_quic_streams.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_streams.c Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,1608 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++#include <ngx_event.h>
++#include <ngx_event_quic_connection.h>
++
++
++#define NGX_QUIC_STREAM_GONE (void *) -1
++
++
++static ngx_int_t ngx_quic_shutdown_stream_send(ngx_connection_t *c);
++static ngx_int_t ngx_quic_shutdown_stream_recv(ngx_connection_t *c);
++static ngx_quic_stream_t *ngx_quic_get_stream(ngx_connection_t *c, uint64_t id);
++static ngx_int_t ngx_quic_reject_stream(ngx_connection_t *c, uint64_t id);
++static void ngx_quic_init_stream_handler(ngx_event_t *ev);
++static void ngx_quic_init_streams_handler(ngx_connection_t *c);
++static ngx_quic_stream_t *ngx_quic_create_stream(ngx_connection_t *c,
++ uint64_t id);
++static void ngx_quic_empty_handler(ngx_event_t *ev);
++static ssize_t ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf,
++ size_t size);
++static ssize_t ngx_quic_stream_send(ngx_connection_t *c, u_char *buf,
++ size_t size);
++static ngx_chain_t *ngx_quic_stream_send_chain(ngx_connection_t *c,
++ ngx_chain_t *in, off_t limit);
++static size_t ngx_quic_max_stream_flow(ngx_connection_t *c);
++static void ngx_quic_stream_cleanup_handler(void *data);
++static ngx_int_t ngx_quic_control_flow(ngx_connection_t *c, uint64_t last);
++static ngx_int_t ngx_quic_update_flow(ngx_connection_t *c, uint64_t last);
++static ngx_int_t ngx_quic_update_max_stream_data(ngx_connection_t *c);
++static ngx_int_t ngx_quic_update_max_data(ngx_connection_t *c);
++
++
++ngx_connection_t *
++ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi)
++{
++ uint64_t id;
++ ngx_quic_stream_t *qs, *nqs;
++ ngx_quic_connection_t *qc;
++
++ qs = c->quic;
++ qc = ngx_quic_get_connection(qs->parent);
++
++ if (bidi) {
++ if (qc->streams.server_streams_bidi
++ >= qc->streams.server_max_streams_bidi)
++ {
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic too many server bidi streams:%uL",
++ qc->streams.server_streams_bidi);
++ return NULL;
++ }
++
++ id = (qc->streams.server_streams_bidi << 2)
++ | NGX_QUIC_STREAM_SERVER_INITIATED;
++
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic creating server bidi stream"
++ " streams:%uL max:%uL id:0x%xL",
++ qc->streams.server_streams_bidi,
++ qc->streams.server_max_streams_bidi, id);
++
++ qc->streams.server_streams_bidi++;
++
++ } else {
++ if (qc->streams.server_streams_uni
++ >= qc->streams.server_max_streams_uni)
++ {
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic too many server uni streams:%uL",
++ qc->streams.server_streams_uni);
++ return NULL;
++ }
++
++ id = (qc->streams.server_streams_uni << 2)
++ | NGX_QUIC_STREAM_SERVER_INITIATED
++ | NGX_QUIC_STREAM_UNIDIRECTIONAL;
++
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic creating server uni stream"
++ " streams:%uL max:%uL id:0x%xL",
++ qc->streams.server_streams_uni,
++ qc->streams.server_max_streams_uni, id);
++
++ qc->streams.server_streams_uni++;
++ }
++
++ nqs = ngx_quic_create_stream(qs->parent, id);
++ if (nqs == NULL) {
++ return NULL;
++ }
++
++ return nqs->connection;
++}
++
++
++void
++ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp,
++ ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
++{
++ ngx_rbtree_node_t **p;
++ ngx_quic_stream_t *qn, *qnt;
++
++ for ( ;; ) {
++ qn = (ngx_quic_stream_t *) node;
++ qnt = (ngx_quic_stream_t *) temp;
++
++ p = (qn->id < qnt->id) ? &temp->left : &temp->right;
++
++ if (*p == sentinel) {
++ break;
++ }
++
++ temp = *p;
++ }
++
++ *p = node;
++ node->parent = temp;
++ node->left = sentinel;
++ node->right = sentinel;
++ ngx_rbt_red(node);
++}
++
++
++ngx_quic_stream_t *
++ngx_quic_find_stream(ngx_rbtree_t *rbtree, uint64_t id)
++{
++ ngx_rbtree_node_t *node, *sentinel;
++ ngx_quic_stream_t *qn;
++
++ node = rbtree->root;
++ sentinel = rbtree->sentinel;
++
++ while (node != sentinel) {
++ qn = (ngx_quic_stream_t *) node;
++
++ if (id == qn->id) {
++ return qn;
++ }
++
++ node = (id < qn->id) ? node->left : node->right;
++ }
++
++ return NULL;
++}
++
++
++ngx_int_t
++ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc)
++{
++ ngx_pool_t *pool;
++ ngx_queue_t *q;
++ ngx_event_t *rev, *wev;
++ ngx_rbtree_t *tree;
++ ngx_rbtree_node_t *node;
++ ngx_quic_stream_t *qs;
++
++#if (NGX_DEBUG)
++ ngx_uint_t ns;
++#endif
++
++ while (!ngx_queue_empty(&qc->streams.uninitialized)) {
++ q = ngx_queue_head(&qc->streams.uninitialized);
++ ngx_queue_remove(q);
++
++ qs = ngx_queue_data(q, ngx_quic_stream_t, queue);
++ pool = qs->connection->pool;
++
++ ngx_close_connection(qs->connection);
++ ngx_destroy_pool(pool);
++ }
++
++ tree = &qc->streams.tree;
++
++ if (tree->root == tree->sentinel) {
++ return NGX_OK;
++ }
++
++#if (NGX_DEBUG)
++ ns = 0;
++#endif
++
++ for (node = ngx_rbtree_min(tree->root, tree->sentinel);
++ node;
++ node = ngx_rbtree_next(tree, node))
++ {
++ qs = (ngx_quic_stream_t *) node;
++
++ rev = qs->connection->read;
++ rev->error = 1;
++ rev->ready = 1;
++
++ wev = qs->connection->write;
++ wev->error = 1;
++ wev->ready = 1;
++
++ ngx_post_event(rev, &ngx_posted_events);
++
++ if (rev->timer_set) {
++ ngx_del_timer(rev);
++ }
++
++#if (NGX_DEBUG)
++ ns++;
++#endif
++ }
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic connection has %ui active streams", ns);
++
++ return NGX_AGAIN;
++}
++
++
++ngx_int_t
++ngx_quic_reset_stream(ngx_connection_t *c, ngx_uint_t err)
++{
++ ngx_event_t *wev;
++ ngx_connection_t *pc;
++ ngx_quic_frame_t *frame;
++ ngx_quic_stream_t *qs;
++ ngx_quic_connection_t *qc;
++
++ wev = c->write;
++
++ if (wev->error) {
++ return NGX_OK;
++ }
++
++ qs = c->quic;
++ pc = qs->parent;
++ qc = ngx_quic_get_connection(pc);
++
++ frame = ngx_quic_alloc_frame(pc);
++ if (frame == NULL) {
++ return NGX_ERROR;
++ }
++
++ frame->level = ssl_encryption_application;
++ frame->type = NGX_QUIC_FT_RESET_STREAM;
++ frame->u.reset_stream.id = qs->id;
++ frame->u.reset_stream.error_code = err;
++ frame->u.reset_stream.final_size = c->sent;
++
++ ngx_quic_queue_frame(qc, frame);
++
++ wev->error = 1;
++ wev->ready = 1;
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_shutdown_stream(ngx_connection_t *c, int how)
++{
++ ngx_quic_stream_t *qs;
++
++ qs = c->quic;
++
++ if (how == NGX_RDWR_SHUTDOWN || how == NGX_WRITE_SHUTDOWN) {
++ if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED)
++ || (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0)
++ {
++ if (ngx_quic_shutdown_stream_send(c) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++ }
++
++ if (how == NGX_RDWR_SHUTDOWN || how == NGX_READ_SHUTDOWN) {
++ if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0
++ || (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0)
++ {
++ if (ngx_quic_shutdown_stream_recv(c) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++ }
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_shutdown_stream_send(ngx_connection_t *c)
++{
++ ngx_event_t *wev;
++ ngx_connection_t *pc;
++ ngx_quic_frame_t *frame;
++ ngx_quic_stream_t *qs;
++ ngx_quic_connection_t *qc;
++
++ wev = c->write;
++
++ if (wev->error) {
++ return NGX_OK;
++ }
++
++ qs = c->quic;
++ pc = qs->parent;
++ qc = ngx_quic_get_connection(pc);
++
++ frame = ngx_quic_alloc_frame(pc);
++ if (frame == NULL) {
++ return NGX_ERROR;
++ }
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic stream id:0x%xL send shutdown", qs->id);
++
++ frame->level = ssl_encryption_application;
++ frame->type = NGX_QUIC_FT_STREAM;
++ frame->u.stream.off = 1;
++ frame->u.stream.len = 1;
++ frame->u.stream.fin = 1;
++
++ frame->u.stream.stream_id = qs->id;
++ frame->u.stream.offset = c->sent;
++ frame->u.stream.length = 0;
++
++ ngx_quic_queue_frame(qc, frame);
++
++ wev->error = 1;
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_shutdown_stream_recv(ngx_connection_t *c)
++{
++ ngx_event_t *rev;
++ ngx_connection_t *pc;
++ ngx_quic_frame_t *frame;
++ ngx_quic_stream_t *qs;
++ ngx_quic_connection_t *qc;
++
++ rev = c->read;
++
++ if (rev->pending_eof || rev->error) {
++ return NGX_OK;
++ }
++
++ qs = c->quic;
++ pc = qs->parent;
++ qc = ngx_quic_get_connection(pc);
++
++ if (qc->conf->stream_close_code == 0) {
++ return NGX_OK;
++ }
++
++ frame = ngx_quic_alloc_frame(pc);
++ if (frame == NULL) {
++ return NGX_ERROR;
++ }
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic stream id:0x%xL recv shutdown", qs->id);
++
++ frame->level = ssl_encryption_application;
++ frame->type = NGX_QUIC_FT_STOP_SENDING;
++ frame->u.stop_sending.id = qs->id;
++ frame->u.stop_sending.error_code = qc->conf->stream_close_code;
++
++ ngx_quic_queue_frame(qc, frame);
++
++ rev->error = 1;
++
++ return NGX_OK;
++}
++
++
++static ngx_quic_stream_t *
++ngx_quic_get_stream(ngx_connection_t *c, uint64_t id)
++{
++ uint64_t min_id;
++ ngx_event_t *rev;
++ ngx_quic_stream_t *qs;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ qs = ngx_quic_find_stream(&qc->streams.tree, id);
++
++ if (qs) {
++ return qs;
++ }
++
++ if (qc->shutdown || qc->closing) {
++ return NGX_QUIC_STREAM_GONE;
++ }
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic stream id:0x%xL is missing", id);
++
++ if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) {
++
++ if (id & NGX_QUIC_STREAM_SERVER_INITIATED) {
++ if ((id >> 2) < qc->streams.server_streams_uni) {
++ return NGX_QUIC_STREAM_GONE;
++ }
++
++ qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR;
++ return NULL;
++ }
++
++ if ((id >> 2) < qc->streams.client_streams_uni) {
++ return NGX_QUIC_STREAM_GONE;
++ }
++
++ if ((id >> 2) >= qc->streams.client_max_streams_uni) {
++ qc->error = NGX_QUIC_ERR_STREAM_LIMIT_ERROR;
++ return NULL;
++ }
++
++ min_id = (qc->streams.client_streams_uni << 2)
++ | NGX_QUIC_STREAM_UNIDIRECTIONAL;
++ qc->streams.client_streams_uni = (id >> 2) + 1;
++
++ } else {
++
++ if (id & NGX_QUIC_STREAM_SERVER_INITIATED) {
++ if ((id >> 2) < qc->streams.server_streams_bidi) {
++ return NGX_QUIC_STREAM_GONE;
++ }
++
++ qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR;
++ return NULL;
++ }
++
++ if ((id >> 2) < qc->streams.client_streams_bidi) {
++ return NGX_QUIC_STREAM_GONE;
++ }
++
++ if ((id >> 2) >= qc->streams.client_max_streams_bidi) {
++ qc->error = NGX_QUIC_ERR_STREAM_LIMIT_ERROR;
++ return NULL;
++ }
++
++ min_id = (qc->streams.client_streams_bidi << 2);
++ qc->streams.client_streams_bidi = (id >> 2) + 1;
++ }
++
++ /*
++ * RFC 9000, 2.1. Stream Types and Identifiers
++ *
++ * successive streams of each type are created with numerically increasing
++ * stream IDs. A stream ID that is used out of order results in all
++ * streams of that type with lower-numbered stream IDs also being opened.
++ */
++
++#if (NGX_SUPPRESS_WARN)
++ qs = NULL;
++#endif
++
++ for ( /* void */ ; min_id <= id; min_id += 0x04) {
++
++ qs = ngx_quic_create_stream(c, min_id);
++
++ if (qs == NULL) {
++ if (ngx_quic_reject_stream(c, min_id) != NGX_OK) {
++ return NULL;
++ }
++
++ continue;
++ }
++
++ ngx_queue_insert_tail(&qc->streams.uninitialized, &qs->queue);
++
++ rev = qs->connection->read;
++ rev->handler = ngx_quic_init_stream_handler;
++
++ if (qc->streams.initialized) {
++ ngx_post_event(rev, &ngx_posted_events);
++ }
++ }
++
++ if (qs == NULL) {
++ return NGX_QUIC_STREAM_GONE;
++ }
++
++ return qs;
++}
++
++
++static ngx_int_t
++ngx_quic_reject_stream(ngx_connection_t *c, uint64_t id)
++{
++ uint64_t code;
++ ngx_quic_frame_t *frame;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ code = (id & NGX_QUIC_STREAM_UNIDIRECTIONAL)
++ ? qc->conf->stream_reject_code_uni
++ : qc->conf->stream_reject_code_bidi;
++
++ if (code == 0) {
++ return NGX_DECLINED;
++ }
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic stream id:0x%xL reject err:0x%xL", id, code);
++
++ frame = ngx_quic_alloc_frame(c);
++ if (frame == NULL) {
++ return NGX_ERROR;
++ }
++
++ frame->level = ssl_encryption_application;
++ frame->type = NGX_QUIC_FT_RESET_STREAM;
++ frame->u.reset_stream.id = id;
++ frame->u.reset_stream.error_code = code;
++ frame->u.reset_stream.final_size = 0;
++
++ ngx_quic_queue_frame(qc, frame);
++
++ frame = ngx_quic_alloc_frame(c);
++ if (frame == NULL) {
++ return NGX_ERROR;
++ }
++
++ frame->level = ssl_encryption_application;
++ frame->type = NGX_QUIC_FT_STOP_SENDING;
++ frame->u.stop_sending.id = id;
++ frame->u.stop_sending.error_code = code;
++
++ ngx_quic_queue_frame(qc, frame);
++
++ return NGX_OK;
++}
++
++
++static void
++ngx_quic_init_stream_handler(ngx_event_t *ev)
++{
++ ngx_connection_t *c;
++ ngx_quic_stream_t *qs;
++
++ c = ev->data;
++ qs = c->quic;
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic init stream");
++
++ ngx_queue_remove(&qs->queue);
++
++ c->listening->handler(c);
++}
++
++
++ngx_int_t
++ngx_quic_init_streams(ngx_connection_t *c)
++{
++ ngx_int_t rc;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ if (qc->streams.initialized) {
++ return NGX_OK;
++ }
++
++ rc = ngx_ssl_ocsp_validate(c);
++
++ if (rc == NGX_ERROR) {
++ return NGX_ERROR;
++ }
++
++ if (rc == NGX_AGAIN) {
++ c->ssl->handler = ngx_quic_init_streams_handler;
++ return NGX_OK;
++ }
++
++ ngx_quic_init_streams_handler(c);
++
++ return NGX_OK;
++}
++
++
++static void
++ngx_quic_init_streams_handler(ngx_connection_t *c)
++{
++ ngx_queue_t *q;
++ ngx_quic_stream_t *qs;
++ ngx_quic_connection_t *qc;
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic init streams");
++
++ qc = ngx_quic_get_connection(c);
++
++ for (q = ngx_queue_head(&qc->streams.uninitialized);
++ q != ngx_queue_sentinel(&qc->streams.uninitialized);
++ q = ngx_queue_next(q))
++ {
++ qs = ngx_queue_data(q, ngx_quic_stream_t, queue);
++ ngx_post_event(qs->connection->read, &ngx_posted_events);
++ }
++
++ qc->streams.initialized = 1;
++}
++
++
++static ngx_quic_stream_t *
++ngx_quic_create_stream(ngx_connection_t *c, uint64_t id)
++{
++ ngx_log_t *log;
++ ngx_pool_t *pool;
++ ngx_connection_t *sc;
++ ngx_quic_stream_t *qs;
++ ngx_pool_cleanup_t *cln;
++ ngx_quic_connection_t *qc;
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic stream id:0x%xL create", id);
++
++ qc = ngx_quic_get_connection(c);
++
++ pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, c->log);
++ if (pool == NULL) {
++ return NULL;
++ }
++
++ qs = ngx_pcalloc(pool, sizeof(ngx_quic_stream_t));
++ if (qs == NULL) {
++ ngx_destroy_pool(pool);
++ return NULL;
++ }
++
++ qs->node.key = id;
++ qs->parent = c;
++ qs->id = id;
++ qs->final_size = (uint64_t) -1;
++
++ log = ngx_palloc(pool, sizeof(ngx_log_t));
++ if (log == NULL) {
++ ngx_destroy_pool(pool);
++ return NULL;
++ }
++
++ *log = *c->log;
++ pool->log = log;
++
++ sc = ngx_get_connection(c->fd, log);
++ if (sc == NULL) {
++ ngx_destroy_pool(pool);
++ return NULL;
++ }
++
++ qs->connection = sc;
++
++ sc->quic = qs;
++ sc->shared = 1;
++ sc->type = SOCK_STREAM;
++ sc->pool = pool;
++ sc->ssl = c->ssl;
++ sc->sockaddr = c->sockaddr;
++ sc->listening = c->listening;
++ sc->addr_text = c->addr_text;
++ sc->local_sockaddr = c->local_sockaddr;
++ sc->local_socklen = c->local_socklen;
++ sc->number = ngx_atomic_fetch_add(ngx_connection_counter, 1);
++ sc->tcp_nodelay = NGX_TCP_NODELAY_DISABLED;
++
++ sc->recv = ngx_quic_stream_recv;
++ sc->send = ngx_quic_stream_send;
++ sc->send_chain = ngx_quic_stream_send_chain;
++
++ sc->read->log = log;
++ sc->write->log = log;
++
++ sc->read->handler = ngx_quic_empty_handler;
++ sc->write->handler = ngx_quic_empty_handler;
++
++ log->connection = sc->number;
++
++ if ((id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0
++ || (id & NGX_QUIC_STREAM_SERVER_INITIATED))
++ {
++ sc->write->ready = 1;
++ }
++
++ if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) {
++ if (id & NGX_QUIC_STREAM_SERVER_INITIATED) {
++ qs->send_max_data = qc->ctp.initial_max_stream_data_uni;
++
++ } else {
++ qs->recv_max_data = qc->tp.initial_max_stream_data_uni;
++ }
++
++ } else {
++ if (id & NGX_QUIC_STREAM_SERVER_INITIATED) {
++ qs->send_max_data = qc->ctp.initial_max_stream_data_bidi_remote;
++ qs->recv_max_data = qc->tp.initial_max_stream_data_bidi_local;
++
++ } else {
++ qs->send_max_data = qc->ctp.initial_max_stream_data_bidi_local;
++ qs->recv_max_data = qc->tp.initial_max_stream_data_bidi_remote;
++ }
++ }
++
++ qs->recv_window = qs->recv_max_data;
++
++ cln = ngx_pool_cleanup_add(pool, 0);
++ if (cln == NULL) {
++ ngx_close_connection(sc);
++ ngx_destroy_pool(pool);
++ return NULL;
++ }
++
++ cln->handler = ngx_quic_stream_cleanup_handler;
++ cln->data = sc;
++
++ ngx_rbtree_insert(&qc->streams.tree, &qs->node);
++
++ return qs;
++}
++
++
++static void
++ngx_quic_empty_handler(ngx_event_t *ev)
++{
++}
++
++
++static ssize_t
++ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size)
++{
++ ssize_t len;
++ ngx_buf_t *b;
++ ngx_chain_t *cl, *in;
++ ngx_event_t *rev;
++ ngx_connection_t *pc;
++ ngx_quic_stream_t *qs;
++
++ qs = c->quic;
++ pc = qs->parent;
++ rev = c->read;
++
++ if (rev->error) {
++ return NGX_ERROR;
++ }
++
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic stream id:0x%xL recv eof:%d buf:%uz",
++ qs->id, rev->pending_eof, size);
++
++ if (qs->in == NULL || qs->in->buf->sync) {
++ rev->ready = 0;
++
++ if (qs->recv_offset == qs->final_size) {
++ rev->eof = 1;
++ return 0;
++ }
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic stream id:0x%xL recv() not ready", qs->id);
++ return NGX_AGAIN;
++ }
++
++ in = ngx_quic_read_chain(pc, &qs->in, size);
++ if (in == NGX_CHAIN_ERROR) {
++ return NGX_ERROR;
++ }
++
++ len = 0;
++
++ for (cl = in; cl; cl = cl->next) {
++ b = cl->buf;
++ len += b->last - b->pos;
++ buf = ngx_cpymem(buf, b->pos, b->last - b->pos);
++ }
++
++ ngx_quic_free_chain(pc, in);
++
++ if (qs->in == NULL) {
++ rev->ready = rev->pending_eof;
++ }
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic stream id:0x%xL recv len:%z", qs->id, len);
++
++ if (ngx_quic_update_flow(c, qs->recv_offset + len) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ return len;
++}
++
++
++static ssize_t
++ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size)
++{
++ ngx_buf_t b;
++ ngx_chain_t cl;
++
++ ngx_memzero(&b, sizeof(ngx_buf_t));
++
++ b.memory = 1;
++ b.pos = buf;
++ b.last = buf + size;
++
++ cl.buf = &b;
++ cl.next = NULL;
++
++ if (ngx_quic_stream_send_chain(c, &cl, 0) == NGX_CHAIN_ERROR) {
++ return NGX_ERROR;
++ }
++
++ if (b.pos == buf) {
++ return NGX_AGAIN;
++ }
++
++ return b.pos - buf;
++}
++
++
++static ngx_chain_t *
++ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit)
++{
++ off_t n, flow;
++ ngx_event_t *wev;
++ ngx_chain_t *out, *cl;
++ ngx_connection_t *pc;
++ ngx_quic_frame_t *frame;
++ ngx_quic_stream_t *qs;
++ ngx_quic_connection_t *qc;
++
++ qs = c->quic;
++ pc = qs->parent;
++ qc = ngx_quic_get_connection(pc);
++ wev = c->write;
++
++ if (wev->error) {
++ return NGX_CHAIN_ERROR;
++ }
++
++ flow = ngx_quic_max_stream_flow(c);
++ if (flow == 0) {
++ wev->ready = 0;
++ return in;
++ }
++
++ if (limit == 0 || limit > flow) {
++ limit = flow;
++ }
++
++ n = 0;
++
++ for (cl = in; cl; cl = cl->next) {
++ n += cl->buf->last - cl->buf->pos;
++ if (n >= limit) {
++ n = limit;
++ break;
++ }
++ }
++
++ in = ngx_quic_write_chain(pc, &qs->out, in, n, 0);
++ if (in == NGX_CHAIN_ERROR) {
++ return NGX_CHAIN_ERROR;
++ }
++
++ out = ngx_quic_read_chain(pc, &qs->out, n);
++ if (out == NGX_CHAIN_ERROR) {
++ return NGX_CHAIN_ERROR;
++ }
++
++ frame = ngx_quic_alloc_frame(pc);
++ if (frame == NULL) {
++ return NGX_CHAIN_ERROR;
++ }
++
++ frame->level = ssl_encryption_application;
++ frame->type = NGX_QUIC_FT_STREAM;
++ frame->data = out;
++ frame->u.stream.off = 1;
++ frame->u.stream.len = 1;
++ frame->u.stream.fin = 0;
++
++ frame->u.stream.stream_id = qs->id;
++ frame->u.stream.offset = c->sent;
++ frame->u.stream.length = n;
++
++ c->sent += n;
++ qc->streams.sent += n;
++
++ ngx_quic_queue_frame(qc, frame);
++
++ if (in) {
++ wev->ready = 0;
++ }
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic send_chain sent:%O", n);
++
++ return in;
++}
++
++
++static size_t
++ngx_quic_max_stream_flow(ngx_connection_t *c)
++{
++ size_t size;
++ uint64_t sent, unacked;
++ ngx_quic_stream_t *qs;
++ ngx_quic_connection_t *qc;
++
++ qs = c->quic;
++ qc = ngx_quic_get_connection(qs->parent);
++
++ size = qc->conf->stream_buffer_size;
++ sent = c->sent;
++ unacked = sent - qs->acked;
++
++ if (qc->streams.send_max_data == 0) {
++ qc->streams.send_max_data = qc->ctp.initial_max_data;
++ }
++
++ if (unacked >= size) {
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic send flow hit buffer size");
++ return 0;
++ }
++
++ size -= unacked;
++
++ if (qc->streams.sent >= qc->streams.send_max_data) {
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic send flow hit MAX_DATA");
++ return 0;
++ }
++
++ if (qc->streams.sent + size > qc->streams.send_max_data) {
++ size = qc->streams.send_max_data - qc->streams.sent;
++ }
++
++ if (sent >= qs->send_max_data) {
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic send flow hit MAX_STREAM_DATA");
++ return 0;
++ }
++
++ if (sent + size > qs->send_max_data) {
++ size = qs->send_max_data - sent;
++ }
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic send flow:%uz", size);
++
++ return size;
++}
++
++
++static void
++ngx_quic_stream_cleanup_handler(void *data)
++{
++ ngx_connection_t *c = data;
++
++ ngx_connection_t *pc;
++ ngx_quic_frame_t *frame;
++ ngx_quic_stream_t *qs;
++ ngx_quic_connection_t *qc;
++
++ qs = c->quic;
++ pc = qs->parent;
++ qc = ngx_quic_get_connection(pc);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic stream id:0x%xL cleanup", qs->id);
++
++ ngx_rbtree_delete(&qc->streams.tree, &qs->node);
++ ngx_quic_free_chain(pc, qs->in);
++ ngx_quic_free_chain(pc, qs->out);
++
++ if (qc->closing) {
++ /* schedule handler call to continue ngx_quic_close_connection() */
++ ngx_post_event(pc->read, &ngx_posted_events);
++ return;
++ }
++
++ if (qc->error) {
++ goto done;
++ }
++
++ (void) ngx_quic_shutdown_stream(c, NGX_RDWR_SHUTDOWN);
++
++ (void) ngx_quic_update_flow(c, qs->recv_last);
++
++ if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) {
++ frame = ngx_quic_alloc_frame(pc);
++ if (frame == NULL) {
++ goto done;
++ }
++
++ frame->level = ssl_encryption_application;
++ frame->type = NGX_QUIC_FT_MAX_STREAMS;
++
++ if (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) {
++ frame->u.max_streams.limit = ++qc->streams.client_max_streams_uni;
++ frame->u.max_streams.bidi = 0;
++
++ } else {
++ frame->u.max_streams.limit = ++qc->streams.client_max_streams_bidi;
++ frame->u.max_streams.bidi = 1;
++ }
++
++ ngx_quic_queue_frame(qc, frame);
++ }
++
++done:
++
++ (void) ngx_quic_output(pc);
++
++ if (qc->shutdown) {
++ ngx_post_event(pc->read, &ngx_posted_events);
++ }
++}
++
++
++ngx_int_t
++ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt,
++ ngx_quic_frame_t *frame)
++{
++ uint64_t last;
++ ngx_event_t *rev;
++ ngx_connection_t *sc;
++ ngx_quic_stream_t *qs;
++ ngx_quic_connection_t *qc;
++ ngx_quic_stream_frame_t *f;
++
++ qc = ngx_quic_get_connection(c);
++ f = &frame->u.stream;
++
++ if ((f->stream_id & NGX_QUIC_STREAM_UNIDIRECTIONAL)
++ && (f->stream_id & NGX_QUIC_STREAM_SERVER_INITIATED))
++ {
++ qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR;
++ return NGX_ERROR;
++ }
++
++ /* no overflow since both values are 62-bit */
++ last = f->offset + f->length;
++
++ qs = ngx_quic_get_stream(c, f->stream_id);
++
++ if (qs == NULL) {
++ return NGX_ERROR;
++ }
++
++ if (qs == NGX_QUIC_STREAM_GONE) {
++ return NGX_OK;
++ }
++
++ sc = qs->connection;
++
++ rev = sc->read;
++
++ if (rev->error) {
++ return NGX_OK;
++ }
++
++ if (ngx_quic_control_flow(sc, last) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ if (qs->final_size != (uint64_t) -1 && last > qs->final_size) {
++ qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR;
++ return NGX_ERROR;
++ }
++
++ if (last <= qs->recv_offset) {
++ return NGX_OK;
++ }
++
++ if (f->offset < qs->recv_offset) {
++ ngx_quic_trim_chain(frame->data, qs->recv_offset - f->offset);
++ f->offset = qs->recv_offset;
++ }
++
++ if (f->fin) {
++ if (qs->final_size != (uint64_t) -1 && qs->final_size != last) {
++ qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR;
++ return NGX_ERROR;
++ }
++
++ if (qs->recv_last > last) {
++ qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR;
++ return NGX_ERROR;
++ }
++
++ rev->pending_eof = 1;
++ qs->final_size = last;
++ }
++
++ if (ngx_quic_write_chain(c, &qs->in, frame->data, f->length,
++ f->offset - qs->recv_offset)
++ == NGX_CHAIN_ERROR)
++ {
++ return NGX_ERROR;
++ }
++
++ if (f->offset == qs->recv_offset) {
++ rev->ready = 1;
++
++ if (rev->active) {
++ ngx_post_event(rev, &ngx_posted_events);
++ }
++ }
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_handle_max_data_frame(ngx_connection_t *c,
++ ngx_quic_max_data_frame_t *f)
++{
++ ngx_event_t *wev;
++ ngx_rbtree_t *tree;
++ ngx_rbtree_node_t *node;
++ ngx_quic_stream_t *qs;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++ tree = &qc->streams.tree;
++
++ if (f->max_data <= qc->streams.send_max_data) {
++ return NGX_OK;
++ }
++
++ if (tree->root != tree->sentinel
++ && qc->streams.sent >= qc->streams.send_max_data)
++ {
++
++ for (node = ngx_rbtree_min(tree->root, tree->sentinel);
++ node;
++ node = ngx_rbtree_next(tree, node))
++ {
++ qs = (ngx_quic_stream_t *) node;
++ wev = qs->connection->write;
++
++ if (wev->active) {
++ wev->ready = 1;
++ ngx_post_event(wev, &ngx_posted_events);
++ }
++ }
++ }
++
++ qc->streams.send_max_data = f->max_data;
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c,
++ ngx_quic_header_t *pkt, ngx_quic_streams_blocked_frame_t *f)
++{
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_handle_data_blocked_frame(ngx_connection_t *c,
++ ngx_quic_header_t *pkt, ngx_quic_data_blocked_frame_t *f)
++{
++ return ngx_quic_update_max_data(c);
++}
++
++
++ngx_int_t
++ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c,
++ ngx_quic_header_t *pkt, ngx_quic_stream_data_blocked_frame_t *f)
++{
++ ngx_quic_stream_t *qs;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL)
++ && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED))
++ {
++ qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR;
++ return NGX_ERROR;
++ }
++
++ qs = ngx_quic_get_stream(c, f->id);
++
++ if (qs == NULL) {
++ return NGX_ERROR;
++ }
++
++ if (qs == NGX_QUIC_STREAM_GONE) {
++ return NGX_OK;
++ }
++
++ return ngx_quic_update_max_stream_data(qs->connection);
++}
++
++
++ngx_int_t
++ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c,
++ ngx_quic_header_t *pkt, ngx_quic_max_stream_data_frame_t *f)
++{
++ uint64_t sent;
++ ngx_event_t *wev;
++ ngx_quic_stream_t *qs;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL)
++ && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0)
++ {
++ qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR;
++ return NGX_ERROR;
++ }
++
++ qs = ngx_quic_get_stream(c, f->id);
++
++ if (qs == NULL) {
++ return NGX_ERROR;
++ }
++
++ if (qs == NGX_QUIC_STREAM_GONE) {
++ return NGX_OK;
++ }
++
++ if (f->limit <= qs->send_max_data) {
++ return NGX_OK;
++ }
++
++ sent = qs->connection->sent;
++
++ if (sent >= qs->send_max_data) {
++ wev = qs->connection->write;
++
++ if (wev->active) {
++ wev->ready = 1;
++ ngx_post_event(wev, &ngx_posted_events);
++ }
++ }
++
++ qs->send_max_data = f->limit;
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_handle_reset_stream_frame(ngx_connection_t *c,
++ ngx_quic_header_t *pkt, ngx_quic_reset_stream_frame_t *f)
++{
++ ngx_event_t *rev;
++ ngx_connection_t *sc;
++ ngx_quic_stream_t *qs;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL)
++ && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED))
++ {
++ qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR;
++ return NGX_ERROR;
++ }
++
++ qs = ngx_quic_get_stream(c, f->id);
++
++ if (qs == NULL) {
++ return NGX_ERROR;
++ }
++
++ if (qs == NGX_QUIC_STREAM_GONE) {
++ return NGX_OK;
++ }
++
++ sc = qs->connection;
++
++ rev = sc->read;
++ rev->error = 1;
++ rev->ready = 1;
++
++ if (ngx_quic_control_flow(sc, f->final_size) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ if (qs->final_size != (uint64_t) -1 && qs->final_size != f->final_size) {
++ qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR;
++ return NGX_ERROR;
++ }
++
++ if (qs->recv_last > f->final_size) {
++ qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR;
++ return NGX_ERROR;
++ }
++
++ qs->final_size = f->final_size;
++
++ if (ngx_quic_update_flow(sc, qs->final_size) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ if (rev->active) {
++ ngx_post_event(rev, &ngx_posted_events);
++ }
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_handle_stop_sending_frame(ngx_connection_t *c,
++ ngx_quic_header_t *pkt, ngx_quic_stop_sending_frame_t *f)
++{
++ ngx_event_t *wev;
++ ngx_quic_stream_t *qs;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL)
++ && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0)
++ {
++ qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR;
++ return NGX_ERROR;
++ }
++
++ qs = ngx_quic_get_stream(c, f->id);
++
++ if (qs == NULL) {
++ return NGX_ERROR;
++ }
++
++ if (qs == NGX_QUIC_STREAM_GONE) {
++ return NGX_OK;
++ }
++
++ if (ngx_quic_reset_stream(qs->connection, f->error_code) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ wev = qs->connection->write;
++
++ if (wev->active) {
++ ngx_post_event(wev, &ngx_posted_events);
++ }
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_handle_max_streams_frame(ngx_connection_t *c,
++ ngx_quic_header_t *pkt, ngx_quic_max_streams_frame_t *f)
++{
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ if (f->bidi) {
++ if (qc->streams.server_max_streams_bidi < f->limit) {
++ qc->streams.server_max_streams_bidi = f->limit;
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic max_streams_bidi:%uL", f->limit);
++ }
++
++ } else {
++ if (qc->streams.server_max_streams_uni < f->limit) {
++ qc->streams.server_max_streams_uni = f->limit;
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic max_streams_uni:%uL", f->limit);
++ }
++ }
++
++ return NGX_OK;
++}
++
++
++void
++ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f)
++{
++ uint64_t sent, unacked;
++ ngx_event_t *wev;
++ ngx_quic_stream_t *qs;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ qs = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id);
++ if (qs == NULL) {
++ return;
++ }
++
++ wev = qs->connection->write;
++ sent = qs->connection->sent;
++ unacked = sent - qs->acked;
++
++ if (unacked >= qc->conf->stream_buffer_size && wev->active) {
++ wev->ready = 1;
++ ngx_post_event(wev, &ngx_posted_events);
++ }
++
++ qs->acked += f->u.stream.length;
++
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, qs->connection->log, 0,
++ "quic stream ack len:%uL acked:%uL unacked:%uL",
++ f->u.stream.length, qs->acked, sent - qs->acked);
++}
++
++
++static ngx_int_t
++ngx_quic_control_flow(ngx_connection_t *c, uint64_t last)
++{
++ uint64_t len;
++ ngx_event_t *rev;
++ ngx_quic_stream_t *qs;
++ ngx_quic_connection_t *qc;
++
++ rev = c->read;
++ qs = c->quic;
++ qc = ngx_quic_get_connection(qs->parent);
++
++ if (last <= qs->recv_last) {
++ return NGX_OK;
++ }
++
++ len = last - qs->recv_last;
++
++ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic flow control msd:%uL/%uL md:%uL/%uL",
++ last, qs->recv_max_data, qc->streams.recv_last + len,
++ qc->streams.recv_max_data);
++
++ qs->recv_last += len;
++
++ if (!rev->error && qs->recv_last > qs->recv_max_data) {
++ qc->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR;
++ return NGX_ERROR;
++ }
++
++ qc->streams.recv_last += len;
++
++ if (qc->streams.recv_last > qc->streams.recv_max_data) {
++ qc->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR;
++ return NGX_ERROR;
++ }
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_update_flow(ngx_connection_t *c, uint64_t last)
++{
++ uint64_t len;
++ ngx_event_t *rev;
++ ngx_connection_t *pc;
++ ngx_quic_stream_t *qs;
++ ngx_quic_connection_t *qc;
++
++ rev = c->read;
++ qs = c->quic;
++ pc = qs->parent;
++ qc = ngx_quic_get_connection(pc);
++
++ if (last <= qs->recv_offset) {
++ return NGX_OK;
++ }
++
++ len = last - qs->recv_offset;
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic flow update %uL", last);
++
++ qs->recv_offset += len;
++
++ if (!rev->pending_eof && !rev->error
++ && qs->recv_max_data <= qs->recv_offset + qs->recv_window / 2)
++ {
++ if (ngx_quic_update_max_stream_data(c) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++
++ qc->streams.recv_offset += len;
++
++ if (qc->streams.recv_max_data
++ <= qc->streams.recv_offset + qc->streams.recv_window / 2)
++ {
++ if (ngx_quic_update_max_data(pc) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_update_max_stream_data(ngx_connection_t *c)
++{
++ uint64_t recv_max_data;
++ ngx_connection_t *pc;
++ ngx_quic_frame_t *frame;
++ ngx_quic_stream_t *qs;
++ ngx_quic_connection_t *qc;
++
++ qs = c->quic;
++ pc = qs->parent;
++ qc = ngx_quic_get_connection(pc);
++
++ recv_max_data = qs->recv_offset + qs->recv_window;
++
++ if (qs->recv_max_data == recv_max_data) {
++ return NGX_OK;
++ }
++
++ qs->recv_max_data = recv_max_data;
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic flow update msd:%uL", qs->recv_max_data);
++
++ frame = ngx_quic_alloc_frame(pc);
++ if (frame == NULL) {
++ return NGX_ERROR;
++ }
++
++ frame->level = ssl_encryption_application;
++ frame->type = NGX_QUIC_FT_MAX_STREAM_DATA;
++ frame->u.max_stream_data.id = qs->id;
++ frame->u.max_stream_data.limit = qs->recv_max_data;
++
++ ngx_quic_queue_frame(qc, frame);
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_update_max_data(ngx_connection_t *c)
++{
++ uint64_t recv_max_data;
++ ngx_quic_frame_t *frame;
++ ngx_quic_connection_t *qc;
++
++ qc = ngx_quic_get_connection(c);
++
++ recv_max_data = qc->streams.recv_offset + qc->streams.recv_window;
++
++ if (qc->streams.recv_max_data == recv_max_data) {
++ return NGX_OK;
++ }
++
++ qc->streams.recv_max_data = recv_max_data;
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic flow update md:%uL", qc->streams.recv_max_data);
++
++ frame = ngx_quic_alloc_frame(c);
++ if (frame == NULL) {
++ return NGX_ERROR;
++ }
++
++ frame->level = ssl_encryption_application;
++ frame->type = NGX_QUIC_FT_MAX_DATA;
++ frame->u.max_data.max_data = qc->streams.recv_max_data;
++
++ ngx_quic_queue_frame(qc, frame);
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_handle_read_event(ngx_event_t *rev, ngx_uint_t flags)
++{
++ if (!rev->active && !rev->ready) {
++ rev->active = 1;
++
++ } else if (rev->active && (rev->ready || (flags & NGX_CLOSE_EVENT))) {
++ rev->active = 0;
++ }
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_handle_write_event(ngx_event_t *wev, size_t lowat)
++{
++ if (!wev->active && !wev->ready) {
++ wev->active = 1;
++
++ } else if (wev->active && wev->ready) {
++ wev->active = 0;
++ }
++
++ return NGX_OK;
++}
+diff -r 67408b4a12c0 src/event/quic/ngx_event_quic_streams.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_streams.h Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,44 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#ifndef _NGX_EVENT_QUIC_STREAMS_H_INCLUDED_
++#define _NGX_EVENT_QUIC_STREAMS_H_INCLUDED_
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++
++
++ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c,
++ ngx_quic_header_t *pkt, ngx_quic_frame_t *frame);
++void ngx_quic_handle_stream_ack(ngx_connection_t *c,
++ ngx_quic_frame_t *f);
++ngx_int_t ngx_quic_handle_max_data_frame(ngx_connection_t *c,
++ ngx_quic_max_data_frame_t *f);
++ngx_int_t ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c,
++ ngx_quic_header_t *pkt, ngx_quic_streams_blocked_frame_t *f);
++ngx_int_t ngx_quic_handle_data_blocked_frame(ngx_connection_t *c,
++ ngx_quic_header_t *pkt, ngx_quic_data_blocked_frame_t *f);
++ngx_int_t ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c,
++ ngx_quic_header_t *pkt, ngx_quic_stream_data_blocked_frame_t *f);
++ngx_int_t ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c,
++ ngx_quic_header_t *pkt, ngx_quic_max_stream_data_frame_t *f);
++ngx_int_t ngx_quic_handle_reset_stream_frame(ngx_connection_t *c,
++ ngx_quic_header_t *pkt, ngx_quic_reset_stream_frame_t *f);
++ngx_int_t ngx_quic_handle_stop_sending_frame(ngx_connection_t *c,
++ ngx_quic_header_t *pkt, ngx_quic_stop_sending_frame_t *f);
++ngx_int_t ngx_quic_handle_max_streams_frame(ngx_connection_t *c,
++ ngx_quic_header_t *pkt, ngx_quic_max_streams_frame_t *f);
++
++ngx_int_t ngx_quic_init_streams(ngx_connection_t *c);
++void ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp,
++ ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
++ngx_quic_stream_t *ngx_quic_find_stream(ngx_rbtree_t *rbtree,
++ uint64_t id);
++ngx_int_t ngx_quic_close_streams(ngx_connection_t *c,
++ ngx_quic_connection_t *qc);
++
++#endif /* _NGX_EVENT_QUIC_STREAMS_H_INCLUDED_ */
+diff -r 67408b4a12c0 src/event/quic/ngx_event_quic_tokens.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_tokens.c Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,295 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++#include <ngx_event.h>
++#include <ngx_sha1.h>
++#include <ngx_event_quic_connection.h>
++
++
++#define NGX_QUIC_MAX_TOKEN_SIZE 64
++ /* SHA-1(addr)=20 + sizeof(time_t) + retry(1) + odcid.len(1) + odcid */
++
++/* RFC 3602, 2.1 and 2.4 for AES-CBC block size and IV length */
++#define NGX_QUIC_AES_256_CBC_IV_LEN 16
++#define NGX_QUIC_AES_256_CBC_BLOCK_SIZE 16
++
++
++static void ngx_quic_address_hash(struct sockaddr *sockaddr, socklen_t socklen,
++ ngx_uint_t no_port, u_char buf[20]);
++
++
++ngx_int_t
++ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, u_char *secret,
++ u_char *token)
++{
++ ngx_str_t tmp;
++
++ tmp.data = secret;
++ tmp.len = NGX_QUIC_SR_KEY_LEN;
++
++ if (ngx_quic_derive_key(c->log, "sr_token_key", &tmp, cid, token,
++ NGX_QUIC_SR_TOKEN_LEN)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic stateless reset token %*xs",
++ (size_t) NGX_QUIC_SR_TOKEN_LEN, token);
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_new_token(ngx_connection_t *c, struct sockaddr *sockaddr,
++ socklen_t socklen, u_char *key, ngx_str_t *token, ngx_str_t *odcid,
++ time_t exp, ngx_uint_t is_retry)
++{
++ int len, iv_len;
++ u_char *p, *iv;
++ EVP_CIPHER_CTX *ctx;
++ const EVP_CIPHER *cipher;
++
++ u_char in[NGX_QUIC_MAX_TOKEN_SIZE];
++
++ ngx_quic_address_hash(sockaddr, socklen, !is_retry, in);
++
++ p = in + 20;
++
++ p = ngx_cpymem(p, &exp, sizeof(time_t));
++
++ *p++ = is_retry ? 1 : 0;
++
++ if (odcid) {
++ *p++ = odcid->len;
++ p = ngx_cpymem(p, odcid->data, odcid->len);
++
++ } else {
++ *p++ = 0;
++ }
++
++ len = p - in;
++
++ cipher = EVP_aes_256_cbc();
++ iv_len = NGX_QUIC_AES_256_CBC_IV_LEN;
++
++ token->len = iv_len + len + NGX_QUIC_AES_256_CBC_BLOCK_SIZE;
++ token->data = ngx_pnalloc(c->pool, token->len);
++ if (token->data == NULL) {
++ return NGX_ERROR;
++ }
++
++ ctx = EVP_CIPHER_CTX_new();
++ if (ctx == NULL) {
++ return NGX_ERROR;
++ }
++
++ iv = token->data;
++
++ if (RAND_bytes(iv, iv_len) <= 0
++ || !EVP_EncryptInit_ex(ctx, cipher, NULL, key, iv))
++ {
++ EVP_CIPHER_CTX_free(ctx);
++ return NGX_ERROR;
++ }
++
++ token->len = iv_len;
++
++ if (EVP_EncryptUpdate(ctx, token->data + token->len, &len, in, len) != 1) {
++ EVP_CIPHER_CTX_free(ctx);
++ return NGX_ERROR;
++ }
++
++ token->len += len;
++
++ if (EVP_EncryptFinal_ex(ctx, token->data + token->len, &len) <= 0) {
++ EVP_CIPHER_CTX_free(ctx);
++ return NGX_ERROR;
++ }
++
++ token->len += len;
++
++ EVP_CIPHER_CTX_free(ctx);
++
++#ifdef NGX_QUIC_DEBUG_PACKETS
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "quic new token len:%uz %xV", token->len, token);
++#endif
++
++ return NGX_OK;
++}
++
++
++static void
++ngx_quic_address_hash(struct sockaddr *sockaddr, socklen_t socklen,
++ ngx_uint_t no_port, u_char buf[20])
++{
++ size_t len;
++ u_char *data;
++ ngx_sha1_t sha1;
++ struct sockaddr_in *sin;
++#if (NGX_HAVE_INET6)
++ struct sockaddr_in6 *sin6;
++#endif
++
++ len = (size_t) socklen;
++ data = (u_char *) sockaddr;
++
++ if (no_port) {
++ switch (sockaddr->sa_family) {
++
++#if (NGX_HAVE_INET6)
++ case AF_INET6:
++ sin6 = (struct sockaddr_in6 *) sockaddr;
++
++ len = sizeof(struct in6_addr);
++ data = sin6->sin6_addr.s6_addr;
++
++ break;
++#endif
++
++ case AF_INET:
++ sin = (struct sockaddr_in *) sockaddr;
++
++ len = sizeof(in_addr_t);
++ data = (u_char *) &sin->sin_addr;
++
++ break;
++ }
++ }
++
++ ngx_sha1_init(&sha1);
++ ngx_sha1_update(&sha1, data, len);
++ ngx_sha1_final(buf, &sha1);
++}
++
++
++ngx_int_t
++ngx_quic_validate_token(ngx_connection_t *c, u_char *key,
++ ngx_quic_header_t *pkt)
++{
++ int len, tlen, iv_len;
++ u_char *iv, *p;
++ time_t now, exp;
++ size_t total;
++ ngx_str_t odcid;
++ EVP_CIPHER_CTX *ctx;
++ const EVP_CIPHER *cipher;
++
++ u_char addr_hash[20];
++ u_char tdec[NGX_QUIC_MAX_TOKEN_SIZE];
++
++ /* Retry token or NEW_TOKEN in a previous connection */
++
++ cipher = EVP_aes_256_cbc();
++ iv = pkt->token.data;
++ iv_len = NGX_QUIC_AES_256_CBC_IV_LEN;
++
++ /* sanity checks */
++
++ if (pkt->token.len < (size_t) iv_len + NGX_QUIC_AES_256_CBC_BLOCK_SIZE) {
++ goto garbage;
++ }
++
++ if (pkt->token.len > (size_t) iv_len + NGX_QUIC_MAX_TOKEN_SIZE) {
++ goto garbage;
++ }
++
++ ctx = EVP_CIPHER_CTX_new();
++ if (ctx == NULL) {
++ return NGX_ERROR;
++ }
++
++ if (!EVP_DecryptInit_ex(ctx, cipher, NULL, key, iv)) {
++ EVP_CIPHER_CTX_free(ctx);
++ return NGX_ERROR;
++ }
++
++ p = pkt->token.data + iv_len;
++ len = pkt->token.len - iv_len;
++
++ if (EVP_DecryptUpdate(ctx, tdec, &len, p, len) != 1) {
++ EVP_CIPHER_CTX_free(ctx);
++ goto garbage;
++ }
++ total = len;
++
++ if (EVP_DecryptFinal_ex(ctx, tdec + len, &tlen) <= 0) {
++ EVP_CIPHER_CTX_free(ctx);
++ goto garbage;
++ }
++ total += tlen;
++
++ EVP_CIPHER_CTX_free(ctx);
++
++ if (total < (20 + sizeof(time_t) + 2)) {
++ goto garbage;
++ }
++
++ p = tdec + 20;
++
++ ngx_memcpy(&exp, p, sizeof(time_t));
++ p += sizeof(time_t);
++
++ pkt->retried = (*p++ == 1);
++
++ ngx_quic_address_hash(c->sockaddr, c->socklen, !pkt->retried, addr_hash);
++
++ if (ngx_memcmp(tdec, addr_hash, 20) != 0) {
++ goto bad_token;
++ }
++
++ odcid.len = *p++;
++ if (odcid.len) {
++ if (odcid.len > NGX_QUIC_MAX_CID_LEN) {
++ goto bad_token;
++ }
++
++ if ((size_t)(tdec + total - p) < odcid.len) {
++ goto bad_token;
++ }
++
++ odcid.data = p;
++ }
++
++ now = ngx_time();
++
++ if (now > exp) {
++ ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic expired token");
++ return NGX_DECLINED;
++ }
++
++ if (odcid.len) {
++ pkt->odcid.len = odcid.len;
++ pkt->odcid.data = ngx_pstrdup(c->pool, &odcid);
++ if (pkt->odcid.data == NULL) {
++ return NGX_ERROR;
++ }
++
++ } else {
++ pkt->odcid = pkt->dcid;
++ }
++
++ pkt->validated = 1;
++
++ return NGX_OK;
++
++garbage:
++
++ ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic garbage token");
++
++ return NGX_ABORT;
++
++bad_token:
++
++ ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid token");
++
++ return NGX_DECLINED;
++}
+diff -r 67408b4a12c0 src/event/quic/ngx_event_quic_tokens.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_tokens.h Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,23 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#ifndef _NGX_EVENT_QUIC_TOKENS_H_INCLUDED_
++#define _NGX_EVENT_QUIC_TOKENS_H_INCLUDED_
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++
++
++ngx_int_t ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid,
++ u_char *secret, u_char *token);
++ngx_int_t ngx_quic_new_token(ngx_connection_t *c, struct sockaddr *sockaddr,
++ socklen_t socklen, u_char *key, ngx_str_t *token, ngx_str_t *odcid,
++ time_t expires, ngx_uint_t is_retry);
++ngx_int_t ngx_quic_validate_token(ngx_connection_t *c,
++ u_char *key, ngx_quic_header_t *pkt);
++
++#endif /* _NGX_EVENT_QUIC_TOKENS_H_INCLUDED_ */
+diff -r 67408b4a12c0 src/event/quic/ngx_event_quic_transport.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_transport.c Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,2170 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++#include <ngx_event.h>
++#include <ngx_event_quic_connection.h>
++
++
++#define NGX_QUIC_LONG_DCID_LEN_OFFSET 5
++#define NGX_QUIC_LONG_DCID_OFFSET 6
++#define NGX_QUIC_SHORT_DCID_OFFSET 1
++
++#define NGX_QUIC_STREAM_FRAME_FIN 0x01
++#define NGX_QUIC_STREAM_FRAME_LEN 0x02
++#define NGX_QUIC_STREAM_FRAME_OFF 0x04
++
++
++#if (NGX_HAVE_NONALIGNED)
++
++#define ngx_quic_parse_uint16(p) ntohs(*(uint16_t *) (p))
++#define ngx_quic_parse_uint32(p) ntohl(*(uint32_t *) (p))
++
++#define ngx_quic_write_uint16 ngx_quic_write_uint16_aligned
++#define ngx_quic_write_uint32 ngx_quic_write_uint32_aligned
++
++#else
++
++#define ngx_quic_parse_uint16(p) ((p)[0] << 8 | (p)[1])
++#define ngx_quic_parse_uint32(p) \
++ ((uint32_t) (p)[0] << 24 | (p)[1] << 16 | (p)[2] << 8 | (p)[3])
++
++#define ngx_quic_write_uint16(p, s) \
++ ((p)[0] = (u_char) ((s) >> 8), \
++ (p)[1] = (u_char) (s), \
++ (p) + sizeof(uint16_t))
++
++#define ngx_quic_write_uint32(p, s) \
++ ((p)[0] = (u_char) ((s) >> 24), \
++ (p)[1] = (u_char) ((s) >> 16), \
++ (p)[2] = (u_char) ((s) >> 8), \
++ (p)[3] = (u_char) (s), \
++ (p) + sizeof(uint32_t))
++
++#endif
++
++#define ngx_quic_write_uint64(p, s) \
++ ((p)[0] = (u_char) ((s) >> 56), \
++ (p)[1] = (u_char) ((s) >> 48), \
++ (p)[2] = (u_char) ((s) >> 40), \
++ (p)[3] = (u_char) ((s) >> 32), \
++ (p)[4] = (u_char) ((s) >> 24), \
++ (p)[5] = (u_char) ((s) >> 16), \
++ (p)[6] = (u_char) ((s) >> 8), \
++ (p)[7] = (u_char) (s), \
++ (p) + sizeof(uint64_t))
++
++#define ngx_quic_write_uint24(p, s) \
++ ((p)[0] = (u_char) ((s) >> 16), \
++ (p)[1] = (u_char) ((s) >> 8), \
++ (p)[2] = (u_char) (s), \
++ (p) + 3)
++
++#define ngx_quic_write_uint16_aligned(p, s) \
++ (*(uint16_t *) (p) = htons((uint16_t) (s)), (p) + sizeof(uint16_t))
++
++#define ngx_quic_write_uint32_aligned(p, s) \
++ (*(uint32_t *) (p) = htonl((uint32_t) (s)), (p) + sizeof(uint32_t))
++
++#define ngx_quic_build_int_set(p, value, len, bits) \
++ (*(p)++ = ((value >> ((len) * 8)) & 0xff) | ((bits) << 6))
++
++#define NGX_QUIC_VERSION(c) (0xff000000 + (c))
++
++
++static u_char *ngx_quic_parse_int(u_char *pos, u_char *end, uint64_t *out);
++static ngx_uint_t ngx_quic_varint_len(uint64_t value);
++static void ngx_quic_build_int(u_char **pos, uint64_t value);
++
++static u_char *ngx_quic_read_uint8(u_char *pos, u_char *end, uint8_t *value);
++static u_char *ngx_quic_read_uint32(u_char *pos, u_char *end, uint32_t *value);
++static u_char *ngx_quic_read_bytes(u_char *pos, u_char *end, size_t len,
++ u_char **out);
++static u_char *ngx_quic_copy_bytes(u_char *pos, u_char *end, size_t len,
++ u_char *dst);
++
++static ngx_int_t ngx_quic_parse_short_header(ngx_quic_header_t *pkt,
++ size_t dcid_len);
++static ngx_int_t ngx_quic_parse_long_header(ngx_quic_header_t *pkt);
++static ngx_int_t ngx_quic_supported_version(uint32_t version);
++static ngx_int_t ngx_quic_parse_long_header_v1(ngx_quic_header_t *pkt);
++
++static size_t ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out,
++ u_char **pnp);
++static size_t ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out,
++ u_char **pnp);
++
++static ngx_int_t ngx_quic_frame_allowed(ngx_quic_header_t *pkt,
++ ngx_uint_t frame_type);
++static size_t ngx_quic_create_ping(u_char *p);
++static size_t ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack,
++ ngx_chain_t *ranges);
++static size_t ngx_quic_create_reset_stream(u_char *p,
++ ngx_quic_reset_stream_frame_t *rs);
++static size_t ngx_quic_create_stop_sending(u_char *p,
++ ngx_quic_stop_sending_frame_t *ss);
++static size_t ngx_quic_create_crypto(u_char *p,
++ ngx_quic_crypto_frame_t *crypto, ngx_chain_t *data);
++static size_t ngx_quic_create_hs_done(u_char *p);
++static size_t ngx_quic_create_new_token(u_char *p,
++ ngx_quic_new_token_frame_t *token);
++static size_t ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf,
++ ngx_chain_t *data);
++static size_t ngx_quic_create_max_streams(u_char *p,
++ ngx_quic_max_streams_frame_t *ms);
++static size_t ngx_quic_create_max_stream_data(u_char *p,
++ ngx_quic_max_stream_data_frame_t *ms);
++static size_t ngx_quic_create_max_data(u_char *p,
++ ngx_quic_max_data_frame_t *md);
++static size_t ngx_quic_create_path_challenge(u_char *p,
++ ngx_quic_path_challenge_frame_t *pc);
++static size_t ngx_quic_create_path_response(u_char *p,
++ ngx_quic_path_challenge_frame_t *pc);
++static size_t ngx_quic_create_new_connection_id(u_char *p,
++ ngx_quic_new_conn_id_frame_t *rcid);
++static size_t ngx_quic_create_retire_connection_id(u_char *p,
++ ngx_quic_retire_cid_frame_t *rcid);
++static size_t ngx_quic_create_close(u_char *p, ngx_quic_frame_t *f);
++
++static ngx_int_t ngx_quic_parse_transport_param(u_char *p, u_char *end,
++ uint16_t id, ngx_quic_tp_t *dst);
++
++
++uint32_t ngx_quic_versions[] = {
++ /* QUICv1 */
++ 0x00000001,
++ NGX_QUIC_VERSION(29),
++ NGX_QUIC_VERSION(30),
++ NGX_QUIC_VERSION(31),
++ NGX_QUIC_VERSION(32),
++};
++
++#define NGX_QUIC_NVERSIONS \
++ (sizeof(ngx_quic_versions) / sizeof(ngx_quic_versions[0]))
++
++
++static ngx_inline u_char *
++ngx_quic_parse_int(u_char *pos, u_char *end, uint64_t *out)
++{
++ u_char *p;
++ uint64_t value;
++ ngx_uint_t len;
++
++ if (pos >= end) {
++ return NULL;
++ }
++
++ p = pos;
++ len = 1 << (*p >> 6);
++
++ value = *p++ & 0x3f;
++
++ if ((size_t)(end - p) < (len - 1)) {
++ return NULL;
++ }
++
++ while (--len) {
++ value = (value << 8) + *p++;
++ }
++
++ *out = value;
++
++ return p;
++}
++
++
++static ngx_inline u_char *
++ngx_quic_read_uint8(u_char *pos, u_char *end, uint8_t *value)
++{
++ if ((size_t)(end - pos) < 1) {
++ return NULL;
++ }
++
++ *value = *pos;
++
++ return pos + 1;
++}
++
++
++static ngx_inline u_char *
++ngx_quic_read_uint32(u_char *pos, u_char *end, uint32_t *value)
++{
++ if ((size_t)(end - pos) < sizeof(uint32_t)) {
++ return NULL;
++ }
++
++ *value = ngx_quic_parse_uint32(pos);
++
++ return pos + sizeof(uint32_t);
++}
++
++
++static ngx_inline u_char *
++ngx_quic_read_bytes(u_char *pos, u_char *end, size_t len, u_char **out)
++{
++ if ((size_t)(end - pos) < len) {
++ return NULL;
++ }
++
++ *out = pos;
++
++ return pos + len;
++}
++
++
++static u_char *
++ngx_quic_copy_bytes(u_char *pos, u_char *end, size_t len, u_char *dst)
++{
++ if ((size_t)(end - pos) < len) {
++ return NULL;
++ }
++
++ ngx_memcpy(dst, pos, len);
++
++ return pos + len;
++}
++
++
++static ngx_inline ngx_uint_t
++ngx_quic_varint_len(uint64_t value)
++{
++ if (value < (1 << 6)) {
++ return 1;
++ }
++
++ if (value < (1 << 14)) {
++ return 2;
++ }
++
++ if (value < (1 << 30)) {
++ return 4;
++ }
++
++ return 8;
++}
++
++
++static ngx_inline void
++ngx_quic_build_int(u_char **pos, uint64_t value)
++{
++ u_char *p;
++
++ p = *pos;
++
++ if (value < (1 << 6)) {
++ ngx_quic_build_int_set(p, value, 0, 0);
++
++ } else if (value < (1 << 14)) {
++ ngx_quic_build_int_set(p, value, 1, 1);
++ ngx_quic_build_int_set(p, value, 0, 0);
++
++ } else if (value < (1 << 30)) {
++ ngx_quic_build_int_set(p, value, 3, 2);
++ ngx_quic_build_int_set(p, value, 2, 0);
++ ngx_quic_build_int_set(p, value, 1, 0);
++ ngx_quic_build_int_set(p, value, 0, 0);
++
++ } else {
++ ngx_quic_build_int_set(p, value, 7, 3);
++ ngx_quic_build_int_set(p, value, 6, 0);
++ ngx_quic_build_int_set(p, value, 5, 0);
++ ngx_quic_build_int_set(p, value, 4, 0);
++ ngx_quic_build_int_set(p, value, 3, 0);
++ ngx_quic_build_int_set(p, value, 2, 0);
++ ngx_quic_build_int_set(p, value, 1, 0);
++ ngx_quic_build_int_set(p, value, 0, 0);
++ }
++
++ *pos = p;
++}
++
++
++ngx_int_t
++ngx_quic_parse_packet(ngx_quic_header_t *pkt)
++{
++ if (!ngx_quic_long_pkt(pkt->flags)) {
++ pkt->level = ssl_encryption_application;
++
++ if (ngx_quic_parse_short_header(pkt, NGX_QUIC_SERVER_CID_LEN) != NGX_OK)
++ {
++ return NGX_DECLINED;
++ }
++
++ return NGX_OK;
++ }
++
++ if (ngx_quic_parse_long_header(pkt) != NGX_OK) {
++ return NGX_DECLINED;
++ }
++
++ if (!ngx_quic_supported_version(pkt->version)) {
++ return NGX_ABORT;
++ }
++
++ if (ngx_quic_parse_long_header_v1(pkt) != NGX_OK) {
++ return NGX_DECLINED;
++ }
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_parse_short_header(ngx_quic_header_t *pkt, size_t dcid_len)
++{
++ u_char *p, *end;
++
++ p = pkt->raw->pos;
++ end = pkt->data + pkt->len;
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
++ "quic packet rx short flags:%xd", pkt->flags);
++
++ if (!(pkt->flags & NGX_QUIC_PKT_FIXED_BIT)) {
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic fixed bit is not set");
++ return NGX_ERROR;
++ }
++
++ pkt->dcid.len = dcid_len;
++
++ p = ngx_quic_read_bytes(p, end, dcid_len, &pkt->dcid.data);
++ if (p == NULL) {
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
++ "quic packet is too small to read dcid");
++ return NGX_ERROR;
++ }
++
++ pkt->raw->pos = p;
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_parse_long_header(ngx_quic_header_t *pkt)
++{
++ u_char *p, *end;
++ uint8_t idlen;
++
++ p = pkt->raw->pos;
++ end = pkt->data + pkt->len;
++
++ p = ngx_quic_read_uint32(p, end, &pkt->version);
++ if (p == NULL) {
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
++ "quic packet is too small to read version");
++ return NGX_ERROR;
++ }
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
++ "quic packet rx long flags:%xd version:%xD",
++ pkt->flags, pkt->version);
++
++ if (!(pkt->flags & NGX_QUIC_PKT_FIXED_BIT)) {
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic fixed bit is not set");
++ return NGX_ERROR;
++ }
++
++ p = ngx_quic_read_uint8(p, end, &idlen);
++ if (p == NULL) {
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
++ "quic packet is too small to read dcid len");
++ return NGX_ERROR;
++ }
++
++ if (idlen > NGX_QUIC_CID_LEN_MAX) {
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
++ "quic packet dcid is too long");
++ return NGX_ERROR;
++ }
++
++ pkt->dcid.len = idlen;
++
++ p = ngx_quic_read_bytes(p, end, idlen, &pkt->dcid.data);
++ if (p == NULL) {
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
++ "quic packet is too small to read dcid");
++ return NGX_ERROR;
++ }
++
++ p = ngx_quic_read_uint8(p, end, &idlen);
++ if (p == NULL) {
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
++ "quic packet is too small to read scid len");
++ return NGX_ERROR;
++ }
++
++ if (idlen > NGX_QUIC_CID_LEN_MAX) {
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
++ "quic packet scid is too long");
++ return NGX_ERROR;
++ }
++
++ pkt->scid.len = idlen;
++
++ p = ngx_quic_read_bytes(p, end, idlen, &pkt->scid.data);
++ if (p == NULL) {
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
++ "quic packet is too small to read scid");
++ return NGX_ERROR;
++ }
++
++ pkt->raw->pos = p;
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_quic_supported_version(uint32_t version)
++{
++ ngx_uint_t i;
++
++ for (i = 0; i < NGX_QUIC_NVERSIONS; i++) {
++ if (ngx_quic_versions[i] == version) {
++ return 1;
++ }
++ }
++
++ return 0;
++}
++
++
++static ngx_int_t
++ngx_quic_parse_long_header_v1(ngx_quic_header_t *pkt)
++{
++ u_char *p, *end;
++ uint64_t varint;
++
++ p = pkt->raw->pos;
++ end = pkt->raw->last;
++
++ pkt->log->action = "parsing quic long header";
++
++ if (ngx_quic_pkt_in(pkt->flags)) {
++
++ if (pkt->len < NGX_QUIC_MIN_INITIAL_SIZE) {
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
++ "quic UDP datagram is too small for initial packet");
++ return NGX_DECLINED;
++ }
++
++ p = ngx_quic_parse_int(p, end, &varint);
++ if (p == NULL) {
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
++ "quic failed to parse token length");
++ return NGX_ERROR;
++ }
++
++ pkt->token.len = varint;
++
++ p = ngx_quic_read_bytes(p, end, pkt->token.len, &pkt->token.data);
++ if (p == NULL) {
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
++ "quic packet too small to read token data");
++ return NGX_ERROR;
++ }
++
++ pkt->level = ssl_encryption_initial;
++
++ } else if (ngx_quic_pkt_zrtt(pkt->flags)) {
++ pkt->level = ssl_encryption_early_data;
++
++ } else if (ngx_quic_pkt_hs(pkt->flags)) {
++ pkt->level = ssl_encryption_handshake;
++
++ } else {
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
++ "quic bad packet type");
++ return NGX_DECLINED;
++ }
++
++ p = ngx_quic_parse_int(p, end, &varint);
++ if (p == NULL) {
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic bad packet length");
++ return NGX_ERROR;
++ }
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
++ "quic packet rx %s len:%uL",
++ ngx_quic_level_name(pkt->level), varint);
++
++ if (varint > (uint64_t) ((pkt->data + pkt->len) - p)) {
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic truncated %s packet",
++ ngx_quic_level_name(pkt->level));
++ return NGX_ERROR;
++ }
++
++ pkt->raw->pos = p;
++ pkt->len = p + varint - pkt->data;
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_get_packet_dcid(ngx_log_t *log, u_char *data, size_t n,
++ ngx_str_t *dcid)
++{
++ size_t len, offset;
++
++ if (n == 0) {
++ goto failed;
++ }
++
++ if (ngx_quic_long_pkt(*data)) {
++ if (n < NGX_QUIC_LONG_DCID_LEN_OFFSET + 1) {
++ goto failed;
++ }
++
++ len = data[NGX_QUIC_LONG_DCID_LEN_OFFSET];
++ offset = NGX_QUIC_LONG_DCID_OFFSET;
++
++ } else {
++ len = NGX_QUIC_SERVER_CID_LEN;
++ offset = NGX_QUIC_SHORT_DCID_OFFSET;
++ }
++
++ if (n < len + offset) {
++ goto failed;
++ }
++
++ dcid->len = len;
++ dcid->data = &data[offset];
++
++ return NGX_OK;
++
++failed:
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, log, 0, "quic malformed packet");
++
++ return NGX_ERROR;
++}
++
++
++size_t
++ngx_quic_create_version_negotiation(ngx_quic_header_t *pkt, u_char *out)
++{
++ u_char *p, *start;
++ ngx_uint_t i;
++
++ p = start = out;
++
++ *p++ = pkt->flags;
++
++ /*
++ * The Version field of a Version Negotiation packet
++ * MUST be set to 0x00000000
++ */
++ p = ngx_quic_write_uint32(p, 0);
++
++ *p++ = pkt->dcid.len;
++ p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len);
++
++ *p++ = pkt->scid.len;
++ p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len);
++
++ for (i = 0; i < NGX_QUIC_NVERSIONS; i++) {
++ p = ngx_quic_write_uint32(p, ngx_quic_versions[i]);
++ }
++
++ return p - start;
++}
++
++
++/* returns the amount of payload quic packet of "pkt_len" size may fit or 0 */
++size_t
++ngx_quic_payload_size(ngx_quic_header_t *pkt, size_t pkt_len)
++{
++ size_t len;
++
++ if ngx_quic_short_pkt(pkt->flags) {
++
++ len = 1 + pkt->dcid.len + pkt->num_len + EVP_GCM_TLS_TAG_LEN;
++ if (len > pkt_len) {
++ return 0;
++ }
++
++ return pkt_len - len;
++ }
++
++ /* flags, version, dcid and scid with lengths and zero-length token */
++ len = 5 + 2 + pkt->dcid.len + pkt->scid.len
++ + (pkt->level == ssl_encryption_initial ? 1 : 0);
++
++ if (len > pkt_len) {
++ return 0;
++ }
++
++ /* (pkt_len - len) is 'remainder' packet length (see RFC 9000, 17.2) */
++ len += ngx_quic_varint_len(pkt_len - len)
++ + pkt->num_len + EVP_GCM_TLS_TAG_LEN;
++
++ if (len > pkt_len) {
++ return 0;
++ }
++
++ return pkt_len - len;
++}
++
++
++size_t
++ngx_quic_create_header(ngx_quic_header_t *pkt, u_char *out, u_char **pnp)
++{
++ return ngx_quic_short_pkt(pkt->flags)
++ ? ngx_quic_create_short_header(pkt, out, pnp)
++ : ngx_quic_create_long_header(pkt, out, pnp);
++}
++
++
++static size_t
++ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out,
++ u_char **pnp)
++{
++ size_t rem_len;
++ u_char *p, *start;
++
++ rem_len = pkt->num_len + pkt->payload.len + EVP_GCM_TLS_TAG_LEN;
++
++ if (out == NULL) {
++ return 5 + 2 + pkt->dcid.len + pkt->scid.len
++ + ngx_quic_varint_len(rem_len) + pkt->num_len
++ + (pkt->level == ssl_encryption_initial ? 1 : 0);
++ }
++
++ p = start = out;
++
++ *p++ = pkt->flags;
++
++ p = ngx_quic_write_uint32(p, pkt->version);
++
++ *p++ = pkt->dcid.len;
++ p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len);
++
++ *p++ = pkt->scid.len;
++ p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len);
++
++ if (pkt->level == ssl_encryption_initial) {
++ ngx_quic_build_int(&p, 0);
++ }
++
++ ngx_quic_build_int(&p, rem_len);
++
++ *pnp = p;
++
++ switch (pkt->num_len) {
++ case 1:
++ *p++ = pkt->trunc;
++ break;
++ case 2:
++ p = ngx_quic_write_uint16(p, pkt->trunc);
++ break;
++ case 3:
++ p = ngx_quic_write_uint24(p, pkt->trunc);
++ break;
++ case 4:
++ p = ngx_quic_write_uint32(p, pkt->trunc);
++ break;
++ }
++
++ return p - start;
++}
++
++
++static size_t
++ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out,
++ u_char **pnp)
++{
++ u_char *p, *start;
++
++ if (out == NULL) {
++ return 1 + pkt->dcid.len + pkt->num_len;
++ }
++
++ p = start = out;
++
++ *p++ = pkt->flags;
++
++ p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len);
++
++ *pnp = p;
++
++ switch (pkt->num_len) {
++ case 1:
++ *p++ = pkt->trunc;
++ break;
++ case 2:
++ p = ngx_quic_write_uint16(p, pkt->trunc);
++ break;
++ case 3:
++ p = ngx_quic_write_uint24(p, pkt->trunc);
++ break;
++ case 4:
++ p = ngx_quic_write_uint32(p, pkt->trunc);
++ break;
++ }
++
++ return p - start;
++}
++
++
++size_t
++ngx_quic_create_retry_itag(ngx_quic_header_t *pkt, u_char *out,
++ u_char **start)
++{
++ u_char *p;
++
++ p = out;
++
++ *p++ = pkt->odcid.len;
++ p = ngx_cpymem(p, pkt->odcid.data, pkt->odcid.len);
++
++ *start = p;
++
++ *p++ = 0xff;
++
++ p = ngx_quic_write_uint32(p, pkt->version);
++
++ *p++ = pkt->dcid.len;
++ p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len);
++
++ *p++ = pkt->scid.len;
++ p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len);
++
++ p = ngx_cpymem(p, pkt->token.data, pkt->token.len);
++
++ return p - out;
++}
++
++
++ssize_t
++ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end,
++ ngx_quic_frame_t *f)
++{
++ u_char *p;
++ uint64_t varint;
++ ngx_buf_t *b;
++ ngx_uint_t i;
++
++ b = f->data->buf;
++
++ p = start;
++
++ p = ngx_quic_parse_int(p, end, &varint);
++ if (p == NULL) {
++ pkt->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR;
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
++ "quic failed to obtain quic frame type");
++ return NGX_ERROR;
++ }
++
++ if (varint > NGX_QUIC_FT_LAST) {
++ pkt->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR;
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
++ "quic unknown frame type 0x%xL", varint);
++ return NGX_ERROR;
++ }
++
++ f->type = varint;
++
++ if (ngx_quic_frame_allowed(pkt, f->type) != NGX_OK) {
++ pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION;
++ return NGX_ERROR;
++ }
++
++ switch (f->type) {
++
++ case NGX_QUIC_FT_CRYPTO:
++
++ p = ngx_quic_parse_int(p, end, &f->u.crypto.offset);
++ if (p == NULL) {
++ goto error;
++ }
++
++ p = ngx_quic_parse_int(p, end, &f->u.crypto.length);
++ if (p == NULL) {
++ goto error;
++ }
++
++ p = ngx_quic_read_bytes(p, end, f->u.crypto.length, &b->pos);
++ if (p == NULL) {
++ goto error;
++ }
++
++ b->last = p;
++
++ break;
++
++ case NGX_QUIC_FT_PADDING:
++
++ while (p < end && *p == NGX_QUIC_FT_PADDING) {
++ p++;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_ACK:
++ case NGX_QUIC_FT_ACK_ECN:
++
++ if (!((p = ngx_quic_parse_int(p, end, &f->u.ack.largest))
++ && (p = ngx_quic_parse_int(p, end, &f->u.ack.delay))
++ && (p = ngx_quic_parse_int(p, end, &f->u.ack.range_count))
++ && (p = ngx_quic_parse_int(p, end, &f->u.ack.first_range))))
++ {
++ goto error;
++ }
++
++ b->pos = p;
++
++ /* process all ranges to get bounds, values are ignored */
++ for (i = 0; i < f->u.ack.range_count; i++) {
++
++ p = ngx_quic_parse_int(p, end, &varint);
++ if (p) {
++ p = ngx_quic_parse_int(p, end, &varint);
++ }
++
++ if (p == NULL) {
++ goto error;
++ }
++ }
++
++ b->last = p;
++
++ f->u.ack.ranges_length = b->last - b->pos;
++
++ if (f->type == NGX_QUIC_FT_ACK_ECN) {
++
++ if (!((p = ngx_quic_parse_int(p, end, &f->u.ack.ect0))
++ && (p = ngx_quic_parse_int(p, end, &f->u.ack.ect1))
++ && (p = ngx_quic_parse_int(p, end, &f->u.ack.ce))))
++ {
++ goto error;
++ }
++
++ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
++ "quic ACK ECN counters ect0:%uL ect1:%uL ce:%uL",
++ f->u.ack.ect0, f->u.ack.ect1, f->u.ack.ce);
++ }
++
++ break;
++
++ case NGX_QUIC_FT_PING:
++ break;
++
++ case NGX_QUIC_FT_NEW_CONNECTION_ID:
++
++ p = ngx_quic_parse_int(p, end, &f->u.ncid.seqnum);
++ if (p == NULL) {
++ goto error;
++ }
++
++ p = ngx_quic_parse_int(p, end, &f->u.ncid.retire);
++ if (p == NULL) {
++ goto error;
++ }
++
++ if (f->u.ncid.retire > f->u.ncid.seqnum) {
++ goto error;
++ }
++
++ p = ngx_quic_read_uint8(p, end, &f->u.ncid.len);
++ if (p == NULL) {
++ goto error;
++ }
++
++ if (f->u.ncid.len < 1 || f->u.ncid.len > NGX_QUIC_CID_LEN_MAX) {
++ goto error;
++ }
++
++ p = ngx_quic_copy_bytes(p, end, f->u.ncid.len, f->u.ncid.cid);
++ if (p == NULL) {
++ goto error;
++ }
++
++ p = ngx_quic_copy_bytes(p, end, NGX_QUIC_SR_TOKEN_LEN, f->u.ncid.srt);
++ if (p == NULL) {
++ goto error;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_RETIRE_CONNECTION_ID:
++
++ p = ngx_quic_parse_int(p, end, &f->u.retire_cid.sequence_number);
++ if (p == NULL) {
++ goto error;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_CONNECTION_CLOSE:
++ case NGX_QUIC_FT_CONNECTION_CLOSE_APP:
++
++ p = ngx_quic_parse_int(p, end, &f->u.close.error_code);
++ if (p == NULL) {
++ goto error;
++ }
++
++ if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) {
++ p = ngx_quic_parse_int(p, end, &f->u.close.frame_type);
++ if (p == NULL) {
++ goto error;
++ }
++ }
++
++ p = ngx_quic_parse_int(p, end, &varint);
++ if (p == NULL) {
++ goto error;
++ }
++
++ f->u.close.reason.len = varint;
++
++ p = ngx_quic_read_bytes(p, end, f->u.close.reason.len,
++ &f->u.close.reason.data);
++ if (p == NULL) {
++ goto error;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_STREAM:
++ case NGX_QUIC_FT_STREAM1:
++ case NGX_QUIC_FT_STREAM2:
++ case NGX_QUIC_FT_STREAM3:
++ case NGX_QUIC_FT_STREAM4:
++ case NGX_QUIC_FT_STREAM5:
++ case NGX_QUIC_FT_STREAM6:
++ case NGX_QUIC_FT_STREAM7:
++
++ f->u.stream.fin = (f->type & NGX_QUIC_STREAM_FRAME_FIN) ? 1 : 0;
++
++ p = ngx_quic_parse_int(p, end, &f->u.stream.stream_id);
++ if (p == NULL) {
++ goto error;
++ }
++
++ if (f->type & NGX_QUIC_STREAM_FRAME_OFF) {
++ f->u.stream.off = 1;
++
++ p = ngx_quic_parse_int(p, end, &f->u.stream.offset);
++ if (p == NULL) {
++ goto error;
++ }
++
++ } else {
++ f->u.stream.off = 0;
++ f->u.stream.offset = 0;
++ }
++
++ if (f->type & NGX_QUIC_STREAM_FRAME_LEN) {
++ f->u.stream.len = 1;
++
++ p = ngx_quic_parse_int(p, end, &f->u.stream.length);
++ if (p == NULL) {
++ goto error;
++ }
++
++ } else {
++ f->u.stream.len = 0;
++ f->u.stream.length = end - p; /* up to packet end */
++ }
++
++ p = ngx_quic_read_bytes(p, end, f->u.stream.length, &b->pos);
++ if (p == NULL) {
++ goto error;
++ }
++
++ b->last = p;
++
++ f->type = NGX_QUIC_FT_STREAM;
++ break;
++
++ case NGX_QUIC_FT_MAX_DATA:
++
++ p = ngx_quic_parse_int(p, end, &f->u.max_data.max_data);
++ if (p == NULL) {
++ goto error;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_RESET_STREAM:
++
++ if (!((p = ngx_quic_parse_int(p, end, &f->u.reset_stream.id))
++ && (p = ngx_quic_parse_int(p, end, &f->u.reset_stream.error_code))
++ && (p = ngx_quic_parse_int(p, end,
++ &f->u.reset_stream.final_size))))
++ {
++ goto error;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_STOP_SENDING:
++
++ p = ngx_quic_parse_int(p, end, &f->u.stop_sending.id);
++ if (p == NULL) {
++ goto error;
++ }
++
++ p = ngx_quic_parse_int(p, end, &f->u.stop_sending.error_code);
++ if (p == NULL) {
++ goto error;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_STREAMS_BLOCKED:
++ case NGX_QUIC_FT_STREAMS_BLOCKED2:
++
++ p = ngx_quic_parse_int(p, end, &f->u.streams_blocked.limit);
++ if (p == NULL) {
++ goto error;
++ }
++
++ if (f->u.streams_blocked.limit > 0x1000000000000000) {
++ goto error;
++ }
++
++ f->u.streams_blocked.bidi =
++ (f->type == NGX_QUIC_FT_STREAMS_BLOCKED) ? 1 : 0;
++ break;
++
++ case NGX_QUIC_FT_MAX_STREAMS:
++ case NGX_QUIC_FT_MAX_STREAMS2:
++
++ p = ngx_quic_parse_int(p, end, &f->u.max_streams.limit);
++ if (p == NULL) {
++ goto error;
++ }
++
++ if (f->u.max_streams.limit > 0x1000000000000000) {
++ goto error;
++ }
++
++ f->u.max_streams.bidi = (f->type == NGX_QUIC_FT_MAX_STREAMS) ? 1 : 0;
++
++ break;
++
++ case NGX_QUIC_FT_MAX_STREAM_DATA:
++
++ p = ngx_quic_parse_int(p, end, &f->u.max_stream_data.id);
++ if (p == NULL) {
++ goto error;
++ }
++
++ p = ngx_quic_parse_int(p, end, &f->u.max_stream_data.limit);
++ if (p == NULL) {
++ goto error;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_DATA_BLOCKED:
++
++ p = ngx_quic_parse_int(p, end, &f->u.data_blocked.limit);
++ if (p == NULL) {
++ goto error;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_STREAM_DATA_BLOCKED:
++
++ p = ngx_quic_parse_int(p, end, &f->u.stream_data_blocked.id);
++ if (p == NULL) {
++ goto error;
++ }
++
++ p = ngx_quic_parse_int(p, end, &f->u.stream_data_blocked.limit);
++ if (p == NULL) {
++ goto error;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_PATH_CHALLENGE:
++
++ p = ngx_quic_copy_bytes(p, end, 8, f->u.path_challenge.data);
++ if (p == NULL) {
++ goto error;
++ }
++
++ break;
++
++ case NGX_QUIC_FT_PATH_RESPONSE:
++
++ p = ngx_quic_copy_bytes(p, end, 8, f->u.path_response.data);
++ if (p == NULL) {
++ goto error;
++ }
++
++ break;
++
++ default:
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
++ "quic unknown frame type 0x%xi", f->type);
++ return NGX_ERROR;
++ }
++
++ f->level = pkt->level;
++
++ return p - start;
++
++error:
++
++ pkt->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR;
++
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
++ "quic failed to parse frame type:0x%xi", f->type);
++
++ return NGX_ERROR;
++}
++
++
++static ngx_int_t
++ngx_quic_frame_allowed(ngx_quic_header_t *pkt, ngx_uint_t frame_type)
++{
++ uint8_t ptype;
++
++ /*
++ * RFC 9000, 12.4. Frames and Frame Types: Table 3
++ *
++ * Frame permissions per packet: 4 bits: IH01
++ */
++ static uint8_t ngx_quic_frame_masks[] = {
++ /* PADDING */ 0xF,
++ /* PING */ 0xF,
++ /* ACK */ 0xD,
++ /* ACK_ECN */ 0xD,
++ /* RESET_STREAM */ 0x3,
++ /* STOP_SENDING */ 0x3,
++ /* CRYPTO */ 0xD,
++ /* NEW_TOKEN */ 0x0, /* only sent by server */
++ /* STREAM */ 0x3,
++ /* STREAM1 */ 0x3,
++ /* STREAM2 */ 0x3,
++ /* STREAM3 */ 0x3,
++ /* STREAM4 */ 0x3,
++ /* STREAM5 */ 0x3,
++ /* STREAM6 */ 0x3,
++ /* STREAM7 */ 0x3,
++ /* MAX_DATA */ 0x3,
++ /* MAX_STREAM_DATA */ 0x3,
++ /* MAX_STREAMS */ 0x3,
++ /* MAX_STREAMS2 */ 0x3,
++ /* DATA_BLOCKED */ 0x3,
++ /* STREAM_DATA_BLOCKED */ 0x3,
++ /* STREAMS_BLOCKED */ 0x3,
++ /* STREAMS_BLOCKED2 */ 0x3,
++ /* NEW_CONNECTION_ID */ 0x3,
++ /* RETIRE_CONNECTION_ID */ 0x3,
++ /* PATH_CHALLENGE */ 0x3,
++ /* PATH_RESPONSE */ 0x1,
++ /* CONNECTION_CLOSE */ 0xF,
++ /* CONNECTION_CLOSE2 */ 0x3,
++ /* HANDSHAKE_DONE */ 0x0, /* only sent by server */
++ };
++
++ if (ngx_quic_long_pkt(pkt->flags)) {
++
++ if (ngx_quic_pkt_in(pkt->flags)) {
++ ptype = 8; /* initial */
++
++ } else if (ngx_quic_pkt_hs(pkt->flags)) {
++ ptype = 4; /* handshake */
++
++ } else {
++ ptype = 2; /* zero-rtt */
++ }
++
++ } else {
++ ptype = 1; /* application data */
++ }
++
++ if (ptype & ngx_quic_frame_masks[frame_type]) {
++ return NGX_OK;
++ }
++
++ ngx_log_error(NGX_LOG_INFO, pkt->log, 0,
++ "quic frame type 0x%xi is not "
++ "allowed in packet with flags 0x%xd",
++ frame_type, pkt->flags);
++
++ return NGX_DECLINED;
++}
++
++
++ssize_t
++ngx_quic_parse_ack_range(ngx_log_t *log, u_char *start, u_char *end,
++ uint64_t *gap, uint64_t *range)
++{
++ u_char *p;
++
++ p = start;
++
++ p = ngx_quic_parse_int(p, end, gap);
++ if (p == NULL) {
++ ngx_log_error(NGX_LOG_INFO, log, 0,
++ "quic failed to parse ack frame gap");
++ return NGX_ERROR;
++ }
++
++ p = ngx_quic_parse_int(p, end, range);
++ if (p == NULL) {
++ ngx_log_error(NGX_LOG_INFO, log, 0,
++ "quic failed to parse ack frame range");
++ return NGX_ERROR;
++ }
++
++ return p - start;
++}
++
++
++size_t
++ngx_quic_create_ack_range(u_char *p, uint64_t gap, uint64_t range)
++{
++ size_t len;
++ u_char *start;
++
++ if (p == NULL) {
++ len = ngx_quic_varint_len(gap);
++ len += ngx_quic_varint_len(range);
++ return len;
++ }
++
++ start = p;
++
++ ngx_quic_build_int(&p, gap);
++ ngx_quic_build_int(&p, range);
++
++ return p - start;
++}
++
++
++ssize_t
++ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f)
++{
++ /*
++ * RFC 9002, 2. Conventions and Definitions
++ *
++ * Ack-eliciting frames: All frames other than ACK, PADDING, and
++ * CONNECTION_CLOSE are considered ack-eliciting.
++ */
++ f->need_ack = 1;
++
++ switch (f->type) {
++ case NGX_QUIC_FT_PING:
++ return ngx_quic_create_ping(p);
++
++ case NGX_QUIC_FT_ACK:
++ f->need_ack = 0;
++ return ngx_quic_create_ack(p, &f->u.ack, f->data);
++
++ case NGX_QUIC_FT_RESET_STREAM:
++ return ngx_quic_create_reset_stream(p, &f->u.reset_stream);
++
++ case NGX_QUIC_FT_STOP_SENDING:
++ return ngx_quic_create_stop_sending(p, &f->u.stop_sending);
++
++ case NGX_QUIC_FT_CRYPTO:
++ return ngx_quic_create_crypto(p, &f->u.crypto, f->data);
++
++ case NGX_QUIC_FT_HANDSHAKE_DONE:
++ return ngx_quic_create_hs_done(p);
++
++ case NGX_QUIC_FT_NEW_TOKEN:
++ return ngx_quic_create_new_token(p, &f->u.token);
++
++ case NGX_QUIC_FT_STREAM:
++ return ngx_quic_create_stream(p, &f->u.stream, f->data);
++
++ case NGX_QUIC_FT_CONNECTION_CLOSE:
++ case NGX_QUIC_FT_CONNECTION_CLOSE_APP:
++ f->need_ack = 0;
++ return ngx_quic_create_close(p, f);
++
++ case NGX_QUIC_FT_MAX_STREAMS:
++ return ngx_quic_create_max_streams(p, &f->u.max_streams);
++
++ case NGX_QUIC_FT_MAX_STREAM_DATA:
++ return ngx_quic_create_max_stream_data(p, &f->u.max_stream_data);
++
++ case NGX_QUIC_FT_MAX_DATA:
++ return ngx_quic_create_max_data(p, &f->u.max_data);
++
++ case NGX_QUIC_FT_PATH_CHALLENGE:
++ return ngx_quic_create_path_challenge(p, &f->u.path_challenge);
++
++ case NGX_QUIC_FT_PATH_RESPONSE:
++ return ngx_quic_create_path_response(p, &f->u.path_response);
++
++ case NGX_QUIC_FT_NEW_CONNECTION_ID:
++ return ngx_quic_create_new_connection_id(p, &f->u.ncid);
++
++ case NGX_QUIC_FT_RETIRE_CONNECTION_ID:
++ return ngx_quic_create_retire_connection_id(p, &f->u.retire_cid);
++
++ default:
++ /* BUG: unsupported frame type generated */
++ return NGX_ERROR;
++ }
++}
++
++
++static size_t
++ngx_quic_create_ping(u_char *p)
++{
++ u_char *start;
++
++ if (p == NULL) {
++ return ngx_quic_varint_len(NGX_QUIC_FT_PING);
++ }
++
++ start = p;
++
++ ngx_quic_build_int(&p, NGX_QUIC_FT_PING);
++
++ return p - start;
++}
++
++
++static size_t
++ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack, ngx_chain_t *ranges)
++{
++ size_t len;
++ u_char *start;
++ ngx_buf_t *b;
++
++ if (p == NULL) {
++ len = ngx_quic_varint_len(NGX_QUIC_FT_ACK);
++ len += ngx_quic_varint_len(ack->largest);
++ len += ngx_quic_varint_len(ack->delay);
++ len += ngx_quic_varint_len(ack->range_count);
++ len += ngx_quic_varint_len(ack->first_range);
++ len += ack->ranges_length;
++
++ return len;
++ }
++
++ start = p;
++
++ ngx_quic_build_int(&p, NGX_QUIC_FT_ACK);
++ ngx_quic_build_int(&p, ack->largest);
++ ngx_quic_build_int(&p, ack->delay);
++ ngx_quic_build_int(&p, ack->range_count);
++ ngx_quic_build_int(&p, ack->first_range);
++
++ while (ranges) {
++ b = ranges->buf;
++ p = ngx_cpymem(p, b->pos, b->last - b->pos);
++ ranges = ranges->next;
++ }
++
++ return p - start;
++}
++
++
++static size_t
++ngx_quic_create_reset_stream(u_char *p, ngx_quic_reset_stream_frame_t *rs)
++{
++ size_t len;
++ u_char *start;
++
++ if (p == NULL) {
++ len = ngx_quic_varint_len(NGX_QUIC_FT_RESET_STREAM);
++ len += ngx_quic_varint_len(rs->id);
++ len += ngx_quic_varint_len(rs->error_code);
++ len += ngx_quic_varint_len(rs->final_size);
++ return len;
++ }
++
++ start = p;
++
++ ngx_quic_build_int(&p, NGX_QUIC_FT_RESET_STREAM);
++ ngx_quic_build_int(&p, rs->id);
++ ngx_quic_build_int(&p, rs->error_code);
++ ngx_quic_build_int(&p, rs->final_size);
++
++ return p - start;
++}
++
++
++static size_t
++ngx_quic_create_stop_sending(u_char *p, ngx_quic_stop_sending_frame_t *ss)
++{
++ size_t len;
++ u_char *start;
++
++ if (p == NULL) {
++ len = ngx_quic_varint_len(NGX_QUIC_FT_STOP_SENDING);
++ len += ngx_quic_varint_len(ss->id);
++ len += ngx_quic_varint_len(ss->error_code);
++ return len;
++ }
++
++ start = p;
++
++ ngx_quic_build_int(&p, NGX_QUIC_FT_STOP_SENDING);
++ ngx_quic_build_int(&p, ss->id);
++ ngx_quic_build_int(&p, ss->error_code);
++
++ return p - start;
++}
++
++
++static size_t
++ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto,
++ ngx_chain_t *data)
++{
++ size_t len;
++ u_char *start;
++ ngx_buf_t *b;
++
++ if (p == NULL) {
++ len = ngx_quic_varint_len(NGX_QUIC_FT_CRYPTO);
++ len += ngx_quic_varint_len(crypto->offset);
++ len += ngx_quic_varint_len(crypto->length);
++ len += crypto->length;
++
++ return len;
++ }
++
++ start = p;
++
++ ngx_quic_build_int(&p, NGX_QUIC_FT_CRYPTO);
++ ngx_quic_build_int(&p, crypto->offset);
++ ngx_quic_build_int(&p, crypto->length);
++
++ while (data) {
++ b = data->buf;
++ p = ngx_cpymem(p, b->pos, b->last - b->pos);
++ data = data->next;
++ }
++
++ return p - start;
++}
++
++
++static size_t
++ngx_quic_create_hs_done(u_char *p)
++{
++ u_char *start;
++
++ if (p == NULL) {
++ return ngx_quic_varint_len(NGX_QUIC_FT_HANDSHAKE_DONE);
++ }
++
++ start = p;
++
++ ngx_quic_build_int(&p, NGX_QUIC_FT_HANDSHAKE_DONE);
++
++ return p - start;
++}
++
++
++static size_t
++ngx_quic_create_new_token(u_char *p, ngx_quic_new_token_frame_t *token)
++{
++ size_t len;
++ u_char *start;
++
++ if (p == NULL) {
++ len = ngx_quic_varint_len(NGX_QUIC_FT_NEW_TOKEN);
++ len += ngx_quic_varint_len(token->length);
++ len += token->length;
++
++ return len;
++ }
++
++ start = p;
++
++ ngx_quic_build_int(&p, NGX_QUIC_FT_NEW_TOKEN);
++ ngx_quic_build_int(&p, token->length);
++ p = ngx_cpymem(p, token->data, token->length);
++
++ return p - start;
++}
++
++
++static size_t
++ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf,
++ ngx_chain_t *data)
++{
++ size_t len;
++ u_char *start, type;
++ ngx_buf_t *b;
++
++ type = NGX_QUIC_FT_STREAM;
++
++ if (sf->off) {
++ type |= NGX_QUIC_STREAM_FRAME_OFF;
++ }
++
++ if (sf->len) {
++ type |= NGX_QUIC_STREAM_FRAME_LEN;
++ }
++
++ if (sf->fin) {
++ type |= NGX_QUIC_STREAM_FRAME_FIN;
++ }
++
++ if (p == NULL) {
++ len = ngx_quic_varint_len(type);
++ len += ngx_quic_varint_len(sf->stream_id);
++
++ if (sf->off) {
++ len += ngx_quic_varint_len(sf->offset);
++ }
++
++ if (sf->len) {
++ len += ngx_quic_varint_len(sf->length);
++ }
++
++ len += sf->length;
++
++ return len;
++ }
++
++ start = p;
++
++ ngx_quic_build_int(&p, type);
++ ngx_quic_build_int(&p, sf->stream_id);
++
++ if (sf->off) {
++ ngx_quic_build_int(&p, sf->offset);
++ }
++
++ if (sf->len) {
++ ngx_quic_build_int(&p, sf->length);
++ }
++
++ while (data) {
++ b = data->buf;
++ p = ngx_cpymem(p, b->pos, b->last - b->pos);
++ data = data->next;
++ }
++
++ return p - start;
++}
++
++
++static size_t
++ngx_quic_create_max_streams(u_char *p, ngx_quic_max_streams_frame_t *ms)
++{
++ size_t len;
++ u_char *start;
++ ngx_uint_t type;
++
++ type = ms->bidi ? NGX_QUIC_FT_MAX_STREAMS : NGX_QUIC_FT_MAX_STREAMS2;
++
++ if (p == NULL) {
++ len = ngx_quic_varint_len(type);
++ len += ngx_quic_varint_len(ms->limit);
++ return len;
++ }
++
++ start = p;
++
++ ngx_quic_build_int(&p, type);
++ ngx_quic_build_int(&p, ms->limit);
++
++ return p - start;
++}
++
++
++static ngx_int_t
++ngx_quic_parse_transport_param(u_char *p, u_char *end, uint16_t id,
++ ngx_quic_tp_t *dst)
++{
++ uint64_t varint;
++ ngx_str_t str;
++
++ varint = 0;
++ ngx_str_null(&str);
++
++ switch (id) {
++
++ case NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION:
++ /* zero-length option */
++ if (end - p != 0) {
++ return NGX_ERROR;
++ }
++ dst->disable_active_migration = 1;
++ return NGX_OK;
++
++ case NGX_QUIC_TP_MAX_IDLE_TIMEOUT:
++ case NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE:
++ case NGX_QUIC_TP_INITIAL_MAX_DATA:
++ case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL:
++ case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE:
++ case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI:
++ case NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI:
++ case NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI:
++ case NGX_QUIC_TP_ACK_DELAY_EXPONENT:
++ case NGX_QUIC_TP_MAX_ACK_DELAY:
++ case NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT:
++
++ p = ngx_quic_parse_int(p, end, &varint);
++ if (p == NULL) {
++ return NGX_ERROR;
++ }
++ break;
++
++ case NGX_QUIC_TP_INITIAL_SCID:
++
++ str.len = end - p;
++ str.data = p;
++ break;
++
++ default:
++ return NGX_DECLINED;
++ }
++
++ switch (id) {
++
++ case NGX_QUIC_TP_MAX_IDLE_TIMEOUT:
++ dst->max_idle_timeout = varint;
++ break;
++
++ case NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE:
++ dst->max_udp_payload_size = varint;
++ break;
++
++ case NGX_QUIC_TP_INITIAL_MAX_DATA:
++ dst->initial_max_data = varint;
++ break;
++
++ case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL:
++ dst->initial_max_stream_data_bidi_local = varint;
++ break;
++
++ case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE:
++ dst->initial_max_stream_data_bidi_remote = varint;
++ break;
++
++ case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI:
++ dst->initial_max_stream_data_uni = varint;
++ break;
++
++ case NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI:
++ dst->initial_max_streams_bidi = varint;
++ break;
++
++ case NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI:
++ dst->initial_max_streams_uni = varint;
++ break;
++
++ case NGX_QUIC_TP_ACK_DELAY_EXPONENT:
++ dst->ack_delay_exponent = varint;
++ break;
++
++ case NGX_QUIC_TP_MAX_ACK_DELAY:
++ dst->max_ack_delay = varint;
++ break;
++
++ case NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT:
++ dst->active_connection_id_limit = varint;
++ break;
++
++ case NGX_QUIC_TP_INITIAL_SCID:
++ dst->initial_scid = str;
++ break;
++
++ default:
++ return NGX_ERROR;
++ }
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp,
++ ngx_log_t *log)
++{
++ uint64_t id, len;
++ ngx_int_t rc;
++
++ while (p < end) {
++ p = ngx_quic_parse_int(p, end, &id);
++ if (p == NULL) {
++ ngx_log_error(NGX_LOG_INFO, log, 0,
++ "quic failed to parse transport param id");
++ return NGX_ERROR;
++ }
++
++ switch (id) {
++ case NGX_QUIC_TP_ORIGINAL_DCID:
++ case NGX_QUIC_TP_PREFERRED_ADDRESS:
++ case NGX_QUIC_TP_RETRY_SCID:
++ case NGX_QUIC_TP_SR_TOKEN:
++ ngx_log_error(NGX_LOG_INFO, log, 0,
++ "quic client sent forbidden transport param"
++ " id:0x%xL", id);
++ return NGX_ERROR;
++ }
++
++ p = ngx_quic_parse_int(p, end, &len);
++ if (p == NULL) {
++ ngx_log_error(NGX_LOG_INFO, log, 0,
++ "quic failed to parse"
++ " transport param id:0x%xL length", id);
++ return NGX_ERROR;
++ }
++
++ rc = ngx_quic_parse_transport_param(p, p + len, id, tp);
++
++ if (rc == NGX_ERROR) {
++ ngx_log_error(NGX_LOG_INFO, log, 0,
++ "quic failed to parse"
++ " transport param id:0x%xL data", id);
++ return NGX_ERROR;
++ }
++
++ if (rc == NGX_DECLINED) {
++ ngx_log_error(NGX_LOG_INFO, log, 0,
++ "quic %s transport param id:0x%xL, skipped",
++ (id % 31 == 27) ? "reserved" : "unknown", id);
++ }
++
++ p += len;
++ }
++
++ if (p != end) {
++ ngx_log_error(NGX_LOG_INFO, log, 0,
++ "quic trailing garbage in"
++ " transport parameters: bytes:%ui",
++ end - p);
++ return NGX_ERROR;
++ }
++
++ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, log, 0,
++ "quic transport parameters parsed ok");
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
++ "quic tp disable active migration: %ui",
++ tp->disable_active_migration);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp idle_timeout:%ui",
++ tp->max_idle_timeout);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
++ "quic tp max_udp_payload_size:%ui",
++ tp->max_udp_payload_size);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp max_data:%ui",
++ tp->initial_max_data);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
++ "quic tp max_stream_data_bidi_local:%ui",
++ tp->initial_max_stream_data_bidi_local);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
++ "quic tp max_stream_data_bidi_remote:%ui",
++ tp->initial_max_stream_data_bidi_remote);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
++ "quic tp max_stream_data_uni:%ui",
++ tp->initial_max_stream_data_uni);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
++ "quic tp initial_max_streams_bidi:%ui",
++ tp->initial_max_streams_bidi);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
++ "quic tp initial_max_streams_uni:%ui",
++ tp->initial_max_streams_uni);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
++ "quic tp ack_delay_exponent:%ui",
++ tp->ack_delay_exponent);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp max_ack_delay:%ui",
++ tp->max_ack_delay);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
++ "quic tp active_connection_id_limit:%ui",
++ tp->active_connection_id_limit);
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0,
++ "quic tp initial source_connection_id len:%uz %xV",
++ tp->initial_scid.len, &tp->initial_scid);
++
++ return NGX_OK;
++}
++
++
++static size_t
++ngx_quic_create_max_stream_data(u_char *p, ngx_quic_max_stream_data_frame_t *ms)
++{
++ size_t len;
++ u_char *start;
++
++ if (p == NULL) {
++ len = ngx_quic_varint_len(NGX_QUIC_FT_MAX_STREAM_DATA);
++ len += ngx_quic_varint_len(ms->id);
++ len += ngx_quic_varint_len(ms->limit);
++ return len;
++ }
++
++ start = p;
++
++ ngx_quic_build_int(&p, NGX_QUIC_FT_MAX_STREAM_DATA);
++ ngx_quic_build_int(&p, ms->id);
++ ngx_quic_build_int(&p, ms->limit);
++
++ return p - start;
++}
++
++
++static size_t
++ngx_quic_create_max_data(u_char *p, ngx_quic_max_data_frame_t *md)
++{
++ size_t len;
++ u_char *start;
++
++ if (p == NULL) {
++ len = ngx_quic_varint_len(NGX_QUIC_FT_MAX_DATA);
++ len += ngx_quic_varint_len(md->max_data);
++ return len;
++ }
++
++ start = p;
++
++ ngx_quic_build_int(&p, NGX_QUIC_FT_MAX_DATA);
++ ngx_quic_build_int(&p, md->max_data);
++
++ return p - start;
++}
++
++
++static size_t
++ngx_quic_create_path_challenge(u_char *p, ngx_quic_path_challenge_frame_t *pc)
++{
++ size_t len;
++ u_char *start;
++
++ if (p == NULL) {
++ len = ngx_quic_varint_len(NGX_QUIC_FT_PATH_CHALLENGE);
++ len += sizeof(pc->data);
++ return len;
++ }
++
++ start = p;
++
++ ngx_quic_build_int(&p, NGX_QUIC_FT_PATH_CHALLENGE);
++ p = ngx_cpymem(p, &pc->data, sizeof(pc->data));
++
++ return p - start;
++}
++
++
++static size_t
++ngx_quic_create_path_response(u_char *p, ngx_quic_path_challenge_frame_t *pc)
++{
++ size_t len;
++ u_char *start;
++
++ if (p == NULL) {
++ len = ngx_quic_varint_len(NGX_QUIC_FT_PATH_RESPONSE);
++ len += sizeof(pc->data);
++ return len;
++ }
++
++ start = p;
++
++ ngx_quic_build_int(&p, NGX_QUIC_FT_PATH_RESPONSE);
++ p = ngx_cpymem(p, &pc->data, sizeof(pc->data));
++
++ return p - start;
++}
++
++
++static size_t
++ngx_quic_create_new_connection_id(u_char *p, ngx_quic_new_conn_id_frame_t *ncid)
++{
++ size_t len;
++ u_char *start;
++
++ if (p == NULL) {
++ len = ngx_quic_varint_len(NGX_QUIC_FT_NEW_CONNECTION_ID);
++ len += ngx_quic_varint_len(ncid->seqnum);
++ len += ngx_quic_varint_len(ncid->retire);
++ len++;
++ len += ncid->len;
++ len += NGX_QUIC_SR_TOKEN_LEN;
++ return len;
++ }
++
++ start = p;
++
++ ngx_quic_build_int(&p, NGX_QUIC_FT_NEW_CONNECTION_ID);
++ ngx_quic_build_int(&p, ncid->seqnum);
++ ngx_quic_build_int(&p, ncid->retire);
++ *p++ = ncid->len;
++ p = ngx_cpymem(p, ncid->cid, ncid->len);
++ p = ngx_cpymem(p, ncid->srt, NGX_QUIC_SR_TOKEN_LEN);
++
++ return p - start;
++}
++
++
++static size_t
++ngx_quic_create_retire_connection_id(u_char *p,
++ ngx_quic_retire_cid_frame_t *rcid)
++{
++ size_t len;
++ u_char *start;
++
++ if (p == NULL) {
++ len = ngx_quic_varint_len(NGX_QUIC_FT_RETIRE_CONNECTION_ID);
++ len += ngx_quic_varint_len(rcid->sequence_number);
++ return len;
++ }
++
++ start = p;
++
++ ngx_quic_build_int(&p, NGX_QUIC_FT_RETIRE_CONNECTION_ID);
++ ngx_quic_build_int(&p, rcid->sequence_number);
++
++ return p - start;
++}
++
++
++ngx_int_t
++ngx_quic_init_transport_params(ngx_quic_tp_t *tp, ngx_quic_conf_t *qcf)
++{
++ ngx_uint_t nstreams;
++
++ ngx_memzero(tp, sizeof(ngx_quic_tp_t));
++
++ /*
++ * set by ngx_memzero():
++ *
++ * tp->disable_active_migration = 0;
++ * tp->original_dcid = { 0, NULL };
++ * tp->initial_scid = { 0, NULL };
++ * tp->retry_scid = { 0, NULL };
++ * tp->sr_token = { 0 }
++ * tp->sr_enabled = 0
++ * tp->preferred_address = NULL
++ */
++
++ tp->max_idle_timeout = qcf->timeout;
++
++ tp->max_udp_payload_size = qcf->mtu;
++
++ nstreams = qcf->max_concurrent_streams_bidi
++ + qcf->max_concurrent_streams_uni;
++
++ tp->initial_max_data = nstreams * qcf->stream_buffer_size;
++ tp->initial_max_stream_data_bidi_local = qcf->stream_buffer_size;
++ tp->initial_max_stream_data_bidi_remote = qcf->stream_buffer_size;
++ tp->initial_max_stream_data_uni = qcf->stream_buffer_size;
++
++ tp->initial_max_streams_bidi = qcf->max_concurrent_streams_bidi;
++ tp->initial_max_streams_uni = qcf->max_concurrent_streams_uni;
++
++ tp->max_ack_delay = NGX_QUIC_DEFAULT_MAX_ACK_DELAY;
++ tp->ack_delay_exponent = NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT;
++
++ tp->active_connection_id_limit = 2;
++ tp->disable_active_migration = qcf->disable_active_migration;
++
++ return NGX_OK;
++}
++
++
++ssize_t
++ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp,
++ size_t *clen)
++{
++ u_char *p;
++ size_t len;
++
++#define ngx_quic_tp_len(id, value) \
++ ngx_quic_varint_len(id) \
++ + ngx_quic_varint_len(value) \
++ + ngx_quic_varint_len(ngx_quic_varint_len(value))
++
++#define ngx_quic_tp_vint(id, value) \
++ do { \
++ ngx_quic_build_int(&p, id); \
++ ngx_quic_build_int(&p, ngx_quic_varint_len(value)); \
++ ngx_quic_build_int(&p, value); \
++ } while (0)
++
++#define ngx_quic_tp_strlen(id, value) \
++ ngx_quic_varint_len(id) \
++ + ngx_quic_varint_len(value.len) \
++ + value.len
++
++#define ngx_quic_tp_str(id, value) \
++ do { \
++ ngx_quic_build_int(&p, id); \
++ ngx_quic_build_int(&p, value.len); \
++ p = ngx_cpymem(p, value.data, value.len); \
++ } while (0)
++
++ len = ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_DATA, tp->initial_max_data);
++
++ len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI,
++ tp->initial_max_streams_uni);
++
++ len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI,
++ tp->initial_max_streams_bidi);
++
++ len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL,
++ tp->initial_max_stream_data_bidi_local);
++
++ len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE,
++ tp->initial_max_stream_data_bidi_remote);
++
++ len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI,
++ tp->initial_max_stream_data_uni);
++
++ len += ngx_quic_tp_len(NGX_QUIC_TP_MAX_IDLE_TIMEOUT,
++ tp->max_idle_timeout);
++
++ len += ngx_quic_tp_len(NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE,
++ tp->max_udp_payload_size);
++
++ if (tp->disable_active_migration) {
++ len += ngx_quic_varint_len(NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION);
++ len += ngx_quic_varint_len(0);
++ }
++
++ len += ngx_quic_tp_len(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT,
++ tp->active_connection_id_limit);
++
++ /* transport parameters listed above will be saved in 0-RTT context */
++ if (clen) {
++ *clen = len;
++ }
++
++ len += ngx_quic_tp_len(NGX_QUIC_TP_MAX_ACK_DELAY,
++ tp->max_ack_delay);
++
++ len += ngx_quic_tp_len(NGX_QUIC_TP_ACK_DELAY_EXPONENT,
++ tp->ack_delay_exponent);
++
++ len += ngx_quic_tp_strlen(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid);
++ len += ngx_quic_tp_strlen(NGX_QUIC_TP_INITIAL_SCID, tp->initial_scid);
++
++ if (tp->retry_scid.len) {
++ len += ngx_quic_tp_strlen(NGX_QUIC_TP_RETRY_SCID, tp->retry_scid);
++ }
++
++ len += ngx_quic_varint_len(NGX_QUIC_TP_SR_TOKEN);
++ len += ngx_quic_varint_len(NGX_QUIC_SR_TOKEN_LEN);
++ len += NGX_QUIC_SR_TOKEN_LEN;
++
++ if (pos == NULL) {
++ return len;
++ }
++
++ p = pos;
++
++ ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_DATA,
++ tp->initial_max_data);
++
++ ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI,
++ tp->initial_max_streams_uni);
++
++ ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI,
++ tp->initial_max_streams_bidi);
++
++ ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL,
++ tp->initial_max_stream_data_bidi_local);
++
++ ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE,
++ tp->initial_max_stream_data_bidi_remote);
++
++ ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI,
++ tp->initial_max_stream_data_uni);
++
++ ngx_quic_tp_vint(NGX_QUIC_TP_MAX_IDLE_TIMEOUT,
++ tp->max_idle_timeout);
++
++ ngx_quic_tp_vint(NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE,
++ tp->max_udp_payload_size);
++
++ if (tp->disable_active_migration) {
++ ngx_quic_build_int(&p, NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION);
++ ngx_quic_build_int(&p, 0);
++ }
++
++ ngx_quic_tp_vint(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT,
++ tp->active_connection_id_limit);
++
++ ngx_quic_tp_vint(NGX_QUIC_TP_MAX_ACK_DELAY,
++ tp->max_ack_delay);
++
++ ngx_quic_tp_vint(NGX_QUIC_TP_ACK_DELAY_EXPONENT,
++ tp->ack_delay_exponent);
++
++ ngx_quic_tp_str(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid);
++ ngx_quic_tp_str(NGX_QUIC_TP_INITIAL_SCID, tp->initial_scid);
++
++ if (tp->retry_scid.len) {
++ ngx_quic_tp_str(NGX_QUIC_TP_RETRY_SCID, tp->retry_scid);
++ }
++
++ ngx_quic_build_int(&p, NGX_QUIC_TP_SR_TOKEN);
++ ngx_quic_build_int(&p, NGX_QUIC_SR_TOKEN_LEN);
++ p = ngx_cpymem(p, tp->sr_token, NGX_QUIC_SR_TOKEN_LEN);
++
++ return p - pos;
++}
++
++
++static size_t
++ngx_quic_create_close(u_char *p, ngx_quic_frame_t *f)
++{
++ size_t len;
++ u_char *start;
++ ngx_quic_close_frame_t *cl;
++
++ cl = &f->u.close;
++
++ if (p == NULL) {
++ len = ngx_quic_varint_len(f->type);
++ len += ngx_quic_varint_len(cl->error_code);
++
++ if (f->type != NGX_QUIC_FT_CONNECTION_CLOSE_APP) {
++ len += ngx_quic_varint_len(cl->frame_type);
++ }
++
++ len += ngx_quic_varint_len(cl->reason.len);
++ len += cl->reason.len;
++
++ return len;
++ }
++
++ start = p;
++
++ ngx_quic_build_int(&p, f->type);
++ ngx_quic_build_int(&p, cl->error_code);
++
++ if (f->type != NGX_QUIC_FT_CONNECTION_CLOSE_APP) {
++ ngx_quic_build_int(&p, cl->frame_type);
++ }
++
++ ngx_quic_build_int(&p, cl->reason.len);
++ p = ngx_cpymem(p, cl->reason.data, cl->reason.len);
++
++ return p - start;
++}
++
++
++void
++ngx_quic_dcid_encode_key(u_char *dcid, uint64_t key)
++{
++ (void) ngx_quic_write_uint64(dcid, key);
++}
+diff -r 67408b4a12c0 src/event/quic/ngx_event_quic_transport.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/event/quic/ngx_event_quic_transport.h Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,395 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#ifndef _NGX_EVENT_QUIC_TRANSPORT_H_INCLUDED_
++#define _NGX_EVENT_QUIC_TRANSPORT_H_INCLUDED_
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++
++
++/*
++ * RFC 9000, 17.2. Long Header Packets
++ * 17.3. Short Header Packets
++ *
++ * QUIC flags in first byte
++ */
++#define NGX_QUIC_PKT_LONG 0x80 /* header form */
++#define NGX_QUIC_PKT_FIXED_BIT 0x40
++#define NGX_QUIC_PKT_TYPE 0x30 /* in long packet */
++#define NGX_QUIC_PKT_KPHASE 0x04 /* in short packet */
++
++#define ngx_quic_long_pkt(flags) ((flags) & NGX_QUIC_PKT_LONG)
++#define ngx_quic_short_pkt(flags) (((flags) & NGX_QUIC_PKT_LONG) == 0)
++
++/* Long packet types */
++#define NGX_QUIC_PKT_INITIAL 0x00
++#define NGX_QUIC_PKT_ZRTT 0x10
++#define NGX_QUIC_PKT_HANDSHAKE 0x20
++#define NGX_QUIC_PKT_RETRY 0x30
++
++#define ngx_quic_pkt_in(flags) \
++ (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_INITIAL)
++#define ngx_quic_pkt_zrtt(flags) \
++ (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_ZRTT)
++#define ngx_quic_pkt_hs(flags) \
++ (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_HANDSHAKE)
++#define ngx_quic_pkt_retry(flags) \
++ (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_RETRY)
++
++#define ngx_quic_pkt_rb_mask(flags) \
++ (ngx_quic_long_pkt(flags) ? 0x0C : 0x18)
++#define ngx_quic_pkt_hp_mask(flags) \
++ (ngx_quic_long_pkt(flags) ? 0x0F : 0x1F)
++
++#define ngx_quic_level_name(lvl) \
++ (lvl == ssl_encryption_application) ? "app" \
++ : (lvl == ssl_encryption_initial) ? "init" \
++ : (lvl == ssl_encryption_handshake) ? "hs" : "early"
++
++#define NGX_QUIC_MAX_CID_LEN 20
++#define NGX_QUIC_SERVER_CID_LEN NGX_QUIC_MAX_CID_LEN
++
++/* 12.4. Frames and Frame Types */
++#define NGX_QUIC_FT_PADDING 0x00
++#define NGX_QUIC_FT_PING 0x01
++#define NGX_QUIC_FT_ACK 0x02
++#define NGX_QUIC_FT_ACK_ECN 0x03
++#define NGX_QUIC_FT_RESET_STREAM 0x04
++#define NGX_QUIC_FT_STOP_SENDING 0x05
++#define NGX_QUIC_FT_CRYPTO 0x06
++#define NGX_QUIC_FT_NEW_TOKEN 0x07
++#define NGX_QUIC_FT_STREAM 0x08
++#define NGX_QUIC_FT_STREAM1 0x09
++#define NGX_QUIC_FT_STREAM2 0x0A
++#define NGX_QUIC_FT_STREAM3 0x0B
++#define NGX_QUIC_FT_STREAM4 0x0C
++#define NGX_QUIC_FT_STREAM5 0x0D
++#define NGX_QUIC_FT_STREAM6 0x0E
++#define NGX_QUIC_FT_STREAM7 0x0F
++#define NGX_QUIC_FT_MAX_DATA 0x10
++#define NGX_QUIC_FT_MAX_STREAM_DATA 0x11
++#define NGX_QUIC_FT_MAX_STREAMS 0x12
++#define NGX_QUIC_FT_MAX_STREAMS2 0x13
++#define NGX_QUIC_FT_DATA_BLOCKED 0x14
++#define NGX_QUIC_FT_STREAM_DATA_BLOCKED 0x15
++#define NGX_QUIC_FT_STREAMS_BLOCKED 0x16
++#define NGX_QUIC_FT_STREAMS_BLOCKED2 0x17
++#define NGX_QUIC_FT_NEW_CONNECTION_ID 0x18
++#define NGX_QUIC_FT_RETIRE_CONNECTION_ID 0x19
++#define NGX_QUIC_FT_PATH_CHALLENGE 0x1A
++#define NGX_QUIC_FT_PATH_RESPONSE 0x1B
++#define NGX_QUIC_FT_CONNECTION_CLOSE 0x1C
++#define NGX_QUIC_FT_CONNECTION_CLOSE_APP 0x1D
++#define NGX_QUIC_FT_HANDSHAKE_DONE 0x1E
++
++#define NGX_QUIC_FT_LAST NGX_QUIC_FT_HANDSHAKE_DONE
++
++/* 22.5. QUIC Transport Error Codes Registry */
++#define NGX_QUIC_ERR_NO_ERROR 0x00
++#define NGX_QUIC_ERR_INTERNAL_ERROR 0x01
++#define NGX_QUIC_ERR_CONNECTION_REFUSED 0x02
++#define NGX_QUIC_ERR_FLOW_CONTROL_ERROR 0x03
++#define NGX_QUIC_ERR_STREAM_LIMIT_ERROR 0x04
++#define NGX_QUIC_ERR_STREAM_STATE_ERROR 0x05
++#define NGX_QUIC_ERR_FINAL_SIZE_ERROR 0x06
++#define NGX_QUIC_ERR_FRAME_ENCODING_ERROR 0x07
++#define NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR 0x08
++#define NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR 0x09
++#define NGX_QUIC_ERR_PROTOCOL_VIOLATION 0x0A
++#define NGX_QUIC_ERR_INVALID_TOKEN 0x0B
++#define NGX_QUIC_ERR_APPLICATION_ERROR 0x0C
++#define NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED 0x0D
++#define NGX_QUIC_ERR_KEY_UPDATE_ERROR 0x0E
++#define NGX_QUIC_ERR_AEAD_LIMIT_REACHED 0x0F
++#define NGX_QUIC_ERR_NO_VIABLE_PATH 0x10
++
++#define NGX_QUIC_ERR_CRYPTO_ERROR 0x100
++
++#define NGX_QUIC_ERR_CRYPTO(e) (NGX_QUIC_ERR_CRYPTO_ERROR + (e))
++
++
++/* 22.3. QUIC Transport Parameters Registry */
++#define NGX_QUIC_TP_ORIGINAL_DCID 0x00
++#define NGX_QUIC_TP_MAX_IDLE_TIMEOUT 0x01
++#define NGX_QUIC_TP_SR_TOKEN 0x02
++#define NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE 0x03
++#define NGX_QUIC_TP_INITIAL_MAX_DATA 0x04
++#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL 0x05
++#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE 0x06
++#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI 0x07
++#define NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI 0x08
++#define NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI 0x09
++#define NGX_QUIC_TP_ACK_DELAY_EXPONENT 0x0A
++#define NGX_QUIC_TP_MAX_ACK_DELAY 0x0B
++#define NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION 0x0C
++#define NGX_QUIC_TP_PREFERRED_ADDRESS 0x0D
++#define NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT 0x0E
++#define NGX_QUIC_TP_INITIAL_SCID 0x0F
++#define NGX_QUIC_TP_RETRY_SCID 0x10
++
++#define NGX_QUIC_CID_LEN_MIN 8
++#define NGX_QUIC_CID_LEN_MAX 20
++
++#define NGX_QUIC_MAX_RANGES 10
++
++
++typedef struct {
++ uint64_t gap;
++ uint64_t range;
++} ngx_quic_ack_range_t;
++
++
++typedef struct {
++ uint64_t largest;
++ uint64_t delay;
++ uint64_t range_count;
++ uint64_t first_range;
++ uint64_t ect0;
++ uint64_t ect1;
++ uint64_t ce;
++ uint64_t ranges_length;
++} ngx_quic_ack_frame_t;
++
++
++typedef struct {
++ uint64_t seqnum;
++ uint64_t retire;
++ uint8_t len;
++ u_char cid[NGX_QUIC_CID_LEN_MAX];
++ u_char srt[NGX_QUIC_SR_TOKEN_LEN];
++} ngx_quic_new_conn_id_frame_t;
++
++
++typedef struct {
++ uint64_t length;
++ u_char *data;
++} ngx_quic_new_token_frame_t;
++
++/*
++ * common layout for CRYPTO and STREAM frames;
++ * conceptually, CRYPTO frame is also a stream
++ * frame lacking some properties
++ */
++typedef struct {
++ uint64_t offset;
++ uint64_t length;
++} ngx_quic_ordered_frame_t;
++
++typedef ngx_quic_ordered_frame_t ngx_quic_crypto_frame_t;
++
++
++typedef struct {
++ /* initial fields same as in ngx_quic_ordered_frame_t */
++ uint64_t offset;
++ uint64_t length;
++
++ uint64_t stream_id;
++ unsigned off:1;
++ unsigned len:1;
++ unsigned fin:1;
++} ngx_quic_stream_frame_t;
++
++
++typedef struct {
++ uint64_t max_data;
++} ngx_quic_max_data_frame_t;
++
++
++typedef struct {
++ uint64_t error_code;
++ uint64_t frame_type;
++ ngx_str_t reason;
++} ngx_quic_close_frame_t;
++
++
++typedef struct {
++ uint64_t id;
++ uint64_t error_code;
++ uint64_t final_size;
++} ngx_quic_reset_stream_frame_t;
++
++
++typedef struct {
++ uint64_t id;
++ uint64_t error_code;
++} ngx_quic_stop_sending_frame_t;
++
++
++typedef struct {
++ uint64_t limit;
++ ngx_uint_t bidi; /* unsigned: bidi:1 */
++} ngx_quic_streams_blocked_frame_t;
++
++
++typedef struct {
++ uint64_t limit;
++ ngx_uint_t bidi; /* unsigned: bidi:1 */
++} ngx_quic_max_streams_frame_t;
++
++
++typedef struct {
++ uint64_t id;
++ uint64_t limit;
++} ngx_quic_max_stream_data_frame_t;
++
++
++typedef struct {
++ uint64_t limit;
++} ngx_quic_data_blocked_frame_t;
++
++
++typedef struct {
++ uint64_t id;
++ uint64_t limit;
++} ngx_quic_stream_data_blocked_frame_t;
++
++
++typedef struct {
++ uint64_t sequence_number;
++} ngx_quic_retire_cid_frame_t;
++
++
++typedef struct {
++ u_char data[8];
++} ngx_quic_path_challenge_frame_t;
++
++
++typedef struct ngx_quic_frame_s ngx_quic_frame_t;
++
++struct ngx_quic_frame_s {
++ ngx_uint_t type;
++ enum ssl_encryption_level_t level;
++ ngx_queue_t queue;
++ uint64_t pnum;
++ size_t plen;
++ ngx_msec_t first;
++ ngx_msec_t last;
++ ssize_t len;
++ unsigned need_ack:1;
++ unsigned pkt_need_ack:1;
++ unsigned flush:1;
++
++ ngx_chain_t *data;
++ union {
++ ngx_quic_ack_frame_t ack;
++ ngx_quic_crypto_frame_t crypto;
++ ngx_quic_ordered_frame_t ord;
++ ngx_quic_new_conn_id_frame_t ncid;
++ ngx_quic_new_token_frame_t token;
++ ngx_quic_stream_frame_t stream;
++ ngx_quic_max_data_frame_t max_data;
++ ngx_quic_close_frame_t close;
++ ngx_quic_reset_stream_frame_t reset_stream;
++ ngx_quic_stop_sending_frame_t stop_sending;
++ ngx_quic_streams_blocked_frame_t streams_blocked;
++ ngx_quic_max_streams_frame_t max_streams;
++ ngx_quic_max_stream_data_frame_t max_stream_data;
++ ngx_quic_data_blocked_frame_t data_blocked;
++ ngx_quic_stream_data_blocked_frame_t stream_data_blocked;
++ ngx_quic_retire_cid_frame_t retire_cid;
++ ngx_quic_path_challenge_frame_t path_challenge;
++ ngx_quic_path_challenge_frame_t path_response;
++ } u;
++};
++
++
++typedef struct {
++ ngx_log_t *log;
++
++ ngx_quic_keys_t *keys;
++
++ ngx_msec_t received;
++ uint64_t number;
++ uint8_t num_len;
++ uint32_t trunc;
++ uint8_t flags;
++ uint32_t version;
++ ngx_str_t token;
++ enum ssl_encryption_level_t level;
++ ngx_uint_t error;
++
++ /* filled in by parser */
++ ngx_buf_t *raw; /* udp datagram */
++
++ u_char *data; /* quic packet */
++ size_t len;
++
++ /* cleartext fields */
++ ngx_str_t odcid; /* retry packet tag */
++ ngx_str_t dcid;
++ ngx_str_t scid;
++ uint64_t pn;
++ u_char *plaintext;
++ ngx_str_t payload; /* decrypted data */
++
++ unsigned need_ack:1;
++ unsigned key_phase:1;
++ unsigned key_update:1;
++ unsigned parsed:1;
++ unsigned decrypted:1;
++ unsigned validated:1;
++ unsigned retried:1;
++ unsigned first:1;
++} ngx_quic_header_t;
++
++
++typedef struct {
++ ngx_msec_t max_idle_timeout;
++ ngx_msec_t max_ack_delay;
++
++ size_t max_udp_payload_size;
++ size_t initial_max_data;
++ size_t initial_max_stream_data_bidi_local;
++ size_t initial_max_stream_data_bidi_remote;
++ size_t initial_max_stream_data_uni;
++ ngx_uint_t initial_max_streams_bidi;
++ ngx_uint_t initial_max_streams_uni;
++ ngx_uint_t ack_delay_exponent;
++ ngx_uint_t active_connection_id_limit;
++ ngx_flag_t disable_active_migration;
++
++ ngx_str_t original_dcid;
++ ngx_str_t initial_scid;
++ ngx_str_t retry_scid;
++ u_char sr_token[NGX_QUIC_SR_TOKEN_LEN];
++
++ /* TODO */
++ void *preferred_address;
++} ngx_quic_tp_t;
++
++
++ngx_int_t ngx_quic_parse_packet(ngx_quic_header_t *pkt);
++
++size_t ngx_quic_create_version_negotiation(ngx_quic_header_t *pkt, u_char *out);
++
++size_t ngx_quic_payload_size(ngx_quic_header_t *pkt, size_t pkt_len);
++
++size_t ngx_quic_create_header(ngx_quic_header_t *pkt, u_char *out,
++ u_char **pnp);
++
++size_t ngx_quic_create_retry_itag(ngx_quic_header_t *pkt, u_char *out,
++ u_char **start);
++
++ssize_t ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end,
++ ngx_quic_frame_t *frame);
++ssize_t ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f);
++
++ssize_t ngx_quic_parse_ack_range(ngx_log_t *log, u_char *start,
++ u_char *end, uint64_t *gap, uint64_t *range);
++size_t ngx_quic_create_ack_range(u_char *p, uint64_t gap, uint64_t range);
++
++ngx_int_t ngx_quic_init_transport_params(ngx_quic_tp_t *tp,
++ ngx_quic_conf_t *qcf);
++ngx_int_t ngx_quic_parse_transport_params(u_char *p, u_char *end,
++ ngx_quic_tp_t *tp, ngx_log_t *log);
++ssize_t ngx_quic_create_transport_params(u_char *p, u_char *end,
++ ngx_quic_tp_t *tp, size_t *clen);
++
++void ngx_quic_dcid_encode_key(u_char *dcid, uint64_t key);
++
++#endif /* _NGX_EVENT_QUIC_TRANSPORT_H_INCLUDED_ */
+diff -r 67408b4a12c0 src/http/modules/ngx_http_ssl_module.c
+--- a/src/http/modules/ngx_http_ssl_module.c Tue Dec 28 18:28:38 2021 +0300
++++ b/src/http/modules/ngx_http_ssl_module.c Tue Jan 04 18:14:15 2022 -0500
+@@ -419,16 +419,22 @@
+ unsigned char *outlen, const unsigned char *in, unsigned int inlen,
+ void *arg)
+ {
+- unsigned int srvlen;
+- unsigned char *srv;
++#if (NGX_HTTP_V3)
++ const char *fmt;
++#endif
++ unsigned int srvlen;
++ unsigned char *srv;
+ #if (NGX_DEBUG)
+- unsigned int i;
++ unsigned int i;
+ #endif
+-#if (NGX_HTTP_V2)
+- ngx_http_connection_t *hc;
++#if (NGX_HTTP_V2 || NGX_HTTP_V3)
++ ngx_http_connection_t *hc;
+ #endif
+-#if (NGX_HTTP_V2 || NGX_DEBUG)
+- ngx_connection_t *c;
++#if (NGX_HTTP_V3 && NGX_HTTP_V3_HQ)
++ ngx_http_v3_srv_conf_t *h3scf;
++#endif
++#if (NGX_HTTP_V2 || NGX_HTTP_V3 || NGX_DEBUG)
++ ngx_connection_t *c;
+
+ c = ngx_ssl_get_connection(ssl_conn);
+ #endif
+@@ -441,14 +447,46 @@
+ }
+ #endif
+
+-#if (NGX_HTTP_V2)
++#if (NGX_HTTP_V2 || NGX_HTTP_V3)
+ hc = c->data;
++#endif
+
++#if (NGX_HTTP_V2)
+ if (hc->addr_conf->http2) {
+ srv = (unsigned char *) NGX_HTTP_V2_ALPN_PROTO NGX_HTTP_ALPN_PROTOS;
+ srvlen = sizeof(NGX_HTTP_V2_ALPN_PROTO NGX_HTTP_ALPN_PROTOS) - 1;
+ } else
+ #endif
++#if (NGX_HTTP_V3)
++ if (hc->addr_conf->http3) {
++
++#if (NGX_HTTP_V3_HQ)
++ h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module);
++
++ if (h3scf->hq) {
++ srv = (unsigned char *) NGX_HTTP_V3_HQ_ALPN_PROTO;
++ srvlen = sizeof(NGX_HTTP_V3_HQ_ALPN_PROTO) - 1;
++ fmt = NGX_HTTP_V3_HQ_ALPN_DRAFT_FMT;
++ } else
++#endif
++ {
++ srv = (unsigned char *) NGX_HTTP_V3_ALPN_PROTO;
++ srvlen = sizeof(NGX_HTTP_V3_ALPN_PROTO) - 1;
++ fmt = NGX_HTTP_V3_ALPN_DRAFT_FMT;
++ }
++
++ /* QUIC draft */
++
++ if (ngx_quic_version(c) > 1) {
++ srv = ngx_pnalloc(c->pool, sizeof("\x05h3-xx") - 1);
++ if (srv == NULL) {
++ return SSL_TLSEXT_ERR_NOACK;
++ }
++ srvlen = ngx_sprintf(srv, fmt, ngx_quic_version(c)) - srv;
++ }
++
++ } else
++#endif
+ {
+ srv = (unsigned char *) NGX_HTTP_ALPN_PROTOS;
+ srvlen = sizeof(NGX_HTTP_ALPN_PROTOS) - 1;
+@@ -1240,6 +1278,7 @@
+ ngx_http_ssl_init(ngx_conf_t *cf)
+ {
+ ngx_uint_t a, p, s;
++ const char *name;
+ ngx_http_conf_addr_t *addr;
+ ngx_http_conf_port_t *port;
+ ngx_http_ssl_srv_conf_t *sscf;
+@@ -1289,22 +1328,38 @@
+ addr = port[p].addrs.elts;
+ for (a = 0; a < port[p].addrs.nelts; a++) {
+
+- if (!addr[a].opt.ssl) {
++ if (!addr[a].opt.ssl && !addr[a].opt.http3) {
+ continue;
+ }
+
++ if (addr[a].opt.http3) {
++ name = "http3";
++
++ } else {
++ name = "ssl";
++ }
++
+ cscf = addr[a].default_server;
+ sscf = cscf->ctx->srv_conf[ngx_http_ssl_module.ctx_index];
+
+ if (sscf->certificates) {
++
++ if (addr[a].opt.http3 && !(sscf->protocols & NGX_SSL_TLSv1_3)) {
++ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
++ "\"ssl_protocols\" must enable TLSv1.3 for "
++ "the \"listen ... %s\" directive in %s:%ui",
++ name, cscf->file_name, cscf->line);
++ return NGX_ERROR;
++ }
++
+ continue;
+ }
+
+ if (!sscf->reject_handshake) {
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "no \"ssl_certificate\" is defined for "
+- "the \"listen ... ssl\" directive in %s:%ui",
+- cscf->file_name, cscf->line);
++ "the \"listen ... %s\" directive in %s:%ui",
++ name, cscf->file_name, cscf->line);
+ return NGX_ERROR;
+ }
+
+@@ -1325,8 +1380,8 @@
+
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "no \"ssl_certificate\" is defined for "
+- "the \"listen ... ssl\" directive in %s:%ui",
+- cscf->file_name, cscf->line);
++ "the \"listen ... %s\" directive in %s:%ui",
++ name, cscf->file_name, cscf->line);
+ return NGX_ERROR;
+ }
+ }
+diff -r 67408b4a12c0 src/http/ngx_http.c
+--- a/src/http/ngx_http.c Tue Dec 28 18:28:38 2021 +0300
++++ b/src/http/ngx_http.c Tue Jan 04 18:14:15 2022 -0500
+@@ -1200,7 +1200,10 @@
+ port = cmcf->ports->elts;
+ for (i = 0; i < cmcf->ports->nelts; i++) {
+
+- if (p != port[i].port || sa->sa_family != port[i].family) {
++ if (p != port[i].port
++ || lsopt->type != port[i].type
++ || sa->sa_family != port[i].family)
++ {
+ continue;
+ }
+
+@@ -1217,6 +1220,7 @@
+ }
+
+ port->family = sa->sa_family;
++ port->type = lsopt->type;
+ port->port = p;
+ port->addrs.elts = NULL;
+
+@@ -1236,6 +1240,9 @@
+ #if (NGX_HTTP_V2)
+ ngx_uint_t http2;
+ #endif
++#if (NGX_HTTP_V3)
++ ngx_uint_t http3;
++#endif
+
+ /*
+ * we cannot compare whole sockaddr struct's as kernel
+@@ -1271,6 +1278,9 @@
+ #if (NGX_HTTP_V2)
+ http2 = lsopt->http2 || addr[i].opt.http2;
+ #endif
++#if (NGX_HTTP_V3)
++ http3 = lsopt->http3 || addr[i].opt.http3;
++#endif
+
+ if (lsopt->set) {
+
+@@ -1307,6 +1317,9 @@
+ #if (NGX_HTTP_V2)
+ addr[i].opt.http2 = http2;
+ #endif
++#if (NGX_HTTP_V3)
++ addr[i].opt.http3 = http3;
++#endif
+
+ return NGX_OK;
+ }
+@@ -1349,6 +1362,17 @@
+
+ #endif
+
++#if (NGX_HTTP_V3 && !defined NGX_QUIC)
++
++ if (lsopt->http3) {
++ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
++ "nginx was built with OpenSSL that lacks QUIC "
++ "support, HTTP/3 is not enabled for %V",
++ &lsopt->addr_text);
++ }
++
++#endif
++
+ addr = ngx_array_push(&port->addrs);
+ if (addr == NULL) {
+ return NGX_ERROR;
+@@ -1770,6 +1794,7 @@
+ }
+ #endif
+
++ ls->type = addr->opt.type;
+ ls->backlog = addr->opt.backlog;
+ ls->rcvbuf = addr->opt.rcvbuf;
+ ls->sndbuf = addr->opt.sndbuf;
+@@ -1805,6 +1830,12 @@
+ ls->reuseport = addr->opt.reuseport;
+ #endif
+
++ ls->wildcard = addr->opt.wildcard;
++
++#if (NGX_HTTP_V3)
++ ls->quic = addr->opt.http3;
++#endif
++
+ return ls;
+ }
+
+@@ -1837,6 +1868,9 @@
+ #if (NGX_HTTP_V2)
+ addrs[i].conf.http2 = addr[i].opt.http2;
+ #endif
++#if (NGX_HTTP_V3)
++ addrs[i].conf.http3 = addr[i].opt.http3;
++#endif
+ addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol;
+
+ if (addr[i].hash.buckets == NULL
+@@ -1902,6 +1936,9 @@
+ #if (NGX_HTTP_V2)
+ addrs6[i].conf.http2 = addr[i].opt.http2;
+ #endif
++#if (NGX_HTTP_V3)
++ addrs6[i].conf.http3 = addr[i].opt.http3;
++#endif
+ addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol;
+
+ if (addr[i].hash.buckets == NULL
+diff -r 67408b4a12c0 src/http/ngx_http.h
+--- a/src/http/ngx_http.h Tue Dec 28 18:28:38 2021 +0300
++++ b/src/http/ngx_http.h Tue Jan 04 18:14:15 2022 -0500
+@@ -20,6 +20,8 @@
+ typedef struct ngx_http_log_ctx_s ngx_http_log_ctx_t;
+ typedef struct ngx_http_chunked_s ngx_http_chunked_t;
+ typedef struct ngx_http_v2_stream_s ngx_http_v2_stream_t;
++typedef struct ngx_http_v3_parse_s ngx_http_v3_parse_t;
++typedef struct ngx_http_v3_session_s ngx_http_v3_session_t;
+
+ typedef ngx_int_t (*ngx_http_header_handler_pt)(ngx_http_request_t *r,
+ ngx_table_elt_t *h, ngx_uint_t offset);
+@@ -38,6 +40,9 @@
+ #if (NGX_HTTP_V2)
+ #include <ngx_http_v2.h>
+ #endif
++#if (NGX_HTTP_V3)
++#include <ngx_http_v3.h>
++#endif
+ #if (NGX_HTTP_CACHE)
+ #include <ngx_http_cache.h>
+ #endif
+@@ -124,6 +129,11 @@
+ void ngx_http_run_posted_requests(ngx_connection_t *c);
+ ngx_int_t ngx_http_post_request(ngx_http_request_t *r,
+ ngx_http_posted_request_t *pr);
++ngx_int_t ngx_http_set_virtual_server(ngx_http_request_t *r,
++ ngx_str_t *host);
++ngx_int_t ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool,
++ ngx_uint_t alloc);
++void ngx_http_close_request(ngx_http_request_t *r, ngx_int_t rc);
+ void ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc);
+ void ngx_http_free_request(ngx_http_request_t *r, ngx_int_t rc);
+
+@@ -167,7 +177,7 @@
+ #endif
+
+
+-#if (NGX_HTTP_V2)
++#if (NGX_HTTP_V2 || NGX_HTTP_V3)
+ ngx_int_t ngx_http_huff_decode(u_char *state, u_char *src, size_t len,
+ u_char **dst, ngx_uint_t last, ngx_log_t *log);
+ size_t ngx_http_huff_encode(u_char *src, size_t len, u_char *dst,
+diff -r 67408b4a12c0 src/http/ngx_http_core_module.c
+--- a/src/http/ngx_http_core_module.c Tue Dec 28 18:28:38 2021 +0300
++++ b/src/http/ngx_http_core_module.c Tue Jan 04 18:14:15 2022 -0500
+@@ -3897,6 +3897,7 @@
+ ngx_memzero(&lsopt, sizeof(ngx_http_listen_opt_t));
+
+ lsopt.backlog = NGX_LISTEN_BACKLOG;
++ lsopt.type = SOCK_STREAM;
+ lsopt.rcvbuf = -1;
+ lsopt.sndbuf = -1;
+ #if (NGX_HAVE_SETFIB)
+@@ -4095,6 +4096,19 @@
+ #endif
+ }
+
++ if (ngx_strcmp(value[n].data, "http3") == 0) {
++#if (NGX_HTTP_V3)
++ lsopt.http3 = 1;
++ lsopt.type = SOCK_DGRAM;
++ continue;
++#else
++ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
++ "the \"http3\" parameter requires "
++ "ngx_http_v3_module");
++ return NGX_CONF_ERROR;
++#endif
++ }
++
+ if (ngx_strncmp(value[n].data, "so_keepalive=", 13) == 0) {
+
+ if (ngx_strcmp(&value[n].data[13], "on") == 0) {
+@@ -4196,6 +4210,12 @@
+ return NGX_CONF_ERROR;
+ }
+
++#if (NGX_HTTP_SSL && NGX_HTTP_V3)
++ if (lsopt.ssl && lsopt.http3) {
++ return "\"ssl\" parameter is incompatible with \"http3\"";
++ }
++#endif
++
+ for (n = 0; n < u.naddrs; n++) {
+ lsopt.sockaddr = u.addrs[n].sockaddr;
+ lsopt.socklen = u.addrs[n].socklen;
+diff -r 67408b4a12c0 src/http/ngx_http_core_module.h
+--- a/src/http/ngx_http_core_module.h Tue Dec 28 18:28:38 2021 +0300
++++ b/src/http/ngx_http_core_module.h Tue Jan 04 18:14:15 2022 -0500
+@@ -75,6 +75,7 @@
+ unsigned wildcard:1;
+ unsigned ssl:1;
+ unsigned http2:1;
++ unsigned http3:1;
+ #if (NGX_HAVE_INET6)
+ unsigned ipv6only:1;
+ #endif
+@@ -86,6 +87,7 @@
+ int backlog;
+ int rcvbuf;
+ int sndbuf;
++ int type;
+ #if (NGX_HAVE_SETFIB)
+ int setfib;
+ #endif
+@@ -237,6 +239,7 @@
+
+ unsigned ssl:1;
+ unsigned http2:1;
++ unsigned http3:1;
+ unsigned proxy_protocol:1;
+ };
+
+@@ -266,6 +269,7 @@
+
+ typedef struct {
+ ngx_int_t family;
++ ngx_int_t type;
+ in_port_t port;
+ ngx_array_t addrs; /* array of ngx_http_conf_addr_t */
+ } ngx_http_conf_port_t;
+diff -r 67408b4a12c0 src/http/ngx_http_request.c
+--- a/src/http/ngx_http_request.c Tue Dec 28 18:28:38 2021 +0300
++++ b/src/http/ngx_http_request.c Tue Jan 04 18:14:15 2022 -0500
+@@ -31,10 +31,6 @@
+ static ngx_int_t ngx_http_process_user_agent(ngx_http_request_t *r,
+ ngx_table_elt_t *h, ngx_uint_t offset);
+
+-static ngx_int_t ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool,
+- ngx_uint_t alloc);
+-static ngx_int_t ngx_http_set_virtual_server(ngx_http_request_t *r,
+- ngx_str_t *host);
+ static ngx_int_t ngx_http_find_virtual_server(ngx_connection_t *c,
+ ngx_http_virtual_names_t *virtual_names, ngx_str_t *host,
+ ngx_http_request_t *r, ngx_http_core_srv_conf_t **cscfp);
+@@ -52,7 +48,6 @@
+ static void ngx_http_set_lingering_close(ngx_connection_t *c);
+ static void ngx_http_lingering_close_handler(ngx_event_t *ev);
+ static ngx_int_t ngx_http_post_action(ngx_http_request_t *r);
+-static void ngx_http_close_request(ngx_http_request_t *r, ngx_int_t error);
+ static void ngx_http_log_request(ngx_http_request_t *r);
+
+ static u_char *ngx_http_log_error(ngx_log_t *log, u_char *buf, size_t len);
+@@ -331,6 +326,13 @@
+ }
+ #endif
+
++#if (NGX_HTTP_V3)
++ if (hc->addr_conf->http3) {
++ ngx_http_v3_init(c);
++ return;
++ }
++#endif
++
+ #if (NGX_HTTP_SSL)
+ {
+ ngx_http_ssl_srv_conf_t *sscf;
+@@ -952,6 +954,14 @@
+ #ifdef SSL_OP_NO_RENEGOTIATION
+ SSL_set_options(ssl_conn, SSL_OP_NO_RENEGOTIATION);
+ #endif
++
++#ifdef SSL_OP_ENABLE_MIDDLEBOX_COMPAT
++#if (NGX_HTTP_V3)
++ if (c->listening->quic) {
++ SSL_clear_options(ssl_conn, SSL_OP_ENABLE_MIDDLEBOX_COMPAT);
++ }
++#endif
++#endif
+ }
+
+ done:
+@@ -2121,7 +2131,7 @@
+ }
+
+
+-static ngx_int_t
++ngx_int_t
+ ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool, ngx_uint_t alloc)
+ {
+ u_char *h, ch;
+@@ -2213,7 +2223,7 @@
+ }
+
+
+-static ngx_int_t
++ngx_int_t
+ ngx_http_set_virtual_server(ngx_http_request_t *r, ngx_str_t *host)
+ {
+ ngx_int_t rc;
+@@ -2736,6 +2746,13 @@
+ }
+ #endif
+
++#if (NGX_HTTP_V3)
++ if (r->connection->quic) {
++ ngx_http_close_request(r, 0);
++ return;
++ }
++#endif
++
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (r->main->count != 1) {
+@@ -2950,6 +2967,19 @@
+
+ #endif
+
++#if (NGX_HTTP_V3)
++
++ if (c->quic) {
++ if (c->read->error) {
++ err = 0;
++ goto closed;
++ }
++
++ return;
++ }
++
++#endif
++
+ #if (NGX_HAVE_KQUEUE)
+
+ if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
+@@ -3614,7 +3644,7 @@
+ }
+
+
+-static void
++void
+ ngx_http_close_request(ngx_http_request_t *r, ngx_int_t rc)
+ {
+ ngx_connection_t *c;
+@@ -3701,7 +3731,12 @@
+
+ log->action = "closing request";
+
+- if (r->connection->timedout) {
++ if (r->connection->timedout
++#if (NGX_HTTP_V3)
++ && r->connection->quic == NULL
++#endif
++ )
++ {
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (clcf->reset_timedout_connection) {
+@@ -3774,6 +3809,12 @@
+
+ #endif
+
++#if (NGX_HTTP_V3)
++ if (c->quic) {
++ ngx_http_v3_reset_connection(c);
++ }
++#endif
++
+ #if (NGX_STAT_STUB)
+ (void) ngx_atomic_fetch_add(ngx_stat_active, -1);
+ #endif
+diff -r 67408b4a12c0 src/http/ngx_http_request.h
+--- a/src/http/ngx_http_request.h Tue Dec 28 18:28:38 2021 +0300
++++ b/src/http/ngx_http_request.h Tue Jan 04 18:14:15 2022 -0500
+@@ -24,6 +24,7 @@
+ #define NGX_HTTP_VERSION_10 1000
+ #define NGX_HTTP_VERSION_11 1001
+ #define NGX_HTTP_VERSION_20 2000
++#define NGX_HTTP_VERSION_30 3000
+
+ #define NGX_HTTP_UNKNOWN 0x00000001
+ #define NGX_HTTP_GET 0x00000002
+@@ -321,6 +322,10 @@
+ #endif
+ #endif
+
++#if (NGX_HTTP_V3 || NGX_COMPAT)
++ ngx_http_v3_session_t *v3_session;
++#endif
++
+ ngx_chain_t *busy;
+ ngx_int_t nbusy;
+
+@@ -449,6 +454,7 @@
+
+ ngx_http_connection_t *http_connection;
+ ngx_http_v2_stream_t *stream;
++ ngx_http_v3_parse_t *v3_parse;
+
+ ngx_http_log_handler_pt log_handler;
+
+@@ -541,6 +547,7 @@
+ unsigned request_complete:1;
+ unsigned request_output:1;
+ unsigned header_sent:1;
++ unsigned response_sent:1;
+ unsigned expect_tested:1;
+ unsigned root_tested:1;
+ unsigned done:1;
+diff -r 67408b4a12c0 src/http/ngx_http_request_body.c
+--- a/src/http/ngx_http_request_body.c Tue Dec 28 18:28:38 2021 +0300
++++ b/src/http/ngx_http_request_body.c Tue Jan 04 18:14:15 2022 -0500
+@@ -92,6 +92,13 @@
+ }
+ #endif
+
++#if (NGX_HTTP_V3)
++ if (r->http_version == NGX_HTTP_VERSION_30) {
++ rc = ngx_http_v3_read_request_body(r);
++ goto done;
++ }
++#endif
++
+ preread = r->header_in->last - r->header_in->pos;
+
+ if (preread) {
+@@ -238,6 +245,18 @@
+ }
+ #endif
+
++#if (NGX_HTTP_V3)
++ if (r->http_version == NGX_HTTP_VERSION_30) {
++ rc = ngx_http_v3_read_unbuffered_request_body(r);
++
++ if (rc == NGX_OK) {
++ r->reading_body = 0;
++ }
++
++ return rc;
++ }
++#endif
++
+ if (r->connection->read->timedout) {
+ r->connection->timedout = 1;
+ return NGX_HTTP_REQUEST_TIME_OUT;
+@@ -625,6 +644,12 @@
+ }
+ #endif
+
++#if (NGX_HTTP_V3)
++ if (r->http_version == NGX_HTTP_VERSION_30) {
++ return NGX_OK;
++ }
++#endif
++
+ if (ngx_http_test_expect(r) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+@@ -917,11 +942,7 @@
+
+ if (r->expect_tested
+ || r->headers_in.expect == NULL
+- || r->http_version < NGX_HTTP_VERSION_11
+-#if (NGX_HTTP_V2)
+- || r->stream != NULL
+-#endif
+- )
++ || r->http_version != NGX_HTTP_VERSION_11)
+ {
+ return NGX_OK;
+ }
+diff -r 67408b4a12c0 src/http/ngx_http_upstream.c
+--- a/src/http/ngx_http_upstream.c Tue Dec 28 18:28:38 2021 +0300
++++ b/src/http/ngx_http_upstream.c Tue Jan 04 18:14:15 2022 -0500
+@@ -525,6 +525,13 @@
+ }
+ #endif
+
++#if (NGX_HTTP_V3)
++ if (c->quic) {
++ ngx_http_upstream_init_request(r);
++ return;
++ }
++#endif
++
+ if (c->read->timer_set) {
+ ngx_del_timer(c->read);
+ }
+@@ -1358,6 +1365,19 @@
+ }
+ #endif
+
++#if (NGX_HTTP_V3)
++
++ if (c->quic) {
++ if (c->write->error) {
++ ngx_http_upstream_finalize_request(r, u,
++ NGX_HTTP_CLIENT_CLOSED_REQUEST);
++ }
++
++ return;
++ }
++
++#endif
++
+ #if (NGX_HAVE_KQUEUE)
+
+ if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
+diff -r 67408b4a12c0 src/http/ngx_http_write_filter_module.c
+--- a/src/http/ngx_http_write_filter_module.c Tue Dec 28 18:28:38 2021 +0300
++++ b/src/http/ngx_http_write_filter_module.c Tue Jan 04 18:14:15 2022 -0500
+@@ -239,6 +239,10 @@
+ r->out = NULL;
+ c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;
+
++ if (last) {
++ r->response_sent = 1;
++ }
++
+ return NGX_OK;
+ }
+
+@@ -345,6 +349,10 @@
+
+ c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;
+
++ if (last) {
++ r->response_sent = 1;
++ }
++
+ if ((c->buffered & NGX_LOWLEVEL_BUFFERED) && r->postponed == NULL) {
+ return NGX_AGAIN;
+ }
+diff -r 67408b4a12c0 src/http/v3/ngx_http_v3.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/http/v3/ngx_http_v3.c Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,115 @@
++
++/*
++ * 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_connection_t *pc;
++ ngx_pool_cleanup_t *cln;
++ ngx_http_connection_t *hc;
++ ngx_http_v3_session_t *h3c;
++
++ pc = c->quic->parent;
++ hc = pc->data;
++
++ if (hc->v3_session) {
++ return NGX_OK;
++ }
++
++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init session");
++
++ h3c = ngx_pcalloc(pc->pool, sizeof(ngx_http_v3_session_t));
++ if (h3c == NULL) {
++ goto failed;
++ }
++
++ h3c->max_push_id = (uint64_t) -1;
++ h3c->goaway_push_id = (uint64_t) -1;
++
++ ngx_queue_init(&h3c->blocked);
++ ngx_queue_init(&h3c->pushing);
++
++ h3c->keepalive.log = pc->log;
++ h3c->keepalive.data = pc;
++ h3c->keepalive.handler = ngx_http_v3_keepalive_handler;
++ h3c->keepalive.cancelable = 1;
++
++ cln = ngx_pool_cleanup_add(pc->pool, 0);
++ if (cln == NULL) {
++ goto failed;
++ }
++
++ cln->handler = ngx_http_v3_cleanup_session;
++ cln->data = h3c;
++
++ hc->v3_session = h3c;
++
++ return ngx_http_v3_send_settings(c);
++
++failed:
++
++ ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create http3 session");
++
++ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR,
++ "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_quic_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);
++ }
++}
++
++
++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 -r 67408b4a12c0 src/http/v3/ngx_http_v3.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/http/v3/ngx_http_v3.h Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,166 @@
++
++/*
++ * 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_ALPN_DRAFT_FMT "\x05h3-%02uD"
++
++#define NGX_HTTP_V3_HQ_ALPN_PROTO "\x0Ahq-interop"
++#define NGX_HTTP_V3_HQ_ALPN_DRAFT_FMT "\x05hq-%02uD"
++
++#define NGX_HTTP_V3_VARLEN_INT_LEN 4
++#define NGX_HTTP_V3_PREFIX_INT_LEN 11
++
++#define NGX_HTTP_V3_STREAM_CONTROL 0x00
++#define NGX_HTTP_V3_STREAM_PUSH 0x01
++#define NGX_HTTP_V3_STREAM_ENCODER 0x02
++#define NGX_HTTP_V3_STREAM_DECODER 0x03
++
++#define NGX_HTTP_V3_FRAME_DATA 0x00
++#define NGX_HTTP_V3_FRAME_HEADERS 0x01
++#define NGX_HTTP_V3_FRAME_CANCEL_PUSH 0x03
++#define NGX_HTTP_V3_FRAME_SETTINGS 0x04
++#define NGX_HTTP_V3_FRAME_PUSH_PROMISE 0x05
++#define NGX_HTTP_V3_FRAME_GOAWAY 0x07
++#define NGX_HTTP_V3_FRAME_MAX_PUSH_ID 0x0d
++
++#define NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY 0x01
++#define NGX_HTTP_V3_PARAM_MAX_HEADER_LIST_SIZE 0x06
++#define NGX_HTTP_V3_PARAM_BLOCKED_STREAMS 0x07
++
++#define NGX_HTTP_V3_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->parent->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->parent, code, reason)
++
++#define ngx_http_v3_shutdown_connection(c, code, reason) \
++ ngx_quic_shutdown_connection(c->quic->parent, code, reason)
++
++
++typedef struct {
++ size_t max_table_capacity;
++ ngx_uint_t max_blocked_streams;
++ ngx_uint_t max_concurrent_pushes;
++ ngx_uint_t max_concurrent_streams;
++#if (NGX_HTTP_V3_HQ)
++ ngx_flag_t hq;
++#endif
++ ngx_quic_conf_t quic;
++} ngx_http_v3_srv_conf_t;
++
++
++typedef struct {
++ ngx_flag_t push_preload;
++ ngx_flag_t push;
++ ngx_array_t *pushes;
++} ngx_http_v3_loc_conf_t;
++
++
++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;
++
++ ngx_queue_t pushing;
++ ngx_uint_t npushing;
++ uint64_t next_push_id;
++ uint64_t max_push_id;
++ uint64_t goaway_push_id;
++
++ off_t total_bytes;
++ off_t payload_bytes;
++
++ ngx_uint_t goaway; /* unsigned goaway:1; */
++
++ ngx_connection_t *known_streams[NGX_HTTP_V3_MAX_KNOWN_STREAM];
++};
++
++
++void ngx_http_v3_init(ngx_connection_t *c);
++void ngx_http_v3_reset_connection(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_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 -r 67408b4a12c0 src/http/v3/ngx_http_v3_encode.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/http/v3/ngx_http_v3_encode.c Tue Jan 04 18:14:15 2022 -0500
+@@ -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 -r 67408b4a12c0 src/http/v3/ngx_http_v3_encode.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/http/v3/ngx_http_v3_encode.h Tue Jan 04 18:14:15 2022 -0500
+@@ -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 -r 67408b4a12c0 src/http/v3/ngx_http_v3_filter_module.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/http/v3/ngx_http_v3_filter_module.c Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,1538 @@
++
++/*
++ * 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_push_resources(ngx_http_request_t *r,
++ ngx_chain_t ***out);
++static ngx_int_t ngx_http_v3_push_resource(ngx_http_request_t *r,
++ ngx_str_t *path, ngx_chain_t ***out);
++static ngx_int_t ngx_http_v3_create_push_request(
++ ngx_http_request_t *pr, ngx_str_t *path, uint64_t push_id);
++static ngx_int_t ngx_http_v3_set_push_header(ngx_http_request_t *r,
++ const char *name, ngx_str_t *value);
++static void ngx_http_v3_push_request_handler(ngx_event_t *ev);
++static ngx_chain_t *ngx_http_v3_create_push_promise(ngx_http_request_t *r,
++ ngx_str_t *path, uint64_t push_id);
++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;
++
++ if ((c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0
++ && r->method != NGX_HTTP_HEAD)
++ {
++ if (ngx_http_v3_push_resources(r, &ll) != NGX_OK) {
++ return NGX_ERROR;
++ }
++ }
++
++ 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_push_resources(ngx_http_request_t *r, ngx_chain_t ***out)
++{
++ u_char *start, *end, *last;
++ ngx_str_t path;
++ ngx_int_t rc;
++ ngx_uint_t i, push;
++ ngx_table_elt_t **h;
++ ngx_http_v3_loc_conf_t *h3lcf;
++ ngx_http_complex_value_t *pushes;
++
++ h3lcf = ngx_http_get_module_loc_conf(r, ngx_http_v3_module);
++
++ if (h3lcf->pushes) {
++ pushes = h3lcf->pushes->elts;
++
++ for (i = 0; i < h3lcf->pushes->nelts; i++) {
++
++ if (ngx_http_complex_value(r, &pushes[i], &path) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ if (path.len == 0) {
++ continue;
++ }
++
++ if (path.len == 3 && ngx_strncmp(path.data, "off", 3) == 0) {
++ continue;
++ }
++
++ rc = ngx_http_v3_push_resource(r, &path, out);
++
++ if (rc == NGX_ERROR) {
++ return NGX_ERROR;
++ }
++
++ if (rc == NGX_ABORT) {
++ return NGX_OK;
++ }
++
++ /* NGX_OK, NGX_DECLINED */
++ }
++ }
++
++ if (!h3lcf->push_preload) {
++ return NGX_OK;
++ }
++
++ h = r->headers_out.link.elts;
++
++ for (i = 0; i < r->headers_out.link.nelts; i++) {
++
++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
++ "http3 parse link: \"%V\"", &h[i]->value);
++
++ start = h[i]->value.data;
++ end = h[i]->value.data + h[i]->value.len;
++
++ next_link:
++
++ while (start < end && *start == ' ') { start++; }
++
++ if (start == end || *start++ != '<') {
++ continue;
++ }
++
++ while (start < end && *start == ' ') { start++; }
++
++ for (last = start; last < end && *last != '>'; last++) {
++ /* void */
++ }
++
++ if (last == start || last == end) {
++ continue;
++ }
++
++ path.len = last - start;
++ path.data = start;
++
++ start = last + 1;
++
++ while (start < end && *start == ' ') { start++; }
++
++ if (start == end) {
++ continue;
++ }
++
++ if (*start == ',') {
++ start++;
++ goto next_link;
++ }
++
++ if (*start++ != ';') {
++ continue;
++ }
++
++ last = ngx_strlchr(start, end, ',');
++
++ if (last == NULL) {
++ last = end;
++ }
++
++ push = 0;
++
++ for ( ;; ) {
++
++ while (start < last && *start == ' ') { start++; }
++
++ if (last - start >= 6
++ && ngx_strncasecmp(start, (u_char *) "nopush", 6) == 0)
++ {
++ start += 6;
++
++ if (start == last || *start == ' ' || *start == ';') {
++ push = 0;
++ break;
++ }
++
++ goto next_param;
++ }
++
++ if (last - start >= 11
++ && ngx_strncasecmp(start, (u_char *) "rel=preload", 11) == 0)
++ {
++ start += 11;
++
++ if (start == last || *start == ' ' || *start == ';') {
++ push = 1;
++ }
++
++ goto next_param;
++ }
++
++ if (last - start >= 4
++ && ngx_strncasecmp(start, (u_char *) "rel=", 4) == 0)
++ {
++ start += 4;
++
++ while (start < last && *start == ' ') { start++; }
++
++ if (start == last || *start++ != '"') {
++ goto next_param;
++ }
++
++ for ( ;; ) {
++
++ while (start < last && *start == ' ') { start++; }
++
++ if (last - start >= 7
++ && ngx_strncasecmp(start, (u_char *) "preload", 7) == 0)
++ {
++ start += 7;
++
++ if (start < last && (*start == ' ' || *start == '"')) {
++ push = 1;
++ break;
++ }
++ }
++
++ while (start < last && *start != ' ' && *start != '"') {
++ start++;
++ }
++
++ if (start == last) {
++ break;
++ }
++
++ if (*start == '"') {
++ break;
++ }
++
++ start++;
++ }
++ }
++
++ next_param:
++
++ start = ngx_strlchr(start, last, ';');
++
++ if (start == NULL) {
++ break;
++ }
++
++ start++;
++ }
++
++ if (push) {
++ while (path.len && path.data[path.len - 1] == ' ') {
++ path.len--;
++ }
++ }
++
++ if (push && path.len
++ && !(path.len > 1 && path.data[0] == '/' && path.data[1] == '/'))
++ {
++ rc = ngx_http_v3_push_resource(r, &path, out);
++
++ if (rc == NGX_ERROR) {
++ return NGX_ERROR;
++ }
++
++ if (rc == NGX_ABORT) {
++ return NGX_OK;
++ }
++
++ /* NGX_OK, NGX_DECLINED */
++ }
++
++ if (last < end) {
++ start = last + 1;
++ goto next_link;
++ }
++ }
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_http_v3_push_resource(ngx_http_request_t *r, ngx_str_t *path,
++ ngx_chain_t ***ll)
++{
++ uint64_t push_id;
++ ngx_int_t rc;
++ ngx_chain_t *cl;
++ ngx_connection_t *c;
++ ngx_http_v3_session_t *h3c;
++ ngx_http_v3_srv_conf_t *h3scf;
++
++ c = r->connection;
++ h3c = ngx_http_v3_get_session(c);
++ h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module);
++
++ ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0,
++ "http3 push \"%V\" pushing:%ui/%ui id:%uL/%L",
++ path, h3c->npushing, h3scf->max_concurrent_pushes,
++ h3c->next_push_id, h3c->max_push_id);
++
++ if (!ngx_path_separator(path->data[0])) {
++ ngx_log_error(NGX_LOG_WARN, c->log, 0,
++ "non-absolute path \"%V\" not pushed", path);
++ return NGX_DECLINED;
++ }
++
++ if (h3c->max_push_id == (uint64_t) -1
++ || h3c->next_push_id > h3c->max_push_id)
++ {
++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
++ "http3 abort pushes due to max_push_id");
++ return NGX_ABORT;
++ }
++
++ if (h3c->goaway_push_id != (uint64_t) -1) {
++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
++ "http3 abort pushes due to goaway");
++ return NGX_ABORT;
++ }
++
++ if (h3c->npushing >= h3scf->max_concurrent_pushes) {
++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
++ "http3 abort pushes due to max_concurrent_pushes");
++ return NGX_ABORT;
++ }
++
++ if (r->headers_in.server.len == 0) {
++ return NGX_ABORT;
++ }
++
++ push_id = h3c->next_push_id++;
++
++ rc = ngx_http_v3_create_push_request(r, path, push_id);
++ if (rc != NGX_OK) {
++ return rc;
++ }
++
++ cl = ngx_http_v3_create_push_promise(r, path, push_id);
++ if (cl == NULL) {
++ return NGX_ERROR;
++ }
++
++ for (**ll = cl; **ll; *ll = &(**ll)->next);
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_http_v3_create_push_request(ngx_http_request_t *pr, ngx_str_t *path,
++ uint64_t push_id)
++{
++ ngx_connection_t *c, *pc;
++ ngx_http_request_t *r;
++ ngx_http_log_ctx_t *ctx;
++ ngx_http_connection_t *hc, *phc;
++ ngx_http_core_srv_conf_t *cscf;
++
++ pc = pr->connection;
++
++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
++ "http3 create push request id:%uL", push_id);
++
++ c = ngx_http_v3_create_push_stream(pc, push_id);
++ if (c == NULL) {
++ return NGX_ABORT;
++ }
++
++#if (NGX_STAT_STUB)
++ (void) ngx_atomic_fetch_add(ngx_stat_active, 1);
++#endif
++
++ hc = ngx_palloc(c->pool, sizeof(ngx_http_connection_t));
++ if (hc == NULL) {
++ ngx_http_close_connection(c);
++ return NGX_ERROR;
++ }
++
++ phc = ngx_http_quic_get_connection(pc);
++ ngx_memcpy(hc, phc, sizeof(ngx_http_connection_t));
++ c->data = hc;
++
++ ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t));
++ if (ctx == NULL) {
++ ngx_http_close_connection(c);
++ return NGX_ERROR;
++ }
++
++ ctx->connection = c;
++ ctx->request = NULL;
++ ctx->current_request = NULL;
++
++ c->log->handler = pc->log->handler;
++ c->log->data = ctx;
++ c->log->action = "processing pushed request headers";
++
++ c->log_error = NGX_ERROR_INFO;
++
++ r = ngx_http_create_request(c);
++ if (r == NULL) {
++ ngx_http_close_connection(c);
++ return NGX_ERROR;
++ }
++
++ c->data = r;
++
++ ngx_str_set(&r->http_protocol, "HTTP/3.0");
++
++ r->http_version = NGX_HTTP_VERSION_30;
++ r->method_name = ngx_http_core_get_method;
++ r->method = NGX_HTTP_GET;
++
++ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
++
++ r->header_in = ngx_create_temp_buf(r->pool,
++ cscf->client_header_buffer_size);
++ if (r->header_in == NULL) {
++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
++ return NGX_ERROR;
++ }
++
++ if (ngx_list_init(&r->headers_in.headers, r->pool, 4,
++ sizeof(ngx_table_elt_t))
++ != NGX_OK)
++ {
++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
++ return NGX_ERROR;
++ }
++
++ r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE;
++
++ r->schema.data = ngx_pstrdup(r->pool, &pr->schema);
++ if (r->schema.data == NULL) {
++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
++ return NGX_ERROR;
++ }
++
++ r->schema.len = pr->schema.len;
++
++ r->uri_start = ngx_pstrdup(r->pool, path);
++ if (r->uri_start == NULL) {
++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
++ return NGX_ERROR;
++ }
++
++ r->uri_end = r->uri_start + path->len;
++
++ if (ngx_http_parse_uri(r) != NGX_OK) {
++ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
++ return NGX_ERROR;
++ }
++
++ if (ngx_http_process_request_uri(r) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ if (ngx_http_v3_set_push_header(r, "host", &pr->headers_in.server)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++
++ if (pr->headers_in.accept_encoding) {
++ if (ngx_http_v3_set_push_header(r, "accept-encoding",
++ &pr->headers_in.accept_encoding->value)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++ }
++
++ if (pr->headers_in.accept_language) {
++ if (ngx_http_v3_set_push_header(r, "accept-language",
++ &pr->headers_in.accept_language->value)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++ }
++
++ if (pr->headers_in.user_agent) {
++ if (ngx_http_v3_set_push_header(r, "user-agent",
++ &pr->headers_in.user_agent->value)
++ != NGX_OK)
++ {
++ return NGX_ERROR;
++ }
++ }
++
++ c->read->handler = ngx_http_v3_push_request_handler;
++ c->read->handler = ngx_http_v3_push_request_handler;
++
++ ngx_post_event(c->read, &ngx_posted_events);
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_http_v3_set_push_header(ngx_http_request_t *r, const char *name,
++ ngx_str_t *value)
++{
++ u_char *p;
++ ngx_table_elt_t *h;
++ ngx_http_header_t *hh;
++ ngx_http_core_main_conf_t *cmcf;
++
++ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
++ "http3 push header \"%s\": \"%V\"", name, value);
++
++ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
++
++ p = ngx_pnalloc(r->pool, value->len + 1);
++ if (p == NULL) {
++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
++ return NGX_ERROR;
++ }
++
++ ngx_memcpy(p, value->data, value->len);
++ p[value->len] = '\0';
++
++ h = ngx_list_push(&r->headers_in.headers);
++ if (h == NULL) {
++ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
++ return NGX_ERROR;
++ }
++
++ h->key.data = (u_char *) name;
++ h->key.len = ngx_strlen(name);
++ h->hash = ngx_hash_key(h->key.data, h->key.len);
++ h->lowcase_key = (u_char *) name;
++ h->value.data = p;
++ h->value.len = value->len;
++
++ hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash,
++ h->lowcase_key, h->key.len);
++
++ if (hh && hh->handler(r, h, hh->offset) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ return NGX_OK;
++}
++
++
++static void
++ngx_http_v3_push_request_handler(ngx_event_t *ev)
++{
++ ngx_connection_t *c;
++ ngx_http_request_t *r;
++
++ c = ev->data;
++ r = c->data;
++
++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 push request handler");
++
++ ngx_http_process_request(r);
++}
++
++
++static ngx_chain_t *
++ngx_http_v3_create_push_promise(ngx_http_request_t *r, ngx_str_t *path,
++ uint64_t push_id)
++{
++ size_t n, len;
++ ngx_buf_t *b;
++ ngx_chain_t *hl, *cl;
++ ngx_http_v3_session_t *h3c;
++
++ h3c = ngx_http_v3_get_session(r->connection);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
++ "http3 create push promise id:%uL", push_id);
++
++ len = ngx_http_v3_encode_varlen_int(NULL, push_id);
++
++ len += ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0);
++
++ len += ngx_http_v3_encode_field_ri(NULL, 0,
++ NGX_HTTP_V3_HEADER_METHOD_GET);
++
++ len += ngx_http_v3_encode_field_lri(NULL, 0,
++ NGX_HTTP_V3_HEADER_AUTHORITY,
++ NULL, r->headers_in.server.len);
++
++ if (path->len == 1 && path->data[0] == '/') {
++ len += ngx_http_v3_encode_field_ri(NULL, 0,
++ NGX_HTTP_V3_HEADER_PATH_ROOT);
++
++ } else {
++ len += ngx_http_v3_encode_field_lri(NULL, 0,
++ NGX_HTTP_V3_HEADER_PATH_ROOT,
++ NULL, path->len);
++ }
++
++ if (r->schema.len == 5 && ngx_strncmp(r->schema.data, "https", 5) == 0) {
++ len += ngx_http_v3_encode_field_ri(NULL, 0,
++ NGX_HTTP_V3_HEADER_SCHEME_HTTPS);
++
++ } else if (r->schema.len == 4
++ && ngx_strncmp(r->schema.data, "http", 4) == 0)
++ {
++ len += ngx_http_v3_encode_field_ri(NULL, 0,
++ NGX_HTTP_V3_HEADER_SCHEME_HTTP);
++
++ } else {
++ len += ngx_http_v3_encode_field_lri(NULL, 0,
++ NGX_HTTP_V3_HEADER_SCHEME_HTTP,
++ NULL, r->schema.len);
++ }
++
++ if (r->headers_in.accept_encoding) {
++ len += ngx_http_v3_encode_field_lri(NULL, 0,
++ NGX_HTTP_V3_HEADER_ACCEPT_ENCODING, NULL,
++ r->headers_in.accept_encoding->value.len);
++ }
++
++ if (r->headers_in.accept_language) {
++ len += ngx_http_v3_encode_field_lri(NULL, 0,
++ NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE, NULL,
++ r->headers_in.accept_language->value.len);
++ }
++
++ if (r->headers_in.user_agent) {
++ len += ngx_http_v3_encode_field_lri(NULL, 0,
++ NGX_HTTP_V3_HEADER_USER_AGENT, NULL,
++ r->headers_in.user_agent->value.len);
++ }
++
++ b = ngx_create_temp_buf(r->pool, len);
++ if (b == NULL) {
++ return NULL;
++ }
++
++ b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, push_id);
++
++ b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->last,
++ 0, 0, 0);
++
++ b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0,
++ NGX_HTTP_V3_HEADER_METHOD_GET);
++
++ b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
++ NGX_HTTP_V3_HEADER_AUTHORITY,
++ r->headers_in.server.data,
++ r->headers_in.server.len);
++
++ if (path->len == 1 && path->data[0] == '/') {
++ b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0,
++ NGX_HTTP_V3_HEADER_PATH_ROOT);
++
++ } else {
++ b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
++ NGX_HTTP_V3_HEADER_PATH_ROOT,
++ path->data, path->len);
++ }
++
++ if (r->schema.len == 5 && ngx_strncmp(r->schema.data, "https", 5) == 0) {
++ b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0,
++ NGX_HTTP_V3_HEADER_SCHEME_HTTPS);
++
++ } else if (r->schema.len == 4
++ && ngx_strncmp(r->schema.data, "http", 4) == 0)
++ {
++ b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0,
++ NGX_HTTP_V3_HEADER_SCHEME_HTTP);
++
++ } else {
++ b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
++ NGX_HTTP_V3_HEADER_SCHEME_HTTP,
++ r->schema.data, r->schema.len);
++ }
++
++ if (r->headers_in.accept_encoding) {
++ b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
++ NGX_HTTP_V3_HEADER_ACCEPT_ENCODING,
++ r->headers_in.accept_encoding->value.data,
++ r->headers_in.accept_encoding->value.len);
++ }
++
++ if (r->headers_in.accept_language) {
++ b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
++ NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE,
++ r->headers_in.accept_language->value.data,
++ r->headers_in.accept_language->value.len);
++ }
++
++ if (r->headers_in.user_agent) {
++ b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
++ NGX_HTTP_V3_HEADER_USER_AGENT,
++ r->headers_in.user_agent->value.data,
++ r->headers_in.user_agent->value.len);
++ }
++
++ cl = ngx_alloc_chain_link(r->pool);
++ if (cl == NULL) {
++ return NULL;
++ }
++
++ cl->buf = b;
++ cl->next = NULL;
++
++ n = b->last - b->pos;
++
++ h3c->payload_bytes += n;
++
++ len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_PUSH_PROMISE)
++ + ngx_http_v3_encode_varlen_int(NULL, n);
++
++ b = ngx_create_temp_buf(r->pool, len);
++ if (b == NULL) {
++ return NULL;
++ }
++
++ b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last,
++ NGX_HTTP_V3_FRAME_PUSH_PROMISE);
++ b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n);
++
++ hl = ngx_alloc_chain_link(r->pool);
++ if (hl == NULL) {
++ return NULL;
++ }
++
++ hl->buf = b;
++ hl->next = cl;
++
++ return hl;
++}
++
++
++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 -r 67408b4a12c0 src/http/v3/ngx_http_v3_module.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/http/v3/ngx_http_v3_module.c Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,539 @@
++
++/*
++ * 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_mtu(ngx_conf_t *cf, void *post,
++ void *data);
++static char *ngx_http_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd,
++ void *conf);
++static void *ngx_http_v3_create_loc_conf(ngx_conf_t *cf);
++static char *ngx_http_v3_merge_loc_conf(ngx_conf_t *cf, void *parent,
++ void *child);
++static char *ngx_http_v3_push(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
++
++
++static ngx_conf_post_t ngx_http_quic_mtu_post =
++ { ngx_http_quic_mtu };
++
++
++static ngx_command_t ngx_http_v3_commands[] = {
++
++ { ngx_string("http3_max_concurrent_pushes"),
++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
++ ngx_conf_set_num_slot,
++ NGX_HTTP_SRV_CONF_OFFSET,
++ offsetof(ngx_http_v3_srv_conf_t, max_concurrent_pushes),
++ NULL },
++
++ { ngx_string("http3_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 },
++
++#if (NGX_HTTP_V3_HQ)
++ { 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, hq),
++ NULL },
++#endif
++
++ { ngx_string("http3_push"),
++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
++ ngx_http_v3_push,
++ NGX_HTTP_LOC_CONF_OFFSET,
++ 0,
++ NULL },
++
++ { ngx_string("http3_push_preload"),
++ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
++ ngx_conf_set_flag_slot,
++ NGX_HTTP_LOC_CONF_OFFSET,
++ offsetof(ngx_http_v3_loc_conf_t, push_preload),
++ NULL },
++
++ { ngx_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_mtu"),
++ 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.mtu),
++ &ngx_http_quic_mtu_post },
++
++ { 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_null_command
++};
++
++
++static ngx_http_module_t ngx_http_v3_module_ctx = {
++ ngx_http_v3_add_variables, /* preconfiguration */
++ NULL, /* postconfiguration */
++
++ NULL, /* create main configuration */
++ NULL, /* init main configuration */
++
++ ngx_http_v3_create_srv_conf, /* create server configuration */
++ ngx_http_v3_merge_srv_conf, /* merge server configuration */
++
++ ngx_http_v3_create_loc_conf, /* create location configuration */
++ ngx_http_v3_merge_loc_conf /* merge location configuration */
++};
++
++
++ngx_module_t ngx_http_v3_module = {
++ NGX_MODULE_V1,
++ &ngx_http_v3_module_ctx, /* module context */
++ ngx_http_v3_commands, /* module directives */
++ NGX_HTTP_MODULE, /* module type */
++ NULL, /* init master */
++ NULL, /* init module */
++ NULL, /* init process */
++ NULL, /* init thread */
++ NULL, /* exit thread */
++ NULL, /* exit process */
++ NULL, /* exit master */
++ NGX_MODULE_V1_PADDING
++};
++
++
++static ngx_http_variable_t ngx_http_v3_vars[] = {
++
++ { ngx_string("http3"), NULL, ngx_http_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)
++{
++ if (r->connection->quic) {
++#if (NGX_HTTP_V3_HQ)
++
++ ngx_http_v3_srv_conf_t *h3scf;
++
++ h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module);
++
++ if (h3scf->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;
++ }
++
++#endif
++
++ 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->max_table_capacity = NGX_HTTP_V3_MAX_TABLE_CAPACITY;
++ h3scf->max_concurrent_pushes = NGX_CONF_UNSET_UINT;
++ h3scf->max_concurrent_streams = NGX_CONF_UNSET_UINT;
++#if (NGX_HTTP_V3_HQ)
++ h3scf->hq = NGX_CONF_UNSET;
++#endif
++
++ h3scf->quic.mtu = NGX_CONF_UNSET_SIZE;
++ 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;
++
++ 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_uint_value(conf->max_concurrent_pushes,
++ prev->max_concurrent_pushes, 10);
++
++ ngx_conf_merge_uint_value(conf->max_concurrent_streams,
++ prev->max_concurrent_streams, 128);
++
++ conf->max_blocked_streams = conf->max_concurrent_streams;
++
++#if (NGX_HTTP_V3_HQ)
++ ngx_conf_merge_value(conf->hq, prev->hq, 0);
++#endif
++
++
++ ngx_conf_merge_size_value(conf->quic.mtu, prev->quic.mtu,
++ NGX_QUIC_MAX_UDP_PAYLOAD_SIZE);
++
++ 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, "");
++
++ 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_mtu(ngx_conf_t *cf, void *post, void *data)
++{
++ size_t *sp = data;
++
++ if (*sp < NGX_QUIC_MIN_INITIAL_SIZE
++ || *sp > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE)
++ {
++ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
++ "\"quic_mtu\" must be between %d and %d",
++ NGX_QUIC_MIN_INITIAL_SIZE,
++ NGX_QUIC_MAX_UDP_PAYLOAD_SIZE);
++
++ return NGX_CONF_ERROR;
++ }
++
++ return NGX_CONF_OK;
++}
++
++
++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;
++}
++
++
++static void *
++ngx_http_v3_create_loc_conf(ngx_conf_t *cf)
++{
++ ngx_http_v3_loc_conf_t *h3lcf;
++
++ h3lcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v3_loc_conf_t));
++ if (h3lcf == NULL) {
++ return NULL;
++ }
++
++ /*
++ * set by ngx_pcalloc():
++ *
++ * h3lcf->pushes = NULL;
++ */
++
++ h3lcf->push_preload = NGX_CONF_UNSET;
++ h3lcf->push = NGX_CONF_UNSET;
++
++ return h3lcf;
++}
++
++
++static char *
++ngx_http_v3_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
++{
++ ngx_http_v3_loc_conf_t *prev = parent;
++ ngx_http_v3_loc_conf_t *conf = child;
++
++ ngx_conf_merge_value(conf->push, prev->push, 1);
++
++ if (conf->push && conf->pushes == NULL) {
++ conf->pushes = prev->pushes;
++ }
++
++ ngx_conf_merge_value(conf->push_preload, prev->push_preload, 0);
++
++ return NGX_CONF_OK;
++}
++
++
++static char *
++ngx_http_v3_push(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
++{
++ ngx_http_v3_loc_conf_t *h3lcf = conf;
++
++ ngx_str_t *value;
++ ngx_http_complex_value_t *cv;
++ ngx_http_compile_complex_value_t ccv;
++
++ value = cf->args->elts;
++
++ if (ngx_strcmp(value[1].data, "off") == 0) {
++
++ if (h3lcf->pushes) {
++ return "\"off\" parameter cannot be used with URI";
++ }
++
++ if (h3lcf->push == 0) {
++ return "is duplicate";
++ }
++
++ h3lcf->push = 0;
++ return NGX_CONF_OK;
++ }
++
++ if (h3lcf->push == 0) {
++ return "URI cannot be used with \"off\" parameter";
++ }
++
++ h3lcf->push = 1;
++
++ if (h3lcf->pushes == NULL) {
++ h3lcf->pushes = ngx_array_create(cf->pool, 1,
++ sizeof(ngx_http_complex_value_t));
++ if (h3lcf->pushes == NULL) {
++ return NGX_CONF_ERROR;
++ }
++ }
++
++ cv = ngx_array_push(h3lcf->pushes);
++ if (cv == NULL) {
++ return NGX_CONF_ERROR;
++ }
++
++ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
++
++ ccv.cf = cf;
++ ccv.value = &value[1];
++ ccv.complex_value = cv;
++
++ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
++ return NGX_CONF_ERROR;
++ }
++
++ return NGX_CONF_OK;
++}
+diff -r 67408b4a12c0 src/http/v3/ngx_http_v3_parse.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/http/v3/ngx_http_v3_parse.c Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,2005 @@
++
++/*
++ * 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;
++ }
++ }
++
++ 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) {
++ 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_cancel_push,
++ sw_settings,
++ sw_max_push_id,
++ sw_goaway,
++ 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;
++ }
++
++ 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_CANCEL_PUSH:
++ st->state = sw_cancel_push;
++ break;
++
++ case NGX_HTTP_V3_FRAME_SETTINGS:
++ st->state = sw_settings;
++ break;
++
++ case NGX_HTTP_V3_FRAME_MAX_PUSH_ID:
++ st->state = sw_max_push_id;
++ break;
++
++ case NGX_HTTP_V3_FRAME_GOAWAY:
++ st->state = sw_goaway;
++ break;
++
++ default:
++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
++ "http3 parse skip unknown frame");
++ st->state = sw_skip;
++ }
++
++ break;
++
++ case sw_cancel_push:
++
++ ngx_http_v3_parse_start_local(b, &loc, st->length);
++
++ rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, &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;
++ }
++
++ rc = ngx_http_v3_cancel_push(c, st->vlint.value);
++ if (rc != NGX_OK) {
++ return rc;
++ }
++
++ st->state = sw_type;
++ 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_max_push_id:
++
++ ngx_http_v3_parse_start_local(b, &loc, st->length);
++
++ rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, &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;
++ }
++
++ rc = ngx_http_v3_set_max_push_id(c, st->vlint.value);
++ if (rc != NGX_OK) {
++ return rc;
++ }
++
++ st->state = sw_type;
++ break;
++
++ case sw_goaway:
++
++ ngx_http_v3_parse_start_local(b, &loc, st->length);
++
++ rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, &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;
++ }
++
++ rc = ngx_http_v3_goaway(c, st->vlint.value);
++ if (rc != NGX_OK) {
++ return rc;
++ }
++
++ 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 -r 67408b4a12c0 src/http/v3/ngx_http_v3_parse.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/http/v3/ngx_http_v3_parse.h Tue Jan 04 18:14:15 2022 -0500
+@@ -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 -r 67408b4a12c0 src/http/v3/ngx_http_v3_request.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/http/v3/ngx_http_v3_request.c Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,1687 @@
++
++/*
++ * Copyright (C) Roman Arutyunyan
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++#include <ngx_http.h>
++
++
++#if (NGX_HTTP_V3_HQ)
++static void ngx_http_v3_init_hq_stream(ngx_connection_t *c);
++#endif
++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_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(ngx_connection_t *c)
++{
++ ngx_http_connection_t *hc, *phc;
++ ngx_http_v3_srv_conf_t *h3scf;
++ ngx_http_core_loc_conf_t *clcf;
++
++ hc = c->data;
++
++ hc->ssl = 1;
++
++ clcf = ngx_http_get_module_loc_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) {
++ 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;
++ hc->conf_ctx = phc->conf_ctx;
++
++ ngx_set_connection_log(c, clcf->error_log);
++ }
++
++#if (NGX_HTTP_V3_HQ)
++ if (h3scf->hq) {
++ ngx_http_v3_init_hq_stream(c);
++ return;
++ }
++#endif
++
++ if (ngx_http_v3_init_session(c) != NGX_OK) {
++ ngx_http_close_connection(c);
++ return;
++ }
++
++ if (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) {
++ ngx_http_v3_init_uni_stream(c);
++
++ } else {
++ ngx_http_v3_init_request_stream(c);
++ }
++}
++
++
++#if (NGX_HTTP_V3_HQ)
++
++static void
++ngx_http_v3_init_hq_stream(ngx_connection_t *c)
++{
++ uint64_t n;
++ ngx_event_t *rev;
++ ngx_http_connection_t *hc;
++ 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 hq stream");
++
++#if (NGX_STAT_STUB)
++ (void) ngx_atomic_fetch_add(ngx_stat_active, 1);
++#endif
++
++ hc = c->data;
++
++ /* Use HTTP/3 General Protocol Error Code 0x101 for finalization */
++
++ if (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) {
++ ngx_quic_finalize_connection(c->quic->parent,
++ NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR,
++ "unexpected uni stream");
++ ngx_http_close_connection(c);
++ return;
++ }
++
++ clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module);
++
++ n = c->quic->id >> 2;
++
++ if (n >= clcf->keepalive_requests) {
++ ngx_quic_finalize_connection(c->quic->parent,
++ NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR,
++ "reached maximum number of requests");
++ ngx_http_close_connection(c);
++ return;
++ }
++
++ if (ngx_current_msec - c->quic->parent->start_time
++ > clcf->keepalive_time)
++ {
++ ngx_quic_finalize_connection(c->quic->parent,
++ NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR,
++ "reached maximum time for requests");
++ ngx_http_close_connection(c);
++ return;
++ }
++
++ rev = c->read;
++
++ 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;
++ }
++}
++
++#endif
++
++
++static void
++ngx_http_v3_init_request_stream(ngx_connection_t *c)
++{
++ uint64_t n;
++ ngx_event_t *rev;
++ 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;
++ }
++
++ if (n + 1 == clcf->keepalive_requests
++ || ngx_current_msec - c->quic->parent->start_time
++ > clcf->keepalive_time)
++ {
++ h3c->goaway = 1;
++
++ if (ngx_http_v3_send_goaway(c, (n + 1) << 2) != 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");
++ }
++
++ rev = c->read;
++ 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_v3_session_t *h3c;
++ 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;
++
++ h3c = ngx_http_v3_get_session(c);
++ h3c->nrequests++;
++
++ if (h3c->keepalive.timer_set) {
++ ngx_del_timer(&h3c->keepalive);
++ }
++
++ rev->handler = ngx_http_v3_process_request;
++ ngx_http_v3_process_request(rev);
++}
++
++
++void
++ngx_http_v3_reset_connection(ngx_connection_t *c)
++{
++ ngx_http_v3_srv_conf_t *h3scf;
++
++ h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module);
++
++#if (NGX_HTTP_V3_HQ)
++ if (h3scf->hq) {
++ return;
++ }
++#endif
++
++ if (h3scf->max_table_capacity > 0 && !c->read->eof
++ && (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_request(void *data)
++{
++ ngx_http_request_t *r = data;
++
++ ngx_connection_t *c;
++ ngx_http_v3_session_t *h3c;
++ ngx_http_core_loc_conf_t *clcf;
++
++ c = r->connection;
++
++ if (!r->response_sent) {
++ c->error = 1;
++ }
++
++ 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_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_close_request(r, NGX_HTTP_CLOSE);
++ break;
++ }
++
++ 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;
++
++ c = r->connection;
++
++ if (ngx_http_v3_init_pseudo_headers(r) != NGX_OK) {
++ 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;
++ }
++
++ 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 -r 67408b4a12c0 src/http/v3/ngx_http_v3_table.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/http/v3/ngx_http_v3_table.c Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,678 @@
++
++/*
++ * Copyright (C) Roman Arutyunyan
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++#include <ngx_http.h>
++
++
++#define ngx_http_v3_table_entry_size(n, v) ((n)->len + (v)->len + 32)
++
++
++static ngx_int_t ngx_http_v3_evict(ngx_connection_t *c, size_t need);
++static void ngx_http_v3_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);
++
++ if (ngx_http_v3_evict(c, size) != NGX_OK) {
++ return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
++ }
++
++ h3c = ngx_http_v3_get_session(c);
++ dt = &h3c->table;
++
++ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0,
++ "http3 insert [%ui] \"%V\":\"%V\", size:%uz",
++ dt->base + dt->nelts, name, value, size);
++
++ p = ngx_alloc(sizeof(ngx_http_v3_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;
++
++ /* TODO increment can be sent less often */
++
++ if (ngx_http_v3_send_inc_insert_count(c, 1) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ if (ngx_http_v3_new_entry(c) != NGX_OK) {
++ return NGX_ERROR;
++ }
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity)
++{
++ ngx_uint_t max, prev_max;
++ ngx_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;
++ }
++
++ dt = &h3c->table;
++
++ if (dt->size > capacity) {
++ if (ngx_http_v3_evict(c, dt->size - capacity) != NGX_OK) {
++ return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
++ }
++ }
++
++ max = capacity / 32;
++ prev_max = dt->capacity / 32;
++
++ if (max > prev_max) {
++ elts = ngx_alloc(max * sizeof(void *), c->log);
++ if (elts == NULL) {
++ return NGX_ERROR;
++ }
++
++ if (dt->elts) {
++ 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 need)
++{
++ size_t size, target;
++ 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;
++
++ if (need > dt->capacity) {
++ ngx_log_error(NGX_LOG_ERR, c->log, 0,
++ "not enough dynamic table capacity");
++ return NGX_ERROR;
++ }
++
++ target = dt->capacity - need;
++ n = 0;
++
++ while (dt->size > target) {
++ 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;
++}
++
++
++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_HEADER_LIST_SIZE:
++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
++ "http3 param SETTINGS_MAX_HEADER_LIST_SIZE:%uL", value);
++ break;
++
++ case NGX_HTTP_V3_PARAM_BLOCKED_STREAMS:
++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
++ "http3 param QPACK_BLOCKED_STREAMS:%uL", value);
++ break;
++
++ default:
++
++ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
++ "http3 param #%uL:%uL", id, value);
++ }
++
++ return NGX_OK;
++}
+diff -r 67408b4a12c0 src/http/v3/ngx_http_v3_table.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/http/v3/ngx_http_v3_table.h Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,53 @@
++
++/*
++ * 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;
++} ngx_http_v3_dynamic_table_t;
++
++
++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);
++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 -r 67408b4a12c0 src/http/v3/ngx_http_v3_uni.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/http/v3/ngx_http_v3_uni.c Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,733 @@
++
++/*
++ * 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;
++
++
++typedef struct {
++ ngx_queue_t queue;
++ uint64_t id;
++ ngx_connection_t *connection;
++ ngx_uint_t *npushing;
++} ngx_http_v3_push_t;
++
++
++static void ngx_http_v3_close_uni_stream(ngx_connection_t *c);
++static void ngx_http_v3_uni_read_handler(ngx_event_t *rev);
++static void ngx_http_v3_dummy_write_handler(ngx_event_t *wev);
++static void ngx_http_v3_push_cleanup(void *data);
++static ngx_connection_t *ngx_http_v3_get_uni_stream(ngx_connection_t *c,
++ ngx_uint_t type);
++
++
++void
++ngx_http_v3_init_uni_stream(ngx_connection_t *c)
++{
++ uint64_t n;
++ ngx_http_v3_uni_stream_t *us;
++
++ 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;
++ }
++
++ c->quic->cancelable = 1;
++
++ 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_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");
++
++ 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_dummy_write_handler(ngx_event_t *wev)
++{
++ ngx_connection_t *c;
++
++ c = wev->data;
++
++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 dummy write handler");
++
++ if (ngx_handle_write_event(wev, 0) != NGX_OK) {
++ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR,
++ NULL);
++ ngx_http_v3_close_uni_stream(c);
++ }
++}
++
++
++/* XXX async & buffered stream writes */
++
++ngx_connection_t *
++ngx_http_v3_create_push_stream(ngx_connection_t *c, uint64_t push_id)
++{
++ u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 2];
++ size_t n;
++ ngx_connection_t *sc;
++ ngx_pool_cleanup_t *cln;
++ ngx_http_v3_push_t *push;
++ ngx_http_v3_session_t *h3c;
++
++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
++ "http3 create push stream id:%uL", push_id);
++
++ sc = ngx_quic_open_stream(c, 0);
++ if (sc == NULL) {
++ goto failed;
++ }
++
++ p = buf;
++ p = (u_char *) ngx_http_v3_encode_varlen_int(p, NGX_HTTP_V3_STREAM_PUSH);
++ p = (u_char *) ngx_http_v3_encode_varlen_int(p, push_id);
++ n = p - buf;
++
++ h3c = ngx_http_v3_get_session(c);
++ h3c->total_bytes += n;
++
++ if (sc->send(sc, buf, n) != (ssize_t) n) {
++ goto failed;
++ }
++
++ cln = ngx_pool_cleanup_add(sc->pool, sizeof(ngx_http_v3_push_t));
++ if (cln == NULL) {
++ goto failed;
++ }
++
++ h3c->npushing++;
++
++ cln->handler = ngx_http_v3_push_cleanup;
++
++ push = cln->data;
++ push->id = push_id;
++ push->connection = sc;
++ push->npushing = &h3c->npushing;
++
++ ngx_queue_insert_tail(&h3c->pushing, &push->queue);
++
++ return sc;
++
++failed:
++
++ ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create push stream");
++
++ ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR,
++ "failed to create push stream");
++ if (sc) {
++ ngx_http_v3_close_uni_stream(sc);
++ }
++
++ return NULL;
++}
++
++
++static void
++ngx_http_v3_push_cleanup(void *data)
++{
++ ngx_http_v3_push_t *push = data;
++
++ ngx_queue_remove(&push->queue);
++ (*push->npushing)--;
++}
++
++
++static ngx_connection_t *
++ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type)
++{
++ u_char buf[NGX_HTTP_V3_VARLEN_INT_LEN];
++ size_t n;
++ ngx_int_t index;
++ ngx_connection_t *sc;
++ ngx_http_v3_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;
++ }
++
++ sc->quic->cancelable = 1;
++
++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
++ "http3 create uni stream, type:%ui", type);
++
++ us = ngx_pcalloc(sc->pool, sizeof(ngx_http_v3_uni_stream_t));
++ if (us == NULL) {
++ goto failed;
++ }
++
++ us->index = index;
++
++ sc->data = us;
++
++ sc->read->handler = ngx_http_v3_uni_read_handler;
++ sc->write->handler = ngx_http_v3_dummy_write_handler;
++
++ if (index >= 0) {
++ h3c->known_streams[index] = sc;
++ }
++
++ n = (u_char *) ngx_http_v3_encode_varlen_int(buf, type) - buf;
++
++ h3c = ngx_http_v3_get_session(c);
++ h3c->total_bytes += n;
++
++ if (sc->send(sc, buf, n) != (ssize_t) n) {
++ goto failed;
++ }
++
++ 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_set_max_push_id(ngx_connection_t *c, uint64_t max_push_id)
++{
++ ngx_http_v3_session_t *h3c;
++
++ h3c = ngx_http_v3_get_session(c);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
++ "http3 MAX_PUSH_ID:%uL", max_push_id);
++
++ if (h3c->max_push_id != (uint64_t) -1 && max_push_id < h3c->max_push_id) {
++ return NGX_HTTP_V3_ERR_ID_ERROR;
++ }
++
++ h3c->max_push_id = max_push_id;
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_http_v3_goaway(ngx_connection_t *c, uint64_t push_id)
++{
++ ngx_http_v3_session_t *h3c;
++
++ h3c = ngx_http_v3_get_session(c);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 GOAWAY:%uL", push_id);
++
++ h3c->goaway_push_id = push_id;
++
++ return NGX_OK;
++}
++
++
++ngx_int_t
++ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id)
++{
++ ngx_queue_t *q;
++ ngx_http_request_t *r;
++ ngx_http_v3_push_t *push;
++ ngx_http_v3_session_t *h3c;
++
++ h3c = ngx_http_v3_get_session(c);
++
++ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
++ "http3 CANCEL_PUSH:%uL", push_id);
++
++ if (push_id >= h3c->next_push_id) {
++ return NGX_HTTP_V3_ERR_ID_ERROR;
++ }
++
++ for (q = ngx_queue_head(&h3c->pushing);
++ q != ngx_queue_sentinel(&h3c->pushing);
++ q = ngx_queue_next(&h3c->pushing))
++ {
++ push = (ngx_http_v3_push_t *) q;
++
++ if (push->id != push_id) {
++ continue;
++ }
++
++ r = push->connection->data;
++
++ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
++ "http3 cancel push");
++
++ ngx_http_finalize_request(r, NGX_HTTP_CLOSE);
++
++ break;
++ }
++
++ return NGX_OK;
++}
++
++
++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 -r 67408b4a12c0 src/http/v3/ngx_http_v3_uni.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/http/v3/ngx_http_v3_uni.h Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,38 @@
++
++/*
++ * 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_connection_t *ngx_http_v3_create_push_stream(ngx_connection_t *c,
++ uint64_t push_id);
++ngx_int_t ngx_http_v3_set_max_push_id(ngx_connection_t *c,
++ uint64_t max_push_id);
++ngx_int_t ngx_http_v3_goaway(ngx_connection_t *c, uint64_t push_id);
++ngx_int_t ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id);
++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_ */
+diff -r 67408b4a12c0 src/os/unix/ngx_linux_config.h
+--- a/src/os/unix/ngx_linux_config.h Tue Dec 28 18:28:38 2021 +0300
++++ b/src/os/unix/ngx_linux_config.h Tue Jan 04 18:14:15 2022 -0500
+@@ -103,6 +103,10 @@
+ #include <linux/capability.h>
+ #endif
+
++#if (NGX_HAVE_UDP_SEGMENT)
++#include <netinet/udp.h>
++#endif
++
+
+ #define NGX_LISTEN_BACKLOG 511
+
+diff -r 67408b4a12c0 src/os/unix/ngx_socket.h
+--- a/src/os/unix/ngx_socket.h Tue Dec 28 18:28:38 2021 +0300
++++ b/src/os/unix/ngx_socket.h Tue Jan 04 18:14:15 2022 -0500
+@@ -13,6 +13,8 @@
+
+
+ #define NGX_WRITE_SHUTDOWN SHUT_WR
++#define NGX_READ_SHUTDOWN SHUT_RD
++#define NGX_RDWR_SHUTDOWN SHUT_RDWR
+
+ typedef int ngx_socket_t;
+
+diff -r 67408b4a12c0 src/os/unix/ngx_udp_sendmsg_chain.c
+--- a/src/os/unix/ngx_udp_sendmsg_chain.c Tue Dec 28 18:28:38 2021 +0300
++++ b/src/os/unix/ngx_udp_sendmsg_chain.c Tue Jan 04 18:14:15 2022 -0500
+@@ -12,7 +12,7 @@
+
+ static ngx_chain_t *ngx_udp_output_chain_to_iovec(ngx_iovec_t *vec,
+ ngx_chain_t *in, ngx_log_t *log);
+-static ssize_t ngx_sendmsg(ngx_connection_t *c, ngx_iovec_t *vec);
++static ssize_t ngx_sendmsg_vec(ngx_connection_t *c, ngx_iovec_t *vec);
+
+
+ ngx_chain_t *
+@@ -88,7 +88,7 @@
+
+ send += vec.size;
+
+- n = ngx_sendmsg(c, &vec);
++ n = ngx_sendmsg_vec(c, &vec);
+
+ if (n == NGX_ERROR) {
+ return NGX_CHAIN_ERROR;
+@@ -204,24 +204,13 @@
+
+
+ static ssize_t
+-ngx_sendmsg(ngx_connection_t *c, ngx_iovec_t *vec)
++ngx_sendmsg_vec(ngx_connection_t *c, ngx_iovec_t *vec)
+ {
+- ssize_t n;
+- ngx_err_t err;
+- struct msghdr msg;
+-
+-#if (NGX_HAVE_MSGHDR_MSG_CONTROL)
++ struct msghdr msg;
+
+-#if (NGX_HAVE_IP_SENDSRCADDR)
+- u_char msg_control[CMSG_SPACE(sizeof(struct in_addr))];
+-#elif (NGX_HAVE_IP_PKTINFO)
+- u_char msg_control[CMSG_SPACE(sizeof(struct in_pktinfo))];
+-#endif
+-
+-#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO)
+- u_char msg_control6[CMSG_SPACE(sizeof(struct in6_pktinfo))];
+-#endif
+-
++#if defined(NGX_HAVE_ADDRINFO_CMSG)
++ struct cmsghdr *cmsg;
++ u_char msg_control[CMSG_SPACE(sizeof(ngx_addrinfo_t))];
+ #endif
+
+ ngx_memzero(&msg, sizeof(struct msghdr));
+@@ -234,88 +223,180 @@
+ msg.msg_iov = vec->iovs;
+ msg.msg_iovlen = vec->count;
+
+-#if (NGX_HAVE_MSGHDR_MSG_CONTROL)
++#if defined(NGX_HAVE_ADDRINFO_CMSG)
++ if (c->listening && c->listening->wildcard && c->local_sockaddr) {
++
++ msg.msg_control = msg_control;
++ msg.msg_controllen = sizeof(msg_control);
++ ngx_memzero(msg_control, sizeof(msg_control));
++
++ cmsg = CMSG_FIRSTHDR(&msg);
++
++ msg.msg_controllen = ngx_set_srcaddr_cmsg(cmsg, c->local_sockaddr);
++ }
++#endif
++
++ return ngx_sendmsg(c, &msg, 0);
++}
++
++
++#if defined(NGX_HAVE_ADDRINFO_CMSG)
+
+- if (c->listening && c->listening->wildcard && c->local_sockaddr) {
++size_t
++ngx_set_srcaddr_cmsg(struct cmsghdr *cmsg, struct sockaddr *local_sockaddr)
++{
++ size_t len;
++#if (NGX_HAVE_IP_SENDSRCADDR)
++ struct in_addr *addr;
++ struct sockaddr_in *sin;
++#elif (NGX_HAVE_IP_PKTINFO)
++ struct in_pktinfo *pkt;
++ struct sockaddr_in *sin;
++#endif
++
++#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO)
++ struct in6_pktinfo *pkt6;
++ struct sockaddr_in6 *sin6;
++#endif
++
++
++#if (NGX_HAVE_IP_SENDSRCADDR) || (NGX_HAVE_IP_PKTINFO)
++
++ if (local_sockaddr->sa_family == AF_INET) {
++
++ cmsg->cmsg_level = IPPROTO_IP;
+
+ #if (NGX_HAVE_IP_SENDSRCADDR)
+
+- if (c->local_sockaddr->sa_family == AF_INET) {
+- struct cmsghdr *cmsg;
+- struct in_addr *addr;
+- struct sockaddr_in *sin;
+-
+- msg.msg_control = &msg_control;
+- msg.msg_controllen = sizeof(msg_control);
++ cmsg->cmsg_type = IP_SENDSRCADDR;
++ cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_addr));
++ len = CMSG_SPACE(sizeof(struct in_addr));
+
+- cmsg = CMSG_FIRSTHDR(&msg);
+- cmsg->cmsg_level = IPPROTO_IP;
+- cmsg->cmsg_type = IP_SENDSRCADDR;
+- cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_addr));
++ sin = (struct sockaddr_in *) local_sockaddr;
+
+- sin = (struct sockaddr_in *) c->local_sockaddr;
+-
+- addr = (struct in_addr *) CMSG_DATA(cmsg);
+- *addr = sin->sin_addr;
+- }
++ addr = (struct in_addr *) CMSG_DATA(cmsg);
++ *addr = sin->sin_addr;
+
+ #elif (NGX_HAVE_IP_PKTINFO)
+
+- if (c->local_sockaddr->sa_family == AF_INET) {
+- struct cmsghdr *cmsg;
+- struct in_pktinfo *pkt;
+- struct sockaddr_in *sin;
++ cmsg->cmsg_type = IP_PKTINFO;
++ cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
++ len = CMSG_SPACE(sizeof(struct in_pktinfo));
++
++ sin = (struct sockaddr_in *) local_sockaddr;
++
++ pkt = (struct in_pktinfo *) CMSG_DATA(cmsg);
++ ngx_memzero(pkt, sizeof(struct in_pktinfo));
++ pkt->ipi_spec_dst = sin->sin_addr;
++
++#endif
++ return len;
++ }
++
++#endif
++
++#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO)
++ if (local_sockaddr->sa_family == AF_INET6) {
+
+- msg.msg_control = &msg_control;
+- msg.msg_controllen = sizeof(msg_control);
++ cmsg->cmsg_level = IPPROTO_IPV6;
++ cmsg->cmsg_type = IPV6_PKTINFO;
++ cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
++ len = CMSG_SPACE(sizeof(struct in6_pktinfo));
++
++ sin6 = (struct sockaddr_in6 *) local_sockaddr;
++
++ pkt6 = (struct in6_pktinfo *) CMSG_DATA(cmsg);
++ ngx_memzero(pkt6, sizeof(struct in6_pktinfo));
++ pkt6->ipi6_addr = sin6->sin6_addr;
++
++ return len;
++ }
++#endif
++
++ return 0;
++}
++
+
+- cmsg = CMSG_FIRSTHDR(&msg);
+- cmsg->cmsg_level = IPPROTO_IP;
+- cmsg->cmsg_type = IP_PKTINFO;
+- cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
++ngx_int_t
++ngx_get_srcaddr_cmsg(struct cmsghdr *cmsg, struct sockaddr *local_sockaddr)
++{
++
++#if (NGX_HAVE_IP_RECVDSTADDR)
++ struct in_addr *addr;
++ struct sockaddr_in *sin;
++#elif (NGX_HAVE_IP_PKTINFO)
++ struct in_pktinfo *pkt;
++ struct sockaddr_in *sin;
++#endif
++
++#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO)
++ struct in6_pktinfo *pkt6;
++ struct sockaddr_in6 *sin6;
++#endif
++
++
++ #if (NGX_HAVE_IP_RECVDSTADDR)
+
+- sin = (struct sockaddr_in *) c->local_sockaddr;
++ if (cmsg->cmsg_level == IPPROTO_IP
++ && cmsg->cmsg_type == IP_RECVDSTADDR
++ && local_sockaddr->sa_family == AF_INET)
++ {
++ addr = (struct in_addr *) CMSG_DATA(cmsg);
++ sin = (struct sockaddr_in *) local_sockaddr;
++ sin->sin_addr = *addr;
++
++ return NGX_OK;
++ }
+
+- pkt = (struct in_pktinfo *) CMSG_DATA(cmsg);
+- ngx_memzero(pkt, sizeof(struct in_pktinfo));
+- pkt->ipi_spec_dst = sin->sin_addr;
+- }
++#elif (NGX_HAVE_IP_PKTINFO)
++
++ if (cmsg->cmsg_level == IPPROTO_IP
++ && cmsg->cmsg_type == IP_PKTINFO
++ && local_sockaddr->sa_family == AF_INET)
++ {
++ pkt = (struct in_pktinfo *) CMSG_DATA(cmsg);
++ sin = (struct sockaddr_in *) local_sockaddr;
++ sin->sin_addr = pkt->ipi_addr;
++
++ return NGX_OK;
++ }
+
+ #endif
+
+ #if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO)
+
+- if (c->local_sockaddr->sa_family == AF_INET6) {
+- struct cmsghdr *cmsg;
+- struct in6_pktinfo *pkt6;
+- struct sockaddr_in6 *sin6;
+-
+- msg.msg_control = &msg_control6;
+- msg.msg_controllen = sizeof(msg_control6);
++ if (cmsg->cmsg_level == IPPROTO_IPV6
++ && cmsg->cmsg_type == IPV6_PKTINFO
++ && local_sockaddr->sa_family == AF_INET6)
++ {
++ pkt6 = (struct in6_pktinfo *) CMSG_DATA(cmsg);
++ sin6 = (struct sockaddr_in6 *) local_sockaddr;
++ sin6->sin6_addr = pkt6->ipi6_addr;
+
+- cmsg = CMSG_FIRSTHDR(&msg);
+- cmsg->cmsg_level = IPPROTO_IPV6;
+- cmsg->cmsg_type = IPV6_PKTINFO;
+- cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
+-
+- sin6 = (struct sockaddr_in6 *) c->local_sockaddr;
+-
+- pkt6 = (struct in6_pktinfo *) CMSG_DATA(cmsg);
+- ngx_memzero(pkt6, sizeof(struct in6_pktinfo));
+- pkt6->ipi6_addr = sin6->sin6_addr;
+- }
+-
+-#endif
++ return NGX_OK;
+ }
+
+ #endif
+
++ return NGX_DECLINED;
++}
++
++#endif
++
++
++ssize_t
++ngx_sendmsg(ngx_connection_t *c, struct msghdr *msg, int flags)
++{
++ ssize_t n;
++ ngx_err_t err;
++#if (NGX_DEBUG)
++ size_t size;
++ ngx_uint_t i;
++#endif
++
+ eintr:
+
+- n = sendmsg(c->fd, &msg, 0);
+-
+- ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+- "sendmsg: %z of %uz", n, vec->size);
++ n = sendmsg(c->fd, msg, flags);
+
+ if (n == -1) {
+ err = ngx_errno;
+@@ -338,5 +419,14 @@
+ }
+ }
+
++#if (NGX_DEBUG)
++ for (i = 0, size = 0; i < (size_t) msg->msg_iovlen; i++) {
++ size += msg->msg_iov[i].iov_len;
++ }
++
++ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
++ "sendmsg: %z of %uz", n, size);
++#endif
++
+ return n;
+ }
+diff -r 67408b4a12c0 src/stream/ngx_stream.c
+--- a/src/stream/ngx_stream.c Tue Dec 28 18:28:38 2021 +0300
++++ b/src/stream/ngx_stream.c Tue Jan 04 18:14:15 2022 -0500
+@@ -518,6 +518,9 @@
+ ls->reuseport = addr[i].opt.reuseport;
+ #endif
+
++#if (NGX_STREAM_QUIC)
++ ls->quic = addr[i].opt.quic;
++#endif
+ stport = ngx_palloc(cf->pool, sizeof(ngx_stream_port_t));
+ if (stport == NULL) {
+ return NGX_CONF_ERROR;
+@@ -576,6 +579,9 @@
+ #if (NGX_STREAM_SSL)
+ addrs[i].conf.ssl = addr[i].opt.ssl;
+ #endif
++#if (NGX_STREAM_QUIC)
++ addrs[i].conf.quic = addr[i].opt.quic;
++#endif
+ addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol;
+ addrs[i].conf.addr_text = addr[i].opt.addr_text;
+ }
+@@ -611,6 +617,9 @@
+ #if (NGX_STREAM_SSL)
+ addrs6[i].conf.ssl = addr[i].opt.ssl;
+ #endif
++#if (NGX_STREAM_QUIC)
++ addrs6[i].conf.quic = addr[i].opt.quic;
++#endif
+ addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol;
+ addrs6[i].conf.addr_text = addr[i].opt.addr_text;
+ }
+diff -r 67408b4a12c0 src/stream/ngx_stream.h
+--- a/src/stream/ngx_stream.h Tue Dec 28 18:28:38 2021 +0300
++++ b/src/stream/ngx_stream.h Tue Jan 04 18:14:15 2022 -0500
+@@ -16,6 +16,10 @@
+ #include <ngx_stream_ssl_module.h>
+ #endif
+
++#if (NGX_STREAM_QUIC)
++#include <ngx_stream_quic_module.h>
++#endif
++
+
+ typedef struct ngx_stream_session_s ngx_stream_session_t;
+
+@@ -51,6 +55,7 @@
+ unsigned bind:1;
+ unsigned wildcard:1;
+ unsigned ssl:1;
++ unsigned quic:1;
+ #if (NGX_HAVE_INET6)
+ unsigned ipv6only:1;
+ #endif
+@@ -76,6 +81,7 @@
+ ngx_stream_conf_ctx_t *ctx;
+ ngx_str_t addr_text;
+ unsigned ssl:1;
++ unsigned quic:1;
+ unsigned proxy_protocol:1;
+ } ngx_stream_addr_conf_t;
+
+diff -r 67408b4a12c0 src/stream/ngx_stream_core_module.c
+--- a/src/stream/ngx_stream_core_module.c Tue Dec 28 18:28:38 2021 +0300
++++ b/src/stream/ngx_stream_core_module.c Tue Jan 04 18:14:15 2022 -0500
+@@ -760,6 +760,29 @@
+ #endif
+ }
+
++ if (ngx_strcmp(value[i].data, "quic") == 0) {
++#if (NGX_STREAM_QUIC)
++ ngx_stream_ssl_conf_t *sslcf;
++
++ sslcf = ngx_stream_conf_get_module_srv_conf(cf,
++ ngx_stream_ssl_module);
++
++ sslcf->listen = 1;
++ sslcf->file = cf->conf_file->file.name.data;
++ sslcf->line = cf->conf_file->line;
++
++ ls->quic = 1;
++ ls->type = SOCK_DGRAM;
++
++ continue;
++#else
++ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
++ "the \"quic\" parameter requires "
++ "ngx_stream_quic_module");
++ return NGX_CONF_ERROR;
++#endif
++ }
++
+ if (ngx_strncmp(value[i].data, "so_keepalive=", 13) == 0) {
+
+ if (ngx_strcmp(&value[i].data[13], "on") == 0) {
+@@ -871,6 +894,12 @@
+ }
+ #endif
+
++#if (NGX_STREAM_SSL && NGX_STREAM_QUIC)
++ if (ls->ssl && ls->quic) {
++ return "\"ssl\" parameter is incompatible with \"quic\"";
++ }
++#endif
++
+ if (ls->so_keepalive) {
+ return "\"so_keepalive\" parameter is incompatible with \"udp\"";
+ }
+diff -r 67408b4a12c0 src/stream/ngx_stream_handler.c
+--- a/src/stream/ngx_stream_handler.c Tue Dec 28 18:28:38 2021 +0300
++++ b/src/stream/ngx_stream_handler.c Tue Jan 04 18:14:15 2022 -0500
+@@ -129,6 +129,10 @@
+ s->ssl = addr_conf->ssl;
+ #endif
+
++#if (NGX_STREAM_QUIC)
++ s->ssl |= addr_conf->quic;
++#endif
++
+ if (c->buffer) {
+ s->received += c->buffer->last - c->buffer->pos;
+ }
+@@ -173,6 +177,21 @@
+ s->start_sec = tp->sec;
+ s->start_msec = tp->msec;
+
++#if (NGX_STREAM_QUIC)
++
++ if (addr_conf->quic) {
++ ngx_quic_conf_t *qcf;
++
++ if (c->quic == NULL) {
++ qcf = ngx_stream_get_module_srv_conf(addr_conf->ctx,
++ ngx_stream_quic_module);
++ ngx_quic_run(c, qcf);
++ return;
++ }
++ }
++
++#endif
++
+ rev = c->read;
+ rev->handler = ngx_stream_session_handler;
+
+diff -r 67408b4a12c0 src/stream/ngx_stream_proxy_module.c
+--- a/src/stream/ngx_stream_proxy_module.c Tue Dec 28 18:28:38 2021 +0300
++++ b/src/stream/ngx_stream_proxy_module.c Tue Jan 04 18:14:15 2022 -0500
+@@ -1767,6 +1767,21 @@
+ if (dst->type == SOCK_STREAM && pscf->half_close
+ && src->read->eof && !u->half_closed && !dst->buffered)
+ {
++
++#if (NGX_STREAM_QUIC)
++ if (dst->quic) {
++
++ if (ngx_quic_shutdown_stream(dst, NGX_WRITE_SHUTDOWN)
++ != NGX_OK)
++ {
++ ngx_stream_proxy_finalize(s,
++ NGX_STREAM_INTERNAL_SERVER_ERROR);
++ return;
++ }
++
++ } else
++#endif
++
+ if (ngx_shutdown_socket(dst->fd, NGX_WRITE_SHUTDOWN) == -1) {
+ ngx_connection_error(c, ngx_socket_errno,
+ ngx_shutdown_socket_n " failed");
+diff -r 67408b4a12c0 src/stream/ngx_stream_quic_module.c
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/stream/ngx_stream_quic_module.c Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,360 @@
++
++/*
++ * Copyright (C) Nginx, Inc.
++ * Copyright (C) Roman Arutyunyan
++ */
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++#include <ngx_stream.h>
++
++
++static ngx_int_t ngx_stream_variable_quic(ngx_stream_session_t *s,
++ ngx_stream_variable_value_t *v, uintptr_t data);
++static ngx_int_t ngx_stream_quic_add_variables(ngx_conf_t *cf);
++static void *ngx_stream_quic_create_srv_conf(ngx_conf_t *cf);
++static char *ngx_stream_quic_merge_srv_conf(ngx_conf_t *cf, void *parent,
++ void *child);
++static char *ngx_stream_quic_mtu(ngx_conf_t *cf, void *post, void *data);
++static char *ngx_stream_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd,
++ void *conf);
++
++static ngx_conf_post_t ngx_stream_quic_mtu_post =
++ { ngx_stream_quic_mtu };
++
++static ngx_command_t ngx_stream_quic_commands[] = {
++
++ { ngx_string("quic_timeout"),
++ NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
++ ngx_conf_set_msec_slot,
++ NGX_STREAM_SRV_CONF_OFFSET,
++ offsetof(ngx_quic_conf_t, timeout),
++ NULL },
++
++ { ngx_string("quic_mtu"),
++ NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
++ ngx_conf_set_size_slot,
++ NGX_STREAM_SRV_CONF_OFFSET,
++ offsetof(ngx_quic_conf_t, mtu),
++ &ngx_stream_quic_mtu_post },
++
++ { ngx_string("quic_stream_buffer_size"),
++ NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
++ ngx_conf_set_size_slot,
++ NGX_STREAM_SRV_CONF_OFFSET,
++ offsetof(ngx_quic_conf_t, stream_buffer_size),
++ NULL },
++
++ { ngx_string("quic_retry"),
++ NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG,
++ ngx_conf_set_flag_slot,
++ NGX_STREAM_SRV_CONF_OFFSET,
++ offsetof(ngx_quic_conf_t, retry),
++ NULL },
++
++ { ngx_string("quic_gso"),
++ NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG,
++ ngx_conf_set_flag_slot,
++ NGX_STREAM_SRV_CONF_OFFSET,
++ offsetof(ngx_quic_conf_t, gso_enabled),
++ NULL },
++
++ { ngx_string("quic_host_key"),
++ NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG,
++ ngx_stream_quic_host_key,
++ NGX_STREAM_SRV_CONF_OFFSET,
++ 0,
++ NULL },
++
++ ngx_null_command
++};
++
++
++static ngx_stream_module_t ngx_stream_quic_module_ctx = {
++ ngx_stream_quic_add_variables, /* preconfiguration */
++ NULL, /* postconfiguration */
++
++ NULL, /* create main configuration */
++ NULL, /* init main configuration */
++
++ ngx_stream_quic_create_srv_conf, /* create server configuration */
++ ngx_stream_quic_merge_srv_conf, /* merge server configuration */
++};
++
++
++ngx_module_t ngx_stream_quic_module = {
++ NGX_MODULE_V1,
++ &ngx_stream_quic_module_ctx, /* module context */
++ ngx_stream_quic_commands, /* module directives */
++ NGX_STREAM_MODULE, /* module type */
++ NULL, /* init master */
++ NULL, /* init module */
++ NULL, /* init process */
++ NULL, /* init thread */
++ NULL, /* exit thread */
++ NULL, /* exit process */
++ NULL, /* exit master */
++ NGX_MODULE_V1_PADDING
++};
++
++
++static ngx_stream_variable_t ngx_stream_quic_vars[] = {
++
++ { ngx_string("quic"), NULL, ngx_stream_variable_quic, 0, 0, 0 },
++
++ ngx_stream_null_variable
++};
++
++static ngx_str_t ngx_stream_quic_salt = ngx_string("ngx_quic");
++
++
++static ngx_int_t
++ngx_stream_variable_quic(ngx_stream_session_t *s,
++ ngx_stream_variable_value_t *v, uintptr_t data)
++{
++ if (s->connection->quic) {
++
++ v->len = 4;
++ v->valid = 1;
++ v->no_cacheable = 1;
++ v->not_found = 0;
++ v->data = (u_char *) "quic";
++ return NGX_OK;
++ }
++
++ v->not_found = 1;
++
++ return NGX_OK;
++}
++
++
++static ngx_int_t
++ngx_stream_quic_add_variables(ngx_conf_t *cf)
++{
++ ngx_stream_variable_t *var, *v;
++
++ for (v = ngx_stream_quic_vars; v->name.len; v++) {
++ var = ngx_stream_add_variable(cf, &v->name, v->flags);
++ if (var == NULL) {
++ return NGX_ERROR;
++ }
++
++ var->get_handler = v->get_handler;
++ var->data = v->data;
++ }
++
++ return NGX_OK;
++}
++
++
++static void *
++ngx_stream_quic_create_srv_conf(ngx_conf_t *cf)
++{
++ ngx_quic_conf_t *conf;
++
++ conf = ngx_pcalloc(cf->pool, sizeof(ngx_quic_conf_t));
++ if (conf == NULL) {
++ return NULL;
++ }
++
++ /*
++ * set by ngx_pcalloc():
++ *
++ * conf->host_key = { 0, NULL }
++ * conf->stream_close_code = 0;
++ * conf->stream_reject_code_uni = 0;
++ * conf->stream_reject_code_bidi= 0;
++ */
++
++ conf->timeout = NGX_CONF_UNSET_MSEC;
++ conf->mtu = NGX_CONF_UNSET_SIZE;
++ conf->stream_buffer_size = NGX_CONF_UNSET_SIZE;
++ conf->max_concurrent_streams_bidi = NGX_CONF_UNSET_UINT;
++ conf->max_concurrent_streams_uni = NGX_CONF_UNSET_UINT;
++
++ conf->retry = NGX_CONF_UNSET;
++ conf->gso_enabled = NGX_CONF_UNSET;
++
++ return conf;
++}
++
++
++static char *
++ngx_stream_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
++{
++ ngx_quic_conf_t *prev = parent;
++ ngx_quic_conf_t *conf = child;
++
++ ngx_stream_ssl_conf_t *scf;
++
++ ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 60000);
++
++ ngx_conf_merge_size_value(conf->mtu, prev->mtu,
++ NGX_QUIC_MAX_UDP_PAYLOAD_SIZE);
++
++ ngx_conf_merge_uint_value(conf->max_concurrent_streams_bidi,
++ prev->max_concurrent_streams_bidi, 16);
++
++ ngx_conf_merge_uint_value(conf->max_concurrent_streams_uni,
++ prev->max_concurrent_streams_uni, 3);
++
++ ngx_conf_merge_value(conf->retry, prev->retry, 0);
++ ngx_conf_merge_value(conf->gso_enabled, prev->gso_enabled, 0);
++
++ ngx_conf_merge_str_value(conf->host_key, prev->host_key, "");
++
++ if (conf->host_key.len == 0) {
++
++ conf->host_key.len = NGX_QUIC_DEFAULT_HOST_KEY_LEN;
++ conf->host_key.data = ngx_palloc(cf->pool, conf->host_key.len);
++ if (conf->host_key.data == NULL) {
++ return NGX_CONF_ERROR;
++ }
++
++ if (RAND_bytes(conf->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->host_key, &ngx_stream_quic_salt,
++ conf->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->host_key, &ngx_stream_quic_salt,
++ conf->sr_token_key, NGX_QUIC_SR_KEY_LEN)
++ != NGX_OK)
++ {
++ return NGX_CONF_ERROR;
++ }
++
++ scf = ngx_stream_conf_get_module_srv_conf(cf, ngx_stream_ssl_module);
++ conf->ssl = &scf->ssl;
++
++ return NGX_CONF_OK;
++}
++
++
++static char *
++ngx_stream_quic_mtu(ngx_conf_t *cf, void *post, void *data)
++{
++ size_t *sp = data;
++
++ if (*sp < NGX_QUIC_MIN_INITIAL_SIZE
++ || *sp > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE)
++ {
++ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
++ "\"quic_mtu\" must be between %d and %d",
++ NGX_QUIC_MIN_INITIAL_SIZE,
++ NGX_QUIC_MAX_UDP_PAYLOAD_SIZE);
++
++ return NGX_CONF_ERROR;
++ }
++
++ return NGX_CONF_OK;
++}
++
++
++static char *
++ngx_stream_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
++{
++ ngx_quic_conf_t *qcf = conf;
++
++ u_char *buf;
++ size_t size;
++ ssize_t n;
++ ngx_str_t *value;
++ ngx_file_t file;
++ ngx_file_info_t fi;
++
++ 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 -r 67408b4a12c0 src/stream/ngx_stream_quic_module.h
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/src/stream/ngx_stream_quic_module.h Tue Jan 04 18:14:15 2022 -0500
+@@ -0,0 +1,20 @@
++
++/*
++ * Copyright (C) Roman Arutyunyan
++ * Copyright (C) Nginx, Inc.
++ */
++
++
++#ifndef _NGX_STREAM_QUIC_H_INCLUDED_
++#define _NGX_STREAM_QUIC_H_INCLUDED_
++
++
++#include <ngx_config.h>
++#include <ngx_core.h>
++#include <ngx_stream.h>
++
++
++extern ngx_module_t ngx_stream_quic_module;
++
++
++#endif /* _NGX_STREAM_QUIC_H_INCLUDED_ */
+diff -r 67408b4a12c0 src/stream/ngx_stream_ssl_module.c
+--- a/src/stream/ngx_stream_ssl_module.c Tue Dec 28 18:28:38 2021 +0300
++++ b/src/stream/ngx_stream_ssl_module.c Tue Jan 04 18:14:15 2022 -0500
+@@ -1194,7 +1194,10 @@
+ static ngx_int_t
+ ngx_stream_ssl_init(ngx_conf_t *cf)
+ {
++ ngx_uint_t i;
++ ngx_stream_listen_t *listen;
+ ngx_stream_handler_pt *h;
++ ngx_stream_ssl_conf_t *scf;
+ ngx_stream_core_main_conf_t *cmcf;
+
+ cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module);
+@@ -1206,5 +1209,23 @@
+
+ *h = ngx_stream_ssl_handler;
+
++ listen = cmcf->listen.elts;
++
++ for (i = 0; i < cmcf->listen.nelts; i++) {
++ if (!listen[i].quic) {
++ continue;
++ }
++
++ scf = listen[i].ctx->srv_conf[ngx_stream_ssl_module.ctx_index];
++
++ if (scf->certificates && !(scf->protocols & NGX_SSL_TLSv1_3)) {
++ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
++ "\"ssl_protocols\" must enable TLSv1.3 for "
++ "the \"listen ... quic\" directive in %s:%ui",
++ scf->file, scf->line);
++ return NGX_ERROR;
++ }
++ }
++
+ return NGX_OK;
+ }