changeset 8838:d6e191a583cc quic

HTTP/3: bulk parse functions. Previously HTTP/3 streams were parsed by one character. Now all parse functions receive buffers. This should optimize parsing time and CPU load.
author Roman Arutyunyan <arut@nginx.com>
date Thu, 08 Jul 2021 21:52:47 +0300
parents 8f0f6407ae23
children fac88e160653
files src/http/v3/ngx_http_v3_parse.c src/http/v3/ngx_http_v3_parse.h src/http/v3/ngx_http_v3_request.c src/http/v3/ngx_http_v3_streams.c
diffstat 4 files changed, 1376 insertions(+), 1178 deletions(-) [+]
line wrap: on
line diff
--- a/src/http/v3/ngx_http_v3_parse.c
+++ b/src/http/v3/ngx_http_v3_parse.c
@@ -14,51 +14,91 @@
     ((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, u_char ch);
+    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, u_char ch);
+    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, u_char ch);
+    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, u_char ch);
+    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, u_char ch);
+    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, u_char ch);
+    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, u_char ch);
+    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, u_char ch);
+    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, u_char ch);
+    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, u_char ch);
+    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, u_char ch);
+    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, u_char ch);
+    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, u_char ch);
+    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, u_char ch);
+    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, u_char ch);
+    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, u_char ch);
+    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, u_char ch)
+    ngx_http_v3_parse_varlen_int_t *st, ngx_buf_t *b)
 {
+    u_char  ch;
     enum {
         sw_start = 0,
         sw_length_2,
@@ -70,58 +110,65 @@ ngx_http_v3_parse_varlen_int(ngx_connect
         sw_length_8
     };
 
-    switch (st->state) {
-
-    case sw_start:
-
-        st->value = ch;
-        if (st->value & 0xc0) {
-            st->state = sw_length_2;
-            break;
+    for ( ;; ) {
+
+        if (b->pos == b->last) {
+            return NGX_AGAIN;
         }
 
-        goto done;
-
-    case sw_length_2:
-
-        st->value = (st->value << 8) + ch;
-        if ((st->value & 0xc000) == 0x4000) {
-            st->value &= 0x3fff;
+        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;
         }
-
-        st->state = sw_length_3;
-        break;
-
-    case sw_length_4:
-
-        st->value = (st->value << 8) + ch;
-        if ((st->value & 0xc0000000) == 0x80000000) {
-            st->value &= 0x3fffffff;
-            goto done;
-        }
-
-        st->state = sw_length_5;
-        break;
-
-    case sw_length_3:
-    case sw_length_5:
-    case sw_length_6:
-    case sw_length_7:
-
-        st->value = (st->value << 8) + ch;
-        st->state++;
-        break;
-
-    case sw_length_8:
-
-        st->value = (st->value << 8) + ch;
-        st->value &= 0x3fffffffffffffff;
-        goto done;
     }
 
-    return NGX_AGAIN;
-
 done:
 
     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
@@ -134,51 +181,59 @@ 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, u_char ch)
+    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
     };
 
-    switch (st->state) {
-
-    case sw_start:
-
-        mask = (1 << prefix) - 1;
-        st->value = ch & mask;
-
-        if (st->value != mask) {
+    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;
         }
-
-        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;
     }
 
-    return NGX_AGAIN;
-
 done:
 
     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
@@ -191,8 +246,9 @@ done:
 
 ngx_int_t
 ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st,
-    u_char ch)
+    ngx_buf_t *b)
 {
+    ngx_buf_t  loc;
     ngx_int_t  rc;
     enum {
         sw_start = 0,
@@ -205,119 +261,132 @@ ngx_http_v3_parse_headers(ngx_connection
         sw_done
     };
 
-    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, ch);
-        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, ch);
-        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;
+    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;
-        }
-
-        if (st->length == 0) {
-            return NGX_HTTP_V3_ERR_FRAME_ERROR;
+
+        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;
         }
-
-        st->state = sw_prefix;
-        break;
-
-    case sw_skip:
-
-        if (--st->length == 0) {
-            st->state = sw_type;
-        }
-
-        break;
-
-    case sw_prefix:
-
-        if (--st->length == 0) {
-            return NGX_HTTP_V3_ERR_FRAME_ERROR;
-        }
-
-        rc = ngx_http_v3_parse_field_section_prefix(c, &st->prefix, ch);
-        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:
-
-        rc = ngx_http_v3_parse_field_rep(c, &st->field_rep, st->prefix.base,
-                                         ch);
-
-        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;
     }
 
