view src/http/v3/ngx_http_v3_streams.c @ 7690:ae35ccba7aa6 quic

Extracted transport part of the code into separate file. All code dealing with serializing/deserializing is moved int srv/event/ngx_event_quic_transport.c/h file. All macros for dealing with data are internal to source file. The header file exposes frame types and error codes. The exported functions are currently packet header parsers and writers and frames parser/writer. The ngx_quic_header_t structure is updated with 'log' member. This avoids passing extra argument to parsing functions that need to report errors.
author Vladimir Homutov <vl@nginx.com>
date Wed, 18 Mar 2020 12:58:27 +0300
parents 38c0898b6df7
children 268f4389130d
line wrap: on
line source


/*
 * Copyright (C) Roman Arutyunyan
 * Copyright (C) Nginx, Inc.
 */


#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>


#define NGX_HTTP_V3_CONTROL_STREAM  0x00
#define NGX_HTTP_V3_PUSH_STREAM     0x01
#define NGX_HTTP_V3_ENCODER_STREAM  0x02
#define NGX_HTTP_V3_DECODER_STREAM  0x03


typedef struct {
    uint32_t    signature; /* QSTR */
    u_char      buf[4];

    ngx_uint_t  len;
    ngx_uint_t  type;
    ngx_uint_t  state;
    ngx_uint_t  index;
    ngx_uint_t  offset;

    ngx_str_t   name;
    ngx_str_t   value;

    unsigned    client:1;
    unsigned    dynamic:1;
    unsigned    huffman:1;
} ngx_http_v3_uni_stream_t;


static void ngx_http_v3_close_uni_stream(ngx_connection_t *c);
static void ngx_http_v3_uni_stream_cleanup(void *data);
static void ngx_http_v3_read_uni_stream_type(ngx_event_t *rev);
static void ngx_http_v3_dummy_stream_handler(ngx_event_t *rev);
static void ngx_http_v3_client_encoder_handler(ngx_event_t *rev);
static void ngx_http_v3_client_decoder_handler(ngx_event_t *rev);

static ngx_connection_t *ngx_http_v3_create_uni_stream(ngx_connection_t *c,
    ngx_uint_t type);
static ngx_connection_t *ngx_http_v3_get_server_encoder(ngx_connection_t *c);
static ngx_connection_t *ngx_http_v3_get_server_decoder(ngx_connection_t *c);


void
ngx_http_v3_handle_client_uni_stream(ngx_connection_t *c)
{
    ngx_pool_cleanup_t        *cln;
    ngx_http_v3_uni_stream_t  *us;

    c->log->connection = c->number;

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
                   "http3 new uni stream id:0x%uXL", c->qs->id);

    us = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_uni_stream_t));
    if (us == NULL) {
        ngx_http_v3_close_uni_stream(c);
        return;
    }

    us->signature = NGX_HTTP_V3_STREAM;
    us->client = 1;
    us->type = (ngx_uint_t) -1;

    c->data = us;

    cln = ngx_pool_cleanup_add(c->pool, 0);
    if (cln == NULL) {
        ngx_http_v3_close_uni_stream(c);
        return;
    }

    cln->handler = ngx_http_v3_uni_stream_cleanup;
    cln->data = c;

    c->read->handler = ngx_http_v3_read_uni_stream_type;
    c->read->handler(c->read);
}


static void
ngx_http_v3_close_uni_stream(ngx_connection_t *c)
{
    ngx_pool_t  *pool;

    c->destroyed = 1;

    pool = c->pool;

    ngx_close_connection(c);

    ngx_destroy_pool(pool);
}


static void
ngx_http_v3_uni_stream_cleanup(void *data)
{
    ngx_connection_t  *c = data;

    ngx_http_v3_connection_t  *h3c;
    ngx_http_v3_uni_stream_t  *us;

    us = c->data;
    h3c = c->qs->parent->data;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 close stream");

    switch (us->type) {

    case NGX_HTTP_V3_ENCODER_STREAM:

        if (us->client) {
            h3c->client_encoder = NULL;
        } else {
            h3c->server_encoder = NULL;
        }

        break;

    case NGX_HTTP_V3_DECODER_STREAM:

        if (us->client) {
            h3c->client_decoder = NULL;
        } else {
            h3c->server_decoder = NULL;
        }

        break;
    }
}


