diff options
Diffstat (limited to 'tests')
-rw-r--r-- | tests/.gitignore | 1 | ||||
-rw-r--r-- | tests/CMakeLists.txt | 57 | ||||
-rw-r--r-- | tests/Makefile.am | 65 | ||||
-rw-r--r-- | tests/main.c | 146 | ||||
-rw-r--r-- | tests/nghttp3_conn_test.c | 3448 | ||||
-rw-r--r-- | tests/nghttp3_conn_test.h | 58 | ||||
-rw-r--r-- | tests/nghttp3_conv_test.c | 51 | ||||
-rw-r--r-- | tests/nghttp3_conv_test.h | 34 | ||||
-rw-r--r-- | tests/nghttp3_http_test.c | 676 | ||||
-rw-r--r-- | tests/nghttp3_http_test.h | 37 | ||||
-rw-r--r-- | tests/nghttp3_qpack_test.c | 779 | ||||
-rw-r--r-- | tests/nghttp3_qpack_test.h | 41 | ||||
-rw-r--r-- | tests/nghttp3_test_helper.c | 139 | ||||
-rw-r--r-- | tests/nghttp3_test_helper.h | 72 | ||||
-rw-r--r-- | tests/nghttp3_tnode_test.c | 164 | ||||
-rw-r--r-- | tests/nghttp3_tnode_test.h | 34 |
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 */ |