-    return NGX_AGAIN;
-
 done:
 
     ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse headers done");
@@ -335,8 +404,9 @@ 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, u_char ch)
+    ngx_http_v3_parse_field_section_prefix_t *st, ngx_buf_t *b)
 {
+    u_char     ch;
     ngx_int_t  rc;
     enum {
         sw_start = 0,
@@ -345,48 +415,55 @@ ngx_http_v3_parse_field_section_prefix(n
         sw_read_delta_base
     };
 
-    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, ch);
-        if (rc != NGX_DONE) {
-            return rc;
+    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;
         }
-
-        st->insert_count = st->pint.value;
-        st->state = sw_delta_base;
-        break;
-
-    case sw_delta_base:
-
-        st->sign = (ch & 0x80) ? 1 : 0;
-        st->state = sw_read_delta_base;
-
-        /* fall through */
-
-    case sw_read_delta_base:
-
-        rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch);
-        if (rc != NGX_DONE) {
-            return rc;
-        }
-
-        st->delta_base = st->pint.value;
-        goto done;
     }
 
-    return NGX_AGAIN;
-
 done:
 
     rc = ngx_http_v3_decode_insert_count(c, &st->insert_count);
@@ -412,8 +489,9 @@ 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, u_char ch)
+    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,
@@ -429,6 +507,12 @@ ngx_http_v3_parse_field_rep(ngx_connecti
         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;
@@ -463,23 +547,23 @@ ngx_http_v3_parse_field_rep(ngx_connecti
     switch (st->state) {
 
     case sw_field_ri:
-        rc = ngx_http_v3_parse_field_ri(c, &st->field, ch);
+        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, ch);
+        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, ch);
+        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, ch);
+        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, ch);
+        rc = ngx_http_v3_parse_field_lpbi(c, &st->field, b);
         break;
 
     default:
@@ -500,8 +584,9 @@ ngx_http_v3_parse_field_rep(ngx_connecti
 
 static ngx_int_t
 ngx_http_v3_parse_literal(ngx_connection_t *c, ngx_http_v3_parse_literal_t *st,
-    u_char ch)
+    ngx_buf_t *b)
 {
+    u_char                     ch;
     ngx_uint_t                 n;
     ngx_http_core_srv_conf_t  *cscf;
     enum {
@@ -509,64 +594,71 @@ ngx_http_v3_parse_literal(ngx_connection
         sw_value
     };
 
-    switch (st->state) {
-
-    case sw_start:
-
-        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                       "http3 parse literal huff:%ui, len:%ui",
-                       st->huffman, st->length);
-
-        n = st->length;
-
-        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 (st->huffman) {
-            if (ngx_http_v2_huff_decode(&st->huffstate, &ch, 1, &st->last,
-                                        st->length == 1, c->log)
-                != NGX_OK)
-            {
+    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;
             }
 
-        } else {
-            *st->last++ = ch;
-        }
-
-        if (--st->length) {
-            break;
+            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_v2_huff_decode(&st->huffstate, &ch, 1, &st->last,
+                                            st->length == 1, c->log)
+                    != NGX_OK)
+                {
+                    return NGX_ERROR;
+                }
+
+            } else {
+                *st->last++ = ch;
+            }
+
+            if (--st->length) {
+                break;
+            }
+
+            st->value.len = st->last - st->value.data;
+            *st->last = '\0';
+            goto done;
         }
-
-        st->value.len = st->last - st->value.data;
-        *st->last = '\0';
-        goto done;
     }
 
-    return NGX_AGAIN;
-
 done:
 
     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
@@ -579,38 +671,47 @@ done:
 
 static ngx_int_t
 ngx_http_v3_parse_field_ri(ngx_connection_t *c, ngx_http_v3_parse_field_t *st,
-    u_char ch)
+    ngx_buf_t *b)
 {
+    u_char     ch;
     ngx_int_t  rc;
     enum {
         sw_start = 0,
         sw_index
     };
 
-    switch (st->state) {
-
-    case sw_start:
-
-        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse field ri");
-
-        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, ch);
-        if (rc != NGX_DONE) {
-            return rc;
+    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;
         }
-
-        st->index = st->pint.value;
-        goto done;
     }
 
