]> git.kaiwu.me - nginx.git/commitdiff
Merged with the default branch.
authorRuslan Ermilov <ru@nginx.com>
Fri, 24 Dec 2021 12:53:59 +0000 (15:53 +0300)
committerRuslan Ermilov <ru@nginx.com>
Fri, 24 Dec 2021 12:53:59 +0000 (15:53 +0300)
1  2 
auto/modules
auto/sources
src/event/ngx_event_openssl.c
src/event/ngx_event_openssl.h
src/http/modules/ngx_http_ssl_module.c
src/http/ngx_http.h
src/http/ngx_http_upstream.c
src/http/v3/ngx_http_v3_encode.c
src/http/v3/ngx_http_v3_parse.c
src/stream/ngx_stream_ssl_module.c

diff --cc auto/modules
index 95c237c801dd14b3c544a93fa82198aba94a9d4e,94867bfc0eae489bd6abe362e696e81d80a167e0..19967fa408048d024851bf7ba183da4b96e2bc83
@@@ -102,6 -102,11 +102,11 @@@ if [ $HTTP = YES ]; the
      fi
  
  
 -    if [ $HTTP_V2 = YES ]; then
++    if [ $HTTP_V2 = YES -o $HTTP_V3 = YES ]; then
+         HTTP_SRCS="$HTTP_SRCS $HTTP_HUFF_SRCS"
+     fi
      # the module order is important
      #     ngx_http_static_module
      #     ngx_http_gzip_static_module
          . auto/module
      fi
  
-         if [ $HTTP_V2 = NO ]; then
-             ngx_module_srcs="$ngx_module_srcs \
-                              src/http/v2/ngx_http_v2_huff_decode.c \
-                              src/http/v2/ngx_http_v2_huff_encode.c"
-         fi
 +    if [ $HTTP_V3 = YES ]; then
 +        USE_OPENSSL_QUIC=YES
 +        HTTP_SSL=YES
 +
 +        have=NGX_HTTP_V3 . auto/have
 +        have=NGX_HTTP_HEADERS . auto/have
 +
 +        ngx_module_name=ngx_http_v3_module
 +        ngx_module_incs=src/http/v3
 +        ngx_module_deps="src/http/v3/ngx_http_v3.h \
 +                         src/http/v3/ngx_http_v3_encode.h \
 +                         src/http/v3/ngx_http_v3_parse.h \
 +                         src/http/v3/ngx_http_v3_table.h \
 +                         src/http/v3/ngx_http_v3_uni.h"
 +        ngx_module_srcs="src/http/v3/ngx_http_v3.c \
 +                         src/http/v3/ngx_http_v3_encode.c \
 +                         src/http/v3/ngx_http_v3_parse.c \
 +                         src/http/v3/ngx_http_v3_table.c \
 +                         src/http/v3/ngx_http_v3_uni.c \
 +                         src/http/v3/ngx_http_v3_request.c \
 +                         src/http/v3/ngx_http_v3_module.c"
 +        ngx_module_libs=
 +        ngx_module_link=$HTTP_V3
 +
 +        . auto/module
 +    fi
 +
      if :; then
          ngx_module_name=ngx_http_static_module
          ngx_module_incs=
diff --cc auto/sources
Simple merge
Simple merge
Simple merge
index 5ac65c859ae57e2eb429fce2f2d941bf66bdb050,be8b7cd713cf3f18646b0e116f2270d888580992..a5bdeaaddf92b3c556eda5af31cdc975ca3ac7d8
@@@ -176,10 -166,11 +176,10 @@@ ngx_int_t ngx_http_set_default_types(ng
  ngx_uint_t  ngx_http_degraded(ngx_http_request_t *);
  #endif
  
 -
 -#if (NGX_HTTP_V2)
 +#if (NGX_HTTP_V2 || NGX_HTTP_V3)
- ngx_int_t ngx_http_v2_huff_decode(u_char *state, u_char *src, size_t len,
+ ngx_int_t ngx_http_huff_decode(u_char *state, u_char *src, size_t len,
      u_char **dst, ngx_uint_t last, ngx_log_t *log);
- size_t ngx_http_v2_huff_encode(u_char *src, size_t len, u_char *dst,
+ size_t ngx_http_huff_encode(u_char *src, size_t len, u_char *dst,
      ngx_uint_t lower);
  #endif
  
Simple merge
index a62f5d30fb496d0351c84a377135b8bddc3760ce,0000000000000000000000000000000000000000..fb089c41320e813a0d22b3c3110846000664148a
mode 100644,000000..100644
--- /dev/null
@@@ -1,304 -1,0 +1,304 @@@
-         hlen = ngx_http_v2_huff_encode(data, len, p, 0);
 +
 +/*
 + * Copyright (C) Roman Arutyunyan
 + * Copyright (C) Nginx, Inc.
 + */
 +
 +
 +#include <ngx_config.h>
 +#include <ngx_core.h>
 +#include <ngx_http.h>
 +
 +
 +uintptr_t
 +ngx_http_v3_encode_varlen_int(u_char *p, uint64_t value)
 +{
 +    if (value <= 63) {
 +        if (p == NULL) {
 +            return 1;
 +        }
 +
 +        *p++ = value;
 +        return (uintptr_t) p;
 +    }
 +
 +    if (value <= 16383) {
 +        if (p == NULL) {
 +            return 2;
 +        }
 +
 +        *p++ = 0x40 | (value >> 8);
 +        *p++ = value;
 +        return (uintptr_t) p;
 +    }
 +
 +    if (value <= 1073741823) {
 +        if (p == NULL) {
 +            return 4;
 +        }
 +
 +        *p++ = 0x80 | (value >> 24);
 +        *p++ = (value >> 16);
 +        *p++ = (value >> 8);
 +        *p++ = value;
 +        return (uintptr_t) p;
 +    }
 +
 +    if (p == NULL) {
 +        return 8;
 +    }
 +
 +    *p++ = 0xc0 | (value >> 56);
 +    *p++ = (value >> 48);
 +    *p++ = (value >> 40);
 +    *p++ = (value >> 32);
 +    *p++ = (value >> 24);
 +    *p++ = (value >> 16);
 +    *p++ = (value >> 8);
 +    *p++ = value;
 +    return (uintptr_t) p;
 +}
 +
 +
 +uintptr_t
 +ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value, ngx_uint_t prefix)
 +{
 +    ngx_uint_t  thresh, n;
 +
 +    thresh = (1 << prefix) - 1;
 +
 +    if (value < thresh) {
 +        if (p == NULL) {
 +            return 1;
 +        }
 +
 +        *p++ |= value;
 +        return (uintptr_t) p;
 +    }
 +
 +    value -= thresh;
 +
 +    if (p == NULL) {
 +        for (n = 2; value >= 128; n++) {
 +            value >>= 7;
 +        }
 +
 +        return n;
 +    }
 +
 +    *p++ |= thresh;
 +
 +    while (value >= 128) {
 +        *p++ = 0x80 | value;
 +        value >>= 7;
 +    }
 +
 +    *p++ = value;
 +
 +    return (uintptr_t) p;
 +}
 +
 +
 +uintptr_t
 +ngx_http_v3_encode_field_section_prefix(u_char *p, ngx_uint_t insert_count,
 +    ngx_uint_t sign, ngx_uint_t delta_base)
 +{
 +    if (p == NULL) {
 +        return ngx_http_v3_encode_prefix_int(NULL, insert_count, 8)
 +               + ngx_http_v3_encode_prefix_int(NULL, delta_base, 7);
 +    }
 +
 +    *p = 0;
 +    p = (u_char *) ngx_http_v3_encode_prefix_int(p, insert_count, 8);
 +
 +    *p = sign ? 0x80 : 0;
 +    p = (u_char *) ngx_http_v3_encode_prefix_int(p, delta_base, 7);
 +
 +    return (uintptr_t) p;
 +}
 +
 +
 +uintptr_t
 +ngx_http_v3_encode_field_ri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index)
 +{
 +    /* Indexed Field Line */
 +
 +    if (p == NULL) {
 +        return ngx_http_v3_encode_prefix_int(NULL, index, 6);
 +    }
 +
 +    *p = dynamic ? 0x80 : 0xc0;
 +
 +    return ngx_http_v3_encode_prefix_int(p, index, 6);
 +}
 +
 +
 +uintptr_t
 +ngx_http_v3_encode_field_lri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index,
 +    u_char *data, size_t len)
 +{
 +    size_t   hlen;
 +    u_char  *p1, *p2;
 +
 +    /* Literal Field Line With Name Reference */
 +
 +    if (p == NULL) {
 +        return ngx_http_v3_encode_prefix_int(NULL, index, 4)
 +               + ngx_http_v3_encode_prefix_int(NULL, len, 7)
 +               + len;
 +    }
 +
 +    *p = dynamic ? 0x40 : 0x50;
 +    p = (u_char *) ngx_http_v3_encode_prefix_int(p, index, 4);
 +
 +    p1 = p;
 +    *p = 0;
 +    p = (u_char *) ngx_http_v3_encode_prefix_int(p, len, 7);
 +
 +    if (data) {
 +        p2 = p;
-     hlen = ngx_http_v2_huff_encode(name->data, name->len, p, 1);
++        hlen = ngx_http_huff_encode(data, len, p, 0);
 +
 +        if (hlen) {
 +            p = p1;
 +            *p = 0x80;
 +            p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 7);
 +
 +            if (p != p2) {
 +                ngx_memmove(p, p2, hlen);
 +            }
 +
 +            p += hlen;
 +
 +        } else {
 +            p = ngx_cpymem(p, data, len);
 +        }
 +    }
 +
 +    return (uintptr_t) p;
 +}
 +
 +
 +uintptr_t
 +ngx_http_v3_encode_field_l(u_char *p, ngx_str_t *name, ngx_str_t *value)
 +{
 +    size_t   hlen;
 +    u_char  *p1, *p2;
 +
 +    /* Literal Field Line With Literal Name */
 +
 +    if (p == NULL) {
 +        return ngx_http_v3_encode_prefix_int(NULL, name->len, 3)
 +               + name->len
 +               + ngx_http_v3_encode_prefix_int(NULL, value->len, 7)
 +               + value->len;
 +    }
 +
 +    p1 = p;
 +    *p = 0x20;
 +    p = (u_char *) ngx_http_v3_encode_prefix_int(p, name->len, 3);
 +
 +    p2 = p;
-     hlen = ngx_http_v2_huff_encode(value->data, value->len, p, 0);
++    hlen = ngx_http_huff_encode(name->data, name->len, p, 1);
 +
 +    if (hlen) {
 +        p = p1;
 +        *p = 0x28;
 +        p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 3);
 +
 +        if (p != p2) {
 +            ngx_memmove(p, p2, hlen);
 +        }
 +
 +        p += hlen;
 +
 +    } else {
 +        ngx_strlow(p, name->data, name->len);
 +        p += name->len;
 +    }
 +
 +    p1 = p;
 +    *p = 0;
 +    p = (u_char *) ngx_http_v3_encode_prefix_int(p, value->len, 7);
 +
 +    p2 = p;
