changeset 8925:18d23ed15eef quic

HTTP/3: renamed files. ngx_http_v3_tables.h and ngx_http_v3_tables.c are renamed to ngx_http_v3_table.h and ngx_http_v3_table.c to better match HTTP/2 code. ngx_http_v3_streams.h and ngx_http_v3_streams.c are renamed to ngx_http_v3_uni.h and ngx_http_v3_uni.c to better match their content.
author Roman Arutyunyan <arut@nginx.com>
date Tue, 07 Dec 2021 13:01:28 +0300
parents d6ef13c5fd8e
children 3341e4089c6c
files auto/modules src/http/v3/ngx_http_v3.h src/http/v3/ngx_http_v3_streams.c src/http/v3/ngx_http_v3_streams.h src/http/v3/ngx_http_v3_table.c src/http/v3/ngx_http_v3_table.h src/http/v3/ngx_http_v3_tables.c src/http/v3/ngx_http_v3_tables.h src/http/v3/ngx_http_v3_uni.c src/http/v3/ngx_http_v3_uni.h
diffstat 10 files changed, 1508 insertions(+), 1508 deletions(-) [+]
line wrap: on
line diff
--- a/auto/modules	Mon Dec 06 15:19:54 2021 +0300
+++ b/auto/modules	Tue Dec 07 13:01:28 2021 +0300
@@ -448,13 +448,13 @@
         ngx_module_deps="src/http/v3/ngx_http_v3.h \
                          src/http/v3/ngx_http_v3_encode.h \
                          src/http/v3/ngx_http_v3_parse.h \
-                         src/http/v3/ngx_http_v3_tables.h \
-                         src/http/v3/ngx_http_v3_streams.h"
+                         src/http/v3/ngx_http_v3_table.h \
+                         src/http/v3/ngx_http_v3_uni.h"
         ngx_module_srcs="src/http/v3/ngx_http_v3.c \
                          src/http/v3/ngx_http_v3_encode.c \
                          src/http/v3/ngx_http_v3_parse.c \
-                         src/http/v3/ngx_http_v3_tables.c \
-                         src/http/v3/ngx_http_v3_streams.c \
+                         src/http/v3/ngx_http_v3_table.c \
+                         src/http/v3/ngx_http_v3_uni.c \
                          src/http/v3/ngx_http_v3_request.c \
                          src/http/v3/ngx_http_v3_module.c"
         ngx_module_libs=
--- a/src/http/v3/ngx_http_v3.h	Mon Dec 06 15:19:54 2021 +0300
+++ b/src/http/v3/ngx_http_v3.h	Tue Dec 07 13:01:28 2021 +0300
@@ -15,8 +15,8 @@
 
 #include <ngx_http_v3_parse.h>
 #include <ngx_http_v3_encode.h>
-#include <ngx_http_v3_streams.h>
-#include <ngx_http_v3_tables.h>
+#include <ngx_http_v3_uni.h>
+#include <ngx_http_v3_table.h>
 
 
 #define NGX_HTTP_V3_ALPN_PROTO                     "\x02h3"