-    return NGX_AGAIN;
-
 done:
 
     ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
@@ -634,8 +735,9 @@ done:
 
 static ngx_int_t
 ngx_http_v3_parse_field_lri(ngx_connection_t *c,
-    ngx_http_v3_parse_field_t *st, u_char ch)
+    ngx_http_v3_parse_field_t *st, ngx_buf_t *b)
 {
+    u_char     ch;
     ngx_int_t  rc;
     enum {
         sw_start = 0,
@@ -645,63 +747,77 @@ ngx_http_v3_parse_field_lri(ngx_connecti
         sw_value
     };
 
-    switch (st->state) {
-
-    case sw_start:
-
-        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse field lri");
-
-        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, ch);
-        if (rc != NGX_DONE) {
-            return rc;
-        }
-
-        st->index = st->pint.value;
-        st->state = sw_value_len;
-        break;
-
-    case sw_value_len:
-
-        st->literal.huffman = (ch & 0x80) ? 1 : 0;
-        st->state = sw_read_value_len;
-
-        /* fall through */
-
-    case sw_read_value_len:
-
-        rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch);
-        if (rc != NGX_DONE) {
-            return rc;
-        }
-
-        st->literal.length = st->pint.value;
-        if (st->literal.length == 0) {
+    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;
         }
-
-        st->state = sw_value;
-        break;
-
-    case sw_value:
-
-        rc = ngx_http_v3_parse_literal(c, &st->literal, ch);
-        if (rc != NGX_DONE) {
-            return rc;
-        }
-
-        st->value = st->literal.value;
-        goto done;
     }
 
-    return NGX_AGAIN;
-
 done:
 
     ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
@@ -725,8 +841,9 @@ done:
 
 static ngx_int_t
 ngx_http_v3_parse_field_l(ngx_connection_t *c,
-    ngx_http_v3_parse_field_t *st, u_char ch)
+    ngx_http_v3_parse_field_t *st, ngx_buf_t *b)
 {
+    u_char     ch;
     ngx_int_t  rc;
     enum {
         sw_start = 0,
@@ -737,78 +854,91 @@ ngx_http_v3_parse_field_l(ngx_connection
         sw_value
     };
 
-    switch (st->state) {
-
-    case sw_start:
-
-        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse field l");
-
-        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, ch);
-        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, ch);
-        if (rc != NGX_DONE) {
-            return rc;
-        }
-
-        st->name = st->literal.value;
-        st->state = sw_value_len;
-        break;
-
-    case sw_value_len:
-
-        st->literal.huffman = (ch & 0x80) ? 1 : 0;
-        st->state = sw_read_value_len;
-
-        /* fall through */
-
-    case sw_read_value_len:
-
-        rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch);
-        if (rc != NGX_DONE) {
-            return rc;
-        }
-
-        st->literal.length = st->pint.value;
-        if (st->literal.length == 0) {
+    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;
         }
-
-        st->state = sw_value;
-        break;
-
-    case sw_value:
-
-        rc = ngx_http_v3_parse_literal(c, &st->literal, ch);
-        if (rc != NGX_DONE) {
-            return rc;
-        }
-
-        st->value = st->literal.value;
-        goto done;
     }
 
-    return NGX_AGAIN;
-
 done:
 
     ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
@@ -822,7 +952,7 @@ done:
 
 static ngx_int_t
 ngx_http_v3_parse_field_pbi(ngx_connection_t *c,
-    ngx_http_v3_parse_field_t *st, u_char ch)
+    ngx_http_v3_parse_field_t *st, ngx_buf_t *b)
 {
     ngx_int_t  rc;
     enum {
@@ -830,29 +960,31 @@ ngx_http_v3_parse_field_pbi(ngx_connecti
         sw_index
     };
 
-    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, ch);
-        if (rc != NGX_DONE) {
-            return rc;
+    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;
         }
-
-        st->index = st->pint.value;
-        goto done;
     }
 
-    return NGX_AGAIN;
-
 done:
 
     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