-         hlen = ngx_http_v2_huff_encode(data, len, p, 0);
++    hlen = ngx_http_huff_encode(value->data, value->len, p, 0);
 +
 +    if (hlen) {
 +        p = p1;
 +        *p = 0x80;
 +        p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 7);
 +
 +        if (p != p2) {
 +            ngx_memmove(p, p2, hlen);
 +        }
 +
 +        p += hlen;
 +
 +    } else {
 +        p = ngx_cpymem(p, value->data, value->len);
 +    }
 +
 +    return (uintptr_t) p;
 +}
 +
 +
 +uintptr_t
 +ngx_http_v3_encode_field_pbi(u_char *p, ngx_uint_t index)
 +{
 +    /* Indexed Field Line With Post-Base Index */
 +
 +    if (p == NULL) {
 +        return ngx_http_v3_encode_prefix_int(NULL, index, 4);
 +    }
 +
 +    *p = 0x10;
 +
 +    return ngx_http_v3_encode_prefix_int(p, index, 4);
 +}
 +
 +
 +uintptr_t
 +ngx_http_v3_encode_field_lpbi(u_char *p, ngx_uint_t index, u_char *data,
 +    size_t len)
 +{
 +    size_t   hlen;
 +    u_char  *p1, *p2;
 +
 +    /* Literal Field Line With Post-Base Name Reference */
 +
 +    if (p == NULL) {
 +        return ngx_http_v3_encode_prefix_int(NULL, index, 3)
 +               + ngx_http_v3_encode_prefix_int(NULL, len, 7)
 +               + len;
 +    }
 +
 +    *p = 0;
 +    p = (u_char *) ngx_http_v3_encode_prefix_int(p, index, 3);
 +
 +    p1 = p;
 +    *p = 0;
 +    p = (u_char *) ngx_http_v3_encode_prefix_int(p, len, 7);
 +
 +    if (data) {
 +        p2 = p;
++        hlen = ngx_http_huff_encode(data, len, p, 0);
 +
 +        if (hlen) {
 +            p = p1;
 +            *p = 0x80;
 +            p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 7);
 +
 +            if (p != p2) {
 +                ngx_memmove(p, p2, hlen);
 +            }
 +
 +            p += hlen;
 +
 +        } else {
 +            p = ngx_cpymem(p, data, len);
 +        }
 +    }
 +
 +    return (uintptr_t) p;
 +}