--- a/src/http/v3/ngx_http_v3_streams.c	Mon Dec 06 15:19:54 2021 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,733 +0,0 @@
-
-/*
- * Copyright (C) Roman Arutyunyan
- * Copyright (C) Nginx, Inc.
- */
-
-
-#include <ngx_config.h>
-#include <ngx_core.h>
-#include <ngx_http.h>
-
-
-typedef struct {
-    ngx_http_v3_parse_uni_t         parse;
-    ngx_int_t                       index;
-} ngx_http_v3_uni_stream_t;
-
-
-typedef struct {
-    ngx_queue_t                     queue;
-    uint64_t                        id;
-    ngx_connection_t               *connection;
-    ngx_uint_t                     *npushing;
-} ngx_http_v3_push_t;
-
-
-static void ngx_http_v3_close_uni_stream(ngx_connection_t *c);
-static void ngx_http_v3_uni_read_handler(ngx_event_t *rev);
-static void ngx_http_v3_dummy_write_handler(ngx_event_t *wev);
-static void ngx_http_v3_push_cleanup(void *data);
-static ngx_connection_t *ngx_http_v3_get_uni_stream(ngx_connection_t *c,
-    ngx_uint_t type);
-
-
-void
-ngx_http_v3_init_uni_stream(ngx_connection_t *c)
-{
-    uint64_t                   n;
-    ngx_http_v3_uni_stream_t  *us;
-
-    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init uni stream");
-
-    n = c->quic->id >> 2;
-
-    if (n >= NGX_HTTP_V3_MAX_UNI_STREAMS) {
-        ngx_http_v3_finalize_connection(c,
-                                      NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR,
-                                      "reached maximum number of uni streams");
-        c->data = NULL;
-        ngx_http_v3_close_uni_stream(c);
-        return;
-    }
-
-    c->quic->cancelable = 1;
-
-    us = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_uni_stream_t));
-    if (us == NULL) {
-        ngx_http_v3_finalize_connection(c,
-                                        NGX_HTTP_V3_ERR_INTERNAL_ERROR,
-                                        "memory allocation error");
-        c->data = NULL;
-        ngx_http_v3_close_uni_stream(c);
-        return;
-    }
-
-    us->index = -1;
-
-    c->data = us;
-
-    c->read->handler = ngx_http_v3_uni_read_handler;
-    c->write->handler = ngx_http_v3_dummy_write_handler;
-
-    ngx_http_v3_uni_read_handler(c->read);
-}
-
-
-static void
-ngx_http_v3_close_uni_stream(ngx_connection_t *c)
-{
-    ngx_pool_t                *pool;
-    ngx_http_v3_session_t     *h3c;
-    ngx_http_v3_uni_stream_t  *us;
-
-    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 close stream");
-
-    us = c->data;
-
-    if (us && us->index >= 0) {
-        h3c = ngx_http_v3_get_session(c);
-        h3c->known_streams[us->index] = NULL;
-    }
-
-    c->destroyed = 1;
-
-    pool = c->pool;
-
-    ngx_close_connection(c);
-
-    ngx_destroy_pool(pool);
-}
-
-
-ngx_int_t
-ngx_http_v3_register_uni_stream(ngx_connection_t *c, uint64_t type)
-{
-    ngx_int_t                  index;
-    ngx_http_v3_session_t     *h3c;
-    ngx_http_v3_uni_stream_t  *us;
-
-    h3c = ngx_http_v3_get_session(c);
-
-    switch (type) {
-
-    case NGX_HTTP_V3_STREAM_ENCODER:
-
-        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                       "http3 encoder stream");
-        index = NGX_HTTP_V3_STREAM_CLIENT_ENCODER;
-        break;
-
-    case NGX_HTTP_V3_STREAM_DECODER:
-
-        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                       "http3 decoder stream");
-        index = NGX_HTTP_V3_STREAM_CLIENT_DECODER;
-        break;
-
-    case NGX_HTTP_V3_STREAM_CONTROL:
-
-        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                       "http3 control stream");
-        index = NGX_HTTP_V3_STREAM_CLIENT_CONTROL;
-
-        break;
-
-    default:
-
-        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                       "http3 stream 0x%02xL", type);
-
-        if (h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_ENCODER] == NULL
-            || h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_DECODER] == NULL
-            || h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_CONTROL] == NULL)
-        {
-            ngx_log_error(NGX_LOG_INFO, c->log, 0, "missing mandatory stream");
-            return NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR;
-        }
-
-        index = -1;
-    }
-
-    if (index >= 0) {
-        if (h3c->known_streams[index]) {
-            ngx_log_error(NGX_LOG_INFO, c->log, 0, "stream exists");
-            return NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR;
-        }
-
-        h3c->known_streams[index] = c;
-
-        us = c->data;
-        us->index = index;
-    }
-
-    return NGX_OK;
-}
-
-
-static void
-ngx_http_v3_uni_read_handler(ngx_event_t *rev)
-{
-    u_char                     buf[128];
-    ssize_t                    n;
-    ngx_buf_t                  b;
-    ngx_int_t                  rc;
-    ngx_connection_t          *c;
-    ngx_http_v3_session_t     *h3c;
-    ngx_http_v3_uni_stream_t  *us;
-
-    c = rev->data;
-    us = c->data;
-
-    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read handler");
-
-    ngx_memzero(&b, sizeof(ngx_buf_t));
-
-    while (rev->ready) {
-
-        n = c->recv(c, buf, sizeof(buf));
-
-        if (n == NGX_ERROR) {
-            rc = NGX_HTTP_V3_ERR_INTERNAL_ERROR;
-            goto failed;
-        }
-
-        if (n == 0) {
-            if (us->index >= 0) {
-                rc = NGX_HTTP_V3_ERR_CLOSED_CRITICAL_STREAM;
-                goto failed;
-            }
-
-            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read eof");
-            ngx_http_v3_close_uni_stream(c);
-            return;
-        }
-
-        if (n == NGX_AGAIN) {
-            break;
-        }
-
-        b.pos = buf;
-        b.last = buf + n;
-
-        h3c = ngx_http_v3_get_session(c);
-        h3c->total_bytes += n;
-
-        if (ngx_http_v3_check_flood(c) != NGX_OK) {
-            ngx_http_v3_close_uni_stream(c);
-            return;
-        }
-
-        rc = ngx_http_v3_parse_uni(c, &us->parse, &b);
-
-        if (rc == NGX_DONE) {
-            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                           "http3 read done");
-            ngx_http_v3_close_uni_stream(c);
-            return;
-        }
-
-        if (rc > 0) {
-            goto failed;
-        }
-
-        if (rc != NGX_AGAIN) {
-            rc = NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR;
-            goto failed;
-        }
-    }
-
-    if (ngx_handle_read_event(rev, 0) != NGX_OK) {
-        rc = NGX_HTTP_V3_ERR_INTERNAL_ERROR;
-        goto failed;
-    }
-
-    return;
-
-failed:
-
-    ngx_http_v3_finalize_connection(c, rc, "stream error");
-    ngx_http_v3_close_uni_stream(c);
-}
-
-
-static void
-ngx_http_v3_dummy_write_handler(ngx_event_t *wev)
-{
-    ngx_connection_t  *c;
-
-    c = wev->data;
-
-    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 dummy write handler");
-
-    if (ngx_handle_write_event(wev, 0) != NGX_OK) {
-        ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR,
-                                        NULL);
-        ngx_http_v3_close_uni_stream(c);
-    }
-}
-
-
-/* XXX async & buffered stream writes */
-
-ngx_connection_t *
-ngx_http_v3_create_push_stream(ngx_connection_t *c, uint64_t push_id)
-{
-    u_char                 *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 2];
-    size_t                  n;
-    ngx_connection_t       *sc;
-    ngx_pool_cleanup_t     *cln;
-    ngx_http_v3_push_t     *push;
-    ngx_http_v3_session_t  *h3c;
-
-    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                   "http3 create push stream id:%uL", push_id);
-
-    sc = ngx_quic_open_stream(c, 0);
-    if (sc == NULL) {
-        goto failed;
-    }
-
-    p = buf;
-    p = (u_char *) ngx_http_v3_encode_varlen_int(p, NGX_HTTP_V3_STREAM_PUSH);
-    p = (u_char *) ngx_http_v3_encode_varlen_int(p, push_id);
-    n = p - buf;
-
-    h3c = ngx_http_v3_get_session(c);
-    h3c->total_bytes += n;
-
-    if (sc->send(sc, buf, n) != (ssize_t) n) {
-        goto failed;
-    }
-
-    cln = ngx_pool_cleanup_add(sc->pool, sizeof(ngx_http_v3_push_t));
-    if (cln == NULL) {
-        goto failed;
-    }
-
-    h3c->npushing++;
-
-    cln->handler = ngx_http_v3_push_cleanup;
-
-    push = cln->data;
-    push->id = push_id;
-    push->connection = sc;
-    push->npushing = &h3c->npushing;
-
-    ngx_queue_insert_tail(&h3c->pushing, &push->queue);
-
-    return sc;
-
-failed:
-
-    ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create push stream");
-
-    ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR,
-                                    "failed to create push stream");
-    if (sc) {
-        ngx_http_v3_close_uni_stream(sc);
-    }
-
-    return NULL;
-}
-
-
-static void
-ngx_http_v3_push_cleanup(void *data)
-{
-    ngx_http_v3_push_t  *push = data;
-
-    ngx_queue_remove(&push->queue);
-    (*push->npushing)--;
-}
-
-
-static ngx_connection_t *
-ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type)
-{
-    u_char                     buf[NGX_HTTP_V3_VARLEN_INT_LEN];
-    size_t                     n;
-    ngx_int_t                  index;
-    ngx_connection_t          *sc;
-    ngx_http_v3_session_t     *h3c;
-    ngx_http_v3_uni_stream_t  *us;
-
-    switch (type) {
-    case NGX_HTTP_V3_STREAM_ENCODER:
-        index = NGX_HTTP_V3_STREAM_SERVER_ENCODER;
-        break;
-    case NGX_HTTP_V3_STREAM_DECODER:
-        index = NGX_HTTP_V3_STREAM_SERVER_DECODER;
-        break;
-    case NGX_HTTP_V3_STREAM_CONTROL:
-        index = NGX_HTTP_V3_STREAM_SERVER_CONTROL;
-        break;
-    default:
-        index = -1;
-    }
-
-    h3c = ngx_http_v3_get_session(c);
-
-    if (index >= 0) {
-        if (h3c->known_streams[index]) {
-            return h3c->known_streams[index];
-        }
-    }
-
-    sc = ngx_quic_open_stream(c, 0);
-    if (sc == NULL) {
-        goto failed;
-    }
-
-    sc->quic->cancelable = 1;
-
-    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                   "http3 create uni stream, type:%ui", type);
-
-    us = ngx_pcalloc(sc->pool, sizeof(ngx_http_v3_uni_stream_t));
-    if (us == NULL) {
-        goto failed;
-    }
-
-    us->index = index;
-
-    sc->data = us;
-
-    sc->read->handler = ngx_http_v3_uni_read_handler;
-    sc->write->handler = ngx_http_v3_dummy_write_handler;
-
-    if (index >= 0) {
-        h3c->known_streams[index] = sc;
-    }
-
-    n = (u_char *) ngx_http_v3_encode_varlen_int(buf, type) - buf;
-
-    h3c = ngx_http_v3_get_session(c);
-    h3c->total_bytes += n;
-
-    if (sc->send(sc, buf, n) != (ssize_t) n) {
-        goto failed;
-    }
-
-    return sc;
-
-failed:
-
-    ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create server stream");
-
-    ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR,
-                                    "failed to create server stream");
-    if (sc) {
-        ngx_http_v3_close_uni_stream(sc);
-    }
-
-    return NULL;
-}
-
-
-ngx_int_t
-ngx_http_v3_send_settings(ngx_connection_t *c)
-{
-    u_char                  *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 6];
-    size_t                   n;
-    ngx_connection_t        *cc;
-    ngx_http_v3_session_t   *h3c;
-    ngx_http_v3_srv_conf_t  *h3scf;
-
-    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send settings");
-
-    cc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL);
-    if (cc == NULL) {
-        return NGX_ERROR;
-    }
-
-    h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module);
-
-    n = ngx_http_v3_encode_varlen_int(NULL,
-                                      NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY);
-    n += ngx_http_v3_encode_varlen_int(NULL, h3scf->max_table_capacity);
-    n += ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_PARAM_BLOCKED_STREAMS);
-    n += ngx_http_v3_encode_varlen_int(NULL, h3scf->max_blocked_streams);
-
-    p = (u_char *) ngx_http_v3_encode_varlen_int(buf,
-                                                 NGX_HTTP_V3_FRAME_SETTINGS);
-    p = (u_char *) ngx_http_v3_encode_varlen_int(p, n);
-    p = (u_char *) ngx_http_v3_encode_varlen_int(p,
-                                         NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY);
-    p = (u_char *) ngx_http_v3_encode_varlen_int(p, h3scf->max_table_capacity);
-    p = (u_char *) ngx_http_v3_encode_varlen_int(p,
-                                            NGX_HTTP_V3_PARAM_BLOCKED_STREAMS);
-    p = (u_char *) ngx_http_v3_encode_varlen_int(p, h3scf->max_blocked_streams);
-    n = p - buf;
-
-    h3c = ngx_http_v3_get_session(c);
-    h3c->total_bytes += n;
-
-    if (cc->send(cc, buf, n) != (ssize_t) n) {
-        goto failed;
-    }
-
-    return NGX_OK;
-
-failed:
-
-    ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send settings");
-
-    ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD,
-                                    "failed to send settings");
-    ngx_http_v3_close_uni_stream(cc);
-
-    return NGX_ERROR;
-}
-
-
-ngx_int_t
-ngx_http_v3_send_goaway(ngx_connection_t *c, uint64_t id)
-{
-    u_char                 *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 3];
-    size_t                  n;
-    ngx_connection_t       *cc;
-    ngx_http_v3_session_t  *h3c;
-
-    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send goaway %uL", id);
-
-    cc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL);
-    if (cc == NULL) {
-        return NGX_ERROR;
-    }
-
-    n = ngx_http_v3_encode_varlen_int(NULL, id);
-    p = (u_char *) ngx_http_v3_encode_varlen_int(buf, NGX_HTTP_V3_FRAME_GOAWAY);
-    p = (u_char *) ngx_http_v3_encode_varlen_int(p, n);
-    p = (u_char *) ngx_http_v3_encode_varlen_int(p, id);
-    n = p - buf;
-
-    h3c = ngx_http_v3_get_session(c);
-    h3c->total_bytes += n;
-
-    if (cc->send(cc, buf, n) != (ssize_t) n) {
-        goto failed;
-    }
-
-    return NGX_OK;
-
-failed:
-
-    ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send goaway");
-
-    ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD,
-                                    "failed to send goaway");
-    ngx_http_v3_close_uni_stream(cc);
-
-    return NGX_ERROR;
-}
-
-
-ngx_int_t
-ngx_http_v3_send_ack_section(ngx_connection_t *c, ngx_uint_t stream_id)
-{
-    u_char                  buf[NGX_HTTP_V3_PREFIX_INT_LEN];
-    size_t                  n;
-    ngx_connection_t       *dc;
-    ngx_http_v3_session_t  *h3c;
-
-    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                   "http3 send section acknowledgement %ui", stream_id);
-
-    dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER);
-    if (dc == NULL) {
-        return NGX_ERROR;
-    }
-
-    buf[0] = 0x80;
-    n = (u_char *) ngx_http_v3_encode_prefix_int(buf, stream_id, 7) - buf;
-
-    h3c = ngx_http_v3_get_session(c);
-    h3c->total_bytes += n;
-
-    if (dc->send(dc, buf, n) != (ssize_t) n) {
-        goto failed;
-    }
-
-    return NGX_OK;
-
-failed:
-
-    ngx_log_error(NGX_LOG_ERR, c->log, 0,
-                  "failed to send section acknowledgement");
-
-    ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD,
-                                    "failed to send section acknowledgement");
-    ngx_http_v3_close_uni_stream(dc);
-
-    return NGX_ERROR;
-}
-
-
-ngx_int_t
-ngx_http_v3_send_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id)
-{
-    u_char                  buf[NGX_HTTP_V3_PREFIX_INT_LEN];
-    size_t                  n;
-    ngx_connection_t       *dc;
-    ngx_http_v3_session_t  *h3c;
-
-    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                   "http3 send stream cancellation %ui", stream_id);
-
-    dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER);
-    if (dc == NULL) {
-        return NGX_ERROR;
-    }
-
-    buf[0] = 0x40;
-    n = (u_char *) ngx_http_v3_encode_prefix_int(buf, stream_id, 6) - buf;
-
-    h3c = ngx_http_v3_get_session(c);
-    h3c->total_bytes += n;
-
-    if (dc->send(dc, buf, n) != (ssize_t) n) {
-        goto failed;
-    }
-
-    return NGX_OK;
-
-failed:
-
-    ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send stream cancellation");
-
-    ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD,
-                                    "failed to send stream cancellation");
-    ngx_http_v3_close_uni_stream(dc);
-
-    return NGX_ERROR;
-}
-
-
-ngx_int_t
-ngx_http_v3_send_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc)
-{
-    u_char                  buf[NGX_HTTP_V3_PREFIX_INT_LEN];
-    size_t                  n;
-    ngx_connection_t       *dc;
-    ngx_http_v3_session_t  *h3c;
-
-    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                   "http3 send insert count increment %ui", inc);
-
-    dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER);
-    if (dc == NULL) {
-        return NGX_ERROR;
-    }
-
-    buf[0] = 0;
-    n = (u_char *) ngx_http_v3_encode_prefix_int(buf, inc, 6) - buf;
-
-    h3c = ngx_http_v3_get_session(c);
-    h3c->total_bytes += n;
-
-    if (dc->send(dc, buf, n) != (ssize_t) n) {
-        goto failed;
-    }
-
-    return NGX_OK;
-
-failed:
-
-    ngx_log_error(NGX_LOG_ERR, c->log, 0,
-                  "failed to send insert count increment");
-
-    ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD,
-                                    "failed to send insert count increment");
-    ngx_http_v3_close_uni_stream(dc);
-
-    return NGX_ERROR;
-}
-
-
-ngx_int_t
-ngx_http_v3_set_max_push_id(ngx_connection_t *c, uint64_t max_push_id)
-{
-    ngx_http_v3_session_t  *h3c;
-
-    h3c = ngx_http_v3_get_session(c);
-
-    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                   "http3 MAX_PUSH_ID:%uL", max_push_id);
-
-    if (h3c->max_push_id != (uint64_t) -1 && max_push_id < h3c->max_push_id) {
-        return NGX_HTTP_V3_ERR_ID_ERROR;
-    }
-
-    h3c->max_push_id = max_push_id;
-
-    return NGX_OK;
-}
-
-
-ngx_int_t
-ngx_http_v3_goaway(ngx_connection_t *c, uint64_t push_id)
-{
-    ngx_http_v3_session_t  *h3c;
-
-    h3c = ngx_http_v3_get_session(c);
-
-    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 GOAWAY:%uL", push_id);
-
-    h3c->goaway_push_id = push_id;
-
-    return NGX_OK;
-}
-
-
-ngx_int_t
-ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id)
-{
-    ngx_queue_t            *q;
-    ngx_http_request_t     *r;
-    ngx_http_v3_push_t     *push;
-    ngx_http_v3_session_t  *h3c;
-
-    h3c = ngx_http_v3_get_session(c);
-
-    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                   "http3 CANCEL_PUSH:%uL", push_id);
-
-    if (push_id >= h3c->next_push_id) {
-        return NGX_HTTP_V3_ERR_ID_ERROR;
-    }
-
-    for (q = ngx_queue_head(&h3c->pushing);
-         q != ngx_queue_sentinel(&h3c->pushing);
-         q = ngx_queue_next(&h3c->pushing))
-    {
-        push = (ngx_http_v3_push_t *) q;
-
-        if (push->id != push_id) {
-            continue;
-        }
-
-        r = push->connection->data;
-
-        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
-                       "http3 cancel push");
-
-        ngx_http_finalize_request(r, NGX_HTTP_CLOSE);
-
-        break;
-    }
-
-    return NGX_OK;
-}
-
-
-ngx_int_t
-ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id)
-{
-    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                   "http3 cancel stream %ui", stream_id);
-
-    /* we do not use dynamic tables */
-
-    return NGX_OK;
-}
--- a/src/http/v3/ngx_http_v3_streams.h	Mon Dec 06 15:19:54 2021 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,38 +0,0 @@
-
-/*
- * Copyright (C) Roman Arutyunyan
- * Copyright (C) Nginx, Inc.
- */
-
-
-#ifndef _NGX_HTTP_V3_STREAMS_H_INCLUDED_
-#define _NGX_HTTP_V3_STREAMS_H_INCLUDED_
-
-
-#include <ngx_config.h>
-#include <ngx_core.h>
-#include <ngx_http.h>
-
-
-void ngx_http_v3_init_uni_stream(ngx_connection_t *c);
-ngx_int_t ngx_http_v3_register_uni_stream(ngx_connection_t *c, uint64_t type);
-
-ngx_connection_t *ngx_http_v3_create_push_stream(ngx_connection_t *c,
-    uint64_t push_id);
-ngx_int_t ngx_http_v3_set_max_push_id(ngx_connection_t *c,
-    uint64_t max_push_id);
-ngx_int_t ngx_http_v3_goaway(ngx_connection_t *c, uint64_t push_id);
-ngx_int_t ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id);
-ngx_int_t ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id);
-
-ngx_int_t ngx_http_v3_send_settings(ngx_connection_t *c);
-ngx_int_t ngx_http_v3_send_goaway(ngx_connection_t *c, uint64_t id);
-ngx_int_t ngx_http_v3_send_ack_section(ngx_connection_t *c,
-    ngx_uint_t stream_id);
-ngx_int_t ngx_http_v3_send_cancel_stream(ngx_connection_t *c,
-    ngx_uint_t stream_id);
-ngx_int_t ngx_http_v3_send_inc_insert_count(ngx_connection_t *c,
-    ngx_uint_t inc);
-
-
-#endif /* _NGX_HTTP_V3_STREAMS_H_INCLUDED_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/http/v3/ngx_http_v3_table.c	Tue Dec 07 13:01:28 2021 +0300
@@ -0,0 +1,678 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#define ngx_http_v3_table_entry_size(n, v) ((n)->len + (v)->len + 32)
+
+
+static ngx_int_t ngx_http_v3_evict(ngx_connection_t *c, size_t need);
+static void ngx_http_v3_unblock(void *data);
+static ngx_int_t ngx_http_v3_new_entry(ngx_connection_t *c);
+
+
+typedef struct {
+    ngx_queue_t        queue;
+    ngx_connection_t  *connection;
+    ngx_uint_t        *nblocked;
+} ngx_http_v3_block_t;
+
+
+static ngx_http_v3_field_t  ngx_http_v3_static_table[] = {
+
+    { ngx_string(":authority"),            ngx_string("") },
+    { ngx_string(":path"),                 ngx_string("/") },
+    { ngx_string("age"),                   ngx_string("0") },
+    { ngx_string("content-disposition"),   ngx_string("") },
+    { ngx_string("content-length"),        ngx_string("0") },
+    { ngx_string("cookie"),                ngx_string("") },
+    { ngx_string("date"),                  ngx_string("") },
+    { ngx_string("etag"),                  ngx_string("") },
+    { ngx_string("if-modified-since"),     ngx_string("") },
+    { ngx_string("if-none-match"),         ngx_string("") },
+    { ngx_string("last-modified"),         ngx_string("") },
+    { ngx_string("link"),                  ngx_string("") },
+    { ngx_string("location"),              ngx_string("") },
+    { ngx_string("referer"),               ngx_string("") },
+    { ngx_string("set-cookie"),            ngx_string("") },
+    { ngx_string(":method"),               ngx_string("CONNECT") },
+    { ngx_string(":method"),               ngx_string("DELETE") },
+    { ngx_string(":method"),               ngx_string("GET") },
+    { ngx_string(":method"),               ngx_string("HEAD") },
+    { ngx_string(":method"),               ngx_string("OPTIONS") },
+    { ngx_string(":method"),               ngx_string("POST") },
+    { ngx_string(":method"),               ngx_string("PUT") },
+    { ngx_string(":scheme"),               ngx_string("http") },
+    { ngx_string(":scheme"),               ngx_string("https") },
+    { ngx_string(":status"),               ngx_string("103") },
+    { ngx_string(":status"),               ngx_string("200") },
+    { ngx_string(":status"),               ngx_string("304") },
+    { ngx_string(":status"),               ngx_string("404") },
+    { ngx_string(":status"),               ngx_string("503") },
+    { ngx_string("accept"),                ngx_string("*/*") },
+    { ngx_string("accept"),
+          ngx_string("application/dns-message") },
+    { ngx_string("accept-encoding"),       ngx_string("gzip, deflate, br") },
+    { ngx_string("accept-ranges"),         ngx_string("bytes") },
+    { ngx_string("access-control-allow-headers"),
+                                           ngx_string("cache-control") },
+    { ngx_string("access-control-allow-headers"),
+                                           ngx_string("content-type") },
+    { ngx_string("access-control-allow-origin"),
+                                           ngx_string("*") },
+    { ngx_string("cache-control"),         ngx_string("max-age=0") },
+    { ngx_string("cache-control"),         ngx_string("max-age=2592000") },
+    { ngx_string("cache-control"),         ngx_string("max-age=604800") },
+    { ngx_string("cache-control"),         ngx_string("no-cache") },
+    { ngx_string("cache-control"),         ngx_string("no-store") },
+    { ngx_string("cache-control"),
+          ngx_string("public, max-age=31536000") },
+    { ngx_string("content-encoding"),      ngx_string("br") },
+    { ngx_string("content-encoding"),      ngx_string("gzip") },
+    { ngx_string("content-type"),
+          ngx_string("application/dns-message") },
+    { ngx_string("content-type"),
+          ngx_string("application/javascript") },
+    { ngx_string("content-type"),          ngx_string("application/json") },
+    { ngx_string("content-type"),
+          ngx_string("application/x-www-form-urlencoded") },
+    { ngx_string("content-type"),          ngx_string("image/gif") },
+    { ngx_string("content-type"),          ngx_string("image/jpeg") },
+    { ngx_string("content-type"),          ngx_string("image/png") },
+    { ngx_string("content-type"),          ngx_string("text/css") },
+    { ngx_string("content-type"),
+          ngx_string("text/html;charset=utf-8") },
+    { ngx_string("content-type"),          ngx_string("text/plain") },
+    { ngx_string("content-type"),
+          ngx_string("text/plain;charset=utf-8") },
+    { ngx_string("range"),                 ngx_string("bytes=0-") },
+    { ngx_string("strict-transport-security"),
+                                           ngx_string("max-age=31536000") },
+    { ngx_string("strict-transport-security"),
+          ngx_string("max-age=31536000;includesubdomains") },
+    { ngx_string("strict-transport-security"),
+          ngx_string("max-age=31536000;includesubdomains;preload") },
+    { ngx_string("vary"),                  ngx_string("accept-encoding") },
+    { ngx_string("vary"),                  ngx_string("origin") },
+    { ngx_string("x-content-type-options"),
+                                           ngx_string("nosniff") },
+    { ngx_string("x-xss-protection"),      ngx_string("1;mode=block") },
+    { ngx_string(":status"),               ngx_string("100") },
+    { ngx_string(":status"),               ngx_string("204") },
+    { ngx_string(":status"),               ngx_string("206") },
+    { ngx_string(":status"),               ngx_string("302") },
+    { ngx_string(":status"),               ngx_string("400") },
+    { ngx_string(":status"),               ngx_string("403") },
+    { ngx_string(":status"),               ngx_string("421") },
+    { ngx_string(":status"),               ngx_string("425") },
+    { ngx_string(":status"),               ngx_string("500") },
+    { ngx_string("accept-language"),       ngx_string("") },
+    { ngx_string("access-control-allow-credentials"),
+                                           ngx_string("FALSE") },
+    { ngx_string("access-control-allow-credentials"),
+                                           ngx_string("TRUE") },
+    { ngx_string("access-control-allow-headers"),
+                                           ngx_string("*") },
+    { ngx_string("access-control-allow-methods"),
+                                           ngx_string("get") },
+    { ngx_string("access-control-allow-methods"),
+                                           ngx_string("get, post, options") },
+    { ngx_string("access-control-allow-methods"),
+                                           ngx_string("options") },
+    { ngx_string("access-control-expose-headers"),
+                                           ngx_string("content-length") },
+    { ngx_string("access-control-request-headers"),
+                                           ngx_string("content-type") },
+    { ngx_string("access-control-request-method"),
+                                           ngx_string("get") },
+    { ngx_string("access-control-request-method"),
+                                           ngx_string("post") },
+    { ngx_string("alt-svc"),               ngx_string("clear") },
+    { ngx_string("authorization"),         ngx_string("") },
+    { ngx_string("content-security-policy"),
+          ngx_string("script-src 'none';object-src 'none';base-uri 'none'") },
+    { ngx_string("early-data"),            ngx_string("1") },
+    { ngx_string("expect-ct"),             ngx_string("") },
+    { ngx_string("forwarded"),             ngx_string("") },
+    { ngx_string("if-range"),              ngx_string("") },
+    { ngx_string("origin"),                ngx_string("") },
+    { ngx_string("purpose"),               ngx_string("prefetch") },
+    { ngx_string("server"),                ngx_string("") },
+    { ngx_string("timing-allow-origin"),   ngx_string("*") },
+    { ngx_string("upgrade-insecure-requests"),
+                                           ngx_string("1") },
+    { ngx_string("user-agent"),            ngx_string("") },
+    { ngx_string("x-forwarded-for"),       ngx_string("") },
+    { ngx_string("x-frame-options"),       ngx_string("deny") },
+    { ngx_string("x-frame-options"),       ngx_string("sameorigin") }
+};
+
+
+ngx_int_t
+ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic,
+    ngx_uint_t index, ngx_str_t *value)
+{
+    ngx_str_t                     name;
+    ngx_http_v3_session_t        *h3c;
+    ngx_http_v3_dynamic_table_t  *dt;
+
+    if (dynamic) {
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                       "http3 ref insert dynamic[%ui] \"%V\"", index, value);
+
+        h3c = ngx_http_v3_get_session(c);
+        dt = &h3c->table;
+
+        if (dt->base + dt->nelts <= index) {
+            return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
+        }
+
+        index = dt->base + dt->nelts - 1 - index;
+
+        if (ngx_http_v3_lookup(c, index, &name, NULL) != NGX_OK) {
+            return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
+        }
+
+    } else {
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                       "http3 ref insert static[%ui] \"%V\"", index, value);
+
+        if (ngx_http_v3_lookup_static(c, index, &name, NULL) != NGX_OK) {
+            return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
+        }
+    }
+
+    return ngx_http_v3_insert(c, &name, value);
+}
+
+
+ngx_int_t
+ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, ngx_str_t *value)
+{
+    u_char                       *p;
+    size_t                        size;
+    ngx_http_v3_field_t          *field;
+    ngx_http_v3_session_t        *h3c;
+    ngx_http_v3_dynamic_table_t  *dt;
+
+    size = ngx_http_v3_table_entry_size(name, value);
+
+    if (ngx_http_v3_evict(c, size) != NGX_OK) {
+        return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
+    }
+
+    h3c = ngx_http_v3_get_session(c);
+    dt = &h3c->table;
+
+    ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http3 insert [%ui] \"%V\":\"%V\", size:%uz",
+                   dt->base + dt->nelts, name, value, size);
+
+    p = ngx_alloc(sizeof(ngx_http_v3_field_t) + name->len + value->len,
+                  c->log);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    field = (ngx_http_v3_field_t *) p;
+
+    field->name.data = p + sizeof(ngx_http_v3_field_t);
+    field->name.len = name->len;
+    field->value.data = ngx_cpymem(field->name.data, name->data, name->len);
+    field->value.len = value->len;
+    ngx_memcpy(field->value.data, value->data, value->len);
+
+    dt->elts[dt->nelts++] = field;
+    dt->size += size;
+
+    /* TODO increment can be sent less often */
+
+    if (ngx_http_v3_send_inc_insert_count(c, 1) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    if (ngx_http_v3_new_entry(c) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity)
+{
+    ngx_uint_t                     max, prev_max;
+    ngx_http_v3_field_t          **elts;
+    ngx_http_v3_session_t         *h3c;
+    ngx_http_v3_srv_conf_t        *h3scf;
+    ngx_http_v3_dynamic_table_t   *dt;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http3 set capacity %ui", capacity);
+
+    h3c = ngx_http_v3_get_session(c);
+    h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module);
+
+    if (capacity > h3scf->max_table_capacity) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                      "client exceeded http3_max_table_capacity limit");
+        return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
+    }
+
+    dt = &h3c->table;
+
+    if (dt->size > capacity) {
+        if (ngx_http_v3_evict(c, dt->size - capacity) != NGX_OK) {
+            return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
+        }
+    }
+
+    max = capacity / 32;
+    prev_max = dt->capacity / 32;
+
+    if (max > prev_max) {
+        elts = ngx_alloc(max * sizeof(void *), c->log);
+        if (elts == NULL) {
+            return NGX_ERROR;
+        }
+
+        if (dt->elts) {
+            ngx_memcpy(elts, dt->elts, dt->nelts * sizeof(void *));
+            ngx_free(dt->elts);
+        }
+
+        dt->elts = elts;
+    }
+
+    dt->capacity = capacity;
+
+    return NGX_OK;
+}
+
+
+void
+ngx_http_v3_cleanup_table(ngx_http_v3_session_t *h3c)
+{
+    ngx_uint_t                    n;
+    ngx_http_v3_dynamic_table_t  *dt;
+
+    dt = &h3c->table;
+
+    if (dt->elts == NULL) {
+        return;
+    }
+
+    for (n = 0; n < dt->nelts; n++) {
+        ngx_free(dt->elts[n]);
+    }
+
+    ngx_free(dt->elts);
+}
+
+
+static ngx_int_t
+ngx_http_v3_evict(ngx_connection_t *c, size_t need)
+{
+    size_t                        size, target;
+    ngx_uint_t                    n;
+    ngx_http_v3_field_t          *field;
+    ngx_http_v3_session_t        *h3c;
+    ngx_http_v3_dynamic_table_t  *dt;
+
+    h3c = ngx_http_v3_get_session(c);
+    dt = &h3c->table;
+
+    if (need > dt->capacity) {
+        ngx_log_error(NGX_LOG_ERR, c->log, 0,
+                      "not enough dynamic table capacity");
+        return NGX_ERROR;
+    }
+
+    target = dt->capacity - need;
+    n = 0;
+
+    while (dt->size > target) {
+        field = dt->elts[n++];
+        size = ngx_http_v3_table_entry_size(&field->name, &field->value);
+
+        ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                       "http3 evict [%ui] \"%V\":\"%V\" size:%uz",
+                       dt->base, &field->name, &field->value, size);
+
+        ngx_free(field);
+        dt->size -= size;
+    }
+
+    if (n) {
+        dt->nelts -= n;
+        dt->base += n;
+        ngx_memmove(dt->elts, &dt->elts[n], dt->nelts * sizeof(void *));
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index)
+{
+    ngx_str_t                     name, value;
+    ngx_http_v3_session_t        *h3c;
+    ngx_http_v3_dynamic_table_t  *dt;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 duplicate %ui", index);
+
+    h3c = ngx_http_v3_get_session(c);
+    dt = &h3c->table;
+
+    if (dt->base + dt->nelts <= index) {
+        return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
+    }
+
+    index = dt->base + dt->nelts - 1 - index;
+
+    if (ngx_http_v3_lookup(c, index, &name, &value) != NGX_OK) {
+        return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
+    }
+
+    return ngx_http_v3_insert(c, &name, &value);
+}
+
+
+ngx_int_t
+ngx_http_v3_ack_section(ngx_connection_t *c, ngx_uint_t stream_id)
+{
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http3 ack section %ui", stream_id);
+
+    /* we do not use dynamic tables */
+
+    return NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR;
+}
+
+
+ngx_int_t
+ngx_http_v3_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc)
+{
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http3 increment insert count %ui", inc);
+
+    /* we do not use dynamic tables */
+
+    return NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR;
+}
+
+
+ngx_int_t
+ngx_http_v3_lookup_static(ngx_connection_t *c, ngx_uint_t index,
+    ngx_str_t *name, ngx_str_t *value)
+{
+    ngx_uint_t            nelts;
+    ngx_http_v3_field_t  *field;
+
+    nelts = sizeof(ngx_http_v3_static_table)
+            / sizeof(ngx_http_v3_static_table[0]);
+
+    if (index >= nelts) {
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                       "http3 static[%ui] lookup out of bounds: %ui",
+                       index, nelts);
+        return NGX_ERROR;
+    }
+
+    field = &ngx_http_v3_static_table[index];
+
+    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http3 static[%ui] lookup \"%V\":\"%V\"",
+                   index, &field->name, &field->value);
+
+    if (name) {
+        *name = field->name;
+    }
+
+    if (value) {
+        *value = field->value;
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_lookup(ngx_connection_t *c, ngx_uint_t index, ngx_str_t *name,
+    ngx_str_t *value)
+{
+    ngx_http_v3_field_t          *field;
+    ngx_http_v3_session_t        *h3c;
+    ngx_http_v3_dynamic_table_t  *dt;
+
+    h3c = ngx_http_v3_get_session(c);
+    dt = &h3c->table;
+
+    if (index < dt->base || index - dt->base >= dt->nelts) {
+        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                       "http3 dynamic[%ui] lookup out of bounds: [%ui,%ui]",
+                       index, dt->base, dt->base + dt->nelts);
+        return NGX_ERROR;
+    }
+
+    field = dt->elts[index - dt->base];
+
+    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http3 dynamic[%ui] lookup \"%V\":\"%V\"",
+                   index, &field->name, &field->value);
+
+    if (name) {
+        *name = field->name;
+    }
+
+    if (value) {
+        *value = field->value;
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_decode_insert_count(ngx_connection_t *c, ngx_uint_t *insert_count)
+{
+    ngx_uint_t                    max_entries, full_range, max_value,
+                                  max_wrapped, req_insert_count;
+    ngx_http_v3_srv_conf_t       *h3scf;
+    ngx_http_v3_session_t        *h3c;
+    ngx_http_v3_dynamic_table_t  *dt;
+
+    /* QPACK 4.5.1.1. Required Insert Count */
+
+    if (*insert_count == 0) {
+        return NGX_OK;
+    }
+
+    h3c = ngx_http_v3_get_session(c);
+    dt = &h3c->table;
+
+    h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module);
+
+    max_entries = h3scf->max_table_capacity / 32;
+    full_range = 2 * max_entries;
+
+    if (*insert_count > full_range) {
+        return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED;
+    }
+
+    max_value = dt->base + dt->nelts + max_entries;
+    max_wrapped = (max_value / full_range) * full_range;
+    req_insert_count = max_wrapped + *insert_count - 1;
+
+    if (req_insert_count > max_value) {
+        if (req_insert_count <= full_range) {
+            return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED;
+        }
+
+        req_insert_count -= full_range;
+    }
+
+    if (req_insert_count == 0) {
+        return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http3 decode insert_count %ui -> %ui",
+                   *insert_count, req_insert_count);
+
+    *insert_count = req_insert_count;
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_check_insert_count(ngx_connection_t *c, ngx_uint_t insert_count)
+{
+    size_t                        n;
+    ngx_pool_cleanup_t           *cln;
+    ngx_http_v3_block_t          *block;
+    ngx_http_v3_session_t        *h3c;
+    ngx_http_v3_srv_conf_t       *h3scf;
+    ngx_http_v3_dynamic_table_t  *dt;
+
+    h3c = ngx_http_v3_get_session(c);
+    dt = &h3c->table;
+
+    n = dt->base + dt->nelts;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http3 check insert count req:%ui, have:%ui",
+                   insert_count, n);
+
+    if (n >= insert_count) {
+        return NGX_OK;
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 block stream");
+
+    block = NULL;
+
+    for (cln = c->pool->cleanup; cln; cln = cln->next) {
+        if (cln->handler == ngx_http_v3_unblock) {
+            block = cln->data;
+            break;
+        }
+    }
+
+    if (block == NULL) {
+        cln = ngx_pool_cleanup_add(c->pool, sizeof(ngx_http_v3_block_t));
+        if (cln == NULL) {
+            return NGX_ERROR;
+        }
+
+        cln->handler = ngx_http_v3_unblock;
+
+        block = cln->data;
+        block->queue.prev = NULL;
+        block->connection = c;
+        block->nblocked = &h3c->nblocked;
+    }
+
+    if (block->queue.prev == NULL) {
+        h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module);
+
+        if (h3c->nblocked == h3scf->max_blocked_streams) {
+            ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                          "client exceeded http3_max_blocked_streams limit");
+
+            ngx_http_v3_finalize_connection(c,
+                                          NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED,
+                                          "too many blocked streams");
+            return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED;
+        }
+
+        h3c->nblocked++;
+        ngx_queue_insert_tail(&h3c->blocked, &block->queue);
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http3 blocked:%ui", h3c->nblocked);
+
+    return NGX_BUSY;
+}
+
+
+static void
+ngx_http_v3_unblock(void *data)
+{
+    ngx_http_v3_block_t  *block = data;
+
+    if (block->queue.prev) {
+        ngx_queue_remove(&block->queue);
+        block->queue.prev = NULL;
+        (*block->nblocked)--;
+    }
+}
+
+
+static ngx_int_t
+ngx_http_v3_new_entry(ngx_connection_t *c)
+{
+    ngx_queue_t            *q;
+    ngx_connection_t       *bc;
+    ngx_http_v3_block_t    *block;
+    ngx_http_v3_session_t  *h3c;
+
+    h3c = ngx_http_v3_get_session(c);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http3 new dynamic entry, blocked:%ui", h3c->nblocked);
+
+    while (!ngx_queue_empty(&h3c->blocked)) {
+        q = ngx_queue_head(&h3c->blocked);
+        block = (ngx_http_v3_block_t *) q;
+        bc = block->connection;
+
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, bc->log, 0, "http3 unblock stream");
+
+        ngx_http_v3_unblock(block);
+        ngx_post_event(bc->read, &ngx_posted_events);
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_set_param(ngx_connection_t *c, uint64_t id, uint64_t value)
+{
+    switch (id) {
+
+    case NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY:
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                       "http3 param QPACK_MAX_TABLE_CAPACITY:%uL", value);
+        break;
+
+    case NGX_HTTP_V3_PARAM_MAX_HEADER_LIST_SIZE:
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                       "http3 param SETTINGS_MAX_HEADER_LIST_SIZE:%uL", value);
+        break;
+
+    case NGX_HTTP_V3_PARAM_BLOCKED_STREAMS:
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                       "http3 param QPACK_BLOCKED_STREAMS:%uL", value);
+        break;
+
+    default:
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                       "http3 param #%uL:%uL", id, value);
+    }
+
+    return NGX_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/http/v3/ngx_http_v3_table.h	Tue Dec 07 13:01:28 2021 +0300
@@ -0,0 +1,53 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_HTTP_V3_TABLE_H_INCLUDED_
+#define _NGX_HTTP_V3_TABLE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    ngx_str_t                     name;
+    ngx_str_t                     value;
+} ngx_http_v3_field_t;
+
+
+typedef struct {
+    ngx_http_v3_field_t         **elts;
+    ngx_uint_t                    nelts;
+    ngx_uint_t                    base;
+    size_t                        size;
+    size_t                        capacity;
+} ngx_http_v3_dynamic_table_t;
+
+
+void ngx_http_v3_cleanup_table(ngx_http_v3_session_t *h3c);
+ngx_int_t ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic,
+    ngx_uint_t index, ngx_str_t *value);
+ngx_int_t ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name,
+    ngx_str_t *value);
+ngx_int_t ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity);
+ngx_int_t ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index);
+ngx_int_t ngx_http_v3_ack_section(ngx_connection_t *c, ngx_uint_t stream_id);
+ngx_int_t ngx_http_v3_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc);
+ngx_int_t ngx_http_v3_lookup_static(ngx_connection_t *c, ngx_uint_t index,
+    ngx_str_t *name, ngx_str_t *value);
+ngx_int_t ngx_http_v3_lookup(ngx_connection_t *c, ngx_uint_t index,
+    ngx_str_t *name, ngx_str_t *value);
+ngx_int_t ngx_http_v3_decode_insert_count(ngx_connection_t *c,
+    ngx_uint_t *insert_count);
+ngx_int_t ngx_http_v3_check_insert_count(ngx_connection_t *c,
+    ngx_uint_t insert_count);
+ngx_int_t ngx_http_v3_set_param(ngx_connection_t *c, uint64_t id,
+    uint64_t value);
+
+
+#endif /* _NGX_HTTP_V3_TABLE_H_INCLUDED_ */
--- a/src/http/v3/ngx_http_v3_tables.c	Mon Dec 06 15:19:54 2021 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,678 +0,0 @@
-
-/*
- * Copyright (C) Roman Arutyunyan
- * Copyright (C) Nginx, Inc.
- */
-
-
-#include <ngx_config.h>
-#include <ngx_core.h>
-#include <ngx_http.h>
-
-
-#define ngx_http_v3_table_entry_size(n, v) ((n)->len + (v)->len + 32)
-
-
-static ngx_int_t ngx_http_v3_evict(ngx_connection_t *c, size_t need);
-static void ngx_http_v3_unblock(void *data);
-static ngx_int_t ngx_http_v3_new_entry(ngx_connection_t *c);
-
-
-typedef struct {
-    ngx_queue_t        queue;
-    ngx_connection_t  *connection;
-    ngx_uint_t        *nblocked;
-} ngx_http_v3_block_t;
-
-
-static ngx_http_v3_field_t  ngx_http_v3_static_table[] = {
-
-    { ngx_string(":authority"),            ngx_string("") },
-    { ngx_string(":path"),                 ngx_string("/") },
-    { ngx_string("age"),                   ngx_string("0") },
-    { ngx_string("content-disposition"),   ngx_string("") },
-    { ngx_string("content-length"),        ngx_string("0") },
-    { ngx_string("cookie"),                ngx_string("") },
-    { ngx_string("date"),                  ngx_string("") },
-    { ngx_string("etag"),                  ngx_string("") },
-    { ngx_string("if-modified-since"),     ngx_string("") },
-    { ngx_string("if-none-match"),         ngx_string("") },
-    { ngx_string("last-modified"),         ngx_string("") },
-    { ngx_string("link"),                  ngx_string("") },
-    { ngx_string("location"),              ngx_string("") },
-    { ngx_string("referer"),               ngx_string("") },
-    { ngx_string("set-cookie"),            ngx_string("") },
-    { ngx_string(":method"),               ngx_string("CONNECT") },
-    { ngx_string(":method"),               ngx_string("DELETE") },
-    { ngx_string(":method"),               ngx_string("GET") },
-    { ngx_string(":method"),               ngx_string("HEAD") },
-    { ngx_string(":method"),               ngx_string("OPTIONS") },
-    { ngx_string(":method"),               ngx_string("POST") },
-    { ngx_string(":method"),               ngx_string("PUT") },
-    { ngx_string(":scheme"),               ngx_string("http") },
-    { ngx_string(":scheme"),               ngx_string("https") },
-    { ngx_string(":status"),               ngx_string("103") },
-    { ngx_string(":status"),               ngx_string("200") },
-    { ngx_string(":status"),               ngx_string("304") },
-    { ngx_string(":status"),               ngx_string("404") },
-    { ngx_string(":status"),               ngx_string("503") },
-    { ngx_string("accept"),                ngx_string("*/*") },
-    { ngx_string("accept"),
-          ngx_string("application/dns-message") },
-    { ngx_string("accept-encoding"),       ngx_string("gzip, deflate, br") },
-    { ngx_string("accept-ranges"),         ngx_string("bytes") },
-    { ngx_string("access-control-allow-headers"),
-                                           ngx_string("cache-control") },
-    { ngx_string("access-control-allow-headers"),
-                                           ngx_string("content-type") },
-    { ngx_string("access-control-allow-origin"),
-                                           ngx_string("*") },
-    { ngx_string("cache-control"),         ngx_string("max-age=0") },
-    { ngx_string("cache-control"),         ngx_string("max-age=2592000") },
-    { ngx_string("cache-control"),         ngx_string("max-age=604800") },
-    { ngx_string("cache-control"),         ngx_string("no-cache") },
-    { ngx_string("cache-control"),         ngx_string("no-store") },
-    { ngx_string("cache-control"),
-          ngx_string("public, max-age=31536000") },
-    { ngx_string("content-encoding"),      ngx_string("br") },
-    { ngx_string("content-encoding"),      ngx_string("gzip") },
-    { ngx_string("content-type"),
-          ngx_string("application/dns-message") },
-    { ngx_string("content-type"),
-          ngx_string("application/javascript") },
-    { ngx_string("content-type"),          ngx_string("application/json") },
-    { ngx_string("content-type"),
-          ngx_string("application/x-www-form-urlencoded") },
-    { ngx_string("content-type"),          ngx_string("image/gif") },
-    { ngx_string("content-type"),          ngx_string("image/jpeg") },
-    { ngx_string("content-type"),          ngx_string("image/png") },
-    { ngx_string("content-type"),          ngx_string("text/css") },
-    { ngx_string("content-type"),
-          ngx_string("text/html;charset=utf-8") },
-    { ngx_string("content-type"),          ngx_string("text/plain") },
-    { ngx_string("content-type"),
-          ngx_string("text/plain;charset=utf-8") },
-    { ngx_string("range"),                 ngx_string("bytes=0-") },
-    { ngx_string("strict-transport-security"),
-                                           ngx_string("max-age=31536000") },
-    { ngx_string("strict-transport-security"),
-          ngx_string("max-age=31536000;includesubdomains") },
-    { ngx_string("strict-transport-security"),
-          ngx_string("max-age=31536000;includesubdomains;preload") },
-    { ngx_string("vary"),                  ngx_string("accept-encoding") },
-    { ngx_string("vary"),                  ngx_string("origin") },
-    { ngx_string("x-content-type-options"),
-                                           ngx_string("nosniff") },
-    { ngx_string("x-xss-protection"),      ngx_string("1;mode=block") },
-    { ngx_string(":status"),               ngx_string("100") },
-    { ngx_string(":status"),               ngx_string("204") },
-    { ngx_string(":status"),               ngx_string("206") },
-    { ngx_string(":status"),               ngx_string("302") },
-    { ngx_string(":status"),               ngx_string("400") },
-    { ngx_string(":status"),               ngx_string("403") },
-    { ngx_string(":status"),               ngx_string("421") },
-    { ngx_string(":status"),               ngx_string("425") },
-    { ngx_string(":status"),               ngx_string("500") },
-    { ngx_string("accept-language"),       ngx_string("") },
-    { ngx_string("access-control-allow-credentials"),
-                                           ngx_string("FALSE") },
-    { ngx_string("access-control-allow-credentials"),
-                                           ngx_string("TRUE") },
-    { ngx_string("access-control-allow-headers"),
-                                           ngx_string("*") },
-    { ngx_string("access-control-allow-methods"),
-                                           ngx_string("get") },
-    { ngx_string("access-control-allow-methods"),
-                                           ngx_string("get, post, options") },
-    { ngx_string("access-control-allow-methods"),
-                                           ngx_string("options") },
-    { ngx_string("access-control-expose-headers"),
-                                           ngx_string("content-length") },
-    { ngx_string("access-control-request-headers"),
-                                           ngx_string("content-type") },
-    { ngx_string("access-control-request-method"),
-                                           ngx_string("get") },
-    { ngx_string("access-control-request-method"),
-                                           ngx_string("post") },
-    { ngx_string("alt-svc"),               ngx_string("clear") },
-    { ngx_string("authorization"),         ngx_string("") },
-    { ngx_string("content-security-policy"),
-          ngx_string("script-src 'none';object-src 'none';base-uri 'none'") },
-    { ngx_string("early-data"),            ngx_string("1") },
-    { ngx_string("expect-ct"),             ngx_string("") },
-    { ngx_string("forwarded"),             ngx_string("") },
-    { ngx_string("if-range"),              ngx_string("") },
-    { ngx_string("origin"),                ngx_string("") },
-    { ngx_string("purpose"),               ngx_string("prefetch") },
-    { ngx_string("server"),                ngx_string("") },
-    { ngx_string("timing-allow-origin"),   ngx_string("*") },
-    { ngx_string("upgrade-insecure-requests"),
-                                           ngx_string("1") },
-    { ngx_string("user-agent"),            ngx_string("") },
-    { ngx_string("x-forwarded-for"),       ngx_string("") },
-    { ngx_string("x-frame-options"),       ngx_string("deny") },
-    { ngx_string("x-frame-options"),       ngx_string("sameorigin") }
-};
-
-
-ngx_int_t
-ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic,
-    ngx_uint_t index, ngx_str_t *value)
-{
-    ngx_str_t                     name;
-    ngx_http_v3_session_t        *h3c;
-    ngx_http_v3_dynamic_table_t  *dt;
-
-    if (dynamic) {
-        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                       "http3 ref insert dynamic[%ui] \"%V\"", index, value);
-
-        h3c = ngx_http_v3_get_session(c);
-        dt = &h3c->table;
-
-        if (dt->base + dt->nelts <= index) {
-            return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
-        }
-
-        index = dt->base + dt->nelts - 1 - index;
-
-        if (ngx_http_v3_lookup(c, index, &name, NULL) != NGX_OK) {
-            return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
-        }
-
-    } else {
-        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                       "http3 ref insert static[%ui] \"%V\"", index, value);
-
-        if (ngx_http_v3_lookup_static(c, index, &name, NULL) != NGX_OK) {
-            return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
-        }
-    }
-
-    return ngx_http_v3_insert(c, &name, value);
-}
-
-
-ngx_int_t
-ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, ngx_str_t *value)
-{
-    u_char                       *p;
-    size_t                        size;
-    ngx_http_v3_field_t          *field;
-    ngx_http_v3_session_t        *h3c;
-    ngx_http_v3_dynamic_table_t  *dt;
-
-    size = ngx_http_v3_table_entry_size(name, value);
-
-    if (ngx_http_v3_evict(c, size) != NGX_OK) {
-        return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
-    }
-
-    h3c = ngx_http_v3_get_session(c);
-    dt = &h3c->table;
-
-    ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                   "http3 insert [%ui] \"%V\":\"%V\", size:%uz",
-                   dt->base + dt->nelts, name, value, size);
-
-    p = ngx_alloc(sizeof(ngx_http_v3_field_t) + name->len + value->len,
-                  c->log);
-    if (p == NULL) {
-        return NGX_ERROR;
-    }
-
-    field = (ngx_http_v3_field_t *) p;
-
-    field->name.data = p + sizeof(ngx_http_v3_field_t);
-    field->name.len = name->len;
-    field->value.data = ngx_cpymem(field->name.data, name->data, name->len);
-    field->value.len = value->len;
-    ngx_memcpy(field->value.data, value->data, value->len);
-
-    dt->elts[dt->nelts++] = field;
-    dt->size += size;
-
-    /* TODO increment can be sent less often */
-
-    if (ngx_http_v3_send_inc_insert_count(c, 1) != NGX_OK) {
-        return NGX_ERROR;
-    }
-
-    if (ngx_http_v3_new_entry(c) != NGX_OK) {
-        return NGX_ERROR;
-    }
-
-    return NGX_OK;
-}
-
-
-ngx_int_t
-ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity)
-{
-    ngx_uint_t                     max, prev_max;
-    ngx_http_v3_field_t          **elts;
-    ngx_http_v3_session_t         *h3c;
-    ngx_http_v3_srv_conf_t        *h3scf;
-    ngx_http_v3_dynamic_table_t   *dt;
-
-    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                   "http3 set capacity %ui", capacity);
-
-    h3c = ngx_http_v3_get_session(c);
-    h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module);
-
-    if (capacity > h3scf->max_table_capacity) {
-        ngx_log_error(NGX_LOG_INFO, c->log, 0,
-                      "client exceeded http3_max_table_capacity limit");
-        return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
-    }
-
-    dt = &h3c->table;
-
-    if (dt->size > capacity) {
-        if (ngx_http_v3_evict(c, dt->size - capacity) != NGX_OK) {
-            return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
-        }
-    }
-
-    max = capacity / 32;
-    prev_max = dt->capacity / 32;
-
-    if (max > prev_max) {
-        elts = ngx_alloc(max * sizeof(void *), c->log);
-        if (elts == NULL) {
-            return NGX_ERROR;
-        }
-
-        if (dt->elts) {
-            ngx_memcpy(elts, dt->elts, dt->nelts * sizeof(void *));
-            ngx_free(dt->elts);
-        }
-
-        dt->elts = elts;
-    }
-
-    dt->capacity = capacity;
-
-    return NGX_OK;
-}
-
-
-void
-ngx_http_v3_cleanup_table(ngx_http_v3_session_t *h3c)
-{
-    ngx_uint_t                    n;
-    ngx_http_v3_dynamic_table_t  *dt;
-
-    dt = &h3c->table;
-
-    if (dt->elts == NULL) {
-        return;
-    }
-
-    for (n = 0; n < dt->nelts; n++) {
-        ngx_free(dt->elts[n]);
-    }
-
-    ngx_free(dt->elts);
-}
-
-
-static ngx_int_t
-ngx_http_v3_evict(ngx_connection_t *c, size_t need)
-{
-    size_t                        size, target;
-    ngx_uint_t                    n;
-    ngx_http_v3_field_t          *field;
-    ngx_http_v3_session_t        *h3c;
-    ngx_http_v3_dynamic_table_t  *dt;
-
-    h3c = ngx_http_v3_get_session(c);
-    dt = &h3c->table;
-
-    if (need > dt->capacity) {
-        ngx_log_error(NGX_LOG_ERR, c->log, 0,
-                      "not enough dynamic table capacity");
-        return NGX_ERROR;
-    }
-
-    target = dt->capacity - need;
-    n = 0;
-
-    while (dt->size > target) {
-        field = dt->elts[n++];
-        size = ngx_http_v3_table_entry_size(&field->name, &field->value);
-
-        ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                       "http3 evict [%ui] \"%V\":\"%V\" size:%uz",
-                       dt->base, &field->name, &field->value, size);
-
-        ngx_free(field);
-        dt->size -= size;
-    }
-
-    if (n) {
-        dt->nelts -= n;
-        dt->base += n;
-        ngx_memmove(dt->elts, &dt->elts[n], dt->nelts * sizeof(void *));
-    }
-
-    return NGX_OK;
-}
-
-
-ngx_int_t
-ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index)
-{
-    ngx_str_t                     name, value;
-    ngx_http_v3_session_t        *h3c;
-    ngx_http_v3_dynamic_table_t  *dt;
-
-    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 duplicate %ui", index);
-
-    h3c = ngx_http_v3_get_session(c);
-    dt = &h3c->table;
-
-    if (dt->base + dt->nelts <= index) {
-        return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
-    }
-
-    index = dt->base + dt->nelts - 1 - index;
-
-    if (ngx_http_v3_lookup(c, index, &name, &value) != NGX_OK) {
-        return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
-    }
-
-    return ngx_http_v3_insert(c, &name, &value);
-}
-
-
-ngx_int_t
-ngx_http_v3_ack_section(ngx_connection_t *c, ngx_uint_t stream_id)
-{
-    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                   "http3 ack section %ui", stream_id);
-
-    /* we do not use dynamic tables */
-
-    return NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR;
-}
-
-
-ngx_int_t
-ngx_http_v3_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc)
-{
-    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                   "http3 increment insert count %ui", inc);
-
-    /* we do not use dynamic tables */
-
-    return NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR;
-}
-
-
-ngx_int_t
-ngx_http_v3_lookup_static(ngx_connection_t *c, ngx_uint_t index,
-    ngx_str_t *name, ngx_str_t *value)
-{
-    ngx_uint_t            nelts;
-    ngx_http_v3_field_t  *field;
-
-    nelts = sizeof(ngx_http_v3_static_table)
-            / sizeof(ngx_http_v3_static_table[0]);
-
-    if (index >= nelts) {
-        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                       "http3 static[%ui] lookup out of bounds: %ui",
-                       index, nelts);
-        return NGX_ERROR;
-    }
-
-    field = &ngx_http_v3_static_table[index];
-
-    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                   "http3 static[%ui] lookup \"%V\":\"%V\"",
-                   index, &field->name, &field->value);
-
-    if (name) {
-        *name = field->name;
-    }
-
-    if (value) {
-        *value = field->value;
-    }
-
-    return NGX_OK;
-}
-
-
-ngx_int_t
-ngx_http_v3_lookup(ngx_connection_t *c, ngx_uint_t index, ngx_str_t *name,
-    ngx_str_t *value)
-{
-    ngx_http_v3_field_t          *field;
-    ngx_http_v3_session_t        *h3c;
-    ngx_http_v3_dynamic_table_t  *dt;
-
-    h3c = ngx_http_v3_get_session(c);
-    dt = &h3c->table;
-
-    if (index < dt->base || index - dt->base >= dt->nelts) {
-        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                       "http3 dynamic[%ui] lookup out of bounds: [%ui,%ui]",
-                       index, dt->base, dt->base + dt->nelts);
-        return NGX_ERROR;
-    }
-
-    field = dt->elts[index - dt->base];
-
-    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                   "http3 dynamic[%ui] lookup \"%V\":\"%V\"",
-                   index, &field->name, &field->value);
-
-    if (name) {
-        *name = field->name;
-    }
-
-    if (value) {
-        *value = field->value;
-    }
-
-    return NGX_OK;
-}
-
-
-ngx_int_t
-ngx_http_v3_decode_insert_count(ngx_connection_t *c, ngx_uint_t *insert_count)
-{
-    ngx_uint_t                    max_entries, full_range, max_value,
-                                  max_wrapped, req_insert_count;
-    ngx_http_v3_srv_conf_t       *h3scf;
-    ngx_http_v3_session_t        *h3c;
-    ngx_http_v3_dynamic_table_t  *dt;
-
-    /* QPACK 4.5.1.1. Required Insert Count */
-
-    if (*insert_count == 0) {
-        return NGX_OK;
-    }
-
-    h3c = ngx_http_v3_get_session(c);
-    dt = &h3c->table;
-
-    h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module);
-
-    max_entries = h3scf->max_table_capacity / 32;
-    full_range = 2 * max_entries;
-
-    if (*insert_count > full_range) {
-        return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED;
-    }
-
-    max_value = dt->base + dt->nelts + max_entries;
-    max_wrapped = (max_value / full_range) * full_range;
-    req_insert_count = max_wrapped + *insert_count - 1;
-
-    if (req_insert_count > max_value) {
-        if (req_insert_count <= full_range) {
-            return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED;
-        }
-
-        req_insert_count -= full_range;
-    }
-
-    if (req_insert_count == 0) {
-        return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED;
-    }
-
-    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                   "http3 decode insert_count %ui -> %ui",
-                   *insert_count, req_insert_count);
-
-    *insert_count = req_insert_count;
-
-    return NGX_OK;
-}
-
-
-ngx_int_t
-ngx_http_v3_check_insert_count(ngx_connection_t *c, ngx_uint_t insert_count)
-{
-    size_t                        n;
-    ngx_pool_cleanup_t           *cln;
-    ngx_http_v3_block_t          *block;
-    ngx_http_v3_session_t        *h3c;
-    ngx_http_v3_srv_conf_t       *h3scf;
-    ngx_http_v3_dynamic_table_t  *dt;
-
-    h3c = ngx_http_v3_get_session(c);
-    dt = &h3c->table;
-
-    n = dt->base + dt->nelts;
-
-    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                   "http3 check insert count req:%ui, have:%ui",
-                   insert_count, n);
-
-    if (n >= insert_count) {
-        return NGX_OK;
-    }
-
-    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 block stream");
-
-    block = NULL;
-
-    for (cln = c->pool->cleanup; cln; cln = cln->next) {
-        if (cln->handler == ngx_http_v3_unblock) {
-            block = cln->data;
-            break;
-        }
-    }
-
-    if (block == NULL) {
-        cln = ngx_pool_cleanup_add(c->pool, sizeof(ngx_http_v3_block_t));
-        if (cln == NULL) {
-            return NGX_ERROR;
-        }
-
-        cln->handler = ngx_http_v3_unblock;
-
-        block = cln->data;
-        block->queue.prev = NULL;
-        block->connection = c;
-        block->nblocked = &h3c->nblocked;
-    }
-
-    if (block->queue.prev == NULL) {
-        h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module);
-
-        if (h3c->nblocked == h3scf->max_blocked_streams) {
-            ngx_log_error(NGX_LOG_INFO, c->log, 0,
-                          "client exceeded http3_max_blocked_streams limit");
-
-            ngx_http_v3_finalize_connection(c,
-                                          NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED,
-                                          "too many blocked streams");
-            return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED;
-        }
-
-        h3c->nblocked++;
-        ngx_queue_insert_tail(&h3c->blocked, &block->queue);
-    }
-
-    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                   "http3 blocked:%ui", h3c->nblocked);
-
-    return NGX_BUSY;
-}
-
-
-static void
-ngx_http_v3_unblock(void *data)
-{
-    ngx_http_v3_block_t  *block = data;
-
-    if (block->queue.prev) {
-        ngx_queue_remove(&block->queue);
-        block->queue.prev = NULL;
-        (*block->nblocked)--;
-    }
-}
-
-
-static ngx_int_t
-ngx_http_v3_new_entry(ngx_connection_t *c)
-{
-    ngx_queue_t            *q;
-    ngx_connection_t       *bc;
-    ngx_http_v3_block_t    *block;
-    ngx_http_v3_session_t  *h3c;
-
-    h3c = ngx_http_v3_get_session(c);
-
-    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                   "http3 new dynamic entry, blocked:%ui", h3c->nblocked);
-
-    while (!ngx_queue_empty(&h3c->blocked)) {
-        q = ngx_queue_head(&h3c->blocked);
-        block = (ngx_http_v3_block_t *) q;
-        bc = block->connection;
-
-        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, bc->log, 0, "http3 unblock stream");
-
-        ngx_http_v3_unblock(block);
-        ngx_post_event(bc->read, &ngx_posted_events);
-    }
-
-    return NGX_OK;
-}
-
-
-ngx_int_t
-ngx_http_v3_set_param(ngx_connection_t *c, uint64_t id, uint64_t value)
-{
-    switch (id) {
-
-    case NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY:
-        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                       "http3 param QPACK_MAX_TABLE_CAPACITY:%uL", value);
-        break;
-
-    case NGX_HTTP_V3_PARAM_MAX_HEADER_LIST_SIZE:
-        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                       "http3 param SETTINGS_MAX_HEADER_LIST_SIZE:%uL", value);
-        break;
-
-    case NGX_HTTP_V3_PARAM_BLOCKED_STREAMS:
-        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                       "http3 param QPACK_BLOCKED_STREAMS:%uL", value);
-        break;
-
-    default:
-
-        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                       "http3 param #%uL:%uL", id, value);
-    }
-
-    return NGX_OK;
-}
--- a/src/http/v3/ngx_http_v3_tables.h	Mon Dec 06 15:19:54 2021 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,53 +0,0 @@
-
-/*
- * Copyright (C) Roman Arutyunyan
- * Copyright (C) Nginx, Inc.
- */
-
-
-#ifndef _NGX_HTTP_V3_TABLES_H_INCLUDED_
-#define _NGX_HTTP_V3_TABLES_H_INCLUDED_
-
-
-#include <ngx_config.h>
-#include <ngx_core.h>
-#include <ngx_http.h>
-
-
-typedef struct {
-    ngx_str_t                     name;
-    ngx_str_t                     value;
-} ngx_http_v3_field_t;
-
-
-typedef struct {
-    ngx_http_v3_field_t         **elts;
-    ngx_uint_t                    nelts;
-    ngx_uint_t                    base;
-    size_t                        size;
-    size_t                        capacity;
-} ngx_http_v3_dynamic_table_t;
-
-
-void ngx_http_v3_cleanup_table(ngx_http_v3_session_t *h3c);
-ngx_int_t ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic,
-    ngx_uint_t index, ngx_str_t *value);
-ngx_int_t ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name,
-    ngx_str_t *value);
-ngx_int_t ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity);
-ngx_int_t ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index);
-ngx_int_t ngx_http_v3_ack_section(ngx_connection_t *c, ngx_uint_t stream_id);
-ngx_int_t ngx_http_v3_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc);
-ngx_int_t ngx_http_v3_lookup_static(ngx_connection_t *c, ngx_uint_t index,
-    ngx_str_t *name, ngx_str_t *value);
-ngx_int_t ngx_http_v3_lookup(ngx_connection_t *c, ngx_uint_t index,
-    ngx_str_t *name, ngx_str_t *value);
-ngx_int_t ngx_http_v3_decode_insert_count(ngx_connection_t *c,
-    ngx_uint_t *insert_count);
-ngx_int_t ngx_http_v3_check_insert_count(ngx_connection_t *c,
-    ngx_uint_t insert_count);
-ngx_int_t ngx_http_v3_set_param(ngx_connection_t *c, uint64_t id,
-    uint64_t value);
-
-
-#endif /* _NGX_HTTP_V3_TABLES_H_INCLUDED_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/http/v3/ngx_http_v3_uni.c	Tue Dec 07 13:01:28 2021 +0300
@@ -0,0 +1,733 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    ngx_http_v3_parse_uni_t         parse;
+    ngx_int_t                       index;
+} ngx_http_v3_uni_stream_t;
+
+
+typedef struct {
+    ngx_queue_t                     queue;
+    uint64_t                        id;
+    ngx_connection_t               *connection;
+    ngx_uint_t                     *npushing;
+} ngx_http_v3_push_t;
+
+
+static void ngx_http_v3_close_uni_stream(ngx_connection_t *c);
+static void ngx_http_v3_uni_read_handler(ngx_event_t *rev);
+static void ngx_http_v3_dummy_write_handler(ngx_event_t *wev);
+static void ngx_http_v3_push_cleanup(void *data);
+static ngx_connection_t *ngx_http_v3_get_uni_stream(ngx_connection_t *c,
+    ngx_uint_t type);
+
+
+void
+ngx_http_v3_init_uni_stream(ngx_connection_t *c)
+{
+    uint64_t                   n;
+    ngx_http_v3_uni_stream_t  *us;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init uni stream");
+
+    n = c->quic->id >> 2;
+
+    if (n >= NGX_HTTP_V3_MAX_UNI_STREAMS) {
+        ngx_http_v3_finalize_connection(c,
+                                      NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR,
+                                      "reached maximum number of uni streams");
+        c->data = NULL;
+        ngx_http_v3_close_uni_stream(c);
+        return;
+    }
+
+    c->quic->cancelable = 1;
+
+    us = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_uni_stream_t));
+    if (us == NULL) {
+        ngx_http_v3_finalize_connection(c,
+                                        NGX_HTTP_V3_ERR_INTERNAL_ERROR,
+                                        "memory allocation error");
+        c->data = NULL;
+        ngx_http_v3_close_uni_stream(c);
+        return;
+    }
+
+    us->index = -1;
+
+    c->data = us;
+
+    c->read->handler = ngx_http_v3_uni_read_handler;
+    c->write->handler = ngx_http_v3_dummy_write_handler;
+
+    ngx_http_v3_uni_read_handler(c->read);
+}
+
+
+static void
+ngx_http_v3_close_uni_stream(ngx_connection_t *c)
+{
+    ngx_pool_t                *pool;
+    ngx_http_v3_session_t     *h3c;
+    ngx_http_v3_uni_stream_t  *us;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 close stream");
+
+    us = c->data;
+
+    if (us && us->index >= 0) {
+        h3c = ngx_http_v3_get_session(c);
+        h3c->known_streams[us->index] = NULL;
+    }
+
+    c->destroyed = 1;
+
+    pool = c->pool;
+
+    ngx_close_connection(c);
+
+    ngx_destroy_pool(pool);
+}
+
+
+ngx_int_t
+ngx_http_v3_register_uni_stream(ngx_connection_t *c, uint64_t type)
+{
+    ngx_int_t                  index;
+    ngx_http_v3_session_t     *h3c;
+    ngx_http_v3_uni_stream_t  *us;
+
+    h3c = ngx_http_v3_get_session(c);
+
+    switch (type) {
+
+    case NGX_HTTP_V3_STREAM_ENCODER:
+
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                       "http3 encoder stream");
+        index = NGX_HTTP_V3_STREAM_CLIENT_ENCODER;
+        break;
+
+    case NGX_HTTP_V3_STREAM_DECODER:
+
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                       "http3 decoder stream");
+        index = NGX_HTTP_V3_STREAM_CLIENT_DECODER;
+        break;
+
+    case NGX_HTTP_V3_STREAM_CONTROL:
+
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                       "http3 control stream");
+        index = NGX_HTTP_V3_STREAM_CLIENT_CONTROL;
+
+        break;
+
+    default:
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                       "http3 stream 0x%02xL", type);
+
+        if (h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_ENCODER] == NULL
+            || h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_DECODER] == NULL
+            || h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_CONTROL] == NULL)
+        {
+            ngx_log_error(NGX_LOG_INFO, c->log, 0, "missing mandatory stream");
+            return NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR;
+        }
+
+        index = -1;
+    }
+
+    if (index >= 0) {
+        if (h3c->known_streams[index]) {
+            ngx_log_error(NGX_LOG_INFO, c->log, 0, "stream exists");
+            return NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR;
+        }
+
+        h3c->known_streams[index] = c;
+
+        us = c->data;
+        us->index = index;
+    }
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_http_v3_uni_read_handler(ngx_event_t *rev)
+{
+    u_char                     buf[128];
+    ssize_t                    n;
+    ngx_buf_t                  b;
+    ngx_int_t                  rc;
+    ngx_connection_t          *c;
+    ngx_http_v3_session_t     *h3c;
+    ngx_http_v3_uni_stream_t  *us;
+
+    c = rev->data;
+    us = c->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read handler");
+
+    ngx_memzero(&b, sizeof(ngx_buf_t));
+
+    while (rev->ready) {
+
+        n = c->recv(c, buf, sizeof(buf));
+
+        if (n == NGX_ERROR) {
+            rc = NGX_HTTP_V3_ERR_INTERNAL_ERROR;
+            goto failed;
+        }
+
+        if (n == 0) {
+            if (us->index >= 0) {
+                rc = NGX_HTTP_V3_ERR_CLOSED_CRITICAL_STREAM;
+                goto failed;
+            }
+
+            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read eof");
+            ngx_http_v3_close_uni_stream(c);
+            return;
+        }
+
+        if (n == NGX_AGAIN) {
+            break;
+        }
+
+        b.pos = buf;
+        b.last = buf + n;
+
+        h3c = ngx_http_v3_get_session(c);
+        h3c->total_bytes += n;
+
+        if (ngx_http_v3_check_flood(c) != NGX_OK) {
+            ngx_http_v3_close_uni_stream(c);
+            return;
+        }
+
+        rc = ngx_http_v3_parse_uni(c, &us->parse, &b);
+
+        if (rc == NGX_DONE) {
+            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                           "http3 read done");
+            ngx_http_v3_close_uni_stream(c);
+            return;
+        }
+
+        if (rc > 0) {
+            goto failed;
+        }
+
+        if (rc != NGX_AGAIN) {
+            rc = NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR;
+            goto failed;
+        }
+    }
+
+    if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+        rc = NGX_HTTP_V3_ERR_INTERNAL_ERROR;
+        goto failed;
+    }
+
+    return;
+
+failed:
+
+    ngx_http_v3_finalize_connection(c, rc, "stream error");
+    ngx_http_v3_close_uni_stream(c);
+}
+
+
+static void
+ngx_http_v3_dummy_write_handler(ngx_event_t *wev)
+{
+    ngx_connection_t  *c;
+
+    c = wev->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 dummy write handler");
+
+    if (ngx_handle_write_event(wev, 0) != NGX_OK) {
+        ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR,
+                                        NULL);
+        ngx_http_v3_close_uni_stream(c);
+    }
+}
+
+
+/* XXX async & buffered stream writes */
+
+ngx_connection_t *
+ngx_http_v3_create_push_stream(ngx_connection_t *c, uint64_t push_id)
+{
+    u_char                 *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 2];
+    size_t                  n;
+    ngx_connection_t       *sc;
+    ngx_pool_cleanup_t     *cln;
+    ngx_http_v3_push_t     *push;
+    ngx_http_v3_session_t  *h3c;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http3 create push stream id:%uL", push_id);
+
+    sc = ngx_quic_open_stream(c, 0);
+    if (sc == NULL) {
+        goto failed;
+    }
+
+    p = buf;
+    p = (u_char *) ngx_http_v3_encode_varlen_int(p, NGX_HTTP_V3_STREAM_PUSH);
+    p = (u_char *) ngx_http_v3_encode_varlen_int(p, push_id);
+    n = p - buf;
+
+    h3c = ngx_http_v3_get_session(c);
+    h3c->total_bytes += n;
+
+    if (sc->send(sc, buf, n) != (ssize_t) n) {
+        goto failed;
+    }
+
+    cln = ngx_pool_cleanup_add(sc->pool, sizeof(ngx_http_v3_push_t));
+    if (cln == NULL) {
+        goto failed;
+    }
+
+    h3c->npushing++;
+
+    cln->handler = ngx_http_v3_push_cleanup;
+
+    push = cln->data;
+    push->id = push_id;
+    push->connection = sc;
+    push->npushing = &h3c->npushing;
+
+    ngx_queue_insert_tail(&h3c->pushing, &push->queue);
+
+    return sc;
+
+failed:
+
+    ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create push stream");
+
+    ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR,
+                                    "failed to create push stream");
+    if (sc) {
+        ngx_http_v3_close_uni_stream(sc);
+    }
+
+    return NULL;
+}
+
+
+static void
+ngx_http_v3_push_cleanup(void *data)
+{
+    ngx_http_v3_push_t  *push = data;
+
+    ngx_queue_remove(&push->queue);
+    (*push->npushing)--;
+}
+
+
+static ngx_connection_t *
+ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type)
+{
+    u_char                     buf[NGX_HTTP_V3_VARLEN_INT_LEN];
+    size_t                     n;
+    ngx_int_t                  index;
+    ngx_connection_t          *sc;
+    ngx_http_v3_session_t     *h3c;
+    ngx_http_v3_uni_stream_t  *us;
+
+    switch (type) {
+    case NGX_HTTP_V3_STREAM_ENCODER:
+        index = NGX_HTTP_V3_STREAM_SERVER_ENCODER;
+        break;
+    case NGX_HTTP_V3_STREAM_DECODER:
+        index = NGX_HTTP_V3_STREAM_SERVER_DECODER;
+        break;
+    case NGX_HTTP_V3_STREAM_CONTROL:
+        index = NGX_HTTP_V3_STREAM_SERVER_CONTROL;
+        break;
+    default:
+        index = -1;
+    }
+
+    h3c = ngx_http_v3_get_session(c);
+
+    if (index >= 0) {
+        if (h3c->known_streams[index]) {
+            return h3c->known_streams[index];
+        }
+    }
+
+    sc = ngx_quic_open_stream(c, 0);
+    if (sc == NULL) {
+        goto failed;
+    }
+
+    sc->quic->cancelable = 1;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http3 create uni stream, type:%ui", type);
+
+    us = ngx_pcalloc(sc->pool, sizeof(ngx_http_v3_uni_stream_t));
+    if (us == NULL) {
+        goto failed;
+    }
+
+    us->index = index;
+
+    sc->data = us;
+
+    sc->read->handler = ngx_http_v3_uni_read_handler;
+    sc->write->handler = ngx_http_v3_dummy_write_handler;
+
+    if (index >= 0) {
+        h3c->known_streams[index] = sc;
+    }
+
+    n = (u_char *) ngx_http_v3_encode_varlen_int(buf, type) - buf;
+
+    h3c = ngx_http_v3_get_session(c);
+    h3c->total_bytes += n;
+
+    if (sc->send(sc, buf, n) != (ssize_t) n) {
+        goto failed;
+    }
+
+    return sc;
+
+failed:
+
+    ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create server stream");
+
+    ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR,
+                                    "failed to create server stream");
+    if (sc) {
+        ngx_http_v3_close_uni_stream(sc);
+    }
+
+    return NULL;
+}
+
+
+ngx_int_t
+ngx_http_v3_send_settings(ngx_connection_t *c)
+{
+    u_char                  *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 6];
+    size_t                   n;
+    ngx_connection_t        *cc;
+    ngx_http_v3_session_t   *h3c;
+    ngx_http_v3_srv_conf_t  *h3scf;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send settings");
+
+    cc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL);
+    if (cc == NULL) {
+        return NGX_ERROR;
+    }
+
+    h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module);
+
+    n = ngx_http_v3_encode_varlen_int(NULL,
+                                      NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY);
+    n += ngx_http_v3_encode_varlen_int(NULL, h3scf->max_table_capacity);
+    n += ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_PARAM_BLOCKED_STREAMS);
+    n += ngx_http_v3_encode_varlen_int(NULL, h3scf->max_blocked_streams);
+
+    p = (u_char *) ngx_http_v3_encode_varlen_int(buf,
+                                                 NGX_HTTP_V3_FRAME_SETTINGS);
+    p = (u_char *) ngx_http_v3_encode_varlen_int(p, n);
+    p = (u_char *) ngx_http_v3_encode_varlen_int(p,
+                                         NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY);
+    p = (u_char *) ngx_http_v3_encode_varlen_int(p, h3scf->max_table_capacity);
+    p = (u_char *) ngx_http_v3_encode_varlen_int(p,
+                                            NGX_HTTP_V3_PARAM_BLOCKED_STREAMS);
+    p = (u_char *) ngx_http_v3_encode_varlen_int(p, h3scf->max_blocked_streams);
+    n = p - buf;
+
+    h3c = ngx_http_v3_get_session(c);
+    h3c->total_bytes += n;
+
+    if (cc->send(cc, buf, n) != (ssize_t) n) {
+        goto failed;
+    }
+
+    return NGX_OK;
+
+failed:
+
+    ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send settings");
+
+    ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD,
+                                    "failed to send settings");
+    ngx_http_v3_close_uni_stream(cc);
+
+    return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_http_v3_send_goaway(ngx_connection_t *c, uint64_t id)
+{
+    u_char                 *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 3];
+    size_t                  n;
+    ngx_connection_t       *cc;
+    ngx_http_v3_session_t  *h3c;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send goaway %uL", id);
+
+    cc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL);
+    if (cc == NULL) {
+        return NGX_ERROR;
+    }
+
+    n = ngx_http_v3_encode_varlen_int(NULL, id);
+    p = (u_char *) ngx_http_v3_encode_varlen_int(buf, NGX_HTTP_V3_FRAME_GOAWAY);
+    p = (u_char *) ngx_http_v3_encode_varlen_int(p, n);
+    p = (u_char *) ngx_http_v3_encode_varlen_int(p, id);
+    n = p - buf;
+
+    h3c = ngx_http_v3_get_session(c);
+    h3c->total_bytes += n;
+
+    if (cc->send(cc, buf, n) != (ssize_t) n) {
+        goto failed;
+    }
+
+    return NGX_OK;
+
+failed:
+
+    ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send goaway");
+
+    ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD,
+                                    "failed to send goaway");
+    ngx_http_v3_close_uni_stream(cc);
+
+    return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_http_v3_send_ack_section(ngx_connection_t *c, ngx_uint_t stream_id)
+{
+    u_char                  buf[NGX_HTTP_V3_PREFIX_INT_LEN];
+    size_t                  n;
+    ngx_connection_t       *dc;
+    ngx_http_v3_session_t  *h3c;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http3 send section acknowledgement %ui", stream_id);
+
+    dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER);
+    if (dc == NULL) {
+        return NGX_ERROR;
+    }
+
+    buf[0] = 0x80;
+    n = (u_char *) ngx_http_v3_encode_prefix_int(buf, stream_id, 7) - buf;
+
+    h3c = ngx_http_v3_get_session(c);
+    h3c->total_bytes += n;
+
+    if (dc->send(dc, buf, n) != (ssize_t) n) {
+        goto failed;
+    }
+
+    return NGX_OK;
+
+failed:
+
+    ngx_log_error(NGX_LOG_ERR, c->log, 0,
+                  "failed to send section acknowledgement");
+
+    ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD,
+                                    "failed to send section acknowledgement");
+    ngx_http_v3_close_uni_stream(dc);
+
+    return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_http_v3_send_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id)
+{
+    u_char                  buf[NGX_HTTP_V3_PREFIX_INT_LEN];
+    size_t                  n;
+    ngx_connection_t       *dc;
+    ngx_http_v3_session_t  *h3c;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http3 send stream cancellation %ui", stream_id);
+
+    dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER);
+    if (dc == NULL) {
+        return NGX_ERROR;
+    }
+
+    buf[0] = 0x40;
+    n = (u_char *) ngx_http_v3_encode_prefix_int(buf, stream_id, 6) - buf;
+
+    h3c = ngx_http_v3_get_session(c);
+    h3c->total_bytes += n;
+
+    if (dc->send(dc, buf, n) != (ssize_t) n) {
+        goto failed;
+    }
+
+    return NGX_OK;
+
+failed:
+
+    ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send stream cancellation");
+
+    ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD,
+                                    "failed to send stream cancellation");
+    ngx_http_v3_close_uni_stream(dc);
+
+    return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_http_v3_send_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc)
+{
+    u_char                  buf[NGX_HTTP_V3_PREFIX_INT_LEN];
+    size_t                  n;
+    ngx_connection_t       *dc;
+    ngx_http_v3_session_t  *h3c;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http3 send insert count increment %ui", inc);
+
+    dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER);
+    if (dc == NULL) {
+        return NGX_ERROR;
+    }
+
+    buf[0] = 0;
+    n = (u_char *) ngx_http_v3_encode_prefix_int(buf, inc, 6) - buf;
+
+    h3c = ngx_http_v3_get_session(c);
+    h3c->total_bytes += n;
+
+    if (dc->send(dc, buf, n) != (ssize_t) n) {
+        goto failed;
+    }
+
+    return NGX_OK;
+
+failed:
+
+    ngx_log_error(NGX_LOG_ERR, c->log, 0,
+                  "failed to send insert count increment");
+
+    ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD,
+                                    "failed to send insert count increment");
+    ngx_http_v3_close_uni_stream(dc);
+
+    return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_http_v3_set_max_push_id(ngx_connection_t *c, uint64_t max_push_id)
+{
+    ngx_http_v3_session_t  *h3c;
+
+    h3c = ngx_http_v3_get_session(c);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http3 MAX_PUSH_ID:%uL", max_push_id);
+
+    if (h3c->max_push_id != (uint64_t) -1 && max_push_id < h3c->max_push_id) {
+        return NGX_HTTP_V3_ERR_ID_ERROR;
+    }
+
+    h3c->max_push_id = max_push_id;
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_goaway(ngx_connection_t *c, uint64_t push_id)
+{
+    ngx_http_v3_session_t  *h3c;
+
+    h3c = ngx_http_v3_get_session(c);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 GOAWAY:%uL", push_id);
+
+    h3c->goaway_push_id = push_id;
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id)
+{
+    ngx_queue_t            *q;
+    ngx_http_request_t     *r;
+    ngx_http_v3_push_t     *push;
+    ngx_http_v3_session_t  *h3c;
+
+    h3c = ngx_http_v3_get_session(c);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http3 CANCEL_PUSH:%uL", push_id);
+
+    if (push_id >= h3c->next_push_id) {
+        return NGX_HTTP_V3_ERR_ID_ERROR;
+    }
+
+    for (q = ngx_queue_head(&h3c->pushing);
+         q != ngx_queue_sentinel(&h3c->pushing);
+         q = ngx_queue_next(&h3c->pushing))
+    {
+        push = (ngx_http_v3_push_t *) q;
+
+        if (push->id != push_id) {
+            continue;
+        }
+
+        r = push->connection->data;
+
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http3 cancel push");
+
+        ngx_http_finalize_request(r, NGX_HTTP_CLOSE);
+
+        break;
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id)
+{
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http3 cancel stream %ui", stream_id);
+
+    /* we do not use dynamic tables */
+
+    return NGX_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/http/v3/ngx_http_v3_uni.h	Tue Dec 07 13:01:28 2021 +0300
@@ -0,0 +1,38 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_HTTP_V3_UNI_H_INCLUDED_
+#define _NGX_HTTP_V3_UNI_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+void ngx_http_v3_init_uni_stream(ngx_connection_t *c);
+ngx_int_t ngx_http_v3_register_uni_stream(ngx_connection_t *c, uint64_t type);
+
+ngx_connection_t *ngx_http_v3_create_push_stream(ngx_connection_t *c,
+    uint64_t push_id);
+ngx_int_t ngx_http_v3_set_max_push_id(ngx_connection_t *c,
+    uint64_t max_push_id);
+ngx_int_t ngx_http_v3_goaway(ngx_connection_t *c, uint64_t push_id);
+ngx_int_t ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id);
+ngx_int_t ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id);
+
+ngx_int_t ngx_http_v3_send_settings(ngx_connection_t *c);
+ngx_int_t ngx_http_v3_send_goaway(ngx_connection_t *c, uint64_t id);
+ngx_int_t ngx_http_v3_send_ack_section(ngx_connection_t *c,
+    ngx_uint_t stream_id);
+ngx_int_t ngx_http_v3_send_cancel_stream(ngx_connection_t *c,
+    ngx_uint_t stream_id);
+ngx_int_t ngx_http_v3_send_inc_insert_count(ngx_connection_t *c,
+    ngx_uint_t inc);
+
+
+#endif /* _NGX_HTTP_V3_UNI_H_INCLUDED_ */