@@ -871,8 +1003,9 @@ done:
 
 static ngx_int_t
 ngx_http_v3_parse_field_lpbi(ngx_connection_t *c,
-    ngx_http_v3_parse_field_t *st, u_char ch)
+    ngx_http_v3_parse_field_t *st, ngx_buf_t *b)
 {
+    u_char     ch;
     ngx_int_t  rc;
     enum {
         sw_start = 0,
@@ -882,63 +1015,70 @@ ngx_http_v3_parse_field_lpbi(ngx_connect
         sw_value
     };
 
-    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, ch);
-        if (rc != NGX_DONE) {
-            return rc;
-        }
-
-        st->index = st->pint.value;
-        st->state = sw_value_len;
-        break;
-
-    case sw_value_len:
-
-        st->literal.huffman = (ch & 0x80) ? 1 : 0;
-        st->state = sw_read_value_len;
-
-        /* fall through */
-
-    case sw_read_value_len:
-
-        rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch);
-        if (rc != NGX_DONE) {
-            return rc;
-        }
-
-        st->literal.length = st->pint.value;
-        if (st->literal.length == 0) {
+    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;
         }
-
-        st->state = sw_value;
-        break;
-
-    case sw_value:
-
-        rc = ngx_http_v3_parse_literal(c, &st->literal, ch);
-        if (rc != NGX_DONE) {
-            return rc;
-        }
-
-        st->value = st->literal.value;
-        goto done;
     }
 
-    return NGX_AGAIN;
-
 done:
 
     ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
@@ -1001,8 +1141,9 @@ ngx_http_v3_parse_lookup(ngx_connection_
 
 static ngx_int_t
 ngx_http_v3_parse_control(ngx_connection_t *c, ngx_http_v3_parse_control_t *st,
-    u_char ch)
+    ngx_buf_t *b)
 {
+    ngx_buf_t  loc;
     ngx_int_t  rc;
     enum {
         sw_start = 0,
@@ -1016,188 +1157,208 @@ ngx_http_v3_parse_control(ngx_connection
         sw_skip
     };
 
-    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, ch);
-        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, ch);
-        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) {
+    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;
         }
-
-        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:
-
-        rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch);
-
-        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:
-
-        rc = ngx_http_v3_parse_settings(c, &st->settings, ch);
-
-        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:
-
-        rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch);
-
-        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:
-
-        rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch);
-
-        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:
-
-        if (--st->length == 0) {
-            st->state = sw_type;
-        }
-
-        break;
     }
-
-    return NGX_AGAIN;
 }
 
 
 static ngx_int_t
 ngx_http_v3_parse_settings(ngx_connection_t *c,
-    ngx_http_v3_parse_settings_t *st, u_char ch)
+    ngx_http_v3_parse_settings_t *st, ngx_buf_t *b)
 {
     ngx_int_t  rc;
     enum {
@@ -1206,43 +1367,45 @@ ngx_http_v3_parse_settings(ngx_connectio
         sw_value
     };
 
-    switch (st->state) {
-
-    case sw_start:
-
-        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse settings");
-
-        st->state = sw_id;
-
-        /* fall through */
-
-    case sw_id:
-
-        rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch);
-        if (rc != NGX_DONE) {
-            return rc;
+    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;
         }
-
-        st->id = st->vlint.value;
-        st->state = sw_value;
-        break;
-
-    case sw_value:
-
-        rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch);
-        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;
     }
 
-    return NGX_AGAIN;
-
 done:
 
     ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse settings done");