static void
ngx_http_v3_read_uni_stream_type(ngx_event_t *rev)
{
    u_char                    *p;
    ssize_t                    n, len;
    ngx_connection_t          *c;
    ngx_http_v3_connection_t  *h3c;
    ngx_http_v3_uni_stream_t  *us;

    c = rev->data;
    us = c->data;
    h3c = c->qs->parent->data;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read stream type");

    while (rev->ready) {

        p = &us->buf[us->len];

        if (us->len == 0) {
            len = 1;
        } else {
            len = (us->buf[0] >> 6) + 1 - us->len;
        }

        n = c->recv(c, p, len);

        if (n == NGX_ERROR) {
            goto failed;
        }

        if (n == NGX_AGAIN) {
            break;
        }

        us->len += n;

        if (n != len) {
            break;
        }

        if ((us->buf[0] >> 6) + 1 == us->len) {
            us->type = ngx_http_v3_decode_varlen_int(us->buf);

            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
                           "http3 stream type:%ui", us->type);

            switch (us->type) {

            case NGX_HTTP_V3_ENCODER_STREAM:
                if (h3c->client_encoder) {
                    goto failed;
                }

                h3c->client_encoder = c;
                rev->handler = ngx_http_v3_client_encoder_handler;
                break;

            case NGX_HTTP_V3_DECODER_STREAM:
                if (h3c->client_decoder) {
                    goto failed;
                }

                h3c->client_decoder = c;
                rev->handler = ngx_http_v3_client_decoder_handler;
                break;

            case NGX_HTTP_V3_CONTROL_STREAM:
            case NGX_HTTP_V3_PUSH_STREAM:

                /* ignore these */

            default:
                rev->handler = ngx_http_v3_dummy_stream_handler;
            }

            rev->handler(rev);
            return;
        }
    }

    if (ngx_handle_read_event(rev, 0) != NGX_OK) {
        goto failed;
    }

    return;

failed:

    ngx_http_v3_close_uni_stream(c);
}


static void
ngx_http_v3_dummy_stream_handler(ngx_event_t *rev)
{
    u_char             buf[128];
    ngx_connection_t  *c;

    /* read out and ignore */

    c = rev->data;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 dummy stream reader");

    while (rev->ready) {
        if (c->recv(c, buf, sizeof(buf)) == NGX_ERROR) {
            goto failed;
        }
    }

    if (ngx_handle_read_event(rev, 0) != NGX_OK) {
        goto failed;
    }

    return;

failed:

    ngx_http_v3_close_uni_stream(c);
}