index 3f676b4bb077698c5494d2b1cea4c3c2578049b9,0000000000000000000000000000000000000000..25c1edf6d5ae1dc69968b0fb1be391b60f90b549
mode 100644,000000..100644
--- /dev/null
@@@ -1,2005 -1,0 +1,2005 @@@
-                 if (ngx_http_v2_huff_decode(&st->huffstate, &ch, 1, &st->last,
-                                             st->length == 1, c->log)
 +
 +/*
 + * Copyright (C) Roman Arutyunyan
 + * Copyright (C) Nginx, Inc.
 + */
 +
 +
 +#include <ngx_config.h>
 +#include <ngx_core.h>
 +#include <ngx_http.h>
 +
 +
 +#define ngx_http_v3_is_v2_frame(type)                                         \
 +    ((type) == 0x02 || (type) == 0x06 || (type) == 0x08 || (type) == 0x09)
 +
 +
 +static void ngx_http_v3_parse_start_local(ngx_buf_t *b, ngx_buf_t *loc,
 +    ngx_uint_t n);
 +static void ngx_http_v3_parse_end_local(ngx_buf_t *b, ngx_buf_t *loc,
 +    ngx_uint_t *n);
 +static ngx_int_t ngx_http_v3_parse_skip(ngx_buf_t *b, ngx_uint_t *length);
 +
 +static ngx_int_t ngx_http_v3_parse_varlen_int(ngx_connection_t *c,
 +    ngx_http_v3_parse_varlen_int_t *st, ngx_buf_t *b);
 +static ngx_int_t ngx_http_v3_parse_prefix_int(ngx_connection_t *c,
 +    ngx_http_v3_parse_prefix_int_t *st, ngx_uint_t prefix, ngx_buf_t *b);
 +
 +static ngx_int_t ngx_http_v3_parse_field_section_prefix(ngx_connection_t *c,
 +    ngx_http_v3_parse_field_section_prefix_t *st, ngx_buf_t *b);
 +static ngx_int_t ngx_http_v3_parse_field_rep(ngx_connection_t *c,
 +    ngx_http_v3_parse_field_rep_t *st, ngx_uint_t base, ngx_buf_t *b);
 +static ngx_int_t ngx_http_v3_parse_literal(ngx_connection_t *c,
 +    ngx_http_v3_parse_literal_t *st, ngx_buf_t *b);
 +static ngx_int_t ngx_http_v3_parse_field_ri(ngx_connection_t *c,
 +    ngx_http_v3_parse_field_t *st, ngx_buf_t *b);
 +static ngx_int_t ngx_http_v3_parse_field_lri(ngx_connection_t *c,
 +    ngx_http_v3_parse_field_t *st, ngx_buf_t *b);
 +static ngx_int_t ngx_http_v3_parse_field_l(ngx_connection_t *c,
 +    ngx_http_v3_parse_field_t *st, ngx_buf_t *b);
 +static ngx_int_t ngx_http_v3_parse_field_pbi(ngx_connection_t *c,
 +    ngx_http_v3_parse_field_t *st, ngx_buf_t *b);
 +static ngx_int_t ngx_http_v3_parse_field_lpbi(ngx_connection_t *c,
 +    ngx_http_v3_parse_field_t *st, ngx_buf_t *b);
 +
 +static ngx_int_t ngx_http_v3_parse_control(ngx_connection_t *c,
 +    ngx_http_v3_parse_control_t *st, ngx_buf_t *b);
 +static ngx_int_t ngx_http_v3_parse_settings(ngx_connection_t *c,
 +    ngx_http_v3_parse_settings_t *st, ngx_buf_t *b);
 +
 +static ngx_int_t ngx_http_v3_parse_encoder(ngx_connection_t *c,
 +    ngx_http_v3_parse_encoder_t *st, ngx_buf_t *b);
 +static ngx_int_t ngx_http_v3_parse_field_inr(ngx_connection_t *c,
 +    ngx_http_v3_parse_field_t *st, ngx_buf_t *b);
 +static ngx_int_t ngx_http_v3_parse_field_iln(ngx_connection_t *c,
 +    ngx_http_v3_parse_field_t *st, ngx_buf_t *b);
 +
 +static ngx_int_t ngx_http_v3_parse_decoder(ngx_connection_t *c,
 +    ngx_http_v3_parse_decoder_t *st, ngx_buf_t *b);
 +
 +static ngx_int_t ngx_http_v3_parse_lookup(ngx_connection_t *c,
 +    ngx_uint_t dynamic, ngx_uint_t index, ngx_str_t *name, ngx_str_t *value);
 +
 +
 +static void
 +ngx_http_v3_parse_start_local(ngx_buf_t *b, ngx_buf_t *loc, ngx_uint_t n)
 +{
 +    *loc = *b;
 +
 +    if ((size_t) (loc->last - loc->pos) > n) {
 +        loc->last = loc->pos + n;
 +    }
 +}
 +
 +
 +static void
 +ngx_http_v3_parse_end_local(ngx_buf_t *b, ngx_buf_t *loc, ngx_uint_t *pn)
 +{
 +    *pn -= loc->pos - b->pos;
 +    b->pos = loc->pos;
 +}
 +
 +
 +static ngx_int_t
 +ngx_http_v3_parse_skip(ngx_buf_t *b, ngx_uint_t *length)
 +{
 +    if ((size_t) (b->last - b->pos) < *length) {
 +        *length -= b->last - b->pos;
 +        b->pos = b->last;
 +        return NGX_AGAIN;
 +    }
 +
 +    b->pos += *length;
 +    return NGX_DONE;
 +}
 +
 +
 +static ngx_int_t
 +ngx_http_v3_parse_varlen_int(ngx_connection_t *c,
 +    ngx_http_v3_parse_varlen_int_t *st, ngx_buf_t *b)
 +{
 +    u_char  ch;
 +    enum {
 +        sw_start = 0,
 +        sw_length_2,
 +        sw_length_3,
 +        sw_length_4,
 +        sw_length_5,
 +        sw_length_6,
 +        sw_length_7,
 +        sw_length_8
 +    };
 +
 +    for ( ;; ) {
 +
 +        if (b->pos == b->last) {
 +            return NGX_AGAIN;
 +        }
 +
 +        ch = *b->pos++;
 +
 +        switch (st->state) {
 +
 +        case sw_start:
 +
 +            st->value = ch;
 +            if (st->value & 0xc0) {
 +                st->state = sw_length_2;
 +                break;
 +            }
 +
 +            goto done;
 +
 +        case sw_length_2:
 +
 +            st->value = (st->value << 8) + ch;
 +            if ((st->value & 0xc000) == 0x4000) {
 +                st->value &= 0x3fff;
 +                goto done;
 +            }
 +
 +            st->state = sw_length_3;
 +            break;
 +
 +        case sw_length_4:
 +
 +            st->value = (st->value << 8) + ch;
 +            if ((st->value & 0xc0000000) == 0x80000000) {
 +                st->value &= 0x3fffffff;
 +                goto done;
 +            }
 +
 +            st->state = sw_length_5;
 +            break;
 +
 +        case sw_length_3:
 +        case sw_length_5:
 +        case sw_length_6:
 +        case sw_length_7:
 +
 +            st->value = (st->value << 8) + ch;
 +            st->state++;
 +            break;
 +
 +        case sw_length_8:
 +
 +            st->value = (st->value << 8) + ch;
 +            st->value &= 0x3fffffffffffffff;
 +            goto done;
 +        }
 +    }
 +
 +done:
 +
 +    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                   "http3 parse varlen int %uL", st->value);
 +
 +    st->state = sw_start;
 +    return NGX_DONE;
 +}
 +
 +
 +static ngx_int_t
 +ngx_http_v3_parse_prefix_int(ngx_connection_t *c,
 +    ngx_http_v3_parse_prefix_int_t *st, ngx_uint_t prefix, ngx_buf_t *b)
 +{
 +    u_char      ch;
 +    ngx_uint_t  mask;
 +    enum {
 +        sw_start = 0,
 +        sw_value
 +    };
 +
 +    for ( ;; ) {
 +
 +        if (b->pos == b->last) {
 +            return NGX_AGAIN;
 +        }
 +
 +        ch = *b->pos++;
 +
 +        switch (st->state) {
 +
 +        case sw_start:
 +
 +            mask = (1 << prefix) - 1;
 +            st->value = ch & mask;
 +
 +            if (st->value != mask) {
 +                goto done;
 +            }
 +
 +            st->shift = 0;
 +            st->state = sw_value;
 +            break;
 +
 +        case sw_value:
 +
 +            st->value += (uint64_t) (ch & 0x7f) << st->shift;
 +
 +            if (st->shift == 56
 +                && ((ch & 0x80) || (st->value & 0xc000000000000000)))
 +            {
 +                ngx_log_error(NGX_LOG_INFO, c->log, 0,
 +                              "client exceeded integer size limit");
 +                return NGX_HTTP_V3_ERR_EXCESSIVE_LOAD;
 +            }
 +
 +            if (ch & 0x80) {
 +                st->shift += 7;
 +                break;
 +            }
 +
 +            goto done;
 +        }
 +    }
 +
 +done:
 +
 +    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                   "http3 parse prefix int %uL", st->value);
 +
 +    st->state = sw_start;
 +    return NGX_DONE;
 +}
 +
 +
 +ngx_int_t
 +ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st,
 +    ngx_buf_t *b)
 +{
 +    ngx_buf_t  loc;
 +    ngx_int_t  rc;
 +    enum {
 +        sw_start = 0,
 +        sw_type,
 +        sw_length,
 +        sw_skip,
 +        sw_prefix,
 +        sw_verify,
 +        sw_field_rep,
 +        sw_done
 +    };
 +
 +    for ( ;; ) {
 +
 +        switch (st->state) {
 +
 +        case sw_start:
 +
 +            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                           "http3 parse headers");
 +
 +            st->state = sw_type;
 +
 +            /* fall through */
 +
 +        case sw_type:
 +
 +            rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            st->type = st->vlint.value;
 +
 +            if (ngx_http_v3_is_v2_frame(st->type)
 +                || st->type == NGX_HTTP_V3_FRAME_DATA
 +                || st->type == NGX_HTTP_V3_FRAME_GOAWAY
 +                || st->type == NGX_HTTP_V3_FRAME_SETTINGS
 +                || st->type == NGX_HTTP_V3_FRAME_MAX_PUSH_ID
 +                || st->type == NGX_HTTP_V3_FRAME_CANCEL_PUSH
 +                || st->type == NGX_HTTP_V3_FRAME_PUSH_PROMISE)
 +            {
 +                return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED;
 +            }
 +
 +            st->state = sw_length;
 +            break;
 +
 +        case sw_length:
 +
 +            rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            st->length = st->vlint.value;
 +
 +            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                           "http3 parse headers type:%ui, len:%ui",
 +                           st->type, st->length);
 +
 +            if (st->type != NGX_HTTP_V3_FRAME_HEADERS) {
 +                st->state = st->length > 0 ? sw_skip : sw_type;
 +                break;
 +            }
 +
 +            if (st->length == 0) {
 +                return NGX_HTTP_V3_ERR_FRAME_ERROR;
 +            }
 +
 +            st->state = sw_prefix;
 +            break;
 +
 +        case sw_skip:
 +
 +            rc = ngx_http_v3_parse_skip(b, &st->length);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            st->state = sw_type;
 +            break;
 +
 +        case sw_prefix:
 +
 +            ngx_http_v3_parse_start_local(b, &loc, st->length);
 +
 +            rc = ngx_http_v3_parse_field_section_prefix(c, &st->prefix, &loc);
 +
 +            ngx_http_v3_parse_end_local(b, &loc, &st->length);
 +
 +            if (st->length == 0 && rc == NGX_AGAIN) {
 +                return NGX_HTTP_V3_ERR_FRAME_ERROR;
 +            }
 +
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            st->state = sw_verify;
 +            break;
 +
 +        case sw_verify:
 +
 +            rc = ngx_http_v3_check_insert_count(c, st->prefix.insert_count);
 +            if (rc != NGX_OK) {
 +                return rc;
 +            }
 +
 +            st->state = sw_field_rep;
 +
 +            /* fall through */
 +
 +        case sw_field_rep:
 +
 +            ngx_http_v3_parse_start_local(b, &loc, st->length);
 +
 +            rc = ngx_http_v3_parse_field_rep(c, &st->field_rep, st->prefix.base,
 +                                             &loc);
 +
 +            ngx_http_v3_parse_end_local(b, &loc, &st->length);
 +
 +            if (st->length == 0 && rc == NGX_AGAIN) {
 +                return NGX_HTTP_V3_ERR_FRAME_ERROR;
 +            }
 +
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            if (st->length == 0) {
 +                goto done;
 +            }
 +
 +            return NGX_OK;
 +        }
 +    }
 +
 +done:
 +
 +    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse headers done");
 +
 +    if (st->prefix.insert_count > 0) {
 +        if (ngx_http_v3_send_ack_section(c, c->quic->id) != NGX_OK) {
 +            return NGX_ERROR;
 +        }
 +    }
 +
 +    st->state = sw_start;
 +    return NGX_DONE;
 +}
 +
 +
 +static ngx_int_t
 +ngx_http_v3_parse_field_section_prefix(ngx_connection_t *c,
 +    ngx_http_v3_parse_field_section_prefix_t *st, ngx_buf_t *b)
 +{
 +    u_char     ch;
 +    ngx_int_t  rc;
 +    enum {
 +        sw_start = 0,
 +        sw_req_insert_count,
 +        sw_delta_base,
 +        sw_read_delta_base
 +    };
 +
 +    for ( ;; ) {
 +
 +        switch (st->state) {
 +
 +        case sw_start:
 +
 +            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                           "http3 parse field section prefix");
 +
 +            st->state = sw_req_insert_count;
 +
 +            /* fall through */
 +
 +        case sw_req_insert_count:
 +
 +            rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 8, b);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            st->insert_count = st->pint.value;
 +            st->state = sw_delta_base;
 +            break;
 +
 +        case sw_delta_base:
 +
 +            if (b->pos == b->last) {
 +                return NGX_AGAIN;
 +            }
 +
 +            ch = *b->pos;
 +
 +            st->sign = (ch & 0x80) ? 1 : 0;
 +            st->state = sw_read_delta_base;
 +
 +            /* fall through */
 +
 +        case sw_read_delta_base:
 +
 +            rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            st->delta_base = st->pint.value;
 +            goto done;
 +        }
 +    }
 +
 +done:
 +
 +    rc = ngx_http_v3_decode_insert_count(c, &st->insert_count);
 +    if (rc != NGX_OK) {
 +        return rc;
 +    }
 +
 +    if (st->sign) {
 +        st->base = st->insert_count - st->delta_base - 1;
 +    } else {
 +        st->base = st->insert_count + st->delta_base;
 +    }
 +
 +    ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                  "http3 parse field section prefix done "
 +                  "insert_count:%ui, sign:%ui, delta_base:%ui, base:%ui",
 +                  st->insert_count, st->sign, st->delta_base, st->base);
 +
 +    st->state = sw_start;
 +    return NGX_DONE;
 +}
 +
 +
 +static ngx_int_t
 +ngx_http_v3_parse_field_rep(ngx_connection_t *c,
 +    ngx_http_v3_parse_field_rep_t *st, ngx_uint_t base, ngx_buf_t *b)
 +{
 +    u_char     ch;
 +    ngx_int_t  rc;
 +    enum {
 +        sw_start = 0,
 +        sw_field_ri,
 +        sw_field_lri,
 +        sw_field_l,
 +        sw_field_pbi,
 +        sw_field_lpbi
 +    };
 +
 +    if (st->state == sw_start) {
 +
 +        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                       "http3 parse field representation");
 +
 +        if (b->pos == b->last) {
 +            return NGX_AGAIN;
 +        }
 +
 +        ch = *b->pos;
 +
 +        ngx_memzero(&st->field, sizeof(ngx_http_v3_parse_field_t));
 +
 +        st->field.base = base;
 +
 +        if (ch & 0x80) {
 +            /* Indexed Field Line */
 +
 +            st->state = sw_field_ri;
 +
 +        } else if (ch & 0x40) {
 +            /* Literal Field Line With Name Reference */
 +
 +            st->state = sw_field_lri;
 +
 +        } else if (ch & 0x20) {
 +            /* Literal Field Line With Literal Name */
 +
 +            st->state = sw_field_l;
 +
 +        } else if (ch & 0x10) {
 +            /* Indexed Field Line With Post-Base Index */
 +
 +            st->state = sw_field_pbi;
 +
 +        } else {
 +            /* Literal Field Line With Post-Base Name Reference */
 +
 +            st->state = sw_field_lpbi;
 +        }
 +    }
 +
 +    switch (st->state) {
 +
 +    case sw_field_ri:
 +        rc = ngx_http_v3_parse_field_ri(c, &st->field, b);
 +        break;
 +
 +    case sw_field_lri:
 +        rc = ngx_http_v3_parse_field_lri(c, &st->field, b);
 +        break;
 +
 +    case sw_field_l:
 +        rc = ngx_http_v3_parse_field_l(c, &st->field, b);
 +        break;
 +
 +    case sw_field_pbi:
 +        rc = ngx_http_v3_parse_field_pbi(c, &st->field, b);
 +        break;
 +
 +    case sw_field_lpbi:
 +        rc = ngx_http_v3_parse_field_lpbi(c, &st->field, b);
 +        break;
 +
 +    default:
 +        rc = NGX_OK;
 +    }
 +
 +    if (rc != NGX_DONE) {
 +        return rc;
 +    }
 +
 +    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                   "http3 parse field representation done");
 +
 +    st->state = sw_start;
 +    return NGX_DONE;
 +}
 +
 +
 +static ngx_int_t
 +ngx_http_v3_parse_literal(ngx_connection_t *c, ngx_http_v3_parse_literal_t *st,
 +    ngx_buf_t *b)
 +{
 +    u_char                     ch;
 +    ngx_uint_t                 n;
 +    ngx_http_core_srv_conf_t  *cscf;
 +    enum {
 +        sw_start = 0,
 +        sw_value
 +    };
 +
 +    for ( ;; ) {
 +
 +        switch (st->state) {
 +
 +        case sw_start:
 +
 +            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                           "http3 parse literal huff:%ui, len:%ui",
 +                           st->huffman, st->length);
 +
 +            n = st->length;
 +
 +            cscf = ngx_http_v3_get_module_srv_conf(c, ngx_http_core_module);
 +
 +            if (n > cscf->large_client_header_buffers.size) {
 +                ngx_log_error(NGX_LOG_INFO, c->log, 0,
 +                              "client sent too large field line");
 +                return NGX_HTTP_V3_ERR_EXCESSIVE_LOAD;
 +            }
 +
 +            if (st->huffman) {
 +                n = n * 8 / 5;
 +                st->huffstate = 0;
 +            }
 +
 +            st->last = ngx_pnalloc(c->pool, n + 1);
 +            if (st->last == NULL) {
 +                return NGX_ERROR;
 +            }
 +
 +            st->value.data = st->last;
 +            st->state = sw_value;
 +
 +            /* fall through */
 +
 +        case sw_value:
 +
 +            if (b->pos == b->last) {
 +                return NGX_AGAIN;
 +            }
 +
 +            ch = *b->pos++;
 +
 +            if (st->huffman) {
++                if (ngx_http_huff_decode(&st->huffstate, &ch, 1, &st->last,
++                                         st->length == 1, c->log)
 +                    != NGX_OK)
 +                {
 +                    return NGX_ERROR;
 +                }
 +
 +            } else {
 +                *st->last++ = ch;
 +            }
 +
 +            if (--st->length) {
 +                break;
 +            }
 +
 +            st->value.len = st->last - st->value.data;
 +            *st->last = '\0';
 +            goto done;
 +        }
 +    }
 +
 +done:
 +
 +    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                   "http3 parse literal done \"%V\"", &st->value);
 +
 +    st->state = sw_start;
 +    return NGX_DONE;
 +}
 +
 +
 +static ngx_int_t
 +ngx_http_v3_parse_field_ri(ngx_connection_t *c, ngx_http_v3_parse_field_t *st,
 +    ngx_buf_t *b)
 +{
 +    u_char     ch;
 +    ngx_int_t  rc;
 +    enum {
 +        sw_start = 0,
 +        sw_index
 +    };
 +
 +    for ( ;; ) {
 +
 +        switch (st->state) {
 +
 +        case sw_start:
 +
 +            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                           "http3 parse field ri");
 +
 +            if (b->pos == b->last) {
 +                return NGX_AGAIN;
 +            }
 +
 +            ch = *b->pos;
 +
 +            st->dynamic = (ch & 0x40) ? 0 : 1;
 +            st->state = sw_index;
 +
 +            /* fall through */
 +
 +        case sw_index:
 +
 +            rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, b);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            st->index = st->pint.value;
 +            goto done;
 +        }
 +    }
 +
 +done:
 +
 +    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                   "http3 parse field ri done %s%ui]",
 +                   st->dynamic ? "dynamic[-" : "static[", st->index);
 +
 +    if (st->dynamic) {
 +        st->index = st->base - st->index - 1;
 +    }
 +
 +    rc = ngx_http_v3_parse_lookup(c, st->dynamic, st->index, &st->name,
 +                                  &st->value);
 +    if (rc != NGX_OK) {
 +        return rc;
 +    }
 +
 +    st->state = sw_start;
 +    return NGX_DONE;
 +}
 +
 +
 +static ngx_int_t
 +ngx_http_v3_parse_field_lri(ngx_connection_t *c,
 +    ngx_http_v3_parse_field_t *st, ngx_buf_t *b)
 +{
 +    u_char     ch;
 +    ngx_int_t  rc;
 +    enum {
 +        sw_start = 0,
 +        sw_index,
 +        sw_value_len,
 +        sw_read_value_len,
 +        sw_value
 +    };
 +
 +    for ( ;; ) {
 +
 +        switch (st->state) {
 +
 +        case sw_start:
 +
 +            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                           "http3 parse field lri");
 +
 +            if (b->pos == b->last) {
 +                return NGX_AGAIN;
 +            }
 +
 +            ch = *b->pos;
 +
 +            st->dynamic = (ch & 0x10) ? 0 : 1;
 +            st->state = sw_index;
 +
 +            /* fall through */
 +
 +        case sw_index:
 +
 +            rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 4, b);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            st->index = st->pint.value;
 +            st->state = sw_value_len;
 +            break;
 +
 +        case sw_value_len:
 +
 +            if (b->pos == b->last) {
 +                return NGX_AGAIN;
 +            }
 +
 +            ch = *b->pos;
 +
 +            st->literal.huffman = (ch & 0x80) ? 1 : 0;
 +            st->state = sw_read_value_len;
 +
 +            /* fall through */
 +
 +        case sw_read_value_len:
 +
 +            rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            st->literal.length = st->pint.value;
 +            if (st->literal.length == 0) {
 +                goto done;
 +            }
 +
 +            st->state = sw_value;
 +            break;
 +
 +        case sw_value:
 +
 +            rc = ngx_http_v3_parse_literal(c, &st->literal, b);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            st->value = st->literal.value;
 +            goto done;
 +        }
 +    }
 +
 +done:
 +
 +    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                   "http3 parse field lri done %s%ui] \"%V\"",
 +                   st->dynamic ? "dynamic[-" : "static[",
 +                   st->index, &st->value);
 +
 +    if (st->dynamic) {
 +        st->index = st->base - st->index - 1;
 +    }
 +
 +    rc = ngx_http_v3_parse_lookup(c, st->dynamic, st->index, &st->name, NULL);
 +    if (rc != NGX_OK) {
 +        return rc;
 +    }
 +
 +    st->state = sw_start;
 +    return NGX_DONE;
 +}
 +
 +
 +static ngx_int_t
 +ngx_http_v3_parse_field_l(ngx_connection_t *c,
 +    ngx_http_v3_parse_field_t *st, ngx_buf_t *b)
 +{
 +    u_char     ch;
 +    ngx_int_t  rc;
 +    enum {
 +        sw_start = 0,
 +        sw_name_len,
 +        sw_name,
 +        sw_value_len,
 +        sw_read_value_len,
 +        sw_value
 +    };
 +
 +    for ( ;; ) {
 +
 +        switch (st->state) {
 +
 +        case sw_start:
 +
 +            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse field l");
 +
 +            if (b->pos == b->last) {
 +                return NGX_AGAIN;
 +            }
 +
 +            ch = *b->pos;
 +
 +            st->literal.huffman = (ch & 0x08) ? 1 : 0;
 +            st->state = sw_name_len;
 +
 +            /* fall through */
 +
 +        case sw_name_len:
 +
 +            rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 3, b);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            st->literal.length = st->pint.value;
 +            if (st->literal.length == 0) {
 +                return NGX_ERROR;
 +            }
 +
 +            st->state = sw_name;
 +            break;
 +
 +        case sw_name:
 +
 +            rc = ngx_http_v3_parse_literal(c, &st->literal, b);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            st->name = st->literal.value;
 +            st->state = sw_value_len;
 +            break;
 +
 +        case sw_value_len:
 +
 +            if (b->pos == b->last) {
 +                return NGX_AGAIN;
 +            }
 +
 +            ch = *b->pos;
 +
 +            st->literal.huffman = (ch & 0x80) ? 1 : 0;
 +            st->state = sw_read_value_len;
 +
 +            /* fall through */
 +
 +        case sw_read_value_len:
 +
 +            rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            st->literal.length = st->pint.value;
 +            if (st->literal.length == 0) {
 +                goto done;
 +            }
 +
 +            st->state = sw_value;
 +            break;
 +
 +        case sw_value:
 +
 +            rc = ngx_http_v3_parse_literal(c, &st->literal, b);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            st->value = st->literal.value;
 +            goto done;
 +        }
 +    }
 +
 +done:
 +
 +    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                   "http3 parse field l done \"%V\" \"%V\"",
 +                   &st->name, &st->value);
 +
 +    st->state = sw_start;
 +    return NGX_DONE;
 +}
 +
 +
 +static ngx_int_t
 +ngx_http_v3_parse_field_pbi(ngx_connection_t *c,
 +    ngx_http_v3_parse_field_t *st, ngx_buf_t *b)
 +{
 +    ngx_int_t  rc;
 +    enum {
 +        sw_start = 0,
 +        sw_index
 +    };
 +
 +    for ( ;; ) {
 +
 +        switch (st->state) {
 +
 +        case sw_start:
 +
 +            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                           "http3 parse field pbi");
 +
 +            st->state = sw_index;
 +
 +            /* fall through */
 +
 +        case sw_index:
 +
 +            rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 4, b);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            st->index = st->pint.value;
 +            goto done;
 +        }
 +    }
 +
 +done:
 +
 +    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                   "http3 parse field pbi done dynamic[+%ui]", st->index);
 +
 +    rc = ngx_http_v3_parse_lookup(c, 1, st->base + st->index, &st->name,
 +                                  &st->value);
 +    if (rc != NGX_OK) {
 +        return rc;
 +    }
 +
 +    st->state = sw_start;
 +    return NGX_DONE;
 +}
 +
 +
 +static ngx_int_t
 +ngx_http_v3_parse_field_lpbi(ngx_connection_t *c,
 +    ngx_http_v3_parse_field_t *st, ngx_buf_t *b)
 +{
 +    u_char     ch;
 +    ngx_int_t  rc;
 +    enum {
 +        sw_start = 0,
 +        sw_index,
 +        sw_value_len,
 +        sw_read_value_len,
 +        sw_value
 +    };
 +
 +    for ( ;; ) {
 +
 +        switch (st->state) {
 +
 +        case sw_start:
 +
 +            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                           "http3 parse field lpbi");
 +
 +            st->state = sw_index;
 +
 +            /* fall through */
 +
 +        case sw_index:
 +
 +            rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 3, b);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            st->index = st->pint.value;
 +            st->state = sw_value_len;
 +            break;
 +
 +        case sw_value_len:
 +
 +            if (b->pos == b->last) {
 +                return NGX_AGAIN;
 +            }
 +
 +            ch = *b->pos;
 +
 +            st->literal.huffman = (ch & 0x80) ? 1 : 0;
 +            st->state = sw_read_value_len;
 +
 +            /* fall through */
 +
 +        case sw_read_value_len:
 +
 +            rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            st->literal.length = st->pint.value;
 +            if (st->literal.length == 0) {
 +                goto done;
 +            }
 +
 +            st->state = sw_value;
 +            break;
 +
 +        case sw_value:
 +
 +            rc = ngx_http_v3_parse_literal(c, &st->literal, b);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            st->value = st->literal.value;
 +            goto done;
 +        }
 +    }
 +
 +done:
 +
 +    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                   "http3 parse field lpbi done dynamic[+%ui] \"%V\"",
 +                   st->index, &st->value);
 +
 +    rc = ngx_http_v3_parse_lookup(c, 1, st->base + st->index, &st->name, NULL);
 +    if (rc != NGX_OK) {
 +        return rc;
 +    }
 +
 +    st->state = sw_start;
 +    return NGX_DONE;
 +}
 +
 +
 +static ngx_int_t
 +ngx_http_v3_parse_lookup(ngx_connection_t *c, ngx_uint_t dynamic,
 +    ngx_uint_t index, ngx_str_t *name, ngx_str_t *value)
 +{
 +    u_char  *p;
 +
 +    if (!dynamic) {
 +        if (ngx_http_v3_lookup_static(c, index, name, value) != NGX_OK) {
 +            return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED;
 +        }
 +
 +        return NGX_OK;
 +    }
 +
 +    if (ngx_http_v3_lookup(c, index, name, value) != NGX_OK) {
 +        return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED;
 +    }
 +
 +    if (name) {
 +        p = ngx_pnalloc(c->pool, name->len + 1);
 +        if (p == NULL) {
 +            return NGX_ERROR;
 +        }
 +
 +        ngx_memcpy(p, name->data, name->len);
 +        p[name->len] = '\0';
 +        name->data = p;
 +    }
 +
 +    if (value) {
 +        p = ngx_pnalloc(c->pool, value->len + 1);
 +        if (p == NULL) {
 +            return NGX_ERROR;
 +        }
 +
 +        ngx_memcpy(p, value->data, value->len);
 +        p[value->len] = '\0';
 +        value->data = p;
 +    }
 +
 +    return NGX_OK;
 +}
 +
 +
 +static ngx_int_t
 +ngx_http_v3_parse_control(ngx_connection_t *c, ngx_http_v3_parse_control_t *st,
 +    ngx_buf_t *b)
 +{
 +    ngx_buf_t  loc;
 +    ngx_int_t  rc;
 +    enum {
 +        sw_start = 0,
 +        sw_first_type,
 +        sw_type,
 +        sw_length,
 +        sw_cancel_push,
 +        sw_settings,
 +        sw_max_push_id,
 +        sw_goaway,
 +        sw_skip
 +    };
 +
 +    for ( ;; ) {
 +
 +        switch (st->state) {
 +
 +        case sw_start:
 +
 +            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                           "http3 parse control");
 +
 +            st->state = sw_first_type;
 +
 +            /* fall through */
 +
 +        case sw_first_type:
 +        case sw_type:
 +
 +            rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            st->type = st->vlint.value;
 +
 +            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                           "http3 parse frame type:%ui", st->type);
 +
 +            if (st->state == sw_first_type
 +                && st->type != NGX_HTTP_V3_FRAME_SETTINGS)
 +            {
 +                return NGX_HTTP_V3_ERR_MISSING_SETTINGS;
 +            }
 +
 +            if (st->state != sw_first_type
 +                && st->type == NGX_HTTP_V3_FRAME_SETTINGS)
 +            {
 +                return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED;
 +            }
 +
 +            if (ngx_http_v3_is_v2_frame(st->type)
 +                || st->type == NGX_HTTP_V3_FRAME_DATA
 +                || st->type == NGX_HTTP_V3_FRAME_HEADERS
 +                || st->type == NGX_HTTP_V3_FRAME_PUSH_PROMISE)
 +            {
 +                return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED;
 +            }
 +
 +            st->state = sw_length;
 +            break;
 +
 +        case sw_length:
 +
 +            rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                           "http3 parse frame len:%uL", st->vlint.value);
 +
 +            st->length = st->vlint.value;
 +            if (st->length == 0) {
 +                st->state = sw_type;
 +                break;
 +            }
 +
 +            switch (st->type) {
 +
 +            case NGX_HTTP_V3_FRAME_CANCEL_PUSH:
 +                st->state = sw_cancel_push;
 +                break;
 +
 +            case NGX_HTTP_V3_FRAME_SETTINGS:
 +                st->state = sw_settings;
 +                break;
 +
 +            case NGX_HTTP_V3_FRAME_MAX_PUSH_ID:
 +                st->state = sw_max_push_id;
 +                break;
 +
 +            case NGX_HTTP_V3_FRAME_GOAWAY:
 +                st->state = sw_goaway;
 +                break;
 +
 +            default:
 +                ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                               "http3 parse skip unknown frame");
 +                st->state = sw_skip;
 +            }
 +
 +            break;
 +
 +        case sw_cancel_push:
 +
 +            ngx_http_v3_parse_start_local(b, &loc, st->length);
 +
 +            rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, &loc);
 +
 +            ngx_http_v3_parse_end_local(b, &loc, &st->length);
 +
 +            if (st->length == 0 && rc == NGX_AGAIN) {
 +                return NGX_HTTP_V3_ERR_FRAME_ERROR;
 +            }
 +
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            rc = ngx_http_v3_cancel_push(c, st->vlint.value);
 +            if (rc != NGX_OK) {
 +                return rc;
 +            }
 +
 +            st->state = sw_type;
 +            break;
 +
 +        case sw_settings:
 +
 +            ngx_http_v3_parse_start_local(b, &loc, st->length);
 +
 +            rc = ngx_http_v3_parse_settings(c, &st->settings, &loc);
 +
 +            ngx_http_v3_parse_end_local(b, &loc, &st->length);
 +
 +            if (st->length == 0 && rc == NGX_AGAIN) {
 +                return NGX_HTTP_V3_ERR_SETTINGS_ERROR;
 +            }
 +
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            if (st->length == 0) {
 +                st->state = sw_type;
 +            }
 +
 +            break;
 +
 +        case sw_max_push_id:
 +
 +            ngx_http_v3_parse_start_local(b, &loc, st->length);
 +
 +            rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, &loc);
 +
 +            ngx_http_v3_parse_end_local(b, &loc, &st->length);
 +
 +            if (st->length == 0 && rc == NGX_AGAIN) {
 +                return NGX_HTTP_V3_ERR_FRAME_ERROR;
 +            }
 +
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            rc = ngx_http_v3_set_max_push_id(c, st->vlint.value);
 +            if (rc != NGX_OK) {
 +                return rc;
 +            }
 +
 +            st->state = sw_type;
 +            break;
 +
 +        case sw_goaway:
 +
 +            ngx_http_v3_parse_start_local(b, &loc, st->length);
 +
 +            rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, &loc);
 +
 +            ngx_http_v3_parse_end_local(b, &loc, &st->length);
 +
 +            if (st->length == 0 && rc == NGX_AGAIN) {
 +                return NGX_HTTP_V3_ERR_FRAME_ERROR;
 +            }
 +
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            rc = ngx_http_v3_goaway(c, st->vlint.value);
 +            if (rc != NGX_OK) {
 +                return rc;
 +            }
 +
 +            st->state = sw_type;
 +            break;
 +
 +        case sw_skip:
 +
 +            rc = ngx_http_v3_parse_skip(b, &st->length);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            st->state = sw_type;
 +            break;
 +        }
 +    }
 +}
 +
 +
 +static ngx_int_t
 +ngx_http_v3_parse_settings(ngx_connection_t *c,
 +    ngx_http_v3_parse_settings_t *st, ngx_buf_t *b)
 +{
 +    ngx_int_t  rc;
 +    enum {
 +        sw_start = 0,
 +        sw_id,
 +        sw_value
 +    };
 +
 +    for ( ;; ) {
 +
 +        switch (st->state) {
 +
 +        case sw_start:
 +
 +            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                           "http3 parse settings");
 +
 +            st->state = sw_id;
 +
 +            /* fall through */
 +
 +        case sw_id:
 +
 +            rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            st->id = st->vlint.value;
 +            st->state = sw_value;
 +            break;
 +
 +        case sw_value:
 +
 +            rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            if (ngx_http_v3_set_param(c, st->id, st->vlint.value) != NGX_OK) {
 +                return NGX_HTTP_V3_ERR_SETTINGS_ERROR;
 +            }
 +
 +            goto done;
 +        }
 +    }
 +
 +done:
 +
 +    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse settings done");
 +
 +    st->state = sw_start;
 +    return NGX_DONE;
 +}
 +
 +
 +static ngx_int_t
 +ngx_http_v3_parse_encoder(ngx_connection_t *c, ngx_http_v3_parse_encoder_t *st,
 +    ngx_buf_t *b)
 +{
 +    u_char     ch;
 +    ngx_int_t  rc;
 +    enum {
 +        sw_start = 0,
 +        sw_inr,
 +        sw_iln,
 +        sw_capacity,
 +        sw_duplicate
 +    };
 +
 +    for ( ;; ) {
 +
 +        if (st->state == sw_start) {
 +
 +            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                           "http3 parse encoder instruction");
 +
 +            if (b->pos == b->last) {
 +                return NGX_AGAIN;
 +            }
 +
 +            ch = *b->pos;
 +
 +            if (ch & 0x80) {
 +                /* Insert With Name Reference */
 +
 +                st->state = sw_inr;
 +
 +            } else if (ch & 0x40) {
 +                /* Insert With Literal Name */
 +
 +                st->state = sw_iln;
 +
 +            } else if (ch & 0x20) {
 +                /* Set Dynamic Table Capacity */
 +
 +                st->state = sw_capacity;
 +
 +            } else {
 +                /* Duplicate */
 +
 +                st->state = sw_duplicate;
 +            }
 +        }
 +
 +        switch (st->state) {
 +
 +        case sw_inr:
 +
 +            rc = ngx_http_v3_parse_field_inr(c, &st->field, b);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            st->state = sw_start;
 +            break;
 +
 +        case sw_iln:
 +
 +            rc = ngx_http_v3_parse_field_iln(c, &st->field, b);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            st->state = sw_start;
 +            break;
 +
 +        case sw_capacity:
 +
 +            rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 5, b);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            rc = ngx_http_v3_set_capacity(c, st->pint.value);
 +            if (rc != NGX_OK) {
 +                return rc;
 +            }
 +
 +            st->state = sw_start;
 +            break;
 +
 +        default: /* sw_duplicate */
 +
 +            rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 5, b);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            rc = ngx_http_v3_duplicate(c, st->pint.value);
 +            if (rc != NGX_OK) {
 +                return rc;
 +            }
 +
 +            st->state = sw_start;
 +            break;
 +        }
 +    }
 +}
 +
 +
 +static ngx_int_t
 +ngx_http_v3_parse_field_inr(ngx_connection_t *c,
 +    ngx_http_v3_parse_field_t *st, ngx_buf_t *b)
 +{
 +    u_char     ch;
 +    ngx_int_t  rc;
 +    enum {
 +        sw_start = 0,
 +        sw_name_index,
 +        sw_value_len,
 +        sw_read_value_len,
 +        sw_value
 +    };
 +
 +    for ( ;; ) {
 +
 +        switch (st->state) {
 +
 +        case sw_start:
 +
 +            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                           "http3 parse field inr");
 +
 +            if (b->pos == b->last) {
 +                return NGX_AGAIN;
 +            }
 +
 +            ch = *b->pos;
 +
 +            st->dynamic = (ch & 0x40) ? 0 : 1;
 +            st->state = sw_name_index;
 +
 +            /* fall through */
 +
 +        case sw_name_index:
 +
 +            rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, b);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            st->index = st->pint.value;
 +            st->state = sw_value_len;
 +            break;
 +
 +        case sw_value_len:
 +
 +            if (b->pos == b->last) {
 +                return NGX_AGAIN;
 +            }
 +
 +            ch = *b->pos;
 +
 +            st->literal.huffman = (ch & 0x80) ? 1 : 0;
 +            st->state = sw_read_value_len;
 +
 +            /* fall through */
 +
 +        case sw_read_value_len:
 +
 +            rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            st->literal.length = st->pint.value;
 +            if (st->literal.length == 0) {
 +                st->value.len = 0;
 +                goto done;
 +            }
 +
 +            st->state = sw_value;
 +            break;
 +
 +        case sw_value:
 +
 +            rc = ngx_http_v3_parse_literal(c, &st->literal, b);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            st->value = st->literal.value;
 +            goto done;
 +        }
 +    }
 +
 +done:
 +
 +    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                   "http3 parse field inr done %s[%ui] \"%V\"",
 +                   st->dynamic ? "dynamic" : "static",
 +                   st->index, &st->value);
 +
 +    rc = ngx_http_v3_ref_insert(c, st->dynamic, st->index, &st->value);
 +    if (rc != NGX_OK) {
 +        return rc;
 +    }
 +
 +    st->state = sw_start;
 +    return NGX_DONE;
 +}
 +
 +
 +static ngx_int_t
 +ngx_http_v3_parse_field_iln(ngx_connection_t *c,
 +    ngx_http_v3_parse_field_t *st, ngx_buf_t *b)
 +{
 +    u_char     ch;
 +    ngx_int_t  rc;
 +    enum {
 +        sw_start = 0,
 +        sw_name_len,
 +        sw_name,
 +        sw_value_len,
 +        sw_read_value_len,
 +        sw_value
 +    };
 +
 +    for ( ;; ) {
 +
 +        switch (st->state) {
 +
 +        case sw_start:
 +
 +            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                           "http3 parse field iln");
 +
 +            if (b->pos == b->last) {
 +                return NGX_AGAIN;
 +            }
 +
 +            ch = *b->pos;
 +
 +            st->literal.huffman = (ch & 0x20) ? 1 : 0;
 +            st->state = sw_name_len;
 +
 +            /* fall through */
 +
 +        case sw_name_len:
 +
 +            rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 5, b);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            st->literal.length = st->pint.value;
 +            if (st->literal.length == 0) {
 +                return NGX_ERROR;
 +            }
 +
 +            st->state = sw_name;
 +            break;
 +
 +        case sw_name:
 +
 +            rc = ngx_http_v3_parse_literal(c, &st->literal, b);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            st->name = st->literal.value;
 +            st->state = sw_value_len;
 +            break;
 +
 +        case sw_value_len:
 +
 +            if (b->pos == b->last) {
 +                return NGX_AGAIN;
 +            }
 +
 +            ch = *b->pos;
 +
 +            st->literal.huffman = (ch & 0x80) ? 1 : 0;
 +            st->state = sw_read_value_len;
 +
 +            /* fall through */
 +
 +        case sw_read_value_len:
 +
 +            rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            st->literal.length = st->pint.value;
 +            if (st->literal.length == 0) {
 +                st->value.len = 0;
 +                goto done;
 +            }
 +
 +            st->state = sw_value;
 +            break;
 +
 +        case sw_value:
 +
 +            rc = ngx_http_v3_parse_literal(c, &st->literal, b);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            st->value = st->literal.value;
 +            goto done;
 +        }
 +    }
 +
 +done:
 +
 +    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                   "http3 parse field iln done \"%V\":\"%V\"",
 +                   &st->name, &st->value);
 +
 +    rc = ngx_http_v3_insert(c, &st->name, &st->value);
 +    if (rc != NGX_OK) {
 +        return rc;
 +    }
 +
 +    st->state = sw_start;
 +    return NGX_DONE;
 +}
 +
 +
 +static ngx_int_t
 +ngx_http_v3_parse_decoder(ngx_connection_t *c, ngx_http_v3_parse_decoder_t *st,
 +    ngx_buf_t *b)
 +{
 +    u_char     ch;
 +    ngx_int_t  rc;
 +    enum {
 +        sw_start = 0,
 +        sw_ack_section,
 +        sw_cancel_stream,
 +        sw_inc_insert_count
 +    };
 +
 +    for ( ;; ) {
 +
 +        if (st->state == sw_start) {
 +
 +            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                           "http3 parse decoder instruction");
 +
 +            if (b->pos == b->last) {
 +                return NGX_AGAIN;
 +            }
 +
 +            ch = *b->pos;
 +
 +            if (ch & 0x80) {
 +                /* Section Acknowledgment */
 +
 +                st->state = sw_ack_section;
 +
 +            } else if (ch & 0x40) {
 +                /*  Stream Cancellation */
 +
 +                st->state = sw_cancel_stream;
 +
 +            }  else {
 +                /*  Insert Count Increment */
 +
 +                st->state = sw_inc_insert_count;
 +            }
 +        }
 +
 +        switch (st->state) {
 +
 +        case sw_ack_section:
 +
 +            rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            rc = ngx_http_v3_ack_section(c, st->pint.value);
 +            if (rc != NGX_OK) {
 +                return rc;
 +            }
 +
 +            st->state = sw_start;
 +            break;
 +
 +        case sw_cancel_stream:
 +
 +            rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, b);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            rc = ngx_http_v3_cancel_stream(c, st->pint.value);
 +            if (rc != NGX_OK) {
 +                return rc;
 +            }
 +
 +            st->state = sw_start;
 +            break;
 +
 +        case sw_inc_insert_count:
 +
 +            rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, b);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            rc = ngx_http_v3_inc_insert_count(c, st->pint.value);
 +            if (rc != NGX_OK) {
 +                return rc;
 +            }
 +
 +            st->state = sw_start;
 +            break;
 +        }
 +    }
 +}
 +
 +
 +ngx_int_t
 +ngx_http_v3_parse_data(ngx_connection_t *c, ngx_http_v3_parse_data_t *st,
 +    ngx_buf_t *b)
 +{
 +    ngx_int_t  rc;
 +    enum {
 +        sw_start = 0,
 +        sw_type,
 +        sw_length,
 +        sw_skip
 +    };
 +
 +    for ( ;; ) {
 +
 +        switch (st->state) {
 +
 +        case sw_start:
 +
 +            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse data");
 +
 +            st->state = sw_type;
 +
 +            /* fall through */
 +
 +        case sw_type:
 +
 +            rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            st->type = st->vlint.value;
 +
 +            if (st->type == NGX_HTTP_V3_FRAME_HEADERS) {
 +                /* trailers */
 +                goto done;
 +            }
 +
 +            if (ngx_http_v3_is_v2_frame(st->type)
 +                || st->type == NGX_HTTP_V3_FRAME_GOAWAY
 +                || st->type == NGX_HTTP_V3_FRAME_SETTINGS
 +                || st->type == NGX_HTTP_V3_FRAME_MAX_PUSH_ID
 +                || st->type == NGX_HTTP_V3_FRAME_CANCEL_PUSH
 +                || st->type == NGX_HTTP_V3_FRAME_PUSH_PROMISE)
 +            {
 +                return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED;
 +            }
 +
 +            st->state = sw_length;
 +            break;
 +
 +        case sw_length:
 +
 +            rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            st->length = st->vlint.value;
 +
 +            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                           "http3 parse data type:%ui, len:%ui",
 +                           st->type, st->length);
 +
 +            if (st->type != NGX_HTTP_V3_FRAME_DATA && st->length > 0) {
 +                st->state = sw_skip;
 +                break;
 +            }
 +
 +            st->state = sw_type;
 +            return NGX_OK;
 +
 +        case sw_skip:
 +
 +            rc = ngx_http_v3_parse_skip(b, &st->length);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            st->state = sw_type;
 +            break;
 +        }
 +    }
 +
 +done:
 +
 +    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse data done");
 +
 +    st->state = sw_start;
 +    return NGX_DONE;
 +}
 +
 +
 +ngx_int_t
 +ngx_http_v3_parse_uni(ngx_connection_t *c, ngx_http_v3_parse_uni_t *st,
 +    ngx_buf_t *b)
 +{
 +    ngx_int_t  rc;
 +    enum {
 +        sw_start = 0,
 +        sw_type,
 +        sw_control,
 +        sw_encoder,
 +        sw_decoder,
 +        sw_unknown
 +    };
 +
 +    for ( ;; ) {
 +
 +        switch (st->state) {
 +        case sw_start:
 +
 +            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse uni");
 +
 +            st->state = sw_type;
 +
 +            /* fall through */
 +
 +        case sw_type:
 +
 +            rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b);
 +            if (rc != NGX_DONE) {
 +                return rc;
 +            }
 +
 +            rc = ngx_http_v3_register_uni_stream(c, st->vlint.value);
 +            if (rc != NGX_OK) {
 +                return rc;
 +            }
 +
 +            switch (st->vlint.value) {
 +            case NGX_HTTP_V3_STREAM_CONTROL:
 +                st->state = sw_control;
 +                break;
 +
 +            case NGX_HTTP_V3_STREAM_ENCODER:
 +                st->state = sw_encoder;
 +                break;
 +
 +            case NGX_HTTP_V3_STREAM_DECODER:
 +                st->state = sw_decoder;
 +                break;
 +
 +            default:
 +                st->state = sw_unknown;
 +            }
 +
 +            break;
 +
 +        case sw_control:
 +
 +            return ngx_http_v3_parse_control(c, &st->u.control, b);
 +
 +        case sw_encoder:
 +
 +            return ngx_http_v3_parse_encoder(c, &st->u.encoder, b);
 +
 +        case sw_decoder:
 +
 +            return ngx_http_v3_parse_decoder(c, &st->u.decoder, b);
 +
 +        case sw_unknown:
 +
 +            b->pos = b->last;
 +            return NGX_AGAIN;
 +        }
 +    }
 +}
Simple merge