@@ -1254,8 +1417,9 @@ done:
 
 static ngx_int_t
 ngx_http_v3_parse_encoder(ngx_connection_t *c, ngx_http_v3_parse_encoder_t *st,
-    u_char ch)
+    ngx_buf_t *b)
 {
+    u_char     ch;
     ngx_int_t  rc;
     enum {
         sw_start = 0,
@@ -1265,98 +1429,102 @@ ngx_http_v3_parse_encoder(ngx_connection
         sw_duplicate
     };
 
-    if (st->state == sw_start) {
-
-        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                       "http3 parse encoder instruction");
-
-        if (ch & 0x80) {
-            /* Insert With Name Reference */
-
-            st->state = sw_inr;
-
-        } else if (ch & 0x40) {
-            /* Insert 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;
+    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;
         }
     }
-
-    switch (st->state) {
-
-    case sw_inr:
-
-        rc = ngx_http_v3_parse_field_inr(c, &st->field, ch);
-        if (rc != NGX_DONE) {
-            return rc;
-        }
-
-        goto done;
-
-    case sw_iln:
-
-        rc = ngx_http_v3_parse_field_iln(c, &st->field, ch);
-        if (rc != NGX_DONE) {
-            return rc;
-        }
-
-        goto done;
-
-    case sw_capacity:
-
-        rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 5, ch);
-        if (rc != NGX_DONE) {
-            return rc;
-        }
-
-        rc = ngx_http_v3_set_capacity(c, st->pint.value);
-        if (rc != NGX_OK) {
-            return rc;
-        }
-
-        goto done;
-
-    case sw_duplicate:
-
-        rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 5, ch);
-        if (rc != NGX_DONE) {
-            return rc;
-        }
-
-        rc = ngx_http_v3_duplicate(c, st->pint.value);
-        if (rc != NGX_OK) {
-            return rc;
-        }
-
-        goto done;
-    }
-
-    return NGX_AGAIN;
-
-done:
-
-    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                   "http3 parse encoder instruction done");
-
-    st->state = sw_start;
-    return NGX_AGAIN;
 }
 
 
 static ngx_int_t
 ngx_http_v3_parse_field_inr(ngx_connection_t *c,
-    ngx_http_v3_parse_field_t *st, u_char ch)
+    ngx_http_v3_parse_field_t *st, ngx_buf_t *b)
 {
+    u_char     ch;
     ngx_int_t  rc;
     enum {
         sw_start = 0,
@@ -1366,64 +1534,78 @@ ngx_http_v3_parse_field_inr(ngx_connecti
         sw_value
     };
 
-    switch (st->state) {
-
-    case sw_start:
-
-        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse field inr");
-
-        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, ch);
-        if (rc != NGX_DONE) {
-            return rc;
-        }
-
-        st->index = st->pint.value;
-        st->state = sw_value_len;
-        break;
-
-    case sw_value_len:
-
-        st->literal.huffman = (ch & 0x80) ? 1 : 0;
-        st->state = sw_read_value_len;
-
-        /* fall through */
-
-    case sw_read_value_len:
-
-        rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch);
-        if (rc != NGX_DONE) {
-            return rc;
-        }
-
-        st->literal.length = st->pint.value;
-        if (st->literal.length == 0) {
-            st->value.len = 0;
+    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;
         }
-
-        st->state = sw_value;
-        break;
-
-    case sw_value:
-
-        rc = ngx_http_v3_parse_literal(c, &st->literal, ch);
-        if (rc != NGX_DONE) {
-            return rc;
-        }
-
-        st->value = st->literal.value;
-        goto done;
     }
 
-    return NGX_AGAIN;
-
 done:
 
     ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
@@ -1443,8 +1625,9 @@ done:
 
 static ngx_int_t
 ngx_http_v3_parse_field_iln(ngx_connection_t *c,
-    ngx_http_v3_parse_field_t *st, u_char ch)
+    ngx_http_v3_parse_field_t *st, ngx_buf_t *b)
 {
+    u_char     ch;
     ngx_int_t  rc;
     enum {
         sw_start = 0,
@@ -1455,80 +1638,93 @@ ngx_http_v3_parse_field_iln(ngx_connecti
         sw_value
     };
 
-    switch (st->state) {
-
-    case sw_start:
-
-        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                       "http3 parse field iln");
-
-        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, ch);
-        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, ch);
-        if (rc != NGX_DONE) {
-            return rc;
-        }
-
-        st->name = st->literal.value;
-        st->state = sw_value_len;
-        break;
-
-    case sw_value_len:
-
-        st->literal.huffman = (ch & 0x80) ? 1 : 0;
-        st->state = sw_read_value_len;
-
-        /* fall through */
-
-    case sw_read_value_len:
-
-        rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch);
-        if (rc != NGX_DONE) {
-            return rc;
-        }
-
-        st->literal.length = st->pint.value;
-        if (st->literal.length == 0) {
-            st->value.len = 0;
+    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;
         }