static void
ngx_http_v3_client_encoder_handler(ngx_event_t *rev)
{
    u_char                     v;
    ssize_t                    n;
    ngx_str_t                  name, value;
    ngx_uint_t                 dynamic, huffman, index, offset;
    ngx_connection_t          *c, *pc;
    ngx_http_v3_uni_stream_t  *st;
    enum {
        sw_start = 0,
        sw_inr_name_index,
        sw_inr_value_length,
        sw_inr_read_value_length,
        sw_inr_value,
        sw_iwnr_name_length,
        sw_iwnr_name,
        sw_iwnr_value_length,
        sw_iwnr_read_value_length,
        sw_iwnr_value,
        sw_capacity,
        sw_duplicate
    } state;

    c = rev->data;
    st = c->data;
    pc = c->qs->parent;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 client encoder");

    state = st->state;
    dynamic = st->dynamic;
    huffman = st->huffman;
    index = st->index;
    offset = st->offset;
    name = st->name;
    value = st->value;

    while (rev->ready) {

        /* XXX limit checks */
        /* XXX buffer input */

        n = c->recv(c, &v, 1);

        if (n == NGX_ERROR || n == 0) {
            goto failed;
        }

        if (n != 1) {
            break;
        }

        /* XXX v -> ch */

        switch (state) {

        case sw_start:

            if (v & 0x80) {
                /* Insert With Name Reference */

                dynamic = (v & 0x40) ? 0 : 1;
                index = v & 0x3f;

                if (index != 0x3f) {
                    state = sw_inr_value_length;
                    break;
                }

                index = 0;
                state = sw_inr_name_index;
                break;
            }

            if (v & 0x40) {
                /*  Insert Without Name Reference */

                huffman = (v & 0x20) ? 1 : 0;
                name.len = v & 0x1f;

                if (name.len != 0x1f) {
                    offset = 0;
                    state = sw_iwnr_name;
                    break;
                }

                name.len = 0;
                state = sw_iwnr_name_length;
                break;
            }

            if (v & 0x20) {
                /*  Set Dynamic Table Capacity */

                index = v & 0x1f;

                if (index != 0x1f) {
                    if (ngx_http_v3_set_capacity(c, index) != NGX_OK) {
                        goto failed;
                    }

                    break;
                }

                index = 0;
                state = sw_capacity;
                break;
            }

            /* Duplicate */

            index = v & 0x1f;

            if (index != 0x1f) {
                if (ngx_http_v3_duplicate(c, index) != NGX_OK) {
                    goto failed;
                }

                break;
            }

            index = 0;
            state = sw_duplicate;
            break;

        case sw_inr_name_index:

            index = (index << 7) + (v & 0x7f);
            if (v & 0x80) {
                break;
            }

            index += 0x3f;
            state = sw_inr_value_length;
            break;

        case sw_inr_value_length:

            huffman = (v & 0x80) ? 1 : 0;
            value.len = v & 0x7f;

            if (value.len == 0) {
                value.data = NULL;

                if (ngx_http_v3_ref_insert(c, dynamic, index, &value) != NGX_OK)
                {
                    goto failed;
                }

                state = sw_start;
                break;
            }

            if (value.len != 0x7f) {
                value.data = ngx_pnalloc(pc->pool, value.len);
                if (value.data == NULL) {
                    goto failed;
                }

                state = sw_inr_value;
                offset = 0;
                break;
            }

            value.len = 0;
            state = sw_inr_read_value_length;
            break;

        case sw_inr_read_value_length:

            value.len = (value.len << 7) + (v & 0x7f);
            if (v & 0x80) {
                break;
            }

            value.len += 0x7f;

            value.data = ngx_pnalloc(pc->pool, value.len);
            if (value.data == NULL) {
                goto failed;
            }

            state = sw_inr_value;
            offset = 0;
            break;

        case sw_inr_value:

            value.data[offset++] = v;
            if (offset != value.len) {
                break;
            }

            if (huffman) {
                if (ngx_http_v3_decode_huffman(pc, &value) != NGX_OK) {
                    goto failed;
                }
            }

            if (ngx_http_v3_ref_insert(c, dynamic, index, &value) != NGX_OK) {
                goto failed;
            }

            state = sw_start;
            break;

        case sw_iwnr_name_length:

            name.len = (name.len << 7) + (v & 0x7f);
            if (v & 0x80) {
                break;
            }

            name.len += 0x1f;

            name.data = ngx_pnalloc(pc->pool, name.len);
            if (name.data == NULL) {
                goto failed;
            }

            offset = 0;
            state = sw_iwnr_name;
            break;

        case sw_iwnr_name:

            name.data[offset++] = v;
            if (offset != name.len) {
                break;
            }

            if (huffman) {
                if (ngx_http_v3_decode_huffman(pc, &name) != NGX_OK) {
                    goto failed;
                }
            }

            state = sw_iwnr_value_length;
            break;

        case sw_iwnr_value_length:

            huffman = (v & 0x80) ? 1 : 0;
            value.len = v & 0x7f;

            if (value.len == 0) {
                value.data = NULL;

                if (ngx_http_v3_insert(c, &name, &value) != NGX_OK) {
                    goto failed;
                }

                state = sw_start;
                break;
            }

            if (value.len != 0x7f) {
                value.data = ngx_pnalloc(pc->pool, value.len);
                if (value.data == NULL) {
                    goto failed;
                }

                offset = 0;
                state = sw_iwnr_value;
                break;
            }

            state = sw_iwnr_read_value_length;
            break;

        case sw_iwnr_read_value_length:

            value.len = (value.len << 7) + (v & 0x7f);
            if (v & 0x80) {
                break;
            }

            value.data = ngx_pnalloc(pc->pool, value.len);
            if (value.data == NULL) {
                goto failed;
            }

            offset = 0;
            state = sw_iwnr_value;
            break;

        case sw_iwnr_value:

            value.data[offset++] = v;
            if (offset != value.len) {
                break;
            }

            if (huffman) {
                if (ngx_http_v3_decode_huffman(pc, &value) != NGX_OK) {
                    goto failed;
                }
            }

            if (ngx_http_v3_insert(c, &name, &value) != NGX_OK) {
                goto failed;
            }

            state = sw_start;
            break;


        case sw_capacity:

            index = (index << 7) + (v & 0x7f);
            if (v & 0x80) {
                break;
            }

            index += 0x1f;

            if (ngx_http_v3_set_capacity(c, index) != NGX_OK) {
                goto failed;
            }

            state = sw_start;
            break;

        case sw_duplicate:

            index = (index << 7) + (v & 0x7f);
            if (v & 0x80) {
                break;
            }

            index += 0x1f;

            if (ngx_http_v3_duplicate(c, index) != NGX_OK) {
                goto failed;
            }

            state = sw_start;
            break;
        }
    }

    st->state = state;
    st->dynamic = dynamic;
    st->huffman = huffman;
    st->index = index;
    st->offset = offset;
    st->name = name;
    st->value = value;

    if (ngx_handle_read_event(rev, 0) != NGX_OK) {
        goto failed;
    }

    return;

failed:

    ngx_http_v3_close_uni_stream(c);
}


