summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/.gitignore1
-rw-r--r--tests/CMakeLists.txt57
-rw-r--r--tests/Makefile.am65
-rw-r--r--tests/main.c146
-rw-r--r--tests/nghttp3_conn_test.c3448
-rw-r--r--tests/nghttp3_conn_test.h58
-rw-r--r--tests/nghttp3_conv_test.c51
-rw-r--r--tests/nghttp3_conv_test.h34
-rw-r--r--tests/nghttp3_http_test.c676
-rw-r--r--tests/nghttp3_http_test.h37
-rw-r--r--tests/nghttp3_qpack_test.c779
-rw-r--r--tests/nghttp3_qpack_test.h41
-rw-r--r--tests/nghttp3_test_helper.c139
-rw-r--r--tests/nghttp3_test_helper.h72
-rw-r--r--tests/nghttp3_tnode_test.c164
-rw-r--r--tests/nghttp3_tnode_test.h34
16 files changed, 5802 insertions, 0 deletions
diff --git a/tests/.gitignore b/tests/.gitignore
new file mode 100644
index 0000000..3e57a18
--- /dev/null
+++ b/tests/.gitignore
@@ -0,0 +1 @@
+/main \ No newline at end of file
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
new file mode 100644
index 0000000..003706b
--- /dev/null
+++ b/tests/CMakeLists.txt
@@ -0,0 +1,57 @@
+# nghttp3
+#
+# Copyright (c) 2019 nghttp3 contributors
+# Copyright (c) 2016 ngtcp2 contributors
+# Copyright (c) 2012 nghttp2 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.
+
+if(HAVE_CUNIT)
+ include_directories(
+ "${CMAKE_SOURCE_DIR}/lib"
+ "${CMAKE_SOURCE_DIR}/lib/includes"
+ "${CMAKE_BINARY_DIR}/lib/includes"
+ ${CUNIT_INCLUDE_DIRS}
+ )
+
+ set(main_SOURCES
+ main.c
+ nghttp3_qpack_test.c
+ nghttp3_conn_test.c
+ nghttp3_tnode_test.c
+ nghttp3_http_test.c
+ nghttp3_conv_test.c
+ nghttp3_test_helper.c
+ )
+
+ add_executable(main EXCLUDE_FROM_ALL
+ ${main_SOURCES}
+ )
+ target_include_directories(main PRIVATE ${CUNIT_INCLUDE_DIRS})
+ # FIXME enable and fix warnings
+ #set_target_properties(main PROPERTIES COMPILE_FLAGS "${WARNCFLAGS}")
+ target_link_libraries(main
+ nghttp3_static
+ ${CUNIT_LIBRARIES}
+ m
+ )
+ add_test(main main)
+ add_dependencies(check main)
+endif()
diff --git a/tests/Makefile.am b/tests/Makefile.am
new file mode 100644
index 0000000..aa3566c
--- /dev/null
+++ b/tests/Makefile.am
@@ -0,0 +1,65 @@
+# nghttp3
+#
+# Copyright (c) 2019 nghttp3 contributors
+# Copyright (c) 2016 ngtcp2 contributors
+# Copyright (c) 2012 nghttp2 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.
+EXTRA_DIST = CMakeLists.txt
+
+if HAVE_CUNIT
+
+check_PROGRAMS = main
+
+OBJECTS = \
+ main.c \
+ nghttp3_qpack_test.c \
+ nghttp3_conn_test.c \
+ nghttp3_tnode_test.c \
+ nghttp3_http_test.c \
+ nghttp3_conv_test.c \
+ nghttp3_test_helper.c
+HFILES = \
+ nghttp3_qpack_test.h \
+ nghttp3_conn_test.h \
+ nghttp3_tnode_test.h \
+ nghttp3_http_test.h \
+ nghttp3_conv_test.h \
+ nghttp3_test_helper.h
+
+main_SOURCES = $(HFILES) $(OBJECTS)
+
+# With static lib disabled and symbol hiding enabled, we have to link object
+# files directly because the tests use symbols not included in public API.
+main_LDADD = ${top_builddir}/lib/.libs/*.o
+main_LDADD += @CUNIT_LIBS@ -lm
+main_LDFLAGS = -static
+
+AM_CFLAGS = $(WARNCFLAGS) \
+ -I${top_srcdir}/lib \
+ -I${top_srcdir}/lib/includes \
+ -I${top_builddir}/lib/includes \
+ -DBUILDING_NGHTTP3 \
+ @CUNIT_CFLAGS@ @DEFS@
+AM_LDFLAGS = -no-install
+
+TESTS = main
+
+endif # HAVE_CUNIT
diff --git a/tests/main.c b/tests/main.c
new file mode 100644
index 0000000..201c1bc
--- /dev/null
+++ b/tests/main.c
@@ -0,0 +1,146 @@
+/*
+ * nghttp3
+ *
+ * Copyright (c) 2019 nghttp3 contributors
+ * Copyright (c) 2016 ngtcp2 contributors
+ * Copyright (c) 2012 nghttp2 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.
+ */
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <stdio.h>
+#include <string.h>
+#include <CUnit/Basic.h>
+/* include test cases' include files here */
+#include "nghttp3_qpack_test.h"
+#include "nghttp3_conn_test.h"
+#include "nghttp3_tnode_test.h"
+#include "nghttp3_http_test.h"
+#include "nghttp3_conv_test.h"
+
+static int init_suite1(void) { return 0; }
+
+static int clean_suite1(void) { return 0; }
+
+int main(void) {
+ CU_pSuite pSuite = NULL;
+ unsigned int num_tests_failed;
+
+ /* initialize the CUnit test registry */
+ if (CUE_SUCCESS != CU_initialize_registry())
+ return (int)CU_get_error();
+
+ /* add a suite to the registry */
+ pSuite = CU_add_suite("libnghttp3_TestSuite", init_suite1, clean_suite1);
+ if (NULL == pSuite) {
+ CU_cleanup_registry();
+ return (int)CU_get_error();
+ }
+
+ /* add the tests to the suite */
+ if (!CU_add_test(pSuite, "qpack_encoder_encode",
+ test_nghttp3_qpack_encoder_encode) ||
+ !CU_add_test(pSuite, "qpack_encoder_still_blocked",
+ test_nghttp3_qpack_encoder_still_blocked) ||
+ !CU_add_test(pSuite, "qpack_encoder_set_dtable_cap",
+ test_nghttp3_qpack_encoder_set_dtable_cap) ||
+ !CU_add_test(pSuite, "qpack_decoder_feedback",
+ test_nghttp3_qpack_decoder_feedback) ||
+ !CU_add_test(pSuite, "qpack_decoder_stream_overflow",
+ test_nghttp3_qpack_decoder_stream_overflow) ||
+ !CU_add_test(pSuite, "qpack_huffman", test_nghttp3_qpack_huffman) ||
+ !CU_add_test(pSuite, "qpack_huffman_decode_failure_state",
+ test_nghttp3_qpack_huffman_decode_failure_state) ||
+ !CU_add_test(pSuite, "qpack_decoder_reconstruct_ricnt",
+ test_nghttp3_qpack_decoder_reconstruct_ricnt) ||
+ !CU_add_test(pSuite, "conn_read_control",
+ test_nghttp3_conn_read_control) ||
+ !CU_add_test(pSuite, "conn_write_control",
+ test_nghttp3_conn_write_control) ||
+ !CU_add_test(pSuite, "conn_submit_request",
+ test_nghttp3_conn_submit_request) ||
+ !CU_add_test(pSuite, "conn_http_request",
+ test_nghttp3_conn_http_request) ||
+ !CU_add_test(pSuite, "conn_http_resp_header",
+ test_nghttp3_conn_http_resp_header) ||
+ !CU_add_test(pSuite, "conn_http_req_header",
+ test_nghttp3_conn_http_req_header) ||
+ !CU_add_test(pSuite, "conn_http_content_length",
+ test_nghttp3_conn_http_content_length) ||
+ !CU_add_test(pSuite, "conn_http_content_length_mismatch",
+ test_nghttp3_conn_http_content_length_mismatch) ||
+ !CU_add_test(pSuite, "conn_http_non_final_response",
+ test_nghttp3_conn_http_non_final_response) ||
+ !CU_add_test(pSuite, "conn_http_trailers",
+ test_nghttp3_conn_http_trailers) ||
+ !CU_add_test(pSuite, "conn_http_ignore_content_length",
+ test_nghttp3_conn_http_ignore_content_length) ||
+ !CU_add_test(pSuite, "conn_http_record_request_method",
+ test_nghttp3_conn_http_record_request_method) ||
+ !CU_add_test(pSuite, "conn_http_error", test_nghttp3_conn_http_error) ||
+ !CU_add_test(pSuite, "conn_qpack_blocked_stream",
+ test_nghttp3_conn_qpack_blocked_stream) ||
+ !CU_add_test(pSuite, "conn_submit_response_read_blocked",
+ test_nghttp3_conn_submit_response_read_blocked) ||
+ !CU_add_test(pSuite, "conn_just_fin", test_nghttp3_conn_just_fin) ||
+ !CU_add_test(pSuite, "conn_recv_uni", test_nghttp3_conn_recv_uni) ||
+ !CU_add_test(pSuite, "conn_recv_goaway", test_nghttp3_conn_recv_goaway) ||
+ !CU_add_test(pSuite, "conn_shutdown_server",
+ test_nghttp3_conn_shutdown_server) ||
+ !CU_add_test(pSuite, "conn_shutdown_client",
+ test_nghttp3_conn_shutdown_client) ||
+ !CU_add_test(pSuite, "conn_priority_update",
+ test_nghttp3_conn_priority_update) ||
+ !CU_add_test(pSuite, "conn_request_priority",
+ test_nghttp3_conn_request_priority) ||
+ !CU_add_test(pSuite, "conn_set_stream_priority",
+ test_nghttp3_conn_set_stream_priority) ||
+ !CU_add_test(pSuite, "conn_shutdown_stream_read",
+ test_nghttp3_conn_shutdown_stream_read) ||
+ !CU_add_test(pSuite, "conn_stream_data_overflow",
+ test_nghttp3_conn_stream_data_overflow) ||
+ !CU_add_test(pSuite, "tnode_schedule", test_nghttp3_tnode_schedule) ||
+ !CU_add_test(pSuite, "http_parse_priority",
+ test_nghttp3_http_parse_priority) ||
+ !CU_add_test(pSuite, "sf_parse_item", test_nghttp3_sf_parse_item) ||
+ !CU_add_test(pSuite, "sf_parse_inner_list",
+ test_nghttp3_sf_parse_inner_list) ||
+ !CU_add_test(pSuite, "check_header_value",
+ test_nghttp3_check_header_value) ||
+ !CU_add_test(pSuite, "pri_to_uint8", test_nghttp3_pri_to_uint8)) {
+ CU_cleanup_registry();
+ return (int)CU_get_error();
+ }
+
+ /* Run all tests using the CUnit Basic interface */
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_tests_failed = CU_get_number_of_tests_failed();
+ CU_cleanup_registry();
+ if (CU_get_error() == CUE_SUCCESS) {
+ return (int)num_tests_failed;
+ } else {
+ printf("CUnit Error: %s\n", CU_get_error_msg());
+ return (int)CU_get_error();
+ }
+}
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 */
+}
diff --git a/tests/nghttp3_conn_test.h b/tests/nghttp3_conn_test.h
new file mode 100644
index 0000000..f17327a
--- /dev/null
+++ b/tests/nghttp3_conn_test.h
@@ -0,0 +1,58 @@
+/*
+ * 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.
+ */
+#ifndef NGTCP2_CONN_TEST_H
+#define NGTCP2_CONN_TEST_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+void test_nghttp3_conn_read_control(void);
+void test_nghttp3_conn_write_control(void);
+void test_nghttp3_conn_submit_request(void);
+void test_nghttp3_conn_http_request(void);
+void test_nghttp3_conn_http_resp_header(void);
+void test_nghttp3_conn_http_req_header(void);
+void test_nghttp3_conn_http_content_length(void);
+void test_nghttp3_conn_http_content_length_mismatch(void);
+void test_nghttp3_conn_http_non_final_response(void);
+void test_nghttp3_conn_http_trailers(void);
+void test_nghttp3_conn_http_ignore_content_length(void);
+void test_nghttp3_conn_http_record_request_method(void);
+void test_nghttp3_conn_http_error(void);
+void test_nghttp3_conn_qpack_blocked_stream(void);
+void test_nghttp3_conn_just_fin(void);
+void test_nghttp3_conn_submit_response_read_blocked(void);
+void test_nghttp3_conn_recv_uni(void);
+void test_nghttp3_conn_recv_goaway(void);
+void test_nghttp3_conn_shutdown_server(void);
+void test_nghttp3_conn_shutdown_client(void);
+void test_nghttp3_conn_priority_update(void);
+void test_nghttp3_conn_request_priority(void);
+void test_nghttp3_conn_set_stream_priority(void);
+void test_nghttp3_conn_shutdown_stream_read(void);
+void test_nghttp3_conn_stream_data_overflow(void);
+
+#endif /* NGTCP2_CONN_TEST_H */
diff --git a/tests/nghttp3_conv_test.c b/tests/nghttp3_conv_test.c
new file mode 100644
index 0000000..6db014b
--- /dev/null
+++ b/tests/nghttp3_conv_test.c
@@ -0,0 +1,51 @@
+/*
+ * nghttp3
+ *
+ * Copyright (c) 2020 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_conv_test.h"
+
+#include <assert.h>
+
+#include <CUnit/CUnit.h>
+
+#include "nghttp3_conv.h"
+#include "nghttp3_test_helper.h"
+
+void test_nghttp3_pri_to_uint8(void) {
+ {
+ nghttp3_pri pri = {1, 0};
+ CU_ASSERT(1 == nghttp3_pri_to_uint8(&pri));
+ }
+ {
+ nghttp3_pri pri = {1, 1};
+ CU_ASSERT((0x80 | 1) == nghttp3_pri_to_uint8(&pri));
+ }
+ {
+ nghttp3_pri pri = {7, 1};
+ CU_ASSERT((0x80 | 7) == nghttp3_pri_to_uint8(&pri));
+ }
+ {
+ nghttp3_pri pri = {7, 0};
+ CU_ASSERT(7 == nghttp3_pri_to_uint8(&pri));
+ }
+}
diff --git a/tests/nghttp3_conv_test.h b/tests/nghttp3_conv_test.h
new file mode 100644
index 0000000..c1274c0
--- /dev/null
+++ b/tests/nghttp3_conv_test.h
@@ -0,0 +1,34 @@
+/*
+ * nghttp3
+ *
+ * Copyright (c) 2020 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.
+ */
+#ifndef NGTCP2_CONV_TEST_H
+#define NGTCP2_CONV_TEST_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+void test_nghttp3_pri_to_uint8(void);
+
+#endif /* NGTCP2_CONV_TEST_H */
diff --git a/tests/nghttp3_http_test.c b/tests/nghttp3_http_test.c
new file mode 100644
index 0000000..89aa2e8
--- /dev/null
+++ b/tests/nghttp3_http_test.c
@@ -0,0 +1,676 @@
+/*
+ * nghttp3
+ *
+ * Copyright (c) 2020 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_http_test.h"
+
+#include <assert.h>
+
+#include <CUnit/CUnit.h>
+
+#include "nghttp3_http.h"
+#include "nghttp3_macro.h"
+#include "nghttp3_test_helper.h"
+
+void test_nghttp3_http_parse_priority(void) {
+ int rv;
+
+ {
+ nghttp3_pri pri = {(uint32_t)-1, -1};
+ const uint8_t v[] = "";
+
+ rv = nghttp3_http_parse_priority(&pri, v, sizeof(v) - 1);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT((uint32_t)-1 == pri.urgency);
+ CU_ASSERT(-1 == pri.inc);
+ }
+
+ {
+ nghttp3_pri pri = {(uint32_t)-1, -1};
+ const uint8_t v[] = "u=7,i";
+
+ rv = nghttp3_http_parse_priority(&pri, v, sizeof(v) - 1);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT((uint32_t)7 == pri.urgency);
+ CU_ASSERT(1 == pri.inc);
+ }
+
+ {
+ nghttp3_pri pri = {(uint32_t)-1, -1};
+ const uint8_t v[] = "u=0,i=?0";
+
+ rv = nghttp3_http_parse_priority(&pri, v, sizeof(v) - 1);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT((uint32_t)0 == pri.urgency);
+ CU_ASSERT(0 == pri.inc);
+ }
+
+ {
+ nghttp3_pri pri = {(uint32_t)-1, -1};
+ const uint8_t v[] = "u=3, i";
+
+ rv = nghttp3_http_parse_priority(&pri, v, sizeof(v) - 1);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT((uint32_t)3 == pri.urgency);
+ CU_ASSERT(1 == pri.inc);
+ }
+
+ {
+ nghttp3_pri pri = {(uint32_t)-1, -1};
+ const uint8_t v[] = "u=0, i, i=?0, u=6";
+
+ rv = nghttp3_http_parse_priority(&pri, v, sizeof(v) - 1);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT((uint32_t)6 == pri.urgency);
+ CU_ASSERT(0 == pri.inc);
+ }
+
+ {
+ nghttp3_pri pri = {(uint32_t)-1, -1};
+ const uint8_t v[] = "u=0,";
+
+ rv = nghttp3_http_parse_priority(&pri, v, sizeof(v) - 1);
+
+ CU_ASSERT(NGHTTP3_ERR_INVALID_ARGUMENT == rv);
+ }
+
+ {
+ nghttp3_pri pri = {(uint32_t)-1, -1};
+ const uint8_t v[] = "u=0, ";
+
+ rv = nghttp3_http_parse_priority(&pri, v, sizeof(v) - 1);
+
+ CU_ASSERT(NGHTTP3_ERR_INVALID_ARGUMENT == rv);
+ }
+
+ {
+ nghttp3_pri pri = {(uint32_t)-1, -1};
+ const uint8_t v[] = "u=";
+
+ rv = nghttp3_http_parse_priority(&pri, v, sizeof(v) - 1);
+
+ CU_ASSERT(NGHTTP3_ERR_INVALID_ARGUMENT == rv);
+ }
+
+ {
+ nghttp3_pri pri = {(uint32_t)-1, -1};
+ const uint8_t v[] = "u";
+
+ rv = nghttp3_http_parse_priority(&pri, v, sizeof(v) - 1);
+
+ CU_ASSERT(NGHTTP3_ERR_INVALID_ARGUMENT == rv);
+ }
+
+ {
+ nghttp3_pri pri = {(uint32_t)-1, -1};
+ const uint8_t v[] = "i=?1";
+
+ rv = nghttp3_http_parse_priority(&pri, v, sizeof(v) - 1);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT((uint32_t)-1 == pri.urgency);
+ CU_ASSERT(1 == pri.inc);
+ }
+
+ {
+ nghttp3_pri pri = {(uint32_t)-1, -1};
+ const uint8_t v[] = "i=?2";
+
+ rv = nghttp3_http_parse_priority(&pri, v, sizeof(v) - 1);
+
+ CU_ASSERT(NGHTTP3_ERR_INVALID_ARGUMENT == rv);
+ }
+
+ {
+ nghttp3_pri pri = {(uint32_t)-1, -1};
+ const uint8_t v[] = "i=?";
+
+ rv = nghttp3_http_parse_priority(&pri, v, sizeof(v) - 1);
+
+ CU_ASSERT(NGHTTP3_ERR_INVALID_ARGUMENT == rv);
+ }
+
+ {
+ nghttp3_pri pri = {(uint32_t)-1, -1};
+ const uint8_t v[] = "i=";
+
+ rv = nghttp3_http_parse_priority(&pri, v, sizeof(v) - 1);
+
+ CU_ASSERT(NGHTTP3_ERR_INVALID_ARGUMENT == rv);
+ }
+
+ {
+ nghttp3_pri pri = {(uint32_t)-1, -1};
+ const uint8_t v[] = "u=-1";
+
+ rv = nghttp3_http_parse_priority(&pri, v, sizeof(v) - 1);
+
+ CU_ASSERT(NGHTTP3_ERR_INVALID_ARGUMENT == rv);
+ }
+
+ {
+ nghttp3_pri pri = {(uint32_t)-1, -1};
+ const uint8_t v[] = "u=8";
+
+ rv = nghttp3_http_parse_priority(&pri, v, sizeof(v) - 1);
+
+ CU_ASSERT(NGHTTP3_ERR_INVALID_ARGUMENT == rv);
+ }
+
+ {
+ nghttp3_pri pri = {(uint32_t)-1, -1};
+ const uint8_t v[] =
+ "i=?0, u=1, a=(x y z), u=2; i=?0;foo=\",,,\", i=?1;i=?0; u=6";
+
+ rv = nghttp3_http_parse_priority(&pri, v, sizeof(v) - 1);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT((uint32_t)2 == pri.urgency);
+ CU_ASSERT(1 == pri.inc);
+ }
+
+ {
+ nghttp3_pri pri = {(uint32_t)-1, -1};
+ const uint8_t v[] = {'u', '='};
+
+ rv = nghttp3_http_parse_priority(&pri, v, sizeof(v));
+
+ CU_ASSERT(NGHTTP3_ERR_INVALID_ARGUMENT == rv);
+ }
+}
+
+void test_nghttp3_sf_parse_item(void) {
+ {
+ nghttp3_sf_value val;
+ const uint8_t s[] = "?1";
+ val.type = 0xff;
+
+ CU_ASSERT(2 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1));
+ CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_BOOLEAN == val.type);
+ CU_ASSERT(1 == val.b);
+ }
+
+ {
+ nghttp3_sf_value val;
+ const uint8_t s[] = "?1 ";
+ val.type = 0xff;
+
+ CU_ASSERT(2 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1));
+ CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_BOOLEAN == val.type);
+ CU_ASSERT(1 == val.b);
+ }
+
+ {
+ nghttp3_sf_value val;
+ const uint8_t s[] = "?1;foo=bar";
+ val.type = 0xff;
+
+ CU_ASSERT(10 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1));
+ CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_BOOLEAN == val.type);
+ CU_ASSERT(1 == val.b);
+ }
+
+ {
+ const uint8_t s[] = {'?', '1', ';', 'f', 'o', 'o', '='};
+
+ CU_ASSERT(-1 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s)));
+ }
+
+ {
+ nghttp3_sf_value val;
+ const uint8_t s[] = "?0";
+ val.type = 0xff;
+
+ CU_ASSERT(2 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1));
+ CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_BOOLEAN == val.type);
+ CU_ASSERT(0 == val.b);
+ }
+
+ {
+ nghttp3_sf_value val;
+ const uint8_t s[] = "?0 ";
+ val.type = 0xff;
+
+ CU_ASSERT(2 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1));
+ CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_BOOLEAN == val.type);
+ CU_ASSERT(0 == val.b);
+ }
+
+ {
+ const uint8_t s[] = "?2";
+
+ CU_ASSERT(-1 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1));
+ }
+
+ {
+ const uint8_t s[] = "?";
+
+ CU_ASSERT(-1 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1));
+ }
+
+ {
+ const uint8_t s[] = "?1";
+
+ CU_ASSERT(2 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1));
+ }
+
+ {
+ nghttp3_sf_value val;
+ const uint8_t s[] = ":cHJldGVuZCB0aGlzIGlzIGJpbmFyeSBjb250ZW50Lg==:";
+ val.type = 0xff;
+
+ CU_ASSERT(46 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1));
+ CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_BYTESEQ == val.type);
+ CU_ASSERT(44 == val.s.len);
+ CU_ASSERT(0 == memcmp("cHJldGVuZCB0aGlzIGlzIGJpbmFyeSBjb250ZW50Lg==",
+ val.s.base, val.s.len));
+ }
+
+ {
+ nghttp3_sf_value val;
+ const uint8_t s[] = ":cHJldGVuZCB0aGlzIGlzIGJpbmFyeSBjb250ZW50Lg==: ";
+ val.type = 0xff;
+
+ CU_ASSERT(46 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1));
+ CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_BYTESEQ == val.type);
+ CU_ASSERT(44 == val.s.len);
+ CU_ASSERT(0 == memcmp("cHJldGVuZCB0aGlzIGlzIGJpbmFyeSBjb250ZW50Lg==",
+ val.s.base, val.s.len));
+ }
+
+ {
+ nghttp3_sf_value val;
+ const uint8_t s[] = "::";
+ val.type = 0xff;
+
+ CU_ASSERT(2 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1));
+ CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_BYTESEQ == val.type);
+ CU_ASSERT(0 == val.s.len);
+ }
+
+ {
+ const uint8_t s[] = ":cHJldGVuZCB0aGlzIGlzIGJpbmFyeSBjb250ZW50Lg==";
+
+ CU_ASSERT(-1 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1));
+ }
+
+ {
+ const uint8_t s[] = ":";
+
+ CU_ASSERT(-1 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1));
+ }
+
+ {
+ const uint8_t s[] = ":@:";
+
+ CU_ASSERT(-1 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1));
+ }
+
+ {
+ const uint8_t s[] = ":foo:";
+
+ CU_ASSERT(5 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1));
+ }
+
+ {
+ nghttp3_sf_value val;
+ const uint8_t s[] =
+ ":abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=:";
+ val.type = 0xff;
+
+ CU_ASSERT(67 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1));
+ CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_BYTESEQ == val.type);
+ CU_ASSERT(65 == val.s.len);
+ CU_ASSERT(
+ 0 ==
+ memcmp(
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=",
+ val.s.base, val.s.len));
+ }
+
+ {
+ nghttp3_sf_value val;
+ const uint8_t s[] = "foo123/456";
+ val.type = 0xff;
+
+ CU_ASSERT(10 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1));
+ CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_TOKEN == val.type);
+ CU_ASSERT(10 == val.s.len);
+ CU_ASSERT(0 == memcmp(s, val.s.base, val.s.len));
+ }
+
+ {
+ nghttp3_sf_value val;
+ const uint8_t s[] = "foo123/456 ";
+ val.type = 0xff;
+
+ CU_ASSERT(10 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1));
+ CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_TOKEN == val.type);
+ CU_ASSERT(10 == val.s.len);
+ CU_ASSERT(0 == memcmp(s, val.s.base, val.s.len));
+ }
+
+ {
+ nghttp3_sf_value val;
+ const uint8_t s[] = "*";
+ val.type = 0xff;
+
+ CU_ASSERT(1 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1));
+ CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_TOKEN == val.type);
+ CU_ASSERT(1 == val.s.len);
+ CU_ASSERT(0 == memcmp(s, val.s.base, val.s.len));
+ }
+
+ {
+ const uint8_t s[] = "*";
+
+ CU_ASSERT(1 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1));
+ }
+
+ {
+ nghttp3_sf_value val;
+ const uint8_t s[] = "\"hello world\"";
+ val.type = 0xff;
+
+ CU_ASSERT(13 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1));
+ CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_STRING == val.type);
+ CU_ASSERT(11 == val.s.len);
+ CU_ASSERT(0 == memcmp("hello world", val.s.base, val.s.len));
+ }
+
+ {
+ nghttp3_sf_value val;
+ const uint8_t s[] = "\"hello world\" ";
+ val.type = 0xff;
+
+ CU_ASSERT(13 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1));
+ CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_STRING == val.type);
+ CU_ASSERT(11 == val.s.len);
+ CU_ASSERT(0 == memcmp("hello world", val.s.base, val.s.len));
+ }
+
+ {
+ nghttp3_sf_value val;
+ const uint8_t s[] = "\"foo\\\"\\\\\"";
+ val.type = 0xff;
+
+ CU_ASSERT(9 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1));
+ CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_STRING == val.type);
+ CU_ASSERT(7 == val.s.len);
+ CU_ASSERT(0 == memcmp("foo\\\"\\\\", val.s.base, val.s.len));
+ }
+
+ {
+ const uint8_t s[] = "\"foo\\x\"";
+
+ CU_ASSERT(-1 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1));
+ }
+
+ {
+ const uint8_t s[] = "\"foo";
+
+ CU_ASSERT(-1 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1));
+ }
+
+ {
+ const uint8_t s[] = "\"\x7f\"";
+
+ CU_ASSERT(-1 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1));
+ }
+
+ {
+ const uint8_t s[] = "\"\x1f\"";
+
+ CU_ASSERT(-1 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1));
+ }
+
+ {
+ const uint8_t s[] = "\"foo\"";
+
+ CU_ASSERT(5 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1));
+ }
+
+ {
+ nghttp3_sf_value val;
+ const uint8_t s[] = "4.5";
+ val.type = NGHTTP3_SF_VALUE_TYPE_DECIMAL;
+
+ CU_ASSERT(3 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1));
+ CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_DECIMAL == val.type);
+ CU_ASSERT(fabs(4.5 - val.d) < 1e-9);
+ }
+
+ {
+ nghttp3_sf_value val;
+ const uint8_t s[] = "4.5 ";
+ val.type = NGHTTP3_SF_VALUE_TYPE_DECIMAL;
+
+ CU_ASSERT(3 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1));
+ CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_DECIMAL == val.type);
+ CU_ASSERT(fabs(4.5 - val.d) < 1e-9);
+ }
+
+ {
+ nghttp3_sf_value val;
+ const uint8_t s[] = "-4.5";
+ val.type = NGHTTP3_SF_VALUE_TYPE_DECIMAL;
+
+ CU_ASSERT(4 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1));
+ CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_DECIMAL == val.type);
+ CU_ASSERT(fabs(-4.5 - val.d) < 1e-9);
+ }
+
+ {
+ const uint8_t s[] = "4.5";
+
+ CU_ASSERT(3 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1));
+ }
+
+ {
+ nghttp3_sf_value val;
+ const uint8_t s[] = "123456789012.123";
+ val.type = NGHTTP3_SF_VALUE_TYPE_DECIMAL;
+
+ CU_ASSERT(16 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1));
+ CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_DECIMAL == val.type);
+ CU_ASSERT(fabs(123456789012.123 - val.d) < 1e-9);
+ }
+
+ {
+ const uint8_t s[] = "1123456789012.123";
+
+ CU_ASSERT(-1 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1));
+ }
+
+ {
+ const uint8_t s[] = "123456789012.1234";
+
+ CU_ASSERT(-1 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1));
+ }
+
+ {
+ const uint8_t s[] = "1.";
+
+ CU_ASSERT(-1 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1));
+ }
+
+ {
+ nghttp3_sf_value val;
+ const uint8_t s[] = "123456789012345";
+ val.type = NGHTTP3_SF_VALUE_TYPE_DECIMAL;
+
+ CU_ASSERT(15 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1));
+ CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_INTEGER == val.type);
+ CU_ASSERT(123456789012345 == val.i);
+ }
+
+ {
+ nghttp3_sf_value val;
+ const uint8_t s[] = "1 ";
+ val.type = NGHTTP3_SF_VALUE_TYPE_DECIMAL;
+
+ CU_ASSERT(1 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1));
+ CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_INTEGER == val.type);
+ CU_ASSERT(1 == val.i);
+ }
+
+ {
+ const uint8_t s[] = "1";
+
+ CU_ASSERT(1 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1));
+ }
+
+ {
+ const uint8_t s[] = "1234567890123456";
+
+ CU_ASSERT(-1 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1));
+ }
+
+ {
+ nghttp3_sf_value val;
+ const uint8_t s[] = "\"foo\";a; b=\"bar\";c=1.3;d=9;e=baz;f=:aaa:";
+ val.type = 0xff;
+
+ CU_ASSERT(41 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1));
+ CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_STRING == val.type);
+ CU_ASSERT(0 == memcmp("foo", val.s.base, val.s.len));
+ }
+
+ {
+ const uint8_t s[] = "\"foo\";a; b=\"bar";
+
+ CU_ASSERT(-1 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1));
+ }
+
+ {
+ const uint8_t s[] = "foo;";
+
+ CU_ASSERT(-1 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1));
+ }
+}
+
+void test_nghttp3_sf_parse_inner_list(void) {
+ {
+ nghttp3_sf_value val;
+ const uint8_t s[] = "()";
+ val.type = 0xff;
+
+ CU_ASSERT(2 == nghttp3_sf_parse_inner_list(&val, s, s + sizeof(s) - 1));
+ CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_INNER_LIST == val.type);
+ }
+
+ {
+ nghttp3_sf_value val;
+ const uint8_t s[] = "( )";
+ val.type = 0xff;
+
+ CU_ASSERT(7 == nghttp3_sf_parse_inner_list(&val, s, s + sizeof(s) - 1));
+ CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_INNER_LIST == val.type);
+ }
+
+ {
+ nghttp3_sf_value val;
+ const uint8_t s[] = "(a)";
+ val.type = 0xff;
+
+ CU_ASSERT(3 == nghttp3_sf_parse_inner_list(&val, s, s + sizeof(s) - 1));
+ CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_INNER_LIST == val.type);
+ }
+
+ {
+ nghttp3_sf_value val;
+ const uint8_t s[] = "(a b)";
+ val.type = 0xff;
+
+ CU_ASSERT(5 == nghttp3_sf_parse_inner_list(&val, s, s + sizeof(s) - 1));
+ CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_INNER_LIST == val.type);
+ }
+
+ {
+ nghttp3_sf_value val;
+ const uint8_t s[] = "( a b )";
+ val.type = 0xff;
+
+ CU_ASSERT(10 == nghttp3_sf_parse_inner_list(&val, s, s + sizeof(s) - 1));
+ CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_INNER_LIST == val.type);
+ }
+
+ {
+ nghttp3_sf_value val;
+ const uint8_t s[] = "( a;foo=bar)";
+ val.type = 0xff;
+
+ CU_ASSERT(12 == nghttp3_sf_parse_inner_list(&val, s, s + sizeof(s) - 1));
+ CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_INNER_LIST == val.type);
+ }
+
+ {
+ const uint8_t s[] = "(";
+
+ CU_ASSERT(-1 == nghttp3_sf_parse_inner_list(NULL, s, s + sizeof(s) - 1));
+ }
+
+ {
+ const uint8_t s[] = "(a";
+
+ CU_ASSERT(-1 == nghttp3_sf_parse_inner_list(NULL, s, s + sizeof(s) - 1));
+ }
+
+ {
+ const uint8_t s[] = "(a ";
+
+ CU_ASSERT(-1 == nghttp3_sf_parse_inner_list(NULL, s, s + sizeof(s) - 1));
+ }
+
+ {
+ const uint8_t s[] = "(a;b";
+
+ CU_ASSERT(-1 == nghttp3_sf_parse_inner_list(NULL, s, s + sizeof(s) - 1));
+ }
+}
+
+#define check_header_value(S) \
+ nghttp3_check_header_value((const uint8_t *)S, sizeof(S) - 1)
+
+void test_nghttp3_check_header_value(void) {
+ uint8_t goodval[] = {'a', 'b', 0x80u, 'c', 0xffu, 'd'};
+ uint8_t badval1[] = {'a', 0x1fu, 'b'};
+ uint8_t badval2[] = {'a', 0x7fu, 'b'};
+
+ CU_ASSERT(check_header_value("!|}~"));
+ CU_ASSERT(!check_header_value(" !|}~"));
+ CU_ASSERT(!check_header_value("!|}~ "));
+ CU_ASSERT(!check_header_value("\t!|}~"));
+ CU_ASSERT(!check_header_value("!|}~\t"));
+ CU_ASSERT(check_header_value(goodval));
+ CU_ASSERT(!check_header_value(badval1));
+ CU_ASSERT(!check_header_value(badval2));
+ CU_ASSERT(check_header_value(""));
+ CU_ASSERT(!check_header_value(" "));
+ CU_ASSERT(!check_header_value("\t"));
+}
diff --git a/tests/nghttp3_http_test.h b/tests/nghttp3_http_test.h
new file mode 100644
index 0000000..47f902c
--- /dev/null
+++ b/tests/nghttp3_http_test.h
@@ -0,0 +1,37 @@
+/*
+ * nghttp3
+ *
+ * Copyright (c) 2020 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.
+ */
+#ifndef NGTCP2_HTTP_TEST_H
+#define NGTCP2_HTTP_TEST_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+void test_nghttp3_http_parse_priority(void);
+void test_nghttp3_sf_parse_item(void);
+void test_nghttp3_sf_parse_inner_list(void);
+void test_nghttp3_check_header_value(void);
+
+#endif /* NGTCP2_CONN_TEST_H */
diff --git a/tests/nghttp3_qpack_test.c b/tests/nghttp3_qpack_test.c
new file mode 100644
index 0000000..c8e1983
--- /dev/null
+++ b/tests/nghttp3_qpack_test.c
@@ -0,0 +1,779 @@
+/*
+ * 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_qpack_test.h"
+
+#include <stdlib.h>
+
+#include <CUnit/CUnit.h>
+
+#include "nghttp3_qpack.h"
+#include "nghttp3_macro.h"
+#include "nghttp3_test_helper.h"
+
+static void check_decode_header(nghttp3_qpack_decoder *dec, nghttp3_buf *pbuf,
+ nghttp3_buf *rbuf, nghttp3_buf *ebuf,
+ int64_t stream_id, const nghttp3_nv *nva,
+ size_t nvlen, const nghttp3_mem *mem) {
+ nghttp3_ssize nread;
+ nghttp3_qpack_stream_context sctx;
+ nghttp3_qpack_nv qnv;
+ const nghttp3_nv *nv;
+ uint8_t flags;
+ size_t i = 0;
+
+ nread =
+ nghttp3_qpack_decoder_read_encoder(dec, ebuf->pos, nghttp3_buf_len(ebuf));
+
+ CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(ebuf) == nread);
+
+ nghttp3_qpack_stream_context_init(&sctx, stream_id, mem);
+
+ nread = nghttp3_qpack_decoder_read_request(
+ dec, &sctx, &qnv, &flags, pbuf->pos, nghttp3_buf_len(pbuf), 0);
+
+ CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(pbuf) == nread);
+
+ for (; nghttp3_buf_len(rbuf);) {
+ nread = nghttp3_qpack_decoder_read_request(
+ dec, &sctx, &qnv, &flags, rbuf->pos, nghttp3_buf_len(rbuf), 1);
+
+ CU_ASSERT(nread > 0);
+
+ if (nread < 0) {
+ break;
+ }
+
+ rbuf->pos += nread;
+
+ if (flags & NGHTTP3_QPACK_DECODE_FLAG_FINAL) {
+ break;
+ }
+ if (flags & NGHTTP3_QPACK_DECODE_FLAG_EMIT) {
+ nv = &nva[i++];
+ CU_ASSERT(nv->namelen == qnv.name->len);
+ CU_ASSERT(0 == memcmp(nv->name, qnv.name->base, nv->namelen));
+ CU_ASSERT(nv->valuelen == qnv.value->len);
+ CU_ASSERT(0 == memcmp(nv->value, qnv.value->base, nv->valuelen));
+
+ nghttp3_rcbuf_decref(qnv.name);
+ nghttp3_rcbuf_decref(qnv.value);
+ }
+ }
+
+ CU_ASSERT(i == nvlen);
+
+ nghttp3_qpack_stream_context_free(&sctx);
+ nghttp3_buf_reset(pbuf);
+ nghttp3_buf_reset(rbuf);
+ nghttp3_buf_reset(ebuf);
+}
+
+static void decode_header_block(nghttp3_qpack_decoder *dec, nghttp3_buf *pbuf,
+ nghttp3_buf *rbuf, int64_t stream_id,
+ const nghttp3_mem *mem) {
+ nghttp3_ssize nread;
+ nghttp3_qpack_stream_context sctx;
+ nghttp3_qpack_nv qnv;
+ uint8_t flags;
+
+ nghttp3_qpack_stream_context_init(&sctx, stream_id, mem);
+
+ nread = nghttp3_qpack_decoder_read_request(
+ dec, &sctx, &qnv, &flags, pbuf->pos, nghttp3_buf_len(pbuf), 0);
+
+ CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(pbuf) == nread);
+
+ for (;;) {
+ nread = nghttp3_qpack_decoder_read_request(
+ dec, &sctx, &qnv, &flags, rbuf->pos, nghttp3_buf_len(rbuf), 1);
+
+ CU_ASSERT(nread >= 0);
+
+ if (nread < 0) {
+ break;
+ }
+
+ if (flags & NGHTTP3_QPACK_DECODE_FLAG_FINAL) {
+ CU_ASSERT(nread == 0);
+ CU_ASSERT(!(flags & NGHTTP3_QPACK_DECODE_FLAG_EMIT));
+ CU_ASSERT(0 == nghttp3_buf_len(rbuf));
+ CU_ASSERT(nghttp3_buf_len(&dec->dbuf) > 0);
+
+ break;
+ }
+
+ CU_ASSERT(nread > 0);
+ CU_ASSERT(flags & NGHTTP3_QPACK_DECODE_FLAG_EMIT);
+
+ nghttp3_rcbuf_decref(qnv.name);
+ nghttp3_rcbuf_decref(qnv.value);
+
+ rbuf->pos += nread;
+ }
+
+ nghttp3_qpack_stream_context_free(&sctx);
+}
+
+void test_nghttp3_qpack_encoder_encode(void) {
+ const nghttp3_mem *mem = nghttp3_mem_default();
+ nghttp3_qpack_encoder enc;
+ nghttp3_qpack_decoder dec;
+ nghttp3_nv nva[] = {
+ MAKE_NV(":path", "/rsrc.php/v3/yn/r/rIPZ9Qkrdd9.png"),
+ MAKE_NV(":authority", "static.xx.fbcdn.net"),
+ MAKE_NV(":scheme", "https"),
+ MAKE_NV(":method", "GET"),
+ MAKE_NV("accept-encoding", "gzip, deflate, br"),
+ MAKE_NV("accept-language", "en-US,en;q=0.9"),
+ MAKE_NV(
+ "user-agent",
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64)AppleWebKit/537.36(KHTML, "
+ "like Gecko) Chrome/63.0.3239.70 Safari/537.36"),
+ MAKE_NV("accept", "image/webp,image/apng,image/*,*/*;q=0.8"),
+ MAKE_NV("referer", "https://static.xx.fbcdn.net/rsrc.php/v3/yT/l/0,cross/"
+ "dzXGESIlGQQ.css"),
+ };
+ int rv;
+ nghttp3_buf pbuf, rbuf, ebuf;
+ nghttp3_qpack_stream *stream;
+ nghttp3_qpack_header_block_ref *ref;
+
+ nghttp3_buf_init(&pbuf);
+ nghttp3_buf_init(&rbuf);
+ nghttp3_buf_init(&ebuf);
+ rv = nghttp3_qpack_encoder_init(&enc, 4096, mem);
+
+ CU_ASSERT(0 == rv);
+
+ nghttp3_qpack_encoder_set_max_blocked_streams(&enc, 1);
+
+ nghttp3_qpack_encoder_set_max_dtable_capacity(&enc, 4096);
+
+ rv = nghttp3_qpack_decoder_init(&dec, 4096, 1, mem);
+
+ CU_ASSERT(0 == rv);
+
+ rv = nghttp3_qpack_encoder_encode(&enc, &pbuf, &rbuf, &ebuf, 0, nva,
+ nghttp3_arraylen(nva));
+
+ CU_ASSERT(0 == rv);
+
+ stream = nghttp3_qpack_encoder_find_stream(&enc, 0);
+
+ CU_ASSERT(NULL != stream);
+ CU_ASSERT(nghttp3_qpack_encoder_stream_is_blocked(&enc, stream));
+ CU_ASSERT(1 == nghttp3_qpack_encoder_get_num_blocked_streams(&enc));
+
+ ref =
+ *(nghttp3_qpack_header_block_ref **)nghttp3_ringbuf_get(&stream->refs, 0);
+
+ CU_ASSERT(5 == ref->max_cnt);
+ CU_ASSERT(1 == ref->min_cnt);
+ CU_ASSERT(5 == nghttp3_qpack_stream_get_max_cnt(stream));
+ CU_ASSERT(1 == nghttp3_qpack_encoder_get_min_cnt(&enc));
+ CU_ASSERT(2 == nghttp3_buf_len(&pbuf));
+
+ check_decode_header(&dec, &pbuf, &rbuf, &ebuf, 0, nva, nghttp3_arraylen(nva),
+ mem);
+
+ rv = nghttp3_qpack_encoder_encode(&enc, &pbuf, &rbuf, &ebuf, 4, nva,
+ nghttp3_arraylen(nva));
+
+ CU_ASSERT(0 == rv);
+
+ stream = nghttp3_qpack_encoder_find_stream(&enc, 4);
+
+ CU_ASSERT(NULL == stream);
+
+ check_decode_header(&dec, &pbuf, &rbuf, &ebuf, 4, nva, nghttp3_arraylen(nva),
+ mem);
+
+ nghttp3_qpack_encoder_ack_header(&enc, 0);
+
+ CU_ASSERT(5 == enc.krcnt);
+
+ rv = nghttp3_qpack_encoder_encode(&enc, &pbuf, &rbuf, &ebuf, 8, nva,
+ nghttp3_arraylen(nva));
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(0 == nghttp3_qpack_encoder_get_num_blocked_streams(&enc));
+
+ check_decode_header(&dec, &pbuf, &rbuf, &ebuf, 8, nva, nghttp3_arraylen(nva),
+ mem);
+
+ nghttp3_qpack_decoder_free(&dec);
+ nghttp3_qpack_encoder_free(&enc);
+ nghttp3_buf_free(&ebuf, mem);
+ nghttp3_buf_free(&rbuf, mem);
+ nghttp3_buf_free(&pbuf, mem);
+}
+
+void test_nghttp3_qpack_encoder_still_blocked(void) {
+ const nghttp3_mem *mem = nghttp3_mem_default();
+ nghttp3_qpack_encoder enc;
+ nghttp3_nv nva1[] = {
+ MAKE_NV(":status", "103"),
+ MAKE_NV("link", "foo"),
+ };
+ nghttp3_nv nva2[] = {
+ MAKE_NV(":status", "200"),
+ MAKE_NV("content-type", "text/foo"),
+ };
+ int rv;
+ nghttp3_buf pbuf, rbuf, ebuf;
+ nghttp3_qpack_stream *stream;
+ nghttp3_qpack_header_block_ref *ref;
+
+ nghttp3_buf_init(&pbuf);
+ nghttp3_buf_init(&rbuf);
+ nghttp3_buf_init(&ebuf);
+ rv = nghttp3_qpack_encoder_init(&enc, 4096, mem);
+
+ CU_ASSERT(0 == rv);
+
+ nghttp3_qpack_encoder_set_max_blocked_streams(&enc, 1);
+
+ nghttp3_qpack_encoder_set_max_dtable_capacity(&enc, 4096);
+
+ rv = nghttp3_qpack_encoder_encode(&enc, &pbuf, &rbuf, &ebuf, 0, nva1,
+ nghttp3_arraylen(nva1));
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(1 == nghttp3_qpack_encoder_get_num_blocked_streams(&enc));
+
+ rv = nghttp3_qpack_encoder_encode(&enc, &pbuf, &rbuf, &ebuf, 0, nva2,
+ nghttp3_arraylen(nva2));
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(1 == nghttp3_qpack_encoder_get_num_blocked_streams(&enc));
+
+ stream = nghttp3_qpack_encoder_find_stream(&enc, 0);
+
+ ref =
+ *(nghttp3_qpack_header_block_ref **)nghttp3_ringbuf_get(&stream->refs, 0);
+
+ CU_ASSERT(nghttp3_ringbuf_len(&stream->refs) > 1);
+ CU_ASSERT(ref->max_cnt != nghttp3_qpack_stream_get_max_cnt(stream));
+
+ ref =
+ *(nghttp3_qpack_header_block_ref **)nghttp3_ringbuf_get(&stream->refs, 1);
+
+ CU_ASSERT(ref->max_cnt == nghttp3_qpack_stream_get_max_cnt(stream));
+
+ nghttp3_qpack_encoder_ack_header(&enc, 0);
+
+ CU_ASSERT(1 == nghttp3_qpack_encoder_get_num_blocked_streams(&enc));
+
+ stream = nghttp3_qpack_encoder_find_stream(&enc, 0);
+
+ ref =
+ *(nghttp3_qpack_header_block_ref **)nghttp3_ringbuf_get(&stream->refs, 0);
+
+ CU_ASSERT(1 == nghttp3_ringbuf_len(&stream->refs));
+ CU_ASSERT(ref->max_cnt == nghttp3_qpack_stream_get_max_cnt(stream));
+
+ nghttp3_qpack_encoder_ack_header(&enc, 0);
+
+ CU_ASSERT(0 == nghttp3_qpack_encoder_get_num_blocked_streams(&enc));
+ CU_ASSERT(NULL == nghttp3_qpack_encoder_find_stream(&enc, 0));
+
+ nghttp3_qpack_encoder_free(&enc);
+ nghttp3_buf_free(&ebuf, mem);
+ nghttp3_buf_free(&rbuf, mem);
+ nghttp3_buf_free(&pbuf, mem);
+}
+
+void test_nghttp3_qpack_encoder_set_dtable_cap(void) {
+ const nghttp3_mem *mem = nghttp3_mem_default();
+ nghttp3_qpack_encoder enc;
+ nghttp3_qpack_decoder dec;
+ nghttp3_buf pbuf, rbuf, ebuf;
+ const nghttp3_nv nva1[] = {
+ MAKE_NV(":path", "/"),
+ MAKE_NV("date", "bar1"),
+ };
+ const nghttp3_nv nva2[] = {
+ MAKE_NV(":path", "/"),
+ MAKE_NV("vary", "bar2"),
+ };
+ int rv;
+ nghttp3_ssize nread;
+
+ nghttp3_buf_init(&pbuf);
+ nghttp3_buf_init(&rbuf);
+ nghttp3_buf_init(&ebuf);
+
+ rv = nghttp3_qpack_encoder_init(&enc, 4096, mem);
+
+ CU_ASSERT(0 == rv);
+
+ nghttp3_qpack_encoder_set_max_blocked_streams(&enc, 3);
+
+ nghttp3_qpack_encoder_set_max_dtable_capacity(&enc, 4096);
+
+ rv = nghttp3_qpack_decoder_init(&dec, 4096, 3, mem);
+
+ CU_ASSERT(0 == rv);
+
+ rv = nghttp3_qpack_encoder_encode(&enc, &pbuf, &rbuf, &ebuf, 0, nva1,
+ nghttp3_arraylen(nva1));
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(1 == enc.ctx.next_absidx);
+ CU_ASSERT(strlen("date") + strlen("bar1") + NGHTTP3_QPACK_ENTRY_OVERHEAD ==
+ enc.ctx.dtable_size);
+
+ nread = nghttp3_qpack_decoder_read_encoder(&dec, ebuf.pos,
+ nghttp3_buf_len(&ebuf));
+
+ CU_ASSERT(nread == (nghttp3_ssize)nghttp3_buf_len(&ebuf));
+ CU_ASSERT(1 == dec.ctx.next_absidx);
+ CU_ASSERT(strlen("date") + strlen("bar1") + NGHTTP3_QPACK_ENTRY_OVERHEAD ==
+ dec.ctx.dtable_size);
+ CU_ASSERT(4096 == dec.ctx.max_dtable_capacity);
+
+ decode_header_block(&dec, &pbuf, &rbuf, 0, mem);
+ nghttp3_buf_reset(&pbuf);
+ nghttp3_buf_reset(&rbuf);
+ nghttp3_buf_reset(&ebuf);
+
+ rv = nghttp3_qpack_encoder_encode(&enc, &pbuf, &rbuf, &ebuf, 4, nva2,
+ nghttp3_arraylen(nva2));
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(strlen("date") + strlen("bar1") + NGHTTP3_QPACK_ENTRY_OVERHEAD +
+ strlen("vary") + strlen("bar2") +
+ NGHTTP3_QPACK_ENTRY_OVERHEAD ==
+ enc.ctx.dtable_size);
+ CU_ASSERT(2 == enc.ctx.next_absidx);
+ CU_ASSERT(2 == nghttp3_qpack_encoder_get_num_blocked_streams(&enc));
+
+ nread = nghttp3_qpack_decoder_read_encoder(&dec, ebuf.pos,
+ nghttp3_buf_len(&ebuf));
+
+ CU_ASSERT(nread == (nghttp3_ssize)nghttp3_buf_len(&ebuf));
+ CU_ASSERT(2 == dec.ctx.next_absidx);
+ CU_ASSERT(strlen("date") + strlen("bar1") + NGHTTP3_QPACK_ENTRY_OVERHEAD +
+ strlen("vary") + strlen("bar2") +
+ NGHTTP3_QPACK_ENTRY_OVERHEAD ==
+ dec.ctx.dtable_size);
+ CU_ASSERT(4096 == dec.ctx.max_dtable_capacity);
+
+ decode_header_block(&dec, &pbuf, &rbuf, 4, mem);
+ nghttp3_buf_reset(&pbuf);
+ nghttp3_buf_reset(&rbuf);
+ nghttp3_buf_reset(&ebuf);
+
+ nghttp3_qpack_encoder_set_max_dtable_capacity(&enc, 0);
+
+ CU_ASSERT(0 == enc.ctx.max_dtable_capacity);
+ CU_ASSERT(2 == nghttp3_qpack_encoder_get_num_blocked_streams(&enc));
+
+ /* Cannot index more headers because we set max_dtable_capacity to
+ 0. */
+ rv = nghttp3_qpack_encoder_encode(&enc, &pbuf, &rbuf, &ebuf, 8, nva2,
+ nghttp3_arraylen(nva2));
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(0 == enc.ctx.max_dtable_capacity);
+ CU_ASSERT(2 == enc.ctx.next_absidx);
+ CU_ASSERT(2 == nghttp3_qpack_encoder_get_num_blocked_streams(&enc));
+
+ nread = nghttp3_qpack_decoder_read_encoder(&dec, ebuf.pos,
+ nghttp3_buf_len(&ebuf));
+
+ CU_ASSERT(nread == (nghttp3_ssize)nghttp3_buf_len(&ebuf));
+ CU_ASSERT(2 == dec.ctx.next_absidx);
+ CU_ASSERT(strlen("date") + strlen("bar1") + NGHTTP3_QPACK_ENTRY_OVERHEAD +
+ strlen("vary") + strlen("bar2") +
+ NGHTTP3_QPACK_ENTRY_OVERHEAD ==
+ dec.ctx.dtable_size);
+ CU_ASSERT(4096 == dec.ctx.max_dtable_capacity);
+
+ decode_header_block(&dec, &pbuf, &rbuf, 8, mem);
+ nghttp3_buf_reset(&pbuf);
+ nghttp3_buf_reset(&rbuf);
+ nghttp3_buf_reset(&ebuf);
+
+ /* Acking stream 0 will evict first entry */
+ nghttp3_qpack_encoder_ack_header(&enc, 0);
+
+ rv = nghttp3_qpack_encoder_encode(&enc, &pbuf, &rbuf, &ebuf, 12, nva2,
+ nghttp3_arraylen(nva2));
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(strlen("vary") + strlen("bar2") + NGHTTP3_QPACK_ENTRY_OVERHEAD ==
+ enc.ctx.dtable_size);
+ CU_ASSERT(0 == enc.ctx.max_dtable_capacity);
+ CU_ASSERT(1 == nghttp3_qpack_encoder_get_num_blocked_streams(&enc));
+
+ nread = nghttp3_qpack_decoder_read_encoder(&dec, ebuf.pos,
+ nghttp3_buf_len(&ebuf));
+
+ CU_ASSERT(nread == (nghttp3_ssize)nghttp3_buf_len(&ebuf));
+ /* decoder still has 2 entries because encoder does not emit Set
+ Dynamic Table Capacity. */
+ CU_ASSERT(2 == dec.ctx.next_absidx);
+ CU_ASSERT(strlen("date") + strlen("bar1") + NGHTTP3_QPACK_ENTRY_OVERHEAD +
+ strlen("vary") + strlen("bar2") +
+ NGHTTP3_QPACK_ENTRY_OVERHEAD ==
+ dec.ctx.dtable_size);
+ CU_ASSERT(4096 == dec.ctx.max_dtable_capacity);
+
+ decode_header_block(&dec, &pbuf, &rbuf, 12, mem);
+ nghttp3_buf_reset(&pbuf);
+ nghttp3_buf_reset(&rbuf);
+ nghttp3_buf_reset(&ebuf);
+
+ /* Acking stream 4 will evict another entry */
+ nghttp3_qpack_encoder_ack_header(&enc, 4);
+
+ rv = nghttp3_qpack_encoder_encode(&enc, &pbuf, &rbuf, &ebuf, 16, nva2,
+ nghttp3_arraylen(nva2));
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(0 == enc.ctx.dtable_size);
+ CU_ASSERT(0 == enc.ctx.max_dtable_capacity);
+ CU_ASSERT(0 == enc.last_max_dtable_update);
+ CU_ASSERT(SIZE_MAX == enc.min_dtable_update);
+ CU_ASSERT(0 == nghttp3_qpack_encoder_get_num_blocked_streams(&enc));
+
+ nread = nghttp3_qpack_decoder_read_encoder(&dec, ebuf.pos,
+ nghttp3_buf_len(&ebuf));
+
+ CU_ASSERT(nread == (nghttp3_ssize)nghttp3_buf_len(&ebuf));
+ CU_ASSERT(2 == dec.ctx.next_absidx);
+ CU_ASSERT(0 == dec.ctx.dtable_size);
+ CU_ASSERT(0 == dec.ctx.max_dtable_capacity);
+
+ decode_header_block(&dec, &pbuf, &rbuf, 16, mem);
+ nghttp3_buf_reset(&pbuf);
+ nghttp3_buf_reset(&rbuf);
+ nghttp3_buf_reset(&ebuf);
+
+ nghttp3_qpack_decoder_free(&dec);
+ nghttp3_qpack_encoder_free(&enc);
+
+ /* Check that minimum size is emitted */
+ nghttp3_qpack_encoder_init(&enc, 4096, mem);
+ nghttp3_qpack_encoder_set_max_blocked_streams(&enc, 1);
+ nghttp3_qpack_encoder_set_max_dtable_capacity(&enc, 4096);
+ nghttp3_qpack_decoder_init(&dec, 4096, 1, mem);
+
+ rv = nghttp3_qpack_encoder_encode(&enc, &pbuf, &rbuf, &ebuf, 0, nva1,
+ nghttp3_arraylen(nva1));
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(1 == enc.ctx.next_absidx);
+ CU_ASSERT(strlen("date") + strlen("bar1") + NGHTTP3_QPACK_ENTRY_OVERHEAD ==
+ enc.ctx.dtable_size);
+
+ nread = nghttp3_qpack_decoder_read_encoder(&dec, ebuf.pos,
+ nghttp3_buf_len(&ebuf));
+
+ CU_ASSERT(nread == (nghttp3_ssize)nghttp3_buf_len(&ebuf));
+ CU_ASSERT(1 == dec.ctx.next_absidx);
+ CU_ASSERT(strlen("date") + strlen("bar1") + NGHTTP3_QPACK_ENTRY_OVERHEAD ==
+ dec.ctx.dtable_size);
+ CU_ASSERT(4096 == dec.ctx.max_dtable_capacity);
+
+ decode_header_block(&dec, &pbuf, &rbuf, 0, mem);
+ nghttp3_buf_reset(&pbuf);
+ nghttp3_buf_reset(&rbuf);
+ nghttp3_buf_reset(&ebuf);
+
+ nghttp3_qpack_encoder_set_max_dtable_capacity(&enc, 0);
+ nghttp3_qpack_encoder_set_max_dtable_capacity(&enc, 1024);
+
+ CU_ASSERT(0 == enc.min_dtable_update);
+ CU_ASSERT(1024 == enc.last_max_dtable_update);
+
+ nghttp3_qpack_encoder_ack_header(&enc, 0);
+
+ rv = nghttp3_qpack_encoder_encode(&enc, &pbuf, &rbuf, &ebuf, 4, nva1,
+ nghttp3_arraylen(nva1));
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(2 == enc.ctx.next_absidx);
+ CU_ASSERT(strlen("date") + strlen("bar1") + NGHTTP3_QPACK_ENTRY_OVERHEAD ==
+ enc.ctx.dtable_size);
+ CU_ASSERT(SIZE_MAX == enc.min_dtable_update);
+ CU_ASSERT(1024 == enc.last_max_dtable_update);
+ CU_ASSERT(1024 == enc.ctx.max_dtable_capacity);
+
+ nread = nghttp3_qpack_decoder_read_encoder(&dec, ebuf.pos,
+ nghttp3_buf_len(&ebuf));
+
+ CU_ASSERT(nread == (nghttp3_ssize)nghttp3_buf_len(&ebuf));
+ CU_ASSERT(2 == dec.ctx.next_absidx);
+ CU_ASSERT(strlen("date") + strlen("bar1") + NGHTTP3_QPACK_ENTRY_OVERHEAD ==
+ dec.ctx.dtable_size);
+ CU_ASSERT(1024 == dec.ctx.max_dtable_capacity);
+
+ decode_header_block(&dec, &pbuf, &rbuf, 4, mem);
+ nghttp3_buf_reset(&pbuf);
+ nghttp3_buf_reset(&rbuf);
+ nghttp3_buf_reset(&ebuf);
+
+ nghttp3_qpack_decoder_free(&dec);
+ nghttp3_qpack_encoder_free(&enc);
+ nghttp3_buf_free(&ebuf, mem);
+ nghttp3_buf_free(&rbuf, mem);
+ nghttp3_buf_free(&pbuf, mem);
+}
+
+void test_nghttp3_qpack_decoder_feedback(void) {
+ const nghttp3_mem *mem = nghttp3_mem_default();
+ nghttp3_qpack_encoder enc;
+ nghttp3_qpack_decoder dec;
+ nghttp3_buf pbuf1, rbuf1, pbuf2, rbuf2, pbuf3, rbuf3, ebuf, dbuf;
+ const nghttp3_nv nva1[] = {
+ MAKE_NV(":path", "/"),
+ MAKE_NV("date", "bar1"),
+ };
+ const nghttp3_nv nva2[] = {
+ MAKE_NV(":path", "/"),
+ MAKE_NV("vary", "bar2"),
+ };
+ const nghttp3_nv nva3[] = {
+ MAKE_NV(":path", "/"),
+ MAKE_NV("link", "bar3"),
+ };
+ int rv;
+ nghttp3_ssize nread;
+
+ nghttp3_buf_init(&pbuf1);
+ nghttp3_buf_init(&rbuf1);
+ nghttp3_buf_init(&pbuf2);
+ nghttp3_buf_init(&rbuf2);
+ nghttp3_buf_init(&pbuf3);
+ nghttp3_buf_init(&rbuf3);
+ nghttp3_buf_init(&ebuf);
+ nghttp3_buf_init(&dbuf);
+
+ nghttp3_buf_reserve(&dbuf, 4096, mem);
+
+ rv = nghttp3_qpack_encoder_init(&enc, 4096, mem);
+
+ CU_ASSERT(0 == rv);
+
+ nghttp3_qpack_encoder_set_max_blocked_streams(&enc, 2);
+
+ nghttp3_qpack_encoder_set_max_dtable_capacity(&enc, 4096);
+
+ rv = nghttp3_qpack_decoder_init(&dec, 4096, 2, mem);
+
+ CU_ASSERT(0 == rv);
+
+ rv = nghttp3_qpack_encoder_encode(&enc, &pbuf1, &rbuf1, &ebuf, 0, nva1,
+ nghttp3_arraylen(nva1));
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(1 == nghttp3_qpack_encoder_get_num_blocked_streams(&enc));
+
+ rv = nghttp3_qpack_encoder_encode(&enc, &pbuf2, &rbuf2, &ebuf, 4, nva2,
+ nghttp3_arraylen(nva2));
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(2 == nghttp3_qpack_encoder_get_num_blocked_streams(&enc));
+
+ nread = nghttp3_qpack_decoder_read_encoder(&dec, ebuf.pos,
+ nghttp3_buf_len(&ebuf));
+
+ CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&ebuf) == nread);
+
+ /* Process stream 4 first */
+ decode_header_block(&dec, &pbuf2, &rbuf2, 4, mem);
+
+ CU_ASSERT(2 == dec.written_icnt);
+
+ nghttp3_qpack_decoder_write_decoder(&dec, &dbuf);
+
+ nread = nghttp3_qpack_encoder_read_decoder(&enc, dbuf.pos,
+ nghttp3_buf_len(&dbuf));
+
+ CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&dbuf) == nread);
+ /* This will unblock all streams because higher insert count is
+ acknowledged. */
+ CU_ASSERT(0 == nghttp3_qpack_encoder_get_num_blocked_streams(&enc));
+ CU_ASSERT(1 == nghttp3_map_size(&enc.streams));
+ CU_ASSERT(1 == nghttp3_pq_size(&enc.min_cnts));
+ CU_ASSERT(0 == nghttp3_ksl_len(&enc.blocked_streams));
+ CU_ASSERT(2 == enc.krcnt);
+
+ /* Process stream 0 */
+ decode_header_block(&dec, &pbuf1, &rbuf1, 0, mem);
+ nghttp3_buf_reset(&dbuf);
+ nghttp3_qpack_decoder_write_decoder(&dec, &dbuf);
+
+ nread = nghttp3_qpack_encoder_read_decoder(&enc, dbuf.pos,
+ nghttp3_buf_len(&dbuf));
+
+ CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&dbuf) == nread);
+ CU_ASSERT(0 == nghttp3_map_size(&enc.streams));
+ CU_ASSERT(0 == nghttp3_pq_size(&enc.min_cnts));
+ CU_ASSERT(2 == enc.krcnt);
+
+ /* Encode another header, and then read encoder stream only. Write
+ decoder stream. */
+ nghttp3_buf_reset(&ebuf);
+ rv = nghttp3_qpack_encoder_encode(&enc, &pbuf3, &rbuf3, &ebuf, 8, nva3,
+ nghttp3_arraylen(nva3));
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(1 == nghttp3_qpack_encoder_get_num_blocked_streams(&enc));
+
+ nread = nghttp3_qpack_decoder_read_encoder(&dec, ebuf.pos,
+ nghttp3_buf_len(&ebuf));
+
+ CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&ebuf) == nread);
+
+ nghttp3_buf_reset(&dbuf);
+ nghttp3_qpack_decoder_write_decoder(&dec, &dbuf);
+
+ CU_ASSERT(nghttp3_buf_len(&dbuf) > 0);
+ CU_ASSERT(3 == dec.written_icnt);
+
+ nread = nghttp3_qpack_encoder_read_decoder(&enc, dbuf.pos,
+ nghttp3_buf_len(&dbuf));
+
+ CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&dbuf) == nread);
+ CU_ASSERT(0 == nghttp3_qpack_encoder_get_num_blocked_streams(&enc));
+ CU_ASSERT(1 == nghttp3_map_size(&enc.streams));
+ CU_ASSERT(3 == enc.krcnt);
+
+ /* Cancel stream 8 */
+ rv = nghttp3_qpack_decoder_cancel_stream(&dec, 8);
+
+ CU_ASSERT(0 == rv);
+
+ nghttp3_buf_reset(&dbuf);
+ nghttp3_qpack_decoder_write_decoder(&dec, &dbuf);
+
+ CU_ASSERT(nghttp3_buf_len(&dbuf) > 0);
+
+ nread = nghttp3_qpack_encoder_read_decoder(&enc, dbuf.pos,
+ nghttp3_buf_len(&dbuf));
+
+ CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&dbuf) == nread);
+ CU_ASSERT(0 == nghttp3_qpack_encoder_get_num_blocked_streams(&enc));
+ CU_ASSERT(0 == nghttp3_ksl_len(&enc.blocked_streams));
+ CU_ASSERT(0 == nghttp3_pq_size(&enc.min_cnts));
+ CU_ASSERT(0 == nghttp3_map_size(&enc.streams));
+
+ nghttp3_qpack_decoder_free(&dec);
+ nghttp3_qpack_encoder_free(&enc);
+ nghttp3_buf_free(&dbuf, mem);
+ nghttp3_buf_free(&ebuf, mem);
+ nghttp3_buf_free(&rbuf3, mem);
+ nghttp3_buf_free(&pbuf3, mem);
+ nghttp3_buf_free(&rbuf2, mem);
+ nghttp3_buf_free(&pbuf2, mem);
+ nghttp3_buf_free(&rbuf1, mem);
+ nghttp3_buf_free(&pbuf1, mem);
+}
+
+void test_nghttp3_qpack_decoder_stream_overflow(void) {
+ const nghttp3_mem *mem = nghttp3_mem_default();
+ nghttp3_qpack_decoder dec;
+ size_t i;
+ int rv;
+
+ nghttp3_qpack_decoder_init(&dec, 4096, 0, mem);
+
+ for (i = 0;; ++i) {
+ rv = nghttp3_qpack_decoder_cancel_stream(&dec, (int64_t)i);
+ if (rv == NGHTTP3_ERR_QPACK_FATAL) {
+ break;
+ }
+ }
+
+ nghttp3_qpack_decoder_free(&dec);
+}
+
+void test_nghttp3_qpack_huffman(void) {
+ size_t i, j;
+ uint8_t raw[100], ebuf[4096], dbuf[4096];
+ uint8_t *end;
+ nghttp3_qpack_huffman_decode_context ctx;
+ nghttp3_ssize nwrite;
+
+ srand(1000000007);
+
+ for (i = 0; i < 100000; ++i) {
+ for (j = 0; j < sizeof(raw); ++j) {
+ raw[j] = (uint8_t)round(((double)rand() / RAND_MAX * 255));
+ }
+ end = nghttp3_qpack_huffman_encode(ebuf, raw, sizeof(raw));
+
+ nghttp3_qpack_huffman_decode_context_init(&ctx);
+ nwrite =
+ nghttp3_qpack_huffman_decode(&ctx, dbuf, ebuf, (size_t)(end - ebuf), 1);
+ if (nwrite <= 0) {
+ CU_ASSERT(nwrite > 0);
+ continue;
+ }
+ CU_ASSERT((sizeof(raw) == (size_t)nwrite));
+ CU_ASSERT(0 == memcmp(raw, dbuf, sizeof(raw)));
+ }
+}
+
+void test_nghttp3_qpack_huffman_decode_failure_state(void) {
+ nghttp3_qpack_huffman_decode_context ctx;
+ const uint8_t data[] = {0xff, 0xff, 0xff, 0xff};
+ uint8_t buf[4096];
+ nghttp3_ssize nwrite;
+
+ nghttp3_qpack_huffman_decode_context_init(&ctx);
+ nwrite = nghttp3_qpack_huffman_decode(&ctx, buf, data, sizeof(data) - 1, 0);
+
+ CU_ASSERT(0 == nwrite);
+ CU_ASSERT(!nghttp3_qpack_huffman_decode_failure_state(&ctx));
+
+ nwrite = nghttp3_qpack_huffman_decode(&ctx, buf, data, 1, 0);
+
+ CU_ASSERT(0 == nwrite);
+ CU_ASSERT(nghttp3_qpack_huffman_decode_failure_state(&ctx));
+}
+
+void test_nghttp3_qpack_decoder_reconstruct_ricnt(void) {
+ const nghttp3_mem *mem = nghttp3_mem_default();
+ nghttp3_qpack_decoder dec;
+ uint64_t ricnt;
+ int rv;
+
+ rv = nghttp3_qpack_decoder_init(&dec, 100, 1, mem);
+
+ CU_ASSERT(0 == rv);
+
+ dec.ctx.next_absidx = 10;
+
+ rv = nghttp3_qpack_decoder_reconstruct_ricnt(&dec, &ricnt, 3);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(8 == ricnt);
+
+ nghttp3_qpack_decoder_free(&dec);
+}
diff --git a/tests/nghttp3_qpack_test.h b/tests/nghttp3_qpack_test.h
new file mode 100644
index 0000000..8f3240c
--- /dev/null
+++ b/tests/nghttp3_qpack_test.h
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+#ifndef NGTCP2_QPACK_TEST_H
+#define NGTCP2_QPACK_TEST_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+void test_nghttp3_qpack_encoder_encode(void);
+void test_nghttp3_qpack_encoder_still_blocked(void);
+void test_nghttp3_qpack_encoder_set_dtable_cap(void);
+void test_nghttp3_qpack_decoder_feedback(void);
+void test_nghttp3_qpack_decoder_stream_overflow(void);
+void test_nghttp3_qpack_huffman(void);
+void test_nghttp3_qpack_huffman_decode_failure_state(void);
+void test_nghttp3_qpack_decoder_reconstruct_ricnt(void);
+
+#endif /* NGTCP2_QPCK_TEST_H */
diff --git a/tests/nghttp3_test_helper.c b/tests/nghttp3_test_helper.c
new file mode 100644
index 0000000..93ebfec
--- /dev/null
+++ b/tests/nghttp3_test_helper.c
@@ -0,0 +1,139 @@
+/*
+ * 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_test_helper.h"
+
+#include <string.h>
+#include <assert.h>
+
+#include "nghttp3_str.h"
+#include "nghttp3_conv.h"
+
+void nghttp3_write_frame(nghttp3_buf *dest, nghttp3_frame *fr) {
+ switch (fr->hd.type) {
+ case NGHTTP3_FRAME_SETTINGS:
+ nghttp3_frame_write_settings_len(&fr->hd.length, &fr->settings);
+ dest->last = nghttp3_frame_write_settings(dest->last, &fr->settings);
+ break;
+ case NGHTTP3_FRAME_GOAWAY:
+ nghttp3_frame_write_goaway_len(&fr->hd.length, &fr->goaway);
+ dest->last = nghttp3_frame_write_goaway(dest->last, &fr->goaway);
+ break;
+ case NGHTTP3_FRAME_PRIORITY_UPDATE:
+ case NGHTTP3_FRAME_PRIORITY_UPDATE_PUSH_ID:
+ nghttp3_frame_write_priority_update_len(&fr->hd.length,
+ &fr->priority_update);
+ dest->last =
+ nghttp3_frame_write_priority_update(dest->last, &fr->priority_update);
+ break;
+ default:
+ assert(0);
+ }
+}
+
+void nghttp3_write_frame_qpack(nghttp3_buf *dest, nghttp3_qpack_encoder *qenc,
+ int64_t stream_id, nghttp3_frame *fr) {
+ int rv;
+ const nghttp3_nv *nva;
+ size_t nvlen;
+ nghttp3_buf pbuf, rbuf, ebuf;
+ const nghttp3_mem *mem = nghttp3_mem_default();
+ (void)rv;
+
+ switch (fr->hd.type) {
+ case NGHTTP3_FRAME_HEADERS:
+ nva = fr->headers.nva;
+ nvlen = fr->headers.nvlen;
+ break;
+ default:
+ assert(0);
+ abort();
+ }
+
+ nghttp3_buf_init(&pbuf);
+ nghttp3_buf_init(&rbuf);
+ nghttp3_buf_init(&ebuf);
+
+ rv = nghttp3_qpack_encoder_encode(qenc, &pbuf, &rbuf, &ebuf, stream_id, nva,
+ nvlen);
+ assert(0 == rv);
+ assert(0 == nghttp3_buf_len(&ebuf));
+
+ fr->hd.length = (int64_t)(nghttp3_buf_len(&pbuf) + nghttp3_buf_len(&rbuf));
+
+ dest->last = nghttp3_frame_write_hd(dest->last, &fr->hd);
+ dest->last = nghttp3_cpymem(dest->last, pbuf.pos, nghttp3_buf_len(&pbuf));
+ dest->last = nghttp3_cpymem(dest->last, rbuf.pos, nghttp3_buf_len(&rbuf));
+
+ nghttp3_buf_free(&rbuf, mem);
+ nghttp3_buf_free(&pbuf, mem);
+}
+
+void nghttp3_write_frame_qpack_dyn(nghttp3_buf *dest, nghttp3_buf *ebuf,
+ nghttp3_qpack_encoder *qenc,
+ int64_t stream_id, nghttp3_frame *fr) {
+ int rv;
+ const nghttp3_nv *nva;
+ size_t nvlen;
+ nghttp3_buf pbuf, rbuf;
+ const nghttp3_mem *mem = nghttp3_mem_default();
+ (void)rv;
+
+ switch (fr->hd.type) {
+ case NGHTTP3_FRAME_HEADERS:
+ nva = fr->headers.nva;
+ nvlen = fr->headers.nvlen;
+ break;
+ default:
+ assert(0);
+ abort();
+ }
+
+ nghttp3_buf_init(&pbuf);
+ nghttp3_buf_init(&rbuf);
+
+ rv = nghttp3_qpack_encoder_encode(qenc, &pbuf, &rbuf, ebuf, stream_id, nva,
+ nvlen);
+ assert(0 == rv);
+
+ fr->hd.length = (int64_t)(nghttp3_buf_len(&pbuf) + nghttp3_buf_len(&rbuf));
+
+ dest->last = nghttp3_frame_write_hd(dest->last, &fr->hd);
+ dest->last = nghttp3_cpymem(dest->last, pbuf.pos, nghttp3_buf_len(&pbuf));
+ dest->last = nghttp3_cpymem(dest->last, rbuf.pos, nghttp3_buf_len(&rbuf));
+
+ nghttp3_buf_free(&rbuf, mem);
+ nghttp3_buf_free(&pbuf, mem);
+}
+
+void nghttp3_write_frame_data(nghttp3_buf *dest, size_t len) {
+ nghttp3_frame_data fr;
+
+ fr.hd.type = NGHTTP3_FRAME_DATA;
+ fr.hd.length = (int64_t)len;
+
+ dest->last = nghttp3_frame_write_hd(dest->last, &fr.hd);
+ memset(dest->last, 0, len);
+ dest->last += len;
+}
diff --git a/tests/nghttp3_test_helper.h b/tests/nghttp3_test_helper.h
new file mode 100644
index 0000000..6f6516b
--- /dev/null
+++ b/tests/nghttp3_test_helper.h
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+#ifndef NGHTTP3_TEST_HELPER
+#define NGHTTP3_TEST_HELPER
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "nghttp3_buf.h"
+#include "nghttp3_frame.h"
+#include "nghttp3_qpack.h"
+
+#define MAKE_NV(NAME, VALUE) \
+ { \
+ (uint8_t *)(NAME), (uint8_t *)(VALUE), sizeof((NAME)) - 1, \
+ sizeof((VALUE)) - 1, NGHTTP3_NV_FLAG_NONE \
+ }
+
+/*
+ * nghttp3_write_frame writes |fr| to |dest|. This function
+ * calculates the payload length and assigns it to fr->hd.length;
+ */
+void nghttp3_write_frame(nghttp3_buf *dest, nghttp3_frame *fr);
+
+/*
+ * nghttp3_write_frame_qpack writes |fr| to |dest|. |fr| is supposed
+ * to be a frame which uses QPACK encoder |qenc|. |qenc| must be
+ * configured so that it does not use dynamic table. This function
+ * calculates the payload length and assigns it to fr->hd.length;
+ */
+void nghttp3_write_frame_qpack(nghttp3_buf *dest, nghttp3_qpack_encoder *qenc,
+ int64_t stream_id, nghttp3_frame *fr);
+
+/*
+ * nghttp3_write_frame_qpack_dyn is similar to
+ * nghttp3_write_frame_qpack but it can use dynamic table. The it
+ * will write encoder stream to |ebuf|.
+ */
+void nghttp3_write_frame_qpack_dyn(nghttp3_buf *dest, nghttp3_buf *ebuf,
+ nghttp3_qpack_encoder *qenc,
+ int64_t stream_id, nghttp3_frame *fr);
+
+/*
+ * nghttp3_write_frame_data writes DATA frame which has |len| bytes of
+ * payload.
+ */
+void nghttp3_write_frame_data(nghttp3_buf *dest, size_t len);
+
+#endif /* NGHTTP3_TEST_HELPER */
diff --git a/tests/nghttp3_tnode_test.c b/tests/nghttp3_tnode_test.c
new file mode 100644
index 0000000..831a7d3
--- /dev/null
+++ b/tests/nghttp3_tnode_test.c
@@ -0,0 +1,164 @@
+/*
+ * 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_tnode_test.h"
+
+#include <CUnit/CUnit.h>
+
+#include "nghttp3_tnode.h"
+#include "nghttp3_macro.h"
+#include "nghttp3_test_helper.h"
+
+static int cycle_less(const nghttp3_pq_entry *lhsx,
+ const nghttp3_pq_entry *rhsx) {
+ const nghttp3_tnode *lhs = nghttp3_struct_of(lhsx, nghttp3_tnode, pe);
+ const nghttp3_tnode *rhs = nghttp3_struct_of(rhsx, nghttp3_tnode, pe);
+
+ if (lhs->cycle == rhs->cycle) {
+ return lhs->id < rhs->id;
+ }
+
+ return rhs->cycle - lhs->cycle <= NGHTTP3_TNODE_MAX_CYCLE_GAP;
+}
+
+void test_nghttp3_tnode_schedule(void) {
+ const nghttp3_mem *mem = nghttp3_mem_default();
+ nghttp3_tnode node, node2;
+ nghttp3_pq pq;
+ int rv;
+ nghttp3_pq_entry *ent;
+ nghttp3_tnode *p;
+
+ /* Schedule node with incremental enabled */
+ nghttp3_tnode_init(&node, 0, (1 << 7) | NGHTTP3_DEFAULT_URGENCY);
+
+ nghttp3_pq_init(&pq, cycle_less, mem);
+
+ rv = nghttp3_tnode_schedule(&node, &pq, 0);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(0 == node.cycle);
+
+ /* Schedule another node */
+ nghttp3_tnode_init(&node2, 1, (1 << 7) | NGHTTP3_DEFAULT_URGENCY);
+
+ rv = nghttp3_tnode_schedule(&node2, &pq, 0);
+
+ CU_ASSERT(0 == rv);
+
+ /* Rescheduling node with nwrite > 0 */
+
+ rv = nghttp3_tnode_schedule(&node, &pq, 1000);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(1 == node.cycle);
+
+ /* Rescheduling node with nwrite == 0 */
+
+ rv = nghttp3_tnode_schedule(&node, &pq, 0);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(1 == node.cycle);
+
+ nghttp3_pq_free(&pq);
+
+ /* Schedule node without incremental */
+ nghttp3_tnode_init(&node, 0, NGHTTP3_DEFAULT_URGENCY);
+
+ nghttp3_pq_init(&pq, cycle_less, mem);
+
+ rv = nghttp3_tnode_schedule(&node, &pq, 0);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(0 == node.cycle);
+
+ /* Schedule another node */
+ nghttp3_tnode_init(&node2, 1, NGHTTP3_DEFAULT_URGENCY);
+
+ rv = nghttp3_tnode_schedule(&node2, &pq, 0);
+
+ CU_ASSERT(0 == rv);
+
+ /* Rescheduling node with nwrite > 0 */
+
+ rv = nghttp3_tnode_schedule(&node, &pq, 1000);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(0 == node.cycle);
+
+ /* Rescheduling node with nwrit == 0 */
+
+ rv = nghttp3_tnode_schedule(&node, &pq, 0);
+
+ CU_ASSERT(0 == rv);
+ CU_ASSERT(0 == node.cycle);
+
+ nghttp3_pq_free(&pq);
+
+ /* Stream with lower stream ID takes precedence */
+ nghttp3_pq_init(&pq, cycle_less, mem);
+
+ nghttp3_tnode_init(&node2, 1, NGHTTP3_DEFAULT_URGENCY);
+
+ rv = nghttp3_tnode_schedule(&node2, &pq, 0);
+
+ CU_ASSERT(0 == rv);
+
+ nghttp3_tnode_init(&node, 0, NGHTTP3_DEFAULT_URGENCY);
+
+ rv = nghttp3_tnode_schedule(&node, &pq, 0);
+
+ CU_ASSERT(0 == rv);
+
+ ent = nghttp3_pq_top(&pq);
+
+ p = nghttp3_struct_of(ent, nghttp3_tnode, pe);
+
+ CU_ASSERT(0 == p->id);
+
+ nghttp3_pq_free(&pq);
+
+ /* Check the same reversing push order */
+ nghttp3_pq_init(&pq, cycle_less, mem);
+
+ nghttp3_tnode_init(&node, 0, NGHTTP3_DEFAULT_URGENCY);
+
+ rv = nghttp3_tnode_schedule(&node, &pq, 0);
+
+ CU_ASSERT(0 == rv);
+
+ nghttp3_tnode_init(&node2, 1, NGHTTP3_DEFAULT_URGENCY);
+
+ rv = nghttp3_tnode_schedule(&node2, &pq, 0);
+
+ CU_ASSERT(0 == rv);
+
+ ent = nghttp3_pq_top(&pq);
+
+ p = nghttp3_struct_of(ent, nghttp3_tnode, pe);
+
+ CU_ASSERT(0 == p->id);
+
+ nghttp3_pq_free(&pq);
+}
diff --git a/tests/nghttp3_tnode_test.h b/tests/nghttp3_tnode_test.h
new file mode 100644
index 0000000..7e4db02
--- /dev/null
+++ b/tests/nghttp3_tnode_test.h
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+#ifndef NGTCP2_TNODE_TEST_H
+#define NGTCP2_TNODE_TEST_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+void test_nghttp3_tnode_schedule(void);
+
+#endif /* NGTCP2_TNODE_TEST_H */