-
-        st->state = sw_value;
-        break;
-
-    case sw_value:
-
-        rc = ngx_http_v3_parse_literal(c, &st->literal, ch);
-        if (rc != NGX_DONE) {
-            return rc;
-        }
-
-        st->value = st->literal.value;
-        goto done;
     }
 
-    return NGX_AGAIN;
-
 done:
 
     ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
@@ -1547,8 +1743,9 @@ done:
 
 static ngx_int_t
 ngx_http_v3_parse_decoder(ngx_connection_t *c, ngx_http_v3_parse_decoder_t *st,
-    u_char ch)
+    ngx_buf_t *b)
 {
+    u_char     ch;
     ngx_int_t  rc;
     enum {
         sw_start = 0,
@@ -1557,88 +1754,90 @@ ngx_http_v3_parse_decoder(ngx_connection
         sw_inc_insert_count
     };
 
-    if (st->state == sw_start) {
-
-        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                       "http3 parse decoder instruction");
-
-        if (ch & 0x80) {
-            /* 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;
+    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;
         }
     }
-
-    switch (st->state) {
-
-    case sw_ack_section:
-
-        rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch);
-        if (rc != NGX_DONE) {
-            return rc;
-        }
-
-        rc = ngx_http_v3_ack_section(c, st->pint.value);
-        if (rc != NGX_OK) {
-            return rc;
-        }
-
-        goto done;
-
-    case sw_cancel_stream:
-
-        rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, ch);
-        if (rc != NGX_DONE) {
-            return rc;
-        }
-
-        rc = ngx_http_v3_cancel_stream(c, st->pint.value);
-        if (rc != NGX_OK) {
-            return rc;
-        }
-
-        goto done;
-
-    case sw_inc_insert_count:
-
-        rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, ch);
-        if (rc != NGX_DONE) {
-            return rc;
-        }
-
-        rc = ngx_http_v3_inc_insert_count(c, st->pint.value);
-        if (rc != NGX_OK) {
-            return rc;
-        }
-
-        goto done;
-    }
-
-    return NGX_AGAIN;
-
-done:
-
-    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                   "http3 parse decoder instruction done");
-
-    st->state = sw_start;
-    return NGX_AGAIN;
 }
 
 
 ngx_int_t
 ngx_http_v3_parse_data(ngx_connection_t *c, ngx_http_v3_parse_data_t *st,
-    u_char ch)
+    ngx_buf_t *b)
 {
     ngx_int_t  rc;
     enum {
@@ -1648,75 +1847,78 @@ ngx_http_v3_parse_data(ngx_connection_t 
         sw_skip
     };
 
-    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, ch);
-        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, ch);
-        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;
+    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;
         }
-
-        st->state = sw_type;
-        return NGX_OK;
-
-    case sw_skip:
-
-        if (--st->length == 0) {
-            st->state = sw_type;
-        }
-
-        break;
     }
 
-    return NGX_AGAIN;
-
 done:
 
     ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse data done");
@@ -1728,7 +1930,7 @@ done:
 
 ngx_int_t
 ngx_http_v3_parse_uni(ngx_connection_t *c, ngx_http_v3_parse_uni_t *st,
-    u_char ch)
+    ngx_buf_t *b)
 {
     ngx_int_t  rc;
     enum {
@@ -1740,76 +1942,64 @@ ngx_http_v3_parse_uni(ngx_connection_t *
         sw_unknown
     };
 
-    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, ch);
-        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;
+    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 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;
+        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;
         }
-
-        break;
-
-    case sw_control:
-
-        rc = ngx_http_v3_parse_control(c, &st->u.control, ch);
-        if (rc != NGX_OK) {
-            return rc;
-        }
-
-        break;
-
-    case sw_encoder:
-
-        rc = ngx_http_v3_parse_encoder(c, &st->u.encoder, ch);
-        if (rc != NGX_OK) {
-            return rc;
-        }
-
-        break;
-
-    case sw_decoder:
-
-        rc = ngx_http_v3_parse_decoder(c, &st->u.decoder, ch);
-        if (rc != NGX_OK) {
-            return rc;
-        }
-
-        break;
-
-    case sw_unknown:
-        break;
     }
-
-    return NGX_AGAIN;
 }
--- a/src/http/v3/ngx_http_v3_parse.h
+++ b/src/http/v3/ngx_http_v3_parse.h
@@ -136,11 +136,11 @@ typedef struct {
  */
 
 ngx_int_t ngx_http_v3_parse_headers(ngx_connection_t *c,
-    ngx_http_v3_parse_headers_t *st, u_char ch);
+    ngx_http_v3_parse_headers_t *st, ngx_buf_t *b);
 ngx_int_t ngx_http_v3_parse_data(ngx_connection_t *c,
-    ngx_http_v3_parse_data_t *st, u_char ch);
+    ngx_http_v3_parse_data_t *st, ngx_buf_t *b);
 ngx_int_t ngx_http_v3_parse_uni(ngx_connection_t *c,
-    ngx_http_v3_parse_uni_t *st, u_char ch);
+    ngx_http_v3_parse_uni_t *st, ngx_buf_t *b);
 
 
 #endif /* _NGX_HTTP_V3_PARSE_H_INCLUDED_ */