static void
ngx_http_v3_client_decoder_handler(ngx_event_t *rev)
{
    u_char                     v;
    ssize_t                    n;
    ngx_uint_t                 index;
    ngx_connection_t          *c;
    ngx_http_v3_uni_stream_t  *st;
    enum {
        sw_start = 0,
        sw_ack_header,
        sw_cancel_stream,
        sw_inc_insert_count
    } state;

    c = rev->data;
    st = c->data;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 client decoder");

    state = st->state;
    index = st->index;

    while (rev->ready) {

        /* XXX limit checks */
        /* XXX buffer input */

        n = c->recv(c, &v, 1);

        if (n == NGX_ERROR || n == 0) {
            goto failed;
        }

        if (n != 1) {
            break;
        }

        switch (state) {

        case sw_start:

            if (v & 0x80) {
                /* Header Acknowledgement */

                index = v & 0x7f;

                if (index != 0x7f) {
                    if (ngx_http_v3_ack_header(c, index) != NGX_OK) {
                        goto failed;
                    }

                    break;
                }

                index = 0;
                state = sw_ack_header;
                break;
            }

            if (v & 0x40) {
                /*  Stream Cancellation */

                index = v & 0x3f;

                if (index != 0x3f) {
                    if (ngx_http_v3_cancel_stream(c, index) != NGX_OK) {
                        goto failed;
                    }

                    break;
                }

                index = 0;
                state = sw_cancel_stream;
                break;
            }

            /*  Insert Count Increment */

            index = v & 0x3f;

            if (index != 0x3f) {
                if (ngx_http_v3_inc_insert_count(c, index) != NGX_OK) {
                    goto failed;
                }

                break;
            }

            index = 0;
            state = sw_inc_insert_count;
            break;

        case sw_ack_header:

            index = (index << 7) + (v & 0x7f);
            if (v & 0x80) {
                break;
            }

            index += 0x7f;

            if (ngx_http_v3_ack_header(c, index) != NGX_OK) {
                goto failed;
            }

            state = sw_start;
            break;

        case sw_cancel_stream:

            index = (index << 7) + (v & 0x7f);
            if (v & 0x80) {
                break;
            }

            index += 0x3f;

            if (ngx_http_v3_cancel_stream(c, index) != NGX_OK) {
                goto failed;
            }

            state = sw_start;
            break;

        case sw_inc_insert_count:

            index = (index << 7) + (v & 0x7f);
            if (v & 0x80) {
                break;
            }

            index += 0x3f;

            if (ngx_http_v3_inc_insert_count(c, index) != NGX_OK) {
                goto failed;
            }

            state = sw_start;
            break;
        }
    }

    st->state = state;
    st->index = index;

    if (ngx_handle_read_event(rev, 0) != NGX_OK) {
        goto failed;
    }

    return;

failed:

    ngx_http_v3_close_uni_stream(c);
}


/* XXX async & buffered stream writes */

