summaryrefslogtreecommitdiffstats
path: root/tests/nghttp3_conn_test.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--tests/nghttp3_conn_test.c3448
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 */
+}