summaryrefslogtreecommitdiffhomepage
path: root/src/http
diff options
context:
space:
mode:
Diffstat (limited to 'src/http')
-rw-r--r--src/http/modules/ngx_http_chunked_filter_module.c58
-rw-r--r--src/http/modules/ngx_http_ssl_module.c22
-rw-r--r--src/http/ngx_http.c33
-rw-r--r--src/http/ngx_http.h6
-rw-r--r--src/http/ngx_http_core_module.c16
-rw-r--r--src/http/ngx_http_core_module.h4
-rw-r--r--src/http/ngx_http_header_filter_module.c28
-rw-r--r--src/http/ngx_http_parse.c14
-rw-r--r--src/http/ngx_http_request.c194
-rw-r--r--src/http/ngx_http_request.h7
-rw-r--r--src/http/ngx_http_request_body.c29
-rw-r--r--src/http/v3/ngx_http_v3.c96
-rw-r--r--src/http/v3/ngx_http_v3.h116
-rw-r--r--src/http/v3/ngx_http_v3_module.c302
-rw-r--r--src/http/v3/ngx_http_v3_parse.c1481
-rw-r--r--src/http/v3/ngx_http_v3_parse.h155
-rw-r--r--src/http/v3/ngx_http_v3_request.c669
-rw-r--r--src/http/v3/ngx_http_v3_streams.c571
-rw-r--r--src/http/v3/ngx_http_v3_tables.c416
19 files changed, 4157 insertions, 60 deletions
diff --git a/src/http/modules/ngx_http_chunked_filter_module.c b/src/http/modules/ngx_http_chunked_filter_module.c
index 4d6fd3eed..87b032496 100644
--- a/src/http/modules/ngx_http_chunked_filter_module.c
+++ b/src/http/modules/ngx_http_chunked_filter_module.c
@@ -18,7 +18,7 @@ typedef struct {
static ngx_int_t ngx_http_chunked_filter_init(ngx_conf_t *cf);
static ngx_chain_t *ngx_http_chunked_create_trailers(ngx_http_request_t *r,
- ngx_http_chunked_filter_ctx_t *ctx);
+ ngx_http_chunked_filter_ctx_t *ctx, size_t size);
static ngx_http_module_t ngx_http_chunked_filter_module_ctx = {
@@ -106,6 +106,7 @@ ngx_http_chunked_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
u_char *chunk;
off_t size;
+ size_t n;
ngx_int_t rc;
ngx_buf_t *b;
ngx_chain_t *out, *cl, *tl, **ll;
@@ -161,29 +162,50 @@ ngx_http_chunked_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
chunk = b->start;
if (chunk == NULL) {
- /* the "0000000000000000" is 64-bit hexadecimal string */
- chunk = ngx_palloc(r->pool, sizeof("0000000000000000" CRLF) - 1);
+#if (NGX_HTTP_V3)
+ if (r->http_version == NGX_HTTP_VERSION_30) {
+ n = NGX_HTTP_V3_VARLEN_INT_LEN * 2;
+
+ } else
+#endif
+ {
+ /* the "0000000000000000" is 64-bit hexadecimal string */
+ n = sizeof("0000000000000000" CRLF) - 1;
+ }
+
+ chunk = ngx_palloc(r->pool, n);
if (chunk == NULL) {
return NGX_ERROR;
}
b->start = chunk;
- b->end = chunk + sizeof("0000000000000000" CRLF) - 1;
+ b->end = chunk + n;
}
b->tag = (ngx_buf_tag_t) &ngx_http_chunked_filter_module;
b->memory = 0;
b->temporary = 1;
b->pos = chunk;
- b->last = ngx_sprintf(chunk, "%xO" CRLF, size);
+
+#if (NGX_HTTP_V3)
+ if (r->http_version == NGX_HTTP_VERSION_30) {
+ b->last = (u_char *) ngx_http_v3_encode_varlen_int(chunk,
+ NGX_HTTP_V3_FRAME_DATA);
+ b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, size);
+
+ } else
+#endif
+ {
+ b->last = ngx_sprintf(chunk, "%xO" CRLF, size);
+ }
tl->next = out;
out = tl;
}
if (cl->buf->last_buf) {
- tl = ngx_http_chunked_create_trailers(r, ctx);
+ tl = ngx_http_chunked_create_trailers(r, ctx, size);
if (tl == NULL) {
return NGX_ERROR;
}
@@ -192,11 +214,12 @@ ngx_http_chunked_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
*ll = tl;
- if (size == 0) {
- tl->buf->pos += 2;
- }
-
- } else if (size > 0) {
+ } else if (size > 0
+#if (NGX_HTTP_V3)
+ && r->http_version != NGX_HTTP_VERSION_30
+#endif
+ )
+ {
tl = ngx_chain_get_free_buf(r->pool, &ctx->free);
if (tl == NULL) {
return NGX_ERROR;
@@ -227,7 +250,7 @@ ngx_http_chunked_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
static ngx_chain_t *
ngx_http_chunked_create_trailers(ngx_http_request_t *r,
- ngx_http_chunked_filter_ctx_t *ctx)
+ ngx_http_chunked_filter_ctx_t *ctx, size_t size)
{
size_t len;
ngx_buf_t *b;
@@ -236,6 +259,12 @@ ngx_http_chunked_create_trailers(ngx_http_request_t *r,
ngx_list_part_t *part;
ngx_table_elt_t *header;
+#if (NGX_HTTP_V3)
+ if (r->http_version == NGX_HTTP_VERSION_30) {
+ return ngx_http_v3_create_trailers(r);
+ }
+#endif
+
len = 0;
part = &r->headers_out.trailers.part;
@@ -288,7 +317,10 @@ ngx_http_chunked_create_trailers(ngx_http_request_t *r,
b->last = b->pos;
- *b->last++ = CR; *b->last++ = LF;
+ if (size > 0) {
+ *b->last++ = CR; *b->last++ = LF;
+ }
+
*b->last++ = '0';
*b->last++ = CR; *b->last++ = LF;
diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c
index 495e628d3..a48d3b924 100644
--- a/src/http/modules/ngx_http_ssl_module.c
+++ b/src/http/modules/ngx_http_ssl_module.c
@@ -371,7 +371,7 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out,
#if (NGX_DEBUG)
unsigned int i;
#endif
-#if (NGX_HTTP_V2)
+#if (NGX_HTTP_V2 || NGX_HTTP_V3)
ngx_http_connection_t *hc;
#endif
#if (NGX_HTTP_V2 || NGX_DEBUG)
@@ -388,9 +388,11 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out,
}
#endif
-#if (NGX_HTTP_V2)
+#if (NGX_HTTP_V2 || NGX_HTTP_V3)
hc = c->data;
+#endif
+#if (NGX_HTTP_V2)
if (hc->addr_conf->http2) {
srv =
(unsigned char *) NGX_HTTP_V2_ALPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE;
@@ -398,6 +400,12 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out,
} else
#endif
+#if (NGX_HTTP_V3)
+ if (hc->addr_conf->http3) {
+ srv = (unsigned char *) NGX_HTTP_V3_ALPN_ADVERTISE;
+ srvlen = sizeof(NGX_HTTP_V3_ALPN_ADVERTISE) - 1;
+ } else
+#endif
{
srv = (unsigned char *) NGX_HTTP_NPN_ADVERTISE;
srvlen = sizeof(NGX_HTTP_NPN_ADVERTISE) - 1;
@@ -1142,7 +1150,7 @@ ngx_http_ssl_init(ngx_conf_t *cf)
addr = port[p].addrs.elts;
for (a = 0; a < port[p].addrs.nelts; a++) {
- if (!addr[a].opt.ssl) {
+ if (!addr[a].opt.ssl && !addr[a].opt.http3) {
continue;
}
@@ -1156,6 +1164,14 @@ ngx_http_ssl_init(ngx_conf_t *cf)
cscf->file_name, cscf->line);
return NGX_ERROR;
}
+
+ if (addr[a].opt.http3 && !(sscf->protocols & NGX_SSL_TLSv1_3)) {
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "\"ssl_protocols\" did not enable TLSv1.3 for "
+ "the \"listen ... http3\" directive in %s:%ui",
+ cscf->file_name, cscf->line);
+ return NGX_ERROR;
+ }
}
}
diff --git a/src/http/ngx_http.c b/src/http/ngx_http.c
index 79ef9c644..10f88fabb 100644
--- a/src/http/ngx_http.c
+++ b/src/http/ngx_http.c
@@ -1163,7 +1163,10 @@ ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
port = cmcf->ports->elts;
for (i = 0; i < cmcf->ports->nelts; i++) {
- if (p != port[i].port || sa->sa_family != port[i].family) {
+ if (p != port[i].port
+ || lsopt->type != port[i].type
+ || sa->sa_family != port[i].family)
+ {
continue;
}
@@ -1180,6 +1183,7 @@ ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
}
port->family = sa->sa_family;
+ port->type = lsopt->type;
port->port = p;
port->addrs.elts = NULL;
@@ -1199,6 +1203,9 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
#if (NGX_HTTP_V2)
ngx_uint_t http2;
#endif
+#if (NGX_HTTP_SSL)
+ ngx_uint_t http3;
+#endif
/*
* we cannot compare whole sockaddr struct's as kernel
@@ -1234,6 +1241,9 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
#if (NGX_HTTP_V2)
http2 = lsopt->http2 || addr[i].opt.http2;
#endif
+#if (NGX_HTTP_SSL)
+ http3 = lsopt->http3 || addr[i].opt.http3;
+#endif
if (lsopt->set) {
@@ -1270,6 +1280,9 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
#if (NGX_HTTP_V2)
addr[i].opt.http2 = http2;
#endif
+#if (NGX_HTTP_SSL)
+ addr[i].opt.http3 = http3;
+#endif
return NGX_OK;
}
@@ -1313,6 +1326,17 @@ ngx_http_add_address(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
#endif
+#if (NGX_HTTP_SSL && !defined NGX_OPENSSL_QUIC)
+
+ if (lsopt->http3) {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "nginx was built with OpenSSL that lacks QUIC "
+ "support, HTTP/3 is not enabled for %V",
+ &lsopt->addr_text);
+ }
+
+#endif
+
addr = ngx_array_push(&port->addrs);
if (addr == NULL) {
return NGX_ERROR;
@@ -1735,6 +1759,7 @@ ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr)
}
#endif
+ ls->type = addr->opt.type;
ls->backlog = addr->opt.backlog;
ls->rcvbuf = addr->opt.rcvbuf;
ls->sndbuf = addr->opt.sndbuf;
@@ -1802,6 +1827,9 @@ ngx_http_add_addrs(ngx_conf_t *cf, ngx_http_port_t *hport,
#if (NGX_HTTP_V2)
addrs[i].conf.http2 = addr[i].opt.http2;
#endif
+#if (NGX_HTTP_SSL)
+ addrs[i].conf.http3 = addr[i].opt.http3;
+#endif
addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol;
if (addr[i].hash.buckets == NULL
@@ -1867,6 +1895,9 @@ ngx_http_add_addrs6(ngx_conf_t *cf, ngx_http_port_t *hport,
#if (NGX_HTTP_V2)
addrs6[i].conf.http2 = addr[i].opt.http2;
#endif
+#if (NGX_HTTP_SSL)
+ addrs6[i].conf.http3 = addr[i].opt.http3;
+#endif
addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol;
if (addr[i].hash.buckets == NULL
diff --git a/src/http/ngx_http.h b/src/http/ngx_http.h
index 8b43857ee..a0946c95a 100644
--- a/src/http/ngx_http.h
+++ b/src/http/ngx_http.h
@@ -38,6 +38,9 @@ typedef u_char *(*ngx_http_log_handler_pt)(ngx_http_request_t *r,
#if (NGX_HTTP_V2)
#include <ngx_http_v2.h>
#endif
+#if (NGX_HTTP_V3)
+#include <ngx_http_v3.h>
+#endif
#if (NGX_HTTP_CACHE)
#include <ngx_http_cache.h>
#endif
@@ -60,6 +63,9 @@ struct ngx_http_chunked_s {
ngx_uint_t state;
off_t size;
off_t length;
+#if (NGX_HTTP_V3)
+ void *h3_parse;
+#endif
};
diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c
index 3671558d8..9bb89ee37 100644
--- a/src/http/ngx_http_core_module.c
+++ b/src/http/ngx_http_core_module.c
@@ -819,7 +819,7 @@ ngx_http_handler(ngx_http_request_t *r)
if (!r->internal) {
switch (r->headers_in.connection_type) {
case 0:
- r->keepalive = (r->http_version > NGX_HTTP_VERSION_10);
+ r->keepalive = (r->http_version == NGX_HTTP_VERSION_11);
break;
case NGX_HTTP_CONNECTION_CLOSE:
@@ -3880,6 +3880,7 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
ngx_memzero(&lsopt, sizeof(ngx_http_listen_opt_t));
lsopt.backlog = NGX_LISTEN_BACKLOG;
+ lsopt.type = SOCK_STREAM;
lsopt.rcvbuf = -1;
lsopt.sndbuf = -1;
#if (NGX_HAVE_SETFIB)
@@ -4078,6 +4079,19 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
#endif
}
+ if (ngx_strcmp(value[n].data, "http3") == 0) {
+#if (NGX_HTTP_V3)
+ lsopt.http3 = 1;
+ lsopt.type = SOCK_DGRAM;
+ continue;
+#else
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "the \"http3\" parameter requires "
+ "ngx_http_v3_module");
+ return NGX_CONF_ERROR;
+#endif
+ }
+
if (ngx_strcmp(value[n].data, "spdy") == 0) {
ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
"invalid parameter \"spdy\": "
diff --git a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h
index 2aadae7ff..e7c117c9e 100644
--- a/src/http/ngx_http_core_module.h
+++ b/src/http/ngx_http_core_module.h
@@ -75,6 +75,7 @@ typedef struct {
unsigned wildcard:1;
unsigned ssl:1;
unsigned http2:1;
+ unsigned http3:1;
#if (NGX_HAVE_INET6)
unsigned ipv6only:1;
#endif
@@ -86,6 +87,7 @@ typedef struct {
int backlog;
int rcvbuf;
int sndbuf;
+ int type;
#if (NGX_HAVE_SETFIB)
int setfib;
#endif
@@ -237,6 +239,7 @@ struct ngx_http_addr_conf_s {
unsigned ssl:1;
unsigned http2:1;
+ unsigned http3:1;
unsigned proxy_protocol:1;
};
@@ -266,6 +269,7 @@ typedef struct {
typedef struct {
ngx_int_t family;
+ ngx_int_t type;
in_port_t port;
ngx_array_t addrs; /* array of ngx_http_conf_addr_t */
} ngx_http_conf_port_t;
diff --git a/src/http/ngx_http_header_filter_module.c b/src/http/ngx_http_header_filter_module.c
index 9b8940590..85edf3657 100644
--- a/src/http/ngx_http_header_filter_module.c
+++ b/src/http/ngx_http_header_filter_module.c
@@ -187,6 +187,29 @@ ngx_http_header_filter(ngx_http_request_t *r)
r->header_only = 1;
}
+ if (r->headers_out.status_line.len == 0) {
+ if (r->headers_out.status == NGX_HTTP_NO_CONTENT
+ || r->headers_out.status == NGX_HTTP_NOT_MODIFIED)
+ {
+ r->header_only = 1;
+ }
+ }
+
+#if (NGX_HTTP_V3)
+
+ if (r->http_version == NGX_HTTP_VERSION_30) {
+ ngx_chain_t *cl;
+
+ cl = ngx_http_v3_create_header(r);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ return ngx_http_write_filter(r, cl);
+ }
+
+#endif
+
if (r->headers_out.last_modified_time != -1) {
if (r->headers_out.status != NGX_HTTP_OK
&& r->headers_out.status != NGX_HTTP_PARTIAL_CONTENT
@@ -220,7 +243,6 @@ ngx_http_header_filter(ngx_http_request_t *r)
/* 2XX */
if (status == NGX_HTTP_NO_CONTENT) {
- r->header_only = 1;
ngx_str_null(&r->headers_out.content_type);
r->headers_out.last_modified_time = -1;
r->headers_out.last_modified = NULL;
@@ -237,10 +259,6 @@ ngx_http_header_filter(ngx_http_request_t *r)
{
/* 3XX */
- if (status == NGX_HTTP_NOT_MODIFIED) {
- r->header_only = 1;
- }
-
status = status - NGX_HTTP_MOVED_PERMANENTLY + NGX_HTTP_OFF_3XX;
status_line = &ngx_http_status_lines[status];
len += ngx_http_status_lines[status].len;
diff --git a/src/http/ngx_http_parse.c b/src/http/ngx_http_parse.c
index cfc42f9dd..92bcf12ad 100644
--- a/src/http/ngx_http_parse.c
+++ b/src/http/ngx_http_parse.c
@@ -144,6 +144,7 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b)
/* HTTP methods: GET, HEAD, POST */
case sw_start:
r->request_start = p;
+ r->method_start = p;
if (ch == CR || ch == LF) {
break;
@@ -158,7 +159,7 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b)
case sw_method:
if (ch == ' ') {
- r->method_end = p - 1;
+ r->method_end = p;
m = r->request_start;
switch (p - m) {
@@ -2184,6 +2185,12 @@ ngx_http_parse_chunked(ngx_http_request_t *r, ngx_buf_t *b,
sw_trailer_header_almost_done
} state;
+#if (NGX_HTTP_V3)
+ if (r->http_version == NGX_HTTP_VERSION_30) {
+ return ngx_http_v3_parse_request_body(r, b, ctx);
+ }
+#endif
+
state = ctx->state;
if (state == sw_chunk_data && ctx->size == 0) {
@@ -2370,6 +2377,11 @@ ngx_http_parse_chunked(ngx_http_request_t *r, ngx_buf_t *b,
}
}
+ if (b->last_buf) {
+ /* XXX client prematurely closed connection */
+ return NGX_ERROR;
+ }
+
data:
ctx->state = state;
diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c
index eb53996b1..082938e00 100644
--- a/src/http/ngx_http_request.c
+++ b/src/http/ngx_http_request.c
@@ -64,6 +64,9 @@ static void ngx_http_ssl_handshake(ngx_event_t *rev);
static void ngx_http_ssl_handshake_handler(ngx_connection_t *c);
#endif
+#if (NGX_HTTP_V3)
+static void ngx_http_quic_stream_handler(ngx_connection_t *c);
+#endif
static char *ngx_http_client_errors[] = {
@@ -218,7 +221,16 @@ ngx_http_init_connection(ngx_connection_t *c)
ngx_http_in6_addr_t *addr6;
#endif
- hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t));
+#if (NGX_HTTP_V3)
+ if (c->type == SOCK_DGRAM) {
+ hc = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_connection_t));
+ hc->quic = 1;
+ hc->ssl = 1;
+
+ } else
+#endif
+ hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t));
+
if (hc == NULL) {
ngx_http_close_connection(c);
return;
@@ -324,6 +336,23 @@ ngx_http_init_connection(ngx_connection_t *c)
rev->handler = ngx_http_wait_request_handler;
c->write->handler = ngx_http_empty_handler;
+ if (c->shared) {
+ rev->ready = 1;
+ }
+
+#if (NGX_HTTP_V3)
+ if (hc->quic) {
+ ngx_http_v3_srv_conf_t *v3cf;
+ ngx_http_ssl_srv_conf_t *sscf;
+
+ v3cf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module);
+ sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module);
+
+ ngx_quic_run(c, &sscf->ssl, &v3cf->quic, ngx_http_quic_stream_handler);
+ return;
+ }
+#endif
+
#if (NGX_HTTP_V2)
if (hc->addr_conf->http2) {
rev->handler = ngx_http_v2_init;
@@ -371,6 +400,64 @@ ngx_http_init_connection(ngx_connection_t *c)
}
+#if (NGX_HTTP_V3)
+
+static void
+ngx_http_quic_stream_handler(ngx_connection_t *c)
+{
+ ngx_event_t *rev;
+ ngx_connection_t *pc;
+ ngx_http_log_ctx_t *ctx;
+ ngx_http_connection_t *hc;
+ ngx_http_v3_connection_t *h3c;
+
+ pc = c->qs->parent;
+ h3c = pc->data;
+
+ if (c->qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) {
+ ngx_http_v3_handle_client_uni_stream(c);
+ return;
+ }
+
+ hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t));
+ if (hc == NULL) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ ngx_memcpy(hc, h3c, sizeof(ngx_http_connection_t));
+ c->data = hc;
+
+ ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t));
+ if (ctx == NULL) {
+ ngx_http_close_connection(c);
+ return;
+ }
+
+ ctx->connection = c;
+ ctx->request = NULL;
+ ctx->current_request = NULL;
+
+ c->log->connection = c->number;
+ c->log->handler = ngx_http_log_error;
+ c->log->data = ctx;
+ c->log->action = "waiting for request";
+
+ c->log_error = NGX_ERROR_INFO;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 new stream id:0x%uXL", c->qs->id);
+
+ rev = c->read;
+ rev->handler = ngx_http_wait_request_handler;
+ c->write->handler = ngx_http_empty_handler;
+
+ rev->handler(rev);
+}
+
+#endif
+
+
static void
ngx_http_wait_request_handler(ngx_event_t *rev)
{
@@ -619,6 +706,13 @@ ngx_http_alloc_request(ngx_connection_t *c)
r->method = NGX_HTTP_UNKNOWN;
r->http_version = NGX_HTTP_VERSION_10;
+#if (NGX_HTTP_V3)
+ if (hc->quic) {
+ r->http_version = NGX_HTTP_VERSION_30;
+ r->headers_in.chunked = 1;
+ }
+#endif
+
r->headers_in.content_length_n = -1;
r->headers_in.keep_alive_n = -1;
r->headers_out.content_length_n = -1;
@@ -1068,7 +1162,16 @@ ngx_http_process_request_line(ngx_event_t *rev)
}
}
- rc = ngx_http_parse_request_line(r, r->header_in);
+ switch (r->http_version) {
+#if (NGX_HTTP_V3)
+ case NGX_HTTP_VERSION_30:
+ rc = ngx_http_v3_parse_header(r, r->header_in);
+ break;
+#endif
+
+ default: /* HTTP/1.x */
+ rc = ngx_http_parse_request_line(r, r->header_in);
+ }
if (rc == NGX_OK) {
@@ -1076,13 +1179,13 @@ ngx_http_process_request_line(ngx_event_t *rev)
r->request_line.len = r->request_end - r->request_start;
r->request_line.data = r->request_start;
- r->request_length = r->header_in->pos - r->request_start;
+ r->request_length = r->header_in->pos - r->request_start; /* XXX */
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http request line: \"%V\"", &r->request_line);
- r->method_name.len = r->method_end - r->request_start + 1;
- r->method_name.data = r->request_line.data;
+ r->method_name.len = r->method_end - r->method_start;
+ r->method_name.data = r->method_start;
if (r->http_protocol.data) {
r->http_protocol.len = r->request_end - r->http_protocol.data;
@@ -1153,6 +1256,15 @@ ngx_http_process_request_line(ngx_event_t *rev)
break;
}
+ if (rc == NGX_DONE) {
+ if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ break;
+ }
+
if (rc != NGX_AGAIN) {
/* there was error while a request line parsing */
@@ -1343,7 +1455,7 @@ ngx_http_process_request_headers(ngx_event_t *rev)
cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
- rc = NGX_AGAIN;
+ rc = NGX_OK;
for ( ;; ) {
@@ -1397,11 +1509,21 @@ ngx_http_process_request_headers(ngx_event_t *rev)
/* the host header could change the server configuration context */
cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
- rc = ngx_http_parse_header_line(r, r->header_in,
- cscf->underscores_in_headers);
+ switch (r->http_version) {
+#if (NGX_HTTP_V3)
+ case NGX_HTTP_VERSION_30:
+ rc = ngx_http_v3_parse_header(r, r->header_in);
+ break;
+#endif
+
+ default: /* HTTP/1.x */
+ rc = ngx_http_parse_header_line(r, r->header_in,
+ cscf->underscores_in_headers);
+ }
if (rc == NGX_OK) {
+ /* XXX */
r->request_length += r->header_in->pos - r->header_name_start;
if (r->invalid_header && cscf->ignore_invalid_headers) {
@@ -1427,11 +1549,17 @@ ngx_http_process_request_headers(ngx_event_t *rev)
h->key.len = r->header_name_end - r->header_name_start;
h->key.data = r->header_name_start;
- h->key.data[h->key.len] = '\0';
+
+ if (h->key.data[h->key.len]) {
+ h->key.data[h->key.len] = '\0';
+ }
h->value.len = r->header_end - r->header_start;
h->value.data = r->header_start;
- h->value.data[h->value.len] = '\0';
+
+ if (h->value.data[h->value.len]) {
+ h->value.data[h->value.len] = '\0';
+ }
h->lowcase_key = ngx_pnalloc(r->pool, h->key.len);
if (h->lowcase_key == NULL) {
@@ -1467,7 +1595,7 @@ ngx_http_process_request_headers(ngx_event_t *rev)
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http header done");
- r->request_length += r->header_in->pos - r->header_name_start;
+ r->request_length += r->header_in->pos - r->header_name_start; /* XXX */
r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE;
@@ -1582,7 +1710,7 @@ ngx_http_alloc_large_header_buffer(ngx_http_request_t *r,
return NGX_OK;
}
- old = request_line ? r->request_start : r->header_name_start;
+ old = request_line ? r->request_start : r->header_name_start; /* XXX */
cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
@@ -1661,45 +1789,59 @@ ngx_http_alloc_large_header_buffer(ngx_http_request_t *r,
r->request_end = new + (r->request_end - old);
}
- r->method_end = new + (r->method_end - old);
+ if (r->method_start >= old && r->method_start < r->header_in->pos) {
+ r->method_start = new + (r->method_start - old);
+ r->method_end = new + (r->method_end - old);
+ }
- r->uri_start = new + (r->uri_start - old);
- r->uri_end = new + (r->uri_end - old);
+ if (r->uri_start >= old && r->uri_start < r->header_in->pos) {
+ r->uri_start = new + (r->uri_start - old);
+ r->uri_end = new + (r->uri_end - old);
+ }
- if (r->schema_start) {
+ if (r->schema_start >= old && r->schema_start < r->header_in->pos) {
r->schema_start = new + (r->schema_start - old);
r->schema_end = new + (r->schema_end - old);
}
- if (r->host_start) {
+ if (r->host_start >= old && r->host_start < r->header_in->pos) {
r->host_start = new + (r->host_start - old);
if (r->host_end) {
r->host_end = new + (r->host_end - old);
}
}
- if (r->port_start) {
+ if (r->port_start >= old && r->port_start < r->header_in->pos) {
r->port_start = new + (r->port_start - old);
r->port_end = new + (r->port_end - old);
}
- if (r->uri_ext) {
+ if (r->uri_ext >= old && r->uri_ext < r->header_in->pos) {
r->uri_ext = new + (r->uri_ext - old);
}
- if (r->args_start) {
+ if (r->args_start >= old && r->args_start < r->header_in->pos) {
r->args_start = new + (r->args_start - old);
}
- if (r->http_protocol.data) {
+ if (r->http_protocol.data >= old
+ && r->http_protocol.data < r->header_in->pos)
+ {
r->http_protocol.data = new + (r->http_protocol.data - old);
}
} else {
- r->header_name_start = new;
- r->header_name_end = new + (r->header_name_end - old);
- r->header_start = new + (r->header_start - old);
- r->header_end = new + (r->header_end - old);
+ if (r->header_name_start >= old
+ && r->header_name_start < r->header_in->pos)
+ {
+ r->header_name_start = new;
+ r->header_name_end = new + (r->header_name_end - old);
+ }
+
+ if (r->header_start >= old && r->header_start < r->header_in->pos) {
+ r->header_start = new + (r->header_start - old);
+ r->header_end = new + (r->header_end - old);
+ }
}
r->header_in = b;
@@ -1924,7 +2066,7 @@ ngx_http_process_request_header(ngx_http_request_t *r)
return NGX_ERROR;
}
- if (r->headers_in.host == NULL && r->http_version > NGX_HTTP_VERSION_10) {
+ if (r->headers_in.host == NULL && r->http_version == NGX_HTTP_VERSION_11) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"client sent HTTP/1.1 request without \"Host\" header");
ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h
index 70c2d424d..772d53b2d 100644
--- a/src/http/ngx_http_request.h
+++ b/src/http/ngx_http_request.h
@@ -24,6 +24,7 @@
#define NGX_HTTP_VERSION_10 1000
#define NGX_HTTP_VERSION_11 1001
#define NGX_HTTP_VERSION_20 2000
+#define NGX_HTTP_VERSION_30 3000
#define NGX_HTTP_UNKNOWN 0x0001
#define NGX_HTTP_GET 0x0002
@@ -323,6 +324,7 @@ typedef struct {
ngx_chain_t *free;
unsigned ssl:1;
+ unsigned quic:1;
unsigned proxy_protocol:1;
} ngx_http_connection_t;
@@ -583,6 +585,7 @@ struct ngx_http_request_s {
u_char *args_start;
u_char *request_start;
u_char *request_end;
+ u_char *method_start;
u_char *method_end;
u_char *schema_start;
u_char *schema_end;
@@ -591,6 +594,10 @@ struct ngx_http_request_s {
u_char *port_start;
u_char *port_end;
+#if (NGX_HTTP_V3)
+ void *h3_parse;
+#endif
+
unsigned http_minor:16;
unsigned http_major:16;
};
diff --git a/src/http/ngx_http_request_body.c b/src/http/ngx_http_request_body.c
index c4f092e59..b07d8562f 100644
--- a/src/http/ngx_http_request_body.c
+++ b/src/http/ngx_http_request_body.c
@@ -343,11 +343,10 @@ ngx_http_do_read_client_request_body(ngx_http_request_t *r)
}
if (n == 0) {
- ngx_log_error(NGX_LOG_INFO, c->log, 0,
- "client prematurely closed connection");
+ rb->buf->last_buf = 1;
}
- if (n == 0 || n == NGX_ERROR) {
+ if (n == NGX_ERROR) {
c->error = 1;
return NGX_HTTP_BAD_REQUEST;
}
@@ -355,7 +354,7 @@ ngx_http_do_read_client_request_body(ngx_http_request_t *r)
rb->buf->last += n;
r->request_length += n;
- if (n == rest) {
+ if (n == rest || n == 0) {
/* pass buffer to request body filter chain */
out.buf = rb->buf;
@@ -805,11 +804,7 @@ ngx_http_test_expect(ngx_http_request_t *r)
if (r->expect_tested
|| r->headers_in.expect == NULL
- || r->http_version < NGX_HTTP_VERSION_11
-#if (NGX_HTTP_V2)
- || r->stream != NULL
-#endif
- )
+ || r->http_version != NGX_HTTP_VERSION_11)
{
return NGX_OK;
}
@@ -914,6 +909,11 @@ ngx_http_request_body_length_filter(ngx_http_request_t *r, ngx_chain_t *in)
b->last_buf = 1;
}
+ if (cl->buf->last_buf && rb->rest > 0) {
+ /* XXX client prematurely closed connection */
+ return NGX_ERROR;
+ }
+
*ll = tl;
ll = &tl->next;
}
@@ -950,7 +950,16 @@ ngx_http_request_body_chunked_filter(ngx_http_request_t *r, ngx_chain_t *in)
}
r->headers_in.content_length_n = 0;
- rb->rest = 3;
+
+#if (NGX_HTTP_V3)
+ if (r->http_version == NGX_HTTP_VERSION_30) {
+ rb->rest = 1;
+
+ } else
+#endif
+ {
+ rb->rest = 3;
+ }
}
out = NULL;
diff --git a/src/http/v3/ngx_http_v3.c b/src/http/v3/ngx_http_v3.c
new file mode 100644
index 000000000..cca84dbc1
--- /dev/null
+++ b/src/http/v3/ngx_http_v3.c
@@ -0,0 +1,96 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+uintptr_t
+ngx_http_v3_encode_varlen_int(u_char *p, uint64_t value)
+{
+ if (value <= 63) {
+ if (p == NULL) {
+ return 1;
+ }
+
+ *p++ = value;
+ return (uintptr_t) p;
+ }
+
+ if (value <= 16383) {
+ if (p == NULL) {
+ return 2;
+ }
+
+ *p++ = 0x40 | (value >> 8);
+ *p++ = value;
+ return (uintptr_t) p;
+ }
+
+ if (value <= 1073741823) {
+ if (p == NULL) {
+ return 3;
+ }
+
+ *p++ = 0x80 | (value >> 16);
+ *p++ = (value >> 8);
+ *p++ = value;
+ return (uintptr_t) p;
+
+ }
+
+ if (p == NULL) {
+ return 4;
+ }
+
+ *p++ = 0xc0 | (value >> 24);
+ *p++ = (value >> 16);
+ *p++ = (value >> 8);
+ *p++ = value;
+ return (uintptr_t) p;
+}
+
+
+uintptr_t
+ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value, ngx_uint_t prefix)
+{
+ ngx_uint_t thresh, n;
+
+ thresh = (1 << prefix) - 1;
+
+ if (value < thresh) {
+ if (p == NULL) {
+ return 1;
+ }
+
+ *p++ |= value;
+ return (uintptr_t) p;
+ }
+
+ value -= thresh;
+
+ for (n = 10; n > 1; n--) {
+ if (value >> (7 * (n - 1))) {
+ break;
+ }
+ }
+
+ if (p == NULL) {
+ return n + 1;
+ }
+
+ *p++ |= thresh;
+
+ for ( /* void */ ; n > 1; n--) {
+ *p++ = 0x80 | (value >> 7 * (n - 1));
+ }
+
+ *p++ = value & 0x7f;
+
+ return (uintptr_t) p;
+}
diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h
new file mode 100644
index 000000000..3f35e985e
--- /dev/null
+++ b/src/http/v3/ngx_http_v3.h
@@ -0,0 +1,116 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_HTTP_V3_H_INCLUDED_
+#define _NGX_HTTP_V3_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <ngx_http_v3_parse.h>
+
+
+#define NGX_HTTP_V3_ALPN(s) NGX_HTTP_V3_ALPN_DRAFT(s)
+#define NGX_HTTP_V3_ALPN_DRAFT(s) "\x05h3-" #s
+#define NGX_HTTP_V3_ALPN_ADVERTISE NGX_HTTP_V3_ALPN(NGX_QUIC_DRAFT_VERSION)
+
+#define NGX_HTTP_V3_VARLEN_INT_LEN 4
+#define NGX_HTTP_V3_PREFIX_INT_LEN 11
+
+#define NGX_HTTP_V3_STREAM_CONTROL 0x00
+#define NGX_HTTP_V3_STREAM_PUSH 0x01
+#define NGX_HTTP_V3_STREAM_ENCODER 0x02
+#define NGX_HTTP_V3_STREAM_DECODER 0x03
+
+#define NGX_HTTP_V3_FRAME_DATA 0x00
+#define NGX_HTTP_V3_FRAME_HEADERS 0x01
+#define NGX_HTTP_V3_FRAME_CANCEL_PUSH 0x03
+#define NGX_HTTP_V3_FRAME_SETTINGS 0x04
+#define NGX_HTTP_V3_FRAME_PUSH_PROMISE 0x05
+#define NGX_HTTP_V3_FRAME_GOAWAY 0x07
+#define NGX_HTTP_V3_FRAME_MAX_PUSH_ID 0x0d
+
+#define NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY 0x01
+#define NGX_HTTP_V3_PARAM_MAX_HEADER_LIST_SIZE 0x06
+#define NGX_HTTP_V3_PARAM_BLOCKED_STREAMS 0x07
+
+#define NGX_HTTP_V3_STREAM_CLIENT_CONTROL 0
+#define NGX_HTTP_V3_STREAM_SERVER_CONTROL 1
+#define NGX_HTTP_V3_STREAM_CLIENT_ENCODER 2
+#define NGX_HTTP_V3_STREAM_SERVER_ENCODER 3
+#define NGX_HTTP_V3_STREAM_CLIENT_DECODER 4
+#define NGX_HTTP_V3_STREAM_SERVER_DECODER 5
+#define NGX_HTTP_V3_MAX_KNOWN_STREAM 6
+
+
+typedef struct {
+ ngx_quic_tp_t quic;
+} ngx_http_v3_srv_conf_t;
+
+
+typedef struct {
+ ngx_http_connection_t hc;
+
+ ngx_array_t *dynamic;
+ ngx_connection_t *known_streams[NGX_HTTP_V3_MAX_KNOWN_STREAM];
+} ngx_http_v3_connection_t;
+
+
+typedef struct {
+ ngx_str_t name;
+ ngx_str_t value;
+} ngx_http_v3_header_t;
+
+
+ngx_int_t ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b);
+ngx_int_t ngx_http_v3_parse_request_body(ngx_http_request_t *r, ngx_buf_t *b,
+ ngx_http_chunked_t *ctx);
+ngx_chain_t *ngx_http_v3_create_header(ngx_http_request_t *r);
+ngx_chain_t *ngx_http_v3_create_trailers(ngx_http_request_t *r);
+
+uintptr_t ngx_http_v3_encode_varlen_int(u_char *p, uint64_t value);
+uintptr_t ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value,
+ ngx_uint_t prefix);
+
+void ngx_http_v3_handle_client_uni_stream(ngx_connection_t *c);
+
+ngx_int_t ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic,
+ ngx_uint_t index, ngx_str_t *value);
+ngx_int_t ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name,
+ ngx_str_t *value);
+ngx_int_t ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity);
+ngx_int_t ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index);
+ngx_int_t ngx_http_v3_ack_header(ngx_connection_t *c, ngx_uint_t stream_id);
+ngx_int_t ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id);
+ngx_int_t ngx_http_v3_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc);
+ngx_http_v3_header_t *ngx_http_v3_lookup_table(ngx_connection_t *c,
+ ngx_uint_t dynamic, ngx_uint_t index);
+ngx_int_t ngx_http_v3_check_insert_count(ngx_connection_t *c,
+ ngx_uint_t insert_count);
+ngx_int_t ngx_http_v3_set_param(ngx_connection_t *c, uint64_t id,
+ uint64_t value);
+
+ngx_int_t ngx_http_v3_client_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic,
+ ngx_uint_t index, ngx_str_t *value);
+ngx_int_t ngx_http_v3_client_insert(ngx_connection_t *c, ngx_str_t *name,
+ ngx_str_t *value);
+ngx_int_t ngx_http_v3_client_set_capacity(ngx_connection_t *c,
+ ngx_uint_t capacity);
+ngx_int_t ngx_http_v3_client_duplicate(ngx_connection_t *c, ngx_uint_t index);
+ngx_int_t ngx_http_v3_client_ack_header(ngx_connection_t *c,
+ ngx_uint_t stream_id);
+ngx_int_t ngx_http_v3_client_cancel_stream(ngx_connection_t *c,
+ ngx_uint_t stream_id);
+ngx_int_t ngx_http_v3_client_inc_insert_count(ngx_connection_t *c,
+ ngx_uint_t inc);
+
+
+extern ngx_module_t ngx_http_v3_module;
+
+
+#endif /* _NGX_HTTP_V3_H_INCLUDED_ */
diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c
new file mode 100644
index 000000000..279210337
--- /dev/null
+++ b/src/http/v3/ngx_http_v3_module.c
@@ -0,0 +1,302 @@
+
+/*
+ * Copyright (C) Nginx, Inc.
+ * Copyright (C) Roman Arutyunyan
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static ngx_command_t ngx_http_v3_commands[] = {
+
+ { ngx_string("quic_max_idle_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_v3_srv_conf_t, quic.max_idle_timeout),
+ NULL },
+
+ { ngx_string("quic_max_ack_delay"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_v3_srv_conf_t, quic.max_ack_delay),
+ NULL },
+
+ { ngx_string("quic_max_packet_size"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_v3_srv_conf_t, quic.max_packet_size),
+ NULL },
+
+ { ngx_string("quic_initial_max_data"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_v3_srv_conf_t, quic.initial_max_data),
+ NULL },
+
+ { ngx_string("quic_initial_max_stream_data_bidi_local"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_v3_srv_conf_t, quic.initial_max_stream_data_bidi_local),
+ NULL },
+
+ { ngx_string("quic_initial_max_stream_data_bidi_remote"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_v3_srv_conf_t, quic.initial_max_stream_data_bidi_remote),
+ NULL },
+
+ { ngx_string("quic_initial_max_stream_data_uni"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_v3_srv_conf_t, quic.initial_max_stream_data_uni),
+ NULL },
+
+ { ngx_string("quic_initial_max_streams_bidi"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_v3_srv_conf_t, quic.initial_max_streams_bidi),
+ NULL },
+
+ { ngx_string("quic_initial_max_streams_uni"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_v3_srv_conf_t, quic.initial_max_streams_uni),
+ NULL },
+
+ { ngx_string("quic_ack_delay_exponent"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_v3_srv_conf_t, quic.ack_delay_exponent),
+ NULL },
+
+ { ngx_string("quic_active_migration"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_v3_srv_conf_t, quic.disable_active_migration),
+ NULL },
+
+ { ngx_string("quic_active_connection_id_limit"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_v3_srv_conf_t, quic.active_connection_id_limit),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_int_t ngx_http_variable_quic(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_http3(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_v3_add_variables(ngx_conf_t *cf);
+static void *ngx_http_v3_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_http_v3_merge_srv_conf(ngx_conf_t *cf,
+ void *parent, void *child);
+
+
+static ngx_http_module_t ngx_http_v3_module_ctx = {
+ ngx_http_v3_add_variables, /* preconfiguration */
+ NULL, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ ngx_http_v3_create_srv_conf, /* create server configuration */
+ ngx_http_v3_merge_srv_conf, /* merge server configuration */
+
+ NULL, /* create location configuration */
+ NULL /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_v3_module = {
+ NGX_MODULE_V1,
+ &ngx_http_v3_module_ctx, /* module context */
+ ngx_http_v3_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_variable_t ngx_http_v3_vars[] = {
+ { ngx_string("quic"), NULL, ngx_http_variable_quic,
+ 0, 0, 0 },
+
+ { ngx_string("http3"), NULL, ngx_http_variable_http3,
+ 0, 0, 0 },
+
+ ngx_http_null_variable
+};
+
+
+static ngx_int_t
+ngx_http_variable_quic(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ if (r->connection->qs) {
+
+ v->len = 4;
+ v->valid = 1;
+ v->no_cacheable = 1;
+ v->not_found = 0;
+ v->data = (u_char *) "quic";
+ return NGX_OK;
+ }
+
+ v->not_found = 1;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_http3(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ v->valid = 1;
+ v->no_cacheable = 1;
+ v->not_found = 0;
+
+ v->data = ngx_pnalloc(r->pool, sizeof("h3-xx") - 1);
+ if (v->data == NULL) {
+ return NGX_ERROR;
+ }
+
+ v->len = ngx_sprintf(v->data, "h3-%d", NGX_QUIC_DRAFT_VERSION) - v->data;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v3_add_variables(ngx_conf_t *cf)
+{
+ ngx_http_variable_t *var, *v;
+
+ for (v = ngx_http_v3_vars; v->name.len; v++) {
+ var = ngx_http_add_variable(cf, &v->name, v->flags);
+ if (var == NULL) {
+ return NGX_ERROR;
+ }
+
+ var->get_handler = v->get_handler;
+ var->data = v->data;
+ }
+
+ return NGX_OK;
+}
+
+
+
+static void *
+ngx_http_v3_create_srv_conf(ngx_conf_t *cf)
+{
+ ngx_http_v3_srv_conf_t *v3cf;
+
+ v3cf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v3_srv_conf_t));
+ if (v3cf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ * v3cf->quic.original_connection_id = 0;
+ * v3cf->quic.stateless_reset_token = { 0 }
+ * conf->quic.preferred_address = NULL
+ */
+
+ v3cf->quic.max_idle_timeout = NGX_CONF_UNSET_MSEC;
+ v3cf->quic.max_ack_delay = NGX_CONF_UNSET_MSEC;
+
+ v3cf->quic.max_packet_size = NGX_CONF_UNSET_UINT;
+ v3cf->quic.initial_max_data = NGX_CONF_UNSET_UINT;
+ v3cf->quic.initial_max_stream_data_bidi_local = NGX_CONF_UNSET_UINT;
+ v3cf->quic.initial_max_stream_data_bidi_remote = NGX_CONF_UNSET_UINT;
+ v3cf->quic.initial_max_stream_data_uni = NGX_CONF_UNSET_UINT;
+ v3cf->quic.initial_max_streams_bidi = NGX_CONF_UNSET_UINT;
+ v3cf->quic.initial_max_streams_uni = NGX_CONF_UNSET_UINT;
+ v3cf->quic.ack_delay_exponent = NGX_CONF_UNSET_UINT;
+ v3cf->quic.disable_active_migration = NGX_CONF_UNSET_UINT;
+ v3cf->quic.active_connection_id_limit = NGX_CONF_UNSET_UINT;
+
+ return v3cf;
+}
+
+
+static char *
+ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_v3_srv_conf_t *prev = parent;
+ ngx_http_v3_srv_conf_t *conf = child;
+
+ ngx_conf_merge_msec_value(conf->quic.max_idle_timeout,
+ prev->quic.max_idle_timeout, 60000);
+
+ // > 2 ^ 14 is invalid
+ ngx_conf_merge_msec_value(conf->quic.max_ack_delay,
+ prev->quic.max_ack_delay,
+ NGX_QUIC_DEFAULT_MAX_ACK_DELAY);
+
+ // < 1200 is invalid
+ ngx_conf_merge_uint_value(conf->quic.max_packet_size,
+ prev->quic.max_packet_size,
+ NGX_QUIC_DEFAULT_MAX_PACKET_SIZE);
+
+ ngx_conf_merge_uint_value(conf->quic.initial_max_data,
+ prev->quic.initial_max_data, 10000000);
+
+ ngx_conf_merge_uint_value(conf->quic.initial_max_stream_data_bidi_local,
+ prev->quic.initial_max_stream_data_bidi_local,
+ 255);
+
+ ngx_conf_merge_uint_value(conf->quic.initial_max_stream_data_bidi_remote,
+ prev->quic.initial_max_stream_data_bidi_remote,
+ 255);
+
+ ngx_conf_merge_uint_value(conf->quic.initial_max_stream_data_uni,
+ prev->quic.initial_max_stream_data_uni, 255);
+
+ ngx_conf_merge_uint_value(conf->quic.initial_max_streams_bidi,
+ prev->quic.initial_max_streams_bidi, 16);
+
+ ngx_conf_merge_uint_value(conf->quic.initial_max_streams_uni,
+ prev->quic.initial_max_streams_uni, 16);
+
+ // > 20 is invalid
+ ngx_conf_merge_uint_value(conf->quic.ack_delay_exponent,
+ prev->quic.ack_delay_exponent,
+ NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT);
+
+ ngx_conf_merge_uint_value(conf->quic.disable_active_migration,
+ prev->quic.disable_active_migration, 1);
+
+ // < 2 is invalid
+ ngx_conf_merge_uint_value(conf->quic.active_connection_id_limit,
+ prev->quic.active_connection_id_limit, 2);
+
+ return NGX_CONF_OK;
+}
+
diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c
new file mode 100644
index 000000000..3be3802ed
--- /dev/null
+++ b/src/http/v3/ngx_http_v3_parse.c
@@ -0,0 +1,1481 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+ngx_int_t
+ngx_http_v3_parse_varlen_int(ngx_connection_t *c,
+ ngx_http_v3_parse_varlen_int_t *st, u_char ch)
+{
+ enum {
+ sw_start = 0,
+ sw_length_2,
+ sw_length_3,
+ sw_length_4,
+ sw_length_5,
+ sw_length_6,
+ sw_length_7,
+ sw_length_8
+ };
+
+ switch (st->state) {
+
+ case sw_start:
+
+ st->value = ch;
+ if (st->value & 0xc0) {
+ st->state = sw_length_2;
+ break;
+ }
+
+ goto done;
+
+ case sw_length_2:
+
+ st->value = (st->value << 8) + ch;
+ if ((st->value & 0xc000) == 0x4000) {
+ st->value &= 0x3fff;
+ goto done;
+ }
+
+ st->state = sw_length_3;
+ break;
+
+ case sw_length_4:
+
+ st->value = (st->value << 8) + ch;
+ if ((st->value & 0xc0000000) == 0x80000000) {
+ st->value &= 0x3fffffff;
+ goto done;
+ }
+
+ st->state = sw_length_5;
+ break;
+
+ case sw_length_3:
+ case sw_length_5:
+ case sw_length_6:
+ case sw_length_7:
+
+ st->value = (st->value << 8) + ch;
+ st->state++;
+ break;
+
+ case sw_length_8:
+
+ st->value = (st->value << 8) + ch;
+ st->value &= 0x3fffffffffffffff;
+ goto done;
+ }
+
+ return NGX_AGAIN;
+
+done:
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse varlen int %uL", st->value);
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_prefix_int(ngx_connection_t *c,
+ ngx_http_v3_parse_prefix_int_t *st, ngx_uint_t prefix, u_char ch)
+{
+ enum {
+ sw_start = 0,
+ sw_value
+ };
+
+ switch (st->state) {
+
+ case sw_start:
+
+ st->mask = (1 << prefix) - 1;
+ st->value = (ch & st->mask);
+
+ if (st->value != st->mask) {
+ goto done;
+ }
+
+ st->value = 0;
+ st->state = sw_value;
+ break;
+
+ case sw_value:
+
+ st->value = (st->value << 7) + (ch & 0x7f);
+ if (ch & 0x80) {
+ break;
+ }
+
+ st->value += st->mask;
+ goto done;
+ }
+
+ return NGX_AGAIN;
+
+done:
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse prefix int %uL", st->value);
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st,
+ u_char ch)
+{
+ ngx_int_t rc;
+ enum {
+ sw_start = 0,
+ sw_length,
+ sw_prefix,
+ sw_header_rep,
+ sw_done
+ };
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse headers");
+
+ if (ch != NGX_HTTP_V3_FRAME_HEADERS) {
+ return NGX_ERROR;
+ }
+
+ st->state = sw_length;
+ break;
+
+ case sw_length:
+
+ if (ngx_http_v3_parse_varlen_int(c, &st->vlint, ch) != NGX_DONE) {
+ break;
+ }
+
+ st->length = st->vlint.value;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse headers len:%ui", st->length);
+
+ st->state = sw_prefix;
+ break;
+
+ case sw_prefix:
+
+ if (st->length-- == 0) {
+ return NGX_ERROR;
+ }
+
+ rc = ngx_http_v3_parse_header_block_prefix(c, &st->prefix, ch);
+
+ if (rc == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (rc != NGX_DONE) {
+ break;
+ }
+
+ if (st->length == 0) {
+ return NGX_ERROR;
+ }
+
+ st->state = sw_header_rep;
+ break;
+
+ case sw_header_rep:
+
+ rc = ngx_http_v3_parse_header_rep(c, &st->header_rep, st->prefix.base,
+ ch);
+
+ if (rc == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (--st->length == 0) {
+ if (rc != NGX_DONE) {
+ return NGX_ERROR;
+ }
+
+ goto done;
+ }
+
+ if (rc == NGX_DONE) {
+ return NGX_OK;
+ }
+
+ break;
+ }
+
+ return NGX_AGAIN;
+
+done:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse headers done");
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_header_block_prefix(ngx_connection_t *c,
+ ngx_http_v3_parse_header_block_prefix_t *st, u_char ch)
+{
+ enum {
+ sw_start = 0,
+ sw_req_insert_count,
+ sw_delta_base,
+ sw_read_delta_base
+ };
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse header block prefix");
+
+ st->state = sw_req_insert_count;
+
+ /* fall through */
+
+ case sw_req_insert_count:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 8, ch) != NGX_DONE) {
+ break;
+ }
+
+ st->insert_count = st->pint.value;
+ st->state = sw_delta_base;
+ break;
+
+ case sw_delta_base:
+
+ st->sign = (ch & 0x80) ? 1 : 0;
+ st->state = sw_read_delta_base;
+
+ /* fall through */
+
+ case sw_read_delta_base:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch) != NGX_DONE) {
+ break;
+ }
+
+ st->delta_base = st->pint.value;
+ goto done;
+ }
+
+ return NGX_AGAIN;
+
+done:
+
+ if (st->sign) {
+ st->base = st->insert_count - st->delta_base - 1;
+ } else {
+ st->base = st->insert_count + st->delta_base;
+ }
+
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse header block prefix done "
+ "i:%ui, s:%ui, d:%ui, base:%uL",
+ st->insert_count, st->sign, st->delta_base, st->base);
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_header_rep(ngx_connection_t *c,
+ ngx_http_v3_parse_header_rep_t *st, ngx_uint_t base, u_char ch)
+{
+ ngx_int_t rc;
+ enum {
+ sw_start = 0,
+ sw_header_ri,
+ sw_header_lri,
+ sw_header_l,
+ sw_header_pbi,
+ sw_header_lpbi
+ };
+
+ if (st->state == sw_start) {
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse header representation");
+
+ ngx_memzero(&st->header, sizeof(ngx_http_v3_parse_header_t));
+
+ st->header.base = base;
+
+ if (ch & 0x80) {
+ /* Indexed Header Field */
+
+ st->state = sw_header_ri;
+
+ } else if (ch & 0x40) {
+ /* Literal Header Field With Name Reference */
+
+ st->state = sw_header_lri;
+
+ } else if (ch & 0x20) {
+ /* Literal Header Field Without Name Reference */
+
+ st->state = sw_header_l;
+
+ } else if (ch & 0x10) {
+ /* Indexed Header Field With Post-Base Index */
+
+ st->state = sw_header_pbi;
+
+ } else {
+ /* Literal Header Field With Post-Base Name Reference */
+
+ st->state = sw_header_lpbi;
+ }
+ }
+
+ switch (st->state) {
+
+ case sw_header_ri:
+ rc = ngx_http_v3_parse_header_ri(c, &st->header, ch);
+ break;
+
+ case sw_header_lri:
+ rc = ngx_http_v3_parse_header_lri(c, &st->header, ch);
+ break;
+
+ case sw_header_l:
+ rc = ngx_http_v3_parse_header_l(c, &st->header, ch);
+ break;
+
+ case sw_header_pbi:
+ rc = ngx_http_v3_parse_header_pbi(c, &st->header, ch);
+ break;
+
+ case sw_header_lpbi:
+ rc = ngx_http_v3_parse_header_lpbi(c, &st->header, ch);
+ break;
+
+ default:
+ rc = NGX_OK;
+ }
+
+ if (rc == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (rc == NGX_AGAIN) {
+ return NGX_AGAIN;
+ }
+
+ /* rc == NGX_DONE */
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse header representation done");
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_literal(ngx_connection_t *c, ngx_http_v3_parse_literal_t *st,
+ u_char ch)
+{
+ ngx_uint_t n;
+ enum {
+ sw_start = 0,
+ sw_value
+ };
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse literal huff:%ui, len:%ui",
+ st->huffman, st->length);
+
+ n = st->length;
+
+ if (st->huffman) {
+ n = n * 8 / 5;
+ st->huffstate = 0;
+ }
+
+ st->last = ngx_pnalloc(c->pool, n + 1);
+ if (st->last == NULL) {
+ return NGX_ERROR;
+ }
+
+ st->value.data = st->last;
+ st->state = sw_value;
+
+ /* fall through */
+
+ case sw_value:
+
+ if (st->huffman) {
+ if (ngx_http_v2_huff_decode(&st->huffstate, &ch, 1, &st->last,
+ st->length == 1, c->log)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ } else {
+ *st->last++ = ch;
+ }
+
+ if (--st->length) {
+ break;
+ }
+
+ st->value.len = st->last - st->value.data;
+ *st->last = '\0';
+ goto done;
+ }
+
+ return NGX_AGAIN;
+
+done:
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse literal done \"%V\"", &st->value);
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_header_ri(ngx_connection_t *c, ngx_http_v3_parse_header_t *st,
+ u_char ch)
+{
+ ngx_http_v3_header_t *h;
+ enum {
+ sw_start = 0,
+ sw_index
+ };
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header ri");
+
+ st->dynamic = (ch & 0x40) ? 0 : 1;
+ st->state = sw_index;
+
+ /* fall through */
+
+ case sw_index:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 6, ch) != NGX_DONE) {
+ break;
+ }
+
+ st->index = st->pint.value;
+ goto done;
+ }
+
+ return NGX_AGAIN;
+
+done:
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse header ri done %s%ui]",
+ st->dynamic ? "dynamic[-" : "static[", st->index);
+
+ if (st->dynamic) {
+ st->index = st->base - st->index - 1;
+ }
+
+ h = ngx_http_v3_lookup_table(c, st->dynamic, st->index);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ st->name = h->name;
+ st->value = h->value;
+ st->state = sw_start;
+
+ return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_header_lri(ngx_connection_t *c,
+ ngx_http_v3_parse_header_t *st, u_char ch)
+{
+ ngx_int_t rc;
+ ngx_http_v3_header_t *h;
+ enum {
+ sw_start = 0,
+ sw_index,
+ sw_value_len,
+ sw_read_value_len,
+ sw_value
+ };
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header lri");
+
+ st->dynamic = (ch & 0x10) ? 0 : 1;
+ st->state = sw_index;
+
+ /* fall through */
+
+ case sw_index:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 4, ch) != NGX_DONE) {
+ break;
+ }
+
+ st->index = st->pint.value;
+ st->state = sw_value_len;
+ break;
+
+ case sw_value_len:
+
+ st->literal.huffman = (ch & 0x80) ? 1 : 0;
+ st->state = sw_read_value_len;
+
+ /* fall through */
+
+ case sw_read_value_len:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch) != NGX_DONE) {
+ break;
+ }
+
+ st->literal.length = st->pint.value;
+ if (st->literal.length == 0) {
+ goto done;
+ }
+
+ st->state = sw_value;
+ break;
+
+ case sw_value:
+
+ rc = ngx_http_v3_parse_literal(c, &st->literal, ch);
+
+ if (rc == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (rc == NGX_DONE) {
+ st->value = st->literal.value;
+ goto done;
+ }
+
+ break;
+ }
+
+ return NGX_AGAIN;
+
+done:
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse header lri done %s%ui] \"%V\"",
+ st->dynamic ? "dynamic[-" : "static[",
+ st->index, &st->value);
+
+ if (st->dynamic) {
+ st->index = st->base - st->index - 1;
+ }
+
+ h = ngx_http_v3_lookup_table(c, st->dynamic, st->index);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ st->name = h->name;
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_header_l(ngx_connection_t *c,
+ ngx_http_v3_parse_header_t *st, u_char ch)
+{
+ ngx_int_t rc;
+ enum {
+ sw_start = 0,
+ sw_name_len,
+ sw_name,
+ sw_value_len,
+ sw_read_value_len,
+ sw_value
+ };
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header l");
+
+ st->literal.huffman = (ch & 0x08) ? 1 : 0;
+ st->state = sw_name_len;
+
+ /* fall through */
+
+ case sw_name_len:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 3, ch) != NGX_DONE) {
+ break;
+ }
+
+ st->literal.length = st->pint.value;
+ if (st->literal.length == 0) {
+ return NGX_ERROR;
+ }
+
+ st->state = sw_name;
+ break;
+
+ case sw_name:
+
+ rc = ngx_http_v3_parse_literal(c, &st->literal, ch);
+
+ if (rc == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (rc == NGX_DONE) {
+ st->name = st->literal.value;
+ st->state = sw_value_len;
+ }
+
+ break;
+
+ case sw_value_len:
+
+ st->literal.huffman = (ch & 0x80) ? 1 : 0;
+ st->state = sw_read_value_len;
+
+ /* fall through */
+
+ case sw_read_value_len:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch) != NGX_DONE) {
+ break;
+ }
+
+ st->literal.length = st->pint.value;
+ if (st->literal.length == 0) {
+ goto done;
+ }
+
+ st->state = sw_value;
+ break;
+
+ case sw_value:
+
+ rc = ngx_http_v3_parse_literal(c, &st->literal, ch);
+
+ if (rc == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (rc == NGX_DONE) {
+ st->value = st->literal.value;
+ goto done;
+ }
+
+ break;
+ }
+
+ return NGX_AGAIN;
+
+done:
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse header l done \"%V\" \"%V\"",
+ &st->name, &st->value);
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_header_pbi(ngx_connection_t *c,
+ ngx_http_v3_parse_header_t *st, u_char ch)
+{
+ ngx_http_v3_header_t *h;
+ enum {
+ sw_start = 0,
+ sw_index
+ };
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header pbi");
+
+ st->state = sw_index;
+
+ /* fall through */
+
+ case sw_index:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 4, ch) != NGX_DONE) {
+ break;
+ }
+
+ st->index = st->pint.value;
+ goto done;
+ }
+
+ return NGX_AGAIN;
+
+done:
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse header pbi done dynamic[+%ui]", st->index);
+
+ h = ngx_http_v3_lookup_table(c, 1, st->base + st->index);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ st->name = h->name;
+ st->value = h->value;
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_header_lpbi(ngx_connection_t *c,
+ ngx_http_v3_parse_header_t *st, u_char ch)
+{
+ ngx_int_t rc;
+ ngx_http_v3_header_t *h;
+ enum {
+ sw_start = 0,
+ sw_index,
+ sw_value_len,
+ sw_read_value_len,
+ sw_value
+ };
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse header lpbi");
+
+ st->state = sw_index;
+
+ /* fall through */
+
+ case sw_index:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 3, ch) != NGX_DONE) {
+ break;
+ }
+
+ st->index = st->pint.value;
+ st->state = sw_value_len;
+ break;
+
+ case sw_value_len:
+
+ st->literal.huffman = (ch & 0x80) ? 1 : 0;
+ st->state = sw_read_value_len;
+
+ /* fall through */
+
+ case sw_read_value_len:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch) != NGX_DONE) {
+ break;
+ }
+
+ st->literal.length = st->pint.value;
+ if (st->literal.length == 0) {
+ goto done;
+ }
+
+ st->state = sw_value;
+ break;
+
+ case sw_value:
+
+ rc = ngx_http_v3_parse_literal(c, &st->literal, ch);
+
+ if (rc == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (rc == NGX_DONE) {
+ st->value = st->literal.value;
+ goto done;
+ }
+
+ break;
+ }
+
+ return NGX_AGAIN;
+
+done:
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse header lpbi done dynamic[+%ui] \"%V\"",
+ st->index, &st->value);
+
+ h = ngx_http_v3_lookup_table(c, 1, st->base + st->index);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ st->name = h->name;
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_control(ngx_connection_t *c, void *data, u_char ch)
+{
+ ngx_http_v3_parse_control_t *st = data;
+
+ ngx_int_t rc;
+ enum {
+ sw_start = 0,
+ sw_type,
+ sw_length,
+ sw_settings,
+ sw_max_push_id,
+ sw_skip
+ };
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse control");
+
+ st->state = sw_type;
+
+ /* fall through */
+
+ case sw_type:
+
+ if (ngx_http_v3_parse_varlen_int(c, &st->vlint, ch) != NGX_DONE) {
+ break;
+ }
+
+ st->type = st->vlint.value;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse frame type:%ui", st->type);
+
+ st->state = sw_length;
+ break;
+
+ case sw_length:
+
+ if (ngx_http_v3_parse_varlen_int(c, &st->vlint, ch) != NGX_DONE) {
+ break;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse frame len:%uL", st->vlint.value);
+
+ st->length = st->vlint.value;
+ if (st->length == 0) {
+ st->state = sw_type;
+ break;
+ }
+
+ switch (st->type) {
+
+ case NGX_HTTP_V3_FRAME_SETTINGS:
+ st->state = sw_settings;
+ break;
+
+ case NGX_HTTP_V3_FRAME_MAX_PUSH_ID:
+ st->state = sw_max_push_id;
+ break;
+
+ default:
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse skip unknown frame");
+ st->state = sw_skip;
+ }
+
+ break;
+
+ case sw_settings:
+
+ rc = ngx_http_v3_parse_settings(c, &st->settings, ch);
+
+ if (rc == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (--st->length > 0) {
+ break;
+ }
+
+ if (rc != NGX_DONE) {
+ return NGX_ERROR;
+ }
+
+ st->state = sw_type;
+ break;
+
+ case sw_max_push_id:
+
+ if (ngx_http_v3_parse_varlen_int(c, &st->vlint, ch) != NGX_DONE) {
+ break;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse MAX_PUSH_ID:%uL", st->vlint.value);
+
+ st->state = sw_type;
+ break;
+
+ case sw_skip:
+
+ if (--st->length == 0) {
+ st->state = sw_type;
+ }
+
+ break;
+ }
+
+ return NGX_AGAIN;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_settings(ngx_connection_t *c,
+ ngx_http_v3_parse_settings_t *st, u_char ch)
+{
+ enum {
+ sw_start = 0,
+ sw_id,
+ sw_value
+ };
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse settings");
+
+ st->state = sw_id;
+
+ /* fall through */
+
+ case sw_id:
+
+ if (ngx_http_v3_parse_varlen_int(c, &st->vlint, ch) != NGX_DONE) {
+ break;
+ }
+
+ st->id = st->vlint.value;
+ st->state = sw_value;
+ break;
+
+ case sw_value:
+
+ if (ngx_http_v3_parse_varlen_int(c, &st->vlint, ch) != NGX_DONE) {
+ break;
+ }
+
+ if (ngx_http_v3_set_param(c, st->id, st->vlint.value) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ goto done;
+ }
+
+ return NGX_AGAIN;
+
+done:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse settings done");
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_encoder(ngx_connection_t *c, void *data, u_char ch)
+{
+ ngx_http_v3_parse_encoder_t *st = data;
+
+ ngx_int_t rc;
+ enum {
+ sw_start = 0,
+ sw_inr,
+ sw_iwnr,
+ sw_capacity,
+ sw_duplicate
+ };
+
+ if (st->state == sw_start) {
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse encoder instruction");
+
+ if (ch & 0x80) {
+ /* Insert With Name Reference */
+
+ st->state = sw_inr;
+
+ } else if (ch & 0x40) {
+ /* Insert Without Name Reference */
+
+ st->state = sw_iwnr;
+
+ } else if (ch & 0x20) {
+ /* Set Dynamic Table Capacity */
+
+ st->state = sw_capacity;
+
+ } else {
+ /* Duplicate */
+
+ st->state = sw_duplicate;
+ }
+ }
+
+ switch (st->state) {
+
+ case sw_inr:
+
+ rc = ngx_http_v3_parse_header_inr(c, &st->header, ch);
+
+ if (rc == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (rc != NGX_DONE) {
+ break;
+ }
+
+ goto done;
+
+ case sw_iwnr:
+
+ rc = ngx_http_v3_parse_header_iwnr(c, &st->header, ch);
+
+ if (rc == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (rc != NGX_DONE) {
+ break;
+ }
+
+ goto done;
+
+ case sw_capacity:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 5, ch) != NGX_DONE) {
+ break;
+ }
+
+ if (ngx_http_v3_set_capacity(c, st->pint.value) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ goto done;
+
+ case sw_duplicate:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 5, ch) != NGX_DONE) {
+ break;
+ }
+
+ if (ngx_http_v3_duplicate(c, st->pint.value) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ goto done;
+ }
+
+ return NGX_AGAIN;
+
+done:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse encoder instruction done");
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_header_inr(ngx_connection_t *c,
+ ngx_http_v3_parse_header_t *st, u_char ch)
+{
+ ngx_int_t rc;
+ enum {
+ sw_start = 0,
+ sw_name_index,
+ sw_value_len,
+ sw_read_value_len,
+ sw_value
+ };
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header inr");
+
+ st->dynamic = (ch & 0x40) ? 0 : 1;
+ st->state = sw_name_index;
+
+ /* fall through */
+
+ case sw_name_index:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 6, ch) != NGX_DONE) {
+ break;
+ }
+
+ st->index = st->pint.value;
+ st->state = sw_value_len;
+ break;
+
+ case sw_value_len:
+
+ st->literal.huffman = (ch & 0x80) ? 1 : 0;
+ st->state = sw_read_value_len;
+
+ /* fall through */
+
+ case sw_read_value_len:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch) != NGX_DONE) {
+ break;
+ }
+
+ st->literal.length = st->pint.value;
+ if (st->literal.length == 0) {
+ goto done;
+ }
+
+ st->state = sw_value;
+ break;
+
+ case sw_value:
+
+ rc = ngx_http_v3_parse_literal(c, &st->literal, ch);
+
+ if (rc == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (rc == NGX_DONE) {
+ st->value = st->literal.value;
+ goto done;
+ }
+
+ break;
+ }
+
+ return NGX_AGAIN;
+
+done:
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse header inr done %s[%ui] \"%V\"",
+ st->dynamic ? "dynamic" : "static",
+ st->index, &st->value);
+
+ if (ngx_http_v3_ref_insert(c, st->dynamic, st->index, &st->value) != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_header_iwnr(ngx_connection_t *c,
+ ngx_http_v3_parse_header_t *st, u_char ch)
+{
+ ngx_int_t rc;
+ enum {
+ sw_start = 0,
+ sw_name_len,
+ sw_name,
+ sw_value_len,
+ sw_read_value_len,
+ sw_value
+ };
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse header iwnr");
+
+ st->literal.huffman = (ch & 0x20) ? 1 : 0;
+ st->state = sw_name_len;
+
+ /* fall through */
+
+ case sw_name_len:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 5, ch) != NGX_DONE) {
+ break;
+ }
+
+ st->literal.length = st->pint.value;
+ if (st->literal.length == 0) {
+ return NGX_ERROR;
+ }
+
+ st->state = sw_name;
+ break;
+
+ case sw_name:
+
+ rc = ngx_http_v3_parse_literal(c, &st->literal, ch);
+
+ if (rc == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (rc == NGX_DONE) {
+ st->name = st->literal.value;
+ st->state = sw_value_len;
+ }
+
+ break;
+
+ case sw_value_len:
+
+ st->literal.huffman = (ch & 0x80) ? 1 : 0;
+ st->state = sw_read_value_len;
+
+ /* fall through */
+
+ case sw_read_value_len:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch) != NGX_DONE) {
+ break;
+ }
+
+ st->literal.length = st->pint.value;
+ if (st->literal.length == 0) {
+ goto done;
+ }
+
+ st->state = sw_value;
+ break;
+
+ case sw_value:
+
+ rc = ngx_http_v3_parse_literal(c, &st->literal, ch);
+
+ if (rc == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (rc == NGX_DONE) {
+ st->value = st->literal.value;
+ goto done;
+ }
+
+ break;
+ }
+
+ return NGX_AGAIN;
+
+done:
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse header iwnr done \"%V\":\"%V\"",
+ &st->name, &st->value);
+
+ if (ngx_http_v3_insert(c, &st->name, &st->value) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_decoder(ngx_connection_t *c, void *data, u_char ch)
+{
+ ngx_http_v3_parse_decoder_t *st = data;
+
+ enum {
+ sw_start = 0,
+ sw_ack_header,
+ sw_cancel_stream,
+ sw_inc_insert_count
+ };
+
+ if (st->state == sw_start) {
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse decoder instruction");
+
+ if (ch & 0x80) {
+ /* Header Acknowledgement */
+
+ st->state = sw_ack_header;
+
+ } else if (ch & 0x40) {
+ /* Stream Cancellation */
+
+ st->state = sw_cancel_stream;
+
+ } else {
+ /* Insert Count Increment */
+
+ st->state = sw_inc_insert_count;
+ }
+ }
+
+ switch (st->state) {
+
+ case sw_ack_header:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 6, ch) != NGX_DONE) {
+ break;
+ }
+
+ if (ngx_http_v3_ack_header(c, st->pint.value) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ goto done;
+
+ case sw_cancel_stream:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 6, ch) != NGX_DONE) {
+ break;
+ }
+
+ if (ngx_http_v3_cancel_stream(c, st->pint.value) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ goto done;
+
+ case sw_inc_insert_count:
+
+ if (ngx_http_v3_parse_prefix_int(c, &st->pint, 6, ch) != NGX_DONE) {
+ break;
+ }
+
+ if (ngx_http_v3_inc_insert_count(c, st->pint.value) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ goto done;
+ }
+
+ return NGX_AGAIN;
+
+done:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse decoder instruction done");
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_data(ngx_connection_t *c, ngx_http_v3_parse_data_t *st,
+ u_char ch)
+{
+ enum {
+ sw_start = 0,
+ sw_type,
+ sw_length
+ };
+
+ switch (st->state) {
+
+ case sw_start:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse data");
+
+ st->state = sw_type;
+
+ /* fall through */
+
+ case sw_type:
+
+ if (ngx_http_v3_parse_varlen_int(c, &st->vlint, ch) != NGX_DONE) {
+ break;
+ }
+
+ if (st->vlint.value != NGX_HTTP_V3_FRAME_DATA) {
+ return NGX_ERROR;
+ }
+
+ st->state = sw_length;
+ break;
+
+ case sw_length:
+
+ if (ngx_http_v3_parse_varlen_int(c, &st->vlint, ch) != NGX_DONE) {
+ break;
+ }
+
+ st->length = st->vlint.value;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse data frame len:%ui", st->length);
+
+ goto done;
+ }
+
+ return NGX_AGAIN;
+
+done:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse data done");
+
+ st->state = sw_start;
+ return NGX_DONE;
+}
diff --git a/src/http/v3/ngx_http_v3_parse.h b/src/http/v3/ngx_http_v3_parse.h
new file mode 100644
index 000000000..ec78c7c35
--- /dev/null
+++ b/src/http/v3/ngx_http_v3_parse.h
@@ -0,0 +1,155 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_HTTP_V3_PARSE_H_INCLUDED_
+#define _NGX_HTTP_V3_PARSE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+ ngx_uint_t state;
+ uint64_t value;
+} ngx_http_v3_parse_varlen_int_t;
+
+
+typedef struct {
+ ngx_uint_t state;
+ ngx_uint_t mask;
+ uint64_t value;
+} ngx_http_v3_parse_prefix_int_t;
+
+
+typedef struct {
+ ngx_uint_t state;
+ uint64_t id;
+ ngx_http_v3_parse_varlen_int_t vlint;
+} ngx_http_v3_parse_settings_t;
+
+
+typedef struct {
+ ngx_uint_t state;
+ ngx_uint_t insert_count;
+ ngx_uint_t delta_base;
+ ngx_uint_t sign;
+ ngx_uint_t base;
+ ngx_http_v3_parse_prefix_int_t pint;
+} ngx_http_v3_parse_header_block_prefix_t;
+
+
+typedef struct {
+ ngx_uint_t state;
+ ngx_uint_t length;
+ ngx_uint_t huffman;
+ ngx_str_t value;
+ u_char *last;
+ u_char huffstate;
+} ngx_http_v3_parse_literal_t;
+
+
+typedef struct {
+ ngx_uint_t state;
+ ngx_uint_t index;
+ ngx_uint_t base;
+ ngx_uint_t dynamic;
+
+ ngx_str_t name;
+ ngx_str_t value;
+
+ ngx_http_v3_parse_prefix_int_t pint;
+ ngx_http_v3_parse_literal_t literal;
+} ngx_http_v3_parse_header_t;
+
+
+typedef struct {
+ ngx_uint_t state;
+ ngx_http_v3_parse_header_t header;
+} ngx_http_v3_parse_header_rep_t;
+
+
+typedef struct {
+ ngx_uint_t state;
+ ngx_uint_t length;
+ ngx_http_v3_parse_varlen_int_t vlint;
+ ngx_http_v3_parse_header_block_prefix_t prefix;
+ ngx_http_v3_parse_header_rep_t header_rep;
+} ngx_http_v3_parse_headers_t;
+
+
+typedef struct {
+ ngx_uint_t state;
+ ngx_http_v3_parse_header_t header;
+ ngx_http_v3_parse_prefix_int_t pint;
+} ngx_http_v3_parse_encoder_t;
+
+
+typedef struct {
+ ngx_uint_t state;
+ ngx_http_v3_parse_prefix_int_t pint;
+} ngx_http_v3_parse_decoder_t;
+
+
+typedef struct {
+ ngx_uint_t state;
+ ngx_uint_t type;
+ ngx_uint_t length;
+ ngx_http_v3_parse_varlen_int_t vlint;
+ ngx_http_v3_parse_settings_t settings;
+} ngx_http_v3_parse_control_t;
+
+
+typedef struct {
+ ngx_uint_t state;
+ ngx_uint_t length;
+ ngx_http_v3_parse_varlen_int_t vlint;
+} ngx_http_v3_parse_data_t;
+
+
+ngx_int_t ngx_http_v3_parse_varlen_int(ngx_connection_t *c,
+ ngx_http_v3_parse_varlen_int_t *st, u_char ch);
+ngx_int_t ngx_http_v3_parse_prefix_int(ngx_connection_t *c,
+ ngx_http_v3_parse_prefix_int_t *st, ngx_uint_t prefix, u_char ch);
+
+ngx_int_t ngx_http_v3_parse_headers(ngx_connection_t *c,
+ ngx_http_v3_parse_headers_t *st, u_char ch);
+ngx_int_t ngx_http_v3_parse_header_block_prefix(ngx_connection_t *c,
+ ngx_http_v3_parse_header_block_prefix_t *st, u_char ch);
+ngx_int_t ngx_http_v3_parse_header_rep(ngx_connection_t *c,
+ ngx_http_v3_parse_header_rep_t *st, ngx_uint_t base, u_char ch);
+ngx_int_t ngx_http_v3_parse_literal(ngx_connection_t *c,
+ ngx_http_v3_parse_literal_t *st, u_char ch);
+ngx_int_t ngx_http_v3_parse_header_ri(ngx_connection_t *c,
+ ngx_http_v3_parse_header_t *st, u_char ch);
+ngx_int_t ngx_http_v3_parse_header_lri(ngx_connection_t *c,
+ ngx_http_v3_parse_header_t *st, u_char ch);
+ngx_int_t ngx_http_v3_parse_header_l(ngx_connection_t *c,
+ ngx_http_v3_parse_header_t *st, u_char ch);
+ngx_int_t ngx_http_v3_parse_header_pbi(ngx_connection_t *c,
+ ngx_http_v3_parse_header_t *st, u_char ch);
+ngx_int_t ngx_http_v3_parse_header_lpbi(ngx_connection_t *c,
+ ngx_http_v3_parse_header_t *st, u_char ch);
+
+ngx_int_t ngx_http_v3_parse_control(ngx_connection_t *c, void *data, u_char ch);
+ngx_int_t ngx_http_v3_parse_settings(ngx_connection_t *c,
+ ngx_http_v3_parse_settings_t *st, u_char ch);
+
+ngx_int_t ngx_http_v3_parse_encoder(ngx_connection_t *c, void *data, u_char ch);
+ngx_int_t ngx_http_v3_parse_header_inr(ngx_connection_t *c,
+ ngx_http_v3_parse_header_t *st, u_char ch);
+ngx_int_t ngx_http_v3_parse_header_iwnr(ngx_connection_t *c,
+ ngx_http_v3_parse_header_t *st, u_char ch);
+
+ngx_int_t ngx_http_v3_parse_decoder(ngx_connection_t *c, void *data, u_char ch);
+
+ngx_int_t ngx_http_v3_parse_data(ngx_connection_t *c,
+ ngx_http_v3_parse_data_t *st, u_char ch);
+
+
+#endif /* _NGX_HTTP_V3_PARSE_H_INCLUDED_ */
diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c
new file mode 100644
index 000000000..e0ee0c882
--- /dev/null
+++ b/src/http/v3/ngx_http_v3_request.c
@@ -0,0 +1,669 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r,
+ ngx_str_t *name, ngx_str_t *value);
+
+
+struct {
+ ngx_str_t name;
+ ngx_uint_t method;
+} ngx_http_v3_methods[] = {
+
+ { ngx_string("GET"), NGX_HTTP_GET },
+ { ngx_string("POST"), NGX_HTTP_POST },
+ { ngx_string("HEAD"), NGX_HTTP_HEAD },
+ { ngx_string("OPTIONS"), NGX_HTTP_OPTIONS },
+ { ngx_string("PROPFIND"), NGX_HTTP_PROPFIND },
+ { ngx_string("PUT"), NGX_HTTP_PUT },
+ { ngx_string("MKCOL"), NGX_HTTP_MKCOL },
+ { ngx_string("DELETE"), NGX_HTTP_DELETE },
+ { ngx_string("COPY"), NGX_HTTP_COPY },
+ { ngx_string("MOVE"), NGX_HTTP_MOVE },
+ { ngx_string("PROPPATCH"), NGX_HTTP_PROPPATCH },
+ { ngx_string("LOCK"), NGX_HTTP_LOCK },
+ { ngx_string("UNLOCK"), NGX_HTTP_UNLOCK },
+ { ngx_string("PATCH"), NGX_HTTP_PATCH },
+ { ngx_string("TRACE"), NGX_HTTP_TRACE }
+};
+
+
+ngx_int_t
+ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b)
+{
+ size_t n;
+ u_char *p;
+ ngx_int_t rc;
+ ngx_str_t *name, *value;
+ ngx_connection_t *c;
+ ngx_http_v3_parse_headers_t *st;
+ enum {
+ sw_start = 0,
+ sw_prev,
+ sw_headers,
+ sw_last,
+ sw_done
+ };
+
+ c = r->connection;
+ st = r->h3_parse;
+
+ if (st == NULL) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header");
+
+ st = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_parse_headers_t));
+ if (st == NULL) {
+ goto failed;
+ }
+
+ r->h3_parse = st;
+ }
+
+ switch (r->state) {
+
+ case sw_prev:
+ r->state = sw_headers;
+ return NGX_OK;
+
+ case sw_done:
+ goto done;
+
+ case sw_last:
+ r->state = sw_done;
+ return NGX_OK;
+
+ default:
+ break;
+ }
+
+ while (b->pos < b->last) {
+ rc = ngx_http_v3_parse_headers(c, st, *b->pos++);
+
+ if (rc == NGX_ERROR) {
+ goto failed;
+ }
+
+ if (rc == NGX_AGAIN) {
+ continue;
+ }
+
+ name = &st->header_rep.header.name;
+ value = &st->header_rep.header.value;
+
+ if (r->state == sw_start) {
+
+ if (ngx_http_v3_process_pseudo_header(r, name, value) == NGX_OK) {
+ if (rc == NGX_OK) {
+ continue;
+ }
+
+ r->state = sw_done;
+
+ } else if (rc == NGX_OK) {
+ r->state = sw_prev;
+
+ } else {
+ r->state = sw_last;
+ }
+
+ n = (r->method_end - r->method_start) + 1
+ + (r->uri_end - r->uri_start) + 1
+ + sizeof("HTTP/3") - 1;
+
+ p = ngx_pnalloc(c->pool, n);
+ if (p == NULL) {
+ goto failed;
+ }
+
+ r->request_start = p;
+
+ p = ngx_cpymem(p, r->method_start, r->method_end - r->method_start);
+ *p++ = ' ';
+ p = ngx_cpymem(p, r->uri_start, r->uri_end - r->uri_start);
+ *p++ = ' ';
+ p = ngx_cpymem(p, "HTTP/3", sizeof("HTTP/3") - 1);
+
+ r->request_end = p;
+
+ } else if (rc == NGX_DONE) {
+ r->state = sw_done;
+ }
+
+ r->header_name_start = name->data;
+ r->header_name_end = name->data + name->len;
+ r->header_start = value->data;
+ r->header_end = value->data + value->len;
+ r->header_hash = ngx_hash_key(name->data, name->len);
+
+ /* XXX r->lowcase_index = i; */
+
+ return NGX_OK;
+ }
+
+ return NGX_AGAIN;
+
+failed:
+
+ return r->state == sw_start ? NGX_HTTP_PARSE_INVALID_REQUEST
+ : NGX_HTTP_PARSE_INVALID_HEADER;
+
+done:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header done");
+
+ return NGX_HTTP_PARSE_HEADER_DONE;
+}
+
+
+static ngx_int_t
+ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name,
+ ngx_str_t *value)
+{
+ ngx_uint_t i;
+ ngx_connection_t *c;
+
+ if (name->len == 0 || name->data[0] != ':') {
+ return NGX_DONE;
+ }
+
+ c = r->connection;
+
+ if (name->len == 7 && ngx_strncmp(name->data, ":method", 7) == 0) {
+ r->method_start = value->data;
+ r->method_end = value->data + value->len;
+
+ for (i = 0; i < sizeof(ngx_http_v3_methods)
+ / sizeof(ngx_http_v3_methods[0]); i++)
+ {
+ if (value->len == ngx_http_v3_methods[i].name.len
+ && ngx_strncmp(value->data, ngx_http_v3_methods[i].name.data,
+ value->len) == 0)
+ {
+ r->method = ngx_http_v3_methods[i].method;
+ break;
+ }
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 method \"%V\" %ui", value, r->method);
+ return NGX_OK;
+ }
+
+ if (name->len == 5 && ngx_strncmp(name->data, ":path", 5) == 0) {
+ r->uri_start = value->data;
+ r->uri_end = value->data + value->len;
+
+ if (ngx_http_parse_uri(r) != NGX_OK) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client sent invalid :path header: \"%V\"", value);
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 path \"%V\"", value);
+
+ return NGX_OK;
+ }
+
+ if (name->len == 7 && ngx_strncmp(name->data, ":scheme", 7) == 0) {
+ r->schema_start = value->data;
+ r->schema_end = value->data + value->len;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 schema \"%V\"", value);
+
+ return NGX_OK;
+ }
+
+ if (name->len == 10 && ngx_strncmp(name->data, ":authority", 10) == 0) {
+ r->host_start = value->data;
+ r->host_end = value->data + value->len;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 authority \"%V\"", value);
+
+ return NGX_OK;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 unknown pseudo header \"%V\" \"%V\"", name, value);
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_parse_request_body(ngx_http_request_t *r, ngx_buf_t *b,
+ ngx_http_chunked_t *ctx)
+{
+ ngx_int_t rc;
+ ngx_connection_t *c;
+ ngx_http_v3_parse_data_t *st;
+
+ c = r->connection;
+ st = ctx->h3_parse;
+
+ if (st == NULL) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 parse request body");
+
+ st = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_parse_data_t));
+ if (st == NULL) {
+ goto failed;
+ }
+
+ r->h3_parse = st;
+ }
+
+ if (ctx->size) {
+ ctx->length = ctx->size + 1;
+ return (b->pos == b->last) ? NGX_AGAIN : NGX_OK;
+ }
+
+ while (b->pos < b->last) {
+ rc = ngx_http_v3_parse_data(c, st, *b->pos++);
+
+ if (rc == NGX_ERROR) {
+ goto failed;
+ }
+
+ if (rc == NGX_AGAIN) {
+ continue;
+ }
+
+ /* rc == NGX_DONE */
+
+ ctx->size = st->length;
+ return NGX_OK;
+ }
+
+ if (!b->last_buf) {
+ ctx->length = 1;
+ return NGX_AGAIN;
+ }
+
+ if (st->state) {
+ goto failed;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header done");
+
+ return NGX_DONE;
+
+failed:
+
+ return NGX_ERROR;
+}
+
+
+ngx_chain_t *
+ngx_http_v3_create_header(ngx_http_request_t *r)
+{
+ u_char *p;
+ size_t len, n;
+ ngx_buf_t *b;
+ ngx_uint_t i, j;
+ ngx_chain_t *hl, *cl, *bl;
+ ngx_list_part_t *part;
+ ngx_table_elt_t *header;
+ ngx_connection_t *c;
+ ngx_http_core_loc_conf_t *clcf;
+
+ c = r->connection;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 create header");
+
+ len = 2;
+
+ if (r->headers_out.status == NGX_HTTP_OK) {
+ len += ngx_http_v3_encode_prefix_int(NULL, 25, 6);
+
+ } else {
+ len += 3 + ngx_http_v3_encode_prefix_int(NULL, 25, 4)
+ + ngx_http_v3_encode_prefix_int(NULL, 3, 7);
+ }
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (r->headers_out.server == NULL) {
+ if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) {
+ n = sizeof(NGINX_VER) - 1;
+
+ } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) {
+ n = sizeof(NGINX_VER_BUILD) - 1;
+
+ } else {
+ n = sizeof("nginx") - 1;
+ }
+
+ len += ngx_http_v3_encode_prefix_int(NULL, 92, 4)
+ + ngx_http_v3_encode_prefix_int(NULL, n, 7) + n;
+ }
+
+ if (r->headers_out.date == NULL) {
+ len += ngx_http_v3_encode_prefix_int(NULL, 6, 4)
+ + ngx_http_v3_encode_prefix_int(NULL, ngx_cached_http_time.len,
+ 7)
+ + ngx_cached_http_time.len;
+ }
+
+ if (r->headers_out.content_type.len) {
+ n = r->headers_out.content_type.len;
+
+ if (r->headers_out.content_type_len == r->headers_out.content_type.len
+ && r->headers_out.charset.len)
+ {
+ n += sizeof("; charset=") - 1 + r->headers_out.charset.len;
+ }
+
+ len += ngx_http_v3_encode_prefix_int(NULL, 53, 4)
+ + ngx_http_v3_encode_prefix_int(NULL, n, 7) + n;
+ }
+
+ if (r->headers_out.content_length_n > 0) {
+ len += ngx_http_v3_encode_prefix_int(NULL, 4, 4) + 1 + NGX_OFF_T_LEN;
+
+ } else if (r->headers_out.content_length_n == 0) {
+ len += ngx_http_v3_encode_prefix_int(NULL, 4, 6);
+ }
+
+ if (r->headers_out.last_modified == NULL
+ && r->headers_out.last_modified_time != -1)
+ {
+ len += ngx_http_v3_encode_prefix_int(NULL, 10, 4) + 1
+ + sizeof("Last-Modified: Mon, 28 Sep 1970 06:00:00 GMT");
+ }
+
+ /* XXX location */
+
+#if (NGX_HTTP_GZIP)
+ if (r->gzip_vary) {
+ if (clcf->gzip_vary) {
+ /* Vary: Accept-Encoding */
+ len += ngx_http_v3_encode_prefix_int(NULL, 59, 6);
+
+ } else {
+ r->gzip_vary = 0;
+ }
+ }
+#endif
+
+ part = &r->headers_out.headers.part;
+ header = part->elts;
+
+ for (i = 0; /* void */; i++) {
+
+ if (i >= part->nelts) {
+ if (part->next == NULL) {
+ break;
+ }
+
+ part = part->next;
+ header = part->elts;
+ i = 0;
+ }
+
+ if (header[i].hash == 0) {
+ continue;
+ }
+
+ len += ngx_http_v3_encode_prefix_int(NULL, header[i].key.len, 3)
+ + header[i].key.len
+ + ngx_http_v3_encode_prefix_int(NULL, header[i].value.len, 7 )
+ + header[i].value.len;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 header len:%uz", len);
+
+ b = ngx_create_temp_buf(r->pool, len);
+ if (b == NULL) {
+ return NULL;
+ }
+
+ *b->last++ = 0;
+ *b->last++ = 0;
+
+ if (r->headers_out.status == NGX_HTTP_OK) {
+ /* :status: 200 */
+ *b->last = 0xc0;
+ b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 25, 6);
+
+ } else {
+ /* :status: 200 */
+ *b->last = 0x70;
+ b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 25, 4);
+ *b->last = 0;
+ b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 3, 7);
+ b->last = ngx_sprintf(b->last, "%03ui", r->headers_out.status);
+ }
+
+ if (r->headers_out.server == NULL) {
+ if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) {
+ p = (u_char *) NGINX_VER;
+ n = sizeof(NGINX_VER) - 1;
+
+ } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) {
+ p = (u_char *) NGINX_VER_BUILD;
+ n = sizeof(NGINX_VER_BUILD) - 1;
+
+ } else {
+ p = (u_char *) "nginx";
+ n = sizeof("nginx") - 1;
+ }
+
+ /* server */
+ *b->last = 0x70;
+ b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 92, 4);
+ *b->last = 0;
+ b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, n, 7);
+ b->last = ngx_cpymem(b->last, p, n);
+ }
+
+ if (r->headers_out.date == NULL) {
+ /* date */
+ *b->last = 0x70;
+ b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 6, 4);
+ *b->last = 0;
+ b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last,
+ ngx_cached_http_time.len, 7);
+ b->last = ngx_cpymem(b->last, ngx_cached_http_time.data,
+ ngx_cached_http_time.len);
+ }
+
+ if (r->headers_out.content_type.len) {
+ n = r->headers_out.content_type.len;
+
+ if (r->headers_out.content_type_len == r->headers_out.content_type.len
+ && r->headers_out.charset.len)
+ {
+ n += sizeof("; charset=") - 1 + r->headers_out.charset.len;
+ }
+
+ /* content-type: text/plain */
+ *b->last = 0x70;
+ b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 53, 4);
+ *b->last = 0;
+ b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, n, 7);
+
+ p = b->last;
+ b->last = ngx_copy(b->last, r->headers_out.content_type.data,
+ r->headers_out.content_type.len);
+
+ if (r->headers_out.content_type_len == r->headers_out.content_type.len
+ && r->headers_out.charset.len)
+ {
+ b->last = ngx_cpymem(b->last, "; charset=",
+ sizeof("; charset=") - 1);
+ b->last = ngx_copy(b->last, r->headers_out.charset.data,
+ r->headers_out.charset.len);
+
+ /* update r->headers_out.content_type for possible logging */
+
+ r->headers_out.content_type.len = b->last - p;
+ r->headers_out.content_type.data = p;
+ }
+ }
+
+ if (r->headers_out.content_length_n > 0) {
+ /* content-length: 0 */
+ *b->last = 0x70;
+ b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 4, 4);
+ p = b->last++;
+ b->last = ngx_sprintf(b->last, "%O", r->headers_out.content_length_n);
+ *p = b->last - p - 1;
+
+ } else if (r->headers_out.content_length_n == 0) {
+ /* content-length: 0 */
+ *b->last = 0xc0;
+ b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 4, 6);
+ }
+
+ if (r->headers_out.last_modified == NULL
+ && r->headers_out.last_modified_time != -1)
+ {
+ /* last-modified */
+ *b->last = 0x70;
+ b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 10, 4);
+ p = b->last++;
+ b->last = ngx_http_time(b->last, r->headers_out.last_modified_time);
+ *p = b->last - p - 1;
+ }
+
+#if (NGX_HTTP_GZIP)
+ if (r->gzip_vary) {
+ /* vary: accept-encoding */
+ *b->last = 0xc0;
+ b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 59, 6);
+ }
+#endif
+
+ part = &r->headers_out.headers.part;
+ header = part->elts;
+
+ for (i = 0; /* void */; i++) {
+
+ if (i >= part->nelts) {
+ if (part->next == NULL) {
+ break;
+ }
+
+ part = part->next;
+ header = part->elts;
+ i = 0;
+ }
+
+ if (header[i].hash == 0) {
+ continue;
+ }
+
+ *b->last = 0x30;
+ b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last,
+ header[i].key.len,
+ 3);
+ for (j = 0; j < header[i].key.len; j++) {
+ *b->last++ = ngx_tolower(header[i].key.data[j]);
+ }
+
+ *b->last = 0;
+ b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last,
+ header[i].value.len,
+ 7);
+ b->last = ngx_copy(b->last, header[i].value.data, header[i].value.len);
+ }
+
+ if (r->header_only) {
+ b->last_buf = 1;
+ }
+
+ cl = ngx_alloc_chain_link(c->pool);
+ if (cl == NULL) {
+ return NULL;
+ }
+
+ cl->buf = b;
+ cl->next = NULL;
+
+ n = b->last - b->pos;
+
+ len = 1 + ngx_http_v3_encode_varlen_int(NULL, n);
+
+ b = ngx_create_temp_buf(c->pool, len);
+ if (b == NULL) {
+ return NULL;
+ }
+
+ *b->last++ = NGX_HTTP_V3_FRAME_HEADERS;
+ b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n);
+
+ hl = ngx_alloc_chain_link(c->pool);
+ if (hl == NULL) {
+ return NULL;
+ }
+
+ hl->buf = b;
+ hl->next = cl;
+
+ if (r->headers_out.content_length_n >= 0) {
+ len = 1 + ngx_http_v3_encode_varlen_int(NULL,
+ r->headers_out.content_length_n);
+
+ b = ngx_create_temp_buf(c->pool, len);
+ if (b == NULL) {
+ NULL;
+ }
+
+ *b->last++ = NGX_HTTP_V3_FRAME_DATA;
+ b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last,
+ r->headers_out.content_length_n);
+
+ bl = ngx_alloc_chain_link(c->pool);
+ if (bl == NULL) {
+ return NULL;
+ }
+
+ bl->buf = b;
+ bl->next = NULL;
+ cl->next = bl;
+ }
+
+ return hl;
+}
+
+
+ngx_chain_t *
+ngx_http_v3_create_trailers(ngx_http_request_t *r)
+{
+ ngx_buf_t *b;
+ ngx_chain_t *cl;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http3 create trailers");
+
+ /* XXX */
+
+ b = ngx_calloc_buf(r->pool);
+ if (b == NULL) {
+ return NULL;
+ }
+
+ b->last_buf = 1;
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NULL;
+ }
+
+ cl->buf = b;
+ cl->next = NULL;
+
+ return cl;
+}
diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c
new file mode 100644
index 000000000..6078725d7
--- /dev/null
+++ b/src/http/v3/ngx_http_v3_streams.c
@@ -0,0 +1,571 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef ngx_int_t (*ngx_http_v3_handler_pt)(ngx_connection_t *c, void *data,
+ u_char ch);
+
+
+typedef struct {
+ ngx_http_v3_handler_pt handler;
+ void *data;
+ ngx_int_t index;
+} ngx_http_v3_uni_stream_t;
+
+
+static void ngx_http_v3_close_uni_stream(ngx_connection_t *c);
+static void ngx_http_v3_read_uni_stream_type(ngx_event_t *rev);
+static void ngx_http_v3_uni_read_handler(ngx_event_t *rev);
+static void ngx_http_v3_dummy_write_handler(ngx_event_t *wev);
+static ngx_connection_t *ngx_http_v3_get_uni_stream(ngx_connection_t *c,
+ ngx_uint_t type);
+
+
+void
+ngx_http_v3_handle_client_uni_stream(ngx_connection_t *c)
+{
+ ngx_http_v3_uni_stream_t *us;
+
+ c->log->connection = c->number;
+
+ ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL);
+ ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_ENCODER);
+ ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 new uni stream id:0x%uxL", c->qs->id);
+
+ us = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_uni_stream_t));
+ if (us == NULL) {
+ ngx_http_v3_close_uni_stream(c);
+ return;
+ }
+
+ us->index = -1;
+
+ c->data = us;
+
+ c->read->handler = ngx_http_v3_read_uni_stream_type;
+ c->write->handler = ngx_http_v3_dummy_write_handler;
+
+ ngx_http_v3_read_uni_stream_type(c->read);
+}
+
+
+static void
+ngx_http_v3_close_uni_stream(ngx_connection_t *c)
+{
+ ngx_pool_t *pool;
+ ngx_http_v3_connection_t *h3c;
+ ngx_http_v3_uni_stream_t *us;
+
+ us = c->data;
+ h3c = c->qs->parent->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 close stream");
+
+ if (us->index >= 0) {
+ h3c->known_streams[us->index] = NULL;
+ }
+
+ c->destroyed = 1;
+
+ pool = c->pool;
+
+ ngx_close_connection(c);
+
+ ngx_destroy_pool(pool);
+}
+
+
+static void
+ngx_http_v3_read_uni_stream_type(ngx_event_t *rev)
+{
+ u_char ch;
+ ssize_t n;
+ ngx_int_t index;
+ ngx_connection_t *c;
+ ngx_http_v3_connection_t *h3c;
+ ngx_http_v3_uni_stream_t *us;
+
+ c = rev->data;
+ us = c->data;
+ h3c = c->qs->parent->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read stream type");
+
+ while (rev->ready) {
+
+ n = c->recv(c, &ch, 1);
+
+ if (n == NGX_ERROR) {
+ goto failed;
+ }
+
+ if (n == NGX_AGAIN || n != 1) {
+ break;
+ }
+
+ switch (ch) {
+
+ case NGX_HTTP_V3_STREAM_ENCODER:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 encoder stream");
+
+ index = NGX_HTTP_V3_STREAM_CLIENT_ENCODER;
+ us->handler = ngx_http_v3_parse_encoder;
+ n = sizeof(ngx_http_v3_parse_encoder_t);
+
+ break;
+
+ case NGX_HTTP_V3_STREAM_DECODER:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 decoder stream");
+
+ index = NGX_HTTP_V3_STREAM_CLIENT_DECODER;
+ us->handler = ngx_http_v3_parse_decoder;
+ n = sizeof(ngx_http_v3_parse_decoder_t);
+
+ break;
+
+ case NGX_HTTP_V3_STREAM_CONTROL:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 control stream");
+
+ index = NGX_HTTP_V3_STREAM_CLIENT_CONTROL;
+ us->handler = ngx_http_v3_parse_control;
+ n = sizeof(ngx_http_v3_parse_control_t);
+
+ break;
+
+ default:
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 stream 0x%02xi", (ngx_int_t) ch);
+ index = -1;
+ n = 0;
+ }
+
+ if (index >= 0) {
+ if (h3c->known_streams[index]) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "stream exists");
+ goto failed;
+ }
+
+ us->index = index;
+ h3c->known_streams[index] = c;
+ }
+
+ if (n) {
+ us->data = ngx_pcalloc(c->pool, n);
+ if (us->data == NULL) {
+ goto failed;
+ }
+ }
+
+ rev->handler = ngx_http_v3_uni_read_handler;
+ ngx_http_v3_uni_read_handler(rev);
+ return;
+ }
+
+ if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+ goto failed;
+ }
+
+ return;
+
+failed:
+
+ ngx_http_v3_close_uni_stream(c);
+}
+
+
+static void
+ngx_http_v3_uni_read_handler(ngx_event_t *rev)
+{
+ u_char buf[128];
+ ssize_t n;
+ ngx_int_t rc, i;
+ ngx_connection_t *c;
+ ngx_http_v3_uni_stream_t *us;
+
+ c = rev->data;
+ us = c->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read handler");
+
+ while (rev->ready) {
+
+ n = c->recv(c, buf, sizeof(buf));
+
+ if (n == NGX_ERROR || n == 0) {
+ goto failed;
+ }
+
+ if (n == NGX_AGAIN) {
+ break;
+ }
+
+ if (us->handler == NULL) {
+ continue;
+ }
+
+ for (i = 0; i < n; i++) {
+
+ rc = us->handler(c, us->data, buf[i]);
+
+ if (rc == NGX_ERROR) {
+ goto failed;
+ }
+
+ if (rc == NGX_DONE) {
+ goto done;
+ }
+
+ /* rc == NGX_AGAIN */
+ }
+ }
+
+ if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+ goto failed;
+ }
+
+ return;
+
+done:
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read done");
+
+failed:
+
+ ngx_http_v3_close_uni_stream(c);
+}
+
+
+static void
+ngx_http_v3_dummy_write_handler(ngx_event_t *wev)
+{
+ ngx_connection_t *c;
+
+ c = wev->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 dummy write handler");
+
+ if (ngx_handle_write_event(wev, 0) != NGX_OK) {
+ ngx_http_v3_close_uni_stream(c);
+ }
+}
+
+
+/* XXX async & buffered stream writes */
+
+static ngx_connection_t *
+ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type)
+{
+ u_char buf[NGX_HTTP_V3_VARLEN_INT_LEN];
+ size_t n;
+ ngx_int_t index;
+ ngx_connection_t *sc;
+ ngx_http_v3_connection_t *h3c;
+ ngx_http_v3_uni_stream_t *us;
+
+ switch (type) {
+ case NGX_HTTP_V3_STREAM_ENCODER:
+ index = NGX_HTTP_V3_STREAM_SERVER_ENCODER;
+ break;
+ case NGX_HTTP_V3_STREAM_DECODER:
+ index = NGX_HTTP_V3_STREAM_SERVER_DECODER;
+ break;
+ case NGX_HTTP_V3_STREAM_CONTROL:
+ index = NGX_HTTP_V3_STREAM_SERVER_CONTROL;
+ break;
+ default:
+ index = -1;
+ }
+
+ h3c = c->qs->parent->data;
+
+ if (index >= 0) {
+ if (h3c->known_streams[index]) {
+ return h3c->known_streams[index];
+ }
+ }
+
+ sc = ngx_quic_create_uni_stream(c);
+ if (sc == NULL) {
+ return NULL;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 create uni stream, type:%ui", type);
+
+ us = ngx_pcalloc(sc->pool, sizeof(ngx_http_v3_uni_stream_t));
+ if (us == NULL) {
+ goto failed;
+ }
+
+ us->index = index;
+
+ sc->data = us;
+
+ sc->read->handler = ngx_http_v3_uni_read_handler;
+ sc->write->handler = ngx_http_v3_dummy_write_handler;
+
+ h3c->known_streams[index] = sc;
+
+ n = (u_char *) ngx_http_v3_encode_varlen_int(buf, type) - buf;
+
+ if (sc->send(sc, buf, n) != (ssize_t) n) {
+ goto failed;
+ }
+
+ return sc;
+
+failed:
+
+ ngx_http_v3_close_uni_stream(sc);
+
+ return NULL;
+}
+
+
+ngx_int_t
+ngx_http_v3_client_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic,
+ ngx_uint_t index, ngx_str_t *value)
+{
+ u_char *p, buf[NGX_HTTP_V3_PREFIX_INT_LEN * 2];
+ size_t n;
+ ngx_connection_t *ec;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 client ref insert, %s[%ui] \"%V\"",
+ dynamic ? "dynamic" : "static", index, value);
+
+ ec = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_ENCODER);
+ if (ec == NULL) {
+ return NGX_ERROR;
+ }
+
+ p = buf;
+
+ *p = (dynamic ? 0x80 : 0xc0);
+ p = (u_char *) ngx_http_v3_encode_prefix_int(p, index, 6);
+
+ /* XXX option for huffman? */
+ *p = 0;
+ p = (u_char *) ngx_http_v3_encode_prefix_int(p, value->len, 7);
+
+ n = p - buf;
+
+ if (ec->send(ec, buf, n) != (ssize_t) n) {
+ goto failed;
+ }
+
+ if (ec->send(ec, value->data, value->len) != (ssize_t) value->len) {
+ goto failed;
+ }
+
+ return NGX_OK;
+
+failed:
+
+ ngx_http_v3_close_uni_stream(ec);
+
+ return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_http_v3_client_insert(ngx_connection_t *c, ngx_str_t *name,
+ ngx_str_t *value)
+{
+ u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN];
+ size_t n;
+ ngx_connection_t *ec;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 client insert \"%V\":\"%V\"", name, value);
+
+ ec = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_ENCODER);
+ if (ec == NULL) {
+ return NGX_ERROR;
+ }
+
+ /* XXX option for huffman? */
+ buf[0] = 0x40;
+ n = (u_char *) ngx_http_v3_encode_prefix_int(buf, name->len, 5) - buf;
+
+ if (ec->send(ec, buf, n) != (ssize_t) n) {
+ goto failed;
+ }
+
+ if (ec->send(ec, name->data, name->len) != (ssize_t) name->len) {
+ goto failed;
+ }
+
+ /* XXX option for huffman? */
+ buf[0] = 0;
+ n = (u_char *) ngx_http_v3_encode_prefix_int(buf, value->len, 7) - buf;
+
+ if (ec->send(ec, buf, n) != (ssize_t) n) {
+ goto failed;
+ }
+
+ if (ec->send(ec, value->data, value->len) != (ssize_t) value->len) {
+ goto failed;
+ }
+
+ return NGX_OK;
+
+failed:
+
+ ngx_http_v3_close_uni_stream(ec);
+
+ return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_http_v3_client_set_capacity(ngx_connection_t *c, ngx_uint_t capacity)
+{
+ u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN];
+ size_t n;
+ ngx_connection_t *ec;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 client set capacity %ui", capacity);
+
+ ec = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_ENCODER);
+ if (ec == NULL) {
+ return NGX_ERROR;
+ }
+
+ buf[0] = 0x20;
+ n = (u_char *) ngx_http_v3_encode_prefix_int(buf, capacity, 5) - buf;
+
+ if (ec->send(ec, buf, n) != (ssize_t) n) {
+ ngx_http_v3_close_uni_stream(ec);
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_client_duplicate(ngx_connection_t *c, ngx_uint_t index)
+{
+ u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN];
+ size_t n;
+ ngx_connection_t *ec;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 client duplicate %ui", index);
+
+ ec = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_ENCODER);
+ if (ec == NULL) {
+ return NGX_ERROR;
+ }
+
+ buf[0] = 0;
+ n = (u_char *) ngx_http_v3_encode_prefix_int(buf, index, 5) - buf;
+
+ if (ec->send(ec, buf, n) != (ssize_t) n) {
+ ngx_http_v3_close_uni_stream(ec);
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_client_ack_header(ngx_connection_t *c, ngx_uint_t stream_id)
+{
+ u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN];
+ size_t n;
+ ngx_connection_t *dc;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 client ack header %ui", stream_id);
+
+ dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER);
+ if (dc == NULL) {
+ return NGX_ERROR;
+ }
+
+ buf[0] = 0x80;
+ n = (u_char *) ngx_http_v3_encode_prefix_int(buf, stream_id, 7) - buf;
+
+ if (dc->send(dc, buf, n) != (ssize_t) n) {
+ ngx_http_v3_close_uni_stream(dc);
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_client_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id)
+{
+ u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN];
+ size_t n;
+ ngx_connection_t *dc;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 client cancel stream %ui", stream_id);
+
+ dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER);
+ if (dc == NULL) {
+ return NGX_ERROR;
+ }
+
+ buf[0] = 0x40;
+ n = (u_char *) ngx_http_v3_encode_prefix_int(buf, stream_id, 6) - buf;
+
+ if (dc->send(dc, buf, n) != (ssize_t) n) {
+ ngx_http_v3_close_uni_stream(dc);
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_client_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc)
+{
+ u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN];
+ size_t n;
+ ngx_connection_t *dc;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 client increment insert count %ui", inc);
+
+ dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER);
+ if (dc == NULL) {
+ return NGX_ERROR;
+ }
+
+ buf[0] = 0;
+ n = (u_char *) ngx_http_v3_encode_prefix_int(buf, inc, 6) - buf;
+
+ if (dc->send(dc, buf, n) != (ssize_t) n) {
+ ngx_http_v3_close_uni_stream(dc);
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
diff --git a/src/http/v3/ngx_http_v3_tables.c b/src/http/v3/ngx_http_v3_tables.c
new file mode 100644
index 000000000..f58f190f1
--- /dev/null
+++ b/src/http/v3/ngx_http_v3_tables.c
@@ -0,0 +1,416 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static ngx_array_t *ngx_http_v3_get_dynamic_table(ngx_connection_t *c);
+static ngx_int_t ngx_http_v3_new_header(ngx_connection_t *c);
+
+
+static ngx_http_v3_header_t ngx_http_v3_static_table[] = {
+
+ { ngx_string(":authority"), ngx_string("") },
+ { ngx_string(":path"), ngx_string("/") },
+ { ngx_string("age"), ngx_string("0") },
+ { ngx_string("content-disposition"), ngx_string("") },
+ { ngx_string("content-length"), ngx_string("0") },
+ { ngx_string("cookie"), ngx_string("") },
+ { ngx_string("date"), ngx_string("") },
+ { ngx_string("etag"), ngx_string("") },
+ { ngx_string("if-modified-since"), ngx_string("") },
+ { ngx_string("if-none-match"), ngx_string("") },
+ { ngx_string("last-modified"), ngx_string("") },
+ { ngx_string("link"), ngx_string("") },
+ { ngx_string("location"), ngx_string("") },
+ { ngx_string("referer"), ngx_string("") },
+ { ngx_string("set-cookie"), ngx_string("") },
+ { ngx_string(":method"), ngx_string("CONNECT") },
+ { ngx_string(":method"), ngx_string("DELETE") },
+ { ngx_string(":method"), ngx_string("GET") },
+ { ngx_string(":method"), ngx_string("HEAD") },
+ { ngx_string(":method"), ngx_string("OPTIONS") },
+ { ngx_string(":method"), ngx_string("POST") },
+ { ngx_string(":method"), ngx_string("PUT") },
+ { ngx_string(":scheme"), ngx_string("http") },
+ { ngx_string(":scheme"), ngx_string("https") },
+ { ngx_string(":status"), ngx_string("103") },
+ { ngx_string(":status"), ngx_string("200") },
+ { ngx_string(":status"), ngx_string("304") },
+ { ngx_string(":status"), ngx_string("404") },
+ { ngx_string(":status"), ngx_string("503") },
+ { ngx_string("accept"), ngx_string("*/*") },
+ { ngx_string("accept"),
+ ngx_string("application/dns-message") },
+ { ngx_string("accept-encoding"), ngx_string("gzip, deflate, br") },
+ { ngx_string("accept-ranges"), ngx_string("bytes") },
+ { ngx_string("access-control-allow-headers"),
+ ngx_string("cache-control") },
+ { ngx_string("access-control-allow-headers"),
+ ngx_string("content-type") },
+ { ngx_string("access-control-allow-origin"),
+ ngx_string("*") },
+ { ngx_string("cache-control"), ngx_string("max-age=0") },
+ { ngx_string("cache-control"), ngx_string("max-age=2592000") },
+ { ngx_string("cache-control"), ngx_string("max-age=604800") },
+ { ngx_string("cache-control"), ngx_string("no-cache") },
+ { ngx_string("cache-control"), ngx_string("no-store") },
+ { ngx_string("cache-control"),
+ ngx_string("public, max-age=31536000") },
+ { ngx_string("content-encoding"), ngx_string("br") },
+ { ngx_string("content-encoding"), ngx_string("gzip") },
+ { ngx_string("content-type"),
+ ngx_string("application/dns-message") },
+ { ngx_string("content-type"),
+ ngx_string("application/javascript") },
+ { ngx_string("content-type"), ngx_string("application/json") },
+ { ngx_string("content-type"),
+ ngx_string("application/x-www-form-urlencoded") },
+ { ngx_string("content-type"), ngx_string("image/gif") },
+ { ngx_string("content-type"), ngx_string("image/jpeg") },
+ { ngx_string("content-type"), ngx_string("image/png") },
+ { ngx_string("content-type"), ngx_string("text/css") },
+ { ngx_string("content-type"),
+ ngx_string("text/html;charset=utf-8") },
+ { ngx_string("content-type"), ngx_string("text/plain") },
+ { ngx_string("content-type"),
+ ngx_string("text/plain;charset=utf-8") },
+ { ngx_string("range"), ngx_string("bytes=0-") },
+ { ngx_string("strict-transport-security"),
+ ngx_string("max-age=31536000") },
+ { ngx_string("strict-transport-security"),
+ ngx_string("max-age=31536000;includesubdomains") },
+ { ngx_string("strict-transport-security"),
+ ngx_string("max-age=31536000;includesubdomains;preload") },
+ { ngx_string("vary"), ngx_string("accept-encoding") },
+ { ngx_string("vary"), ngx_string("origin") },
+ { ngx_string("x-content-type-options"),
+ ngx_string("nosniff") },
+ { ngx_string("x-xss-protection"), ngx_string("1;mode=block") },
+ { ngx_string(":status"), ngx_string("100") },
+ { ngx_string(":status"), ngx_string("204") },
+ { ngx_string(":status"), ngx_string("206") },
+ { ngx_string(":status"), ngx_string("302") },
+ { ngx_string(":status"), ngx_string("400") },
+ { ngx_string(":status"), ngx_string("403") },
+ { ngx_string(":status"), ngx_string("421") },
+ { ngx_string(":status"), ngx_string("425") },
+ { ngx_string(":status"), ngx_string("500") },
+ { ngx_string("accept-language"), ngx_string("") },
+ { ngx_string("access-control-allow-credentials"),
+ ngx_string("FALSE") },
+ { ngx_string("access-control-allow-credentials"),
+ ngx_string("TRUE") },
+ { ngx_string("access-control-allow-headers"),
+ ngx_string("*") },
+ { ngx_string("access-control-allow-methods"),
+ ngx_string("get") },
+ { ngx_string("access-control-allow-methods"),
+ ngx_string("get, post, options") },
+ { ngx_string("access-control-allow-methods"),
+ ngx_string("options") },
+ { ngx_string("access-control-expose-headers"),
+ ngx_string("content-length") },
+ { ngx_string("access-control-request-headers"),
+ ngx_string("content-type") },
+ { ngx_string("access-control-request-method"),
+ ngx_string("get") },
+ { ngx_string("access-control-request-method"),
+ ngx_string("post") },
+ { ngx_string("alt-svc"), ngx_string("clear") },
+ { ngx_string("authorization"), ngx_string("") },
+ { ngx_string("content-security-policy"),
+ ngx_string("script-src 'none';object-src 'none';base-uri 'none'") },
+ { ngx_string("early-data"), ngx_string("1") },
+ { ngx_string("expect-ct"), ngx_string("") },
+ { ngx_string("forwarded"), ngx_string("") },
+ { ngx_string("if-range"), ngx_string("") },
+ { ngx_string("origin"), ngx_string("") },
+ { ngx_string("purpose"), ngx_string("prefetch") },
+ { ngx_string("server"), ngx_string("") },
+ { ngx_string("timing-allow-origin"), ngx_string("*") },
+ { ngx_string("upgrade-insecure-requests"),
+ ngx_string("1") },
+ { ngx_string("user-agent"), ngx_string("") },
+ { ngx_string("x-forwarded-for"), ngx_string("") },
+ { ngx_string("x-frame-options"), ngx_string("deny") },
+ { ngx_string("x-frame-options"), ngx_string("sameorigin") }
+};
+
+
+ngx_int_t
+ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic,
+ ngx_uint_t index, ngx_str_t *value)
+{
+ ngx_array_t *dt;
+ ngx_http_v3_header_t *ref, *h;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 ref insert %s[$ui] \"%V\"",
+ dynamic ? "dynamic" : "static", index, value);
+
+ ref = ngx_http_v3_lookup_table(c, dynamic, index);
+ if (ref == NULL) {
+ return NGX_ERROR;
+ }
+
+ dt = ngx_http_v3_get_dynamic_table(c);
+ if (dt == NULL) {
+ return NGX_ERROR;
+ }
+
+ h = ngx_array_push(dt);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ h->name = ref->name;
+ h->value = *value;
+
+ if (ngx_http_v3_new_header(c) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name,
+ ngx_str_t *value)
+{
+ ngx_array_t *dt;
+ ngx_http_v3_header_t *h;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 insert \"%V\":\"%V\"", name, value);
+
+ dt = ngx_http_v3_get_dynamic_table(c);
+ if (dt == NULL) {
+ return NGX_ERROR;
+ }
+
+ h = ngx_array_push(dt);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ h->name = *name;
+ h->value = *value;
+
+ if (ngx_http_v3_new_header(c) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity)
+{
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 set capacity %ui", capacity);
+
+ /* XXX ignore capacity */
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index)
+{
+ ngx_array_t *dt;
+ ngx_http_v3_header_t *ref, *h;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 duplicate %ui", index);
+
+ ref = ngx_http_v3_lookup_table(c, 1, index);
+ if (ref == NULL) {
+ return NGX_ERROR;
+ }
+
+ dt = ngx_http_v3_get_dynamic_table(c);
+ if (dt == NULL) {
+ return NGX_ERROR;
+ }
+
+ h = ngx_array_push(dt);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ *h = *ref;
+
+ if (ngx_http_v3_new_header(c) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_ack_header(ngx_connection_t *c, ngx_uint_t stream_id)
+{
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 ack header %ui", stream_id);
+
+ /* XXX */
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id)
+{
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 cancel stream %ui", stream_id);
+
+ /* XXX */
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc)
+{
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 increment insert count %ui", inc);
+
+ /* XXX */
+
+ return NGX_OK;
+}
+
+
+static ngx_array_t *
+ngx_http_v3_get_dynamic_table(ngx_connection_t *c)
+{
+ ngx_connection_t *pc;
+ ngx_http_v3_connection_t *h3c;
+
+ pc = c->qs->parent;
+ h3c = pc->data;
+
+ if (h3c->dynamic) {
+ return h3c->dynamic;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 create dynamic table");
+
+ h3c->dynamic = ngx_array_create(pc->pool, 1, sizeof(ngx_http_v3_header_t));
+
+ return h3c->dynamic;
+}
+
+
+ngx_http_v3_header_t *
+ngx_http_v3_lookup_table(ngx_connection_t *c, ngx_uint_t dynamic,
+ ngx_uint_t index)
+{
+ ngx_uint_t nelts;
+ ngx_array_t *dt;
+ ngx_http_v3_header_t *table;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 lookup %s[%ui]",
+ dynamic ? "dynamic" : "static", index);
+
+ if (dynamic) {
+ dt = ngx_http_v3_get_dynamic_table(c);
+ if (dt == NULL) {
+ return NULL;
+ }
+
+ table = dt->elts;
+ nelts = dt->nelts;
+
+ } else {
+ table = ngx_http_v3_static_table;
+ nelts = sizeof(ngx_http_v3_static_table)
+ / sizeof(ngx_http_v3_static_table[0]);
+ }
+
+ if (index >= nelts) {
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 lookup out of bounds: %ui", nelts);
+ return NULL;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 lookup \"%V\":\"%V\"",
+ &table[index].name, &table[index].value);
+
+ return &table[index];
+}
+
+
+ngx_int_t
+ngx_http_v3_check_insert_count(ngx_connection_t *c, ngx_uint_t insert_count)
+{
+ size_t n;
+ ngx_http_v3_connection_t *h3c;
+
+ h3c = c->qs->parent->data;
+ n = h3c->dynamic ? h3c->dynamic->nelts : 0;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 check insert count %ui/%ui", insert_count, n);
+
+ if (n < insert_count) {
+ /* XXX how to get notified? */
+ /* XXX wake all streams on any arrival to the encoder stream? */
+ return NGX_AGAIN;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v3_new_header(ngx_connection_t *c)
+{
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 new dynamic header");
+
+ /* XXX report all waiting streams of a new header */
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_set_param(ngx_connection_t *c, uint64_t id, uint64_t value)
+{
+ switch (id) {
+
+ case NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY:
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 param QPACK_MAX_TABLE_CAPACITY:%uL", value);
+ break;
+
+ case NGX_HTTP_V3_PARAM_MAX_HEADER_LIST_SIZE:
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 param SETTINGS_MAX_HEADER_LIST_SIZE:%uL", value);
+ break;
+
+ case NGX_HTTP_V3_PARAM_BLOCKED_STREAMS:
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 param QPACK_BLOCKED_STREAMS:%uL", value);
+ break;
+
+ default:
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http3 param #%uL:%uL", id, value);
+ }
+
+ return NGX_OK;
+}