static ngx_connection_t *
ngx_http_v3_create_uni_stream(ngx_connection_t *c, ngx_uint_t type)
{
    u_char                     buf[NGX_HTTP_V3_VARLEN_INT_LEN];
    size_t                     n;
    ngx_connection_t          *sc;
    ngx_pool_cleanup_t        *cln;
    ngx_http_v3_uni_stream_t  *us;

    sc = ngx_quic_create_uni_stream(c->qs->parent);
    if (sc == NULL) {
        return NULL;
    }

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
                   "http3 create uni stream, type:%ui", type);

    us = ngx_pcalloc(sc->pool, sizeof(ngx_http_v3_uni_stream_t));
    if (us == NULL) {
        goto failed;
    }

    us->signature = NGX_HTTP_V3_STREAM;
    us->type = type;
    sc->data = us;

    cln = ngx_pool_cleanup_add(sc->pool, 0);
    if (cln == NULL) {
        goto failed;
    }

    cln->handler = ngx_http_v3_uni_stream_cleanup;
    cln->data = sc;

    n = (u_char *) ngx_http_v3_encode_varlen_int(buf, type) - buf;

    if (sc->send(sc, buf, n) != (ssize_t) n) {
        goto failed;
    }

    return sc;

failed:

    ngx_http_v3_close_uni_stream(sc);

    return NULL;
}


static ngx_connection_t *
ngx_http_v3_get_server_encoder(ngx_connection_t *c)
{
    ngx_http_v3_connection_t  *h3c;

    h3c = c->qs->parent->data;

    if (h3c->server_encoder == NULL) {
        h3c->server_encoder = ngx_http_v3_create_uni_stream(c,
                                                   NGX_HTTP_V3_ENCODER_STREAM);
    }

    return h3c->server_encoder;
}


static ngx_connection_t *
ngx_http_v3_get_server_decoder(ngx_connection_t *c)
{
    ngx_http_v3_connection_t  *h3c;

    h3c = c->qs->parent->data;

    if (h3c->server_decoder == NULL) {
        h3c->server_decoder = ngx_http_v3_create_uni_stream(c,
                                                   NGX_HTTP_V3_DECODER_STREAM);
    }

    return h3c->server_decoder;
}


ngx_int_t
ngx_http_v3_client_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic,
    ngx_uint_t index, ngx_str_t *value)
{
    u_char            *p, buf[NGX_HTTP_V3_PREFIX_INT_LEN * 2];
    size_t             n;
    ngx_connection_t  *ec;

    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
                   "http3 client ref insert, %s[%ui] \"%V\"",
                   dynamic ? "dynamic" : "static", index, value);

    ec = ngx_http_v3_get_server_encoder(c);
    if (ec == NULL) {
        return NGX_ERROR;
    }

    p = buf;

    *p = (dynamic ? 0x80 : 0xc0);
    p = (u_char *) ngx_http_v3_encode_prefix_int(p, index, 6);

    /* XXX option for huffman? */
    *p = 0;
    p = (u_char *) ngx_http_v3_encode_prefix_int(p, value->len, 7);

    n = p - buf;

    if (ec->send(ec, buf, n) != (ssize_t) n) {
        goto failed;
    }

    if (ec->send(ec, value->data, value->len) != (ssize_t) value->len) {
        goto failed;
    }

    return NGX_OK;

failed:

    ngx_http_v3_close_uni_stream(ec);

    return NGX_ERROR;
}


ngx_int_t
ngx_http_v3_client_insert(ngx_connection_t *c, ngx_str_t *name,
    ngx_str_t *value)
{
    u_char             buf[NGX_HTTP_V3_PREFIX_INT_LEN];
    size_t             n;
    ngx_connection_t  *ec;

    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
                   "http3 client insert \"%V\":\"%V\"", name, value);

    ec = ngx_http_v3_get_server_encoder(c);
    if (ec == NULL) {
        return NGX_ERROR;
    }

    /* XXX option for huffman? */
    buf[0] = 0x40;
    n = (u_char *) ngx_http_v3_encode_prefix_int(buf, name->len, 5) - buf;

    if (ec->send(ec, buf, n) != (ssize_t) n) {
        goto failed;
    }

    if (ec->send(ec, name->data, name->len) != (ssize_t) name->len) {
        goto failed;
    }

    /* XXX option for huffman? */
    buf[0] = 0;
    n = (u_char *) ngx_http_v3_encode_prefix_int(buf, value->len, 7) - buf;

    if (ec->send(ec, buf, n) != (ssize_t) n) {
        goto failed;
    }

    if (ec->send(ec, value->data, value->len) != (ssize_t) value->len) {
        goto failed;
    }

    return NGX_OK;

