diff options
Diffstat (limited to '')
-rw-r--r-- | tests/nghttp3_conn_test.c | 3448 |
1 files changed, 3448 insertions, 0 deletions
diff --git a/tests/nghttp3_conn_test.c b/tests/nghttp3_conn_test.c new file mode 100644 index 0000000..b60220a --- /dev/null +++ b/tests/nghttp3_conn_test.c @@ -0,0 +1,3448 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp3_conn_test.h" + +#include <assert.h> + +#include <CUnit/CUnit.h> + +#include "nghttp3_conn.h" +#include "nghttp3_macro.h" +#include "nghttp3_conv.h" +#include "nghttp3_frame.h" +#include "nghttp3_vec.h" +#include "nghttp3_test_helper.h" +#include "nghttp3_http.h" + +static uint8_t nulldata[4096]; + +typedef struct { + struct { + size_t nblock; + size_t left; + size_t step; + } data; + struct { + uint64_t acc; + } ack; + struct { + size_t ncalled; + int64_t stream_id; + uint64_t app_error_code; + } stop_sending_cb; + struct { + size_t ncalled; + int64_t stream_id; + uint64_t app_error_code; + } reset_stream_cb; + struct { + size_t ncalled; + int64_t id; + } shutdown_cb; + struct { + uint64_t consumed_total; + } deferred_consume_cb; +} userdata; + +static int acked_stream_data(nghttp3_conn *conn, int64_t stream_id, + uint64_t datalen, void *user_data, + void *stream_user_data) { + userdata *ud = user_data; + + (void)conn; + (void)stream_id; + (void)stream_user_data; + + ud->ack.acc += datalen; + + return 0; +} + +static int begin_headers(nghttp3_conn *conn, int64_t stream_id, void *user_data, + void *stream_user_data) { + (void)conn; + (void)stream_id; + (void)stream_user_data; + (void)user_data; + return 0; +} + +static int recv_header(nghttp3_conn *conn, int64_t stream_id, int32_t token, + nghttp3_rcbuf *name, nghttp3_rcbuf *value, uint8_t flags, + void *user_data, void *stream_user_data) { + (void)conn; + (void)stream_id; + (void)token; + (void)name; + (void)value; + (void)flags; + (void)stream_user_data; + (void)user_data; + return 0; +} + +static int end_headers(nghttp3_conn *conn, int64_t stream_id, int fin, + void *user_data, void *stream_user_data) { + (void)conn; + (void)stream_id; + (void)fin; + (void)stream_user_data; + (void)user_data; + return 0; +} + +static nghttp3_ssize step_read_data(nghttp3_conn *conn, int64_t stream_id, + nghttp3_vec *vec, size_t veccnt, + uint32_t *pflags, void *user_data, + void *stream_user_data) { + userdata *ud = user_data; + size_t n = nghttp3_min(ud->data.left, ud->data.step); + + (void)conn; + (void)stream_id; + (void)veccnt; + (void)stream_user_data; + + ud->data.left -= n; + if (ud->data.left == 0) { + *pflags = NGHTTP3_DATA_FLAG_EOF; + + if (n == 0) { + return 0; + } + } + + vec[0].base = nulldata; + vec[0].len = n; + + return 1; +} + +static nghttp3_ssize +block_then_step_read_data(nghttp3_conn *conn, int64_t stream_id, + nghttp3_vec *vec, size_t veccnt, uint32_t *pflags, + void *user_data, void *stream_user_data) { + userdata *ud = user_data; + + if (ud->data.nblock == 0) { + return step_read_data(conn, stream_id, vec, veccnt, pflags, user_data, + stream_user_data); + } + + --ud->data.nblock; + + return NGHTTP3_ERR_WOULDBLOCK; +} + +static nghttp3_ssize +step_then_block_read_data(nghttp3_conn *conn, int64_t stream_id, + nghttp3_vec *vec, size_t veccnt, uint32_t *pflags, + void *user_data, void *stream_user_data) { + nghttp3_ssize rv; + + rv = step_read_data(conn, stream_id, vec, veccnt, pflags, user_data, + stream_user_data); + + assert(rv >= 0); + + if (*pflags & NGHTTP3_DATA_FLAG_EOF) { + *pflags &= (uint32_t)~NGHTTP3_DATA_FLAG_EOF; + + if (nghttp3_vec_len(vec, (size_t)rv) == 0) { + return NGHTTP3_ERR_WOULDBLOCK; + } + } + + return rv; +} + +#if SIZE_MAX > UINT32_MAX +static nghttp3_ssize stream_data_overflow_read_data( + nghttp3_conn *conn, int64_t stream_id, nghttp3_vec *vec, size_t veccnt, + uint32_t *pflags, void *user_data, void *stream_user_data) { + (void)conn; + (void)stream_id; + (void)veccnt; + (void)pflags; + (void)user_data; + (void)stream_user_data; + + vec[0].base = nulldata; + vec[0].len = NGHTTP3_MAX_VARINT + 1; + + return 1; +} + +static nghttp3_ssize stream_data_almost_overflow_read_data( + nghttp3_conn *conn, int64_t stream_id, nghttp3_vec *vec, size_t veccnt, + uint32_t *pflags, void *user_data, void *stream_user_data) { + (void)conn; + (void)stream_id; + (void)veccnt; + (void)pflags; + (void)user_data; + (void)stream_user_data; + + vec[0].base = nulldata; + vec[0].len = NGHTTP3_MAX_VARINT; + + return 1; +} +#endif /* SIZE_MAX > UINT32_MAX */ + +static int stop_sending(nghttp3_conn *conn, int64_t stream_id, + uint64_t app_error_code, void *user_data, + void *stream_user_data) { + userdata *ud = user_data; + + (void)conn; + (void)stream_user_data; + + ++ud->stop_sending_cb.ncalled; + ud->stop_sending_cb.stream_id = stream_id; + ud->stop_sending_cb.app_error_code = app_error_code; + + return 0; +} + +static int reset_stream(nghttp3_conn *conn, int64_t stream_id, + uint64_t app_error_code, void *user_data, + void *stream_user_data) { + userdata *ud = user_data; + + (void)conn; + (void)stream_user_data; + + ++ud->reset_stream_cb.ncalled; + ud->reset_stream_cb.stream_id = stream_id; + ud->reset_stream_cb.app_error_code = app_error_code; + + return 0; +} + +static int conn_shutdown(nghttp3_conn *conn, int64_t id, void *user_data) { + userdata *ud = user_data; + + (void)conn; + + ++ud->shutdown_cb.ncalled; + ud->shutdown_cb.id = id; + + return 0; +} + +static int deferred_consume(nghttp3_conn *conn, int64_t stream_id, + size_t consumed, void *user_data, + void *stream_user_data) { + userdata *ud = user_data; + + (void)conn; + (void)stream_user_data; + (void)stream_id; + + ud->deferred_consume_cb.consumed_total += consumed; + + return 0; +} + +void test_nghttp3_conn_read_control(void) { + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + int rv; + uint8_t rawbuf[2048]; + nghttp3_buf buf; + struct { + nghttp3_frame_settings settings; + nghttp3_settings_entry iv[15]; + } fr; + nghttp3_ssize nconsumed; + nghttp3_settings_entry *iv; + size_t i; + + memset(&callbacks, 0, sizeof(callbacks)); + nghttp3_settings_default(&settings); + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_CONTROL); + + fr.settings.hd.type = NGHTTP3_FRAME_SETTINGS; + iv = fr.settings.iv; + iv[0].id = NGHTTP3_SETTINGS_ID_MAX_FIELD_SECTION_SIZE; + iv[0].value = 65536; + iv[1].id = 1000000009; + iv[1].value = 1000000007; + iv[2].id = NGHTTP3_SETTINGS_ID_QPACK_MAX_TABLE_CAPACITY; + iv[2].value = 4096; + iv[3].id = NGHTTP3_SETTINGS_ID_QPACK_BLOCKED_STREAMS; + iv[3].value = 99; + fr.settings.niv = 4; + + nghttp3_write_frame(&buf, (nghttp3_frame *)&fr); + + rv = nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + + CU_ASSERT(0 == rv); + + nconsumed = nghttp3_conn_read_stream(conn, 2, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(nconsumed == (nghttp3_ssize)nghttp3_buf_len(&buf)); + CU_ASSERT(65536 == conn->remote.settings.max_field_section_size); + CU_ASSERT(4096 == conn->remote.settings.qpack_max_dtable_capacity); + CU_ASSERT(99 == conn->remote.settings.qpack_blocked_streams); + CU_ASSERT(4096 == conn->qenc.ctx.hard_max_dtable_capacity); + CU_ASSERT(4096 == conn->qenc.ctx.max_dtable_capacity); + CU_ASSERT(99 == conn->qenc.ctx.max_blocked_streams); + + nghttp3_conn_del(conn); + + /* Feed 1 byte at a time to verify that state machine works */ + rv = nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + + CU_ASSERT(0 == rv); + + for (i = 0; i < nghttp3_buf_len(&buf); ++i) { + nconsumed = + nghttp3_conn_read_stream(conn, 2, buf.pos + i, 1, /* fin = */ 0); + + CU_ASSERT(1 == nconsumed); + } + + CU_ASSERT(65536 == conn->remote.settings.max_field_section_size); + CU_ASSERT(4096 == conn->remote.settings.qpack_max_dtable_capacity); + CU_ASSERT(99 == conn->remote.settings.qpack_blocked_streams); + + nghttp3_conn_del(conn); + + /* Receiver should enforce its own limits for QPACK parameters. */ + nghttp3_settings_default(&settings); + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_CONTROL); + + fr.settings.hd.type = NGHTTP3_FRAME_SETTINGS; + iv = fr.settings.iv; + iv[0].id = NGHTTP3_SETTINGS_ID_QPACK_MAX_TABLE_CAPACITY; + iv[0].value = 4097; + iv[1].id = NGHTTP3_SETTINGS_ID_QPACK_BLOCKED_STREAMS; + iv[1].value = 101; + fr.settings.niv = 2; + + nghttp3_write_frame(&buf, (nghttp3_frame *)&fr); + + rv = nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + + CU_ASSERT(0 == rv); + + nconsumed = nghttp3_conn_read_stream(conn, 2, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(nconsumed == (nghttp3_ssize)nghttp3_buf_len(&buf)); + CU_ASSERT(4097 == conn->remote.settings.qpack_max_dtable_capacity); + CU_ASSERT(101 == conn->remote.settings.qpack_blocked_streams); + CU_ASSERT(4096 == conn->qenc.ctx.hard_max_dtable_capacity); + CU_ASSERT(4096 == conn->qenc.ctx.max_dtable_capacity); + CU_ASSERT(100 == conn->qenc.ctx.max_blocked_streams); + + nghttp3_conn_del(conn); + + /* Receiving multiple nonzero SETTINGS_QPACK_MAX_TABLE_CAPACITY is + treated as error. */ + nghttp3_settings_default(&settings); + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_CONTROL); + + fr.settings.hd.type = NGHTTP3_FRAME_SETTINGS; + iv = fr.settings.iv; + iv[0].id = NGHTTP3_SETTINGS_ID_QPACK_MAX_TABLE_CAPACITY; + iv[0].value = 4097; + iv[1].id = NGHTTP3_SETTINGS_ID_QPACK_MAX_TABLE_CAPACITY; + iv[1].value = 4097; + fr.settings.niv = 2; + + nghttp3_write_frame(&buf, (nghttp3_frame *)&fr); + + rv = nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + + CU_ASSERT(0 == rv); + + nconsumed = nghttp3_conn_read_stream(conn, 2, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(NGHTTP3_ERR_H3_SETTINGS_ERROR == nconsumed); + + nghttp3_conn_del(conn); + + /* Receiving multiple nonzero SETTINGS_QPACK_BLOCKED_STREAMS is + treated as error. */ + nghttp3_settings_default(&settings); + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_CONTROL); + + fr.settings.hd.type = NGHTTP3_FRAME_SETTINGS; + iv = fr.settings.iv; + iv[0].id = NGHTTP3_SETTINGS_ID_QPACK_BLOCKED_STREAMS; + iv[0].value = 1; + iv[1].id = NGHTTP3_SETTINGS_ID_QPACK_BLOCKED_STREAMS; + iv[1].value = 1; + fr.settings.niv = 2; + + nghttp3_write_frame(&buf, (nghttp3_frame *)&fr); + + rv = nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + + CU_ASSERT(0 == rv); + + nconsumed = nghttp3_conn_read_stream(conn, 2, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(NGHTTP3_ERR_H3_SETTINGS_ERROR == nconsumed); + + nghttp3_conn_del(conn); + + /* Receive ENABLE_CONNECT_PROTOCOL = 1 */ + nghttp3_settings_default(&settings); + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_CONTROL); + + fr.settings.hd.type = NGHTTP3_FRAME_SETTINGS; + iv = fr.settings.iv; + iv[0].id = NGHTTP3_SETTINGS_ID_ENABLE_CONNECT_PROTOCOL; + iv[0].value = 1; + fr.settings.niv = 1; + + nghttp3_write_frame(&buf, (nghttp3_frame *)&fr); + + rv = nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + + CU_ASSERT(0 == rv); + + nconsumed = nghttp3_conn_read_stream(conn, 2, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == nconsumed); + CU_ASSERT(1 == conn->remote.settings.enable_connect_protocol); + + nghttp3_conn_del(conn); + + /* Receiving ENABLE_CONNECT_PROTOCOL = 0 after seeing + ENABLE_CONNECT_PROTOCOL = 1 */ + nghttp3_settings_default(&settings); + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_CONTROL); + + fr.settings.hd.type = NGHTTP3_FRAME_SETTINGS; + iv = fr.settings.iv; + iv[0].id = NGHTTP3_SETTINGS_ID_ENABLE_CONNECT_PROTOCOL; + iv[0].value = 1; + iv[1].id = NGHTTP3_SETTINGS_ID_ENABLE_CONNECT_PROTOCOL; + iv[1].value = 0; + fr.settings.niv = 2; + + nghttp3_write_frame(&buf, (nghttp3_frame *)&fr); + + rv = nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + + CU_ASSERT(0 == rv); + + nconsumed = nghttp3_conn_read_stream(conn, 2, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(NGHTTP3_ERR_H3_SETTINGS_ERROR == nconsumed); + + nghttp3_conn_del(conn); +} + +void test_nghttp3_conn_write_control(void) { + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + nghttp3_vec vec[256]; + nghttp3_ssize sveccnt; + int rv; + int64_t stream_id; + int fin; + + memset(&callbacks, 0, sizeof(callbacks)); + nghttp3_settings_default(&settings); + + rv = nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + + CU_ASSERT(0 == rv); + + rv = nghttp3_conn_bind_control_stream(conn, 3); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL != conn->tx.ctrl); + CU_ASSERT(NGHTTP3_STREAM_TYPE_CONTROL == conn->tx.ctrl->type); + + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(3 == stream_id); + CU_ASSERT(1 == sveccnt); + CU_ASSERT(vec[0].len > 1); + CU_ASSERT(NGHTTP3_STREAM_TYPE_CONTROL == vec[0].base[0]); + + nghttp3_conn_del(conn); +} + +void test_nghttp3_conn_submit_request(void) { + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + nghttp3_vec vec[256]; + nghttp3_ssize sveccnt; + int rv; + int64_t stream_id; + const nghttp3_nv nva[] = { + MAKE_NV(":path", "/"), + MAKE_NV(":authority", "example.com"), + MAKE_NV(":scheme", "https"), + MAKE_NV(":method", "GET"), + }; + uint64_t len; + size_t i; + nghttp3_stream *stream; + userdata ud; + nghttp3_data_reader dr; + int fin; + + memset(&callbacks, 0, sizeof(callbacks)); + memset(&ud, 0, sizeof(ud)); + nghttp3_settings_default(&settings); + + callbacks.acked_stream_data = acked_stream_data; + + ud.data.left = 2000; + ud.data.step = 1200; + + rv = nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, &ud); + + CU_ASSERT(0 == rv); + + rv = nghttp3_conn_bind_qpack_streams(conn, 6, 10); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL != conn->tx.qenc); + CU_ASSERT(NGHTTP3_STREAM_TYPE_QPACK_ENCODER == conn->tx.qenc->type); + CU_ASSERT(NULL != conn->tx.qdec); + CU_ASSERT(NGHTTP3_STREAM_TYPE_QPACK_DECODER == conn->tx.qdec->type); + + dr.read_data = step_read_data; + rv = nghttp3_conn_submit_request(conn, 0, nva, nghttp3_arraylen(nva), &dr, + NULL); + + CU_ASSERT(0 == rv); + + /* This will write QPACK decoder stream; just stream type */ + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(10 == stream_id); + CU_ASSERT(1 == sveccnt); + CU_ASSERT(1 == nghttp3_ringbuf_len(&conn->tx.qdec->outq)); + CU_ASSERT(0 == conn->tx.qdec->outq_idx); + CU_ASSERT(0 == conn->tx.qdec->outq_offset); + + /* Calling twice will return the same result */ + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(10 == stream_id); + CU_ASSERT(1 == sveccnt); + + rv = nghttp3_conn_add_write_offset(conn, 10, vec[0].len); + + CU_ASSERT(0 == rv); + CU_ASSERT(1 == nghttp3_ringbuf_len(&conn->tx.qdec->outq)); + CU_ASSERT(1 == conn->tx.qdec->outq_idx); + CU_ASSERT(0 == conn->tx.qdec->outq_offset); + + rv = nghttp3_conn_add_ack_offset(conn, 10, vec[0].len); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == nghttp3_ringbuf_len(&conn->tx.qdec->outq)); + CU_ASSERT(0 == conn->tx.qdec->outq_idx); + CU_ASSERT(0 == conn->tx.qdec->outq_offset); + CU_ASSERT(0 == conn->tx.qdec->ack_offset); + + /* This will write QPACK encoder stream; just stream type */ + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(6 == stream_id); + CU_ASSERT(1 == sveccnt); + + rv = nghttp3_conn_add_write_offset(conn, 6, vec[0].len); + + CU_ASSERT(0 == rv); + + rv = nghttp3_conn_add_ack_offset(conn, 6, vec[0].len); + + CU_ASSERT(0 == rv); + + /* This will write request stream */ + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(0 == stream_id); + CU_ASSERT(2 == sveccnt); + + len = nghttp3_vec_len(vec, (size_t)sveccnt); + for (i = 0; i < len; ++i) { + rv = nghttp3_conn_add_write_offset(conn, 0, 1); + + CU_ASSERT(0 == rv); + + rv = nghttp3_conn_add_ack_offset(conn, 0, 1); + + CU_ASSERT(0 == rv); + } + + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(0 == stream_id); + CU_ASSERT(2 == sveccnt); + + len = nghttp3_vec_len(vec, (size_t)sveccnt); + + for (i = 0; i < len; ++i) { + rv = nghttp3_conn_add_write_offset(conn, 0, 1); + + CU_ASSERT(0 == rv); + + rv = nghttp3_conn_add_ack_offset(conn, 0, 1); + + CU_ASSERT(0 == rv); + } + + stream = nghttp3_conn_find_stream(conn, 0); + + CU_ASSERT(0 == nghttp3_ringbuf_len(&stream->outq)); + CU_ASSERT(0 == nghttp3_ringbuf_len(&stream->chunks)); + CU_ASSERT(0 == stream->outq_idx); + CU_ASSERT(0 == stream->outq_offset); + CU_ASSERT(0 == stream->ack_offset); + CU_ASSERT(2000 == ud.ack.acc); + + nghttp3_conn_del(conn); +} + +void test_nghttp3_conn_http_request(void) { + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_conn *cl, *sv; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + nghttp3_vec vec[256]; + nghttp3_ssize sveccnt; + nghttp3_ssize sconsumed; + int rv; + int64_t stream_id; + const nghttp3_nv reqnva[] = { + MAKE_NV(":path", "/"), + MAKE_NV(":authority", "example.com"), + MAKE_NV(":scheme", "https"), + MAKE_NV(":method", "GET"), + }; + const nghttp3_nv respnva[] = { + MAKE_NV(":status", "200"), + MAKE_NV("server", "nghttp3"), + MAKE_NV("content-length", "1999"), + }; + nghttp3_data_reader dr; + int fin; + userdata clud, svud; + size_t i; + size_t nconsumed; + size_t nread; + + memset(&callbacks, 0, sizeof(callbacks)); + nghttp3_settings_default(&settings); + memset(&clud, 0, sizeof(clud)); + + callbacks.begin_headers = begin_headers; + callbacks.recv_header = recv_header; + callbacks.end_headers = end_headers; + + settings.qpack_max_dtable_capacity = 4096; + settings.qpack_blocked_streams = 100; + + clud.data.left = 2000; + clud.data.step = 1200; + + svud.data.left = 1999; + svud.data.step = 1000; + + nghttp3_conn_client_new(&cl, &callbacks, &settings, mem, &clud); + nghttp3_conn_server_new(&sv, &callbacks, &settings, mem, &svud); + + nghttp3_conn_bind_control_stream(cl, 2); + nghttp3_conn_bind_control_stream(sv, 3); + + nghttp3_conn_bind_qpack_streams(cl, 6, 10); + nghttp3_conn_bind_qpack_streams(sv, 7, 11); + + dr.read_data = step_read_data; + rv = nghttp3_conn_submit_request(cl, 0, reqnva, nghttp3_arraylen(reqnva), &dr, + NULL); + + CU_ASSERT(0 == rv); + + nread = 0; + nconsumed = 0; + + for (;;) { + sveccnt = nghttp3_conn_writev_stream(cl, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(sveccnt >= 0); + + if (sveccnt <= 0) { + break; + } + + rv = nghttp3_conn_add_write_offset( + cl, stream_id, (size_t)nghttp3_vec_len(vec, (size_t)sveccnt)); + + CU_ASSERT(0 == rv); + + for (i = 0; i < (size_t)sveccnt; ++i) { + sconsumed = + nghttp3_conn_read_stream(sv, stream_id, vec[i].base, vec[i].len, + fin && i == (size_t)sveccnt - 1); + CU_ASSERT(sconsumed >= 0); + + nread += vec[i].len; + nconsumed += (size_t)sconsumed; + } + + rv = nghttp3_conn_add_ack_offset(cl, stream_id, + nghttp3_vec_len(vec, (size_t)sveccnt)); + + CU_ASSERT(0 == rv); + } + + CU_ASSERT(nread == nconsumed + 2000); + + rv = nghttp3_conn_submit_response(sv, 0, respnva, nghttp3_arraylen(respnva), + &dr); + + CU_ASSERT(0 == rv); + + nread = 0; + nconsumed = 0; + + for (;;) { + sveccnt = nghttp3_conn_writev_stream(sv, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(sveccnt >= 0); + + if (sveccnt <= 0) { + break; + } + + rv = nghttp3_conn_add_write_offset( + sv, stream_id, (size_t)nghttp3_vec_len(vec, (size_t)sveccnt)); + + CU_ASSERT(0 == rv); + + for (i = 0; i < (size_t)sveccnt; ++i) { + sconsumed = + nghttp3_conn_read_stream(cl, stream_id, vec[i].base, vec[i].len, + fin && i == (size_t)sveccnt - 1); + CU_ASSERT(sconsumed >= 0); + + nread += vec[i].len; + nconsumed += (size_t)sconsumed; + } + + rv = nghttp3_conn_add_ack_offset(sv, stream_id, + nghttp3_vec_len(vec, (size_t)sveccnt)); + + CU_ASSERT(0 == rv); + } + + CU_ASSERT(nread == nconsumed + 1999); + + nghttp3_conn_del(sv); + nghttp3_conn_del(cl); +} + +static void check_http_header(const nghttp3_nv *nva, size_t nvlen, int request, + int want_lib_error) { + uint8_t rawbuf[4096]; + nghttp3_buf buf; + nghttp3_frame_headers fr; + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_ssize sconsumed; + nghttp3_qpack_encoder qenc; + nghttp3_stream *stream; + + memset(&callbacks, 0, sizeof(callbacks)); + nghttp3_settings_default(&settings); + settings.enable_connect_protocol = 1; + + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)nva; + fr.nvlen = nvlen; + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + if (request) { + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + } else { + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_create_stream(conn, &stream, 0); + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + } + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + if (want_lib_error) { + if (want_lib_error == NGHTTP3_ERR_MALFORMED_HTTP_HEADER) { + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + + stream = nghttp3_conn_find_stream(conn, 0); + + CU_ASSERT(stream->flags & NGHTTP3_STREAM_FLAG_HTTP_ERROR); + } else { + CU_ASSERT(want_lib_error == sconsumed); + } + } else { + CU_ASSERT(sconsumed > 0); + } + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); +} + +static void check_http_resp_header(const nghttp3_nv *nva, size_t nvlen, + int want_lib_error) { + check_http_header(nva, nvlen, /* request = */ 0, want_lib_error); +} + +static void check_http_req_header(const nghttp3_nv *nva, size_t nvlen, + int want_lib_error) { + check_http_header(nva, nvlen, /* request = */ 1, want_lib_error); +} + +void test_nghttp3_conn_http_resp_header(void) { + /* test case for response */ + /* response header lacks :status */ + const nghttp3_nv nostatus_resnv[] = { + MAKE_NV("server", "foo"), + }; + /* response header has 2 :status */ + const nghttp3_nv dupstatus_resnv[] = { + MAKE_NV(":status", "200"), + MAKE_NV(":status", "200"), + }; + /* response header has bad pseudo header :scheme */ + const nghttp3_nv badpseudo_resnv[] = { + MAKE_NV(":status", "200"), + MAKE_NV(":scheme", "https"), + }; + /* response header has :status after regular header field */ + const nghttp3_nv latepseudo_resnv[] = { + MAKE_NV("server", "foo"), + MAKE_NV(":status", "200"), + }; + /* response header has bad status code */ + const nghttp3_nv badstatus_resnv[] = { + MAKE_NV(":status", "2000"), + }; + /* response header has bad content-length */ + const nghttp3_nv badcl_resnv[] = { + MAKE_NV(":status", "200"), + MAKE_NV("content-length", "-1"), + }; + /* response header has multiple content-length */ + const nghttp3_nv dupcl_resnv[] = { + MAKE_NV(":status", "200"), + MAKE_NV("content-length", "0"), + MAKE_NV("content-length", "0"), + }; + /* response header has disallowed header field */ + const nghttp3_nv badhd_resnv[] = { + MAKE_NV(":status", "200"), + MAKE_NV("connection", "close"), + }; + /* response header has content-length with 100 status code */ + const nghttp3_nv cl1xx_resnv[] = { + MAKE_NV(":status", "100"), + MAKE_NV("content-length", "0"), + }; + /* response header has 0 content-length with 204 status code */ + const nghttp3_nv cl204_resnv[] = { + MAKE_NV(":status", "204"), + MAKE_NV("content-length", "0"), + }; + /* response header has nonzero content-length with 204 status + code */ + const nghttp3_nv clnonzero204_resnv[] = { + MAKE_NV(":status", "204"), + MAKE_NV("content-length", "100"), + }; + /* status code 101 should not be used in HTTP/3 because it is used + for HTTP Upgrade which HTTP/3 removes. */ + const nghttp3_nv status101_resnv[] = { + MAKE_NV(":status", "101"), + }; + + check_http_resp_header(nostatus_resnv, nghttp3_arraylen(nostatus_resnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_resp_header(dupstatus_resnv, nghttp3_arraylen(dupstatus_resnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_resp_header(badpseudo_resnv, nghttp3_arraylen(badpseudo_resnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_resp_header(latepseudo_resnv, nghttp3_arraylen(latepseudo_resnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_resp_header(badstatus_resnv, nghttp3_arraylen(badstatus_resnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_resp_header(badcl_resnv, nghttp3_arraylen(badcl_resnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_resp_header(dupcl_resnv, nghttp3_arraylen(dupcl_resnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_resp_header(badhd_resnv, nghttp3_arraylen(badhd_resnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + /* Ignore content-length in 1xx response. */ + check_http_resp_header(cl1xx_resnv, nghttp3_arraylen(cl1xx_resnv), 0); + /* This is allowed to work with widely used services. */ + check_http_resp_header(cl204_resnv, nghttp3_arraylen(cl204_resnv), 0); + check_http_resp_header(clnonzero204_resnv, + nghttp3_arraylen(clnonzero204_resnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_resp_header(status101_resnv, nghttp3_arraylen(status101_resnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); +} + +void test_nghttp3_conn_http_req_header(void) { + /* test case for request */ + /* request header has no :path */ + const nghttp3_nv nopath_reqnv[] = { + MAKE_NV(":scheme", "https"), + MAKE_NV(":method", "GET"), + MAKE_NV(":authority", "localhost"), + }; + /* request header has CONNECT method, but followed by :path */ + const nghttp3_nv earlyconnect_reqnv[] = { + MAKE_NV(":method", "CONNECT"), + MAKE_NV(":scheme", "https"), + MAKE_NV(":path", "/"), + MAKE_NV(":authority", "localhost"), + }; + /* request header has CONNECT method following :path */ + const nghttp3_nv lateconnect_reqnv[] = { + MAKE_NV(":scheme", "https"), + MAKE_NV(":path", "/"), + MAKE_NV(":method", "CONNECT"), + MAKE_NV(":authority", "localhost"), + }; + /* request header has multiple :path */ + const nghttp3_nv duppath_reqnv[] = { + MAKE_NV(":scheme", "https"), + MAKE_NV(":method", "GET"), + MAKE_NV(":authority", "localhost"), + MAKE_NV(":path", "/"), + MAKE_NV(":path", "/"), + }; + /* request header has bad content-length */ + const nghttp3_nv badcl_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":method", "POST"), + MAKE_NV(":authority", "localhost"), MAKE_NV(":path", "/"), + MAKE_NV("content-length", "-1"), + }; + /* request header has multiple content-length */ + const nghttp3_nv dupcl_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":method", "POST"), + MAKE_NV(":authority", "localhost"), MAKE_NV(":path", "/"), + MAKE_NV("content-length", "0"), MAKE_NV("content-length", "0"), + }; + /* request header has disallowed header field */ + const nghttp3_nv badhd_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":method", "GET"), + MAKE_NV(":authority", "localhost"), MAKE_NV(":path", "/"), + MAKE_NV("connection", "close"), + }; + /* request header has :authority header field containing illegal + characters */ + const nghttp3_nv badauthority_reqnv[] = { + MAKE_NV(":scheme", "https"), + MAKE_NV(":method", "GET"), + MAKE_NV(":authority", "\x0d\x0alocalhost"), + MAKE_NV(":path", "/"), + }; + /* request header has regular header field containing illegal + character before all mandatory header fields are seen. */ + const nghttp3_nv badhdbtw_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":method", "GET"), + MAKE_NV("foo", "\x0d\x0a"), MAKE_NV(":authority", "localhost"), + MAKE_NV(":path", "/"), + }; + /* request header has "*" in :path header field while method is GET. + :path is received before :method */ + const nghttp3_nv asteriskget1_reqnv[] = { + MAKE_NV(":path", "*"), + MAKE_NV(":scheme", "https"), + MAKE_NV(":authority", "localhost"), + MAKE_NV(":method", "GET"), + }; + /* request header has "*" in :path header field while method is GET. + :method is received before :path */ + const nghttp3_nv asteriskget2_reqnv[] = { + MAKE_NV(":scheme", "https"), + MAKE_NV(":authority", "localhost"), + MAKE_NV(":method", "GET"), + MAKE_NV(":path", "*"), + }; + /* OPTIONS method can include "*" in :path header field. :path is + received before :method. */ + const nghttp3_nv asteriskoptions1_reqnv[] = { + MAKE_NV(":path", "*"), + MAKE_NV(":scheme", "https"), + MAKE_NV(":authority", "localhost"), + MAKE_NV(":method", "OPTIONS"), + }; + /* OPTIONS method can include "*" in :path header field. :method is + received before :path. */ + const nghttp3_nv asteriskoptions2_reqnv[] = { + MAKE_NV(":scheme", "https"), + MAKE_NV(":authority", "localhost"), + MAKE_NV(":method", "OPTIONS"), + MAKE_NV(":path", "*"), + }; + /* header name contains invalid character */ + const nghttp3_nv invalidname_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":method", "GET"), + MAKE_NV(":authority", "localhost"), MAKE_NV(":path", "/"), + MAKE_NV("\x0foo", "zzz"), + }; + /* header value contains invalid character */ + const nghttp3_nv invalidvalue_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":method", "GET"), + MAKE_NV(":authority", "localhost"), MAKE_NV(":path", "/"), + MAKE_NV("foo", "\x0zzz"), + }; + /* :protocol is not allowed unless it is enabled by the local + endpoint. */ + /* :protocol is allowed if SETTINGS_CONNECT_PROTOCOL is enabled by + the local endpoint. */ + const nghttp3_nv connectproto_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":path", "/"), + MAKE_NV(":method", "CONNECT"), MAKE_NV(":authority", "localhost"), + MAKE_NV(":protocol", "websocket"), + }; + /* :protocol is only allowed with CONNECT method. */ + const nghttp3_nv connectprotoget_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":path", "/"), + MAKE_NV(":method", "GET"), MAKE_NV(":authority", "localhost"), + MAKE_NV(":protocol", "websocket"), + }; + /* CONNECT method with :protocol requires :path. */ + const nghttp3_nv connectprotonopath_reqnv[] = { + MAKE_NV(":scheme", "https"), + MAKE_NV(":method", "CONNECT"), + MAKE_NV(":authority", "localhost"), + MAKE_NV(":protocol", "websocket"), + }; + /* CONNECT method with :protocol requires :authority. */ + const nghttp3_nv connectprotonoauth_reqnv[] = { + MAKE_NV(":scheme", "http"), MAKE_NV(":path", "/"), + MAKE_NV(":method", "CONNECT"), MAKE_NV("host", "localhost"), + MAKE_NV(":protocol", "websocket"), + }; + /* regular CONNECT method should succeed with + SETTINGS_CONNECT_PROTOCOL */ + const nghttp3_nv regularconnect_reqnv[] = { + MAKE_NV(":method", "CONNECT"), + MAKE_NV(":authority", "localhost"), + }; + + /* request header has no :path */ + check_http_req_header(nopath_reqnv, nghttp3_arraylen(nopath_reqnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_req_header(earlyconnect_reqnv, + nghttp3_arraylen(earlyconnect_reqnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_req_header(lateconnect_reqnv, nghttp3_arraylen(lateconnect_reqnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_req_header(duppath_reqnv, nghttp3_arraylen(duppath_reqnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_req_header(badcl_reqnv, nghttp3_arraylen(badcl_reqnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_req_header(dupcl_reqnv, nghttp3_arraylen(dupcl_reqnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_req_header(badhd_reqnv, nghttp3_arraylen(badhd_reqnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_req_header(badauthority_reqnv, + nghttp3_arraylen(badauthority_reqnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_req_header(badhdbtw_reqnv, nghttp3_arraylen(badhdbtw_reqnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_req_header(asteriskget1_reqnv, + nghttp3_arraylen(asteriskget1_reqnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_req_header(asteriskget2_reqnv, + nghttp3_arraylen(asteriskget2_reqnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_req_header(asteriskoptions1_reqnv, + nghttp3_arraylen(asteriskoptions1_reqnv), 0); + check_http_req_header(asteriskoptions2_reqnv, + nghttp3_arraylen(asteriskoptions2_reqnv), 0); + check_http_req_header(invalidname_reqnv, nghttp3_arraylen(invalidname_reqnv), + 0); + check_http_req_header(invalidvalue_reqnv, + nghttp3_arraylen(invalidvalue_reqnv), 0); + check_http_req_header(connectproto_reqnv, + nghttp3_arraylen(connectproto_reqnv), 0); + check_http_req_header(connectprotoget_reqnv, + nghttp3_arraylen(connectprotoget_reqnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_req_header(connectprotonopath_reqnv, + nghttp3_arraylen(connectprotonopath_reqnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_req_header(connectprotonoauth_reqnv, + nghttp3_arraylen(connectprotonoauth_reqnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_req_header(regularconnect_reqnv, + nghttp3_arraylen(regularconnect_reqnv), 0); +} + +void test_nghttp3_conn_http_content_length(void) { + uint8_t rawbuf[4096]; + nghttp3_buf buf; + nghttp3_frame_headers fr; + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_ssize sconsumed; + nghttp3_qpack_encoder qenc; + nghttp3_stream *stream; + const nghttp3_nv reqnv[] = { + MAKE_NV(":path", "/"), MAKE_NV(":method", "PUT"), + MAKE_NV(":scheme", "https"), MAKE_NV("te", "trailers"), + MAKE_NV("host", "localhost"), MAKE_NV("content-length", "9000000000"), + }; + const nghttp3_nv resnv[] = { + MAKE_NV(":status", "200"), + MAKE_NV("te", "trailers"), + MAKE_NV("content-length", "9000000000"), + }; + + memset(&callbacks, 0, sizeof(callbacks)); + nghttp3_settings_default(&settings); + + /* client */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)resnv; + fr.nvlen = nghttp3_arraylen(resnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_create_stream(conn, &stream, 0); + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + CU_ASSERT(9000000000LL == stream->rx.http.content_length); + CU_ASSERT(200 == stream->rx.http.status_code); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* server */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)reqnv; + fr.nvlen = nghttp3_arraylen(reqnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + + stream = nghttp3_conn_find_stream(conn, 0); + + CU_ASSERT(9000000000LL == stream->rx.http.content_length); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); +} + +void test_nghttp3_conn_http_content_length_mismatch(void) { + uint8_t rawbuf[4096]; + nghttp3_buf buf; + nghttp3_frame_headers fr; + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_ssize sconsumed; + nghttp3_qpack_encoder qenc; + const nghttp3_nv reqnv[] = { + MAKE_NV(":path", "/"), + MAKE_NV(":method", "PUT"), + MAKE_NV(":authority", "localhost"), + MAKE_NV(":scheme", "https"), + MAKE_NV("content-length", "20"), + }; + const nghttp3_nv resnv[] = { + MAKE_NV(":status", "200"), + MAKE_NV("content-length", "20"), + }; + int rv; + nghttp3_stream *stream; + + memset(&callbacks, 0, sizeof(callbacks)); + nghttp3_settings_default(&settings); + + /* content-length is 20, but no DATA is present and see fin */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)reqnv; + fr.nvlen = nghttp3_arraylen(reqnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 1); + + CU_ASSERT(NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING == sconsumed); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* content-length is 20, but no DATA is present and stream is + reset */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)reqnv; + fr.nvlen = nghttp3_arraylen(reqnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + + rv = nghttp3_conn_shutdown_stream_read(conn, 0); + + CU_ASSERT(0 == rv); + + rv = nghttp3_conn_close_stream(conn, 0, NGHTTP3_H3_NO_ERROR); + + CU_ASSERT(0 == rv); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* content-length is 20, but server receives 21 bytes DATA. */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)reqnv; + fr.nvlen = nghttp3_arraylen(reqnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + nghttp3_write_frame_data(&buf, 21); + + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING == sconsumed); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* Check client side as well */ + + /* content-length is 20, but no DATA is present and see fin */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)resnv; + fr.nvlen = nghttp3_arraylen(resnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_create_stream(conn, &stream, 0); + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 1); + + CU_ASSERT(NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING == sconsumed); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* content-length is 20, but no DATA is present and stream is + reset */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)resnv; + fr.nvlen = nghttp3_arraylen(resnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_create_stream(conn, &stream, 0); + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + + rv = nghttp3_conn_shutdown_stream_read(conn, 0); + + CU_ASSERT(0 == rv); + + rv = nghttp3_conn_close_stream(conn, 0, NGHTTP3_H3_NO_ERROR); + + CU_ASSERT(0 == rv); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* content-length is 20, but server receives 21 bytes DATA. */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)resnv; + fr.nvlen = nghttp3_arraylen(resnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + nghttp3_write_frame_data(&buf, 21); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_create_stream(conn, &stream, 0); + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING == sconsumed); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); +} + +void test_nghttp3_conn_http_non_final_response(void) { + uint8_t rawbuf[4096]; + nghttp3_buf buf; + nghttp3_frame_headers fr; + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_ssize sconsumed; + nghttp3_qpack_encoder qenc; + const nghttp3_nv infonv[] = { + MAKE_NV(":status", "103"), + }; + const nghttp3_nv resnv[] = { + MAKE_NV(":status", "204"), + }; + const nghttp3_nv trnv[] = { + MAKE_NV("my-status", "ok"), + }; + nghttp3_stream *stream; + + memset(&callbacks, 0, sizeof(callbacks)); + nghttp3_settings_default(&settings); + + /* non-final followed by DATA is illegal. */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)infonv; + fr.nvlen = nghttp3_arraylen(infonv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + nghttp3_write_frame_data(&buf, 0); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_create_stream(conn, &stream, 0); + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(NGHTTP3_ERR_H3_FRAME_UNEXPECTED == sconsumed); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* 2 non-finals followed by final headers */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)infonv; + fr.nvlen = nghttp3_arraylen(infonv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)resnv; + fr.nvlen = nghttp3_arraylen(resnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_create_stream(conn, &stream, 0); + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* non-finals followed by trailers; this trailer is treated as + another non-final or final header fields. Since it does not + include mandatory header field, it is treated as error. */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)infonv; + fr.nvlen = nghttp3_arraylen(infonv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)trnv; + fr.nvlen = nghttp3_arraylen(trnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_create_stream(conn, &stream, 0); + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + + stream = nghttp3_conn_find_stream(conn, 0); + + CU_ASSERT(stream->flags & NGHTTP3_STREAM_FLAG_HTTP_ERROR); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); +} + +void test_nghttp3_conn_http_trailers(void) { + uint8_t rawbuf[4096]; + nghttp3_buf buf; + nghttp3_frame_headers fr; + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_ssize sconsumed; + nghttp3_qpack_encoder qenc; + const nghttp3_nv reqnv[] = { + MAKE_NV(":path", "/"), + MAKE_NV(":method", "PUT"), + MAKE_NV(":authority", "localhost"), + MAKE_NV(":scheme", "https"), + }; + const nghttp3_nv connect_reqnv[] = { + MAKE_NV(":method", "CONNECT"), + MAKE_NV(":authority", "localhost"), + }; + const nghttp3_nv resnv[] = { + MAKE_NV(":status", "200"), + }; + const nghttp3_nv trnv[] = { + MAKE_NV("foo", "bar"), + }; + nghttp3_stream *stream; + + memset(&callbacks, 0, sizeof(callbacks)); + nghttp3_settings_default(&settings); + + /* final response followed by trailers */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)resnv; + fr.nvlen = nghttp3_arraylen(resnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)trnv; + fr.nvlen = nghttp3_arraylen(trnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_create_stream(conn, &stream, 0); + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* trailers contain :status */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)resnv; + fr.nvlen = nghttp3_arraylen(resnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)resnv; + fr.nvlen = nghttp3_arraylen(resnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_create_stream(conn, &stream, 0); + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + + stream = nghttp3_conn_find_stream(conn, 0); + + CU_ASSERT(stream->flags & NGHTTP3_STREAM_FLAG_HTTP_ERROR); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* Receiving 2 trailers HEADERS is invalid*/ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)resnv; + fr.nvlen = nghttp3_arraylen(resnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)trnv; + fr.nvlen = nghttp3_arraylen(trnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_create_stream(conn, &stream, 0); + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING == sconsumed); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* We don't expect response trailers after HEADERS with CONNECT + request */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)resnv; + fr.nvlen = nghttp3_arraylen(resnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)trnv; + fr.nvlen = nghttp3_arraylen(trnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_create_stream(conn, &stream, 0); + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + nghttp3_http_record_request_method(stream, connect_reqnv, + nghttp3_arraylen(connect_reqnv)); + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(NGHTTP3_ERR_H3_FRAME_UNEXPECTED == sconsumed); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* We don't expect response trailers after DATA with CONNECT + request */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)resnv; + fr.nvlen = nghttp3_arraylen(resnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + nghttp3_write_frame_data(&buf, 99); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)trnv; + fr.nvlen = nghttp3_arraylen(trnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_create_stream(conn, &stream, 0); + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + nghttp3_http_record_request_method(stream, connect_reqnv, + nghttp3_arraylen(connect_reqnv)); + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(NGHTTP3_ERR_H3_FRAME_UNEXPECTED == sconsumed); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* request followed by trailers */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)reqnv; + fr.nvlen = nghttp3_arraylen(reqnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)trnv; + fr.nvlen = nghttp3_arraylen(trnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* request followed by trailers which contains pseudo headers */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)reqnv; + fr.nvlen = nghttp3_arraylen(reqnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + + stream = nghttp3_conn_find_stream(conn, 0); + + CU_ASSERT(stream->flags & NGHTTP3_STREAM_FLAG_HTTP_ERROR); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* request followed by 2 trailers */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)reqnv; + fr.nvlen = nghttp3_arraylen(reqnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)trnv; + fr.nvlen = nghttp3_arraylen(trnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(NGHTTP3_ERR_H3_FRAME_UNEXPECTED == sconsumed); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* We don't expect trailers after HEADERS with CONNECT request */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)connect_reqnv; + fr.nvlen = nghttp3_arraylen(connect_reqnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)trnv; + fr.nvlen = nghttp3_arraylen(trnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(NGHTTP3_ERR_H3_FRAME_UNEXPECTED == sconsumed); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* We don't expect trailers after DATA with CONNECT request */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)connect_reqnv; + fr.nvlen = nghttp3_arraylen(connect_reqnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + nghttp3_write_frame_data(&buf, 11); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)trnv; + fr.nvlen = nghttp3_arraylen(trnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(NGHTTP3_ERR_H3_FRAME_UNEXPECTED == sconsumed); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); +} + +void test_nghttp3_conn_http_ignore_content_length(void) { + uint8_t rawbuf[4096]; + nghttp3_buf buf; + nghttp3_frame_headers fr; + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_ssize sconsumed; + nghttp3_qpack_encoder qenc; + const nghttp3_nv reqnv[] = { + MAKE_NV(":authority", "localhost"), + MAKE_NV(":method", "CONNECT"), + MAKE_NV("content-length", "999999"), + }; + const nghttp3_nv resnv[] = { + MAKE_NV(":status", "304"), + MAKE_NV("content-length", "20"), + }; + const nghttp3_nv cl_resnv[] = { + MAKE_NV(":status", "200"), + MAKE_NV("content-length", "0"), + }; + int rv; + nghttp3_stream *stream; + + memset(&callbacks, 0, sizeof(callbacks)); + nghttp3_settings_default(&settings); + + /* If status code is 304, content-length must be ignored. */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)resnv; + fr.nvlen = nghttp3_arraylen(resnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_create_stream(conn, &stream, 0); + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + CU_ASSERT(0 == stream->rx.http.content_length); + + rv = nghttp3_conn_close_stream(conn, 0, NGHTTP3_H3_NO_ERROR); + + CU_ASSERT(0 == rv); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* If method is CONNECT, content-length must be ignored. */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)reqnv; + fr.nvlen = nghttp3_arraylen(reqnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + + stream = nghttp3_conn_find_stream(conn, 0); + + CU_ASSERT(-1 == stream->rx.http.content_length); + + rv = nghttp3_conn_close_stream(conn, 0, NGHTTP3_H3_NO_ERROR); + + CU_ASSERT(0 == rv); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* Content-Length in 200 response to CONNECT is ignored */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)cl_resnv; + fr.nvlen = nghttp3_arraylen(cl_resnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_create_stream(conn, &stream, 0); + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + stream->rx.http.flags |= NGHTTP3_HTTP_FLAG_METH_CONNECT; + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + CU_ASSERT(-1 == stream->rx.http.content_length); + + rv = nghttp3_conn_close_stream(conn, 0, NGHTTP3_H3_NO_ERROR); + + CU_ASSERT(0 == rv); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); +} + +void test_nghttp3_conn_http_record_request_method(void) { + uint8_t rawbuf[4096]; + nghttp3_buf buf; + nghttp3_frame_headers fr; + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_ssize sconsumed; + nghttp3_qpack_encoder qenc; + const nghttp3_nv connect_reqnv[] = { + MAKE_NV(":authority", "localhost"), + MAKE_NV(":method", "CONNECT"), + }; + const nghttp3_nv head_reqnv[] = { + MAKE_NV(":authority", "localhost"), + MAKE_NV(":method", "HEAD"), + MAKE_NV(":scheme", "https"), + MAKE_NV(":path", "/"), + }; + const nghttp3_nv resnv[] = { + MAKE_NV(":status", "200"), + MAKE_NV("content-length", "1000000007"), + }; + nghttp3_stream *stream; + + memset(&callbacks, 0, sizeof(callbacks)); + nghttp3_settings_default(&settings); + + /* content-length is not allowed with 200 status code in response to + CONNECT request. Just ignore it. */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)resnv; + fr.nvlen = nghttp3_arraylen(resnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_create_stream(conn, &stream, 0); + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + nghttp3_http_record_request_method(stream, connect_reqnv, + nghttp3_arraylen(connect_reqnv)); + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + CU_ASSERT(-1 == stream->rx.http.content_length); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* The content-length in response to HEAD request must be + ignored. */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)resnv; + fr.nvlen = nghttp3_arraylen(resnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_create_stream(conn, &stream, 0); + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + nghttp3_http_record_request_method(stream, head_reqnv, + nghttp3_arraylen(head_reqnv)); + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + CU_ASSERT(0 == stream->rx.http.content_length); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); +} + +void test_nghttp3_conn_http_error(void) { + uint8_t rawbuf[4096]; + nghttp3_buf buf, ebuf; + nghttp3_frame_headers fr; + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_ssize sconsumed; + nghttp3_qpack_encoder qenc; + nghttp3_settings settings; + const nghttp3_nv dupschemenv[] = { + MAKE_NV(":path", "/"), + MAKE_NV(":method", "GET"), + MAKE_NV(":authority", "localhost"), + MAKE_NV(":scheme", "https"), + MAKE_NV(":scheme", "https"), + }; + const nghttp3_nv noschemenv[] = { + MAKE_NV(":path", "/"), + MAKE_NV(":method", "GET"), + MAKE_NV(":authority", "localhost"), + }; + userdata ud; + nghttp3_stream *stream; + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.stop_sending = stop_sending; + callbacks.reset_stream = reset_stream; + nghttp3_settings_default(&settings); + settings.qpack_max_dtable_capacity = 4096; + settings.qpack_blocked_streams = 100; + + /* duplicated :scheme */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + memset(&ud, 0, sizeof(ud)); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)dupschemenv; + fr.nvlen = nghttp3_arraylen(dupschemenv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, &ud); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + CU_ASSERT(1 == ud.stop_sending_cb.ncalled); + CU_ASSERT(0 == ud.stop_sending_cb.stream_id); + CU_ASSERT(NGHTTP3_H3_MESSAGE_ERROR == ud.stop_sending_cb.app_error_code); + CU_ASSERT(1 == ud.reset_stream_cb.ncalled); + CU_ASSERT(0 == ud.reset_stream_cb.stream_id); + CU_ASSERT(NGHTTP3_H3_MESSAGE_ERROR == ud.reset_stream_cb.app_error_code); + + stream = nghttp3_conn_find_stream(conn, 0); + + CU_ASSERT(stream->flags & NGHTTP3_STREAM_FLAG_HTTP_ERROR); + + /* After the error, everything is just discarded. */ + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + CU_ASSERT(1 == ud.stop_sending_cb.ncalled); + CU_ASSERT(1 == ud.reset_stream_cb.ncalled); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* without :scheme */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + memset(&ud, 0, sizeof(ud)); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)noschemenv; + fr.nvlen = nghttp3_arraylen(noschemenv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, &ud); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + CU_ASSERT(1 == ud.stop_sending_cb.ncalled); + CU_ASSERT(0 == ud.stop_sending_cb.stream_id); + CU_ASSERT(NGHTTP3_H3_MESSAGE_ERROR == ud.stop_sending_cb.app_error_code); + CU_ASSERT(1 == ud.reset_stream_cb.ncalled); + CU_ASSERT(0 == ud.reset_stream_cb.stream_id); + CU_ASSERT(NGHTTP3_H3_MESSAGE_ERROR == ud.reset_stream_cb.app_error_code); + + stream = nghttp3_conn_find_stream(conn, 0); + + CU_ASSERT(stream->flags & NGHTTP3_STREAM_FLAG_HTTP_ERROR); + + /* After the error, everything is just discarded. */ + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + CU_ASSERT(1 == ud.stop_sending_cb.ncalled); + CU_ASSERT(1 == ud.reset_stream_cb.ncalled); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* error on blocked stream */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, settings.qpack_max_dtable_capacity, mem); + nghttp3_qpack_encoder_set_max_blocked_streams(&qenc, + settings.qpack_blocked_streams); + nghttp3_qpack_encoder_set_max_dtable_capacity( + &qenc, settings.qpack_max_dtable_capacity); + memset(&ud, 0, sizeof(ud)); + + nghttp3_buf_init(&ebuf); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)noschemenv; + fr.nvlen = nghttp3_arraylen(noschemenv); + + nghttp3_write_frame_qpack_dyn(&buf, &ebuf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, &ud); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(sconsumed > 0); + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) != sconsumed); + CU_ASSERT(0 == ud.stop_sending_cb.ncalled); + CU_ASSERT(0 == ud.reset_stream_cb.ncalled); + + stream = nghttp3_conn_find_stream(conn, 0); + + CU_ASSERT(!(stream->flags & NGHTTP3_STREAM_FLAG_HTTP_ERROR)); + CU_ASSERT(0 != nghttp3_ringbuf_len(&stream->inq)); + + nghttp3_buf_reset(&buf); + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_QPACK_ENCODER); + + sconsumed = nghttp3_conn_read_stream(conn, 7, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + + sconsumed = nghttp3_conn_read_stream(conn, 7, ebuf.pos, + nghttp3_buf_len(&ebuf), /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&ebuf) == sconsumed); + CU_ASSERT(stream->flags & NGHTTP3_STREAM_FLAG_HTTP_ERROR); + CU_ASSERT(0 == nghttp3_ringbuf_len(&stream->inq)); + CU_ASSERT(1 == ud.stop_sending_cb.ncalled); + CU_ASSERT(0 == ud.stop_sending_cb.stream_id); + CU_ASSERT(NGHTTP3_H3_MESSAGE_ERROR == ud.stop_sending_cb.app_error_code); + CU_ASSERT(1 == ud.reset_stream_cb.ncalled); + CU_ASSERT(0 == ud.reset_stream_cb.stream_id); + CU_ASSERT(NGHTTP3_H3_MESSAGE_ERROR == ud.reset_stream_cb.app_error_code); + + /* After the error, everything is just discarded. */ + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + CU_ASSERT(1 == ud.stop_sending_cb.ncalled); + CU_ASSERT(1 == ud.reset_stream_cb.ncalled); + + nghttp3_buf_free(&ebuf, mem); + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); +} + +void test_nghttp3_conn_qpack_blocked_stream(void) { + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + nghttp3_qpack_encoder qenc; + int rv; + nghttp3_buf ebuf; + uint8_t rawbuf[4096]; + nghttp3_buf buf; + const nghttp3_nv reqnv[] = { + MAKE_NV(":authority", "localhost"), + MAKE_NV(":method", "GET"), + MAKE_NV(":path", "/"), + MAKE_NV(":scheme", "https"), + }; + const nghttp3_nv resnv[] = { + MAKE_NV(":status", "200"), + MAKE_NV("server", "nghttp3"), + }; + nghttp3_frame fr; + nghttp3_ssize sconsumed; + nghttp3_stream *stream; + + memset(&callbacks, 0, sizeof(callbacks)); + nghttp3_settings_default(&settings); + settings.qpack_max_dtable_capacity = 4096; + settings.qpack_blocked_streams = 100; + + /* The deletion of QPACK blocked stream is deferred to the moment + when it is unblocked */ + nghttp3_buf_init(&ebuf); + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + + nghttp3_qpack_encoder_init(&qenc, settings.qpack_max_dtable_capacity, mem); + nghttp3_qpack_encoder_set_max_blocked_streams(&qenc, + settings.qpack_blocked_streams); + nghttp3_qpack_encoder_set_max_dtable_capacity( + &qenc, settings.qpack_max_dtable_capacity); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_bind_qpack_streams(conn, 2, 6); + + rv = nghttp3_conn_submit_request(conn, 0, reqnv, nghttp3_arraylen(reqnv), + NULL, NULL); + + CU_ASSERT(0 == rv); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.headers.nva = (nghttp3_nv *)resnv; + fr.headers.nvlen = nghttp3_arraylen(resnv); + + nghttp3_write_frame_qpack_dyn(&buf, &ebuf, &qenc, 0, &fr); + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 1); + + CU_ASSERT(sconsumed > 0); + CU_ASSERT(sconsumed != (nghttp3_ssize)nghttp3_buf_len(&buf)); + + rv = nghttp3_conn_close_stream(conn, 0, NGHTTP3_H3_NO_ERROR); + + CU_ASSERT(0 == rv); + + stream = nghttp3_conn_find_stream(conn, 0); + + CU_ASSERT(stream->flags & NGHTTP3_STREAM_FLAG_CLOSED); + + nghttp3_buf_reset(&buf); + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_QPACK_ENCODER); + + sconsumed = nghttp3_conn_read_stream(conn, 7, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(sconsumed == (nghttp3_ssize)nghttp3_buf_len(&buf)); + CU_ASSERT(NULL != nghttp3_conn_find_stream(conn, 0)); + + sconsumed = nghttp3_conn_read_stream(conn, 7, ebuf.pos, + nghttp3_buf_len(&ebuf), /* fin = */ 0); + + CU_ASSERT(sconsumed == (nghttp3_ssize)nghttp3_buf_len(&ebuf)); + CU_ASSERT(NULL == nghttp3_conn_find_stream(conn, 0)); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + nghttp3_buf_free(&ebuf, mem); + + /* Stream that is blocked receives HEADERS which has empty + representation (that is only include Header Block Prefix) */ + nghttp3_buf_init(&ebuf); + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + + nghttp3_qpack_encoder_init(&qenc, settings.qpack_max_dtable_capacity, mem); + nghttp3_qpack_encoder_set_max_blocked_streams(&qenc, + settings.qpack_blocked_streams); + nghttp3_qpack_encoder_set_max_dtable_capacity( + &qenc, settings.qpack_max_dtable_capacity); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_bind_qpack_streams(conn, 2, 6); + + rv = nghttp3_conn_submit_request(conn, 0, reqnv, nghttp3_arraylen(reqnv), + NULL, NULL); + + CU_ASSERT(0 == rv); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.headers.nva = (nghttp3_nv *)resnv; + fr.headers.nvlen = nghttp3_arraylen(resnv); + + nghttp3_write_frame_qpack_dyn(&buf, &ebuf, &qenc, 0, &fr); + + assert(nghttp3_buf_len(&buf) > 4); + + /* Craft empty HEADERS (just leave Header Block Prefix) */ + buf.pos[1] = 2; + /* Write garbage to continue to read stream */ + buf.pos[4] = 0xff; + + sconsumed = nghttp3_conn_read_stream( + conn, 0, buf.pos, 5 /* Frame header + Header Block Prefix */, + /* fin = */ 1); + + CU_ASSERT(sconsumed > 0); + CU_ASSERT(sconsumed != (nghttp3_ssize)nghttp3_buf_len(&buf)); + + rv = nghttp3_conn_close_stream(conn, 0, NGHTTP3_H3_NO_ERROR); + + CU_ASSERT(0 == rv); + + stream = nghttp3_conn_find_stream(conn, 0); + + CU_ASSERT(stream->flags & NGHTTP3_STREAM_FLAG_CLOSED); + + nghttp3_buf_reset(&buf); + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_QPACK_ENCODER); + + sconsumed = nghttp3_conn_read_stream(conn, 7, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(sconsumed == (nghttp3_ssize)nghttp3_buf_len(&buf)); + CU_ASSERT(NULL != nghttp3_conn_find_stream(conn, 0)); + + sconsumed = nghttp3_conn_read_stream(conn, 7, ebuf.pos, + nghttp3_buf_len(&ebuf), /* fin = */ 0); + + CU_ASSERT(NGHTTP3_ERR_QPACK_DECOMPRESSION_FAILED == sconsumed); + CU_ASSERT(NULL != nghttp3_conn_find_stream(conn, 0)); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + nghttp3_buf_free(&ebuf, mem); +} + +void test_nghttp3_conn_just_fin(void) { + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + nghttp3_vec vec[256]; + nghttp3_ssize sveccnt; + int rv; + int64_t stream_id; + const nghttp3_nv nva[] = { + MAKE_NV(":path", "/"), + MAKE_NV(":authority", "example.com"), + MAKE_NV(":scheme", "https"), + MAKE_NV(":method", "GET"), + }; + nghttp3_data_reader dr; + int fin; + userdata ud; + + memset(&callbacks, 0, sizeof(callbacks)); + nghttp3_settings_default(&settings); + memset(&ud, 0, sizeof(ud)); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, &ud); + + nghttp3_conn_bind_control_stream(conn, 2); + nghttp3_conn_bind_qpack_streams(conn, 6, 10); + + /* Write control streams */ + for (;;) { + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(sveccnt >= 0); + + if (sveccnt == 0) { + break; + } + + rv = nghttp3_conn_add_write_offset( + conn, stream_id, (size_t)nghttp3_vec_len(vec, (size_t)sveccnt)); + + CU_ASSERT(0 == rv); + } + + /* No DATA frame header */ + dr.read_data = step_read_data; + rv = nghttp3_conn_submit_request(conn, 0, nva, nghttp3_arraylen(nva), &dr, + NULL); + + CU_ASSERT(0 == rv); + + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(1 == sveccnt); + CU_ASSERT(0 == stream_id); + CU_ASSERT(1 == fin); + + rv = nghttp3_conn_add_write_offset( + conn, stream_id, (size_t)nghttp3_vec_len(vec, (size_t)sveccnt)); + + CU_ASSERT(0 == rv); + + /* Just fin */ + ud.data.nblock = 1; + dr.read_data = block_then_step_read_data; + + rv = nghttp3_conn_submit_request(conn, 4, nva, nghttp3_arraylen(nva), &dr, + NULL); + + CU_ASSERT(0 == rv); + + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(1 == sveccnt); + CU_ASSERT(4 == stream_id); + CU_ASSERT(0 == fin); + + rv = nghttp3_conn_add_write_offset( + conn, stream_id, (size_t)nghttp3_vec_len(vec, (size_t)sveccnt)); + + CU_ASSERT(0 == rv); + + /* Resume stream 4 because it was blocked */ + nghttp3_conn_resume_stream(conn, 4); + + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(0 == sveccnt); + CU_ASSERT(4 == stream_id); + CU_ASSERT(1 == fin); + + rv = nghttp3_conn_add_write_offset( + conn, stream_id, (size_t)nghttp3_vec_len(vec, (size_t)sveccnt)); + + CU_ASSERT(0 == rv); + + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(0 == sveccnt); + CU_ASSERT(-1 == stream_id); + CU_ASSERT(0 == fin); + + nghttp3_conn_del(conn); +} + +void test_nghttp3_conn_submit_response_read_blocked(void) { + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + const nghttp3_nv nva[] = { + MAKE_NV(":status", "200"), + }; + nghttp3_stream *stream; + int rv; + nghttp3_vec vec[256]; + int fin; + int64_t stream_id; + nghttp3_ssize sveccnt; + nghttp3_data_reader dr = {step_then_block_read_data}; + userdata ud; + + memset(&callbacks, 0, sizeof(callbacks)); + nghttp3_settings_default(&settings); + + /* Make sure that flushing serialized data while + NGHTTP3_STREAM_FLAG_READ_DATA_BLOCKED is set does not cause any + error */ + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, &ud); + conn->remote.bidi.max_client_streams = 1; + nghttp3_conn_bind_qpack_streams(conn, 7, 11); + + nghttp3_conn_create_stream(conn, &stream, 0); + + ud.data.left = 1000; + ud.data.step = 1000; + rv = nghttp3_conn_submit_response(conn, 0, nva, nghttp3_arraylen(nva), &dr); + + CU_ASSERT(0 == rv); + + for (;;) { + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(sveccnt >= 0); + + if (sveccnt <= 0) { + break; + } + + rv = nghttp3_conn_add_write_offset(conn, stream_id, 1); + + CU_ASSERT(0 == rv); + } + + nghttp3_conn_del(conn); +} + +void test_nghttp3_conn_recv_uni(void) { + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + nghttp3_ssize nread; + uint8_t buf[256]; + + memset(&callbacks, 0, sizeof(callbacks)); + nghttp3_settings_default(&settings); + + /* 0 length unidirectional stream must be ignored */ + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + + nread = nghttp3_conn_read_stream(conn, 3, NULL, 0, /* fin = */ 1); + + CU_ASSERT(0 == nread); + CU_ASSERT(NULL == nghttp3_conn_find_stream(conn, 3)); + + nghttp3_conn_del(conn); + + /* 0 length unidirectional stream; first get 0 length without fin, + and then get 0 length with fin. */ + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + + nread = nghttp3_conn_read_stream(conn, 3, NULL, 0, /* fin = */ 0); + + CU_ASSERT(0 == nread); + CU_ASSERT(NULL != nghttp3_conn_find_stream(conn, 3)); + + nread = nghttp3_conn_read_stream(conn, 3, NULL, 0, /* fin = */ 1); + + CU_ASSERT(0 == nread); + CU_ASSERT(NULL == nghttp3_conn_find_stream(conn, 3)); + + nghttp3_conn_del(conn); + + /* Fin while reading stream header is treated as error. */ + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + + /* 4 bytes integer */ + buf[0] = 0xc0; + nread = nghttp3_conn_read_stream(conn, 3, buf, 1, /* fin = */ 0); + + CU_ASSERT(1 == nread); + CU_ASSERT(NULL != nghttp3_conn_find_stream(conn, 3)); + + nread = nghttp3_conn_read_stream(conn, 3, NULL, 0, /* fin = */ 1); + + CU_ASSERT(NGHTTP3_ERR_H3_GENERAL_PROTOCOL_ERROR == nread); + + nghttp3_conn_del(conn); +} + +void test_nghttp3_conn_recv_goaway(void) { + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + nghttp3_frame fr; + uint8_t rawbuf[1024]; + nghttp3_buf buf; + nghttp3_ssize nconsumed; + const nghttp3_nv nva[] = { + MAKE_NV(":path", "/"), + MAKE_NV(":authority", "example.com"), + MAKE_NV(":scheme", "https"), + MAKE_NV(":method", "GET"), + }; + int rv; + userdata ud; + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.shutdown = conn_shutdown; + memset(&ud, 0, sizeof(ud)); + nghttp3_settings_default(&settings); + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + + /* Client receives GOAWAY */ + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, &ud); + nghttp3_conn_bind_control_stream(conn, 2); + nghttp3_conn_bind_qpack_streams(conn, 6, 10); + + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_CONTROL); + + fr.hd.type = NGHTTP3_FRAME_SETTINGS; + fr.settings.niv = 0; + + nghttp3_write_frame(&buf, &fr); + + fr.hd.type = NGHTTP3_FRAME_GOAWAY; + fr.goaway.id = 12; + + nghttp3_write_frame(&buf, &fr); + + ud.shutdown_cb.ncalled = 0; + ud.shutdown_cb.id = 0; + nconsumed = nghttp3_conn_read_stream(conn, 3, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == nconsumed); + CU_ASSERT(conn->flags & NGHTTP3_CONN_FLAG_GOAWAY_RECVED); + CU_ASSERT(12 == conn->rx.goaway_id); + CU_ASSERT(1 == ud.shutdown_cb.ncalled); + CU_ASSERT(12 == ud.shutdown_cb.id); + + /* Cannot submit request anymore */ + rv = nghttp3_conn_submit_request(conn, 0, nva, nghttp3_arraylen(nva), NULL, + NULL); + + CU_ASSERT(NGHTTP3_ERR_CONN_CLOSING == rv); + + nghttp3_conn_del(conn); + + nghttp3_buf_reset(&buf); + + /* Receiving GOAWAY with increased ID is treated as error */ + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, &ud); + nghttp3_conn_bind_control_stream(conn, 2); + nghttp3_conn_bind_qpack_streams(conn, 6, 10); + + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_CONTROL); + + fr.hd.type = NGHTTP3_FRAME_SETTINGS; + fr.settings.niv = 0; + + nghttp3_write_frame(&buf, &fr); + + fr.hd.type = NGHTTP3_FRAME_GOAWAY; + fr.goaway.id = 12; + + nghttp3_write_frame(&buf, &fr); + + fr.hd.type = NGHTTP3_FRAME_GOAWAY; + fr.goaway.id = 16; + + nghttp3_write_frame(&buf, &fr); + + ud.shutdown_cb.ncalled = 0; + ud.shutdown_cb.id = 0; + nconsumed = nghttp3_conn_read_stream(conn, 3, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(NGHTTP3_ERR_H3_ID_ERROR == nconsumed); + CU_ASSERT(conn->flags & NGHTTP3_CONN_FLAG_GOAWAY_RECVED); + CU_ASSERT(12 == conn->rx.goaway_id); + CU_ASSERT(1 == ud.shutdown_cb.ncalled); + CU_ASSERT(12 == ud.shutdown_cb.id); + + nghttp3_conn_del(conn); + + nghttp3_buf_reset(&buf); + + /* Server receives GOAWAY */ + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, &ud); + nghttp3_conn_bind_control_stream(conn, 3); + nghttp3_conn_bind_qpack_streams(conn, 7, 11); + + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_CONTROL); + + fr.hd.type = NGHTTP3_FRAME_SETTINGS; + fr.settings.niv = 0; + + nghttp3_write_frame(&buf, &fr); + + fr.hd.type = NGHTTP3_FRAME_GOAWAY; + fr.goaway.id = 101; + + nghttp3_write_frame(&buf, &fr); + + ud.shutdown_cb.ncalled = 0; + ud.shutdown_cb.id = 0; + nconsumed = nghttp3_conn_read_stream(conn, 2, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == nconsumed); + CU_ASSERT(conn->flags & NGHTTP3_CONN_FLAG_GOAWAY_RECVED); + CU_ASSERT(101 == conn->rx.goaway_id); + CU_ASSERT(1 == ud.shutdown_cb.ncalled); + CU_ASSERT(101 == ud.shutdown_cb.id); + + nghttp3_conn_del(conn); +} + +void test_nghttp3_conn_shutdown_server(void) { + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + nghttp3_frame fr; + uint8_t rawbuf[1024]; + nghttp3_buf buf; + nghttp3_ssize nconsumed; + nghttp3_stream *stream; + nghttp3_qpack_encoder qenc; + const nghttp3_nv nva[] = { + MAKE_NV(":path", "/"), + MAKE_NV(":authority", "example.com"), + MAKE_NV(":scheme", "https"), + MAKE_NV(":method", "GET"), + }; + int rv; + userdata ud; + nghttp3_ssize sveccnt; + nghttp3_vec vec[256]; + int64_t stream_id; + int fin; + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.stop_sending = stop_sending; + callbacks.reset_stream = reset_stream; + nghttp3_settings_default(&settings); + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + + /* Server sends GOAWAY and rejects stream whose ID is greater than + or equal to the ID in GOAWAY. */ + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, &ud); + nghttp3_conn_bind_control_stream(conn, 3); + nghttp3_conn_bind_qpack_streams(conn, 7, 11); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.headers.nva = (nghttp3_nv *)nva; + fr.headers.nvlen = nghttp3_arraylen(nva); + + nghttp3_write_frame_qpack(&buf, &qenc, 4, &fr); + + nconsumed = nghttp3_conn_read_stream(conn, 4, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == nconsumed); + CU_ASSERT(4 == conn->rx.max_stream_id_bidi); + + rv = nghttp3_conn_shutdown(conn); + + CU_ASSERT(0 == rv); + CU_ASSERT(conn->flags & NGHTTP3_CONN_FLAG_GOAWAY_QUEUED); + CU_ASSERT(8 == conn->tx.goaway_id); + + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(sveccnt > 0); + CU_ASSERT(3 == stream_id); + + nghttp3_buf_reset(&buf); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.headers.nva = (nghttp3_nv *)nva; + fr.headers.nvlen = nghttp3_arraylen(nva); + + nghttp3_write_frame_qpack(&buf, &qenc, 8, &fr); + + memset(&ud, 0, sizeof(ud)); + nconsumed = nghttp3_conn_read_stream(conn, 8, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == nconsumed); + CU_ASSERT(8 == conn->rx.max_stream_id_bidi); + CU_ASSERT(1 == ud.stop_sending_cb.ncalled); + CU_ASSERT(8 == ud.stop_sending_cb.stream_id); + CU_ASSERT(NGHTTP3_H3_REQUEST_REJECTED == ud.stop_sending_cb.app_error_code); + CU_ASSERT(1 == ud.reset_stream_cb.ncalled); + CU_ASSERT(8 == ud.reset_stream_cb.stream_id); + CU_ASSERT(NGHTTP3_H3_REQUEST_REJECTED == ud.reset_stream_cb.app_error_code); + + stream = nghttp3_conn_find_stream(conn, 8); + + CU_ASSERT(NGHTTP3_REQ_STREAM_STATE_IGN_REST == stream->rstate.state); + + nghttp3_qpack_encoder_free(&qenc); + nghttp3_conn_del(conn); +} + +void test_nghttp3_conn_shutdown_client(void) { + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + uint8_t rawbuf[1024]; + nghttp3_buf buf; + const nghttp3_nv nva[] = { + MAKE_NV(":path", "/"), + MAKE_NV(":authority", "example.com"), + MAKE_NV(":scheme", "https"), + MAKE_NV(":method", "GET"), + }; + int rv; + userdata ud; + nghttp3_ssize sveccnt; + nghttp3_vec vec[256]; + int64_t stream_id; + int fin; + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.stop_sending = stop_sending; + callbacks.reset_stream = reset_stream; + nghttp3_settings_default(&settings); + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + + /* Client sends GOAWAY and rejects PUSH_PROMISE whose ID is greater + than or equal to the ID in GOAWAY. */ + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, &ud); + nghttp3_conn_bind_control_stream(conn, 2); + nghttp3_conn_bind_qpack_streams(conn, 6, 10); + + rv = nghttp3_conn_submit_request(conn, 0, nva, nghttp3_arraylen(nva), NULL, + NULL); + + CU_ASSERT(0 == rv); + + rv = nghttp3_conn_shutdown(conn); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == conn->tx.goaway_id); + + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(sveccnt > 0); + CU_ASSERT(2 == stream_id); + + nghttp3_buf_reset(&buf); + + nghttp3_conn_del(conn); + + nghttp3_buf_reset(&buf); +} + +void test_nghttp3_conn_priority_update(void) { + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + nghttp3_frame fr; + nghttp3_ssize nconsumed; + uint8_t rawbuf[2048]; + nghttp3_buf buf; + nghttp3_stream *stream; + int rv; + userdata ud; + nghttp3_qpack_encoder qenc; + const nghttp3_nv nva[] = { + MAKE_NV(":path", "/"), MAKE_NV(":authority", "example.com"), + MAKE_NV(":scheme", "https"), MAKE_NV(":method", "GET"), + MAKE_NV("priority", "u=5, i"), + }; + + memset(&callbacks, 0, sizeof(callbacks)); + memset(&ud, 0, sizeof(ud)); + nghttp3_settings_default(&settings); + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + + /* Receive PRIORITY_UPDATE and stream has not been created yet */ + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_bind_control_stream(conn, 3); + nghttp3_conn_bind_qpack_streams(conn, 7, 11); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_CONTROL); + + fr.hd.type = NGHTTP3_FRAME_SETTINGS; + fr.settings.niv = 0; + + nghttp3_write_frame(&buf, (nghttp3_frame *)&fr); + + fr.hd.type = NGHTTP3_FRAME_PRIORITY_UPDATE; + fr.priority_update.pri_elem_id = 0; + fr.priority_update.pri.urgency = 2; + fr.priority_update.pri.inc = 1; + + nghttp3_write_frame(&buf, (nghttp3_frame *)&fr); + + nconsumed = nghttp3_conn_read_stream(conn, 2, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == nconsumed); + + stream = nghttp3_conn_find_stream(conn, 0); + + CU_ASSERT(NULL != stream); + CU_ASSERT(stream->flags & NGHTTP3_STREAM_FLAG_PRIORITY_UPDATE_RECVED); + CU_ASSERT(2 == nghttp3_pri_uint8_urgency(stream->node.pri)); + CU_ASSERT(1 == nghttp3_pri_uint8_inc(stream->node.pri)); + + nghttp3_buf_reset(&buf); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.headers.nva = (nghttp3_nv *)nva; + fr.headers.nvlen = nghttp3_arraylen(nva); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, &fr); + + nconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 1); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == nconsumed); + + /* priority header field should not override the value set by + PRIORITY_UPDATE frame. */ + CU_ASSERT(2 == nghttp3_pri_uint8_urgency(stream->node.pri)); + CU_ASSERT(1 == nghttp3_pri_uint8_inc(stream->node.pri)); + + nghttp3_qpack_encoder_free(&qenc); + nghttp3_conn_del(conn); + nghttp3_buf_reset(&buf); + + /* Receive PRIORITY_UPDATE and stream has been created */ + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_bind_control_stream(conn, 3); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + + rv = nghttp3_conn_create_stream(conn, &stream, 0); + + CU_ASSERT(0 == rv); + + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_CONTROL); + + fr.hd.type = NGHTTP3_FRAME_SETTINGS; + fr.settings.niv = 0; + + nghttp3_write_frame(&buf, (nghttp3_frame *)&fr); + + fr.hd.type = NGHTTP3_FRAME_PRIORITY_UPDATE; + fr.priority_update.pri_elem_id = 0; + fr.priority_update.pri.urgency = 6; + fr.priority_update.pri.inc = 0; + + nghttp3_write_frame(&buf, (nghttp3_frame *)&fr); + + nconsumed = nghttp3_conn_read_stream(conn, 2, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == nconsumed); + CU_ASSERT(stream->flags & NGHTTP3_STREAM_FLAG_PRIORITY_UPDATE_RECVED); + CU_ASSERT(6 == nghttp3_pri_uint8_urgency(stream->node.pri)); + CU_ASSERT(0 == nghttp3_pri_uint8_inc(stream->node.pri)); + + nghttp3_conn_del(conn); + nghttp3_buf_reset(&buf); + + /* Receive PRIORITY_UPDATE against non-existent push_promise */ + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, &ud); + nghttp3_conn_bind_control_stream(conn, 3); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_CONTROL); + + fr.hd.type = NGHTTP3_FRAME_SETTINGS; + fr.settings.niv = 0; + + nghttp3_write_frame(&buf, (nghttp3_frame *)&fr); + + fr.hd.type = NGHTTP3_FRAME_PRIORITY_UPDATE_PUSH_ID; + fr.priority_update.pri_elem_id = 0; + fr.priority_update.pri.urgency = 6; + fr.priority_update.pri.inc = 0; + + nghttp3_write_frame(&buf, (nghttp3_frame *)&fr); + + nconsumed = nghttp3_conn_read_stream(conn, 2, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(NGHTTP3_ERR_H3_ID_ERROR == nconsumed); + + nghttp3_conn_del(conn); + nghttp3_buf_reset(&buf); + + /* Receive PRIORITY_UPDATE and its Priority Field Value is larger + than buffer */ + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, &ud); + nghttp3_conn_bind_control_stream(conn, 3); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_CONTROL); + + fr.hd.type = NGHTTP3_FRAME_SETTINGS; + fr.settings.niv = 0; + + nghttp3_write_frame(&buf, (nghttp3_frame *)&fr); + + fr.hd.type = NGHTTP3_FRAME_PRIORITY_UPDATE; + fr.priority_update.pri_elem_id = 0; + fr.priority_update.pri.urgency = 2; + fr.priority_update.pri.inc = 1; + + nghttp3_frame_write_priority_update_len(&fr.hd.length, &fr.priority_update); + fr.hd.length += 10; + buf.last = nghttp3_frame_write_priority_update(buf.last, &fr.priority_update); + memset(buf.last, ' ', 10); + buf.last += 10; + + /* Make sure boundary check works when frame is fragmented. */ + nconsumed = + nghttp3_conn_read_stream(conn, 2, buf.pos, nghttp3_buf_len(&buf) - 10, + /* fin = */ 0); + stream = nghttp3_conn_find_stream(conn, 2); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) - 10 == nconsumed); + CU_ASSERT(NGHTTP3_CTRL_STREAM_STATE_PRIORITY_UPDATE == stream->rstate.state); + + nconsumed = + nghttp3_conn_read_stream(conn, 2, buf.pos + nconsumed, 10, /* fin = */ 0); + + CU_ASSERT(10 == nconsumed); + CU_ASSERT(NGHTTP3_CTRL_STREAM_STATE_FRAME_TYPE == stream->rstate.state); + CU_ASSERT(NULL == nghttp3_conn_find_stream(conn, 0)); + + nghttp3_conn_del(conn); +} + +void test_nghttp3_conn_request_priority(void) { + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + nghttp3_frame fr; + nghttp3_ssize nconsumed; + uint8_t rawbuf[2048]; + nghttp3_buf buf; + nghttp3_stream *stream; + userdata ud; + nghttp3_qpack_encoder qenc; + const nghttp3_nv nva[] = { + MAKE_NV(":path", "/"), MAKE_NV(":authority", "example.com"), + MAKE_NV(":scheme", "https"), MAKE_NV(":method", "GET"), + MAKE_NV("priority", "u=5, i"), + }; + const nghttp3_nv badpri_nva[] = { + MAKE_NV(":path", "/"), MAKE_NV(":authority", "example.com"), + MAKE_NV(":scheme", "https"), MAKE_NV(":method", "GET"), + MAKE_NV("priority", "u=5, i"), MAKE_NV("priority", "i, u=x"), + }; + + memset(&callbacks, 0, sizeof(callbacks)); + memset(&ud, 0, sizeof(ud)); + nghttp3_settings_default(&settings); + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + + /* Priority in request */ + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_bind_control_stream(conn, 3); + nghttp3_conn_bind_qpack_streams(conn, 7, 11); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_CONTROL); + + fr.hd.type = NGHTTP3_FRAME_SETTINGS; + fr.settings.niv = 0; + + nghttp3_write_frame(&buf, (nghttp3_frame *)&fr); + + nconsumed = nghttp3_conn_read_stream(conn, 2, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == nconsumed); + + nghttp3_buf_reset(&buf); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.headers.nva = (nghttp3_nv *)nva; + fr.headers.nvlen = nghttp3_arraylen(nva); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, &fr); + + nconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 1); + + stream = nghttp3_conn_find_stream(conn, 0); + + CU_ASSERT(NULL != stream); + CU_ASSERT(5 == nghttp3_pri_uint8_urgency(stream->node.pri)); + CU_ASSERT(1 == nghttp3_pri_uint8_inc(stream->node.pri)); + + nghttp3_qpack_encoder_free(&qenc); + nghttp3_conn_del(conn); + nghttp3_buf_reset(&buf); + + /* Bad priority in request */ + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_bind_control_stream(conn, 3); + nghttp3_conn_bind_qpack_streams(conn, 7, 11); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_CONTROL); + + fr.hd.type = NGHTTP3_FRAME_SETTINGS; + fr.settings.niv = 0; + + nghttp3_write_frame(&buf, (nghttp3_frame *)&fr); + + nconsumed = nghttp3_conn_read_stream(conn, 2, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == nconsumed); + + nghttp3_buf_reset(&buf); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.headers.nva = (nghttp3_nv *)badpri_nva; + fr.headers.nvlen = nghttp3_arraylen(badpri_nva); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, &fr); + + nconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 1); + + stream = nghttp3_conn_find_stream(conn, 0); + + CU_ASSERT(NULL != stream); + CU_ASSERT(NGHTTP3_DEFAULT_URGENCY == stream->node.pri); + + nghttp3_qpack_encoder_free(&qenc); + nghttp3_conn_del(conn); + nghttp3_buf_reset(&buf); +} + +void test_nghttp3_conn_set_stream_priority(void) { + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + const nghttp3_nv nva[] = { + MAKE_NV(":path", "/"), + MAKE_NV(":authority", "example.com"), + MAKE_NV(":scheme", "https"), + MAKE_NV(":method", "GET"), + }; + int rv; + nghttp3_pri pri; + nghttp3_frame_entry *ent; + nghttp3_stream *stream; + size_t i; + + memset(&callbacks, 0, sizeof(callbacks)); + nghttp3_settings_default(&settings); + + /* Update stream priority by client */ + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_bind_control_stream(conn, 2); + nghttp3_conn_bind_qpack_streams(conn, 6, 10); + + rv = nghttp3_conn_submit_request(conn, 0, nva, nghttp3_arraylen(nva), NULL, + NULL); + + CU_ASSERT(0 == rv); + + pri.urgency = 2; + pri.inc = 1; + + rv = nghttp3_conn_set_stream_priority(conn, 0, &pri); + + CU_ASSERT(0 == rv); + + stream = nghttp3_conn_find_stream(conn, 2); + + for (i = 0; i < nghttp3_ringbuf_len(&stream->frq); ++i) { + ent = nghttp3_ringbuf_get(&stream->frq, i); + if (ent->fr.hd.type != NGHTTP3_FRAME_PRIORITY_UPDATE) { + continue; + } + + CU_ASSERT(2 == ent->fr.priority_update.pri.urgency); + CU_ASSERT(1 == ent->fr.priority_update.pri.inc); + + break; + } + + CU_ASSERT(i < nghttp3_ringbuf_len(&stream->frq)); + + nghttp3_conn_del(conn); + + /* Updating priority of stream which does not exist is an error */ + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_bind_control_stream(conn, 2); + nghttp3_conn_bind_qpack_streams(conn, 6, 10); + + pri.urgency = 2; + pri.inc = 1; + + rv = nghttp3_conn_set_stream_priority(conn, 0, &pri); + + CU_ASSERT(NGHTTP3_ERR_STREAM_NOT_FOUND == rv); + + nghttp3_conn_del(conn); + + /* Update stream priority by server */ + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_bind_control_stream(conn, 3); + + rv = nghttp3_conn_create_stream(conn, &stream, 0); + + CU_ASSERT(0 == rv); + + pri.urgency = 4; + pri.inc = 0; + + rv = nghttp3_conn_set_stream_priority(conn, 0, &pri); + + CU_ASSERT(0 == rv); + + stream = nghttp3_conn_find_stream(conn, 0); + + CU_ASSERT(stream->flags & NGHTTP3_STREAM_FLAG_SERVER_PRIORITY_SET); + CU_ASSERT(nghttp3_pri_to_uint8(&pri) == stream->node.pri); + + nghttp3_conn_del(conn); +} + +void test_nghttp3_conn_shutdown_stream_read(void) { + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + nghttp3_qpack_encoder qenc; + int rv; + nghttp3_buf ebuf; + uint8_t rawbuf[4096]; + nghttp3_buf buf; + const nghttp3_nv reqnv[] = { + MAKE_NV(":authority", "localhost"), + MAKE_NV(":method", "GET"), + MAKE_NV(":path", "/"), + MAKE_NV(":scheme", "https"), + }; + const nghttp3_nv resnv[] = { + MAKE_NV(":status", "200"), + MAKE_NV("server", "nghttp3"), + }; + nghttp3_frame fr; + nghttp3_ssize sconsumed; + size_t consumed_total; + userdata ud; + size_t indatalen; + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.deferred_consume = deferred_consume; + nghttp3_settings_default(&settings); + settings.qpack_max_dtable_capacity = 4096; + settings.qpack_blocked_streams = 100; + + /* Shutting down read-side stream when a stream is blocked by QPACK + dependency. */ + nghttp3_buf_init(&ebuf); + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + + nghttp3_qpack_encoder_init(&qenc, settings.qpack_max_dtable_capacity, mem); + nghttp3_qpack_encoder_set_max_blocked_streams(&qenc, + settings.qpack_blocked_streams); + nghttp3_qpack_encoder_set_max_dtable_capacity( + &qenc, settings.qpack_max_dtable_capacity); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, &ud); + nghttp3_conn_bind_qpack_streams(conn, 2, 6); + + rv = nghttp3_conn_submit_request(conn, 0, reqnv, nghttp3_arraylen(reqnv), + NULL, NULL); + + CU_ASSERT(0 == rv); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.headers.nva = (nghttp3_nv *)resnv; + fr.headers.nvlen = nghttp3_arraylen(resnv); + + nghttp3_write_frame_qpack_dyn(&buf, &ebuf, &qenc, 0, &fr); + + indatalen = nghttp3_buf_len(&buf); + + ud.deferred_consume_cb.consumed_total = 0; + consumed_total = 0; + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(sconsumed > 0); + CU_ASSERT(sconsumed != (nghttp3_ssize)nghttp3_buf_len(&buf)); + + consumed_total += (size_t)sconsumed; + + rv = nghttp3_conn_shutdown_stream_read(conn, 0); + + CU_ASSERT(0 == rv); + CU_ASSERT(1 == nghttp3_buf_len(&conn->qdec.dbuf)); + + /* Reading further stream data is discarded. */ + nghttp3_buf_reset(&buf); + *buf.pos = 0; + ++buf.last; + + ++indatalen; + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 1); + + CU_ASSERT(1 == sconsumed); + + consumed_total += (size_t)sconsumed; + + nghttp3_buf_reset(&buf); + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_QPACK_ENCODER); + + sconsumed = nghttp3_conn_read_stream(conn, 7, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(sconsumed == (nghttp3_ssize)nghttp3_buf_len(&buf)); + + sconsumed = nghttp3_conn_read_stream(conn, 7, ebuf.pos, + nghttp3_buf_len(&ebuf), /* fin = */ 0); + + CU_ASSERT(sconsumed == (nghttp3_ssize)nghttp3_buf_len(&ebuf)); + /* Make sure that Section Acknowledgement is not written. */ + CU_ASSERT(1 == nghttp3_buf_len(&conn->qdec.dbuf)); + CU_ASSERT(indatalen == + consumed_total + ud.deferred_consume_cb.consumed_total); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + nghttp3_buf_free(&ebuf, mem); +} + +void test_nghttp3_conn_stream_data_overflow(void) { +#if SIZE_MAX > UINT32_MAX + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + const nghttp3_nv nva[] = { + MAKE_NV(":path", "/"), + MAKE_NV(":authority", "example.com"), + MAKE_NV(":scheme", "https"), + MAKE_NV(":method", "GET"), + }; + nghttp3_vec vec[256]; + nghttp3_ssize sveccnt; + int rv; + int64_t stream_id; + nghttp3_data_reader dr; + int fin; + + memset(&callbacks, 0, sizeof(callbacks)); + nghttp3_settings_default(&settings); + + /* Specify NGHTTP3_MAX_VARINT + 1 bytes data in + nghttp3_read_data_callback. */ + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_bind_qpack_streams(conn, 6, 10); + + dr.read_data = stream_data_overflow_read_data; + + /* QPACK decoder stream */ + rv = nghttp3_conn_submit_request(conn, 0, nva, nghttp3_arraylen(nva), &dr, + NULL); + + CU_ASSERT(0 == rv); + + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(10 == stream_id); + CU_ASSERT(1 == sveccnt); + + nghttp3_conn_add_write_offset(conn, 10, vec[0].len); + + /* QPACK encoder stream */ + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(6 == stream_id); + CU_ASSERT(1 == sveccnt); + + nghttp3_conn_add_write_offset(conn, 6, vec[0].len); + + /* Write request stream */ + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(NGHTTP3_ERR_STREAM_DATA_OVERFLOW == sveccnt); + + nghttp3_conn_del(conn); + + /* nghttp3_stream_outq_add detects stream data overflow */ + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_bind_qpack_streams(conn, 6, 10); + + dr.read_data = stream_data_almost_overflow_read_data; + + /* QPACK decoder stream */ + rv = nghttp3_conn_submit_request(conn, 0, nva, nghttp3_arraylen(nva), &dr, + NULL); + + CU_ASSERT(0 == rv); + + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(10 == stream_id); + CU_ASSERT(1 == sveccnt); + + nghttp3_conn_add_write_offset(conn, 10, vec[0].len); + + /* QPACK encoder stream */ + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(6 == stream_id); + CU_ASSERT(1 == sveccnt); + + nghttp3_conn_add_write_offset(conn, 6, vec[0].len); + + /* Write request stream */ + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(NGHTTP3_ERR_STREAM_DATA_OVERFLOW == sveccnt); + + nghttp3_conn_del(conn); +#endif /* SIZE_MAX > UINT32_MAX */ +} |