--- a/src/http/v3/ngx_http_v3_request.c
+++ b/src/http/v3/ngx_http_v3_request.c
@@ -207,6 +207,7 @@ ngx_http_v3_cleanup_request(void *data)
 static void
 ngx_http_v3_process_request(ngx_event_t *rev)
 {
+    u_char                       *p;
     ssize_t                       n;
     ngx_buf_t                    *b;
     ngx_int_t                     rc;
@@ -273,7 +274,9 @@ ngx_http_v3_process_request(ngx_event_t 
             b->last = b->start + n;
         }
 
-        rc = ngx_http_v3_parse_headers(c, st, *b->pos);
+        p = b->pos;
+
+        rc = ngx_http_v3_parse_headers(c, st, b);
 
         if (rc > 0) {
             ngx_http_v3_finalize_connection(c, rc,
@@ -302,8 +305,7 @@ ngx_http_v3_process_request(ngx_event_t 
             break;
         }
 
-        b->pos++;
-        r->request_length++;
+        r->request_length += b->pos - p;
 
         if (rc == NGX_AGAIN) {
             continue;
@@ -1024,6 +1026,7 @@ ngx_http_v3_request_body_filter(ngx_http
 {
     off_t                      max;
     size_t                     size;
+    u_char                    *p;
     ngx_int_t                  rc;
     ngx_buf_t                 *b;
     ngx_uint_t                 last;
@@ -1078,9 +1081,11 @@ ngx_http_v3_request_body_filter(ngx_http
         while (cl->buf->pos < cl->buf->last) {
 
             if (st->length == 0) {
-                r->request_length++;
+                p = cl->buf->pos;
 
-                rc = ngx_http_v3_parse_data(r->connection, st, *cl->buf->pos++);
+                rc = ngx_http_v3_parse_data(r->connection, st, cl->buf);
+
+                r->request_length += cl->buf->pos - p;
 
                 if (rc == NGX_AGAIN) {
                     continue;
--- a/src/http/v3/ngx_http_v3_streams.c
+++ b/src/http/v3/ngx_http_v3_streams.c
@@ -168,7 +168,8 @@ ngx_http_v3_uni_read_handler(ngx_event_t
 {
     u_char                     buf[128];
     ssize_t                    n;
-    ngx_int_t                  rc, i;
+    ngx_buf_t                  b;
+    ngx_int_t                  rc;
     ngx_connection_t          *c;
     ngx_http_v3_uni_stream_t  *us;
 
@@ -177,6 +178,8 @@ ngx_http_v3_uni_read_handler(ngx_event_t
 
     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));
@@ -201,25 +204,25 @@ ngx_http_v3_uni_read_handler(ngx_event_t
             break;
         }
 
-        for (i = 0; i < n; i++) {
+        b.pos = buf;
+        b.last = buf + n;
 
-            rc = ngx_http_v3_parse_uni(c, &us->parse, buf[i]);
+        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 == 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 > 0) {
+            goto failed;
+        }
 
-            if (rc != NGX_AGAIN) {
-                rc = NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR;
-                goto failed;
-            }
+        if (rc != NGX_AGAIN) {
+            rc = NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR;
+            goto failed;
         }
     }