failed:

    ngx_http_v3_close_uni_stream(ec);

    return NGX_ERROR;
}


ngx_int_t
ngx_http_v3_client_set_capacity(ngx_connection_t *c, ngx_uint_t capacity)
{
    u_char             buf[NGX_HTTP_V3_PREFIX_INT_LEN];
    size_t             n;
    ngx_connection_t  *ec;

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
                   "http3 client set capacity %ui", capacity);

    ec = ngx_http_v3_get_server_encoder(c);
    if (ec == NULL) {
        return NGX_ERROR;
    }

    buf[0] = 0x20;
    n = (u_char *) ngx_http_v3_encode_prefix_int(buf, capacity, 5) - buf;

    if (ec->send(ec, buf, n) != (ssize_t) n) {
        ngx_http_v3_close_uni_stream(ec);
        return NGX_ERROR;
    }

    return NGX_OK;
}


ngx_int_t
ngx_http_v3_client_duplicate(ngx_connection_t *c, ngx_uint_t index)
{
    u_char             buf[NGX_HTTP_V3_PREFIX_INT_LEN];
    size_t             n;
    ngx_connection_t  *ec;

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
                   "http3 client duplicate %ui", index);

    ec = ngx_http_v3_get_server_encoder(c);
    if (ec == NULL) {
        return NGX_ERROR;
    }

    buf[0] = 0;
    n = (u_char *) ngx_http_v3_encode_prefix_int(buf, index, 5) - buf;

    if (ec->send(ec, buf, n) != (ssize_t) n) {
        ngx_http_v3_close_uni_stream(ec);
        return NGX_ERROR;
    }

    return NGX_OK;
}


ngx_int_t
ngx_http_v3_client_ack_header(ngx_connection_t *c, ngx_uint_t stream_id)
{
    u_char             buf[NGX_HTTP_V3_PREFIX_INT_LEN];
    size_t             n;
    ngx_connection_t  *dc;

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
                   "http3 client ack header %ui", stream_id);

    dc = ngx_http_v3_get_server_decoder(c);
    if (dc == NULL) {
        return NGX_ERROR;
    }

    buf[0] = 0x80;
    n = (u_char *) ngx_http_v3_encode_prefix_int(buf, stream_id, 7) - buf;

    if (dc->send(dc, buf, n) != (ssize_t) n) {
        ngx_http_v3_close_uni_stream(dc);
        return NGX_ERROR;
    }

    return NGX_OK;
}


ngx_int_t
ngx_http_v3_client_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id)
{
    u_char             buf[NGX_HTTP_V3_PREFIX_INT_LEN];
    size_t             n;
    ngx_connection_t  *dc;

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
                   "http3 client cancel stream %ui", stream_id);

    dc = ngx_http_v3_get_server_decoder(c);
    if (dc == NULL) {
        return NGX_ERROR;
    }

    buf[0] = 0x40;
    n = (u_char *) ngx_http_v3_encode_prefix_int(buf, stream_id, 6) - buf;

    if (dc->send(dc, buf, n) != (ssize_t) n) {
        ngx_http_v3_close_uni_stream(dc);
        return NGX_ERROR;
    }

    return NGX_OK;
}


ngx_int_t
ngx_http_v3_client_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc)
{
    u_char             buf[NGX_HTTP_V3_PREFIX_INT_LEN];
    size_t             n;
    ngx_connection_t  *dc;

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
                   "http3 client increment insert count %ui", inc);

    dc = ngx_http_v3_get_server_decoder(c);
    if (dc == NULL) {
        return NGX_ERROR;
    }

    buf[0] = 0;
    n = (u_char *) ngx_http_v3_encode_prefix_int(buf, inc, 6) - buf;

    if (dc->send(dc, buf, n) != (ssize_t) n) {
        ngx_http_v3_close_uni_stream(dc);
        return NGX_ERROR;
    }

    return NGX_OK;
}